xref: /linux/drivers/platform/x86/lenovo/yb9-kbdock.c (revision 5b05bb3f6c5716fab6911e12d60dd1f43ad9806a)
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Lenovo Yoga Book 9 keyboard-dock detection
4  *
5  * The Yoga Book 9 ships with a detachable Bluetooth keyboard that magnetically
6  * attaches to the bottom screen in one of two positions.  The EC tracks
7  * attachment state in a 2-bit field called BKBD and signals changes via WMI
8  * event 0xEB on the WM10 ACPI device (_UID "GMZN").
9  *
10  * BKBD values:
11  *   0 = keyboard detached
12  *   1 = keyboard docked on the top half of the bottom screen
13  *   2 = keyboard docked on the bottom half of the bottom screen
14  *   3 = reserved / not observed
15  *
16  * Two WMI interfaces are used (documented in embedded BMOF, WQDD, 20705 bytes):
17  *
18  *   LENOVO_BTKBD_EVENT (event GUID, 806BD2A2-...)
19  *     WmiDataId(1) uint32 Status — _WED(0xEB) returns EC.BKBD directly.
20  *     The notify callback receives BKBD as an integer; no separate query needed.
21  *
22  *   LENOVO_FEATURE_STATUS_DATA (block GUID, E7F300FA-...)
23  *     WmiDataId(1) uint32 IDs   = 0x00060000 (feature selector)
24  *     WmiDataId(2) uint32 Status = BKBD value
25  *     Used on probe and resume to read initial state.
26  *
27  * The event driver (LENOVO_BTKBD_EVENT) fires a notifier chain on each WMI
28  * event.  The block driver (LENOVO_FEATURE_STATUS_DATA) owns the input_dev
29  * and registers a notifier_block to receive those events, eliminating the
30  * need for shared global state or a mutex.
31  *
32  * SW_TABLET_MODE=1 is reported when the keyboard is detached;
33  * SW_TABLET_MODE=0 when docked in either position (keyboard present).
34  * The raw BKBD value is exposed via the sysfs attribute "keyboard_position".
35  *
36  * Copyright (C) 2026 Dave Carey <carvsdriver@gmail.com>
37  */
38 
39 #include <linux/acpi.h>
40 #include <linux/cleanup.h>
41 #include <linux/compiler_attributes.h>
42 #include <linux/dev_printk.h>
43 #include <linux/dmi.h>
44 #include <linux/input.h>
45 #include <linux/module.h>
46 #include <linux/notifier.h>
47 #include <linux/pm.h>
48 #include <linux/slab.h>
49 #include <linux/spinlock.h>
50 #include <linux/sysfs.h>
51 #include <linux/types.h>
52 #include <linux/wmi.h>
53 
54 #define YB9_KBDOCK_EVENT_GUID	"806BD2A2-177B-481D-BFB5-3BA0BB4A2285"
55 #define YB9_KBDOCK_QUERY_GUID	"E7F300FA-21CD-4003-ADAC-2696135982E6"
56 
57 /* BKBD encoding */
58 #define BKBD_DETACHED		0
59 
60 /* LENOVO_FEATURE_STATUS_DATA feature selector */
61 #define YB9_FEATURE_STATUS_ID	0x00060000u
62 
63 /*
64  * LENOVO_FEATURE_STATUS_DATA: 8-byte buffer {uint32 IDs, uint32 Status}.
65  * IDs is always 0x00060000; Status holds the BKBD value (0–3).
66  */
67 struct lenovo_feature_status {
68 	__le32 id;
69 	__le32 status;
70 } __packed;
71 
72 /* ------------------------------------------------------------------
73  * Notifier chain — event driver fires it, block driver listens
74  * ------------------------------------------------------------------ */
75 
76 static BLOCKING_NOTIFIER_HEAD(yb9_kbdock_chain_head);
77 
devm_yb9_kbdock_unregister_notifier(void * data)78 static void devm_yb9_kbdock_unregister_notifier(void *data)
79 {
80 	struct notifier_block *nb = data;
81 
82 	blocking_notifier_chain_unregister(&yb9_kbdock_chain_head, nb);
83 }
84 
devm_yb9_kbdock_register_notifier(struct device * dev,struct notifier_block * nb)85 static int devm_yb9_kbdock_register_notifier(struct device *dev,
86 					      struct notifier_block *nb)
87 {
88 	int ret;
89 
90 	ret = blocking_notifier_chain_register(&yb9_kbdock_chain_head, nb);
91 	if (ret < 0)
92 		return ret;
93 
94 	return devm_add_action_or_reset(dev, devm_yb9_kbdock_unregister_notifier, nb);
95 }
96 
97 /* ------------------------------------------------------------------
98  * Block WMI driver — LENOVO_FEATURE_STATUS_DATA
99  * (owns input_dev, sysfs, PM resume)
100  * ------------------------------------------------------------------ */
101 
102 struct yb9_kbdock_data {
103 	struct wmi_device	*wdev;
104 	struct input_dev	*input_dev;
105 	struct notifier_block	 nb;
106 	spinlock_t		 lock;	/* protects input_report_switch + input_sync */
107 };
108 
yb9_kbdock_query(struct yb9_kbdock_data * d,u32 * bkbd)109 static int yb9_kbdock_query(struct yb9_kbdock_data *d, u32 *bkbd)
110 {
111 	struct wmi_buffer out;
112 	int ret;
113 
114 	ret = wmidev_query_block(d->wdev, 0, &out,
115 				 sizeof(struct lenovo_feature_status));
116 	if (ret)
117 		return ret;
118 
119 	struct lenovo_feature_status *fs __free(kfree) = out.data;
120 
121 	if (le32_to_cpu(fs->id) != YB9_FEATURE_STATUS_ID)
122 		return -EIO;
123 
124 	*bkbd = le32_to_cpu(fs->status);
125 	return 0;
126 }
127 
yb9_kbdock_report(struct yb9_kbdock_data * d,u32 bkbd)128 static void yb9_kbdock_report(struct yb9_kbdock_data *d, u32 bkbd)
129 {
130 	int tablet = (bkbd == BKBD_DETACHED) ? 1 : 0;
131 
132 	spin_lock(&d->lock);
133 	input_report_switch(d->input_dev, SW_TABLET_MODE, tablet);
134 	input_sync(d->input_dev);
135 	spin_unlock(&d->lock);
136 	dev_dbg(&d->wdev->dev, "BKBD=%u SW_TABLET_MODE=%d\n", bkbd, tablet);
137 }
138 
yb9_kbdock_sync(struct yb9_kbdock_data * d)139 static int yb9_kbdock_sync(struct yb9_kbdock_data *d)
140 {
141 	u32 bkbd;
142 	int ret;
143 
144 	ret = yb9_kbdock_query(d, &bkbd);
145 	if (ret)
146 		return ret;
147 
148 	yb9_kbdock_report(d, bkbd);
149 	return 0;
150 }
151 
yb9_kbdock_nb_call(struct notifier_block * nb,unsigned long bkbd,void * unused)152 static int yb9_kbdock_nb_call(struct notifier_block *nb,
153 			       unsigned long bkbd, void *unused)
154 {
155 	struct yb9_kbdock_data *d =
156 		container_of(nb, struct yb9_kbdock_data, nb);
157 
158 	yb9_kbdock_report(d, bkbd);
159 	return NOTIFY_DONE;
160 }
161 
keyboard_position_show(struct device * dev,struct device_attribute * attr,char * buf)162 static ssize_t keyboard_position_show(struct device *dev,
163 				       struct device_attribute *attr,
164 				       char *buf)
165 {
166 	struct yb9_kbdock_data *d = dev_get_drvdata(dev);
167 	u32 bkbd;
168 	int ret;
169 
170 	ret = yb9_kbdock_query(d, &bkbd);
171 	if (ret)
172 		return ret;
173 	return sysfs_emit(buf, "%u\n", bkbd);
174 }
175 static DEVICE_ATTR_RO(keyboard_position);
176 
177 static const struct attribute * const yb9_kbdock_attrs[] = {
178 	&dev_attr_keyboard_position.attr,
179 	NULL,
180 };
181 ATTRIBUTE_GROUPS(yb9_kbdock);
182 
yb9_kbdock_resume(struct device * dev)183 static int yb9_kbdock_resume(struct device *dev)
184 {
185 	struct yb9_kbdock_data *d = dev_get_drvdata(dev);
186 
187 	return yb9_kbdock_sync(d);
188 }
189 static DEFINE_SIMPLE_DEV_PM_OPS(yb9_kbdock_pm_ops, NULL, yb9_kbdock_resume);
190 
yb9_kbdock_block_probe(struct wmi_device * wdev,const void * ctx)191 static int yb9_kbdock_block_probe(struct wmi_device *wdev, const void *ctx)
192 {
193 	struct yb9_kbdock_data *d;
194 	struct input_dev *input_dev;
195 	int ret;
196 
197 	d = devm_kzalloc(&wdev->dev, sizeof(*d), GFP_KERNEL);
198 	if (!d)
199 		return -ENOMEM;
200 
201 	d->wdev = wdev;
202 	spin_lock_init(&d->lock);
203 
204 	input_dev = devm_input_allocate_device(&wdev->dev);
205 	if (!input_dev)
206 		return -ENOMEM;
207 
208 	input_dev->name		= "Lenovo Yoga Book 9 keyboard dock switch";
209 	input_dev->phys		= YB9_KBDOCK_QUERY_GUID "/input0";
210 	input_dev->id.bustype	= BUS_HOST;
211 	input_set_capability(input_dev, EV_SW, SW_TABLET_MODE);
212 
213 	ret = input_register_device(input_dev);
214 	if (ret)
215 		return ret;
216 
217 	d->input_dev		= input_dev;
218 	d->nb.notifier_call	= yb9_kbdock_nb_call;
219 
220 	ret = devm_yb9_kbdock_register_notifier(&wdev->dev, &d->nb);
221 	if (ret)
222 		return ret;
223 
224 	dev_set_drvdata(&wdev->dev, d);
225 	return yb9_kbdock_sync(d);
226 }
227 
228 static const struct wmi_device_id yb9_kbdock_block_id_table[] = {
229 	{ .guid_string = YB9_KBDOCK_QUERY_GUID },
230 	{ }
231 };
232 
233 static struct wmi_driver yb9_kbdock_block_driver = {
234 	.driver = {
235 		.name		= "lenovo-yb9-kbdock",
236 		.dev_groups	= yb9_kbdock_groups,
237 		.pm		= pm_sleep_ptr(&yb9_kbdock_pm_ops),
238 	},
239 	.id_table	= yb9_kbdock_block_id_table,
240 	.no_singleton	= true,
241 	.probe		= yb9_kbdock_block_probe,
242 };
243 
244 /* ------------------------------------------------------------------
245  * Event WMI driver — LENOVO_BTKBD_EVENT
246  * (fires the notifier chain on each WMI event)
247  * ------------------------------------------------------------------ */
248 
yb9_kbdock_notify_new(struct wmi_device * wdev,const struct wmi_buffer * data)249 static void yb9_kbdock_notify_new(struct wmi_device *wdev,
250 				  const struct wmi_buffer *data)
251 {
252 	/*
253 	 * _WED(0xEB) returns EC.BKBD directly as a 32-bit integer
254 	 * (LENOVO_BTKBD_EVENT WmiDataId(1) uint32 Status).
255 	 * Short-buffer guard is handled by .min_event_size below.
256 	 */
257 	u32 bkbd = le32_to_cpu(*(const __le32 *)data->data);
258 
259 	blocking_notifier_call_chain(&yb9_kbdock_chain_head, bkbd, NULL);
260 }
261 
262 static const struct wmi_device_id yb9_kbdock_event_id_table[] = {
263 	{ .guid_string = YB9_KBDOCK_EVENT_GUID },
264 	{ }
265 };
266 MODULE_DEVICE_TABLE(wmi, yb9_kbdock_event_id_table);
267 
268 static struct wmi_driver yb9_kbdock_event_driver = {
269 	.driver = {
270 		.name = "lenovo-yb9-kbdock-event",
271 	},
272 	.id_table	= yb9_kbdock_event_id_table,
273 	.no_singleton	= true,
274 	.notify_new	= yb9_kbdock_notify_new,
275 	.min_event_size	= sizeof(__le32),
276 };
277 
278 /* ------------------------------------------------------------------
279  * Module init / exit
280  * ------------------------------------------------------------------ */
281 
282 static const struct dmi_system_id yb9_kbdock_dmi_table[] __initconst = {
283 	{
284 		/* Lenovo Yoga Book 9 14IAH10 */
285 		.matches = {
286 			DMI_MATCH(DMI_SYS_VENDOR,   "LENOVO"),
287 			DMI_MATCH(DMI_PRODUCT_NAME, "83KJ"),
288 		},
289 	},
290 	{ }
291 };
292 
yb9_kbdock_init(void)293 static int __init yb9_kbdock_init(void)
294 {
295 	int ret;
296 
297 	if (!dmi_check_system(yb9_kbdock_dmi_table))
298 		return -ENODEV;
299 
300 	ret = wmi_driver_register(&yb9_kbdock_event_driver);
301 	if (ret)
302 		return ret;
303 
304 	ret = wmi_driver_register(&yb9_kbdock_block_driver);
305 	if (ret) {
306 		wmi_driver_unregister(&yb9_kbdock_event_driver);
307 		return ret;
308 	}
309 
310 	return 0;
311 }
312 module_init(yb9_kbdock_init);
313 
yb9_kbdock_exit(void)314 static void __exit yb9_kbdock_exit(void)
315 {
316 	wmi_driver_unregister(&yb9_kbdock_block_driver);
317 	wmi_driver_unregister(&yb9_kbdock_event_driver);
318 }
319 module_exit(yb9_kbdock_exit);
320 
321 MODULE_AUTHOR("Dave Carey <carvsdriver@gmail.com>");
322 MODULE_DESCRIPTION("Lenovo Yoga Book 9 keyboard dock detection");
323 MODULE_LICENSE("GPL");
324