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: thinkpad-t14s-ec: new driver

Introduce EC driver for the ThinkPad T14s Gen6 Snapdragon, which is in
theory compatible with ThinkPad ACPI. On Linux the system is booted with
device tree, which is not supported by the ThinkPad ACPI driver
(drivers/platform/x86/lenovo/thinkpad_acpi.c). Also most of the hardware
compatibility is handled via ACPI tables, which are obviously not used
when booting via device tree. Thus adding DT compatibility to the
existing driver is not worth it as there is almost no code sharing.

The driver currently exposes features, which are not available
via other means:

* Extra Keys
* System LEDs
* Keyboard Backlight Control

The driver has been developed by reading the ACPI DSDT. There
are some more features around thermal control, which are not
yet supported by the driver.

The speaker mute and mic mute LEDs need some additional changes
in the ALSA UCM to be set automatically.

Tested-by: Neil Armstrong <neil.armstrong@linaro.org> # on Thinkpad T14S OLED
Reviewed-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org>
Signed-off-by: Sebastian Reichel <sre@kernel.org>
Link: https://patch.msgid.link/20250918-thinkpad-t14s-ec-v5-2-ac0bc6382c5c@collabora.com
[ij: folded in patch from Chen Ni <nichen@iscas.ac.cn>]
Link: https://patch.msgid.link/20250926091302.817919-1-nichen@iscas.ac.cn
[ij: folded in patch from Lukas Bulwahn <lbulwahn@redhat.com>]
Link: https://patch.msgid.link/20250926071859.138396-1-lukas.bulwahn@redhat.com
Reviewed-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>

authored by

Sebastian Reichel and committed by
Ilpo Järvinen
60b7ab6c bee278e1

+643
+6
MAINTAINERS
··· 25071 25071 T: git git://repo.or.cz/linux-2.6/linux-acpi-2.6/ibm-acpi-2.6.git 25072 25072 F: drivers/platform/x86/lenovo/thinkpad_acpi.c 25073 25073 25074 + THINKPAD T14S EMBEDDED CONTROLLER DRIVER 25075 + M: Sebastian Reichel <sre@kernel.org> 25076 + S: Maintained 25077 + F: Documentation/devicetree/bindings/embedded-controller/lenovo,thinkpad-t14s-ec.yaml 25078 + F: drivers/platform/arm64/lenovo-thinkpad-t14s.c 25079 + 25074 25080 THINKPAD LMI DRIVER 25075 25081 M: Mark Pearson <mpearson-lenovo@squebb.ca> 25076 25082 L: platform-driver-x86@vger.kernel.org
+20
drivers/platform/arm64/Kconfig
··· 70 70 71 71 Say M or Y here to include this support. 72 72 73 + config EC_LENOVO_THINKPAD_T14S 74 + tristate "Lenovo Thinkpad T14s Embedded Controller driver" 75 + depends on ARCH_QCOM || COMPILE_TEST 76 + depends on I2C 77 + depends on INPUT 78 + select INPUT_SPARSEKMAP 79 + select LEDS_CLASS 80 + select NEW_LEDS 81 + select SND_CTL_LED if SND 82 + help 83 + Driver for the Embedded Controller in the Qualcomm Snapdragon-based 84 + Lenovo Thinkpad T14s, which provides access to keyboard backlight 85 + and status LEDs. 86 + 87 + This driver provides support for the mentioned laptop where this 88 + information is not properly exposed via the standard Qualcomm 89 + devices. 90 + 91 + Say M or Y here to include this support. 92 + 73 93 endif # ARM64_PLATFORM_DEVICES
+1
drivers/platform/arm64/Makefile
··· 8 8 obj-$(CONFIG_EC_ACER_ASPIRE1) += acer-aspire1-ec.o 9 9 obj-$(CONFIG_EC_HUAWEI_GAOKUN) += huawei-gaokun-ec.o 10 10 obj-$(CONFIG_EC_LENOVO_YOGA_C630) += lenovo-yoga-c630.o 11 + obj-$(CONFIG_EC_LENOVO_THINKPAD_T14S) += lenovo-thinkpad-t14s.o
+616
drivers/platform/arm64/lenovo-thinkpad-t14s.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + /* 3 + * Copyright (c) 2025, Sebastian Reichel 4 + */ 5 + 6 + #include <linux/bitfield.h> 7 + #include <linux/bits.h> 8 + #include <linux/cleanup.h> 9 + #include <linux/container_of.h> 10 + #include <linux/device.h> 11 + #include <linux/delay.h> 12 + #include <linux/dev_printk.h> 13 + #include <linux/err.h> 14 + #include <linux/i2c.h> 15 + #include <linux/input.h> 16 + #include <linux/input/sparse-keymap.h> 17 + #include <linux/interrupt.h> 18 + #include <linux/leds.h> 19 + #include <linux/lockdep.h> 20 + #include <linux/module.h> 21 + #include <linux/regmap.h> 22 + #include <linux/slab.h> 23 + 24 + #define T14S_EC_CMD_ECRD 0x02 25 + #define T14S_EC_CMD_ECWR 0x03 26 + #define T14S_EC_CMD_EVT 0xf0 27 + 28 + #define T14S_EC_REG_LED 0x0c 29 + #define T14S_EC_REG_KBD_BL1 0x0d 30 + #define T14S_EC_REG_KBD_BL2 0xe1 31 + #define T14S_EC_KBD_BL1_MASK GENMASK_U8(7, 6) 32 + #define T14S_EC_KBD_BL2_MASK GENMASK_U8(3, 2) 33 + #define T14S_EC_REG_AUD 0x30 34 + #define T14S_EC_MIC_MUTE_LED BIT(5) 35 + #define T14S_EC_SPK_MUTE_LED BIT(6) 36 + 37 + #define T14S_EC_EVT_NONE 0x00 38 + #define T14S_EC_EVT_KEY_FN_4 0x13 39 + #define T14S_EC_EVT_KEY_FN_F7 0x16 40 + #define T14S_EC_EVT_KEY_FN_SPACE 0x1f 41 + #define T14S_EC_EVT_KEY_TP_DOUBLE_TAP 0x20 42 + #define T14S_EC_EVT_AC_CONNECTED 0x26 43 + #define T14S_EC_EVT_AC_DISCONNECTED 0x27 44 + #define T14S_EC_EVT_KEY_POWER 0x28 45 + #define T14S_EC_EVT_LID_OPEN 0x2a 46 + #define T14S_EC_EVT_LID_CLOSED 0x2b 47 + #define T14S_EC_EVT_THERMAL_TZ40 0x5c 48 + #define T14S_EC_EVT_THERMAL_TZ42 0x5d 49 + #define T14S_EC_EVT_THERMAL_TZ39 0x5e 50 + #define T14S_EC_EVT_KEY_FN_F12 0x62 51 + #define T14S_EC_EVT_KEY_FN_TAB 0x63 52 + #define T14S_EC_EVT_KEY_FN_F8 0x64 53 + #define T14S_EC_EVT_KEY_FN_F10 0x65 54 + #define T14S_EC_EVT_KEY_FN_F4 0x6a 55 + #define T14S_EC_EVT_KEY_FN_D 0x6b 56 + #define T14S_EC_EVT_KEY_FN_T 0x6c 57 + #define T14S_EC_EVT_KEY_FN_H 0x6d 58 + #define T14S_EC_EVT_KEY_FN_M 0x6e 59 + #define T14S_EC_EVT_KEY_FN_L 0x6f 60 + #define T14S_EC_EVT_KEY_FN_RIGHT_SHIFT 0x71 61 + #define T14S_EC_EVT_KEY_FN_ESC 0x74 62 + #define T14S_EC_EVT_KEY_FN_N 0x79 63 + #define T14S_EC_EVT_KEY_FN_F11 0x7a 64 + #define T14S_EC_EVT_KEY_FN_G 0x7e 65 + 66 + /* Hardware LED blink rate is 1 Hz (500ms off, 500ms on) */ 67 + #define T14S_EC_BLINK_RATE_ON_OFF_MS 500 68 + 69 + /* 70 + * Add a virtual offset on all key event codes for sparse keymap handling, 71 + * since the sparse keymap infrastructure does not map some raw key event 72 + * codes used by the EC. For example 0x16 (T14S_EC_EVT_KEY_FN_F7) is mapped 73 + * to KEY_MUTE if no offset is applied. 74 + */ 75 + #define T14S_EC_KEY_EVT_OFFSET 0x1000 76 + #define T14S_EC_KEY_ENTRY(key, value) \ 77 + { KE_KEY, T14S_EC_KEY_EVT_OFFSET + T14S_EC_EVT_KEY_##key, { value } } 78 + 79 + enum t14s_ec_led_status_t { 80 + T14S_EC_LED_OFF = 0x00, 81 + T14S_EC_LED_ON = 0x80, 82 + T14S_EC_LED_BLINK = 0xc0, 83 + }; 84 + 85 + struct t14s_ec_led_classdev { 86 + struct led_classdev led_classdev; 87 + int led; 88 + enum t14s_ec_led_status_t cache; 89 + struct t14s_ec *ec; 90 + }; 91 + 92 + struct t14s_ec { 93 + struct regmap *regmap; 94 + struct device *dev; 95 + struct t14s_ec_led_classdev led_pwr_btn; 96 + struct t14s_ec_led_classdev led_chrg_orange; 97 + struct t14s_ec_led_classdev led_chrg_white; 98 + struct t14s_ec_led_classdev led_lid_logo_dot; 99 + struct led_classdev kbd_backlight; 100 + struct led_classdev led_mic_mute; 101 + struct led_classdev led_spk_mute; 102 + struct input_dev *inputdev; 103 + }; 104 + 105 + static const struct regmap_config t14s_ec_regmap_config = { 106 + .reg_bits = 8, 107 + .val_bits = 8, 108 + .max_register = 0xff, 109 + }; 110 + 111 + static int t14s_ec_write(void *context, unsigned int reg, 112 + unsigned int val) 113 + { 114 + struct t14s_ec *ec = context; 115 + struct i2c_client *client = to_i2c_client(ec->dev); 116 + u8 buf[5] = {T14S_EC_CMD_ECWR, reg, 0x00, 0x01, val}; 117 + int ret; 118 + 119 + ret = i2c_master_send(client, buf, sizeof(buf)); 120 + if (ret < 0) 121 + return ret; 122 + 123 + return 0; 124 + } 125 + 126 + static int t14s_ec_read(void *context, unsigned int reg, 127 + unsigned int *val) 128 + { 129 + struct t14s_ec *ec = context; 130 + struct i2c_client *client = to_i2c_client(ec->dev); 131 + u8 buf[4] = {T14S_EC_CMD_ECRD, reg, 0x00, 0x01}; 132 + struct i2c_msg request, response; 133 + u8 result; 134 + int ret; 135 + 136 + request.addr = client->addr; 137 + request.flags = I2C_M_STOP; 138 + request.len = sizeof(buf); 139 + request.buf = buf; 140 + response.addr = client->addr; 141 + response.flags = I2C_M_RD; 142 + response.len = 1; 143 + response.buf = &result; 144 + 145 + i2c_lock_bus(client->adapter, I2C_LOCK_SEGMENT); 146 + 147 + ret = __i2c_transfer(client->adapter, &request, 1); 148 + if (ret < 0) 149 + goto out; 150 + 151 + ret = __i2c_transfer(client->adapter, &response, 1); 152 + if (ret < 0) 153 + goto out; 154 + 155 + *val = result; 156 + ret = 0; 157 + 158 + out: 159 + i2c_unlock_bus(client->adapter, I2C_LOCK_SEGMENT); 160 + return ret; 161 + } 162 + 163 + static const struct regmap_bus t14s_ec_regmap_bus = { 164 + .reg_write = t14s_ec_write, 165 + .reg_read = t14s_ec_read, 166 + }; 167 + 168 + static int t14s_ec_read_evt(struct t14s_ec *ec, u8 *val) 169 + { 170 + struct i2c_client *client = to_i2c_client(ec->dev); 171 + u8 buf[4] = {T14S_EC_CMD_EVT, 0x00, 0x00, 0x01}; 172 + struct i2c_msg request, response; 173 + int ret; 174 + 175 + request.addr = client->addr; 176 + request.flags = I2C_M_STOP; 177 + request.len = sizeof(buf); 178 + request.buf = buf; 179 + response.addr = client->addr; 180 + response.flags = I2C_M_RD; 181 + response.len = 1; 182 + response.buf = val; 183 + 184 + i2c_lock_bus(client->adapter, I2C_LOCK_SEGMENT); 185 + 186 + ret = __i2c_transfer(client->adapter, &request, 1); 187 + if (ret < 0) 188 + goto out; 189 + 190 + ret = __i2c_transfer(client->adapter, &response, 1); 191 + if (ret < 0) 192 + goto out; 193 + 194 + ret = 0; 195 + 196 + out: 197 + i2c_unlock_bus(client->adapter, I2C_LOCK_SEGMENT); 198 + return ret; 199 + } 200 + 201 + static int t14s_led_set_status(struct t14s_ec *ec, 202 + struct t14s_ec_led_classdev *led, 203 + const enum t14s_ec_led_status_t ledstatus) 204 + { 205 + int ret; 206 + 207 + ret = regmap_write(ec->regmap, T14S_EC_REG_LED, 208 + led->led | ledstatus); 209 + if (ret < 0) 210 + return ret; 211 + 212 + led->cache = ledstatus; 213 + return 0; 214 + } 215 + 216 + static int t14s_led_brightness_set(struct led_classdev *led_cdev, 217 + enum led_brightness brightness) 218 + { 219 + struct t14s_ec_led_classdev *led = container_of(led_cdev, 220 + struct t14s_ec_led_classdev, led_classdev); 221 + enum t14s_ec_led_status_t new_state; 222 + 223 + if (brightness == LED_OFF) 224 + new_state = T14S_EC_LED_OFF; 225 + else if (led->cache == T14S_EC_LED_BLINK) 226 + new_state = T14S_EC_LED_BLINK; 227 + else 228 + new_state = T14S_EC_LED_ON; 229 + 230 + return t14s_led_set_status(led->ec, led, new_state); 231 + } 232 + 233 + static int t14s_led_blink_set(struct led_classdev *led_cdev, 234 + unsigned long *delay_on, 235 + unsigned long *delay_off) 236 + { 237 + struct t14s_ec_led_classdev *led = container_of(led_cdev, 238 + struct t14s_ec_led_classdev, led_classdev); 239 + 240 + if (*delay_on == 0 && *delay_off == 0) { 241 + /* Userspace does not provide a blink rate; we can choose it */ 242 + *delay_on = T14S_EC_BLINK_RATE_ON_OFF_MS; 243 + *delay_off = T14S_EC_BLINK_RATE_ON_OFF_MS; 244 + } else if ((*delay_on != T14S_EC_BLINK_RATE_ON_OFF_MS) || 245 + (*delay_off != T14S_EC_BLINK_RATE_ON_OFF_MS)) 246 + return -EINVAL; 247 + 248 + return t14s_led_set_status(led->ec, led, T14S_EC_LED_BLINK); 249 + } 250 + 251 + static int t14s_init_led(struct t14s_ec *ec, struct t14s_ec_led_classdev *led, 252 + u8 id, const char *name) 253 + { 254 + led->led_classdev.name = name; 255 + led->led_classdev.flags = LED_RETAIN_AT_SHUTDOWN; 256 + led->led_classdev.max_brightness = 1; 257 + led->led_classdev.brightness_set_blocking = t14s_led_brightness_set; 258 + led->led_classdev.blink_set = t14s_led_blink_set; 259 + led->ec = ec; 260 + led->led = id; 261 + 262 + return devm_led_classdev_register(ec->dev, &led->led_classdev); 263 + } 264 + 265 + static int t14s_leds_probe(struct t14s_ec *ec) 266 + { 267 + int ret; 268 + 269 + ret = t14s_init_led(ec, &ec->led_pwr_btn, 0, "platform::power"); 270 + if (ret) 271 + return ret; 272 + 273 + ret = t14s_init_led(ec, &ec->led_chrg_orange, 1, 274 + "platform:amber:battery-charging"); 275 + if (ret) 276 + return ret; 277 + 278 + ret = t14s_init_led(ec, &ec->led_chrg_white, 2, 279 + "platform:white:battery-full"); 280 + if (ret) 281 + return ret; 282 + 283 + ret = t14s_init_led(ec, &ec->led_lid_logo_dot, 10, 284 + "platform::lid_logo_dot"); 285 + if (ret) 286 + return ret; 287 + 288 + return 0; 289 + } 290 + 291 + static int t14s_kbd_bl_set(struct led_classdev *led_cdev, 292 + enum led_brightness brightness) 293 + { 294 + struct t14s_ec *ec = container_of(led_cdev, struct t14s_ec, 295 + kbd_backlight); 296 + int ret; 297 + u8 val; 298 + 299 + val = FIELD_PREP(T14S_EC_KBD_BL1_MASK, brightness); 300 + ret = regmap_update_bits(ec->regmap, T14S_EC_REG_KBD_BL1, 301 + T14S_EC_KBD_BL1_MASK, val); 302 + if (ret < 0) 303 + return ret; 304 + 305 + val = FIELD_PREP(T14S_EC_KBD_BL2_MASK, brightness); 306 + ret = regmap_update_bits(ec->regmap, T14S_EC_REG_KBD_BL2, 307 + T14S_EC_KBD_BL2_MASK, val); 308 + if (ret < 0) 309 + return ret; 310 + 311 + return 0; 312 + } 313 + 314 + static enum led_brightness t14s_kbd_bl_get(struct led_classdev *led_cdev) 315 + { 316 + struct t14s_ec *ec = container_of(led_cdev, struct t14s_ec, 317 + kbd_backlight); 318 + unsigned int val; 319 + int ret; 320 + 321 + ret = regmap_read(ec->regmap, T14S_EC_REG_KBD_BL1, &val); 322 + if (ret < 0) 323 + return ret; 324 + 325 + return FIELD_GET(T14S_EC_KBD_BL1_MASK, val); 326 + } 327 + 328 + static void t14s_kbd_bl_update(struct t14s_ec *ec) 329 + { 330 + enum led_brightness brightness = t14s_kbd_bl_get(&ec->kbd_backlight); 331 + 332 + led_classdev_notify_brightness_hw_changed(&ec->kbd_backlight, brightness); 333 + } 334 + 335 + static int t14s_kbd_backlight_probe(struct t14s_ec *ec) 336 + { 337 + ec->kbd_backlight.name = "platform::kbd_backlight"; 338 + ec->kbd_backlight.flags = LED_BRIGHT_HW_CHANGED; 339 + ec->kbd_backlight.max_brightness = 2; 340 + ec->kbd_backlight.brightness_set_blocking = t14s_kbd_bl_set; 341 + ec->kbd_backlight.brightness_get = t14s_kbd_bl_get; 342 + 343 + return devm_led_classdev_register(ec->dev, &ec->kbd_backlight); 344 + } 345 + 346 + static enum led_brightness t14s_audio_led_get(struct t14s_ec *ec, u8 led_bit) 347 + { 348 + unsigned int val; 349 + int ret; 350 + 351 + ret = regmap_read(ec->regmap, T14S_EC_REG_AUD, &val); 352 + if (ret < 0) 353 + return ret; 354 + 355 + return !!(val & led_bit) ? LED_ON : LED_OFF; 356 + } 357 + 358 + static enum led_brightness t14s_audio_led_set(struct t14s_ec *ec, 359 + u8 led_mask, 360 + enum led_brightness brightness) 361 + { 362 + return regmap_assign_bits(ec->regmap, T14S_EC_REG_AUD, led_mask, brightness > 0); 363 + } 364 + 365 + static enum led_brightness t14s_mic_mute_led_get(struct led_classdev *led_cdev) 366 + { 367 + struct t14s_ec *ec = container_of(led_cdev, struct t14s_ec, 368 + led_mic_mute); 369 + 370 + return t14s_audio_led_get(ec, T14S_EC_MIC_MUTE_LED); 371 + } 372 + 373 + static int t14s_mic_mute_led_set(struct led_classdev *led_cdev, 374 + enum led_brightness brightness) 375 + { 376 + struct t14s_ec *ec = container_of(led_cdev, struct t14s_ec, 377 + led_mic_mute); 378 + 379 + return t14s_audio_led_set(ec, T14S_EC_MIC_MUTE_LED, brightness); 380 + } 381 + 382 + static enum led_brightness t14s_spk_mute_led_get(struct led_classdev *led_cdev) 383 + { 384 + struct t14s_ec *ec = container_of(led_cdev, struct t14s_ec, 385 + led_spk_mute); 386 + 387 + return t14s_audio_led_get(ec, T14S_EC_SPK_MUTE_LED); 388 + } 389 + 390 + static int t14s_spk_mute_led_set(struct led_classdev *led_cdev, 391 + enum led_brightness brightness) 392 + { 393 + struct t14s_ec *ec = container_of(led_cdev, struct t14s_ec, 394 + led_spk_mute); 395 + 396 + return t14s_audio_led_set(ec, T14S_EC_SPK_MUTE_LED, brightness); 397 + } 398 + 399 + static int t14s_kbd_audio_led_probe(struct t14s_ec *ec) 400 + { 401 + int ret; 402 + 403 + ec->led_mic_mute.name = "platform::micmute"; 404 + ec->led_mic_mute.max_brightness = 1; 405 + ec->led_mic_mute.default_trigger = "audio-micmute"; 406 + ec->led_mic_mute.brightness_set_blocking = t14s_mic_mute_led_set; 407 + ec->led_mic_mute.brightness_get = t14s_mic_mute_led_get; 408 + 409 + ec->led_spk_mute.name = "platform::mute"; 410 + ec->led_spk_mute.max_brightness = 1; 411 + ec->led_spk_mute.default_trigger = "audio-mute"; 412 + ec->led_spk_mute.brightness_set_blocking = t14s_spk_mute_led_set; 413 + ec->led_spk_mute.brightness_get = t14s_spk_mute_led_get; 414 + 415 + ret = devm_led_classdev_register(ec->dev, &ec->led_mic_mute); 416 + if (ret) 417 + return ret; 418 + 419 + return devm_led_classdev_register(ec->dev, &ec->led_spk_mute); 420 + } 421 + 422 + static const struct key_entry t14s_keymap[] = { 423 + T14S_EC_KEY_ENTRY(FN_4, KEY_SLEEP), 424 + T14S_EC_KEY_ENTRY(FN_N, KEY_VENDOR), 425 + T14S_EC_KEY_ENTRY(FN_F4, KEY_MICMUTE), 426 + T14S_EC_KEY_ENTRY(FN_F7, KEY_SWITCHVIDEOMODE), 427 + T14S_EC_KEY_ENTRY(FN_F8, KEY_PERFORMANCE), 428 + T14S_EC_KEY_ENTRY(FN_F10, KEY_SELECTIVE_SCREENSHOT), 429 + T14S_EC_KEY_ENTRY(FN_F11, KEY_LINK_PHONE), 430 + T14S_EC_KEY_ENTRY(FN_F12, KEY_BOOKMARKS), 431 + T14S_EC_KEY_ENTRY(FN_SPACE, KEY_KBDILLUMTOGGLE), 432 + T14S_EC_KEY_ENTRY(FN_ESC, KEY_FN_ESC), 433 + T14S_EC_KEY_ENTRY(FN_TAB, KEY_ZOOM), 434 + T14S_EC_KEY_ENTRY(FN_RIGHT_SHIFT, KEY_FN_RIGHT_SHIFT), 435 + T14S_EC_KEY_ENTRY(TP_DOUBLE_TAP, KEY_PROG4), 436 + { KE_END } 437 + }; 438 + 439 + static int t14s_input_probe(struct t14s_ec *ec) 440 + { 441 + int ret; 442 + 443 + ec->inputdev = devm_input_allocate_device(ec->dev); 444 + if (!ec->inputdev) 445 + return -ENOMEM; 446 + 447 + ec->inputdev->name = "ThinkPad Extra Buttons"; 448 + ec->inputdev->phys = "thinkpad/input0"; 449 + ec->inputdev->id.bustype = BUS_HOST; 450 + ec->inputdev->dev.parent = ec->dev; 451 + 452 + ret = sparse_keymap_setup(ec->inputdev, t14s_keymap, NULL); 453 + if (ret) 454 + return ret; 455 + 456 + return input_register_device(ec->inputdev); 457 + } 458 + 459 + static irqreturn_t t14s_ec_irq_handler(int irq, void *data) 460 + { 461 + struct t14s_ec *ec = data; 462 + int ret; 463 + u8 val; 464 + 465 + ret = t14s_ec_read_evt(ec, &val); 466 + if (ret < 0) { 467 + dev_err(ec->dev, "Failed to read event\n"); 468 + return IRQ_HANDLED; 469 + } 470 + 471 + switch (val) { 472 + case T14S_EC_EVT_NONE: 473 + break; 474 + case T14S_EC_EVT_KEY_FN_SPACE: 475 + t14s_kbd_bl_update(ec); 476 + fallthrough; 477 + case T14S_EC_EVT_KEY_FN_F4: 478 + case T14S_EC_EVT_KEY_FN_F7: 479 + case T14S_EC_EVT_KEY_FN_4: 480 + case T14S_EC_EVT_KEY_FN_F8: 481 + case T14S_EC_EVT_KEY_FN_F12: 482 + case T14S_EC_EVT_KEY_FN_TAB: 483 + case T14S_EC_EVT_KEY_FN_F10: 484 + case T14S_EC_EVT_KEY_FN_N: 485 + case T14S_EC_EVT_KEY_FN_F11: 486 + case T14S_EC_EVT_KEY_FN_ESC: 487 + case T14S_EC_EVT_KEY_FN_RIGHT_SHIFT: 488 + case T14S_EC_EVT_KEY_TP_DOUBLE_TAP: 489 + sparse_keymap_report_event(ec->inputdev, 490 + T14S_EC_KEY_EVT_OFFSET + val, 1, true); 491 + break; 492 + case T14S_EC_EVT_AC_CONNECTED: 493 + dev_dbg(ec->dev, "AC connected\n"); 494 + break; 495 + case T14S_EC_EVT_AC_DISCONNECTED: 496 + dev_dbg(ec->dev, "AC disconnected\n"); 497 + break; 498 + case T14S_EC_EVT_KEY_POWER: 499 + dev_dbg(ec->dev, "power button\n"); 500 + break; 501 + case T14S_EC_EVT_LID_OPEN: 502 + dev_dbg(ec->dev, "LID open\n"); 503 + break; 504 + case T14S_EC_EVT_LID_CLOSED: 505 + dev_dbg(ec->dev, "LID closed\n"); 506 + break; 507 + case T14S_EC_EVT_THERMAL_TZ40: 508 + dev_dbg(ec->dev, "Thermal Zone 40 Status Change Event (CPU/GPU)\n"); 509 + break; 510 + case T14S_EC_EVT_THERMAL_TZ42: 511 + dev_dbg(ec->dev, "Thermal Zone 42 Status Change Event (Battery)\n"); 512 + break; 513 + case T14S_EC_EVT_THERMAL_TZ39: 514 + dev_dbg(ec->dev, "Thermal Zone 39 Status Change Event (CPU/GPU)\n"); 515 + break; 516 + case T14S_EC_EVT_KEY_FN_G: 517 + dev_dbg(ec->dev, "FN + G - toggle double-tapping\n"); 518 + break; 519 + case T14S_EC_EVT_KEY_FN_L: 520 + dev_dbg(ec->dev, "FN + L - low performance mode\n"); 521 + break; 522 + case T14S_EC_EVT_KEY_FN_M: 523 + dev_dbg(ec->dev, "FN + M - medium performance mode\n"); 524 + break; 525 + case T14S_EC_EVT_KEY_FN_H: 526 + dev_dbg(ec->dev, "FN + H - high performance mode\n"); 527 + break; 528 + case T14S_EC_EVT_KEY_FN_T: 529 + dev_dbg(ec->dev, "FN + T - toggle intelligent cooling mode\n"); 530 + break; 531 + case T14S_EC_EVT_KEY_FN_D: 532 + dev_dbg(ec->dev, "FN + D - toggle privacy guard mode\n"); 533 + break; 534 + default: 535 + dev_info(ec->dev, "Unknown EC event: 0x%02x\n", val); 536 + break; 537 + } 538 + 539 + return IRQ_HANDLED; 540 + } 541 + 542 + static int t14s_ec_probe(struct i2c_client *client) 543 + { 544 + struct device *dev = &client->dev; 545 + struct t14s_ec *ec; 546 + int ret; 547 + 548 + ec = devm_kzalloc(dev, sizeof(*ec), GFP_KERNEL); 549 + if (!ec) 550 + return -ENOMEM; 551 + 552 + ec->dev = dev; 553 + 554 + ec->regmap = devm_regmap_init(dev, &t14s_ec_regmap_bus, 555 + ec, &t14s_ec_regmap_config); 556 + if (IS_ERR(ec->regmap)) 557 + return dev_err_probe(dev, PTR_ERR(ec->regmap), 558 + "Failed to init regmap\n"); 559 + 560 + ret = devm_request_threaded_irq(dev, client->irq, NULL, 561 + t14s_ec_irq_handler, 562 + IRQF_ONESHOT, dev_name(dev), ec); 563 + if (ret < 0) 564 + return dev_err_probe(dev, ret, "Failed to get IRQ\n"); 565 + 566 + ret = t14s_leds_probe(ec); 567 + if (ret < 0) 568 + return ret; 569 + 570 + ret = t14s_kbd_backlight_probe(ec); 571 + if (ret < 0) 572 + return ret; 573 + 574 + ret = t14s_kbd_audio_led_probe(ec); 575 + if (ret < 0) 576 + return ret; 577 + 578 + ret = t14s_input_probe(ec); 579 + if (ret < 0) 580 + return ret; 581 + 582 + /* 583 + * Disable wakeup support by default, because the driver currently does 584 + * not support masking any events and the laptop should not wake up when 585 + * the LID is closed. 586 + */ 587 + device_wakeup_disable(dev); 588 + 589 + return 0; 590 + } 591 + 592 + static const struct of_device_id t14s_ec_of_match[] = { 593 + { .compatible = "lenovo,thinkpad-t14s-ec" }, 594 + {} 595 + }; 596 + MODULE_DEVICE_TABLE(of, t14s_ec_of_match); 597 + 598 + static const struct i2c_device_id t14s_ec_i2c_id_table[] = { 599 + { "thinkpad-t14s-ec", }, 600 + {} 601 + }; 602 + MODULE_DEVICE_TABLE(i2c, t14s_ec_i2c_id_table); 603 + 604 + static struct i2c_driver t14s_ec_i2c_driver = { 605 + .driver = { 606 + .name = "thinkpad-t14s-ec", 607 + .of_match_table = t14s_ec_of_match, 608 + }, 609 + .probe = t14s_ec_probe, 610 + .id_table = t14s_ec_i2c_id_table, 611 + }; 612 + module_i2c_driver(t14s_ec_i2c_driver); 613 + 614 + MODULE_AUTHOR("Sebastian Reichel <sre@kernel.org>"); 615 + MODULE_DESCRIPTION("Lenovo Thinkpad T14s Embedded Controller"); 616 + MODULE_LICENSE("GPL");