Commit | Line | Data |
---|---|---|
33bf3327 | 1 | /* |
f8bfff8e SK |
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 | ||
f8bfff8e SK |
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 | ||
30 | static struct clk *tin_event; | |
31 | static struct clk *tin_source; | |
32 | static struct clk *tdiv_event; | |
33 | static struct clk *tdiv_source; | |
34 | static struct clk *timerclk; | |
35 | static struct s5p_timer_source timer_source; | |
36 | static unsigned long clock_count_per_tick; | |
37 | static void s5p_timer_resume(void); | |
38 | ||
39 | static 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 | ||
73 | static 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 | ||
117 | static 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 | ||
181 | static 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 | ||
190 | static 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 | ||
214 | static 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 | ||
225 | void __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 | ||
235 | static 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 | ||
243 | static 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 | ||
252 | static 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 | ||
259 | static 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 | ||
f8bfff8e | 277 | time_event_device.cpumask = cpumask_of(0); |
838a2ae8 | 278 | clockevents_config_and_register(&time_event_device, clock_rate, 1, -1); |
f8bfff8e SK |
279 | |
280 | irq_number = timer_source.event_id + IRQ_TIMER0; | |
281 | setup_irq(irq_number, &s5p_clock_event_irq); | |
282 | } | |
283 | ||
df4c144f | 284 | static void __iomem *s5p_timer_reg(void) |
f8bfff8e SK |
285 | { |
286 | unsigned long offset = 0; | |
287 | ||
288 | switch (timer_source.source_id) { | |
289 | case S5P_PWM0: | |
290 | case S5P_PWM1: | |
291 | case S5P_PWM2: | |
292 | case S5P_PWM3: | |
293 | offset = (timer_source.source_id * 0x0c) + 0x14; | |
294 | break; | |
295 | ||
296 | case S5P_PWM4: | |
297 | offset = 0x40; | |
298 | break; | |
299 | ||
300 | default: | |
301 | printk(KERN_ERR "Invalid Timer %d\n", timer_source.source_id); | |
df4c144f | 302 | return NULL; |
f8bfff8e SK |
303 | } |
304 | ||
df4c144f RK |
305 | return S3C_TIMERREG(offset); |
306 | } | |
307 | ||
f8bfff8e SK |
308 | /* |
309 | * Override the global weak sched_clock symbol with this | |
310 | * local implementation which uses the clocksource to get some | |
311 | * better resolution when scheduling the kernel. We accept that | |
312 | * this wraps around for now, since it is just a relative time | |
313 | * stamp. (Inspired by U300 implementation.) | |
314 | */ | |
2f0778af | 315 | static u32 notrace s5p_read_sched_clock(void) |
f8bfff8e | 316 | { |
df4c144f | 317 | void __iomem *reg = s5p_timer_reg(); |
f8bfff8e | 318 | |
df4c144f | 319 | if (!reg) |
f8bfff8e | 320 | return 0; |
f8bfff8e | 321 | |
2f0778af | 322 | return ~__raw_readl(reg); |
f8bfff8e SK |
323 | } |
324 | ||
f8bfff8e SK |
325 | static void __init s5p_clocksource_init(void) |
326 | { | |
327 | unsigned long pclk; | |
328 | unsigned long clock_rate; | |
329 | ||
330 | pclk = clk_get_rate(timerclk); | |
331 | ||
332 | clk_set_rate(tdiv_source, pclk / 2); | |
333 | clk_set_parent(tin_source, tdiv_source); | |
334 | ||
335 | clock_rate = clk_get_rate(tin_source); | |
336 | ||
f8bfff8e SK |
337 | s5p_time_setup(timer_source.source_id, TCNT_MAX); |
338 | s5p_time_start(timer_source.source_id, PERIODIC); | |
339 | ||
2f0778af | 340 | setup_sched_clock(s5p_read_sched_clock, 32, clock_rate); |
27ea7fd2 | 341 | |
0665ccc4 CC |
342 | if (clocksource_mmio_init(s5p_timer_reg(), "s5p_clocksource_timer", |
343 | clock_rate, 250, 32, clocksource_mmio_readl_down)) | |
344 | panic("s5p_clocksource_timer: can't register clocksource\n"); | |
f8bfff8e SK |
345 | } |
346 | ||
347 | static void __init s5p_timer_resources(void) | |
348 | { | |
349 | ||
350 | unsigned long event_id = timer_source.event_id; | |
351 | unsigned long source_id = timer_source.source_id; | |
d8b22d25 | 352 | char devname[15]; |
f8bfff8e SK |
353 | |
354 | timerclk = clk_get(NULL, "timers"); | |
355 | if (IS_ERR(timerclk)) | |
356 | panic("failed to get timers clock for timer"); | |
357 | ||
358 | clk_enable(timerclk); | |
359 | ||
d8b22d25 TA |
360 | sprintf(devname, "s3c24xx-pwm.%lu", event_id); |
361 | s3c_device_timer[event_id].id = event_id; | |
362 | s3c_device_timer[event_id].dev.init_name = devname; | |
363 | ||
f8bfff8e SK |
364 | tin_event = clk_get(&s3c_device_timer[event_id].dev, "pwm-tin"); |
365 | if (IS_ERR(tin_event)) | |
366 | panic("failed to get pwm-tin clock for event timer"); | |
367 | ||
368 | tdiv_event = clk_get(&s3c_device_timer[event_id].dev, "pwm-tdiv"); | |
369 | if (IS_ERR(tdiv_event)) | |
370 | panic("failed to get pwm-tdiv clock for event timer"); | |
371 | ||
372 | clk_enable(tin_event); | |
373 | ||
d8b22d25 TA |
374 | sprintf(devname, "s3c24xx-pwm.%lu", source_id); |
375 | s3c_device_timer[source_id].id = source_id; | |
376 | s3c_device_timer[source_id].dev.init_name = devname; | |
377 | ||
f8bfff8e SK |
378 | tin_source = clk_get(&s3c_device_timer[source_id].dev, "pwm-tin"); |
379 | if (IS_ERR(tin_source)) | |
380 | panic("failed to get pwm-tin clock for source timer"); | |
381 | ||
382 | tdiv_source = clk_get(&s3c_device_timer[source_id].dev, "pwm-tdiv"); | |
383 | if (IS_ERR(tdiv_source)) | |
384 | panic("failed to get pwm-tdiv clock for source timer"); | |
385 | ||
386 | clk_enable(tin_source); | |
387 | } | |
388 | ||
389 | static void __init s5p_timer_init(void) | |
390 | { | |
391 | s5p_timer_resources(); | |
392 | s5p_clockevent_init(); | |
393 | s5p_clocksource_init(); | |
394 | } | |
395 | ||
396 | struct sys_timer s5p_timer = { | |
397 | .init = s5p_timer_init, | |
398 | }; |