From 0f44bd6bec708782f38bba4d03deecf927d1c83d Mon Sep 17 00:00:00 2001
From: "ziv.xu" <ziv.xu@starfivetech.com>
Date: Fri, 9 Jun 2023 15:31:53 +0800
Subject: [PATCH 065/116] driver: rtc: Add StarFive JH7110 rtc driver

Add RTC driver and support for StarFive JH7110 SoC.

Signed-off-by: ziv.xu <ziv.xu@starfivetech.com>
Signed-off-by: Hal Feng <hal.feng@starfivetech.com>
---
 drivers/rtc/Kconfig        |   8 +
 drivers/rtc/Makefile       |   1 +
 drivers/rtc/rtc-starfive.c | 743 +++++++++++++++++++++++++++++++++++++
 3 files changed, 752 insertions(+)
 create mode 100644 drivers/rtc/rtc-starfive.c

--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -1327,6 +1327,14 @@ config RTC_DRV_NTXEC
 	  embedded controller found in certain e-book readers designed by the
 	  original design manufacturer Netronix.
 
+config RTC_DRV_STARFIVE
+	tristate "StarFive 32.768k-RTC"
+	depends on ARCH_STARFIVE
+	depends on OF
+	help
+	  If you say Y here you will get support for the RTC found on
+	  StarFive SOCS.
+
 comment "on-CPU RTC drivers"
 
 config RTC_DRV_ASM9260
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -163,6 +163,7 @@ obj-$(CONFIG_RTC_DRV_SH)	+= rtc-sh.o
 obj-$(CONFIG_RTC_DRV_SNVS)	+= rtc-snvs.o
 obj-$(CONFIG_RTC_DRV_SPEAR)	+= rtc-spear.o
 obj-$(CONFIG_RTC_DRV_STARFIRE)	+= rtc-starfire.o
+obj-$(CONFIG_RTC_DRV_STARFIVE)	+= rtc-starfive.o
 obj-$(CONFIG_RTC_DRV_STK17TA8)	+= rtc-stk17ta8.o
 obj-$(CONFIG_RTC_DRV_ST_LPC)	+= rtc-st-lpc.o
 obj-$(CONFIG_RTC_DRV_STM32) 	+= rtc-stm32.o
--- /dev/null
+++ b/drivers/rtc/rtc-starfive.c
@@ -0,0 +1,743 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * RTC driver for the StarFive JH7110 SoC
+ *
+ * Copyright (C) 2021 StarFive Technology Co., Ltd.
+ */
+
+#include <asm/delay.h>
+#include <linux/bcd.h>
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/reset.h>
+#include <linux/completion.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/iopoll.h>
+#include <linux/platform_device.h>
+#include <linux/rtc.h>
+
+/* Registers */
+#define SFT_RTC_CFG		0x00
+#define SFT_RTC_SW_CAL_VALUE	0x04
+#define SFT_RTC_HW_CAL_CFG	0x08
+#define SFT_RTC_CMP_CFG		0x0C
+#define SFT_RTC_IRQ_EN		0x10
+#define SFT_RTC_IRQ_EVEVT	0x14
+#define SFT_RTC_IRQ_STATUS	0x18
+#define SFT_RTC_CAL_VALUE	0x24
+#define SFT_RTC_CFG_TIME	0x28
+#define SFT_RTC_CFG_DATE	0x2C
+#define SFT_RTC_ACT_TIME	0x34
+#define SFT_RTC_ACT_DATE	0x38
+#define SFT_RTC_TIME		0x3C
+#define SFT_RTC_DATE		0x40
+#define SFT_RTC_TIME_LATCH	0x44
+#define SFT_RTC_DATE_LATCH	0x48
+
+/* RTC_CFG */
+#define RTC_CFG_ENABLE_SHIFT	0  /* RW: RTC Enable. */
+#define RTC_CFG_CAL_EN_HW_SHIFT	1  /* RW: Enable of hardware calibretion. */
+#define RTC_CFG_CAL_SEL_SHIFT	2  /* RW: select the hw/sw calibretion mode.*/
+#define RTC_CFG_HOUR_MODE_SHIFT	3  /* RW: time hour mode. 24h|12h */
+
+/* RTC_SW_CAL_VALUE */
+#define RTC_SW_CAL_VALUE_MASK	GENMASK(15, 0)
+#define RTC_SW_CAL_MAX		RTC_SW_CAL_VALUE_MASK
+#define RTC_SW_CAL_MIN		0
+#define RTC_TICKS_PER_SEC	32768		/* Number of ticks per second */
+#define RTC_PPB_MULT		1000000000LL	/* Multiplier for ppb conversions */
+
+/* RTC_HW_CAL_CFG */
+#define RTC_HW_CAL_REF_SEL_SHIFT	0
+#define RTC_HW_CAL_FRQ_SEL_SHIFT	1
+
+/* IRQ_EN/IRQ_EVEVT/IRQ_STATUS */
+#define RTC_IRQ_CAL_START	BIT(0)
+#define RTC_IRQ_CAL_FINISH	BIT(1)
+#define RTC_IRQ_CMP		BIT(2)
+#define RTC_IRQ_1SEC		BIT(3)
+#define RTC_IRQ_ALAEM		BIT(4)
+#define RTC_IRQ_EVT_UPDATE_PSE	BIT(31)	/* WO: Enable of update time&&date, IRQ_EVEVT only */
+#define RTC_IRQ_ALL		(RTC_IRQ_CAL_START \
+				| RTC_IRQ_CAL_FINISH \
+				| RTC_IRQ_CMP \
+				| RTC_IRQ_1SEC \
+				| RTC_IRQ_ALAEM)
+
+/* CAL_VALUE */
+#define RTC_CAL_VALUE_MASK	GENMASK(15, 0)
+
+/* CFG_TIME/ACT_TIME/RTC_TIME */
+#define TIME_SEC_MASK		GENMASK(6, 0)
+#define TIME_MIN_MASK		GENMASK(13, 7)
+#define TIME_HOUR_MASK		GENMASK(20, 14)
+
+/* CFG_DATE/ACT_DATE/RTC_DATE */
+#define DATE_DAY_MASK		GENMASK(5, 0)
+#define DATE_MON_MASK		GENMASK(10, 6)
+#define DATE_YEAR_MASK		GENMASK(18, 11)
+
+#define INT_TIMEOUT_US		180
+
+enum RTC_HOUR_MODE {
+	RTC_HOUR_MODE_12H = 0,
+	RTC_HOUR_MODE_24H = 1
+};
+
+enum RTC_CAL_MODE {
+	RTC_CAL_MODE_SW = 0,
+	RTC_CAL_MODE_HW = 1
+};
+
+enum RTC_HW_CAL_REF_MODE {
+	RTC_CAL_CLK_REF = 0,
+	RTC_CAL_CLK_MARK = 1
+};
+
+static const unsigned long refclk_list[] = {
+	1000000,
+	2000000,
+	4000000,
+	5927000,
+	6000000,
+	7200000,
+	8000000,
+	10250000,
+	11059200,
+	12000000,
+	12288000,
+	13560000,
+	16000000,
+	19200000,
+	20000000,
+	22118000,
+	24000000,
+	24567000,
+	25000000,
+	26000000,
+	27000000,
+	30000000,
+	32000000,
+	33868800,
+	36000000,
+	36860000,
+	40000000,
+	44000000,
+	50000000,
+	54000000,
+	28224000,
+	28000000,
+};
+
+struct sft_rtc {
+	struct rtc_device *rtc_dev;
+	struct completion cal_done;
+	struct completion onesec_done;
+	struct clk *pclk;
+	struct clk *cal_clk;
+	struct reset_control *rst_array;
+	int hw_cal_map;
+	void __iomem *regs;
+	int rtc_irq;
+	int ms_pulse_irq;
+	int one_sec_pulse_irq;
+};
+
+static inline void sft_rtc_set_enabled(struct sft_rtc *srtc, bool enabled)
+{
+	u32 val;
+
+	if (enabled) {
+		val = readl(srtc->regs + SFT_RTC_CFG);
+		val |= BIT(RTC_CFG_ENABLE_SHIFT);
+		writel(val, srtc->regs + SFT_RTC_CFG);
+	} else {
+		val = readl(srtc->regs + SFT_RTC_CFG);
+		val &= ~BIT(RTC_CFG_ENABLE_SHIFT);
+		writel(val, srtc->regs + SFT_RTC_CFG);
+	}
+}
+
+static inline bool sft_rtc_get_enabled(struct sft_rtc *srtc)
+{
+	return !!(readl(srtc->regs + SFT_RTC_CFG) & BIT(RTC_CFG_ENABLE_SHIFT));
+}
+
+static inline void sft_rtc_set_mode(struct sft_rtc *srtc, enum RTC_HOUR_MODE mode)
+{
+	u32 val;
+
+	val = readl(srtc->regs + SFT_RTC_CFG);
+	val |= mode << RTC_CFG_HOUR_MODE_SHIFT;
+	writel(val, srtc->regs + SFT_RTC_CFG);
+}
+
+static inline int sft_rtc_irq_enable(struct sft_rtc *srtc, u32 irq, bool enable)
+{
+	u32 val;
+
+	if (!(irq & RTC_IRQ_ALL))
+		return -EINVAL;
+
+	if (enable) {
+		val = readl(srtc->regs + SFT_RTC_IRQ_EN);
+		val |= irq;
+		writel(val, srtc->regs + SFT_RTC_IRQ_EN);
+	} else {
+		val = readl(srtc->regs + SFT_RTC_IRQ_EN);
+		val &= ~irq;
+		writel(val, srtc->regs + SFT_RTC_IRQ_EN);
+	}
+	return 0;
+}
+
+static inline void
+sft_rtc_set_cal_hw_enable(struct sft_rtc *srtc, bool enable)
+{
+	u32 val;
+
+	if (enable) {
+		val = readl(srtc->regs + SFT_RTC_CFG);
+		val |= BIT(RTC_CFG_CAL_EN_HW_SHIFT);
+		writel(val, srtc->regs + SFT_RTC_CFG);
+	} else {
+		val = readl(srtc->regs + SFT_RTC_CFG);
+		val &= ~BIT(RTC_CFG_CAL_EN_HW_SHIFT);
+		writel(val, srtc->regs + SFT_RTC_CFG);
+	}
+}
+
+static inline void
+sft_rtc_set_cal_mode(struct sft_rtc *srtc, enum RTC_CAL_MODE mode)
+{
+	u32 val;
+
+	val = readl(srtc->regs + SFT_RTC_CFG);
+	val |= mode << RTC_CFG_CAL_SEL_SHIFT;
+	writel(val, srtc->regs + SFT_RTC_CFG);
+}
+
+static int sft_rtc_get_hw_calclk(struct device *dev, unsigned long freq)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(refclk_list); i++)
+		if (refclk_list[i] == freq)
+			return i;
+
+	dev_err(dev, "refclk: %ldHz do not support.\n", freq);
+	return -EINVAL;
+}
+
+static inline void sft_rtc_reg2time(struct rtc_time *tm, u32 reg)
+{
+	tm->tm_hour = bcd2bin(FIELD_GET(TIME_HOUR_MASK, reg));
+	tm->tm_min = bcd2bin(FIELD_GET(TIME_MIN_MASK, reg));
+	tm->tm_sec = bcd2bin(FIELD_GET(TIME_SEC_MASK, reg));
+}
+
+static inline void sft_rtc_reg2date(struct rtc_time *tm, u32 reg)
+{
+	tm->tm_year = bcd2bin(FIELD_GET(DATE_YEAR_MASK, reg)) + 100;
+	tm->tm_mon = bcd2bin(FIELD_GET(DATE_MON_MASK, reg)) - 1;
+	tm->tm_mday = bcd2bin(FIELD_GET(DATE_DAY_MASK, reg));
+}
+
+static inline u32 sft_rtc_time2reg(struct rtc_time *tm)
+{
+	return	FIELD_PREP(TIME_HOUR_MASK, bin2bcd(tm->tm_hour)) |
+		FIELD_PREP(TIME_MIN_MASK, bin2bcd(tm->tm_min)) |
+		FIELD_PREP(TIME_SEC_MASK, bin2bcd(tm->tm_sec));
+}
+
+static inline u32 sft_rtc_date2reg(struct rtc_time *tm)
+{
+	return	FIELD_PREP(DATE_YEAR_MASK, bin2bcd(tm->tm_year - 100)) |
+		FIELD_PREP(DATE_MON_MASK, bin2bcd(tm->tm_mon + 1)) |
+		FIELD_PREP(DATE_DAY_MASK, bin2bcd(tm->tm_mday));
+}
+
+static inline void sft_rtc_update_pulse(struct sft_rtc *srtc)
+{
+	u32 val;
+
+	val = readl(srtc->regs + SFT_RTC_IRQ_EVEVT);
+	val |= RTC_IRQ_EVT_UPDATE_PSE;
+	writel(val, srtc->regs + SFT_RTC_IRQ_EVEVT);
+}
+
+static irqreturn_t sft_rtc_irq_handler(int irq, void *data)
+{
+	struct sft_rtc *srtc = data;
+	struct timerqueue_node *next;
+	u32 irq_flags = 0;
+	u32 irq_mask = 0;
+	u32 val;
+	int ret = 0;
+
+	val = readl(srtc->regs + SFT_RTC_IRQ_EVEVT);
+	if (val & RTC_IRQ_CAL_START)
+		irq_mask |= RTC_IRQ_CAL_START;
+
+	if (val & RTC_IRQ_CAL_FINISH) {
+		irq_mask |= RTC_IRQ_CAL_FINISH;
+		complete(&srtc->cal_done);
+	}
+
+	if (val & RTC_IRQ_CMP)
+		irq_mask |= RTC_IRQ_CMP;
+
+	if (val & RTC_IRQ_1SEC) {
+		irq_flags |= RTC_PF;
+		irq_mask |= RTC_IRQ_1SEC;
+		complete(&srtc->onesec_done);
+	}
+
+	if (val & RTC_IRQ_ALAEM) {
+		irq_flags |= RTC_AF;
+		irq_mask |= RTC_IRQ_ALAEM;
+
+		next = timerqueue_getnext(&srtc->rtc_dev->timerqueue);
+		if (next == &srtc->rtc_dev->aie_timer.node)
+			dev_info(&srtc->rtc_dev->dev, "alarm expires");
+	}
+
+	writel(irq_mask, srtc->regs + SFT_RTC_IRQ_EVEVT);
+
+	/* Wait interrupt flag clear */
+	ret = readl_poll_timeout_atomic(srtc->regs + SFT_RTC_IRQ_EVEVT, val,
+					(val & irq_mask) == 0, 0, INT_TIMEOUT_US);
+	if (ret)
+		dev_warn(&srtc->rtc_dev->dev, "fail to clear rtc interrupt flag\n");
+
+	if (irq_flags)
+		rtc_update_irq(srtc->rtc_dev, 1, irq_flags | RTC_IRQF);
+
+	return IRQ_HANDLED;
+}
+
+static int sft_rtc_read_time(struct device *dev, struct rtc_time *tm)
+{
+	struct sft_rtc *srtc = dev_get_drvdata(dev);
+	u32 val;
+	int irq_1sec_state_start, irq_1sec_state_end;
+
+	/* If the RTC is disabled, assume the values are invalid */
+	if (!sft_rtc_get_enabled(srtc))
+		return -EINVAL;
+
+	irq_1sec_state_start =
+		(readl(srtc->regs + SFT_RTC_IRQ_STATUS) & RTC_IRQ_1SEC) == 0 ? 0 : 1;
+
+read_again:
+	val = readl(srtc->regs + SFT_RTC_TIME);
+	sft_rtc_reg2time(tm, val);
+
+	val = readl(srtc->regs + SFT_RTC_DATE);
+	sft_rtc_reg2date(tm, val);
+
+	if (irq_1sec_state_start == 0) {
+		irq_1sec_state_end =
+			(readl(srtc->regs + SFT_RTC_IRQ_STATUS) & RTC_IRQ_1SEC) == 0 ? 0 : 1;
+		if (irq_1sec_state_end == 1) {
+			irq_1sec_state_start = 1;
+			goto read_again;
+		}
+	}
+
+	return 0;
+}
+
+static int sft_rtc_set_time(struct device *dev, struct rtc_time *tm)
+{
+	struct sft_rtc *srtc = dev_get_drvdata(dev);
+	u32 val;
+	int ret;
+
+	val = sft_rtc_time2reg(tm);
+	writel(val, srtc->regs + SFT_RTC_CFG_TIME);
+
+	val = sft_rtc_date2reg(tm);
+	writel(val, srtc->regs + SFT_RTC_CFG_DATE);
+
+	/* Update pulse */
+	sft_rtc_update_pulse(srtc);
+
+	/* Ensure that data is fully written */
+	ret = wait_for_completion_interruptible_timeout(&srtc->onesec_done,
+							usecs_to_jiffies(120));
+	if (ret) {
+		dev_warn(dev,
+			 "rtc wait for completion interruptible timeout.\n");
+	}
+	return 0;
+}
+
+static int sft_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled)
+{
+	struct sft_rtc *srtc = dev_get_drvdata(dev);
+
+	return sft_rtc_irq_enable(srtc, RTC_IRQ_ALAEM, enabled);
+}
+
+static int sft_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm)
+{
+	struct sft_rtc *srtc = dev_get_drvdata(dev);
+	u32 val;
+
+	val = readl(srtc->regs + SFT_RTC_ACT_TIME);
+	sft_rtc_reg2time(&alarm->time, val);
+
+	val = readl(srtc->regs + SFT_RTC_ACT_DATE);
+	sft_rtc_reg2date(&alarm->time, val);
+
+	return 0;
+}
+
+static int sft_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm)
+{
+	struct sft_rtc *srtc = dev_get_drvdata(dev);
+	u32 val;
+
+	sft_rtc_alarm_irq_enable(dev, 0);
+
+	val = sft_rtc_time2reg(&alarm->time);
+	writel(val, srtc->regs + SFT_RTC_ACT_TIME);
+
+	val = sft_rtc_date2reg(&alarm->time);
+	writel(val, srtc->regs + SFT_RTC_ACT_DATE);
+
+	sft_rtc_alarm_irq_enable(dev, alarm->enabled);
+
+	return 0;
+}
+
+static int sft_rtc_get_offset(struct device *dev, long *offset)
+{
+	struct sft_rtc *srtc = dev_get_drvdata(dev);
+	s64 tmp;
+	u32 val;
+
+	val = readl(srtc->regs + SFT_RTC_CAL_VALUE)
+			& RTC_SW_CAL_VALUE_MASK;
+	val += 1;
+	/*
+	 * the adjust val range is [0x0000-0xffff],
+	 * the default val is 0x7fff (32768-1),mapping offset=0 ;
+	 */
+	tmp = (s64)val - RTC_TICKS_PER_SEC;
+	tmp *= RTC_PPB_MULT;
+	tmp = div_s64(tmp, RTC_TICKS_PER_SEC);
+
+	/* Offset value operates in negative way, so swap sign */
+	*offset = -tmp;
+
+	return 0;
+}
+
+static int sft_rtc_set_offset(struct device *dev, long offset)
+{
+	struct sft_rtc *srtc = dev_get_drvdata(dev);
+	s64 tmp;
+	u32 val;
+
+	tmp = offset * RTC_TICKS_PER_SEC;
+	tmp = div_s64(tmp, RTC_PPB_MULT);
+
+	tmp = RTC_TICKS_PER_SEC - tmp;
+	tmp -= 1;
+	if (tmp > RTC_SW_CAL_MAX || tmp < RTC_SW_CAL_MIN) {
+		dev_err(dev, "offset is out of range.\n");
+		return -EINVAL;
+	}
+
+	val = tmp & RTC_SW_CAL_VALUE_MASK;
+	/* set software calibration value */
+	writel(val, srtc->regs + SFT_RTC_SW_CAL_VALUE);
+
+	/* set CFG_RTC-cal_sel to select calibretion by software. */
+	sft_rtc_set_cal_mode(srtc, RTC_CAL_MODE_SW);
+
+	return 0;
+}
+
+static __maybe_unused int
+sft_rtc_hw_adjustment(struct device *dev, unsigned int enable)
+{
+	struct sft_rtc *srtc = dev_get_drvdata(dev);
+	u32 val;
+
+	if (srtc->hw_cal_map <= 0) {
+		dev_err(dev, "fail to get cal-clock-freq.\n");
+		return -EFAULT;
+	}
+
+	if (enable) {
+		sft_rtc_irq_enable(srtc, RTC_IRQ_CAL_FINISH, true);
+
+		/* Set reference clock frequency value */
+		val = readl(srtc->regs + SFT_RTC_HW_CAL_CFG);
+		val |= (srtc->hw_cal_map << RTC_HW_CAL_FRQ_SEL_SHIFT);
+		writel(val, srtc->regs + SFT_RTC_HW_CAL_CFG);
+
+		/* Set CFG_RTC-cal_sel to select calibretion by hardware. */
+		sft_rtc_set_cal_mode(srtc, RTC_CAL_MODE_HW);
+
+		/* Set CFG_RTC-cal_en_hw to launch hardware calibretion.*/
+		sft_rtc_set_cal_hw_enable(srtc, true);
+
+		wait_for_completion_interruptible_timeout(&srtc->cal_done,
+							  usecs_to_jiffies(100));
+
+		sft_rtc_irq_enable(srtc, RTC_IRQ_CAL_FINISH, false);
+	} else {
+		sft_rtc_set_cal_mode(srtc, RTC_CAL_MODE_SW);
+		sft_rtc_set_cal_hw_enable(srtc, false);
+	}
+
+	return 0;
+}
+
+static int sft_rtc_get_cal_clk(struct device *dev, struct sft_rtc *srtc)
+{
+	struct device_node *np = dev->of_node;
+	unsigned long cal_clk_freq;
+	u32 freq;
+	int ret;
+
+	srtc->cal_clk = devm_clk_get(dev, "cal_clk");
+	if (IS_ERR(srtc->cal_clk))
+		return PTR_ERR(srtc->cal_clk);
+
+	clk_prepare_enable(srtc->cal_clk);
+
+	cal_clk_freq = clk_get_rate(srtc->cal_clk);
+	if (!cal_clk_freq) {
+		dev_warn(dev,
+			 "get rate failed, next try to get from dts.\n");
+		ret = of_property_read_u32(np, "rtc,cal-clock-freq", &freq);
+		if (!ret) {
+			cal_clk_freq = (u64)freq;
+		} else {
+			dev_err(dev,
+				"Need rtc,cal-clock-freq define in dts.\n");
+			goto err_disable_cal_clk;
+		}
+	}
+
+	srtc->hw_cal_map = sft_rtc_get_hw_calclk(dev, cal_clk_freq);
+	if (srtc->hw_cal_map < 0) {
+		ret = srtc->hw_cal_map;
+		goto err_disable_cal_clk;
+	}
+
+	return 0;
+
+err_disable_cal_clk:
+	clk_disable_unprepare(srtc->cal_clk);
+
+	return ret;
+}
+
+static int sft_rtc_get_irq(struct platform_device *pdev, struct sft_rtc *srtc)
+{
+	int ret;
+
+	srtc->rtc_irq = platform_get_irq_byname(pdev, "rtc");
+	if (srtc->rtc_irq < 0)
+		return -EINVAL;
+
+	ret = devm_request_irq(&pdev->dev, srtc->rtc_irq,
+			       sft_rtc_irq_handler, 0,
+				KBUILD_MODNAME, srtc);
+	if (ret)
+		dev_err(&pdev->dev, "Failed to request interrupt, %d\n", ret);
+
+	return ret;
+}
+
+static const struct rtc_class_ops starfive_rtc_ops = {
+	.read_time		= sft_rtc_read_time,
+	.set_time		= sft_rtc_set_time,
+	.read_alarm		= sft_rtc_read_alarm,
+	.set_alarm		= sft_rtc_set_alarm,
+	.alarm_irq_enable	= sft_rtc_alarm_irq_enable,
+	.set_offset		= sft_rtc_set_offset,
+	.read_offset		= sft_rtc_get_offset,
+};
+
+static int sft_rtc_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct sft_rtc *srtc;
+	struct rtc_time tm;
+	struct irq_desc *desc;
+	int ret;
+
+	srtc = devm_kzalloc(dev, sizeof(*srtc), GFP_KERNEL);
+	if (!srtc)
+		return -ENOMEM;
+
+	srtc->regs = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(srtc->regs))
+		return PTR_ERR(srtc->regs);
+
+	srtc->pclk = devm_clk_get(dev, "pclk");
+	if (IS_ERR(srtc->pclk)) {
+		ret = PTR_ERR(srtc->pclk);
+		dev_err(dev,
+			"Failed to retrieve the peripheral clock, %d\n", ret);
+		return ret;
+	}
+
+	srtc->rst_array = devm_reset_control_array_get_exclusive(dev);
+	if (IS_ERR(srtc->rst_array)) {
+		ret = PTR_ERR(srtc->rst_array);
+		dev_err(dev,
+			"Failed to retrieve the rtc reset, %d\n", ret);
+		return ret;
+	}
+
+	init_completion(&srtc->cal_done);
+	init_completion(&srtc->onesec_done);
+
+	ret = clk_prepare_enable(srtc->pclk);
+	if (ret) {
+		dev_err(dev,
+			"Failed to enable the peripheral clock, %d\n", ret);
+		return ret;
+	}
+
+	ret = sft_rtc_get_cal_clk(dev, srtc);
+	if (ret)
+		goto err_disable_pclk;
+
+	ret = reset_control_deassert(srtc->rst_array);
+	if (ret) {
+		dev_err(dev,
+			"Failed to deassert rtc resets, %d\n", ret);
+		goto err_disable_cal_clk;
+	}
+
+	ret = sft_rtc_get_irq(pdev, srtc);
+	if (ret)
+		goto err_disable_cal_clk;
+
+	srtc->rtc_dev = devm_rtc_allocate_device(dev);
+	if (IS_ERR(srtc->rtc_dev))
+		return PTR_ERR(srtc->rtc_dev);
+
+	platform_set_drvdata(pdev, srtc);
+
+	/* The RTC supports 01.01.2001 - 31.12.2099 */
+	srtc->rtc_dev->range_min = mktime64(2001,  1,  1,  0,  0,  0);
+	srtc->rtc_dev->range_max = mktime64(2099, 12, 31, 23, 59, 59);
+
+	srtc->rtc_dev->ops = &starfive_rtc_ops;
+	device_init_wakeup(dev, true);
+
+	desc = irq_to_desc(srtc->rtc_irq);
+	irq_desc_get_chip(desc)->flags = IRQCHIP_SKIP_SET_WAKE;
+
+	/* Always use 24-hour mode and keep the RTC values */
+	sft_rtc_set_mode(srtc, RTC_HOUR_MODE_24H);
+
+	sft_rtc_set_enabled(srtc, true);
+
+	if (device_property_read_bool(dev, "rtc,hw-adjustment"))
+		sft_rtc_hw_adjustment(dev, true);
+
+	/*
+	 * If rtc time is out of supported range, reset it to the minimum time.
+	 * notice that, actual year = 1900 + tm.tm_year
+	 *              actual month = 1 + tm.tm_mon
+	 */
+	sft_rtc_read_time(dev, &tm);
+	if (tm.tm_year < 101 || tm.tm_year > 199 || tm.tm_mon < 0 || tm.tm_mon > 11 ||
+	    tm.tm_mday < 1 || tm.tm_mday > 31 || tm.tm_hour < 0 || tm.tm_hour > 23 ||
+	    tm.tm_min < 0 || tm.tm_min > 59 || tm.tm_sec < 0 || tm.tm_sec > 59) {
+		rtc_time64_to_tm(srtc->rtc_dev->range_min, &tm);
+		sft_rtc_set_time(dev, &tm);
+	}
+
+	ret = devm_rtc_register_device(srtc->rtc_dev);
+	if (ret)
+		goto err_disable_wakeup;
+
+	return 0;
+
+err_disable_wakeup:
+	device_init_wakeup(dev, false);
+
+err_disable_cal_clk:
+	clk_disable_unprepare(srtc->cal_clk);
+
+err_disable_pclk:
+	clk_disable_unprepare(srtc->pclk);
+
+	return ret;
+}
+
+static int sft_rtc_remove(struct platform_device *pdev)
+{
+	struct sft_rtc *srtc = platform_get_drvdata(pdev);
+
+	sft_rtc_alarm_irq_enable(&pdev->dev, 0);
+	device_init_wakeup(&pdev->dev, 0);
+
+	clk_disable_unprepare(srtc->pclk);
+	clk_disable_unprepare(srtc->cal_clk);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int sft_rtc_suspend(struct device *dev)
+{
+	struct sft_rtc *srtc = dev_get_drvdata(dev);
+
+	if (device_may_wakeup(dev))
+		enable_irq_wake(srtc->rtc_irq);
+
+	return 0;
+}
+
+static int sft_rtc_resume(struct device *dev)
+{
+	struct sft_rtc *srtc = dev_get_drvdata(dev);
+
+	if (device_may_wakeup(dev))
+		disable_irq_wake(srtc->rtc_irq);
+
+	return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(sft_rtc_pm_ops, sft_rtc_suspend, sft_rtc_resume);
+
+static const struct of_device_id sft_rtc_of_match[] = {
+	{ .compatible = "starfive,jh7110-rtc" },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, sft_rtc_of_match);
+
+static struct platform_driver starfive_rtc_driver = {
+	.driver = {
+		.name = "starfive-rtc",
+		.of_match_table = sft_rtc_of_match,
+		.pm   = &sft_rtc_pm_ops,
+	},
+	.probe = sft_rtc_probe,
+	.remove = sft_rtc_remove,
+};
+module_platform_driver(starfive_rtc_driver);
+
+MODULE_AUTHOR("Samin Guo <samin.guo@starfivetech.com>");
+MODULE_AUTHOR("Hal Feng <hal.feng@starfivetech.com>");
+MODULE_DESCRIPTION("StarFive RTC driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:starfive-rtc");
