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