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 led_version = obj->integer.value; 127 else 128 return -EIO; 129 130 wpriv->cdev[led_type].max_brightness = LED_ON; 131 wpriv->cdev[led_type].flags = LED_CORE_SUSPENDRESUME; 132 133 switch (led_type) { 134 case MIC_MUTE: 135 if (led_version != WMI_LUD_SUPPORT_MICMUTE_LED_VER) 136 return -EIO; 137 138 wpriv->cdev[led_type].name = "platform::micmute"; 139 wpriv->cdev[led_type].brightness_set_blocking = &lsh_wmi_micmute_led_set; 140 wpriv->cdev[led_type].default_trigger = "audio-micmute"; 141 break; 142 case AUDIO_MUTE: 143 if (led_version != WMI_LUD_SUPPORT_AUDIOMUTE_LED_VER) 144 return -EIO; 145 146 wpriv->cdev[led_type].name = "platform::mute"; 147 wpriv->cdev[led_type].brightness_set_blocking = &lsh_wmi_audiomute_led_set; 148 wpriv->cdev[led_type].default_trigger = "audio-mute"; 149 break; 150 default: 151 dev_err(dev, "Unknown LED type %d\n", led_type); 152 return -EINVAL; 153 } 154 155 err = devm_led_classdev_register(dev, &wpriv->cdev[led_type]); 156 if (err < 0) { 157 dev_err(dev, "Could not register mute LED %d : %d\n", led_type, err); 158 return err; 159 } 160 return 0; 161 } 162 163 static int lenovo_super_hotkey_wmi_leds_setup(struct device *dev) 164 { 165 int err; 166 167 err = lenovo_super_hotkey_wmi_led_init(MIC_MUTE, dev); 168 if (err) 169 return err; 170 171 err = lenovo_super_hotkey_wmi_led_init(AUDIO_MUTE, dev); 172 if (err) 173 return err; 174 175 return 0; 176 } 177 178 static int lenovo_super_hotkey_wmi_probe(struct wmi_device *wdev, const void *context) 179 { 180 struct lenovo_super_hotkey_wmi_private *wpriv; 181 182 wpriv = devm_kzalloc(&wdev->dev, sizeof(*wpriv), GFP_KERNEL); 183 if (!wpriv) 184 return -ENOMEM; 185 186 dev_set_drvdata(&wdev->dev, wpriv); 187 wpriv->led_wdev = wdev; 188 return lenovo_super_hotkey_wmi_leds_setup(&wdev->dev); 189 } 190 191 static const struct wmi_device_id lenovo_super_hotkey_wmi_id_table[] = { 192 { LUD_WMI_METHOD_GUID, NULL }, /* Utility data */ 193 { } 194 }; 195 196 MODULE_DEVICE_TABLE(wmi, lenovo_super_hotkey_wmi_id_table); 197 198 static struct wmi_driver lenovo_wmi_hotkey_utilities_driver = { 199 .driver = { 200 .name = "lenovo_wmi_hotkey_utilities", 201 .probe_type = PROBE_PREFER_ASYNCHRONOUS 202 }, 203 .id_table = lenovo_super_hotkey_wmi_id_table, 204 .probe = lenovo_super_hotkey_wmi_probe, 205 .no_singleton = true, 206 }; 207 208 module_wmi_driver(lenovo_wmi_hotkey_utilities_driver); 209 210 MODULE_AUTHOR("Jackie Dong <dongeg1@lenovo.com>"); 211 MODULE_DESCRIPTION("Lenovo Super Hotkey Utility WMI extras driver"); 212 MODULE_LICENSE("GPL"); 213