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.

platform: arm64: add Huawei Matebook E Go EC driver

There are three variants of which Huawei released the first two
simultaneously.

Huawei Matebook E Go LTE(sc8180x), codename seems to be gaokun2.
Huawei Matebook E Go(sc8280xp@3.0GHz), codename must be gaokun3. (see [1])
Huawei Matebook E Go 2023(sc8280xp@2.69GHz), codename should be also gaokun3.

Adding support for the latter two variants for now, this driver should
also work for the sc8180x variant according to acpi table files, but I
don't have the device to test yet.

Different from other Qualcomm Snapdragon sc8280xp based machines, the
Huawei Matebook E Go uses an embedded controller while others use
a system called PMIC GLink. This embedded controller can be used to
perform a set of various functions, including, but not limited to:

- Battery and charger monitoring;
- Charge control and smart charge;
- Fn_lock settings;
- Tablet lid status;
- Temperature sensors;
- USB Type-C notifications (ports orientation, DP alt mode HPD);
- USB Type-C PD (according to observation, up to 48w).

Add a driver for the EC which creates devices for UCSI and power supply
devices.

This driver is inspired by the following drivers:
drivers/platform/arm64/acer-aspire1-ec.c
drivers/platform/arm64/lenovo-yoga-c630.c
drivers/platform/x86/huawei-wmi.c

Also thanks for reviewers' working. They have made this patch improve
a lot.

[1] https://bugzilla.kernel.org/show_bug.cgi?id=219645

Signed-off-by: Pengyu Luo <mitltlatltl@gmail.com>
Reviewed-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Link: https://lore.kernel.org/r/20250214180656.28599-3-mitltlatltl@gmail.com
Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>

authored by

Pengyu Luo and committed by
Ilpo Järvinen
7636f090 defcf2fb

+933
+7
MAINTAINERS
··· 10698 10698 F: Documentation/networking/device_drivers/ethernet/huawei/hinic.rst 10699 10699 F: drivers/net/ethernet/huawei/hinic/ 10700 10700 10701 + HUAWEI MATEBOOK E GO EMBEDDED CONTROLLER DRIVER 10702 + M: Pengyu Luo <mitltlatltl@gmail.com> 10703 + S: Maintained 10704 + F: Documentation/devicetree/bindings/platform/huawei,gaokun-ec.yaml 10705 + F: drivers/platform/arm64/huawei-gaokun-ec.c 10706 + F: include/linux/platform_data/huawei-gaokun-ec.h 10707 + 10701 10708 HUGETLB SUBSYSTEM 10702 10709 M: Muchun Song <muchun.song@linux.dev> 10703 10710 L: linux-mm@kvack.org
+21
drivers/platform/arm64/Kconfig
··· 33 33 laptop where this information is not properly exposed via the 34 34 standard ACPI devices. 35 35 36 + config EC_HUAWEI_GAOKUN 37 + tristate "Huawei Matebook E Go Embedded Controller driver" 38 + depends on ARCH_QCOM || COMPILE_TEST 39 + depends on I2C 40 + depends on INPUT 41 + depends on HWMON 42 + select AUXILIARY_BUS 43 + 44 + help 45 + Say Y here to enable the EC driver for the Huawei Matebook E Go 46 + which is a sc8280xp-based 2-in-1 tablet. The driver handles battery 47 + (information, charge control) and USB Type-C DP HPD events as well 48 + as some misc functions like the lid sensor and temperature sensors, 49 + etc. 50 + 51 + This driver provides battery and AC status support for the mentioned 52 + laptop where this information is not properly exposed via the 53 + standard ACPI devices. 54 + 55 + Say M or Y here to include this support. 56 + 36 57 config EC_LENOVO_YOGA_C630 37 58 tristate "Lenovo Yoga C630 Embedded Controller driver" 38 59 depends on ARCH_QCOM || COMPILE_TEST
+1
drivers/platform/arm64/Makefile
··· 6 6 # 7 7 8 8 obj-$(CONFIG_EC_ACER_ASPIRE1) += acer-aspire1-ec.o 9 + obj-$(CONFIG_EC_HUAWEI_GAOKUN) += huawei-gaokun-ec.o 9 10 obj-$(CONFIG_EC_LENOVO_YOGA_C630) += lenovo-yoga-c630.o
+825
drivers/platform/arm64/huawei-gaokun-ec.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + /* 3 + * huawei-gaokun-ec - An EC driver for HUAWEI Matebook E Go 4 + * 5 + * Copyright (C) 2024-2025 Pengyu Luo <mitltlatltl@gmail.com> 6 + */ 7 + 8 + #include <linux/auxiliary_bus.h> 9 + #include <linux/cleanup.h> 10 + #include <linux/delay.h> 11 + #include <linux/device.h> 12 + #include <linux/hwmon.h> 13 + #include <linux/hwmon-sysfs.h> 14 + #include <linux/i2c.h> 15 + #include <linux/input.h> 16 + #include <linux/notifier.h> 17 + #include <linux/module.h> 18 + #include <linux/mutex.h> 19 + #include <linux/platform_data/huawei-gaokun-ec.h> 20 + 21 + #define EC_EVENT 0x06 22 + 23 + /* Also can be found in ACPI specification 12.3 */ 24 + #define EC_READ 0x80 25 + #define EC_WRITE 0x81 26 + #define EC_BURST 0x82 27 + #define EC_QUERY 0x84 28 + 29 + #define EC_FN_LOCK_ON 0x5A 30 + #define EC_FN_LOCK_OFF 0x55 31 + #define EC_FN_LOCK_READ 0x6B 32 + #define EC_FN_LOCK_WRITE 0x6C 33 + 34 + #define EC_EVENT_LID 0x81 35 + 36 + #define EC_LID_STATE 0x80 37 + #define EC_LID_OPEN BIT(1) 38 + 39 + #define EC_TEMP_REG 0x61 40 + 41 + #define EC_STANDBY_REG 0xB2 42 + #define EC_STANDBY_ENTER 0xDB 43 + #define EC_STANDBY_EXIT 0xEB 44 + 45 + enum gaokun_ec_smart_charge_cmd { 46 + SMART_CHARGE_DATA_WRITE = 0xE3, 47 + SMART_CHARGE_DATA_READ, 48 + SMART_CHARGE_ENABLE_WRITE, 49 + SMART_CHARGE_ENABLE_READ, 50 + }; 51 + 52 + enum gaokun_ec_ucsi_cmd { 53 + UCSI_REG_WRITE = 0xD2, 54 + UCSI_REG_READ, 55 + UCSI_DATA_WRITE, 56 + UCSI_DATA_READ, 57 + }; 58 + 59 + #define UCSI_REG_SIZE 7 60 + 61 + /* 62 + * For tx, command sequences are arranged as 63 + * {master_cmd, slave_cmd, data_len, data_seq} 64 + */ 65 + #define REQ_HDR_SIZE 3 66 + #define INPUT_SIZE_OFFSET 2 67 + #define REQ_LEN(req) (REQ_HDR_SIZE + (req)[INPUT_SIZE_OFFSET]) 68 + 69 + /* 70 + * For rx, data sequences are arranged as 71 + * {status, data_len(unreliable), data_seq} 72 + */ 73 + #define RESP_HDR_SIZE 2 74 + 75 + #define MKREQ(REG0, REG1, SIZE, ...) \ 76 + { \ 77 + REG0, REG1, SIZE, \ 78 + /* ## will remove comma when SIZE is 0 */ \ 79 + ## __VA_ARGS__, \ 80 + /* make sure len(pkt[3:]) >= SIZE */ \ 81 + [3 + (SIZE)] = 0, \ 82 + } 83 + 84 + #define MKRESP(SIZE) \ 85 + { \ 86 + [RESP_HDR_SIZE + (SIZE) - 1] = 0, \ 87 + } 88 + 89 + /* Possible size 1, 4, 20, 24. Most of the time, the size is 1. */ 90 + static inline void refill_req(u8 *dest, const u8 *src, size_t size) 91 + { 92 + memcpy(dest + REQ_HDR_SIZE, src, size); 93 + } 94 + 95 + static inline void refill_req_byte(u8 *dest, const u8 *src) 96 + { 97 + dest[REQ_HDR_SIZE] = *src; 98 + } 99 + 100 + /* Possible size 1, 2, 4, 7, 20. Most of the time, the size is 1. */ 101 + static inline void extr_resp(u8 *dest, const u8 *src, size_t size) 102 + { 103 + memcpy(dest, src + RESP_HDR_SIZE, size); 104 + } 105 + 106 + static inline void extr_resp_byte(u8 *dest, const u8 *src) 107 + { 108 + *dest = src[RESP_HDR_SIZE]; 109 + } 110 + 111 + static inline void *extr_resp_shallow(const u8 *src) 112 + { 113 + return (void *)(src + RESP_HDR_SIZE); 114 + } 115 + 116 + struct gaokun_ec { 117 + struct i2c_client *client; 118 + struct mutex lock; /* EC transaction lock */ 119 + struct blocking_notifier_head notifier_list; 120 + struct device *hwmon_dev; 121 + struct input_dev *idev; 122 + bool suspended; 123 + }; 124 + 125 + static int gaokun_ec_request(struct gaokun_ec *ec, const u8 *req, 126 + size_t resp_len, u8 *resp) 127 + { 128 + struct i2c_client *client = ec->client; 129 + struct i2c_msg msgs[] = { 130 + { 131 + .addr = client->addr, 132 + .flags = client->flags, 133 + .len = REQ_LEN(req), 134 + .buf = (void *)req, 135 + }, { 136 + .addr = client->addr, 137 + .flags = client->flags | I2C_M_RD, 138 + .len = resp_len, 139 + .buf = resp, 140 + }, 141 + }; 142 + int ret; 143 + 144 + guard(mutex)(&ec->lock); 145 + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); 146 + if (ret != ARRAY_SIZE(msgs)) { 147 + dev_err(&client->dev, "I2C transfer error %d\n", ret); 148 + goto out_after_break; 149 + } 150 + 151 + ret = *resp; 152 + if (ret) 153 + dev_err(&client->dev, "EC transaction error %d\n", ret); 154 + 155 + out_after_break: 156 + usleep_range(2000, 2500); /* have a break, ACPI did this */ 157 + 158 + return ret; 159 + } 160 + 161 + /* -------------------------------------------------------------------------- */ 162 + /* Common API */ 163 + 164 + /** 165 + * gaokun_ec_read - Read from EC 166 + * @ec: The gaokun_ec structure 167 + * @req: The sequence to request 168 + * @resp_len: The size to read 169 + * @resp: The buffer to store response sequence 170 + * 171 + * This function is used to read data after writing a magic sequence to EC. 172 + * All EC operations depend on this function. 173 + * 174 + * Huawei uses magic sequences everywhere to complete various functions, all 175 + * these sequences are passed to ECCD(a ACPI method which is quiet similar 176 + * to gaokun_ec_request), there is no good abstraction to generalize these 177 + * sequences, so just wrap it for now. Almost all magic sequences are kept 178 + * in this file. 179 + * 180 + * Return: 0 on success or negative error code. 181 + */ 182 + int gaokun_ec_read(struct gaokun_ec *ec, const u8 *req, 183 + size_t resp_len, u8 *resp) 184 + { 185 + return gaokun_ec_request(ec, req, resp_len, resp); 186 + } 187 + EXPORT_SYMBOL_GPL(gaokun_ec_read); 188 + 189 + /** 190 + * gaokun_ec_write - Write to EC 191 + * @ec: The gaokun_ec structure 192 + * @req: The sequence to request 193 + * 194 + * This function has no big difference from gaokun_ec_read. When caller care 195 + * only write status and no actual data are returned, then use it. 196 + * 197 + * Return: 0 on success or negative error code. 198 + */ 199 + int gaokun_ec_write(struct gaokun_ec *ec, const u8 *req) 200 + { 201 + u8 ec_resp[] = MKRESP(0); 202 + 203 + return gaokun_ec_request(ec, req, sizeof(ec_resp), ec_resp); 204 + } 205 + EXPORT_SYMBOL_GPL(gaokun_ec_write); 206 + 207 + int gaokun_ec_read_byte(struct gaokun_ec *ec, const u8 *req, u8 *byte) 208 + { 209 + int ret; 210 + u8 ec_resp[] = MKRESP(sizeof(*byte)); 211 + 212 + ret = gaokun_ec_read(ec, req, sizeof(ec_resp), ec_resp); 213 + extr_resp_byte(byte, ec_resp); 214 + 215 + return ret; 216 + } 217 + EXPORT_SYMBOL_GPL(gaokun_ec_read_byte); 218 + 219 + /** 220 + * gaokun_ec_register_notify - Register a notifier callback for EC events. 221 + * @ec: The gaokun_ec structure 222 + * @nb: Notifier block pointer to register 223 + * 224 + * Return: 0 on success or negative error code. 225 + */ 226 + int gaokun_ec_register_notify(struct gaokun_ec *ec, struct notifier_block *nb) 227 + { 228 + return blocking_notifier_chain_register(&ec->notifier_list, nb); 229 + } 230 + EXPORT_SYMBOL_GPL(gaokun_ec_register_notify); 231 + 232 + /** 233 + * gaokun_ec_unregister_notify - Unregister notifier callback for EC events. 234 + * @ec: The gaokun_ec structure 235 + * @nb: Notifier block pointer to unregister 236 + * 237 + * Unregister a notifier callback that was previously registered with 238 + * gaokun_ec_register_notify(). 239 + */ 240 + void gaokun_ec_unregister_notify(struct gaokun_ec *ec, struct notifier_block *nb) 241 + { 242 + blocking_notifier_chain_unregister(&ec->notifier_list, nb); 243 + } 244 + EXPORT_SYMBOL_GPL(gaokun_ec_unregister_notify); 245 + 246 + /* -------------------------------------------------------------------------- */ 247 + /* API for PSY */ 248 + 249 + /** 250 + * gaokun_ec_psy_multi_read - Read contiguous registers 251 + * @ec: The gaokun_ec structure 252 + * @reg: The start register 253 + * @resp_len: The number of registers to be read 254 + * @resp: The buffer to store response sequence 255 + * 256 + * Return: 0 on success or negative error code. 257 + */ 258 + int gaokun_ec_psy_multi_read(struct gaokun_ec *ec, u8 reg, 259 + size_t resp_len, u8 *resp) 260 + { 261 + u8 ec_req[] = MKREQ(0x02, EC_READ, 1, 0); 262 + u8 ec_resp[] = MKRESP(1); 263 + int i, ret; 264 + 265 + for (i = 0; i < resp_len; ++i, reg++) { 266 + refill_req_byte(ec_req, &reg); 267 + ret = gaokun_ec_read(ec, ec_req, sizeof(ec_resp), ec_resp); 268 + if (ret) 269 + return ret; 270 + extr_resp_byte(&resp[i], ec_resp); 271 + } 272 + 273 + return 0; 274 + } 275 + EXPORT_SYMBOL_GPL(gaokun_ec_psy_multi_read); 276 + 277 + /* Smart charge */ 278 + 279 + /** 280 + * gaokun_ec_psy_get_smart_charge - Get smart charge data from EC 281 + * @ec: The gaokun_ec structure 282 + * @resp: The buffer to store response sequence (mode, delay, start, end) 283 + * 284 + * Return: 0 on success or negative error code. 285 + */ 286 + int gaokun_ec_psy_get_smart_charge(struct gaokun_ec *ec, 287 + u8 resp[GAOKUN_SMART_CHARGE_DATA_SIZE]) 288 + { 289 + /* GBCM */ 290 + u8 ec_req[] = MKREQ(0x02, SMART_CHARGE_DATA_READ, 0); 291 + u8 ec_resp[] = MKRESP(GAOKUN_SMART_CHARGE_DATA_SIZE); 292 + int ret; 293 + 294 + ret = gaokun_ec_read(ec, ec_req, sizeof(ec_resp), ec_resp); 295 + if (ret) 296 + return ret; 297 + 298 + extr_resp(resp, ec_resp, GAOKUN_SMART_CHARGE_DATA_SIZE); 299 + 300 + return 0; 301 + } 302 + EXPORT_SYMBOL_GPL(gaokun_ec_psy_get_smart_charge); 303 + 304 + static inline bool validate_battery_threshold_range(u8 start, u8 end) 305 + { 306 + return end != 0 && start <= end && end <= 100; 307 + } 308 + 309 + /** 310 + * gaokun_ec_psy_set_smart_charge - Set smart charge data 311 + * @ec: The gaokun_ec structure 312 + * @req: The sequence to request (mode, delay, start, end) 313 + * 314 + * Return: 0 on success or negative error code. 315 + */ 316 + int gaokun_ec_psy_set_smart_charge(struct gaokun_ec *ec, 317 + const u8 req[GAOKUN_SMART_CHARGE_DATA_SIZE]) 318 + { 319 + /* SBCM */ 320 + u8 ec_req[] = MKREQ(0x02, SMART_CHARGE_DATA_WRITE, 321 + GAOKUN_SMART_CHARGE_DATA_SIZE); 322 + 323 + if (!validate_battery_threshold_range(req[2], req[3])) 324 + return -EINVAL; 325 + 326 + refill_req(ec_req, req, GAOKUN_SMART_CHARGE_DATA_SIZE); 327 + 328 + return gaokun_ec_write(ec, ec_req); 329 + } 330 + EXPORT_SYMBOL_GPL(gaokun_ec_psy_set_smart_charge); 331 + 332 + /* Smart charge enable */ 333 + 334 + /** 335 + * gaokun_ec_psy_get_smart_charge_enable - Get smart charge state 336 + * @ec: The gaokun_ec structure 337 + * @on: The state 338 + * 339 + * Return: 0 on success or negative error code. 340 + */ 341 + int gaokun_ec_psy_get_smart_charge_enable(struct gaokun_ec *ec, bool *on) 342 + { 343 + /* GBAC */ 344 + u8 ec_req[] = MKREQ(0x02, SMART_CHARGE_ENABLE_READ, 0); 345 + u8 state; 346 + int ret; 347 + 348 + ret = gaokun_ec_read_byte(ec, ec_req, &state); 349 + if (ret) 350 + return ret; 351 + 352 + *on = !!state; 353 + 354 + return 0; 355 + } 356 + EXPORT_SYMBOL_GPL(gaokun_ec_psy_get_smart_charge_enable); 357 + 358 + /** 359 + * gaokun_ec_psy_set_smart_charge_enable - Set smart charge state 360 + * @ec: The gaokun_ec structure 361 + * @on: The state 362 + * 363 + * Return: 0 on success or negative error code. 364 + */ 365 + int gaokun_ec_psy_set_smart_charge_enable(struct gaokun_ec *ec, bool on) 366 + { 367 + /* SBAC */ 368 + u8 ec_req[] = MKREQ(0x02, SMART_CHARGE_ENABLE_WRITE, 1, on); 369 + 370 + return gaokun_ec_write(ec, ec_req); 371 + } 372 + EXPORT_SYMBOL_GPL(gaokun_ec_psy_set_smart_charge_enable); 373 + 374 + /* -------------------------------------------------------------------------- */ 375 + /* API for UCSI */ 376 + 377 + /** 378 + * gaokun_ec_ucsi_read - Read UCSI data from EC 379 + * @ec: The gaokun_ec structure 380 + * @resp: The buffer to store response sequence 381 + * 382 + * Read CCI and MSGI (used by UCSI subdriver). 383 + * 384 + * Return: 0 on success or negative error code. 385 + */ 386 + int gaokun_ec_ucsi_read(struct gaokun_ec *ec, 387 + u8 resp[GAOKUN_UCSI_READ_SIZE]) 388 + { 389 + u8 ec_req[] = MKREQ(0x03, UCSI_DATA_READ, 0); 390 + u8 ec_resp[] = MKRESP(GAOKUN_UCSI_READ_SIZE); 391 + int ret; 392 + 393 + ret = gaokun_ec_read(ec, ec_req, sizeof(ec_resp), ec_resp); 394 + if (ret) 395 + return ret; 396 + 397 + extr_resp(resp, ec_resp, GAOKUN_UCSI_READ_SIZE); 398 + return 0; 399 + } 400 + EXPORT_SYMBOL_GPL(gaokun_ec_ucsi_read); 401 + 402 + /** 403 + * gaokun_ec_ucsi_write - Write UCSI data to EC 404 + * @ec: The gaokun_ec structure 405 + * @req: The sequence to request 406 + * 407 + * Write CTRL and MSGO (used by UCSI subdriver). 408 + * 409 + * Return: 0 on success or negative error code. 410 + */ 411 + int gaokun_ec_ucsi_write(struct gaokun_ec *ec, 412 + const u8 req[GAOKUN_UCSI_WRITE_SIZE]) 413 + { 414 + u8 ec_req[] = MKREQ(0x03, UCSI_DATA_WRITE, GAOKUN_UCSI_WRITE_SIZE); 415 + 416 + refill_req(ec_req, req, GAOKUN_UCSI_WRITE_SIZE); 417 + 418 + return gaokun_ec_write(ec, ec_req); 419 + } 420 + EXPORT_SYMBOL_GPL(gaokun_ec_ucsi_write); 421 + 422 + /** 423 + * gaokun_ec_ucsi_get_reg - Get UCSI register from EC 424 + * @ec: The gaokun_ec structure 425 + * @ureg: The gaokun ucsi register 426 + * 427 + * Get UCSI register data (used by UCSI subdriver). 428 + * 429 + * Return: 0 on success or negative error code. 430 + */ 431 + int gaokun_ec_ucsi_get_reg(struct gaokun_ec *ec, struct gaokun_ucsi_reg *ureg) 432 + { 433 + u8 ec_req[] = MKREQ(0x03, UCSI_REG_READ, 0); 434 + u8 ec_resp[] = MKRESP(UCSI_REG_SIZE); 435 + int ret; 436 + 437 + ret = gaokun_ec_read(ec, ec_req, sizeof(ec_resp), ec_resp); 438 + if (ret) 439 + return ret; 440 + 441 + extr_resp((u8 *)ureg, ec_resp, UCSI_REG_SIZE); 442 + 443 + return 0; 444 + } 445 + EXPORT_SYMBOL_GPL(gaokun_ec_ucsi_get_reg); 446 + 447 + /** 448 + * gaokun_ec_ucsi_pan_ack - Ack pin assignment notifications from EC 449 + * @ec: The gaokun_ec structure 450 + * @port_id: The port id receiving and handling the notifications 451 + * 452 + * Ack pin assignment notifications (used by UCSI subdriver). 453 + * 454 + * Return: 0 on success or negative error code. 455 + */ 456 + int gaokun_ec_ucsi_pan_ack(struct gaokun_ec *ec, int port_id) 457 + { 458 + u8 ec_req[] = MKREQ(0x03, UCSI_REG_WRITE, 1); 459 + u8 data = 1 << port_id; 460 + 461 + if (port_id == GAOKUN_UCSI_NO_PORT_UPDATE) 462 + data = 0; 463 + 464 + refill_req_byte(ec_req, &data); 465 + 466 + return gaokun_ec_write(ec, ec_req); 467 + } 468 + EXPORT_SYMBOL_GPL(gaokun_ec_ucsi_pan_ack); 469 + 470 + /* -------------------------------------------------------------------------- */ 471 + /* EC Sysfs */ 472 + 473 + /* Fn lock */ 474 + static int gaokun_ec_get_fn_lock(struct gaokun_ec *ec, bool *on) 475 + { 476 + /* GFRS */ 477 + u8 ec_req[] = MKREQ(0x02, EC_FN_LOCK_READ, 0); 478 + int ret; 479 + u8 state; 480 + 481 + ret = gaokun_ec_read_byte(ec, ec_req, &state); 482 + if (ret) 483 + return ret; 484 + 485 + if (state == EC_FN_LOCK_ON) 486 + *on = true; 487 + else if (state == EC_FN_LOCK_OFF) 488 + *on = false; 489 + else 490 + return -EIO; 491 + 492 + return 0; 493 + } 494 + 495 + static int gaokun_ec_set_fn_lock(struct gaokun_ec *ec, bool on) 496 + { 497 + /* SFRS */ 498 + u8 ec_req[] = MKREQ(0x02, EC_FN_LOCK_WRITE, 1, 499 + on ? EC_FN_LOCK_ON : EC_FN_LOCK_OFF); 500 + 501 + return gaokun_ec_write(ec, ec_req); 502 + } 503 + 504 + static ssize_t fn_lock_show(struct device *dev, 505 + struct device_attribute *attr, 506 + char *buf) 507 + { 508 + struct gaokun_ec *ec = dev_get_drvdata(dev); 509 + bool on; 510 + int ret; 511 + 512 + ret = gaokun_ec_get_fn_lock(ec, &on); 513 + if (ret) 514 + return ret; 515 + 516 + return sysfs_emit(buf, "%d\n", on); 517 + } 518 + 519 + static ssize_t fn_lock_store(struct device *dev, 520 + struct device_attribute *attr, 521 + const char *buf, size_t size) 522 + { 523 + struct gaokun_ec *ec = dev_get_drvdata(dev); 524 + bool on; 525 + int ret; 526 + 527 + if (kstrtobool(buf, &on)) 528 + return -EINVAL; 529 + 530 + ret = gaokun_ec_set_fn_lock(ec, on); 531 + if (ret) 532 + return ret; 533 + 534 + return size; 535 + } 536 + 537 + static DEVICE_ATTR_RW(fn_lock); 538 + 539 + static struct attribute *gaokun_ec_attrs[] = { 540 + &dev_attr_fn_lock.attr, 541 + NULL, 542 + }; 543 + ATTRIBUTE_GROUPS(gaokun_ec); 544 + 545 + /* -------------------------------------------------------------------------- */ 546 + /* Thermal Zone HwMon */ 547 + 548 + /* Range from 0 to 0x2C, partially valid */ 549 + static const u8 temp_reg[] = { 550 + 0x05, 0x07, 0x08, 0x0E, 0x0F, 0x12, 0x15, 0x1E, 551 + 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 552 + 0x27, 0x28, 0x29, 0x2A 553 + }; 554 + 555 + static int gaokun_ec_get_temp(struct gaokun_ec *ec, u8 idx, long *temp) 556 + { 557 + /* GTMP */ 558 + u8 ec_req[] = MKREQ(0x02, EC_TEMP_REG, 1, temp_reg[idx]); 559 + u8 ec_resp[] = MKRESP(sizeof(__le16)); 560 + __le16 *tmp; 561 + int ret; 562 + 563 + ret = gaokun_ec_read(ec, ec_req, sizeof(ec_resp), ec_resp); 564 + if (ret) 565 + return ret; 566 + 567 + tmp = (__le16 *)extr_resp_shallow(ec_resp); 568 + *temp = le16_to_cpu(*tmp) * 100; /* convert to HwMon's unit */ 569 + 570 + return 0; 571 + } 572 + 573 + static umode_t 574 + gaokun_ec_hwmon_is_visible(const void *data, enum hwmon_sensor_types type, 575 + u32 attr, int channel) 576 + { 577 + return type == hwmon_temp ? 0444 : 0; 578 + } 579 + 580 + static int 581 + gaokun_ec_hwmon_read(struct device *dev, enum hwmon_sensor_types type, 582 + u32 attr, int channel, long *val) 583 + { 584 + struct gaokun_ec *ec = dev_get_drvdata(dev); 585 + 586 + if (type == hwmon_temp) 587 + return gaokun_ec_get_temp(ec, channel, val); 588 + 589 + return -EINVAL; 590 + } 591 + 592 + static const struct hwmon_ops gaokun_ec_hwmon_ops = { 593 + .is_visible = gaokun_ec_hwmon_is_visible, 594 + .read = gaokun_ec_hwmon_read, 595 + }; 596 + 597 + static u32 gaokun_ec_temp_config[] = { 598 + [0 ... ARRAY_SIZE(temp_reg) - 1] = HWMON_T_INPUT, 599 + 0 600 + }; 601 + 602 + static const struct hwmon_channel_info gaokun_ec_temp = { 603 + .type = hwmon_temp, 604 + .config = gaokun_ec_temp_config, 605 + }; 606 + 607 + static const struct hwmon_channel_info * const gaokun_ec_hwmon_info[] = { 608 + &gaokun_ec_temp, 609 + NULL 610 + }; 611 + 612 + static const struct hwmon_chip_info gaokun_ec_hwmon_chip_info = { 613 + .ops = &gaokun_ec_hwmon_ops, 614 + .info = gaokun_ec_hwmon_info, 615 + }; 616 + 617 + /* -------------------------------------------------------------------------- */ 618 + /* Modern Standby */ 619 + 620 + static int gaokun_ec_suspend(struct device *dev) 621 + { 622 + struct gaokun_ec *ec = dev_get_drvdata(dev); 623 + u8 ec_req[] = MKREQ(0x02, EC_STANDBY_REG, 1, EC_STANDBY_ENTER); 624 + int ret; 625 + 626 + if (ec->suspended) 627 + return 0; 628 + 629 + ret = gaokun_ec_write(ec, ec_req); 630 + if (ret) 631 + return ret; 632 + 633 + ec->suspended = true; 634 + 635 + return 0; 636 + } 637 + 638 + static int gaokun_ec_resume(struct device *dev) 639 + { 640 + struct gaokun_ec *ec = dev_get_drvdata(dev); 641 + u8 ec_req[] = MKREQ(0x02, EC_STANDBY_REG, 1, EC_STANDBY_EXIT); 642 + int ret; 643 + int i; 644 + 645 + if (!ec->suspended) 646 + return 0; 647 + 648 + for (i = 0; i < 3; ++i) { 649 + ret = gaokun_ec_write(ec, ec_req); 650 + if (ret == 0) 651 + break; 652 + 653 + msleep(100); /* EC need time to resume */ 654 + }; 655 + 656 + ec->suspended = false; 657 + 658 + return 0; 659 + } 660 + 661 + static void gaokun_aux_release(struct device *dev) 662 + { 663 + struct auxiliary_device *adev = to_auxiliary_dev(dev); 664 + 665 + kfree(adev); 666 + } 667 + 668 + static void gaokun_aux_remove(void *data) 669 + { 670 + struct auxiliary_device *adev = data; 671 + 672 + auxiliary_device_delete(adev); 673 + auxiliary_device_uninit(adev); 674 + } 675 + 676 + static int gaokun_aux_init(struct device *parent, const char *name, 677 + struct gaokun_ec *ec) 678 + { 679 + struct auxiliary_device *adev; 680 + int ret; 681 + 682 + adev = kzalloc(sizeof(*adev), GFP_KERNEL); 683 + if (!adev) 684 + return -ENOMEM; 685 + 686 + adev->name = name; 687 + adev->id = 0; 688 + adev->dev.parent = parent; 689 + adev->dev.release = gaokun_aux_release; 690 + adev->dev.platform_data = ec; 691 + /* Allow aux devices to access parent's DT nodes directly */ 692 + device_set_of_node_from_dev(&adev->dev, parent); 693 + 694 + ret = auxiliary_device_init(adev); 695 + if (ret) { 696 + kfree(adev); 697 + return ret; 698 + } 699 + 700 + ret = auxiliary_device_add(adev); 701 + if (ret) { 702 + auxiliary_device_uninit(adev); 703 + return ret; 704 + } 705 + 706 + return devm_add_action_or_reset(parent, gaokun_aux_remove, adev); 707 + } 708 + 709 + /* -------------------------------------------------------------------------- */ 710 + /* EC */ 711 + 712 + static irqreturn_t gaokun_ec_irq_handler(int irq, void *data) 713 + { 714 + struct gaokun_ec *ec = data; 715 + u8 ec_req[] = MKREQ(EC_EVENT, EC_QUERY, 0); 716 + u8 status, id; 717 + int ret; 718 + 719 + ret = gaokun_ec_read_byte(ec, ec_req, &id); 720 + if (ret) 721 + return IRQ_HANDLED; 722 + 723 + switch (id) { 724 + case 0x0: /* No event */ 725 + break; 726 + 727 + case EC_EVENT_LID: 728 + gaokun_ec_psy_read_byte(ec, EC_LID_STATE, &status); 729 + status &= EC_LID_OPEN; 730 + input_report_switch(ec->idev, SW_LID, !status); 731 + input_sync(ec->idev); 732 + break; 733 + 734 + default: 735 + blocking_notifier_call_chain(&ec->notifier_list, id, ec); 736 + } 737 + 738 + return IRQ_HANDLED; 739 + } 740 + 741 + static int gaokun_ec_probe(struct i2c_client *client) 742 + { 743 + struct device *dev = &client->dev; 744 + struct gaokun_ec *ec; 745 + int ret; 746 + 747 + ec = devm_kzalloc(dev, sizeof(*ec), GFP_KERNEL); 748 + if (!ec) 749 + return -ENOMEM; 750 + 751 + ret = devm_mutex_init(dev, &ec->lock); 752 + if (ret) 753 + return ret; 754 + 755 + ec->client = client; 756 + i2c_set_clientdata(client, ec); 757 + BLOCKING_INIT_NOTIFIER_HEAD(&ec->notifier_list); 758 + 759 + /* Lid switch */ 760 + ec->idev = devm_input_allocate_device(dev); 761 + if (!ec->idev) 762 + return -ENOMEM; 763 + 764 + ec->idev->name = "LID"; 765 + ec->idev->phys = "gaokun-ec/input0"; 766 + input_set_capability(ec->idev, EV_SW, SW_LID); 767 + 768 + ret = input_register_device(ec->idev); 769 + if (ret) 770 + return dev_err_probe(dev, ret, "Failed to register input device\n"); 771 + 772 + ret = gaokun_aux_init(dev, GAOKUN_DEV_PSY, ec); 773 + if (ret) 774 + return ret; 775 + 776 + ret = gaokun_aux_init(dev, GAOKUN_DEV_UCSI, ec); 777 + if (ret) 778 + return ret; 779 + 780 + ret = devm_request_threaded_irq(dev, client->irq, NULL, 781 + gaokun_ec_irq_handler, IRQF_ONESHOT, 782 + dev_name(dev), ec); 783 + if (ret) 784 + return dev_err_probe(dev, ret, "Failed to request IRQ\n"); 785 + 786 + ec->hwmon_dev = devm_hwmon_device_register_with_info(dev, "gaokun_ec_hwmon", 787 + ec, &gaokun_ec_hwmon_chip_info, NULL); 788 + if (IS_ERR(ec->hwmon_dev)) 789 + return dev_err_probe(dev, PTR_ERR(ec->hwmon_dev), 790 + "Failed to register hwmon device\n"); 791 + 792 + return 0; 793 + } 794 + 795 + static const struct i2c_device_id gaokun_ec_id[] = { 796 + { "gaokun-ec", }, 797 + { } 798 + }; 799 + MODULE_DEVICE_TABLE(i2c, gaokun_ec_id); 800 + 801 + static const struct of_device_id gaokun_ec_of_match[] = { 802 + { .compatible = "huawei,gaokun3-ec", }, 803 + { } 804 + }; 805 + MODULE_DEVICE_TABLE(of, gaokun_ec_of_match); 806 + 807 + static const struct dev_pm_ops gaokun_ec_pm_ops = { 808 + NOIRQ_SYSTEM_SLEEP_PM_OPS(gaokun_ec_suspend, gaokun_ec_resume) 809 + }; 810 + 811 + static struct i2c_driver gaokun_ec_driver = { 812 + .driver = { 813 + .name = "gaokun-ec", 814 + .of_match_table = gaokun_ec_of_match, 815 + .pm = &gaokun_ec_pm_ops, 816 + .dev_groups = gaokun_ec_groups, 817 + }, 818 + .probe = gaokun_ec_probe, 819 + .id_table = gaokun_ec_id, 820 + }; 821 + module_i2c_driver(gaokun_ec_driver); 822 + 823 + MODULE_DESCRIPTION("HUAWEI Matebook E Go EC driver"); 824 + MODULE_AUTHOR("Pengyu Luo <mitltlatltl@gmail.com>"); 825 + MODULE_LICENSE("GPL");
+79
include/linux/platform_data/huawei-gaokun-ec.h
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + /* 3 + * Huawei Matebook E Go Embedded Controller 4 + * 5 + * Copyright (C) 2024-2025 Pengyu Luo <mitltlatltl@gmail.com> 6 + */ 7 + 8 + #ifndef __HUAWEI_GAOKUN_EC_H__ 9 + #define __HUAWEI_GAOKUN_EC_H__ 10 + 11 + #define GAOKUN_UCSI_CCI_SIZE 4 12 + #define GAOKUN_UCSI_MSGI_SIZE 16 13 + #define GAOKUN_UCSI_READ_SIZE (GAOKUN_UCSI_CCI_SIZE + GAOKUN_UCSI_MSGI_SIZE) 14 + #define GAOKUN_UCSI_WRITE_SIZE 24 /* 8B CTRL, 16B MSGO */ 15 + 16 + #define GAOKUN_UCSI_NO_PORT_UPDATE (-1) 17 + 18 + #define GAOKUN_SMART_CHARGE_DATA_SIZE 4 /* mode, delay, start, end */ 19 + 20 + /* -------------------------------------------------------------------------- */ 21 + 22 + struct gaokun_ec; 23 + struct gaokun_ucsi_reg; 24 + struct notifier_block; 25 + 26 + #define GAOKUN_MOD_NAME "huawei_gaokun_ec" 27 + #define GAOKUN_DEV_PSY "psy" 28 + #define GAOKUN_DEV_UCSI "ucsi" 29 + 30 + /* -------------------------------------------------------------------------- */ 31 + /* Common API */ 32 + 33 + int gaokun_ec_register_notify(struct gaokun_ec *ec, 34 + struct notifier_block *nb); 35 + void gaokun_ec_unregister_notify(struct gaokun_ec *ec, 36 + struct notifier_block *nb); 37 + 38 + int gaokun_ec_read(struct gaokun_ec *ec, const u8 *req, 39 + size_t resp_len, u8 *resp); 40 + int gaokun_ec_write(struct gaokun_ec *ec, const u8 *req); 41 + int gaokun_ec_read_byte(struct gaokun_ec *ec, const u8 *req, u8 *byte); 42 + 43 + /* -------------------------------------------------------------------------- */ 44 + /* API for PSY */ 45 + 46 + int gaokun_ec_psy_multi_read(struct gaokun_ec *ec, u8 reg, 47 + size_t resp_len, u8 *resp); 48 + 49 + static inline int gaokun_ec_psy_read_byte(struct gaokun_ec *ec, 50 + u8 reg, u8 *byte) 51 + { 52 + return gaokun_ec_psy_multi_read(ec, reg, sizeof(*byte), byte); 53 + } 54 + 55 + static inline int gaokun_ec_psy_read_word(struct gaokun_ec *ec, 56 + u8 reg, u16 *word) 57 + { 58 + return gaokun_ec_psy_multi_read(ec, reg, sizeof(*word), (u8 *)word); 59 + } 60 + 61 + int gaokun_ec_psy_get_smart_charge(struct gaokun_ec *ec, 62 + u8 resp[GAOKUN_SMART_CHARGE_DATA_SIZE]); 63 + int gaokun_ec_psy_set_smart_charge(struct gaokun_ec *ec, 64 + const u8 req[GAOKUN_SMART_CHARGE_DATA_SIZE]); 65 + 66 + int gaokun_ec_psy_get_smart_charge_enable(struct gaokun_ec *ec, bool *on); 67 + int gaokun_ec_psy_set_smart_charge_enable(struct gaokun_ec *ec, bool on); 68 + 69 + /* -------------------------------------------------------------------------- */ 70 + /* API for UCSI */ 71 + 72 + int gaokun_ec_ucsi_read(struct gaokun_ec *ec, u8 resp[GAOKUN_UCSI_READ_SIZE]); 73 + int gaokun_ec_ucsi_write(struct gaokun_ec *ec, 74 + const u8 req[GAOKUN_UCSI_WRITE_SIZE]); 75 + 76 + int gaokun_ec_ucsi_get_reg(struct gaokun_ec *ec, struct gaokun_ucsi_reg *ureg); 77 + int gaokun_ec_ucsi_pan_ack(struct gaokun_ec *ec, int port_id); 78 + 79 + #endif /* __HUAWEI_GAOKUN_EC_H__ */