Commit | Line | Data |
---|---|---|
a1d0d98d DG |
1 | /* |
2 | * SCOM support for A2 platforms | |
3 | * | |
4 | * Copyright 2007-2011 Benjamin Herrenschmidt, David Gibson, | |
5 | * Michael Ellerman, IBM Corp. | |
6 | * | |
7 | * This program is free software; you can redistribute it and/or | |
8 | * modify it under the terms of the GNU General Public License | |
9 | * as published by the Free Software Foundation; either version | |
10 | * 2 of the License, or (at your option) any later version. | |
11 | */ | |
12 | ||
13 | #include <linux/cpumask.h> | |
14 | #include <linux/io.h> | |
15 | #include <linux/of.h> | |
16 | #include <linux/spinlock.h> | |
17 | #include <linux/types.h> | |
18 | ||
19 | #include <asm/cputhreads.h> | |
20 | #include <asm/reg_a2.h> | |
21 | #include <asm/scom.h> | |
22 | #include <asm/udbg.h> | |
2751b628 | 23 | #include <asm/code-patching.h> |
a1d0d98d DG |
24 | |
25 | #include "wsp.h" | |
26 | ||
27 | #define SCOM_RAMC 0x2a /* Ram Command */ | |
28 | #define SCOM_RAMC_TGT1_EXT 0x80000000 | |
29 | #define SCOM_RAMC_SRC1_EXT 0x40000000 | |
30 | #define SCOM_RAMC_SRC2_EXT 0x20000000 | |
31 | #define SCOM_RAMC_SRC3_EXT 0x10000000 | |
32 | #define SCOM_RAMC_ENABLE 0x00080000 | |
33 | #define SCOM_RAMC_THREADSEL 0x00060000 | |
34 | #define SCOM_RAMC_EXECUTE 0x00010000 | |
35 | #define SCOM_RAMC_MSR_OVERRIDE 0x00008000 | |
36 | #define SCOM_RAMC_MSR_PR 0x00004000 | |
37 | #define SCOM_RAMC_MSR_GS 0x00002000 | |
38 | #define SCOM_RAMC_FORCE 0x00001000 | |
39 | #define SCOM_RAMC_FLUSH 0x00000800 | |
40 | #define SCOM_RAMC_INTERRUPT 0x00000004 | |
41 | #define SCOM_RAMC_ERROR 0x00000002 | |
42 | #define SCOM_RAMC_DONE 0x00000001 | |
43 | #define SCOM_RAMI 0x29 /* Ram Instruction */ | |
44 | #define SCOM_RAMIC 0x28 /* Ram Instruction and Command */ | |
45 | #define SCOM_RAMIC_INSN 0xffffffff00000000 | |
46 | #define SCOM_RAMD 0x2d /* Ram Data */ | |
47 | #define SCOM_RAMDH 0x2e /* Ram Data High */ | |
48 | #define SCOM_RAMDL 0x2f /* Ram Data Low */ | |
49 | #define SCOM_PCCR0 0x33 /* PC Configuration Register 0 */ | |
50 | #define SCOM_PCCR0_ENABLE_DEBUG 0x80000000 | |
51 | #define SCOM_PCCR0_ENABLE_RAM 0x40000000 | |
52 | #define SCOM_THRCTL 0x30 /* Thread Control and Status */ | |
53 | #define SCOM_THRCTL_T0_STOP 0x80000000 | |
54 | #define SCOM_THRCTL_T1_STOP 0x40000000 | |
55 | #define SCOM_THRCTL_T2_STOP 0x20000000 | |
56 | #define SCOM_THRCTL_T3_STOP 0x10000000 | |
57 | #define SCOM_THRCTL_T0_STEP 0x08000000 | |
58 | #define SCOM_THRCTL_T1_STEP 0x04000000 | |
59 | #define SCOM_THRCTL_T2_STEP 0x02000000 | |
60 | #define SCOM_THRCTL_T3_STEP 0x01000000 | |
61 | #define SCOM_THRCTL_T0_RUN 0x00800000 | |
62 | #define SCOM_THRCTL_T1_RUN 0x00400000 | |
63 | #define SCOM_THRCTL_T2_RUN 0x00200000 | |
64 | #define SCOM_THRCTL_T3_RUN 0x00100000 | |
65 | #define SCOM_THRCTL_T0_PM 0x00080000 | |
66 | #define SCOM_THRCTL_T1_PM 0x00040000 | |
67 | #define SCOM_THRCTL_T2_PM 0x00020000 | |
68 | #define SCOM_THRCTL_T3_PM 0x00010000 | |
69 | #define SCOM_THRCTL_T0_UDE 0x00008000 | |
70 | #define SCOM_THRCTL_T1_UDE 0x00004000 | |
71 | #define SCOM_THRCTL_T2_UDE 0x00002000 | |
72 | #define SCOM_THRCTL_T3_UDE 0x00001000 | |
73 | #define SCOM_THRCTL_ASYNC_DIS 0x00000800 | |
74 | #define SCOM_THRCTL_TB_DIS 0x00000400 | |
75 | #define SCOM_THRCTL_DEC_DIS 0x00000200 | |
76 | #define SCOM_THRCTL_AND 0x31 /* Thread Control and Status */ | |
77 | #define SCOM_THRCTL_OR 0x32 /* Thread Control and Status */ | |
78 | ||
79 | ||
80 | static DEFINE_PER_CPU(scom_map_t, scom_ptrs); | |
81 | ||
82 | static scom_map_t get_scom(int cpu, struct device_node *np, int *first_thread) | |
83 | { | |
84 | scom_map_t scom = per_cpu(scom_ptrs, cpu); | |
85 | int tcpu; | |
86 | ||
87 | if (scom_map_ok(scom)) { | |
88 | *first_thread = 0; | |
89 | return scom; | |
90 | } | |
91 | ||
92 | *first_thread = 1; | |
93 | ||
94 | scom = scom_map_device(np, 0); | |
95 | ||
96 | for (tcpu = cpu_first_thread_sibling(cpu); | |
97 | tcpu <= cpu_last_thread_sibling(cpu); tcpu++) | |
98 | per_cpu(scom_ptrs, tcpu) = scom; | |
99 | ||
100 | /* Hack: for the boot core, this will actually get called on | |
101 | * the second thread up, not the first so our test above will | |
102 | * set first_thread incorrectly. */ | |
103 | if (cpu_first_thread_sibling(cpu) == 0) | |
104 | *first_thread = 0; | |
105 | ||
106 | return scom; | |
107 | } | |
108 | ||
109 | static int a2_scom_ram(scom_map_t scom, int thread, u32 insn, int extmask) | |
110 | { | |
111 | u64 cmd, mask, val; | |
112 | int n = 0; | |
113 | ||
114 | cmd = ((u64)insn << 32) | (((u64)extmask & 0xf) << 28) | |
115 | | ((u64)thread << 17) | SCOM_RAMC_ENABLE | SCOM_RAMC_EXECUTE; | |
116 | mask = SCOM_RAMC_DONE | SCOM_RAMC_INTERRUPT | SCOM_RAMC_ERROR; | |
117 | ||
118 | scom_write(scom, SCOM_RAMIC, cmd); | |
119 | ||
aaa63093 BH |
120 | for (;;) { |
121 | if (scom_read(scom, SCOM_RAMC, &val) != 0) { | |
122 | pr_err("SCOM error on instruction 0x%08x, thread %d\n", | |
123 | insn, thread); | |
124 | return -1; | |
125 | } | |
126 | if (val & mask) | |
127 | break; | |
a1d0d98d DG |
128 | pr_devel("Waiting on RAMC = 0x%llx\n", val); |
129 | if (++n == 3) { | |
130 | pr_err("RAMC timeout on instruction 0x%08x, thread %d\n", | |
131 | insn, thread); | |
132 | return -1; | |
133 | } | |
134 | } | |
135 | ||
136 | if (val & SCOM_RAMC_INTERRUPT) { | |
137 | pr_err("RAMC interrupt on instruction 0x%08x, thread %d\n", | |
138 | insn, thread); | |
139 | return -SCOM_RAMC_INTERRUPT; | |
140 | } | |
141 | ||
142 | if (val & SCOM_RAMC_ERROR) { | |
143 | pr_err("RAMC error on instruction 0x%08x, thread %d\n", | |
144 | insn, thread); | |
145 | return -SCOM_RAMC_ERROR; | |
146 | } | |
147 | ||
148 | return 0; | |
149 | } | |
150 | ||
151 | static int a2_scom_getgpr(scom_map_t scom, int thread, int gpr, int alt, | |
152 | u64 *out_gpr) | |
153 | { | |
154 | int rc; | |
155 | ||
156 | /* or rN, rN, rN */ | |
157 | u32 insn = 0x7c000378 | (gpr << 21) | (gpr << 16) | (gpr << 11); | |
158 | rc = a2_scom_ram(scom, thread, insn, alt ? 0xf : 0x0); | |
159 | if (rc) | |
160 | return rc; | |
161 | ||
aaa63093 | 162 | return scom_read(scom, SCOM_RAMD, out_gpr); |
a1d0d98d DG |
163 | } |
164 | ||
165 | static int a2_scom_getspr(scom_map_t scom, int thread, int spr, u64 *out_spr) | |
166 | { | |
167 | int rc, sprhi, sprlo; | |
168 | u32 insn; | |
169 | ||
170 | sprhi = spr >> 5; | |
171 | sprlo = spr & 0x1f; | |
172 | insn = 0x7c2002a6 | (sprlo << 16) | (sprhi << 11); /* mfspr r1,spr */ | |
173 | ||
174 | if (spr == 0x0ff0) | |
175 | insn = 0x7c2000a6; /* mfmsr r1 */ | |
176 | ||
177 | rc = a2_scom_ram(scom, thread, insn, 0xf); | |
178 | if (rc) | |
179 | return rc; | |
180 | return a2_scom_getgpr(scom, thread, 1, 1, out_spr); | |
181 | } | |
182 | ||
183 | static int a2_scom_setgpr(scom_map_t scom, int thread, int gpr, | |
184 | int alt, u64 val) | |
185 | { | |
186 | u32 lis = 0x3c000000 | (gpr << 21); | |
187 | u32 li = 0x38000000 | (gpr << 21); | |
188 | u32 oris = 0x64000000 | (gpr << 21) | (gpr << 16); | |
189 | u32 ori = 0x60000000 | (gpr << 21) | (gpr << 16); | |
190 | u32 rldicr32 = 0x780007c6 | (gpr << 21) | (gpr << 16); | |
191 | u32 highest = val >> 48; | |
192 | u32 higher = (val >> 32) & 0xffff; | |
193 | u32 high = (val >> 16) & 0xffff; | |
194 | u32 low = val & 0xffff; | |
195 | int lext = alt ? 0x8 : 0x0; | |
196 | int oext = alt ? 0xf : 0x0; | |
197 | int rc = 0; | |
198 | ||
199 | if (highest) | |
200 | rc |= a2_scom_ram(scom, thread, lis | highest, lext); | |
201 | ||
202 | if (higher) { | |
203 | if (highest) | |
204 | rc |= a2_scom_ram(scom, thread, oris | higher, oext); | |
205 | else | |
206 | rc |= a2_scom_ram(scom, thread, li | higher, lext); | |
207 | } | |
208 | ||
209 | if (highest || higher) | |
210 | rc |= a2_scom_ram(scom, thread, rldicr32, oext); | |
211 | ||
212 | if (high) { | |
213 | if (highest || higher) | |
214 | rc |= a2_scom_ram(scom, thread, oris | high, oext); | |
215 | else | |
216 | rc |= a2_scom_ram(scom, thread, lis | high, lext); | |
217 | } | |
218 | ||
219 | if (highest || higher || high) | |
220 | rc |= a2_scom_ram(scom, thread, ori | low, oext); | |
221 | else | |
222 | rc |= a2_scom_ram(scom, thread, li | low, lext); | |
223 | ||
224 | return rc; | |
225 | } | |
226 | ||
227 | static int a2_scom_setspr(scom_map_t scom, int thread, int spr, u64 val) | |
228 | { | |
229 | int sprhi = spr >> 5; | |
230 | int sprlo = spr & 0x1f; | |
231 | /* mtspr spr, r1 */ | |
232 | u32 insn = 0x7c2003a6 | (sprlo << 16) | (sprhi << 11); | |
233 | ||
234 | if (spr == 0x0ff0) | |
235 | insn = 0x7c200124; /* mtmsr r1 */ | |
236 | ||
237 | if (a2_scom_setgpr(scom, thread, 1, 1, val)) | |
238 | return -1; | |
239 | ||
240 | return a2_scom_ram(scom, thread, insn, 0xf); | |
241 | } | |
242 | ||
243 | static int a2_scom_initial_tlb(scom_map_t scom, int thread) | |
244 | { | |
245 | extern u32 a2_tlbinit_code_start[], a2_tlbinit_code_end[]; | |
246 | extern u32 a2_tlbinit_after_iprot_flush[]; | |
247 | extern u32 a2_tlbinit_after_linear_map[]; | |
248 | u32 assoc, entries, i; | |
249 | u64 epn, tlbcfg; | |
250 | u32 *p; | |
251 | int rc; | |
252 | ||
253 | /* Invalidate all entries (including iprot) */ | |
254 | ||
255 | rc = a2_scom_getspr(scom, thread, SPRN_TLB0CFG, &tlbcfg); | |
256 | if (rc) | |
257 | goto scom_fail; | |
258 | entries = tlbcfg & TLBnCFG_N_ENTRY; | |
259 | assoc = (tlbcfg & TLBnCFG_ASSOC) >> 24; | |
260 | epn = 0; | |
261 | ||
262 | /* Set MMUCR2 to enable 4K, 64K, 1M, 16M and 1G pages */ | |
263 | a2_scom_setspr(scom, thread, SPRN_MMUCR2, 0x000a7531); | |
264 | /* Set MMUCR3 to write all thids bit to the TLB */ | |
265 | a2_scom_setspr(scom, thread, SPRN_MMUCR3, 0x0000000f); | |
266 | ||
267 | /* Set MAS1 for 1G page size, and MAS2 to our initial EPN */ | |
268 | a2_scom_setspr(scom, thread, SPRN_MAS1, MAS1_TSIZE(BOOK3E_PAGESZ_1GB)); | |
269 | a2_scom_setspr(scom, thread, SPRN_MAS2, epn); | |
270 | for (i = 0; i < entries; i++) { | |
271 | ||
272 | a2_scom_setspr(scom, thread, SPRN_MAS0, MAS0_ESEL(i % assoc)); | |
273 | ||
274 | /* tlbwe */ | |
275 | rc = a2_scom_ram(scom, thread, 0x7c0007a4, 0); | |
276 | if (rc) | |
277 | goto scom_fail; | |
278 | ||
279 | /* Next entry is new address? */ | |
280 | if((i + 1) % assoc == 0) { | |
281 | epn += (1 << 30); | |
282 | a2_scom_setspr(scom, thread, SPRN_MAS2, epn); | |
283 | } | |
284 | } | |
285 | ||
286 | /* Setup args for linear mapping */ | |
287 | rc = a2_scom_setgpr(scom, thread, 3, 0, MAS0_TLBSEL(0)); | |
288 | if (rc) | |
289 | goto scom_fail; | |
290 | ||
291 | /* Linear mapping */ | |
292 | for (p = a2_tlbinit_code_start; p < a2_tlbinit_after_linear_map; p++) { | |
293 | rc = a2_scom_ram(scom, thread, *p, 0); | |
294 | if (rc) | |
295 | goto scom_fail; | |
296 | } | |
297 | ||
298 | /* | |
299 | * For the boot thread, between the linear mapping and the debug | |
300 | * mappings there is a loop to flush iprot mappings. Ramming doesn't do | |
301 | * branches, but the secondary threads don't need to be nearly as smart | |
302 | * (i.e. we don't need to worry about invalidating the mapping we're | |
303 | * standing on). | |
304 | */ | |
305 | ||
306 | /* Debug mappings. Expects r11 = MAS0 from linear map (set above) */ | |
307 | for (p = a2_tlbinit_after_iprot_flush; p < a2_tlbinit_code_end; p++) { | |
308 | rc = a2_scom_ram(scom, thread, *p, 0); | |
309 | if (rc) | |
310 | goto scom_fail; | |
311 | } | |
312 | ||
313 | scom_fail: | |
314 | if (rc) | |
315 | pr_err("Setting up initial TLB failed, err %d\n", rc); | |
316 | ||
317 | if (rc == -SCOM_RAMC_INTERRUPT) { | |
318 | /* Interrupt, dump some status */ | |
319 | int rc[10]; | |
320 | u64 iar, srr0, srr1, esr, mas0, mas1, mas2, mas7_3, mas8, ccr2; | |
321 | rc[0] = a2_scom_getspr(scom, thread, SPRN_IAR, &iar); | |
322 | rc[1] = a2_scom_getspr(scom, thread, SPRN_SRR0, &srr0); | |
323 | rc[2] = a2_scom_getspr(scom, thread, SPRN_SRR1, &srr1); | |
324 | rc[3] = a2_scom_getspr(scom, thread, SPRN_ESR, &esr); | |
325 | rc[4] = a2_scom_getspr(scom, thread, SPRN_MAS0, &mas0); | |
326 | rc[5] = a2_scom_getspr(scom, thread, SPRN_MAS1, &mas1); | |
327 | rc[6] = a2_scom_getspr(scom, thread, SPRN_MAS2, &mas2); | |
328 | rc[7] = a2_scom_getspr(scom, thread, SPRN_MAS7_MAS3, &mas7_3); | |
329 | rc[8] = a2_scom_getspr(scom, thread, SPRN_MAS8, &mas8); | |
330 | rc[9] = a2_scom_getspr(scom, thread, SPRN_A2_CCR2, &ccr2); | |
331 | pr_err(" -> retreived IAR =0x%llx (err %d)\n", iar, rc[0]); | |
332 | pr_err(" retreived SRR0=0x%llx (err %d)\n", srr0, rc[1]); | |
333 | pr_err(" retreived SRR1=0x%llx (err %d)\n", srr1, rc[2]); | |
334 | pr_err(" retreived ESR =0x%llx (err %d)\n", esr, rc[3]); | |
335 | pr_err(" retreived MAS0=0x%llx (err %d)\n", mas0, rc[4]); | |
336 | pr_err(" retreived MAS1=0x%llx (err %d)\n", mas1, rc[5]); | |
337 | pr_err(" retreived MAS2=0x%llx (err %d)\n", mas2, rc[6]); | |
338 | pr_err(" retreived MS73=0x%llx (err %d)\n", mas7_3, rc[7]); | |
339 | pr_err(" retreived MAS8=0x%llx (err %d)\n", mas8, rc[8]); | |
340 | pr_err(" retreived CCR2=0x%llx (err %d)\n", ccr2, rc[9]); | |
341 | } | |
342 | ||
343 | return rc; | |
344 | } | |
345 | ||
cad5cef6 | 346 | int a2_scom_startup_cpu(unsigned int lcpu, int thr_idx, struct device_node *np) |
a1d0d98d DG |
347 | { |
348 | u64 init_iar, init_msr, init_ccr2; | |
349 | unsigned long start_here; | |
350 | int rc, core_setup; | |
351 | scom_map_t scom; | |
352 | u64 pccr0; | |
353 | ||
354 | scom = get_scom(lcpu, np, &core_setup); | |
355 | if (!scom) { | |
356 | printk(KERN_ERR "Couldn't map SCOM for CPU%d\n", lcpu); | |
357 | return -1; | |
358 | } | |
359 | ||
360 | pr_devel("Bringing up CPU%d using SCOM...\n", lcpu); | |
361 | ||
aaa63093 BH |
362 | if (scom_read(scom, SCOM_PCCR0, &pccr0) != 0) { |
363 | printk(KERN_ERR "XSCOM failure readng PCCR0 on CPU%d\n", lcpu); | |
364 | return -1; | |
365 | } | |
a1d0d98d DG |
366 | scom_write(scom, SCOM_PCCR0, pccr0 | SCOM_PCCR0_ENABLE_DEBUG | |
367 | SCOM_PCCR0_ENABLE_RAM); | |
368 | ||
369 | /* Stop the thead with THRCTL. If we are setting up the TLB we stop all | |
370 | * threads. We also disable asynchronous interrupts while RAMing. | |
371 | */ | |
372 | if (core_setup) | |
373 | scom_write(scom, SCOM_THRCTL_OR, | |
374 | SCOM_THRCTL_T0_STOP | | |
375 | SCOM_THRCTL_T1_STOP | | |
376 | SCOM_THRCTL_T2_STOP | | |
377 | SCOM_THRCTL_T3_STOP | | |
378 | SCOM_THRCTL_ASYNC_DIS); | |
379 | else | |
380 | scom_write(scom, SCOM_THRCTL_OR, SCOM_THRCTL_T0_STOP >> thr_idx); | |
381 | ||
382 | /* Flush its pipeline just in case */ | |
383 | scom_write(scom, SCOM_RAMC, ((u64)thr_idx << 17) | | |
384 | SCOM_RAMC_FLUSH | SCOM_RAMC_ENABLE); | |
385 | ||
386 | a2_scom_getspr(scom, thr_idx, SPRN_IAR, &init_iar); | |
387 | a2_scom_getspr(scom, thr_idx, 0x0ff0, &init_msr); | |
388 | a2_scom_getspr(scom, thr_idx, SPRN_A2_CCR2, &init_ccr2); | |
389 | ||
390 | /* Set MSR to MSR_CM (0x0ff0 is magic value for MSR_CM) */ | |
391 | rc = a2_scom_setspr(scom, thr_idx, 0x0ff0, MSR_CM); | |
392 | if (rc) { | |
393 | pr_err("Failed to set MSR ! err %d\n", rc); | |
394 | return rc; | |
395 | } | |
396 | ||
397 | /* RAM in an sync/isync for the sake of it */ | |
398 | a2_scom_ram(scom, thr_idx, 0x7c0004ac, 0); | |
399 | a2_scom_ram(scom, thr_idx, 0x4c00012c, 0); | |
400 | ||
401 | if (core_setup) { | |
402 | pr_devel("CPU%d is first thread in core, initializing TLB...\n", | |
403 | lcpu); | |
404 | rc = a2_scom_initial_tlb(scom, thr_idx); | |
405 | if (rc) | |
406 | goto fail; | |
407 | } | |
408 | ||
2751b628 | 409 | start_here = ppc_function_entry(core_setup ? generic_secondary_smp_init |
a1d0d98d DG |
410 | : generic_secondary_thread_init); |
411 | pr_devel("CPU%d entry point at 0x%lx...\n", lcpu, start_here); | |
412 | ||
413 | rc |= a2_scom_setspr(scom, thr_idx, SPRN_IAR, start_here); | |
414 | rc |= a2_scom_setgpr(scom, thr_idx, 3, 0, | |
415 | get_hard_smp_processor_id(lcpu)); | |
416 | /* | |
417 | * Tell book3e_secondary_core_init not to set up the TLB, we've | |
418 | * already done that. | |
419 | */ | |
420 | rc |= a2_scom_setgpr(scom, thr_idx, 4, 0, 1); | |
421 | ||
422 | rc |= a2_scom_setspr(scom, thr_idx, SPRN_TENS, 0x1 << thr_idx); | |
423 | ||
424 | scom_write(scom, SCOM_RAMC, 0); | |
425 | scom_write(scom, SCOM_THRCTL_AND, ~(SCOM_THRCTL_T0_STOP >> thr_idx)); | |
426 | scom_write(scom, SCOM_PCCR0, pccr0); | |
427 | fail: | |
428 | pr_devel(" SCOM initialization %s\n", rc ? "failed" : "succeeded"); | |
429 | if (rc) { | |
430 | pr_err("Old IAR=0x%08llx MSR=0x%08llx CCR2=0x%08llx\n", | |
431 | init_iar, init_msr, init_ccr2); | |
432 | } | |
433 | ||
434 | return rc; | |
435 | } |