drivers: timer: NXP OS Timer updated for low power modes

Add ability to set a wakeup counter in case OS Timer is
disabled in certain low power modes. Also add code to
compensate the tick value.

Signed-off-by: Mahesh Mahadevan <mahesh.mahadevan@nxp.com>
This commit is contained in:
Mahesh Mahadevan 2023-11-09 08:08:11 -06:00 committed by Alberto Escolar
parent 0e94934c4b
commit 4045975c80
3 changed files with 171 additions and 7 deletions

View file

@ -11,3 +11,13 @@ config MCUX_OS_TIMER
help help
This module implements a kernel device driver for the NXP OS This module implements a kernel device driver for the NXP OS
event timer and provides the standard "system clock driver" interfaces. event timer and provides the standard "system clock driver" interfaces.
if MCUX_OS_TIMER
config MCUX_OS_TIMER_PM_POWERED_OFF
bool "Reinitialize the OS Timer"
help
OS Timer is turned off in certain low power modes. When this option is
picked, OS Timer will take steps to store state and reinitialize on wakeups.
endif # MCUX_OS_TIMER

View file

@ -13,6 +13,8 @@
#include <zephyr/irq.h> #include <zephyr/irq.h>
#include <zephyr/sys_clock.h> #include <zephyr/sys_clock.h>
#include <zephyr/spinlock.h> #include <zephyr/spinlock.h>
#include <zephyr/drivers/counter.h>
#include <zephyr/pm/pm.h>
#include "fsl_ostimer.h" #include "fsl_ostimer.h"
#include "fsl_power.h" #include "fsl_power.h"
@ -27,13 +29,26 @@
static struct k_spinlock lock; static struct k_spinlock lock;
static uint64_t last_count; static uint64_t last_count;
static OSTIMER_Type *base; static OSTIMER_Type *base;
/* Total cycles of the timer compensated to include the time lost in "sleep/deep sleep" modes.
* This maintains the timer count to account for the case if the OS Timer is reset in
* certain deep sleep modes and the time elapsed when it is powered off.
*/
static uint64_t cyc_sys_compensated;
#if DT_NODE_HAS_STATUS(DT_NODELABEL(standby), okay) && CONFIG_PM
static const struct device *counter_dev;
#endif
static uint64_t mcux_lpc_ostick_get_compensated_timer_value(void)
{
return (OSTIMER_GetCurrentTimerValue(base) + cyc_sys_compensated);
}
void mcux_lpc_ostick_isr(const void *arg) void mcux_lpc_ostick_isr(const void *arg)
{ {
ARG_UNUSED(arg); ARG_UNUSED(arg);
k_spinlock_key_t key = k_spin_lock(&lock); k_spinlock_key_t key = k_spin_lock(&lock);
uint64_t now = OSTIMER_GetCurrentTimerValue(base); uint64_t now = mcux_lpc_ostick_get_compensated_timer_value();
uint32_t dticks = (uint32_t)((now - last_count) / CYC_PER_TICK); uint32_t dticks = (uint32_t)((now - last_count) / CYC_PER_TICK);
/* Clear interrupt flag by writing 1. */ /* Clear interrupt flag by writing 1. */
@ -54,6 +69,104 @@ void mcux_lpc_ostick_isr(const void *arg)
sys_clock_announce(IS_ENABLED(CONFIG_TICKLESS_KERNEL) ? dticks : 1); sys_clock_announce(IS_ENABLED(CONFIG_TICKLESS_KERNEL) ? dticks : 1);
} }
#if DT_NODE_HAS_STATUS(DT_NODELABEL(standby), okay) && CONFIG_PM
/* The OS Timer is disabled in certain low power modes and cannot wakeup the system
* on timeout. This function will be called by the low power code to allow the
* OS Timer to save off the count if needed and also start a wakeup counter
* that would wakeup the system from deep power down modes.
*/
static uint32_t mcux_lpc_ostick_set_counter_timeout(int32_t curr_timeout)
{
uint32_t ret = 0;
if (counter_dev) {
uint32_t timeout;
int32_t ticks;
struct counter_top_cfg top_cfg = { 0 };
timeout = k_ticks_to_us_ceil32(curr_timeout);
ticks = counter_us_to_ticks(counter_dev, timeout);
ticks = CLAMP(ticks, 1, counter_get_max_top_value(counter_dev));
top_cfg.ticks = ticks;
top_cfg.callback = NULL;
top_cfg.user_data = NULL;
top_cfg.flags = 0;
if (counter_set_top_value(counter_dev, &top_cfg) != 0) {
/* Setting top value failed, try setting an alarm */
struct counter_alarm_cfg alarm_cfg;
alarm_cfg.ticks = ticks;
alarm_cfg.callback = NULL;
alarm_cfg.user_data = NULL;
alarm_cfg.flags = 0;
if (counter_set_channel_alarm(counter_dev, 0, &alarm_cfg) != 0) {
ret = 1;
goto done;
}
}
#if CONFIG_MCUX_OS_TIMER_PM_POWERED_OFF
/* Capture the current timer value for cases where it loses its state
* in low power modes.
*/
cyc_sys_compensated += OSTIMER_GetCurrentTimerValue(base);
#endif
/* Counter is set to wakeup the system after the requested time */
if (counter_start(counter_dev) != 0) {
ret = 1;
}
} else {
ret = 1;
}
done:
return ret;
}
/* After exit from certain low power modes where the OS Timer was disabled, the
* current tick value should be updated to account for the period when the OS Timer
* was disabled. Also in certain cases, the OS Timer might lose its state and needs
* to be reinitialized.
*/
static uint32_t mcux_lpc_ostick_compensate_system_timer(void)
{
uint32_t ret = 0;
if (counter_dev) {
uint32_t slept_time_ticks;
uint32_t slept_time_us;
counter_stop(counter_dev);
counter_get_value(counter_dev, &slept_time_ticks);
if (!(counter_is_counting_up(counter_dev))) {
slept_time_ticks = counter_get_top_value(counter_dev) - slept_time_ticks;
}
slept_time_us = counter_ticks_to_us(counter_dev, slept_time_ticks);
cyc_sys_compensated += (k_us_to_ticks_floor32(slept_time_us) * CYC_PER_TICK);
#if CONFIG_MCUX_OS_TIMER_PM_POWERED_OFF
/* Reactivate os_timer for cases where it loses its state */
OSTIMER_Init(base);
#endif
/* Announce the time slept to the kernel*/
mcux_lpc_ostick_isr(NULL);
} else {
ret = 1;
}
return ret;
}
#endif
void sys_clock_set_timeout(int32_t ticks, bool idle) void sys_clock_set_timeout(int32_t ticks, bool idle)
{ {
ARG_UNUSED(idle); ARG_UNUSED(idle);
@ -63,11 +176,28 @@ void sys_clock_set_timeout(int32_t ticks, bool idle)
return; return;
} }
#if DT_NODE_HAS_STATUS(DT_NODELABEL(standby), okay) && CONFIG_PM
if (idle) {
/* OS Timer may not be able to wakeup in certain low power modes.
* For these cases, we start a counter that can wakeup
* from low power modes.
*/
if (pm_state_next_get(0)->state == PM_STATE_STANDBY) {
if (mcux_lpc_ostick_set_counter_timeout(ticks) == 0) {
/* A low power counter has been started. No need to
* go further, simply return
*/
return;
}
}
}
#endif
ticks = ticks == K_TICKS_FOREVER ? MAX_TICKS : ticks; ticks = ticks == K_TICKS_FOREVER ? MAX_TICKS : ticks;
ticks = CLAMP(ticks - 1, 0, (int32_t)MAX_TICKS); ticks = CLAMP(ticks - 1, 0, (int32_t)MAX_TICKS);
k_spinlock_key_t key = k_spin_lock(&lock); k_spinlock_key_t key = k_spin_lock(&lock);
uint64_t now = OSTIMER_GetCurrentTimerValue(base); uint64_t now = mcux_lpc_ostick_get_compensated_timer_value();
uint32_t adj, cyc = ticks * CYC_PER_TICK; uint32_t adj, cyc = ticks * CYC_PER_TICK;
/* Round up to next tick boundary. */ /* Round up to next tick boundary. */
@ -83,7 +213,7 @@ void sys_clock_set_timeout(int32_t ticks, bool idle)
cyc += CYC_PER_TICK; cyc += CYC_PER_TICK;
} }
OSTIMER_SetMatchValue(base, cyc + last_count, NULL); OSTIMER_SetMatchValue(base, cyc + last_count - cyc_sys_compensated, NULL);
k_spin_unlock(&lock, key); k_spin_unlock(&lock, key);
} }
@ -96,7 +226,7 @@ uint32_t sys_clock_elapsed(void)
} }
k_spinlock_key_t key = k_spin_lock(&lock); k_spinlock_key_t key = k_spin_lock(&lock);
uint32_t ret = ((uint32_t)OSTIMER_GetCurrentTimerValue(base) - uint32_t ret = ((uint32_t)mcux_lpc_ostick_get_compensated_timer_value() -
(uint32_t)last_count) / CYC_PER_TICK; (uint32_t)last_count) / CYC_PER_TICK;
k_spin_unlock(&lock, key); k_spin_unlock(&lock, key);
@ -105,12 +235,24 @@ uint32_t sys_clock_elapsed(void)
uint32_t sys_clock_cycle_get_32(void) uint32_t sys_clock_cycle_get_32(void)
{ {
return (uint32_t)OSTIMER_GetCurrentTimerValue(base); return (uint32_t)mcux_lpc_ostick_get_compensated_timer_value();
} }
uint64_t sys_clock_cycle_get_64(void) uint64_t sys_clock_cycle_get_64(void)
{ {
return OSTIMER_GetCurrentTimerValue(base); return mcux_lpc_ostick_get_compensated_timer_value();
}
void sys_clock_idle_exit(void)
{
#if DT_NODE_HAS_STATUS(DT_NODELABEL(standby), okay) && CONFIG_PM
/* The tick should be compensated for states where the
* OS Timer is disabled
*/
if (pm_state_next_get(0)->state == PM_STATE_STANDBY) {
mcux_lpc_ostick_compensate_system_timer();
}
#endif
} }
static int sys_clock_driver_init(void) static int sys_clock_driver_init(void)
@ -129,12 +271,17 @@ static int sys_clock_driver_init(void)
/* Initialize the OS timer, setting clock configuration. */ /* Initialize the OS timer, setting clock configuration. */
OSTIMER_Init(base); OSTIMER_Init(base);
last_count = OSTIMER_GetCurrentTimerValue(base); last_count = mcux_lpc_ostick_get_compensated_timer_value();
OSTIMER_SetMatchValue(base, last_count + CYC_PER_TICK, NULL); OSTIMER_SetMatchValue(base, last_count + CYC_PER_TICK, NULL);
/* Enable event timer interrupt */ /* Enable event timer interrupt */
irq_enable(DT_INST_IRQN(0)); irq_enable(DT_INST_IRQN(0));
/* On some SoC's, OS Timer cannot wakeup from low power mode in standby modes */
#if DT_NODE_HAS_STATUS(DT_NODELABEL(standby), okay) && CONFIG_PM
counter_dev = DEVICE_DT_GET_OR_NULL(DT_INST_PHANDLE(0, deep_sleep_counter));
#endif
return 0; return 0;
} }

View file

@ -13,3 +13,10 @@ properties:
interrupts: interrupts:
required: true required: true
deep-sleep-counter:
type: phandle
description: |
Instance of a counter peripheral. The OS Timer maybe powered off in
certain deep power down modes. The OS Timer driver will use this
counter to wakeup and also to keep track of system time.