Skip to content

Commit 6125066

Browse files
Jackie Dongij-intel
authored andcommitted
platform/x86:lenovo-wmi-hotkey-utilities.c: Support for mic and audio mute LEDs
Implement Lenovo utility data WMI calls needed to make LEDs work on Ideapads that support this GUID. This enables the mic and audio LEDs to be updated correctly. Tested on below samples. ThinkBook 13X Gen4 IMH ThinkBook 14 G6 ABP ThinkBook 16p Gen4-21J8 ThinkBook 16 G8 IRL ThinkBook 16 G7+ ASP Signed-off-by: Jackie Dong <[email protected]> Suggested-by: Mark Pearson <[email protected]> Reviewed-by: Kurt Borja <[email protected]> Reviewed-by: Ilpo Järvinen <[email protected]> Reviewed-by: Armin Wolf <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Ilpo Järvinen <[email protected]>
1 parent baa18b6 commit 6125066

File tree

4 files changed

+230
-0
lines changed

4 files changed

+230
-0
lines changed

MAINTAINERS

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13158,6 +13158,12 @@ S: Maintained
1315813158
W: http://legousb.sourceforge.net/
1315913159
F: drivers/usb/misc/legousbtower.c
1316013160

13161+
LENOVO WMI HOTKEY UTILITIES DRIVER
13162+
M: Jackie Dong <[email protected]>
13163+
13164+
S: Maintained
13165+
F: drivers/platform/x86/lenovo-wmi-hotkey-utilities.c
13166+
1316113167
LETSKETCH HID TABLET DRIVER
1316213168
M: Hans de Goede <[email protected]>
1316313169

drivers/platform/x86/Kconfig

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,17 @@ config IDEAPAD_LAPTOP
475475
This is a driver for Lenovo IdeaPad netbooks contains drivers for
476476
rfkill switch, hotkey, fan control and backlight control.
477477

478+
config LENOVO_WMI_HOTKEY_UTILITIES
479+
tristate "Lenovo Hotkey Utility WMI extras driver"
480+
depends on ACPI_WMI
481+
select NEW_LEDS
482+
select LEDS_CLASS
483+
imply IDEAPAD_LAPTOP
484+
help
485+
This driver provides WMI support for Lenovo customized hotkeys function,
486+
such as LED control for audio/mic mute event for Ideapad, YOGA, XiaoXin,
487+
Gaming, ThinkBook and so on.
488+
478489
config LENOVO_YMC
479490
tristate "Lenovo Yoga Tablet Mode Control"
480491
depends on ACPI_WMI

drivers/platform/x86/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ obj-$(CONFIG_UV_SYSFS) += uv_sysfs.o
6161
# IBM Thinkpad and Lenovo
6262
obj-$(CONFIG_IBM_RTL) += ibm_rtl.o
6363
obj-$(CONFIG_IDEAPAD_LAPTOP) += ideapad-laptop.o
64+
obj-$(CONFIG_LENOVO_WMI_HOTKEY_UTILITIES) += lenovo-wmi-hotkey-utilities.o
6465
obj-$(CONFIG_LENOVO_YMC) += lenovo-ymc.o
6566
obj-$(CONFIG_SENSORS_HDAPS) += hdaps.o
6667
obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o
Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
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 <[email protected]>");
211+
MODULE_DESCRIPTION("Lenovo Super Hotkey Utility WMI extras driver");
212+
MODULE_LICENSE("GPL");

0 commit comments

Comments
 (0)