1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Intel HID event & 5 button array driver |
4 | * |
5 | * Copyright (C) 2015 Alex Hung <alex.hung@canonical.com> |
6 | * Copyright (C) 2015 Andrew Lutomirski <luto@kernel.org> |
7 | */ |
8 | |
9 | #include <linux/acpi.h> |
10 | #include <linux/dmi.h> |
11 | #include <linux/input.h> |
12 | #include <linux/input/sparse-keymap.h> |
13 | #include <linux/kernel.h> |
14 | #include <linux/module.h> |
15 | #include <linux/platform_device.h> |
16 | #include <linux/suspend.h> |
17 | #include "../dual_accel_detect.h" |
18 | |
19 | enum intel_hid_tablet_sw_mode { |
20 | TABLET_SW_AUTO = -1, |
21 | TABLET_SW_OFF = 0, |
22 | TABLET_SW_AT_EVENT, |
23 | TABLET_SW_AT_PROBE, |
24 | }; |
25 | |
26 | static bool enable_5_button_array; |
27 | module_param(enable_5_button_array, bool, 0444); |
28 | MODULE_PARM_DESC(enable_5_button_array, |
29 | "Enable 5 Button Array support. " |
30 | "If you need this please report this to: platform-driver-x86@vger.kernel.org" ); |
31 | |
32 | static int enable_sw_tablet_mode = TABLET_SW_AUTO; |
33 | module_param(enable_sw_tablet_mode, int, 0444); |
34 | MODULE_PARM_DESC(enable_sw_tablet_mode, |
35 | "Enable SW_TABLET_MODE reporting -1:auto 0:off 1:at-first-event 2:at-probe. " |
36 | "If you need this please report this to: platform-driver-x86@vger.kernel.org" ); |
37 | |
38 | /* When NOT in tablet mode, VGBS returns with the flag 0x40 */ |
39 | #define TABLET_MODE_FLAG BIT(6) |
40 | |
41 | MODULE_LICENSE("GPL" ); |
42 | MODULE_AUTHOR("Alex Hung" ); |
43 | |
44 | static const struct acpi_device_id intel_hid_ids[] = { |
45 | {"INT33D5" , 0}, |
46 | {"INTC1051" , 0}, |
47 | {"INTC1054" , 0}, |
48 | {"INTC1070" , 0}, |
49 | {"INTC1076" , 0}, |
50 | {"INTC1077" , 0}, |
51 | {"INTC1078" , 0}, |
52 | {"INTC107B" , 0}, |
53 | {"INTC10CB" , 0}, |
54 | {"" , 0}, |
55 | }; |
56 | MODULE_DEVICE_TABLE(acpi, intel_hid_ids); |
57 | |
58 | /* In theory, these are HID usages. */ |
59 | static const struct key_entry intel_hid_keymap[] = { |
60 | /* 1: LSuper (Page 0x07, usage 0xE3) -- unclear what to do */ |
61 | /* 2: Toggle SW_ROTATE_LOCK -- easy to implement if seen in wild */ |
62 | { KE_KEY, 3, { KEY_NUMLOCK } }, |
63 | { KE_KEY, 4, { KEY_HOME } }, |
64 | { KE_KEY, 5, { KEY_END } }, |
65 | { KE_KEY, 6, { KEY_PAGEUP } }, |
66 | { KE_KEY, 7, { KEY_PAGEDOWN } }, |
67 | { KE_KEY, 8, { KEY_RFKILL } }, |
68 | { KE_KEY, 9, { KEY_POWER } }, |
69 | { KE_KEY, 11, { KEY_SLEEP } }, |
70 | /* 13 has two different meanings in the spec -- ignore it. */ |
71 | { KE_KEY, 14, { KEY_STOPCD } }, |
72 | { KE_KEY, 15, { KEY_PLAYPAUSE } }, |
73 | { KE_KEY, 16, { KEY_MUTE } }, |
74 | { KE_KEY, 17, { KEY_VOLUMEUP } }, |
75 | { KE_KEY, 18, { KEY_VOLUMEDOWN } }, |
76 | { KE_KEY, 19, { KEY_BRIGHTNESSUP } }, |
77 | { KE_KEY, 20, { KEY_BRIGHTNESSDOWN } }, |
78 | /* 27: wake -- needs special handling */ |
79 | { KE_END }, |
80 | }; |
81 | |
82 | /* 5 button array notification value. */ |
83 | static const struct key_entry intel_array_keymap[] = { |
84 | { KE_KEY, 0xC2, { KEY_LEFTMETA } }, /* Press */ |
85 | { KE_IGNORE, 0xC3, { KEY_LEFTMETA } }, /* Release */ |
86 | { KE_KEY, 0xC4, { KEY_VOLUMEUP } }, /* Press */ |
87 | { KE_IGNORE, 0xC5, { KEY_VOLUMEUP } }, /* Release */ |
88 | { KE_KEY, 0xC6, { KEY_VOLUMEDOWN } }, /* Press */ |
89 | { KE_IGNORE, 0xC7, { KEY_VOLUMEDOWN } }, /* Release */ |
90 | { KE_KEY, 0xC8, { KEY_ROTATE_LOCK_TOGGLE } }, /* Press */ |
91 | { KE_IGNORE, 0xC9, { KEY_ROTATE_LOCK_TOGGLE } }, /* Release */ |
92 | { KE_KEY, 0xCE, { KEY_POWER } }, /* Press */ |
93 | { KE_IGNORE, 0xCF, { KEY_POWER } }, /* Release */ |
94 | { KE_END }, |
95 | }; |
96 | |
97 | static const struct dmi_system_id button_array_table[] = { |
98 | { |
99 | .ident = "Wacom MobileStudio Pro 13" , |
100 | .matches = { |
101 | DMI_MATCH(DMI_SYS_VENDOR, "Wacom Co.,Ltd" ), |
102 | DMI_MATCH(DMI_PRODUCT_NAME, "Wacom MobileStudio Pro 13" ), |
103 | }, |
104 | }, |
105 | { |
106 | .ident = "Wacom MobileStudio Pro 16" , |
107 | .matches = { |
108 | DMI_MATCH(DMI_SYS_VENDOR, "Wacom Co.,Ltd" ), |
109 | DMI_MATCH(DMI_PRODUCT_NAME, "Wacom MobileStudio Pro 16" ), |
110 | }, |
111 | }, |
112 | { |
113 | .ident = "HP Spectre x2 (2015)" , |
114 | .matches = { |
115 | DMI_MATCH(DMI_SYS_VENDOR, "HP" ), |
116 | DMI_MATCH(DMI_PRODUCT_NAME, "HP Spectre x2 Detachable" ), |
117 | }, |
118 | }, |
119 | { |
120 | .ident = "Lenovo ThinkPad X1 Tablet Gen 2" , |
121 | .matches = { |
122 | DMI_MATCH(DMI_SYS_VENDOR, "LENOVO" ), |
123 | DMI_MATCH(DMI_PRODUCT_FAMILY, "ThinkPad X1 Tablet Gen 2" ), |
124 | }, |
125 | }, |
126 | { |
127 | .ident = "Microsoft Surface Go 3" , |
128 | .matches = { |
129 | DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation" ), |
130 | DMI_MATCH(DMI_PRODUCT_NAME, "Surface Go 3" ), |
131 | }, |
132 | }, |
133 | { } |
134 | }; |
135 | |
136 | /* |
137 | * Some convertible use the intel-hid ACPI interface to report SW_TABLET_MODE, |
138 | * these need to be compared via a DMI based authorization list because some |
139 | * models have unreliable VGBS return which could cause incorrect |
140 | * SW_TABLET_MODE report. |
141 | */ |
142 | static const struct dmi_system_id dmi_vgbs_allow_list[] = { |
143 | { |
144 | .matches = { |
145 | DMI_MATCH(DMI_SYS_VENDOR, "HP" ), |
146 | DMI_MATCH(DMI_PRODUCT_NAME, "HP Spectre x360 Convertible 15-df0xxx" ), |
147 | }, |
148 | }, |
149 | { |
150 | .matches = { |
151 | DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation" ), |
152 | DMI_MATCH(DMI_PRODUCT_NAME, "Surface Go" ), |
153 | }, |
154 | }, |
155 | { |
156 | .matches = { |
157 | DMI_MATCH(DMI_SYS_VENDOR, "HP" ), |
158 | DMI_MATCH(DMI_PRODUCT_NAME, "HP Elite Dragonfly G2 Notebook PC" ), |
159 | }, |
160 | }, |
161 | { } |
162 | }; |
163 | |
164 | /* |
165 | * Some devices, even non convertible ones, can send incorrect SW_TABLET_MODE |
166 | * reports. Accept such reports only from devices in this list. |
167 | */ |
168 | static const struct dmi_system_id dmi_auto_add_switch[] = { |
169 | { |
170 | .matches = { |
171 | DMI_EXACT_MATCH(DMI_CHASSIS_TYPE, "31" /* Convertible */), |
172 | }, |
173 | }, |
174 | { |
175 | .matches = { |
176 | DMI_EXACT_MATCH(DMI_CHASSIS_TYPE, "32" /* Detachable */), |
177 | }, |
178 | }, |
179 | {} /* Array terminator */ |
180 | }; |
181 | |
182 | struct intel_hid_priv { |
183 | struct input_dev *input_dev; |
184 | struct input_dev *array; |
185 | struct input_dev *switches; |
186 | bool wakeup_mode; |
187 | }; |
188 | |
189 | #define HID_EVENT_FILTER_UUID "eeec56b3-4442-408f-a792-4edd4d758054" |
190 | |
191 | enum intel_hid_dsm_fn_codes { |
192 | INTEL_HID_DSM_FN_INVALID, |
193 | INTEL_HID_DSM_BTNL_FN, |
194 | INTEL_HID_DSM_HDMM_FN, |
195 | INTEL_HID_DSM_HDSM_FN, |
196 | INTEL_HID_DSM_HDEM_FN, |
197 | INTEL_HID_DSM_BTNS_FN, |
198 | INTEL_HID_DSM_BTNE_FN, |
199 | INTEL_HID_DSM_HEBC_V1_FN, |
200 | INTEL_HID_DSM_VGBS_FN, |
201 | INTEL_HID_DSM_HEBC_V2_FN, |
202 | INTEL_HID_DSM_FN_MAX |
203 | }; |
204 | |
205 | static const char *intel_hid_dsm_fn_to_method[INTEL_HID_DSM_FN_MAX] = { |
206 | NULL, |
207 | "BTNL" , |
208 | "HDMM" , |
209 | "HDSM" , |
210 | "HDEM" , |
211 | "BTNS" , |
212 | "BTNE" , |
213 | "HEBC" , |
214 | "VGBS" , |
215 | "HEBC" |
216 | }; |
217 | |
218 | static unsigned long long intel_hid_dsm_fn_mask; |
219 | static guid_t intel_dsm_guid; |
220 | |
221 | static bool intel_hid_execute_method(acpi_handle handle, |
222 | enum intel_hid_dsm_fn_codes fn_index, |
223 | unsigned long long arg) |
224 | { |
225 | union acpi_object *obj, argv4, req; |
226 | acpi_status status; |
227 | char *method_name; |
228 | |
229 | if (fn_index <= INTEL_HID_DSM_FN_INVALID || |
230 | fn_index >= INTEL_HID_DSM_FN_MAX) |
231 | return false; |
232 | |
233 | method_name = (char *)intel_hid_dsm_fn_to_method[fn_index]; |
234 | |
235 | if (!(intel_hid_dsm_fn_mask & BIT(fn_index))) |
236 | goto skip_dsm_exec; |
237 | |
238 | /* All methods expects a package with one integer element */ |
239 | req.type = ACPI_TYPE_INTEGER; |
240 | req.integer.value = arg; |
241 | |
242 | argv4.type = ACPI_TYPE_PACKAGE; |
243 | argv4.package.count = 1; |
244 | argv4.package.elements = &req; |
245 | |
246 | obj = acpi_evaluate_dsm(handle, guid: &intel_dsm_guid, rev: 1, func: fn_index, argv4: &argv4); |
247 | if (obj) { |
248 | acpi_handle_debug(handle, "Exec DSM Fn code: %d[%s] success\n" , |
249 | fn_index, method_name); |
250 | ACPI_FREE(obj); |
251 | return true; |
252 | } |
253 | |
254 | skip_dsm_exec: |
255 | status = acpi_execute_simple_method(handle, method: method_name, arg); |
256 | if (ACPI_SUCCESS(status)) |
257 | return true; |
258 | |
259 | return false; |
260 | } |
261 | |
262 | static bool intel_hid_evaluate_method(acpi_handle handle, |
263 | enum intel_hid_dsm_fn_codes fn_index, |
264 | unsigned long long *result) |
265 | { |
266 | union acpi_object *obj; |
267 | acpi_status status; |
268 | char *method_name; |
269 | |
270 | if (fn_index <= INTEL_HID_DSM_FN_INVALID || |
271 | fn_index >= INTEL_HID_DSM_FN_MAX) |
272 | return false; |
273 | |
274 | method_name = (char *)intel_hid_dsm_fn_to_method[fn_index]; |
275 | |
276 | if (!(intel_hid_dsm_fn_mask & BIT(fn_index))) |
277 | goto skip_dsm_eval; |
278 | |
279 | obj = acpi_evaluate_dsm_typed(handle, guid: &intel_dsm_guid, |
280 | rev: 1, func: fn_index, |
281 | NULL, ACPI_TYPE_INTEGER); |
282 | if (obj) { |
283 | *result = obj->integer.value; |
284 | acpi_handle_debug(handle, |
285 | "Eval DSM Fn code: %d[%s] results: 0x%llx\n" , |
286 | fn_index, method_name, *result); |
287 | ACPI_FREE(obj); |
288 | return true; |
289 | } |
290 | |
291 | skip_dsm_eval: |
292 | status = acpi_evaluate_integer(handle, pathname: method_name, NULL, data: result); |
293 | if (ACPI_SUCCESS(status)) |
294 | return true; |
295 | |
296 | return false; |
297 | } |
298 | |
299 | static void intel_hid_init_dsm(acpi_handle handle) |
300 | { |
301 | union acpi_object *obj; |
302 | |
303 | guid_parse(HID_EVENT_FILTER_UUID, u: &intel_dsm_guid); |
304 | |
305 | obj = acpi_evaluate_dsm_typed(handle, guid: &intel_dsm_guid, rev: 1, func: 0, NULL, |
306 | ACPI_TYPE_BUFFER); |
307 | if (obj) { |
308 | switch (obj->buffer.length) { |
309 | default: |
310 | case 2: |
311 | intel_hid_dsm_fn_mask = *(u16 *)obj->buffer.pointer; |
312 | break; |
313 | case 1: |
314 | intel_hid_dsm_fn_mask = *obj->buffer.pointer; |
315 | break; |
316 | case 0: |
317 | acpi_handle_warn(handle, "intel_hid_dsm_fn_mask length is zero\n" ); |
318 | intel_hid_dsm_fn_mask = 0; |
319 | break; |
320 | } |
321 | ACPI_FREE(obj); |
322 | } |
323 | |
324 | acpi_handle_debug(handle, "intel_hid_dsm_fn_mask = %llx\n" , |
325 | intel_hid_dsm_fn_mask); |
326 | } |
327 | |
328 | static int intel_hid_set_enable(struct device *device, bool enable) |
329 | { |
330 | acpi_handle handle = ACPI_HANDLE(device); |
331 | |
332 | /* Enable|disable features - power button is always enabled */ |
333 | if (!intel_hid_execute_method(handle, fn_index: INTEL_HID_DSM_HDSM_FN, |
334 | arg: enable)) { |
335 | dev_warn(device, "failed to %sable hotkeys\n" , |
336 | enable ? "en" : "dis" ); |
337 | return -EIO; |
338 | } |
339 | |
340 | return 0; |
341 | } |
342 | |
343 | static void intel_button_array_enable(struct device *device, bool enable) |
344 | { |
345 | struct intel_hid_priv *priv = dev_get_drvdata(dev: device); |
346 | acpi_handle handle = ACPI_HANDLE(device); |
347 | unsigned long long button_cap; |
348 | acpi_status status; |
349 | |
350 | if (!priv->array) |
351 | return; |
352 | |
353 | /* Query supported platform features */ |
354 | status = acpi_evaluate_integer(handle, pathname: "BTNC" , NULL, data: &button_cap); |
355 | if (ACPI_FAILURE(status)) { |
356 | dev_warn(device, "failed to get button capability\n" ); |
357 | return; |
358 | } |
359 | |
360 | /* Enable|disable features - power button is always enabled */ |
361 | if (!intel_hid_execute_method(handle, fn_index: INTEL_HID_DSM_BTNE_FN, |
362 | arg: enable ? button_cap : 1)) |
363 | dev_warn(device, "failed to set button capability\n" ); |
364 | } |
365 | |
366 | static int intel_hid_pm_prepare(struct device *device) |
367 | { |
368 | if (device_may_wakeup(dev: device)) { |
369 | struct intel_hid_priv *priv = dev_get_drvdata(dev: device); |
370 | |
371 | priv->wakeup_mode = true; |
372 | } |
373 | return 0; |
374 | } |
375 | |
376 | static void intel_hid_pm_complete(struct device *device) |
377 | { |
378 | struct intel_hid_priv *priv = dev_get_drvdata(dev: device); |
379 | |
380 | priv->wakeup_mode = false; |
381 | } |
382 | |
383 | static int intel_hid_pl_suspend_handler(struct device *device) |
384 | { |
385 | intel_button_array_enable(device, enable: false); |
386 | |
387 | if (!pm_suspend_no_platform()) |
388 | intel_hid_set_enable(device, enable: false); |
389 | |
390 | return 0; |
391 | } |
392 | |
393 | static int intel_hid_pl_resume_handler(struct device *device) |
394 | { |
395 | intel_hid_pm_complete(device); |
396 | |
397 | if (!pm_suspend_no_platform()) |
398 | intel_hid_set_enable(device, enable: true); |
399 | |
400 | intel_button_array_enable(device, enable: true); |
401 | return 0; |
402 | } |
403 | |
404 | static const struct dev_pm_ops intel_hid_pl_pm_ops = { |
405 | .prepare = intel_hid_pm_prepare, |
406 | .complete = intel_hid_pm_complete, |
407 | .freeze = intel_hid_pl_suspend_handler, |
408 | .thaw = intel_hid_pl_resume_handler, |
409 | .restore = intel_hid_pl_resume_handler, |
410 | .suspend = intel_hid_pl_suspend_handler, |
411 | .resume = intel_hid_pl_resume_handler, |
412 | }; |
413 | |
414 | static int intel_hid_input_setup(struct platform_device *device) |
415 | { |
416 | struct intel_hid_priv *priv = dev_get_drvdata(dev: &device->dev); |
417 | int ret; |
418 | |
419 | priv->input_dev = devm_input_allocate_device(&device->dev); |
420 | if (!priv->input_dev) |
421 | return -ENOMEM; |
422 | |
423 | ret = sparse_keymap_setup(dev: priv->input_dev, keymap: intel_hid_keymap, NULL); |
424 | if (ret) |
425 | return ret; |
426 | |
427 | priv->input_dev->name = "Intel HID events" ; |
428 | priv->input_dev->id.bustype = BUS_HOST; |
429 | |
430 | return input_register_device(priv->input_dev); |
431 | } |
432 | |
433 | static int intel_button_array_input_setup(struct platform_device *device) |
434 | { |
435 | struct intel_hid_priv *priv = dev_get_drvdata(dev: &device->dev); |
436 | int ret; |
437 | |
438 | /* Setup input device for 5 button array */ |
439 | priv->array = devm_input_allocate_device(&device->dev); |
440 | if (!priv->array) |
441 | return -ENOMEM; |
442 | |
443 | ret = sparse_keymap_setup(dev: priv->array, keymap: intel_array_keymap, NULL); |
444 | if (ret) |
445 | return ret; |
446 | |
447 | priv->array->name = "Intel HID 5 button array" ; |
448 | priv->array->id.bustype = BUS_HOST; |
449 | |
450 | return input_register_device(priv->array); |
451 | } |
452 | |
453 | static int intel_hid_switches_setup(struct platform_device *device) |
454 | { |
455 | struct intel_hid_priv *priv = dev_get_drvdata(dev: &device->dev); |
456 | |
457 | /* Setup input device for switches */ |
458 | priv->switches = devm_input_allocate_device(&device->dev); |
459 | if (!priv->switches) |
460 | return -ENOMEM; |
461 | |
462 | __set_bit(EV_SW, priv->switches->evbit); |
463 | __set_bit(SW_TABLET_MODE, priv->switches->swbit); |
464 | |
465 | priv->switches->name = "Intel HID switches" ; |
466 | priv->switches->id.bustype = BUS_HOST; |
467 | return input_register_device(priv->switches); |
468 | } |
469 | |
470 | static void report_tablet_mode_state(struct platform_device *device) |
471 | { |
472 | struct intel_hid_priv *priv = dev_get_drvdata(dev: &device->dev); |
473 | acpi_handle handle = ACPI_HANDLE(&device->dev); |
474 | unsigned long long vgbs; |
475 | int m; |
476 | |
477 | if (!intel_hid_evaluate_method(handle, fn_index: INTEL_HID_DSM_VGBS_FN, result: &vgbs)) |
478 | return; |
479 | |
480 | m = !(vgbs & TABLET_MODE_FLAG); |
481 | input_report_switch(dev: priv->switches, SW_TABLET_MODE, value: m); |
482 | input_sync(dev: priv->switches); |
483 | } |
484 | |
485 | static bool report_tablet_mode_event(struct input_dev *input_dev, u32 event) |
486 | { |
487 | if (!input_dev) |
488 | return false; |
489 | |
490 | switch (event) { |
491 | case 0xcc: |
492 | input_report_switch(dev: input_dev, SW_TABLET_MODE, value: 1); |
493 | input_sync(dev: input_dev); |
494 | return true; |
495 | case 0xcd: |
496 | input_report_switch(dev: input_dev, SW_TABLET_MODE, value: 0); |
497 | input_sync(dev: input_dev); |
498 | return true; |
499 | default: |
500 | return false; |
501 | } |
502 | } |
503 | |
504 | static void notify_handler(acpi_handle handle, u32 event, void *context) |
505 | { |
506 | struct platform_device *device = context; |
507 | struct intel_hid_priv *priv = dev_get_drvdata(dev: &device->dev); |
508 | unsigned long long ev_index; |
509 | struct key_entry *ke; |
510 | int err; |
511 | |
512 | /* |
513 | * Some convertible have unreliable VGBS return which could cause incorrect |
514 | * SW_TABLET_MODE report, in these cases we enable support when receiving |
515 | * the first event instead of during driver setup. |
516 | */ |
517 | if (!priv->switches && enable_sw_tablet_mode == TABLET_SW_AT_EVENT && |
518 | (event == 0xcc || event == 0xcd)) { |
519 | dev_info(&device->dev, "switch event received, enable switches supports\n" ); |
520 | err = intel_hid_switches_setup(device); |
521 | if (err) |
522 | pr_err("Failed to setup Intel HID switches\n" ); |
523 | } |
524 | |
525 | if (priv->wakeup_mode) { |
526 | /* |
527 | * Needed for wakeup from suspend-to-idle to work on some |
528 | * platforms that don't expose the 5-button array, but still |
529 | * send notifies with the power button event code to this |
530 | * device object on power button actions while suspended. |
531 | */ |
532 | if (event == 0xce) |
533 | goto wakeup; |
534 | |
535 | /* |
536 | * Some devices send (duplicate) tablet-mode events when moved |
537 | * around even though the mode has not changed; and they do this |
538 | * even when suspended. |
539 | * Update the switch state in case it changed and then return |
540 | * without waking up to avoid spurious wakeups. |
541 | */ |
542 | if (event == 0xcc || event == 0xcd) { |
543 | report_tablet_mode_event(input_dev: priv->switches, event); |
544 | return; |
545 | } |
546 | |
547 | /* Wake up on 5-button array events only. */ |
548 | if (event == 0xc0 || !priv->array) |
549 | return; |
550 | |
551 | ke = sparse_keymap_entry_from_scancode(dev: priv->array, code: event); |
552 | if (!ke) { |
553 | dev_info(&device->dev, "unknown event 0x%x\n" , event); |
554 | return; |
555 | } |
556 | |
557 | if (ke->type == KE_IGNORE) |
558 | return; |
559 | |
560 | wakeup: |
561 | pm_wakeup_hard_event(dev: &device->dev); |
562 | |
563 | return; |
564 | } |
565 | |
566 | /* |
567 | * Needed for suspend to work on some platforms that don't expose |
568 | * the 5-button array, but still send notifies with power button |
569 | * event code to this device object on power button actions. |
570 | * |
571 | * Report the power button press and release. |
572 | */ |
573 | if (!priv->array) { |
574 | if (event == 0xce) { |
575 | input_report_key(dev: priv->input_dev, KEY_POWER, value: 1); |
576 | input_sync(dev: priv->input_dev); |
577 | return; |
578 | } |
579 | |
580 | if (event == 0xcf) { |
581 | input_report_key(dev: priv->input_dev, KEY_POWER, value: 0); |
582 | input_sync(dev: priv->input_dev); |
583 | return; |
584 | } |
585 | } |
586 | |
587 | if (report_tablet_mode_event(input_dev: priv->switches, event)) |
588 | return; |
589 | |
590 | /* 0xC0 is for HID events, other values are for 5 button array */ |
591 | if (event != 0xc0) { |
592 | if (!priv->array || |
593 | !sparse_keymap_report_event(dev: priv->array, code: event, value: 1, autorelease: true)) |
594 | dev_dbg(&device->dev, "unknown event 0x%x\n" , event); |
595 | return; |
596 | } |
597 | |
598 | if (!intel_hid_evaluate_method(handle, fn_index: INTEL_HID_DSM_HDEM_FN, |
599 | result: &ev_index)) { |
600 | dev_warn(&device->dev, "failed to get event index\n" ); |
601 | return; |
602 | } |
603 | |
604 | if (!sparse_keymap_report_event(dev: priv->input_dev, code: ev_index, value: 1, autorelease: true)) |
605 | dev_dbg(&device->dev, "unknown event index 0x%llx\n" , |
606 | ev_index); |
607 | } |
608 | |
609 | static bool button_array_present(struct platform_device *device) |
610 | { |
611 | acpi_handle handle = ACPI_HANDLE(&device->dev); |
612 | unsigned long long event_cap; |
613 | |
614 | if (intel_hid_evaluate_method(handle, fn_index: INTEL_HID_DSM_HEBC_V2_FN, |
615 | result: &event_cap)) { |
616 | /* Check presence of 5 button array or v2 power button */ |
617 | if (event_cap & 0x60000) |
618 | return true; |
619 | } |
620 | |
621 | if (intel_hid_evaluate_method(handle, fn_index: INTEL_HID_DSM_HEBC_V1_FN, |
622 | result: &event_cap)) { |
623 | if (event_cap & 0x20000) |
624 | return true; |
625 | } |
626 | |
627 | if (enable_5_button_array || dmi_check_system(list: button_array_table)) |
628 | return true; |
629 | |
630 | return false; |
631 | } |
632 | |
633 | static int intel_hid_probe(struct platform_device *device) |
634 | { |
635 | acpi_handle handle = ACPI_HANDLE(&device->dev); |
636 | unsigned long long mode, dummy; |
637 | struct intel_hid_priv *priv; |
638 | acpi_status status; |
639 | int err; |
640 | |
641 | intel_hid_init_dsm(handle); |
642 | |
643 | if (!intel_hid_evaluate_method(handle, fn_index: INTEL_HID_DSM_HDMM_FN, result: &mode)) { |
644 | dev_warn(&device->dev, "failed to read mode\n" ); |
645 | return -ENODEV; |
646 | } |
647 | |
648 | if (mode != 0) { |
649 | /* |
650 | * This driver only implements "simple" mode. There appear |
651 | * to be no other modes, but we should be paranoid and check |
652 | * for compatibility. |
653 | */ |
654 | dev_info(&device->dev, "platform is not in simple mode\n" ); |
655 | return -ENODEV; |
656 | } |
657 | |
658 | priv = devm_kzalloc(dev: &device->dev, size: sizeof(*priv), GFP_KERNEL); |
659 | if (!priv) |
660 | return -ENOMEM; |
661 | dev_set_drvdata(dev: &device->dev, data: priv); |
662 | |
663 | /* See dual_accel_detect.h for more info on the dual_accel check. */ |
664 | if (enable_sw_tablet_mode == TABLET_SW_AUTO) { |
665 | if (dmi_check_system(list: dmi_vgbs_allow_list)) |
666 | enable_sw_tablet_mode = TABLET_SW_AT_PROBE; |
667 | else if (dmi_check_system(list: dmi_auto_add_switch) && !dual_accel_detect()) |
668 | enable_sw_tablet_mode = TABLET_SW_AT_EVENT; |
669 | else |
670 | enable_sw_tablet_mode = TABLET_SW_OFF; |
671 | } |
672 | |
673 | err = intel_hid_input_setup(device); |
674 | if (err) { |
675 | pr_err("Failed to setup Intel HID hotkeys\n" ); |
676 | return err; |
677 | } |
678 | |
679 | /* Setup 5 button array */ |
680 | if (button_array_present(device)) { |
681 | dev_info(&device->dev, "platform supports 5 button array\n" ); |
682 | err = intel_button_array_input_setup(device); |
683 | if (err) |
684 | pr_err("Failed to setup Intel 5 button array hotkeys\n" ); |
685 | } |
686 | |
687 | /* Setup switches for devices that we know VGBS return correctly */ |
688 | if (enable_sw_tablet_mode == TABLET_SW_AT_PROBE) { |
689 | dev_info(&device->dev, "platform supports switches\n" ); |
690 | err = intel_hid_switches_setup(device); |
691 | if (err) |
692 | pr_err("Failed to setup Intel HID switches\n" ); |
693 | else |
694 | report_tablet_mode_state(device); |
695 | } |
696 | |
697 | status = acpi_install_notify_handler(device: handle, |
698 | ACPI_DEVICE_NOTIFY, |
699 | handler: notify_handler, |
700 | context: device); |
701 | if (ACPI_FAILURE(status)) |
702 | return -EBUSY; |
703 | |
704 | err = intel_hid_set_enable(device: &device->dev, enable: true); |
705 | if (err) |
706 | goto err_remove_notify; |
707 | |
708 | intel_button_array_enable(device: &device->dev, enable: true); |
709 | |
710 | /* |
711 | * Call button load method to enable HID power button |
712 | * Always do this since it activates events on some devices without |
713 | * a button array too. |
714 | */ |
715 | if (!intel_hid_evaluate_method(handle, fn_index: INTEL_HID_DSM_BTNL_FN, result: &dummy)) |
716 | dev_warn(&device->dev, "failed to enable HID power button\n" ); |
717 | |
718 | device_init_wakeup(dev: &device->dev, enable: true); |
719 | /* |
720 | * In order for system wakeup to work, the EC GPE has to be marked as |
721 | * a wakeup one, so do that here (this setting will persist, but it has |
722 | * no effect until the wakeup mask is set for the EC GPE). |
723 | */ |
724 | acpi_ec_mark_gpe_for_wake(); |
725 | return 0; |
726 | |
727 | err_remove_notify: |
728 | acpi_remove_notify_handler(device: handle, ACPI_DEVICE_NOTIFY, handler: notify_handler); |
729 | |
730 | return err; |
731 | } |
732 | |
733 | static void intel_hid_remove(struct platform_device *device) |
734 | { |
735 | acpi_handle handle = ACPI_HANDLE(&device->dev); |
736 | |
737 | device_init_wakeup(dev: &device->dev, enable: false); |
738 | acpi_remove_notify_handler(device: handle, ACPI_DEVICE_NOTIFY, handler: notify_handler); |
739 | intel_hid_set_enable(device: &device->dev, enable: false); |
740 | intel_button_array_enable(device: &device->dev, enable: false); |
741 | } |
742 | |
743 | static struct platform_driver intel_hid_pl_driver = { |
744 | .driver = { |
745 | .name = "intel-hid" , |
746 | .acpi_match_table = intel_hid_ids, |
747 | .pm = &intel_hid_pl_pm_ops, |
748 | }, |
749 | .probe = intel_hid_probe, |
750 | .remove_new = intel_hid_remove, |
751 | }; |
752 | |
753 | /* |
754 | * Unfortunately, some laptops provide a _HID="INT33D5" device with |
755 | * _CID="PNP0C02". This causes the pnpacpi scan driver to claim the |
756 | * ACPI node, so no platform device will be created. The pnpacpi |
757 | * driver rejects this device in subsequent processing, so no physical |
758 | * node is created at all. |
759 | * |
760 | * As a workaround until the ACPI core figures out how to handle |
761 | * this corner case, manually ask the ACPI platform device code to |
762 | * claim the ACPI node. |
763 | */ |
764 | static acpi_status __init |
765 | check_acpi_dev(acpi_handle handle, u32 lvl, void *context, void **rv) |
766 | { |
767 | const struct acpi_device_id *ids = context; |
768 | struct acpi_device *dev = acpi_fetch_acpi_dev(handle); |
769 | |
770 | if (dev && acpi_match_device_ids(device: dev, ids) == 0) |
771 | if (!IS_ERR_OR_NULL(ptr: acpi_create_platform_device(dev, NULL))) |
772 | dev_info(&dev->dev, |
773 | "intel-hid: created platform device\n" ); |
774 | |
775 | return AE_OK; |
776 | } |
777 | |
778 | static int __init intel_hid_init(void) |
779 | { |
780 | acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, |
781 | ACPI_UINT32_MAX, descending_callback: check_acpi_dev, NULL, |
782 | context: (void *)intel_hid_ids, NULL); |
783 | |
784 | return platform_driver_register(&intel_hid_pl_driver); |
785 | } |
786 | module_init(intel_hid_init); |
787 | |
788 | static void __exit intel_hid_exit(void) |
789 | { |
790 | platform_driver_unregister(&intel_hid_pl_driver); |
791 | } |
792 | module_exit(intel_hid_exit); |
793 | |