1
2
3
4
5
6
7
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
20
21
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
46
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
62
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
74
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
142
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
154
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
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*:*");
322