Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 | // SPDX-License-Identifier: GPL-2.0 /* * Huawei WMI hotkeys * * Copyright (C) 2018 Ayman Bagabas <ayman.bagabas@gmail.com> */ #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); 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; return input_register_device(priv->idev); } 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; dev_set_drvdata(&wdev->dev, priv); err = huawei_wmi_input_setup(wdev); if (err) return err; return huawei_wmi_leds_setup(wdev); } 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_DEVICE_TABLE(wmi, huawei_wmi_id_table); MODULE_AUTHOR("Ayman Bagabas <ayman.bagabas@gmail.com>"); MODULE_DESCRIPTION("Huawei WMI hotkeys"); MODULE_LICENSE("GPL v2"); |