linux/drivers/platform/x86/intel-vbtn.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2/*
   3 *  Intel Virtual Button driver for Windows 8.1+
   4 *
   5 *  Copyright (C) 2016 AceLan Kao <acelan.kao@canonical.com>
   6 *  Copyright (C) 2016 Alex Hung <alex.hung@canonical.com>
   7 */
   8
   9#include <linux/acpi.h>
  10#include <linux/dmi.h>
  11#include <linux/input.h>
  12#include <linux/input/sparse-keymap.h>
  13#include <linux/kernel.h>
  14#include <linux/module.h>
  15#include <linux/platform_device.h>
  16#include <linux/suspend.h>
  17
  18/* When NOT in tablet mode, VGBS returns with the flag 0x40 */
  19#define TABLET_MODE_FLAG 0x40
  20#define DOCK_MODE_FLAG   0x80
  21
  22MODULE_LICENSE("GPL");
  23MODULE_AUTHOR("AceLan Kao");
  24
  25static const struct acpi_device_id intel_vbtn_ids[] = {
  26        {"INT33D6", 0},
  27        {"", 0},
  28};
  29
  30/* In theory, these are HID usages. */
  31static const struct key_entry intel_vbtn_keymap[] = {
  32        { KE_KEY, 0xC0, { KEY_POWER } },        /* power key press */
  33        { KE_IGNORE, 0xC1, { KEY_POWER } },     /* power key release */
  34        { KE_KEY, 0xC2, { KEY_LEFTMETA } },             /* 'Windows' key press */
  35        { KE_KEY, 0xC3, { KEY_LEFTMETA } },             /* 'Windows' key release */
  36        { KE_KEY, 0xC4, { KEY_VOLUMEUP } },             /* volume-up key press */
  37        { KE_IGNORE, 0xC5, { KEY_VOLUMEUP } },          /* volume-up key release */
  38        { KE_KEY, 0xC6, { KEY_VOLUMEDOWN } },           /* volume-down key press */
  39        { KE_IGNORE, 0xC7, { KEY_VOLUMEDOWN } },        /* volume-down key release */
  40        { KE_KEY,    0xC8, { KEY_ROTATE_LOCK_TOGGLE } },        /* rotate-lock key press */
  41        { KE_KEY,    0xC9, { KEY_ROTATE_LOCK_TOGGLE } },        /* rotate-lock key release */
  42        { KE_SW,     0xCA, { .sw = { SW_DOCK, 1 } } },          /* Docked */
  43        { KE_SW,     0xCB, { .sw = { SW_DOCK, 0 } } },          /* Undocked */
  44        { KE_SW,     0xCC, { .sw = { SW_TABLET_MODE, 1 } } },   /* Tablet */
  45        { KE_SW,     0xCD, { .sw = { SW_TABLET_MODE, 0 } } },   /* Laptop */
  46        { KE_END },
  47};
  48
  49struct intel_vbtn_priv {
  50        struct input_dev *input_dev;
  51        bool wakeup_mode;
  52};
  53
  54static int intel_vbtn_input_setup(struct platform_device *device)
  55{
  56        struct intel_vbtn_priv *priv = dev_get_drvdata(&device->dev);
  57        int ret;
  58
  59        priv->input_dev = devm_input_allocate_device(&device->dev);
  60        if (!priv->input_dev)
  61                return -ENOMEM;
  62
  63        ret = sparse_keymap_setup(priv->input_dev, intel_vbtn_keymap, NULL);
  64        if (ret)
  65                return ret;
  66
  67        priv->input_dev->dev.parent = &device->dev;
  68        priv->input_dev->name = "Intel Virtual Button driver";
  69        priv->input_dev->id.bustype = BUS_HOST;
  70
  71        return input_register_device(priv->input_dev);
  72}
  73
  74static void notify_handler(acpi_handle handle, u32 event, void *context)
  75{
  76        struct platform_device *device = context;
  77        struct intel_vbtn_priv *priv = dev_get_drvdata(&device->dev);
  78        unsigned int val = !(event & 1); /* Even=press, Odd=release */
  79        const struct key_entry *ke, *ke_rel;
  80        bool autorelease;
  81
  82        if (priv->wakeup_mode) {
  83                ke = sparse_keymap_entry_from_scancode(priv->input_dev, event);
  84                if (ke) {
  85                        pm_wakeup_hard_event(&device->dev);
  86
  87                        /*
  88                         * Switch events like tablet mode will wake the device
  89                         * and report the new switch position to the input
  90                         * subsystem.
  91                         */
  92                        if (ke->type == KE_SW)
  93                                sparse_keymap_report_event(priv->input_dev,
  94                                                           event,
  95                                                           val,
  96                                                           0);
  97                        return;
  98                }
  99                goto out_unknown;
 100        }
 101
 102        /*
 103         * Even press events are autorelease if there is no corresponding odd
 104         * release event, or if the odd event is KE_IGNORE.
 105         */
 106        ke_rel = sparse_keymap_entry_from_scancode(priv->input_dev, event | 1);
 107        autorelease = val && (!ke_rel || ke_rel->type == KE_IGNORE);
 108
 109        if (sparse_keymap_report_event(priv->input_dev, event, val, autorelease))
 110                return;
 111
 112out_unknown:
 113        dev_dbg(&device->dev, "unknown event index 0x%x\n", event);
 114}
 115
 116static void detect_tablet_mode(struct platform_device *device)
 117{
 118        const char *chassis_type = dmi_get_system_info(DMI_CHASSIS_TYPE);
 119        struct intel_vbtn_priv *priv = dev_get_drvdata(&device->dev);
 120        acpi_handle handle = ACPI_HANDLE(&device->dev);
 121        struct acpi_buffer vgbs_output = { ACPI_ALLOCATE_BUFFER, NULL };
 122        union acpi_object *obj;
 123        acpi_status status;
 124        int m;
 125
 126        if (!(chassis_type && strcmp(chassis_type, "31") == 0))
 127                goto out;
 128
 129        status = acpi_evaluate_object(handle, "VGBS", NULL, &vgbs_output);
 130        if (ACPI_FAILURE(status))
 131                goto out;
 132
 133        obj = vgbs_output.pointer;
 134        if (!(obj && obj->type == ACPI_TYPE_INTEGER))
 135                goto out;
 136
 137        m = !(obj->integer.value & TABLET_MODE_FLAG);
 138        input_report_switch(priv->input_dev, SW_TABLET_MODE, m);
 139        m = (obj->integer.value & DOCK_MODE_FLAG) ? 1 : 0;
 140        input_report_switch(priv->input_dev, SW_DOCK, m);
 141out:
 142        kfree(vgbs_output.pointer);
 143}
 144
 145static int intel_vbtn_probe(struct platform_device *device)
 146{
 147        acpi_handle handle = ACPI_HANDLE(&device->dev);
 148        struct intel_vbtn_priv *priv;
 149        acpi_status status;
 150        int err;
 151
 152        status = acpi_evaluate_object(handle, "VBDL", NULL, NULL);
 153        if (ACPI_FAILURE(status)) {
 154                dev_warn(&device->dev, "failed to read Intel Virtual Button driver\n");
 155                return -ENODEV;
 156        }
 157
 158        priv = devm_kzalloc(&device->dev, sizeof(*priv), GFP_KERNEL);
 159        if (!priv)
 160                return -ENOMEM;
 161        dev_set_drvdata(&device->dev, priv);
 162
 163        err = intel_vbtn_input_setup(device);
 164        if (err) {
 165                pr_err("Failed to setup Intel Virtual Button\n");
 166                return err;
 167        }
 168
 169        detect_tablet_mode(device);
 170
 171        status = acpi_install_notify_handler(handle,
 172                                             ACPI_DEVICE_NOTIFY,
 173                                             notify_handler,
 174                                             device);
 175        if (ACPI_FAILURE(status))
 176                return -EBUSY;
 177
 178        device_init_wakeup(&device->dev, true);
 179        /*
 180         * In order for system wakeup to work, the EC GPE has to be marked as
 181         * a wakeup one, so do that here (this setting will persist, but it has
 182         * no effect until the wakeup mask is set for the EC GPE).
 183         */
 184        acpi_ec_mark_gpe_for_wake();
 185        return 0;
 186}
 187
 188static int intel_vbtn_remove(struct platform_device *device)
 189{
 190        acpi_handle handle = ACPI_HANDLE(&device->dev);
 191
 192        device_init_wakeup(&device->dev, false);
 193        acpi_remove_notify_handler(handle, ACPI_DEVICE_NOTIFY, notify_handler);
 194
 195        /*
 196         * Even if we failed to shut off the event stream, we can still
 197         * safely detach from the device.
 198         */
 199        return 0;
 200}
 201
 202static int intel_vbtn_pm_prepare(struct device *dev)
 203{
 204        if (device_may_wakeup(dev)) {
 205                struct intel_vbtn_priv *priv = dev_get_drvdata(dev);
 206
 207                priv->wakeup_mode = true;
 208        }
 209        return 0;
 210}
 211
 212static void intel_vbtn_pm_complete(struct device *dev)
 213{
 214        struct intel_vbtn_priv *priv = dev_get_drvdata(dev);
 215
 216        priv->wakeup_mode = false;
 217}
 218
 219static int intel_vbtn_pm_resume(struct device *dev)
 220{
 221        intel_vbtn_pm_complete(dev);
 222        return 0;
 223}
 224
 225static const struct dev_pm_ops intel_vbtn_pm_ops = {
 226        .prepare = intel_vbtn_pm_prepare,
 227        .complete = intel_vbtn_pm_complete,
 228        .resume = intel_vbtn_pm_resume,
 229        .restore = intel_vbtn_pm_resume,
 230        .thaw = intel_vbtn_pm_resume,
 231};
 232
 233static struct platform_driver intel_vbtn_pl_driver = {
 234        .driver = {
 235                .name = "intel-vbtn",
 236                .acpi_match_table = intel_vbtn_ids,
 237                .pm = &intel_vbtn_pm_ops,
 238        },
 239        .probe = intel_vbtn_probe,
 240        .remove = intel_vbtn_remove,
 241};
 242MODULE_DEVICE_TABLE(acpi, intel_vbtn_ids);
 243
 244static acpi_status __init
 245check_acpi_dev(acpi_handle handle, u32 lvl, void *context, void **rv)
 246{
 247        const struct acpi_device_id *ids = context;
 248        struct acpi_device *dev;
 249
 250        if (acpi_bus_get_device(handle, &dev) != 0)
 251                return AE_OK;
 252
 253        if (acpi_match_device_ids(dev, ids) == 0)
 254                if (acpi_create_platform_device(dev, NULL))
 255                        dev_info(&dev->dev,
 256                                 "intel-vbtn: created platform device\n");
 257
 258        return AE_OK;
 259}
 260
 261static int __init intel_vbtn_init(void)
 262{
 263        acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT,
 264                            ACPI_UINT32_MAX, check_acpi_dev, NULL,
 265                            (void *)intel_vbtn_ids, NULL);
 266
 267        return platform_driver_register(&intel_vbtn_pl_driver);
 268}
 269module_init(intel_vbtn_init);
 270
 271static void __exit intel_vbtn_exit(void)
 272{
 273        platform_driver_unregister(&intel_vbtn_pl_driver);
 274}
 275module_exit(intel_vbtn_exit);
 276