diff options
Diffstat (limited to 'arch/arm/mach-exynos/mcpm-exynos.c')
-rw-r--r-- | arch/arm/mach-exynos/mcpm-exynos.c | 357 |
1 files changed, 357 insertions, 0 deletions
diff --git a/arch/arm/mach-exynos/mcpm-exynos.c b/arch/arm/mach-exynos/mcpm-exynos.c new file mode 100644 index 000000000000..0498d0b887ef --- /dev/null +++ b/arch/arm/mach-exynos/mcpm-exynos.c | |||
@@ -0,0 +1,357 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2014 Samsung Electronics Co., Ltd. | ||
3 | * http://www.samsung.com | ||
4 | * | ||
5 | * arch/arm/mach-exynos/mcpm-exynos.c | ||
6 | * | ||
7 | * Based on arch/arm/mach-vexpress/dcscb.c | ||
8 | * | ||
9 | * This program is free software; you can redistribute it and/or modify | ||
10 | * it under the terms of the GNU General Public License version 2 as | ||
11 | * published by the Free Software Foundation. | ||
12 | */ | ||
13 | |||
14 | #include <linux/arm-cci.h> | ||
15 | #include <linux/delay.h> | ||
16 | #include <linux/io.h> | ||
17 | #include <linux/of_address.h> | ||
18 | |||
19 | #include <asm/cputype.h> | ||
20 | #include <asm/cp15.h> | ||
21 | #include <asm/mcpm.h> | ||
22 | |||
23 | #include "regs-pmu.h" | ||
24 | #include "common.h" | ||
25 | |||
26 | #define EXYNOS5420_CPUS_PER_CLUSTER 4 | ||
27 | #define EXYNOS5420_NR_CLUSTERS 2 | ||
28 | #define MCPM_BOOT_ADDR_OFFSET 0x1c | ||
29 | |||
30 | /* | ||
31 | * The common v7_exit_coherency_flush API could not be used because of the | ||
32 | * Erratum 799270 workaround. This macro is the same as the common one (in | ||
33 | * arch/arm/include/asm/cacheflush.h) except for the erratum handling. | ||
34 | */ | ||
35 | #define exynos_v7_exit_coherency_flush(level) \ | ||
36 | asm volatile( \ | ||
37 | "stmfd sp!, {fp, ip}\n\t"\ | ||
38 | "mrc p15, 0, r0, c1, c0, 0 @ get SCTLR\n\t" \ | ||
39 | "bic r0, r0, #"__stringify(CR_C)"\n\t" \ | ||
40 | "mcr p15, 0, r0, c1, c0, 0 @ set SCTLR\n\t" \ | ||
41 | "isb\n\t"\ | ||
42 | "bl v7_flush_dcache_"__stringify(level)"\n\t" \ | ||
43 | "clrex\n\t"\ | ||
44 | "mrc p15, 0, r0, c1, c0, 1 @ get ACTLR\n\t" \ | ||
45 | "bic r0, r0, #(1 << 6) @ disable local coherency\n\t" \ | ||
46 | /* Dummy Load of a device register to avoid Erratum 799270 */ \ | ||
47 | "ldr r4, [%0]\n\t" \ | ||
48 | "and r4, r4, #0\n\t" \ | ||
49 | "orr r0, r0, r4\n\t" \ | ||
50 | "mcr p15, 0, r0, c1, c0, 1 @ set ACTLR\n\t" \ | ||
51 | "isb\n\t" \ | ||
52 | "dsb\n\t" \ | ||
53 | "ldmfd sp!, {fp, ip}" \ | ||
54 | : \ | ||
55 | : "Ir" (S5P_INFORM0) \ | ||
56 | : "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", \ | ||
57 | "r9", "r10", "lr", "memory") | ||
58 | |||
59 | /* | ||
60 | * We can't use regular spinlocks. In the switcher case, it is possible | ||
61 | * for an outbound CPU to call power_down() after its inbound counterpart | ||
62 | * is already live using the same logical CPU number which trips lockdep | ||
63 | * debugging. | ||
64 | */ | ||
65 | static arch_spinlock_t exynos_mcpm_lock = __ARCH_SPIN_LOCK_UNLOCKED; | ||
66 | static int | ||
67 | cpu_use_count[EXYNOS5420_CPUS_PER_CLUSTER][EXYNOS5420_NR_CLUSTERS]; | ||
68 | |||
69 | #define exynos_cluster_usecnt(cluster) \ | ||
70 | (cpu_use_count[0][cluster] + \ | ||
71 | cpu_use_count[1][cluster] + \ | ||
72 | cpu_use_count[2][cluster] + \ | ||
73 | cpu_use_count[3][cluster]) | ||
74 | |||
75 | #define exynos_cluster_unused(cluster) !exynos_cluster_usecnt(cluster) | ||
76 | |||
77 | static int exynos_cluster_power_control(unsigned int cluster, int enable) | ||
78 | { | ||
79 | unsigned int tries = 100; | ||
80 | unsigned int val; | ||
81 | |||
82 | if (enable) { | ||
83 | exynos_cluster_power_up(cluster); | ||
84 | val = S5P_CORE_LOCAL_PWR_EN; | ||
85 | } else { | ||
86 | exynos_cluster_power_down(cluster); | ||
87 | val = 0; | ||
88 | } | ||
89 | |||
90 | /* Wait until cluster power control is applied */ | ||
91 | while (tries--) { | ||
92 | if (exynos_cluster_power_state(cluster) == val) | ||
93 | return 0; | ||
94 | |||
95 | cpu_relax(); | ||
96 | } | ||
97 | pr_debug("timed out waiting for cluster %u to power %s\n", cluster, | ||
98 | enable ? "on" : "off"); | ||
99 | |||
100 | return -ETIMEDOUT; | ||
101 | } | ||
102 | |||
103 | static int exynos_power_up(unsigned int cpu, unsigned int cluster) | ||
104 | { | ||
105 | unsigned int cpunr = cpu + (cluster * EXYNOS5420_CPUS_PER_CLUSTER); | ||
106 | int err = 0; | ||
107 | |||
108 | pr_debug("%s: cpu %u cluster %u\n", __func__, cpu, cluster); | ||
109 | if (cpu >= EXYNOS5420_CPUS_PER_CLUSTER || | ||
110 | cluster >= EXYNOS5420_NR_CLUSTERS) | ||
111 | return -EINVAL; | ||
112 | |||
113 | /* | ||
114 | * Since this is called with IRQs enabled, and no arch_spin_lock_irq | ||
115 | * variant exists, we need to disable IRQs manually here. | ||
116 | */ | ||
117 | local_irq_disable(); | ||
118 | arch_spin_lock(&exynos_mcpm_lock); | ||
119 | |||
120 | cpu_use_count[cpu][cluster]++; | ||
121 | if (cpu_use_count[cpu][cluster] == 1) { | ||
122 | bool was_cluster_down = | ||
123 | (exynos_cluster_usecnt(cluster) == 1); | ||
124 | |||
125 | /* | ||
126 | * Turn on the cluster (L2/COMMON) and then power on the | ||
127 | * cores. | ||
128 | */ | ||
129 | if (was_cluster_down) | ||
130 | err = exynos_cluster_power_control(cluster, 1); | ||
131 | |||
132 | if (!err) | ||
133 | exynos_cpu_power_up(cpunr); | ||
134 | else | ||
135 | exynos_cluster_power_control(cluster, 0); | ||
136 | } else if (cpu_use_count[cpu][cluster] != 2) { | ||
137 | /* | ||
138 | * The only possible values are: | ||
139 | * 0 = CPU down | ||
140 | * 1 = CPU (still) up | ||
141 | * 2 = CPU requested to be up before it had a chance | ||
142 | * to actually make itself down. | ||
143 | * Any other value is a bug. | ||
144 | */ | ||
145 | BUG(); | ||
146 | } | ||
147 | |||
148 | arch_spin_unlock(&exynos_mcpm_lock); | ||
149 | local_irq_enable(); | ||
150 | |||
151 | return err; | ||
152 | } | ||
153 | |||
154 | /* | ||
155 | * NOTE: This function requires the stack data to be visible through power down | ||
156 | * and can only be executed on processors like A15 and A7 that hit the cache | ||
157 | * with the C bit clear in the SCTLR register. | ||
158 | */ | ||
159 | static void exynos_power_down(void) | ||
160 | { | ||
161 | unsigned int mpidr, cpu, cluster; | ||
162 | bool last_man = false, skip_wfi = false; | ||
163 | unsigned int cpunr; | ||
164 | |||
165 | mpidr = read_cpuid_mpidr(); | ||
166 | cpu = MPIDR_AFFINITY_LEVEL(mpidr, 0); | ||
167 | cluster = MPIDR_AFFINITY_LEVEL(mpidr, 1); | ||
168 | cpunr = cpu + (cluster * EXYNOS5420_CPUS_PER_CLUSTER); | ||
169 | |||
170 | pr_debug("%s: cpu %u cluster %u\n", __func__, cpu, cluster); | ||
171 | BUG_ON(cpu >= EXYNOS5420_CPUS_PER_CLUSTER || | ||
172 | cluster >= EXYNOS5420_NR_CLUSTERS); | ||
173 | |||
174 | __mcpm_cpu_going_down(cpu, cluster); | ||
175 | |||
176 | arch_spin_lock(&exynos_mcpm_lock); | ||
177 | BUG_ON(__mcpm_cluster_state(cluster) != CLUSTER_UP); | ||
178 | cpu_use_count[cpu][cluster]--; | ||
179 | if (cpu_use_count[cpu][cluster] == 0) { | ||
180 | exynos_cpu_power_down(cpunr); | ||
181 | |||
182 | if (exynos_cluster_unused(cluster)) | ||
183 | /* TODO: Turn off the cluster here to save power. */ | ||
184 | last_man = true; | ||
185 | } else if (cpu_use_count[cpu][cluster] == 1) { | ||
186 | /* | ||
187 | * A power_up request went ahead of us. | ||
188 | * Even if we do not want to shut this CPU down, | ||
189 | * the caller expects a certain state as if the WFI | ||
190 | * was aborted. So let's continue with cache cleaning. | ||
191 | */ | ||
192 | skip_wfi = true; | ||
193 | } else { | ||
194 | BUG(); | ||
195 | } | ||
196 | |||
197 | if (last_man && __mcpm_outbound_enter_critical(cpu, cluster)) { | ||
198 | arch_spin_unlock(&exynos_mcpm_lock); | ||
199 | |||
200 | if (read_cpuid_part_number() == ARM_CPU_PART_CORTEX_A15) { | ||
201 | /* | ||
202 | * On the Cortex-A15 we need to disable | ||
203 | * L2 prefetching before flushing the cache. | ||
204 | */ | ||
205 | asm volatile( | ||
206 | "mcr p15, 1, %0, c15, c0, 3\n\t" | ||
207 | "isb\n\t" | ||
208 | "dsb" | ||
209 | : : "r" (0x400)); | ||
210 | } | ||
211 | |||
212 | /* Flush all cache levels for this cluster. */ | ||
213 | exynos_v7_exit_coherency_flush(all); | ||
214 | |||
215 | /* | ||
216 | * Disable cluster-level coherency by masking | ||
217 | * incoming snoops and DVM messages: | ||
218 | */ | ||
219 | cci_disable_port_by_cpu(mpidr); | ||
220 | |||
221 | __mcpm_outbound_leave_critical(cluster, CLUSTER_DOWN); | ||
222 | } else { | ||
223 | arch_spin_unlock(&exynos_mcpm_lock); | ||
224 | |||
225 | /* Disable and flush the local CPU cache. */ | ||
226 | exynos_v7_exit_coherency_flush(louis); | ||
227 | } | ||
228 | |||
229 | __mcpm_cpu_down(cpu, cluster); | ||
230 | |||
231 | /* Now we are prepared for power-down, do it: */ | ||
232 | if (!skip_wfi) | ||
233 | wfi(); | ||
234 | |||
235 | /* Not dead at this point? Let our caller cope. */ | ||
236 | } | ||
237 | |||
238 | static int exynos_wait_for_powerdown(unsigned int cpu, unsigned int cluster) | ||
239 | { | ||
240 | unsigned int tries = 100; | ||
241 | unsigned int cpunr = cpu + (cluster * EXYNOS5420_CPUS_PER_CLUSTER); | ||
242 | |||
243 | pr_debug("%s: cpu %u cluster %u\n", __func__, cpu, cluster); | ||
244 | BUG_ON(cpu >= EXYNOS5420_CPUS_PER_CLUSTER || | ||
245 | cluster >= EXYNOS5420_NR_CLUSTERS); | ||
246 | |||
247 | /* Wait for the core state to be OFF */ | ||
248 | while (tries--) { | ||
249 | if (ACCESS_ONCE(cpu_use_count[cpu][cluster]) == 0) { | ||
250 | if ((exynos_cpu_power_state(cpunr) == 0)) | ||
251 | return 0; /* success: the CPU is halted */ | ||
252 | } | ||
253 | |||
254 | /* Otherwise, wait and retry: */ | ||
255 | msleep(1); | ||
256 | } | ||
257 | |||
258 | return -ETIMEDOUT; /* timeout */ | ||
259 | } | ||
260 | |||
261 | static const struct mcpm_platform_ops exynos_power_ops = { | ||
262 | .power_up = exynos_power_up, | ||
263 | .power_down = exynos_power_down, | ||
264 | .wait_for_powerdown = exynos_wait_for_powerdown, | ||
265 | }; | ||
266 | |||
267 | static void __init exynos_mcpm_usage_count_init(void) | ||
268 | { | ||
269 | unsigned int mpidr, cpu, cluster; | ||
270 | |||
271 | mpidr = read_cpuid_mpidr(); | ||
272 | cpu = MPIDR_AFFINITY_LEVEL(mpidr, 0); | ||
273 | cluster = MPIDR_AFFINITY_LEVEL(mpidr, 1); | ||
274 | |||
275 | pr_debug("%s: cpu %u cluster %u\n", __func__, cpu, cluster); | ||
276 | BUG_ON(cpu >= EXYNOS5420_CPUS_PER_CLUSTER || | ||
277 | cluster >= EXYNOS5420_NR_CLUSTERS); | ||
278 | |||
279 | cpu_use_count[cpu][cluster] = 1; | ||
280 | } | ||
281 | |||
282 | /* | ||
283 | * Enable cluster-level coherency, in preparation for turning on the MMU. | ||
284 | */ | ||
285 | static void __naked exynos_pm_power_up_setup(unsigned int affinity_level) | ||
286 | { | ||
287 | asm volatile ("\n" | ||
288 | "cmp r0, #1\n" | ||
289 | "bxne lr\n" | ||
290 | "b cci_enable_port_for_self"); | ||
291 | } | ||
292 | |||
293 | static const struct of_device_id exynos_dt_mcpm_match[] = { | ||
294 | { .compatible = "samsung,exynos5420" }, | ||
295 | { .compatible = "samsung,exynos5800" }, | ||
296 | {}, | ||
297 | }; | ||
298 | |||
299 | static int __init exynos_mcpm_init(void) | ||
300 | { | ||
301 | struct device_node *node; | ||
302 | void __iomem *ns_sram_base_addr; | ||
303 | int ret; | ||
304 | |||
305 | node = of_find_matching_node(NULL, exynos_dt_mcpm_match); | ||
306 | if (!node) | ||
307 | return -ENODEV; | ||
308 | of_node_put(node); | ||
309 | |||
310 | if (!cci_probed()) | ||
311 | return -ENODEV; | ||
312 | |||
313 | node = of_find_compatible_node(NULL, NULL, | ||
314 | "samsung,exynos4210-sysram-ns"); | ||
315 | if (!node) | ||
316 | return -ENODEV; | ||
317 | |||
318 | ns_sram_base_addr = of_iomap(node, 0); | ||
319 | of_node_put(node); | ||
320 | if (!ns_sram_base_addr) { | ||
321 | pr_err("failed to map non-secure iRAM base address\n"); | ||
322 | return -ENOMEM; | ||
323 | } | ||
324 | |||
325 | /* | ||
326 | * To increase the stability of KFC reset we need to program | ||
327 | * the PMU SPARE3 register | ||
328 | */ | ||
329 | __raw_writel(EXYNOS5420_SWRESET_KFC_SEL, S5P_PMU_SPARE3); | ||
330 | |||
331 | exynos_mcpm_usage_count_init(); | ||
332 | |||
333 | ret = mcpm_platform_register(&exynos_power_ops); | ||
334 | if (!ret) | ||
335 | ret = mcpm_sync_init(exynos_pm_power_up_setup); | ||
336 | if (ret) { | ||
337 | iounmap(ns_sram_base_addr); | ||
338 | return ret; | ||
339 | } | ||
340 | |||
341 | mcpm_smp_set_ops(); | ||
342 | |||
343 | pr_info("Exynos MCPM support installed\n"); | ||
344 | |||
345 | /* | ||
346 | * Future entries into the kernel can now go | ||
347 | * through the cluster entry vectors. | ||
348 | */ | ||
349 | __raw_writel(virt_to_phys(mcpm_entry_point), | ||
350 | ns_sram_base_addr + MCPM_BOOT_ADDR_OFFSET); | ||
351 | |||
352 | iounmap(ns_sram_base_addr); | ||
353 | |||
354 | return ret; | ||
355 | } | ||
356 | |||
357 | early_initcall(exynos_mcpm_init); | ||