mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/chenhuacai/linux-loongson
synced 2025-08-28 00:19:36 +00:00

Merge the fixes branch into the for-next branch to resolve Makefile conflict and include the power supply accessor work that is required by the upcoming Uniwill driver.
225 lines
5.6 KiB
C
225 lines
5.6 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Lenovo Super Hotkey Utility WMI extras driver for Ideapad laptop
|
|
*
|
|
* Copyright (C) 2025 Lenovo
|
|
*/
|
|
|
|
#include <linux/cleanup.h>
|
|
#include <linux/dev_printk.h>
|
|
#include <linux/device.h>
|
|
#include <linux/leds.h>
|
|
#include <linux/module.h>
|
|
#include <linux/wmi.h>
|
|
|
|
/* Lenovo Super Hotkey WMI GUIDs */
|
|
#define LUD_WMI_METHOD_GUID "CE6C0974-0407-4F50-88BA-4FC3B6559AD8"
|
|
|
|
/* Lenovo Utility Data WMI method_id */
|
|
#define WMI_LUD_GET_SUPPORT 1
|
|
#define WMI_LUD_SET_FEATURE 2
|
|
|
|
#define WMI_LUD_GET_MICMUTE_LED_VER 20
|
|
#define WMI_LUD_GET_AUDIOMUTE_LED_VER 26
|
|
|
|
#define WMI_LUD_SUPPORT_MICMUTE_LED_VER 25
|
|
#define WMI_LUD_SUPPORT_AUDIOMUTE_LED_VER 27
|
|
|
|
/* Input parameters to mute/unmute audio LED and Mic LED */
|
|
struct wmi_led_args {
|
|
u8 id;
|
|
u8 subid;
|
|
u16 value;
|
|
};
|
|
|
|
/* Values of input parameters to SetFeature of audio LED and Mic LED */
|
|
enum hotkey_set_feature {
|
|
MIC_MUTE_LED_ON = 1,
|
|
MIC_MUTE_LED_OFF = 2,
|
|
AUDIO_MUTE_LED_ON = 4,
|
|
AUDIO_MUTE_LED_OFF = 5,
|
|
};
|
|
|
|
#define LSH_ACPI_LED_MAX 2
|
|
|
|
struct lenovo_super_hotkey_wmi_private {
|
|
struct led_classdev cdev[LSH_ACPI_LED_MAX];
|
|
struct wmi_device *led_wdev;
|
|
};
|
|
|
|
enum mute_led_type {
|
|
MIC_MUTE,
|
|
AUDIO_MUTE,
|
|
};
|
|
|
|
static int lsh_wmi_mute_led_set(enum mute_led_type led_type, struct led_classdev *led_cdev,
|
|
enum led_brightness brightness)
|
|
|
|
{
|
|
struct lenovo_super_hotkey_wmi_private *wpriv = container_of(led_cdev,
|
|
struct lenovo_super_hotkey_wmi_private, cdev[led_type]);
|
|
struct wmi_led_args led_arg = {0, 0, 0};
|
|
struct acpi_buffer input;
|
|
acpi_status status;
|
|
|
|
switch (led_type) {
|
|
case MIC_MUTE:
|
|
led_arg.id = brightness == LED_ON ? MIC_MUTE_LED_ON : MIC_MUTE_LED_OFF;
|
|
break;
|
|
case AUDIO_MUTE:
|
|
led_arg.id = brightness == LED_ON ? AUDIO_MUTE_LED_ON : AUDIO_MUTE_LED_OFF;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
input.length = sizeof(led_arg);
|
|
input.pointer = &led_arg;
|
|
status = wmidev_evaluate_method(wpriv->led_wdev, 0, WMI_LUD_SET_FEATURE, &input, NULL);
|
|
if (ACPI_FAILURE(status))
|
|
return -EIO;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int lsh_wmi_audiomute_led_set(struct led_classdev *led_cdev,
|
|
enum led_brightness brightness)
|
|
|
|
{
|
|
return lsh_wmi_mute_led_set(AUDIO_MUTE, led_cdev, brightness);
|
|
}
|
|
|
|
static int lsh_wmi_micmute_led_set(struct led_classdev *led_cdev,
|
|
enum led_brightness brightness)
|
|
{
|
|
return lsh_wmi_mute_led_set(MIC_MUTE, led_cdev, brightness);
|
|
}
|
|
|
|
static int lenovo_super_hotkey_wmi_led_init(enum mute_led_type led_type, struct device *dev)
|
|
{
|
|
struct lenovo_super_hotkey_wmi_private *wpriv = dev_get_drvdata(dev);
|
|
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
|
|
struct acpi_buffer input;
|
|
int led_version, err = 0;
|
|
unsigned int wmiarg;
|
|
acpi_status status;
|
|
|
|
switch (led_type) {
|
|
case MIC_MUTE:
|
|
wmiarg = WMI_LUD_GET_MICMUTE_LED_VER;
|
|
break;
|
|
case AUDIO_MUTE:
|
|
wmiarg = WMI_LUD_GET_AUDIOMUTE_LED_VER;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
input.length = sizeof(wmiarg);
|
|
input.pointer = &wmiarg;
|
|
status = wmidev_evaluate_method(wpriv->led_wdev, 0, WMI_LUD_GET_SUPPORT, &input, &output);
|
|
if (ACPI_FAILURE(status))
|
|
return -EIO;
|
|
|
|
union acpi_object *obj __free(kfree) = output.pointer;
|
|
if (!obj || obj->type != ACPI_TYPE_INTEGER)
|
|
return -EIO;
|
|
|
|
led_version = obj->integer.value;
|
|
|
|
/*
|
|
* Output parameters define: 0 means mute LED is not supported, Non-zero means
|
|
* mute LED can be supported.
|
|
*/
|
|
if (led_version == 0)
|
|
return 0;
|
|
|
|
|
|
switch (led_type) {
|
|
case MIC_MUTE:
|
|
if (led_version != WMI_LUD_SUPPORT_MICMUTE_LED_VER) {
|
|
pr_warn("The MIC_MUTE LED of this device isn't supported.\n");
|
|
return 0;
|
|
}
|
|
|
|
wpriv->cdev[led_type].name = "platform::micmute";
|
|
wpriv->cdev[led_type].brightness_set_blocking = &lsh_wmi_micmute_led_set;
|
|
wpriv->cdev[led_type].default_trigger = "audio-micmute";
|
|
break;
|
|
case AUDIO_MUTE:
|
|
if (led_version != WMI_LUD_SUPPORT_AUDIOMUTE_LED_VER) {
|
|
pr_warn("The AUDIO_MUTE LED of this device isn't supported.\n");
|
|
return 0;
|
|
}
|
|
|
|
wpriv->cdev[led_type].name = "platform::mute";
|
|
wpriv->cdev[led_type].brightness_set_blocking = &lsh_wmi_audiomute_led_set;
|
|
wpriv->cdev[led_type].default_trigger = "audio-mute";
|
|
break;
|
|
default:
|
|
dev_err(dev, "Unknown LED type %d\n", led_type);
|
|
return -EINVAL;
|
|
}
|
|
|
|
wpriv->cdev[led_type].max_brightness = LED_ON;
|
|
wpriv->cdev[led_type].flags = LED_CORE_SUSPENDRESUME;
|
|
|
|
err = devm_led_classdev_register(dev, &wpriv->cdev[led_type]);
|
|
if (err < 0) {
|
|
dev_err(dev, "Could not register mute LED %d : %d\n", led_type, err);
|
|
return err;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int lenovo_super_hotkey_wmi_leds_setup(struct device *dev)
|
|
{
|
|
int err;
|
|
|
|
err = lenovo_super_hotkey_wmi_led_init(MIC_MUTE, dev);
|
|
if (err)
|
|
return err;
|
|
|
|
err = lenovo_super_hotkey_wmi_led_init(AUDIO_MUTE, dev);
|
|
if (err)
|
|
return err;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int lenovo_super_hotkey_wmi_probe(struct wmi_device *wdev, const void *context)
|
|
{
|
|
struct lenovo_super_hotkey_wmi_private *wpriv;
|
|
|
|
wpriv = devm_kzalloc(&wdev->dev, sizeof(*wpriv), GFP_KERNEL);
|
|
if (!wpriv)
|
|
return -ENOMEM;
|
|
|
|
dev_set_drvdata(&wdev->dev, wpriv);
|
|
wpriv->led_wdev = wdev;
|
|
return lenovo_super_hotkey_wmi_leds_setup(&wdev->dev);
|
|
}
|
|
|
|
static const struct wmi_device_id lenovo_super_hotkey_wmi_id_table[] = {
|
|
{ LUD_WMI_METHOD_GUID, NULL }, /* Utility data */
|
|
{ }
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(wmi, lenovo_super_hotkey_wmi_id_table);
|
|
|
|
static struct wmi_driver lenovo_wmi_hotkey_utilities_driver = {
|
|
.driver = {
|
|
.name = "lenovo_wmi_hotkey_utilities",
|
|
.probe_type = PROBE_PREFER_ASYNCHRONOUS
|
|
},
|
|
.id_table = lenovo_super_hotkey_wmi_id_table,
|
|
.probe = lenovo_super_hotkey_wmi_probe,
|
|
.no_singleton = true,
|
|
};
|
|
|
|
module_wmi_driver(lenovo_wmi_hotkey_utilities_driver);
|
|
|
|
MODULE_AUTHOR("Jackie Dong <dongeg1@lenovo.com>");
|
|
MODULE_DESCRIPTION("Lenovo Super Hotkey Utility WMI extras driver");
|
|
MODULE_LICENSE("GPL");
|