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