1 | // SPDX-License-Identifier: GPL-2.0 |
2 | // |
3 | // Copyright (c) 2011-2014 Samsung Electronics Co., Ltd. |
4 | // http://www.samsung.com |
5 | // |
6 | // Exynos - Power Management support |
7 | // |
8 | // Based on arch/arm/mach-s3c2410/pm.c |
9 | // Copyright (c) 2006 Simtec Electronics |
10 | // Ben Dooks <ben@simtec.co.uk> |
11 | |
12 | #include <linux/init.h> |
13 | #include <linux/suspend.h> |
14 | #include <linux/cpu_pm.h> |
15 | #include <linux/io.h> |
16 | #include <linux/of.h> |
17 | #include <linux/soc/samsung/exynos-regs-pmu.h> |
18 | #include <linux/soc/samsung/exynos-pmu.h> |
19 | |
20 | #include <asm/firmware.h> |
21 | #include <asm/smp_scu.h> |
22 | #include <asm/suspend.h> |
23 | #include <asm/cacheflush.h> |
24 | |
25 | #include "common.h" |
26 | |
27 | static inline void __iomem *exynos_boot_vector_addr(void) |
28 | { |
29 | if (exynos_rev() == EXYNOS4210_REV_1_1) |
30 | return pmu_base_addr + S5P_INFORM7; |
31 | else if (exynos_rev() == EXYNOS4210_REV_1_0) |
32 | return sysram_base_addr + 0x24; |
33 | return pmu_base_addr + S5P_INFORM0; |
34 | } |
35 | |
36 | static inline void __iomem *exynos_boot_vector_flag(void) |
37 | { |
38 | if (exynos_rev() == EXYNOS4210_REV_1_1) |
39 | return pmu_base_addr + S5P_INFORM6; |
40 | else if (exynos_rev() == EXYNOS4210_REV_1_0) |
41 | return sysram_base_addr + 0x20; |
42 | return pmu_base_addr + S5P_INFORM1; |
43 | } |
44 | |
45 | #define S5P_CHECK_AFTR 0xFCBA0D10 |
46 | |
47 | /* For Cortex-A9 Diagnostic and Power control register */ |
48 | static unsigned int save_arm_register[2]; |
49 | |
50 | void exynos_cpu_save_register(void) |
51 | { |
52 | unsigned long tmp; |
53 | |
54 | /* Save Power control register */ |
55 | asm ("mrc p15, 0, %0, c15, c0, 0" |
56 | : "=r" (tmp) : : "cc" ); |
57 | |
58 | save_arm_register[0] = tmp; |
59 | |
60 | /* Save Diagnostic register */ |
61 | asm ("mrc p15, 0, %0, c15, c0, 1" |
62 | : "=r" (tmp) : : "cc" ); |
63 | |
64 | save_arm_register[1] = tmp; |
65 | } |
66 | |
67 | void exynos_cpu_restore_register(void) |
68 | { |
69 | unsigned long tmp; |
70 | |
71 | /* Restore Power control register */ |
72 | tmp = save_arm_register[0]; |
73 | |
74 | asm volatile ("mcr p15, 0, %0, c15, c0, 0" |
75 | : : "r" (tmp) |
76 | : "cc" ); |
77 | |
78 | /* Restore Diagnostic register */ |
79 | tmp = save_arm_register[1]; |
80 | |
81 | asm volatile ("mcr p15, 0, %0, c15, c0, 1" |
82 | : : "r" (tmp) |
83 | : "cc" ); |
84 | } |
85 | |
86 | void exynos_pm_central_suspend(void) |
87 | { |
88 | unsigned long tmp; |
89 | |
90 | /* Setting Central Sequence Register for power down mode */ |
91 | tmp = pmu_raw_readl(S5P_CENTRAL_SEQ_CONFIGURATION); |
92 | tmp &= ~S5P_CENTRAL_LOWPWR_CFG; |
93 | pmu_raw_writel(val: tmp, S5P_CENTRAL_SEQ_CONFIGURATION); |
94 | } |
95 | |
96 | int exynos_pm_central_resume(void) |
97 | { |
98 | unsigned long tmp; |
99 | |
100 | /* |
101 | * If PMU failed while entering sleep mode, WFI will be |
102 | * ignored by PMU and then exiting cpu_do_idle(). |
103 | * S5P_CENTRAL_LOWPWR_CFG bit will not be set automatically |
104 | * in this situation. |
105 | */ |
106 | tmp = pmu_raw_readl(S5P_CENTRAL_SEQ_CONFIGURATION); |
107 | if (!(tmp & S5P_CENTRAL_LOWPWR_CFG)) { |
108 | tmp |= S5P_CENTRAL_LOWPWR_CFG; |
109 | pmu_raw_writel(val: tmp, S5P_CENTRAL_SEQ_CONFIGURATION); |
110 | /* clear the wakeup state register */ |
111 | pmu_raw_writel(val: 0x0, S5P_WAKEUP_STAT); |
112 | /* No need to perform below restore code */ |
113 | return -1; |
114 | } |
115 | |
116 | return 0; |
117 | } |
118 | |
119 | /* Ext-GIC nIRQ/nFIQ is the only wakeup source in AFTR */ |
120 | static void exynos_set_wakeupmask(long mask) |
121 | { |
122 | pmu_raw_writel(val: mask, S5P_WAKEUP_MASK); |
123 | if (soc_is_exynos3250()) |
124 | pmu_raw_writel(val: 0x0, S5P_WAKEUP_MASK2); |
125 | } |
126 | |
127 | static void exynos_cpu_set_boot_vector(long flags) |
128 | { |
129 | writel_relaxed(__pa_symbol(exynos_cpu_resume), |
130 | exynos_boot_vector_addr()); |
131 | writel_relaxed(flags, exynos_boot_vector_flag()); |
132 | } |
133 | |
134 | static int exynos_aftr_finisher(unsigned long flags) |
135 | { |
136 | int ret; |
137 | |
138 | exynos_set_wakeupmask(soc_is_exynos3250() ? 0x40003ffe : 0x0000ff3e); |
139 | /* Set value of power down register for aftr mode */ |
140 | exynos_sys_powerdown_conf(mode: SYS_AFTR); |
141 | |
142 | ret = call_firmware_op(do_idle, FW_DO_IDLE_AFTR); |
143 | if (ret == -ENOSYS) { |
144 | if (read_cpuid_part() == ARM_CPU_PART_CORTEX_A9) |
145 | exynos_cpu_save_register(); |
146 | exynos_cpu_set_boot_vector(S5P_CHECK_AFTR); |
147 | cpu_do_idle(); |
148 | } |
149 | |
150 | return 1; |
151 | } |
152 | |
153 | void exynos_enter_aftr(void) |
154 | { |
155 | unsigned int cpuid = smp_processor_id(); |
156 | |
157 | cpu_pm_enter(); |
158 | |
159 | if (soc_is_exynos3250()) |
160 | exynos_set_boot_flag(cpu: cpuid, C2_STATE); |
161 | |
162 | exynos_pm_central_suspend(); |
163 | |
164 | if (soc_is_exynos4212() || soc_is_exynos4412()) { |
165 | /* Setting SEQ_OPTION register */ |
166 | pmu_raw_writel(S5P_USE_STANDBY_WFI0 | S5P_USE_STANDBY_WFE0, |
167 | S5P_CENTRAL_SEQ_OPTION); |
168 | } |
169 | |
170 | cpu_suspend(0, exynos_aftr_finisher); |
171 | |
172 | if (read_cpuid_part() == ARM_CPU_PART_CORTEX_A9) { |
173 | exynos_scu_enable(); |
174 | if (call_firmware_op(resume) == -ENOSYS) |
175 | exynos_cpu_restore_register(); |
176 | } |
177 | |
178 | exynos_pm_central_resume(); |
179 | |
180 | if (soc_is_exynos3250()) |
181 | exynos_clear_boot_flag(cpu: cpuid, C2_STATE); |
182 | |
183 | cpu_pm_exit(); |
184 | } |
185 | |
186 | #if defined(CONFIG_SMP) && defined(CONFIG_ARM_EXYNOS_CPUIDLE) |
187 | static atomic_t cpu1_wakeup = ATOMIC_INIT(0); |
188 | |
189 | static int exynos_cpu0_enter_aftr(void) |
190 | { |
191 | int ret = -1; |
192 | |
193 | /* |
194 | * If the other cpu is powered on, we have to power it off, because |
195 | * the AFTR state won't work otherwise |
196 | */ |
197 | if (cpu_online(1)) { |
198 | /* |
199 | * We reach a sync point with the coupled idle state, we know |
200 | * the other cpu will power down itself or will abort the |
201 | * sequence, let's wait for one of these to happen |
202 | */ |
203 | while (exynos_cpu_power_state(1)) { |
204 | unsigned long boot_addr; |
205 | |
206 | /* |
207 | * The other cpu may skip idle and boot back |
208 | * up again |
209 | */ |
210 | if (atomic_read(&cpu1_wakeup)) |
211 | goto abort; |
212 | |
213 | /* |
214 | * The other cpu may bounce through idle and |
215 | * boot back up again, getting stuck in the |
216 | * boot rom code |
217 | */ |
218 | ret = exynos_get_boot_addr(1, &boot_addr); |
219 | if (ret) |
220 | goto fail; |
221 | ret = -1; |
222 | if (boot_addr == 0) |
223 | goto abort; |
224 | |
225 | cpu_relax(); |
226 | } |
227 | } |
228 | |
229 | exynos_enter_aftr(); |
230 | ret = 0; |
231 | |
232 | abort: |
233 | if (cpu_online(1)) { |
234 | unsigned long boot_addr = __pa_symbol(exynos_cpu_resume); |
235 | |
236 | /* |
237 | * Set the boot vector to something non-zero |
238 | */ |
239 | ret = exynos_set_boot_addr(1, boot_addr); |
240 | if (ret) |
241 | goto fail; |
242 | dsb(); |
243 | |
244 | /* |
245 | * Turn on cpu1 and wait for it to be on |
246 | */ |
247 | exynos_cpu_power_up(1); |
248 | while (exynos_cpu_power_state(1) != S5P_CORE_LOCAL_PWR_EN) |
249 | cpu_relax(); |
250 | |
251 | if (soc_is_exynos3250()) { |
252 | while (!pmu_raw_readl(S5P_PMU_SPARE2) && |
253 | !atomic_read(&cpu1_wakeup)) |
254 | cpu_relax(); |
255 | |
256 | if (!atomic_read(&cpu1_wakeup)) |
257 | exynos_core_restart(1); |
258 | } |
259 | |
260 | while (!atomic_read(&cpu1_wakeup)) { |
261 | smp_rmb(); |
262 | |
263 | /* |
264 | * Poke cpu1 out of the boot rom |
265 | */ |
266 | |
267 | ret = exynos_set_boot_addr(1, boot_addr); |
268 | if (ret) |
269 | goto fail; |
270 | |
271 | call_firmware_op(cpu_boot, 1); |
272 | dsb_sev(); |
273 | } |
274 | } |
275 | fail: |
276 | return ret; |
277 | } |
278 | |
279 | static int exynos_wfi_finisher(unsigned long flags) |
280 | { |
281 | if (soc_is_exynos3250()) |
282 | flush_cache_all(); |
283 | cpu_do_idle(); |
284 | |
285 | return -1; |
286 | } |
287 | |
288 | static int exynos_cpu1_powerdown(void) |
289 | { |
290 | int ret = -1; |
291 | |
292 | /* |
293 | * Idle sequence for cpu1 |
294 | */ |
295 | if (cpu_pm_enter()) |
296 | goto cpu1_aborted; |
297 | |
298 | /* |
299 | * Turn off cpu 1 |
300 | */ |
301 | exynos_cpu_power_down(1); |
302 | |
303 | if (soc_is_exynos3250()) |
304 | pmu_raw_writel(0, S5P_PMU_SPARE2); |
305 | |
306 | ret = cpu_suspend(0, exynos_wfi_finisher); |
307 | |
308 | cpu_pm_exit(); |
309 | |
310 | cpu1_aborted: |
311 | dsb(); |
312 | /* |
313 | * Notify cpu 0 that cpu 1 is awake |
314 | */ |
315 | atomic_set(&cpu1_wakeup, 1); |
316 | |
317 | return ret; |
318 | } |
319 | |
320 | static void exynos_pre_enter_aftr(void) |
321 | { |
322 | unsigned long boot_addr = __pa_symbol(exynos_cpu_resume); |
323 | |
324 | (void)exynos_set_boot_addr(1, boot_addr); |
325 | } |
326 | |
327 | static void exynos_post_enter_aftr(void) |
328 | { |
329 | atomic_set(&cpu1_wakeup, 0); |
330 | } |
331 | |
332 | struct cpuidle_exynos_data cpuidle_coupled_exynos_data = { |
333 | .cpu0_enter_aftr = exynos_cpu0_enter_aftr, |
334 | .cpu1_powerdown = exynos_cpu1_powerdown, |
335 | .pre_enter_aftr = exynos_pre_enter_aftr, |
336 | .post_enter_aftr = exynos_post_enter_aftr, |
337 | }; |
338 | #endif /* CONFIG_SMP && CONFIG_ARM_EXYNOS_CPUIDLE */ |
339 | |