1 // SPDX-License-Identifier: GPL-2.0+
2
3 /* Platform driver for GPD devices that expose fan control via hwmon sysfs.
4 *
5 * Fan control is provided via pwm interface in the range [0-255].
6 * Each model has a different range in the EC, the written value is scaled to
7 * accommodate for that.
8 *
9 * Based on this repo:
10 * https://github.com/Cryolitia/gpd-fan-driver
11 *
12 * Copyright (c) 2024 Cryolitia PukNgae
13 */
14
15 #include <linux/dmi.h>
16 #include <linux/hwmon.h>
17 #include <linux/io.h>
18 #include <linux/ioport.h>
19 #include <linux/kernel.h>
20 #include <linux/module.h>
21 #include <linux/platform_device.h>
22
23 #define DRIVER_NAME "gpdfan"
24 #define GPD_PWM_CTR_OFFSET 0x1841
25
26 static char *gpd_fan_board = "";
27 module_param(gpd_fan_board, charp, 0444);
28
29 // EC read/write locker, protecting a sequence of EC operations
30 static DEFINE_MUTEX(gpd_fan_sequence_lock);
31
32 enum gpd_board {
33 win_mini,
34 win4_6800u,
35 win_max_2,
36 duo,
37 };
38
39 enum FAN_PWM_ENABLE {
40 DISABLE = 0,
41 MANUAL = 1,
42 AUTOMATIC = 2,
43 };
44
45 static struct {
46 enum FAN_PWM_ENABLE pwm_enable;
47 u8 pwm_value;
48
49 const struct gpd_fan_drvdata *drvdata;
50 } gpd_driver_priv;
51
52 struct gpd_fan_drvdata {
53 const char *board_name; // Board name for module param comparison
54 const enum gpd_board board;
55
56 const u8 addr_port;
57 const u8 data_port;
58 const u16 manual_control_enable;
59 const u16 rpm_read;
60 const u16 pwm_write;
61 const u16 pwm_max;
62 };
63
64 static struct gpd_fan_drvdata gpd_win_mini_drvdata = {
65 .board_name = "win_mini",
66 .board = win_mini,
67
68 .addr_port = 0x4E,
69 .data_port = 0x4F,
70 .manual_control_enable = 0x047A,
71 .rpm_read = 0x0478,
72 .pwm_write = 0x047A,
73 .pwm_max = 244,
74 };
75
76 static struct gpd_fan_drvdata gpd_duo_drvdata = {
77 .board_name = "duo",
78 .board = duo,
79
80 .addr_port = 0x4E,
81 .data_port = 0x4F,
82 .manual_control_enable = 0x047A,
83 .rpm_read = 0x0478,
84 .pwm_write = 0x047A,
85 .pwm_max = 244,
86 };
87
88 static struct gpd_fan_drvdata gpd_win4_drvdata = {
89 .board_name = "win4",
90 .board = win4_6800u,
91
92 .addr_port = 0x2E,
93 .data_port = 0x2F,
94 .manual_control_enable = 0xC311,
95 .rpm_read = 0xC880,
96 .pwm_write = 0xC311,
97 .pwm_max = 127,
98 };
99
100 static struct gpd_fan_drvdata gpd_wm2_drvdata = {
101 .board_name = "wm2",
102 .board = win_max_2,
103
104 .addr_port = 0x4E,
105 .data_port = 0x4F,
106 .manual_control_enable = 0x0275,
107 .rpm_read = 0x0218,
108 .pwm_write = 0x1809,
109 .pwm_max = 184,
110 };
111
112 static const struct dmi_system_id dmi_table[] = {
113 {
114 // GPD Win Mini
115 // GPD Win Mini with AMD Ryzen 8840U
116 .matches = {
117 DMI_MATCH(DMI_SYS_VENDOR, "GPD"),
118 DMI_MATCH(DMI_PRODUCT_NAME, "G1617-01")
119 },
120 .driver_data = &gpd_win_mini_drvdata,
121 },
122 {
123 // GPD Win Mini
124 // GPD Win Mini with AMD Ryzen HX370
125 .matches = {
126 DMI_MATCH(DMI_SYS_VENDOR, "GPD"),
127 DMI_MATCH(DMI_PRODUCT_NAME, "G1617-02")
128 },
129 .driver_data = &gpd_win_mini_drvdata,
130 },
131 {
132 // GPD Win Mini
133 // GPD Win Mini with AMD Ryzen HX370
134 .matches = {
135 DMI_MATCH(DMI_SYS_VENDOR, "GPD"),
136 DMI_MATCH(DMI_PRODUCT_NAME, "G1617-02-L")
137 },
138 .driver_data = &gpd_win_mini_drvdata,
139 },
140 {
141 // GPD Win 4 with AMD Ryzen 6800U
142 .matches = {
143 DMI_MATCH(DMI_SYS_VENDOR, "GPD"),
144 DMI_MATCH(DMI_PRODUCT_NAME, "G1618-04"),
145 DMI_MATCH(DMI_BOARD_VERSION, "Default string"),
146 },
147 .driver_data = &gpd_win4_drvdata,
148 },
149 {
150 // GPD Win 4 with Ryzen 7840U
151 .matches = {
152 DMI_MATCH(DMI_SYS_VENDOR, "GPD"),
153 DMI_MATCH(DMI_PRODUCT_NAME, "G1618-04"),
154 DMI_MATCH(DMI_BOARD_VERSION, "Ver. 1.0"),
155 },
156 // Since 7840U, win4 uses the same drvdata as wm2
157 .driver_data = &gpd_wm2_drvdata,
158 },
159 {
160 // GPD Win 4 with Ryzen 7840U (another)
161 .matches = {
162 DMI_MATCH(DMI_SYS_VENDOR, "GPD"),
163 DMI_MATCH(DMI_PRODUCT_NAME, "G1618-04"),
164 DMI_MATCH(DMI_BOARD_VERSION, "Ver.1.0"),
165 },
166 .driver_data = &gpd_wm2_drvdata,
167 },
168 {
169 // GPD Win Max 2 with Ryzen 6800U
170 // GPD Win Max 2 2023 with Ryzen 7840U
171 // GPD Win Max 2 2024 with Ryzen 8840U
172 .matches = {
173 DMI_MATCH(DMI_SYS_VENDOR, "GPD"),
174 DMI_MATCH(DMI_PRODUCT_NAME, "G1619-04"),
175 },
176 .driver_data = &gpd_wm2_drvdata,
177 },
178 {
179 // GPD Win Max 2 with AMD Ryzen HX370
180 .matches = {
181 DMI_MATCH(DMI_SYS_VENDOR, "GPD"),
182 DMI_MATCH(DMI_PRODUCT_NAME, "G1619-05"),
183 },
184 .driver_data = &gpd_wm2_drvdata,
185 },
186 {
187 // GPD Duo
188 .matches = {
189 DMI_MATCH(DMI_SYS_VENDOR, "GPD"),
190 DMI_MATCH(DMI_PRODUCT_NAME, "G1622-01"),
191 },
192 .driver_data = &gpd_duo_drvdata,
193 },
194 {
195 // GPD Duo (another)
196 .matches = {
197 DMI_MATCH(DMI_SYS_VENDOR, "GPD"),
198 DMI_MATCH(DMI_PRODUCT_NAME, "G1622-01-L"),
199 },
200 .driver_data = &gpd_duo_drvdata,
201 },
202 {
203 // GPD Pocket 4
204 .matches = {
205 DMI_MATCH(DMI_SYS_VENDOR, "GPD"),
206 DMI_MATCH(DMI_PRODUCT_NAME, "G1628-04"),
207 },
208 .driver_data = &gpd_win_mini_drvdata,
209 },
210 {
211 // GPD Pocket 4 (another)
212 .matches = {
213 DMI_MATCH(DMI_SYS_VENDOR, "GPD"),
214 DMI_MATCH(DMI_PRODUCT_NAME, "G1628-04-L"),
215 },
216 .driver_data = &gpd_win_mini_drvdata,
217 },
218 {}
219 };
220
221 static const struct gpd_fan_drvdata *gpd_module_drvdata[] = {
222 &gpd_win_mini_drvdata, &gpd_win4_drvdata, &gpd_wm2_drvdata, NULL
223 };
224
225 // Helper functions to handle EC read/write
gpd_ecram_read(u16 offset,u8 * val)226 static void gpd_ecram_read(u16 offset, u8 *val)
227 {
228 u16 addr_port = gpd_driver_priv.drvdata->addr_port;
229 u16 data_port = gpd_driver_priv.drvdata->data_port;
230
231 outb(0x2E, addr_port);
232 outb(0x11, data_port);
233 outb(0x2F, addr_port);
234 outb((u8)((offset >> 8) & 0xFF), data_port);
235
236 outb(0x2E, addr_port);
237 outb(0x10, data_port);
238 outb(0x2F, addr_port);
239 outb((u8)(offset & 0xFF), data_port);
240
241 outb(0x2E, addr_port);
242 outb(0x12, data_port);
243 outb(0x2F, addr_port);
244 *val = inb(data_port);
245 }
246
gpd_ecram_write(u16 offset,u8 value)247 static void gpd_ecram_write(u16 offset, u8 value)
248 {
249 u16 addr_port = gpd_driver_priv.drvdata->addr_port;
250 u16 data_port = gpd_driver_priv.drvdata->data_port;
251
252 outb(0x2E, addr_port);
253 outb(0x11, data_port);
254 outb(0x2F, addr_port);
255 outb((u8)((offset >> 8) & 0xFF), data_port);
256
257 outb(0x2E, addr_port);
258 outb(0x10, data_port);
259 outb(0x2F, addr_port);
260 outb((u8)(offset & 0xFF), data_port);
261
262 outb(0x2E, addr_port);
263 outb(0x12, data_port);
264 outb(0x2F, addr_port);
265 outb(value, data_port);
266 }
267
gpd_generic_read_rpm(void)268 static int gpd_generic_read_rpm(void)
269 {
270 const struct gpd_fan_drvdata *const drvdata = gpd_driver_priv.drvdata;
271 u8 high, low;
272
273 gpd_ecram_read(drvdata->rpm_read, &high);
274 gpd_ecram_read(drvdata->rpm_read + 1, &low);
275
276 return (u16)high << 8 | low;
277 }
278
gpd_wm2_read_rpm(void)279 static int gpd_wm2_read_rpm(void)
280 {
281 for (u16 pwm_ctr_offset = GPD_PWM_CTR_OFFSET;
282 pwm_ctr_offset <= GPD_PWM_CTR_OFFSET + 2; pwm_ctr_offset++) {
283 u8 PWMCTR;
284
285 gpd_ecram_read(pwm_ctr_offset, &PWMCTR);
286
287 if (PWMCTR != 0xB8)
288 gpd_ecram_write(pwm_ctr_offset, 0xB8);
289 }
290
291 return gpd_generic_read_rpm();
292 }
293
294 // Read value for fan1_input
gpd_read_rpm(void)295 static int gpd_read_rpm(void)
296 {
297 switch (gpd_driver_priv.drvdata->board) {
298 case win4_6800u:
299 case win_mini:
300 case duo:
301 return gpd_generic_read_rpm();
302 case win_max_2:
303 return gpd_wm2_read_rpm();
304 }
305
306 return 0;
307 }
308
gpd_wm2_read_pwm(void)309 static int gpd_wm2_read_pwm(void)
310 {
311 const struct gpd_fan_drvdata *const drvdata = gpd_driver_priv.drvdata;
312 u8 var;
313
314 gpd_ecram_read(drvdata->pwm_write, &var);
315
316 // Match gpd_generic_write_pwm(u8) below
317 return DIV_ROUND_CLOSEST((var - 1) * 255, (drvdata->pwm_max - 1));
318 }
319
320 // Read value for pwm1
gpd_read_pwm(void)321 static int gpd_read_pwm(void)
322 {
323 switch (gpd_driver_priv.drvdata->board) {
324 case win_mini:
325 case duo:
326 case win4_6800u:
327 switch (gpd_driver_priv.pwm_enable) {
328 case DISABLE:
329 return 255;
330 case MANUAL:
331 return gpd_driver_priv.pwm_value;
332 case AUTOMATIC:
333 return -EOPNOTSUPP;
334 }
335 break;
336 case win_max_2:
337 return gpd_wm2_read_pwm();
338 }
339 return 0;
340 }
341
342 // PWM value's range in EC is 1 - pwm_max, cast 0 - 255 to it.
gpd_cast_pwm_range(u8 val)343 static inline u8 gpd_cast_pwm_range(u8 val)
344 {
345 const struct gpd_fan_drvdata *const drvdata = gpd_driver_priv.drvdata;
346
347 return DIV_ROUND_CLOSEST(val * (drvdata->pwm_max - 1), 255) + 1;
348 }
349
gpd_generic_write_pwm(u8 val)350 static void gpd_generic_write_pwm(u8 val)
351 {
352 const struct gpd_fan_drvdata *const drvdata = gpd_driver_priv.drvdata;
353 u8 pwm_reg;
354
355 pwm_reg = gpd_cast_pwm_range(val);
356 gpd_ecram_write(drvdata->pwm_write, pwm_reg);
357 }
358
gpd_duo_write_pwm(u8 val)359 static void gpd_duo_write_pwm(u8 val)
360 {
361 const struct gpd_fan_drvdata *const drvdata = gpd_driver_priv.drvdata;
362 u8 pwm_reg;
363
364 pwm_reg = gpd_cast_pwm_range(val);
365 gpd_ecram_write(drvdata->pwm_write, pwm_reg);
366 gpd_ecram_write(drvdata->pwm_write + 1, pwm_reg);
367 }
368
369 // Write value for pwm1
gpd_write_pwm(u8 val)370 static int gpd_write_pwm(u8 val)
371 {
372 if (gpd_driver_priv.pwm_enable != MANUAL)
373 return -EPERM;
374
375 switch (gpd_driver_priv.drvdata->board) {
376 case duo:
377 gpd_duo_write_pwm(val);
378 break;
379 case win_mini:
380 case win4_6800u:
381 case win_max_2:
382 gpd_generic_write_pwm(val);
383 break;
384 }
385
386 return 0;
387 }
388
gpd_win_mini_set_pwm_enable(enum FAN_PWM_ENABLE pwm_enable)389 static void gpd_win_mini_set_pwm_enable(enum FAN_PWM_ENABLE pwm_enable)
390 {
391 switch (pwm_enable) {
392 case DISABLE:
393 gpd_generic_write_pwm(255);
394 break;
395 case MANUAL:
396 gpd_generic_write_pwm(gpd_driver_priv.pwm_value);
397 break;
398 case AUTOMATIC:
399 gpd_ecram_write(gpd_driver_priv.drvdata->pwm_write, 0);
400 break;
401 }
402 }
403
gpd_duo_set_pwm_enable(enum FAN_PWM_ENABLE pwm_enable)404 static void gpd_duo_set_pwm_enable(enum FAN_PWM_ENABLE pwm_enable)
405 {
406 switch (pwm_enable) {
407 case DISABLE:
408 gpd_duo_write_pwm(255);
409 break;
410 case MANUAL:
411 gpd_duo_write_pwm(gpd_driver_priv.pwm_value);
412 break;
413 case AUTOMATIC:
414 gpd_ecram_write(gpd_driver_priv.drvdata->pwm_write, 0);
415 break;
416 }
417 }
418
gpd_wm2_set_pwm_enable(enum FAN_PWM_ENABLE enable)419 static void gpd_wm2_set_pwm_enable(enum FAN_PWM_ENABLE enable)
420 {
421 const struct gpd_fan_drvdata *const drvdata = gpd_driver_priv.drvdata;
422
423 switch (enable) {
424 case DISABLE:
425 gpd_generic_write_pwm(255);
426 gpd_ecram_write(drvdata->manual_control_enable, 1);
427 break;
428 case MANUAL:
429 gpd_generic_write_pwm(gpd_driver_priv.pwm_value);
430 gpd_ecram_write(drvdata->manual_control_enable, 1);
431 break;
432 case AUTOMATIC:
433 gpd_ecram_write(drvdata->manual_control_enable, 0);
434 break;
435 }
436 }
437
438 // Write value for pwm1_enable
gpd_set_pwm_enable(enum FAN_PWM_ENABLE enable)439 static void gpd_set_pwm_enable(enum FAN_PWM_ENABLE enable)
440 {
441 if (enable == MANUAL)
442 // Set pwm_value to max firstly when switching to manual mode, in
443 // consideration of device safety.
444 gpd_driver_priv.pwm_value = 255;
445
446 switch (gpd_driver_priv.drvdata->board) {
447 case win_mini:
448 case win4_6800u:
449 gpd_win_mini_set_pwm_enable(enable);
450 break;
451 case duo:
452 gpd_duo_set_pwm_enable(enable);
453 break;
454 case win_max_2:
455 gpd_wm2_set_pwm_enable(enable);
456 break;
457 }
458 }
459
gpd_fan_hwmon_is_visible(__always_unused const void * drvdata,enum hwmon_sensor_types type,u32 attr,__always_unused int channel)460 static umode_t gpd_fan_hwmon_is_visible(__always_unused const void *drvdata,
461 enum hwmon_sensor_types type, u32 attr,
462 __always_unused int channel)
463 {
464 if (type == hwmon_fan && attr == hwmon_fan_input) {
465 return 0444;
466 } else if (type == hwmon_pwm) {
467 switch (attr) {
468 case hwmon_pwm_enable:
469 case hwmon_pwm_input:
470 return 0644;
471 default:
472 return 0;
473 }
474 }
475 return 0;
476 }
477
gpd_fan_hwmon_read(__always_unused struct device * dev,enum hwmon_sensor_types type,u32 attr,__always_unused int channel,long * val)478 static int gpd_fan_hwmon_read(__always_unused struct device *dev,
479 enum hwmon_sensor_types type, u32 attr,
480 __always_unused int channel, long *val)
481 {
482 int ret;
483
484 ret = mutex_lock_interruptible(&gpd_fan_sequence_lock);
485 if (ret)
486 return ret;
487
488 if (type == hwmon_fan) {
489 if (attr == hwmon_fan_input) {
490 ret = gpd_read_rpm();
491
492 if (ret < 0)
493 goto OUT;
494
495 *val = ret;
496 ret = 0;
497 goto OUT;
498 }
499 } else if (type == hwmon_pwm) {
500 switch (attr) {
501 case hwmon_pwm_enable:
502 *val = gpd_driver_priv.pwm_enable;
503 ret = 0;
504 goto OUT;
505 case hwmon_pwm_input:
506 ret = gpd_read_pwm();
507
508 if (ret < 0)
509 goto OUT;
510
511 *val = ret;
512 ret = 0;
513 goto OUT;
514 }
515 }
516
517 ret = -EOPNOTSUPP;
518
519 OUT:
520 mutex_unlock(&gpd_fan_sequence_lock);
521 return ret;
522 }
523
gpd_fan_hwmon_write(__always_unused struct device * dev,enum hwmon_sensor_types type,u32 attr,__always_unused int channel,long val)524 static int gpd_fan_hwmon_write(__always_unused struct device *dev,
525 enum hwmon_sensor_types type, u32 attr,
526 __always_unused int channel, long val)
527 {
528 int ret;
529
530 ret = mutex_lock_interruptible(&gpd_fan_sequence_lock);
531 if (ret)
532 return ret;
533
534 if (type == hwmon_pwm) {
535 switch (attr) {
536 case hwmon_pwm_enable:
537 if (!in_range(val, 0, 3)) {
538 ret = -EINVAL;
539 goto OUT;
540 }
541
542 gpd_driver_priv.pwm_enable = val;
543
544 gpd_set_pwm_enable(gpd_driver_priv.pwm_enable);
545 ret = 0;
546 goto OUT;
547 case hwmon_pwm_input:
548 if (!in_range(val, 0, 256)) {
549 ret = -ERANGE;
550 goto OUT;
551 }
552
553 gpd_driver_priv.pwm_value = val;
554
555 ret = gpd_write_pwm(val);
556 goto OUT;
557 }
558 }
559
560 ret = -EOPNOTSUPP;
561
562 OUT:
563 mutex_unlock(&gpd_fan_sequence_lock);
564 return ret;
565 }
566
567 static const struct hwmon_ops gpd_fan_ops = {
568 .is_visible = gpd_fan_hwmon_is_visible,
569 .read = gpd_fan_hwmon_read,
570 .write = gpd_fan_hwmon_write,
571 };
572
573 static const struct hwmon_channel_info *gpd_fan_hwmon_channel_info[] = {
574 HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT),
575 HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT | HWMON_PWM_ENABLE),
576 NULL
577 };
578
579 static struct hwmon_chip_info gpd_fan_chip_info = {
580 .ops = &gpd_fan_ops,
581 .info = gpd_fan_hwmon_channel_info
582 };
583
gpd_win4_init_ec(void)584 static void gpd_win4_init_ec(void)
585 {
586 u8 chip_id, chip_ver;
587
588 gpd_ecram_read(0x2000, &chip_id);
589
590 if (chip_id == 0x55) {
591 gpd_ecram_read(0x1060, &chip_ver);
592 gpd_ecram_write(0x1060, chip_ver | 0x80);
593 }
594 }
595
gpd_init_ec(void)596 static void gpd_init_ec(void)
597 {
598 // The buggy firmware won't initialize EC properly on boot.
599 // Before its initialization, reading RPM will always return 0,
600 // and writing PWM will have no effect.
601 // Initialize it manually on driver load.
602 if (gpd_driver_priv.drvdata->board == win4_6800u)
603 gpd_win4_init_ec();
604 }
605
gpd_fan_probe(struct platform_device * pdev)606 static int gpd_fan_probe(struct platform_device *pdev)
607 {
608 struct device *dev = &pdev->dev;
609 const struct resource *region;
610 const struct resource *res;
611 const struct device *hwdev;
612
613 res = platform_get_resource(pdev, IORESOURCE_IO, 0);
614 if (!res)
615 return dev_err_probe(dev, -EINVAL,
616 "Failed to get platform resource\n");
617
618 region = devm_request_region(dev, res->start,
619 resource_size(res), DRIVER_NAME);
620 if (!region)
621 return dev_err_probe(dev, -EBUSY,
622 "Failed to request region\n");
623
624 hwdev = devm_hwmon_device_register_with_info(dev,
625 DRIVER_NAME,
626 NULL,
627 &gpd_fan_chip_info,
628 NULL);
629 if (IS_ERR(hwdev))
630 return dev_err_probe(dev, PTR_ERR(hwdev),
631 "Failed to register hwmon device\n");
632
633 gpd_init_ec();
634
635 return 0;
636 }
637
gpd_fan_remove(__always_unused struct platform_device * pdev)638 static void gpd_fan_remove(__always_unused struct platform_device *pdev)
639 {
640 gpd_driver_priv.pwm_enable = AUTOMATIC;
641 gpd_set_pwm_enable(AUTOMATIC);
642 }
643
644 static struct platform_driver gpd_fan_driver = {
645 .probe = gpd_fan_probe,
646 .remove = gpd_fan_remove,
647 .driver = {
648 .name = KBUILD_MODNAME,
649 },
650 };
651
652 static struct platform_device *gpd_fan_platform_device;
653
gpd_fan_init(void)654 static int __init gpd_fan_init(void)
655 {
656 const struct gpd_fan_drvdata *match = NULL;
657
658 for (const struct gpd_fan_drvdata **p = gpd_module_drvdata; *p; p++) {
659 if (strcmp(gpd_fan_board, (*p)->board_name) == 0) {
660 match = *p;
661 break;
662 }
663 }
664
665 if (!match) {
666 const struct dmi_system_id *dmi_match =
667 dmi_first_match(dmi_table);
668 if (dmi_match)
669 match = dmi_match->driver_data;
670 }
671
672 if (!match)
673 return -ENODEV;
674
675 gpd_driver_priv.pwm_enable = AUTOMATIC;
676 gpd_driver_priv.pwm_value = 255;
677 gpd_driver_priv.drvdata = match;
678
679 struct resource gpd_fan_resources[] = {
680 {
681 .start = match->addr_port,
682 .end = match->data_port,
683 .flags = IORESOURCE_IO,
684 },
685 };
686
687 gpd_fan_platform_device = platform_create_bundle(&gpd_fan_driver,
688 gpd_fan_probe,
689 gpd_fan_resources,
690 1, NULL, 0);
691
692 if (IS_ERR(gpd_fan_platform_device)) {
693 pr_warn("Failed to create platform device\n");
694 return PTR_ERR(gpd_fan_platform_device);
695 }
696
697 return 0;
698 }
699
gpd_fan_exit(void)700 static void __exit gpd_fan_exit(void)
701 {
702 platform_device_unregister(gpd_fan_platform_device);
703 platform_driver_unregister(&gpd_fan_driver);
704 }
705
706 MODULE_DEVICE_TABLE(dmi, dmi_table);
707
708 module_init(gpd_fan_init);
709 module_exit(gpd_fan_exit);
710
711 MODULE_LICENSE("GPL");
712 MODULE_AUTHOR("Cryolitia PukNgae <cryolitia@uniontech.com>");
713 MODULE_DESCRIPTION("GPD Devices fan control driver");
714