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 do {} while (false)
34 #endif
35 
36 #if defined(CONFIG_ARM_ON_ENTER_CPU_IDLE_HOOK)
37 #define SLEEP_IF_ALLOWED(wait_instr) do { \
38 	/* Skip the wait instr if on_enter_cpu_idle returns false */ \
39 	if (z_arm_on_enter_cpu_idle()) { \
40 		/* Wait for all memory transaction to complete */ \
41 		/* before entering low power state. */ \
42 		__DSB(); \
43 		wait_instr(); \
44 		/* Inline the macro provided by SoC-specific code */ \
45 		ON_EXIT_IDLE_HOOK; \
46 	} \
47 } while (false)
48 #else
49 #define SLEEP_IF_ALLOWED(wait_instr) do { \
50 	__DSB(); \
51 	wait_instr(); \
52 	ON_EXIT_IDLE_HOOK; \
53 } while (false)
54 #endif
55 
56 #ifndef CONFIG_ARCH_HAS_CUSTOM_CPU_IDLE
arch_cpu_idle(void)57 void arch_cpu_idle(void)
58 {
59 #if defined(CONFIG_TRACING)
60 	sys_trace_idle();
61 #endif
62 
63 #if CONFIG_ARM_ON_ENTER_CPU_IDLE_PREPARE_HOOK
64 	z_arm_on_enter_cpu_idle_prepare();
65 #endif
66 
67 #if defined(CONFIG_ARMV7_M_ARMV8_M_MAINLINE)
68 	/*
69 	 * PRIMASK is always cleared on ARMv7-M and ARMv8-M (not used
70 	 * for interrupt locking), and configuring BASEPRI to the lowest
71 	 * priority to ensure wake-up will cause interrupts to be serviced
72 	 * before entering low power state.
73 	 *
74 	 * Set PRIMASK before configuring BASEPRI to prevent interruption
75 	 * before wake-up.
76 	 */
77 	__disable_irq();
78 
79 	/*
80 	 * Set wake-up interrupt priority to the lowest and synchronize to
81 	 * ensure that this is visible to the WFI instruction.
82 	 */
83 	__set_BASEPRI(0);
84 	__ISB();
85 #else
86 	/*
87 	 * For all the other ARM architectures that do not implement BASEPRI,
88 	 * PRIMASK is used as the interrupt locking mechanism, and it is not
89 	 * necessary to set PRIMASK here, as PRIMASK would have already been
90 	 * set by the caller as part of interrupt locking if necessary
91 	 * (i.e. if the caller sets _kernel.idle).
92 	 */
93 #endif
94 
95 	SLEEP_IF_ALLOWED(__WFI);
96 
97 	__enable_irq();
98 	__ISB();
99 }
100 #endif
101 
102 #ifndef CONFIG_ARCH_HAS_CUSTOM_CPU_ATOMIC_IDLE
arch_cpu_atomic_idle(unsigned int key)103 void arch_cpu_atomic_idle(unsigned int key)
104 {
105 #if defined(CONFIG_TRACING)
106 	sys_trace_idle();
107 #endif
108 
109 #if CONFIG_ARM_ON_ENTER_CPU_IDLE_PREPARE_HOOK
110 	z_arm_on_enter_cpu_idle_prepare();
111 #endif
112 
113 	/*
114 	 * Lock PRIMASK while sleeping: wfe will still get interrupted by
115 	 * incoming interrupts but the CPU will not service them right away.
116 	 */
117 	__disable_irq();
118 
119 	/*
120 	 * No need to set SEVONPEND, it's set once in z_arm_cpu_idle_init()
121 	 * and never touched again.
122 	 */
123 
124 #if defined(CONFIG_ARMV6_M_ARMV8_M_BASELINE)
125 	/* No BASEPRI, call wfe directly. (SEVONPEND is set in z_arm_cpu_idle_init()) */
126 #elif defined(CONFIG_ARMV7_M_ARMV8_M_MAINLINE)
127 	/* unlock BASEPRI so wfe gets interrupted by incoming interrupts  */
128 	__set_BASEPRI(0);
129 	__ISB();
130 #else
131 #error Unsupported architecture
132 #endif
133 
134 	SLEEP_IF_ALLOWED(__WFE);
135 
136 	arch_irq_unlock(key);
137 #if defined(CONFIG_ARMV7_M_ARMV8_M_MAINLINE)
138 	__enable_irq();
139 #endif
140 }
141 #endif
142