Skip to content

Commit 4da30d0

Browse files
mmahadevan108kartben
authored andcommitted
drivers: mcux_os_timer: Handle counter overflow in Power Mode 3
The counter that is used in Power Mode 3 to track System time could overflow for large timeouts. Add an API that the power system could use to ignore wakeup events from the timer. Signed-off-by: Mahesh Mahadevan <[email protected]>
1 parent ac7451e commit 4da30d0

File tree

2 files changed

+79
-13
lines changed

2 files changed

+79
-13
lines changed

drivers/timer/mcux_os_timer.c

Lines changed: 48 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
#include <zephyr/init.h>
1212
#include <zephyr/drivers/timer/system_timer.h>
13+
#include <zephyr/drivers/timer/nxp_os_timer.h>
1314
#include <zephyr/irq.h>
1415
#include <zephyr/sys_clock.h>
1516
#include <zephyr/spinlock.h>
@@ -45,6 +46,11 @@ static uint64_t cyc_sys_compensated;
4546
static const struct device *counter_dev;
4647
/* Indicates if the counter is running. */
4748
static bool counter_running;
49+
/* Indicates we received a call with ticks set to wait forever */
50+
static bool wait_forever;
51+
/* Incase of counter overflow, track the remaining ticks left */
52+
static uint32_t counter_remaining_ticks;
53+
static uint32_t counter_max_val;
4854
#endif
4955

5056
static uint64_t mcux_lpc_ostick_get_compensated_timer_value(void)
@@ -93,8 +99,21 @@ static uint32_t mcux_lpc_ostick_set_counter_timeout(int32_t curr_timeout)
9399
uint32_t ticks;
94100
struct counter_top_cfg top_cfg = { 0 };
95101

96-
ticks = counter_us_to_ticks(counter_dev, curr_timeout);
97-
ticks = CLAMP(ticks, 1, counter_get_max_top_value(counter_dev));
102+
/* Check if we should use the remaining ticks from a prior overflow */
103+
if (counter_remaining_ticks) {
104+
ticks = counter_remaining_ticks;
105+
} else {
106+
ticks = counter_us_to_ticks(counter_dev, curr_timeout);
107+
counter_remaining_ticks = ticks;
108+
}
109+
110+
/* Check if the counter overflows */
111+
if (ticks > counter_max_val) {
112+
counter_remaining_ticks -= counter_max_val;
113+
} else {
114+
counter_remaining_ticks = 0;
115+
}
116+
ticks = CLAMP(ticks, 1, counter_max_val);
98117

99118
top_cfg.ticks = ticks;
100119
top_cfg.callback = NULL;
@@ -175,6 +194,11 @@ static uint32_t mcux_lpc_ostick_compensate_system_timer(void)
175194
return 0;
176195
}
177196

197+
bool z_nxp_os_timer_ignore_timer_wakeup(void)
198+
{
199+
return (wait_forever || counter_remaining_ticks);
200+
}
201+
178202
#endif
179203

180204
void sys_clock_set_timeout(int32_t ticks, bool idle)
@@ -194,17 +218,23 @@ void sys_clock_set_timeout(int32_t ticks, bool idle)
194218
if (pm_state_next_get(0)->state == PM_STATE_STANDBY) {
195219
uint64_t timeout;
196220

197-
/* Check the amount of time left and switch to a counter
198-
* that is active in this power mode.
199-
*/
200-
timeout = base->MATCH_L;
201-
timeout |= (uint64_t)(base->MATCH_H) << 32;
202-
timeout = OSTIMER_GrayToDecimal(timeout);
203-
timeout -= OSTIMER_GetCurrentTimerValue(base);
204-
/* Round up to the next tick boundary */
205-
timeout += (CYC_PER_TICK - 1);
206-
/* Convert to microseconds and round up to the next value */
207-
timeout = (((timeout / CYC_PER_TICK) * CYC_PER_TICK) * CYC_PER_US);
221+
if (wait_forever) {
222+
timeout = UINT32_MAX;
223+
} else if (counter_remaining_ticks) {
224+
timeout = counter_remaining_ticks;
225+
} else {
226+
/* Check the amount of time left and switch to a counter
227+
* that is active in this power mode.
228+
*/
229+
timeout = base->MATCH_L;
230+
timeout |= (uint64_t)(base->MATCH_H) << 32;
231+
timeout = OSTIMER_GrayToDecimal(timeout);
232+
timeout -= OSTIMER_GetCurrentTimerValue(base);
233+
/* Round up to the next tick boundary */
234+
timeout += (CYC_PER_TICK - 1);
235+
/* Convert to microseconds and round up to the next value */
236+
timeout = (((timeout / CYC_PER_TICK) * CYC_PER_TICK) * CYC_PER_US);
237+
}
208238
if (mcux_lpc_ostick_set_counter_timeout(timeout) == 0) {
209239
/* A low power counter has been started. No need to
210240
* go further, simply return
@@ -213,6 +243,10 @@ void sys_clock_set_timeout(int32_t ticks, bool idle)
213243
}
214244
}
215245
}
246+
/* When using a counter for certain low power modes, set this flag when the requested
247+
* delay is forever. This is to keep track of wakeup sources in case of counter overflows.
248+
*/
249+
wait_forever = (ticks == SYS_CLOCK_MAX_WAIT);
216250
#else
217251
ARG_UNUSED(idle);
218252
#endif
@@ -304,6 +338,7 @@ static int sys_clock_driver_init(void)
304338
/* On some SoC's, OS Timer cannot wakeup from low power mode in standby modes */
305339
#if DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(standby)) && CONFIG_PM
306340
counter_dev = DEVICE_DT_GET_OR_NULL(DT_INST_PHANDLE(0, deep_sleep_counter));
341+
counter_max_val = counter_get_max_top_value(counter_dev);
307342
#endif
308343

309344
return 0;
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright (c) 2025 NXP
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#ifndef ZEPHYR_INCLUDE_DRIVERS_TIMER_NXP_OS_TIMER_H
8+
#define ZEPHYR_INCLUDE_DRIVERS_TIMER_NXP_OS_TIMER_H
9+
10+
#ifdef __cplusplus
11+
extern "C" {
12+
#endif
13+
14+
/** @brief The timer may have overflowed and triggered a wakeup.
15+
*
16+
* OS Timer for certain low power modes switches to a counter maintain
17+
* system time as it is powered off. These counters could overflow for
18+
* certain tick values like K_TICKS_FOREVER. This could trigger a wakeup
19+
* event which the system could ignore and go back to sleep.
20+
*
21+
* @retval true if the PM subsystem should ignore wakeup events and go back
22+
* to sleep.
23+
* false if the timer wakeup event is as expected
24+
*/
25+
bool z_nxp_os_timer_ignore_timer_wakeup(void);
26+
27+
#ifdef __cplusplus
28+
}
29+
#endif
30+
31+
#endif /* ZEPHYR_INCLUDE_DRIVERS_TIMER_NXP_OS_TIMER_H */

0 commit comments

Comments
 (0)