xref: /linux/drivers/platform/x86/lenovo-wmi-hotkey-utilities.c (revision 4f9786035f9e519db41375818e1d0b5f20da2f10)
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