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 #include <linux/smp.h> 13 #include <linux/irq.h> 14 15 #include <asm/time.h> 16 #include <asm/cevt-r4k.h> 17 #include <asm/gic.h> 18 19 static int mips_next_event(unsigned long delta, 20 struct clock_event_device *evt) 21 { 22 unsigned int cnt; 23 int res; 24 25 cnt = read_c0_count(); 26 cnt += delta; 27 write_c0_compare(cnt); 28 res = ((int)(read_c0_count() - cnt) >= 0) ? -ETIME : 0; 29 return res; 30 } 31 32 void mips_set_clock_mode(enum clock_event_mode mode, 33 struct clock_event_device *evt) 34 { 35 /* Nothing to do ... */ 36 } 37 38 DEFINE_PER_CPU(struct clock_event_device, mips_clockevent_device); 39 int cp0_timer_irq_installed; 40 41 irqreturn_t c0_compare_interrupt(int irq, void *dev_id) 42 { 43 const int r2 = cpu_has_mips_r2; 44 struct clock_event_device *cd; 45 int cpu = smp_processor_id(); 46 47 /* 48 * Suckage alert: 49 * Before R2 of the architecture there was no way to see if a 50 * performance counter interrupt was pending, so we have to run 51 * the performance counter interrupt handler anyway. 52 */ 53 if (handle_perf_irq(r2)) 54 goto out; 55 56 /* 57 * The same applies to performance counter interrupts. But with the 58 * above we now know that the reason we got here must be a timer 59 * interrupt. Being the paranoiacs we are we check anyway. 60 */ 61 if (!r2 || (read_c0_cause() & (1 << 30))) { 62 /* Clear Count/Compare Interrupt */ 63 write_c0_compare(read_c0_compare()); 64 cd = &per_cpu(mips_clockevent_device, cpu); 65 cd->event_handler(cd); 66 } 67 68 out: 69 return IRQ_HANDLED; 70 } 71 72 struct irqaction c0_compare_irqaction = { 73 .handler = c0_compare_interrupt, 74 .flags = IRQF_PERCPU | IRQF_TIMER, 75 .name = "timer", 76 }; 77 78 79 void mips_event_handler(struct clock_event_device *dev) 80 { 81 } 82 83 /* 84 * FIXME: This doesn't hold for the relocated E9000 compare interrupt. 85 */ 86 static int c0_compare_int_pending(void) 87 { 88 #ifdef CONFIG_IRQ_GIC 89 if (cpu_has_veic) 90 return gic_get_timer_pending(); 91 #endif 92 return (read_c0_cause() >> cp0_compare_irq_shift) & (1ul << CAUSEB_IP); 93 } 94 95 /* 96 * Compare interrupt can be routed and latched outside the core, 97 * so wait up to worst case number of cycle counter ticks for timer interrupt 98 * changes to propagate to the cause register. 99 */ 100 #define COMPARE_INT_SEEN_TICKS 50 101 102 int c0_compare_int_usable(void) 103 { 104 unsigned int delta; 105 unsigned int cnt; 106 107 #ifdef CONFIG_KVM_GUEST 108 return 1; 109 #endif 110 111 /* 112 * IP7 already pending? Try to clear it by acking the timer. 113 */ 114 if (c0_compare_int_pending()) { 115 cnt = read_c0_count(); 116 write_c0_compare(cnt); 117 back_to_back_c0_hazard(); 118 while (read_c0_count() < (cnt + COMPARE_INT_SEEN_TICKS)) 119 if (!c0_compare_int_pending()) 120 break; 121 if (c0_compare_int_pending()) 122 return 0; 123 } 124 125 for (delta = 0x10; delta <= 0x400000; delta <<= 1) { 126 cnt = read_c0_count(); 127 cnt += delta; 128 write_c0_compare(cnt); 129 back_to_back_c0_hazard(); 130 if ((int)(read_c0_count() - cnt) < 0) 131 break; 132 /* increase delta if the timer was already expired */ 133 } 134 135 while ((int)(read_c0_count() - cnt) <= 0) 136 ; /* Wait for expiry */ 137 138 while (read_c0_count() < (cnt + COMPARE_INT_SEEN_TICKS)) 139 if (c0_compare_int_pending()) 140 break; 141 if (!c0_compare_int_pending()) 142 return 0; 143 cnt = read_c0_count(); 144 write_c0_compare(cnt); 145 back_to_back_c0_hazard(); 146 while (read_c0_count() < (cnt + COMPARE_INT_SEEN_TICKS)) 147 if (!c0_compare_int_pending()) 148 break; 149 if (c0_compare_int_pending()) 150 return 0; 151 152 /* 153 * Feels like a real count / compare timer. 154 */ 155 return 1; 156 } 157 158 int r4k_clockevent_init(void) 159 { 160 unsigned int cpu = smp_processor_id(); 161 struct clock_event_device *cd; 162 unsigned int irq; 163 164 if (!cpu_has_counter || !mips_hpt_frequency) 165 return -ENXIO; 166 167 if (!c0_compare_int_usable()) 168 return -ENXIO; 169 170 /* 171 * With vectored interrupts things are getting platform specific. 172 * get_c0_compare_int is a hook to allow a platform to return the 173 * interrupt number of it's liking. 174 */ 175 irq = MIPS_CPU_IRQ_BASE + cp0_compare_irq; 176 if (get_c0_compare_int) 177 irq = get_c0_compare_int(); 178 179 cd = &per_cpu(mips_clockevent_device, cpu); 180 181 cd->name = "MIPS"; 182 cd->features = CLOCK_EVT_FEAT_ONESHOT | 183 CLOCK_EVT_FEAT_C3STOP | 184 CLOCK_EVT_FEAT_PERCPU; 185 186 clockevent_set_clock(cd, mips_hpt_frequency); 187 188 /* Calculate the min / max delta */ 189 cd->max_delta_ns = clockevent_delta2ns(0x7fffffff, cd); 190 cd->min_delta_ns = clockevent_delta2ns(0x300, cd); 191 192 cd->rating = 300; 193 cd->irq = irq; 194 cd->cpumask = cpumask_of(cpu); 195 cd->set_next_event = mips_next_event; 196 cd->set_mode = mips_set_clock_mode; 197 cd->event_handler = mips_event_handler; 198 199 clockevents_register_device(cd); 200 201 if (cp0_timer_irq_installed) 202 return 0; 203 204 cp0_timer_irq_installed = 1; 205 206 setup_irq(irq, &c0_compare_irqaction); 207 208 return 0; 209 } 210 211