root/drivers/platform/x86/lenovo/wmi-hotkey-utilities.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");