projects/linux·blob master · refs

336 lines · 8.6 KiB

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 <[email protected]>
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
25static const struct property_entry lid_device_props_l17[] = {
26 PROPERTY_ENTRY_U32("gpe", 0x17),
27 {},
28};
29
30static const struct property_entry lid_device_props_l4B[] = {
31 PROPERTY_ENTRY_U32("gpe", 0x4B),
32 {},
33};
34
35static const struct property_entry lid_device_props_l4D[] = {
36 PROPERTY_ENTRY_U32("gpe", 0x4D),
37 {},
38};
39
40static const struct property_entry lid_device_props_l4F[] = {
41 PROPERTY_ENTRY_U32("gpe", 0x4F),
42 {},
43};
44
45static 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 */
54static 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
198struct surface_lid_device {
199 u32 gpe_number;
200};
201
202static 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
218static int __maybe_unused surface_gpe_suspend(struct device *dev)
219{
220 return surface_lid_enable_wakeup(dev, true);
221}
222
223static int __maybe_unused surface_gpe_resume(struct device *dev)
224{
225 return surface_lid_enable_wakeup(dev, false);
226}
227
228static SIMPLE_DEV_PM_OPS(surface_gpe_pm, surface_gpe_suspend, surface_gpe_resume);
229
230static 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
271static 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
280static 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
290static struct platform_device *surface_gpe_device;
291
292static 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}
324module_init(surface_gpe_init);
325
326static void __exit surface_gpe_exit(void)
327{
328 platform_device_unregister(surface_gpe_device);
329 platform_driver_unregister(&surface_gpe_driver);
330}
331module_exit(surface_gpe_exit);
332
333MODULE_AUTHOR("Maximilian Luz <[email protected]>");
334MODULE_DESCRIPTION("Surface GPE/Lid Driver");
335MODULE_LICENSE("GPL");
336MODULE_ALIAS("dmi:*:svnMicrosoftCorporation:pnSurface*:*");