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