source: src/linux/universal/linux-4.9/arch/arm/kernel/smp_twd.c @ 31871

Last change on this file since 31871 was 31871, checked in by brainslayer, 11 days ago

change mpcore watchdog driver for laguna

File size: 9.5 KB
Line 
1/*
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>
13#include <linux/clk.h>
14#include <linux/cpu.h>
15#include <linux/delay.h>
16#include <linux/device.h>
17#include <linux/err.h>
18#include <linux/export.h>
19#include <linux/smp.h>
20#include <linux/jiffies.h>
21#include <linux/clockchips.h>
22#include <linux/interrupt.h>
23#include <linux/io.h>
24#include <linux/of_irq.h>
25#include <linux/of_address.h>
26#include <linux/module.h>
27#include <linux/moduleparam.h>
28
29#include <asm/smp_twd.h>
30
31/* set up by the platform code */
32static void __iomem *twd_base;
33
34static struct clk *twd_clk;
35static unsigned long twd_timer_rate;
36static DEFINE_PER_CPU(bool, percpu_setup_called);
37
38static struct clock_event_device __percpu *twd_evt;
39static unsigned int twd_features =
40                CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT;
41static int twd_ppi;
42
43static int twd_shutdown(struct clock_event_device *clk)
44{
45        writel_relaxed(0, twd_base + TWD_TIMER_CONTROL);
46        return 0;
47}
48
49static int twd_set_oneshot(struct clock_event_device *clk)
50{
51        /* period set, and timer enabled in 'next_event' hook */
52        writel_relaxed(TWD_TIMER_CONTROL_IT_ENABLE | TWD_TIMER_CONTROL_ONESHOT,
53                       twd_base + TWD_TIMER_CONTROL);
54        return 0;
55}
56
57static int twd_set_periodic(struct clock_event_device *clk)
58{
59        unsigned long ctrl = TWD_TIMER_CONTROL_ENABLE |
60                             TWD_TIMER_CONTROL_IT_ENABLE |
61                             TWD_TIMER_CONTROL_PERIODIC;
62
63        writel_relaxed(DIV_ROUND_CLOSEST(twd_timer_rate, HZ),
64                       twd_base + TWD_TIMER_LOAD);
65        writel_relaxed(ctrl, twd_base + TWD_TIMER_CONTROL);
66        return 0;
67}
68
69static int twd_set_next_event(unsigned long evt,
70                        struct clock_event_device *unused)
71{
72        unsigned long ctrl = readl_relaxed(twd_base + TWD_TIMER_CONTROL);
73
74        ctrl |= TWD_TIMER_CONTROL_ENABLE;
75
76        writel_relaxed(evt, twd_base + TWD_TIMER_COUNTER);
77        writel_relaxed(ctrl, twd_base + TWD_TIMER_CONTROL);
78
79        return 0;
80}
81
82/*
83 * local_timer_ack: checks for a local timer interrupt.
84 *
85 * If a local timer interrupt has occurred, acknowledge and return 1.
86 * Otherwise, return 0.
87 */
88static int twd_timer_ack(void)
89{
90        if (readl_relaxed(twd_base + TWD_TIMER_INTSTAT)) {
91                writel_relaxed(1, twd_base + TWD_TIMER_INTSTAT);
92                return 1;
93        }
94
95        return 0;
96}
97
98static void twd_timer_stop(void)
99{
100        struct clock_event_device *clk = raw_cpu_ptr(twd_evt);
101
102        twd_shutdown(clk);
103        disable_percpu_irq(clk->irq);
104}
105
106#ifdef CONFIG_COMMON_CLK
107
108/*
109 * Updates clockevent frequency when the cpu frequency changes.
110 * Called on the cpu that is changing frequency with interrupts disabled.
111 */
112static void twd_update_frequency(void *new_rate)
113{
114        twd_timer_rate = *((unsigned long *) new_rate);
115
116        clockevents_update_freq(raw_cpu_ptr(twd_evt), twd_timer_rate);
117}
118
119static int twd_rate_change(struct notifier_block *nb,
120        unsigned long flags, void *data)
121{
122        struct clk_notifier_data *cnd = data;
123
124        /*
125         * The twd clock events must be reprogrammed to account for the new
126         * frequency.  The timer is local to a cpu, so cross-call to the
127         * changing cpu.
128         */
129        if (flags == POST_RATE_CHANGE)
130                on_each_cpu(twd_update_frequency,
131                                  (void *)&cnd->new_rate, 1);
132
133        return NOTIFY_OK;
134}
135
136static struct notifier_block twd_clk_nb = {
137        .notifier_call = twd_rate_change,
138};
139
140static int twd_clk_init(void)
141{
142        if (twd_evt && raw_cpu_ptr(twd_evt) && !IS_ERR(twd_clk))
143                return clk_notifier_register(twd_clk, &twd_clk_nb);
144
145        return 0;
146}
147core_initcall(twd_clk_init);
148
149#elif defined (CONFIG_CPU_FREQ)
150
151#include <linux/cpufreq.h>
152
153/*
154 * Updates clockevent frequency when the cpu frequency changes.
155 * Called on the cpu that is changing frequency with interrupts disabled.
156 */
157static void twd_update_frequency(void *data)
158{
159        twd_timer_rate = clk_get_rate(twd_clk);
160
161        clockevents_update_freq(raw_cpu_ptr(twd_evt), twd_timer_rate);
162}
163
164static int twd_cpufreq_transition(struct notifier_block *nb,
165        unsigned long state, void *data)
166{
167        struct cpufreq_freqs *freqs = data;
168
169        /*
170         * The twd clock events must be reprogrammed to account for the new
171         * frequency.  The timer is local to a cpu, so cross-call to the
172         * changing cpu.
173         */
174        if (state == CPUFREQ_POSTCHANGE)
175                smp_call_function_single(freqs->cpu, twd_update_frequency,
176                        NULL, 1);
177
178        return NOTIFY_OK;
179}
180
181static struct notifier_block twd_cpufreq_nb = {
182        .notifier_call = twd_cpufreq_transition,
183};
184
185static int twd_cpufreq_init(void)
186{
187        if (twd_evt && raw_cpu_ptr(twd_evt) && !IS_ERR(twd_clk))
188                return cpufreq_register_notifier(&twd_cpufreq_nb,
189                        CPUFREQ_TRANSITION_NOTIFIER);
190
191        return 0;
192}
193core_initcall(twd_cpufreq_init);
194
195#endif
196
197static void twd_calibrate_rate(void)
198{
199        unsigned long count;
200        u64 waitjiffies;
201
202        /*
203         * If this is the first time round, we need to work out how fast
204         * the timer ticks
205         */
206        if (twd_timer_rate == 0) {
207                pr_info("Calibrating local timer... ");
208
209                /* Wait for a tick to start */
210                waitjiffies = get_jiffies_64() + 1;
211
212                while (get_jiffies_64() < waitjiffies)
213                        udelay(10);
214
215                /* OK, now the tick has started, let's get the timer going */
216                waitjiffies += 5;
217
218                                 /* enable, no interrupt or reload */
219                writel_relaxed(0x1, twd_base + TWD_TIMER_CONTROL);
220
221                                 /* maximum value */
222                writel_relaxed(0xFFFFFFFFU, twd_base + TWD_TIMER_COUNTER);
223
224                while (get_jiffies_64() < waitjiffies)
225                        udelay(10);
226
227                count = readl_relaxed(twd_base + TWD_TIMER_COUNTER);
228
229                twd_timer_rate = (0xFFFFFFFFU - count) * (HZ / 5);
230
231                pr_cont("%lu.%02luMHz.\n", twd_timer_rate / 1000000,
232                        (twd_timer_rate / 10000) % 100);
233        }
234}
235
236static irqreturn_t twd_handler(int irq, void *dev_id)
237{
238        struct clock_event_device *evt = dev_id;
239
240        if (twd_timer_ack()) {
241                evt->event_handler(evt);
242                return IRQ_HANDLED;
243        }
244
245        return IRQ_NONE;
246}
247#ifndef CONFIG_ARCH_CNS3XXX
248
249static void twd_get_clock(struct device_node *np)
250{
251        int err;
252
253        if (np)
254                twd_clk = of_clk_get(np, 0);
255        else
256                twd_clk = clk_get_sys("smp_twd", NULL);
257
258        if (IS_ERR(twd_clk)) {
259                pr_err("smp_twd: clock not found %d\n", (int) PTR_ERR(twd_clk));
260                return;
261        }
262
263        err = clk_prepare_enable(twd_clk);
264        if (err) {
265                pr_err("smp_twd: clock failed to prepare+enable: %d\n", err);
266                clk_put(twd_clk);
267                return;
268        }
269
270        twd_timer_rate = clk_get_rate(twd_clk);
271}
272#endif
273/*
274 * Setup the local clock events for a CPU.
275 */
276static void twd_timer_setup(void)
277{
278        struct clock_event_device *clk = raw_cpu_ptr(twd_evt);
279
280        int cpu = smp_processor_id();
281
282        /*
283         * If the basic setup for this CPU has been done before don't
284         * bother with the below.
285         */
286        if (per_cpu(percpu_setup_called, cpu)) {
287                writel_relaxed(0, twd_base + TWD_TIMER_CONTROL);
288                clockevents_register_device(clk);
289                enable_percpu_irq(clk->irq, 0);
290                return;
291        }
292        per_cpu(percpu_setup_called, cpu) = true;
293
294
295        twd_calibrate_rate();
296
297        /*
298         * The following is done once per CPU the first time .setup() is
299         * called.
300         */
301
302        writel_relaxed(0, twd_base + TWD_TIMER_CONTROL);
303
304        clk->name = "local_timer";
305        clk->features = twd_features;
306        clk->rating = 350;
307        clk->set_state_shutdown = twd_shutdown;
308        clk->set_state_periodic = twd_set_periodic;
309        clk->set_state_oneshot = twd_set_oneshot;
310        clk->tick_resume = twd_shutdown;
311        clk->set_next_event = twd_set_next_event;
312        clk->irq = twd_ppi;
313        clk->cpumask = cpumask_of(cpu);
314
315        clockevents_config_and_register(clk, twd_timer_rate,
316                                        0xf, 0xffffffff);
317        enable_percpu_irq(clk->irq, 0);
318}
319
320static int twd_timer_starting_cpu(unsigned int cpu)
321{
322        twd_timer_setup();
323        return 0;
324}
325
326static int twd_timer_dying_cpu(unsigned int cpu)
327{
328        twd_timer_stop();
329        return 0;
330}
331
332static int __init twd_local_timer_common_register(struct device_node *np)
333{
334        int err;
335
336        twd_evt = alloc_percpu(struct clock_event_device);
337        if (!twd_evt) {
338                err = -ENOMEM;
339                goto out_free;
340        }
341
342        err = request_percpu_irq(twd_ppi, twd_handler, "twd", twd_evt);
343        if (err) {
344                pr_err("twd: can't register interrupt %d (%d)\n", twd_ppi, err);
345                goto out_free;
346        }
347
348        cpuhp_setup_state_nocalls(CPUHP_AP_ARM_TWD_STARTING,
349                                  "AP_ARM_TWD_STARTING",
350                                  twd_timer_starting_cpu, twd_timer_dying_cpu);
351
352#ifndef CONFIG_ARCH_CNS3XXX
353        twd_get_clock(np);
354#endif
355        if (!of_property_read_bool(np, "always-on"))
356                twd_features |= CLOCK_EVT_FEAT_C3STOP;
357
358        /*
359         * Immediately configure the timer on the boot CPU, unless we need
360         * jiffies to be incrementing to calibrate the rate in which case
361         * setup the timer in late_time_init.
362         */
363        if (twd_timer_rate)
364                twd_timer_setup();
365        else
366                late_time_init = twd_timer_setup;
367
368        return 0;
369
370out_free:
371        iounmap(twd_base);
372        twd_base = NULL;
373        free_percpu(twd_evt);
374
375        return err;
376}
377
378int __init twd_local_timer_register(struct twd_local_timer *tlt)
379{
380        if (twd_base || twd_evt)
381                return -EBUSY;
382
383        twd_ppi = tlt->res[1].start;
384
385        twd_base = ioremap(tlt->res[0].start, resource_size(&tlt->res[0]));
386        if (!twd_base)
387                return -ENOMEM;
388
389        return twd_local_timer_common_register(NULL);
390}
391
392/* Needed by mpcore_wdt */
393unsigned long twd_timer_get_rate(void)
394{
395        return twd_timer_rate;
396}
397EXPORT_SYMBOL_GPL(twd_timer_get_rate);
398
399
400#ifdef CONFIG_OF
401static int __init twd_local_timer_of_register(struct device_node *np)
402{
403        int err;
404
405        twd_ppi = irq_of_parse_and_map(np, 0);
406        if (!twd_ppi) {
407                err = -EINVAL;
408                goto out;
409        }
410
411        twd_base = of_iomap(np, 0);
412        if (!twd_base) {
413                err = -ENOMEM;
414                goto out;
415        }
416
417        err = twd_local_timer_common_register(np);
418
419out:
420        WARN(err, "twd_local_timer_of_register failed (%d)\n", err);
421        return err;
422}
423CLOCKSOURCE_OF_DECLARE(arm_twd_a9, "arm,cortex-a9-twd-timer", twd_local_timer_of_register);
424CLOCKSOURCE_OF_DECLARE(arm_twd_a5, "arm,cortex-a5-twd-timer", twd_local_timer_of_register);
425CLOCKSOURCE_OF_DECLARE(arm_twd_11mp, "arm,arm11mp-twd-timer", twd_local_timer_of_register);
426#endif
Note: See TracBrowser for help on using the repository browser.