On Tue, Dec 11, 2018 at 8:02 AM Ayman Bagabas <ayman.bagabas@xxxxxxxxx> wrote: > > This driver adds support for missing hotkeys on some Huawei laptops. > Laptops such as the Matebook X have non functioning hotkeys. Whereas > newer laptops such as the Matebook X Pro come with working hotkeys out > of the box. > > Old laptops, such as the Matebook X, report hotkey events through ACPI > device "\WMI0". However, new laptops, such as the Matebook X Pro, > does not have this WMI device. > > All the hotkeys on the Matebook X Pro work fine > without this patch except (micmute, wlan, and huawei key). These keys > and the brightness keys report events to "\AMW0" ACPI device. One > problem is that brightness keys on the Matebook X Pro work without this > patch. This results in reporting two brightness key press > events one is captured by ACPI and another by this driver. > > A solution would be to check if such event came from the "\AMW0" WMI driver > then skip reporting event. Another solution would be to leave this to > user-space to handle. Which can be achieved by using "hwdb" tables and > remap those keys to "unknown". This solution seems more natural to me > because it leaves the decision to user-space. > > Reviewed-by: Takashi Iwai <tiwai@xxxxxxx> > Signed-off-by: Ayman Bagabas <ayman.bagabas@xxxxxxxxx> > --- > drivers/platform/x86/Kconfig | 17 +++ > drivers/platform/x86/Makefile | 1 + > drivers/platform/x86/huawei-wmi.c | 220 ++++++++++++++++++++++++++++++ > 3 files changed, 238 insertions(+) > create mode 100644 drivers/platform/x86/huawei-wmi.c > > diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig > index 87f70e8f4dd0..45ef4d22f14c 100644 > --- a/drivers/platform/x86/Kconfig > +++ b/drivers/platform/x86/Kconfig > @@ -1292,6 +1292,23 @@ config INTEL_ATOMISP2_PM > To compile this driver as a module, choose M here: the module > will be called intel_atomisp2_pm. > > +config HUAWEI_WMI > + tristate "Huawei WMI hotkeys driver" > + depends on ACPI_WMI > + depends on INPUT > + select INPUT_SPARSEKMAP > + select LEDS_CLASS > + select LEDS_TRIGGERS > + select LEDS_TRIGGER_AUDIO > + select NEW_LEDS > + help > + This driver provides support for Huawei WMI hotkeys. > + It enables the missing keys and adds support to the micmute > + LED found on some of these laptops. > + > + To compile this driver as a module, choose M here: the module > + will be called huawei-wmi. > + > endif # X86_PLATFORM_DEVICES > > config PMC_ATOM > diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile > index 39ae94135406..d841c550e3cc 100644 > --- a/drivers/platform/x86/Makefile > +++ b/drivers/platform/x86/Makefile > @@ -32,6 +32,7 @@ obj-$(CONFIG_ACERHDF) += acerhdf.o > obj-$(CONFIG_HP_ACCEL) += hp_accel.o > obj-$(CONFIG_HP_WIRELESS) += hp-wireless.o > obj-$(CONFIG_HP_WMI) += hp-wmi.o > +obj-$(CONFIG_HUAWEI_WMI) += huawei-wmi.o > obj-$(CONFIG_AMILO_RFKILL) += amilo-rfkill.o > obj-$(CONFIG_GPD_POCKET_FAN) += gpd-pocket-fan.o > obj-$(CONFIG_TC1100_WMI) += tc1100-wmi.o > diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c > new file mode 100644 > index 000000000000..89ba7ea33499 > --- /dev/null > +++ b/drivers/platform/x86/huawei-wmi.c > @@ -0,0 +1,220 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Huawei WMI hotkeys > + * > + * Copyright (C) 2018 Ayman Bagabas <ayman.bagabas@xxxxxxxxx> > + */ > + > +#include <linux/acpi.h> > +#include <linux/input.h> > +#include <linux/input/sparse-keymap.h> > +#include <linux/leds.h> > +#include <linux/module.h> > +#include <linux/wmi.h> > + > +/* > + * Huawei WMI GUIDs > + */ > +#define WMI0_EVENT_GUID "59142400-C6A3-40fa-BADB-8A2652834100" > +#define AMW0_EVENT_GUID "ABBC0F5C-8EA1-11D1-A000-C90629100000" > + > +#define WMI0_EXPENSIVE_GUID "39142400-C6A3-40fa-BADB-8A2652834100" > + > +struct huawei_wmi_priv { > + struct input_dev *idev; > + struct led_classdev cdev; > + acpi_handle handle; > + char *acpi_method; > +}; > + > +static const struct key_entry huawei_wmi_keymap[] = { > + { KE_KEY, 0x281, { KEY_BRIGHTNESSDOWN } }, > + { KE_KEY, 0x282, { KEY_BRIGHTNESSUP } }, > + { KE_KEY, 0x284, { KEY_MUTE } }, > + { KE_KEY, 0x285, { KEY_VOLUMEDOWN } }, > + { KE_KEY, 0x286, { KEY_VOLUMEUP } }, > + { KE_KEY, 0x287, { KEY_MICMUTE } }, > + { KE_KEY, 0x289, { KEY_WLAN } }, > + // Huawei |M| key > + { KE_KEY, 0x28a, { KEY_CONFIG } }, > + // Keyboard backlight > + { KE_IGNORE, 0x293, { KEY_KBDILLUMTOGGLE } }, > + { KE_IGNORE, 0x294, { KEY_KBDILLUMUP } }, > + { KE_IGNORE, 0x295, { KEY_KBDILLUMUP } }, > + { KE_END, 0 } > +}; > + > +static int huawei_wmi_micmute_led_set(struct led_classdev *led_cdev, > + enum led_brightness brightness) > +{ > + struct huawei_wmi_priv *priv = dev_get_drvdata(led_cdev->dev->parent); > + acpi_status status; > + union acpi_object args[3]; > + struct acpi_object_list arg_list = { > + .pointer = args, > + .count = ARRAY_SIZE(args), > + }; > + > + args[0].type = args[1].type = args[2].type = ACPI_TYPE_INTEGER; > + args[1].integer.value = 0x04; > + > + if (strcmp(priv->acpi_method, "SPIN") == 0) { > + args[0].integer.value = 0; > + args[2].integer.value = brightness ? 1 : 0; > + } else if (strcmp(priv->acpi_method, "WPIN") == 0) { > + args[0].integer.value = 1; > + args[2].integer.value = brightness ? 0 : 1; > + } else { > + return -EINVAL; > + } > + > + status = acpi_evaluate_object(priv->handle, priv->acpi_method, &arg_list, NULL); > + if (ACPI_FAILURE(status)) > + return -ENXIO; > + > + return 0; > +} > + > +static int huawei_wmi_leds_setup(struct wmi_device *wdev) > +{ > + struct huawei_wmi_priv *priv = dev_get_drvdata(&wdev->dev); > + acpi_status status; > + > + /* Skip registering LED subsystem if no ACPI method was found. > + * ec_get_handle() returns the first embedded controller device > + * handle which then used to locate SPIN and WPIN methods. > + */ Comment style is cripple here. All issues we may fix when applying. > + priv->handle = ec_get_handle(); > + if (!priv->handle) > + return 0; > + > + if (acpi_has_method(priv->handle, "SPIN")) > + priv->acpi_method = "SPIN"; > + else if (acpi_has_method(priv->handle, "WPIN")) > + priv->acpi_method = "WPIN"; > + else > + return 0; > + > + priv->cdev.name = "platform::micmute"; > + priv->cdev.max_brightness = 1; > + priv->cdev.brightness_set_blocking = huawei_wmi_micmute_led_set; > + priv->cdev.default_trigger = "audio-micmute"; > + priv->cdev.brightness = ledtrig_audio_get(LED_AUDIO_MICMUTE); > + priv->cdev.dev = &wdev->dev; > + priv->cdev.flags = LED_CORE_SUSPENDRESUME; > + > + return devm_led_classdev_register(&wdev->dev, &priv->cdev); > +} > + > +static void huawei_wmi_process_key(struct wmi_device *wdev, int code) > +{ > + struct huawei_wmi_priv *priv = dev_get_drvdata(&wdev->dev); > + const struct key_entry *key; > + > + /* > + * WMI0 uses code 0x80 to indicate a hotkey event. > + * The actual key is fetched from the method WQ00 > + * using WMI0_EXPENSIVE_GUID. > + */ > + if (code == 0x80) { > + struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; > + union acpi_object *obj; > + acpi_status status; > + > + status = wmi_query_block(WMI0_EXPENSIVE_GUID, 0, &response); > + if (ACPI_FAILURE(status)) > + return; > + > + obj = (union acpi_object *)response.pointer; > + if (obj && obj->type == ACPI_TYPE_INTEGER) > + code = obj->integer.value; > + > + kfree(response.pointer); > + } > + > + key = sparse_keymap_entry_from_scancode(priv->idev, code); > + if (!key) { > + dev_info(&wdev->dev, "Unknown key pressed, code: 0x%04x\n", code); > + return; > + } > + > + sparse_keymap_report_entry(priv->idev, key, 1, true); > +} > + > +static void huawei_wmi_notify(struct wmi_device *wdev, > + union acpi_object *obj) > +{ > + if (obj->type == ACPI_TYPE_INTEGER) > + huawei_wmi_process_key(wdev, obj->integer.value); > + else > + dev_info(&wdev->dev, "Bad response type %d\n", obj->type); > +} > + > +static int huawei_wmi_input_setup(struct wmi_device *wdev) > +{ > + struct huawei_wmi_priv *priv = dev_get_drvdata(&wdev->dev); > + int err; > + > + priv->idev = devm_input_allocate_device(&wdev->dev); > + if (!priv->idev) > + return -ENOMEM; > + > + priv->idev->name = "Huawei WMI hotkeys"; > + priv->idev->phys = "wmi/input0"; > + priv->idev->id.bustype = BUS_HOST; > + priv->idev->dev.parent = &wdev->dev; > + > + err = sparse_keymap_setup(priv->idev, huawei_wmi_keymap, NULL); > + if (err) > + return err; > + > + err = input_register_device(priv->idev); > + if (err) > + return err; > + > + return 0; return input_register_device(...); > +} > + > +static int huawei_wmi_probe(struct wmi_device *wdev) > +{ > + struct huawei_wmi_priv *priv; > + int err; > + > + priv = devm_kzalloc(&wdev->dev, sizeof(struct huawei_wmi_priv), GFP_KERNEL); > + if (!priv) > + return -ENOMEM; + blank line. > + dev_set_drvdata(&wdev->dev, priv); > + > + err = huawei_wmi_input_setup(wdev); > + if (err) > + return err; > + > + err = huawei_wmi_leds_setup(wdev); > + if (err) > + return err; > + > + return 0; return huawei_wmi_leds_setup(...); > +} > + > +static const struct wmi_device_id huawei_wmi_id_table[] = { > + { .guid_string = WMI0_EVENT_GUID }, > + { .guid_string = AMW0_EVENT_GUID }, > + { } > +}; > + > +static struct wmi_driver huawei_wmi_driver = { > + .driver = { > + .name = "huawei-wmi", > + }, > + .id_table = huawei_wmi_id_table, > + .probe = huawei_wmi_probe, > + .notify = huawei_wmi_notify, > +}; > + > +module_wmi_driver(huawei_wmi_driver); > + > +MODULE_ALIAS("wmi:"WMI0_EVENT_GUID); > +MODULE_ALIAS("wmi:"AMW0_EVENT_GUID); > +MODULE_AUTHOR("Ayman Bagabas <ayman.bagabas@xxxxxxxxx>"); > +MODULE_DESCRIPTION("Huawei WMI hotkeys"); > +MODULE_LICENSE("GPL v2"); > -- > 2.19.2 > -- With Best Regards, Andy Shevchenko