diff options
author | Jan Kotas <jank@cadence.com> | 2019-01-22 11:42:16 +0100 |
---|---|---|
committer | Alexandre Belloni <alexandre.belloni@bootlin.com> | 2019-01-22 19:18:27 +0100 |
commit | 42986fb8119928d32e585c73181f39506937a3b2 (patch) | |
tree | e9f8a30bd7904707d49740a7b6387bca8e745864 /drivers/rtc | |
parent | dt-bindings: rtc: Add bindings for Cadence RTC (diff) | |
download | linux-42986fb8119928d32e585c73181f39506937a3b2.tar.xz linux-42986fb8119928d32e585c73181f39506937a3b2.zip |
rtc: Add Cadence RTC driver
This patch adds a driver for Cadence RTC controller.
It can be enabled with RTC_DRV_CADENCE Kconfig option.
It supports waking system from sleep modes.
Signed-off-by: Jan Kotas <jank@cadence.com>
Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
Diffstat (limited to 'drivers/rtc')
-rw-r--r-- | drivers/rtc/Kconfig | 10 | ||||
-rw-r--r-- | drivers/rtc/Makefile | 1 | ||||
-rw-r--r-- | drivers/rtc/rtc-cadence.c | 423 |
3 files changed, 434 insertions, 0 deletions
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index a24fe8b7b98a..faa9ae1a3062 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -1517,6 +1517,16 @@ config RTC_DRV_ARMADA38X This driver can also be built as a module. If so, the module will be called armada38x-rtc. +config RTC_DRV_CADENCE + tristate "Cadence RTC driver" + depends on OF && HAS_IOMEM + help + If you say Y here you will get access to Cadence RTC IP + found on certain SOCs. + + To compile this driver as a module, choose M here: the + module will be called rtc-cadence. + config RTC_DRV_FTRTC010 tristate "Faraday Technology FTRTC010 RTC" depends on HAS_IOMEM diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index 39eb6de4ad74..faca02109aaa 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -39,6 +39,7 @@ obj-$(CONFIG_RTC_DRV_AU1XXX) += rtc-au1xxx.o obj-$(CONFIG_RTC_DRV_BQ32K) += rtc-bq32k.o obj-$(CONFIG_RTC_DRV_BQ4802) += rtc-bq4802.o obj-$(CONFIG_RTC_DRV_BRCMSTB) += rtc-brcmstb-waketimer.o +obj-$(CONFIG_RTC_DRV_CADENCE) += rtc-cadence.o obj-$(CONFIG_RTC_DRV_CMOS) += rtc-cmos.o obj-$(CONFIG_RTC_DRV_COH901331) += rtc-coh901331.o obj-$(CONFIG_RTC_DRV_CPCAP) += rtc-cpcap.o diff --git a/drivers/rtc/rtc-cadence.c b/drivers/rtc/rtc-cadence.c new file mode 100644 index 000000000000..3b7d643c8a63 --- /dev/null +++ b/drivers/rtc/rtc-cadence.c @@ -0,0 +1,423 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* + * Copyright 2019 Cadence + * + * Authors: + * Jan Kotas <jank@cadence.com> + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/of.h> +#include <linux/io.h> +#include <linux/rtc.h> +#include <linux/clk.h> +#include <linux/bcd.h> +#include <linux/bitfield.h> +#include <linux/interrupt.h> +#include <linux/pm_wakeirq.h> + +/* Registers */ +#define CDNS_RTC_CTLR 0x00 +#define CDNS_RTC_HMR 0x04 +#define CDNS_RTC_TIMR 0x08 +#define CDNS_RTC_CALR 0x0C +#define CDNS_RTC_TIMAR 0x10 +#define CDNS_RTC_CALAR 0x14 +#define CDNS_RTC_AENR 0x18 +#define CDNS_RTC_EFLR 0x1C +#define CDNS_RTC_IENR 0x20 +#define CDNS_RTC_IDISR 0x24 +#define CDNS_RTC_IMSKR 0x28 +#define CDNS_RTC_STSR 0x2C +#define CDNS_RTC_KRTCR 0x30 + +/* Control */ +#define CDNS_RTC_CTLR_TIME BIT(0) +#define CDNS_RTC_CTLR_CAL BIT(1) +#define CDNS_RTC_CTLR_TIME_CAL (CDNS_RTC_CTLR_TIME | CDNS_RTC_CTLR_CAL) + +/* Status */ +#define CDNS_RTC_STSR_VT BIT(0) +#define CDNS_RTC_STSR_VC BIT(1) +#define CDNS_RTC_STSR_VTA BIT(2) +#define CDNS_RTC_STSR_VCA BIT(3) +#define CDNS_RTC_STSR_VT_VC (CDNS_RTC_STSR_VT | CDNS_RTC_STSR_VC) +#define CDNS_RTC_STSR_VTA_VCA (CDNS_RTC_STSR_VTA | CDNS_RTC_STSR_VCA) + +/* Keep RTC */ +#define CDNS_RTC_KRTCR_KRTC BIT(0) + +/* Alarm, Event, Interrupt */ +#define CDNS_RTC_AEI_HOS BIT(0) +#define CDNS_RTC_AEI_SEC BIT(1) +#define CDNS_RTC_AEI_MIN BIT(2) +#define CDNS_RTC_AEI_HOUR BIT(3) +#define CDNS_RTC_AEI_DATE BIT(4) +#define CDNS_RTC_AEI_MNTH BIT(5) +#define CDNS_RTC_AEI_ALRM BIT(6) + +/* Time */ +#define CDNS_RTC_TIME_H GENMASK(7, 0) +#define CDNS_RTC_TIME_S GENMASK(14, 8) +#define CDNS_RTC_TIME_M GENMASK(22, 16) +#define CDNS_RTC_TIME_HR GENMASK(29, 24) +#define CDNS_RTC_TIME_PM BIT(30) +#define CDNS_RTC_TIME_CH BIT(31) + +/* Calendar */ +#define CDNS_RTC_CAL_DAY GENMASK(2, 0) +#define CDNS_RTC_CAL_M GENMASK(7, 3) +#define CDNS_RTC_CAL_D GENMASK(13, 8) +#define CDNS_RTC_CAL_Y GENMASK(23, 16) +#define CDNS_RTC_CAL_C GENMASK(29, 24) +#define CDNS_RTC_CAL_CH BIT(31) + +#define CDNS_RTC_MAX_REGS_TRIES 3 + +struct cdns_rtc { + struct rtc_device *rtc_dev; + struct clk *pclk; + struct clk *ref_clk; + void __iomem *regs; + int irq; +}; + +static void cdns_rtc_set_enabled(struct cdns_rtc *crtc, bool enabled) +{ + u32 reg = enabled ? 0x0 : CDNS_RTC_CTLR_TIME_CAL; + + writel(reg, crtc->regs + CDNS_RTC_CTLR); +} + +static bool cdns_rtc_get_enabled(struct cdns_rtc *crtc) +{ + return !(readl(crtc->regs + CDNS_RTC_CTLR) & CDNS_RTC_CTLR_TIME_CAL); +} + +static irqreturn_t cdns_rtc_irq_handler(int irq, void *id) +{ + struct device *dev = id; + struct cdns_rtc *crtc = dev_get_drvdata(dev); + + /* Reading the register clears it */ + if (!(readl(crtc->regs + CDNS_RTC_EFLR) & CDNS_RTC_AEI_ALRM)) + return IRQ_NONE; + + rtc_update_irq(crtc->rtc_dev, 1, RTC_IRQF | RTC_AF); + return IRQ_HANDLED; +} + +static u32 cdns_rtc_time2reg(struct rtc_time *tm) +{ + return FIELD_PREP(CDNS_RTC_TIME_S, bin2bcd(tm->tm_sec)) + | FIELD_PREP(CDNS_RTC_TIME_M, bin2bcd(tm->tm_min)) + | FIELD_PREP(CDNS_RTC_TIME_HR, bin2bcd(tm->tm_hour)); +} + +static void cdns_rtc_reg2time(u32 reg, struct rtc_time *tm) +{ + tm->tm_sec = bcd2bin(FIELD_GET(CDNS_RTC_TIME_S, reg)); + tm->tm_min = bcd2bin(FIELD_GET(CDNS_RTC_TIME_M, reg)); + tm->tm_hour = bcd2bin(FIELD_GET(CDNS_RTC_TIME_HR, reg)); +} + +static int cdns_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + struct cdns_rtc *crtc = dev_get_drvdata(dev); + u32 reg; + + /* If the RTC is disabled, assume the values are invalid */ + if (!cdns_rtc_get_enabled(crtc)) + return -EINVAL; + + cdns_rtc_set_enabled(crtc, false); + + reg = readl(crtc->regs + CDNS_RTC_TIMR); + cdns_rtc_reg2time(reg, tm); + + reg = readl(crtc->regs + CDNS_RTC_CALR); + tm->tm_mday = bcd2bin(FIELD_GET(CDNS_RTC_CAL_D, reg)); + tm->tm_mon = bcd2bin(FIELD_GET(CDNS_RTC_CAL_M, reg)) - 1; + tm->tm_year = bcd2bin(FIELD_GET(CDNS_RTC_CAL_Y, reg)) + + bcd2bin(FIELD_GET(CDNS_RTC_CAL_C, reg)) * 100 - 1900; + tm->tm_wday = bcd2bin(FIELD_GET(CDNS_RTC_CAL_DAY, reg)) - 1; + + cdns_rtc_set_enabled(crtc, true); + return 0; +} + +static int cdns_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + struct cdns_rtc *crtc = dev_get_drvdata(dev); + u32 timr, calr, stsr; + int ret = -EIO; + int year = tm->tm_year + 1900; + int tries; + + cdns_rtc_set_enabled(crtc, false); + + timr = cdns_rtc_time2reg(tm); + + calr = FIELD_PREP(CDNS_RTC_CAL_D, bin2bcd(tm->tm_mday)) + | FIELD_PREP(CDNS_RTC_CAL_M, bin2bcd(tm->tm_mon + 1)) + | FIELD_PREP(CDNS_RTC_CAL_Y, bin2bcd(year % 100)) + | FIELD_PREP(CDNS_RTC_CAL_C, bin2bcd(year / 100)) + | FIELD_PREP(CDNS_RTC_CAL_DAY, tm->tm_wday + 1); + + /* Update registers, check valid flags */ + for (tries = 0; tries < CDNS_RTC_MAX_REGS_TRIES; tries++) { + writel(timr, crtc->regs + CDNS_RTC_TIMR); + writel(calr, crtc->regs + CDNS_RTC_CALR); + stsr = readl(crtc->regs + CDNS_RTC_STSR); + + if ((stsr & CDNS_RTC_STSR_VT_VC) == CDNS_RTC_STSR_VT_VC) { + ret = 0; + break; + } + } + + cdns_rtc_set_enabled(crtc, true); + return ret; +} + +static int cdns_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled) +{ + struct cdns_rtc *crtc = dev_get_drvdata(dev); + + if (enabled) { + writel((CDNS_RTC_AEI_SEC | CDNS_RTC_AEI_MIN | CDNS_RTC_AEI_HOUR + | CDNS_RTC_AEI_DATE | CDNS_RTC_AEI_MNTH), + crtc->regs + CDNS_RTC_AENR); + writel(CDNS_RTC_AEI_ALRM, crtc->regs + CDNS_RTC_IENR); + } else { + writel(0, crtc->regs + CDNS_RTC_AENR); + writel(CDNS_RTC_AEI_ALRM, crtc->regs + CDNS_RTC_IDISR); + } + + return 0; +} + +static int cdns_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm) +{ + struct cdns_rtc *crtc = dev_get_drvdata(dev); + u32 reg; + + reg = readl(crtc->regs + CDNS_RTC_TIMAR); + cdns_rtc_reg2time(reg, &alarm->time); + + reg = readl(crtc->regs + CDNS_RTC_CALAR); + alarm->time.tm_mday = bcd2bin(FIELD_GET(CDNS_RTC_CAL_D, reg)); + alarm->time.tm_mon = bcd2bin(FIELD_GET(CDNS_RTC_CAL_M, reg)) - 1; + + return 0; +} + +static int cdns_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm) +{ + struct cdns_rtc *crtc = dev_get_drvdata(dev); + int ret = -EIO; + int tries; + u32 timar, calar, stsr; + + cdns_rtc_alarm_irq_enable(dev, 0); + + timar = cdns_rtc_time2reg(&alarm->time); + calar = FIELD_PREP(CDNS_RTC_CAL_D, bin2bcd(alarm->time.tm_mday)) + | FIELD_PREP(CDNS_RTC_CAL_M, bin2bcd(alarm->time.tm_mon + 1)); + + /* Update registers, check valid alarm flags */ + for (tries = 0; tries < CDNS_RTC_MAX_REGS_TRIES; tries++) { + writel(timar, crtc->regs + CDNS_RTC_TIMAR); + writel(calar, crtc->regs + CDNS_RTC_CALAR); + stsr = readl(crtc->regs + CDNS_RTC_STSR); + + if ((stsr & CDNS_RTC_STSR_VTA_VCA) == CDNS_RTC_STSR_VTA_VCA) { + ret = 0; + break; + } + } + + if (!ret) + cdns_rtc_alarm_irq_enable(dev, alarm->enabled); + return ret; +} + +static const struct rtc_class_ops cdns_rtc_ops = { + .read_time = cdns_rtc_read_time, + .set_time = cdns_rtc_set_time, + .read_alarm = cdns_rtc_read_alarm, + .set_alarm = cdns_rtc_set_alarm, + .alarm_irq_enable = cdns_rtc_alarm_irq_enable, +}; + +static int cdns_rtc_probe(struct platform_device *pdev) +{ + struct cdns_rtc *crtc; + struct resource *res; + int ret; + unsigned long ref_clk_freq; + + crtc = devm_kzalloc(&pdev->dev, sizeof(*crtc), GFP_KERNEL); + if (!crtc) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + crtc->regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(crtc->regs)) + return PTR_ERR(crtc->regs); + + crtc->irq = platform_get_irq(pdev, 0); + if (crtc->irq < 0) + return -EINVAL; + + crtc->pclk = devm_clk_get(&pdev->dev, "pclk"); + if (IS_ERR(crtc->pclk)) { + ret = PTR_ERR(crtc->pclk); + dev_err(&pdev->dev, + "Failed to retrieve the peripheral clock, %d\n", ret); + return ret; + } + + crtc->ref_clk = devm_clk_get(&pdev->dev, "ref_clk"); + if (IS_ERR(crtc->ref_clk)) { + ret = PTR_ERR(crtc->ref_clk); + dev_err(&pdev->dev, + "Failed to retrieve the reference clock, %d\n", ret); + return ret; + } + + crtc->rtc_dev = devm_rtc_allocate_device(&pdev->dev); + if (IS_ERR(crtc->rtc_dev)) { + ret = PTR_ERR(crtc->rtc_dev); + dev_err(&pdev->dev, + "Failed to allocate the RTC device, %d\n", ret); + return ret; + } + + platform_set_drvdata(pdev, crtc); + + ret = clk_prepare_enable(crtc->pclk); + if (ret) { + dev_err(&pdev->dev, + "Failed to enable the peripheral clock, %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(crtc->ref_clk); + if (ret) { + dev_err(&pdev->dev, + "Failed to enable the reference clock, %d\n", ret); + goto err_disable_pclk; + } + + ref_clk_freq = clk_get_rate(crtc->ref_clk); + if ((ref_clk_freq != 1) && (ref_clk_freq != 100)) { + dev_err(&pdev->dev, + "Invalid reference clock frequency %lu Hz.\n", + ref_clk_freq); + ret = -EINVAL; + goto err_disable_ref_clk; + } + + ret = devm_request_irq(&pdev->dev, crtc->irq, + cdns_rtc_irq_handler, 0, + dev_name(&pdev->dev), &pdev->dev); + if (ret) { + dev_err(&pdev->dev, + "Failed to request interrupt for the device, %d\n", + ret); + goto err_disable_ref_clk; + } + + /* The RTC supports 01.01.1900 - 31.12.2999 */ + crtc->rtc_dev->range_min = mktime64(1900, 1, 1, 0, 0, 0); + crtc->rtc_dev->range_max = mktime64(2999, 12, 31, 23, 59, 59); + + crtc->rtc_dev->ops = &cdns_rtc_ops; + device_init_wakeup(&pdev->dev, true); + + /* Always use 24-hour mode and keep the RTC values */ + writel(0, crtc->regs + CDNS_RTC_HMR); + writel(CDNS_RTC_KRTCR_KRTC, crtc->regs + CDNS_RTC_KRTCR); + + ret = rtc_register_device(crtc->rtc_dev); + if (ret) { + dev_err(&pdev->dev, + "Failed to register the RTC device, %d\n", ret); + goto err_disable_wakeup; + } + + return 0; + +err_disable_wakeup: + device_init_wakeup(&pdev->dev, false); + +err_disable_ref_clk: + clk_disable_unprepare(crtc->ref_clk); + +err_disable_pclk: + clk_disable_unprepare(crtc->pclk); + + return ret; +} + +static int cdns_rtc_remove(struct platform_device *pdev) +{ + struct cdns_rtc *crtc = platform_get_drvdata(pdev); + + cdns_rtc_alarm_irq_enable(&pdev->dev, 0); + device_init_wakeup(&pdev->dev, 0); + + clk_disable_unprepare(crtc->pclk); + clk_disable_unprepare(crtc->ref_clk); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int cdns_rtc_suspend(struct device *dev) +{ + struct cdns_rtc *crtc = dev_get_drvdata(dev); + + if (device_may_wakeup(dev)) + enable_irq_wake(crtc->irq); + + return 0; +} + +static int cdns_rtc_resume(struct device *dev) +{ + struct cdns_rtc *crtc = dev_get_drvdata(dev); + + if (device_may_wakeup(dev)) + disable_irq_wake(crtc->irq); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(cdns_rtc_pm_ops, cdns_rtc_suspend, cdns_rtc_resume); + +static const struct of_device_id cdns_rtc_of_match[] = { + { .compatible = "cdns,rtc-r109v3" }, + { }, +}; +MODULE_DEVICE_TABLE(of, cdns_rtc_of_match); + +static struct platform_driver cdns_rtc_driver = { + .driver = { + .name = "cdns-rtc", + .of_match_table = cdns_rtc_of_match, + .pm = &cdns_rtc_pm_ops, + }, + .probe = cdns_rtc_probe, + .remove = cdns_rtc_remove, +}; +module_platform_driver(cdns_rtc_driver); + +MODULE_AUTHOR("Jan Kotas <jank@cadence.com>"); +MODULE_DESCRIPTION("Cadence RTC driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:cdns-rtc"); |