diff --git a/drivers/timer/Kconfig.stm32_lptim b/drivers/timer/Kconfig.stm32_lptim index dc64ec5bda..31bddc0740 100644 --- a/drivers/timer/Kconfig.stm32_lptim +++ b/drivers/timer/Kconfig.stm32_lptim @@ -3,6 +3,8 @@ # Copyright (c) 2019 STMicroelectronics # SPDX-License-Identifier: Apache-2.0 +DT_CHOSEN_STDBY_TIMER := st,lptim-stdby-timer + menuconfig STM32_LPTIM_TIMER bool "STM32 Low Power Timer [EXPERIMENTAL]" default y @@ -56,4 +58,22 @@ config STM32_LPTIM_TICK_FREQ_RATIO_OVERRIDE in the driver. This options allows to override this check +config STM32_LPTIM_STDBY_TIMER + bool "Use an additional timer while entering Standby mode" + default $(dt_chosen_enabled,$(DT_CHOSEN_STDBY_TIMER)) + depends on COUNTER + depends on TICKLESS_KERNEL + select EXPERIMENTAL + help + There are chips e.g. STM32WBAX family that use LPTIM as a system timer, + but LPTIM is not clocked in standby mode. These chips usually have + another timer that is not stopped, but it has lower frequency e.g. + RTC, thus it can't be used as a main system timer. + + Use the Standby timer for timeout (wakeup) when the system is entering + Standby state. + + The chosen Standby timer node has to support setting alarm from the + counter API. + endif # STM32_LPTIM_TIMER diff --git a/drivers/timer/stm32_lptim_timer.c b/drivers/timer/stm32_lptim_timer.c index 4ad3bae3ca..ba00014ad4 100644 --- a/drivers/timer/stm32_lptim_timer.c +++ b/drivers/timer/stm32_lptim_timer.c @@ -17,6 +17,8 @@ #include #include #include +#include +#include #include @@ -80,6 +82,32 @@ static bool autoreload_ready = true; static struct k_spinlock lock; +#ifdef CONFIG_STM32_LPTIM_STDBY_TIMER + +#define CURRENT_CPU \ + (COND_CODE_1(CONFIG_SMP, (arch_curr_cpu()->id), (_current_cpu->id))) + +#define cycle_t uint32_t + +/* This local variable indicates that the timeout was set right before + * entering standby state. + * + * It is used for chips that has to use a separate standby timer in such + * case because the LPTIM is not clocked in some low power mode state. + */ +static bool timeout_stdby; + +/* Cycle counter before entering the standby state. */ +static cycle_t lptim_cnt_pre_stdby; + +/* Standby timer value before entering the standby state. */ +static uint32_t stdby_timer_pre_stdby; + +/* Standby timer used for timer while entering the standby state */ +static const struct device *stdby_timer = DEVICE_DT_GET(DT_CHOSEN(st_lptim_stdby_timer)); + +#endif /* CONFIG_STM32_LPTIM_STDBY_TIMER */ + static inline bool arrm_state_get(void) { return (LL_LPTIM_IsActiveFlag_ARRM(LPTIM) && LL_LPTIM_IsEnabledIT_ARRM(LPTIM)); @@ -171,6 +199,41 @@ void sys_clock_set_timeout(int32_t ticks, bool idle) ARG_UNUSED(idle); +#ifdef CONFIG_STM32_LPTIM_STDBY_TIMER + const struct pm_state_info *next; + + next = pm_policy_next_state(CURRENT_CPU, ticks); + + if ((next != NULL) && (next->state == PM_STATE_SUSPEND_TO_RAM)) { + uint64_t timeout_us = + ((uint64_t)ticks * USEC_PER_SEC) / CONFIG_SYS_CLOCK_TICKS_PER_SEC; + + struct counter_alarm_cfg cfg = { + .callback = NULL, + .ticks = counter_us_to_ticks(stdby_timer, timeout_us), + .user_data = NULL, + .flags = 0, + }; + + timeout_stdby = true; + + /* Set the alarm using timer that runs the standby. + * Needed rump-up/setting time, lower accurency etc. should be + * included in the exit-latency in the power state definition. + */ + counter_cancel_channel_alarm(stdby_timer, 0); + counter_set_channel_alarm(stdby_timer, 0, &cfg); + + /* Store current values to calculate a difference in + * measurements after exiting the standby state. + */ + counter_get_value(stdby_timer, &stdby_timer_pre_stdby); + lptim_cnt_pre_stdby = z_clock_lptim_getcounter(); + + return; + } +#endif /* CONFIG_STM32_LPTIM_STDBY_TIMER */ + if (!IS_ENABLED(CONFIG_TICKLESS_KERNEL)) { return; } @@ -480,5 +543,55 @@ static int sys_clock_driver_init(void) return 0; } +void sys_clock_idle_exit(void) +{ +#ifdef CONFIG_STM32_LPTIM_STDBY_TIMER + if (clock_control_get_status(clk_ctrl, + (clock_control_subsys_t) &lptim_clk[0]) + != CLOCK_CONTROL_STATUS_ON) { + sys_clock_driver_init(); + } else if (timeout_stdby) { + cycle_t missed_lptim_cnt; + uint32_t stdby_timer_diff, stdby_timer_post, dticks; + uint64_t stdby_timer_us; + + /* Get current value for standby timer and reset LPTIM counter value + * to start anew. + */ + LL_LPTIM_ResetCounter(LPTIM); + counter_get_value(stdby_timer, &stdby_timer_post); + + /* Calculate how much time has passed since last measurement for standby timer */ + /* Check IDLE timer overflow */ + if (stdby_timer_pre_stdby > stdby_timer_post) { + stdby_timer_diff = + (counter_get_top_value(stdby_timer) - stdby_timer_pre_stdby) + + stdby_timer_post + 1; + + } else { + stdby_timer_diff = stdby_timer_post - stdby_timer_pre_stdby; + } + stdby_timer_us = counter_ticks_to_us(stdby_timer, stdby_timer_diff); + + /* Convert standby time in LPTIM cnt */ + missed_lptim_cnt = (sys_clock_hw_cycles_per_sec() * stdby_timer_us) / + USEC_PER_SEC; + /* Add the LPTIM cnt pre standby */ + missed_lptim_cnt += lptim_cnt_pre_stdby; + + /* Update the cycle counter to include the cycles missed in standby */ + accumulated_lptim_cnt += missed_lptim_cnt; + + /* Announce the passed ticks to the kernel */ + dticks = (missed_lptim_cnt * CONFIG_SYS_CLOCK_TICKS_PER_SEC) + / lptim_clock_freq; + sys_clock_announce(dticks); + + /* We've already performed all needed operations */ + timeout_stdby = false; + } +#endif /* CONFIG_STM32_LPTIM_STDBY_TIMER */ +} + SYS_INIT(sys_clock_driver_init, PRE_KERNEL_2, CONFIG_SYSTEM_CLOCK_INIT_PRIORITY);