2024-04-12 09:30:39

by Ziv Xu

[permalink] [raw]
Subject: [PATCH v10 0/3] Add timer driver for StarFive JH7110 RISC-V SoC

This patch serises are to add timer driver for the StarFive JH7110
RISC-V SoC. The first patch adds documentation to describe device
tree bindings. The subsequent patch adds timer driver and support
JH7110 SoC. The last patch adds device node about timer in JH7110
dts.

This timer has four free-running 32 bit counters and runs in 24MHz
clock on StarFive JH7110 SoC. And each channel(counter) triggers
an interrupt when timeout. They support one-shot mode and
continuous-run mode.

This timer is used as global timer and register clockevent for each
CPU core after riscv-timer registration on the StarFive JH7110 SoC.

Changes since v9:
- Rebase on 6.9-rc2.
- checked the return value in the jh7110_timer_start function.

v9: https://lore.kernel.org/all/[email protected]/

Changes since v8:
- Rebased on 6.8
- Improved the cpu hot swap startup process of the timer.
- Modified irq request timing to prevent sleep.
- Deleted clockevent suspend and resume function and these
operations are included in cpu hot swap operations.
- Formated data structures.

v8: https://lore.kernel.org/all/[email protected]/

Changes since v7:
- Rebased on 6.7-rc6.
- Modified the Kconfig file and added selection in SOC_STARFIVE.
- Used the timer as a global timer and registered as clockevent
for each CPU core.
- Dropped the timeout function in the interrupt handler callback.
- Changed the way in the functions of jh7110_timer_tick_resume() and
jh7110_timer_resume().
- Dropped the registration of clocksource in the probe.

v7: https://lore.kernel.org/all/[email protected]/

Changes since v6:
- Rebased on 6.6-rc6.
- Used sizeof() instead of the numbers of characters about names.
- Added devm_add_action_or_reset() to release the resets and
clocksources in the case of remove or error in the probe.
- Added flags to check each clocksource is suceessfully registered and
used in the release function.
- Dropped the variable of irq in the jh7110_clkevt struct.
- Dropped the wrappers and used enum definitions and writel() calls
directly.

v6: https://lore.kernel.org/all/[email protected]/

Changes since v5:
- Rebased on 6.6-rc5.
- Changed the number about characters of name.
- Made the clkevt->periodic to a local variable.
- Dropped the variables of device and base.
- Used clkevt->evt.irq directly and dropped the extra copy of irq.

V5: https://lore.kernel.org/all/[email protected]/

Changes since v4:
- Rebased on 6.5.
- Dropped the useless enum and used value directly when writing
registers.
- Modified the description in Kconfig.
- Add the reviewed tag in patch 3.

v4: https://lore.kernel.org/all/[email protected]/

Changes since v3:
- Rebased on 6.5-rc6
- Dropped the useless enum names like 'JH7110_TIMER_CH_0'.
- Dropped the platform data about JH7110 and used the register offsets
directly.
- Drroped the useless functions of clk_disable_unprepare().

v3: https://lore.kernel.org/all/[email protected]/

Changes since v2:
- Rebased on 6.4-rc7.
- Merged the header file into the c file.
- Renamed the functions from 'starfive_' to 'jh7110_'
- Used function 'clocksource_register_hz' instead of
'clocksource_mmio_init'.

v2: https://lore.kernel.org/all/[email protected]/

Changes since v1:
- Added description about timer and modified properties' description
in dt-bindings.
- Dropped the 'interrupt-names' and 'clock-frequency' in dt-bindings.
- Renamed the functions and added 'starfive_'
- Modified that the driver probe by platform bus.

v1: https://lore.kernel.org/all/[email protected]/

Xingyu Wu (3):
dt-bindings: timer: Add timer for StarFive JH7110 SoC
clocksource: Add JH7110 timer driver
riscv: dts: jh7110: starfive: Add timer node

.../bindings/timer/starfive,jh7110-timer.yaml | 96 +++++
MAINTAINERS | 7 +
arch/riscv/boot/dts/starfive/jh7110.dtsi | 20 +
drivers/clocksource/Kconfig | 11 +
drivers/clocksource/Makefile | 1 +
drivers/clocksource/timer-jh7110.c | 345 ++++++++++++++++++
include/linux/cpuhotplug.h | 1 +
7 files changed, 481 insertions(+)
create mode 100644 Documentation/devicetree/bindings/timer/starfive,jh7110-timer.yaml
create mode 100644 drivers/clocksource/timer-jh7110.c

--
2.17.1



2024-04-12 09:42:04

by Ziv Xu

[permalink] [raw]
Subject: [PATCH v10 2/3] clocksource: Add JH7110 timer driver

From: Xingyu Wu <[email protected]>

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 <[email protected]>
Signed-off-by: Xingyu Wu <[email protected]>
---
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 <[email protected]>
+M: Xingyu Wu <[email protected]>
+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 <[email protected]>
M: Hal Feng <[email protected]>
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 <[email protected]>");
+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


2024-04-12 13:24:38

by Ziv Xu

[permalink] [raw]
Subject: [PATCH v10 1/3] dt-bindings: timer: Add timer for StarFive JH7110 SoC

From: Xingyu Wu <[email protected]>

Add bindings for the timer on the JH7110 RISC-V SoC
by StarFive Technology Ltd.

Reviewed-by: Krzysztof Kozlowski <[email protected]>
Signed-off-by: Xingyu Wu <[email protected]>
---
.../bindings/timer/starfive,jh7110-timer.yaml | 96 +++++++++++++++++++
1 file changed, 96 insertions(+)
create mode 100644 Documentation/devicetree/bindings/timer/starfive,jh7110-timer.yaml

diff --git a/Documentation/devicetree/bindings/timer/starfive,jh7110-timer.yaml b/Documentation/devicetree/bindings/timer/starfive,jh7110-timer.yaml
new file mode 100644
index 000000000000..9a2dac11eb06
--- /dev/null
+++ b/Documentation/devicetree/bindings/timer/starfive,jh7110-timer.yaml
@@ -0,0 +1,96 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/timer/starfive,jh7110-timer.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: StarFive JH7110 Timer
+
+maintainers:
+ - Xingyu Wu <[email protected]>
+ - Samin Guo <[email protected]>
+
+description:
+ This timer has four free-running 32 bit counters in StarFive JH7110 SoC.
+ And each channel(counter) triggers an interrupt when timeout. They support
+ one-shot mode and continuous-run mode.
+
+properties:
+ compatible:
+ const: starfive,jh7110-timer
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ items:
+ - description: channel 0
+ - description: channel 1
+ - description: channel 2
+ - description: channel 3
+
+ clocks:
+ items:
+ - description: timer APB
+ - description: channel 0
+ - description: channel 1
+ - description: channel 2
+ - description: channel 3
+
+ clock-names:
+ items:
+ - const: apb
+ - const: ch0
+ - const: ch1
+ - const: ch2
+ - const: ch3
+
+ resets:
+ items:
+ - description: timer APB
+ - description: channel 0
+ - description: channel 1
+ - description: channel 2
+ - description: channel 3
+
+ reset-names:
+ items:
+ - const: apb
+ - const: ch0
+ - const: ch1
+ - const: ch2
+ - const: ch3
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - clocks
+ - clock-names
+ - resets
+ - reset-names
+
+additionalProperties: false
+
+examples:
+ - |
+ timer@13050000 {
+ compatible = "starfive,jh7110-timer";
+ reg = <0x13050000 0x10000>;
+ interrupts = <69>, <70>, <71> ,<72>;
+ clocks = <&clk 124>,
+ <&clk 125>,
+ <&clk 126>,
+ <&clk 127>,
+ <&clk 128>;
+ clock-names = "apb", "ch0", "ch1",
+ "ch2", "ch3";
+ resets = <&rst 117>,
+ <&rst 118>,
+ <&rst 119>,
+ <&rst 120>,
+ <&rst 121>;
+ reset-names = "apb", "ch0", "ch1",
+ "ch2", "ch3";
+ };
+
--
2.17.1


2024-04-30 06:37:36

by Ziv Xu

[permalink] [raw]
Subject: 回复: [PATCH v10 2/3] clocksource: Add JH7110 timer driver



> -----?ʼ?ԭ??-----
> ??????: Ziv Xu
> ????ʱ??: 2024??4??12?? 16:46
> ?ռ???: Daniel Lezcano <[email protected]>; Thomas Gleixner
> <[email protected]>; Emil Renner Berthing
> <[email protected]>; Christophe JAILLET
> <[email protected]>
> ????: [email protected]; [email protected]; Rob Herring
> <[email protected]>; Krzysztof Kozlowski
> <[email protected]>; Paul Walmsley
> <[email protected]>; Palmer Dabbelt <[email protected]>; Albert
> Ou <[email protected]>; Philipp Zabel <[email protected]>; Walker
> Chen <[email protected]>; Xingyu Wu
> <[email protected]>; [email protected]; Conor Dooley
> <[email protected]>
> ????: [PATCH v10 2/3] clocksource: Add JH7110 timer driver
>
> From: Xingyu Wu <[email protected]>
>
> 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 <[email protected]>
> Signed-off-by: Xingyu Wu <[email protected]>
> ---
> 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 <[email protected]>
> +M: Xingyu Wu <[email protected]>
> +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 <[email protected]>
> M: Hal Feng <[email protected]>
> 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 <[email protected]>");
> +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

2024-04-30 10:19:11

by Emil Renner Berthing

[permalink] [raw]
Subject: Re: 回复: [PATCH v10 2/3] clocksource: Add JH7110 timer driver

Ziv Xu wrote:
>
>
> > -----邮件原件-----
> > 发件人: Ziv Xu
> > 发送时间: 2024年4月12日 16:46
> > 收件人: Daniel Lezcano <[email protected]>; Thomas Gleixner
> > <[email protected]>; Emil Renner Berthing
> > <[email protected]>; Christophe JAILLET
> > <[email protected]>
> > 抄送: [email protected]; [email protected]; Rob Herring
> > <[email protected]>; Krzysztof Kozlowski
> > <[email protected]>; Paul Walmsley
> > <[email protected]>; Palmer Dabbelt <[email protected]>; Albert
> > Ou <[email protected]>; Philipp Zabel <[email protected]>; Walker
> > Chen <[email protected]>; Xingyu Wu
> > <[email protected]>; [email protected]; Conor Dooley
> > <[email protected]>
> > 主题: [PATCH v10 2/3] clocksource: Add JH7110 timer driver
> >
> > From: Xingyu Wu <[email protected]>
> >
> > 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 <[email protected]>
> > Signed-off-by: Xingyu Wu <[email protected]>
> > ---
> > 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 <[email protected]>
> > +M: Xingyu Wu <[email protected]>
> > +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 <[email protected]>
> > M: Hal Feng <[email protected]>
> > 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 <[email protected]>");
> > +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

2024-05-07 08:38:01

by Ziv Xu

[permalink] [raw]
Subject: 回复: 回复: [PATCH v10 2/3] clocksource: Add JH7110 timer driver



> -----邮件原件-----
> 发件人: Emil Renner Berthing <[email protected]>
> 发送时间: 2024年4月30日 18:19
> 收件人: Ziv Xu <[email protected]>; Daniel Lezcano
> <[email protected]>; Thomas Gleixner <[email protected]>
> 抄送: [email protected]; [email protected]; Rob Herring
> <[email protected]>; Krzysztof Kozlowski
> <[email protected]>; Paul Walmsley
> <[email protected]>; Palmer Dabbelt <[email protected]>; Albert
> Ou <[email protected]>; Philipp Zabel <[email protected]>; Walker
> Chen <[email protected]>; Xingyu Wu
> <[email protected]>; [email protected]; Conor Dooley
> <[email protected]>
> 主题: Re: 回复: [PATCH v10 2/3] clocksource: Add JH7110 timer driver
>
> Ziv Xu wrote:
> >
> >
> > > -----邮件原件-----
> > > 发件人: Ziv Xu
> > > 发送时间: 2024年4月12日 16:46
> > > 收件人: Daniel Lezcano <[email protected]>; Thomas Gleixner
> > > <[email protected]>; Emil Renner Berthing
> > > <[email protected]>; Christophe JAILLET
> > > <[email protected]>
> > > 抄送: [email protected]; [email protected]; Rob
> > > Herring <[email protected]>; Krzysztof Kozlowski
> > > <[email protected]>; Paul Walmsley
> > > <[email protected]>; Palmer Dabbelt <[email protected]>;
> > > Albert Ou <[email protected]>; Philipp Zabel
> > > <[email protected]>; Walker Chen
> > > <[email protected]>; Xingyu Wu
> > > <[email protected]>; [email protected]; Conor
> > > Dooley <[email protected]>
> > > 主题: [PATCH v10 2/3] clocksource: Add JH7110 timer driver
> > >
> > > From: Xingyu Wu <[email protected]>
> > >
> > > 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 <[email protected]>
> > > Signed-off-by: Xingyu Wu <[email protected]>
> > > ---
> > > 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 <[email protected]>
> > > +M: Xingyu Wu <[email protected]>
> > > +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 <[email protected]>
> > > M: Hal Feng <[email protected]>
> > > 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 <[email protected]>");
> > > +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


Attachments:
log for 6.9-rc6.txt (30.32 kB)
log for 6.9-rc6.txt

2024-05-07 09:55:11

by Emil Renner Berthing

[permalink] [raw]
Subject: Re: 回复: 回复: [PATCH v10 2/3] clocksource: Add JH7110 timer driver

Ziv Xu wrote:
>
>
> > -----邮件原件-----
> > 发件人: Emil Renner Berthing <[email protected]>
> > 发送时间: 2024年4月30日 18:19
> > 收件人: Ziv Xu <[email protected]>; Daniel Lezcano
> > <[email protected]>; Thomas Gleixner <[email protected]>
> > 抄送: [email protected]; [email protected]; Rob Herring
> > <[email protected]>; Krzysztof Kozlowski
> > <[email protected]>; Paul Walmsley
> > <[email protected]>; Palmer Dabbelt <[email protected]>; Albert
> > Ou <[email protected]>; Philipp Zabel <[email protected]>; Walker
> > Chen <[email protected]>; Xingyu Wu
> > <[email protected]>; [email protected]; Conor Dooley
> > <[email protected]>
> > 主题: Re: 回复: [PATCH v10 2/3] clocksource: Add JH7110 timer driver
> >
> > Ziv Xu wrote:
> > >
> > >
> > > > -----邮件原件-----
> > > > 发件人: Ziv Xu
> > > > 发送时间: 2024年4月12日 16:46
> > > > 收件人: Daniel Lezcano <[email protected]>; Thomas Gleixner
> > > > <[email protected]>; Emil Renner Berthing
> > > > <[email protected]>; Christophe JAILLET
> > > > <[email protected]>
> > > > 抄送: [email protected]; [email protected]; Rob
> > > > Herring <[email protected]>; Krzysztof Kozlowski
> > > > <[email protected]>; Paul Walmsley
> > > > <[email protected]>; Palmer Dabbelt <[email protected]>;
> > > > Albert Ou <[email protected]>; Philipp Zabel
> > > > <[email protected]>; Walker Chen
> > > > <[email protected]>; Xingyu Wu
> > > > <[email protected]>; [email protected]; Conor
> > > > Dooley <[email protected]>
> > > > 主题: [PATCH v10 2/3] clocksource: Add JH7110 timer driver
> > > >
> > > > From: Xingyu Wu <[email protected]>
> > > >
> > > > 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 <[email protected]>
> > > > Signed-off-by: Xingyu Wu <[email protected]>
> > > > ---
> > > > 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 <[email protected]>
> > > > +M: Xingyu Wu <[email protected]>
> > > > +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 <[email protected]>
> > > > M: Hal Feng <[email protected]>
> > > > 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 <[email protected]>");
> > > > +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

2024-05-22 09:52:22

by Ziv Xu

[permalink] [raw]
Subject: 回复: 回复: 回复: [PATCH v10 2/3] clock source: Add JH7110 timer driver

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