diff mbox series

[v8,2/3] clocksource: Add JH7110 timer driver

Message ID 20231219145402.7879-3-xingyu.wu@starfivetech.com (mailing list archive)
State Handled Elsewhere
Headers show
Series Add timer driver for StarFive JH7110 RISC-V SoC | expand

Checks

Context Check Description
conchuod/vmtest-for-next-PR success PR summary
conchuod/patch-2-test-1 success .github/scripts/patches/tests/build_rv32_defconfig.sh
conchuod/patch-2-test-2 success .github/scripts/patches/tests/build_rv64_clang_allmodconfig.sh
conchuod/patch-2-test-3 success .github/scripts/patches/tests/build_rv64_gcc_allmodconfig.sh
conchuod/patch-2-test-4 success .github/scripts/patches/tests/build_rv64_nommu_k210_defconfig.sh
conchuod/patch-2-test-5 success .github/scripts/patches/tests/build_rv64_nommu_virt_defconfig.sh
conchuod/patch-2-test-6 warning .github/scripts/patches/tests/checkpatch.sh
conchuod/patch-2-test-7 success .github/scripts/patches/tests/dtb_warn_rv64.sh
conchuod/patch-2-test-8 success .github/scripts/patches/tests/header_inline.sh
conchuod/patch-2-test-9 success .github/scripts/patches/tests/kdoc.sh
conchuod/patch-2-test-10 success .github/scripts/patches/tests/module_param.sh
conchuod/patch-2-test-11 success .github/scripts/patches/tests/verify_fixes.sh
conchuod/patch-2-test-12 success .github/scripts/patches/tests/verify_signedoff.sh

Commit Message

Xingyu Wu Dec. 19, 2023, 2:54 p.m. UTC
Add timer driver for the StarFive JH7110 SoC and select it by
CONFIG_SOC_STARFIVE.

This timer has four free-running and independent 32-bit counters.
Each channel(counter) can trigger an interrupt when timeout even
CPU is sleeping. So this timer is used as global timer and register
clockevent for each CPU core after riscv-timer registration on the
StarFive JH7110 SoC.

Signed-off-by: Xingyu Wu <xingyu.wu@starfivetech.com>
---
 MAINTAINERS                        |   7 +
 arch/riscv/Kconfig.socs            |   1 +
 drivers/clocksource/Kconfig        |   9 +
 drivers/clocksource/Makefile       |   1 +
 drivers/clocksource/timer-jh7110.c | 360 +++++++++++++++++++++++++++++
 include/linux/cpuhotplug.h         |   1 +
 6 files changed, 379 insertions(+)
 create mode 100644 drivers/clocksource/timer-jh7110.c

Comments

Emil Renner Berthing Dec. 20, 2023, 1:59 p.m. UTC | #1
Xingyu Wu wrote:
> Add timer driver for the StarFive JH7110 SoC and select it by
> CONFIG_SOC_STARFIVE.
>
> This timer has four free-running and independent 32-bit counters.
> Each channel(counter) can trigger an interrupt when timeout even
> CPU is sleeping. So this timer is used as global timer and register
> clockevent for each CPU core after riscv-timer registration on the
> StarFive JH7110 SoC.
>
> Signed-off-by: Xingyu Wu <xingyu.wu@starfivetech.com>
> ---
>  MAINTAINERS                        |   7 +
>  arch/riscv/Kconfig.socs            |   1 +
>  drivers/clocksource/Kconfig        |   9 +
>  drivers/clocksource/Makefile       |   1 +
>  drivers/clocksource/timer-jh7110.c | 360 +++++++++++++++++++++++++++++
>  include/linux/cpuhotplug.h         |   1 +
>  6 files changed, 379 insertions(+)
>  create mode 100644 drivers/clocksource/timer-jh7110.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 9104430e148e..fe0e803606a5 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -20617,6 +20617,13 @@ S:	Maintained
>  F:	Documentation/devicetree/bindings/sound/starfive,jh7110-tdm.yaml
>  F:	sound/soc/starfive/jh7110_tdm.c
>
> +STARFIVE JH7110 TIMER DRIVER
> +M:	Samin Guo <samin.guo@starfivetech.com>

Last time I sent a mail to samin.guo@starfivetech.com it bounced. Was that just
a temporary error?

/Emil

> +M:	Xingyu Wu <xingyu.wu@starfivetech.com>
> +S:	Supported
> +F:	Documentation/devicetree/bindings/timer/starfive,jh7110-timer.yaml
> +F:	drivers/clocksource/timer-jh7110.c
> +
>  STARFIVE JH71X0 CLOCK DRIVERS
>  M:	Emil Renner Berthing <kernel@esmil.dk>
>  M:	Hal Feng <hal.feng@starfivetech.com>
Xingyu Wu Dec. 21, 2023, 1:50 a.m. UTC | #2
On 2023/12/20 21:59, Emil Renner Berthing wrote:
> Xingyu Wu wrote:
>> Add timer driver for the StarFive JH7110 SoC and select it by
>> CONFIG_SOC_STARFIVE.
>>
>> This timer has four free-running and independent 32-bit counters.
>> Each channel(counter) can trigger an interrupt when timeout even
>> CPU is sleeping. So this timer is used as global timer and register
>> clockevent for each CPU core after riscv-timer registration on the
>> StarFive JH7110 SoC.
>>
>> Signed-off-by: Xingyu Wu <xingyu.wu@starfivetech.com>
>> ---
>>  MAINTAINERS                        |   7 +
>>  arch/riscv/Kconfig.socs            |   1 +
>>  drivers/clocksource/Kconfig        |   9 +
>>  drivers/clocksource/Makefile       |   1 +
>>  drivers/clocksource/timer-jh7110.c | 360 +++++++++++++++++++++++++++++
>>  include/linux/cpuhotplug.h         |   1 +
>>  6 files changed, 379 insertions(+)
>>  create mode 100644 drivers/clocksource/timer-jh7110.c
>>
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index 9104430e148e..fe0e803606a5 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -20617,6 +20617,13 @@ S:	Maintained
>>  F:	Documentation/devicetree/bindings/sound/starfive,jh7110-tdm.yaml
>>  F:	sound/soc/starfive/jh7110_tdm.c
>>
>> +STARFIVE JH7110 TIMER DRIVER
>> +M:	Samin Guo <samin.guo@starfivetech.com>
> 
> Last time I sent a mail to samin.guo@starfivetech.com it bounced. Was that just
> a temporary error?
> 
> /Emil

Oh, This email has been deactivated and I don't have his other personal email.
I had dropped it in the driver but forget it here.
Will fix.

Thanks,
Xingyu Wu

> 
>> +M:	Xingyu Wu <xingyu.wu@starfivetech.com>
>> +S:	Supported
>> +F:	Documentation/devicetree/bindings/timer/starfive,jh7110-timer.yaml
>> +F:	drivers/clocksource/timer-jh7110.c
>> +
>>  STARFIVE JH71X0 CLOCK DRIVERS
>>  M:	Emil Renner Berthing <kernel@esmil.dk>
>>  M:	Hal Feng <hal.feng@starfivetech.com>
Thomas Gleixner Feb. 27, 2024, 4:32 p.m. UTC | #3
On Tue, Dec 19 2023 at 22:54, Xingyu Wu wrote:
> +
> +struct jh7110_clkevt {
> +	struct clk *clk;
> +	struct reset_control *rst;
> +	void __iomem *base;
> +	u32 reload_val;
> +	int irq;
> +	char name[sizeof("jh7110-timer.chX")];
> +};
> +
> +struct jh7110_timer_priv {
> +	struct reset_control *prst;
> +	struct device *dev;
> +	struct jh7110_clkevt clkevt[JH7110_TIMER_CH_MAX];
> +};

Please format your data structures according to documentation:

https://www.kernel.org/doc/html/latest/process/maintainer-tip.html#struct-declarations-and-initializers

> +/* IRQ handler for the timer */
> +static irqreturn_t jh7110_timer_interrupt(int irq, void *data)
> +{
> +	struct clock_event_device *evt = data;
> +	struct jh7110_clkevt *clkevt = &jh7110_timer_info.clkevt[0];
> +	u8 cpu_id = smp_processor_id();
> +	u32 reg = readl(clkevt->base + JH7110_TIMER_INT_STATUS);

https://www.kernel.org/doc/html/latest/process/maintainer-tip.html#variable-declarations

> +	/* Check interrupt status and channel(cpu) ID */
> +	if (!(reg & BIT(cpu_id)))
> +		return IRQ_NONE;
> +
> +	clkevt = &jh7110_timer_info.clkevt[cpu_id];
> +	writel(JH7110_TIMER_INT_CLR_ENA, (clkevt->base + JH7110_TIMER_INT_CLR));
> +
> +	if (evt->event_handler)
> +		evt->event_handler(evt);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int jh7110_timer_set_periodic(struct clock_event_device *evt)
> +{
> +	struct jh7110_clkevt *clkevt = JH7110_PERCPU_GET_CLKEVT;
> +
> +	writel(JH7110_TIMER_MODE_CONTIN, clkevt->base + JH7110_TIMER_CTL);
> +	return 0;
> +}
> +
> +static int jh7110_timer_set_oneshot(struct clock_event_device *evt)
> +{
> +	struct jh7110_clkevt *clkevt = JH7110_PERCPU_GET_CLKEVT;
> +
> +	writel(JH7110_TIMER_MODE_SINGLE, clkevt->base + JH7110_TIMER_CTL);
> +	return 0;
> +}
> +
> +static int jh7110_timer_set_next_event(unsigned long next,
> +				       struct clock_event_device *evt)
> +{
> +	struct jh7110_clkevt *clkevt = JH7110_PERCPU_GET_CLKEVT;
> +
> +	writel(JH7110_TIMER_MODE_SINGLE, clkevt->base + JH7110_TIMER_CTL);
> +	writel(next, clkevt->base + JH7110_TIMER_LOAD);
> +
> +	return jh7110_timer_start(clkevt);
> +}
> +
> +static DEFINE_PER_CPU(struct clock_event_device, jh7110_clock_event) = {
> +	.features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
> +	.rating = JH7110_CLOCKEVENT_RATING,
> +	.set_state_shutdown = jh7110_timer_shutdown,
> +	.set_state_periodic = jh7110_timer_set_periodic,
> +	.set_state_oneshot = jh7110_timer_set_oneshot,
> +	.set_state_oneshot_stopped = jh7110_timer_shutdown,
> +	.tick_resume = jh7110_timer_tick_resume,
> +	.set_next_event = jh7110_timer_set_next_event,
> +	.suspend = jh7110_timer_suspend,
> +	.resume = jh7110_timer_resume,
> +};

See formatting rules.

> +static int jh7110_timer_starting_cpu(unsigned int cpu)
> +{
> +	struct clock_event_device *evt = per_cpu_ptr(&jh7110_clock_event, cpu);
> +	struct jh7110_timer_priv *priv = &jh7110_timer_info;
> +	int ret;
> +	u32 rate;
> +
> +	if (cpu >= JH7110_TIMER_CH_MAX)
> +		return -ENOMEM;
> +
> +	ret = clk_prepare_enable(priv->clkevt[cpu].clk);
> +	if (ret)
> +		return ret;
> +
> +	ret = reset_control_deassert(priv->clkevt[cpu].rst);
> +	if (ret)
> +		return ret;
> +
> +	rate = clk_get_rate(priv->clkevt[cpu].clk);
> +	evt->cpumask = cpumask_of(cpu);
> +	evt->irq = priv->clkevt[cpu].irq;
> +	evt->name = priv->clkevt[cpu].name;
> +	clockevents_config_and_register(evt, rate, JH7110_TIMER_MIN_TICKS,
> +					JH7110_TIMER_MAX_TICKS);
> +
> +	ret = devm_request_irq(priv->dev, evt->irq, jh7110_timer_interrupt,
> +			       IRQF_TIMER | IRQF_IRQPOLL,
> +			       evt->name, evt);

How is this all supposed to work from a CPU hotplug state callback which
runs in the early bringup phase with interrupts disabled? clk_prepare()
which is invoked from clk_prepare_enable() can sleep and
devm_request_irq() can sleep too.

Did you ever test this with the required debug config options enabled?

  https://www.kernel.org/doc/html/latest/process/submit-checklist.html

Obviously not.

> +	if (ret)
> +		return ret;
> +
> +	ret = irq_set_affinity(evt->irq, cpumask_of(cpu));
> +	if (ret)
> +		return ret;
> +	/* Use one-shot mode */
> +	writel(JH7110_TIMER_MODE_SINGLE, (priv->clkevt[cpu].base + JH7110_TIMER_CTL));
> +
> +	return jh7110_timer_start(&priv->clkevt[cpu]);
> +}
> +
> +static void jh7110_timer_release(void *data)
> +{
> +	struct jh7110_timer_priv *priv = data;
> +	int i;
> +
> +	for (i = 0; i < JH7110_TIMER_CH_MAX; i++) {
> +		/* Disable each channel of timer */
> +		if (priv->clkevt[i].base)
> +			writel(JH7110_TIMER_DIS, priv->clkevt[i].base + JH7110_TIMER_ENABLE);
> +
> +		/* Avoid no initialization in the loop of the probe */
> +		if (!IS_ERR_OR_NULL(priv->clkevt[i].rst))
> +			reset_control_assert(priv->clkevt[i].rst);
> +
> +		if (!IS_ERR_OR_NULL(priv->clkevt[i].clk))
> +			clk_disable_unprepare(priv->clkevt[i].clk);

Same problem. And of course this does not undo the other steps of
jh7110_timer_starting_cpu() so if you offline a CPU and then online it
again the callback will fail because the clockevent is already
registered and the interrupt requested. Clearly untested too.

Thanks,

        tglx
diff mbox series

Patch

diff --git a/MAINTAINERS b/MAINTAINERS
index 9104430e148e..fe0e803606a5 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -20617,6 +20617,13 @@  S:	Maintained
 F:	Documentation/devicetree/bindings/sound/starfive,jh7110-tdm.yaml
 F:	sound/soc/starfive/jh7110_tdm.c
 
+STARFIVE JH7110 TIMER DRIVER
+M:	Samin Guo <samin.guo@starfivetech.com>
+M:	Xingyu Wu <xingyu.wu@starfivetech.com>
+S:	Supported
+F:	Documentation/devicetree/bindings/timer/starfive,jh7110-timer.yaml
+F:	drivers/clocksource/timer-jh7110.c
+
 STARFIVE JH71X0 CLOCK DRIVERS
 M:	Emil Renner Berthing <kernel@esmil.dk>
 M:	Hal Feng <hal.feng@starfivetech.com>
diff --git a/arch/riscv/Kconfig.socs b/arch/riscv/Kconfig.socs
index e08e91c49abe..e986590f40b0 100644
--- a/arch/riscv/Kconfig.socs
+++ b/arch/riscv/Kconfig.socs
@@ -35,6 +35,7 @@  config SOC_STARFIVE
 	select PINCTRL
 	select RESET_CONTROLLER
 	select ARM_AMBA
+	select STARFIVE_JH7110_TIMER
 	help
 	  This enables support for StarFive SoC platform hardware.
 
diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig
index 34faa0320ece..3f273f07a258 100644
--- a/drivers/clocksource/Kconfig
+++ b/drivers/clocksource/Kconfig
@@ -641,6 +641,15 @@  config RISCV_TIMER
 	  is accessed via both the SBI and the rdcycle instruction.  This is
 	  required for all RISC-V systems.
 
+config STARFIVE_JH7110_TIMER
+	bool "Timer for the STARFIVE JH7110 SoC" if COMPILE_TEST
+	select TIMER_OF
+	select CLKSRC_MMIO
+	help
+	  This enables the timer for StarFive JH7110 SoC. On RISC-V platform,
+	  the system has started RISCV_TIMER, but you can also use this timer
+	  which can provide four channels of higher rating on the JH7110 SoC.
+
 config CLINT_TIMER
 	bool "CLINT Timer for the RISC-V platform" if COMPILE_TEST
 	depends on GENERIC_SCHED_CLOCK && RISCV
diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile
index 4bb856e4df55..8dc2f0ea2d0f 100644
--- a/drivers/clocksource/Makefile
+++ b/drivers/clocksource/Makefile
@@ -80,6 +80,7 @@  obj-$(CONFIG_INGENIC_TIMER)		+= ingenic-timer.o
 obj-$(CONFIG_CLKSRC_ST_LPC)		+= clksrc_st_lpc.o
 obj-$(CONFIG_X86_NUMACHIP)		+= numachip.o
 obj-$(CONFIG_RISCV_TIMER)		+= timer-riscv.o
+obj-$(CONFIG_STARFIVE_JH7110_TIMER)	+= timer-jh7110.o
 obj-$(CONFIG_CLINT_TIMER)		+= timer-clint.o
 obj-$(CONFIG_CSKY_MP_TIMER)		+= timer-mp-csky.o
 obj-$(CONFIG_GX6605S_TIMER)		+= timer-gx6605s.o
diff --git a/drivers/clocksource/timer-jh7110.c b/drivers/clocksource/timer-jh7110.c
new file mode 100644
index 000000000000..bd93e71969c2
--- /dev/null
+++ b/drivers/clocksource/timer-jh7110.c
@@ -0,0 +1,360 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Starfive JH7110 Timer driver
+ *
+ * Copyright (C) 2023 StarFive Technology Co., Ltd.
+ *
+ * This timer has four free-running and independent 32-bit counters and runs in 24MHz
+ * clock on the StarFive JH7110 SoC. Each channel(counter) can trigger an interrupt
+ * when timeout even CPU is sleeping. They support one-shot mode and continuous-run mode.
+ *
+ * Each channel is used as a global timer that serves each cpu core:
+ * JH7110 Timer Channel 0 -- CPU 0
+ * JH7110 Timer Channel 1 -- CPU 1
+ * JH7110 Timer Channel 2 -- CPU 2
+ * JH7110 Timer Channel 3 -- CPU 3
+ */
+
+#include <linux/clk.h>
+#include <linux/clockchips.h>
+#include <linux/cpu.h>
+#include <linux/iopoll.h>
+#include <linux/irq.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+
+/* Bias: Ch0-0x0, Ch1-0x40, Ch2-0x80, and so on. */
+#define JH7110_TIMER_CH_LEN		0x40
+#define JH7110_TIMER_CH_BASE(x)		((x) * JH7110_TIMER_CH_LEN)
+#define JH7110_TIMER_CH_MAX		4
+
+#define JH7110_DELAY_US			0
+#define JH7110_TIMEOUT_US		10000
+#define JH7110_CLOCKEVENT_RATING	300
+#define JH7110_TIMER_MAX_TICKS		0xffffffff
+#define JH7110_TIMER_MIN_TICKS		0xf
+
+#define JH7110_TIMER_INT_STATUS		0x00 /*
+					      * RO[0:4]: Interrupt Status for channel0~4
+					      * Only valid on the JH7110_TIMER_CH_BASE(0)
+					      */
+#define JH7110_TIMER_CTL		0x04 /* RW[0]: 0-continuous run, 1-single run */
+#define JH7110_TIMER_LOAD		0x08 /* RW: load value to counter */
+#define JH7110_TIMER_ENABLE		0x10 /* RW[0]: timer enable register */
+#define JH7110_TIMER_RELOAD		0x14 /* RW: write 1 or 0 both reload counter */
+#define JH7110_TIMER_VALUE		0x18 /* RO: timer value register */
+#define JH7110_TIMER_INT_CLR		0x20 /* RW: timer interrupt clear register */
+#define JH7110_TIMER_INT_MASK		0x24 /* RW[0]: timer interrupt mask register */
+
+#define JH7110_TIMER_RELOAD_VALID	0
+#define JH7110_TIMER_INT_CLR_ENA	BIT(0)
+#define JH7110_TIMER_INT_CLR_AVA_MASK	BIT(1)
+
+#define JH7110_PERCPU_GET_CLKEVT	(&jh7110_timer_info.clkevt[smp_processor_id()])
+
+struct jh7110_clkevt {
+	struct clk *clk;
+	struct reset_control *rst;
+	void __iomem *base;
+	u32 reload_val;
+	int irq;
+	char name[sizeof("jh7110-timer.chX")];
+};
+
+struct jh7110_timer_priv {
+	struct reset_control *prst;
+	struct device *dev;
+	struct jh7110_clkevt clkevt[JH7110_TIMER_CH_MAX];
+};
+
+/* 0:continuous-run mode, 1:single-run mode */
+enum jh7110_timer_mode {
+	JH7110_TIMER_MODE_CONTIN,
+	JH7110_TIMER_MODE_SINGLE,
+};
+
+/* Interrupt Mask, 0:Unmask, 1:Mask */
+enum jh7110_timer_int_mask {
+	JH7110_TIMER_INT_ENA,
+	JH7110_TIMER_INT_DIS,
+};
+
+enum jh7110_timer_enable {
+	JH7110_TIMER_DIS,
+	JH7110_TIMER_ENA,
+};
+
+static struct jh7110_timer_priv jh7110_timer_info;
+
+/*
+ * BIT(0): Read value represent channel int status.
+ * Write 1 to this bit to clear interrupt. Write 0 has no effects.
+ * BIT(1): "1" means that it is clearing interrupt. BIT(0) can not be written.
+ */
+static int jh7110_timer_int_is_clearing(struct jh7110_clkevt *clkevt)
+{
+	u32 value;
+
+	/* Waiting interrupt can be cleared */
+	return readl_poll_timeout_atomic(clkevt->base + JH7110_TIMER_INT_CLR, value,
+					 !(value & JH7110_TIMER_INT_CLR_AVA_MASK),
+					 JH7110_DELAY_US, JH7110_TIMEOUT_US);
+}
+
+static int jh7110_timer_start(struct jh7110_clkevt *clkevt)
+{
+	int ret;
+
+	/* Disable and clear interrupt first */
+	writel(JH7110_TIMER_INT_DIS, clkevt->base + JH7110_TIMER_INT_MASK);
+	ret = jh7110_timer_int_is_clearing(clkevt);
+	if (ret)
+		return ret;
+
+	writel(JH7110_TIMER_INT_CLR_ENA, (clkevt->base + JH7110_TIMER_INT_CLR));
+	writel(JH7110_TIMER_INT_ENA, clkevt->base + JH7110_TIMER_INT_MASK);
+	writel(JH7110_TIMER_ENA, clkevt->base + JH7110_TIMER_ENABLE);
+
+	return 0;
+}
+
+static int jh7110_timer_shutdown(struct clock_event_device *evt)
+{
+	struct jh7110_clkevt *clkevt = JH7110_PERCPU_GET_CLKEVT;
+	int ret = 0;
+
+	writel(JH7110_TIMER_DIS, clkevt->base + JH7110_TIMER_ENABLE);
+	ret = jh7110_timer_int_is_clearing(clkevt);
+	if (!ret)
+		writel(JH7110_TIMER_INT_CLR_ENA, (clkevt->base + JH7110_TIMER_INT_CLR));
+
+	return ret;
+}
+
+static void jh7110_timer_suspend(struct clock_event_device *evt)
+{
+	struct jh7110_clkevt *clkevt = JH7110_PERCPU_GET_CLKEVT;
+
+	clkevt->reload_val = readl(clkevt->base + JH7110_TIMER_LOAD);
+	jh7110_timer_shutdown(evt);
+}
+
+static int jh7110_timer_tick_resume(struct clock_event_device *evt)
+{
+	struct jh7110_clkevt *clkevt = JH7110_PERCPU_GET_CLKEVT;
+
+	writel(clkevt->reload_val, clkevt->base + JH7110_TIMER_LOAD);
+	writel(JH7110_TIMER_RELOAD_VALID, clkevt->base + JH7110_TIMER_RELOAD);
+	return jh7110_timer_start(clkevt);
+}
+
+static void jh7110_timer_resume(struct clock_event_device *evt)
+{
+	jh7110_timer_tick_resume(evt);
+}
+
+/* IRQ handler for the timer */
+static irqreturn_t jh7110_timer_interrupt(int irq, void *data)
+{
+	struct clock_event_device *evt = data;
+	struct jh7110_clkevt *clkevt = &jh7110_timer_info.clkevt[0];
+	u8 cpu_id = smp_processor_id();
+	u32 reg = readl(clkevt->base + JH7110_TIMER_INT_STATUS);
+
+	/* Check interrupt status and channel(cpu) ID */
+	if (!(reg & BIT(cpu_id)))
+		return IRQ_NONE;
+
+	clkevt = &jh7110_timer_info.clkevt[cpu_id];
+	writel(JH7110_TIMER_INT_CLR_ENA, (clkevt->base + JH7110_TIMER_INT_CLR));
+
+	if (evt->event_handler)
+		evt->event_handler(evt);
+
+	return IRQ_HANDLED;
+}
+
+static int jh7110_timer_set_periodic(struct clock_event_device *evt)
+{
+	struct jh7110_clkevt *clkevt = JH7110_PERCPU_GET_CLKEVT;
+
+	writel(JH7110_TIMER_MODE_CONTIN, clkevt->base + JH7110_TIMER_CTL);
+	return 0;
+}
+
+static int jh7110_timer_set_oneshot(struct clock_event_device *evt)
+{
+	struct jh7110_clkevt *clkevt = JH7110_PERCPU_GET_CLKEVT;
+
+	writel(JH7110_TIMER_MODE_SINGLE, clkevt->base + JH7110_TIMER_CTL);
+	return 0;
+}
+
+static int jh7110_timer_set_next_event(unsigned long next,
+				       struct clock_event_device *evt)
+{
+	struct jh7110_clkevt *clkevt = JH7110_PERCPU_GET_CLKEVT;
+
+	writel(JH7110_TIMER_MODE_SINGLE, clkevt->base + JH7110_TIMER_CTL);
+	writel(next, clkevt->base + JH7110_TIMER_LOAD);
+
+	return jh7110_timer_start(clkevt);
+}
+
+static DEFINE_PER_CPU(struct clock_event_device, jh7110_clock_event) = {
+	.features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
+	.rating = JH7110_CLOCKEVENT_RATING,
+	.set_state_shutdown = jh7110_timer_shutdown,
+	.set_state_periodic = jh7110_timer_set_periodic,
+	.set_state_oneshot = jh7110_timer_set_oneshot,
+	.set_state_oneshot_stopped = jh7110_timer_shutdown,
+	.tick_resume = jh7110_timer_tick_resume,
+	.set_next_event = jh7110_timer_set_next_event,
+	.suspend = jh7110_timer_suspend,
+	.resume = jh7110_timer_resume,
+};
+
+static int jh7110_timer_starting_cpu(unsigned int cpu)
+{
+	struct clock_event_device *evt = per_cpu_ptr(&jh7110_clock_event, cpu);
+	struct jh7110_timer_priv *priv = &jh7110_timer_info;
+	int ret;
+	u32 rate;
+
+	if (cpu >= JH7110_TIMER_CH_MAX)
+		return -ENOMEM;
+
+	ret = clk_prepare_enable(priv->clkevt[cpu].clk);
+	if (ret)
+		return ret;
+
+	ret = reset_control_deassert(priv->clkevt[cpu].rst);
+	if (ret)
+		return ret;
+
+	rate = clk_get_rate(priv->clkevt[cpu].clk);
+	evt->cpumask = cpumask_of(cpu);
+	evt->irq = priv->clkevt[cpu].irq;
+	evt->name = priv->clkevt[cpu].name;
+	clockevents_config_and_register(evt, rate, JH7110_TIMER_MIN_TICKS,
+					JH7110_TIMER_MAX_TICKS);
+
+	ret = devm_request_irq(priv->dev, evt->irq, jh7110_timer_interrupt,
+			       IRQF_TIMER | IRQF_IRQPOLL,
+			       evt->name, evt);
+	if (ret)
+		return ret;
+
+	ret = irq_set_affinity(evt->irq, cpumask_of(cpu));
+	if (ret)
+		return ret;
+
+	/* Use one-shot mode */
+	writel(JH7110_TIMER_MODE_SINGLE, (priv->clkevt[cpu].base + JH7110_TIMER_CTL));
+
+	return jh7110_timer_start(&priv->clkevt[cpu]);
+}
+
+static void jh7110_timer_release(void *data)
+{
+	struct jh7110_timer_priv *priv = data;
+	int i;
+
+	for (i = 0; i < JH7110_TIMER_CH_MAX; i++) {
+		/* Disable each channel of timer */
+		if (priv->clkevt[i].base)
+			writel(JH7110_TIMER_DIS, priv->clkevt[i].base + JH7110_TIMER_ENABLE);
+
+		/* Avoid no initialization in the loop of the probe */
+		if (!IS_ERR_OR_NULL(priv->clkevt[i].rst))
+			reset_control_assert(priv->clkevt[i].rst);
+
+		if (!IS_ERR_OR_NULL(priv->clkevt[i].clk))
+			clk_disable_unprepare(priv->clkevt[i].clk);
+	}
+
+	reset_control_assert(priv->prst);
+}
+
+static int jh7110_timer_probe(struct platform_device *pdev)
+{
+	struct jh7110_timer_priv *priv = &jh7110_timer_info;
+	struct jh7110_clkevt *clkevt;
+	struct clk *pclk;
+	void __iomem *base;
+	int ch;
+	int ret;
+	char name[sizeof("chX")];
+
+	base = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(base))
+		return dev_err_probe(&pdev->dev, PTR_ERR(base),
+				     "failed to map registers\n");
+
+	priv->prst = devm_reset_control_get_exclusive(&pdev->dev, "apb");
+	if (IS_ERR(priv->prst))
+		return dev_err_probe(&pdev->dev, PTR_ERR(priv->prst),
+				     "failed to get apb reset\n");
+
+	pclk = devm_clk_get_enabled(&pdev->dev, "apb");
+	if (IS_ERR(pclk))
+		return dev_err_probe(&pdev->dev, PTR_ERR(pclk),
+				     "failed to get & enable apb clock\n");
+
+	ret = reset_control_deassert(priv->prst);
+	if (ret)
+		return dev_err_probe(&pdev->dev, ret, "failed to deassert apb reset\n");
+
+	ret = devm_add_action_or_reset(&pdev->dev, jh7110_timer_release, priv);
+	if (ret)
+		return ret;
+
+	priv->dev = &pdev->dev;
+	for (ch = 0; ch < JH7110_TIMER_CH_MAX; ch++) {
+		clkevt = &priv->clkevt[ch];
+		snprintf(name, sizeof(name), "ch%d", ch);
+
+		clkevt->base = base + JH7110_TIMER_CH_BASE(ch);
+		/* Ensure timer is disabled */
+		writel(JH7110_TIMER_DIS, clkevt->base + JH7110_TIMER_ENABLE);
+		if (!jh7110_timer_int_is_clearing(clkevt))
+			writel(JH7110_TIMER_INT_CLR_ENA, (clkevt->base + JH7110_TIMER_INT_CLR));
+
+		clkevt->rst = devm_reset_control_get_exclusive(&pdev->dev, name);
+		if (IS_ERR(clkevt->rst))
+			return PTR_ERR(clkevt->rst);
+
+		clkevt->clk = devm_clk_get(&pdev->dev, name);
+		if (IS_ERR(clkevt->clk))
+			return PTR_ERR(clkevt->clk);
+
+		clkevt->irq = platform_get_irq(pdev, ch);
+		if (clkevt->irq < 0)
+			return clkevt->irq;
+
+		snprintf(clkevt->name, sizeof(clkevt->name), "jh7110-timer.ch%d", ch);
+	}
+
+	return cpuhp_setup_state(CPUHP_AP_JH7110_TIMER_STARTING,
+				 "clockevents/jh7110/timer:starting",
+				 jh7110_timer_starting_cpu, NULL);
+}
+
+static const struct of_device_id jh7110_timer_match[] = {
+	{ .compatible = "starfive,jh7110-timer", },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, jh7110_timer_match);
+
+static struct platform_driver jh7110_timer_driver = {
+	.probe = jh7110_timer_probe,
+	.driver = {
+		.name = "jh7110-timer",
+		.of_match_table = jh7110_timer_match,
+	},
+};
+module_platform_driver(jh7110_timer_driver);
+
+MODULE_AUTHOR("Xingyu Wu <xingyu.wu@starfivetech.com>");
+MODULE_DESCRIPTION("StarFive JH7110 timer driver");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/cpuhotplug.h b/include/linux/cpuhotplug.h
index efc0c0b07efb..f28a3aaf93f9 100644
--- a/include/linux/cpuhotplug.h
+++ b/include/linux/cpuhotplug.h
@@ -187,6 +187,7 @@  enum cpuhp_state {
 	CPUHP_AP_CSKY_TIMER_STARTING,
 	CPUHP_AP_TI_GP_TIMER_STARTING,
 	CPUHP_AP_HYPERV_TIMER_STARTING,
+	CPUHP_AP_JH7110_TIMER_STARTING,
 	/* Must be the last timer callback */
 	CPUHP_AP_DUMMY_TIMER_STARTING,
 	CPUHP_AP_ARM_XEN_STARTING,