xref: /linux/drivers/platform/x86/lenovo/wmi-hotkey-utilities.c (revision b5f20799f164053a0fbf7c61b3c99f8cf9cf0656)
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 		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 
lenovo_super_hotkey_wmi_leds_setup(struct device * dev)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 
lenovo_super_hotkey_wmi_probe(struct wmi_device * wdev,const void * context)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