]> git.proxmox.com Git - mirror_ubuntu-hirsute-kernel.git/commitdiff
rtc: Add Cadence RTC driver
authorJan Kotas <jank@cadence.com>
Tue, 22 Jan 2019 10:42:16 +0000 (10:42 +0000)
committerAlexandre Belloni <alexandre.belloni@bootlin.com>
Tue, 22 Jan 2019 18:18:27 +0000 (19:18 +0100)
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>
drivers/rtc/Kconfig
drivers/rtc/Makefile
drivers/rtc/rtc-cadence.c [new file with mode: 0644]

index a24fe8b7b98aceaea8f2e6d1fda81553aff2b9c9..faa9ae1a30622e85a881e2e51b69117ae2ef7d06 100644 (file)
@@ -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
index 39eb6de4ad74518682a502c3a319390e1d4ec8e3..faca02109aaa4dfa453c994aef7480d3c451d5cf 100644 (file)
@@ -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 (file)
index 0000000..3b7d643
--- /dev/null
@@ -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");