From a151e4b8740bd6dd4fe439a217a58f24f5127621 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Sun, 16 Aug 2020 23:39:56 +0200 Subject: [PATCH 6/6] surface-gpe --- drivers/platform/x86/Kconfig | 9 + drivers/platform/x86/Makefile | 1 + drivers/platform/x86/surface_gpe.c | 302 +++++++++++++++++++++++++++++ 3 files changed, 312 insertions(+) create mode 100644 drivers/platform/x86/surface_gpe.c diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 0ad7ad8cf8e17..47e75acd6c333 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -868,6 +868,15 @@ config SURFACE_PRO3_BUTTON ---help--- This driver handles the power/home/volume buttons on the Microsoft Surface Pro 3/4 tablet. +config SURFACE_GPE + tristate "Surface GPE/Lid Driver" + depends on ACPI + 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 MSI_LAPTOP tristate "MSI Laptop Extras" depends on ACPI diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 53408d9658740..0df0165ba340b 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -84,6 +84,7 @@ 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_PRO3_BUTTON) += surfacepro3_button.o +obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o # MSI obj-$(CONFIG_MSI_LAPTOP) += msi-laptop.o diff --git a/drivers/platform/x86/surface_gpe.c b/drivers/platform/x86/surface_gpe.c new file mode 100644 index 0000000000000..3031a94cddeb7 --- /dev/null +++ b/drivers/platform/x86/surface_gpe.c @@ -0,0 +1,302 @@ +// 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. + */ + +#include +#include +#include +#include +#include + + +struct surface_lid_device { + u32 gpe_number; +}; + +static const struct surface_lid_device lid_device_l17 = { + .gpe_number = 0x17, +}; + +static const struct surface_lid_device lid_device_l4D = { + .gpe_number = 0x4D, +}; + +static const struct surface_lid_device lid_device_l4F = { + .gpe_number = 0x4F, +}; + +static const struct surface_lid_device lid_device_l57 = { + .gpe_number = 0x57, +}; + + +// Note: When changing this don't forget to change the MODULE_ALIAS below. +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_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_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_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_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_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_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_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_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_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_l57, + }, + { + .ident = "Surface Laptop 3 (Intel 13\")", + .matches = { + /* + * We match for SKU here due to different vairants: 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_l4D, + }, + { } +}; + + +static int surface_lid_enable_wakeup(struct device *dev, + const struct surface_lid_device *lid, + bool enable) +{ + int action = enable ? ACPI_GPE_ENABLE : ACPI_GPE_DISABLE; + acpi_status status; + + status = acpi_set_gpe_wake_mask(NULL, lid->gpe_number, action); + if (status) { + dev_err(dev, "failed to set GPE wake mask: %d\n", status); + return -EINVAL; + } + + return 0; +} + + +static int surface_gpe_suspend(struct device *dev) +{ + const struct surface_lid_device *lid; + + lid = dev_get_platdata(dev); + return surface_lid_enable_wakeup(dev, lid, true); +} + +static int surface_gpe_resume(struct device *dev) +{ + const struct surface_lid_device *lid; + + lid = dev_get_platdata(dev); + return surface_lid_enable_wakeup(dev, lid, false); +} + +static SIMPLE_DEV_PM_OPS(surface_gpe_pm, surface_gpe_suspend, surface_gpe_resume); + + +static int surface_gpe_probe(struct platform_device *pdev) +{ + const struct surface_lid_device *lid; + int status; + + lid = dev_get_platdata(&pdev->dev); + if (!lid) + return -ENODEV; + + status = acpi_mark_gpe_for_wake(NULL, lid->gpe_number); + if (status) { + dev_err(&pdev->dev, "failed to mark GPE for wake: %d\n", status); + return -EINVAL; + } + + status = acpi_enable_gpe(NULL, lid->gpe_number); + if (status) { + dev_err(&pdev->dev, "failed to enable GPE: %d\n", status); + return -EINVAL; + } + + status = surface_lid_enable_wakeup(&pdev->dev, lid, false); + if (status) { + acpi_disable_gpe(NULL, lid->gpe_number); + return status; + } + + return 0; +} + +static int surface_gpe_remove(struct platform_device *pdev) +{ + struct surface_lid_device *lid = dev_get_platdata(&pdev->dev); + + /* restore default behavior without this module */ + surface_lid_enable_wakeup(&pdev->dev, lid, 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; + const struct surface_lid_device *lid; + + struct platform_device *pdev; + int status; + + surface_gpe_device = NULL; + + match = dmi_first_match(dmi_lid_device_table); + if (!match) { + pr_info(KBUILD_MODNAME": no device detected, exiting\n"); + return 0; + } + + lid = match->driver_data; + + status = platform_driver_register(&surface_gpe_driver); + if (status) + return status; + + pdev = platform_device_alloc("surface_gpe", PLATFORM_DEVID_NONE); + if (!pdev) { + platform_driver_unregister(&surface_gpe_driver); + return -ENOMEM; + } + + status = platform_device_add_data(pdev, lid, sizeof(*lid)); + if (status) { + platform_device_put(pdev); + platform_driver_unregister(&surface_gpe_driver); + return status; + } + + status = platform_device_add(pdev); + if (status) { + platform_device_put(pdev); + platform_driver_unregister(&surface_gpe_driver); + return status; + } + + surface_gpe_device = pdev; + return 0; +} + +static void __exit surface_gpe_exit(void) +{ + if (!surface_gpe_device) + return; + + platform_device_unregister(surface_gpe_device); + platform_driver_unregister(&surface_gpe_driver); +} + +module_init(surface_gpe_init); +module_exit(surface_gpe_exit); + +MODULE_AUTHOR("Maximilian Luz "); +MODULE_DESCRIPTION("Surface GPE/Lid Driver"); +MODULE_LICENSE("GPL"); + +MODULE_ALIAS("dmi:*:svnMicrosoftCorporation:pnSurfacePro:*"); +MODULE_ALIAS("dmi:*:svnMicrosoftCorporation:pnSurfacePro4:*"); +MODULE_ALIAS("dmi:*:svnMicrosoftCorporation:pnSurfacePro6:*"); +MODULE_ALIAS("dmi:*:svnMicrosoftCorporation:pnSurfacePro7:*"); +MODULE_ALIAS("dmi:*:svnMicrosoftCorporation:pnSurfaceBook:*"); +MODULE_ALIAS("dmi:*:svnMicrosoftCorporation:pnSurfaceBook2:*"); +MODULE_ALIAS("dmi:*:svnMicrosoftCorporation:pnSurfaceBook3:*"); +MODULE_ALIAS("dmi:*:svnMicrosoftCorporation:pnSurfaceLaptop:*"); +MODULE_ALIAS("dmi:*:svnMicrosoftCorporation:pnSurfaceLaptop2:*"); +MODULE_ALIAS("dmi:*:svnMicrosoftCorporation:pnSurfaceLaptop3:*"); -- 2.28.0