Merge tag 'sound-5.15-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/tiwai...
[linux-2.6-microblaze.git] / drivers / platform / surface / surface_gpe.c
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*:*");