diff --git a/boards/nxp/imx95_evk/imx95_evk_mimx9596_m7.dts b/boards/nxp/imx95_evk/imx95_evk_mimx9596_m7.dts index 7e513f583660e..ce5b74e8ccd4a 100644 --- a/boards/nxp/imx95_evk/imx95_evk_mimx9596_m7.dts +++ b/boards/nxp/imx95_evk/imx95_evk_mimx9596_m7.dts @@ -142,7 +142,7 @@ status = "okay"; }; -&lptmr2 { +&lptmr1 { status = "okay"; }; diff --git a/drivers/timer/Kconfig.mcux_lptmr b/drivers/timer/Kconfig.mcux_lptmr index 0e68b22b399d5..025aaab0646a7 100644 --- a/drivers/timer/Kconfig.mcux_lptmr +++ b/drivers/timer/Kconfig.mcux_lptmr @@ -1,6 +1,7 @@ # Copyright (c) 2014-2015 Wind River Systems, Inc. # Copyright (c) 2016 Cadence Design Systems, Inc. # Copyright (c) 2019 Intel Corp. +# Copyright 2025 NXP # SPDX-License-Identifier: Apache-2.0 config MCUX_LPTMR_TIMER @@ -13,3 +14,23 @@ config MCUX_LPTMR_TIMER This module implements a kernel device driver for the NXP MCUX Low Power Timer (LPTMR) and provides the standard "system clock driver" interfaces. + +config MCUX_LPTMR_TIMER_CUSTOM_SAFETY_CYCLES_WINDOW + bool "Custom LPTMR safety window" + depends on MCUX_LPTMR_TIMER + help + Custom safety window configuration for LPTMR timer. + If enabled, the driver will use SoC-specific default values. + +config MCUX_LPTMR_TIMER_SAFETY_WINDOW_CYCLES + int "MCUX LPTMR timer safety window cycles" + default 100 + depends on MCUX_LPTMR_TIMER_CUSTOM_SAFETY_CYCLES_WINDOW + help + Safety window in cycles to prevent race conditions when updating + the LPTMR compare register (CMR). This value determines the minimum + distance between the current hardware counter and the new compare + value to ensure reliable interrupt triggering. + A smaller value reduces timer latency but increases the risk of + missed interrupts due to hardware timing constraints. A larger + value improves reliability at the cost of slightly increased. diff --git a/drivers/timer/mcux_lptmr_timer.c b/drivers/timer/mcux_lptmr_timer.c index f83e4c39e26a7..d05bb6bab46a8 100644 --- a/drivers/timer/mcux_lptmr_timer.c +++ b/drivers/timer/mcux_lptmr_timer.c @@ -1,5 +1,6 @@ /* * Copyright (c) 2021 Vestas Wind Systems A/S + * Copyright 2025 NXP * * SPDX-License-Identifier: Apache-2.0 */ @@ -51,9 +52,46 @@ BUILD_ASSERT(DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT) == 1, #define CYCLES_PER_TICK ((uint32_t)((uint64_t)sys_clock_hw_cycles_per_sec() \ / (uint64_t)CONFIG_SYS_CLOCK_TICKS_PER_SEC)) +#define COUNTER_MAX 0xffffffff + +#define MAX_TICKS ((COUNTER_MAX / CYCLES_PER_TICK) - 1) +#define MAX_CYCLES (MAX_TICKS * CYCLES_PER_TICK) +#define MIN_DELAY 1000 + +#ifdef CONFIG_MCUX_LPTMR_TIMER_SAFETY_WINDOW_CYCLES +#define SAFETY_WINDOW_CYCLES CONFIG_MCUX_LPTMR_TIMER_SAFETY_WINDOW_CYCLES +#else +#define SAFETY_WINDOW_CYCLES 100 +#endif + /* 32 bit cycle counter */ static volatile uint32_t cycles; +/* + * Stores the current number of cycles the system has had announced to it, + * since the last rollover of the free running counter. + */ +static uint32_t announced_cycles; + +/* Lock on shared variables */ +static struct k_spinlock lock; + +static void lptmr_set_safe_immediate(uint32_t target_cycles) +{ + uint32_t hw_counter; + + /* Read current hardware counter */ + hw_counter = LPTMR_GetCurrentTimerCount(LPTMR_BASE); + + /* adjust target to be outside of safety window */ + if ((target_cycles > hw_counter) && + ((target_cycles - hw_counter) <= SAFETY_WINDOW_CYCLES)) { + target_cycles = hw_counter + SAFETY_WINDOW_CYCLES + 1; + } + + LPTMR_SetTimerPeriod(LPTMR_BASE, target_cycles); +} + void sys_clock_set_timeout(int32_t ticks, bool idle) { ARG_UNUSED(idle); @@ -61,6 +99,47 @@ void sys_clock_set_timeout(int32_t ticks, bool idle) if (idle && (ticks == K_TICKS_FOREVER)) { LPTMR_DisableInterrupts(LPTMR_BASE, kLPTMR_TimerInterruptEnable); } + + if (!IS_ENABLED(CONFIG_TICKLESS_KERNEL)) { + return; + } + + k_spinlock_key_t key; + uint32_t next, adj, now; + + ticks = (ticks == K_TICKS_FOREVER) ? MAX_TICKS : ticks; + /* Clamp ticks. We subtract one since we round up to next tick */ + ticks = CLAMP((ticks - 1), 0, (int32_t)MAX_TICKS); + + key = k_spin_lock(&lock); + + /* Read current timer value */ + now = LPTMR_GetCurrentTimerCount(LPTMR_BASE); + + /* Adjustment value, used to ensure next capture is on tick boundary */ + adj = (now - announced_cycles) + (CYCLES_PER_TICK - 1); + + next = ticks * CYCLES_PER_TICK; + /* + * The following section rounds the capture value up to the next tick + * boundary + */ + if (next <= MAX_CYCLES - adj) { + next += adj; + } else { + next = MAX_CYCLES; + } + next = (next / CYCLES_PER_TICK) * CYCLES_PER_TICK; + + if ((int32_t)(next + announced_cycles - now) < MIN_DELAY) { + next += CYCLES_PER_TICK; + } + + next += announced_cycles; + + /* Set LPTMR output value */ + lptmr_set_safe_immediate(next); + k_spin_unlock(&lock, key); } void sys_clock_idle_exit(void) @@ -78,7 +157,17 @@ void sys_clock_disable(void) uint32_t sys_clock_elapsed(void) { - return 0; + if (!IS_ENABLED(CONFIG_TICKLESS_KERNEL)) { + return 0; + } + + k_spinlock_key_t key = k_spin_lock(&lock); + uint32_t now = LPTMR_GetCurrentTimerCount(LPTMR_BASE); + + now -= announced_cycles; + k_spin_unlock(&lock, key); + + return now / CYCLES_PER_TICK; } uint32_t sys_clock_cycle_get_32(void) @@ -89,11 +178,23 @@ uint32_t sys_clock_cycle_get_32(void) static void mcux_lptmr_timer_isr(const void *arg) { ARG_UNUSED(arg); + k_spinlock_key_t key; + uint32_t tick = 0; + + key = k_spin_lock(&lock); + if (IS_ENABLED(CONFIG_TICKLESS_KERNEL)) { + uint32_t now = LPTMR_GetCurrentTimerCount(LPTMR_BASE); + + LPTMR_ClearStatusFlags(LPTMR_BASE, kLPTMR_TimerCompareFlag); + tick += (now - announced_cycles) / CYCLES_PER_TICK; + announced_cycles = now; + } else { + LPTMR_ClearStatusFlags(LPTMR_BASE, kLPTMR_TimerCompareFlag); + cycles += CYCLES_PER_TICK; + } - cycles += CYCLES_PER_TICK; - - sys_clock_announce(1); - LPTMR_ClearStatusFlags(LPTMR_BASE, kLPTMR_TimerCompareFlag); + k_spin_unlock(&lock, key); + sys_clock_announce(IS_ENABLED(CONFIG_TICKLESS_KERNEL) ? tick : 1); } static int sys_clock_driver_init(void) @@ -103,7 +204,11 @@ static int sys_clock_driver_init(void) LPTMR_GetDefaultConfig(&config); config.timerMode = kLPTMR_TimerModeTimeCounter; +#if defined(CONFIG_TICKLESS_KERNEL) + config.enableFreeRunning = true; +#else config.enableFreeRunning = false; +#endif config.prescalerClockSource = LPTMR_CLK_SOURCE; #if LPTMR_BYPASS_PRESCALER diff --git a/dts/arm/nxp/nxp_imx95_m7.dtsi b/dts/arm/nxp/nxp_imx95_m7.dtsi index dd0678fe13b86..4e3d23eff705f 100644 --- a/dts/arm/nxp/nxp_imx95_m7.dtsi +++ b/dts/arm/nxp/nxp_imx95_m7.dtsi @@ -508,6 +508,18 @@ status = "disabled"; }; + lptmr1: timer@44300000 { + compatible = "nxp,lptmr"; + reg = <0x44300000 DT_SIZE_K(4)>; + interrupts = <18 0>; + clocks = <&scmi_clk IMX95_CLK_LPTMR1>; + clock-frequency = ; + clk-source = <2>; + prescaler = <1>; + resolution = <32>; + status = "disabled"; + }; + lptmr2: timer@424d0000 { compatible = "nxp,lptmr"; reg = <0x424d0000 DT_SIZE_K(4)>; diff --git a/soc/nxp/imx/imx9/imx95/Kconfig.defconfig.mimx95.m7 b/soc/nxp/imx/imx9/imx95/Kconfig.defconfig.mimx95.m7 index 7addf0e799193..c86662ba4ce90 100644 --- a/soc/nxp/imx/imx9/imx95/Kconfig.defconfig.mimx95.m7 +++ b/soc/nxp/imx/imx9/imx95/Kconfig.defconfig.mimx95.m7 @@ -42,19 +42,33 @@ config 3RD_LEVEL_INTERRUPTS config NUM_IRQS default 250 # 2ND_LVL_ISR_TBL_OFFSET + MAX_IRQ_PER_AGGREGATOR * NUM_2ND_LEVEL_AGGREGATORS +config CORTEX_M_SYSTICK + default n if MCUX_LPTMR_TIMER + config SYS_CLOCK_HW_CYCLES_PER_SEC - default 800000000 + default 32768 if MCUX_LPTMR_TIMER + default 800000000 if CORTEX_M_SYSTICK config CACHE_MANAGEMENT default y config ETH_NXP_IMX_MSGINTR default 2 + if PM # PM code that runs from the idle loop has a large # footprint. Hence increase the size when PM is enabled. config IDLE_STACK_SIZE default 640 + +config MCUX_LPTMR_TIMER + default y + +config TICKLESS_KERNEL + default y + +config SYS_CLOCK_TICKS_PER_SEC + default 1000 endif config ROM_START_OFFSET diff --git a/tests/drivers/counter/counter_basic_api/boards/imx95_evk_mimx9596_m7.overlay b/tests/drivers/counter/counter_basic_api/boards/imx95_evk_mimx9596_m7.overlay new file mode 100644 index 0000000000000..78ef9b6bb57b5 --- /dev/null +++ b/tests/drivers/counter/counter_basic_api/boards/imx95_evk_mimx9596_m7.overlay @@ -0,0 +1,9 @@ +/* + * Copyright 2025 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +&lptmr2 { + status = "okay"; +};