|
@@ -478,6 +478,7 @@ static void __cpuinit setup_APIC_timer(void)
|
|
|
clockevents_register_device(levt);
|
|
|
}
|
|
|
|
|
|
+#ifdef CONFIG_X86_64
|
|
|
/*
|
|
|
* In this function we calibrate APIC bus clocks to the external
|
|
|
* timer. Unfortunately we cannot use jiffies and the timer irq
|
|
@@ -560,6 +561,220 @@ static int __init calibrate_APIC_clock(void)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
+#else
|
|
|
+/*
|
|
|
+ * In this functions we calibrate APIC bus clocks to the external timer.
|
|
|
+ *
|
|
|
+ * We want to do the calibration only once since we want to have local timer
|
|
|
+ * irqs syncron. CPUs connected by the same APIC bus have the very same bus
|
|
|
+ * frequency.
|
|
|
+ *
|
|
|
+ * This was previously done by reading the PIT/HPET and waiting for a wrap
|
|
|
+ * around to find out, that a tick has elapsed. I have a box, where the PIT
|
|
|
+ * readout is broken, so it never gets out of the wait loop again. This was
|
|
|
+ * also reported by others.
|
|
|
+ *
|
|
|
+ * Monitoring the jiffies value is inaccurate and the clockevents
|
|
|
+ * infrastructure allows us to do a simple substitution of the interrupt
|
|
|
+ * handler.
|
|
|
+ *
|
|
|
+ * The calibration routine also uses the pm_timer when possible, as the PIT
|
|
|
+ * happens to run way too slow (factor 2.3 on my VAIO CoreDuo, which goes
|
|
|
+ * back to normal later in the boot process).
|
|
|
+ */
|
|
|
+
|
|
|
+#define LAPIC_CAL_LOOPS (HZ/10)
|
|
|
+
|
|
|
+static __initdata int lapic_cal_loops = -1;
|
|
|
+static __initdata long lapic_cal_t1, lapic_cal_t2;
|
|
|
+static __initdata unsigned long long lapic_cal_tsc1, lapic_cal_tsc2;
|
|
|
+static __initdata unsigned long lapic_cal_pm1, lapic_cal_pm2;
|
|
|
+static __initdata unsigned long lapic_cal_j1, lapic_cal_j2;
|
|
|
+
|
|
|
+/*
|
|
|
+ * Temporary interrupt handler.
|
|
|
+ */
|
|
|
+static void __init lapic_cal_handler(struct clock_event_device *dev)
|
|
|
+{
|
|
|
+ unsigned long long tsc = 0;
|
|
|
+ long tapic = apic_read(APIC_TMCCT);
|
|
|
+ unsigned long pm = acpi_pm_read_early();
|
|
|
+
|
|
|
+ if (cpu_has_tsc)
|
|
|
+ rdtscll(tsc);
|
|
|
+
|
|
|
+ switch (lapic_cal_loops++) {
|
|
|
+ case 0:
|
|
|
+ lapic_cal_t1 = tapic;
|
|
|
+ lapic_cal_tsc1 = tsc;
|
|
|
+ lapic_cal_pm1 = pm;
|
|
|
+ lapic_cal_j1 = jiffies;
|
|
|
+ break;
|
|
|
+
|
|
|
+ case LAPIC_CAL_LOOPS:
|
|
|
+ lapic_cal_t2 = tapic;
|
|
|
+ lapic_cal_tsc2 = tsc;
|
|
|
+ if (pm < lapic_cal_pm1)
|
|
|
+ pm += ACPI_PM_OVRRUN;
|
|
|
+ lapic_cal_pm2 = pm;
|
|
|
+ lapic_cal_j2 = jiffies;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static int __init calibrate_APIC_clock(void)
|
|
|
+{
|
|
|
+ struct clock_event_device *levt = &__get_cpu_var(lapic_events);
|
|
|
+ const long pm_100ms = PMTMR_TICKS_PER_SEC/10;
|
|
|
+ const long pm_thresh = pm_100ms/100;
|
|
|
+ void (*real_handler)(struct clock_event_device *dev);
|
|
|
+ unsigned long deltaj;
|
|
|
+ long delta, deltapm;
|
|
|
+ int pm_referenced = 0;
|
|
|
+
|
|
|
+ local_irq_disable();
|
|
|
+
|
|
|
+ /* Replace the global interrupt handler */
|
|
|
+ real_handler = global_clock_event->event_handler;
|
|
|
+ global_clock_event->event_handler = lapic_cal_handler;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Setup the APIC counter to 1e9. There is no way the lapic
|
|
|
+ * can underflow in the 100ms detection time frame
|
|
|
+ */
|
|
|
+ __setup_APIC_LVTT(1000000000, 0, 0);
|
|
|
+
|
|
|
+ /* Let the interrupts run */
|
|
|
+ local_irq_enable();
|
|
|
+
|
|
|
+ while (lapic_cal_loops <= LAPIC_CAL_LOOPS)
|
|
|
+ cpu_relax();
|
|
|
+
|
|
|
+ local_irq_disable();
|
|
|
+
|
|
|
+ /* Restore the real event handler */
|
|
|
+ global_clock_event->event_handler = real_handler;
|
|
|
+
|
|
|
+ /* Build delta t1-t2 as apic timer counts down */
|
|
|
+ delta = lapic_cal_t1 - lapic_cal_t2;
|
|
|
+ apic_printk(APIC_VERBOSE, "... lapic delta = %ld\n", delta);
|
|
|
+
|
|
|
+ /* Check, if the PM timer is available */
|
|
|
+ deltapm = lapic_cal_pm2 - lapic_cal_pm1;
|
|
|
+ apic_printk(APIC_VERBOSE, "... PM timer delta = %ld\n", deltapm);
|
|
|
+
|
|
|
+ if (deltapm) {
|
|
|
+ unsigned long mult;
|
|
|
+ u64 res;
|
|
|
+
|
|
|
+ mult = clocksource_hz2mult(PMTMR_TICKS_PER_SEC, 22);
|
|
|
+
|
|
|
+ if (deltapm > (pm_100ms - pm_thresh) &&
|
|
|
+ deltapm < (pm_100ms + pm_thresh)) {
|
|
|
+ apic_printk(APIC_VERBOSE, "... PM timer result ok\n");
|
|
|
+ } else {
|
|
|
+ res = (((u64) deltapm) * mult) >> 22;
|
|
|
+ do_div(res, 1000000);
|
|
|
+ printk(KERN_WARNING "APIC calibration not consistent "
|
|
|
+ "with PM Timer: %ldms instead of 100ms\n",
|
|
|
+ (long)res);
|
|
|
+ /* Correct the lapic counter value */
|
|
|
+ res = (((u64) delta) * pm_100ms);
|
|
|
+ do_div(res, deltapm);
|
|
|
+ printk(KERN_INFO "APIC delta adjusted to PM-Timer: "
|
|
|
+ "%lu (%ld)\n", (unsigned long) res, delta);
|
|
|
+ delta = (long) res;
|
|
|
+ }
|
|
|
+ pm_referenced = 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Calculate the scaled math multiplication factor */
|
|
|
+ lapic_clockevent.mult = div_sc(delta, TICK_NSEC * LAPIC_CAL_LOOPS,
|
|
|
+ lapic_clockevent.shift);
|
|
|
+ lapic_clockevent.max_delta_ns =
|
|
|
+ clockevent_delta2ns(0x7FFFFF, &lapic_clockevent);
|
|
|
+ lapic_clockevent.min_delta_ns =
|
|
|
+ clockevent_delta2ns(0xF, &lapic_clockevent);
|
|
|
+
|
|
|
+ calibration_result = (delta * APIC_DIVISOR) / LAPIC_CAL_LOOPS;
|
|
|
+
|
|
|
+ apic_printk(APIC_VERBOSE, "..... delta %ld\n", delta);
|
|
|
+ apic_printk(APIC_VERBOSE, "..... mult: %ld\n", lapic_clockevent.mult);
|
|
|
+ apic_printk(APIC_VERBOSE, "..... calibration result: %u\n",
|
|
|
+ calibration_result);
|
|
|
+
|
|
|
+ if (cpu_has_tsc) {
|
|
|
+ delta = (long)(lapic_cal_tsc2 - lapic_cal_tsc1);
|
|
|
+ apic_printk(APIC_VERBOSE, "..... CPU clock speed is "
|
|
|
+ "%ld.%04ld MHz.\n",
|
|
|
+ (delta / LAPIC_CAL_LOOPS) / (1000000 / HZ),
|
|
|
+ (delta / LAPIC_CAL_LOOPS) % (1000000 / HZ));
|
|
|
+ }
|
|
|
+
|
|
|
+ apic_printk(APIC_VERBOSE, "..... host bus clock speed is "
|
|
|
+ "%u.%04u MHz.\n",
|
|
|
+ calibration_result / (1000000 / HZ),
|
|
|
+ calibration_result % (1000000 / HZ));
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Do a sanity check on the APIC calibration result
|
|
|
+ */
|
|
|
+ if (calibration_result < (1000000 / HZ)) {
|
|
|
+ local_irq_enable();
|
|
|
+ printk(KERN_WARNING
|
|
|
+ "APIC frequency too slow, disabling apic timer\n");
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ levt->features &= ~CLOCK_EVT_FEAT_DUMMY;
|
|
|
+
|
|
|
+ /* We trust the pm timer based calibration */
|
|
|
+ if (!pm_referenced) {
|
|
|
+ apic_printk(APIC_VERBOSE, "... verify APIC timer\n");
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Setup the apic timer manually
|
|
|
+ */
|
|
|
+ levt->event_handler = lapic_cal_handler;
|
|
|
+ lapic_timer_setup(CLOCK_EVT_MODE_PERIODIC, levt);
|
|
|
+ lapic_cal_loops = -1;
|
|
|
+
|
|
|
+ /* Let the interrupts run */
|
|
|
+ local_irq_enable();
|
|
|
+
|
|
|
+ while (lapic_cal_loops <= LAPIC_CAL_LOOPS)
|
|
|
+ cpu_relax();
|
|
|
+
|
|
|
+ local_irq_disable();
|
|
|
+
|
|
|
+ /* Stop the lapic timer */
|
|
|
+ lapic_timer_setup(CLOCK_EVT_MODE_SHUTDOWN, levt);
|
|
|
+
|
|
|
+ local_irq_enable();
|
|
|
+
|
|
|
+ /* Jiffies delta */
|
|
|
+ deltaj = lapic_cal_j2 - lapic_cal_j1;
|
|
|
+ apic_printk(APIC_VERBOSE, "... jiffies delta = %lu\n", deltaj);
|
|
|
+
|
|
|
+ /* Check, if the jiffies result is consistent */
|
|
|
+ if (deltaj >= LAPIC_CAL_LOOPS-2 && deltaj <= LAPIC_CAL_LOOPS+2)
|
|
|
+ apic_printk(APIC_VERBOSE, "... jiffies result ok\n");
|
|
|
+ else
|
|
|
+ levt->features |= CLOCK_EVT_FEAT_DUMMY;
|
|
|
+ } else
|
|
|
+ local_irq_enable();
|
|
|
+
|
|
|
+ if (levt->features & CLOCK_EVT_FEAT_DUMMY) {
|
|
|
+ printk(KERN_WARNING
|
|
|
+ "APIC timer disabled due to verification failure.\n");
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+#endif
|
|
|
+
|
|
|
/*
|
|
|
* Setup the boot APIC
|
|
|
*
|