From: "Ilpo Järvinen" <ilpo.jarvinen@linux.intel.com>
To: Konrad Dybcio <konradybcio@kernel.org>
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>,
Abel Vesa <abelvesa@kernel.org>,
devicetree@vger.kernel.org, LKML <linux-kernel@vger.kernel.org>,
platform-driver-x86@vger.kernel.org,
linux-hwmon@vger.kernel.org, linux-arm-msm@vger.kernel.org,
Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
Subject: Re: [PATCH v2 2/3] platform: arm64: Add a driver for the EC found on ASUS Glymur machines
Date: Tue, 22 Sep 2026 12:49:23 +0300 (EEST) [thread overview]
Message-ID: <f1197190-347b-c0b3-1d4d-c19cb365a082@linux.intel.com> (raw)
In-Reply-To: <20260922-topic-asus_ec-v2-2-0bd923d1b853@oss.qualcomm.com>
On Tue, 22 Sep 2026, Konrad Dybcio wrote:
> From: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
>
> Some Qualcomm Snapdragon X2 Elite (Extreme)-based ASUS laptops (at
> least the Zenbook A14 and A16) expose a custom, somewhat
> Qualcomm reference-derived EC interface. Introduce a driver to
> communicate with said EC, allowing for fan speed reporting, keyboard
> backlight setting and receiving of a number of miscellaneous sideband
> events.
>
> Assisted-by: LLM
> Signed-off-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
> ---
> drivers/platform/arm64/Kconfig | 16 +
> drivers/platform/arm64/Makefile | 1 +
> drivers/platform/arm64/asus-glymur-ec.c | 592 ++++++++++++++++++++++++++++++++
> 3 files changed, 609 insertions(+)
>
> diff --git a/drivers/platform/arm64/Kconfig b/drivers/platform/arm64/Kconfig
> index e32e01b2a9bd..84c475c62990 100644
> --- a/drivers/platform/arm64/Kconfig
> +++ b/drivers/platform/arm64/Kconfig
> @@ -33,6 +33,22 @@ config EC_ACER_ASPIRE1
> laptop where this information is not properly exposed via the
> standard ACPI devices.
>
> +config EC_ASUS_GLYMUR
> + tristate "ASUS Zenbook A16 (Qualcomm) Embedded Controller driver"
> + depends on ARCH_QCOM || COMPILE_TEST
> + depends on I2C
> + depends on HWMON
> + depends on INPUT
> + depends on PM
> + depends on THERMAL || THERMAL=n
> + select NEW_LEDS
> + select LEDS_CLASS
> + help
> + Say M or Y here to enable the EC driver for the Qualcomm Snapdragon-
> + based ASUS Zenbook A16 (UX3607OA) and similar machines. It provides
> + temperature sensors readout, fan monitoring, keyboard and EC event
> + reporting.
> +
> config EC_HUAWEI_GAOKUN
> tristate "Huawei Matebook E Go Embedded Controller driver"
> depends on ARCH_QCOM || COMPILE_TEST
> diff --git a/drivers/platform/arm64/Makefile b/drivers/platform/arm64/Makefile
> index 7681be4a46e9..7122e8ea325f 100644
> --- a/drivers/platform/arm64/Makefile
> +++ b/drivers/platform/arm64/Makefile
> @@ -6,6 +6,7 @@
> #
>
> obj-$(CONFIG_EC_ACER_ASPIRE1) += acer-aspire1-ec.o
> +obj-$(CONFIG_EC_ASUS_GLYMUR) += asus-glymur-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
> diff --git a/drivers/platform/arm64/asus-glymur-ec.c b/drivers/platform/arm64/asus-glymur-ec.c
> new file mode 100644
> index 000000000000..dec9b137b820
> --- /dev/null
> +++ b/drivers/platform/arm64/asus-glymur-ec.c
> @@ -0,0 +1,592 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * The EC firmware on the ASUS Zenbook A16 looks to be derived from the
> + * 'Qualcomm Hamoa EC' reference base with many major modifications. Some of
> + * the original interface is reused, albeit many of its core invariants (such
> + * as the capabilities bitmap) are absent.
> + *
> + * It presents itself as two I2C devices:
> + * [0x5b] is the custom ASUS interface
> + * [0x76] is the Hamoa EC-derived interface
> + */
> +
> +#include <linux/cleanup.h>
> +#include <linux/delay.h>
> +#include <linux/device.h>
> +#include <linux/err.h>
> +#include <linux/hwmon.h>
> +#include <linux/i2c.h>
> +#include <linux/interrupt.h>
> +#include <linux/kernel.h>
> +#include <linux/leds.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/pm.h>
> +#include <linux/types.h>
> +#include <linux/unaligned.h>
> +#include <linux/units.h>
> +
> +/* Qualcomm-derived registers (main device @ 0x76) */
> +#define ASUS_QCOM_EC_EVENT_CMD 0x05
> +
> +#define ASUS_QCOM_EC_SUBCMD_FULL_VER 0x0f
> +#define ASUS_QCOM_EC_FULL_VER_LEN 7
> +
> +/* This "read RAM" command isn't actually part of the Qualcomm standard */
> +#define ASUS_QCOM_EC_RECM_OFFS 0x52
> +#define ASUS_QCOM_EC_RECM_FRAME_LEN 5
> +
> +#define ASUS_QCOM_EC_RAM_FAN_CPU 0x0602
> +#define ASUS_QCOM_EC_RAM_AC_STATUS 0x0609
> +#define ASUS_QCOM_EC_RAM_FAN_GPU 0x0624
> +
> +#define ASUS_QCOM_EC_MODERN_STANDBY_CMD 0x23
> +#define ASUS_QCOM_EC_MODERN_STANDBY_ENTER 0x07
> +#define ASUS_QCOM_EC_MODERN_STANDBY_EXIT 0x08
> +
> +/* ASUS-specific registers (secondary device @ 0x5b) */
> +#define ASUS_EC_SUBDEV_ADDR 0x5b
> +
> +#define ASUS_EC_REQ_REG 0x10
> +#define ASUS_EC_RESP_REG 0x11
> +
> +#define ASUS_EC_PAGE_MAILBOX 0xc4
> + #define ASUS_EC_MBOX_REG_CMD 0x30
> + #define ASUS_EC_MBOX_REG_SUBCMD 0x31
> + #define ASUS_EC_MBOX_REG_DATA 0x32
> +
> +#define ASUS_EC_MBOX_CMD_KBD 0x01
> + #define ASUS_EC_MBOX_SUBCMD_KBD_EN 0x81
> + #define ASUS_EC_MBOX_DATA_KBD_EN 0x04
> + #define ASUS_EC_MBOX_SUBCMD_KBD_LVL 0x87
> +
> +#define ASUS_EC_MBOX_CMD_MISC 0x02
> + #define ASUS_EC_MBOX_SUBCMD_MISC_ENABLE 0x83
> +
> +#define ASUS_EC_PAGE_TEMPERATURE 0xc6
> + #define ASUS_EC_TEMP_REG_CPU 0xa6
> + #define ASUS_EC_TEMP_REG_SOC 0x2a
> + #define ASUS_EC_TEMP_MAX 0x7f
> +
> +#define ASUS_EC_KBD_MAX_BRIGHTNESS 3
> +
> +static const u8 asus_ec_kbd_levels[] = {
> + [0] = 0x00,
> + [1] = 0x55,
> + [2] = 0xaa,
> + [3] = 0xff,
> +};
> +
> +enum asus_ec_event {
> + ASUS_EC_EVT_CHARGE_STATE = 0x1c,
> + ASUS_EC_EVT_AC_STATE = 0x1d,
> + ASUS_EC_EVT_CRIT_TRIP = 0x1e,
> + ASUS_EC_EVT_THERMISTOR = 0x4f,
> + ASUS_EC_EVT_HOTKEY = 0xda,
> + ASUS_EC_EVT_FAN_STATUS = 0xdb,
> + ASUS_EC_EVT_THERMAL_TRIP = 0xe0,
> + ASUS_EC_EVT_CHARGE_COMMIT = 0xe4,
> +};
> +
> +struct asus_ec {
> + struct i2c_client *client;
> + struct i2c_client *subdev_client;
> + /* Serializes all EC access across both I2C devices */
> + struct mutex lock;
> + struct device *hwmon_dev;
> + struct led_classdev kbd_led;
> +};
> +
> +static int asus_qcom_ec_write(struct asus_ec *ec, u8 offset,
> + const u8 *buf, u8 len)
> +{
> + int ret;
> +
> + lockdep_assert_held(&ec->lock);
Add include.
> +
> + ret = i2c_smbus_write_i2c_block_data(ec->client, offset, len, buf);
> + if (ret < 0)
> + dev_dbg(&ec->client->dev,
> + "Main device write off %#x len %u failed: %d\n",
> + offset, len, ret);
Add include for dev_dbg().
Please add braces to any multiline construct even if they're just a
single statement in the block.
> +
> + return ret;
> +}
> +
> +static int asus_qcom_ec_read(struct asus_ec *ec, u8 offset, u8 *buf, u8 len)
> +{
> + int ret;
> +
> + lockdep_assert_held(&ec->lock);
> +
> + ret = i2c_smbus_read_i2c_block_data(ec->client, offset, len, buf);
> + if (ret < 0)
> + return ret;
> + if (ret < len) {
> + dev_err(&ec->client->dev,
> + "Short read off %#x: got %d of %u bytes\n",
> + offset, ret, len);
> + return -EIO;
> + }
> +
> + return 0;
> +}
> +
> +static int asus_qcom_ec_read_ram(struct asus_ec *ec, u16 addr, u8 count, u16 *out)
> +{
> + u8 frame[ASUS_QCOM_EC_RECM_FRAME_LEN] = { 0, 0, count, 0, 0 };
> + u8 resp[ASUS_QCOM_EC_RECM_FRAME_LEN] = { };
> + int ret;
> +
> + lockdep_assert_held(&ec->lock);
> +
> + if (count != 1 && count != 2)
> + return -EINVAL;
> +
> + put_unaligned_be16(addr, frame);
> +
> + ret = asus_qcom_ec_write(ec, ASUS_QCOM_EC_RECM_OFFS, frame,
> + ASUS_QCOM_EC_RECM_FRAME_LEN);
> + if (ret < 0)
> + return ret;
> +
> + ret = asus_qcom_ec_read(ec, ASUS_QCOM_EC_RECM_OFFS, resp,
> + ASUS_QCOM_EC_RECM_FRAME_LEN);
> + if (ret < 0)
> + return ret;
> +
> + *out = (count == 2) ? get_unaligned_le16(resp) : resp[0];
> +
> + return 0;
> +}
> +
> +static int asus_ec_readb(struct asus_ec *ec, u8 page, u8 reg, u8 *out)
> +{
> + u8 sel[2] = { page, reg };
> + int ret;
> +
> + lockdep_assert_held(&ec->lock);
> +
> + ret = i2c_smbus_write_i2c_block_data(ec->subdev_client,
> + ASUS_EC_REQ_REG,
> + sizeof(sel), sel);
Fits to two lines.
> + if (ret < 0)
> + return ret;
> +
> + ret = i2c_smbus_read_byte_data(ec->subdev_client, ASUS_EC_RESP_REG);
> + if (ret < 0)
> + return ret;
> +
> + *out = ret;
> +
> + return 0;
> +}
> +
> +static int asus_ec_writeb(struct asus_ec *ec, u8 page, u8 reg, u8 data)
> +{
> + u8 sel[2] = { page, reg };
> + int ret;
> +
> + lockdep_assert_held(&ec->lock);
> +
> + ret = i2c_smbus_write_i2c_block_data(ec->subdev_client,
> + ASUS_EC_REQ_REG,
> + sizeof(sel), sel);
Fits to two lines.
> + if (ret < 0)
> + return ret;
> +
> + return i2c_smbus_write_byte_data(ec->subdev_client,
> + ASUS_EC_RESP_REG, data);
> +}
> +
> +static int asus_ec_mailbox_cmd(struct asus_ec *ec, u8 cmd, u8 sub, u8 data)
> +{
> + int ret, tries;
> + u8 status;
> +
> + lockdep_assert_held(&ec->lock);
> +
> + for (tries = 5; tries > 0; tries--) {
> + ret = asus_ec_readb(ec, ASUS_EC_PAGE_MAILBOX,
> + ASUS_EC_MBOX_REG_CMD, &status);
> + if (ret < 0)
> + return ret;
> + if (status == 0)
> + break;
> + usleep_range(5000, 6000);
> + }
> + if (tries == 0)
> + return -EBUSY;
> +
> + ret = asus_ec_writeb(ec, ASUS_EC_PAGE_MAILBOX, ASUS_EC_MBOX_REG_SUBCMD, sub);
> + if (ret < 0)
> + return ret;
> +
> + ret = asus_ec_writeb(ec, ASUS_EC_PAGE_MAILBOX, ASUS_EC_MBOX_REG_DATA, data);
> + if (ret < 0)
> + return ret;
> +
> + ret = asus_ec_writeb(ec, ASUS_EC_PAGE_MAILBOX, ASUS_EC_MBOX_REG_CMD, cmd);
> + if (ret < 0)
> + return ret;
> +
> + for (tries = 5; tries > 0; tries--) {
> + ret = asus_ec_readb(ec, ASUS_EC_PAGE_MAILBOX,
> + ASUS_EC_MBOX_REG_CMD, &status);
> + if (ret < 0)
> + return ret;
> + if (status == 0)
> + return 0;
> +
> + usleep_range(5000, 6000);
> + }
This waiting code seems to be duplicated so it should be moved into own
function.
> +
> + return -ETIMEDOUT;
> +}
> +
> +static int asus_ec_enable_writes(struct asus_ec *ec)
> +{
> + guard(mutex)(&ec->lock);
> +
> + return asus_ec_mailbox_cmd(ec, ASUS_EC_MBOX_CMD_MISC,
> + ASUS_EC_MBOX_SUBCMD_MISC_ENABLE, 1);
> +}
> +
> +static int asus_ec_read_version(struct asus_ec *ec, u8 *major, u8 *minor, u8 *patch)
> +{
> + u8 resp[ASUS_QCOM_EC_FULL_VER_LEN + 1];
> + int ret;
> +
> + guard(mutex)(&ec->lock);
> +
> + ret = asus_qcom_ec_read(ec, ASUS_QCOM_EC_SUBCMD_FULL_VER,
> + resp, sizeof(resp));
> + if (ret < 0)
> + return ret;
> + if (resp[0] < ASUS_QCOM_EC_FULL_VER_LEN)
> + return -EIO;
> +
> + *major = resp[3];
> + *minor = resp[2];
> + *patch = resp[1];
> +
> + return 0;
> +}
> +
> +static int asus_ec_get_temp(struct asus_ec *ec, u8 reg, long *millideg)
> +{
> + u8 raw;
> + int ret;
> +
> + guard(mutex)(&ec->lock);
> +
> + ret = asus_ec_readb(ec, ASUS_EC_PAGE_TEMPERATURE, reg, &raw);
> + if (ret < 0)
> + return ret;
> + if (raw > ASUS_EC_TEMP_MAX)
> + return -ENODATA;
> +
> + *millideg = raw * MILLIDEGREE_PER_DEGREE;
> +
> + return 0;
> +}
> +
> +static int asus_ec_get_fan_rpm(struct asus_ec *ec, u16 base, long *rpm)
> +{
> + u16 val;
> + int ret;
> +
> + guard(mutex)(&ec->lock);
> +
> + ret = asus_qcom_ec_read_ram(ec, base, 2, &val);
> + if (ret < 0)
> + return ret;
> +
> + *rpm = val;
> +
> + return 0;
> +}
> +
> +static irqreturn_t asus_ec_irq(int irq, void *data)
> +{
> + struct asus_ec *ec = data;
> + struct device *dev = &ec->client->dev;
> + int code;
> +
> + scoped_guard(mutex, &ec->lock)
> + code = i2c_smbus_read_byte_data(ec->client, ASUS_QCOM_EC_EVENT_CMD);
> +
> + if (code < 0) {
> + dev_err_ratelimited(dev, "Failed to read EC event: %d\n", code);
> + return IRQ_HANDLED;
> + }
> + if (code == 0 || code == 0xff)
Could these literals be named with defines?
> + return IRQ_HANDLED;
> +
> + switch (code) {
> + case ASUS_EC_EVT_HOTKEY:
> + /* Present in DSDT, don't know what triggers it */
> + dev_dbg_ratelimited(dev, "EC hotkey event\n");
> + break;
> +
> + case ASUS_EC_EVT_FAN_STATUS:
> + dev_dbg_ratelimited(dev, "Fan status changed\n");
> + break;
> +
> + /* Maybe the temperature of the EC itself? */
> + case ASUS_EC_EVT_THERMAL_TRIP:
> + case ASUS_EC_EVT_CRIT_TRIP:
> + /* ASUS_EC_TEMP_SOC climbing above 91 *C or falling below 92 *C */
> + case ASUS_EC_EVT_THERMISTOR:
> + dev_dbg_ratelimited(dev, "Thermal event %#x\n", code);
> + break;
> +
> + case ASUS_EC_EVT_AC_STATE: {
> + u16 ac = 0;
> + int ret;
> +
> + /* TODO: ping SoCCP over the pmic_glink OEM-specific interface */
> + scoped_guard(mutex, &ec->lock)
> + ret = asus_qcom_ec_read_ram(ec, ASUS_QCOM_EC_RAM_AC_STATUS, 1, &ac);
> + if (ret < 0)
> + dev_warn_ratelimited(dev,
> + "Failed to read AC status: %d\n",
> + ret);
Add braces to if/else.
Usually, the error handling is done on the same level as the call, even if
it wouldn't strictly need to hold the mutex.
> + else
> + dev_info_ratelimited(dev, "AC event: 0x%x\n", ac);
How is this print useful for a normal user?
> + break;
> + }
> + case ASUS_EC_EVT_CHARGE_STATE:
> + case ASUS_EC_EVT_CHARGE_COMMIT:
> + break;
> +
> + default:
> + dev_info_ratelimited(dev, "unknown EC event: %#x\n", code);
> + break;
> + }
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int asus_ec_kbd_led_set(struct led_classdev *led,
> + enum led_brightness brightness)
> +{
> + struct asus_ec *ec = container_of(led, struct asus_ec, kbd_led);
Please add include for container_of().
> + int ret;
> +
> + if (brightness > ASUS_EC_KBD_MAX_BRIGHTNESS)
> + brightness = ASUS_EC_KBD_MAX_BRIGHTNESS;
> +
> + guard(mutex)(&ec->lock);
> +
> + ret = asus_ec_mailbox_cmd(ec, ASUS_EC_MBOX_CMD_KBD, ASUS_EC_MBOX_SUBCMD_KBD_EN,
> + ASUS_EC_MBOX_DATA_KBD_EN);
> + if (ret < 0)
> + return ret;
> +
> + return asus_ec_mailbox_cmd(ec, ASUS_EC_MBOX_CMD_KBD, ASUS_EC_MBOX_SUBCMD_KBD_LVL,
> + asus_ec_kbd_levels[brightness]);
> +}
> +
> +enum asus_ec_temp_channel {
> + /* External sensor? */
> + ASUS_EC_TEMP_CPU,
> + /* Likely SoC-internal temp data communicated over a side channel */
> + ASUS_EC_TEMP_SOC,
> + ASUS_EC_TEMP_COUNT,
> +};
> +
> +static umode_t asus_ec_hwmon_is_visible(const void *data,
> + enum hwmon_sensor_types type,
> + u32 attr, int channel)
> +{
> + switch (type) {
> + case hwmon_fan:
> + case hwmon_temp:
> + return 0444;
> + default:
> + return 0;
> + }
> +}
> +
> +static int asus_ec_hwmon_read(struct device *dev,
> + enum hwmon_sensor_types type,
> + u32 attr, int channel, long *val)
> +{
> + struct asus_ec *ec = dev_get_drvdata(dev);
> +
> + switch (type) {
> + case hwmon_temp:
> + switch (channel) {
> + case ASUS_EC_TEMP_CPU:
> + return asus_ec_get_temp(ec, ASUS_EC_TEMP_REG_CPU, val);
> + case ASUS_EC_TEMP_SOC:
> + return asus_ec_get_temp(ec, ASUS_EC_TEMP_REG_SOC, val);
> + default:
> + return -EINVAL;
> + }
> + case hwmon_fan:
> + switch (channel) {
> + case 0:
> + return asus_ec_get_fan_rpm(ec, ASUS_QCOM_EC_RAM_FAN_CPU, val);
> + case 1:
> + return asus_ec_get_fan_rpm(ec, ASUS_QCOM_EC_RAM_FAN_GPU, val);
> + default:
> + return -EINVAL;
> + }
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static int asus_ec_hwmon_read_string(struct device *dev,
> + enum hwmon_sensor_types type,
> + u32 attr, int channel, const char **str)
> +{
> + if (type != hwmon_temp || channel >= ASUS_EC_TEMP_COUNT)
> + return -EINVAL;
> +
> + switch (channel) {
> + case ASUS_EC_TEMP_CPU:
> + *str = "CPU";
> + break;
> + case ASUS_EC_TEMP_SOC:
> + *str = "SoC";
> + break;
> + }
> +
> + return 0;
> +}
> +
> +static const struct hwmon_channel_info * const asus_ec_hwmon_info[] = {
> + HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ),
> + HWMON_CHANNEL_INFO(temp,
> + HWMON_T_INPUT | HWMON_T_LABEL,
> + HWMON_T_INPUT | HWMON_T_LABEL),
> + HWMON_CHANNEL_INFO(fan,
> + HWMON_F_INPUT,
> + HWMON_F_INPUT),
> + NULL
> +};
> +
> +static const struct hwmon_ops asus_ec_hwmon_ops = {
> + .is_visible = asus_ec_hwmon_is_visible,
> + .read = asus_ec_hwmon_read,
> + .read_string = asus_ec_hwmon_read_string,
> +};
> +
> +static const struct hwmon_chip_info asus_ec_hwmon_chip_info = {
> + .ops = &asus_ec_hwmon_ops,
> + .info = asus_ec_hwmon_info,
> +};
> +
> +static int asus_glymur_ec_probe(struct i2c_client *client)
> +{
> + struct device *dev = &client->dev;
> + u8 major, minor, patch;
> + struct asus_ec *ec;
> + int ret;
> +
> + 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;
> +
> + ec->subdev_client = devm_i2c_new_dummy_device(dev, client->adapter,
> + ASUS_EC_SUBDEV_ADDR);
> + if (IS_ERR(ec->subdev_client))
> + return dev_err_probe(dev, PTR_ERR(ec->subdev_client),
> + "Failed to register 0x5b EC device\n");
Add braces to multiline statements.
> + ret = asus_ec_enable_writes(ec);
> + if (ret < 0)
> + return dev_err_probe(dev, ret, "Failed to enable EC direct access: %d\n", ret);
> +
> + ret = asus_ec_read_version(ec, &major, &minor, &patch);
> + if (ret < 0)
> + return dev_err_probe(dev, ret, "Failed to read EC version: %d\n", ret);
> +
> + dev_dbg(dev, "ASUS Zenbook EC version %u.%u.%u\n", major, minor, patch);
> +
> + ec->kbd_led.name = "asus::kbd_backlight";
> + ec->kbd_led.max_brightness = ASUS_EC_KBD_MAX_BRIGHTNESS;
> + ec->kbd_led.brightness_set_blocking = asus_ec_kbd_led_set;
> + ec->kbd_led.flags = LED_CORE_SUSPENDRESUME;
> +
> + ret = devm_led_classdev_register(dev, &ec->kbd_led);
> + if (ret)
> + return dev_err_probe(dev, ret, "Failed to register keyboard LED\n");
> +
> + ret = devm_request_threaded_irq(dev, client->irq, NULL, asus_ec_irq,
> + IRQF_ONESHOT, dev_name(dev), ec);
> + if (ret)
> + return dev_err_probe(dev, ret, "Failed to request IRQ\n");
> +
> + ec->hwmon_dev = devm_hwmon_device_register_with_info(dev,
> + "asus_glymur_ec",
> + ec,
> + &asus_ec_hwmon_chip_info,
> + NULL);
> + if (IS_ERR(ec->hwmon_dev))
> + return dev_err_probe(dev, PTR_ERR(ec->hwmon_dev),
> + "Failed to register hwmon\n");
Ditto.
> +
> + return 0;
> +}
> +
> +static int asus_glymur_ec_modern_standby(struct asus_ec *ec, u8 val)
> +{
> + guard(mutex)(&ec->lock);
> +
> + return i2c_smbus_write_byte_data(ec->client,
> + ASUS_QCOM_EC_MODERN_STANDBY_CMD, val);
> +}
> +
> +static int asus_glymur_ec_suspend(struct device *dev)
> +{
> + return asus_glymur_ec_modern_standby(dev_get_drvdata(dev),
> + ASUS_QCOM_EC_MODERN_STANDBY_ENTER);
> +}
> +
> +static int asus_glymur_ec_resume(struct device *dev)
> +{
> + return asus_glymur_ec_modern_standby(dev_get_drvdata(dev),
> + ASUS_QCOM_EC_MODERN_STANDBY_EXIT);
> +}
> +
> +static DEFINE_SIMPLE_DEV_PM_OPS(asus_glymur_ec_pm_ops,
> + asus_glymur_ec_suspend,
> + asus_glymur_ec_resume);
> +
> +static const struct of_device_id asus_glymur_ec_of_match[] = {
> + { .compatible = "asus,zenbook-a14-ux3407na-ec" },
> + { .compatible = "asus,zenbook-a16-ux3607oa-ec" },
> + {}
> +};
> +MODULE_DEVICE_TABLE(of, asus_glymur_ec_of_match);
> +
> +static const struct i2c_device_id asus_glymur_ec_id[] = {
> + { "asus-glymur-ec" },
> + {}
> +};
> +MODULE_DEVICE_TABLE(i2c, asus_glymur_ec_id);
> +
> +static struct i2c_driver asus_glymur_ec_driver = {
> + .driver = {
> + .name = "asus-glymur-ec",
> + .of_match_table = asus_glymur_ec_of_match,
> + .pm = pm_sleep_ptr(&asus_glymur_ec_pm_ops),
> + },
> + .probe = asus_glymur_ec_probe,
> + .id_table = asus_glymur_ec_id,
> +};
> +module_i2c_driver(asus_glymur_ec_driver);
> +
> +MODULE_DESCRIPTION("ASUS Zenbook A16 (Qualcomm) Embedded Controller driver");
> +MODULE_LICENSE("GPL");
>
>
--
i.
next prev parent reply other threads:[~2026-09-22 9:49 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-22 9:33 [PATCH v2 0/3] Asus Zenbook A16/A14 (UX3607OA/UX3407NA) EC driver Konrad Dybcio
2026-09-22 9:33 ` [PATCH v2 1/3] dt-bindings: embedded-controller: Add ASUS Zenbook A16 EC Konrad Dybcio
2026-09-22 9:37 ` sashiko-bot
2026-09-22 9:33 ` [PATCH v2 2/3] platform: arm64: Add a driver for the EC found on ASUS Glymur machines Konrad Dybcio
2026-09-22 9:43 ` sashiko-bot
2026-09-22 9:49 ` Ilpo Järvinen [this message]
2026-09-22 9:33 ` [PATCH v2 3/3] arm64: dts: qcom: glymur-zenbook-a16: Add Embedded Controller Konrad Dybcio
2026-09-22 9:38 ` sashiko-bot
2026-09-22 20:31 ` Abel Vesa
2026-09-23 10:04 ` Konrad Dybcio
2026-09-24 11:33 ` Abel Vesa
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=f1197190-347b-c0b3-1d4d-c19cb365a082@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=konrad.dybcio@oss.qualcomm.com \
--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=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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.