Add a driver for the CV1800 RTC seconds counter described by the
upstream devicetree. Map the node's register range, enable the RTC
clock, and implement reading and setting the 32-bit counter. Bind
the existing sysreset driver as a child when enabled.

Adapt the register definitions and seconds-counter sequence from
Linux drivers/rtc/rtc-cv1800.c by Jingbao Qiu.

Link: https://git.kernel.org/linus/d9f82683b123ce315cc36c4b20a5bc3d8644f947

Signed-off-by: Zhuxu Ran <[email protected]>
---
 drivers/rtc/Kconfig      |   9 ++++
 drivers/rtc/Makefile     |   1 +
 drivers/rtc/rtc-cv1800.c | 123 +++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 133 insertions(+)

diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index 5b44cc99c97..547ac1643c9 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -54,6 +54,15 @@ config RTC_ARMADA38X
          This adds support for the in-chip RTC that can be found in the
          Armada 38x Marvell's SoC devices.
 
+config RTC_CV1800B
+       bool "Enable Sophgo CV1800B RTC driver"
+       depends on DM_RTC
+       select REGMAP
+       help
+         Enable access to the RTC seconds counter found on Sophgo CV1800
+         series SoCs. When system reset support is enabled, the driver also
+         binds the reset function provided by the RTC subsystem.
+
 config RTC_PCF2127
        bool "Enable PCF2127 driver"
        depends on DM_RTC
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index 742481ce185..961fe5409f0 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -7,6 +7,7 @@
 obj-$(CONFIG_$(PHASE_)DM_RTC) += rtc-uclass.o
 
 obj-$(CONFIG_RTC_ARMADA38X) += armada38x.o
+obj-$(CONFIG_RTC_CV1800B) += rtc-cv1800.o
 obj-$(CONFIG_RTC_DAVINCI) += davinci.o
 obj-$(CONFIG_RTC_DS1307) += ds1307.o
 obj-$(CONFIG_RTC_DS1337) += ds1337.o
diff --git a/drivers/rtc/rtc-cv1800.c b/drivers/rtc/rtc-cv1800.c
new file mode 100644
index 00000000000..3a2b7b25748
--- /dev/null
+++ b/drivers/rtc/rtc-cv1800.c
@@ -0,0 +1,123 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2026 Zhuxu Ran <[email protected]>
+ *
+ * Based on the Linux driver by Jingbao Qiu <[email protected]>.
+ */
+
+#include <clk.h>
+#include <dm.h>
+#include <dm/lists.h>
+#include <regmap.h>
+#include <rtc.h>
+#include <linux/bitops.h>
+#include <linux/errno.h>
+#include <linux/err.h>
+
+#define SEC_PULSE_GEN          0x1004
+#define SET_SEC_CNTR_VAL       0x1010
+#define SET_SEC_CNTR_TRIG      0x1014
+#define SEC_CNTR_VAL           0x1018
+#define MACRO_RG_SET_T         0x1498
+
+#define SEL_SEC_PULSE          BIT(31)
+
+struct cv1800_rtc_priv {
+       struct regmap *regmap;
+       struct clk clk;
+};
+
+static int cv1800_rtc_get(struct udevice *dev, struct rtc_time *time)
+{
+       struct cv1800_rtc_priv *priv = dev_get_priv(dev);
+       u32 value;
+       int ret;
+
+       ret = regmap_read(priv->regmap, SEC_PULSE_GEN, &value);
+       if (ret)
+               return ret;
+       if (value & SEL_SEC_PULSE)
+               return -EINVAL;
+
+       ret = regmap_read(priv->regmap, SEC_CNTR_VAL, &value);
+       if (ret)
+               return ret;
+
+       rtc_to_tm(value, time);
+
+       return 0;
+}
+
+static int cv1800_rtc_set(struct udevice *dev, const struct rtc_time *time)
+{
+       struct cv1800_rtc_priv *priv = dev_get_priv(dev);
+       time64_t seconds;
+       int ret;
+
+       if (time->tm_year < 1970)
+               return -EINVAL;
+
+       seconds = rtc_mktime(time);
+       if (seconds < 0 || seconds > U32_MAX)
+               return -ERANGE;
+
+       ret = regmap_write(priv->regmap, SET_SEC_CNTR_VAL, seconds);
+       if (ret)
+               return ret;
+
+       ret = regmap_write(priv->regmap, SET_SEC_CNTR_TRIG, 1);
+       if (ret)
+               return ret;
+
+       ret = regmap_write(priv->regmap, MACRO_RG_SET_T, seconds);
+       if (ret)
+               return ret;
+
+       return regmap_update_bits(priv->regmap, SEC_PULSE_GEN,
+                                 SEL_SEC_PULSE, 0);
+}
+
+static int cv1800_rtc_bind(struct udevice *dev)
+{
+       if (!CONFIG_IS_ENABLED(SYSRESET_CV1800B))
+               return 0;
+
+       return device_bind_driver_to_node(dev, "cv1800b_sysreset",
+                                         "sysreset", dev_ofnode(dev), NULL);
+}
+
+static int cv1800_rtc_probe(struct udevice *dev)
+{
+       struct cv1800_rtc_priv *priv = dev_get_priv(dev);
+       int ret;
+
+       priv->regmap = devm_regmap_init(dev, NULL, NULL, NULL);
+       if (IS_ERR(priv->regmap))
+               return PTR_ERR(priv->regmap);
+
+       ret = clk_get_by_name(dev, "rtc", &priv->clk);
+       if (ret)
+               return ret;
+
+       return clk_enable(&priv->clk);
+}
+
+static const struct rtc_ops cv1800_rtc_ops = {
+       .get = cv1800_rtc_get,
+       .set = cv1800_rtc_set,
+};
+
+static const struct udevice_id cv1800_rtc_ids[] = {
+       { .compatible = "sophgo,cv1800b-rtc" },
+       { }
+};
+
+U_BOOT_DRIVER(cv1800b_rtc) = {
+       .name = "cv1800b_rtc",
+       .id = UCLASS_RTC,
+       .of_match = cv1800_rtc_ids,
+       .bind = cv1800_rtc_bind,
+       .probe = cv1800_rtc_probe,
+       .ops = &cv1800_rtc_ops,
+       .priv_auto = sizeof(struct cv1800_rtc_priv),
+};

-- 
2.55.0

Reply via email to