File Index
Files and directories
Sign in
FILE

wmi-hotkey-utilities.c

File preview with syntax highlighting.

linux-6.19.14/drivers/platform/x86/lenovo/wmi-hotkey-utilities.c C
  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 */
 29struct 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 */
 36enum 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
 45struct lenovo_super_hotkey_wmi_private {
 46	struct led_classdev cdev[LSH_ACPI_LED_MAX];
 47	struct wmi_device *led_wdev;
 48};
 49
 50enum mute_led_type {
 51	MIC_MUTE,
 52	AUDIO_MUTE,
 53};
 54
 55static 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
 85static 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
 92static 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
 98static 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
175static 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
190static 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
203static const struct wmi_device_id lenovo_super_hotkey_wmi_id_table[] = {
204	{ LUD_WMI_METHOD_GUID, NULL }, /* Utility data */
205	{ }
206};
207
208MODULE_DEVICE_TABLE(wmi, lenovo_super_hotkey_wmi_id_table);
209
210static 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
220module_wmi_driver(lenovo_wmi_hotkey_utilities_driver);
221
222MODULE_AUTHOR("Jackie Dong <dongeg1@lenovo.com>");
223MODULE_DESCRIPTION("Lenovo Super Hotkey Utility WMI extras driver");
224MODULE_LICENSE("GPL");
~/ Read-only file preview.