Blame view
arch/arm/mach-exynos/platsmp.c
9.88 KB
347863d47
|
1 2 3 4 5 6 7 8 |
// SPDX-License-Identifier: GPL-2.0 // Copyright (c) 2010-2011 Samsung Electronics Co., Ltd. // http://www.samsung.com // // Cloned from linux/arch/arm/mach-vexpress/platsmp.c // // Copyright (C) 2002 ARM Ltd. // All Rights Reserved |
2b12b5c4f
|
9 10 11 12 |
#include <linux/init.h> #include <linux/errno.h> #include <linux/delay.h> |
2b12b5c4f
|
13 14 15 |
#include <linux/jiffies.h> #include <linux/smp.h> #include <linux/io.h> |
b3205dea8
|
16 |
#include <linux/of_address.h> |
2262d6ef5
|
17 |
#include <linux/soc/samsung/exynos-regs-pmu.h> |
2b12b5c4f
|
18 19 |
#include <asm/cacheflush.h> |
6f0b7c0c6
|
20 |
#include <asm/cp15.h> |
eb50439b9
|
21 |
#include <asm/smp_plat.h> |
2b12b5c4f
|
22 |
#include <asm/smp_scu.h> |
beddf63fc
|
23 |
#include <asm/firmware.h> |
2b12b5c4f
|
24 |
|
06853ae47
|
25 |
#include "common.h" |
7d30e8b38
|
26 |
extern void exynos4_secondary_startup(void); |
2b12b5c4f
|
27 |
|
6213f70e7
|
28 29 |
/* XXX exynos_pen_release is cargo culted code - DO NOT COPY XXX */ volatile int exynos_pen_release = -1; |
6f0b7c0c6
|
30 |
#ifdef CONFIG_HOTPLUG_CPU |
13cfa6c4f
|
31 |
static inline void cpu_leave_lowpower(u32 core_id) |
6f0b7c0c6
|
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
{ unsigned int v; asm volatile( "mrc p15, 0, %0, c1, c0, 0 " " orr %0, %0, %1 " " mcr p15, 0, %0, c1, c0, 0 " " mrc p15, 0, %0, c1, c0, 1 " " orr %0, %0, %2 " " mcr p15, 0, %0, c1, c0, 1 " : "=&r" (v) : "Ir" (CR_C), "Ir" (0x40) : "cc"); } static inline void platform_do_lowpower(unsigned int cpu, int *spurious) { u32 mpidr = cpu_logical_map(cpu); u32 core_id = MPIDR_AFFINITY_LEVEL(mpidr, 0); for (;;) { /* Turn the CPU off on next WFI instruction. */ exynos_cpu_power_down(core_id); wfi(); |
6213f70e7
|
64 |
if (exynos_pen_release == core_id) { |
6f0b7c0c6
|
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
/* * OK, proper wakeup, we're done */ break; } /* * Getting here, means that we have come out of WFI without * having been woken up - this shouldn't happen * * Just note it happening - when we're woken, we can report * its occurrence. */ (*spurious)++; } } #endif /* CONFIG_HOTPLUG_CPU */ |
7310d99ff
|
82 83 84 85 86 87 88 89 90 91 |
/** * exynos_core_power_down : power down the specified cpu * @cpu : the cpu to power down * * Power down the specified cpu. The sequence must be finished by a * call to cpu_do_idle() * */ void exynos_cpu_power_down(int cpu) { |
497ab3b30
|
92 |
u32 core_conf; |
ca489c58e
|
93 |
if (cpu == 0 && (soc_is_exynos5420() || soc_is_exynos5800())) { |
adc548d77
|
94 95 96 97 98 99 100 101 102 103 |
/* * Bypass power down for CPU0 during suspend. Check for * the SYS_PWR_REG value to decide if we are suspending * the system. */ int val = pmu_raw_readl(EXYNOS5_ARM_CORE0_SYS_PWR_REG); if (!(val & S5P_CORE_LOCAL_PWR_EN)) return; } |
497ab3b30
|
104 105 106 107 |
core_conf = pmu_raw_readl(EXYNOS_ARM_CORE_CONFIGURATION(cpu)); core_conf &= ~S5P_CORE_LOCAL_PWR_EN; pmu_raw_writel(core_conf, EXYNOS_ARM_CORE_CONFIGURATION(cpu)); |
7310d99ff
|
108 109 110 111 112 113 114 115 116 117 |
} /** * exynos_cpu_power_up : power up the specified cpu * @cpu : the cpu to power up * * Power up the specified cpu */ void exynos_cpu_power_up(int cpu) { |
497ab3b30
|
118 119 120 121 122 123 |
u32 core_conf = S5P_CORE_LOCAL_PWR_EN; if (soc_is_exynos3250()) core_conf |= S5P_CORE_AUTOWAKEUP_EN; pmu_raw_writel(core_conf, |
944483d03
|
124 |
EXYNOS_ARM_CORE_CONFIGURATION(cpu)); |
7310d99ff
|
125 126 127 128 129 130 131 132 133 |
} /** * exynos_cpu_power_state : returns the power state of the cpu * @cpu : the cpu to retrieve the power state from * */ int exynos_cpu_power_state(int cpu) { |
944483d03
|
134 |
return (pmu_raw_readl(EXYNOS_ARM_CORE_STATUS(cpu)) & |
7310d99ff
|
135 136 137 138 139 140 141 142 143 |
S5P_CORE_LOCAL_PWR_EN); } /** * exynos_cluster_power_down : power down the specified cluster * @cluster : the cluster to power down */ void exynos_cluster_power_down(int cluster) { |
944483d03
|
144 |
pmu_raw_writel(0, EXYNOS_COMMON_CONFIGURATION(cluster)); |
7310d99ff
|
145 146 147 148 149 150 151 152 |
} /** * exynos_cluster_power_up : power up the specified cluster * @cluster : the cluster to power up */ void exynos_cluster_power_up(int cluster) { |
944483d03
|
153 154 |
pmu_raw_writel(S5P_CORE_LOCAL_PWR_EN, EXYNOS_COMMON_CONFIGURATION(cluster)); |
7310d99ff
|
155 156 157 158 159 160 161 162 163 |
} /** * exynos_cluster_power_state : returns the power state of the cluster * @cluster : the cluster to retrieve the power state from * */ int exynos_cluster_power_state(int cluster) { |
944483d03
|
164 165 |
return (pmu_raw_readl(EXYNOS_COMMON_STATUS(cluster)) & S5P_CORE_LOCAL_PWR_EN); |
7310d99ff
|
166 |
} |
3c33710b4
|
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 |
/** * exynos_scu_enable : enables SCU for Cortex-A9 based system */ void exynos_scu_enable(void) { struct device_node *np; static void __iomem *scu_base; if (!scu_base) { np = of_find_compatible_node(NULL, NULL, "arm,cortex-a9-scu"); if (np) { scu_base = of_iomap(np, 0); of_node_put(np); } else { scu_base = ioremap(scu_a9_get_base(), SZ_4K); } } scu_enable(scu_base); } |
af9971144
|
186 |
static void __iomem *cpu_boot_reg_base(void) |
1f054f52e
|
187 |
{ |
edaff7e1c
|
188 |
if (soc_is_exynos4210() && exynos_rev() == EXYNOS4210_REV_1_1) |
2e94ac428
|
189 |
return pmu_base_addr + S5P_INFORM5; |
b3205dea8
|
190 |
return sysram_base_addr; |
1f054f52e
|
191 192 193 194 195 196 197 |
} static inline void __iomem *cpu_boot_reg(int cpu) { void __iomem *boot_reg; boot_reg = cpu_boot_reg_base(); |
b3205dea8
|
198 |
if (!boot_reg) |
2cc6b8135
|
199 |
return IOMEM_ERR_PTR(-ENODEV); |
1f054f52e
|
200 201 |
if (soc_is_exynos4412()) boot_reg += 4*cpu; |
86c6f1488
|
202 |
else if (soc_is_exynos5420() || soc_is_exynos5800()) |
1580be3d3
|
203 |
boot_reg += 4; |
1f054f52e
|
204 205 |
return boot_reg; } |
911c29b0e
|
206 |
|
2b12b5c4f
|
207 |
/* |
b588aaec6
|
208 209 210 211 |
* Set wake up by local power mode and execute software reset for given core. * * Currently this is needed only when booting secondary CPU on Exynos3250. */ |
af9971144
|
212 |
void exynos_core_restart(u32 core_id) |
b588aaec6
|
213 |
{ |
98a3308ea
|
214 |
unsigned int timeout = 16; |
b588aaec6
|
215 216 217 218 |
u32 val; if (!of_machine_is_compatible("samsung,exynos3250")) return; |
98a3308ea
|
219 220 |
while (timeout && !pmu_raw_readl(S5P_PMU_SPARE2)) { timeout--; |
497ab3b30
|
221 |
udelay(10); |
98a3308ea
|
222 223 224 225 226 227 |
} if (timeout == 0) { pr_err("cpu core %u restart failed ", core_id); return; } |
497ab3b30
|
228 |
udelay(10); |
b588aaec6
|
229 230 231 |
val = pmu_raw_readl(EXYNOS_ARM_CORE_STATUS(core_id)); val |= S5P_CORE_WAKEUP_FROM_LOCAL_CFG; pmu_raw_writel(val, EXYNOS_ARM_CORE_STATUS(core_id)); |
b588aaec6
|
232 233 234 235 |
pmu_raw_writel(EXYNOS_CORE_PO_RESET(core_id), EXYNOS_SWRESET); } /* |
6213f70e7
|
236 237 238 239 |
* XXX CARGO CULTED CODE - DO NOT COPY XXX * * Write exynos_pen_release in a way that is guaranteed to be visible to * all observers, irrespective of whether they're taking part in coherency |
3705ff6da
|
240 241 |
* or not. This is necessary for the hotplug code to work reliably. */ |
6213f70e7
|
242 |
static void exynos_write_pen_release(int val) |
3705ff6da
|
243 |
{ |
6213f70e7
|
244 |
exynos_pen_release = val; |
3705ff6da
|
245 |
smp_wmb(); |
6213f70e7
|
246 |
sync_cache_w(&exynos_pen_release); |
3705ff6da
|
247 |
} |
2b12b5c4f
|
248 |
static DEFINE_SPINLOCK(boot_lock); |
8bd26e3a7
|
249 |
static void exynos_secondary_init(unsigned int cpu) |
2b12b5c4f
|
250 |
{ |
2b12b5c4f
|
251 |
/* |
2b12b5c4f
|
252 253 254 |
* let the primary processor know we're out of the * pen, then head off into the C entry point */ |
6213f70e7
|
255 |
exynos_write_pen_release(-1); |
2b12b5c4f
|
256 257 258 259 260 261 262 |
/* * Synchronise with the boot thread. */ spin_lock(&boot_lock); spin_unlock(&boot_lock); } |
af9971144
|
263 |
int exynos_set_boot_addr(u32 core_id, unsigned long boot_addr) |
955d4cf82
|
264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 |
{ int ret; /* * Try to set boot address using firmware first * and fall back to boot register if it fails. */ ret = call_firmware_op(set_cpu_boot_addr, core_id, boot_addr); if (ret && ret != -ENOSYS) goto fail; if (ret == -ENOSYS) { void __iomem *boot_reg = cpu_boot_reg(core_id); if (IS_ERR(boot_reg)) { ret = PTR_ERR(boot_reg); goto fail; } |
458ad21df
|
281 |
writel_relaxed(boot_addr, boot_reg); |
955d4cf82
|
282 283 284 285 286 |
ret = 0; } fail: return ret; } |
af9971144
|
287 |
int exynos_get_boot_addr(u32 core_id, unsigned long *boot_addr) |
1225ad728
|
288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 |
{ int ret; /* * Try to get boot address using firmware first * and fall back to boot register if it fails. */ ret = call_firmware_op(get_cpu_boot_addr, core_id, boot_addr); if (ret && ret != -ENOSYS) goto fail; if (ret == -ENOSYS) { void __iomem *boot_reg = cpu_boot_reg(core_id); if (IS_ERR(boot_reg)) { ret = PTR_ERR(boot_reg); goto fail; } |
458ad21df
|
305 |
*boot_addr = readl_relaxed(boot_reg); |
1225ad728
|
306 307 308 309 310 |
ret = 0; } fail: return ret; } |
8bd26e3a7
|
311 |
static int exynos_boot_secondary(unsigned int cpu, struct task_struct *idle) |
2b12b5c4f
|
312 313 |
{ unsigned long timeout; |
9637f30e6
|
314 315 |
u32 mpidr = cpu_logical_map(cpu); u32 core_id = MPIDR_AFFINITY_LEVEL(mpidr, 0); |
b3205dea8
|
316 |
int ret = -ENOSYS; |
2b12b5c4f
|
317 318 319 320 321 322 323 324 325 326 |
/* * Set synchronisation state between this boot processor * and the secondary one */ spin_lock(&boot_lock); /* * The secondary processor is waiting to be released from * the holding pen - release it, then wait for it to flag |
6213f70e7
|
327 |
* that it has been released by resetting exynos_pen_release. |
2b12b5c4f
|
328 |
* |
6213f70e7
|
329 |
* Note that "exynos_pen_release" is the hardware CPU core ID, whereas |
2b12b5c4f
|
330 331 |
* "cpu" is Linux's internal ID. */ |
6213f70e7
|
332 |
exynos_write_pen_release(core_id); |
2b12b5c4f
|
333 |
|
9637f30e6
|
334 335 |
if (!exynos_cpu_power_state(core_id)) { exynos_cpu_power_up(core_id); |
911c29b0e
|
336 337 338 |
timeout = 10; /* wait max 10 ms until cpu1 is on */ |
9637f30e6
|
339 340 |
while (exynos_cpu_power_state(core_id) != S5P_CORE_LOCAL_PWR_EN) { |
4bdf2f3f2
|
341 |
if (timeout == 0) |
911c29b0e
|
342 |
break; |
4bdf2f3f2
|
343 |
timeout--; |
911c29b0e
|
344 345 346 347 348 349 350 351 352 |
mdelay(1); } if (timeout == 0) { printk(KERN_ERR "cpu1 power enable failed"); spin_unlock(&boot_lock); return -ETIMEDOUT; } } |
b588aaec6
|
353 354 |
exynos_core_restart(core_id); |
2b12b5c4f
|
355 356 357 358 359 |
/* * Send the secondary CPU a soft interrupt, thereby causing * the boot monitor to read the system wide flags register, * and branch to the address found there. */ |
2b12b5c4f
|
360 361 362 |
timeout = jiffies + (1 * HZ); while (time_before(jiffies, timeout)) { |
beddf63fc
|
363 |
unsigned long boot_addr; |
2b12b5c4f
|
364 |
smp_rmb(); |
911c29b0e
|
365 |
|
64fc2a947
|
366 |
boot_addr = __pa_symbol(exynos4_secondary_startup); |
beddf63fc
|
367 |
|
955d4cf82
|
368 369 |
ret = exynos_set_boot_addr(core_id, boot_addr); if (ret) |
b3205dea8
|
370 |
goto fail; |
beddf63fc
|
371 |
|
9637f30e6
|
372 |
call_firmware_op(cpu_boot, core_id); |
beddf63fc
|
373 |
|
497ab3b30
|
374 375 376 377 |
if (soc_is_exynos3250()) dsb_sev(); else arch_send_wakeup_ipi_mask(cpumask_of(cpu)); |
911c29b0e
|
378 |
|
6213f70e7
|
379 |
if (exynos_pen_release == -1) |
2b12b5c4f
|
380 381 382 383 |
break; udelay(10); } |
6213f70e7
|
384 |
if (exynos_pen_release != -1) |
9f294c178
|
385 |
ret = -ETIMEDOUT; |
2b12b5c4f
|
386 387 388 389 |
/* * now the secondary core is starting up let it run its * calibrations, then wait for it to finish */ |
b3205dea8
|
390 |
fail: |
2b12b5c4f
|
391 |
spin_unlock(&boot_lock); |
6213f70e7
|
392 |
return exynos_pen_release != -1 ? ret : 0; |
2b12b5c4f
|
393 |
} |
06853ae47
|
394 |
static void __init exynos_smp_prepare_cpus(unsigned int max_cpus) |
2b12b5c4f
|
395 |
{ |
1754c42e3
|
396 |
exynos_sysram_init(); |
6f024978e
|
397 |
exynos_set_delayed_reset_assertion(true); |
af040ffc9
|
398 |
if (read_cpuid_part() == ARM_CPU_PART_CORTEX_A9) |
3c33710b4
|
399 |
exynos_scu_enable(); |
2b12b5c4f
|
400 |
} |
06853ae47
|
401 |
|
6f0b7c0c6
|
402 403 404 405 406 407 |
#ifdef CONFIG_HOTPLUG_CPU /* * platform-specific code to shutdown a CPU * * Called with IRQs disabled */ |
27b9ee852
|
408 |
static void exynos_cpu_die(unsigned int cpu) |
6f0b7c0c6
|
409 410 |
{ int spurious = 0; |
13cfa6c4f
|
411 412 |
u32 mpidr = cpu_logical_map(cpu); u32 core_id = MPIDR_AFFINITY_LEVEL(mpidr, 0); |
6f0b7c0c6
|
413 414 415 416 417 418 419 420 421 |
v7_exit_coherency_flush(louis); platform_do_lowpower(cpu, &spurious); /* * bring this CPU back into the world of cache * coherency, and then restore interrupts */ |
13cfa6c4f
|
422 |
cpu_leave_lowpower(core_id); |
6f0b7c0c6
|
423 424 425 426 427 428 |
if (spurious) pr_warn("CPU%u: %u spurious wakeup calls ", cpu, spurious); } #endif /* CONFIG_HOTPLUG_CPU */ |
75305275a
|
429 |
const struct smp_operations exynos_smp_ops __initconst = { |
06853ae47
|
430 431 432 433 434 435 436 |
.smp_prepare_cpus = exynos_smp_prepare_cpus, .smp_secondary_init = exynos_secondary_init, .smp_boot_secondary = exynos_boot_secondary, #ifdef CONFIG_HOTPLUG_CPU .cpu_die = exynos_cpu_die, #endif }; |