Linux Audio

Check our new training course

Loading...
v6.2
  1// SPDX-License-Identifier: GPL-2.0-or-later
  2/*
  3 * Surface GPE/Lid driver to enable wakeup from suspend via the lid by
  4 * properly configuring the respective GPEs. Required for wakeup via lid on
  5 * newer Intel-based Microsoft Surface devices.
  6 *
  7 * Copyright (C) 2020-2022 Maximilian Luz <luzmaximilian@gmail.com>
  8 */
  9
 10#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
 11
 12#include <linux/acpi.h>
 13#include <linux/dmi.h>
 14#include <linux/kernel.h>
 15#include <linux/module.h>
 16#include <linux/platform_device.h>
 17
 18/*
 19 * Note: The GPE numbers for the lid devices found below have been obtained
 20 *       from ACPI/the DSDT table, specifically from the GPE handler for the
 21 *       lid.
 22 */
 23
 24static const struct property_entry lid_device_props_l17[] = {
 25	PROPERTY_ENTRY_U32("gpe", 0x17),
 26	{},
 27};
 28
 29static const struct property_entry lid_device_props_l4B[] = {
 30	PROPERTY_ENTRY_U32("gpe", 0x4B),
 31	{},
 32};
 33
 34static const struct property_entry lid_device_props_l4D[] = {
 35	PROPERTY_ENTRY_U32("gpe", 0x4D),
 36	{},
 37};
 38
 39static const struct property_entry lid_device_props_l4F[] = {
 40	PROPERTY_ENTRY_U32("gpe", 0x4F),
 41	{},
 42};
 43
 44static const struct property_entry lid_device_props_l57[] = {
 45	PROPERTY_ENTRY_U32("gpe", 0x57),
 46	{},
 47};
 48
 49/*
 50 * Note: When changing this, don't forget to check that the MODULE_ALIAS below
 51 *       still fits.
 52 */
 53static const struct dmi_system_id dmi_lid_device_table[] = {
 54	{
 55		.ident = "Surface Pro 4",
 56		.matches = {
 57			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
 58			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"),
 59		},
 60		.driver_data = (void *)lid_device_props_l17,
 61	},
 62	{
 63		.ident = "Surface Pro 5",
 64		.matches = {
 65			/*
 66			 * We match for SKU here due to generic product name
 67			 * "Surface Pro".
 68			 */
 69			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
 70			DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"),
 71		},
 72		.driver_data = (void *)lid_device_props_l4F,
 73	},
 74	{
 75		.ident = "Surface Pro 5 (LTE)",
 76		.matches = {
 77			/*
 78			 * We match for SKU here due to generic product name
 79			 * "Surface Pro"
 80			 */
 81			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
 82			DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"),
 83		},
 84		.driver_data = (void *)lid_device_props_l4F,
 85	},
 86	{
 87		.ident = "Surface Pro 6",
 88		.matches = {
 89			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
 90			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"),
 91		},
 92		.driver_data = (void *)lid_device_props_l4F,
 93	},
 94	{
 95		.ident = "Surface Pro 7",
 96		.matches = {
 97			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
 98			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 7"),
 99		},
100		.driver_data = (void *)lid_device_props_l4D,
101	},
102	{
103		.ident = "Surface Pro 8",
104		.matches = {
105			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
106			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 8"),
107		},
108		.driver_data = (void *)lid_device_props_l4B,
109	},
110	{
111		.ident = "Surface Book 1",
112		.matches = {
113			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
114			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"),
115		},
116		.driver_data = (void *)lid_device_props_l17,
117	},
118	{
119		.ident = "Surface Book 2",
120		.matches = {
121			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
122			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"),
123		},
124		.driver_data = (void *)lid_device_props_l17,
125	},
126	{
127		.ident = "Surface Book 3",
128		.matches = {
129			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
130			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 3"),
131		},
132		.driver_data = (void *)lid_device_props_l4D,
133	},
134	{
135		.ident = "Surface Laptop 1",
136		.matches = {
137			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
138			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"),
139		},
140		.driver_data = (void *)lid_device_props_l57,
141	},
142	{
143		.ident = "Surface Laptop 2",
144		.matches = {
145			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
146			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"),
147		},
148		.driver_data = (void *)lid_device_props_l57,
149	},
150	{
151		.ident = "Surface Laptop 3 (Intel 13\")",
152		.matches = {
153			/*
154			 * We match for SKU here due to different variants: The
155			 * AMD (15") version does not rely on GPEs.
156			 */
157			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
158			DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_3_1867:1868"),
159		},
160		.driver_data = (void *)lid_device_props_l4D,
161	},
162	{
163		.ident = "Surface Laptop 3 (Intel 15\")",
164		.matches = {
165			/*
166			 * We match for SKU here due to different variants: The
167			 * AMD (15") version does not rely on GPEs.
168			 */
169			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
170			DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_3_1872"),
171		},
172		.driver_data = (void *)lid_device_props_l4D,
173	},
174	{
175		.ident = "Surface Laptop 4 (Intel 13\")",
176		.matches = {
177			/*
178			 * We match for SKU here due to different variants: The
179			 * AMD (15") version does not rely on GPEs.
180			 */
181			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
182			DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1950:1951"),
183		},
184		.driver_data = (void *)lid_device_props_l4B,
185	},
186	{
187		.ident = "Surface Laptop Studio",
188		.matches = {
189			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
190			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop Studio"),
191		},
192		.driver_data = (void *)lid_device_props_l4B,
193	},
194	{ }
195};
196
197struct surface_lid_device {
198	u32 gpe_number;
199};
200
201static int surface_lid_enable_wakeup(struct device *dev, bool enable)
202{
203	const struct surface_lid_device *lid = dev_get_drvdata(dev);
204	int action = enable ? ACPI_GPE_ENABLE : ACPI_GPE_DISABLE;
205	acpi_status status;
206
207	status = acpi_set_gpe_wake_mask(NULL, lid->gpe_number, action);
208	if (ACPI_FAILURE(status)) {
209		dev_err(dev, "failed to set GPE wake mask: %s\n",
210			acpi_format_exception(status));
211		return -EINVAL;
212	}
213
214	return 0;
215}
216
217static int __maybe_unused surface_gpe_suspend(struct device *dev)
218{
219	return surface_lid_enable_wakeup(dev, true);
220}
221
222static int __maybe_unused surface_gpe_resume(struct device *dev)
223{
224	return surface_lid_enable_wakeup(dev, false);
225}
226
227static SIMPLE_DEV_PM_OPS(surface_gpe_pm, surface_gpe_suspend, surface_gpe_resume);
228
229static int surface_gpe_probe(struct platform_device *pdev)
230{
231	struct surface_lid_device *lid;
232	u32 gpe_number;
233	acpi_status status;
234	int ret;
235
236	ret = device_property_read_u32(&pdev->dev, "gpe", &gpe_number);
237	if (ret) {
238		dev_err(&pdev->dev, "failed to read 'gpe' property: %d\n", ret);
239		return ret;
240	}
241
242	lid = devm_kzalloc(&pdev->dev, sizeof(*lid), GFP_KERNEL);
243	if (!lid)
244		return -ENOMEM;
245
246	lid->gpe_number = gpe_number;
247	platform_set_drvdata(pdev, lid);
248
249	status = acpi_mark_gpe_for_wake(NULL, gpe_number);
250	if (ACPI_FAILURE(status)) {
251		dev_err(&pdev->dev, "failed to mark GPE for wake: %s\n",
252			acpi_format_exception(status));
253		return -EINVAL;
254	}
255
256	status = acpi_enable_gpe(NULL, gpe_number);
257	if (ACPI_FAILURE(status)) {
258		dev_err(&pdev->dev, "failed to enable GPE: %s\n",
259			acpi_format_exception(status));
260		return -EINVAL;
261	}
262
263	ret = surface_lid_enable_wakeup(&pdev->dev, false);
264	if (ret)
265		acpi_disable_gpe(NULL, gpe_number);
266
267	return ret;
268}
269
270static int surface_gpe_remove(struct platform_device *pdev)
271{
272	struct surface_lid_device *lid = dev_get_drvdata(&pdev->dev);
273
274	/* restore default behavior without this module */
275	surface_lid_enable_wakeup(&pdev->dev, false);
276	acpi_disable_gpe(NULL, lid->gpe_number);
277
278	return 0;
279}
280
281static struct platform_driver surface_gpe_driver = {
282	.probe = surface_gpe_probe,
283	.remove = surface_gpe_remove,
284	.driver = {
285		.name = "surface_gpe",
286		.pm = &surface_gpe_pm,
287		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
288	},
289};
290
291static struct platform_device *surface_gpe_device;
292
293static int __init surface_gpe_init(void)
294{
295	const struct dmi_system_id *match;
296	struct platform_device *pdev;
297	struct fwnode_handle *fwnode;
298	int status;
299
300	match = dmi_first_match(dmi_lid_device_table);
301	if (!match) {
302		pr_info("no compatible Microsoft Surface device found, exiting\n");
303		return -ENODEV;
304	}
305
306	status = platform_driver_register(&surface_gpe_driver);
307	if (status)
308		return status;
309
310	fwnode = fwnode_create_software_node(match->driver_data, NULL);
311	if (IS_ERR(fwnode)) {
312		status = PTR_ERR(fwnode);
313		goto err_node;
314	}
315
316	pdev = platform_device_alloc("surface_gpe", PLATFORM_DEVID_NONE);
317	if (!pdev) {
318		status = -ENOMEM;
319		goto err_alloc;
320	}
321
322	pdev->dev.fwnode = fwnode;
323
324	status = platform_device_add(pdev);
325	if (status)
326		goto err_add;
327
328	surface_gpe_device = pdev;
329	return 0;
330
331err_add:
332	platform_device_put(pdev);
333err_alloc:
334	fwnode_remove_software_node(fwnode);
335err_node:
336	platform_driver_unregister(&surface_gpe_driver);
337	return status;
338}
339module_init(surface_gpe_init);
340
341static void __exit surface_gpe_exit(void)
342{
343	struct fwnode_handle *fwnode = surface_gpe_device->dev.fwnode;
344
345	platform_device_unregister(surface_gpe_device);
346	platform_driver_unregister(&surface_gpe_driver);
347	fwnode_remove_software_node(fwnode);
348}
349module_exit(surface_gpe_exit);
350
351MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
352MODULE_DESCRIPTION("Surface GPE/Lid Driver");
353MODULE_LICENSE("GPL");
354MODULE_ALIAS("dmi:*:svnMicrosoftCorporation:pnSurface*:*");
v5.14.15
  1// SPDX-License-Identifier: GPL-2.0-or-later
  2/*
  3 * Surface GPE/Lid driver to enable wakeup from suspend via the lid by
  4 * properly configuring the respective GPEs. Required for wakeup via lid on
  5 * newer Intel-based Microsoft Surface devices.
  6 *
  7 * Copyright (C) 2020 Maximilian Luz <luzmaximilian@gmail.com>
  8 */
  9
 10#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
 11
 12#include <linux/acpi.h>
 13#include <linux/dmi.h>
 14#include <linux/kernel.h>
 15#include <linux/module.h>
 16#include <linux/platform_device.h>
 17
 18/*
 19 * Note: The GPE numbers for the lid devices found below have been obtained
 20 *       from ACPI/the DSDT table, specifically from the GPE handler for the
 21 *       lid.
 22 */
 23
 24static const struct property_entry lid_device_props_l17[] = {
 25	PROPERTY_ENTRY_U32("gpe", 0x17),
 26	{},
 27};
 28
 
 
 
 
 
 29static const struct property_entry lid_device_props_l4D[] = {
 30	PROPERTY_ENTRY_U32("gpe", 0x4D),
 31	{},
 32};
 33
 34static const struct property_entry lid_device_props_l4F[] = {
 35	PROPERTY_ENTRY_U32("gpe", 0x4F),
 36	{},
 37};
 38
 39static const struct property_entry lid_device_props_l57[] = {
 40	PROPERTY_ENTRY_U32("gpe", 0x57),
 41	{},
 42};
 43
 44/*
 45 * Note: When changing this, don't forget to check that the MODULE_ALIAS below
 46 *       still fits.
 47 */
 48static const struct dmi_system_id dmi_lid_device_table[] = {
 49	{
 50		.ident = "Surface Pro 4",
 51		.matches = {
 52			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
 53			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"),
 54		},
 55		.driver_data = (void *)lid_device_props_l17,
 56	},
 57	{
 58		.ident = "Surface Pro 5",
 59		.matches = {
 60			/*
 61			 * We match for SKU here due to generic product name
 62			 * "Surface Pro".
 63			 */
 64			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
 65			DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"),
 66		},
 67		.driver_data = (void *)lid_device_props_l4F,
 68	},
 69	{
 70		.ident = "Surface Pro 5 (LTE)",
 71		.matches = {
 72			/*
 73			 * We match for SKU here due to generic product name
 74			 * "Surface Pro"
 75			 */
 76			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
 77			DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"),
 78		},
 79		.driver_data = (void *)lid_device_props_l4F,
 80	},
 81	{
 82		.ident = "Surface Pro 6",
 83		.matches = {
 84			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
 85			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"),
 86		},
 87		.driver_data = (void *)lid_device_props_l4F,
 88	},
 89	{
 90		.ident = "Surface Pro 7",
 91		.matches = {
 92			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
 93			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 7"),
 94		},
 95		.driver_data = (void *)lid_device_props_l4D,
 96	},
 97	{
 
 
 
 
 
 
 
 
 98		.ident = "Surface Book 1",
 99		.matches = {
100			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
101			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"),
102		},
103		.driver_data = (void *)lid_device_props_l17,
104	},
105	{
106		.ident = "Surface Book 2",
107		.matches = {
108			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
109			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"),
110		},
111		.driver_data = (void *)lid_device_props_l17,
112	},
113	{
114		.ident = "Surface Book 3",
115		.matches = {
116			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
117			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 3"),
118		},
119		.driver_data = (void *)lid_device_props_l4D,
120	},
121	{
122		.ident = "Surface Laptop 1",
123		.matches = {
124			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
125			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"),
126		},
127		.driver_data = (void *)lid_device_props_l57,
128	},
129	{
130		.ident = "Surface Laptop 2",
131		.matches = {
132			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
133			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"),
134		},
135		.driver_data = (void *)lid_device_props_l57,
136	},
137	{
138		.ident = "Surface Laptop 3 (Intel 13\")",
139		.matches = {
140			/*
141			 * We match for SKU here due to different variants: The
142			 * AMD (15") version does not rely on GPEs.
143			 */
144			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
145			DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_3_1867:1868"),
146		},
147		.driver_data = (void *)lid_device_props_l4D,
148	},
149	{
150		.ident = "Surface Laptop 3 (Intel 15\")",
151		.matches = {
152			/*
153			 * We match for SKU here due to different variants: The
154			 * AMD (15") version does not rely on GPEs.
155			 */
156			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
157			DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_3_1872"),
158		},
159		.driver_data = (void *)lid_device_props_l4D,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
160	},
161	{ }
162};
163
164struct surface_lid_device {
165	u32 gpe_number;
166};
167
168static int surface_lid_enable_wakeup(struct device *dev, bool enable)
169{
170	const struct surface_lid_device *lid = dev_get_drvdata(dev);
171	int action = enable ? ACPI_GPE_ENABLE : ACPI_GPE_DISABLE;
172	acpi_status status;
173
174	status = acpi_set_gpe_wake_mask(NULL, lid->gpe_number, action);
175	if (ACPI_FAILURE(status)) {
176		dev_err(dev, "failed to set GPE wake mask: %s\n",
177			acpi_format_exception(status));
178		return -EINVAL;
179	}
180
181	return 0;
182}
183
184static int __maybe_unused surface_gpe_suspend(struct device *dev)
185{
186	return surface_lid_enable_wakeup(dev, true);
187}
188
189static int __maybe_unused surface_gpe_resume(struct device *dev)
190{
191	return surface_lid_enable_wakeup(dev, false);
192}
193
194static SIMPLE_DEV_PM_OPS(surface_gpe_pm, surface_gpe_suspend, surface_gpe_resume);
195
196static int surface_gpe_probe(struct platform_device *pdev)
197{
198	struct surface_lid_device *lid;
199	u32 gpe_number;
200	acpi_status status;
201	int ret;
202
203	ret = device_property_read_u32(&pdev->dev, "gpe", &gpe_number);
204	if (ret) {
205		dev_err(&pdev->dev, "failed to read 'gpe' property: %d\n", ret);
206		return ret;
207	}
208
209	lid = devm_kzalloc(&pdev->dev, sizeof(*lid), GFP_KERNEL);
210	if (!lid)
211		return -ENOMEM;
212
213	lid->gpe_number = gpe_number;
214	platform_set_drvdata(pdev, lid);
215
216	status = acpi_mark_gpe_for_wake(NULL, gpe_number);
217	if (ACPI_FAILURE(status)) {
218		dev_err(&pdev->dev, "failed to mark GPE for wake: %s\n",
219			acpi_format_exception(status));
220		return -EINVAL;
221	}
222
223	status = acpi_enable_gpe(NULL, gpe_number);
224	if (ACPI_FAILURE(status)) {
225		dev_err(&pdev->dev, "failed to enable GPE: %s\n",
226			acpi_format_exception(status));
227		return -EINVAL;
228	}
229
230	ret = surface_lid_enable_wakeup(&pdev->dev, false);
231	if (ret)
232		acpi_disable_gpe(NULL, gpe_number);
233
234	return ret;
235}
236
237static int surface_gpe_remove(struct platform_device *pdev)
238{
239	struct surface_lid_device *lid = dev_get_drvdata(&pdev->dev);
240
241	/* restore default behavior without this module */
242	surface_lid_enable_wakeup(&pdev->dev, false);
243	acpi_disable_gpe(NULL, lid->gpe_number);
244
245	return 0;
246}
247
248static struct platform_driver surface_gpe_driver = {
249	.probe = surface_gpe_probe,
250	.remove = surface_gpe_remove,
251	.driver = {
252		.name = "surface_gpe",
253		.pm = &surface_gpe_pm,
254		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
255	},
256};
257
258static struct platform_device *surface_gpe_device;
259
260static int __init surface_gpe_init(void)
261{
262	const struct dmi_system_id *match;
263	struct platform_device *pdev;
264	struct fwnode_handle *fwnode;
265	int status;
266
267	match = dmi_first_match(dmi_lid_device_table);
268	if (!match) {
269		pr_info("no compatible Microsoft Surface device found, exiting\n");
270		return -ENODEV;
271	}
272
273	status = platform_driver_register(&surface_gpe_driver);
274	if (status)
275		return status;
276
277	fwnode = fwnode_create_software_node(match->driver_data, NULL);
278	if (IS_ERR(fwnode)) {
279		status = PTR_ERR(fwnode);
280		goto err_node;
281	}
282
283	pdev = platform_device_alloc("surface_gpe", PLATFORM_DEVID_NONE);
284	if (!pdev) {
285		status = -ENOMEM;
286		goto err_alloc;
287	}
288
289	pdev->dev.fwnode = fwnode;
290
291	status = platform_device_add(pdev);
292	if (status)
293		goto err_add;
294
295	surface_gpe_device = pdev;
296	return 0;
297
298err_add:
299	platform_device_put(pdev);
300err_alloc:
301	fwnode_remove_software_node(fwnode);
302err_node:
303	platform_driver_unregister(&surface_gpe_driver);
304	return status;
305}
306module_init(surface_gpe_init);
307
308static void __exit surface_gpe_exit(void)
309{
310	struct fwnode_handle *fwnode = surface_gpe_device->dev.fwnode;
311
312	platform_device_unregister(surface_gpe_device);
313	platform_driver_unregister(&surface_gpe_driver);
314	fwnode_remove_software_node(fwnode);
315}
316module_exit(surface_gpe_exit);
317
318MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
319MODULE_DESCRIPTION("Surface GPE/Lid Driver");
320MODULE_LICENSE("GPL");
321MODULE_ALIAS("dmi:*:svnMicrosoftCorporation:pnSurface*:*");