Skip to content

Commit 85f6d6b

Browse files
committed
soc: st: stm32: Provide PM support for STM32WB0x
Provide PM support, specifically suspend-to-ram, for STM32WB0x. Enable STM32_RADIO_TIMER Kconfig parameter when PM is set. Signed-off-by: Ali Hozhabri <[email protected]>
1 parent 6d281a8 commit 85f6d6b

File tree

7 files changed

+349
-1
lines changed

7 files changed

+349
-1
lines changed

drivers/timer/Kconfig.stm32_radio

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
config STM32_RADIO_TIMER
77
bool "STM32 Radio Timer for WB0x"
8-
default y if BT
8+
default y if PM || BT
99
depends on SOC_SERIES_STM32WB0X
1010
select USE_STM32_HAL_RADIO_TIMER
1111
select TICKLESS_CAPABLE

drivers/timer/stm32_radio_timer.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ LOG_MODULE_REGISTER(radio_timer_driver);
3737
BUILD_ASSERT(DT_NODE_HAS_STATUS(DT_NODELABEL(clk_lsi), disabled),
3838
"At the moment, LSI is not supported");
3939

40+
#if (defined(CONFIG_SOC_STM32WB06) || defined(CONFIG_SOC_STM32WB07)) && defined(CONFIG_PM)
41+
#error "At the moment, PM is not supported for WB06 & WB07"
42+
#endif /* (CONFIG_SOC_STM32WB06 || CONFIG_SOC_STM32WB07) && CONFIG_PM */
43+
4044
/* Translate STU to MTU and vice versa. It is implemented by using integer operations. */
4145
uint32_t blue_unit_conversion(uint32_t time, uint32_t period_freq, uint32_t thr);
4246
static uint64_t announced_cycles;

dts/arm/st/wb0/stm32wb0.dtsi

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,27 @@
3333
cpu0: cpu@0 {
3434
device_type = "cpu";
3535
compatible = "arm,cortex-m0+";
36+
cpu-power-states = <&deepstop>;
3637
reg = <0>;
3738
};
39+
40+
power-states {
41+
deepstop: state0 {
42+
compatible = "zephyr,power-state";
43+
power-state-name = "suspend-to-ram";
44+
substate-id = <0>;
45+
46+
/* One milisecond seems reasonable for now. */
47+
min-residency-us = <1000>;
48+
49+
/**
50+
* tWUDEEPSTOP value from datasheets:
51+
* STM32WB06/WB07: typ. 110µs
52+
* STM32WB05/WB09: typ. 170µs
53+
*/
54+
exit-latency-us = <550>;
55+
};
56+
};
3857
};
3958

4059
sram0: memory@20000000 {

soc/st/stm32/stm32wb0x/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
zephyr_include_directories(${ZEPHYR_BASE}/drivers)
44

55
zephyr_sources(soc.c)
6+
zephyr_sources_ifdef(CONFIG_PM power.c s2ram_marking.S)
67
zephyr_include_directories(.)
78

89
zephyr_linker_sources(RAM_SECTIONS ram_sections.ld)

soc/st/stm32/stm32wb0x/Kconfig

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,20 @@ config SOC_SERIES_STM32WB0X
99
select CPU_CORTEX_M_HAS_VTOR
1010
select CPU_CORTEX_M_HAS_SYSTICK
1111
select CPU_HAS_ARM_MPU
12+
select HAS_PM
1213
select HAS_STM32CUBE
1314
select SOC_EARLY_INIT_HOOK
1415
# WB0x has a ROM bootloader executed at reset,
1516
# which makes the following option required
1617
select INIT_ARCH_HW_AT_BOOT
18+
19+
# STM32WB0 series only supports suspend-to-RAM as low-power mode.
20+
# Make sure that S2RAM support is enabled if Power Management is
21+
# selected by the user. PM_DEVICE is also required because devices
22+
# need to be reinitialized after resuming. CUSTOM_MARKING is enabled
23+
# to use SoC-specific hardware registers for resume detection, and
24+
# CORTEX_M_SYSTICK_RESET_BY_LOW_POWER is required because the SoC
25+
# is reset upon wake-up from Deepstop.
26+
imply PM_DEVICE if PM
27+
imply PM_S2RAM
28+
imply PM_S2RAM_CUSTOM_MARKING

soc/st/stm32/stm32wb0x/power.c

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
/*
2+
* Copyright (c) 2025 STMicroelectronics.
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
/**
7+
* STM32WB0 Deepstop implementation for Power Management framework
8+
*
9+
* TODO:
10+
* - document the control flow on PM transitions
11+
* - assertions around system configuration
12+
* (e.g., valid slow clock selected, RTC enabled, ...)
13+
* - ...
14+
*/
15+
#include <zephyr/kernel.h>
16+
#include <zephyr/pm/pm.h>
17+
#include <zephyr/sys_clock.h>
18+
#include <zephyr/init.h>
19+
#include <zephyr/arch/common/pm_s2ram.h>
20+
21+
/* Private headers in zephyr/drivers/... */
22+
#include <clock_control/clock_stm32_ll_common.h>
23+
24+
#include <soc.h>
25+
#include <stm32_ll_pwr.h>
26+
#include <stm32_ll_rcc.h>
27+
#include <stm32_ll_cortex.h>
28+
#include <stm32_ll_system.h>
29+
30+
#include <zephyr/logging/log.h>
31+
LOG_MODULE_DECLARE(soc, CONFIG_SOC_LOG_LEVEL);
32+
33+
#if defined(CONFIG_SOC_STM32WB05XX) || defined(CONFIG_SOC_STM32WB09XX)
34+
#define HAS_GPIO_RETENTION 1
35+
#else
36+
#define HAS_GPIO_RETENTION 0
37+
#endif /* CONFIG_SOC_STM32WB05XX || CONFIG_SOC_STM32WB09XX */
38+
39+
/**
40+
* System-level state managed by PM callbacks
41+
*
42+
* Things that need to be preserved across Deepstop, but
43+
* have no associated driver to backup and restore them.
44+
*/
45+
#define SRAM DT_CHOSEN(zephyr_sram)
46+
#define BL_STK_SIZ (20 * 4) /* in bytes */
47+
#define BL_STK_TOP ((void *)(DT_REG_ADDR(SRAM) + DT_REG_SIZE(SRAM) - BL_STK_SIZ))
48+
static uint8_t bl_stk_area_backup[BL_STK_SIZ];
49+
50+
uint32_t RCC_APB1ENR_vr, RCC_AHBENR_vr;
51+
52+
static void save_system_level_state(void)
53+
{
54+
/**
55+
* The STM32WB0 bootloader uses the end of SRAM as stack.
56+
* Since it is executed on every reset, including wakeup
57+
* from Deepstop, any data placed at the end of SRAM would
58+
* be corrupted.
59+
*
60+
* Backup these words for later restoration to avoid data
61+
* corruption. A much better solution would mark this part
62+
* of SRAM as unusable, but no easy solution was found to
63+
* achieve this.
64+
*/
65+
memcpy(bl_stk_area_backup, BL_STK_TOP, BL_STK_SIZ);
66+
}
67+
68+
static void restore_system_level_state(void)
69+
{
70+
/* Restore bootloader stack area */
71+
memcpy(BL_STK_TOP, bl_stk_area_backup, BL_STK_SIZ);
72+
}
73+
74+
/* Callback for arch_pm_s2ram_suspend */
75+
static int suspend_system_to_deepstop(void)
76+
{
77+
/* Enable SLEEPDEEP to allow entry in Deepstop */
78+
LL_LPM_EnableDeepSleep();
79+
80+
/* Complete all memory transactions */
81+
__DSB();
82+
83+
84+
/* Attempt entry in Deepstop */
85+
__WFI();
86+
87+
/**
88+
* Make sure no meaningful instruction is
89+
* executed during the two cycles latency
90+
* it takes to power-gate the CPU.
91+
*/
92+
__NOP();
93+
__NOP();
94+
95+
/**
96+
* This code is reached only if the device did not
97+
* enter Deepstop mode (e.g., because an interrupt
98+
* became pending during preparatory work).
99+
*
100+
* Disable SLEEPDEEP and return the appropriate error.
101+
*/
102+
LL_LPM_EnableSleep();
103+
104+
return -EBUSY;
105+
}
106+
107+
/**
108+
* Backup system state to save and configure power
109+
* controller before entry in Deepstop mode
110+
*/
111+
static void prepare_for_deepstop_entry(void)
112+
{
113+
/**
114+
* DEEPSTOP2 configuration is performed in familiy-wide code
115+
* instead of here (see `soc/st/stm32/common/soc_config.c`).
116+
*
117+
* RAMRET configuration is performed once during SoC init,
118+
* since it is retained across Deepstop (see `soc.c`).
119+
**/
120+
121+
/* Save the clock configuration. */
122+
RCC_APB1ENR_vr = RCC->APB1ENR;
123+
RCC_AHBENR_vr = RCC->AHBENR;
124+
125+
/* Clear wakeup reason flags (which inhibit Deepstop) */
126+
LL_PWR_ClearWakeupSource(LL_PWR_WAKEUP_ALL);
127+
LL_SYSCFG_PWRC_ClearIT(LL_SYSCFG_PWRC_WKUP);
128+
LL_PWR_ClearDeepstopSeqFlag();
129+
LL_PWR_EnableWU_EWBLEHCPU();
130+
131+
#if HAS_GPIO_RETENTION
132+
/**
133+
* Enable GPIO state retention in Deepstop if available.
134+
*
135+
* Do not enable this if low-power mode debugging has been
136+
* enabled via Kconfig, because it prevents the debugger
137+
* from staying connected to the SoC.
138+
*/
139+
if (!IS_ENABLED(CONFIG_STM32_ENABLE_DEBUG_SLEEP_STOP)) {
140+
LL_PWR_EnableGPIORET();
141+
LL_PWR_EnableDBGRET();
142+
}
143+
#endif /* HAS_GPIO_RETENTION */
144+
save_system_level_state();
145+
}
146+
147+
/**
148+
* @brief Restore SoC-level configuration lost in Deepstop
149+
* @note This function must be called right after wakeup.
150+
*/
151+
static void post_resume_configuration(void)
152+
{
153+
__ASSERT_NO_MSG(LL_PWR_GetDeepstopSeqFlag() == 1);
154+
155+
/**
156+
* VTOR has been reset to its default value: restore it.
157+
* (Note that RAM_VR.AppBase was filled during SoC init)
158+
*/
159+
SCB->VTOR = RAM_VR.AppBase;
160+
161+
/* Restore the clock configuration. */
162+
RCC->AHBENR = RCC_AHBENR_vr;
163+
RCC->APB1ENR = RCC_APB1ENR_vr;
164+
165+
/**
166+
* Restore other miscellanous system-level things.
167+
*/
168+
restore_system_level_state();
169+
}
170+
171+
/**
172+
* Power Management subsystem callbacks
173+
*/
174+
void pm_state_set(enum pm_state state, uint8_t substate_id)
175+
{
176+
/* Ignore substate: STM32WB0 has only one low-power mode */
177+
ARG_UNUSED(substate_id);
178+
179+
int res;
180+
181+
if (state != PM_STATE_SUSPEND_TO_RAM) {
182+
/**
183+
* Deepstop is a suspend-to-RAM state.
184+
* Something is wrong if a different
185+
* power state has been requested.
186+
*/
187+
LOG_ERR("Unsupported power state %u", state);
188+
}
189+
prepare_for_deepstop_entry();
190+
191+
/* Select Deepstop low-power mode and suspend system */
192+
LL_PWR_SetPowerMode(LL_PWR_MODE_DEEPSTOP);
193+
194+
res = arch_pm_s2ram_suspend(suspend_system_to_deepstop);
195+
196+
if (res >= 0) {
197+
/**
198+
* Restore system configuration only if the SoC actually
199+
* entered Deepstop - otherwise, no state has been lost
200+
* and it would be a waste of time to do so.
201+
*/
202+
post_resume_configuration();
203+
}
204+
}
205+
206+
void pm_state_exit_post_ops(enum pm_state state, uint8_t substate_id)
207+
{
208+
ARG_UNUSED(state);
209+
ARG_UNUSED(substate_id);
210+
211+
/**
212+
* We restore system state in @ref{post_resume_configuration}.
213+
* The only thing we may have to do is release GPIO retention,
214+
* which we have not done yet because we wanted the driver to
215+
* restore all configuration first.
216+
*
217+
* We also need to enable IRQs to fullfill the API contract.
218+
*/
219+
#if HAS_GPIO_RETENTION
220+
LL_PWR_DisableGPIORET();
221+
LL_PWR_DisableDBGRET();
222+
#endif /* HAS_GPIO_RETENTION */
223+
224+
#if defined(CONFIG_SOC_STM32WB06) || defined(CONFIG_SOC_STM32WB07)
225+
irq_enable(RADIO_TIMER_TXRX_WKUP_IRQn);
226+
#endif /* CONFIG_SOC_STM32WB06 || CONFIG_SOC_STM32WB07 */
227+
irq_enable(RADIO_TIMER_CPU_WKUP_IRQn);
228+
irq_enable(RADIO_TIMER_ERROR_IRQn);
229+
__enable_irq();
230+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
* Copyright (c) 2025 STMicroelectronics
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
/**
8+
* STM32WB0-specific support code for suspend-to-RAM
9+
*/
10+
11+
#include <zephyr/toolchain.h>
12+
#include <offsets_short.h>
13+
#include <zephyr/arch/cpu.h>
14+
#include <zephyr/arch/common/pm_s2ram.h>
15+
16+
/* Read RCC and PWRC base from Device Tree */
17+
#include <zephyr/devicetree.h>
18+
19+
#define RCC_CSR (DT_REG_ADDR(DT_NODELABEL(rcc)) + 0x94)
20+
#define PWR_BASE DT_REG_ADDR(DT_NODELABEL(pwrc))
21+
#define PWR_SR1 0x10
22+
#define PWR_SR3 0x38
23+
24+
_ASM_FILE_PROLOGUE
25+
26+
GTEXT(pm_s2ram_mark_set)
27+
SECTION_FUNC(TEXT, pm_s2ram_mark_set)
28+
/*
29+
* Managed by hardware - nothing to do here.
30+
*/
31+
bx lr
32+
33+
/**
34+
* @brief Check whether SoC is waking up from Deepstop
35+
* @returns 1 if SoC is waking up from Deepstop, 0 otherwise.
36+
* @note Registers are cleared by hardware upon reset, or
37+
* the SoC PM code layer upon entry in Deepstop, so
38+
* this function does not clear the registers.
39+
*/
40+
GTEXT(pm_s2ram_mark_check_and_clear)
41+
SECTION_FUNC(TEXT, pm_s2ram_mark_check_and_clear)
42+
/*
43+
* Check for Deepstop exit on wakeup event:
44+
* - RCC_CSR is zero
45+
* - PWRC.EXTSRR has bit DEEPSTOPF set
46+
* (Redundant => unchecked)
47+
* - Either PWRC_SR1 or PWRC_SR3 is non-zero
48+
*
49+
* Note that we don't have to clear any register since
50+
* they are automatically updated on reset/wake-up.
51+
*
52+
* IMPLEMENTATIONS DETAILS:
53+
* r1 must not be modified and the stack must not be
54+
* used by this function as of writing, due to the
55+
* current implementation of arch_pm_s2ram_resume.
56+
* As such, we can only use r0, r2 and r3 here.
57+
*
58+
* N.B.: r12 is also volatile for the ARM ABI, but it
59+
* cannot be used for most operations on ARMv6-M due
60+
* to 16-bit Thumb limitations, so we might as well
61+
* avoid using it entirely.
62+
*/
63+
ldr r0, =RCC_CSR
64+
ldr r2, [r0]
65+
cmp r2, #0
66+
bne not_deepstop_wakeup
67+
68+
ldr r0, =PWR_BASE
69+
ldr r2, [r0, #PWR_SR1]
70+
ldr r3, [r0, #PWR_SR3]
71+
orrs r2, r2, r3
72+
beq not_deepstop_wakeup
73+
74+
/**
75+
* All conditions met: this is a wakeup from Deepstop.
76+
*/
77+
movs r0, #1
78+
bx lr
79+
80+
not_deepstop_wakeup:
81+
movs r0, #0
82+
bx lr

0 commit comments

Comments
 (0)