lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <aV/eLFO57v567Fwa@lizhi-Precision-Tower-5810>
Date: Thu, 8 Jan 2026 11:41:16 -0500
From: Frank Li <Frank.li@....com>
To: Daniel Lezcano <daniel.lezcano@...aro.org>
Cc: wbg@...nel.org, robh@...nel.org, conor+dt@...nel.org,
	krzk+dt@...nel.org, s32@....com, devicetree@...r.kernel.org,
	linux-kernel@...r.kernel.org, linux-iio@...r.kernel.org
Subject: Re: [PATCH v4 3/3] counter: Add STM based counter

On Wed, Jan 07, 2026 at 02:39:52PM +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@...aro.org>
> ---
>  drivers/counter/Kconfig       |  10 +
>  drivers/counter/Makefile      |   1 +
>  drivers/counter/nxp-stm-cnt.c | 433 ++++++++++++++++++++++++++++++++++
>  3 files changed, 444 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..9f2edd2161c8
> --- /dev/null
> +++ b/drivers/counter/nxp-stm-cnt.c
> @@ -0,0 +1,433 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright 2018,2021-2025 NXP
> + * Copyright 2025 Linaro Limited
> + *
> + * Author: Daniel Lezcano <daniel.lezcano@...aro.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/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>
> +#include <linux/spinlock.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 {
> +	spinlock_t lock; /* Protects counter */
> +	void __iomem *base;
> +	u64 counter;
> +	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);
> +
> +	counter_push_event(counter, COUNTER_EVENT_OVERFLOW, 0);
> +
> +	spin_lock(&stm_cnt->lock);
> +	stm_cnt->counter += U32_MAX;
> +	spin_unlock(&stm_cnt->lock);
> +
> +	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);
> +	unsigned long irqflags;
> +
> +	spin_lock_irqsave(&stm_cnt->lock, irqflags);
> +	*val = stm_cnt->counter + nxp_stm_cnt_get_counter(stm_cnt);
> +	spin_unlock_irqrestore(&stm_cnt->lock, irqflags);

It think there are still rise condition here.

cpu 0                                            cpu 1
1:  nxp_stm_cnt_get_counter(stm_cnt); (0)
2: 						 irq_handle()
					         counter += INT_MAX;
3:

when wrap happen, nxp_stm_cnt_get_counter() return 0, but, irq still not
handle yet.

so nxp_stm_cnt_count_read() return 0 at 1.

it return INT_MAX at 3 suddently.

Frank
> +
> +	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);
> +	unsigned long irqflags;
> +
> +	spin_lock_irqsave(&stm_cnt->lock, irqflags);
> +	stm_cnt->counter = 0;
> +	nxp_stm_cnt_set_counter(stm_cnt, 0);
> +	spin_unlock_irqrestore(&stm_cnt->lock, irqflags);
> +
> +	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 const enum counter_synapse_action nxp_stm_cnt_synapse_actions[] = {
> +	COUNTER_SYNAPSE_ACTION_RISING_EDGE,
> +};
> +
> +static struct counter_signal nxp_stm_cnt_signals[] = {
> +	{
> +		.id = 0,
> +		.name = "Counter wrap signal"
> +	},
> +};
> +
> +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[0],
> +	},
> +};
> +
> +static struct counter_count nxp_stm_cnt_counts[] = {
> +	{
> +		.id = 0,
> +		.name = "System Timer Module Counter",
> +		.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;
> +	stm_cnt->counter = 0;
> +	spin_lock_init(&stm_cnt->lock);
> +
> +	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
>

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ