[PATCH 2/2] clocksource/drivers/nxp-timer: Add the System Timer Module for the s32g platform

From: Daniel Lezcano
Date: Mon Mar 24 2025 - 06:00:46 EST


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.

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.

This driver provides the core code to support both platform but only
the s32g2 is configured. Adding the s32g3 STM support is
straighforward.

The first probed STM is used as a clocksource, the second will be the
broadcast timer and the rest are used as a clockevent with the
affinity set to a CPU. The rating is higher than the ARM architected
timers, so if they are enabled in the kernel configuration, they will
take over and used in place of the architected timers. The plaform
data is used to specify if a clocksource, a broadcast clockevent or a
per-cpu clockevent is desired thus allowing more flexibility in the
future to configure the STMs on the system.

Cc: Thomas Fossati <thomas.fossati@xxxxxxxxxx>
Co-developed-by: Larisa Grigore <Larisa.Grigore@xxxxxxx>
Signed-off-by: Larisa Grigore <Larisa.Grigore@xxxxxxx>
Co-developed-by: Ghennadi Procopciuc <ghennadi.procopciuc@xxxxxxx>
Signed-off-by: Ghennadi Procopciuc <ghennadi.procopciuc@xxxxxxx>
Co-developed-by: Daniel Lezcano <daniel.lezcano@xxxxxxxxxx>
Signed-off-by: Daniel Lezcano <daniel.lezcano@xxxxxxxxxx>
---
drivers/clocksource/Kconfig | 9 +
drivers/clocksource/Makefile | 2 +
drivers/clocksource/timer-nxp-stm.c | 524 ++++++++++++++++++++++++++++
3 files changed, 535 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..b67e438487ae
--- /dev/null
+++ b/drivers/clocksource/timer-nxp-stm.c
@@ -0,0 +1,524 @@
+// 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_address.h>
+#include <linux/of_irq.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/sched_clock.h>
+
+/*
+ * Each stm has 4 channels which take 0x10 Bytes register space
+ */
+#define STM_CHANNEL(n) (0x10 * ((n) + 1))
+
+#define STM_CCR 0x00
+#define STM_CCR_CEN BIT(0)
+
+#define STM_CIR 0x04
+#define STM_CIR_CIF BIT(0)
+
+#define STM_CMP 0x08
+
+#define STM_CR 0x00
+#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_CR_CPS(x) (((x) << STM_CR_CPS_OFFSET) & STM_CR_CPS_MASK)
+
+#define STM_CNT 0x04
+
+#define STM_ENABLE_MASK (STM_CR_FRZ | STM_CR_TEN)
+
+struct stm_clocksource {
+ struct clocksource cs;
+ int counter;
+};
+
+struct stm_clockevent {
+ struct clock_event_device ced;
+ unsigned long delta;
+};
+
+struct stm_timer {
+ void __iomem *base;
+ unsigned long rate;
+ union {
+ struct stm_clocksource scs;
+ struct stm_clockevent sce;
+ };
+};
+
+static DEFINE_PER_CPU(struct stm_timer *, stm_timers);
+
+static struct stm_timer *stm_sched_clock;
+
+/**
+ * struct stm_instances - a set of counter for the STM initialized
+ *
+ * @clocksource: an integer giving the number of initialized clocksource
+ * @clockevent_per_cpu: an integer giving the number of initialized clockevent per cpu
+ * @clockevent_broadcast: an integer giving the number of initialized broadcast clockevent
+ * @features: a set of flag telling what kind of timer to initialize
+ */
+struct stm_instances {
+ int clocksource;
+ int clockevent_per_cpu;
+ int clockevent_broadcast;
+ int features;
+};
+
+#define STM_CLKSRC BIT(0)
+#define STM_CLKEVT_PER_CPU BIT(1)
+#define STM_CLKEVT_BROADCAST BIT(2)
+
+static struct stm_clocksource *cs_to_scs(struct clocksource *cs)
+{
+ return container_of(cs, struct stm_clocksource, cs);
+}
+
+static struct stm_clockevent *ced_to_sced(struct clock_event_device *ced)
+{
+ return container_of(ced, struct stm_clockevent, ced);
+}
+
+static struct stm_timer *cs_to_stm(struct clocksource *cs)
+{
+ struct stm_clocksource *scs = cs_to_scs(cs);
+
+ return container_of(scs, struct stm_timer, scs);
+}
+
+static struct stm_timer *ced_to_stm(struct clock_event_device *ced)
+{
+ struct stm_clockevent *sce = ced_to_sced(ced);
+
+ return container_of(sce, struct stm_timer, sce);
+}
+
+static u64 notrace nxp_stm_read_sched_clock(void)
+{
+ return readl(stm_sched_clock->base + STM_CNT);
+}
+
+static u32 nxp_stm_clocksource_getcnt(struct stm_timer *stm_timer)
+{
+ return readl(stm_timer->base + STM_CNT);
+}
+
+static void nxp_stm_clocksource_setcnt(struct stm_timer *stm_timer, u32 cnt)
+{
+ writel(cnt, stm_timer->base + STM_CNT);
+}
+
+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 int nxp_stm_clocksource_enable(struct clocksource *cs)
+{
+ struct stm_timer *stm_timer = cs_to_stm(cs);
+ u32 reg;
+
+ reg = readl(stm_timer->base + STM_CR);
+ reg &= ~STM_CR_CPS_MASK;
+ reg |= STM_ENABLE_MASK;
+
+ writel(reg, stm_timer->base + STM_CR);
+
+ return 0;
+}
+
+static void nxp_stm_clocksource_disable(struct clocksource *cs)
+{
+ struct stm_timer *stm_timer = cs_to_stm(cs);
+ u32 reg;
+
+ reg = readl(stm_timer->base + STM_CR);
+ reg &= ~(STM_CR_CPS_MASK | STM_ENABLE_MASK);
+
+ writel(reg, stm_timer->base + STM_CR);
+}
+
+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->scs.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->scs.counter);
+ nxp_stm_clocksource_enable(cs);
+}
+
+static int __init nxp_stm_clocksource_init(struct device *dev, const char *name,
+ void __iomem *base, struct clk *clk)
+{
+ struct stm_timer *stm_timer;
+ int ret;
+
+ stm_timer = devm_kzalloc(dev, sizeof(*stm_timer), GFP_KERNEL);
+ if (!stm_timer)
+ return -ENOMEM;
+
+ stm_timer->base = base;
+ stm_timer->rate = clk_get_rate(clk);
+
+ stm_timer->scs.cs.name = name;
+ stm_timer->scs.cs.rating = 460;
+ stm_timer->scs.cs.read = nxp_stm_clocksource_read;
+ stm_timer->scs.cs.enable = nxp_stm_clocksource_enable;
+ stm_timer->scs.cs.disable = nxp_stm_clocksource_disable;
+ stm_timer->scs.cs.suspend = nxp_stm_clocksource_suspend;
+ stm_timer->scs.cs.resume = nxp_stm_clocksource_resume;
+ stm_timer->scs.cs.mask = CLOCKSOURCE_MASK(32);
+ stm_timer->scs.cs.flags = CLOCK_SOURCE_IS_CONTINUOUS;
+
+ ret = clocksource_register_hz(&stm_timer->scs.cs, stm_timer->rate);
+ if (ret)
+ return ret;
+
+ stm_sched_clock = stm_timer;
+
+ sched_clock_register(nxp_stm_read_sched_clock, 32, stm_timer->rate);
+
+ dev_set_drvdata(dev, stm_timer);
+
+ 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_timer->base + STM_CNT);
+}
+
+static void nxp_stm_clockevent_disable(struct stm_timer *stm_timer)
+{
+ /*
+ * The counter is shared between channels and will continue to
+ * be incremented. If STM_CMP value is too small, the next event can
+ * be lost if we don't disable the entire module.
+ * Disabling the entire module, makes STM not suitable as clocksource.
+ */
+ writel(0, stm_timer->base + STM_CR);
+ writel(0, stm_timer->base + STM_CHANNEL(0) + STM_CCR);
+}
+
+static void nxp_stm_clockevent_enable(struct stm_timer *stm_timer)
+{
+ u32 reg = readl(stm_timer->base + STM_CR);
+
+ reg &= ~STM_CR_CPS_MASK;
+ reg |= STM_ENABLE_MASK;
+
+ writel(reg, stm_timer->base + STM_CR);
+ writel(STM_CCR_CEN, stm_timer->base + STM_CHANNEL(0) + STM_CCR);
+}
+
+static void nxp_stm_clockevent_irq_clr(struct stm_timer *stm_timer)
+{
+ /* Clear the interrupt */
+ writel(STM_CIR_CIF, stm_timer->base + STM_CHANNEL(0) + STM_CIR);
+}
+
+static void nxp_stm_clockevent_irq_ack(struct stm_timer *stm_timer)
+{
+ u32 val;
+
+ nxp_stm_clockevent_irq_clr(stm_timer);
+
+ /*
+ * Update STM_CMP value using the counter value
+ */
+ val = nxp_stm_clockevent_read_counter(stm_timer) + stm_timer->sce.delta;
+
+ writel(val, stm_timer->base + STM_CHANNEL(0) + STM_CMP);
+}
+
+static irqreturn_t nxp_stm_clockevent_interrupt(int irq, void *dev_id)
+{
+ struct clock_event_device *ced = dev_id;
+ struct stm_timer *stm_timer = ced_to_stm(ced);
+
+ nxp_stm_clockevent_irq_ack(stm_timer);
+
+ /*
+ * 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 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->sce.delta = delta;
+
+ val = nxp_stm_clockevent_read_counter(stm_timer) + delta;
+
+ writel(val, stm_timer->base + STM_CHANNEL(0) + STM_CMP);
+
+ 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 int __init nxp_stm_clockevent_broadcast_init(struct device *dev, const char *name, void __iomem *base,
+ int irq, struct clk *clk)
+{
+ struct stm_timer *stm_timer;
+ int ret;
+
+ stm_timer = devm_kzalloc(dev, sizeof(*stm_timer), GFP_KERNEL);
+ if (!stm_timer)
+ return -ENOMEM;
+
+ stm_timer->base = base;
+ stm_timer->rate = clk_get_rate(clk);
+
+ stm_timer->sce.ced.name = name;
+ stm_timer->sce.ced.features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT;
+ stm_timer->sce.ced.set_state_shutdown = nxp_stm_clockevent_shutdown;
+ stm_timer->sce.ced.set_state_periodic = nxp_stm_clockevent_set_periodic;
+ stm_timer->sce.ced.set_next_event = nxp_stm_clockevent_set_next_event;
+ stm_timer->sce.ced.cpumask = cpu_possible_mask;
+ stm_timer->sce.ced.rating = 460;
+ stm_timer->sce.ced.irq = irq;
+
+ nxp_stm_clockevent_irq_clr(stm_timer);
+
+ ret = request_irq(irq, nxp_stm_clockevent_interrupt,
+ IRQF_TIMER | IRQF_NOBALANCING, name, &stm_timer->sce.ced);
+ if (ret) {
+ dev_err(dev, "Unable to allocate interrupt line: %d\n", ret);
+ return ret;
+ }
+
+ clockevents_config_and_register(&stm_timer->sce.ced, stm_timer->rate, 1, 0xffffffff);
+
+ dev_dbg(dev, "Registered broadcast clockevent %s irq=%d\n", name, irq);
+
+ return 0;
+}
+
+static int __init nxp_stm_clockevent_per_cpu_init(struct device *dev, const char *name, void __iomem *base,
+ int irq, struct clk *clk, int cpu)
+{
+ struct stm_timer *stm_timer;
+ int ret;
+
+ stm_timer = devm_kzalloc(dev, sizeof(*stm_timer), GFP_KERNEL);
+ if (!stm_timer)
+ return -ENOMEM;
+
+ stm_timer->base = base;
+ stm_timer->rate = clk_get_rate(clk);
+
+ stm_timer->sce.ced.name = name;
+ stm_timer->sce.ced.features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT;
+ stm_timer->sce.ced.set_state_shutdown = nxp_stm_clockevent_shutdown;
+ stm_timer->sce.ced.set_state_periodic = nxp_stm_clockevent_set_periodic;
+ stm_timer->sce.ced.set_next_event = nxp_stm_clockevent_set_next_event;
+ stm_timer->sce.ced.cpumask = cpumask_of(cpu);
+ stm_timer->sce.ced.rating = 460;
+ stm_timer->sce.ced.irq = irq;
+
+ nxp_stm_clockevent_irq_clr(stm_timer);
+
+ ret = request_irq(irq, nxp_stm_clockevent_interrupt,
+ IRQF_TIMER | IRQF_NOBALANCING, name, &stm_timer->sce.ced);
+ if (ret) {
+ dev_err(dev, "Unable to allocate interrupt line: %d\n", ret);
+ return ret;
+ }
+
+ per_cpu(stm_timers, cpu) = 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->sce.ced.irq, cpumask_of(cpu));
+ if (ret)
+ return ret;
+
+ clockevents_config_and_register(&stm_timer->sce.ced, stm_timer->rate, 1, 0xffffffff);
+
+ return 0;
+}
+
+static int __init nxp_stm_timer_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct stm_instances *stm_instances;
+ const char *name = of_node_full_name(np);
+ void __iomem *base;
+ int irq, ret;
+ struct clk *clk;
+
+ stm_instances = (typeof(stm_instances))of_device_get_match_data(dev);
+ if (!stm_instances) {
+ dev_err(dev, "No STM instances associated with a cpu");
+ return -EINVAL;
+ }
+
+ base = devm_of_iomap(dev, np, 0, NULL);
+ if (IS_ERR(base)) {
+ dev_err(dev, "Failed to iomap %pOFn\n", np);
+ return PTR_ERR(base);
+ }
+
+ irq = irq_of_parse_and_map(np, 0);
+ if (irq <= 0) {
+ dev_err(dev, "Failed to parse and map IRQ: %d\n", irq);
+ return -EINVAL;
+ }
+
+ clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(clk)) {
+ dev_err(dev, "Clock not found\n");
+ return PTR_ERR(clk);
+ }
+
+ ret = clk_prepare_enable(clk);
+ if (ret) {
+ dev_err(dev, "Failed to enable STM timer clock: %d\n", ret);
+ return ret;
+ }
+
+ if (!stm_instances->clocksource && (stm_instances->features & STM_CLKSRC)) {
+
+ /*
+ * First probed STM will be a clocksource
+ */
+ ret = nxp_stm_clocksource_init(dev, name, base, clk);
+ if (ret)
+ return ret;
+ stm_instances->clocksource++;
+
+ } else if (!stm_instances->clockevent_broadcast &&
+ (stm_instances->features & STM_CLKEVT_BROADCAST)) {
+
+ /*
+ * Second probed STM will be a broadcast clockevent
+ */
+ ret = nxp_stm_clockevent_broadcast_init(dev, name, base, irq, clk);
+ if (ret)
+ return ret;
+ stm_instances->clockevent_broadcast++;
+
+ } else if (stm_instances->clockevent_per_cpu < num_possible_cpus() &&
+ (stm_instances->features & STM_CLKEVT_PER_CPU)) {
+
+ /*
+ * 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, name, base, irq, clk,
+ stm_instances->clockevent_per_cpu);
+ if (ret)
+ return ret;
+
+ stm_instances->clockevent_per_cpu++;
+
+ /*
+ * 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->clockevent_per_cpu == 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 struct stm_instances s32g_stm_instances = { .features = STM_CLKSRC | STM_CLKEVT_PER_CPU };
+
+static const struct of_device_id nxp_stm_of_match[] = {
+ { .compatible = "nxp,s32g2-stm", &s32g_stm_instances },
+ { }
+};
+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 = of_match_ptr(nxp_stm_of_match),
+ },
+};
+module_platform_driver(nxp_stm_probe);
+
+MODULE_DESCRIPTION("NXP System Timer Module driver");
+MODULE_LICENSE("GPL");
--
2.43.0