* [PATCH 0/3] platform: arm64: lenovo-thinkpad-t14s: Add support for the Gen 7 EC
@ 2026-11-06 22:01 Abel Vesa
2026-11-06 22:01 ` [PATCH 1/3] dt-bindings: embedded-controller: lenovo,thinkpad-t14s-ec: Add Gen 7 support Abel Vesa
` (2 more replies)
0 siblings, 3 replies; 7+ messages in thread
From: Abel Vesa @ 2026-11-06 22:01 UTC (permalink / raw)
To: Sebastian Reichel, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Hans de Goede, Ilpo Järvinen,
Bryan O'Donoghue, Bjorn Andersson, Konrad Dybcio, Abel Vesa
Cc: devicetree, linux-kernel, platform-driver-x86, linux-arm-msm, Abel Vesa
The Lenovo ThinkPad T14s Gen 7 (Snapdragon X2, Mahua) has an embedded
controller at the same I2C address as the Gen 6 one, with the same
register layout and event codes, but it speaks a different command
protocol over I2C. With the existing driver the EC never reports any
event and the LED and keyboard backlight writes have no effect.
The Gen 7 protocol is taken from the ACPI EC accessors in the Gen 7
firmware (RDEC, WREC and QEVT): reads, writes and event reads use the
0x80, 0x81 and 0x84 commands. The firmware also describes the EC
interrupt as edge triggered, active low.
Patch 1 adds a Gen 7 compatible to the binding, patch 2 adds the Gen 7
variant to the driver and patch 3 describes the EC on the T14s Gen 7.
Tested on a ThinkPad T14s Gen 7 (LCD): Fn+F4 reports KEY_MICMUTE, the
mic mute and lid logo LEDs (including the hardware blinking) and the
keyboard backlight work, and Fn+Space changes are reported through the
LED class.
Prepared with help from an LLM-based assistant.
---
Abel Vesa (3):
dt-bindings: embedded-controller: lenovo,thinkpad-t14s-ec: Add Gen 7 support
platform: arm64: lenovo-thinkpad-t14s: Add Gen 7 EC support
arm64: dts: qcom: mahua-lenovo-thinkpad-t14s-gen7: Describe the Embedded Controller
.../lenovo,thinkpad-t14s-ec.yaml | 4 +-
.../qcom/mahua-lenovo-thinkpad-t14s-gen7-lcd.dts | 24 +++
drivers/platform/arm64/lenovo-thinkpad-t14s.c | 176 +++++++++++++++++++--
3 files changed, 193 insertions(+), 11 deletions(-)
---
base-commit: ddfe51b0a0f5dc9e415cc2c8ad8db17532dd426b
change-id: 20261106-t14s-gen7-ec-297593bda0fc
Best regards,
--
Abel
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH 1/3] dt-bindings: embedded-controller: lenovo,thinkpad-t14s-ec: Add Gen 7 support
2026-11-06 22:01 [PATCH 0/3] platform: arm64: lenovo-thinkpad-t14s: Add support for the Gen 7 EC Abel Vesa
@ 2026-11-06 22:01 ` Abel Vesa
2026-11-06 22:01 ` [PATCH 2/3] platform: arm64: lenovo-thinkpad-t14s: Add Gen 7 EC support Abel Vesa
2026-11-06 22:01 ` [PATCH 3/3] arm64: dts: qcom: mahua-lenovo-thinkpad-t14s-gen7: Describe the Embedded Controller Abel Vesa
2 siblings, 0 replies; 7+ messages in thread
From: Abel Vesa @ 2026-11-06 22:01 UTC (permalink / raw)
To: Sebastian Reichel, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Hans de Goede, Ilpo Järvinen,
Bryan O'Donoghue, Bjorn Andersson, Konrad Dybcio, Abel Vesa
Cc: devicetree, linux-kernel, platform-driver-x86, linux-arm-msm, Abel Vesa
The ThinkPad T14s Gen 7 (Snapdragon X2) has an embedded controller at
the same I2C address as the Gen 6 one and reports the same kind of
events, but it talks a different command protocol.
Add a compatible for it.
Signed-off-by: Abel Vesa <abel.vesa@oss.qualcomm.com>
---
.../bindings/embedded-controller/lenovo,thinkpad-t14s-ec.yaml | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/Documentation/devicetree/bindings/embedded-controller/lenovo,thinkpad-t14s-ec.yaml b/Documentation/devicetree/bindings/embedded-controller/lenovo,thinkpad-t14s-ec.yaml
index c87ccb5b3086..99e5e7ae3cb6 100644
--- a/Documentation/devicetree/bindings/embedded-controller/lenovo,thinkpad-t14s-ec.yaml
+++ b/Documentation/devicetree/bindings/embedded-controller/lenovo,thinkpad-t14s-ec.yaml
@@ -16,7 +16,9 @@ description:
properties:
compatible:
- const: lenovo,thinkpad-t14s-ec
+ enum:
+ - lenovo,thinkpad-t14s-ec
+ - lenovo,thinkpad-t14s-gen7-ec
reg:
const: 0x28
--
2.54.0
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH 2/3] platform: arm64: lenovo-thinkpad-t14s: Add Gen 7 EC support
2026-11-06 22:01 [PATCH 0/3] platform: arm64: lenovo-thinkpad-t14s: Add support for the Gen 7 EC Abel Vesa
2026-11-06 22:01 ` [PATCH 1/3] dt-bindings: embedded-controller: lenovo,thinkpad-t14s-ec: Add Gen 7 support Abel Vesa
@ 2026-11-06 22:01 ` Abel Vesa
2026-11-09 14:52 ` Hans de Goede
2026-11-06 22:01 ` [PATCH 3/3] arm64: dts: qcom: mahua-lenovo-thinkpad-t14s-gen7: Describe the Embedded Controller Abel Vesa
2 siblings, 1 reply; 7+ messages in thread
From: Abel Vesa @ 2026-11-06 22:01 UTC (permalink / raw)
To: Sebastian Reichel, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Hans de Goede, Ilpo Järvinen,
Bryan O'Donoghue, Bjorn Andersson, Konrad Dybcio, Abel Vesa
Cc: devicetree, linux-kernel, platform-driver-x86, linux-arm-msm, Abel Vesa
The embedded controller of the ThinkPad T14s Gen 7 uses the same
register layout and event codes as the Gen 6 one, but a different set
of I2C commands. Going by the ACPI EC accessors in the Gen 7 firmware:
- register read: write {0x80, reg}, then write {0x80} + read 1 byte
- register write: write {0x81, reg, val}
- event read: write {0x84, 0x00}, then write {0x84} + read 1 byte
With the Gen 6 commands the EC never reports any event and the LED
and keyboard backlight accesses have no effect.
Describe the bus accessors and event read per variant and add one for
the Gen 7. The Gen 7 firmware does not use the second keyboard backlight
register or the modern standby register, it drives the mic mute LED as a
generic LED (id 0x0e) instead of a bit in the audio register and, when
the keyboard backlight is cycled with Fn+Space, it also reports 3 for
the high level, so handle those differences too. Also handle the Gen 7
only events as known ones.
Signed-off-by: Abel Vesa <abel.vesa@oss.qualcomm.com>
---
drivers/platform/arm64/lenovo-thinkpad-t14s.c | 176 ++++++++++++++++++++++++--
1 file changed, 166 insertions(+), 10 deletions(-)
diff --git a/drivers/platform/arm64/lenovo-thinkpad-t14s.c b/drivers/platform/arm64/lenovo-thinkpad-t14s.c
index 7a875877c8f3..b488b85bf7c0 100644
--- a/drivers/platform/arm64/lenovo-thinkpad-t14s.c
+++ b/drivers/platform/arm64/lenovo-thinkpad-t14s.c
@@ -17,7 +17,9 @@
#include <linux/interrupt.h>
#include <linux/leds.h>
#include <linux/lockdep.h>
+#include <linux/minmax.h>
#include <linux/module.h>
+#include <linux/mutex.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <linux/pm.h>
@@ -26,6 +28,15 @@
#define T14S_EC_CMD_ECWR 0x03
#define T14S_EC_CMD_EVT 0xf0
+/*
+ * The Gen 7 (Snapdragon X2) EC uses a different command set over I2C, as
+ * described by the ACPI EC accessors in its firmware (RDEC, WREC, QEVT).
+ */
+#define T14S_GEN7_EC_CMD_RD 0x80
+#define T14S_GEN7_EC_CMD_WR 0x81
+#define T14S_GEN7_EC_CMD_EVT 0x84
+#define T14S_GEN7_EC_LED_MIC_MUTE 0x0e
+
#define T14S_EC_REG_LED 0x0c
#define T14S_EC_REG_KBD_BL1 0x0d
#define T14S_EC_REG_MODERN_STANDBY 0xe0
@@ -67,6 +78,15 @@
#define T14S_EC_EVT_KEY_FN_F11 0x7a
#define T14S_EC_EVT_KEY_FN_G 0x7e
+/* Gen 7 only */
+#define T14S_GEN7_EC_EVT_HKEY_1017 0x1c
+#define T14S_GEN7_EC_EVT_STATUS_35 0x35
+#define T14S_GEN7_EC_EVT_DYTC 0x3c
+#define T14S_GEN7_EC_EVT_STATUS_40 0x40
+#define T14S_GEN7_EC_EVT_STATUS_4B 0x4b
+#define T14S_GEN7_EC_EVT_STATUS_4E 0x4e
+#define T14S_GEN7_EC_EVT_HKEY_1016 0x76
+
/* Hardware LED blink rate is 1 Hz (500ms off, 500ms on) */
#define T14S_EC_BLINK_RATE_ON_OFF_MS 500
@@ -93,9 +113,24 @@ struct t14s_ec_led_classdev {
struct t14s_ec *ec;
};
+struct t14s_ec;
+
+struct t14s_ec_variant {
+ const struct regmap_bus *regmap_bus;
+ int (*read_evt)(struct t14s_ec *ec, u8 *val);
+ /* EC has the second keyboard backlight and the modern standby registers */
+ bool has_kbd_bl2_and_standby;
+ /* mic mute LED is a generic LED (in T14S_EC_REG_LED) and not a bit in T14S_EC_REG_AUD */
+ bool mic_mute_led_generic;
+};
+
struct t14s_ec {
struct regmap *regmap;
struct device *dev;
+ const struct t14s_ec_variant *variant;
+ /* serializes the multi-message Gen 7 EC transactions */
+ struct mutex xfer_lock;
+ enum led_brightness mic_mute_cache;
struct t14s_ec_led_classdev led_pwr_btn;
struct t14s_ec_led_classdev led_chrg_orange;
struct t14s_ec_led_classdev led_chrg_white;
@@ -206,6 +241,85 @@ static int t14s_ec_read_evt(struct t14s_ec *ec, u8 *val)
return ret;
}
+static int t14s_gen7_ec_cmd_read(struct t14s_ec *ec, u8 cmd, u8 arg, u8 *val)
+{
+ struct i2c_client *client = to_i2c_client(ec->dev);
+ u8 req[2] = { cmd, arg };
+ struct i2c_msg msgs[2] = {
+ { .addr = client->addr, .flags = 0, .len = 1, .buf = &req[0] },
+ { .addr = client->addr, .flags = I2C_M_RD, .len = 1, .buf = val },
+ };
+ int ret;
+
+ guard(mutex)(&ec->xfer_lock);
+
+ ret = i2c_master_send(client, req, sizeof(req));
+ if (ret < 0)
+ return ret;
+
+ fsleep(5000);
+
+ ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
+ if (ret < 0)
+ return ret;
+
+ return ret == ARRAY_SIZE(msgs) ? 0 : -EIO;
+}
+
+static int t14s_gen7_ec_write(void *context, unsigned int reg,
+ unsigned int val)
+{
+ struct t14s_ec *ec = context;
+ struct i2c_client *client = to_i2c_client(ec->dev);
+ u8 buf[3] = { T14S_GEN7_EC_CMD_WR, reg, val };
+ int ret;
+
+ guard(mutex)(&ec->xfer_lock);
+
+ ret = i2c_master_send(client, buf, sizeof(buf));
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int t14s_gen7_ec_read(void *context, unsigned int reg,
+ unsigned int *val)
+{
+ struct t14s_ec *ec = context;
+ u8 result;
+ int ret;
+
+ ret = t14s_gen7_ec_cmd_read(ec, T14S_GEN7_EC_CMD_RD, reg, &result);
+ if (ret)
+ return ret;
+
+ *val = result;
+ return 0;
+}
+
+static const struct regmap_bus t14s_gen7_ec_regmap_bus = {
+ .reg_write = t14s_gen7_ec_write,
+ .reg_read = t14s_gen7_ec_read,
+};
+
+static int t14s_gen7_ec_read_evt(struct t14s_ec *ec, u8 *val)
+{
+ return t14s_gen7_ec_cmd_read(ec, T14S_GEN7_EC_CMD_EVT, 0x00, val);
+}
+
+static const struct t14s_ec_variant t14s_ec_variant = {
+ .regmap_bus = &t14s_ec_regmap_bus,
+ .read_evt = t14s_ec_read_evt,
+ .has_kbd_bl2_and_standby = true,
+};
+
+static const struct t14s_ec_variant t14s_gen7_ec_variant = {
+ .regmap_bus = &t14s_gen7_ec_regmap_bus,
+ .read_evt = t14s_gen7_ec_read_evt,
+ .mic_mute_led_generic = true,
+};
+
static void t14s_ec_write_sequence(struct t14s_ec *ec, u8 reg, u8 val, u8 cnt)
{
int i;
@@ -318,6 +432,9 @@ static int t14s_kbd_bl_set(struct led_classdev *led_cdev,
if (ret < 0)
return ret;
+ if (!ec->variant->has_kbd_bl2_and_standby)
+ return 0;
+
val = FIELD_PREP(T14S_EC_KBD_BL2_MASK, brightness);
ret = regmap_update_bits(ec->regmap, T14S_EC_REG_KBD_BL2,
T14S_EC_KBD_BL2_MASK, val);
@@ -338,7 +455,11 @@ static enum led_brightness t14s_kbd_bl_get(struct led_classdev *led_cdev)
if (ret < 0)
return ret;
- return FIELD_GET(T14S_EC_KBD_BL1_MASK, val);
+ /*
+ * When cycled with Fn+Space, the Gen 7 EC also uses 3 for the high
+ * level, which looks the same as 2.
+ */
+ return min(FIELD_GET(T14S_EC_KBD_BL1_MASK, val), 2);
}
static void t14s_kbd_bl_update(struct t14s_ec *ec)
@@ -383,6 +504,9 @@ static enum led_brightness t14s_mic_mute_led_get(struct led_classdev *led_cdev)
struct t14s_ec *ec = container_of(led_cdev, struct t14s_ec,
led_mic_mute);
+ if (ec->variant->mic_mute_led_generic)
+ return ec->mic_mute_cache;
+
return t14s_audio_led_get(ec, T14S_EC_MIC_MUTE_LED);
}
@@ -391,8 +515,19 @@ static int t14s_mic_mute_led_set(struct led_classdev *led_cdev,
{
struct t14s_ec *ec = container_of(led_cdev, struct t14s_ec,
led_mic_mute);
+ int ret;
- return t14s_audio_led_set(ec, T14S_EC_MIC_MUTE_LED, brightness);
+ if (!ec->variant->mic_mute_led_generic)
+ return t14s_audio_led_set(ec, T14S_EC_MIC_MUTE_LED, brightness);
+
+ ret = regmap_write(ec->regmap, T14S_EC_REG_LED,
+ T14S_GEN7_EC_LED_MIC_MUTE |
+ (brightness ? T14S_EC_LED_ON : T14S_EC_LED_OFF));
+ if (ret < 0)
+ return ret;
+
+ ec->mic_mute_cache = brightness ? LED_ON : LED_OFF;
+ return 0;
}
static enum led_brightness t14s_spk_mute_led_get(struct led_classdev *led_cdev)
@@ -478,7 +613,7 @@ static irqreturn_t t14s_ec_irq_handler(int irq, void *data)
int ret;
u8 val;
- ret = t14s_ec_read_evt(ec, &val);
+ ret = ec->variant->read_evt(ec, &val);
if (ret < 0) {
dev_err(ec->dev, "Failed to read event\n");
return IRQ_HANDLED;
@@ -553,6 +688,16 @@ static irqreturn_t t14s_ec_irq_handler(int irq, void *data)
case T14S_EC_EVT_KEY_FN_D:
dev_dbg(ec->dev, "FN + D - toggle privacy guard mode\n");
break;
+ case T14S_GEN7_EC_EVT_HKEY_1017:
+ case T14S_GEN7_EC_EVT_HKEY_1016:
+ case T14S_GEN7_EC_EVT_DYTC:
+ /* Status events the Gen 7 firmware does not act upon either */
+ case T14S_GEN7_EC_EVT_STATUS_35:
+ case T14S_GEN7_EC_EVT_STATUS_40:
+ case T14S_GEN7_EC_EVT_STATUS_4B:
+ case T14S_GEN7_EC_EVT_STATUS_4E:
+ dev_dbg(ec->dev, "EC event 0x%02x\n", val);
+ break;
default:
dev_info(ec->dev, "Unknown EC event: 0x%02x\n", val);
break;
@@ -572,9 +717,17 @@ static int t14s_ec_probe(struct i2c_client *client)
return -ENOMEM;
ec->dev = dev;
+ ec->variant = i2c_get_match_data(client);
+ if (!ec->variant)
+ ec->variant = &t14s_ec_variant;
+
+ ret = devm_mutex_init(dev, &ec->xfer_lock);
+ if (ret)
+ return ret;
+
i2c_set_clientdata(client, ec);
- ec->regmap = devm_regmap_init(dev, &t14s_ec_regmap_bus,
+ ec->regmap = devm_regmap_init(dev, ec->variant->regmap_bus,
ec, &t14s_ec_regmap_config);
if (IS_ERR(ec->regmap))
return dev_err_probe(dev, PTR_ERR(ec->regmap),
@@ -618,8 +771,9 @@ static int t14s_ec_suspend(struct device *dev)
led_classdev_suspend(&ec->kbd_backlight);
- t14s_ec_write_sequence(ec, T14S_EC_REG_MODERN_STANDBY,
- T14S_EC_MODERN_STANDBY_ENTRY, 3);
+ if (ec->variant->has_kbd_bl2_and_standby)
+ t14s_ec_write_sequence(ec, T14S_EC_REG_MODERN_STANDBY,
+ T14S_EC_MODERN_STANDBY_ENTRY, 3);
return 0;
}
@@ -628,8 +782,9 @@ static int t14s_ec_resume(struct device *dev)
{
struct t14s_ec *ec = dev_get_drvdata(dev);
- t14s_ec_write_sequence(ec, T14S_EC_REG_MODERN_STANDBY,
- T14S_EC_MODERN_STANDBY_EXIT, 3);
+ if (ec->variant->has_kbd_bl2_and_standby)
+ t14s_ec_write_sequence(ec, T14S_EC_REG_MODERN_STANDBY,
+ T14S_EC_MODERN_STANDBY_EXIT, 3);
led_classdev_resume(&ec->kbd_backlight);
@@ -637,13 +792,14 @@ static int t14s_ec_resume(struct device *dev)
}
static const struct of_device_id t14s_ec_of_match[] = {
- { .compatible = "lenovo,thinkpad-t14s-ec" },
+ { .compatible = "lenovo,thinkpad-t14s-ec", .data = &t14s_ec_variant },
+ { .compatible = "lenovo,thinkpad-t14s-gen7-ec", .data = &t14s_gen7_ec_variant },
{}
};
MODULE_DEVICE_TABLE(of, t14s_ec_of_match);
static const struct i2c_device_id t14s_ec_i2c_id_table[] = {
- { .name = "thinkpad-t14s-ec" },
+ { .name = "thinkpad-t14s-ec", .driver_data = (kernel_ulong_t)&t14s_ec_variant },
{ }
};
MODULE_DEVICE_TABLE(i2c, t14s_ec_i2c_id_table);
--
2.54.0
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH 3/3] arm64: dts: qcom: mahua-lenovo-thinkpad-t14s-gen7: Describe the Embedded Controller
2026-11-06 22:01 [PATCH 0/3] platform: arm64: lenovo-thinkpad-t14s: Add support for the Gen 7 EC Abel Vesa
2026-11-06 22:01 ` [PATCH 1/3] dt-bindings: embedded-controller: lenovo,thinkpad-t14s-ec: Add Gen 7 support Abel Vesa
2026-11-06 22:01 ` [PATCH 2/3] platform: arm64: lenovo-thinkpad-t14s: Add Gen 7 EC support Abel Vesa
@ 2026-11-06 22:01 ` Abel Vesa
2026-11-09 10:19 ` Konrad Dybcio
2 siblings, 1 reply; 7+ messages in thread
From: Abel Vesa @ 2026-11-06 22:01 UTC (permalink / raw)
To: Sebastian Reichel, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Hans de Goede, Ilpo Järvinen,
Bryan O'Donoghue, Bjorn Andersson, Konrad Dybcio, Abel Vesa
Cc: devicetree, linux-kernel, platform-driver-x86, linux-arm-msm, Abel Vesa
The ThinkPad T14s Gen 7 has its embedded controller at 0x28 on QUP
i2c9 (ACPI \_SB.IC10). It uses a different command protocol than the
Gen 6 one, so describe it with the Gen 7 compatible. Its interrupt is
GPIO 66, as on the Gen 6, but the firmware describes it as edge
triggered, active low. Using a level trigger makes the handler fire
continuously while the EC holds the line low, with no event pending.
This provides the EC hotkeys, LEDs and keyboard backlight.
Signed-off-by: Abel Vesa <abel.vesa@oss.qualcomm.com>
---
.../qcom/mahua-lenovo-thinkpad-t14s-gen7-lcd.dts | 24 ++++++++++++++++++++++
1 file changed, 24 insertions(+)
diff --git a/arch/arm64/boot/dts/qcom/mahua-lenovo-thinkpad-t14s-gen7-lcd.dts b/arch/arm64/boot/dts/qcom/mahua-lenovo-thinkpad-t14s-gen7-lcd.dts
index 26d6d8285e04..05814dc8d905 100644
--- a/arch/arm64/boot/dts/qcom/mahua-lenovo-thinkpad-t14s-gen7-lcd.dts
+++ b/arch/arm64/boot/dts/qcom/mahua-lenovo-thinkpad-t14s-gen7-lcd.dts
@@ -735,6 +735,24 @@ ptn3222_fpr: redriver@4f {
};
};
+&i2c9 {
+ clock-frequency = <400000>;
+
+ status = "okay";
+
+ embedded-controller@28 {
+ compatible = "lenovo,thinkpad-t14s-gen7-ec";
+ reg = <0x28>;
+
+ interrupts-extended = <&tlmm 66 IRQ_TYPE_EDGE_FALLING>;
+
+ pinctrl-0 = <&ec_int_n_default>;
+ pinctrl-names = "default";
+
+ wakeup-source;
+ };
+};
+
&lpass_vamacro {
pinctrl-0 = <&dmic01_default>;
pinctrl-names = "default";
@@ -987,6 +1005,12 @@ &tlmm {
<44 4>, /* TPM */
<139 1>; /* TZ trigger */
+ ec_int_n_default: ec-int-n-state {
+ pins = "gpio66";
+ function = "gpio";
+ bias-disable;
+ };
+
edp_bl_en: edp-bl-en-state {
pins = "gpio18";
function = "gpio";
--
2.54.0
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH 3/3] arm64: dts: qcom: mahua-lenovo-thinkpad-t14s-gen7: Describe the Embedded Controller
2026-11-06 22:01 ` [PATCH 3/3] arm64: dts: qcom: mahua-lenovo-thinkpad-t14s-gen7: Describe the Embedded Controller Abel Vesa
@ 2026-11-09 10:19 ` Konrad Dybcio
2026-11-09 11:07 ` Abel Vesa
0 siblings, 1 reply; 7+ messages in thread
From: Konrad Dybcio @ 2026-11-09 10:19 UTC (permalink / raw)
To: Abel Vesa, Sebastian Reichel, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Hans de Goede, Ilpo Järvinen,
Bryan O'Donoghue, Bjorn Andersson, Konrad Dybcio, Abel Vesa
Cc: devicetree, linux-kernel, platform-driver-x86, linux-arm-msm
On 11/6/26 11:01 PM, Abel Vesa wrote:
> The ThinkPad T14s Gen 7 has its embedded controller at 0x28 on QUP
> i2c9 (ACPI \_SB.IC10). It uses a different command protocol than the
> Gen 6 one, so describe it with the Gen 7 compatible. Its interrupt is
> GPIO 66, as on the Gen 6, but the firmware describes it as edge
> triggered, active low. Using a level trigger makes the handler fire
> continuously while the EC holds the line low, with no event pending.
>
> This provides the EC hotkeys, LEDs and keyboard backlight.
>
> Signed-off-by: Abel Vesa <abel.vesa@oss.qualcomm.com>
> ---
[...]
> &lpass_vamacro {
> pinctrl-0 = <&dmic01_default>;
> pinctrl-names = "default";
> @@ -987,6 +1005,12 @@ &tlmm {
> <44 4>, /* TPM */
> <139 1>; /* TZ trigger */
>
> + ec_int_n_default: ec-int-n-state {
> + pins = "gpio66";
> + function = "gpio";
> + bias-disable;
> + };
nit: please keep these sorted by pin #
Reviewed-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
Konrad
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH 3/3] arm64: dts: qcom: mahua-lenovo-thinkpad-t14s-gen7: Describe the Embedded Controller
2026-11-09 10:19 ` Konrad Dybcio
@ 2026-11-09 11:07 ` Abel Vesa
0 siblings, 0 replies; 7+ messages in thread
From: Abel Vesa @ 2026-11-09 11:07 UTC (permalink / raw)
To: Konrad Dybcio
Cc: Sebastian Reichel, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Hans de Goede, Ilpo Järvinen,
Bryan O'Donoghue, Bjorn Andersson, Konrad Dybcio, Abel Vesa,
devicetree, linux-kernel, platform-driver-x86, linux-arm-msm
On 26-11-09 11:19:46, Konrad Dybcio wrote:
> On 11/6/26 11:01 PM, Abel Vesa wrote:
> > The ThinkPad T14s Gen 7 has its embedded controller at 0x28 on QUP
> > i2c9 (ACPI \_SB.IC10). It uses a different command protocol than the
> > Gen 6 one, so describe it with the Gen 7 compatible. Its interrupt is
> > GPIO 66, as on the Gen 6, but the firmware describes it as edge
> > triggered, active low. Using a level trigger makes the handler fire
> > continuously while the EC holds the line low, with no event pending.
> >
> > This provides the EC hotkeys, LEDs and keyboard backlight.
> >
> > Signed-off-by: Abel Vesa <abel.vesa@oss.qualcomm.com>
> > ---
>
> [...]
>
> > &lpass_vamacro {
> > pinctrl-0 = <&dmic01_default>;
> > pinctrl-names = "default";
> > @@ -987,6 +1005,12 @@ &tlmm {
> > <44 4>, /* TPM */
> > <139 1>; /* TZ trigger */
> >
> > + ec_int_n_default: ec-int-n-state {
> > + pins = "gpio66";
> > + function = "gpio";
> > + bias-disable;
> > + };
>
> nit: please keep these sorted by pin #
Yep, will fix in v2.
>
> Reviewed-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
Thanks,
Abel
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH 2/3] platform: arm64: lenovo-thinkpad-t14s: Add Gen 7 EC support
2026-11-06 22:01 ` [PATCH 2/3] platform: arm64: lenovo-thinkpad-t14s: Add Gen 7 EC support Abel Vesa
@ 2026-11-09 14:52 ` Hans de Goede
0 siblings, 0 replies; 7+ messages in thread
From: Hans de Goede @ 2026-11-09 14:52 UTC (permalink / raw)
To: Abel Vesa, Sebastian Reichel, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Ilpo Järvinen, Bryan O'Donoghue,
Bjorn Andersson, Konrad Dybcio, Abel Vesa
Cc: devicetree, linux-kernel, platform-driver-x86, linux-arm-msm
[-- Attachment #1: Type: text/plain, Size: 12812 bytes --]
Hi Abel,
On 6-Nov-26 23:01, Abel Vesa wrote:
> The embedded controller of the ThinkPad T14s Gen 7 uses the same
> register layout and event codes as the Gen 6 one, but a different set
> of I2C commands. Going by the ACPI EC accessors in the Gen 7 firmware:
>
> - register read: write {0x80, reg}, then write {0x80} + read 1 byte
> - register write: write {0x81, reg, val}
> - event read: write {0x84, 0x00}, then write {0x84} + read 1 byte
> > With the Gen 6 commands the EC never reports any event and the LED
> and keyboard backlight accesses have no effect.
Thank you for working in this. I've been testing this and my own T14s gen7
and also I've taken a closer look at the DSDT leading to a bunch of
improvements. Instead of making these as suggested I've just gone ahead and
implemented these as fixup-s to be squashed into a v2 patch.
For easier review by you I've split the improvements into 4 patches (attached):
1: Fix Fn-F12 not emitting a key-press + filter out some EC codes being reported
when pressing Fn + Fxx key combos which already send an I2C-HID kbd event.
2: Add Gen 7 suspend/resume handling
3: Cleanup: has_kbd_bl2_and_standby and mic_mute_led_generic checks are
really just checks for the original (gen6) support vs gen7 support
drop the bools and directly check which variant ec->variant points
at instead.
4: Cleanup: has_kbd_bl2_and_standby and mic_mute_led_generic checks are
really just checks for the original (gen6) support vs gen7 support
drop the bools and directly check which variant ec->variant points
at instead.
> Describe the bus accessors and event read per variant and add one for
> the Gen 7. The Gen 7 firmware does not use the second keyboard backlight
> register or the modern standby register, it drives the mic mute LED as a
> generic LED (id 0x0e) instead of a bit in the audio register and, when
> the keyboard backlight is cycled with Fn+Space, it also reports 3 for
> the high level, so handle those differences too. Also handle the Gen 7
> only events as known ones.
So about these differences:
1: For the spk / mic mute LEDs, on a hunch I tried using std LED controls
with a LED idx/id of 15 for the spk mute LED and that also works.
I did not find either way (REG_AUD vs REG_LED) for the spk mute LED in
the DSDT, instead the DSDT ends up sending an ACPI notify event, relying
on some windows driver to handle this.
How did you find that REG_AUD still works, did you just try the gen 6
method ?
2: For the kbd backlight, again the DSDT ends up sending an ACPI notify event
and does not access the EC itself at all.
How did you find that T14S_EC_REG_KBD_BL1 also works on gen 7?
3: For suspend/resume the DSDT does have modern-standby handling both
making the LEDS glow and setting some magic bit (which presumably makes
the EC use less power or maybe makes it send less wakeup events if we
were to make the EC IRQ a wakeup source) ? This is implemented in
my second fixup patch.
Regards,
Hans
>
> Signed-off-by: Abel Vesa <abel.vesa@oss.qualcomm.com>
> ---
> drivers/platform/arm64/lenovo-thinkpad-t14s.c | 176 ++++++++++++++++++++++++--
> 1 file changed, 166 insertions(+), 10 deletions(-)
>
> diff --git a/drivers/platform/arm64/lenovo-thinkpad-t14s.c b/drivers/platform/arm64/lenovo-thinkpad-t14s.c
> index 7a875877c8f3..b488b85bf7c0 100644
> --- a/drivers/platform/arm64/lenovo-thinkpad-t14s.c
> +++ b/drivers/platform/arm64/lenovo-thinkpad-t14s.c
> @@ -17,7 +17,9 @@
> #include <linux/interrupt.h>
> #include <linux/leds.h>
> #include <linux/lockdep.h>
> +#include <linux/minmax.h>
> #include <linux/module.h>
> +#include <linux/mutex.h>
> #include <linux/regmap.h>
> #include <linux/slab.h>
> #include <linux/pm.h>
> @@ -26,6 +28,15 @@
> #define T14S_EC_CMD_ECWR 0x03
> #define T14S_EC_CMD_EVT 0xf0
>
> +/*
> + * The Gen 7 (Snapdragon X2) EC uses a different command set over I2C, as
> + * described by the ACPI EC accessors in its firmware (RDEC, WREC, QEVT).
> + */
> +#define T14S_GEN7_EC_CMD_RD 0x80
> +#define T14S_GEN7_EC_CMD_WR 0x81
> +#define T14S_GEN7_EC_CMD_EVT 0x84
> +#define T14S_GEN7_EC_LED_MIC_MUTE 0x0e
> +
> #define T14S_EC_REG_LED 0x0c
> #define T14S_EC_REG_KBD_BL1 0x0d
> #define T14S_EC_REG_MODERN_STANDBY 0xe0
> @@ -67,6 +78,15 @@
> #define T14S_EC_EVT_KEY_FN_F11 0x7a
> #define T14S_EC_EVT_KEY_FN_G 0x7e
>
> +/* Gen 7 only */
> +#define T14S_GEN7_EC_EVT_HKEY_1017 0x1c
> +#define T14S_GEN7_EC_EVT_STATUS_35 0x35
> +#define T14S_GEN7_EC_EVT_DYTC 0x3c
> +#define T14S_GEN7_EC_EVT_STATUS_40 0x40
> +#define T14S_GEN7_EC_EVT_STATUS_4B 0x4b
> +#define T14S_GEN7_EC_EVT_STATUS_4E 0x4e
> +#define T14S_GEN7_EC_EVT_HKEY_1016 0x76
> +
> /* Hardware LED blink rate is 1 Hz (500ms off, 500ms on) */
> #define T14S_EC_BLINK_RATE_ON_OFF_MS 500
>
> @@ -93,9 +113,24 @@ struct t14s_ec_led_classdev {
> struct t14s_ec *ec;
> };
>
> +struct t14s_ec;
> +
> +struct t14s_ec_variant {
> + const struct regmap_bus *regmap_bus;
> + int (*read_evt)(struct t14s_ec *ec, u8 *val);
> + /* EC has the second keyboard backlight and the modern standby registers */
> + bool has_kbd_bl2_and_standby;
> + /* mic mute LED is a generic LED (in T14S_EC_REG_LED) and not a bit in T14S_EC_REG_AUD */
> + bool mic_mute_led_generic;
> +};
> +
> struct t14s_ec {
> struct regmap *regmap;
> struct device *dev;
> + const struct t14s_ec_variant *variant;
> + /* serializes the multi-message Gen 7 EC transactions */
> + struct mutex xfer_lock;
> + enum led_brightness mic_mute_cache;
> struct t14s_ec_led_classdev led_pwr_btn;
> struct t14s_ec_led_classdev led_chrg_orange;
> struct t14s_ec_led_classdev led_chrg_white;
> @@ -206,6 +241,85 @@ static int t14s_ec_read_evt(struct t14s_ec *ec, u8 *val)
> return ret;
> }
>
> +static int t14s_gen7_ec_cmd_read(struct t14s_ec *ec, u8 cmd, u8 arg, u8 *val)
> +{
> + struct i2c_client *client = to_i2c_client(ec->dev);
> + u8 req[2] = { cmd, arg };
> + struct i2c_msg msgs[2] = {
> + { .addr = client->addr, .flags = 0, .len = 1, .buf = &req[0] },
> + { .addr = client->addr, .flags = I2C_M_RD, .len = 1, .buf = val },
> + };
> + int ret;
> +
> + guard(mutex)(&ec->xfer_lock);
> +
> + ret = i2c_master_send(client, req, sizeof(req));
> + if (ret < 0)
> + return ret;
> +
> + fsleep(5000);
> +
> + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
> + if (ret < 0)
> + return ret;
> +
> + return ret == ARRAY_SIZE(msgs) ? 0 : -EIO;
> +}
> +
> +static int t14s_gen7_ec_write(void *context, unsigned int reg,
> + unsigned int val)
> +{
> + struct t14s_ec *ec = context;
> + struct i2c_client *client = to_i2c_client(ec->dev);
> + u8 buf[3] = { T14S_GEN7_EC_CMD_WR, reg, val };
> + int ret;
> +
> + guard(mutex)(&ec->xfer_lock);
> +
> + ret = i2c_master_send(client, buf, sizeof(buf));
> + if (ret < 0)
> + return ret;
> +
> + return 0;
> +}
> +
> +static int t14s_gen7_ec_read(void *context, unsigned int reg,
> + unsigned int *val)
> +{
> + struct t14s_ec *ec = context;
> + u8 result;
> + int ret;
> +
> + ret = t14s_gen7_ec_cmd_read(ec, T14S_GEN7_EC_CMD_RD, reg, &result);
> + if (ret)
> + return ret;
> +
> + *val = result;
> + return 0;
> +}
> +
> +static const struct regmap_bus t14s_gen7_ec_regmap_bus = {
> + .reg_write = t14s_gen7_ec_write,
> + .reg_read = t14s_gen7_ec_read,
> +};
> +
> +static int t14s_gen7_ec_read_evt(struct t14s_ec *ec, u8 *val)
> +{
> + return t14s_gen7_ec_cmd_read(ec, T14S_GEN7_EC_CMD_EVT, 0x00, val);
> +}
> +
> +static const struct t14s_ec_variant t14s_ec_variant = {
> + .regmap_bus = &t14s_ec_regmap_bus,
> + .read_evt = t14s_ec_read_evt,
> + .has_kbd_bl2_and_standby = true,
> +};
> +
> +static const struct t14s_ec_variant t14s_gen7_ec_variant = {
> + .regmap_bus = &t14s_gen7_ec_regmap_bus,
> + .read_evt = t14s_gen7_ec_read_evt,
> + .mic_mute_led_generic = true,
> +};
> +
> static void t14s_ec_write_sequence(struct t14s_ec *ec, u8 reg, u8 val, u8 cnt)
> {
> int i;
> @@ -318,6 +432,9 @@ static int t14s_kbd_bl_set(struct led_classdev *led_cdev,
> if (ret < 0)
> return ret;
>
> + if (!ec->variant->has_kbd_bl2_and_standby)
> + return 0;
> +
> val = FIELD_PREP(T14S_EC_KBD_BL2_MASK, brightness);
> ret = regmap_update_bits(ec->regmap, T14S_EC_REG_KBD_BL2,
> T14S_EC_KBD_BL2_MASK, val);
> @@ -338,7 +455,11 @@ static enum led_brightness t14s_kbd_bl_get(struct led_classdev *led_cdev)
> if (ret < 0)
> return ret;
>
> - return FIELD_GET(T14S_EC_KBD_BL1_MASK, val);
> + /*
> + * When cycled with Fn+Space, the Gen 7 EC also uses 3 for the high
> + * level, which looks the same as 2.
> + */
> + return min(FIELD_GET(T14S_EC_KBD_BL1_MASK, val), 2);
> }
>
> static void t14s_kbd_bl_update(struct t14s_ec *ec)
> @@ -383,6 +504,9 @@ static enum led_brightness t14s_mic_mute_led_get(struct led_classdev *led_cdev)
> struct t14s_ec *ec = container_of(led_cdev, struct t14s_ec,
> led_mic_mute);
>
> + if (ec->variant->mic_mute_led_generic)
> + return ec->mic_mute_cache;
> +
> return t14s_audio_led_get(ec, T14S_EC_MIC_MUTE_LED);
> }
>
> @@ -391,8 +515,19 @@ static int t14s_mic_mute_led_set(struct led_classdev *led_cdev,
> {
> struct t14s_ec *ec = container_of(led_cdev, struct t14s_ec,
> led_mic_mute);
> + int ret;
>
> - return t14s_audio_led_set(ec, T14S_EC_MIC_MUTE_LED, brightness);
> + if (!ec->variant->mic_mute_led_generic)
> + return t14s_audio_led_set(ec, T14S_EC_MIC_MUTE_LED, brightness);
> +
> + ret = regmap_write(ec->regmap, T14S_EC_REG_LED,
> + T14S_GEN7_EC_LED_MIC_MUTE |
> + (brightness ? T14S_EC_LED_ON : T14S_EC_LED_OFF));
> + if (ret < 0)
> + return ret;
> +
> + ec->mic_mute_cache = brightness ? LED_ON : LED_OFF;
> + return 0;
> }
>
> static enum led_brightness t14s_spk_mute_led_get(struct led_classdev *led_cdev)
> @@ -478,7 +613,7 @@ static irqreturn_t t14s_ec_irq_handler(int irq, void *data)
> int ret;
> u8 val;
>
> - ret = t14s_ec_read_evt(ec, &val);
> + ret = ec->variant->read_evt(ec, &val);
> if (ret < 0) {
> dev_err(ec->dev, "Failed to read event\n");
> return IRQ_HANDLED;
> @@ -553,6 +688,16 @@ static irqreturn_t t14s_ec_irq_handler(int irq, void *data)
> case T14S_EC_EVT_KEY_FN_D:
> dev_dbg(ec->dev, "FN + D - toggle privacy guard mode\n");
> break;
> + case T14S_GEN7_EC_EVT_HKEY_1017:
> + case T14S_GEN7_EC_EVT_HKEY_1016:
> + case T14S_GEN7_EC_EVT_DYTC:
> + /* Status events the Gen 7 firmware does not act upon either */
> + case T14S_GEN7_EC_EVT_STATUS_35:
> + case T14S_GEN7_EC_EVT_STATUS_40:
> + case T14S_GEN7_EC_EVT_STATUS_4B:
> + case T14S_GEN7_EC_EVT_STATUS_4E:
> + dev_dbg(ec->dev, "EC event 0x%02x\n", val);
> + break;
> default:
> dev_info(ec->dev, "Unknown EC event: 0x%02x\n", val);
> break;
> @@ -572,9 +717,17 @@ static int t14s_ec_probe(struct i2c_client *client)
> return -ENOMEM;
>
> ec->dev = dev;
> + ec->variant = i2c_get_match_data(client);
> + if (!ec->variant)
> + ec->variant = &t14s_ec_variant;
> +
> + ret = devm_mutex_init(dev, &ec->xfer_lock);
> + if (ret)
> + return ret;
> +
> i2c_set_clientdata(client, ec);
>
> - ec->regmap = devm_regmap_init(dev, &t14s_ec_regmap_bus,
> + ec->regmap = devm_regmap_init(dev, ec->variant->regmap_bus,
> ec, &t14s_ec_regmap_config);
> if (IS_ERR(ec->regmap))
> return dev_err_probe(dev, PTR_ERR(ec->regmap),
> @@ -618,8 +771,9 @@ static int t14s_ec_suspend(struct device *dev)
>
> led_classdev_suspend(&ec->kbd_backlight);
>
> - t14s_ec_write_sequence(ec, T14S_EC_REG_MODERN_STANDBY,
> - T14S_EC_MODERN_STANDBY_ENTRY, 3);
> + if (ec->variant->has_kbd_bl2_and_standby)
> + t14s_ec_write_sequence(ec, T14S_EC_REG_MODERN_STANDBY,
> + T14S_EC_MODERN_STANDBY_ENTRY, 3);
>
> return 0;
> }
> @@ -628,8 +782,9 @@ static int t14s_ec_resume(struct device *dev)
> {
> struct t14s_ec *ec = dev_get_drvdata(dev);
>
> - t14s_ec_write_sequence(ec, T14S_EC_REG_MODERN_STANDBY,
> - T14S_EC_MODERN_STANDBY_EXIT, 3);
> + if (ec->variant->has_kbd_bl2_and_standby)
> + t14s_ec_write_sequence(ec, T14S_EC_REG_MODERN_STANDBY,
> + T14S_EC_MODERN_STANDBY_EXIT, 3);
>
> led_classdev_resume(&ec->kbd_backlight);
>
> @@ -637,13 +792,14 @@ static int t14s_ec_resume(struct device *dev)
> }
>
> static const struct of_device_id t14s_ec_of_match[] = {
> - { .compatible = "lenovo,thinkpad-t14s-ec" },
> + { .compatible = "lenovo,thinkpad-t14s-ec", .data = &t14s_ec_variant },
> + { .compatible = "lenovo,thinkpad-t14s-gen7-ec", .data = &t14s_gen7_ec_variant },
> {}
> };
> MODULE_DEVICE_TABLE(of, t14s_ec_of_match);
>
> static const struct i2c_device_id t14s_ec_i2c_id_table[] = {
> - { .name = "thinkpad-t14s-ec" },
> + { .name = "thinkpad-t14s-ec", .driver_data = (kernel_ulong_t)&t14s_ec_variant },
> { }
> };
> MODULE_DEVICE_TABLE(i2c, t14s_ec_i2c_id_table);
>
[-- Attachment #2: 0001-fixup-platform-arm64-lenovo-thinkpad-t14s-add-Gen-7-.patch --]
[-- Type: text/x-patch, Size: 3137 bytes --]
From 2e120788866e237b36fe1f884fd4b11b30e0435e Mon Sep 17 00:00:00 2001
From: Hans de Goede <johannes.goede@oss.qualcomm.com>
Date: Sun, 8 Nov 2026 21:38:36 +0100
Subject: [PATCH 1/4] fixup! platform: arm64: lenovo-thinkpad-t14s: add Gen 7
EC support
Fix:
- Fn-F12 key not registering a keypress
- thinkpad-t14s-ec 7-0028: Unknown EC event: 0xXX messages for
some other Fn-Fxx key combos
Signed-off-by: Hans de Goede <johannes.goede@oss.qualcomm.com>
---
drivers/platform/arm64/lenovo-thinkpad-t14s.c | 17 +++++++++++++++--
1 file changed, 15 insertions(+), 2 deletions(-)
diff --git a/drivers/platform/arm64/lenovo-thinkpad-t14s.c b/drivers/platform/arm64/lenovo-thinkpad-t14s.c
index b488b85bf7c0..75b221403a87 100644
--- a/drivers/platform/arm64/lenovo-thinkpad-t14s.c
+++ b/drivers/platform/arm64/lenovo-thinkpad-t14s.c
@@ -52,6 +52,7 @@
#define T14S_EC_EVT_NONE 0x00
#define T14S_EC_EVT_KEY_FN_4 0x13
#define T14S_EC_EVT_KEY_FN_F7 0x16
+#define T14S_EC_EVT_KEY_FN_F12_GEN7 0x19
#define T14S_EC_EVT_KEY_FN_SPACE 0x1f
#define T14S_EC_EVT_KEY_TP_DOUBLE_TAP 0x20
#define T14S_EC_EVT_AC_CONNECTED 0x26
@@ -79,6 +80,8 @@
#define T14S_EC_EVT_KEY_FN_G 0x7e
/* Gen 7 only */
+#define T14S_GEN7_EC_EVT_HKEY_1010 0x14
+#define T14S_GEN7_EC_EVT_HKEY_1011 0x15
#define T14S_GEN7_EC_EVT_HKEY_1017 0x1c
#define T14S_GEN7_EC_EVT_STATUS_35 0x35
#define T14S_GEN7_EC_EVT_DYTC 0x3c
@@ -86,6 +89,7 @@
#define T14S_GEN7_EC_EVT_STATUS_4B 0x4b
#define T14S_GEN7_EC_EVT_STATUS_4E 0x4e
#define T14S_GEN7_EC_EVT_HKEY_1016 0x76
+#define T14S_GEN7_EC_EVT_HKEY_1015 0x7b
/* Hardware LED blink rate is 1 Hz (500ms off, 500ms on) */
#define T14S_EC_BLINK_RATE_ON_OFF_MS 500
@@ -579,6 +583,7 @@ static const struct key_entry t14s_keymap[] = {
T14S_EC_KEY_ENTRY(FN_F10, KEY_SELECTIVE_SCREENSHOT),
T14S_EC_KEY_ENTRY(FN_F11, KEY_LINK_PHONE),
T14S_EC_KEY_ENTRY(FN_F12, KEY_BOOKMARKS),
+ T14S_EC_KEY_ENTRY(FN_F12_GEN7, KEY_BOOKMARKS),
T14S_EC_KEY_ENTRY(FN_SPACE, KEY_KBDILLUMTOGGLE),
T14S_EC_KEY_ENTRY(FN_ESC, KEY_FN_ESC),
T14S_EC_KEY_ENTRY(FN_TAB, KEY_ZOOM),
@@ -636,6 +641,7 @@ static irqreturn_t t14s_ec_irq_handler(int irq, void *data)
case T14S_EC_EVT_KEY_FN_4:
case T14S_EC_EVT_KEY_FN_F8:
case T14S_EC_EVT_KEY_FN_F12:
+ case T14S_EC_EVT_KEY_FN_F12_GEN7:
case T14S_EC_EVT_KEY_FN_TAB:
case T14S_EC_EVT_KEY_FN_F10:
case T14S_EC_EVT_KEY_FN_N:
@@ -688,9 +694,16 @@ static irqreturn_t t14s_ec_irq_handler(int irq, void *data)
case T14S_EC_EVT_KEY_FN_D:
dev_dbg(ec->dev, "FN + D - toggle privacy guard mode\n");
break;
- case T14S_GEN7_EC_EVT_HKEY_1017:
- case T14S_GEN7_EC_EVT_HKEY_1016:
case T14S_GEN7_EC_EVT_DYTC:
+ /*
+ * Keys which send an EC event which the DSDT maps to a HKEY, which
+ * also send a key-press over I2C-HID, so ignore the EC event.
+ */
+ case T14S_GEN7_EC_EVT_HKEY_1010:
+ case T14S_GEN7_EC_EVT_HKEY_1011:
+ case T14S_GEN7_EC_EVT_HKEY_1015:
+ case T14S_GEN7_EC_EVT_HKEY_1016:
+ case T14S_GEN7_EC_EVT_HKEY_1017:
/* Status events the Gen 7 firmware does not act upon either */
case T14S_GEN7_EC_EVT_STATUS_35:
case T14S_GEN7_EC_EVT_STATUS_40:
--
2.55.0
[-- Attachment #3: 0002-fixup-platform-arm64-lenovo-thinkpad-t14s-add-Gen-7-.patch --]
[-- Type: text/x-patch, Size: 2746 bytes --]
From fdb01621eeafe774d11671e80ada403a8cc1df70 Mon Sep 17 00:00:00 2001
From: Hans de Goede <johannes.goede@oss.qualcomm.com>
Date: Mon, 9 Nov 2026 13:21:24 +0100
Subject: [PATCH 2/4] fixup! platform: arm64: lenovo-thinkpad-t14s: add Gen 7
EC support
Add Gen 7 suspend/resume handling. Note the name of
the has_kbd_bl2_and_standby bool is no longer accurate after
this since Gen 7 does have a (different) modern standby register,
replace the bool check with checking at which variant ec->variant
points.
Signed-off-by: Hans de Goede <johannes.goede@oss.qualcomm.com>
---
drivers/platform/arm64/lenovo-thinkpad-t14s.c | 20 +++++++++++++++++--
1 file changed, 18 insertions(+), 2 deletions(-)
diff --git a/drivers/platform/arm64/lenovo-thinkpad-t14s.c b/drivers/platform/arm64/lenovo-thinkpad-t14s.c
index 75b221403a87..b2f571413b8d 100644
--- a/drivers/platform/arm64/lenovo-thinkpad-t14s.c
+++ b/drivers/platform/arm64/lenovo-thinkpad-t14s.c
@@ -39,6 +39,9 @@
#define T14S_EC_REG_LED 0x0c
#define T14S_EC_REG_KBD_BL1 0x0d
+#define T14S_EC_REG_MODERN_STANDBY_GEN7 0xcf
+/* Gen 7 DSDT sets this bit on Windows modern standby entry */
+#define T14S_EC_MODERN_STANDBY_GEN7_ESLS BIT(5)
#define T14S_EC_REG_MODERN_STANDBY 0xe0
#define T14S_EC_MODERN_STANDBY_ENTRY BIT(1)
#define T14S_EC_MODERN_STANDBY_EXIT BIT(0)
@@ -107,6 +110,7 @@
enum t14s_ec_led_status_t {
T14S_EC_LED_OFF = 0x00,
T14S_EC_LED_ON = 0x80,
+ T14S_EC_LED_GLOW = 0xa0, /* Gen 7 only, only works for power and lid LEDs */
T14S_EC_LED_BLINK = 0xc0,
};
@@ -784,9 +788,15 @@ static int t14s_ec_suspend(struct device *dev)
led_classdev_suspend(&ec->kbd_backlight);
- if (ec->variant->has_kbd_bl2_and_standby)
+ if (ec->variant == &t14s_ec_variant) {
t14s_ec_write_sequence(ec, T14S_EC_REG_MODERN_STANDBY,
T14S_EC_MODERN_STANDBY_ENTRY, 3);
+ } else {
+ regmap_assign_bits(ec->regmap, T14S_EC_REG_MODERN_STANDBY_GEN7,
+ T14S_EC_MODERN_STANDBY_GEN7_ESLS, 1);
+ t14s_led_set_status(ec, &ec->led_pwr_btn, T14S_EC_LED_GLOW);
+ t14s_led_set_status(ec, &ec->led_lid_logo_dot, T14S_EC_LED_GLOW);
+ }
return 0;
}
@@ -795,9 +805,15 @@ static int t14s_ec_resume(struct device *dev)
{
struct t14s_ec *ec = dev_get_drvdata(dev);
- if (ec->variant->has_kbd_bl2_and_standby)
+ if (ec->variant == &t14s_ec_variant) {
t14s_ec_write_sequence(ec, T14S_EC_REG_MODERN_STANDBY,
T14S_EC_MODERN_STANDBY_EXIT, 3);
+ } else {
+ regmap_assign_bits(ec->regmap, T14S_EC_REG_MODERN_STANDBY_GEN7,
+ T14S_EC_MODERN_STANDBY_GEN7_ESLS, 0);
+ t14s_led_set_status(ec, &ec->led_pwr_btn, T14S_EC_LED_ON);
+ t14s_led_set_status(ec, &ec->led_lid_logo_dot, T14S_EC_LED_ON);
+ }
led_classdev_resume(&ec->kbd_backlight);
--
2.55.0
[-- Attachment #4: 0003-fixup-platform-arm64-lenovo-thinkpad-t14s-add-Gen-7-.patch --]
[-- Type: text/x-patch, Size: 3097 bytes --]
From ddc62a2cd376ad190f0511a73fc285bb75dd015c Mon Sep 17 00:00:00 2001
From: Hans de Goede <johannes.goede@oss.qualcomm.com>
Date: Mon, 9 Nov 2026 13:29:23 +0100
Subject: [PATCH 3/4] fixup! platform: arm64: lenovo-thinkpad-t14s: add Gen 7
EC support
The has_kbd_bl2_and_standby and mic_mute_led_generic checks are
really just checks for the original (gen6) support vs gen7 support
drop the bools and directly check which variant ec->variant points
at instead.
While at it also swap the gen6 vs gen7 paths in t14s_mic_mute_led_get()
to mirror the flow (gen6 checked for, handled with early exit) used
in t14s_mic_mute_led_set().
Signed-off-by: Hans de Goede <johannes.goede@oss.qualcomm.com>
---
drivers/platform/arm64/lenovo-thinkpad-t14s.c | 16 +++++-----------
1 file changed, 5 insertions(+), 11 deletions(-)
diff --git a/drivers/platform/arm64/lenovo-thinkpad-t14s.c b/drivers/platform/arm64/lenovo-thinkpad-t14s.c
index b2f571413b8d..a24bf95268dc 100644
--- a/drivers/platform/arm64/lenovo-thinkpad-t14s.c
+++ b/drivers/platform/arm64/lenovo-thinkpad-t14s.c
@@ -126,10 +126,6 @@ struct t14s_ec;
struct t14s_ec_variant {
const struct regmap_bus *regmap_bus;
int (*read_evt)(struct t14s_ec *ec, u8 *val);
- /* EC has the second keyboard backlight and the modern standby registers */
- bool has_kbd_bl2_and_standby;
- /* mic mute LED is a generic LED (in T14S_EC_REG_LED) and not a bit in T14S_EC_REG_AUD */
- bool mic_mute_led_generic;
};
struct t14s_ec {
@@ -319,13 +315,11 @@ static int t14s_gen7_ec_read_evt(struct t14s_ec *ec, u8 *val)
static const struct t14s_ec_variant t14s_ec_variant = {
.regmap_bus = &t14s_ec_regmap_bus,
.read_evt = t14s_ec_read_evt,
- .has_kbd_bl2_and_standby = true,
};
static const struct t14s_ec_variant t14s_gen7_ec_variant = {
.regmap_bus = &t14s_gen7_ec_regmap_bus,
.read_evt = t14s_gen7_ec_read_evt,
- .mic_mute_led_generic = true,
};
static void t14s_ec_write_sequence(struct t14s_ec *ec, u8 reg, u8 val, u8 cnt)
@@ -440,7 +434,7 @@ static int t14s_kbd_bl_set(struct led_classdev *led_cdev,
if (ret < 0)
return ret;
- if (!ec->variant->has_kbd_bl2_and_standby)
+ if (ec->variant == &t14s_gen7_ec_variant)
return 0;
val = FIELD_PREP(T14S_EC_KBD_BL2_MASK, brightness);
@@ -512,10 +506,10 @@ static enum led_brightness t14s_mic_mute_led_get(struct led_classdev *led_cdev)
struct t14s_ec *ec = container_of(led_cdev, struct t14s_ec,
led_mic_mute);
- if (ec->variant->mic_mute_led_generic)
- return ec->mic_mute_cache;
+ if (ec->variant == &t14s_ec_variant)
+ return t14s_audio_led_get(ec, T14S_EC_MIC_MUTE_LED);
- return t14s_audio_led_get(ec, T14S_EC_MIC_MUTE_LED);
+ return ec->mic_mute_cache;
}
static int t14s_mic_mute_led_set(struct led_classdev *led_cdev,
@@ -525,7 +519,7 @@ static int t14s_mic_mute_led_set(struct led_classdev *led_cdev,
led_mic_mute);
int ret;
- if (!ec->variant->mic_mute_led_generic)
+ if (ec->variant == &t14s_ec_variant)
return t14s_audio_led_set(ec, T14S_EC_MIC_MUTE_LED, brightness);
ret = regmap_write(ec->regmap, T14S_EC_REG_LED,
--
2.55.0
[-- Attachment #5: 0004-fixup-platform-arm64-lenovo-thinkpad-t14s-add-Gen-7-.patch --]
[-- Type: text/x-patch, Size: 3078 bytes --]
From cb7db6f4ec786aa08dcaff276bdeb2dac65f0f49 Mon Sep 17 00:00:00 2001
From: Hans de Goede <johannes.goede@oss.qualcomm.com>
Date: Mon, 9 Nov 2026 14:29:37 +0100
Subject: [PATCH 4/4] fixup! platform: arm64: lenovo-thinkpad-t14s: add Gen 7
EC support
Drop the xfer_lock mutex and instead use i2c_lock_bus() like
the existing t14s gen6 support does for multi-msg transactions.
Also drop the fsleep(5000) in t14s_gen7_ec_cmd_read() the DSDT code
has no delay between the transfers so this is not necessary.
Signed-off-by: Hans de Goede <johannes.goede@oss.qualcomm.com>
---
drivers/platform/arm64/lenovo-thinkpad-t14s.c | 33 +++++++++----------
1 file changed, 16 insertions(+), 17 deletions(-)
diff --git a/drivers/platform/arm64/lenovo-thinkpad-t14s.c b/drivers/platform/arm64/lenovo-thinkpad-t14s.c
index a24bf95268dc..7f30f67173c3 100644
--- a/drivers/platform/arm64/lenovo-thinkpad-t14s.c
+++ b/drivers/platform/arm64/lenovo-thinkpad-t14s.c
@@ -132,8 +132,6 @@ struct t14s_ec {
struct regmap *regmap;
struct device *dev;
const struct t14s_ec_variant *variant;
- /* serializes the multi-message Gen 7 EC transactions */
- struct mutex xfer_lock;
enum led_brightness mic_mute_cache;
struct t14s_ec_led_classdev led_pwr_btn;
struct t14s_ec_led_classdev led_chrg_orange;
@@ -250,24 +248,31 @@ static int t14s_gen7_ec_cmd_read(struct t14s_ec *ec, u8 cmd, u8 arg, u8 *val)
struct i2c_client *client = to_i2c_client(ec->dev);
u8 req[2] = { cmd, arg };
struct i2c_msg msgs[2] = {
- { .addr = client->addr, .flags = 0, .len = 1, .buf = &req[0] },
+ { .addr = client->addr, .flags = 0, .len = 2, .buf = &req[0] },
{ .addr = client->addr, .flags = I2C_M_RD, .len = 1, .buf = val },
};
int ret;
- guard(mutex)(&ec->xfer_lock);
+ i2c_lock_bus(client->adapter, I2C_LOCK_SEGMENT);
- ret = i2c_master_send(client, req, sizeof(req));
+ /* Send cmd + arg */
+ ret = __i2c_transfer(client->adapter, msgs, 1);
if (ret < 0)
- return ret;
+ goto out;
- fsleep(5000);
-
- ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
+ /* Resend the cmd byte + read the value */
+ msgs[0].addr = client->addr;
+ msgs[0].flags = 0;
+ msgs[0].len = 1;
+ msgs[0].buf = &cmd;
+ ret = __i2c_transfer(client->adapter, msgs, 2);
if (ret < 0)
- return ret;
+ goto out;
- return ret == ARRAY_SIZE(msgs) ? 0 : -EIO;
+ ret = ret == 2 ? 0 : EIO;
+out:
+ i2c_unlock_bus(client->adapter, I2C_LOCK_SEGMENT);
+ return ret;
}
static int t14s_gen7_ec_write(void *context, unsigned int reg,
@@ -278,8 +283,6 @@ static int t14s_gen7_ec_write(void *context, unsigned int reg,
u8 buf[3] = { T14S_GEN7_EC_CMD_WR, reg, val };
int ret;
- guard(mutex)(&ec->xfer_lock);
-
ret = i2c_master_send(client, buf, sizeof(buf));
if (ret < 0)
return ret;
@@ -732,10 +735,6 @@ static int t14s_ec_probe(struct i2c_client *client)
if (!ec->variant)
ec->variant = &t14s_ec_variant;
- ret = devm_mutex_init(dev, &ec->xfer_lock);
- if (ret)
- return ret;
-
i2c_set_clientdata(client, ec);
ec->regmap = devm_regmap_init(dev, ec->variant->regmap_bus,
--
2.55.0
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-11-09 14:52 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-06 22:01 [PATCH 0/3] platform: arm64: lenovo-thinkpad-t14s: Add support for the Gen 7 EC Abel Vesa
2026-11-06 22:01 ` [PATCH 1/3] dt-bindings: embedded-controller: lenovo,thinkpad-t14s-ec: Add Gen 7 support Abel Vesa
2026-11-06 22:01 ` [PATCH 2/3] platform: arm64: lenovo-thinkpad-t14s: Add Gen 7 EC support Abel Vesa
2026-11-09 14:52 ` Hans de Goede
2026-11-06 22:01 ` [PATCH 3/3] arm64: dts: qcom: mahua-lenovo-thinkpad-t14s-gen7: Describe the Embedded Controller Abel Vesa
2026-11-09 10:19 ` Konrad Dybcio
2026-11-09 11:07 ` Abel Vesa
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®