From ee9d23945f18868196db0353e2879a955af2667d Mon Sep 17 00:00:00 2001 From: Adam Kondraciuk Date: Mon, 6 May 2024 13:59:32 +0200 Subject: [PATCH] soc: nordic: nrf54h: poweroff: Add support for s2ram Add functions for local domain suspend to RAM. Add matching resume procedure. Add pm_s2ram function for determining source of reset. Add preserving NVIC and MPU state in retained RAM when CPU is powered off during S2RAM procedure. Signed-off-by: Adam Kondraciuk --- soc/nordic/common/poweroff.c | 4 + soc/nordic/nrf54h/CMakeLists.txt | 5 + soc/nordic/nrf54h/Kconfig | 4 + soc/nordic/nrf54h/pm_s2ram.c | 151 +++++++++++++++++++++++++++++++ soc/nordic/nrf54h/pm_s2ram.h | 31 +++++++ soc/nordic/nrf54h/power.c | 145 +++++++++++++++++++++++++++++ soc/nordic/nrf54h/power.h | 20 ++++ soc/nordic/nrf54h/soc.c | 29 +++++- soc/nordic/nrf54h/soc.h | 27 ++++++ 9 files changed, 414 insertions(+), 2 deletions(-) create mode 100644 soc/nordic/nrf54h/pm_s2ram.c create mode 100644 soc/nordic/nrf54h/pm_s2ram.h create mode 100644 soc/nordic/nrf54h/power.c create mode 100644 soc/nordic/nrf54h/power.h diff --git a/soc/nordic/common/poweroff.c b/soc/nordic/common/poweroff.c index 1c43da3e9ea..2a6387c1e67 100644 --- a/soc/nordic/common/poweroff.c +++ b/soc/nordic/common/poweroff.c @@ -8,6 +8,8 @@ #if defined(CONFIG_SOC_SERIES_NRF51X) || defined(CONFIG_SOC_SERIES_NRF52X) #include +#elif defined(CONFIG_SOC_SERIES_NRF54HX) +#include #else #include #endif @@ -16,6 +18,8 @@ void z_sys_poweroff(void) { #if defined(CONFIG_SOC_SERIES_NRF51X) || defined(CONFIG_SOC_SERIES_NRF52X) nrf_power_system_off(NRF_POWER); +#elif defined(CONFIG_SOC_SERIES_NRF54HX) + nrf_poweroff(); #else nrf_regulators_system_off(NRF_REGULATORS); #endif diff --git a/soc/nordic/nrf54h/CMakeLists.txt b/soc/nordic/nrf54h/CMakeLists.txt index 1aa4723814f..0496841ffe7 100644 --- a/soc/nordic/nrf54h/CMakeLists.txt +++ b/soc/nordic/nrf54h/CMakeLists.txt @@ -3,8 +3,13 @@ if(CONFIG_ARM) zephyr_library_sources(soc.c) + if(CONFIG_PM OR CONFIG_POWEROFF) + zephyr_library_sources(power.c) + endif() endif() +zephyr_library_sources_ifdef(CONFIG_PM_S2RAM pm_s2ram.c) + zephyr_include_directories(.) # Ensure that image size aligns with 16 bytes so that MRAMC finalizes all writes diff --git a/soc/nordic/nrf54h/Kconfig b/soc/nordic/nrf54h/Kconfig index ac2e91346ce..1cad8917162 100644 --- a/soc/nordic/nrf54h/Kconfig +++ b/soc/nordic/nrf54h/Kconfig @@ -26,6 +26,8 @@ config SOC_NRF54H20_CPUAPP select NRFS_HAS_MRAM_SERVICE select NRFS_HAS_TEMP_SERVICE select NRFS_HAS_VBUS_DETECTOR_SERVICE + select HAS_PM + select HAS_POWEROFF config SOC_NRF54H20_CPURAD select ARM @@ -42,6 +44,8 @@ config SOC_NRF54H20_CPURAD select NRFS_HAS_MRAM_SERVICE select NRFS_HAS_TEMP_SERVICE select HAS_NORDIC_DMM + select HAS_PM + select HAS_POWEROFF config SOC_NRF54H20_CPUPPR depends on RISCV_CORE_NORDIC_VPR diff --git a/soc/nordic/nrf54h/pm_s2ram.c b/soc/nordic/nrf54h/pm_s2ram.c new file mode 100644 index 00000000000..10cdd36a762 --- /dev/null +++ b/soc/nordic/nrf54h/pm_s2ram.c @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include "pm_s2ram.h" + +#include + +#define NVIC_MEMBER_SIZE(member) ARRAY_SIZE(((NVIC_Type *)0)->member) + +/* Currently dynamic regions are only used in case of userspace or stack guard and + * stack guard is not used by default on Cortex-M33 because there is a dedicated + * mechanism for stack overflow detection. Unless those condition change we don't + * need to store MPU content, it can just be reinitialized on resuming. + */ +#define MPU_USE_DYNAMIC_REGIONS IS_ENABLED(CONFIG_USERSPACE) || IS_ENABLED(CONFIG_MPU_STACK_GUARD) + +/* TODO: The num-mpu-regions property should be used. Needs to be added to dts bindings. */ +#define MPU_MAX_NUM_REGIONS 16 + +typedef struct { + /* NVIC components stored into RAM. */ + uint32_t ISER[NVIC_MEMBER_SIZE(ISER)]; + uint32_t ISPR[NVIC_MEMBER_SIZE(ISPR)]; + uint8_t IPR[NVIC_MEMBER_SIZE(IPR)]; +} _nvic_context_t; + +typedef struct { + uint32_t RNR; + uint32_t RBAR[MPU_MAX_NUM_REGIONS]; + uint32_t RLAR[MPU_MAX_NUM_REGIONS]; + uint32_t MAIR0; + uint32_t MAIR1; + uint32_t CTRL; +} _mpu_context_t; + +struct backup { + _nvic_context_t nvic_context; + _mpu_context_t mpu_context; +}; + +static __noinit struct backup backup_data; + +extern void z_arm_configure_static_mpu_regions(void); +extern int z_arm_mpu_init(void); + +/* MPU registers cannot be simply copied because content of RBARx RLARx registers + * depends on region which is selected by RNR register. + */ +static void mpu_suspend(_mpu_context_t *backup) +{ + if (!MPU_USE_DYNAMIC_REGIONS) { + return; + } + + backup->RNR = MPU->RNR; + + for (uint8_t i = 0; i < MPU_MAX_NUM_REGIONS; i++) { + MPU->RNR = i; + backup->RBAR[i] = MPU->RBAR; + backup->RLAR[i] = MPU->RLAR; + } + backup->MAIR0 = MPU->MAIR0; + backup->MAIR1 = MPU->MAIR1; + backup->CTRL = MPU->CTRL; +} + +static void mpu_resume(_mpu_context_t *backup) +{ + if (!MPU_USE_DYNAMIC_REGIONS) { + z_arm_mpu_init(); + z_arm_configure_static_mpu_regions(); + return; + } + + uint32_t rnr = backup->RNR; + + for (uint8_t i = 0; i < MPU_MAX_NUM_REGIONS; i++) { + MPU->RNR = i; + MPU->RBAR = backup->RBAR[i]; + MPU->RLAR = backup->RLAR[i]; + } + + MPU->MAIR0 = backup->MAIR0; + MPU->MAIR1 = backup->MAIR1; + MPU->RNR = rnr; + MPU->CTRL = backup->CTRL; +} + +static void nvic_suspend(_nvic_context_t *backup) +{ + memcpy(backup->ISER, (uint32_t *)NVIC->ISER, sizeof(NVIC->ISER)); + memcpy(backup->ISPR, (uint32_t *)NVIC->ISPR, sizeof(NVIC->ISPR)); + memcpy(backup->IPR, (uint32_t *)NVIC->IPR, sizeof(NVIC->IPR)); +} + +static void nvic_resume(_nvic_context_t *backup) +{ + memcpy((uint32_t *)NVIC->ISER, backup->ISER, sizeof(NVIC->ISER)); + memcpy((uint32_t *)NVIC->ISPR, backup->ISPR, sizeof(NVIC->ISPR)); + memcpy((uint32_t *)NVIC->IPR, backup->IPR, sizeof(NVIC->IPR)); +} + +int soc_s2ram_suspend(pm_s2ram_system_off_fn_t system_off) +{ + int ret; + + __disable_irq(); + nvic_suspend(&backup_data.nvic_context); + mpu_suspend(&backup_data.mpu_context); + ret = arch_pm_s2ram_suspend(system_off); + if (ret < 0) { + __enable_irq(); + return ret; + } + + mpu_resume(&backup_data.mpu_context); + nvic_resume(&backup_data.nvic_context); + __enable_irq(); + + return ret; +} + +void pm_s2ram_mark_set(void) +{ + /* empty */ +} + +bool pm_s2ram_mark_check_and_clear(void) +{ + bool unretained_wake; + bool restore_valid; + uint32_t reset_reason = nrf_resetinfo_resetreas_local_get(NRF_RESETINFO); + + if (reset_reason != NRF_RESETINFO_RESETREAS_LOCAL_UNRETAINED_MASK) { + return false; + } + unretained_wake = reset_reason & NRF_RESETINFO_RESETREAS_LOCAL_UNRETAINED_MASK; + nrf_resetinfo_resetreas_local_set(NRF_RESETINFO, 0); + + restore_valid = nrf_resetinfo_restore_valid_check(NRF_RESETINFO); + nrf_resetinfo_restore_valid_set(NRF_RESETINFO, false); + + return (unretained_wake & restore_valid) ? true : false; +} diff --git a/soc/nordic/nrf54h/pm_s2ram.h b/soc/nordic/nrf54h/pm_s2ram.h new file mode 100644 index 00000000000..565afad6ca1 --- /dev/null +++ b/soc/nordic/nrf54h/pm_s2ram.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file Common pm_s2ram.h include for Nordic SoCs. + */ + +#ifndef _ZEPHYR_SOC_ARM_NORDIC_NRF_PM_S2RAM_H_ +#define _ZEPHYR_SOC_ARM_NORDIC_NRF_PM_S2RAM_H_ + +/** + * @brief Save CPU state on suspend + * + * This function is used on suspend-to-RAM (S2RAM) to save the CPU state in + * (retained) RAM before powering the system off using the provided function. + * This function is usually called from the PM subsystem / hooks. + * + * The CPU state consist of internal registers and peripherals like + * interrupt controller, memory controllers, etc. + * + * @param system_off Function to power off the system. + * + * @retval 0 The CPU context was successfully saved and restored. + * @retval -EBUSY The system is busy and cannot be suspended at this time. + * @retval -errno Negative errno code in case of failure. + */ +int soc_s2ram_suspend(pm_s2ram_system_off_fn_t system_off); + +#endif /* _ZEPHYR_SOC_ARM_NORDIC_NRF_PM_S2RAM_H_ */ diff --git a/soc/nordic/nrf54h/power.c b/soc/nordic/nrf54h/power.c new file mode 100644 index 00000000000..ad7ae18d9bb --- /dev/null +++ b/soc/nordic/nrf54h/power.c @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "pm_s2ram.h" + +static void suspend_common(void) +{ + + /* Flush, disable and power down DCACHE */ + sys_cache_data_flush_all(); + sys_cache_data_disable(); + nrf_memconf_ramblock_control_enable_set(NRF_MEMCONF, RAMBLOCK_POWER_ID, + RAMBLOCK_CONTROL_BIT_DCACHE, false); + + if (IS_ENABLED(CONFIG_ICACHE)) { + /* Disable and power down ICACHE */ + sys_cache_instr_disable(); + nrf_memconf_ramblock_control_enable_set(NRF_MEMCONF, RAMBLOCK_POWER_ID, + RAMBLOCK_CONTROL_BIT_ICACHE, false); + } + + /* Disable retention */ + nrf_lrcconf_retain_set(NRF_LRCCONF010, NRF_LRCCONF_POWER_DOMAIN_0, false); + nrf_lrcconf_poweron_force_set(NRF_LRCCONF010, NRF_LRCCONF_POWER_DOMAIN_0, false); +} + +void nrf_poweroff(void) +{ + nrf_resetinfo_resetreas_local_set(NRF_RESETINFO, 0); + nrf_resetinfo_restore_valid_set(NRF_RESETINFO, false); + + nrf_lrcconf_retain_set(NRF_LRCCONF010, NRF_LRCCONF_POWER_MAIN, false); + + /* TODO: Move it around k_cpu_idle() implementation. */ + nrf_lrcconf_poweron_force_set(NRF_LRCCONF010, NRF_LRCCONF_POWER_MAIN, false); + nrf_lrcconf_poweron_force_set(NRF_LRCCONF010, NRF_LRCCONF_POWER_DOMAIN_0, false); + + suspend_common(); + + nrf_lrcconf_task_trigger(NRF_LRCCONF010, NRF_LRCCONF_TASK_SYSTEMOFFREADY); + + __set_BASEPRI(0); + __ISB(); + __DSB(); + __WFI(); + + CODE_UNREACHABLE; +} + +#if IS_ENABLED(CONFIG_PM_S2RAM) +/* Resume domain after local suspend to RAM. */ +static void sys_resume(void) +{ + if (IS_ENABLED(CONFIG_ICACHE)) { + /* Power up and re-enable ICACHE */ + nrf_memconf_ramblock_control_enable_set(NRF_MEMCONF, RAMBLOCK_POWER_ID, + RAMBLOCK_CONTROL_BIT_ICACHE, true); + sys_cache_instr_enable(); + } + + if (IS_ENABLED(CONFIG_DCACHE)) { + /* Power up and re-enable DCACHE */ + nrf_memconf_ramblock_control_enable_set(NRF_MEMCONF, RAMBLOCK_POWER_ID, + RAMBLOCK_CONTROL_BIT_DCACHE, true); + sys_cache_data_enable(); + } + + /* Re-enable domain retention. */ + nrf_lrcconf_retain_set(NRF_LRCCONF010, NRF_LRCCONF_POWER_DOMAIN_0, true); + + /* TODO: Move it around k_cpu_idle() implementation. */ + nrf_lrcconf_poweron_force_set(NRF_LRCCONF010, NRF_LRCCONF_POWER_MAIN, + !IS_ENABLED(CONFIG_SOC_NRF54H20_CPURAD)); +} + +/* Function called during local domain suspend to RAM. */ +static int sys_suspend_to_ram(void) +{ + /* Set intormation which is used on domain wakeup to determine if resume from RAM shall + * be performed. + */ + nrf_resetinfo_resetreas_local_set(NRF_RESETINFO, + NRF_RESETINFO_RESETREAS_LOCAL_UNRETAINED_MASK); + nrf_resetinfo_restore_valid_set(NRF_RESETINFO, true); + nrf_lrcconf_poweron_force_set(NRF_LRCCONF010, NRF_LRCCONF_POWER_DOMAIN_0, false); + nrf_lrcconf_poweron_force_set(NRF_LRCCONF010, NRF_LRCCONF_POWER_MAIN, false); + + suspend_common(); + + __set_BASEPRI(0); + __ISB(); + __DSB(); + __WFI(); + /* + * We might reach this point is k_cpu_idle returns (there is a pre sleep hook that + * can abort sleeping. + */ + return -EBUSY; +} + +static void do_suspend_to_ram(void) +{ + /* + * Save the CPU context (including the return address),set the SRAM + * marker and power off the system. + */ + if (soc_s2ram_suspend(sys_suspend_to_ram)) { + return; + } + + /* + * On resuming or error we return exactly *HERE* + */ + + sys_resume(); +} +#endif /* IS_ENABLED(CONFIG_PM_S2RAM) */ + +void pm_state_set(enum pm_state state, uint8_t substate_id) +{ + if (state != PM_STATE_SUSPEND_TO_RAM) { + k_cpu_idle(); + return; + } +#if IS_ENABLED(CONFIG_PM_S2RAM) + do_suspend_to_ram(); +#endif +} + +void pm_state_exit_post_ops(enum pm_state state, uint8_t substate_id) +{ + irq_unlock(0); +} diff --git a/soc/nordic/nrf54h/power.h b/soc/nordic/nrf54h/power.h new file mode 100644 index 00000000000..021c20ec4dc --- /dev/null +++ b/soc/nordic/nrf54h/power.h @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file Common power.h include for Nordic SoCs. + */ + +#ifndef _ZEPHYR_SOC_ARM_NORDIC_NRF_POWER_H_ +#define _ZEPHYR_SOC_ARM_NORDIC_NRF_POWER_H_ + +/** + * @brief Perform a power off. + * + * This function performs a power off of the core. + */ +void nrf_poweroff(void); + +#endif /* _ZEPHYR_SOC_ARM_NORDIC_NRF_POWER_H_ */ diff --git a/soc/nordic/nrf54h/soc.c b/soc/nordic/nrf54h/soc.c index 75ac118a94a..b7d7658b7e3 100644 --- a/soc/nordic/nrf54h/soc.c +++ b/soc/nordic/nrf54h/soc.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -47,11 +48,35 @@ static void power_domain_init(void) * WFI the power domain will be correctly retained. */ - nrf_lrcconf_poweron_force_set(NRF_LRCCONF010, NRF_LRCCONF_POWER_MAIN, true); - nrf_lrcconf_poweron_force_set(NRF_LRCCONF010, NRF_LRCCONF_POWER_DOMAIN_0, true); + nrf_lrcconf_poweron_force_set(NRF_LRCCONF010, NRF_LRCCONF_POWER_DOMAIN_0, + !IS_ENABLED(CONFIG_SOC_NRF54H20_CPURAD)); + nrf_lrcconf_poweron_force_set(NRF_LRCCONF010, NRF_LRCCONF_POWER_MAIN, + !IS_ENABLED(CONFIG_SOC_NRF54H20_CPURAD)); nrf_lrcconf_retain_set(NRF_LRCCONF010, NRF_LRCCONF_POWER_MAIN, true); nrf_lrcconf_retain_set(NRF_LRCCONF010, NRF_LRCCONF_POWER_DOMAIN_0, true); + nrf_memconf_ramblock_ret_enable_set(NRF_MEMCONF, 0, RAMBLOCK_RET_BIT_ICACHE, false); + nrf_memconf_ramblock_ret_enable_set(NRF_MEMCONF, 0, RAMBLOCK_RET_BIT_DCACHE, false); + nrf_memconf_ramblock_ret_enable_set(NRF_MEMCONF, 1, RAMBLOCK_RET_BIT_ICACHE, false); + nrf_memconf_ramblock_ret_enable_set(NRF_MEMCONF, 1, RAMBLOCK_RET_BIT_DCACHE, false); +#if defined(RAMBLOCK_RET2_BIT_ICACHE) + nrf_memconf_ramblock_ret2_enable_set(NRF_MEMCONF, 0, RAMBLOCK_RET2_BIT_ICACHE, false); + nrf_memconf_ramblock_ret2_enable_set(NRF_MEMCONF, 1, RAMBLOCK_RET2_BIT_ICACHE, false); +#endif +#if defined(RAMBLOCK_RET2_BIT_DCACHE) + nrf_memconf_ramblock_ret2_enable_set(NRF_MEMCONF, 0, RAMBLOCK_RET2_BIT_DCACHE, false); + nrf_memconf_ramblock_ret2_enable_set(NRF_MEMCONF, 1, RAMBLOCK_RET2_BIT_DCACHE, false); +#endif + nrf_memconf_ramblock_ret_mask_enable_set(NRF_MEMCONF, 0, RAMBLOCK_RET_MASK, true); + nrf_memconf_ramblock_ret_mask_enable_set(NRF_MEMCONF, 1, RAMBLOCK_RET_MASK, true); +#if defined(RAMBLOCK_RET2_MASK) + /* + * TODO: Use nrf_memconf_ramblock_ret2_mask_enable_set() function + * when will be provided by HAL. + */ + NRF_MEMCONF->POWER[0].RET2 = RAMBLOCK_RET2_MASK; + NRF_MEMCONF->POWER[1].RET2 = RAMBLOCK_RET2_MASK; +#endif } static int trim_hsfll(void) diff --git a/soc/nordic/nrf54h/soc.h b/soc/nordic/nrf54h/soc.h index 9a44ab24982..566c07a8c2c 100644 --- a/soc/nordic/nrf54h/soc.h +++ b/soc/nordic/nrf54h/soc.h @@ -9,4 +9,31 @@ #include +#if defined(CONFIG_SOC_NRF54H20_CPUAPP) +#define RAMBLOCK_CONTROL_BIT_ICACHE MEMCONF_POWER_CONTROL_MEM1_Pos +#define RAMBLOCK_CONTROL_BIT_DCACHE MEMCONF_POWER_CONTROL_MEM2_Pos +#define RAMBLOCK_POWER_ID 0 +#define RAMBLOCK_CONTROL_OFF 0 +#define RAMBLOCK_RET_MASK (MEMCONF_POWER_RET_MEM0_Msk) +#define RAMBLOCK_RET_BIT_ICACHE MEMCONF_POWER_RET_MEM1_Pos +#define RAMBLOCK_RET_BIT_DCACHE MEMCONF_POWER_RET_MEM2_Pos +#elif defined(CONFIG_SOC_NRF54H20_CPURAD) +#define RAMBLOCK_CONTROL_BIT_ICACHE MEMCONF_POWER_CONTROL_MEM6_Pos +#define RAMBLOCK_CONTROL_BIT_DCACHE MEMCONF_POWER_CONTROL_MEM7_Pos +#define RAMBLOCK_POWER_ID 0 +#define RAMBLOCK_CONTROL_OFF 0 +#define RAMBLOCK_RET_MASK \ + (MEMCONF_POWER_RET_MEM0_Msk | MEMCONF_POWER_RET_MEM1_Msk | MEMCONF_POWER_RET_MEM2_Msk | \ + MEMCONF_POWER_RET_MEM3_Msk | MEMCONF_POWER_RET_MEM4_Msk | MEMCONF_POWER_RET_MEM5_Msk | \ + MEMCONF_POWER_RET_MEM8_Msk) +#define RAMBLOCK_RET2_MASK \ + (MEMCONF_POWER_RET2_MEM0_Msk | MEMCONF_POWER_RET2_MEM1_Msk | MEMCONF_POWER_RET2_MEM2_Msk | \ + MEMCONF_POWER_RET2_MEM3_Msk | MEMCONF_POWER_RET2_MEM4_Msk | MEMCONF_POWER_RET2_MEM5_Msk | \ + MEMCONF_POWER_RET2_MEM8_Msk) +#define RAMBLOCK_RET_BIT_ICACHE MEMCONF_POWER_RET_MEM6_Pos +#define RAMBLOCK_RET_BIT_DCACHE MEMCONF_POWER_RET_MEM7_Pos +#define RAMBLOCK_RET2_BIT_ICACHE MEMCONF_POWER_RET2_MEM6_Pos +#define RAMBLOCK_RET2_BIT_DCACHE MEMCONF_POWER_RET2_MEM7_Pos +#endif + #endif /* SOC_ARM_NORDIC_NRF_NRF54H_SOC_H_ */