Steven King
2012-06-17 07:50:45 UTC
Add support for the Coldfire m5441x on-chip rtc.
Signed-off-by: Stven King <sfking at fdwdc.com>
---
drivers/rtc/Kconfig | 10 ++
drivers/rtc/Makefile | 1 +
drivers/rtc/rtc-m5441x.c | 363 ++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 374 insertions(+)
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index 08cbdb9..da2b3c4 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -1087,4 +1087,14 @@ config RTC_DRV_MXC
This driver can also be built as a module, if so, the module
will be called "rtc-mxc".
+config RTC_DRV_M5441x
+ tristate "Freescale Coldfire M5441x RTC support"
+ depends on M5441x
+ help
+ This enables support for the RTC on the Freescale Coldfire 5441x
+ (54410/54415/54416/54417/54418).
+
+ This driver can also be built as a module. If so, the module
+ will be called rtc-m5441x.
+
endif # RTC_CLASS
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index 2973921..a9ded2c 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -61,6 +61,7 @@ obj-$(CONFIG_RTC_DRV_M41T94) += rtc-m41t94.o
obj-$(CONFIG_RTC_DRV_M48T35) += rtc-m48t35.o
obj-$(CONFIG_RTC_DRV_M48T59) += rtc-m48t59.o
obj-$(CONFIG_RTC_DRV_M48T86) += rtc-m48t86.o
+obj-$(CONFIG_RTC_DRV_M5441x) += rtc-m5441x.o
obj-$(CONFIG_RTC_DRV_MXC) += rtc-mxc.o
obj-$(CONFIG_RTC_DRV_MAX6900) += rtc-max6900.o
obj-$(CONFIG_RTC_DRV_MAX8925) += rtc-max8925.o
diff --git a/drivers/rtc/rtc-m5441x.c b/drivers/rtc/rtc-m5441x.c
new file mode 100644
index 0000000..ba677f3
--- /dev/null
+++ b/drivers/rtc/rtc-m5441x.c
@@ -0,0 +1,363 @@
+/*
+ * RTC driver for the Freescale Coldfire 5441x SoCs.
+ *
+ * Copyright(C) 2012, Steven King <sfking at fdwdc.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/clk.h>
+#include <linux/rtc.h>
+
+static void __iomem *m5441x_rtc_base;
+static int m5441x_rtc_irq;
+static struct clk *m5441x_rtc_clk;
+
+#define M5441X_RTC_YEARMON (m5441x_rtc_base + 0x00)
+#define M5441X_RTC_DAYS (m5441x_rtc_base + 0x02)
+#define M5441X_RTC_HOURMIN (m5441x_rtc_base + 0x04)
+#define M5441X_RTC_SECONDS (m5441x_rtc_base + 0x06)
+#define M5441X_RTC_ALM_YRMON (m5441x_rtc_base + 0x08)
+#define M5441X_RTC_ALM_DAYS (m5441x_rtc_base + 0x0a)
+#define M5441X_RTC_ALM_HM (m5441x_rtc_base + 0x0c)
+#define M5441X_RTC_ALM_SEC (m5441x_rtc_base + 0x0e)
+#define M5441X_RTC_CR (m5441x_rtc_base + 0x10)
+#define M5441X_RTC_CR_AM0 (0 << 2) /* secs/mins/hrs */
+#define M5441X_RTC_CR_AM1 (1 << 2) /* secs/mins/days */
+#define M5441X_RTC_CR_AM2 (2 << 2) /* secs/mins/days/mons */
+#define M5441X_RTC_CR_AM3 (3 << 2) /* alarm match all */
+#define M5441X_RTC_CR_DTDEN (1 << 6) /* DST enable */
+#define M5441X_RTC_CR_BCDEN (1 << 7) /* BCD enable */
+#define M5441X_RTC_CR_SWR (1 << 8) /* software reset */
+#define M5441X_RTC_SR (m5441x_rtc_base + 0x12)
+#define M5441X_RTC_SR_INVAL (1 << 0) /* time invalid */
+#define M5441X_RTC_SR_CDON (1 << 1) /* Compensation done */
+#define M5441X_RTC_SR_BERR (1 << 2) /* Bus err */
+#define M5441X_RTC_SR_WPE (1 << 4) /* write protect enabled */
+#define M5441X_RTC_SR_PORB (1 << 6) /* power on reset boot */
+#define M5441X_RTC_ISR (m5441x_rtc_base + 0x14)
+#define M5441X_RTC_ISR_STW (1 << 1) /* Stop watch */
+#define M5441X_RTC_ISR_ALM (1 << 2) /* Alarm */
+#define M5441X_RTC_ISR_DAY (1 << 3) /* day counter inc */
+#define M5441X_RTC_ISR_HR (1 << 4) /* hour counter inc */
+#define M5441X_RTC_ISR_MIN (1 << 5) /* minute counter inc */
+#define M5441X_RTC_ISR_1HZ (1 << 6) /* second counter inc */
+#define M5441X_RTC_ISR_2HZ (1 << 7) /* 2Hz counter inc */
+#define M5441X_RTC_IER (m5441x_rtc_base + 0x16)
+#define M5441X_RTC_IER_STW (1 << 1) /* Stop watch */
+#define M5441X_RTC_IER_ALM (1 << 2) /* Alarm */
+#define M5441X_RTC_IER_DAY (1 << 3) /* day counter inc */
+#define M5441X_RTC_IER_HR (1 << 4) /* hour counter inc */
+#define M5441X_RTC_IER_MIN (1 << 5) /* minute counter inc */
+#define M5441X_RTC_IER_1HZ (1 << 6) /* second counter inc */
+#define M5441X_RTC_IER_2HZ (1 << 7) /* 2Hz counter inc */
+#define M5441X_RTC_COUNT_DN (m5441x_rtc_base + 0x18)
+#define M5441X_RTC_CFG_DATA (m5441x_rtc_base + 0x20)
+#define M5441X_RTC_DST_HOUR (m5441x_rtc_base + 0x22)
+#define M5441X_RTC_DST_MON (m5441x_rtc_base + 0x24)
+#define M5441X_RTC_DST_DAY (m5441x_rtc_base + 0x26)
+#define M5441X_RTC_RTC_COMPEN (m5441x_rtc_base + 0x28)
+#define M5441X_RTC_UP_CNTRH (m5441x_rtc_base + 0x32)
+#define M5441X_RTC_UP_CNTRL (m5441x_rtc_base + 0x24)
+#define M5441X_RTC_Standy_RAM (m5441x_rtc_base + 0x40)
+
+static int m5441x_rtc_read_time(struct device *dev, struct rtc_time *tm)
+{
+ u16 r[4];
+
+ do {
+ r[0] = __raw_readw(M5441X_RTC_SECONDS);
+ r[1] = __raw_readw(M5441X_RTC_HOURMIN);
+ r[2] = __raw_readw(M5441X_RTC_DAYS);
+ r[3] = __raw_readw(M5441X_RTC_YEARMON);
+ } while (r[0] != __raw_readw(M5441X_RTC_SECONDS));
+
+ tm->tm_sec = r[0];
+ tm->tm_min = r[1] & 0xff;
+ tm->tm_hour = r[1] >> 8;
+ tm->tm_mday = (r[2] & 0xff) - 1;
+ tm->tm_wday = r[2] >> 8;
+ tm->tm_mon = (r[3] & 0xff) - 1;
+ tm->tm_year = (((s16)r[3]) >> 8) + 212;
+
+ return rtc_valid_tm(tm);
+}
+
+static int m5441x_rtc_set_time(struct device *dev, struct rtc_time *tm)
+{
+ u16 r[4];
+
+ if ((tm->tm_year < 84) || (tm->tm_year > 339))
+ return -EINVAL; /* 1984 <-> 2239 */
+
+ r[0] = tm->tm_sec;
+ r[1] = (tm->tm_hour << 8) | tm->tm_min;
+ r[2] = (tm->tm_wday << 8) | (tm->tm_mday + 1);
+ r[3] = ((tm->tm_year - 212) << 8) | (tm->tm_mon + 1);
+
+ /* disable write protect */
+ __raw_writew(0x0, M5441X_RTC_CR);
+ __raw_writew(0x1, M5441X_RTC_CR);
+ __raw_writew(0x3, M5441X_RTC_CR);
+ __raw_writew(0x2, M5441X_RTC_CR);
+
+ __raw_writew(r[0], M5441X_RTC_SECONDS);
+ __raw_writew(r[1], M5441X_RTC_HOURMIN);
+ __raw_writew(r[2], M5441X_RTC_DAYS);
+ __raw_writew(r[3], M5441X_RTC_YEARMON);
+
+ /* enable write protect */
+ __raw_writew(0x2, M5441X_RTC_CR);
+
+ return 0;
+}
+
+static int m5441x_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alm)
+{
+ u16 r[4];
+
+ r[0] = __raw_readw(M5441X_RTC_ALM_SEC);
+ r[1] = __raw_readw(M5441X_RTC_ALM_HM);
+ r[2] = __raw_readw(M5441X_RTC_ALM_DAYS);
+ r[3] = __raw_readw(M5441X_RTC_ALM_YRMON);
+
+ alm->time.tm_sec = r[0] & 0xff;
+ alm->time.tm_min = r[1] & 0xff;
+ alm->time.tm_hour = r[1] >> 8;
+ alm->time.tm_mday = (r[2] & 0xff) - 1;
+ alm->time.tm_mon = (r[3] & 0xff) - 1;
+ alm->time.tm_year = (((s16)r[3]) >> 8) + 212;
+
+ alm->enabled = !!(__raw_readw(M5441X_RTC_IER) & M5441X_RTC_IER_ALM);
+
+ return 0;
+}
+
+static int m5441x_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alm)
+{
+ u16 r[4];
+
+ if ((alm->time.tm_year < 84) || (alm->time.tm_year > 339))
+ return -EINVAL;
+
+ r[0] = alm->time.tm_sec;
+ r[1] = (alm->time.tm_hour << 8) | alm->time.tm_min;
+ r[2] = alm->time.tm_mday + 1;
+ r[3] = ((alm->time.tm_year - 212) << 8) | (alm->time.tm_mon + 1);
+
+ local_irq_disable();
+
+ /* disable write protect */
+ __raw_writew(0x0, M5441X_RTC_CR);
+ __raw_writew(0x1, M5441X_RTC_CR);
+ __raw_writew(0x3, M5441X_RTC_CR);
+ __raw_writew(0x2, M5441X_RTC_CR);
+
+ __raw_writew(r[0], M5441X_RTC_ALM_SEC);
+ __raw_writew(r[1], M5441X_RTC_ALM_HM);
+ __raw_writew(r[2], M5441X_RTC_ALM_DAYS);
+ __raw_writew(r[3], M5441X_RTC_ALM_YRMON);
+
+ r[0] = __raw_readw(M5441X_RTC_IER);
+ if (alm->enabled)
+ r[0] |= M5441X_RTC_IER_ALM;
+ else
+ r[0] &= ~M5441X_RTC_IER_ALM;
+ __raw_writew(r[0], M5441X_RTC_IER);
+
+ /* enable write protect */
+ __raw_writew(0x2, M5441X_RTC_CR);
+
+ local_irq_enable();
+
+ return 0;
+}
+
+static int m5441x_rtc_alarm_irq_enable(struct device *dev, unsigned int enable)
+{
+ u16 r;
+
+ local_irq_disable();
+
+ /* disable write protect */
+ __raw_writew(0x0, M5441X_RTC_CR);
+ __raw_writew(0x1, M5441X_RTC_CR);
+ __raw_writew(0x3, M5441X_RTC_CR);
+ __raw_writew(0x2, M5441X_RTC_CR);
+
+ r = __raw_readw(M5441X_RTC_IER);
+ if (enable)
+ r |= M5441X_RTC_IER_ALM;
+ else
+ r &= ~M5441X_RTC_IER_ALM;
+ __raw_writew(r, M5441X_RTC_IER);
+
+ /* enable write protect */
+ __raw_writew(0x2, M5441X_RTC_CR);
+
+ local_irq_enable();
+
+ return 0;
+}
+
+static struct rtc_class_ops m5441x_rtc_ops = {
+ .read_time = m5441x_rtc_read_time,
+ .set_time = m5441x_rtc_set_time,
+ .read_alarm = m5441x_rtc_read_alarm,
+ .set_alarm = m5441x_rtc_set_alarm,
+ .alarm_irq_enable = m5441x_rtc_alarm_irq_enable,
+};
+
+static irqreturn_t m5441x_rtc_irq_handler(int irq, void *rtc)
+{
+ unsigned long events = 0;
+ u16 r;
+
+ /* disable write protect */
+ __raw_writew(0x0, M5441X_RTC_CR);
+ __raw_writew(0x1, M5441X_RTC_CR);
+ __raw_writew(0x3, M5441X_RTC_CR);
+ __raw_writew(0x2, M5441X_RTC_CR);
+
+ while ((r = __raw_readw(M5441X_RTC_ISR)) != 0) {
+ if (r & M5441X_RTC_ISR_ALM)
+ events |= RTC_IRQF | RTC_AF;
+ if (r & M5441X_RTC_ISR_1HZ)
+ events |= RTC_IRQF | RTC_UF;
+ __raw_writew(r, M5441X_RTC_ISR);
+ }
+
+ /* enable write protect */
+ __raw_writew(0x2, M5441X_RTC_CR);
+
+ if (events)
+ rtc_update_irq(rtc, 1, events);
+ return IRQ_HANDLED;
+}
+
+static int __devinit m5441x_rtc_probe(struct platform_device *pdev)
+{
+ struct rtc_device *m5441x_rtc;
+ struct resource *res;
+ int status;
+
+ m5441x_rtc_irq = platform_get_irq(pdev, 0);
+ if (m5441x_rtc_irq < 0) {
+ dev_dbg(&pdev->dev, "platform_get_irq failed\n");
+ return -ENOENT;
+ }
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_dbg(&pdev->dev, "platform_get_resource failed\n");
+ return -ENOENT;
+ }
+ if (!request_mem_region(res->start, resource_size(res), pdev->name)) {
+ dev_dbg(&pdev->dev, "request_mem_region failed\n");
+ return -EBUSY;
+ }
+
+ m5441x_rtc_base = ioremap(res->start, resource_size(res));
+ if (!m5441x_rtc_base) {
+ dev_dbg(&pdev->dev, "ioremap failed\n");
+ status = -ENXIO;
+ goto fail0;
+ }
+
+ m5441x_rtc_clk = clk_get(&pdev->dev, NULL);
+ if (IS_ERR(m5441x_rtc_clk)) {
+ dev_dbg(&pdev->dev, "clk_get failed\n");
+ status = PTR_ERR(m5441x_rtc_clk);
+ goto fail1;
+ }
+
+ m5441x_rtc = rtc_device_register("mcfrtc", &pdev->dev, &m5441x_rtc_ops,
+ THIS_MODULE);
+ if (IS_ERR(m5441x_rtc)) {
+ dev_dbg(&pdev->dev, "rtc_device_register failed\n");
+ status = PTR_ERR(m5441x_rtc);
+ goto fail2;
+ }
+
+ platform_set_drvdata(pdev, m5441x_rtc);
+
+ if (__raw_readw(M5441X_RTC_SR) & M5441X_RTC_SR_PORB)
+ dev_info(&pdev->dev, "power lost? RTC time maybe invalid\n");
+
+ /* disable write protect */
+ __raw_writew(0x0, M5441X_RTC_CR);
+ __raw_writew(0x1, M5441X_RTC_CR);
+ __raw_writew(0x3, M5441X_RTC_CR);
+ __raw_writew(0x2, M5441X_RTC_CR);
+ /* clear any previous alarms */
+ __raw_writew(M5441X_RTC_CR_SWR, M5441X_RTC_CR);
+ /* alarm match on seconds/mmins/days/months/year */
+ __raw_writew(M5441X_RTC_CR_AM3, M5441X_RTC_CR);
+ /* 1Hz alarm */
+ __raw_writew(M5441X_RTC_IER_1HZ, M5441X_RTC_IER);
+ /* enable write protect */
+ __raw_writew(0x2, M5441X_RTC_CR);
+
+ status = request_irq(m5441x_rtc_irq, m5441x_rtc_irq_handler, 0,
+ pdev->name, m5441x_rtc);
+ if (status) {
+ dev_dbg(&pdev->dev, "request_irq failed\n");
+ goto fail3;
+ }
+ return 0;
+
+fail3:
+ platform_set_drvdata(pdev, NULL);
+ rtc_device_unregister(m5441x_rtc);
+fail2:
+ clk_disable(m5441x_rtc_clk);
+ clk_put(m5441x_rtc_clk);
+fail1:
+ iounmap(m5441x_rtc_base);
+fail0:
+ release_mem_region(res->start, resource_size(res));
+ dev_dbg(&pdev->dev, "probe failed\n");
+
+ return status;
+}
+
+static int __devexit m5441x_rtc_remove(struct platform_device *pdev)
+{
+ struct rtc_device *m5441x_rtc = platform_get_drvdata(pdev);
+ struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+
+ free_irq(m5441x_rtc_irq, m5441x_rtc);
+ platform_set_drvdata(pdev, NULL);
+ rtc_device_unregister(m5441x_rtc);
+ clk_disable(m5441x_rtc_clk);
+ clk_put(m5441x_rtc_clk);
+ iounmap(m5441x_rtc_base);
+ release_mem_region(res->start, resource_size(res));
+
+ return 0;
+}
+
+static struct platform_driver m5441x_rtc_driver = {
+ .driver.name = "mcfrtc",
+ .driver.owner = THIS_MODULE,
+ .remove = __devexit_p(m5441x_rtc_remove),
+};
+
+static int __init m5441x_rtc_init(void)
+{
+ return platform_driver_probe(&m5441x_rtc_driver, m5441x_rtc_probe);
+}
+module_init(m5441x_rtc_init);
+
+static void __exit m5441x_rtc_exit(void)
+{
+ platform_driver_unregister(&m5441x_rtc_driver);
+}
+module_exit(m5441x_rtc_exit);
+
+MODULE_AUTHOR("Steven King <sfking at fdwdc.com>");
+MODULE_DESCRIPTION("M5441x RTC driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:rtc");
Signed-off-by: Stven King <sfking at fdwdc.com>
---
drivers/rtc/Kconfig | 10 ++
drivers/rtc/Makefile | 1 +
drivers/rtc/rtc-m5441x.c | 363 ++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 374 insertions(+)
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index 08cbdb9..da2b3c4 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -1087,4 +1087,14 @@ config RTC_DRV_MXC
This driver can also be built as a module, if so, the module
will be called "rtc-mxc".
+config RTC_DRV_M5441x
+ tristate "Freescale Coldfire M5441x RTC support"
+ depends on M5441x
+ help
+ This enables support for the RTC on the Freescale Coldfire 5441x
+ (54410/54415/54416/54417/54418).
+
+ This driver can also be built as a module. If so, the module
+ will be called rtc-m5441x.
+
endif # RTC_CLASS
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index 2973921..a9ded2c 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -61,6 +61,7 @@ obj-$(CONFIG_RTC_DRV_M41T94) += rtc-m41t94.o
obj-$(CONFIG_RTC_DRV_M48T35) += rtc-m48t35.o
obj-$(CONFIG_RTC_DRV_M48T59) += rtc-m48t59.o
obj-$(CONFIG_RTC_DRV_M48T86) += rtc-m48t86.o
+obj-$(CONFIG_RTC_DRV_M5441x) += rtc-m5441x.o
obj-$(CONFIG_RTC_DRV_MXC) += rtc-mxc.o
obj-$(CONFIG_RTC_DRV_MAX6900) += rtc-max6900.o
obj-$(CONFIG_RTC_DRV_MAX8925) += rtc-max8925.o
diff --git a/drivers/rtc/rtc-m5441x.c b/drivers/rtc/rtc-m5441x.c
new file mode 100644
index 0000000..ba677f3
--- /dev/null
+++ b/drivers/rtc/rtc-m5441x.c
@@ -0,0 +1,363 @@
+/*
+ * RTC driver for the Freescale Coldfire 5441x SoCs.
+ *
+ * Copyright(C) 2012, Steven King <sfking at fdwdc.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/clk.h>
+#include <linux/rtc.h>
+
+static void __iomem *m5441x_rtc_base;
+static int m5441x_rtc_irq;
+static struct clk *m5441x_rtc_clk;
+
+#define M5441X_RTC_YEARMON (m5441x_rtc_base + 0x00)
+#define M5441X_RTC_DAYS (m5441x_rtc_base + 0x02)
+#define M5441X_RTC_HOURMIN (m5441x_rtc_base + 0x04)
+#define M5441X_RTC_SECONDS (m5441x_rtc_base + 0x06)
+#define M5441X_RTC_ALM_YRMON (m5441x_rtc_base + 0x08)
+#define M5441X_RTC_ALM_DAYS (m5441x_rtc_base + 0x0a)
+#define M5441X_RTC_ALM_HM (m5441x_rtc_base + 0x0c)
+#define M5441X_RTC_ALM_SEC (m5441x_rtc_base + 0x0e)
+#define M5441X_RTC_CR (m5441x_rtc_base + 0x10)
+#define M5441X_RTC_CR_AM0 (0 << 2) /* secs/mins/hrs */
+#define M5441X_RTC_CR_AM1 (1 << 2) /* secs/mins/days */
+#define M5441X_RTC_CR_AM2 (2 << 2) /* secs/mins/days/mons */
+#define M5441X_RTC_CR_AM3 (3 << 2) /* alarm match all */
+#define M5441X_RTC_CR_DTDEN (1 << 6) /* DST enable */
+#define M5441X_RTC_CR_BCDEN (1 << 7) /* BCD enable */
+#define M5441X_RTC_CR_SWR (1 << 8) /* software reset */
+#define M5441X_RTC_SR (m5441x_rtc_base + 0x12)
+#define M5441X_RTC_SR_INVAL (1 << 0) /* time invalid */
+#define M5441X_RTC_SR_CDON (1 << 1) /* Compensation done */
+#define M5441X_RTC_SR_BERR (1 << 2) /* Bus err */
+#define M5441X_RTC_SR_WPE (1 << 4) /* write protect enabled */
+#define M5441X_RTC_SR_PORB (1 << 6) /* power on reset boot */
+#define M5441X_RTC_ISR (m5441x_rtc_base + 0x14)
+#define M5441X_RTC_ISR_STW (1 << 1) /* Stop watch */
+#define M5441X_RTC_ISR_ALM (1 << 2) /* Alarm */
+#define M5441X_RTC_ISR_DAY (1 << 3) /* day counter inc */
+#define M5441X_RTC_ISR_HR (1 << 4) /* hour counter inc */
+#define M5441X_RTC_ISR_MIN (1 << 5) /* minute counter inc */
+#define M5441X_RTC_ISR_1HZ (1 << 6) /* second counter inc */
+#define M5441X_RTC_ISR_2HZ (1 << 7) /* 2Hz counter inc */
+#define M5441X_RTC_IER (m5441x_rtc_base + 0x16)
+#define M5441X_RTC_IER_STW (1 << 1) /* Stop watch */
+#define M5441X_RTC_IER_ALM (1 << 2) /* Alarm */
+#define M5441X_RTC_IER_DAY (1 << 3) /* day counter inc */
+#define M5441X_RTC_IER_HR (1 << 4) /* hour counter inc */
+#define M5441X_RTC_IER_MIN (1 << 5) /* minute counter inc */
+#define M5441X_RTC_IER_1HZ (1 << 6) /* second counter inc */
+#define M5441X_RTC_IER_2HZ (1 << 7) /* 2Hz counter inc */
+#define M5441X_RTC_COUNT_DN (m5441x_rtc_base + 0x18)
+#define M5441X_RTC_CFG_DATA (m5441x_rtc_base + 0x20)
+#define M5441X_RTC_DST_HOUR (m5441x_rtc_base + 0x22)
+#define M5441X_RTC_DST_MON (m5441x_rtc_base + 0x24)
+#define M5441X_RTC_DST_DAY (m5441x_rtc_base + 0x26)
+#define M5441X_RTC_RTC_COMPEN (m5441x_rtc_base + 0x28)
+#define M5441X_RTC_UP_CNTRH (m5441x_rtc_base + 0x32)
+#define M5441X_RTC_UP_CNTRL (m5441x_rtc_base + 0x24)
+#define M5441X_RTC_Standy_RAM (m5441x_rtc_base + 0x40)
+
+static int m5441x_rtc_read_time(struct device *dev, struct rtc_time *tm)
+{
+ u16 r[4];
+
+ do {
+ r[0] = __raw_readw(M5441X_RTC_SECONDS);
+ r[1] = __raw_readw(M5441X_RTC_HOURMIN);
+ r[2] = __raw_readw(M5441X_RTC_DAYS);
+ r[3] = __raw_readw(M5441X_RTC_YEARMON);
+ } while (r[0] != __raw_readw(M5441X_RTC_SECONDS));
+
+ tm->tm_sec = r[0];
+ tm->tm_min = r[1] & 0xff;
+ tm->tm_hour = r[1] >> 8;
+ tm->tm_mday = (r[2] & 0xff) - 1;
+ tm->tm_wday = r[2] >> 8;
+ tm->tm_mon = (r[3] & 0xff) - 1;
+ tm->tm_year = (((s16)r[3]) >> 8) + 212;
+
+ return rtc_valid_tm(tm);
+}
+
+static int m5441x_rtc_set_time(struct device *dev, struct rtc_time *tm)
+{
+ u16 r[4];
+
+ if ((tm->tm_year < 84) || (tm->tm_year > 339))
+ return -EINVAL; /* 1984 <-> 2239 */
+
+ r[0] = tm->tm_sec;
+ r[1] = (tm->tm_hour << 8) | tm->tm_min;
+ r[2] = (tm->tm_wday << 8) | (tm->tm_mday + 1);
+ r[3] = ((tm->tm_year - 212) << 8) | (tm->tm_mon + 1);
+
+ /* disable write protect */
+ __raw_writew(0x0, M5441X_RTC_CR);
+ __raw_writew(0x1, M5441X_RTC_CR);
+ __raw_writew(0x3, M5441X_RTC_CR);
+ __raw_writew(0x2, M5441X_RTC_CR);
+
+ __raw_writew(r[0], M5441X_RTC_SECONDS);
+ __raw_writew(r[1], M5441X_RTC_HOURMIN);
+ __raw_writew(r[2], M5441X_RTC_DAYS);
+ __raw_writew(r[3], M5441X_RTC_YEARMON);
+
+ /* enable write protect */
+ __raw_writew(0x2, M5441X_RTC_CR);
+
+ return 0;
+}
+
+static int m5441x_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alm)
+{
+ u16 r[4];
+
+ r[0] = __raw_readw(M5441X_RTC_ALM_SEC);
+ r[1] = __raw_readw(M5441X_RTC_ALM_HM);
+ r[2] = __raw_readw(M5441X_RTC_ALM_DAYS);
+ r[3] = __raw_readw(M5441X_RTC_ALM_YRMON);
+
+ alm->time.tm_sec = r[0] & 0xff;
+ alm->time.tm_min = r[1] & 0xff;
+ alm->time.tm_hour = r[1] >> 8;
+ alm->time.tm_mday = (r[2] & 0xff) - 1;
+ alm->time.tm_mon = (r[3] & 0xff) - 1;
+ alm->time.tm_year = (((s16)r[3]) >> 8) + 212;
+
+ alm->enabled = !!(__raw_readw(M5441X_RTC_IER) & M5441X_RTC_IER_ALM);
+
+ return 0;
+}
+
+static int m5441x_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alm)
+{
+ u16 r[4];
+
+ if ((alm->time.tm_year < 84) || (alm->time.tm_year > 339))
+ return -EINVAL;
+
+ r[0] = alm->time.tm_sec;
+ r[1] = (alm->time.tm_hour << 8) | alm->time.tm_min;
+ r[2] = alm->time.tm_mday + 1;
+ r[3] = ((alm->time.tm_year - 212) << 8) | (alm->time.tm_mon + 1);
+
+ local_irq_disable();
+
+ /* disable write protect */
+ __raw_writew(0x0, M5441X_RTC_CR);
+ __raw_writew(0x1, M5441X_RTC_CR);
+ __raw_writew(0x3, M5441X_RTC_CR);
+ __raw_writew(0x2, M5441X_RTC_CR);
+
+ __raw_writew(r[0], M5441X_RTC_ALM_SEC);
+ __raw_writew(r[1], M5441X_RTC_ALM_HM);
+ __raw_writew(r[2], M5441X_RTC_ALM_DAYS);
+ __raw_writew(r[3], M5441X_RTC_ALM_YRMON);
+
+ r[0] = __raw_readw(M5441X_RTC_IER);
+ if (alm->enabled)
+ r[0] |= M5441X_RTC_IER_ALM;
+ else
+ r[0] &= ~M5441X_RTC_IER_ALM;
+ __raw_writew(r[0], M5441X_RTC_IER);
+
+ /* enable write protect */
+ __raw_writew(0x2, M5441X_RTC_CR);
+
+ local_irq_enable();
+
+ return 0;
+}
+
+static int m5441x_rtc_alarm_irq_enable(struct device *dev, unsigned int enable)
+{
+ u16 r;
+
+ local_irq_disable();
+
+ /* disable write protect */
+ __raw_writew(0x0, M5441X_RTC_CR);
+ __raw_writew(0x1, M5441X_RTC_CR);
+ __raw_writew(0x3, M5441X_RTC_CR);
+ __raw_writew(0x2, M5441X_RTC_CR);
+
+ r = __raw_readw(M5441X_RTC_IER);
+ if (enable)
+ r |= M5441X_RTC_IER_ALM;
+ else
+ r &= ~M5441X_RTC_IER_ALM;
+ __raw_writew(r, M5441X_RTC_IER);
+
+ /* enable write protect */
+ __raw_writew(0x2, M5441X_RTC_CR);
+
+ local_irq_enable();
+
+ return 0;
+}
+
+static struct rtc_class_ops m5441x_rtc_ops = {
+ .read_time = m5441x_rtc_read_time,
+ .set_time = m5441x_rtc_set_time,
+ .read_alarm = m5441x_rtc_read_alarm,
+ .set_alarm = m5441x_rtc_set_alarm,
+ .alarm_irq_enable = m5441x_rtc_alarm_irq_enable,
+};
+
+static irqreturn_t m5441x_rtc_irq_handler(int irq, void *rtc)
+{
+ unsigned long events = 0;
+ u16 r;
+
+ /* disable write protect */
+ __raw_writew(0x0, M5441X_RTC_CR);
+ __raw_writew(0x1, M5441X_RTC_CR);
+ __raw_writew(0x3, M5441X_RTC_CR);
+ __raw_writew(0x2, M5441X_RTC_CR);
+
+ while ((r = __raw_readw(M5441X_RTC_ISR)) != 0) {
+ if (r & M5441X_RTC_ISR_ALM)
+ events |= RTC_IRQF | RTC_AF;
+ if (r & M5441X_RTC_ISR_1HZ)
+ events |= RTC_IRQF | RTC_UF;
+ __raw_writew(r, M5441X_RTC_ISR);
+ }
+
+ /* enable write protect */
+ __raw_writew(0x2, M5441X_RTC_CR);
+
+ if (events)
+ rtc_update_irq(rtc, 1, events);
+ return IRQ_HANDLED;
+}
+
+static int __devinit m5441x_rtc_probe(struct platform_device *pdev)
+{
+ struct rtc_device *m5441x_rtc;
+ struct resource *res;
+ int status;
+
+ m5441x_rtc_irq = platform_get_irq(pdev, 0);
+ if (m5441x_rtc_irq < 0) {
+ dev_dbg(&pdev->dev, "platform_get_irq failed\n");
+ return -ENOENT;
+ }
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_dbg(&pdev->dev, "platform_get_resource failed\n");
+ return -ENOENT;
+ }
+ if (!request_mem_region(res->start, resource_size(res), pdev->name)) {
+ dev_dbg(&pdev->dev, "request_mem_region failed\n");
+ return -EBUSY;
+ }
+
+ m5441x_rtc_base = ioremap(res->start, resource_size(res));
+ if (!m5441x_rtc_base) {
+ dev_dbg(&pdev->dev, "ioremap failed\n");
+ status = -ENXIO;
+ goto fail0;
+ }
+
+ m5441x_rtc_clk = clk_get(&pdev->dev, NULL);
+ if (IS_ERR(m5441x_rtc_clk)) {
+ dev_dbg(&pdev->dev, "clk_get failed\n");
+ status = PTR_ERR(m5441x_rtc_clk);
+ goto fail1;
+ }
+
+ m5441x_rtc = rtc_device_register("mcfrtc", &pdev->dev, &m5441x_rtc_ops,
+ THIS_MODULE);
+ if (IS_ERR(m5441x_rtc)) {
+ dev_dbg(&pdev->dev, "rtc_device_register failed\n");
+ status = PTR_ERR(m5441x_rtc);
+ goto fail2;
+ }
+
+ platform_set_drvdata(pdev, m5441x_rtc);
+
+ if (__raw_readw(M5441X_RTC_SR) & M5441X_RTC_SR_PORB)
+ dev_info(&pdev->dev, "power lost? RTC time maybe invalid\n");
+
+ /* disable write protect */
+ __raw_writew(0x0, M5441X_RTC_CR);
+ __raw_writew(0x1, M5441X_RTC_CR);
+ __raw_writew(0x3, M5441X_RTC_CR);
+ __raw_writew(0x2, M5441X_RTC_CR);
+ /* clear any previous alarms */
+ __raw_writew(M5441X_RTC_CR_SWR, M5441X_RTC_CR);
+ /* alarm match on seconds/mmins/days/months/year */
+ __raw_writew(M5441X_RTC_CR_AM3, M5441X_RTC_CR);
+ /* 1Hz alarm */
+ __raw_writew(M5441X_RTC_IER_1HZ, M5441X_RTC_IER);
+ /* enable write protect */
+ __raw_writew(0x2, M5441X_RTC_CR);
+
+ status = request_irq(m5441x_rtc_irq, m5441x_rtc_irq_handler, 0,
+ pdev->name, m5441x_rtc);
+ if (status) {
+ dev_dbg(&pdev->dev, "request_irq failed\n");
+ goto fail3;
+ }
+ return 0;
+
+fail3:
+ platform_set_drvdata(pdev, NULL);
+ rtc_device_unregister(m5441x_rtc);
+fail2:
+ clk_disable(m5441x_rtc_clk);
+ clk_put(m5441x_rtc_clk);
+fail1:
+ iounmap(m5441x_rtc_base);
+fail0:
+ release_mem_region(res->start, resource_size(res));
+ dev_dbg(&pdev->dev, "probe failed\n");
+
+ return status;
+}
+
+static int __devexit m5441x_rtc_remove(struct platform_device *pdev)
+{
+ struct rtc_device *m5441x_rtc = platform_get_drvdata(pdev);
+ struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+
+ free_irq(m5441x_rtc_irq, m5441x_rtc);
+ platform_set_drvdata(pdev, NULL);
+ rtc_device_unregister(m5441x_rtc);
+ clk_disable(m5441x_rtc_clk);
+ clk_put(m5441x_rtc_clk);
+ iounmap(m5441x_rtc_base);
+ release_mem_region(res->start, resource_size(res));
+
+ return 0;
+}
+
+static struct platform_driver m5441x_rtc_driver = {
+ .driver.name = "mcfrtc",
+ .driver.owner = THIS_MODULE,
+ .remove = __devexit_p(m5441x_rtc_remove),
+};
+
+static int __init m5441x_rtc_init(void)
+{
+ return platform_driver_probe(&m5441x_rtc_driver, m5441x_rtc_probe);
+}
+module_init(m5441x_rtc_init);
+
+static void __exit m5441x_rtc_exit(void)
+{
+ platform_driver_unregister(&m5441x_rtc_driver);
+}
+module_exit(m5441x_rtc_exit);
+
+MODULE_AUTHOR("Steven King <sfking at fdwdc.com>");
+MODULE_DESCRIPTION("M5441x RTC driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:rtc");