OpenWrt – Blame information for rev 4
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
4 | office | 1 | /* |
2 | * linux/arch/arm/mach-cns3xxx/platsmp.c |
||
3 | * |
||
4 | * Copyright (C) 2002 ARM Ltd. |
||
5 | * Copyright 2012 Gateworks Corporation |
||
6 | * Chris Lang <clang@gateworks.com> |
||
7 | * Tim Harvey <tharvey@gateworks.com> |
||
8 | * |
||
9 | * All Rights Reserved |
||
10 | * |
||
11 | * This program is free software; you can redistribute it and/or modify |
||
12 | * it under the terms of the GNU General Public License version 2 as |
||
13 | * published by the Free Software Foundation. |
||
14 | */ |
||
15 | #include <linux/init.h> |
||
16 | #include <linux/errno.h> |
||
17 | #include <linux/delay.h> |
||
18 | #include <linux/device.h> |
||
19 | #include <linux/jiffies.h> |
||
20 | #include <linux/smp.h> |
||
21 | #include <linux/io.h> |
||
22 | |||
23 | #include <asm/cacheflush.h> |
||
24 | #include <asm/smp_scu.h> |
||
25 | #include <asm/unified.h> |
||
26 | #include <asm/fiq.h> |
||
27 | #include <mach/smp.h> |
||
28 | #include "cns3xxx.h" |
||
29 | |||
30 | static struct fiq_handler fh = { |
||
31 | .name = "cns3xxx-fiq" |
||
32 | }; |
||
33 | |||
34 | struct fiq_req { |
||
35 | union { |
||
36 | struct { |
||
37 | const void *addr; |
||
38 | size_t size; |
||
39 | } map; |
||
40 | struct { |
||
41 | const void *addr; |
||
42 | size_t size; |
||
43 | } unmap; |
||
44 | struct { |
||
45 | const void *start; |
||
46 | const void *end; |
||
47 | } flush; |
||
48 | }; |
||
49 | volatile uint flags; |
||
50 | void __iomem *reg; |
||
51 | } ____cacheline_aligned; |
||
52 | |||
53 | extern unsigned int fiq_number[2]; |
||
54 | |||
55 | DEFINE_PER_CPU(struct fiq_req, fiq_data); |
||
56 | |||
57 | #define FIQ_ENABLED 0x80000000 |
||
58 | #define FIQ_GENERATE 0x00010000 |
||
59 | #define CNS3XXX_MAP_AREA 0x01000000 |
||
60 | #define CNS3XXX_UNMAP_AREA 0x02000000 |
||
61 | #define CNS3XXX_FLUSH_RANGE 0x03000000 |
||
62 | |||
63 | extern void cns3xxx_secondary_startup(void); |
||
64 | extern unsigned char cns3xxx_fiq_start, cns3xxx_fiq_end; |
||
65 | |||
66 | #define SCU_CPU_STATUS 0x08 |
||
67 | static void __iomem *scu_base; |
||
68 | |||
69 | static inline void cns3xxx_set_fiq_regs(unsigned int cpu) |
||
70 | { |
||
71 | struct pt_regs FIQ_regs; |
||
72 | struct fiq_req *fiq_req = &per_cpu(fiq_data, !cpu); |
||
73 | |||
74 | FIQ_regs.ARM_r8 = 0; |
||
75 | FIQ_regs.ARM_ip = (unsigned int)fiq_req; |
||
76 | FIQ_regs.ARM_sp = (int) MISC_FIQ_CPU(!cpu); |
||
77 | fiq_req->reg = MISC_FIQ_CPU(!cpu); |
||
78 | |||
79 | set_fiq_regs(&FIQ_regs); |
||
80 | } |
||
81 | |||
82 | static void __init cns3xxx_init_fiq(void) |
||
83 | { |
||
84 | void *fiqhandler_start; |
||
85 | unsigned int fiqhandler_length; |
||
86 | int ret; |
||
87 | |||
88 | fiqhandler_start = &cns3xxx_fiq_start; |
||
89 | fiqhandler_length = &cns3xxx_fiq_end - &cns3xxx_fiq_start; |
||
90 | |||
91 | ret = claim_fiq(&fh); |
||
92 | if (ret) |
||
93 | return; |
||
94 | |||
95 | set_fiq_handler(fiqhandler_start, fiqhandler_length); |
||
96 | } |
||
97 | |||
98 | |||
99 | /* |
||
100 | * Write pen_release in a way that is guaranteed to be visible to all |
||
101 | * observers, irrespective of whether they're taking part in coherency |
||
102 | * or not. This is necessary for the hotplug code to work reliably. |
||
103 | */ |
||
104 | static void write_pen_release(int val) |
||
105 | { |
||
106 | pen_release = val; |
||
107 | smp_wmb(); |
||
108 | __cpuc_flush_dcache_area((void *)&pen_release, sizeof(pen_release)); |
||
109 | outer_clean_range(__pa(&pen_release), __pa(&pen_release + 1)); |
||
110 | } |
||
111 | |||
112 | static DEFINE_SPINLOCK(boot_lock); |
||
113 | |||
114 | static void cns3xxx_secondary_init(unsigned int cpu) |
||
115 | { |
||
116 | /* |
||
117 | * Setup Secondary Core FIQ regs |
||
118 | */ |
||
119 | cns3xxx_set_fiq_regs(1); |
||
120 | |||
121 | /* |
||
122 | * let the primary processor know we're out of the |
||
123 | * pen, then head off into the C entry point |
||
124 | */ |
||
125 | write_pen_release(-1); |
||
126 | |||
127 | /* |
||
128 | * Synchronise with the boot thread. |
||
129 | */ |
||
130 | spin_lock(&boot_lock); |
||
131 | spin_unlock(&boot_lock); |
||
132 | } |
||
133 | |||
134 | static int cns3xxx_boot_secondary(unsigned int cpu, struct task_struct *idle) |
||
135 | { |
||
136 | unsigned long timeout; |
||
137 | |||
138 | /* |
||
139 | * Set synchronisation state between this boot processor |
||
140 | * and the secondary one |
||
141 | */ |
||
142 | spin_lock(&boot_lock); |
||
143 | |||
144 | /* |
||
145 | * The secondary processor is waiting to be released from |
||
146 | * the holding pen - release it, then wait for it to flag |
||
147 | * that it has been released by resetting pen_release. |
||
148 | * |
||
149 | * Note that "pen_release" is the hardware CPU ID, whereas |
||
150 | * "cpu" is Linux's internal ID. |
||
151 | */ |
||
152 | write_pen_release(cpu); |
||
153 | |||
154 | /* |
||
155 | * Send the secondary CPU a soft interrupt, thereby causing |
||
156 | * the boot monitor to read the system wide flags register, |
||
157 | * and branch to the address found there. |
||
158 | */ |
||
159 | arch_send_wakeup_ipi_mask(cpumask_of(cpu));; |
||
160 | |||
161 | timeout = jiffies + (1 * HZ); |
||
162 | while (time_before(jiffies, timeout)) { |
||
163 | smp_rmb(); |
||
164 | if (pen_release == -1) |
||
165 | break; |
||
166 | |||
167 | udelay(10); |
||
168 | } |
||
169 | |||
170 | /* |
||
171 | * now the secondary core is starting up let it run its |
||
172 | * calibrations, then wait for it to finish |
||
173 | */ |
||
174 | spin_unlock(&boot_lock); |
||
175 | |||
176 | return pen_release != -1 ? -ENOSYS : 0; |
||
177 | } |
||
178 | |||
179 | /* |
||
180 | * Initialise the CPU possible map early - this describes the CPUs |
||
181 | * which may be present or become present in the system. |
||
182 | */ |
||
183 | static void __init cns3xxx_smp_init_cpus(void) |
||
184 | { |
||
185 | unsigned int i, ncores; |
||
186 | unsigned int status; |
||
187 | |||
188 | scu_base = (void __iomem *) CNS3XXX_TC11MP_SCU_BASE_VIRT; |
||
189 | |||
190 | /* for CNS3xxx SCU_CPU_STATUS must be examined instead of SCU_CONFIGURATION |
||
191 | * used in scu_get_core_count |
||
192 | */ |
||
193 | status = __raw_readl(scu_base + SCU_CPU_STATUS); |
||
194 | for (i = 0; i < NR_CPUS+1; i++) { |
||
195 | if (((status >> (i*2)) & 0x3) == 0) |
||
196 | set_cpu_possible(i, true); |
||
197 | else |
||
198 | break; |
||
199 | } |
||
200 | ncores = i; |
||
201 | } |
||
202 | |||
203 | static void __init cns3xxx_smp_prepare_cpus(unsigned int max_cpus) |
||
204 | { |
||
205 | /* |
||
206 | * enable SCU |
||
207 | */ |
||
208 | scu_enable(scu_base); |
||
209 | |||
210 | /* |
||
211 | * Write the address of secondary startup into the |
||
212 | * system-wide flags register. The boot monitor waits |
||
213 | * until it receives a soft interrupt, and then the |
||
214 | * secondary CPU branches to this address. |
||
215 | */ |
||
216 | __raw_writel(virt_to_phys(cns3xxx_secondary_startup), |
||
217 | (void __iomem *)(CNS3XXX_MISC_BASE_VIRT + 0x0600)); |
||
218 | |||
219 | /* |
||
220 | * Setup FIQ's for main cpu |
||
221 | */ |
||
222 | cns3xxx_init_fiq(); |
||
223 | cns3xxx_set_fiq_regs(0); |
||
224 | } |
||
225 | |||
226 | extern void v6_dma_map_area(const void *, size_t, int); |
||
227 | extern void v6_dma_unmap_area(const void *, size_t, int); |
||
228 | extern void v6_dma_flush_range(const void *, const void *); |
||
229 | extern void v6_flush_kern_dcache_area(void *, size_t); |
||
230 | |||
231 | void fiq_dma_map_area(const void *addr, size_t size, int dir) |
||
232 | { |
||
233 | unsigned long flags; |
||
234 | struct fiq_req *req; |
||
235 | |||
236 | raw_local_irq_save(flags); |
||
237 | /* currently, not possible to take cpu0 down, so only check cpu1 */ |
||
238 | if (!cpu_online(1)) { |
||
239 | raw_local_irq_restore(flags); |
||
240 | v6_dma_map_area(addr, size, dir); |
||
241 | return; |
||
242 | } |
||
243 | |||
244 | req = this_cpu_ptr(&fiq_data); |
||
245 | req->map.addr = addr; |
||
246 | req->map.size = size; |
||
247 | req->flags = dir | CNS3XXX_MAP_AREA; |
||
248 | smp_mb(); |
||
249 | |||
250 | writel_relaxed(FIQ_GENERATE, req->reg); |
||
251 | |||
252 | v6_dma_map_area(addr, size, dir); |
||
253 | while (req->flags) |
||
254 | barrier(); |
||
255 | |||
256 | raw_local_irq_restore(flags); |
||
257 | } |
||
258 | |||
259 | void fiq_dma_unmap_area(const void *addr, size_t size, int dir) |
||
260 | { |
||
261 | unsigned long flags; |
||
262 | struct fiq_req *req; |
||
263 | |||
264 | raw_local_irq_save(flags); |
||
265 | /* currently, not possible to take cpu0 down, so only check cpu1 */ |
||
266 | if (!cpu_online(1)) { |
||
267 | raw_local_irq_restore(flags); |
||
268 | v6_dma_unmap_area(addr, size, dir); |
||
269 | return; |
||
270 | } |
||
271 | |||
272 | req = this_cpu_ptr(&fiq_data); |
||
273 | req->unmap.addr = addr; |
||
274 | req->unmap.size = size; |
||
275 | req->flags = dir | CNS3XXX_UNMAP_AREA; |
||
276 | smp_mb(); |
||
277 | |||
278 | writel_relaxed(FIQ_GENERATE, req->reg); |
||
279 | |||
280 | v6_dma_unmap_area(addr, size, dir); |
||
281 | while (req->flags) |
||
282 | barrier(); |
||
283 | |||
284 | raw_local_irq_restore(flags); |
||
285 | } |
||
286 | |||
287 | void fiq_dma_flush_range(const void *start, const void *end) |
||
288 | { |
||
289 | unsigned long flags; |
||
290 | struct fiq_req *req; |
||
291 | |||
292 | raw_local_irq_save(flags); |
||
293 | /* currently, not possible to take cpu0 down, so only check cpu1 */ |
||
294 | if (!cpu_online(1)) { |
||
295 | raw_local_irq_restore(flags); |
||
296 | v6_dma_flush_range(start, end); |
||
297 | return; |
||
298 | } |
||
299 | |||
300 | req = this_cpu_ptr(&fiq_data); |
||
301 | |||
302 | req->flush.start = start; |
||
303 | req->flush.end = end; |
||
304 | req->flags = CNS3XXX_FLUSH_RANGE; |
||
305 | smp_mb(); |
||
306 | |||
307 | writel_relaxed(FIQ_GENERATE, req->reg); |
||
308 | |||
309 | v6_dma_flush_range(start, end); |
||
310 | |||
311 | while (req->flags) |
||
312 | barrier(); |
||
313 | |||
314 | raw_local_irq_restore(flags); |
||
315 | } |
||
316 | |||
317 | void fiq_flush_kern_dcache_area(void *addr, size_t size) |
||
318 | { |
||
319 | fiq_dma_flush_range(addr, addr + size); |
||
320 | } |
||
321 | |||
322 | struct smp_operations cns3xxx_smp_ops __initdata = { |
||
323 | .smp_init_cpus = cns3xxx_smp_init_cpus, |
||
324 | .smp_prepare_cpus = cns3xxx_smp_prepare_cpus, |
||
325 | .smp_secondary_init = cns3xxx_secondary_init, |
||
326 | .smp_boot_secondary = cns3xxx_boot_secondary, |
||
327 | }; |