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

Add suspend to RAM support for nRF54H20 #73095

Merged
merged 3 commits into from
Sep 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion drivers/timer/nrf_grtc_timer.c
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ int z_nrf_grtc_timer_capture_read(int32_t chan, uint64_t *captured_time)
return 0;
}

#if defined(CONFIG_NRF_GRTC_SLEEP_ALLOWED)
#if defined(CONFIG_NRF_GRTC_SLEEP_ALLOWED) && defined(CONFIG_NRF_GRTC_START_SYSCOUNTER)
int z_nrf_grtc_wakeup_prepare(uint64_t wake_time_us)
{
nrfx_err_t err_code;
Expand Down
4 changes: 4 additions & 0 deletions soc/nordic/common/poweroff.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

#if defined(CONFIG_SOC_SERIES_NRF51X) || defined(CONFIG_SOC_SERIES_NRF52X)
#include <hal/nrf_power.h>
#elif defined(CONFIG_SOC_SERIES_NRF54HX)
#include <power.h>
#else
#include <hal/nrf_regulators.h>
#endif
Expand All @@ -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
Expand Down
5 changes: 5 additions & 0 deletions soc/nordic/nrf54h/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions soc/nordic/nrf54h/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
151 changes: 151 additions & 0 deletions soc/nordic/nrf54h/pm_s2ram.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/*
* Copyright (c) 2024 Nordic Semiconductor ASA
* SPDX-License-Identifier: Apache-2.0
*/

#include <zephyr/arch/cpu.h>
#include <zephyr/arch/common/pm_s2ram.h>
#include <zephyr/linker/sections.h>
#include <zephyr/sys/util.h>
#include <hal/nrf_resetinfo.h>
#include "pm_s2ram.h"

#include <cmsis_core.h>

#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;
}
31 changes: 31 additions & 0 deletions soc/nordic/nrf54h/pm_s2ram.h
Original file line number Diff line number Diff line change
@@ -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_ */
145 changes: 145 additions & 0 deletions soc/nordic/nrf54h/power.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/*
* Copyright (c) 2024 Nordic Semiconductor ASA
* SPDX-License-Identifier: Apache-2.0
*/

#include <zephyr/kernel.h>
#include <zephyr/sys/poweroff.h>
#include <zephyr/toolchain.h>
#include <zephyr/pm/policy.h>
#include <zephyr/arch/common/pm_s2ram.h>
#include <hal/nrf_resetinfo.h>
#include <hal/nrf_lrcconf.h>
#include <hal/nrf_memconf.h>
#include <zephyr/cache.h>
#include <power.h>
#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)
gmarull marked this conversation as resolved.
Show resolved Hide resolved
{
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;
Comment on lines +102 to +110
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly to above, should this clean up in the failure case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that this is not the same case (#73095 (comment)) - __set_BASEPRI(0) doesn't disable interrupts but clears the interrupt mask. @hubertmis , correct me if I'm wrong.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about the modifications to RESETINFO like setting RESTOREVALID, could those have unintended consequences when we don't actually suspend here? I guess the stored CPU context is no longer valid at this point

Copy link
Contributor Author

@adamkondraciuk adamkondraciuk Jun 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, the CPU context is no longer valid, but we don't check the RESETINFO then (and hence no CPU context restoring) - it happens only when reset vector is called after wake-up.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the only edge case that could cause issues would be domain resets initiated from the secure domain, but I think the UNRETAINEDWAKE bit in RESETREAS.LOCAL will always be cleared in those instances so it should be fine.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that this is not the same case (#73095 (comment)) - __set_BASEPRI(0) doesn't disable interrupts but clears the interrupt mask. @hubertmis , correct me if I'm wrong.

From ARMv8M manual:

Software can set this field to a priority number between 1 and the maximum supported priority number. This boosts the current execution priority to that number, masking all exceptions with a lower priority.

Setting BASEPRI to 0 effectively locks all IRQs (including ZLI).

How about the modifications to RESETINFO like setting RESTOREVALID, could those have unintended consequences when we don't actually suspend here? I guess the stored CPU context is no longer valid at this point

When this function returns, it should enter the "resume" procedure which in both cases of wake-up and returning error here should clean up after suspending things like LRCCONF settings or RESETINFO.RESTOREVALID.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I see there is no such "resume" procedure and LRCCONFs are never restored. That's something we need to improve.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or is it z_pm_sys_resume()? This code is so all over the place with functions with similar naming it's hard to understand what's going on in which procedure ;)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or is it z_pm_sys_resume()? This code is so all over the place with functions with similar naming it's hard to understand what's going on in which procedure ;)

Yup - it is z_pm_sys_resume()

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about the modifications to RESETINFO like setting RESTOREVALID, could those have unintended consequences when we don't actually suspend here? I guess the stored CPU context is no longer valid at this point

I missed that pm_s2ram_mark_check_and_clear() should always be called when this function returns, so my particular question about RESETINFO is not valid since it is indeed cleaned up on failure.

}

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you are not doing anything you should call k_cpu_idle() otherwise the idle thread will loop on it until the next scheduled time is lower than the suspend-to-ram residency time.

What does not make much sense is having PM enabled if you don't handle any power state, you could simply disable PM when CONFIG_PM_S2RAM is disabled.

Note that device power management does not require CONFIG_PM

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does not make much sense is having PM enabled if you don't handle any power state, you could simply disable PM when CONFIG_PM_S2RAM is disabled.
But here is reversed dependence.

}

void pm_state_exit_post_ops(enum pm_state state, uint8_t substate_id)
{
irq_unlock(0);
}
Loading
Loading