1 /* 2 * Copyright (c) 2013-2014 Wind River Systems, Inc. 3 * Copyright (c) 2023 Arm Limited 4 * 5 * SPDX-License-Identifier: Apache-2.0 6 */ 7 8 /** 9 * @file 10 * @brief ARM Cortex-M power management 11 */ 12 #include <zephyr/kernel.h> 13 #include <cmsis_core.h> 14 15 #if defined(CONFIG_ARM_ON_EXIT_CPU_IDLE) 16 #include <soc_cpu_idle.h> 17 #endif 18 19 /** 20 * @brief Initialization of CPU idle 21 * 22 * Only called by arch_kernel_init(). Sets SEVONPEND bit once for the system's 23 * duration. 24 */ z_arm_cpu_idle_init(void)25void z_arm_cpu_idle_init(void) 26 { 27 SCB->SCR = SCB_SCR_SEVONPEND_Msk; 28 } 29 30 #if defined(CONFIG_ARM_ON_EXIT_CPU_IDLE) 31 #define ON_EXIT_IDLE_HOOK SOC_ON_EXIT_CPU_IDLE 32 #else 33 #define ON_EXIT_IDLE_HOOK \ 34 do { \ 35 } while (false) 36 #endif 37 38 #if defined(CONFIG_ARM_ON_ENTER_CPU_IDLE_HOOK) 39 #define SLEEP_IF_ALLOWED(wait_instr) \ 40 do { \ 41 /* Skip the wait instr if on_enter_cpu_idle returns false */ \ 42 if (z_arm_on_enter_cpu_idle()) { \ 43 /* Wait for all memory transaction to complete */ \ 44 /* before entering low power state. */ \ 45 __DSB(); \ 46 wait_instr(); \ 47 /* Inline the macro provided by SoC-specific code */ \ 48 ON_EXIT_IDLE_HOOK; \ 49 } \ 50 } while (false) 51 #else 52 #define SLEEP_IF_ALLOWED(wait_instr) \ 53 do { \ 54 __DSB(); \ 55 wait_instr(); \ 56 ON_EXIT_IDLE_HOOK; \ 57 } while (false) 58 #endif 59 60 #ifndef CONFIG_ARCH_HAS_CUSTOM_CPU_IDLE arch_cpu_idle(void)61void arch_cpu_idle(void) 62 { 63 #if defined(CONFIG_TRACING) 64 sys_trace_idle(); 65 #endif 66 67 #if CONFIG_ARM_ON_ENTER_CPU_IDLE_PREPARE_HOOK 68 z_arm_on_enter_cpu_idle_prepare(); 69 #endif 70 71 #if defined(CONFIG_ARMV7_M_ARMV8_M_MAINLINE) 72 /* 73 * PRIMASK is always cleared on ARMv7-M and ARMv8-M (not used 74 * for interrupt locking), and configuring BASEPRI to the lowest 75 * priority to ensure wake-up will cause interrupts to be serviced 76 * before entering low power state. 77 * 78 * Set PRIMASK before configuring BASEPRI to prevent interruption 79 * before wake-up. 80 */ 81 __disable_irq(); 82 83 /* 84 * Set wake-up interrupt priority to the lowest and synchronize to 85 * ensure that this is visible to the WFI instruction. 86 */ 87 __set_BASEPRI(0); 88 __ISB(); 89 #else 90 /* 91 * For all the other ARM architectures that do not implement BASEPRI, 92 * PRIMASK is used as the interrupt locking mechanism, and it is not 93 * necessary to set PRIMASK here, as PRIMASK would have already been 94 * set by the caller as part of interrupt locking if necessary 95 * (i.e. if the caller sets _kernel.idle). 96 */ 97 #endif 98 99 SLEEP_IF_ALLOWED(__WFI); 100 101 #if defined(CONFIG_TRACING) 102 sys_trace_idle_exit(); 103 #endif 104 __enable_irq(); 105 __ISB(); 106 } 107 #endif 108 109 #ifndef CONFIG_ARCH_HAS_CUSTOM_CPU_ATOMIC_IDLE arch_cpu_atomic_idle(unsigned int key)110void arch_cpu_atomic_idle(unsigned int key) 111 { 112 #if defined(CONFIG_TRACING) 113 sys_trace_idle(); 114 #endif 115 116 #if CONFIG_ARM_ON_ENTER_CPU_IDLE_PREPARE_HOOK 117 z_arm_on_enter_cpu_idle_prepare(); 118 #endif 119 120 /* 121 * Lock PRIMASK while sleeping: wfe will still get interrupted by 122 * incoming interrupts but the CPU will not service them right away. 123 */ 124 __disable_irq(); 125 126 /* 127 * No need to set SEVONPEND, it's set once in z_arm_cpu_idle_init() 128 * and never touched again. 129 */ 130 131 #if defined(CONFIG_ARMV6_M_ARMV8_M_BASELINE) 132 /* No BASEPRI, call wfe directly. (SEVONPEND is set in z_arm_cpu_idle_init()) */ 133 #elif defined(CONFIG_ARMV7_M_ARMV8_M_MAINLINE) 134 /* unlock BASEPRI so wfe gets interrupted by incoming interrupts */ 135 __set_BASEPRI(0); 136 __ISB(); 137 #else 138 #error Unsupported architecture 139 #endif 140 141 SLEEP_IF_ALLOWED(__WFE); 142 143 #if defined(CONFIG_TRACING) 144 sys_trace_idle_exit(); 145 #endif 146 147 arch_irq_unlock(key); 148 #if defined(CONFIG_ARMV7_M_ARMV8_M_MAINLINE) 149 __enable_irq(); 150 #endif 151 } 152 #endif 153