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 void 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
279static struct platform_driver surface_gpe_driver = {
280	.probe = surface_gpe_probe,
281	.remove_new = surface_gpe_remove,
282	.driver = {
283		.name = "surface_gpe",
284		.pm = &surface_gpe_pm,
285		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
286	},
287};
288
289static struct platform_device *surface_gpe_device;
290
291static int __init surface_gpe_init(void)
292{
293	const struct dmi_system_id *match;
294	struct platform_device *pdev;
295	struct fwnode_handle *fwnode;
296	int status;
297
298	match = dmi_first_match(dmi_lid_device_table);
299	if (!match) {
300		pr_info("no compatible Microsoft Surface device found, exiting\n");
301		return -ENODEV;
302	}
303
304	status = platform_driver_register(&surface_gpe_driver);
305	if (status)
306		return status;
307
308	fwnode = fwnode_create_software_node(match->driver_data, NULL);
309	if (IS_ERR(fwnode)) {
310		status = PTR_ERR(fwnode);
311		goto err_node;
312	}
313
314	pdev = platform_device_alloc("surface_gpe", PLATFORM_DEVID_NONE);
315	if (!pdev) {
316		status = -ENOMEM;
317		goto err_alloc;
318	}
319
320	pdev->dev.fwnode = fwnode;
321
322	status = platform_device_add(pdev);
323	if (status)
324		goto err_add;
325
326	surface_gpe_device = pdev;
327	return 0;
328
329err_add:
330	platform_device_put(pdev);
331err_alloc:
332	fwnode_remove_software_node(fwnode);
333err_node:
334	platform_driver_unregister(&surface_gpe_driver);
335	return status;
336}
337module_init(surface_gpe_init);
338
339static void __exit surface_gpe_exit(void)
340{
341	struct fwnode_handle *fwnode = surface_gpe_device->dev.fwnode;
342
343	platform_device_unregister(surface_gpe_device);
344	platform_driver_unregister(&surface_gpe_driver);
345	fwnode_remove_software_node(fwnode);
346}
347module_exit(surface_gpe_exit);
348
349MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
350MODULE_DESCRIPTION("Surface GPE/Lid Driver");
351MODULE_LICENSE("GPL");
352MODULE_ALIAS("dmi:*:svnMicrosoftCorporation:pnSurface*:*");