Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

STM32 Add PM Suspend to Ram support #67534

Merged
16 changes: 15 additions & 1 deletion drivers/adc/adc_stm32.c
Original file line number Diff line number Diff line change
Expand Up @@ -680,7 +680,7 @@ static void adc_stm32_oversampling_ratioshift(ADC_TypeDef *adc, uint32_t ratio,
}

/*
* Function to configure the oversampling ratio and shit using stm32 LL
* Function to configure the oversampling ratio and shift using stm32 LL
* ratio is directly the sequence->oversampling (a 2^n value)
* shift is the corresponding LL_ADC_OVS_SHIFT_RIGHT_x constant
*/
Expand Down Expand Up @@ -764,6 +764,10 @@ static void dma_callback(const struct device *dev, void *user_data,
adc_context_on_sampling_done(&data->ctx, dev);
pm_policy_state_lock_put(PM_STATE_SUSPEND_TO_IDLE,
PM_ALL_SUBSTATES);
if (IS_ENABLED(CONFIG_PM_S2RAM)) {
pm_policy_state_lock_put(PM_STATE_SUSPEND_TO_RAM,
PM_ALL_SUBSTATES);
}
} else if (status < 0) {
LOG_ERR("DMA sampling complete, but DMA reported error %d", status);
data->dma_error = status;
Expand Down Expand Up @@ -1066,6 +1070,10 @@ static void adc_stm32_isr(const struct device *dev)
adc_context_on_sampling_done(&data->ctx, dev);
pm_policy_state_lock_put(PM_STATE_SUSPEND_TO_IDLE,
PM_ALL_SUBSTATES);
if (IS_ENABLED(CONFIG_PM_S2RAM)) {
pm_policy_state_lock_put(PM_STATE_SUSPEND_TO_RAM,
PM_ALL_SUBSTATES);
}
}
}

Expand Down Expand Up @@ -1101,6 +1109,9 @@ static int adc_stm32_read(const struct device *dev,

adc_context_lock(&data->ctx, false, NULL);
pm_policy_state_lock_get(PM_STATE_SUSPEND_TO_IDLE, PM_ALL_SUBSTATES);
if (IS_ENABLED(CONFIG_PM_S2RAM)) {
pm_policy_state_lock_get(PM_STATE_SUSPEND_TO_RAM, PM_ALL_SUBSTATES);
}
error = start_read(dev, sequence);
adc_context_release(&data->ctx, error);

Expand All @@ -1117,6 +1128,9 @@ static int adc_stm32_read_async(const struct device *dev,

adc_context_lock(&data->ctx, true, async);
pm_policy_state_lock_get(PM_STATE_SUSPEND_TO_IDLE, PM_ALL_SUBSTATES);
if (IS_ENABLED(CONFIG_PM_S2RAM)) {
pm_policy_state_lock_get(PM_STATE_SUSPEND_TO_RAM, PM_ALL_SUBSTATES);
}
error = start_read(dev, sequence);
adc_context_release(&data->ctx, error);

Expand Down
33 changes: 30 additions & 3 deletions drivers/entropy/entropy_stm32.c
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,9 @@ static int start_pool_filling(bool wait)
* rng pool is filled.
*/
pm_policy_state_lock_get(PM_STATE_SUSPEND_TO_IDLE, PM_ALL_SUBSTATES);
if (IS_ENABLED(CONFIG_PM_S2RAM)) {
pm_policy_state_lock_get(PM_STATE_SUSPEND_TO_RAM, PM_ALL_SUBSTATES);
}

acquire_rng();
irq_enable(IRQN);
Expand Down Expand Up @@ -546,6 +549,9 @@ static void stm32_rng_isr(const void *arg)
irq_disable(IRQN);
release_rng();
pm_policy_state_lock_put(PM_STATE_SUSPEND_TO_IDLE, PM_ALL_SUBSTATES);
if (IS_ENABLED(CONFIG_PM_S2RAM)) {
pm_policy_state_lock_put(PM_STATE_SUSPEND_TO_RAM, PM_ALL_SUBSTATES);
}
entropy_stm32_rng_data.filling_pools = false;
}

Expand Down Expand Up @@ -695,16 +701,37 @@ static int entropy_stm32_rng_init(const struct device *dev)
static int entropy_stm32_rng_pm_action(const struct device *dev,
enum pm_device_action action)
{
struct entropy_stm32_rng_dev_data *dev_data = dev->data;

int res = 0;

/* Remove warning on some platforms */
ARG_UNUSED(dev_data);

switch (action) {
case PM_DEVICE_ACTION_SUSPEND:
res = entropy_stm32_suspend();
break;
case PM_DEVICE_ACTION_RESUME:
/* Resume RNG only if it was suspended during filling pool */
if (entropy_stm32_rng_data.filling_pools) {
res = entropy_stm32_resume();
if (IS_ENABLED(CONFIG_PM_S2RAM)) {
#if DT_INST_NODE_HAS_PROP(0, health_test_config)
entropy_stm32_resume();
#if DT_INST_NODE_HAS_PROP(0, health_test_magic)
LL_RNG_SetHealthConfig(rng, DT_INST_PROP(0, health_test_magic));
#endif /* health_test_magic */
if (LL_RNG_GetHealthConfig(dev_data->rng) !=
DT_INST_PROP_OR(0, health_test_config, 0U)) {
entropy_stm32_rng_init(dev);
} else if (!entropy_stm32_rng_data.filling_pools) {
/* Resume RNG only if it was suspended during filling pool */
entropy_stm32_suspend();
}
#endif /* health_test_config */
} else {
/* Resume RNG only if it was suspended during filling pool */
if (entropy_stm32_rng_data.filling_pools) {
res = entropy_stm32_resume();
}
}
break;
default:
Expand Down
8 changes: 5 additions & 3 deletions drivers/gpio/gpio_stm32.c
Original file line number Diff line number Diff line change
Expand Up @@ -524,9 +524,11 @@ static int gpio_stm32_config(const struct device *dev,
}

/* Enable device clock before configuration (requires bank writes) */
err = pm_device_runtime_get(dev);
if (err < 0) {
return err;
if (((flags & GPIO_OUTPUT) != 0) || ((flags & GPIO_INPUT) != 0)) {
erwango marked this conversation as resolved.
Show resolved Hide resolved
err = pm_device_runtime_get(dev);
if (err < 0) {
return err;
}
}

if ((flags & GPIO_OUTPUT) != 0) {
Expand Down
35 changes: 25 additions & 10 deletions drivers/serial/uart_stm32.c
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ static void uart_stm32_pm_policy_state_lock_get(const struct device *dev)
if (!data->pm_policy_state_on) {
data->pm_policy_state_on = true;
pm_policy_state_lock_get(PM_STATE_SUSPEND_TO_IDLE, PM_ALL_SUBSTATES);
if (IS_ENABLED(CONFIG_PM_S2RAM)) {
pm_policy_state_lock_get(PM_STATE_SUSPEND_TO_RAM, PM_ALL_SUBSTATES);
}
}
}

Expand All @@ -106,6 +109,9 @@ static void uart_stm32_pm_policy_state_lock_put(const struct device *dev)
if (data->pm_policy_state_on) {
data->pm_policy_state_on = false;
pm_policy_state_lock_put(PM_STATE_SUSPEND_TO_IDLE, PM_ALL_SUBSTATES);
if (IS_ENABLED(CONFIG_PM_S2RAM)) {
pm_policy_state_lock_put(PM_STATE_SUSPEND_TO_RAM, PM_ALL_SUBSTATES);
}
}
}
#endif /* CONFIG_PM */
Expand Down Expand Up @@ -2075,17 +2081,26 @@ static int uart_stm32_pm_action(const struct device *dev,

switch (action) {
case PM_DEVICE_ACTION_RESUME:
/* Set pins to active state */
err = pinctrl_apply_state(config->pcfg, PINCTRL_STATE_DEFAULT);
if (err < 0) {
return err;
}
/* When exiting low power mode, check whether UART is enabled.
* If not, it means we are exiting Suspend to RAM mode (STM32
* Standby), and the driver need to be reinitialized
*/
if (LL_USART_IsEnabled(config->usart)) {
/* Set pins to active state */
err = pinctrl_apply_state(config->pcfg, PINCTRL_STATE_DEFAULT);
if (err < 0) {
return err;
}

/* enable clock */
err = clock_control_on(data->clock, (clock_control_subsys_t)&config->pclken[0]);
if (err != 0) {
LOG_ERR("Could not enable (LP)UART clock");
return err;
/* enable clock */
err = clock_control_on(data->clock,
(clock_control_subsys_t)&config->pclken[0]);
if (err != 0) {
LOG_ERR("Could not enable (LP)UART clock");
return err;
}
} else {
uart_stm32_init(dev);
gautierg-st marked this conversation as resolved.
Show resolved Hide resolved
}
break;
case PM_DEVICE_ACTION_SUSPEND:
Expand Down
20 changes: 20 additions & 0 deletions drivers/timer/Kconfig.stm32_lptim
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
gautierg-st marked this conversation as resolved.
Show resolved Hide resolved
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
115 changes: 113 additions & 2 deletions drivers/timer/stm32_lptim_timer.c
Original file line number Diff line number Diff line change
Expand Up @@ -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>

Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -448,8 +511,6 @@ static int sys_clock_driver_init(void)
stm32_lptim_wait_ready();
LL_LPTIM_ClearFlag_ARROK(LPTIM);

accumulated_lptim_cnt = 0;
gautierg-st marked this conversation as resolved.
Show resolved Hide resolved

#if !defined(CONFIG_SOC_SERIES_STM32U5X) && \
!defined(CONFIG_SOC_SERIES_STM32WBAX)
/* Enable the LPTIM counter */
Expand Down Expand Up @@ -482,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);