7c0e669f67
Changes: - SAM: - Update DTX driver state after resume. - Add DTX Documentation, misc. fixes, and cleanup. - IPTS: This implements a new and refined UAPI interface that should improve stability during suspend and move some responsibility into userspace, making the driver simpler. It also fixes some sleep issues due to improper shutdown of the device. Shortlog: 0a4a44c Add missing include 31ae03d Improve error handling of ipts_control_* functions 287dea0 Prevent lockups if stop is called from the receiver thread b737a9c On remove, wait until CLEAR_MEM_WINDOW returned. c5b66a5 Add GET_DEVICE_READY ioctl af0f84a Seperate UAPI initialization and device probing 4ae7674 Patch the MEI bus to allow sending commands on remove 27772bc Just a few refactorings... Links: - SAM:af4bb01042
- IPTS:0a4a44c2a9
- kernel:6e8bb10ad8
388 lines
11 KiB
Diff
388 lines
11 KiB
Diff
From aa25cec8fd87df94cabc33c37cc0a2778d63f3d4 Mon Sep 17 00:00:00 2001
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Date: Sun, 16 Aug 2020 23:39:56 +0200
|
|
Subject: [PATCH] platform/x86: Add Driver to set up lid GPEs on MS Surface
|
|
device
|
|
|
|
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
|
|
|
|
f941d3e41da7f86bdb9dcc1977c2bcc6b89bfe47
|
|
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, mark the corresponding GPE of the lid device for
|
|
wake and enable 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@gmail.com>
|
|
Patchset: surface-gpe
|
|
---
|
|
drivers/platform/x86/Kconfig | 9 +
|
|
drivers/platform/x86/Makefile | 1 +
|
|
drivers/platform/x86/surface_gpe.c | 307 +++++++++++++++++++++++++++++
|
|
3 files changed, 317 insertions(+)
|
|
create mode 100644 drivers/platform/x86/surface_gpe.c
|
|
|
|
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
|
|
index a5ad36083b67..01752d1f40f8 100644
|
|
--- a/drivers/platform/x86/Kconfig
|
|
+++ b/drivers/platform/x86/Kconfig
|
|
@@ -880,6 +880,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 2b85852a1a87..a64ce216719a 100644
|
|
--- a/drivers/platform/x86/Makefile
|
|
+++ b/drivers/platform/x86/Makefile
|
|
@@ -85,6 +85,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 000000000000..2857e3862ca4
|
|
--- /dev/null
|
|
+++ b/drivers/platform/x86/surface_gpe.c
|
|
@@ -0,0 +1,307 @@
|
|
+// 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.
|
|
+ */
|
|
+
|
|
+#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 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_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@gmail.com>");
|
|
+MODULE_DESCRIPTION("Surface GPE/Lid Driver");
|
|
+MODULE_LICENSE("GPL");
|
|
+MODULE_ALIAS("dmi:*:svnMicrosoftCorporation:pnSurface*:*");
|
|
--
|
|
2.28.0
|
|
|