From: "Ilpo Järvinen" <ilpo.jarvinen@linux.intel.com>
To: Oleg Keri <okerixx@gmail.com>
Cc: Rob Herring <robh@kernel.org>,
Krzysztof Kozlowski <krzk+dt@kernel.org>,
Conor Dooley <conor+dt@kernel.org>,
Hans de Goede <hansg@kernel.org>,
Bryan O'Donoghue <bryan.odonoghue@linaro.org>,
Guenter Roeck <linux@roeck-us.net>,
Bjorn Andersson <andersson@kernel.org>,
Konrad Dybcio <konradybcio@kernel.org>,
Abel Vesa <abelvesa@kernel.org>,
linux-arm-msm@vger.kernel.org, devicetree@vger.kernel.org,
LKML <linux-kernel@vger.kernel.org>,
platform-driver-x86@vger.kernel.org,
linux-hwmon@vger.kernel.org
Subject: Re: [PATCH v3 2/3] platform/arm64: add Lenovo Yoga Slim 7x Gen 11 EC driver
Date: Mon, 21 Sep 2026 18:20:26 +0300 (EEST) [thread overview]
Message-ID: <55181989-11d7-ce0d-f9c3-e9dceaaf1fb4@linux.intel.com> (raw)
In-Reply-To: <20260921144953.15113-3-okerixx@gmail.com>
On Mon, 21 Sep 2026, Oleg Keri wrote:
> The Lenovo Yoga Slim 7x Gen 11 (Qualcomm glymur / Snapdragon X2 Elite)
> carries a Compal EF06 embedded controller on I2C at address 0x70. The
> platform firmware describes it as an ACPI GenericSerialBus region, but the
> machine boots from device tree, so nothing otherwise talks to it.
>
> Commands are exchanged with two raw I2C block transfers: a 7 byte write to
> register 0x02 carrying the command and up to six arguments, then a 6 byte
> read of register 0x01 for the result, with a short delay in between before
> the result register is valid. The EC offers no way to match a result to
> its command; the delay is the one the vendor ACPI code uses, and the one
> value that lives in a mailbox shared with other EC activity, the cell
> voltages, is validated against a signature and retried.
>
> Expose what the EC offers that the rest of the kernel does not already
> provide:
>
> - five board thermistors as hwmon temperature channels. These are
> charger, CPU voltage regulator, 5 V rail and ambient sensors, not die
> sensors, and read considerably cooler than tsens
> - the four battery cell voltages, which live in a banked mailbox shared
> with other EC activity; readings are validated against a signature and
> retried, and cached briefly so that all four channels come from one
> coherent sample
> - CPU fan speed in RPM
> - the keyboard backlight as an LED, off/low/high
> - Fn-lock as a read/write sysfs attribute, matching the ideapad-laptop
> interface. The EC reports Fn+Esc as an event but keeps no state of its
> own, so the driver owns the bit and toggles it on the key
> - the mic-mute and airplane-mode keys as KEY_MICMUTE and KEY_RFKILL.
> These reach the host only as EC events; they are not on the keyboard's
> HID interface and have no GPIO of their own
>
> The EC signals pending events on a dedicated line, and keeps pulsing it
> until they are read - so the queue must be drained completely on every
> interrupt, including codes this driver ignores. Leaving one behind makes
> the controller pulse indefinitely.
>
> Fan speed is reported but not controlled. The EC accepts a manual setpoint,
> however it enforces no thermal floor over it: with the fan pinned at
> roughly 2000 RPM under full load the SoC rose from 69 to 91 degrees in 30
> seconds without the EC ever overriding. Exposing pwm1_enable or fan1_target
> would therefore let userspace overheat the machine, so neither is
> implemented.
>
> Note this is the Gen 11 machine specifically. Its EC differs from the
> Qualcomm reference EC that the reference designs place at address 0x76,
> which is not populated here, and so EC_QCOM_HAMOA does not drive it.
>
> Signed-off-by: Oleg Keri <okerixx@gmail.com>
> ---
> .../sysfs-driver-lenovo-yoga-slim7x-gen11-ec | 21 +
> drivers/platform/arm64/Kconfig | 23 +
> drivers/platform/arm64/Makefile | 1 +
> .../arm64/lenovo-yoga-slim7x-gen11-ec.c | 653 ++++++++++++++++++
> 4 files changed, 698 insertions(+)
> create mode 100644 Documentation/ABI/testing/sysfs-driver-lenovo-yoga-slim7x-gen11-ec
> create mode 100644 drivers/platform/arm64/lenovo-yoga-slim7x-gen11-ec.c
>
> diff --git a/Documentation/ABI/testing/sysfs-driver-lenovo-yoga-slim7x-gen11-ec b/Documentation/ABI/testing/sysfs-driver-lenovo-yoga-slim7x-gen11-ec
> new file mode 100644
> index 000000000000..c8ab554ad452
> --- /dev/null
> +++ b/Documentation/ABI/testing/sysfs-driver-lenovo-yoga-slim7x-gen11-ec
> @@ -0,0 +1,21 @@
> +What: /sys/bus/i2c/devices/*/fn_lock
> +Date: August 2026
??
> +KernelVersion: 7.3
This ship has already sailed.
> +Contact: Oleg Keri <okerixx@gmail.com>
> +Description:
> + Control fn-lock mode.
> +
> + * 1 -> Switched On
> + * 0 -> Switched Off
> +
> + When switched on, the function keys act as F1..F12 and the
> + multimedia functions require the Fn modifier.
> +
> + The embedded controller reports the Fn+Esc key as an event but
> + keeps no state of its own, so this attribute is the state: the
> + driver toggles it when the key is pressed, and writing it takes
> + effect immediately.
> +
> + For example::
> +
> + # echo "0" > /sys/bus/i2c/devices/9-0070/fn_lock
> diff --git a/drivers/platform/arm64/Kconfig b/drivers/platform/arm64/Kconfig
> index e32e01b2a9bd..7581432e69b0 100644
> --- a/drivers/platform/arm64/Kconfig
> +++ b/drivers/platform/arm64/Kconfig
> @@ -90,6 +90,29 @@ config EC_LENOVO_THINKPAD_T14S
>
> Say M or Y here to include this support.
>
> +config EC_LENOVO_YOGA_SLIM7X_GEN11
> + tristate "Lenovo Yoga Slim 7x Gen 11 Embedded Controller driver"
> + depends on ARCH_QCOM || COMPILE_TEST
> + depends on I2C
> + depends on INPUT
> + depends on HWMON
> + select NEW_LEDS
> + select LEDS_CLASS
> + help
> + Say Y here to enable the EC driver for the (Snapdragon X2 Elite,
> + glymur-based) Lenovo Yoga Slim 7x Gen 11 laptop. The EC provides
> + thermistors, battery cell voltages and CPU fan speed as hwmon
> + channels, and the keyboard backlight as an LED.
> +
> + This is the Gen 11 (glymur) machine specifically. Its EC is a Compal
> + design at i2c address 0x70 and speaks a different protocol from the
> + Qualcomm reference EC that the reference designs place at 0x76, so
> + EC_QCOM_HAMOA does not drive it - and conversely this driver does not
> + cover the X1E-based Yoga Slim 7x Gen 9.
> +
> + To compile this driver as a module, choose M here: the module will
> + be called lenovo-yoga-slim7x-gen11-ec.
> +
> config EC_QCOM_HAMOA
> tristate "Embedded Controller driver for Qualcomm Hamoa/Glymur reference devices"
> depends on ARCH_QCOM || COMPILE_TEST
> diff --git a/drivers/platform/arm64/Makefile b/drivers/platform/arm64/Makefile
> index 7681be4a46e9..605cca4d42a9 100644
> --- a/drivers/platform/arm64/Makefile
> +++ b/drivers/platform/arm64/Makefile
> @@ -9,4 +9,5 @@ obj-$(CONFIG_EC_ACER_ASPIRE1) += acer-aspire1-ec.o
> obj-$(CONFIG_EC_HUAWEI_GAOKUN) += huawei-gaokun-ec.o
> obj-$(CONFIG_EC_LENOVO_YOGA_C630) += lenovo-yoga-c630.o
> obj-$(CONFIG_EC_LENOVO_THINKPAD_T14S) += lenovo-thinkpad-t14s.o
> +obj-$(CONFIG_EC_LENOVO_YOGA_SLIM7X_GEN11) += lenovo-yoga-slim7x-gen11-ec.o
> obj-$(CONFIG_EC_QCOM_HAMOA) += qcom-hamoa-ec.o
> diff --git a/drivers/platform/arm64/lenovo-yoga-slim7x-gen11-ec.c b/drivers/platform/arm64/lenovo-yoga-slim7x-gen11-ec.c
> new file mode 100644
> index 000000000000..c8c7928475b7
> --- /dev/null
> +++ b/drivers/platform/arm64/lenovo-yoga-slim7x-gen11-ec.c
> @@ -0,0 +1,653 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +
> +#include <linux/bitfield.h>
> +#include <linux/bitops.h>
> +#include <linux/cleanup.h>
> +#include <linux/container_of.h>
> +#include <linux/delay.h>
> +#include <linux/device.h>
> +#include <linux/err.h>
> +#include <linux/errno.h>
> +#include <linux/gfp_types.h>
> +#include <linux/hwmon.h>
> +#include <linux/i2c.h>
> +#include <linux/input.h>
> +#include <linux/interrupt.h>
> +#include <linux/jiffies.h>
> +#include <linux/kstrtox.h>
> +#include <linux/leds.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/pm.h>
> +#include <linux/string.h>
> +#include <linux/sysfs.h>
> +#include <linux/types.h>
> +
> +#define YOGA_EC_BLOCK_READ 0x01
> +#define YOGA_EC_BLOCK_WRITE 0x02
> +#define YOGA_EC_RESP_LEN 6
> +#define YOGA_EC_REQ_LEN 7
> +
> +#define YOGA_EC_CMD_RAM_READ 0xb0
> +#define YOGA_EC_CMD_RAM_WRITE 0xb1
> +#define YOGA_EC_CMD_BANK_READ 0xb4
> +#define YOGA_EC_CMD_FAN 0x46
> +#define YOGA_EC_FAN_GET_SPEED 0x81
> +
> +#define YOGA_EC_CMD_EVENT 0x84
> +#define YOGA_EC_EVT_MAX_DRAIN 64
> +#define YOGA_EC_EVT_FN_LOCK 0x0f
> +#define YOGA_EC_EVT_MIC_MUTE 0x18
> +#define YOGA_EC_EVT_FLIGHT_MODE 0x19
> +
> +#define YOGA_EC_SCUK 0x1c
> +#define YOGA_EC_SCUK_FN_LOCK BIT(7)
> +#define YOGA_EC_SCUK_FN_LOCK_IND BIT(5)
Add include for BIT()
> +#define YOGA_EC_SCUK_FN_LOCK_MASK (YOGA_EC_SCUK_FN_LOCK | YOGA_EC_SCUK_FN_LOCK_IND)
> +
> +#define YOGA_EC_KBLT 0x03
> +#define YOGA_EC_KBLT_LEVEL GENMASK(1, 0)
> +#define YOGA_EC_KBLT_AUTO 3
> +#define YOGA_EC_KBLT_MAX 2
> +
> +#define YOGA_EC_TEMP_FIRST 0x12
> +#define YOGA_EC_NUM_TEMP 6
> +#define YOGA_EC_TEMP_MAX 120
> +
> +#define YOGA_EC_CELL_BANK 0x0b
> +#define YOGA_EC_CELL_SIG_LO 0x02
> +#define YOGA_EC_CELL_SIG_HI 0x0c
> +#define YOGA_EC_CELL_FIRST 0x04
> +#define YOGA_EC_NUM_CELL 4
> +#define YOGA_EC_CELL_MIN_MV 2000
> +#define YOGA_EC_CELL_MAX_MV 5000
> +#define YOGA_EC_CELL_TRIES 10
> +
> +#define YOGA_EC_CACHE (2 * HZ)
> +#define YOGA_EC_STALE (10 * HZ)
It would be better to have time is some time unit + include the unit into
name, and only convert to jiffies when a call requires jiffies/timeout
input.
> +
> +struct yoga_ec {
> + struct i2c_client *client;
> + /* serializes all EC access */
> + struct mutex lock;
Consider adding lockdep asserts to the EC access functions below to ensure
the locking rules are never violated.
> + unsigned long temp_present;
> + unsigned long cell_read_at;
> + u16 cell[YOGA_EC_NUM_CELL];
> + bool cell_cached;
> + struct led_classdev kbd_led;
> + u8 kblt_level;
> + bool kblt_restore;
> + struct input_dev *keys;
> +};
> +
> +static int yoga_ec_command(struct yoga_ec *ec, u8 cmd, u8 arg0, u8 arg1,
> + u8 resp[YOGA_EC_RESP_LEN])
> +{
> + u8 req[YOGA_EC_REQ_LEN] = { cmd, arg0, arg1 };
> + int ret;
> +
> + ret = i2c_smbus_write_i2c_block_data(ec->client, YOGA_EC_BLOCK_WRITE,
> + sizeof(req), req);
> + if (ret < 0)
> + return ret;
> +
> + usleep_range(2000, 3000);
> +
> + ret = i2c_smbus_read_i2c_block_data(ec->client, YOGA_EC_BLOCK_READ,
> + YOGA_EC_RESP_LEN, resp);
> + if (ret < 0)
> + return ret;
> + if (ret != YOGA_EC_RESP_LEN)
> + return -EIO;
> +
> + return 0;
> +}
> +
> +static int yoga_ec_ram_read(struct yoga_ec *ec, u8 addr, u8 *val)
> +{
> + u8 resp[YOGA_EC_RESP_LEN];
> + int ret;
> +
> + ret = yoga_ec_command(ec, YOGA_EC_CMD_RAM_READ, addr, 0, resp);
> + if (ret)
> + return ret;
> +
> + *val = resp[0];
> + return 0;
> +}
> +
> +static int yoga_ec_bank_read(struct yoga_ec *ec, u8 bank, u8 addr, u8 *val)
> +{
> + u8 resp[YOGA_EC_RESP_LEN];
> + int ret;
> +
> + ret = yoga_ec_command(ec, YOGA_EC_CMD_BANK_READ, bank, addr, resp);
> + if (ret)
> + return ret;
> +
> + *val = resp[0];
> + return 0;
> +}
> +
> +static int yoga_ec_ram_write(struct yoga_ec *ec, u8 addr, u8 val)
> +{
> + u8 resp[YOGA_EC_RESP_LEN];
> +
> + return yoga_ec_command(ec, YOGA_EC_CMD_RAM_WRITE, addr, val, resp);
> +}
> +
> +static int yoga_ec_fan_read(struct yoga_ec *ec, long *rpm)
> +{
> + u8 resp[YOGA_EC_RESP_LEN];
> + int ret;
> +
> + ret = yoga_ec_command(ec, YOGA_EC_CMD_FAN, YOGA_EC_FAN_GET_SPEED, 0,
> + resp);
> + if (ret)
> + return ret;
> +
> + *rpm = resp[0] * 100;
> + return 0;
> +}
> +
> +static int yoga_ec_refresh_cells(struct yoga_ec *ec)
> +{
> + unsigned int try;
> + int ret;
> +
> + if (ec->cell_cached &&
> + time_before(jiffies, ec->cell_read_at + YOGA_EC_CACHE))
> + return 0;
> +
> + for (try = 0; try < YOGA_EC_CELL_TRIES; try++) {
> + u16 cell[YOGA_EC_NUM_CELL];
> + u8 sig[3];
> + unsigned int i;
> + bool ok = true;
> +
> + ret = yoga_ec_bank_read(ec, YOGA_EC_CELL_BANK,
> + YOGA_EC_CELL_SIG_LO, &sig[0]);
> + if (!ret)
> + ret = yoga_ec_bank_read(ec, YOGA_EC_CELL_BANK,
> + YOGA_EC_CELL_SIG_LO + 1, &sig[1]);
> + if (!ret)
> + ret = yoga_ec_bank_read(ec, YOGA_EC_CELL_BANK,
> + YOGA_EC_CELL_SIG_HI, &sig[2]);
> + if (ret)
> + return ret;
Please don't complicate error handling flow but handle each error right
after the first call.
> +
> + if (sig[0] != 0x01 || sig[1] != 0x01 || sig[2] != 0x03)
> + continue;
> +
> + for (i = 0; i < YOGA_EC_NUM_CELL; i++) {
ARRAY_SIZE() + don't forget the include.
> + u8 lsb, msb;
> +
> + ret = yoga_ec_bank_read(ec, YOGA_EC_CELL_BANK,
> + YOGA_EC_CELL_FIRST + i * 2, &lsb);
> + if (!ret)
> + ret = yoga_ec_bank_read(ec, YOGA_EC_CELL_BANK,
> + YOGA_EC_CELL_FIRST + i * 2 + 1,
> + &msb);
> + if (ret)
> + return ret;
Simplify error handling similar to above.
> +
> + cell[i] = lsb | (msb << 8);
> + if (cell[i] < YOGA_EC_CELL_MIN_MV ||
> + cell[i] > YOGA_EC_CELL_MAX_MV)
> + ok = false;
> + }
> +
> + if (!ok)
> + continue;
> +
> + memcpy(ec->cell, cell, sizeof(cell));
> + ec->cell_cached = true;
> + ec->cell_read_at = jiffies;
> +
> + return 0;
> + }
> +
> + if (ec->cell_cached &&
> + time_before(jiffies, ec->cell_read_at + YOGA_EC_STALE))
> + return 0;
> +
> + return -EAGAIN;
> +}
> +
> +static umode_t yoga_ec_is_visible(const void *drvdata,
> + enum hwmon_sensor_types type,
> + u32 attr, int channel)
> +{
> + const struct yoga_ec *ec = drvdata;
> +
> + switch (type) {
> + case hwmon_temp:
> + if (!(ec->temp_present & BIT(channel)))
> + return 0;
> + return 0444;
> + case hwmon_in:
> + return 0444;
> + case hwmon_fan:
> + return 0444;
> + default:
> + return 0;
> + }
> +}
> +
> +static int yoga_ec_read(struct device *dev, enum hwmon_sensor_types type,
> + u32 attr, int channel, long *val)
> +{
> + struct yoga_ec *ec = dev_get_drvdata(dev);
> + int ret;
> + u8 raw;
> +
> + guard(mutex)(&ec->lock);
> +
> + switch (type) {
> + case hwmon_temp:
> + ret = yoga_ec_ram_read(ec, YOGA_EC_TEMP_FIRST + channel, &raw);
> + if (ret)
> + return ret;
> + if (!raw)
> + return -ENODATA;
> + if (raw > YOGA_EC_TEMP_MAX)
> + return -EIO;
> + *val = raw * 1000;
Can something from units.h replace this literal? Don't forget to add the
include if you start to use a define from there.
> + return 0;
> + case hwmon_in:
> + ret = yoga_ec_refresh_cells(ec);
> + if (ret)
> + return ret;
> + *val = ec->cell[channel];
> + return 0;
> + case hwmon_fan:
> + return yoga_ec_fan_read(ec, val);
> + default:
> + return -EOPNOTSUPP;
> + }
> +}
> +
> +static const char * const yoga_ec_temp_labels[YOGA_EC_NUM_TEMP] = {
> + "Charger A",
> + "CPU VR",
> + "5V rail",
> + "Ambient",
> + "Charger B",
> + "Thermistor 6",
> +};
> +
> +static const char * const yoga_ec_cell_labels[YOGA_EC_NUM_CELL] = {
> + "Cell 1", "Cell 2", "Cell 3", "Cell 4",
> +};
> +
> +static int yoga_ec_read_string(struct device *dev, enum hwmon_sensor_types type,
> + u32 attr, int channel, const char **str)
> +{
> + switch (type) {
> + case hwmon_temp:
> + *str = yoga_ec_temp_labels[channel];
> + return 0;
> + case hwmon_in:
> + *str = yoga_ec_cell_labels[channel];
> + return 0;
> + case hwmon_fan:
> + *str = "CPU fan";
> + return 0;
> + default:
> + return -EOPNOTSUPP;
> + }
> +}
> +
> +static enum led_brightness yoga_ec_kbd_led_get(struct led_classdev *led)
> +{
> + struct yoga_ec *ec = container_of(led, struct yoga_ec, kbd_led);
> + u8 raw;
> +
> + guard(mutex)(&ec->lock);
> +
> + if (yoga_ec_ram_read(ec, YOGA_EC_KBLT, &raw))
> + return 0;
> +
> + raw = FIELD_GET(YOGA_EC_KBLT_LEVEL, raw);
> +
> + if (raw == YOGA_EC_KBLT_AUTO)
> + return YOGA_EC_KBLT_MAX;
> +
> + return raw;
> +}
> +
> +static int yoga_ec_kbd_led_level(struct yoga_ec *ec, u8 level, u8 *old)
> +{
> + u8 raw;
> + int ret;
> +
> + ret = yoga_ec_ram_read(ec, YOGA_EC_KBLT, &raw);
> + if (ret)
> + return ret;
> +
> + if (old)
> + *old = FIELD_GET(YOGA_EC_KBLT_LEVEL, raw);
> +
> + raw &= ~YOGA_EC_KBLT_LEVEL;
> + raw |= FIELD_PREP(YOGA_EC_KBLT_LEVEL, level);
> +
> + return yoga_ec_ram_write(ec, YOGA_EC_KBLT, raw);
> +}
> +
> +static int yoga_ec_kbd_led_set(struct led_classdev *led,
> + enum led_brightness brightness)
> +{
> + struct yoga_ec *ec = container_of(led, struct yoga_ec, kbd_led);
> +
> + guard(mutex)(&ec->lock);
> +
> + return yoga_ec_kbd_led_level(ec, brightness, NULL);
> +}
> +
> +static const struct hwmon_ops yoga_ec_hwmon_ops = {
> + .is_visible = yoga_ec_is_visible,
> + .read = yoga_ec_read,
> + .read_string = yoga_ec_read_string,
> +};
> +
> +static const struct hwmon_channel_info * const yoga_ec_hwmon_info[] = {
> + HWMON_CHANNEL_INFO(in,
> + HWMON_I_INPUT | HWMON_I_LABEL,
> + HWMON_I_INPUT | HWMON_I_LABEL,
> + HWMON_I_INPUT | HWMON_I_LABEL,
> + HWMON_I_INPUT | HWMON_I_LABEL),
> + HWMON_CHANNEL_INFO(temp,
> + HWMON_T_INPUT | HWMON_T_LABEL,
> + HWMON_T_INPUT | HWMON_T_LABEL,
> + HWMON_T_INPUT | HWMON_T_LABEL,
> + HWMON_T_INPUT | HWMON_T_LABEL,
> + HWMON_T_INPUT | HWMON_T_LABEL,
> + HWMON_T_INPUT | HWMON_T_LABEL),
> + HWMON_CHANNEL_INFO(fan,
> + HWMON_F_INPUT | HWMON_F_LABEL),
> + NULL
> +};
> +
> +static const struct hwmon_chip_info yoga_ec_chip_info = {
> + .ops = &yoga_ec_hwmon_ops,
> + .info = yoga_ec_hwmon_info,
> +};
> +
> +static int yoga_ec_fn_lock_toggle(struct yoga_ec *ec)
> +{
> + u8 raw;
> + int ret;
> +
> + ret = yoga_ec_ram_read(ec, YOGA_EC_SCUK, &raw);
> + if (ret)
> + return ret;
> +
> + if (raw & YOGA_EC_SCUK_FN_LOCK)
> + raw &= ~YOGA_EC_SCUK_FN_LOCK_MASK;
> + else
> + raw |= YOGA_EC_SCUK_FN_LOCK_MASK;
> +
> + return yoga_ec_ram_write(ec, YOGA_EC_SCUK, raw);
> +}
> +
> +static void yoga_ec_report_key(struct yoga_ec *ec, unsigned int code)
> +{
> + if (!ec->keys)
> + return;
> +
> + input_report_key(ec->keys, code, 1);
> + input_sync(ec->keys);
> + input_report_key(ec->keys, code, 0);
> + input_sync(ec->keys);
> +}
> +
> +static irqreturn_t yoga_ec_irq(int irq, void *data)
> +{
> + struct yoga_ec *ec = data;
> + unsigned int i;
> +
> + guard(mutex)(&ec->lock);
> +
> + for (i = 0; i < YOGA_EC_EVT_MAX_DRAIN; i++) {
> + u8 resp[YOGA_EC_RESP_LEN];
> +
> + if (yoga_ec_command(ec, YOGA_EC_CMD_EVENT, 0, 0, resp))
> + break;
> + if (!resp[0])
> + break;
> +
> + switch (resp[0]) {
> + case YOGA_EC_EVT_FN_LOCK:
> + yoga_ec_fn_lock_toggle(ec);
> + break;
> + case YOGA_EC_EVT_MIC_MUTE:
> + yoga_ec_report_key(ec, KEY_MICMUTE);
> + break;
> + case YOGA_EC_EVT_FLIGHT_MODE:
> + yoga_ec_report_key(ec, KEY_RFKILL);
> + break;
> + default:
> + break;
> + }
> + }
> +
> + return IRQ_HANDLED;
> +}
> +
> +static void yoga_ec_register_keys(struct yoga_ec *ec)
> +{
> + struct device *dev = &ec->client->dev;
> + struct input_dev *input;
> + int ret;
> +
> + input = devm_input_allocate_device(dev);
> + if (!input) {
> + dev_warn(dev, "no memory for the hotkey device\n");
Add include.
> + return;
> + }
> +
> + input->name = "Lenovo Yoga Slim 7x hotkeys";
> + input->phys = "lenovo-yoga-slim7x-gen11-ec/input0";
> + input->id.bustype = BUS_I2C;
> + input_set_capability(input, EV_KEY, KEY_MICMUTE);
> + input_set_capability(input, EV_KEY, KEY_RFKILL);
> +
> + ret = input_register_device(input);
> + if (ret) {
> + dev_warn(dev, "cannot register the hotkey device: %d\n", ret);
> + return;
> + }
> +
> + ec->keys = input;
> +}
> +
> +static ssize_t fn_lock_show(struct device *dev, struct device_attribute *attr,
> + char *buf)
> +{
> + struct yoga_ec *ec = dev_get_drvdata(dev);
> + u8 raw;
> + int ret;
> +
> + scoped_guard(mutex, &ec->lock)
> + ret = yoga_ec_ram_read(ec, YOGA_EC_SCUK, &raw);
> +
> + if (ret)
> + return ret;
> +
> + return sysfs_emit(buf, "%u\n", !!(raw & YOGA_EC_SCUK_FN_LOCK));
> +}
> +
> +static ssize_t fn_lock_store(struct device *dev, struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + struct yoga_ec *ec = dev_get_drvdata(dev);
> + bool enable;
> + u8 raw;
> + int ret;
> +
> + ret = kstrtobool(buf, &enable);
> + if (ret)
> + return ret;
> +
> + guard(mutex)(&ec->lock);
> +
> + ret = yoga_ec_ram_read(ec, YOGA_EC_SCUK, &raw);
> + if (ret)
> + return ret;
> +
> + if (enable)
> + raw |= YOGA_EC_SCUK_FN_LOCK_MASK;
> + else
> + raw &= ~YOGA_EC_SCUK_FN_LOCK_MASK;
> +
> + ret = yoga_ec_ram_write(ec, YOGA_EC_SCUK, raw);
> + if (ret)
> + return ret;
> +
> + return count;
> +}
> +static DEVICE_ATTR_RW(fn_lock);
> +
> +static struct attribute *yoga_ec_attrs[] = {
> + &dev_attr_fn_lock.attr,
> + NULL
> +};
> +ATTRIBUTE_GROUPS(yoga_ec);
> +
> +static int yoga_ec_probe(struct i2c_client *client)
> +{
> + struct device *dev = &client->dev;
> + struct device *hwmon;
> + struct yoga_ec *ec;
> + unsigned int i;
> + int ret;
> +
> + if (!i2c_check_functionality(client->adapter,
> + I2C_FUNC_SMBUS_I2C_BLOCK))
> + return dev_err_probe(dev, -ENODEV,
> + "adapter does not support SMBus block transfers\n");
> +
> + ec = devm_kzalloc(dev, sizeof(*ec), GFP_KERNEL);
> + if (!ec)
> + return -ENOMEM;
> +
> + ec->client = client;
> + i2c_set_clientdata(client, ec);
> +
> + ret = devm_mutex_init(dev, &ec->lock);
> + if (ret)
> + return ret;
> +
> + for (i = 0; i < YOGA_EC_NUM_TEMP; i++) {
> + u8 raw;
> +
> + ret = yoga_ec_ram_read(ec, YOGA_EC_TEMP_FIRST + i, &raw);
> + if (ret)
> + return dev_err_probe(dev, ret,
> + "failed to read thermistor %u\n", i);
> +
> + if (raw > 0 && raw <= YOGA_EC_TEMP_MAX)
> + ec->temp_present |= BIT(i);
> + }
> +
> + if (!ec->temp_present)
> + return dev_err_probe(dev, -ENODEV,
> + "no thermistor reported a usable value\n");
Please use braces in multi-line constructs.
> +
> + dev_dbg(dev, "thermistors present: %#lx\n", ec->temp_present);
> +
> + hwmon = devm_hwmon_device_register_with_info(dev, "yoga_slim7x_ec", ec,
> + &yoga_ec_chip_info, NULL);
> + if (IS_ERR(hwmon))
> + return PTR_ERR(hwmon);
> +
> + ec->kbd_led.name = "platform::kbd_backlight";
> + ec->kbd_led.max_brightness = YOGA_EC_KBLT_MAX;
> + ec->kbd_led.brightness_get = yoga_ec_kbd_led_get;
> + ec->kbd_led.brightness_set_blocking = yoga_ec_kbd_led_set;
> +
> + ret = devm_led_classdev_register(dev, &ec->kbd_led);
> + if (ret)
> + return dev_err_probe(dev, ret,
> + "failed to register keyboard backlight\n");
> +
> + if (client->irq) {
> + yoga_ec_register_keys(ec);
> +
> + ret = devm_request_threaded_irq(dev, client->irq, NULL,
> + yoga_ec_irq, IRQF_ONESHOT,
> + "lenovo-yoga-slim7x-gen11-ec", ec);
> + if (ret)
> + return dev_err_probe(dev, ret, "cannot request the EC interrupt\n");
> +
> + scoped_guard(mutex, &ec->lock) {
> + unsigned int i;
> +
> + for (i = 0; i < YOGA_EC_EVT_MAX_DRAIN; i++) {
> + u8 resp[YOGA_EC_RESP_LEN];
> +
> + if (yoga_ec_command(ec, YOGA_EC_CMD_EVENT, 0, 0, resp))
> + break;
> + if (!resp[0])
> + break;
> + }
> + }
> + }
> +
> + return 0;
> +}
> +
> +static int yoga_ec_suspend(struct device *dev)
> +{
> + struct yoga_ec *ec = dev_get_drvdata(dev);
> +
> + guard(mutex)(&ec->lock);
> + ec->kblt_restore = !yoga_ec_kbd_led_level(ec, 0, &ec->kblt_level);
> +
> + return 0;
> +}
> +
> +static int yoga_ec_resume(struct device *dev)
> +{
> + struct yoga_ec *ec = dev_get_drvdata(dev);
> +
> + guard(mutex)(&ec->lock);
> + ec->cell_cached = false;
> +
> + if (ec->kblt_restore) {
> + ec->kblt_restore = false;
> + yoga_ec_kbd_led_level(ec, ec->kblt_level, NULL);
> + }
> +
> + return 0;
> +}
> +
> +static DEFINE_SIMPLE_DEV_PM_OPS(yoga_ec_pm_ops, yoga_ec_suspend, yoga_ec_resume);
> +
> +static const struct of_device_id yoga_ec_of_match[] = {
> + { .compatible = "lenovo,yoga-slim7x-gen11-ec" },
> + { }
> +};
> +MODULE_DEVICE_TABLE(of, yoga_ec_of_match);
> +
> +static const struct i2c_device_id yoga_ec_i2c_id[] = {
> + { "yoga-slim7x-gen11-ec" },
> + { }
> +};
> +MODULE_DEVICE_TABLE(i2c, yoga_ec_i2c_id);
> +
> +static struct i2c_driver yoga_ec_driver = {
> + .driver = {
> + .name = "lenovo-yoga-slim7x-gen11-ec",
> + .of_match_table = yoga_ec_of_match,
> + .dev_groups = yoga_ec_groups,
> + .pm = pm_sleep_ptr(&yoga_ec_pm_ops),
> + },
> + .probe = yoga_ec_probe,
> + .id_table = yoga_ec_i2c_id,
> +};
> +module_i2c_driver(yoga_ec_driver);
> +
> +MODULE_AUTHOR("Oleg Keri <okerixx@gmail.com>");
> +MODULE_DESCRIPTION("Lenovo Yoga Slim 7x Gen 11 embedded controller");
> +MODULE_LICENSE("GPL");
>
--
i.
next prev parent reply other threads:[~2026-09-21 15:20 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-21 14:49 [PATCH v3 0/3] platform/arm64: Lenovo Yoga Slim 7x Gen 11 embedded controller Oleg Keri
2026-09-21 14:49 ` [PATCH v3 1/3] dt-bindings: embedded-controller: add Lenovo Yoga Slim 7x Gen 11 EC Oleg Keri
2026-09-21 14:49 ` [PATCH v3 2/3] platform/arm64: add Lenovo Yoga Slim 7x Gen 11 EC driver Oleg Keri
2026-09-21 15:20 ` Ilpo Järvinen [this message]
2026-09-21 15:52 ` Oleg Keri
2026-09-21 14:49 ` [PATCH v3 3/3] arm64: dts: qcom: glymur-lenovo-yoga-slim7x: add the embedded controller Oleg Keri
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=55181989-11d7-ce0d-f9c3-e9dceaaf1fb4@linux.intel.com \
--to=ilpo.jarvinen@linux.intel.com \
--cc=abelvesa@kernel.org \
--cc=andersson@kernel.org \
--cc=bryan.odonoghue@linaro.org \
--cc=conor+dt@kernel.org \
--cc=devicetree@vger.kernel.org \
--cc=hansg@kernel.org \
--cc=konradybcio@kernel.org \
--cc=krzk+dt@kernel.org \
--cc=linux-arm-msm@vger.kernel.org \
--cc=linux-hwmon@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux@roeck-us.net \
--cc=okerixx@gmail.com \
--cc=platform-driver-x86@vger.kernel.org \
--cc=robh@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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®