Linux Audio

Check our new training course

Loading...
Note: File does not exist in v3.1.
  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*:*");