diff options
author | Andi Kleen <ak@suse.de> | 2006-02-03 15:51:41 -0500 |
---|---|---|
committer | Linus Torvalds <torvalds@g5.osdl.org> | 2006-02-04 19:43:15 -0500 |
commit | 0c3749c41f5eee0da36bbf92b2793338b4d8574f (patch) | |
tree | 3af004c5533ab04297004f5ec40da4c6801b8fda /arch/x86_64/kernel | |
parent | 099f318b8d97490e58c595eb1b6d3415da5ccc03 (diff) |
[PATCH] x86_64: Calibrate APIC timer using PM timer
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>
Diffstat (limited to 'arch/x86_64/kernel')
-rw-r--r-- | arch/x86_64/kernel/apic.c | 33 | ||||
-rw-r--r-- | arch/x86_64/kernel/pmtimer.c | 20 |
2 files changed, 47 insertions, 6 deletions
diff --git a/arch/x86_64/kernel/apic.c b/arch/x86_64/kernel/apic.c index 673a2fe9923c..c02218b3ae2b 100644 --- a/arch/x86_64/kernel/apic.c +++ b/arch/x86_64/kernel/apic.c | |||
@@ -40,6 +40,7 @@ | |||
40 | 40 | ||
41 | int apic_verbosity; | 41 | int apic_verbosity; |
42 | int apic_runs_main_timer; | 42 | int apic_runs_main_timer; |
43 | int apic_calibrate_pmtmr __initdata; | ||
43 | 44 | ||
44 | int disable_apic_timer __initdata; | 45 | int disable_apic_timer __initdata; |
45 | 46 | ||
@@ -746,14 +747,27 @@ static int __init calibrate_APIC_clock(void) | |||
746 | __setup_APIC_LVTT(1000000000); | 747 | __setup_APIC_LVTT(1000000000); |
747 | 748 | ||
748 | apic_start = apic_read(APIC_TMCCT); | 749 | apic_start = apic_read(APIC_TMCCT); |
749 | rdtscl(tsc_start); | 750 | #ifdef CONFIG_X86_PM_TIMER |
750 | 751 | if (apic_calibrate_pmtmr && pmtmr_ioport) { | |
751 | do { | 752 | pmtimer_wait(5000); /* 5ms wait */ |
752 | apic = apic_read(APIC_TMCCT); | 753 | apic = apic_read(APIC_TMCCT); |
753 | rdtscl(tsc); | 754 | result = (apic_start - apic) * 1000L / 5; |
754 | } while ((tsc - tsc_start) < TICK_COUNT && (apic - apic_start) < TICK_COUNT); | 755 | } else |
756 | #endif | ||
757 | { | ||
758 | rdtscl(tsc_start); | ||
759 | |||
760 | do { | ||
761 | apic = apic_read(APIC_TMCCT); | ||
762 | rdtscl(tsc); | ||
763 | } while ((tsc - tsc_start) < TICK_COUNT && | ||
764 | (apic - apic_start) < TICK_COUNT); | ||
765 | |||
766 | result = (apic_start - apic) * 1000L * cpu_khz / | ||
767 | (tsc - tsc_start); | ||
768 | } | ||
769 | printk("result %d\n", result); | ||
755 | 770 | ||
756 | result = (apic_start - apic) * 1000L * cpu_khz / (tsc - tsc_start); | ||
757 | 771 | ||
758 | printk(KERN_INFO "Detected %d.%03d MHz APIC timer.\n", | 772 | printk(KERN_INFO "Detected %d.%03d MHz APIC timer.\n", |
759 | result / 1000 / 1000, result / 1000 % 1000); | 773 | result / 1000 / 1000, result / 1000 % 1000); |
@@ -1115,6 +1129,13 @@ static __init int setup_noapicmaintimer(char *str) | |||
1115 | } | 1129 | } |
1116 | __setup("noapicmaintimer", setup_noapicmaintimer); | 1130 | __setup("noapicmaintimer", setup_noapicmaintimer); |
1117 | 1131 | ||
1132 | static __init int setup_apicpmtimer(char *s) | ||
1133 | { | ||
1134 | apic_calibrate_pmtmr = 1; | ||
1135 | return setup_apicmaintimer(NULL); | ||
1136 | } | ||
1137 | __setup("apicpmtimer", setup_apicpmtimer); | ||
1138 | |||
1118 | /* dummy parsing: see setup.c */ | 1139 | /* dummy parsing: see setup.c */ |
1119 | 1140 | ||
1120 | __setup("disableapic", setup_disableapic); | 1141 | __setup("disableapic", setup_disableapic); |
diff --git a/arch/x86_64/kernel/pmtimer.c b/arch/x86_64/kernel/pmtimer.c index 8b2655ae4e61..5c51d10408a6 100644 --- a/arch/x86_64/kernel/pmtimer.c +++ b/arch/x86_64/kernel/pmtimer.c | |||
@@ -80,6 +80,26 @@ int pmtimer_mark_offset(void) | |||
80 | return lost - 1; | 80 | return lost - 1; |
81 | } | 81 | } |
82 | 82 | ||
83 | static unsigned pmtimer_wait_tick(void) | ||
84 | { | ||
85 | u32 a, b; | ||
86 | for (a = b = inl(pmtmr_ioport) & ACPI_PM_MASK; | ||
87 | a == b; | ||
88 | b = inl(pmtmr_ioport) & ACPI_PM_MASK) | ||
89 | ; | ||
90 | return b; | ||
91 | } | ||
92 | |||
93 | /* note: wait time is rounded up to one tick */ | ||
94 | void pmtimer_wait(unsigned us) | ||
95 | { | ||
96 | u32 a, b; | ||
97 | a = pmtimer_wait_tick(); | ||
98 | do { | ||
99 | b = inl(pmtmr_ioport); | ||
100 | } while (cyc2us(b - a) < us); | ||
101 | } | ||
102 | |||
83 | void pmtimer_resume(void) | 103 | void pmtimer_resume(void) |
84 | { | 104 | { |
85 | last_pmtmr_tick = inl(pmtmr_ioport); | 105 | last_pmtmr_tick = inl(pmtmr_ioport); |