summaryrefslogtreecommitdiff
path: root/drivers/rtc/rtc-mxs.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/rtc/rtc-mxs.c')
-rw-r--r--drivers/rtc/rtc-mxs.c322
1 files changed, 322 insertions, 0 deletions
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");