18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci *  Intel HID event & 5 button array driver
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci *  Copyright (C) 2015 Alex Hung <alex.hung@canonical.com>
68c2ecf20Sopenharmony_ci *  Copyright (C) 2015 Andrew Lutomirski <luto@kernel.org>
78c2ecf20Sopenharmony_ci */
88c2ecf20Sopenharmony_ci
98c2ecf20Sopenharmony_ci#include <linux/acpi.h>
108c2ecf20Sopenharmony_ci#include <linux/dmi.h>
118c2ecf20Sopenharmony_ci#include <linux/input.h>
128c2ecf20Sopenharmony_ci#include <linux/input/sparse-keymap.h>
138c2ecf20Sopenharmony_ci#include <linux/kernel.h>
148c2ecf20Sopenharmony_ci#include <linux/module.h>
158c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
168c2ecf20Sopenharmony_ci#include <linux/suspend.h>
178c2ecf20Sopenharmony_ci
188c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
198c2ecf20Sopenharmony_ciMODULE_AUTHOR("Alex Hung");
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_cistatic const struct acpi_device_id intel_hid_ids[] = {
228c2ecf20Sopenharmony_ci	{"INT33D5", 0},
238c2ecf20Sopenharmony_ci	{"INTC1051", 0},
248c2ecf20Sopenharmony_ci	{"", 0},
258c2ecf20Sopenharmony_ci};
268c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(acpi, intel_hid_ids);
278c2ecf20Sopenharmony_ci
288c2ecf20Sopenharmony_ci/* In theory, these are HID usages. */
298c2ecf20Sopenharmony_cistatic const struct key_entry intel_hid_keymap[] = {
308c2ecf20Sopenharmony_ci	/* 1: LSuper (Page 0x07, usage 0xE3) -- unclear what to do */
318c2ecf20Sopenharmony_ci	/* 2: Toggle SW_ROTATE_LOCK -- easy to implement if seen in wild */
328c2ecf20Sopenharmony_ci	{ KE_KEY, 3, { KEY_NUMLOCK } },
338c2ecf20Sopenharmony_ci	{ KE_KEY, 4, { KEY_HOME } },
348c2ecf20Sopenharmony_ci	{ KE_KEY, 5, { KEY_END } },
358c2ecf20Sopenharmony_ci	{ KE_KEY, 6, { KEY_PAGEUP } },
368c2ecf20Sopenharmony_ci	{ KE_KEY, 7, { KEY_PAGEDOWN } },
378c2ecf20Sopenharmony_ci	{ KE_KEY, 8, { KEY_RFKILL } },
388c2ecf20Sopenharmony_ci	{ KE_KEY, 9, { KEY_POWER } },
398c2ecf20Sopenharmony_ci	{ KE_KEY, 11, { KEY_SLEEP } },
408c2ecf20Sopenharmony_ci	/* 13 has two different meanings in the spec -- ignore it. */
418c2ecf20Sopenharmony_ci	{ KE_KEY, 14, { KEY_STOPCD } },
428c2ecf20Sopenharmony_ci	{ KE_KEY, 15, { KEY_PLAYPAUSE } },
438c2ecf20Sopenharmony_ci	{ KE_KEY, 16, { KEY_MUTE } },
448c2ecf20Sopenharmony_ci	{ KE_KEY, 17, { KEY_VOLUMEUP } },
458c2ecf20Sopenharmony_ci	{ KE_KEY, 18, { KEY_VOLUMEDOWN } },
468c2ecf20Sopenharmony_ci	{ KE_KEY, 19, { KEY_BRIGHTNESSUP } },
478c2ecf20Sopenharmony_ci	{ KE_KEY, 20, { KEY_BRIGHTNESSDOWN } },
488c2ecf20Sopenharmony_ci	/* 27: wake -- needs special handling */
498c2ecf20Sopenharmony_ci	{ KE_END },
508c2ecf20Sopenharmony_ci};
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_ci/* 5 button array notification value. */
538c2ecf20Sopenharmony_cistatic const struct key_entry intel_array_keymap[] = {
548c2ecf20Sopenharmony_ci	{ KE_KEY,    0xC2, { KEY_LEFTMETA } },                /* Press */
558c2ecf20Sopenharmony_ci	{ KE_IGNORE, 0xC3, { KEY_LEFTMETA } },                /* Release */
568c2ecf20Sopenharmony_ci	{ KE_KEY,    0xC4, { KEY_VOLUMEUP } },                /* Press */
578c2ecf20Sopenharmony_ci	{ KE_IGNORE, 0xC5, { KEY_VOLUMEUP } },                /* Release */
588c2ecf20Sopenharmony_ci	{ KE_KEY,    0xC6, { KEY_VOLUMEDOWN } },              /* Press */
598c2ecf20Sopenharmony_ci	{ KE_IGNORE, 0xC7, { KEY_VOLUMEDOWN } },              /* Release */
608c2ecf20Sopenharmony_ci	{ KE_KEY,    0xC8, { KEY_ROTATE_LOCK_TOGGLE } },      /* Press */
618c2ecf20Sopenharmony_ci	{ KE_IGNORE, 0xC9, { KEY_ROTATE_LOCK_TOGGLE } },      /* Release */
628c2ecf20Sopenharmony_ci	{ KE_KEY,    0xCE, { KEY_POWER } },                   /* Press */
638c2ecf20Sopenharmony_ci	{ KE_IGNORE, 0xCF, { KEY_POWER } },                   /* Release */
648c2ecf20Sopenharmony_ci	{ KE_END },
658c2ecf20Sopenharmony_ci};
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_cistatic const struct dmi_system_id button_array_table[] = {
688c2ecf20Sopenharmony_ci	{
698c2ecf20Sopenharmony_ci		.ident = "Wacom MobileStudio Pro 13",
708c2ecf20Sopenharmony_ci		.matches = {
718c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_SYS_VENDOR, "Wacom Co.,Ltd"),
728c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_PRODUCT_NAME, "Wacom MobileStudio Pro 13"),
738c2ecf20Sopenharmony_ci		},
748c2ecf20Sopenharmony_ci	},
758c2ecf20Sopenharmony_ci	{
768c2ecf20Sopenharmony_ci		.ident = "Wacom MobileStudio Pro 16",
778c2ecf20Sopenharmony_ci		.matches = {
788c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_SYS_VENDOR, "Wacom Co.,Ltd"),
798c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_PRODUCT_NAME, "Wacom MobileStudio Pro 16"),
808c2ecf20Sopenharmony_ci		},
818c2ecf20Sopenharmony_ci	},
828c2ecf20Sopenharmony_ci	{
838c2ecf20Sopenharmony_ci		.ident = "HP Spectre x2 (2015)",
848c2ecf20Sopenharmony_ci		.matches = {
858c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_SYS_VENDOR, "HP"),
868c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_PRODUCT_NAME, "HP Spectre x2 Detachable"),
878c2ecf20Sopenharmony_ci		},
888c2ecf20Sopenharmony_ci	},
898c2ecf20Sopenharmony_ci	{
908c2ecf20Sopenharmony_ci		.ident = "Lenovo ThinkPad X1 Tablet Gen 2",
918c2ecf20Sopenharmony_ci		.matches = {
928c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
938c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_PRODUCT_FAMILY, "ThinkPad X1 Tablet Gen 2"),
948c2ecf20Sopenharmony_ci		},
958c2ecf20Sopenharmony_ci	},
968c2ecf20Sopenharmony_ci	{
978c2ecf20Sopenharmony_ci		.ident = "Microsoft Surface Go 3",
988c2ecf20Sopenharmony_ci		.matches = {
998c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
1008c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_PRODUCT_NAME, "Surface Go 3"),
1018c2ecf20Sopenharmony_ci		},
1028c2ecf20Sopenharmony_ci	},
1038c2ecf20Sopenharmony_ci	{ }
1048c2ecf20Sopenharmony_ci};
1058c2ecf20Sopenharmony_ci
1068c2ecf20Sopenharmony_cistruct intel_hid_priv {
1078c2ecf20Sopenharmony_ci	struct input_dev *input_dev;
1088c2ecf20Sopenharmony_ci	struct input_dev *array;
1098c2ecf20Sopenharmony_ci	bool wakeup_mode;
1108c2ecf20Sopenharmony_ci};
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_ci#define HID_EVENT_FILTER_UUID	"eeec56b3-4442-408f-a792-4edd4d758054"
1138c2ecf20Sopenharmony_ci
1148c2ecf20Sopenharmony_cienum intel_hid_dsm_fn_codes {
1158c2ecf20Sopenharmony_ci	INTEL_HID_DSM_FN_INVALID,
1168c2ecf20Sopenharmony_ci	INTEL_HID_DSM_BTNL_FN,
1178c2ecf20Sopenharmony_ci	INTEL_HID_DSM_HDMM_FN,
1188c2ecf20Sopenharmony_ci	INTEL_HID_DSM_HDSM_FN,
1198c2ecf20Sopenharmony_ci	INTEL_HID_DSM_HDEM_FN,
1208c2ecf20Sopenharmony_ci	INTEL_HID_DSM_BTNS_FN,
1218c2ecf20Sopenharmony_ci	INTEL_HID_DSM_BTNE_FN,
1228c2ecf20Sopenharmony_ci	INTEL_HID_DSM_HEBC_V1_FN,
1238c2ecf20Sopenharmony_ci	INTEL_HID_DSM_VGBS_FN,
1248c2ecf20Sopenharmony_ci	INTEL_HID_DSM_HEBC_V2_FN,
1258c2ecf20Sopenharmony_ci	INTEL_HID_DSM_FN_MAX
1268c2ecf20Sopenharmony_ci};
1278c2ecf20Sopenharmony_ci
1288c2ecf20Sopenharmony_cistatic const char *intel_hid_dsm_fn_to_method[INTEL_HID_DSM_FN_MAX] = {
1298c2ecf20Sopenharmony_ci	NULL,
1308c2ecf20Sopenharmony_ci	"BTNL",
1318c2ecf20Sopenharmony_ci	"HDMM",
1328c2ecf20Sopenharmony_ci	"HDSM",
1338c2ecf20Sopenharmony_ci	"HDEM",
1348c2ecf20Sopenharmony_ci	"BTNS",
1358c2ecf20Sopenharmony_ci	"BTNE",
1368c2ecf20Sopenharmony_ci	"HEBC",
1378c2ecf20Sopenharmony_ci	"VGBS",
1388c2ecf20Sopenharmony_ci	"HEBC"
1398c2ecf20Sopenharmony_ci};
1408c2ecf20Sopenharmony_ci
1418c2ecf20Sopenharmony_cistatic unsigned long long intel_hid_dsm_fn_mask;
1428c2ecf20Sopenharmony_cistatic guid_t intel_dsm_guid;
1438c2ecf20Sopenharmony_ci
1448c2ecf20Sopenharmony_cistatic bool intel_hid_execute_method(acpi_handle handle,
1458c2ecf20Sopenharmony_ci				     enum intel_hid_dsm_fn_codes fn_index,
1468c2ecf20Sopenharmony_ci				     unsigned long long arg)
1478c2ecf20Sopenharmony_ci{
1488c2ecf20Sopenharmony_ci	union acpi_object *obj, argv4, req;
1498c2ecf20Sopenharmony_ci	acpi_status status;
1508c2ecf20Sopenharmony_ci	char *method_name;
1518c2ecf20Sopenharmony_ci
1528c2ecf20Sopenharmony_ci	if (fn_index <= INTEL_HID_DSM_FN_INVALID ||
1538c2ecf20Sopenharmony_ci	    fn_index >= INTEL_HID_DSM_FN_MAX)
1548c2ecf20Sopenharmony_ci		return false;
1558c2ecf20Sopenharmony_ci
1568c2ecf20Sopenharmony_ci	method_name = (char *)intel_hid_dsm_fn_to_method[fn_index];
1578c2ecf20Sopenharmony_ci
1588c2ecf20Sopenharmony_ci	if (!(intel_hid_dsm_fn_mask & fn_index))
1598c2ecf20Sopenharmony_ci		goto skip_dsm_exec;
1608c2ecf20Sopenharmony_ci
1618c2ecf20Sopenharmony_ci	/* All methods expects a package with one integer element */
1628c2ecf20Sopenharmony_ci	req.type = ACPI_TYPE_INTEGER;
1638c2ecf20Sopenharmony_ci	req.integer.value = arg;
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_ci	argv4.type = ACPI_TYPE_PACKAGE;
1668c2ecf20Sopenharmony_ci	argv4.package.count = 1;
1678c2ecf20Sopenharmony_ci	argv4.package.elements = &req;
1688c2ecf20Sopenharmony_ci
1698c2ecf20Sopenharmony_ci	obj = acpi_evaluate_dsm(handle, &intel_dsm_guid, 1, fn_index, &argv4);
1708c2ecf20Sopenharmony_ci	if (obj) {
1718c2ecf20Sopenharmony_ci		acpi_handle_debug(handle, "Exec DSM Fn code: %d[%s] success\n",
1728c2ecf20Sopenharmony_ci				  fn_index, method_name);
1738c2ecf20Sopenharmony_ci		ACPI_FREE(obj);
1748c2ecf20Sopenharmony_ci		return true;
1758c2ecf20Sopenharmony_ci	}
1768c2ecf20Sopenharmony_ci
1778c2ecf20Sopenharmony_ciskip_dsm_exec:
1788c2ecf20Sopenharmony_ci	status = acpi_execute_simple_method(handle, method_name, arg);
1798c2ecf20Sopenharmony_ci	if (ACPI_SUCCESS(status))
1808c2ecf20Sopenharmony_ci		return true;
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_ci	return false;
1838c2ecf20Sopenharmony_ci}
1848c2ecf20Sopenharmony_ci
1858c2ecf20Sopenharmony_cistatic bool intel_hid_evaluate_method(acpi_handle handle,
1868c2ecf20Sopenharmony_ci				      enum intel_hid_dsm_fn_codes fn_index,
1878c2ecf20Sopenharmony_ci				      unsigned long long *result)
1888c2ecf20Sopenharmony_ci{
1898c2ecf20Sopenharmony_ci	union acpi_object *obj;
1908c2ecf20Sopenharmony_ci	acpi_status status;
1918c2ecf20Sopenharmony_ci	char *method_name;
1928c2ecf20Sopenharmony_ci
1938c2ecf20Sopenharmony_ci	if (fn_index <= INTEL_HID_DSM_FN_INVALID ||
1948c2ecf20Sopenharmony_ci	    fn_index >= INTEL_HID_DSM_FN_MAX)
1958c2ecf20Sopenharmony_ci		return false;
1968c2ecf20Sopenharmony_ci
1978c2ecf20Sopenharmony_ci	method_name = (char *)intel_hid_dsm_fn_to_method[fn_index];
1988c2ecf20Sopenharmony_ci
1998c2ecf20Sopenharmony_ci	if (!(intel_hid_dsm_fn_mask & fn_index))
2008c2ecf20Sopenharmony_ci		goto skip_dsm_eval;
2018c2ecf20Sopenharmony_ci
2028c2ecf20Sopenharmony_ci	obj = acpi_evaluate_dsm_typed(handle, &intel_dsm_guid,
2038c2ecf20Sopenharmony_ci				      1, fn_index,
2048c2ecf20Sopenharmony_ci				      NULL,  ACPI_TYPE_INTEGER);
2058c2ecf20Sopenharmony_ci	if (obj) {
2068c2ecf20Sopenharmony_ci		*result = obj->integer.value;
2078c2ecf20Sopenharmony_ci		acpi_handle_debug(handle,
2088c2ecf20Sopenharmony_ci				  "Eval DSM Fn code: %d[%s] results: 0x%llx\n",
2098c2ecf20Sopenharmony_ci				  fn_index, method_name, *result);
2108c2ecf20Sopenharmony_ci		ACPI_FREE(obj);
2118c2ecf20Sopenharmony_ci		return true;
2128c2ecf20Sopenharmony_ci	}
2138c2ecf20Sopenharmony_ci
2148c2ecf20Sopenharmony_ciskip_dsm_eval:
2158c2ecf20Sopenharmony_ci	status = acpi_evaluate_integer(handle, method_name, NULL, result);
2168c2ecf20Sopenharmony_ci	if (ACPI_SUCCESS(status))
2178c2ecf20Sopenharmony_ci		return true;
2188c2ecf20Sopenharmony_ci
2198c2ecf20Sopenharmony_ci	return false;
2208c2ecf20Sopenharmony_ci}
2218c2ecf20Sopenharmony_ci
2228c2ecf20Sopenharmony_cistatic void intel_hid_init_dsm(acpi_handle handle)
2238c2ecf20Sopenharmony_ci{
2248c2ecf20Sopenharmony_ci	union acpi_object *obj;
2258c2ecf20Sopenharmony_ci
2268c2ecf20Sopenharmony_ci	guid_parse(HID_EVENT_FILTER_UUID, &intel_dsm_guid);
2278c2ecf20Sopenharmony_ci
2288c2ecf20Sopenharmony_ci	obj = acpi_evaluate_dsm_typed(handle, &intel_dsm_guid, 1, 0, NULL,
2298c2ecf20Sopenharmony_ci				      ACPI_TYPE_BUFFER);
2308c2ecf20Sopenharmony_ci	if (obj) {
2318c2ecf20Sopenharmony_ci		intel_hid_dsm_fn_mask = *obj->buffer.pointer;
2328c2ecf20Sopenharmony_ci		ACPI_FREE(obj);
2338c2ecf20Sopenharmony_ci	}
2348c2ecf20Sopenharmony_ci
2358c2ecf20Sopenharmony_ci	acpi_handle_debug(handle, "intel_hid_dsm_fn_mask = %llx\n",
2368c2ecf20Sopenharmony_ci			  intel_hid_dsm_fn_mask);
2378c2ecf20Sopenharmony_ci}
2388c2ecf20Sopenharmony_ci
2398c2ecf20Sopenharmony_cistatic int intel_hid_set_enable(struct device *device, bool enable)
2408c2ecf20Sopenharmony_ci{
2418c2ecf20Sopenharmony_ci	acpi_handle handle = ACPI_HANDLE(device);
2428c2ecf20Sopenharmony_ci
2438c2ecf20Sopenharmony_ci	/* Enable|disable features - power button is always enabled */
2448c2ecf20Sopenharmony_ci	if (!intel_hid_execute_method(handle, INTEL_HID_DSM_HDSM_FN,
2458c2ecf20Sopenharmony_ci				      enable)) {
2468c2ecf20Sopenharmony_ci		dev_warn(device, "failed to %sable hotkeys\n",
2478c2ecf20Sopenharmony_ci			 enable ? "en" : "dis");
2488c2ecf20Sopenharmony_ci		return -EIO;
2498c2ecf20Sopenharmony_ci	}
2508c2ecf20Sopenharmony_ci
2518c2ecf20Sopenharmony_ci	return 0;
2528c2ecf20Sopenharmony_ci}
2538c2ecf20Sopenharmony_ci
2548c2ecf20Sopenharmony_cistatic void intel_button_array_enable(struct device *device, bool enable)
2558c2ecf20Sopenharmony_ci{
2568c2ecf20Sopenharmony_ci	struct intel_hid_priv *priv = dev_get_drvdata(device);
2578c2ecf20Sopenharmony_ci	acpi_handle handle = ACPI_HANDLE(device);
2588c2ecf20Sopenharmony_ci	unsigned long long button_cap;
2598c2ecf20Sopenharmony_ci	acpi_status status;
2608c2ecf20Sopenharmony_ci
2618c2ecf20Sopenharmony_ci	if (!priv->array)
2628c2ecf20Sopenharmony_ci		return;
2638c2ecf20Sopenharmony_ci
2648c2ecf20Sopenharmony_ci	/* Query supported platform features */
2658c2ecf20Sopenharmony_ci	status = acpi_evaluate_integer(handle, "BTNC", NULL, &button_cap);
2668c2ecf20Sopenharmony_ci	if (ACPI_FAILURE(status)) {
2678c2ecf20Sopenharmony_ci		dev_warn(device, "failed to get button capability\n");
2688c2ecf20Sopenharmony_ci		return;
2698c2ecf20Sopenharmony_ci	}
2708c2ecf20Sopenharmony_ci
2718c2ecf20Sopenharmony_ci	/* Enable|disable features - power button is always enabled */
2728c2ecf20Sopenharmony_ci	if (!intel_hid_execute_method(handle, INTEL_HID_DSM_BTNE_FN,
2738c2ecf20Sopenharmony_ci				      enable ? button_cap : 1))
2748c2ecf20Sopenharmony_ci		dev_warn(device, "failed to set button capability\n");
2758c2ecf20Sopenharmony_ci}
2768c2ecf20Sopenharmony_ci
2778c2ecf20Sopenharmony_cistatic int intel_hid_pm_prepare(struct device *device)
2788c2ecf20Sopenharmony_ci{
2798c2ecf20Sopenharmony_ci	if (device_may_wakeup(device)) {
2808c2ecf20Sopenharmony_ci		struct intel_hid_priv *priv = dev_get_drvdata(device);
2818c2ecf20Sopenharmony_ci
2828c2ecf20Sopenharmony_ci		priv->wakeup_mode = true;
2838c2ecf20Sopenharmony_ci	}
2848c2ecf20Sopenharmony_ci	return 0;
2858c2ecf20Sopenharmony_ci}
2868c2ecf20Sopenharmony_ci
2878c2ecf20Sopenharmony_cistatic void intel_hid_pm_complete(struct device *device)
2888c2ecf20Sopenharmony_ci{
2898c2ecf20Sopenharmony_ci	struct intel_hid_priv *priv = dev_get_drvdata(device);
2908c2ecf20Sopenharmony_ci
2918c2ecf20Sopenharmony_ci	priv->wakeup_mode = false;
2928c2ecf20Sopenharmony_ci}
2938c2ecf20Sopenharmony_ci
2948c2ecf20Sopenharmony_cistatic int intel_hid_pl_suspend_handler(struct device *device)
2958c2ecf20Sopenharmony_ci{
2968c2ecf20Sopenharmony_ci	intel_button_array_enable(device, false);
2978c2ecf20Sopenharmony_ci
2988c2ecf20Sopenharmony_ci	if (!pm_suspend_no_platform())
2998c2ecf20Sopenharmony_ci		intel_hid_set_enable(device, false);
3008c2ecf20Sopenharmony_ci
3018c2ecf20Sopenharmony_ci	return 0;
3028c2ecf20Sopenharmony_ci}
3038c2ecf20Sopenharmony_ci
3048c2ecf20Sopenharmony_cistatic int intel_hid_pl_resume_handler(struct device *device)
3058c2ecf20Sopenharmony_ci{
3068c2ecf20Sopenharmony_ci	intel_hid_pm_complete(device);
3078c2ecf20Sopenharmony_ci
3088c2ecf20Sopenharmony_ci	if (!pm_suspend_no_platform())
3098c2ecf20Sopenharmony_ci		intel_hid_set_enable(device, true);
3108c2ecf20Sopenharmony_ci
3118c2ecf20Sopenharmony_ci	intel_button_array_enable(device, true);
3128c2ecf20Sopenharmony_ci	return 0;
3138c2ecf20Sopenharmony_ci}
3148c2ecf20Sopenharmony_ci
3158c2ecf20Sopenharmony_cistatic const struct dev_pm_ops intel_hid_pl_pm_ops = {
3168c2ecf20Sopenharmony_ci	.prepare = intel_hid_pm_prepare,
3178c2ecf20Sopenharmony_ci	.complete = intel_hid_pm_complete,
3188c2ecf20Sopenharmony_ci	.freeze  = intel_hid_pl_suspend_handler,
3198c2ecf20Sopenharmony_ci	.thaw  = intel_hid_pl_resume_handler,
3208c2ecf20Sopenharmony_ci	.restore  = intel_hid_pl_resume_handler,
3218c2ecf20Sopenharmony_ci	.suspend  = intel_hid_pl_suspend_handler,
3228c2ecf20Sopenharmony_ci	.resume  = intel_hid_pl_resume_handler,
3238c2ecf20Sopenharmony_ci};
3248c2ecf20Sopenharmony_ci
3258c2ecf20Sopenharmony_cistatic int intel_hid_input_setup(struct platform_device *device)
3268c2ecf20Sopenharmony_ci{
3278c2ecf20Sopenharmony_ci	struct intel_hid_priv *priv = dev_get_drvdata(&device->dev);
3288c2ecf20Sopenharmony_ci	int ret;
3298c2ecf20Sopenharmony_ci
3308c2ecf20Sopenharmony_ci	priv->input_dev = devm_input_allocate_device(&device->dev);
3318c2ecf20Sopenharmony_ci	if (!priv->input_dev)
3328c2ecf20Sopenharmony_ci		return -ENOMEM;
3338c2ecf20Sopenharmony_ci
3348c2ecf20Sopenharmony_ci	ret = sparse_keymap_setup(priv->input_dev, intel_hid_keymap, NULL);
3358c2ecf20Sopenharmony_ci	if (ret)
3368c2ecf20Sopenharmony_ci		return ret;
3378c2ecf20Sopenharmony_ci
3388c2ecf20Sopenharmony_ci	priv->input_dev->name = "Intel HID events";
3398c2ecf20Sopenharmony_ci	priv->input_dev->id.bustype = BUS_HOST;
3408c2ecf20Sopenharmony_ci
3418c2ecf20Sopenharmony_ci	return input_register_device(priv->input_dev);
3428c2ecf20Sopenharmony_ci}
3438c2ecf20Sopenharmony_ci
3448c2ecf20Sopenharmony_cistatic int intel_button_array_input_setup(struct platform_device *device)
3458c2ecf20Sopenharmony_ci{
3468c2ecf20Sopenharmony_ci	struct intel_hid_priv *priv = dev_get_drvdata(&device->dev);
3478c2ecf20Sopenharmony_ci	int ret;
3488c2ecf20Sopenharmony_ci
3498c2ecf20Sopenharmony_ci	/* Setup input device for 5 button array */
3508c2ecf20Sopenharmony_ci	priv->array = devm_input_allocate_device(&device->dev);
3518c2ecf20Sopenharmony_ci	if (!priv->array)
3528c2ecf20Sopenharmony_ci		return -ENOMEM;
3538c2ecf20Sopenharmony_ci
3548c2ecf20Sopenharmony_ci	ret = sparse_keymap_setup(priv->array, intel_array_keymap, NULL);
3558c2ecf20Sopenharmony_ci	if (ret)
3568c2ecf20Sopenharmony_ci		return ret;
3578c2ecf20Sopenharmony_ci
3588c2ecf20Sopenharmony_ci	priv->array->name = "Intel HID 5 button array";
3598c2ecf20Sopenharmony_ci	priv->array->id.bustype = BUS_HOST;
3608c2ecf20Sopenharmony_ci
3618c2ecf20Sopenharmony_ci	return input_register_device(priv->array);
3628c2ecf20Sopenharmony_ci}
3638c2ecf20Sopenharmony_ci
3648c2ecf20Sopenharmony_cistatic void notify_handler(acpi_handle handle, u32 event, void *context)
3658c2ecf20Sopenharmony_ci{
3668c2ecf20Sopenharmony_ci	struct platform_device *device = context;
3678c2ecf20Sopenharmony_ci	struct intel_hid_priv *priv = dev_get_drvdata(&device->dev);
3688c2ecf20Sopenharmony_ci	unsigned long long ev_index;
3698c2ecf20Sopenharmony_ci
3708c2ecf20Sopenharmony_ci	if (priv->wakeup_mode) {
3718c2ecf20Sopenharmony_ci		/*
3728c2ecf20Sopenharmony_ci		 * Needed for wakeup from suspend-to-idle to work on some
3738c2ecf20Sopenharmony_ci		 * platforms that don't expose the 5-button array, but still
3748c2ecf20Sopenharmony_ci		 * send notifies with the power button event code to this
3758c2ecf20Sopenharmony_ci		 * device object on power button actions while suspended.
3768c2ecf20Sopenharmony_ci		 */
3778c2ecf20Sopenharmony_ci		if (event == 0xce)
3788c2ecf20Sopenharmony_ci			goto wakeup;
3798c2ecf20Sopenharmony_ci
3808c2ecf20Sopenharmony_ci		/* Wake up on 5-button array events only. */
3818c2ecf20Sopenharmony_ci		if (event == 0xc0 || !priv->array)
3828c2ecf20Sopenharmony_ci			return;
3838c2ecf20Sopenharmony_ci
3848c2ecf20Sopenharmony_ci		if (!sparse_keymap_entry_from_scancode(priv->array, event)) {
3858c2ecf20Sopenharmony_ci			dev_info(&device->dev, "unknown event 0x%x\n", event);
3868c2ecf20Sopenharmony_ci			return;
3878c2ecf20Sopenharmony_ci		}
3888c2ecf20Sopenharmony_ci
3898c2ecf20Sopenharmony_ciwakeup:
3908c2ecf20Sopenharmony_ci		pm_wakeup_hard_event(&device->dev);
3918c2ecf20Sopenharmony_ci		return;
3928c2ecf20Sopenharmony_ci	}
3938c2ecf20Sopenharmony_ci
3948c2ecf20Sopenharmony_ci	/*
3958c2ecf20Sopenharmony_ci	 * Needed for suspend to work on some platforms that don't expose
3968c2ecf20Sopenharmony_ci	 * the 5-button array, but still send notifies with power button
3978c2ecf20Sopenharmony_ci	 * event code to this device object on power button actions.
3988c2ecf20Sopenharmony_ci	 *
3998c2ecf20Sopenharmony_ci	 * Report the power button press and release.
4008c2ecf20Sopenharmony_ci	 */
4018c2ecf20Sopenharmony_ci	if (!priv->array) {
4028c2ecf20Sopenharmony_ci		if (event == 0xce) {
4038c2ecf20Sopenharmony_ci			input_report_key(priv->input_dev, KEY_POWER, 1);
4048c2ecf20Sopenharmony_ci			input_sync(priv->input_dev);
4058c2ecf20Sopenharmony_ci			return;
4068c2ecf20Sopenharmony_ci		}
4078c2ecf20Sopenharmony_ci
4088c2ecf20Sopenharmony_ci		if (event == 0xcf) {
4098c2ecf20Sopenharmony_ci			input_report_key(priv->input_dev, KEY_POWER, 0);
4108c2ecf20Sopenharmony_ci			input_sync(priv->input_dev);
4118c2ecf20Sopenharmony_ci			return;
4128c2ecf20Sopenharmony_ci		}
4138c2ecf20Sopenharmony_ci	}
4148c2ecf20Sopenharmony_ci
4158c2ecf20Sopenharmony_ci	/* 0xC0 is for HID events, other values are for 5 button array */
4168c2ecf20Sopenharmony_ci	if (event != 0xc0) {
4178c2ecf20Sopenharmony_ci		if (!priv->array ||
4188c2ecf20Sopenharmony_ci		    !sparse_keymap_report_event(priv->array, event, 1, true))
4198c2ecf20Sopenharmony_ci			dev_dbg(&device->dev, "unknown event 0x%x\n", event);
4208c2ecf20Sopenharmony_ci		return;
4218c2ecf20Sopenharmony_ci	}
4228c2ecf20Sopenharmony_ci
4238c2ecf20Sopenharmony_ci	if (!intel_hid_evaluate_method(handle, INTEL_HID_DSM_HDEM_FN,
4248c2ecf20Sopenharmony_ci				       &ev_index)) {
4258c2ecf20Sopenharmony_ci		dev_warn(&device->dev, "failed to get event index\n");
4268c2ecf20Sopenharmony_ci		return;
4278c2ecf20Sopenharmony_ci	}
4288c2ecf20Sopenharmony_ci
4298c2ecf20Sopenharmony_ci	if (!sparse_keymap_report_event(priv->input_dev, ev_index, 1, true))
4308c2ecf20Sopenharmony_ci		dev_dbg(&device->dev, "unknown event index 0x%llx\n",
4318c2ecf20Sopenharmony_ci			 ev_index);
4328c2ecf20Sopenharmony_ci}
4338c2ecf20Sopenharmony_ci
4348c2ecf20Sopenharmony_cistatic bool button_array_present(struct platform_device *device)
4358c2ecf20Sopenharmony_ci{
4368c2ecf20Sopenharmony_ci	acpi_handle handle = ACPI_HANDLE(&device->dev);
4378c2ecf20Sopenharmony_ci	unsigned long long event_cap;
4388c2ecf20Sopenharmony_ci
4398c2ecf20Sopenharmony_ci	if (intel_hid_evaluate_method(handle, INTEL_HID_DSM_HEBC_V2_FN,
4408c2ecf20Sopenharmony_ci				      &event_cap)) {
4418c2ecf20Sopenharmony_ci		/* Check presence of 5 button array or v2 power button */
4428c2ecf20Sopenharmony_ci		if (event_cap & 0x60000)
4438c2ecf20Sopenharmony_ci			return true;
4448c2ecf20Sopenharmony_ci	}
4458c2ecf20Sopenharmony_ci
4468c2ecf20Sopenharmony_ci	if (intel_hid_evaluate_method(handle, INTEL_HID_DSM_HEBC_V1_FN,
4478c2ecf20Sopenharmony_ci				      &event_cap)) {
4488c2ecf20Sopenharmony_ci		if (event_cap & 0x20000)
4498c2ecf20Sopenharmony_ci			return true;
4508c2ecf20Sopenharmony_ci	}
4518c2ecf20Sopenharmony_ci
4528c2ecf20Sopenharmony_ci	if (dmi_check_system(button_array_table))
4538c2ecf20Sopenharmony_ci		return true;
4548c2ecf20Sopenharmony_ci
4558c2ecf20Sopenharmony_ci	return false;
4568c2ecf20Sopenharmony_ci}
4578c2ecf20Sopenharmony_ci
4588c2ecf20Sopenharmony_cistatic int intel_hid_probe(struct platform_device *device)
4598c2ecf20Sopenharmony_ci{
4608c2ecf20Sopenharmony_ci	acpi_handle handle = ACPI_HANDLE(&device->dev);
4618c2ecf20Sopenharmony_ci	unsigned long long mode, dummy;
4628c2ecf20Sopenharmony_ci	struct intel_hid_priv *priv;
4638c2ecf20Sopenharmony_ci	acpi_status status;
4648c2ecf20Sopenharmony_ci	int err;
4658c2ecf20Sopenharmony_ci
4668c2ecf20Sopenharmony_ci	intel_hid_init_dsm(handle);
4678c2ecf20Sopenharmony_ci
4688c2ecf20Sopenharmony_ci	if (!intel_hid_evaluate_method(handle, INTEL_HID_DSM_HDMM_FN, &mode)) {
4698c2ecf20Sopenharmony_ci		dev_warn(&device->dev, "failed to read mode\n");
4708c2ecf20Sopenharmony_ci		return -ENODEV;
4718c2ecf20Sopenharmony_ci	}
4728c2ecf20Sopenharmony_ci
4738c2ecf20Sopenharmony_ci	if (mode != 0) {
4748c2ecf20Sopenharmony_ci		/*
4758c2ecf20Sopenharmony_ci		 * This driver only implements "simple" mode.  There appear
4768c2ecf20Sopenharmony_ci		 * to be no other modes, but we should be paranoid and check
4778c2ecf20Sopenharmony_ci		 * for compatibility.
4788c2ecf20Sopenharmony_ci		 */
4798c2ecf20Sopenharmony_ci		dev_info(&device->dev, "platform is not in simple mode\n");
4808c2ecf20Sopenharmony_ci		return -ENODEV;
4818c2ecf20Sopenharmony_ci	}
4828c2ecf20Sopenharmony_ci
4838c2ecf20Sopenharmony_ci	priv = devm_kzalloc(&device->dev, sizeof(*priv), GFP_KERNEL);
4848c2ecf20Sopenharmony_ci	if (!priv)
4858c2ecf20Sopenharmony_ci		return -ENOMEM;
4868c2ecf20Sopenharmony_ci	dev_set_drvdata(&device->dev, priv);
4878c2ecf20Sopenharmony_ci
4888c2ecf20Sopenharmony_ci	err = intel_hid_input_setup(device);
4898c2ecf20Sopenharmony_ci	if (err) {
4908c2ecf20Sopenharmony_ci		pr_err("Failed to setup Intel HID hotkeys\n");
4918c2ecf20Sopenharmony_ci		return err;
4928c2ecf20Sopenharmony_ci	}
4938c2ecf20Sopenharmony_ci
4948c2ecf20Sopenharmony_ci	/* Setup 5 button array */
4958c2ecf20Sopenharmony_ci	if (button_array_present(device)) {
4968c2ecf20Sopenharmony_ci		dev_info(&device->dev, "platform supports 5 button array\n");
4978c2ecf20Sopenharmony_ci		err = intel_button_array_input_setup(device);
4988c2ecf20Sopenharmony_ci		if (err)
4998c2ecf20Sopenharmony_ci			pr_err("Failed to setup Intel 5 button array hotkeys\n");
5008c2ecf20Sopenharmony_ci	}
5018c2ecf20Sopenharmony_ci
5028c2ecf20Sopenharmony_ci	status = acpi_install_notify_handler(handle,
5038c2ecf20Sopenharmony_ci					     ACPI_DEVICE_NOTIFY,
5048c2ecf20Sopenharmony_ci					     notify_handler,
5058c2ecf20Sopenharmony_ci					     device);
5068c2ecf20Sopenharmony_ci	if (ACPI_FAILURE(status))
5078c2ecf20Sopenharmony_ci		return -EBUSY;
5088c2ecf20Sopenharmony_ci
5098c2ecf20Sopenharmony_ci	err = intel_hid_set_enable(&device->dev, true);
5108c2ecf20Sopenharmony_ci	if (err)
5118c2ecf20Sopenharmony_ci		goto err_remove_notify;
5128c2ecf20Sopenharmony_ci
5138c2ecf20Sopenharmony_ci	intel_button_array_enable(&device->dev, true);
5148c2ecf20Sopenharmony_ci
5158c2ecf20Sopenharmony_ci	/*
5168c2ecf20Sopenharmony_ci	 * Call button load method to enable HID power button
5178c2ecf20Sopenharmony_ci	 * Always do this since it activates events on some devices without
5188c2ecf20Sopenharmony_ci	 * a button array too.
5198c2ecf20Sopenharmony_ci	 */
5208c2ecf20Sopenharmony_ci	if (!intel_hid_evaluate_method(handle, INTEL_HID_DSM_BTNL_FN, &dummy))
5218c2ecf20Sopenharmony_ci		dev_warn(&device->dev, "failed to enable HID power button\n");
5228c2ecf20Sopenharmony_ci
5238c2ecf20Sopenharmony_ci	device_init_wakeup(&device->dev, true);
5248c2ecf20Sopenharmony_ci	/*
5258c2ecf20Sopenharmony_ci	 * In order for system wakeup to work, the EC GPE has to be marked as
5268c2ecf20Sopenharmony_ci	 * a wakeup one, so do that here (this setting will persist, but it has
5278c2ecf20Sopenharmony_ci	 * no effect until the wakeup mask is set for the EC GPE).
5288c2ecf20Sopenharmony_ci	 */
5298c2ecf20Sopenharmony_ci	acpi_ec_mark_gpe_for_wake();
5308c2ecf20Sopenharmony_ci	return 0;
5318c2ecf20Sopenharmony_ci
5328c2ecf20Sopenharmony_cierr_remove_notify:
5338c2ecf20Sopenharmony_ci	acpi_remove_notify_handler(handle, ACPI_DEVICE_NOTIFY, notify_handler);
5348c2ecf20Sopenharmony_ci
5358c2ecf20Sopenharmony_ci	return err;
5368c2ecf20Sopenharmony_ci}
5378c2ecf20Sopenharmony_ci
5388c2ecf20Sopenharmony_cistatic int intel_hid_remove(struct platform_device *device)
5398c2ecf20Sopenharmony_ci{
5408c2ecf20Sopenharmony_ci	acpi_handle handle = ACPI_HANDLE(&device->dev);
5418c2ecf20Sopenharmony_ci
5428c2ecf20Sopenharmony_ci	device_init_wakeup(&device->dev, false);
5438c2ecf20Sopenharmony_ci	acpi_remove_notify_handler(handle, ACPI_DEVICE_NOTIFY, notify_handler);
5448c2ecf20Sopenharmony_ci	intel_hid_set_enable(&device->dev, false);
5458c2ecf20Sopenharmony_ci	intel_button_array_enable(&device->dev, false);
5468c2ecf20Sopenharmony_ci
5478c2ecf20Sopenharmony_ci	/*
5488c2ecf20Sopenharmony_ci	 * Even if we failed to shut off the event stream, we can still
5498c2ecf20Sopenharmony_ci	 * safely detach from the device.
5508c2ecf20Sopenharmony_ci	 */
5518c2ecf20Sopenharmony_ci	return 0;
5528c2ecf20Sopenharmony_ci}
5538c2ecf20Sopenharmony_ci
5548c2ecf20Sopenharmony_cistatic struct platform_driver intel_hid_pl_driver = {
5558c2ecf20Sopenharmony_ci	.driver = {
5568c2ecf20Sopenharmony_ci		.name = "intel-hid",
5578c2ecf20Sopenharmony_ci		.acpi_match_table = intel_hid_ids,
5588c2ecf20Sopenharmony_ci		.pm = &intel_hid_pl_pm_ops,
5598c2ecf20Sopenharmony_ci	},
5608c2ecf20Sopenharmony_ci	.probe = intel_hid_probe,
5618c2ecf20Sopenharmony_ci	.remove = intel_hid_remove,
5628c2ecf20Sopenharmony_ci};
5638c2ecf20Sopenharmony_ci
5648c2ecf20Sopenharmony_ci/*
5658c2ecf20Sopenharmony_ci * Unfortunately, some laptops provide a _HID="INT33D5" device with
5668c2ecf20Sopenharmony_ci * _CID="PNP0C02".  This causes the pnpacpi scan driver to claim the
5678c2ecf20Sopenharmony_ci * ACPI node, so no platform device will be created.  The pnpacpi
5688c2ecf20Sopenharmony_ci * driver rejects this device in subsequent processing, so no physical
5698c2ecf20Sopenharmony_ci * node is created at all.
5708c2ecf20Sopenharmony_ci *
5718c2ecf20Sopenharmony_ci * As a workaround until the ACPI core figures out how to handle
5728c2ecf20Sopenharmony_ci * this corner case, manually ask the ACPI platform device code to
5738c2ecf20Sopenharmony_ci * claim the ACPI node.
5748c2ecf20Sopenharmony_ci */
5758c2ecf20Sopenharmony_cistatic acpi_status __init
5768c2ecf20Sopenharmony_cicheck_acpi_dev(acpi_handle handle, u32 lvl, void *context, void **rv)
5778c2ecf20Sopenharmony_ci{
5788c2ecf20Sopenharmony_ci	const struct acpi_device_id *ids = context;
5798c2ecf20Sopenharmony_ci	struct acpi_device *dev;
5808c2ecf20Sopenharmony_ci
5818c2ecf20Sopenharmony_ci	if (acpi_bus_get_device(handle, &dev) != 0)
5828c2ecf20Sopenharmony_ci		return AE_OK;
5838c2ecf20Sopenharmony_ci
5848c2ecf20Sopenharmony_ci	if (acpi_match_device_ids(dev, ids) == 0)
5858c2ecf20Sopenharmony_ci		if (!IS_ERR_OR_NULL(acpi_create_platform_device(dev, NULL)))
5868c2ecf20Sopenharmony_ci			dev_info(&dev->dev,
5878c2ecf20Sopenharmony_ci				 "intel-hid: created platform device\n");
5888c2ecf20Sopenharmony_ci
5898c2ecf20Sopenharmony_ci	return AE_OK;
5908c2ecf20Sopenharmony_ci}
5918c2ecf20Sopenharmony_ci
5928c2ecf20Sopenharmony_cistatic int __init intel_hid_init(void)
5938c2ecf20Sopenharmony_ci{
5948c2ecf20Sopenharmony_ci	acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT,
5958c2ecf20Sopenharmony_ci			    ACPI_UINT32_MAX, check_acpi_dev, NULL,
5968c2ecf20Sopenharmony_ci			    (void *)intel_hid_ids, NULL);
5978c2ecf20Sopenharmony_ci
5988c2ecf20Sopenharmony_ci	return platform_driver_register(&intel_hid_pl_driver);
5998c2ecf20Sopenharmony_ci}
6008c2ecf20Sopenharmony_cimodule_init(intel_hid_init);
6018c2ecf20Sopenharmony_ci
6028c2ecf20Sopenharmony_cistatic void __exit intel_hid_exit(void)
6038c2ecf20Sopenharmony_ci{
6048c2ecf20Sopenharmony_ci	platform_driver_unregister(&intel_hid_pl_driver);
6058c2ecf20Sopenharmony_ci}
6068c2ecf20Sopenharmony_cimodule_exit(intel_hid_exit);
607