blob: 952a106ebb19d54125869458e403a0d2171602ce [file] [log] [blame]
/*
* Copyright(c) 2009 Dialog Semiconductor Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* rtc-da9052.c: RTC driver for DA9052
*/
#include <linux/platform_device.h>
#include <linux/rtc.h>
#include <linux/mfd/da9052/da9052.h>
#include <linux/mfd/da9052/reg.h>
#include <linux/mfd/da9052/rtc.h>
#define DRIVER_NAME "da9052-rtc"
#define ENABLE 1
#define DISABLE 0
struct da9052_rtc {
struct rtc_device *rtc;
struct da9052 *da9052;
struct da9052_eh_nb eh_data;
unsigned char is_min_alarm;
unsigned char enable_tick_alarm;
unsigned char enable_clk_buffer;
unsigned char set_osc_trim_freq;
};
static int da9052_rtc_enable_alarm(struct da9052 *da9052, unsigned char flag);
void da9052_rtc_notifier(struct da9052_eh_nb *eh_data, unsigned int event)
{
struct da9052_rtc *rtc =
container_of(eh_data, struct da9052_rtc, eh_data);
struct da9052_ssc_msg msg;
unsigned int ret;
/* Check the alarm type - TIMER or TICK */
msg.addr = DA9052_ALARMMI_REG;
da9052_lock(rtc->da9052);
ret = rtc->da9052->read(rtc->da9052, &msg);
if (ret != 0) {
da9052_unlock(rtc->da9052);
return;
}
da9052_unlock(rtc->da9052);
if (msg.data & DA9052_ALARMMI_ALARMTYPE) {
da9052_rtc_enable_alarm(rtc->da9052, 0);
printk(KERN_INFO "RTC: TIMER ALARM\n");
} else {
kobject_uevent(&rtc->rtc->dev.kobj, KOBJ_CHANGE);
printk(KERN_INFO "RTC: TICK ALARM\n");
}
}
static int da9052_rtc_validate_parameters(struct rtc_time *rtc_tm)
{
if (rtc_tm->tm_sec > DA9052_RTC_SECONDS_LIMIT)
return DA9052_RTC_INVALID_SECONDS;
if (rtc_tm->tm_min > DA9052_RTC_MINUTES_LIMIT)
return DA9052_RTC_INVALID_MINUTES;
if (rtc_tm->tm_hour > DA9052_RTC_HOURS_LIMIT)
return DA9052_RTC_INVALID_HOURS;
if (rtc_tm->tm_mday == 0)
return DA9052_RTC_INVALID_DAYS;
if ((rtc_tm->tm_mon > DA9052_RTC_MONTHS_LIMIT) ||
(rtc_tm->tm_mon == 0))
return DA9052_RTC_INVALID_MONTHS;
if (rtc_tm->tm_year > DA9052_RTC_YEARS_LIMIT)
return DA9052_RTC_INVALID_YEARS;
if ((rtc_tm->tm_mon == FEBRUARY)) {
if (((rtc_tm->tm_year % 4 == 0) &&
(rtc_tm->tm_year % 100 != 0)) ||
(rtc_tm->tm_year % 400 == 0)) {
if (rtc_tm->tm_mday > 29)
return DA9052_RTC_INVALID_DAYS;
} else if (rtc_tm->tm_mday > 28) {
return DA9052_RTC_INVALID_DAYS;
}
}
if (((rtc_tm->tm_mon == APRIL) || (rtc_tm->tm_mon == JUNE) ||
(rtc_tm->tm_mon == SEPTEMBER) || (rtc_tm->tm_mon == NOVEMBER))
&& (rtc_tm->tm_mday == 31)) {
return DA9052_RTC_INVALID_DAYS;
}
return 0;
}
static int da9052_rtc_settime(struct da9052 *da9052, struct rtc_time *rtc_tm)
{
struct da9052_ssc_msg msg_arr[6];
int validate_param = 0;
unsigned char loop_index = 0;
int ret = 0;
/* System compatability */
rtc_tm->tm_year -= 100;
rtc_tm->tm_mon += 1;
validate_param = da9052_rtc_validate_parameters(rtc_tm);
if (validate_param)
return validate_param;
msg_arr[loop_index].addr = DA9052_COUNTS_REG;
msg_arr[loop_index++].data = DA9052_COUNTS_MONITOR | rtc_tm->tm_sec;
msg_arr[loop_index].addr = DA9052_COUNTMI_REG;
msg_arr[loop_index].data = 0;
msg_arr[loop_index++].data = rtc_tm->tm_min;
msg_arr[loop_index].addr = DA9052_COUNTH_REG;
msg_arr[loop_index].data = 0;
msg_arr[loop_index++].data = rtc_tm->tm_hour;
msg_arr[loop_index].addr = DA9052_COUNTD_REG;
msg_arr[loop_index].data = 0;
msg_arr[loop_index++].data = rtc_tm->tm_mday;
msg_arr[loop_index].addr = DA9052_COUNTMO_REG;
msg_arr[loop_index].data = 0;
msg_arr[loop_index++].data = rtc_tm->tm_mon;
msg_arr[loop_index].addr = DA9052_COUNTY_REG;
msg_arr[loop_index].data = 0;
msg_arr[loop_index++].data = rtc_tm->tm_year;
da9052_lock(da9052);
ret = da9052->write_many(da9052, msg_arr, loop_index);
if (ret != 0) {
da9052_unlock(da9052);
return ret;
}
da9052_unlock(da9052);
return 0;
}
static int da9052_rtc_gettime(struct da9052 *da9052, struct rtc_time *rtc_tm)
{
struct da9052_ssc_msg msg[6];
unsigned char loop_index = 0;
int validate_param = 0;
int ret = 0;
msg[loop_index].data = 0;
msg[loop_index++].addr = DA9052_COUNTS_REG;
msg[loop_index].data = 0;
msg[loop_index++].addr = DA9052_COUNTMI_REG;
msg[loop_index].data = 0;
msg[loop_index++].addr = DA9052_COUNTH_REG;
msg[loop_index].data = 0;
msg[loop_index++].addr = DA9052_COUNTD_REG;
msg[loop_index].data = 0;
msg[loop_index++].addr = DA9052_COUNTMO_REG;
msg[loop_index].data = 0;
msg[loop_index++].addr = DA9052_COUNTY_REG;
da9052_lock(da9052);
ret = da9052->read_many(da9052, msg, loop_index);
if (ret != 0) {
da9052_unlock(da9052);
return ret;
}
da9052_unlock(da9052);
rtc_tm->tm_year = msg[--loop_index].data & DA9052_COUNTY_COUNTYEAR;
rtc_tm->tm_mon = msg[--loop_index].data & DA9052_COUNTMO_COUNTMONTH;
rtc_tm->tm_mday = msg[--loop_index].data & DA9052_COUNTD_COUNTDAY;
rtc_tm->tm_hour = msg[--loop_index].data & DA9052_COUNTH_COUNTHOUR;
rtc_tm->tm_min = msg[--loop_index].data & DA9052_COUNTMI_COUNTMIN;
rtc_tm->tm_sec = msg[--loop_index].data & DA9052_COUNTS_COUNTSEC;
validate_param = da9052_rtc_validate_parameters(rtc_tm);
if (validate_param)
return validate_param;
/* System compatability */
rtc_tm->tm_year += 100;
rtc_tm->tm_mon -= 1;
return 0;
}
static int da9052_alarm_gettime(struct da9052 *da9052, struct rtc_time *rtc_tm)
{
struct da9052_ssc_msg msg[5];
unsigned char loop_index = 0;
int validate_param = 0;
int ret = 0;
msg[loop_index].data = 0;
msg[loop_index++].addr = DA9052_ALARMMI_REG;
msg[loop_index].data = 0;
msg[loop_index++].addr = DA9052_ALARMH_REG;
msg[loop_index].data = 0;
msg[loop_index++].addr = DA9052_ALARMD_REG;
msg[loop_index].data = 0;
msg[loop_index++].addr = DA9052_ALARMMO_REG;
msg[loop_index].data = 0;
msg[loop_index++].addr = DA9052_ALARMY_REG;
da9052_lock(da9052);
ret = da9052->read_many(da9052, msg, loop_index);
if (ret != 0) {
da9052_unlock(da9052);
return ret;
}
da9052_unlock(da9052);
rtc_tm->tm_year = msg[--loop_index].data & DA9052_ALARMY_ALARMYEAR;
rtc_tm->tm_mon = msg[--loop_index].data & DA9052_ALARMMO_ALARMMONTH;
rtc_tm->tm_mday = msg[--loop_index].data & DA9052_ALARMD_ALARMDAY;
rtc_tm->tm_hour = msg[--loop_index].data & DA9052_ALARMH_ALARMHOUR;
rtc_tm->tm_min = msg[--loop_index].data & DA9052_ALARMMI_ALARMMIN;
validate_param = da9052_rtc_validate_parameters(rtc_tm);
if (validate_param)
return validate_param;
/* System compatability */
rtc_tm->tm_year += 100;
rtc_tm->tm_mon -= 1;
return 0;
}
static int da9052_alarm_settime(struct da9052 *da9052, struct rtc_time *rtc_tm)
{
struct da9052_ssc_msg msg_arr[5];
struct da9052_ssc_msg msg;
int validate_param = 0;
unsigned char loop_index = 0;
int ret = 0;
rtc_tm->tm_sec = 0;
/* System compatability */
rtc_tm->tm_year -= 100;
rtc_tm->tm_mon += 1;
validate_param = da9052_rtc_validate_parameters(rtc_tm);
if (validate_param)
return validate_param;
msg.addr = DA9052_ALARMMI_REG;
msg.data = 0;
da9052_lock(da9052);
ret = da9052->read(da9052, &msg);
if (ret != 0) {
da9052_unlock(da9052);
return ret;
}
msg.data = msg.data & ~(DA9052_ALARMMI_ALARMMIN);
msg.data |= rtc_tm->tm_min;
msg_arr[loop_index].addr = DA9052_ALARMMI_REG;
msg_arr[loop_index].data = 0;
msg_arr[loop_index++].data = msg.data;
msg_arr[loop_index].addr = DA9052_ALARMH_REG;
msg_arr[loop_index].data = 0;
msg_arr[loop_index++].data = rtc_tm->tm_hour;
msg_arr[loop_index].addr = DA9052_ALARMD_REG;
msg_arr[loop_index].data = 0;
msg_arr[loop_index++].data = rtc_tm->tm_mday;
msg_arr[loop_index].addr = DA9052_ALARMMO_REG;
msg_arr[loop_index].data = 0;
msg_arr[loop_index++].data = rtc_tm->tm_mon;
msg.addr = DA9052_ALARMY_REG;
msg.data = 0;
ret = da9052->read(da9052, &msg);
if (ret != 0) {
da9052_unlock(da9052);
return ret;
}
msg.data = msg.data & ~(DA9052_ALARMY_ALARMYEAR);
msg.data |= rtc_tm->tm_year;
msg_arr[loop_index].addr = DA9052_ALARMY_REG;
msg_arr[loop_index].data = 0;
msg_arr[loop_index++].data = msg.data;
ret = da9052->write_many(da9052, msg_arr, loop_index);
if (ret) {
da9052_unlock(da9052);
return ret;
}
da9052_unlock(da9052);
return 0;
}
static int da9052_rtc_get_alarm_status(struct da9052 *da9052)
{
struct da9052_ssc_msg msg;
int ret = 0;
msg.addr = DA9052_ALARMY_REG;
msg.data = 0;
da9052_lock(da9052);
ret = da9052->read(da9052, &msg);
if (ret != 0) {
da9052_unlock(da9052);
return ret;
}
da9052_unlock(da9052);
msg.data &= DA9052_ALARMY_ALARMON;
return (msg.data > 0) ? 1 : 0;
}
static int da9052_rtc_enable_alarm(struct da9052 *da9052, unsigned char flag)
{
struct da9052_ssc_msg msg;
int ret = 0;
msg.addr = DA9052_ALARMY_REG;
da9052_lock(da9052);
ret = da9052->read(da9052, &msg);
if (ret != 0) {
da9052_unlock(da9052);
return ret;
}
if (flag)
msg.data = msg.data | DA9052_ALARMY_ALARMON;
else
msg.data = msg.data & ~(DA9052_ALARMY_ALARMON);
ret = da9052->write(da9052, &msg);
if (ret != 0) {
da9052_unlock(da9052);
return ret;
}
da9052_unlock(da9052);
return 0;
}
static ssize_t da9052_rtc_mask_irq(struct da9052 *da9052)
{
unsigned char data = 0;
ssize_t ret = 0;
struct da9052_ssc_msg ssc_msg;
ssc_msg.addr = DA9052_IRQMASKA_REG;
ssc_msg.data = 0;
da9052_lock(da9052);
ret = da9052->read(da9052, &ssc_msg);
if (ret != 0) {
da9052_unlock(da9052);
return ret;
}
data = ret;
ssc_msg.data = data |= DA9052_IRQMASKA_MALRAM;
ret = da9052->write(da9052, &ssc_msg);
if (ret != 0) {
da9052_unlock(da9052);
return ret;
}
da9052_unlock(da9052);
return 0;
}
static ssize_t da9052_rtc_unmask_irq(struct da9052 *da9052)
{
unsigned char data = 0;
ssize_t ret = 0;
struct da9052_ssc_msg ssc_msg;
ssc_msg.addr = DA9052_IRQMASKA_REG;
ssc_msg.data = 0;
da9052_lock(da9052);
ret = da9052->read(da9052, &ssc_msg);
if (ret != 0) {
da9052_unlock(da9052);
return ret;
}
data = ret;
ssc_msg.data = data &= ~DA9052_IRQMASKA_MALRAM;
ret = da9052->write(da9052, &ssc_msg);
if (ret != 0) {
da9052_unlock(da9052);
return ret;
}
da9052_unlock(da9052);
return 0;
}
static int da9052_rtc_class_ops_gettime
(struct device *dev, struct rtc_time *rtc_tm)
{
int ret;
struct da9052 *da9052 = dev_get_drvdata(dev->parent);
ret = da9052_rtc_gettime(da9052, rtc_tm);
if (ret)
return ret;
return 0;
}
static int da9052_rtc_class_ops_settime(struct device *dev, struct rtc_time *tm)
{
int ret;
struct da9052 *da9052 = dev_get_drvdata(dev->parent);
ret = da9052_rtc_settime(da9052, tm);
return ret;
}
static int da9052_rtc_readalarm(struct device *dev, struct rtc_wkalrm *alrm)
{
int ret;
struct rtc_time *tm = &alrm->time;
struct da9052 *da9052 = dev_get_drvdata(dev->parent);
ret = da9052_alarm_gettime(da9052, tm);
if (ret)
return ret;
alrm->enabled = da9052_rtc_get_alarm_status(da9052);
return 0;
}
static int da9052_rtc_setalarm(struct device *dev, struct rtc_wkalrm *alrm)
{
int ret = 0;
struct rtc_time *tm = &alrm->time;
struct da9052 *da9052 = dev_get_drvdata(dev->parent);
ret = da9052_alarm_settime(da9052, tm);
if (ret)
return ret;
ret = da9052_rtc_enable_alarm(da9052, 1);
return ret;
}
static int da9052_rtc_update_irq_enable(struct device *dev,
unsigned int enabled)
{
struct da9052_rtc *priv = dev_get_drvdata(dev);
int ret = -ENODATA;
da9052_lock(priv->da9052);
ret = (enabled ? da9052_rtc_unmask_irq : da9052_rtc_mask_irq)
(priv->da9052);
da9052_unlock(priv->da9052);
return ret;
}
static int da9052_rtc_alarm_irq_enable(struct device *dev,
unsigned int enabled)
{
struct da9052_rtc *priv = dev_get_drvdata(dev);
if (enabled)
return da9052_rtc_enable_alarm(priv->da9052, enabled);
else
return da9052_rtc_enable_alarm(priv->da9052, enabled);
}
static const struct rtc_class_ops da9052_rtc_ops = {
.read_time = da9052_rtc_class_ops_gettime,
.set_time = da9052_rtc_class_ops_settime,
.read_alarm = da9052_rtc_readalarm,
.set_alarm = da9052_rtc_setalarm,
#if 0
.update_irq_enable = da9052_rtc_update_irq_enable,
.alarm_irq_enable = da9052_rtc_alarm_irq_enable,
#endif
};
static int __devinit da9052_rtc_probe(struct platform_device *pdev)
{
int ret;
struct da9052_rtc *priv;
struct da9052_ssc_msg ssc_msg;
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->da9052 = dev_get_drvdata(pdev->dev.parent);
platform_set_drvdata(pdev, priv);
/* Added to support sysfs wakealarm attribute */
pdev->dev.power.can_wakeup = 1;
/* Added to support sysfs wakealarm attribute */
/* Set the EH structure */
priv->eh_data.eve_type = ALARM_EVE;
priv->eh_data.call_back = &da9052_rtc_notifier;
ret = priv->da9052->register_event_notifier(priv->da9052,
&priv->eh_data);
if (ret)
goto err_register_alarm;
priv->is_min_alarm = 1;
priv->enable_tick_alarm = 1;
priv->enable_clk_buffer = 1;
priv->set_osc_trim_freq = 5;
/* Enable/Disable TICK Alarm */
/* Read ALARM YEAR register */
ssc_msg.addr = DA9052_ALARMY_REG;
ssc_msg.data = 0;
da9052_lock(priv->da9052);
ret = priv->da9052->read(priv->da9052, &ssc_msg);
if (ret != 0) {
da9052_unlock(priv->da9052);
goto err_ssc_comm;
}
if (priv->enable_tick_alarm)
ssc_msg.data = (ssc_msg.data | DA9052_ALARMY_TICKON);
else
ssc_msg.data =
((ssc_msg.data & ~(DA9052_ALARMY_TICKON)));
ret = priv->da9052->write(priv->da9052, &ssc_msg);
if (ret != 0) {
da9052_unlock(priv->da9052);
goto err_ssc_comm;
}
/* Set TICK Alarm to 1 minute or 1 sec */
/* Read ALARM MINUTES register */
ssc_msg.addr = DA9052_ALARMMI_REG;
ssc_msg.data = 0;
ret = priv->da9052->read(priv->da9052, &ssc_msg);
if (ret != 0) {
da9052_unlock(priv->da9052);
goto err_ssc_comm;
}
if (priv->is_min_alarm)
/* Set 1 minute tick type */
ssc_msg.data = (ssc_msg.data | DA9052_ALARMMI_TICKTYPE);
else
/* Set 1 sec tick type */
ssc_msg.data = (ssc_msg.data & ~(DA9052_ALARMMI_TICKTYPE));
ret = priv->da9052->write(priv->da9052, &ssc_msg);
if (ret != 0) {
da9052_unlock(priv->da9052);
goto err_ssc_comm;
}
/* Enable/Disable Clock buffer in Power Down Mode */
ssc_msg.addr = DA9052_PDDIS_REG;
ssc_msg.data = 0;
ret = priv->da9052->read(priv->da9052, &ssc_msg);
if (ret != 0) {
da9052_unlock(priv->da9052);
goto err_ssc_comm;
}
if (priv->enable_clk_buffer)
ssc_msg.data = (ssc_msg.data | DA9052_PDDIS_OUT32KPD);
else
ssc_msg.data = (ssc_msg.data & ~(DA9052_PDDIS_OUT32KPD));
ret = priv->da9052->write(priv->da9052, &ssc_msg);
if (ret != 0) {
da9052_unlock(priv->da9052);
goto err_ssc_comm;
}
/* Set clock trim frequency value */
ssc_msg.addr = DA9052_OSCTRIM_REG;
ssc_msg.data = priv->set_osc_trim_freq;
ret = priv->da9052->write(priv->da9052, &ssc_msg);
if (ret != 0) {
da9052_unlock(priv->da9052);
goto err_ssc_comm;
}
da9052_unlock(priv->da9052);
priv->rtc = rtc_device_register(pdev->name,
&pdev->dev, &da9052_rtc_ops, THIS_MODULE);
if (IS_ERR(priv->rtc)) {
ret = PTR_ERR(priv->rtc);
goto err_ssc_comm;
}
return 0;
err_ssc_comm:
priv->da9052->unregister_event_notifier
(priv->da9052, &priv->eh_data);
err_register_alarm:
platform_set_drvdata(pdev, NULL);
kfree(priv);
return ret;
}
static int __devexit da9052_rtc_remove(struct platform_device *pdev)
{
struct da9052_rtc *priv = platform_get_drvdata(pdev);
rtc_device_unregister(priv->rtc);
da9052_lock(priv->da9052);
priv->da9052->unregister_event_notifier(priv->da9052, &priv->eh_data);
da9052_unlock(priv->da9052);
platform_set_drvdata(pdev, NULL);
kfree(priv);
return 0;
}
static struct platform_driver da9052_rtc_driver = {
.probe = da9052_rtc_probe,
.remove = __devexit_p(da9052_rtc_remove),
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
},
};
static int __init da9052_rtc_init(void)
{
return platform_driver_register(&da9052_rtc_driver);
}
module_init(da9052_rtc_init);
static void __exit da9052_rtc_exit(void)
{
platform_driver_unregister(&da9052_rtc_driver);
}
module_exit(da9052_rtc_exit);
MODULE_AUTHOR("Dialog Semiconductor Ltd <dchen@diasemi.com>");
MODULE_DESCRIPTION("RTC driver for Dialog DA9052 PMIC");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:" DRIVER_NAME);