summaryrefslogtreecommitdiffstats
path: root/arch/x86_64/kernel/pmtimer.c
diff options
context:
space:
mode:
authorAndi Kleen <ak@suse.de>2005-05-16 21:53:28 -0700
committerLinus Torvalds <torvalds@ppc970.osdl.org>2005-05-17 07:59:15 -0700
commit312df5f1a1da780e084b328bcabb02a6dcd044c3 (patch)
tree04f0a70177979e4b8924015448a72644f1ce1c79 /arch/x86_64/kernel/pmtimer.c
parent0af2be0b721997512191e981a051fcb070b87260 (diff)
[PATCH] x86_64: Add pmtimer support
There are unfortunately more and more multi processor Opteron systems which don't have HPET timer support in the southbridge. This covers in particular Nvidia and VIA chipsets. They also don't guarantee that the TSCs are synchronized between CPUs; and especially with MP powernow the systems are nearly unusable because the time gets very inconsistent between CPUs. The timer code for x86-64 was originally written under the assumption that we could fall back to the HPET timer on such systems. But this doesn't work there. Another alternative is to use the ACPI PM timer as primary time source. This patch does that. The kernel only uses PM timer when there is no other choice because it has some disadvantages. Ported over from i386. It should be faster than the i386 version because I dropped the "read three times" workaround, but is still considerable slower than HPET and also does not work together with vsyscalls which have to be disabled. Cc: <mark.langsdorf@amd.com> Signed-off-by: Andi Kleen <ak@suse.de> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
Diffstat (limited to 'arch/x86_64/kernel/pmtimer.c')
-rw-r--r--arch/x86_64/kernel/pmtimer.c101
1 files changed, 101 insertions, 0 deletions
diff --git a/arch/x86_64/kernel/pmtimer.c b/arch/x86_64/kernel/pmtimer.c
new file mode 100644
index 00000000000..feb5f108dd2
--- /dev/null
+++ b/arch/x86_64/kernel/pmtimer.c
@@ -0,0 +1,101 @@
+/* Ported over from i386 by AK, original copyright was:
+ *
+ * (C) Dominik Brodowski <linux@brodo.de> 2003
+ *
+ * Driver to use the Power Management Timer (PMTMR) available in some
+ * southbridges as primary timing source for the Linux kernel.
+ *
+ * Based on parts of linux/drivers/acpi/hardware/hwtimer.c, timer_pit.c,
+ * timer_hpet.c, and on Arjan van de Ven's implementation for 2.4.
+ *
+ * This file is licensed under the GPL v2.
+ *
+ * Dropped all the hardware bug workarounds for now. Hopefully they
+ * are not needed on 64bit chipsets.
+ */
+
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/time.h>
+#include <linux/init.h>
+#include <linux/cpumask.h>
+#include <asm/io.h>
+#include <asm/proto.h>
+#include <asm/msr.h>
+#include <asm/vsyscall.h>
+
+/* The I/O port the PMTMR resides at.
+ * The location is detected during setup_arch(),
+ * in arch/i386/kernel/acpi/boot.c */
+u32 pmtmr_ioport;
+
+/* value of the Power timer at last timer interrupt */
+static u32 offset_delay;
+static u32 last_pmtmr_tick;
+
+#define ACPI_PM_MASK 0xFFFFFF /* limit it to 24 bits */
+
+static inline u32 cyc2us(u32 cycles)
+{
+ /* The Power Management Timer ticks at 3.579545 ticks per microsecond.
+ * 1 / PM_TIMER_FREQUENCY == 0.27936511 =~ 286/1024 [error: 0.024%]
+ *
+ * Even with HZ = 100, delta is at maximum 35796 ticks, so it can
+ * easily be multiplied with 286 (=0x11E) without having to fear
+ * u32 overflows.
+ */
+ cycles *= 286;
+ return (cycles >> 10);
+}
+
+int pmtimer_mark_offset(void)
+{
+ static int first_run = 1;
+ unsigned long tsc;
+ u32 lost;
+
+ u32 tick = inl(pmtmr_ioport);
+ u32 delta;
+
+ delta = cyc2us((tick - last_pmtmr_tick) & ACPI_PM_MASK);
+
+ last_pmtmr_tick = tick;
+ monotonic_base += delta * NSEC_PER_USEC;
+
+ delta += offset_delay;
+
+ lost = delta / (USEC_PER_SEC / HZ);
+ offset_delay = delta % (USEC_PER_SEC / HZ);
+
+ rdtscll(tsc);
+ vxtime.last_tsc = tsc - offset_delay * cpu_khz;
+
+ /* don't calculate delay for first run,
+ or if we've got less then a tick */
+ if (first_run || (lost < 1)) {
+ first_run = 0;
+ offset_delay = 0;
+ }
+
+ return lost - 1;
+}
+
+unsigned int do_gettimeoffset_pm(void)
+{
+ u32 now, offset, delta = 0;
+
+ offset = last_pmtmr_tick;
+ now = inl(pmtmr_ioport);
+ delta = (now - offset) & ACPI_PM_MASK;
+
+ return offset_delay + cyc2us(delta);
+}
+
+
+static int __init nopmtimer_setup(char *s)
+{
+ pmtmr_ioport = 0;
+ return 0;
+}
+
+__setup("nopmtimer", nopmtimer_setup);