Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion boards/nxp/imx95_evk/imx95_evk_mimx9596_m7.dts
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@
status = "okay";
};

&lptmr2 {
&lptmr1 {
status = "okay";
};

Expand Down
21 changes: 21 additions & 0 deletions drivers/timer/Kconfig.mcux_lptmr
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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.
115 changes: 110 additions & 5 deletions drivers/timer/mcux_lptmr_timer.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2021 Vestas Wind Systems A/S
* Copyright 2025 NXP
*
* SPDX-License-Identifier: Apache-2.0
*/
Expand Down Expand Up @@ -51,16 +52,94 @@ 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);

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)
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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
Expand Down
12 changes: 12 additions & 0 deletions dts/arm/nxp/nxp_imx95_m7.dtsi
Original file line number Diff line number Diff line change
Expand Up @@ -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 = <DT_FREQ_K(32)>;
clk-source = <2>;
prescaler = <1>;
resolution = <32>;
status = "disabled";
};

lptmr2: timer@424d0000 {
compatible = "nxp,lptmr";
reg = <0x424d0000 DT_SIZE_K(4)>;
Expand Down
16 changes: 15 additions & 1 deletion soc/nxp/imx/imx9/imx95/Kconfig.defconfig.mimx95.m7
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* Copyright 2025 NXP
*
* SPDX-License-Identifier: Apache-2.0
*/

&lptmr2 {
status = "okay";
};
Loading