Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

hwmon: add GPD devices sensor driver

Sensors driver for GPD Handhelds that expose fan reading and control via
hwmon sysfs.

Shenzhen GPD Technology Co., Ltd. manufactures a series of handheld
devices. This driver implements these functions through x86 port-mapped
IO.

Tested-by: Marcin Strągowski <marcin@stragowski.com>
Tested-by: someone5678 <someone5678.dev@gmail.com>
Tested-by: Justin Weiss <justin@justinweiss.com>
Tested-by: Antheas Kapenekakis <lkml@antheas.dev>
Tested-by: command_block <mtf@ik.me>
Tested-by: derjohn <himself@derjohn.de>
Tested-by: Crashdummyy <crashdummy1337@proton.me>
Signed-off-by: Cryolitia PukNgae <cryolitia@uniontech.com>
Link: https://lore.kernel.org/r/20250908-gpd_fan-v9-1-7b4506c03953@uniontech.com
Signed-off-by: Guenter Roeck <linux@roeck-us.net>

authored by

Cryolitia PukNgae and committed by
Guenter Roeck
0ab88e23 a0cce093

+732
+6
MAINTAINERS
··· 10421 10421 F: include/dt-bindings/clock/google,gs101.h 10422 10422 K: [gG]oogle.?[tT]ensor 10423 10423 10424 + GPD FAN DRIVER 10425 + M: Cryolitia PukNgae <cryolitia@uniontech.com> 10426 + L: linux-hwmon@vger.kernel.org 10427 + S: Maintained 10428 + F: drivers/hwmon/gpd-fan.c 10429 + 10424 10430 GPD POCKET FAN DRIVER 10425 10431 M: Hans de Goede <hansg@kernel.org> 10426 10432 L: platform-driver-x86@vger.kernel.org
+10
drivers/hwmon/Kconfig
··· 769 769 This driver can also be built as a module. If so, the module 770 770 will be called gl520sm. 771 771 772 + config SENSORS_GPD 773 + tristate "GPD handhelds" 774 + depends on X86 775 + help 776 + If you say yes here you get support for fan readings and 777 + control over GPD handheld devices. 778 + 779 + Can also be built as a module. In that case it will be 780 + called gpd-fan. 781 + 772 782 config SENSORS_G760A 773 783 tristate "GMT G760A" 774 784 depends on I2C
+1
drivers/hwmon/Makefile
··· 88 88 obj-$(CONFIG_SENSORS_GL518SM) += gl518sm.o 89 89 obj-$(CONFIG_SENSORS_GL520SM) += gl520sm.o 90 90 obj-$(CONFIG_SENSORS_GSC) += gsc-hwmon.o 91 + obj-$(CONFIG_SENSORS_GPD) += gpd-fan.o 91 92 obj-$(CONFIG_SENSORS_GPIO_FAN) += gpio-fan.o 92 93 obj-$(CONFIG_SENSORS_GXP_FAN_CTRL) += gxp-fan-ctrl.o 93 94 obj-$(CONFIG_SENSORS_HIH6130) += hih6130.o
+715
drivers/hwmon/gpd-fan.c
··· 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/acpi.h> 16 + #include <linux/dmi.h> 17 + #include <linux/hwmon.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 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 + 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 + 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 + 279 + static void gpd_win4_init_ec(void) 280 + { 281 + u8 chip_id, chip_ver; 282 + 283 + gpd_ecram_read(0x2000, &chip_id); 284 + 285 + if (chip_id == 0x55) { 286 + gpd_ecram_read(0x1060, &chip_ver); 287 + gpd_ecram_write(0x1060, chip_ver | 0x80); 288 + } 289 + } 290 + 291 + static int gpd_win4_read_rpm(void) 292 + { 293 + int ret; 294 + 295 + ret = gpd_generic_read_rpm(); 296 + 297 + if (ret == 0) 298 + // Re-init EC when speed is 0 299 + gpd_win4_init_ec(); 300 + 301 + return ret; 302 + } 303 + 304 + static int gpd_wm2_read_rpm(void) 305 + { 306 + for (u16 pwm_ctr_offset = GPD_PWM_CTR_OFFSET; 307 + pwm_ctr_offset <= GPD_PWM_CTR_OFFSET + 2; pwm_ctr_offset++) { 308 + u8 PWMCTR; 309 + 310 + gpd_ecram_read(pwm_ctr_offset, &PWMCTR); 311 + 312 + if (PWMCTR != 0xB8) 313 + gpd_ecram_write(pwm_ctr_offset, 0xB8); 314 + } 315 + 316 + return gpd_generic_read_rpm(); 317 + } 318 + 319 + // Read value for fan1_input 320 + static int gpd_read_rpm(void) 321 + { 322 + switch (gpd_driver_priv.drvdata->board) { 323 + case win_mini: 324 + case duo: 325 + return gpd_generic_read_rpm(); 326 + case win4_6800u: 327 + return gpd_win4_read_rpm(); 328 + case win_max_2: 329 + return gpd_wm2_read_rpm(); 330 + } 331 + 332 + return 0; 333 + } 334 + 335 + static int gpd_wm2_read_pwm(void) 336 + { 337 + const struct gpd_fan_drvdata *const drvdata = gpd_driver_priv.drvdata; 338 + u8 var; 339 + 340 + gpd_ecram_read(drvdata->pwm_write, &var); 341 + 342 + // Match gpd_generic_write_pwm(u8) below 343 + return DIV_ROUND_CLOSEST((var - 1) * 255, (drvdata->pwm_max - 1)); 344 + } 345 + 346 + // Read value for pwm1 347 + static int gpd_read_pwm(void) 348 + { 349 + switch (gpd_driver_priv.drvdata->board) { 350 + case win_mini: 351 + case duo: 352 + case win4_6800u: 353 + switch (gpd_driver_priv.pwm_enable) { 354 + case DISABLE: 355 + return 255; 356 + case MANUAL: 357 + return gpd_driver_priv.pwm_value; 358 + case AUTOMATIC: 359 + return -EOPNOTSUPP; 360 + } 361 + break; 362 + case win_max_2: 363 + return gpd_wm2_read_pwm(); 364 + } 365 + return 0; 366 + } 367 + 368 + // PWM value's range in EC is 1 - pwm_max, cast 0 - 255 to it. 369 + static inline u8 gpd_cast_pwm_range(u8 val) 370 + { 371 + const struct gpd_fan_drvdata *const drvdata = gpd_driver_priv.drvdata; 372 + 373 + return DIV_ROUND_CLOSEST(val * (drvdata->pwm_max - 1), 255) + 1; 374 + } 375 + 376 + static void gpd_generic_write_pwm(u8 val) 377 + { 378 + const struct gpd_fan_drvdata *const drvdata = gpd_driver_priv.drvdata; 379 + u8 pwm_reg; 380 + 381 + pwm_reg = gpd_cast_pwm_range(val); 382 + gpd_ecram_write(drvdata->pwm_write, pwm_reg); 383 + } 384 + 385 + static void gpd_duo_write_pwm(u8 val) 386 + { 387 + const struct gpd_fan_drvdata *const drvdata = gpd_driver_priv.drvdata; 388 + u8 pwm_reg; 389 + 390 + pwm_reg = gpd_cast_pwm_range(val); 391 + gpd_ecram_write(drvdata->pwm_write, pwm_reg); 392 + gpd_ecram_write(drvdata->pwm_write + 1, pwm_reg); 393 + } 394 + 395 + // Write value for pwm1 396 + static int gpd_write_pwm(u8 val) 397 + { 398 + if (gpd_driver_priv.pwm_enable != MANUAL) 399 + return -EPERM; 400 + 401 + switch (gpd_driver_priv.drvdata->board) { 402 + case duo: 403 + gpd_duo_write_pwm(val); 404 + break; 405 + case win_mini: 406 + case win4_6800u: 407 + case win_max_2: 408 + gpd_generic_write_pwm(val); 409 + break; 410 + } 411 + 412 + return 0; 413 + } 414 + 415 + static void gpd_win_mini_set_pwm_enable(enum FAN_PWM_ENABLE pwm_enable) 416 + { 417 + switch (pwm_enable) { 418 + case DISABLE: 419 + gpd_generic_write_pwm(255); 420 + break; 421 + case MANUAL: 422 + gpd_generic_write_pwm(gpd_driver_priv.pwm_value); 423 + break; 424 + case AUTOMATIC: 425 + gpd_ecram_write(gpd_driver_priv.drvdata->pwm_write, 0); 426 + break; 427 + } 428 + } 429 + 430 + static void gpd_duo_set_pwm_enable(enum FAN_PWM_ENABLE pwm_enable) 431 + { 432 + switch (pwm_enable) { 433 + case DISABLE: 434 + gpd_duo_write_pwm(255); 435 + break; 436 + case MANUAL: 437 + gpd_duo_write_pwm(gpd_driver_priv.pwm_value); 438 + break; 439 + case AUTOMATIC: 440 + gpd_ecram_write(gpd_driver_priv.drvdata->pwm_write, 0); 441 + break; 442 + } 443 + } 444 + 445 + static void gpd_wm2_set_pwm_enable(enum FAN_PWM_ENABLE enable) 446 + { 447 + const struct gpd_fan_drvdata *const drvdata = gpd_driver_priv.drvdata; 448 + 449 + switch (enable) { 450 + case DISABLE: 451 + gpd_generic_write_pwm(255); 452 + gpd_ecram_write(drvdata->manual_control_enable, 1); 453 + break; 454 + case MANUAL: 455 + gpd_generic_write_pwm(gpd_driver_priv.pwm_value); 456 + gpd_ecram_write(drvdata->manual_control_enable, 1); 457 + break; 458 + case AUTOMATIC: 459 + gpd_ecram_write(drvdata->manual_control_enable, 0); 460 + break; 461 + } 462 + } 463 + 464 + // Write value for pwm1_enable 465 + static void gpd_set_pwm_enable(enum FAN_PWM_ENABLE enable) 466 + { 467 + if (enable == MANUAL) 468 + // Set pwm_value to max firstly when switching to manual mode, in 469 + // consideration of device safety. 470 + gpd_driver_priv.pwm_value = 255; 471 + 472 + switch (gpd_driver_priv.drvdata->board) { 473 + case win_mini: 474 + case win4_6800u: 475 + gpd_win_mini_set_pwm_enable(enable); 476 + break; 477 + case duo: 478 + gpd_duo_set_pwm_enable(enable); 479 + break; 480 + case win_max_2: 481 + gpd_wm2_set_pwm_enable(enable); 482 + break; 483 + } 484 + } 485 + 486 + static umode_t gpd_fan_hwmon_is_visible(__always_unused const void *drvdata, 487 + enum hwmon_sensor_types type, u32 attr, 488 + __always_unused int channel) 489 + { 490 + if (type == hwmon_fan && attr == hwmon_fan_input) { 491 + return 0444; 492 + } else if (type == hwmon_pwm) { 493 + switch (attr) { 494 + case hwmon_pwm_enable: 495 + case hwmon_pwm_input: 496 + return 0644; 497 + default: 498 + return 0; 499 + } 500 + } 501 + return 0; 502 + } 503 + 504 + static int gpd_fan_hwmon_read(__always_unused struct device *dev, 505 + enum hwmon_sensor_types type, u32 attr, 506 + __always_unused int channel, long *val) 507 + { 508 + int ret; 509 + 510 + ret = mutex_lock_interruptible(&gpd_fan_sequence_lock); 511 + if (ret) 512 + return ret; 513 + 514 + if (type == hwmon_fan) { 515 + if (attr == hwmon_fan_input) { 516 + ret = gpd_read_rpm(); 517 + 518 + if (ret < 0) 519 + goto OUT; 520 + 521 + *val = ret; 522 + ret = 0; 523 + goto OUT; 524 + } 525 + } else if (type == hwmon_pwm) { 526 + switch (attr) { 527 + case hwmon_pwm_enable: 528 + *val = gpd_driver_priv.pwm_enable; 529 + ret = 0; 530 + goto OUT; 531 + case hwmon_pwm_input: 532 + ret = gpd_read_pwm(); 533 + 534 + if (ret < 0) 535 + goto OUT; 536 + 537 + *val = ret; 538 + ret = 0; 539 + goto OUT; 540 + } 541 + } 542 + 543 + ret = -EOPNOTSUPP; 544 + 545 + OUT: 546 + mutex_unlock(&gpd_fan_sequence_lock); 547 + return ret; 548 + } 549 + 550 + static int gpd_fan_hwmon_write(__always_unused struct device *dev, 551 + enum hwmon_sensor_types type, u32 attr, 552 + __always_unused int channel, long val) 553 + { 554 + int ret; 555 + 556 + ret = mutex_lock_interruptible(&gpd_fan_sequence_lock); 557 + if (ret) 558 + return ret; 559 + 560 + if (type == hwmon_pwm) { 561 + switch (attr) { 562 + case hwmon_pwm_enable: 563 + if (!in_range(val, 0, 3)) { 564 + ret = -EINVAL; 565 + goto OUT; 566 + } 567 + 568 + gpd_driver_priv.pwm_enable = val; 569 + 570 + gpd_set_pwm_enable(gpd_driver_priv.pwm_enable); 571 + ret = 0; 572 + goto OUT; 573 + case hwmon_pwm_input: 574 + if (!in_range(val, 0, 255)) { 575 + ret = -ERANGE; 576 + goto OUT; 577 + } 578 + 579 + gpd_driver_priv.pwm_value = val; 580 + 581 + ret = gpd_write_pwm(val); 582 + goto OUT; 583 + } 584 + } 585 + 586 + ret = -EOPNOTSUPP; 587 + 588 + OUT: 589 + mutex_unlock(&gpd_fan_sequence_lock); 590 + return ret; 591 + } 592 + 593 + static const struct hwmon_ops gpd_fan_ops = { 594 + .is_visible = gpd_fan_hwmon_is_visible, 595 + .read = gpd_fan_hwmon_read, 596 + .write = gpd_fan_hwmon_write, 597 + }; 598 + 599 + static const struct hwmon_channel_info *gpd_fan_hwmon_channel_info[] = { 600 + HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT), 601 + HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT | HWMON_PWM_ENABLE), 602 + NULL 603 + }; 604 + 605 + static struct hwmon_chip_info gpd_fan_chip_info = { 606 + .ops = &gpd_fan_ops, 607 + .info = gpd_fan_hwmon_channel_info 608 + }; 609 + 610 + static int gpd_fan_probe(struct platform_device *pdev) 611 + { 612 + struct device *dev = &pdev->dev; 613 + const struct resource *region; 614 + const struct resource *res; 615 + const struct device *hwdev; 616 + 617 + res = platform_get_resource(pdev, IORESOURCE_IO, 0); 618 + if (IS_ERR(res)) 619 + return dev_err_probe(dev, PTR_ERR(res), 620 + "Failed to get platform resource\n"); 621 + 622 + region = devm_request_region(dev, res->start, 623 + resource_size(res), DRIVER_NAME); 624 + if (IS_ERR(region)) 625 + return dev_err_probe(dev, PTR_ERR(region), 626 + "Failed to request region\n"); 627 + 628 + hwdev = devm_hwmon_device_register_with_info(dev, 629 + DRIVER_NAME, 630 + NULL, 631 + &gpd_fan_chip_info, 632 + NULL); 633 + if (IS_ERR(hwdev)) 634 + return dev_err_probe(dev, PTR_ERR(region), 635 + "Failed to register hwmon device\n"); 636 + 637 + return 0; 638 + } 639 + 640 + static void gpd_fan_remove(__always_unused struct platform_device *pdev) 641 + { 642 + gpd_driver_priv.pwm_enable = AUTOMATIC; 643 + gpd_set_pwm_enable(AUTOMATIC); 644 + } 645 + 646 + static struct platform_driver gpd_fan_driver = { 647 + .probe = gpd_fan_probe, 648 + .remove = gpd_fan_remove, 649 + .driver = { 650 + .name = KBUILD_MODNAME, 651 + }, 652 + }; 653 + 654 + static struct platform_device *gpd_fan_platform_device; 655 + 656 + static int __init gpd_fan_init(void) 657 + { 658 + const struct gpd_fan_drvdata *match = NULL; 659 + 660 + for (const struct gpd_fan_drvdata **p = gpd_module_drvdata; *p; p++) { 661 + if (strcmp(gpd_fan_board, (*p)->board_name) == 0) { 662 + match = *p; 663 + break; 664 + } 665 + } 666 + 667 + if (!match) { 668 + const struct dmi_system_id *dmi_match = 669 + dmi_first_match(dmi_table); 670 + if (dmi_match) 671 + match = dmi_match->driver_data; 672 + } 673 + 674 + if (!match) 675 + return -ENODEV; 676 + 677 + gpd_driver_priv.pwm_enable = AUTOMATIC; 678 + gpd_driver_priv.pwm_value = 255; 679 + gpd_driver_priv.drvdata = match; 680 + 681 + struct resource gpd_fan_resources[] = { 682 + { 683 + .start = match->addr_port, 684 + .end = match->data_port, 685 + .flags = IORESOURCE_IO, 686 + }, 687 + }; 688 + 689 + gpd_fan_platform_device = platform_create_bundle(&gpd_fan_driver, 690 + gpd_fan_probe, 691 + gpd_fan_resources, 692 + 1, NULL, 0); 693 + 694 + if (IS_ERR(gpd_fan_platform_device)) { 695 + pr_warn("Failed to create platform device\n"); 696 + return PTR_ERR(gpd_fan_platform_device); 697 + } 698 + 699 + return 0; 700 + } 701 + 702 + static void __exit gpd_fan_exit(void) 703 + { 704 + platform_device_unregister(gpd_fan_platform_device); 705 + platform_driver_unregister(&gpd_fan_driver); 706 + } 707 + 708 + MODULE_DEVICE_TABLE(dmi, dmi_table); 709 + 710 + module_init(gpd_fan_init); 711 + module_exit(gpd_fan_exit); 712 + 713 + MODULE_LICENSE("GPL"); 714 + MODULE_AUTHOR("Cryolitia PukNgae <cryolitia@uniontech.com>"); 715 + MODULE_DESCRIPTION("GPD Devices fan control driver");