0004-surface-gpe.patch 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. From 849654292dbcc5e30b1bcf878b6cc313be7eae50 Mon Sep 17 00:00:00 2001
  2. From: Maximilian Luz <luzmaximilian@gmail.com>
  3. Date: Sun, 16 Aug 2020 23:39:56 +0200
  4. Subject: [PATCH] platform/x86: Add Driver to set up lid GPEs on MS Surface
  5. device
  6. Conventionally, wake-up events for a specific device, in our case the
  7. lid device, are managed via the ACPI _PRW field. While this does not
  8. seem strictly necessary based on ACPI spec, the kernel disables GPE
  9. wakeups to avoid non-wakeup interrupts preventing suspend by default and
  10. only enables GPEs associated via the _PRW field with a wake-up capable
  11. device. This behavior has been introduced in commit
  12. f941d3e41da7f86bdb9dcc1977c2bcc6b89bfe47
  13. ACPI: EC / PM: Disable non-wakeup GPEs for suspend-to-idle
  14. and is described in more detail in its commit message.
  15. Unfortunately, on MS Surface devices, there is no _PRW field present on
  16. the lid device, thus no GPE is associated with it, and therefore the GPE
  17. responsible for sending the status-change notification to the lid gets
  18. disabled during suspend, making it impossible to wake the device via the
  19. lid.
  20. This patch introduces a pseudo-device and respective driver which, based
  21. on some DMI matching, mark the corresponding GPE of the lid device for
  22. wake and enable it during suspend. The behavior of this driver models
  23. the behavior of the ACPI/PM core for normal wakeup GPEs, properly
  24. declared via the _PRW field.
  25. Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
  26. Patchset: surface-gpe
  27. ---
  28. drivers/platform/x86/Kconfig | 9 +
  29. drivers/platform/x86/Makefile | 1 +
  30. drivers/platform/x86/surface_gpe.c | 321 +++++++++++++++++++++++++++++
  31. 3 files changed, 331 insertions(+)
  32. create mode 100644 drivers/platform/x86/surface_gpe.c
  33. diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
  34. index 0d91d136bc3b..d9d3c2149e8b 100644
  35. --- a/drivers/platform/x86/Kconfig
  36. +++ b/drivers/platform/x86/Kconfig
  37. @@ -901,6 +901,15 @@ config SURFACE_PRO3_BUTTON
  38. help
  39. This driver handles the power/home/volume buttons on the Microsoft Surface Pro 3/4 tablet.
  40. +config SURFACE_GPE
  41. + tristate "Surface GPE/Lid Driver"
  42. + depends on ACPI
  43. + help
  44. + This driver marks the GPEs related to the ACPI lid device found on
  45. + Microsoft Surface devices as wakeup sources and prepares them
  46. + accordingly. It is required on those devices to allow wake-ups from
  47. + suspend by opening the lid.
  48. +
  49. config MSI_LAPTOP
  50. tristate "MSI Laptop Extras"
  51. depends on ACPI
  52. diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
  53. index 5f823f7eff45..c0d1c753eb3c 100644
  54. --- a/drivers/platform/x86/Makefile
  55. +++ b/drivers/platform/x86/Makefile
  56. @@ -86,6 +86,7 @@ obj-$(CONFIG_SURFACE3_WMI) += surface3-wmi.o
  57. obj-$(CONFIG_SURFACE_3_BUTTON) += surface3_button.o
  58. obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o
  59. obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o
  60. +obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o
  61. # MSI
  62. obj-$(CONFIG_MSI_LAPTOP) += msi-laptop.o
  63. diff --git a/drivers/platform/x86/surface_gpe.c b/drivers/platform/x86/surface_gpe.c
  64. new file mode 100644
  65. index 000000000000..573dc52f568f
  66. --- /dev/null
  67. +++ b/drivers/platform/x86/surface_gpe.c
  68. @@ -0,0 +1,321 @@
  69. +// SPDX-License-Identifier: GPL-2.0-or-later
  70. +/*
  71. + * Surface GPE/Lid driver to enable wakeup from suspend via the lid by
  72. + * properly configuring the respective GPEs. Required for wakeup via lid on
  73. + * newer Intel-based Microsoft Surface devices.
  74. + *
  75. + * Copyright (C) 2020 Maximilian Luz <luzmaximilian@gmail.com>
  76. + */
  77. +
  78. +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
  79. +
  80. +#include <linux/acpi.h>
  81. +#include <linux/dmi.h>
  82. +#include <linux/kernel.h>
  83. +#include <linux/module.h>
  84. +#include <linux/platform_device.h>
  85. +
  86. +/*
  87. + * Note: The GPE numbers for the lid devices found below have been obtained
  88. + * from ACPI/the DSDT table, specifically from the GPE handler for the
  89. + * lid.
  90. + */
  91. +
  92. +static const struct property_entry lid_device_props_l17[] = {
  93. + PROPERTY_ENTRY_U32("gpe", 0x17),
  94. + {},
  95. +};
  96. +
  97. +static const struct property_entry lid_device_props_l4D[] = {
  98. + PROPERTY_ENTRY_U32("gpe", 0x4D),
  99. + {},
  100. +};
  101. +
  102. +static const struct property_entry lid_device_props_l4F[] = {
  103. + PROPERTY_ENTRY_U32("gpe", 0x4F),
  104. + {},
  105. +};
  106. +
  107. +static const struct property_entry lid_device_props_l57[] = {
  108. + PROPERTY_ENTRY_U32("gpe", 0x57),
  109. + {},
  110. +};
  111. +
  112. +/*
  113. + * Note: When changing this, don't forget to check that the MODULE_ALIAS below
  114. + * still fits.
  115. + */
  116. +static const struct dmi_system_id dmi_lid_device_table[] = {
  117. + {
  118. + .ident = "Surface Pro 4",
  119. + .matches = {
  120. + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
  121. + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"),
  122. + },
  123. + .driver_data = (void *)lid_device_props_l17,
  124. + },
  125. + {
  126. + .ident = "Surface Pro 5",
  127. + .matches = {
  128. + /*
  129. + * We match for SKU here due to generic product name
  130. + * "Surface Pro".
  131. + */
  132. + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
  133. + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"),
  134. + },
  135. + .driver_data = (void *)lid_device_props_l4F,
  136. + },
  137. + {
  138. + .ident = "Surface Pro 5 (LTE)",
  139. + .matches = {
  140. + /*
  141. + * We match for SKU here due to generic product name
  142. + * "Surface Pro"
  143. + */
  144. + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
  145. + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"),
  146. + },
  147. + .driver_data = (void *)lid_device_props_l4F,
  148. + },
  149. + {
  150. + .ident = "Surface Pro 6",
  151. + .matches = {
  152. + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
  153. + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"),
  154. + },
  155. + .driver_data = (void *)lid_device_props_l4F,
  156. + },
  157. + {
  158. + .ident = "Surface Pro 7",
  159. + .matches = {
  160. + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
  161. + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 7"),
  162. + },
  163. + .driver_data = (void *)lid_device_props_l4D,
  164. + },
  165. + {
  166. + .ident = "Surface Book 1",
  167. + .matches = {
  168. + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
  169. + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"),
  170. + },
  171. + .driver_data = (void *)lid_device_props_l17,
  172. + },
  173. + {
  174. + .ident = "Surface Book 2",
  175. + .matches = {
  176. + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
  177. + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"),
  178. + },
  179. + .driver_data = (void *)lid_device_props_l17,
  180. + },
  181. + {
  182. + .ident = "Surface Book 3",
  183. + .matches = {
  184. + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
  185. + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 3"),
  186. + },
  187. + .driver_data = (void *)lid_device_props_l4D,
  188. + },
  189. + {
  190. + .ident = "Surface Laptop 1",
  191. + .matches = {
  192. + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
  193. + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"),
  194. + },
  195. + .driver_data = (void *)lid_device_props_l57,
  196. + },
  197. + {
  198. + .ident = "Surface Laptop 2",
  199. + .matches = {
  200. + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
  201. + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"),
  202. + },
  203. + .driver_data = (void *)lid_device_props_l57,
  204. + },
  205. + {
  206. + .ident = "Surface Laptop 3 (Intel 13\")",
  207. + .matches = {
  208. + /*
  209. + * We match for SKU here due to different vairants: The
  210. + * AMD (15") version does not rely on GPEs.
  211. + */
  212. + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
  213. + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_3_1867:1868"),
  214. + },
  215. + .driver_data = (void *)lid_device_props_l4D,
  216. + },
  217. + {
  218. + .ident = "Surface Laptop 3 (Intel 15\")",
  219. + .matches = {
  220. + /*
  221. + * We match for SKU here due to different vairants: The
  222. + * AMD (15") version does not rely on GPEs.
  223. + */
  224. + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
  225. + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_3_1872"),
  226. + },
  227. + .driver_data = (void *)lid_device_props_l4D,
  228. + },
  229. + { }
  230. +};
  231. +
  232. +struct surface_lid_device {
  233. + u32 gpe_number;
  234. +};
  235. +
  236. +static int surface_lid_enable_wakeup(struct device *dev, bool enable)
  237. +{
  238. + const struct surface_lid_device *lid = dev_get_drvdata(dev);
  239. + int action = enable ? ACPI_GPE_ENABLE : ACPI_GPE_DISABLE;
  240. + acpi_status status;
  241. +
  242. + status = acpi_set_gpe_wake_mask(NULL, lid->gpe_number, action);
  243. + if (ACPI_FAILURE(status)) {
  244. + dev_err(dev, "failed to set GPE wake mask: %s\n",
  245. + acpi_format_exception(status));
  246. + return -EINVAL;
  247. + }
  248. +
  249. + return 0;
  250. +}
  251. +
  252. +static int surface_gpe_suspend(struct device *dev)
  253. +{
  254. + return surface_lid_enable_wakeup(dev, true);
  255. +}
  256. +
  257. +static int surface_gpe_resume(struct device *dev)
  258. +{
  259. + return surface_lid_enable_wakeup(dev, false);
  260. +}
  261. +
  262. +static SIMPLE_DEV_PM_OPS(surface_gpe_pm, surface_gpe_suspend, surface_gpe_resume);
  263. +
  264. +static int surface_gpe_probe(struct platform_device *pdev)
  265. +{
  266. + struct surface_lid_device *lid;
  267. + u32 gpe_number;
  268. + acpi_status status;
  269. + int ret;
  270. +
  271. + ret = device_property_read_u32(&pdev->dev, "gpe", &gpe_number);
  272. + if (ret) {
  273. + dev_err(&pdev->dev, "failed to read 'gpe' property: %d\n", ret);
  274. + return ret;
  275. + }
  276. +
  277. + lid = devm_kzalloc(&pdev->dev, sizeof(*lid), GFP_KERNEL);
  278. + if (!lid)
  279. + return -ENOMEM;
  280. +
  281. + lid->gpe_number = gpe_number;
  282. + platform_set_drvdata(pdev, lid);
  283. +
  284. + status = acpi_mark_gpe_for_wake(NULL, gpe_number);
  285. + if (ACPI_FAILURE(status)) {
  286. + dev_err(&pdev->dev, "failed to mark GPE for wake: %s\n",
  287. + acpi_format_exception(status));
  288. + return -EINVAL;
  289. + }
  290. +
  291. + status = acpi_enable_gpe(NULL, gpe_number);
  292. + if (ACPI_FAILURE(status)) {
  293. + dev_err(&pdev->dev, "failed to enable GPE: %s\n",
  294. + acpi_format_exception(status));
  295. + return -EINVAL;
  296. + }
  297. +
  298. + ret = surface_lid_enable_wakeup(&pdev->dev, false);
  299. + if (ret)
  300. + acpi_disable_gpe(NULL, gpe_number);
  301. +
  302. + return ret;
  303. +}
  304. +
  305. +static int surface_gpe_remove(struct platform_device *pdev)
  306. +{
  307. + struct surface_lid_device *lid = dev_get_drvdata(&pdev->dev);
  308. +
  309. + /* restore default behavior without this module */
  310. + surface_lid_enable_wakeup(&pdev->dev, false);
  311. + acpi_disable_gpe(NULL, lid->gpe_number);
  312. +
  313. + return 0;
  314. +}
  315. +
  316. +static struct platform_driver surface_gpe_driver = {
  317. + .probe = surface_gpe_probe,
  318. + .remove = surface_gpe_remove,
  319. + .driver = {
  320. + .name = "surface_gpe",
  321. + .pm = &surface_gpe_pm,
  322. + .probe_type = PROBE_PREFER_ASYNCHRONOUS,
  323. + },
  324. +};
  325. +
  326. +static struct platform_device *surface_gpe_device;
  327. +
  328. +static int __init surface_gpe_init(void)
  329. +{
  330. + const struct dmi_system_id *match;
  331. + struct platform_device *pdev;
  332. + struct fwnode_handle *fwnode;
  333. + int status;
  334. +
  335. + match = dmi_first_match(dmi_lid_device_table);
  336. + if (!match) {
  337. + pr_info("no compatible Microsoft Surface device found, exiting\n");
  338. + return -ENODEV;
  339. + }
  340. +
  341. + status = platform_driver_register(&surface_gpe_driver);
  342. + if (status)
  343. + return status;
  344. +
  345. + fwnode = fwnode_create_software_node(match->driver_data, NULL);
  346. + if (IS_ERR(fwnode)) {
  347. + status = PTR_ERR(fwnode);
  348. + goto err_node;
  349. + }
  350. +
  351. + pdev = platform_device_alloc("surface_gpe", PLATFORM_DEVID_NONE);
  352. + if (!pdev) {
  353. + status = -ENOMEM;
  354. + goto err_alloc;
  355. + }
  356. +
  357. + pdev->dev.fwnode = fwnode;
  358. +
  359. + status = platform_device_add(pdev);
  360. + if (status)
  361. + goto err_add;
  362. +
  363. + surface_gpe_device = pdev;
  364. + return 0;
  365. +
  366. +err_add:
  367. + platform_device_put(pdev);
  368. +err_alloc:
  369. + fwnode_remove_software_node(fwnode);
  370. +err_node:
  371. + platform_driver_unregister(&surface_gpe_driver);
  372. + return status;
  373. +}
  374. +module_init(surface_gpe_init);
  375. +
  376. +static void __exit surface_gpe_exit(void)
  377. +{
  378. + struct fwnode_handle *fwnode = surface_gpe_device->dev.fwnode;
  379. +
  380. + platform_device_unregister(surface_gpe_device);
  381. + platform_driver_unregister(&surface_gpe_driver);
  382. + fwnode_remove_software_node(fwnode);
  383. +}
  384. +module_exit(surface_gpe_exit);
  385. +
  386. +MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
  387. +MODULE_DESCRIPTION("Surface GPE/Lid Driver");
  388. +MODULE_LICENSE("GPL");
  389. +MODULE_ALIAS("dmi:*:svnMicrosoftCorporation:pnSurface*:*");
  390. --
  391. 2.29.2