diff mbox series

[v15] rtc: sophgo: add rtc support for Sophgo CV1800 SoC

Message ID 20250507195626.502240-1-alexander.sverdlin@gmail.com
State Accepted
Headers show
Series [v15] rtc: sophgo: add rtc support for Sophgo CV1800 SoC | expand

Commit Message

Alexander Sverdlin May 7, 2025, 7:56 p.m. UTC
From: Jingbao Qiu <qiujingbao.dlmu@gmail.com>

Implement the RTC driver for CV1800, which able to provide time alarm.

Signed-off-by: Jingbao Qiu <qiujingbao.dlmu@gmail.com>
Signed-off-by: Alexander Sverdlin <alexander.sverdlin@gmail.com>
---
Changelog:
v15:
- the only patch in the series left
- dropped changes to MAINTAINERS file
v14:
- https://lore.kernel.org/lkml/gztsdu5p4tzt7emlwiuc3z74f4tfgkclcyrl324prqzp6dqhhf@ezrdmmhvf2nm/T/
- platform device name "cv1800-rtc" -> "cv1800b-rtc"
v13:
- Change in the Kconfig dependency caused by the move of the previous
  patch from MFD into SOC
v12:
- added MAINTAINERS entry
- depends on cv1800-rtcsys MFD driver
- use syscon for regmap
- get named clock from parent MFD
- corresponding platform device is expected to be instantiated by MFD stub
Changes since v10:
- only start RTC on set_time;
Changes since v9:
- further simplified bitmask macros;
- unconditional RTC start (rtc_enable_sec_counter()), otherwise
didn't start on SG2000;
- dropped ANA_CALIB modification (has been forgotten in v8 with
the drop of SW calibration to switch to HW calibration);
- successfully tested on SG2000;


 drivers/rtc/Kconfig      |  12 +++
 drivers/rtc/Makefile     |   1 +
 drivers/rtc/rtc-cv1800.c | 218 +++++++++++++++++++++++++++++++++++++++
 4 files changed, 232 insertions(+)
 create mode 100644 drivers/rtc/rtc-cv1800.c

Comments

Inochi Amaoto May 8, 2025, 10:21 p.m. UTC | #1
On Wed, May 07, 2025 at 09:56:20PM +0200, Alexander Sverdlin wrote:
> From: Jingbao Qiu <qiujingbao.dlmu@gmail.com>
> 
> Implement the RTC driver for CV1800, which able to provide time alarm.
> 
> Signed-off-by: Jingbao Qiu <qiujingbao.dlmu@gmail.com>
> Signed-off-by: Alexander Sverdlin <alexander.sverdlin@gmail.com>
> ---
> Changelog:
> v15:
> - the only patch in the series left
> - dropped changes to MAINTAINERS file
> v14:
> - https://lore.kernel.org/lkml/gztsdu5p4tzt7emlwiuc3z74f4tfgkclcyrl324prqzp6dqhhf@ezrdmmhvf2nm/T/
> - platform device name "cv1800-rtc" -> "cv1800b-rtc"
> v13:
> - Change in the Kconfig dependency caused by the move of the previous
>   patch from MFD into SOC
> v12:
> - added MAINTAINERS entry
> - depends on cv1800-rtcsys MFD driver
> - use syscon for regmap
> - get named clock from parent MFD
> - corresponding platform device is expected to be instantiated by MFD stub
> Changes since v10:
> - only start RTC on set_time;
> Changes since v9:
> - further simplified bitmask macros;
> - unconditional RTC start (rtc_enable_sec_counter()), otherwise
> didn't start on SG2000;
> - dropped ANA_CALIB modification (has been forgotten in v8 with
> the drop of SW calibration to switch to HW calibration);
> - successfully tested on SG2000;
> 
> 
>  drivers/rtc/Kconfig      |  12 +++
>  drivers/rtc/Makefile     |   1 +
>  drivers/rtc/rtc-cv1800.c | 218 +++++++++++++++++++++++++++++++++++++++
>  4 files changed, 232 insertions(+)
>  create mode 100644 drivers/rtc/rtc-cv1800.c
> 
> diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
> index 0bbbf778ecfa..46593103db11 100644
> --- a/drivers/rtc/Kconfig
> +++ b/drivers/rtc/Kconfig
> @@ -1395,6 +1395,18 @@ config RTC_DRV_ASM9260
>  	  This driver can also be built as a module. If so, the module
>  	  will be called rtc-asm9260.
>  
> +config RTC_DRV_CV1800
> +	tristate "Sophgo CV1800 RTC"
> +	depends on SOPHGO_CV1800_RTCSYS || COMPILE_TEST
> +	select MFD_SYSCON
> +	select REGMAP
> +	help
> +	  If you say yes here you get support the RTC driver for Sophgo CV1800
> +	  series SoC.
> +
> +	  This driver can also be built as a module. If so, the module will be
> +	  called rtc-cv1800.
> +
>  config RTC_DRV_DIGICOLOR
>  	tristate "Conexant Digicolor RTC"
>  	depends on ARCH_DIGICOLOR || COMPILE_TEST
> diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
> index 489b4ab07068..621b30a33dda 100644
> --- a/drivers/rtc/Makefile
> +++ b/drivers/rtc/Makefile
> @@ -44,6 +44,7 @@ obj-$(CONFIG_RTC_DRV_CADENCE)	+= rtc-cadence.o
>  obj-$(CONFIG_RTC_DRV_CMOS)	+= rtc-cmos.o
>  obj-$(CONFIG_RTC_DRV_CPCAP)	+= rtc-cpcap.o
>  obj-$(CONFIG_RTC_DRV_CROS_EC)	+= rtc-cros-ec.o
> +obj-$(CONFIG_RTC_DRV_CV1800)	+= rtc-cv1800.o
>  obj-$(CONFIG_RTC_DRV_DA9052)	+= rtc-da9052.o
>  obj-$(CONFIG_RTC_DRV_DA9055)	+= rtc-da9055.o
>  obj-$(CONFIG_RTC_DRV_DA9063)	+= rtc-da9063.o
> diff --git a/drivers/rtc/rtc-cv1800.c b/drivers/rtc/rtc-cv1800.c
> new file mode 100644
> index 000000000000..18bc542bbdb8
> --- /dev/null
> +++ b/drivers/rtc/rtc-cv1800.c
> @@ -0,0 +1,218 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * rtc-cv1800.c: RTC driver for Sophgo cv1800 RTC
> + *
> + * Author: Jingbao Qiu <qiujingbao.dlmu@gmail.com>
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/irq.h>
> +#include <linux/kernel.h>
> +#include <linux/mfd/syscon.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/regmap.h>
> +#include <linux/rtc.h>
> +
> +#define SEC_PULSE_GEN          0x1004
> +#define ALARM_TIME             0x1008
> +#define ALARM_ENABLE           0x100C
> +#define SET_SEC_CNTR_VAL       0x1010
> +#define SET_SEC_CNTR_TRIG      0x1014
> +#define SEC_CNTR_VAL           0x1018
> +
> +/*
> + * When in VDDBKUP domain, this MACRO register
> + * does not power down
> + */
> +#define MACRO_RO_T             0x14A8
> +#define MACRO_RG_SET_T         0x1498
> +
> +#define ALARM_ENABLE_MASK      BIT(0)
> +#define SEL_SEC_PULSE          BIT(31)
> +
> +struct cv1800_rtc_priv {
> +	struct rtc_device *rtc_dev;
> +	struct regmap *rtc_map;
> +	struct clk *clk;
> +	int irq;
> +};
> +
> +static bool cv1800_rtc_enabled(struct device *dev)
> +{
> +	struct cv1800_rtc_priv *info = dev_get_drvdata(dev);
> +	u32 reg;
> +
> +	regmap_read(info->rtc_map, SEC_PULSE_GEN, &reg);
> +
> +	return (reg & SEL_SEC_PULSE) == 0;
> +}
> +
> +static void cv1800_rtc_enable(struct device *dev)
> +{
> +	struct cv1800_rtc_priv *info = dev_get_drvdata(dev);
> +
> +	/* Sec pulse generated internally */
> +	regmap_update_bits(info->rtc_map, SEC_PULSE_GEN, SEL_SEC_PULSE, 0);
> +}
> +
> +static int cv1800_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled)
> +{
> +	struct cv1800_rtc_priv *info = dev_get_drvdata(dev);
> +
> +	regmap_write(info->rtc_map, ALARM_ENABLE, enabled);
> +
> +	return 0;
> +}
> +
> +static int cv1800_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
> +{
> +	struct cv1800_rtc_priv *info = dev_get_drvdata(dev);
> +	unsigned long alarm_time;
> +
> +	alarm_time = rtc_tm_to_time64(&alrm->time);
> +
> +	cv1800_rtc_alarm_irq_enable(dev, 0);
> +
> +	regmap_write(info->rtc_map, ALARM_TIME, alarm_time);
> +
> +	cv1800_rtc_alarm_irq_enable(dev, alrm->enabled);
> +
> +	return 0;
> +}
> +
> +static int cv1800_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm)
> +{
> +	struct cv1800_rtc_priv *info = dev_get_drvdata(dev);
> +	u32 enabled;
> +	u32 time;
> +
> +	if (!cv1800_rtc_enabled(dev)) {
> +		alarm->enabled = 0;
> +		return 0;
> +	}
> +
> +	regmap_read(info->rtc_map, ALARM_ENABLE, &enabled);
> +
> +	alarm->enabled = enabled & ALARM_ENABLE_MASK;
> +
> +	regmap_read(info->rtc_map, ALARM_TIME, &time);
> +
> +	rtc_time64_to_tm(time, &alarm->time);
> +
> +	return 0;
> +}
> +
> +static int cv1800_rtc_read_time(struct device *dev, struct rtc_time *tm)
> +{
> +	struct cv1800_rtc_priv *info = dev_get_drvdata(dev);
> +	u32 sec;
> +
> +	if (!cv1800_rtc_enabled(dev))
> +		return -EINVAL;
> +
> +	regmap_read(info->rtc_map, SEC_CNTR_VAL, &sec);
> +
> +	rtc_time64_to_tm(sec, tm);
> +
> +	return 0;
> +}
> +
> +static int cv1800_rtc_set_time(struct device *dev, struct rtc_time *tm)
> +{
> +	struct cv1800_rtc_priv *info = dev_get_drvdata(dev);
> +	unsigned long sec;
> +
> +	sec = rtc_tm_to_time64(tm);
> +
> +	regmap_write(info->rtc_map, SET_SEC_CNTR_VAL, sec);
> +	regmap_write(info->rtc_map, SET_SEC_CNTR_TRIG, 1);
> +
> +	regmap_write(info->rtc_map, MACRO_RG_SET_T, sec);
> +
> +	cv1800_rtc_enable(dev);
> +
> +	return 0;
> +}
> +
> +static irqreturn_t cv1800_rtc_irq_handler(int irq, void *dev_id)
> +{
> +	struct cv1800_rtc_priv *info = dev_id;
> +
> +	rtc_update_irq(info->rtc_dev, 1, RTC_IRQF | RTC_AF);
> +
> +	regmap_write(info->rtc_map, ALARM_ENABLE, 0);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static const struct rtc_class_ops cv1800_rtc_ops = {
> +	.read_time = cv1800_rtc_read_time,
> +	.set_time = cv1800_rtc_set_time,
> +	.read_alarm = cv1800_rtc_read_alarm,
> +	.set_alarm = cv1800_rtc_set_alarm,
> +	.alarm_irq_enable = cv1800_rtc_alarm_irq_enable,
> +};
> +

> +static int cv1800_rtc_probe(struct platform_device *pdev)
> +{
> +	struct cv1800_rtc_priv *rtc;
> +	int ret;
> +
> +	rtc = devm_kzalloc(&pdev->dev, sizeof(*rtc), GFP_KERNEL);
> +	if (!rtc)
> +		return -ENOMEM;
> +
> +	rtc->rtc_map = device_node_to_regmap(pdev->dev.parent->of_node);
> +	if (IS_ERR(rtc->rtc_map))
> +		return dev_err_probe(&pdev->dev, PTR_ERR(rtc->rtc_map),
> +				     "cannot get parent regmap\n");
> +
> +	rtc->irq = platform_get_irq(pdev, 0);
> +	if (rtc->irq < 0)
> +		return rtc->irq;
> +
> +	rtc->clk = devm_clk_get_enabled(pdev->dev.parent, "rtc");
> +	if (IS_ERR(rtc->clk))
> +		return dev_err_probe(&pdev->dev, PTR_ERR(rtc->clk),
> +				     "rtc clk not found\n");
> +
> +	platform_set_drvdata(pdev, rtc);
> +
> +	device_init_wakeup(&pdev->dev, 1);
> +
> +	rtc->rtc_dev = devm_rtc_allocate_device(&pdev->dev);
> +	if (IS_ERR(rtc->rtc_dev))
> +		return PTR_ERR(rtc->rtc_dev);
> +
> +	rtc->rtc_dev->ops = &cv1800_rtc_ops;
> +	rtc->rtc_dev->range_max = U32_MAX;
> +
> +	ret = devm_request_irq(&pdev->dev, rtc->irq, cv1800_rtc_irq_handler,
> +			       IRQF_TRIGGER_HIGH, "rtc alarm", rtc);
> +	if (ret)
> +		return dev_err_probe(&pdev->dev, ret,
> +				     "cannot register interrupt handler\n");
> +
> +	return devm_rtc_register_device(rtc->rtc_dev);
> +}
> +

I wonder whether the rtc driver may need reset (maybe optional) for this?
If so, please add it.

Regards,
Inochi
Alexander Sverdlin May 10, 2025, 2:30 p.m. UTC | #2
Hi Inochi!

On Fri, 2025-05-09 at 06:21 +0800, Inochi Amaoto wrote:
> On Wed, May 07, 2025 at 09:56:20PM +0200, Alexander Sverdlin wrote:
> > From: Jingbao Qiu <qiujingbao.dlmu@gmail.com>
> > 
> > Implement the RTC driver for CV1800, which able to provide time alarm.
> > 
> > Signed-off-by: Jingbao Qiu <qiujingbao.dlmu@gmail.com>
> > Signed-off-by: Alexander Sverdlin <alexander.sverdlin@gmail.com>

...

> > +static int cv1800_rtc_probe(struct platform_device *pdev)
> > +{
> > +	struct cv1800_rtc_priv *rtc;
> > +	int ret;
> > +
> > +	rtc = devm_kzalloc(&pdev->dev, sizeof(*rtc), GFP_KERNEL);
> > +	if (!rtc)
> > +		return -ENOMEM;
> > +
> > +	rtc->rtc_map = device_node_to_regmap(pdev->dev.parent->of_node);
> > +	if (IS_ERR(rtc->rtc_map))
> > +		return dev_err_probe(&pdev->dev, PTR_ERR(rtc->rtc_map),
> > +				     "cannot get parent regmap\n");
> > +
> > +	rtc->irq = platform_get_irq(pdev, 0);
> > +	if (rtc->irq < 0)
> > +		return rtc->irq;
> > +
> > +	rtc->clk = devm_clk_get_enabled(pdev->dev.parent, "rtc");
> > +	if (IS_ERR(rtc->clk))
> > +		return dev_err_probe(&pdev->dev, PTR_ERR(rtc->clk),
> > +				     "rtc clk not found\n");
> > +
> > +	platform_set_drvdata(pdev, rtc);
> > +
> > +	device_init_wakeup(&pdev->dev, 1);
> > +
> > +	rtc->rtc_dev = devm_rtc_allocate_device(&pdev->dev);
> > +	if (IS_ERR(rtc->rtc_dev))
> > +		return PTR_ERR(rtc->rtc_dev);
> > +
> > +	rtc->rtc_dev->ops = &cv1800_rtc_ops;
> > +	rtc->rtc_dev->range_max = U32_MAX;
> > +
> > +	ret = devm_request_irq(&pdev->dev, rtc->irq, cv1800_rtc_irq_handler,
> > +			       IRQF_TRIGGER_HIGH, "rtc alarm", rtc);
> > +	if (ret)
> > +		return dev_err_probe(&pdev->dev, ret,
> > +				     "cannot register interrupt handler\n");
> > +
> > +	return devm_rtc_register_device(rtc->rtc_dev);
> > +}
> > +
> 
> I wonder whether the rtc driver may need reset (maybe optional) for this?
> If so, please add it.

I'm not sure which reset you are referring to... RTC module can carry out
system-wide resets, but cannot be reset itself (as I understand).

Initially I was thinking about providing a reboot driver for Linux utilizing
the RTC module but it turns out PSCI interface is not optional on ARM64, which
means PSCI reset interface has to be provided by the firmware (I'm thinking
about U-Boot) and Linux will rely on PSCI reboot.
Inochi Amaoto May 10, 2025, 10:34 p.m. UTC | #3
On Sat, May 10, 2025 at 04:30:07PM +0200, Alexander Sverdlin wrote:
> Hi Inochi!
> 
> On Fri, 2025-05-09 at 06:21 +0800, Inochi Amaoto wrote:
> > On Wed, May 07, 2025 at 09:56:20PM +0200, Alexander Sverdlin wrote:
> > > From: Jingbao Qiu <qiujingbao.dlmu@gmail.com>
> > > 
> > > Implement the RTC driver for CV1800, which able to provide time alarm.
> > > 
> > > Signed-off-by: Jingbao Qiu <qiujingbao.dlmu@gmail.com>
> > > Signed-off-by: Alexander Sverdlin <alexander.sverdlin@gmail.com>
> 
> ...
> 
> > > +static int cv1800_rtc_probe(struct platform_device *pdev)
> > > +{
> > > +	struct cv1800_rtc_priv *rtc;
> > > +	int ret;
> > > +
> > > +	rtc = devm_kzalloc(&pdev->dev, sizeof(*rtc), GFP_KERNEL);
> > > +	if (!rtc)
> > > +		return -ENOMEM;
> > > +
> > > +	rtc->rtc_map = device_node_to_regmap(pdev->dev.parent->of_node);
> > > +	if (IS_ERR(rtc->rtc_map))
> > > +		return dev_err_probe(&pdev->dev, PTR_ERR(rtc->rtc_map),
> > > +				     "cannot get parent regmap\n");
> > > +
> > > +	rtc->irq = platform_get_irq(pdev, 0);
> > > +	if (rtc->irq < 0)
> > > +		return rtc->irq;
> > > +
> > > +	rtc->clk = devm_clk_get_enabled(pdev->dev.parent, "rtc");
> > > +	if (IS_ERR(rtc->clk))
> > > +		return dev_err_probe(&pdev->dev, PTR_ERR(rtc->clk),
> > > +				     "rtc clk not found\n");
> > > +
> > > +	platform_set_drvdata(pdev, rtc);
> > > +
> > > +	device_init_wakeup(&pdev->dev, 1);
> > > +
> > > +	rtc->rtc_dev = devm_rtc_allocate_device(&pdev->dev);
> > > +	if (IS_ERR(rtc->rtc_dev))
> > > +		return PTR_ERR(rtc->rtc_dev);
> > > +
> > > +	rtc->rtc_dev->ops = &cv1800_rtc_ops;
> > > +	rtc->rtc_dev->range_max = U32_MAX;
> > > +
> > > +	ret = devm_request_irq(&pdev->dev, rtc->irq, cv1800_rtc_irq_handler,
> > > +			       IRQF_TRIGGER_HIGH, "rtc alarm", rtc);
> > > +	if (ret)
> > > +		return dev_err_probe(&pdev->dev, ret,
> > > +				     "cannot register interrupt handler\n");
> > > +
> > > +	return devm_rtc_register_device(rtc->rtc_dev);
> > > +}
> > > +
> > 
> > I wonder whether the rtc driver may need reset (maybe optional) for this?
> > If so, please add it.
> 
> I'm not sure which reset you are referring to... RTC module can carry out
> system-wide resets, but cannot be reset itself (as I understand).
> 

This is fine for me.

> Initially I was thinking about providing a reboot driver for Linux utilizing
> the RTC module but it turns out PSCI interface is not optional on ARM64, which
> means PSCI reset interface has to be provided by the firmware (I'm thinking
> about U-Boot) and Linux will rely on PSCI reboot.
> 

I am not familiar with PSCI, but there is a fact that preserve uboot may
be costly as the ram is limited. I think it may be fine to have a reboot
driver to provide power function at the same time. But if you find there
is a way to implement PSCI reboot, just do the thing you prefer.

Regards,
Inochi
Alexandre Belloni May 24, 2025, 10:17 p.m. UTC | #4
On Wed, 07 May 2025 21:56:20 +0200, Alexander Sverdlin wrote:
> Implement the RTC driver for CV1800, which able to provide time alarm.
> 
> 

Applied, thanks!

[1/1] rtc: sophgo: add rtc support for Sophgo CV1800 SoC
      https://git.kernel.org/abelloni/c/d9f82683b123

Best regards,
diff mbox series

Patch

diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index 0bbbf778ecfa..46593103db11 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -1395,6 +1395,18 @@  config RTC_DRV_ASM9260
 	  This driver can also be built as a module. If so, the module
 	  will be called rtc-asm9260.
 
+config RTC_DRV_CV1800
+	tristate "Sophgo CV1800 RTC"
+	depends on SOPHGO_CV1800_RTCSYS || COMPILE_TEST
+	select MFD_SYSCON
+	select REGMAP
+	help
+	  If you say yes here you get support the RTC driver for Sophgo CV1800
+	  series SoC.
+
+	  This driver can also be built as a module. If so, the module will be
+	  called rtc-cv1800.
+
 config RTC_DRV_DIGICOLOR
 	tristate "Conexant Digicolor RTC"
 	depends on ARCH_DIGICOLOR || COMPILE_TEST
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index 489b4ab07068..621b30a33dda 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -44,6 +44,7 @@  obj-$(CONFIG_RTC_DRV_CADENCE)	+= rtc-cadence.o
 obj-$(CONFIG_RTC_DRV_CMOS)	+= rtc-cmos.o
 obj-$(CONFIG_RTC_DRV_CPCAP)	+= rtc-cpcap.o
 obj-$(CONFIG_RTC_DRV_CROS_EC)	+= rtc-cros-ec.o
+obj-$(CONFIG_RTC_DRV_CV1800)	+= rtc-cv1800.o
 obj-$(CONFIG_RTC_DRV_DA9052)	+= rtc-da9052.o
 obj-$(CONFIG_RTC_DRV_DA9055)	+= rtc-da9055.o
 obj-$(CONFIG_RTC_DRV_DA9063)	+= rtc-da9063.o
diff --git a/drivers/rtc/rtc-cv1800.c b/drivers/rtc/rtc-cv1800.c
new file mode 100644
index 000000000000..18bc542bbdb8
--- /dev/null
+++ b/drivers/rtc/rtc-cv1800.c
@@ -0,0 +1,218 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * rtc-cv1800.c: RTC driver for Sophgo cv1800 RTC
+ *
+ * Author: Jingbao Qiu <qiujingbao.dlmu@gmail.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/irq.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/rtc.h>
+
+#define SEC_PULSE_GEN          0x1004
+#define ALARM_TIME             0x1008
+#define ALARM_ENABLE           0x100C
+#define SET_SEC_CNTR_VAL       0x1010
+#define SET_SEC_CNTR_TRIG      0x1014
+#define SEC_CNTR_VAL           0x1018
+
+/*
+ * When in VDDBKUP domain, this MACRO register
+ * does not power down
+ */
+#define MACRO_RO_T             0x14A8
+#define MACRO_RG_SET_T         0x1498
+
+#define ALARM_ENABLE_MASK      BIT(0)
+#define SEL_SEC_PULSE          BIT(31)
+
+struct cv1800_rtc_priv {
+	struct rtc_device *rtc_dev;
+	struct regmap *rtc_map;
+	struct clk *clk;
+	int irq;
+};
+
+static bool cv1800_rtc_enabled(struct device *dev)
+{
+	struct cv1800_rtc_priv *info = dev_get_drvdata(dev);
+	u32 reg;
+
+	regmap_read(info->rtc_map, SEC_PULSE_GEN, &reg);
+
+	return (reg & SEL_SEC_PULSE) == 0;
+}
+
+static void cv1800_rtc_enable(struct device *dev)
+{
+	struct cv1800_rtc_priv *info = dev_get_drvdata(dev);
+
+	/* Sec pulse generated internally */
+	regmap_update_bits(info->rtc_map, SEC_PULSE_GEN, SEL_SEC_PULSE, 0);
+}
+
+static int cv1800_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled)
+{
+	struct cv1800_rtc_priv *info = dev_get_drvdata(dev);
+
+	regmap_write(info->rtc_map, ALARM_ENABLE, enabled);
+
+	return 0;
+}
+
+static int cv1800_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+	struct cv1800_rtc_priv *info = dev_get_drvdata(dev);
+	unsigned long alarm_time;
+
+	alarm_time = rtc_tm_to_time64(&alrm->time);
+
+	cv1800_rtc_alarm_irq_enable(dev, 0);
+
+	regmap_write(info->rtc_map, ALARM_TIME, alarm_time);
+
+	cv1800_rtc_alarm_irq_enable(dev, alrm->enabled);
+
+	return 0;
+}
+
+static int cv1800_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm)
+{
+	struct cv1800_rtc_priv *info = dev_get_drvdata(dev);
+	u32 enabled;
+	u32 time;
+
+	if (!cv1800_rtc_enabled(dev)) {
+		alarm->enabled = 0;
+		return 0;
+	}
+
+	regmap_read(info->rtc_map, ALARM_ENABLE, &enabled);
+
+	alarm->enabled = enabled & ALARM_ENABLE_MASK;
+
+	regmap_read(info->rtc_map, ALARM_TIME, &time);
+
+	rtc_time64_to_tm(time, &alarm->time);
+
+	return 0;
+}
+
+static int cv1800_rtc_read_time(struct device *dev, struct rtc_time *tm)
+{
+	struct cv1800_rtc_priv *info = dev_get_drvdata(dev);
+	u32 sec;
+
+	if (!cv1800_rtc_enabled(dev))
+		return -EINVAL;
+
+	regmap_read(info->rtc_map, SEC_CNTR_VAL, &sec);
+
+	rtc_time64_to_tm(sec, tm);
+
+	return 0;
+}
+
+static int cv1800_rtc_set_time(struct device *dev, struct rtc_time *tm)
+{
+	struct cv1800_rtc_priv *info = dev_get_drvdata(dev);
+	unsigned long sec;
+
+	sec = rtc_tm_to_time64(tm);
+
+	regmap_write(info->rtc_map, SET_SEC_CNTR_VAL, sec);
+	regmap_write(info->rtc_map, SET_SEC_CNTR_TRIG, 1);
+
+	regmap_write(info->rtc_map, MACRO_RG_SET_T, sec);
+
+	cv1800_rtc_enable(dev);
+
+	return 0;
+}
+
+static irqreturn_t cv1800_rtc_irq_handler(int irq, void *dev_id)
+{
+	struct cv1800_rtc_priv *info = dev_id;
+
+	rtc_update_irq(info->rtc_dev, 1, RTC_IRQF | RTC_AF);
+
+	regmap_write(info->rtc_map, ALARM_ENABLE, 0);
+
+	return IRQ_HANDLED;
+}
+
+static const struct rtc_class_ops cv1800_rtc_ops = {
+	.read_time = cv1800_rtc_read_time,
+	.set_time = cv1800_rtc_set_time,
+	.read_alarm = cv1800_rtc_read_alarm,
+	.set_alarm = cv1800_rtc_set_alarm,
+	.alarm_irq_enable = cv1800_rtc_alarm_irq_enable,
+};
+
+static int cv1800_rtc_probe(struct platform_device *pdev)
+{
+	struct cv1800_rtc_priv *rtc;
+	int ret;
+
+	rtc = devm_kzalloc(&pdev->dev, sizeof(*rtc), GFP_KERNEL);
+	if (!rtc)
+		return -ENOMEM;
+
+	rtc->rtc_map = device_node_to_regmap(pdev->dev.parent->of_node);
+	if (IS_ERR(rtc->rtc_map))
+		return dev_err_probe(&pdev->dev, PTR_ERR(rtc->rtc_map),
+				     "cannot get parent regmap\n");
+
+	rtc->irq = platform_get_irq(pdev, 0);
+	if (rtc->irq < 0)
+		return rtc->irq;
+
+	rtc->clk = devm_clk_get_enabled(pdev->dev.parent, "rtc");
+	if (IS_ERR(rtc->clk))
+		return dev_err_probe(&pdev->dev, PTR_ERR(rtc->clk),
+				     "rtc clk not found\n");
+
+	platform_set_drvdata(pdev, rtc);
+
+	device_init_wakeup(&pdev->dev, 1);
+
+	rtc->rtc_dev = devm_rtc_allocate_device(&pdev->dev);
+	if (IS_ERR(rtc->rtc_dev))
+		return PTR_ERR(rtc->rtc_dev);
+
+	rtc->rtc_dev->ops = &cv1800_rtc_ops;
+	rtc->rtc_dev->range_max = U32_MAX;
+
+	ret = devm_request_irq(&pdev->dev, rtc->irq, cv1800_rtc_irq_handler,
+			       IRQF_TRIGGER_HIGH, "rtc alarm", rtc);
+	if (ret)
+		return dev_err_probe(&pdev->dev, ret,
+				     "cannot register interrupt handler\n");
+
+	return devm_rtc_register_device(rtc->rtc_dev);
+}
+
+static const struct platform_device_id cv1800_rtc_id[] = {
+	{ .name = "cv1800b-rtc" },
+	{ /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(platform, cv1800_rtc_id);
+
+static struct platform_driver cv1800_rtc_driver = {
+	.driver = {
+		.name = "sophgo-cv1800-rtc",
+	},
+	.probe = cv1800_rtc_probe,
+	.id_table = cv1800_rtc_id,
+};
+
+module_platform_driver(cv1800_rtc_driver);
+MODULE_AUTHOR("Jingbao Qiu");
+MODULE_DESCRIPTION("Sophgo cv1800 RTC Driver");
+MODULE_LICENSE("GPL");