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

power: supply: add Huawei Matebook E Go psy driver

On the Huawei Matebook E Go tablet the EC provides access to the adapter
and battery status. Add the driver to read power supply status on the
tablet.

This driver is inspired by the following drivers:
drivers/power/supply/lenovo_yoga_c630_battery.c
drivers/platform/arm64/acer-aspire1-ec.c
drivers/acpi/battery.c
drivers/acpi/ac.c

base-commit: 613af589b566093ce7388bf3202fca70d742c166

Signed-off-by: Pengyu Luo <mitltlatltl@gmail.com>
Link: https://lore.kernel.org/r/20250313103437.108772-1-mitltlatltl@gmail.com
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>

authored by

Pengyu Luo and committed by
Sebastian Reichel
cfe76967 fbc1d056

+684
+27
Documentation/ABI/testing/sysfs-class-power-gaokun
··· 1 + What: /sys/class/power_supply/gaokun-ec-battery/smart_charge_delay 2 + Date: March 2025 3 + KernelVersion: 6.15 4 + Contact: Pengyu Luo <mitltlatltl@gmail.com> 5 + Description: 6 + This entry allows configuration of smart charging delay. 7 + 8 + Smart charging behavior: when the power adapter is connected 9 + for delay hours, battery charging will follow the rules of 10 + charge_control_start_threshold and charge_control_end_threshold. 11 + For more information about charge control, please refer to 12 + sysfs-class-power. 13 + 14 + Access: Read, Write 15 + 16 + Valid values: In hours (non-negative) 17 + 18 + What: /sys/class/power_supply/gaokun-ec-battery/battery_adaptive_charge 19 + Date: March 2025 20 + KernelVersion: 6.15 21 + Contact: Pengyu Luo <mitltlatltl@gmail.com> 22 + Description: 23 + This entry allows enabling battery adaptive charging. 24 + 25 + Access: Read, Write 26 + 27 + Valid values: 0 (disabled) or 1 (enabled)
+1
MAINTAINERS
··· 10950 10950 S: Maintained 10951 10951 F: Documentation/devicetree/bindings/platform/huawei,gaokun-ec.yaml 10952 10952 F: drivers/platform/arm64/huawei-gaokun-ec.c 10953 + F: drivers/power/supply/huawei-gaokun-battery.c 10953 10954 F: include/linux/platform_data/huawei-gaokun-ec.h 10954 10955 10955 10956 HUGETLB SUBSYSTEM
+10
drivers/power/supply/Kconfig
··· 173 173 Say Y here to enable support for the DS2782/DS2786 standalone battery 174 174 gas-gauge. 175 175 176 + config BATTERY_HUAWEI_GAOKUN 177 + tristate "Huawei Matebook E Go power supply" 178 + depends on EC_HUAWEI_GAOKUN 179 + help 180 + This driver enables battery and adapter support on the Huawei Matebook 181 + E Go, which is a sc8280xp-based 2-in-1 tablet. 182 + 183 + To compile the driver as a module, choose M here: the module will be 184 + called huawei-gaokun-battery. 185 + 176 186 config BATTERY_LEGO_EV3 177 187 tristate "LEGO MINDSTORMS EV3 battery" 178 188 depends on OF && IIO && GPIOLIB && (ARCH_DAVINCI_DA850 || COMPILE_TEST)
+1
drivers/power/supply/Makefile
··· 32 32 obj-$(CONFIG_BATTERY_DS2782) += ds2782_battery.o 33 33 obj-$(CONFIG_BATTERY_GAUGE_LTC2941) += ltc2941-battery-gauge.o 34 34 obj-$(CONFIG_BATTERY_GOLDFISH) += goldfish_battery.o 35 + obj-$(CONFIG_BATTERY_HUAWEI_GAOKUN) += huawei-gaokun-battery.o 35 36 obj-$(CONFIG_BATTERY_LEGO_EV3) += lego_ev3_battery.o 36 37 obj-$(CONFIG_BATTERY_LENOVO_YOGA_C630) += lenovo_yoga_c630_battery.o 37 38 obj-$(CONFIG_BATTERY_PMU) += pmu_battery.o
+645
drivers/power/supply/huawei-gaokun-battery.c
··· 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");