aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTomasz Figa <t.figa@samsung.com>2013-04-20 17:22:13 -0400
committerArnd Bergmann <arnd@arndb.de>2013-04-20 18:21:05 -0400
commitf11899894c0a683c754ca71b45f7c9c3d35a3a1c (patch)
tree5e3ec9824da92d1f9394e09d3f4f9be6b4fbd182
parent3d5a96582303e28c48699f3faaf920ef7d43e6f2 (diff)
clocksource: add samsung pwm timer driver
This adds a new clocksource driver for the PWM timer that is present in most Samsung SoCs, based on the existing driver in arch/arm/plat-samsung/samsung-time.c and many changes implemented by Tomasz Figa. Originally, the conversion of all Samsung machines to the new driver was planned for 3.10, but that work ended up being too late and too invasive just before the merge window. Unfortunately, other changes in the Exynos platform resulted in some Exynos4 setups, particularly the Universal C210 board to be broken. In order to fix that with minimum risk, so we now leave the existing pwm clocksource driver in place for all older platforms and use the new driver only for device tree enabled boards. This way, we can get the broken machines running again using DT descriptions. All clocksource changes were implemented by Tomasz, while the DT registration was rewritten by Arnd. Signed-off-by: Arnd Bergmann <arnd@arndb.de> Cc: Tomasz Figa <t.figa@samsung.com> Cc: Kyungmin Park <kyungmin.park@samsung.com> Cc: Kukjin Kim <kgene.kim@samsung.com> Cc: Ben Dooks <ben-linux@fluff.org> Cc: John Stultz <john.stultz@linaro.org> Cc: Thomas Gleixner <tglx@linutronix.de>
-rw-r--r--drivers/clocksource/Kconfig10
-rw-r--r--drivers/clocksource/Makefile1
-rw-r--r--drivers/clocksource/samsung_pwm_timer.c477
-rw-r--r--include/clocksource/samsung_pwm.h41
4 files changed, 529 insertions, 0 deletions
diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig
index e507ab7df60b..03caba2540e2 100644
--- a/drivers/clocksource/Kconfig
+++ b/drivers/clocksource/Kconfig
@@ -67,3 +67,13 @@ config CLKSRC_METAG_GENERIC
67 def_bool y if METAG 67 def_bool y if METAG
68 help 68 help
69 This option enables support for the Meta per-thread timers. 69 This option enables support for the Meta per-thread timers.
70
71config CLKSRC_SAMSUNG_PWM
72 def_bool ARCH_EXYNOS4
73 depends on OF
74 select CLKSRC_MMIO
75 help
76 This is a new clocksource driver for the PWM timer found in
77 Samsung S3C, S5P and Exynos SoCs, replacing an earlier driver
78 for all devicetree enabled platforms. This driver will be
79 needed only on systems that do not have the Exynos MCT available.
diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile
index 4d8283aec5b5..891c9f2af021 100644
--- a/drivers/clocksource/Makefile
+++ b/drivers/clocksource/Makefile
@@ -19,6 +19,7 @@ obj-$(CONFIG_ARCH_BCM2835) += bcm2835_timer.o
19obj-$(CONFIG_SUNXI_TIMER) += sunxi_timer.o 19obj-$(CONFIG_SUNXI_TIMER) += sunxi_timer.o
20obj-$(CONFIG_ARCH_TEGRA) += tegra20_timer.o 20obj-$(CONFIG_ARCH_TEGRA) += tegra20_timer.o
21obj-$(CONFIG_VT8500_TIMER) += vt8500_timer.o 21obj-$(CONFIG_VT8500_TIMER) += vt8500_timer.o
22obj-$(CONFIG_CLKSRC_SAMSUNG_PWM) += samsung_pwm_timer.o
22 23
23obj-$(CONFIG_ARM_ARCH_TIMER) += arm_arch_timer.o 24obj-$(CONFIG_ARM_ARCH_TIMER) += arm_arch_timer.o
24obj-$(CONFIG_CLKSRC_METAG_GENERIC) += metag_generic.o 25obj-$(CONFIG_CLKSRC_METAG_GENERIC) += metag_generic.o
diff --git a/drivers/clocksource/samsung_pwm_timer.c b/drivers/clocksource/samsung_pwm_timer.c
new file mode 100644
index 000000000000..1752457a4f76
--- /dev/null
+++ b/drivers/clocksource/samsung_pwm_timer.c
@@ -0,0 +1,477 @@
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
52struct samsung_timer_source {
53 unsigned int event_id;
54 unsigned int source_id;
55 unsigned int tcnt_max;
56 unsigned int tscaler_div;
57 unsigned int tdiv;
58};
59
60static struct samsung_pwm *pwm;
61static struct clk *timerclk;
62static struct samsung_timer_source timer_source;
63static unsigned long clock_count_per_tick;
64
65static void samsung_timer_set_prescale(struct samsung_pwm *pwm,
66 unsigned int channel, u16 prescale)
67{
68 unsigned long flags;
69 u8 shift = 0;
70 u32 reg;
71
72 if (channel >= 2)
73 shift = TCFG0_PRESCALER1_SHIFT;
74
75 spin_lock_irqsave(&pwm->slock, flags);
76
77 reg = readl(pwm->base + REG_TCFG0);
78 reg &= ~(TCFG0_PRESCALER_MASK << shift);
79 reg |= (prescale - 1) << shift;
80 writel(reg, pwm->base + REG_TCFG0);
81
82 spin_unlock_irqrestore(&pwm->slock, flags);
83}
84
85static void samsung_timer_set_divisor(struct samsung_pwm *pwm,
86 unsigned int channel, u8 divisor)
87{
88 u8 shift = TCFG1_SHIFT(channel);
89 unsigned long flags;
90 u32 reg;
91 u8 bits;
92
93 bits = (fls(divisor) - 1) - pwm->variant.div_base;
94
95 spin_lock_irqsave(&pwm->slock, flags);
96
97 reg = readl(pwm->base + REG_TCFG1);
98 reg &= ~(TCFG1_MUX_MASK << shift);
99 reg |= bits << shift;
100 writel(reg, pwm->base + REG_TCFG1);
101
102 spin_unlock_irqrestore(&pwm->slock, flags);
103}
104
105static void samsung_time_stop(unsigned int channel)
106{
107 unsigned long tcon;
108 unsigned long flags;
109
110 if (channel > 0)
111 ++channel;
112
113 spin_lock_irqsave(&pwm->slock, flags);
114
115 tcon = __raw_readl(pwm->base + REG_TCON);
116 tcon &= ~TCON_START(channel);
117 __raw_writel(tcon, pwm->base + REG_TCON);
118
119 spin_unlock_irqrestore(&pwm->slock, flags);
120}
121
122static void samsung_time_setup(unsigned int channel, unsigned long tcnt)
123{
124 unsigned long tcon;
125 unsigned long flags;
126 unsigned int tcon_chan = channel;
127
128 if (tcon_chan > 0)
129 ++tcon_chan;
130
131 spin_lock_irqsave(&pwm->slock, flags);
132
133 tcon = __raw_readl(pwm->base + REG_TCON);
134
135 tcnt--;
136
137 tcon &= ~(TCON_START(tcon_chan) | TCON_AUTORELOAD(tcon_chan));
138 tcon |= TCON_MANUALUPDATE(tcon_chan);
139
140 __raw_writel(tcnt, pwm->base + REG_TCNTB(channel));
141 __raw_writel(tcnt, pwm->base + REG_TCMPB(channel));
142 __raw_writel(tcon, pwm->base + REG_TCON);
143
144 spin_unlock_irqrestore(&pwm->slock, flags);
145}
146
147static void samsung_time_start(unsigned int channel, bool periodic)
148{
149 unsigned long tcon;
150 unsigned long flags;
151
152 if (channel > 0)
153 ++channel;
154
155 spin_lock_irqsave(&pwm->slock, flags);
156
157 tcon = __raw_readl(pwm->base + REG_TCON);
158
159 tcon &= ~TCON_MANUALUPDATE(channel);
160 tcon |= TCON_START(channel);
161
162 if (periodic)
163 tcon |= TCON_AUTORELOAD(channel);
164 else
165 tcon &= ~TCON_AUTORELOAD(channel);
166
167 __raw_writel(tcon, pwm->base + REG_TCON);
168
169 spin_unlock_irqrestore(&pwm->slock, flags);
170}
171
172static int samsung_set_next_event(unsigned long cycles,
173 struct clock_event_device *evt)
174{
175 samsung_time_setup(timer_source.event_id, cycles);
176 samsung_time_start(timer_source.event_id, false);
177
178 return 0;
179}
180
181static void samsung_timer_resume(void)
182{
183 /* event timer restart */
184 samsung_time_setup(timer_source.event_id, clock_count_per_tick);
185 samsung_time_start(timer_source.event_id, true);
186
187 /* source timer restart */
188 samsung_time_setup(timer_source.source_id, timer_source.tcnt_max);
189 samsung_time_start(timer_source.source_id, true);
190}
191
192static void samsung_set_mode(enum clock_event_mode mode,
193 struct clock_event_device *evt)
194{
195 samsung_time_stop(timer_source.event_id);
196
197 switch (mode) {
198 case CLOCK_EVT_MODE_PERIODIC:
199 samsung_time_setup(timer_source.event_id, clock_count_per_tick);
200 samsung_time_start(timer_source.event_id, true);
201 break;
202
203 case CLOCK_EVT_MODE_ONESHOT:
204 break;
205
206 case CLOCK_EVT_MODE_UNUSED:
207 case CLOCK_EVT_MODE_SHUTDOWN:
208 break;
209
210 case CLOCK_EVT_MODE_RESUME:
211 samsung_timer_resume();
212 break;
213 }
214}
215
216static struct clock_event_device time_event_device = {
217 .name = "samsung_event_timer",
218 .features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
219 .rating = 200,
220 .set_next_event = samsung_set_next_event,
221 .set_mode = samsung_set_mode,
222};
223
224static irqreturn_t samsung_clock_event_isr(int irq, void *dev_id)
225{
226 struct clock_event_device *evt = dev_id;
227
228 if (pwm->variant.has_tint_cstat) {
229 u32 mask = (1 << timer_source.event_id);
230 writel(mask | (mask << 5), pwm->base + REG_TINT_CSTAT);
231 }
232
233 evt->event_handler(evt);
234
235 return IRQ_HANDLED;
236}
237
238static struct irqaction samsung_clock_event_irq = {
239 .name = "samsung_time_irq",
240 .flags = IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL,
241 .handler = samsung_clock_event_isr,
242 .dev_id = &time_event_device,
243};
244
245static void __init samsung_clockevent_init(void)
246{
247 unsigned long pclk;
248 unsigned long clock_rate;
249 unsigned int irq_number;
250
251 pclk = clk_get_rate(timerclk);
252
253 samsung_timer_set_prescale(pwm, timer_source.event_id,
254 timer_source.tscaler_div);
255 samsung_timer_set_divisor(pwm, timer_source.event_id,
256 timer_source.tdiv);
257
258 clock_rate = pclk / (timer_source.tscaler_div * timer_source.tdiv);
259 clock_count_per_tick = clock_rate / HZ;
260
261 time_event_device.cpumask = cpumask_of(0);
262 clockevents_config_and_register(&time_event_device, clock_rate, 1, -1);
263
264 irq_number = pwm->irq[timer_source.event_id];
265 setup_irq(irq_number, &samsung_clock_event_irq);
266
267 if (pwm->variant.has_tint_cstat) {
268 u32 mask = (1 << timer_source.event_id);
269 writel(mask | (mask << 5), pwm->base + REG_TINT_CSTAT);
270 }
271}
272
273static void __iomem *samsung_timer_reg(void)
274{
275 switch (timer_source.source_id) {
276 case 0:
277 case 1:
278 case 2:
279 case 3:
280 return pwm->base + timer_source.source_id * 0x0c + 0x14;
281
282 case 4:
283 return pwm->base + 0x40;
284
285 default:
286 BUG();
287 }
288}
289
290/*
291 * Override the global weak sched_clock symbol with this
292 * local implementation which uses the clocksource to get some
293 * better resolution when scheduling the kernel. We accept that
294 * this wraps around for now, since it is just a relative time
295 * stamp. (Inspired by U300 implementation.)
296 */
297static u32 notrace samsung_read_sched_clock(void)
298{
299 void __iomem *reg = samsung_timer_reg();
300
301 if (!reg)
302 return 0;
303
304 return ~__raw_readl(reg);
305}
306
307static void __init samsung_clocksource_init(void)
308{
309 void __iomem *reg = samsung_timer_reg();
310 unsigned long pclk;
311 unsigned long clock_rate;
312 int ret;
313
314 pclk = clk_get_rate(timerclk);
315
316 samsung_timer_set_prescale(pwm, timer_source.source_id,
317 timer_source.tscaler_div);
318 samsung_timer_set_divisor(pwm, timer_source.source_id,
319 timer_source.tdiv);
320
321 clock_rate = pclk / (timer_source.tscaler_div * timer_source.tdiv);
322
323 samsung_time_setup(timer_source.source_id, timer_source.tcnt_max);
324 samsung_time_start(timer_source.source_id, true);
325
326 setup_sched_clock(samsung_read_sched_clock,
327 pwm->variant.bits, clock_rate);
328
329 ret = clocksource_mmio_init(reg, "samsung_clocksource_timer",
330 clock_rate, 250, pwm->variant.bits,
331 clocksource_mmio_readl_down);
332 if (ret)
333 panic("samsung_clocksource_timer: can't register clocksource\n");
334}
335
336static void __init samsung_timer_resources(void)
337{
338 timerclk = clk_get(NULL, "timers");
339 if (IS_ERR(timerclk))
340 panic("failed to get timers clock for timer");
341
342 clk_prepare_enable(timerclk);
343
344 timer_source.tcnt_max = (1UL << pwm->variant.bits) - 1;
345 if (pwm->variant.bits == 16) {
346 timer_source.tscaler_div = 25;
347 timer_source.tdiv = 2;
348 } else {
349 timer_source.tscaler_div = 2;
350 timer_source.tdiv = 1;
351 }
352}
353
354/*
355 * PWM master driver
356 */
357static void __init samsung_pwm_clocksource_init(void)
358{
359 u8 mask;
360 int channel;
361
362 if (!pwm)
363 panic("no pwm clocksource device found");
364
365 mask = ~pwm->variant.output_mask & ((1 << SAMSUNG_PWM_NUM) - 1);
366 channel = fls(mask) - 1;
367 if (channel < 0)
368 panic("failed to find PWM channel for clocksource");
369 timer_source.source_id = channel;
370
371 mask &= ~(1 << channel);
372 channel = fls(mask) - 1;
373 if (channel < 0)
374 panic("failed to find PWM channel for clock event");
375 timer_source.event_id = channel;
376
377 samsung_timer_resources();
378 samsung_clockevent_init();
379 samsung_clocksource_init();
380}
381
382static void __init samsung_pwm_alloc(struct device_node *np,
383 const struct samsung_pwm_variant *variant)
384{
385 struct resource res;
386 struct property *prop;
387 const __be32 *cur;
388 u32 val;
389 int i;
390
391 pwm = kzalloc(sizeof(*pwm), GFP_KERNEL);
392 if (!pwm) {
393 pr_err("%s: could not allocate PWM device struct\n", __func__);
394 return;
395 }
396 memcpy(&pwm->variant, variant, sizeof(pwm->variant));
397 spin_lock_init(&pwm->slock);
398 for (i = 0; i < SAMSUNG_PWM_NUM; ++i)
399 pwm->irq[i] = irq_of_parse_and_map(np, i);
400
401 of_property_for_each_u32(np, "samsung,pwm-outputs", prop, cur, val) {
402 if (val >= SAMSUNG_PWM_NUM) {
403 pr_warning("%s: invalid channel index in samsung,pwm-outputs property\n",
404 __func__);
405 continue;
406 }
407 pwm->variant.output_mask |= 1 << val;
408 }
409
410 of_address_to_resource(np, 0, &res);
411 if (!request_mem_region(res.start,
412 resource_size(&res), "samsung-pwm")) {
413 pr_err("%s: failed to request IO mem region\n", __func__);
414 return;
415 }
416
417 pwm->base = ioremap(res.start, resource_size(&res));
418 if (!pwm->base) {
419 pr_err("%s: failed to map PWM registers\n", __func__);
420 release_mem_region(res.start, resource_size(&res));
421 return;
422 }
423
424 samsung_pwm_clocksource_init();
425}
426
427static const struct samsung_pwm_variant s3c24xx_variant = {
428 .bits = 16,
429 .div_base = 1,
430 .has_tint_cstat = false,
431 .tclk_mask = (1 << 4),
432};
433
434static void __init s3c2410_pwm_clocksource_init(struct device_node *np)
435{
436 samsung_pwm_alloc(np, &s3c24xx_variant);
437}
438CLOCKSOURCE_OF_DECLARE(s3c2410_pwm, "samsung,s3c2410-pwm", s3c2410_pwm_clocksource_init);
439
440static const struct samsung_pwm_variant s3c64xx_variant = {
441 .bits = 32,
442 .div_base = 0,
443 .has_tint_cstat = true,
444 .tclk_mask = (1 << 7) | (1 << 6) | (1 << 5),
445};
446
447static void __init s3c64xx_pwm_clocksource_init(struct device_node *np)
448{
449 samsung_pwm_alloc(np, &s3c64xx_variant);
450}
451CLOCKSOURCE_OF_DECLARE(s3c6400_pwm, "samsung,s3c6400-pwm", s3c64xx_pwm_clocksource_init);
452
453static const struct samsung_pwm_variant s5p64x0_variant = {
454 .bits = 32,
455 .div_base = 0,
456 .has_tint_cstat = true,
457 .tclk_mask = 0,
458};
459
460static void __init s5p64x0_pwm_clocksource_init(struct device_node *np)
461{
462 samsung_pwm_alloc(np, &s5p64x0_variant);
463}
464CLOCKSOURCE_OF_DECLARE(s5p6440_pwm, "samsung,s5p6440-pwm", s5p64x0_pwm_clocksource_init);
465
466static const struct samsung_pwm_variant s5p_variant = {
467 .bits = 32,
468 .div_base = 0,
469 .has_tint_cstat = true,
470 .tclk_mask = (1 << 5),
471};
472
473static void __init s5p_pwm_clocksource_init(struct device_node *np)
474{
475 samsung_pwm_alloc(np, &s5p_variant);
476}
477CLOCKSOURCE_OF_DECLARE(s5pc100_pwm, "samsung,s5pc100-pwm", s5p_pwm_clocksource_init);
diff --git a/include/clocksource/samsung_pwm.h b/include/clocksource/samsung_pwm.h
new file mode 100644
index 000000000000..eff8668da252
--- /dev/null
+++ b/include/clocksource/samsung_pwm.h
@@ -0,0 +1,41 @@
1/*
2 * Copyright (C) 2013 Samsung Electronics Co., Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 as
6 * published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16#ifndef __CLOCKSOURCE_SAMSUNG_PWM_H
17#define __CLOCKSOURCE_SAMSUNG_PWM_H
18
19#include <linux/spinlock.h>
20
21#define SAMSUNG_PWM_NUM 5
22
23struct platform_device;
24struct device_node;
25
26struct samsung_pwm_variant {
27 u8 bits;
28 u8 div_base;
29 u8 tclk_mask;
30 u8 output_mask;
31 bool has_tint_cstat;
32};
33
34struct samsung_pwm {
35 struct samsung_pwm_variant variant;
36 spinlock_t slock;
37 void __iomem *base;
38 int irq[SAMSUNG_PWM_NUM];
39};
40
41#endif /* __CLOCKSOURCE_SAMSUNG_PWM_H */