Commit | Line | Data |
---|---|---|
42f77542 RB |
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 | ||
f887b93e | 13 | #include <asm/smtc_ipi.h> |
42f77542 RB |
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); | |
c637fecb | 31 | res = ((int)(read_c0_count() - cnt) > 0) ? -ETIME : 0; |
42f77542 RB |
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 | { | |
3a6c43a7 | 182 | unsigned int delta; |
42f77542 RB |
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()) { | |
dab969c0 | 189 | write_c0_compare(read_c0_count()); |
42f77542 RB |
190 | irq_disable_hazard(); |
191 | if (c0_compare_int_pending()) | |
192 | return 0; | |
193 | } | |
194 | ||
3a6c43a7 AN |
195 | for (delta = 0x10; delta <= 0x400000; delta <<= 1) { |
196 | cnt = read_c0_count(); | |
197 | cnt += delta; | |
198 | write_c0_compare(cnt); | |
199 | irq_disable_hazard(); | |
200 | if ((int)(read_c0_count() - cnt) < 0) | |
201 | break; | |
202 | /* increase delta if the timer was already expired */ | |
203 | } | |
42f77542 | 204 | |
c637fecb | 205 | while ((int)(read_c0_count() - cnt) <= 0) |
42f77542 RB |
206 | ; /* Wait for expiry */ |
207 | ||
208 | if (!c0_compare_int_pending()) | |
209 | return 0; | |
210 | ||
dab969c0 | 211 | write_c0_compare(read_c0_count()); |
42f77542 RB |
212 | irq_disable_hazard(); |
213 | if (c0_compare_int_pending()) | |
214 | return 0; | |
215 | ||
216 | /* | |
217 | * Feels like a real count / compare timer. | |
218 | */ | |
219 | return 1; | |
220 | } | |
221 | ||
222 | void __cpuinit mips_clockevent_init(void) | |
223 | { | |
224 | uint64_t mips_freq = mips_hpt_frequency; | |
225 | unsigned int cpu = smp_processor_id(); | |
226 | struct clock_event_device *cd; | |
38760d40 | 227 | unsigned int irq; |
42f77542 | 228 | |
22df3f53 | 229 | if (!cpu_has_counter || !mips_hpt_frequency) |
42f77542 RB |
230 | return; |
231 | ||
232 | #ifdef CONFIG_MIPS_MT_SMTC | |
233 | setup_smtc_dummy_clockevent_device(); | |
234 | ||
235 | /* | |
236 | * On SMTC we only register VPE0's compare interrupt as clockevent | |
237 | * device. | |
238 | */ | |
239 | if (cpu) | |
240 | return; | |
241 | #endif | |
242 | ||
243 | if (!c0_compare_int_usable()) | |
244 | return; | |
245 | ||
38760d40 RB |
246 | /* |
247 | * With vectored interrupts things are getting platform specific. | |
248 | * get_c0_compare_int is a hook to allow a platform to return the | |
249 | * interrupt number of it's liking. | |
250 | */ | |
251 | irq = MIPS_CPU_IRQ_BASE + cp0_compare_irq; | |
252 | if (get_c0_compare_int) | |
253 | irq = get_c0_compare_int(); | |
254 | ||
42f77542 RB |
255 | cd = &per_cpu(mips_clockevent_device, cpu); |
256 | ||
257 | cd->name = "MIPS"; | |
258 | cd->features = CLOCK_EVT_FEAT_ONESHOT; | |
259 | ||
260 | /* Calculate the min / max delta */ | |
261 | cd->mult = div_sc((unsigned long) mips_freq, NSEC_PER_SEC, 32); | |
262 | cd->shift = 32; | |
263 | cd->max_delta_ns = clockevent_delta2ns(0x7fffffff, cd); | |
264 | cd->min_delta_ns = clockevent_delta2ns(0x300, cd); | |
265 | ||
266 | cd->rating = 300; | |
267 | cd->irq = irq; | |
268 | #ifdef CONFIG_MIPS_MT_SMTC | |
269 | cd->cpumask = CPU_MASK_ALL; | |
270 | #else | |
271 | cd->cpumask = cpumask_of_cpu(cpu); | |
272 | #endif | |
273 | cd->set_next_event = mips_next_event; | |
274 | cd->set_mode = mips_set_mode; | |
275 | cd->event_handler = mips_event_handler; | |
276 | ||
277 | clockevents_register_device(cd); | |
278 | ||
38760d40 RB |
279 | if (!cp0_timer_irq_installed) |
280 | return; | |
281 | ||
282 | cp0_timer_irq_installed = 1; | |
283 | ||
42f77542 RB |
284 | #ifdef CONFIG_MIPS_MT_SMTC |
285 | #define CPUCTR_IMASKBIT (0x100 << cp0_compare_irq) | |
38760d40 | 286 | setup_irq_smtc(irq, &c0_compare_irqaction, CPUCTR_IMASKBIT); |
42f77542 | 287 | #else |
38760d40 RB |
288 | setup_irq(irq, &c0_compare_irqaction); |
289 | #endif | |
42f77542 | 290 | } |