vbtn.c 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. // SPDX-License-Identifier: GPL-2.0+
  2. /*
  3. * Intel Virtual Button driver for Windows 8.1+
  4. *
  5. * Copyright (C) 2016 AceLan Kao <acelan.kao@canonical.com>
  6. * Copyright (C) 2016 Alex Hung <alex.hung@canonical.com>
  7. */
  8. #include <linux/acpi.h>
  9. #include <linux/cleanup.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/mutex.h>
  16. #include <linux/platform_device.h>
  17. #include <linux/suspend.h>
  18. #include "../dual_accel_detect.h"
  19. /* Returned when NOT in tablet mode on some HP Stream x360 11 models */
  20. #define VGBS_TABLET_MODE_FLAG_ALT 0x10
  21. /* When NOT in tablet mode, VGBS returns with the flag 0x40 */
  22. #define VGBS_TABLET_MODE_FLAG 0x40
  23. #define VGBS_DOCK_MODE_FLAG 0x80
  24. #define VGBS_TABLET_MODE_FLAGS (VGBS_TABLET_MODE_FLAG | VGBS_TABLET_MODE_FLAG_ALT)
  25. MODULE_DESCRIPTION("Intel Virtual Button driver");
  26. MODULE_LICENSE("GPL");
  27. MODULE_AUTHOR("AceLan Kao");
  28. static const struct acpi_device_id intel_vbtn_ids[] = {
  29. {"INT33D6", 0},
  30. {"", 0},
  31. };
  32. MODULE_DEVICE_TABLE(acpi, intel_vbtn_ids);
  33. /* In theory, these are HID usages. */
  34. static const struct key_entry intel_vbtn_keymap[] = {
  35. { KE_KEY, 0xC0, { KEY_POWER } }, /* power key press */
  36. { KE_IGNORE, 0xC1, { KEY_POWER } }, /* power key release */
  37. { KE_KEY, 0xC2, { KEY_LEFTMETA } }, /* 'Windows' key press */
  38. { KE_KEY, 0xC3, { KEY_LEFTMETA } }, /* 'Windows' key release */
  39. { KE_KEY, 0xC4, { KEY_VOLUMEUP } }, /* volume-up key press */
  40. { KE_IGNORE, 0xC5, { KEY_VOLUMEUP } }, /* volume-up key release */
  41. { KE_KEY, 0xC6, { KEY_VOLUMEDOWN } }, /* volume-down key press */
  42. { KE_IGNORE, 0xC7, { KEY_VOLUMEDOWN } }, /* volume-down key release */
  43. { KE_KEY, 0xC8, { KEY_ROTATE_LOCK_TOGGLE } }, /* rotate-lock key press */
  44. { KE_KEY, 0xC9, { KEY_ROTATE_LOCK_TOGGLE } }, /* rotate-lock key release */
  45. { KE_END }
  46. };
  47. static const struct key_entry intel_vbtn_switchmap[] = {
  48. /*
  49. * SW_DOCK should only be reported for docking stations, but DSDTs using the
  50. * intel-vbtn code, always seem to use this for 2-in-1s / convertibles and set
  51. * SW_DOCK=1 when in laptop-mode (in tandem with setting SW_TABLET_MODE=0).
  52. * This causes userspace to think the laptop is docked to a port-replicator
  53. * and to disable suspend-on-lid-close, which is undesirable.
  54. * Map the dock events to KEY_IGNORE to avoid this broken SW_DOCK reporting.
  55. */
  56. { KE_IGNORE, 0xCA, { .sw = { SW_DOCK, 1 } } }, /* Docked */
  57. { KE_IGNORE, 0xCB, { .sw = { SW_DOCK, 0 } } }, /* Undocked */
  58. { KE_SW, 0xCC, { .sw = { SW_TABLET_MODE, 1 } } }, /* Tablet */
  59. { KE_SW, 0xCD, { .sw = { SW_TABLET_MODE, 0 } } }, /* Laptop */
  60. { KE_END }
  61. };
  62. struct intel_vbtn_priv {
  63. struct mutex mutex; /* Avoid notify_handler() racing with itself */
  64. struct input_dev *buttons_dev;
  65. struct input_dev *switches_dev;
  66. bool dual_accel;
  67. bool has_buttons;
  68. bool has_switches;
  69. bool wakeup_mode;
  70. };
  71. static void detect_tablet_mode(struct device *dev)
  72. {
  73. struct intel_vbtn_priv *priv = dev_get_drvdata(dev);
  74. acpi_handle handle = ACPI_HANDLE(dev);
  75. unsigned long long vgbs;
  76. acpi_status status;
  77. int m;
  78. status = acpi_evaluate_integer(handle, "VGBS", NULL, &vgbs);
  79. if (ACPI_FAILURE(status))
  80. return;
  81. m = !(vgbs & VGBS_TABLET_MODE_FLAGS);
  82. input_report_switch(priv->switches_dev, SW_TABLET_MODE, m);
  83. m = (vgbs & VGBS_DOCK_MODE_FLAG) ? 1 : 0;
  84. input_report_switch(priv->switches_dev, SW_DOCK, m);
  85. input_sync(priv->switches_dev);
  86. }
  87. /*
  88. * Note this unconditionally creates the 2 input_dev-s and sets up
  89. * the sparse-keymaps. Only the registration is conditional on
  90. * have_buttons / have_switches. This is done so that the notify
  91. * handler can always call sparse_keymap_entry_from_scancode()
  92. * on the input_dev-s do determine the event type.
  93. */
  94. static int intel_vbtn_input_setup(struct platform_device *device)
  95. {
  96. struct intel_vbtn_priv *priv = dev_get_drvdata(&device->dev);
  97. int ret;
  98. priv->buttons_dev = devm_input_allocate_device(&device->dev);
  99. if (!priv->buttons_dev)
  100. return -ENOMEM;
  101. ret = sparse_keymap_setup(priv->buttons_dev, intel_vbtn_keymap, NULL);
  102. if (ret)
  103. return ret;
  104. priv->buttons_dev->dev.parent = &device->dev;
  105. priv->buttons_dev->name = "Intel Virtual Buttons";
  106. priv->buttons_dev->id.bustype = BUS_HOST;
  107. if (priv->has_buttons) {
  108. ret = input_register_device(priv->buttons_dev);
  109. if (ret)
  110. return ret;
  111. }
  112. priv->switches_dev = devm_input_allocate_device(&device->dev);
  113. if (!priv->switches_dev)
  114. return -ENOMEM;
  115. ret = sparse_keymap_setup(priv->switches_dev, intel_vbtn_switchmap, NULL);
  116. if (ret)
  117. return ret;
  118. priv->switches_dev->dev.parent = &device->dev;
  119. priv->switches_dev->name = "Intel Virtual Switches";
  120. priv->switches_dev->id.bustype = BUS_HOST;
  121. if (priv->has_switches) {
  122. ret = input_register_device(priv->switches_dev);
  123. if (ret)
  124. return ret;
  125. }
  126. return 0;
  127. }
  128. static void notify_handler(acpi_handle handle, u32 event, void *context)
  129. {
  130. struct platform_device *device = context;
  131. struct intel_vbtn_priv *priv = dev_get_drvdata(&device->dev);
  132. unsigned int val = !(event & 1); /* Even=press, Odd=release */
  133. const struct key_entry *ke, *ke_rel;
  134. struct input_dev *input_dev;
  135. bool autorelease;
  136. int ret;
  137. guard(mutex)(&priv->mutex);
  138. if ((ke = sparse_keymap_entry_from_scancode(priv->buttons_dev, event))) {
  139. if (!priv->has_buttons) {
  140. dev_warn(&device->dev, "Warning: received 0x%02x button event on a device without buttons, please report this.\n",
  141. event);
  142. return;
  143. }
  144. input_dev = priv->buttons_dev;
  145. } else if ((ke = sparse_keymap_entry_from_scancode(priv->switches_dev, event))) {
  146. if (!priv->has_switches) {
  147. /* See dual_accel_detect.h for more info */
  148. if (priv->dual_accel)
  149. return;
  150. dev_info(&device->dev, "Registering Intel Virtual Switches input-dev after receiving a switch event\n");
  151. ret = input_register_device(priv->switches_dev);
  152. if (ret)
  153. return;
  154. priv->has_switches = true;
  155. }
  156. input_dev = priv->switches_dev;
  157. } else {
  158. dev_dbg(&device->dev, "unknown event index 0x%x\n", event);
  159. return;
  160. }
  161. if (priv->wakeup_mode) {
  162. pm_wakeup_hard_event(&device->dev);
  163. /*
  164. * Skip reporting an evdev event for button wake events,
  165. * mirroring how the drivers/acpi/button.c code skips this too.
  166. */
  167. if (ke->type == KE_KEY)
  168. return;
  169. }
  170. /*
  171. * Even press events are autorelease if there is no corresponding odd
  172. * release event, or if the odd event is KE_IGNORE.
  173. */
  174. ke_rel = sparse_keymap_entry_from_scancode(input_dev, event | 1);
  175. autorelease = val && (!ke_rel || ke_rel->type == KE_IGNORE);
  176. sparse_keymap_report_event(input_dev, event, val, autorelease);
  177. }
  178. /*
  179. * There are several laptops (non 2-in-1) models out there which support VGBS,
  180. * but simply always return 0, which we translate to SW_TABLET_MODE=1. This in
  181. * turn causes userspace (libinput) to suppress events from the builtin
  182. * keyboard and touchpad, making the laptop essentially unusable.
  183. *
  184. * Since the problem of wrongly reporting SW_TABLET_MODE=1 in combination
  185. * with libinput, leads to a non-usable system. Where as OTOH many people will
  186. * not even notice when SW_TABLET_MODE is not being reported, a DMI based allow
  187. * list is used here. This list mainly matches on the chassis-type of 2-in-1s.
  188. *
  189. * There are also some 2-in-1s which use the intel-vbtn ACPI interface to report
  190. * SW_TABLET_MODE with a chassis-type of 8 ("Portable") or 10 ("Notebook"),
  191. * these are matched on a per model basis, since many normal laptops with a
  192. * possible broken VGBS ACPI-method also use these chassis-types.
  193. */
  194. static const struct dmi_system_id dmi_switches_allow_list[] = {
  195. {
  196. .matches = {
  197. DMI_EXACT_MATCH(DMI_CHASSIS_TYPE, "31" /* Convertible */),
  198. },
  199. },
  200. {
  201. .matches = {
  202. DMI_EXACT_MATCH(DMI_CHASSIS_TYPE, "32" /* Detachable */),
  203. },
  204. },
  205. {
  206. .matches = {
  207. DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
  208. DMI_MATCH(DMI_PRODUCT_NAME, "Venue 11 Pro 7130"),
  209. },
  210. },
  211. {
  212. .matches = {
  213. DMI_MATCH(DMI_SYS_VENDOR, "Hewlett-Packard"),
  214. DMI_MATCH(DMI_PRODUCT_NAME, "HP Pavilion 13 x360 PC"),
  215. },
  216. },
  217. {
  218. .matches = {
  219. DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
  220. DMI_MATCH(DMI_PRODUCT_NAME, "Switch SA5-271"),
  221. },
  222. },
  223. {
  224. .matches = {
  225. DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
  226. DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 7352"),
  227. },
  228. },
  229. {} /* Array terminator */
  230. };
  231. static bool intel_vbtn_has_switches(acpi_handle handle, bool dual_accel)
  232. {
  233. /* See dual_accel_detect.h for more info */
  234. if (dual_accel)
  235. return false;
  236. if (!dmi_check_system(dmi_switches_allow_list))
  237. return false;
  238. return acpi_has_method(handle, "VGBS");
  239. }
  240. static int intel_vbtn_probe(struct platform_device *device)
  241. {
  242. acpi_handle handle = ACPI_HANDLE(&device->dev);
  243. bool dual_accel, has_buttons, has_switches;
  244. struct intel_vbtn_priv *priv;
  245. acpi_status status;
  246. int err;
  247. dual_accel = dual_accel_detect();
  248. has_buttons = acpi_has_method(handle, "VBDL");
  249. has_switches = intel_vbtn_has_switches(handle, dual_accel);
  250. if (!has_buttons && !has_switches) {
  251. dev_warn(&device->dev, "failed to read Intel Virtual Button driver\n");
  252. return -ENODEV;
  253. }
  254. priv = devm_kzalloc(&device->dev, sizeof(*priv), GFP_KERNEL);
  255. if (!priv)
  256. return -ENOMEM;
  257. dev_set_drvdata(&device->dev, priv);
  258. err = devm_mutex_init(&device->dev, &priv->mutex);
  259. if (err)
  260. return err;
  261. priv->dual_accel = dual_accel;
  262. priv->has_buttons = has_buttons;
  263. priv->has_switches = has_switches;
  264. err = intel_vbtn_input_setup(device);
  265. if (err) {
  266. pr_err("Failed to setup Intel Virtual Button\n");
  267. return err;
  268. }
  269. status = acpi_install_notify_handler(handle,
  270. ACPI_DEVICE_NOTIFY,
  271. notify_handler,
  272. device);
  273. if (ACPI_FAILURE(status))
  274. return -EBUSY;
  275. if (has_buttons) {
  276. status = acpi_evaluate_object(handle, "VBDL", NULL, NULL);
  277. if (ACPI_FAILURE(status))
  278. dev_err(&device->dev, "Error VBDL failed with ACPI status %d\n", status);
  279. }
  280. // Check switches after buttons since VBDL may have side effects.
  281. if (has_switches)
  282. detect_tablet_mode(&device->dev);
  283. device_init_wakeup(&device->dev, true);
  284. /*
  285. * In order for system wakeup to work, the EC GPE has to be marked as
  286. * a wakeup one, so do that here (this setting will persist, but it has
  287. * no effect until the wakeup mask is set for the EC GPE).
  288. */
  289. acpi_ec_mark_gpe_for_wake();
  290. return 0;
  291. }
  292. static void intel_vbtn_remove(struct platform_device *device)
  293. {
  294. acpi_handle handle = ACPI_HANDLE(&device->dev);
  295. device_init_wakeup(&device->dev, false);
  296. acpi_remove_notify_handler(handle, ACPI_DEVICE_NOTIFY, notify_handler);
  297. }
  298. static int intel_vbtn_pm_prepare(struct device *dev)
  299. {
  300. if (device_may_wakeup(dev)) {
  301. struct intel_vbtn_priv *priv = dev_get_drvdata(dev);
  302. priv->wakeup_mode = true;
  303. }
  304. return 0;
  305. }
  306. static void intel_vbtn_pm_complete(struct device *dev)
  307. {
  308. struct intel_vbtn_priv *priv = dev_get_drvdata(dev);
  309. priv->wakeup_mode = false;
  310. }
  311. static int intel_vbtn_pm_resume(struct device *dev)
  312. {
  313. struct intel_vbtn_priv *priv = dev_get_drvdata(dev);
  314. intel_vbtn_pm_complete(dev);
  315. if (priv->has_switches)
  316. detect_tablet_mode(dev);
  317. return 0;
  318. }
  319. static const struct dev_pm_ops intel_vbtn_pm_ops = {
  320. .prepare = intel_vbtn_pm_prepare,
  321. .complete = intel_vbtn_pm_complete,
  322. .resume = intel_vbtn_pm_resume,
  323. .restore = intel_vbtn_pm_resume,
  324. .thaw = intel_vbtn_pm_resume,
  325. };
  326. static struct platform_driver intel_vbtn_pl_driver = {
  327. .driver = {
  328. .name = "intel-vbtn",
  329. .acpi_match_table = intel_vbtn_ids,
  330. .pm = &intel_vbtn_pm_ops,
  331. },
  332. .probe = intel_vbtn_probe,
  333. .remove = intel_vbtn_remove,
  334. };
  335. module_platform_driver(intel_vbtn_pl_driver);