diff options
author | Kukjin Kim <kgene.kim@samsung.com> | 2011-11-06 00:54:56 -0400 |
---|---|---|
committer | Kukjin Kim <kgene.kim@samsung.com> | 2011-11-06 00:54:56 -0400 |
commit | 830145796a5c8f1ca3f87ea619063c1d99a57df5 (patch) | |
tree | e72a0ecacfcce228c46d93c946cfd65a44cc1fd3 /arch/arm/mach-exynos/mct.c | |
parent | e700e41d9abfbf9fee01e979a41b185695132c19 (diff) |
ARM: EXYNOS: Add ARCH_EXYNOS and reorganize arch/arm/mach-exynos
The arch/arm/mach-exynos4 directory (CONFIG_ARCH_EXYNOS4) has
made for plaforms based on EXYNOS4 SoCs. But since upcoming
Samsung's SoCs such as EXYNOS5 (ARM Cortex A15) can reuse most
codes in current mach-exynos4, one mach-exynos directory will
be used for them.
This patch changes to CONFIG_ARCH_EXYNOS (arch/arm/mach-exynos)
but keeps original CONFIG_ARCH_EXYNOS4 in mach-exynos/Kconfig to
avoid changing in driver side.
Cc: Arnd Bergmann <arnd@arndb.de>
Cc: Russell King <rmk+kernel@arm.linux.org.uk>
Signed-off-by: Kukjin Kim <kgene.kim@samsung.com>
Diffstat (limited to 'arch/arm/mach-exynos/mct.c')
-rw-r--r-- | arch/arm/mach-exynos/mct.c | 471 |
1 files changed, 471 insertions, 0 deletions
diff --git a/arch/arm/mach-exynos/mct.c b/arch/arm/mach-exynos/mct.c new file mode 100644 index 000000000000..97343df8f132 --- /dev/null +++ b/arch/arm/mach-exynos/mct.c | |||
@@ -0,0 +1,471 @@ | |||
1 | /* linux/arch/arm/mach-exynos4/mct.c | ||
2 | * | ||
3 | * Copyright (c) 2011 Samsung Electronics Co., Ltd. | ||
4 | * http://www.samsung.com | ||
5 | * | ||
6 | * EXYNOS4 MCT(Multi-Core Timer) support | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify | ||
9 | * it under the terms of the GNU General Public License version 2 as | ||
10 | * published by the Free Software Foundation. | ||
11 | */ | ||
12 | |||
13 | #include <linux/sched.h> | ||
14 | #include <linux/interrupt.h> | ||
15 | #include <linux/irq.h> | ||
16 | #include <linux/err.h> | ||
17 | #include <linux/clk.h> | ||
18 | #include <linux/clockchips.h> | ||
19 | #include <linux/platform_device.h> | ||
20 | #include <linux/delay.h> | ||
21 | #include <linux/percpu.h> | ||
22 | |||
23 | #include <asm/hardware/gic.h> | ||
24 | |||
25 | #include <plat/cpu.h> | ||
26 | |||
27 | #include <mach/map.h> | ||
28 | #include <mach/irqs.h> | ||
29 | #include <mach/regs-mct.h> | ||
30 | #include <asm/mach/time.h> | ||
31 | |||
32 | enum { | ||
33 | MCT_INT_SPI, | ||
34 | MCT_INT_PPI | ||
35 | }; | ||
36 | |||
37 | static unsigned long clk_cnt_per_tick; | ||
38 | static unsigned long clk_rate; | ||
39 | static unsigned int mct_int_type; | ||
40 | |||
41 | struct mct_clock_event_device { | ||
42 | struct clock_event_device *evt; | ||
43 | void __iomem *base; | ||
44 | char name[10]; | ||
45 | }; | ||
46 | |||
47 | static DEFINE_PER_CPU(struct mct_clock_event_device, percpu_mct_tick); | ||
48 | |||
49 | static void exynos4_mct_write(unsigned int value, void *addr) | ||
50 | { | ||
51 | void __iomem *stat_addr; | ||
52 | u32 mask; | ||
53 | u32 i; | ||
54 | |||
55 | __raw_writel(value, addr); | ||
56 | |||
57 | if (likely(addr >= EXYNOS4_MCT_L_BASE(0))) { | ||
58 | u32 base = (u32) addr & EXYNOS4_MCT_L_MASK; | ||
59 | switch ((u32) addr & ~EXYNOS4_MCT_L_MASK) { | ||
60 | case (u32) MCT_L_TCON_OFFSET: | ||
61 | stat_addr = (void __iomem *) base + MCT_L_WSTAT_OFFSET; | ||
62 | mask = 1 << 3; /* L_TCON write status */ | ||
63 | break; | ||
64 | case (u32) MCT_L_ICNTB_OFFSET: | ||
65 | stat_addr = (void __iomem *) base + MCT_L_WSTAT_OFFSET; | ||
66 | mask = 1 << 1; /* L_ICNTB write status */ | ||
67 | break; | ||
68 | case (u32) MCT_L_TCNTB_OFFSET: | ||
69 | stat_addr = (void __iomem *) base + MCT_L_WSTAT_OFFSET; | ||
70 | mask = 1 << 0; /* L_TCNTB write status */ | ||
71 | break; | ||
72 | default: | ||
73 | return; | ||
74 | } | ||
75 | } else { | ||
76 | switch ((u32) addr) { | ||
77 | case (u32) EXYNOS4_MCT_G_TCON: | ||
78 | stat_addr = EXYNOS4_MCT_G_WSTAT; | ||
79 | mask = 1 << 16; /* G_TCON write status */ | ||
80 | break; | ||
81 | case (u32) EXYNOS4_MCT_G_COMP0_L: | ||
82 | stat_addr = EXYNOS4_MCT_G_WSTAT; | ||
83 | mask = 1 << 0; /* G_COMP0_L write status */ | ||
84 | break; | ||
85 | case (u32) EXYNOS4_MCT_G_COMP0_U: | ||
86 | stat_addr = EXYNOS4_MCT_G_WSTAT; | ||
87 | mask = 1 << 1; /* G_COMP0_U write status */ | ||
88 | break; | ||
89 | case (u32) EXYNOS4_MCT_G_COMP0_ADD_INCR: | ||
90 | stat_addr = EXYNOS4_MCT_G_WSTAT; | ||
91 | mask = 1 << 2; /* G_COMP0_ADD_INCR w status */ | ||
92 | break; | ||
93 | case (u32) EXYNOS4_MCT_G_CNT_L: | ||
94 | stat_addr = EXYNOS4_MCT_G_CNT_WSTAT; | ||
95 | mask = 1 << 0; /* G_CNT_L write status */ | ||
96 | break; | ||
97 | case (u32) EXYNOS4_MCT_G_CNT_U: | ||
98 | stat_addr = EXYNOS4_MCT_G_CNT_WSTAT; | ||
99 | mask = 1 << 1; /* G_CNT_U write status */ | ||
100 | break; | ||
101 | default: | ||
102 | return; | ||
103 | } | ||
104 | } | ||
105 | |||
106 | /* Wait maximum 1 ms until written values are applied */ | ||
107 | for (i = 0; i < loops_per_jiffy / 1000 * HZ; i++) | ||
108 | if (__raw_readl(stat_addr) & mask) { | ||
109 | __raw_writel(mask, stat_addr); | ||
110 | return; | ||
111 | } | ||
112 | |||
113 | panic("MCT hangs after writing %d (addr:0x%08x)\n", value, (u32)addr); | ||
114 | } | ||
115 | |||
116 | /* Clocksource handling */ | ||
117 | static void exynos4_mct_frc_start(u32 hi, u32 lo) | ||
118 | { | ||
119 | u32 reg; | ||
120 | |||
121 | exynos4_mct_write(lo, EXYNOS4_MCT_G_CNT_L); | ||
122 | exynos4_mct_write(hi, EXYNOS4_MCT_G_CNT_U); | ||
123 | |||
124 | reg = __raw_readl(EXYNOS4_MCT_G_TCON); | ||
125 | reg |= MCT_G_TCON_START; | ||
126 | exynos4_mct_write(reg, EXYNOS4_MCT_G_TCON); | ||
127 | } | ||
128 | |||
129 | static cycle_t exynos4_frc_read(struct clocksource *cs) | ||
130 | { | ||
131 | unsigned int lo, hi; | ||
132 | u32 hi2 = __raw_readl(EXYNOS4_MCT_G_CNT_U); | ||
133 | |||
134 | do { | ||
135 | hi = hi2; | ||
136 | lo = __raw_readl(EXYNOS4_MCT_G_CNT_L); | ||
137 | hi2 = __raw_readl(EXYNOS4_MCT_G_CNT_U); | ||
138 | } while (hi != hi2); | ||
139 | |||
140 | return ((cycle_t)hi << 32) | lo; | ||
141 | } | ||
142 | |||
143 | static void exynos4_frc_resume(struct clocksource *cs) | ||
144 | { | ||
145 | exynos4_mct_frc_start(0, 0); | ||
146 | } | ||
147 | |||
148 | struct clocksource mct_frc = { | ||
149 | .name = "mct-frc", | ||
150 | .rating = 400, | ||
151 | .read = exynos4_frc_read, | ||
152 | .mask = CLOCKSOURCE_MASK(64), | ||
153 | .flags = CLOCK_SOURCE_IS_CONTINUOUS, | ||
154 | .resume = exynos4_frc_resume, | ||
155 | }; | ||
156 | |||
157 | static void __init exynos4_clocksource_init(void) | ||
158 | { | ||
159 | exynos4_mct_frc_start(0, 0); | ||
160 | |||
161 | if (clocksource_register_hz(&mct_frc, clk_rate)) | ||
162 | panic("%s: can't register clocksource\n", mct_frc.name); | ||
163 | } | ||
164 | |||
165 | static void exynos4_mct_comp0_stop(void) | ||
166 | { | ||
167 | unsigned int tcon; | ||
168 | |||
169 | tcon = __raw_readl(EXYNOS4_MCT_G_TCON); | ||
170 | tcon &= ~(MCT_G_TCON_COMP0_ENABLE | MCT_G_TCON_COMP0_AUTO_INC); | ||
171 | |||
172 | exynos4_mct_write(tcon, EXYNOS4_MCT_G_TCON); | ||
173 | exynos4_mct_write(0, EXYNOS4_MCT_G_INT_ENB); | ||
174 | } | ||
175 | |||
176 | static void exynos4_mct_comp0_start(enum clock_event_mode mode, | ||
177 | unsigned long cycles) | ||
178 | { | ||
179 | unsigned int tcon; | ||
180 | cycle_t comp_cycle; | ||
181 | |||
182 | tcon = __raw_readl(EXYNOS4_MCT_G_TCON); | ||
183 | |||
184 | if (mode == CLOCK_EVT_MODE_PERIODIC) { | ||
185 | tcon |= MCT_G_TCON_COMP0_AUTO_INC; | ||
186 | exynos4_mct_write(cycles, EXYNOS4_MCT_G_COMP0_ADD_INCR); | ||
187 | } | ||
188 | |||
189 | comp_cycle = exynos4_frc_read(&mct_frc) + cycles; | ||
190 | exynos4_mct_write((u32)comp_cycle, EXYNOS4_MCT_G_COMP0_L); | ||
191 | exynos4_mct_write((u32)(comp_cycle >> 32), EXYNOS4_MCT_G_COMP0_U); | ||
192 | |||
193 | exynos4_mct_write(0x1, EXYNOS4_MCT_G_INT_ENB); | ||
194 | |||
195 | tcon |= MCT_G_TCON_COMP0_ENABLE; | ||
196 | exynos4_mct_write(tcon , EXYNOS4_MCT_G_TCON); | ||
197 | } | ||
198 | |||
199 | static int exynos4_comp_set_next_event(unsigned long cycles, | ||
200 | struct clock_event_device *evt) | ||
201 | { | ||
202 | exynos4_mct_comp0_start(evt->mode, cycles); | ||
203 | |||
204 | return 0; | ||
205 | } | ||
206 | |||
207 | static void exynos4_comp_set_mode(enum clock_event_mode mode, | ||
208 | struct clock_event_device *evt) | ||
209 | { | ||
210 | exynos4_mct_comp0_stop(); | ||
211 | |||
212 | switch (mode) { | ||
213 | case CLOCK_EVT_MODE_PERIODIC: | ||
214 | exynos4_mct_comp0_start(mode, clk_cnt_per_tick); | ||
215 | break; | ||
216 | |||
217 | case CLOCK_EVT_MODE_ONESHOT: | ||
218 | case CLOCK_EVT_MODE_UNUSED: | ||
219 | case CLOCK_EVT_MODE_SHUTDOWN: | ||
220 | case CLOCK_EVT_MODE_RESUME: | ||
221 | break; | ||
222 | } | ||
223 | } | ||
224 | |||
225 | static struct clock_event_device mct_comp_device = { | ||
226 | .name = "mct-comp", | ||
227 | .features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT, | ||
228 | .rating = 250, | ||
229 | .set_next_event = exynos4_comp_set_next_event, | ||
230 | .set_mode = exynos4_comp_set_mode, | ||
231 | }; | ||
232 | |||
233 | static irqreturn_t exynos4_mct_comp_isr(int irq, void *dev_id) | ||
234 | { | ||
235 | struct clock_event_device *evt = dev_id; | ||
236 | |||
237 | exynos4_mct_write(0x1, EXYNOS4_MCT_G_INT_CSTAT); | ||
238 | |||
239 | evt->event_handler(evt); | ||
240 | |||
241 | return IRQ_HANDLED; | ||
242 | } | ||
243 | |||
244 | static struct irqaction mct_comp_event_irq = { | ||
245 | .name = "mct_comp_irq", | ||
246 | .flags = IRQF_TIMER | IRQF_IRQPOLL, | ||
247 | .handler = exynos4_mct_comp_isr, | ||
248 | .dev_id = &mct_comp_device, | ||
249 | }; | ||
250 | |||
251 | static void exynos4_clockevent_init(void) | ||
252 | { | ||
253 | clk_cnt_per_tick = clk_rate / 2 / HZ; | ||
254 | |||
255 | clockevents_calc_mult_shift(&mct_comp_device, clk_rate / 2, 5); | ||
256 | mct_comp_device.max_delta_ns = | ||
257 | clockevent_delta2ns(0xffffffff, &mct_comp_device); | ||
258 | mct_comp_device.min_delta_ns = | ||
259 | clockevent_delta2ns(0xf, &mct_comp_device); | ||
260 | mct_comp_device.cpumask = cpumask_of(0); | ||
261 | clockevents_register_device(&mct_comp_device); | ||
262 | |||
263 | setup_irq(IRQ_MCT_G0, &mct_comp_event_irq); | ||
264 | } | ||
265 | |||
266 | #ifdef CONFIG_LOCAL_TIMERS | ||
267 | /* Clock event handling */ | ||
268 | static void exynos4_mct_tick_stop(struct mct_clock_event_device *mevt) | ||
269 | { | ||
270 | unsigned long tmp; | ||
271 | unsigned long mask = MCT_L_TCON_INT_START | MCT_L_TCON_TIMER_START; | ||
272 | void __iomem *addr = mevt->base + MCT_L_TCON_OFFSET; | ||
273 | |||
274 | tmp = __raw_readl(addr); | ||
275 | if (tmp & mask) { | ||
276 | tmp &= ~mask; | ||
277 | exynos4_mct_write(tmp, addr); | ||
278 | } | ||
279 | } | ||
280 | |||
281 | static void exynos4_mct_tick_start(unsigned long cycles, | ||
282 | struct mct_clock_event_device *mevt) | ||
283 | { | ||
284 | unsigned long tmp; | ||
285 | |||
286 | exynos4_mct_tick_stop(mevt); | ||
287 | |||
288 | tmp = (1 << 31) | cycles; /* MCT_L_UPDATE_ICNTB */ | ||
289 | |||
290 | /* update interrupt count buffer */ | ||
291 | exynos4_mct_write(tmp, mevt->base + MCT_L_ICNTB_OFFSET); | ||
292 | |||
293 | /* enable MCT tick interrupt */ | ||
294 | exynos4_mct_write(0x1, mevt->base + MCT_L_INT_ENB_OFFSET); | ||
295 | |||
296 | tmp = __raw_readl(mevt->base + MCT_L_TCON_OFFSET); | ||
297 | tmp |= MCT_L_TCON_INT_START | MCT_L_TCON_TIMER_START | | ||
298 | MCT_L_TCON_INTERVAL_MODE; | ||
299 | exynos4_mct_write(tmp, mevt->base + MCT_L_TCON_OFFSET); | ||
300 | } | ||
301 | |||
302 | static int exynos4_tick_set_next_event(unsigned long cycles, | ||
303 | struct clock_event_device *evt) | ||
304 | { | ||
305 | struct mct_clock_event_device *mevt = this_cpu_ptr(&percpu_mct_tick); | ||
306 | |||
307 | exynos4_mct_tick_start(cycles, mevt); | ||
308 | |||
309 | return 0; | ||
310 | } | ||
311 | |||
312 | static inline void exynos4_tick_set_mode(enum clock_event_mode mode, | ||
313 | struct clock_event_device *evt) | ||
314 | { | ||
315 | struct mct_clock_event_device *mevt = this_cpu_ptr(&percpu_mct_tick); | ||
316 | |||
317 | exynos4_mct_tick_stop(mevt); | ||
318 | |||
319 | switch (mode) { | ||
320 | case CLOCK_EVT_MODE_PERIODIC: | ||
321 | exynos4_mct_tick_start(clk_cnt_per_tick, mevt); | ||
322 | break; | ||
323 | |||
324 | case CLOCK_EVT_MODE_ONESHOT: | ||
325 | case CLOCK_EVT_MODE_UNUSED: | ||
326 | case CLOCK_EVT_MODE_SHUTDOWN: | ||
327 | case CLOCK_EVT_MODE_RESUME: | ||
328 | break; | ||
329 | } | ||
330 | } | ||
331 | |||
332 | static int exynos4_mct_tick_clear(struct mct_clock_event_device *mevt) | ||
333 | { | ||
334 | struct clock_event_device *evt = mevt->evt; | ||
335 | |||
336 | /* | ||
337 | * This is for supporting oneshot mode. | ||
338 | * Mct would generate interrupt periodically | ||
339 | * without explicit stopping. | ||
340 | */ | ||
341 | if (evt->mode != CLOCK_EVT_MODE_PERIODIC) | ||
342 | exynos4_mct_tick_stop(mevt); | ||
343 | |||
344 | /* Clear the MCT tick interrupt */ | ||
345 | if (__raw_readl(mevt->base + MCT_L_INT_CSTAT_OFFSET) & 1) { | ||
346 | exynos4_mct_write(0x1, mevt->base + MCT_L_INT_CSTAT_OFFSET); | ||
347 | return 1; | ||
348 | } else { | ||
349 | return 0; | ||
350 | } | ||
351 | } | ||
352 | |||
353 | static irqreturn_t exynos4_mct_tick_isr(int irq, void *dev_id) | ||
354 | { | ||
355 | struct mct_clock_event_device *mevt = dev_id; | ||
356 | struct clock_event_device *evt = mevt->evt; | ||
357 | |||
358 | exynos4_mct_tick_clear(mevt); | ||
359 | |||
360 | evt->event_handler(evt); | ||
361 | |||
362 | return IRQ_HANDLED; | ||
363 | } | ||
364 | |||
365 | static struct irqaction mct_tick0_event_irq = { | ||
366 | .name = "mct_tick0_irq", | ||
367 | .flags = IRQF_TIMER | IRQF_NOBALANCING, | ||
368 | .handler = exynos4_mct_tick_isr, | ||
369 | }; | ||
370 | |||
371 | static struct irqaction mct_tick1_event_irq = { | ||
372 | .name = "mct_tick1_irq", | ||
373 | .flags = IRQF_TIMER | IRQF_NOBALANCING, | ||
374 | .handler = exynos4_mct_tick_isr, | ||
375 | }; | ||
376 | |||
377 | static void exynos4_mct_tick_init(struct clock_event_device *evt) | ||
378 | { | ||
379 | struct mct_clock_event_device *mevt; | ||
380 | unsigned int cpu = smp_processor_id(); | ||
381 | |||
382 | mevt = this_cpu_ptr(&percpu_mct_tick); | ||
383 | mevt->evt = evt; | ||
384 | |||
385 | mevt->base = EXYNOS4_MCT_L_BASE(cpu); | ||
386 | sprintf(mevt->name, "mct_tick%d", cpu); | ||
387 | |||
388 | evt->name = mevt->name; | ||
389 | evt->cpumask = cpumask_of(cpu); | ||
390 | evt->set_next_event = exynos4_tick_set_next_event; | ||
391 | evt->set_mode = exynos4_tick_set_mode; | ||
392 | evt->features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT; | ||
393 | evt->rating = 450; | ||
394 | |||
395 | clockevents_calc_mult_shift(evt, clk_rate / 2, 5); | ||
396 | evt->max_delta_ns = | ||
397 | clockevent_delta2ns(0x7fffffff, evt); | ||
398 | evt->min_delta_ns = | ||
399 | clockevent_delta2ns(0xf, evt); | ||
400 | |||
401 | clockevents_register_device(evt); | ||
402 | |||
403 | exynos4_mct_write(0x1, mevt->base + MCT_L_TCNTB_OFFSET); | ||
404 | |||
405 | if (mct_int_type == MCT_INT_SPI) { | ||
406 | if (cpu == 0) { | ||
407 | mct_tick0_event_irq.dev_id = mevt; | ||
408 | evt->irq = IRQ_MCT_L0; | ||
409 | setup_irq(IRQ_MCT_L0, &mct_tick0_event_irq); | ||
410 | } else { | ||
411 | mct_tick1_event_irq.dev_id = mevt; | ||
412 | evt->irq = IRQ_MCT_L1; | ||
413 | setup_irq(IRQ_MCT_L1, &mct_tick1_event_irq); | ||
414 | irq_set_affinity(IRQ_MCT_L1, cpumask_of(1)); | ||
415 | } | ||
416 | } else { | ||
417 | enable_percpu_irq(IRQ_MCT_LOCALTIMER, 0); | ||
418 | } | ||
419 | } | ||
420 | |||
421 | /* Setup the local clock events for a CPU */ | ||
422 | int __cpuinit local_timer_setup(struct clock_event_device *evt) | ||
423 | { | ||
424 | exynos4_mct_tick_init(evt); | ||
425 | |||
426 | return 0; | ||
427 | } | ||
428 | |||
429 | void local_timer_stop(struct clock_event_device *evt) | ||
430 | { | ||
431 | evt->set_mode(CLOCK_EVT_MODE_UNUSED, evt); | ||
432 | if (mct_int_type == MCT_INT_SPI) | ||
433 | disable_irq(evt->irq); | ||
434 | else | ||
435 | disable_percpu_irq(IRQ_MCT_LOCALTIMER); | ||
436 | } | ||
437 | #endif /* CONFIG_LOCAL_TIMERS */ | ||
438 | |||
439 | static void __init exynos4_timer_resources(void) | ||
440 | { | ||
441 | struct clk *mct_clk; | ||
442 | mct_clk = clk_get(NULL, "xtal"); | ||
443 | |||
444 | clk_rate = clk_get_rate(mct_clk); | ||
445 | |||
446 | if (mct_int_type == MCT_INT_PPI) { | ||
447 | int err; | ||
448 | |||
449 | err = request_percpu_irq(IRQ_MCT_LOCALTIMER, | ||
450 | exynos4_mct_tick_isr, "MCT", | ||
451 | &percpu_mct_tick); | ||
452 | WARN(err, "MCT: can't request IRQ %d (%d)\n", | ||
453 | IRQ_MCT_LOCALTIMER, err); | ||
454 | } | ||
455 | } | ||
456 | |||
457 | static void __init exynos4_timer_init(void) | ||
458 | { | ||
459 | if (soc_is_exynos4210()) | ||
460 | mct_int_type = MCT_INT_SPI; | ||
461 | else | ||
462 | mct_int_type = MCT_INT_PPI; | ||
463 | |||
464 | exynos4_timer_resources(); | ||
465 | exynos4_clocksource_init(); | ||
466 | exynos4_clockevent_init(); | ||
467 | } | ||
468 | |||
469 | struct sys_timer exynos4_timer = { | ||
470 | .init = exynos4_timer_init, | ||
471 | }; | ||