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
lsh_wmi_mute_led_set(enum mute_led_type led_type,struct led_classdev * led_cdev,enum led_brightness brightness)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
lsh_wmi_audiomute_led_set(struct led_classdev * led_cdev,enum led_brightness brightness)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
lsh_wmi_micmute_led_set(struct led_classdev * led_cdev,enum led_brightness brightness)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
lenovo_super_hotkey_wmi_led_init(enum mute_led_type led_type,struct device * dev)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
lenovo_super_hotkey_wmi_leds_setup(struct device * dev)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
lenovo_super_hotkey_wmi_probe(struct wmi_device * wdev,const void * context)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