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