18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * power/home/volume button support for 48c2ecf20Sopenharmony_ci * Microsoft Surface Pro 3/4 tablet. 58c2ecf20Sopenharmony_ci * 68c2ecf20Sopenharmony_ci * Copyright (c) 2015 Intel Corporation. 78c2ecf20Sopenharmony_ci * All rights reserved. 88c2ecf20Sopenharmony_ci */ 98c2ecf20Sopenharmony_ci 108c2ecf20Sopenharmony_ci#include <linux/kernel.h> 118c2ecf20Sopenharmony_ci#include <linux/module.h> 128c2ecf20Sopenharmony_ci#include <linux/init.h> 138c2ecf20Sopenharmony_ci#include <linux/types.h> 148c2ecf20Sopenharmony_ci#include <linux/input.h> 158c2ecf20Sopenharmony_ci#include <linux/acpi.h> 168c2ecf20Sopenharmony_ci#include <acpi/button.h> 178c2ecf20Sopenharmony_ci 188c2ecf20Sopenharmony_ci#define SURFACE_PRO3_BUTTON_HID "MSHW0028" 198c2ecf20Sopenharmony_ci#define SURFACE_PRO4_BUTTON_HID "MSHW0040" 208c2ecf20Sopenharmony_ci#define SURFACE_BUTTON_OBJ_NAME "VGBI" 218c2ecf20Sopenharmony_ci#define SURFACE_BUTTON_DEVICE_NAME "Surface Pro 3/4 Buttons" 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_ci#define MSHW0040_DSM_REVISION 0x01 248c2ecf20Sopenharmony_ci#define MSHW0040_DSM_GET_OMPR 0x02 // get OEM Platform Revision 258c2ecf20Sopenharmony_cistatic const guid_t MSHW0040_DSM_UUID = 268c2ecf20Sopenharmony_ci GUID_INIT(0x6fd05c69, 0xcde3, 0x49f4, 0x95, 0xed, 0xab, 0x16, 0x65, 278c2ecf20Sopenharmony_ci 0x49, 0x80, 0x35); 288c2ecf20Sopenharmony_ci 298c2ecf20Sopenharmony_ci#define SURFACE_BUTTON_NOTIFY_TABLET_MODE 0xc8 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_ci#define SURFACE_BUTTON_NOTIFY_PRESS_POWER 0xc6 328c2ecf20Sopenharmony_ci#define SURFACE_BUTTON_NOTIFY_RELEASE_POWER 0xc7 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_ci#define SURFACE_BUTTON_NOTIFY_PRESS_HOME 0xc4 358c2ecf20Sopenharmony_ci#define SURFACE_BUTTON_NOTIFY_RELEASE_HOME 0xc5 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_ci#define SURFACE_BUTTON_NOTIFY_PRESS_VOLUME_UP 0xc0 388c2ecf20Sopenharmony_ci#define SURFACE_BUTTON_NOTIFY_RELEASE_VOLUME_UP 0xc1 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_ci#define SURFACE_BUTTON_NOTIFY_PRESS_VOLUME_DOWN 0xc2 418c2ecf20Sopenharmony_ci#define SURFACE_BUTTON_NOTIFY_RELEASE_VOLUME_DOWN 0xc3 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_ciACPI_MODULE_NAME("surface pro 3 button"); 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_ciMODULE_AUTHOR("Chen Yu"); 468c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Surface Pro3 Button Driver"); 478c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_ci/* 508c2ecf20Sopenharmony_ci * Power button, Home button, Volume buttons support is supposed to 518c2ecf20Sopenharmony_ci * be covered by drivers/input/misc/soc_button_array.c, which is implemented 528c2ecf20Sopenharmony_ci * according to "Windows ACPI Design Guide for SoC Platforms". 538c2ecf20Sopenharmony_ci * However surface pro3 seems not to obey the specs, instead it uses 548c2ecf20Sopenharmony_ci * device VGBI(MSHW0028) for dispatching the events. 558c2ecf20Sopenharmony_ci * We choose acpi_driver rather than platform_driver/i2c_driver because 568c2ecf20Sopenharmony_ci * although VGBI has an i2c resource connected to i2c controller, it 578c2ecf20Sopenharmony_ci * is not embedded in any i2c controller's scope, thus neither platform_device 588c2ecf20Sopenharmony_ci * will be created, nor i2c_client will be enumerated, we have to use 598c2ecf20Sopenharmony_ci * acpi_driver. 608c2ecf20Sopenharmony_ci */ 618c2ecf20Sopenharmony_cistatic const struct acpi_device_id surface_button_device_ids[] = { 628c2ecf20Sopenharmony_ci {SURFACE_PRO3_BUTTON_HID, 0}, 638c2ecf20Sopenharmony_ci {SURFACE_PRO4_BUTTON_HID, 0}, 648c2ecf20Sopenharmony_ci {"", 0}, 658c2ecf20Sopenharmony_ci}; 668c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(acpi, surface_button_device_ids); 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_cistruct surface_button { 698c2ecf20Sopenharmony_ci unsigned int type; 708c2ecf20Sopenharmony_ci struct input_dev *input; 718c2ecf20Sopenharmony_ci char phys[32]; /* for input device */ 728c2ecf20Sopenharmony_ci unsigned long pushed; 738c2ecf20Sopenharmony_ci bool suspended; 748c2ecf20Sopenharmony_ci}; 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_cistatic void surface_button_notify(struct acpi_device *device, u32 event) 778c2ecf20Sopenharmony_ci{ 788c2ecf20Sopenharmony_ci struct surface_button *button = acpi_driver_data(device); 798c2ecf20Sopenharmony_ci struct input_dev *input; 808c2ecf20Sopenharmony_ci int key_code = KEY_RESERVED; 818c2ecf20Sopenharmony_ci bool pressed = false; 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_ci switch (event) { 848c2ecf20Sopenharmony_ci /* Power button press,release handle */ 858c2ecf20Sopenharmony_ci case SURFACE_BUTTON_NOTIFY_PRESS_POWER: 868c2ecf20Sopenharmony_ci pressed = true; 878c2ecf20Sopenharmony_ci fallthrough; 888c2ecf20Sopenharmony_ci case SURFACE_BUTTON_NOTIFY_RELEASE_POWER: 898c2ecf20Sopenharmony_ci key_code = KEY_POWER; 908c2ecf20Sopenharmony_ci break; 918c2ecf20Sopenharmony_ci /* Home button press,release handle */ 928c2ecf20Sopenharmony_ci case SURFACE_BUTTON_NOTIFY_PRESS_HOME: 938c2ecf20Sopenharmony_ci pressed = true; 948c2ecf20Sopenharmony_ci fallthrough; 958c2ecf20Sopenharmony_ci case SURFACE_BUTTON_NOTIFY_RELEASE_HOME: 968c2ecf20Sopenharmony_ci key_code = KEY_LEFTMETA; 978c2ecf20Sopenharmony_ci break; 988c2ecf20Sopenharmony_ci /* Volume up button press,release handle */ 998c2ecf20Sopenharmony_ci case SURFACE_BUTTON_NOTIFY_PRESS_VOLUME_UP: 1008c2ecf20Sopenharmony_ci pressed = true; 1018c2ecf20Sopenharmony_ci fallthrough; 1028c2ecf20Sopenharmony_ci case SURFACE_BUTTON_NOTIFY_RELEASE_VOLUME_UP: 1038c2ecf20Sopenharmony_ci key_code = KEY_VOLUMEUP; 1048c2ecf20Sopenharmony_ci break; 1058c2ecf20Sopenharmony_ci /* Volume down button press,release handle */ 1068c2ecf20Sopenharmony_ci case SURFACE_BUTTON_NOTIFY_PRESS_VOLUME_DOWN: 1078c2ecf20Sopenharmony_ci pressed = true; 1088c2ecf20Sopenharmony_ci fallthrough; 1098c2ecf20Sopenharmony_ci case SURFACE_BUTTON_NOTIFY_RELEASE_VOLUME_DOWN: 1108c2ecf20Sopenharmony_ci key_code = KEY_VOLUMEDOWN; 1118c2ecf20Sopenharmony_ci break; 1128c2ecf20Sopenharmony_ci case SURFACE_BUTTON_NOTIFY_TABLET_MODE: 1138c2ecf20Sopenharmony_ci dev_warn_once(&device->dev, "Tablet mode is not supported\n"); 1148c2ecf20Sopenharmony_ci break; 1158c2ecf20Sopenharmony_ci default: 1168c2ecf20Sopenharmony_ci dev_info_ratelimited(&device->dev, 1178c2ecf20Sopenharmony_ci "Unsupported event [0x%x]\n", event); 1188c2ecf20Sopenharmony_ci break; 1198c2ecf20Sopenharmony_ci } 1208c2ecf20Sopenharmony_ci input = button->input; 1218c2ecf20Sopenharmony_ci if (key_code == KEY_RESERVED) 1228c2ecf20Sopenharmony_ci return; 1238c2ecf20Sopenharmony_ci if (pressed) 1248c2ecf20Sopenharmony_ci pm_wakeup_dev_event(&device->dev, 0, button->suspended); 1258c2ecf20Sopenharmony_ci if (button->suspended) 1268c2ecf20Sopenharmony_ci return; 1278c2ecf20Sopenharmony_ci input_report_key(input, key_code, pressed?1:0); 1288c2ecf20Sopenharmony_ci input_sync(input); 1298c2ecf20Sopenharmony_ci} 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_ci#ifdef CONFIG_PM_SLEEP 1328c2ecf20Sopenharmony_cistatic int surface_button_suspend(struct device *dev) 1338c2ecf20Sopenharmony_ci{ 1348c2ecf20Sopenharmony_ci struct acpi_device *device = to_acpi_device(dev); 1358c2ecf20Sopenharmony_ci struct surface_button *button = acpi_driver_data(device); 1368c2ecf20Sopenharmony_ci 1378c2ecf20Sopenharmony_ci button->suspended = true; 1388c2ecf20Sopenharmony_ci return 0; 1398c2ecf20Sopenharmony_ci} 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_cistatic int surface_button_resume(struct device *dev) 1428c2ecf20Sopenharmony_ci{ 1438c2ecf20Sopenharmony_ci struct acpi_device *device = to_acpi_device(dev); 1448c2ecf20Sopenharmony_ci struct surface_button *button = acpi_driver_data(device); 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_ci button->suspended = false; 1478c2ecf20Sopenharmony_ci return 0; 1488c2ecf20Sopenharmony_ci} 1498c2ecf20Sopenharmony_ci#endif 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_ci/* 1528c2ecf20Sopenharmony_ci * Surface Pro 4 and Surface Book 2 / Surface Pro 2017 use the same device 1538c2ecf20Sopenharmony_ci * ID (MSHW0040) for the power/volume buttons. Make sure this is the right 1548c2ecf20Sopenharmony_ci * device by checking for the _DSM method and OEM Platform Revision. 1558c2ecf20Sopenharmony_ci * 1568c2ecf20Sopenharmony_ci * Returns true if the driver should bind to this device, i.e. the device is 1578c2ecf20Sopenharmony_ci * either MSWH0028 (Pro 3) or MSHW0040 on a Pro 4 or Book 1. 1588c2ecf20Sopenharmony_ci */ 1598c2ecf20Sopenharmony_cistatic bool surface_button_check_MSHW0040(struct acpi_device *dev) 1608c2ecf20Sopenharmony_ci{ 1618c2ecf20Sopenharmony_ci acpi_handle handle = dev->handle; 1628c2ecf20Sopenharmony_ci union acpi_object *result; 1638c2ecf20Sopenharmony_ci u64 oem_platform_rev = 0; // valid revisions are nonzero 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_ci // get OEM platform revision 1668c2ecf20Sopenharmony_ci result = acpi_evaluate_dsm_typed(handle, &MSHW0040_DSM_UUID, 1678c2ecf20Sopenharmony_ci MSHW0040_DSM_REVISION, 1688c2ecf20Sopenharmony_ci MSHW0040_DSM_GET_OMPR, 1698c2ecf20Sopenharmony_ci NULL, ACPI_TYPE_INTEGER); 1708c2ecf20Sopenharmony_ci 1718c2ecf20Sopenharmony_ci /* 1728c2ecf20Sopenharmony_ci * If evaluating the _DSM fails, the method is not present. This means 1738c2ecf20Sopenharmony_ci * that we have either MSHW0028 or MSHW0040 on Pro 4 or Book 1, so we 1748c2ecf20Sopenharmony_ci * should use this driver. We use revision 0 indicating it is 1758c2ecf20Sopenharmony_ci * unavailable. 1768c2ecf20Sopenharmony_ci */ 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_ci if (result) { 1798c2ecf20Sopenharmony_ci oem_platform_rev = result->integer.value; 1808c2ecf20Sopenharmony_ci ACPI_FREE(result); 1818c2ecf20Sopenharmony_ci } 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_ci dev_dbg(&dev->dev, "OEM Platform Revision %llu\n", oem_platform_rev); 1848c2ecf20Sopenharmony_ci 1858c2ecf20Sopenharmony_ci return oem_platform_rev == 0; 1868c2ecf20Sopenharmony_ci} 1878c2ecf20Sopenharmony_ci 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_cistatic int surface_button_add(struct acpi_device *device) 1908c2ecf20Sopenharmony_ci{ 1918c2ecf20Sopenharmony_ci struct surface_button *button; 1928c2ecf20Sopenharmony_ci struct input_dev *input; 1938c2ecf20Sopenharmony_ci const char *hid = acpi_device_hid(device); 1948c2ecf20Sopenharmony_ci char *name; 1958c2ecf20Sopenharmony_ci int error; 1968c2ecf20Sopenharmony_ci 1978c2ecf20Sopenharmony_ci if (strncmp(acpi_device_bid(device), SURFACE_BUTTON_OBJ_NAME, 1988c2ecf20Sopenharmony_ci strlen(SURFACE_BUTTON_OBJ_NAME))) 1998c2ecf20Sopenharmony_ci return -ENODEV; 2008c2ecf20Sopenharmony_ci 2018c2ecf20Sopenharmony_ci if (!surface_button_check_MSHW0040(device)) 2028c2ecf20Sopenharmony_ci return -ENODEV; 2038c2ecf20Sopenharmony_ci 2048c2ecf20Sopenharmony_ci button = kzalloc(sizeof(struct surface_button), GFP_KERNEL); 2058c2ecf20Sopenharmony_ci if (!button) 2068c2ecf20Sopenharmony_ci return -ENOMEM; 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_ci device->driver_data = button; 2098c2ecf20Sopenharmony_ci button->input = input = input_allocate_device(); 2108c2ecf20Sopenharmony_ci if (!input) { 2118c2ecf20Sopenharmony_ci error = -ENOMEM; 2128c2ecf20Sopenharmony_ci goto err_free_button; 2138c2ecf20Sopenharmony_ci } 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_ci name = acpi_device_name(device); 2168c2ecf20Sopenharmony_ci strcpy(name, SURFACE_BUTTON_DEVICE_NAME); 2178c2ecf20Sopenharmony_ci snprintf(button->phys, sizeof(button->phys), "%s/buttons", hid); 2188c2ecf20Sopenharmony_ci 2198c2ecf20Sopenharmony_ci input->name = name; 2208c2ecf20Sopenharmony_ci input->phys = button->phys; 2218c2ecf20Sopenharmony_ci input->id.bustype = BUS_HOST; 2228c2ecf20Sopenharmony_ci input->dev.parent = &device->dev; 2238c2ecf20Sopenharmony_ci input_set_capability(input, EV_KEY, KEY_POWER); 2248c2ecf20Sopenharmony_ci input_set_capability(input, EV_KEY, KEY_LEFTMETA); 2258c2ecf20Sopenharmony_ci input_set_capability(input, EV_KEY, KEY_VOLUMEUP); 2268c2ecf20Sopenharmony_ci input_set_capability(input, EV_KEY, KEY_VOLUMEDOWN); 2278c2ecf20Sopenharmony_ci 2288c2ecf20Sopenharmony_ci error = input_register_device(input); 2298c2ecf20Sopenharmony_ci if (error) 2308c2ecf20Sopenharmony_ci goto err_free_input; 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_ci device_init_wakeup(&device->dev, true); 2338c2ecf20Sopenharmony_ci dev_info(&device->dev, 2348c2ecf20Sopenharmony_ci "%s [%s]\n", name, acpi_device_bid(device)); 2358c2ecf20Sopenharmony_ci return 0; 2368c2ecf20Sopenharmony_ci 2378c2ecf20Sopenharmony_ci err_free_input: 2388c2ecf20Sopenharmony_ci input_free_device(input); 2398c2ecf20Sopenharmony_ci err_free_button: 2408c2ecf20Sopenharmony_ci kfree(button); 2418c2ecf20Sopenharmony_ci return error; 2428c2ecf20Sopenharmony_ci} 2438c2ecf20Sopenharmony_ci 2448c2ecf20Sopenharmony_cistatic int surface_button_remove(struct acpi_device *device) 2458c2ecf20Sopenharmony_ci{ 2468c2ecf20Sopenharmony_ci struct surface_button *button = acpi_driver_data(device); 2478c2ecf20Sopenharmony_ci 2488c2ecf20Sopenharmony_ci input_unregister_device(button->input); 2498c2ecf20Sopenharmony_ci kfree(button); 2508c2ecf20Sopenharmony_ci return 0; 2518c2ecf20Sopenharmony_ci} 2528c2ecf20Sopenharmony_ci 2538c2ecf20Sopenharmony_cistatic SIMPLE_DEV_PM_OPS(surface_button_pm, 2548c2ecf20Sopenharmony_ci surface_button_suspend, surface_button_resume); 2558c2ecf20Sopenharmony_ci 2568c2ecf20Sopenharmony_cistatic struct acpi_driver surface_button_driver = { 2578c2ecf20Sopenharmony_ci .name = "surface_pro3_button", 2588c2ecf20Sopenharmony_ci .class = "SurfacePro3", 2598c2ecf20Sopenharmony_ci .ids = surface_button_device_ids, 2608c2ecf20Sopenharmony_ci .ops = { 2618c2ecf20Sopenharmony_ci .add = surface_button_add, 2628c2ecf20Sopenharmony_ci .remove = surface_button_remove, 2638c2ecf20Sopenharmony_ci .notify = surface_button_notify, 2648c2ecf20Sopenharmony_ci }, 2658c2ecf20Sopenharmony_ci .drv.pm = &surface_button_pm, 2668c2ecf20Sopenharmony_ci}; 2678c2ecf20Sopenharmony_ci 2688c2ecf20Sopenharmony_cimodule_acpi_driver(surface_button_driver); 269