Add driver for Sunplus SP7021 Signed-off-by: Vincent Shih <vincent.shih@xxxxxxxxxxx> --- MAINTAINERS | 6 + drivers/rtc/Kconfig | 10 ++ drivers/rtc/Makefile | 1 + drivers/rtc/rtc-sunplus.c | 389 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 406 insertions(+) create mode 100644 drivers/rtc/rtc-sunplus.c diff --git a/MAINTAINERS b/MAINTAINERS index 3b79fd4..6c1a535 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -17945,6 +17945,12 @@ L: netdev@xxxxxxxxxxxxxxx S: Maintained F: drivers/net/ethernet/dlink/sundance.c +SUNPLUS RTC DRIVER +M: Vincent Shih <vincent.shih@xxxxxxxxxxx> +L: linux-rtc@xxxxxxxxxxxxxxx +S: Maintained +F: drivers/rtc/rtc-sunplus.c + SUPERH M: Yoshinori Sato <ysato@xxxxxxxxxxxxxxxxxxxx> M: Rich Felker <dalias@xxxxxxxx> diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index e1bc521..0c205d2 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -1028,6 +1028,16 @@ config RTC_DRV_DS1685_FAMILY This driver can also be built as a module. If so, the module will be called rtc-ds1685. +config RTC_DRV_SUNPLUS + bool "Sunplus SP7021 RTC" + depends on SOC_SP7021 + help + Say 'yse' to get support for Sunplus SP7021 real-time clock + (RTC) for industrial applications. + It provides RTC status check, timer/alarm functionalities, + user data reservation only with battery with voltage over 2.5V, + RTC power status check and battery charge. + choice prompt "Subtype" depends on RTC_DRV_DS1685_FAMILY diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index 5ceeafe..92039b3 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -165,6 +165,7 @@ obj-$(CONFIG_RTC_DRV_STMP) += rtc-stmp3xxx.o obj-$(CONFIG_RTC_DRV_SUN4V) += rtc-sun4v.o obj-$(CONFIG_RTC_DRV_SUN6I) += rtc-sun6i.o obj-$(CONFIG_RTC_DRV_SUNXI) += rtc-sunxi.o +obj-$(CONFIG_RTC_DRV_SUNPLUS) += rtc-sunplus.o obj-$(CONFIG_RTC_DRV_TEGRA) += rtc-tegra.o obj-$(CONFIG_RTC_DRV_TEST) += rtc-test.o obj-$(CONFIG_RTC_DRV_TPS6586X) += rtc-tps6586x.o diff --git a/drivers/rtc/rtc-sunplus.c b/drivers/rtc/rtc-sunplus.c new file mode 100644 index 0000000..6d87ab6 --- /dev/null +++ b/drivers/rtc/rtc-sunplus.c @@ -0,0 +1,389 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +/**************************************************************************************************/ +/* How to test RTC: */ +/* */ +/* 1. use kernel commands */ +/* hwclock - query and set the hardware clock (RTC) */ +/* */ +/* (for i in `seq 5`; do (echo ------ && echo -n 'date : ' && date && echo -n 'hwclock -r: ' */ +/* && hwclock -r; sleep 1); done) */ +/* date 121209002014 # Set system to 2014/Dec/12 09:00 */ +/* (for i in `seq 5`; do (echo ------ && echo -n 'date : ' && date && echo -n 'hwclock -r: ' */ +/* && hwclock -r; sleep 1); done) */ +/* hwclock -s # Set the System Time from the Hardware Clock */ +/* (for i in `seq 5`; do (echo ------ && echo -n 'date : ' && date && echo -n 'hwclock -r: ' */ +/* && hwclock -r; sleep 1); done) */ +/* date 121213002014 # Set system to 2014/Dec/12 13:00 */ +/* (for i in `seq 5`; do (echo ------ && echo -n 'date : ' && date && echo -n 'hwclock -r: ' */ +/* && hwclock -r; sleep 1); done) */ +/* hwclock -w # Set the Hardware Clock to the current System Time */ +/* (for i in `seq 10000`; do (echo ------ && echo -n 'date : ' && date && echo -n 'hwclock -r: ' */ +/* && hwclock -r; sleep 1); done) */ +/* */ +/* How to setup alarm (e.g., 10 sec later): */ +/* echo 0 > /sys/class/rtc/rtc0/wakealarm && nnn=`date '+%s'` && echo $nnn && \ */ +/* nnn=`expr $nnn + 10` && echo $nnn > /sys/class/rtc/rtc0/wakealarm */ +/* */ +/* 2. use RTC Driver Test Program (\linux\application\module_test\rtc\rtc-test.c) */ +/* */ +/**************************************************************************************************/ +#include <linux/module.h> +#include <linux/err.h> +#include <linux/rtc.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/reset.h> +#include <linux/of.h> +#include <linux/ktime.h> +#include <linux/io.h> + +/* ---------------------------------------------------------------------------------------------- */ +#define FUNC_DEBUG() pr_debug("[RTC] Debug: %s(%d)\n", __func__, __LINE__) + +#define RTC_DEBUG(fmt, args ...) pr_debug("[RTC] Debug: " fmt, ## args) +#define RTC_INFO(fmt, args ...) pr_info("[RTC] Info: " fmt, ## args) +#define RTC_WARN(fmt, args ...) pr_warn("[RTC] Warning: " fmt, ## args) +#define RTC_ERR(fmt, args ...) pr_err("[RTC] Error: " fmt, ## args) +/* ---------------------------------------------------------------------------------------------- */ + +struct sunplus_rtc { + struct clk *rtcclk; + struct reset_control *rstc; + unsigned long set_alarm_again; + u32 charging_mode; +}; + +struct sunplus_rtc sp_rtc; + +#define RTC_REG_NAME "rtc_reg" + +struct sp_rtc_reg { + unsigned int rsv00; + unsigned int rsv01; + unsigned int rsv02; + unsigned int rsv03; + unsigned int rsv04; + unsigned int rsv05; + unsigned int rsv06; + unsigned int rsv07; + unsigned int rsv08; + unsigned int rsv09; + unsigned int rsv10; + unsigned int rsv11; + unsigned int rsv12; + unsigned int rsv13; + unsigned int rsv14; + unsigned int rsv15; + unsigned int rtc_ctrl; + unsigned int rtc_timer_out; + unsigned int rtc_divider; + unsigned int rtc_timer_set; + unsigned int rtc_alarm_set; + unsigned int rtc_user_data; + unsigned int rtc_reset_record; + unsigned int rtc_battery_ctrl; + unsigned int rtc_trim_ctrl; + unsigned int rsv25; + unsigned int rsv26; + unsigned int rsv27; + unsigned int rsv28; + unsigned int rsv29; + unsigned int rsv30; + unsigned int rsv31; +}; + +static struct sp_rtc_reg *rtc_reg_ptr; + +static void sp_get_seconds(unsigned long *secs) +{ + *secs = (unsigned long)readl(&rtc_reg_ptr->rtc_timer_out); +} + +static void sp_set_seconds(unsigned long secs) +{ + writel((u32)secs, &rtc_reg_ptr->rtc_timer_set); +} + +static int sp_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + unsigned long secs; + + sp_get_seconds(&secs); + rtc_time64_to_tm(secs, tm); + RTC_DEBUG("%s: RTC date/time to %d-%d-%d, %02d:%02d:%02d.\r\n", + __func__, tm->tm_mday, tm->tm_mon + 1, tm->tm_year, + tm->tm_hour, tm->tm_min, tm->tm_sec); + + return rtc_valid_tm(tm); +} + +int sp_rtc_get_time(struct rtc_time *tm) +{ + unsigned long secs; + + sp_get_seconds(&secs); + rtc_time64_to_tm(secs, tm); + return 0; +} +EXPORT_SYMBOL(sp_rtc_get_time); + +static int sp_rtc_suspend(struct platform_device *pdev, pm_message_t state) +{ + FUNC_DEBUG(); + + // Keep RTC from system reset + writel((1 << (16+4)) | (1 << 4), &rtc_reg_ptr->rtc_ctrl); + + return 0; +} + +static int sp_rtc_resume(struct platform_device *pdev) +{ + /* */ + /* Because RTC is still powered during suspend, */ + /* there is nothing to do here. */ + /* */ + FUNC_DEBUG(); + + // Keep RTC from system reset + writel((1 << (16+4)) | (1 << 4), &rtc_reg_ptr->rtc_ctrl); + + return 0; +} + +static int sp_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + unsigned long secs; + + secs = rtc_tm_to_time64(tm); + RTC_DEBUG("%s, secs = %lu\n", __func__, secs); + sp_set_seconds(secs); + + return 0; +} + +static int sp_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct rtc_device *rtc = dev_get_drvdata(dev); + unsigned long alarm_time; + + alarm_time = rtc_tm_to_time64(&alrm->time); + RTC_DEBUG("%s, alarm_time: %u\n", __func__, (u32)(alarm_time)); + + if (alarm_time > 0xFFFFFFFF) + return -EINVAL; + + if ((rtc->aie_timer.enabled) && (rtc->aie_timer.node.expires == ktime_set(alarm_time, 0))) { + if (rtc->uie_rtctimer.enabled) + sp_rtc.set_alarm_again = 1; + } + + writel((u32)alarm_time, &rtc_reg_ptr->rtc_alarm_set); + wmb(); // make sure settings are effective. + + // enable alarm for update irq + if (rtc->uie_rtctimer.enabled) + writel((0x003F << 16) | 0x17, &rtc_reg_ptr->rtc_ctrl); + else if (!rtc->aie_timer.enabled) + writel((0x0007 << 16) | 0x0, &rtc_reg_ptr->rtc_ctrl); + + return 0; +} + +static int sp_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + unsigned int alarm_time; + + alarm_time = readl(&rtc_reg_ptr->rtc_alarm_set); + RTC_DEBUG("%s, alarm_time: %u\n", __func__, alarm_time); + rtc_time64_to_tm((unsigned long)(alarm_time), &alrm->time); + + return 0; +} + +static int sp_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled) +{ + struct rtc_device *rtc = dev_get_drvdata(dev); + + if (enabled) + writel((0x003F << 16) | 0x17, &rtc_reg_ptr->rtc_ctrl); + else if (!rtc->uie_rtctimer.enabled) + writel((0x0007 << 16) | 0x0, &rtc_reg_ptr->rtc_ctrl); + + return 0; +} + +static const struct rtc_class_ops sp_rtc_ops = { + .read_time = sp_rtc_read_time, + .set_time = sp_rtc_set_time, + .set_alarm = sp_rtc_set_alarm, + .read_alarm = sp_rtc_read_alarm, + .alarm_irq_enable = sp_rtc_alarm_irq_enable, +}; + +static irqreturn_t rtc_irq_handler(int irq, void *dev_id) +{ + struct platform_device *plat_dev = dev_id; + struct rtc_device *rtc = platform_get_drvdata(plat_dev); + + if (rtc->uie_rtctimer.enabled) { + rtc_update_irq(rtc, 1, RTC_IRQF | RTC_UF); + RTC_DEBUG("[RTC] update irq\n"); + + if (sp_rtc.set_alarm_again == 1) { + sp_rtc.set_alarm_again = 0; + rtc_update_irq(rtc, 1, RTC_IRQF | RTC_AF); + RTC_DEBUG("[RTC] alarm irq\n"); + } + } else { + rtc_update_irq(rtc, 1, RTC_IRQF | RTC_AF); + RTC_DEBUG("[RTC] alarm irq\n"); + } + + return IRQ_HANDLED; +} + +/* ---------------------------------------------------------------------------------------------- */ +/* mode bat_charge_rsel bat_charge_dsel bat_charge_en Remarks */ +/* 0xE x x 0 Disable */ +/* 0x1 0 0 1 0.86mA (2K Ohm with diode) */ +/* 0x5 1 0 1 1.81mA (250 Ohm with diode) */ +/* 0x9 2 0 1 2.07mA (50 Ohm with diode) */ +/* 0xD 3 0 1 16.0mA (0 Ohm with diode) */ +/* 0x3 0 1 1 1.36mA (2K Ohm without diode) */ +/* 0x7 1 1 1 3.99mA (250 Ohm without diode) */ +/* 0xB 2 1 1 4.41mA (50 Ohm without diode) */ +/* 0xF 3 1 1 16.0mA (0 Ohm without diode) */ +/* ---------------------------------------------------------------------------------------------- */ +static void sp_rtc_set_batt_charge_ctrl(u32 _mode) +{ + u8 m = _mode & 0x000F; + + RTC_DEBUG("battery charge mode: 0x%X\n", m); + writel((0x000F << 16) | m, &rtc_reg_ptr->rtc_battery_ctrl); +} + +static int sp_rtc_probe(struct platform_device *plat_dev) +{ + int ret; + int err, irq; + struct rtc_device *rtc = NULL; + struct resource *res; + void __iomem *reg_base = NULL; + + FUNC_DEBUG(); + + memset(&sp_rtc, 0, sizeof(sp_rtc)); + + // find and map our resources + res = platform_get_resource_byname(plat_dev, IORESOURCE_MEM, RTC_REG_NAME); + RTC_DEBUG("res = 0x%x\n", res->start); + + if (res) { + reg_base = devm_ioremap_resource(&plat_dev->dev, res); + if (IS_ERR(reg_base)) + RTC_ERR("%s devm_ioremap_resource fail\n", RTC_REG_NAME); + } + RTC_DEBUG("reg_base = 0x%lx\n", (unsigned long)reg_base); + + // clk + sp_rtc.rtcclk = devm_clk_get(&plat_dev->dev, NULL); + RTC_DEBUG("sp_rtc->clk = 0x%lx\n", (unsigned long)sp_rtc.rtcclk); + if (IS_ERR(sp_rtc.rtcclk)) + RTC_DEBUG("devm_clk_get fail\n"); + + ret = clk_prepare_enable(sp_rtc.rtcclk); + + // reset + sp_rtc.rstc = devm_reset_control_get(&plat_dev->dev, NULL); + RTC_DEBUG("sp_rtc->rstc = 0x%lx\n", (unsigned long)sp_rtc.rstc); + if (IS_ERR(sp_rtc.rstc)) { + ret = PTR_ERR(sp_rtc.rstc); + RTC_ERR("SPI failed to retrieve reset controller: %d\n", ret); + goto free_clk; + } + + ret = reset_control_deassert(sp_rtc.rstc); + if (ret) + goto free_clk; + + rtc_reg_ptr = (struct sp_rtc_reg *)(reg_base); + + // Keep RTC from system reset + writel((1 << (16+4)) | (1 << 4), &rtc_reg_ptr->rtc_ctrl); + + // request irq + irq = platform_get_irq(plat_dev, 0); + if (irq < 0) { + RTC_ERR("platform_get_irq failed\n"); + goto free_reset_assert; + } + + err = devm_request_irq(&plat_dev->dev, irq, rtc_irq_handler, + IRQF_TRIGGER_RISING, "rtc irq", plat_dev); + if (err) { + RTC_ERR("devm_request_irq failed: %d\n", err); + goto free_reset_assert; + } + + // Get charging-mode. + ret = of_property_read_u32(plat_dev->dev.of_node, "charging-mode", &sp_rtc.charging_mode); + if (ret) { + RTC_ERR("Failed to retrieve \'charging-mode\'!\n"); + goto free_reset_assert; + } + sp_rtc_set_batt_charge_ctrl(sp_rtc.charging_mode); + + device_init_wakeup(&plat_dev->dev, 1); + + rtc = devm_rtc_device_register(&plat_dev->dev, "sp7021-rtc", &sp_rtc_ops, THIS_MODULE); + if (IS_ERR(rtc)) { + ret = PTR_ERR(rtc); + goto free_reset_assert; + } + + platform_set_drvdata(plat_dev, rtc); + + RTC_INFO("sp7021-rtc loaded\n"); + + return 0; + +free_reset_assert: + reset_control_assert(sp_rtc.rstc); +free_clk: + clk_disable_unprepare(sp_rtc.rtcclk); + + return ret; +} + +static int sp_rtc_remove(struct platform_device *plat_dev) +{ + reset_control_assert(sp_rtc.rstc); + + return 0; +} + +static const struct of_device_id sp_rtc_of_match[] = { + { .compatible = "sunplus,sp7021-rtc" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, sp_rtc_of_match); + +static struct platform_driver sp_rtc_driver = { + .probe = sp_rtc_probe, + .remove = sp_rtc_remove, + .suspend = sp_rtc_suspend, + .resume = sp_rtc_resume, + .driver = { + .name = "sp7021-rtc", + .owner = THIS_MODULE, + .of_match_table = sp_rtc_of_match, + }, +}; +module_platform_driver(sp_rtc_driver); + +MODULE_AUTHOR("Vincent Shih <vincent.shih@xxxxxxxxxxx>"); +MODULE_DESCRIPTION("Sunplus RTC driver"); +MODULE_LICENSE("GPL v2"); + -- 2.7.4