Blame view
drivers/acpi/fan.c
12.2 KB
c942fddf8
|
1 |
// SPDX-License-Identifier: GPL-2.0-or-later |
1da177e4c
|
2 3 4 5 6 |
/* * acpi_fan.c - ACPI Fan Driver ($Revision: 29 $) * * Copyright (C) 2001, 2002 Andy Grover <andrew.grover@intel.com> * Copyright (C) 2001, 2002 Paul Diefenbaugh <paul.s.diefenbaugh@intel.com> |
1da177e4c
|
7 8 9 10 11 12 |
*/ #include <linux/kernel.h> #include <linux/module.h> #include <linux/init.h> #include <linux/types.h> |
88989fd26
|
13 |
#include <linux/uaccess.h> |
05a83d972
|
14 |
#include <linux/thermal.h> |
8b48463f8
|
15 |
#include <linux/acpi.h> |
19593a1fb
|
16 |
#include <linux/platform_device.h> |
9519a6356
|
17 |
#include <linux/sort.h> |
1da177e4c
|
18 |
|
f52fd66d2
|
19 |
MODULE_AUTHOR("Paul Diefenbaugh"); |
7cda93e00
|
20 |
MODULE_DESCRIPTION("ACPI Fan Driver"); |
1da177e4c
|
21 |
MODULE_LICENSE("GPL"); |
19593a1fb
|
22 23 |
static int acpi_fan_probe(struct platform_device *pdev); static int acpi_fan_remove(struct platform_device *pdev); |
1da177e4c
|
24 |
|
1ba90e3a8
|
25 26 |
static const struct acpi_device_id fan_device_ids[] = { {"PNP0C0B", 0}, |
c248dfe7e
|
27 |
{"INT1044", 0}, |
d806c6e9c
|
28 |
{"INT3404", 0}, |
1ba90e3a8
|
29 30 31 |
{"", 0}, }; MODULE_DEVICE_TABLE(acpi, fan_device_ids); |
906924048
|
32 |
#ifdef CONFIG_PM_SLEEP |
62fcbdd95
|
33 34 |
static int acpi_fan_suspend(struct device *dev); static int acpi_fan_resume(struct device *dev); |
1d751584e
|
35 |
static const struct dev_pm_ops acpi_fan_pm = { |
b9b8515f3
|
36 37 38 39 40 41 |
.resume = acpi_fan_resume, .freeze = acpi_fan_suspend, .thaw = acpi_fan_resume, .restore = acpi_fan_resume, }; #define FAN_PM_OPS_PTR (&acpi_fan_pm) |
b108e0ea9
|
42 |
#else |
b9b8515f3
|
43 |
#define FAN_PM_OPS_PTR NULL |
906924048
|
44 |
#endif |
62fcbdd95
|
45 |
|
d19e470b6
|
46 |
#define ACPI_FPS_NAME_LEN 20 |
9519a6356
|
47 48 49 50 51 52 |
struct acpi_fan_fps { u64 control; u64 trip_point; u64 speed; u64 noise_level; u64 power; |
d19e470b6
|
53 54 |
char name[ACPI_FPS_NAME_LEN]; struct device_attribute dev_attr; |
9519a6356
|
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
}; struct acpi_fan_fif { u64 revision; u64 fine_grain_ctrl; u64 step_size; u64 low_speed_notification; }; struct acpi_fan { bool acpi4; struct acpi_fan_fif fif; struct acpi_fan_fps *fps; int fps_count; struct thermal_cooling_device *cdev; }; |
19593a1fb
|
71 72 73 74 75 76 77 78 |
static struct platform_driver acpi_fan_driver = { .probe = acpi_fan_probe, .remove = acpi_fan_remove, .driver = { .name = "acpi-fan", .acpi_match_table = fan_device_ids, .pm = FAN_PM_OPS_PTR, }, |
1da177e4c
|
79 |
}; |
05a83d972
|
80 |
/* thermal cooling device callbacks */ |
6503e5df0
|
81 82 |
static int fan_get_max_state(struct thermal_cooling_device *cdev, unsigned long *state) |
05a83d972
|
83 |
{ |
9519a6356
|
84 85 86 87 88 89 90 |
struct acpi_device *device = cdev->devdata; struct acpi_fan *fan = acpi_driver_data(device); if (fan->acpi4) *state = fan->fps_count - 1; else *state = 1; |
6503e5df0
|
91 |
return 0; |
05a83d972
|
92 |
} |
9519a6356
|
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
static int fan_get_state_acpi4(struct acpi_device *device, unsigned long *state) { struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; struct acpi_fan *fan = acpi_driver_data(device); union acpi_object *obj; acpi_status status; int control, i; status = acpi_evaluate_object(device->handle, "_FST", NULL, &buffer); if (ACPI_FAILURE(status)) { dev_err(&device->dev, "Get fan state failed "); return status; } obj = buffer.pointer; if (!obj || obj->type != ACPI_TYPE_PACKAGE || obj->package.count != 3 || obj->package.elements[1].type != ACPI_TYPE_INTEGER) { dev_err(&device->dev, "Invalid _FST data "); status = -EINVAL; goto err; } control = obj->package.elements[1].integer.value; for (i = 0; i < fan->fps_count; i++) { |
84baf1725
|
120 121 122 123 124 125 126 127 |
/* * When Fine Grain Control is set, return the state * corresponding to maximum fan->fps[i].control * value compared to the current speed. Here the * fan->fps[] is sorted array with increasing speed. */ if (fan->fif.fine_grain_ctrl && control < fan->fps[i].control) { i = (i > 0) ? i - 1 : 0; |
9519a6356
|
128 |
break; |
84baf1725
|
129 130 131 |
} else if (control == fan->fps[i].control) { break; } |
9519a6356
|
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 |
} if (i == fan->fps_count) { dev_dbg(&device->dev, "Invalid control value returned "); status = -EINVAL; goto err; } *state = i; err: kfree(obj); return status; } static int fan_get_state(struct acpi_device *device, unsigned long *state) |
05a83d972
|
148 |
{ |
05a83d972
|
149 |
int result; |
85eb98274
|
150 |
int acpi_state = ACPI_STATE_D0; |
05a83d972
|
151 |
|
2bb3a2bf9
|
152 |
result = acpi_device_update_power(device, &acpi_state); |
05a83d972
|
153 154 |
if (result) return result; |
20dacb71a
|
155 156 157 |
*state = acpi_state == ACPI_STATE_D3_COLD || acpi_state == ACPI_STATE_D3_HOT ? 0 : (acpi_state == ACPI_STATE_D0 ? 1 : -1); |
6503e5df0
|
158 |
return 0; |
05a83d972
|
159 |
} |
9519a6356
|
160 161 |
static int fan_get_cur_state(struct thermal_cooling_device *cdev, unsigned long *state) |
05a83d972
|
162 163 |
{ struct acpi_device *device = cdev->devdata; |
9519a6356
|
164 |
struct acpi_fan *fan = acpi_driver_data(device); |
05a83d972
|
165 |
|
9519a6356
|
166 167 168 169 170 |
if (fan->acpi4) return fan_get_state_acpi4(device, state); else return fan_get_state(device, state); } |
05a83d972
|
171 |
|
9519a6356
|
172 173 174 |
static int fan_set_state(struct acpi_device *device, unsigned long state) { if (state != 0 && state != 1) |
05a83d972
|
175 |
return -EINVAL; |
9519a6356
|
176 177 178 |
return acpi_device_set_power(device, state ? ACPI_STATE_D0 : ACPI_STATE_D3_COLD); } |
05a83d972
|
179 |
|
9519a6356
|
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 |
static int fan_set_state_acpi4(struct acpi_device *device, unsigned long state) { struct acpi_fan *fan = acpi_driver_data(device); acpi_status status; if (state >= fan->fps_count) return -EINVAL; status = acpi_execute_simple_method(device->handle, "_FSL", fan->fps[state].control); if (ACPI_FAILURE(status)) { dev_dbg(&device->dev, "Failed to set state by _FSL "); return status; } return 0; |
05a83d972
|
197 |
} |
9519a6356
|
198 199 200 201 202 203 204 205 206 207 |
static int fan_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state) { struct acpi_device *device = cdev->devdata; struct acpi_fan *fan = acpi_driver_data(device); if (fan->acpi4) return fan_set_state_acpi4(device, state); else return fan_set_state(device, state); |
447a5647c
|
208 |
} |
9519a6356
|
209 |
|
9c8b04be4
|
210 |
static const struct thermal_cooling_device_ops fan_cooling_ops = { |
05a83d972
|
211 212 213 214 |
.get_max_state = fan_get_max_state, .get_cur_state = fan_get_cur_state, .set_cur_state = fan_set_cur_state, }; |
1da177e4c
|
215 |
/* -------------------------------------------------------------------------- |
88989fd26
|
216 217 218 |
* Driver Interface * -------------------------------------------------------------------------- */ |
1da177e4c
|
219 |
|
9519a6356
|
220 |
static bool acpi_fan_is_acpi4(struct acpi_device *device) |
1da177e4c
|
221 |
{ |
9519a6356
|
222 223 224 225 226 |
return acpi_has_method(device->handle, "_FIF") && acpi_has_method(device->handle, "_FPS") && acpi_has_method(device->handle, "_FSL") && acpi_has_method(device->handle, "_FST"); } |
1da177e4c
|
227 |
|
9519a6356
|
228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 |
static int acpi_fan_get_fif(struct acpi_device *device) { struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; struct acpi_fan *fan = acpi_driver_data(device); struct acpi_buffer format = { sizeof("NNNN"), "NNNN" }; struct acpi_buffer fif = { sizeof(fan->fif), &fan->fif }; union acpi_object *obj; acpi_status status; status = acpi_evaluate_object(device->handle, "_FIF", NULL, &buffer); if (ACPI_FAILURE(status)) return status; obj = buffer.pointer; if (!obj || obj->type != ACPI_TYPE_PACKAGE) { dev_err(&device->dev, "Invalid _FIF data "); status = -EINVAL; goto err; } |
1da177e4c
|
248 |
|
9519a6356
|
249 250 251 252 253 254 |
status = acpi_extract_package(obj, &format, &fif); if (ACPI_FAILURE(status)) { dev_err(&device->dev, "Invalid _FIF element "); status = -EINVAL; } |
1da177e4c
|
255 |
|
9519a6356
|
256 257 258 259 260 261 262 263 264 265 266 |
err: kfree(obj); return status; } static int acpi_fan_speed_cmp(const void *a, const void *b) { const struct acpi_fan_fps *fps1 = a; const struct acpi_fan_fps *fps2 = b; return fps1->speed - fps2->speed; } |
d19e470b6
|
267 268 269 270 271 272 |
static ssize_t show_state(struct device *dev, struct device_attribute *attr, char *buf) { struct acpi_fan_fps *fps = container_of(attr, struct acpi_fan_fps, dev_attr); int count; if (fps->control == 0xFFFFFFFF || fps->control > 100) |
949fe25f2
|
273 |
count = scnprintf(buf, PAGE_SIZE, "not-defined:"); |
d19e470b6
|
274 |
else |
949fe25f2
|
275 |
count = scnprintf(buf, PAGE_SIZE, "%lld:", fps->control); |
d19e470b6
|
276 277 |
if (fps->trip_point == 0xFFFFFFFF || fps->trip_point > 9) |
949fe25f2
|
278 |
count += scnprintf(&buf[count], PAGE_SIZE - count, "not-defined:"); |
d19e470b6
|
279 |
else |
949fe25f2
|
280 |
count += scnprintf(&buf[count], PAGE_SIZE - count, "%lld:", fps->trip_point); |
d19e470b6
|
281 282 |
if (fps->speed == 0xFFFFFFFF) |
949fe25f2
|
283 |
count += scnprintf(&buf[count], PAGE_SIZE - count, "not-defined:"); |
d19e470b6
|
284 |
else |
949fe25f2
|
285 |
count += scnprintf(&buf[count], PAGE_SIZE - count, "%lld:", fps->speed); |
d19e470b6
|
286 287 |
if (fps->noise_level == 0xFFFFFFFF) |
949fe25f2
|
288 |
count += scnprintf(&buf[count], PAGE_SIZE - count, "not-defined:"); |
d19e470b6
|
289 |
else |
949fe25f2
|
290 |
count += scnprintf(&buf[count], PAGE_SIZE - count, "%lld:", fps->noise_level * 100); |
d19e470b6
|
291 292 |
if (fps->power == 0xFFFFFFFF) |
949fe25f2
|
293 294 |
count += scnprintf(&buf[count], PAGE_SIZE - count, "not-defined "); |
d19e470b6
|
295 |
else |
949fe25f2
|
296 297 |
count += scnprintf(&buf[count], PAGE_SIZE - count, "%lld ", fps->power); |
d19e470b6
|
298 299 300 |
return count; } |
9519a6356
|
301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 |
static int acpi_fan_get_fps(struct acpi_device *device) { struct acpi_fan *fan = acpi_driver_data(device); struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; union acpi_object *obj; acpi_status status; int i; status = acpi_evaluate_object(device->handle, "_FPS", NULL, &buffer); if (ACPI_FAILURE(status)) return status; obj = buffer.pointer; if (!obj || obj->type != ACPI_TYPE_PACKAGE || obj->package.count < 2) { dev_err(&device->dev, "Invalid _FPS data "); status = -EINVAL; goto err; } fan->fps_count = obj->package.count - 1; /* minus revision field */ |
a86854d0c
|
322 323 |
fan->fps = devm_kcalloc(&device->dev, fan->fps_count, sizeof(struct acpi_fan_fps), |
9519a6356
|
324 325 326 327 328 329 330 331 332 |
GFP_KERNEL); if (!fan->fps) { dev_err(&device->dev, "Not enough memory "); status = -ENOMEM; goto err; } for (i = 0; i < fan->fps_count; i++) { struct acpi_buffer format = { sizeof("NNNNN"), "NNNNN" }; |
d19e470b6
|
333 334 |
struct acpi_buffer fps = { offsetof(struct acpi_fan_fps, name), &fan->fps[i] }; |
9519a6356
|
335 336 337 338 339 |
status = acpi_extract_package(&obj->package.elements[i + 1], &format, &fps); if (ACPI_FAILURE(status)) { dev_err(&device->dev, "Invalid _FPS element "); |
d19e470b6
|
340 |
goto err; |
9519a6356
|
341 342 343 344 345 346 |
} } /* sort the state array according to fan speed in increase order */ sort(fan->fps, fan->fps_count, sizeof(*fan->fps), acpi_fan_speed_cmp, NULL); |
d19e470b6
|
347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 |
for (i = 0; i < fan->fps_count; ++i) { struct acpi_fan_fps *fps = &fan->fps[i]; snprintf(fps->name, ACPI_FPS_NAME_LEN, "state%d", i); fps->dev_attr.show = show_state; fps->dev_attr.store = NULL; fps->dev_attr.attr.name = fps->name; fps->dev_attr.attr.mode = 0444; status = sysfs_create_file(&device->dev.kobj, &fps->dev_attr.attr); if (status) { int j; for (j = 0; j < i; ++j) sysfs_remove_file(&device->dev.kobj, &fan->fps[j].dev_attr.attr); break; } } |
9519a6356
|
364 365 366 367 |
err: kfree(obj); return status; } |
19593a1fb
|
368 |
static int acpi_fan_probe(struct platform_device *pdev) |
1da177e4c
|
369 |
{ |
4be44fcd3
|
370 |
int result = 0; |
05a83d972
|
371 |
struct thermal_cooling_device *cdev; |
9519a6356
|
372 |
struct acpi_fan *fan; |
19593a1fb
|
373 |
struct acpi_device *device = ACPI_COMPANION(&pdev->dev); |
bbb16fef1
|
374 |
char *name; |
1da177e4c
|
375 |
|
9519a6356
|
376 377 378 379 380 381 382 383 384 385 |
fan = devm_kzalloc(&pdev->dev, sizeof(*fan), GFP_KERNEL); if (!fan) { dev_err(&device->dev, "No memory for fan "); return -ENOMEM; } device->driver_data = fan; platform_set_drvdata(pdev, fan); if (acpi_fan_is_acpi4(device)) { |
d19e470b6
|
386 387 388 389 390 391 392 |
result = acpi_fan_get_fif(device); if (result) return result; result = acpi_fan_get_fps(device); if (result) return result; |
9519a6356
|
393 394 395 396 |
fan->acpi4 = true; } else { result = acpi_device_update_power(device, NULL); if (result) { |
f97279996
|
397 398 |
dev_err(&device->dev, "Failed to set initial power state "); |
d19e470b6
|
399 |
goto err_end; |
9519a6356
|
400 |
} |
1da177e4c
|
401 |
} |
bbb16fef1
|
402 403 404 405 406 407 |
if (!strncmp(pdev->name, "PNP0C0B", strlen("PNP0C0B"))) name = "Fan"; else name = acpi_device_bid(device); cdev = thermal_cooling_device_register(name, device, |
05a83d972
|
408 |
&fan_cooling_ops); |
19b36780e
|
409 410 |
if (IS_ERR(cdev)) { result = PTR_ERR(cdev); |
d19e470b6
|
411 |
goto err_end; |
19b36780e
|
412 |
} |
9030062f3
|
413 |
|
19593a1fb
|
414 415 |
dev_dbg(&pdev->dev, "registered as cooling_device%d ", cdev->id); |
9030062f3
|
416 |
|
9519a6356
|
417 |
fan->cdev = cdev; |
19593a1fb
|
418 |
result = sysfs_create_link(&pdev->dev.kobj, |
9030062f3
|
419 420 421 |
&cdev->device.kobj, "thermal_cooling"); if (result) |
8264fce6d
|
422 423 |
dev_err(&pdev->dev, "Failed to create sysfs link 'thermal_cooling' "); |
9030062f3
|
424 425 |
result = sysfs_create_link(&cdev->device.kobj, |
19593a1fb
|
426 |
&pdev->dev.kobj, |
9030062f3
|
427 |
"device"); |
d19e470b6
|
428 |
if (result) { |
8264fce6d
|
429 430 |
dev_err(&pdev->dev, "Failed to create sysfs link 'device' "); |
d19e470b6
|
431 432 433 434 435 436 437 438 439 440 441 442 |
goto err_end; } return 0; err_end: if (fan->acpi4) { int i; for (i = 0; i < fan->fps_count; ++i) sysfs_remove_file(&device->dev.kobj, &fan->fps[i].dev_attr.attr); } |
1da177e4c
|
443 |
|
d550d98d3
|
444 |
return result; |
1da177e4c
|
445 |
} |
19593a1fb
|
446 |
static int acpi_fan_remove(struct platform_device *pdev) |
1da177e4c
|
447 |
{ |
9519a6356
|
448 |
struct acpi_fan *fan = platform_get_drvdata(pdev); |
1da177e4c
|
449 |
|
d19e470b6
|
450 451 452 453 454 455 456 |
if (fan->acpi4) { struct acpi_device *device = ACPI_COMPANION(&pdev->dev); int i; for (i = 0; i < fan->fps_count; ++i) sysfs_remove_file(&device->dev.kobj, &fan->fps[i].dev_attr.attr); } |
19593a1fb
|
457 |
sysfs_remove_link(&pdev->dev.kobj, "thermal_cooling"); |
9519a6356
|
458 459 |
sysfs_remove_link(&fan->cdev->device.kobj, "device"); thermal_cooling_device_unregister(fan->cdev); |
1da177e4c
|
460 |
|
d550d98d3
|
461 |
return 0; |
1da177e4c
|
462 |
} |
906924048
|
463 |
#ifdef CONFIG_PM_SLEEP |
62fcbdd95
|
464 |
static int acpi_fan_suspend(struct device *dev) |
ec68373c0
|
465 |
{ |
9519a6356
|
466 467 468 |
struct acpi_fan *fan = dev_get_drvdata(dev); if (fan->acpi4) return 0; |
ec68373c0
|
469 |
|
19593a1fb
|
470 |
acpi_device_set_power(ACPI_COMPANION(dev), ACPI_STATE_D0); |
ec68373c0
|
471 472 473 |
return AE_OK; } |
62fcbdd95
|
474 |
static int acpi_fan_resume(struct device *dev) |
ec68373c0
|
475 |
{ |
488a76c52
|
476 |
int result; |
9519a6356
|
477 |
struct acpi_fan *fan = dev_get_drvdata(dev); |
ec68373c0
|
478 |
|
9519a6356
|
479 480 |
if (fan->acpi4) return 0; |
ec68373c0
|
481 |
|
19593a1fb
|
482 |
result = acpi_device_update_power(ACPI_COMPANION(dev), NULL); |
488a76c52
|
483 |
if (result) |
88989fd26
|
484 485 |
dev_err(dev, "Error updating fan power state "); |
ec68373c0
|
486 487 488 |
return result; } |
906924048
|
489 |
#endif |
ec68373c0
|
490 |
|
19593a1fb
|
491 |
module_platform_driver(acpi_fan_driver); |