| 1 | |
| 2 | |
| 3 | * power/home/volume button support for
|
| 4 | * Microsoft Surface Pro 3/4 tablet.
|
| 5 | *
|
| 6 | * Copyright (c) 2015 Intel Corporation.
|
| 7 | * All rights reserved.
|
| 8 |
|
| 9 |
|
| 10 | #include <linux/kernel.h>
|
| 11 | #include <linux/module.h>
|
| 12 | #include <linux/init.h>
|
| 13 | #include <linux/string.h>
|
| 14 | #include <linux/types.h>
|
| 15 | #include <linux/input.h>
|
| 16 | #include <linux/acpi.h>
|
| 17 | #include <linux/platform_device.h>
|
| 18 | #include <acpi/button.h>
|
| 19 |
|
| 20 | #define SURFACE_PRO3_BUTTON_HID "MSHW0028"
|
| 21 | #define SURFACE_PRO4_BUTTON_HID "MSHW0040"
|
| 22 | #define SURFACE_BUTTON_OBJ_NAME "VGBI"
|
| 23 |
|
| 24 | #define MSHW0040_DSM_REVISION 0x01
|
| 25 | #define MSHW0040_DSM_GET_OMPR 0x02
|
| 26 | static const guid_t MSHW0040_DSM_UUID =
|
| 27 | GUID_INIT(0x6fd05c69, 0xcde3, 0x49f4, 0x95, 0xed, 0xab, 0x16, 0x65,
|
| 28 | 0x49, 0x80, 0x35);
|
| 29 |
|
| 30 | #define SURFACE_BUTTON_NOTIFY_TABLET_MODE 0xc8
|
| 31 |
|
| 32 | #define SURFACE_BUTTON_NOTIFY_PRESS_POWER 0xc6
|
| 33 | #define SURFACE_BUTTON_NOTIFY_RELEASE_POWER 0xc7
|
| 34 |
|
| 35 | #define SURFACE_BUTTON_NOTIFY_PRESS_HOME 0xc4
|
| 36 | #define SURFACE_BUTTON_NOTIFY_RELEASE_HOME 0xc5
|
| 37 |
|
| 38 | #define SURFACE_BUTTON_NOTIFY_PRESS_VOLUME_UP 0xc0
|
| 39 | #define SURFACE_BUTTON_NOTIFY_RELEASE_VOLUME_UP 0xc1
|
| 40 |
|
| 41 | #define SURFACE_BUTTON_NOTIFY_PRESS_VOLUME_DOWN 0xc2
|
| 42 | #define SURFACE_BUTTON_NOTIFY_RELEASE_VOLUME_DOWN 0xc3
|
| 43 |
|
| 44 | MODULE_AUTHOR("Chen Yu");
|
| 45 | MODULE_DESCRIPTION("Surface Pro3 Button Driver");
|
| 46 | MODULE_LICENSE("GPL v2");
|
| 47 |
|
| 48 | |
| 49 | * Power button, Home button, Volume buttons support is supposed to
|
| 50 | * be covered by drivers/input/misc/soc_button_array.c, which is implemented
|
| 51 | * according to "Windows ACPI Design Guide for SoC Platforms".
|
| 52 | * However surface pro3 seems not to obey the specs, instead it uses
|
| 53 | * device VGBI(MSHW0028) for dispatching the events.
|
| 54 | * We choose acpi_driver rather than platform_driver/i2c_driver because
|
| 55 | * although VGBI has an i2c resource connected to i2c controller, it
|
| 56 | * is not embedded in any i2c controller's scope, thus neither platform_device
|
| 57 | * will be created, nor i2c_client will be enumerated, we have to use
|
| 58 | * acpi_driver.
|
| 59 |
|
| 60 | static const struct acpi_device_id surface_button_device_ids[] = {
|
| 61 | {SURFACE_PRO3_BUTTON_HID, 0},
|
| 62 | {SURFACE_PRO4_BUTTON_HID, 0},
|
| 63 | {"", 0},
|
| 64 | };
|
| 65 | MODULE_DEVICE_TABLE(acpi, surface_button_device_ids);
|
| 66 |
|
| 67 | struct surface_button {
|
| 68 | unsigned int type;
|
| 69 | struct input_dev *input;
|
| 70 | char phys[32];
|
| 71 | unsigned long pushed;
|
| 72 | bool suspended;
|
| 73 | };
|
| 74 |
|
| 75 | static void surface_button_notify(acpi_handle handle, u32 event, void *data)
|
| 76 | {
|
| 77 | struct device *dev = data;
|
| 78 | struct surface_button *button = dev_get_drvdata(dev);
|
| 79 | struct input_dev *input;
|
| 80 | int key_code = KEY_RESERVED;
|
| 81 | bool pressed = false;
|
| 82 |
|
| 83 | switch (event) {
|
| 84 |
|
| 85 | case SURFACE_BUTTON_NOTIFY_PRESS_POWER:
|
| 86 | pressed = true;
|
| 87 | fallthrough;
|
| 88 | case SURFACE_BUTTON_NOTIFY_RELEASE_POWER:
|
| 89 | key_code = KEY_POWER;
|
| 90 | break;
|
| 91 |
|
| 92 | case SURFACE_BUTTON_NOTIFY_PRESS_HOME:
|
| 93 | pressed = true;
|
| 94 | fallthrough;
|
| 95 | case SURFACE_BUTTON_NOTIFY_RELEASE_HOME:
|
| 96 | key_code = KEY_LEFTMETA;
|
| 97 | break;
|
| 98 |
|
| 99 | case SURFACE_BUTTON_NOTIFY_PRESS_VOLUME_UP:
|
| 100 | pressed = true;
|
| 101 | fallthrough;
|
| 102 | case SURFACE_BUTTON_NOTIFY_RELEASE_VOLUME_UP:
|
| 103 | key_code = KEY_VOLUMEUP;
|
| 104 | break;
|
| 105 |
|
| 106 | case SURFACE_BUTTON_NOTIFY_PRESS_VOLUME_DOWN:
|
| 107 | pressed = true;
|
| 108 | fallthrough;
|
| 109 | case SURFACE_BUTTON_NOTIFY_RELEASE_VOLUME_DOWN:
|
| 110 | key_code = KEY_VOLUMEDOWN;
|
| 111 | break;
|
| 112 | case SURFACE_BUTTON_NOTIFY_TABLET_MODE:
|
| 113 | dev_warn_once(dev, "Tablet mode is not supported\n");
|
| 114 | break;
|
| 115 | default:
|
| 116 | dev_info_ratelimited(dev, "Unsupported event [0x%x]\n", event);
|
| 117 | break;
|
| 118 | }
|
| 119 | input = button->input;
|
| 120 | if (key_code == KEY_RESERVED)
|
| 121 | return;
|
| 122 | if (pressed)
|
| 123 | pm_wakeup_dev_event(dev, 0, button->suspended);
|
| 124 | if (button->suspended)
|
| 125 | return;
|
| 126 | input_report_key(input, key_code, pressed?1:0);
|
| 127 | input_sync(input);
|
| 128 | }
|
| 129 |
|
| 130 | #ifdef CONFIG_PM_SLEEP
|
| 131 | static int surface_button_suspend(struct device *dev)
|
| 132 | {
|
| 133 | struct surface_button *button = dev_get_drvdata(dev);
|
| 134 |
|
| 135 | button->suspended = true;
|
| 136 | return 0;
|
| 137 | }
|
| 138 |
|
| 139 | static int surface_button_resume(struct device *dev)
|
| 140 | {
|
| 141 | struct surface_button *button = dev_get_drvdata(dev);
|
| 142 |
|
| 143 | button->suspended = false;
|
| 144 | return 0;
|
| 145 | }
|
| 146 | #endif
|
| 147 |
|
| 148 | |
| 149 | * Surface Pro 4 and Surface Book 2 / Surface Pro 2017 use the same device
|
| 150 | * ID (MSHW0040) for the power/volume buttons. Make sure this is the right
|
| 151 | * device by checking for the _DSM method and OEM Platform Revision.
|
| 152 | *
|
| 153 | * Returns true if the driver should bind to this device, i.e. the device is
|
| 154 | * either MSWH0028 (Pro 3) or MSHW0040 on a Pro 4 or Book 1.
|
| 155 |
|
| 156 | static bool surface_button_check_MSHW0040(struct device *dev, acpi_handle handle)
|
| 157 | {
|
| 158 | union acpi_object *result;
|
| 159 | u64 oem_platform_rev = 0; |
| 160 |
|
| 161 | |
| 162 | result = acpi_evaluate_dsm_typed(handle, &MSHW0040_DSM_UUID,
|
| 163 | MSHW0040_DSM_REVISION,
|
| 164 | MSHW0040_DSM_GET_OMPR,
|
| 165 | NULL, ACPI_TYPE_INTEGER);
|
| 166 |
|
| 167 | |
| 168 | * If evaluating the _DSM fails, the method is not present. This means
|
| 169 | * that we have either MSHW0028 or MSHW0040 on Pro 4 or Book 1, so we
|
| 170 | * should use this driver. We use revision 0 indicating it is
|
| 171 | * unavailable.
|
| 172 |
|
| 173 |
|
| 174 | if (result) {
|
| 175 | oem_platform_rev = result->integer.value;
|
| 176 | ACPI_FREE(result);
|
| 177 | }
|
| 178 |
|
| 179 | dev_dbg(dev, "OEM Platform Revision %llu\n", oem_platform_rev);
|
| 180 |
|
| 181 | return oem_platform_rev == 0;
|
| 182 | }
|
| 183 |
|
| 184 |
|
| 185 | static int surface_button_probe(struct platform_device *pdev)
|
| 186 | {
|
| 187 | struct surface_button *button;
|
| 188 | struct acpi_device *device;
|
| 189 | struct input_dev *input;
|
| 190 | int error;
|
| 191 |
|
| 192 | device = ACPI_COMPANION(&pdev->dev);
|
| 193 | if (!device)
|
| 194 | return -ENODEV;
|
| 195 |
|
| 196 | if (strncmp(acpi_device_bid(device), SURFACE_BUTTON_OBJ_NAME,
|
| 197 | strlen(SURFACE_BUTTON_OBJ_NAME)))
|
| 198 | return -ENODEV;
|
| 199 |
|
| 200 | if (!surface_button_check_MSHW0040(&pdev->dev, device->handle))
|
| 201 | return -ENODEV;
|
| 202 |
|
| 203 | button = kzalloc_obj(struct surface_button);
|
| 204 | if (!button)
|
| 205 | return -ENOMEM;
|
| 206 |
|
| 207 | platform_set_drvdata(pdev, button);
|
| 208 | button->input = input = input_allocate_device();
|
| 209 | if (!input) {
|
| 210 | error = -ENOMEM;
|
| 211 | goto err_free_button;
|
| 212 | }
|
| 213 |
|
| 214 | snprintf(button->phys, sizeof(button->phys), "%s/buttons",
|
| 215 | acpi_device_hid(device));
|
| 216 |
|
| 217 | input->name = "Surface Pro 3/4 Buttons";
|
| 218 | input->phys = button->phys;
|
| 219 | input->id.bustype = BUS_HOST;
|
| 220 | input->dev.parent = &pdev->dev;
|
| 221 | input_set_capability(input, EV_KEY, KEY_POWER);
|
| 222 | input_set_capability(input, EV_KEY, KEY_LEFTMETA);
|
| 223 | input_set_capability(input, EV_KEY, KEY_VOLUMEUP);
|
| 224 | input_set_capability(input, EV_KEY, KEY_VOLUMEDOWN);
|
| 225 |
|
| 226 | error = input_register_device(input);
|
| 227 | if (error)
|
| 228 | goto err_free_input;
|
| 229 |
|
| 230 | device_init_wakeup(&pdev->dev, true);
|
| 231 |
|
| 232 | error = acpi_dev_install_notify_handler(device, ACPI_DEVICE_NOTIFY,
|
| 233 | surface_button_notify, &pdev->dev);
|
| 234 | if (error) {
|
| 235 | device_init_wakeup(&pdev->dev, false);
|
| 236 | input_unregister_device(input);
|
| 237 | goto err_free_button;
|
| 238 | }
|
| 239 |
|
| 240 | dev_info(&pdev->dev, "%s [%s]\n", input->name, acpi_device_bid(device));
|
| 241 |
|
| 242 | return 0;
|
| 243 |
|
| 244 | err_free_input:
|
| 245 | input_free_device(input);
|
| 246 | err_free_button:
|
| 247 | kfree(button);
|
| 248 | return error;
|
| 249 | }
|
| 250 |
|
| 251 | static void surface_button_remove(struct platform_device *pdev)
|
| 252 | {
|
| 253 | struct surface_button *button = platform_get_drvdata(pdev);
|
| 254 |
|
| 255 | acpi_dev_remove_notify_handler(ACPI_COMPANION(&pdev->dev),
|
| 256 | ACPI_DEVICE_NOTIFY, surface_button_notify);
|
| 257 | device_init_wakeup(&pdev->dev, false);
|
| 258 | input_unregister_device(button->input);
|
| 259 | kfree(button);
|
| 260 | }
|
| 261 |
|
| 262 | static SIMPLE_DEV_PM_OPS(surface_button_pm,
|
| 263 | surface_button_suspend, surface_button_resume);
|
| 264 |
|
| 265 | static struct platform_driver surface_button_driver = {
|
| 266 | .probe = surface_button_probe,
|
| 267 | .remove = surface_button_remove,
|
| 268 | .driver = {
|
| 269 | .name = "surface_pro3_button",
|
| 270 | .acpi_match_table = surface_button_device_ids,
|
| 271 | .pm = &surface_button_pm,
|
| 272 | },
|
| 273 | };
|
| 274 |
|
| 275 | module_platform_driver(surface_button_driver);
|