public inbox for devicetree@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH v5 0/3] Add the System Timer Module counter
@ 2026-01-13 16:52 Daniel Lezcano
  2026-01-13 16:52 ` [PATCH v5 1/3] counters: Reorder the Makefile Daniel Lezcano
                   ` (2 more replies)
  0 siblings, 3 replies; 8+ messages in thread
From: Daniel Lezcano @ 2026-01-13 16:52 UTC (permalink / raw)
  To: wbg
  Cc: Frank.li, robh, conor+dt, krzk+dt, s32, devicetree, linux-kernel,
	linux-iio

The NXP S32 family provides a System Timer Module (STM), a 32-bit
free-running counter clocked from a peripheral clock. The STM includes
a prescaler and one or more compare channels generating optional
interrupts. When used as a generic hardware counter, only the main
free-running counter is required, while the compare channels are
typically unused.

On S32G2 devices, the STM is exposed as a simple counter block that
can operate continuously and be shared across subsystems such as the
Linux kernel, firmware components running on Cortex-M7 cores, or other
co-processors. The counter can be read atomically and provides a
stable timestamp source to correlate events occurring in different
execution contexts.

The Linux kernel controls the STM through a memory-mapped interface,
configuring the prescaler, enabling or disabling the counter, and
accounting for wrap-arounds. Other subsystems access the counter in
read-only mode, making it a shared timestamp reference across the
platform.

This driver adds support for the STM when used as a counter on S32G2
platforms. The device is described in the device tree using the
following compatible:

compatible = "nxp,s32g2-stm-cnt";

The driver exposes basic counter functionality: start, stop, reset,
prescaler configuration, and overflow handling.

Changelog:
	* v5
	  - Use a lockless version to implement accumulated time (Frank Li)

	* v4
	  - Split context structure to suspend/resume
	  - Converted counter to a u64 to accumulate the time
	  - Replaced 'reset' to a count write to reset (William Breathitt Gray)
	  - Added events for userspace (William Breathitt Gray)
	  - Added action COUNTER_SYNAPSE_ACTION_RISING_EDGE for signal (William Breathitt Gray)
	  - Renamed counter name to "System Timer Module Counter" (William Breathitt Gray)

	* v3
	  - Fixed compatible typo "nxp,s32g2-stm" to "nxp,s32g2-stm-cnt"

	* v2
	  - Added Rob's tag
	  ** kbuild
	  - Reordered alphabetically the headers
	  - Added bitfield.h header
	  - Use DEFINE_SIMPLE_DEV_PM_OPS() and pm_sleep_ptr()

Daniel Lezcano (3):
  counters: Reorder the Makefile
  dt-bindings: counter: Add NXP System Timer Module Counter
  counter: Add STM based counter

 .../bindings/counter/nxp,s32g2-stm-cnt.yaml   |  64 +++
 arch/arm64/boot/dts/freescale/s32g2.dtsi      |   6 +-
 .../boot/dts/freescale/s32g274a-rdb2.dts      |  10 +-
 drivers/counter/Kconfig                       |  10 +
 drivers/counter/Makefile                      |  21 +-
 drivers/counter/nxp-stm-cnt.c                 | 432 ++++++++++++++++++
 6 files changed, 524 insertions(+), 19 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/counter/nxp,s32g2-stm-cnt.yaml
 create mode 100644 drivers/counter/nxp-stm-cnt.c

-- 
2.43.0


^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH v5 1/3] counters: Reorder the Makefile
  2026-01-13 16:52 [PATCH v5 0/3] Add the System Timer Module counter Daniel Lezcano
@ 2026-01-13 16:52 ` Daniel Lezcano
  2026-03-21 13:38   ` William Breathitt Gray
  2026-01-13 16:52 ` [PATCH v5 2/3] dt-bindings: counter: Add NXP System Timer Module Counter Daniel Lezcano
  2026-01-13 16:52 ` [PATCH v5 3/3] counter: Add STM based counter Daniel Lezcano
  2 siblings, 1 reply; 8+ messages in thread
From: Daniel Lezcano @ 2026-01-13 16:52 UTC (permalink / raw)
  To: wbg
  Cc: Frank.li, robh, conor+dt, krzk+dt, s32, devicetree, linux-kernel,
	linux-iio

The next changes provide a new driver. For the sake of clarity,
reorder the Makefile alphabetically.

No functional changes intended.

Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org>
---
 drivers/counter/Makefile | 20 +++++++++++---------
 1 file changed, 11 insertions(+), 9 deletions(-)

diff --git a/drivers/counter/Makefile b/drivers/counter/Makefile
index fa3c1d08f706..40e644948e7a 100644
--- a/drivers/counter/Makefile
+++ b/drivers/counter/Makefile
@@ -6,14 +6,16 @@
 obj-$(CONFIG_COUNTER) += counter.o
 counter-y := counter-core.o counter-sysfs.o counter-chrdev.o
 
-obj-$(CONFIG_I8254)		+= i8254.o
-obj-$(CONFIG_104_QUAD_8)	+= 104-quad-8.o
+obj-$(CONFIG_104_QUAD_8)		+= 104-quad-8.o
+obj-$(CONFIG_FTM_QUADDEC)		+= ftm-quaddec.o
+obj-$(CONFIG_I8254)			+= i8254.o
+obj-$(CONFIG_INTEL_QEP)			+= intel-qep.o
 obj-$(CONFIG_INTERRUPT_CNT)		+= interrupt-cnt.o
-obj-$(CONFIG_RZ_MTU3_CNT)	+= rz-mtu3-cnt.o
-obj-$(CONFIG_STM32_TIMER_CNT)	+= stm32-timer-cnt.o
-obj-$(CONFIG_STM32_LPTIMER_CNT)	+= stm32-lptimer-cnt.o
-obj-$(CONFIG_TI_EQEP)		+= ti-eqep.o
-obj-$(CONFIG_FTM_QUADDEC)	+= ftm-quaddec.o
 obj-$(CONFIG_MICROCHIP_TCB_CAPTURE)	+= microchip-tcb-capture.o
-obj-$(CONFIG_INTEL_QEP)		+= intel-qep.o
-obj-$(CONFIG_TI_ECAP_CAPTURE)	+= ti-ecap-capture.o
+obj-$(CONFIG_RZ_MTU3_CNT)		+= rz-mtu3-cnt.o
+obj-$(CONFIG_STM32_TIMER_CNT)		+= stm32-timer-cnt.o
+obj-$(CONFIG_STM32_LPTIMER_CNT)		+= stm32-lptimer-cnt.o
+obj-$(CONFIG_TI_ECAP_CAPTURE)		+= ti-ecap-capture.o
+obj-$(CONFIG_TI_EQEP)			+= ti-eqep.o
+
+
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH v5 2/3] dt-bindings: counter: Add NXP System Timer Module Counter
  2026-01-13 16:52 [PATCH v5 0/3] Add the System Timer Module counter Daniel Lezcano
  2026-01-13 16:52 ` [PATCH v5 1/3] counters: Reorder the Makefile Daniel Lezcano
@ 2026-01-13 16:52 ` Daniel Lezcano
  2026-01-13 16:52 ` [PATCH v5 3/3] counter: Add STM based counter Daniel Lezcano
  2 siblings, 0 replies; 8+ messages in thread
From: Daniel Lezcano @ 2026-01-13 16:52 UTC (permalink / raw)
  To: wbg
  Cc: Frank.li, robh, conor+dt, krzk+dt, s32, devicetree, linux-kernel,
	linux-iio

Add the System Timer Module description found on the NXP s32 platform
when it is used as a counter and the compatible for the s32g2 variant.

Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org>
---
 .../bindings/counter/nxp,s32g2-stm-cnt.yaml   | 64 +++++++++++++++++++
 arch/arm64/boot/dts/freescale/s32g2.dtsi      |  6 +-
 .../boot/dts/freescale/s32g274a-rdb2.dts      | 10 +--
 3 files changed, 70 insertions(+), 10 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/counter/nxp,s32g2-stm-cnt.yaml

diff --git a/Documentation/devicetree/bindings/counter/nxp,s32g2-stm-cnt.yaml b/Documentation/devicetree/bindings/counter/nxp,s32g2-stm-cnt.yaml
new file mode 100644
index 000000000000..4d42996f5ad3
--- /dev/null
+++ b/Documentation/devicetree/bindings/counter/nxp,s32g2-stm-cnt.yaml
@@ -0,0 +1,64 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/counter/nxp,s32g2-stm-cnt.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 counter is driven by the STM module clock divided by an 8-bit prescale
+  value.
+
+properties:
+  compatible:
+    oneOf:
+      - const: nxp,s32g2-stm-cnt
+      - items:
+          - const: nxp,s32g3-stm-cnt
+          - const: nxp,s32g2-stm-cnt
+
+  reg:
+    maxItems: 1
+
+  interrupts:
+    maxItems: 1
+
+  clocks:
+    items:
+      - description: Counter clock
+      - description: Module clock
+      - description: Register clock
+
+  clock-names:
+    items:
+      - const: counter
+      - const: module
+      - const: register
+
+required:
+  - compatible
+  - reg
+  - interrupts
+  - clocks
+  - clock-names
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/interrupt-controller/arm-gic.h>
+
+    timer@4011c000 {
+        compatible = "nxp,s32g2-stm-cnt";
+        reg = <0x4011c000 0x3000>;
+        interrupts = <GIC_SPI 24 IRQ_TYPE_LEVEL_HIGH>;
+        clocks = <&clks 0x3b>, <&clks 0x3c>, <&clks 0x3c>;
+        clock-names = "counter", "module", "register";
+    };
diff --git a/arch/arm64/boot/dts/freescale/s32g2.dtsi b/arch/arm64/boot/dts/freescale/s32g2.dtsi
index 51d00dac12de..6bc0c75b574f 100644
--- a/arch/arm64/boot/dts/freescale/s32g2.dtsi
+++ b/arch/arm64/boot/dts/freescale/s32g2.dtsi
@@ -579,7 +579,7 @@ swt6: watchdog@40208000 {
 		};
 
 		stm4: timer@4021c000 {
-			compatible = "nxp,s32g2-stm";
+			compatible = "nxp,s32g2-stm-cnt";
 			reg = <0x4021c000 0x3000>;
 			clocks = <&clks 0x3b>, <&clks 0x3c>, <&clks 0x3c>;
 			clock-names = "counter", "module", "register";
@@ -588,7 +588,7 @@ stm4: timer@4021c000 {
 		};
 
 		stm5: timer@40220000 {
-			compatible = "nxp,s32g2-stm";
+			compatible = "nxp,s32g2-stm-cnt";
 			reg = <0x40220000 0x3000>;
 			clocks = <&clks 0x3b>, <&clks 0x3c>, <&clks 0x3c>;
 			clock-names = "counter", "module", "register";
@@ -597,7 +597,7 @@ stm5: timer@40220000 {
 		};
 
 		stm6: timer@40224000 {
-			compatible = "nxp,s32g2-stm";
+			compatible = "nxp,s32g2-stm-cnt";
 			reg = <0x40224000 0x3000>;
 			clocks = <&clks 0x3b>, <&clks 0x3c>, <&clks 0x3c>;
 			clock-names = "counter", "module", "register";
diff --git a/arch/arm64/boot/dts/freescale/s32g274a-rdb2.dts b/arch/arm64/boot/dts/freescale/s32g274a-rdb2.dts
index ee3121b192e5..bde5d3726825 100644
--- a/arch/arm64/boot/dts/freescale/s32g274a-rdb2.dts
+++ b/arch/arm64/boot/dts/freescale/s32g274a-rdb2.dts
@@ -41,19 +41,15 @@ &uart1 {
 	status = "okay";
 };
 
-&stm0 {
+&stm4 {
 	status = "okay";
 };
 
-&stm1 {
+&stm5 {
 	status = "okay";
 };
 
-&stm2 {
-	status = "okay";
-};
-
-&stm3 {
+&stm6 {
 	status = "okay";
 };
 
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH v5 3/3] counter: Add STM based counter
  2026-01-13 16:52 [PATCH v5 0/3] Add the System Timer Module counter Daniel Lezcano
  2026-01-13 16:52 ` [PATCH v5 1/3] counters: Reorder the Makefile Daniel Lezcano
  2026-01-13 16:52 ` [PATCH v5 2/3] dt-bindings: counter: Add NXP System Timer Module Counter Daniel Lezcano
@ 2026-01-13 16:52 ` Daniel Lezcano
  2026-01-13 18:01   ` Frank Li
  2026-01-16  5:57   ` William Breathitt Gray
  2 siblings, 2 replies; 8+ messages in thread
From: Daniel Lezcano @ 2026-01-13 16:52 UTC (permalink / raw)
  To: wbg
  Cc: Frank.li, robh, conor+dt, krzk+dt, s32, devicetree, linux-kernel,
	linux-iio

The NXP S32G2 automotive platform integrates four Cortex-A53 cores and
three Cortex-M7 cores, along with a large number of timers and
counters. These hardware blocks can be used as clocksources or
clockevents, or as timestamp counters shared across the various
subsystems running alongside the Linux kernel, such as firmware
components. Their actual usage depends on the overall platform
software design.

In a Linux-based system, the kernel controls the counter, which is a
read-only shared resource for the other subsystems. One of its primary
purposes is to act as a common timestamp source for messages or
traces, allowing correlation of events occurring in different
operating system contexts.

These changes introduce a basic counter driver that can start, stop,
and reset the counter. It also handles overflow accounting and
configures the prescaler value.

Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org>
---
 drivers/counter/Kconfig       |  10 +
 drivers/counter/Makefile      |   1 +
 drivers/counter/nxp-stm-cnt.c | 432 ++++++++++++++++++++++++++++++++++
 3 files changed, 443 insertions(+)
 create mode 100644 drivers/counter/nxp-stm-cnt.c

diff --git a/drivers/counter/Kconfig b/drivers/counter/Kconfig
index d30d22dfe577..bf5b281f194c 100644
--- a/drivers/counter/Kconfig
+++ b/drivers/counter/Kconfig
@@ -90,6 +90,16 @@ config MICROCHIP_TCB_CAPTURE
 	  To compile this driver as a module, choose M here: the
 	  module will be called microchip-tcb-capture.
 
+config NXP_STM_CNT
+	tristate "NXP System Timer Module Counter driver"
+	depends on ARCH_S32 || COMPILE_TEST
+	help
+	  Select this option to enable the NXP System Timer Module
+	  Counter driver.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called nxp_stm_cnt.
+
 config RZ_MTU3_CNT
 	tristate "Renesas RZ/G2L MTU3a counter driver"
 	depends on RZ_MTU3
diff --git a/drivers/counter/Makefile b/drivers/counter/Makefile
index 40e644948e7a..196b3c216875 100644
--- a/drivers/counter/Makefile
+++ b/drivers/counter/Makefile
@@ -12,6 +12,7 @@ obj-$(CONFIG_I8254)			+= i8254.o
 obj-$(CONFIG_INTEL_QEP)			+= intel-qep.o
 obj-$(CONFIG_INTERRUPT_CNT)		+= interrupt-cnt.o
 obj-$(CONFIG_MICROCHIP_TCB_CAPTURE)	+= microchip-tcb-capture.o
+obj-$(CONFIG_NXP_STM_CNT)		+= nxp-stm-cnt.o
 obj-$(CONFIG_RZ_MTU3_CNT)		+= rz-mtu3-cnt.o
 obj-$(CONFIG_STM32_TIMER_CNT)		+= stm32-timer-cnt.o
 obj-$(CONFIG_STM32_LPTIMER_CNT)		+= stm32-lptimer-cnt.o
diff --git a/drivers/counter/nxp-stm-cnt.c b/drivers/counter/nxp-stm-cnt.c
new file mode 100644
index 000000000000..dc2a666b323a
--- /dev/null
+++ b/drivers/counter/nxp-stm-cnt.c
@@ -0,0 +1,432 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2018,2021-2025 NXP
+ * Copyright 2025 Linaro Limited
+ *
+ * Author: Daniel Lezcano <daniel.lezcano@linaro.org>
+ *
+ * NXP S32G System Timer Module counters:
+ *
+ *  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/atomic.h>
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/counter.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/slab.h>
+
+#define STM_CR(__base)		(__base)
+#define STM_CR_TEN		BIT(0)
+#define STM_CR_FRZ		BIT(1)
+#define STM_CR_CPS_MASK         GENMASK(15, 8)
+
+#define STM_CCR0(__base)	((__base) + 0x10)
+#define STM_CCR_CEN		BIT(0)
+
+#define STM_CIR0(__base)	((__base) + 0x14)
+#define STM_CIR_CIF		BIT(0)
+
+#define STM_CMP0(__base)	((__base) + 0x18)
+
+#define STM_CNT(__base)		((__base) + 0x04)
+
+#define STM_ENABLE_MASK	(STM_CR_FRZ | STM_CR_TEN)
+
+struct nxp_stm_context {
+	u32 counter;
+	u8 prescaler;
+	bool is_started;
+};
+
+struct nxp_stm_cnt {
+	void __iomem *base;
+	atomic_t nr_wraps;
+	struct nxp_stm_context context;
+};
+
+static void nxp_stm_cnt_enable(struct nxp_stm_cnt *stm_cnt)
+{
+	u32 reg;
+
+	reg = readl(STM_CR(stm_cnt->base));
+
+	reg |= STM_ENABLE_MASK;
+
+	writel(reg, STM_CR(stm_cnt->base));
+}
+
+static void nxp_stm_cnt_disable(struct nxp_stm_cnt *stm_cnt)
+{
+	u32 reg;
+
+	reg = readl(STM_CR(stm_cnt->base));
+
+	reg &= ~STM_ENABLE_MASK;
+
+	writel(reg, STM_CR(stm_cnt->base));
+}
+
+static void nxp_stm_cnt_ccr_disable(struct nxp_stm_cnt *stm_cnt)
+{
+	writel(0, STM_CCR0(stm_cnt->base));
+}
+
+static void nxp_stm_cnt_ccr_enable(struct nxp_stm_cnt *stm_cnt)
+{
+	writel(STM_CCR_CEN, STM_CCR0(stm_cnt->base));
+}
+
+static void nxp_stm_cnt_cfg_overflow(struct nxp_stm_cnt *stm_cnt)
+{
+	/*
+	 * The STM does not have a dedicated interrupt when the
+	 * counter wraps. We need to use the comparator to check if it
+	 * wrapped or not.
+	 */
+	writel(0, STM_CMP0(stm_cnt->base));
+}
+
+static u32 nxp_stm_cnt_get_counter(struct nxp_stm_cnt *stm_cnt)
+{
+	return readl(STM_CNT(stm_cnt->base));
+}
+
+static void nxp_stm_cnt_set_counter(struct nxp_stm_cnt *stm_cnt, u32 counter)
+{
+	writel(counter, STM_CNT(stm_cnt->base));
+}
+
+static void nxp_stm_cnt_set_prescaler(struct nxp_stm_cnt *stm_cnt, u8 prescaler)
+{
+	u32 reg;
+
+	reg = readl(STM_CR(stm_cnt->base));
+
+	FIELD_MODIFY(STM_CR_CPS_MASK, &reg, prescaler);
+
+	writel(reg, STM_CR(stm_cnt->base));
+}
+
+static u8 nxp_stm_cnt_get_prescaler(struct nxp_stm_cnt *stm_cnt)
+{
+	u32 reg = readl(STM_CR(stm_cnt->base));
+
+	return FIELD_GET(STM_CR_CPS_MASK, reg);
+}
+
+static bool nxp_stm_cnt_is_started(struct nxp_stm_cnt *stm_cnt)
+{
+	u32 reg;
+
+	reg = readl(STM_CR(stm_cnt->base));
+
+	return !!FIELD_GET(STM_CR_TEN, reg);
+}
+
+static void nxp_stm_cnt_irq_ack(struct nxp_stm_cnt *stm_cnt)
+{
+	writel(STM_CIR_CIF, STM_CIR0(stm_cnt->base));
+}
+
+static irqreturn_t nxp_stm_cnt_irq(int irq, void *dev_id)
+{
+	struct counter_device *counter = dev_id;
+	struct nxp_stm_cnt *stm_cnt = counter_priv(counter);
+
+	nxp_stm_cnt_irq_ack(stm_cnt);
+
+	atomic_inc(&stm_cnt->nr_wraps);
+
+	counter_push_event(counter, COUNTER_EVENT_OVERFLOW, 0);
+
+	return IRQ_HANDLED;
+}
+
+static void nxp_stm_cnt_start(struct nxp_stm_cnt *stm_cnt)
+{
+	nxp_stm_cnt_cfg_overflow(stm_cnt);
+	nxp_stm_cnt_enable(stm_cnt);
+	nxp_stm_cnt_ccr_enable(stm_cnt);
+}
+
+static void nxp_stm_cnt_stop(struct nxp_stm_cnt *stm_cnt)
+{
+	nxp_stm_cnt_disable(stm_cnt);
+	nxp_stm_cnt_irq_ack(stm_cnt);
+	nxp_stm_cnt_ccr_disable(stm_cnt);
+}
+
+static int nxp_stm_cnt_prescaler_read(struct counter_device *counter,
+				      struct counter_count *count, u8 *val)
+{
+	struct nxp_stm_cnt *stm_cnt = counter_priv(counter);
+
+	*val = nxp_stm_cnt_get_prescaler(stm_cnt);
+
+	return 0;
+}
+
+static int nxp_stm_cnt_prescaler_write(struct counter_device *counter,
+				       struct counter_count *count, u8 val)
+{
+	struct nxp_stm_cnt *stm_cnt = counter_priv(counter);
+
+	nxp_stm_cnt_set_prescaler(stm_cnt, val);
+
+	return 0;
+}
+
+static int nxp_stm_cnt_count_enable_write(struct counter_device *counter,
+					  struct counter_count *count, u8 enable)
+{
+	struct nxp_stm_cnt *stm_cnt = counter_priv(counter);
+
+	if (enable)
+		nxp_stm_cnt_start(stm_cnt);
+	else
+		nxp_stm_cnt_stop(stm_cnt);
+
+	return 0;
+}
+
+static int nxp_stm_cnt_count_enable_read(struct counter_device *counter,
+					 struct counter_count *count, u8 *enable)
+{
+	struct nxp_stm_cnt *stm_cnt = counter_priv(counter);
+
+	*enable = nxp_stm_cnt_is_started(stm_cnt);
+
+	return 0;
+}
+
+static struct counter_comp stm_cnt_count_ext[] = {
+	COUNTER_COMP_COUNT_U8("prescaler", nxp_stm_cnt_prescaler_read, nxp_stm_cnt_prescaler_write),
+	COUNTER_COMP_ENABLE(nxp_stm_cnt_count_enable_read, nxp_stm_cnt_count_enable_write),
+};
+
+static int nxp_stm_cnt_action_read(struct counter_device *counter,
+				   struct counter_count *count,
+				   struct counter_synapse *synapse,
+				   enum counter_synapse_action *action)
+{
+	*action = COUNTER_SYNAPSE_ACTION_RISING_EDGE;
+
+	return 0;
+}
+
+static int nxp_stm_cnt_count_read(struct counter_device *dev,
+				  struct counter_count *count, u64 *val)
+{
+	struct nxp_stm_cnt *stm_cnt = counter_priv(dev);
+	u32 w1, w2, cnt;
+
+	do {
+		w1 = atomic_read(&stm_cnt->nr_wraps);
+		cnt = nxp_stm_cnt_get_counter(stm_cnt);
+		w2 = atomic_read(&stm_cnt->nr_wraps);
+	} while (w1 != w2);
+
+	*val = ((u64)w1 << 32) | cnt;
+
+	return 0;
+}
+
+static int nxp_stm_cnt_count_write(struct counter_device *dev,
+				   struct counter_count *count, u64 val)
+{
+	struct nxp_stm_cnt *stm_cnt = counter_priv(dev);
+
+	nxp_stm_cnt_set_counter(stm_cnt, 0);
+	atomic_set(&stm_cnt->nr_wraps, 0);
+
+	return 0;
+}
+
+static const enum counter_function nxp_stm_cnt_functions[] = {
+	COUNTER_FUNCTION_INCREASE,
+};
+
+static int nxp_stm_cnt_function_read(struct counter_device *counter,
+				     struct counter_count *count,
+				     enum counter_function *function)
+{
+	*function = COUNTER_FUNCTION_INCREASE;
+
+	return 0;
+}
+
+static int nxp_stm_cnt_watch_validate(struct counter_device *counter,
+				      const struct counter_watch *watch)
+{
+	switch (watch->event) {
+	case COUNTER_EVENT_OVERFLOW:
+		return 0;
+	default:
+		return -EINVAL;
+	}
+}
+
+static const struct counter_ops nxp_stm_cnt_counter_ops = {
+	.action_read = nxp_stm_cnt_action_read,
+	.count_read  = nxp_stm_cnt_count_read,
+	.count_write = nxp_stm_cnt_count_write,
+	.function_read = nxp_stm_cnt_function_read,
+	.watch_validate = nxp_stm_cnt_watch_validate,
+};
+
+static struct counter_signal nxp_stm_cnt_signals[] = {
+	{
+		.id = 0,
+		.name = "Counter wrap signal",
+	},
+};
+
+static const enum counter_synapse_action nxp_stm_cnt_synapse_actions[] = {
+	COUNTER_SYNAPSE_ACTION_RISING_EDGE,
+};
+
+static struct counter_synapse nxp_stm_cnt_synapses[] = {
+	{
+		.actions_list = nxp_stm_cnt_synapse_actions,
+		.num_actions = ARRAY_SIZE(nxp_stm_cnt_synapse_actions),
+		.signal = nxp_stm_cnt_signals,
+	},
+};
+
+static struct counter_count nxp_stm_cnt_counts[] = {
+	{
+		.id = 0,
+		.name = "System Timer Module Counter",
+		.functions_list = nxp_stm_cnt_functions,
+		.num_functions = ARRAY_SIZE(nxp_stm_cnt_functions),
+		.synapses = nxp_stm_cnt_synapses,
+		.num_synapses = ARRAY_SIZE(nxp_stm_cnt_synapses),
+		.ext = stm_cnt_count_ext,
+		.num_ext = ARRAY_SIZE(stm_cnt_count_ext),
+	},
+};
+
+static int nxp_stm_cnt_suspend(struct device *dev)
+{
+	struct nxp_stm_cnt *stm_cnt = dev_get_drvdata(dev);
+
+	stm_cnt->context.is_started = nxp_stm_cnt_is_started(stm_cnt);
+
+	if (stm_cnt->context.is_started) {
+		nxp_stm_cnt_stop(stm_cnt);
+		stm_cnt->context.prescaler = nxp_stm_cnt_get_prescaler(stm_cnt);
+		stm_cnt->context.counter = nxp_stm_cnt_get_counter(stm_cnt);
+	}
+
+	return 0;
+}
+
+static int nxp_stm_cnt_resume(struct device *dev)
+{
+	struct nxp_stm_cnt *stm_cnt = dev_get_drvdata(dev);
+
+	if (stm_cnt->context.is_started) {
+		nxp_stm_cnt_set_counter(stm_cnt, stm_cnt->context.counter);
+		nxp_stm_cnt_set_prescaler(stm_cnt, stm_cnt->context.prescaler);
+		nxp_stm_cnt_start(stm_cnt);
+	}
+
+	return 0;
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(nxp_stm_cnt_pm_ops, nxp_stm_cnt_suspend,
+				nxp_stm_cnt_resume);
+
+static int nxp_stm_cnt_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct device_node *np = dev->of_node;
+	struct counter_device *counter;
+	struct nxp_stm_cnt *stm_cnt;
+	struct clk *clk;
+	void __iomem *base;
+	int irq, ret;
+
+	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");
+
+	counter = devm_counter_alloc(dev, sizeof(*stm_cnt));
+	if (!counter)
+		return -ENOMEM;
+
+	stm_cnt = counter_priv(counter);
+
+	stm_cnt->base = base;
+	atomic_set(&stm_cnt->nr_wraps, 0);
+
+	counter->name       = "stm_counter";
+	counter->parent     = &pdev->dev;
+	counter->ops        = &nxp_stm_cnt_counter_ops;
+	counter->counts     = nxp_stm_cnt_counts;
+	counter->num_counts = ARRAY_SIZE(nxp_stm_cnt_counts);
+
+	ret = devm_request_irq(dev, irq, nxp_stm_cnt_irq, IRQF_TIMER | IRQF_NOBALANCING,
+			       dev_name(&counter->dev), counter);
+	if (ret)
+		return dev_err_probe(dev, ret, "Unable to allocate interrupt line\n");
+
+	ret = devm_counter_add(dev, counter);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to register counter\n");
+
+	platform_set_drvdata(pdev, stm_cnt);
+
+	return 0;
+}
+
+static void nxp_stm_cnt_remove(struct platform_device *pdev)
+{
+	struct nxp_stm_cnt *stm_cnt = platform_get_drvdata(pdev);
+
+	if (nxp_stm_cnt_is_started(stm_cnt))
+		nxp_stm_cnt_stop(stm_cnt);
+}
+
+static const struct of_device_id nxp_stm_cnt_of_match[] = {
+	{ .compatible = "nxp,s32g2-stm-cnt", },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, nxp_stm_cnt_of_match);
+
+static struct platform_driver nxp_stm_cnt_driver = {
+	.probe  = nxp_stm_cnt_probe,
+	.remove = nxp_stm_cnt_remove,
+	.driver = {
+		.name           = "nxp-stm-cnt",
+		.pm		= pm_sleep_ptr(&nxp_stm_cnt_pm_ops),
+		.of_match_table = nxp_stm_cnt_of_match,
+	},
+};
+module_platform_driver(nxp_stm_cnt_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Daniel Lezcano");
+MODULE_DESCRIPTION("NXP System Timer Module counter driver");
+MODULE_IMPORT_NS("COUNTER");
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* Re: [PATCH v5 3/3] counter: Add STM based counter
  2026-01-13 16:52 ` [PATCH v5 3/3] counter: Add STM based counter Daniel Lezcano
@ 2026-01-13 18:01   ` Frank Li
  2026-01-14 16:38     ` Daniel Lezcano
  2026-01-16  5:57   ` William Breathitt Gray
  1 sibling, 1 reply; 8+ messages in thread
From: Frank Li @ 2026-01-13 18:01 UTC (permalink / raw)
  To: Daniel Lezcano
  Cc: wbg, robh, conor+dt, krzk+dt, s32, devicetree, linux-kernel,
	linux-iio

On Tue, Jan 13, 2026 at 05:52:20PM +0100, Daniel Lezcano wrote:
> The NXP S32G2 automotive platform integrates four Cortex-A53 cores and
> three Cortex-M7 cores, along with a large number of timers and
> counters. These hardware blocks can be used as clocksources or
> clockevents, or as timestamp counters shared across the various
> subsystems running alongside the Linux kernel, such as firmware
> components. Their actual usage depends on the overall platform
> software design.
>
> In a Linux-based system, the kernel controls the counter, which is a
> read-only shared resource for the other subsystems. One of its primary
> purposes is to act as a common timestamp source for messages or
> traces, allowing correlation of events occurring in different
> operating system contexts.
>
> These changes introduce a basic counter driver that can start, stop,
> and reset the counter. It also handles overflow accounting and
> configures the prescaler value.
>
> Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org>
> ---
>  drivers/counter/Kconfig       |  10 +
>  drivers/counter/Makefile      |   1 +
>  drivers/counter/nxp-stm-cnt.c | 432 ++++++++++++++++++++++++++++++++++
>  3 files changed, 443 insertions(+)
>  create mode 100644 drivers/counter/nxp-stm-cnt.c
>
> diff --git a/drivers/counter/Kconfig b/drivers/counter/Kconfig
> index d30d22dfe577..bf5b281f194c 100644
> --- a/drivers/counter/Kconfig
> +++ b/drivers/counter/Kconfig
> @@ -90,6 +90,16 @@ config MICROCHIP_TCB_CAPTURE
>  	  To compile this driver as a module, choose M here: the
>  	  module will be called microchip-tcb-capture.
>
> +config NXP_STM_CNT
> +	tristate "NXP System Timer Module Counter driver"
> +	depends on ARCH_S32 || COMPILE_TEST
> +	help
> +	  Select this option to enable the NXP System Timer Module
> +	  Counter driver.
> +
> +	  To compile this driver as a module, choose M here: the
> +	  module will be called nxp_stm_cnt.
> +
>  config RZ_MTU3_CNT
>  	tristate "Renesas RZ/G2L MTU3a counter driver"
>  	depends on RZ_MTU3
> diff --git a/drivers/counter/Makefile b/drivers/counter/Makefile
> index 40e644948e7a..196b3c216875 100644
> --- a/drivers/counter/Makefile
> +++ b/drivers/counter/Makefile
> @@ -12,6 +12,7 @@ obj-$(CONFIG_I8254)			+= i8254.o
>  obj-$(CONFIG_INTEL_QEP)			+= intel-qep.o
>  obj-$(CONFIG_INTERRUPT_CNT)		+= interrupt-cnt.o
>  obj-$(CONFIG_MICROCHIP_TCB_CAPTURE)	+= microchip-tcb-capture.o
> +obj-$(CONFIG_NXP_STM_CNT)		+= nxp-stm-cnt.o
>  obj-$(CONFIG_RZ_MTU3_CNT)		+= rz-mtu3-cnt.o
>  obj-$(CONFIG_STM32_TIMER_CNT)		+= stm32-timer-cnt.o
>  obj-$(CONFIG_STM32_LPTIMER_CNT)		+= stm32-lptimer-cnt.o
> diff --git a/drivers/counter/nxp-stm-cnt.c b/drivers/counter/nxp-stm-cnt.c
> new file mode 100644
> index 000000000000..dc2a666b323a
> --- /dev/null
> +++ b/drivers/counter/nxp-stm-cnt.c
> @@ -0,0 +1,432 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright 2018,2021-2025 NXP
> + * Copyright 2025 Linaro Limited
> + *
> + * Author: Daniel Lezcano <daniel.lezcano@linaro.org>
> + *
> + * NXP S32G System Timer Module counters:
> + *
> + *  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/atomic.h>
> +#include <linux/bitfield.h>
> +#include <linux/clk.h>
> +#include <linux/counter.h>
> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm.h>
> +#include <linux/slab.h>
> +
> +#define STM_CR(__base)		(__base)
> +#define STM_CR_TEN		BIT(0)
> +#define STM_CR_FRZ		BIT(1)
> +#define STM_CR_CPS_MASK         GENMASK(15, 8)
> +
> +#define STM_CCR0(__base)	((__base) + 0x10)
> +#define STM_CCR_CEN		BIT(0)
> +
> +#define STM_CIR0(__base)	((__base) + 0x14)
> +#define STM_CIR_CIF		BIT(0)
> +
> +#define STM_CMP0(__base)	((__base) + 0x18)
> +
> +#define STM_CNT(__base)		((__base) + 0x04)
> +
> +#define STM_ENABLE_MASK	(STM_CR_FRZ | STM_CR_TEN)
> +
> +struct nxp_stm_context {
> +	u32 counter;
> +	u8 prescaler;
> +	bool is_started;
> +};
> +
> +struct nxp_stm_cnt {
> +	void __iomem *base;
> +	atomic_t nr_wraps;
> +	struct nxp_stm_context context;
> +};
> +
> +static void nxp_stm_cnt_enable(struct nxp_stm_cnt *stm_cnt)
> +{
> +	u32 reg;
> +
> +	reg = readl(STM_CR(stm_cnt->base));
> +
> +	reg |= STM_ENABLE_MASK;
> +
> +	writel(reg, STM_CR(stm_cnt->base));
> +}
> +
> +static void nxp_stm_cnt_disable(struct nxp_stm_cnt *stm_cnt)
> +{
> +	u32 reg;
> +
> +	reg = readl(STM_CR(stm_cnt->base));
> +
> +	reg &= ~STM_ENABLE_MASK;
> +
> +	writel(reg, STM_CR(stm_cnt->base));
> +}
> +
> +static void nxp_stm_cnt_ccr_disable(struct nxp_stm_cnt *stm_cnt)
> +{
> +	writel(0, STM_CCR0(stm_cnt->base));
> +}
> +
> +static void nxp_stm_cnt_ccr_enable(struct nxp_stm_cnt *stm_cnt)
> +{
> +	writel(STM_CCR_CEN, STM_CCR0(stm_cnt->base));
> +}
> +
> +static void nxp_stm_cnt_cfg_overflow(struct nxp_stm_cnt *stm_cnt)
> +{
> +	/*
> +	 * The STM does not have a dedicated interrupt when the
> +	 * counter wraps. We need to use the comparator to check if it
> +	 * wrapped or not.
> +	 */
> +	writel(0, STM_CMP0(stm_cnt->base));
> +}
> +
> +static u32 nxp_stm_cnt_get_counter(struct nxp_stm_cnt *stm_cnt)
> +{
> +	return readl(STM_CNT(stm_cnt->base));
> +}
> +
> +static void nxp_stm_cnt_set_counter(struct nxp_stm_cnt *stm_cnt, u32 counter)
> +{
> +	writel(counter, STM_CNT(stm_cnt->base));
> +}
> +
> +static void nxp_stm_cnt_set_prescaler(struct nxp_stm_cnt *stm_cnt, u8 prescaler)
> +{
> +	u32 reg;
> +
> +	reg = readl(STM_CR(stm_cnt->base));
> +
> +	FIELD_MODIFY(STM_CR_CPS_MASK, &reg, prescaler);
> +
> +	writel(reg, STM_CR(stm_cnt->base));
> +}
> +
> +static u8 nxp_stm_cnt_get_prescaler(struct nxp_stm_cnt *stm_cnt)
> +{
> +	u32 reg = readl(STM_CR(stm_cnt->base));
> +
> +	return FIELD_GET(STM_CR_CPS_MASK, reg);
> +}
> +
> +static bool nxp_stm_cnt_is_started(struct nxp_stm_cnt *stm_cnt)
> +{
> +	u32 reg;
> +
> +	reg = readl(STM_CR(stm_cnt->base));
> +
> +	return !!FIELD_GET(STM_CR_TEN, reg);
> +}
> +
> +static void nxp_stm_cnt_irq_ack(struct nxp_stm_cnt *stm_cnt)
> +{
> +	writel(STM_CIR_CIF, STM_CIR0(stm_cnt->base));
> +}
> +
> +static irqreturn_t nxp_stm_cnt_irq(int irq, void *dev_id)
> +{
> +	struct counter_device *counter = dev_id;
> +	struct nxp_stm_cnt *stm_cnt = counter_priv(counter);
> +
> +	nxp_stm_cnt_irq_ack(stm_cnt);
> +
> +	atomic_inc(&stm_cnt->nr_wraps);
> +
> +	counter_push_event(counter, COUNTER_EVENT_OVERFLOW, 0);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static void nxp_stm_cnt_start(struct nxp_stm_cnt *stm_cnt)
> +{
> +	nxp_stm_cnt_cfg_overflow(stm_cnt);
> +	nxp_stm_cnt_enable(stm_cnt);
> +	nxp_stm_cnt_ccr_enable(stm_cnt);
> +}
> +
> +static void nxp_stm_cnt_stop(struct nxp_stm_cnt *stm_cnt)
> +{
> +	nxp_stm_cnt_disable(stm_cnt);
> +	nxp_stm_cnt_irq_ack(stm_cnt);
> +	nxp_stm_cnt_ccr_disable(stm_cnt);
> +}
> +
> +static int nxp_stm_cnt_prescaler_read(struct counter_device *counter,
> +				      struct counter_count *count, u8 *val)
> +{
> +	struct nxp_stm_cnt *stm_cnt = counter_priv(counter);
> +
> +	*val = nxp_stm_cnt_get_prescaler(stm_cnt);
> +
> +	return 0;
> +}
> +
> +static int nxp_stm_cnt_prescaler_write(struct counter_device *counter,
> +				       struct counter_count *count, u8 val)
> +{
> +	struct nxp_stm_cnt *stm_cnt = counter_priv(counter);
> +
> +	nxp_stm_cnt_set_prescaler(stm_cnt, val);
> +
> +	return 0;
> +}
> +
> +static int nxp_stm_cnt_count_enable_write(struct counter_device *counter,
> +					  struct counter_count *count, u8 enable)
> +{
> +	struct nxp_stm_cnt *stm_cnt = counter_priv(counter);
> +
> +	if (enable)
> +		nxp_stm_cnt_start(stm_cnt);
> +	else
> +		nxp_stm_cnt_stop(stm_cnt);
> +
> +	return 0;
> +}
> +
> +static int nxp_stm_cnt_count_enable_read(struct counter_device *counter,
> +					 struct counter_count *count, u8 *enable)
> +{
> +	struct nxp_stm_cnt *stm_cnt = counter_priv(counter);
> +
> +	*enable = nxp_stm_cnt_is_started(stm_cnt);
> +
> +	return 0;
> +}
> +
> +static struct counter_comp stm_cnt_count_ext[] = {
> +	COUNTER_COMP_COUNT_U8("prescaler", nxp_stm_cnt_prescaler_read, nxp_stm_cnt_prescaler_write),
> +	COUNTER_COMP_ENABLE(nxp_stm_cnt_count_enable_read, nxp_stm_cnt_count_enable_write),
> +};
> +
> +static int nxp_stm_cnt_action_read(struct counter_device *counter,
> +				   struct counter_count *count,
> +				   struct counter_synapse *synapse,
> +				   enum counter_synapse_action *action)
> +{
> +	*action = COUNTER_SYNAPSE_ACTION_RISING_EDGE;
> +
> +	return 0;
> +}
> +
> +static int nxp_stm_cnt_count_read(struct counter_device *dev,
> +				  struct counter_count *count, u64 *val)
> +{
> +	struct nxp_stm_cnt *stm_cnt = counter_priv(dev);
> +	u32 w1, w2, cnt;
> +
> +	do {
> +		w1 = atomic_read(&stm_cnt->nr_wraps);
> +		cnt = nxp_stm_cnt_get_counter(stm_cnt);
> +		w2 = atomic_read(&stm_cnt->nr_wraps);
> +	} while (w1 != w2);

Still have problem. Does hardware have overflow flags? This way is just
work for hardware overflow

CPU1

	w1  = 0
        count = 0xffff,ffff
	w2 = 0


when count wrap to 0, triger irq, but irq handle have latency at CPU0,
CPU0 have not chance to sevice irq. you can add printk at irq sevice to
create corner case.

So CPU1 get

	w1= 0
	count = 0
	w2=0

so counter still suddently change to 0.

Frank
> +
> +	*val = ((u64)w1 << 32) | cnt;
> +
> +	return 0;
> +}
> +
> +static int nxp_stm_cnt_count_write(struct counter_device *dev,
> +				   struct counter_count *count, u64 val)
> +{
> +	struct nxp_stm_cnt *stm_cnt = counter_priv(dev);
> +
> +	nxp_stm_cnt_set_counter(stm_cnt, 0);
> +	atomic_set(&stm_cnt->nr_wraps, 0);
> +
> +	return 0;
> +}
> +
> +static const enum counter_function nxp_stm_cnt_functions[] = {
> +	COUNTER_FUNCTION_INCREASE,
> +};
> +
> +static int nxp_stm_cnt_function_read(struct counter_device *counter,
> +				     struct counter_count *count,
> +				     enum counter_function *function)
> +{
> +	*function = COUNTER_FUNCTION_INCREASE;
> +
> +	return 0;
> +}
> +
> +static int nxp_stm_cnt_watch_validate(struct counter_device *counter,
> +				      const struct counter_watch *watch)
> +{
> +	switch (watch->event) {
> +	case COUNTER_EVENT_OVERFLOW:
> +		return 0;
> +	default:
> +		return -EINVAL;
> +	}
> +}
> +
> +static const struct counter_ops nxp_stm_cnt_counter_ops = {
> +	.action_read = nxp_stm_cnt_action_read,
> +	.count_read  = nxp_stm_cnt_count_read,
> +	.count_write = nxp_stm_cnt_count_write,
> +	.function_read = nxp_stm_cnt_function_read,
> +	.watch_validate = nxp_stm_cnt_watch_validate,
> +};
> +
> +static struct counter_signal nxp_stm_cnt_signals[] = {
> +	{
> +		.id = 0,
> +		.name = "Counter wrap signal",
> +	},
> +};
> +
> +static const enum counter_synapse_action nxp_stm_cnt_synapse_actions[] = {
> +	COUNTER_SYNAPSE_ACTION_RISING_EDGE,
> +};
> +
> +static struct counter_synapse nxp_stm_cnt_synapses[] = {
> +	{
> +		.actions_list = nxp_stm_cnt_synapse_actions,
> +		.num_actions = ARRAY_SIZE(nxp_stm_cnt_synapse_actions),
> +		.signal = nxp_stm_cnt_signals,
> +	},
> +};
> +
> +static struct counter_count nxp_stm_cnt_counts[] = {
> +	{
> +		.id = 0,
> +		.name = "System Timer Module Counter",
> +		.functions_list = nxp_stm_cnt_functions,
> +		.num_functions = ARRAY_SIZE(nxp_stm_cnt_functions),
> +		.synapses = nxp_stm_cnt_synapses,
> +		.num_synapses = ARRAY_SIZE(nxp_stm_cnt_synapses),
> +		.ext = stm_cnt_count_ext,
> +		.num_ext = ARRAY_SIZE(stm_cnt_count_ext),
> +	},
> +};
> +
> +static int nxp_stm_cnt_suspend(struct device *dev)
> +{
> +	struct nxp_stm_cnt *stm_cnt = dev_get_drvdata(dev);
> +
> +	stm_cnt->context.is_started = nxp_stm_cnt_is_started(stm_cnt);
> +
> +	if (stm_cnt->context.is_started) {
> +		nxp_stm_cnt_stop(stm_cnt);
> +		stm_cnt->context.prescaler = nxp_stm_cnt_get_prescaler(stm_cnt);
> +		stm_cnt->context.counter = nxp_stm_cnt_get_counter(stm_cnt);
> +	}
> +
> +	return 0;
> +}
> +
> +static int nxp_stm_cnt_resume(struct device *dev)
> +{
> +	struct nxp_stm_cnt *stm_cnt = dev_get_drvdata(dev);
> +
> +	if (stm_cnt->context.is_started) {
> +		nxp_stm_cnt_set_counter(stm_cnt, stm_cnt->context.counter);
> +		nxp_stm_cnt_set_prescaler(stm_cnt, stm_cnt->context.prescaler);
> +		nxp_stm_cnt_start(stm_cnt);
> +	}
> +
> +	return 0;
> +}
> +
> +static DEFINE_SIMPLE_DEV_PM_OPS(nxp_stm_cnt_pm_ops, nxp_stm_cnt_suspend,
> +				nxp_stm_cnt_resume);
> +
> +static int nxp_stm_cnt_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct device_node *np = dev->of_node;
> +	struct counter_device *counter;
> +	struct nxp_stm_cnt *stm_cnt;
> +	struct clk *clk;
> +	void __iomem *base;
> +	int irq, ret;
> +
> +	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");
> +
> +	counter = devm_counter_alloc(dev, sizeof(*stm_cnt));
> +	if (!counter)
> +		return -ENOMEM;
> +
> +	stm_cnt = counter_priv(counter);
> +
> +	stm_cnt->base = base;
> +	atomic_set(&stm_cnt->nr_wraps, 0);
> +
> +	counter->name       = "stm_counter";
> +	counter->parent     = &pdev->dev;
> +	counter->ops        = &nxp_stm_cnt_counter_ops;
> +	counter->counts     = nxp_stm_cnt_counts;
> +	counter->num_counts = ARRAY_SIZE(nxp_stm_cnt_counts);
> +
> +	ret = devm_request_irq(dev, irq, nxp_stm_cnt_irq, IRQF_TIMER | IRQF_NOBALANCING,
> +			       dev_name(&counter->dev), counter);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "Unable to allocate interrupt line\n");
> +
> +	ret = devm_counter_add(dev, counter);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "Failed to register counter\n");
> +
> +	platform_set_drvdata(pdev, stm_cnt);
> +
> +	return 0;
> +}
> +
> +static void nxp_stm_cnt_remove(struct platform_device *pdev)
> +{
> +	struct nxp_stm_cnt *stm_cnt = platform_get_drvdata(pdev);
> +
> +	if (nxp_stm_cnt_is_started(stm_cnt))
> +		nxp_stm_cnt_stop(stm_cnt);
> +}
> +
> +static const struct of_device_id nxp_stm_cnt_of_match[] = {
> +	{ .compatible = "nxp,s32g2-stm-cnt", },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(of, nxp_stm_cnt_of_match);
> +
> +static struct platform_driver nxp_stm_cnt_driver = {
> +	.probe  = nxp_stm_cnt_probe,
> +	.remove = nxp_stm_cnt_remove,
> +	.driver = {
> +		.name           = "nxp-stm-cnt",
> +		.pm		= pm_sleep_ptr(&nxp_stm_cnt_pm_ops),
> +		.of_match_table = nxp_stm_cnt_of_match,
> +	},
> +};
> +module_platform_driver(nxp_stm_cnt_driver);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_AUTHOR("Daniel Lezcano");
> +MODULE_DESCRIPTION("NXP System Timer Module counter driver");
> +MODULE_IMPORT_NS("COUNTER");
> --
> 2.43.0
>

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH v5 3/3] counter: Add STM based counter
  2026-01-13 18:01   ` Frank Li
@ 2026-01-14 16:38     ` Daniel Lezcano
  0 siblings, 0 replies; 8+ messages in thread
From: Daniel Lezcano @ 2026-01-14 16:38 UTC (permalink / raw)
  To: Frank Li
  Cc: wbg, robh, conor+dt, krzk+dt, s32, devicetree, linux-kernel,
	linux-iio

On 1/13/26 19:01, Frank Li wrote:
> On Tue, Jan 13, 2026 at 05:52:20PM +0100, Daniel Lezcano wrote:
>> The NXP S32G2 automotive platform integrates four Cortex-A53 cores and
>> three Cortex-M7 cores, along with a large number of timers and
>> counters. These hardware blocks can be used as clocksources or
>> clockevents, or as timestamp counters shared across the various
>> subsystems running alongside the Linux kernel, such as firmware
>> components. Their actual usage depends on the overall platform
>> software design.
>>
>> In a Linux-based system, the kernel controls the counter, which is a
>> read-only shared resource for the other subsystems. One of its primary
>> purposes is to act as a common timestamp source for messages or
>> traces, allowing correlation of events occurring in different
>> operating system contexts.
>>
>> These changes introduce a basic counter driver that can start, stop,
>> and reset the counter. It also handles overflow accounting and
>> configures the prescaler value.
>>
>> Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org>
>> ---

[ ... ]

>> +static int nxp_stm_cnt_count_read(struct counter_device *dev,
>> +				  struct counter_count *count, u64 *val)
>> +{
>> +	struct nxp_stm_cnt *stm_cnt = counter_priv(dev);
>> +	u32 w1, w2, cnt;
>> +
>> +	do {
>> +		w1 = atomic_read(&stm_cnt->nr_wraps);
>> +		cnt = nxp_stm_cnt_get_counter(stm_cnt);
>> +		w2 = atomic_read(&stm_cnt->nr_wraps);
>> +	} while (w1 != w2);
> 
> Still have problem. Does hardware have overflow flags? This way is just
> work for hardware overflow

No, there is no overflow flag. Just a comparator.

> CPU1
> 
> 	w1  = 0
>          count = 0xffff,ffff
> 	w2 = 0
> 
> 
> when count wrap to 0, triger irq, but irq handle have latency at CPU0,
> CPU0 have not chance to sevice irq. you can add printk at irq sevice to
> create corner case.
> 
> So CPU1 get
> 
> 	w1= 0
> 	count = 0
> 	w2=0
> 
> so counter still suddently change to 0.

Ok, so I think I'll stick to the initial implementation and just give 
the counter number instead of trying to do fancy things.


[ ... ]

-- 
<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] 8+ messages in thread

* Re: [PATCH v5 3/3] counter: Add STM based counter
  2026-01-13 16:52 ` [PATCH v5 3/3] counter: Add STM based counter Daniel Lezcano
  2026-01-13 18:01   ` Frank Li
@ 2026-01-16  5:57   ` William Breathitt Gray
  1 sibling, 0 replies; 8+ messages in thread
From: William Breathitt Gray @ 2026-01-16  5:57 UTC (permalink / raw)
  To: Daniel Lezcano
  Cc: William Breathitt Gray, Frank.li, robh, conor+dt, krzk+dt, s32,
	devicetree, linux-kernel, linux-iio

On Tue, Jan 13, 2026 at 05:52:20PM +0100, Daniel Lezcano wrote:
> The NXP S32G2 automotive platform integrates four Cortex-A53 cores and
> three Cortex-M7 cores, along with a large number of timers and
> counters. These hardware blocks can be used as clocksources or
> clockevents, or as timestamp counters shared across the various
> subsystems running alongside the Linux kernel, such as firmware
> components. Their actual usage depends on the overall platform
> software design.
> 
> In a Linux-based system, the kernel controls the counter, which is a
> read-only shared resource for the other subsystems. One of its primary
> purposes is to act as a common timestamp source for messages or
> traces, allowing correlation of events occurring in different
> operating system contexts.
> 
> These changes introduce a basic counter driver that can start, stop,
> and reset the counter. It also handles overflow accounting and
> configures the prescaler value.
> 
> Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org>

Hi Daniel,

A few of the features in this driver are implemented using somewhat of
anti-patterns in the Generic Counter paradigm. In particular, I believe
the overflow and accumulation features would be best implemented using
the Counter events and watches idioms rather than in the Counter
attributes. I'll explain further inline below.

> +static void nxp_stm_cnt_cfg_overflow(struct nxp_stm_cnt *stm_cnt)
> +{
> +	/*
> +	 * The STM does not have a dedicated interrupt when the
> +	 * counter wraps. We need to use the comparator to check if it
> +	 * wrapped or not.
> +	 */
> +	writel(0, STM_CMP0(stm_cnt->base));
> +}

So to implement overflows, you're currently setting up a compare against
0 to fire off interrupts.

The problem, besides the added complexity in your driver, is that users
now lose control over the comparator for other threshold checks. A
better approach is to define a COUNTER_COMP_COMPARE() attribute for this
comparator which users can set to the value they desire, and push
COUNTER_EVENT_THRESHOLD on the interrupts. Userspace can then watch for
these COUNTER_EVENT_THRESHOLD events and use them to detect overflows,
or for any other general purpose now that they can control the
particular threshold value.

> +static irqreturn_t nxp_stm_cnt_irq(int irq, void *dev_id)
> +{
> +	struct counter_device *counter = dev_id;
> +	struct nxp_stm_cnt *stm_cnt = counter_priv(counter);
> +
> +	nxp_stm_cnt_irq_ack(stm_cnt);
> +
> +	atomic_inc(&stm_cnt->nr_wraps);
> +
> +	counter_push_event(counter, COUNTER_EVENT_OVERFLOW, 0);

Remove nr_wraps and push COUNTER_EVENT_THRESHOLD here instead if you
implement the comparator design I suggested above.

> +static struct counter_comp stm_cnt_count_ext[] = {
> +	COUNTER_COMP_COUNT_U8("prescaler", nxp_stm_cnt_prescaler_read, nxp_stm_cnt_prescaler_write),
> +	COUNTER_COMP_ENABLE(nxp_stm_cnt_count_enable_read, nxp_stm_cnt_count_enable_write),

Add COUNTER_COMP_COMPARE() here for your Counter's comparator.

> +static int nxp_stm_cnt_count_read(struct counter_device *dev,
> +				  struct counter_count *count, u64 *val)
> +{
> +	struct nxp_stm_cnt *stm_cnt = counter_priv(dev);
> +	u32 w1, w2, cnt;
> +
> +	do {
> +		w1 = atomic_read(&stm_cnt->nr_wraps);
> +		cnt = nxp_stm_cnt_get_counter(stm_cnt);
> +		w2 = atomic_read(&stm_cnt->nr_wraps);
> +	} while (w1 != w2);
> +
> +	*val = ((u64)w1 << 32) | cnt;

Just report the raw counter value back directly. If a user wants to
count wraparounds and accumulate the counter, they can do so by setting
a Counter watch in userspace for the COUNTER_EVENT_THRESHOLD event.
Userspace can then keep track of the nr_wraps and perform the
accumulation calculation with the current count value.

> +static int nxp_stm_cnt_watch_validate(struct counter_device *counter,
> +				      const struct counter_watch *watch)
> +{
> +	switch (watch->event) {
> +	case COUNTER_EVENT_OVERFLOW:

This becomes COUNTER_EVENT_THRESHOLD.

> +static const struct counter_ops nxp_stm_cnt_counter_ops = {
> +	.action_read = nxp_stm_cnt_action_read,
> +	.count_read  = nxp_stm_cnt_count_read,
> +	.count_write = nxp_stm_cnt_count_write,
> +	.function_read = nxp_stm_cnt_function_read,
> +	.watch_validate = nxp_stm_cnt_watch_validate,
> +};
> +
> +static struct counter_signal nxp_stm_cnt_signals[] = {
> +	{
> +		.id = 0,
> +		.name = "Counter wrap signal",

Surely this can't be how the signal is described in the datasheet (is
it?). Although you do not have to match the datasheet exactly, the
description should make it obvious which Signal is the source trigger
for the Count state changes. I haven't read the manual for this device,
but I suspect to this signal is named after some sort of clock
oscillator if it's for a timer module; this could be named "System Timer
Module Clock" if the signal is identified simply as that in the manual.

William Breathitt Gray

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH v5 1/3] counters: Reorder the Makefile
  2026-01-13 16:52 ` [PATCH v5 1/3] counters: Reorder the Makefile Daniel Lezcano
@ 2026-03-21 13:38   ` William Breathitt Gray
  0 siblings, 0 replies; 8+ messages in thread
From: William Breathitt Gray @ 2026-03-21 13:38 UTC (permalink / raw)
  To: Daniel Lezcano
  Cc: William Breathitt Gray, Frank.li, robh, conor+dt, krzk+dt, s32,
	devicetree, linux-kernel, linux-iio

On Tue, Jan 13, 2026 at 05:52:18PM +0100, Daniel Lezcano wrote:
> The next changes provide a new driver. For the sake of clarity,
> reorder the Makefile alphabetically.
> 
> No functional changes intended.
> 
> Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org>
> ---
>  drivers/counter/Makefile | 20 +++++++++++---------
>  1 file changed, 11 insertions(+), 9 deletions(-)
> 
> diff --git a/drivers/counter/Makefile b/drivers/counter/Makefile
> index fa3c1d08f706..40e644948e7a 100644
> --- a/drivers/counter/Makefile
> +++ b/drivers/counter/Makefile
> @@ -6,14 +6,16 @@
>  obj-$(CONFIG_COUNTER) += counter.o
>  counter-y := counter-core.o counter-sysfs.o counter-chrdev.o
> 
> -obj-$(CONFIG_I8254)		+= i8254.o
> -obj-$(CONFIG_104_QUAD_8)	+= 104-quad-8.o
> +obj-$(CONFIG_104_QUAD_8)		+= 104-quad-8.o
> +obj-$(CONFIG_FTM_QUADDEC)		+= ftm-quaddec.o
> +obj-$(CONFIG_I8254)			+= i8254.o
> +obj-$(CONFIG_INTEL_QEP)			+= intel-qep.o
>  obj-$(CONFIG_INTERRUPT_CNT)		+= interrupt-cnt.o
> -obj-$(CONFIG_RZ_MTU3_CNT)	+= rz-mtu3-cnt.o
> -obj-$(CONFIG_STM32_TIMER_CNT)	+= stm32-timer-cnt.o
> -obj-$(CONFIG_STM32_LPTIMER_CNT)	+= stm32-lptimer-cnt.o
> -obj-$(CONFIG_TI_EQEP)		+= ti-eqep.o
> -obj-$(CONFIG_FTM_QUADDEC)	+= ftm-quaddec.o
>  obj-$(CONFIG_MICROCHIP_TCB_CAPTURE)	+= microchip-tcb-capture.o
> -obj-$(CONFIG_INTEL_QEP)		+= intel-qep.o
> -obj-$(CONFIG_TI_ECAP_CAPTURE)	+= ti-ecap-capture.o
> +obj-$(CONFIG_RZ_MTU3_CNT)		+= rz-mtu3-cnt.o
> +obj-$(CONFIG_STM32_TIMER_CNT)		+= stm32-timer-cnt.o
> +obj-$(CONFIG_STM32_LPTIMER_CNT)		+= stm32-lptimer-cnt.o
> +obj-$(CONFIG_TI_ECAP_CAPTURE)		+= ti-ecap-capture.o
> +obj-$(CONFIG_TI_EQEP)			+= ti-eqep.o
> +
> +

Those two empty lines at the end look superfluous; if you remove them
and resubmit this patch separately I should be able to pick it up.

William Breathitt Gray

^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2026-03-21 13:39 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-01-13 16:52 [PATCH v5 0/3] Add the System Timer Module counter Daniel Lezcano
2026-01-13 16:52 ` [PATCH v5 1/3] counters: Reorder the Makefile Daniel Lezcano
2026-03-21 13:38   ` William Breathitt Gray
2026-01-13 16:52 ` [PATCH v5 2/3] dt-bindings: counter: Add NXP System Timer Module Counter Daniel Lezcano
2026-01-13 16:52 ` [PATCH v5 3/3] counter: Add STM based counter Daniel Lezcano
2026-01-13 18:01   ` Frank Li
2026-01-14 16:38     ` Daniel Lezcano
2026-01-16  5:57   ` William Breathitt Gray

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox