clocksource: add common i8253 PIT clocksource
[linux-2.6.git] / drivers / clocksource / acpi_pm.c
index 7ad3be8..effe797 100644 (file)
  * This file is licensed under the GPL v2.
  */
 
+#include <linux/acpi_pmtmr.h>
 #include <linux/clocksource.h>
+#include <linux/timex.h>
 #include <linux/errno.h>
 #include <linux/init.h>
 #include <linux/pci.h>
+#include <linux/delay.h>
 #include <asm/io.h>
 
-/* Number of PMTMR ticks expected during calibration run */
-#define PMTMR_TICKS_PER_SEC 3579545
-
 /*
  * The I/O port the PMTMR resides at.
  * The location is detected during setup_arch(),
- * in arch/i386/acpi/boot.c
+ * in arch/i386/kernel/acpi/boot.c
  */
 u32 pmtmr_ioport __read_mostly;
 
-#define ACPI_PM_MASK CLOCKSOURCE_MASK(24) /* limit it to 24 bits */
-
 static inline u32 read_pmtmr(void)
 {
        /* mask the output to 24 bits */
        return inl(pmtmr_ioport) & ACPI_PM_MASK;
 }
 
-static cycle_t acpi_pm_read_verified(void)
+u32 acpi_pm_read_verified(void)
 {
        u32 v1 = 0, v2 = 0, v3 = 0;
 
@@ -54,13 +52,13 @@ static cycle_t acpi_pm_read_verified(void)
                v1 = read_pmtmr();
                v2 = read_pmtmr();
                v3 = read_pmtmr();
-       } while ((v1 > v2 && v1 < v3) || (v2 > v3 && v2 < v1)
-                       || (v3 > v1 && v3 < v2));
+       } while (unlikely((v1 > v2 && v1 < v3) || (v2 > v3 && v2 < v1)
+                         || (v3 > v1 && v3 < v2)));
 
-       return (cycle_t)v2;
+       return v2;
 }
 
-static cycle_t acpi_pm_read(void)
+static cycle_t acpi_pm_read(struct clocksource *cs)
 {
        return (cycle_t)read_pmtmr();
 }
@@ -70,25 +68,28 @@ static struct clocksource clocksource_acpi_pm = {
        .rating         = 200,
        .read           = acpi_pm_read,
        .mask           = (cycle_t)ACPI_PM_MASK,
-       .mult           = 0, /*to be caluclated*/
-       .shift          = 22,
-       .is_continuous  = 1,
+       .flags          = CLOCK_SOURCE_IS_CONTINUOUS,
 };
 
 
 #ifdef CONFIG_PCI
-static int acpi_pm_good;
+static int __devinitdata acpi_pm_good;
 static int __init acpi_pm_good_setup(char *__str)
 {
-       acpi_pm_good = 1;
-       return 1;
+       acpi_pm_good = 1;
+       return 1;
 }
 __setup("acpi_pm_good", acpi_pm_good_setup);
 
+static cycle_t acpi_pm_read_slow(struct clocksource *cs)
+{
+       return (cycle_t)acpi_pm_read_verified();
+}
+
 static inline void acpi_pm_need_workaround(void)
 {
-       clocksource_acpi_pm.read = acpi_pm_read_verified;
-       clocksource_acpi_pm.rating = 110;
+       clocksource_acpi_pm.read = acpi_pm_read_slow;
+       clocksource_acpi_pm.rating = 120;
 }
 
 /*
@@ -103,14 +104,11 @@ static inline void acpi_pm_need_workaround(void)
  */
 static void __devinit acpi_pm_check_blacklist(struct pci_dev *dev)
 {
-       u8 rev;
-
        if (acpi_pm_good)
                return;
 
-       pci_read_config_byte(dev, PCI_REVISION_ID, &rev);
        /* the bug has been fixed in PIIX4M */
-       if (rev < 3) {
+       if (dev->revision < 3) {
                printk(KERN_WARNING "* Found PM-Timer Bug on the chipset."
                       " Due to workarounds for a bug,\n"
                       "* this clock source is slow. Consider trying"
@@ -138,40 +136,115 @@ static void __devinit acpi_pm_check_graylist(struct pci_dev *dev)
 }
 DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801DB_0,
                        acpi_pm_check_graylist);
+DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_SERVERWORKS, PCI_DEVICE_ID_SERVERWORKS_LE,
+                       acpi_pm_check_graylist);
+#endif
+
+#ifndef CONFIG_X86_64
+#include <asm/mach_timer.h>
+#define PMTMR_EXPECTED_RATE \
+  ((CALIBRATE_LATCH * (PMTMR_TICKS_PER_SEC >> 10)) / (CLOCK_TICK_RATE>>10))
+/*
+ * 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)
+{
+       cycle_t value1, value2;
+       unsigned long count, delta;
+
+       mach_prepare_counter();
+       value1 = clocksource_acpi_pm.read(&clocksource_acpi_pm);
+       mach_countup(&count);
+       value2 = clocksource_acpi_pm.read(&clocksource_acpi_pm);
+       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;
+}
+#else
+#define verify_pmtmr_rate() (0)
 #endif
 
+/* Number of monotonicity checks to perform during initialization */
+#define ACPI_PM_MONOTONICITY_CHECKS 10
+/* Number of reads we try to get two different values */
+#define ACPI_PM_READ_CHECKS 10000
 
 static int __init init_acpi_pm_clocksource(void)
 {
-       u32 value1, value2;
-       unsigned int i;
+       cycle_t value1, value2;
+       unsigned int i, j = 0;
 
        if (!pmtmr_ioport)
                return -ENODEV;
 
-       clocksource_acpi_pm.mult = clocksource_hz2mult(PMTMR_TICKS_PER_SEC,
-                                               clocksource_acpi_pm.shift);
-
        /* "verify" this timing source: */
-       value1 = read_pmtmr();
-       for (i = 0; i < 10000; i++) {
-               value2 = read_pmtmr();
-               if (value2 == value1)
-                       continue;
-               if (value2 > value1)
-                       goto pm_good;
-               if ((value2 < value1) && ((value2) < 0xFFF))
-                       goto pm_good;
-               printk(KERN_INFO "PM-Timer had inconsistent results:"
-                       " 0x%#x, 0x%#x - aborting.\n", value1, value2);
-               return -EINVAL;
+       for (j = 0; j < ACPI_PM_MONOTONICITY_CHECKS; j++) {
+               udelay(100 * j);
+               value1 = clocksource_acpi_pm.read(&clocksource_acpi_pm);
+               for (i = 0; i < ACPI_PM_READ_CHECKS; i++) {
+                       value2 = clocksource_acpi_pm.read(&clocksource_acpi_pm);
+                       if (value2 == value1)
+                               continue;
+                       if (value2 > value1)
+                               break;
+                       if ((value2 < value1) && ((value2) < 0xFFF))
+                               break;
+                       printk(KERN_INFO "PM-Timer had inconsistent results:"
+                              " 0x%#llx, 0x%#llx - aborting.\n",
+                              value1, value2);
+                       pmtmr_ioport = 0;
+                       return -EINVAL;
+               }
+               if (i == ACPI_PM_READ_CHECKS) {
+                       printk(KERN_INFO "PM-Timer failed consistency check "
+                              " (0x%#llx) - aborting.\n", value1);
+                       pmtmr_ioport = 0;
+                       return -ENODEV;
+               }
+       }
+
+       if (verify_pmtmr_rate() != 0){
+               pmtmr_ioport = 0;
+               return -ENODEV;
        }
-       printk(KERN_INFO "PM-Timer had no reasonable result:"
-                       " 0x%#x - aborting.\n", value1);
-       return -ENODEV;
 
-pm_good:
-       return clocksource_register(&clocksource_acpi_pm);
+       return clocksource_register_hz(&clocksource_acpi_pm,
+                                               PMTMR_TICKS_PER_SEC);
 }
 
-module_init(init_acpi_pm_clocksource);
+/* We use fs_initcall because we want the PCI fixups to have run
+ * but we still need to load before device_initcall
+ */
+fs_initcall(init_acpi_pm_clocksource);
+
+/*
+ * Allow an override of the IOPort. Stupid BIOSes do not tell us about
+ * the PMTimer, but we might know where it is.
+ */
+static int __init parse_pmtmr(char *arg)
+{
+       unsigned long base;
+
+       if (strict_strtoul(arg, 16, &base))
+               return -EINVAL;
+#ifdef CONFIG_X86_64
+       if (base > UINT_MAX)
+               return -ERANGE;
+#endif
+       printk(KERN_INFO "PMTMR IOPort override: 0x%04x -> 0x%04lx\n",
+              pmtmr_ioport, base);
+       pmtmr_ioport = base;
+
+       return 1;
+}
+__setup("pmtmr=", parse_pmtmr);