* [PATCH v5] platform/x86/lenovo: Add Yoga Book 9 keyboard dock detection driver [not found] <20260521144034.282419-1-carvsdriver@gmail.com> @ 2026-05-27 12:27 ` Dave Carey 2026-06-08 9:13 ` johannes.goede 0 siblings, 1 reply; 20+ messages in thread From: Dave Carey @ 2026-05-27 12:27 UTC (permalink / raw) To: platform-driver-x86 Cc: hdegoede, ilpo.jarvinen, armin.wolf, linux-kernel, Dave Carey The Lenovo Yoga Book 9 14IAH10 ships with a detachable Bluetooth keyboard that magnetically attaches to the bottom (secondary) screen in one of two positions. The Embedded Controller tracks the attachment state in a 2-bit field called BKBD and signals changes via WMI event GUID 806BD2A2-177B-481D-BFB5-3BA0BB4A2285 (notify ID 0xEB on the WM10 ACPI device, _UID "GMZN"). The device contains embedded BMOF data (WQDD, 20705 bytes) documenting both WMI interfaces used by this driver: LENOVO_BTKBD_EVENT (event GUID): WmiDataId(1) uint32 Status. The ACPI _WED(0xEB) method returns EC.BKBD directly as an integer, so the notify callback receives BKBD without a separate query. LENOVO_FEATURE_STATUS_DATA (block GUID, WQAF method): returns an 8-byte buffer {uint32 IDs=0x00060000, uint32 Status=BKBD}. Used for the initial state read on probe and after resume. BKBD encoding: 0 = keyboard detached 1 = keyboard docked on top half of bottom screen 2 = keyboard docked on bottom half of bottom screen 3 = reserved (not observed in practice) This driver: - Registers two WMI drivers: one on the event GUID (LENOVO_BTKBD_EVENT) and one on the block GUID (LENOVO_FEATURE_STATUS_DATA). - On probe, reads initial BKBD state via wmidev_block_query() on the block device; whichever driver probes last triggers the initial read. - On WMI notification, reads BKBD directly from the event data integer (ACPI _WED(0xEB) returns EC.BKBD) without a redundant WQAF call. - Reports SW_TABLET_MODE=1 when detached, SW_TABLET_MODE=0 when docked in either position (a physical keyboard is present in both cases). - Exposes the raw BKBD value via read-only sysfs attribute "keyboard_position". - Re-reads BKBD state on resume from suspend or hibernation. Tested on: Lenovo Yoga Book 9 14IAH10 (model 83KJ), kernel 7.0. Signed-off-by: Dave Carey <carvsdriver@gmail.com> --- v5: - Rewrote as two WMI drivers (event + block) to avoid the deprecated wmi_query_block() API: event driver owns the GUID that delivers notifications, block driver owns the GUID used for querying state. Either can probe first; both probe callbacks check whether the other has already probed and fire the initial read when both are ready. - Use wmidev_block_query() on the block wmi_device directly (replaces the deprecated wmi_query_block()). - In the notify callback, read BKBD directly from the event data integer passed by the WMI core (_WED(0xEB) returns EC.BKBD; confirmed by decoding the embedded BMOF from WQDD). Eliminates the redundant WQAF call that v4 made on every notification. - Documented the two BMOF classes (LENOVO_BTKBD_EVENT and LENOVO_FEATURE_STATUS_DATA) with field layouts in the commit message and file header. - sysfs keyboard_position: output bare integer, drop the parenthetical position-name suffix that a couple of reviewers found non-standard. - Set .no_singleton = true on both WMI drivers (was missing from block driver in v4). - Add PM resume callback (yb9_kbdock_resume) to re-read BKBD state after suspend/hibernation. v4: - Dropped module_wmi_driver(); registered two WMI drivers manually to allow sharing state. module_init/exit pair registers/unregisters both. - Added ATTRIBUTE_GROUPS() and .dev_groups for the sysfs attribute. - sysfs show: performed a live query instead of returning a cached value. v3: - Switched to devm_input_allocate_device(). - Added DMI guard (dmi_check_system) to reject non-YB9 machines. - Removed redundant MODULE_DEVICE_TABLE on block driver. v2: - Added .no_singleton = true on the event WMI driver. - Added PM suspend/resume skeleton. - Fixed MODULE_LICENSE to "GPL" (was "GPL v2"). .../testing/sysfs-driver-lenovo-yb9-kbdock | 19 + MAINTAINERS | 7 + drivers/platform/x86/lenovo/Kconfig | 14 + drivers/platform/x86/lenovo/Makefile | 1 + drivers/platform/x86/lenovo/yb9-kbdock.c | 324 ++++++++++++++++++ 5 files changed, 365 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock create mode 100644 drivers/platform/x86/lenovo/yb9-kbdock.c diff --git a/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock b/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock new file mode 100644 index 0000000..04e5293 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock @@ -0,0 +1,19 @@ +What: /sys/bus/wmi/drivers/lenovo-yb9-kbdock/<guid>/keyboard_position +Date: April 2026 +KernelVersion: 6.10 +Contact: Dave Carey <carvsdriver@gmail.com> +Description: + Read-only attribute reporting the current keyboard dock position + as reported by the Embedded Controller on the Lenovo Yoga Book 9 + 14IAH10. + + Possible values: + + == ============================================================= + 0 keyboard is not docked to any screen (detached) + 1 keyboard docked on the top half of the bottom screen + 2 keyboard docked on the bottom half of the bottom screen + == ============================================================= + + SW_TABLET_MODE input events are also emitted: 0 when the keyboard + is docked (either position), 1 when detached. diff --git a/MAINTAINERS b/MAINTAINERS index d1cc0e1..00e8275 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14479,6 +14479,13 @@ L: platform-driver-x86@vger.kernel.org S: Maintained F: drivers/platform/x86/lenovo/wmi-hotkey-utilities.c +LENOVO YOGA BOOK 9 KEYBOARD DOCK DRIVER +M: Dave Carey <carvsdriver@gmail.com> +L: platform-driver-x86@vger.kernel.org +S: Maintained +F: Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock +F: drivers/platform/x86/lenovo/yb9-kbdock.c + LETSKETCH HID TABLET DRIVER M: Hans de Goede <hansg@kernel.org> L: linux-input@vger.kernel.org diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/lenovo/Kconfig index 9c48487..938b361 100644 --- a/drivers/platform/x86/lenovo/Kconfig +++ b/drivers/platform/x86/lenovo/Kconfig @@ -43,6 +43,20 @@ config LENOVO_WMI_CAMERA To compile this driver as a module, choose M here: the module will be called lenovo-wmi-camera. +config LENOVO_YB9_KBDOCK + tristate "Lenovo Yoga Book 9 keyboard dock detection" + depends on ACPI_WMI + depends on DMI + depends on INPUT + help + Say Y here to enable keyboard dock detection on the Lenovo Yoga Book 9 + 14IAH10. The detachable Bluetooth keyboard magnetically attaches to + either screen; this driver reports SW_TABLET_MODE input events based + on the attachment state and exposes the raw position in sysfs. + + To compile this driver as a module, choose M here: the module will be + called lenovo-yb9-kbdock. + config LENOVO_YMC tristate "Lenovo Yoga Tablet Mode Control" depends on ACPI_WMI diff --git a/drivers/platform/x86/lenovo/Makefile b/drivers/platform/x86/lenovo/Makefile index 7b2128e..2842d7d 100644 --- a/drivers/platform/x86/lenovo/Makefile +++ b/drivers/platform/x86/lenovo/Makefile @@ -8,6 +8,7 @@ obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o lenovo-target-$(CONFIG_LENOVO_WMI_HOTKEY_UTILITIES) += wmi-hotkey-utilities.o +lenovo-target-$(CONFIG_LENOVO_YB9_KBDOCK) += yb9-kbdock.o lenovo-target-$(CONFIG_LENOVO_YMC) += ymc.o lenovo-target-$(CONFIG_YOGABOOK) += yogabook.o lenovo-target-$(CONFIG_YT2_1380) += yoga-tab2-pro-1380-fastcharger.o diff --git a/drivers/platform/x86/lenovo/yb9-kbdock.c b/drivers/platform/x86/lenovo/yb9-kbdock.c new file mode 100644 index 0000000..28a3ec7 --- /dev/null +++ b/drivers/platform/x86/lenovo/yb9-kbdock.c @@ -0,0 +1,324 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Lenovo Yoga Book 9 keyboard-dock detection + * + * The Yoga Book 9 ships with a detachable Bluetooth keyboard that magnetically + * attaches to the bottom screen in one of two positions. The EC tracks + * attachment state in a 2-bit field called BKBD and signals changes via WMI + * event 0xEB on the WM10 ACPI device (_UID "GMZN"). + * + * BKBD values: + * 0 = keyboard detached + * 1 = keyboard docked on the top half of the bottom screen + * 2 = keyboard docked on the bottom half of the bottom screen + * 3 = reserved / not observed + * + * Two WMI interfaces are used (documented in embedded BMOF, WQDD, 20705 bytes): + * + * LENOVO_BTKBD_EVENT (event GUID, 806BD2A2-...) + * WmiDataId(1) uint32 Status — _WED(0xEB) returns EC.BKBD directly. + * The notify callback receives BKBD as an integer; no separate query needed. + * + * LENOVO_FEATURE_STATUS_DATA (block GUID, E7F300FA-...) + * WmiDataId(1) uint32 IDs = 0x00060000 (feature selector) + * WmiDataId(2) uint32 Status = BKBD value + * Used on probe and resume to read initial state. + * + * SW_TABLET_MODE=1 is reported when the keyboard is detached; + * SW_TABLET_MODE=0 when docked in either position (keyboard present). + * The raw BKBD value is exposed via the sysfs attribute "keyboard_position". + * + * Copyright (C) 2026 Dave Carey <carvsdriver@gmail.com> + */ + +#include <linux/acpi.h> +#include <linux/dmi.h> +#include <linux/input.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/pm.h> +#include <linux/wmi.h> + +#define YB9_KBDOCK_EVENT_GUID "806BD2A2-177B-481D-BFB5-3BA0BB4A2285" +#define YB9_KBDOCK_QUERY_GUID "E7F300FA-21CD-4003-ADAC-2696135982E6" + +/* BKBD encoding */ +#define BKBD_DETACHED 0 +#define BKBD_TOP_HALF 1 +#define BKBD_BOTTOM_HALF 2 + +static const struct dmi_system_id yb9_kbdock_dmi_table[] = { + { + /* Lenovo Yoga Book 9 14IAH10 */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "83KJ"), + }, + }, + { } +}; + +/* + * Shared state between the event and block WMI drivers. Protected by lock. + * The lock may be held across wmidev_query_block() calls (which can sleep); + * block_remove() acquires the lock before clearing block_wdev, ensuring + * block_wdev remains valid for the duration of any in-progress query. + */ +static struct { + struct mutex lock; + struct input_dev *input_dev; /* set by event probe */ + struct wmi_device *block_wdev; /* set by block probe */ +} yb9; + +/* + * Read BKBD from the block device via wmidev_block_query(). + * Returns 0-3 on success, -errno on failure. Caller must hold yb9.lock. + */ +static int yb9_kbdock_query_locked(struct device *log_dev) +{ + union acpi_object *obj; + u32 bkbd; + + if (!yb9.block_wdev) + return -ENODEV; + + obj = wmidev_block_query(yb9.block_wdev, 0); + if (!obj) { + dev_warn(log_dev, "WQAF returned NULL\n"); + return -EIO; + } + + /* + * LENOVO_FEATURE_STATUS_DATA: 8-byte buffer {IDs=0x00060000, Status=BKBD}. + */ + if (obj->type == ACPI_TYPE_BUFFER && obj->buffer.length >= 8) { + memcpy(&bkbd, obj->buffer.pointer + 4, sizeof(bkbd)); + bkbd &= 0x3; + } else if (obj->type == ACPI_TYPE_INTEGER) { + bkbd = obj->integer.value & 0x3; + } else { + dev_warn(log_dev, "WQAF: unexpected type %d len %u\n", + obj->type, + obj->type == ACPI_TYPE_BUFFER ? obj->buffer.length : 0); + kfree(obj); + return -EIO; + } + + kfree(obj); + return (int)bkbd; +} + +/* Report SW_TABLET_MODE from BKBD. Caller must hold yb9.lock. */ +static void yb9_kbdock_report_locked(int bkbd, struct device *log_dev) +{ + int tablet = (bkbd == BKBD_DETACHED) ? 1 : 0; + + input_report_switch(yb9.input_dev, SW_TABLET_MODE, tablet); + input_sync(yb9.input_dev); + dev_dbg(log_dev, "BKBD=%d SW_TABLET_MODE=%d\n", bkbd, tablet); +} + +/* ------------------------------------------------------------------ + * sysfs + * ------------------------------------------------------------------ */ + +static ssize_t keyboard_position_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int bkbd; + + mutex_lock(&yb9.lock); + bkbd = yb9_kbdock_query_locked(dev); + mutex_unlock(&yb9.lock); + + if (bkbd < 0) + return bkbd; + return sysfs_emit(buf, "%u\n", (unsigned int)bkbd); +} +static DEVICE_ATTR_RO(keyboard_position); + +static struct attribute *yb9_kbdock_attrs[] = { + &dev_attr_keyboard_position.attr, + NULL, +}; +ATTRIBUTE_GROUPS(yb9_kbdock); + +/* ------------------------------------------------------------------ + * Event WMI driver — LENOVO_BTKBD_EVENT + * ------------------------------------------------------------------ */ + +static void yb9_kbdock_notify(struct wmi_device *wdev, union acpi_object *data) +{ + u32 bkbd; + + /* + * _WED(0xEB) returns EC.BKBD directly as an integer + * (LENOVO_BTKBD_EVENT WmiDataId(1) uint32 Status). + */ + if (!data || data->type != ACPI_TYPE_INTEGER) { + dev_warn(&wdev->dev, "unexpected event data type %d\n", + data ? data->type : -1); + return; + } + bkbd = data->integer.value & 0x3; + + mutex_lock(&yb9.lock); + if (yb9.input_dev) + yb9_kbdock_report_locked(bkbd, &wdev->dev); + mutex_unlock(&yb9.lock); +} + +static int yb9_kbdock_event_probe(struct wmi_device *wdev, const void *ctx) +{ + struct input_dev *input_dev; + int bkbd, err; + + if (!dmi_check_system(yb9_kbdock_dmi_table)) + return -ENODEV; + + input_dev = devm_input_allocate_device(&wdev->dev); + if (!input_dev) + return -ENOMEM; + + input_dev->name = "Lenovo Yoga Book 9 keyboard dock switch"; + input_dev->phys = YB9_KBDOCK_EVENT_GUID "/input0"; + input_dev->id.bustype = BUS_HOST; + input_set_capability(input_dev, EV_SW, SW_TABLET_MODE); + + err = input_register_device(input_dev); + if (err) + return err; + + mutex_lock(&yb9.lock); + yb9.input_dev = input_dev; + /* Read initial state if the block device has already probed. */ + if (yb9.block_wdev) { + bkbd = yb9_kbdock_query_locked(&wdev->dev); + if (bkbd >= 0) + yb9_kbdock_report_locked(bkbd, &wdev->dev); + } + mutex_unlock(&yb9.lock); + + return 0; +} + +static void yb9_kbdock_event_remove(struct wmi_device *wdev) +{ + mutex_lock(&yb9.lock); + yb9.input_dev = NULL; + mutex_unlock(&yb9.lock); +} + +static int yb9_kbdock_resume(struct device *dev) +{ + int bkbd; + + mutex_lock(&yb9.lock); + if (yb9.input_dev && yb9.block_wdev) { + bkbd = yb9_kbdock_query_locked(dev); + if (bkbd >= 0) + yb9_kbdock_report_locked(bkbd, dev); + } + mutex_unlock(&yb9.lock); + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(yb9_kbdock_pm_ops, NULL, yb9_kbdock_resume); + +static const struct wmi_device_id yb9_kbdock_event_id_table[] = { + { .guid_string = YB9_KBDOCK_EVENT_GUID }, + { } +}; +MODULE_DEVICE_TABLE(wmi, yb9_kbdock_event_id_table); + +static struct wmi_driver yb9_kbdock_event_driver = { + .driver = { + .name = "lenovo-yb9-kbdock", + .dev_groups = yb9_kbdock_groups, + .pm = pm_sleep_ptr(&yb9_kbdock_pm_ops), + }, + .id_table = yb9_kbdock_event_id_table, + .no_singleton = true, + .probe = yb9_kbdock_event_probe, + .remove = yb9_kbdock_event_remove, + .notify = yb9_kbdock_notify, +}; + +/* ------------------------------------------------------------------ + * Block WMI driver — LENOVO_FEATURE_STATUS_DATA + * ------------------------------------------------------------------ */ + +static int yb9_kbdock_block_probe(struct wmi_device *wdev, const void *ctx) +{ + int bkbd; + + if (!dmi_check_system(yb9_kbdock_dmi_table)) + return -ENODEV; + + mutex_lock(&yb9.lock); + yb9.block_wdev = wdev; + /* Read initial state if the event device has already probed. */ + if (yb9.input_dev) { + bkbd = yb9_kbdock_query_locked(&wdev->dev); + if (bkbd >= 0) + yb9_kbdock_report_locked(bkbd, &wdev->dev); + } + mutex_unlock(&yb9.lock); + return 0; +} + +static void yb9_kbdock_block_remove(struct wmi_device *wdev) +{ + mutex_lock(&yb9.lock); + yb9.block_wdev = NULL; + mutex_unlock(&yb9.lock); +} + +static const struct wmi_device_id yb9_kbdock_block_id_table[] = { + { .guid_string = YB9_KBDOCK_QUERY_GUID }, + { } +}; + +static struct wmi_driver yb9_kbdock_block_driver = { + .driver = { + .name = "lenovo-yb9-kbdock-block", + }, + .id_table = yb9_kbdock_block_id_table, + .no_singleton = true, + .probe = yb9_kbdock_block_probe, + .remove = yb9_kbdock_block_remove, +}; + +/* ------------------------------------------------------------------ + * Module init / exit + * ------------------------------------------------------------------ */ + +static int __init yb9_kbdock_init(void) +{ + int ret; + + mutex_init(&yb9.lock); + + ret = wmi_driver_register(&yb9_kbdock_event_driver); + if (ret) + return ret; + + ret = wmi_driver_register(&yb9_kbdock_block_driver); + if (ret) { + wmi_driver_unregister(&yb9_kbdock_event_driver); + return ret; + } + return 0; +} +module_init(yb9_kbdock_init); + +static void __exit yb9_kbdock_exit(void) +{ + wmi_driver_unregister(&yb9_kbdock_block_driver); + wmi_driver_unregister(&yb9_kbdock_event_driver); +} +module_exit(yb9_kbdock_exit); + +MODULE_AUTHOR("Dave Carey <carvsdriver@gmail.com>"); +MODULE_DESCRIPTION("Lenovo Yoga Book 9 keyboard dock detection"); +MODULE_LICENSE("GPL"); -- 2.54.0 ^ permalink raw reply related [flat|nested] 20+ messages in thread
* Re: [PATCH v5] platform/x86/lenovo: Add Yoga Book 9 keyboard dock detection driver 2026-05-27 12:27 ` [PATCH v5] platform/x86/lenovo: Add Yoga Book 9 keyboard dock detection driver Dave Carey @ 2026-06-08 9:13 ` johannes.goede 2026-06-08 12:52 ` Dave Carey 0 siblings, 1 reply; 20+ messages in thread From: johannes.goede @ 2026-06-08 9:13 UTC (permalink / raw) To: Dave Carey, platform-driver-x86, Pit Henrich, Mark Pearson Cc: hdegoede, ilpo.jarvinen, armin.wolf, linux-kernel On 27-May-26 2:27 PM, Dave Carey wrote: > The Lenovo Yoga Book 9 14IAH10 ships with a detachable Bluetooth keyboard > that magnetically attaches to the bottom (secondary) screen in one of two > positions. The Embedded Controller tracks the attachment state in a 2-bit > field called BKBD and signals changes via WMI event GUID > 806BD2A2-177B-481D-BFB5-3BA0BB4A2285 (notify ID 0xEB on the WM10 ACPI > device, _UID "GMZN"). > > The device contains embedded BMOF data (WQDD, 20705 bytes) documenting > both WMI interfaces used by this driver: > > LENOVO_BTKBD_EVENT (event GUID): WmiDataId(1) uint32 Status. > The ACPI _WED(0xEB) method returns EC.BKBD directly as an integer, > so the notify callback receives BKBD without a separate query. > > LENOVO_FEATURE_STATUS_DATA (block GUID, WQAF method): returns an > 8-byte buffer {uint32 IDs=0x00060000, uint32 Status=BKBD}. > Used for the initial state read on probe and after resume. > > BKBD encoding: > 0 = keyboard detached > 1 = keyboard docked on top half of bottom screen > 2 = keyboard docked on bottom half of bottom screen > 3 = reserved (not observed in practice) > > This driver: > - Registers two WMI drivers: one on the event GUID (LENOVO_BTKBD_EVENT) > and one on the block GUID (LENOVO_FEATURE_STATUS_DATA). > - On probe, reads initial BKBD state via wmidev_block_query() on the > block device; whichever driver probes last triggers the initial read. > - On WMI notification, reads BKBD directly from the event data integer > (ACPI _WED(0xEB) returns EC.BKBD) without a redundant WQAF call. > - Reports SW_TABLET_MODE=1 when detached, SW_TABLET_MODE=0 when docked > in either position (a physical keyboard is present in both cases). > - Exposes the raw BKBD value via read-only sysfs attribute > "keyboard_position". > - Re-reads BKBD state on resume from suspend or hibernation. > > Tested on: Lenovo Yoga Book 9 14IAH10 (model 83KJ), kernel 7.0. > > Signed-off-by: Dave Carey <carvsdriver@gmail.com> Thank you for your patch. It seems that a second patch adding the same functionality by extending the thinkpad_acpi driver has been developed in parallel : https://patchwork.kernel.org/project/platform-driver-x86/patch/20260419102724.91451-1-pithenrich2d@gmail.com/ +Cc Pit Henrich (author of that patch) and Mark Pearson (Lenovo) Mark do you know what Windows is using to access this info, is the Windows sw using WMI like this driver or making direct ACPI calls ? (or maybe different pieces of Windows use different paths?) As I wrote in reply to the thinkpad_acpi patch: Since doing this in thinkpad_acpi requires adding a DMI match and directly calling an APCI method which name may change I believe that the WMI approach may be better. There we can rely on only the Fold exposing the WMI GUID for this functionality. Dave, I see that your "[PATCH v5] platform/x86/lenovo: Add Yoga Book 9 keyboard dock detection driver" patch also uses a DMI match, is that necessary ? If we need the DMI match because the GUID is not unique enough, then we might just as well add this functionality here as this patch is doing ... I do wonder since this seems to emit TP_HKEY_EV_TABLET_CHANGED thinkpad_acpi events if the existing thinkpad_acpi support does not already provide working SW_TABLET_MODE input ? If it does then also having the WMI driver emit SW_TABLET_MODE events seems to be undesirable duplicate functionality. Regards, Hans > --- > v5: > - Rewrote as two WMI drivers (event + block) to avoid the deprecated > wmi_query_block() API: event driver owns the GUID that delivers > notifications, block driver owns the GUID used for querying state. > Either can probe first; both probe callbacks check whether the other > has already probed and fire the initial read when both are ready. > - Use wmidev_block_query() on the block wmi_device directly (replaces > the deprecated wmi_query_block()). > - In the notify callback, read BKBD directly from the event data integer > passed by the WMI core (_WED(0xEB) returns EC.BKBD; confirmed by > decoding the embedded BMOF from WQDD). Eliminates the redundant WQAF > call that v4 made on every notification. > - Documented the two BMOF classes (LENOVO_BTKBD_EVENT and > LENOVO_FEATURE_STATUS_DATA) with field layouts in the commit message > and file header. > - sysfs keyboard_position: output bare integer, drop the parenthetical > position-name suffix that a couple of reviewers found non-standard. > - Set .no_singleton = true on both WMI drivers (was missing from block > driver in v4). > - Add PM resume callback (yb9_kbdock_resume) to re-read BKBD state > after suspend/hibernation. > > v4: > - Dropped module_wmi_driver(); registered two WMI drivers manually to > allow sharing state. module_init/exit pair registers/unregisters > both. > - Added ATTRIBUTE_GROUPS() and .dev_groups for the sysfs attribute. > - sysfs show: performed a live query instead of returning a cached value. > > v3: > - Switched to devm_input_allocate_device(). > - Added DMI guard (dmi_check_system) to reject non-YB9 machines. > - Removed redundant MODULE_DEVICE_TABLE on block driver. > > v2: > - Added .no_singleton = true on the event WMI driver. > - Added PM suspend/resume skeleton. > - Fixed MODULE_LICENSE to "GPL" (was "GPL v2"). > > .../testing/sysfs-driver-lenovo-yb9-kbdock | 19 + > MAINTAINERS | 7 + > drivers/platform/x86/lenovo/Kconfig | 14 + > drivers/platform/x86/lenovo/Makefile | 1 + > drivers/platform/x86/lenovo/yb9-kbdock.c | 324 ++++++++++++++++++ > 5 files changed, 365 insertions(+) > create mode 100644 Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock > create mode 100644 drivers/platform/x86/lenovo/yb9-kbdock.c > > diff --git a/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock b/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock > new file mode 100644 > index 0000000..04e5293 > --- /dev/null > +++ b/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock > @@ -0,0 +1,19 @@ > +What: /sys/bus/wmi/drivers/lenovo-yb9-kbdock/<guid>/keyboard_position > +Date: April 2026 > +KernelVersion: 6.10 > +Contact: Dave Carey <carvsdriver@gmail.com> > +Description: > + Read-only attribute reporting the current keyboard dock position > + as reported by the Embedded Controller on the Lenovo Yoga Book 9 > + 14IAH10. > + > + Possible values: > + > + == ============================================================= > + 0 keyboard is not docked to any screen (detached) > + 1 keyboard docked on the top half of the bottom screen > + 2 keyboard docked on the bottom half of the bottom screen > + == ============================================================= > + > + SW_TABLET_MODE input events are also emitted: 0 when the keyboard > + is docked (either position), 1 when detached. > diff --git a/MAINTAINERS b/MAINTAINERS > index d1cc0e1..00e8275 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -14479,6 +14479,13 @@ L: platform-driver-x86@vger.kernel.org > S: Maintained > F: drivers/platform/x86/lenovo/wmi-hotkey-utilities.c > > +LENOVO YOGA BOOK 9 KEYBOARD DOCK DRIVER > +M: Dave Carey <carvsdriver@gmail.com> > +L: platform-driver-x86@vger.kernel.org > +S: Maintained > +F: Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock > +F: drivers/platform/x86/lenovo/yb9-kbdock.c > + > LETSKETCH HID TABLET DRIVER > M: Hans de Goede <hansg@kernel.org> > L: linux-input@vger.kernel.org > diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/lenovo/Kconfig > index 9c48487..938b361 100644 > --- a/drivers/platform/x86/lenovo/Kconfig > +++ b/drivers/platform/x86/lenovo/Kconfig > @@ -43,6 +43,20 @@ config LENOVO_WMI_CAMERA > To compile this driver as a module, choose M here: the module > will be called lenovo-wmi-camera. > > +config LENOVO_YB9_KBDOCK > + tristate "Lenovo Yoga Book 9 keyboard dock detection" > + depends on ACPI_WMI > + depends on DMI > + depends on INPUT > + help > + Say Y here to enable keyboard dock detection on the Lenovo Yoga Book 9 > + 14IAH10. The detachable Bluetooth keyboard magnetically attaches to > + either screen; this driver reports SW_TABLET_MODE input events based > + on the attachment state and exposes the raw position in sysfs. > + > + To compile this driver as a module, choose M here: the module will be > + called lenovo-yb9-kbdock. > + > config LENOVO_YMC > tristate "Lenovo Yoga Tablet Mode Control" > depends on ACPI_WMI > diff --git a/drivers/platform/x86/lenovo/Makefile b/drivers/platform/x86/lenovo/Makefile > index 7b2128e..2842d7d 100644 > --- a/drivers/platform/x86/lenovo/Makefile > +++ b/drivers/platform/x86/lenovo/Makefile > @@ -8,6 +8,7 @@ obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o > obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o > > lenovo-target-$(CONFIG_LENOVO_WMI_HOTKEY_UTILITIES) += wmi-hotkey-utilities.o > +lenovo-target-$(CONFIG_LENOVO_YB9_KBDOCK) += yb9-kbdock.o > lenovo-target-$(CONFIG_LENOVO_YMC) += ymc.o > lenovo-target-$(CONFIG_YOGABOOK) += yogabook.o > lenovo-target-$(CONFIG_YT2_1380) += yoga-tab2-pro-1380-fastcharger.o > diff --git a/drivers/platform/x86/lenovo/yb9-kbdock.c b/drivers/platform/x86/lenovo/yb9-kbdock.c > new file mode 100644 > index 0000000..28a3ec7 > --- /dev/null > +++ b/drivers/platform/x86/lenovo/yb9-kbdock.c > @@ -0,0 +1,324 @@ > +// SPDX-License-Identifier: GPL-2.0-or-later > +/* > + * Lenovo Yoga Book 9 keyboard-dock detection > + * > + * The Yoga Book 9 ships with a detachable Bluetooth keyboard that magnetically > + * attaches to the bottom screen in one of two positions. The EC tracks > + * attachment state in a 2-bit field called BKBD and signals changes via WMI > + * event 0xEB on the WM10 ACPI device (_UID "GMZN"). > + * > + * BKBD values: > + * 0 = keyboard detached > + * 1 = keyboard docked on the top half of the bottom screen > + * 2 = keyboard docked on the bottom half of the bottom screen > + * 3 = reserved / not observed > + * > + * Two WMI interfaces are used (documented in embedded BMOF, WQDD, 20705 bytes): > + * > + * LENOVO_BTKBD_EVENT (event GUID, 806BD2A2-...) > + * WmiDataId(1) uint32 Status — _WED(0xEB) returns EC.BKBD directly. > + * The notify callback receives BKBD as an integer; no separate query needed. > + * > + * LENOVO_FEATURE_STATUS_DATA (block GUID, E7F300FA-...) > + * WmiDataId(1) uint32 IDs = 0x00060000 (feature selector) > + * WmiDataId(2) uint32 Status = BKBD value > + * Used on probe and resume to read initial state. > + * > + * SW_TABLET_MODE=1 is reported when the keyboard is detached; > + * SW_TABLET_MODE=0 when docked in either position (keyboard present). > + * The raw BKBD value is exposed via the sysfs attribute "keyboard_position". > + * > + * Copyright (C) 2026 Dave Carey <carvsdriver@gmail.com> > + */ > + > +#include <linux/acpi.h> > +#include <linux/dmi.h> > +#include <linux/input.h> > +#include <linux/module.h> > +#include <linux/mutex.h> > +#include <linux/pm.h> > +#include <linux/wmi.h> > + > +#define YB9_KBDOCK_EVENT_GUID "806BD2A2-177B-481D-BFB5-3BA0BB4A2285" > +#define YB9_KBDOCK_QUERY_GUID "E7F300FA-21CD-4003-ADAC-2696135982E6" > + > +/* BKBD encoding */ > +#define BKBD_DETACHED 0 > +#define BKBD_TOP_HALF 1 > +#define BKBD_BOTTOM_HALF 2 > + > +static const struct dmi_system_id yb9_kbdock_dmi_table[] = { > + { > + /* Lenovo Yoga Book 9 14IAH10 */ > + .matches = { > + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), > + DMI_MATCH(DMI_PRODUCT_NAME, "83KJ"), > + }, > + }, > + { } > +}; > + > +/* > + * Shared state between the event and block WMI drivers. Protected by lock. > + * The lock may be held across wmidev_query_block() calls (which can sleep); > + * block_remove() acquires the lock before clearing block_wdev, ensuring > + * block_wdev remains valid for the duration of any in-progress query. > + */ > +static struct { > + struct mutex lock; > + struct input_dev *input_dev; /* set by event probe */ > + struct wmi_device *block_wdev; /* set by block probe */ > +} yb9; > + > +/* > + * Read BKBD from the block device via wmidev_block_query(). > + * Returns 0-3 on success, -errno on failure. Caller must hold yb9.lock. > + */ > +static int yb9_kbdock_query_locked(struct device *log_dev) > +{ > + union acpi_object *obj; > + u32 bkbd; > + > + if (!yb9.block_wdev) > + return -ENODEV; > + > + obj = wmidev_block_query(yb9.block_wdev, 0); > + if (!obj) { > + dev_warn(log_dev, "WQAF returned NULL\n"); > + return -EIO; > + } > + > + /* > + * LENOVO_FEATURE_STATUS_DATA: 8-byte buffer {IDs=0x00060000, Status=BKBD}. > + */ > + if (obj->type == ACPI_TYPE_BUFFER && obj->buffer.length >= 8) { > + memcpy(&bkbd, obj->buffer.pointer + 4, sizeof(bkbd)); > + bkbd &= 0x3; > + } else if (obj->type == ACPI_TYPE_INTEGER) { > + bkbd = obj->integer.value & 0x3; > + } else { > + dev_warn(log_dev, "WQAF: unexpected type %d len %u\n", > + obj->type, > + obj->type == ACPI_TYPE_BUFFER ? obj->buffer.length : 0); > + kfree(obj); > + return -EIO; > + } > + > + kfree(obj); > + return (int)bkbd; > +} > + > +/* Report SW_TABLET_MODE from BKBD. Caller must hold yb9.lock. */ > +static void yb9_kbdock_report_locked(int bkbd, struct device *log_dev) > +{ > + int tablet = (bkbd == BKBD_DETACHED) ? 1 : 0; > + > + input_report_switch(yb9.input_dev, SW_TABLET_MODE, tablet); > + input_sync(yb9.input_dev); > + dev_dbg(log_dev, "BKBD=%d SW_TABLET_MODE=%d\n", bkbd, tablet); > +} > + > +/* ------------------------------------------------------------------ > + * sysfs > + * ------------------------------------------------------------------ */ > + > +static ssize_t keyboard_position_show(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + int bkbd; > + > + mutex_lock(&yb9.lock); > + bkbd = yb9_kbdock_query_locked(dev); > + mutex_unlock(&yb9.lock); > + > + if (bkbd < 0) > + return bkbd; > + return sysfs_emit(buf, "%u\n", (unsigned int)bkbd); > +} > +static DEVICE_ATTR_RO(keyboard_position); > + > +static struct attribute *yb9_kbdock_attrs[] = { > + &dev_attr_keyboard_position.attr, > + NULL, > +}; > +ATTRIBUTE_GROUPS(yb9_kbdock); > + > +/* ------------------------------------------------------------------ > + * Event WMI driver — LENOVO_BTKBD_EVENT > + * ------------------------------------------------------------------ */ > + > +static void yb9_kbdock_notify(struct wmi_device *wdev, union acpi_object *data) > +{ > + u32 bkbd; > + > + /* > + * _WED(0xEB) returns EC.BKBD directly as an integer > + * (LENOVO_BTKBD_EVENT WmiDataId(1) uint32 Status). > + */ > + if (!data || data->type != ACPI_TYPE_INTEGER) { > + dev_warn(&wdev->dev, "unexpected event data type %d\n", > + data ? data->type : -1); > + return; > + } > + bkbd = data->integer.value & 0x3; > + > + mutex_lock(&yb9.lock); > + if (yb9.input_dev) > + yb9_kbdock_report_locked(bkbd, &wdev->dev); > + mutex_unlock(&yb9.lock); > +} > + > +static int yb9_kbdock_event_probe(struct wmi_device *wdev, const void *ctx) > +{ > + struct input_dev *input_dev; > + int bkbd, err; > + > + if (!dmi_check_system(yb9_kbdock_dmi_table)) > + return -ENODEV; > + > + input_dev = devm_input_allocate_device(&wdev->dev); > + if (!input_dev) > + return -ENOMEM; > + > + input_dev->name = "Lenovo Yoga Book 9 keyboard dock switch"; > + input_dev->phys = YB9_KBDOCK_EVENT_GUID "/input0"; > + input_dev->id.bustype = BUS_HOST; > + input_set_capability(input_dev, EV_SW, SW_TABLET_MODE); > + > + err = input_register_device(input_dev); > + if (err) > + return err; > + > + mutex_lock(&yb9.lock); > + yb9.input_dev = input_dev; > + /* Read initial state if the block device has already probed. */ > + if (yb9.block_wdev) { > + bkbd = yb9_kbdock_query_locked(&wdev->dev); > + if (bkbd >= 0) > + yb9_kbdock_report_locked(bkbd, &wdev->dev); > + } > + mutex_unlock(&yb9.lock); > + > + return 0; > +} > + > +static void yb9_kbdock_event_remove(struct wmi_device *wdev) > +{ > + mutex_lock(&yb9.lock); > + yb9.input_dev = NULL; > + mutex_unlock(&yb9.lock); > +} > + > +static int yb9_kbdock_resume(struct device *dev) > +{ > + int bkbd; > + > + mutex_lock(&yb9.lock); > + if (yb9.input_dev && yb9.block_wdev) { > + bkbd = yb9_kbdock_query_locked(dev); > + if (bkbd >= 0) > + yb9_kbdock_report_locked(bkbd, dev); > + } > + mutex_unlock(&yb9.lock); > + return 0; > +} > + > +static DEFINE_SIMPLE_DEV_PM_OPS(yb9_kbdock_pm_ops, NULL, yb9_kbdock_resume); > + > +static const struct wmi_device_id yb9_kbdock_event_id_table[] = { > + { .guid_string = YB9_KBDOCK_EVENT_GUID }, > + { } > +}; > +MODULE_DEVICE_TABLE(wmi, yb9_kbdock_event_id_table); > + > +static struct wmi_driver yb9_kbdock_event_driver = { > + .driver = { > + .name = "lenovo-yb9-kbdock", > + .dev_groups = yb9_kbdock_groups, > + .pm = pm_sleep_ptr(&yb9_kbdock_pm_ops), > + }, > + .id_table = yb9_kbdock_event_id_table, > + .no_singleton = true, > + .probe = yb9_kbdock_event_probe, > + .remove = yb9_kbdock_event_remove, > + .notify = yb9_kbdock_notify, > +}; > + > +/* ------------------------------------------------------------------ > + * Block WMI driver — LENOVO_FEATURE_STATUS_DATA > + * ------------------------------------------------------------------ */ > + > +static int yb9_kbdock_block_probe(struct wmi_device *wdev, const void *ctx) > +{ > + int bkbd; > + > + if (!dmi_check_system(yb9_kbdock_dmi_table)) > + return -ENODEV; > + > + mutex_lock(&yb9.lock); > + yb9.block_wdev = wdev; > + /* Read initial state if the event device has already probed. */ > + if (yb9.input_dev) { > + bkbd = yb9_kbdock_query_locked(&wdev->dev); > + if (bkbd >= 0) > + yb9_kbdock_report_locked(bkbd, &wdev->dev); > + } > + mutex_unlock(&yb9.lock); > + return 0; > +} > + > +static void yb9_kbdock_block_remove(struct wmi_device *wdev) > +{ > + mutex_lock(&yb9.lock); > + yb9.block_wdev = NULL; > + mutex_unlock(&yb9.lock); > +} > + > +static const struct wmi_device_id yb9_kbdock_block_id_table[] = { > + { .guid_string = YB9_KBDOCK_QUERY_GUID }, > + { } > +}; > + > +static struct wmi_driver yb9_kbdock_block_driver = { > + .driver = { > + .name = "lenovo-yb9-kbdock-block", > + }, > + .id_table = yb9_kbdock_block_id_table, > + .no_singleton = true, > + .probe = yb9_kbdock_block_probe, > + .remove = yb9_kbdock_block_remove, > +}; > + > +/* ------------------------------------------------------------------ > + * Module init / exit > + * ------------------------------------------------------------------ */ > + > +static int __init yb9_kbdock_init(void) > +{ > + int ret; > + > + mutex_init(&yb9.lock); > + > + ret = wmi_driver_register(&yb9_kbdock_event_driver); > + if (ret) > + return ret; > + > + ret = wmi_driver_register(&yb9_kbdock_block_driver); > + if (ret) { > + wmi_driver_unregister(&yb9_kbdock_event_driver); > + return ret; > + } > + return 0; > +} > +module_init(yb9_kbdock_init); > + > +static void __exit yb9_kbdock_exit(void) > +{ > + wmi_driver_unregister(&yb9_kbdock_block_driver); > + wmi_driver_unregister(&yb9_kbdock_event_driver); > +} > +module_exit(yb9_kbdock_exit); > + > +MODULE_AUTHOR("Dave Carey <carvsdriver@gmail.com>"); > +MODULE_DESCRIPTION("Lenovo Yoga Book 9 keyboard dock detection"); > +MODULE_LICENSE("GPL"); ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v5] platform/x86/lenovo: Add Yoga Book 9 keyboard dock detection driver 2026-06-08 9:13 ` johannes.goede @ 2026-06-08 12:52 ` Dave Carey 2026-06-08 15:55 ` johannes.goede 0 siblings, 1 reply; 20+ messages in thread From: Dave Carey @ 2026-06-08 12:52 UTC (permalink / raw) To: johannes.goede Cc: platform-driver-x86, pithenrich2d, mpearson-lenovo, hdegoede, ilpo.jarvinen, armin.wolf, linux-kernel, Dave Carey On 08-Jun-26 11:13, Hans de Goede wrote: > Dave, I see that your "[PATCH v5] platform/x86/lenovo: Add Yoga Book 9 keyboard > dock detection driver" patch also uses a DMI match, is that necessary ? > > If we need the DMI match because the GUID is not unique enough, then we might > just as well add this functionality here as this patch is doing ... Yes, the DMI match is necessary, specifically for the block GUID (E7F300FA, LENOVO_FEATURE_STATUS_DATA). This is a generic Lenovo feature query interface that may appear on other Lenovo machines for unrelated purposes. Both GUIDs live under a PNP0C14 device with _UID "GMZN" on this machine, which is firmware-specific, but the WMI core matches by GUID alone — not by parent device UID. Without the DMI guard the block driver could bind on unrelated hardware and misinterpret WQAF's return value as a BKBD field. The event GUID (806BD2A2, LENOVO_BTKBD_EVENT) is named specifically for Bluetooth keyboard events in the BMOF and is likely unique to BT keyboard dock hardware, but keeping the DMI guard on both drivers is the safe choice given we can't easily verify all Lenovo platforms. > I do wonder since this seems to emit TP_HKEY_EV_TABLET_CHANGED thinkpad_acpi > events if the existing thinkpad_acpi support does not already provide working > SW_TABLET_MODE input ? > > If it does then also having the WMI driver emit SW_TABLET_MODE events seems > to be undesirable duplicate functionality. Tested: no overlap. On the YB9 the existing SW_TABLET_MODE source is lenovo-ymc (not thinkpad_acpi). The YMC driver tracks hinge/orientation modes (clamshell=0, tent/tablet/stand=1) — it does not track BT keyboard attachment state. I ran evtest on both the lenovo-ymc and yb9-kbdock input devices while detaching and reattaching the keyboard: lenovo-ymc did not fire at all; only yb9-kbdock emitted SW_TABLET_MODE events. There is no duplicate functionality. Regards, Dave ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v5] platform/x86/lenovo: Add Yoga Book 9 keyboard dock detection driver 2026-06-08 12:52 ` Dave Carey @ 2026-06-08 15:55 ` johannes.goede 2026-06-08 19:05 ` Dave Carey 0 siblings, 1 reply; 20+ messages in thread From: johannes.goede @ 2026-06-08 15:55 UTC (permalink / raw) To: Dave Carey Cc: platform-driver-x86, pithenrich2d, mpearson-lenovo, hdegoede, ilpo.jarvinen, armin.wolf, linux-kernel Hi Dave, On 8-Jun-26 14:52, Dave Carey wrote: > On 08-Jun-26 11:13, Hans de Goede wrote: >> Dave, I see that your "[PATCH v5] platform/x86/lenovo: Add Yoga Book 9 keyboard >> dock detection driver" patch also uses a DMI match, is that necessary ? >> >> If we need the DMI match because the GUID is not unique enough, then we might >> just as well add this functionality here as this patch is doing ... > > Yes, the DMI match is necessary, specifically for the block GUID > (E7F300FA, LENOVO_FEATURE_STATUS_DATA). This is a generic Lenovo feature > query interface that may appear on other Lenovo machines for unrelated > purposes. Both GUIDs live under a PNP0C14 device with _UID "GMZN" on > this machine, which is firmware-specific, but the WMI core matches by > GUID alone — not by parent device UID. Without the DMI guard the block > driver could bind on unrelated hardware and misinterpret WQAF's return > value as a BKBD field. > > The event GUID (806BD2A2, LENOVO_BTKBD_EVENT) is named specifically for > Bluetooth keyboard events in the BMOF and is likely unique to BT keyboard > dock hardware, but keeping the DMI guard on both drivers is the safe choice > given we can't easily verify all Lenovo platforms. > >> I do wonder since this seems to emit TP_HKEY_EV_TABLET_CHANGED thinkpad_acpi >> events if the existing thinkpad_acpi support does not already provide working >> SW_TABLET_MODE input ? >> >> If it does then also having the WMI driver emit SW_TABLET_MODE events seems >> to be undesirable duplicate functionality. > > Tested: no overlap. On the YB9 the existing SW_TABLET_MODE source is > lenovo-ymc (not thinkpad_acpi). Hmm, interesting not having thinkpad_acpi on a (non ThinkPad) Yoga makes sense. Let me double check if this driver really is duplicate with the thinkpad_acpi patch from pit: https://patchwork.kernel.org/project/platform-driver-x86/patch/20260419102724.91451-1-pithenrich2d@gmail.com/ Ok, I should have looked closer, that one if for a "ThinkPad X1 Fold 16 Gen 1" and yours is for the "Yoga Book 9" so I was mistaken that these drivers are duplicate, we will need both, my bad. That does bring the question if we want to unify the userspace interface, but the 2 setups seem to be different enough that having a single unified userspace interface also seems unnecessary. The yoga book 9 has a keyboard which as your driver reports can either cover the bottom or top half of the bottom screen, where as the "ThinkPad X1 Fold 16 Gen 1" has a keyboard trackpad combo which covers the whole bottom screen (*) when attached to the screen. *) bottom screen half as it is a single big screen > The YMC driver tracks hinge/orientation > modes (clamshell=0, tent/tablet/stand=1) — it does not track BT keyboard > attachment state. I ran evtest on both the lenovo-ymc and yb9-kbdock input > devices while detaching and reattaching the keyboard: lenovo-ymc did not > fire at all; only yb9-kbdock emitted SW_TABLET_MODE events. There is no > duplicate functionality. Hmm, so lenovo-ymc does load and create a /dev/input/event# nodes with a SW_TABLET_MODE switch reported as being present ? That is a problem because for switches unlike keys the mere presence of them has meaning. E.g GNOME will read the SW_TABLET_MODE switch value at GNOME start and use that to decide if it should start in tablet or regular=clamshell/desktop mode. So the mere presence of a SW_* style evdev node can cause behavior changes. And userspace really does not expect there to be 2 evdev nodes which both report SW_TABLET_MODE. So I think we need a patch to the lenovo-ymc driver to exit probe() early (with return 0) when loading on the yoga book 9 to avoid there being 2 SW_TABLET_MODE reporting input devices. Regards, Hans ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v5] platform/x86/lenovo: Add Yoga Book 9 keyboard dock detection driver 2026-06-08 15:55 ` johannes.goede @ 2026-06-08 19:05 ` Dave Carey 2026-06-08 19:23 ` johannes.goede 0 siblings, 1 reply; 20+ messages in thread From: Dave Carey @ 2026-06-08 19:05 UTC (permalink / raw) To: johannes.goede Cc: platform-driver-x86, pithenrich2d, mpearson-lenovo, hdegoede, ilpo.jarvinen, armin.wolf, linux-kernel, Dave Carey Hi Hans, Confirmed: on the YB9, both lenovo-ymc and yb9-kbdock register input nodes with SW_TABLET_MODE in their capability bitmap (SW=2 in /proc/bus/input/devices). You are right that this is a problem regardless of which one fires events -- GNOME reads the capability at startup. The recommended fix would be: add a DMI blocklist to lenovo_ymc_probe() to return early on the Yoga Book 9, matching on: DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), DMI_MATCH(DMI_PRODUCT_NAME, "83KJ"), That leaves yb9-kbdock as the sole SW_TABLET_MODE source on this hardware. Two questions on how you'd like to proceed: 1. Should the lenovo-ymc fix come as a standalone patch, or would you prefer a v6 2-patch series with the ymc change as patch 1/2 and the kbdock driver as patch 2/2? 2. For the early return in probe(): you suggested "return 0" to avoid WMI framework error messages. That requires a NULL guard in lenovo_ymc_notify() since priv is never set in that path. Is that the right approach, or would you prefer a different pattern? Thanks, Dave ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v5] platform/x86/lenovo: Add Yoga Book 9 keyboard dock detection driver 2026-06-08 19:05 ` Dave Carey @ 2026-06-08 19:23 ` johannes.goede 2026-06-08 21:52 ` [PATCH v6 0/2] " Dave Carey 0 siblings, 1 reply; 20+ messages in thread From: johannes.goede @ 2026-06-08 19:23 UTC (permalink / raw) To: Dave Carey Cc: platform-driver-x86, pithenrich2d, mpearson-lenovo, hdegoede, ilpo.jarvinen, armin.wolf, linux-kernel Hi, On 8-Jun-26 21:05, Dave Carey wrote: > Hi Hans, > > Confirmed: on the YB9, both lenovo-ymc and yb9-kbdock register input > nodes with SW_TABLET_MODE in their capability bitmap (SW=2 in > /proc/bus/input/devices). You are right that this is a problem > regardless of which one fires events -- GNOME reads the capability at > startup. > > The recommended fix would be: add a DMI blocklist to lenovo_ymc_probe() > to return early on the Yoga Book 9, matching on: > > DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), > DMI_MATCH(DMI_PRODUCT_NAME, "83KJ"), > > That leaves yb9-kbdock as the sole SW_TABLET_MODE source on this > hardware. Ack, that sounds like the right approach. > Two questions on how you'd like to proceed: > > 1. Should the lenovo-ymc fix come as a standalone patch, or would you > prefer a v6 2-patch series with the ymc change as patch 1/2 and the > kbdock driver as patch 2/2? I think a v6 series makes most sense, even if one of the patches is just an unmodified reposting of this v5. > 2. For the early return in probe(): you suggested "return 0" to avoid > WMI framework error messages. That requires a NULL guard in > lenovo_ymc_notify() since priv is never set in that path. Is that > the right approach, or would you prefer a different pattern? My bad, that should be -ENODEV, so that the driver does not count as being bound, while still suppressing errors from the caller of probe. That should avoid lenovo_ymc_notify() getting called. Assuming there is no other functionality in lenovo-ymc we care about ... Regards, Hans ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v6 0/2] platform/x86/lenovo: Add Yoga Book 9 keyboard dock detection driver 2026-06-08 19:23 ` johannes.goede @ 2026-06-08 21:52 ` Dave Carey 2026-06-08 21:52 ` [PATCH v6 1/2] platform/x86/lenovo: lenovo-ymc: Suppress probe on Yoga Book 9 14IAH10 Dave Carey ` (2 more replies) 0 siblings, 3 replies; 20+ messages in thread From: Dave Carey @ 2026-06-08 21:52 UTC (permalink / raw) To: platform-driver-x86 Cc: johannes.goede, mpearson-lenovo, ilpo.jarvinen, armin.wolf, linux-kernel, Dave Carey This series adds keyboard dock detection for the Lenovo Yoga Book 9 14IAH10 and fixes a pre-existing SW_TABLET_MODE conflict that the new driver exposes. Patch 1/2 fixes lenovo-ymc: it registers an input node advertising SW_TABLET_MODE on the Yoga Book 9, but so does patch 2/2. Userspace (GNOME) reads SW_TABLET_MODE at login from every input node that advertises the capability and does not tolerate two such nodes. The fix returns -ENODEV in lenovo-ymc probe() for this hardware; the ymc_ec_trigger EC write is separately gated by a DMI table that already excludes this machine, so no other functionality is affected. Patch 2/2 is the keyboard dock driver, unchanged from v5. Changes since v5: - New patch 1/2: lenovo-ymc: Suppress probe on Yoga Book 9 14IAH10. Identified during v5 review by Hans de Goede. - Patch 2/2: no code changes; reposted as part of the series. Dave Carey (2): platform/x86/lenovo: lenovo-ymc: Suppress probe on Yoga Book 9 14IAH10 platform/x86/lenovo: Add Yoga Book 9 keyboard dock detection driver .../testing/sysfs-driver-lenovo-yb9-kbdock | 19 + MAINTAINERS | 7 + drivers/platform/x86/lenovo/Kconfig | 14 + drivers/platform/x86/lenovo/Makefile | 1 + drivers/platform/x86/lenovo/yb9-kbdock.c | 324 ++++++++++++++++++ drivers/platform/x86/lenovo/ymc.c | 19 +++++++++++++++++++ 6 files changed, 384 insertions(+) -- 2.54.0 ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v6 1/2] platform/x86/lenovo: lenovo-ymc: Suppress probe on Yoga Book 9 14IAH10 2026-06-08 21:52 ` [PATCH v6 0/2] " Dave Carey @ 2026-06-08 21:52 ` Dave Carey 2026-06-09 1:29 ` Mark Pearson 2026-06-09 8:50 ` johannes.goede 2026-06-08 21:52 ` [PATCH v6 2/2] platform/x86/lenovo: Add Yoga Book 9 keyboard dock detection driver Dave Carey 2026-06-09 21:26 ` [PATCH v7 0/2] " Dave Carey 2 siblings, 2 replies; 20+ messages in thread From: Dave Carey @ 2026-06-08 21:52 UTC (permalink / raw) To: platform-driver-x86 Cc: johannes.goede, mpearson-lenovo, ilpo.jarvinen, armin.wolf, linux-kernel, Dave Carey The Yoga Book 9 14IAH10 (DMI product name "83KJ") has a dedicated yb9-kbdock WMI driver that registers an input device reporting SW_TABLET_MODE to track the detachable Bluetooth keyboard. lenovo-ymc also loads on this machine and creates an input node with the SW_TABLET_MODE capability bit set. For input switches, the presence of the capability bit has semantic meaning: userspace (e.g. GNOME) reads the switch state at startup from every node advertising the capability and does not expect more than one such node. Add a DMI match for the Yoga Book 9 14IAH10 to probe() so that lenovo-ymc returns -ENODEV on this hardware, leaving yb9-kbdock as the sole SW_TABLET_MODE source. The ymc_ec_trigger EC write, the only other action taken in response to a YMC event, is guarded by a separate DMI table that excludes this machine; no other functionality is affected. Signed-off-by: Dave Carey <carvsdriver@gmail.com> --- drivers/platform/x86/lenovo/ymc.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/drivers/platform/x86/lenovo/ymc.c b/drivers/platform/x86/lenovo/ymc.c --- a/drivers/platform/x86/lenovo/ymc.c +++ b/drivers/platform/x86/lenovo/ymc.c @@ -23,7 +23,23 @@ module_param(force, bool, 0444); static bool force; module_param(force, bool, 0444); MODULE_PARM_DESC(force, "Force loading on boards without a convertible DMI chassis-type"); +static const struct dmi_system_id lenovo_ymc_nosupport_dmi_table[] = { + { + /* + * Yoga Book 9 14IAH10: SW_TABLET_MODE is reported by the + * yb9-kbdock driver. Suppress lenovo-ymc on this machine to + * avoid userspace seeing two input nodes that both advertise + * the SW_TABLET_MODE capability. + */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "83KJ"), + }, + }, + { } +}; + static const struct dmi_system_id allowed_chasis_types_dmi_table[] = { { .matches = { @@ -100,7 +116,10 @@ static int lenovo_ymc_probe(struct wmi_device *wdev, const void *ctx) struct lenovo_ymc_private *priv; struct input_dev *input_dev; int err; + if (dmi_check_system(lenovo_ymc_nosupport_dmi_table)) + return -ENODEV; + if (!dmi_check_system(allowed_chasis_types_dmi_table)) { if (force) dev_info(&wdev->dev, "Force loading Lenovo YMC support\n"); -- 2.54.0 ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v6 1/2] platform/x86/lenovo: lenovo-ymc: Suppress probe on Yoga Book 9 14IAH10 2026-06-08 21:52 ` [PATCH v6 1/2] platform/x86/lenovo: lenovo-ymc: Suppress probe on Yoga Book 9 14IAH10 Dave Carey @ 2026-06-09 1:29 ` Mark Pearson 2026-06-09 1:29 ` Mark Pearson 2026-06-09 8:50 ` johannes.goede 1 sibling, 1 reply; 20+ messages in thread From: Mark Pearson @ 2026-06-09 1:29 UTC (permalink / raw) To: Dave Carey, platform-driver-x86@vger.kernel.org Cc: Hans de Goede, Ilpo Järvinen, armin.wolf, linux-kernel Hi Dave On Mon, Jun 8, 2026, at 5:52 PM, Dave Carey wrote: > The Yoga Book 9 14IAH10 (DMI product name "83KJ") has a dedicated > yb9-kbdock WMI driver that registers an input device reporting > SW_TABLET_MODE to track the detachable Bluetooth keyboard. > > lenovo-ymc also loads on this machine and creates an input node with the > SW_TABLET_MODE capability bit set. For input switches, the presence of > the capability bit has semantic meaning: userspace (e.g. GNOME) reads > the switch state at startup from every node advertising the capability > and does not expect more than one such node. > > Add a DMI match for the Yoga Book 9 14IAH10 to probe() so that > lenovo-ymc returns -ENODEV on this hardware, leaving yb9-kbdock as the > sole SW_TABLET_MODE source. The ymc_ec_trigger EC write, the only > other action taken in response to a YMC event, is guarded by a separate > DMI table that excludes this machine; no other functionality is affected. > > Signed-off-by: Dave Carey <carvsdriver@gmail.com> > --- > drivers/platform/x86/lenovo/ymc.c | 19 +++++++++++++++++++ > 1 file changed, 19 insertions(+) > > diff --git a/drivers/platform/x86/lenovo/ymc.c > b/drivers/platform/x86/lenovo/ymc.c > --- a/drivers/platform/x86/lenovo/ymc.c > +++ b/drivers/platform/x86/lenovo/ymc.c > @@ -23,7 +23,23 @@ module_param(force, bool, 0444); > static bool force; > module_param(force, bool, 0444); > MODULE_PARM_DESC(force, "Force loading on boards without a convertible > DMI chassis-type"); > > +static const struct dmi_system_id lenovo_ymc_nosupport_dmi_table[] = { > + { > + /* > + * Yoga Book 9 14IAH10: SW_TABLET_MODE is reported by the > + * yb9-kbdock driver. Suppress lenovo-ymc on this machine to > + * avoid userspace seeing two input nodes that both advertise > + * the SW_TABLET_MODE capability. > + */ > + .matches = { > + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), > + DMI_MATCH(DMI_PRODUCT_NAME, "83KJ"), > + }, > + }, > + { } > +}; > + > static const struct dmi_system_id allowed_chasis_types_dmi_table[] = { > { > .matches = { > @@ -100,7 +116,10 @@ static int lenovo_ymc_probe(struct wmi_device > *wdev, const void *ctx) > struct lenovo_ymc_private *priv; > struct input_dev *input_dev; > int err; > > + if (dmi_check_system(lenovo_ymc_nosupport_dmi_table)) > + return -ENODEV; > + > if (!dmi_check_system(allowed_chasis_types_dmi_table)) { > if (force) > dev_info(&wdev->dev, "Force loading Lenovo YMC support\n"); > -- > 2.54.0 I'm sure I'm missing the obvious - but where is the yb9-kbdock driver? I can't find it... I was curious as to whether a separate driver was really needed for this platform, or if it should have been integrated in the ymc driver in the first place? Mark ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v6 1/2] platform/x86/lenovo: lenovo-ymc: Suppress probe on Yoga Book 9 14IAH10 2026-06-09 1:29 ` Mark Pearson @ 2026-06-09 1:29 ` Mark Pearson 0 siblings, 0 replies; 20+ messages in thread From: Mark Pearson @ 2026-06-09 1:29 UTC (permalink / raw) To: Dave Carey, platform-driver-x86@vger.kernel.org Cc: Hans de Goede, Ilpo Järvinen, armin.wolf, linux-kernel On Mon, Jun 8, 2026, at 9:29 PM, Mark Pearson wrote: > Hi Dave > > On Mon, Jun 8, 2026, at 5:52 PM, Dave Carey wrote: >> The Yoga Book 9 14IAH10 (DMI product name "83KJ") has a dedicated >> yb9-kbdock WMI driver that registers an input device reporting >> SW_TABLET_MODE to track the detachable Bluetooth keyboard. >> >> lenovo-ymc also loads on this machine and creates an input node with the >> SW_TABLET_MODE capability bit set. For input switches, the presence of >> the capability bit has semantic meaning: userspace (e.g. GNOME) reads >> the switch state at startup from every node advertising the capability >> and does not expect more than one such node. >> >> Add a DMI match for the Yoga Book 9 14IAH10 to probe() so that >> lenovo-ymc returns -ENODEV on this hardware, leaving yb9-kbdock as the >> sole SW_TABLET_MODE source. The ymc_ec_trigger EC write, the only >> other action taken in response to a YMC event, is guarded by a separate >> DMI table that excludes this machine; no other functionality is affected. >> >> Signed-off-by: Dave Carey <carvsdriver@gmail.com> >> --- >> drivers/platform/x86/lenovo/ymc.c | 19 +++++++++++++++++++ >> 1 file changed, 19 insertions(+) >> >> diff --git a/drivers/platform/x86/lenovo/ymc.c >> b/drivers/platform/x86/lenovo/ymc.c >> --- a/drivers/platform/x86/lenovo/ymc.c >> +++ b/drivers/platform/x86/lenovo/ymc.c >> @@ -23,7 +23,23 @@ module_param(force, bool, 0444); >> static bool force; >> module_param(force, bool, 0444); >> MODULE_PARM_DESC(force, "Force loading on boards without a convertible >> DMI chassis-type"); >> >> +static const struct dmi_system_id lenovo_ymc_nosupport_dmi_table[] = { >> + { >> + /* >> + * Yoga Book 9 14IAH10: SW_TABLET_MODE is reported by the >> + * yb9-kbdock driver. Suppress lenovo-ymc on this machine to >> + * avoid userspace seeing two input nodes that both advertise >> + * the SW_TABLET_MODE capability. >> + */ >> + .matches = { >> + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), >> + DMI_MATCH(DMI_PRODUCT_NAME, "83KJ"), >> + }, >> + }, >> + { } >> +}; >> + >> static const struct dmi_system_id allowed_chasis_types_dmi_table[] = { >> { >> .matches = { >> @@ -100,7 +116,10 @@ static int lenovo_ymc_probe(struct wmi_device >> *wdev, const void *ctx) >> struct lenovo_ymc_private *priv; >> struct input_dev *input_dev; >> int err; >> >> + if (dmi_check_system(lenovo_ymc_nosupport_dmi_table)) >> + return -ENODEV; >> + >> if (!dmi_check_system(allowed_chasis_types_dmi_table)) { >> if (force) >> dev_info(&wdev->dev, "Force loading Lenovo YMC support\n"); >> -- >> 2.54.0 > > I'm sure I'm missing the obvious - but where is the yb9-kbdock driver? > I can't find it... > > I was curious as to whether a separate driver was really needed for > this platform, or if it should have been integrated in the ymc driver > in the first place? > > Mark Nevermind - ignore this. I just saw patch 2 and went Durrrr.... My brain is still in vacation mode :) ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v6 1/2] platform/x86/lenovo: lenovo-ymc: Suppress probe on Yoga Book 9 14IAH10 2026-06-08 21:52 ` [PATCH v6 1/2] platform/x86/lenovo: lenovo-ymc: Suppress probe on Yoga Book 9 14IAH10 Dave Carey 2026-06-09 1:29 ` Mark Pearson @ 2026-06-09 8:50 ` johannes.goede 1 sibling, 0 replies; 20+ messages in thread From: johannes.goede @ 2026-06-09 8:50 UTC (permalink / raw) To: Dave Carey, platform-driver-x86 Cc: mpearson-lenovo, ilpo.jarvinen, armin.wolf, linux-kernel Hi, On 8-Jun-26 23:52, Dave Carey wrote: > The Yoga Book 9 14IAH10 (DMI product name "83KJ") has a dedicated > yb9-kbdock WMI driver that registers an input device reporting > SW_TABLET_MODE to track the detachable Bluetooth keyboard. > > lenovo-ymc also loads on this machine and creates an input node with the > SW_TABLET_MODE capability bit set. For input switches, the presence of > the capability bit has semantic meaning: userspace (e.g. GNOME) reads > the switch state at startup from every node advertising the capability > and does not expect more than one such node. > > Add a DMI match for the Yoga Book 9 14IAH10 to probe() so that > lenovo-ymc returns -ENODEV on this hardware, leaving yb9-kbdock as the > sole SW_TABLET_MODE source. The ymc_ec_trigger EC write, the only > other action taken in response to a YMC event, is guarded by a separate > DMI table that excludes this machine; no other functionality is affected. > > Signed-off-by: Dave Carey <carvsdriver@gmail.com> Thanks, patch looks good to me: Reviewed-by: Hans de Goede <johannes.goede@oss.qualcomm.com> Regards, Hans > --- > drivers/platform/x86/lenovo/ymc.c | 19 +++++++++++++++++++ > 1 file changed, 19 insertions(+) > > diff --git a/drivers/platform/x86/lenovo/ymc.c b/drivers/platform/x86/lenovo/ymc.c > --- a/drivers/platform/x86/lenovo/ymc.c > +++ b/drivers/platform/x86/lenovo/ymc.c > @@ -23,7 +23,23 @@ module_param(force, bool, 0444); > static bool force; > module_param(force, bool, 0444); > MODULE_PARM_DESC(force, "Force loading on boards without a convertible DMI chassis-type"); > > +static const struct dmi_system_id lenovo_ymc_nosupport_dmi_table[] = { > + { > + /* > + * Yoga Book 9 14IAH10: SW_TABLET_MODE is reported by the > + * yb9-kbdock driver. Suppress lenovo-ymc on this machine to > + * avoid userspace seeing two input nodes that both advertise > + * the SW_TABLET_MODE capability. > + */ > + .matches = { > + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), > + DMI_MATCH(DMI_PRODUCT_NAME, "83KJ"), > + }, > + }, > + { } > +}; > + > static const struct dmi_system_id allowed_chasis_types_dmi_table[] = { > { > .matches = { > @@ -100,7 +116,10 @@ static int lenovo_ymc_probe(struct wmi_device *wdev, const void *ctx) > struct lenovo_ymc_private *priv; > struct input_dev *input_dev; > int err; > > + if (dmi_check_system(lenovo_ymc_nosupport_dmi_table)) > + return -ENODEV; > + > if (!dmi_check_system(allowed_chasis_types_dmi_table)) { > if (force) > dev_info(&wdev->dev, "Force loading Lenovo YMC support\n"); > -- > 2.54.0 ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v6 2/2] platform/x86/lenovo: Add Yoga Book 9 keyboard dock detection driver 2026-06-08 21:52 ` [PATCH v6 0/2] " Dave Carey 2026-06-08 21:52 ` [PATCH v6 1/2] platform/x86/lenovo: lenovo-ymc: Suppress probe on Yoga Book 9 14IAH10 Dave Carey @ 2026-06-08 21:52 ` Dave Carey 2026-06-09 8:50 ` johannes.goede 2026-06-09 9:47 ` Ilpo Järvinen 2026-06-09 21:26 ` [PATCH v7 0/2] " Dave Carey 2 siblings, 2 replies; 20+ messages in thread From: Dave Carey @ 2026-06-08 21:52 UTC (permalink / raw) To: platform-driver-x86 Cc: johannes.goede, mpearson-lenovo, ilpo.jarvinen, armin.wolf, linux-kernel, Dave Carey The Lenovo Yoga Book 9 14IAH10 ships with a detachable Bluetooth keyboard that magnetically attaches to the bottom (secondary) screen in one of two positions. The Embedded Controller tracks the attachment state in a 2-bit field called BKBD and signals changes via WMI event GUID 806BD2A2-177B-481D-BFB5-3BA0BB4A2285 (notify ID 0xEB on the WM10 ACPI device, _UID "GMZN"). The device contains embedded BMOF data (WQDD, 20705 bytes) documenting both WMI interfaces used by this driver: LENOVO_BTKBD_EVENT (event GUID): WmiDataId(1) uint32 Status. The ACPI _WED(0xEB) method returns EC.BKBD directly as an integer, so the notify callback receives BKBD without a separate query. LENOVO_FEATURE_STATUS_DATA (block GUID, WQAF method): returns an 8-byte buffer {uint32 IDs=0x00060000, uint32 Status=BKBD}. Used for the initial state read on probe and after resume. BKBD encoding: 0 = keyboard detached 1 = keyboard docked on top half of bottom screen 2 = keyboard docked on bottom half of bottom screen 3 = reserved (not observed in practice) This driver: - Registers two WMI drivers: one on the event GUID (LENOVO_BTKBD_EVENT) and one on the block GUID (LENOVO_FEATURE_STATUS_DATA). - On probe, reads initial BKBD state via wmidev_block_query() on the block device; whichever driver probes last triggers the initial read. - On WMI notification, reads BKBD directly from the event data integer (ACPI _WED(0xEB) returns EC.BKBD) without a redundant WQAF call. - Reports SW_TABLET_MODE=1 when detached, SW_TABLET_MODE=0 when docked in either position (a physical keyboard is present in both cases). - Exposes the raw BKBD value via read-only sysfs attribute "keyboard_position". - Re-reads BKBD state on resume from suspend or hibernation. Tested on: Lenovo Yoga Book 9 14IAH10 (model 83KJ), kernel 7.0. Signed-off-by: Dave Carey <carvsdriver@gmail.com> --- v6: - Submitted as 2/2; patch 1/2 adds a DMI early-exit to lenovo-ymc to prevent duplicate SW_TABLET_MODE input nodes on the YB9. v5: - Rewrote as two WMI drivers (event + block) to avoid the deprecated wmi_query_block() API: event driver owns the GUID that delivers notifications, block driver owns the GUID used for querying state. Either can probe first; both probe callbacks check whether the other has already probed and fire the initial read when both are ready. - Use wmidev_block_query() on the block wmi_device directly (replaces the deprecated wmi_query_block()). - In the notify callback, read BKBD directly from the event data integer passed by the WMI core (_WED(0xEB) returns EC.BKBD; confirmed by decoding the embedded BMOF from WQDD). Eliminates the redundant WQAF call that v4 made on every notification. - Documented the two BMOF classes (LENOVO_BTKBD_EVENT and LENOVO_FEATURE_STATUS_DATA) with field layouts in the commit message and file header. - sysfs keyboard_position: output bare integer, drop the parenthetical position-name suffix that a couple of reviewers found non-standard. - Set .no_singleton = true on both WMI drivers (was missing from block driver in v4). - Add PM resume callback (yb9_kbdock_resume) to re-read BKBD state after suspend/hibernation. v4: - Dropped module_wmi_driver(); registered two WMI drivers manually to allow sharing state. module_init/exit pair registers/unregisters both. - Added ATTRIBUTE_GROUPS() and .dev_groups for the sysfs attribute. - sysfs show: performed a live query instead of returning a cached value. v3: - Switched to devm_input_allocate_device(). - Added DMI guard (dmi_check_system) to reject non-YB9 machines. - Removed redundant MODULE_DEVICE_TABLE on block driver. v2: - Added .no_singleton = true on the event WMI driver. - Added PM suspend/resume skeleton. - Fixed MODULE_LICENSE to "GPL" (was "GPL v2"). .../testing/sysfs-driver-lenovo-yb9-kbdock | 19 + MAINTAINERS | 7 + drivers/platform/x86/lenovo/Kconfig | 14 + drivers/platform/x86/lenovo/Makefile | 1 + drivers/platform/x86/lenovo/yb9-kbdock.c | 324 ++++++++++++++++++ 5 files changed, 365 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock create mode 100644 drivers/platform/x86/lenovo/yb9-kbdock.c diff --git a/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock b/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock new file mode 100644 index 0000000..04e5293 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock @@ -0,0 +1,19 @@ +What: /sys/bus/wmi/drivers/lenovo-yb9-kbdock/<guid>/keyboard_position +Date: April 2026 +KernelVersion: 6.10 +Contact: Dave Carey <carvsdriver@gmail.com> +Description: + Read-only attribute reporting the current keyboard dock position + as reported by the Embedded Controller on the Lenovo Yoga Book 9 + 14IAH10. + + Possible values: + + == ============================================================= + 0 keyboard is not docked to any screen (detached) + 1 keyboard docked on the top half of the bottom screen + 2 keyboard docked on the bottom half of the bottom screen + == ============================================================= + + SW_TABLET_MODE input events are also emitted: 0 when the keyboard + is docked (either position), 1 when detached. diff --git a/MAINTAINERS b/MAINTAINERS index d1cc0e1..00e8275 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14479,6 +14479,13 @@ L: platform-driver-x86@vger.kernel.org S: Maintained F: drivers/platform/x86/lenovo/wmi-hotkey-utilities.c +LENOVO YOGA BOOK 9 KEYBOARD DOCK DRIVER +M: Dave Carey <carvsdriver@gmail.com> +L: platform-driver-x86@vger.kernel.org +S: Maintained +F: Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock +F: drivers/platform/x86/lenovo/yb9-kbdock.c + LETSKETCH HID TABLET DRIVER M: Hans de Goede <hansg@kernel.org> L: linux-input@vger.kernel.org diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/lenovo/Kconfig index 9c48487..938b361 100644 --- a/drivers/platform/x86/lenovo/Kconfig +++ b/drivers/platform/x86/lenovo/Kconfig @@ -43,6 +43,20 @@ config LENOVO_WMI_CAMERA To compile this driver as a module, choose M here: the module will be called lenovo-wmi-camera. +config LENOVO_YB9_KBDOCK + tristate "Lenovo Yoga Book 9 keyboard dock detection" + depends on ACPI_WMI + depends on DMI + depends on INPUT + help + Say Y here to enable keyboard dock detection on the Lenovo Yoga Book 9 + 14IAH10. The detachable Bluetooth keyboard magnetically attaches to + either screen; this driver reports SW_TABLET_MODE input events based + on the attachment state and exposes the raw position in sysfs. + + To compile this driver as a module, choose M here: the module will be + called lenovo-yb9-kbdock. + config LENOVO_YMC tristate "Lenovo Yoga Tablet Mode Control" depends on ACPI_WMI diff --git a/drivers/platform/x86/lenovo/Makefile b/drivers/platform/x86/lenovo/Makefile index 7b2128e..2842d7d 100644 --- a/drivers/platform/x86/lenovo/Makefile +++ b/drivers/platform/x86/lenovo/Makefile @@ -8,6 +8,7 @@ obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o lenovo-target-$(CONFIG_LENOVO_WMI_HOTKEY_UTILITIES) += wmi-hotkey-utilities.o +lenovo-target-$(CONFIG_LENOVO_YB9_KBDOCK) += yb9-kbdock.o lenovo-target-$(CONFIG_LENOVO_YMC) += ymc.o lenovo-target-$(CONFIG_YOGABOOK) += yogabook.o lenovo-target-$(CONFIG_YT2_1380) += yoga-tab2-pro-1380-fastcharger.o diff --git a/drivers/platform/x86/lenovo/yb9-kbdock.c b/drivers/platform/x86/lenovo/yb9-kbdock.c new file mode 100644 index 0000000..28a3ec7 --- /dev/null +++ b/drivers/platform/x86/lenovo/yb9-kbdock.c @@ -0,0 +1,324 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Lenovo Yoga Book 9 keyboard-dock detection + * + * The Yoga Book 9 ships with a detachable Bluetooth keyboard that magnetically + * attaches to the bottom screen in one of two positions. The EC tracks + * attachment state in a 2-bit field called BKBD and signals changes via WMI + * event 0xEB on the WM10 ACPI device (_UID "GMZN"). + * + * BKBD values: + * 0 = keyboard detached + * 1 = keyboard docked on the top half of the bottom screen + * 2 = keyboard docked on the bottom half of the bottom screen + * 3 = reserved / not observed + * + * Two WMI interfaces are used (documented in embedded BMOF, WQDD, 20705 bytes): + * + * LENOVO_BTKBD_EVENT (event GUID, 806BD2A2-...) + * WmiDataId(1) uint32 Status — _WED(0xEB) returns EC.BKBD directly. + * The notify callback receives BKBD as an integer; no separate query needed. + * + * LENOVO_FEATURE_STATUS_DATA (block GUID, E7F300FA-...) + * WmiDataId(1) uint32 IDs = 0x00060000 (feature selector) + * WmiDataId(2) uint32 Status = BKBD value + * Used on probe and resume to read initial state. + * + * SW_TABLET_MODE=1 is reported when the keyboard is detached; + * SW_TABLET_MODE=0 when docked in either position (keyboard present). + * The raw BKBD value is exposed via the sysfs attribute "keyboard_position". + * + * Copyright (C) 2026 Dave Carey <carvsdriver@gmail.com> + */ + +#include <linux/acpi.h> +#include <linux/dmi.h> +#include <linux/input.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/pm.h> +#include <linux/wmi.h> + +#define YB9_KBDOCK_EVENT_GUID "806BD2A2-177B-481D-BFB5-3BA0BB4A2285" +#define YB9_KBDOCK_QUERY_GUID "E7F300FA-21CD-4003-ADAC-2696135982E6" + +/* BKBD encoding */ +#define BKBD_DETACHED 0 +#define BKBD_TOP_HALF 1 +#define BKBD_BOTTOM_HALF 2 + +static const struct dmi_system_id yb9_kbdock_dmi_table[] = { + { + /* Lenovo Yoga Book 9 14IAH10 */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "83KJ"), + }, + }, + { } +}; + +/* + * Shared state between the event and block WMI drivers. Protected by lock. + * The lock may be held across wmidev_query_block() calls (which can sleep); + * block_remove() acquires the lock before clearing block_wdev, ensuring + * block_wdev remains valid for the duration of any in-progress query. + */ +static struct { + struct mutex lock; + struct input_dev *input_dev; /* set by event probe */ + struct wmi_device *block_wdev; /* set by block probe */ +} yb9; + +/* + * Read BKBD from the block device via wmidev_block_query(). + * Returns 0-3 on success, -errno on failure. Caller must hold yb9.lock. + */ +static int yb9_kbdock_query_locked(struct device *log_dev) +{ + union acpi_object *obj; + u32 bkbd; + + if (!yb9.block_wdev) + return -ENODEV; + + obj = wmidev_block_query(yb9.block_wdev, 0); + if (!obj) { + dev_warn(log_dev, "WQAF returned NULL\n"); + return -EIO; + } + + /* + * LENOVO_FEATURE_STATUS_DATA: 8-byte buffer {IDs=0x00060000, Status=BKBD}. + */ + if (obj->type == ACPI_TYPE_BUFFER && obj->buffer.length >= 8) { + memcpy(&bkbd, obj->buffer.pointer + 4, sizeof(bkbd)); + bkbd &= 0x3; + } else if (obj->type == ACPI_TYPE_INTEGER) { + bkbd = obj->integer.value & 0x3; + } else { + dev_warn(log_dev, "WQAF: unexpected type %d len %u\n", + obj->type, + obj->type == ACPI_TYPE_BUFFER ? obj->buffer.length : 0); + kfree(obj); + return -EIO; + } + + kfree(obj); + return (int)bkbd; +} + +/* Report SW_TABLET_MODE from BKBD. Caller must hold yb9.lock. */ +static void yb9_kbdock_report_locked(int bkbd, struct device *log_dev) +{ + int tablet = (bkbd == BKBD_DETACHED) ? 1 : 0; + + input_report_switch(yb9.input_dev, SW_TABLET_MODE, tablet); + input_sync(yb9.input_dev); + dev_dbg(log_dev, "BKBD=%d SW_TABLET_MODE=%d\n", bkbd, tablet); +} + +/* ------------------------------------------------------------------ + * sysfs + * ------------------------------------------------------------------ */ + +static ssize_t keyboard_position_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int bkbd; + + mutex_lock(&yb9.lock); + bkbd = yb9_kbdock_query_locked(dev); + mutex_unlock(&yb9.lock); + + if (bkbd < 0) + return bkbd; + return sysfs_emit(buf, "%u\n", (unsigned int)bkbd); +} +static DEVICE_ATTR_RO(keyboard_position); + +static struct attribute *yb9_kbdock_attrs[] = { + &dev_attr_keyboard_position.attr, + NULL, +}; +ATTRIBUTE_GROUPS(yb9_kbdock); + +/* ------------------------------------------------------------------ + * Event WMI driver — LENOVO_BTKBD_EVENT + * ------------------------------------------------------------------ */ + +static void yb9_kbdock_notify(struct wmi_device *wdev, union acpi_object *data) +{ + u32 bkbd; + + /* + * _WED(0xEB) returns EC.BKBD directly as an integer + * (LENOVO_BTKBD_EVENT WmiDataId(1) uint32 Status). + */ + if (!data || data->type != ACPI_TYPE_INTEGER) { + dev_warn(&wdev->dev, "unexpected event data type %d\n", + data ? data->type : -1); + return; + } + bkbd = data->integer.value & 0x3; + + mutex_lock(&yb9.lock); + if (yb9.input_dev) + yb9_kbdock_report_locked(bkbd, &wdev->dev); + mutex_unlock(&yb9.lock); +} + +static int yb9_kbdock_event_probe(struct wmi_device *wdev, const void *ctx) +{ + struct input_dev *input_dev; + int bkbd, err; + + if (!dmi_check_system(yb9_kbdock_dmi_table)) + return -ENODEV; + + input_dev = devm_input_allocate_device(&wdev->dev); + if (!input_dev) + return -ENOMEM; + + input_dev->name = "Lenovo Yoga Book 9 keyboard dock switch"; + input_dev->phys = YB9_KBDOCK_EVENT_GUID "/input0"; + input_dev->id.bustype = BUS_HOST; + input_set_capability(input_dev, EV_SW, SW_TABLET_MODE); + + err = input_register_device(input_dev); + if (err) + return err; + + mutex_lock(&yb9.lock); + yb9.input_dev = input_dev; + /* Read initial state if the block device has already probed. */ + if (yb9.block_wdev) { + bkbd = yb9_kbdock_query_locked(&wdev->dev); + if (bkbd >= 0) + yb9_kbdock_report_locked(bkbd, &wdev->dev); + } + mutex_unlock(&yb9.lock); + + return 0; +} + +static void yb9_kbdock_event_remove(struct wmi_device *wdev) +{ + mutex_lock(&yb9.lock); + yb9.input_dev = NULL; + mutex_unlock(&yb9.lock); +} + +static int yb9_kbdock_resume(struct device *dev) +{ + int bkbd; + + mutex_lock(&yb9.lock); + if (yb9.input_dev && yb9.block_wdev) { + bkbd = yb9_kbdock_query_locked(dev); + if (bkbd >= 0) + yb9_kbdock_report_locked(bkbd, dev); + } + mutex_unlock(&yb9.lock); + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(yb9_kbdock_pm_ops, NULL, yb9_kbdock_resume); + +static const struct wmi_device_id yb9_kbdock_event_id_table[] = { + { .guid_string = YB9_KBDOCK_EVENT_GUID }, + { } +}; +MODULE_DEVICE_TABLE(wmi, yb9_kbdock_event_id_table); + +static struct wmi_driver yb9_kbdock_event_driver = { + .driver = { + .name = "lenovo-yb9-kbdock", + .dev_groups = yb9_kbdock_groups, + .pm = pm_sleep_ptr(&yb9_kbdock_pm_ops), + }, + .id_table = yb9_kbdock_event_id_table, + .no_singleton = true, + .probe = yb9_kbdock_event_probe, + .remove = yb9_kbdock_event_remove, + .notify = yb9_kbdock_notify, +}; + +/* ------------------------------------------------------------------ + * Block WMI driver — LENOVO_FEATURE_STATUS_DATA + * ------------------------------------------------------------------ */ + +static int yb9_kbdock_block_probe(struct wmi_device *wdev, const void *ctx) +{ + int bkbd; + + if (!dmi_check_system(yb9_kbdock_dmi_table)) + return -ENODEV; + + mutex_lock(&yb9.lock); + yb9.block_wdev = wdev; + /* Read initial state if the event device has already probed. */ + if (yb9.input_dev) { + bkbd = yb9_kbdock_query_locked(&wdev->dev); + if (bkbd >= 0) + yb9_kbdock_report_locked(bkbd, &wdev->dev); + } + mutex_unlock(&yb9.lock); + return 0; +} + +static void yb9_kbdock_block_remove(struct wmi_device *wdev) +{ + mutex_lock(&yb9.lock); + yb9.block_wdev = NULL; + mutex_unlock(&yb9.lock); +} + +static const struct wmi_device_id yb9_kbdock_block_id_table[] = { + { .guid_string = YB9_KBDOCK_QUERY_GUID }, + { } +}; + +static struct wmi_driver yb9_kbdock_block_driver = { + .driver = { + .name = "lenovo-yb9-kbdock-block", + }, + .id_table = yb9_kbdock_block_id_table, + .no_singleton = true, + .probe = yb9_kbdock_block_probe, + .remove = yb9_kbdock_block_remove, +}; + +/* ------------------------------------------------------------------ + * Module init / exit + * ------------------------------------------------------------------ */ + +static int __init yb9_kbdock_init(void) +{ + int ret; + + mutex_init(&yb9.lock); + + ret = wmi_driver_register(&yb9_kbdock_event_driver); + if (ret) + return ret; + + ret = wmi_driver_register(&yb9_kbdock_block_driver); + if (ret) { + wmi_driver_unregister(&yb9_kbdock_event_driver); + return ret; + } + return 0; +} +module_init(yb9_kbdock_init); + +static void __exit yb9_kbdock_exit(void) +{ + wmi_driver_unregister(&yb9_kbdock_block_driver); + wmi_driver_unregister(&yb9_kbdock_event_driver); +} +module_exit(yb9_kbdock_exit); + +MODULE_AUTHOR("Dave Carey <carvsdriver@gmail.com>"); +MODULE_DESCRIPTION("Lenovo Yoga Book 9 keyboard dock detection"); +MODULE_LICENSE("GPL"); -- 2.54.0 ^ permalink raw reply related [flat|nested] 20+ messages in thread
* Re: [PATCH v6 2/2] platform/x86/lenovo: Add Yoga Book 9 keyboard dock detection driver 2026-06-08 21:52 ` [PATCH v6 2/2] platform/x86/lenovo: Add Yoga Book 9 keyboard dock detection driver Dave Carey @ 2026-06-09 8:50 ` johannes.goede 2026-06-09 9:47 ` Ilpo Järvinen 1 sibling, 0 replies; 20+ messages in thread From: johannes.goede @ 2026-06-09 8:50 UTC (permalink / raw) To: Dave Carey, platform-driver-x86 Cc: mpearson-lenovo, ilpo.jarvinen, armin.wolf, linux-kernel Hi, On 8-Jun-26 23:52, Dave Carey wrote: > The Lenovo Yoga Book 9 14IAH10 ships with a detachable Bluetooth keyboard > that magnetically attaches to the bottom (secondary) screen in one of two > positions. The Embedded Controller tracks the attachment state in a 2-bit > field called BKBD and signals changes via WMI event GUID > 806BD2A2-177B-481D-BFB5-3BA0BB4A2285 (notify ID 0xEB on the WM10 ACPI > device, _UID "GMZN"). > > The device contains embedded BMOF data (WQDD, 20705 bytes) documenting > both WMI interfaces used by this driver: > > LENOVO_BTKBD_EVENT (event GUID): WmiDataId(1) uint32 Status. > The ACPI _WED(0xEB) method returns EC.BKBD directly as an integer, > so the notify callback receives BKBD without a separate query. > > LENOVO_FEATURE_STATUS_DATA (block GUID, WQAF method): returns an > 8-byte buffer {uint32 IDs=0x00060000, uint32 Status=BKBD}. > Used for the initial state read on probe and after resume. > > BKBD encoding: > 0 = keyboard detached > 1 = keyboard docked on top half of bottom screen > 2 = keyboard docked on bottom half of bottom screen > 3 = reserved (not observed in practice) > > This driver: > - Registers two WMI drivers: one on the event GUID (LENOVO_BTKBD_EVENT) > and one on the block GUID (LENOVO_FEATURE_STATUS_DATA). > - On probe, reads initial BKBD state via wmidev_block_query() on the > block device; whichever driver probes last triggers the initial read. > - On WMI notification, reads BKBD directly from the event data integer > (ACPI _WED(0xEB) returns EC.BKBD) without a redundant WQAF call. > - Reports SW_TABLET_MODE=1 when detached, SW_TABLET_MODE=0 when docked > in either position (a physical keyboard is present in both cases). > - Exposes the raw BKBD value via read-only sysfs attribute > "keyboard_position". > - Re-reads BKBD state on resume from suspend or hibernation. > > Tested on: Lenovo Yoga Book 9 14IAH10 (model 83KJ), kernel 7.0. > > Signed-off-by: Dave Carey <carvsdriver@gmail.com> > ---> v6: > - Submitted as 2/2; patch 1/2 adds a DMI early-exit to lenovo-ymc to > prevent duplicate SW_TABLET_MODE input nodes on the YB9. Thank you. For the userspace-API parts of this and the previously discussed DMI match usage: Acked-by: Hans de Goede <johannes.goede@oss.qualcomm.com> Regards, Hans > > v5: > - Rewrote as two WMI drivers (event + block) to avoid the deprecated > wmi_query_block() API: event driver owns the GUID that delivers > notifications, block driver owns the GUID used for querying state. > Either can probe first; both probe callbacks check whether the other > has already probed and fire the initial read when both are ready. > - Use wmidev_block_query() on the block wmi_device directly (replaces > the deprecated wmi_query_block()). > - In the notify callback, read BKBD directly from the event data integer > passed by the WMI core (_WED(0xEB) returns EC.BKBD; confirmed by > decoding the embedded BMOF from WQDD). Eliminates the redundant WQAF > call that v4 made on every notification. > - Documented the two BMOF classes (LENOVO_BTKBD_EVENT and > LENOVO_FEATURE_STATUS_DATA) with field layouts in the commit message > and file header. > - sysfs keyboard_position: output bare integer, drop the parenthetical > position-name suffix that a couple of reviewers found non-standard. > - Set .no_singleton = true on both WMI drivers (was missing from block > driver in v4). > - Add PM resume callback (yb9_kbdock_resume) to re-read BKBD state > after suspend/hibernation. > > v4: > - Dropped module_wmi_driver(); registered two WMI drivers manually to > allow sharing state. module_init/exit pair registers/unregisters > both. > - Added ATTRIBUTE_GROUPS() and .dev_groups for the sysfs attribute. > - sysfs show: performed a live query instead of returning a cached value. > > v3: > - Switched to devm_input_allocate_device(). > - Added DMI guard (dmi_check_system) to reject non-YB9 machines. > - Removed redundant MODULE_DEVICE_TABLE on block driver. > > v2: > - Added .no_singleton = true on the event WMI driver. > - Added PM suspend/resume skeleton. > - Fixed MODULE_LICENSE to "GPL" (was "GPL v2"). > > .../testing/sysfs-driver-lenovo-yb9-kbdock | 19 + > MAINTAINERS | 7 + > drivers/platform/x86/lenovo/Kconfig | 14 + > drivers/platform/x86/lenovo/Makefile | 1 + > drivers/platform/x86/lenovo/yb9-kbdock.c | 324 ++++++++++++++++++ > 5 files changed, 365 insertions(+) > create mode 100644 Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock > create mode 100644 drivers/platform/x86/lenovo/yb9-kbdock.c > > diff --git a/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock b/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock > new file mode 100644 > index 0000000..04e5293 > --- /dev/null > +++ b/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock > @@ -0,0 +1,19 @@ > +What: /sys/bus/wmi/drivers/lenovo-yb9-kbdock/<guid>/keyboard_position > +Date: April 2026 > +KernelVersion: 6.10 > +Contact: Dave Carey <carvsdriver@gmail.com> > +Description: > + Read-only attribute reporting the current keyboard dock position > + as reported by the Embedded Controller on the Lenovo Yoga Book 9 > + 14IAH10. > + > + Possible values: > + > + == ============================================================= > + 0 keyboard is not docked to any screen (detached) > + 1 keyboard docked on the top half of the bottom screen > + 2 keyboard docked on the bottom half of the bottom screen > + == ============================================================= > + > + SW_TABLET_MODE input events are also emitted: 0 when the keyboard > + is docked (either position), 1 when detached. > diff --git a/MAINTAINERS b/MAINTAINERS > index d1cc0e1..00e8275 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -14479,6 +14479,13 @@ L: platform-driver-x86@vger.kernel.org > S: Maintained > F: drivers/platform/x86/lenovo/wmi-hotkey-utilities.c > > +LENOVO YOGA BOOK 9 KEYBOARD DOCK DRIVER > +M: Dave Carey <carvsdriver@gmail.com> > +L: platform-driver-x86@vger.kernel.org > +S: Maintained > +F: Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock > +F: drivers/platform/x86/lenovo/yb9-kbdock.c > + > LETSKETCH HID TABLET DRIVER > M: Hans de Goede <hansg@kernel.org> > L: linux-input@vger.kernel.org > diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/lenovo/Kconfig > index 9c48487..938b361 100644 > --- a/drivers/platform/x86/lenovo/Kconfig > +++ b/drivers/platform/x86/lenovo/Kconfig > @@ -43,6 +43,20 @@ config LENOVO_WMI_CAMERA > To compile this driver as a module, choose M here: the module > will be called lenovo-wmi-camera. > > +config LENOVO_YB9_KBDOCK > + tristate "Lenovo Yoga Book 9 keyboard dock detection" > + depends on ACPI_WMI > + depends on DMI > + depends on INPUT > + help > + Say Y here to enable keyboard dock detection on the Lenovo Yoga Book 9 > + 14IAH10. The detachable Bluetooth keyboard magnetically attaches to > + either screen; this driver reports SW_TABLET_MODE input events based > + on the attachment state and exposes the raw position in sysfs. > + > + To compile this driver as a module, choose M here: the module will be > + called lenovo-yb9-kbdock. > + > config LENOVO_YMC > tristate "Lenovo Yoga Tablet Mode Control" > depends on ACPI_WMI > diff --git a/drivers/platform/x86/lenovo/Makefile b/drivers/platform/x86/lenovo/Makefile > index 7b2128e..2842d7d 100644 > --- a/drivers/platform/x86/lenovo/Makefile > +++ b/drivers/platform/x86/lenovo/Makefile > @@ -8,6 +8,7 @@ obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o > obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o > > lenovo-target-$(CONFIG_LENOVO_WMI_HOTKEY_UTILITIES) += wmi-hotkey-utilities.o > +lenovo-target-$(CONFIG_LENOVO_YB9_KBDOCK) += yb9-kbdock.o > lenovo-target-$(CONFIG_LENOVO_YMC) += ymc.o > lenovo-target-$(CONFIG_YOGABOOK) += yogabook.o > lenovo-target-$(CONFIG_YT2_1380) += yoga-tab2-pro-1380-fastcharger.o > diff --git a/drivers/platform/x86/lenovo/yb9-kbdock.c b/drivers/platform/x86/lenovo/yb9-kbdock.c > new file mode 100644 > index 0000000..28a3ec7 > --- /dev/null > +++ b/drivers/platform/x86/lenovo/yb9-kbdock.c > @@ -0,0 +1,324 @@ > +// SPDX-License-Identifier: GPL-2.0-or-later > +/* > + * Lenovo Yoga Book 9 keyboard-dock detection > + * > + * The Yoga Book 9 ships with a detachable Bluetooth keyboard that magnetically > + * attaches to the bottom screen in one of two positions. The EC tracks > + * attachment state in a 2-bit field called BKBD and signals changes via WMI > + * event 0xEB on the WM10 ACPI device (_UID "GMZN"). > + * > + * BKBD values: > + * 0 = keyboard detached > + * 1 = keyboard docked on the top half of the bottom screen > + * 2 = keyboard docked on the bottom half of the bottom screen > + * 3 = reserved / not observed > + * > + * Two WMI interfaces are used (documented in embedded BMOF, WQDD, 20705 bytes): > + * > + * LENOVO_BTKBD_EVENT (event GUID, 806BD2A2-...) > + * WmiDataId(1) uint32 Status — _WED(0xEB) returns EC.BKBD directly. > + * The notify callback receives BKBD as an integer; no separate query needed. > + * > + * LENOVO_FEATURE_STATUS_DATA (block GUID, E7F300FA-...) > + * WmiDataId(1) uint32 IDs = 0x00060000 (feature selector) > + * WmiDataId(2) uint32 Status = BKBD value > + * Used on probe and resume to read initial state. > + * > + * SW_TABLET_MODE=1 is reported when the keyboard is detached; > + * SW_TABLET_MODE=0 when docked in either position (keyboard present). > + * The raw BKBD value is exposed via the sysfs attribute "keyboard_position". > + * > + * Copyright (C) 2026 Dave Carey <carvsdriver@gmail.com> > + */ > + > +#include <linux/acpi.h> > +#include <linux/dmi.h> > +#include <linux/input.h> > +#include <linux/module.h> > +#include <linux/mutex.h> > +#include <linux/pm.h> > +#include <linux/wmi.h> > + > +#define YB9_KBDOCK_EVENT_GUID "806BD2A2-177B-481D-BFB5-3BA0BB4A2285" > +#define YB9_KBDOCK_QUERY_GUID "E7F300FA-21CD-4003-ADAC-2696135982E6" > + > +/* BKBD encoding */ > +#define BKBD_DETACHED 0 > +#define BKBD_TOP_HALF 1 > +#define BKBD_BOTTOM_HALF 2 > + > +static const struct dmi_system_id yb9_kbdock_dmi_table[] = { > + { > + /* Lenovo Yoga Book 9 14IAH10 */ > + .matches = { > + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), > + DMI_MATCH(DMI_PRODUCT_NAME, "83KJ"), > + }, > + }, > + { } > +}; > + > +/* > + * Shared state between the event and block WMI drivers. Protected by lock. > + * The lock may be held across wmidev_query_block() calls (which can sleep); > + * block_remove() acquires the lock before clearing block_wdev, ensuring > + * block_wdev remains valid for the duration of any in-progress query. > + */ > +static struct { > + struct mutex lock; > + struct input_dev *input_dev; /* set by event probe */ > + struct wmi_device *block_wdev; /* set by block probe */ > +} yb9; > + > +/* > + * Read BKBD from the block device via wmidev_block_query(). > + * Returns 0-3 on success, -errno on failure. Caller must hold yb9.lock. > + */ > +static int yb9_kbdock_query_locked(struct device *log_dev) > +{ > + union acpi_object *obj; > + u32 bkbd; > + > + if (!yb9.block_wdev) > + return -ENODEV; > + > + obj = wmidev_block_query(yb9.block_wdev, 0); > + if (!obj) { > + dev_warn(log_dev, "WQAF returned NULL\n"); > + return -EIO; > + } > + > + /* > + * LENOVO_FEATURE_STATUS_DATA: 8-byte buffer {IDs=0x00060000, Status=BKBD}. > + */ > + if (obj->type == ACPI_TYPE_BUFFER && obj->buffer.length >= 8) { > + memcpy(&bkbd, obj->buffer.pointer + 4, sizeof(bkbd)); > + bkbd &= 0x3; > + } else if (obj->type == ACPI_TYPE_INTEGER) { > + bkbd = obj->integer.value & 0x3; > + } else { > + dev_warn(log_dev, "WQAF: unexpected type %d len %u\n", > + obj->type, > + obj->type == ACPI_TYPE_BUFFER ? obj->buffer.length : 0); > + kfree(obj); > + return -EIO; > + } > + > + kfree(obj); > + return (int)bkbd; > +} > + > +/* Report SW_TABLET_MODE from BKBD. Caller must hold yb9.lock. */ > +static void yb9_kbdock_report_locked(int bkbd, struct device *log_dev) > +{ > + int tablet = (bkbd == BKBD_DETACHED) ? 1 : 0; > + > + input_report_switch(yb9.input_dev, SW_TABLET_MODE, tablet); > + input_sync(yb9.input_dev); > + dev_dbg(log_dev, "BKBD=%d SW_TABLET_MODE=%d\n", bkbd, tablet); > +} > + > +/* ------------------------------------------------------------------ > + * sysfs > + * ------------------------------------------------------------------ */ > + > +static ssize_t keyboard_position_show(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + int bkbd; > + > + mutex_lock(&yb9.lock); > + bkbd = yb9_kbdock_query_locked(dev); > + mutex_unlock(&yb9.lock); > + > + if (bkbd < 0) > + return bkbd; > + return sysfs_emit(buf, "%u\n", (unsigned int)bkbd); > +} > +static DEVICE_ATTR_RO(keyboard_position); > + > +static struct attribute *yb9_kbdock_attrs[] = { > + &dev_attr_keyboard_position.attr, > + NULL, > +}; > +ATTRIBUTE_GROUPS(yb9_kbdock); > + > +/* ------------------------------------------------------------------ > + * Event WMI driver — LENOVO_BTKBD_EVENT > + * ------------------------------------------------------------------ */ > + > +static void yb9_kbdock_notify(struct wmi_device *wdev, union acpi_object *data) > +{ > + u32 bkbd; > + > + /* > + * _WED(0xEB) returns EC.BKBD directly as an integer > + * (LENOVO_BTKBD_EVENT WmiDataId(1) uint32 Status). > + */ > + if (!data || data->type != ACPI_TYPE_INTEGER) { > + dev_warn(&wdev->dev, "unexpected event data type %d\n", > + data ? data->type : -1); > + return; > + } > + bkbd = data->integer.value & 0x3; > + > + mutex_lock(&yb9.lock); > + if (yb9.input_dev) > + yb9_kbdock_report_locked(bkbd, &wdev->dev); > + mutex_unlock(&yb9.lock); > +} > + > +static int yb9_kbdock_event_probe(struct wmi_device *wdev, const void *ctx) > +{ > + struct input_dev *input_dev; > + int bkbd, err; > + > + if (!dmi_check_system(yb9_kbdock_dmi_table)) > + return -ENODEV; > + > + input_dev = devm_input_allocate_device(&wdev->dev); > + if (!input_dev) > + return -ENOMEM; > + > + input_dev->name = "Lenovo Yoga Book 9 keyboard dock switch"; > + input_dev->phys = YB9_KBDOCK_EVENT_GUID "/input0"; > + input_dev->id.bustype = BUS_HOST; > + input_set_capability(input_dev, EV_SW, SW_TABLET_MODE); > + > + err = input_register_device(input_dev); > + if (err) > + return err; > + > + mutex_lock(&yb9.lock); > + yb9.input_dev = input_dev; > + /* Read initial state if the block device has already probed. */ > + if (yb9.block_wdev) { > + bkbd = yb9_kbdock_query_locked(&wdev->dev); > + if (bkbd >= 0) > + yb9_kbdock_report_locked(bkbd, &wdev->dev); > + } > + mutex_unlock(&yb9.lock); > + > + return 0; > +} > + > +static void yb9_kbdock_event_remove(struct wmi_device *wdev) > +{ > + mutex_lock(&yb9.lock); > + yb9.input_dev = NULL; > + mutex_unlock(&yb9.lock); > +} > + > +static int yb9_kbdock_resume(struct device *dev) > +{ > + int bkbd; > + > + mutex_lock(&yb9.lock); > + if (yb9.input_dev && yb9.block_wdev) { > + bkbd = yb9_kbdock_query_locked(dev); > + if (bkbd >= 0) > + yb9_kbdock_report_locked(bkbd, dev); > + } > + mutex_unlock(&yb9.lock); > + return 0; > +} > + > +static DEFINE_SIMPLE_DEV_PM_OPS(yb9_kbdock_pm_ops, NULL, yb9_kbdock_resume); > + > +static const struct wmi_device_id yb9_kbdock_event_id_table[] = { > + { .guid_string = YB9_KBDOCK_EVENT_GUID }, > + { } > +}; > +MODULE_DEVICE_TABLE(wmi, yb9_kbdock_event_id_table); > + > +static struct wmi_driver yb9_kbdock_event_driver = { > + .driver = { > + .name = "lenovo-yb9-kbdock", > + .dev_groups = yb9_kbdock_groups, > + .pm = pm_sleep_ptr(&yb9_kbdock_pm_ops), > + }, > + .id_table = yb9_kbdock_event_id_table, > + .no_singleton = true, > + .probe = yb9_kbdock_event_probe, > + .remove = yb9_kbdock_event_remove, > + .notify = yb9_kbdock_notify, > +}; > + > +/* ------------------------------------------------------------------ > + * Block WMI driver — LENOVO_FEATURE_STATUS_DATA > + * ------------------------------------------------------------------ */ > + > +static int yb9_kbdock_block_probe(struct wmi_device *wdev, const void *ctx) > +{ > + int bkbd; > + > + if (!dmi_check_system(yb9_kbdock_dmi_table)) > + return -ENODEV; > + > + mutex_lock(&yb9.lock); > + yb9.block_wdev = wdev; > + /* Read initial state if the event device has already probed. */ > + if (yb9.input_dev) { > + bkbd = yb9_kbdock_query_locked(&wdev->dev); > + if (bkbd >= 0) > + yb9_kbdock_report_locked(bkbd, &wdev->dev); > + } > + mutex_unlock(&yb9.lock); > + return 0; > +} > + > +static void yb9_kbdock_block_remove(struct wmi_device *wdev) > +{ > + mutex_lock(&yb9.lock); > + yb9.block_wdev = NULL; > + mutex_unlock(&yb9.lock); > +} > + > +static const struct wmi_device_id yb9_kbdock_block_id_table[] = { > + { .guid_string = YB9_KBDOCK_QUERY_GUID }, > + { } > +}; > + > +static struct wmi_driver yb9_kbdock_block_driver = { > + .driver = { > + .name = "lenovo-yb9-kbdock-block", > + }, > + .id_table = yb9_kbdock_block_id_table, > + .no_singleton = true, > + .probe = yb9_kbdock_block_probe, > + .remove = yb9_kbdock_block_remove, > +}; > + > +/* ------------------------------------------------------------------ > + * Module init / exit > + * ------------------------------------------------------------------ */ > + > +static int __init yb9_kbdock_init(void) > +{ > + int ret; > + > + mutex_init(&yb9.lock); > + > + ret = wmi_driver_register(&yb9_kbdock_event_driver); > + if (ret) > + return ret; > + > + ret = wmi_driver_register(&yb9_kbdock_block_driver); > + if (ret) { > + wmi_driver_unregister(&yb9_kbdock_event_driver); > + return ret; > + } > + return 0; > +} > +module_init(yb9_kbdock_init); > + > +static void __exit yb9_kbdock_exit(void) > +{ > + wmi_driver_unregister(&yb9_kbdock_block_driver); > + wmi_driver_unregister(&yb9_kbdock_event_driver); > +} > +module_exit(yb9_kbdock_exit); > + > +MODULE_AUTHOR("Dave Carey <carvsdriver@gmail.com>"); > +MODULE_DESCRIPTION("Lenovo Yoga Book 9 keyboard dock detection"); > +MODULE_LICENSE("GPL"); > -- > 2.54.0 ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v6 2/2] platform/x86/lenovo: Add Yoga Book 9 keyboard dock detection driver 2026-06-08 21:52 ` [PATCH v6 2/2] platform/x86/lenovo: Add Yoga Book 9 keyboard dock detection driver Dave Carey 2026-06-09 8:50 ` johannes.goede @ 2026-06-09 9:47 ` Ilpo Järvinen 1 sibling, 0 replies; 20+ messages in thread From: Ilpo Järvinen @ 2026-06-09 9:47 UTC (permalink / raw) To: Dave Carey Cc: platform-driver-x86, johannes.goede, Mark Pearson, armin.wolf, LKML [-- Attachment #1: Type: text/plain, Size: 19230 bytes --] On Mon, 8 Jun 2026, Dave Carey wrote: > The Lenovo Yoga Book 9 14IAH10 ships with a detachable Bluetooth keyboard > that magnetically attaches to the bottom (secondary) screen in one of two > positions. The Embedded Controller tracks the attachment state in a 2-bit > field called BKBD Please add a define for the field then with GENMASK() and extract it with FIELD_GET(). Don't forget to add headers. > and signals changes via WMI event GUID > 806BD2A2-177B-481D-BFB5-3BA0BB4A2285 (notify ID 0xEB on the WM10 ACPI > device, _UID "GMZN"). > > The device contains embedded BMOF data (WQDD, 20705 bytes) documenting > both WMI interfaces used by this driver: > > LENOVO_BTKBD_EVENT (event GUID): WmiDataId(1) uint32 Status. > The ACPI _WED(0xEB) method returns EC.BKBD directly as an integer, > so the notify callback receives BKBD without a separate query. > > LENOVO_FEATURE_STATUS_DATA (block GUID, WQAF method): returns an > 8-byte buffer {uint32 IDs=0x00060000, uint32 Status=BKBD}. > Used for the initial state read on probe and after resume. > > BKBD encoding: > 0 = keyboard detached > 1 = keyboard docked on top half of bottom screen > 2 = keyboard docked on bottom half of bottom screen > 3 = reserved (not observed in practice) > > This driver: > - Registers two WMI drivers: one on the event GUID (LENOVO_BTKBD_EVENT) > and one on the block GUID (LENOVO_FEATURE_STATUS_DATA). > - On probe, reads initial BKBD state via wmidev_block_query() on the > block device; whichever driver probes last triggers the initial read. > - On WMI notification, reads BKBD directly from the event data integer > (ACPI _WED(0xEB) returns EC.BKBD) without a redundant WQAF call. > - Reports SW_TABLET_MODE=1 when detached, SW_TABLET_MODE=0 when docked > in either position (a physical keyboard is present in both cases). > - Exposes the raw BKBD value via read-only sysfs attribute > "keyboard_position". > - Re-reads BKBD state on resume from suspend or hibernation. > > Tested on: Lenovo Yoga Book 9 14IAH10 (model 83KJ), kernel 7.0. > > Signed-off-by: Dave Carey <carvsdriver@gmail.com> > --- > v6: > - Submitted as 2/2; patch 1/2 adds a DMI early-exit to lenovo-ymc to > prevent duplicate SW_TABLET_MODE input nodes on the YB9. > > v5: > - Rewrote as two WMI drivers (event + block) to avoid the deprecated > wmi_query_block() API: event driver owns the GUID that delivers > notifications, block driver owns the GUID used for querying state. > Either can probe first; both probe callbacks check whether the other > has already probed and fire the initial read when both are ready. > - Use wmidev_block_query() on the block wmi_device directly (replaces > the deprecated wmi_query_block()). > - In the notify callback, read BKBD directly from the event data integer > passed by the WMI core (_WED(0xEB) returns EC.BKBD; confirmed by > decoding the embedded BMOF from WQDD). Eliminates the redundant WQAF > call that v4 made on every notification. > - Documented the two BMOF classes (LENOVO_BTKBD_EVENT and > LENOVO_FEATURE_STATUS_DATA) with field layouts in the commit message > and file header. > - sysfs keyboard_position: output bare integer, drop the parenthetical > position-name suffix that a couple of reviewers found non-standard. > - Set .no_singleton = true on both WMI drivers (was missing from block > driver in v4). > - Add PM resume callback (yb9_kbdock_resume) to re-read BKBD state > after suspend/hibernation. > > v4: > - Dropped module_wmi_driver(); registered two WMI drivers manually to > allow sharing state. module_init/exit pair registers/unregisters > both. > - Added ATTRIBUTE_GROUPS() and .dev_groups for the sysfs attribute. > - sysfs show: performed a live query instead of returning a cached value. > > v3: > - Switched to devm_input_allocate_device(). > - Added DMI guard (dmi_check_system) to reject non-YB9 machines. > - Removed redundant MODULE_DEVICE_TABLE on block driver. > > v2: > - Added .no_singleton = true on the event WMI driver. > - Added PM suspend/resume skeleton. > - Fixed MODULE_LICENSE to "GPL" (was "GPL v2"). > > .../testing/sysfs-driver-lenovo-yb9-kbdock | 19 + > MAINTAINERS | 7 + > drivers/platform/x86/lenovo/Kconfig | 14 + > drivers/platform/x86/lenovo/Makefile | 1 + > drivers/platform/x86/lenovo/yb9-kbdock.c | 324 ++++++++++++++++++ > 5 files changed, 365 insertions(+) > create mode 100644 Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock > create mode 100644 drivers/platform/x86/lenovo/yb9-kbdock.c > > diff --git a/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock b/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock > new file mode 100644 > index 0000000..04e5293 > --- /dev/null > +++ b/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock > @@ -0,0 +1,19 @@ > +What: /sys/bus/wmi/drivers/lenovo-yb9-kbdock/<guid>/keyboard_position > +Date: April 2026 > +KernelVersion: 6.10 > +Contact: Dave Carey <carvsdriver@gmail.com> > +Description: > + Read-only attribute reporting the current keyboard dock position > + as reported by the Embedded Controller on the Lenovo Yoga Book 9 > + 14IAH10. > + > + Possible values: > + > + == ============================================================= > + 0 keyboard is not docked to any screen (detached) > + 1 keyboard docked on the top half of the bottom screen > + 2 keyboard docked on the bottom half of the bottom screen > + == ============================================================= > + > + SW_TABLET_MODE input events are also emitted: 0 when the keyboard > + is docked (either position), 1 when detached. > diff --git a/MAINTAINERS b/MAINTAINERS > index d1cc0e1..00e8275 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -14479,6 +14479,13 @@ L: platform-driver-x86@vger.kernel.org > S: Maintained > F: drivers/platform/x86/lenovo/wmi-hotkey-utilities.c > > +LENOVO YOGA BOOK 9 KEYBOARD DOCK DRIVER > +M: Dave Carey <carvsdriver@gmail.com> > +L: platform-driver-x86@vger.kernel.org > +S: Maintained > +F: Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock > +F: drivers/platform/x86/lenovo/yb9-kbdock.c > + > LETSKETCH HID TABLET DRIVER > M: Hans de Goede <hansg@kernel.org> > L: linux-input@vger.kernel.org > diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/lenovo/Kconfig > index 9c48487..938b361 100644 > --- a/drivers/platform/x86/lenovo/Kconfig > +++ b/drivers/platform/x86/lenovo/Kconfig > @@ -43,6 +43,20 @@ config LENOVO_WMI_CAMERA > To compile this driver as a module, choose M here: the module > will be called lenovo-wmi-camera. > > +config LENOVO_YB9_KBDOCK > + tristate "Lenovo Yoga Book 9 keyboard dock detection" > + depends on ACPI_WMI > + depends on DMI > + depends on INPUT > + help > + Say Y here to enable keyboard dock detection on the Lenovo Yoga Book 9 > + 14IAH10. The detachable Bluetooth keyboard magnetically attaches to > + either screen; this driver reports SW_TABLET_MODE input events based > + on the attachment state and exposes the raw position in sysfs. > + > + To compile this driver as a module, choose M here: the module will be > + called lenovo-yb9-kbdock. > + > config LENOVO_YMC > tristate "Lenovo Yoga Tablet Mode Control" > depends on ACPI_WMI > diff --git a/drivers/platform/x86/lenovo/Makefile b/drivers/platform/x86/lenovo/Makefile > index 7b2128e..2842d7d 100644 > --- a/drivers/platform/x86/lenovo/Makefile > +++ b/drivers/platform/x86/lenovo/Makefile > @@ -8,6 +8,7 @@ obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o > obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o > > lenovo-target-$(CONFIG_LENOVO_WMI_HOTKEY_UTILITIES) += wmi-hotkey-utilities.o > +lenovo-target-$(CONFIG_LENOVO_YB9_KBDOCK) += yb9-kbdock.o > lenovo-target-$(CONFIG_LENOVO_YMC) += ymc.o > lenovo-target-$(CONFIG_YOGABOOK) += yogabook.o > lenovo-target-$(CONFIG_YT2_1380) += yoga-tab2-pro-1380-fastcharger.o > diff --git a/drivers/platform/x86/lenovo/yb9-kbdock.c b/drivers/platform/x86/lenovo/yb9-kbdock.c > new file mode 100644 > index 0000000..28a3ec7 > --- /dev/null > +++ b/drivers/platform/x86/lenovo/yb9-kbdock.c > @@ -0,0 +1,324 @@ > +// SPDX-License-Identifier: GPL-2.0-or-later > +/* > + * Lenovo Yoga Book 9 keyboard-dock detection > + * > + * The Yoga Book 9 ships with a detachable Bluetooth keyboard that magnetically > + * attaches to the bottom screen in one of two positions. The EC tracks > + * attachment state in a 2-bit field called BKBD and signals changes via WMI > + * event 0xEB on the WM10 ACPI device (_UID "GMZN"). > + * > + * BKBD values: > + * 0 = keyboard detached > + * 1 = keyboard docked on the top half of the bottom screen > + * 2 = keyboard docked on the bottom half of the bottom screen > + * 3 = reserved / not observed > + * > + * Two WMI interfaces are used (documented in embedded BMOF, WQDD, 20705 bytes): > + * > + * LENOVO_BTKBD_EVENT (event GUID, 806BD2A2-...) > + * WmiDataId(1) uint32 Status — _WED(0xEB) returns EC.BKBD directly. > + * The notify callback receives BKBD as an integer; no separate query needed. > + * > + * LENOVO_FEATURE_STATUS_DATA (block GUID, E7F300FA-...) > + * WmiDataId(1) uint32 IDs = 0x00060000 (feature selector) > + * WmiDataId(2) uint32 Status = BKBD value > + * Used on probe and resume to read initial state. > + * > + * SW_TABLET_MODE=1 is reported when the keyboard is detached; > + * SW_TABLET_MODE=0 when docked in either position (keyboard present). > + * The raw BKBD value is exposed via the sysfs attribute "keyboard_position". > + * > + * Copyright (C) 2026 Dave Carey <carvsdriver@gmail.com> > + */ > + > +#include <linux/acpi.h> > +#include <linux/dmi.h> > +#include <linux/input.h> > +#include <linux/module.h> > +#include <linux/mutex.h> > +#include <linux/pm.h> > +#include <linux/wmi.h> > + > +#define YB9_KBDOCK_EVENT_GUID "806BD2A2-177B-481D-BFB5-3BA0BB4A2285" > +#define YB9_KBDOCK_QUERY_GUID "E7F300FA-21CD-4003-ADAC-2696135982E6" > + > +/* BKBD encoding */ > +#define BKBD_DETACHED 0 > +#define BKBD_TOP_HALF 1 > +#define BKBD_BOTTOM_HALF 2 > + > +static const struct dmi_system_id yb9_kbdock_dmi_table[] = { > + { > + /* Lenovo Yoga Book 9 14IAH10 */ > + .matches = { > + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), > + DMI_MATCH(DMI_PRODUCT_NAME, "83KJ"), > + }, > + }, > + { } > +}; > + > +/* > + * Shared state between the event and block WMI drivers. Protected by lock. > + * The lock may be held across wmidev_query_block() calls (which can sleep); > + * block_remove() acquires the lock before clearing block_wdev, ensuring > + * block_wdev remains valid for the duration of any in-progress query. > + */ > +static struct { > + struct mutex lock; Please add a short comment here on the same line too about what it protects. > + struct input_dev *input_dev; /* set by event probe */ > + struct wmi_device *block_wdev; /* set by block probe */ > +} yb9; > + > +/* > + * Read BKBD from the block device via wmidev_block_query(). > + * Returns 0-3 on success, -errno on failure. Caller must hold yb9.lock. To match kerneldoc formatting guidelines, use: Context: Caller must hold yb9.lock. Returns: > + */ > +static int yb9_kbdock_query_locked(struct device *log_dev) > +{ > + union acpi_object *obj; > + u32 bkbd; > + > + if (!yb9.block_wdev) > + return -ENODEV; > + > + obj = wmidev_block_query(yb9.block_wdev, 0); This is interface is deprecated: * wmidev_block_query - Return contents of a WMI block (deprectated) > + if (!obj) { > + dev_warn(log_dev, "WQAF returned NULL\n"); Please add include. > + return -EIO; > + } > + > + /* > + * LENOVO_FEATURE_STATUS_DATA: 8-byte buffer {IDs=0x00060000, Status=BKBD}. > + */ > + if (obj->type == ACPI_TYPE_BUFFER && obj->buffer.length >= 8) { > + memcpy(&bkbd, obj->buffer.pointer + 4, sizeof(bkbd)); > + bkbd &= 0x3; > + } else if (obj->type == ACPI_TYPE_INTEGER) { > + bkbd = obj->integer.value & 0x3; > + } else { > + dev_warn(log_dev, "WQAF: unexpected type %d len %u\n", > + obj->type, > + obj->type == ACPI_TYPE_BUFFER ? obj->buffer.length : 0); > + kfree(obj); > + return -EIO; > + } > + > + kfree(obj); Please add include for kfree(). With the new WMI API, please use __free() to avoid dupli > + return (int)bkbd; > +} > + > +/* Report SW_TABLET_MODE from BKBD. Caller must hold yb9.lock. */ > +static void yb9_kbdock_report_locked(int bkbd, struct device *log_dev) > +{ > + int tablet = (bkbd == BKBD_DETACHED) ? 1 : 0; > + > + input_report_switch(yb9.input_dev, SW_TABLET_MODE, tablet); > + input_sync(yb9.input_dev); > + dev_dbg(log_dev, "BKBD=%d SW_TABLET_MODE=%d\n", bkbd, tablet); > +} > + > +/* ------------------------------------------------------------------ > + * sysfs > + * ------------------------------------------------------------------ */ > + > +static ssize_t keyboard_position_show(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + int bkbd; > + > + mutex_lock(&yb9.lock); > + bkbd = yb9_kbdock_query_locked(dev); > + mutex_unlock(&yb9.lock); > + > + if (bkbd < 0) > + return bkbd; > + return sysfs_emit(buf, "%u\n", (unsigned int)bkbd); > +} > +static DEVICE_ATTR_RO(keyboard_position); > + > +static struct attribute *yb9_kbdock_attrs[] = { > + &dev_attr_keyboard_position.attr, > + NULL, > +}; > +ATTRIBUTE_GROUPS(yb9_kbdock); > + > +/* ------------------------------------------------------------------ > + * Event WMI driver — LENOVO_BTKBD_EVENT > + * ------------------------------------------------------------------ */ > + > +static void yb9_kbdock_notify(struct wmi_device *wdev, union acpi_object *data) > +{ > + u32 bkbd; > + > + /* > + * _WED(0xEB) returns EC.BKBD directly as an integer > + * (LENOVO_BTKBD_EVENT WmiDataId(1) uint32 Status). > + */ > + if (!data || data->type != ACPI_TYPE_INTEGER) { > + dev_warn(&wdev->dev, "unexpected event data type %d\n", > + data ? data->type : -1); > + return; > + } > + bkbd = data->integer.value & 0x3; > + > + mutex_lock(&yb9.lock); guard() > + if (yb9.input_dev) > + yb9_kbdock_report_locked(bkbd, &wdev->dev); > + mutex_unlock(&yb9.lock); > +} > + > +static int yb9_kbdock_event_probe(struct wmi_device *wdev, const void *ctx) > +{ > + struct input_dev *input_dev; > + int bkbd, err; > + > + if (!dmi_check_system(yb9_kbdock_dmi_table)) > + return -ENODEV; > + > + input_dev = devm_input_allocate_device(&wdev->dev); > + if (!input_dev) > + return -ENOMEM; > + > + input_dev->name = "Lenovo Yoga Book 9 keyboard dock switch"; > + input_dev->phys = YB9_KBDOCK_EVENT_GUID "/input0"; > + input_dev->id.bustype = BUS_HOST; > + input_set_capability(input_dev, EV_SW, SW_TABLET_MODE); > + > + err = input_register_device(input_dev); > + if (err) > + return err; > + > + mutex_lock(&yb9.lock); guard() > + yb9.input_dev = input_dev; > + /* Read initial state if the block device has already probed. */ > + if (yb9.block_wdev) { > + bkbd = yb9_kbdock_query_locked(&wdev->dev); > + if (bkbd >= 0) > + yb9_kbdock_report_locked(bkbd, &wdev->dev); > + } > + mutex_unlock(&yb9.lock); > + > + return 0; > +} > + > +static void yb9_kbdock_event_remove(struct wmi_device *wdev) > +{ > + mutex_lock(&yb9.lock); guard() > + yb9.input_dev = NULL; > + mutex_unlock(&yb9.lock); > +} > + > +static int yb9_kbdock_resume(struct device *dev) > +{ > + int bkbd; > + > + mutex_lock(&yb9.lock); > + if (yb9.input_dev && yb9.block_wdev) { > + bkbd = yb9_kbdock_query_locked(dev); > + if (bkbd >= 0) > + yb9_kbdock_report_locked(bkbd, dev); This looks mostly duplicated with the code in yb9_kbdock_event_probe(), try to consolidate somehow. > + } > + mutex_unlock(&yb9.lock); > + return 0; > +} > + > +static DEFINE_SIMPLE_DEV_PM_OPS(yb9_kbdock_pm_ops, NULL, yb9_kbdock_resume); > + > +static const struct wmi_device_id yb9_kbdock_event_id_table[] = { > + { .guid_string = YB9_KBDOCK_EVENT_GUID }, > + { } > +}; > +MODULE_DEVICE_TABLE(wmi, yb9_kbdock_event_id_table); > + > +static struct wmi_driver yb9_kbdock_event_driver = { > + .driver = { > + .name = "lenovo-yb9-kbdock", > + .dev_groups = yb9_kbdock_groups, > + .pm = pm_sleep_ptr(&yb9_kbdock_pm_ops), > + }, > + .id_table = yb9_kbdock_event_id_table, > + .no_singleton = true, > + .probe = yb9_kbdock_event_probe, > + .remove = yb9_kbdock_event_remove, > + .notify = yb9_kbdock_notify, > +}; > + > +/* ------------------------------------------------------------------ > + * Block WMI driver — LENOVO_FEATURE_STATUS_DATA > + * ------------------------------------------------------------------ */ > + > +static int yb9_kbdock_block_probe(struct wmi_device *wdev, const void *ctx) > +{ > + int bkbd; > + > + if (!dmi_check_system(yb9_kbdock_dmi_table)) > + return -ENODEV; > + > + mutex_lock(&yb9.lock); > + yb9.block_wdev = wdev; > + /* Read initial state if the event device has already probed. */ > + if (yb9.input_dev) { > + bkbd = yb9_kbdock_query_locked(&wdev->dev); > + if (bkbd >= 0) > + yb9_kbdock_report_locked(bkbd, &wdev->dev); > + } > + mutex_unlock(&yb9.lock); More duplication? > + return 0; > +} > + > +static void yb9_kbdock_block_remove(struct wmi_device *wdev) > +{ > + mutex_lock(&yb9.lock); guard() > + yb9.block_wdev = NULL; > + mutex_unlock(&yb9.lock); > +} > + > +static const struct wmi_device_id yb9_kbdock_block_id_table[] = { > + { .guid_string = YB9_KBDOCK_QUERY_GUID }, > + { } > +}; > + > +static struct wmi_driver yb9_kbdock_block_driver = { > + .driver = { > + .name = "lenovo-yb9-kbdock-block", > + }, > + .id_table = yb9_kbdock_block_id_table, > + .no_singleton = true, > + .probe = yb9_kbdock_block_probe, > + .remove = yb9_kbdock_block_remove, > +}; > + > +/* ------------------------------------------------------------------ > + * Module init / exit > + * ------------------------------------------------------------------ */ > + > +static int __init yb9_kbdock_init(void) > +{ > + int ret; > + > + mutex_init(&yb9.lock); > + > + ret = wmi_driver_register(&yb9_kbdock_event_driver); > + if (ret) > + return ret; > + > + ret = wmi_driver_register(&yb9_kbdock_block_driver); > + if (ret) { > + wmi_driver_unregister(&yb9_kbdock_event_driver); > + return ret; > + } > + return 0; > +} > +module_init(yb9_kbdock_init); > + > +static void __exit yb9_kbdock_exit(void) > +{ > + wmi_driver_unregister(&yb9_kbdock_block_driver); > + wmi_driver_unregister(&yb9_kbdock_event_driver); > +} > +module_exit(yb9_kbdock_exit); > + > +MODULE_AUTHOR("Dave Carey <carvsdriver@gmail.com>"); > +MODULE_DESCRIPTION("Lenovo Yoga Book 9 keyboard dock detection"); > +MODULE_LICENSE("GPL"); > -- > 2.54.0 > -- i. ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v7 0/2] platform/x86/lenovo: Add Yoga Book 9 keyboard dock detection driver 2026-06-08 21:52 ` [PATCH v6 0/2] " Dave Carey 2026-06-08 21:52 ` [PATCH v6 1/2] platform/x86/lenovo: lenovo-ymc: Suppress probe on Yoga Book 9 14IAH10 Dave Carey 2026-06-08 21:52 ` [PATCH v6 2/2] platform/x86/lenovo: Add Yoga Book 9 keyboard dock detection driver Dave Carey @ 2026-06-09 21:26 ` Dave Carey 2026-06-09 21:26 ` [PATCH v7 1/2] platform/x86/lenovo: lenovo-ymc: Suppress probe on Yoga Book 9 14IAH10 Dave Carey 2026-06-09 21:26 ` [PATCH v7 2/2] platform/x86/lenovo: Add Yoga Book 9 keyboard dock detection driver Dave Carey 2 siblings, 2 replies; 20+ messages in thread From: Dave Carey @ 2026-06-09 21:26 UTC (permalink / raw) To: platform-driver-x86 Cc: johannes.goede, mpearson-lenovo, ilpo.jarvinen, armin.wolf, linux-kernel, Dave Carey This series adds keyboard dock detection for the Lenovo Yoga Book 9 14IAH10 and fixes a pre-existing SW_TABLET_MODE conflict that the new driver exposes. Patch 1/2 fixes lenovo-ymc: it registers an input node advertising SW_TABLET_MODE on the Yoga Book 9, but so does patch 2/2. Userspace (GNOME) reads SW_TABLET_MODE at login from every input node that advertises the capability and does not tolerate two such nodes. The fix returns -ENODEV in lenovo-ymc probe() for this hardware; the ymc_ec_trigger EC write is separately gated by a DMI table that already excludes this machine, so no other functionality is affected. Patch 2/2 is the keyboard dock driver. Changes since v6: - Use wmidev_query_block() in place of deprecated wmidev_block_query(). Use __free(kfree) to manage the output buffer; simplify the parsing to a single length check now that the WMI core handles type conversion. - Define BKBD_FIELD with GENMASK() and extract the field with FIELD_GET() instead of open-coded masking. - Use guard(mutex) for all lock/unlock pairs. - Add yb9_kbdock_sync_locked() helper to consolidate the duplicated initial-state read pattern in event_probe, block_probe, and resume. - Add linux/bitfield.h, linux/cleanup.h, linux/slab.h includes. - Fix comment on struct mutex field; use Context:/Returns: sections in the kerneldoc comment for yb9_kbdock_query_locked(). Dave Carey (2): platform/x86/lenovo: lenovo-ymc: Suppress probe on Yoga Book 9 14IAH10 platform/x86/lenovo: Add Yoga Book 9 keyboard dock detection driver .../testing/sysfs-driver-lenovo-yb9-kbdock | 19 + MAINTAINERS | 7 + drivers/platform/x86/lenovo/Kconfig | 14 + drivers/platform/x86/lenovo/Makefile | 1 + drivers/platform/x86/lenovo/yb9-kbdock.c | 317 ++++++++++++++++++ drivers/platform/x86/lenovo/ymc.c | 19 +++++++++++++++ 6 files changed, 377 insertions(+) -- 2.54.0 ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v7 1/2] platform/x86/lenovo: lenovo-ymc: Suppress probe on Yoga Book 9 14IAH10 2026-06-09 21:26 ` [PATCH v7 0/2] " Dave Carey @ 2026-06-09 21:26 ` Dave Carey 2026-06-10 8:00 ` johannes.goede 2026-06-09 21:26 ` [PATCH v7 2/2] platform/x86/lenovo: Add Yoga Book 9 keyboard dock detection driver Dave Carey 1 sibling, 1 reply; 20+ messages in thread From: Dave Carey @ 2026-06-09 21:26 UTC (permalink / raw) To: platform-driver-x86 Cc: johannes.goede, mpearson-lenovo, ilpo.jarvinen, armin.wolf, linux-kernel, Dave Carey The Yoga Book 9 14IAH10 (DMI product name "83KJ") has a dedicated yb9-kbdock WMI driver that registers an input device reporting SW_TABLET_MODE to track the detachable Bluetooth keyboard. lenovo-ymc also loads on this machine and creates an input node with the SW_TABLET_MODE capability bit set. For input switches, the presence of the capability bit has semantic meaning: userspace (e.g. GNOME) reads the switch state at startup from every node advertising the capability and does not expect more than one such node. Add a DMI match for the Yoga Book 9 14IAH10 to probe() so that lenovo-ymc returns -ENODEV on this hardware, leaving yb9-kbdock as the sole SW_TABLET_MODE source. The ymc_ec_trigger EC write, the only other action taken in response to a YMC event, is guarded by a separate DMI table that excludes this machine; no other functionality is affected. Signed-off-by: Dave Carey <carvsdriver@gmail.com> --- drivers/platform/x86/lenovo/ymc.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/drivers/platform/x86/lenovo/ymc.c b/drivers/platform/x86/lenovo/ymc.c --- a/drivers/platform/x86/lenovo/ymc.c +++ b/drivers/platform/x86/lenovo/ymc.c @@ -23,7 +23,23 @@ module_param(force, bool, 0444); static bool force; module_param(force, bool, 0444); MODULE_PARM_DESC(force, "Force loading on boards without a convertible DMI chassis-type"); +static const struct dmi_system_id lenovo_ymc_nosupport_dmi_table[] = { + { + /* + * Yoga Book 9 14IAH10: SW_TABLET_MODE is reported by the + * yb9-kbdock driver. Suppress lenovo-ymc on this machine to + * avoid userspace seeing two input nodes that both advertise + * the SW_TABLET_MODE capability. + */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "83KJ"), + }, + }, + { } +}; + static const struct dmi_system_id allowed_chasis_types_dmi_table[] = { { .matches = { @@ -100,7 +116,10 @@ static int lenovo_ymc_probe(struct wmi_device *wdev, const void *ctx) struct lenovo_ymc_private *priv; struct input_dev *input_dev; int err; + if (dmi_check_system(lenovo_ymc_nosupport_dmi_table)) + return -ENODEV; + if (!dmi_check_system(allowed_chasis_types_dmi_table)) { if (force) dev_info(&wdev->dev, "Force loading Lenovo YMC support\n"); -- 2.54.0 ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v7 1/2] platform/x86/lenovo: lenovo-ymc: Suppress probe on Yoga Book 9 14IAH10 2026-06-09 21:26 ` [PATCH v7 1/2] platform/x86/lenovo: lenovo-ymc: Suppress probe on Yoga Book 9 14IAH10 Dave Carey @ 2026-06-10 8:00 ` johannes.goede 0 siblings, 0 replies; 20+ messages in thread From: johannes.goede @ 2026-06-10 8:00 UTC (permalink / raw) To: Dave Carey, platform-driver-x86 Cc: mpearson-lenovo, ilpo.jarvinen, armin.wolf, linux-kernel Hi, On 9-Jun-26 23:26, Dave Carey wrote: > The Yoga Book 9 14IAH10 (DMI product name "83KJ") has a dedicated > yb9-kbdock WMI driver that registers an input device reporting > SW_TABLET_MODE to track the detachable Bluetooth keyboard. > > lenovo-ymc also loads on this machine and creates an input node with the > SW_TABLET_MODE capability bit set. For input switches, the presence of > the capability bit has semantic meaning: userspace (e.g. GNOME) reads > the switch state at startup from every node advertising the capability > and does not expect more than one such node. > > Add a DMI match for the Yoga Book 9 14IAH10 to probe() so that > lenovo-ymc returns -ENODEV on this hardware, leaving yb9-kbdock as the > sole SW_TABLET_MODE source. The ymc_ec_trigger EC write, the only > other action taken in response to a YMC event, is guarded by a separate > DMI table that excludes this machine; no other functionality is affected. > > Signed-off-by: Dave Carey <carvsdriver@gmail.com> Thanks, patch looks good to me: Reviewed-by: Hans de Goede <johannes.goede@oss.qualcomm.com> Dave, note it is normal to add received tags like my Reviewed-by above to new version of the series, just above your Signed-off-by. With the exception being to drop Reviewed / Acked tags when there are a lot of changes to patch. Regards, Hans > --- > drivers/platform/x86/lenovo/ymc.c | 19 +++++++++++++++++++ > 1 file changed, 19 insertions(+) > > diff --git a/drivers/platform/x86/lenovo/ymc.c b/drivers/platform/x86/lenovo/ymc.c > --- a/drivers/platform/x86/lenovo/ymc.c > +++ b/drivers/platform/x86/lenovo/ymc.c > @@ -23,7 +23,23 @@ module_param(force, bool, 0444); > static bool force; > module_param(force, bool, 0444); > MODULE_PARM_DESC(force, "Force loading on boards without a convertible DMI chassis-type"); > > +static const struct dmi_system_id lenovo_ymc_nosupport_dmi_table[] = { > + { > + /* > + * Yoga Book 9 14IAH10: SW_TABLET_MODE is reported by the > + * yb9-kbdock driver. Suppress lenovo-ymc on this machine to > + * avoid userspace seeing two input nodes that both advertise > + * the SW_TABLET_MODE capability. > + */ > + .matches = { > + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), > + DMI_MATCH(DMI_PRODUCT_NAME, "83KJ"), > + }, > + }, > + { } > +}; > + > static const struct dmi_system_id allowed_chasis_types_dmi_table[] = { > { > .matches = { > @@ -100,7 +116,10 @@ static int lenovo_ymc_probe(struct wmi_device *wdev, const void *ctx) > struct lenovo_ymc_private *priv; > struct input_dev *input_dev; > int err; > > + if (dmi_check_system(lenovo_ymc_nosupport_dmi_table)) > + return -ENODEV; > + > if (!dmi_check_system(allowed_chasis_types_dmi_table)) { > if (force) > dev_info(&wdev->dev, "Force loading Lenovo YMC support\n"); > -- > 2.54.0 ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v7 2/2] platform/x86/lenovo: Add Yoga Book 9 keyboard dock detection driver 2026-06-09 21:26 ` [PATCH v7 0/2] " Dave Carey 2026-06-09 21:26 ` [PATCH v7 1/2] platform/x86/lenovo: lenovo-ymc: Suppress probe on Yoga Book 9 14IAH10 Dave Carey @ 2026-06-09 21:26 ` Dave Carey 2026-06-10 8:36 ` johannes.goede 2026-06-10 8:44 ` Ilpo Järvinen 1 sibling, 2 replies; 20+ messages in thread From: Dave Carey @ 2026-06-09 21:26 UTC (permalink / raw) To: platform-driver-x86 Cc: johannes.goede, mpearson-lenovo, ilpo.jarvinen, armin.wolf, linux-kernel, Dave Carey The Lenovo Yoga Book 9 14IAH10 ships with a detachable Bluetooth keyboard that magnetically attaches to the bottom (secondary) screen in one of two positions. The Embedded Controller tracks the attachment state in a 2-bit field called BKBD and signals changes via WMI event GUID 806BD2A2-177B-481D-BFB5-3BA0BB4A2285 (notify ID 0xEB on the WM10 ACPI device, _UID "GMZN"). The device contains embedded BMOF data (WQDD, 20705 bytes) documenting both WMI interfaces used by this driver: LENOVO_BTKBD_EVENT (event GUID): WmiDataId(1) uint32 Status. The ACPI _WED(0xEB) method returns EC.BKBD directly as an integer, so the notify callback receives BKBD without a separate query. LENOVO_FEATURE_STATUS_DATA (block GUID, WQAF method): returns an 8-byte buffer {uint32 IDs=0x00060000, uint32 Status=BKBD}. Used for the initial state read on probe and after resume. BKBD encoding: 0 = keyboard detached 1 = keyboard docked on top half of bottom screen 2 = keyboard docked on bottom half of bottom screen 3 = reserved (not observed in practice) This driver: - Registers two WMI drivers: one on the event GUID (LENOVO_BTKBD_EVENT) and one on the block GUID (LENOVO_FEATURE_STATUS_DATA). - On probe, reads initial BKBD state via wmidev_query_block() on the block device; whichever driver probes last triggers the initial read. - On WMI notification, reads BKBD directly from the event data integer (ACPI _WED(0xEB) returns EC.BKBD) without a redundant WQAF call. - Reports SW_TABLET_MODE=1 when detached, SW_TABLET_MODE=0 when docked in either position (a physical keyboard is present in both cases). - Exposes the raw BKBD value via read-only sysfs attribute "keyboard_position". - Re-reads BKBD state on resume from suspend or hibernation. Tested on: Lenovo Yoga Book 9 14IAH10 (model 83KJ), kernel 7.0. Signed-off-by: Dave Carey <carvsdriver@gmail.com> --- v7: - Use wmidev_query_block() in place of deprecated wmidev_block_query(). Use __free(kfree) to manage the output buffer; simplify the parsing to a single length check now that the WMI core handles type conversion. - Define BKBD_FIELD with GENMASK() and extract the field with FIELD_GET() instead of open-coded masking. - Use guard(mutex) for all lock/unlock pairs. - Add yb9_kbdock_sync_locked() helper to consolidate the duplicated initial-state read pattern in event_probe, block_probe, and resume. - Add linux/bitfield.h, linux/cleanup.h, linux/slab.h includes. - Fix comment on struct mutex field; use Context:/Returns: sections in the kerneldoc comment for yb9_kbdock_query_locked(). v6: - Submitted as 2/2; patch 1/2 adds a DMI early-exit to lenovo-ymc to prevent duplicate SW_TABLET_MODE input nodes on the YB9. v5: - Rewrote as two WMI drivers (event + block) to avoid the deprecated wmi_query_block() API: event driver owns the GUID that delivers notifications, block driver owns the GUID used for querying state. Either can probe first; both probe callbacks check whether the other has already probed and fire the initial read when both are ready. - Use wmidev_block_query() on the block wmi_device directly (replaces the deprecated wmi_query_block()). - In the notify callback, read BKBD directly from the event data integer passed by the WMI core (_WED(0xEB) returns EC.BKBD; confirmed by decoding the embedded BMOF from WQDD). Eliminates the redundant WQAF call that v4 made on every notification. - Documented the two BMOF classes (LENOVO_BTKBD_EVENT and LENOVO_FEATURE_STATUS_DATA) with field layouts in the commit message and file header. - sysfs keyboard_position: output bare integer, drop the parenthetical position-name suffix that a couple of reviewers found non-standard. - Set .no_singleton = true on both WMI drivers (was missing from block driver in v4). - Add PM resume callback (yb9_kbdock_resume) to re-read BKBD state after suspend/hibernation. v4: - Dropped module_wmi_driver(); registered two WMI drivers manually to allow sharing state. module_init/exit pair registers/unregisters both. - Added ATTRIBUTE_GROUPS() and .dev_groups for the sysfs attribute. - sysfs show: performed a live query instead of returning a cached value. v3: - Switched to devm_input_allocate_device(). - Added DMI guard (dmi_check_system) to reject non-YB9 machines. - Removed redundant MODULE_DEVICE_TABLE on block driver. v2: - Added .no_singleton = true on the event WMI driver. - Added PM suspend/resume skeleton. - Fixed MODULE_LICENSE to "GPL" (was "GPL v2"). .../testing/sysfs-driver-lenovo-yb9-kbdock | 19 + MAINTAINERS | 7 + drivers/platform/x86/lenovo/Kconfig | 14 + drivers/platform/x86/lenovo/Makefile | 1 + drivers/platform/x86/lenovo/yb9-kbdock.c | 317 ++++++++++++++++++ 5 files changed, 358 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock create mode 100644 drivers/platform/x86/lenovo/yb9-kbdock.c diff --git a/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock b/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock new file mode 100644 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock @@ -0,0 +1,19 @@ +What: /sys/bus/wmi/drivers/lenovo-yb9-kbdock/<guid>/keyboard_position +Date: April 2026 +KernelVersion: 6.10 +Contact: Dave Carey <carvsdriver@gmail.com> +Description: + Read-only attribute reporting the current keyboard dock position + as reported by the Embedded Controller on the Lenovo Yoga Book 9 + 14IAH10. + + Possible values: + + == ============================================================= + 0 keyboard is not docked to any screen (detached) + 1 keyboard docked on the top half of the bottom screen + 2 keyboard docked on the bottom half of the bottom screen + == ============================================================= + + SW_TABLET_MODE input events are also emitted: 0 when the keyboard + is docked (either position), 1 when detached. diff --git a/MAINTAINERS b/MAINTAINERS --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14479,6 +14479,13 @@ L: platform-driver-x86@vger.kernel.org S: Maintained F: drivers/platform/x86/lenovo/wmi-hotkey-utilities.c +LENOVO YOGA BOOK 9 KEYBOARD DOCK DRIVER +M: Dave Carey <carvsdriver@gmail.com> +L: platform-driver-x86@vger.kernel.org +S: Maintained +F: Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock +F: drivers/platform/x86/lenovo/yb9-kbdock.c + LETSKETCH HID TABLET DRIVER M: Hans de Goede <hansg@kernel.org> L: linux-input@vger.kernel.org diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/lenovo/Kconfig --- a/drivers/platform/x86/lenovo/Kconfig +++ b/drivers/platform/x86/lenovo/Kconfig @@ -43,6 +43,20 @@ config LENOVO_WMI_CAMERA To compile this driver as a module, choose M here: the module will be called lenovo-wmi-camera. +config LENOVO_YB9_KBDOCK + tristate "Lenovo Yoga Book 9 keyboard dock detection" + depends on ACPI_WMI + depends on DMI + depends on INPUT + help + Say Y here to enable keyboard dock detection on the Lenovo Yoga Book 9 + 14IAH10. The detachable Bluetooth keyboard magnetically attaches to + either screen; this driver reports SW_TABLET_MODE input events based + on the attachment state and exposes the raw position in sysfs. + + To compile this driver as a module, choose M here: the module will be + called lenovo-yb9-kbdock. + config LENOVO_YMC tristate "Lenovo Yoga Tablet Mode Control" depends on ACPI_WMI diff --git a/drivers/platform/x86/lenovo/Makefile b/drivers/platform/x86/lenovo/Makefile --- a/drivers/platform/x86/lenovo/Makefile +++ b/drivers/platform/x86/lenovo/Makefile @@ -8,6 +8,7 @@ obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o lenovo-target-$(CONFIG_LENOVO_WMI_HOTKEY_UTILITIES) += wmi-hotkey-utilities.o +lenovo-target-$(CONFIG_LENOVO_YB9_KBDOCK) += yb9-kbdock.o lenovo-target-$(CONFIG_LENOVO_YMC) += ymc.o lenovo-target-$(CONFIG_YOGABOOK) += yogabook.o lenovo-target-$(CONFIG_YT2_1380) += yoga-tab2-pro-1380-fastcharger.o diff --git a/drivers/platform/x86/lenovo/yb9-kbdock.c b/drivers/platform/x86/lenovo/yb9-kbdock.c new file mode 100644 --- /dev/null +++ b/drivers/platform/x86/lenovo/yb9-kbdock.c @@ -0,0 +1,317 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Lenovo Yoga Book 9 keyboard-dock detection + * + * The Yoga Book 9 ships with a detachable Bluetooth keyboard that magnetically + * attaches to the bottom screen in one of two positions. The EC tracks + * attachment state in a 2-bit field called BKBD and signals changes via WMI + * event 0xEB on the WM10 ACPI device (_UID "GMZN"). + * + * BKBD values: + * 0 = keyboard detached + * 1 = keyboard docked on the top half of the bottom screen + * 2 = keyboard docked on the bottom half of the bottom screen + * 3 = reserved / not observed + * + * Two WMI interfaces are used (documented in embedded BMOF, WQDD, 20705 bytes): + * + * LENOVO_BTKBD_EVENT (event GUID, 806BD2A2-...) + * WmiDataId(1) uint32 Status — _WED(0xEB) returns EC.BKBD directly. + * The notify callback receives BKBD as an integer; no separate query needed. + * + * LENOVO_FEATURE_STATUS_DATA (block GUID, E7F300FA-...) + * WmiDataId(1) uint32 IDs = 0x00060000 (feature selector) + * WmiDataId(2) uint32 Status = BKBD value + * Used on probe and resume to read initial state. + * + * SW_TABLET_MODE=1 is reported when the keyboard is detached; + * SW_TABLET_MODE=0 when docked in either position (keyboard present). + * The raw BKBD value is exposed via the sysfs attribute "keyboard_position". + * + * Copyright (C) 2026 Dave Carey <carvsdriver@gmail.com> + */ + +#include <linux/acpi.h> +#include <linux/bitfield.h> +#include <linux/cleanup.h> +#include <linux/dmi.h> +#include <linux/input.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/pm.h> +#include <linux/slab.h> +#include <linux/wmi.h> + +#define YB9_KBDOCK_EVENT_GUID "806BD2A2-177B-481D-BFB5-3BA0BB4A2285" +#define YB9_KBDOCK_QUERY_GUID "E7F300FA-21CD-4003-ADAC-2696135982E6" + +/* 2-bit EC field encoding the keyboard dock position */ +#define BKBD_FIELD GENMASK(1, 0) + +/* BKBD encoding */ +#define BKBD_DETACHED 0 +#define BKBD_TOP_HALF 1 +#define BKBD_BOTTOM_HALF 2 + +static const struct dmi_system_id yb9_kbdock_dmi_table[] = { + { + /* Lenovo Yoga Book 9 14IAH10 */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "83KJ"), + }, + }, + { } +}; + +/* + * Shared state between the event and block WMI drivers. Protected by lock. + * The lock may be held across wmidev_query_block() calls (which can sleep); + * block_remove() acquires the lock before clearing block_wdev, ensuring + * block_wdev remains valid for the duration of any in-progress query. + */ +static struct { + struct mutex lock; /* protects input_dev and block_wdev */ + struct input_dev *input_dev; /* set by event probe */ + struct wmi_device *block_wdev; /* set by block probe */ +} yb9; + +/** + * yb9_kbdock_query_locked() - Read BKBD from the block device. + * @log_dev: Device for logging. + * + * Context: Caller must hold yb9.lock. + * Returns: 0-3 on success, -errno on failure. + */ +static int yb9_kbdock_query_locked(struct device *log_dev) +{ + struct wmi_buffer out = {}; + void *data __free(kfree) = NULL; + u32 bkbd; + int ret; + + if (!yb9.block_wdev) + return -ENODEV; + + ret = wmidev_query_block(yb9.block_wdev, 0, &out); + if (ret) { + dev_warn(log_dev, "WQAF failed: %d\n", ret); + return ret; + } + + data = out.data; + + /* + * LENOVO_FEATURE_STATUS_DATA: 8-byte buffer {IDs=0x00060000, Status=BKBD}. + * BKBD is at bytes 4-7. + */ + if (out.length < 8) { + dev_warn(log_dev, "WQAF: buffer too short (%zu)\n", out.length); + return -EIO; + } + + memcpy(&bkbd, (u8 *)data + 4, sizeof(bkbd)); + return (int)FIELD_GET(BKBD_FIELD, bkbd); +} + +/* Report SW_TABLET_MODE from BKBD. Caller must hold yb9.lock. */ +static void yb9_kbdock_report_locked(int bkbd, struct device *log_dev) +{ + int tablet = (bkbd == BKBD_DETACHED) ? 1 : 0; + + input_report_switch(yb9.input_dev, SW_TABLET_MODE, tablet); + input_sync(yb9.input_dev); + dev_dbg(log_dev, "BKBD=%d SW_TABLET_MODE=%d\n", bkbd, tablet); +} + +/* Read BKBD and report if both WMI devices are ready. Caller must hold yb9.lock. */ +static void yb9_kbdock_sync_locked(struct device *log_dev) +{ + int bkbd; + + if (!yb9.input_dev || !yb9.block_wdev) + return; + + bkbd = yb9_kbdock_query_locked(log_dev); + if (bkbd >= 0) + yb9_kbdock_report_locked(bkbd, log_dev); +} + +/* ------------------------------------------------------------------ + * sysfs + * ------------------------------------------------------------------ */ + +static ssize_t keyboard_position_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int bkbd; + + guard(mutex)(&yb9.lock); + bkbd = yb9_kbdock_query_locked(dev); + + if (bkbd < 0) + return bkbd; + return sysfs_emit(buf, "%u\n", (unsigned int)bkbd); +} +static DEVICE_ATTR_RO(keyboard_position); + +static struct attribute *yb9_kbdock_attrs[] = { + &dev_attr_keyboard_position.attr, + NULL, +}; +ATTRIBUTE_GROUPS(yb9_kbdock); + +/* ------------------------------------------------------------------ + * Event WMI driver — LENOVO_BTKBD_EVENT + * ------------------------------------------------------------------ */ + +static void yb9_kbdock_notify(struct wmi_device *wdev, union acpi_object *data) +{ + u32 bkbd; + + /* + * _WED(0xEB) returns EC.BKBD directly as an integer + * (LENOVO_BTKBD_EVENT WmiDataId(1) uint32 Status). + */ + if (!data || data->type != ACPI_TYPE_INTEGER) { + dev_warn(&wdev->dev, "unexpected event data type %d\n", + data ? data->type : -1); + return; + } + bkbd = FIELD_GET(BKBD_FIELD, data->integer.value); + + guard(mutex)(&yb9.lock); + if (yb9.input_dev) + yb9_kbdock_report_locked(bkbd, &wdev->dev); +} + +static int yb9_kbdock_event_probe(struct wmi_device *wdev, const void *ctx) +{ + struct input_dev *input_dev; + int err; + + if (!dmi_check_system(yb9_kbdock_dmi_table)) + return -ENODEV; + + input_dev = devm_input_allocate_device(&wdev->dev); + if (!input_dev) + return -ENOMEM; + + input_dev->name = "Lenovo Yoga Book 9 keyboard dock switch"; + input_dev->phys = YB9_KBDOCK_EVENT_GUID "/input0"; + input_dev->id.bustype = BUS_HOST; + input_set_capability(input_dev, EV_SW, SW_TABLET_MODE); + + err = input_register_device(input_dev); + if (err) + return err; + + guard(mutex)(&yb9.lock); + yb9.input_dev = input_dev; + yb9_kbdock_sync_locked(&wdev->dev); + return 0; +} + +static void yb9_kbdock_event_remove(struct wmi_device *wdev) +{ + guard(mutex)(&yb9.lock); + yb9.input_dev = NULL; +} + +static int yb9_kbdock_resume(struct device *dev) +{ + guard(mutex)(&yb9.lock); + yb9_kbdock_sync_locked(dev); + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(yb9_kbdock_pm_ops, NULL, yb9_kbdock_resume); + +static const struct wmi_device_id yb9_kbdock_event_id_table[] = { + { .guid_string = YB9_KBDOCK_EVENT_GUID }, + { } +}; +MODULE_DEVICE_TABLE(wmi, yb9_kbdock_event_id_table); + +static struct wmi_driver yb9_kbdock_event_driver = { + .driver = { + .name = "lenovo-yb9-kbdock", + .dev_groups = yb9_kbdock_groups, + .pm = pm_sleep_ptr(&yb9_kbdock_pm_ops), + }, + .id_table = yb9_kbdock_event_id_table, + .no_singleton = true, + .probe = yb9_kbdock_event_probe, + .remove = yb9_kbdock_event_remove, + .notify = yb9_kbdock_notify, +}; + +/* ------------------------------------------------------------------ + * Block WMI driver — LENOVO_FEATURE_STATUS_DATA + * ------------------------------------------------------------------ */ + +static int yb9_kbdock_block_probe(struct wmi_device *wdev, const void *ctx) +{ + if (!dmi_check_system(yb9_kbdock_dmi_table)) + return -ENODEV; + + guard(mutex)(&yb9.lock); + yb9.block_wdev = wdev; + yb9_kbdock_sync_locked(&wdev->dev); + return 0; +} + +static void yb9_kbdock_block_remove(struct wmi_device *wdev) +{ + guard(mutex)(&yb9.lock); + yb9.block_wdev = NULL; +} + +static const struct wmi_device_id yb9_kbdock_block_id_table[] = { + { .guid_string = YB9_KBDOCK_QUERY_GUID }, + { } +}; + +static struct wmi_driver yb9_kbdock_block_driver = { + .driver = { + .name = "lenovo-yb9-kbdock-block", + }, + .id_table = yb9_kbdock_block_id_table, + .no_singleton = true, + .probe = yb9_kbdock_block_probe, + .remove = yb9_kbdock_block_remove, +}; + +/* ------------------------------------------------------------------ + * Module init / exit + * ------------------------------------------------------------------ */ + +static int __init yb9_kbdock_init(void) +{ + int ret; + + mutex_init(&yb9.lock); + + ret = wmi_driver_register(&yb9_kbdock_event_driver); + if (ret) + return ret; + + ret = wmi_driver_register(&yb9_kbdock_block_driver); + if (ret) { + wmi_driver_unregister(&yb9_kbdock_event_driver); + return ret; + } + return 0; +} +module_init(yb9_kbdock_init); + +static void __exit yb9_kbdock_exit(void) +{ + wmi_driver_unregister(&yb9_kbdock_block_driver); + wmi_driver_unregister(&yb9_kbdock_event_driver); +} +module_exit(yb9_kbdock_exit); + +MODULE_AUTHOR("Dave Carey <carvsdriver@gmail.com>"); +MODULE_DESCRIPTION("Lenovo Yoga Book 9 keyboard dock detection"); +MODULE_LICENSE("GPL"); -- 2.54.0 ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v7 2/2] platform/x86/lenovo: Add Yoga Book 9 keyboard dock detection driver 2026-06-09 21:26 ` [PATCH v7 2/2] platform/x86/lenovo: Add Yoga Book 9 keyboard dock detection driver Dave Carey @ 2026-06-10 8:36 ` johannes.goede 2026-06-10 8:44 ` Ilpo Järvinen 1 sibling, 0 replies; 20+ messages in thread From: johannes.goede @ 2026-06-10 8:36 UTC (permalink / raw) To: Dave Carey, platform-driver-x86 Cc: mpearson-lenovo, ilpo.jarvinen, armin.wolf, linux-kernel Hi, On 9-Jun-26 23:26, Dave Carey wrote: > The Lenovo Yoga Book 9 14IAH10 ships with a detachable Bluetooth keyboard > that magnetically attaches to the bottom (secondary) screen in one of two > positions. The Embedded Controller tracks the attachment state in a 2-bit > field called BKBD and signals changes via WMI event GUID > 806BD2A2-177B-481D-BFB5-3BA0BB4A2285 (notify ID 0xEB on the WM10 ACPI > device, _UID "GMZN"). > > The device contains embedded BMOF data (WQDD, 20705 bytes) documenting > both WMI interfaces used by this driver: > > LENOVO_BTKBD_EVENT (event GUID): WmiDataId(1) uint32 Status. > The ACPI _WED(0xEB) method returns EC.BKBD directly as an integer, > so the notify callback receives BKBD without a separate query. > > LENOVO_FEATURE_STATUS_DATA (block GUID, WQAF method): returns an > 8-byte buffer {uint32 IDs=0x00060000, uint32 Status=BKBD}. > Used for the initial state read on probe and after resume. > > BKBD encoding: > 0 = keyboard detached > 1 = keyboard docked on top half of bottom screen > 2 = keyboard docked on bottom half of bottom screen > 3 = reserved (not observed in practice) > > This driver: > - Registers two WMI drivers: one on the event GUID (LENOVO_BTKBD_EVENT) > and one on the block GUID (LENOVO_FEATURE_STATUS_DATA). > - On probe, reads initial BKBD state via wmidev_query_block() on the > block device; whichever driver probes last triggers the initial read. > - On WMI notification, reads BKBD directly from the event data integer > (ACPI _WED(0xEB) returns EC.BKBD) without a redundant WQAF call. > - Reports SW_TABLET_MODE=1 when detached, SW_TABLET_MODE=0 when docked > in either position (a physical keyboard is present in both cases). > - Exposes the raw BKBD value via read-only sysfs attribute > "keyboard_position". > - Re-reads BKBD state on resume from suspend or hibernation. > > Tested on: Lenovo Yoga Book 9 14IAH10 (model 83KJ), kernel 7.0. > > Signed-off-by: Dave Carey <carvsdriver@gmail.com> For the user-space API and DMI table usage: Acked-by: Hans de Goede <johannes.goede@oss.qualcomm.com> Regards, Hans > --- > v7: > - Use wmidev_query_block() in place of deprecated wmidev_block_query(). > Use __free(kfree) to manage the output buffer; simplify the parsing > to a single length check now that the WMI core handles type conversion. > - Define BKBD_FIELD with GENMASK() and extract the field with FIELD_GET() > instead of open-coded masking. > - Use guard(mutex) for all lock/unlock pairs. > - Add yb9_kbdock_sync_locked() helper to consolidate the duplicated > initial-state read pattern in event_probe, block_probe, and resume. > - Add linux/bitfield.h, linux/cleanup.h, linux/slab.h includes. > - Fix comment on struct mutex field; use Context:/Returns: sections in > the kerneldoc comment for yb9_kbdock_query_locked(). > > v6: > - Submitted as 2/2; patch 1/2 adds a DMI early-exit to lenovo-ymc to > prevent duplicate SW_TABLET_MODE input nodes on the YB9. > > v5: > - Rewrote as two WMI drivers (event + block) to avoid the deprecated > wmi_query_block() API: event driver owns the GUID that delivers > notifications, block driver owns the GUID used for querying state. > Either can probe first; both probe callbacks check whether the other > has already probed and fire the initial read when both are ready. > - Use wmidev_block_query() on the block wmi_device directly (replaces > the deprecated wmi_query_block()). > - In the notify callback, read BKBD directly from the event data integer > passed by the WMI core (_WED(0xEB) returns EC.BKBD; confirmed by > decoding the embedded BMOF from WQDD). Eliminates the redundant WQAF > call that v4 made on every notification. > - Documented the two BMOF classes (LENOVO_BTKBD_EVENT and > LENOVO_FEATURE_STATUS_DATA) with field layouts in the commit message > and file header. > - sysfs keyboard_position: output bare integer, drop the parenthetical > position-name suffix that a couple of reviewers found non-standard. > - Set .no_singleton = true on both WMI drivers (was missing from block > driver in v4). > - Add PM resume callback (yb9_kbdock_resume) to re-read BKBD state > after suspend/hibernation. > > v4: > - Dropped module_wmi_driver(); registered two WMI drivers manually to > allow sharing state. module_init/exit pair registers/unregisters > both. > - Added ATTRIBUTE_GROUPS() and .dev_groups for the sysfs attribute. > - sysfs show: performed a live query instead of returning a cached value. > > v3: > - Switched to devm_input_allocate_device(). > - Added DMI guard (dmi_check_system) to reject non-YB9 machines. > - Removed redundant MODULE_DEVICE_TABLE on block driver. > > v2: > - Added .no_singleton = true on the event WMI driver. > - Added PM suspend/resume skeleton. > - Fixed MODULE_LICENSE to "GPL" (was "GPL v2"). > > .../testing/sysfs-driver-lenovo-yb9-kbdock | 19 + > MAINTAINERS | 7 + > drivers/platform/x86/lenovo/Kconfig | 14 + > drivers/platform/x86/lenovo/Makefile | 1 + > drivers/platform/x86/lenovo/yb9-kbdock.c | 317 ++++++++++++++++++ > 5 files changed, 358 insertions(+) > create mode 100644 Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock > create mode 100644 drivers/platform/x86/lenovo/yb9-kbdock.c > > diff --git a/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock b/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock > new file mode 100644 > --- /dev/null > +++ b/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock > @@ -0,0 +1,19 @@ > +What: /sys/bus/wmi/drivers/lenovo-yb9-kbdock/<guid>/keyboard_position > +Date: April 2026 > +KernelVersion: 6.10 > +Contact: Dave Carey <carvsdriver@gmail.com> > +Description: > + Read-only attribute reporting the current keyboard dock position > + as reported by the Embedded Controller on the Lenovo Yoga Book 9 > + 14IAH10. > + > + Possible values: > + > + == ============================================================= > + 0 keyboard is not docked to any screen (detached) > + 1 keyboard docked on the top half of the bottom screen > + 2 keyboard docked on the bottom half of the bottom screen > + == ============================================================= > + > + SW_TABLET_MODE input events are also emitted: 0 when the keyboard > + is docked (either position), 1 when detached. > diff --git a/MAINTAINERS b/MAINTAINERS > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -14479,6 +14479,13 @@ L: platform-driver-x86@vger.kernel.org > S: Maintained > F: drivers/platform/x86/lenovo/wmi-hotkey-utilities.c > > +LENOVO YOGA BOOK 9 KEYBOARD DOCK DRIVER > +M: Dave Carey <carvsdriver@gmail.com> > +L: platform-driver-x86@vger.kernel.org > +S: Maintained > +F: Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock > +F: drivers/platform/x86/lenovo/yb9-kbdock.c > + > LETSKETCH HID TABLET DRIVER > M: Hans de Goede <hansg@kernel.org> > L: linux-input@vger.kernel.org > diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/lenovo/Kconfig > --- a/drivers/platform/x86/lenovo/Kconfig > +++ b/drivers/platform/x86/lenovo/Kconfig > @@ -43,6 +43,20 @@ config LENOVO_WMI_CAMERA > To compile this driver as a module, choose M here: the module > will be called lenovo-wmi-camera. > > +config LENOVO_YB9_KBDOCK > + tristate "Lenovo Yoga Book 9 keyboard dock detection" > + depends on ACPI_WMI > + depends on DMI > + depends on INPUT > + help > + Say Y here to enable keyboard dock detection on the Lenovo Yoga Book 9 > + 14IAH10. The detachable Bluetooth keyboard magnetically attaches to > + either screen; this driver reports SW_TABLET_MODE input events based > + on the attachment state and exposes the raw position in sysfs. > + > + To compile this driver as a module, choose M here: the module will be > + called lenovo-yb9-kbdock. > + > config LENOVO_YMC > tristate "Lenovo Yoga Tablet Mode Control" > depends on ACPI_WMI > diff --git a/drivers/platform/x86/lenovo/Makefile b/drivers/platform/x86/lenovo/Makefile > --- a/drivers/platform/x86/lenovo/Makefile > +++ b/drivers/platform/x86/lenovo/Makefile > @@ -8,6 +8,7 @@ obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o > obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o > > lenovo-target-$(CONFIG_LENOVO_WMI_HOTKEY_UTILITIES) += wmi-hotkey-utilities.o > +lenovo-target-$(CONFIG_LENOVO_YB9_KBDOCK) += yb9-kbdock.o > lenovo-target-$(CONFIG_LENOVO_YMC) += ymc.o > lenovo-target-$(CONFIG_YOGABOOK) += yogabook.o > lenovo-target-$(CONFIG_YT2_1380) += yoga-tab2-pro-1380-fastcharger.o > diff --git a/drivers/platform/x86/lenovo/yb9-kbdock.c b/drivers/platform/x86/lenovo/yb9-kbdock.c > new file mode 100644 > --- /dev/null > +++ b/drivers/platform/x86/lenovo/yb9-kbdock.c > @@ -0,0 +1,317 @@ > +// SPDX-License-Identifier: GPL-2.0-or-later > +/* > + * Lenovo Yoga Book 9 keyboard-dock detection > + * > + * The Yoga Book 9 ships with a detachable Bluetooth keyboard that magnetically > + * attaches to the bottom screen in one of two positions. The EC tracks > + * attachment state in a 2-bit field called BKBD and signals changes via WMI > + * event 0xEB on the WM10 ACPI device (_UID "GMZN"). > + * > + * BKBD values: > + * 0 = keyboard detached > + * 1 = keyboard docked on the top half of the bottom screen > + * 2 = keyboard docked on the bottom half of the bottom screen > + * 3 = reserved / not observed > + * > + * Two WMI interfaces are used (documented in embedded BMOF, WQDD, 20705 bytes): > + * > + * LENOVO_BTKBD_EVENT (event GUID, 806BD2A2-...) > + * WmiDataId(1) uint32 Status — _WED(0xEB) returns EC.BKBD directly. > + * The notify callback receives BKBD as an integer; no separate query needed. > + * > + * LENOVO_FEATURE_STATUS_DATA (block GUID, E7F300FA-...) > + * WmiDataId(1) uint32 IDs = 0x00060000 (feature selector) > + * WmiDataId(2) uint32 Status = BKBD value > + * Used on probe and resume to read initial state. > + * > + * SW_TABLET_MODE=1 is reported when the keyboard is detached; > + * SW_TABLET_MODE=0 when docked in either position (keyboard present). > + * The raw BKBD value is exposed via the sysfs attribute "keyboard_position". > + * > + * Copyright (C) 2026 Dave Carey <carvsdriver@gmail.com> > + */ > + > +#include <linux/acpi.h> > +#include <linux/bitfield.h> > +#include <linux/cleanup.h> > +#include <linux/dmi.h> > +#include <linux/input.h> > +#include <linux/module.h> > +#include <linux/mutex.h> > +#include <linux/pm.h> > +#include <linux/slab.h> > +#include <linux/wmi.h> > + > +#define YB9_KBDOCK_EVENT_GUID "806BD2A2-177B-481D-BFB5-3BA0BB4A2285" > +#define YB9_KBDOCK_QUERY_GUID "E7F300FA-21CD-4003-ADAC-2696135982E6" > + > +/* 2-bit EC field encoding the keyboard dock position */ > +#define BKBD_FIELD GENMASK(1, 0) > + > +/* BKBD encoding */ > +#define BKBD_DETACHED 0 > +#define BKBD_TOP_HALF 1 > +#define BKBD_BOTTOM_HALF 2 > + > +static const struct dmi_system_id yb9_kbdock_dmi_table[] = { > + { > + /* Lenovo Yoga Book 9 14IAH10 */ > + .matches = { > + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), > + DMI_MATCH(DMI_PRODUCT_NAME, "83KJ"), > + }, > + }, > + { } > +}; > + > +/* > + * Shared state between the event and block WMI drivers. Protected by lock. > + * The lock may be held across wmidev_query_block() calls (which can sleep); > + * block_remove() acquires the lock before clearing block_wdev, ensuring > + * block_wdev remains valid for the duration of any in-progress query. > + */ > +static struct { > + struct mutex lock; /* protects input_dev and block_wdev */ > + struct input_dev *input_dev; /* set by event probe */ > + struct wmi_device *block_wdev; /* set by block probe */ > +} yb9; > + > +/** > + * yb9_kbdock_query_locked() - Read BKBD from the block device. > + * @log_dev: Device for logging. > + * > + * Context: Caller must hold yb9.lock. > + * Returns: 0-3 on success, -errno on failure. > + */ > +static int yb9_kbdock_query_locked(struct device *log_dev) > +{ > + struct wmi_buffer out = {}; > + void *data __free(kfree) = NULL; > + u32 bkbd; > + int ret; > + > + if (!yb9.block_wdev) > + return -ENODEV; > + > + ret = wmidev_query_block(yb9.block_wdev, 0, &out); > + if (ret) { > + dev_warn(log_dev, "WQAF failed: %d\n", ret); > + return ret; > + } > + > + data = out.data; > + > + /* > + * LENOVO_FEATURE_STATUS_DATA: 8-byte buffer {IDs=0x00060000, Status=BKBD}. > + * BKBD is at bytes 4-7. > + */ > + if (out.length < 8) { > + dev_warn(log_dev, "WQAF: buffer too short (%zu)\n", out.length); > + return -EIO; > + } > + > + memcpy(&bkbd, (u8 *)data + 4, sizeof(bkbd)); > + return (int)FIELD_GET(BKBD_FIELD, bkbd); > +} > + > +/* Report SW_TABLET_MODE from BKBD. Caller must hold yb9.lock. */ > +static void yb9_kbdock_report_locked(int bkbd, struct device *log_dev) > +{ > + int tablet = (bkbd == BKBD_DETACHED) ? 1 : 0; > + > + input_report_switch(yb9.input_dev, SW_TABLET_MODE, tablet); > + input_sync(yb9.input_dev); > + dev_dbg(log_dev, "BKBD=%d SW_TABLET_MODE=%d\n", bkbd, tablet); > +} > + > +/* Read BKBD and report if both WMI devices are ready. Caller must hold yb9.lock. */ > +static void yb9_kbdock_sync_locked(struct device *log_dev) > +{ > + int bkbd; > + > + if (!yb9.input_dev || !yb9.block_wdev) > + return; > + > + bkbd = yb9_kbdock_query_locked(log_dev); > + if (bkbd >= 0) > + yb9_kbdock_report_locked(bkbd, log_dev); > +} > + > +/* ------------------------------------------------------------------ > + * sysfs > + * ------------------------------------------------------------------ */ > + > +static ssize_t keyboard_position_show(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + int bkbd; > + > + guard(mutex)(&yb9.lock); > + bkbd = yb9_kbdock_query_locked(dev); > + > + if (bkbd < 0) > + return bkbd; > + return sysfs_emit(buf, "%u\n", (unsigned int)bkbd); > +} > +static DEVICE_ATTR_RO(keyboard_position); > + > +static struct attribute *yb9_kbdock_attrs[] = { > + &dev_attr_keyboard_position.attr, > + NULL, > +}; > +ATTRIBUTE_GROUPS(yb9_kbdock); > + > +/* ------------------------------------------------------------------ > + * Event WMI driver — LENOVO_BTKBD_EVENT > + * ------------------------------------------------------------------ */ > + > +static void yb9_kbdock_notify(struct wmi_device *wdev, union acpi_object *data) > +{ > + u32 bkbd; > + > + /* > + * _WED(0xEB) returns EC.BKBD directly as an integer > + * (LENOVO_BTKBD_EVENT WmiDataId(1) uint32 Status). > + */ > + if (!data || data->type != ACPI_TYPE_INTEGER) { > + dev_warn(&wdev->dev, "unexpected event data type %d\n", > + data ? data->type : -1); > + return; > + } > + bkbd = FIELD_GET(BKBD_FIELD, data->integer.value); > + > + guard(mutex)(&yb9.lock); > + if (yb9.input_dev) > + yb9_kbdock_report_locked(bkbd, &wdev->dev); > +} > + > +static int yb9_kbdock_event_probe(struct wmi_device *wdev, const void *ctx) > +{ > + struct input_dev *input_dev; > + int err; > + > + if (!dmi_check_system(yb9_kbdock_dmi_table)) > + return -ENODEV; > + > + input_dev = devm_input_allocate_device(&wdev->dev); > + if (!input_dev) > + return -ENOMEM; > + > + input_dev->name = "Lenovo Yoga Book 9 keyboard dock switch"; > + input_dev->phys = YB9_KBDOCK_EVENT_GUID "/input0"; > + input_dev->id.bustype = BUS_HOST; > + input_set_capability(input_dev, EV_SW, SW_TABLET_MODE); > + > + err = input_register_device(input_dev); > + if (err) > + return err; > + > + guard(mutex)(&yb9.lock); > + yb9.input_dev = input_dev; > + yb9_kbdock_sync_locked(&wdev->dev); > + return 0; > +} > + > +static void yb9_kbdock_event_remove(struct wmi_device *wdev) > +{ > + guard(mutex)(&yb9.lock); > + yb9.input_dev = NULL; > +} > + > +static int yb9_kbdock_resume(struct device *dev) > +{ > + guard(mutex)(&yb9.lock); > + yb9_kbdock_sync_locked(dev); > + return 0; > +} > + > +static DEFINE_SIMPLE_DEV_PM_OPS(yb9_kbdock_pm_ops, NULL, yb9_kbdock_resume); > + > +static const struct wmi_device_id yb9_kbdock_event_id_table[] = { > + { .guid_string = YB9_KBDOCK_EVENT_GUID }, > + { } > +}; > +MODULE_DEVICE_TABLE(wmi, yb9_kbdock_event_id_table); > + > +static struct wmi_driver yb9_kbdock_event_driver = { > + .driver = { > + .name = "lenovo-yb9-kbdock", > + .dev_groups = yb9_kbdock_groups, > + .pm = pm_sleep_ptr(&yb9_kbdock_pm_ops), > + }, > + .id_table = yb9_kbdock_event_id_table, > + .no_singleton = true, > + .probe = yb9_kbdock_event_probe, > + .remove = yb9_kbdock_event_remove, > + .notify = yb9_kbdock_notify, > +}; > + > +/* ------------------------------------------------------------------ > + * Block WMI driver — LENOVO_FEATURE_STATUS_DATA > + * ------------------------------------------------------------------ */ > + > +static int yb9_kbdock_block_probe(struct wmi_device *wdev, const void *ctx) > +{ > + if (!dmi_check_system(yb9_kbdock_dmi_table)) > + return -ENODEV; > + > + guard(mutex)(&yb9.lock); > + yb9.block_wdev = wdev; > + yb9_kbdock_sync_locked(&wdev->dev); > + return 0; > +} > + > +static void yb9_kbdock_block_remove(struct wmi_device *wdev) > +{ > + guard(mutex)(&yb9.lock); > + yb9.block_wdev = NULL; > +} > + > +static const struct wmi_device_id yb9_kbdock_block_id_table[] = { > + { .guid_string = YB9_KBDOCK_QUERY_GUID }, > + { } > +}; > + > +static struct wmi_driver yb9_kbdock_block_driver = { > + .driver = { > + .name = "lenovo-yb9-kbdock-block", > + }, > + .id_table = yb9_kbdock_block_id_table, > + .no_singleton = true, > + .probe = yb9_kbdock_block_probe, > + .remove = yb9_kbdock_block_remove, > +}; > + > +/* ------------------------------------------------------------------ > + * Module init / exit > + * ------------------------------------------------------------------ */ > + > +static int __init yb9_kbdock_init(void) > +{ > + int ret; > + > + mutex_init(&yb9.lock); > + > + ret = wmi_driver_register(&yb9_kbdock_event_driver); > + if (ret) > + return ret; > + > + ret = wmi_driver_register(&yb9_kbdock_block_driver); > + if (ret) { > + wmi_driver_unregister(&yb9_kbdock_event_driver); > + return ret; > + } > + return 0; > +} > +module_init(yb9_kbdock_init); > + > +static void __exit yb9_kbdock_exit(void) > +{ > + wmi_driver_unregister(&yb9_kbdock_block_driver); > + wmi_driver_unregister(&yb9_kbdock_event_driver); > +} > +module_exit(yb9_kbdock_exit); > + > +MODULE_AUTHOR("Dave Carey <carvsdriver@gmail.com>"); > +MODULE_DESCRIPTION("Lenovo Yoga Book 9 keyboard dock detection"); > +MODULE_LICENSE("GPL"); > > -- > 2.54.0 ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v7 2/2] platform/x86/lenovo: Add Yoga Book 9 keyboard dock detection driver 2026-06-09 21:26 ` [PATCH v7 2/2] platform/x86/lenovo: Add Yoga Book 9 keyboard dock detection driver Dave Carey 2026-06-10 8:36 ` johannes.goede @ 2026-06-10 8:44 ` Ilpo Järvinen 1 sibling, 0 replies; 20+ messages in thread From: Ilpo Järvinen @ 2026-06-10 8:44 UTC (permalink / raw) To: Dave Carey Cc: platform-driver-x86, johannes.goede, Mark Pearson, armin.wolf, LKML [-- Attachment #1: Type: text/plain, Size: 19886 bytes --] On Tue, 9 Jun 2026, Dave Carey wrote: > The Lenovo Yoga Book 9 14IAH10 ships with a detachable Bluetooth keyboard > that magnetically attaches to the bottom (secondary) screen in one of two > positions. The Embedded Controller tracks the attachment state in a 2-bit > field called BKBD and signals changes via WMI event GUID > 806BD2A2-177B-481D-BFB5-3BA0BB4A2285 (notify ID 0xEB on the WM10 ACPI > device, _UID "GMZN"). > > The device contains embedded BMOF data (WQDD, 20705 bytes) documenting > both WMI interfaces used by this driver: > > LENOVO_BTKBD_EVENT (event GUID): WmiDataId(1) uint32 Status. > The ACPI _WED(0xEB) method returns EC.BKBD directly as an integer, > so the notify callback receives BKBD without a separate query. > > LENOVO_FEATURE_STATUS_DATA (block GUID, WQAF method): returns an > 8-byte buffer {uint32 IDs=0x00060000, uint32 Status=BKBD}. > Used for the initial state read on probe and after resume. > > BKBD encoding: > 0 = keyboard detached > 1 = keyboard docked on top half of bottom screen > 2 = keyboard docked on bottom half of bottom screen > 3 = reserved (not observed in practice) > > This driver: > - Registers two WMI drivers: one on the event GUID (LENOVO_BTKBD_EVENT) > and one on the block GUID (LENOVO_FEATURE_STATUS_DATA). > - On probe, reads initial BKBD state via wmidev_query_block() on the > block device; whichever driver probes last triggers the initial read. > - On WMI notification, reads BKBD directly from the event data integer > (ACPI _WED(0xEB) returns EC.BKBD) without a redundant WQAF call. > - Reports SW_TABLET_MODE=1 when detached, SW_TABLET_MODE=0 when docked > in either position (a physical keyboard is present in both cases). > - Exposes the raw BKBD value via read-only sysfs attribute > "keyboard_position". > - Re-reads BKBD state on resume from suspend or hibernation. > > Tested on: Lenovo Yoga Book 9 14IAH10 (model 83KJ), kernel 7.0. > > Signed-off-by: Dave Carey <carvsdriver@gmail.com> > --- > v7: > - Use wmidev_query_block() in place of deprecated wmidev_block_query(). > Use __free(kfree) to manage the output buffer; simplify the parsing > to a single length check now that the WMI core handles type conversion. > - Define BKBD_FIELD with GENMASK() and extract the field with FIELD_GET() > instead of open-coded masking. > - Use guard(mutex) for all lock/unlock pairs. > - Add yb9_kbdock_sync_locked() helper to consolidate the duplicated > initial-state read pattern in event_probe, block_probe, and resume. > - Add linux/bitfield.h, linux/cleanup.h, linux/slab.h includes. > - Fix comment on struct mutex field; use Context:/Returns: sections in > the kerneldoc comment for yb9_kbdock_query_locked(). > > v6: > - Submitted as 2/2; patch 1/2 adds a DMI early-exit to lenovo-ymc to > prevent duplicate SW_TABLET_MODE input nodes on the YB9. > > v5: > - Rewrote as two WMI drivers (event + block) to avoid the deprecated > wmi_query_block() API: event driver owns the GUID that delivers > notifications, block driver owns the GUID used for querying state. > Either can probe first; both probe callbacks check whether the other > has already probed and fire the initial read when both are ready. > - Use wmidev_block_query() on the block wmi_device directly (replaces > the deprecated wmi_query_block()). > - In the notify callback, read BKBD directly from the event data integer > passed by the WMI core (_WED(0xEB) returns EC.BKBD; confirmed by > decoding the embedded BMOF from WQDD). Eliminates the redundant WQAF > call that v4 made on every notification. > - Documented the two BMOF classes (LENOVO_BTKBD_EVENT and > LENOVO_FEATURE_STATUS_DATA) with field layouts in the commit message > and file header. > - sysfs keyboard_position: output bare integer, drop the parenthetical > position-name suffix that a couple of reviewers found non-standard. > - Set .no_singleton = true on both WMI drivers (was missing from block > driver in v4). > - Add PM resume callback (yb9_kbdock_resume) to re-read BKBD state > after suspend/hibernation. > > v4: > - Dropped module_wmi_driver(); registered two WMI drivers manually to > allow sharing state. module_init/exit pair registers/unregisters > both. > - Added ATTRIBUTE_GROUPS() and .dev_groups for the sysfs attribute. > - sysfs show: performed a live query instead of returning a cached value. > > v3: > - Switched to devm_input_allocate_device(). > - Added DMI guard (dmi_check_system) to reject non-YB9 machines. > - Removed redundant MODULE_DEVICE_TABLE on block driver. > > v2: > - Added .no_singleton = true on the event WMI driver. > - Added PM suspend/resume skeleton. > - Fixed MODULE_LICENSE to "GPL" (was "GPL v2"). > > .../testing/sysfs-driver-lenovo-yb9-kbdock | 19 + > MAINTAINERS | 7 + > drivers/platform/x86/lenovo/Kconfig | 14 + > drivers/platform/x86/lenovo/Makefile | 1 + > drivers/platform/x86/lenovo/yb9-kbdock.c | 317 ++++++++++++++++++ > 5 files changed, 358 insertions(+) > create mode 100644 Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock > create mode 100644 drivers/platform/x86/lenovo/yb9-kbdock.c > > diff --git a/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock b/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock > new file mode 100644 > --- /dev/null > +++ b/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock > @@ -0,0 +1,19 @@ > +What: /sys/bus/wmi/drivers/lenovo-yb9-kbdock/<guid>/keyboard_position > +Date: April 2026 > +KernelVersion: 6.10 > +Contact: Dave Carey <carvsdriver@gmail.com> > +Description: > + Read-only attribute reporting the current keyboard dock position > + as reported by the Embedded Controller on the Lenovo Yoga Book 9 > + 14IAH10. > + > + Possible values: > + > + == ============================================================= > + 0 keyboard is not docked to any screen (detached) > + 1 keyboard docked on the top half of the bottom screen > + 2 keyboard docked on the bottom half of the bottom screen > + == ============================================================= > + > + SW_TABLET_MODE input events are also emitted: 0 when the keyboard > + is docked (either position), 1 when detached. > diff --git a/MAINTAINERS b/MAINTAINERS > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -14479,6 +14479,13 @@ L: platform-driver-x86@vger.kernel.org > S: Maintained > F: drivers/platform/x86/lenovo/wmi-hotkey-utilities.c > > +LENOVO YOGA BOOK 9 KEYBOARD DOCK DRIVER > +M: Dave Carey <carvsdriver@gmail.com> > +L: platform-driver-x86@vger.kernel.org > +S: Maintained > +F: Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock > +F: drivers/platform/x86/lenovo/yb9-kbdock.c > + > LETSKETCH HID TABLET DRIVER > M: Hans de Goede <hansg@kernel.org> > L: linux-input@vger.kernel.org > diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/lenovo/Kconfig > --- a/drivers/platform/x86/lenovo/Kconfig > +++ b/drivers/platform/x86/lenovo/Kconfig > @@ -43,6 +43,20 @@ config LENOVO_WMI_CAMERA > To compile this driver as a module, choose M here: the module > will be called lenovo-wmi-camera. > > +config LENOVO_YB9_KBDOCK > + tristate "Lenovo Yoga Book 9 keyboard dock detection" > + depends on ACPI_WMI > + depends on DMI > + depends on INPUT > + help > + Say Y here to enable keyboard dock detection on the Lenovo Yoga Book 9 > + 14IAH10. The detachable Bluetooth keyboard magnetically attaches to > + either screen; this driver reports SW_TABLET_MODE input events based > + on the attachment state and exposes the raw position in sysfs. > + > + To compile this driver as a module, choose M here: the module will be > + called lenovo-yb9-kbdock. > + > config LENOVO_YMC > tristate "Lenovo Yoga Tablet Mode Control" > depends on ACPI_WMI > diff --git a/drivers/platform/x86/lenovo/Makefile b/drivers/platform/x86/lenovo/Makefile > --- a/drivers/platform/x86/lenovo/Makefile > +++ b/drivers/platform/x86/lenovo/Makefile > @@ -8,6 +8,7 @@ obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o > obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o > > lenovo-target-$(CONFIG_LENOVO_WMI_HOTKEY_UTILITIES) += wmi-hotkey-utilities.o > +lenovo-target-$(CONFIG_LENOVO_YB9_KBDOCK) += yb9-kbdock.o > lenovo-target-$(CONFIG_LENOVO_YMC) += ymc.o > lenovo-target-$(CONFIG_YOGABOOK) += yogabook.o > lenovo-target-$(CONFIG_YT2_1380) += yoga-tab2-pro-1380-fastcharger.o > diff --git a/drivers/platform/x86/lenovo/yb9-kbdock.c b/drivers/platform/x86/lenovo/yb9-kbdock.c > new file mode 100644 > --- /dev/null > +++ b/drivers/platform/x86/lenovo/yb9-kbdock.c > @@ -0,0 +1,317 @@ > +// SPDX-License-Identifier: GPL-2.0-or-later > +/* > + * Lenovo Yoga Book 9 keyboard-dock detection > + * > + * The Yoga Book 9 ships with a detachable Bluetooth keyboard that magnetically > + * attaches to the bottom screen in one of two positions. The EC tracks > + * attachment state in a 2-bit field called BKBD and signals changes via WMI > + * event 0xEB on the WM10 ACPI device (_UID "GMZN"). > + * > + * BKBD values: > + * 0 = keyboard detached > + * 1 = keyboard docked on the top half of the bottom screen > + * 2 = keyboard docked on the bottom half of the bottom screen > + * 3 = reserved / not observed > + * > + * Two WMI interfaces are used (documented in embedded BMOF, WQDD, 20705 bytes): > + * > + * LENOVO_BTKBD_EVENT (event GUID, 806BD2A2-...) > + * WmiDataId(1) uint32 Status — _WED(0xEB) returns EC.BKBD directly. > + * The notify callback receives BKBD as an integer; no separate query needed. > + * > + * LENOVO_FEATURE_STATUS_DATA (block GUID, E7F300FA-...) > + * WmiDataId(1) uint32 IDs = 0x00060000 (feature selector) > + * WmiDataId(2) uint32 Status = BKBD value > + * Used on probe and resume to read initial state. > + * > + * SW_TABLET_MODE=1 is reported when the keyboard is detached; > + * SW_TABLET_MODE=0 when docked in either position (keyboard present). > + * The raw BKBD value is exposed via the sysfs attribute "keyboard_position". > + * > + * Copyright (C) 2026 Dave Carey <carvsdriver@gmail.com> > + */ > + > +#include <linux/acpi.h> > +#include <linux/bitfield.h> > +#include <linux/cleanup.h> > +#include <linux/dmi.h> > +#include <linux/input.h> > +#include <linux/module.h> > +#include <linux/mutex.h> > +#include <linux/pm.h> > +#include <linux/slab.h> > +#include <linux/wmi.h> > + > +#define YB9_KBDOCK_EVENT_GUID "806BD2A2-177B-481D-BFB5-3BA0BB4A2285" > +#define YB9_KBDOCK_QUERY_GUID "E7F300FA-21CD-4003-ADAC-2696135982E6" > + > +/* 2-bit EC field encoding the keyboard dock position */ > +#define BKBD_FIELD GENMASK(1, 0) Please add #include <linux/bits.h> > + > +/* BKBD encoding */ > +#define BKBD_DETACHED 0 > +#define BKBD_TOP_HALF 1 > +#define BKBD_BOTTOM_HALF 2 > + > +static const struct dmi_system_id yb9_kbdock_dmi_table[] = { > + { > + /* Lenovo Yoga Book 9 14IAH10 */ > + .matches = { > + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), > + DMI_MATCH(DMI_PRODUCT_NAME, "83KJ"), > + }, > + }, > + { } > +}; > + > +/* > + * Shared state between the event and block WMI drivers. Protected by lock. > + * The lock may be held across wmidev_query_block() calls (which can sleep); > + * block_remove() acquires the lock before clearing block_wdev, ensuring > + * block_wdev remains valid for the duration of any in-progress query. > + */ > +static struct { > + struct mutex lock; /* protects input_dev and block_wdev */ > + struct input_dev *input_dev; /* set by event probe */ > + struct wmi_device *block_wdev; /* set by block probe */ > +} yb9; > + > +/** > + * yb9_kbdock_query_locked() - Read BKBD from the block device. > + * @log_dev: Device for logging. > + * > + * Context: Caller must hold yb9.lock. > + * Returns: 0-3 on success, -errno on failure. > + */ > +static int yb9_kbdock_query_locked(struct device *log_dev) > +{ > + struct wmi_buffer out = {}; > + void *data __free(kfree) = NULL; This variable should be declared down below where you assign the real value to it (to understand the reasonale, please see the long comment in cleanup.h). > + u32 bkbd; > + int ret; > + > + if (!yb9.block_wdev) > + return -ENODEV; > + > + ret = wmidev_query_block(yb9.block_wdev, 0, &out); This isn't based on the latest WMI core code? /** * wmidev_query_block - Return contents of a WMI data block * @wdev: A wmi bus device from a driver * @instance: Instance index * @out: WMI buffer to fill * @min_size: Minimum size of the result data in bytes The latest WMI core can also handle size check for you. > + if (ret) { > + dev_warn(log_dev, "WQAF failed: %d\n", ret); Still missing an include for dev_*() printing. > + return ret; > + } > + > + data = out.data; > + > + /* > + * LENOVO_FEATURE_STATUS_DATA: 8-byte buffer {IDs=0x00060000, Status=BKBD}. > + * BKBD is at bytes 4-7. > + */ > + if (out.length < 8) { > + dev_warn(log_dev, "WQAF: buffer too short (%zu)\n", out.length); > + return -EIO; > + } > + > + memcpy(&bkbd, (u8 *)data + 4, sizeof(bkbd)); Why is data void * if you need to do this? > + return (int)FIELD_GET(BKBD_FIELD, bkbd); Unnecessary cast. > +} > + > +/* Report SW_TABLET_MODE from BKBD. Caller must hold yb9.lock. */ > +static void yb9_kbdock_report_locked(int bkbd, struct device *log_dev) > +{ > + int tablet = (bkbd == BKBD_DETACHED) ? 1 : 0; > + > + input_report_switch(yb9.input_dev, SW_TABLET_MODE, tablet); > + input_sync(yb9.input_dev); > + dev_dbg(log_dev, "BKBD=%d SW_TABLET_MODE=%d\n", bkbd, tablet); > +} > + > +/* Read BKBD and report if both WMI devices are ready. Caller must hold yb9.lock. */ > +static void yb9_kbdock_sync_locked(struct device *log_dev) > +{ > + int bkbd; > + > + if (!yb9.input_dev || !yb9.block_wdev) > + return; > + > + bkbd = yb9_kbdock_query_locked(log_dev); > + if (bkbd >= 0) > + yb9_kbdock_report_locked(bkbd, log_dev); > +} > + > +/* ------------------------------------------------------------------ > + * sysfs > + * ------------------------------------------------------------------ */ > + > +static ssize_t keyboard_position_show(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + int bkbd; > + > + guard(mutex)(&yb9.lock); > + bkbd = yb9_kbdock_query_locked(dev); > + > + if (bkbd < 0) Please don't leave empty line between call and it's error handling. > + return bkbd; > + return sysfs_emit(buf, "%u\n", (unsigned int)bkbd); Just print with %d instead to avoid the cast. > +} > +static DEVICE_ATTR_RO(keyboard_position); > + > +static struct attribute *yb9_kbdock_attrs[] = { > + &dev_attr_keyboard_position.attr, > + NULL, > +}; > +ATTRIBUTE_GROUPS(yb9_kbdock); > + > +/* ------------------------------------------------------------------ > + * Event WMI driver — LENOVO_BTKBD_EVENT > + * ------------------------------------------------------------------ */ > + > +static void yb9_kbdock_notify(struct wmi_device *wdev, union acpi_object *data) > +{ > + u32 bkbd; > + > + /* > + * _WED(0xEB) returns EC.BKBD directly as an integer > + * (LENOVO_BTKBD_EVENT WmiDataId(1) uint32 Status). > + */ > + if (!data || data->type != ACPI_TYPE_INTEGER) { > + dev_warn(&wdev->dev, "unexpected event data type %d\n", > + data ? data->type : -1); > + return; > + } > + bkbd = FIELD_GET(BKBD_FIELD, data->integer.value); > + > + guard(mutex)(&yb9.lock); > + if (yb9.input_dev) > + yb9_kbdock_report_locked(bkbd, &wdev->dev); > +} > + > +static int yb9_kbdock_event_probe(struct wmi_device *wdev, const void *ctx) > +{ > + struct input_dev *input_dev; > + int err; > + > + if (!dmi_check_system(yb9_kbdock_dmi_table)) > + return -ENODEV; > + > + input_dev = devm_input_allocate_device(&wdev->dev); > + if (!input_dev) > + return -ENOMEM; > + > + input_dev->name = "Lenovo Yoga Book 9 keyboard dock switch"; > + input_dev->phys = YB9_KBDOCK_EVENT_GUID "/input0"; > + input_dev->id.bustype = BUS_HOST; > + input_set_capability(input_dev, EV_SW, SW_TABLET_MODE); > + > + err = input_register_device(input_dev); > + if (err) > + return err; > + > + guard(mutex)(&yb9.lock); > + yb9.input_dev = input_dev; > + yb9_kbdock_sync_locked(&wdev->dev); > + return 0; > +} > + > +static void yb9_kbdock_event_remove(struct wmi_device *wdev) > +{ > + guard(mutex)(&yb9.lock); > + yb9.input_dev = NULL; > +} > + > +static int yb9_kbdock_resume(struct device *dev) > +{ > + guard(mutex)(&yb9.lock); > + yb9_kbdock_sync_locked(dev); > + return 0; > +} > + > +static DEFINE_SIMPLE_DEV_PM_OPS(yb9_kbdock_pm_ops, NULL, yb9_kbdock_resume); > + > +static const struct wmi_device_id yb9_kbdock_event_id_table[] = { > + { .guid_string = YB9_KBDOCK_EVENT_GUID }, > + { } > +}; > +MODULE_DEVICE_TABLE(wmi, yb9_kbdock_event_id_table); > + > +static struct wmi_driver yb9_kbdock_event_driver = { > + .driver = { > + .name = "lenovo-yb9-kbdock", > + .dev_groups = yb9_kbdock_groups, > + .pm = pm_sleep_ptr(&yb9_kbdock_pm_ops), > + }, > + .id_table = yb9_kbdock_event_id_table, > + .no_singleton = true, > + .probe = yb9_kbdock_event_probe, > + .remove = yb9_kbdock_event_remove, > + .notify = yb9_kbdock_notify, > +}; > + > +/* ------------------------------------------------------------------ > + * Block WMI driver — LENOVO_FEATURE_STATUS_DATA > + * ------------------------------------------------------------------ */ > + > +static int yb9_kbdock_block_probe(struct wmi_device *wdev, const void *ctx) > +{ > + if (!dmi_check_system(yb9_kbdock_dmi_table)) > + return -ENODEV; > + > + guard(mutex)(&yb9.lock); > + yb9.block_wdev = wdev; > + yb9_kbdock_sync_locked(&wdev->dev); > + return 0; > +} > + > +static void yb9_kbdock_block_remove(struct wmi_device *wdev) > +{ > + guard(mutex)(&yb9.lock); > + yb9.block_wdev = NULL; > +} > + > +static const struct wmi_device_id yb9_kbdock_block_id_table[] = { > + { .guid_string = YB9_KBDOCK_QUERY_GUID }, > + { } > +}; > + > +static struct wmi_driver yb9_kbdock_block_driver = { > + .driver = { > + .name = "lenovo-yb9-kbdock-block", > + }, > + .id_table = yb9_kbdock_block_id_table, > + .no_singleton = true, > + .probe = yb9_kbdock_block_probe, > + .remove = yb9_kbdock_block_remove, > +}; > + > +/* ------------------------------------------------------------------ > + * Module init / exit > + * ------------------------------------------------------------------ */ > + > +static int __init yb9_kbdock_init(void) > +{ > + int ret; > + > + mutex_init(&yb9.lock); You'd need a pairing mutex_destroy() but please use devm_mutex_init() instead so don't need to handle it manually (remember to do error handling for devm_mutex_init() as it can fail). > + > + ret = wmi_driver_register(&yb9_kbdock_event_driver); > + if (ret) > + return ret; > + > + ret = wmi_driver_register(&yb9_kbdock_block_driver); > + if (ret) { > + wmi_driver_unregister(&yb9_kbdock_event_driver); > + return ret; > + } > + return 0; > +} > +module_init(yb9_kbdock_init); > + > +static void __exit yb9_kbdock_exit(void) > +{ > + wmi_driver_unregister(&yb9_kbdock_block_driver); > + wmi_driver_unregister(&yb9_kbdock_event_driver); > +} > +module_exit(yb9_kbdock_exit); > + > +MODULE_AUTHOR("Dave Carey <carvsdriver@gmail.com>"); > +MODULE_DESCRIPTION("Lenovo Yoga Book 9 keyboard dock detection"); > +MODULE_LICENSE("GPL"); > > -- > 2.54.0 > -- i. ^ permalink raw reply [flat|nested] 20+ messages in thread
end of thread, other threads:[~2026-06-10 8:44 UTC | newest]
Thread overview: 20+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
[not found] <20260521144034.282419-1-carvsdriver@gmail.com>
2026-05-27 12:27 ` [PATCH v5] platform/x86/lenovo: Add Yoga Book 9 keyboard dock detection driver Dave Carey
2026-06-08 9:13 ` johannes.goede
2026-06-08 12:52 ` Dave Carey
2026-06-08 15:55 ` johannes.goede
2026-06-08 19:05 ` Dave Carey
2026-06-08 19:23 ` johannes.goede
2026-06-08 21:52 ` [PATCH v6 0/2] " Dave Carey
2026-06-08 21:52 ` [PATCH v6 1/2] platform/x86/lenovo: lenovo-ymc: Suppress probe on Yoga Book 9 14IAH10 Dave Carey
2026-06-09 1:29 ` Mark Pearson
2026-06-09 1:29 ` Mark Pearson
2026-06-09 8:50 ` johannes.goede
2026-06-08 21:52 ` [PATCH v6 2/2] platform/x86/lenovo: Add Yoga Book 9 keyboard dock detection driver Dave Carey
2026-06-09 8:50 ` johannes.goede
2026-06-09 9:47 ` Ilpo Järvinen
2026-06-09 21:26 ` [PATCH v7 0/2] " Dave Carey
2026-06-09 21:26 ` [PATCH v7 1/2] platform/x86/lenovo: lenovo-ymc: Suppress probe on Yoga Book 9 14IAH10 Dave Carey
2026-06-10 8:00 ` johannes.goede
2026-06-09 21:26 ` [PATCH v7 2/2] platform/x86/lenovo: Add Yoga Book 9 keyboard dock detection driver Dave Carey
2026-06-10 8:36 ` johannes.goede
2026-06-10 8:44 ` Ilpo Järvinen
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox