1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * huawei-gaokun-battery - A power supply driver for HUAWEI Matebook E Go 4 * 5 * Copyright (C) 2024 Pengyu Luo <mitltlatltl@gmail.com> 6 */ 7 8 #include <linux/auxiliary_bus.h> 9 #include <linux/bits.h> 10 #include <linux/delay.h> 11 #include <linux/jiffies.h> 12 #include <linux/module.h> 13 #include <linux/notifier.h> 14 #include <linux/platform_data/huawei-gaokun-ec.h> 15 #include <linux/power_supply.h> 16 #include <linux/sprintf.h> 17 18 /* -------------------------------------------------------------------------- */ 19 /* String Data Reg */ 20 21 #define EC_BAT_VENDOR 0x01 /* from 0x01 to 0x0F, SUNWODA */ 22 #define EC_BAT_MODEL 0x11 /* from 0x11 to 0x1F, HB30A8P9ECW-22T */ 23 24 #define EC_ADP_STATUS 0x81 25 #define EC_AC_STATUS BIT(0) 26 #define EC_BAT_PRESENT BIT(1) /* BATC._STA */ 27 28 #define EC_BAT_STATUS 0x82 /* _BST */ 29 #define EC_BAT_DISCHARGING BIT(0) 30 #define EC_BAT_CHARGING BIT(1) 31 #define EC_BAT_CRITICAL BIT(2) /* Low Battery Level */ 32 #define EC_BAT_FULL BIT(3) 33 34 /* -------------------------------------------------------------------------- */ 35 /* Word Data Reg */ 36 37 /* 0x5A: ? 38 * 0x5C: ? 39 * 0x5E: ? 40 * 0X60: ? 41 * 0x84: ? 42 */ 43 44 #define EC_BAT_STATUS_START 0x90 45 #define EC_BAT_PERCENTAGE 0x90 46 #define EC_BAT_VOLTAGE 0x92 47 #define EC_BAT_CAPACITY 0x94 48 #define EC_BAT_FULL_CAPACITY 0x96 49 /* 0x98: ? */ 50 #define EC_BAT_CURRENT 0x9A 51 /* 0x9C: ? */ 52 53 #define EC_BAT_INFO_START 0xA0 54 /* 0xA0: POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT? */ 55 #define EC_BAT_DESIGN_CAPACITY 0xA2 56 #define EC_BAT_DESIGN_VOLTAGE 0xA4 57 #define EC_BAT_SERIAL_NUMBER 0xA6 58 #define EC_BAT_CYCLE_COUNT 0xAA 59 60 /* -------------------------------------------------------------------------- */ 61 /* Battery Event ID */ 62 63 #define EC_EVENT_BAT_A0 0xA0 64 #define EC_EVENT_BAT_A1 0xA1 65 #define EC_EVENT_BAT_A2 0xA2 66 #define EC_EVENT_BAT_A3 0xA3 67 #define EC_EVENT_BAT_B1 0xB1 68 /* EVENT B1 A0 A1 repeat about every 1s 2s 3s respectively */ 69 70 /* ACPI _BIX field, Min sampling time, the duration between two _BST */ 71 #define CACHE_TIME 2000 /* cache time in milliseconds */ 72 73 #define MILLI_TO_MICRO 1000 74 75 #define SMART_CHARGE_MODE 0 76 #define SMART_CHARGE_DELAY 1 77 #define SMART_CHARGE_START 2 78 #define SMART_CHARGE_END 3 79 80 #define NO_DELAY_MODE 1 81 #define DELAY_MODE 4 82 83 struct gaokun_psy_bat_status { 84 __le16 percentage_now; /* 0x90 */ 85 __le16 voltage_now; 86 __le16 capacity_now; 87 __le16 full_capacity; 88 __le16 unknown1; 89 __le16 rate_now; 90 __le16 unknown2; /* 0x9C */ 91 } __packed; 92 93 struct gaokun_psy_bat_info { 94 __le16 unknown3; /* 0xA0 */ 95 __le16 design_capacity; 96 __le16 design_voltage; 97 __le16 serial_number; 98 __le16 padding2; 99 __le16 cycle_count; /* 0xAA */ 100 } __packed; 101 102 struct gaokun_psy { 103 struct gaokun_ec *ec; 104 struct device *dev; 105 struct notifier_block nb; 106 107 struct power_supply *bat_psy; 108 struct power_supply *adp_psy; 109 110 unsigned long update_time; 111 struct gaokun_psy_bat_status status; 112 struct gaokun_psy_bat_info info; 113 114 char battery_model[0x10]; /* HB30A8P9ECW-22T, the real one is XXX-22A */ 115 char battery_serial[0x10]; 116 char battery_vendor[0x10]; 117 118 int charge_now; 119 int online; 120 int bat_present; 121 }; 122 123 /* -------------------------------------------------------------------------- */ 124 /* Adapter */ 125 126 static int gaokun_psy_get_adp_status(struct gaokun_psy *ecbat) 127 { 128 /* _PSR */ 129 int ret; 130 u8 online; 131 132 ret = gaokun_ec_psy_read_byte(ecbat->ec, EC_ADP_STATUS, &online); 133 if (ret) 134 return ret; 135 136 ecbat->online = !!(online & EC_AC_STATUS); 137 138 return 0; 139 } 140 141 static int gaokun_psy_get_adp_property(struct power_supply *psy, 142 enum power_supply_property psp, 143 union power_supply_propval *val) 144 { 145 struct gaokun_psy *ecbat = power_supply_get_drvdata(psy); 146 int ret; 147 148 ret = gaokun_psy_get_adp_status(ecbat); 149 if (ret) 150 return ret; 151 152 switch (psp) { 153 case POWER_SUPPLY_PROP_ONLINE: 154 val->intval = ecbat->online; 155 break; 156 case POWER_SUPPLY_PROP_USB_TYPE: 157 val->intval = POWER_SUPPLY_USB_TYPE_C; 158 break; 159 default: 160 return -EINVAL; 161 } 162 163 return 0; 164 } 165 166 static enum power_supply_property gaokun_psy_adp_props[] = { 167 POWER_SUPPLY_PROP_ONLINE, 168 POWER_SUPPLY_PROP_USB_TYPE, 169 }; 170 171 static const struct power_supply_desc gaokun_psy_adp_desc = { 172 .name = "gaokun-ec-adapter", 173 .type = POWER_SUPPLY_TYPE_USB, 174 .usb_types = BIT(POWER_SUPPLY_USB_TYPE_C), 175 .get_property = gaokun_psy_get_adp_property, 176 .properties = gaokun_psy_adp_props, 177 .num_properties = ARRAY_SIZE(gaokun_psy_adp_props), 178 }; 179 180 /* -------------------------------------------------------------------------- */ 181 /* Battery */ 182 183 static inline void gaokun_psy_get_bat_present(struct gaokun_psy *ecbat) 184 { 185 int ret; 186 u8 present; 187 188 /* Some kind of initialization */ 189 gaokun_ec_write(ecbat->ec, (u8 []){0x02, 0xB2, 1, 0x90}); 190 191 ret = gaokun_ec_psy_read_byte(ecbat->ec, EC_ADP_STATUS, &present); 192 193 ecbat->bat_present = ret ? false : !!(present & EC_BAT_PRESENT); 194 } 195 196 static inline int gaokun_psy_bat_present(struct gaokun_psy *ecbat) 197 { 198 return ecbat->bat_present; 199 } 200 201 static int gaokun_psy_get_bat_info(struct gaokun_psy *ecbat) 202 { 203 /* _BIX */ 204 if (!gaokun_psy_bat_present(ecbat)) 205 return 0; 206 207 return gaokun_ec_psy_multi_read(ecbat->ec, EC_BAT_INFO_START, 208 sizeof(ecbat->info), (u8 *)&ecbat->info); 209 } 210 211 static void gaokun_psy_update_bat_charge(struct gaokun_psy *ecbat) 212 { 213 u8 charge; 214 215 gaokun_ec_psy_read_byte(ecbat->ec, EC_BAT_STATUS, &charge); 216 217 switch (charge) { 218 case EC_BAT_CHARGING: 219 ecbat->charge_now = POWER_SUPPLY_STATUS_CHARGING; 220 break; 221 case EC_BAT_DISCHARGING: 222 ecbat->charge_now = POWER_SUPPLY_STATUS_DISCHARGING; 223 break; 224 case EC_BAT_FULL: 225 ecbat->charge_now = POWER_SUPPLY_STATUS_FULL; 226 break; 227 default: 228 dev_warn(ecbat->dev, "unknown charge state %d\n", charge); 229 } 230 } 231 232 static int gaokun_psy_get_bat_status(struct gaokun_psy *ecbat) 233 { 234 /* _BST */ 235 int ret; 236 237 if (time_before(jiffies, ecbat->update_time + 238 msecs_to_jiffies(CACHE_TIME))) 239 return 0; 240 241 gaokun_psy_update_bat_charge(ecbat); 242 ret = gaokun_ec_psy_multi_read(ecbat->ec, EC_BAT_STATUS_START, 243 sizeof(ecbat->status), (u8 *)&ecbat->status); 244 245 ecbat->update_time = jiffies; 246 247 return ret; 248 } 249 250 static void gaokun_psy_init(struct gaokun_psy *ecbat) 251 { 252 gaokun_psy_get_bat_present(ecbat); 253 if (!gaokun_psy_bat_present(ecbat)) 254 return; 255 256 gaokun_psy_get_bat_info(ecbat); 257 258 snprintf(ecbat->battery_serial, sizeof(ecbat->battery_serial), 259 "%d", le16_to_cpu(ecbat->info.serial_number)); 260 261 gaokun_ec_psy_multi_read(ecbat->ec, EC_BAT_VENDOR, 262 sizeof(ecbat->battery_vendor) - 1, 263 ecbat->battery_vendor); 264 265 gaokun_ec_psy_multi_read(ecbat->ec, EC_BAT_MODEL, 266 sizeof(ecbat->battery_model) - 1, 267 ecbat->battery_model); 268 269 ecbat->battery_model[14] = 'A'; /* FIX UP */ 270 } 271 272 static int gaokun_psy_get_bat_property(struct power_supply *psy, 273 enum power_supply_property psp, 274 union power_supply_propval *val) 275 { 276 struct gaokun_psy *ecbat = power_supply_get_drvdata(psy); 277 u8 buf[GAOKUN_SMART_CHARGE_DATA_SIZE]; 278 int ret; 279 280 if (gaokun_psy_bat_present(ecbat)) 281 gaokun_psy_get_bat_status(ecbat); 282 else if (psp != POWER_SUPPLY_PROP_PRESENT) 283 return -ENODEV; 284 285 switch (psp) { 286 case POWER_SUPPLY_PROP_STATUS: 287 val->intval = ecbat->charge_now; 288 break; 289 290 case POWER_SUPPLY_PROP_PRESENT: 291 val->intval = ecbat->bat_present; 292 break; 293 294 case POWER_SUPPLY_PROP_TECHNOLOGY: 295 val->intval = POWER_SUPPLY_TECHNOLOGY_LION; 296 break; 297 298 case POWER_SUPPLY_PROP_CYCLE_COUNT: 299 val->intval = le16_to_cpu(ecbat->info.cycle_count); 300 break; 301 302 case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: 303 val->intval = le16_to_cpu(ecbat->info.design_voltage) * MILLI_TO_MICRO; 304 break; 305 306 case POWER_SUPPLY_PROP_VOLTAGE_NOW: 307 val->intval = le16_to_cpu(ecbat->status.voltage_now) * MILLI_TO_MICRO; 308 break; 309 310 case POWER_SUPPLY_PROP_CURRENT_NOW: 311 val->intval = (s16)le16_to_cpu(ecbat->status.rate_now) * MILLI_TO_MICRO; 312 break; 313 314 case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: 315 val->intval = le16_to_cpu(ecbat->info.design_capacity) * MILLI_TO_MICRO; 316 break; 317 318 case POWER_SUPPLY_PROP_CHARGE_FULL: 319 val->intval = le16_to_cpu(ecbat->status.full_capacity) * MILLI_TO_MICRO; 320 break; 321 322 case POWER_SUPPLY_PROP_CHARGE_NOW: 323 val->intval = le16_to_cpu(ecbat->status.capacity_now) * MILLI_TO_MICRO; 324 break; 325 326 case POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD: 327 case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD: 328 ret = gaokun_ec_psy_get_smart_charge(ecbat->ec, buf); 329 if (ret) 330 return ret; 331 332 if (psp == POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD) 333 val->intval = buf[SMART_CHARGE_START]; 334 else 335 val->intval = buf[SMART_CHARGE_END]; 336 break; 337 338 case POWER_SUPPLY_PROP_CAPACITY: 339 val->intval = le16_to_cpu(ecbat->status.percentage_now); 340 break; 341 342 case POWER_SUPPLY_PROP_MODEL_NAME: 343 val->strval = ecbat->battery_model; 344 break; 345 346 case POWER_SUPPLY_PROP_MANUFACTURER: 347 val->strval = ecbat->battery_vendor; 348 break; 349 350 case POWER_SUPPLY_PROP_SERIAL_NUMBER: 351 val->strval = ecbat->battery_serial; 352 break; 353 354 default: 355 return -EINVAL; 356 } 357 358 return 0; 359 } 360 361 static int gaokun_psy_set_bat_property(struct power_supply *psy, 362 enum power_supply_property psp, 363 const union power_supply_propval *val) 364 { 365 struct gaokun_psy *ecbat = power_supply_get_drvdata(psy); 366 u8 buf[GAOKUN_SMART_CHARGE_DATA_SIZE]; 367 int ret; 368 369 if (!gaokun_psy_bat_present(ecbat)) 370 return -ENODEV; 371 372 ret = gaokun_ec_psy_get_smart_charge(ecbat->ec, buf); 373 if (ret) 374 return ret; 375 376 switch (psp) { 377 /* 378 * Resetting another thershold makes single thersold setting more likely 379 * to succeed. But setting start = end makes thing strange(failure). 380 */ 381 case POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD: 382 buf[SMART_CHARGE_START] = val->intval; 383 if (buf[SMART_CHARGE_START] > buf[SMART_CHARGE_END]) 384 buf[SMART_CHARGE_END] = buf[SMART_CHARGE_START] + 1; 385 return gaokun_ec_psy_set_smart_charge(ecbat->ec, buf); 386 387 case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD: 388 buf[SMART_CHARGE_END] = val->intval; 389 if (buf[SMART_CHARGE_END] < buf[SMART_CHARGE_START]) 390 buf[SMART_CHARGE_START] = buf[SMART_CHARGE_END] - 1; 391 return gaokun_ec_psy_set_smart_charge(ecbat->ec, buf); 392 393 default: 394 return -EINVAL; 395 } 396 397 return 0; 398 } 399 400 static int gaokun_psy_is_bat_property_writeable(struct power_supply *psy, 401 enum power_supply_property psp) 402 { 403 return psp == POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD || 404 psp == POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD; 405 } 406 407 static enum power_supply_property gaokun_psy_bat_props[] = { 408 POWER_SUPPLY_PROP_STATUS, 409 POWER_SUPPLY_PROP_PRESENT, 410 POWER_SUPPLY_PROP_TECHNOLOGY, 411 POWER_SUPPLY_PROP_CYCLE_COUNT, 412 POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, 413 POWER_SUPPLY_PROP_VOLTAGE_NOW, 414 POWER_SUPPLY_PROP_CURRENT_NOW, 415 POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, 416 POWER_SUPPLY_PROP_CHARGE_FULL, 417 POWER_SUPPLY_PROP_CHARGE_NOW, 418 POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD, 419 POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD, 420 POWER_SUPPLY_PROP_CAPACITY, 421 POWER_SUPPLY_PROP_MODEL_NAME, 422 POWER_SUPPLY_PROP_MANUFACTURER, 423 POWER_SUPPLY_PROP_SERIAL_NUMBER, 424 }; 425 426 static const struct power_supply_desc gaokun_psy_bat_desc = { 427 .name = "gaokun-ec-battery", 428 .type = POWER_SUPPLY_TYPE_BATTERY, 429 .get_property = gaokun_psy_get_bat_property, 430 .set_property = gaokun_psy_set_bat_property, 431 .property_is_writeable = gaokun_psy_is_bat_property_writeable, 432 .properties = gaokun_psy_bat_props, 433 .num_properties = ARRAY_SIZE(gaokun_psy_bat_props), 434 }; 435 436 /* -------------------------------------------------------------------------- */ 437 /* Sysfs */ 438 439 /* 440 * Note that, HUAWEI calls them SBAC/GBAC and SBCM/GBCM in DSDT, they are likely 441 * Set/Get Battery Adaptive Charging and Set/Get Battery Charging Mode. 442 */ 443 444 /* battery adaptive charge */ 445 static ssize_t battery_adaptive_charge_show(struct device *dev, 446 struct device_attribute *attr, 447 char *buf) 448 { 449 struct power_supply *psy = to_power_supply(dev); 450 struct gaokun_psy *ecbat = power_supply_get_drvdata(psy); 451 int ret; 452 bool on; 453 454 ret = gaokun_ec_psy_get_smart_charge_enable(ecbat->ec, &on); 455 if (ret) 456 return ret; 457 458 return sysfs_emit(buf, "%d\n", on); 459 } 460 461 static ssize_t battery_adaptive_charge_store(struct device *dev, 462 struct device_attribute *attr, 463 const char *buf, size_t size) 464 { 465 struct power_supply *psy = to_power_supply(dev); 466 struct gaokun_psy *ecbat = power_supply_get_drvdata(psy); 467 int ret; 468 bool on; 469 470 if (kstrtobool(buf, &on)) 471 return -EINVAL; 472 473 ret = gaokun_ec_psy_set_smart_charge_enable(ecbat->ec, on); 474 if (ret) 475 return ret; 476 477 return size; 478 } 479 480 static DEVICE_ATTR_RW(battery_adaptive_charge); 481 482 static inline int get_charge_delay(u8 buf[GAOKUN_SMART_CHARGE_DATA_SIZE]) 483 { 484 return buf[SMART_CHARGE_MODE] == NO_DELAY_MODE ? 0 : buf[SMART_CHARGE_DELAY]; 485 } 486 487 static inline void 488 set_charge_delay(u8 buf[GAOKUN_SMART_CHARGE_DATA_SIZE], u8 delay) 489 { 490 if (delay) { 491 buf[SMART_CHARGE_DELAY] = delay; 492 buf[SMART_CHARGE_MODE] = DELAY_MODE; 493 } else { 494 /* No writing zero, there is a specific mode for it. */ 495 buf[SMART_CHARGE_MODE] = NO_DELAY_MODE; 496 } 497 } 498 499 /* Smart charge */ 500 static ssize_t smart_charge_delay_show(struct device *dev, 501 struct device_attribute *attr, 502 char *buf) 503 { 504 struct power_supply *psy = to_power_supply(dev); 505 struct gaokun_psy *ecbat = power_supply_get_drvdata(psy); 506 u8 bf[GAOKUN_SMART_CHARGE_DATA_SIZE]; 507 int ret; 508 509 ret = gaokun_ec_psy_get_smart_charge(ecbat->ec, bf); 510 if (ret) 511 return ret; 512 513 return sysfs_emit(buf, "%d\n", get_charge_delay(bf)); 514 } 515 516 static ssize_t smart_charge_delay_store(struct device *dev, 517 struct device_attribute *attr, 518 const char *buf, size_t size) 519 { 520 struct power_supply *psy = to_power_supply(dev); 521 struct gaokun_psy *ecbat = power_supply_get_drvdata(psy); 522 u8 bf[GAOKUN_SMART_CHARGE_DATA_SIZE]; 523 u8 delay; 524 int ret; 525 526 if (kstrtou8(buf, 10, &delay)) 527 return -EINVAL; 528 529 ret = gaokun_ec_psy_get_smart_charge(ecbat->ec, bf); 530 if (ret) 531 return ret; 532 533 set_charge_delay(bf, delay); 534 535 ret = gaokun_ec_psy_set_smart_charge(ecbat->ec, bf); 536 if (ret) 537 return ret; 538 539 return size; 540 } 541 542 static DEVICE_ATTR_RW(smart_charge_delay); 543 544 static struct attribute *gaokun_psy_features_attrs[] = { 545 &dev_attr_battery_adaptive_charge.attr, 546 &dev_attr_smart_charge_delay.attr, 547 NULL, 548 }; 549 ATTRIBUTE_GROUPS(gaokun_psy_features); 550 551 static int gaokun_psy_notify(struct notifier_block *nb, 552 unsigned long action, void *data) 553 { 554 struct gaokun_psy *ecbat = container_of(nb, struct gaokun_psy, nb); 555 556 switch (action) { 557 case EC_EVENT_BAT_A2: 558 case EC_EVENT_BAT_B1: 559 gaokun_psy_get_bat_info(ecbat); 560 return NOTIFY_OK; 561 562 case EC_EVENT_BAT_A0: 563 gaokun_psy_get_adp_status(ecbat); 564 power_supply_changed(ecbat->adp_psy); 565 msleep(10); 566 fallthrough; 567 568 case EC_EVENT_BAT_A1: 569 case EC_EVENT_BAT_A3: 570 if (action == EC_EVENT_BAT_A3) { 571 gaokun_psy_get_bat_info(ecbat); 572 msleep(100); 573 } 574 gaokun_psy_get_bat_status(ecbat); 575 power_supply_changed(ecbat->bat_psy); 576 return NOTIFY_OK; 577 578 default: 579 return NOTIFY_DONE; 580 } 581 } 582 583 static int gaokun_psy_probe(struct auxiliary_device *adev, 584 const struct auxiliary_device_id *id) 585 { 586 struct gaokun_ec *ec = adev->dev.platform_data; 587 struct power_supply_config psy_cfg = {}; 588 struct device *dev = &adev->dev; 589 struct gaokun_psy *ecbat; 590 591 ecbat = devm_kzalloc(&adev->dev, sizeof(*ecbat), GFP_KERNEL); 592 if (!ecbat) 593 return -ENOMEM; 594 595 ecbat->ec = ec; 596 ecbat->dev = dev; 597 ecbat->nb.notifier_call = gaokun_psy_notify; 598 599 auxiliary_set_drvdata(adev, ecbat); 600 601 psy_cfg.drv_data = ecbat; 602 ecbat->adp_psy = devm_power_supply_register(dev, &gaokun_psy_adp_desc, 603 &psy_cfg); 604 if (IS_ERR(ecbat->adp_psy)) 605 return dev_err_probe(dev, PTR_ERR(ecbat->adp_psy), 606 "Failed to register AC power supply\n"); 607 608 psy_cfg.supplied_to = (char **)&gaokun_psy_bat_desc.name; 609 psy_cfg.num_supplicants = 1; 610 psy_cfg.no_wakeup_source = true; 611 psy_cfg.attr_grp = gaokun_psy_features_groups; 612 ecbat->bat_psy = devm_power_supply_register(dev, &gaokun_psy_bat_desc, 613 &psy_cfg); 614 if (IS_ERR(ecbat->bat_psy)) 615 return dev_err_probe(dev, PTR_ERR(ecbat->bat_psy), 616 "Failed to register battery power supply\n"); 617 gaokun_psy_init(ecbat); 618 619 return gaokun_ec_register_notify(ec, &ecbat->nb); 620 } 621 622 static void gaokun_psy_remove(struct auxiliary_device *adev) 623 { 624 struct gaokun_psy *ecbat = auxiliary_get_drvdata(adev); 625 626 gaokun_ec_unregister_notify(ecbat->ec, &ecbat->nb); 627 } 628 629 static const struct auxiliary_device_id gaokun_psy_id_table[] = { 630 { .name = GAOKUN_MOD_NAME "." GAOKUN_DEV_PSY, }, 631 {} 632 }; 633 MODULE_DEVICE_TABLE(auxiliary, gaokun_psy_id_table); 634 635 static struct auxiliary_driver gaokun_psy_driver = { 636 .name = GAOKUN_DEV_PSY, 637 .id_table = gaokun_psy_id_table, 638 .probe = gaokun_psy_probe, 639 .remove = gaokun_psy_remove, 640 }; 641 642 module_auxiliary_driver(gaokun_psy_driver); 643 644 MODULE_DESCRIPTION("HUAWEI Matebook E Go psy driver"); 645 MODULE_LICENSE("GPL"); 646