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        return 0;
 180}
 181
 182static int intel_vbtn_remove(struct platform_device *device)
 183{
 184        acpi_handle handle = ACPI_HANDLE(&device->dev);
 185
 186        device_init_wakeup(&device->dev, false);
 187        acpi_remove_notify_handler(handle, ACPI_DEVICE_NOTIFY, notify_handler);
 188
 189        /*
 190         * Even if we failed to shut off the event stream, we can still
 191         * safely detach from the device.
 192         */
 193        return 0;
 194}
 195
 196static int intel_vbtn_pm_prepare(struct device *dev)
 197{
 198        struct intel_vbtn_priv *priv = dev_get_drvdata(dev);
 199
 200        priv->wakeup_mode = true;
 201        return 0;
 202}
 203
 204static int intel_vbtn_pm_resume(struct device *dev)
 205{
 206        struct intel_vbtn_priv *priv = dev_get_drvdata(dev);
 207
 208        priv->wakeup_mode = false;
 209        return 0;
 210}
 211
 212static const struct dev_pm_ops intel_vbtn_pm_ops = {
 213        .prepare = intel_vbtn_pm_prepare,
 214        .resume = intel_vbtn_pm_resume,
 215        .restore = intel_vbtn_pm_resume,
 216        .thaw = intel_vbtn_pm_resume,
 217};
 218
 219static struct platform_driver intel_vbtn_pl_driver = {
 220        .driver = {
 221                .name = "intel-vbtn",
 222                .acpi_match_table = intel_vbtn_ids,
 223                .pm = &intel_vbtn_pm_ops,
 224        },
 225        .probe = intel_vbtn_probe,
 226        .remove = intel_vbtn_remove,
 227};
 228MODULE_DEVICE_TABLE(acpi, intel_vbtn_ids);
 229
 230static acpi_status __init
 231check_acpi_dev(acpi_handle handle, u32 lvl, void *context, void **rv)
 232{
 233        const struct acpi_device_id *ids = context;
 234        struct acpi_device *dev;
 235
 236        if (acpi_bus_get_device(handle, &dev) != 0)
 237                return AE_OK;
 238
 239        if (acpi_match_device_ids(dev, ids) == 0)
 240                if (acpi_create_platform_device(dev, NULL))
 241                        dev_info(&dev->dev,
 242                                 "intel-vbtn: created platform device\n");
 243
 244        return AE_OK;
 245}
 246
 247static int __init intel_vbtn_init(void)
 248{
 249        acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT,
 250                            ACPI_UINT32_MAX, check_acpi_dev, NULL,
 251                            (void *)intel_vbtn_ids, NULL);
 252
 253        return platform_driver_register(&intel_vbtn_pl_driver);
 254}
 255module_init(intel_vbtn_init);
 256
 257static void __exit intel_vbtn_exit(void)
 258{
 259        platform_driver_unregister(&intel_vbtn_pl_driver);
 260}
 261module_exit(intel_vbtn_exit);
 262