Commit | Line | Data |
---|---|---|
bca28f8f TF |
1 | /* |
2 | * Copyright (C) 2012 Samsung Electronics. | |
3 | * Kyungmin Park <kyungmin.park@samsung.com> | |
4 | * Tomasz Figa <t.figa@samsung.com> | |
5 | * | |
6 | * This program is free software,you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License version 2 as | |
8 | * published by the Free Software Foundation. | |
9 | */ | |
10 | ||
11 | #include <linux/kernel.h> | |
12 | #include <linux/io.h> | |
13 | #include <linux/init.h> | |
14 | #include <linux/of.h> | |
15 | #include <linux/of_address.h> | |
16 | ||
2b9d9c32 TF |
17 | #include <asm/cacheflush.h> |
18 | #include <asm/cputype.h> | |
bca28f8f | 19 | #include <asm/firmware.h> |
5445b640 | 20 | #include <asm/hardware/cache-l2x0.h> |
2b9d9c32 | 21 | #include <asm/suspend.h> |
bca28f8f | 22 | |
b3205dea | 23 | #include "common.h" |
bca28f8f TF |
24 | #include "smc.h" |
25 | ||
2b9d9c32 TF |
26 | #define EXYNOS_BOOT_ADDR 0x8 |
27 | #define EXYNOS_BOOT_FLAG 0xc | |
28 | ||
a135e201 BZ |
29 | static void exynos_save_cp15(void) |
30 | { | |
31 | /* Save Power control and Diagnostic registers */ | |
32 | asm ("mrc p15, 0, %0, c15, c0, 0\n" | |
33 | "mrc p15, 0, %1, c15, c0, 1\n" | |
34 | : "=r" (cp15_save_power), "=r" (cp15_save_diag) | |
35 | : : "cc"); | |
36 | } | |
37 | ||
0b7778a8 | 38 | static int exynos_do_idle(unsigned long mode) |
bca28f8f | 39 | { |
0b7778a8 BZ |
40 | switch (mode) { |
41 | case FW_DO_IDLE_AFTR: | |
a135e201 BZ |
42 | if (read_cpuid_part() == ARM_CPU_PART_CORTEX_A9) |
43 | exynos_save_cp15(); | |
458ad21d BD |
44 | writel_relaxed(virt_to_phys(exynos_cpu_resume_ns), |
45 | sysram_ns_base_addr + 0x24); | |
46 | writel_relaxed(EXYNOS_AFTR_MAGIC, sysram_ns_base_addr + 0x20); | |
89366409 | 47 | if (soc_is_exynos3250()) { |
af997114 | 48 | flush_cache_all(); |
89366409 BZ |
49 | exynos_smc(SMC_CMD_SAVE, OP_TYPE_CORE, |
50 | SMC_POWERSTATE_IDLE, 0); | |
51 | exynos_smc(SMC_CMD_SHUTDOWN, OP_TYPE_CLUSTER, | |
52 | SMC_POWERSTATE_IDLE, 0); | |
53 | } else | |
54 | exynos_smc(SMC_CMD_CPU0AFTR, 0, 0, 0); | |
0b7778a8 BZ |
55 | break; |
56 | case FW_DO_IDLE_SLEEP: | |
57 | exynos_smc(SMC_CMD_SLEEP, 0, 0, 0); | |
58 | } | |
bca28f8f TF |
59 | return 0; |
60 | } | |
61 | ||
62 | static int exynos_cpu_boot(int cpu) | |
63 | { | |
6457158a CC |
64 | /* |
65 | * Exynos3250 doesn't need to send smc command for secondary CPU boot | |
66 | * because Exynos3250 removes WFE in secure mode. | |
67 | */ | |
68 | if (soc_is_exynos3250()) | |
69 | return 0; | |
70 | ||
989ff3fd KP |
71 | /* |
72 | * The second parameter of SMC_CMD_CPU1BOOT command means CPU id. | |
73 | * But, Exynos4212 has only one secondary CPU so second parameter | |
74 | * isn't used for informing secure firmware about CPU id. | |
75 | */ | |
76 | if (soc_is_exynos4212()) | |
77 | cpu = 0; | |
78 | ||
bca28f8f TF |
79 | exynos_smc(SMC_CMD_CPU1BOOT, cpu, 0, 0); |
80 | return 0; | |
81 | } | |
82 | ||
83 | static int exynos_set_cpu_boot_addr(int cpu, unsigned long boot_addr) | |
84 | { | |
b3205dea SK |
85 | void __iomem *boot_reg; |
86 | ||
87 | if (!sysram_ns_base_addr) | |
88 | return -ENODEV; | |
89 | ||
fe388fac | 90 | boot_reg = sysram_ns_base_addr + 0x1c; |
989ff3fd | 91 | |
35e75645 SK |
92 | /* |
93 | * Almost all Exynos-series of SoCs that run in secure mode don't need | |
94 | * additional offset for every CPU, with Exynos4412 being the only | |
95 | * exception. | |
96 | */ | |
97 | if (soc_is_exynos4412()) | |
98 | boot_reg += 4 * cpu; | |
bca28f8f | 99 | |
458ad21d | 100 | writel_relaxed(boot_addr, boot_reg); |
bca28f8f TF |
101 | return 0; |
102 | } | |
103 | ||
1225ad72 BZ |
104 | static int exynos_get_cpu_boot_addr(int cpu, unsigned long *boot_addr) |
105 | { | |
106 | void __iomem *boot_reg; | |
107 | ||
108 | if (!sysram_ns_base_addr) | |
109 | return -ENODEV; | |
110 | ||
111 | boot_reg = sysram_ns_base_addr + 0x1c; | |
112 | ||
113 | if (soc_is_exynos4412()) | |
114 | boot_reg += 4 * cpu; | |
115 | ||
458ad21d | 116 | *boot_addr = readl_relaxed(boot_reg); |
1225ad72 BZ |
117 | return 0; |
118 | } | |
119 | ||
2b9d9c32 TF |
120 | static int exynos_cpu_suspend(unsigned long arg) |
121 | { | |
122 | flush_cache_all(); | |
123 | outer_flush_all(); | |
124 | ||
125 | exynos_smc(SMC_CMD_SLEEP, 0, 0, 0); | |
126 | ||
127 | pr_info("Failed to suspend the system\n"); | |
128 | writel(0, sysram_ns_base_addr + EXYNOS_BOOT_FLAG); | |
129 | return 1; | |
130 | } | |
131 | ||
132 | static int exynos_suspend(void) | |
133 | { | |
a135e201 BZ |
134 | if (read_cpuid_part() == ARM_CPU_PART_CORTEX_A9) |
135 | exynos_save_cp15(); | |
2b9d9c32 TF |
136 | |
137 | writel(EXYNOS_SLEEP_MAGIC, sysram_ns_base_addr + EXYNOS_BOOT_FLAG); | |
138 | writel(virt_to_phys(exynos_cpu_resume_ns), | |
139 | sysram_ns_base_addr + EXYNOS_BOOT_ADDR); | |
140 | ||
141 | return cpu_suspend(0, exynos_cpu_suspend); | |
142 | } | |
143 | ||
144 | static int exynos_resume(void) | |
145 | { | |
146 | writel(0, sysram_ns_base_addr + EXYNOS_BOOT_FLAG); | |
147 | ||
148 | return 0; | |
149 | } | |
150 | ||
bca28f8f | 151 | static const struct firmware_ops exynos_firmware_ops = { |
03c1b760 | 152 | .do_idle = IS_ENABLED(CONFIG_EXYNOS_CPU_SUSPEND) ? exynos_do_idle : NULL, |
bca28f8f | 153 | .set_cpu_boot_addr = exynos_set_cpu_boot_addr, |
1225ad72 | 154 | .get_cpu_boot_addr = exynos_get_cpu_boot_addr, |
bca28f8f | 155 | .cpu_boot = exynos_cpu_boot, |
03c1b760 AB |
156 | .suspend = IS_ENABLED(CONFIG_PM_SLEEP) ? exynos_suspend : NULL, |
157 | .resume = IS_ENABLED(CONFIG_EXYNOS_CPU_SUSPEND) ? exynos_resume : NULL, | |
bca28f8f TF |
158 | }; |
159 | ||
5445b640 TF |
160 | static void exynos_l2_write_sec(unsigned long val, unsigned reg) |
161 | { | |
162 | static int l2cache_enabled; | |
163 | ||
164 | switch (reg) { | |
165 | case L2X0_CTRL: | |
166 | if (val & L2X0_CTRL_EN) { | |
167 | /* | |
168 | * Before the cache can be enabled, due to firmware | |
169 | * design, SMC_CMD_L2X0INVALL must be called. | |
170 | */ | |
171 | if (!l2cache_enabled) { | |
172 | exynos_smc(SMC_CMD_L2X0INVALL, 0, 0, 0); | |
173 | l2cache_enabled = 1; | |
174 | } | |
175 | } else { | |
176 | l2cache_enabled = 0; | |
177 | } | |
178 | exynos_smc(SMC_CMD_L2X0CTRL, val, 0, 0); | |
179 | break; | |
180 | ||
181 | case L2X0_DEBUG_CTRL: | |
182 | exynos_smc(SMC_CMD_L2X0DEBUG, val, 0, 0); | |
183 | break; | |
184 | ||
185 | default: | |
186 | WARN_ONCE(1, "%s: ignoring write to reg 0x%x\n", __func__, reg); | |
187 | } | |
188 | } | |
189 | ||
190 | static void exynos_l2_configure(const struct l2x0_regs *regs) | |
191 | { | |
192 | exynos_smc(SMC_CMD_L2X0SETUP1, regs->tag_latency, regs->data_latency, | |
193 | regs->prefetch_ctrl); | |
194 | exynos_smc(SMC_CMD_L2X0SETUP2, regs->pwr_ctrl, regs->aux_ctrl, 0); | |
195 | } | |
196 | ||
bca28f8f TF |
197 | void __init exynos_firmware_init(void) |
198 | { | |
4ee1cc79 TF |
199 | struct device_node *nd; |
200 | const __be32 *addr; | |
bca28f8f | 201 | |
4ee1cc79 TF |
202 | nd = of_find_compatible_node(NULL, NULL, |
203 | "samsung,secure-firmware"); | |
204 | if (!nd) | |
205 | return; | |
bca28f8f | 206 | |
4ee1cc79 TF |
207 | addr = of_get_address(nd, 0, NULL, NULL); |
208 | if (!addr) { | |
209 | pr_err("%s: No address specified.\n", __func__); | |
210 | return; | |
bca28f8f TF |
211 | } |
212 | ||
213 | pr_info("Running under secure firmware.\n"); | |
214 | ||
215 | register_firmware_ops(&exynos_firmware_ops); | |
5445b640 TF |
216 | |
217 | /* | |
218 | * Exynos 4 SoCs (based on Cortex A9 and equipped with L2C-310), | |
219 | * running under secure firmware, require certain registers of L2 | |
220 | * cache controller to be written in secure mode. Here .write_sec | |
221 | * callback is provided to perform necessary SMC calls. | |
222 | */ | |
223 | if (IS_ENABLED(CONFIG_CACHE_L2X0) && | |
224 | read_cpuid_part() == ARM_CPU_PART_CORTEX_A9) { | |
225 | outer_cache.write_sec = exynos_l2_write_sec; | |
226 | outer_cache.configure = exynos_l2_configure; | |
227 | } | |
bca28f8f | 228 | } |
dc1b9448 BZ |
229 | |
230 | #define REG_CPU_STATE_ADDR (sysram_ns_base_addr + 0x28) | |
231 | #define BOOT_MODE_MASK 0x1f | |
232 | ||
233 | void exynos_set_boot_flag(unsigned int cpu, unsigned int mode) | |
234 | { | |
235 | unsigned int tmp; | |
236 | ||
458ad21d | 237 | tmp = readl_relaxed(REG_CPU_STATE_ADDR + cpu * 4); |
dc1b9448 BZ |
238 | |
239 | if (mode & BOOT_MODE_MASK) | |
240 | tmp &= ~BOOT_MODE_MASK; | |
241 | ||
242 | tmp |= mode; | |
458ad21d | 243 | writel_relaxed(tmp, REG_CPU_STATE_ADDR + cpu * 4); |
dc1b9448 BZ |
244 | } |
245 | ||
246 | void exynos_clear_boot_flag(unsigned int cpu, unsigned int mode) | |
247 | { | |
248 | unsigned int tmp; | |
249 | ||
458ad21d | 250 | tmp = readl_relaxed(REG_CPU_STATE_ADDR + cpu * 4); |
dc1b9448 | 251 | tmp &= ~mode; |
458ad21d | 252 | writel_relaxed(tmp, REG_CPU_STATE_ADDR + cpu * 4); |
dc1b9448 | 253 | } |