blob: 73e81d291f84e1594f246bbbeb70d7e03b23c5ef [file] [log] [blame]
/*
* Copyright (c) 2022 STMicroelectronics
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <zephyr/cache.h>
#include <zephyr/pm/pm.h>
#include <soc.h>
#include <zephyr/init.h>
#include <zephyr/arch/common/pm_s2ram.h>
#include <zephyr/drivers/timer/system_timer.h>
#include <zephyr/arch/arm/cortex_m/fpu.h>
#include <zephyr/arch/arm/cortex_m/scb.h>
#include <zephyr/arch/arm/mpu/arm_mpu.h>
#include <kernel_arch_func.h>
#include <cmsis_core.h>
#include <stm32wbaxx_ll_bus.h>
#include <stm32wbaxx_ll_cortex.h>
#include <stm32wbaxx_ll_pwr.h>
#include <stm32wbaxx_ll_rcc.h>
#include <stm32wbaxx_ll_system.h>
#include <clock_control/clock_stm32_ll_common.h>
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(soc, CONFIG_SOC_LOG_LEVEL);
#define HSE_ON (READ_BIT(RCC->CR, RCC_CR_HSEON) == RCC_CR_HSEON)
static uint32_t ram_waitstates_backup;
static uint32_t flash_latency_backup;
#if defined(CONFIG_PM_S2RAM)
static struct fpu_ctx_full fpu_state;
static struct scb_context scb_state;
static struct z_mpu_context_retained mpu_state;
#endif
static int enter_low_power_mode(void)
{
uint32_t basepri;
LL_LPM_EnableDeepSleep();
while (LL_PWR_IsActiveFlag_ACTVOS() == 0) {
}
/*
* Prevent spurious entry in low-power state if any
* interrupt is currently pending. The WFI instruction
* will not enter low-power state if an interrupt with
* higher priority than BASEPRI is pending, regardless
* of the value of PRIMASK.
*
* When this function executes, Zephyr's arch_irq_lock() has
* configured BASEPRI to mask interrupts. Set PRIMASK to keep
* interrupts masked then lower BASEPRI to 0, after saving its
* current value, so that any pending interrupt is seen by WFI
* and inhibits low-power state entry.
*/
__disable_irq();
basepri = __get_BASEPRI();
__set_BASEPRI(0);
__ISB();
__DSB();
/* Attempt entry in low-power state */
__WFI();
/*
* If entry in STOP mode was attempted, execution
* resumes here regardless of success or failure.
* If entry in STANDBY mode was attempted, execution
* will reach this point only if entry in low-power
* state did not occur. Restore BASEPRI and clear
* PRIMASK then return an error to indicate that
* entry in STANDBY mode failed (return value of
* this function is ignored for STOP mode entry).
*/
__set_BASEPRI(basepri);
__enable_irq();
/* Disable SLEEPDEEP at Cortex-M level */
LL_LPM_EnableSleep();
return -EBUSY;
}
#if defined(CONFIG_PM_S2RAM)
static void set_mode_suspend_to_ram_enter(void)
{
/* Enable RTC wakeup
* This configures an internal pin that generates an event to wakeup the system
*/
LL_PWR_EnableWakeUpPin(LL_PWR_WAKEUP_PIN7);
LL_PWR_SetWakeUpPinSignal3Selection(LL_PWR_WAKEUP_PIN7);
/* Clear flags */
LL_PWR_ClearFlag_SB();
LL_PWR_ClearFlag_WU();
LL_RCC_ClearResetFlags();
sys_cache_instr_disable();
/* Select standby mode */
LL_PWR_SetPowerMode(LL_PWR_MODE_STANDBY);
/* Save FPU, SCB and MPU states */
z_arm_save_fp_context(&fpu_state);
z_arm_save_scb_context(&scb_state);
#if defined(CONFIG_ARM_MPU)
z_arm_save_mpu_context(&mpu_state);
#endif /* CONFIG_ARM_MPU */
/* Save context and enter Standby mode */
arch_pm_s2ram_suspend(enter_low_power_mode);
}
static void set_mode_suspend_to_ram_exit(void)
{
/* Save MPU, SCB and FPU states */
#if defined(CONFIG_ARM_MPU)
z_arm_restore_mpu_context(&mpu_state);
#endif /* CONFIG_ARM_MPU */
z_arm_restore_scb_context(&scb_state);
z_arm_restore_fp_context(&fpu_state);
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_RCC_RAMCFG_CLK_ENABLE();
/* Restore system clock as soon as we exit standby mode */
stm32_clock_control_standby_exit();
if (LL_PWR_IsActiveFlag_SB() || !HSE_ON) {
stm32_clock_control_init(NULL);
}
/* Resume configuration */
stm32wba_init();
stm32_power_init();
}
#endif
static void set_mode_stop_enter(uint8_t substate_id)
{
LL_PWR_ClearFlag_STOP();
LL_RCC_ClearResetFlags();
/* Erratum 2.2.15:
* Disabling ICACHE is required before entering stop mode
*/
sys_cache_instr_disable();
/* If stop mode is greater than 1, manage flash latency and RAMCFG */
if (substate_id > 1) {
flash_latency_backup = __HAL_FLASH_GET_LATENCY();
if (flash_latency_backup < FLASH_LATENCY_1) {
/* Set flash latency to 1, since system will restart from HSI16 */
__HAL_FLASH_SET_LATENCY(FLASH_LATENCY_1);
while (__HAL_FLASH_GET_LATENCY() != FLASH_LATENCY_1) {
}
}
ram_waitstates_backup = READ_BIT(RAMCFG_SRAM1->CR, RAMCFG_CR_WSC);
MODIFY_REG(RAMCFG_SRAM1->CR, RAMCFG_CR_WSC, RAMCFG_WAITSTATE_1);
MODIFY_REG(RAMCFG_SRAM2->CR, RAMCFG_CR_WSC, RAMCFG_WAITSTATE_1);
}
switch (substate_id) {
case 1:
LL_PWR_SetPowerMode(LL_PWR_MODE_STOP0);
break;
case 2:
LL_PWR_SetPowerMode(LL_PWR_MODE_STOP1);
break;
default:
LOG_DBG("Unsupported power state substate-id %u", substate_id);
return;
}
enter_low_power_mode();
}
static void set_mode_stop_exit(uint8_t substate_id)
{
if (LL_PWR_IsActiveFlag_STOP() || !HSE_ON) {
/* Reconfigure the clock (incl. RAM/FLASH latency) */
stm32_clock_control_init(NULL);
} else {
/* Stop mode skipped */
if (substate_id > 1) {
__HAL_FLASH_SET_LATENCY(flash_latency_backup);
while (__HAL_FLASH_GET_LATENCY() != flash_latency_backup) {
}
MODIFY_REG(RAMCFG_SRAM1->CR, RAMCFG_CR_WSC, ram_waitstates_backup);
MODIFY_REG(RAMCFG_SRAM2->CR, RAMCFG_CR_WSC, ram_waitstates_backup);
}
}
/* Erratum 2.2.15:
* Enable ICACHE when exiting stop mode
*/
sys_cache_instr_enable();
}
/* Invoke Low Power/System Off specific Tasks */
void pm_state_set(enum pm_state state, uint8_t substate_id)
{
switch (state) {
case PM_STATE_SUSPEND_TO_IDLE:
set_mode_stop_enter(substate_id);
/* Resume here */
set_mode_stop_exit(substate_id);
break;
#if defined(CONFIG_PM_S2RAM)
case PM_STATE_SUSPEND_TO_RAM:
set_mode_suspend_to_ram_enter();
/* Resume here */
set_mode_suspend_to_ram_exit();
break;
#endif
default:
LOG_DBG("Unsupported power state %u", state);
return;
}
}
/* Handle SOC specific activity after Low Power Mode Exit */
void pm_state_exit_post_ops(enum pm_state state, uint8_t substate_id)
{
/*
* System is now in active mode.
* Reenable interrupts which were disabled
* when OS started idling code.
*/
irq_unlock(0);
}
/* Initialize STM32 Power */
void stm32_power_init(void)
{
LL_AHB4_GRP1_EnableClock(LL_AHB4_GRP1_PERIPH_PWR);
#ifdef CONFIG_DEBUG
LL_DBGMCU_EnableDBGStandbyMode();
LL_DBGMCU_APB7_GRP1_FreezePeriph(LL_DBGMCU_APB7_GRP1_RTC_STOP);
LL_DBGMCU_APB7_GRP1_FreezePeriph(LL_DBGMCU_APB7_GRP1_LPTIM1_STOP);
#else
LL_DBGMCU_DisableDBGStandbyMode();
#endif
/* Enable SRAM full retention */
LL_PWR_SetSRAM1SBRetention(LL_PWR_SRAM1_SB_FULL_RETENTION);
LL_PWR_SetSRAM2SBRetention(LL_PWR_SRAM2_SB_FULL_RETENTION);
/* Enable Radio RAM full retention */
LL_PWR_SetRadioSBRetention(LL_PWR_RADIO_SB_FULL_RETENTION);
/* Enabling Ultra Low power mode */
LL_PWR_EnableUltraLowPowerMode();
LL_FLASH_EnableSleepPowerDown();
}