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