diff --git a/drivers/timer/Kconfig.mcux_os b/drivers/timer/Kconfig.mcux_os index 972a669c86ee6f..22aef2ccdde884 100644 --- a/drivers/timer/Kconfig.mcux_os +++ b/drivers/timer/Kconfig.mcux_os @@ -11,3 +11,13 @@ config MCUX_OS_TIMER help This module implements a kernel device driver for the NXP OS 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 diff --git a/drivers/timer/mcux_os_timer.c b/drivers/timer/mcux_os_timer.c index f65459505a5a37..c278f59ed95525 100644 --- a/drivers/timer/mcux_os_timer.c +++ b/drivers/timer/mcux_os_timer.c @@ -13,6 +13,8 @@ #include #include #include +#include +#include #include "fsl_ostimer.h" #include "fsl_power.h" @@ -27,13 +29,26 @@ static struct k_spinlock lock; static uint64_t last_count; 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) { ARG_UNUSED(arg); 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); /* 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); } +#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) { ARG_UNUSED(idle); @@ -63,11 +176,28 @@ void sys_clock_set_timeout(int32_t ticks, bool idle) 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 = CLAMP(ticks - 1, 0, (int32_t)MAX_TICKS); 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; /* Round up to next tick boundary. */ @@ -83,7 +213,7 @@ void sys_clock_set_timeout(int32_t ticks, bool idle) 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); } @@ -96,7 +226,7 @@ uint32_t sys_clock_elapsed(void) } 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; k_spin_unlock(&lock, key); @@ -105,12 +235,24 @@ uint32_t sys_clock_elapsed(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) { - 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) @@ -129,12 +271,17 @@ static int sys_clock_driver_init(void) /* Initialize the OS timer, setting clock configuration. */ 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); /* Enable event timer interrupt */ 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; } diff --git a/dts/bindings/timer/nxp,os-timer.yaml b/dts/bindings/timer/nxp,os-timer.yaml index 649bce77c5efd3..eb945b807fc472 100644 --- a/dts/bindings/timer/nxp,os-timer.yaml +++ b/dts/bindings/timer/nxp,os-timer.yaml @@ -13,3 +13,10 @@ properties: interrupts: 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.