1*61250669SJackie Dong // SPDX-License-Identifier: GPL-2.0 2*61250669SJackie Dong /* 3*61250669SJackie Dong * Lenovo Super Hotkey Utility WMI extras driver for Ideapad laptop 4*61250669SJackie Dong * 5*61250669SJackie Dong * Copyright (C) 2025 Lenovo 6*61250669SJackie Dong */ 7*61250669SJackie Dong 8*61250669SJackie Dong #include <linux/cleanup.h> 9*61250669SJackie Dong #include <linux/dev_printk.h> 10*61250669SJackie Dong #include <linux/device.h> 11*61250669SJackie Dong #include <linux/leds.h> 12*61250669SJackie Dong #include <linux/module.h> 13*61250669SJackie Dong #include <linux/wmi.h> 14*61250669SJackie Dong 15*61250669SJackie Dong /* Lenovo Super Hotkey WMI GUIDs */ 16*61250669SJackie Dong #define LUD_WMI_METHOD_GUID "CE6C0974-0407-4F50-88BA-4FC3B6559AD8" 17*61250669SJackie Dong 18*61250669SJackie Dong /* Lenovo Utility Data WMI method_id */ 19*61250669SJackie Dong #define WMI_LUD_GET_SUPPORT 1 20*61250669SJackie Dong #define WMI_LUD_SET_FEATURE 2 21*61250669SJackie Dong 22*61250669SJackie Dong #define WMI_LUD_GET_MICMUTE_LED_VER 20 23*61250669SJackie Dong #define WMI_LUD_GET_AUDIOMUTE_LED_VER 26 24*61250669SJackie Dong 25*61250669SJackie Dong #define WMI_LUD_SUPPORT_MICMUTE_LED_VER 25 26*61250669SJackie Dong #define WMI_LUD_SUPPORT_AUDIOMUTE_LED_VER 27 27*61250669SJackie Dong 28*61250669SJackie Dong /* Input parameters to mute/unmute audio LED and Mic LED */ 29*61250669SJackie Dong struct wmi_led_args { 30*61250669SJackie Dong u8 id; 31*61250669SJackie Dong u8 subid; 32*61250669SJackie Dong u16 value; 33*61250669SJackie Dong }; 34*61250669SJackie Dong 35*61250669SJackie Dong /* Values of input parameters to SetFeature of audio LED and Mic LED */ 36*61250669SJackie Dong enum hotkey_set_feature { 37*61250669SJackie Dong MIC_MUTE_LED_ON = 1, 38*61250669SJackie Dong MIC_MUTE_LED_OFF = 2, 39*61250669SJackie Dong AUDIO_MUTE_LED_ON = 4, 40*61250669SJackie Dong AUDIO_MUTE_LED_OFF = 5, 41*61250669SJackie Dong }; 42*61250669SJackie Dong 43*61250669SJackie Dong #define LSH_ACPI_LED_MAX 2 44*61250669SJackie Dong 45*61250669SJackie Dong struct lenovo_super_hotkey_wmi_private { 46*61250669SJackie Dong struct led_classdev cdev[LSH_ACPI_LED_MAX]; 47*61250669SJackie Dong struct wmi_device *led_wdev; 48*61250669SJackie Dong }; 49*61250669SJackie Dong 50*61250669SJackie Dong enum mute_led_type { 51*61250669SJackie Dong MIC_MUTE, 52*61250669SJackie Dong AUDIO_MUTE, 53*61250669SJackie Dong }; 54*61250669SJackie Dong 55*61250669SJackie Dong static int lsh_wmi_mute_led_set(enum mute_led_type led_type, struct led_classdev *led_cdev, 56*61250669SJackie Dong enum led_brightness brightness) 57*61250669SJackie Dong 58*61250669SJackie Dong { 59*61250669SJackie Dong struct lenovo_super_hotkey_wmi_private *wpriv = container_of(led_cdev, 60*61250669SJackie Dong struct lenovo_super_hotkey_wmi_private, cdev[led_type]); 61*61250669SJackie Dong struct wmi_led_args led_arg = {0, 0, 0}; 62*61250669SJackie Dong struct acpi_buffer input; 63*61250669SJackie Dong acpi_status status; 64*61250669SJackie Dong 65*61250669SJackie Dong switch (led_type) { 66*61250669SJackie Dong case MIC_MUTE: 67*61250669SJackie Dong led_arg.id = brightness == LED_ON ? MIC_MUTE_LED_ON : MIC_MUTE_LED_OFF; 68*61250669SJackie Dong break; 69*61250669SJackie Dong case AUDIO_MUTE: 70*61250669SJackie Dong led_arg.id = brightness == LED_ON ? AUDIO_MUTE_LED_ON : AUDIO_MUTE_LED_OFF; 71*61250669SJackie Dong break; 72*61250669SJackie Dong default: 73*61250669SJackie Dong return -EINVAL; 74*61250669SJackie Dong } 75*61250669SJackie Dong 76*61250669SJackie Dong input.length = sizeof(led_arg); 77*61250669SJackie Dong input.pointer = &led_arg; 78*61250669SJackie Dong status = wmidev_evaluate_method(wpriv->led_wdev, 0, WMI_LUD_SET_FEATURE, &input, NULL); 79*61250669SJackie Dong if (ACPI_FAILURE(status)) 80*61250669SJackie Dong return -EIO; 81*61250669SJackie Dong 82*61250669SJackie Dong return 0; 83*61250669SJackie Dong } 84*61250669SJackie Dong 85*61250669SJackie Dong static int lsh_wmi_audiomute_led_set(struct led_classdev *led_cdev, 86*61250669SJackie Dong enum led_brightness brightness) 87*61250669SJackie Dong 88*61250669SJackie Dong { 89*61250669SJackie Dong return lsh_wmi_mute_led_set(AUDIO_MUTE, led_cdev, brightness); 90*61250669SJackie Dong } 91*61250669SJackie Dong 92*61250669SJackie Dong static int lsh_wmi_micmute_led_set(struct led_classdev *led_cdev, 93*61250669SJackie Dong enum led_brightness brightness) 94*61250669SJackie Dong { 95*61250669SJackie Dong return lsh_wmi_mute_led_set(MIC_MUTE, led_cdev, brightness); 96*61250669SJackie Dong } 97*61250669SJackie Dong 98*61250669SJackie Dong static int lenovo_super_hotkey_wmi_led_init(enum mute_led_type led_type, struct device *dev) 99*61250669SJackie Dong { 100*61250669SJackie Dong struct lenovo_super_hotkey_wmi_private *wpriv = dev_get_drvdata(dev); 101*61250669SJackie Dong struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; 102*61250669SJackie Dong struct acpi_buffer input; 103*61250669SJackie Dong int led_version, err = 0; 104*61250669SJackie Dong unsigned int wmiarg; 105*61250669SJackie Dong acpi_status status; 106*61250669SJackie Dong 107*61250669SJackie Dong switch (led_type) { 108*61250669SJackie Dong case MIC_MUTE: 109*61250669SJackie Dong wmiarg = WMI_LUD_GET_MICMUTE_LED_VER; 110*61250669SJackie Dong break; 111*61250669SJackie Dong case AUDIO_MUTE: 112*61250669SJackie Dong wmiarg = WMI_LUD_GET_AUDIOMUTE_LED_VER; 113*61250669SJackie Dong break; 114*61250669SJackie Dong default: 115*61250669SJackie Dong return -EINVAL; 116*61250669SJackie Dong } 117*61250669SJackie Dong 118*61250669SJackie Dong input.length = sizeof(wmiarg); 119*61250669SJackie Dong input.pointer = &wmiarg; 120*61250669SJackie Dong status = wmidev_evaluate_method(wpriv->led_wdev, 0, WMI_LUD_GET_SUPPORT, &input, &output); 121*61250669SJackie Dong if (ACPI_FAILURE(status)) 122*61250669SJackie Dong return -EIO; 123*61250669SJackie Dong 124*61250669SJackie Dong union acpi_object *obj __free(kfree) = output.pointer; 125*61250669SJackie Dong if (obj && obj->type == ACPI_TYPE_INTEGER) 126*61250669SJackie Dong led_version = obj->integer.value; 127*61250669SJackie Dong else 128*61250669SJackie Dong return -EIO; 129*61250669SJackie Dong 130*61250669SJackie Dong wpriv->cdev[led_type].max_brightness = LED_ON; 131*61250669SJackie Dong wpriv->cdev[led_type].flags = LED_CORE_SUSPENDRESUME; 132*61250669SJackie Dong 133*61250669SJackie Dong switch (led_type) { 134*61250669SJackie Dong case MIC_MUTE: 135*61250669SJackie Dong if (led_version != WMI_LUD_SUPPORT_MICMUTE_LED_VER) 136*61250669SJackie Dong return -EIO; 137*61250669SJackie Dong 138*61250669SJackie Dong wpriv->cdev[led_type].name = "platform::micmute"; 139*61250669SJackie Dong wpriv->cdev[led_type].brightness_set_blocking = &lsh_wmi_micmute_led_set; 140*61250669SJackie Dong wpriv->cdev[led_type].default_trigger = "audio-micmute"; 141*61250669SJackie Dong break; 142*61250669SJackie Dong case AUDIO_MUTE: 143*61250669SJackie Dong if (led_version != WMI_LUD_SUPPORT_AUDIOMUTE_LED_VER) 144*61250669SJackie Dong return -EIO; 145*61250669SJackie Dong 146*61250669SJackie Dong wpriv->cdev[led_type].name = "platform::mute"; 147*61250669SJackie Dong wpriv->cdev[led_type].brightness_set_blocking = &lsh_wmi_audiomute_led_set; 148*61250669SJackie Dong wpriv->cdev[led_type].default_trigger = "audio-mute"; 149*61250669SJackie Dong break; 150*61250669SJackie Dong default: 151*61250669SJackie Dong dev_err(dev, "Unknown LED type %d\n", led_type); 152*61250669SJackie Dong return -EINVAL; 153*61250669SJackie Dong } 154*61250669SJackie Dong 155*61250669SJackie Dong err = devm_led_classdev_register(dev, &wpriv->cdev[led_type]); 156*61250669SJackie Dong if (err < 0) { 157*61250669SJackie Dong dev_err(dev, "Could not register mute LED %d : %d\n", led_type, err); 158*61250669SJackie Dong return err; 159*61250669SJackie Dong } 160*61250669SJackie Dong return 0; 161*61250669SJackie Dong } 162*61250669SJackie Dong 163*61250669SJackie Dong static int lenovo_super_hotkey_wmi_leds_setup(struct device *dev) 164*61250669SJackie Dong { 165*61250669SJackie Dong int err; 166*61250669SJackie Dong 167*61250669SJackie Dong err = lenovo_super_hotkey_wmi_led_init(MIC_MUTE, dev); 168*61250669SJackie Dong if (err) 169*61250669SJackie Dong return err; 170*61250669SJackie Dong 171*61250669SJackie Dong err = lenovo_super_hotkey_wmi_led_init(AUDIO_MUTE, dev); 172*61250669SJackie Dong if (err) 173*61250669SJackie Dong return err; 174*61250669SJackie Dong 175*61250669SJackie Dong return 0; 176*61250669SJackie Dong } 177*61250669SJackie Dong 178*61250669SJackie Dong static int lenovo_super_hotkey_wmi_probe(struct wmi_device *wdev, const void *context) 179*61250669SJackie Dong { 180*61250669SJackie Dong struct lenovo_super_hotkey_wmi_private *wpriv; 181*61250669SJackie Dong 182*61250669SJackie Dong wpriv = devm_kzalloc(&wdev->dev, sizeof(*wpriv), GFP_KERNEL); 183*61250669SJackie Dong if (!wpriv) 184*61250669SJackie Dong return -ENOMEM; 185*61250669SJackie Dong 186*61250669SJackie Dong dev_set_drvdata(&wdev->dev, wpriv); 187*61250669SJackie Dong wpriv->led_wdev = wdev; 188*61250669SJackie Dong return lenovo_super_hotkey_wmi_leds_setup(&wdev->dev); 189*61250669SJackie Dong } 190*61250669SJackie Dong 191*61250669SJackie Dong static const struct wmi_device_id lenovo_super_hotkey_wmi_id_table[] = { 192*61250669SJackie Dong { LUD_WMI_METHOD_GUID, NULL }, /* Utility data */ 193*61250669SJackie Dong { } 194*61250669SJackie Dong }; 195*61250669SJackie Dong 196*61250669SJackie Dong MODULE_DEVICE_TABLE(wmi, lenovo_super_hotkey_wmi_id_table); 197*61250669SJackie Dong 198*61250669SJackie Dong static struct wmi_driver lenovo_wmi_hotkey_utilities_driver = { 199*61250669SJackie Dong .driver = { 200*61250669SJackie Dong .name = "lenovo_wmi_hotkey_utilities", 201*61250669SJackie Dong .probe_type = PROBE_PREFER_ASYNCHRONOUS 202*61250669SJackie Dong }, 203*61250669SJackie Dong .id_table = lenovo_super_hotkey_wmi_id_table, 204*61250669SJackie Dong .probe = lenovo_super_hotkey_wmi_probe, 205*61250669SJackie Dong .no_singleton = true, 206*61250669SJackie Dong }; 207*61250669SJackie Dong 208*61250669SJackie Dong module_wmi_driver(lenovo_wmi_hotkey_utilities_driver); 209*61250669SJackie Dong 210*61250669SJackie Dong MODULE_AUTHOR("Jackie Dong <dongeg1@lenovo.com>"); 211*61250669SJackie Dong MODULE_DESCRIPTION("Lenovo Super Hotkey Utility WMI extras driver"); 212*61250669SJackie Dong MODULE_LICENSE("GPL"); 213