Add support for the Coldfire m5441x on-chip rtc. Signed-off-by: Stven King <sfk...@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 <sfk...@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 <sfk...@fdwdc.com>"); +MODULE_DESCRIPTION("M5441x RTC driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:rtc"); _______________________________________________ uClinux-dev mailing list uClinux-dev@uclinux.org http://mailman.uclinux.org/mailman/listinfo/uclinux-dev This message was resent by uclinux-dev@uclinux.org To unsubscribe see: http://mailman.uclinux.org/mailman/options/uclinux-dev