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 
24 static const struct property_entry lid_device_props_l17[] = {
25 	PROPERTY_ENTRY_U32("gpe", 0x17),
26 	{},
27 };
28 
29 static const struct property_entry lid_device_props_l4D[] = {
30 	PROPERTY_ENTRY_U32("gpe", 0x4D),
31 	{},
32 };
33 
34 static const struct property_entry lid_device_props_l4F[] = {
35 	PROPERTY_ENTRY_U32("gpe", 0x4F),
36 	{},
37 };
38 
39 static 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  */
48 static 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 
164 struct surface_lid_device {
165 	u32 gpe_number;
166 };
167 
168 static 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 
184 static int __maybe_unused surface_gpe_suspend(struct device *dev)
185 {
186 	return surface_lid_enable_wakeup(dev, true);
187 }
188 
189 static int __maybe_unused surface_gpe_resume(struct device *dev)
190 {
191 	return surface_lid_enable_wakeup(dev, false);
192 }
193 
194 static SIMPLE_DEV_PM_OPS(surface_gpe_pm, surface_gpe_suspend, surface_gpe_resume);
195 
196 static 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 
237 static 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 
248 static 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 
258 static struct platform_device *surface_gpe_device;
259 
260 static 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 
298 err_add:
299 	platform_device_put(pdev);
300 err_alloc:
301 	fwnode_remove_software_node(fwnode);
302 err_node:
303 	platform_driver_unregister(&surface_gpe_driver);
304 	return status;
305 }
306 module_init(surface_gpe_init);
307 
308 static 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 }
316 module_exit(surface_gpe_exit);
317 
318 MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
319 MODULE_DESCRIPTION("Surface GPE/Lid Driver");
320 MODULE_LICENSE("GPL");
321 MODULE_ALIAS("dmi:*:svnMicrosoftCorporation:pnSurface*:*");
322