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