blob: 4df091fbbd633ee7881d82f49e9ac4589a8538b1 [file] [log] [blame]
/*
* Copyright (c) 2013-2014 Wind River Systems, Inc.
* Copyright (c) 2023 Arm Limited
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief ARM Cortex-M power management
*/
#include <zephyr/kernel.h>
#include <cmsis_core.h>
#if defined(CONFIG_ARM_ON_EXIT_CPU_IDLE)
#include <soc_cpu_idle.h>
#endif
/**
* @brief Initialization of CPU idle
*
* Only called by arch_kernel_init(). Sets SEVONPEND bit once for the system's
* duration.
*/
void z_arm_cpu_idle_init(void)
{
SCB->SCR = SCB_SCR_SEVONPEND_Msk;
}
#if defined(CONFIG_ARM_ON_EXIT_CPU_IDLE)
#define ON_EXIT_IDLE_HOOK SOC_ON_EXIT_CPU_IDLE
#else
#define ON_EXIT_IDLE_HOOK do {} while (false)
#endif
#if defined(CONFIG_ARM_ON_ENTER_CPU_IDLE_HOOK)
#define SLEEP_IF_ALLOWED(wait_instr) do { \
/* Skip the wait instr if on_enter_cpu_idle returns false */ \
if (z_arm_on_enter_cpu_idle()) { \
/* Wait for all memory transaction to complete */ \
/* before entering low power state. */ \
__DSB(); \
wait_instr(); \
/* Inline the macro provided by SoC-specific code */ \
ON_EXIT_IDLE_HOOK; \
} \
} while (false)
#else
#define SLEEP_IF_ALLOWED(wait_instr) do { \
__DSB(); \
wait_instr(); \
ON_EXIT_IDLE_HOOK; \
} while (false)
#endif
void arch_cpu_idle(void)
{
#if defined(CONFIG_TRACING)
sys_trace_idle();
#endif
#if CONFIG_ARM_ON_ENTER_CPU_IDLE_PREPARE_HOOK
z_arm_on_enter_cpu_idle_prepare();
#endif
#if defined(CONFIG_ARMV7_M_ARMV8_M_MAINLINE)
/*
* PRIMASK is always cleared on ARMv7-M and ARMv8-M (not used
* for interrupt locking), and configuring BASEPRI to the lowest
* priority to ensure wake-up will cause interrupts to be serviced
* before entering low power state.
*
* Set PRIMASK before configuring BASEPRI to prevent interruption
* before wake-up.
*/
__disable_irq();
/*
* Set wake-up interrupt priority to the lowest and synchronize to
* ensure that this is visible to the WFI instruction.
*/
__set_BASEPRI(0);
__ISB();
#else
/*
* For all the other ARM architectures that do not implement BASEPRI,
* PRIMASK is used as the interrupt locking mechanism, and it is not
* necessary to set PRIMASK here, as PRIMASK would have already been
* set by the caller as part of interrupt locking if necessary
* (i.e. if the caller sets _kernel.idle).
*/
#endif
SLEEP_IF_ALLOWED(__WFI);
__enable_irq();
__ISB();
}
void arch_cpu_atomic_idle(unsigned int key)
{
#if defined(CONFIG_TRACING)
sys_trace_idle();
#endif
#if CONFIG_ARM_ON_ENTER_CPU_IDLE_PREPARE_HOOK
z_arm_on_enter_cpu_idle_prepare();
#endif
/*
* Lock PRIMASK while sleeping: wfe will still get interrupted by
* incoming interrupts but the CPU will not service them right away.
*/
__disable_irq();
/*
* No need to set SEVONPEND, it's set once in z_arm_cpu_idle_init()
* and never touched again.
*/
#if defined(CONFIG_ARMV6_M_ARMV8_M_BASELINE)
/* No BASEPRI, call wfe directly. (SEVONPEND is set in z_arm_cpu_idle_init()) */
#elif defined(CONFIG_ARMV7_M_ARMV8_M_MAINLINE)
/* unlock BASEPRI so wfe gets interrupted by incoming interrupts */
__set_BASEPRI(0);
__ISB();
#else
#error Unsupported architecture
#endif
SLEEP_IF_ALLOWED(__WFE);
arch_irq_unlock(key);
#if defined(CONFIG_ARMV7_M_ARMV8_M_MAINLINE)
__enable_irq();
#endif
}