[PATCH] x86_64: Calibrate APIC timer using PM timer
authorAndi Kleen <ak@suse.de>
Fri, 3 Feb 2006 20:51:41 +0000 (21:51 +0100)
committerLinus Torvalds <torvalds@g5.osdl.org>
Sun, 5 Feb 2006 00:43:15 +0000 (16:43 -0800)
On some broken motherboards (at least one NForce3 based AMD64 laptop)
the PIT timer runs at a incorrect frequency.  This patch adds a new
option "apicpmtimer" that allows to use the APIC timer and calibrate it
using the PMTimer.  It requires the earlier patch that allows to run the
main timer from the APIC.

Specifying apicpmtimer implies apicmaintimer.

The option defaults to off for now.

I tested it on a few systems and the resulting APIC timer frequencies
were usually a bit off, but always <1%, which should be tolerable.

TBD figure out heuristic to enable this automatically on the affected
systems TBD perhaps do it on all NForce3s or using DMI?

Signed-off-by: Andi Kleen <ak@suse.de>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
Documentation/x86_64/boot-options.txt
arch/x86_64/kernel/apic.c
arch/x86_64/kernel/pmtimer.c
include/asm-x86_64/proto.h

index 654ea4fccff880f0e42a95d0aa3c06ffd60ee1ac..153740f460a6cc67713662c2a2cf7b6c50169ebe 100644 (file)
@@ -47,6 +47,11 @@ APICs
    noapicmaintimer  Don't do time keeping using the APIC timer.
                 Useful when this option was auto selected, but doesn't work.
 
+   apicpmtimer
+                Do APIC timer calibration using the pmtimer. Implies
+                apicmaintimer. Useful when your PIT timer is totally
+                broken.
+
 Early Console
 
    syntax: earlyprintk=vga
index 673a2fe9923cb492fd96eff68f4b3a08a92cc965..c02218b3ae2b23b51056a0d36c3a48258ba87a08 100644 (file)
@@ -40,6 +40,7 @@
 
 int apic_verbosity;
 int apic_runs_main_timer;
+int apic_calibrate_pmtmr __initdata;
 
 int disable_apic_timer __initdata;
 
@@ -746,14 +747,27 @@ static int __init calibrate_APIC_clock(void)
        __setup_APIC_LVTT(1000000000);
 
        apic_start = apic_read(APIC_TMCCT);
-       rdtscl(tsc_start);
-
-       do {
+#ifdef CONFIG_X86_PM_TIMER
+       if (apic_calibrate_pmtmr && pmtmr_ioport) {
+               pmtimer_wait(5000);  /* 5ms wait */
                apic = apic_read(APIC_TMCCT);
-               rdtscl(tsc);
-       } while ((tsc - tsc_start) < TICK_COUNT && (apic - apic_start) < TICK_COUNT);
+               result = (apic_start - apic) * 1000L / 5;
+       } else
+#endif
+       {
+               rdtscl(tsc_start);
+
+               do {
+                       apic = apic_read(APIC_TMCCT);
+                       rdtscl(tsc);
+               } while ((tsc - tsc_start) < TICK_COUNT &&
+                               (apic - apic_start) < TICK_COUNT);
+
+               result = (apic_start - apic) * 1000L * cpu_khz /
+                                       (tsc - tsc_start);
+       }
+       printk("result %d\n", result);
 
-       result = (apic_start - apic) * 1000L * cpu_khz / (tsc - tsc_start);
 
        printk(KERN_INFO "Detected %d.%03d MHz APIC timer.\n",
                result / 1000 / 1000, result / 1000 % 1000);
@@ -1115,6 +1129,13 @@ static __init int setup_noapicmaintimer(char *str)
 }
 __setup("noapicmaintimer", setup_noapicmaintimer);
 
+static __init int setup_apicpmtimer(char *s)
+{
+       apic_calibrate_pmtmr = 1;
+       return setup_apicmaintimer(NULL);
+}
+__setup("apicpmtimer", setup_apicpmtimer);
+
 /* dummy parsing: see setup.c */
 
 __setup("disableapic", setup_disableapic); 
index 8b2655ae4e61cb2601b44b43e9d89e4e7774a668..5c51d10408a677695c9f2d2d8cd0e5d69571c760 100644 (file)
@@ -80,6 +80,26 @@ int pmtimer_mark_offset(void)
        return lost - 1;
 }
 
+static unsigned pmtimer_wait_tick(void)
+{
+       u32 a, b;
+       for (a = b = inl(pmtmr_ioport) & ACPI_PM_MASK;
+            a == b;
+            b = inl(pmtmr_ioport) & ACPI_PM_MASK)
+               ;
+       return b;
+}
+
+/* note: wait time is rounded up to one tick */
+void pmtimer_wait(unsigned us)
+{
+       u32 a, b;
+       a = pmtimer_wait_tick();
+       do {
+               b = inl(pmtmr_ioport);
+       } while (cyc2us(b - a) < us);
+}
+
 void pmtimer_resume(void)
 {
        last_pmtmr_tick = inl(pmtmr_ioport);
index a6748b9568fef2a0ba7e4acf1afce1b1a4347c94..c99832e7bf3f76a0b531e8d9ae913fc17fcb3f40 100644 (file)
@@ -42,6 +42,7 @@ extern void iommu_hole_init(void);
 extern void time_init_gtod(void);
 extern int pmtimer_mark_offset(void);
 extern void pmtimer_resume(void);
+extern void pmtimer_wait(unsigned);
 extern unsigned int do_gettimeoffset_pm(void);
 #ifdef CONFIG_X86_PM_TIMER
 extern u32 pmtmr_ioport;