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