xref: /linux/drivers/acpi/fan_core.c (revision 7fc2cd2e4b398c57c9cf961cfea05eadbf34c05c)
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/bits.h>
11 #include <linux/kernel.h>
12 #include <linux/limits.h>
13 #include <linux/math.h>
14 #include <linux/math64.h>
15 #include <linux/module.h>
16 #include <linux/init.h>
17 #include <linux/types.h>
18 #include <linux/uaccess.h>
19 #include <linux/uuid.h>
20 #include <linux/thermal.h>
21 #include <linux/acpi.h>
22 #include <linux/platform_device.h>
23 #include <linux/sort.h>
24 
25 #include "fan.h"
26 
27 #define ACPI_FAN_NOTIFY_STATE_CHANGED	0x80
28 
29 /*
30  * Defined inside the "Fan Noise Signal" section at
31  * https://learn.microsoft.com/en-us/windows-hardware/design/device-experiences/design-guide.
32  */
33 static const guid_t acpi_fan_microsoft_guid = GUID_INIT(0xA7611840, 0x99FE, 0x41AE, 0xA4, 0x88,
34 							0x35, 0xC7, 0x59, 0x26, 0xC8, 0xEB);
35 #define ACPI_FAN_DSM_GET_TRIP_POINT_GRANULARITY 1
36 #define ACPI_FAN_DSM_SET_TRIP_POINTS		2
37 #define ACPI_FAN_DSM_GET_OPERATING_RANGES	3
38 
39 /*
40  * Ensures that fans with a very low trip point granularity
41  * do not send too many notifications.
42  */
43 static uint min_trip_distance = 100;
44 module_param(min_trip_distance, uint, 0);
45 MODULE_PARM_DESC(min_trip_distance, "Minimum distance between fan speed trip points in RPM");
46 
47 static const struct acpi_device_id fan_device_ids[] = {
48 	ACPI_FAN_DEVICE_IDS,
49 	{"", 0},
50 };
51 MODULE_DEVICE_TABLE(acpi, fan_device_ids);
52 
53 /* thermal cooling device callbacks */
54 static int fan_get_max_state(struct thermal_cooling_device *cdev, unsigned long
55 			     *state)
56 {
57 	struct acpi_device *device = cdev->devdata;
58 	struct acpi_fan *fan = acpi_driver_data(device);
59 
60 	if (fan->acpi4) {
61 		if (fan->fif.fine_grain_ctrl)
62 			*state = 100 / fan->fif.step_size;
63 		else
64 			*state = fan->fps_count - 1;
65 	} else {
66 		*state = 1;
67 	}
68 
69 	return 0;
70 }
71 
72 int acpi_fan_get_fst(acpi_handle handle, struct acpi_fan_fst *fst)
73 {
74 	struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
75 	union acpi_object *obj;
76 	acpi_status status;
77 	int ret = 0;
78 
79 	status = acpi_evaluate_object(handle, "_FST", NULL, &buffer);
80 	if (ACPI_FAILURE(status))
81 		return -EIO;
82 
83 	obj = buffer.pointer;
84 	if (!obj)
85 		return -ENODATA;
86 
87 	if (obj->type != ACPI_TYPE_PACKAGE || obj->package.count != 3) {
88 		ret = -EPROTO;
89 		goto err;
90 	}
91 
92 	if (obj->package.elements[0].type != ACPI_TYPE_INTEGER ||
93 	    obj->package.elements[1].type != ACPI_TYPE_INTEGER ||
94 	    obj->package.elements[2].type != ACPI_TYPE_INTEGER) {
95 		ret = -EPROTO;
96 		goto err;
97 	}
98 
99 	fst->revision = obj->package.elements[0].integer.value;
100 	fst->control = obj->package.elements[1].integer.value;
101 	fst->speed = obj->package.elements[2].integer.value;
102 
103 err:
104 	kfree(obj);
105 	return ret;
106 }
107 
108 static int fan_get_state_acpi4(struct acpi_device *device, unsigned long *state)
109 {
110 	struct acpi_fan *fan = acpi_driver_data(device);
111 	struct acpi_fan_fst fst;
112 	int status, i;
113 
114 	status = acpi_fan_get_fst(device->handle, &fst);
115 	if (status)
116 		return status;
117 
118 	if (fan->fif.fine_grain_ctrl) {
119 		/* This control should be same what we set using _FSL by spec */
120 		if (fst.control > 100) {
121 			dev_dbg(&device->dev, "Invalid control value returned\n");
122 			goto match_fps;
123 		}
124 
125 		*state = (int) fst.control / fan->fif.step_size;
126 		return 0;
127 	}
128 
129 match_fps:
130 	for (i = 0; i < fan->fps_count; i++) {
131 		if (fst.control == fan->fps[i].control)
132 			break;
133 	}
134 	if (i == fan->fps_count) {
135 		dev_dbg(&device->dev, "No matching fps control value\n");
136 		return -EINVAL;
137 	}
138 
139 	*state = i;
140 
141 	return status;
142 }
143 
144 static int fan_get_state(struct acpi_device *device, unsigned long *state)
145 {
146 	int result;
147 	int acpi_state = ACPI_STATE_D0;
148 
149 	result = acpi_device_update_power(device, &acpi_state);
150 	if (result)
151 		return result;
152 
153 	*state = acpi_state == ACPI_STATE_D3_COLD
154 			|| acpi_state == ACPI_STATE_D3_HOT ?
155 		0 : (acpi_state == ACPI_STATE_D0 ? 1 : -1);
156 	return 0;
157 }
158 
159 static int fan_get_cur_state(struct thermal_cooling_device *cdev, unsigned long
160 			     *state)
161 {
162 	struct acpi_device *device = cdev->devdata;
163 	struct acpi_fan *fan = acpi_driver_data(device);
164 
165 	if (fan->acpi4)
166 		return fan_get_state_acpi4(device, state);
167 	else
168 		return fan_get_state(device, state);
169 }
170 
171 static int fan_set_state(struct acpi_device *device, unsigned long state)
172 {
173 	if (state != 0 && state != 1)
174 		return -EINVAL;
175 
176 	return acpi_device_set_power(device,
177 				     state ? ACPI_STATE_D0 : ACPI_STATE_D3_COLD);
178 }
179 
180 static int fan_set_state_acpi4(struct acpi_device *device, unsigned long state)
181 {
182 	struct acpi_fan *fan = acpi_driver_data(device);
183 	acpi_status status;
184 	u64 value = state;
185 	int max_state;
186 
187 	if (fan->fif.fine_grain_ctrl)
188 		max_state = 100 / fan->fif.step_size;
189 	else
190 		max_state = fan->fps_count - 1;
191 
192 	if (state > max_state)
193 		return -EINVAL;
194 
195 	if (fan->fif.fine_grain_ctrl) {
196 		value *= fan->fif.step_size;
197 		/* Spec allows compensate the last step only */
198 		if (value + fan->fif.step_size > 100)
199 			value = 100;
200 	} else {
201 		value = fan->fps[state].control;
202 	}
203 
204 	status = acpi_execute_simple_method(device->handle, "_FSL", value);
205 	if (ACPI_FAILURE(status)) {
206 		dev_dbg(&device->dev, "Failed to set state by _FSL\n");
207 		return -ENODEV;
208 	}
209 
210 	return 0;
211 }
212 
213 static int
214 fan_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state)
215 {
216 	struct acpi_device *device = cdev->devdata;
217 	struct acpi_fan *fan = acpi_driver_data(device);
218 
219 	if (fan->acpi4)
220 		return fan_set_state_acpi4(device, state);
221 	else
222 		return fan_set_state(device, state);
223 }
224 
225 static const struct thermal_cooling_device_ops fan_cooling_ops = {
226 	.get_max_state = fan_get_max_state,
227 	.get_cur_state = fan_get_cur_state,
228 	.set_cur_state = fan_set_cur_state,
229 };
230 
231 /* --------------------------------------------------------------------------
232  *                               Driver Interface
233  * --------------------------------------------------------------------------
234 */
235 
236 static int acpi_fan_get_fif(struct acpi_device *device)
237 {
238 	struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
239 	struct acpi_fan *fan = acpi_driver_data(device);
240 	struct acpi_buffer format = { sizeof("NNNN"), "NNNN" };
241 	u64 fields[4];
242 	struct acpi_buffer fif = { sizeof(fields), fields };
243 	union acpi_object *obj;
244 	acpi_status status;
245 
246 	status = acpi_evaluate_object(device->handle, "_FIF", NULL, &buffer);
247 	if (ACPI_FAILURE(status))
248 		return status;
249 
250 	obj = buffer.pointer;
251 	if (!obj || obj->type != ACPI_TYPE_PACKAGE) {
252 		dev_err(&device->dev, "Invalid _FIF data\n");
253 		status = -EINVAL;
254 		goto err;
255 	}
256 
257 	status = acpi_extract_package(obj, &format, &fif);
258 	if (ACPI_FAILURE(status)) {
259 		dev_err(&device->dev, "Invalid _FIF element\n");
260 		status = -EINVAL;
261 		goto err;
262 	}
263 
264 	fan->fif.revision = fields[0];
265 	fan->fif.fine_grain_ctrl = fields[1];
266 	fan->fif.step_size = fields[2];
267 	fan->fif.low_speed_notification = fields[3];
268 
269 	/* If there is a bug in step size and set as 0, change to 1 */
270 	if (!fan->fif.step_size)
271 		fan->fif.step_size = 1;
272 	/* If step size > 9, change to 9 (by spec valid values 1-9) */
273 	else if (fan->fif.step_size > 9)
274 		fan->fif.step_size = 9;
275 err:
276 	kfree(obj);
277 	return status;
278 }
279 
280 static int acpi_fan_speed_cmp(const void *a, const void *b)
281 {
282 	const struct acpi_fan_fps *fps1 = a;
283 	const struct acpi_fan_fps *fps2 = b;
284 	return fps1->speed - fps2->speed;
285 }
286 
287 static int acpi_fan_get_fps(struct acpi_device *device)
288 {
289 	struct acpi_fan *fan = acpi_driver_data(device);
290 	struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
291 	union acpi_object *obj;
292 	acpi_status status;
293 	int i;
294 
295 	status = acpi_evaluate_object(device->handle, "_FPS", NULL, &buffer);
296 	if (ACPI_FAILURE(status))
297 		return status;
298 
299 	obj = buffer.pointer;
300 	if (!obj || obj->type != ACPI_TYPE_PACKAGE || obj->package.count < 2) {
301 		dev_err(&device->dev, "Invalid _FPS data\n");
302 		status = -EINVAL;
303 		goto err;
304 	}
305 
306 	fan->fps_count = obj->package.count - 1; /* minus revision field */
307 	fan->fps = devm_kcalloc(&device->dev,
308 				fan->fps_count, sizeof(struct acpi_fan_fps),
309 				GFP_KERNEL);
310 	if (!fan->fps) {
311 		dev_err(&device->dev, "Not enough memory\n");
312 		status = -ENOMEM;
313 		goto err;
314 	}
315 	for (i = 0; i < fan->fps_count; i++) {
316 		struct acpi_buffer format = { sizeof("NNNNN"), "NNNNN" };
317 		struct acpi_buffer fps = { offsetof(struct acpi_fan_fps, name),
318 						&fan->fps[i] };
319 		status = acpi_extract_package(&obj->package.elements[i + 1],
320 					      &format, &fps);
321 		if (ACPI_FAILURE(status)) {
322 			dev_err(&device->dev, "Invalid _FPS element\n");
323 			goto err;
324 		}
325 	}
326 
327 	/* sort the state array according to fan speed in increase order */
328 	sort(fan->fps, fan->fps_count, sizeof(*fan->fps),
329 	     acpi_fan_speed_cmp, NULL);
330 
331 err:
332 	kfree(obj);
333 	return status;
334 }
335 
336 static int acpi_fan_dsm_init(struct device *dev)
337 {
338 	union acpi_object dummy = {
339 		.package = {
340 			.type = ACPI_TYPE_PACKAGE,
341 			.count = 0,
342 			.elements = NULL,
343 		},
344 	};
345 	struct acpi_fan *fan = dev_get_drvdata(dev);
346 	union acpi_object *obj;
347 	int ret = 0;
348 
349 	if (!acpi_check_dsm(fan->handle, &acpi_fan_microsoft_guid, 0,
350 			    BIT(ACPI_FAN_DSM_GET_TRIP_POINT_GRANULARITY) |
351 			    BIT(ACPI_FAN_DSM_SET_TRIP_POINTS)))
352 		return 0;
353 
354 	dev_info(dev, "Using Microsoft fan extensions\n");
355 
356 	obj = acpi_evaluate_dsm_typed(fan->handle, &acpi_fan_microsoft_guid, 0,
357 				      ACPI_FAN_DSM_GET_TRIP_POINT_GRANULARITY, &dummy,
358 				      ACPI_TYPE_INTEGER);
359 	if (!obj)
360 		return -EIO;
361 
362 	if (obj->integer.value > U32_MAX)
363 		ret = -EOVERFLOW;
364 	else
365 		fan->fan_trip_granularity = obj->integer.value;
366 
367 	kfree(obj);
368 
369 	return ret;
370 }
371 
372 static int acpi_fan_dsm_set_trip_points(struct device *dev, u64 upper, u64 lower)
373 {
374 	union acpi_object args[2] = {
375 		{
376 			.integer = {
377 				.type = ACPI_TYPE_INTEGER,
378 				.value = lower,
379 			},
380 		},
381 		{
382 			.integer = {
383 				.type = ACPI_TYPE_INTEGER,
384 				.value = upper,
385 			},
386 		},
387 	};
388 	struct acpi_fan *fan = dev_get_drvdata(dev);
389 	union acpi_object in = {
390 		.package = {
391 			.type = ACPI_TYPE_PACKAGE,
392 			.count = ARRAY_SIZE(args),
393 			.elements = args,
394 		},
395 	};
396 	union acpi_object *obj;
397 
398 	obj = acpi_evaluate_dsm(fan->handle, &acpi_fan_microsoft_guid, 0,
399 				ACPI_FAN_DSM_SET_TRIP_POINTS, &in);
400 	kfree(obj);
401 
402 	return 0;
403 }
404 
405 static int acpi_fan_dsm_start(struct device *dev)
406 {
407 	struct acpi_fan *fan = dev_get_drvdata(dev);
408 	int ret;
409 
410 	if (!fan->fan_trip_granularity)
411 		return 0;
412 
413 	/*
414 	 * Some firmware implementations only update the values returned by the
415 	 * _FST control method when a notification is received. This usually
416 	 * works with Microsoft Windows as setting up trip points will keep
417 	 * triggering said notifications, but will cause issues when using _FST
418 	 * without the Microsoft-specific trip point extension.
419 	 *
420 	 * Because of this, an initial notification needs to be triggered to
421 	 * start the cycle of trip points updates. This is achieved by setting
422 	 * the trip points sequencially to two separate ranges. As by the
423 	 * Microsoft specification the firmware should trigger a notification
424 	 * immediately if the fan speed is outside the trip point range. This
425 	 * _should_ result in at least one notification as both ranges do not
426 	 * overlap, meaning that the current fan speed needs to be outside at
427 	 * least one range.
428 	 */
429 	ret = acpi_fan_dsm_set_trip_points(dev, fan->fan_trip_granularity, 0);
430 	if (ret < 0)
431 		return ret;
432 
433 	return acpi_fan_dsm_set_trip_points(dev, fan->fan_trip_granularity * 3,
434 					    fan->fan_trip_granularity * 2);
435 }
436 
437 static int acpi_fan_dsm_update_trips_points(struct device *dev, struct acpi_fan_fst *fst)
438 {
439 	struct acpi_fan *fan = dev_get_drvdata(dev);
440 	u64 upper, lower;
441 
442 	if (!fan->fan_trip_granularity)
443 		return 0;
444 
445 	if (!acpi_fan_speed_valid(fst->speed))
446 		return -EINVAL;
447 
448 	upper = roundup_u64(fst->speed + min_trip_distance, fan->fan_trip_granularity);
449 	if (fst->speed <= min_trip_distance) {
450 		lower = 0;
451 	} else {
452 		/*
453 		 * Valid fan speed values cannot be larger than 32 bit, so
454 		 * we can safely assume that no overflow will happen here.
455 		 */
456 		lower = rounddown((u32)fst->speed - min_trip_distance, fan->fan_trip_granularity);
457 	}
458 
459 	return acpi_fan_dsm_set_trip_points(dev, upper, lower);
460 }
461 
462 static void acpi_fan_notify_handler(acpi_handle handle, u32 event, void *context)
463 {
464 	struct device *dev = context;
465 	struct acpi_fan_fst fst;
466 	int ret;
467 
468 	switch (event) {
469 	case ACPI_FAN_NOTIFY_STATE_CHANGED:
470 		/*
471 		 * The ACPI specification says that we must evaluate _FST when we
472 		 * receive an ACPI event indicating that the fan state has changed.
473 		 */
474 		ret = acpi_fan_get_fst(handle, &fst);
475 		if (ret < 0) {
476 			dev_err(dev, "Error retrieving current fan status: %d\n", ret);
477 		} else {
478 			ret = acpi_fan_dsm_update_trips_points(dev, &fst);
479 			if (ret < 0)
480 				dev_err(dev, "Failed to update trip points: %d\n", ret);
481 		}
482 
483 		acpi_fan_notify_hwmon(dev);
484 		acpi_bus_generate_netlink_event("fan", dev_name(dev), event, 0);
485 		break;
486 	default:
487 		dev_dbg(dev, "Unsupported ACPI notification 0x%x\n", event);
488 		break;
489 	}
490 }
491 
492 static void acpi_fan_notify_remove(void *data)
493 {
494 	struct acpi_fan *fan = data;
495 
496 	acpi_remove_notify_handler(fan->handle, ACPI_DEVICE_NOTIFY, acpi_fan_notify_handler);
497 }
498 
499 static int devm_acpi_fan_notify_init(struct device *dev)
500 {
501 	struct acpi_fan *fan = dev_get_drvdata(dev);
502 	acpi_status status;
503 
504 	status = acpi_install_notify_handler(fan->handle, ACPI_DEVICE_NOTIFY,
505 					     acpi_fan_notify_handler, dev);
506 	if (ACPI_FAILURE(status))
507 		return -EIO;
508 
509 	return devm_add_action_or_reset(dev, acpi_fan_notify_remove, fan);
510 }
511 
512 static int acpi_fan_probe(struct platform_device *pdev)
513 {
514 	int result = 0;
515 	struct thermal_cooling_device *cdev;
516 	struct acpi_fan *fan;
517 	struct acpi_device *device = ACPI_COMPANION(&pdev->dev);
518 	char *name;
519 
520 	if (!device)
521 		return -ENODEV;
522 
523 	fan = devm_kzalloc(&pdev->dev, sizeof(*fan), GFP_KERNEL);
524 	if (!fan) {
525 		dev_err(&device->dev, "No memory for fan\n");
526 		return -ENOMEM;
527 	}
528 
529 	fan->handle = device->handle;
530 	device->driver_data = fan;
531 	platform_set_drvdata(pdev, fan);
532 
533 	if (acpi_has_method(device->handle, "_FST")) {
534 		fan->has_fst = true;
535 		fan->acpi4 = acpi_has_method(device->handle, "_FIF") &&
536 				acpi_has_method(device->handle, "_FPS") &&
537 				acpi_has_method(device->handle, "_FSL");
538 	}
539 
540 	if (fan->acpi4) {
541 		result = acpi_fan_get_fif(device);
542 		if (result)
543 			return result;
544 
545 		result = acpi_fan_get_fps(device);
546 		if (result)
547 			return result;
548 	}
549 
550 	if (fan->has_fst) {
551 		result = acpi_fan_dsm_init(&pdev->dev);
552 		if (result)
553 			return result;
554 
555 		result = devm_acpi_fan_create_hwmon(&pdev->dev);
556 		if (result)
557 			return result;
558 
559 		result = devm_acpi_fan_notify_init(&pdev->dev);
560 		if (result)
561 			return result;
562 
563 		result = acpi_fan_dsm_start(&pdev->dev);
564 		if (result) {
565 			dev_err(&pdev->dev, "Failed to start Microsoft fan extensions\n");
566 			return result;
567 		}
568 
569 		result = acpi_fan_create_attributes(device);
570 		if (result)
571 			return result;
572 	}
573 
574 	if (!fan->acpi4) {
575 		result = acpi_device_update_power(device, NULL);
576 		if (result) {
577 			dev_err(&device->dev, "Failed to set initial power state\n");
578 			goto err_end;
579 		}
580 	}
581 
582 	if (!strncmp(pdev->name, "PNP0C0B", strlen("PNP0C0B")))
583 		name = "Fan";
584 	else
585 		name = acpi_device_bid(device);
586 
587 	cdev = thermal_cooling_device_register(name, device,
588 						&fan_cooling_ops);
589 	if (IS_ERR(cdev)) {
590 		result = PTR_ERR(cdev);
591 		goto err_end;
592 	}
593 
594 	dev_dbg(&pdev->dev, "registered as cooling_device%d\n", cdev->id);
595 
596 	fan->cdev = cdev;
597 	result = sysfs_create_link(&pdev->dev.kobj,
598 				   &cdev->device.kobj,
599 				   "thermal_cooling");
600 	if (result) {
601 		dev_err(&pdev->dev, "Failed to create sysfs link 'thermal_cooling'\n");
602 		goto err_unregister;
603 	}
604 
605 	result = sysfs_create_link(&cdev->device.kobj,
606 				   &pdev->dev.kobj,
607 				   "device");
608 	if (result) {
609 		dev_err(&pdev->dev, "Failed to create sysfs link 'device'\n");
610 		goto err_remove_link;
611 	}
612 
613 	return 0;
614 
615 err_remove_link:
616 	sysfs_remove_link(&pdev->dev.kobj, "thermal_cooling");
617 err_unregister:
618 	thermal_cooling_device_unregister(cdev);
619 err_end:
620 	if (fan->has_fst)
621 		acpi_fan_delete_attributes(device);
622 
623 	return result;
624 }
625 
626 static void acpi_fan_remove(struct platform_device *pdev)
627 {
628 	struct acpi_fan *fan = platform_get_drvdata(pdev);
629 
630 	if (fan->has_fst) {
631 		struct acpi_device *device = ACPI_COMPANION(&pdev->dev);
632 
633 		acpi_fan_delete_attributes(device);
634 	}
635 	sysfs_remove_link(&pdev->dev.kobj, "thermal_cooling");
636 	sysfs_remove_link(&fan->cdev->device.kobj, "device");
637 	thermal_cooling_device_unregister(fan->cdev);
638 }
639 
640 #ifdef CONFIG_PM_SLEEP
641 static int acpi_fan_suspend(struct device *dev)
642 {
643 	struct acpi_fan *fan = dev_get_drvdata(dev);
644 	if (fan->acpi4)
645 		return 0;
646 
647 	acpi_device_set_power(ACPI_COMPANION(dev), ACPI_STATE_D0);
648 
649 	return AE_OK;
650 }
651 
652 static int acpi_fan_resume(struct device *dev)
653 {
654 	struct acpi_fan *fan = dev_get_drvdata(dev);
655 	int result;
656 
657 	if (fan->has_fst) {
658 		result = acpi_fan_dsm_start(dev);
659 		if (result)
660 			dev_err(dev, "Failed to start Microsoft fan extensions: %d\n", result);
661 	}
662 
663 	if (fan->acpi4)
664 		return 0;
665 
666 	result = acpi_device_update_power(ACPI_COMPANION(dev), NULL);
667 	if (result)
668 		dev_err(dev, "Error updating fan power state\n");
669 
670 	return result;
671 }
672 
673 static const struct dev_pm_ops acpi_fan_pm = {
674 	.resume = acpi_fan_resume,
675 	.freeze = acpi_fan_suspend,
676 	.thaw = acpi_fan_resume,
677 	.restore = acpi_fan_resume,
678 };
679 #define FAN_PM_OPS_PTR (&acpi_fan_pm)
680 
681 #else
682 
683 #define FAN_PM_OPS_PTR NULL
684 
685 #endif
686 
687 static struct platform_driver acpi_fan_driver = {
688 	.probe = acpi_fan_probe,
689 	.remove = acpi_fan_remove,
690 	.driver = {
691 		.name = "acpi-fan",
692 		.acpi_match_table = fan_device_ids,
693 		.pm = FAN_PM_OPS_PTR,
694 	},
695 };
696 
697 module_platform_driver(acpi_fan_driver);
698 
699 MODULE_AUTHOR("Paul Diefenbaugh");
700 MODULE_DESCRIPTION("ACPI Fan Driver");
701 MODULE_LICENSE("GPL");
702