1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * SMP support for SoCs with APMU |
4 | * |
5 | * Copyright (C) 2014 Renesas Electronics Corporation |
6 | * Copyright (C) 2013 Magnus Damm |
7 | */ |
8 | #include <linux/cpu_pm.h> |
9 | #include <linux/delay.h> |
10 | #include <linux/init.h> |
11 | #include <linux/io.h> |
12 | #include <linux/ioport.h> |
13 | #include <linux/of.h> |
14 | #include <linux/of_address.h> |
15 | #include <linux/smp.h> |
16 | #include <linux/suspend.h> |
17 | #include <linux/threads.h> |
18 | #include <asm/cacheflush.h> |
19 | #include <asm/cp15.h> |
20 | #include <asm/proc-fns.h> |
21 | #include <asm/smp_plat.h> |
22 | #include <asm/suspend.h> |
23 | #include "common.h" |
24 | #include "rcar-gen2.h" |
25 | |
26 | static struct { |
27 | void __iomem *iomem; |
28 | int bit; |
29 | } apmu_cpus[NR_CPUS]; |
30 | |
31 | #define WUPCR_OFFS 0x10 /* Wake Up Control Register */ |
32 | #define PSTR_OFFS 0x40 /* Power Status Register */ |
33 | #define CPUNCR_OFFS(n) (0x100 + (0x10 * (n))) |
34 | /* CPUn Power Status Control Register */ |
35 | #define DBGRCR_OFFS 0x180 /* Debug Resource Reset Control Reg. */ |
36 | |
37 | /* Power Status Register */ |
38 | #define CPUNST(r, n) (((r) >> (n * 4)) & 3) /* CPUn Status Bit */ |
39 | #define CPUST_RUN 0 /* Run Mode */ |
40 | #define CPUST_STANDBY 3 /* CoreStandby Mode */ |
41 | |
42 | /* Debug Resource Reset Control Register */ |
43 | #define DBGCPUREN BIT(24) /* CPU Other Reset Request Enable */ |
44 | #define DBGCPUNREN(n) BIT((n) + 20) /* CPUn Reset Request Enable */ |
45 | #define DBGCPUPREN BIT(19) /* CPU Peripheral Reset Req. Enable */ |
46 | |
47 | static int __maybe_unused apmu_power_on(void __iomem *p, int bit) |
48 | { |
49 | /* request power on */ |
50 | writel_relaxed(BIT(bit), p + WUPCR_OFFS); |
51 | |
52 | /* wait for APMU to finish */ |
53 | while (readl_relaxed(p + WUPCR_OFFS) != 0) |
54 | ; |
55 | |
56 | return 0; |
57 | } |
58 | |
59 | static int __maybe_unused apmu_power_off(void __iomem *p, int bit) |
60 | { |
61 | /* request Core Standby for next WFI */ |
62 | writel_relaxed(3, p + CPUNCR_OFFS(bit)); |
63 | return 0; |
64 | } |
65 | |
66 | static int __maybe_unused apmu_power_off_poll(void __iomem *p, int bit) |
67 | { |
68 | int k; |
69 | |
70 | for (k = 0; k < 1000; k++) { |
71 | if (CPUNST(readl_relaxed(p + PSTR_OFFS), bit) == CPUST_STANDBY) |
72 | return 1; |
73 | |
74 | mdelay(1); |
75 | } |
76 | |
77 | return 0; |
78 | } |
79 | |
80 | static int __maybe_unused apmu_wrap(int cpu, int (*fn)(void __iomem *p, int cpu)) |
81 | { |
82 | void __iomem *p = apmu_cpus[cpu].iomem; |
83 | |
84 | return p ? fn(p, apmu_cpus[cpu].bit) : -EINVAL; |
85 | } |
86 | |
87 | #if defined(CONFIG_HOTPLUG_CPU) || defined(CONFIG_SUSPEND) |
88 | /* nicked from arch/arm/mach-exynos/hotplug.c */ |
89 | static inline void cpu_enter_lowpower_a15(void) |
90 | { |
91 | unsigned int v; |
92 | |
93 | asm volatile( |
94 | " mrc p15, 0, %0, c1, c0, 0\n" |
95 | " bic %0, %0, %1\n" |
96 | " mcr p15, 0, %0, c1, c0, 0\n" |
97 | : "=&r" (v) |
98 | : "Ir" (CR_C) |
99 | : "cc" ); |
100 | |
101 | flush_cache_louis(); |
102 | |
103 | asm volatile( |
104 | /* |
105 | * Turn off coherency |
106 | */ |
107 | " mrc p15, 0, %0, c1, c0, 1\n" |
108 | " bic %0, %0, %1\n" |
109 | " mcr p15, 0, %0, c1, c0, 1\n" |
110 | : "=&r" (v) |
111 | : "Ir" (0x40) |
112 | : "cc" ); |
113 | |
114 | isb(); |
115 | dsb(); |
116 | } |
117 | |
118 | static void shmobile_smp_apmu_cpu_shutdown(unsigned int cpu) |
119 | { |
120 | |
121 | /* Select next sleep mode using the APMU */ |
122 | apmu_wrap(cpu, fn: apmu_power_off); |
123 | |
124 | /* Do ARM specific CPU shutdown */ |
125 | cpu_enter_lowpower_a15(); |
126 | } |
127 | #endif |
128 | |
129 | #if defined(CONFIG_HOTPLUG_CPU) |
130 | static void shmobile_smp_apmu_cpu_die(unsigned int cpu) |
131 | { |
132 | /* For this particular CPU deregister boot vector */ |
133 | shmobile_smp_hook(cpu, fn: 0, arg: 0); |
134 | |
135 | /* Shutdown CPU core */ |
136 | shmobile_smp_apmu_cpu_shutdown(cpu); |
137 | |
138 | /* jump to shared mach-shmobile sleep / reset code */ |
139 | shmobile_smp_sleep(); |
140 | } |
141 | |
142 | static int shmobile_smp_apmu_cpu_kill(unsigned int cpu) |
143 | { |
144 | return apmu_wrap(cpu, fn: apmu_power_off_poll); |
145 | } |
146 | #endif |
147 | |
148 | #if defined(CONFIG_SUSPEND) |
149 | static int shmobile_smp_apmu_do_suspend(unsigned long cpu) |
150 | { |
151 | shmobile_smp_hook(cpu, __pa_symbol(cpu_resume), arg: 0); |
152 | shmobile_smp_apmu_cpu_shutdown(cpu); |
153 | cpu_do_idle(); /* WFI selects Core Standby */ |
154 | return 1; |
155 | } |
156 | |
157 | static inline void cpu_leave_lowpower(void) |
158 | { |
159 | unsigned int v; |
160 | |
161 | asm volatile("mrc p15, 0, %0, c1, c0, 0\n" |
162 | " orr %0, %0, %1\n" |
163 | " mcr p15, 0, %0, c1, c0, 0\n" |
164 | " mrc p15, 0, %0, c1, c0, 1\n" |
165 | " orr %0, %0, %2\n" |
166 | " mcr p15, 0, %0, c1, c0, 1\n" |
167 | : "=&r" (v) |
168 | : "Ir" (CR_C), "Ir" (0x40) |
169 | : "cc" ); |
170 | } |
171 | |
172 | static int shmobile_smp_apmu_enter_suspend(suspend_state_t state) |
173 | { |
174 | cpu_suspend(smp_processor_id(), shmobile_smp_apmu_do_suspend); |
175 | cpu_leave_lowpower(); |
176 | return 0; |
177 | } |
178 | |
179 | void __init shmobile_smp_apmu_suspend_init(void) |
180 | { |
181 | shmobile_suspend_ops.enter = shmobile_smp_apmu_enter_suspend; |
182 | } |
183 | #endif |
184 | |
185 | #ifdef CONFIG_SMP |
186 | static void apmu_init_cpu(struct resource *res, int cpu, int bit) |
187 | { |
188 | u32 x; |
189 | |
190 | if ((cpu >= ARRAY_SIZE(apmu_cpus)) || apmu_cpus[cpu].iomem) |
191 | return; |
192 | |
193 | apmu_cpus[cpu].iomem = ioremap(offset: res->start, size: resource_size(res)); |
194 | apmu_cpus[cpu].bit = bit; |
195 | |
196 | pr_debug("apmu ioremap %d %d %pr\n" , cpu, bit, res); |
197 | |
198 | /* Setup for debug mode */ |
199 | x = readl(addr: apmu_cpus[cpu].iomem + DBGRCR_OFFS); |
200 | x |= DBGCPUREN | DBGCPUNREN(bit) | DBGCPUPREN; |
201 | writel(val: x, addr: apmu_cpus[cpu].iomem + DBGRCR_OFFS); |
202 | } |
203 | |
204 | static const struct of_device_id apmu_ids[] = { |
205 | { .compatible = "renesas,apmu" }, |
206 | { /*sentinel*/ } |
207 | }; |
208 | |
209 | static void apmu_parse_dt(void (*fn)(struct resource *res, int cpu, int bit)) |
210 | { |
211 | struct device_node *np_apmu, *np_cpu; |
212 | struct resource res; |
213 | int bit, index; |
214 | |
215 | for_each_matching_node(np_apmu, apmu_ids) { |
216 | /* only enable the cluster that includes the boot CPU */ |
217 | bool is_allowed = false; |
218 | |
219 | for (bit = 0; bit < CONFIG_NR_CPUS; bit++) { |
220 | np_cpu = of_parse_phandle(np: np_apmu, phandle_name: "cpus" , index: bit); |
221 | if (!np_cpu) |
222 | break; |
223 | if (of_cpu_node_to_id(np: np_cpu) == 0) { |
224 | is_allowed = true; |
225 | of_node_put(node: np_cpu); |
226 | break; |
227 | } |
228 | of_node_put(node: np_cpu); |
229 | } |
230 | if (!is_allowed) |
231 | continue; |
232 | |
233 | for (bit = 0; bit < CONFIG_NR_CPUS; bit++) { |
234 | np_cpu = of_parse_phandle(np: np_apmu, phandle_name: "cpus" , index: bit); |
235 | if (!np_cpu) |
236 | break; |
237 | |
238 | index = of_cpu_node_to_id(np: np_cpu); |
239 | if ((index >= 0) && |
240 | !of_address_to_resource(dev: np_apmu, index: 0, r: &res)) |
241 | fn(&res, index, bit); |
242 | |
243 | of_node_put(node: np_cpu); |
244 | } |
245 | } |
246 | } |
247 | |
248 | static void __init shmobile_smp_apmu_setup_boot(void) |
249 | { |
250 | /* install boot code shared by all CPUs */ |
251 | shmobile_boot_fn = __pa_symbol(shmobile_smp_boot); |
252 | shmobile_boot_fn_gen2 = shmobile_boot_fn; |
253 | } |
254 | |
255 | static int shmobile_smp_apmu_boot_secondary(unsigned int cpu, |
256 | struct task_struct *idle) |
257 | { |
258 | /* For this particular CPU register boot vector */ |
259 | shmobile_smp_hook(cpu, __pa_symbol(shmobile_boot_apmu), arg: 0); |
260 | |
261 | return apmu_wrap(cpu, fn: apmu_power_on); |
262 | } |
263 | |
264 | static void __init shmobile_smp_apmu_prepare_cpus_dt(unsigned int max_cpus) |
265 | { |
266 | shmobile_smp_apmu_setup_boot(); |
267 | apmu_parse_dt(fn: apmu_init_cpu); |
268 | rcar_gen2_pm_init(); |
269 | } |
270 | |
271 | static struct smp_operations apmu_smp_ops __initdata = { |
272 | .smp_prepare_cpus = shmobile_smp_apmu_prepare_cpus_dt, |
273 | .smp_boot_secondary = shmobile_smp_apmu_boot_secondary, |
274 | #ifdef CONFIG_HOTPLUG_CPU |
275 | .cpu_can_disable = shmobile_smp_cpu_can_disable, |
276 | .cpu_die = shmobile_smp_apmu_cpu_die, |
277 | .cpu_kill = shmobile_smp_apmu_cpu_kill, |
278 | #endif |
279 | }; |
280 | |
281 | CPU_METHOD_OF_DECLARE(shmobile_smp_apmu, "renesas,apmu" , &apmu_smp_ops); |
282 | #endif /* CONFIG_SMP */ |
283 | |