diff options
Diffstat (limited to 'drivers/rtc/rtc-rk-timer.c')
-rw-r--r-- | drivers/rtc/rtc-rk-timer.c | 494 |
1 files changed, 494 insertions, 0 deletions
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); |