From: Ziqiang Xia via B4 Relay <devnull+xiazq123666.gmail.com@kernel.org>
To: "Armin Wolf" <W_Armin@gmx.de>, "Hans de Goede" <hansg@kernel.org>,
"Ilpo Järvinen" <ilpo.jarvinen@linux.intel.com>,
"Guenter Roeck" <linux@roeck-us.net>
Cc: platform-driver-x86@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-hwmon@vger.kernel.org,
Ziqiang Xia <xiazq123666@gmail.com>
Subject: [PATCH RFC 2/2] platform/x86: Add Emdoor Intel EWMI platform driver
Date: Fri, 13 Nov 2026 10:01:31 -0800 [thread overview]
Message-ID: <20261113-emdoor-intel-wmi-rfc-v1-2-d77ecf23693b@gmail.com> (raw)
In-Reply-To: <20261113-emdoor-intel-wmi-rfc-v1-0-d77ecf23693b@gmail.com>
From: Ziqiang Xia <xiazq123666@gmail.com>
Add support for the Emdoor Intel EWMI (\_SB.EWMI) WMI interface on
MECHREVO Wujie 14 2026 (WUJIE Series-Lark4-LNL, BIOS EM_LNL326_V1.0.19),
exposing hwmon fan and EC temperature sensors, platform_profile
(balanced/performance), Fn/Win lock and touchpad state attributes,
WMI hotkey sparse keymap events, and AC auto-power-on control.
Signed-off-by: Ziqiang Xia <xiazq123666@gmail.com>
---
MAINTAINERS | 1 +
drivers/platform/x86/Kconfig | 18 +
drivers/platform/x86/Makefile | 1 +
drivers/platform/x86/emdoor-intel-wmi.c | 873 ++++++++++++++++++++++++++++++++
4 files changed, 893 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 70c976ffe..1a377dd56 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -9853,6 +9853,7 @@ M: Ziqiang Xia <xiazq123666@gmail.com>
L: platform-driver-x86@vger.kernel.org
S: Maintained
F: Documentation/ABI/testing/sysfs-bus-wmi-devices-emdoor-ewmi
+F: drivers/platform/x86/emdoor-intel-wmi.c
EMMC CMDQ HOST CONTROLLER INTERFACE (CQHCI) DRIVER
M: Adrian Hunter <adrian.hunter@intel.com>
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index b2c6cc923..4682b79e0 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -120,6 +120,24 @@ config BITLAND_MIFS_WMI
To compile this driver as a module, choose M here: the module will
be called bitland-mifs-wmi.
+config EMDOOR_INTEL_WMI
+ tristate "Emdoor Intel EWMI laptop support"
+ depends on ACPI_EC
+ depends on ACPI_WMI
+ depends on DMI
+ depends on HWMON
+ depends on INPUT
+ select ACPI_PLATFORM_PROFILE
+ select INPUT_SPARSEKMAP
+ help
+ This is a driver for Emdoor Intel EWMI laptops, initially limited to
+ the MECHREVO Wujie 14 2026 (WUJIE Series-Lark4-LNL, EM_LNL326_V1.0.19).
+ It exposes hwmon fan/temperature sensors, platform_profile, hotkeys,
+ Fn/Win lock controls, and AC auto-power-on control.
+
+ To compile this driver as a module, choose M here: the module will be
+ called emdoor-intel-wmi.
+
config ACERHDF
tristate "Acer Aspire One temperature and fan driver"
depends on ACPI_EC && THERMAL
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
index 88f45e03e..45adabafb 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -12,6 +12,7 @@ obj-$(CONFIG_XIAOMI_WMI) += xiaomi-wmi.o
obj-$(CONFIG_REDMI_WMI) += redmi-wmi.o
obj-$(CONFIG_GIGABYTE_WMI) += gigabyte-wmi.o
obj-$(CONFIG_BITLAND_MIFS_WMI) += bitland-mifs-wmi.o
+obj-$(CONFIG_EMDOOR_INTEL_WMI) += emdoor-intel-wmi.o
# Acer
obj-$(CONFIG_ACERHDF) += acerhdf.o
diff --git a/drivers/platform/x86/emdoor-intel-wmi.c b/drivers/platform/x86/emdoor-intel-wmi.c
new file mode 100644
index 000000000..6edd09f6f
--- /dev/null
+++ b/drivers/platform/x86/emdoor-intel-wmi.c
@@ -0,0 +1,873 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Emdoor Intel EWMI platform driver for MECHREVO Wujie 14 2026
+ * (WUJIE Series-Lark4-LNL, BIOS EM_LNL326_V1.0.19).
+ */
+
+#include <linux/acpi.h>
+#include <linux/atomic.h>
+#include <linux/bits.h>
+#include <linux/cleanup.h>
+#include <linux/container_of.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dmi.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/hwmon.h>
+#include <linux/input.h>
+#include <linux/input/sparse-keymap.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/notifier.h>
+#include <linux/platform_profile.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/sysfs.h>
+#include <linux/types.h>
+#include <linux/unaligned.h>
+#include <linux/wmi.h>
+#include <linux/workqueue.h>
+
+#define EWMI_METHOD_GUID "0B30FD67-1B62-4FCA-B10B-50DC7ADE6A39"
+#define EWMI_EVENT_GUID "0B30FD67-1B62-4FCA-B10B-50DC7ADE6DDD"
+#define EWMI_AUTOPOWER_GUID "C75665A3-4382-4646-ADC8-C81AE146E455"
+
+#define EWMI_INPUT_SIZE 64
+#define EWMI_OUTPUT_SIZE 256
+
+enum {
+ EWMI_FAN_COUNT = 2,
+ EWMI_TEMP_COUNT = 4,
+ EWMI_MAILBOX_RETRIES = 3,
+};
+
+struct ewmi_board_config {
+ u8 mailbox_reg;
+ u8 autopower_reg;
+ unsigned int query_settle_ms;
+ unsigned int setter_settle_ms;
+ u8 temp_regs[EWMI_TEMP_COUNT];
+};
+
+struct ewmi_reply {
+ u8 reserved[4];
+ u8 status;
+ u8 data[EWMI_OUTPUT_SIZE - 1];
+};
+
+static_assert(sizeof(struct ewmi_reply) == 260);
+
+struct ewmi_method {
+ struct wmi_device *wdev;
+ const struct ewmi_board_config *board;
+ struct mutex lock; /* Serializes firmware calls and EC sensor reads. */
+ struct device *profile;
+ struct notifier_block event_nb;
+ struct work_struct profile_work;
+ atomic_t mailbox_event_seq; /* EC5D hotkeys invalidate in-flight queries. */
+ bool profile_dirty; /* A partial set must not become a successful no-op. */
+ u8 lock_dirty; /* Uncertain lock settings must be explicitly restored. */
+};
+
+struct ewmi_event {
+ struct input_dev *input;
+ struct mutex lock; /* Serializes input press/release sequences. */
+};
+
+struct ewmi_autopower {
+ struct wmi_device *wdev;
+ const struct ewmi_board_config *board;
+ struct mutex lock; /* Serializes auto-power-on queries and settings. */
+ bool dirty; /* A failed setting may have partially applied. */
+};
+
+static BLOCKING_NOTIFIER_HEAD(ewmi_event_chain);
+
+/* Offsets apply only to the verified MECHREVO Lark4 Lunar Lake BIOS. */
+static const struct ewmi_board_config ewmi_lark4_lnl_config = {
+ .mailbox_reg = 0x5d,
+ .autopower_reg = 0xf5,
+ .query_settle_ms = 100,
+ .setter_settle_ms = 100,
+ .temp_regs = { 0x70, 0x73, 0x72, 0x62 },
+};
+
+static const struct dmi_system_id ewmi_dmi_table[] = {
+ {
+ .ident = "MECHREVO Wujie 14 2026 Lark4 Lunar Lake",
+ .matches = {
+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "MECHREVO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "WUJIE Series-Lark4-LNL"),
+ DMI_EXACT_MATCH(DMI_BIOS_VERSION, "EM_LNL326_V1.0.19"),
+ },
+ .driver_data = (void *)&ewmi_lark4_lnl_config,
+ },
+ { }
+};
+
+static const struct ewmi_board_config *ewmi_get_board_config(void)
+{
+ const struct dmi_system_id *id;
+
+ id = dmi_first_match(ewmi_dmi_table);
+ return id ? id->driver_data : NULL;
+}
+
+/*
+ * GWLS (0x03/0x01) and GFLS (0x03/0x03) read the shared EC mailbox register
+ * (EC5D) after settling without sending a second overlapping command.
+ */
+static int ewmi_mailbox_reply(struct ewmi_method *priv, u8 command,
+ u8 *value)
+{
+ u8 raw;
+ int ret;
+
+ lockdep_assert_held(&priv->lock);
+ ret = ec_read(priv->board->mailbox_reg, &raw);
+ if (ret)
+ return ret;
+
+ switch (command) {
+ case 0x01:
+ if (raw != 0 && raw != 2)
+ goto invalid;
+ *value = raw == 2;
+ return 0;
+ case 0x03:
+ if (raw > 1)
+ goto invalid;
+ *value = raw;
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+invalid:
+ dev_dbg(&priv->wdev->dev,
+ "Settled mailbox 03/%02x transient/invalid raw 0x%02x\n",
+ command, raw);
+ return -EIO;
+}
+
+/*
+ * WMCD returns Package(Buffer(4), Buffer(256)). The WMI core marshals
+ * both buffers into 260 bytes; the firmware status is at byte 4.
+ * Always check the full result size before accessing the payload.
+ */
+static int ewmi_call_status(struct ewmi_method *priv, u8 group, u8 command,
+ u8 argument, void *result, size_t result_len,
+ bool *firmware_failure)
+{
+ bool mailbox_query = group == 0x03 && (command == 0x01 || command == 0x03);
+ u8 request[EWMI_INPUT_SIZE] = { group, command, argument };
+ struct wmi_buffer in = { .length = sizeof(request), .data = request };
+ struct wmi_buffer out = { };
+ const struct ewmi_reply *reply;
+ int attempt, ret = -EIO;
+
+ lockdep_assert_held(&priv->lock);
+ if (firmware_failure)
+ *firmware_failure = false;
+ if (result_len > sizeof(reply->data))
+ return -EINVAL;
+ if (mailbox_query && result_len != sizeof(u8))
+ return -EINVAL;
+
+ for (attempt = 0; attempt < EWMI_MAILBOX_RETRIES; attempt++) {
+ int seq = atomic_read(&priv->mailbox_event_seq);
+
+ ret = wmidev_invoke_method(priv->wdev, 0, 1, &in, &out,
+ sizeof(*reply));
+ if (ret) {
+ dev_warn_ratelimited(&priv->wdev->dev,
+ "WMCD %02x/%02x transport error %d\n",
+ group, command, ret);
+ return ret;
+ }
+ if (!mailbox_query)
+ break;
+
+ /*
+ * GWLS/GFLS share EC5D with each other and with _QF6/_QF9
+ * hotkey notifications. Wait for the EC to settle and retry
+ * if an in-flight hotkey overwrites the mailbox.
+ */
+ kfree(out.data);
+ out = (struct wmi_buffer) { };
+ msleep(priv->board->query_settle_ms);
+ ret = ewmi_mailbox_reply(priv, command, result);
+ if (!ret && atomic_read(&priv->mailbox_event_seq) != seq)
+ ret = -EAGAIN;
+ if (!ret)
+ return 0;
+ if ((ret == -EIO || ret == -EAGAIN) &&
+ attempt + 1 < EWMI_MAILBOX_RETRIES)
+ continue;
+ if (ret == -EAGAIN)
+ ret = -EIO;
+ dev_warn_ratelimited(&priv->wdev->dev,
+ "WMCD %02x/%02x settled mailbox error %d\n",
+ group, command, ret);
+ return ret;
+ }
+
+ reply = out.data;
+ if (reply->status) {
+ if (firmware_failure && reply->status == 1)
+ *firmware_failure = true;
+ dev_warn_ratelimited(&priv->wdev->dev,
+ "WMCD %02x/%02x firmware status %u, data %8ph\n",
+ group, command, reply->status, reply->data);
+ ret = -EIO;
+ } else if (result_len) {
+ memcpy(result, reply->data, result_len);
+ }
+ kfree(out.data);
+ return ret;
+}
+
+static int ewmi_call(struct ewmi_method *priv, u8 group, u8 command,
+ u8 argument, void *result, size_t result_len)
+{
+ return ewmi_call_status(priv, group, command, argument, result,
+ result_len, NULL);
+}
+
+static ssize_t ewmi_switch_show(struct device *dev, u8 command, char *buf)
+{
+ struct ewmi_method *priv = dev_get_drvdata(dev);
+ u8 value;
+ int ret;
+
+ guard(mutex)(&priv->lock);
+ ret = ewmi_call(priv, 0x03, command, 0, &value, sizeof(value));
+ if (ret)
+ return ret;
+ if (value > 1)
+ return -EPROTO;
+ return sysfs_emit(buf, "%u\n", value);
+}
+
+static ssize_t ewmi_switch_store(struct device *dev, u8 command,
+ const char *buf, size_t count)
+{
+ struct ewmi_method *priv = dev_get_drvdata(dev);
+ bool value;
+ u8 actual, mask;
+ int ret;
+
+ if (command != 0x02 && command != 0x04)
+ return -EINVAL;
+ mask = command == 0x02 ? BIT(0) : BIT(1);
+ ret = kstrtobool(buf, &value);
+ if (ret)
+ return ret;
+
+ guard(mutex)(&priv->lock);
+ ret = ewmi_call(priv, 0x03, command - 1, 0, &actual, sizeof(actual));
+ if (ret) {
+ dev_warn_ratelimited(&priv->wdev->dev,
+ "Lock 03/%02x before-set query error %d\n",
+ command, ret);
+ return ret;
+ }
+ if (actual == value && !(priv->lock_dirty & mask))
+ return count;
+ priv->lock_dirty |= mask;
+ ret = ewmi_call(priv, 0x03, command, value, NULL, 0);
+ if (ret)
+ return ret;
+ /* SFLS/SWLS do not wait: a query can overwrite the pending set command. */
+ msleep(priv->board->setter_settle_ms);
+ /* The paired query waits for the EC reply before returning its state. */
+ ret = ewmi_call(priv, 0x03, command - 1, 0, &actual, sizeof(actual));
+ if (ret) {
+ dev_warn_ratelimited(&priv->wdev->dev,
+ "Lock 03/%02x after-set query error %d\n",
+ command, ret);
+ return ret;
+ }
+ if (actual != value) {
+ dev_warn_ratelimited(&priv->wdev->dev,
+ "Lock 03/%02x requested %u, settled readback %u\n",
+ command, value, actual);
+ return -EIO;
+ }
+ priv->lock_dirty &= ~mask;
+ return count;
+}
+
+static ssize_t fn_lock_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ return ewmi_switch_show(dev, 0x03, buf);
+}
+
+static ssize_t fn_lock_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ return ewmi_switch_store(dev, 0x04, buf, count);
+}
+static DEVICE_ATTR_RW(fn_lock);
+
+static ssize_t win_lock_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ return ewmi_switch_show(dev, 0x01, buf);
+}
+
+static ssize_t win_lock_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ return ewmi_switch_store(dev, 0x02, buf, count);
+}
+static DEVICE_ATTR_RW(win_lock);
+
+static ssize_t touchpad_enabled_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return ewmi_switch_show(dev, 0x07, buf);
+}
+static DEVICE_ATTR_RO(touchpad_enabled);
+
+static struct attribute *ewmi_attrs[] = {
+ &dev_attr_fn_lock.attr,
+ &dev_attr_win_lock.attr,
+ &dev_attr_touchpad_enabled.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(ewmi);
+
+static const char * const ewmi_temp_labels[EWMI_TEMP_COUNT] = {
+ "CPU", "GT VR", "Ambient", "Charger"
+};
+
+static const char * const ewmi_fan_labels[EWMI_FAN_COUNT] = {
+ "Fan 1", "Fan 2"
+};
+
+static int ewmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *value)
+{
+ struct ewmi_method *priv = dev_get_drvdata(dev);
+ u8 temperature;
+ __le16 rpm;
+ int ret;
+
+ guard(mutex)(&priv->lock);
+ if (type == hwmon_fan && attr == hwmon_fan_input) {
+ if (channel < 0 || channel >= EWMI_FAN_COUNT)
+ return -EOPNOTSUPP;
+ ret = ewmi_call(priv, 0x02, 0x01, channel, &rpm, sizeof(rpm));
+ if (!ret)
+ *value = le16_to_cpu(rpm);
+ return ret;
+ }
+ if (type == hwmon_temp && attr == hwmon_temp_input) {
+ if (channel < 0 || channel >= EWMI_TEMP_COUNT)
+ return -EOPNOTSUPP;
+ ret = ec_read(priv->board->temp_regs[channel], &temperature);
+ if (!ret) {
+ if (temperature == 0xff)
+ return -ENODATA;
+ *value = temperature * 1000L;
+ }
+ return ret;
+ }
+ return -EOPNOTSUPP;
+}
+
+static int ewmi_hwmon_read_string(struct device *dev,
+ enum hwmon_sensor_types type, u32 attr,
+ int channel, const char **str)
+{
+ if (type == hwmon_fan && attr == hwmon_fan_label &&
+ channel >= 0 && channel < EWMI_FAN_COUNT) {
+ *str = ewmi_fan_labels[channel];
+ return 0;
+ }
+ if (type == hwmon_temp && attr == hwmon_temp_label &&
+ channel >= 0 && channel < EWMI_TEMP_COUNT) {
+ *str = ewmi_temp_labels[channel];
+ return 0;
+ }
+ return -EOPNOTSUPP;
+}
+
+static const struct hwmon_ops ewmi_hwmon_ops = {
+ .visible = 0444,
+ .read = ewmi_hwmon_read,
+ .read_string = ewmi_hwmon_read_string,
+};
+
+static const struct hwmon_channel_info * const ewmi_hwmon_info[] = {
+ HWMON_CHANNEL_INFO(fan,
+ HWMON_F_INPUT | HWMON_F_LABEL,
+ HWMON_F_INPUT | HWMON_F_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),
+ NULL
+};
+
+static const struct hwmon_chip_info ewmi_hwmon_chip = {
+ .ops = &ewmi_hwmon_ops,
+ .info = ewmi_hwmon_info,
+};
+
+static int ewmi_profile_probe(void *drvdata, unsigned long *choices)
+{
+ set_bit(PLATFORM_PROFILE_BALANCED, choices);
+ set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
+ return 0;
+}
+
+static int ewmi_profile_get(struct device *dev,
+ enum platform_profile_option *profile)
+{
+ struct ewmi_method *priv = dev_get_drvdata(dev);
+ u8 mode;
+ int ret;
+
+ guard(mutex)(&priv->lock);
+ ret = ewmi_call(priv, 0x03, 0x09, 0, &mode, sizeof(mode));
+ if (ret)
+ return ret;
+ switch (mode) {
+ case 1:
+ *profile = PLATFORM_PROFILE_BALANCED;
+ return 0;
+ case 2:
+ *profile = PLATFORM_PROFILE_PERFORMANCE;
+ return 0;
+ default:
+ return -EPROTO;
+ }
+}
+
+static int ewmi_profile_set(struct device *dev,
+ enum platform_profile_option profile)
+{
+ struct ewmi_method *priv = dev_get_drvdata(dev);
+ bool firmware_failure;
+ u8 mode, actual;
+ int attempt, ret;
+
+ switch (profile) {
+ case PLATFORM_PROFILE_BALANCED:
+ mode = 1;
+ break;
+ case PLATFORM_PROFILE_PERFORMANCE:
+ mode = 2;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ guard(mutex)(&priv->lock);
+ /* Avoid a redundant EC command when userspace reapplies this profile. */
+ ret = ewmi_call(priv, 0x03, 0x09, 0, &actual, sizeof(actual));
+ if (ret)
+ return ret;
+ if (actual == mode && !priv->profile_dirty)
+ return 0;
+
+ /*
+ * SPFM arms the EC (ZZTT/TIZZ) before Notify(IETM, 0x88) and before
+ * writing EC5F, so an EC scan during the notify can consume a stale
+ * EC5F and return RTNC=0 without updating PPMD; SPFM can also report
+ * RTNC=1 after changing mode. Reissue this absolute mode command up
+ * to three times when RTNC=1 or 03/09 still reads back the old mode.
+ */
+ priv->profile_dirty = true;
+ for (attempt = 0; attempt < EWMI_MAILBOX_RETRIES; attempt++) {
+ ret = ewmi_call_status(priv, 0x03, 0x0a, mode, NULL, 0,
+ &firmware_failure);
+ if (ret) {
+ if (!firmware_failure ||
+ attempt + 1 == EWMI_MAILBOX_RETRIES)
+ return ret;
+ msleep(priv->board->setter_settle_ms);
+ continue;
+ }
+ ret = ewmi_call(priv, 0x03, 0x09, 0, &actual, sizeof(actual));
+ if (ret)
+ return ret;
+ if (actual == mode) {
+ priv->profile_dirty = false;
+ return 0;
+ }
+ msleep(priv->board->setter_settle_ms);
+ ret = ewmi_call(priv, 0x03, 0x09, 0, &actual, sizeof(actual));
+ if (ret)
+ return ret;
+ if (actual == mode) {
+ priv->profile_dirty = false;
+ return 0;
+ }
+ if (attempt + 1 < EWMI_MAILBOX_RETRIES)
+ dev_dbg(dev,
+ "Profile 03/0a requested %u, readback %u on attempt %d/%d; retrying\n",
+ mode, actual, attempt + 1,
+ EWMI_MAILBOX_RETRIES);
+ }
+ dev_warn_ratelimited(dev,
+ "Profile 03/0a requested %u, settled readback %u\n",
+ mode, actual);
+ return -EIO;
+}
+
+static const struct platform_profile_ops ewmi_profile_ops = {
+ .probe = ewmi_profile_probe,
+ .profile_get = ewmi_profile_get,
+ .profile_set = ewmi_profile_set,
+};
+
+static void ewmi_profile_work(struct work_struct *work)
+{
+ struct ewmi_method *priv = container_of(work, struct ewmi_method,
+ profile_work);
+
+ platform_profile_notify(priv->profile);
+}
+
+static int ewmi_method_event(struct notifier_block *nb, unsigned long code,
+ void *data)
+{
+ struct ewmi_method *priv = container_of(nb, struct ewmi_method,
+ event_nb);
+ struct wmi_device *event_wdev = data;
+
+ if (event_wdev->dev.parent != priv->wdev->dev.parent)
+ return NOTIFY_DONE;
+ /* _QF6 (0x50/0x51) and _QF9 (0x20-0x22) share EC5D with WMCD queries. */
+ if (code == 0x20 || code == 0x21 || code == 0x22 ||
+ code == 0x50 || code == 0x51)
+ atomic_inc(&priv->mailbox_event_seq);
+ if (code == 0 || code == 0x41 || code == 0x42)
+ schedule_work(&priv->profile_work);
+ return NOTIFY_OK;
+}
+
+static void ewmi_method_stop(void *data)
+{
+ struct ewmi_method *priv = data;
+
+ blocking_notifier_chain_unregister(&ewmi_event_chain, &priv->event_nb);
+ cancel_work_sync(&priv->profile_work);
+}
+
+static int ewmi_method_probe(struct wmi_device *wdev, const void *context)
+{
+ static const u8 expected_version[] = { 1, 0, 1, 0 };
+ const struct ewmi_board_config *board;
+ struct ewmi_method *priv;
+ struct device *hwmon;
+ u8 version[4];
+ int ret;
+
+ board = ewmi_get_board_config();
+ if (!board)
+ return -ENODEV;
+ priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+ priv->wdev = wdev;
+ priv->board = board;
+ mutex_init(&priv->lock);
+ INIT_WORK(&priv->profile_work, ewmi_profile_work);
+ dev_set_drvdata(&wdev->dev, priv);
+
+ scoped_guard(mutex, &priv->lock)
+ ret = ewmi_call(priv, 0x01, 0x01, 0, version, sizeof(version));
+ if (ret)
+ return dev_err_probe(&wdev->dev, ret,
+ "Cannot query protocol version\n");
+ if (memcmp(version, expected_version, sizeof(version)))
+ return dev_err_probe(&wdev->dev, -ENODEV,
+ "Unsupported protocol version\n");
+
+ priv->profile = devm_platform_profile_register(&wdev->dev,
+ "emdoor-intel-wmi",
+ priv, &ewmi_profile_ops);
+ if (IS_ERR(priv->profile))
+ return dev_err_probe(&wdev->dev, PTR_ERR(priv->profile),
+ "Cannot register platform profile\n");
+
+ priv->event_nb.notifier_call = ewmi_method_event;
+ ret = blocking_notifier_chain_register(&ewmi_event_chain,
+ &priv->event_nb);
+ if (ret)
+ return ret;
+ ret = devm_add_action_or_reset(&wdev->dev, ewmi_method_stop, priv);
+ if (ret)
+ return ret;
+
+ hwmon = devm_hwmon_device_register_with_info(&wdev->dev, "emdoor_ewmi",
+ priv, &ewmi_hwmon_chip,
+ NULL);
+ if (IS_ERR(hwmon))
+ return dev_err_probe(&wdev->dev, PTR_ERR(hwmon),
+ "Cannot register hwmon\n");
+
+ return 0;
+}
+
+/* Firmware already changes these states; never request a second toggle. */
+static const struct key_entry ewmi_keymap[] = {
+ { KE_KEY, 0x30, { KEY_TOUCHPAD_OFF } },
+ { KE_KEY, 0x31, { KEY_TOUCHPAD_ON } },
+ { KE_KEY, 0xa0, { KEY_PROG1 } },
+ { KE_KEY, 0xa1, { KEY_PROG2 } },
+ { KE_KEY, 0xa2, { KEY_PROG3 } },
+ { KE_IGNORE, 0x00, { 0 } },
+ { KE_IGNORE, 0x41, { 0 } },
+ { KE_IGNORE, 0x42, { 0 } },
+ { KE_IGNORE, 0x20, { 0 } },
+ { KE_IGNORE, 0x21, { 0 } },
+ { KE_IGNORE, 0x22, { 0 } },
+ { KE_IGNORE, 0x50, { 0 } },
+ { KE_IGNORE, 0x51, { 0 } },
+ { KE_END, 0 }
+};
+
+static int ewmi_event_probe(struct wmi_device *wdev, const void *context)
+{
+ struct ewmi_event *priv;
+ int ret;
+
+ if (!ewmi_get_board_config())
+ return -ENODEV;
+ priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+ mutex_init(&priv->lock);
+ priv->input = devm_input_allocate_device(&wdev->dev);
+ if (!priv->input)
+ return -ENOMEM;
+ priv->input->name = "Emdoor EWMI hotkeys";
+ priv->input->phys = "wmi/input0";
+ priv->input->id.bustype = BUS_HOST;
+ ret = sparse_keymap_setup(priv->input, ewmi_keymap, NULL);
+ if (ret)
+ return ret;
+ dev_set_drvdata(&wdev->dev, priv);
+ return input_register_device(priv->input);
+}
+
+static void ewmi_event_notify(struct wmi_device *wdev,
+ const struct wmi_buffer *event)
+{
+ struct ewmi_event *priv = dev_get_drvdata(&wdev->dev);
+ u32 code = get_unaligned_le32(event->data);
+
+ blocking_notifier_call_chain(&ewmi_event_chain, code, wdev);
+ guard(mutex)(&priv->lock);
+ if (!sparse_keymap_report_event(priv->input, code, 1, true))
+ dev_dbg(&wdev->dev, "Unknown event 0x%x\n", code);
+}
+
+static const struct wmi_device_id ewmi_method_ids[] = {
+ { EWMI_METHOD_GUID, NULL },
+ { }
+};
+MODULE_DEVICE_TABLE(wmi, ewmi_method_ids);
+
+static const struct wmi_device_id ewmi_event_ids[] = {
+ { EWMI_EVENT_GUID, NULL },
+ { }
+};
+MODULE_DEVICE_TABLE(wmi, ewmi_event_ids);
+
+static struct wmi_driver ewmi_method_driver = {
+ .driver = {
+ .name = "emdoor-ewmi",
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ .dev_groups = ewmi_groups,
+ },
+ .id_table = ewmi_method_ids,
+ .no_singleton = true,
+ .probe = ewmi_method_probe,
+};
+
+static struct wmi_driver ewmi_event_driver = {
+ .driver = {
+ .name = "emdoor-ewmi-events",
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ },
+ .id_table = ewmi_event_ids,
+ .no_singleton = true,
+ .min_event_size = sizeof(__le32),
+ .probe = ewmi_event_probe,
+ .notify_new = ewmi_event_notify,
+};
+
+static int ewmi_autopower_get(struct ewmi_autopower *priv, bool *enabled)
+{
+ u8 raw;
+ int ret;
+
+ lockdep_assert_held(&priv->lock);
+ ret = ec_read(priv->board->autopower_reg, &raw);
+ if (ret)
+ return ret;
+ if (raw != 0 && raw != 0xa0)
+ return -EPROTO;
+ *enabled = raw == 0xa0;
+ return 0;
+}
+
+static int ewmi_autopower_set(struct ewmi_autopower *priv, bool enabled)
+{
+ __le64 argument = cpu_to_le64(enabled);
+ struct wmi_buffer in = { .length = sizeof(argument), .data = &argument };
+ struct wmi_buffer out = { };
+ bool actual;
+ u8 status;
+ int ret;
+
+ lockdep_assert_held(&priv->lock);
+ ret = ewmi_autopower_get(priv, &actual);
+ if (ret || (actual == enabled && !priv->dirty))
+ return ret;
+ priv->dirty = true;
+ ret = wmidev_invoke_method(priv->wdev, 0, 1, &in, &out, 8);
+ if (ret) {
+ dev_warn_ratelimited(&priv->wdev->dev,
+ "WMCC transport error %d\n", ret);
+ return ret;
+ }
+ status = ((u8 *)out.data)[0];
+ ret = status ? -EIO : 0;
+ kfree(out.data);
+ msleep(priv->board->setter_settle_ms);
+ if (ret) {
+ dev_warn_ratelimited(&priv->wdev->dev,
+ "WMCC firmware status %u\n", status);
+ return ret;
+ }
+ ret = ewmi_autopower_get(priv, &actual);
+ if (ret) {
+ dev_warn_ratelimited(&priv->wdev->dev,
+ "WMCC readback error %d\n", ret);
+ return ret;
+ }
+ if (actual != enabled) {
+ dev_warn_ratelimited(&priv->wdev->dev,
+ "WMCC requested %u, readback %u\n",
+ enabled, actual);
+ return -EIO;
+ }
+ priv->dirty = false;
+ return 0;
+}
+
+static ssize_t auto_power_on_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ewmi_autopower *priv = dev_get_drvdata(dev);
+ bool enabled;
+ int ret;
+
+ guard(mutex)(&priv->lock);
+ ret = ewmi_autopower_get(priv, &enabled);
+ if (ret)
+ return ret;
+ return sysfs_emit(buf, "%u\n", enabled);
+}
+
+static ssize_t auto_power_on_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct ewmi_autopower *priv = dev_get_drvdata(dev);
+ bool enabled;
+ int ret;
+
+ ret = kstrtobool(buf, &enabled);
+ if (ret)
+ return ret;
+ guard(mutex)(&priv->lock);
+ ret = ewmi_autopower_set(priv, enabled);
+ return ret ? ret : count;
+}
+static DEVICE_ATTR_RW(auto_power_on);
+
+static struct attribute *ewmi_autopower_attrs[] = {
+ &dev_attr_auto_power_on.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(ewmi_autopower);
+
+static int ewmi_autopower_probe(struct wmi_device *wdev, const void *context)
+{
+ const struct ewmi_board_config *board;
+ struct ewmi_autopower *priv;
+
+ board = ewmi_get_board_config();
+ if (!board)
+ return -ENODEV;
+ priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+ priv->wdev = wdev;
+ priv->board = board;
+ mutex_init(&priv->lock);
+ dev_set_drvdata(&wdev->dev, priv);
+ return 0;
+}
+
+static const struct wmi_device_id ewmi_autopower_ids[] = {
+ { EWMI_AUTOPOWER_GUID, NULL },
+ { }
+};
+MODULE_DEVICE_TABLE(wmi, ewmi_autopower_ids);
+
+static struct wmi_driver ewmi_autopower_driver = {
+ .driver = {
+ .name = "emdoor-ewmi-autopower",
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ .dev_groups = ewmi_autopower_groups,
+ },
+ .id_table = ewmi_autopower_ids,
+ .no_singleton = true,
+ .probe = ewmi_autopower_probe,
+};
+
+static int __init ewmi_init(void)
+{
+ int ret;
+
+ ret = wmi_driver_register(&ewmi_method_driver);
+ if (ret)
+ return ret;
+ ret = wmi_driver_register(&ewmi_event_driver);
+ if (ret) {
+ wmi_driver_unregister(&ewmi_method_driver);
+ return ret;
+ }
+ ret = wmi_driver_register(&ewmi_autopower_driver);
+ if (ret) {
+ wmi_driver_unregister(&ewmi_event_driver);
+ wmi_driver_unregister(&ewmi_method_driver);
+ }
+ return ret;
+}
+
+static void __exit ewmi_exit(void)
+{
+ wmi_driver_unregister(&ewmi_autopower_driver);
+ wmi_driver_unregister(&ewmi_event_driver);
+ wmi_driver_unregister(&ewmi_method_driver);
+}
+
+module_init(ewmi_init);
+module_exit(ewmi_exit);
+
+MODULE_AUTHOR("Ziqiang Xia <xiazq123666@gmail.com>");
+MODULE_DESCRIPTION("Emdoor Intel EWMI platform driver");
+MODULE_LICENSE("GPL");
--
2.56.1
next prev parent reply other threads:[~2026-11-13 18:01 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-13 18:01 [PATCH RFC 0/2] " Ziqiang Xia via B4 Relay
2026-11-13 18:01 ` [PATCH RFC 1/2] Documentation: ABI: testing: Add emdoor-intel-wmi sysfs ABI Ziqiang Xia via B4 Relay
2026-11-13 18:01 ` Ziqiang Xia via B4 Relay [this message]
2026-11-13 20:37 ` [PATCH RFC 0/2] platform/x86: Add Emdoor Intel EWMI platform driver Armin Wolf
2026-11-14 3:30 ` Ziqiang Xia
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=20261113-emdoor-intel-wmi-rfc-v1-2-d77ecf23693b@gmail.com \
--to=devnull+xiazq123666.gmail.com@kernel.org \
--cc=W_Armin@gmx.de \
--cc=hansg@kernel.org \
--cc=ilpo.jarvinen@linux.intel.com \
--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=xiazq123666@gmail.com \
/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®