162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci *  Intel HID event & 5 button array driver
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci *  Copyright (C) 2015 Alex Hung <alex.hung@canonical.com>
662306a36Sopenharmony_ci *  Copyright (C) 2015 Andrew Lutomirski <luto@kernel.org>
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#include <linux/acpi.h>
1062306a36Sopenharmony_ci#include <linux/dmi.h>
1162306a36Sopenharmony_ci#include <linux/input.h>
1262306a36Sopenharmony_ci#include <linux/input/sparse-keymap.h>
1362306a36Sopenharmony_ci#include <linux/kernel.h>
1462306a36Sopenharmony_ci#include <linux/module.h>
1562306a36Sopenharmony_ci#include <linux/platform_device.h>
1662306a36Sopenharmony_ci#include <linux/suspend.h>
1762306a36Sopenharmony_ci#include "../dual_accel_detect.h"
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_cienum intel_hid_tablet_sw_mode {
2062306a36Sopenharmony_ci	TABLET_SW_AUTO = -1,
2162306a36Sopenharmony_ci	TABLET_SW_OFF  = 0,
2262306a36Sopenharmony_ci	TABLET_SW_AT_EVENT,
2362306a36Sopenharmony_ci	TABLET_SW_AT_PROBE,
2462306a36Sopenharmony_ci};
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_cistatic bool enable_5_button_array;
2762306a36Sopenharmony_cimodule_param(enable_5_button_array, bool, 0444);
2862306a36Sopenharmony_ciMODULE_PARM_DESC(enable_5_button_array,
2962306a36Sopenharmony_ci	"Enable 5 Button Array support. "
3062306a36Sopenharmony_ci	"If you need this please report this to: platform-driver-x86@vger.kernel.org");
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_cistatic int enable_sw_tablet_mode = TABLET_SW_AUTO;
3362306a36Sopenharmony_cimodule_param(enable_sw_tablet_mode, int, 0444);
3462306a36Sopenharmony_ciMODULE_PARM_DESC(enable_sw_tablet_mode,
3562306a36Sopenharmony_ci	"Enable SW_TABLET_MODE reporting -1:auto 0:off 1:at-first-event 2:at-probe. "
3662306a36Sopenharmony_ci	"If you need this please report this to: platform-driver-x86@vger.kernel.org");
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci/* When NOT in tablet mode, VGBS returns with the flag 0x40 */
3962306a36Sopenharmony_ci#define TABLET_MODE_FLAG BIT(6)
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ciMODULE_LICENSE("GPL");
4262306a36Sopenharmony_ciMODULE_AUTHOR("Alex Hung");
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_cistatic const struct acpi_device_id intel_hid_ids[] = {
4562306a36Sopenharmony_ci	{"INT33D5", 0},
4662306a36Sopenharmony_ci	{"INTC1051", 0},
4762306a36Sopenharmony_ci	{"INTC1054", 0},
4862306a36Sopenharmony_ci	{"INTC1070", 0},
4962306a36Sopenharmony_ci	{"INTC1076", 0},
5062306a36Sopenharmony_ci	{"INTC1077", 0},
5162306a36Sopenharmony_ci	{"INTC1078", 0},
5262306a36Sopenharmony_ci	{"", 0},
5362306a36Sopenharmony_ci};
5462306a36Sopenharmony_ciMODULE_DEVICE_TABLE(acpi, intel_hid_ids);
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_ci/* In theory, these are HID usages. */
5762306a36Sopenharmony_cistatic const struct key_entry intel_hid_keymap[] = {
5862306a36Sopenharmony_ci	/* 1: LSuper (Page 0x07, usage 0xE3) -- unclear what to do */
5962306a36Sopenharmony_ci	/* 2: Toggle SW_ROTATE_LOCK -- easy to implement if seen in wild */
6062306a36Sopenharmony_ci	{ KE_KEY, 3, { KEY_NUMLOCK } },
6162306a36Sopenharmony_ci	{ KE_KEY, 4, { KEY_HOME } },
6262306a36Sopenharmony_ci	{ KE_KEY, 5, { KEY_END } },
6362306a36Sopenharmony_ci	{ KE_KEY, 6, { KEY_PAGEUP } },
6462306a36Sopenharmony_ci	{ KE_KEY, 7, { KEY_PAGEDOWN } },
6562306a36Sopenharmony_ci	{ KE_KEY, 8, { KEY_RFKILL } },
6662306a36Sopenharmony_ci	{ KE_KEY, 9, { KEY_POWER } },
6762306a36Sopenharmony_ci	{ KE_KEY, 11, { KEY_SLEEP } },
6862306a36Sopenharmony_ci	/* 13 has two different meanings in the spec -- ignore it. */
6962306a36Sopenharmony_ci	{ KE_KEY, 14, { KEY_STOPCD } },
7062306a36Sopenharmony_ci	{ KE_KEY, 15, { KEY_PLAYPAUSE } },
7162306a36Sopenharmony_ci	{ KE_KEY, 16, { KEY_MUTE } },
7262306a36Sopenharmony_ci	{ KE_KEY, 17, { KEY_VOLUMEUP } },
7362306a36Sopenharmony_ci	{ KE_KEY, 18, { KEY_VOLUMEDOWN } },
7462306a36Sopenharmony_ci	{ KE_KEY, 19, { KEY_BRIGHTNESSUP } },
7562306a36Sopenharmony_ci	{ KE_KEY, 20, { KEY_BRIGHTNESSDOWN } },
7662306a36Sopenharmony_ci	/* 27: wake -- needs special handling */
7762306a36Sopenharmony_ci	{ KE_END },
7862306a36Sopenharmony_ci};
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci/* 5 button array notification value. */
8162306a36Sopenharmony_cistatic const struct key_entry intel_array_keymap[] = {
8262306a36Sopenharmony_ci	{ KE_KEY,    0xC2, { KEY_LEFTMETA } },                /* Press */
8362306a36Sopenharmony_ci	{ KE_IGNORE, 0xC3, { KEY_LEFTMETA } },                /* Release */
8462306a36Sopenharmony_ci	{ KE_KEY,    0xC4, { KEY_VOLUMEUP } },                /* Press */
8562306a36Sopenharmony_ci	{ KE_IGNORE, 0xC5, { KEY_VOLUMEUP } },                /* Release */
8662306a36Sopenharmony_ci	{ KE_KEY,    0xC6, { KEY_VOLUMEDOWN } },              /* Press */
8762306a36Sopenharmony_ci	{ KE_IGNORE, 0xC7, { KEY_VOLUMEDOWN } },              /* Release */
8862306a36Sopenharmony_ci	{ KE_KEY,    0xC8, { KEY_ROTATE_LOCK_TOGGLE } },      /* Press */
8962306a36Sopenharmony_ci	{ KE_IGNORE, 0xC9, { KEY_ROTATE_LOCK_TOGGLE } },      /* Release */
9062306a36Sopenharmony_ci	{ KE_KEY,    0xCE, { KEY_POWER } },                   /* Press */
9162306a36Sopenharmony_ci	{ KE_IGNORE, 0xCF, { KEY_POWER } },                   /* Release */
9262306a36Sopenharmony_ci	{ KE_END },
9362306a36Sopenharmony_ci};
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_cistatic const struct dmi_system_id button_array_table[] = {
9662306a36Sopenharmony_ci	{
9762306a36Sopenharmony_ci		.ident = "Wacom MobileStudio Pro 13",
9862306a36Sopenharmony_ci		.matches = {
9962306a36Sopenharmony_ci			DMI_MATCH(DMI_SYS_VENDOR, "Wacom Co.,Ltd"),
10062306a36Sopenharmony_ci			DMI_MATCH(DMI_PRODUCT_NAME, "Wacom MobileStudio Pro 13"),
10162306a36Sopenharmony_ci		},
10262306a36Sopenharmony_ci	},
10362306a36Sopenharmony_ci	{
10462306a36Sopenharmony_ci		.ident = "Wacom MobileStudio Pro 16",
10562306a36Sopenharmony_ci		.matches = {
10662306a36Sopenharmony_ci			DMI_MATCH(DMI_SYS_VENDOR, "Wacom Co.,Ltd"),
10762306a36Sopenharmony_ci			DMI_MATCH(DMI_PRODUCT_NAME, "Wacom MobileStudio Pro 16"),
10862306a36Sopenharmony_ci		},
10962306a36Sopenharmony_ci	},
11062306a36Sopenharmony_ci	{
11162306a36Sopenharmony_ci		.ident = "HP Spectre x2 (2015)",
11262306a36Sopenharmony_ci		.matches = {
11362306a36Sopenharmony_ci			DMI_MATCH(DMI_SYS_VENDOR, "HP"),
11462306a36Sopenharmony_ci			DMI_MATCH(DMI_PRODUCT_NAME, "HP Spectre x2 Detachable"),
11562306a36Sopenharmony_ci		},
11662306a36Sopenharmony_ci	},
11762306a36Sopenharmony_ci	{
11862306a36Sopenharmony_ci		.ident = "Lenovo ThinkPad X1 Tablet Gen 2",
11962306a36Sopenharmony_ci		.matches = {
12062306a36Sopenharmony_ci			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
12162306a36Sopenharmony_ci			DMI_MATCH(DMI_PRODUCT_FAMILY, "ThinkPad X1 Tablet Gen 2"),
12262306a36Sopenharmony_ci		},
12362306a36Sopenharmony_ci	},
12462306a36Sopenharmony_ci	{
12562306a36Sopenharmony_ci		.ident = "Microsoft Surface Go 3",
12662306a36Sopenharmony_ci		.matches = {
12762306a36Sopenharmony_ci			DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
12862306a36Sopenharmony_ci			DMI_MATCH(DMI_PRODUCT_NAME, "Surface Go 3"),
12962306a36Sopenharmony_ci		},
13062306a36Sopenharmony_ci	},
13162306a36Sopenharmony_ci	{ }
13262306a36Sopenharmony_ci};
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci/*
13562306a36Sopenharmony_ci * Some convertible use the intel-hid ACPI interface to report SW_TABLET_MODE,
13662306a36Sopenharmony_ci * these need to be compared via a DMI based authorization list because some
13762306a36Sopenharmony_ci * models have unreliable VGBS return which could cause incorrect
13862306a36Sopenharmony_ci * SW_TABLET_MODE report.
13962306a36Sopenharmony_ci */
14062306a36Sopenharmony_cistatic const struct dmi_system_id dmi_vgbs_allow_list[] = {
14162306a36Sopenharmony_ci	{
14262306a36Sopenharmony_ci		.matches = {
14362306a36Sopenharmony_ci			DMI_MATCH(DMI_SYS_VENDOR, "HP"),
14462306a36Sopenharmony_ci			DMI_MATCH(DMI_PRODUCT_NAME, "HP Spectre x360 Convertible 15-df0xxx"),
14562306a36Sopenharmony_ci		},
14662306a36Sopenharmony_ci	},
14762306a36Sopenharmony_ci	{
14862306a36Sopenharmony_ci		.matches = {
14962306a36Sopenharmony_ci			DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
15062306a36Sopenharmony_ci			DMI_MATCH(DMI_PRODUCT_NAME, "Surface Go"),
15162306a36Sopenharmony_ci		},
15262306a36Sopenharmony_ci	},
15362306a36Sopenharmony_ci	{
15462306a36Sopenharmony_ci		.matches = {
15562306a36Sopenharmony_ci			DMI_MATCH(DMI_SYS_VENDOR, "HP"),
15662306a36Sopenharmony_ci			DMI_MATCH(DMI_PRODUCT_NAME, "HP Elite Dragonfly G2 Notebook PC"),
15762306a36Sopenharmony_ci		},
15862306a36Sopenharmony_ci	},
15962306a36Sopenharmony_ci	{ }
16062306a36Sopenharmony_ci};
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci/*
16362306a36Sopenharmony_ci * Some devices, even non convertible ones, can send incorrect SW_TABLET_MODE
16462306a36Sopenharmony_ci * reports. Accept such reports only from devices in this list.
16562306a36Sopenharmony_ci */
16662306a36Sopenharmony_cistatic const struct dmi_system_id dmi_auto_add_switch[] = {
16762306a36Sopenharmony_ci	{
16862306a36Sopenharmony_ci		.matches = {
16962306a36Sopenharmony_ci			DMI_EXACT_MATCH(DMI_CHASSIS_TYPE, "31" /* Convertible */),
17062306a36Sopenharmony_ci		},
17162306a36Sopenharmony_ci	},
17262306a36Sopenharmony_ci	{
17362306a36Sopenharmony_ci		.matches = {
17462306a36Sopenharmony_ci			DMI_EXACT_MATCH(DMI_CHASSIS_TYPE, "32" /* Detachable */),
17562306a36Sopenharmony_ci		},
17662306a36Sopenharmony_ci	},
17762306a36Sopenharmony_ci	{} /* Array terminator */
17862306a36Sopenharmony_ci};
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_cistruct intel_hid_priv {
18162306a36Sopenharmony_ci	struct input_dev *input_dev;
18262306a36Sopenharmony_ci	struct input_dev *array;
18362306a36Sopenharmony_ci	struct input_dev *switches;
18462306a36Sopenharmony_ci	bool wakeup_mode;
18562306a36Sopenharmony_ci};
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci#define HID_EVENT_FILTER_UUID	"eeec56b3-4442-408f-a792-4edd4d758054"
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_cienum intel_hid_dsm_fn_codes {
19062306a36Sopenharmony_ci	INTEL_HID_DSM_FN_INVALID,
19162306a36Sopenharmony_ci	INTEL_HID_DSM_BTNL_FN,
19262306a36Sopenharmony_ci	INTEL_HID_DSM_HDMM_FN,
19362306a36Sopenharmony_ci	INTEL_HID_DSM_HDSM_FN,
19462306a36Sopenharmony_ci	INTEL_HID_DSM_HDEM_FN,
19562306a36Sopenharmony_ci	INTEL_HID_DSM_BTNS_FN,
19662306a36Sopenharmony_ci	INTEL_HID_DSM_BTNE_FN,
19762306a36Sopenharmony_ci	INTEL_HID_DSM_HEBC_V1_FN,
19862306a36Sopenharmony_ci	INTEL_HID_DSM_VGBS_FN,
19962306a36Sopenharmony_ci	INTEL_HID_DSM_HEBC_V2_FN,
20062306a36Sopenharmony_ci	INTEL_HID_DSM_FN_MAX
20162306a36Sopenharmony_ci};
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_cistatic const char *intel_hid_dsm_fn_to_method[INTEL_HID_DSM_FN_MAX] = {
20462306a36Sopenharmony_ci	NULL,
20562306a36Sopenharmony_ci	"BTNL",
20662306a36Sopenharmony_ci	"HDMM",
20762306a36Sopenharmony_ci	"HDSM",
20862306a36Sopenharmony_ci	"HDEM",
20962306a36Sopenharmony_ci	"BTNS",
21062306a36Sopenharmony_ci	"BTNE",
21162306a36Sopenharmony_ci	"HEBC",
21262306a36Sopenharmony_ci	"VGBS",
21362306a36Sopenharmony_ci	"HEBC"
21462306a36Sopenharmony_ci};
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_cistatic unsigned long long intel_hid_dsm_fn_mask;
21762306a36Sopenharmony_cistatic guid_t intel_dsm_guid;
21862306a36Sopenharmony_ci
21962306a36Sopenharmony_cistatic bool intel_hid_execute_method(acpi_handle handle,
22062306a36Sopenharmony_ci				     enum intel_hid_dsm_fn_codes fn_index,
22162306a36Sopenharmony_ci				     unsigned long long arg)
22262306a36Sopenharmony_ci{
22362306a36Sopenharmony_ci	union acpi_object *obj, argv4, req;
22462306a36Sopenharmony_ci	acpi_status status;
22562306a36Sopenharmony_ci	char *method_name;
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_ci	if (fn_index <= INTEL_HID_DSM_FN_INVALID ||
22862306a36Sopenharmony_ci	    fn_index >= INTEL_HID_DSM_FN_MAX)
22962306a36Sopenharmony_ci		return false;
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_ci	method_name = (char *)intel_hid_dsm_fn_to_method[fn_index];
23262306a36Sopenharmony_ci
23362306a36Sopenharmony_ci	if (!(intel_hid_dsm_fn_mask & BIT(fn_index)))
23462306a36Sopenharmony_ci		goto skip_dsm_exec;
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_ci	/* All methods expects a package with one integer element */
23762306a36Sopenharmony_ci	req.type = ACPI_TYPE_INTEGER;
23862306a36Sopenharmony_ci	req.integer.value = arg;
23962306a36Sopenharmony_ci
24062306a36Sopenharmony_ci	argv4.type = ACPI_TYPE_PACKAGE;
24162306a36Sopenharmony_ci	argv4.package.count = 1;
24262306a36Sopenharmony_ci	argv4.package.elements = &req;
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_ci	obj = acpi_evaluate_dsm(handle, &intel_dsm_guid, 1, fn_index, &argv4);
24562306a36Sopenharmony_ci	if (obj) {
24662306a36Sopenharmony_ci		acpi_handle_debug(handle, "Exec DSM Fn code: %d[%s] success\n",
24762306a36Sopenharmony_ci				  fn_index, method_name);
24862306a36Sopenharmony_ci		ACPI_FREE(obj);
24962306a36Sopenharmony_ci		return true;
25062306a36Sopenharmony_ci	}
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_ciskip_dsm_exec:
25362306a36Sopenharmony_ci	status = acpi_execute_simple_method(handle, method_name, arg);
25462306a36Sopenharmony_ci	if (ACPI_SUCCESS(status))
25562306a36Sopenharmony_ci		return true;
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_ci	return false;
25862306a36Sopenharmony_ci}
25962306a36Sopenharmony_ci
26062306a36Sopenharmony_cistatic bool intel_hid_evaluate_method(acpi_handle handle,
26162306a36Sopenharmony_ci				      enum intel_hid_dsm_fn_codes fn_index,
26262306a36Sopenharmony_ci				      unsigned long long *result)
26362306a36Sopenharmony_ci{
26462306a36Sopenharmony_ci	union acpi_object *obj;
26562306a36Sopenharmony_ci	acpi_status status;
26662306a36Sopenharmony_ci	char *method_name;
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_ci	if (fn_index <= INTEL_HID_DSM_FN_INVALID ||
26962306a36Sopenharmony_ci	    fn_index >= INTEL_HID_DSM_FN_MAX)
27062306a36Sopenharmony_ci		return false;
27162306a36Sopenharmony_ci
27262306a36Sopenharmony_ci	method_name = (char *)intel_hid_dsm_fn_to_method[fn_index];
27362306a36Sopenharmony_ci
27462306a36Sopenharmony_ci	if (!(intel_hid_dsm_fn_mask & BIT(fn_index)))
27562306a36Sopenharmony_ci		goto skip_dsm_eval;
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_ci	obj = acpi_evaluate_dsm_typed(handle, &intel_dsm_guid,
27862306a36Sopenharmony_ci				      1, fn_index,
27962306a36Sopenharmony_ci				      NULL,  ACPI_TYPE_INTEGER);
28062306a36Sopenharmony_ci	if (obj) {
28162306a36Sopenharmony_ci		*result = obj->integer.value;
28262306a36Sopenharmony_ci		acpi_handle_debug(handle,
28362306a36Sopenharmony_ci				  "Eval DSM Fn code: %d[%s] results: 0x%llx\n",
28462306a36Sopenharmony_ci				  fn_index, method_name, *result);
28562306a36Sopenharmony_ci		ACPI_FREE(obj);
28662306a36Sopenharmony_ci		return true;
28762306a36Sopenharmony_ci	}
28862306a36Sopenharmony_ci
28962306a36Sopenharmony_ciskip_dsm_eval:
29062306a36Sopenharmony_ci	status = acpi_evaluate_integer(handle, method_name, NULL, result);
29162306a36Sopenharmony_ci	if (ACPI_SUCCESS(status))
29262306a36Sopenharmony_ci		return true;
29362306a36Sopenharmony_ci
29462306a36Sopenharmony_ci	return false;
29562306a36Sopenharmony_ci}
29662306a36Sopenharmony_ci
29762306a36Sopenharmony_cistatic void intel_hid_init_dsm(acpi_handle handle)
29862306a36Sopenharmony_ci{
29962306a36Sopenharmony_ci	union acpi_object *obj;
30062306a36Sopenharmony_ci
30162306a36Sopenharmony_ci	guid_parse(HID_EVENT_FILTER_UUID, &intel_dsm_guid);
30262306a36Sopenharmony_ci
30362306a36Sopenharmony_ci	obj = acpi_evaluate_dsm_typed(handle, &intel_dsm_guid, 1, 0, NULL,
30462306a36Sopenharmony_ci				      ACPI_TYPE_BUFFER);
30562306a36Sopenharmony_ci	if (obj) {
30662306a36Sopenharmony_ci		switch (obj->buffer.length) {
30762306a36Sopenharmony_ci		default:
30862306a36Sopenharmony_ci		case 2:
30962306a36Sopenharmony_ci			intel_hid_dsm_fn_mask = *(u16 *)obj->buffer.pointer;
31062306a36Sopenharmony_ci			break;
31162306a36Sopenharmony_ci		case 1:
31262306a36Sopenharmony_ci			intel_hid_dsm_fn_mask = *obj->buffer.pointer;
31362306a36Sopenharmony_ci			break;
31462306a36Sopenharmony_ci		case 0:
31562306a36Sopenharmony_ci			acpi_handle_warn(handle, "intel_hid_dsm_fn_mask length is zero\n");
31662306a36Sopenharmony_ci			intel_hid_dsm_fn_mask = 0;
31762306a36Sopenharmony_ci			break;
31862306a36Sopenharmony_ci		}
31962306a36Sopenharmony_ci		ACPI_FREE(obj);
32062306a36Sopenharmony_ci	}
32162306a36Sopenharmony_ci
32262306a36Sopenharmony_ci	acpi_handle_debug(handle, "intel_hid_dsm_fn_mask = %llx\n",
32362306a36Sopenharmony_ci			  intel_hid_dsm_fn_mask);
32462306a36Sopenharmony_ci}
32562306a36Sopenharmony_ci
32662306a36Sopenharmony_cistatic int intel_hid_set_enable(struct device *device, bool enable)
32762306a36Sopenharmony_ci{
32862306a36Sopenharmony_ci	acpi_handle handle = ACPI_HANDLE(device);
32962306a36Sopenharmony_ci
33062306a36Sopenharmony_ci	/* Enable|disable features - power button is always enabled */
33162306a36Sopenharmony_ci	if (!intel_hid_execute_method(handle, INTEL_HID_DSM_HDSM_FN,
33262306a36Sopenharmony_ci				      enable)) {
33362306a36Sopenharmony_ci		dev_warn(device, "failed to %sable hotkeys\n",
33462306a36Sopenharmony_ci			 enable ? "en" : "dis");
33562306a36Sopenharmony_ci		return -EIO;
33662306a36Sopenharmony_ci	}
33762306a36Sopenharmony_ci
33862306a36Sopenharmony_ci	return 0;
33962306a36Sopenharmony_ci}
34062306a36Sopenharmony_ci
34162306a36Sopenharmony_cistatic void intel_button_array_enable(struct device *device, bool enable)
34262306a36Sopenharmony_ci{
34362306a36Sopenharmony_ci	struct intel_hid_priv *priv = dev_get_drvdata(device);
34462306a36Sopenharmony_ci	acpi_handle handle = ACPI_HANDLE(device);
34562306a36Sopenharmony_ci	unsigned long long button_cap;
34662306a36Sopenharmony_ci	acpi_status status;
34762306a36Sopenharmony_ci
34862306a36Sopenharmony_ci	if (!priv->array)
34962306a36Sopenharmony_ci		return;
35062306a36Sopenharmony_ci
35162306a36Sopenharmony_ci	/* Query supported platform features */
35262306a36Sopenharmony_ci	status = acpi_evaluate_integer(handle, "BTNC", NULL, &button_cap);
35362306a36Sopenharmony_ci	if (ACPI_FAILURE(status)) {
35462306a36Sopenharmony_ci		dev_warn(device, "failed to get button capability\n");
35562306a36Sopenharmony_ci		return;
35662306a36Sopenharmony_ci	}
35762306a36Sopenharmony_ci
35862306a36Sopenharmony_ci	/* Enable|disable features - power button is always enabled */
35962306a36Sopenharmony_ci	if (!intel_hid_execute_method(handle, INTEL_HID_DSM_BTNE_FN,
36062306a36Sopenharmony_ci				      enable ? button_cap : 1))
36162306a36Sopenharmony_ci		dev_warn(device, "failed to set button capability\n");
36262306a36Sopenharmony_ci}
36362306a36Sopenharmony_ci
36462306a36Sopenharmony_cistatic int intel_hid_pm_prepare(struct device *device)
36562306a36Sopenharmony_ci{
36662306a36Sopenharmony_ci	if (device_may_wakeup(device)) {
36762306a36Sopenharmony_ci		struct intel_hid_priv *priv = dev_get_drvdata(device);
36862306a36Sopenharmony_ci
36962306a36Sopenharmony_ci		priv->wakeup_mode = true;
37062306a36Sopenharmony_ci	}
37162306a36Sopenharmony_ci	return 0;
37262306a36Sopenharmony_ci}
37362306a36Sopenharmony_ci
37462306a36Sopenharmony_cistatic void intel_hid_pm_complete(struct device *device)
37562306a36Sopenharmony_ci{
37662306a36Sopenharmony_ci	struct intel_hid_priv *priv = dev_get_drvdata(device);
37762306a36Sopenharmony_ci
37862306a36Sopenharmony_ci	priv->wakeup_mode = false;
37962306a36Sopenharmony_ci}
38062306a36Sopenharmony_ci
38162306a36Sopenharmony_cistatic int intel_hid_pl_suspend_handler(struct device *device)
38262306a36Sopenharmony_ci{
38362306a36Sopenharmony_ci	intel_button_array_enable(device, false);
38462306a36Sopenharmony_ci
38562306a36Sopenharmony_ci	if (!pm_suspend_no_platform())
38662306a36Sopenharmony_ci		intel_hid_set_enable(device, false);
38762306a36Sopenharmony_ci
38862306a36Sopenharmony_ci	return 0;
38962306a36Sopenharmony_ci}
39062306a36Sopenharmony_ci
39162306a36Sopenharmony_cistatic int intel_hid_pl_resume_handler(struct device *device)
39262306a36Sopenharmony_ci{
39362306a36Sopenharmony_ci	intel_hid_pm_complete(device);
39462306a36Sopenharmony_ci
39562306a36Sopenharmony_ci	if (!pm_suspend_no_platform())
39662306a36Sopenharmony_ci		intel_hid_set_enable(device, true);
39762306a36Sopenharmony_ci
39862306a36Sopenharmony_ci	intel_button_array_enable(device, true);
39962306a36Sopenharmony_ci	return 0;
40062306a36Sopenharmony_ci}
40162306a36Sopenharmony_ci
40262306a36Sopenharmony_cistatic const struct dev_pm_ops intel_hid_pl_pm_ops = {
40362306a36Sopenharmony_ci	.prepare = intel_hid_pm_prepare,
40462306a36Sopenharmony_ci	.complete = intel_hid_pm_complete,
40562306a36Sopenharmony_ci	.freeze  = intel_hid_pl_suspend_handler,
40662306a36Sopenharmony_ci	.thaw  = intel_hid_pl_resume_handler,
40762306a36Sopenharmony_ci	.restore  = intel_hid_pl_resume_handler,
40862306a36Sopenharmony_ci	.suspend  = intel_hid_pl_suspend_handler,
40962306a36Sopenharmony_ci	.resume  = intel_hid_pl_resume_handler,
41062306a36Sopenharmony_ci};
41162306a36Sopenharmony_ci
41262306a36Sopenharmony_cistatic int intel_hid_input_setup(struct platform_device *device)
41362306a36Sopenharmony_ci{
41462306a36Sopenharmony_ci	struct intel_hid_priv *priv = dev_get_drvdata(&device->dev);
41562306a36Sopenharmony_ci	int ret;
41662306a36Sopenharmony_ci
41762306a36Sopenharmony_ci	priv->input_dev = devm_input_allocate_device(&device->dev);
41862306a36Sopenharmony_ci	if (!priv->input_dev)
41962306a36Sopenharmony_ci		return -ENOMEM;
42062306a36Sopenharmony_ci
42162306a36Sopenharmony_ci	ret = sparse_keymap_setup(priv->input_dev, intel_hid_keymap, NULL);
42262306a36Sopenharmony_ci	if (ret)
42362306a36Sopenharmony_ci		return ret;
42462306a36Sopenharmony_ci
42562306a36Sopenharmony_ci	priv->input_dev->name = "Intel HID events";
42662306a36Sopenharmony_ci	priv->input_dev->id.bustype = BUS_HOST;
42762306a36Sopenharmony_ci
42862306a36Sopenharmony_ci	return input_register_device(priv->input_dev);
42962306a36Sopenharmony_ci}
43062306a36Sopenharmony_ci
43162306a36Sopenharmony_cistatic int intel_button_array_input_setup(struct platform_device *device)
43262306a36Sopenharmony_ci{
43362306a36Sopenharmony_ci	struct intel_hid_priv *priv = dev_get_drvdata(&device->dev);
43462306a36Sopenharmony_ci	int ret;
43562306a36Sopenharmony_ci
43662306a36Sopenharmony_ci	/* Setup input device for 5 button array */
43762306a36Sopenharmony_ci	priv->array = devm_input_allocate_device(&device->dev);
43862306a36Sopenharmony_ci	if (!priv->array)
43962306a36Sopenharmony_ci		return -ENOMEM;
44062306a36Sopenharmony_ci
44162306a36Sopenharmony_ci	ret = sparse_keymap_setup(priv->array, intel_array_keymap, NULL);
44262306a36Sopenharmony_ci	if (ret)
44362306a36Sopenharmony_ci		return ret;
44462306a36Sopenharmony_ci
44562306a36Sopenharmony_ci	priv->array->name = "Intel HID 5 button array";
44662306a36Sopenharmony_ci	priv->array->id.bustype = BUS_HOST;
44762306a36Sopenharmony_ci
44862306a36Sopenharmony_ci	return input_register_device(priv->array);
44962306a36Sopenharmony_ci}
45062306a36Sopenharmony_ci
45162306a36Sopenharmony_cistatic int intel_hid_switches_setup(struct platform_device *device)
45262306a36Sopenharmony_ci{
45362306a36Sopenharmony_ci	struct intel_hid_priv *priv = dev_get_drvdata(&device->dev);
45462306a36Sopenharmony_ci
45562306a36Sopenharmony_ci	/* Setup input device for switches */
45662306a36Sopenharmony_ci	priv->switches = devm_input_allocate_device(&device->dev);
45762306a36Sopenharmony_ci	if (!priv->switches)
45862306a36Sopenharmony_ci		return -ENOMEM;
45962306a36Sopenharmony_ci
46062306a36Sopenharmony_ci	__set_bit(EV_SW, priv->switches->evbit);
46162306a36Sopenharmony_ci	__set_bit(SW_TABLET_MODE, priv->switches->swbit);
46262306a36Sopenharmony_ci
46362306a36Sopenharmony_ci	priv->switches->name = "Intel HID switches";
46462306a36Sopenharmony_ci	priv->switches->id.bustype = BUS_HOST;
46562306a36Sopenharmony_ci	return input_register_device(priv->switches);
46662306a36Sopenharmony_ci}
46762306a36Sopenharmony_ci
46862306a36Sopenharmony_cistatic void report_tablet_mode_state(struct platform_device *device)
46962306a36Sopenharmony_ci{
47062306a36Sopenharmony_ci	struct intel_hid_priv *priv = dev_get_drvdata(&device->dev);
47162306a36Sopenharmony_ci	acpi_handle handle = ACPI_HANDLE(&device->dev);
47262306a36Sopenharmony_ci	unsigned long long vgbs;
47362306a36Sopenharmony_ci	int m;
47462306a36Sopenharmony_ci
47562306a36Sopenharmony_ci	if (!intel_hid_evaluate_method(handle, INTEL_HID_DSM_VGBS_FN, &vgbs))
47662306a36Sopenharmony_ci		return;
47762306a36Sopenharmony_ci
47862306a36Sopenharmony_ci	m = !(vgbs & TABLET_MODE_FLAG);
47962306a36Sopenharmony_ci	input_report_switch(priv->switches, SW_TABLET_MODE, m);
48062306a36Sopenharmony_ci	input_sync(priv->switches);
48162306a36Sopenharmony_ci}
48262306a36Sopenharmony_ci
48362306a36Sopenharmony_cistatic bool report_tablet_mode_event(struct input_dev *input_dev, u32 event)
48462306a36Sopenharmony_ci{
48562306a36Sopenharmony_ci	if (!input_dev)
48662306a36Sopenharmony_ci		return false;
48762306a36Sopenharmony_ci
48862306a36Sopenharmony_ci	switch (event) {
48962306a36Sopenharmony_ci	case 0xcc:
49062306a36Sopenharmony_ci		input_report_switch(input_dev, SW_TABLET_MODE, 1);
49162306a36Sopenharmony_ci		input_sync(input_dev);
49262306a36Sopenharmony_ci		return true;
49362306a36Sopenharmony_ci	case 0xcd:
49462306a36Sopenharmony_ci		input_report_switch(input_dev, SW_TABLET_MODE, 0);
49562306a36Sopenharmony_ci		input_sync(input_dev);
49662306a36Sopenharmony_ci		return true;
49762306a36Sopenharmony_ci	default:
49862306a36Sopenharmony_ci		return false;
49962306a36Sopenharmony_ci	}
50062306a36Sopenharmony_ci}
50162306a36Sopenharmony_ci
50262306a36Sopenharmony_cistatic void notify_handler(acpi_handle handle, u32 event, void *context)
50362306a36Sopenharmony_ci{
50462306a36Sopenharmony_ci	struct platform_device *device = context;
50562306a36Sopenharmony_ci	struct intel_hid_priv *priv = dev_get_drvdata(&device->dev);
50662306a36Sopenharmony_ci	unsigned long long ev_index;
50762306a36Sopenharmony_ci	int err;
50862306a36Sopenharmony_ci
50962306a36Sopenharmony_ci	/*
51062306a36Sopenharmony_ci	 * Some convertible have unreliable VGBS return which could cause incorrect
51162306a36Sopenharmony_ci	 * SW_TABLET_MODE report, in these cases we enable support when receiving
51262306a36Sopenharmony_ci	 * the first event instead of during driver setup.
51362306a36Sopenharmony_ci	 */
51462306a36Sopenharmony_ci	if (!priv->switches && enable_sw_tablet_mode == TABLET_SW_AT_EVENT &&
51562306a36Sopenharmony_ci	    (event == 0xcc || event == 0xcd)) {
51662306a36Sopenharmony_ci		dev_info(&device->dev, "switch event received, enable switches supports\n");
51762306a36Sopenharmony_ci		err = intel_hid_switches_setup(device);
51862306a36Sopenharmony_ci		if (err)
51962306a36Sopenharmony_ci			pr_err("Failed to setup Intel HID switches\n");
52062306a36Sopenharmony_ci	}
52162306a36Sopenharmony_ci
52262306a36Sopenharmony_ci	if (priv->wakeup_mode) {
52362306a36Sopenharmony_ci		/*
52462306a36Sopenharmony_ci		 * Needed for wakeup from suspend-to-idle to work on some
52562306a36Sopenharmony_ci		 * platforms that don't expose the 5-button array, but still
52662306a36Sopenharmony_ci		 * send notifies with the power button event code to this
52762306a36Sopenharmony_ci		 * device object on power button actions while suspended.
52862306a36Sopenharmony_ci		 */
52962306a36Sopenharmony_ci		if (event == 0xce)
53062306a36Sopenharmony_ci			goto wakeup;
53162306a36Sopenharmony_ci
53262306a36Sopenharmony_ci		/*
53362306a36Sopenharmony_ci		 * Some devices send (duplicate) tablet-mode events when moved
53462306a36Sopenharmony_ci		 * around even though the mode has not changed; and they do this
53562306a36Sopenharmony_ci		 * even when suspended.
53662306a36Sopenharmony_ci		 * Update the switch state in case it changed and then return
53762306a36Sopenharmony_ci		 * without waking up to avoid spurious wakeups.
53862306a36Sopenharmony_ci		 */
53962306a36Sopenharmony_ci		if (event == 0xcc || event == 0xcd) {
54062306a36Sopenharmony_ci			report_tablet_mode_event(priv->switches, event);
54162306a36Sopenharmony_ci			return;
54262306a36Sopenharmony_ci		}
54362306a36Sopenharmony_ci
54462306a36Sopenharmony_ci		/* Wake up on 5-button array events only. */
54562306a36Sopenharmony_ci		if (event == 0xc0 || !priv->array)
54662306a36Sopenharmony_ci			return;
54762306a36Sopenharmony_ci
54862306a36Sopenharmony_ci		if (!sparse_keymap_entry_from_scancode(priv->array, event)) {
54962306a36Sopenharmony_ci			dev_info(&device->dev, "unknown event 0x%x\n", event);
55062306a36Sopenharmony_ci			return;
55162306a36Sopenharmony_ci		}
55262306a36Sopenharmony_ci
55362306a36Sopenharmony_ciwakeup:
55462306a36Sopenharmony_ci		pm_wakeup_hard_event(&device->dev);
55562306a36Sopenharmony_ci
55662306a36Sopenharmony_ci		return;
55762306a36Sopenharmony_ci	}
55862306a36Sopenharmony_ci
55962306a36Sopenharmony_ci	/*
56062306a36Sopenharmony_ci	 * Needed for suspend to work on some platforms that don't expose
56162306a36Sopenharmony_ci	 * the 5-button array, but still send notifies with power button
56262306a36Sopenharmony_ci	 * event code to this device object on power button actions.
56362306a36Sopenharmony_ci	 *
56462306a36Sopenharmony_ci	 * Report the power button press and release.
56562306a36Sopenharmony_ci	 */
56662306a36Sopenharmony_ci	if (!priv->array) {
56762306a36Sopenharmony_ci		if (event == 0xce) {
56862306a36Sopenharmony_ci			input_report_key(priv->input_dev, KEY_POWER, 1);
56962306a36Sopenharmony_ci			input_sync(priv->input_dev);
57062306a36Sopenharmony_ci			return;
57162306a36Sopenharmony_ci		}
57262306a36Sopenharmony_ci
57362306a36Sopenharmony_ci		if (event == 0xcf) {
57462306a36Sopenharmony_ci			input_report_key(priv->input_dev, KEY_POWER, 0);
57562306a36Sopenharmony_ci			input_sync(priv->input_dev);
57662306a36Sopenharmony_ci			return;
57762306a36Sopenharmony_ci		}
57862306a36Sopenharmony_ci	}
57962306a36Sopenharmony_ci
58062306a36Sopenharmony_ci	if (report_tablet_mode_event(priv->switches, event))
58162306a36Sopenharmony_ci		return;
58262306a36Sopenharmony_ci
58362306a36Sopenharmony_ci	/* 0xC0 is for HID events, other values are for 5 button array */
58462306a36Sopenharmony_ci	if (event != 0xc0) {
58562306a36Sopenharmony_ci		if (!priv->array ||
58662306a36Sopenharmony_ci		    !sparse_keymap_report_event(priv->array, event, 1, true))
58762306a36Sopenharmony_ci			dev_dbg(&device->dev, "unknown event 0x%x\n", event);
58862306a36Sopenharmony_ci		return;
58962306a36Sopenharmony_ci	}
59062306a36Sopenharmony_ci
59162306a36Sopenharmony_ci	if (!intel_hid_evaluate_method(handle, INTEL_HID_DSM_HDEM_FN,
59262306a36Sopenharmony_ci				       &ev_index)) {
59362306a36Sopenharmony_ci		dev_warn(&device->dev, "failed to get event index\n");
59462306a36Sopenharmony_ci		return;
59562306a36Sopenharmony_ci	}
59662306a36Sopenharmony_ci
59762306a36Sopenharmony_ci	if (!sparse_keymap_report_event(priv->input_dev, ev_index, 1, true))
59862306a36Sopenharmony_ci		dev_dbg(&device->dev, "unknown event index 0x%llx\n",
59962306a36Sopenharmony_ci			 ev_index);
60062306a36Sopenharmony_ci}
60162306a36Sopenharmony_ci
60262306a36Sopenharmony_cistatic bool button_array_present(struct platform_device *device)
60362306a36Sopenharmony_ci{
60462306a36Sopenharmony_ci	acpi_handle handle = ACPI_HANDLE(&device->dev);
60562306a36Sopenharmony_ci	unsigned long long event_cap;
60662306a36Sopenharmony_ci
60762306a36Sopenharmony_ci	if (intel_hid_evaluate_method(handle, INTEL_HID_DSM_HEBC_V2_FN,
60862306a36Sopenharmony_ci				      &event_cap)) {
60962306a36Sopenharmony_ci		/* Check presence of 5 button array or v2 power button */
61062306a36Sopenharmony_ci		if (event_cap & 0x60000)
61162306a36Sopenharmony_ci			return true;
61262306a36Sopenharmony_ci	}
61362306a36Sopenharmony_ci
61462306a36Sopenharmony_ci	if (intel_hid_evaluate_method(handle, INTEL_HID_DSM_HEBC_V1_FN,
61562306a36Sopenharmony_ci				      &event_cap)) {
61662306a36Sopenharmony_ci		if (event_cap & 0x20000)
61762306a36Sopenharmony_ci			return true;
61862306a36Sopenharmony_ci	}
61962306a36Sopenharmony_ci
62062306a36Sopenharmony_ci	if (enable_5_button_array || dmi_check_system(button_array_table))
62162306a36Sopenharmony_ci		return true;
62262306a36Sopenharmony_ci
62362306a36Sopenharmony_ci	return false;
62462306a36Sopenharmony_ci}
62562306a36Sopenharmony_ci
62662306a36Sopenharmony_cistatic int intel_hid_probe(struct platform_device *device)
62762306a36Sopenharmony_ci{
62862306a36Sopenharmony_ci	acpi_handle handle = ACPI_HANDLE(&device->dev);
62962306a36Sopenharmony_ci	unsigned long long mode, dummy;
63062306a36Sopenharmony_ci	struct intel_hid_priv *priv;
63162306a36Sopenharmony_ci	acpi_status status;
63262306a36Sopenharmony_ci	int err;
63362306a36Sopenharmony_ci
63462306a36Sopenharmony_ci	intel_hid_init_dsm(handle);
63562306a36Sopenharmony_ci
63662306a36Sopenharmony_ci	if (!intel_hid_evaluate_method(handle, INTEL_HID_DSM_HDMM_FN, &mode)) {
63762306a36Sopenharmony_ci		dev_warn(&device->dev, "failed to read mode\n");
63862306a36Sopenharmony_ci		return -ENODEV;
63962306a36Sopenharmony_ci	}
64062306a36Sopenharmony_ci
64162306a36Sopenharmony_ci	if (mode != 0) {
64262306a36Sopenharmony_ci		/*
64362306a36Sopenharmony_ci		 * This driver only implements "simple" mode.  There appear
64462306a36Sopenharmony_ci		 * to be no other modes, but we should be paranoid and check
64562306a36Sopenharmony_ci		 * for compatibility.
64662306a36Sopenharmony_ci		 */
64762306a36Sopenharmony_ci		dev_info(&device->dev, "platform is not in simple mode\n");
64862306a36Sopenharmony_ci		return -ENODEV;
64962306a36Sopenharmony_ci	}
65062306a36Sopenharmony_ci
65162306a36Sopenharmony_ci	priv = devm_kzalloc(&device->dev, sizeof(*priv), GFP_KERNEL);
65262306a36Sopenharmony_ci	if (!priv)
65362306a36Sopenharmony_ci		return -ENOMEM;
65462306a36Sopenharmony_ci	dev_set_drvdata(&device->dev, priv);
65562306a36Sopenharmony_ci
65662306a36Sopenharmony_ci	/* See dual_accel_detect.h for more info on the dual_accel check. */
65762306a36Sopenharmony_ci	if (enable_sw_tablet_mode == TABLET_SW_AUTO) {
65862306a36Sopenharmony_ci		if (dmi_check_system(dmi_vgbs_allow_list))
65962306a36Sopenharmony_ci			enable_sw_tablet_mode = TABLET_SW_AT_PROBE;
66062306a36Sopenharmony_ci		else if (dmi_check_system(dmi_auto_add_switch) && !dual_accel_detect())
66162306a36Sopenharmony_ci			enable_sw_tablet_mode = TABLET_SW_AT_EVENT;
66262306a36Sopenharmony_ci		else
66362306a36Sopenharmony_ci			enable_sw_tablet_mode = TABLET_SW_OFF;
66462306a36Sopenharmony_ci	}
66562306a36Sopenharmony_ci
66662306a36Sopenharmony_ci	err = intel_hid_input_setup(device);
66762306a36Sopenharmony_ci	if (err) {
66862306a36Sopenharmony_ci		pr_err("Failed to setup Intel HID hotkeys\n");
66962306a36Sopenharmony_ci		return err;
67062306a36Sopenharmony_ci	}
67162306a36Sopenharmony_ci
67262306a36Sopenharmony_ci	/* Setup 5 button array */
67362306a36Sopenharmony_ci	if (button_array_present(device)) {
67462306a36Sopenharmony_ci		dev_info(&device->dev, "platform supports 5 button array\n");
67562306a36Sopenharmony_ci		err = intel_button_array_input_setup(device);
67662306a36Sopenharmony_ci		if (err)
67762306a36Sopenharmony_ci			pr_err("Failed to setup Intel 5 button array hotkeys\n");
67862306a36Sopenharmony_ci	}
67962306a36Sopenharmony_ci
68062306a36Sopenharmony_ci	/* Setup switches for devices that we know VGBS return correctly */
68162306a36Sopenharmony_ci	if (enable_sw_tablet_mode == TABLET_SW_AT_PROBE) {
68262306a36Sopenharmony_ci		dev_info(&device->dev, "platform supports switches\n");
68362306a36Sopenharmony_ci		err = intel_hid_switches_setup(device);
68462306a36Sopenharmony_ci		if (err)
68562306a36Sopenharmony_ci			pr_err("Failed to setup Intel HID switches\n");
68662306a36Sopenharmony_ci		else
68762306a36Sopenharmony_ci			report_tablet_mode_state(device);
68862306a36Sopenharmony_ci	}
68962306a36Sopenharmony_ci
69062306a36Sopenharmony_ci	status = acpi_install_notify_handler(handle,
69162306a36Sopenharmony_ci					     ACPI_DEVICE_NOTIFY,
69262306a36Sopenharmony_ci					     notify_handler,
69362306a36Sopenharmony_ci					     device);
69462306a36Sopenharmony_ci	if (ACPI_FAILURE(status))
69562306a36Sopenharmony_ci		return -EBUSY;
69662306a36Sopenharmony_ci
69762306a36Sopenharmony_ci	err = intel_hid_set_enable(&device->dev, true);
69862306a36Sopenharmony_ci	if (err)
69962306a36Sopenharmony_ci		goto err_remove_notify;
70062306a36Sopenharmony_ci
70162306a36Sopenharmony_ci	intel_button_array_enable(&device->dev, true);
70262306a36Sopenharmony_ci
70362306a36Sopenharmony_ci	/*
70462306a36Sopenharmony_ci	 * Call button load method to enable HID power button
70562306a36Sopenharmony_ci	 * Always do this since it activates events on some devices without
70662306a36Sopenharmony_ci	 * a button array too.
70762306a36Sopenharmony_ci	 */
70862306a36Sopenharmony_ci	if (!intel_hid_evaluate_method(handle, INTEL_HID_DSM_BTNL_FN, &dummy))
70962306a36Sopenharmony_ci		dev_warn(&device->dev, "failed to enable HID power button\n");
71062306a36Sopenharmony_ci
71162306a36Sopenharmony_ci	device_init_wakeup(&device->dev, true);
71262306a36Sopenharmony_ci	/*
71362306a36Sopenharmony_ci	 * In order for system wakeup to work, the EC GPE has to be marked as
71462306a36Sopenharmony_ci	 * a wakeup one, so do that here (this setting will persist, but it has
71562306a36Sopenharmony_ci	 * no effect until the wakeup mask is set for the EC GPE).
71662306a36Sopenharmony_ci	 */
71762306a36Sopenharmony_ci	acpi_ec_mark_gpe_for_wake();
71862306a36Sopenharmony_ci	return 0;
71962306a36Sopenharmony_ci
72062306a36Sopenharmony_cierr_remove_notify:
72162306a36Sopenharmony_ci	acpi_remove_notify_handler(handle, ACPI_DEVICE_NOTIFY, notify_handler);
72262306a36Sopenharmony_ci
72362306a36Sopenharmony_ci	return err;
72462306a36Sopenharmony_ci}
72562306a36Sopenharmony_ci
72662306a36Sopenharmony_cistatic void intel_hid_remove(struct platform_device *device)
72762306a36Sopenharmony_ci{
72862306a36Sopenharmony_ci	acpi_handle handle = ACPI_HANDLE(&device->dev);
72962306a36Sopenharmony_ci
73062306a36Sopenharmony_ci	device_init_wakeup(&device->dev, false);
73162306a36Sopenharmony_ci	acpi_remove_notify_handler(handle, ACPI_DEVICE_NOTIFY, notify_handler);
73262306a36Sopenharmony_ci	intel_hid_set_enable(&device->dev, false);
73362306a36Sopenharmony_ci	intel_button_array_enable(&device->dev, false);
73462306a36Sopenharmony_ci}
73562306a36Sopenharmony_ci
73662306a36Sopenharmony_cistatic struct platform_driver intel_hid_pl_driver = {
73762306a36Sopenharmony_ci	.driver = {
73862306a36Sopenharmony_ci		.name = "intel-hid",
73962306a36Sopenharmony_ci		.acpi_match_table = intel_hid_ids,
74062306a36Sopenharmony_ci		.pm = &intel_hid_pl_pm_ops,
74162306a36Sopenharmony_ci	},
74262306a36Sopenharmony_ci	.probe = intel_hid_probe,
74362306a36Sopenharmony_ci	.remove_new = intel_hid_remove,
74462306a36Sopenharmony_ci};
74562306a36Sopenharmony_ci
74662306a36Sopenharmony_ci/*
74762306a36Sopenharmony_ci * Unfortunately, some laptops provide a _HID="INT33D5" device with
74862306a36Sopenharmony_ci * _CID="PNP0C02".  This causes the pnpacpi scan driver to claim the
74962306a36Sopenharmony_ci * ACPI node, so no platform device will be created.  The pnpacpi
75062306a36Sopenharmony_ci * driver rejects this device in subsequent processing, so no physical
75162306a36Sopenharmony_ci * node is created at all.
75262306a36Sopenharmony_ci *
75362306a36Sopenharmony_ci * As a workaround until the ACPI core figures out how to handle
75462306a36Sopenharmony_ci * this corner case, manually ask the ACPI platform device code to
75562306a36Sopenharmony_ci * claim the ACPI node.
75662306a36Sopenharmony_ci */
75762306a36Sopenharmony_cistatic acpi_status __init
75862306a36Sopenharmony_cicheck_acpi_dev(acpi_handle handle, u32 lvl, void *context, void **rv)
75962306a36Sopenharmony_ci{
76062306a36Sopenharmony_ci	const struct acpi_device_id *ids = context;
76162306a36Sopenharmony_ci	struct acpi_device *dev = acpi_fetch_acpi_dev(handle);
76262306a36Sopenharmony_ci
76362306a36Sopenharmony_ci	if (dev && acpi_match_device_ids(dev, ids) == 0)
76462306a36Sopenharmony_ci		if (!IS_ERR_OR_NULL(acpi_create_platform_device(dev, NULL)))
76562306a36Sopenharmony_ci			dev_info(&dev->dev,
76662306a36Sopenharmony_ci				 "intel-hid: created platform device\n");
76762306a36Sopenharmony_ci
76862306a36Sopenharmony_ci	return AE_OK;
76962306a36Sopenharmony_ci}
77062306a36Sopenharmony_ci
77162306a36Sopenharmony_cistatic int __init intel_hid_init(void)
77262306a36Sopenharmony_ci{
77362306a36Sopenharmony_ci	acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT,
77462306a36Sopenharmony_ci			    ACPI_UINT32_MAX, check_acpi_dev, NULL,
77562306a36Sopenharmony_ci			    (void *)intel_hid_ids, NULL);
77662306a36Sopenharmony_ci
77762306a36Sopenharmony_ci	return platform_driver_register(&intel_hid_pl_driver);
77862306a36Sopenharmony_ci}
77962306a36Sopenharmony_cimodule_init(intel_hid_init);
78062306a36Sopenharmony_ci
78162306a36Sopenharmony_cistatic void __exit intel_hid_exit(void)
78262306a36Sopenharmony_ci{
78362306a36Sopenharmony_ci	platform_driver_unregister(&intel_hid_pl_driver);
78462306a36Sopenharmony_ci}
78562306a36Sopenharmony_cimodule_exit(intel_hid_exit);
786