Skip to content

Commit 58c296b

Browse files
gautierg-stcarlescufi
authored andcommitted
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 <[email protected]>
1 parent e541666 commit 58c296b

File tree

2 files changed

+133
-0
lines changed

2 files changed

+133
-0
lines changed

drivers/timer/Kconfig.stm32_lptim

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
# Copyright (c) 2019 STMicroelectronics
44
# SPDX-License-Identifier: Apache-2.0
55

6+
DT_CHOSEN_STDBY_TIMER := st,lptim-stdby-timer
7+
68
menuconfig STM32_LPTIM_TIMER
79
bool "STM32 Low Power Timer [EXPERIMENTAL]"
810
default y
@@ -56,4 +58,22 @@ config STM32_LPTIM_TICK_FREQ_RATIO_OVERRIDE
5658
in the driver.
5759
This options allows to override this check
5860

61+
config STM32_LPTIM_STDBY_TIMER
62+
bool "Use an additional timer while entering Standby mode"
63+
default $(dt_chosen_enabled,$(DT_CHOSEN_STDBY_TIMER))
64+
depends on COUNTER
65+
depends on TICKLESS_KERNEL
66+
select EXPERIMENTAL
67+
help
68+
There are chips e.g. STM32WBAX family that use LPTIM as a system timer,
69+
but LPTIM is not clocked in standby mode. These chips usually have
70+
another timer that is not stopped, but it has lower frequency e.g.
71+
RTC, thus it can't be used as a main system timer.
72+
73+
Use the Standby timer for timeout (wakeup) when the system is entering
74+
Standby state.
75+
76+
The chosen Standby timer node has to support setting alarm from the
77+
counter API.
78+
5979
endif # STM32_LPTIM_TIMER

drivers/timer/stm32_lptim_timer.c

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
#include <zephyr/drivers/timer/system_timer.h>
1818
#include <zephyr/sys_clock.h>
1919
#include <zephyr/irq.h>
20+
#include <zephyr/drivers/counter.h>
21+
#include <zephyr/pm/policy.h>
2022

2123
#include <zephyr/spinlock.h>
2224

@@ -80,6 +82,32 @@ static bool autoreload_ready = true;
8082

8183
static struct k_spinlock lock;
8284

85+
#ifdef CONFIG_STM32_LPTIM_STDBY_TIMER
86+
87+
#define CURRENT_CPU \
88+
(COND_CODE_1(CONFIG_SMP, (arch_curr_cpu()->id), (_current_cpu->id)))
89+
90+
#define cycle_t uint32_t
91+
92+
/* This local variable indicates that the timeout was set right before
93+
* entering standby state.
94+
*
95+
* It is used for chips that has to use a separate standby timer in such
96+
* case because the LPTIM is not clocked in some low power mode state.
97+
*/
98+
static bool timeout_stdby;
99+
100+
/* Cycle counter before entering the standby state. */
101+
static cycle_t lptim_cnt_pre_stdby;
102+
103+
/* Standby timer value before entering the standby state. */
104+
static uint32_t stdby_timer_pre_stdby;
105+
106+
/* Standby timer used for timer while entering the standby state */
107+
static const struct device *stdby_timer = DEVICE_DT_GET(DT_CHOSEN(st_lptim_stdby_timer));
108+
109+
#endif /* CONFIG_STM32_LPTIM_STDBY_TIMER */
110+
83111
static inline bool arrm_state_get(void)
84112
{
85113
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)
171199

172200
ARG_UNUSED(idle);
173201

202+
#ifdef CONFIG_STM32_LPTIM_STDBY_TIMER
203+
const struct pm_state_info *next;
204+
205+
next = pm_policy_next_state(CURRENT_CPU, ticks);
206+
207+
if ((next != NULL) && (next->state == PM_STATE_SUSPEND_TO_RAM)) {
208+
uint64_t timeout_us =
209+
((uint64_t)ticks * USEC_PER_SEC) / CONFIG_SYS_CLOCK_TICKS_PER_SEC;
210+
211+
struct counter_alarm_cfg cfg = {
212+
.callback = NULL,
213+
.ticks = counter_us_to_ticks(stdby_timer, timeout_us),
214+
.user_data = NULL,
215+
.flags = 0,
216+
};
217+
218+
timeout_stdby = true;
219+
220+
/* Set the alarm using timer that runs the standby.
221+
* Needed rump-up/setting time, lower accurency etc. should be
222+
* included in the exit-latency in the power state definition.
223+
*/
224+
counter_cancel_channel_alarm(stdby_timer, 0);
225+
counter_set_channel_alarm(stdby_timer, 0, &cfg);
226+
227+
/* Store current values to calculate a difference in
228+
* measurements after exiting the standby state.
229+
*/
230+
counter_get_value(stdby_timer, &stdby_timer_pre_stdby);
231+
lptim_cnt_pre_stdby = z_clock_lptim_getcounter();
232+
233+
return;
234+
}
235+
#endif /* CONFIG_STM32_LPTIM_STDBY_TIMER */
236+
174237
if (!IS_ENABLED(CONFIG_TICKLESS_KERNEL)) {
175238
return;
176239
}
@@ -480,5 +543,55 @@ static int sys_clock_driver_init(void)
480543
return 0;
481544
}
482545

546+
void sys_clock_idle_exit(void)
547+
{
548+
#ifdef CONFIG_STM32_LPTIM_STDBY_TIMER
549+
if (clock_control_get_status(clk_ctrl,
550+
(clock_control_subsys_t) &lptim_clk[0])
551+
!= CLOCK_CONTROL_STATUS_ON) {
552+
sys_clock_driver_init();
553+
} else if (timeout_stdby) {
554+
cycle_t missed_lptim_cnt;
555+
uint32_t stdby_timer_diff, stdby_timer_post, dticks;
556+
uint64_t stdby_timer_us;
557+
558+
/* Get current value for standby timer and reset LPTIM counter value
559+
* to start anew.
560+
*/
561+
LL_LPTIM_ResetCounter(LPTIM);
562+
counter_get_value(stdby_timer, &stdby_timer_post);
563+
564+
/* Calculate how much time has passed since last measurement for standby timer */
565+
/* Check IDLE timer overflow */
566+
if (stdby_timer_pre_stdby > stdby_timer_post) {
567+
stdby_timer_diff =
568+
(counter_get_top_value(stdby_timer) - stdby_timer_pre_stdby) +
569+
stdby_timer_post + 1;
570+
571+
} else {
572+
stdby_timer_diff = stdby_timer_post - stdby_timer_pre_stdby;
573+
}
574+
stdby_timer_us = counter_ticks_to_us(stdby_timer, stdby_timer_diff);
575+
576+
/* Convert standby time in LPTIM cnt */
577+
missed_lptim_cnt = (sys_clock_hw_cycles_per_sec() * stdby_timer_us) /
578+
USEC_PER_SEC;
579+
/* Add the LPTIM cnt pre standby */
580+
missed_lptim_cnt += lptim_cnt_pre_stdby;
581+
582+
/* Update the cycle counter to include the cycles missed in standby */
583+
accumulated_lptim_cnt += missed_lptim_cnt;
584+
585+
/* Announce the passed ticks to the kernel */
586+
dticks = (missed_lptim_cnt * CONFIG_SYS_CLOCK_TICKS_PER_SEC)
587+
/ lptim_clock_freq;
588+
sys_clock_announce(dticks);
589+
590+
/* We've already performed all needed operations */
591+
timeout_stdby = false;
592+
}
593+
#endif /* CONFIG_STM32_LPTIM_STDBY_TIMER */
594+
}
595+
483596
SYS_INIT(sys_clock_driver_init, PRE_KERNEL_2,
484597
CONFIG_SYSTEM_CLOCK_INIT_PRIORITY);

0 commit comments

Comments
 (0)