drivers: timer: stm32 lptim: add support for backup standby timer
Add support for a backup standby timer in STM32 LPTIM driver for cases when the LPTIM is not available (ie standby low power mode). A counter (typically RTC) is used for such a case. Signed-off-by: Guillaume Gautier <guillaume.gautier-ext@st.com>
This commit is contained in:
parent
e541666d90
commit
58c296b30f
|
@ -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
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
#include <zephyr/drivers/timer/system_timer.h>
|
||||
#include <zephyr/sys_clock.h>
|
||||
#include <zephyr/irq.h>
|
||||
#include <zephyr/drivers/counter.h>
|
||||
#include <zephyr/pm/policy.h>
|
||||
|
||||
#include <zephyr/spinlock.h>
|
||||
|
||||
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue