1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* intel_pch_thermal.c - Intel PCH Thermal driver |
3 | * |
4 | * Copyright (c) 2015, Intel Corporation. |
5 | * |
6 | * Authors: |
7 | * Tushar Dave <tushar.n.dave@intel.com> |
8 | */ |
9 | |
10 | #include <linux/acpi.h> |
11 | #include <linux/delay.h> |
12 | #include <linux/module.h> |
13 | #include <linux/init.h> |
14 | #include <linux/pci.h> |
15 | #include <linux/pm.h> |
16 | #include <linux/suspend.h> |
17 | #include <linux/thermal.h> |
18 | #include <linux/types.h> |
19 | #include <linux/units.h> |
20 | |
21 | /* Intel PCH thermal Device IDs */ |
22 | #define PCH_THERMAL_DID_HSW_1 0x9C24 /* Haswell PCH */ |
23 | #define PCH_THERMAL_DID_HSW_2 0x8C24 /* Haswell PCH */ |
24 | #define PCH_THERMAL_DID_WPT 0x9CA4 /* Wildcat Point */ |
25 | #define PCH_THERMAL_DID_SKL 0x9D31 /* Skylake PCH */ |
26 | #define PCH_THERMAL_DID_SKL_H 0xA131 /* Skylake PCH 100 series */ |
27 | #define PCH_THERMAL_DID_CNL 0x9Df9 /* CNL PCH */ |
28 | #define PCH_THERMAL_DID_CNL_H 0xA379 /* CNL-H PCH */ |
29 | #define PCH_THERMAL_DID_CNL_LP 0x02F9 /* CNL-LP PCH */ |
30 | #define PCH_THERMAL_DID_CML_H 0X06F9 /* CML-H PCH */ |
31 | #define PCH_THERMAL_DID_LWB 0xA1B1 /* Lewisburg PCH */ |
32 | #define PCH_THERMAL_DID_WBG 0x8D24 /* Wellsburg PCH */ |
33 | |
34 | /* Wildcat Point-LP PCH Thermal registers */ |
35 | #define WPT_TEMP 0x0000 /* Temperature */ |
36 | #define WPT_TSC 0x04 /* Thermal Sensor Control */ |
37 | #define WPT_TSS 0x06 /* Thermal Sensor Status */ |
38 | #define WPT_TSEL 0x08 /* Thermal Sensor Enable and Lock */ |
39 | #define WPT_TSREL 0x0A /* Thermal Sensor Report Enable and Lock */ |
40 | #define WPT_TSMIC 0x0C /* Thermal Sensor SMI Control */ |
41 | #define WPT_CTT 0x0010 /* Catastrophic Trip Point */ |
42 | #define WPT_TSPM 0x001C /* Thermal Sensor Power Management */ |
43 | #define WPT_TAHV 0x0014 /* Thermal Alert High Value */ |
44 | #define WPT_TALV 0x0018 /* Thermal Alert Low Value */ |
45 | #define WPT_TL 0x00000040 /* Throttle Value */ |
46 | #define WPT_PHL 0x0060 /* PCH Hot Level */ |
47 | #define WPT_PHLC 0x62 /* PHL Control */ |
48 | #define WPT_TAS 0x80 /* Thermal Alert Status */ |
49 | #define WPT_TSPIEN 0x82 /* PCI Interrupt Event Enables */ |
50 | #define WPT_TSGPEN 0x84 /* General Purpose Event Enables */ |
51 | |
52 | /* Wildcat Point-LP PCH Thermal Register bit definitions */ |
53 | #define WPT_TEMP_TSR 0x01ff /* Temp TS Reading */ |
54 | #define WPT_TSC_CPDE 0x01 /* Catastrophic Power-Down Enable */ |
55 | #define WPT_TSS_TSDSS 0x10 /* Thermal Sensor Dynamic Shutdown Status */ |
56 | #define WPT_TSS_GPES 0x08 /* GPE status */ |
57 | #define WPT_TSEL_ETS 0x01 /* Enable TS */ |
58 | #define WPT_TSEL_PLDB 0x80 /* TSEL Policy Lock-Down Bit */ |
59 | #define WPT_TL_TOL 0x000001FF /* T0 Level */ |
60 | #define WPT_TL_T1L 0x1ff00000 /* T1 Level */ |
61 | #define WPT_TL_TTEN 0x20000000 /* TT Enable */ |
62 | |
63 | /* Resolution of 1/2 degree C and an offset of -50C */ |
64 | #define PCH_TEMP_OFFSET (-50) |
65 | #define GET_WPT_TEMP(x) ((x) * MILLIDEGREE_PER_DEGREE / 2 + WPT_TEMP_OFFSET) |
66 | #define WPT_TEMP_OFFSET (PCH_TEMP_OFFSET * MILLIDEGREE_PER_DEGREE) |
67 | #define GET_PCH_TEMP(x) (((x) / 2) + PCH_TEMP_OFFSET) |
68 | |
69 | #define PCH_MAX_TRIPS 3 /* critical, hot, passive */ |
70 | |
71 | /* Amount of time for each cooling delay, 100ms by default for now */ |
72 | static unsigned int delay_timeout = 100; |
73 | module_param(delay_timeout, int, 0644); |
74 | MODULE_PARM_DESC(delay_timeout, "amount of time delay for each iteration." ); |
75 | |
76 | /* Number of iterations for cooling delay, 600 counts by default for now */ |
77 | static unsigned int delay_cnt = 600; |
78 | module_param(delay_cnt, int, 0644); |
79 | MODULE_PARM_DESC(delay_cnt, "total number of iterations for time delay." ); |
80 | |
81 | static char driver_name[] = "Intel PCH thermal driver" ; |
82 | |
83 | struct pch_thermal_device { |
84 | void __iomem *hw_base; |
85 | struct pci_dev *pdev; |
86 | struct thermal_zone_device *tzd; |
87 | struct thermal_trip trips[PCH_MAX_TRIPS]; |
88 | bool bios_enabled; |
89 | }; |
90 | |
91 | #ifdef CONFIG_ACPI |
92 | /* |
93 | * On some platforms, there is a companion ACPI device, which adds |
94 | * passive trip temperature using _PSV method. There is no specific |
95 | * passive temperature setting in MMIO interface of this PCI device. |
96 | */ |
97 | static int pch_wpt_add_acpi_psv_trip(struct pch_thermal_device *ptd, int trip) |
98 | { |
99 | struct acpi_device *adev; |
100 | int temp; |
101 | |
102 | adev = ACPI_COMPANION(&ptd->pdev->dev); |
103 | if (!adev) |
104 | return 0; |
105 | |
106 | if (thermal_acpi_passive_trip_temp(adev, ret_temp: &temp) || temp <= 0) |
107 | return 0; |
108 | |
109 | ptd->trips[trip].type = THERMAL_TRIP_PASSIVE; |
110 | ptd->trips[trip].temperature = temp; |
111 | return 1; |
112 | } |
113 | #else |
114 | static int pch_wpt_add_acpi_psv_trip(struct pch_thermal_device *ptd, int trip) |
115 | { |
116 | return 0; |
117 | } |
118 | #endif |
119 | |
120 | static int pch_thermal_get_temp(struct thermal_zone_device *tzd, int *temp) |
121 | { |
122 | struct pch_thermal_device *ptd = thermal_zone_device_priv(tzd); |
123 | |
124 | *temp = GET_WPT_TEMP(WPT_TEMP_TSR & readw(ptd->hw_base + WPT_TEMP)); |
125 | return 0; |
126 | } |
127 | |
128 | static void pch_critical(struct thermal_zone_device *tzd) |
129 | { |
130 | dev_dbg(thermal_zone_device(tzd), "%s: critical temperature reached\n" , |
131 | thermal_zone_device_type(tzd)); |
132 | } |
133 | |
134 | static struct thermal_zone_device_ops tzd_ops = { |
135 | .get_temp = pch_thermal_get_temp, |
136 | .critical = pch_critical, |
137 | }; |
138 | |
139 | enum pch_board_ids { |
140 | PCH_BOARD_HSW = 0, |
141 | PCH_BOARD_WPT, |
142 | PCH_BOARD_SKL, |
143 | PCH_BOARD_CNL, |
144 | PCH_BOARD_CML, |
145 | PCH_BOARD_LWB, |
146 | PCH_BOARD_WBG, |
147 | }; |
148 | |
149 | static const char *board_names[] = { |
150 | [PCH_BOARD_HSW] = "pch_haswell" , |
151 | [PCH_BOARD_WPT] = "pch_wildcat_point" , |
152 | [PCH_BOARD_SKL] = "pch_skylake" , |
153 | [PCH_BOARD_CNL] = "pch_cannonlake" , |
154 | [PCH_BOARD_CML] = "pch_cometlake" , |
155 | [PCH_BOARD_LWB] = "pch_lewisburg" , |
156 | [PCH_BOARD_WBG] = "pch_wellsburg" , |
157 | }; |
158 | |
159 | static int intel_pch_thermal_probe(struct pci_dev *pdev, |
160 | const struct pci_device_id *id) |
161 | { |
162 | enum pch_board_ids board_id = id->driver_data; |
163 | struct pch_thermal_device *ptd; |
164 | int nr_trips = 0; |
165 | u16 trip_temp; |
166 | u8 tsel; |
167 | int err; |
168 | |
169 | ptd = devm_kzalloc(dev: &pdev->dev, size: sizeof(*ptd), GFP_KERNEL); |
170 | if (!ptd) |
171 | return -ENOMEM; |
172 | |
173 | pci_set_drvdata(pdev, data: ptd); |
174 | ptd->pdev = pdev; |
175 | |
176 | err = pci_enable_device(dev: pdev); |
177 | if (err) { |
178 | dev_err(&pdev->dev, "failed to enable pci device\n" ); |
179 | return err; |
180 | } |
181 | |
182 | err = pci_request_regions(pdev, driver_name); |
183 | if (err) { |
184 | dev_err(&pdev->dev, "failed to request pci region\n" ); |
185 | goto error_disable; |
186 | } |
187 | |
188 | ptd->hw_base = pci_ioremap_bar(pdev, bar: 0); |
189 | if (!ptd->hw_base) { |
190 | err = -ENOMEM; |
191 | dev_err(&pdev->dev, "failed to map mem base\n" ); |
192 | goto error_release; |
193 | } |
194 | |
195 | /* Check if BIOS has already enabled thermal sensor */ |
196 | if (WPT_TSEL_ETS & readb(addr: ptd->hw_base + WPT_TSEL)) { |
197 | ptd->bios_enabled = true; |
198 | goto read_trips; |
199 | } |
200 | |
201 | tsel = readb(addr: ptd->hw_base + WPT_TSEL); |
202 | /* |
203 | * When TSEL's Policy Lock-Down bit is 1, TSEL become RO. |
204 | * If so, thermal sensor cannot enable. Bail out. |
205 | */ |
206 | if (tsel & WPT_TSEL_PLDB) { |
207 | dev_err(&ptd->pdev->dev, "Sensor can't be enabled\n" ); |
208 | err = -ENODEV; |
209 | goto error_cleanup; |
210 | } |
211 | |
212 | writeb(val: tsel|WPT_TSEL_ETS, addr: ptd->hw_base + WPT_TSEL); |
213 | if (!(WPT_TSEL_ETS & readb(addr: ptd->hw_base + WPT_TSEL))) { |
214 | dev_err(&ptd->pdev->dev, "Sensor can't be enabled\n" ); |
215 | err = -ENODEV; |
216 | goto error_cleanup; |
217 | } |
218 | |
219 | read_trips: |
220 | trip_temp = readw(addr: ptd->hw_base + WPT_CTT); |
221 | trip_temp &= 0x1FF; |
222 | if (trip_temp) { |
223 | ptd->trips[nr_trips].temperature = GET_WPT_TEMP(trip_temp); |
224 | ptd->trips[nr_trips++].type = THERMAL_TRIP_CRITICAL; |
225 | } |
226 | |
227 | trip_temp = readw(addr: ptd->hw_base + WPT_PHL); |
228 | trip_temp &= 0x1FF; |
229 | if (trip_temp) { |
230 | ptd->trips[nr_trips].temperature = GET_WPT_TEMP(trip_temp); |
231 | ptd->trips[nr_trips++].type = THERMAL_TRIP_HOT; |
232 | } |
233 | |
234 | nr_trips += pch_wpt_add_acpi_psv_trip(ptd, trip: nr_trips); |
235 | |
236 | ptd->tzd = thermal_zone_device_register_with_trips(type: board_names[board_id], |
237 | trips: ptd->trips, num_trips: nr_trips, |
238 | mask: 0, devdata: ptd, ops: &tzd_ops, |
239 | NULL, passive_delay: 0, polling_delay: 0); |
240 | if (IS_ERR(ptr: ptd->tzd)) { |
241 | dev_err(&pdev->dev, "Failed to register thermal zone %s\n" , |
242 | board_names[board_id]); |
243 | err = PTR_ERR(ptr: ptd->tzd); |
244 | goto error_cleanup; |
245 | } |
246 | err = thermal_zone_device_enable(tz: ptd->tzd); |
247 | if (err) |
248 | goto err_unregister; |
249 | |
250 | return 0; |
251 | |
252 | err_unregister: |
253 | thermal_zone_device_unregister(tz: ptd->tzd); |
254 | error_cleanup: |
255 | iounmap(addr: ptd->hw_base); |
256 | error_release: |
257 | pci_release_regions(pdev); |
258 | error_disable: |
259 | pci_disable_device(dev: pdev); |
260 | dev_err(&pdev->dev, "pci device failed to probe\n" ); |
261 | return err; |
262 | } |
263 | |
264 | static void intel_pch_thermal_remove(struct pci_dev *pdev) |
265 | { |
266 | struct pch_thermal_device *ptd = pci_get_drvdata(pdev); |
267 | |
268 | thermal_zone_device_unregister(tz: ptd->tzd); |
269 | iounmap(addr: ptd->hw_base); |
270 | pci_set_drvdata(pdev, NULL); |
271 | pci_release_regions(pdev); |
272 | pci_disable_device(dev: pdev); |
273 | } |
274 | |
275 | static int intel_pch_thermal_suspend_noirq(struct device *device) |
276 | { |
277 | struct pch_thermal_device *ptd = dev_get_drvdata(dev: device); |
278 | u16 pch_thr_temp, pch_cur_temp; |
279 | int pch_delay_cnt = 0; |
280 | u8 tsel; |
281 | |
282 | /* Shutdown the thermal sensor if it is not enabled by BIOS */ |
283 | if (!ptd->bios_enabled) { |
284 | tsel = readb(addr: ptd->hw_base + WPT_TSEL); |
285 | writeb(val: tsel & 0xFE, addr: ptd->hw_base + WPT_TSEL); |
286 | return 0; |
287 | } |
288 | |
289 | /* Do not check temperature if it is not s2idle */ |
290 | if (pm_suspend_via_firmware()) |
291 | return 0; |
292 | |
293 | /* Get the PCH temperature threshold value */ |
294 | pch_thr_temp = GET_PCH_TEMP(WPT_TEMP_TSR & readw(ptd->hw_base + WPT_TSPM)); |
295 | |
296 | /* Get the PCH current temperature value */ |
297 | pch_cur_temp = GET_PCH_TEMP(WPT_TEMP_TSR & readw(ptd->hw_base + WPT_TEMP)); |
298 | |
299 | /* |
300 | * If current PCH temperature is higher than configured PCH threshold |
301 | * value, run some delay loop with sleep to let the current temperature |
302 | * go down below the threshold value which helps to allow system enter |
303 | * lower power S0ix suspend state. Even after delay loop if PCH current |
304 | * temperature stays above threshold, notify the warning message |
305 | * which helps to indentify the reason why S0ix entry was rejected. |
306 | */ |
307 | while (pch_delay_cnt < delay_cnt) { |
308 | if (pch_cur_temp < pch_thr_temp) |
309 | break; |
310 | |
311 | if (pm_wakeup_pending()) { |
312 | dev_warn(&ptd->pdev->dev, "Wakeup event detected, abort cooling\n" ); |
313 | return 0; |
314 | } |
315 | |
316 | pch_delay_cnt++; |
317 | dev_dbg(&ptd->pdev->dev, |
318 | "CPU-PCH current temp [%dC] higher than the threshold temp [%dC], sleep %d times for %d ms duration\n" , |
319 | pch_cur_temp, pch_thr_temp, pch_delay_cnt, delay_timeout); |
320 | msleep(msecs: delay_timeout); |
321 | /* Read the PCH current temperature for next cycle. */ |
322 | pch_cur_temp = GET_PCH_TEMP(WPT_TEMP_TSR & readw(ptd->hw_base + WPT_TEMP)); |
323 | } |
324 | |
325 | if (pch_cur_temp >= pch_thr_temp) |
326 | dev_warn(&ptd->pdev->dev, |
327 | "CPU-PCH is hot [%dC] after %d ms delay. S0ix might fail\n" , |
328 | pch_cur_temp, pch_delay_cnt * delay_timeout); |
329 | else { |
330 | if (pch_delay_cnt) |
331 | dev_info(&ptd->pdev->dev, |
332 | "CPU-PCH is cool [%dC] after %d ms delay\n" , |
333 | pch_cur_temp, pch_delay_cnt * delay_timeout); |
334 | else |
335 | dev_info(&ptd->pdev->dev, |
336 | "CPU-PCH is cool [%dC]\n" , |
337 | pch_cur_temp); |
338 | } |
339 | |
340 | return 0; |
341 | } |
342 | |
343 | static int intel_pch_thermal_resume(struct device *device) |
344 | { |
345 | struct pch_thermal_device *ptd = dev_get_drvdata(dev: device); |
346 | u8 tsel; |
347 | |
348 | if (ptd->bios_enabled) |
349 | return 0; |
350 | |
351 | tsel = readb(addr: ptd->hw_base + WPT_TSEL); |
352 | |
353 | writeb(val: tsel | WPT_TSEL_ETS, addr: ptd->hw_base + WPT_TSEL); |
354 | |
355 | return 0; |
356 | } |
357 | |
358 | static const struct pci_device_id intel_pch_thermal_id[] = { |
359 | { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_HSW_1), |
360 | .driver_data = PCH_BOARD_HSW, }, |
361 | { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_HSW_2), |
362 | .driver_data = PCH_BOARD_HSW, }, |
363 | { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_WPT), |
364 | .driver_data = PCH_BOARD_WPT, }, |
365 | { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_SKL), |
366 | .driver_data = PCH_BOARD_SKL, }, |
367 | { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_SKL_H), |
368 | .driver_data = PCH_BOARD_SKL, }, |
369 | { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_CNL), |
370 | .driver_data = PCH_BOARD_CNL, }, |
371 | { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_CNL_H), |
372 | .driver_data = PCH_BOARD_CNL, }, |
373 | { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_CNL_LP), |
374 | .driver_data = PCH_BOARD_CNL, }, |
375 | { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_CML_H), |
376 | .driver_data = PCH_BOARD_CML, }, |
377 | { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_LWB), |
378 | .driver_data = PCH_BOARD_LWB, }, |
379 | { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_WBG), |
380 | .driver_data = PCH_BOARD_WBG, }, |
381 | { 0, }, |
382 | }; |
383 | MODULE_DEVICE_TABLE(pci, intel_pch_thermal_id); |
384 | |
385 | static const struct dev_pm_ops intel_pch_pm_ops = { |
386 | .suspend_noirq = intel_pch_thermal_suspend_noirq, |
387 | .resume = intel_pch_thermal_resume, |
388 | }; |
389 | |
390 | static struct pci_driver intel_pch_thermal_driver = { |
391 | .name = "intel_pch_thermal" , |
392 | .id_table = intel_pch_thermal_id, |
393 | .probe = intel_pch_thermal_probe, |
394 | .remove = intel_pch_thermal_remove, |
395 | .driver.pm = &intel_pch_pm_ops, |
396 | }; |
397 | |
398 | module_pci_driver(intel_pch_thermal_driver); |
399 | |
400 | MODULE_LICENSE("GPL v2" ); |
401 | MODULE_DESCRIPTION("Intel PCH Thermal driver" ); |
402 | |