Linux Kernel  3.7.1
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
s5p-time.c
Go to the documentation of this file.
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 
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  }
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);
85  break;
86 
87  case S5P_PWM1:
88  tcon &= ~(0x0f << 8);
90  break;
91 
92  case S5P_PWM2:
93  tcon &= ~(0x0f << 12);
95  break;
96 
97  case S5P_PWM3:
98  tcon &= ~(0x0f << 16);
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 
226  enum s5p_timer_mode source)
227 {
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",
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 
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 
291 static 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  */
322 static 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 
332 static 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 
354 static 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);
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 
396 static void __init s5p_timer_init(void)
397 {
398  s5p_timer_resources();
399  s5p_clockevent_init();
400  s5p_clocksource_init();
401 }
402 
404  .init = s5p_timer_init,
405 };