aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHaojian Zhuang <haojian.zhuang@linaro.org>2013-03-25 00:13:47 +0800
committerZhangfei Gao <zhangfei.gao@linaro.org>2014-08-25 12:24:24 +0800
commit799771f6c7560646280f40dd046494db665769dc (patch)
tree5dadd4fd85606c5bf57783acad8cea4b563b16c1
parentac80462effdfb9688eb9525f166dca51e17d10d9 (diff)
rtc: add hi6421 rtccore-track-17
Support hi6421 rtc function. Signed-off-by: Haojian Zhuang <haojian.zhuang@linaro.org>
-rw-r--r--drivers/rtc/Kconfig11
-rw-r--r--drivers/rtc/Makefile1
-rw-r--r--drivers/rtc/rtc-hi6421.c259
3 files changed, 271 insertions, 0 deletions
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index a168e96142b9..519ee1db7dff 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -229,6 +229,17 @@ config RTC_DRV_LP8788
help
Say Y to enable support for the LP8788 RTC/ALARM driver.
+config RTC_DRV_HI6421
+ tristate "Hisilicon Hi6421 RTC"
+ depends on MFD_HI6421_PMIC
+ help
+ If you say yes here you get support for Hisilicon Hi6421 PMIC
+ RTC functions. If an interrupt is associated with the device,
+ the alarm functionality is supported.
+
+ This driver can also be built as a module. If so, the module
+ will be called rtc-hi6421.
+
config RTC_DRV_MAX6900
tristate "Maxim MAX6900"
help
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index 56f061c7c815..d57cd5c465fb 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -61,6 +61,7 @@ obj-$(CONFIG_RTC_DRV_EM3027) += rtc-em3027.o
obj-$(CONFIG_RTC_DRV_EP93XX) += rtc-ep93xx.o
obj-$(CONFIG_RTC_DRV_FM3130) += rtc-fm3130.o
obj-$(CONFIG_RTC_DRV_GENERIC) += rtc-generic.o
+obj-$(CONFIG_RTC_DRV_HI6421) += rtc-hi6421.o
obj-$(CONFIG_RTC_DRV_HID_SENSOR_TIME) += rtc-hid-sensor-time.o
obj-$(CONFIG_RTC_DRV_HYM8563) += rtc-hym8563.o
obj-$(CONFIG_RTC_DRV_IMXDI) += rtc-imxdi.o
diff --git a/drivers/rtc/rtc-hi6421.c b/drivers/rtc/rtc-hi6421.c
new file mode 100644
index 000000000000..50ec3911861b
--- /dev/null
+++ b/drivers/rtc/rtc-hi6421.c
@@ -0,0 +1,259 @@
+/*
+ * Hisilicon Hi6421 RTC driver
+ *
+ * Copyright (C) 2013 Hisilicon Ltd.
+ * Copyright (C) 2013 Linaro Ltd.
+ *
+ * Author: Haojian Zhuang <haojian.zhuang@linaro.org>
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/of_device.h>
+#include <linux/rtc.h>
+#include <linux/mfd/hi6421-pmic.h>
+
+#define REG_IRQ1 0x01
+#define REG_IRQM1 0x04
+#define REG_RTCDR0 0x58
+#define REG_RTCDR1 0x59
+#define REG_RTCDR2 0x5a
+#define REG_RTCDR3 0x5b
+#define REG_RTCMR0 0x5c
+#define REG_RTCMR1 0x5d
+#define REG_RTCMR2 0x5e
+#define REG_RTCMR3 0x5f
+#define REG_RTCLR0 0x60
+#define REG_RTCLR1 0x61
+#define REG_RTCLR2 0x62
+#define REG_RTCLR3 0x63
+#define REG_RTCCTRL 0x64
+#define REG_SOFT_RST 0x86
+
+#define ALARM_ON (1 << HI6421_IRQ_ALARM)
+
+struct hi6421_rtc_info {
+ struct rtc_device *rtc;
+ struct hi6421_pmic *pmic;
+ int irq;
+};
+
+static irqreturn_t hi6421_rtc_handler(int irq, void *data)
+{
+ struct hi6421_rtc_info *info = (struct hi6421_rtc_info *)data;
+
+ /* clear alarm status */
+ hi6421_pmic_rmw(info->pmic, REG_IRQ1, ALARM_ON, ALARM_ON);
+ rtc_update_irq(info->rtc, 1, RTC_AF);
+ return IRQ_HANDLED;
+}
+
+/* read 4 8-bit registers & covert it into a 32-bit data */
+static unsigned int hi6421_read_bulk(struct hi6421_pmic *pmic,
+ unsigned int addr)
+{
+ unsigned int data, sum = 0;
+ int i;
+
+ for (i = 0; i < 4; i++) {
+ data = hi6421_pmic_read(pmic, addr + i);
+ sum |= (data & 0xff) << (i * 8);
+ }
+ return sum;
+}
+
+/* write a 32-bit data into 4 8-bit registers */
+static void hi6421_write_bulk(struct hi6421_pmic *pmic, unsigned int addr,
+ unsigned int data)
+{
+ unsigned int value;
+ int i;
+
+ for (i = 0; i < 4; i++) {
+ value = (data >> (i * 8)) & 0xff;
+ hi6421_pmic_write(pmic, addr + i, value);
+ }
+}
+
+static int hi6421_rtc_read_time(struct device *dev, struct rtc_time *tm)
+{
+ struct hi6421_rtc_info *info = dev_get_drvdata(dev);
+ unsigned long ticks;
+
+ ticks = hi6421_read_bulk(info->pmic, REG_RTCDR0);
+ rtc_time_to_tm(ticks, tm);
+ return 0;
+}
+
+static int hi6421_rtc_set_time(struct device *dev, struct rtc_time *tm)
+{
+ struct hi6421_rtc_info *info = dev_get_drvdata(dev);
+ unsigned long ticks;
+
+ if ((tm->tm_year < 70) || (tm->tm_year > 138)) {
+ dev_dbg(dev, "Set time %d out of range. "
+ "Please set time between 1970 to 2038.\n",
+ 1900 + tm->tm_year);
+ return -EINVAL;
+ }
+ rtc_tm_to_time(tm, &ticks);
+ hi6421_write_bulk(info->pmic, REG_RTCLR0, ticks);
+ return 0;
+}
+
+static int hi6421_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+ struct hi6421_rtc_info *info = dev_get_drvdata(dev);
+ unsigned long ticks, data;
+
+ ticks = hi6421_read_bulk(info->pmic, REG_RTCMR0);
+ rtc_time_to_tm(ticks, &alrm->time);
+
+ data = hi6421_pmic_read(info->pmic, REG_IRQ1);
+ alrm->pending = (data & ALARM_ON) ? 1 : 0;
+
+ data = hi6421_pmic_read(info->pmic, REG_IRQM1);
+ alrm->enabled = (data & ALARM_ON) ? 0 : 1;
+ return 0;
+}
+
+/*
+ * Calculate the next alarm time given the requested alarm time mask
+ * and the current time.
+ */
+static void rtc_next_alarm_time(struct rtc_time *next, struct rtc_time *now,
+ struct rtc_time *alrm)
+{
+ unsigned long next_time;
+ unsigned long now_time;
+
+ next->tm_year = now->tm_year;
+ next->tm_mon = now->tm_mon;
+ next->tm_mday = now->tm_mday;
+ next->tm_hour = alrm->tm_hour;
+ next->tm_min = alrm->tm_min;
+ next->tm_sec = alrm->tm_sec;
+
+ rtc_tm_to_time(now, &now_time);
+ rtc_tm_to_time(next, &next_time);
+
+ if (next_time < now_time) {
+ /* Advance one day */
+ next_time += 60 * 60 * 24;
+ rtc_time_to_tm(next_time, next);
+ }
+}
+
+static int hi6421_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+ struct hi6421_rtc_info *info = dev_get_drvdata(dev);
+ struct rtc_time now_tm, alarm_tm;
+ unsigned long ticks;
+
+ /* load 32-bit read-only counter */
+ ticks = hi6421_read_bulk(info->pmic, REG_RTCDR0);
+ rtc_time_to_tm(ticks, &now_tm);
+ dev_dbg(dev, "%s, now time : %lu\n", __func__, ticks);
+ rtc_next_alarm_time(&alarm_tm, &now_tm, &alrm->time);
+
+ /* get new ticks for alarm in 24 hours */
+ rtc_tm_to_time(&alarm_tm, &ticks);
+ dev_dbg(dev, "%s, alarm time: %lu\n", __func__, ticks);
+ if (alrm->enabled)
+ hi6421_write_bulk(info->pmic, REG_RTCMR0, ticks);
+ return 0;
+}
+
+static int hi6421_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled)
+{
+ struct hi6421_rtc_info *info = dev_get_drvdata(dev);
+ unsigned int data = 0;
+
+ if (!enabled)
+ data = ALARM_ON;
+ hi6421_pmic_rmw(info->pmic, REG_IRQM1, ALARM_ON, data);
+ return 0;
+}
+
+static const struct rtc_class_ops hi6421_rtc_ops = {
+ .read_time = hi6421_rtc_read_time,
+ .set_time = hi6421_rtc_set_time,
+ .read_alarm = hi6421_rtc_read_alarm,
+ .set_alarm = hi6421_rtc_set_alarm,
+ .alarm_irq_enable = hi6421_rtc_alarm_irq_enable,
+};
+
+static int hi6421_rtc_probe(struct platform_device *pdev)
+{
+ struct hi6421_rtc_info *info;
+ int ret;
+
+ info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
+ if (!info) {
+ dev_err(&pdev->dev, "failed to allocate memory\n");
+ return -ENOMEM;
+ }
+ info->irq = platform_get_irq(pdev, 0);
+ if (info->irq < 0)
+ return -ENOENT;
+
+ info->pmic = dev_get_drvdata(pdev->dev.parent);
+ platform_set_drvdata(pdev, info);
+
+ /* enable RTC device */
+ hi6421_pmic_write(info->pmic, REG_RTCCTRL, 1);
+
+ info->rtc = devm_rtc_device_register(&pdev->dev, pdev->name,
+ &hi6421_rtc_ops, THIS_MODULE);
+ if (IS_ERR(info->rtc))
+ return PTR_ERR(info->rtc);
+
+ ret = devm_request_irq(&pdev->dev, info->irq, hi6421_rtc_handler,
+ IRQF_DISABLED, "alarm", info);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int hi6421_rtc_remove(struct platform_device *pdev)
+{
+ return 0;
+}
+
+static struct of_device_id hi6421_rtc_of_match[] = {
+ { .compatible = "hisilicon,hi6421-rtc" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, hi6421_rtc_of_match);
+
+static struct platform_driver hi6421_rtc_driver = {
+ .driver = {
+ .name = "hi6421-rtc",
+ .of_match_table = of_match_ptr(hi6421_rtc_of_match),
+ },
+ .probe = hi6421_rtc_probe,
+ .remove = hi6421_rtc_remove,
+};
+module_platform_driver(hi6421_rtc_driver);
+
+MODULE_LICENSE("GPLv2");
+MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@linaro.org");
+MODULE_ALIAS("platform:hi6421-rtc");