1/*
2 * Copyright (c) 2013-2014 Wind River Systems, Inc.
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7/**
8 * @file
9 * @brief ARM Cortex-M and Cortex-R power management
10 *
11 */
12
13#include <toolchain.h>
14#include <linker/sections.h>
15
16_ASM_FILE_PROLOGUE
17
18GTEXT(z_arm_cpu_idle_init)
19GTEXT(arch_cpu_idle)
20GTEXT(arch_cpu_atomic_idle)
21
22#if defined(CONFIG_CPU_CORTEX_M)
23#define _SCB_SCR		0xE000ED10
24
25#define _SCB_SCR_SEVONPEND	(1 << 4)
26#define _SCB_SCR_SLEEPDEEP	(1 << 2)
27#define _SCB_SCR_SLEEPONEXIT	(1 << 1)
28#define _SCR_INIT_BITS		_SCB_SCR_SEVONPEND
29#endif
30
31/**
32 *
33 * @brief Initialization of CPU idle
34 *
35 * Only called by arch_kernel_init(). Sets SEVONPEND bit once for the system's
36 * duration.
37 *
38 * @return N/A
39 *
40 * C function prototype:
41 *
42 * void z_arm_cpu_idle_init(void);
43 */
44
45SECTION_FUNC(TEXT, z_arm_cpu_idle_init)
46#if defined(CONFIG_CPU_CORTEX_M)
47	ldr	r1, =_SCB_SCR
48	movs.n	r2, #_SCR_INIT_BITS
49	str	r2, [r1]
50#endif
51	bx	lr
52
53SECTION_FUNC(TEXT, arch_cpu_idle)
54#ifdef CONFIG_TRACING
55	push	{r0, lr}
56	bl	sys_trace_idle
57#if defined(CONFIG_ARMV6_M_ARMV8_M_BASELINE)
58	pop	{r0, r1}
59	mov	lr, r1
60#else
61	pop	{r0, lr}
62#endif /* CONFIG_ARMV6_M_ARMV8_M_BASELINE */
63#endif /* CONFIG_TRACING */
64
65#if defined(CONFIG_ARMV7_M_ARMV8_M_MAINLINE)
66	/*
67	 * PRIMASK is always cleared on ARMv7-M and ARMv8-M Mainline (not used
68	 * for interrupt locking), and configuring BASEPRI to the lowest
69	 * priority to ensure wake-up will cause interrupts to be serviced
70	 * before entering low power state.
71	 *
72	 * Set PRIMASK before configuring BASEPRI to prevent interruption
73	 * before wake-up.
74	 */
75	cpsid	i
76
77	/*
78	 * Set wake-up interrupt priority to the lowest and synchronise to
79	 * ensure that this is visible to the WFI instruction.
80	 */
81	eors.n	r0, r0
82	msr	BASEPRI, r0
83	isb
84#else
85	/*
86	 * For all the other ARM architectures that do not implement BASEPRI,
87	 * PRIMASK is used as the interrupt locking mechanism, and it is not
88	 * necessary to set PRIMASK here, as PRIMASK would have already been
89	 * set by the caller as part of interrupt locking if necessary
90	 * (i.e. if the caller sets _kernel.idle).
91	 */
92#endif /* CONFIG_ARMV7_M_ARMV8_M_MAINLINE */
93
94	/*
95	 * Wait for all memory transactions to complete before entering low
96	 * power state.
97	 */
98	dsb
99
100	/* Enter low power state */
101	wfi
102
103	/*
104	 * Clear PRIMASK and flush instruction buffer to immediately service
105	 * the wake-up interrupt.
106	 */
107	cpsie	i
108	isb
109
110	bx	lr
111
112SECTION_FUNC(TEXT, arch_cpu_atomic_idle)
113#ifdef CONFIG_TRACING
114	push	{r0, lr}
115	bl	sys_trace_idle
116#if defined(CONFIG_ARMV6_M_ARMV8_M_BASELINE)
117	pop	{r0, r1}
118	mov	lr, r1
119#else
120	pop	{r0, lr}
121#endif /* CONFIG_ARMV6_M_ARMV8_M_BASELINE */
122#endif /* CONFIG_TRACING */
123
124	/*
125	 * Lock PRIMASK while sleeping: wfe will still get interrupted by
126	 * incoming interrupts but the CPU will not service them right away.
127	 */
128	cpsid	i
129
130	/*
131	 * No need to set SEVONPEND, it's set once in z_arm_cpu_idle_init()
132	 * and never touched again.
133	 */
134
135	/* r0: interrupt mask from caller */
136
137#if defined(CONFIG_ARMV6_M_ARMV8_M_BASELINE) \
138	|| defined(CONFIG_ARMV7_R)
139	/* No BASEPRI, call wfe directly
140	 * (SEVONPEND is set in z_arm_cpu_idle_init())
141	 */
142	wfe
143
144	cmp	r0, #0
145	bne	_irq_disabled
146	cpsie	i
147_irq_disabled:
148
149#elif defined(CONFIG_ARMV7_M_ARMV8_M_MAINLINE)
150	/* r1: zero, for setting BASEPRI (needs a register) */
151	eors.n	r1, r1
152
153	/* unlock BASEPRI so wfe gets interrupted by incoming interrupts */
154	msr	BASEPRI, r1
155
156	wfe
157
158	msr	BASEPRI, r0
159	cpsie	i
160#else
161#error Unknown ARM architecture
162#endif /* CONFIG_ARMV6_M_ARMV8_M_BASELINE */
163	bx	lr
164