rtc: add armada38x driver
authorMarek Behún <marek.behun@nic.cz>
Fri, 26 Feb 2021 09:30:19 +0000 (10:30 +0100)
committerStefan Roese <sr@denx.de>
Thu, 8 Apr 2021 06:44:51 +0000 (08:44 +0200)
Add RTC driver for Armada 38x, based on Linux' driver.
For now implement only `marvell,armada-380-rtc` compatible.

Signed-off-by: Marek Behún <marek.behun@nic.cz>
Reviewed-by: Stefan Roese <sr@denx.de>
Cc: Pali Rohár <pali@kernel.org>
Cc: Baruch Siach <baruch@tkos.co.il>
Cc: Chris Packham <judge.packham@gmail.com>
Cc: Simon Glass <sjg@chromium.org>
Acked-by: Pali Rohár <pali@kernel.org>
drivers/rtc/Kconfig
drivers/rtc/Makefile
drivers/rtc/armada38x.c [new file with mode: 0644]

index aa6d901..dafba35 100644 (file)
@@ -38,6 +38,13 @@ config RTC_ENABLE_32KHZ_OUTPUT
           Some real-time clocks support the output of 32kHz square waves (such as ds3231),
           the config symbol choose Real Time Clock device 32Khz output feature.
 
+config RTC_ARMADA38X
+       bool "Enable Armada 38x Marvell SoC RTC"
+       depends on DM_RTC && ARCH_MVEBU
+       help
+         This adds support for the in-chip RTC that can be found in the
+         Armada 38x Marvell's SoC devices.
+
 config RTC_PCF2127
        bool "Enable PCF2127 driver"
        depends on DM_RTC
index 6a45a9c..15609e7 100644 (file)
@@ -8,6 +8,7 @@ obj-$(CONFIG_$(SPL_TPL_)DM_RTC) += rtc-uclass.o
 
 obj-$(CONFIG_RTC_AT91SAM9_RTT) += at91sam9_rtt.o
 obj-y += rtc-lib.o
+obj-$(CONFIG_RTC_ARMADA38X) += armada38x.o
 obj-$(CONFIG_RTC_DAVINCI) += davinci.o
 obj-$(CONFIG_RTC_DS1302) += ds1302.o
 obj-$(CONFIG_RTC_DS1306) += ds1306.o
diff --git a/drivers/rtc/armada38x.c b/drivers/rtc/armada38x.c
new file mode 100644 (file)
index 0000000..2d264ac
--- /dev/null
@@ -0,0 +1,184 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * RTC driver for the Armada 38x Marvell SoCs
+ *
+ * Copyright (C) 2021 Marek Behun <marek.behun@nic.cz>
+ *
+ * Based on Linux' driver by Gregory Clement and Marvell
+ */
+
+#include <asm/io.h>
+#include <dm.h>
+#include <linux/delay.h>
+#include <rtc.h>
+
+#define RTC_STATUS                     0x0
+#define RTC_TIME                       0xC
+#define RTC_CONF_TEST                  0x1C
+
+/* Armada38x SoC registers  */
+#define RTC_38X_BRIDGE_TIMING_CTL      0x0
+#define RTC_38X_PERIOD_OFFS            0
+#define RTC_38X_PERIOD_MASK            (0x3FF << RTC_38X_PERIOD_OFFS)
+#define RTC_38X_READ_DELAY_OFFS                26
+#define RTC_38X_READ_DELAY_MASK                (0x1F << RTC_38X_READ_DELAY_OFFS)
+
+#define SAMPLE_NR                      100
+
+struct armada38x_rtc {
+       void __iomem *regs;
+       void __iomem *regs_soc;
+};
+
+/*
+ * According to Erratum RES-3124064 we have to do some configuration in MBUS.
+ * To read an RTC register we need to read it 100 times and return the most
+ * frequent value.
+ * To write an RTC register we need to write 2x zero into STATUS register,
+ * followed by the proper write. Linux adds an 5 us delay after this, so we do
+ * it here as well.
+ */
+static void update_38x_mbus_timing_params(struct armada38x_rtc *rtc)
+{
+       u32 reg;
+
+       reg = readl(rtc->regs_soc + RTC_38X_BRIDGE_TIMING_CTL);
+       reg &= ~RTC_38X_PERIOD_MASK;
+       reg |= 0x3FF << RTC_38X_PERIOD_OFFS; /* Maximum value */
+       reg &= ~RTC_38X_READ_DELAY_MASK;
+       reg |= 0x1F << RTC_38X_READ_DELAY_OFFS; /* Maximum value */
+       writel(reg, rtc->regs_soc + RTC_38X_BRIDGE_TIMING_CTL);
+}
+
+static void armada38x_rtc_write(u32 val, struct armada38x_rtc *rtc, u8 reg)
+{
+       writel(0, rtc->regs + RTC_STATUS);
+       writel(0, rtc->regs + RTC_STATUS);
+       writel(val, rtc->regs + reg);
+       udelay(5);
+}
+
+static u32 armada38x_rtc_read(struct armada38x_rtc *rtc, u8 reg)
+{
+       u8 counts[SAMPLE_NR], max_idx;
+       u32 samples[SAMPLE_NR], max;
+       int i, j, last;
+
+       for (i = 0, last = 0; i < SAMPLE_NR; ++i) {
+               u32 sample = readl(rtc->regs + reg);
+
+               /* find if this value was already read */
+               for (j = 0; j < last; ++j) {
+                       if (samples[j] == sample)
+                               break;
+               }
+
+               if (j < last) {
+                       /* if yes, increment count */
+                       ++counts[j];
+               } else {
+                       /* if not, add */
+                       samples[last] = sample;
+                       counts[last] = 1;
+                       ++last;
+               }
+       }
+
+       /* finally find the sample that was read the most */
+       max = 0;
+       max_idx = 0;
+
+       for (i = 0; i < last; ++i) {
+               if (counts[i] > max) {
+                       max = counts[i];
+                       max_idx = i;
+               }
+       }
+
+       return samples[max_idx];
+}
+
+static int armada38x_rtc_get(struct udevice *dev, struct rtc_time *tm)
+{
+       struct armada38x_rtc *rtc = dev_get_priv(dev);
+       u32 time;
+
+       time = armada38x_rtc_read(rtc, RTC_TIME);
+
+       rtc_to_tm(time, tm);
+
+       return 0;
+}
+
+static int armada38x_rtc_reset(struct udevice *dev)
+{
+       struct armada38x_rtc *rtc = dev_get_priv(dev);
+       u32 reg;
+
+       reg = armada38x_rtc_read(rtc, RTC_CONF_TEST);
+
+       if (reg & 0xff) {
+               armada38x_rtc_write(0, rtc, RTC_CONF_TEST);
+               mdelay(500);
+               armada38x_rtc_write(0, rtc, RTC_TIME);
+               armada38x_rtc_write(BIT(0) | BIT(1), 0, RTC_STATUS);
+       }
+
+       return 0;
+}
+
+static int armada38x_rtc_set(struct udevice *dev, const struct rtc_time *tm)
+{
+       struct armada38x_rtc *rtc = dev_get_priv(dev);
+       unsigned long time;
+
+       time = rtc_mktime(tm);
+
+       if (time > U32_MAX)
+               printf("%s: requested time to set will overflow\n", dev->name);
+
+       armada38x_rtc_reset(dev);
+       armada38x_rtc_write(time, rtc, RTC_TIME);
+
+       return 0;
+}
+
+static int armada38x_probe(struct udevice *dev)
+{
+       struct armada38x_rtc *rtc = dev_get_priv(dev);
+
+       rtc->regs = dev_remap_addr_name(dev, "rtc");
+       if (!rtc->regs)
+               goto err;
+
+       rtc->regs_soc = dev_remap_addr_name(dev, "rtc-soc");
+       if (!rtc->regs_soc)
+               goto err;
+
+       update_38x_mbus_timing_params(rtc);
+
+       return 0;
+err:
+       printf("%s: io address missing\n", dev->name);
+       return -ENODEV;
+}
+
+static const struct rtc_ops armada38x_rtc_ops = {
+       .get = armada38x_rtc_get,
+       .set = armada38x_rtc_set,
+       .reset = armada38x_rtc_reset,
+};
+
+static const struct udevice_id armada38x_rtc_ids[] = {
+       { .compatible = "marvell,armada-380-rtc", .data = 0 },
+       { }
+};
+
+U_BOOT_DRIVER(rtc_armada38x) = {
+       .name           = "rtc-armada38x",
+       .id             = UCLASS_RTC,
+       .of_match       = armada38x_rtc_ids,
+       .probe          = armada38x_probe,
+       .priv_auto      = sizeof(struct armada38x_rtc),
+       .ops            = &armada38x_rtc_ops,
+};