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