blob: 4c30f3294b7ac5b286419e22d612cb0b0fdcfec9 [file] [log] [blame]
Tom Rini10e47792018-05-06 17:58:06 -04001// SPDX-License-Identifier: GPL-2.0
Chen-Yu Tsai60e0b182016-06-07 10:54:34 +08002/*
3 * Copyright (C) 2016
4 * Author: Chen-Yu Tsai <wens@csie.org>
5 *
6 * Based on assembly code by Marc Zyngier <marc.zyngier@arm.com>,
7 * which was based on code by Carl van Schaik <carl@ok-labs.com>.
Chen-Yu Tsai60e0b182016-06-07 10:54:34 +08008 */
9#include <config.h>
Simon Glass274e0b02020-05-10 11:39:56 -060010#include <asm/cache.h>
Chen-Yu Tsai60e0b182016-06-07 10:54:34 +080011
12#include <asm/arch/cpu.h>
Chen-Yu Tsai60e0b182016-06-07 10:54:34 +080013#include <asm/armv7.h>
14#include <asm/gic.h>
15#include <asm/io.h>
16#include <asm/psci.h>
Chen-Yu Tsai7ca14502016-06-19 12:38:41 +080017#include <asm/secure.h>
Chen-Yu Tsai60e0b182016-06-07 10:54:34 +080018#include <asm/system.h>
19
20#include <linux/bitops.h>
21
Chen-Yu Tsai60e0b182016-06-07 10:54:34 +080022#define __irq __attribute__ ((interrupt ("IRQ")))
23
24#define GICD_BASE (SUNXI_GIC400_BASE + GIC_DIST_OFFSET)
25#define GICC_BASE (SUNXI_GIC400_BASE + GIC_CPU_OFFSET_A15)
26
Chen-Yu Tsaib1a1fda2017-03-01 11:03:15 +080027/*
Sam Edwards28c68992023-10-11 19:47:55 -060028 * Offsets into the CPUCFG block applicable to most SUNXIs.
29 */
30#define SUNXI_CPU_RST(cpu) (0x40 + (cpu) * 0x40 + 0x0)
31#define SUNXI_CPU_STATUS(cpu) (0x40 + (cpu) * 0x40 + 0x8)
32#define SUNXI_GEN_CTRL (0x184)
33#define SUNXI_PRIV0 (0x1a4)
34#define SUN7I_CPU1_PWR_CLAMP (0x1b0)
35#define SUN7I_CPU1_PWROFF (0x1b4)
36#define SUNXI_DBG_CTRL1 (0x1e4)
37
38/*
Chen-Yu Tsaib1a1fda2017-03-01 11:03:15 +080039 * R40 is different from other single cluster SoCs.
40 *
41 * The power clamps are located in the unused space after the per-core
42 * reset controls for core 3. The secondary core entry address register
43 * is in the SRAM controller address range.
44 */
45#define SUN8I_R40_PWROFF (0x110)
46#define SUN8I_R40_PWR_CLAMP(cpu) (0x120 + (cpu) * 0x4)
47#define SUN8I_R40_SRAMC_SOFT_ENTRY_REG0 (0xbc)
48
Sam Edwards21e27f02023-10-11 19:47:56 -060049/*
50 * R528 is also different, as it has both cores powered up (but held in reset
51 * state) after the SoC is reset. Like the R40, it uses a "soft" entry point
52 * address register, but unlike the R40, it uses a newer "CPUX" block to manage
53 * CPU state, rather than the older CPUCFG system.
54 */
55#define SUN8I_R528_SOFT_ENTRY (0x1c8)
56#define SUN8I_R528_C0_RST_CTRL (0x0000)
57#define SUN8I_R528_C0_CTRL_REG0 (0x0010)
58#define SUN8I_R528_C0_CPU_STATUS (0x0080)
59
60#define SUN8I_R528_C0_STATUS_STANDBYWFI (16)
61
62/* Only newer cores have this additional IP block. */
63#ifndef SUNXI_R_CPUCFG_BASE
64#define SUNXI_R_CPUCFG_BASE 0
65#endif
66
Chen-Yu Tsai60e0b182016-06-07 10:54:34 +080067static void __secure cp15_write_cntp_tval(u32 tval)
68{
69 asm volatile ("mcr p15, 0, %0, c14, c2, 0" : : "r" (tval));
70}
71
72static void __secure cp15_write_cntp_ctl(u32 val)
73{
74 asm volatile ("mcr p15, 0, %0, c14, c2, 1" : : "r" (val));
75}
76
77static u32 __secure cp15_read_cntp_ctl(void)
78{
79 u32 val;
80
81 asm volatile ("mrc p15, 0, %0, c14, c2, 1" : "=r" (val));
82
83 return val;
84}
85
Peng Fane7c59392022-04-13 17:47:22 +080086#define ONE_MS (CONFIG_COUNTER_FREQUENCY / 1000)
Chen-Yu Tsai60e0b182016-06-07 10:54:34 +080087
88static void __secure __mdelay(u32 ms)
89{
90 u32 reg = ONE_MS * ms;
91
92 cp15_write_cntp_tval(reg);
Tom Rini3b787ef2016-08-01 18:54:53 -040093 isb();
Chen-Yu Tsai60e0b182016-06-07 10:54:34 +080094 cp15_write_cntp_ctl(3);
95
96 do {
Tom Rini3b787ef2016-08-01 18:54:53 -040097 isb();
Chen-Yu Tsai60e0b182016-06-07 10:54:34 +080098 reg = cp15_read_cntp_ctl();
99 } while (!(reg & BIT(2)));
100
101 cp15_write_cntp_ctl(0);
Tom Rini3b787ef2016-08-01 18:54:53 -0400102 isb();
Chen-Yu Tsai60e0b182016-06-07 10:54:34 +0800103}
104
Sam Edwardscbb505c2023-10-11 19:47:53 -0600105static void __secure clamp_release(u32 *clamp)
Chen-Yu Tsai60e0b182016-06-07 10:54:34 +0800106{
Chen-Yu Tsai60e0b182016-06-07 10:54:34 +0800107 u32 tmp = 0x1ff;
108 do {
109 tmp >>= 1;
110 writel(tmp, clamp);
111 } while (tmp);
112
113 __mdelay(10);
Chen-Yu Tsai60e0b182016-06-07 10:54:34 +0800114}
115
Sam Edwardscbb505c2023-10-11 19:47:53 -0600116static void __secure clamp_set(u32 *clamp)
Chen-Yu Tsai60e0b182016-06-07 10:54:34 +0800117{
Chen-Yu Tsai60e0b182016-06-07 10:54:34 +0800118 writel(0xff, clamp);
Chen-Yu Tsai60e0b182016-06-07 10:54:34 +0800119}
120
Sam Edwards1f684b52023-10-11 19:47:54 -0600121static void __secure sunxi_cpu_set_entry(int __always_unused cpu, void *entry)
Chen-Yu Tsai60e0b182016-06-07 10:54:34 +0800122{
Sam Edwardscbb505c2023-10-11 19:47:53 -0600123 if (IS_ENABLED(CONFIG_MACH_SUN8I_R40)) {
124 writel((u32)entry,
125 SUNXI_SRAMC_BASE + SUN8I_R40_SRAMC_SOFT_ENTRY_REG0);
Sam Edwards21e27f02023-10-11 19:47:56 -0600126 } else if (IS_ENABLED(CONFIG_MACH_SUN8I_R528)) {
127 writel((u32)entry,
128 SUNXI_R_CPUCFG_BASE + SUN8I_R528_SOFT_ENTRY);
Chen-Yu Tsai60e0b182016-06-07 10:54:34 +0800129 } else {
Sam Edwards28c68992023-10-11 19:47:55 -0600130 writel((u32)entry, SUNXI_CPUCFG_BASE + SUNXI_PRIV0);
Chen-Yu Tsai60e0b182016-06-07 10:54:34 +0800131 }
132}
133
Sam Edwardscbb505c2023-10-11 19:47:53 -0600134static void __secure sunxi_cpu_set_power(int cpu, bool on)
Chen-Yu Tsaic4a87602017-06-07 15:11:49 +0800135{
Sam Edwardscbb505c2023-10-11 19:47:53 -0600136 u32 *clamp = NULL;
137 u32 *pwroff;
Chen-Yu Tsaic4a87602017-06-07 15:11:49 +0800138
Sam Edwardscbb505c2023-10-11 19:47:53 -0600139 /* sun7i (A20) is different from other single cluster SoCs */
140 if (IS_ENABLED(CONFIG_MACH_SUN7I)) {
Sam Edwards28c68992023-10-11 19:47:55 -0600141 clamp = (void *)SUNXI_CPUCFG_BASE + SUN7I_CPU1_PWR_CLAMP;
142 pwroff = (void *)SUNXI_CPUCFG_BASE + SUN7I_CPU1_PWROFF;
Sam Edwardscbb505c2023-10-11 19:47:53 -0600143 cpu = 0;
144 } else if (IS_ENABLED(CONFIG_MACH_SUN8I_R40)) {
Sam Edwards28c68992023-10-11 19:47:55 -0600145 clamp = (void *)SUNXI_CPUCFG_BASE + SUN8I_R40_PWR_CLAMP(cpu);
146 pwroff = (void *)SUNXI_CPUCFG_BASE + SUN8I_R40_PWROFF;
Sam Edwards21e27f02023-10-11 19:47:56 -0600147 } else if (IS_ENABLED(CONFIG_MACH_SUN8I_R528)) {
148 /* R528 leaves both cores powered up, manages them via reset */
149 return;
Sam Edwardscbb505c2023-10-11 19:47:53 -0600150 } else {
Sam Edwardscbb505c2023-10-11 19:47:53 -0600151 if (IS_ENABLED(CONFIG_MACH_SUN6I) ||
152 IS_ENABLED(CONFIG_MACH_SUN8I_H3))
Sam Edwards28c68992023-10-11 19:47:55 -0600153 clamp = (void *)SUNXI_PRCM_BASE + 0x140 + cpu * 0x4;
Chen-Yu Tsai60e0b182016-06-07 10:54:34 +0800154
Sam Edwards28c68992023-10-11 19:47:55 -0600155 pwroff = (void *)SUNXI_PRCM_BASE + 0x100;
Sam Edwardscbb505c2023-10-11 19:47:53 -0600156 }
Chen-Yu Tsaib1a1fda2017-03-01 11:03:15 +0800157
Sam Edwardscbb505c2023-10-11 19:47:53 -0600158 if (on) {
159 /* Release power clamp */
160 if (clamp)
161 clamp_release(clamp);
Chen-Yu Tsai60e0b182016-06-07 10:54:34 +0800162
Sam Edwardscbb505c2023-10-11 19:47:53 -0600163 /* Clear power gating */
164 clrbits_le32(pwroff, BIT(cpu));
165 } else {
166 /* Set power gating */
167 setbits_le32(pwroff, BIT(cpu));
168
169 /* Activate power clamp */
170 if (clamp)
171 clamp_set(clamp);
172 }
Chen-Yu Tsai60e0b182016-06-07 10:54:34 +0800173}
Chen-Yu Tsai60e0b182016-06-07 10:54:34 +0800174
Sam Edwards1f684b52023-10-11 19:47:54 -0600175static void __secure sunxi_cpu_set_reset(int cpu, bool reset)
Chen-Yu Tsai60e0b182016-06-07 10:54:34 +0800176{
Sam Edwards21e27f02023-10-11 19:47:56 -0600177 if (IS_ENABLED(CONFIG_MACH_SUN8I_R528)) {
178 if (reset)
179 clrbits_le32(SUNXI_CPUCFG_BASE + SUN8I_R528_C0_RST_CTRL,
180 BIT(cpu));
181 else
182 setbits_le32(SUNXI_CPUCFG_BASE + SUN8I_R528_C0_RST_CTRL,
183 BIT(cpu));
184
185 return;
186 }
187
Sam Edwards28c68992023-10-11 19:47:55 -0600188 writel(reset ? 0b00 : 0b11, SUNXI_CPUCFG_BASE + SUNXI_CPU_RST(cpu));
Sam Edwards1f684b52023-10-11 19:47:54 -0600189}
190
191static void __secure sunxi_cpu_set_locking(int cpu, bool lock)
192{
Sam Edwards21e27f02023-10-11 19:47:56 -0600193 if (IS_ENABLED(CONFIG_MACH_SUN8I_R528)) {
194 /* Not required on R528 */
195 return;
196 }
197
Sam Edwards1f684b52023-10-11 19:47:54 -0600198 if (lock)
Sam Edwards28c68992023-10-11 19:47:55 -0600199 clrbits_le32(SUNXI_CPUCFG_BASE + SUNXI_DBG_CTRL1, BIT(cpu));
Sam Edwards1f684b52023-10-11 19:47:54 -0600200 else
Sam Edwards28c68992023-10-11 19:47:55 -0600201 setbits_le32(SUNXI_CPUCFG_BASE + SUNXI_DBG_CTRL1, BIT(cpu));
Sam Edwards1f684b52023-10-11 19:47:54 -0600202}
203
204static bool __secure sunxi_cpu_poll_wfi(int cpu)
205{
Sam Edwards21e27f02023-10-11 19:47:56 -0600206 if (IS_ENABLED(CONFIG_MACH_SUN8I_R528)) {
207 return !!(readl(SUNXI_CPUCFG_BASE + SUN8I_R528_C0_CPU_STATUS) &
208 BIT(SUN8I_R528_C0_STATUS_STANDBYWFI + cpu));
209 }
210
Sam Edwards28c68992023-10-11 19:47:55 -0600211 return !!(readl(SUNXI_CPUCFG_BASE + SUNXI_CPU_STATUS(cpu)) & BIT(2));
Sam Edwards1f684b52023-10-11 19:47:54 -0600212}
213
214static void __secure sunxi_cpu_invalidate_cache(int cpu)
215{
Sam Edwards21e27f02023-10-11 19:47:56 -0600216 if (IS_ENABLED(CONFIG_MACH_SUN8I_R528)) {
217 clrbits_le32(SUNXI_CPUCFG_BASE + SUN8I_R528_C0_CTRL_REG0,
218 BIT(cpu));
219 return;
220 }
221
Sam Edwards28c68992023-10-11 19:47:55 -0600222 clrbits_le32(SUNXI_CPUCFG_BASE + SUNXI_GEN_CTRL, BIT(cpu));
Sam Edwards1f684b52023-10-11 19:47:54 -0600223}
224
225static void __secure sunxi_cpu_power_off(u32 cpuid)
226{
Chen-Yu Tsai60e0b182016-06-07 10:54:34 +0800227 u32 cpu = cpuid & 0x3;
228
229 /* Wait for the core to enter WFI */
Sam Edwards1f684b52023-10-11 19:47:54 -0600230 while (!sunxi_cpu_poll_wfi(cpu))
Chen-Yu Tsai60e0b182016-06-07 10:54:34 +0800231 __mdelay(1);
Chen-Yu Tsai60e0b182016-06-07 10:54:34 +0800232
233 /* Assert reset on target CPU */
Sam Edwards1f684b52023-10-11 19:47:54 -0600234 sunxi_cpu_set_reset(cpu, true);
Chen-Yu Tsai60e0b182016-06-07 10:54:34 +0800235
236 /* Lock CPU (Disable external debug access) */
Sam Edwards1f684b52023-10-11 19:47:54 -0600237 sunxi_cpu_set_locking(cpu, true);
Chen-Yu Tsai60e0b182016-06-07 10:54:34 +0800238
239 /* Power down CPU */
240 sunxi_cpu_set_power(cpuid, false);
241
Sam Edwards1f684b52023-10-11 19:47:54 -0600242 /* Unlock CPU (Reenable external debug access) */
243 sunxi_cpu_set_locking(cpu, false);
Chen-Yu Tsai60e0b182016-06-07 10:54:34 +0800244}
245
246static u32 __secure cp15_read_scr(void)
247{
248 u32 scr;
249
250 asm volatile ("mrc p15, 0, %0, c1, c1, 0" : "=r" (scr));
251
252 return scr;
253}
254
255static void __secure cp15_write_scr(u32 scr)
256{
257 asm volatile ("mcr p15, 0, %0, c1, c1, 0" : : "r" (scr));
Tom Rini3b787ef2016-08-01 18:54:53 -0400258 isb();
Chen-Yu Tsai60e0b182016-06-07 10:54:34 +0800259}
260
261/*
262 * Although this is an FIQ handler, the FIQ is processed in monitor mode,
263 * which means there's no FIQ banked registers. This is the same as IRQ
264 * mode, so use the IRQ attribute to ask the compiler to handler entry
265 * and return.
266 */
267void __secure __irq psci_fiq_enter(void)
268{
269 u32 scr, reg, cpu;
270
271 /* Switch to secure mode */
272 scr = cp15_read_scr();
273 cp15_write_scr(scr & ~BIT(0));
274
275 /* Validate reason based on IAR and acknowledge */
276 reg = readl(GICC_BASE + GICC_IAR);
277
278 /* Skip spurious interrupts 1022 and 1023 */
279 if (reg == 1023 || reg == 1022)
280 goto out;
281
282 /* End of interrupt */
283 writel(reg, GICC_BASE + GICC_EOIR);
Tom Rini3b787ef2016-08-01 18:54:53 -0400284 dsb();
Chen-Yu Tsai60e0b182016-06-07 10:54:34 +0800285
286 /* Get CPU number */
287 cpu = (reg >> 10) & 0x7;
288
289 /* Power off the CPU */
290 sunxi_cpu_power_off(cpu);
291
292out:
293 /* Restore security level */
294 cp15_write_scr(scr);
295}
296
Patrick Delaunay93b114c2018-04-16 10:15:11 +0200297int __secure psci_cpu_on(u32 __always_unused unused, u32 mpidr, u32 pc,
298 u32 context_id)
Chen-Yu Tsai60e0b182016-06-07 10:54:34 +0800299{
Chen-Yu Tsai60e0b182016-06-07 10:54:34 +0800300 u32 cpu = (mpidr & 0x3);
301
Patrick Delaunay93b114c2018-04-16 10:15:11 +0200302 /* store target PC and context id */
303 psci_save(cpu, pc, context_id);
Chen-Yu Tsai60e0b182016-06-07 10:54:34 +0800304
305 /* Set secondary core power on PC */
Sam Edwards1f684b52023-10-11 19:47:54 -0600306 sunxi_cpu_set_entry(cpu, &psci_cpu_entry);
Chen-Yu Tsai60e0b182016-06-07 10:54:34 +0800307
308 /* Assert reset on target CPU */
Sam Edwards1f684b52023-10-11 19:47:54 -0600309 sunxi_cpu_set_reset(cpu, true);
Chen-Yu Tsai60e0b182016-06-07 10:54:34 +0800310
311 /* Invalidate L1 cache */
Sam Edwards1f684b52023-10-11 19:47:54 -0600312 sunxi_cpu_invalidate_cache(cpu);
Chen-Yu Tsai60e0b182016-06-07 10:54:34 +0800313
314 /* Lock CPU (Disable external debug access) */
Sam Edwards1f684b52023-10-11 19:47:54 -0600315 sunxi_cpu_set_locking(cpu, true);
Chen-Yu Tsai60e0b182016-06-07 10:54:34 +0800316
317 /* Power up target CPU */
318 sunxi_cpu_set_power(cpu, true);
319
320 /* De-assert reset on target CPU */
Sam Edwards1f684b52023-10-11 19:47:54 -0600321 sunxi_cpu_set_reset(cpu, false);
Chen-Yu Tsai60e0b182016-06-07 10:54:34 +0800322
Sam Edwards1f684b52023-10-11 19:47:54 -0600323 /* Unlock CPU (Reenable external debug access) */
324 sunxi_cpu_set_locking(cpu, false);
Chen-Yu Tsai60e0b182016-06-07 10:54:34 +0800325
326 return ARM_PSCI_RET_SUCCESS;
327}
328
Patrick Delaunay9c59d862019-07-22 14:19:20 +0200329s32 __secure psci_cpu_off(void)
Chen-Yu Tsai60e0b182016-06-07 10:54:34 +0800330{
331 psci_cpu_off_common();
332
333 /* Ask CPU0 via SGI15 to pull the rug... */
334 writel(BIT(16) | 15, GICD_BASE + GICD_SGIR);
Tom Rini3b787ef2016-08-01 18:54:53 -0400335 dsb();
Chen-Yu Tsai60e0b182016-06-07 10:54:34 +0800336
337 /* Wait to be turned off */
338 while (1)
339 wfi();
340}
341
Chen-Yu Tsai766c22e2016-06-19 12:38:32 +0800342void __secure psci_arch_init(void)
Chen-Yu Tsai60e0b182016-06-07 10:54:34 +0800343{
344 u32 reg;
345
346 /* SGI15 as Group-0 */
347 clrbits_le32(GICD_BASE + GICD_IGROUPRn, BIT(15));
348
349 /* Set SGI15 priority to 0 */
350 writeb(0, GICD_BASE + GICD_IPRIORITYRn + 15);
351
352 /* Be cool with non-secure */
353 writel(0xff, GICC_BASE + GICC_PMR);
354
355 /* Switch FIQEn on */
356 setbits_le32(GICC_BASE + GICC_CTLR, BIT(3));
357
358 reg = cp15_read_scr();
359 reg |= BIT(2); /* Enable FIQ in monitor mode */
360 reg &= ~BIT(0); /* Secure mode */
361 cp15_write_scr(reg);
362}