* [PATCH v4 1/2] dt-bindings: timer: Add NXP System Timer Module
[not found] <20250402090714.3548055-1-daniel.lezcano@linaro.org>
@ 2025-04-02 9:07 ` Daniel Lezcano
2025-04-03 6:33 ` Ghennadi Procopciuc
2025-04-02 9:07 ` [PATCH v4 2/2] clocksource/drivers/nxp-timer: Add the System Timer Module for the s32gx platforms Daniel Lezcano
1 sibling, 1 reply; 7+ messages in thread
From: Daniel Lezcano @ 2025-04-02 9:07 UTC (permalink / raw)
To: daniel.lezcano, tglx
Cc: linux-kernel, thomas.fossati, Larisa.Grigore, ghennadi.procopciuc,
krzysztof.kozlowski, S32, Ghennadi Procopciuc, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Maxime Coquelin,
Alexandre Torgue,
open list:OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS,
moderated list:ARM/STM32 ARCHITECTURE,
moderated list:ARM/STM32 ARCHITECTURE
Add the System Timer Module description found on the NXP s32 platform
and the compatible for the s32g2 variant.
Cc: Ghennadi Procopciuc <ghennadi.procopciuc@oss.nxp.com>
Cc: Thomas Fossati <thomas.fossati@linaro.org>
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org>
---
.../bindings/timer/nxp,s32g2-stm.yaml | 53 +++++++++++++++++++
1 file changed, 53 insertions(+)
create mode 100644 Documentation/devicetree/bindings/timer/nxp,s32g2-stm.yaml
diff --git a/Documentation/devicetree/bindings/timer/nxp,s32g2-stm.yaml b/Documentation/devicetree/bindings/timer/nxp,s32g2-stm.yaml
new file mode 100644
index 000000000000..2016f346b2ee
--- /dev/null
+++ b/Documentation/devicetree/bindings/timer/nxp,s32g2-stm.yaml
@@ -0,0 +1,53 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/timer/nxp,s32g2-stm.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: NXP System Timer Module (STM)
+
+maintainers:
+ - Daniel Lezcano <daniel.lezcano@kernel.org>
+
+description:
+ The System Timer Module supports commonly required system and application
+ software timing functions. STM includes a 32-bit count-up timer and four
+ 32-bit compare channels with a separate interrupt source for each channel.
+ The timer is driven by the STM module clock divided by an 8-bit prescale
+ value.
+
+properties:
+ compatible:
+ oneOf:
+ - const: nxp,s32g2-stm
+ - items:
+ - const: nxp,s32g3-stm
+ - const: nxp,s32g2-stm
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+ clocks:
+ maxItems: 1
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - clocks
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+
+ timer@4011c000 {
+ compatible = "nxp,s32g2-stm";
+ reg = <0x4011c000 0x3000>;
+ interrupts = <GIC_SPI 24 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&clks 0x3b>;
+ };
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v4 2/2] clocksource/drivers/nxp-timer: Add the System Timer Module for the s32gx platforms
[not found] <20250402090714.3548055-1-daniel.lezcano@linaro.org>
2025-04-02 9:07 ` [PATCH v4 1/2] dt-bindings: timer: Add NXP System Timer Module Daniel Lezcano
@ 2025-04-02 9:07 ` Daniel Lezcano
2025-04-03 5:27 ` Ghennadi Procopciuc
1 sibling, 1 reply; 7+ messages in thread
From: Daniel Lezcano @ 2025-04-02 9:07 UTC (permalink / raw)
To: daniel.lezcano, tglx
Cc: linux-kernel, thomas.fossati, Larisa.Grigore, ghennadi.procopciuc,
krzysztof.kozlowski, S32, Ghennadi Procopciuc, Maxime Coquelin,
Alexandre Torgue, moderated list:ARM/STM32 ARCHITECTURE,
moderated list:ARM/STM32 ARCHITECTURE
STM supports commonly required system and application software timing
functions. STM includes a 32-bit count-up timer and four 32-bit
compare channels with a separate interrupt source for each
channel. The timer is driven by the STM module clock divided by an
8-bit prescale value (1 to 256).
STM has the following features:
• One 32-bit count-up timer with an 8-bit prescaler
• Four 32-bit compare channels
• An independent interrupt source for each channel
• Ability to stop the timer in Debug mode
The s32g platform is declined into two versions, the s32g2 and the
s32g3. The former has a STM block with 8 timers and the latter has 12
timers.
The platform is designed to have one usable STM instance per core on
the system which is composed of 3 x Cortex-M3 + 4 Cortex-A53 for the
s32g2 and 3 x Cortex-M3 + 8 Cortex-A53 for the s32g3.
There is a special STM instance called STM_TS which is dedicated to
the timestamp. The 7th STM instance STM_07 is directly tied to the
STM_TS which means it is not usable as a clockevent.
The driver instantiate each STM described in the device tree as a
clocksource and a clockevent conforming to the reference manual even
if the Linux system does not use all of the clocksource. Each
clockevent will have a cpumask set for a specific CPU.
Given the counter is shared between the clocksource and the
clockevent, the STM module can not be disabled by one or another so
the refcounting mechanism is used to stop the counter when it reaches
zero and to start it when it is one. The suspend and resume relies on
the refcount to stop the module.
As the device tree will have multiple STM entries, the driver can be
probed in parallel with the async option but it is not enabled
yet. However, the driver code takes care of preventing a race by
putting a lock to protect the number of STM instances global variable
which means it is ready to support the option when enough testing will
be done with the underlying time framework.
Cc: Ghennadi Procopciuc <ghennadi.procopciuc@oss.nxp.com>
Cc: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
Cc: Thomas Fossati <thomas.fossati@linaro.org>
Suggested-by: Ghennadi Procopciuc <ghennadi.procopciuc@nxp.com>
Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org>
---
drivers/clocksource/Kconfig | 9 +
drivers/clocksource/Makefile | 2 +
drivers/clocksource/timer-nxp-stm.c | 495 ++++++++++++++++++++++++++++
3 files changed, 506 insertions(+)
create mode 100644 drivers/clocksource/timer-nxp-stm.c
diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig
index 487c85259967..e86e327392af 100644
--- a/drivers/clocksource/Kconfig
+++ b/drivers/clocksource/Kconfig
@@ -763,4 +763,13 @@ config RALINK_TIMER
Enables support for system tick counter present on
Ralink SoCs RT3352 and MT7620.
+config NXP_STM_TIMER
+ bool "NXP System Timer Module driver"
+ depends on ARCH_S32 || COMPILE_TEST
+ select CLKSRC_MMIO
+ help
+ Support for NXP System Timer Module. It will create, in this
+ order, a clocksource, a broadcast clockevent and a per cpu
+ clockevent.
+
endmenu
diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile
index 43ef16a4efa6..c3a92e6b9f94 100644
--- a/drivers/clocksource/Makefile
+++ b/drivers/clocksource/Makefile
@@ -92,3 +92,5 @@ obj-$(CONFIG_GXP_TIMER) += timer-gxp.o
obj-$(CONFIG_CLKSRC_LOONGSON1_PWM) += timer-loongson1-pwm.o
obj-$(CONFIG_EP93XX_TIMER) += timer-ep93xx.o
obj-$(CONFIG_RALINK_TIMER) += timer-ralink.o
+obj-$(CONFIG_NXP_STM_TIMER) += timer-nxp-stm.o
+
diff --git a/drivers/clocksource/timer-nxp-stm.c b/drivers/clocksource/timer-nxp-stm.c
new file mode 100644
index 000000000000..8c542139033c
--- /dev/null
+++ b/drivers/clocksource/timer-nxp-stm.c
@@ -0,0 +1,495 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2016 Freescale Semiconductor, Inc.
+ * Copyright 2018,2021-2025 NXP
+ *
+ * NXP System Timer Module:
+ *
+ * STM supports commonly required system and application software
+ * timing functions. STM includes a 32-bit count-up timer and four
+ * 32-bit compare channels with a separate interrupt source for each
+ * channel. The timer is driven by the STM module clock divided by an
+ * 8-bit prescale value (1 to 256). It has ability to stop the timer
+ * in Debug mode
+ */
+#include <linux/clk.h>
+#include <linux/clockchips.h>
+#include <linux/cpuhotplug.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+#include <linux/sched_clock.h>
+#include <linux/units.h>
+
+#define STM_CR(__base) (__base)
+
+#define STM_CR_TEN BIT(0)
+#define STM_CR_FRZ BIT(1)
+#define STM_CR_CPS_OFFSET 8u
+#define STM_CR_CPS_MASK GENMASK(15, STM_CR_CPS_OFFSET)
+
+#define STM_CNT(__base) ((__base) + 0x04)
+
+#define STM_CCR0(__base) ((__base) + 0x10)
+#define STM_CCR1(__base) ((__base) + 0x20)
+#define STM_CCR2(__base) ((__base) + 0x30)
+#define STM_CCR3(__base) ((__base) + 0x40)
+
+#define STM_CCR_CEN BIT(0)
+
+#define STM_CIR0(__base) ((__base) + 0x14)
+#define STM_CIR1(__base) ((__base) + 0x24)
+#define STM_CIR2(__base) ((__base) + 0x34)
+#define STM_CIR3(__base) ((__base) + 0x44)
+
+#define STM_CIR_CIF BIT(0)
+
+#define STM_CMP0(__base) ((__base) + 0x18)
+#define STM_CMP1(__base) ((__base) + 0x28)
+#define STM_CMP2(__base) ((__base) + 0x38)
+#define STM_CMP3(__base) ((__base) + 0x48)
+
+#define STM_ENABLE_MASK (STM_CR_FRZ | STM_CR_TEN)
+
+struct stm_timer {
+ void __iomem *base;
+ unsigned long rate;
+ unsigned long delta;
+ unsigned long counter;
+ struct clock_event_device ced;
+ struct clocksource cs;
+ atomic_t refcnt;
+};
+
+static DEFINE_PER_CPU(struct stm_timer *, stm_timers);
+
+static struct stm_timer *stm_sched_clock;
+
+/*
+ * Global structure for multiple STMs initialization
+ */
+static int stm_instances;
+
+/*
+ * This global lock is used to prevent race conditions with the
+ * stm_instances in case the driver is using the ASYNC option
+ */
+static DEFINE_MUTEX(stm_instances_lock);
+
+DEFINE_GUARD(stm_instances, struct mutex *, mutex_lock(_T), mutex_unlock(_T))
+
+static struct stm_timer *cs_to_stm(struct clocksource *cs)
+{
+ return container_of(cs, struct stm_timer, cs);
+}
+
+static struct stm_timer *ced_to_stm(struct clock_event_device *ced)
+{
+ return container_of(ced, struct stm_timer, ced);
+}
+
+static u64 notrace nxp_stm_read_sched_clock(void)
+{
+ return readl(STM_CNT(stm_sched_clock->base));
+}
+
+static u32 nxp_stm_clocksource_getcnt(struct stm_timer *stm_timer)
+{
+ return readl(STM_CNT(stm_timer->base));
+}
+
+static void nxp_stm_clocksource_setcnt(struct stm_timer *stm_timer, u32 cnt)
+{
+ writel(cnt, STM_CNT(stm_timer->base));
+}
+
+static u64 nxp_stm_clocksource_read(struct clocksource *cs)
+{
+ struct stm_timer *stm_timer = cs_to_stm(cs);
+
+ return (u64)nxp_stm_clocksource_getcnt(stm_timer);
+}
+
+static void nxp_stm_module_enable(struct stm_timer *stm_timer)
+{
+ u32 reg;
+
+ reg = readl(STM_CR(stm_timer->base));
+
+ reg |= STM_ENABLE_MASK;
+
+ writel(reg, STM_CR(stm_timer->base));
+}
+
+static void nxp_stm_module_disable(struct stm_timer *stm_timer)
+{
+ u32 reg;
+
+ reg = readl(STM_CR(stm_timer->base));
+
+ reg &= ~STM_ENABLE_MASK;
+
+ writel(reg, STM_CR(stm_timer->base));
+}
+
+static void nxp_stm_module_put(struct stm_timer *stm_timer)
+{
+ if (atomic_dec_and_test(&stm_timer->refcnt))
+ nxp_stm_module_disable(stm_timer);
+}
+
+static void nxp_stm_module_get(struct stm_timer *stm_timer)
+{
+ if (atomic_inc_return(&stm_timer->refcnt) == 1)
+ nxp_stm_module_enable(stm_timer);
+}
+
+static int nxp_stm_clocksource_enable(struct clocksource *cs)
+{
+ struct stm_timer *stm_timer = cs_to_stm(cs);
+
+ nxp_stm_module_get(stm_timer);
+
+ return 0;
+}
+
+static void nxp_stm_clocksource_disable(struct clocksource *cs)
+{
+ struct stm_timer *stm_timer = cs_to_stm(cs);
+
+ nxp_stm_module_put(stm_timer);
+}
+
+static void nxp_stm_clocksource_suspend(struct clocksource *cs)
+{
+ struct stm_timer *stm_timer = cs_to_stm(cs);
+
+ nxp_stm_clocksource_disable(cs);
+ stm_timer->counter = nxp_stm_clocksource_getcnt(stm_timer);
+}
+
+static void nxp_stm_clocksource_resume(struct clocksource *cs)
+{
+ struct stm_timer *stm_timer = cs_to_stm(cs);
+
+ nxp_stm_clocksource_setcnt(stm_timer, stm_timer->counter);
+ nxp_stm_clocksource_enable(cs);
+}
+
+static void __init devm_clocksource_unregister(void *data)
+{
+ struct stm_timer *stm_timer = data;
+
+ clocksource_unregister(&stm_timer->cs);
+}
+
+static int __init nxp_stm_clocksource_init(struct device *dev, struct stm_timer *stm_timer,
+ const char *name, void __iomem *base, struct clk *clk)
+{
+ int ret;
+
+ stm_timer->base = base;
+ stm_timer->rate = clk_get_rate(clk);
+
+ stm_timer->cs.name = name;
+ stm_timer->cs.rating = 460;
+ stm_timer->cs.read = nxp_stm_clocksource_read;
+ stm_timer->cs.enable = nxp_stm_clocksource_enable;
+ stm_timer->cs.disable = nxp_stm_clocksource_disable;
+ stm_timer->cs.suspend = nxp_stm_clocksource_suspend;
+ stm_timer->cs.resume = nxp_stm_clocksource_resume;
+ stm_timer->cs.mask = CLOCKSOURCE_MASK(32);
+ stm_timer->cs.flags = CLOCK_SOURCE_IS_CONTINUOUS;
+
+ ret = clocksource_register_hz(&stm_timer->cs, stm_timer->rate);
+ if (ret)
+ return ret;
+
+ ret = devm_add_action_or_reset(dev, devm_clocksource_unregister, stm_timer);
+ if (ret) {
+ clocksource_unregister(&stm_timer->cs);
+ return ret;
+ }
+
+ stm_sched_clock = stm_timer;
+
+ sched_clock_register(nxp_stm_read_sched_clock, 32, stm_timer->rate);
+
+ dev_dbg(dev, "Registered clocksource %s\n", name);
+
+ return 0;
+}
+
+static int nxp_stm_clockevent_read_counter(struct stm_timer *stm_timer)
+{
+ return readl(STM_CNT(stm_timer->base));
+}
+
+static void nxp_stm_clockevent_disable(struct stm_timer *stm_timer)
+{
+ writel(0, STM_CCR0(stm_timer->base));
+}
+
+static void nxp_stm_clockevent_enable(struct stm_timer *stm_timer)
+{
+ writel(STM_CCR_CEN, STM_CCR0(stm_timer->base));
+}
+
+static int nxp_stm_clockevent_shutdown(struct clock_event_device *ced)
+{
+ struct stm_timer *stm_timer = ced_to_stm(ced);
+
+ nxp_stm_clockevent_disable(stm_timer);
+
+ return 0;
+}
+
+static int nxp_stm_clockevent_set_next_event(unsigned long delta, struct clock_event_device *ced)
+{
+ struct stm_timer *stm_timer = ced_to_stm(ced);
+ u32 val;
+
+ nxp_stm_clockevent_disable(stm_timer);
+
+ stm_timer->delta = delta;
+
+ val = nxp_stm_clockevent_read_counter(stm_timer) + delta;
+
+ writel(val, STM_CMP0(stm_timer->base));
+
+ /*
+ * The counter is shared across the channels and can not be
+ * stopped while we are setting the next event. If the delta
+ * is very small it is possible the counter increases above
+ * the computed 'val'. The min_delta value specified when
+ * registering the clockevent will prevent that. The second
+ * case is if the counter wraps while we compute the 'val' and
+ * before writing the comparator register. We read the counter,
+ * check if we are back in time and abort the timer with -ETIME.
+ */
+ if (val > nxp_stm_clockevent_read_counter(stm_timer) + delta)
+ return -ETIME;
+
+ nxp_stm_clockevent_enable(stm_timer);
+
+ return 0;
+}
+
+static int nxp_stm_clockevent_set_periodic(struct clock_event_device *ced)
+{
+ struct stm_timer *stm_timer = ced_to_stm(ced);
+
+ return nxp_stm_clockevent_set_next_event(stm_timer->rate, ced);
+}
+
+static void nxp_stm_clockevent_suspend(struct clock_event_device *ced)
+{
+ struct stm_timer *stm_timer = ced_to_stm(ced);
+
+ nxp_stm_module_put(stm_timer);
+}
+
+static void nxp_stm_clockevent_resume(struct clock_event_device *ced)
+{
+ struct stm_timer *stm_timer = ced_to_stm(ced);
+
+ nxp_stm_module_get(stm_timer);
+}
+
+static int __init nxp_stm_clockevent_per_cpu_init(struct device *dev, struct stm_timer *stm_timer,
+ const char *name, void __iomem *base, int irq,
+ struct clk *clk, int cpu)
+{
+ stm_timer->base = base;
+ stm_timer->rate = clk_get_rate(clk);
+
+ stm_timer->ced.name = name;
+ stm_timer->ced.features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT;
+ stm_timer->ced.set_state_shutdown = nxp_stm_clockevent_shutdown;
+ stm_timer->ced.set_state_periodic = nxp_stm_clockevent_set_periodic;
+ stm_timer->ced.set_next_event = nxp_stm_clockevent_set_next_event;
+ stm_timer->ced.suspend = nxp_stm_clockevent_suspend;
+ stm_timer->ced.resume = nxp_stm_clockevent_resume;
+ stm_timer->ced.cpumask = cpumask_of(cpu);
+ stm_timer->ced.rating = 460;
+ stm_timer->ced.irq = irq;
+
+ per_cpu(stm_timers, cpu) = stm_timer;
+
+ nxp_stm_module_get(stm_timer);
+
+ dev_dbg(dev, "Initialized per cpu clockevent name=%s, irq=%d, cpu=%d\n", name, irq, cpu);
+
+ return 0;
+}
+
+static int nxp_stm_clockevent_starting_cpu(unsigned int cpu)
+{
+ struct stm_timer *stm_timer = per_cpu(stm_timers, cpu);
+ int ret;
+
+ if (WARN_ON(!stm_timer))
+ return -EFAULT;
+
+ ret = irq_force_affinity(stm_timer->ced.irq, cpumask_of(cpu));
+ if (ret)
+ return ret;
+
+ /*
+ * The timings measurement show reading the counter register
+ * and writing to the comparator register takes as a maximum
+ * value 1100 ns at 133MHz rate frequency. The timer must be
+ * set above this value and to be secure we set the minimum
+ * value equal to 2000ns, so 2us.
+ *
+ * minimum ticks = (rate / MICRO) * 2
+ */
+ clockevents_config_and_register(&stm_timer->ced, stm_timer->rate,
+ (stm_timer->rate / MICRO) * 2, 0xffffffff);
+
+ return 0;
+}
+
+static irqreturn_t nxp_stm_module_interrupt(int irq, void *dev_id)
+{
+ struct stm_timer *stm_timer = dev_id;
+ struct clock_event_device *ced = &stm_timer->ced;
+ u32 val;
+
+ /*
+ * The interrupt is shared across the channels in the
+ * module. But this one is configured to run only one channel,
+ * consequently it is pointless to test the interrupt flags
+ * before and we can directly reset the channel 0 irq flag
+ * register.
+ */
+ writel(STM_CIR_CIF, STM_CIR0(stm_timer->base));
+
+ /*
+ * Update STM_CMP value using the counter value
+ */
+ val = nxp_stm_clockevent_read_counter(stm_timer) + stm_timer->delta;
+
+ writel(val, STM_CMP0(stm_timer->base));
+
+ /*
+ * stm hardware doesn't support oneshot, it will generate an
+ * interrupt and start the counter again so software need to
+ * disable the timer to stop the counter loop in ONESHOT mode.
+ */
+ if (likely(clockevent_state_oneshot(ced)))
+ nxp_stm_clockevent_disable(stm_timer);
+
+ ced->event_handler(ced);
+
+ return IRQ_HANDLED;
+}
+
+static int __init nxp_stm_timer_probe(struct platform_device *pdev)
+{
+ struct stm_timer *stm_timer;
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ const char *name = of_node_full_name(np);
+ struct clk *clk;
+ void __iomem *base;
+ int irq, ret;
+
+ /*
+ * The device tree can have multiple STM nodes described, so
+ * it makes this driver a good candidate for the async probe.
+ * It is still unclear if the time framework does correctly
+ * handle a parallel loading of the timers but at least this
+ * driver is ready to support the option.
+ */
+ guard(stm_instances)(&stm_instances_lock);
+
+ /*
+ * The S32Gx are SoCs featuring a diverse set of cores. Linux
+ * is expected to run on Cortex-A53 cores, while other
+ * software stacks will operate on Cortex-M cores. The number
+ * of STM instances has been sized to include at most one
+ * instance per core.
+ *
+ * As we need a clocksource and a clockevent per cpu, we
+ * simply initialize a clocksource per cpu along with the
+ * clockevent which makes the resulting code simpler.
+ *
+ * However if the device tree is describing more STM instances
+ * than the number of cores, then we ignore them.
+ */
+ if (stm_instances >= num_possible_cpus())
+ return 0;
+
+ base = devm_of_iomap(dev, np, 0, NULL);
+ if (IS_ERR(base))
+ return dev_err_probe(dev, PTR_ERR(base), "Failed to iomap %pOFn\n", np);
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return dev_err_probe(dev, irq, "Failed to get IRQ\n");
+
+ clk = devm_clk_get_enabled(dev, NULL);
+ if (IS_ERR(clk))
+ return dev_err_probe(dev, PTR_ERR(clk), "Clock not found\n");
+
+ stm_timer = devm_kzalloc(dev, sizeof(*stm_timer), GFP_KERNEL);
+ if (!stm_timer)
+ return -ENOMEM;
+
+ ret = devm_request_irq(dev, irq, nxp_stm_module_interrupt,
+ IRQF_TIMER | IRQF_NOBALANCING, name, stm_timer);
+ if (ret)
+ return dev_err_probe(dev, ret, "Unable to allocate interrupt line\n");
+
+ ret = nxp_stm_clocksource_init(dev, stm_timer, name, base, clk);
+ if (ret)
+ return ret;
+
+ /*
+ * Next probed STM will be a per CPU clockevent, until
+ * we probe as much as we have CPUs available on the
+ * system, we do a partial initialization
+ */
+ ret = nxp_stm_clockevent_per_cpu_init(dev, stm_timer, name,
+ base, irq, clk,
+ stm_instances);
+ if (ret)
+ return ret;
+
+ stm_instances++;
+
+ /*
+ * The number of probed STM for per CPU clockevent is
+ * equal to the number of available CPUs on the
+ * system. We install the cpu hotplug to finish the
+ * initialization by registering the clockevents
+ */
+ if (stm_instances == num_possible_cpus()) {
+ ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "STM timer:starting",
+ nxp_stm_clockevent_starting_cpu, NULL);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct of_device_id nxp_stm_of_match[] = {
+ { .compatible = "nxp,s32g2-stm" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, nxp_stm_of_match);
+
+static struct platform_driver nxp_stm_probe = {
+ .probe = nxp_stm_timer_probe,
+ .driver = {
+ .name = "nxp-stm",
+ .of_match_table = nxp_stm_of_match,
+ },
+};
+module_platform_driver(nxp_stm_probe);
+
+MODULE_DESCRIPTION("NXP System Timer Module driver");
+MODULE_LICENSE("GPL");
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH v4 2/2] clocksource/drivers/nxp-timer: Add the System Timer Module for the s32gx platforms
2025-04-02 9:07 ` [PATCH v4 2/2] clocksource/drivers/nxp-timer: Add the System Timer Module for the s32gx platforms Daniel Lezcano
@ 2025-04-03 5:27 ` Ghennadi Procopciuc
0 siblings, 0 replies; 7+ messages in thread
From: Ghennadi Procopciuc @ 2025-04-03 5:27 UTC (permalink / raw)
To: Daniel Lezcano, tglx
Cc: linux-kernel, thomas.fossati, Larisa.Grigore, ghennadi.procopciuc,
krzysztof.kozlowski, S32, Maxime Coquelin, Alexandre Torgue,
moderated list:ARM/STM32 ARCHITECTURE,
moderated list:ARM/STM32 ARCHITECTURE
On 4/2/2025 12:07 PM, Daniel Lezcano wrote:
> STM supports commonly required system and application software timing
> functions. STM includes a 32-bit count-up timer and four 32-bit
> compare channels with a separate interrupt source for each
> channel. The timer is driven by the STM module clock divided by an
> 8-bit prescale value (1 to 256).
>
> STM has the following features:
> • One 32-bit count-up timer with an 8-bit prescaler
> • Four 32-bit compare channels
> • An independent interrupt source for each channel
> • Ability to stop the timer in Debug mode
>
> The s32g platform is declined into two versions, the s32g2 and the
> s32g3. The former has a STM block with 8 timers and the latter has 12
> timers.
>
> The platform is designed to have one usable STM instance per core on
> the system which is composed of 3 x Cortex-M3 + 4 Cortex-A53 for the
> s32g2 and 3 x Cortex-M3 + 8 Cortex-A53 for the s32g3.
>
> There is a special STM instance called STM_TS which is dedicated to
> the timestamp. The 7th STM instance STM_07 is directly tied to the
> STM_TS which means it is not usable as a clockevent.
>
> The driver instantiate each STM described in the device tree as a
nitpick: instantiate -> instantiates
> clocksource and a clockevent conforming to the reference manual even
> if the Linux system does not use all of the clocksource. Each
> clockevent will have a cpumask set for a specific CPU.
>
> Given the counter is shared between the clocksource and the
> clockevent, the STM module can not be disabled by one or another so
> the refcounting mechanism is used to stop the counter when it reaches
> zero and to start it when it is one. The suspend and resume relies on
nitpick: relies -> rely
> the refcount to stop the module.
>
> As the device tree will have multiple STM entries, the driver can be
> probed in parallel with the async option but it is not enabled
> yet. However, the driver code takes care of preventing a race by
> putting a lock to protect the number of STM instances global variable
> which means it is ready to support the option when enough testing will
> be done with the underlying time framework.
>
> Cc: Ghennadi Procopciuc <ghennadi.procopciuc@oss.nxp.com>
> Cc: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
> Cc: Thomas Fossati <thomas.fossati@linaro.org>
> Suggested-by: Ghennadi Procopciuc <ghennadi.procopciuc@nxp.com>
> Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org>
> ---
> drivers/clocksource/Kconfig | 9 +
> drivers/clocksource/Makefile | 2 +
> drivers/clocksource/timer-nxp-stm.c | 495 ++++++++++++++++++++++++++++
> 3 files changed, 506 insertions(+)
> create mode 100644 drivers/clocksource/timer-nxp-stm.c
>
> diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig
> index 487c85259967..e86e327392af 100644
> --- a/drivers/clocksource/Kconfig
> +++ b/drivers/clocksource/Kconfig
> @@ -763,4 +763,13 @@ config RALINK_TIMER
> Enables support for system tick counter present on
> Ralink SoCs RT3352 and MT7620.
>
> +config NXP_STM_TIMER
> + bool "NXP System Timer Module driver"
> + depends on ARCH_S32 || COMPILE_TEST
> + select CLKSRC_MMIO
> + help
> + Support for NXP System Timer Module. It will create, in this
> + order, a clocksource, a broadcast clockevent and a per cpu
> + clockevent.
Is this description accurate? Will a broadcast event be created?
[ ... ]
> +
> +static int nxp_stm_clockevent_set_next_event(unsigned long delta, struct clock_event_device *ced)
> +{
> + struct stm_timer *stm_timer = ced_to_stm(ced);
> + u32 val;
> +
> + nxp_stm_clockevent_disable(stm_timer);
> +
> + stm_timer->delta = delta;
> +
> + val = nxp_stm_clockevent_read_counter(stm_timer) + delta;
> +
> + writel(val, STM_CMP0(stm_timer->base));
> +
> + /*
> + * The counter is shared across the channels and can not be
> + * stopped while we are setting the next event. If the delta
> + * is very small it is possible the counter increases above
> + * the computed 'val'. The min_delta value specified when
> + * registering the clockevent will prevent that. The second
> + * case is if the counter wraps while we compute the 'val' and
> + * before writing the comparator register. We read the counter,
> + * check if we are back in time and abort the timer with -ETIME.
> + */
> + if (val > nxp_stm_clockevent_read_counter(stm_timer) + delta)
> + return -ETIME;
In my opinion, there is still a race condition between the running
counter and the nxp_stm_clockevent_enable function. This means that the
counter may wrap around between the 'val >
nxp_stm_clockevent_read_counter(stm_timer) + delta' check and the actual
instruction part of the nxp_stm_clockevent_enable that enables the
interrupt. Moreover, this implementation seems to prohibit the case when
the value of val results after a wrap-around of the counter, which seems
like an artificial constraint to me.
In my view, the goal here is to identify the cases when the timer fires
while the comparator interrupt is disabled. There would be too many
cases to consider if using u32 only to store the current value of the
counter and the one to be written in the comparator. Therefore, to
simplify the implementation, I would work with u64. Here is the proposal
(not tested):
u64 val, ctime;
nxp_stm_clockevent_disable(stm_timer);
ctime = nxp_stm_clockevent_read_counter(stm_timer);
/* Build the targeted time using u64, which is not impacted by wraps
since both added values are u32 */
val = ctime + delta;
/* Ensure we have enough time to set up the interrupt without generating
a false one */
writel(lower_32_bits(ctime) - 1, STM_CMP0(stm_timer->base));
nxp_stm_clockevent_enable(stm_timer);
ctime = nxp_stm_clockevent_read_counter(stm_timer);
/* The delta has already passed */
if (ctime > lower_32_bits(val)) {
nxp_stm_clockevent_disable(stm_timer);
return -ETIME;
}
/* Keep the 32 LSB bits of the val */
writel(lower_32_bits(val), STM_CMP0(stm_timer->base));
[ ... ]
> +static irqreturn_t nxp_stm_module_interrupt(int irq, void *dev_id)
> +{
> + struct stm_timer *stm_timer = dev_id;
> + struct clock_event_device *ced = &stm_timer->ced;
> + u32 val;
> +
> + /*
> + * The interrupt is shared across the channels in the
> + * module. But this one is configured to run only one channel,
> + * consequently it is pointless to test the interrupt flags
> + * before and we can directly reset the channel 0 irq flag
> + * register.
> + */
> + writel(STM_CIR_CIF, STM_CIR0(stm_timer->base));
> +
> + /*
> + * Update STM_CMP value using the counter value
> + */
> + val = nxp_stm_clockevent_read_counter(stm_timer) + stm_timer->delta;
> +
> + writel(val, STM_CMP0(stm_timer->base));
> +
> + /*
> + * stm hardware doesn't support oneshot, it will generate an
> + * interrupt and start the counter again so software need to
nitpick: software need -> software needs
> + * disable the timer to stop the counter loop in ONESHOT mode.
> + */
> + if (likely(clockevent_state_oneshot(ced)))
> + nxp_stm_clockevent_disable(stm_timer);
> +
> + ced->event_handler(ced);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int __init nxp_stm_timer_probe(struct platform_device *pdev)
> +{
> + struct stm_timer *stm_timer;
> + struct device *dev = &pdev->dev;
> + struct device_node *np = dev->of_node;
> + const char *name = of_node_full_name(np);
> + struct clk *clk;
> + void __iomem *base;
> + int irq, ret;
> +
> + /*
> + * The device tree can have multiple STM nodes described, so
> + * it makes this driver a good candidate for the async probe.
> + * It is still unclear if the time framework does correctly
> + * handle a parallel loading of the timers but at least this
nitpick: does correctly handle a parallel loading -> correctly handles
parallel loading
> + * driver is ready to support the option.
> + */
> + guard(stm_instances)(&stm_instances_lock);
> +
> + /*
> + * The S32Gx are SoCs featuring a diverse set of cores. Linux
> + * is expected to run on Cortex-A53 cores, while other
> + * software stacks will operate on Cortex-M cores. The number
> + * of STM instances has been sized to include at most one
> + * instance per core.
> + *
> + * As we need a clocksource and a clockevent per cpu, we
> + * simply initialize a clocksource per cpu along with the
> + * clockevent which makes the resulting code simpler.
> + *
> + * However if the device tree is describing more STM instances
> + * than the number of cores, then we ignore them.
> + */
> + if (stm_instances >= num_possible_cpus())
> + return 0;
> +
> + base = devm_of_iomap(dev, np, 0, NULL);
> + if (IS_ERR(base))
> + return dev_err_probe(dev, PTR_ERR(base), "Failed to iomap %pOFn\n", np);
> +
> + irq = platform_get_irq(pdev, 0);
> + if (irq < 0)
> + return dev_err_probe(dev, irq, "Failed to get IRQ\n");
> +
> + clk = devm_clk_get_enabled(dev, NULL);
> + if (IS_ERR(clk))
> + return dev_err_probe(dev, PTR_ERR(clk), "Clock not found\n");
> +
> + stm_timer = devm_kzalloc(dev, sizeof(*stm_timer), GFP_KERNEL);
> + if (!stm_timer)
> + return -ENOMEM;
> +
> + ret = devm_request_irq(dev, irq, nxp_stm_module_interrupt,
> + IRQF_TIMER | IRQF_NOBALANCING, name, stm_timer);
> + if (ret)
> + return dev_err_probe(dev, ret, "Unable to allocate interrupt line\n");
> +
> + ret = nxp_stm_clocksource_init(dev, stm_timer, name, base, clk);
> + if (ret)
> + return ret;
> +
> + /*
> + * Next probed STM will be a per CPU clockevent, until
> + * we probe as much as we have CPUs available on the
nitpick: as much -> as many
> + * system, we do a partial initialization
> + */
> + ret = nxp_stm_clockevent_per_cpu_init(dev, stm_timer, name,
> + base, irq, clk,
> + stm_instances);
> + if (ret)
> + return ret;
> +
> + stm_instances++;
> +
> + /*
> + * The number of probed STM for per CPU clockevent is
nitpick: STM -> STMs
> + * equal to the number of available CPUs on the
> + * system. We install the cpu hotplug to finish the
> + * initialization by registering the clockevents
> + */
> + if (stm_instances == num_possible_cpus()) {
> + ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "STM timer:starting",
> + nxp_stm_clockevent_starting_cpu, NULL);
> + if (ret < 0)
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static const struct of_device_id nxp_stm_of_match[] = {
> + { .compatible = "nxp,s32g2-stm" },
> + { }
> +};
> +MODULE_DEVICE_TABLE(of, nxp_stm_of_match);
> +
> +static struct platform_driver nxp_stm_probe = {
> + .probe = nxp_stm_timer_probe,
> + .driver = {
> + .name = "nxp-stm",
> + .of_match_table = nxp_stm_of_match,
> + },
> +};
> +module_platform_driver(nxp_stm_probe);
> +
> +MODULE_DESCRIPTION("NXP System Timer Module driver");
> +MODULE_LICENSE("GPL");
--
Regards,
Ghennadi
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v4 1/2] dt-bindings: timer: Add NXP System Timer Module
2025-04-02 9:07 ` [PATCH v4 1/2] dt-bindings: timer: Add NXP System Timer Module Daniel Lezcano
@ 2025-04-03 6:33 ` Ghennadi Procopciuc
2025-04-03 15:21 ` Daniel Lezcano
0 siblings, 1 reply; 7+ messages in thread
From: Ghennadi Procopciuc @ 2025-04-03 6:33 UTC (permalink / raw)
To: Daniel Lezcano, tglx
Cc: linux-kernel, thomas.fossati, Larisa.Grigore, ghennadi.procopciuc,
krzysztof.kozlowski, S32, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Maxime Coquelin, Alexandre Torgue,
open list:OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS,
moderated list:ARM/STM32 ARCHITECTURE,
moderated list:ARM/STM32 ARCHITECTURE
On 4/2/2025 12:07 PM, Daniel Lezcano wrote:
[ ... ]
> +examples:
> + - |
> + #include <dt-bindings/interrupt-controller/arm-gic.h>
> +
> + timer@4011c000 {
> + compatible = "nxp,s32g2-stm";
> + reg = <0x4011c000 0x3000>;
> + interrupts = <GIC_SPI 24 IRQ_TYPE_LEVEL_HIGH>;
> + clocks = <&clks 0x3b>;
> + };
The S32G reference manual specifies two clocks for the STM module: one
for the registers and another for the counter itself. Shouldn't both
clocks be represented in the bindings?
--
Regards,
Ghennadi
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v4 1/2] dt-bindings: timer: Add NXP System Timer Module
2025-04-03 6:33 ` Ghennadi Procopciuc
@ 2025-04-03 15:21 ` Daniel Lezcano
2025-04-07 7:37 ` Daniel Lezcano
0 siblings, 1 reply; 7+ messages in thread
From: Daniel Lezcano @ 2025-04-03 15:21 UTC (permalink / raw)
To: Ghennadi Procopciuc, tglx
Cc: linux-kernel, thomas.fossati, Larisa.Grigore, ghennadi.procopciuc,
krzysztof.kozlowski, S32, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Maxime Coquelin, Alexandre Torgue,
open list:OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS,
moderated list:ARM/STM32 ARCHITECTURE,
moderated list:ARM/STM32 ARCHITECTURE
On 03/04/2025 08:33, Ghennadi Procopciuc wrote:
> On 4/2/2025 12:07 PM, Daniel Lezcano wrote:
> [ ... ]
>> +examples:
>> + - |
>> + #include <dt-bindings/interrupt-controller/arm-gic.h>
>> +
>> + timer@4011c000 {
>> + compatible = "nxp,s32g2-stm";
>> + reg = <0x4011c000 0x3000>;
>> + interrupts = <GIC_SPI 24 IRQ_TYPE_LEVEL_HIGH>;
>> + clocks = <&clks 0x3b>;
>> + };
>
> The S32G reference manual specifies two clocks for the STM module: one
> for the registers and another for the counter itself. Shouldn't both
> clocks be represented in the bindings?
AFAICS, there are two clocks as described in the documentation for the
s32g2 page 843, section 23.7.3 Timer modules.
The module and the register clock are fed by the XBAR_DIV3_CLK which is
an system clock always-on.
page 1994, 40.5.4 Clocking, the documentation says: "This module has no
clocking considerations."
From my understanding, we should not describe the XBAR_DIV3_CLK as it
is a system clock.
--
<http://www.linaro.org/> Linaro.org │ Open source software for ARM SoCs
Follow Linaro: <http://www.facebook.com/pages/Linaro> Facebook |
<http://twitter.com/#!/linaroorg> Twitter |
<http://www.linaro.org/linaro-blog/> Blog
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v4 1/2] dt-bindings: timer: Add NXP System Timer Module
2025-04-03 15:21 ` Daniel Lezcano
@ 2025-04-07 7:37 ` Daniel Lezcano
2025-04-14 14:31 ` Ghennadi Procopciuc
0 siblings, 1 reply; 7+ messages in thread
From: Daniel Lezcano @ 2025-04-07 7:37 UTC (permalink / raw)
To: Ghennadi Procopciuc, tglx
Cc: linux-kernel, thomas.fossati, Larisa.Grigore, ghennadi.procopciuc,
krzysztof.kozlowski, S32, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Maxime Coquelin, Alexandre Torgue,
open list:OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS,
moderated list:ARM/STM32 ARCHITECTURE,
moderated list:ARM/STM32 ARCHITECTURE
On 03/04/2025 17:21, Daniel Lezcano wrote:
> On 03/04/2025 08:33, Ghennadi Procopciuc wrote:
>> On 4/2/2025 12:07 PM, Daniel Lezcano wrote:
>> [ ... ]
>>> +examples:
>>> + - |
>>> + #include <dt-bindings/interrupt-controller/arm-gic.h>
>>> +
>>> + timer@4011c000 {
>>> + compatible = "nxp,s32g2-stm";
>>> + reg = <0x4011c000 0x3000>;
>>> + interrupts = <GIC_SPI 24 IRQ_TYPE_LEVEL_HIGH>;
>>> + clocks = <&clks 0x3b>;
>>> + };
>>
>> The S32G reference manual specifies two clocks for the STM module: one
>> for the registers and another for the counter itself. Shouldn't both
>> clocks be represented in the bindings?
>
> AFAICS, there are two clocks as described in the documentation for the
> s32g2 page 843, section 23.7.3 Timer modules.
>
> The module and the register clock are fed by the XBAR_DIV3_CLK which is
> an system clock always-on.
>
> page 1994, 40.5.4 Clocking, the documentation says: "This module has no
> clocking considerations."
>
> From my understanding, we should not describe the XBAR_DIV3_CLK as it
> is a system clock.
Can you clarify for the STM if you still want to change the binding ?
--
<http://www.linaro.org/> Linaro.org │ Open source software for ARM SoCs
Follow Linaro: <http://www.facebook.com/pages/Linaro> Facebook |
<http://twitter.com/#!/linaroorg> Twitter |
<http://www.linaro.org/linaro-blog/> Blog
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v4 1/2] dt-bindings: timer: Add NXP System Timer Module
2025-04-07 7:37 ` Daniel Lezcano
@ 2025-04-14 14:31 ` Ghennadi Procopciuc
0 siblings, 0 replies; 7+ messages in thread
From: Ghennadi Procopciuc @ 2025-04-14 14:31 UTC (permalink / raw)
To: Daniel Lezcano, tglx
Cc: linux-kernel, thomas.fossati, Larisa.Grigore, ghennadi.procopciuc,
krzysztof.kozlowski, S32, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Maxime Coquelin, Alexandre Torgue,
open list:OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS,
moderated list:ARM/STM32 ARCHITECTURE,
moderated list:ARM/STM32 ARCHITECTURE
On 4/7/2025 10:37 AM, Daniel Lezcano wrote:
> On 03/04/2025 17:21, Daniel Lezcano wrote:
>> On 03/04/2025 08:33, Ghennadi Procopciuc wrote:
>>> On 4/2/2025 12:07 PM, Daniel Lezcano wrote:
>>> [ ... ]
>>>> +examples:
>>>> + - |
>>>> + #include <dt-bindings/interrupt-controller/arm-gic.h>
>>>> +
>>>> + timer@4011c000 {
>>>> + compatible = "nxp,s32g2-stm";
>>>> + reg = <0x4011c000 0x3000>;
>>>> + interrupts = <GIC_SPI 24 IRQ_TYPE_LEVEL_HIGH>;
>>>> + clocks = <&clks 0x3b>;
>>>> + };
>>>
>>> The S32G reference manual specifies two clocks for the STM module: one
>>> for the registers and another for the counter itself. Shouldn't both
>>> clocks be represented in the bindings?
>>
>> AFAICS, there are two clocks as described in the documentation for the
>> s32g2 page 843, section 23.7.3 Timer modules.
>>
>> The module and the register clock are fed by the XBAR_DIV3_CLK which
>> is an system clock always-on.
>>
>> page 1994, 40.5.4 Clocking, the documentation says: "This module has
>> no clocking considerations."
>>
>> From my understanding, we should not describe the XBAR_DIV3_CLK as it
>> is a system clock.
>
> Can you clarify for the STM if you still want to change the binding ?
>
Yes, I still believe the suggestion is relevant.
--
Regards,
Ghennadi
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2025-04-14 14:40 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
[not found] <20250402090714.3548055-1-daniel.lezcano@linaro.org>
2025-04-02 9:07 ` [PATCH v4 1/2] dt-bindings: timer: Add NXP System Timer Module Daniel Lezcano
2025-04-03 6:33 ` Ghennadi Procopciuc
2025-04-03 15:21 ` Daniel Lezcano
2025-04-07 7:37 ` Daniel Lezcano
2025-04-14 14:31 ` Ghennadi Procopciuc
2025-04-02 9:07 ` [PATCH v4 2/2] clocksource/drivers/nxp-timer: Add the System Timer Module for the s32gx platforms Daniel Lezcano
2025-04-03 5:27 ` Ghennadi Procopciuc
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).