Merge tag 'rtc-5.19' of git://git.kernel.org/pub/scm/linux/kernel/git/abelloni/linux
authorLinus Torvalds <torvalds@linux-foundation.org>
Wed, 1 Jun 2022 21:48:13 +0000 (14:48 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Wed, 1 Jun 2022 21:48:13 +0000 (14:48 -0700)
Pull RTC updates from Alexandre Belloni:
 "A new driver represents the bulk of the changes and then we get the
  usual small fixes.

  New driver:

   - Renesas RZN1 rtc

  Drivers:

   - sun6i: Add nvmem support"

* tag 'rtc-5.19' of git://git.kernel.org/pub/scm/linux/kernel/git/abelloni/linux:
  rtc: mxc: Silence a clang warning
  rtc: rzn1: Fix a variable type
  rtc: rzn1: Fix error code in probe
  rtc: rzn1: Avoid mixing variables
  rtc: ftrtc010: Fix error handling in ftrtc010_rtc_probe
  rtc: mt6397: check return value after calling platform_get_resource()
  rtc: rzn1: fix platform_no_drv_owner.cocci warning
  rtc: gamecube: Add missing iounmap in gamecube_rtc_read_offset_from_sram
  rtc: meson: Fix email address in MODULE_AUTHOR
  rtc: simplify the return expression of rx8025_set_offset()
  rtc: pcf85063: Add a compatible entry for pca85073a
  dt-binding: pcf85063: Add an entry for pca85073a
  MAINTAINERS: Add myself as maintainer of the RZN1 RTC driver
  rtc: rzn1: Add oscillator offset support
  rtc: rzn1: Add alarm support
  rtc: rzn1: Add new RTC driver
  dt-bindings: rtc: rzn1: Describe the RZN1 RTC
  rtc: sun6i: Add NVMEM provider

14 files changed:
Documentation/devicetree/bindings/rtc/nxp,pcf85063.txt
Documentation/devicetree/bindings/rtc/renesas,rzn1-rtc.yaml [new file with mode: 0644]
MAINTAINERS
drivers/rtc/Kconfig
drivers/rtc/Makefile
drivers/rtc/rtc-ftrtc010.c
drivers/rtc/rtc-gamecube.c
drivers/rtc/rtc-meson.c
drivers/rtc/rtc-mt6397.c
drivers/rtc/rtc-mxc.c
drivers/rtc/rtc-pcf85063.c
drivers/rtc/rtc-rx8025.c
drivers/rtc/rtc-rzn1.c [new file with mode: 0644]
drivers/rtc/rtc-sun6i.c

index 6439682c931960f7a94f0a9eb01efe3c8eff9445..217b7cd06c11e235c0435eed0d53ccb96f130399 100644 (file)
@@ -2,6 +2,7 @@
 
 Required properties:
 - compatible: Should one of contain:
+       "nxp,pca85073a",
        "nxp,pcf85063",
        "nxp,pcf85063a",
        "nxp,pcf85063tp",
diff --git a/Documentation/devicetree/bindings/rtc/renesas,rzn1-rtc.yaml b/Documentation/devicetree/bindings/rtc/renesas,rzn1-rtc.yaml
new file mode 100644 (file)
index 0000000..2d4741f
--- /dev/null
@@ -0,0 +1,70 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/rtc/renesas,rzn1-rtc.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Renesas RZ/N1 SoCs Real-Time Clock DT bindings
+
+maintainers:
+  - Miquel Raynal <miquel.raynal@bootlin.com>
+
+allOf:
+  - $ref: rtc.yaml#
+
+properties:
+  compatible:
+    items:
+      - enum:
+          - renesas,r9a06g032-rtc
+      - const: renesas,rzn1-rtc
+
+  reg:
+    maxItems: 1
+
+  interrupts:
+    minItems: 3
+    maxItems: 3
+
+  interrupt-names:
+    items:
+      - const: alarm
+      - const: timer
+      - const: pps
+
+  clocks:
+    maxItems: 1
+
+  clock-names:
+    const: hclk
+
+  power-domains:
+    maxItems: 1
+
+required:
+  - compatible
+  - reg
+  - interrupts
+  - interrupt-names
+  - clocks
+  - clock-names
+  - power-domains
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/interrupt-controller/arm-gic.h>
+    #include <dt-bindings/clock/r9a06g032-sysctrl.h>
+    rtc@40006000 {
+       compatible = "renesas,r9a06g032-rtc", "renesas,rzn1-rtc";
+       reg = <0x40006000 0x1000>;
+       interrupts = <GIC_SPI 66 IRQ_TYPE_EDGE_RISING>,
+                    <GIC_SPI 67 IRQ_TYPE_EDGE_RISING>,
+                    <GIC_SPI 68 IRQ_TYPE_EDGE_RISING>;
+       interrupt-names = "alarm", "timer", "pps";
+       clocks = <&sysctrl R9A06G032_HCLK_RTC>;
+       clock-names = "hclk";
+       power-domains = <&sysctrl>;
+       start-year = <2000>;
+     };
index f1eb3f7d792820fdaf6a4eb772db2fa9999ddd08..7dff84d95e5f289fd1ca91cf31e2e5c33229ec2d 100644 (file)
@@ -16995,6 +16995,14 @@ S:     Supported
 F:     Documentation/devicetree/bindings/iio/adc/renesas,rzg2l-adc.yaml
 F:     drivers/iio/adc/rzg2l_adc.c
 
+RENESAS RZ/N1 RTC CONTROLLER DRIVER
+M:     Miquel Raynal <miquel.raynal@bootlin.com>
+L:     linux-rtc@vger.kernel.org
+L:     linux-renesas-soc@vger.kernel.org
+S:     Maintained
+F:     Documentation/devicetree/bindings/rtc/renesas,rzn1-rtc.yaml
+F:     drivers/rtc/rtc-rzn1.c
+
 RENESAS R-CAR GEN3 & RZ/N1 NAND CONTROLLER DRIVER
 M:     Miquel Raynal <miquel.raynal@bootlin.com>
 L:     linux-mtd@lists.infradead.org
index 41c65b4d2baf63b26b886b1f524697c027d9eabd..a00f901b5c1d7bf701e68ff6e14e96f06e3e486f 100644 (file)
@@ -1548,6 +1548,13 @@ config RTC_DRV_RS5C313
        help
          If you say yes here you get support for the Ricoh RS5C313 RTC chips.
 
+config RTC_DRV_RZN1
+       tristate "Renesas RZ/N1 RTC"
+       depends on ARCH_RZN1 || COMPILE_TEST
+       depends on OF && HAS_IOMEM
+       help
+         If you say yes here you get support for the Renesas RZ/N1 RTC.
+
 config RTC_DRV_GENERIC
        tristate "Generic RTC support"
        # Please consider writing a new RTC driver instead of using the generic
index 2d827d8261d55892f99c1ba891208bcdb8f2ce3b..fb04467b652df2d32ae1795d5e557d5a7bbc3958 100644 (file)
@@ -151,6 +151,7 @@ obj-$(CONFIG_RTC_DRV_RX6110)        += rtc-rx6110.o
 obj-$(CONFIG_RTC_DRV_RX8010)   += rtc-rx8010.o
 obj-$(CONFIG_RTC_DRV_RX8025)   += rtc-rx8025.o
 obj-$(CONFIG_RTC_DRV_RX8581)   += rtc-rx8581.o
+obj-$(CONFIG_RTC_DRV_RZN1)     += rtc-rzn1.o
 obj-$(CONFIG_RTC_DRV_S35390A)  += rtc-s35390a.o
 obj-$(CONFIG_RTC_DRV_S3C)      += rtc-s3c.o
 obj-$(CONFIG_RTC_DRV_S5M)      += rtc-s5m.o
index 53bb08fe1cd462628d8dbedabdbed7f6fe7a15d3..25c6e7d9570f017ed2bcae9683b880984d2c0cfe 100644 (file)
@@ -137,26 +137,34 @@ static int ftrtc010_rtc_probe(struct platform_device *pdev)
                ret = clk_prepare_enable(rtc->extclk);
                if (ret) {
                        dev_err(dev, "failed to enable EXTCLK\n");
-                       return ret;
+                       goto err_disable_pclk;
                }
        }
 
        rtc->rtc_irq = platform_get_irq(pdev, 0);
-       if (rtc->rtc_irq < 0)
-               return rtc->rtc_irq;
+       if (rtc->rtc_irq < 0) {
+               ret = rtc->rtc_irq;
+               goto err_disable_extclk;
+       }
 
        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
-       if (!res)
-               return -ENODEV;
+       if (!res) {
+               ret = -ENODEV;
+               goto err_disable_extclk;
+       }
 
        rtc->rtc_base = devm_ioremap(dev, res->start,
                                     resource_size(res));
-       if (!rtc->rtc_base)
-               return -ENOMEM;
+       if (!rtc->rtc_base) {
+               ret = -ENOMEM;
+               goto err_disable_extclk;
+       }
 
        rtc->rtc_dev = devm_rtc_allocate_device(dev);
-       if (IS_ERR(rtc->rtc_dev))
-               return PTR_ERR(rtc->rtc_dev);
+       if (IS_ERR(rtc->rtc_dev)) {
+               ret = PTR_ERR(rtc->rtc_dev);
+               goto err_disable_extclk;
+       }
 
        rtc->rtc_dev->ops = &ftrtc010_rtc_ops;
 
@@ -172,9 +180,15 @@ static int ftrtc010_rtc_probe(struct platform_device *pdev)
        ret = devm_request_irq(dev, rtc->rtc_irq, ftrtc010_rtc_interrupt,
                               IRQF_SHARED, pdev->name, dev);
        if (unlikely(ret))
-               return ret;
+               goto err_disable_extclk;
 
        return devm_rtc_register_device(rtc->rtc_dev);
+
+err_disable_extclk:
+       clk_disable_unprepare(rtc->extclk);
+err_disable_pclk:
+       clk_disable_unprepare(rtc->pclk);
+       return ret;
 }
 
 static int ftrtc010_rtc_remove(struct platform_device *pdev)
index 18ca3b38b2d0474f58f4a56b3ae94d5afb3f86d9..c2717bb52b2bee9e5996c543ba2212f657bc09d2 100644 (file)
@@ -267,6 +267,7 @@ static int gamecube_rtc_read_offset_from_sram(struct priv *d)
        ret = regmap_read(d->regmap, RTC_SRAM_BIAS, &d->rtc_bias);
        if (ret) {
                pr_err("failed to get the RTC bias\n");
+               iounmap(hw_srnprot);
                return -1;
        }
 
index 44bdc8b4a90d6b27b294e36c1f9e6c44e88b4df4..db1d626edca5fbb590f26c1cdd0774d350244a99 100644 (file)
@@ -399,7 +399,7 @@ static struct platform_driver meson_rtc_driver = {
 module_platform_driver(meson_rtc_driver);
 
 MODULE_DESCRIPTION("Amlogic Meson RTC Driver");
-MODULE_AUTHOR("Ben Dooks <ben.doosk@codethink.co.uk>");
+MODULE_AUTHOR("Ben Dooks <ben.dooks@codethink.co.uk>");
 MODULE_AUTHOR("Martin Blumenstingl <martin.blumenstingl@googlemail.com>");
 MODULE_LICENSE("GPL v2");
 MODULE_ALIAS("platform:meson-rtc");
index 80dc479a6ff02f63b49f9fba67bf40808507ce92..1d297af80f878818f5ee6ca00a6aab7f501b9742 100644 (file)
@@ -269,6 +269,8 @@ static int mtk_rtc_probe(struct platform_device *pdev)
                return -ENOMEM;
 
        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       if (!res)
+               return -EINVAL;
        rtc->addr_base = res->start;
 
        rtc->data = of_device_get_match_data(&pdev->dev);
index 0f08f22df869eed6882c5745022b926ebccdee58..53d4e253e81f07216136482dc189edaec6e7f330 100644 (file)
@@ -311,7 +311,7 @@ static int mxc_rtc_probe(struct platform_device *pdev)
        if (!pdata)
                return -ENOMEM;
 
-       pdata->devtype = (enum imx_rtc_type)of_device_get_match_data(&pdev->dev);
+       pdata->devtype = (uintptr_t)of_device_get_match_data(&pdev->dev);
 
        pdata->ioaddr = devm_platform_ioremap_resource(pdev, 0);
        if (IS_ERR(pdata->ioaddr))
index 9760824ec199e8dda5a583e0dddd45eb06d4a6ca..095891999da11287985c562829f90d9b4f2999ca 100644 (file)
@@ -650,6 +650,7 @@ static int pcf85063_probe(struct i2c_client *client)
 }
 
 static const struct i2c_device_id pcf85063_ids[] = {
+       { "pca85073a", PCF85063A },
        { "pcf85063", PCF85063 },
        { "pcf85063tp", PCF85063TP },
        { "pcf85063a", PCF85063A },
@@ -660,6 +661,7 @@ MODULE_DEVICE_TABLE(i2c, pcf85063_ids);
 
 #ifdef CONFIG_OF
 static const struct of_device_id pcf85063_of_match[] = {
+       { .compatible = "nxp,pca85073a", .data = &pcf85063_cfg[PCF85063A] },
        { .compatible = "nxp,pcf85063", .data = &pcf85063_cfg[PCF85063] },
        { .compatible = "nxp,pcf85063tp", .data = &pcf85063_cfg[PCF85063TP] },
        { .compatible = "nxp,pcf85063a", .data = &pcf85063_cfg[PCF85063A] },
index 5bfdd34a72fffad8c5dfb256b1f5b821d28dff06..b32117ccd74bd21d1a20f0f5463d4c2d225ee7ac 100644 (file)
@@ -436,7 +436,6 @@ static int rx8025_set_offset(struct device *dev, long offset)
 {
        struct i2c_client *client = to_i2c_client(dev);
        u8 digoff;
-       int err;
 
        offset /= RX8025_ADJ_RESOLUTION;
        if (offset > RX8025_ADJ_DATA_MAX)
@@ -449,11 +448,7 @@ static int rx8025_set_offset(struct device *dev, long offset)
                offset += 128;
        digoff = offset;
 
-       err = rx8025_write_reg(client, RX8025_REG_DIGOFF, digoff);
-       if (err)
-               return err;
-
-       return 0;
+       return rx8025_write_reg(client, RX8025_REG_DIGOFF, digoff);
 }
 
 static const struct rtc_class_ops rx8025_rtc_ops = {
diff --git a/drivers/rtc/rtc-rzn1.c b/drivers/rtc/rtc-rzn1.c
new file mode 100644 (file)
index 0000000..ac78879
--- /dev/null
@@ -0,0 +1,418 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Renesas RZ/N1 Real Time Clock interface for Linux
+ *
+ * Copyright:
+ * - 2014 Renesas Electronics Europe Limited
+ * - 2022 Schneider Electric
+ *
+ * Authors:
+ * - Michel Pollet <michel.pollet@bp.renesas.com>, <buserror@gmail.com>
+ * - Miquel Raynal <miquel.raynal@bootlin.com>
+ */
+
+#include <linux/bcd.h>
+#include <linux/init.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/rtc.h>
+
+#define RZN1_RTC_CTL0 0x00
+#define   RZN1_RTC_CTL0_SLSB_SUBU 0
+#define   RZN1_RTC_CTL0_SLSB_SCMP BIT(4)
+#define   RZN1_RTC_CTL0_AMPM BIT(5)
+#define   RZN1_RTC_CTL0_CE BIT(7)
+
+#define RZN1_RTC_CTL1 0x04
+#define   RZN1_RTC_CTL1_ALME BIT(4)
+
+#define RZN1_RTC_CTL2 0x08
+#define   RZN1_RTC_CTL2_WAIT BIT(0)
+#define   RZN1_RTC_CTL2_WST BIT(1)
+#define   RZN1_RTC_CTL2_WUST BIT(5)
+#define   RZN1_RTC_CTL2_STOPPED (RZN1_RTC_CTL2_WAIT | RZN1_RTC_CTL2_WST)
+
+#define RZN1_RTC_SEC 0x14
+#define RZN1_RTC_MIN 0x18
+#define RZN1_RTC_HOUR 0x1c
+#define RZN1_RTC_WEEK 0x20
+#define RZN1_RTC_DAY 0x24
+#define RZN1_RTC_MONTH 0x28
+#define RZN1_RTC_YEAR 0x2c
+
+#define RZN1_RTC_SUBU 0x38
+#define   RZN1_RTC_SUBU_DEV BIT(7)
+#define   RZN1_RTC_SUBU_DECR BIT(6)
+
+#define RZN1_RTC_ALM 0x40
+#define RZN1_RTC_ALH 0x44
+#define RZN1_RTC_ALW 0x48
+
+#define RZN1_RTC_SECC 0x4c
+#define RZN1_RTC_MINC 0x50
+#define RZN1_RTC_HOURC 0x54
+#define RZN1_RTC_WEEKC 0x58
+#define RZN1_RTC_DAYC 0x5c
+#define RZN1_RTC_MONTHC 0x60
+#define RZN1_RTC_YEARC 0x64
+
+struct rzn1_rtc {
+       struct rtc_device *rtcdev;
+       void __iomem *base;
+};
+
+static void rzn1_rtc_get_time_snapshot(struct rzn1_rtc *rtc, struct rtc_time *tm)
+{
+       tm->tm_sec = readl(rtc->base + RZN1_RTC_SECC);
+       tm->tm_min = readl(rtc->base + RZN1_RTC_MINC);
+       tm->tm_hour = readl(rtc->base + RZN1_RTC_HOURC);
+       tm->tm_wday = readl(rtc->base + RZN1_RTC_WEEKC);
+       tm->tm_mday = readl(rtc->base + RZN1_RTC_DAYC);
+       tm->tm_mon = readl(rtc->base + RZN1_RTC_MONTHC);
+       tm->tm_year = readl(rtc->base + RZN1_RTC_YEARC);
+}
+
+static unsigned int rzn1_rtc_tm_to_wday(struct rtc_time *tm)
+{
+       time64_t time;
+       unsigned int days;
+       u32 secs;
+
+       time = rtc_tm_to_time64(tm);
+       days = div_s64_rem(time, 86400, &secs);
+
+       /* day of the week, 1970-01-01 was a Thursday */
+       return (days + 4) % 7;
+}
+
+static int rzn1_rtc_read_time(struct device *dev, struct rtc_time *tm)
+{
+       struct rzn1_rtc *rtc = dev_get_drvdata(dev);
+       u32 val, secs;
+
+       /*
+        * The RTC was not started or is stopped and thus does not carry the
+        * proper time/date.
+        */
+       val = readl(rtc->base + RZN1_RTC_CTL2);
+       if (val & RZN1_RTC_CTL2_STOPPED)
+               return -EINVAL;
+
+       rzn1_rtc_get_time_snapshot(rtc, tm);
+       secs = readl(rtc->base + RZN1_RTC_SECC);
+       if (tm->tm_sec != secs)
+               rzn1_rtc_get_time_snapshot(rtc, tm);
+
+       tm->tm_sec = bcd2bin(tm->tm_sec);
+       tm->tm_min = bcd2bin(tm->tm_min);
+       tm->tm_hour = bcd2bin(tm->tm_hour);
+       tm->tm_wday = bcd2bin(tm->tm_wday);
+       tm->tm_mday = bcd2bin(tm->tm_mday);
+       tm->tm_mon = bcd2bin(tm->tm_mon);
+       tm->tm_year = bcd2bin(tm->tm_year);
+
+       return 0;
+}
+
+static int rzn1_rtc_set_time(struct device *dev, struct rtc_time *tm)
+{
+       struct rzn1_rtc *rtc = dev_get_drvdata(dev);
+       u32 val;
+       int ret;
+
+       tm->tm_sec = bin2bcd(tm->tm_sec);
+       tm->tm_min = bin2bcd(tm->tm_min);
+       tm->tm_hour = bin2bcd(tm->tm_hour);
+       tm->tm_wday = bin2bcd(rzn1_rtc_tm_to_wday(tm));
+       tm->tm_mday = bin2bcd(tm->tm_mday);
+       tm->tm_mon = bin2bcd(tm->tm_mon);
+       tm->tm_year = bin2bcd(tm->tm_year);
+
+       val = readl(rtc->base + RZN1_RTC_CTL2);
+       if (!(val & RZN1_RTC_CTL2_STOPPED)) {
+               /* Hold the counter if it was counting up */
+               writel(RZN1_RTC_CTL2_WAIT, rtc->base + RZN1_RTC_CTL2);
+
+               /* Wait for the counter to stop: two 32k clock cycles */
+               usleep_range(61, 100);
+               ret = readl_poll_timeout(rtc->base + RZN1_RTC_CTL2, val,
+                                        val & RZN1_RTC_CTL2_WST, 0, 100);
+               if (ret)
+                       return ret;
+       }
+
+       writel(tm->tm_sec, rtc->base + RZN1_RTC_SEC);
+       writel(tm->tm_min, rtc->base + RZN1_RTC_MIN);
+       writel(tm->tm_hour, rtc->base + RZN1_RTC_HOUR);
+       writel(tm->tm_wday, rtc->base + RZN1_RTC_WEEK);
+       writel(tm->tm_mday, rtc->base + RZN1_RTC_DAY);
+       writel(tm->tm_mon, rtc->base + RZN1_RTC_MONTH);
+       writel(tm->tm_year, rtc->base + RZN1_RTC_YEAR);
+       writel(0, rtc->base + RZN1_RTC_CTL2);
+
+       return 0;
+}
+
+static irqreturn_t rzn1_rtc_alarm_irq(int irq, void *dev_id)
+{
+       struct rzn1_rtc *rtc = dev_id;
+
+       rtc_update_irq(rtc->rtcdev, 1, RTC_AF | RTC_IRQF);
+
+       return IRQ_HANDLED;
+}
+
+static int rzn1_rtc_alarm_irq_enable(struct device *dev, unsigned int enable)
+{
+       struct rzn1_rtc *rtc = dev_get_drvdata(dev);
+       u32 ctl1 = readl(rtc->base + RZN1_RTC_CTL1);
+
+       if (enable)
+               ctl1 |= RZN1_RTC_CTL1_ALME;
+       else
+               ctl1 &= ~RZN1_RTC_CTL1_ALME;
+
+       writel(ctl1, rtc->base + RZN1_RTC_CTL1);
+
+       return 0;
+}
+
+static int rzn1_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+       struct rzn1_rtc *rtc = dev_get_drvdata(dev);
+       struct rtc_time *tm = &alrm->time;
+       unsigned int min, hour, wday, delta_days;
+       time64_t alarm;
+       u32 ctl1;
+       int ret;
+
+       ret = rzn1_rtc_read_time(dev, tm);
+       if (ret)
+               return ret;
+
+       min = readl(rtc->base + RZN1_RTC_ALM);
+       hour = readl(rtc->base + RZN1_RTC_ALH);
+       wday = readl(rtc->base + RZN1_RTC_ALW);
+
+       tm->tm_sec = 0;
+       tm->tm_min = bcd2bin(min);
+       tm->tm_hour = bcd2bin(hour);
+       delta_days = ((fls(wday) - 1) - tm->tm_wday + 7) % 7;
+       tm->tm_wday = fls(wday) - 1;
+
+       if (delta_days) {
+               alarm = rtc_tm_to_time64(tm) + (delta_days * 86400);
+               rtc_time64_to_tm(alarm, tm);
+       }
+
+       ctl1 = readl(rtc->base + RZN1_RTC_CTL1);
+       alrm->enabled = !!(ctl1 & RZN1_RTC_CTL1_ALME);
+
+       return 0;
+}
+
+static int rzn1_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+       struct rzn1_rtc *rtc = dev_get_drvdata(dev);
+       struct rtc_time *tm = &alrm->time, tm_now;
+       unsigned long alarm, farest;
+       unsigned int days_ahead, wday;
+       int ret;
+
+       ret = rzn1_rtc_read_time(dev, &tm_now);
+       if (ret)
+               return ret;
+
+       /* We cannot set alarms more than one week ahead */
+       farest = rtc_tm_to_time64(&tm_now) + (7 * 86400);
+       alarm = rtc_tm_to_time64(tm);
+       if (time_after(alarm, farest))
+               return -ERANGE;
+
+       /* Convert alarm day into week day */
+       days_ahead = tm->tm_mday - tm_now.tm_mday;
+       wday = (tm_now.tm_wday + days_ahead) % 7;
+
+       writel(bin2bcd(tm->tm_min), rtc->base + RZN1_RTC_ALM);
+       writel(bin2bcd(tm->tm_hour), rtc->base + RZN1_RTC_ALH);
+       writel(BIT(wday), rtc->base + RZN1_RTC_ALW);
+
+       rzn1_rtc_alarm_irq_enable(dev, alrm->enabled);
+
+       return 0;
+}
+
+static int rzn1_rtc_read_offset(struct device *dev, long *offset)
+{
+       struct rzn1_rtc *rtc = dev_get_drvdata(dev);
+       unsigned int ppb_per_step;
+       bool subtract;
+       u32 val;
+
+       val = readl(rtc->base + RZN1_RTC_SUBU);
+       ppb_per_step = val & RZN1_RTC_SUBU_DEV ? 1017 : 3051;
+       subtract = val & RZN1_RTC_SUBU_DECR;
+       val &= 0x3F;
+
+       if (!val)
+               *offset = 0;
+       else if (subtract)
+               *offset = -(((~val) & 0x3F) + 1) * ppb_per_step;
+       else
+               *offset = (val - 1) * ppb_per_step;
+
+       return 0;
+}
+
+static int rzn1_rtc_set_offset(struct device *dev, long offset)
+{
+       struct rzn1_rtc *rtc = dev_get_drvdata(dev);
+       int stepsh, stepsl, steps;
+       u32 subu = 0, ctl2;
+       int ret;
+
+       /*
+        * Check which resolution mode (every 20 or 60s) can be used.
+        * Between 2 and 124 clock pulses can be added or substracted.
+        *
+        * In 20s mode, the minimum resolution is 2 / (32768 * 20) which is
+        * close to 3051 ppb. In 60s mode, the resolution is closer to 1017.
+        */
+       stepsh = DIV_ROUND_CLOSEST(offset, 1017);
+       stepsl = DIV_ROUND_CLOSEST(offset, 3051);
+
+       if (stepsh >= -0x3E && stepsh <= 0x3E) {
+               /* 1017 ppb per step */
+               steps = stepsh;
+               subu |= RZN1_RTC_SUBU_DEV;
+       } else if (stepsl >= -0x3E && stepsl <= 0x3E) {
+               /* 3051 ppb per step */
+               steps = stepsl;
+       } else {
+               return -ERANGE;
+       }
+
+       if (!steps)
+               return 0;
+
+       if (steps > 0) {
+               subu |= steps + 1;
+       } else {
+               subu |= RZN1_RTC_SUBU_DECR;
+               subu |= (~(-steps - 1)) & 0x3F;
+       }
+
+       ret = readl_poll_timeout(rtc->base + RZN1_RTC_CTL2, ctl2,
+                                !(ctl2 & RZN1_RTC_CTL2_WUST), 100, 2000000);
+       if (ret)
+               return ret;
+
+       writel(subu, rtc->base + RZN1_RTC_SUBU);
+
+       return 0;
+}
+
+static const struct rtc_class_ops rzn1_rtc_ops = {
+       .read_time = rzn1_rtc_read_time,
+       .set_time = rzn1_rtc_set_time,
+       .read_alarm = rzn1_rtc_read_alarm,
+       .set_alarm = rzn1_rtc_set_alarm,
+       .alarm_irq_enable = rzn1_rtc_alarm_irq_enable,
+       .read_offset = rzn1_rtc_read_offset,
+       .set_offset = rzn1_rtc_set_offset,
+};
+
+static int rzn1_rtc_probe(struct platform_device *pdev)
+{
+       struct rzn1_rtc *rtc;
+       int alarm_irq;
+       int ret;
+
+       rtc = devm_kzalloc(&pdev->dev, sizeof(*rtc), GFP_KERNEL);
+       if (!rtc)
+               return -ENOMEM;
+
+       platform_set_drvdata(pdev, rtc);
+
+       rtc->base = devm_platform_ioremap_resource(pdev, 0);
+       if (IS_ERR(rtc->base))
+               return dev_err_probe(&pdev->dev, PTR_ERR(rtc->base), "Missing reg\n");
+
+       alarm_irq = platform_get_irq(pdev, 0);
+       if (alarm_irq < 0)
+               return alarm_irq;
+
+       rtc->rtcdev = devm_rtc_allocate_device(&pdev->dev);
+       if (IS_ERR(rtc->rtcdev))
+               return PTR_ERR(rtc->rtcdev);
+
+       rtc->rtcdev->range_min = RTC_TIMESTAMP_BEGIN_2000;
+       rtc->rtcdev->range_max = RTC_TIMESTAMP_END_2099;
+       rtc->rtcdev->ops = &rzn1_rtc_ops;
+       set_bit(RTC_FEATURE_ALARM_RES_MINUTE, rtc->rtcdev->features);
+       clear_bit(RTC_FEATURE_UPDATE_INTERRUPT, rtc->rtcdev->features);
+
+       devm_pm_runtime_enable(&pdev->dev);
+       ret = pm_runtime_resume_and_get(&pdev->dev);
+       if (ret < 0)
+               return ret;
+
+       /*
+        * Ensure the clock counter is enabled.
+        * Set 24-hour mode and possible oscillator offset compensation in SUBU mode.
+        */
+       writel(RZN1_RTC_CTL0_CE | RZN1_RTC_CTL0_AMPM | RZN1_RTC_CTL0_SLSB_SUBU,
+              rtc->base + RZN1_RTC_CTL0);
+
+       /* Disable all interrupts */
+       writel(0, rtc->base + RZN1_RTC_CTL1);
+
+       ret = devm_request_irq(&pdev->dev, alarm_irq, rzn1_rtc_alarm_irq, 0,
+                              dev_name(&pdev->dev), rtc);
+       if (ret) {
+               dev_err(&pdev->dev, "RTC timer interrupt not available\n");
+               goto dis_runtime_pm;
+       }
+
+       ret = devm_rtc_register_device(rtc->rtcdev);
+       if (ret)
+               goto dis_runtime_pm;
+
+       return 0;
+
+dis_runtime_pm:
+       pm_runtime_put(&pdev->dev);
+
+       return ret;
+}
+
+static int rzn1_rtc_remove(struct platform_device *pdev)
+{
+       pm_runtime_put(&pdev->dev);
+
+       return 0;
+}
+
+static const struct of_device_id rzn1_rtc_of_match[] = {
+       { .compatible   = "renesas,rzn1-rtc" },
+       {},
+};
+MODULE_DEVICE_TABLE(of, rzn1_rtc_of_match);
+
+static struct platform_driver rzn1_rtc_driver = {
+       .probe = rzn1_rtc_probe,
+       .remove = rzn1_rtc_remove,
+       .driver = {
+               .name   = "rzn1-rtc",
+               .of_match_table = rzn1_rtc_of_match,
+       },
+};
+module_platform_driver(rzn1_rtc_driver);
+
+MODULE_AUTHOR("Michel Pollet <Michel.Pollet@bp.renesas.com");
+MODULE_AUTHOR("Miquel Raynal <miquel.raynal@bootlin.com");
+MODULE_DESCRIPTION("RZ/N1 RTC driver");
+MODULE_LICENSE("GPL");
index 5252ce4cbda4ecd71a6134f63c4cc672c190ed4f..57540727ce1c1469b5fdd99da0ab99a712e6134d 100644 (file)
 #define SUN6I_LOSC_OUT_GATING                  0x0060
 #define SUN6I_LOSC_OUT_GATING_EN_OFFSET                0
 
+/* General-purpose data */
+#define SUN6I_GP_DATA                          0x0100
+#define SUN6I_GP_DATA_SIZE                     0x20
+
 /*
  * Get date values
  */
@@ -679,6 +683,39 @@ static const struct rtc_class_ops sun6i_rtc_ops = {
        .alarm_irq_enable       = sun6i_rtc_alarm_irq_enable
 };
 
+static int sun6i_rtc_nvmem_read(void *priv, unsigned int offset, void *_val, size_t bytes)
+{
+       struct sun6i_rtc_dev *chip = priv;
+       u32 *val = _val;
+       int i;
+
+       for (i = 0; i < bytes / 4; ++i)
+               val[i] = readl(chip->base + SUN6I_GP_DATA + offset + 4 * i);
+
+       return 0;
+}
+
+static int sun6i_rtc_nvmem_write(void *priv, unsigned int offset, void *_val, size_t bytes)
+{
+       struct sun6i_rtc_dev *chip = priv;
+       u32 *val = _val;
+       int i;
+
+       for (i = 0; i < bytes / 4; ++i)
+               writel(val[i], chip->base + SUN6I_GP_DATA + offset + 4 * i);
+
+       return 0;
+}
+
+static struct nvmem_config sun6i_rtc_nvmem_cfg = {
+       .type           = NVMEM_TYPE_BATTERY_BACKED,
+       .reg_read       = sun6i_rtc_nvmem_read,
+       .reg_write      = sun6i_rtc_nvmem_write,
+       .size           = SUN6I_GP_DATA_SIZE,
+       .word_size      = 4,
+       .stride         = 4,
+};
+
 #ifdef CONFIG_PM_SLEEP
 /* Enable IRQ wake on suspend, to wake up from RTC. */
 static int sun6i_rtc_suspend(struct device *dev)
@@ -812,6 +849,11 @@ static int sun6i_rtc_probe(struct platform_device *pdev)
        if (ret)
                return ret;
 
+       sun6i_rtc_nvmem_cfg.priv = chip;
+       ret = devm_rtc_nvmem_register(chip->rtc, &sun6i_rtc_nvmem_cfg);
+       if (ret)
+               return ret;
+
        dev_info(&pdev->dev, "RTC enabled\n");
 
        return 0;