diff options
Diffstat (limited to 'drivers/rtc')
-rw-r--r-- | drivers/rtc/Kconfig | 54 | ||||
-rw-r--r-- | drivers/rtc/Makefile | 7 | ||||
-rw-r--r-- | drivers/rtc/rtc-da9052.c | 694 | ||||
-rw-r--r-- | drivers/rtc/rtc-imxdi.c | 581 | ||||
-rw-r--r-- | drivers/rtc/rtc-mc13892.c | 257 | ||||
-rw-r--r-- | drivers/rtc/rtc-mxc.c | 4 | ||||
-rw-r--r-- | drivers/rtc/rtc-mxc_v2.c | 754 | ||||
-rw-r--r-- | drivers/rtc/rtc-mxs.c | 322 |
8 files changed, 2670 insertions, 3 deletions
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 10ba12c8c5e0..9987e3765989 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -433,6 +433,14 @@ config RTC_DRV_CMOS This driver can also be built as a module. If so, the module will be called rtc-cmos. +config RTC_DRV_DA9052 + tristate "Dialog DA9052 RTC" + depends on PMIC_DA9052 + help + Say y here to support the RTC found on + Dialog Semiconductor DA9052 PMIC. + + config RTC_DRV_DS1216 tristate "Dallas DS1216" depends on SNI_RM @@ -818,6 +826,52 @@ config RTC_DRV_PXA This RTC driver uses PXA RTC registers available since pxa27x series (RDxR, RYxR) instead of legacy RCNR, RTAR. +config RTC_MXC + tristate "Freescale MXC Real Time Clock" + depends on ARCH_MXC + depends on RTC_CLASS + help + Support for Freescale RTC MXC + +config RTC_DRV_MXC_V2 + tristate "Freescale MXC Secure Real Time Clock" + depends on ARCH_MXC + depends on RTC_CLASS + help + Support for Freescale SRTC MXC + +config RTC_DRV_IMXDI + tristate "Freescale IMX DryIce Real Time Clock" + depends on ARCH_MXC + depends on RTC_CLASS + help + Support for Freescale IMX DryIce RTC + +config RTC_MC13892 + tristate "Freescale MC13892 Real Time Clock" + depends on ARCH_MXC && MXC_PMIC_MC13892 + depends on RTC_CLASS + help + Support for Freescale MC13892 RTC + +config RTC_DRV_STMP3XXX + tristate "Sigmatel STMP3xxx series SoC RTC" + depends on ARCH_STMP3XXX && RTC_CLASS + help + Say Y here to get support for the real-time clock peripheral + on Sigmatel STMP3xxx series SoCs (tested on STMP3700). + + This driver can also be build as a module. If so, the module + will be called rtc-stmp3xxx. + +config RTC_DRV_MXS + tristate "Freescale MXS series SoC RTC" + depends on ARCH_MXS && RTC_CLASS + help + Say Y here to get support for the real-time clock peripheral + on Freescale MXS series SoCs + This driver can also be build as a module. If so, the module + will be called rtc-mxs. config RTC_DRV_SUN4V bool "SUN4V Hypervisor RTC" diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index 5adbba7cf89c..e58c0868eb7e 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -94,3 +94,10 @@ obj-$(CONFIG_RTC_DRV_VR41XX) += rtc-vr41xx.o obj-$(CONFIG_RTC_DRV_WM831X) += rtc-wm831x.o obj-$(CONFIG_RTC_DRV_WM8350) += rtc-wm8350.o obj-$(CONFIG_RTC_DRV_X1205) += rtc-x1205.o +obj-$(CONFIG_RTC_MXC) += rtc-mxc.o +obj-$(CONFIG_RTC_DRV_MXC_V2) += rtc-mxc_v2.o +obj-$(CONFIG_RTC_DRV_IMXDI) += rtc-imxdi.o +obj-$(CONFIG_RTC_MC13892) += rtc-mc13892.o +obj-$(CONFIG_RTC_DRV_MXS) += rtc-mxs.o +obj-$(CONFIG_RTC_DRV_DA9052) += rtc-da9052.o + diff --git a/drivers/rtc/rtc-da9052.c b/drivers/rtc/rtc-da9052.c new file mode 100644 index 000000000000..8f4d26fbc23b --- /dev/null +++ b/drivers/rtc/rtc-da9052.c @@ -0,0 +1,694 @@ +/* + * 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); + + /* KPIT added to support sysfs wakealarm attribute */ + pdev->dev.power.can_wakeup = 1; + /* KPIT 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); diff --git a/drivers/rtc/rtc-imxdi.c b/drivers/rtc/rtc-imxdi.c new file mode 100644 index 000000000000..599c525a9f66 --- /dev/null +++ b/drivers/rtc/rtc-imxdi.c @@ -0,0 +1,581 @@ +/* + * Copyright 2008-2010 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/* based on rtc-mc13892.c */ + +/* + * This driver uses the 47-bit 32 kHz counter in the Freescale DryIce block + * to implement a Linux RTC. Times and alarms are truncated to seconds. + * Since the RTC framework performs API locking via rtc->ops_lock the + * only simultaneous accesses we need to deal with is updating DryIce + * registers while servicing an alarm. + * + * Note that reading the DSR (DryIce Status Register) automatically clears + * the WCF (Write Complete Flag). All DryIce writes are synchronized to the + * LP (Low Power) domain and set the WCF upon completion. Writes to the + * DIER (DryIce Interrupt Enable Register) are the only exception. These + * occur at normal bus speeds and do not set WCF. Periodic interrupts are + * not supported by the hardware. + */ + +/* #define DEBUG */ +/* #define DI_DEBUG_REGIO */ + +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/rtc.h> +#include <linux/workqueue.h> + +/* DryIce Register Definitions */ + +#define DTCMR 0x00 /* Time Counter MSB Reg */ +#define DTCLR 0x04 /* Time Counter LSB Reg */ + +#define DCAMR 0x08 /* Clock Alarm MSB Reg */ +#define DCALR 0x0c /* Clock Alarm LSB Reg */ +#define DCAMR_UNSET 0xFFFFFFFF /* doomsday - 1 sec */ + +#define DCR 0x10 /* Control Reg */ +#define DCR_TCE (1 << 3) /* Time Counter Enable */ + +#define DSR 0x14 /* Status Reg */ +#define DSR_WBF (1 << 10) /* Write Busy Flag */ +#define DSR_WNF (1 << 9) /* Write Next Flag */ +#define DSR_WCF (1 << 8) /* Write Complete Flag */ +#define DSR_WEF (1 << 7) /* Write Error Flag */ +#define DSR_CAF (1 << 4) /* Clock Alarm Flag */ +#define DSR_NVF (1 << 1) /* Non-Valid Flag */ +#define DSR_SVF (1 << 0) /* Security Violation Flag */ + +#define DIER 0x18 /* Interrupt Enable Reg */ +#define DIER_WNIE (1 << 9) /* Write Next Interrupt Enable */ +#define DIER_WCIE (1 << 8) /* Write Complete Interrupt Enable */ +#define DIER_WEIE (1 << 7) /* Write Error Interrupt Enable */ +#define DIER_CAIE (1 << 4) /* Clock Alarm Interrupt Enable */ + +#ifndef DI_DEBUG_REGIO +/* dryice read register */ +#define di_read(pdata, reg) __raw_readl((pdata)->ioaddr + (reg)) + +/* dryice write register */ +#define di_write(pdata, val, reg) __raw_writel((val), (pdata)->ioaddr + (reg)) +#else +/* dryice read register - debug version */ +static inline u32 di_read(struct rtc_drv_data *pdata, int reg) +{ + u32 val = __raw_readl(pdata->ioaddr + reg); + pr_info("di_read(0x%02x) = 0x%08x\n", reg, val); + return val; +} + +/* dryice write register - debug version */ +static inline void di_write(struct rtc_drv_data *pdata, u32 val, int reg) +{ + printk(KERN_INFO "di_write(0x%08x, 0x%02x)\n", val, reg); + __raw_writel(val, pdata->ioaddr + reg); +} +#endif + +/* + * dryice write register with wait and error handling. + * all registers, except for DIER, should use this method. + */ +#define di_write_wait_err(pdata, val, reg, rc, label) \ + do { \ + if (di_write_wait((pdata), (val), (reg))) { \ + rc = -EIO; \ + goto label; \ + } \ + } while (0) + +struct rtc_drv_data { + struct platform_device *pdev; /* pointer to platform dev */ + struct rtc_device *rtc; /* pointer to rtc struct */ + unsigned long baseaddr; /* physical bass address */ + void __iomem *ioaddr; /* virtual base address */ + int size; /* size of register region */ + int irq; /* dryice normal irq */ + struct clk *clk; /* dryice clock control */ + u32 dsr; /* copy of dsr reg from isr */ + spinlock_t irq_lock; /* irq resource lock */ + wait_queue_head_t write_wait; /* write-complete queue */ + struct mutex write_mutex; /* force reg writes to be sequential */ + struct work_struct work; /* schedule alarm work */ +}; + +/* + * enable a dryice interrupt + */ +static inline void di_int_enable(struct rtc_drv_data *pdata, u32 intr) +{ + unsigned long flags; + + spin_lock_irqsave(&pdata->irq_lock, flags); + di_write(pdata, di_read(pdata, DIER) | intr, DIER); + spin_unlock_irqrestore(&pdata->irq_lock, flags); +} + +/* + * disable a dryice interrupt + */ +static inline void di_int_disable(struct rtc_drv_data *pdata, u32 intr) +{ + unsigned long flags; + + spin_lock_irqsave(&pdata->irq_lock, flags); + di_write(pdata, di_read(pdata, DIER) & ~intr, DIER); + spin_unlock_irqrestore(&pdata->irq_lock, flags); +} + +/* + * This function attempts to clear the dryice write-error flag. + * + * A dryice write error is similar to a bus fault and should not occur in + * normal operation. Clearing the flag requires another write, so the root + * cause of the problem may need to be fixed before the flag can be cleared. + */ +static void clear_write_error(struct rtc_drv_data *pdata) +{ + int cnt; + + dev_warn(&pdata->pdev->dev, "WARNING: Register write error!\n"); + + for (;;) { + /* clear the write error flag */ + di_write(pdata, DSR_WEF, DSR); + + /* wait for it to take effect */ + for (cnt = 0; cnt < 100; cnt++) { + if ((di_read(pdata, DSR) & DSR_WEF) == 0) + return; + udelay(10); + } + dev_err(&pdata->pdev->dev, + "ERROR: Cannot clear write-error flag!\n"); + } +} + +/* + * Write a dryice register and wait until it completes. + * + * This function uses interrupts to determine when the + * write has completed. + */ +static int di_write_wait(struct rtc_drv_data *pdata, u32 val, int reg) +{ + int ret; + int rc = 0; + + /* serialize register writes */ + mutex_lock(&pdata->write_mutex); + + /* enable the write-complete interrupt */ + di_int_enable(pdata, DIER_WCIE); + + pdata->dsr = 0; + + /* do the register write */ + di_write(pdata, val, reg); + + /* wait for the write to finish */ + ret = wait_event_interruptible_timeout(pdata->write_wait, + pdata->dsr & (DSR_WCF | DSR_WEF), + 1 * HZ); + if (ret == 0) + dev_warn(&pdata->pdev->dev, "Write-wait timeout\n"); + + /* check for write error */ + if (pdata->dsr & DSR_WEF) { + clear_write_error(pdata); + rc = -EIO; + } + mutex_unlock(&pdata->write_mutex); + return rc; +} + +/* + * rtc device ioctl + * + * The rtc framework handles the basic rtc ioctls on behalf + * of the driver by calling the functions registered in the + * rtc_ops structure. + */ +static int dryice_rtc_ioctl(struct device *dev, unsigned int cmd, + unsigned long arg) +{ + struct rtc_drv_data *pdata = dev_get_drvdata(dev); + + dev_dbg(dev, "%s(0x%x)\n", __func__, cmd); + switch (cmd) { + case RTC_AIE_OFF: /* alarm disable */ + di_int_disable(pdata, DIER_CAIE); + return 0; + + case RTC_AIE_ON: /* alarm enable */ + di_int_enable(pdata, DIER_CAIE); + return 0; + } + return -ENOIOCTLCMD; +} + +/* + * read the seconds portion of the current time from the dryice time counter + */ +static int dryice_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + struct rtc_drv_data *pdata = dev_get_drvdata(dev); + unsigned long now; + + dev_dbg(dev, "%s\n", __func__); + now = di_read(pdata, DTCMR); + rtc_time_to_tm(now, tm); + + return 0; +} + +/* + * set the seconds portion of dryice time counter and clear the + * fractional part. + */ +static int dryice_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + struct rtc_drv_data *pdata = dev_get_drvdata(dev); + unsigned long now; + int rc; + + dev_dbg(dev, "%s\n", __func__); + rc = rtc_tm_to_time(tm, &now); + if (rc == 0) { + /* zero the fractional part first */ + di_write_wait_err(pdata, 0, DTCLR, rc, err); + di_write_wait_err(pdata, now, DTCMR, rc, err); + } +err: + return rc; +} + +/* + * read the seconds portion of the alarm register. + * the fractional part of the alarm register is always zero. + */ +static int dryice_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm) +{ + struct rtc_drv_data *pdata = dev_get_drvdata(dev); + u32 dcamr; + + dev_dbg(dev, "%s\n", __func__); + dcamr = di_read(pdata, DCAMR); + rtc_time_to_tm(dcamr, &alarm->time); + + /* alarm is enabled if the interrupt is enabled */ + alarm->enabled = (di_read(pdata, DIER) & DIER_CAIE) != 0; + + /* don't allow the DSR read to mess up DSR_WCF */ + mutex_lock(&pdata->write_mutex); + + /* alarm is pending if the alarm flag is set */ + alarm->pending = (di_read(pdata, DSR) & DSR_CAF) != 0; + + mutex_unlock(&pdata->write_mutex); + + return 0; +} + +/* + * set the seconds portion of dryice alarm register + */ +static int dryice_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm) +{ + struct rtc_drv_data *pdata = dev_get_drvdata(dev); + unsigned long now; + unsigned long alarm_time; + int rc; + + dev_dbg(dev, "%s\n", __func__); + rc = rtc_tm_to_time(&alarm->time, &alarm_time); + if (rc) + return rc; + + /* don't allow setting alarm in the past */ + now = di_read(pdata, DTCMR); + if (alarm_time < now) + return -EINVAL; + + /* write the new alarm time */ + di_write_wait_err(pdata, (u32)alarm_time, DCAMR, rc, err); + + if (alarm->enabled) + di_int_enable(pdata, DIER_CAIE); /* enable alarm intr */ + else + di_int_disable(pdata, DIER_CAIE); /* disable alarm intr */ +err: + return rc; +} + +static struct rtc_class_ops dryice_rtc_ops = { + .ioctl = dryice_rtc_ioctl, + .read_time = dryice_rtc_read_time, + .set_time = dryice_rtc_set_time, + .read_alarm = dryice_rtc_read_alarm, + .set_alarm = dryice_rtc_set_alarm, +}; + +/* + * dryice "normal" interrupt handler + */ +static irqreturn_t dryice_norm_irq(int irq, void *dev_id) +{ + struct rtc_drv_data *pdata = dev_id; + u32 dsr, dier; + irqreturn_t rc = IRQ_NONE; + + dier = di_read(pdata, DIER); + + /* handle write complete and write error cases */ + if ((dier & DIER_WCIE)) { + /*If the write wait queue is empty then there is no pending + operations. It means the interrupt is for DryIce -Security. + IRQ must be returned as none.*/ + if (list_empty_careful(&pdata->write_wait.task_list)) + return rc; + + /* DSR_WCF clears itself on DSR read */ + dsr = di_read(pdata, DSR); + if ((dsr & (DSR_WCF | DSR_WEF))) { + /* mask the interrupt */ + di_int_disable(pdata, DIER_WCIE); + + /* save the dsr value for the wait queue */ + pdata->dsr |= dsr; + + wake_up_interruptible(&pdata->write_wait); + rc = IRQ_HANDLED; + } + } + + /* handle the alarm case */ + if ((dier & DIER_CAIE)) { + /* DSR_WCF clears itself on DSR read */ + dsr = di_read(pdata, DSR); + if (dsr & DSR_CAF) { + /* mask the interrupt */ + di_int_disable(pdata, DIER_CAIE); + + /* finish alarm in user context */ + schedule_work(&pdata->work); + rc = IRQ_HANDLED; + } + } + return rc; +} + +/* + * post the alarm event from user context so it can sleep + * on the write completion. + */ +static void dryice_work(struct work_struct *work) +{ + struct rtc_drv_data *pdata = container_of(work, struct rtc_drv_data, + work); + int rc; + + /* dismiss the interrupt (ignore error) */ + di_write_wait_err(pdata, DSR_CAF, DSR, rc, err); +err: + /* + * pass the alarm event to the rtc framework. note that + * rtc_update_irq expects to be called with interrupts off. + */ + local_irq_disable(); + rtc_update_irq(pdata->rtc, 1, RTC_AF | RTC_IRQF); + local_irq_enable(); +} + +/* + * probe for dryice rtc device + */ +static int dryice_rtc_probe(struct platform_device *pdev) +{ + struct rtc_device *rtc; + struct resource *res; + struct rtc_drv_data *pdata = NULL; + void __iomem *ioaddr = NULL; + int rc = 0; + + dev_dbg(&pdev->dev, "%s\n", __func__); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + pdata = kzalloc(sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + pdata->pdev = pdev; + pdata->irq = -1; + pdata->size = res->end - res->start + 1; + + if (!request_mem_region(res->start, pdata->size, pdev->name)) { + rc = -EBUSY; + goto err; + } + pdata->baseaddr = res->start; + ioaddr = ioremap(pdata->baseaddr, pdata->size); + if (!ioaddr) { + rc = -ENOMEM; + goto err; + } + pdata->ioaddr = ioaddr; + pdata->irq = platform_get_irq(pdev, 0); + + init_waitqueue_head(&pdata->write_wait); + + INIT_WORK(&pdata->work, dryice_work); + + mutex_init(&pdata->write_mutex); + + pdata->clk = clk_get(NULL, "dryice_clk"); + clk_enable(pdata->clk); + + if (pdata->irq >= 0) { + if (request_irq(pdata->irq, dryice_norm_irq, IRQF_SHARED, + pdev->name, pdata) < 0) { + dev_warn(&pdev->dev, "interrupt not available.\n"); + pdata->irq = -1; + goto err; + } + } + + /* + * Initialize dryice hardware + */ + + /* put dryice into valid state */ + if (di_read(pdata, DSR) & DSR_NVF) + di_write_wait_err(pdata, DSR_NVF | DSR_SVF, DSR, rc, err); + + /* mask alarm interrupt */ + di_int_disable(pdata, DIER_CAIE); + + /* initialize alarm */ + di_write_wait_err(pdata, DCAMR_UNSET, DCAMR, rc, err); + di_write_wait_err(pdata, 0, DCALR, rc, err); + + /* clear alarm flag */ + if (di_read(pdata, DSR) & DSR_CAF) + di_write_wait_err(pdata, DSR_CAF, DSR, rc, err); + + /* the timer won't count if it has never been written to */ + if (!di_read(pdata, DTCMR)) + di_write_wait_err(pdata, 0, DTCMR, rc, err); + + /* start keeping time */ + if (!(di_read(pdata, DCR) & DCR_TCE)) + di_write_wait_err(pdata, di_read(pdata, DCR) | DCR_TCE, DCR, + rc, err); + + rtc = rtc_device_register(pdev->name, &pdev->dev, + &dryice_rtc_ops, THIS_MODULE); + if (IS_ERR(rtc)) { + rc = PTR_ERR(rtc); + goto err; + } + pdata->rtc = rtc; + platform_set_drvdata(pdev, pdata); + + return 0; +err: + if (pdata->rtc) + rtc_device_unregister(pdata->rtc); + + if (pdata->irq >= 0) + free_irq(pdata->irq, pdata); + + if (pdata->clk) { + clk_disable(pdata->clk); + clk_put(pdata->clk); + } + + if (pdata->ioaddr) + iounmap(pdata->ioaddr); + + if (pdata->baseaddr) + release_mem_region(pdata->baseaddr, pdata->size); + + kfree(pdata); + + return rc; +} + +static int __exit dryice_rtc_remove(struct platform_device *pdev) +{ + struct rtc_drv_data *pdata = platform_get_drvdata(pdev); + + flush_scheduled_work(); + + if (pdata->rtc) + rtc_device_unregister(pdata->rtc); + + /* mask alarm interrupt */ + di_int_disable(pdata, DIER_CAIE); + + if (pdata->irq >= 0) + free_irq(pdata->irq, pdata); + + if (pdata->clk) { + clk_disable(pdata->clk); + clk_put(pdata->clk); + } + + if (pdata->ioaddr) + iounmap(pdata->ioaddr); + + if (pdata->baseaddr) + release_mem_region(pdata->baseaddr, pdata->size); + + kfree(pdata); + + return 0; +} + +static struct platform_driver dryice_rtc_driver = { + .driver = { + .name = "imxdi_rtc", + .owner = THIS_MODULE, + }, + .probe = dryice_rtc_probe, + .remove = __exit_p(dryice_rtc_remove), +}; + +static int __init dryice_rtc_init(void) +{ + pr_info("IMXDI Realtime Clock Driver (RTC)\n"); + return platform_driver_register(&dryice_rtc_driver); +} + +static void __exit dryice_rtc_exit(void) +{ + platform_driver_unregister(&dryice_rtc_driver); +} + +module_init(dryice_rtc_init); +module_exit(dryice_rtc_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("IMXDI Realtime Clock Driver (RTC)"); +MODULE_LICENSE("GPL"); diff --git a/drivers/rtc/rtc-mc13892.c b/drivers/rtc/rtc-mc13892.c new file mode 100644 index 000000000000..8abe1ba02bc0 --- /dev/null +++ b/drivers/rtc/rtc-mc13892.c @@ -0,0 +1,257 @@ +/* + * Copyright 2008-2010 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/slab.h> +#include <linux/rtc.h> +#include <linux/module.h> +#include <linux/platform_device.h> + +#include <linux/pmic_status.h> +#include <linux/pmic_external.h> + +#define RTC_TIME_LSH 0 +#define RTC_DAY_LSH 0 +#define RTCALARM_TIME_LSH 0 +#define RTCALARM_DAY_LSH 0 + +#define RTC_TIME_WID 17 +#define RTC_DAY_WID 15 +#define RTCALARM_TIME_WID 17 +#define RTCALARM_DAY_WID 15 + +static unsigned long rtc_status; + +static int mxc_rtc_open(struct device *dev) +{ + if (test_and_set_bit(1, &rtc_status)) + return -EBUSY; + return 0; +} + +static void mxc_rtc_release(struct device *dev) +{ + clear_bit(1, &rtc_status); +} + +static int mxc_rtc_ioctl(struct device *dev, unsigned int cmd, + unsigned long arg) +{ + switch (cmd) { + case RTC_AIE_OFF: + pr_debug("alarm off\n"); + CHECK_ERROR(pmic_write_reg(REG_RTC_ALARM, 0x100000, 0x100000)); + return 0; + case RTC_AIE_ON: + pr_debug("alarm on\n"); + CHECK_ERROR(pmic_write_reg(REG_RTC_ALARM, 0, 0x100000)); + return 0; + } + + return -ENOIOCTLCMD; +} + +static int mxc_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + unsigned int tod_reg_val = 0; + unsigned int day_reg_val = 0, day_reg_val2; + unsigned int mask, value; + unsigned long time; + + do { + mask = BITFMASK(RTC_DAY); + CHECK_ERROR(pmic_read_reg(REG_RTC_DAY, &value, mask)); + day_reg_val = BITFEXT(value, RTC_DAY); + + mask = BITFMASK(RTC_TIME); + CHECK_ERROR(pmic_read_reg(REG_RTC_TIME, &value, mask)); + tod_reg_val = BITFEXT(value, RTC_TIME); + + mask = BITFMASK(RTC_DAY); + CHECK_ERROR(pmic_read_reg(REG_RTC_DAY, &value, mask)); + day_reg_val2 = BITFEXT(value, RTC_DAY); + } while (day_reg_val != day_reg_val2); + + time = (unsigned long)((unsigned long)(tod_reg_val & + 0x0001FFFF) + + (unsigned long)(day_reg_val * 86400)); + + rtc_time_to_tm(time, tm); + + return 0; +} + +static int mxc_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + unsigned int tod_reg_val = 0; + unsigned int day_reg_val, day_reg_val2 = 0; + unsigned int mask, value; + unsigned long time; + + if (rtc_valid_tm(tm)) + return -1; + + rtc_tm_to_time(tm, &time); + + tod_reg_val = time % 86400; + day_reg_val = time / 86400; + + do { + mask = BITFMASK(RTC_DAY); + value = BITFVAL(RTC_DAY, day_reg_val); + CHECK_ERROR(pmic_write_reg(REG_RTC_DAY, value, mask)); + + mask = BITFMASK(RTC_TIME); + value = BITFVAL(RTC_TIME, tod_reg_val); + CHECK_ERROR(pmic_write_reg(REG_RTC_TIME, value, mask)); + + mask = BITFMASK(RTC_DAY); + CHECK_ERROR(pmic_read_reg(REG_RTC_DAY, &value, mask)); + day_reg_val2 = BITFEXT(value, RTC_DAY); + } while (day_reg_val != day_reg_val2); + + return 0; +} + +static int mxc_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + unsigned int tod_reg_val = 0; + unsigned int day_reg_val = 0; + unsigned int mask, value; + unsigned long time; + + mask = BITFMASK(RTCALARM_TIME); + CHECK_ERROR(pmic_read_reg(REG_RTC_ALARM, &value, mask)); + tod_reg_val = BITFEXT(value, RTCALARM_TIME); + + mask = BITFMASK(RTCALARM_DAY); + CHECK_ERROR(pmic_read_reg(REG_RTC_DAY_ALARM, &value, mask)); + day_reg_val = BITFEXT(value, RTCALARM_DAY); + + time = (unsigned long)((unsigned long)(tod_reg_val & + 0x0001FFFF) + + (unsigned long)(day_reg_val * 86400)); + rtc_time_to_tm(time, &(alrm->time)); + + return 0; +} + +static int mxc_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + unsigned int tod_reg_val = 0; + unsigned int day_reg_val = 0; + unsigned int mask, value; + unsigned long time; + + if (rtc_valid_tm(&alrm->time)) + return -1; + + rtc_tm_to_time(&alrm->time, &time); + + tod_reg_val = time % 86400; + day_reg_val = time / 86400; + + mask = BITFMASK(RTCALARM_TIME); + value = BITFVAL(RTCALARM_TIME, tod_reg_val); + CHECK_ERROR(pmic_write_reg(REG_RTC_ALARM, value, mask)); + + mask = BITFMASK(RTCALARM_DAY); + value = BITFVAL(RTCALARM_DAY, day_reg_val); + CHECK_ERROR(pmic_write_reg(REG_RTC_DAY_ALARM, value, mask)); + + return 0; +} + +struct rtc_drv_data { + struct rtc_device *rtc; + pmic_event_callback_t event; +}; + +static struct rtc_class_ops mxc_rtc_ops = { + .open = mxc_rtc_open, + .release = mxc_rtc_release, + .ioctl = mxc_rtc_ioctl, + .read_time = mxc_rtc_read_time, + .set_time = mxc_rtc_set_time, + .read_alarm = mxc_rtc_read_alarm, + .set_alarm = mxc_rtc_set_alarm, +}; + +static void mxc_rtc_alarm_int(void *data) +{ + struct rtc_drv_data *pdata = data; + + rtc_update_irq(pdata->rtc, 1, RTC_AF | RTC_IRQF); +} + +static int mxc_rtc_probe(struct platform_device *pdev) +{ + struct rtc_drv_data *pdata = NULL; + + printk(KERN_INFO "mc13892 rtc probe start\n"); + + pdata = kzalloc(sizeof(*pdata), GFP_KERNEL); + + if (!pdata) + return -ENOMEM; + + pdata->event.func = mxc_rtc_alarm_int; + pdata->event.param = pdata; + CHECK_ERROR(pmic_event_subscribe(EVENT_TODAI, pdata->event)); + + device_init_wakeup(&pdev->dev, 1); + pdata->rtc = rtc_device_register(pdev->name, &pdev->dev, + &mxc_rtc_ops, THIS_MODULE); + + platform_set_drvdata(pdev, pdata); + if (IS_ERR(pdata->rtc)) + return -1; + + printk(KERN_INFO "mc13892 rtc probe succeed\n"); + return 0; +} + +static int __exit mxc_rtc_remove(struct platform_device *pdev) +{ + struct rtc_drv_data *pdata = platform_get_drvdata(pdev); + + rtc_device_unregister(pdata->rtc); + CHECK_ERROR(pmic_event_unsubscribe(EVENT_TODAI, pdata->event)); + + return 0; +} + +static struct platform_driver mxc_rtc_driver = { + .driver = { + .name = "pmic_rtc", + }, + .probe = mxc_rtc_probe, + .remove = __exit_p(mxc_rtc_remove), +}; + +static int __init mxc_rtc_init(void) +{ + return platform_driver_register(&mxc_rtc_driver); +} + +static void __exit mxc_rtc_exit(void) +{ + platform_driver_unregister(&mxc_rtc_driver); + +} + +module_init(mxc_rtc_init); +module_exit(mxc_rtc_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MC13892 Realtime Clock Driver (RTC)"); +MODULE_LICENSE("GPL"); diff --git a/drivers/rtc/rtc-mxc.c b/drivers/rtc/rtc-mxc.c index 25ec921db07c..850be4842d4f 100644 --- a/drivers/rtc/rtc-mxc.c +++ b/drivers/rtc/rtc-mxc.c @@ -1,5 +1,5 @@ /* - * Copyright 2004-2008 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2004-2010 Freescale Semiconductor, Inc. All Rights Reserved. * * The code contained herein is licensed under the GNU General Public * License. You may obtain a copy of the GNU General Public License @@ -17,8 +17,6 @@ #include <linux/platform_device.h> #include <linux/clk.h> -#include <mach/hardware.h> - #define RTC_INPUT_CLK_32768HZ (0x00 << 5) #define RTC_INPUT_CLK_32000HZ (0x01 << 5) #define RTC_INPUT_CLK_38400HZ (0x02 << 5) diff --git a/drivers/rtc/rtc-mxc_v2.c b/drivers/rtc/rtc-mxc_v2.c new file mode 100644 index 000000000000..4d2317bd706d --- /dev/null +++ b/drivers/rtc/rtc-mxc_v2.c @@ -0,0 +1,754 @@ +/* + * Copyright (C) 2004-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * Implementation based on rtc-ds1553.c + */ + +/*! + * @defgroup RTC Real Time Clock (RTC) Driver + */ +/*! + * @file rtc-mxc_v2.c + * @brief Real Time Clock interface + * + * This file contains Real Time Clock interface for Linux. + * + * @ingroup RTC + */ + +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/rtc.h> +#include <linux/module.h> +#include <linux/fs.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/uaccess.h> +#include <linux/io.h> +#include <linux/mxc_srtc.h> + +#define SRTC_LPSCLR_LLPSC_LSH 17 /* start bit for LSB time value */ + +#define SRTC_LPPDR_INIT 0x41736166 /* init for glitch detect */ + +#define SRTC_LPCR_SWR_LP (1 << 0) /* lp software reset */ +#define SRTC_LPCR_EN_LP (1 << 3) /* lp enable */ +#define SRTC_LPCR_WAE (1 << 4) /* lp wakeup alarm enable */ +#define SRTC_LPCR_SAE (1 << 5) /* lp security alarm enable */ +#define SRTC_LPCR_SI (1 << 6) /* lp security interrupt enable */ +#define SRTC_LPCR_ALP (1 << 7) /* lp alarm flag */ +#define SRTC_LPCR_LTC (1 << 8) /* lp lock time counter */ +#define SRTC_LPCR_LMC (1 << 9) /* lp lock monotonic counter */ +#define SRTC_LPCR_SV (1 << 10) /* lp security violation */ +#define SRTC_LPCR_NSA (1 << 11) /* lp non secure access */ +#define SRTC_LPCR_NVEIE (1 << 12) /* lp non valid state exit int en */ +#define SRTC_LPCR_IEIE (1 << 13) /* lp init state exit int enable */ +#define SRTC_LPCR_NVE (1 << 14) /* lp non valid state exit bit */ +#define SRTC_LPCR_IE (1 << 15) /* lp init state exit bit */ + +#define SRTC_LPCR_ALL_INT_EN (SRTC_LPCR_WAE | SRTC_LPCR_SAE | \ + SRTC_LPCR_SI | SRTC_LPCR_ALP | \ + SRTC_LPCR_NVEIE | SRTC_LPCR_IEIE) + +#define SRTC_LPSR_TRI (1 << 0) /* lp time read invalidate */ +#define SRTC_LPSR_PGD (1 << 1) /* lp power supply glitc detected */ +#define SRTC_LPSR_CTD (1 << 2) /* lp clock tampering detected */ +#define SRTC_LPSR_ALP (1 << 3) /* lp alarm flag */ +#define SRTC_LPSR_MR (1 << 4) /* lp monotonic counter rollover */ +#define SRTC_LPSR_TR (1 << 5) /* lp time rollover */ +#define SRTC_LPSR_EAD (1 << 6) /* lp external alarm detected */ +#define SRTC_LPSR_IT0 (1 << 7) /* lp IIM throttle */ +#define SRTC_LPSR_IT1 (1 << 8) +#define SRTC_LPSR_IT2 (1 << 9) +#define SRTC_LPSR_SM0 (1 << 10) /* lp security mode */ +#define SRTC_LPSR_SM1 (1 << 11) +#define SRTC_LPSR_STATE_LP0 (1 << 12) /* lp state */ +#define SRTC_LPSR_STATE_LP1 (1 << 13) +#define SRTC_LPSR_NVES (1 << 14) /* lp non-valid state exit status */ +#define SRTC_LPSR_IES (1 << 15) /* lp init state exit status */ + +#define MAX_PIE_NUM 15 +#define MAX_PIE_FREQ 32768 +#define MIN_PIE_FREQ 1 + +#define SRTC_PI0 (1 << 0) +#define SRTC_PI1 (1 << 1) +#define SRTC_PI2 (1 << 2) +#define SRTC_PI3 (1 << 3) +#define SRTC_PI4 (1 << 4) +#define SRTC_PI5 (1 << 5) +#define SRTC_PI6 (1 << 6) +#define SRTC_PI7 (1 << 7) +#define SRTC_PI8 (1 << 8) +#define SRTC_PI9 (1 << 9) +#define SRTC_PI10 (1 << 10) +#define SRTC_PI11 (1 << 11) +#define SRTC_PI12 (1 << 12) +#define SRTC_PI13 (1 << 13) +#define SRTC_PI14 (1 << 14) +#define SRTC_PI15 (1 << 15) + +#define PIT_ALL_ON (SRTC_PI1 | SRTC_PI2 | SRTC_PI3 | \ + SRTC_PI4 | SRTC_PI5 | SRTC_PI6 | SRTC_PI7 | \ + SRTC_PI8 | SRTC_PI9 | SRTC_PI10 | SRTC_PI11 | \ + SRTC_PI12 | SRTC_PI13 | SRTC_PI14 | SRTC_PI15) + +#define SRTC_SWR_HP (1 << 0) /* hp software reset */ +#define SRTC_EN_HP (1 << 3) /* hp enable */ +#define SRTC_TS (1 << 4) /* time syncronize hp with lp */ + +#define SRTC_IE_AHP (1 << 16) /* Alarm HP Interrupt Enable bit */ +#define SRTC_IE_WDHP (1 << 18) /* Write Done HP Interrupt Enable bit */ +#define SRTC_IE_WDLP (1 << 19) /* Write Done LP Interrupt Enable bit */ + +#define SRTC_ISR_AHP (1 << 16) /* interrupt status: alarm hp */ +#define SRTC_ISR_WDHP (1 << 18) /* interrupt status: write done hp */ +#define SRTC_ISR_WDLP (1 << 19) /* interrupt status: write done lp */ +#define SRTC_ISR_WPHP (1 << 20) /* interrupt status: write pending hp */ +#define SRTC_ISR_WPLP (1 << 21) /* interrupt status: write pending lp */ + +#define SRTC_LPSCMR 0x00 /* LP Secure Counter MSB Reg */ +#define SRTC_LPSCLR 0x04 /* LP Secure Counter LSB Reg */ +#define SRTC_LPSAR 0x08 /* LP Secure Alarm Reg */ +#define SRTC_LPSMCR 0x0C /* LP Secure Monotonic Counter Reg */ +#define SRTC_LPCR 0x10 /* LP Control Reg */ +#define SRTC_LPSR 0x14 /* LP Status Reg */ +#define SRTC_LPPDR 0x18 /* LP Power Supply Glitch Detector Reg */ +#define SRTC_LPGR 0x1C /* LP General Purpose Reg */ +#define SRTC_HPCMR 0x20 /* HP Counter MSB Reg */ +#define SRTC_HPCLR 0x24 /* HP Counter LSB Reg */ +#define SRTC_HPAMR 0x28 /* HP Alarm MSB Reg */ +#define SRTC_HPALR 0x2C /* HP Alarm LSB Reg */ +#define SRTC_HPCR 0x30 /* HP Control Reg */ +#define SRTC_HPISR 0x34 /* HP Interrupt Status Reg */ +#define SRTC_HPIENR 0x38 /* HP Interrupt Enable Reg */ + +#define SRTC_SECMODE_MASK 0x3 /* the mask of SRTC security mode */ +#define SRTC_SECMODE_LOW 0x0 /* Low Security */ +#define SRTC_SECMODE_MED 0x1 /* Medium Security */ +#define SRTC_SECMODE_HIGH 0x2 /* High Security */ +#define SRTC_SECMODE_RESERVED 0x3 /* Reserved */ + +struct rtc_drv_data { + struct rtc_device *rtc; + void __iomem *ioaddr; + unsigned long baseaddr; + int irq; + struct clk *clk; + bool irq_enable; +}; + + +/* completion event for implementing RTC_WAIT_FOR_TIME_SET ioctl */ +DECLARE_COMPLETION(srtc_completion); +/* global to save difference of 47-bit counter value */ +static int64_t time_diff; + +/*! + * @defgroup RTC Real Time Clock (RTC) Driver + */ +/*! + * @file rtc-mxc.c + * @brief Real Time Clock interface + * + * This file contains Real Time Clock interface for Linux. + * + * @ingroup RTC + */ + +static unsigned long rtc_status; + +static DEFINE_SPINLOCK(rtc_lock); + +/*! + * This function does write synchronization for writes to the lp srtc block. + * To take care of the asynchronous CKIL clock, all writes from the IP domain + * will be synchronized to the CKIL domain. + */ +static inline void rtc_write_sync_lp(void __iomem *ioaddr) +{ + unsigned int i, count; + /* Wait for 3 CKIL cycles */ + for (i = 0; i < 3; i++) { + count = __raw_readl(ioaddr + SRTC_LPSCLR); + while + ((__raw_readl(ioaddr + SRTC_LPSCLR)) == count); + } +} + +/*! + * This function updates the RTC alarm registers and then clears all the + * interrupt status bits. + * + * @param alrm the new alarm value to be updated in the RTC + * + * @return 0 if successful; non-zero otherwise. + */ +static int rtc_update_alarm(struct device *dev, struct rtc_time *alrm) +{ + struct rtc_drv_data *pdata = dev_get_drvdata(dev); + void __iomem *ioaddr = pdata->ioaddr; + struct rtc_time alarm_tm, now_tm; + unsigned long now, time; + int ret; + + now = __raw_readl(ioaddr + SRTC_LPSCMR); + rtc_time_to_tm(now, &now_tm); + + alarm_tm.tm_year = now_tm.tm_year; + alarm_tm.tm_mon = now_tm.tm_mon; + alarm_tm.tm_mday = now_tm.tm_mday; + + alarm_tm.tm_hour = alrm->tm_hour; + alarm_tm.tm_min = alrm->tm_min; + alarm_tm.tm_sec = alrm->tm_sec; + + rtc_tm_to_time(&now_tm, &now); + rtc_tm_to_time(&alarm_tm, &time); + + if (time < now) { + time += 60 * 60 * 24; + rtc_time_to_tm(time, &alarm_tm); + } + ret = rtc_tm_to_time(&alarm_tm, &time); + + __raw_writel(time, ioaddr + SRTC_LPSAR); + + /* clear alarm interrupt status bit */ + __raw_writel(SRTC_LPSR_ALP, ioaddr + SRTC_LPSR); + + return ret; +} + +/*! + * This function is the RTC interrupt service routine. + * + * @param irq RTC IRQ number + * @param dev_id device ID which is not used + * + * @return IRQ_HANDLED as defined in the include/linux/interrupt.h file. + */ +static irqreturn_t mxc_rtc_interrupt(int irq, void *dev_id) +{ + struct platform_device *pdev = dev_id; + struct rtc_drv_data *pdata = platform_get_drvdata(pdev); + void __iomem *ioaddr = pdata->ioaddr; + u32 lp_status, lp_cr; + u32 events = 0; + + lp_status = __raw_readl(ioaddr + SRTC_LPSR); + lp_cr = __raw_readl(ioaddr + SRTC_LPCR); + + /* update irq data & counter */ + if (lp_status & SRTC_LPSR_ALP) { + if (lp_cr & SRTC_LPCR_ALP) + events |= (RTC_AF | RTC_IRQF); + + /* disable further lp alarm interrupts */ + lp_cr &= ~(SRTC_LPCR_ALP | SRTC_LPCR_WAE); + } + + /* Update interrupt enables */ + __raw_writel(lp_cr, ioaddr + SRTC_LPCR); + + /* If no interrupts are enabled, turn off interrupts in kernel */ + if (((lp_cr & SRTC_LPCR_ALL_INT_EN) == 0) && (pdata->irq_enable)) { + disable_irq_nosync(pdata->irq); + pdata->irq_enable = false; + } + + /* clear interrupt status */ + __raw_writel(lp_status, ioaddr + SRTC_LPSR); + + rtc_update_irq(pdata->rtc, 1, events); + return IRQ_HANDLED; +} + +/*! + * This function is used to open the RTC driver. + * + * @return 0 if successful; non-zero otherwise. + */ +static int mxc_rtc_open(struct device *dev) +{ + if (test_and_set_bit(1, &rtc_status)) + return -EBUSY; + return 0; +} + +/*! + * clear all interrupts and release the IRQ + */ +static void mxc_rtc_release(struct device *dev) +{ + rtc_status = 0; +} + +/*! + * This function is used to support some ioctl calls directly. + * Other ioctl calls are supported indirectly through the + * arm/common/rtctime.c file. + * + * @param cmd ioctl command as defined in include/linux/rtc.h + * @param arg value for the ioctl command + * + * @return 0 if successful or negative value otherwise. + */ +static int mxc_rtc_ioctl(struct device *dev, unsigned int cmd, + unsigned long arg) +{ + struct rtc_drv_data *pdata = dev_get_drvdata(dev); + void __iomem *ioaddr = pdata->ioaddr; + unsigned long lock_flags = 0; + u32 lp_cr; + u64 time_47bit; + int retVal; + + switch (cmd) { + case RTC_AIE_OFF: + spin_lock_irqsave(&rtc_lock, lock_flags); + lp_cr = __raw_readl(ioaddr + SRTC_LPCR); + lp_cr &= ~(SRTC_LPCR_ALP | SRTC_LPCR_WAE); + if (((lp_cr & SRTC_LPCR_ALL_INT_EN) == 0) + && (pdata->irq_enable)) { + disable_irq(pdata->irq); + pdata->irq_enable = false; + } + __raw_writel(lp_cr, ioaddr + SRTC_LPCR); + spin_unlock_irqrestore(&rtc_lock, lock_flags); + return 0; + + case RTC_AIE_ON: + spin_lock_irqsave(&rtc_lock, lock_flags); + if (!pdata->irq_enable) { + enable_irq(pdata->irq); + pdata->irq_enable = true; + } + lp_cr = __raw_readl(ioaddr + SRTC_LPCR); + lp_cr |= SRTC_LPCR_ALP | SRTC_LPCR_WAE; + __raw_writel(lp_cr, ioaddr + SRTC_LPCR); + spin_unlock_irqrestore(&rtc_lock, lock_flags); + return 0; + + case RTC_READ_TIME_47BIT: + time_47bit = (((u64) __raw_readl(ioaddr + SRTC_LPSCMR)) << 32 | + ((u64) __raw_readl(ioaddr + SRTC_LPSCLR))); + time_47bit >>= SRTC_LPSCLR_LLPSC_LSH; + + if (arg && copy_to_user((u64 *) arg, &time_47bit, sizeof(u64))) + return -EFAULT; + + return 0; + + /* This IOCTL to be used by processes to be notified of time changes */ + case RTC_WAIT_TIME_SET: + + /* don't block without releasing mutex first */ + mutex_unlock(&pdata->rtc->ops_lock); + + /* sleep until awakened by SRTC driver when LPSCMR is changed */ + wait_for_completion(&srtc_completion); + + /* relock mutex because rtc_dev_ioctl will unlock again */ + retVal = mutex_lock_interruptible(&pdata->rtc->ops_lock); + + /* copy the new time difference = new time - previous time + * to the user param. The difference is a signed value */ + if (arg && copy_to_user((int64_t *) arg, &time_diff, + sizeof(int64_t))) + return -EFAULT; + + return retVal; + + } + + return -ENOIOCTLCMD; +} + +/*! + * This function reads the current RTC time into tm in Gregorian date. + * + * @param tm contains the RTC time value upon return + * + * @return 0 if successful; non-zero otherwise. + */ +static int mxc_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + struct rtc_drv_data *pdata = dev_get_drvdata(dev); + void __iomem *ioaddr = pdata->ioaddr; + + rtc_time_to_tm(__raw_readl(ioaddr + SRTC_LPSCMR), tm); + return 0; +} + +/*! + * This function sets the internal RTC time based on tm in Gregorian date. + * + * @param tm the time value to be set in the RTC + * + * @return 0 if successful; non-zero otherwise. + */ +static int mxc_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + struct rtc_drv_data *pdata = dev_get_drvdata(dev); + void __iomem *ioaddr = pdata->ioaddr; + unsigned long time; + u64 old_time_47bit, new_time_47bit; + int ret; + ret = rtc_tm_to_time(tm, &time); + if (ret != 0) + return ret; + + old_time_47bit = (((u64) __raw_readl(ioaddr + SRTC_LPSCMR)) << 32 | + ((u64) __raw_readl(ioaddr + SRTC_LPSCLR))); + old_time_47bit >>= SRTC_LPSCLR_LLPSC_LSH; + + __raw_writel(time, ioaddr + SRTC_LPSCMR); + rtc_write_sync_lp(ioaddr); + + new_time_47bit = (((u64) __raw_readl(ioaddr + SRTC_LPSCMR)) << 32 | + ((u64) __raw_readl(ioaddr + SRTC_LPSCLR))); + new_time_47bit >>= SRTC_LPSCLR_LLPSC_LSH; + + /* update the difference between previous time and new time */ + time_diff = new_time_47bit - old_time_47bit; + + /* signal all waiting threads that time changed */ + complete_all(&srtc_completion); + + /* allow signalled threads to handle the time change notification */ + schedule(); + + /* reinitialize completion variable */ + INIT_COMPLETION(srtc_completion); + + return 0; +} + +/*! + * This function reads the current alarm value into the passed in \b alrm + * argument. It updates the \b alrm's pending field value based on the whether + * an alarm interrupt occurs or not. + * + * @param alrm contains the RTC alarm value upon return + * + * @return 0 if successful; non-zero otherwise. + */ +static int mxc_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct rtc_drv_data *pdata = dev_get_drvdata(dev); + void __iomem *ioaddr = pdata->ioaddr; + + rtc_time_to_tm(__raw_readl(ioaddr + SRTC_LPSAR), &alrm->time); + alrm->pending = + ((__raw_readl(ioaddr + SRTC_LPSR) & SRTC_LPSR_ALP) != 0) ? 1 : 0; + + return 0; +} + +/*! + * This function sets the RTC alarm based on passed in alrm. + * + * @param alrm the alarm value to be set in the RTC + * + * @return 0 if successful; non-zero otherwise. + */ +static int mxc_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct rtc_drv_data *pdata = dev_get_drvdata(dev); + void __iomem *ioaddr = pdata->ioaddr; + unsigned long lock_flags = 0; + u32 lp_cr; + int ret; + + if (rtc_valid_tm(&alrm->time)) { + if (alrm->time.tm_sec > 59 || + alrm->time.tm_hour > 23 || alrm->time.tm_min > 59) { + return -EINVAL; + } + } + + spin_lock_irqsave(&rtc_lock, lock_flags); + lp_cr = __raw_readl(ioaddr + SRTC_LPCR); + + ret = rtc_update_alarm(dev, &alrm->time); + if (ret) + goto out; + + if (alrm->enabled) + lp_cr |= (SRTC_LPCR_ALP | SRTC_LPCR_WAE); + else + lp_cr &= ~(SRTC_LPCR_ALP | SRTC_LPCR_WAE); + + if (lp_cr & SRTC_LPCR_ALL_INT_EN) { + if (!pdata->irq_enable) { + enable_irq(pdata->irq); + pdata->irq_enable = true; + } + } else { + if (pdata->irq_enable) { + disable_irq(pdata->irq); + pdata->irq_enable = false; + } + } + + __raw_writel(lp_cr, ioaddr + SRTC_LPCR); + +out: + spin_unlock_irqrestore(&rtc_lock, lock_flags); + rtc_write_sync_lp(ioaddr); + return ret; +} + +/*! + * This function is used to provide the content for the /proc/driver/rtc + * file. + * + * @param seq buffer to hold the information that the driver wants to write + * + * @return The number of bytes written into the rtc file. + */ +static int mxc_rtc_proc(struct device *dev, struct seq_file *seq) +{ + struct rtc_drv_data *pdata = dev_get_drvdata(dev); + void __iomem *ioaddr = pdata->ioaddr; + + seq_printf(seq, "alarm_IRQ\t: %s\n", + (((__raw_readl(ioaddr + SRTC_LPCR)) & SRTC_LPCR_ALP) != + 0) ? "yes" : "no"); + + return 0; +} + +/*! + * The RTC driver structure + */ +static struct rtc_class_ops mxc_rtc_ops = { + .open = mxc_rtc_open, + .release = mxc_rtc_release, + .ioctl = mxc_rtc_ioctl, + .read_time = mxc_rtc_read_time, + .set_time = mxc_rtc_set_time, + .read_alarm = mxc_rtc_read_alarm, + .set_alarm = mxc_rtc_set_alarm, + .proc = mxc_rtc_proc, +}; + +/*! MXC RTC Power management control */ +static int mxc_rtc_probe(struct platform_device *pdev) +{ + struct clk *clk; + struct timespec tv; + struct resource *res; + struct rtc_device *rtc; + struct rtc_drv_data *pdata = NULL; + void __iomem *ioaddr; + int ret = 0; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + pdata = kzalloc(sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + pdata->clk = clk_get(&pdev->dev, "rtc_clk"); + clk_enable(pdata->clk); + pdata->baseaddr = res->start; + pdata->ioaddr = ioremap(pdata->baseaddr, 0x40); + ioaddr = pdata->ioaddr; + + /* Configure and enable the RTC */ + pdata->irq = platform_get_irq(pdev, 0); + if (pdata->irq >= 0) { + if (request_irq(pdata->irq, mxc_rtc_interrupt, IRQF_SHARED, + pdev->name, pdev) < 0) { + dev_warn(&pdev->dev, "interrupt not available.\n"); + pdata->irq = -1; + } else { + disable_irq(pdata->irq); + pdata->irq_enable = false; + } + } + + clk = clk_get(&pdev->dev, "rtc_clk"); + if (clk_get_rate(clk) != 32768) { + printk(KERN_ALERT "rtc clock is not valid"); + ret = -EINVAL; + clk_put(clk); + goto err_out; + } + clk_put(clk); + + /* initialize glitch detect */ + __raw_writel(SRTC_LPPDR_INIT, ioaddr + SRTC_LPPDR); + udelay(100); + + /* clear lp interrupt status */ + __raw_writel(0xFFFFFFFF, ioaddr + SRTC_LPSR); + udelay(100); + + /* move out of init state */ + __raw_writel((SRTC_LPCR_IE | SRTC_LPCR_NSA), + ioaddr + SRTC_LPCR); + + udelay(100); + + while ((__raw_readl(ioaddr + SRTC_LPSR) & SRTC_LPSR_IES) == 0) + ; + + /* move out of non-valid state */ + __raw_writel((SRTC_LPCR_IE | SRTC_LPCR_NVE | SRTC_LPCR_NSA | + SRTC_LPCR_EN_LP), ioaddr + SRTC_LPCR); + + udelay(100); + + while ((__raw_readl(ioaddr + SRTC_LPSR) & SRTC_LPSR_NVES) == 0) + ; + + __raw_writel(0xFFFFFFFF, ioaddr + SRTC_LPSR); + udelay(100); + + rtc = rtc_device_register(pdev->name, &pdev->dev, + &mxc_rtc_ops, THIS_MODULE); + if (IS_ERR(rtc)) { + ret = PTR_ERR(rtc); + goto err_out; + } + + pdata->rtc = rtc; + platform_set_drvdata(pdev, pdata); + + tv.tv_nsec = 0; + tv.tv_sec = __raw_readl(ioaddr + SRTC_LPSCMR); + + /* By default, devices should wakeup if they can */ + /* So srtc is set as "should wakeup" as it can */ + device_init_wakeup(&pdev->dev, 1); + + return ret; + +err_out: + clk_disable(pdata->clk); + iounmap(ioaddr); + if (pdata->irq >= 0) + free_irq(pdata->irq, pdev); + kfree(pdata); + return ret; +} + +static int __exit mxc_rtc_remove(struct platform_device *pdev) +{ + struct rtc_drv_data *pdata = platform_get_drvdata(pdev); + rtc_device_unregister(pdata->rtc); + if (pdata->irq >= 0) + free_irq(pdata->irq, pdev); + + clk_disable(pdata->clk); + clk_put(pdata->clk); + kfree(pdata); + return 0; +} + +/*! + * This function is called to save the system time delta relative to + * the MXC RTC when enterring a low power state. This time delta is + * then used on resume to adjust the system time to account for time + * loss while suspended. + * + * @param pdev not used + * @param state Power state to enter. + * + * @return The function always returns 0. + */ +static int mxc_rtc_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct rtc_drv_data *pdata = platform_get_drvdata(pdev); + + if (device_may_wakeup(&pdev->dev)) { + enable_irq_wake(pdata->irq); + } else { + if (pdata->irq_enable) + disable_irq(pdata->irq); + } + + return 0; +} + +/*! + * This function is called to correct the system time based on the + * current MXC RTC time relative to the time delta saved during + * suspend. + * + * @param pdev not used + * + * @return The function always returns 0. + */ +static int mxc_rtc_resume(struct platform_device *pdev) +{ + struct rtc_drv_data *pdata = platform_get_drvdata(pdev); + + if (device_may_wakeup(&pdev->dev)) { + disable_irq_wake(pdata->irq); + } else { + if (pdata->irq_enable) + enable_irq(pdata->irq); + } + + return 0; +} + +/*! + * Contains pointers to the power management callback functions. + */ +static struct platform_driver mxc_rtc_driver = { + .driver = { + .name = "mxc_rtc", + }, + .probe = mxc_rtc_probe, + .remove = __exit_p(mxc_rtc_remove), + .suspend = mxc_rtc_suspend, + .resume = mxc_rtc_resume, +}; + +/*! + * This function creates the /proc/driver/rtc file and registers the device RTC + * in the /dev/misc directory. It also reads the RTC value from external source + * and setup the internal RTC properly. + * + * @return -1 if RTC is failed to initialize; 0 is successful. + */ +static int __init mxc_rtc_init(void) +{ + return platform_driver_register(&mxc_rtc_driver); +} + +/*! + * This function removes the /proc/driver/rtc file and un-registers the + * device RTC from the /dev/misc directory. + */ +static void __exit mxc_rtc_exit(void) +{ + platform_driver_unregister(&mxc_rtc_driver); + +} + +module_init(mxc_rtc_init); +module_exit(mxc_rtc_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Realtime Clock Driver (RTC)"); +MODULE_LICENSE("GPL"); diff --git a/drivers/rtc/rtc-mxs.c b/drivers/rtc/rtc-mxs.c new file mode 100644 index 000000000000..02fcfccd8e9d --- /dev/null +++ b/drivers/rtc/rtc-mxs.c @@ -0,0 +1,322 @@ +/* + * Freescale STMP37XX/STMP378X Real Time Clock driver + * + * Copyright (c) 2007 Sigmatel, Inc. + * Peter Hartley, <peter.hartley@sigmatel.com> + * + * Copyright 2008-2010 Freescale Semiconductor, Inc. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/fs.h> +#include <linux/string.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/rtc.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/uaccess.h> + +#include <mach/hardware.h> +#include <mach/regs-rtc.h> + +struct mxs_rtc_data { + struct rtc_device *rtc; + unsigned int base; + int irq_alarm; + int irq_sample; + unsigned irq_count; +}; + +/* Time read/write */ +static int mxs_rtc_gettime(struct device *pdev, struct rtc_time *rtc_tm) +{ + struct mxs_rtc_data *rtc_data = dev_get_drvdata(pdev); + + while (__raw_readl(rtc_data->base + HW_RTC_STAT) & + BF_RTC_STAT_STALE_REGS(0x80)) + cpu_relax(); + + rtc_time_to_tm(__raw_readl(rtc_data->base + HW_RTC_SECONDS), rtc_tm); + return 0; +} + +static int mxs_rtc_settime(struct device *pdev, struct rtc_time *rtc_tm) +{ + unsigned long t; + int rc = rtc_tm_to_time(rtc_tm, &t); + struct mxs_rtc_data *rtc_data = dev_get_drvdata(pdev); + + if (rc == 0) { + __raw_writel(t, rtc_data->base + HW_RTC_SECONDS); + + /* The datasheet doesn't say which way round the + * NEW_REGS/STALE_REGS bitfields go. In fact it's 0x1=P0, + * 0x2=P1, .., 0x20=P5, 0x40=ALARM, 0x80=SECONDS, + */ + while (__raw_readl(rtc_data->base + HW_RTC_STAT) & + BF_RTC_STAT_NEW_REGS(0x80)) + cpu_relax(); + } + return rc; +} + +static irqreturn_t mxs_rtc_interrupt(int irq, void *dev_id) +{ + struct mxs_rtc_data *rtc_data = dev_get_drvdata(dev_id); + u32 status; + u32 events = 0; + + status = __raw_readl(rtc_data->base + HW_RTC_CTRL) & + (BM_RTC_CTRL_ALARM_IRQ | BM_RTC_CTRL_ONEMSEC_IRQ); + if (status & BM_RTC_CTRL_ALARM_IRQ) { + __raw_writel(BM_RTC_CTRL_ALARM_IRQ, + rtc_data->base + HW_RTC_CTRL_CLR); + events |= RTC_AF | RTC_IRQF; + } + if (status & BM_RTC_CTRL_ONEMSEC_IRQ) { + __raw_writel(BM_RTC_CTRL_ONEMSEC_IRQ, + rtc_data->base + HW_RTC_CTRL_CLR); + if (++rtc_data->irq_count % 1000 == 0) { + events |= RTC_UF | RTC_IRQF; + rtc_data->irq_count = 0; + } + } + + if (events) + rtc_update_irq(rtc_data->rtc, 1, events); + + return IRQ_HANDLED; +} + +static int mxs_rtc_open(struct device *pdev) +{ + int r; + struct mxs_rtc_data *rtc_data = dev_get_drvdata(pdev); + + r = request_irq(rtc_data->irq_alarm, mxs_rtc_interrupt, + IRQF_DISABLED, "RTC alarm", pdev); + if (r) { + dev_err(pdev, "Cannot claim IRQ%d\n", rtc_data->irq_alarm); + goto fail_1; + } + r = request_irq(rtc_data->irq_sample, mxs_rtc_interrupt, + IRQF_DISABLED, "RTC tick", pdev); + if (r) { + dev_err(pdev, "Cannot claim IRQ%d\n", rtc_data->irq_sample); + goto fail_2; + } + + return 0; +fail_2: + free_irq(rtc_data->irq_alarm, pdev); +fail_1: + return r; +} + +static void mxs_rtc_release(struct device *pdev) +{ + struct mxs_rtc_data *rtc_data = dev_get_drvdata(pdev); + + __raw_writel(BM_RTC_CTRL_ALARM_IRQ_EN | BM_RTC_CTRL_ONEMSEC_IRQ_EN, + rtc_data->base + HW_RTC_CTRL_CLR); + free_irq(rtc_data->irq_alarm, pdev); + free_irq(rtc_data->irq_sample, pdev); +} + +static int mxs_rtc_ioctl(struct device *pdev, unsigned int cmd, + unsigned long arg) +{ + struct mxs_rtc_data *rtc_data = dev_get_drvdata(pdev); + + switch (cmd) { + case RTC_AIE_OFF: + __raw_writel(BM_RTC_PERSISTENT0_ALARM_EN | + BM_RTC_PERSISTENT0_ALARM_WAKE_EN, + rtc_data->base + HW_RTC_PERSISTENT0_CLR); + __raw_writel(BM_RTC_CTRL_ALARM_IRQ_EN, + rtc_data->base + HW_RTC_CTRL_CLR); + break; + case RTC_AIE_ON: + __raw_writel(BM_RTC_PERSISTENT0_ALARM_EN | + BM_RTC_PERSISTENT0_ALARM_WAKE_EN, + rtc_data->base + HW_RTC_PERSISTENT0_SET); + + __raw_writel(BM_RTC_CTRL_ALARM_IRQ_EN, + rtc_data->base + HW_RTC_CTRL_SET); + break; + case RTC_UIE_ON: + rtc_data->irq_count = 0; + __raw_writel(BM_RTC_CTRL_ONEMSEC_IRQ_EN, + rtc_data->base + HW_RTC_CTRL_SET); + break; + case RTC_UIE_OFF: + __raw_writel(BM_RTC_CTRL_ONEMSEC_IRQ_EN, + rtc_data->base + HW_RTC_CTRL_CLR); + break; + default: + return -ENOIOCTLCMD; + } + + return 0; +} +static int mxs_rtc_read_alarm(struct device *pdev, struct rtc_wkalrm *alm) +{ + u32 t; + struct mxs_rtc_data *rtc_data = dev_get_drvdata(pdev); + + t = __raw_readl(rtc_data->base + HW_RTC_ALARM); + rtc_time_to_tm(t, &alm->time); + return 0; +} + +static int mxs_rtc_set_alarm(struct device *pdev, struct rtc_wkalrm *alm) +{ + unsigned long t; + struct mxs_rtc_data *rtc_data = dev_get_drvdata(pdev); + + rtc_tm_to_time(&alm->time, &t); + __raw_writel(t, rtc_data->base + HW_RTC_ALARM); + return 0; +} + +static struct rtc_class_ops mxs_rtc_ops = { + .open = mxs_rtc_open, + .release = mxs_rtc_release, + .ioctl = mxs_rtc_ioctl, + .read_time = mxs_rtc_gettime, + .set_time = mxs_rtc_settime, + .read_alarm = mxs_rtc_read_alarm, + .set_alarm = mxs_rtc_set_alarm, +}; + +static int mxs_rtc_probe(struct platform_device *pdev) +{ + u32 hwversion; + u32 rtc_stat; + struct resource *res; + struct mxs_rtc_data *rtc_data; + + rtc_data = kzalloc(sizeof(*rtc_data), GFP_KERNEL); + + if (!rtc_data) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + kfree(rtc_data); + return -ENODEV; + } + rtc_data->base = (unsigned int)IO_ADDRESS(res->start); + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (res == NULL) { + kfree(rtc_data); + return -ENODEV; + } + rtc_data->irq_alarm = res->start; + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 1); + if (res == NULL) { + kfree(rtc_data); + return -ENODEV; + } + rtc_data->irq_sample = res->start; + + __raw_writel(BM_RTC_PERSISTENT0_ALARM_EN | + BM_RTC_PERSISTENT0_ALARM_WAKE_EN | + BM_RTC_PERSISTENT0_ALARM_WAKE, + rtc_data->base + HW_RTC_PERSISTENT0_CLR); + + hwversion = __raw_readl(rtc_data->base + HW_RTC_VERSION); + rtc_stat = __raw_readl(rtc_data->base + HW_RTC_STAT); + printk(KERN_INFO "MXS RTC driver v1.0 hardware v%u.%u.%u\n", + (hwversion >> 24), + (hwversion >> 16) & 0xFF, + hwversion & 0xFFFF); + + rtc_data->rtc = rtc_device_register(pdev->name, &pdev->dev, + &mxs_rtc_ops, THIS_MODULE); + if (IS_ERR(rtc_data->rtc)) { + kfree(rtc_data); + return PTR_ERR(rtc_data->rtc); + } + + platform_set_drvdata(pdev, rtc_data); + + device_init_wakeup(&pdev->dev, 1); + + return 0; +} + +static int mxs_rtc_remove(struct platform_device *dev) +{ + struct mxs_rtc_data *rtc_data = platform_get_drvdata(dev); + + if (rtc_data) { + rtc_device_unregister(rtc_data->rtc); + kfree(rtc_data); + } + + return 0; +} + +#ifdef CONFIG_PM +static int mxs_rtc_suspend(struct platform_device *dev, pm_message_t state) +{ + return 0; +} + +static int mxs_rtc_resume(struct platform_device *dev) +{ + struct mxs_rtc_data *rtc_data = platform_get_drvdata(dev); + + __raw_writel(BM_RTC_PERSISTENT0_ALARM_EN | + BM_RTC_PERSISTENT0_ALARM_WAKE_EN | + BM_RTC_PERSISTENT0_ALARM_WAKE, + rtc_data->base + HW_RTC_PERSISTENT0_CLR); + return 0; +} +#else +#define mxs_rtc_suspend NULL +#define mxs_rtc_resume NULL +#endif + +static struct platform_driver mxs_rtcdrv = { + .probe = mxs_rtc_probe, + .remove = mxs_rtc_remove, + .suspend = mxs_rtc_suspend, + .resume = mxs_rtc_resume, + .driver = { + .name = "mxs-rtc", + .owner = THIS_MODULE, + }, +}; + +static int __init mxs_rtc_init(void) +{ + return platform_driver_register(&mxs_rtcdrv); +} + +static void __exit mxs_rtc_exit(void) +{ + platform_driver_unregister(&mxs_rtcdrv); +} + +module_init(mxs_rtc_init); +module_exit(mxs_rtc_exit); + +MODULE_DESCRIPTION("MXS RTC Driver"); +MODULE_AUTHOR("dmitry pervushin <dimka@embeddedalley.com>"); +MODULE_LICENSE("GPL"); |