diff mbox series

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

Message ID 20240412084543.7243-3-ziv.xu@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

Ziv Xu April 12, 2024, 8:45 a.m. UTC
From: Xingyu Wu <xingyu.wu@starfivetech.com>

Add timer driver for the StarFive JH7110 SoC.

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: Ziv Xu <ziv.xu@starfivetech.com>
Signed-off-by: Xingyu Wu <xingyu.wu@starfivetech.com>
---
 MAINTAINERS                        |   7 +
 drivers/clocksource/Kconfig        |  11 +
 drivers/clocksource/Makefile       |   1 +
 drivers/clocksource/timer-jh7110.c | 345 +++++++++++++++++++++++++++++
 include/linux/cpuhotplug.h         |   1 +
 5 files changed, 365 insertions(+)
 create mode 100644 drivers/clocksource/timer-jh7110.c

Comments

Ziv Xu April 30, 2024, 6:21 a.m. UTC | #1
> -----邮件原件-----
> 发件人: Ziv Xu
> 发送时间: 2024年4月12日 16:46
> 收件人: Daniel Lezcano <daniel.lezcano@linaro.org>; Thomas Gleixner
> <tglx@linutronix.de>; Emil Renner Berthing
> <emil.renner.berthing@canonical.com>; Christophe JAILLET
> <christophe.jaillet@wanadoo.fr>
> 抄送: linux-riscv@lists.infradead.org; devicetree@vger.kernel.org; Rob Herring
> <robh+dt@kernel.org>; Krzysztof Kozlowski
> <krzysztof.kozlowski+dt@linaro.org>; Paul Walmsley
> <paul.walmsley@sifive.com>; Palmer Dabbelt <palmer@dabbelt.com>; Albert
> Ou <aou@eecs.berkeley.edu>; Philipp Zabel <p.zabel@pengutronix.de>; Walker
> Chen <walker.chen@starfivetech.com>; Xingyu Wu
> <xingyu.wu@starfivetech.com>; linux-kernel@vger.kernel.org; Conor Dooley
> <conor@kernel.org>
> 主题: [PATCH v10 2/3] clocksource: Add JH7110 timer driver
> 
> From: Xingyu Wu <xingyu.wu@starfivetech.com>
> 
> Add timer driver for the StarFive JH7110 SoC.
> 
> 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: Ziv Xu <ziv.xu@starfivetech.com>
> Signed-off-by: Xingyu Wu <xingyu.wu@starfivetech.com>
> ---
>  MAINTAINERS                        |   7 +
>  drivers/clocksource/Kconfig        |  11 +
>  drivers/clocksource/Makefile       |   1 +
>  drivers/clocksource/timer-jh7110.c | 345 +++++++++++++++++++++++++++++
>  include/linux/cpuhotplug.h         |   1 +
>  5 files changed, 365 insertions(+)
>  create mode 100644 drivers/clocksource/timer-jh7110.c
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 7c121493f43d..ef9b5f5bad9e 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -21043,6 +21043,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/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig index
> 34faa0320ece..2dc97201dee1 100644
> --- a/drivers/clocksource/Kconfig
> +++ b/drivers/clocksource/Kconfig
> @@ -641,6 +641,17 @@ 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"
> +	depends on ARCH_STARFIVE || COMPILE_TEST
> +	select TIMER_OF
> +	select CLKSRC_MMIO
> +	default ARCH_STARFIVE
> +	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 to do a lot more things on 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..dc770507f209
> --- /dev/null
> +++ b/drivers/clocksource/timer-jh7110.c
> @@ -0,0 +1,345 @@
> +// 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 */
> +#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_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 - Description of each timer channel
> + * @clk:		Clock of each timer channel
> + * @rst:		Reset of each timer channel
> + * @base:		Virtual address of each timer channel
> + * @irq:                Interrupt number of each timer channel
> + * @timer_enabled:      Enabled flag for each timer channel
> + * @name:		Name of each timer channel
> + */
> +struct jh7110_clkevt {
> +	struct clk		*clk;
> +	struct reset_control	*rst;
> +	void __iomem		*base;
> +	int			irq;
> +	bool			timer_enabled;
> +	char			name[sizeof("jh7110-timer.chX")];
> +};
> +
> +struct jh7110_timer_priv {
> +	struct clk		*pclk;
> +	struct reset_control	*prst;
> +	struct device		*dev;
> +	struct jh7110_clkevt	clkevt[JH7110_TIMER_CH_MAX];
> +};
> +
> +static struct jh7110_timer_priv jh7110_timer_info;
> +
> +/* 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,
> +};
> +
> +/*
> + * 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 inline int jh7110_timer_int_clear(struct jh7110_clkevt *clkevt)
> +{
> +	u32 value;
> +	int ret;
> +
> +	/* Waiting interrupt can be cleared */
> +	ret = readl_poll_timeout_atomic(clkevt->base + JH7110_TIMER_INT_CLR,
> value,
> +					!(value & JH7110_TIMER_INT_CLR_AVA_MASK),
> +					JH7110_DELAY_US, JH7110_TIMEOUT_US);
> +	if (!ret)
> +		writel(JH7110_TIMER_INT_CLR_ENA, clkevt->base +
> +JH7110_TIMER_INT_CLR);
> +
> +	return ret;
> +}
> +
> +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_clear(clkevt);
> +
> +	writel(JH7110_TIMER_INT_ENA, clkevt->base +
> JH7110_TIMER_INT_MASK);
> +	writel(JH7110_TIMER_ENA, clkevt->base + JH7110_TIMER_ENABLE);
> +
> +	return ret;
> +}
> +
> +static int jh7110_timer_shutdown(struct clock_event_device *evt) {
> +	struct jh7110_clkevt *clkevt = JH7110_PERCPU_GET_CLKEVT;
> +
> +	writel(JH7110_TIMER_DIS, clkevt->base + JH7110_TIMER_ENABLE);
> +	return jh7110_timer_int_clear(clkevt); }
> +
> +/* IRQ handler for the timer */
> +static irqreturn_t jh7110_timer_interrupt(int irq, void *data) {
> +	struct clock_event_device *evt = (struct clock_event_device *)data;
> +	struct jh7110_clkevt *clkevt = &jh7110_timer_info.clkevt[0];
> +	u32 reg = readl(clkevt->base + JH7110_TIMER_INT_STATUS);
> +	u8 cpu_id = smp_processor_id();
> +
> +	/* 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,
> +	.set_next_event			= jh7110_timer_set_next_event,
> +};
> +
> +static int jh7110_timer_dying_cpu(unsigned int cpu) {
> +	struct jh7110_timer_priv *priv = &jh7110_timer_info;
> +
> +	if (!priv->clkevt[cpu].timer_enabled)
> +		return 0;
> +
> +	writel(JH7110_TIMER_DIS, priv->clkevt[cpu].base +
> JH7110_TIMER_ENABLE);
> +	jh7110_timer_int_clear(&priv->clkevt[cpu]);
> +	reset_control_assert(priv->clkevt[cpu].rst);
> +	clk_disable_unprepare(priv->clkevt[cpu].clk);
> +
> +	return 0;
> +}
> +
> +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 err;
> +	u32 rate;
> +
> +	if (cpu >= JH7110_TIMER_CH_MAX)
> +		return -ENOMEM;
> +
> +	err = clk_prepare_enable(priv->clkevt[cpu].clk);
> +	if (err)
> +		goto err_starting_cpu;
> +
> +	err = reset_control_deassert(priv->clkevt[cpu].rst);
> +	if (err)
> +		goto err_soft_reset;
> +
> +	rate = clk_get_rate(priv->clkevt[cpu].clk);
> +	evt->cpumask = cpumask_of(cpu);
> +	evt->irq = priv->clkevt[cpu].irq;
> +
> +	err = irq_force_affinity(evt->irq, cpumask_of(cpu));
> +	if (err)
> +		goto err_affinity;
> +
> +	clockevents_config_and_register(evt, rate, JH7110_TIMER_MIN_TICKS,
> +					JH7110_TIMER_MAX_TICKS);
> +
> +	/* Use one-shot mode */
> +	writel(JH7110_TIMER_MODE_SINGLE, (priv->clkevt[cpu].base +
> +JH7110_TIMER_CTL));
> +
> +	priv->clkevt[cpu].timer_enabled = true;
> +
> +	err = jh7110_timer_start(&priv->clkevt[cpu]);
> +	if (err)
> +		goto err_affinity;
> +	return 0;
> +
> +err_affinity:
> +	reset_control_assert(priv->clkevt[cpu].rst);
> +err_soft_reset:
> +	clk_disable_unprepare(priv->clkevt[cpu].clk);
> +err_starting_cpu:
> +	free_irq(evt->irq, evt);
> +	return err;
> +}
> +
> +static int jh7110_timer_probe(struct platform_device *pdev) {
> +	struct jh7110_timer_priv *priv = &jh7110_timer_info;
> +	struct clock_event_device *evt;
> +	struct jh7110_clkevt *clkevt;
> +	char name[sizeof("chX")];
> +	int ch;
> +	int ret;
> +	void __iomem *base;
> +
> +	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");
> +
> +	priv->pclk = devm_clk_get_enabled(&pdev->dev, "apb");
> +	if (IS_ERR(priv->pclk))
> +		return dev_err_probe(&pdev->dev, PTR_ERR(priv->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");
> +
> +	for (ch = 0; ch < JH7110_TIMER_CH_MAX; ch++) {
> +		evt = per_cpu_ptr(&jh7110_clock_event, 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);
> +		ret = jh7110_timer_int_clear(clkevt);
> +		if (ret)
> +			return ret;
> +
> +		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);
> +		ret = devm_request_irq(&pdev->dev, clkevt->irq,
> jh7110_timer_interrupt,
> +				       IRQF_TIMER | IRQF_IRQPOLL,
> +				       clkevt->name, evt);
> +
> +		if (ret)
> +			return ret;
> +
> +		clkevt->timer_enabled = false;
> +	}
> +
> +	return cpuhp_setup_state(CPUHP_AP_JH7110_TIMER_STARTING,
> +				"clockevents/jh7110/timer:starting",
> +				jh7110_timer_starting_cpu, jh7110_timer_dying_cpu); }
> +
> +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
> 35e78ddb2b37..4a8b487c327e 100644
> --- a/include/linux/cpuhotplug.h
> +++ b/include/linux/cpuhotplug.h
> @@ -175,6 +175,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,
> --
> 2.17.1

Hi Daniel / Thomas

I have submitted new version of patch for jh7110 timer driver. Could you please help to review and give your comments? 
Thanks a lot!

Best regards,
Ziv.Xu
Emil Renner Berthing April 30, 2024, 10:18 a.m. UTC | #2
Ziv Xu wrote:
>
>
> > -----邮件原件-----
> > 发件人: Ziv Xu
> > 发送时间: 2024年4月12日 16:46
> > 收件人: Daniel Lezcano <daniel.lezcano@linaro.org>; Thomas Gleixner
> > <tglx@linutronix.de>; Emil Renner Berthing
> > <emil.renner.berthing@canonical.com>; Christophe JAILLET
> > <christophe.jaillet@wanadoo.fr>
> > 抄送: linux-riscv@lists.infradead.org; devicetree@vger.kernel.org; Rob Herring
> > <robh+dt@kernel.org>; Krzysztof Kozlowski
> > <krzysztof.kozlowski+dt@linaro.org>; Paul Walmsley
> > <paul.walmsley@sifive.com>; Palmer Dabbelt <palmer@dabbelt.com>; Albert
> > Ou <aou@eecs.berkeley.edu>; Philipp Zabel <p.zabel@pengutronix.de>; Walker
> > Chen <walker.chen@starfivetech.com>; Xingyu Wu
> > <xingyu.wu@starfivetech.com>; linux-kernel@vger.kernel.org; Conor Dooley
> > <conor@kernel.org>
> > 主题: [PATCH v10 2/3] clocksource: Add JH7110 timer driver
> >
> > From: Xingyu Wu <xingyu.wu@starfivetech.com>
> >
> > Add timer driver for the StarFive JH7110 SoC.
> >
> > 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: Ziv Xu <ziv.xu@starfivetech.com>
> > Signed-off-by: Xingyu Wu <xingyu.wu@starfivetech.com>
> > ---
> >  MAINTAINERS                        |   7 +
> >  drivers/clocksource/Kconfig        |  11 +
> >  drivers/clocksource/Makefile       |   1 +
> >  drivers/clocksource/timer-jh7110.c | 345 +++++++++++++++++++++++++++++
> >  include/linux/cpuhotplug.h         |   1 +
> >  5 files changed, 365 insertions(+)
> >  create mode 100644 drivers/clocksource/timer-jh7110.c
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index 7c121493f43d..ef9b5f5bad9e 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -21043,6 +21043,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/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig index
> > 34faa0320ece..2dc97201dee1 100644
> > --- a/drivers/clocksource/Kconfig
> > +++ b/drivers/clocksource/Kconfig
> > @@ -641,6 +641,17 @@ 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"
> > +	depends on ARCH_STARFIVE || COMPILE_TEST
> > +	select TIMER_OF
> > +	select CLKSRC_MMIO
> > +	default ARCH_STARFIVE
> > +	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 to do a lot more things on 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..dc770507f209
> > --- /dev/null
> > +++ b/drivers/clocksource/timer-jh7110.c
> > @@ -0,0 +1,345 @@
> > +// 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 */
> > +#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_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 - Description of each timer channel
> > + * @clk:		Clock of each timer channel
> > + * @rst:		Reset of each timer channel
> > + * @base:		Virtual address of each timer channel
> > + * @irq:                Interrupt number of each timer channel
> > + * @timer_enabled:      Enabled flag for each timer channel
> > + * @name:		Name of each timer channel
> > + */
> > +struct jh7110_clkevt {
> > +	struct clk		*clk;
> > +	struct reset_control	*rst;
> > +	void __iomem		*base;
> > +	int			irq;
> > +	bool			timer_enabled;
> > +	char			name[sizeof("jh7110-timer.chX")];
> > +};
> > +
> > +struct jh7110_timer_priv {
> > +	struct clk		*pclk;
> > +	struct reset_control	*prst;
> > +	struct device		*dev;
> > +	struct jh7110_clkevt	clkevt[JH7110_TIMER_CH_MAX];
> > +};
> > +
> > +static struct jh7110_timer_priv jh7110_timer_info;
> > +
> > +/* 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,
> > +};
> > +
> > +/*
> > + * 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 inline int jh7110_timer_int_clear(struct jh7110_clkevt *clkevt)
> > +{
> > +	u32 value;
> > +	int ret;
> > +
> > +	/* Waiting interrupt can be cleared */
> > +	ret = readl_poll_timeout_atomic(clkevt->base + JH7110_TIMER_INT_CLR,
> > value,
> > +					!(value & JH7110_TIMER_INT_CLR_AVA_MASK),
> > +					JH7110_DELAY_US, JH7110_TIMEOUT_US);
> > +	if (!ret)
> > +		writel(JH7110_TIMER_INT_CLR_ENA, clkevt->base +
> > +JH7110_TIMER_INT_CLR);
> > +
> > +	return ret;
> > +}
> > +
> > +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_clear(clkevt);
> > +
> > +	writel(JH7110_TIMER_INT_ENA, clkevt->base +
> > JH7110_TIMER_INT_MASK);
> > +	writel(JH7110_TIMER_ENA, clkevt->base + JH7110_TIMER_ENABLE);
> > +
> > +	return ret;
> > +}
> > +
> > +static int jh7110_timer_shutdown(struct clock_event_device *evt) {
> > +	struct jh7110_clkevt *clkevt = JH7110_PERCPU_GET_CLKEVT;
> > +
> > +	writel(JH7110_TIMER_DIS, clkevt->base + JH7110_TIMER_ENABLE);
> > +	return jh7110_timer_int_clear(clkevt); }
> > +
> > +/* IRQ handler for the timer */
> > +static irqreturn_t jh7110_timer_interrupt(int irq, void *data) {
> > +	struct clock_event_device *evt = (struct clock_event_device *)data;
> > +	struct jh7110_clkevt *clkevt = &jh7110_timer_info.clkevt[0];
> > +	u32 reg = readl(clkevt->base + JH7110_TIMER_INT_STATUS);
> > +	u8 cpu_id = smp_processor_id();
> > +
> > +	/* 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,
> > +	.set_next_event			= jh7110_timer_set_next_event,
> > +};
> > +
> > +static int jh7110_timer_dying_cpu(unsigned int cpu) {
> > +	struct jh7110_timer_priv *priv = &jh7110_timer_info;
> > +
> > +	if (!priv->clkevt[cpu].timer_enabled)
> > +		return 0;
> > +
> > +	writel(JH7110_TIMER_DIS, priv->clkevt[cpu].base +
> > JH7110_TIMER_ENABLE);
> > +	jh7110_timer_int_clear(&priv->clkevt[cpu]);
> > +	reset_control_assert(priv->clkevt[cpu].rst);
> > +	clk_disable_unprepare(priv->clkevt[cpu].clk);
> > +
> > +	return 0;
> > +}
> > +
> > +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 err;
> > +	u32 rate;
> > +
> > +	if (cpu >= JH7110_TIMER_CH_MAX)
> > +		return -ENOMEM;
> > +
> > +	err = clk_prepare_enable(priv->clkevt[cpu].clk);
> > +	if (err)
> > +		goto err_starting_cpu;
> > +
> > +	err = reset_control_deassert(priv->clkevt[cpu].rst);
> > +	if (err)
> > +		goto err_soft_reset;
> > +
> > +	rate = clk_get_rate(priv->clkevt[cpu].clk);
> > +	evt->cpumask = cpumask_of(cpu);
> > +	evt->irq = priv->clkevt[cpu].irq;
> > +
> > +	err = irq_force_affinity(evt->irq, cpumask_of(cpu));
> > +	if (err)
> > +		goto err_affinity;
> > +
> > +	clockevents_config_and_register(evt, rate, JH7110_TIMER_MIN_TICKS,
> > +					JH7110_TIMER_MAX_TICKS);
> > +
> > +	/* Use one-shot mode */
> > +	writel(JH7110_TIMER_MODE_SINGLE, (priv->clkevt[cpu].base +
> > +JH7110_TIMER_CTL));
> > +
> > +	priv->clkevt[cpu].timer_enabled = true;
> > +
> > +	err = jh7110_timer_start(&priv->clkevt[cpu]);
> > +	if (err)
> > +		goto err_affinity;
> > +	return 0;
> > +
> > +err_affinity:
> > +	reset_control_assert(priv->clkevt[cpu].rst);
> > +err_soft_reset:
> > +	clk_disable_unprepare(priv->clkevt[cpu].clk);
> > +err_starting_cpu:
> > +	free_irq(evt->irq, evt);
> > +	return err;
> > +}
> > +
> > +static int jh7110_timer_probe(struct platform_device *pdev) {
> > +	struct jh7110_timer_priv *priv = &jh7110_timer_info;
> > +	struct clock_event_device *evt;
> > +	struct jh7110_clkevt *clkevt;
> > +	char name[sizeof("chX")];
> > +	int ch;
> > +	int ret;
> > +	void __iomem *base;
> > +
> > +	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");
> > +
> > +	priv->pclk = devm_clk_get_enabled(&pdev->dev, "apb");
> > +	if (IS_ERR(priv->pclk))
> > +		return dev_err_probe(&pdev->dev, PTR_ERR(priv->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");
> > +
> > +	for (ch = 0; ch < JH7110_TIMER_CH_MAX; ch++) {
> > +		evt = per_cpu_ptr(&jh7110_clock_event, 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);
> > +		ret = jh7110_timer_int_clear(clkevt);
> > +		if (ret)
> > +			return ret;
> > +
> > +		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);
> > +		ret = devm_request_irq(&pdev->dev, clkevt->irq,
> > jh7110_timer_interrupt,
> > +				       IRQF_TIMER | IRQF_IRQPOLL,
> > +				       clkevt->name, evt);
> > +
> > +		if (ret)
> > +			return ret;
> > +
> > +		clkevt->timer_enabled = false;
> > +	}
> > +
> > +	return cpuhp_setup_state(CPUHP_AP_JH7110_TIMER_STARTING,
> > +				"clockevents/jh7110/timer:starting",
> > +				jh7110_timer_starting_cpu, jh7110_timer_dying_cpu); }
> > +
> > +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
> > 35e78ddb2b37..4a8b487c327e 100644
> > --- a/include/linux/cpuhotplug.h
> > +++ b/include/linux/cpuhotplug.h
> > @@ -175,6 +175,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,
> > --
> > 2.17.1
>
> Hi Daniel / Thomas
>
> I have submitted new version of patch for jh7110 timer driver. Could you please help to review and give your comments?
> Thanks a lot!

Hi Ziv

I tried this on 6.9-rc6 on my VF2. It boots, but very slowly and "choppy". That
is it repeatedly runs for <1s and then hangs for about 4s.

Does this patch work for you?

/Emil
Ziv Xu May 7, 2024, 8:37 a.m. UTC | #3
> -----邮件原件-----
> 发件人: Emil Renner Berthing <emil.renner.berthing@canonical.com>
> 发送时间: 2024年4月30日 18:19
> 收件人: Ziv Xu <ziv.xu@starfivetech.com>; Daniel Lezcano
> <daniel.lezcano@linaro.org>; Thomas Gleixner <tglx@linutronix.de>
> 抄送: linux-riscv@lists.infradead.org; devicetree@vger.kernel.org; Rob Herring
> <robh+dt@kernel.org>; Krzysztof Kozlowski
> <krzysztof.kozlowski+dt@linaro.org>; Paul Walmsley
> <paul.walmsley@sifive.com>; Palmer Dabbelt <palmer@dabbelt.com>; Albert
> Ou <aou@eecs.berkeley.edu>; Philipp Zabel <p.zabel@pengutronix.de>; Walker
> Chen <walker.chen@starfivetech.com>; Xingyu Wu
> <xingyu.wu@starfivetech.com>; linux-kernel@vger.kernel.org; Conor Dooley
> <conor@kernel.org>
> 主题: Re: 回复: [PATCH v10 2/3] clocksource: Add JH7110 timer driver
> 
> Ziv Xu wrote:
> >
> >
> > > -----邮件原件-----
> > > 发件人: Ziv Xu
> > > 发送时间: 2024年4月12日 16:46
> > > 收件人: Daniel Lezcano <daniel.lezcano@linaro.org>; Thomas Gleixner
> > > <tglx@linutronix.de>; Emil Renner Berthing
> > > <emil.renner.berthing@canonical.com>; Christophe JAILLET
> > > <christophe.jaillet@wanadoo.fr>
> > > 抄送: linux-riscv@lists.infradead.org; devicetree@vger.kernel.org; Rob
> > > Herring <robh+dt@kernel.org>; Krzysztof Kozlowski
> > > <krzysztof.kozlowski+dt@linaro.org>; Paul Walmsley
> > > <paul.walmsley@sifive.com>; Palmer Dabbelt <palmer@dabbelt.com>;
> > > Albert Ou <aou@eecs.berkeley.edu>; Philipp Zabel
> > > <p.zabel@pengutronix.de>; Walker Chen
> > > <walker.chen@starfivetech.com>; Xingyu Wu
> > > <xingyu.wu@starfivetech.com>; linux-kernel@vger.kernel.org; Conor
> > > Dooley <conor@kernel.org>
> > > 主题: [PATCH v10 2/3] clocksource: Add JH7110 timer driver
> > >
> > > From: Xingyu Wu <xingyu.wu@starfivetech.com>
> > >
> > > Add timer driver for the StarFive JH7110 SoC.
> > >
> > > 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: Ziv Xu <ziv.xu@starfivetech.com>
> > > Signed-off-by: Xingyu Wu <xingyu.wu@starfivetech.com>
> > > ---
> > >  MAINTAINERS                        |   7 +
> > >  drivers/clocksource/Kconfig        |  11 +
> > >  drivers/clocksource/Makefile       |   1 +
> > >  drivers/clocksource/timer-jh7110.c | 345
> +++++++++++++++++++++++++++++
> > >  include/linux/cpuhotplug.h         |   1 +
> > >  5 files changed, 365 insertions(+)
> > >  create mode 100644 drivers/clocksource/timer-jh7110.c
> > >
> > > diff --git a/MAINTAINERS b/MAINTAINERS index
> > > 7c121493f43d..ef9b5f5bad9e 100644
> > > --- a/MAINTAINERS
> > > +++ b/MAINTAINERS
> > > @@ -21043,6 +21043,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/drivers/clocksource/Kconfig
> > > b/drivers/clocksource/Kconfig index
> > > 34faa0320ece..2dc97201dee1 100644
> > > --- a/drivers/clocksource/Kconfig
> > > +++ b/drivers/clocksource/Kconfig
> > > @@ -641,6 +641,17 @@ 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"
> > > +	depends on ARCH_STARFIVE || COMPILE_TEST
> > > +	select TIMER_OF
> > > +	select CLKSRC_MMIO
> > > +	default ARCH_STARFIVE
> > > +	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 to do a lot more things on 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..dc770507f209
> > > --- /dev/null
> > > +++ b/drivers/clocksource/timer-jh7110.c
> > > @@ -0,0 +1,345 @@
> > > +// 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 */
> > > +#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_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 - Description of each timer channel
> > > + * @clk:		Clock of each timer channel
> > > + * @rst:		Reset of each timer channel
> > > + * @base:		Virtual address of each timer channel
> > > + * @irq:                Interrupt number of each timer channel
> > > + * @timer_enabled:      Enabled flag for each timer channel
> > > + * @name:		Name of each timer channel
> > > + */
> > > +struct jh7110_clkevt {
> > > +	struct clk		*clk;
> > > +	struct reset_control	*rst;
> > > +	void __iomem		*base;
> > > +	int			irq;
> > > +	bool			timer_enabled;
> > > +	char			name[sizeof("jh7110-timer.chX")];
> > > +};
> > > +
> > > +struct jh7110_timer_priv {
> > > +	struct clk		*pclk;
> > > +	struct reset_control	*prst;
> > > +	struct device		*dev;
> > > +	struct jh7110_clkevt	clkevt[JH7110_TIMER_CH_MAX];
> > > +};
> > > +
> > > +static struct jh7110_timer_priv jh7110_timer_info;
> > > +
> > > +/* 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,
> > > +};
> > > +
> > > +/*
> > > + * 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 inline int jh7110_timer_int_clear(struct jh7110_clkevt
> > > +*clkevt) {
> > > +	u32 value;
> > > +	int ret;
> > > +
> > > +	/* Waiting interrupt can be cleared */
> > > +	ret = readl_poll_timeout_atomic(clkevt->base +
> > > +JH7110_TIMER_INT_CLR,
> > > value,
> > > +					!(value & JH7110_TIMER_INT_CLR_AVA_MASK),
> > > +					JH7110_DELAY_US, JH7110_TIMEOUT_US);
> > > +	if (!ret)
> > > +		writel(JH7110_TIMER_INT_CLR_ENA, clkevt->base +
> > > +JH7110_TIMER_INT_CLR);
> > > +
> > > +	return ret;
> > > +}
> > > +
> > > +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_clear(clkevt);
> > > +
> > > +	writel(JH7110_TIMER_INT_ENA, clkevt->base +
> > > JH7110_TIMER_INT_MASK);
> > > +	writel(JH7110_TIMER_ENA, clkevt->base + JH7110_TIMER_ENABLE);
> > > +
> > > +	return ret;
> > > +}
> > > +
> > > +static int jh7110_timer_shutdown(struct clock_event_device *evt) {
> > > +	struct jh7110_clkevt *clkevt = JH7110_PERCPU_GET_CLKEVT;
> > > +
> > > +	writel(JH7110_TIMER_DIS, clkevt->base + JH7110_TIMER_ENABLE);
> > > +	return jh7110_timer_int_clear(clkevt); }
> > > +
> > > +/* IRQ handler for the timer */
> > > +static irqreturn_t jh7110_timer_interrupt(int irq, void *data) {
> > > +	struct clock_event_device *evt = (struct clock_event_device *)data;
> > > +	struct jh7110_clkevt *clkevt = &jh7110_timer_info.clkevt[0];
> > > +	u32 reg = readl(clkevt->base + JH7110_TIMER_INT_STATUS);
> > > +	u8 cpu_id = smp_processor_id();
> > > +
> > > +	/* 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,
> > > +	.set_next_event			= jh7110_timer_set_next_event,
> > > +};
> > > +
> > > +static int jh7110_timer_dying_cpu(unsigned int cpu) {
> > > +	struct jh7110_timer_priv *priv = &jh7110_timer_info;
> > > +
> > > +	if (!priv->clkevt[cpu].timer_enabled)
> > > +		return 0;
> > > +
> > > +	writel(JH7110_TIMER_DIS, priv->clkevt[cpu].base +
> > > JH7110_TIMER_ENABLE);
> > > +	jh7110_timer_int_clear(&priv->clkevt[cpu]);
> > > +	reset_control_assert(priv->clkevt[cpu].rst);
> > > +	clk_disable_unprepare(priv->clkevt[cpu].clk);
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +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 err;
> > > +	u32 rate;
> > > +
> > > +	if (cpu >= JH7110_TIMER_CH_MAX)
> > > +		return -ENOMEM;
> > > +
> > > +	err = clk_prepare_enable(priv->clkevt[cpu].clk);
> > > +	if (err)
> > > +		goto err_starting_cpu;
> > > +
> > > +	err = reset_control_deassert(priv->clkevt[cpu].rst);
> > > +	if (err)
> > > +		goto err_soft_reset;
> > > +
> > > +	rate = clk_get_rate(priv->clkevt[cpu].clk);
> > > +	evt->cpumask = cpumask_of(cpu);
> > > +	evt->irq = priv->clkevt[cpu].irq;
> > > +
> > > +	err = irq_force_affinity(evt->irq, cpumask_of(cpu));
> > > +	if (err)
> > > +		goto err_affinity;
> > > +
> > > +	clockevents_config_and_register(evt, rate,
> JH7110_TIMER_MIN_TICKS,
> > > +					JH7110_TIMER_MAX_TICKS);
> > > +
> > > +	/* Use one-shot mode */
> > > +	writel(JH7110_TIMER_MODE_SINGLE, (priv->clkevt[cpu].base +
> > > +JH7110_TIMER_CTL));
> > > +
> > > +	priv->clkevt[cpu].timer_enabled = true;
> > > +
> > > +	err = jh7110_timer_start(&priv->clkevt[cpu]);
> > > +	if (err)
> > > +		goto err_affinity;
> > > +	return 0;
> > > +
> > > +err_affinity:
> > > +	reset_control_assert(priv->clkevt[cpu].rst);
> > > +err_soft_reset:
> > > +	clk_disable_unprepare(priv->clkevt[cpu].clk);
> > > +err_starting_cpu:
> > > +	free_irq(evt->irq, evt);
> > > +	return err;
> > > +}
> > > +
> > > +static int jh7110_timer_probe(struct platform_device *pdev) {
> > > +	struct jh7110_timer_priv *priv = &jh7110_timer_info;
> > > +	struct clock_event_device *evt;
> > > +	struct jh7110_clkevt *clkevt;
> > > +	char name[sizeof("chX")];
> > > +	int ch;
> > > +	int ret;
> > > +	void __iomem *base;
> > > +
> > > +	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");
> > > +
> > > +	priv->pclk = devm_clk_get_enabled(&pdev->dev, "apb");
> > > +	if (IS_ERR(priv->pclk))
> > > +		return dev_err_probe(&pdev->dev, PTR_ERR(priv->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");
> > > +
> > > +	for (ch = 0; ch < JH7110_TIMER_CH_MAX; ch++) {
> > > +		evt = per_cpu_ptr(&jh7110_clock_event, 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);
> > > +		ret = jh7110_timer_int_clear(clkevt);
> > > +		if (ret)
> > > +			return ret;
> > > +
> > > +		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);
> > > +		ret = devm_request_irq(&pdev->dev, clkevt->irq,
> > > jh7110_timer_interrupt,
> > > +				       IRQF_TIMER | IRQF_IRQPOLL,
> > > +				       clkevt->name, evt);
> > > +
> > > +		if (ret)
> > > +			return ret;
> > > +
> > > +		clkevt->timer_enabled = false;
> > > +	}
> > > +
> > > +	return cpuhp_setup_state(CPUHP_AP_JH7110_TIMER_STARTING,
> > > +				"clockevents/jh7110/timer:starting",
> > > +				jh7110_timer_starting_cpu,
> jh7110_timer_dying_cpu); }
> > > +
> > > +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 35e78ddb2b37..4a8b487c327e 100644
> > > --- a/include/linux/cpuhotplug.h
> > > +++ b/include/linux/cpuhotplug.h
> > > @@ -175,6 +175,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,
> > > --
> > > 2.17.1
> >
> > Hi Daniel / Thomas
> >
> > I have submitted new version of patch for jh7110 timer driver. Could you please
> help to review and give your comments?
> > Thanks a lot!
> 
> Hi Ziv
> 
> I tried this on 6.9-rc6 on my VF2. It boots, but very slowly and "choppy". That is
> it repeatedly runs for <1s and then hangs for about 4s.
> 
> Does this patch work for you?
> 
> /Emil

Hi, Emil

I tried this on 6.9-rc7 and 6.9-rc7, but it doesn't reproduce the phenomenon you said.
The attachment is the log for 6.9-rc6. Could you please share your config file (starfive_visionfive2_defconfig or .config) with me?

Best Regards
Ziv Xu
StarFive # setenv bootfile vmlinuz; setenv fdt_addr_r 0x48000000; setenv fdt_high 0xffffffffffffffff; setenv fdtcontroladdr 0xffffffffffffffff; setenv initrd_high 0xffffffffffffffff; setenv kernel_addr_r 0x44000000; setenv fileaddr a0000000;setenv kernel_comp_addr_r 0xb0000000; setenv kernel_comp_size 0x10000000;setenv ipaddr 192.168.125.96; setenv serverip 192.168.125.171;setenv ramdisk_addr_r 0x48100000;setenv kernel_comp_addr_r 0xb0000000;setenv kernel_comp_size 0x10000000;
StarFive # tftpboot ${fdt_addr_r} ziv/upstream/linux/arch/riscv/boot/dts/starfive/jh7110-starfive-visionfive-2-v1.3b.dtb; tftpboot ${kernel_addr_r} ziv/upstream/linux/arch/riscv/boot/Image.gz; tftpboot ${ramdisk_addr_r} ziv/visionfive/initramfs.cpio.gz;booti ${kernel_addr_r} ${ramdisk_addr_r}:${filesize} ${fdt_addr_r};
ethernet@16030000 Waiting for PHY auto negotiation to complete...... done
Using ethernet@16030000 device
TFTP from server 192.168.125.171; our IP address is 192.168.125.96
Filename 'ziv/upstream/linux/arch/riscv/boot/dts/starfive/jh7110-starfive-visionfive-2-v1.3b.dtb'.
Load address: 0x48000000
Loading: ###
         66.4 KiB/s
done
Bytes transferred = 36396 (8e2c hex)
Using ethernet@16030000 device
TFTP from server 192.168.125.171; our IP address is 192.168.125.96
Filename 'ziv/upstream/linux/arch/riscv/boot/Image.gz'.
Load address: 0x44000000
Loading: #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         ##########
         5.9 MiB/s
done
Bytes transferred = 6819280 (680dd0 hex)
Using ethernet@16030000 device
TFTP from server 192.168.125.171; our IP address is 192.168.125.96
Filename 'ziv/visionfive/initramfs.cpio.gz'.
Load address: 0x48100000
Loading: #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         ##############################################
         7.2 MiB/s
done
Bytes transferred = 114215585 (6cecaa1 hex)
   Uncompressing Kernel Image
Moving Image from 0x44000000 to 0x40200000, end=41727000
## Flattened Device Tree blob at 48000000
   Booting using the fdt blob at 0x48000000
   Using Device Tree in place at 0000000048000000, end 000000004800be2b

Starting kernel ...

clk u2_dw_i2c_clk_core already disabled
clk u2_dw_i2c_clk_apb already disabled
clk u5_dw_i2c_clk_core already disabled
clk u5_dw_i2c_clk_apb already disabled
Linux version 6.9.0-rc6-00003-g9385b82135ff (ziv@SD-Server) (riscv64-linux-gnu-gcc (Ubuntu 8.4.0-1ubuntu1~18.04) 8.4.0, GNU ld (GNU Binutils for Ubuntu) 2.30) #17 SMP Tue May  7 16:29:45 CST 2024
Machine model: StarFive VisionFive 2 v1.3B
SBI specification v1.0 detected
SBI implementation ID=0x1 Version=0x10002
SBI TIME extension detected
SBI IPI extension detected
SBI RFENCE extension detected
SBI SRST extension detected
efi: UEFI not found.
OF: reserved mem: 0x0000000040000000..0x000000004007ffff (512 KiB) nomap non-reusable opensbi@40000000
Zone ranges:
  DMA32    [mem 0x0000000040000000-0x00000000ffffffff]
  Normal   [mem 0x0000000100000000-0x000000013fffffff]
Movable zone start for each node
Early memory node ranges
  node   0: [mem 0x0000000040000000-0x000000004007ffff]
  node   0: [mem 0x0000000040080000-0x000000013fffffff]
Initmem setup node 0 [mem 0x0000000040000000-0x000000013fffffff]
SBI HSM extension detected
CPU with hartid=0 is not available
riscv: base ISA extensions acdfim
riscv: ELF capabilities acdfim
percpu: Embedded 20 pages/cpu s42664 r8192 d31064 u81920
pcpu-alloc: s42664 r8192 d31064 u81920 alloc=20*4096
pcpu-alloc: [0] 0 [0] 1 [0] 2 [0] 3
Kernel command line: console=tty1 console=ttyS0,115200  debug rootwait  earlycon=sbi
Dentry cache hash table entries: 524288 (order: 10, 4194304 bytes, linear)
Inode-cache hash table entries: 262144 (order: 9, 2097152 bytes, linear)
Built 1 zonelists, mobility grouping on.  Total pages: 1034240
mem auto-init: stack:off, heap alloc:off, heap free:off
software IO TLB: area num 4.
software IO TLB: mapped [mem 0x00000000fbfff000-0x00000000fffff000] (64MB)
Memory: 3929352K/4194304K available (8442K kernel code, 4781K rwdata, 4096K rodata, 2199K init, 339K bss, 264952K reserved, 0K cma-reserved)
SLUB: HWalign=64, Order=0-3, MinObjects=0, CPUs=4, Nodes=1
rcu: Hierarchical RCU implementation.
rcu:    RCU restricting CPUs from NR_CPUS=64 to nr_cpu_ids=4.
rcu: RCU calculated value of scheduler-enlistment delay is 25 jiffies.
rcu: Adjusting geometry for rcu_fanout_leaf=16, nr_cpu_ids=4
NR_IRQS: 64, nr_irqs: 64, preallocated irqs: 0
riscv-intc: 64 local interrupts mapped
riscv: providing IPIs using SBI IPI extension
rcu: srcu_init: Setting srcu_struct sizes based on contention.
clocksource: riscv_clocksource: mask: 0xffffffffffffffff max_cycles: 0x1d854df40, max_idle_ns: 881590404240 ns
sched_clock: 64 bits at 4MHz, resolution 250ns, wraps every 2199023255500ns
Calibrating delay loop (skipped), value calculated using timer frequency.. 8.00 BogoMIPS (lpj=16000)
pid_max: default: 32768 minimum: 301
Mount-cache hash table entries: 8192 (order: 4, 65536 bytes, linear)
Mountpoint-cache hash table entries: 8192 (order: 4, 65536 bytes, linear)
CPU node for /cpus/cpu@0 exist but the possible cpu range is :0-3
ASID allocator disabled (0 bits)
rcu: Hierarchical SRCU implementation.
rcu:    Max phase no-delay instances is 1000.
EFI services will not be available.
smp: Bringing up secondary CPUs ...
smp: Brought up 1 node, 4 CPUs
devtmpfs: initialized
clocksource: jiffies: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 7645041785100000 ns
futex hash table entries: 1024 (order: 4, 65536 bytes, linear)
pinctrl core: initialized pinctrl subsystem
NET: Registered PF_NETLINK/PF_ROUTE protocol family
DMA: preallocated 512 KiB GFP_KERNEL pool for atomic allocations
DMA: preallocated 512 KiB GFP_KERNEL|GFP_DMA32 pool for atomic allocations
thermal_sys: Registered thermal governor 'step_wise'
cpuidle: using governor menu
cpu3: Ratio of byte access time to unaligned word access is 0.01, unaligned accesses are slow
cpu1: Ratio of byte access time to unaligned word access is 0.01, unaligned accesses are slow
cpu2: Ratio of byte access time to unaligned word access is 0.01, unaligned accesses are slow
cpu0: Ratio of byte access time to unaligned word access is 0.01, unaligned accesses are slow
CCACHE: 8 banks, 16 ways, sets/bank=256, bytes/block=64
CCACHE: Index of the largest way enabled: 15
platform soc: Fixed dependency cycle(s) with /soc/interrupt-controller@c000000
platform soc: Fixed dependency cycle(s) with /soc/interrupt-controller@c000000
platform 19800000.csi: Fixed dependency cycle(s) with /soc/isp@19840000
platform 19800000.csi: Fixed dependency cycle(s) with /soc/isp@19840000
platform 19840000.isp: Fixed dependency cycle(s) with /soc/csi@19800000
raid6: skipped pq benchmark and selected int64x8
raid6: using intx1 recovery algorithm
SCSI subsystem initialized
usbcore: registered new interface driver usbfs
usbcore: registered new interface driver hub
usbcore: registered new device driver usb
mc: Linux media interface: v0.10
videodev: Linux video capture interface: v2.00
Advanced Linux Sound Architecture Driver Initialized.
vgaarb: loaded
clocksource: Switched to clocksource riscv_clocksource
NET: Registered PF_INET protocol family
IP idents hash table entries: 65536 (order: 7, 524288 bytes, linear)
tcp_listen_portaddr_hash hash table entries: 2048 (order: 3, 32768 bytes, linear)
Table-perturb hash table entries: 65536 (order: 6, 262144 bytes, linear)
TCP established hash table entries: 32768 (order: 6, 262144 bytes, linear)
TCP bind hash table entries: 32768 (order: 8, 1048576 bytes, linear)
TCP: Hash tables configured (established 32768 bind 32768)
UDP hash table entries: 2048 (order: 4, 65536 bytes, linear)
UDP-Lite hash table entries: 2048 (order: 4, 65536 bytes, linear)
NET: Registered PF_UNIX/PF_LOCAL protocol family
RPC: Registered named UNIX socket transport module.
RPC: Registered udp transport module.
RPC: Registered tcp transport module.
RPC: Registered tcp-with-tls transport module.
RPC: Registered tcp NFSv4.1 backchannel transport module.
PCI: CLS 0 bytes, default 64
Unpacking initramfs...
workingset: timestamp_bits=62 max_order=20 bucket_order=0
NFS: Registering the id_resolver key type
Key type id_resolver registered
Key type id_legacy registered
nfs4filelayout_init: NFSv4 File Layout Driver Registering...
nfs4flexfilelayout_init: NFSv4 Flexfile Layout Driver Registering...
ntfs3: Max link count 4000
jffs2: version 2.2. (NAND) © 2001-2006 Red Hat, Inc.
xor: measuring software checksum speed
   8regs           :  2340 MB/sec
   8regs_prefetch  :  2336 MB/sec
   32regs          :  2340 MB/sec
   32regs_prefetch :  2333 MB/sec
xor: using function: 32regs (2340 MB/sec)
Block layer SCSI generic (bsg) driver version 0.4 loaded (major 251)
io scheduler bfq registered
riscv-plic c000000.interrupt-controller: mapped 136 interrupts with 4 handlers for 9 contexts.
Serial: 8250/16550 driver, 6 ports, IRQ sharing disabled
random: crng init done
loop: module loaded
zram: Added device: zram0
Freeing initrd memory: 111536K
4 fixed-partitions partitions found on MTD device 13010000.spi.0
Creating 4 MTD partitions on "13010000.spi.0":
0x000000000000-0x000000080000 : "spl"
0x0000000f0000-0x000000100000 : "uboot-env"
0x000000100000-0x000000500000 : "uboot"
0x000000600000-0x000001000000 : "reserved-data"
starfive-dwmac 16030000.ethernet: IRQ sfty not found
starfive-dwmac 16030000.ethernet: User ID: 0x41, Synopsys ID: 0x52
starfive-dwmac 16030000.ethernet:       DWMAC4/5
starfive-dwmac 16030000.ethernet: DMA HW capability register supported
starfive-dwmac 16030000.ethernet: RX Checksum Offload Engine supported
starfive-dwmac 16030000.ethernet: Wake-Up On Lan supported
starfive-dwmac 16030000.ethernet: TSO supported
starfive-dwmac 16030000.ethernet: Enable RX Mitigation via HW Watchdog Timer
starfive-dwmac 16030000.ethernet: Enabled L3L4 Flow TC (entries=1)
starfive-dwmac 16030000.ethernet: Enabled RFS Flow TC (entries=10)
starfive-dwmac 16030000.ethernet: TSO feature enabled
starfive-dwmac 16030000.ethernet: Using 40/40 bits DMA host/device width
starfive-dwmac 16040000.ethernet: IRQ sfty not found
starfive-dwmac 16040000.ethernet: User ID: 0x41, Synopsys ID: 0x52
starfive-dwmac 16040000.ethernet:       DWMAC4/5
starfive-dwmac 16040000.ethernet: DMA HW capability register supported
starfive-dwmac 16040000.ethernet: RX Checksum Offload Engine supported
starfive-dwmac 16040000.ethernet: Wake-Up On Lan supported
starfive-dwmac 16040000.ethernet: TSO supported
starfive-dwmac 16040000.ethernet: Enable RX Mitigation via HW Watchdog Timer
starfive-dwmac 16040000.ethernet: Enabled L3L4 Flow TC (entries=1)
starfive-dwmac 16040000.ethernet: Enabled RFS Flow TC (entries=10)
starfive-dwmac 16040000.ethernet: TSO feature enabled
starfive-dwmac 16040000.ethernet: Using 40/40 bits DMA host/device width
usbcore: registered new interface driver uas
usbcore: registered new interface driver usb-storage
i2c_dev: i2c /dev entries driver
sdhci: Secure Digital Host Controller Interface driver
sdhci: Copyright(c) Pierre Ossman
Synopsys Designware Multimedia Card Interface Driver
sdhci-pltfm: SDHCI platform and OF driver helper
usbcore: registered new interface driver usbhid
usbhid: USB HID core driver
riscv-pmu-sbi: SBI PMU extension is available
riscv-pmu-sbi: 16 firmware and 4 hardware counters
riscv-pmu-sbi: Perf sampling/filtering is not supported as sscof extension is not available
NET: Registered PF_INET6 protocol family
Segment Routing with IPv6
In-situ OAM (IOAM) with IPv6
NET: Registered PF_PACKET protocol family
Key type dns_resolver registered
Timer migration: 1 hierarchy levels; 8 children per group; 1 crossnode level
Btrfs loaded, zoned=no, fsverity=no
CCACHE: DataError @ 0x00000000.08040270
CCACHE: DataFail @ 0x00000000.08040074
pl08xdmac 16008000.dma-controller: initialized 8 virtual memcpy channels
pl08xdmac 16008000.dma-controller: initialized 16 virtual slave channels
debugfs: Directory '16008000.dma-controller' with parent 'dmaengine' already present!
pl08xdmac 16008000.dma-controller: DMA: PL080 rev0 at 0x16008000 irq 30
gpio gpiochip0: Static allocation of GPIO base is deprecated, use dynamic allocation.
starfive-jh7110-sys-pinctrl 13040000.pinctrl: StarFive GPIO chip registered 64 GPIOs
gpio gpiochip1: Static allocation of GPIO base is deprecated, use dynamic allocation.
starfive-jh7110-aon-pinctrl 17020000.pinctrl: StarFive GPIO chip registered 4 GPIOs
dw_axi_dmac_platform 16050000.dma-controller: DesignWare AXI DMA Controller, 4 channels
printk: legacy console [ttyS0] disabled
10000000.serial: ttyS0 at MMIO 0x10000000 (irq = 33, base_baud = 1500000) is a 16550A
printk: legacy console [ttyS0] enabled
dwmmc_starfive 16020000.mmc: IDMAC supports 32-bit address mode.
designware-i2s 100e0000.i2s: probe with driver designware-i2s failed with error -110
dwmmc_starfive 16020000.mmc: Using internal DMA controller.
dwmmc_starfive 16020000.mmc: Version ID is 290a
dwmmc_starfive 16020000.mmc: DW MMC controller at irq 40,32 bit host data width,32 deep fifo
mmc_host mmc1: card is polling.
mmc_host mmc1: Bus speed (slot 0) = 49500000Hz (slot req 400000Hz, actual 399193HZ div = 62)
designware-i2s 120c0000.i2s: probe with driver designware-i2s failed with error -110
ssp-pl022 10060000.spi: ARM PL022 driver, device ID: 0x00041022
ssp-pl022 10060000.spi: mapped registers from 0x0000000010060000 to 00000000c0024811
axp20x-i2c 5-0036: AXP20x variant AXP15060 found
axp20x-i2c 5-0036: AXP20X driver loaded
starfive-dphy-rx 19820000.phy: supply mipi_0p9 not found, using dummy regulator
cdns-csi2rx 19800000.csi: probe with driver cdns-csi2rx failed with error -22
cpufreq: cpufreq_online: CPU0: Running at unlisted initial frequency: 1000000 KHz, changing to: 1500000 KHz
clk: Disabling unused clocks
PM: genpd: Disabling unused power domains
ALSA device list:
  #0: StarFive-PWMDAC-Sound-Card
dwmmc_starfive 16010000.mmc: IDMAC supports 32-bit address mode.
dwmmc_starfive 16010000.mmc: Using internal DMA controller.
dwmmc_starfive 16010000.mmc: Version ID is 290a
dwmmc_starfive 16010000.mmc: DW MMC controller at irq 62,32 bit host data width,32 deep fifo
mmc_host mmc0: card is non-removable.
mmc_host mmc1: Bus speed (slot 0) = 49500000Hz (slot req 300000Hz, actual 298192HZ div = 83)
mmc_host mmc1: Bus speed (slot 0) = 49500000Hz (slot req 200000Hz, actual 199596HZ div = 124)
mmc_host mmc1: Bus speed (slot 0) = 49500000Hz (slot req 100000Hz, actual 99798HZ div = 248)
mmc_host mmc0: Bus speed (slot 0) = 49500000Hz (slot req 400000Hz, actual 399193HZ div = 62)
Freeing unused kernel image (initmem) memory: 2196K
Checked W+X mappings: passed, no W+X pages found
rodata_test: all tests were successful
Run /init as init process
  with arguments:
    /init
  with environment:
    HOME=/
    TERM=linux
Starting syslogd: OK
Starting klogd: OK
Running sysctl: OK
Populating /dev using udev: udevd[125]: starting version 3.2.10
udevd[126]: starting eudev-3.2.10
mmc_host mmc0: Bus speed (slot 0) = 49500000Hz (slot req 52000000Hz, actual 49500000HZ div = 0)
mmc_host mmc0: Bus speed (slot 0) = 49500000Hz (slot req 100000000Hz, actual 49500000HZ div = 0)
mmc0: new HS200 MMC card at address 0001
mmcblk0: mmc0:0001 DG4032 29.1 GiB
GPT:Primary header thinks Alt. header is not at the end of the disk.
GPT:8191999 != 61071359
GPT:Alternate GPT header not at the end of the disk.
GPT:8191999 != 61071359
GPT: Use GNU Parted to correct GPT errors.
 mmcblk0: p1 p2 p3 p4
mmcblk0boot0: mmc0:0001 DG4032 4.00 MiB
mmcblk0boot1: mmc0:0001 DG4032 4.00 MiB
mmcblk0rpmb: mmc0:0001 DG4032 4.00 MiB, chardev (248:0)
done
Saving random seed: OK
Starting rngd: OK
Starting system message bus: dbus[171]: Unknown username "pulse" in message bus configuration file
done
Starting rpcbind: OK
Starting iptables: OK
Starting bluetoothd: OK
Starting network: OK
Starting Network Interface Plugging Daemon:starfive-dwmac 16030000.ethernet eth0: Register MEM_TYPE_PAGE_POOL RxQ-0
starfive-dwmac 16030000.ethernet eth0: PHY [stmmac-0:00] driver [YT8531 Gigabit Ethernet] (irq=POLL)
dwmac4: Master AXI performs fixed burst length
starfive-dwmac 16030000.ethernet eth0: No Safety Features support found
starfive-dwmac 16030000.ethernet eth0: IEEE 1588-2008 Advanced Timestamp supported
 eth0starfive-dwmac 16030000.ethernet eth0: configuring for phy/rgmii-id link mode
 eth1.
Starting ofono ... done.
Starting ntpd: starfive-dwmac 16040000.ethernet eth1: Register MEM_TYPE_PAGE_POOL RxQ-0
starfive-dwmac 16040000.ethernet eth1: PHY [stmmac-1:00] driver [YT8531 Gigabit Ethernet] (irq=POLL)
starfive-dwmac 16040000.ethernet: Failed to reset the dma
starfive-dwmac 16040000.ethernet eth1: stmmac_hw_setup: DMA engine initialization failed
starfive-dwmac 16040000.ethernet eth1: __stmmac_open: Hw setup failed
starfive-dwmac 16040000.ethernet eth1: Register MEM_TYPE_PAGE_POOL RxQ-0
starfive-dwmac 16040000.ethernet eth1: PHY [stmmac-1:00] driver [YT8531 Gigabit Ethernet] (irq=POLL)
dwmac4: Master AXI performs fixed burst length
starfive-dwmac 16040000.ethernet eth1: No Safety Features support found
starfive-dwmac 16040000.ethernet eth1: IEEE 1588-2008 Advanced Timestamp supported
starfive-dwmac 16040000.ethernet eth1: configuring for phy/rgmii-id link mode
OK
Starting dropbear sshd: OK
Starting multipastarfive-dwmac 16030000.ethernet eth0: Link is Up - 1Gbps/Full - flow control rx/tx
thd: OK
Starting NFS statd: OK
Starting NFS services: OK
Starting NFS daemon: rpc.nfsd: Unable to access /proc/fs/nfsd errno 2 (No such file or directory).
Please try, as root, 'mount -t nfsd nfsd /proc/fs/nfsd' and then restart rpc.nfsd to correct the problem
FAIL
Starting NFS mountd: OK
Starting isp_ctrl_daemon.sh: OK
Starting DHCP server: FAIL
No PARTLABEL=hibernation!

Welcome to Buildroot
buildroot login:
Welcome to Buildroot
buildroot login: root
Password:
# cat /proc/interrupts
           CPU0       CPU1       CPU2       CPU3
 10:       1049         87         28        619  RISC-V INTC   5 Edge      riscv-timer
 12:          0          0          0          0  SiFive PLIC  73 Edge      dw_axi_dmac_platform
 13:          3          0          0          0  SiFive PLIC 111 Edge      17030000.power-controller
 14:         90          0          0          0  SiFive PLIC  30 Edge      1600c000.rng
 15:          6          0          0          0  SiFive PLIC  25 Edge      13010000.spi
 16:         17          0          0          0  SiFive PLIC   7 Edge      eth0
 17:          0          0          0          0  SiFive PLIC   6 Edge      eth0
 18:          0          0          0          0  SiFive PLIC   5 Edge      eth0
 19:          0          0          0          0  SiFive PLIC  78 Edge      eth1
 20:          0          0          0          0  SiFive PLIC  77 Edge      eth1
 21:          0          0          0          0  SiFive PLIC  76 Edge      eth1
 22:       4664          0          0          0  SiFive PLIC  69 Edge      jh7110-timer.ch0
 23:          0       5602          0          0  SiFive PLIC  70 Edge      jh7110-timer.ch1
 24:          0          0       5717          0  SiFive PLIC  71 Edge      jh7110-timer.ch2
 25:          0          0          0       5385  SiFive PLIC  72 Edge      jh7110-timer.ch3
 26:          0          0          0          0  SiFive PLIC   1 Edge      ccache_ecc
 27:          1          0          0          0  SiFive PLIC   3 Edge      ccache_ecc
 28:          1          0          0          0  SiFive PLIC   4 Edge      ccache_ecc
 29:          0          0          0          0  SiFive PLIC   2 Edge      ccache_ecc
 30:          0          0          0          0  SiFive PLIC  29 Edge      pl08xdmac
 33:        164          0          0          0  SiFive PLIC  32 Edge      ttyS0
 35:          0          0          0          0  SiFive PLIC 108 Edge      10100000.usb
 36:          0          0          0          0  SiFive PLIC 110 Edge      10100000.usb
 37:          0          0          0          0  SiFive PLIC  92 Edge      wr_irq
 38:          0          0          0          0  SiFive PLIC  87 Edge      isp_irq
 39:          0          0          0          0  SiFive PLIC  90 Edge      line_irq
 40:        383          0          0          0  SiFive PLIC  75 Edge      dw-mci
 41:          0          0          0          0  SiFive PLIC  38 Edge      pl022
 42:          0          0          0          0  SiFive PLIC  35 Edge      10030000.i2c
 43:          1          0          0          0  SiFive PLIC  37 Edge      10050000.i2c
 44:         90          0          0          0  SiFive PLIC  50 Edge      12050000.i2c
 45:          0          0          0          0  SiFive PLIC   0 Edge      axp15060
 61:          0          0          0          0  SiFive PLIC  51 Edge      12060000.i2c
 62:       1009          0          0          0  SiFive PLIC  74 Edge      dw-mci
IPI0:        49         59         73         82  Rescheduling interrupts
IPI1:       623        743        810        952  Function call interrupts
IPI2:         0          0          0          0  CPU stop interrupts
IPI3:         0          0          0          0  CPU stop (for crash dump) interrupts
IPI4:        39         61         40         27  IRQ work interrupts
IPI5:         0          0          0          0  Timer broadcast interrupts
#
Emil Renner Berthing May 7, 2024, 9:54 a.m. UTC | #4
Ziv Xu wrote:
>
>
> > -----邮件原件-----
> > 发件人: Emil Renner Berthing <emil.renner.berthing@canonical.com>
> > 发送时间: 2024年4月30日 18:19
> > 收件人: Ziv Xu <ziv.xu@starfivetech.com>; Daniel Lezcano
> > <daniel.lezcano@linaro.org>; Thomas Gleixner <tglx@linutronix.de>
> > 抄送: linux-riscv@lists.infradead.org; devicetree@vger.kernel.org; Rob Herring
> > <robh+dt@kernel.org>; Krzysztof Kozlowski
> > <krzysztof.kozlowski+dt@linaro.org>; Paul Walmsley
> > <paul.walmsley@sifive.com>; Palmer Dabbelt <palmer@dabbelt.com>; Albert
> > Ou <aou@eecs.berkeley.edu>; Philipp Zabel <p.zabel@pengutronix.de>; Walker
> > Chen <walker.chen@starfivetech.com>; Xingyu Wu
> > <xingyu.wu@starfivetech.com>; linux-kernel@vger.kernel.org; Conor Dooley
> > <conor@kernel.org>
> > 主题: Re: 回复: [PATCH v10 2/3] clocksource: Add JH7110 timer driver
> >
> > Ziv Xu wrote:
> > >
> > >
> > > > -----邮件原件-----
> > > > 发件人: Ziv Xu
> > > > 发送时间: 2024年4月12日 16:46
> > > > 收件人: Daniel Lezcano <daniel.lezcano@linaro.org>; Thomas Gleixner
> > > > <tglx@linutronix.de>; Emil Renner Berthing
> > > > <emil.renner.berthing@canonical.com>; Christophe JAILLET
> > > > <christophe.jaillet@wanadoo.fr>
> > > > 抄送: linux-riscv@lists.infradead.org; devicetree@vger.kernel.org; Rob
> > > > Herring <robh+dt@kernel.org>; Krzysztof Kozlowski
> > > > <krzysztof.kozlowski+dt@linaro.org>; Paul Walmsley
> > > > <paul.walmsley@sifive.com>; Palmer Dabbelt <palmer@dabbelt.com>;
> > > > Albert Ou <aou@eecs.berkeley.edu>; Philipp Zabel
> > > > <p.zabel@pengutronix.de>; Walker Chen
> > > > <walker.chen@starfivetech.com>; Xingyu Wu
> > > > <xingyu.wu@starfivetech.com>; linux-kernel@vger.kernel.org; Conor
> > > > Dooley <conor@kernel.org>
> > > > 主题: [PATCH v10 2/3] clocksource: Add JH7110 timer driver
> > > >
> > > > From: Xingyu Wu <xingyu.wu@starfivetech.com>
> > > >
> > > > Add timer driver for the StarFive JH7110 SoC.
> > > >
> > > > 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: Ziv Xu <ziv.xu@starfivetech.com>
> > > > Signed-off-by: Xingyu Wu <xingyu.wu@starfivetech.com>
> > > > ---
> > > >  MAINTAINERS                        |   7 +
> > > >  drivers/clocksource/Kconfig        |  11 +
> > > >  drivers/clocksource/Makefile       |   1 +
> > > >  drivers/clocksource/timer-jh7110.c | 345
> > +++++++++++++++++++++++++++++
> > > >  include/linux/cpuhotplug.h         |   1 +
> > > >  5 files changed, 365 insertions(+)
> > > >  create mode 100644 drivers/clocksource/timer-jh7110.c
> > > >
> > > > diff --git a/MAINTAINERS b/MAINTAINERS index
> > > > 7c121493f43d..ef9b5f5bad9e 100644
> > > > --- a/MAINTAINERS
> > > > +++ b/MAINTAINERS
> > > > @@ -21043,6 +21043,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/drivers/clocksource/Kconfig
> > > > b/drivers/clocksource/Kconfig index
> > > > 34faa0320ece..2dc97201dee1 100644
> > > > --- a/drivers/clocksource/Kconfig
> > > > +++ b/drivers/clocksource/Kconfig
> > > > @@ -641,6 +641,17 @@ 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"
> > > > +	depends on ARCH_STARFIVE || COMPILE_TEST
> > > > +	select TIMER_OF
> > > > +	select CLKSRC_MMIO
> > > > +	default ARCH_STARFIVE
> > > > +	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 to do a lot more things on 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..dc770507f209
> > > > --- /dev/null
> > > > +++ b/drivers/clocksource/timer-jh7110.c
> > > > @@ -0,0 +1,345 @@
> > > > +// 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 */
> > > > +#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_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 - Description of each timer channel
> > > > + * @clk:		Clock of each timer channel
> > > > + * @rst:		Reset of each timer channel
> > > > + * @base:		Virtual address of each timer channel
> > > > + * @irq:                Interrupt number of each timer channel
> > > > + * @timer_enabled:      Enabled flag for each timer channel
> > > > + * @name:		Name of each timer channel
> > > > + */
> > > > +struct jh7110_clkevt {
> > > > +	struct clk		*clk;
> > > > +	struct reset_control	*rst;
> > > > +	void __iomem		*base;
> > > > +	int			irq;
> > > > +	bool			timer_enabled;
> > > > +	char			name[sizeof("jh7110-timer.chX")];
> > > > +};
> > > > +
> > > > +struct jh7110_timer_priv {
> > > > +	struct clk		*pclk;
> > > > +	struct reset_control	*prst;
> > > > +	struct device		*dev;
> > > > +	struct jh7110_clkevt	clkevt[JH7110_TIMER_CH_MAX];
> > > > +};
> > > > +
> > > > +static struct jh7110_timer_priv jh7110_timer_info;
> > > > +
> > > > +/* 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,
> > > > +};
> > > > +
> > > > +/*
> > > > + * 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 inline int jh7110_timer_int_clear(struct jh7110_clkevt
> > > > +*clkevt) {
> > > > +	u32 value;
> > > > +	int ret;
> > > > +
> > > > +	/* Waiting interrupt can be cleared */
> > > > +	ret = readl_poll_timeout_atomic(clkevt->base +
> > > > +JH7110_TIMER_INT_CLR,
> > > > value,
> > > > +					!(value & JH7110_TIMER_INT_CLR_AVA_MASK),
> > > > +					JH7110_DELAY_US, JH7110_TIMEOUT_US);
> > > > +	if (!ret)
> > > > +		writel(JH7110_TIMER_INT_CLR_ENA, clkevt->base +
> > > > +JH7110_TIMER_INT_CLR);
> > > > +
> > > > +	return ret;
> > > > +}
> > > > +
> > > > +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_clear(clkevt);
> > > > +
> > > > +	writel(JH7110_TIMER_INT_ENA, clkevt->base +
> > > > JH7110_TIMER_INT_MASK);
> > > > +	writel(JH7110_TIMER_ENA, clkevt->base + JH7110_TIMER_ENABLE);
> > > > +
> > > > +	return ret;
> > > > +}
> > > > +
> > > > +static int jh7110_timer_shutdown(struct clock_event_device *evt) {
> > > > +	struct jh7110_clkevt *clkevt = JH7110_PERCPU_GET_CLKEVT;
> > > > +
> > > > +	writel(JH7110_TIMER_DIS, clkevt->base + JH7110_TIMER_ENABLE);
> > > > +	return jh7110_timer_int_clear(clkevt); }
> > > > +
> > > > +/* IRQ handler for the timer */
> > > > +static irqreturn_t jh7110_timer_interrupt(int irq, void *data) {
> > > > +	struct clock_event_device *evt = (struct clock_event_device *)data;
> > > > +	struct jh7110_clkevt *clkevt = &jh7110_timer_info.clkevt[0];
> > > > +	u32 reg = readl(clkevt->base + JH7110_TIMER_INT_STATUS);
> > > > +	u8 cpu_id = smp_processor_id();
> > > > +
> > > > +	/* 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,
> > > > +	.set_next_event			= jh7110_timer_set_next_event,
> > > > +};
> > > > +
> > > > +static int jh7110_timer_dying_cpu(unsigned int cpu) {
> > > > +	struct jh7110_timer_priv *priv = &jh7110_timer_info;
> > > > +
> > > > +	if (!priv->clkevt[cpu].timer_enabled)
> > > > +		return 0;
> > > > +
> > > > +	writel(JH7110_TIMER_DIS, priv->clkevt[cpu].base +
> > > > JH7110_TIMER_ENABLE);
> > > > +	jh7110_timer_int_clear(&priv->clkevt[cpu]);
> > > > +	reset_control_assert(priv->clkevt[cpu].rst);
> > > > +	clk_disable_unprepare(priv->clkevt[cpu].clk);
> > > > +
> > > > +	return 0;
> > > > +}
> > > > +
> > > > +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 err;
> > > > +	u32 rate;
> > > > +
> > > > +	if (cpu >= JH7110_TIMER_CH_MAX)
> > > > +		return -ENOMEM;
> > > > +
> > > > +	err = clk_prepare_enable(priv->clkevt[cpu].clk);
> > > > +	if (err)
> > > > +		goto err_starting_cpu;
> > > > +
> > > > +	err = reset_control_deassert(priv->clkevt[cpu].rst);
> > > > +	if (err)
> > > > +		goto err_soft_reset;
> > > > +
> > > > +	rate = clk_get_rate(priv->clkevt[cpu].clk);
> > > > +	evt->cpumask = cpumask_of(cpu);
> > > > +	evt->irq = priv->clkevt[cpu].irq;
> > > > +
> > > > +	err = irq_force_affinity(evt->irq, cpumask_of(cpu));
> > > > +	if (err)
> > > > +		goto err_affinity;
> > > > +
> > > > +	clockevents_config_and_register(evt, rate,
> > JH7110_TIMER_MIN_TICKS,
> > > > +					JH7110_TIMER_MAX_TICKS);
> > > > +
> > > > +	/* Use one-shot mode */
> > > > +	writel(JH7110_TIMER_MODE_SINGLE, (priv->clkevt[cpu].base +
> > > > +JH7110_TIMER_CTL));
> > > > +
> > > > +	priv->clkevt[cpu].timer_enabled = true;
> > > > +
> > > > +	err = jh7110_timer_start(&priv->clkevt[cpu]);
> > > > +	if (err)
> > > > +		goto err_affinity;
> > > > +	return 0;
> > > > +
> > > > +err_affinity:
> > > > +	reset_control_assert(priv->clkevt[cpu].rst);
> > > > +err_soft_reset:
> > > > +	clk_disable_unprepare(priv->clkevt[cpu].clk);
> > > > +err_starting_cpu:
> > > > +	free_irq(evt->irq, evt);
> > > > +	return err;
> > > > +}
> > > > +
> > > > +static int jh7110_timer_probe(struct platform_device *pdev) {
> > > > +	struct jh7110_timer_priv *priv = &jh7110_timer_info;
> > > > +	struct clock_event_device *evt;
> > > > +	struct jh7110_clkevt *clkevt;
> > > > +	char name[sizeof("chX")];
> > > > +	int ch;
> > > > +	int ret;
> > > > +	void __iomem *base;
> > > > +
> > > > +	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");
> > > > +
> > > > +	priv->pclk = devm_clk_get_enabled(&pdev->dev, "apb");
> > > > +	if (IS_ERR(priv->pclk))
> > > > +		return dev_err_probe(&pdev->dev, PTR_ERR(priv->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");
> > > > +
> > > > +	for (ch = 0; ch < JH7110_TIMER_CH_MAX; ch++) {
> > > > +		evt = per_cpu_ptr(&jh7110_clock_event, 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);
> > > > +		ret = jh7110_timer_int_clear(clkevt);
> > > > +		if (ret)
> > > > +			return ret;
> > > > +
> > > > +		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);
> > > > +		ret = devm_request_irq(&pdev->dev, clkevt->irq,
> > > > jh7110_timer_interrupt,
> > > > +				       IRQF_TIMER | IRQF_IRQPOLL,
> > > > +				       clkevt->name, evt);
> > > > +
> > > > +		if (ret)
> > > > +			return ret;
> > > > +
> > > > +		clkevt->timer_enabled = false;
> > > > +	}
> > > > +
> > > > +	return cpuhp_setup_state(CPUHP_AP_JH7110_TIMER_STARTING,
> > > > +				"clockevents/jh7110/timer:starting",
> > > > +				jh7110_timer_starting_cpu,
> > jh7110_timer_dying_cpu); }
> > > > +
> > > > +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 35e78ddb2b37..4a8b487c327e 100644
> > > > --- a/include/linux/cpuhotplug.h
> > > > +++ b/include/linux/cpuhotplug.h
> > > > @@ -175,6 +175,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,
> > > > --
> > > > 2.17.1
> > >
> > > Hi Daniel / Thomas
> > >
> > > I have submitted new version of patch for jh7110 timer driver. Could you please
> > help to review and give your comments?
> > > Thanks a lot!
> >
> > Hi Ziv
> >
> > I tried this on 6.9-rc6 on my VF2. It boots, but very slowly and "choppy". That is
> > it repeatedly runs for <1s and then hangs for about 4s.
> >
> > Does this patch work for you?
> >
> > /Emil
>
> Hi, Emil
>
> I tried this on 6.9-rc7 and 6.9-rc7, but it doesn't reproduce the phenomenon you said.
> The attachment is the log for 6.9-rc6. Could you please share your config file (starfive_visionfive2_defconfig or .config) with me?

Yeah, I just tried again on 6.9-rc7 (+ Conor's riscv-dt-for-next, Minda's PCIe
patches and the PWM driver) with this config:

https://sprunge.us/kiXXba

I don't know if it makes a difference but my also board boots via EFI.

/Emil
Ziv Xu May 22, 2024, 9:37 a.m. UTC | #5
Emil wrote:
> 
> > > > Hi Daniel / Thomas
> > > >
> > > > I have submitted new version of patch for jh7110 timer driver.
> > > > Could you please
> > > help to review and give your comments?
> > > > Thanks a lot!
> > >
> > > Hi Ziv
> > >
> > > I tried this on 6.9-rc6 on my VF2. It boots, but very slowly and
> > > "choppy". That is it repeatedly runs for <1s and then hangs for about 4s.
> > >
> > > Does this patch work for you?
> > >
> > > /Emil
> >
> > Hi, Emil
> >
> > I tried this on 6.9-rc7 and 6.9-rc7, but it doesn't reproduce the phenomenon
> you said.
> > The attachment is the log for 6.9-rc6. Could you please share your config file
> (starfive_visionfive2_defconfig or .config) with me?
> 
> Yeah, I just tried again on 6.9-rc7 (+ Conor's riscv-dt-for-next, Minda's PCIe
> patches and the PWM driver) with this config:
> 
> https://sprunge.us/kiXXba
> 
> I don't know if it makes a difference but my also board boots via EFI.
> 
> /Emil

Hi, Emil

The cause of booting slowly is that when JH7110-Timer is registered as clockevent device, the soft timer will be pending while
the crng tries to generate entropy(try_to_generate_entropy funcition in linux/driver/char/random.c).
This can be avoided with setting CONFIG_HW_RANDOM and CONFIG_HW_RANDOM_JH7110 as "y". But I can't find a root casue.

Could you please give me some advice?

Best Regards
Ziv.Xu
diff mbox series

Patch

diff --git a/MAINTAINERS b/MAINTAINERS
index 7c121493f43d..ef9b5f5bad9e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -21043,6 +21043,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/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig
index 34faa0320ece..2dc97201dee1 100644
--- a/drivers/clocksource/Kconfig
+++ b/drivers/clocksource/Kconfig
@@ -641,6 +641,17 @@  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"
+	depends on ARCH_STARFIVE || COMPILE_TEST
+	select TIMER_OF
+	select CLKSRC_MMIO
+	default ARCH_STARFIVE
+	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 to do a lot more things on 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..dc770507f209
--- /dev/null
+++ b/drivers/clocksource/timer-jh7110.c
@@ -0,0 +1,345 @@ 
+// 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 */
+#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_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 - Description of each timer channel
+ * @clk:		Clock of each timer channel
+ * @rst:		Reset of each timer channel
+ * @base:		Virtual address of each timer channel
+ * @irq:                Interrupt number of each timer channel
+ * @timer_enabled:      Enabled flag for each timer channel
+ * @name:		Name of each timer channel
+ */
+struct jh7110_clkevt {
+	struct clk		*clk;
+	struct reset_control	*rst;
+	void __iomem		*base;
+	int			irq;
+	bool			timer_enabled;
+	char			name[sizeof("jh7110-timer.chX")];
+};
+
+struct jh7110_timer_priv {
+	struct clk		*pclk;
+	struct reset_control	*prst;
+	struct device		*dev;
+	struct jh7110_clkevt	clkevt[JH7110_TIMER_CH_MAX];
+};
+
+static struct jh7110_timer_priv jh7110_timer_info;
+
+/* 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,
+};
+
+/*
+ * 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 inline int jh7110_timer_int_clear(struct jh7110_clkevt *clkevt)
+{
+	u32 value;
+	int ret;
+
+	/* Waiting interrupt can be cleared */
+	ret = readl_poll_timeout_atomic(clkevt->base + JH7110_TIMER_INT_CLR, value,
+					!(value & JH7110_TIMER_INT_CLR_AVA_MASK),
+					JH7110_DELAY_US, JH7110_TIMEOUT_US);
+	if (!ret)
+		writel(JH7110_TIMER_INT_CLR_ENA, clkevt->base + JH7110_TIMER_INT_CLR);
+
+	return ret;
+}
+
+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_clear(clkevt);
+
+	writel(JH7110_TIMER_INT_ENA, clkevt->base + JH7110_TIMER_INT_MASK);
+	writel(JH7110_TIMER_ENA, clkevt->base + JH7110_TIMER_ENABLE);
+
+	return ret;
+}
+
+static int jh7110_timer_shutdown(struct clock_event_device *evt)
+{
+	struct jh7110_clkevt *clkevt = JH7110_PERCPU_GET_CLKEVT;
+
+	writel(JH7110_TIMER_DIS, clkevt->base + JH7110_TIMER_ENABLE);
+	return jh7110_timer_int_clear(clkevt);
+}
+
+/* IRQ handler for the timer */
+static irqreturn_t jh7110_timer_interrupt(int irq, void *data)
+{
+	struct clock_event_device *evt = (struct clock_event_device *)data;
+	struct jh7110_clkevt *clkevt = &jh7110_timer_info.clkevt[0];
+	u32 reg = readl(clkevt->base + JH7110_TIMER_INT_STATUS);
+	u8 cpu_id = smp_processor_id();
+
+	/* 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,
+	.set_next_event			= jh7110_timer_set_next_event,
+};
+
+static int jh7110_timer_dying_cpu(unsigned int cpu)
+{
+	struct jh7110_timer_priv *priv = &jh7110_timer_info;
+
+	if (!priv->clkevt[cpu].timer_enabled)
+		return 0;
+
+	writel(JH7110_TIMER_DIS, priv->clkevt[cpu].base + JH7110_TIMER_ENABLE);
+	jh7110_timer_int_clear(&priv->clkevt[cpu]);
+	reset_control_assert(priv->clkevt[cpu].rst);
+	clk_disable_unprepare(priv->clkevt[cpu].clk);
+
+	return 0;
+}
+
+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 err;
+	u32 rate;
+
+	if (cpu >= JH7110_TIMER_CH_MAX)
+		return -ENOMEM;
+
+	err = clk_prepare_enable(priv->clkevt[cpu].clk);
+	if (err)
+		goto err_starting_cpu;
+
+	err = reset_control_deassert(priv->clkevt[cpu].rst);
+	if (err)
+		goto err_soft_reset;
+
+	rate = clk_get_rate(priv->clkevt[cpu].clk);
+	evt->cpumask = cpumask_of(cpu);
+	evt->irq = priv->clkevt[cpu].irq;
+
+	err = irq_force_affinity(evt->irq, cpumask_of(cpu));
+	if (err)
+		goto err_affinity;
+
+	clockevents_config_and_register(evt, rate, JH7110_TIMER_MIN_TICKS,
+					JH7110_TIMER_MAX_TICKS);
+
+	/* Use one-shot mode */
+	writel(JH7110_TIMER_MODE_SINGLE, (priv->clkevt[cpu].base + JH7110_TIMER_CTL));
+
+	priv->clkevt[cpu].timer_enabled = true;
+
+	err = jh7110_timer_start(&priv->clkevt[cpu]);
+	if (err)
+		goto err_affinity;
+	return 0;
+
+err_affinity:
+	reset_control_assert(priv->clkevt[cpu].rst);
+err_soft_reset:
+	clk_disable_unprepare(priv->clkevt[cpu].clk);
+err_starting_cpu:
+	free_irq(evt->irq, evt);
+	return err;
+}
+
+static int jh7110_timer_probe(struct platform_device *pdev)
+{
+	struct jh7110_timer_priv *priv = &jh7110_timer_info;
+	struct clock_event_device *evt;
+	struct jh7110_clkevt *clkevt;
+	char name[sizeof("chX")];
+	int ch;
+	int ret;
+	void __iomem *base;
+
+	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");
+
+	priv->pclk = devm_clk_get_enabled(&pdev->dev, "apb");
+	if (IS_ERR(priv->pclk))
+		return dev_err_probe(&pdev->dev, PTR_ERR(priv->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");
+
+	for (ch = 0; ch < JH7110_TIMER_CH_MAX; ch++) {
+		evt = per_cpu_ptr(&jh7110_clock_event, 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);
+		ret = jh7110_timer_int_clear(clkevt);
+		if (ret)
+			return ret;
+
+		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);
+		ret = devm_request_irq(&pdev->dev, clkevt->irq, jh7110_timer_interrupt,
+				       IRQF_TIMER | IRQF_IRQPOLL,
+				       clkevt->name, evt);
+
+		if (ret)
+			return ret;
+
+		clkevt->timer_enabled = false;
+	}
+
+	return cpuhp_setup_state(CPUHP_AP_JH7110_TIMER_STARTING,
+				"clockevents/jh7110/timer:starting",
+				jh7110_timer_starting_cpu, jh7110_timer_dying_cpu);
+}
+
+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 35e78ddb2b37..4a8b487c327e 100644
--- a/include/linux/cpuhotplug.h
+++ b/include/linux/cpuhotplug.h
@@ -175,6 +175,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,