1 | // SPDX-License-Identifier: GPL-2.0 |
2 | // Copyright (c) 2014 Samsung Electronics Co., Ltd. |
3 | // http://www.samsung.com |
4 | // |
5 | // Based on arch/arm/mach-vexpress/dcscb.c |
6 | |
7 | #include <linux/arm-cci.h> |
8 | #include <linux/delay.h> |
9 | #include <linux/io.h> |
10 | #include <linux/of_address.h> |
11 | #include <linux/syscore_ops.h> |
12 | #include <linux/soc/samsung/exynos-regs-pmu.h> |
13 | |
14 | #include <asm/cputype.h> |
15 | #include <asm/cp15.h> |
16 | #include <asm/mcpm.h> |
17 | #include <asm/smp_plat.h> |
18 | |
19 | #include "common.h" |
20 | |
21 | #define EXYNOS5420_CPUS_PER_CLUSTER 4 |
22 | #define EXYNOS5420_NR_CLUSTERS 2 |
23 | |
24 | #define EXYNOS5420_ENABLE_AUTOMATIC_CORE_DOWN BIT(9) |
25 | #define EXYNOS5420_USE_ARM_CORE_DOWN_STATE BIT(29) |
26 | #define EXYNOS5420_USE_L2_COMMON_UP_STATE BIT(30) |
27 | |
28 | static void __iomem *ns_sram_base_addr __ro_after_init; |
29 | static bool secure_firmware __ro_after_init; |
30 | |
31 | /* |
32 | * The common v7_exit_coherency_flush API could not be used because of the |
33 | * Erratum 799270 workaround. This macro is the same as the common one (in |
34 | * arch/arm/include/asm/cacheflush.h) except for the erratum handling. |
35 | */ |
36 | #define exynos_v7_exit_coherency_flush(level) \ |
37 | asm volatile( \ |
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 | "mrc p15, 0, r0, c1, c0, 1 @ get ACTLR\n\t" \ |
44 | "bic r0, r0, #(1 << 6) @ disable local coherency\n\t" \ |
45 | /* Dummy Load of a device register to avoid Erratum 799270 */ \ |
46 | "ldr r4, [%0]\n\t" \ |
47 | "and r4, r4, #0\n\t" \ |
48 | "orr r0, r0, r4\n\t" \ |
49 | "mcr p15, 0, r0, c1, c0, 1 @ set ACTLR\n\t" \ |
50 | "isb\n\t" \ |
51 | "dsb\n\t" \ |
52 | : \ |
53 | : "Ir" (pmu_base_addr + S5P_INFORM0) \ |
54 | : "r0", "r1", "r2", "r3", "r4", "r5", "r6", \ |
55 | "r9", "r10", "ip", "lr", "memory") |
56 | |
57 | static int exynos_cpu_powerup(unsigned int cpu, unsigned int cluster) |
58 | { |
59 | unsigned int cpunr = cpu + (cluster * EXYNOS5420_CPUS_PER_CLUSTER); |
60 | bool state; |
61 | |
62 | pr_debug("%s: cpu %u cluster %u\n" , __func__, cpu, cluster); |
63 | if (cpu >= EXYNOS5420_CPUS_PER_CLUSTER || |
64 | cluster >= EXYNOS5420_NR_CLUSTERS) |
65 | return -EINVAL; |
66 | |
67 | state = exynos_cpu_power_state(cpu: cpunr); |
68 | exynos_cpu_power_up(cpu: cpunr); |
69 | if (!state && secure_firmware) { |
70 | /* |
71 | * This assumes the cluster number of the big cores(Cortex A15) |
72 | * is 0 and the Little cores(Cortex A7) is 1. |
73 | * When the system was booted from the Little core, |
74 | * they should be reset during power up cpu. |
75 | */ |
76 | if (cluster && |
77 | cluster == MPIDR_AFFINITY_LEVEL(cpu_logical_map(0), 1)) { |
78 | unsigned int timeout = 16; |
79 | |
80 | /* |
81 | * Before we reset the Little cores, we should wait |
82 | * the SPARE2 register is set to 1 because the init |
83 | * codes of the iROM will set the register after |
84 | * initialization. |
85 | */ |
86 | while (timeout && !pmu_raw_readl(S5P_PMU_SPARE2)) { |
87 | timeout--; |
88 | udelay(10); |
89 | } |
90 | |
91 | if (timeout == 0) { |
92 | pr_err("cpu %u cluster %u powerup failed\n" , |
93 | cpu, cluster); |
94 | exynos_cpu_power_down(cpu: cpunr); |
95 | return -ETIMEDOUT; |
96 | } |
97 | |
98 | pmu_raw_writel(EXYNOS5420_KFC_CORE_RESET(cpu), |
99 | EXYNOS_SWRESET); |
100 | } |
101 | } |
102 | |
103 | return 0; |
104 | } |
105 | |
106 | static int exynos_cluster_powerup(unsigned int cluster) |
107 | { |
108 | pr_debug("%s: cluster %u\n" , __func__, cluster); |
109 | if (cluster >= EXYNOS5420_NR_CLUSTERS) |
110 | return -EINVAL; |
111 | |
112 | exynos_cluster_power_up(cluster); |
113 | return 0; |
114 | } |
115 | |
116 | static void exynos_cpu_powerdown_prepare(unsigned int cpu, unsigned int cluster) |
117 | { |
118 | unsigned int cpunr = cpu + (cluster * EXYNOS5420_CPUS_PER_CLUSTER); |
119 | |
120 | pr_debug("%s: cpu %u cluster %u\n" , __func__, cpu, cluster); |
121 | BUG_ON(cpu >= EXYNOS5420_CPUS_PER_CLUSTER || |
122 | cluster >= EXYNOS5420_NR_CLUSTERS); |
123 | exynos_cpu_power_down(cpu: cpunr); |
124 | } |
125 | |
126 | static void exynos_cluster_powerdown_prepare(unsigned int cluster) |
127 | { |
128 | pr_debug("%s: cluster %u\n" , __func__, cluster); |
129 | BUG_ON(cluster >= EXYNOS5420_NR_CLUSTERS); |
130 | exynos_cluster_power_down(cluster); |
131 | } |
132 | |
133 | static void exynos_cpu_cache_disable(void) |
134 | { |
135 | /* Disable and flush the local CPU cache. */ |
136 | exynos_v7_exit_coherency_flush(louis); |
137 | } |
138 | |
139 | static void exynos_cluster_cache_disable(void) |
140 | { |
141 | if (read_cpuid_part() == ARM_CPU_PART_CORTEX_A15) { |
142 | /* |
143 | * On the Cortex-A15 we need to disable |
144 | * L2 prefetching before flushing the cache. |
145 | */ |
146 | asm volatile( |
147 | "mcr p15, 1, %0, c15, c0, 3\n\t" |
148 | "isb\n\t" |
149 | "dsb" |
150 | : : "r" (0x400)); |
151 | } |
152 | |
153 | /* Flush all cache levels for this cluster. */ |
154 | exynos_v7_exit_coherency_flush(all); |
155 | |
156 | /* |
157 | * Disable cluster-level coherency by masking |
158 | * incoming snoops and DVM messages: |
159 | */ |
160 | cci_disable_port_by_cpu(mpidr: read_cpuid_mpidr()); |
161 | } |
162 | |
163 | static int exynos_wait_for_powerdown(unsigned int cpu, unsigned int cluster) |
164 | { |
165 | unsigned int tries = 100; |
166 | unsigned int cpunr = cpu + (cluster * EXYNOS5420_CPUS_PER_CLUSTER); |
167 | |
168 | pr_debug("%s: cpu %u cluster %u\n" , __func__, cpu, cluster); |
169 | BUG_ON(cpu >= EXYNOS5420_CPUS_PER_CLUSTER || |
170 | cluster >= EXYNOS5420_NR_CLUSTERS); |
171 | |
172 | /* Wait for the core state to be OFF */ |
173 | while (tries--) { |
174 | if ((exynos_cpu_power_state(cpu: cpunr) == 0)) |
175 | return 0; /* success: the CPU is halted */ |
176 | |
177 | /* Otherwise, wait and retry: */ |
178 | msleep(msecs: 1); |
179 | } |
180 | |
181 | return -ETIMEDOUT; /* timeout */ |
182 | } |
183 | |
184 | static void exynos_cpu_is_up(unsigned int cpu, unsigned int cluster) |
185 | { |
186 | /* especially when resuming: make sure power control is set */ |
187 | exynos_cpu_powerup(cpu, cluster); |
188 | } |
189 | |
190 | static const struct mcpm_platform_ops exynos_power_ops = { |
191 | .cpu_powerup = exynos_cpu_powerup, |
192 | .cluster_powerup = exynos_cluster_powerup, |
193 | .cpu_powerdown_prepare = exynos_cpu_powerdown_prepare, |
194 | .cluster_powerdown_prepare = exynos_cluster_powerdown_prepare, |
195 | .cpu_cache_disable = exynos_cpu_cache_disable, |
196 | .cluster_cache_disable = exynos_cluster_cache_disable, |
197 | .wait_for_powerdown = exynos_wait_for_powerdown, |
198 | .cpu_is_up = exynos_cpu_is_up, |
199 | }; |
200 | |
201 | /* |
202 | * Enable cluster-level coherency, in preparation for turning on the MMU. |
203 | */ |
204 | static void __naked exynos_pm_power_up_setup(unsigned int affinity_level) |
205 | { |
206 | asm volatile ("\n" |
207 | "cmp r0, #1\n" |
208 | "bxne lr\n" |
209 | "b cci_enable_port_for_self" ); |
210 | } |
211 | |
212 | static const struct of_device_id exynos_dt_mcpm_match[] = { |
213 | { .compatible = "samsung,exynos5420" }, |
214 | { .compatible = "samsung,exynos5800" }, |
215 | {}, |
216 | }; |
217 | |
218 | static void exynos_mcpm_setup_entry_point(void) |
219 | { |
220 | /* |
221 | * U-Boot SPL is hardcoded to jump to the start of ns_sram_base_addr |
222 | * as part of secondary_cpu_start(). Let's redirect it to the |
223 | * mcpm_entry_point(). This is done during both secondary boot-up as |
224 | * well as system resume. |
225 | */ |
226 | __raw_writel(val: 0xe59f0000, addr: ns_sram_base_addr); /* ldr r0, [pc, #0] */ |
227 | __raw_writel(val: 0xe12fff10, addr: ns_sram_base_addr + 4); /* bx r0 */ |
228 | __raw_writel(__pa_symbol(mcpm_entry_point), ns_sram_base_addr + 8); |
229 | } |
230 | |
231 | static struct syscore_ops exynos_mcpm_syscore_ops = { |
232 | .resume = exynos_mcpm_setup_entry_point, |
233 | }; |
234 | |
235 | static int __init exynos_mcpm_init(void) |
236 | { |
237 | struct device_node *node; |
238 | unsigned int value, i; |
239 | int ret; |
240 | |
241 | node = of_find_matching_node(NULL, matches: exynos_dt_mcpm_match); |
242 | if (!node) |
243 | return -ENODEV; |
244 | of_node_put(node); |
245 | |
246 | if (!cci_probed()) |
247 | return -ENODEV; |
248 | |
249 | node = of_find_compatible_node(NULL, NULL, |
250 | compat: "samsung,exynos4210-sysram-ns" ); |
251 | if (!node) |
252 | return -ENODEV; |
253 | |
254 | ns_sram_base_addr = of_iomap(node, index: 0); |
255 | of_node_put(node); |
256 | if (!ns_sram_base_addr) { |
257 | pr_err("failed to map non-secure iRAM base address\n" ); |
258 | return -ENOMEM; |
259 | } |
260 | |
261 | secure_firmware = exynos_secure_firmware_available(); |
262 | |
263 | /* |
264 | * To increase the stability of KFC reset we need to program |
265 | * the PMU SPARE3 register |
266 | */ |
267 | pmu_raw_writel(EXYNOS5420_SWRESET_KFC_SEL, S5P_PMU_SPARE3); |
268 | |
269 | ret = mcpm_platform_register(&exynos_power_ops); |
270 | if (!ret) |
271 | ret = mcpm_sync_init(exynos_pm_power_up_setup); |
272 | if (!ret) |
273 | ret = mcpm_loopback(exynos_cluster_cache_disable); /* turn on the CCI */ |
274 | if (ret) { |
275 | iounmap(addr: ns_sram_base_addr); |
276 | return ret; |
277 | } |
278 | |
279 | mcpm_smp_set_ops(); |
280 | |
281 | pr_info("Exynos MCPM support installed\n" ); |
282 | |
283 | /* |
284 | * On Exynos5420/5800 for the A15 and A7 clusters: |
285 | * |
286 | * EXYNOS5420_ENABLE_AUTOMATIC_CORE_DOWN ensures that all the cores |
287 | * in a cluster are turned off before turning off the cluster L2. |
288 | * |
289 | * EXYNOS5420_USE_ARM_CORE_DOWN_STATE ensures that a cores is powered |
290 | * off before waking it up. |
291 | * |
292 | * EXYNOS5420_USE_L2_COMMON_UP_STATE ensures that cluster L2 will be |
293 | * turned on before the first man is powered up. |
294 | */ |
295 | for (i = 0; i < EXYNOS5420_NR_CLUSTERS; i++) { |
296 | value = pmu_raw_readl(EXYNOS_COMMON_OPTION(i)); |
297 | value |= EXYNOS5420_ENABLE_AUTOMATIC_CORE_DOWN | |
298 | EXYNOS5420_USE_ARM_CORE_DOWN_STATE | |
299 | EXYNOS5420_USE_L2_COMMON_UP_STATE; |
300 | pmu_raw_writel(val: value, EXYNOS_COMMON_OPTION(i)); |
301 | } |
302 | |
303 | exynos_mcpm_setup_entry_point(); |
304 | |
305 | register_syscore_ops(ops: &exynos_mcpm_syscore_ops); |
306 | |
307 | return ret; |
308 | } |
309 | |
310 | early_initcall(exynos_mcpm_init); |
311 | |