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