diff options
Diffstat (limited to 'drivers/clocksource/dw_apb_timer.c')
-rw-r--r-- | drivers/clocksource/dw_apb_timer.c | 401 |
1 files changed, 401 insertions, 0 deletions
diff --git a/drivers/clocksource/dw_apb_timer.c b/drivers/clocksource/dw_apb_timer.c new file mode 100644 index 000000000000..580f870541a3 --- /dev/null +++ b/drivers/clocksource/dw_apb_timer.c | |||
@@ -0,0 +1,401 @@ | |||
1 | /* | ||
2 | * (C) Copyright 2009 Intel Corporation | ||
3 | * Author: Jacob Pan (jacob.jun.pan@intel.com) | ||
4 | * | ||
5 | * Shared with ARM platforms, Jamie Iles, Picochip 2011 | ||
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 | * Support for the Synopsys DesignWare APB Timers. | ||
12 | */ | ||
13 | #include <linux/dw_apb_timer.h> | ||
14 | #include <linux/delay.h> | ||
15 | #include <linux/kernel.h> | ||
16 | #include <linux/interrupt.h> | ||
17 | #include <linux/irq.h> | ||
18 | #include <linux/io.h> | ||
19 | #include <linux/slab.h> | ||
20 | |||
21 | #define APBT_MIN_PERIOD 4 | ||
22 | #define APBT_MIN_DELTA_USEC 200 | ||
23 | |||
24 | #define APBTMR_N_LOAD_COUNT 0x00 | ||
25 | #define APBTMR_N_CURRENT_VALUE 0x04 | ||
26 | #define APBTMR_N_CONTROL 0x08 | ||
27 | #define APBTMR_N_EOI 0x0c | ||
28 | #define APBTMR_N_INT_STATUS 0x10 | ||
29 | |||
30 | #define APBTMRS_INT_STATUS 0xa0 | ||
31 | #define APBTMRS_EOI 0xa4 | ||
32 | #define APBTMRS_RAW_INT_STATUS 0xa8 | ||
33 | #define APBTMRS_COMP_VERSION 0xac | ||
34 | |||
35 | #define APBTMR_CONTROL_ENABLE (1 << 0) | ||
36 | /* 1: periodic, 0:free running. */ | ||
37 | #define APBTMR_CONTROL_MODE_PERIODIC (1 << 1) | ||
38 | #define APBTMR_CONTROL_INT (1 << 2) | ||
39 | |||
40 | static inline struct dw_apb_clock_event_device * | ||
41 | ced_to_dw_apb_ced(struct clock_event_device *evt) | ||
42 | { | ||
43 | return container_of(evt, struct dw_apb_clock_event_device, ced); | ||
44 | } | ||
45 | |||
46 | static inline struct dw_apb_clocksource * | ||
47 | clocksource_to_dw_apb_clocksource(struct clocksource *cs) | ||
48 | { | ||
49 | return container_of(cs, struct dw_apb_clocksource, cs); | ||
50 | } | ||
51 | |||
52 | static unsigned long apbt_readl(struct dw_apb_timer *timer, unsigned long offs) | ||
53 | { | ||
54 | return readl(timer->base + offs); | ||
55 | } | ||
56 | |||
57 | static void apbt_writel(struct dw_apb_timer *timer, unsigned long val, | ||
58 | unsigned long offs) | ||
59 | { | ||
60 | writel(val, timer->base + offs); | ||
61 | } | ||
62 | |||
63 | static void apbt_disable_int(struct dw_apb_timer *timer) | ||
64 | { | ||
65 | unsigned long ctrl = apbt_readl(timer, APBTMR_N_CONTROL); | ||
66 | |||
67 | ctrl |= APBTMR_CONTROL_INT; | ||
68 | apbt_writel(timer, ctrl, APBTMR_N_CONTROL); | ||
69 | } | ||
70 | |||
71 | /** | ||
72 | * dw_apb_clockevent_pause() - stop the clock_event_device from running | ||
73 | * | ||
74 | * @dw_ced: The APB clock to stop generating events. | ||
75 | */ | ||
76 | void dw_apb_clockevent_pause(struct dw_apb_clock_event_device *dw_ced) | ||
77 | { | ||
78 | disable_irq(dw_ced->timer.irq); | ||
79 | apbt_disable_int(&dw_ced->timer); | ||
80 | } | ||
81 | |||
82 | static void apbt_eoi(struct dw_apb_timer *timer) | ||
83 | { | ||
84 | apbt_readl(timer, APBTMR_N_EOI); | ||
85 | } | ||
86 | |||
87 | static irqreturn_t dw_apb_clockevent_irq(int irq, void *data) | ||
88 | { | ||
89 | struct clock_event_device *evt = data; | ||
90 | struct dw_apb_clock_event_device *dw_ced = ced_to_dw_apb_ced(evt); | ||
91 | |||
92 | if (!evt->event_handler) { | ||
93 | pr_info("Spurious APBT timer interrupt %d", irq); | ||
94 | return IRQ_NONE; | ||
95 | } | ||
96 | |||
97 | if (dw_ced->eoi) | ||
98 | dw_ced->eoi(&dw_ced->timer); | ||
99 | |||
100 | evt->event_handler(evt); | ||
101 | return IRQ_HANDLED; | ||
102 | } | ||
103 | |||
104 | static void apbt_enable_int(struct dw_apb_timer *timer) | ||
105 | { | ||
106 | unsigned long ctrl = apbt_readl(timer, APBTMR_N_CONTROL); | ||
107 | /* clear pending intr */ | ||
108 | apbt_readl(timer, APBTMR_N_EOI); | ||
109 | ctrl &= ~APBTMR_CONTROL_INT; | ||
110 | apbt_writel(timer, ctrl, APBTMR_N_CONTROL); | ||
111 | } | ||
112 | |||
113 | static void apbt_set_mode(enum clock_event_mode mode, | ||
114 | struct clock_event_device *evt) | ||
115 | { | ||
116 | unsigned long ctrl; | ||
117 | unsigned long period; | ||
118 | struct dw_apb_clock_event_device *dw_ced = ced_to_dw_apb_ced(evt); | ||
119 | |||
120 | pr_debug("%s CPU %d mode=%d\n", __func__, first_cpu(*evt->cpumask), | ||
121 | mode); | ||
122 | |||
123 | switch (mode) { | ||
124 | case CLOCK_EVT_MODE_PERIODIC: | ||
125 | period = DIV_ROUND_UP(dw_ced->timer.freq, HZ); | ||
126 | ctrl = apbt_readl(&dw_ced->timer, APBTMR_N_CONTROL); | ||
127 | ctrl |= APBTMR_CONTROL_MODE_PERIODIC; | ||
128 | apbt_writel(&dw_ced->timer, ctrl, APBTMR_N_CONTROL); | ||
129 | /* | ||
130 | * DW APB p. 46, have to disable timer before load counter, | ||
131 | * may cause sync problem. | ||
132 | */ | ||
133 | ctrl &= ~APBTMR_CONTROL_ENABLE; | ||
134 | apbt_writel(&dw_ced->timer, ctrl, APBTMR_N_CONTROL); | ||
135 | udelay(1); | ||
136 | pr_debug("Setting clock period %lu for HZ %d\n", period, HZ); | ||
137 | apbt_writel(&dw_ced->timer, period, APBTMR_N_LOAD_COUNT); | ||
138 | ctrl |= APBTMR_CONTROL_ENABLE; | ||
139 | apbt_writel(&dw_ced->timer, ctrl, APBTMR_N_CONTROL); | ||
140 | break; | ||
141 | |||
142 | case CLOCK_EVT_MODE_ONESHOT: | ||
143 | ctrl = apbt_readl(&dw_ced->timer, APBTMR_N_CONTROL); | ||
144 | /* | ||
145 | * set free running mode, this mode will let timer reload max | ||
146 | * timeout which will give time (3min on 25MHz clock) to rearm | ||
147 | * the next event, therefore emulate the one-shot mode. | ||
148 | */ | ||
149 | ctrl &= ~APBTMR_CONTROL_ENABLE; | ||
150 | ctrl &= ~APBTMR_CONTROL_MODE_PERIODIC; | ||
151 | |||
152 | apbt_writel(&dw_ced->timer, ctrl, APBTMR_N_CONTROL); | ||
153 | /* write again to set free running mode */ | ||
154 | apbt_writel(&dw_ced->timer, ctrl, APBTMR_N_CONTROL); | ||
155 | |||
156 | /* | ||
157 | * DW APB p. 46, load counter with all 1s before starting free | ||
158 | * running mode. | ||
159 | */ | ||
160 | apbt_writel(&dw_ced->timer, ~0, APBTMR_N_LOAD_COUNT); | ||
161 | ctrl &= ~APBTMR_CONTROL_INT; | ||
162 | ctrl |= APBTMR_CONTROL_ENABLE; | ||
163 | apbt_writel(&dw_ced->timer, ctrl, APBTMR_N_CONTROL); | ||
164 | break; | ||
165 | |||
166 | case CLOCK_EVT_MODE_UNUSED: | ||
167 | case CLOCK_EVT_MODE_SHUTDOWN: | ||
168 | ctrl = apbt_readl(&dw_ced->timer, APBTMR_N_CONTROL); | ||
169 | ctrl &= ~APBTMR_CONTROL_ENABLE; | ||
170 | apbt_writel(&dw_ced->timer, ctrl, APBTMR_N_CONTROL); | ||
171 | break; | ||
172 | |||
173 | case CLOCK_EVT_MODE_RESUME: | ||
174 | apbt_enable_int(&dw_ced->timer); | ||
175 | break; | ||
176 | } | ||
177 | } | ||
178 | |||
179 | static int apbt_next_event(unsigned long delta, | ||
180 | struct clock_event_device *evt) | ||
181 | { | ||
182 | unsigned long ctrl; | ||
183 | struct dw_apb_clock_event_device *dw_ced = ced_to_dw_apb_ced(evt); | ||
184 | |||
185 | /* Disable timer */ | ||
186 | ctrl = apbt_readl(&dw_ced->timer, APBTMR_N_CONTROL); | ||
187 | ctrl &= ~APBTMR_CONTROL_ENABLE; | ||
188 | apbt_writel(&dw_ced->timer, ctrl, APBTMR_N_CONTROL); | ||
189 | /* write new count */ | ||
190 | apbt_writel(&dw_ced->timer, delta, APBTMR_N_LOAD_COUNT); | ||
191 | ctrl |= APBTMR_CONTROL_ENABLE; | ||
192 | apbt_writel(&dw_ced->timer, ctrl, APBTMR_N_CONTROL); | ||
193 | |||
194 | return 0; | ||
195 | } | ||
196 | |||
197 | /** | ||
198 | * dw_apb_clockevent_init() - use an APB timer as a clock_event_device | ||
199 | * | ||
200 | * @cpu: The CPU the events will be targeted at. | ||
201 | * @name: The name used for the timer and the IRQ for it. | ||
202 | * @rating: The rating to give the timer. | ||
203 | * @base: I/O base for the timer registers. | ||
204 | * @irq: The interrupt number to use for the timer. | ||
205 | * @freq: The frequency that the timer counts at. | ||
206 | * | ||
207 | * This creates a clock_event_device for using with the generic clock layer | ||
208 | * but does not start and register it. This should be done with | ||
209 | * dw_apb_clockevent_register() as the next step. If this is the first time | ||
210 | * it has been called for a timer then the IRQ will be requested, if not it | ||
211 | * just be enabled to allow CPU hotplug to avoid repeatedly requesting and | ||
212 | * releasing the IRQ. | ||
213 | */ | ||
214 | struct dw_apb_clock_event_device * | ||
215 | dw_apb_clockevent_init(int cpu, const char *name, unsigned rating, | ||
216 | void __iomem *base, int irq, unsigned long freq) | ||
217 | { | ||
218 | struct dw_apb_clock_event_device *dw_ced = | ||
219 | kzalloc(sizeof(*dw_ced), GFP_KERNEL); | ||
220 | int err; | ||
221 | |||
222 | if (!dw_ced) | ||
223 | return NULL; | ||
224 | |||
225 | dw_ced->timer.base = base; | ||
226 | dw_ced->timer.irq = irq; | ||
227 | dw_ced->timer.freq = freq; | ||
228 | |||
229 | clockevents_calc_mult_shift(&dw_ced->ced, freq, APBT_MIN_PERIOD); | ||
230 | dw_ced->ced.max_delta_ns = clockevent_delta2ns(0x7fffffff, | ||
231 | &dw_ced->ced); | ||
232 | dw_ced->ced.min_delta_ns = clockevent_delta2ns(5000, &dw_ced->ced); | ||
233 | dw_ced->ced.cpumask = cpumask_of(cpu); | ||
234 | dw_ced->ced.features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT; | ||
235 | dw_ced->ced.set_mode = apbt_set_mode; | ||
236 | dw_ced->ced.set_next_event = apbt_next_event; | ||
237 | dw_ced->ced.irq = dw_ced->timer.irq; | ||
238 | dw_ced->ced.rating = rating; | ||
239 | dw_ced->ced.name = name; | ||
240 | |||
241 | dw_ced->irqaction.name = dw_ced->ced.name; | ||
242 | dw_ced->irqaction.handler = dw_apb_clockevent_irq; | ||
243 | dw_ced->irqaction.dev_id = &dw_ced->ced; | ||
244 | dw_ced->irqaction.irq = irq; | ||
245 | dw_ced->irqaction.flags = IRQF_TIMER | IRQF_IRQPOLL | | ||
246 | IRQF_NOBALANCING | | ||
247 | IRQF_DISABLED; | ||
248 | |||
249 | dw_ced->eoi = apbt_eoi; | ||
250 | err = setup_irq(irq, &dw_ced->irqaction); | ||
251 | if (err) { | ||
252 | pr_err("failed to request timer irq\n"); | ||
253 | kfree(dw_ced); | ||
254 | dw_ced = NULL; | ||
255 | } | ||
256 | |||
257 | return dw_ced; | ||
258 | } | ||
259 | |||
260 | /** | ||
261 | * dw_apb_clockevent_resume() - resume a clock that has been paused. | ||
262 | * | ||
263 | * @dw_ced: The APB clock to resume. | ||
264 | */ | ||
265 | void dw_apb_clockevent_resume(struct dw_apb_clock_event_device *dw_ced) | ||
266 | { | ||
267 | enable_irq(dw_ced->timer.irq); | ||
268 | } | ||
269 | |||
270 | /** | ||
271 | * dw_apb_clockevent_stop() - stop the clock_event_device and release the IRQ. | ||
272 | * | ||
273 | * @dw_ced: The APB clock to stop generating the events. | ||
274 | */ | ||
275 | void dw_apb_clockevent_stop(struct dw_apb_clock_event_device *dw_ced) | ||
276 | { | ||
277 | free_irq(dw_ced->timer.irq, &dw_ced->ced); | ||
278 | } | ||
279 | |||
280 | /** | ||
281 | * dw_apb_clockevent_register() - register the clock with the generic layer | ||
282 | * | ||
283 | * @dw_ced: The APB clock to register as a clock_event_device. | ||
284 | */ | ||
285 | void dw_apb_clockevent_register(struct dw_apb_clock_event_device *dw_ced) | ||
286 | { | ||
287 | apbt_writel(&dw_ced->timer, 0, APBTMR_N_CONTROL); | ||
288 | clockevents_register_device(&dw_ced->ced); | ||
289 | apbt_enable_int(&dw_ced->timer); | ||
290 | } | ||
291 | |||
292 | /** | ||
293 | * dw_apb_clocksource_start() - start the clocksource counting. | ||
294 | * | ||
295 | * @dw_cs: The clocksource to start. | ||
296 | * | ||
297 | * This is used to start the clocksource before registration and can be used | ||
298 | * to enable calibration of timers. | ||
299 | */ | ||
300 | void dw_apb_clocksource_start(struct dw_apb_clocksource *dw_cs) | ||
301 | { | ||
302 | /* | ||
303 | * start count down from 0xffff_ffff. this is done by toggling the | ||
304 | * enable bit then load initial load count to ~0. | ||
305 | */ | ||
306 | unsigned long ctrl = apbt_readl(&dw_cs->timer, APBTMR_N_CONTROL); | ||
307 | |||
308 | ctrl &= ~APBTMR_CONTROL_ENABLE; | ||
309 | apbt_writel(&dw_cs->timer, ctrl, APBTMR_N_CONTROL); | ||
310 | apbt_writel(&dw_cs->timer, ~0, APBTMR_N_LOAD_COUNT); | ||
311 | /* enable, mask interrupt */ | ||
312 | ctrl &= ~APBTMR_CONTROL_MODE_PERIODIC; | ||
313 | ctrl |= (APBTMR_CONTROL_ENABLE | APBTMR_CONTROL_INT); | ||
314 | apbt_writel(&dw_cs->timer, ctrl, APBTMR_N_CONTROL); | ||
315 | /* read it once to get cached counter value initialized */ | ||
316 | dw_apb_clocksource_read(dw_cs); | ||
317 | } | ||
318 | |||
319 | static cycle_t __apbt_read_clocksource(struct clocksource *cs) | ||
320 | { | ||
321 | unsigned long current_count; | ||
322 | struct dw_apb_clocksource *dw_cs = | ||
323 | clocksource_to_dw_apb_clocksource(cs); | ||
324 | |||
325 | current_count = apbt_readl(&dw_cs->timer, APBTMR_N_CURRENT_VALUE); | ||
326 | |||
327 | return (cycle_t)~current_count; | ||
328 | } | ||
329 | |||
330 | static void apbt_restart_clocksource(struct clocksource *cs) | ||
331 | { | ||
332 | struct dw_apb_clocksource *dw_cs = | ||
333 | clocksource_to_dw_apb_clocksource(cs); | ||
334 | |||
335 | dw_apb_clocksource_start(dw_cs); | ||
336 | } | ||
337 | |||
338 | /** | ||
339 | * dw_apb_clocksource_init() - use an APB timer as a clocksource. | ||
340 | * | ||
341 | * @rating: The rating to give the clocksource. | ||
342 | * @name: The name for the clocksource. | ||
343 | * @base: The I/O base for the timer registers. | ||
344 | * @freq: The frequency that the timer counts at. | ||
345 | * | ||
346 | * This creates a clocksource using an APB timer but does not yet register it | ||
347 | * with the clocksource system. This should be done with | ||
348 | * dw_apb_clocksource_register() as the next step. | ||
349 | */ | ||
350 | struct dw_apb_clocksource * | ||
351 | dw_apb_clocksource_init(unsigned rating, char *name, void __iomem *base, | ||
352 | unsigned long freq) | ||
353 | { | ||
354 | struct dw_apb_clocksource *dw_cs = kzalloc(sizeof(*dw_cs), GFP_KERNEL); | ||
355 | |||
356 | if (!dw_cs) | ||
357 | return NULL; | ||
358 | |||
359 | dw_cs->timer.base = base; | ||
360 | dw_cs->timer.freq = freq; | ||
361 | dw_cs->cs.name = name; | ||
362 | dw_cs->cs.rating = rating; | ||
363 | dw_cs->cs.read = __apbt_read_clocksource; | ||
364 | dw_cs->cs.mask = CLOCKSOURCE_MASK(32); | ||
365 | dw_cs->cs.flags = CLOCK_SOURCE_IS_CONTINUOUS; | ||
366 | dw_cs->cs.resume = apbt_restart_clocksource; | ||
367 | |||
368 | return dw_cs; | ||
369 | } | ||
370 | |||
371 | /** | ||
372 | * dw_apb_clocksource_register() - register the APB clocksource. | ||
373 | * | ||
374 | * @dw_cs: The clocksource to register. | ||
375 | */ | ||
376 | void dw_apb_clocksource_register(struct dw_apb_clocksource *dw_cs) | ||
377 | { | ||
378 | clocksource_register_hz(&dw_cs->cs, dw_cs->timer.freq); | ||
379 | } | ||
380 | |||
381 | /** | ||
382 | * dw_apb_clocksource_read() - read the current value of a clocksource. | ||
383 | * | ||
384 | * @dw_cs: The clocksource to read. | ||
385 | */ | ||
386 | cycle_t dw_apb_clocksource_read(struct dw_apb_clocksource *dw_cs) | ||
387 | { | ||
388 | return (cycle_t)~apbt_readl(&dw_cs->timer, APBTMR_N_CURRENT_VALUE); | ||
389 | } | ||
390 | |||
391 | /** | ||
392 | * dw_apb_clocksource_unregister() - unregister and free a clocksource. | ||
393 | * | ||
394 | * @dw_cs: The clocksource to unregister/free. | ||
395 | */ | ||
396 | void dw_apb_clocksource_unregister(struct dw_apb_clocksource *dw_cs) | ||
397 | { | ||
398 | clocksource_unregister(&dw_cs->cs); | ||
399 | |||
400 | kfree(dw_cs); | ||
401 | } | ||