aboutsummaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorRobin Gong <B38343@freescale.com>2011-10-10 18:51:17 +0800
committerEric Miao <eric.miao@canonical.com>2011-11-10 07:38:52 +0800
commitd481eff1798ee81324b44defddb1c49d7ff6adf1 (patch)
treea666afd52072394ce1792216d19615856402dce1 /drivers
parentef0ab88beb87571db82dbeaf57f46035e1541131 (diff)
ENGR00159530-4 mc34708:add new mc34708's rtc driver
Add new mc34708's rtc driver based on new pmic core driver Signed-off-by: Robin Gong <B38343@freescale.com>
Diffstat (limited to 'drivers')
-rw-r--r--drivers/rtc/Kconfig7
-rw-r--r--drivers/rtc/Makefile1
-rw-r--r--drivers/rtc/rtc-mc34708.c471
3 files changed, 479 insertions, 0 deletions
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index de3a2181bd0..d27ebda954d 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -970,6 +970,13 @@ config RTC_DRV_MC13XXX
This enables support for the RTCs found on Freescale's PMICs
MC13783 and MC13892.
+config RTC_DRV_MC34708
+ depends on MFD_MC_PMIC
+ tristate "Freescale MC34708 RTC"
+ help
+ This enables support for the RTCs found on Freescale's PMICs
+ MC34708.
+
config RTC_DRV_MPC5121
tristate "Freescale MPC5121 built-in RTC"
depends on PPC_MPC512x && RTC_CLASS
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index 4f5a62585a4..ec91de51e3d 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -102,5 +102,6 @@ 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_DRV_MXC_V2) += rtc-mxc_v2.o
+obj-$(CONFIG_RTC_DRV_MC34708) += rtc-mc34708.o
obj-$(CONFIG_RTC_DRV_SNVS) += rtc-snvs.o
obj-$(CONFIG_RTC_DRV_DA9052) += rtc-da9052.o
diff --git a/drivers/rtc/rtc-mc34708.c b/drivers/rtc/rtc-mc34708.c
new file mode 100644
index 00000000000..31e58f9e9cf
--- /dev/null
+++ b/drivers/rtc/rtc-mc34708.c
@@ -0,0 +1,471 @@
+
+/*
+ * RTC Driver for Freescale MC34708 PMIC
+ *
+ * Copyright (C) 2004-2011 Freescale Semiconductor, Inc.
+ *
+ * Based on mc13xxx rtc driver :
+ * Copyright 2009 Pengutronix, Sascha Hauer <s.hauer@pengutronix.de>
+ * Copyright 2009 Pengutronix, Uwe Kleine-Koenig
+ * <u.kleine-koenig@pengutronix.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+#include <linux/platform_device.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/rtc.h>
+#include <linux/mfd/mc-pmic.h>
+
+#define DRIVER_NAME "mc34708-rtc"
+
+#define MC34708_RTCTOD 20
+#define MC34708_RTCTODA 21
+#define MC34708_RTCDAY 22
+#define MC34708_RTCDAYA 23
+
+struct mc34708_rtc {
+ struct rtc_device *rtc;
+ struct mc_pmic *mc_pmic;
+ int valid;
+ int hz_alarm; /*workaround the 1hz_alarm loss interrupt */
+};
+
+static int
+mc34708_rtc_irq_enable_unlocked(struct device *dev,
+ unsigned int enabled, int irq)
+{
+ struct mc34708_rtc *priv = dev_get_drvdata(dev);
+ int (*func) (struct mc_pmic *mc_pmic, int irq);
+
+ if (!priv->valid)
+ return -ENODATA;
+
+ func = enabled ? mc_pmic_irq_unmask : mc_pmic_irq_mask;
+ return func(priv->mc_pmic, irq);
+}
+
+static int
+mc34708_rtc_irq_enable(struct device *dev, unsigned int enabled, int irq)
+{
+ struct mc34708_rtc *priv = dev_get_drvdata(dev);
+ int ret;
+
+ mc_pmic_lock(priv->mc_pmic);
+
+ ret = mc34708_rtc_irq_enable_unlocked(dev, enabled, irq);
+
+ mc_pmic_unlock(priv->mc_pmic);
+
+ return ret;
+}
+
+static int mc34708_rtc_read_time(struct device *dev, struct rtc_time *tm)
+{
+ struct mc34708_rtc *priv = dev_get_drvdata(dev);
+ unsigned int seconds, days1, days2;
+ unsigned long s1970;
+ int ret;
+
+ mc_pmic_lock(priv->mc_pmic);
+
+ if (!priv->valid) {
+ ret = -ENODATA;
+ goto out;
+ }
+
+ ret = mc_pmic_reg_read(priv->mc_pmic, MC34708_RTCDAY, &days1);
+ if (unlikely(ret))
+ goto out;
+
+ ret = mc_pmic_reg_read(priv->mc_pmic, MC34708_RTCTOD, &seconds);
+ if (unlikely(ret))
+ goto out;
+
+ ret = mc_pmic_reg_read(priv->mc_pmic, MC34708_RTCDAY, &days2);
+ out:
+ mc_pmic_unlock(priv->mc_pmic);
+
+ if (ret)
+ return ret;
+
+ if (days2 == days1 + 1) {
+ if (seconds >= 86400 / 2)
+ days2 = days1;
+ else
+ days1 = days2;
+ }
+
+ if (days1 != days2)
+ return -EIO;
+
+ s1970 = days1 * 86400 + seconds;
+
+ rtc_time_to_tm(s1970, tm);
+
+ return rtc_valid_tm(tm);
+}
+
+static int mc34708_rtc_set_mmss(struct device *dev, unsigned long secs)
+{
+ struct mc34708_rtc *priv = dev_get_drvdata(dev);
+ unsigned int seconds, days;
+ unsigned int alarmseconds;
+ int ret;
+
+ seconds = secs % 86400;
+ days = secs / 86400;
+
+ mc_pmic_lock(priv->mc_pmic);
+
+ /*
+ * temporarily invalidate alarm to prevent triggering it when the day is
+ * already updated while the time isn't yet.
+ */
+ ret = mc_pmic_reg_read(priv->mc_pmic, MC34708_RTCTODA, &alarmseconds);
+ if (unlikely(ret))
+ goto out;
+
+ if (alarmseconds < 86400) {
+ ret =
+ mc_pmic_reg_write(priv->mc_pmic, MC34708_RTCTODA, 0x1ffff);
+ if (unlikely(ret))
+ goto out;
+ }
+
+ /*
+ * write seconds=0 to prevent a day switch between writing days
+ * and seconds below
+ */
+ ret = mc_pmic_reg_write(priv->mc_pmic, MC34708_RTCTOD, 0);
+ if (unlikely(ret))
+ goto out;
+
+ ret = mc_pmic_reg_write(priv->mc_pmic, MC34708_RTCDAY, days);
+ if (unlikely(ret))
+ goto out;
+
+ ret = mc_pmic_reg_write(priv->mc_pmic, MC34708_RTCTOD, seconds);
+ if (unlikely(ret))
+ goto out;
+
+ /* restore alarm */
+ if (alarmseconds < 86400) {
+ ret =
+ mc_pmic_reg_write(priv->mc_pmic, MC34708_RTCTODA,
+ alarmseconds);
+ if (unlikely(ret))
+ goto out;
+ }
+
+ ret = mc_pmic_irq_ack(priv->mc_pmic, MC_PMIC_IRQ_RTCRST);
+ if (unlikely(ret))
+ goto out;
+
+ ret = mc_pmic_irq_unmask(priv->mc_pmic, MC_PMIC_IRQ_RTCRST);
+ out:
+ priv->valid = !ret;
+
+ mc_pmic_unlock(priv->mc_pmic);
+
+ return ret;
+}
+
+static int mc34708_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm)
+{
+ struct mc34708_rtc *priv = dev_get_drvdata(dev);
+ unsigned seconds, days;
+ unsigned long s1970;
+ int enabled, pending;
+ int ret;
+
+ mc_pmic_lock(priv->mc_pmic);
+
+ ret = mc_pmic_reg_read(priv->mc_pmic, MC34708_RTCTODA, &seconds);
+ if (unlikely(ret))
+ goto out;
+ if (seconds >= 86400) {
+ ret = -ENODATA;
+ goto out;
+ }
+
+ ret = mc_pmic_reg_read(priv->mc_pmic, MC34708_RTCDAY, &days);
+ if (unlikely(ret))
+ goto out;
+
+ ret = mc_pmic_irq_status(priv->mc_pmic, MC_PMIC_IRQ_TODA,
+ &enabled, &pending);
+
+ out:
+ mc_pmic_unlock(priv->mc_pmic);
+
+ if (ret)
+ return ret;
+
+ alarm->enabled = enabled;
+ alarm->pending = pending;
+
+ s1970 = days * 86400 + seconds;
+
+ rtc_time_to_tm(s1970, &alarm->time);
+ dev_dbg(dev, "%s: %lu\n", __func__, s1970);
+
+ return 0;
+}
+
+static int mc34708_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm)
+{
+ struct mc34708_rtc *priv = dev_get_drvdata(dev);
+ unsigned long s1970;
+ unsigned seconds, days, seconds_now, days_now;
+ int ret;
+ mc_pmic_lock(priv->mc_pmic);
+ /* disable alarm to prevent false triggering */
+ ret = mc_pmic_reg_write(priv->mc_pmic, MC34708_RTCTODA, 0x1ffff);
+ if (unlikely(ret))
+ goto out;
+
+ ret = mc_pmic_irq_ack(priv->mc_pmic, MC_PMIC_IRQ_TODA);
+ if (unlikely(ret))
+ goto out;
+ ret = rtc_tm_to_time(&alarm->time, &s1970);
+ if (unlikely(ret))
+ goto out;
+
+ dev_dbg(dev, "%s: o%2.s %lu\n", __func__, alarm->enabled ? "n" : "ff",
+ s1970);
+
+ ret = mc34708_rtc_irq_enable_unlocked(dev, alarm->enabled,
+ MC_PMIC_IRQ_TODA);
+ if (unlikely(ret))
+ goto out;
+
+ seconds = s1970 % 86400;
+ days = s1970 / 86400;
+
+ ret = mc_pmic_reg_read(priv->mc_pmic, MC34708_RTCDAY, &days_now);
+ if (unlikely(ret))
+ goto out;
+ ret = mc_pmic_reg_read(priv->mc_pmic, MC34708_RTCTOD, &seconds_now);
+ if (unlikely(ret))
+ goto out;
+ if ((days_now == days) && (seconds == seconds_now + 1)) {
+ priv->hz_alarm = 1;
+ /*enable 1hz */
+ mc34708_rtc_irq_enable_unlocked(dev, 1, MC_PMIC_IRQ_1HZ);
+ dev_dbg(dev, "1HZ_ALARM enabled!\n");
+ goto out;
+ }
+ ret = mc_pmic_reg_write(priv->mc_pmic, MC34708_RTCDAYA, days);
+ if (unlikely(ret))
+ goto out;
+ ret = mc_pmic_reg_write(priv->mc_pmic, MC34708_RTCTODA, seconds);
+
+ out:
+ mc_pmic_unlock(priv->mc_pmic);
+ return ret;
+}
+
+static irqreturn_t mc34708_rtc_alarm_handler(int irq, void *dev)
+{
+ struct mc34708_rtc *priv = dev;
+ struct mc_pmic *mc_pmic = priv->mc_pmic;
+
+ dev_dbg(&priv->rtc->dev, "Alarm\n");
+
+ rtc_update_irq(priv->rtc, 1, RTC_IRQF | RTC_AF);
+
+ mc_pmic_irq_ack(mc_pmic, irq);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t mc34708_rtc_update_handler(int irq, void *dev)
+{
+ struct mc34708_rtc *priv = dev;
+ struct mc_pmic *mc_pmic = priv->mc_pmic;
+
+ if (priv->hz_alarm == 1) { /*replace alarm irq with 1hz irq */
+ rtc_update_irq(priv->rtc, 1, RTC_IRQF | RTC_AF);
+ priv->hz_alarm = 0;
+ mc_pmic_irq_ack(mc_pmic, irq);
+ mc_pmic_irq_mask(mc_pmic, irq); /*disable 1Hz */
+ dev_dbg(&priv->rtc->dev, "1HZ_ALARM!\n");
+
+ } else {
+ dev_dbg(&priv->rtc->dev, "1HZ\n");
+ rtc_update_irq(priv->rtc, 1, RTC_IRQF | RTC_UF);
+ mc_pmic_irq_ack(mc_pmic, irq);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int
+mc34708_rtc_update_irq_enable(struct device *dev, unsigned int enabled)
+{
+ return mc34708_rtc_irq_enable(dev, enabled, MC_PMIC_IRQ_1HZ);
+}
+
+static int
+mc34708_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled)
+{
+ return mc34708_rtc_irq_enable(dev, enabled, MC_PMIC_IRQ_TODA);
+}
+
+static const struct rtc_class_ops mc34708_rtc_ops = {
+ .read_time = mc34708_rtc_read_time,
+ .set_mmss = mc34708_rtc_set_mmss,
+ .read_alarm = mc34708_rtc_read_alarm,
+ .set_alarm = mc34708_rtc_set_alarm,
+ .alarm_irq_enable = mc34708_rtc_alarm_irq_enable,
+ .update_irq_enable = mc34708_rtc_update_irq_enable,
+};
+
+static irqreturn_t mc34708_rtc_reset_handler(int irq, void *dev)
+{
+ struct mc34708_rtc *priv = dev;
+ struct mc_pmic *mc_pmic = priv->mc_pmic;
+
+ dev_dbg(&priv->rtc->dev, "RTCRST\n");
+ priv->valid = 0;
+
+ mc_pmic_irq_mask(mc_pmic, irq);
+
+ return IRQ_HANDLED;
+}
+
+static int __devinit mc34708_rtc_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct mc34708_rtc *priv;
+ struct mc_pmic *mc_pmic;
+ int rtcrst_pending;
+
+ priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ mc_pmic = dev_get_drvdata(pdev->dev.parent);
+ priv->mc_pmic = mc_pmic;
+
+ platform_set_drvdata(pdev, priv);
+
+ mc_pmic_lock(mc_pmic);
+
+ ret = mc_pmic_irq_request_nounmask(mc_pmic, MC_PMIC_IRQ_RTCRST,
+ mc34708_rtc_reset_handler,
+ DRIVER_NAME, priv);
+ if (ret)
+ goto err_reset_irq_request;
+ ret = mc_pmic_irq_status(mc_pmic, MC_PMIC_IRQ_RTCRST,
+ NULL, &rtcrst_pending);
+ if (ret)
+ goto err_reset_irq_status;
+ priv->valid = rtcrst_pending;
+ if (priv->valid) {
+ /*if reset interrupt status have pending clear it */
+ ret = mc_pmic_irq_ack(priv->mc_pmic, MC_PMIC_IRQ_RTCRST);
+ if (ret)
+ goto err_reset_irq_status;
+ } else
+ priv->valid = 1;
+ mc34708_rtc_irq_enable_unlocked(&pdev->dev, 1, MC_PMIC_IRQ_RTCRST);
+ priv->hz_alarm = 0;
+ ret = mc_pmic_irq_request_nounmask(mc_pmic, MC_PMIC_IRQ_1HZ,
+ mc34708_rtc_update_handler,
+ DRIVER_NAME, priv);
+ if (ret)
+ goto err_update_irq_request;
+
+ ret = mc_pmic_irq_request_nounmask(mc_pmic, MC_PMIC_IRQ_TODA,
+ mc34708_rtc_alarm_handler,
+ DRIVER_NAME, priv);
+ if (ret)
+ goto err_alarm_irq_request;
+
+ priv->rtc = rtc_device_register(pdev->name,
+ &pdev->dev, &mc34708_rtc_ops,
+ THIS_MODULE);
+ if (IS_ERR(priv->rtc)) {
+ ret = PTR_ERR(priv->rtc);
+
+ mc_pmic_irq_free(mc_pmic, MC_PMIC_IRQ_TODA, priv);
+ err_alarm_irq_request:
+
+ mc_pmic_irq_free(mc_pmic, MC_PMIC_IRQ_1HZ, priv);
+ err_update_irq_request:
+
+ err_reset_irq_status:
+
+ mc_pmic_irq_free(mc_pmic, MC_PMIC_IRQ_RTCRST, priv);
+ err_reset_irq_request:
+
+ platform_set_drvdata(pdev, NULL);
+ kfree(priv);
+ }
+
+ mc_pmic_unlock(mc_pmic);
+
+ return ret;
+}
+
+static int __devexit mc34708_rtc_remove(struct platform_device *pdev)
+{
+ struct mc34708_rtc *priv = platform_get_drvdata(pdev);
+
+ mc_pmic_lock(priv->mc_pmic);
+
+ rtc_device_unregister(priv->rtc);
+
+ mc_pmic_irq_free(priv->mc_pmic, MC_PMIC_IRQ_TODA, priv);
+ mc_pmic_irq_free(priv->mc_pmic, MC_PMIC_IRQ_1HZ, priv);
+ mc_pmic_irq_free(priv->mc_pmic, MC_PMIC_IRQ_RTCRST, priv);
+
+ mc_pmic_unlock(priv->mc_pmic);
+
+ platform_set_drvdata(pdev, NULL);
+
+ kfree(priv);
+
+ return 0;
+}
+
+const struct platform_device_id mc34708_rtc_idtable[] = {
+ {
+ .name = "mc34708-rtc",
+ },
+
+};
+
+static struct platform_driver mc34708_rtc_driver = {
+ .id_table = mc34708_rtc_idtable,
+ .remove = __devexit_p(mc34708_rtc_remove),
+ .driver = {
+ .name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init mc34708_rtc_init(void)
+{
+ return platform_driver_probe(&mc34708_rtc_driver, &mc34708_rtc_probe);
+}
+
+module_init(mc34708_rtc_init);
+
+static void __exit mc34708_rtc_exit(void)
+{
+ platform_driver_unregister(&mc34708_rtc_driver);
+}
+
+module_exit(mc34708_rtc_exit);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("RTC driver for Freescale MC34708 PMIC");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" DRIVER_NAME);