0008-surface-hotplug.patch 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487
  1. From d60ac44982cb1b4c8555a30e411ffec54056e0dc Mon Sep 17 00:00:00 2001
  2. From: Maximilian Luz <luzmaximilian@gmail.com>
  3. Date: Mon, 9 Nov 2020 14:23:00 +0100
  4. Subject: [PATCH] PCI: Run platform power transition on initial D0 entry
  5. On some devices and platforms, the initial platform power state is not
  6. in sync with the power state of the PCI device.
  7. pci_enable_device_flags() updates the state of a PCI device by reading
  8. from the the PCI_PM_CTRL register. This may change the stored power
  9. state of the device without running the appropriate platform power
  10. transition.
  11. Due to the stored power-state being changed, the later call to
  12. pci_set_power_state(..., PCI_D0) in do_pci_enable_device() can evaluate
  13. to a no-op if the stored state has been changed to D0 via that. This
  14. will then prevent the appropriate platform power transition to be run,
  15. which can on some devices and platforms lead to platform and PCI power
  16. state being entirely different, i.e. out-of-sync. On ACPI platforms,
  17. this can lead to power resources not being turned on, even though they
  18. are marked as required for D0.
  19. Specifically, on the Microsoft Surface Book 2 and 3, some ACPI power
  20. regions that should be "on" for the D0 state (and others) are
  21. initialized as "off" in ACPI, whereas the PCI device is in D0. As the
  22. state is updated in pci_enable_device_flags() without ensuring that the
  23. platform state is also updated, the power resource will never be
  24. properly turned on. Instead, it lives in a sort of on-but-marked-as-off
  25. zombie-state, which confuses things down the line when attempting to
  26. transition the device into D3cold: As the resource is already marked as
  27. off, it won't be turned off and the device does not fully enter D3cold,
  28. causing increased power consumption during (runtime-)suspend.
  29. By replacing pci_set_power_state() in do_pci_enable_device() with
  30. pci_power_up(), we can force pci_platform_power_transition() to be
  31. called, which will then check if the platform power state needs updating
  32. and appropriate actions need to be taken.
  33. Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
  34. Patchset: surface-hotplug
  35. ---
  36. drivers/pci/pci.c | 4 +---
  37. 1 file changed, 1 insertion(+), 3 deletions(-)
  38. diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
  39. index 89dece8a4132..fd34c8743cbc 100644
  40. --- a/drivers/pci/pci.c
  41. +++ b/drivers/pci/pci.c
  42. @@ -1596,9 +1596,7 @@ static int do_pci_enable_device(struct pci_dev *dev, int bars)
  43. u16 cmd;
  44. u8 pin;
  45. - err = pci_set_power_state(dev, PCI_D0);
  46. - if (err < 0 && err != -EIO)
  47. - return err;
  48. + pci_power_up(dev);
  49. bridge = pci_upstream_bridge(dev);
  50. if (bridge)
  51. --
  52. 2.30.1
  53. From 79034c754cd06ff12003b5ba38ef24706feb2b7c Mon Sep 17 00:00:00 2001
  54. From: Maximilian Luz <luzmaximilian@gmail.com>
  55. Date: Sat, 31 Oct 2020 20:46:33 +0100
  56. Subject: [PATCH] PCI: Add sysfs attribute for PCI device power state
  57. While most PCI power-states can be queried from user-space via lspci,
  58. this has some limits. Specifically, lspci fails to provide an accurate
  59. value when the device is in D3cold as it has to resume the device before
  60. it can access its power state via the configuration space, leading to it
  61. reporting D0 or another on-state. Thus lspci can, for example, not be
  62. used to diagnose power-consumption issues for devices that can enter
  63. D3cold or to ensure that devices properly enter D3cold at all.
  64. To alleviate this issue, introduce a new sysfs device attribute for the
  65. PCI power state, showing the current power state as seen by the kernel.
  66. Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
  67. Patchset: surface-hotplug
  68. ---
  69. Documentation/ABI/testing/sysfs-bus-pci | 9 +++++++++
  70. drivers/pci/pci-sysfs.c | 12 ++++++++++++
  71. 2 files changed, 21 insertions(+)
  72. diff --git a/Documentation/ABI/testing/sysfs-bus-pci b/Documentation/ABI/testing/sysfs-bus-pci
  73. index 8bfee557e50e..460032b4e950 100644
  74. --- a/Documentation/ABI/testing/sysfs-bus-pci
  75. +++ b/Documentation/ABI/testing/sysfs-bus-pci
  76. @@ -347,3 +347,12 @@ Description:
  77. If the device has any Peer-to-Peer memory registered, this
  78. file contains a '1' if the memory has been published for
  79. use outside the driver that owns the device.
  80. +
  81. +What: /sys/bus/pci/devices/.../power_state
  82. +Date: November 2020
  83. +Contact: Linux PCI developers <linux-pci@vger.kernel.org>
  84. +Description:
  85. + This file contains the current PCI power state of the device.
  86. + The value comes from the PCI kernel device state and can be one
  87. + of: "unknown", "error", "D0", D1", "D2", "D3hot", "D3cold".
  88. + The file is read only.
  89. diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c
  90. index e401f040f157..418927872ae6 100644
  91. --- a/drivers/pci/pci-sysfs.c
  92. +++ b/drivers/pci/pci-sysfs.c
  93. @@ -124,6 +124,17 @@ static ssize_t cpulistaffinity_show(struct device *dev,
  94. }
  95. static DEVICE_ATTR_RO(cpulistaffinity);
  96. +/* PCI power state */
  97. +static ssize_t power_state_show(struct device *dev,
  98. + struct device_attribute *attr, char *buf)
  99. +{
  100. + struct pci_dev *pci_dev = to_pci_dev(dev);
  101. + pci_power_t state = READ_ONCE(pci_dev->current_state);
  102. +
  103. + return sprintf(buf, "%s\n", pci_power_name(state));
  104. +}
  105. +static DEVICE_ATTR_RO(power_state);
  106. +
  107. /* show resources */
  108. static ssize_t resource_show(struct device *dev, struct device_attribute *attr,
  109. char *buf)
  110. @@ -598,6 +609,7 @@ static ssize_t driver_override_show(struct device *dev,
  111. static DEVICE_ATTR_RW(driver_override);
  112. static struct attribute *pci_dev_attrs[] = {
  113. + &dev_attr_power_state.attr,
  114. &dev_attr_resource.attr,
  115. &dev_attr_vendor.attr,
  116. &dev_attr_device.attr,
  117. --
  118. 2.30.1
  119. From 14b245813cd398b61cf0507f7fdd6b54acdb5530 Mon Sep 17 00:00:00 2001
  120. From: Maximilian Luz <luzmaximilian@gmail.com>
  121. Date: Mon, 14 Dec 2020 20:50:59 +0100
  122. Subject: [PATCH] platform/x86: Add Surface Hotplug driver
  123. Add a driver to handle out-of-band hot-plug signaling for the discrete
  124. GPU (dGPU) on Microsoft Surface Book 2 and 3 devices. This driver is
  125. required to properly detect hot-plugging of the dGPU and relay the
  126. appropriate signal to the PCIe hot-plug driver core.
  127. Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
  128. Patchset: surface-hotplug
  129. ---
  130. drivers/platform/x86/Kconfig | 19 ++
  131. drivers/platform/x86/Makefile | 1 +
  132. drivers/platform/x86/surface_hotplug.c | 282 +++++++++++++++++++++++++
  133. 3 files changed, 302 insertions(+)
  134. create mode 100644 drivers/platform/x86/surface_hotplug.c
  135. diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
  136. index b5a103d0ccd2..f0c17b65bb5b 100644
  137. --- a/drivers/platform/x86/Kconfig
  138. +++ b/drivers/platform/x86/Kconfig
  139. @@ -756,6 +756,25 @@ config ACPI_WMI
  140. It is safe to enable this driver even if your DSDT doesn't define
  141. any ACPI-WMI devices.
  142. +config SURFACE_HOTPLUG
  143. + tristate "Surface Hot-Plug Driver"
  144. + depends on GPIOLIB
  145. + help
  146. + Driver for out-of-band hot-plug event signaling on Microsoft Surface
  147. + devices with hot-pluggable PCIe cards.
  148. +
  149. + This driver is used on Surface Book (2 and 3) devices with a
  150. + hot-pluggable discrete GPU (dGPU). When not in use, the dGPU on those
  151. + devices can enter D3cold, which prevents in-band (standard) PCIe
  152. + hot-plug signaling. Thus, without this driver, detaching the base
  153. + containing the dGPU will not correctly update the state of the
  154. + corresponding PCIe device if it is in D3cold. This driver adds support
  155. + for out-of-band hot-plug notifications, ensuring that the device state
  156. + is properly updated even when the device in question is in D3cold.
  157. +
  158. + Select M or Y here, if you want to (fully) support hot-plugging of
  159. + dGPU devices on the Surface Book 2 and/or 3 during D3cold.
  160. +
  161. config WMI_BMOF
  162. tristate "WMI embedded Binary MOF driver"
  163. depends on ACPI_WMI
  164. diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
  165. index 328830619b21..1bdad1722521 100644
  166. --- a/drivers/platform/x86/Makefile
  167. +++ b/drivers/platform/x86/Makefile
  168. @@ -93,6 +93,7 @@ obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o
  169. obj-$(CONFIG_SURFACE_3_BUTTON) += surface3_button.o
  170. obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o
  171. obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o
  172. +obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o
  173. obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += sb1_dgpu_sw.o
  174. obj-$(CONFIG_INTEL_PUNIT_IPC) += intel_punit_ipc.o
  175. obj-$(CONFIG_INTEL_BXTWC_PMIC_TMU) += intel_bxtwc_tmu.o
  176. diff --git a/drivers/platform/x86/surface_hotplug.c b/drivers/platform/x86/surface_hotplug.c
  177. new file mode 100644
  178. index 000000000000..cfcc15cfbacb
  179. --- /dev/null
  180. +++ b/drivers/platform/x86/surface_hotplug.c
  181. @@ -0,0 +1,282 @@
  182. +// SPDX-License-Identifier: GPL-2.0+
  183. +/*
  184. + * Surface Book (2 and later) hot-plug driver.
  185. + *
  186. + * Surface Book devices (can) have a hot-pluggable discrete GPU (dGPU). This
  187. + * driver is responsible for out-of-band hot-plug event signaling on these
  188. + * devices. It is specifically required when the hot-plug device is in D3cold
  189. + * and can thus not generate PCIe hot-plug events itself.
  190. + *
  191. + * Event signaling is handled via ACPI, which will generate the appropriate
  192. + * device-check notifications to be picked up by the PCIe hot-plug driver.
  193. + *
  194. + * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
  195. + */
  196. +
  197. +#include <linux/acpi.h>
  198. +#include <linux/gpio.h>
  199. +#include <linux/interrupt.h>
  200. +#include <linux/kernel.h>
  201. +#include <linux/module.h>
  202. +#include <linux/mutex.h>
  203. +#include <linux/platform_device.h>
  204. +
  205. +static const struct acpi_gpio_params shps_base_presence_int = { 0, 0, false };
  206. +static const struct acpi_gpio_params shps_base_presence = { 1, 0, false };
  207. +static const struct acpi_gpio_params shps_device_power_int = { 2, 0, false };
  208. +static const struct acpi_gpio_params shps_device_power = { 3, 0, false };
  209. +static const struct acpi_gpio_params shps_device_presence_int = { 4, 0, false };
  210. +static const struct acpi_gpio_params shps_device_presence = { 5, 0, false };
  211. +
  212. +static const struct acpi_gpio_mapping shps_acpi_gpios[] = {
  213. + { "base_presence-int-gpio", &shps_base_presence_int, 1 },
  214. + { "base_presence-gpio", &shps_base_presence, 1 },
  215. + { "device_power-int-gpio", &shps_device_power_int, 1 },
  216. + { "device_power-gpio", &shps_device_power, 1 },
  217. + { "device_presence-int-gpio", &shps_device_presence_int, 1 },
  218. + { "device_presence-gpio", &shps_device_presence, 1 },
  219. + { },
  220. +};
  221. +
  222. +/* 5515a847-ed55-4b27-8352-cd320e10360a */
  223. +static const guid_t shps_dsm_guid =
  224. + GUID_INIT(0x5515a847, 0xed55, 0x4b27, 0x83, 0x52, 0xcd, 0x32, 0x0e, 0x10, 0x36, 0x0a);
  225. +
  226. +#define SHPS_DSM_REVISION 1
  227. +
  228. +enum shps_dsm_fn {
  229. + SHPS_DSM_FN_PCI_NUM_ENTRIES = 0x01,
  230. + SHPS_DSM_FN_PCI_GET_ENTRIES = 0x02,
  231. + SHPS_DSM_FN_IRQ_BASE_PRESENCE = 0x03,
  232. + SHPS_DSM_FN_IRQ_DEVICE_POWER = 0x04,
  233. + SHPS_DSM_FN_IRQ_DEVICE_PRESENCE = 0x05,
  234. +};
  235. +
  236. +enum shps_irq_type {
  237. + /* NOTE: Must be in order of enum shps_dsm_fn above. */
  238. + SHPS_IRQ_TYPE_BASE_PRESENCE = 0,
  239. + SHPS_IRQ_TYPE_DEVICE_POWER = 1,
  240. + SHPS_IRQ_TYPE_DEVICE_PRESENCE = 2,
  241. + SHPS_NUM_IRQS,
  242. +};
  243. +
  244. +static const char *const shps_gpio_names[] = {
  245. + [SHPS_IRQ_TYPE_BASE_PRESENCE] = "base_presence",
  246. + [SHPS_IRQ_TYPE_DEVICE_POWER] = "device_power",
  247. + [SHPS_IRQ_TYPE_DEVICE_PRESENCE] = "device_presence",
  248. +};
  249. +
  250. +struct shps_device {
  251. + struct mutex lock[SHPS_NUM_IRQS]; /* Protects update in shps_dsm_notify_irq() */
  252. + struct gpio_desc *gpio[SHPS_NUM_IRQS];
  253. + unsigned int irq[SHPS_NUM_IRQS];
  254. +};
  255. +
  256. +#define SHPS_IRQ_NOT_PRESENT ((unsigned int)-1)
  257. +
  258. +static enum shps_dsm_fn shps_dsm_fn_for_irq(enum shps_irq_type type)
  259. +{
  260. + return SHPS_DSM_FN_IRQ_BASE_PRESENCE + type;
  261. +}
  262. +
  263. +static void shps_dsm_notify_irq(struct platform_device *pdev, enum shps_irq_type type)
  264. +{
  265. + struct shps_device *sdev = platform_get_drvdata(pdev);
  266. + acpi_handle handle = ACPI_HANDLE(&pdev->dev);
  267. + union acpi_object *result;
  268. + union acpi_object param;
  269. + int value;
  270. +
  271. + mutex_lock(&sdev->lock[type]);
  272. +
  273. + value = gpiod_get_value_cansleep(sdev->gpio[type]);
  274. + if (value < 0) {
  275. + mutex_unlock(&sdev->lock[type]);
  276. + dev_err(&pdev->dev, "failed to get gpio: %d (irq=%d)\n", type, value);
  277. + return;
  278. + }
  279. +
  280. + dev_dbg(&pdev->dev, "IRQ notification via DSM (irq=%d, value=%d)\n", type, value);
  281. +
  282. + param.type = ACPI_TYPE_INTEGER;
  283. + param.integer.value = value;
  284. +
  285. + result = acpi_evaluate_dsm(handle, &shps_dsm_guid, SHPS_DSM_REVISION,
  286. + shps_dsm_fn_for_irq(type), &param);
  287. +
  288. + if (!result) {
  289. + dev_err(&pdev->dev, "IRQ notification via DSM failed (irq=%d, gpio=%d)\n",
  290. + type, value);
  291. +
  292. + } else if (result->type != ACPI_TYPE_BUFFER) {
  293. + dev_err(&pdev->dev,
  294. + "IRQ notification via DSM failed: unexpected result type (irq=%d, gpio=%d)\n",
  295. + type, value);
  296. +
  297. + } else if (result->buffer.length != 1 || result->buffer.pointer[0] != 0) {
  298. + dev_err(&pdev->dev,
  299. + "IRQ notification via DSM failed: unexpected result value (irq=%d, gpio=%d)\n",
  300. + type, value);
  301. + }
  302. +
  303. + mutex_unlock(&sdev->lock[type]);
  304. +
  305. + if (result)
  306. + ACPI_FREE(result);
  307. +}
  308. +
  309. +static irqreturn_t shps_handle_irq(int irq, void *data)
  310. +{
  311. + struct platform_device *pdev = data;
  312. + struct shps_device *sdev = platform_get_drvdata(pdev);
  313. + int type;
  314. +
  315. + /* Figure out which IRQ we're handling. */
  316. + for (type = 0; type < SHPS_NUM_IRQS; type++)
  317. + if (irq == sdev->irq[type])
  318. + break;
  319. +
  320. + /* We should have found our interrupt, if not: this is a bug. */
  321. + if (WARN(type >= SHPS_NUM_IRQS, "invalid IRQ number: %d\n", irq))
  322. + return IRQ_HANDLED;
  323. +
  324. + /* Forward interrupt to ACPI via DSM. */
  325. + shps_dsm_notify_irq(pdev, type);
  326. + return IRQ_HANDLED;
  327. +}
  328. +
  329. +static int shps_setup_irq(struct platform_device *pdev, enum shps_irq_type type)
  330. +{
  331. + unsigned long flags = IRQF_ONESHOT | IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING;
  332. + struct shps_device *sdev = platform_get_drvdata(pdev);
  333. + struct gpio_desc *gpiod;
  334. + acpi_handle handle = ACPI_HANDLE(&pdev->dev);
  335. + const char *irq_name;
  336. + const int dsm = shps_dsm_fn_for_irq(type);
  337. + int status, irq;
  338. +
  339. + /*
  340. + * Only set up interrupts that we actually need: The Surface Book 3
  341. + * does not have a DSM for base presence, so don't set up an interrupt
  342. + * for that.
  343. + */
  344. + if (!acpi_check_dsm(handle, &shps_dsm_guid, SHPS_DSM_REVISION, BIT(dsm))) {
  345. + dev_dbg(&pdev->dev, "IRQ notification via DSM not present (irq=%d)\n", type);
  346. + return 0;
  347. + }
  348. +
  349. + gpiod = devm_gpiod_get(&pdev->dev, shps_gpio_names[type], GPIOD_ASIS);
  350. + if (IS_ERR(gpiod))
  351. + return PTR_ERR(gpiod);
  352. +
  353. + irq = gpiod_to_irq(gpiod);
  354. + if (irq < 0)
  355. + return irq;
  356. +
  357. + irq_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "shps-irq-%d", type);
  358. + if (!irq_name)
  359. + return -ENOMEM;
  360. +
  361. + status = devm_request_threaded_irq(&pdev->dev, irq, NULL, shps_handle_irq,
  362. + flags, irq_name, pdev);
  363. + if (status)
  364. + return status;
  365. +
  366. + dev_dbg(&pdev->dev, "set up irq %d as type %d\n", irq, type);
  367. +
  368. + sdev->gpio[type] = gpiod;
  369. + sdev->irq[type] = irq;
  370. +
  371. + return 0;
  372. +}
  373. +
  374. +static int surface_hotplug_remove(struct platform_device *pdev)
  375. +{
  376. + struct shps_device *sdev = platform_get_drvdata(pdev);
  377. + int i;
  378. +
  379. + /* Ensure that IRQs have been fully handled and won't trigger any more. */
  380. + for (i = 0; i < SHPS_NUM_IRQS; i++) {
  381. + if (sdev->irq[i] != SHPS_IRQ_NOT_PRESENT)
  382. + disable_irq(sdev->irq[i]);
  383. +
  384. + mutex_destroy(&sdev->lock[i]);
  385. + }
  386. +
  387. + return 0;
  388. +}
  389. +
  390. +static int surface_hotplug_probe(struct platform_device *pdev)
  391. +{
  392. + struct shps_device *sdev;
  393. + int status, i;
  394. +
  395. + /*
  396. + * The MSHW0153 device is also present on the Surface Laptop 3,
  397. + * however that doesn't have a hot-pluggable PCIe device. It also
  398. + * doesn't have any GPIO interrupts/pins under the MSHW0153, so filter
  399. + * it out here.
  400. + */
  401. + if (gpiod_count(&pdev->dev, NULL) < 0)
  402. + return -ENODEV;
  403. +
  404. + status = devm_acpi_dev_add_driver_gpios(&pdev->dev, shps_acpi_gpios);
  405. + if (status)
  406. + return status;
  407. +
  408. + sdev = devm_kzalloc(&pdev->dev, sizeof(*sdev), GFP_KERNEL);
  409. + if (!sdev)
  410. + return -ENOMEM;
  411. +
  412. + platform_set_drvdata(pdev, sdev);
  413. +
  414. + /*
  415. + * Initialize IRQs so that we can safely call surface_hotplug_remove()
  416. + * on errors.
  417. + */
  418. + for (i = 0; i < SHPS_NUM_IRQS; i++)
  419. + sdev->irq[i] = SHPS_IRQ_NOT_PRESENT;
  420. +
  421. + /* Set up IRQs. */
  422. + for (i = 0; i < SHPS_NUM_IRQS; i++) {
  423. + mutex_init(&sdev->lock[i]);
  424. +
  425. + status = shps_setup_irq(pdev, i);
  426. + if (status) {
  427. + dev_err(&pdev->dev, "failed to set up IRQ %d: %d\n", i, status);
  428. + goto err;
  429. + }
  430. + }
  431. +
  432. + /* Ensure everything is up-to-date. */
  433. + for (i = 0; i < SHPS_NUM_IRQS; i++)
  434. + if (sdev->irq[i] != SHPS_IRQ_NOT_PRESENT)
  435. + shps_dsm_notify_irq(pdev, i);
  436. +
  437. + return 0;
  438. +
  439. +err:
  440. + surface_hotplug_remove(pdev);
  441. + return status;
  442. +}
  443. +
  444. +static const struct acpi_device_id surface_hotplug_acpi_match[] = {
  445. + { "MSHW0153", 0 },
  446. + { },
  447. +};
  448. +MODULE_DEVICE_TABLE(acpi, surface_hotplug_acpi_match);
  449. +
  450. +static struct platform_driver surface_hotplug_driver = {
  451. + .probe = surface_hotplug_probe,
  452. + .remove = surface_hotplug_remove,
  453. + .driver = {
  454. + .name = "surface_hotplug",
  455. + .acpi_match_table = surface_hotplug_acpi_match,
  456. + .probe_type = PROBE_PREFER_ASYNCHRONOUS,
  457. + },
  458. +};
  459. +module_platform_driver(surface_hotplug_driver);
  460. +
  461. +MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
  462. +MODULE_DESCRIPTION("Surface Hot-Plug Signaling Driver for Surface Book Devices");
  463. +MODULE_LICENSE("GPL");
  464. --
  465. 2.30.1