162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * power/home/volume button support for 462306a36Sopenharmony_ci * Microsoft Surface Pro 3/4 tablet. 562306a36Sopenharmony_ci * 662306a36Sopenharmony_ci * Copyright (c) 2015 Intel Corporation. 762306a36Sopenharmony_ci * All rights reserved. 862306a36Sopenharmony_ci */ 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#include <linux/kernel.h> 1162306a36Sopenharmony_ci#include <linux/module.h> 1262306a36Sopenharmony_ci#include <linux/init.h> 1362306a36Sopenharmony_ci#include <linux/types.h> 1462306a36Sopenharmony_ci#include <linux/input.h> 1562306a36Sopenharmony_ci#include <linux/acpi.h> 1662306a36Sopenharmony_ci#include <acpi/button.h> 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_ci#define SURFACE_PRO3_BUTTON_HID "MSHW0028" 1962306a36Sopenharmony_ci#define SURFACE_PRO4_BUTTON_HID "MSHW0040" 2062306a36Sopenharmony_ci#define SURFACE_BUTTON_OBJ_NAME "VGBI" 2162306a36Sopenharmony_ci#define SURFACE_BUTTON_DEVICE_NAME "Surface Pro 3/4 Buttons" 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci#define MSHW0040_DSM_REVISION 0x01 2462306a36Sopenharmony_ci#define MSHW0040_DSM_GET_OMPR 0x02 // get OEM Platform Revision 2562306a36Sopenharmony_cistatic const guid_t MSHW0040_DSM_UUID = 2662306a36Sopenharmony_ci GUID_INIT(0x6fd05c69, 0xcde3, 0x49f4, 0x95, 0xed, 0xab, 0x16, 0x65, 2762306a36Sopenharmony_ci 0x49, 0x80, 0x35); 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_ci#define SURFACE_BUTTON_NOTIFY_TABLET_MODE 0xc8 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_ci#define SURFACE_BUTTON_NOTIFY_PRESS_POWER 0xc6 3262306a36Sopenharmony_ci#define SURFACE_BUTTON_NOTIFY_RELEASE_POWER 0xc7 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_ci#define SURFACE_BUTTON_NOTIFY_PRESS_HOME 0xc4 3562306a36Sopenharmony_ci#define SURFACE_BUTTON_NOTIFY_RELEASE_HOME 0xc5 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_ci#define SURFACE_BUTTON_NOTIFY_PRESS_VOLUME_UP 0xc0 3862306a36Sopenharmony_ci#define SURFACE_BUTTON_NOTIFY_RELEASE_VOLUME_UP 0xc1 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_ci#define SURFACE_BUTTON_NOTIFY_PRESS_VOLUME_DOWN 0xc2 4162306a36Sopenharmony_ci#define SURFACE_BUTTON_NOTIFY_RELEASE_VOLUME_DOWN 0xc3 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_ciMODULE_AUTHOR("Chen Yu"); 4462306a36Sopenharmony_ciMODULE_DESCRIPTION("Surface Pro3 Button Driver"); 4562306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_ci/* 4862306a36Sopenharmony_ci * Power button, Home button, Volume buttons support is supposed to 4962306a36Sopenharmony_ci * be covered by drivers/input/misc/soc_button_array.c, which is implemented 5062306a36Sopenharmony_ci * according to "Windows ACPI Design Guide for SoC Platforms". 5162306a36Sopenharmony_ci * However surface pro3 seems not to obey the specs, instead it uses 5262306a36Sopenharmony_ci * device VGBI(MSHW0028) for dispatching the events. 5362306a36Sopenharmony_ci * We choose acpi_driver rather than platform_driver/i2c_driver because 5462306a36Sopenharmony_ci * although VGBI has an i2c resource connected to i2c controller, it 5562306a36Sopenharmony_ci * is not embedded in any i2c controller's scope, thus neither platform_device 5662306a36Sopenharmony_ci * will be created, nor i2c_client will be enumerated, we have to use 5762306a36Sopenharmony_ci * acpi_driver. 5862306a36Sopenharmony_ci */ 5962306a36Sopenharmony_cistatic const struct acpi_device_id surface_button_device_ids[] = { 6062306a36Sopenharmony_ci {SURFACE_PRO3_BUTTON_HID, 0}, 6162306a36Sopenharmony_ci {SURFACE_PRO4_BUTTON_HID, 0}, 6262306a36Sopenharmony_ci {"", 0}, 6362306a36Sopenharmony_ci}; 6462306a36Sopenharmony_ciMODULE_DEVICE_TABLE(acpi, surface_button_device_ids); 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_cistruct surface_button { 6762306a36Sopenharmony_ci unsigned int type; 6862306a36Sopenharmony_ci struct input_dev *input; 6962306a36Sopenharmony_ci char phys[32]; /* for input device */ 7062306a36Sopenharmony_ci unsigned long pushed; 7162306a36Sopenharmony_ci bool suspended; 7262306a36Sopenharmony_ci}; 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_cistatic void surface_button_notify(struct acpi_device *device, u32 event) 7562306a36Sopenharmony_ci{ 7662306a36Sopenharmony_ci struct surface_button *button = acpi_driver_data(device); 7762306a36Sopenharmony_ci struct input_dev *input; 7862306a36Sopenharmony_ci int key_code = KEY_RESERVED; 7962306a36Sopenharmony_ci bool pressed = false; 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci switch (event) { 8262306a36Sopenharmony_ci /* Power button press,release handle */ 8362306a36Sopenharmony_ci case SURFACE_BUTTON_NOTIFY_PRESS_POWER: 8462306a36Sopenharmony_ci pressed = true; 8562306a36Sopenharmony_ci fallthrough; 8662306a36Sopenharmony_ci case SURFACE_BUTTON_NOTIFY_RELEASE_POWER: 8762306a36Sopenharmony_ci key_code = KEY_POWER; 8862306a36Sopenharmony_ci break; 8962306a36Sopenharmony_ci /* Home button press,release handle */ 9062306a36Sopenharmony_ci case SURFACE_BUTTON_NOTIFY_PRESS_HOME: 9162306a36Sopenharmony_ci pressed = true; 9262306a36Sopenharmony_ci fallthrough; 9362306a36Sopenharmony_ci case SURFACE_BUTTON_NOTIFY_RELEASE_HOME: 9462306a36Sopenharmony_ci key_code = KEY_LEFTMETA; 9562306a36Sopenharmony_ci break; 9662306a36Sopenharmony_ci /* Volume up button press,release handle */ 9762306a36Sopenharmony_ci case SURFACE_BUTTON_NOTIFY_PRESS_VOLUME_UP: 9862306a36Sopenharmony_ci pressed = true; 9962306a36Sopenharmony_ci fallthrough; 10062306a36Sopenharmony_ci case SURFACE_BUTTON_NOTIFY_RELEASE_VOLUME_UP: 10162306a36Sopenharmony_ci key_code = KEY_VOLUMEUP; 10262306a36Sopenharmony_ci break; 10362306a36Sopenharmony_ci /* Volume down button press,release handle */ 10462306a36Sopenharmony_ci case SURFACE_BUTTON_NOTIFY_PRESS_VOLUME_DOWN: 10562306a36Sopenharmony_ci pressed = true; 10662306a36Sopenharmony_ci fallthrough; 10762306a36Sopenharmony_ci case SURFACE_BUTTON_NOTIFY_RELEASE_VOLUME_DOWN: 10862306a36Sopenharmony_ci key_code = KEY_VOLUMEDOWN; 10962306a36Sopenharmony_ci break; 11062306a36Sopenharmony_ci case SURFACE_BUTTON_NOTIFY_TABLET_MODE: 11162306a36Sopenharmony_ci dev_warn_once(&device->dev, "Tablet mode is not supported\n"); 11262306a36Sopenharmony_ci break; 11362306a36Sopenharmony_ci default: 11462306a36Sopenharmony_ci dev_info_ratelimited(&device->dev, 11562306a36Sopenharmony_ci "Unsupported event [0x%x]\n", event); 11662306a36Sopenharmony_ci break; 11762306a36Sopenharmony_ci } 11862306a36Sopenharmony_ci input = button->input; 11962306a36Sopenharmony_ci if (key_code == KEY_RESERVED) 12062306a36Sopenharmony_ci return; 12162306a36Sopenharmony_ci if (pressed) 12262306a36Sopenharmony_ci pm_wakeup_dev_event(&device->dev, 0, button->suspended); 12362306a36Sopenharmony_ci if (button->suspended) 12462306a36Sopenharmony_ci return; 12562306a36Sopenharmony_ci input_report_key(input, key_code, pressed?1:0); 12662306a36Sopenharmony_ci input_sync(input); 12762306a36Sopenharmony_ci} 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci#ifdef CONFIG_PM_SLEEP 13062306a36Sopenharmony_cistatic int surface_button_suspend(struct device *dev) 13162306a36Sopenharmony_ci{ 13262306a36Sopenharmony_ci struct acpi_device *device = to_acpi_device(dev); 13362306a36Sopenharmony_ci struct surface_button *button = acpi_driver_data(device); 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_ci button->suspended = true; 13662306a36Sopenharmony_ci return 0; 13762306a36Sopenharmony_ci} 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_cistatic int surface_button_resume(struct device *dev) 14062306a36Sopenharmony_ci{ 14162306a36Sopenharmony_ci struct acpi_device *device = to_acpi_device(dev); 14262306a36Sopenharmony_ci struct surface_button *button = acpi_driver_data(device); 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci button->suspended = false; 14562306a36Sopenharmony_ci return 0; 14662306a36Sopenharmony_ci} 14762306a36Sopenharmony_ci#endif 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_ci/* 15062306a36Sopenharmony_ci * Surface Pro 4 and Surface Book 2 / Surface Pro 2017 use the same device 15162306a36Sopenharmony_ci * ID (MSHW0040) for the power/volume buttons. Make sure this is the right 15262306a36Sopenharmony_ci * device by checking for the _DSM method and OEM Platform Revision. 15362306a36Sopenharmony_ci * 15462306a36Sopenharmony_ci * Returns true if the driver should bind to this device, i.e. the device is 15562306a36Sopenharmony_ci * either MSWH0028 (Pro 3) or MSHW0040 on a Pro 4 or Book 1. 15662306a36Sopenharmony_ci */ 15762306a36Sopenharmony_cistatic bool surface_button_check_MSHW0040(struct acpi_device *dev) 15862306a36Sopenharmony_ci{ 15962306a36Sopenharmony_ci acpi_handle handle = dev->handle; 16062306a36Sopenharmony_ci union acpi_object *result; 16162306a36Sopenharmony_ci u64 oem_platform_rev = 0; // valid revisions are nonzero 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci // get OEM platform revision 16462306a36Sopenharmony_ci result = acpi_evaluate_dsm_typed(handle, &MSHW0040_DSM_UUID, 16562306a36Sopenharmony_ci MSHW0040_DSM_REVISION, 16662306a36Sopenharmony_ci MSHW0040_DSM_GET_OMPR, 16762306a36Sopenharmony_ci NULL, ACPI_TYPE_INTEGER); 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ci /* 17062306a36Sopenharmony_ci * If evaluating the _DSM fails, the method is not present. This means 17162306a36Sopenharmony_ci * that we have either MSHW0028 or MSHW0040 on Pro 4 or Book 1, so we 17262306a36Sopenharmony_ci * should use this driver. We use revision 0 indicating it is 17362306a36Sopenharmony_ci * unavailable. 17462306a36Sopenharmony_ci */ 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci if (result) { 17762306a36Sopenharmony_ci oem_platform_rev = result->integer.value; 17862306a36Sopenharmony_ci ACPI_FREE(result); 17962306a36Sopenharmony_ci } 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci dev_dbg(&dev->dev, "OEM Platform Revision %llu\n", oem_platform_rev); 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_ci return oem_platform_rev == 0; 18462306a36Sopenharmony_ci} 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_cistatic int surface_button_add(struct acpi_device *device) 18862306a36Sopenharmony_ci{ 18962306a36Sopenharmony_ci struct surface_button *button; 19062306a36Sopenharmony_ci struct input_dev *input; 19162306a36Sopenharmony_ci const char *hid = acpi_device_hid(device); 19262306a36Sopenharmony_ci char *name; 19362306a36Sopenharmony_ci int error; 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ci if (strncmp(acpi_device_bid(device), SURFACE_BUTTON_OBJ_NAME, 19662306a36Sopenharmony_ci strlen(SURFACE_BUTTON_OBJ_NAME))) 19762306a36Sopenharmony_ci return -ENODEV; 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_ci if (!surface_button_check_MSHW0040(device)) 20062306a36Sopenharmony_ci return -ENODEV; 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ci button = kzalloc(sizeof(struct surface_button), GFP_KERNEL); 20362306a36Sopenharmony_ci if (!button) 20462306a36Sopenharmony_ci return -ENOMEM; 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_ci device->driver_data = button; 20762306a36Sopenharmony_ci button->input = input = input_allocate_device(); 20862306a36Sopenharmony_ci if (!input) { 20962306a36Sopenharmony_ci error = -ENOMEM; 21062306a36Sopenharmony_ci goto err_free_button; 21162306a36Sopenharmony_ci } 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci name = acpi_device_name(device); 21462306a36Sopenharmony_ci strcpy(name, SURFACE_BUTTON_DEVICE_NAME); 21562306a36Sopenharmony_ci snprintf(button->phys, sizeof(button->phys), "%s/buttons", hid); 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci input->name = name; 21862306a36Sopenharmony_ci input->phys = button->phys; 21962306a36Sopenharmony_ci input->id.bustype = BUS_HOST; 22062306a36Sopenharmony_ci input->dev.parent = &device->dev; 22162306a36Sopenharmony_ci input_set_capability(input, EV_KEY, KEY_POWER); 22262306a36Sopenharmony_ci input_set_capability(input, EV_KEY, KEY_LEFTMETA); 22362306a36Sopenharmony_ci input_set_capability(input, EV_KEY, KEY_VOLUMEUP); 22462306a36Sopenharmony_ci input_set_capability(input, EV_KEY, KEY_VOLUMEDOWN); 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci error = input_register_device(input); 22762306a36Sopenharmony_ci if (error) 22862306a36Sopenharmony_ci goto err_free_input; 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_ci device_init_wakeup(&device->dev, true); 23162306a36Sopenharmony_ci dev_info(&device->dev, 23262306a36Sopenharmony_ci "%s [%s]\n", name, acpi_device_bid(device)); 23362306a36Sopenharmony_ci return 0; 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_ci err_free_input: 23662306a36Sopenharmony_ci input_free_device(input); 23762306a36Sopenharmony_ci err_free_button: 23862306a36Sopenharmony_ci kfree(button); 23962306a36Sopenharmony_ci return error; 24062306a36Sopenharmony_ci} 24162306a36Sopenharmony_ci 24262306a36Sopenharmony_cistatic void surface_button_remove(struct acpi_device *device) 24362306a36Sopenharmony_ci{ 24462306a36Sopenharmony_ci struct surface_button *button = acpi_driver_data(device); 24562306a36Sopenharmony_ci 24662306a36Sopenharmony_ci input_unregister_device(button->input); 24762306a36Sopenharmony_ci kfree(button); 24862306a36Sopenharmony_ci} 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_cistatic SIMPLE_DEV_PM_OPS(surface_button_pm, 25162306a36Sopenharmony_ci surface_button_suspend, surface_button_resume); 25262306a36Sopenharmony_ci 25362306a36Sopenharmony_cistatic struct acpi_driver surface_button_driver = { 25462306a36Sopenharmony_ci .name = "surface_pro3_button", 25562306a36Sopenharmony_ci .class = "SurfacePro3", 25662306a36Sopenharmony_ci .ids = surface_button_device_ids, 25762306a36Sopenharmony_ci .ops = { 25862306a36Sopenharmony_ci .add = surface_button_add, 25962306a36Sopenharmony_ci .remove = surface_button_remove, 26062306a36Sopenharmony_ci .notify = surface_button_notify, 26162306a36Sopenharmony_ci }, 26262306a36Sopenharmony_ci .drv.pm = &surface_button_pm, 26362306a36Sopenharmony_ci}; 26462306a36Sopenharmony_ci 26562306a36Sopenharmony_cimodule_acpi_driver(surface_button_driver); 266