]> nv-tegra.nvidia Code Review - linux-3.10.git/blob - arch/mips/kernel/cevt-r4k.c
08b84d476c8727c243f3a0bb2cb942eae65190f9
[linux-3.10.git] / arch / mips / kernel / cevt-r4k.c
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
15 static 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
39 static void mips_set_mode(enum clock_event_mode mode,
40                           struct clock_event_device *evt)
41 {
42         /* Nothing to do ...  */
43 }
44
45 static DEFINE_PER_CPU(struct clock_event_device, mips_clockevent_device);
46 static int cp0_timer_irq_installed;
47
48 /*
49  * Timer ack for an R4k-compatible timer of a known frequency.
50  */
51 static 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  */
60 static 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
74 static 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
105 out:
106         return IRQ_HANDLED;
107 }
108
109 static 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
120 DEFINE_PER_CPU(struct clock_event_device, smtc_dummy_clockevent_device);
121
122 static void smtc_set_mode(enum clock_event_mode mode,
123                           struct clock_event_device *evt)
124 {
125 }
126
127 static 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
135 static 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
167 static 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  */
174 static int c0_compare_int_pending(void)
175 {
176         return (read_c0_cause() >> cp0_compare_irq) & 0x100;
177 }
178
179 static 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
215 void __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 }