| john stultz | 5d0cf41 | 2006-06-26 00:25:12 -0700 | [diff] [blame] | 1 | /* | 
 | 2 |  * linux/drivers/clocksource/acpi_pm.c | 
 | 3 |  * | 
 | 4 |  * This file contains the ACPI PM based clocksource. | 
 | 5 |  * | 
 | 6 |  * This code was largely moved from the i386 timer_pm.c file | 
 | 7 |  * which was (C) Dominik Brodowski <linux@brodo.de> 2003 | 
 | 8 |  * and contained the following comments: | 
 | 9 |  * | 
 | 10 |  * Driver to use the Power Management Timer (PMTMR) available in some | 
 | 11 |  * southbridges as primary timing source for the Linux kernel. | 
 | 12 |  * | 
 | 13 |  * Based on parts of linux/drivers/acpi/hardware/hwtimer.c, timer_pit.c, | 
 | 14 |  * timer_hpet.c, and on Arjan van de Ven's implementation for 2.4. | 
 | 15 |  * | 
 | 16 |  * This file is licensed under the GPL v2. | 
 | 17 |  */ | 
 | 18 |  | 
| Thomas Gleixner | d66bea5 | 2007-02-16 01:27:57 -0800 | [diff] [blame] | 19 | #include <linux/acpi_pmtmr.h> | 
| john stultz | 5d0cf41 | 2006-06-26 00:25:12 -0700 | [diff] [blame] | 20 | #include <linux/clocksource.h> | 
 | 21 | #include <linux/errno.h> | 
 | 22 | #include <linux/init.h> | 
 | 23 | #include <linux/pci.h> | 
 | 24 | #include <asm/io.h> | 
 | 25 |  | 
| john stultz | 5d0cf41 | 2006-06-26 00:25:12 -0700 | [diff] [blame] | 26 | /* | 
 | 27 |  * The I/O port the PMTMR resides at. | 
 | 28 |  * The location is detected during setup_arch(), | 
| Daniel Walker | 8ce8e2f | 2007-04-25 14:27:06 -0400 | [diff] [blame] | 29 |  * in arch/i386/kernel/acpi/boot.c | 
| john stultz | 5d0cf41 | 2006-06-26 00:25:12 -0700 | [diff] [blame] | 30 |  */ | 
| Andreas Mohr | 7d622d4 | 2006-06-26 00:25:14 -0700 | [diff] [blame] | 31 | u32 pmtmr_ioport __read_mostly; | 
| john stultz | 5d0cf41 | 2006-06-26 00:25:12 -0700 | [diff] [blame] | 32 |  | 
| john stultz | 5d0cf41 | 2006-06-26 00:25:12 -0700 | [diff] [blame] | 33 | static inline u32 read_pmtmr(void) | 
 | 34 | { | 
 | 35 | 	/* mask the output to 24 bits */ | 
 | 36 | 	return inl(pmtmr_ioport) & ACPI_PM_MASK; | 
 | 37 | } | 
 | 38 |  | 
| Thomas Gleixner | d66bea5 | 2007-02-16 01:27:57 -0800 | [diff] [blame] | 39 | u32 acpi_pm_read_verified(void) | 
| john stultz | 5d0cf41 | 2006-06-26 00:25:12 -0700 | [diff] [blame] | 40 | { | 
 | 41 | 	u32 v1 = 0, v2 = 0, v3 = 0; | 
 | 42 |  | 
 | 43 | 	/* | 
 | 44 | 	 * It has been reported that because of various broken | 
 | 45 | 	 * chipsets (ICH4, PIIX4 and PIIX4E) where the ACPI PM clock | 
| Andreas Mohr | 7d622d4 | 2006-06-26 00:25:14 -0700 | [diff] [blame] | 46 | 	 * source is not latched, you must read it multiple | 
| john stultz | 5d0cf41 | 2006-06-26 00:25:12 -0700 | [diff] [blame] | 47 | 	 * times to ensure a safe value is read: | 
 | 48 | 	 */ | 
 | 49 | 	do { | 
 | 50 | 		v1 = read_pmtmr(); | 
 | 51 | 		v2 = read_pmtmr(); | 
 | 52 | 		v3 = read_pmtmr(); | 
| Daniel Walker | 78f3266 | 2006-10-21 10:24:10 -0700 | [diff] [blame] | 53 | 	} while (unlikely((v1 > v2 && v1 < v3) || (v2 > v3 && v2 < v1) | 
 | 54 | 			  || (v3 > v1 && v3 < v2))); | 
| john stultz | 5d0cf41 | 2006-06-26 00:25:12 -0700 | [diff] [blame] | 55 |  | 
| Thomas Gleixner | d66bea5 | 2007-02-16 01:27:57 -0800 | [diff] [blame] | 56 | 	return v2; | 
 | 57 | } | 
 | 58 |  | 
 | 59 | static cycle_t acpi_pm_read_slow(void) | 
 | 60 | { | 
 | 61 | 	return (cycle_t)acpi_pm_read_verified(); | 
| john stultz | 5d0cf41 | 2006-06-26 00:25:12 -0700 | [diff] [blame] | 62 | } | 
 | 63 |  | 
 | 64 | static cycle_t acpi_pm_read(void) | 
 | 65 | { | 
 | 66 | 	return (cycle_t)read_pmtmr(); | 
 | 67 | } | 
 | 68 |  | 
 | 69 | static struct clocksource clocksource_acpi_pm = { | 
 | 70 | 	.name		= "acpi_pm", | 
 | 71 | 	.rating		= 200, | 
 | 72 | 	.read		= acpi_pm_read, | 
 | 73 | 	.mask		= (cycle_t)ACPI_PM_MASK, | 
| Alessio Igor Bogani | 7b0b820 | 2007-07-21 17:11:19 +0200 | [diff] [blame] | 74 | 	.mult		= 0, /*to be calculated*/ | 
| john stultz | 5d0cf41 | 2006-06-26 00:25:12 -0700 | [diff] [blame] | 75 | 	.shift		= 22, | 
| Thomas Gleixner | 73b08d2 | 2007-02-16 01:27:36 -0800 | [diff] [blame] | 76 | 	.flags		= CLOCK_SOURCE_IS_CONTINUOUS, | 
 | 77 |  | 
| john stultz | 5d0cf41 | 2006-06-26 00:25:12 -0700 | [diff] [blame] | 78 | }; | 
 | 79 |  | 
 | 80 |  | 
 | 81 | #ifdef CONFIG_PCI | 
| Daniel Walker | f5f1a24 | 2006-12-10 02:21:33 -0800 | [diff] [blame] | 82 | static int __devinitdata acpi_pm_good; | 
| john stultz | 5d0cf41 | 2006-06-26 00:25:12 -0700 | [diff] [blame] | 83 | static int __init acpi_pm_good_setup(char *__str) | 
 | 84 | { | 
| Daniel Walker | f5f1a24 | 2006-12-10 02:21:33 -0800 | [diff] [blame] | 85 | 	acpi_pm_good = 1; | 
 | 86 | 	return 1; | 
| john stultz | 5d0cf41 | 2006-06-26 00:25:12 -0700 | [diff] [blame] | 87 | } | 
 | 88 | __setup("acpi_pm_good", acpi_pm_good_setup); | 
 | 89 |  | 
 | 90 | static inline void acpi_pm_need_workaround(void) | 
 | 91 | { | 
| Thomas Gleixner | d66bea5 | 2007-02-16 01:27:57 -0800 | [diff] [blame] | 92 | 	clocksource_acpi_pm.read = acpi_pm_read_slow; | 
| john stultz | 1ff100d | 2007-03-26 21:32:19 -0800 | [diff] [blame] | 93 | 	clocksource_acpi_pm.rating = 120; | 
| john stultz | 5d0cf41 | 2006-06-26 00:25:12 -0700 | [diff] [blame] | 94 | } | 
 | 95 |  | 
 | 96 | /* | 
 | 97 |  * PIIX4 Errata: | 
 | 98 |  * | 
 | 99 |  * The power management timer may return improper results when read. | 
 | 100 |  * Although the timer value settles properly after incrementing, | 
 | 101 |  * while incrementing there is a 3 ns window every 69.8 ns where the | 
 | 102 |  * timer value is indeterminate (a 4.2% chance that the data will be | 
 | 103 |  * incorrect when read). As a result, the ACPI free running count up | 
 | 104 |  * timer specification is violated due to erroneous reads. | 
 | 105 |  */ | 
 | 106 | static void __devinit acpi_pm_check_blacklist(struct pci_dev *dev) | 
 | 107 | { | 
| john stultz | 5d0cf41 | 2006-06-26 00:25:12 -0700 | [diff] [blame] | 108 | 	if (acpi_pm_good) | 
 | 109 | 		return; | 
 | 110 |  | 
| john stultz | 5d0cf41 | 2006-06-26 00:25:12 -0700 | [diff] [blame] | 111 | 	/* the bug has been fixed in PIIX4M */ | 
| Auke Kok | 44c1013 | 2007-06-08 15:46:36 -0700 | [diff] [blame] | 112 | 	if (dev->revision < 3) { | 
| john stultz | 5d0cf41 | 2006-06-26 00:25:12 -0700 | [diff] [blame] | 113 | 		printk(KERN_WARNING "* Found PM-Timer Bug on the chipset." | 
 | 114 | 		       " Due to workarounds for a bug,\n" | 
 | 115 | 		       "* this clock source is slow. Consider trying" | 
 | 116 | 		       " other clock sources\n"); | 
 | 117 |  | 
 | 118 | 		acpi_pm_need_workaround(); | 
 | 119 | 	} | 
 | 120 | } | 
 | 121 | DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82371AB_3, | 
 | 122 | 			acpi_pm_check_blacklist); | 
 | 123 |  | 
 | 124 | static void __devinit acpi_pm_check_graylist(struct pci_dev *dev) | 
 | 125 | { | 
 | 126 | 	if (acpi_pm_good) | 
 | 127 | 		return; | 
 | 128 |  | 
 | 129 | 	printk(KERN_WARNING "* The chipset may have PM-Timer Bug. Due to" | 
 | 130 | 	       " workarounds for a bug,\n" | 
 | 131 | 	       "* this clock source is slow. If you are sure your timer" | 
 | 132 | 	       " does not have\n" | 
 | 133 | 	       "* this bug, please use \"acpi_pm_good\" to disable the" | 
 | 134 | 	       " workaround\n"); | 
 | 135 |  | 
 | 136 | 	acpi_pm_need_workaround(); | 
 | 137 | } | 
 | 138 | DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801DB_0, | 
 | 139 | 			acpi_pm_check_graylist); | 
| Daniel Walker | 78f3266 | 2006-10-21 10:24:10 -0700 | [diff] [blame] | 140 | DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_SERVERWORKS, PCI_DEVICE_ID_SERVERWORKS_LE, | 
 | 141 | 			acpi_pm_check_graylist); | 
| john stultz | 5d0cf41 | 2006-06-26 00:25:12 -0700 | [diff] [blame] | 142 | #endif | 
 | 143 |  | 
| john stultz | 562f9c5 | 2006-12-08 02:36:02 -0800 | [diff] [blame] | 144 | #ifndef CONFIG_X86_64 | 
 | 145 | #include "mach_timer.h" | 
 | 146 | #define PMTMR_EXPECTED_RATE \ | 
 | 147 |   ((CALIBRATE_LATCH * (PMTMR_TICKS_PER_SEC >> 10)) / (CLOCK_TICK_RATE>>10)) | 
 | 148 | /* | 
 | 149 |  * Some boards have the PMTMR running way too fast. We check | 
 | 150 |  * the PMTMR rate against PIT channel 2 to catch these cases. | 
 | 151 |  */ | 
 | 152 | static int verify_pmtmr_rate(void) | 
 | 153 | { | 
 | 154 | 	u32 value1, value2; | 
 | 155 | 	unsigned long count, delta; | 
 | 156 |  | 
 | 157 | 	mach_prepare_counter(); | 
 | 158 | 	value1 = read_pmtmr(); | 
 | 159 | 	mach_countup(&count); | 
 | 160 | 	value2 = read_pmtmr(); | 
 | 161 | 	delta = (value2 - value1) & ACPI_PM_MASK; | 
 | 162 |  | 
 | 163 | 	/* Check that the PMTMR delta is within 5% of what we expect */ | 
 | 164 | 	if (delta < (PMTMR_EXPECTED_RATE * 19) / 20 || | 
 | 165 | 	    delta > (PMTMR_EXPECTED_RATE * 21) / 20) { | 
 | 166 | 		printk(KERN_INFO "PM-Timer running at invalid rate: %lu%% " | 
 | 167 | 			"of normal - aborting.\n", | 
 | 168 | 			100UL * delta / PMTMR_EXPECTED_RATE); | 
 | 169 | 		return -1; | 
 | 170 | 	} | 
 | 171 |  | 
 | 172 | 	return 0; | 
 | 173 | } | 
 | 174 | #else | 
 | 175 | #define verify_pmtmr_rate() (0) | 
 | 176 | #endif | 
| john stultz | 5d0cf41 | 2006-06-26 00:25:12 -0700 | [diff] [blame] | 177 |  | 
 | 178 | static int __init init_acpi_pm_clocksource(void) | 
 | 179 | { | 
 | 180 | 	u32 value1, value2; | 
 | 181 | 	unsigned int i; | 
 | 182 |  | 
 | 183 | 	if (!pmtmr_ioport) | 
 | 184 | 		return -ENODEV; | 
 | 185 |  | 
 | 186 | 	clocksource_acpi_pm.mult = clocksource_hz2mult(PMTMR_TICKS_PER_SEC, | 
 | 187 | 						clocksource_acpi_pm.shift); | 
 | 188 |  | 
 | 189 | 	/* "verify" this timing source: */ | 
 | 190 | 	value1 = read_pmtmr(); | 
 | 191 | 	for (i = 0; i < 10000; i++) { | 
 | 192 | 		value2 = read_pmtmr(); | 
 | 193 | 		if (value2 == value1) | 
 | 194 | 			continue; | 
 | 195 | 		if (value2 > value1) | 
 | 196 | 			goto pm_good; | 
 | 197 | 		if ((value2 < value1) && ((value2) < 0xFFF)) | 
 | 198 | 			goto pm_good; | 
 | 199 | 		printk(KERN_INFO "PM-Timer had inconsistent results:" | 
 | 200 | 			" 0x%#x, 0x%#x - aborting.\n", value1, value2); | 
 | 201 | 		return -EINVAL; | 
 | 202 | 	} | 
 | 203 | 	printk(KERN_INFO "PM-Timer had no reasonable result:" | 
 | 204 | 			" 0x%#x - aborting.\n", value1); | 
 | 205 | 	return -ENODEV; | 
 | 206 |  | 
 | 207 | pm_good: | 
| john stultz | 562f9c5 | 2006-12-08 02:36:02 -0800 | [diff] [blame] | 208 | 	if (verify_pmtmr_rate() != 0) | 
 | 209 | 		return -ENODEV; | 
 | 210 |  | 
| john stultz | a275254 | 2006-06-26 00:25:14 -0700 | [diff] [blame] | 211 | 	return clocksource_register(&clocksource_acpi_pm); | 
| john stultz | 5d0cf41 | 2006-06-26 00:25:12 -0700 | [diff] [blame] | 212 | } | 
 | 213 |  | 
| john stultz | 6bb74df | 2007-03-05 00:30:50 -0800 | [diff] [blame] | 214 | /* We use fs_initcall because we want the PCI fixups to have run | 
 | 215 |  * but we still need to load before device_initcall | 
 | 216 |  */ | 
 | 217 | fs_initcall(init_acpi_pm_clocksource); |