aboutsummaryrefslogtreecommitdiffstats
path: root/arch/arm/plat-samsung
diff options
context:
space:
mode:
authorKukjin Kim <kgene.kim@samsung.com>2012-04-17 14:20:49 -0400
committerKukjin Kim <kgene.kim@samsung.com>2012-05-12 18:01:43 -0400
commit33bf33278fa608428c46d4fab58ae5bc3e3acfde (patch)
tree06ae04e544da1f5ac48cbf4b3168cb3e4b98e03a /arch/arm/plat-samsung
parent8eadcf74ec8285800f7f6a36a8f283932a69d17f (diff)
ARM: SAMSUNG: move hr timer for common s5p into plat-samsung
Signed-off-by: Kukjin Kim <kgene.kim@samsung.com>
Diffstat (limited to 'arch/arm/plat-samsung')
-rw-r--r--arch/arm/plat-samsung/Kconfig8
-rw-r--r--arch/arm/plat-samsung/Makefile2
-rw-r--r--arch/arm/plat-samsung/s5p-time.c405
3 files changed, 415 insertions, 0 deletions
diff --git a/arch/arm/plat-samsung/Kconfig b/arch/arm/plat-samsung/Kconfig
index 218462361e31..1922413f2ee7 100644
--- a/arch/arm/plat-samsung/Kconfig
+++ b/arch/arm/plat-samsung/Kconfig
@@ -50,6 +50,14 @@ config S3C_LOWLEVEL_UART_PORT
50 this configuration should be between zero and two. The port 50 this configuration should be between zero and two. The port
51 must have been initialised by the boot-loader before use. 51 must have been initialised by the boot-loader before use.
52 52
53# timer options
54
55config S5P_HRT
56 bool
57 select SAMSUNG_DEV_PWM
58 help
59 Use the High Resolution timer support
60
53# clock options 61# clock options
54 62
55config SAMSUNG_CLKSRC 63config SAMSUNG_CLKSRC
diff --git a/arch/arm/plat-samsung/Makefile b/arch/arm/plat-samsung/Makefile
index caa6f9af1f3d..6217c41e6260 100644
--- a/arch/arm/plat-samsung/Makefile
+++ b/arch/arm/plat-samsung/Makefile
@@ -13,6 +13,8 @@ obj- :=
13 13
14obj-y += init.o cpu.o 14obj-y += init.o cpu.o
15obj-$(CONFIG_ARCH_USES_GETTIMEOFFSET) += time.o 15obj-$(CONFIG_ARCH_USES_GETTIMEOFFSET) += time.o
16obj-$(CONFIG_S5P_HRT) += s5p-time.o
17
16obj-y += clock.o 18obj-y += clock.o
17obj-y += pwm-clock.o 19obj-y += pwm-clock.o
18 20
diff --git a/arch/arm/plat-samsung/s5p-time.c b/arch/arm/plat-samsung/s5p-time.c
new file mode 100644
index 000000000000..028b6e877eb9
--- /dev/null
+++ b/arch/arm/plat-samsung/s5p-time.c
@@ -0,0 +1,405 @@
1/*
2 * Copyright (c) 2011 Samsung Electronics Co., Ltd.
3 * http://www.samsung.com/
4 *
5 * S5P - Common hr-timer support
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/platform_device.h>
18
19#include <asm/smp_twd.h>
20#include <asm/mach/time.h>
21#include <asm/mach/arch.h>
22#include <asm/mach/map.h>
23#include <asm/sched_clock.h>
24
25#include <mach/map.h>
26#include <plat/devs.h>
27#include <plat/regs-timer.h>
28#include <plat/s5p-time.h>
29
30static struct clk *tin_event;
31static struct clk *tin_source;
32static struct clk *tdiv_event;
33static struct clk *tdiv_source;
34static struct clk *timerclk;
35static struct s5p_timer_source timer_source;
36static unsigned long clock_count_per_tick;
37static void s5p_timer_resume(void);
38
39static void s5p_time_stop(enum s5p_timer_mode mode)
40{
41 unsigned long tcon;
42
43 tcon = __raw_readl(S3C2410_TCON);
44
45 switch (mode) {
46 case S5P_PWM0:
47 tcon &= ~S3C2410_TCON_T0START;
48 break;
49
50 case S5P_PWM1:
51 tcon &= ~S3C2410_TCON_T1START;
52 break;
53
54 case S5P_PWM2:
55 tcon &= ~S3C2410_TCON_T2START;
56 break;
57
58 case S5P_PWM3:
59 tcon &= ~S3C2410_TCON_T3START;
60 break;
61
62 case S5P_PWM4:
63 tcon &= ~S3C2410_TCON_T4START;
64 break;
65
66 default:
67 printk(KERN_ERR "Invalid Timer %d\n", mode);
68 break;
69 }
70 __raw_writel(tcon, S3C2410_TCON);
71}
72
73static void s5p_time_setup(enum s5p_timer_mode mode, unsigned long tcnt)
74{
75 unsigned long tcon;
76
77 tcon = __raw_readl(S3C2410_TCON);
78
79 tcnt--;
80
81 switch (mode) {
82 case S5P_PWM0:
83 tcon &= ~(0x0f << 0);
84 tcon |= S3C2410_TCON_T0MANUALUPD;
85 break;
86
87 case S5P_PWM1:
88 tcon &= ~(0x0f << 8);
89 tcon |= S3C2410_TCON_T1MANUALUPD;
90 break;
91
92 case S5P_PWM2:
93 tcon &= ~(0x0f << 12);
94 tcon |= S3C2410_TCON_T2MANUALUPD;
95 break;
96
97 case S5P_PWM3:
98 tcon &= ~(0x0f << 16);
99 tcon |= S3C2410_TCON_T3MANUALUPD;
100 break;
101
102 case S5P_PWM4:
103 tcon &= ~(0x07 << 20);
104 tcon |= S3C2410_TCON_T4MANUALUPD;
105 break;
106
107 default:
108 printk(KERN_ERR "Invalid Timer %d\n", mode);
109 break;
110 }
111
112 __raw_writel(tcnt, S3C2410_TCNTB(mode));
113 __raw_writel(tcnt, S3C2410_TCMPB(mode));
114 __raw_writel(tcon, S3C2410_TCON);
115}
116
117static void s5p_time_start(enum s5p_timer_mode mode, bool periodic)
118{
119 unsigned long tcon;
120
121 tcon = __raw_readl(S3C2410_TCON);
122
123 switch (mode) {
124 case S5P_PWM0:
125 tcon |= S3C2410_TCON_T0START;
126 tcon &= ~S3C2410_TCON_T0MANUALUPD;
127
128 if (periodic)
129 tcon |= S3C2410_TCON_T0RELOAD;
130 else
131 tcon &= ~S3C2410_TCON_T0RELOAD;
132 break;
133
134 case S5P_PWM1:
135 tcon |= S3C2410_TCON_T1START;
136 tcon &= ~S3C2410_TCON_T1MANUALUPD;
137
138 if (periodic)
139 tcon |= S3C2410_TCON_T1RELOAD;
140 else
141 tcon &= ~S3C2410_TCON_T1RELOAD;
142 break;
143
144 case S5P_PWM2:
145 tcon |= S3C2410_TCON_T2START;
146 tcon &= ~S3C2410_TCON_T2MANUALUPD;
147
148 if (periodic)
149 tcon |= S3C2410_TCON_T2RELOAD;
150 else
151 tcon &= ~S3C2410_TCON_T2RELOAD;
152 break;
153
154 case S5P_PWM3:
155 tcon |= S3C2410_TCON_T3START;
156 tcon &= ~S3C2410_TCON_T3MANUALUPD;
157
158 if (periodic)
159 tcon |= S3C2410_TCON_T3RELOAD;
160 else
161 tcon &= ~S3C2410_TCON_T3RELOAD;
162 break;
163
164 case S5P_PWM4:
165 tcon |= S3C2410_TCON_T4START;
166 tcon &= ~S3C2410_TCON_T4MANUALUPD;
167
168 if (periodic)
169 tcon |= S3C2410_TCON_T4RELOAD;
170 else
171 tcon &= ~S3C2410_TCON_T4RELOAD;
172 break;
173
174 default:
175 printk(KERN_ERR "Invalid Timer %d\n", mode);
176 break;
177 }
178 __raw_writel(tcon, S3C2410_TCON);
179}
180
181static int s5p_set_next_event(unsigned long cycles,
182 struct clock_event_device *evt)
183{
184 s5p_time_setup(timer_source.event_id, cycles);
185 s5p_time_start(timer_source.event_id, NON_PERIODIC);
186
187 return 0;
188}
189
190static void s5p_set_mode(enum clock_event_mode mode,
191 struct clock_event_device *evt)
192{
193 s5p_time_stop(timer_source.event_id);
194
195 switch (mode) {
196 case CLOCK_EVT_MODE_PERIODIC:
197 s5p_time_setup(timer_source.event_id, clock_count_per_tick);
198 s5p_time_start(timer_source.event_id, PERIODIC);
199 break;
200
201 case CLOCK_EVT_MODE_ONESHOT:
202 break;
203
204 case CLOCK_EVT_MODE_UNUSED:
205 case CLOCK_EVT_MODE_SHUTDOWN:
206 break;
207
208 case CLOCK_EVT_MODE_RESUME:
209 s5p_timer_resume();
210 break;
211 }
212}
213
214static void s5p_timer_resume(void)
215{
216 /* event timer restart */
217 s5p_time_setup(timer_source.event_id, clock_count_per_tick);
218 s5p_time_start(timer_source.event_id, PERIODIC);
219
220 /* source timer restart */
221 s5p_time_setup(timer_source.source_id, TCNT_MAX);
222 s5p_time_start(timer_source.source_id, PERIODIC);
223}
224
225void __init s5p_set_timer_source(enum s5p_timer_mode event,
226 enum s5p_timer_mode source)
227{
228 s3c_device_timer[event].dev.bus = &platform_bus_type;
229 s3c_device_timer[source].dev.bus = &platform_bus_type;
230
231 timer_source.event_id = event;
232 timer_source.source_id = source;
233}
234
235static struct clock_event_device time_event_device = {
236 .name = "s5p_event_timer",
237 .features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
238 .rating = 200,
239 .set_next_event = s5p_set_next_event,
240 .set_mode = s5p_set_mode,
241};
242
243static irqreturn_t s5p_clock_event_isr(int irq, void *dev_id)
244{
245 struct clock_event_device *evt = dev_id;
246
247 evt->event_handler(evt);
248
249 return IRQ_HANDLED;
250}
251
252static struct irqaction s5p_clock_event_irq = {
253 .name = "s5p_time_irq",
254 .flags = IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL,
255 .handler = s5p_clock_event_isr,
256 .dev_id = &time_event_device,
257};
258
259static void __init s5p_clockevent_init(void)
260{
261 unsigned long pclk;
262 unsigned long clock_rate;
263 unsigned int irq_number;
264 struct clk *tscaler;
265
266 pclk = clk_get_rate(timerclk);
267
268 tscaler = clk_get_parent(tdiv_event);
269
270 clk_set_rate(tscaler, pclk / 2);
271 clk_set_rate(tdiv_event, pclk / 2);
272 clk_set_parent(tin_event, tdiv_event);
273
274 clock_rate = clk_get_rate(tin_event);
275 clock_count_per_tick = clock_rate / HZ;
276
277 clockevents_calc_mult_shift(&time_event_device,
278 clock_rate, S5PTIMER_MIN_RANGE);
279 time_event_device.max_delta_ns =
280 clockevent_delta2ns(-1, &time_event_device);
281 time_event_device.min_delta_ns =
282 clockevent_delta2ns(1, &time_event_device);
283
284 time_event_device.cpumask = cpumask_of(0);
285 clockevents_register_device(&time_event_device);
286
287 irq_number = timer_source.event_id + IRQ_TIMER0;
288 setup_irq(irq_number, &s5p_clock_event_irq);
289}
290
291static void __iomem *s5p_timer_reg(void)
292{
293 unsigned long offset = 0;
294
295 switch (timer_source.source_id) {
296 case S5P_PWM0:
297 case S5P_PWM1:
298 case S5P_PWM2:
299 case S5P_PWM3:
300 offset = (timer_source.source_id * 0x0c) + 0x14;
301 break;
302
303 case S5P_PWM4:
304 offset = 0x40;
305 break;
306
307 default:
308 printk(KERN_ERR "Invalid Timer %d\n", timer_source.source_id);
309 return NULL;
310 }
311
312 return S3C_TIMERREG(offset);
313}
314
315/*
316 * Override the global weak sched_clock symbol with this
317 * local implementation which uses the clocksource to get some
318 * better resolution when scheduling the kernel. We accept that
319 * this wraps around for now, since it is just a relative time
320 * stamp. (Inspired by U300 implementation.)
321 */
322static u32 notrace s5p_read_sched_clock(void)
323{
324 void __iomem *reg = s5p_timer_reg();
325
326 if (!reg)
327 return 0;
328
329 return ~__raw_readl(reg);
330}
331
332static void __init s5p_clocksource_init(void)
333{
334 unsigned long pclk;
335 unsigned long clock_rate;
336
337 pclk = clk_get_rate(timerclk);
338
339 clk_set_rate(tdiv_source, pclk / 2);
340 clk_set_parent(tin_source, tdiv_source);
341
342 clock_rate = clk_get_rate(tin_source);
343
344 s5p_time_setup(timer_source.source_id, TCNT_MAX);
345 s5p_time_start(timer_source.source_id, PERIODIC);
346
347 setup_sched_clock(s5p_read_sched_clock, 32, clock_rate);
348
349 if (clocksource_mmio_init(s5p_timer_reg(), "s5p_clocksource_timer",
350 clock_rate, 250, 32, clocksource_mmio_readl_down))
351 panic("s5p_clocksource_timer: can't register clocksource\n");
352}
353
354static void __init s5p_timer_resources(void)
355{
356
357 unsigned long event_id = timer_source.event_id;
358 unsigned long source_id = timer_source.source_id;
359 char devname[15];
360
361 timerclk = clk_get(NULL, "timers");
362 if (IS_ERR(timerclk))
363 panic("failed to get timers clock for timer");
364
365 clk_enable(timerclk);
366
367 sprintf(devname, "s3c24xx-pwm.%lu", event_id);
368 s3c_device_timer[event_id].id = event_id;
369 s3c_device_timer[event_id].dev.init_name = devname;
370
371 tin_event = clk_get(&s3c_device_timer[event_id].dev, "pwm-tin");
372 if (IS_ERR(tin_event))
373 panic("failed to get pwm-tin clock for event timer");
374
375 tdiv_event = clk_get(&s3c_device_timer[event_id].dev, "pwm-tdiv");
376 if (IS_ERR(tdiv_event))
377 panic("failed to get pwm-tdiv clock for event timer");
378
379 clk_enable(tin_event);
380
381 sprintf(devname, "s3c24xx-pwm.%lu", source_id);
382 s3c_device_timer[source_id].id = source_id;
383 s3c_device_timer[source_id].dev.init_name = devname;
384
385 tin_source = clk_get(&s3c_device_timer[source_id].dev, "pwm-tin");
386 if (IS_ERR(tin_source))
387 panic("failed to get pwm-tin clock for source timer");
388
389 tdiv_source = clk_get(&s3c_device_timer[source_id].dev, "pwm-tdiv");
390 if (IS_ERR(tdiv_source))
391 panic("failed to get pwm-tdiv clock for source timer");
392
393 clk_enable(tin_source);
394}
395
396static void __init s5p_timer_init(void)
397{
398 s5p_timer_resources();
399 s5p_clockevent_init();
400 s5p_clocksource_init();
401}
402
403struct sys_timer s5p_timer = {
404 .init = s5p_timer_init,
405};