353 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * Surface GPE/Lid driver to enable wakeup from suspend via the lid by4 * properly configuring the respective GPEs. Required for wakeup via lid on5 * 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 ": " fmt11 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 obtained20 * from ACPI/the DSDT table, specifically from the GPE handler for the21 * lid.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_l4B[] = {30 PROPERTY_ENTRY_U32("gpe", 0x4B),31 {},32};33 34static const struct property_entry lid_device_props_l4D[] = {35 PROPERTY_ENTRY_U32("gpe", 0x4D),36 {},37};38 39static const struct property_entry lid_device_props_l4F[] = {40 PROPERTY_ENTRY_U32("gpe", 0x4F),41 {},42};43 44static const struct property_entry lid_device_props_l57[] = {45 PROPERTY_ENTRY_U32("gpe", 0x57),46 {},47};48 49/*50 * Note: When changing this, don't forget to check that the MODULE_ALIAS below51 * still fits.52 */53static const struct dmi_system_id dmi_lid_device_table[] = {54 {55 .ident = "Surface Pro 4",56 .matches = {57 DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),58 DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"),59 },60 .driver_data = (void *)lid_device_props_l17,61 },62 {63 .ident = "Surface Pro 5",64 .matches = {65 /*66 * We match for SKU here due to generic product name67 * "Surface Pro".68 */69 DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),70 DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"),71 },72 .driver_data = (void *)lid_device_props_l4F,73 },74 {75 .ident = "Surface Pro 5 (LTE)",76 .matches = {77 /*78 * We match for SKU here due to generic product name79 * "Surface Pro"80 */81 DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),82 DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"),83 },84 .driver_data = (void *)lid_device_props_l4F,85 },86 {87 .ident = "Surface Pro 6",88 .matches = {89 DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),90 DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"),91 },92 .driver_data = (void *)lid_device_props_l4F,93 },94 {95 .ident = "Surface Pro 7",96 .matches = {97 DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),98 DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 7"),99 },100 .driver_data = (void *)lid_device_props_l4D,101 },102 {103 .ident = "Surface Pro 8",104 .matches = {105 DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),106 DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 8"),107 },108 .driver_data = (void *)lid_device_props_l4B,109 },110 {111 .ident = "Surface Book 1",112 .matches = {113 DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),114 DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"),115 },116 .driver_data = (void *)lid_device_props_l17,117 },118 {119 .ident = "Surface Book 2",120 .matches = {121 DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),122 DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"),123 },124 .driver_data = (void *)lid_device_props_l17,125 },126 {127 .ident = "Surface Book 3",128 .matches = {129 DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),130 DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 3"),131 },132 .driver_data = (void *)lid_device_props_l4D,133 },134 {135 .ident = "Surface Laptop 1",136 .matches = {137 DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),138 DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"),139 },140 .driver_data = (void *)lid_device_props_l57,141 },142 {143 .ident = "Surface Laptop 2",144 .matches = {145 DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),146 DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"),147 },148 .driver_data = (void *)lid_device_props_l57,149 },150 {151 .ident = "Surface Laptop 3 (Intel 13\")",152 .matches = {153 /*154 * We match for SKU here due to different variants: The155 * AMD (15") version does not rely on GPEs.156 */157 DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),158 DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_3_1867:1868"),159 },160 .driver_data = (void *)lid_device_props_l4D,161 },162 {163 .ident = "Surface Laptop 3 (Intel 15\")",164 .matches = {165 /*166 * We match for SKU here due to different variants: The167 * AMD (15") version does not rely on GPEs.168 */169 DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),170 DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_3_1872"),171 },172 .driver_data = (void *)lid_device_props_l4D,173 },174 {175 .ident = "Surface Laptop 4 (Intel 13\")",176 .matches = {177 /*178 * We match for SKU here due to different variants: The179 * AMD (15") version does not rely on GPEs.180 */181 DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),182 DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1950:1951"),183 },184 .driver_data = (void *)lid_device_props_l4B,185 },186 {187 .ident = "Surface Laptop Studio",188 .matches = {189 DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),190 DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop Studio"),191 },192 .driver_data = (void *)lid_device_props_l4B,193 },194 { }195};196 197struct surface_lid_device {198 u32 gpe_number;199};200 201static int surface_lid_enable_wakeup(struct device *dev, bool enable)202{203 const struct surface_lid_device *lid = dev_get_drvdata(dev);204 int action = enable ? ACPI_GPE_ENABLE : ACPI_GPE_DISABLE;205 acpi_status status;206 207 status = acpi_set_gpe_wake_mask(NULL, lid->gpe_number, action);208 if (ACPI_FAILURE(status)) {209 dev_err(dev, "failed to set GPE wake mask: %s\n",210 acpi_format_exception(status));211 return -EINVAL;212 }213 214 return 0;215}216 217static int __maybe_unused surface_gpe_suspend(struct device *dev)218{219 return surface_lid_enable_wakeup(dev, true);220}221 222static int __maybe_unused surface_gpe_resume(struct device *dev)223{224 return surface_lid_enable_wakeup(dev, false);225}226 227static SIMPLE_DEV_PM_OPS(surface_gpe_pm, surface_gpe_suspend, surface_gpe_resume);228 229static int surface_gpe_probe(struct platform_device *pdev)230{231 struct surface_lid_device *lid;232 u32 gpe_number;233 acpi_status status;234 int ret;235 236 ret = device_property_read_u32(&pdev->dev, "gpe", &gpe_number);237 if (ret) {238 dev_err(&pdev->dev, "failed to read 'gpe' property: %d\n", ret);239 return ret;240 }241 242 lid = devm_kzalloc(&pdev->dev, sizeof(*lid), GFP_KERNEL);243 if (!lid)244 return -ENOMEM;245 246 lid->gpe_number = gpe_number;247 platform_set_drvdata(pdev, lid);248 249 status = acpi_mark_gpe_for_wake(NULL, gpe_number);250 if (ACPI_FAILURE(status)) {251 dev_err(&pdev->dev, "failed to mark GPE for wake: %s\n",252 acpi_format_exception(status));253 return -EINVAL;254 }255 256 status = acpi_enable_gpe(NULL, gpe_number);257 if (ACPI_FAILURE(status)) {258 dev_err(&pdev->dev, "failed to enable GPE: %s\n",259 acpi_format_exception(status));260 return -EINVAL;261 }262 263 ret = surface_lid_enable_wakeup(&pdev->dev, false);264 if (ret)265 acpi_disable_gpe(NULL, gpe_number);266 267 return ret;268}269 270static void surface_gpe_remove(struct platform_device *pdev)271{272 struct surface_lid_device *lid = dev_get_drvdata(&pdev->dev);273 274 /* restore default behavior without this module */275 surface_lid_enable_wakeup(&pdev->dev, false);276 acpi_disable_gpe(NULL, lid->gpe_number);277}278 279static struct platform_driver surface_gpe_driver = {280 .probe = surface_gpe_probe,281 .remove_new = surface_gpe_remove,282 .driver = {283 .name = "surface_gpe",284 .pm = &surface_gpe_pm,285 .probe_type = PROBE_PREFER_ASYNCHRONOUS,286 },287};288 289static struct platform_device *surface_gpe_device;290 291static int __init surface_gpe_init(void)292{293 const struct dmi_system_id *match;294 struct platform_device *pdev;295 struct fwnode_handle *fwnode;296 int status;297 298 match = dmi_first_match(dmi_lid_device_table);299 if (!match) {300 pr_info("no compatible Microsoft Surface device found, exiting\n");301 return -ENODEV;302 }303 304 status = platform_driver_register(&surface_gpe_driver);305 if (status)306 return status;307 308 fwnode = fwnode_create_software_node(match->driver_data, NULL);309 if (IS_ERR(fwnode)) {310 status = PTR_ERR(fwnode);311 goto err_node;312 }313 314 pdev = platform_device_alloc("surface_gpe", PLATFORM_DEVID_NONE);315 if (!pdev) {316 status = -ENOMEM;317 goto err_alloc;318 }319 320 pdev->dev.fwnode = fwnode;321 322 status = platform_device_add(pdev);323 if (status)324 goto err_add;325 326 surface_gpe_device = pdev;327 return 0;328 329err_add:330 platform_device_put(pdev);331err_alloc:332 fwnode_remove_software_node(fwnode);333err_node:334 platform_driver_unregister(&surface_gpe_driver);335 return status;336}337module_init(surface_gpe_init);338 339static void __exit surface_gpe_exit(void)340{341 struct fwnode_handle *fwnode = surface_gpe_device->dev.fwnode;342 343 platform_device_unregister(surface_gpe_device);344 platform_driver_unregister(&surface_gpe_driver);345 fwnode_remove_software_node(fwnode);346}347module_exit(surface_gpe_exit);348 349MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");350MODULE_DESCRIPTION("Surface GPE/Lid Driver");351MODULE_LICENSE("GPL");352MODULE_ALIAS("dmi:*:svnMicrosoftCorporation:pnSurface*:*");353