Linux Audio

Check our new training course

Loading...
Note: File does not exist in v4.6.
  1// SPDX-License-Identifier: GPL-2.0
  2/*
  3 *  Huawei WMI hotkeys
  4 *
  5 *  Copyright (C) 2018	      Ayman Bagabas <ayman.bagabas@gmail.com>
  6 */
  7
  8#include <linux/acpi.h>
  9#include <linux/input.h>
 10#include <linux/input/sparse-keymap.h>
 11#include <linux/leds.h>
 12#include <linux/module.h>
 13#include <linux/wmi.h>
 14
 15/*
 16 * Huawei WMI GUIDs
 17 */
 18#define WMI0_EVENT_GUID "59142400-C6A3-40fa-BADB-8A2652834100"
 19#define AMW0_EVENT_GUID "ABBC0F5C-8EA1-11D1-A000-C90629100000"
 20
 21#define WMI0_EXPENSIVE_GUID "39142400-C6A3-40fa-BADB-8A2652834100"
 22
 23struct huawei_wmi_priv {
 24	struct input_dev *idev;
 25	struct led_classdev cdev;
 26	acpi_handle handle;
 27	char *acpi_method;
 28};
 29
 30static const struct key_entry huawei_wmi_keymap[] = {
 31	{ KE_KEY,    0x281, { KEY_BRIGHTNESSDOWN } },
 32	{ KE_KEY,    0x282, { KEY_BRIGHTNESSUP } },
 33	{ KE_KEY,    0x284, { KEY_MUTE } },
 34	{ KE_KEY,    0x285, { KEY_VOLUMEDOWN } },
 35	{ KE_KEY,    0x286, { KEY_VOLUMEUP } },
 36	{ KE_KEY,    0x287, { KEY_MICMUTE } },
 37	{ KE_KEY,    0x289, { KEY_WLAN } },
 38	// Huawei |M| key
 39	{ KE_KEY,    0x28a, { KEY_CONFIG } },
 40	// Keyboard backlight
 41	{ KE_IGNORE, 0x293, { KEY_KBDILLUMTOGGLE } },
 42	{ KE_IGNORE, 0x294, { KEY_KBDILLUMUP } },
 43	{ KE_IGNORE, 0x295, { KEY_KBDILLUMUP } },
 44	{ KE_END,	 0 }
 45};
 46
 47static int huawei_wmi_micmute_led_set(struct led_classdev *led_cdev,
 48		enum led_brightness brightness)
 49{
 50	struct huawei_wmi_priv *priv = dev_get_drvdata(led_cdev->dev->parent);
 51	acpi_status status;
 52	union acpi_object args[3];
 53	struct acpi_object_list arg_list = {
 54		.pointer = args,
 55		.count = ARRAY_SIZE(args),
 56	};
 57
 58	args[0].type = args[1].type = args[2].type = ACPI_TYPE_INTEGER;
 59	args[1].integer.value = 0x04;
 60
 61	if (strcmp(priv->acpi_method, "SPIN") == 0) {
 62		args[0].integer.value = 0;
 63		args[2].integer.value = brightness ? 1 : 0;
 64	} else if (strcmp(priv->acpi_method, "WPIN") == 0) {
 65		args[0].integer.value = 1;
 66		args[2].integer.value = brightness ? 0 : 1;
 67	} else {
 68		return -EINVAL;
 69	}
 70
 71	status = acpi_evaluate_object(priv->handle, priv->acpi_method, &arg_list, NULL);
 72	if (ACPI_FAILURE(status))
 73		return -ENXIO;
 74
 75	return 0;
 76}
 77
 78static int huawei_wmi_leds_setup(struct wmi_device *wdev)
 79{
 80	struct huawei_wmi_priv *priv = dev_get_drvdata(&wdev->dev);
 81
 82	priv->handle = ec_get_handle();
 83	if (!priv->handle)
 84		return 0;
 85
 86	if (acpi_has_method(priv->handle, "SPIN"))
 87		priv->acpi_method = "SPIN";
 88	else if (acpi_has_method(priv->handle, "WPIN"))
 89		priv->acpi_method = "WPIN";
 90	else
 91		return 0;
 92
 93	priv->cdev.name = "platform::micmute";
 94	priv->cdev.max_brightness = 1;
 95	priv->cdev.brightness_set_blocking = huawei_wmi_micmute_led_set;
 96	priv->cdev.default_trigger = "audio-micmute";
 97	priv->cdev.brightness = ledtrig_audio_get(LED_AUDIO_MICMUTE);
 98	priv->cdev.dev = &wdev->dev;
 99	priv->cdev.flags = LED_CORE_SUSPENDRESUME;
100
101	return devm_led_classdev_register(&wdev->dev, &priv->cdev);
102}
103
104static void huawei_wmi_process_key(struct wmi_device *wdev, int code)
105{
106	struct huawei_wmi_priv *priv = dev_get_drvdata(&wdev->dev);
107	const struct key_entry *key;
108
109	/*
110	 * WMI0 uses code 0x80 to indicate a hotkey event.
111	 * The actual key is fetched from the method WQ00
112	 * using WMI0_EXPENSIVE_GUID.
113	 */
114	if (code == 0x80) {
115		struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
116		union acpi_object *obj;
117		acpi_status status;
118
119		status = wmi_query_block(WMI0_EXPENSIVE_GUID, 0, &response);
120		if (ACPI_FAILURE(status))
121			return;
122
123		obj = (union acpi_object *)response.pointer;
124		if (obj && obj->type == ACPI_TYPE_INTEGER)
125			code = obj->integer.value;
126
127		kfree(response.pointer);
128	}
129
130	key = sparse_keymap_entry_from_scancode(priv->idev, code);
131	if (!key) {
132		dev_info(&wdev->dev, "Unknown key pressed, code: 0x%04x\n", code);
133		return;
134	}
135
136	sparse_keymap_report_entry(priv->idev, key, 1, true);
137}
138
139static void huawei_wmi_notify(struct wmi_device *wdev,
140		union acpi_object *obj)
141{
142	if (obj->type == ACPI_TYPE_INTEGER)
143		huawei_wmi_process_key(wdev, obj->integer.value);
144	else
145		dev_info(&wdev->dev, "Bad response type %d\n", obj->type);
146}
147
148static int huawei_wmi_input_setup(struct wmi_device *wdev)
149{
150	struct huawei_wmi_priv *priv = dev_get_drvdata(&wdev->dev);
151	int err;
152
153	priv->idev = devm_input_allocate_device(&wdev->dev);
154	if (!priv->idev)
155		return -ENOMEM;
156
157	priv->idev->name = "Huawei WMI hotkeys";
158	priv->idev->phys = "wmi/input0";
159	priv->idev->id.bustype = BUS_HOST;
160	priv->idev->dev.parent = &wdev->dev;
161
162	err = sparse_keymap_setup(priv->idev, huawei_wmi_keymap, NULL);
163	if (err)
164		return err;
165
166	return input_register_device(priv->idev);
167}
168
169static int huawei_wmi_probe(struct wmi_device *wdev, const void *context)
170{
171	struct huawei_wmi_priv *priv;
172	int err;
173
174	priv = devm_kzalloc(&wdev->dev, sizeof(struct huawei_wmi_priv), GFP_KERNEL);
175	if (!priv)
176		return -ENOMEM;
177
178	dev_set_drvdata(&wdev->dev, priv);
179
180	err = huawei_wmi_input_setup(wdev);
181	if (err)
182		return err;
183
184	return huawei_wmi_leds_setup(wdev);
185}
186
187static const struct wmi_device_id huawei_wmi_id_table[] = {
188	{ .guid_string = WMI0_EVENT_GUID },
189	{ .guid_string = AMW0_EVENT_GUID },
190	{  }
191};
192
193static struct wmi_driver huawei_wmi_driver = {
194	.driver = {
195		.name = "huawei-wmi",
196	},
197	.id_table = huawei_wmi_id_table,
198	.probe = huawei_wmi_probe,
199	.notify = huawei_wmi_notify,
200};
201
202module_wmi_driver(huawei_wmi_driver);
203
204MODULE_DEVICE_TABLE(wmi, huawei_wmi_id_table);
205MODULE_AUTHOR("Ayman Bagabas <ayman.bagabas@gmail.com>");
206MODULE_DESCRIPTION("Huawei WMI hotkeys");
207MODULE_LICENSE("GPL v2");