aboutsummaryrefslogtreecommitdiffstats
path: root/arch/mips/kernel/cevt-r4k.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/mips/kernel/cevt-r4k.c')
-rw-r--r--arch/mips/kernel/cevt-r4k.c272
1 files changed, 272 insertions, 0 deletions
diff --git a/arch/mips/kernel/cevt-r4k.c b/arch/mips/kernel/cevt-r4k.c
new file mode 100644
index 000000000000..08b84d476c87
--- /dev/null
+++ b/arch/mips/kernel/cevt-r4k.c
@@ -0,0 +1,272 @@
1/*
2 * This file is subject to the terms and conditions of the GNU General Public
3 * License. See the file "COPYING" in the main directory of this archive
4 * for more details.
5 *
6 * Copyright (C) 2007 MIPS Technologies, Inc.
7 * Copyright (C) 2007 Ralf Baechle <ralf@linux-mips.org>
8 */
9#include <linux/clockchips.h>
10#include <linux/interrupt.h>
11#include <linux/percpu.h>
12
13#include <asm/time.h>
14
15static int mips_next_event(unsigned long delta,
16 struct clock_event_device *evt)
17{
18 unsigned int cnt;
19 int res;
20
21#ifdef CONFIG_MIPS_MT_SMTC
22 {
23 unsigned long flags, vpflags;
24 local_irq_save(flags);
25 vpflags = dvpe();
26#endif
27 cnt = read_c0_count();
28 cnt += delta;
29 write_c0_compare(cnt);
30 res = ((long)(read_c0_count() - cnt ) > 0) ? -ETIME : 0;
31#ifdef CONFIG_MIPS_MT_SMTC
32 evpe(vpflags);
33 local_irq_restore(flags);
34 }
35#endif
36 return res;
37}
38
39static void mips_set_mode(enum clock_event_mode mode,
40 struct clock_event_device *evt)
41{
42 /* Nothing to do ... */
43}
44
45static DEFINE_PER_CPU(struct clock_event_device, mips_clockevent_device);
46static int cp0_timer_irq_installed;
47
48/*
49 * Timer ack for an R4k-compatible timer of a known frequency.
50 */
51static void c0_timer_ack(void)
52{
53 write_c0_compare(read_c0_compare());
54}
55
56/*
57 * Possibly handle a performance counter interrupt.
58 * Return true if the timer interrupt should not be checked
59 */
60static inline int handle_perf_irq(int r2)
61{
62 /*
63 * The performance counter overflow interrupt may be shared with the
64 * timer interrupt (cp0_perfcount_irq < 0). If it is and a
65 * performance counter has overflowed (perf_irq() == IRQ_HANDLED)
66 * and we can't reliably determine if a counter interrupt has also
67 * happened (!r2) then don't check for a timer interrupt.
68 */
69 return (cp0_perfcount_irq < 0) &&
70 perf_irq() == IRQ_HANDLED &&
71 !r2;
72}
73
74static irqreturn_t c0_compare_interrupt(int irq, void *dev_id)
75{
76 const int r2 = cpu_has_mips_r2;
77 struct clock_event_device *cd;
78 int cpu = smp_processor_id();
79
80 /*
81 * Suckage alert:
82 * Before R2 of the architecture there was no way to see if a
83 * performance counter interrupt was pending, so we have to run
84 * the performance counter interrupt handler anyway.
85 */
86 if (handle_perf_irq(r2))
87 goto out;
88
89 /*
90 * The same applies to performance counter interrupts. But with the
91 * above we now know that the reason we got here must be a timer
92 * interrupt. Being the paranoiacs we are we check anyway.
93 */
94 if (!r2 || (read_c0_cause() & (1 << 30))) {
95 c0_timer_ack();
96#ifdef CONFIG_MIPS_MT_SMTC
97 if (cpu_data[cpu].vpe_id)
98 goto out;
99 cpu = 0;
100#endif
101 cd = &per_cpu(mips_clockevent_device, cpu);
102 cd->event_handler(cd);
103 }
104
105out:
106 return IRQ_HANDLED;
107}
108
109static struct irqaction c0_compare_irqaction = {
110 .handler = c0_compare_interrupt,
111#ifdef CONFIG_MIPS_MT_SMTC
112 .flags = IRQF_DISABLED,
113#else
114 .flags = IRQF_DISABLED | IRQF_PERCPU,
115#endif
116 .name = "timer",
117};
118
119#ifdef CONFIG_MIPS_MT_SMTC
120DEFINE_PER_CPU(struct clock_event_device, smtc_dummy_clockevent_device);
121
122static void smtc_set_mode(enum clock_event_mode mode,
123 struct clock_event_device *evt)
124{
125}
126
127static void mips_broadcast(cpumask_t mask)
128{
129 unsigned int cpu;
130
131 for_each_cpu_mask(cpu, mask)
132 smtc_send_ipi(cpu, SMTC_CLOCK_TICK, 0);
133}
134
135static void setup_smtc_dummy_clockevent_device(void)
136{
137 //uint64_t mips_freq = mips_hpt_^frequency;
138 unsigned int cpu = smp_processor_id();
139 struct clock_event_device *cd;
140
141 cd = &per_cpu(smtc_dummy_clockevent_device, cpu);
142
143 cd->name = "SMTC";
144 cd->features = CLOCK_EVT_FEAT_DUMMY;
145
146 /* Calculate the min / max delta */
147 cd->mult = 0; //div_sc((unsigned long) mips_freq, NSEC_PER_SEC, 32);
148 cd->shift = 0; //32;
149 cd->max_delta_ns = 0; //clockevent_delta2ns(0x7fffffff, cd);
150 cd->min_delta_ns = 0; //clockevent_delta2ns(0x30, cd);
151
152 cd->rating = 200;
153 cd->irq = 17; //-1;
154// if (cpu)
155// cd->cpumask = CPU_MASK_ALL; // cpumask_of_cpu(cpu);
156// else
157 cd->cpumask = cpumask_of_cpu(cpu);
158
159 cd->set_mode = smtc_set_mode;
160
161 cd->broadcast = mips_broadcast;
162
163 clockevents_register_device(cd);
164}
165#endif
166
167static void mips_event_handler(struct clock_event_device *dev)
168{
169}
170
171/*
172 * FIXME: This doesn't hold for the relocated E9000 compare interrupt.
173 */
174static int c0_compare_int_pending(void)
175{
176 return (read_c0_cause() >> cp0_compare_irq) & 0x100;
177}
178
179static int c0_compare_int_usable(void)
180{
181 const unsigned int delta = 0x300000;
182 unsigned int cnt;
183
184 /*
185 * IP7 already pending? Try to clear it by acking the timer.
186 */
187 if (c0_compare_int_pending()) {
188 write_c0_compare(read_c0_compare());
189 irq_disable_hazard();
190 if (c0_compare_int_pending())
191 return 0;
192 }
193
194 cnt = read_c0_count();
195 cnt += delta;
196 write_c0_compare(cnt);
197
198 while ((long)(read_c0_count() - cnt) <= 0)
199 ; /* Wait for expiry */
200
201 if (!c0_compare_int_pending())
202 return 0;
203
204 write_c0_compare(read_c0_compare());
205 irq_disable_hazard();
206 if (c0_compare_int_pending())
207 return 0;
208
209 /*
210 * Feels like a real count / compare timer.
211 */
212 return 1;
213}
214
215void __cpuinit mips_clockevent_init(void)
216{
217 uint64_t mips_freq = mips_hpt_frequency;
218 unsigned int cpu = smp_processor_id();
219 struct clock_event_device *cd;
220 unsigned int irq = MIPS_CPU_IRQ_BASE + 7;
221
222 if (!cpu_has_counter)
223 return;
224
225#ifdef CONFIG_MIPS_MT_SMTC
226 setup_smtc_dummy_clockevent_device();
227
228 /*
229 * On SMTC we only register VPE0's compare interrupt as clockevent
230 * device.
231 */
232 if (cpu)
233 return;
234#endif
235
236 if (!c0_compare_int_usable())
237 return;
238
239 cd = &per_cpu(mips_clockevent_device, cpu);
240
241 cd->name = "MIPS";
242 cd->features = CLOCK_EVT_FEAT_ONESHOT;
243
244 /* Calculate the min / max delta */
245 cd->mult = div_sc((unsigned long) mips_freq, NSEC_PER_SEC, 32);
246 cd->shift = 32;
247 cd->max_delta_ns = clockevent_delta2ns(0x7fffffff, cd);
248 cd->min_delta_ns = clockevent_delta2ns(0x300, cd);
249
250 cd->rating = 300;
251 cd->irq = irq;
252#ifdef CONFIG_MIPS_MT_SMTC
253 cd->cpumask = CPU_MASK_ALL;
254#else
255 cd->cpumask = cpumask_of_cpu(cpu);
256#endif
257 cd->set_next_event = mips_next_event;
258 cd->set_mode = mips_set_mode;
259 cd->event_handler = mips_event_handler;
260
261 clockevents_register_device(cd);
262
263 if (!cp0_timer_irq_installed) {
264#ifdef CONFIG_MIPS_MT_SMTC
265#define CPUCTR_IMASKBIT (0x100 << cp0_compare_irq)
266 setup_irq_smtc(irq, &c0_compare_irqaction, CPUCTR_IMASKBIT);
267#else
268 setup_irq(irq, &c0_compare_irqaction);
269#endif /* CONFIG_MIPS_MT_SMTC */
270 cp0_timer_irq_installed = 1;
271 }
272}