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)25 void 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)61 void 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)110 void 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