platform/x86: intel-vbtn: Move to intel sub-directory
authorKate Hsuan <hpa@redhat.com>
Fri, 20 Aug 2021 11:04:56 +0000 (14:04 +0300)
committerHans de Goede <hdegoede@redhat.com>
Fri, 20 Aug 2021 18:11:23 +0000 (20:11 +0200)
Move Intel vButton driver to intel sub-directory to improve readability.

Signed-off-by: Kate Hsuan <hpa@redhat.com>
Reviewed-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Link: https://lore.kernel.org/r/20210820110458.73018-19-andriy.shevchenko@linux.intel.com
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
MAINTAINERS
drivers/platform/x86/Kconfig
drivers/platform/x86/Makefile
drivers/platform/x86/intel-vbtn.c [deleted file]
drivers/platform/x86/intel/Kconfig
drivers/platform/x86/intel/Makefile
drivers/platform/x86/intel/vbtn.c [new file with mode: 0644]

index 5339b210d6a5bd68cf58678dea07392cf7d7667c..4662d4d4db5dc280127d09ebf044d530de082728 100644 (file)
@@ -9571,7 +9571,7 @@ INTEL VIRTUAL BUTTON DRIVER
 M:     AceLan Kao <acelan.kao@canonical.com>
 L:     platform-driver-x86@vger.kernel.org
 S:     Maintained
-F:     drivers/platform/x86/intel-vbtn.c
+F:     drivers/platform/x86/intel/vbtn.c
 
 INTEL WIRELESS 3945ABG/BG, 4965AGN (iwlegacy)
 M:     Stanislaw Gruszka <stf_xl@wp.pl>
index 7f54db30cf5addb3f39819d304b766155f9c11b7..f1349a1e17aeb946bffe3de8140ef9d30b972110 100644 (file)
@@ -668,19 +668,6 @@ config THINKPAD_LMI
 
 source "drivers/platform/x86/intel/Kconfig"
 
-config INTEL_VBTN
-       tristate "INTEL VIRTUAL BUTTON"
-       depends on ACPI
-       depends on INPUT
-       depends on I2C
-       select INPUT_SPARSEKMAP
-       help
-         This driver provides support for the Intel Virtual Button interface.
-         Some laptops require this driver for power button support.
-
-         To compile this driver as a module, choose M here: the module will
-         be called intel_vbtn.
-
 config MSI_LAPTOP
        tristate "MSI Laptop Extras"
        depends on ACPI
index 836fbab61f47b3036fcab1ffd5476f8dbff8cf08..e6e888af9df7979be72385a7aa037ade56466f8c 100644 (file)
@@ -71,8 +71,6 @@ obj-$(CONFIG_THINKPAD_LMI)    += think-lmi.o
 # Intel
 obj-$(CONFIG_X86_PLATFORM_DRIVERS_INTEL)               += intel/
 
-obj-$(CONFIG_INTEL_VBTN)               += intel-vbtn.o
-
 # MSI
 obj-$(CONFIG_MSI_LAPTOP)       += msi-laptop.o
 obj-$(CONFIG_MSI_WMI)          += msi-wmi.o
diff --git a/drivers/platform/x86/intel-vbtn.c b/drivers/platform/x86/intel-vbtn.c
deleted file mode 100644 (file)
index 3091664..0000000
+++ /dev/null
@@ -1,414 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0+
-/*
- *  Intel Virtual Button driver for Windows 8.1+
- *
- *  Copyright (C) 2016 AceLan Kao <acelan.kao@canonical.com>
- *  Copyright (C) 2016 Alex Hung <alex.hung@canonical.com>
- */
-
-#include <linux/acpi.h>
-#include <linux/dmi.h>
-#include <linux/input.h>
-#include <linux/input/sparse-keymap.h>
-#include <linux/kernel.h>
-#include <linux/module.h>
-#include <linux/platform_device.h>
-#include <linux/suspend.h>
-#include "dual_accel_detect.h"
-
-/* Returned when NOT in tablet mode on some HP Stream x360 11 models */
-#define VGBS_TABLET_MODE_FLAG_ALT      0x10
-/* When NOT in tablet mode, VGBS returns with the flag 0x40 */
-#define VGBS_TABLET_MODE_FLAG          0x40
-#define VGBS_DOCK_MODE_FLAG            0x80
-
-#define VGBS_TABLET_MODE_FLAGS (VGBS_TABLET_MODE_FLAG | VGBS_TABLET_MODE_FLAG_ALT)
-
-MODULE_LICENSE("GPL");
-MODULE_AUTHOR("AceLan Kao");
-
-static const struct acpi_device_id intel_vbtn_ids[] = {
-       {"INT33D6", 0},
-       {"", 0},
-};
-MODULE_DEVICE_TABLE(acpi, intel_vbtn_ids);
-
-/* In theory, these are HID usages. */
-static const struct key_entry intel_vbtn_keymap[] = {
-       { KE_KEY, 0xC0, { KEY_POWER } },        /* power key press */
-       { KE_IGNORE, 0xC1, { KEY_POWER } },     /* power key release */
-       { KE_KEY, 0xC2, { KEY_LEFTMETA } },             /* 'Windows' key press */
-       { KE_KEY, 0xC3, { KEY_LEFTMETA } },             /* 'Windows' key release */
-       { KE_KEY, 0xC4, { KEY_VOLUMEUP } },             /* volume-up key press */
-       { KE_IGNORE, 0xC5, { KEY_VOLUMEUP } },          /* volume-up key release */
-       { KE_KEY, 0xC6, { KEY_VOLUMEDOWN } },           /* volume-down key press */
-       { KE_IGNORE, 0xC7, { KEY_VOLUMEDOWN } },        /* volume-down key release */
-       { KE_KEY,    0xC8, { KEY_ROTATE_LOCK_TOGGLE } },        /* rotate-lock key press */
-       { KE_KEY,    0xC9, { KEY_ROTATE_LOCK_TOGGLE } },        /* rotate-lock key release */
-       { KE_END }
-};
-
-static const struct key_entry intel_vbtn_switchmap[] = {
-       /*
-        * SW_DOCK should only be reported for docking stations, but DSDTs using the
-        * intel-vbtn code, always seem to use this for 2-in-1s / convertibles and set
-        * SW_DOCK=1 when in laptop-mode (in tandem with setting SW_TABLET_MODE=0).
-        * This causes userspace to think the laptop is docked to a port-replicator
-        * and to disable suspend-on-lid-close, which is undesirable.
-        * Map the dock events to KEY_IGNORE to avoid this broken SW_DOCK reporting.
-        */
-       { KE_IGNORE, 0xCA, { .sw = { SW_DOCK, 1 } } },          /* Docked */
-       { KE_IGNORE, 0xCB, { .sw = { SW_DOCK, 0 } } },          /* Undocked */
-       { KE_SW,     0xCC, { .sw = { SW_TABLET_MODE, 1 } } },   /* Tablet */
-       { KE_SW,     0xCD, { .sw = { SW_TABLET_MODE, 0 } } },   /* Laptop */
-       { KE_END }
-};
-
-struct intel_vbtn_priv {
-       struct input_dev *buttons_dev;
-       struct input_dev *switches_dev;
-       bool dual_accel;
-       bool has_buttons;
-       bool has_switches;
-       bool wakeup_mode;
-};
-
-static void detect_tablet_mode(struct platform_device *device)
-{
-       struct intel_vbtn_priv *priv = dev_get_drvdata(&device->dev);
-       acpi_handle handle = ACPI_HANDLE(&device->dev);
-       unsigned long long vgbs;
-       acpi_status status;
-       int m;
-
-       status = acpi_evaluate_integer(handle, "VGBS", NULL, &vgbs);
-       if (ACPI_FAILURE(status))
-               return;
-
-       m = !(vgbs & VGBS_TABLET_MODE_FLAGS);
-       input_report_switch(priv->switches_dev, SW_TABLET_MODE, m);
-       m = (vgbs & VGBS_DOCK_MODE_FLAG) ? 1 : 0;
-       input_report_switch(priv->switches_dev, SW_DOCK, m);
-}
-
-/*
- * Note this unconditionally creates the 2 input_dev-s and sets up
- * the sparse-keymaps. Only the registration is conditional on
- * have_buttons / have_switches. This is done so that the notify
- * handler can always call sparse_keymap_entry_from_scancode()
- * on the input_dev-s do determine the event type.
- */
-static int intel_vbtn_input_setup(struct platform_device *device)
-{
-       struct intel_vbtn_priv *priv = dev_get_drvdata(&device->dev);
-       int ret;
-
-       priv->buttons_dev = devm_input_allocate_device(&device->dev);
-       if (!priv->buttons_dev)
-               return -ENOMEM;
-
-       ret = sparse_keymap_setup(priv->buttons_dev, intel_vbtn_keymap, NULL);
-       if (ret)
-               return ret;
-
-       priv->buttons_dev->dev.parent = &device->dev;
-       priv->buttons_dev->name = "Intel Virtual Buttons";
-       priv->buttons_dev->id.bustype = BUS_HOST;
-
-       if (priv->has_buttons) {
-               ret = input_register_device(priv->buttons_dev);
-               if (ret)
-                       return ret;
-       }
-
-       priv->switches_dev = devm_input_allocate_device(&device->dev);
-       if (!priv->switches_dev)
-               return -ENOMEM;
-
-       ret = sparse_keymap_setup(priv->switches_dev, intel_vbtn_switchmap, NULL);
-       if (ret)
-               return ret;
-
-       priv->switches_dev->dev.parent = &device->dev;
-       priv->switches_dev->name = "Intel Virtual Switches";
-       priv->switches_dev->id.bustype = BUS_HOST;
-
-       if (priv->has_switches) {
-               detect_tablet_mode(device);
-
-               ret = input_register_device(priv->switches_dev);
-               if (ret)
-                       return ret;
-       }
-
-       return 0;
-}
-
-static void notify_handler(acpi_handle handle, u32 event, void *context)
-{
-       struct platform_device *device = context;
-       struct intel_vbtn_priv *priv = dev_get_drvdata(&device->dev);
-       unsigned int val = !(event & 1); /* Even=press, Odd=release */
-       const struct key_entry *ke, *ke_rel;
-       struct input_dev *input_dev;
-       bool autorelease;
-       int ret;
-
-       if ((ke = sparse_keymap_entry_from_scancode(priv->buttons_dev, event))) {
-               if (!priv->has_buttons) {
-                       dev_warn(&device->dev, "Warning: received a button event on a device without buttons, please report this.\n");
-                       return;
-               }
-               input_dev = priv->buttons_dev;
-       } else if ((ke = sparse_keymap_entry_from_scancode(priv->switches_dev, event))) {
-               if (!priv->has_switches) {
-                       /* See dual_accel_detect.h for more info */
-                       if (priv->dual_accel)
-                               return;
-
-                       dev_info(&device->dev, "Registering Intel Virtual Switches input-dev after receiving a switch event\n");
-                       ret = input_register_device(priv->switches_dev);
-                       if (ret)
-                               return;
-
-                       priv->has_switches = true;
-               }
-               input_dev = priv->switches_dev;
-       } else {
-               dev_dbg(&device->dev, "unknown event index 0x%x\n", event);
-               return;
-       }
-
-       if (priv->wakeup_mode) {
-               pm_wakeup_hard_event(&device->dev);
-
-               /*
-                * Skip reporting an evdev event for button wake events,
-                * mirroring how the drivers/acpi/button.c code skips this too.
-                */
-               if (ke->type == KE_KEY)
-                       return;
-       }
-
-       /*
-        * Even press events are autorelease if there is no corresponding odd
-        * release event, or if the odd event is KE_IGNORE.
-        */
-       ke_rel = sparse_keymap_entry_from_scancode(input_dev, event | 1);
-       autorelease = val && (!ke_rel || ke_rel->type == KE_IGNORE);
-
-       sparse_keymap_report_event(input_dev, event, val, autorelease);
-}
-
-/*
- * There are several laptops (non 2-in-1) models out there which support VGBS,
- * but simply always return 0, which we translate to SW_TABLET_MODE=1. This in
- * turn causes userspace (libinput) to suppress events from the builtin
- * keyboard and touchpad, making the laptop essentially unusable.
- *
- * Since the problem of wrongly reporting SW_TABLET_MODE=1 in combination
- * with libinput, leads to a non-usable system. Where as OTOH many people will
- * not even notice when SW_TABLET_MODE is not being reported, a DMI based allow
- * list is used here. This list mainly matches on the chassis-type of 2-in-1s.
- *
- * There are also some 2-in-1s which use the intel-vbtn ACPI interface to report
- * SW_TABLET_MODE with a chassis-type of 8 ("Portable") or 10 ("Notebook"),
- * these are matched on a per model basis, since many normal laptops with a
- * possible broken VGBS ACPI-method also use these chassis-types.
- */
-static const struct dmi_system_id dmi_switches_allow_list[] = {
-       {
-               .matches = {
-                       DMI_EXACT_MATCH(DMI_CHASSIS_TYPE, "31" /* Convertible */),
-               },
-       },
-       {
-               .matches = {
-                       DMI_EXACT_MATCH(DMI_CHASSIS_TYPE, "32" /* Detachable */),
-               },
-       },
-       {
-               .matches = {
-                       DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
-                       DMI_MATCH(DMI_PRODUCT_NAME, "Venue 11 Pro 7130"),
-               },
-       },
-       {
-               .matches = {
-                       DMI_MATCH(DMI_SYS_VENDOR, "Hewlett-Packard"),
-                       DMI_MATCH(DMI_PRODUCT_NAME, "HP Pavilion 13 x360 PC"),
-               },
-       },
-       {
-               .matches = {
-                       DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
-                       DMI_MATCH(DMI_PRODUCT_NAME, "Switch SA5-271"),
-               },
-       },
-       {
-               .matches = {
-                       DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
-                       DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 7352"),
-               },
-       },
-       {} /* Array terminator */
-};
-
-static bool intel_vbtn_has_switches(acpi_handle handle, bool dual_accel)
-{
-       unsigned long long vgbs;
-       acpi_status status;
-
-       /* See dual_accel_detect.h for more info */
-       if (dual_accel)
-               return false;
-
-       if (!dmi_check_system(dmi_switches_allow_list))
-               return false;
-
-       status = acpi_evaluate_integer(handle, "VGBS", NULL, &vgbs);
-       return ACPI_SUCCESS(status);
-}
-
-static int intel_vbtn_probe(struct platform_device *device)
-{
-       acpi_handle handle = ACPI_HANDLE(&device->dev);
-       bool dual_accel, has_buttons, has_switches;
-       struct intel_vbtn_priv *priv;
-       acpi_status status;
-       int err;
-
-       dual_accel = dual_accel_detect();
-       has_buttons = acpi_has_method(handle, "VBDL");
-       has_switches = intel_vbtn_has_switches(handle, dual_accel);
-
-       if (!has_buttons && !has_switches) {
-               dev_warn(&device->dev, "failed to read Intel Virtual Button driver\n");
-               return -ENODEV;
-       }
-
-       priv = devm_kzalloc(&device->dev, sizeof(*priv), GFP_KERNEL);
-       if (!priv)
-               return -ENOMEM;
-       dev_set_drvdata(&device->dev, priv);
-
-       priv->dual_accel = dual_accel;
-       priv->has_buttons = has_buttons;
-       priv->has_switches = has_switches;
-
-       err = intel_vbtn_input_setup(device);
-       if (err) {
-               pr_err("Failed to setup Intel Virtual Button\n");
-               return err;
-       }
-
-       status = acpi_install_notify_handler(handle,
-                                            ACPI_DEVICE_NOTIFY,
-                                            notify_handler,
-                                            device);
-       if (ACPI_FAILURE(status))
-               return -EBUSY;
-
-       if (has_buttons) {
-               status = acpi_evaluate_object(handle, "VBDL", NULL, NULL);
-               if (ACPI_FAILURE(status))
-                       dev_err(&device->dev, "Error VBDL failed with ACPI status %d\n", status);
-       }
-
-       device_init_wakeup(&device->dev, true);
-       /*
-        * In order for system wakeup to work, the EC GPE has to be marked as
-        * a wakeup one, so do that here (this setting will persist, but it has
-        * no effect until the wakeup mask is set for the EC GPE).
-        */
-       acpi_ec_mark_gpe_for_wake();
-       return 0;
-}
-
-static int intel_vbtn_remove(struct platform_device *device)
-{
-       acpi_handle handle = ACPI_HANDLE(&device->dev);
-
-       device_init_wakeup(&device->dev, false);
-       acpi_remove_notify_handler(handle, ACPI_DEVICE_NOTIFY, notify_handler);
-
-       /*
-        * Even if we failed to shut off the event stream, we can still
-        * safely detach from the device.
-        */
-       return 0;
-}
-
-static int intel_vbtn_pm_prepare(struct device *dev)
-{
-       if (device_may_wakeup(dev)) {
-               struct intel_vbtn_priv *priv = dev_get_drvdata(dev);
-
-               priv->wakeup_mode = true;
-       }
-       return 0;
-}
-
-static void intel_vbtn_pm_complete(struct device *dev)
-{
-       struct intel_vbtn_priv *priv = dev_get_drvdata(dev);
-
-       priv->wakeup_mode = false;
-}
-
-static int intel_vbtn_pm_resume(struct device *dev)
-{
-       intel_vbtn_pm_complete(dev);
-       return 0;
-}
-
-static const struct dev_pm_ops intel_vbtn_pm_ops = {
-       .prepare = intel_vbtn_pm_prepare,
-       .complete = intel_vbtn_pm_complete,
-       .resume = intel_vbtn_pm_resume,
-       .restore = intel_vbtn_pm_resume,
-       .thaw = intel_vbtn_pm_resume,
-};
-
-static struct platform_driver intel_vbtn_pl_driver = {
-       .driver = {
-               .name = "intel-vbtn",
-               .acpi_match_table = intel_vbtn_ids,
-               .pm = &intel_vbtn_pm_ops,
-       },
-       .probe = intel_vbtn_probe,
-       .remove = intel_vbtn_remove,
-};
-
-static acpi_status __init
-check_acpi_dev(acpi_handle handle, u32 lvl, void *context, void **rv)
-{
-       const struct acpi_device_id *ids = context;
-       struct acpi_device *dev;
-
-       if (acpi_bus_get_device(handle, &dev) != 0)
-               return AE_OK;
-
-       if (acpi_match_device_ids(dev, ids) == 0)
-               if (!IS_ERR_OR_NULL(acpi_create_platform_device(dev, NULL)))
-                       dev_info(&dev->dev,
-                                "intel-vbtn: created platform device\n");
-
-       return AE_OK;
-}
-
-static int __init intel_vbtn_init(void)
-{
-       acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT,
-                           ACPI_UINT32_MAX, check_acpi_dev, NULL,
-                           (void *)intel_vbtn_ids, NULL);
-
-       return platform_driver_register(&intel_vbtn_pl_driver);
-}
-module_init(intel_vbtn_init);
-
-static void __exit intel_vbtn_exit(void)
-{
-       platform_driver_unregister(&intel_vbtn_pl_driver);
-}
-module_exit(intel_vbtn_exit);
index 3c7fd0bd39f1add0dca6a2832e154fd65ac62ee8..a5d71d669b8210e3a0dade2c490d41227c9a29ac 100644 (file)
@@ -38,6 +38,19 @@ config INTEL_HID_EVENT
          To compile this driver as a module, choose M here: the module will
          be called intel_hid.
 
+config INTEL_VBTN
+       tristate "Intel Virtual Button"
+       depends on ACPI
+       depends on INPUT
+       depends on I2C
+       select INPUT_SPARSEKMAP
+       help
+         This driver provides support for the Intel Virtual Button interface.
+         Some laptops require this driver for power button support.
+
+         To compile this driver as a module, choose M here: the module will
+         be called intel_vbtn.
+
 config INTEL_INT0002_VGPIO
        tristate "Intel ACPI INT0002 Virtual GPIO driver"
        depends on GPIOLIB && ACPI && PM_SLEEP
index f27d91464cc9dd8dd45fa23d9d201a1eaa8326dd..67b16119e712fcdc014762df949605e68321c68f 100644 (file)
@@ -16,6 +16,8 @@ obj-$(CONFIG_INTEL_TELEMETRY)         += telemetry/
 # Intel input drivers
 intel-hid-y                            := hid.o
 obj-$(CONFIG_INTEL_HID_EVENT)          += intel-hid.o
+intel-vbtn-y                           := vbtn.o
+obj-$(CONFIG_INTEL_VBTN)               += intel-vbtn.o
 
 # Intel miscellaneous drivers
 intel_int0002_vgpio-y                  := int0002_vgpio.o
diff --git a/drivers/platform/x86/intel/vbtn.c b/drivers/platform/x86/intel/vbtn.c
new file mode 100644 (file)
index 0000000..15f013a
--- /dev/null
@@ -0,0 +1,414 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ *  Intel Virtual Button driver for Windows 8.1+
+ *
+ *  Copyright (C) 2016 AceLan Kao <acelan.kao@canonical.com>
+ *  Copyright (C) 2016 Alex Hung <alex.hung@canonical.com>
+ */
+
+#include <linux/acpi.h>
+#include <linux/dmi.h>
+#include <linux/input.h>
+#include <linux/input/sparse-keymap.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/suspend.h>
+#include "../dual_accel_detect.h"
+
+/* Returned when NOT in tablet mode on some HP Stream x360 11 models */
+#define VGBS_TABLET_MODE_FLAG_ALT      0x10
+/* When NOT in tablet mode, VGBS returns with the flag 0x40 */
+#define VGBS_TABLET_MODE_FLAG          0x40
+#define VGBS_DOCK_MODE_FLAG            0x80
+
+#define VGBS_TABLET_MODE_FLAGS (VGBS_TABLET_MODE_FLAG | VGBS_TABLET_MODE_FLAG_ALT)
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("AceLan Kao");
+
+static const struct acpi_device_id intel_vbtn_ids[] = {
+       {"INT33D6", 0},
+       {"", 0},
+};
+MODULE_DEVICE_TABLE(acpi, intel_vbtn_ids);
+
+/* In theory, these are HID usages. */
+static const struct key_entry intel_vbtn_keymap[] = {
+       { KE_KEY, 0xC0, { KEY_POWER } },        /* power key press */
+       { KE_IGNORE, 0xC1, { KEY_POWER } },     /* power key release */
+       { KE_KEY, 0xC2, { KEY_LEFTMETA } },             /* 'Windows' key press */
+       { KE_KEY, 0xC3, { KEY_LEFTMETA } },             /* 'Windows' key release */
+       { KE_KEY, 0xC4, { KEY_VOLUMEUP } },             /* volume-up key press */
+       { KE_IGNORE, 0xC5, { KEY_VOLUMEUP } },          /* volume-up key release */
+       { KE_KEY, 0xC6, { KEY_VOLUMEDOWN } },           /* volume-down key press */
+       { KE_IGNORE, 0xC7, { KEY_VOLUMEDOWN } },        /* volume-down key release */
+       { KE_KEY,    0xC8, { KEY_ROTATE_LOCK_TOGGLE } },        /* rotate-lock key press */
+       { KE_KEY,    0xC9, { KEY_ROTATE_LOCK_TOGGLE } },        /* rotate-lock key release */
+       { KE_END }
+};
+
+static const struct key_entry intel_vbtn_switchmap[] = {
+       /*
+        * SW_DOCK should only be reported for docking stations, but DSDTs using the
+        * intel-vbtn code, always seem to use this for 2-in-1s / convertibles and set
+        * SW_DOCK=1 when in laptop-mode (in tandem with setting SW_TABLET_MODE=0).
+        * This causes userspace to think the laptop is docked to a port-replicator
+        * and to disable suspend-on-lid-close, which is undesirable.
+        * Map the dock events to KEY_IGNORE to avoid this broken SW_DOCK reporting.
+        */
+       { KE_IGNORE, 0xCA, { .sw = { SW_DOCK, 1 } } },          /* Docked */
+       { KE_IGNORE, 0xCB, { .sw = { SW_DOCK, 0 } } },          /* Undocked */
+       { KE_SW,     0xCC, { .sw = { SW_TABLET_MODE, 1 } } },   /* Tablet */
+       { KE_SW,     0xCD, { .sw = { SW_TABLET_MODE, 0 } } },   /* Laptop */
+       { KE_END }
+};
+
+struct intel_vbtn_priv {
+       struct input_dev *buttons_dev;
+       struct input_dev *switches_dev;
+       bool dual_accel;
+       bool has_buttons;
+       bool has_switches;
+       bool wakeup_mode;
+};
+
+static void detect_tablet_mode(struct platform_device *device)
+{
+       struct intel_vbtn_priv *priv = dev_get_drvdata(&device->dev);
+       acpi_handle handle = ACPI_HANDLE(&device->dev);
+       unsigned long long vgbs;
+       acpi_status status;
+       int m;
+
+       status = acpi_evaluate_integer(handle, "VGBS", NULL, &vgbs);
+       if (ACPI_FAILURE(status))
+               return;
+
+       m = !(vgbs & VGBS_TABLET_MODE_FLAGS);
+       input_report_switch(priv->switches_dev, SW_TABLET_MODE, m);
+       m = (vgbs & VGBS_DOCK_MODE_FLAG) ? 1 : 0;
+       input_report_switch(priv->switches_dev, SW_DOCK, m);
+}
+
+/*
+ * Note this unconditionally creates the 2 input_dev-s and sets up
+ * the sparse-keymaps. Only the registration is conditional on
+ * have_buttons / have_switches. This is done so that the notify
+ * handler can always call sparse_keymap_entry_from_scancode()
+ * on the input_dev-s do determine the event type.
+ */
+static int intel_vbtn_input_setup(struct platform_device *device)
+{
+       struct intel_vbtn_priv *priv = dev_get_drvdata(&device->dev);
+       int ret;
+
+       priv->buttons_dev = devm_input_allocate_device(&device->dev);
+       if (!priv->buttons_dev)
+               return -ENOMEM;
+
+       ret = sparse_keymap_setup(priv->buttons_dev, intel_vbtn_keymap, NULL);
+       if (ret)
+               return ret;
+
+       priv->buttons_dev->dev.parent = &device->dev;
+       priv->buttons_dev->name = "Intel Virtual Buttons";
+       priv->buttons_dev->id.bustype = BUS_HOST;
+
+       if (priv->has_buttons) {
+               ret = input_register_device(priv->buttons_dev);
+               if (ret)
+                       return ret;
+       }
+
+       priv->switches_dev = devm_input_allocate_device(&device->dev);
+       if (!priv->switches_dev)
+               return -ENOMEM;
+
+       ret = sparse_keymap_setup(priv->switches_dev, intel_vbtn_switchmap, NULL);
+       if (ret)
+               return ret;
+
+       priv->switches_dev->dev.parent = &device->dev;
+       priv->switches_dev->name = "Intel Virtual Switches";
+       priv->switches_dev->id.bustype = BUS_HOST;
+
+       if (priv->has_switches) {
+               detect_tablet_mode(device);
+
+               ret = input_register_device(priv->switches_dev);
+               if (ret)
+                       return ret;
+       }
+
+       return 0;
+}
+
+static void notify_handler(acpi_handle handle, u32 event, void *context)
+{
+       struct platform_device *device = context;
+       struct intel_vbtn_priv *priv = dev_get_drvdata(&device->dev);
+       unsigned int val = !(event & 1); /* Even=press, Odd=release */
+       const struct key_entry *ke, *ke_rel;
+       struct input_dev *input_dev;
+       bool autorelease;
+       int ret;
+
+       if ((ke = sparse_keymap_entry_from_scancode(priv->buttons_dev, event))) {
+               if (!priv->has_buttons) {
+                       dev_warn(&device->dev, "Warning: received a button event on a device without buttons, please report this.\n");
+                       return;
+               }
+               input_dev = priv->buttons_dev;
+       } else if ((ke = sparse_keymap_entry_from_scancode(priv->switches_dev, event))) {
+               if (!priv->has_switches) {
+                       /* See dual_accel_detect.h for more info */
+                       if (priv->dual_accel)
+                               return;
+
+                       dev_info(&device->dev, "Registering Intel Virtual Switches input-dev after receiving a switch event\n");
+                       ret = input_register_device(priv->switches_dev);
+                       if (ret)
+                               return;
+
+                       priv->has_switches = true;
+               }
+               input_dev = priv->switches_dev;
+       } else {
+               dev_dbg(&device->dev, "unknown event index 0x%x\n", event);
+               return;
+       }
+
+       if (priv->wakeup_mode) {
+               pm_wakeup_hard_event(&device->dev);
+
+               /*
+                * Skip reporting an evdev event for button wake events,
+                * mirroring how the drivers/acpi/button.c code skips this too.
+                */
+               if (ke->type == KE_KEY)
+                       return;
+       }
+
+       /*
+        * Even press events are autorelease if there is no corresponding odd
+        * release event, or if the odd event is KE_IGNORE.
+        */
+       ke_rel = sparse_keymap_entry_from_scancode(input_dev, event | 1);
+       autorelease = val && (!ke_rel || ke_rel->type == KE_IGNORE);
+
+       sparse_keymap_report_event(input_dev, event, val, autorelease);
+}
+
+/*
+ * There are several laptops (non 2-in-1) models out there which support VGBS,
+ * but simply always return 0, which we translate to SW_TABLET_MODE=1. This in
+ * turn causes userspace (libinput) to suppress events from the builtin
+ * keyboard and touchpad, making the laptop essentially unusable.
+ *
+ * Since the problem of wrongly reporting SW_TABLET_MODE=1 in combination
+ * with libinput, leads to a non-usable system. Where as OTOH many people will
+ * not even notice when SW_TABLET_MODE is not being reported, a DMI based allow
+ * list is used here. This list mainly matches on the chassis-type of 2-in-1s.
+ *
+ * There are also some 2-in-1s which use the intel-vbtn ACPI interface to report
+ * SW_TABLET_MODE with a chassis-type of 8 ("Portable") or 10 ("Notebook"),
+ * these are matched on a per model basis, since many normal laptops with a
+ * possible broken VGBS ACPI-method also use these chassis-types.
+ */
+static const struct dmi_system_id dmi_switches_allow_list[] = {
+       {
+               .matches = {
+                       DMI_EXACT_MATCH(DMI_CHASSIS_TYPE, "31" /* Convertible */),
+               },
+       },
+       {
+               .matches = {
+                       DMI_EXACT_MATCH(DMI_CHASSIS_TYPE, "32" /* Detachable */),
+               },
+       },
+       {
+               .matches = {
+                       DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+                       DMI_MATCH(DMI_PRODUCT_NAME, "Venue 11 Pro 7130"),
+               },
+       },
+       {
+               .matches = {
+                       DMI_MATCH(DMI_SYS_VENDOR, "Hewlett-Packard"),
+                       DMI_MATCH(DMI_PRODUCT_NAME, "HP Pavilion 13 x360 PC"),
+               },
+       },
+       {
+               .matches = {
+                       DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+                       DMI_MATCH(DMI_PRODUCT_NAME, "Switch SA5-271"),
+               },
+       },
+       {
+               .matches = {
+                       DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+                       DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 7352"),
+               },
+       },
+       {} /* Array terminator */
+};
+
+static bool intel_vbtn_has_switches(acpi_handle handle, bool dual_accel)
+{
+       unsigned long long vgbs;
+       acpi_status status;
+
+       /* See dual_accel_detect.h for more info */
+       if (dual_accel)
+               return false;
+
+       if (!dmi_check_system(dmi_switches_allow_list))
+               return false;
+
+       status = acpi_evaluate_integer(handle, "VGBS", NULL, &vgbs);
+       return ACPI_SUCCESS(status);
+}
+
+static int intel_vbtn_probe(struct platform_device *device)
+{
+       acpi_handle handle = ACPI_HANDLE(&device->dev);
+       bool dual_accel, has_buttons, has_switches;
+       struct intel_vbtn_priv *priv;
+       acpi_status status;
+       int err;
+
+       dual_accel = dual_accel_detect();
+       has_buttons = acpi_has_method(handle, "VBDL");
+       has_switches = intel_vbtn_has_switches(handle, dual_accel);
+
+       if (!has_buttons && !has_switches) {
+               dev_warn(&device->dev, "failed to read Intel Virtual Button driver\n");
+               return -ENODEV;
+       }
+
+       priv = devm_kzalloc(&device->dev, sizeof(*priv), GFP_KERNEL);
+       if (!priv)
+               return -ENOMEM;
+       dev_set_drvdata(&device->dev, priv);
+
+       priv->dual_accel = dual_accel;
+       priv->has_buttons = has_buttons;
+       priv->has_switches = has_switches;
+
+       err = intel_vbtn_input_setup(device);
+       if (err) {
+               pr_err("Failed to setup Intel Virtual Button\n");
+               return err;
+       }
+
+       status = acpi_install_notify_handler(handle,
+                                            ACPI_DEVICE_NOTIFY,
+                                            notify_handler,
+                                            device);
+       if (ACPI_FAILURE(status))
+               return -EBUSY;
+
+       if (has_buttons) {
+               status = acpi_evaluate_object(handle, "VBDL", NULL, NULL);
+               if (ACPI_FAILURE(status))
+                       dev_err(&device->dev, "Error VBDL failed with ACPI status %d\n", status);
+       }
+
+       device_init_wakeup(&device->dev, true);
+       /*
+        * In order for system wakeup to work, the EC GPE has to be marked as
+        * a wakeup one, so do that here (this setting will persist, but it has
+        * no effect until the wakeup mask is set for the EC GPE).
+        */
+       acpi_ec_mark_gpe_for_wake();
+       return 0;
+}
+
+static int intel_vbtn_remove(struct platform_device *device)
+{
+       acpi_handle handle = ACPI_HANDLE(&device->dev);
+
+       device_init_wakeup(&device->dev, false);
+       acpi_remove_notify_handler(handle, ACPI_DEVICE_NOTIFY, notify_handler);
+
+       /*
+        * Even if we failed to shut off the event stream, we can still
+        * safely detach from the device.
+        */
+       return 0;
+}
+
+static int intel_vbtn_pm_prepare(struct device *dev)
+{
+       if (device_may_wakeup(dev)) {
+               struct intel_vbtn_priv *priv = dev_get_drvdata(dev);
+
+               priv->wakeup_mode = true;
+       }
+       return 0;
+}
+
+static void intel_vbtn_pm_complete(struct device *dev)
+{
+       struct intel_vbtn_priv *priv = dev_get_drvdata(dev);
+
+       priv->wakeup_mode = false;
+}
+
+static int intel_vbtn_pm_resume(struct device *dev)
+{
+       intel_vbtn_pm_complete(dev);
+       return 0;
+}
+
+static const struct dev_pm_ops intel_vbtn_pm_ops = {
+       .prepare = intel_vbtn_pm_prepare,
+       .complete = intel_vbtn_pm_complete,
+       .resume = intel_vbtn_pm_resume,
+       .restore = intel_vbtn_pm_resume,
+       .thaw = intel_vbtn_pm_resume,
+};
+
+static struct platform_driver intel_vbtn_pl_driver = {
+       .driver = {
+               .name = "intel-vbtn",
+               .acpi_match_table = intel_vbtn_ids,
+               .pm = &intel_vbtn_pm_ops,
+       },
+       .probe = intel_vbtn_probe,
+       .remove = intel_vbtn_remove,
+};
+
+static acpi_status __init
+check_acpi_dev(acpi_handle handle, u32 lvl, void *context, void **rv)
+{
+       const struct acpi_device_id *ids = context;
+       struct acpi_device *dev;
+
+       if (acpi_bus_get_device(handle, &dev) != 0)
+               return AE_OK;
+
+       if (acpi_match_device_ids(dev, ids) == 0)
+               if (!IS_ERR_OR_NULL(acpi_create_platform_device(dev, NULL)))
+                       dev_info(&dev->dev,
+                                "intel-vbtn: created platform device\n");
+
+       return AE_OK;
+}
+
+static int __init intel_vbtn_init(void)
+{
+       acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT,
+                           ACPI_UINT32_MAX, check_acpi_dev, NULL,
+                           (void *)intel_vbtn_ids, NULL);
+
+       return platform_driver_register(&intel_vbtn_pl_driver);
+}
+module_init(intel_vbtn_init);
+
+static void __exit intel_vbtn_exit(void)
+{
+       platform_driver_unregister(&intel_vbtn_pl_driver);
+}
+module_exit(intel_vbtn_exit);