Commit | Line | Data |
---|---|---|
30d8bead CY |
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> | |
2edb36c4 | 22 | #include <linux/of.h> |
30d8bead | 23 | |
2edb36c4 | 24 | #include <asm/arch_timer.h> |
a8cb6041 | 25 | #include <asm/localtimer.h> |
3a062281 CY |
26 | |
27 | #include <plat/cpu.h> | |
28 | ||
30d8bead | 29 | #include <mach/map.h> |
3a062281 | 30 | #include <mach/irqs.h> |
30d8bead CY |
31 | #include <mach/regs-mct.h> |
32 | #include <asm/mach/time.h> | |
33 | ||
4d2e4d7f CY |
34 | #define TICK_BASE_CNT 1 |
35 | ||
3a062281 CY |
36 | enum { |
37 | MCT_INT_SPI, | |
38 | MCT_INT_PPI | |
39 | }; | |
40 | ||
30d8bead | 41 | static unsigned long clk_rate; |
3a062281 | 42 | static unsigned int mct_int_type; |
30d8bead CY |
43 | |
44 | struct mct_clock_event_device { | |
45 | struct clock_event_device *evt; | |
46 | void __iomem *base; | |
c8987470 | 47 | char name[10]; |
30d8bead CY |
48 | }; |
49 | ||
30d8bead CY |
50 | static void exynos4_mct_write(unsigned int value, void *addr) |
51 | { | |
52 | void __iomem *stat_addr; | |
53 | u32 mask; | |
54 | u32 i; | |
55 | ||
56 | __raw_writel(value, addr); | |
57 | ||
c8987470 CY |
58 | if (likely(addr >= EXYNOS4_MCT_L_BASE(0))) { |
59 | u32 base = (u32) addr & EXYNOS4_MCT_L_MASK; | |
60 | switch ((u32) addr & ~EXYNOS4_MCT_L_MASK) { | |
61 | case (u32) MCT_L_TCON_OFFSET: | |
62 | stat_addr = (void __iomem *) base + MCT_L_WSTAT_OFFSET; | |
63 | mask = 1 << 3; /* L_TCON write status */ | |
64 | break; | |
65 | case (u32) MCT_L_ICNTB_OFFSET: | |
66 | stat_addr = (void __iomem *) base + MCT_L_WSTAT_OFFSET; | |
67 | mask = 1 << 1; /* L_ICNTB write status */ | |
68 | break; | |
69 | case (u32) MCT_L_TCNTB_OFFSET: | |
70 | stat_addr = (void __iomem *) base + MCT_L_WSTAT_OFFSET; | |
71 | mask = 1 << 0; /* L_TCNTB write status */ | |
72 | break; | |
73 | default: | |
74 | return; | |
75 | } | |
76 | } else { | |
77 | switch ((u32) addr) { | |
78 | case (u32) EXYNOS4_MCT_G_TCON: | |
79 | stat_addr = EXYNOS4_MCT_G_WSTAT; | |
80 | mask = 1 << 16; /* G_TCON write status */ | |
81 | break; | |
82 | case (u32) EXYNOS4_MCT_G_COMP0_L: | |
83 | stat_addr = EXYNOS4_MCT_G_WSTAT; | |
84 | mask = 1 << 0; /* G_COMP0_L write status */ | |
85 | break; | |
86 | case (u32) EXYNOS4_MCT_G_COMP0_U: | |
87 | stat_addr = EXYNOS4_MCT_G_WSTAT; | |
88 | mask = 1 << 1; /* G_COMP0_U write status */ | |
89 | break; | |
90 | case (u32) EXYNOS4_MCT_G_COMP0_ADD_INCR: | |
91 | stat_addr = EXYNOS4_MCT_G_WSTAT; | |
92 | mask = 1 << 2; /* G_COMP0_ADD_INCR w status */ | |
93 | break; | |
94 | case (u32) EXYNOS4_MCT_G_CNT_L: | |
95 | stat_addr = EXYNOS4_MCT_G_CNT_WSTAT; | |
96 | mask = 1 << 0; /* G_CNT_L write status */ | |
97 | break; | |
98 | case (u32) EXYNOS4_MCT_G_CNT_U: | |
99 | stat_addr = EXYNOS4_MCT_G_CNT_WSTAT; | |
100 | mask = 1 << 1; /* G_CNT_U write status */ | |
101 | break; | |
102 | default: | |
103 | return; | |
104 | } | |
30d8bead CY |
105 | } |
106 | ||
107 | /* Wait maximum 1 ms until written values are applied */ | |
108 | for (i = 0; i < loops_per_jiffy / 1000 * HZ; i++) | |
109 | if (__raw_readl(stat_addr) & mask) { | |
110 | __raw_writel(mask, stat_addr); | |
111 | return; | |
112 | } | |
113 | ||
114 | panic("MCT hangs after writing %d (addr:0x%08x)\n", value, (u32)addr); | |
115 | } | |
116 | ||
117 | /* Clocksource handling */ | |
118 | static void exynos4_mct_frc_start(u32 hi, u32 lo) | |
119 | { | |
120 | u32 reg; | |
121 | ||
122 | exynos4_mct_write(lo, EXYNOS4_MCT_G_CNT_L); | |
123 | exynos4_mct_write(hi, EXYNOS4_MCT_G_CNT_U); | |
124 | ||
125 | reg = __raw_readl(EXYNOS4_MCT_G_TCON); | |
126 | reg |= MCT_G_TCON_START; | |
127 | exynos4_mct_write(reg, EXYNOS4_MCT_G_TCON); | |
128 | } | |
129 | ||
130 | static cycle_t exynos4_frc_read(struct clocksource *cs) | |
131 | { | |
132 | unsigned int lo, hi; | |
133 | u32 hi2 = __raw_readl(EXYNOS4_MCT_G_CNT_U); | |
134 | ||
135 | do { | |
136 | hi = hi2; | |
137 | lo = __raw_readl(EXYNOS4_MCT_G_CNT_L); | |
138 | hi2 = __raw_readl(EXYNOS4_MCT_G_CNT_U); | |
139 | } while (hi != hi2); | |
140 | ||
141 | return ((cycle_t)hi << 32) | lo; | |
142 | } | |
143 | ||
aa421c13 CY |
144 | static void exynos4_frc_resume(struct clocksource *cs) |
145 | { | |
146 | exynos4_mct_frc_start(0, 0); | |
147 | } | |
148 | ||
30d8bead CY |
149 | struct clocksource mct_frc = { |
150 | .name = "mct-frc", | |
151 | .rating = 400, | |
152 | .read = exynos4_frc_read, | |
153 | .mask = CLOCKSOURCE_MASK(64), | |
154 | .flags = CLOCK_SOURCE_IS_CONTINUOUS, | |
aa421c13 | 155 | .resume = exynos4_frc_resume, |
30d8bead CY |
156 | }; |
157 | ||
158 | static void __init exynos4_clocksource_init(void) | |
159 | { | |
160 | exynos4_mct_frc_start(0, 0); | |
161 | ||
162 | if (clocksource_register_hz(&mct_frc, clk_rate)) | |
163 | panic("%s: can't register clocksource\n", mct_frc.name); | |
164 | } | |
165 | ||
166 | static void exynos4_mct_comp0_stop(void) | |
167 | { | |
168 | unsigned int tcon; | |
169 | ||
170 | tcon = __raw_readl(EXYNOS4_MCT_G_TCON); | |
171 | tcon &= ~(MCT_G_TCON_COMP0_ENABLE | MCT_G_TCON_COMP0_AUTO_INC); | |
172 | ||
173 | exynos4_mct_write(tcon, EXYNOS4_MCT_G_TCON); | |
174 | exynos4_mct_write(0, EXYNOS4_MCT_G_INT_ENB); | |
175 | } | |
176 | ||
177 | static void exynos4_mct_comp0_start(enum clock_event_mode mode, | |
178 | unsigned long cycles) | |
179 | { | |
180 | unsigned int tcon; | |
181 | cycle_t comp_cycle; | |
182 | ||
183 | tcon = __raw_readl(EXYNOS4_MCT_G_TCON); | |
184 | ||
185 | if (mode == CLOCK_EVT_MODE_PERIODIC) { | |
186 | tcon |= MCT_G_TCON_COMP0_AUTO_INC; | |
187 | exynos4_mct_write(cycles, EXYNOS4_MCT_G_COMP0_ADD_INCR); | |
188 | } | |
189 | ||
190 | comp_cycle = exynos4_frc_read(&mct_frc) + cycles; | |
191 | exynos4_mct_write((u32)comp_cycle, EXYNOS4_MCT_G_COMP0_L); | |
192 | exynos4_mct_write((u32)(comp_cycle >> 32), EXYNOS4_MCT_G_COMP0_U); | |
193 | ||
194 | exynos4_mct_write(0x1, EXYNOS4_MCT_G_INT_ENB); | |
195 | ||
196 | tcon |= MCT_G_TCON_COMP0_ENABLE; | |
197 | exynos4_mct_write(tcon , EXYNOS4_MCT_G_TCON); | |
198 | } | |
199 | ||
200 | static int exynos4_comp_set_next_event(unsigned long cycles, | |
201 | struct clock_event_device *evt) | |
202 | { | |
203 | exynos4_mct_comp0_start(evt->mode, cycles); | |
204 | ||
205 | return 0; | |
206 | } | |
207 | ||
208 | static void exynos4_comp_set_mode(enum clock_event_mode mode, | |
209 | struct clock_event_device *evt) | |
210 | { | |
4d2e4d7f | 211 | unsigned long cycles_per_jiffy; |
30d8bead CY |
212 | exynos4_mct_comp0_stop(); |
213 | ||
214 | switch (mode) { | |
215 | case CLOCK_EVT_MODE_PERIODIC: | |
4d2e4d7f CY |
216 | cycles_per_jiffy = |
217 | (((unsigned long long) NSEC_PER_SEC / HZ * evt->mult) >> evt->shift); | |
218 | exynos4_mct_comp0_start(mode, cycles_per_jiffy); | |
30d8bead CY |
219 | break; |
220 | ||
221 | case CLOCK_EVT_MODE_ONESHOT: | |
222 | case CLOCK_EVT_MODE_UNUSED: | |
223 | case CLOCK_EVT_MODE_SHUTDOWN: | |
224 | case CLOCK_EVT_MODE_RESUME: | |
225 | break; | |
226 | } | |
227 | } | |
228 | ||
229 | static struct clock_event_device mct_comp_device = { | |
230 | .name = "mct-comp", | |
231 | .features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT, | |
232 | .rating = 250, | |
233 | .set_next_event = exynos4_comp_set_next_event, | |
234 | .set_mode = exynos4_comp_set_mode, | |
235 | }; | |
236 | ||
237 | static irqreturn_t exynos4_mct_comp_isr(int irq, void *dev_id) | |
238 | { | |
239 | struct clock_event_device *evt = dev_id; | |
240 | ||
241 | exynos4_mct_write(0x1, EXYNOS4_MCT_G_INT_CSTAT); | |
242 | ||
243 | evt->event_handler(evt); | |
244 | ||
245 | return IRQ_HANDLED; | |
246 | } | |
247 | ||
248 | static struct irqaction mct_comp_event_irq = { | |
249 | .name = "mct_comp_irq", | |
250 | .flags = IRQF_TIMER | IRQF_IRQPOLL, | |
251 | .handler = exynos4_mct_comp_isr, | |
252 | .dev_id = &mct_comp_device, | |
253 | }; | |
254 | ||
255 | static void exynos4_clockevent_init(void) | |
256 | { | |
30d8bead | 257 | mct_comp_device.cpumask = cpumask_of(0); |
838a2ae8 SG |
258 | clockevents_config_and_register(&mct_comp_device, clk_rate, |
259 | 0xf, 0xffffffff); | |
30d8bead | 260 | |
bb19a751 KK |
261 | if (soc_is_exynos5250()) |
262 | setup_irq(EXYNOS5_IRQ_MCT_G0, &mct_comp_event_irq); | |
263 | else | |
264 | setup_irq(EXYNOS4_IRQ_MCT_G0, &mct_comp_event_irq); | |
30d8bead CY |
265 | } |
266 | ||
267 | #ifdef CONFIG_LOCAL_TIMERS | |
991a6c7d KK |
268 | |
269 | static DEFINE_PER_CPU(struct mct_clock_event_device, percpu_mct_tick); | |
270 | ||
30d8bead CY |
271 | /* Clock event handling */ |
272 | static void exynos4_mct_tick_stop(struct mct_clock_event_device *mevt) | |
273 | { | |
274 | unsigned long tmp; | |
275 | unsigned long mask = MCT_L_TCON_INT_START | MCT_L_TCON_TIMER_START; | |
276 | void __iomem *addr = mevt->base + MCT_L_TCON_OFFSET; | |
277 | ||
278 | tmp = __raw_readl(addr); | |
279 | if (tmp & mask) { | |
280 | tmp &= ~mask; | |
281 | exynos4_mct_write(tmp, addr); | |
282 | } | |
283 | } | |
284 | ||
285 | static void exynos4_mct_tick_start(unsigned long cycles, | |
286 | struct mct_clock_event_device *mevt) | |
287 | { | |
288 | unsigned long tmp; | |
289 | ||
290 | exynos4_mct_tick_stop(mevt); | |
291 | ||
292 | tmp = (1 << 31) | cycles; /* MCT_L_UPDATE_ICNTB */ | |
293 | ||
294 | /* update interrupt count buffer */ | |
295 | exynos4_mct_write(tmp, mevt->base + MCT_L_ICNTB_OFFSET); | |
296 | ||
25985edc | 297 | /* enable MCT tick interrupt */ |
30d8bead CY |
298 | exynos4_mct_write(0x1, mevt->base + MCT_L_INT_ENB_OFFSET); |
299 | ||
300 | tmp = __raw_readl(mevt->base + MCT_L_TCON_OFFSET); | |
301 | tmp |= MCT_L_TCON_INT_START | MCT_L_TCON_TIMER_START | | |
302 | MCT_L_TCON_INTERVAL_MODE; | |
303 | exynos4_mct_write(tmp, mevt->base + MCT_L_TCON_OFFSET); | |
304 | } | |
305 | ||
306 | static int exynos4_tick_set_next_event(unsigned long cycles, | |
307 | struct clock_event_device *evt) | |
308 | { | |
e700e41d | 309 | struct mct_clock_event_device *mevt = this_cpu_ptr(&percpu_mct_tick); |
30d8bead CY |
310 | |
311 | exynos4_mct_tick_start(cycles, mevt); | |
312 | ||
313 | return 0; | |
314 | } | |
315 | ||
316 | static inline void exynos4_tick_set_mode(enum clock_event_mode mode, | |
317 | struct clock_event_device *evt) | |
318 | { | |
e700e41d | 319 | struct mct_clock_event_device *mevt = this_cpu_ptr(&percpu_mct_tick); |
4d2e4d7f | 320 | unsigned long cycles_per_jiffy; |
30d8bead CY |
321 | |
322 | exynos4_mct_tick_stop(mevt); | |
323 | ||
324 | switch (mode) { | |
325 | case CLOCK_EVT_MODE_PERIODIC: | |
4d2e4d7f CY |
326 | cycles_per_jiffy = |
327 | (((unsigned long long) NSEC_PER_SEC / HZ * evt->mult) >> evt->shift); | |
328 | exynos4_mct_tick_start(cycles_per_jiffy, mevt); | |
30d8bead CY |
329 | break; |
330 | ||
331 | case CLOCK_EVT_MODE_ONESHOT: | |
332 | case CLOCK_EVT_MODE_UNUSED: | |
333 | case CLOCK_EVT_MODE_SHUTDOWN: | |
334 | case CLOCK_EVT_MODE_RESUME: | |
335 | break; | |
336 | } | |
337 | } | |
338 | ||
c8987470 | 339 | static int exynos4_mct_tick_clear(struct mct_clock_event_device *mevt) |
30d8bead | 340 | { |
30d8bead CY |
341 | struct clock_event_device *evt = mevt->evt; |
342 | ||
343 | /* | |
344 | * This is for supporting oneshot mode. | |
345 | * Mct would generate interrupt periodically | |
346 | * without explicit stopping. | |
347 | */ | |
348 | if (evt->mode != CLOCK_EVT_MODE_PERIODIC) | |
349 | exynos4_mct_tick_stop(mevt); | |
350 | ||
351 | /* Clear the MCT tick interrupt */ | |
3a062281 CY |
352 | if (__raw_readl(mevt->base + MCT_L_INT_CSTAT_OFFSET) & 1) { |
353 | exynos4_mct_write(0x1, mevt->base + MCT_L_INT_CSTAT_OFFSET); | |
354 | return 1; | |
355 | } else { | |
356 | return 0; | |
357 | } | |
358 | } | |
359 | ||
360 | static irqreturn_t exynos4_mct_tick_isr(int irq, void *dev_id) | |
361 | { | |
362 | struct mct_clock_event_device *mevt = dev_id; | |
363 | struct clock_event_device *evt = mevt->evt; | |
364 | ||
365 | exynos4_mct_tick_clear(mevt); | |
30d8bead CY |
366 | |
367 | evt->event_handler(evt); | |
368 | ||
369 | return IRQ_HANDLED; | |
370 | } | |
371 | ||
372 | static struct irqaction mct_tick0_event_irq = { | |
373 | .name = "mct_tick0_irq", | |
374 | .flags = IRQF_TIMER | IRQF_NOBALANCING, | |
375 | .handler = exynos4_mct_tick_isr, | |
376 | }; | |
377 | ||
378 | static struct irqaction mct_tick1_event_irq = { | |
379 | .name = "mct_tick1_irq", | |
380 | .flags = IRQF_TIMER | IRQF_NOBALANCING, | |
381 | .handler = exynos4_mct_tick_isr, | |
382 | }; | |
383 | ||
a8cb6041 | 384 | static int __cpuinit exynos4_local_timer_setup(struct clock_event_device *evt) |
30d8bead | 385 | { |
e700e41d | 386 | struct mct_clock_event_device *mevt; |
30d8bead | 387 | unsigned int cpu = smp_processor_id(); |
eeed66e3 | 388 | int mct_lx_irq; |
30d8bead | 389 | |
e700e41d MZ |
390 | mevt = this_cpu_ptr(&percpu_mct_tick); |
391 | mevt->evt = evt; | |
30d8bead | 392 | |
e700e41d MZ |
393 | mevt->base = EXYNOS4_MCT_L_BASE(cpu); |
394 | sprintf(mevt->name, "mct_tick%d", cpu); | |
30d8bead | 395 | |
e700e41d | 396 | evt->name = mevt->name; |
30d8bead CY |
397 | evt->cpumask = cpumask_of(cpu); |
398 | evt->set_next_event = exynos4_tick_set_next_event; | |
399 | evt->set_mode = exynos4_tick_set_mode; | |
400 | evt->features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT; | |
401 | evt->rating = 450; | |
838a2ae8 SG |
402 | clockevents_config_and_register(evt, clk_rate / (TICK_BASE_CNT + 1), |
403 | 0xf, 0x7fffffff); | |
30d8bead | 404 | |
4d2e4d7f | 405 | exynos4_mct_write(TICK_BASE_CNT, mevt->base + MCT_L_TCNTB_OFFSET); |
30d8bead | 406 | |
3a062281 CY |
407 | if (mct_int_type == MCT_INT_SPI) { |
408 | if (cpu == 0) { | |
eeed66e3 CY |
409 | mct_lx_irq = soc_is_exynos4210() ? EXYNOS4_IRQ_MCT_L0 : |
410 | EXYNOS5_IRQ_MCT_L0; | |
e700e41d | 411 | mct_tick0_event_irq.dev_id = mevt; |
eeed66e3 CY |
412 | evt->irq = mct_lx_irq; |
413 | setup_irq(mct_lx_irq, &mct_tick0_event_irq); | |
3a062281 | 414 | } else { |
eeed66e3 CY |
415 | mct_lx_irq = soc_is_exynos4210() ? EXYNOS4_IRQ_MCT_L1 : |
416 | EXYNOS5_IRQ_MCT_L1; | |
e700e41d | 417 | mct_tick1_event_irq.dev_id = mevt; |
eeed66e3 CY |
418 | evt->irq = mct_lx_irq; |
419 | setup_irq(mct_lx_irq, &mct_tick1_event_irq); | |
420 | irq_set_affinity(mct_lx_irq, cpumask_of(1)); | |
3a062281 | 421 | } |
30d8bead | 422 | } else { |
bb19a751 | 423 | enable_percpu_irq(EXYNOS_IRQ_MCT_LOCALTIMER, 0); |
30d8bead | 424 | } |
4d487d7e KK |
425 | |
426 | return 0; | |
30d8bead CY |
427 | } |
428 | ||
a8cb6041 | 429 | static void exynos4_local_timer_stop(struct clock_event_device *evt) |
30d8bead | 430 | { |
e248cd5d | 431 | unsigned int cpu = smp_processor_id(); |
28af690a | 432 | evt->set_mode(CLOCK_EVT_MODE_UNUSED, evt); |
e700e41d | 433 | if (mct_int_type == MCT_INT_SPI) |
e248cd5d ADK |
434 | if (cpu == 0) |
435 | remove_irq(evt->irq, &mct_tick0_event_irq); | |
436 | else | |
437 | remove_irq(evt->irq, &mct_tick1_event_irq); | |
e700e41d | 438 | else |
bb19a751 | 439 | disable_percpu_irq(EXYNOS_IRQ_MCT_LOCALTIMER); |
30d8bead | 440 | } |
a8cb6041 MZ |
441 | |
442 | static struct local_timer_ops exynos4_mct_tick_ops __cpuinitdata = { | |
443 | .setup = exynos4_local_timer_setup, | |
444 | .stop = exynos4_local_timer_stop, | |
445 | }; | |
30d8bead CY |
446 | #endif /* CONFIG_LOCAL_TIMERS */ |
447 | ||
448 | static void __init exynos4_timer_resources(void) | |
449 | { | |
450 | struct clk *mct_clk; | |
451 | mct_clk = clk_get(NULL, "xtal"); | |
452 | ||
453 | clk_rate = clk_get_rate(mct_clk); | |
e700e41d | 454 | |
991a6c7d | 455 | #ifdef CONFIG_LOCAL_TIMERS |
e700e41d MZ |
456 | if (mct_int_type == MCT_INT_PPI) { |
457 | int err; | |
458 | ||
bb19a751 | 459 | err = request_percpu_irq(EXYNOS_IRQ_MCT_LOCALTIMER, |
e700e41d MZ |
460 | exynos4_mct_tick_isr, "MCT", |
461 | &percpu_mct_tick); | |
462 | WARN(err, "MCT: can't request IRQ %d (%d)\n", | |
bb19a751 | 463 | EXYNOS_IRQ_MCT_LOCALTIMER, err); |
e700e41d | 464 | } |
a8cb6041 MZ |
465 | |
466 | local_timer_register(&exynos4_mct_tick_ops); | |
991a6c7d | 467 | #endif /* CONFIG_LOCAL_TIMERS */ |
30d8bead CY |
468 | } |
469 | ||
6bb27d73 | 470 | void __init exynos4_timer_init(void) |
30d8bead | 471 | { |
2edb36c4 KK |
472 | if (soc_is_exynos5440()) { |
473 | arch_timer_of_register(); | |
474 | return; | |
475 | } | |
476 | ||
eeed66e3 | 477 | if ((soc_is_exynos4210()) || (soc_is_exynos5250())) |
3a062281 CY |
478 | mct_int_type = MCT_INT_SPI; |
479 | else | |
480 | mct_int_type = MCT_INT_PPI; | |
481 | ||
30d8bead CY |
482 | exynos4_timer_resources(); |
483 | exynos4_clocksource_init(); | |
484 | exynos4_clockevent_init(); | |
485 | } |