From nobody Mon May 25 06:40:19 2026 Received: from mail-qk1-f179.google.com (mail-qk1-f179.google.com [209.85.222.179]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id F2E7432F76D for ; Sun, 17 May 2026 15:02:34 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.222.179 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779030156; cv=none; b=R/8fF7y1yCGVYQ3bLrEPctiLnT9Lx9Sv+unr6Gl5/shlAeSz4rIzp5jXpVkSRUtTDF89F47KPKRIRwEUwkg16Aka6/ShZEfTu2xmJRdNhtiWkazsZnsnNuEDY7+ymOvtaYak6i4DG13VoLpFj0gQyT8U1M3AZZhwGSl7p4rckjs= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779030156; c=relaxed/simple; bh=wxjYC7gAJmXbvVdDxfp/M8UohPrEPkWz1bx2JIWFXQ0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=JMik+O6kqC5fp2tSpTvGiCWBL0SVrFXj2eGPyPVZzFch4KExtNDo+djt/oKJ3xKq5yoYq8Cm3JdPgokakoHkRuV/ZVMS9GK/2c9AhzHmTOQMlBykv5AWlKYrFx6aP9VIA3ApA4W5C0OVglc/D/R1O2iH+qJidqtODpC0Du/CjUU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=JiIJ/pG7; arc=none smtp.client-ip=209.85.222.179 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="JiIJ/pG7" Received: by mail-qk1-f179.google.com with SMTP id af79cd13be357-913cc4d7c71so71959285a.2 for ; Sun, 17 May 2026 08:02:34 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1779030154; x=1779634954; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=FZ5dMrJOu2VDkpK4E4GvRpu/OR2O95cehaSbIxqBQyg=; b=JiIJ/pG7fzdX3v3haG3Gfz/iG9WKz50TKeLk0QWxgXjjRK/3tRgMpJClxu/BCA9YVe iNGX8kJe+g+LvzjPbUhy5ISqe9DJ0bBBhB1ClU9fTTxry/QqBGdopI+J5rdZwLbIC9Sp BAG1qOCYAH9LNICRBwFjQSbvbF1m9ojVveczXKfmxpKBgH9uNotXKXYlftOeo6xE/8x2 d7YxaE7Lh0+a56tEitDwO6i1vyrjxQyUEvw0EEOrrbXwS/yxoEabKAjyyVNm1cT8ePd+ bbWPKe7PuxkzOJPa4A0in86d2EqySj/cY6yctDdjceneDhLFn8IAzXUztHK0HMkRDfW7 h5lA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1779030154; x=1779634954; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=FZ5dMrJOu2VDkpK4E4GvRpu/OR2O95cehaSbIxqBQyg=; b=ma+M3iYLeIikmbo1l/trGjjqWb5tH4y1OHzIU5F4eDDBT8Wyl/3nRhO1tqe93JlaJn UeiitSNE2G/P/HYQr2ECgNCUNCBubwQBS2ZvcTegZhn95mpYq5l2tAcX0s3q9/pw1S8Q HNSTsAdBFME7urXpG2y4Z315I0jvQvaTB3uTtoNKghkVblyIvWkCQiJK13cZa4NPr87a j3+2LT8Jf8bj1ozlrLIhu78aR7G07ufDVWLIXsB4zoRl73TN8CBgWRl3TX6vnjJsKV4g DJr+TOl0kWxhxujf5VZFecjyja5oNUaBRz8eKetHzLCWQzbfEjgyYIw8qIsnjwecOanc 9XWw== X-Forwarded-Encrypted: i=1; AFNElJ90JYCxNYnChLuiaxz7DohhiHozFPcRO+o7oAs7YG4OO19pOPwyjn57yDTjytXxYrnZFI5ySaSW5J8LYiU=@vger.kernel.org X-Gm-Message-State: AOJu0YwnRtJnZ6lPJfIPkV4Ti2B6Uk0DT1M5B2cPWf5EnA2LZFtM7UfH S9V/B2QU/IagM1sSFxTI9RT2pF/JSoGoSQYizVzJnqP0t5gB1DCHsJtp X-Gm-Gg: Acq92OGQQVwGzfUZLHh5vLCeYW0KHjU14LAzFL4dyVDtmtm1GQLzPjBIqgHoRhRwBLW VuR8Dq7lRSxtIidERWuxFpnoyHK/Lh2vCiWwMfw/uMWW0vKaOgn4WmF8C5YBZvNQKXHVg8fU8X7 9su8JOw0hWbAL2QNpndDrzwNOzmYxAlfH30Xk3yOFYcIoxlNoNztQ9PAhT0Zb0jjfAzWanN6cbz hkWrp5AyOgZ4duaywu04pL6zti+l2qxcI2r+di0bsLBOXcL0DoCeucXqKTXcdHs6LGZp/xsHj5o 4nAq69nIM4TDONuWYv3X2vVFFwjYD3Wlgt/flCpmK7jgen980QmlMtOD2+lq1LsD67n/8vtxpX+ plIivs7L/tEK7oHG6YM20zs7OP/KcRQBgOKOkg/Etrju3/Ijxu0xp7vdbGl7iCZyHRTM/kP8P8y oXFgctGjdSLwSSGoXNOuh3fXNtguOKtqBogabaElPJL6fMNzv4QvKhUykF4LOpNlIhyX83 X-Received: by 2002:a05:620a:1a1a:b0:912:c611:80ff with SMTP id af79cd13be357-912c611880cmr1185477885a.52.1779030153734; Sun, 17 May 2026 08:02:33 -0700 (PDT) Received: from fedora (pool-100-11-178-145.phlapa.fios.verizon.net. [100.11.178.145]) by smtp.gmail.com with ESMTPSA id af79cd13be357-910bc936407sm1200753485a.22.2026.05.17.08.02.33 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 17 May 2026 08:02:33 -0700 (PDT) From: Dave Carey To: ilpo.jarvinen@linux.intel.com Cc: hdegoede@redhat.com, pithenrich2d@googlemail.com, mpearson-lenovo@squebb.ca, derekjohn.clark@gmail.com, W_Armin@gmx.de, platform-driver-x86@vger.kernel.org, linux-input@vger.kernel.org, linux-kernel@vger.kernel.org, Dave Carey Subject: [PATCH v2] platform/x86/lenovo: add Yoga Book 9 keyboard dock driver Date: Sun, 17 May 2026 11:02:24 -0400 Message-ID: <20260517150224.50191-1-carvsdriver@gmail.com> X-Mailer: git-send-email 2.54.0 In-Reply-To: <9459f535-d140-a431-3f76-a5d8623f3e2d@linux.intel.com> References: <9459f535-d140-a431-3f76-a5d8623f3e2d@linux.intel.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable The Lenovo Yoga Book 9 14IAH10 (83KJ) ships with a detachable Bluetooth keyboard that magnetically attaches to the bottom screen in one of two positions. The Embedded Controller tracks the attachment state in a 2-bit field called BKBD (byte 0x23 of EC RAM, bits 4-5) and signals changes via EC query _QB0, which calls Notify(WM10, 0xEB) on the WM10 ACPI WMI device (_UID "GMZN"). The WMI event GUID 806BD2A2-177B-481D-BFB5-3BA0BB4A2285 fires on every state change. The current state is readable at any time via WMI data block E7F300FA-21CD-4003-ADAC-2696135982E6 (WQAF), which returns an 8-byte buffer whose upper four bytes hold the BKBD value. BKBD encodes the attachment state as: 0 =3D keyboard detached, 1 =3D docked on the top half of the bott= om screen, 2 =3D docked on the bottom half, 3 =3D reserved (treated as an erro= r by this driver). Add a new driver, lenovo-yb9-kbdock, that queries the BKBD state on probe and on each WMI event notification. Both docked positions indicate a physical keyboard is present and report SW_TABLET_MODE=3D0 (laptop mode); keyboard detached reports SW_TABLET_MODE=3D1 (tablet mode). The raw BKBD value is additionally exposed via the read-only sysfs attribute "keyboard_position" for userspace that needs to distinguish the two docked positions (e.g. to choose a different UI layout). Tested on Lenovo Yoga Book 9 14IAH10 (83KJ): all three BKBD states (detached, top-half dock, bottom-half dock) reported correctly; SW_TABLET_MODE transitions verified with evtest. Signed-off-by: Dave Carey --- drivers/platform/x86/lenovo/Kconfig | 14 ++++ drivers/platform/x86/lenovo/Makefile | 1 + drivers/platform/x86/lenovo/yb9-kbdock.c | 233 +++++++++++++++++++++ 3 files changed, 248 insertions(+) create mode 100644 drivers/platform/x86/lenovo/yb9-kbdock.c --- a/drivers/platform/x86/lenovo/Kconfig +++ b/drivers/platform/x86/lenovo/Kconfig @@ -43,6 +43,20 @@ 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 --- a/drivers/platform/x86/lenovo/Makefile +++ b/drivers/platform/x86/lenovo/Makefile @@ -8,6 +8,7 @@ obj-$(CONFIG_THINKPAD_ACPI) +=3D thinkpad_acpi.o lenovo-target-$(CONFIG_LENOVO_WMI_HOTKEY_UTILITIES) +=3D wmi-hotkey-utilit= ies.o +lenovo-target-$(CONFIG_LENOVO_YB9_KBDOCK) +=3D yb9-kbdock.o lenovo-target-$(CONFIG_LENOVO_YMC) +=3D ymc.o lenovo-target-$(CONFIG_YOGABOOK) +=3D yogabook.o lenovo-target-$(CONFIG_YT2_1380) +=3D yoga-tab2-pro-1380-fastcharger.o --- a/drivers/platform/x86/lenovo/yb9-kbdock.c +++ b/drivers/platform/x86/lenovo/yb9-kbdock.c @@ -0,0 +1,233 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Lenovo Yoga Book 9 keyboard-dock detection + * + * Reports SW_TABLET_MODE based on keyboard attachment state and exposes t= he + * raw dock position via sysfs. + * + * Copyright (C) 2026 Dave Carey + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * WM10 ACPI device (_UID "GMZN") exposes two relevant WMI GUIDs: + * YB9_KBDOCK_EVENT_GUID =E2=80=94 notify ID 0xEB fires on attachment st= ate change. + * YB9_KBDOCK_QUERY_GUID =E2=80=94 object "AF" (WQAF), returns an 8-byte= buffer + * whose upper four bytes hold the BKBD value. + * + * BKBD encoding: + * 0 (BKBD_DETACHED) =E2=80=94 keyboard detached =E2=86=92 SW_T= ABLET_MODE =3D 1 + * 1 (BKBD_TOP_HALF) =E2=80=94 docked, top half =E2=86=92 SW_T= ABLET_MODE =3D 0 + * 2 (BKBD_BOTTOM_HALF) =E2=80=94 docked, bottom half =E2=86=92 SW_T= ABLET_MODE =3D 0 + * 3 =E2=80=94 reserved; treated as an error + */ +#define YB9_KBDOCK_EVENT_GUID "806BD2A2-177B-481D-BFB5-3BA0BB4A2285" +#define YB9_KBDOCK_QUERY_GUID "E7F300FA-21CD-4003-ADAC-2696135982E6" +#define YB9_KBDOCK_QUERY_INSTANCE 0 + +#define BKBD_DETACHED 0 +#define BKBD_TOP_HALF 1 +#define BKBD_BOTTOM_HALF 2 +#define BKBD_MASK GENMASK(1, 0) + +/* Distinguish the two GUIDs via the id_table context field. */ +enum yb9_guid_type { YB9_GUID_EVENT, YB9_GUID_QUERY }; + +/* + * Both GUIDs are children of the same ACPI device (WM10). Store the query + * WMI device globally so the event-device probe and notify path can reach= it + * via wmidev_block_query(). Protected by yb9_query_lock during probe/rem= ove. + */ +static struct wmi_device *yb9_query_wdev; +static DEFINE_MUTEX(yb9_query_lock); + +struct yb9_kbdock_priv { + struct wmi_device *query_wdev; + struct input_dev *input_dev; + unsigned int bkbd; +}; + +/* Returns 0=E2=80=932 on success, -errno on error. */ +static int yb9_kbdock_query(struct wmi_device *event_wdev, + struct wmi_device *query_wdev) +{ + u32 bkbd; + + union acpi_object *obj __free(kfree) =3D + wmidev_block_query(query_wdev, YB9_KBDOCK_QUERY_INSTANCE); + if (!obj) { + dev_warn(&event_wdev->dev, "WQAF query returned NULL\n"); + return -EIO; + } + + /* + * WQAF returns an 8-byte buffer: bytes [0..3] =3D LFID (0x00060000), + * bytes [4..7] =3D BKBD value. Guard against short buffers. + */ + if (obj->type =3D=3D ACPI_TYPE_BUFFER && obj->buffer.length >=3D 8) + memcpy(&bkbd, obj->buffer.pointer + 4, sizeof(bkbd)); + else if (obj->type =3D=3D ACPI_TYPE_INTEGER) + bkbd =3D obj->integer.value; + else { + dev_warn(&event_wdev->dev, + "WQAF: unexpected result type %d len %u\n", + obj->type, obj->type =3D=3D ACPI_TYPE_BUFFER ? obj->buffer.length : 0); + return -EIO; + } + + bkbd =3D FIELD_GET(BKBD_MASK, bkbd); + if (bkbd =3D=3D 3) { + dev_warn(&event_wdev->dev, "BKBD value 3 is reserved\n"); + return -EINVAL; + } + + return bkbd; +} + +static void yb9_kbdock_update(struct wmi_device *wdev) +{ + struct yb9_kbdock_priv *priv =3D dev_get_drvdata(&wdev->dev); + int tablet_mode; + int bkbd; + + bkbd =3D yb9_kbdock_query(wdev, priv->query_wdev); + if (bkbd < 0) + return; + + priv->bkbd =3D bkbd; + tablet_mode =3D (bkbd =3D=3D BKBD_DETACHED) ? 1 : 0; + + input_report_switch(priv->input_dev, SW_TABLET_MODE, tablet_mode); + input_sync(priv->input_dev); + + dev_dbg(&wdev->dev, "BKBD=3D%u tablet_mode=3D%d\n", bkbd, tablet_mode); +} + +static void yb9_kbdock_notify(struct wmi_device *wdev, union acpi_object *= data) +{ + yb9_kbdock_update(wdev); +} + +static ssize_t keyboard_position_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + static const char * const names[] =3D { + "detached", "top-half", "bottom-half", + }; + struct yb9_kbdock_priv *priv =3D dev_get_drvdata(dev); + unsigned int bkbd =3D priv->bkbd; + + if (WARN_ON_ONCE(bkbd >=3D ARRAY_SIZE(names))) + return -EINVAL; + return sysfs_emit(buf, "%u (%s)\n", bkbd, names[bkbd]); +} +static DEVICE_ATTR_RO(keyboard_position); + +static struct attribute *yb9_kbdock_attrs[] =3D { + &dev_attr_keyboard_position.attr, + NULL, +}; +ATTRIBUTE_GROUPS(yb9_kbdock); + +static const struct dmi_system_id yb9_kbdock_dmi_table[] =3D { + { + /* Lenovo Yoga Book 9 14IAH10 */ + .matches =3D { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "83KJ"), + }, + }, + { } +}; + +static int yb9_kbdock_probe(struct wmi_device *wdev, const void *ctx) +{ + enum yb9_guid_type type =3D (enum yb9_guid_type)(uintptr_t)ctx; + struct yb9_kbdock_priv *priv; + struct input_dev *input_dev; + struct wmi_device *qwdev; + int err; + + if (type =3D=3D YB9_GUID_QUERY) { + mutex_lock(&yb9_query_lock); + yb9_query_wdev =3D wdev; + mutex_unlock(&yb9_query_lock); + return 0; + } + + if (!dmi_check_system(yb9_kbdock_dmi_table)) + return -ENODEV; + + mutex_lock(&yb9_query_lock); + qwdev =3D yb9_query_wdev; + mutex_unlock(&yb9_query_lock); + if (!qwdev) + return -EPROBE_DEFER; + + priv =3D devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + input_dev =3D devm_input_allocate_device(&wdev->dev); + if (!input_dev) + return -ENOMEM; + + input_dev->name =3D "Lenovo Yoga Book 9 keyboard dock switch"; + input_dev->phys =3D YB9_KBDOCK_EVENT_GUID "/input0"; + input_dev->id.bustype =3D BUS_HOST; + input_dev->dev.parent =3D &wdev->dev; + input_set_capability(input_dev, EV_SW, SW_TABLET_MODE); + + err =3D input_register_device(input_dev); + if (err) { + dev_err(&wdev->dev, "failed to register input device: %d\n", err); + return err; + } + + priv->query_wdev =3D qwdev; + priv->input_dev =3D input_dev; + dev_set_drvdata(&wdev->dev, priv); + + yb9_kbdock_update(wdev); + return 0; +} + +static void yb9_kbdock_remove(struct wmi_device *wdev) +{ + mutex_lock(&yb9_query_lock); + if (wdev =3D=3D yb9_query_wdev) + yb9_query_wdev =3D NULL; + mutex_unlock(&yb9_query_lock); +} + +static const struct wmi_device_id yb9_kbdock_wmi_id_table[] =3D { + { .guid_string =3D YB9_KBDOCK_EVENT_GUID, .context =3D (void *)YB9_GUID_E= VENT }, + { .guid_string =3D YB9_KBDOCK_QUERY_GUID, .context =3D (void *)YB9_GUID_Q= UERY }, + { } +}; +MODULE_DEVICE_TABLE(wmi, yb9_kbdock_wmi_id_table); + +static struct wmi_driver yb9_kbdock_driver =3D { + .driver =3D { + .name =3D "lenovo-yb9-kbdock", + .dev_groups =3D yb9_kbdock_groups, + }, + .id_table =3D yb9_kbdock_wmi_id_table, + .probe =3D yb9_kbdock_probe, + .remove =3D yb9_kbdock_remove, + .notify =3D yb9_kbdock_notify, +}; +module_wmi_driver(yb9_kbdock_driver); + +MODULE_AUTHOR("Dave Carey "); +MODULE_DESCRIPTION("Lenovo Yoga Book 9 keyboard dock detection"); +MODULE_LICENSE("GPL"); 2.47.0