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*:*"); 322