aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/clocksource
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2013-05-07 14:28:42 -0400
committerLinus Torvalds <torvalds@linux-foundation.org>2013-05-07 14:28:42 -0400
commitbb9055b2744ada735a2fe555c4196ad39a83ef2a (patch)
tree014e0f462217cfa49988872a5134ae41b90b2e9c /drivers/clocksource
parent1bf25e78af317e6d5d9b5594dfeb0036e0d589d6 (diff)
parent241a9871263f3114717c0ed416a1bd1d2415d1fb (diff)
Merge tag 'multiplatform-for-linus-2' of git://git.kernel.org/pub/scm/linux/kernel/git/arm/arm-soc
Pull late ARM Exynos multiplatform changes from Arnd Bergmann: "These continue the multiplatform support for exynos, adding support for building most of the essential drivers (clocksource, clk, irqchip) when combined with other platforms. As a result, it should become really easy to add full multiplatform exynos support in 3.11, although we don't yet enable it for 3.10. The changes were not included in the earlier multiplatform series in order to avoid clashes with the other Exynos updates. This also includes work from Tomasz Figa to fix the pwm clocksource code on Exynos, which is not strictly required for multiplatform, but related to the other patches in this set and needed as a bug fix for at least one board." * tag 'multiplatform-for-linus-2' of git://git.kernel.org/pub/scm/linux/kernel/git/arm/arm-soc: (22 commits) ARM: dts: exynops4210: really add universal_c210 dts ARM: dts: exynos4210: Add basic dts file for universal_c210 board ARM: dts: exynos4: Add node for PWM device ARM: SAMSUNG: Do not register legacy timer interrupts on Exynos clocksource: samsung_pwm_timer: Work around rounding errors in clockevents core clocksource: samsung_pwm_timer: Correct programming of clock events clocksource: samsung_pwm_timer: Use proper clockevents max_delta clocksource: samsung_pwm_timer: Add support for non-DT platforms clocksource: samsung_pwm_timer: Drop unused samsung_pwm struct clocksource: samsung_pwm_timer: Keep all driver data in a structure clocksource: samsung_pwm_timer: Make PWM spinlock global clocksource: samsung_pwm_timer: Let platforms select the driver Documentation: Add device tree bindings for Samsung PWM timers clocksource: add samsung pwm timer driver irqchip: exynos: look up irq using irq_find_mapping irqchip: exynos: pass irq_base from platform irqchip: exynos: localize irq lookup for ATAGS irqchip: exynos: allocate combiner_data dynamically irqchip: exynos: pass max combiner number to combiner_init ARM: exynos: add missing properties for combiner IRQs ...
Diffstat (limited to 'drivers/clocksource')
-rw-r--r--drivers/clocksource/Kconfig9
-rw-r--r--drivers/clocksource/Makefile1
-rw-r--r--drivers/clocksource/exynos_mct.c21
-rw-r--r--drivers/clocksource/samsung_pwm_timer.c494
4 files changed, 510 insertions, 15 deletions
diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig
index c20de4a85cbd..f151c6cf27c3 100644
--- a/drivers/clocksource/Kconfig
+++ b/drivers/clocksource/Kconfig
@@ -76,3 +76,12 @@ config CLKSRC_EXYNOS_MCT
76 def_bool y if ARCH_EXYNOS 76 def_bool y if ARCH_EXYNOS
77 help 77 help
78 Support for Multi Core Timer controller on Exynos SoCs. 78 Support for Multi Core Timer controller on Exynos SoCs.
79
80config CLKSRC_SAMSUNG_PWM
81 bool
82 select CLKSRC_MMIO
83 help
84 This is a new clocksource driver for the PWM timer found in
85 Samsung S3C, S5P and Exynos SoCs, replacing an earlier driver
86 for all devicetree enabled platforms. This driver will be
87 needed only on systems that do not have the Exynos MCT available.
diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile
index caacdb63aff9..8d979c72aa94 100644
--- a/drivers/clocksource/Makefile
+++ b/drivers/clocksource/Makefile
@@ -25,6 +25,7 @@ obj-$(CONFIG_VT8500_TIMER) += vt8500_timer.o
25obj-$(CONFIG_ARCH_BCM) += bcm_kona_timer.o 25obj-$(CONFIG_ARCH_BCM) += bcm_kona_timer.o
26obj-$(CONFIG_CADENCE_TTC_TIMER) += cadence_ttc_timer.o 26obj-$(CONFIG_CADENCE_TTC_TIMER) += cadence_ttc_timer.o
27obj-$(CONFIG_CLKSRC_EXYNOS_MCT) += exynos_mct.o 27obj-$(CONFIG_CLKSRC_EXYNOS_MCT) += exynos_mct.o
28obj-$(CONFIG_CLKSRC_SAMSUNG_PWM) += samsung_pwm_timer.o
28 29
29obj-$(CONFIG_ARM_ARCH_TIMER) += arm_arch_timer.o 30obj-$(CONFIG_ARM_ARCH_TIMER) += arm_arch_timer.o
30obj-$(CONFIG_CLKSRC_METAG_GENERIC) += metag_generic.o 31obj-$(CONFIG_CLKSRC_METAG_GENERIC) += metag_generic.o
diff --git a/drivers/clocksource/exynos_mct.c b/drivers/clocksource/exynos_mct.c
index 13a9e4923a03..662fcc065821 100644
--- a/drivers/clocksource/exynos_mct.c
+++ b/drivers/clocksource/exynos_mct.c
@@ -25,11 +25,6 @@
25#include <linux/clocksource.h> 25#include <linux/clocksource.h>
26 26
27#include <asm/localtimer.h> 27#include <asm/localtimer.h>
28
29#include <plat/cpu.h>
30
31#include <mach/map.h>
32#include <mach/irqs.h>
33#include <asm/mach/time.h> 28#include <asm/mach/time.h>
34 29
35#define EXYNOS4_MCTREG(x) (x) 30#define EXYNOS4_MCTREG(x) (x)
@@ -510,18 +505,14 @@ static void __init exynos4_timer_resources(struct device_node *np, void __iomem
510#endif /* CONFIG_LOCAL_TIMERS */ 505#endif /* CONFIG_LOCAL_TIMERS */
511} 506}
512 507
513void __init mct_init(void) 508void __init mct_init(void __iomem *base, int irq_g0, int irq_l0, int irq_l1)
514{ 509{
515 if (soc_is_exynos4210()) { 510 mct_irqs[MCT_G0_IRQ] = irq_g0;
516 mct_irqs[MCT_G0_IRQ] = EXYNOS4_IRQ_MCT_G0; 511 mct_irqs[MCT_L0_IRQ] = irq_l0;
517 mct_irqs[MCT_L0_IRQ] = EXYNOS4_IRQ_MCT_L0; 512 mct_irqs[MCT_L1_IRQ] = irq_l1;
518 mct_irqs[MCT_L1_IRQ] = EXYNOS4_IRQ_MCT_L1; 513 mct_int_type = MCT_INT_SPI;
519 mct_int_type = MCT_INT_SPI;
520 } else {
521 panic("unable to determine mct controller type\n");
522 }
523 514
524 exynos4_timer_resources(NULL, S5P_VA_SYSTIMER); 515 exynos4_timer_resources(NULL, base);
525 exynos4_clocksource_init(); 516 exynos4_clocksource_init();
526 exynos4_clockevent_init(); 517 exynos4_clockevent_init();
527} 518}
diff --git a/drivers/clocksource/samsung_pwm_timer.c b/drivers/clocksource/samsung_pwm_timer.c
new file mode 100644
index 000000000000..0234c8d2c8f2
--- /dev/null
+++ b/drivers/clocksource/samsung_pwm_timer.c
@@ -0,0 +1,494 @@
1/*
2 * Copyright (c) 2011 Samsung Electronics Co., Ltd.
3 * http://www.samsung.com/
4 *
5 * samsung - Common hr-timer support (s3c and s5p)
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License version 2 as
9 * published by the Free Software Foundation.
10*/
11
12#include <linux/interrupt.h>
13#include <linux/irq.h>
14#include <linux/err.h>
15#include <linux/clk.h>
16#include <linux/clockchips.h>
17#include <linux/list.h>
18#include <linux/module.h>
19#include <linux/of.h>
20#include <linux/of_address.h>
21#include <linux/of_irq.h>
22#include <linux/platform_device.h>
23#include <linux/slab.h>
24
25#include <clocksource/samsung_pwm.h>
26
27#include <asm/sched_clock.h>
28
29/*
30 * Clocksource driver
31 */
32
33#define REG_TCFG0 0x00
34#define REG_TCFG1 0x04
35#define REG_TCON 0x08
36#define REG_TINT_CSTAT 0x44
37
38#define REG_TCNTB(chan) (0x0c + 12 * (chan))
39#define REG_TCMPB(chan) (0x10 + 12 * (chan))
40
41#define TCFG0_PRESCALER_MASK 0xff
42#define TCFG0_PRESCALER1_SHIFT 8
43
44#define TCFG1_SHIFT(x) ((x) * 4)
45#define TCFG1_MUX_MASK 0xf
46
47#define TCON_START(chan) (1 << (4 * (chan) + 0))
48#define TCON_MANUALUPDATE(chan) (1 << (4 * (chan) + 1))
49#define TCON_INVERT(chan) (1 << (4 * (chan) + 2))
50#define TCON_AUTORELOAD(chan) (1 << (4 * (chan) + 3))
51
52DEFINE_SPINLOCK(samsung_pwm_lock);
53EXPORT_SYMBOL(samsung_pwm_lock);
54
55struct samsung_pwm_clocksource {
56 void __iomem *base;
57 unsigned int irq[SAMSUNG_PWM_NUM];
58 struct samsung_pwm_variant variant;
59
60 struct clk *timerclk;
61
62 unsigned int event_id;
63 unsigned int source_id;
64 unsigned int tcnt_max;
65 unsigned int tscaler_div;
66 unsigned int tdiv;
67
68 unsigned long clock_count_per_tick;
69};
70
71static struct samsung_pwm_clocksource pwm;
72
73static void samsung_timer_set_prescale(unsigned int channel, u16 prescale)
74{
75 unsigned long flags;
76 u8 shift = 0;
77 u32 reg;
78
79 if (channel >= 2)
80 shift = TCFG0_PRESCALER1_SHIFT;
81
82 spin_lock_irqsave(&samsung_pwm_lock, flags);
83
84 reg = readl(pwm.base + REG_TCFG0);
85 reg &= ~(TCFG0_PRESCALER_MASK << shift);
86 reg |= (prescale - 1) << shift;
87 writel(reg, pwm.base + REG_TCFG0);
88
89 spin_unlock_irqrestore(&samsung_pwm_lock, flags);
90}
91
92static void samsung_timer_set_divisor(unsigned int channel, u8 divisor)
93{
94 u8 shift = TCFG1_SHIFT(channel);
95 unsigned long flags;
96 u32 reg;
97 u8 bits;
98
99 bits = (fls(divisor) - 1) - pwm.variant.div_base;
100
101 spin_lock_irqsave(&samsung_pwm_lock, flags);
102
103 reg = readl(pwm.base + REG_TCFG1);
104 reg &= ~(TCFG1_MUX_MASK << shift);
105 reg |= bits << shift;
106 writel(reg, pwm.base + REG_TCFG1);
107
108 spin_unlock_irqrestore(&samsung_pwm_lock, flags);
109}
110
111static void samsung_time_stop(unsigned int channel)
112{
113 unsigned long tcon;
114 unsigned long flags;
115
116 if (channel > 0)
117 ++channel;
118
119 spin_lock_irqsave(&samsung_pwm_lock, flags);
120
121 tcon = __raw_readl(pwm.base + REG_TCON);
122 tcon &= ~TCON_START(channel);
123 __raw_writel(tcon, pwm.base + REG_TCON);
124
125 spin_unlock_irqrestore(&samsung_pwm_lock, flags);
126}
127
128static void samsung_time_setup(unsigned int channel, unsigned long tcnt)
129{
130 unsigned long tcon;
131 unsigned long flags;
132 unsigned int tcon_chan = channel;
133
134 if (tcon_chan > 0)
135 ++tcon_chan;
136
137 spin_lock_irqsave(&samsung_pwm_lock, flags);
138
139 tcon = __raw_readl(pwm.base + REG_TCON);
140
141 tcon &= ~(TCON_START(tcon_chan) | TCON_AUTORELOAD(tcon_chan));
142 tcon |= TCON_MANUALUPDATE(tcon_chan);
143
144 __raw_writel(tcnt, pwm.base + REG_TCNTB(channel));
145 __raw_writel(tcnt, pwm.base + REG_TCMPB(channel));
146 __raw_writel(tcon, pwm.base + REG_TCON);
147
148 spin_unlock_irqrestore(&samsung_pwm_lock, flags);
149}
150
151static void samsung_time_start(unsigned int channel, bool periodic)
152{
153 unsigned long tcon;
154 unsigned long flags;
155
156 if (channel > 0)
157 ++channel;
158
159 spin_lock_irqsave(&samsung_pwm_lock, flags);
160
161 tcon = __raw_readl(pwm.base + REG_TCON);
162
163 tcon &= ~TCON_MANUALUPDATE(channel);
164 tcon |= TCON_START(channel);
165
166 if (periodic)
167 tcon |= TCON_AUTORELOAD(channel);
168 else
169 tcon &= ~TCON_AUTORELOAD(channel);
170
171 __raw_writel(tcon, pwm.base + REG_TCON);
172
173 spin_unlock_irqrestore(&samsung_pwm_lock, flags);
174}
175
176static int samsung_set_next_event(unsigned long cycles,
177 struct clock_event_device *evt)
178{
179 /*
180 * This check is needed to account for internal rounding
181 * errors inside clockevents core, which might result in
182 * passing cycles = 0, which in turn would not generate any
183 * timer interrupt and hang the system.
184 *
185 * Another solution would be to set up the clockevent device
186 * with min_delta = 2, but this would unnecessarily increase
187 * the minimum sleep period.
188 */
189 if (!cycles)
190 cycles = 1;
191
192 samsung_time_setup(pwm.event_id, cycles);
193 samsung_time_start(pwm.event_id, false);
194
195 return 0;
196}
197
198static void samsung_timer_resume(void)
199{
200 /* event timer restart */
201 samsung_time_setup(pwm.event_id, pwm.clock_count_per_tick - 1);
202 samsung_time_start(pwm.event_id, true);
203
204 /* source timer restart */
205 samsung_time_setup(pwm.source_id, pwm.tcnt_max);
206 samsung_time_start(pwm.source_id, true);
207}
208
209static void samsung_set_mode(enum clock_event_mode mode,
210 struct clock_event_device *evt)
211{
212 samsung_time_stop(pwm.event_id);
213
214 switch (mode) {
215 case CLOCK_EVT_MODE_PERIODIC:
216 samsung_time_setup(pwm.event_id, pwm.clock_count_per_tick - 1);
217 samsung_time_start(pwm.event_id, true);
218 break;
219
220 case CLOCK_EVT_MODE_ONESHOT:
221 break;
222
223 case CLOCK_EVT_MODE_UNUSED:
224 case CLOCK_EVT_MODE_SHUTDOWN:
225 break;
226
227 case CLOCK_EVT_MODE_RESUME:
228 samsung_timer_resume();
229 break;
230 }
231}
232
233static struct clock_event_device time_event_device = {
234 .name = "samsung_event_timer",
235 .features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
236 .rating = 200,
237 .set_next_event = samsung_set_next_event,
238 .set_mode = samsung_set_mode,
239};
240
241static irqreturn_t samsung_clock_event_isr(int irq, void *dev_id)
242{
243 struct clock_event_device *evt = dev_id;
244
245 if (pwm.variant.has_tint_cstat) {
246 u32 mask = (1 << pwm.event_id);
247 writel(mask | (mask << 5), pwm.base + REG_TINT_CSTAT);
248 }
249
250 evt->event_handler(evt);
251
252 return IRQ_HANDLED;
253}
254
255static struct irqaction samsung_clock_event_irq = {
256 .name = "samsung_time_irq",
257 .flags = IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL,
258 .handler = samsung_clock_event_isr,
259 .dev_id = &time_event_device,
260};
261
262static void __init samsung_clockevent_init(void)
263{
264 unsigned long pclk;
265 unsigned long clock_rate;
266 unsigned int irq_number;
267
268 pclk = clk_get_rate(pwm.timerclk);
269
270 samsung_timer_set_prescale(pwm.event_id, pwm.tscaler_div);
271 samsung_timer_set_divisor(pwm.event_id, pwm.tdiv);
272
273 clock_rate = pclk / (pwm.tscaler_div * pwm.tdiv);
274 pwm.clock_count_per_tick = clock_rate / HZ;
275
276 time_event_device.cpumask = cpumask_of(0);
277 clockevents_config_and_register(&time_event_device,
278 clock_rate, 1, pwm.tcnt_max);
279
280 irq_number = pwm.irq[pwm.event_id];
281 setup_irq(irq_number, &samsung_clock_event_irq);
282
283 if (pwm.variant.has_tint_cstat) {
284 u32 mask = (1 << pwm.event_id);
285 writel(mask | (mask << 5), pwm.base + REG_TINT_CSTAT);
286 }
287}
288
289static void __iomem *samsung_timer_reg(void)
290{
291 switch (pwm.source_id) {
292 case 0:
293 case 1:
294 case 2:
295 case 3:
296 return pwm.base + pwm.source_id * 0x0c + 0x14;
297
298 case 4:
299 return pwm.base + 0x40;
300
301 default:
302 BUG();
303 }
304}
305
306/*
307 * Override the global weak sched_clock symbol with this
308 * local implementation which uses the clocksource to get some
309 * better resolution when scheduling the kernel. We accept that
310 * this wraps around for now, since it is just a relative time
311 * stamp. (Inspired by U300 implementation.)
312 */
313static u32 notrace samsung_read_sched_clock(void)
314{
315 void __iomem *reg = samsung_timer_reg();
316
317 if (!reg)
318 return 0;
319
320 return ~__raw_readl(reg);
321}
322
323static void __init samsung_clocksource_init(void)
324{
325 void __iomem *reg = samsung_timer_reg();
326 unsigned long pclk;
327 unsigned long clock_rate;
328 int ret;
329
330 pclk = clk_get_rate(pwm.timerclk);
331
332 samsung_timer_set_prescale(pwm.source_id, pwm.tscaler_div);
333 samsung_timer_set_divisor(pwm.source_id, pwm.tdiv);
334
335 clock_rate = pclk / (pwm.tscaler_div * pwm.tdiv);
336
337 samsung_time_setup(pwm.source_id, pwm.tcnt_max);
338 samsung_time_start(pwm.source_id, true);
339
340 setup_sched_clock(samsung_read_sched_clock,
341 pwm.variant.bits, clock_rate);
342
343 ret = clocksource_mmio_init(reg, "samsung_clocksource_timer",
344 clock_rate, 250, pwm.variant.bits,
345 clocksource_mmio_readl_down);
346 if (ret)
347 panic("samsung_clocksource_timer: can't register clocksource\n");
348}
349
350static void __init samsung_timer_resources(void)
351{
352 pwm.timerclk = clk_get(NULL, "timers");
353 if (IS_ERR(pwm.timerclk))
354 panic("failed to get timers clock for timer");
355
356 clk_prepare_enable(pwm.timerclk);
357
358 pwm.tcnt_max = (1UL << pwm.variant.bits) - 1;
359 if (pwm.variant.bits == 16) {
360 pwm.tscaler_div = 25;
361 pwm.tdiv = 2;
362 } else {
363 pwm.tscaler_div = 2;
364 pwm.tdiv = 1;
365 }
366}
367
368/*
369 * PWM master driver
370 */
371static void __init _samsung_pwm_clocksource_init(void)
372{
373 u8 mask;
374 int channel;
375
376 mask = ~pwm.variant.output_mask & ((1 << SAMSUNG_PWM_NUM) - 1);
377 channel = fls(mask) - 1;
378 if (channel < 0)
379 panic("failed to find PWM channel for clocksource");
380 pwm.source_id = channel;
381
382 mask &= ~(1 << channel);
383 channel = fls(mask) - 1;
384 if (channel < 0)
385 panic("failed to find PWM channel for clock event");
386 pwm.event_id = channel;
387
388 samsung_timer_resources();
389 samsung_clockevent_init();
390 samsung_clocksource_init();
391}
392
393void __init samsung_pwm_clocksource_init(void __iomem *base,
394 unsigned int *irqs, struct samsung_pwm_variant *variant)
395{
396 pwm.base = base;
397 memcpy(&pwm.variant, variant, sizeof(pwm.variant));
398 memcpy(pwm.irq, irqs, SAMSUNG_PWM_NUM * sizeof(*irqs));
399
400 _samsung_pwm_clocksource_init();
401}
402
403#ifdef CONFIG_CLKSRC_OF
404static void __init samsung_pwm_alloc(struct device_node *np,
405 const struct samsung_pwm_variant *variant)
406{
407 struct resource res;
408 struct property *prop;
409 const __be32 *cur;
410 u32 val;
411 int i;
412
413 memcpy(&pwm.variant, variant, sizeof(pwm.variant));
414 for (i = 0; i < SAMSUNG_PWM_NUM; ++i)
415 pwm.irq[i] = irq_of_parse_and_map(np, i);
416
417 of_property_for_each_u32(np, "samsung,pwm-outputs", prop, cur, val) {
418 if (val >= SAMSUNG_PWM_NUM) {
419 pr_warning("%s: invalid channel index in samsung,pwm-outputs property\n",
420 __func__);
421 continue;
422 }
423 pwm.variant.output_mask |= 1 << val;
424 }
425
426 of_address_to_resource(np, 0, &res);
427 if (!request_mem_region(res.start,
428 resource_size(&res), "samsung-pwm")) {
429 pr_err("%s: failed to request IO mem region\n", __func__);
430 return;
431 }
432
433 pwm.base = ioremap(res.start, resource_size(&res));
434 if (!pwm.base) {
435 pr_err("%s: failed to map PWM registers\n", __func__);
436 release_mem_region(res.start, resource_size(&res));
437 return;
438 }
439
440 _samsung_pwm_clocksource_init();
441}
442
443static const struct samsung_pwm_variant s3c24xx_variant = {
444 .bits = 16,
445 .div_base = 1,
446 .has_tint_cstat = false,
447 .tclk_mask = (1 << 4),
448};
449
450static void __init s3c2410_pwm_clocksource_init(struct device_node *np)
451{
452 samsung_pwm_alloc(np, &s3c24xx_variant);
453}
454CLOCKSOURCE_OF_DECLARE(s3c2410_pwm, "samsung,s3c2410-pwm", s3c2410_pwm_clocksource_init);
455
456static const struct samsung_pwm_variant s3c64xx_variant = {
457 .bits = 32,
458 .div_base = 0,
459 .has_tint_cstat = true,
460 .tclk_mask = (1 << 7) | (1 << 6) | (1 << 5),
461};
462
463static void __init s3c64xx_pwm_clocksource_init(struct device_node *np)
464{
465 samsung_pwm_alloc(np, &s3c64xx_variant);
466}
467CLOCKSOURCE_OF_DECLARE(s3c6400_pwm, "samsung,s3c6400-pwm", s3c64xx_pwm_clocksource_init);
468
469static const struct samsung_pwm_variant s5p64x0_variant = {
470 .bits = 32,
471 .div_base = 0,
472 .has_tint_cstat = true,
473 .tclk_mask = 0,
474};
475
476static void __init s5p64x0_pwm_clocksource_init(struct device_node *np)
477{
478 samsung_pwm_alloc(np, &s5p64x0_variant);
479}
480CLOCKSOURCE_OF_DECLARE(s5p6440_pwm, "samsung,s5p6440-pwm", s5p64x0_pwm_clocksource_init);
481
482static const struct samsung_pwm_variant s5p_variant = {
483 .bits = 32,
484 .div_base = 0,
485 .has_tint_cstat = true,
486 .tclk_mask = (1 << 5),
487};
488
489static void __init s5p_pwm_clocksource_init(struct device_node *np)
490{
491 samsung_pwm_alloc(np, &s5p_variant);
492}
493CLOCKSOURCE_OF_DECLARE(s5pc100_pwm, "samsung,s5pc100-pwm", s5p_pwm_clocksource_init);
494#endif