Branch data Line data Source code
1 : : // SPDX-License-Identifier: GPL-2.0-only
2 : : /*
3 : : * linux/drivers/clocksource/acpi_pm.c
4 : : *
5 : : * This file contains the ACPI PM based clocksource.
6 : : *
7 : : * This code was largely moved from the i386 timer_pm.c file
8 : : * which was (C) Dominik Brodowski <linux@brodo.de> 2003
9 : : * and contained the following comments:
10 : : *
11 : : * Driver to use the Power Management Timer (PMTMR) available in some
12 : : * southbridges as primary timing source for the Linux kernel.
13 : : *
14 : : * Based on parts of linux/drivers/acpi/hardware/hwtimer.c, timer_pit.c,
15 : : * timer_hpet.c, and on Arjan van de Ven's implementation for 2.4.
16 : : */
17 : :
18 : : #include <linux/acpi_pmtmr.h>
19 : : #include <linux/clocksource.h>
20 : : #include <linux/timex.h>
21 : : #include <linux/errno.h>
22 : : #include <linux/init.h>
23 : : #include <linux/pci.h>
24 : : #include <linux/delay.h>
25 : : #include <asm/io.h>
26 : :
27 : : /*
28 : : * The I/O port the PMTMR resides at.
29 : : * The location is detected during setup_arch(),
30 : : * in arch/i386/kernel/acpi/boot.c
31 : : */
32 : : u32 pmtmr_ioport __read_mostly;
33 : :
34 : 978 : static inline u32 read_pmtmr(void)
35 : : {
36 : : /* mask the output to 24 bits */
37 : 1344 : return inl(pmtmr_ioport) & ACPI_PM_MASK;
38 : : }
39 : :
40 : 306 : u32 acpi_pm_read_verified(void)
41 : : {
42 : 306 : u32 v1 = 0, v2 = 0, v3 = 0;
43 : :
44 : : /*
45 : : * It has been reported that because of various broken
46 : : * chipsets (ICH4, PIIX4 and PIIX4E) where the ACPI PM clock
47 : : * source is not latched, you must read it multiple
48 : : * times to ensure a safe value is read:
49 : : */
50 : 306 : do {
51 : 306 : v1 = read_pmtmr();
52 : 306 : v2 = read_pmtmr();
53 : 306 : v3 = read_pmtmr();
54 [ - + - + : 306 : } while (unlikely((v1 > v2 && v1 < v3) || (v2 > v3 && v2 < v1)
- + ]
55 : : || (v3 > v1 && v3 < v2)));
56 : :
57 : 306 : return v2;
58 : : }
59 : :
60 : 60 : static u64 acpi_pm_read(struct clocksource *cs)
61 : : {
62 : 60 : return (u64)read_pmtmr();
63 : : }
64 : :
65 : : static struct clocksource clocksource_acpi_pm = {
66 : : .name = "acpi_pm",
67 : : .rating = 200,
68 : : .read = acpi_pm_read,
69 : : .mask = (u64)ACPI_PM_MASK,
70 : : .flags = CLOCK_SOURCE_IS_CONTINUOUS,
71 : : };
72 : :
73 : :
74 : : #ifdef CONFIG_PCI
75 : : static int acpi_pm_good;
76 : 0 : static int __init acpi_pm_good_setup(char *__str)
77 : : {
78 : 0 : acpi_pm_good = 1;
79 : 0 : return 1;
80 : : }
81 : : __setup("acpi_pm_good", acpi_pm_good_setup);
82 : :
83 : 0 : static u64 acpi_pm_read_slow(struct clocksource *cs)
84 : : {
85 : 0 : return (u64)acpi_pm_read_verified();
86 : : }
87 : :
88 : 0 : static inline void acpi_pm_need_workaround(void)
89 : : {
90 : 0 : clocksource_acpi_pm.read = acpi_pm_read_slow;
91 : 0 : clocksource_acpi_pm.rating = 120;
92 : 0 : }
93 : :
94 : : /*
95 : : * PIIX4 Errata:
96 : : *
97 : : * The power management timer may return improper results when read.
98 : : * Although the timer value settles properly after incrementing,
99 : : * while incrementing there is a 3 ns window every 69.8 ns where the
100 : : * timer value is indeterminate (a 4.2% chance that the data will be
101 : : * incorrect when read). As a result, the ACPI free running count up
102 : : * timer specification is violated due to erroneous reads.
103 : : */
104 : 3 : static void acpi_pm_check_blacklist(struct pci_dev *dev)
105 : : {
106 [ + - ]: 3 : if (acpi_pm_good)
107 : : return;
108 : :
109 : : /* the bug has been fixed in PIIX4M */
110 [ - + ]: 3 : if (dev->revision < 3) {
111 : 0 : pr_warn("* Found PM-Timer Bug on the chipset. Due to workarounds for a bug,\n"
112 : : "* this clock source is slow. Consider trying other clock sources\n");
113 : :
114 : 0 : acpi_pm_need_workaround();
115 : : }
116 : : }
117 : : DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82371AB_3,
118 : : acpi_pm_check_blacklist);
119 : :
120 : 0 : static void acpi_pm_check_graylist(struct pci_dev *dev)
121 : : {
122 [ # # ]: 0 : if (acpi_pm_good)
123 : : return;
124 : :
125 : 0 : pr_warn("* The chipset may have PM-Timer Bug. Due to workarounds for a bug,\n"
126 : : "* this clock source is slow. If you are sure your timer does not have\n"
127 : : "* this bug, please use \"acpi_pm_good\" to disable the workaround\n");
128 : :
129 : 0 : acpi_pm_need_workaround();
130 : : }
131 : : DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801DB_0,
132 : : acpi_pm_check_graylist);
133 : : DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_SERVERWORKS, PCI_DEVICE_ID_SERVERWORKS_LE,
134 : : acpi_pm_check_graylist);
135 : : #endif
136 : :
137 : : #ifndef CONFIG_X86_64
138 : : #include <asm/mach_timer.h>
139 : : #define PMTMR_EXPECTED_RATE \
140 : : ((CALIBRATE_LATCH * (PMTMR_TICKS_PER_SEC >> 10)) / (PIT_TICK_RATE>>10))
141 : : /*
142 : : * Some boards have the PMTMR running way too fast. We check
143 : : * the PMTMR rate against PIT channel 2 to catch these cases.
144 : : */
145 : : static int verify_pmtmr_rate(void)
146 : : {
147 : : u64 value1, value2;
148 : : unsigned long count, delta;
149 : :
150 : : mach_prepare_counter();
151 : : value1 = clocksource_acpi_pm.read(&clocksource_acpi_pm);
152 : : mach_countup(&count);
153 : : value2 = clocksource_acpi_pm.read(&clocksource_acpi_pm);
154 : : delta = (value2 - value1) & ACPI_PM_MASK;
155 : :
156 : : /* Check that the PMTMR delta is within 5% of what we expect */
157 : : if (delta < (PMTMR_EXPECTED_RATE * 19) / 20 ||
158 : : delta > (PMTMR_EXPECTED_RATE * 21) / 20) {
159 : : pr_info("PM-Timer running at invalid rate: %lu%% of normal - aborting.\n",
160 : : 100UL * delta / PMTMR_EXPECTED_RATE);
161 : : return -1;
162 : : }
163 : :
164 : : return 0;
165 : : }
166 : : #else
167 : : #define verify_pmtmr_rate() (0)
168 : : #endif
169 : :
170 : : /* Number of monotonicity checks to perform during initialization */
171 : : #define ACPI_PM_MONOTONICITY_CHECKS 10
172 : : /* Number of reads we try to get two different values */
173 : : #define ACPI_PM_READ_CHECKS 10000
174 : :
175 : 3 : static int __init init_acpi_pm_clocksource(void)
176 : : {
177 : 3 : u64 value1, value2;
178 : 3 : unsigned int i, j = 0;
179 : :
180 [ + - ]: 3 : if (!pmtmr_ioport)
181 : : return -ENODEV;
182 : :
183 : : /* "verify" this timing source: */
184 [ + + ]: 33 : for (j = 0; j < ACPI_PM_MONOTONICITY_CHECKS; j++) {
185 [ - + ]: 30 : udelay(100 * j);
186 : 30 : value1 = clocksource_acpi_pm.read(&clocksource_acpi_pm);
187 [ + - ]: 60 : for (i = 0; i < ACPI_PM_READ_CHECKS; i++) {
188 : 30 : value2 = clocksource_acpi_pm.read(&clocksource_acpi_pm);
189 [ - + ]: 30 : if (value2 == value1)
190 : 0 : continue;
191 [ - + ]: 30 : if (value2 > value1)
192 : : break;
193 [ # # ]: 0 : if ((value2 < value1) && ((value2) < 0xFFF))
194 : : break;
195 : 0 : pr_info("PM-Timer had inconsistent results: %#llx, %#llx - aborting.\n",
196 : : value1, value2);
197 : 0 : pmtmr_ioport = 0;
198 : 0 : return -EINVAL;
199 : : }
200 [ - + ]: 30 : if (i == ACPI_PM_READ_CHECKS) {
201 : 0 : pr_info("PM-Timer failed consistency check (%#llx) - aborting.\n",
202 : : value1);
203 : 0 : pmtmr_ioport = 0;
204 : 0 : return -ENODEV;
205 : : }
206 : : }
207 : :
208 : 3 : if (verify_pmtmr_rate() != 0){
209 : : pmtmr_ioport = 0;
210 : : return -ENODEV;
211 : : }
212 : :
213 : 3 : return clocksource_register_hz(&clocksource_acpi_pm,
214 : : PMTMR_TICKS_PER_SEC);
215 : : }
216 : :
217 : : /* We use fs_initcall because we want the PCI fixups to have run
218 : : * but we still need to load before device_initcall
219 : : */
220 : : fs_initcall(init_acpi_pm_clocksource);
221 : :
222 : : /*
223 : : * Allow an override of the IOPort. Stupid BIOSes do not tell us about
224 : : * the PMTimer, but we might know where it is.
225 : : */
226 : 0 : static int __init parse_pmtmr(char *arg)
227 : : {
228 : 0 : unsigned int base;
229 : 0 : int ret;
230 : :
231 : 0 : ret = kstrtouint(arg, 16, &base);
232 [ # # ]: 0 : if (ret)
233 : : return ret;
234 : :
235 : 0 : pr_info("PMTMR IOPort override: 0x%04x -> 0x%04x\n", pmtmr_ioport,
236 : : base);
237 : 0 : pmtmr_ioport = base;
238 : :
239 : 0 : return 1;
240 : : }
241 : : __setup("pmtmr=", parse_pmtmr);
|