Hi, On 10/28/20 11:54 AM, Maximilian Luz wrote: > Conventionally, wake-up events for a specific device, in our case the > lid device, are managed via the ACPI _PRW field. While this does not > seem strictly necessary based on ACPI spec, the kernel disables GPE > wakeups to avoid non-wakeup interrupts preventing suspend by default and > only enables GPEs associated via the _PRW field with a wake-up capable > device. This behavior has been introduced in commit f941d3e41da7 ("ACPI: > EC / PM: Disable non-wakeup GPEs for suspend-to-idle") and is described > in more detail in its commit message. > > Unfortunately, on MS Surface devices, there is no _PRW field present on > the lid device, thus no GPE is associated with it, and therefore the GPE > responsible for sending the status-change notification to the lid gets > disabled during suspend, making it impossible to wake the device via the > lid. > > This patch introduces a pseudo-device and respective driver which, based > on some DMI matching, marks the corresponding GPE of the lid device for > wake and enables it during suspend. The behavior of this driver models > the behavior of the ACPI/PM core for normal wakeup GPEs, properly > declared via the _PRW field. > > Signed-off-by: Maximilian Luz <luzmaximilian@xxxxxxxxx> Thank you for your patch, I've applied this patch to my review-hans branch: https://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86.git/log/?h=review-hans Note it will show up in my review-hans branch once I've pushed my local branch there, which might take a while. Once I've run some tests on this branch the patches there will be added to the platform-drivers-x86/for-next branch and eventually will be included in the pdx86 pull-request to Linus for the next merge-window. Regards, Hans > --- > > Note: This patch depends and is based on > > [PATCH v4] platform/surface: Create a platform subdirectory for > Microsoft Surface devices > > which can be found at > > https://lore.kernel.org/platform-driver-x86/20201009141128.683254-1-luzmaximilian@xxxxxxxxx/ > > and is currently in platform-drivers-x86/for-next. > > Changes in v2: > - Use software nodes and device properties instead of platform data. > - Simplify module alias. > - Add comment regarding origin of GPE numbers. > - Fix style issues. > > Changes in v3: > - Rebase onto v5.9-rc5 > - Fix and restructure error handling and module cleanup. > - Remove unnecessary platform_set_drvdata(..., NULL) calls. > - Add pr_fmt definition > - Return -ENODEV if no compatible device is found in module init. > > Changes in v4: > - Rebase onto platform/surface patch-set > - Add copyright line > - Fix typo in comment > > Changes in v5: > - Rebase onto current platform-drivers-x86/for-next > - Fix MAINTAINERS entry > > --- > MAINTAINERS | 6 + > drivers/platform/surface/Kconfig | 10 + > drivers/platform/surface/Makefile | 1 + > drivers/platform/surface/surface_gpe.c | 309 +++++++++++++++++++++++++ > 4 files changed, 326 insertions(+) > create mode 100644 drivers/platform/surface/surface_gpe.c > > diff --git a/MAINTAINERS b/MAINTAINERS > index 38b70bd41d96..17e51a309a3a 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -11658,6 +11658,12 @@ F: drivers/scsi/smartpqi/smartpqi*.[ch] > F: include/linux/cciss*.h > F: include/uapi/linux/cciss*.h > > +MICROSOFT SURFACE GPE LID SUPPORT DRIVER > +M: Maximilian Luz <luzmaximilian@xxxxxxxxx> > +L: platform-driver-x86@xxxxxxxxxxxxxxx > +S: Maintained > +F: drivers/platform/surface/surface_gpe.c > + > MICROSOFT SURFACE HARDWARE PLATFORM SUPPORT > M: Hans de Goede <hdegoede@xxxxxxxxxx> > M: Mark Gross <mgross@xxxxxxxxxxxxxxx> > diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig > index 10902ea43861..33040b0b3b79 100644 > --- a/drivers/platform/surface/Kconfig > +++ b/drivers/platform/surface/Kconfig > @@ -40,6 +40,16 @@ config SURFACE_3_POWER_OPREGION > This driver provides support for ACPI operation > region of the Surface 3 battery platform driver. > > +config SURFACE_GPE > + tristate "Surface GPE/Lid Support Driver" > + depends on ACPI > + depends on DMI > + help > + This driver marks the GPEs related to the ACPI lid device found on > + Microsoft Surface devices as wakeup sources and prepares them > + accordingly. It is required on those devices to allow wake-ups from > + suspend by opening the lid. > + > config SURFACE_PRO3_BUTTON > tristate "Power/home/volume buttons driver for Microsoft Surface Pro 3/4 tablet" > depends on ACPI && INPUT > diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile > index dcb1df06d57a..cedfb027ded1 100644 > --- a/drivers/platform/surface/Makefile > +++ b/drivers/platform/surface/Makefile > @@ -7,4 +7,5 @@ > obj-$(CONFIG_SURFACE3_WMI) += surface3-wmi.o > obj-$(CONFIG_SURFACE_3_BUTTON) += surface3_button.o > obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o > +obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o > obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o > diff --git a/drivers/platform/surface/surface_gpe.c b/drivers/platform/surface/surface_gpe.c > new file mode 100644 > index 000000000000..0f44a52d3a9b > --- /dev/null > +++ b/drivers/platform/surface/surface_gpe.c > @@ -0,0 +1,309 @@ > +// SPDX-License-Identifier: GPL-2.0-or-later > +/* > + * Surface GPE/Lid driver to enable wakeup from suspend via the lid by > + * properly configuring the respective GPEs. Required for wakeup via lid on > + * newer Intel-based Microsoft Surface devices. > + * > + * Copyright (C) 2020 Maximilian Luz <luzmaximilian@xxxxxxxxx> > + */ > + > +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt > + > +#include <linux/acpi.h> > +#include <linux/dmi.h> > +#include <linux/kernel.h> > +#include <linux/module.h> > +#include <linux/platform_device.h> > + > +/* > + * Note: The GPE numbers for the lid devices found below have been obtained > + * from ACPI/the DSDT table, specifically from the GPE handler for the > + * lid. > + */ > + > +static const struct property_entry lid_device_props_l17[] = { > + PROPERTY_ENTRY_U32("gpe", 0x17), > + {}, > +}; > + > +static const struct property_entry lid_device_props_l4D[] = { > + PROPERTY_ENTRY_U32("gpe", 0x4D), > + {}, > +}; > + > +static const struct property_entry lid_device_props_l4F[] = { > + PROPERTY_ENTRY_U32("gpe", 0x4F), > + {}, > +}; > + > +static const struct property_entry lid_device_props_l57[] = { > + PROPERTY_ENTRY_U32("gpe", 0x57), > + {}, > +}; > + > +/* > + * Note: When changing this, don't forget to check that the MODULE_ALIAS below > + * still fits. > + */ > +static const struct dmi_system_id dmi_lid_device_table[] = { > + { > + .ident = "Surface Pro 4", > + .matches = { > + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), > + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), > + }, > + .driver_data = (void *)lid_device_props_l17, > + }, > + { > + .ident = "Surface Pro 5", > + .matches = { > + /* > + * We match for SKU here due to generic product name > + * "Surface Pro". > + */ > + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), > + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), > + }, > + .driver_data = (void *)lid_device_props_l4F, > + }, > + { > + .ident = "Surface Pro 5 (LTE)", > + .matches = { > + /* > + * We match for SKU here due to generic product name > + * "Surface Pro" > + */ > + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), > + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), > + }, > + .driver_data = (void *)lid_device_props_l4F, > + }, > + { > + .ident = "Surface Pro 6", > + .matches = { > + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), > + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), > + }, > + .driver_data = (void *)lid_device_props_l4F, > + }, > + { > + .ident = "Surface Pro 7", > + .matches = { > + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), > + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 7"), > + }, > + .driver_data = (void *)lid_device_props_l4D, > + }, > + { > + .ident = "Surface Book 1", > + .matches = { > + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), > + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), > + }, > + .driver_data = (void *)lid_device_props_l17, > + }, > + { > + .ident = "Surface Book 2", > + .matches = { > + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), > + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), > + }, > + .driver_data = (void *)lid_device_props_l17, > + }, > + { > + .ident = "Surface Book 3", > + .matches = { > + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), > + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 3"), > + }, > + .driver_data = (void *)lid_device_props_l4D, > + }, > + { > + .ident = "Surface Laptop 1", > + .matches = { > + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), > + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), > + }, > + .driver_data = (void *)lid_device_props_l57, > + }, > + { > + .ident = "Surface Laptop 2", > + .matches = { > + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), > + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), > + }, > + .driver_data = (void *)lid_device_props_l57, > + }, > + { > + .ident = "Surface Laptop 3 (Intel 13\")", > + .matches = { > + /* > + * We match for SKU here due to different variants: The > + * AMD (15") version does not rely on GPEs. > + */ > + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), > + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_3_1867:1868"), > + }, > + .driver_data = (void *)lid_device_props_l4D, > + }, > + { } > +}; > + > +struct surface_lid_device { > + u32 gpe_number; > +}; > + > +static int surface_lid_enable_wakeup(struct device *dev, bool enable) > +{ > + const struct surface_lid_device *lid = dev_get_drvdata(dev); > + int action = enable ? ACPI_GPE_ENABLE : ACPI_GPE_DISABLE; > + acpi_status status; > + > + status = acpi_set_gpe_wake_mask(NULL, lid->gpe_number, action); > + if (ACPI_FAILURE(status)) { > + dev_err(dev, "failed to set GPE wake mask: %s\n", > + acpi_format_exception(status)); > + return -EINVAL; > + } > + > + return 0; > +} > + > +static int surface_gpe_suspend(struct device *dev) > +{ > + return surface_lid_enable_wakeup(dev, true); > +} > + > +static int surface_gpe_resume(struct device *dev) > +{ > + return surface_lid_enable_wakeup(dev, false); > +} > + > +static SIMPLE_DEV_PM_OPS(surface_gpe_pm, surface_gpe_suspend, surface_gpe_resume); > + > +static int surface_gpe_probe(struct platform_device *pdev) > +{ > + struct surface_lid_device *lid; > + u32 gpe_number; > + acpi_status status; > + int ret; > + > + ret = device_property_read_u32(&pdev->dev, "gpe", &gpe_number); > + if (ret) { > + dev_err(&pdev->dev, "failed to read 'gpe' property: %d\n", ret); > + return ret; > + } > + > + lid = devm_kzalloc(&pdev->dev, sizeof(*lid), GFP_KERNEL); > + if (!lid) > + return -ENOMEM; > + > + lid->gpe_number = gpe_number; > + platform_set_drvdata(pdev, lid); > + > + status = acpi_mark_gpe_for_wake(NULL, gpe_number); > + if (ACPI_FAILURE(status)) { > + dev_err(&pdev->dev, "failed to mark GPE for wake: %s\n", > + acpi_format_exception(status)); > + return -EINVAL; > + } > + > + status = acpi_enable_gpe(NULL, gpe_number); > + if (ACPI_FAILURE(status)) { > + dev_err(&pdev->dev, "failed to enable GPE: %s\n", > + acpi_format_exception(status)); > + return -EINVAL; > + } > + > + ret = surface_lid_enable_wakeup(&pdev->dev, false); > + if (ret) > + acpi_disable_gpe(NULL, gpe_number); > + > + return ret; > +} > + > +static int surface_gpe_remove(struct platform_device *pdev) > +{ > + struct surface_lid_device *lid = dev_get_drvdata(&pdev->dev); > + > + /* restore default behavior without this module */ > + surface_lid_enable_wakeup(&pdev->dev, false); > + acpi_disable_gpe(NULL, lid->gpe_number); > + > + return 0; > +} > + > +static struct platform_driver surface_gpe_driver = { > + .probe = surface_gpe_probe, > + .remove = surface_gpe_remove, > + .driver = { > + .name = "surface_gpe", > + .pm = &surface_gpe_pm, > + .probe_type = PROBE_PREFER_ASYNCHRONOUS, > + }, > +}; > + > +static struct platform_device *surface_gpe_device; > + > +static int __init surface_gpe_init(void) > +{ > + const struct dmi_system_id *match; > + struct platform_device *pdev; > + struct fwnode_handle *fwnode; > + int status; > + > + match = dmi_first_match(dmi_lid_device_table); > + if (!match) { > + pr_info("no compatible Microsoft Surface device found, exiting\n"); > + return -ENODEV; > + } > + > + status = platform_driver_register(&surface_gpe_driver); > + if (status) > + return status; > + > + fwnode = fwnode_create_software_node(match->driver_data, NULL); > + if (IS_ERR(fwnode)) { > + status = PTR_ERR(fwnode); > + goto err_node; > + } > + > + pdev = platform_device_alloc("surface_gpe", PLATFORM_DEVID_NONE); > + if (!pdev) { > + status = -ENOMEM; > + goto err_alloc; > + } > + > + pdev->dev.fwnode = fwnode; > + > + status = platform_device_add(pdev); > + if (status) > + goto err_add; > + > + surface_gpe_device = pdev; > + return 0; > + > +err_add: > + platform_device_put(pdev); > +err_alloc: > + fwnode_remove_software_node(fwnode); > +err_node: > + platform_driver_unregister(&surface_gpe_driver); > + return status; > +} > +module_init(surface_gpe_init); > + > +static void __exit surface_gpe_exit(void) > +{ > + struct fwnode_handle *fwnode = surface_gpe_device->dev.fwnode; > + > + platform_device_unregister(surface_gpe_device); > + platform_driver_unregister(&surface_gpe_driver); > + fwnode_remove_software_node(fwnode); > +} > +module_exit(surface_gpe_exit); > + > +MODULE_AUTHOR("Maximilian Luz <luzmaximilian@xxxxxxxxx>"); > +MODULE_DESCRIPTION("Surface GPE/Lid Driver"); > +MODULE_LICENSE("GPL"); > +MODULE_ALIAS("dmi:*:svnMicrosoftCorporation:pnSurface*:*"); >