1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * acpi_thermal.c - ACPI Thermal Zone Driver ($Revision: 41 $) |
4 | * |
5 | * Copyright (C) 2001, 2002 Andy Grover <andrew.grover@intel.com> |
6 | * Copyright (C) 2001, 2002 Paul Diefenbaugh <paul.s.diefenbaugh@intel.com> |
7 | * |
8 | * This driver fully implements the ACPI thermal policy as described in the |
9 | * ACPI 2.0 Specification. |
10 | * |
11 | * TBD: 1. Implement passive cooling hysteresis. |
12 | * 2. Enhance passive cooling (CPU) states/limit interface to support |
13 | * concepts of 'multiple limiters', upper/lower limits, etc. |
14 | */ |
15 | |
16 | #define pr_fmt(fmt) "ACPI: thermal: " fmt |
17 | |
18 | #include <linux/kernel.h> |
19 | #include <linux/module.h> |
20 | #include <linux/dmi.h> |
21 | #include <linux/init.h> |
22 | #include <linux/slab.h> |
23 | #include <linux/types.h> |
24 | #include <linux/jiffies.h> |
25 | #include <linux/kmod.h> |
26 | #include <linux/reboot.h> |
27 | #include <linux/device.h> |
28 | #include <linux/thermal.h> |
29 | #include <linux/acpi.h> |
30 | #include <linux/workqueue.h> |
31 | #include <linux/uaccess.h> |
32 | #include <linux/units.h> |
33 | |
34 | #include "internal.h" |
35 | |
36 | #define ACPI_THERMAL_CLASS "thermal_zone" |
37 | #define ACPI_THERMAL_DEVICE_NAME "Thermal Zone" |
38 | #define ACPI_THERMAL_NOTIFY_TEMPERATURE 0x80 |
39 | #define ACPI_THERMAL_NOTIFY_THRESHOLDS 0x81 |
40 | #define ACPI_THERMAL_NOTIFY_DEVICES 0x82 |
41 | #define ACPI_THERMAL_NOTIFY_CRITICAL 0xF0 |
42 | #define ACPI_THERMAL_NOTIFY_HOT 0xF1 |
43 | #define ACPI_THERMAL_MODE_ACTIVE 0x00 |
44 | |
45 | #define ACPI_THERMAL_MAX_ACTIVE 10 |
46 | #define ACPI_THERMAL_MAX_LIMIT_STR_LEN 65 |
47 | |
48 | #define ACPI_THERMAL_TRIP_PASSIVE (-1) |
49 | |
50 | #define ACPI_THERMAL_MAX_NR_TRIPS (ACPI_THERMAL_MAX_ACTIVE + 3) |
51 | |
52 | /* |
53 | * This exception is thrown out in two cases: |
54 | * 1.An invalid trip point becomes invalid or a valid trip point becomes invalid |
55 | * when re-evaluating the AML code. |
56 | * 2.TODO: Devices listed in _PSL, _ALx, _TZD may change. |
57 | * We need to re-bind the cooling devices of a thermal zone when this occurs. |
58 | */ |
59 | #define ACPI_THERMAL_TRIPS_EXCEPTION(tz, str) \ |
60 | do { \ |
61 | acpi_handle_info(tz->device->handle, \ |
62 | "ACPI thermal trip point %s changed\n" \ |
63 | "Please report to linux-acpi@vger.kernel.org\n", str); \ |
64 | } while (0) |
65 | |
66 | static int act; |
67 | module_param(act, int, 0644); |
68 | MODULE_PARM_DESC(act, "Disable or override all lowest active trip points." ); |
69 | |
70 | static int crt; |
71 | module_param(crt, int, 0644); |
72 | MODULE_PARM_DESC(crt, "Disable or lower all critical trip points." ); |
73 | |
74 | static int tzp; |
75 | module_param(tzp, int, 0444); |
76 | MODULE_PARM_DESC(tzp, "Thermal zone polling frequency, in 1/10 seconds." ); |
77 | |
78 | static int off; |
79 | module_param(off, int, 0); |
80 | MODULE_PARM_DESC(off, "Set to disable ACPI thermal support." ); |
81 | |
82 | static int psv; |
83 | module_param(psv, int, 0644); |
84 | MODULE_PARM_DESC(psv, "Disable or override all passive trip points." ); |
85 | |
86 | static struct workqueue_struct *acpi_thermal_pm_queue; |
87 | |
88 | struct acpi_thermal_trip { |
89 | unsigned long temp_dk; |
90 | struct acpi_handle_list devices; |
91 | }; |
92 | |
93 | struct acpi_thermal_passive { |
94 | struct acpi_thermal_trip trip; |
95 | unsigned long tc1; |
96 | unsigned long tc2; |
97 | unsigned long delay; |
98 | }; |
99 | |
100 | struct acpi_thermal_active { |
101 | struct acpi_thermal_trip trip; |
102 | }; |
103 | |
104 | struct acpi_thermal_trips { |
105 | struct acpi_thermal_passive passive; |
106 | struct acpi_thermal_active active[ACPI_THERMAL_MAX_ACTIVE]; |
107 | }; |
108 | |
109 | struct acpi_thermal { |
110 | struct acpi_device *device; |
111 | acpi_bus_id name; |
112 | unsigned long temp_dk; |
113 | unsigned long last_temp_dk; |
114 | unsigned long polling_frequency; |
115 | volatile u8 zombie; |
116 | struct acpi_thermal_trips trips; |
117 | struct thermal_zone_device *thermal_zone; |
118 | int kelvin_offset; /* in millidegrees */ |
119 | struct work_struct thermal_check_work; |
120 | struct mutex thermal_check_lock; |
121 | refcount_t thermal_check_count; |
122 | }; |
123 | |
124 | /* -------------------------------------------------------------------------- |
125 | Thermal Zone Management |
126 | -------------------------------------------------------------------------- */ |
127 | |
128 | static int acpi_thermal_get_temperature(struct acpi_thermal *tz) |
129 | { |
130 | acpi_status status = AE_OK; |
131 | unsigned long long tmp; |
132 | |
133 | if (!tz) |
134 | return -EINVAL; |
135 | |
136 | tz->last_temp_dk = tz->temp_dk; |
137 | |
138 | status = acpi_evaluate_integer(handle: tz->device->handle, pathname: "_TMP" , NULL, data: &tmp); |
139 | if (ACPI_FAILURE(status)) |
140 | return -ENODEV; |
141 | |
142 | tz->temp_dk = tmp; |
143 | |
144 | acpi_handle_debug(tz->device->handle, "Temperature is %lu dK\n" , |
145 | tz->temp_dk); |
146 | |
147 | return 0; |
148 | } |
149 | |
150 | static int acpi_thermal_get_polling_frequency(struct acpi_thermal *tz) |
151 | { |
152 | acpi_status status = AE_OK; |
153 | unsigned long long tmp; |
154 | |
155 | if (!tz) |
156 | return -EINVAL; |
157 | |
158 | status = acpi_evaluate_integer(handle: tz->device->handle, pathname: "_TZP" , NULL, data: &tmp); |
159 | if (ACPI_FAILURE(status)) |
160 | return -ENODEV; |
161 | |
162 | tz->polling_frequency = tmp; |
163 | acpi_handle_debug(tz->device->handle, "Polling frequency is %lu dS\n" , |
164 | tz->polling_frequency); |
165 | |
166 | return 0; |
167 | } |
168 | |
169 | static int acpi_thermal_temp(struct acpi_thermal *tz, int temp_deci_k) |
170 | { |
171 | if (temp_deci_k == THERMAL_TEMP_INVALID) |
172 | return THERMAL_TEMP_INVALID; |
173 | |
174 | return deci_kelvin_to_millicelsius_with_offset(t: temp_deci_k, |
175 | offset: tz->kelvin_offset); |
176 | } |
177 | |
178 | static bool acpi_thermal_trip_valid(struct acpi_thermal_trip *acpi_trip) |
179 | { |
180 | return acpi_trip->temp_dk != THERMAL_TEMP_INVALID; |
181 | } |
182 | |
183 | static int active_trip_index(struct acpi_thermal *tz, |
184 | struct acpi_thermal_trip *acpi_trip) |
185 | { |
186 | struct acpi_thermal_active *active; |
187 | |
188 | active = container_of(acpi_trip, struct acpi_thermal_active, trip); |
189 | return active - tz->trips.active; |
190 | } |
191 | |
192 | static long get_passive_temp(struct acpi_thermal *tz) |
193 | { |
194 | int temp; |
195 | |
196 | if (acpi_passive_trip_temp(adev: tz->device, ret_temp: &temp)) |
197 | return THERMAL_TEMP_INVALID; |
198 | |
199 | return temp; |
200 | } |
201 | |
202 | static long get_active_temp(struct acpi_thermal *tz, int index) |
203 | { |
204 | int temp; |
205 | |
206 | if (acpi_active_trip_temp(adev: tz->device, id: index, ret_temp: &temp)) |
207 | return THERMAL_TEMP_INVALID; |
208 | |
209 | /* |
210 | * If an override has been provided, apply it so there are no active |
211 | * trips with thresholds greater than the override. |
212 | */ |
213 | if (act > 0) { |
214 | unsigned long long override = celsius_to_deci_kelvin(t: act); |
215 | |
216 | if (temp > override) |
217 | return override; |
218 | } |
219 | return temp; |
220 | } |
221 | |
222 | static void acpi_thermal_update_trip(struct acpi_thermal *tz, |
223 | const struct thermal_trip *trip) |
224 | { |
225 | struct acpi_thermal_trip *acpi_trip = trip->priv; |
226 | |
227 | if (trip->type == THERMAL_TRIP_PASSIVE) { |
228 | if (psv > 0) |
229 | return; |
230 | |
231 | acpi_trip->temp_dk = get_passive_temp(tz); |
232 | } else { |
233 | int index = active_trip_index(tz, acpi_trip); |
234 | |
235 | acpi_trip->temp_dk = get_active_temp(tz, index); |
236 | } |
237 | |
238 | if (!acpi_thermal_trip_valid(acpi_trip)) |
239 | ACPI_THERMAL_TRIPS_EXCEPTION(tz, "state" ); |
240 | } |
241 | |
242 | static bool update_trip_devices(struct acpi_thermal *tz, |
243 | struct acpi_thermal_trip *acpi_trip, |
244 | int index, bool compare) |
245 | { |
246 | struct acpi_handle_list devices = { 0 }; |
247 | char method[] = "_PSL" ; |
248 | |
249 | if (index != ACPI_THERMAL_TRIP_PASSIVE) { |
250 | method[1] = 'A'; |
251 | method[2] = 'L'; |
252 | method[3] = '0' + index; |
253 | } |
254 | |
255 | if (!acpi_evaluate_reference(handle: tz->device->handle, pathname: method, NULL, list: &devices)) { |
256 | acpi_handle_info(tz->device->handle, "%s evaluation failure\n" , method); |
257 | return false; |
258 | } |
259 | |
260 | if (acpi_handle_list_equal(list1: &acpi_trip->devices, list2: &devices)) { |
261 | acpi_handle_list_free(list: &devices); |
262 | return true; |
263 | } |
264 | |
265 | if (compare) |
266 | ACPI_THERMAL_TRIPS_EXCEPTION(tz, "device" ); |
267 | |
268 | acpi_handle_list_replace(dst: &acpi_trip->devices, src: &devices); |
269 | return true; |
270 | } |
271 | |
272 | static void acpi_thermal_update_trip_devices(struct acpi_thermal *tz, |
273 | const struct thermal_trip *trip) |
274 | { |
275 | struct acpi_thermal_trip *acpi_trip = trip->priv; |
276 | int index = trip->type == THERMAL_TRIP_PASSIVE ? |
277 | ACPI_THERMAL_TRIP_PASSIVE : active_trip_index(tz, acpi_trip); |
278 | |
279 | if (update_trip_devices(tz, acpi_trip, index, compare: true)) |
280 | return; |
281 | |
282 | acpi_trip->temp_dk = THERMAL_TEMP_INVALID; |
283 | ACPI_THERMAL_TRIPS_EXCEPTION(tz, "state" ); |
284 | } |
285 | |
286 | struct adjust_trip_data { |
287 | struct acpi_thermal *tz; |
288 | u32 event; |
289 | }; |
290 | |
291 | static int acpi_thermal_adjust_trip(struct thermal_trip *trip, void *data) |
292 | { |
293 | struct acpi_thermal_trip *acpi_trip = trip->priv; |
294 | struct adjust_trip_data *atd = data; |
295 | struct acpi_thermal *tz = atd->tz; |
296 | int temp; |
297 | |
298 | if (!acpi_trip || !acpi_thermal_trip_valid(acpi_trip)) |
299 | return 0; |
300 | |
301 | if (atd->event == ACPI_THERMAL_NOTIFY_THRESHOLDS) |
302 | acpi_thermal_update_trip(tz, trip); |
303 | else |
304 | acpi_thermal_update_trip_devices(tz, trip); |
305 | |
306 | if (acpi_thermal_trip_valid(acpi_trip)) |
307 | temp = acpi_thermal_temp(tz, temp_deci_k: acpi_trip->temp_dk); |
308 | else |
309 | temp = THERMAL_TEMP_INVALID; |
310 | |
311 | thermal_zone_set_trip_temp(tz: tz->thermal_zone, trip, temp); |
312 | |
313 | return 0; |
314 | } |
315 | |
316 | static void acpi_queue_thermal_check(struct acpi_thermal *tz) |
317 | { |
318 | if (!work_pending(&tz->thermal_check_work)) |
319 | queue_work(wq: acpi_thermal_pm_queue, work: &tz->thermal_check_work); |
320 | } |
321 | |
322 | static void acpi_thermal_trips_update(struct acpi_thermal *tz, u32 event) |
323 | { |
324 | struct adjust_trip_data atd = { .tz = tz, .event = event }; |
325 | struct acpi_device *adev = tz->device; |
326 | |
327 | /* |
328 | * Use thermal_zone_for_each_trip() to carry out the trip points |
329 | * update, so as to protect thermal_get_trend() from getting stale |
330 | * trip point temperatures and to prevent thermal_zone_device_update() |
331 | * invoked from acpi_thermal_check_fn() from producing inconsistent |
332 | * results. |
333 | */ |
334 | thermal_zone_for_each_trip(tz: tz->thermal_zone, |
335 | cb: acpi_thermal_adjust_trip, data: &atd); |
336 | acpi_queue_thermal_check(tz); |
337 | acpi_bus_generate_netlink_event(adev->pnp.device_class, |
338 | dev_name(dev: &adev->dev), event, 0); |
339 | } |
340 | |
341 | static int acpi_thermal_get_critical_trip(struct acpi_thermal *tz) |
342 | { |
343 | int temp; |
344 | |
345 | if (crt > 0) { |
346 | temp = celsius_to_deci_kelvin(t: crt); |
347 | goto set; |
348 | } |
349 | if (crt == -1) { |
350 | acpi_handle_debug(tz->device->handle, "Critical threshold disabled\n" ); |
351 | return THERMAL_TEMP_INVALID; |
352 | } |
353 | |
354 | if (acpi_critical_trip_temp(adev: tz->device, ret_temp: &temp)) |
355 | return THERMAL_TEMP_INVALID; |
356 | |
357 | if (temp <= 2732) { |
358 | /* |
359 | * Below zero (Celsius) values clearly aren't right for sure, |
360 | * so discard them as invalid. |
361 | */ |
362 | pr_info(FW_BUG "Invalid critical threshold (%d)\n" , temp); |
363 | return THERMAL_TEMP_INVALID; |
364 | } |
365 | |
366 | set: |
367 | acpi_handle_debug(tz->device->handle, "Critical threshold [%d]\n" , temp); |
368 | return temp; |
369 | } |
370 | |
371 | static int acpi_thermal_get_hot_trip(struct acpi_thermal *tz) |
372 | { |
373 | int temp; |
374 | |
375 | if (acpi_hot_trip_temp(adev: tz->device, ret_temp: &temp) || temp == THERMAL_TEMP_INVALID) { |
376 | acpi_handle_debug(tz->device->handle, "No hot threshold\n" ); |
377 | return THERMAL_TEMP_INVALID; |
378 | } |
379 | |
380 | acpi_handle_debug(tz->device->handle, "Hot threshold [%d]\n" , temp); |
381 | return temp; |
382 | } |
383 | |
384 | static bool passive_trip_params_init(struct acpi_thermal *tz) |
385 | { |
386 | unsigned long long tmp; |
387 | acpi_status status; |
388 | |
389 | status = acpi_evaluate_integer(handle: tz->device->handle, pathname: "_TC1" , NULL, data: &tmp); |
390 | if (ACPI_FAILURE(status)) |
391 | return false; |
392 | |
393 | tz->trips.passive.tc1 = tmp; |
394 | |
395 | status = acpi_evaluate_integer(handle: tz->device->handle, pathname: "_TC2" , NULL, data: &tmp); |
396 | if (ACPI_FAILURE(status)) |
397 | return false; |
398 | |
399 | tz->trips.passive.tc2 = tmp; |
400 | |
401 | status = acpi_evaluate_integer(handle: tz->device->handle, pathname: "_TFP" , NULL, data: &tmp); |
402 | if (ACPI_SUCCESS(status)) { |
403 | tz->trips.passive.delay = tmp; |
404 | return true; |
405 | } |
406 | |
407 | status = acpi_evaluate_integer(handle: tz->device->handle, pathname: "_TSP" , NULL, data: &tmp); |
408 | if (ACPI_FAILURE(status)) |
409 | return false; |
410 | |
411 | tz->trips.passive.delay = tmp * 100; |
412 | |
413 | return true; |
414 | } |
415 | |
416 | static bool acpi_thermal_init_trip(struct acpi_thermal *tz, int index) |
417 | { |
418 | struct acpi_thermal_trip *acpi_trip; |
419 | long temp; |
420 | |
421 | if (index == ACPI_THERMAL_TRIP_PASSIVE) { |
422 | acpi_trip = &tz->trips.passive.trip; |
423 | |
424 | if (psv == -1) |
425 | goto fail; |
426 | |
427 | if (!passive_trip_params_init(tz)) |
428 | goto fail; |
429 | |
430 | temp = psv > 0 ? celsius_to_deci_kelvin(t: psv) : |
431 | get_passive_temp(tz); |
432 | } else { |
433 | acpi_trip = &tz->trips.active[index].trip; |
434 | |
435 | if (act == -1) |
436 | goto fail; |
437 | |
438 | temp = get_active_temp(tz, index); |
439 | } |
440 | |
441 | if (temp == THERMAL_TEMP_INVALID) |
442 | goto fail; |
443 | |
444 | if (!update_trip_devices(tz, acpi_trip, index, compare: false)) |
445 | goto fail; |
446 | |
447 | acpi_trip->temp_dk = temp; |
448 | return true; |
449 | |
450 | fail: |
451 | acpi_trip->temp_dk = THERMAL_TEMP_INVALID; |
452 | return false; |
453 | } |
454 | |
455 | static void acpi_thermal_get_trip_points(struct acpi_thermal *tz) |
456 | { |
457 | int i; |
458 | |
459 | acpi_thermal_init_trip(tz, ACPI_THERMAL_TRIP_PASSIVE); |
460 | |
461 | for (i = 0; i < ACPI_THERMAL_MAX_ACTIVE; i++) { |
462 | if (!acpi_thermal_init_trip(tz, index: i)) |
463 | break; |
464 | } |
465 | |
466 | while (++i < ACPI_THERMAL_MAX_ACTIVE) |
467 | tz->trips.active[i].trip.temp_dk = THERMAL_TEMP_INVALID; |
468 | } |
469 | |
470 | /* sys I/F for generic thermal sysfs support */ |
471 | |
472 | static int thermal_get_temp(struct thermal_zone_device *thermal, int *temp) |
473 | { |
474 | struct acpi_thermal *tz = thermal_zone_device_priv(tzd: thermal); |
475 | int result; |
476 | |
477 | if (!tz) |
478 | return -EINVAL; |
479 | |
480 | result = acpi_thermal_get_temperature(tz); |
481 | if (result) |
482 | return result; |
483 | |
484 | *temp = deci_kelvin_to_millicelsius_with_offset(t: tz->temp_dk, |
485 | offset: tz->kelvin_offset); |
486 | return 0; |
487 | } |
488 | |
489 | static int thermal_get_trend(struct thermal_zone_device *thermal, |
490 | const struct thermal_trip *trip, |
491 | enum thermal_trend *trend) |
492 | { |
493 | struct acpi_thermal *tz = thermal_zone_device_priv(tzd: thermal); |
494 | struct acpi_thermal_trip *acpi_trip; |
495 | int t; |
496 | |
497 | if (!tz || !trip) |
498 | return -EINVAL; |
499 | |
500 | acpi_trip = trip->priv; |
501 | if (!acpi_trip || !acpi_thermal_trip_valid(acpi_trip)) |
502 | return -EINVAL; |
503 | |
504 | switch (trip->type) { |
505 | case THERMAL_TRIP_PASSIVE: |
506 | t = tz->trips.passive.tc1 * (tz->temp_dk - |
507 | tz->last_temp_dk) + |
508 | tz->trips.passive.tc2 * (tz->temp_dk - |
509 | acpi_trip->temp_dk); |
510 | if (t > 0) |
511 | *trend = THERMAL_TREND_RAISING; |
512 | else if (t < 0) |
513 | *trend = THERMAL_TREND_DROPPING; |
514 | else |
515 | *trend = THERMAL_TREND_STABLE; |
516 | |
517 | return 0; |
518 | |
519 | case THERMAL_TRIP_ACTIVE: |
520 | t = acpi_thermal_temp(tz, temp_deci_k: tz->temp_dk); |
521 | if (t <= trip->temperature) |
522 | break; |
523 | |
524 | *trend = THERMAL_TREND_RAISING; |
525 | |
526 | return 0; |
527 | |
528 | default: |
529 | break; |
530 | } |
531 | |
532 | return -EINVAL; |
533 | } |
534 | |
535 | static void acpi_thermal_zone_device_hot(struct thermal_zone_device *thermal) |
536 | { |
537 | struct acpi_thermal *tz = thermal_zone_device_priv(tzd: thermal); |
538 | |
539 | acpi_bus_generate_netlink_event(tz->device->pnp.device_class, |
540 | dev_name(dev: &tz->device->dev), |
541 | ACPI_THERMAL_NOTIFY_HOT, 1); |
542 | } |
543 | |
544 | static void acpi_thermal_zone_device_critical(struct thermal_zone_device *thermal) |
545 | { |
546 | struct acpi_thermal *tz = thermal_zone_device_priv(tzd: thermal); |
547 | |
548 | acpi_bus_generate_netlink_event(tz->device->pnp.device_class, |
549 | dev_name(dev: &tz->device->dev), |
550 | ACPI_THERMAL_NOTIFY_CRITICAL, 1); |
551 | |
552 | thermal_zone_device_critical(tz: thermal); |
553 | } |
554 | |
555 | struct acpi_thermal_bind_data { |
556 | struct thermal_zone_device *thermal; |
557 | struct thermal_cooling_device *cdev; |
558 | bool bind; |
559 | }; |
560 | |
561 | static int bind_unbind_cdev_cb(struct thermal_trip *trip, void *arg) |
562 | { |
563 | struct acpi_thermal_trip *acpi_trip = trip->priv; |
564 | struct acpi_thermal_bind_data *bd = arg; |
565 | struct thermal_zone_device *thermal = bd->thermal; |
566 | struct thermal_cooling_device *cdev = bd->cdev; |
567 | struct acpi_device *cdev_adev = cdev->devdata; |
568 | int i; |
569 | |
570 | /* Skip critical and hot trips. */ |
571 | if (!acpi_trip) |
572 | return 0; |
573 | |
574 | for (i = 0; i < acpi_trip->devices.count; i++) { |
575 | acpi_handle handle = acpi_trip->devices.handles[i]; |
576 | struct acpi_device *adev = acpi_fetch_acpi_dev(handle); |
577 | |
578 | if (adev != cdev_adev) |
579 | continue; |
580 | |
581 | if (bd->bind) { |
582 | int ret; |
583 | |
584 | ret = thermal_bind_cdev_to_trip(tz: thermal, trip, cdev, |
585 | THERMAL_NO_LIMIT, |
586 | THERMAL_NO_LIMIT, |
587 | THERMAL_WEIGHT_DEFAULT); |
588 | if (ret) |
589 | return ret; |
590 | } else { |
591 | thermal_unbind_cdev_from_trip(tz: thermal, trip, cdev); |
592 | } |
593 | } |
594 | |
595 | return 0; |
596 | } |
597 | |
598 | static int acpi_thermal_bind_unbind_cdev(struct thermal_zone_device *thermal, |
599 | struct thermal_cooling_device *cdev, |
600 | bool bind) |
601 | { |
602 | struct acpi_thermal_bind_data bd = { |
603 | .thermal = thermal, .cdev = cdev, .bind = bind |
604 | }; |
605 | |
606 | return for_each_thermal_trip(tz: thermal, cb: bind_unbind_cdev_cb, data: &bd); |
607 | } |
608 | |
609 | static int |
610 | acpi_thermal_bind_cooling_device(struct thermal_zone_device *thermal, |
611 | struct thermal_cooling_device *cdev) |
612 | { |
613 | return acpi_thermal_bind_unbind_cdev(thermal, cdev, bind: true); |
614 | } |
615 | |
616 | static int |
617 | acpi_thermal_unbind_cooling_device(struct thermal_zone_device *thermal, |
618 | struct thermal_cooling_device *cdev) |
619 | { |
620 | return acpi_thermal_bind_unbind_cdev(thermal, cdev, bind: false); |
621 | } |
622 | |
623 | static const struct thermal_zone_device_ops acpi_thermal_zone_ops = { |
624 | .bind = acpi_thermal_bind_cooling_device, |
625 | .unbind = acpi_thermal_unbind_cooling_device, |
626 | .get_temp = thermal_get_temp, |
627 | .get_trend = thermal_get_trend, |
628 | .hot = acpi_thermal_zone_device_hot, |
629 | .critical = acpi_thermal_zone_device_critical, |
630 | }; |
631 | |
632 | static int acpi_thermal_zone_sysfs_add(struct acpi_thermal *tz) |
633 | { |
634 | struct device *tzdev = thermal_zone_device(tzd: tz->thermal_zone); |
635 | int ret; |
636 | |
637 | ret = sysfs_create_link(kobj: &tz->device->dev.kobj, |
638 | target: &tzdev->kobj, name: "thermal_zone" ); |
639 | if (ret) |
640 | return ret; |
641 | |
642 | ret = sysfs_create_link(kobj: &tzdev->kobj, |
643 | target: &tz->device->dev.kobj, name: "device" ); |
644 | if (ret) |
645 | sysfs_remove_link(kobj: &tz->device->dev.kobj, name: "thermal_zone" ); |
646 | |
647 | return ret; |
648 | } |
649 | |
650 | static void acpi_thermal_zone_sysfs_remove(struct acpi_thermal *tz) |
651 | { |
652 | struct device *tzdev = thermal_zone_device(tzd: tz->thermal_zone); |
653 | |
654 | sysfs_remove_link(kobj: &tz->device->dev.kobj, name: "thermal_zone" ); |
655 | sysfs_remove_link(kobj: &tzdev->kobj, name: "device" ); |
656 | } |
657 | |
658 | static int acpi_thermal_register_thermal_zone(struct acpi_thermal *tz, |
659 | const struct thermal_trip *trip_table, |
660 | unsigned int trip_count, |
661 | int passive_delay) |
662 | { |
663 | int result; |
664 | |
665 | if (trip_count) |
666 | tz->thermal_zone = thermal_zone_device_register_with_trips( |
667 | type: "acpitz" , trips: trip_table, num_trips: trip_count, devdata: tz, |
668 | ops: &acpi_thermal_zone_ops, NULL, passive_delay, |
669 | polling_delay: tz->polling_frequency * 100); |
670 | else |
671 | tz->thermal_zone = thermal_tripless_zone_device_register( |
672 | type: "acpitz" , devdata: tz, ops: &acpi_thermal_zone_ops, NULL); |
673 | |
674 | if (IS_ERR(ptr: tz->thermal_zone)) |
675 | return PTR_ERR(ptr: tz->thermal_zone); |
676 | |
677 | result = acpi_thermal_zone_sysfs_add(tz); |
678 | if (result) |
679 | goto unregister_tzd; |
680 | |
681 | result = thermal_zone_device_enable(tz: tz->thermal_zone); |
682 | if (result) |
683 | goto remove_links; |
684 | |
685 | dev_info(&tz->device->dev, "registered as thermal_zone%d\n" , |
686 | thermal_zone_device_id(tz->thermal_zone)); |
687 | |
688 | return 0; |
689 | |
690 | remove_links: |
691 | acpi_thermal_zone_sysfs_remove(tz); |
692 | unregister_tzd: |
693 | thermal_zone_device_unregister(tz: tz->thermal_zone); |
694 | |
695 | return result; |
696 | } |
697 | |
698 | static void acpi_thermal_unregister_thermal_zone(struct acpi_thermal *tz) |
699 | { |
700 | thermal_zone_device_disable(tz: tz->thermal_zone); |
701 | acpi_thermal_zone_sysfs_remove(tz); |
702 | thermal_zone_device_unregister(tz: tz->thermal_zone); |
703 | tz->thermal_zone = NULL; |
704 | } |
705 | |
706 | |
707 | /* -------------------------------------------------------------------------- |
708 | Driver Interface |
709 | -------------------------------------------------------------------------- */ |
710 | |
711 | static void acpi_thermal_notify(acpi_handle handle, u32 event, void *data) |
712 | { |
713 | struct acpi_device *device = data; |
714 | struct acpi_thermal *tz = acpi_driver_data(d: device); |
715 | |
716 | if (!tz) |
717 | return; |
718 | |
719 | switch (event) { |
720 | case ACPI_THERMAL_NOTIFY_TEMPERATURE: |
721 | acpi_queue_thermal_check(tz); |
722 | break; |
723 | case ACPI_THERMAL_NOTIFY_THRESHOLDS: |
724 | case ACPI_THERMAL_NOTIFY_DEVICES: |
725 | acpi_thermal_trips_update(tz, event); |
726 | break; |
727 | default: |
728 | acpi_handle_debug(device->handle, "Unsupported event [0x%x]\n" , |
729 | event); |
730 | break; |
731 | } |
732 | } |
733 | |
734 | /* |
735 | * On some platforms, the AML code has dependency about |
736 | * the evaluating order of _TMP and _CRT/_HOT/_PSV/_ACx. |
737 | * 1. On HP Pavilion G4-1016tx, _TMP must be invoked after |
738 | * /_CRT/_HOT/_PSV/_ACx, or else system will be power off. |
739 | * 2. On HP Compaq 6715b/6715s, the return value of _PSV is 0 |
740 | * if _TMP has never been evaluated. |
741 | * |
742 | * As this dependency is totally transparent to OS, evaluate |
743 | * all of them once, in the order of _CRT/_HOT/_PSV/_ACx, |
744 | * _TMP, before they are actually used. |
745 | */ |
746 | static void acpi_thermal_aml_dependency_fix(struct acpi_thermal *tz) |
747 | { |
748 | acpi_handle handle = tz->device->handle; |
749 | unsigned long long value; |
750 | int i; |
751 | |
752 | acpi_evaluate_integer(handle, pathname: "_CRT" , NULL, data: &value); |
753 | acpi_evaluate_integer(handle, pathname: "_HOT" , NULL, data: &value); |
754 | acpi_evaluate_integer(handle, pathname: "_PSV" , NULL, data: &value); |
755 | for (i = 0; i < ACPI_THERMAL_MAX_ACTIVE; i++) { |
756 | char name[5] = { '_', 'A', 'C', ('0' + i), '\0' }; |
757 | acpi_status status; |
758 | |
759 | status = acpi_evaluate_integer(handle, pathname: name, NULL, data: &value); |
760 | if (status == AE_NOT_FOUND) |
761 | break; |
762 | } |
763 | acpi_evaluate_integer(handle, pathname: "_TMP" , NULL, data: &value); |
764 | } |
765 | |
766 | /* |
767 | * The exact offset between Kelvin and degree Celsius is 273.15. However ACPI |
768 | * handles temperature values with a single decimal place. As a consequence, |
769 | * some implementations use an offset of 273.1 and others use an offset of |
770 | * 273.2. Try to find out which one is being used, to present the most |
771 | * accurate and visually appealing number. |
772 | * |
773 | * The heuristic below should work for all ACPI thermal zones which have a |
774 | * critical trip point with a value being a multiple of 0.5 degree Celsius. |
775 | */ |
776 | static void acpi_thermal_guess_offset(struct acpi_thermal *tz, long crit_temp) |
777 | { |
778 | if (crit_temp != THERMAL_TEMP_INVALID && crit_temp % 5 == 1) |
779 | tz->kelvin_offset = 273100; |
780 | else |
781 | tz->kelvin_offset = 273200; |
782 | } |
783 | |
784 | static void acpi_thermal_check_fn(struct work_struct *work) |
785 | { |
786 | struct acpi_thermal *tz = container_of(work, struct acpi_thermal, |
787 | thermal_check_work); |
788 | |
789 | /* |
790 | * In general, it is not sufficient to check the pending bit, because |
791 | * subsequent instances of this function may be queued after one of them |
792 | * has started running (e.g. if _TMP sleeps). Avoid bailing out if just |
793 | * one of them is running, though, because it may have done the actual |
794 | * check some time ago, so allow at least one of them to block on the |
795 | * mutex while another one is running the update. |
796 | */ |
797 | if (!refcount_dec_not_one(r: &tz->thermal_check_count)) |
798 | return; |
799 | |
800 | mutex_lock(&tz->thermal_check_lock); |
801 | |
802 | thermal_zone_device_update(tz->thermal_zone, THERMAL_EVENT_UNSPECIFIED); |
803 | |
804 | refcount_inc(r: &tz->thermal_check_count); |
805 | |
806 | mutex_unlock(lock: &tz->thermal_check_lock); |
807 | } |
808 | |
809 | static void acpi_thermal_free_thermal_zone(struct acpi_thermal *tz) |
810 | { |
811 | int i; |
812 | |
813 | acpi_handle_list_free(list: &tz->trips.passive.trip.devices); |
814 | for (i = 0; i < ACPI_THERMAL_MAX_ACTIVE; i++) |
815 | acpi_handle_list_free(list: &tz->trips.active[i].trip.devices); |
816 | |
817 | kfree(objp: tz); |
818 | } |
819 | |
820 | static int acpi_thermal_add(struct acpi_device *device) |
821 | { |
822 | struct thermal_trip trip_table[ACPI_THERMAL_MAX_NR_TRIPS] = { 0 }; |
823 | struct acpi_thermal_trip *acpi_trip; |
824 | struct thermal_trip *trip; |
825 | struct acpi_thermal *tz; |
826 | int crit_temp, hot_temp; |
827 | int passive_delay = 0; |
828 | int result; |
829 | int i; |
830 | |
831 | if (!device) |
832 | return -EINVAL; |
833 | |
834 | tz = kzalloc(size: sizeof(struct acpi_thermal), GFP_KERNEL); |
835 | if (!tz) |
836 | return -ENOMEM; |
837 | |
838 | tz->device = device; |
839 | strcpy(p: tz->name, q: device->pnp.bus_id); |
840 | strcpy(acpi_device_name(device), ACPI_THERMAL_DEVICE_NAME); |
841 | strcpy(acpi_device_class(device), ACPI_THERMAL_CLASS); |
842 | device->driver_data = tz; |
843 | |
844 | acpi_thermal_aml_dependency_fix(tz); |
845 | |
846 | /* Get trip points [_CRT, _PSV, etc.] (required). */ |
847 | acpi_thermal_get_trip_points(tz); |
848 | |
849 | crit_temp = acpi_thermal_get_critical_trip(tz); |
850 | hot_temp = acpi_thermal_get_hot_trip(tz); |
851 | |
852 | /* Get temperature [_TMP] (required). */ |
853 | result = acpi_thermal_get_temperature(tz); |
854 | if (result) |
855 | goto free_memory; |
856 | |
857 | /* Set the cooling mode [_SCP] to active cooling. */ |
858 | acpi_execute_simple_method(handle: tz->device->handle, method: "_SCP" , |
859 | ACPI_THERMAL_MODE_ACTIVE); |
860 | |
861 | /* Determine the default polling frequency [_TZP]. */ |
862 | if (tzp) |
863 | tz->polling_frequency = tzp; |
864 | else |
865 | acpi_thermal_get_polling_frequency(tz); |
866 | |
867 | acpi_thermal_guess_offset(tz, crit_temp); |
868 | |
869 | trip = trip_table; |
870 | |
871 | if (crit_temp != THERMAL_TEMP_INVALID) { |
872 | trip->type = THERMAL_TRIP_CRITICAL; |
873 | trip->temperature = acpi_thermal_temp(tz, temp_deci_k: crit_temp); |
874 | trip++; |
875 | } |
876 | |
877 | if (hot_temp != THERMAL_TEMP_INVALID) { |
878 | trip->type = THERMAL_TRIP_HOT; |
879 | trip->temperature = acpi_thermal_temp(tz, temp_deci_k: hot_temp); |
880 | trip++; |
881 | } |
882 | |
883 | acpi_trip = &tz->trips.passive.trip; |
884 | if (acpi_thermal_trip_valid(acpi_trip)) { |
885 | passive_delay = tz->trips.passive.delay; |
886 | |
887 | trip->type = THERMAL_TRIP_PASSIVE; |
888 | trip->temperature = acpi_thermal_temp(tz, temp_deci_k: acpi_trip->temp_dk); |
889 | trip->priv = acpi_trip; |
890 | trip++; |
891 | } |
892 | |
893 | for (i = 0; i < ACPI_THERMAL_MAX_ACTIVE; i++) { |
894 | acpi_trip = &tz->trips.active[i].trip; |
895 | |
896 | if (!acpi_thermal_trip_valid(acpi_trip)) |
897 | break; |
898 | |
899 | trip->type = THERMAL_TRIP_ACTIVE; |
900 | trip->temperature = acpi_thermal_temp(tz, temp_deci_k: acpi_trip->temp_dk); |
901 | trip->priv = acpi_trip; |
902 | trip++; |
903 | } |
904 | |
905 | if (trip == trip_table) |
906 | pr_warn(FW_BUG "No valid trip points!\n" ); |
907 | |
908 | result = acpi_thermal_register_thermal_zone(tz, trip_table, |
909 | trip_count: trip - trip_table, |
910 | passive_delay); |
911 | if (result) |
912 | goto free_memory; |
913 | |
914 | refcount_set(r: &tz->thermal_check_count, n: 3); |
915 | mutex_init(&tz->thermal_check_lock); |
916 | INIT_WORK(&tz->thermal_check_work, acpi_thermal_check_fn); |
917 | |
918 | pr_info("%s [%s] (%ld C)\n" , acpi_device_name(device), |
919 | acpi_device_bid(device), deci_kelvin_to_celsius(tz->temp_dk)); |
920 | |
921 | result = acpi_dev_install_notify_handler(adev: device, ACPI_DEVICE_NOTIFY, |
922 | handler: acpi_thermal_notify, context: device); |
923 | if (result) |
924 | goto flush_wq; |
925 | |
926 | return 0; |
927 | |
928 | flush_wq: |
929 | flush_workqueue(acpi_thermal_pm_queue); |
930 | acpi_thermal_unregister_thermal_zone(tz); |
931 | free_memory: |
932 | acpi_thermal_free_thermal_zone(tz); |
933 | |
934 | return result; |
935 | } |
936 | |
937 | static void acpi_thermal_remove(struct acpi_device *device) |
938 | { |
939 | struct acpi_thermal *tz; |
940 | |
941 | if (!device || !acpi_driver_data(d: device)) |
942 | return; |
943 | |
944 | tz = acpi_driver_data(d: device); |
945 | |
946 | acpi_dev_remove_notify_handler(adev: device, ACPI_DEVICE_NOTIFY, |
947 | handler: acpi_thermal_notify); |
948 | |
949 | flush_workqueue(acpi_thermal_pm_queue); |
950 | acpi_thermal_unregister_thermal_zone(tz); |
951 | acpi_thermal_free_thermal_zone(tz); |
952 | } |
953 | |
954 | #ifdef CONFIG_PM_SLEEP |
955 | static int acpi_thermal_suspend(struct device *dev) |
956 | { |
957 | /* Make sure the previously queued thermal check work has been done */ |
958 | flush_workqueue(acpi_thermal_pm_queue); |
959 | return 0; |
960 | } |
961 | |
962 | static int acpi_thermal_resume(struct device *dev) |
963 | { |
964 | struct acpi_thermal *tz; |
965 | int i, j, power_state; |
966 | |
967 | if (!dev) |
968 | return -EINVAL; |
969 | |
970 | tz = acpi_driver_data(to_acpi_device(dev)); |
971 | if (!tz) |
972 | return -EINVAL; |
973 | |
974 | for (i = 0; i < ACPI_THERMAL_MAX_ACTIVE; i++) { |
975 | struct acpi_thermal_trip *acpi_trip = &tz->trips.active[i].trip; |
976 | |
977 | if (!acpi_thermal_trip_valid(acpi_trip)) |
978 | break; |
979 | |
980 | for (j = 0; j < acpi_trip->devices.count; j++) { |
981 | acpi_bus_update_power(handle: acpi_trip->devices.handles[j], |
982 | state_p: &power_state); |
983 | } |
984 | } |
985 | |
986 | acpi_queue_thermal_check(tz); |
987 | |
988 | return AE_OK; |
989 | } |
990 | #else |
991 | #define acpi_thermal_suspend NULL |
992 | #define acpi_thermal_resume NULL |
993 | #endif |
994 | static SIMPLE_DEV_PM_OPS(acpi_thermal_pm, acpi_thermal_suspend, acpi_thermal_resume); |
995 | |
996 | static const struct acpi_device_id thermal_device_ids[] = { |
997 | {ACPI_THERMAL_HID, 0}, |
998 | {"" , 0}, |
999 | }; |
1000 | MODULE_DEVICE_TABLE(acpi, thermal_device_ids); |
1001 | |
1002 | static struct acpi_driver acpi_thermal_driver = { |
1003 | .name = "thermal" , |
1004 | .class = ACPI_THERMAL_CLASS, |
1005 | .ids = thermal_device_ids, |
1006 | .ops = { |
1007 | .add = acpi_thermal_add, |
1008 | .remove = acpi_thermal_remove, |
1009 | }, |
1010 | .drv.pm = &acpi_thermal_pm, |
1011 | }; |
1012 | |
1013 | static int thermal_act(const struct dmi_system_id *d) |
1014 | { |
1015 | if (act == 0) { |
1016 | pr_notice("%s detected: disabling all active thermal trip points\n" , |
1017 | d->ident); |
1018 | act = -1; |
1019 | } |
1020 | return 0; |
1021 | } |
1022 | |
1023 | static int thermal_nocrt(const struct dmi_system_id *d) |
1024 | { |
1025 | pr_notice("%s detected: disabling all critical thermal trip point actions.\n" , |
1026 | d->ident); |
1027 | crt = -1; |
1028 | return 0; |
1029 | } |
1030 | |
1031 | static int thermal_tzp(const struct dmi_system_id *d) |
1032 | { |
1033 | if (tzp == 0) { |
1034 | pr_notice("%s detected: enabling thermal zone polling\n" , |
1035 | d->ident); |
1036 | tzp = 300; /* 300 dS = 30 Seconds */ |
1037 | } |
1038 | return 0; |
1039 | } |
1040 | |
1041 | static int thermal_psv(const struct dmi_system_id *d) |
1042 | { |
1043 | if (psv == 0) { |
1044 | pr_notice("%s detected: disabling all passive thermal trip points\n" , |
1045 | d->ident); |
1046 | psv = -1; |
1047 | } |
1048 | return 0; |
1049 | } |
1050 | |
1051 | static const struct dmi_system_id thermal_dmi_table[] __initconst = { |
1052 | /* |
1053 | * Award BIOS on this AOpen makes thermal control almost worthless. |
1054 | * http://bugzilla.kernel.org/show_bug.cgi?id=8842 |
1055 | */ |
1056 | { |
1057 | .callback = thermal_act, |
1058 | .ident = "AOpen i915GMm-HFS" , |
1059 | .matches = { |
1060 | DMI_MATCH(DMI_BOARD_VENDOR, "AOpen" ), |
1061 | DMI_MATCH(DMI_BOARD_NAME, "i915GMm-HFS" ), |
1062 | }, |
1063 | }, |
1064 | { |
1065 | .callback = thermal_psv, |
1066 | .ident = "AOpen i915GMm-HFS" , |
1067 | .matches = { |
1068 | DMI_MATCH(DMI_BOARD_VENDOR, "AOpen" ), |
1069 | DMI_MATCH(DMI_BOARD_NAME, "i915GMm-HFS" ), |
1070 | }, |
1071 | }, |
1072 | { |
1073 | .callback = thermal_tzp, |
1074 | .ident = "AOpen i915GMm-HFS" , |
1075 | .matches = { |
1076 | DMI_MATCH(DMI_BOARD_VENDOR, "AOpen" ), |
1077 | DMI_MATCH(DMI_BOARD_NAME, "i915GMm-HFS" ), |
1078 | }, |
1079 | }, |
1080 | { |
1081 | .callback = thermal_nocrt, |
1082 | .ident = "Gigabyte GA-7ZX" , |
1083 | .matches = { |
1084 | DMI_MATCH(DMI_BOARD_VENDOR, "Gigabyte Technology Co., Ltd." ), |
1085 | DMI_MATCH(DMI_BOARD_NAME, "7ZX" ), |
1086 | }, |
1087 | }, |
1088 | {} |
1089 | }; |
1090 | |
1091 | static int __init acpi_thermal_init(void) |
1092 | { |
1093 | int result; |
1094 | |
1095 | dmi_check_system(list: thermal_dmi_table); |
1096 | |
1097 | if (off) { |
1098 | pr_notice("thermal control disabled\n" ); |
1099 | return -ENODEV; |
1100 | } |
1101 | |
1102 | acpi_thermal_pm_queue = alloc_workqueue(fmt: "acpi_thermal_pm" , |
1103 | flags: WQ_HIGHPRI | WQ_MEM_RECLAIM, max_active: 0); |
1104 | if (!acpi_thermal_pm_queue) |
1105 | return -ENODEV; |
1106 | |
1107 | result = acpi_bus_register_driver(driver: &acpi_thermal_driver); |
1108 | if (result < 0) { |
1109 | destroy_workqueue(wq: acpi_thermal_pm_queue); |
1110 | return -ENODEV; |
1111 | } |
1112 | |
1113 | return 0; |
1114 | } |
1115 | |
1116 | static void __exit acpi_thermal_exit(void) |
1117 | { |
1118 | acpi_bus_unregister_driver(driver: &acpi_thermal_driver); |
1119 | destroy_workqueue(wq: acpi_thermal_pm_queue); |
1120 | } |
1121 | |
1122 | module_init(acpi_thermal_init); |
1123 | module_exit(acpi_thermal_exit); |
1124 | |
1125 | MODULE_IMPORT_NS(ACPI_THERMAL); |
1126 | MODULE_AUTHOR("Paul Diefenbaugh" ); |
1127 | MODULE_DESCRIPTION("ACPI Thermal Zone Driver" ); |
1128 | MODULE_LICENSE("GPL" ); |
1129 | |