|
17 | 17 | #include <zephyr/drivers/timer/system_timer.h>
|
18 | 18 | #include <zephyr/sys_clock.h>
|
19 | 19 | #include <zephyr/irq.h>
|
| 20 | +#include <zephyr/drivers/counter.h> |
| 21 | +#include <zephyr/pm/policy.h> |
20 | 22 |
|
21 | 23 | #include <zephyr/spinlock.h>
|
22 | 24 |
|
@@ -80,6 +82,32 @@ static bool autoreload_ready = true;
|
80 | 82 |
|
81 | 83 | static struct k_spinlock lock;
|
82 | 84 |
|
| 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 | + |
83 | 111 | static inline bool arrm_state_get(void)
|
84 | 112 | {
|
85 | 113 | 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)
|
171 | 199 |
|
172 | 200 | ARG_UNUSED(idle);
|
173 | 201 |
|
| 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 | + |
174 | 237 | if (!IS_ENABLED(CONFIG_TICKLESS_KERNEL)) {
|
175 | 238 | return;
|
176 | 239 | }
|
@@ -480,5 +543,55 @@ static int sys_clock_driver_init(void)
|
480 | 543 | return 0;
|
481 | 544 | }
|
482 | 545 |
|
| 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 | + |
483 | 596 | SYS_INIT(sys_clock_driver_init, PRE_KERNEL_2,
|
484 | 597 | CONFIG_SYSTEM_CLOCK_INIT_PRIORITY);
|
0 commit comments