blob: 761826c628b103d238826c4c968d8046c095cb8f [file] [log] [blame]
Russell Kingf32f4ce2009-05-16 12:14:21 +01001/*
2 * linux/arch/arm/kernel/smp_twd.c
3 *
4 * Copyright (C) 2002 ARM Ltd.
5 * All Rights Reserved
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#include <linux/init.h>
12#include <linux/kernel.h>
Linus Walleij5def51b2011-12-13 12:47:31 +010013#include <linux/clk.h>
Linus Walleij4fd7f9b2011-12-13 12:48:18 +010014#include <linux/cpufreq.h>
Russell Kingf32f4ce2009-05-16 12:14:21 +010015#include <linux/delay.h>
16#include <linux/device.h>
Linus Walleij5def51b2011-12-13 12:47:31 +010017#include <linux/err.h>
Russell Kingf32f4ce2009-05-16 12:14:21 +010018#include <linux/smp.h>
19#include <linux/jiffies.h>
20#include <linux/clockchips.h>
21#include <linux/irq.h>
22#include <linux/io.h>
Marc Zyngierd8e03642012-01-10 22:15:45 +000023#include <linux/of_irq.h>
24#include <linux/of_address.h>
Russell Kingf32f4ce2009-05-16 12:14:21 +010025
26#include <asm/smp_twd.h>
Marc Zyngier28af6902011-07-22 12:52:37 +010027#include <asm/localtimer.h>
Russell Kingf32f4ce2009-05-16 12:14:21 +010028#include <asm/hardware/gic.h>
29
Russell Kingf32f4ce2009-05-16 12:14:21 +010030/* set up by the platform code */
31void __iomem *twd_base;
32
Linus Walleij5def51b2011-12-13 12:47:31 +010033static struct clk *twd_clk;
Russell Kingf32f4ce2009-05-16 12:14:21 +010034static unsigned long twd_timer_rate;
35
Marc Zyngier28af6902011-07-22 12:52:37 +010036static struct clock_event_device __percpu **twd_evt;
Marc Zyngier81e46f72012-01-10 19:39:26 +000037static int twd_ppi;
Marc Zyngier28af6902011-07-22 12:52:37 +010038
Russell Kingf32f4ce2009-05-16 12:14:21 +010039static void twd_set_mode(enum clock_event_mode mode,
40 struct clock_event_device *clk)
41{
42 unsigned long ctrl;
43
Russell King4c5158d2009-05-17 10:58:54 +010044 switch (mode) {
Russell Kingf32f4ce2009-05-16 12:14:21 +010045 case CLOCK_EVT_MODE_PERIODIC:
46 /* timer load already set up */
47 ctrl = TWD_TIMER_CONTROL_ENABLE | TWD_TIMER_CONTROL_IT_ENABLE
48 | TWD_TIMER_CONTROL_PERIODIC;
Russell King03399c12011-01-25 10:35:36 +000049 __raw_writel(twd_timer_rate / HZ, twd_base + TWD_TIMER_LOAD);
Russell Kingf32f4ce2009-05-16 12:14:21 +010050 break;
51 case CLOCK_EVT_MODE_ONESHOT:
52 /* period set, and timer enabled in 'next_event' hook */
53 ctrl = TWD_TIMER_CONTROL_IT_ENABLE | TWD_TIMER_CONTROL_ONESHOT;
54 break;
55 case CLOCK_EVT_MODE_UNUSED:
56 case CLOCK_EVT_MODE_SHUTDOWN:
57 default:
58 ctrl = 0;
59 }
60
61 __raw_writel(ctrl, twd_base + TWD_TIMER_CONTROL);
62}
63
64static int twd_set_next_event(unsigned long evt,
65 struct clock_event_device *unused)
66{
67 unsigned long ctrl = __raw_readl(twd_base + TWD_TIMER_CONTROL);
68
Russell King4c5158d2009-05-17 10:58:54 +010069 ctrl |= TWD_TIMER_CONTROL_ENABLE;
70
Russell Kingf32f4ce2009-05-16 12:14:21 +010071 __raw_writel(evt, twd_base + TWD_TIMER_COUNTER);
Russell King4c5158d2009-05-17 10:58:54 +010072 __raw_writel(ctrl, twd_base + TWD_TIMER_CONTROL);
Russell Kingf32f4ce2009-05-16 12:14:21 +010073
74 return 0;
75}
76
77/*
78 * local_timer_ack: checks for a local timer interrupt.
79 *
80 * If a local timer interrupt has occurred, acknowledge and return 1.
81 * Otherwise, return 0.
82 */
83int twd_timer_ack(void)
84{
85 if (__raw_readl(twd_base + TWD_TIMER_INTSTAT)) {
86 __raw_writel(1, twd_base + TWD_TIMER_INTSTAT);
87 return 1;
88 }
89
90 return 0;
91}
92
Marc Zyngierabde7102012-01-10 19:07:28 +000093static void twd_timer_stop(struct clock_event_device *clk)
Marc Zyngier28af6902011-07-22 12:52:37 +010094{
95 twd_set_mode(CLOCK_EVT_MODE_UNUSED, clk);
96 disable_percpu_irq(clk->irq);
97}
98
Marc Zyngierabde7102012-01-10 19:07:28 +000099/* Temporary hack to be removed when all TWD users are converted to
100 the new registration interface */
101void local_timer_stop(struct clock_event_device *clk)
102 __attribute__ ((alias ("twd_timer_stop")));
103
Linus Walleij4fd7f9b2011-12-13 12:48:18 +0100104#ifdef CONFIG_CPU_FREQ
105
106/*
107 * Updates clockevent frequency when the cpu frequency changes.
108 * Called on the cpu that is changing frequency with interrupts disabled.
109 */
110static void twd_update_frequency(void *data)
111{
112 twd_timer_rate = clk_get_rate(twd_clk);
113
114 clockevents_update_freq(*__this_cpu_ptr(twd_evt), twd_timer_rate);
115}
116
117static int twd_cpufreq_transition(struct notifier_block *nb,
118 unsigned long state, void *data)
119{
120 struct cpufreq_freqs *freqs = data;
121
122 /*
123 * The twd clock events must be reprogrammed to account for the new
124 * frequency. The timer is local to a cpu, so cross-call to the
125 * changing cpu.
126 */
127 if (state == CPUFREQ_POSTCHANGE || state == CPUFREQ_RESUMECHANGE)
128 smp_call_function_single(freqs->cpu, twd_update_frequency,
129 NULL, 1);
130
131 return NOTIFY_OK;
132}
133
134static struct notifier_block twd_cpufreq_nb = {
135 .notifier_call = twd_cpufreq_transition,
136};
137
138static int twd_cpufreq_init(void)
139{
140 if (!IS_ERR(twd_clk))
141 return cpufreq_register_notifier(&twd_cpufreq_nb,
142 CPUFREQ_TRANSITION_NOTIFIER);
143
144 return 0;
145}
146core_initcall(twd_cpufreq_init);
147
148#endif
149
Russell Kingf32f4ce2009-05-16 12:14:21 +0100150static void __cpuinit twd_calibrate_rate(void)
151{
Russell King03399c12011-01-25 10:35:36 +0000152 unsigned long count;
Russell Kingf32f4ce2009-05-16 12:14:21 +0100153 u64 waitjiffies;
154
155 /*
156 * If this is the first time round, we need to work out how fast
157 * the timer ticks
158 */
159 if (twd_timer_rate == 0) {
Russell King4c5158d2009-05-17 10:58:54 +0100160 printk(KERN_INFO "Calibrating local timer... ");
Russell Kingf32f4ce2009-05-16 12:14:21 +0100161
162 /* Wait for a tick to start */
163 waitjiffies = get_jiffies_64() + 1;
164
165 while (get_jiffies_64() < waitjiffies)
166 udelay(10);
167
168 /* OK, now the tick has started, let's get the timer going */
169 waitjiffies += 5;
170
171 /* enable, no interrupt or reload */
172 __raw_writel(0x1, twd_base + TWD_TIMER_CONTROL);
173
174 /* maximum value */
175 __raw_writel(0xFFFFFFFFU, twd_base + TWD_TIMER_COUNTER);
176
177 while (get_jiffies_64() < waitjiffies)
178 udelay(10);
179
180 count = __raw_readl(twd_base + TWD_TIMER_COUNTER);
181
182 twd_timer_rate = (0xFFFFFFFFU - count) * (HZ / 5);
183
184 printk("%lu.%02luMHz.\n", twd_timer_rate / 1000000,
Vitaly Kuzmichev90c5ffe2011-07-07 14:56:05 +0100185 (twd_timer_rate / 10000) % 100);
Russell Kingf32f4ce2009-05-16 12:14:21 +0100186 }
Russell Kingf32f4ce2009-05-16 12:14:21 +0100187}
188
Marc Zyngier28af6902011-07-22 12:52:37 +0100189static irqreturn_t twd_handler(int irq, void *dev_id)
190{
191 struct clock_event_device *evt = *(struct clock_event_device **)dev_id;
192
193 if (twd_timer_ack()) {
194 evt->event_handler(evt);
195 return IRQ_HANDLED;
196 }
197
198 return IRQ_NONE;
199}
200
Linus Walleij5def51b2011-12-13 12:47:31 +0100201static struct clk *twd_get_clock(void)
202{
203 struct clk *clk;
204 int err;
205
206 clk = clk_get_sys("smp_twd", NULL);
207 if (IS_ERR(clk)) {
208 pr_err("smp_twd: clock not found: %d\n", (int)PTR_ERR(clk));
209 return clk;
210 }
211
212 err = clk_prepare(clk);
213 if (err) {
214 pr_err("smp_twd: clock failed to prepare: %d\n", err);
215 clk_put(clk);
216 return ERR_PTR(err);
217 }
218
219 err = clk_enable(clk);
220 if (err) {
221 pr_err("smp_twd: clock failed to enable: %d\n", err);
222 clk_unprepare(clk);
223 clk_put(clk);
224 return ERR_PTR(err);
225 }
226
227 return clk;
228}
229
Russell Kingf32f4ce2009-05-16 12:14:21 +0100230/*
231 * Setup the local clock events for a CPU.
232 */
Marc Zyngier81e46f72012-01-10 19:39:26 +0000233int __cpuinit twd_timer_setup(struct clock_event_device *clk)
Russell Kingf32f4ce2009-05-16 12:14:21 +0100234{
Marc Zyngier28af6902011-07-22 12:52:37 +0100235 struct clock_event_device **this_cpu_clk;
236
237 if (!twd_evt) {
238 int err;
239
240 twd_evt = alloc_percpu(struct clock_event_device *);
241 if (!twd_evt) {
242 pr_err("twd: can't allocate memory\n");
Marc Zyngier81e46f72012-01-10 19:39:26 +0000243 return -ENOMEM;
Marc Zyngier28af6902011-07-22 12:52:37 +0100244 }
245
246 err = request_percpu_irq(clk->irq, twd_handler,
247 "twd", twd_evt);
248 if (err) {
249 pr_err("twd: can't register interrupt %d (%d)\n",
250 clk->irq, err);
Marc Zyngier81e46f72012-01-10 19:39:26 +0000251 return err;
Marc Zyngier28af6902011-07-22 12:52:37 +0100252 }
253 }
254
Linus Walleij5def51b2011-12-13 12:47:31 +0100255 if (!twd_clk)
256 twd_clk = twd_get_clock();
257
258 if (!IS_ERR_OR_NULL(twd_clk))
259 twd_timer_rate = clk_get_rate(twd_clk);
260 else
261 twd_calibrate_rate();
Russell Kingf32f4ce2009-05-16 12:14:21 +0100262
Marc Zyngierc2144552012-01-20 12:24:47 +0100263 __raw_writel(0, twd_base + TWD_TIMER_CONTROL);
264
Russell King4c5158d2009-05-17 10:58:54 +0100265 clk->name = "local_timer";
Russell King5388a6b2010-07-26 13:19:43 +0100266 clk->features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT |
267 CLOCK_EVT_FEAT_C3STOP;
Russell King4c5158d2009-05-17 10:58:54 +0100268 clk->rating = 350;
269 clk->set_mode = twd_set_mode;
270 clk->set_next_event = twd_set_next_event;
Marc Zyngier81e46f72012-01-10 19:39:26 +0000271 if (!clk->irq)
272 clk->irq = twd_ppi;
Russell Kingf32f4ce2009-05-16 12:14:21 +0100273
Marc Zyngier28af6902011-07-22 12:52:37 +0100274 this_cpu_clk = __this_cpu_ptr(twd_evt);
275 *this_cpu_clk = clk;
276
Linus Walleij54d15b12011-12-13 12:46:43 +0100277 clockevents_config_and_register(clk, twd_timer_rate,
278 0xf, 0xffffffff);
Marc Zyngier28af6902011-07-22 12:52:37 +0100279 enable_percpu_irq(clk->irq, 0);
Marc Zyngier81e46f72012-01-10 19:39:26 +0000280
281 return 0;
282}
283
284static struct local_timer_ops twd_lt_ops __cpuinitdata = {
285 .setup = twd_timer_setup,
286 .stop = twd_timer_stop,
287};
288
Marc Zyngierd8e03642012-01-10 22:15:45 +0000289static int __init twd_local_timer_common_register(void)
Marc Zyngier81e46f72012-01-10 19:39:26 +0000290{
291 int err;
292
Marc Zyngier81e46f72012-01-10 19:39:26 +0000293 twd_evt = alloc_percpu(struct clock_event_device *);
Marc Zyngierd8e03642012-01-10 22:15:45 +0000294 if (!twd_evt) {
Marc Zyngier81e46f72012-01-10 19:39:26 +0000295 err = -ENOMEM;
Marc Zyngierd8e03642012-01-10 22:15:45 +0000296 goto out_free;
Marc Zyngier81e46f72012-01-10 19:39:26 +0000297 }
298
299 err = request_percpu_irq(twd_ppi, twd_handler, "twd", twd_evt);
300 if (err) {
301 pr_err("twd: can't register interrupt %d (%d)\n", twd_ppi, err);
Marc Zyngierd8e03642012-01-10 22:15:45 +0000302 goto out_free;
Marc Zyngier81e46f72012-01-10 19:39:26 +0000303 }
304
305 err = local_timer_register(&twd_lt_ops);
306 if (err)
Marc Zyngierd8e03642012-01-10 22:15:45 +0000307 goto out_irq;
Marc Zyngier81e46f72012-01-10 19:39:26 +0000308
309 return 0;
310
Marc Zyngierd8e03642012-01-10 22:15:45 +0000311out_irq:
312 free_percpu_irq(twd_ppi, twd_evt);
313out_free:
Marc Zyngier81e46f72012-01-10 19:39:26 +0000314 iounmap(twd_base);
Marc Zyngierd8e03642012-01-10 22:15:45 +0000315 twd_base = NULL;
Marc Zyngier81e46f72012-01-10 19:39:26 +0000316 free_percpu(twd_evt);
Marc Zyngierd8e03642012-01-10 22:15:45 +0000317
Marc Zyngier81e46f72012-01-10 19:39:26 +0000318 return err;
Russell Kingf32f4ce2009-05-16 12:14:21 +0100319}
Marc Zyngierd8e03642012-01-10 22:15:45 +0000320
321int __init twd_local_timer_register(struct twd_local_timer *tlt)
322{
323 if (twd_base || twd_evt)
324 return -EBUSY;
325
326 twd_ppi = tlt->res[1].start;
327
328 twd_base = ioremap(tlt->res[0].start, resource_size(&tlt->res[0]));
329 if (!twd_base)
330 return -ENOMEM;
331
332 return twd_local_timer_common_register();
333}
334
335#ifdef CONFIG_OF
336const static struct of_device_id twd_of_match[] __initconst = {
337 { .compatible = "arm,cortex-a9-twd-timer", },
338 { .compatible = "arm,cortex-a5-twd-timer", },
339 { .compatible = "arm,arm11mp-twd-timer", },
340 { },
341};
342
343void __init twd_local_timer_of_register(void)
344{
345 struct device_node *np;
346 int err;
347
348 np = of_find_matching_node(NULL, twd_of_match);
349 if (!np) {
350 err = -ENODEV;
351 goto out;
352 }
353
354 twd_ppi = irq_of_parse_and_map(np, 0);
355 if (!twd_ppi) {
356 err = -EINVAL;
357 goto out;
358 }
359
360 twd_base = of_iomap(np, 0);
361 if (!twd_base) {
362 err = -ENOMEM;
363 goto out;
364 }
365
366 err = twd_local_timer_common_register();
367
368out:
369 WARN(err, "twd_local_timer_of_register failed (%d)\n", err);
370}
371#endif