aboutsummaryrefslogtreecommitdiffstats
path: root/arch/i386/kernel/timers/timer_pm.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/i386/kernel/timers/timer_pm.c')
-rw-r--r--arch/i386/kernel/timers/timer_pm.c38
1 files changed, 38 insertions, 0 deletions
diff --git a/arch/i386/kernel/timers/timer_pm.c b/arch/i386/kernel/timers/timer_pm.c
index 7aa63c9b8e18f..737a0219d1937 100644
--- a/arch/i386/kernel/timers/timer_pm.c
+++ b/arch/i386/kernel/timers/timer_pm.c
@@ -21,6 +21,14 @@
#include <asm/io.h>
#include <asm/arch_hooks.h>
+#include <linux/timex.h>
+#include "mach_timer.h"
+
+/* Number of PMTMR ticks expected during calibration run */
+#define PMTMR_TICKS_PER_SEC 3579545
+#define PMTMR_EXPECTED_RATE \
+ ((CALIBRATE_LATCH * (PMTMR_TICKS_PER_SEC >> 10)) / (CLOCK_TICK_RATE>>10))
+
/* The I/O port the PMTMR resides at.
* The location is detected during setup_arch(),
@@ -57,6 +65,33 @@ static inline u32 read_pmtmr(void)
return v2 & ACPI_PM_MASK;
}
+
+/*
+ * Some boards have the PMTMR running way too fast. We check
+ * the PMTMR rate against PIT channel 2 to catch these cases.
+ */
+static int verify_pmtmr_rate(void)
+{
+ u32 value1, value2;
+ unsigned long count, delta;
+
+ mach_prepare_counter();
+ value1 = read_pmtmr();
+ mach_countup(&count);
+ value2 = read_pmtmr();
+ delta = (value2 - value1) & ACPI_PM_MASK;
+
+ /* Check that the PMTMR delta is within 5% of what we expect */
+ if (delta < (PMTMR_EXPECTED_RATE * 19) / 20 ||
+ delta > (PMTMR_EXPECTED_RATE * 21) / 20) {
+ printk(KERN_INFO "PM-Timer running at invalid rate: %lu%% of normal - aborting.\n", 100UL * delta / PMTMR_EXPECTED_RATE);
+ return -1;
+ }
+
+ return 0;
+}
+
+
static int init_pmtmr(char* override)
{
u32 value1, value2;
@@ -89,6 +124,9 @@ static int init_pmtmr(char* override)
return -ENODEV;
pm_good:
+ if (verify_pmtmr_rate() != 0)
+ return -ENODEV;
+
init_cpu_khz();
return 0;
}