diff options
Diffstat (limited to 'drivers/platform')
-rw-r--r-- | drivers/platform/x86/Kconfig | 21 | ||||
-rw-r--r-- | drivers/platform/x86/Makefile | 1 | ||||
-rw-r--r-- | drivers/platform/x86/dell-laptop.c | 27 | ||||
-rw-r--r-- | drivers/platform/x86/huawei-wmi.c | 208 | ||||
-rw-r--r-- | drivers/platform/x86/thinkpad_acpi.c | 67 |
5 files changed, 302 insertions, 22 deletions
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 54f6a40c75c6..45ef4d22f14c 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -177,6 +177,8 @@ config DELL_LAPTOP select POWER_SUPPLY select LEDS_CLASS select NEW_LEDS + select LEDS_TRIGGERS + select LEDS_TRIGGER_AUDIO ---help--- This driver adds support for rfkill and backlight control to Dell laptops (except for some models covered by the Compal driver). @@ -493,6 +495,8 @@ config THINKPAD_ACPI select NVRAM select NEW_LEDS select LEDS_CLASS + select LEDS_TRIGGERS + select LEDS_TRIGGER_AUDIO ---help--- This is a driver for the IBM and Lenovo ThinkPad laptops. It adds support for Fn-Fx key combinations, Bluetooth control, video @@ -1288,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/dell-laptop.c b/drivers/platform/x86/dell-laptop.c index 63121fd22c2e..95e6ca116e00 100644 --- a/drivers/platform/x86/dell-laptop.c +++ b/drivers/platform/x86/dell-laptop.c @@ -29,7 +29,6 @@ #include <linux/mm.h> #include <linux/i8042.h> #include <linux/debugfs.h> -#include <linux/dell-led.h> #include <linux/seq_file.h> #include <acpi/video.h> #include "dell-rbtn.h" @@ -2111,17 +2110,17 @@ static struct notifier_block dell_laptop_notifier = { .notifier_call = dell_laptop_notifier_call, }; -int dell_micmute_led_set(int state) +static int micmute_led_set(struct led_classdev *led_cdev, + enum led_brightness brightness) { struct calling_interface_buffer buffer; struct calling_interface_token *token; + int state = brightness != LED_OFF; if (state == 0) token = dell_smbios_find_token(GLOBAL_MIC_MUTE_DISABLE); - else if (state == 1) - token = dell_smbios_find_token(GLOBAL_MIC_MUTE_ENABLE); else - return -EINVAL; + token = dell_smbios_find_token(GLOBAL_MIC_MUTE_ENABLE); if (!token) return -ENODEV; @@ -2129,9 +2128,15 @@ int dell_micmute_led_set(int state) dell_fill_request(&buffer, token->location, token->value, 0, 0); dell_send_request(&buffer, CLASS_TOKEN_WRITE, SELECT_TOKEN_STD); - return state; + return 0; } -EXPORT_SYMBOL_GPL(dell_micmute_led_set); + +static struct led_classdev micmute_led_cdev = { + .name = "platform::micmute", + .max_brightness = 1, + .brightness_set_blocking = micmute_led_set, + .default_trigger = "audio-micmute", +}; static int __init dell_init(void) { @@ -2177,6 +2182,11 @@ static int __init dell_init(void) dell_laptop_register_notifier(&dell_laptop_notifier); + micmute_led_cdev.brightness = ledtrig_audio_get(LED_AUDIO_MICMUTE); + ret = led_classdev_register(&platform_device->dev, &micmute_led_cdev); + if (ret < 0) + goto fail_led; + if (acpi_video_get_backlight_type() != acpi_backlight_vendor) return 0; @@ -2222,6 +2232,8 @@ static int __init dell_init(void) fail_get_brightness: backlight_device_unregister(dell_backlight_device); fail_backlight: + led_classdev_unregister(&micmute_led_cdev); +fail_led: dell_cleanup_rfkill(); fail_rfkill: platform_device_del(platform_device); @@ -2241,6 +2253,7 @@ static void __exit dell_exit(void) touchpad_led_exit(); kbd_led_exit(); backlight_device_unregister(dell_backlight_device); + led_classdev_unregister(&micmute_led_cdev); dell_cleanup_rfkill(); if (platform_device) { platform_device_unregister(platform_device); diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c new file mode 100644 index 000000000000..59872f87b741 --- /dev/null +++ b/drivers/platform/x86/huawei-wmi.c @@ -0,0 +1,208 @@ +// 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_ALIAS("wmi:"WMI0_EVENT_GUID); +MODULE_ALIAS("wmi:"AMW0_EVENT_GUID); +MODULE_AUTHOR("Ayman Bagabas <ayman.bagabas@gmail.com>"); +MODULE_DESCRIPTION("Huawei WMI hotkeys"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index f5773cdfdebc..726341f2b638 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -81,7 +81,6 @@ #include <linux/acpi.h> #include <linux/pci_ids.h> #include <linux/power_supply.h> -#include <linux/thinkpad_acpi.h> #include <sound/core.h> #include <sound/control.h> #include <sound/initval.h> @@ -9131,6 +9130,7 @@ static struct ibm_struct fan_driver_data = { * Mute LED subdriver */ +#define TPACPI_LED_MAX 2 struct tp_led_table { acpi_string name; @@ -9139,13 +9139,13 @@ struct tp_led_table { int state; }; -static struct tp_led_table led_tables[] = { - [TPACPI_LED_MUTE] = { +static struct tp_led_table led_tables[TPACPI_LED_MAX] = { + [LED_AUDIO_MUTE] = { .name = "SSMS", .on_value = 1, .off_value = 0, }, - [TPACPI_LED_MICMUTE] = { + [LED_AUDIO_MICMUTE] = { .name = "MMTS", .on_value = 2, .off_value = 0, @@ -9170,31 +9170,64 @@ static int mute_led_on_off(struct tp_led_table *t, bool state) return state; } -int tpacpi_led_set(int whichled, bool on) +static int tpacpi_led_set(int whichled, bool on) { struct tp_led_table *t; - if (whichled < 0 || whichled >= TPACPI_LED_MAX) - return -EINVAL; - t = &led_tables[whichled]; if (t->state < 0 || t->state == on) return t->state; return mute_led_on_off(t, on); } -EXPORT_SYMBOL_GPL(tpacpi_led_set); + +static int tpacpi_led_mute_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + return tpacpi_led_set(LED_AUDIO_MUTE, brightness != LED_OFF); +} + +static int tpacpi_led_micmute_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + return tpacpi_led_set(LED_AUDIO_MICMUTE, brightness != LED_OFF); +} + +static struct led_classdev mute_led_cdev[TPACPI_LED_MAX] = { + [LED_AUDIO_MUTE] = { + .name = "platform::mute", + .max_brightness = 1, + .brightness_set_blocking = tpacpi_led_mute_set, + .default_trigger = "audio-mute", + }, + [LED_AUDIO_MICMUTE] = { + .name = "platform::micmute", + .max_brightness = 1, + .brightness_set_blocking = tpacpi_led_micmute_set, + .default_trigger = "audio-micmute", + }, +}; static int mute_led_init(struct ibm_init_struct *iibm) { acpi_handle temp; - int i; + int i, err; for (i = 0; i < TPACPI_LED_MAX; i++) { struct tp_led_table *t = &led_tables[i]; - if (ACPI_SUCCESS(acpi_get_handle(hkey_handle, t->name, &temp))) - mute_led_on_off(t, false); - else + if (ACPI_FAILURE(acpi_get_handle(hkey_handle, t->name, &temp))) { t->state = -ENODEV; + continue; + } + + mute_led_cdev[i].brightness = ledtrig_audio_get(i); + err = led_classdev_register(&tpacpi_pdev->dev, &mute_led_cdev[i]); + if (err < 0) { + while (i--) { + if (led_tables[i].state >= 0) + led_classdev_unregister(&mute_led_cdev[i]); + } + return err; + } } return 0; } @@ -9203,8 +9236,12 @@ static void mute_led_exit(void) { int i; - for (i = 0; i < TPACPI_LED_MAX; i++) - tpacpi_led_set(i, false); + for (i = 0; i < TPACPI_LED_MAX; i++) { + if (led_tables[i].state >= 0) { + led_classdev_unregister(&mute_led_cdev[i]); + tpacpi_led_set(i, false); + } + } } static void mute_led_resume(void) |