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 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*:*");