0007-surface-hotplug.patch 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548
  1. From d99516b57a6b5d02f40423201b0d7f2a9a43fda1 Mon Sep 17 00:00:00 2001
  2. From: Maximilian Luz <luzmaximilian@gmail.com>
  3. Date: Thu, 29 Oct 2020 22:04:38 +0100
  4. Subject: [PATCH] PCI: Allow D3cold for hot-plug ports on Surface Books
  5. The Microsoft Surface Book series of devices have a tablet part (so
  6. called clipboard) that can be detached from the base of the device.
  7. While the clipboard contains the CPU, the base can contain a discrete
  8. GPU (dGPU). This dGPU is connected via a PCIe hot-plug port.
  9. Currently D3cold is disallowed for all hot-plug ports. On the Surface
  10. Book 2 and 3, this leads to increased power consumption during suspend
  11. and when the dGPU is not used (i.e. runtime suspended). This can be
  12. observed not only in battery drain, but also by the dGPU getting notably
  13. warm while suspended and not in D3cold.
  14. Testing shows that the Surface Books behave well with D3cold enabled for
  15. hot-plug ports, alleviating the aforementioned issues. Thus white-list
  16. D3cold for hot-plug ports on those devices.
  17. Note: PCIe hot-plug signalling while the device is in D3cold is handled
  18. via ACPI, out-of-band interrupts, and the surface_hotplug driver
  19. (combined). The device will work without the surface_hotplug driver,
  20. however, device removal/addition will only be detected on device resume.
  21. Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
  22. Patchset: surface-hotplug
  23. ---
  24. drivers/pci/pci.c | 31 +++++++++++++++++++++++++++++--
  25. 1 file changed, 29 insertions(+), 2 deletions(-)
  26. diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
  27. index b2fed944903e..e288e5058520 100644
  28. --- a/drivers/pci/pci.c
  29. +++ b/drivers/pci/pci.c
  30. @@ -2814,6 +2814,32 @@ static const struct dmi_system_id bridge_d3_blacklist[] = {
  31. { }
  32. };
  33. +static const struct dmi_system_id bridge_d3_hotplug_whitelist[] = {
  34. +#ifdef CONFIG_X86
  35. + {
  36. + /*
  37. + * Microsoft Surface Books have a hot-plug root port for the
  38. + * discrete GPU (the device containing it can be detached form
  39. + * the top-part, containing the cpu).
  40. + *
  41. + * If this discrete GPU is not transitioned into D3cold for
  42. + * suspend, the device will become notably warm and also
  43. + * consume a lot more power than desirable.
  44. + *
  45. + * We assume that since those devices have been confirmed
  46. + * working with D3, future Surface devices will too. So let's
  47. + * keep this match generic.
  48. + */
  49. + .ident = "Microsoft Surface",
  50. + .matches = {
  51. + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
  52. + DMI_MATCH(DMI_PRODUCT_NAME, "Surface"),
  53. + },
  54. + },
  55. +#endif
  56. + { }
  57. +};
  58. +
  59. /**
  60. * pci_bridge_d3_possible - Is it possible to put the bridge into D3
  61. * @bridge: Bridge to check
  62. @@ -2854,10 +2880,11 @@ bool pci_bridge_d3_possible(struct pci_dev *bridge)
  63. /*
  64. * Hotplug ports handled natively by the OS were not validated
  65. * by vendors for runtime D3 at least until 2018 because there
  66. - * was no OS support.
  67. + * was no OS support. Explicitly whitelist systems that have
  68. + * been confirmed working.
  69. */
  70. if (bridge->is_hotplug_bridge)
  71. - return false;
  72. + return dmi_check_system(bridge_d3_hotplug_whitelist);
  73. if (dmi_check_system(bridge_d3_blacklist))
  74. return false;
  75. --
  76. 2.29.2
  77. From ac21f0d507c8b1f57566b1bbd947281b708f0fc8 Mon Sep 17 00:00:00 2001
  78. From: Maximilian Luz <luzmaximilian@gmail.com>
  79. Date: Mon, 9 Nov 2020 14:23:00 +0100
  80. Subject: [PATCH] PCI: Run platform power transition on initial D0 entry
  81. On some devices and platforms, the initial platform power state is not
  82. in sync with the power state of the PCI device.
  83. pci_enable_device_flags() updates the state of a PCI device by reading
  84. from the the PCI_PM_CTRL register. This may change the stored power
  85. state of the device without running the appropriate platform power
  86. transition.
  87. Due to the stored power-state being changed, the later call to
  88. pci_set_power_state(..., PCI_D0) in do_pci_enable_device() can evaluate
  89. to a no-op if the stored state has been changed to D0 via that. This
  90. will then prevent the appropriate platform power transition to be run,
  91. which can on some devices and platforms lead to platform and PCI power
  92. state being entirely different, i.e. out-of-sync. On ACPI platforms,
  93. this can lead to power resources not being turned on, even though they
  94. are marked as required for D0.
  95. Specifically, on the Microsoft Surface Book 2 and 3, some ACPI power
  96. regions that should be "on" for the D0 state (and others) are
  97. initialized as "off" in ACPI, whereas the PCI device is in D0. As the
  98. state is updated in pci_enable_device_flags() without ensuring that the
  99. platform state is also updated, the power resource will never be
  100. properly turned on. Instead, it lives in a sort of on-but-marked-as-off
  101. zombie-state, which confuses things down the line when attempting to
  102. transition the device into D3cold: As the resource is already marked as
  103. off, it won't be turned off and the device does not fully enter D3cold,
  104. causing increased power consumption during (runtime-)suspend.
  105. By replacing pci_set_power_state() in do_pci_enable_device() with
  106. pci_power_up(), we can force pci_platform_power_transition() to be
  107. called, which will then check if the platform power state needs updating
  108. and appropriate actions need to be taken.
  109. Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
  110. Patchset: surface-hotplug
  111. ---
  112. drivers/pci/pci.c | 2 +-
  113. 1 file changed, 1 insertion(+), 1 deletion(-)
  114. diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
  115. index e288e5058520..d260c0fcf2d5 100644
  116. --- a/drivers/pci/pci.c
  117. +++ b/drivers/pci/pci.c
  118. @@ -1791,7 +1791,7 @@ static int do_pci_enable_device(struct pci_dev *dev, int bars)
  119. u16 cmd;
  120. u8 pin;
  121. - err = pci_set_power_state(dev, PCI_D0);
  122. + err = pci_power_up(dev);
  123. if (err < 0 && err != -EIO)
  124. return err;
  125. --
  126. 2.29.2
  127. From edebb042089e982ae1623df8eee05ceaae289b3c Mon Sep 17 00:00:00 2001
  128. From: Maximilian Luz <luzmaximilian@gmail.com>
  129. Date: Sat, 31 Oct 2020 20:46:33 +0100
  130. Subject: [PATCH] PCI: Add sysfs attribute for PCI device power state
  131. While most PCI power-states can be queried from user-space via lspci,
  132. this has some limits. Specifically, lspci fails to provide an accurate
  133. value when the device is in D3cold as it has to resume the device before
  134. it can access its power state via the configuration space, leading to it
  135. reporting D0 or another on-state. Thus lspci can, for example, not be
  136. used to diagnose power-consumption issues for devices that can enter
  137. D3cold or to ensure that devices properly enter D3cold at all.
  138. To alleviate this issue, introduce a new sysfs device attribute for the
  139. PCI power state, showing the current power state as seen by the kernel.
  140. Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
  141. Patchset: surface-hotplug
  142. ---
  143. Documentation/ABI/testing/sysfs-bus-pci | 9 +++++++++
  144. drivers/pci/pci-sysfs.c | 12 ++++++++++++
  145. 2 files changed, 21 insertions(+)
  146. diff --git a/Documentation/ABI/testing/sysfs-bus-pci b/Documentation/ABI/testing/sysfs-bus-pci
  147. index 450296cc7948..881040af2611 100644
  148. --- a/Documentation/ABI/testing/sysfs-bus-pci
  149. +++ b/Documentation/ABI/testing/sysfs-bus-pci
  150. @@ -360,3 +360,12 @@ Contact: Heiner Kallweit <hkallweit1@gmail.com>
  151. Description: If ASPM is supported for an endpoint, these files can be
  152. used to disable or enable the individual power management
  153. states. Write y/1/on to enable, n/0/off to disable.
  154. +
  155. +What: /sys/bus/pci/devices/.../power_state
  156. +Date: November 2020
  157. +Contact: Linux PCI developers <linux-pci@vger.kernel.org>
  158. +Description:
  159. + This file contains the current PCI power state of the device.
  160. + The value comes from the PCI kernel device state and can be one
  161. + of: "unknown", "error", "D0", D1", "D2", "D3hot", "D3cold".
  162. + The file is read only.
  163. diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c
  164. index 6d78df981d41..17f186ce8e87 100644
  165. --- a/drivers/pci/pci-sysfs.c
  166. +++ b/drivers/pci/pci-sysfs.c
  167. @@ -124,6 +124,17 @@ static ssize_t cpulistaffinity_show(struct device *dev,
  168. }
  169. static DEVICE_ATTR_RO(cpulistaffinity);
  170. +/* PCI power state */
  171. +static ssize_t power_state_show(struct device *dev,
  172. + struct device_attribute *attr, char *buf)
  173. +{
  174. + struct pci_dev *pci_dev = to_pci_dev(dev);
  175. + pci_power_t state = READ_ONCE(pci_dev->current_state);
  176. +
  177. + return sprintf(buf, "%s\n", pci_power_name(state));
  178. +}
  179. +static DEVICE_ATTR_RO(power_state);
  180. +
  181. /* show resources */
  182. static ssize_t resource_show(struct device *dev, struct device_attribute *attr,
  183. char *buf)
  184. @@ -581,6 +592,7 @@ static ssize_t driver_override_show(struct device *dev,
  185. static DEVICE_ATTR_RW(driver_override);
  186. static struct attribute *pci_dev_attrs[] = {
  187. + &dev_attr_power_state.attr,
  188. &dev_attr_resource.attr,
  189. &dev_attr_vendor.attr,
  190. &dev_attr_device.attr,
  191. --
  192. 2.29.2
  193. From 2f0014c35ac01e1524483b064c0b7e0be21987da Mon Sep 17 00:00:00 2001
  194. From: Maximilian Luz <luzmaximilian@gmail.com>
  195. Date: Mon, 14 Dec 2020 20:50:59 +0100
  196. Subject: [PATCH] platform/x86: Add Surface Hotplug driver
  197. Add a driver to handle out-of-band hot-plug signaling for the discrete
  198. GPU (dGPU) on Microsoft Surface Book 2 and 3 devices. This driver is
  199. required to properly detect hot-plugging of the dGPU and relay the
  200. appropriate signal to the PCIe hot-plug driver core.
  201. Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
  202. Patchset: surface-hotplug
  203. ---
  204. drivers/platform/x86/Kconfig | 12 ++
  205. drivers/platform/x86/Makefile | 1 +
  206. drivers/platform/x86/surface_hotplug.c | 267 +++++++++++++++++++++++++
  207. 3 files changed, 280 insertions(+)
  208. create mode 100644 drivers/platform/x86/surface_hotplug.c
  209. diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
  210. index a9b12f4dcbd1..3e882b1e1f74 100644
  211. --- a/drivers/platform/x86/Kconfig
  212. +++ b/drivers/platform/x86/Kconfig
  213. @@ -910,6 +910,18 @@ config SURFACE_GPE
  214. accordingly. It is required on those devices to allow wake-ups from
  215. suspend by opening the lid.
  216. +config SURFACE_HOTPLUG
  217. + tristate "Surface Hot-Plug System Driver"
  218. + depends on ACPI
  219. + default m
  220. + help
  221. + Driver for the Surface discrete GPU (dGPU) hot-plug system.
  222. +
  223. + This driver provides support for out-of-band hot-plug event signaling
  224. + on Surface Book 2 and 3 devices. This out-of-band signaling is
  225. + required to notify the kernel of any hot-plug events when the dGPU is
  226. + powered off, i.e. in D3cold.
  227. +
  228. config SURFACE_BOOK1_DGPU_SWITCH
  229. tristate "Surface Book 1 dGPU Switch Driver"
  230. depends on ACPI && SYSFS
  231. diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
  232. index 562d83940e7b..2009224dcaae 100644
  233. --- a/drivers/platform/x86/Makefile
  234. +++ b/drivers/platform/x86/Makefile
  235. @@ -87,6 +87,7 @@ obj-$(CONFIG_SURFACE_3_BUTTON) += surface3_button.o
  236. obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o
  237. obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o
  238. obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o
  239. +obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o
  240. obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += sb1_dgpu_sw.o
  241. # MSI
  242. diff --git a/drivers/platform/x86/surface_hotplug.c b/drivers/platform/x86/surface_hotplug.c
  243. new file mode 100644
  244. index 000000000000..572fba30cd77
  245. --- /dev/null
  246. +++ b/drivers/platform/x86/surface_hotplug.c
  247. @@ -0,0 +1,267 @@
  248. +// SPDX-License-Identifier: GPL-2.0+
  249. +/*
  250. + * Surface Book (gen. 2 and later) hot-plug driver.
  251. + *
  252. + * Surface Book devices (can) have a hot-pluggable discrete GPU (dGPU). This
  253. + * driver is responsible for out-of-band hot-plug event signaling on these
  254. + * devices. It is specifically required when the hot-plug device is in D3cold
  255. + * and can thus not generate PCIe hot-plug events itself.
  256. + *
  257. + * Event signaling is handled via ACPI, which will generate the appropriate
  258. + * device-check notifications to be picked up by the PCIe hot-plug driver.
  259. + *
  260. + * Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
  261. + */
  262. +
  263. +#include <linux/acpi.h>
  264. +#include <linux/gpio.h>
  265. +#include <linux/interrupt.h>
  266. +#include <linux/kernel.h>
  267. +#include <linux/module.h>
  268. +#include <linux/mutex.h>
  269. +#include <linux/platform_device.h>
  270. +
  271. +static const struct acpi_gpio_params shps_base_presence_int = { 0, 0, false };
  272. +static const struct acpi_gpio_params shps_base_presence = { 1, 0, false };
  273. +static const struct acpi_gpio_params shps_device_power_int = { 2, 0, false };
  274. +static const struct acpi_gpio_params shps_device_power = { 3, 0, false };
  275. +static const struct acpi_gpio_params shps_device_presence_int = { 4, 0, false };
  276. +static const struct acpi_gpio_params shps_device_presence = { 5, 0, false };
  277. +
  278. +static const struct acpi_gpio_mapping shps_acpi_gpios[] = {
  279. + { "base_presence-int-gpio", &shps_base_presence_int, 1 },
  280. + { "base_presence-gpio", &shps_base_presence, 1 },
  281. + { "device_power-int-gpio", &shps_device_power_int, 1 },
  282. + { "device_power-gpio", &shps_device_power, 1 },
  283. + { "device_presence-int-gpio", &shps_device_presence_int, 1 },
  284. + { "device_presence-gpio", &shps_device_presence, 1 },
  285. + { },
  286. +};
  287. +
  288. +/* 5515a847-ed55-4b27-8352-cd320e10360a */
  289. +static const guid_t shps_dsm_guid =
  290. + GUID_INIT(0x5515a847, 0xed55, 0x4b27, 0x83, 0x52, 0xcd,
  291. + 0x32, 0x0e, 0x10, 0x36, 0x0a);
  292. +
  293. +#define SHPS_DSM_REVISION 1
  294. +
  295. +enum shps_dsm_fn {
  296. + SHPS_DSM_FN_PCI_NUM_ENTRIES = 0x01,
  297. + SHPS_DSM_FN_PCI_GET_ENTRIES = 0x02,
  298. + SHPS_DSM_FN_IRQ_BASE_PRESENCE = 0x03,
  299. + SHPS_DSM_FN_IRQ_DEVICE_POWER = 0x04,
  300. + SHPS_DSM_FN_IRQ_DEVICE_PRESENCE = 0x05,
  301. +};
  302. +
  303. +enum shps_irq_type {
  304. + /* NOTE: Must be in order of DSM function */
  305. + SHPS_IRQ_TYPE_BASE_PRESENCE = 0,
  306. + SHPS_IRQ_TYPE_DEVICE_POWER = 1,
  307. + SHPS_IRQ_TYPE_DEVICE_PRESENCE = 2,
  308. +
  309. + SHPS_NUM_IRQS,
  310. +};
  311. +
  312. +static const char *const shps_gpio_names[] = {
  313. + [SHPS_IRQ_TYPE_BASE_PRESENCE] = "base_presence",
  314. + [SHPS_IRQ_TYPE_DEVICE_POWER] = "device_power",
  315. + [SHPS_IRQ_TYPE_DEVICE_PRESENCE] = "device_presence",
  316. +};
  317. +
  318. +struct shps_device {
  319. + struct mutex lock[SHPS_NUM_IRQS];
  320. + struct gpio_desc *gpio[SHPS_NUM_IRQS];
  321. + unsigned int irq[SHPS_NUM_IRQS];
  322. +};
  323. +
  324. +#define SHPS_IRQ_NOT_PRESENT ((unsigned int)-1)
  325. +
  326. +static void shps_dsm_notify_irq(struct platform_device *pdev,
  327. + enum shps_irq_type type)
  328. +{
  329. + struct shps_device *sdev = platform_get_drvdata(pdev);
  330. + acpi_handle handle = ACPI_HANDLE(&pdev->dev);
  331. + union acpi_object *result;
  332. + union acpi_object param;
  333. + int value;
  334. +
  335. + mutex_lock(&sdev->lock[type]);
  336. +
  337. + value = gpiod_get_value_cansleep(sdev->gpio[type]);
  338. + if (value < 0) {
  339. + mutex_unlock(&sdev->lock[type]);
  340. + dev_err(&pdev->dev, "failed to get gpio: %d (irq=%d)\n",
  341. + type, value);
  342. + return;
  343. + }
  344. +
  345. + dev_dbg(&pdev->dev, "IRQ notification via DSM (irq=%d, value=%d)\n",
  346. + type, value);
  347. +
  348. + param.type = ACPI_TYPE_INTEGER;
  349. + param.integer.value = value;
  350. +
  351. + result = acpi_evaluate_dsm(handle, &shps_dsm_guid, SHPS_DSM_REVISION,
  352. + SHPS_DSM_FN_IRQ_BASE_PRESENCE + type, &param);
  353. +
  354. + if (!result) {
  355. + mutex_unlock(&sdev->lock[type]);
  356. + dev_err(&pdev->dev,
  357. + "IRQ notification via DSM failed (irq=%d, gpio=%d)\n",
  358. + type, value);
  359. + return;
  360. + }
  361. +
  362. + if (result->type != ACPI_TYPE_BUFFER) {
  363. + dev_err(&pdev->dev,
  364. + "IRQ notification via DSM failed: unexpected result type (irq=%d, gpio=%d)\n",
  365. + type, value);
  366. + }
  367. +
  368. + if (result->buffer.length != 1 || result->buffer.pointer[0] != 0) {
  369. + dev_err(&pdev->dev,
  370. + "IRQ notification via DSM failed: unexpected result value (irq=%d, gpio=%d)\n",
  371. + type, value);
  372. + }
  373. +
  374. + mutex_unlock(&sdev->lock[type]);
  375. + ACPI_FREE(result);
  376. +}
  377. +
  378. +static irqreturn_t shps_handle_irq(int irq, void *data)
  379. +{
  380. + struct platform_device *pdev = data;
  381. + struct shps_device *sdev = platform_get_drvdata(pdev);
  382. + int type;
  383. +
  384. + /* Figure out which IRQ we're handling. */
  385. + for (type = 0; type < SHPS_NUM_IRQS; type++)
  386. + if (irq == sdev->irq[type])
  387. + break;
  388. +
  389. + /* We should have found our interrupt, if not: this is a bug. */
  390. + if (WARN(type >= SHPS_NUM_IRQS, "invalid IRQ number: %d\n", irq))
  391. + return IRQ_HANDLED;
  392. +
  393. + /* Forward interrupt to ACPI via DSM. */
  394. + shps_dsm_notify_irq(pdev, type);
  395. + return IRQ_HANDLED;
  396. +}
  397. +
  398. +static int shps_setup_irq(struct platform_device *pdev, enum shps_irq_type type)
  399. +{
  400. + unsigned long flags = IRQF_ONESHOT | IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING;
  401. + struct shps_device *sdev = platform_get_drvdata(pdev);
  402. + struct gpio_desc *gpiod;
  403. + acpi_handle handle = ACPI_HANDLE(&pdev->dev);
  404. + const char *irq_name;
  405. + const int dsm = SHPS_DSM_FN_IRQ_BASE_PRESENCE + type;
  406. + int status, irq;
  407. +
  408. + /* Initialize as "not present". */
  409. + sdev->gpio[type] = NULL;
  410. + sdev->irq[type] = SHPS_IRQ_NOT_PRESENT;
  411. +
  412. + /* Only set up interrupts that we actually need. */
  413. + if (!acpi_check_dsm(handle, &shps_dsm_guid, SHPS_DSM_REVISION, BIT(dsm))) {
  414. + dev_dbg(&pdev->dev, "IRQ notification via DSM not present (irq=%d)\n",
  415. + type);
  416. + return 0;
  417. + }
  418. +
  419. + gpiod = devm_gpiod_get(&pdev->dev, shps_gpio_names[type], GPIOD_ASIS);
  420. + if (IS_ERR(gpiod))
  421. + return PTR_ERR(gpiod);
  422. +
  423. + irq = gpiod_to_irq(gpiod);
  424. + if (irq < 0)
  425. + return irq;
  426. +
  427. + irq_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "shps-irq-%d", type);
  428. + if (!irq_name)
  429. + return -ENOMEM;
  430. +
  431. + status = devm_request_threaded_irq(&pdev->dev, irq, NULL, shps_handle_irq,
  432. + flags, irq_name, pdev);
  433. + if (status)
  434. + return status;
  435. +
  436. + dev_dbg(&pdev->dev, "set up irq %d as type %d\n", irq, type);
  437. +
  438. + sdev->gpio[type] = gpiod;
  439. + sdev->irq[type] = irq;
  440. +
  441. + return 0;
  442. +}
  443. +
  444. +static int surface_hotplug_probe(struct platform_device *pdev)
  445. +{
  446. + struct shps_device *sdev;
  447. + int status, i;
  448. +
  449. + if (gpiod_count(&pdev->dev, NULL) < 0)
  450. + return -ENODEV;
  451. +
  452. + status = devm_acpi_dev_add_driver_gpios(&pdev->dev, shps_acpi_gpios);
  453. + if (status)
  454. + return status;
  455. +
  456. + sdev = devm_kzalloc(&pdev->dev, sizeof(*sdev), GFP_KERNEL);
  457. + if (!sdev)
  458. + return -ENOMEM;
  459. +
  460. + platform_set_drvdata(pdev, sdev);
  461. +
  462. + /* Set up IRQs. */
  463. + for (i = 0; i < SHPS_NUM_IRQS; i++) {
  464. + mutex_init(&sdev->lock[i]);
  465. +
  466. + status = shps_setup_irq(pdev, i);
  467. + if (status) {
  468. + dev_err(&pdev->dev, "failed to set up IRQ %d: %d\n",
  469. + i, status);
  470. + return status;
  471. + }
  472. + }
  473. +
  474. + /* Ensure everything is up-to-date. */
  475. + for (i = 0; i < SHPS_NUM_IRQS; i++)
  476. + if (sdev->irq[i] != SHPS_IRQ_NOT_PRESENT)
  477. + shps_dsm_notify_irq(pdev, i);
  478. +
  479. + return 0;
  480. +}
  481. +
  482. +static int surface_hotplug_remove(struct platform_device *pdev)
  483. +{
  484. + struct shps_device *sdev = platform_get_drvdata(pdev);
  485. + int i;
  486. +
  487. + /* Ensure that IRQs have been fully handled and won't trigger any more. */
  488. + for (i = 0; i < SHPS_NUM_IRQS; i++)
  489. + if (sdev->irq[i] != SHPS_IRQ_NOT_PRESENT)
  490. + disable_irq(sdev->irq[i]);
  491. +
  492. + return 0;
  493. +}
  494. +
  495. +static const struct acpi_device_id surface_hotplug_acpi_match[] = {
  496. + { "MSHW0153", 0 },
  497. + { },
  498. +};
  499. +MODULE_DEVICE_TABLE(acpi, surface_hotplug_acpi_match);
  500. +
  501. +static struct platform_driver surface_hotplug_driver = {
  502. + .probe = surface_hotplug_probe,
  503. + .remove = surface_hotplug_remove,
  504. + .driver = {
  505. + .name = "surface_hotplug",
  506. + .acpi_match_table = surface_hotplug_acpi_match,
  507. + .probe_type = PROBE_PREFER_ASYNCHRONOUS,
  508. + },
  509. +};
  510. +module_platform_driver(surface_hotplug_driver);
  511. +
  512. +MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
  513. +MODULE_DESCRIPTION("Surface Hot-Plug Signaling Driver for Surface Book Devices");
  514. +MODULE_LICENSE("GPL");
  515. --
  516. 2.29.2