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