summaryrefslogtreecommitdiff
path: root/drivers/rtc
diff options
context:
space:
mode:
authorJeffy Chen <jeffy.chen@rock-chips.com>2018-08-22 15:48:47 +0800
committerTao Huang <huangtao@rock-chips.com>2018-08-23 17:51:33 +0800
commit76f2837b6e2c42f1038cea2aaf011f4923388013 (patch)
tree5283945fcaac46faf2b6895ac2cde14017201269 /drivers/rtc
parentb22c44f4f48d4286f9b9f3c4cdd0f68079ad1d5b (diff)
rtc: Add an RTC driver for rk-timer
This driver uses Rockchip timer to simulate RTC functions. Change-Id: I49eed6ecbb4c55527696c63b0d479afe837502d5 Signed-off-by: Jeffy Chen <jeffy.chen@rock-chips.com>
Diffstat (limited to 'drivers/rtc')
-rw-r--r--drivers/rtc/Kconfig10
-rw-r--r--drivers/rtc/Makefile1
-rw-r--r--drivers/rtc/rtc-rk-timer.c494
3 files changed, 505 insertions, 0 deletions
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index a92e5e9b4f7f..aa0428bd66ad 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -341,6 +341,16 @@ config RTC_DRV_RK808
This driver can also be built as a module. If so, the module
will be called rk808-rtc.
+config RTC_DRV_RK_TIMER
+ tristate "Rockchip timer RTC"
+ depends on OF
+ help
+ If you say yes here you will get support for the
+ RTC of Rockchip timer.
+
+ This driver can also be built as a module. If so, the module
+ will be called rtc-rk-timer.
+
config RTC_DRV_MAX77802
tristate "Maxim 77802 RTC"
depends on MFD_MAX77686
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index 0df55faabb8e..a0835df7be3f 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -122,6 +122,7 @@ obj-$(CONFIG_RTC_DRV_PXA) += rtc-pxa.o
obj-$(CONFIG_RTC_DRV_R9701) += rtc-r9701.o
obj-$(CONFIG_RTC_DRV_RC5T583) += rtc-rc5t583.o
obj-$(CONFIG_RTC_DRV_RK808) += rtc-rk808.o
+obj-$(CONFIG_RTC_DRV_RK_TIMER) += rtc-rk-timer.o
obj-$(CONFIG_RTC_DRV_RP5C01) += rtc-rp5c01.o
obj-$(CONFIG_RTC_DRV_RS5C313) += rtc-rs5c313.o
obj-$(CONFIG_RTC_DRV_RS5C348) += rtc-rs5c348.o
diff --git a/drivers/rtc/rtc-rk-timer.c b/drivers/rtc/rtc-rk-timer.c
new file mode 100644
index 000000000000..171e9a31bb6f
--- /dev/null
+++ b/drivers/rtc/rtc-rk-timer.c
@@ -0,0 +1,494 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018, Fuzhou Rockchip Electronics Co., Ltd
+ * Author: Jeffy Chen <jeffy.chen@rock-chips.com>
+ *
+ * Base on the Rockchip timer driver drivers/clocksource/rockchip_timer.c by
+ * Daniel Lezcano <daniel.lezcano@linaro.org>
+ */
+
+#include <linux/clk.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/rtc.h>
+
+#define DRV_NAME "rk-timer-rtc"
+
+#define TIMER_LOAD_COUNT0 0x00
+#define TIMER_LOAD_COUNT1 0x04
+#define TIMER_CURRENT_VALUE0 0x08
+#define TIMER_CURRENT_VALUE1 0x0C
+#define TIMER_CONTROL_REG3288 0x10
+#define TIMER_INT_STATUS 0x18
+
+#define TIMER_ENABLE BIT(0)
+#define TIMER_MODE_USER_DEFINED_COUNT BIT(1)
+#define TIMER_INT_UNMASK BIT(2)
+
+/* Forbid any alarms which would trigger inside the threshold */
+#define TIMER_ALARM_THRESHOLD_MS 10
+
+#if !defined(UINT64_MAX)
+ #define UINT64_MAX ((u64)-1)
+#endif
+
+/**
+ * struct rk_timer_rtc_data - Differences between SoC variants
+ *
+ * @ctrl_reg_offset: The offset of timer control register
+ */
+struct rk_timer_rtc_data {
+ int ctrl_reg_offset;
+};
+
+/**
+ * struct rk_timer_rtc - Driver data for Rockchip timer RTC
+ *
+ * @data: Pointer to rk_timer_rtc_data
+ * @regmap: Register map of the timer
+ * @rtc: Pointer to RTC device
+ * @clk: The timer clock
+ * @pclk: The peripheral clock
+ * @freq: The freq of timer clock
+ * @timebase: The base time of the timer RTC
+ * @alarm_irq_enabled: Whether to report alarm irqs
+ * @irq: The timer IRQ number
+ */
+struct rk_timer_rtc {
+ const struct rk_timer_rtc_data *data;
+ struct regmap *regmap;
+ struct rtc_device *rtc;
+ struct clk *clk;
+ struct clk *pclk;
+ u32 freq;
+ u64 timebase;
+ int alarm_irq_enabled;
+ int irq;
+};
+
+static inline u64 tick_to_sec(struct rk_timer_rtc *rk_timer_rtc, u64 tick)
+{
+ do_div(tick, rk_timer_rtc->freq);
+ return tick;
+}
+
+static inline u64 sec_to_tick(struct rk_timer_rtc *rk_timer_rtc, int sec)
+{
+ return sec * rk_timer_rtc->freq;
+}
+
+static inline u64 ms_to_tick(struct rk_timer_rtc *rk_timer_rtc, int ms)
+{
+ return ms * rk_timer_rtc->freq / 1000;
+}
+
+static inline u64 tick_to_time64(struct rk_timer_rtc *rk_timer_rtc, u64 tick)
+{
+ return tick_to_sec(rk_timer_rtc, tick) + rk_timer_rtc->timebase;
+}
+
+static inline u64 time64_to_tick(struct rk_timer_rtc *rk_timer_rtc, u64 time)
+{
+ return sec_to_tick(rk_timer_rtc, time - rk_timer_rtc->timebase);
+}
+
+static inline int rk_timer_rtc_write64(struct rk_timer_rtc *rk_timer_rtc,
+ u32 reg, u64 val)
+{
+ return regmap_bulk_write(rk_timer_rtc->regmap, reg, &val, 2);
+}
+
+static inline int rk_timer_rtc_read64(struct rk_timer_rtc *rk_timer_rtc,
+ u32 reg, u64 *val)
+{
+ return regmap_bulk_read(rk_timer_rtc->regmap, reg, val, 2);
+}
+
+static inline int rk_timer_rtc_irq_clear(struct rk_timer_rtc *rk_timer_rtc)
+{
+ return regmap_write(rk_timer_rtc->regmap, TIMER_INT_STATUS, 1);
+}
+
+static inline int rk_timer_rtc_irq_enable(struct rk_timer_rtc *rk_timer_rtc,
+ unsigned int enabled)
+{
+ /* Clear any pending irq before enable it */
+ if (enabled)
+ rk_timer_rtc_irq_clear(rk_timer_rtc);
+
+ return regmap_update_bits(rk_timer_rtc->regmap,
+ rk_timer_rtc->data->ctrl_reg_offset,
+ TIMER_INT_UNMASK,
+ enabled ? TIMER_INT_UNMASK : 0);
+}
+
+static int rk_timer_rtc_reset(struct rk_timer_rtc *rk_timer_rtc)
+{
+ int ret;
+
+ ret = regmap_write(rk_timer_rtc->regmap,
+ rk_timer_rtc->data->ctrl_reg_offset, 0);
+ if (ret)
+ return ret;
+
+ /* Init load count to UINT64_MAX to keep timer running */
+ ret = rk_timer_rtc_write64(rk_timer_rtc, TIMER_LOAD_COUNT0, UINT64_MAX);
+ if (ret)
+ return ret;
+
+ /* Clear any pending irq before enable it */
+ rk_timer_rtc_irq_clear(rk_timer_rtc);
+
+ /* Enable timer in user-defined count mode with irq unmasked */
+ return regmap_write(rk_timer_rtc->regmap,
+ rk_timer_rtc->data->ctrl_reg_offset,
+ TIMER_ENABLE | TIMER_MODE_USER_DEFINED_COUNT |
+ TIMER_INT_UNMASK);
+}
+
+static int rk_timer_rtc_read_time(struct device *dev, struct rtc_time *tm)
+{
+ struct rk_timer_rtc *rk_timer_rtc = dev_get_drvdata(dev);
+ int ret;
+ u64 tick;
+
+ ret = rk_timer_rtc_read64(rk_timer_rtc, TIMER_CURRENT_VALUE0, &tick);
+ if (ret)
+ return ret;
+
+ rtc_time64_to_tm(tick_to_time64(rk_timer_rtc, tick), tm);
+
+ dev_dbg(dev, "Read RTC: %4d-%02d-%02d(%d) %02d:%02d:%02d\n",
+ 1900 + tm->tm_year, tm->tm_mon + 1, tm->tm_mday,
+ tm->tm_wday, tm->tm_hour, tm->tm_min, tm->tm_sec);
+
+ return rtc_valid_tm(tm);
+}
+
+static int rk_timer_rtc_set_time(struct device *dev, struct rtc_time *tm)
+{
+ struct rk_timer_rtc *rk_timer_rtc = dev_get_drvdata(dev);
+ int ret;
+
+ dev_dbg(dev, "Set RTC:%4d-%02d-%02d(%d) %02d:%02d:%02d\n",
+ 1900 + tm->tm_year, tm->tm_mon + 1, tm->tm_mday,
+ tm->tm_wday, tm->tm_hour, tm->tm_min, tm->tm_sec);
+
+ ret = rtc_valid_tm(tm);
+ if (ret)
+ return ret;
+
+ rk_timer_rtc->timebase = rtc_tm_to_time64(tm);
+
+ dev_dbg(dev, "Setting new timebase:%lld\n", rk_timer_rtc->timebase);
+
+ /* Restart timer for new timebase */
+ ret = rk_timer_rtc_reset(rk_timer_rtc);
+ if (ret) {
+ dev_err(dev, "Failed to reset timer:%d\n", ret);
+ return ret;
+ }
+
+ /* Tell framework to check alarms */
+ rtc_update_irq(rk_timer_rtc->rtc, 1, RTC_IRQF | RTC_AF);
+
+ return 0;
+}
+
+static int rk_timer_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+ struct rk_timer_rtc *rk_timer_rtc = dev_get_drvdata(dev);
+ int ret;
+ u64 tick;
+
+ ret = rk_timer_rtc_read64(rk_timer_rtc, TIMER_LOAD_COUNT0, &tick);
+ if (ret)
+ return ret;
+
+ rtc_time64_to_tm(tick_to_time64(rk_timer_rtc, tick), &alrm->time);
+
+ dev_dbg(dev, "Read alarm: %4d-%02d-%02d(%d) %02d:%02d:%02d\n",
+ 1900 + alrm->time.tm_year, alrm->time.tm_mon + 1,
+ alrm->time.tm_mday, alrm->time.tm_wday, alrm->time.tm_hour,
+ alrm->time.tm_min, alrm->time.tm_sec);
+
+ return rtc_valid_tm(&alrm->time);
+}
+
+static int rk_timer_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+ struct rk_timer_rtc *rk_timer_rtc = dev_get_drvdata(dev);
+ int ret;
+ u64 alarm_tick, alarm_threshold_tick, cur_tick;
+
+ dev_dbg(dev, "Set alarm:%4d-%02d-%02d(%d) %02d:%02d:%02d\n",
+ 1900 + alrm->time.tm_year, alrm->time.tm_mon + 1,
+ alrm->time.tm_mday, alrm->time.tm_wday, alrm->time.tm_hour,
+ alrm->time.tm_min, alrm->time.tm_sec);
+
+ ret = rtc_valid_tm(&alrm->time);
+ if (ret)
+ return ret;
+
+ rk_timer_rtc->alarm_irq_enabled = false;
+
+ alarm_tick = time64_to_tick(rk_timer_rtc,
+ rtc_tm_to_time64(&alrm->time));
+
+ ret = rk_timer_rtc_read64(rk_timer_rtc, TIMER_CURRENT_VALUE0,
+ &cur_tick);
+ if (ret)
+ return ret;
+
+ /* Don't set an alarm in the past or about to pass */
+ alarm_threshold_tick = ms_to_tick(rk_timer_rtc,
+ TIMER_ALARM_THRESHOLD_MS);
+ if (alarm_tick <= (cur_tick + alarm_threshold_tick))
+ return -ETIME;
+
+ /*
+ * When the current value counts up to the load count, the timer will
+ * stop and generate an irq.
+ */
+ ret = rk_timer_rtc_write64(rk_timer_rtc, TIMER_LOAD_COUNT0, alarm_tick);
+ if (ret)
+ return ret;
+
+ dev_dbg(dev, "New alarm enabled:%d\n", alrm->enabled);
+ rk_timer_rtc->alarm_irq_enabled = alrm->enabled;
+
+ return 0;
+}
+
+static int rk_timer_rtc_alarm_irq_enable(struct device *dev,
+ unsigned int enabled)
+{
+ struct rk_timer_rtc *rk_timer_rtc = dev_get_drvdata(dev);
+
+ dev_dbg(dev, "Set alarm irq enabled:%d\n", enabled);
+ rk_timer_rtc->alarm_irq_enabled = enabled;
+
+ return 0;
+}
+
+static irqreturn_t rk_timer_rtc_alarm_irq(int irq, void *data)
+{
+ struct device *dev = data;
+ struct rk_timer_rtc *rk_timer_rtc = dev_get_drvdata(dev);
+ int ret;
+
+ dev_dbg(dev, "Received timer irq, alarm_irq_enabled:%d\n",
+ rk_timer_rtc->alarm_irq_enabled);
+
+ /* The timer is stopped now, reset the load count to start it again */
+ ret = rk_timer_rtc_write64(rk_timer_rtc, TIMER_LOAD_COUNT0, UINT64_MAX);
+ if (ret)
+ dev_err(dev, "Failed to set load count:%d\n", ret);
+
+ ret = regmap_write(rk_timer_rtc->regmap, TIMER_INT_STATUS, 1);
+ if (ret)
+ dev_err(dev, "Failed to clear irq:%d\n", ret);
+
+ /* Only report rtc irq when alarm irq is enabled */
+ if (rk_timer_rtc->alarm_irq_enabled)
+ rtc_update_irq(rk_timer_rtc->rtc, 1, RTC_IRQF | RTC_AF);
+
+ return IRQ_HANDLED;
+}
+
+static const struct rtc_class_ops rk_timer_rtc_ops = {
+ .read_time = rk_timer_rtc_read_time,
+ .set_time = rk_timer_rtc_set_time,
+ .read_alarm = rk_timer_rtc_read_alarm,
+ .set_alarm = rk_timer_rtc_set_alarm,
+ .alarm_irq_enable = rk_timer_rtc_alarm_irq_enable,
+};
+
+static struct regmap_config rk_timer_regmap_config = {
+ .name = DRV_NAME,
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+};
+
+static const struct of_device_id rk_timer_rtc_dt_match[];
+
+static int rk_timer_rtc_probe(struct platform_device *pdev)
+{
+ const struct of_device_id *match;
+ struct device *dev = &pdev->dev;
+ struct rk_timer_rtc *rk_timer_rtc;
+ void __iomem *base;
+ resource_size_t size;
+ int ret;
+
+ rk_timer_rtc = devm_kzalloc(dev, sizeof(*rk_timer_rtc), GFP_KERNEL);
+ if (!rk_timer_rtc)
+ return -ENOMEM;
+
+ match = of_match_node(rk_timer_rtc_dt_match, dev->of_node);
+ rk_timer_rtc->data = match->data;
+
+ platform_set_drvdata(pdev, rk_timer_rtc);
+
+ base = devm_of_iomap(dev, dev->of_node, 0, &size);
+ if (!base) {
+ dev_err(dev, "Failed to iomap\n");
+ return -EINVAL;
+ }
+
+ rk_timer_regmap_config.max_register = size - 4;
+ rk_timer_rtc->regmap = devm_regmap_init_mmio(dev, base,
+ &rk_timer_regmap_config);
+ if (IS_ERR(rk_timer_rtc->regmap)) {
+ ret = PTR_ERR(rk_timer_rtc->regmap);
+ dev_err(dev, "Failed to init regmap:%d\n", ret);
+ return ret;
+ }
+
+ rk_timer_rtc->irq = platform_get_irq(pdev, 0);
+ if (rk_timer_rtc->irq < 0) {
+ ret = rk_timer_rtc->irq;
+ dev_err(dev, "Failed to get irq:%d\n", ret);
+ return ret;
+ }
+
+ ret = devm_request_irq(dev, rk_timer_rtc->irq, rk_timer_rtc_alarm_irq,
+ 0, dev_name(dev), dev);
+ if (ret) {
+ dev_err(dev, "Failed to request irq:%d\n", ret);
+ return ret;
+ }
+
+ rk_timer_rtc->pclk = devm_clk_get(dev, "pclk");
+ if (IS_ERR(rk_timer_rtc->pclk)) {
+ ret = PTR_ERR(rk_timer_rtc->pclk);
+ pr_err("Failed to get timer pclk:%d\n", ret);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(rk_timer_rtc->pclk);
+ if (ret) {
+ dev_err(dev, "Failed to enable pclk:%d\n", ret);
+ return ret;
+ }
+
+ rk_timer_rtc->clk = devm_clk_get(dev, "timer");
+ if (IS_ERR(rk_timer_rtc->clk)) {
+ ret = PTR_ERR(rk_timer_rtc->clk);
+ pr_err("Failed to get timer clk:%d\n", ret);
+ goto err_disable_pclk;
+ }
+
+ ret = clk_prepare_enable(rk_timer_rtc->clk);
+ if (ret) {
+ dev_err(dev, "Failed to enable timer clk:%d\n", ret);
+ goto err_disable_pclk;
+ }
+
+ rk_timer_rtc->freq = clk_get_rate(rk_timer_rtc->clk);
+ dev_dbg(dev, "RTC timer freq:%d\n", rk_timer_rtc->freq);
+
+ ret = rk_timer_rtc_reset(rk_timer_rtc);
+ if (ret) {
+ dev_err(dev, "Failed to reset timer:%d\n", ret);
+ goto err_disable_clk;
+ }
+
+ ret = device_init_wakeup(dev, true);
+ if (ret) {
+ dev_err(dev, "Failed to init wakeup:%d\n", ret);
+ goto err_disable_irq;
+ }
+
+ rk_timer_rtc->rtc = devm_rtc_device_register(dev, DRV_NAME,
+ &rk_timer_rtc_ops,
+ THIS_MODULE);
+ if (IS_ERR(rk_timer_rtc->rtc)) {
+ ret = PTR_ERR(rk_timer_rtc->rtc);
+ dev_err(dev, "Failed to register rtc:%d\n", ret);
+ goto err_uninit_wakeup;
+ }
+
+ return 0;
+err_uninit_wakeup:
+ device_init_wakeup(&pdev->dev, false);
+err_disable_irq:
+ rk_timer_rtc_irq_enable(rk_timer_rtc, false);
+err_disable_clk:
+ clk_disable_unprepare(rk_timer_rtc->clk);
+err_disable_pclk:
+ clk_disable_unprepare(rk_timer_rtc->pclk);
+ return ret;
+}
+
+static int rk_timer_rtc_remove(struct platform_device *pdev)
+{
+ struct rk_timer_rtc *rk_timer_rtc = dev_get_drvdata(&pdev->dev);
+
+ device_init_wakeup(&pdev->dev, false);
+ rk_timer_rtc_irq_enable(rk_timer_rtc, false);
+ clk_disable_unprepare(rk_timer_rtc->clk);
+ clk_disable_unprepare(rk_timer_rtc->pclk);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int rk_timer_rtc_suspend(struct device *dev)
+{
+ struct rk_timer_rtc *rk_timer_rtc = dev_get_drvdata(dev);
+
+ if (device_may_wakeup(dev))
+ enable_irq_wake(rk_timer_rtc->irq);
+
+ return 0;
+}
+
+static int rk_timer_rtc_resume(struct device *dev)
+{
+ struct rk_timer_rtc *rk_timer_rtc = dev_get_drvdata(dev);
+
+ if (device_may_wakeup(dev))
+ disable_irq_wake(rk_timer_rtc->irq);
+
+ return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(rk_timer_rtc_pm_ops, rk_timer_rtc_suspend,
+ rk_timer_rtc_resume);
+
+static const struct rk_timer_rtc_data rk3288_timer_rtc_data = {
+ .ctrl_reg_offset = TIMER_CONTROL_REG3288,
+};
+
+static const struct of_device_id rk_timer_rtc_dt_match[] = {
+ {
+ .compatible = "rockchip,rk3308-timer-rtc",
+ .data = &rk3288_timer_rtc_data,
+ },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(platform, rk_timer_rtc_dt_match);
+
+static struct platform_driver rk_timer_rtc_driver = {
+ .probe = rk_timer_rtc_probe,
+ .remove = rk_timer_rtc_remove,
+ .driver = {
+ .name = DRV_NAME,
+ .pm = &rk_timer_rtc_pm_ops,
+ .of_match_table = of_match_ptr(rk_timer_rtc_dt_match),
+ },
+};
+
+module_platform_driver(rk_timer_rtc_driver);
+
+MODULE_DESCRIPTION("RTC driver for the rockchip timer");
+MODULE_AUTHOR("Jeffy Chen <jeffy.chen@rock-chips.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" DRV_NAME);