| 1 | |
| 2 | |
| 3 | * acpi_thermal.c - ACPI Thermal Zone Driver ($Revision: 41 $)
|
| 4 | *
|
| 5 | * Copyright (C) 2001, 2002 Andy Grover <[email protected]>
|
| 6 | * Copyright (C) 2001, 2002 Paul Diefenbaugh <[email protected]>
|
| 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/platform_device.h>
|
| 29 | #include <linux/thermal.h>
|
| 30 | #include <linux/acpi.h>
|
| 31 | #include <linux/workqueue.h>
|
| 32 | #include <linux/uaccess.h>
|
| 33 | #include <linux/units.h>
|
| 34 |
|
| 35 | #include "internal.h"
|
| 36 |
|
| 37 | #define ACPI_THERMAL_CLASS "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 [email protected]\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;
|
| 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(tz->device->handle, "_TMP", NULL, &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(tz->device->handle, "_TZP", NULL, &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(temp_deci_k,
|
| 177 | 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(tz->device, &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(tz->device, index, &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(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(tz->device->handle, method, NULL, &devices)) {
|
| 262 | acpi_handle_info(tz->device->handle, "%s evaluation failure\n", method);
|
| 263 | return false;
|
| 264 | }
|
| 265 |
|
| 266 | if (acpi_handle_list_equal(&acpi_trip->devices, &devices)) {
|
| 267 | acpi_handle_list_free(&devices);
|
| 268 | return true;
|
| 269 | }
|
| 270 |
|
| 271 | if (compare)
|
| 272 | ACPI_THERMAL_TRIPS_EXCEPTION(tz, "device");
|
| 273 |
|
| 274 | acpi_handle_list_replace(&acpi_trip->devices, &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, 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, acpi_trip->temp_dk);
|
| 314 | else
|
| 315 | temp = THERMAL_TEMP_INVALID;
|
| 316 |
|
| 317 | thermal_zone_set_trip_temp(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(acpi_thermal_pm_queue, &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->thermal_zone,
|
| 341 | acpi_thermal_adjust_trip, &atd);
|
| 342 | acpi_queue_thermal_check(tz);
|
| 343 | acpi_bus_generate_netlink_event(ACPI_THERMAL_CLASS,
|
| 344 | dev_name(&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(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(tz->device, &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(tz->device, &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(tz->device->handle, "_TC1", NULL, &tmp);
|
| 396 | if (ACPI_FAILURE(status))
|
| 397 | return false;
|
| 398 |
|
| 399 | tz->trips.passive.tc1 = tmp;
|
| 400 |
|
| 401 | status = acpi_evaluate_integer(tz->device->handle, "_TC2", NULL, &tmp);
|
| 402 | if (ACPI_FAILURE(status))
|
| 403 | return false;
|
| 404 |
|
| 405 | tz->trips.passive.tc2 = tmp;
|
| 406 |
|
| 407 | status = acpi_evaluate_integer(tz->device->handle, "_TFP", NULL, &tmp);
|
| 408 | if (ACPI_SUCCESS(status)) {
|
| 409 | tz->trips.passive.delay = tmp;
|
| 410 | return true;
|
| 411 | }
|
| 412 |
|
| 413 | status = acpi_evaluate_integer(tz->device->handle, "_TSP", NULL, &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(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, 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, 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 |
|
| 477 |
|
| 478 | static int thermal_get_temp(struct thermal_zone_device *thermal, int *temp)
|
| 479 | {
|
| 480 | struct acpi_thermal *tz = thermal_zone_device_priv(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(tz->temp_dk,
|
| 491 | 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(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, 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(thermal);
|
| 544 |
|
| 545 | acpi_bus_generate_netlink_event(ACPI_THERMAL_CLASS,
|
| 546 | dev_name(&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(thermal);
|
| 553 |
|
| 554 | acpi_bus_generate_netlink_event(ACPI_THERMAL_CLASS,
|
| 555 | dev_name(&tz->device->dev),
|
| 556 | ACPI_THERMAL_NOTIFY_CRITICAL, 1);
|
| 557 |
|
| 558 | thermal_zone_device_critical(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 |
|
| 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(tz->thermal_zone);
|
| 595 | int ret;
|
| 596 |
|
| 597 | ret = sysfs_create_link(&tz->device->dev.kobj,
|
| 598 | &tzdev->kobj, "thermal_zone");
|
| 599 | if (ret)
|
| 600 | return ret;
|
| 601 |
|
| 602 | ret = sysfs_create_link(&tzdev->kobj,
|
| 603 | &tz->device->dev.kobj, "device");
|
| 604 | if (ret)
|
| 605 | sysfs_remove_link(&tz->device->dev.kobj, "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(tz->thermal_zone);
|
| 613 |
|
| 614 | sysfs_remove_link(&tz->device->dev.kobj, "thermal_zone");
|
| 615 | sysfs_remove_link(&tzdev->kobj, "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 | "acpitz", trip_table, trip_count, tz,
|
| 628 | &acpi_thermal_zone_ops, NULL, passive_delay,
|
| 629 | tz->polling_frequency * 100);
|
| 630 | else
|
| 631 | tz->thermal_zone = thermal_tripless_zone_device_register(
|
| 632 | "acpitz", tz, &acpi_thermal_zone_ops, NULL);
|
| 633 |
|
| 634 | if (IS_ERR(tz->thermal_zone))
|
| 635 | return PTR_ERR(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->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->thermal_zone);
|
| 654 |
|
| 655 | return result;
|
| 656 | }
|
| 657 |
|
| 658 | static void acpi_thermal_zone_unregister(void *data)
|
| 659 | {
|
| 660 | struct acpi_thermal *tz = data;
|
| 661 |
|
| 662 | flush_workqueue(acpi_thermal_pm_queue);
|
| 663 |
|
| 664 | thermal_zone_device_disable(tz->thermal_zone);
|
| 665 | acpi_thermal_zone_sysfs_remove(tz);
|
| 666 | thermal_zone_device_unregister(tz->thermal_zone);
|
| 667 | tz->thermal_zone = NULL;
|
| 668 | }
|
| 669 |
|
| 670 |
|
| 671 | |
| 672 | Driver Interface
|
| 673 | --------------------------------------------------------------------------
|
| 674 |
|
| 675 | static void acpi_thermal_notify(acpi_handle handle, u32 event, void *data)
|
| 676 | {
|
| 677 | struct acpi_thermal *tz = data;
|
| 678 |
|
| 679 | if (!tz)
|
| 680 | return;
|
| 681 |
|
| 682 | switch (event) {
|
| 683 | case ACPI_THERMAL_NOTIFY_TEMPERATURE:
|
| 684 | acpi_queue_thermal_check(tz);
|
| 685 | break;
|
| 686 | case ACPI_THERMAL_NOTIFY_THRESHOLDS:
|
| 687 | case ACPI_THERMAL_NOTIFY_DEVICES:
|
| 688 | acpi_thermal_trips_update(tz, event);
|
| 689 | break;
|
| 690 | default:
|
| 691 | acpi_handle_debug(tz->device->handle,
|
| 692 | "Unsupported event [0x%x]\n", event);
|
| 693 | break;
|
| 694 | }
|
| 695 | }
|
| 696 |
|
| 697 | |
| 698 | * On some platforms, the AML code has dependency about
|
| 699 | * the evaluating order of _TMP and _CRT/_HOT/_PSV/_ACx.
|
| 700 | * 1. On HP Pavilion G4-1016tx, _TMP must be invoked after
|
| 701 | * /_CRT/_HOT/_PSV/_ACx, or else system will be power off.
|
| 702 | * 2. On HP Compaq 6715b/6715s, the return value of _PSV is 0
|
| 703 | * if _TMP has never been evaluated.
|
| 704 | *
|
| 705 | * As this dependency is totally transparent to OS, evaluate
|
| 706 | * all of them once, in the order of _CRT/_HOT/_PSV/_ACx,
|
| 707 | * _TMP, before they are actually used.
|
| 708 |
|
| 709 | static void acpi_thermal_aml_dependency_fix(struct acpi_thermal *tz)
|
| 710 | {
|
| 711 | acpi_handle handle = tz->device->handle;
|
| 712 | unsigned long long value;
|
| 713 | int i;
|
| 714 |
|
| 715 | acpi_evaluate_integer(handle, "_CRT", NULL, &value);
|
| 716 | acpi_evaluate_integer(handle, "_HOT", NULL, &value);
|
| 717 | acpi_evaluate_integer(handle, "_PSV", NULL, &value);
|
| 718 | for (i = 0; i < ACPI_THERMAL_MAX_ACTIVE; i++) {
|
| 719 | char name[5] = { '_', 'A', 'C', ('0' + i), '\0' };
|
| 720 | acpi_status status;
|
| 721 |
|
| 722 | status = acpi_evaluate_integer(handle, name, NULL, &value);
|
| 723 | if (status == AE_NOT_FOUND)
|
| 724 | break;
|
| 725 | }
|
| 726 | acpi_evaluate_integer(handle, "_TMP", NULL, &value);
|
| 727 | }
|
| 728 |
|
| 729 | |
| 730 | * The exact offset between Kelvin and degree Celsius is 273.15. However ACPI
|
| 731 | * handles temperature values with a single decimal place. As a consequence,
|
| 732 | * some implementations use an offset of 273.1 and others use an offset of
|
| 733 | * 273.2. Try to find out which one is being used, to present the most
|
| 734 | * accurate and visually appealing number.
|
| 735 | *
|
| 736 | * The heuristic below should work for all ACPI thermal zones which have a
|
| 737 | * critical trip point with a value being a multiple of 0.5 degree Celsius.
|
| 738 |
|
| 739 | static void acpi_thermal_guess_offset(struct acpi_thermal *tz, long crit_temp)
|
| 740 | {
|
| 741 | if (crit_temp != THERMAL_TEMP_INVALID && crit_temp % 5 == 1)
|
| 742 | tz->kelvin_offset = 273100;
|
| 743 | else
|
| 744 | tz->kelvin_offset = 273200;
|
| 745 | }
|
| 746 |
|
| 747 | static void acpi_thermal_check_fn(struct work_struct *work)
|
| 748 | {
|
| 749 | struct acpi_thermal *tz = container_of(work, struct acpi_thermal,
|
| 750 | thermal_check_work);
|
| 751 |
|
| 752 | |
| 753 | * In general, it is not sufficient to check the pending bit, because
|
| 754 | * subsequent instances of this function may be queued after one of them
|
| 755 | * has started running (e.g. if _TMP sleeps). Avoid bailing out if just
|
| 756 | * one of them is running, though, because it may have done the actual
|
| 757 | * check some time ago, so allow at least one of them to block on the
|
| 758 | * mutex while another one is running the update.
|
| 759 |
|
| 760 | if (!refcount_dec_not_one(&tz->thermal_check_count))
|
| 761 | return;
|
| 762 |
|
| 763 | mutex_lock(&tz->thermal_check_lock);
|
| 764 |
|
| 765 | thermal_zone_device_update(tz->thermal_zone, THERMAL_EVENT_UNSPECIFIED);
|
| 766 |
|
| 767 | refcount_inc(&tz->thermal_check_count);
|
| 768 |
|
| 769 | mutex_unlock(&tz->thermal_check_lock);
|
| 770 | }
|
| 771 |
|
| 772 | static void acpi_thermal_zone_free(void *data)
|
| 773 | {
|
| 774 | struct acpi_thermal *tz = data;
|
| 775 | int i;
|
| 776 |
|
| 777 | acpi_handle_list_free(&tz->trips.passive.trip.devices);
|
| 778 | for (i = 0; i < ACPI_THERMAL_MAX_ACTIVE; i++)
|
| 779 | acpi_handle_list_free(&tz->trips.active[i].trip.devices);
|
| 780 |
|
| 781 | kfree(tz);
|
| 782 | }
|
| 783 |
|
| 784 | static int acpi_thermal_probe(struct platform_device *pdev)
|
| 785 | {
|
| 786 | struct thermal_trip trip_table[ACPI_THERMAL_MAX_NR_TRIPS] = { 0 };
|
| 787 | struct device *dev = &pdev->dev;
|
| 788 | struct acpi_device *device = ACPI_COMPANION(dev);
|
| 789 | struct acpi_thermal_trip *acpi_trip;
|
| 790 | struct thermal_trip *trip;
|
| 791 | struct acpi_thermal *tz;
|
| 792 | int crit_temp, hot_temp;
|
| 793 | int passive_delay = 0;
|
| 794 | int result;
|
| 795 | int i;
|
| 796 |
|
| 797 | if (!device)
|
| 798 | return -ENODEV;
|
| 799 |
|
| 800 | tz = kzalloc_obj(struct acpi_thermal);
|
| 801 | if (!tz)
|
| 802 | return -ENOMEM;
|
| 803 |
|
| 804 | result = devm_add_action_or_reset(dev, acpi_thermal_zone_free, tz);
|
| 805 | if (result)
|
| 806 | return result;
|
| 807 |
|
| 808 | platform_set_drvdata(pdev, tz);
|
| 809 |
|
| 810 | tz->device = device;
|
| 811 | strscpy(tz->name, device->pnp.bus_id);
|
| 812 |
|
| 813 | acpi_thermal_aml_dependency_fix(tz);
|
| 814 |
|
| 815 | |
| 816 | * Set the cooling mode [_SCP] to active cooling. This needs to happen before
|
| 817 | * we retrieve the trip point values.
|
| 818 |
|
| 819 | acpi_execute_simple_method(tz->device->handle, "_SCP", ACPI_THERMAL_MODE_ACTIVE);
|
| 820 |
|
| 821 |
|
| 822 | acpi_thermal_get_trip_points(tz);
|
| 823 |
|
| 824 | crit_temp = acpi_thermal_get_critical_trip(tz);
|
| 825 | hot_temp = acpi_thermal_get_hot_trip(tz);
|
| 826 |
|
| 827 |
|
| 828 | result = acpi_thermal_get_temperature(tz);
|
| 829 | if (result)
|
| 830 | return result;
|
| 831 |
|
| 832 |
|
| 833 | if (tzp)
|
| 834 | tz->polling_frequency = tzp;
|
| 835 | else
|
| 836 | acpi_thermal_get_polling_frequency(tz);
|
| 837 |
|
| 838 | acpi_thermal_guess_offset(tz, crit_temp);
|
| 839 |
|
| 840 | trip = trip_table;
|
| 841 |
|
| 842 | if (crit_temp != THERMAL_TEMP_INVALID) {
|
| 843 | trip->type = THERMAL_TRIP_CRITICAL;
|
| 844 | trip->temperature = acpi_thermal_temp(tz, crit_temp);
|
| 845 | trip++;
|
| 846 | }
|
| 847 |
|
| 848 | if (hot_temp != THERMAL_TEMP_INVALID) {
|
| 849 | trip->type = THERMAL_TRIP_HOT;
|
| 850 | trip->temperature = acpi_thermal_temp(tz, hot_temp);
|
| 851 | trip++;
|
| 852 | }
|
| 853 |
|
| 854 | acpi_trip = &tz->trips.passive.trip;
|
| 855 | if (acpi_thermal_trip_valid(acpi_trip)) {
|
| 856 | passive_delay = tz->trips.passive.delay;
|
| 857 |
|
| 858 | trip->type = THERMAL_TRIP_PASSIVE;
|
| 859 | trip->temperature = acpi_thermal_temp(tz, acpi_trip->temp_dk);
|
| 860 | trip->priv = acpi_trip;
|
| 861 | trip++;
|
| 862 | }
|
| 863 |
|
| 864 | for (i = 0; i < ACPI_THERMAL_MAX_ACTIVE; i++) {
|
| 865 | acpi_trip = &tz->trips.active[i].trip;
|
| 866 |
|
| 867 | if (!acpi_thermal_trip_valid(acpi_trip))
|
| 868 | break;
|
| 869 |
|
| 870 | trip->type = THERMAL_TRIP_ACTIVE;
|
| 871 | trip->temperature = acpi_thermal_temp(tz, acpi_trip->temp_dk);
|
| 872 | trip->priv = acpi_trip;
|
| 873 | trip++;
|
| 874 | }
|
| 875 |
|
| 876 | if (trip == trip_table)
|
| 877 | pr_warn(FW_BUG "No valid trip points!\n");
|
| 878 |
|
| 879 | result = acpi_thermal_register_thermal_zone(tz, trip_table,
|
| 880 | trip - trip_table,
|
| 881 | passive_delay);
|
| 882 | if (result)
|
| 883 | return result;
|
| 884 |
|
| 885 | result = devm_add_action_or_reset(dev, acpi_thermal_zone_unregister, tz);
|
| 886 | if (result)
|
| 887 | return result;
|
| 888 |
|
| 889 | refcount_set(&tz->thermal_check_count, 3);
|
| 890 | mutex_init(&tz->thermal_check_lock);
|
| 891 | INIT_WORK(&tz->thermal_check_work, acpi_thermal_check_fn);
|
| 892 |
|
| 893 | pr_info("Thermal Zone [%s] (%ld C)\n", acpi_device_bid(device),
|
| 894 | deci_kelvin_to_celsius(tz->temp_dk));
|
| 895 |
|
| 896 | return devm_acpi_install_notify_handler(dev, ACPI_DEVICE_NOTIFY,
|
| 897 | acpi_thermal_notify, tz);
|
| 898 | }
|
| 899 |
|
| 900 | #ifdef CONFIG_PM_SLEEP
|
| 901 | static int acpi_thermal_prepare(struct device *dev)
|
| 902 | {
|
| 903 |
|
| 904 | flush_workqueue(acpi_thermal_pm_queue);
|
| 905 | return 0;
|
| 906 | }
|
| 907 |
|
| 908 | static void acpi_thermal_complete(struct device *dev)
|
| 909 | {
|
| 910 | acpi_queue_thermal_check(dev_get_drvdata(dev));
|
| 911 | }
|
| 912 |
|
| 913 | static const struct dev_pm_ops acpi_thermal_pm_ops = {
|
| 914 | .prepare = acpi_thermal_prepare,
|
| 915 | .complete = acpi_thermal_complete,
|
| 916 | };
|
| 917 | #define ACPI_THERMAL_PM &acpi_thermal_pm_ops
|
| 918 | #else
|
| 919 | #define ACPI_THERMAL_PM NULL
|
| 920 | #endif
|
| 921 |
|
| 922 | static const struct acpi_device_id thermal_device_ids[] = {
|
| 923 | {ACPI_THERMAL_HID, 0},
|
| 924 | {"", 0},
|
| 925 | };
|
| 926 | MODULE_DEVICE_TABLE(acpi, thermal_device_ids);
|
| 927 |
|
| 928 | static struct platform_driver acpi_thermal_driver = {
|
| 929 | .probe = acpi_thermal_probe,
|
| 930 | .driver = {
|
| 931 | .name = "acpi-thermal",
|
| 932 | .acpi_match_table = thermal_device_ids,
|
| 933 | .pm = ACPI_THERMAL_PM,
|
| 934 | },
|
| 935 | };
|
| 936 |
|
| 937 | static int thermal_act(const struct dmi_system_id *d)
|
| 938 | {
|
| 939 | if (act == 0) {
|
| 940 | pr_notice("%s detected: disabling all active thermal trip points\n",
|
| 941 | d->ident);
|
| 942 | act = -1;
|
| 943 | }
|
| 944 | return 0;
|
| 945 | }
|
| 946 |
|
| 947 | static int thermal_nocrt(const struct dmi_system_id *d)
|
| 948 | {
|
| 949 | pr_notice("%s detected: disabling all critical thermal trip point actions.\n",
|
| 950 | d->ident);
|
| 951 | crt = -1;
|
| 952 | return 0;
|
| 953 | }
|
| 954 |
|
| 955 | static int thermal_tzp(const struct dmi_system_id *d)
|
| 956 | {
|
| 957 | if (tzp == 0) {
|
| 958 | pr_notice("%s detected: enabling thermal zone polling\n",
|
| 959 | d->ident);
|
| 960 | tzp = 300;
|
| 961 | }
|
| 962 | return 0;
|
| 963 | }
|
| 964 |
|
| 965 | static int thermal_psv(const struct dmi_system_id *d)
|
| 966 | {
|
| 967 | if (psv == 0) {
|
| 968 | pr_notice("%s detected: disabling all passive thermal trip points\n",
|
| 969 | d->ident);
|
| 970 | psv = -1;
|
| 971 | }
|
| 972 | return 0;
|
| 973 | }
|
| 974 |
|
| 975 | static const struct dmi_system_id thermal_dmi_table[] __initconst = {
|
| 976 | |
| 977 | * Award BIOS on this AOpen makes thermal control almost worthless.
|
| 978 | * http://bugzilla.kernel.org/show_bug.cgi?id=8842
|
| 979 |
|
| 980 | {
|
| 981 | .callback = thermal_act,
|
| 982 | .ident = "AOpen i915GMm-HFS",
|
| 983 | .matches = {
|
| 984 | DMI_MATCH(DMI_BOARD_VENDOR, "AOpen"),
|
| 985 | DMI_MATCH(DMI_BOARD_NAME, "i915GMm-HFS"),
|
| 986 | },
|
| 987 | },
|
| 988 | {
|
| 989 | .callback = thermal_psv,
|
| 990 | .ident = "AOpen i915GMm-HFS",
|
| 991 | .matches = {
|
| 992 | DMI_MATCH(DMI_BOARD_VENDOR, "AOpen"),
|
| 993 | DMI_MATCH(DMI_BOARD_NAME, "i915GMm-HFS"),
|
| 994 | },
|
| 995 | },
|
| 996 | {
|
| 997 | .callback = thermal_tzp,
|
| 998 | .ident = "AOpen i915GMm-HFS",
|
| 999 | .matches = {
|
| 1000 | DMI_MATCH(DMI_BOARD_VENDOR, "AOpen"),
|
| 1001 | DMI_MATCH(DMI_BOARD_NAME, "i915GMm-HFS"),
|
| 1002 | },
|
| 1003 | },
|
| 1004 | {
|
| 1005 | .callback = thermal_nocrt,
|
| 1006 | .ident = "Gigabyte GA-7ZX",
|
| 1007 | .matches = {
|
| 1008 | DMI_MATCH(DMI_BOARD_VENDOR, "Gigabyte Technology Co., Ltd."),
|
| 1009 | DMI_MATCH(DMI_BOARD_NAME, "7ZX"),
|
| 1010 | },
|
| 1011 | },
|
| 1012 | {}
|
| 1013 | };
|
| 1014 |
|
| 1015 | static int __init acpi_thermal_init(void)
|
| 1016 | {
|
| 1017 | int result;
|
| 1018 |
|
| 1019 | dmi_check_system(thermal_dmi_table);
|
| 1020 |
|
| 1021 | if (off) {
|
| 1022 | pr_notice("thermal control disabled\n");
|
| 1023 | return -ENODEV;
|
| 1024 | }
|
| 1025 |
|
| 1026 | acpi_thermal_pm_queue = alloc_workqueue("acpi_thermal_pm",
|
| 1027 | WQ_HIGHPRI | WQ_MEM_RECLAIM | WQ_PERCPU,
|
| 1028 | 0);
|
| 1029 | if (!acpi_thermal_pm_queue)
|
| 1030 | return -ENODEV;
|
| 1031 |
|
| 1032 | result = platform_driver_register(&acpi_thermal_driver);
|
| 1033 | if (result < 0) {
|
| 1034 | destroy_workqueue(acpi_thermal_pm_queue);
|
| 1035 | return -ENODEV;
|
| 1036 | }
|
| 1037 |
|
| 1038 | return 0;
|
| 1039 | }
|
| 1040 |
|
| 1041 | static void __exit acpi_thermal_exit(void)
|
| 1042 | {
|
| 1043 | platform_driver_unregister(&acpi_thermal_driver);
|
| 1044 | destroy_workqueue(acpi_thermal_pm_queue);
|
| 1045 | }
|
| 1046 |
|
| 1047 | module_init(acpi_thermal_init);
|
| 1048 | module_exit(acpi_thermal_exit);
|
| 1049 |
|
| 1050 | MODULE_IMPORT_NS("ACPI_THERMAL");
|
| 1051 | MODULE_AUTHOR("Paul Diefenbaugh");
|
| 1052 | MODULE_DESCRIPTION("ACPI Thermal Zone Driver");
|
| 1053 | MODULE_LICENSE("GPL");
|