1 // SPDX-License-Identifier: GPL-2.0-or-later
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.
7 * Copyright (C) 2020 Maximilian Luz <luzmaximilian@gmail.com>
10 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
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>
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
24 static const struct property_entry lid_device_props_l17
[] = {
25 PROPERTY_ENTRY_U32("gpe", 0x17),
29 static const struct property_entry lid_device_props_l4D
[] = {
30 PROPERTY_ENTRY_U32("gpe", 0x4D),
34 static const struct property_entry lid_device_props_l4F
[] = {
35 PROPERTY_ENTRY_U32("gpe", 0x4F),
39 static const struct property_entry lid_device_props_l57
[] = {
40 PROPERTY_ENTRY_U32("gpe", 0x57),
45 * Note: When changing this, don't forget to check that the MODULE_ALIAS below
48 static const struct dmi_system_id dmi_lid_device_table
[] = {
50 .ident
= "Surface Pro 4",
52 DMI_EXACT_MATCH(DMI_SYS_VENDOR
, "Microsoft Corporation"),
53 DMI_EXACT_MATCH(DMI_PRODUCT_NAME
, "Surface Pro 4"),
55 .driver_data
= (void *)lid_device_props_l17
,
58 .ident
= "Surface Pro 5",
61 * We match for SKU here due to generic product name
64 DMI_EXACT_MATCH(DMI_SYS_VENDOR
, "Microsoft Corporation"),
65 DMI_EXACT_MATCH(DMI_PRODUCT_SKU
, "Surface_Pro_1796"),
67 .driver_data
= (void *)lid_device_props_l4F
,
70 .ident
= "Surface Pro 5 (LTE)",
73 * We match for SKU here due to generic product name
76 DMI_EXACT_MATCH(DMI_SYS_VENDOR
, "Microsoft Corporation"),
77 DMI_EXACT_MATCH(DMI_PRODUCT_SKU
, "Surface_Pro_1807"),
79 .driver_data
= (void *)lid_device_props_l4F
,
82 .ident
= "Surface Pro 6",
84 DMI_EXACT_MATCH(DMI_SYS_VENDOR
, "Microsoft Corporation"),
85 DMI_EXACT_MATCH(DMI_PRODUCT_NAME
, "Surface Pro 6"),
87 .driver_data
= (void *)lid_device_props_l4F
,
90 .ident
= "Surface Pro 7",
92 DMI_EXACT_MATCH(DMI_SYS_VENDOR
, "Microsoft Corporation"),
93 DMI_EXACT_MATCH(DMI_PRODUCT_NAME
, "Surface Pro 7"),
95 .driver_data
= (void *)lid_device_props_l4D
,
98 .ident
= "Surface Book 1",
100 DMI_EXACT_MATCH(DMI_SYS_VENDOR
, "Microsoft Corporation"),
101 DMI_EXACT_MATCH(DMI_PRODUCT_NAME
, "Surface Book"),
103 .driver_data
= (void *)lid_device_props_l17
,
106 .ident
= "Surface Book 2",
108 DMI_EXACT_MATCH(DMI_SYS_VENDOR
, "Microsoft Corporation"),
109 DMI_EXACT_MATCH(DMI_PRODUCT_NAME
, "Surface Book 2"),
111 .driver_data
= (void *)lid_device_props_l17
,
114 .ident
= "Surface Book 3",
116 DMI_EXACT_MATCH(DMI_SYS_VENDOR
, "Microsoft Corporation"),
117 DMI_EXACT_MATCH(DMI_PRODUCT_NAME
, "Surface Book 3"),
119 .driver_data
= (void *)lid_device_props_l4D
,
122 .ident
= "Surface Laptop 1",
124 DMI_EXACT_MATCH(DMI_SYS_VENDOR
, "Microsoft Corporation"),
125 DMI_EXACT_MATCH(DMI_PRODUCT_NAME
, "Surface Laptop"),
127 .driver_data
= (void *)lid_device_props_l57
,
130 .ident
= "Surface Laptop 2",
132 DMI_EXACT_MATCH(DMI_SYS_VENDOR
, "Microsoft Corporation"),
133 DMI_EXACT_MATCH(DMI_PRODUCT_NAME
, "Surface Laptop 2"),
135 .driver_data
= (void *)lid_device_props_l57
,
138 .ident
= "Surface Laptop 3 (Intel 13\")",
141 * We match for SKU here due to different variants: The
142 * AMD (15") version does not rely on GPEs.
144 DMI_EXACT_MATCH(DMI_SYS_VENDOR
, "Microsoft Corporation"),
145 DMI_EXACT_MATCH(DMI_PRODUCT_SKU
, "Surface_Laptop_3_1867:1868"),
147 .driver_data
= (void *)lid_device_props_l4D
,
150 .ident
= "Surface Laptop 3 (Intel 15\")",
153 * We match for SKU here due to different variants: The
154 * AMD (15") version does not rely on GPEs.
156 DMI_EXACT_MATCH(DMI_SYS_VENDOR
, "Microsoft Corporation"),
157 DMI_EXACT_MATCH(DMI_PRODUCT_SKU
, "Surface_Laptop_3_1872"),
159 .driver_data
= (void *)lid_device_props_l4D
,
164 struct surface_lid_device
{
168 static int surface_lid_enable_wakeup(struct device
*dev
, bool enable
)
170 const struct surface_lid_device
*lid
= dev_get_drvdata(dev
);
171 int action
= enable
? ACPI_GPE_ENABLE
: ACPI_GPE_DISABLE
;
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
));
184 static int surface_gpe_suspend(struct device
*dev
)
186 return surface_lid_enable_wakeup(dev
, true);
189 static int surface_gpe_resume(struct device
*dev
)
191 return surface_lid_enable_wakeup(dev
, false);
194 static SIMPLE_DEV_PM_OPS(surface_gpe_pm
, surface_gpe_suspend
, surface_gpe_resume
);
196 static int surface_gpe_probe(struct platform_device
*pdev
)
198 struct surface_lid_device
*lid
;
203 ret
= device_property_read_u32(&pdev
->dev
, "gpe", &gpe_number
);
205 dev_err(&pdev
->dev
, "failed to read 'gpe' property: %d\n", ret
);
209 lid
= devm_kzalloc(&pdev
->dev
, sizeof(*lid
), GFP_KERNEL
);
213 lid
->gpe_number
= gpe_number
;
214 platform_set_drvdata(pdev
, lid
);
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
));
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
));
230 ret
= surface_lid_enable_wakeup(&pdev
->dev
, false);
232 acpi_disable_gpe(NULL
, gpe_number
);
237 static int surface_gpe_remove(struct platform_device
*pdev
)
239 struct surface_lid_device
*lid
= dev_get_drvdata(&pdev
->dev
);
241 /* restore default behavior without this module */
242 surface_lid_enable_wakeup(&pdev
->dev
, false);
243 acpi_disable_gpe(NULL
, lid
->gpe_number
);
248 static struct platform_driver surface_gpe_driver
= {
249 .probe
= surface_gpe_probe
,
250 .remove
= surface_gpe_remove
,
252 .name
= "surface_gpe",
253 .pm
= &surface_gpe_pm
,
254 .probe_type
= PROBE_PREFER_ASYNCHRONOUS
,
258 static struct platform_device
*surface_gpe_device
;
260 static int __init
surface_gpe_init(void)
262 const struct dmi_system_id
*match
;
263 struct platform_device
*pdev
;
264 struct fwnode_handle
*fwnode
;
267 match
= dmi_first_match(dmi_lid_device_table
);
269 pr_info("no compatible Microsoft Surface device found, exiting\n");
273 status
= platform_driver_register(&surface_gpe_driver
);
277 fwnode
= fwnode_create_software_node(match
->driver_data
, NULL
);
278 if (IS_ERR(fwnode
)) {
279 status
= PTR_ERR(fwnode
);
283 pdev
= platform_device_alloc("surface_gpe", PLATFORM_DEVID_NONE
);
289 pdev
->dev
.fwnode
= fwnode
;
291 status
= platform_device_add(pdev
);
295 surface_gpe_device
= pdev
;
299 platform_device_put(pdev
);
301 fwnode_remove_software_node(fwnode
);
303 platform_driver_unregister(&surface_gpe_driver
);
306 module_init(surface_gpe_init
);
308 static void __exit
surface_gpe_exit(void)
310 struct fwnode_handle
*fwnode
= surface_gpe_device
->dev
.fwnode
;
312 platform_device_unregister(surface_gpe_device
);
313 platform_driver_unregister(&surface_gpe_driver
);
314 fwnode_remove_software_node(fwnode
);
316 module_exit(surface_gpe_exit
);
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*:*");