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