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