root/drivers/platform/x86/inspur_platform_profile.c
// SPDX-License-Identifier: GPL-2.0
/*
 *  Inspur WMI Platform Profile
 *
 *  Copyright (C) 2018        Ai Chao <aichao@kylinos.cn>
 */

#include <linux/acpi.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/platform_profile.h>
#include <linux/wmi.h>

#define WMI_INSPUR_POWERMODE_BIOS_GUID "596C31E3-332D-43C9-AEE9-585493284F5D"

enum inspur_wmi_method_ids {
        INSPUR_WMI_GET_POWERMODE = 0x02,
        INSPUR_WMI_SET_POWERMODE = 0x03,
};

/*
 * Power Mode:
 *           0x0: Balance Mode
 *           0x1: Performance Mode
 *           0x2: Power Saver Mode
 */
enum inspur_tmp_profile {
        INSPUR_TMP_PROFILE_BALANCE      = 0,
        INSPUR_TMP_PROFILE_PERFORMANCE  = 1,
        INSPUR_TMP_PROFILE_POWERSAVE    = 2,
};

struct inspur_wmi_priv {
        struct wmi_device *wdev;
        struct device *ppdev;
};

static int inspur_wmi_perform_query(struct wmi_device *wdev,
                                    enum inspur_wmi_method_ids query_id,
                                    void *buffer, size_t insize,
                                    size_t outsize)
{
        struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
        struct acpi_buffer input = { insize, buffer};
        union acpi_object *obj;
        acpi_status status;
        int ret = 0;

        status = wmidev_evaluate_method(wdev, 0, query_id, &input, &output);
        if (ACPI_FAILURE(status)) {
                dev_err(&wdev->dev, "EC Powermode control failed: %s\n",
                        acpi_format_exception(status));
                return -EIO;
        }

        obj = output.pointer;
        if (!obj)
                return -EINVAL;

        if (obj->type != ACPI_TYPE_BUFFER ||
            obj->buffer.length != outsize) {
                ret = -EINVAL;
                goto out_free;
        }

        memcpy(buffer, obj->buffer.pointer, obj->buffer.length);

out_free:
        kfree(obj);
        return ret;
}

/*
 * Set Power Mode to EC RAM. If Power Mode value greater than 0x3,
 * return error
 * Method ID: 0x3
 * Arg: 4 Bytes
 * Byte [0]: Power Mode:
 *         0x0: Balance Mode
 *         0x1: Performance Mode
 *         0x2: Power Saver Mode
 * Return Value: 4 Bytes
 * Byte [0]: Return Code
 *         0x0: No Error
 *         0x1: Error
 */
static int inspur_platform_profile_set(struct device *dev,
                                       enum platform_profile_option profile)
{
        struct inspur_wmi_priv *priv = dev_get_drvdata(dev);
        u8 ret_code[4] = {0, 0, 0, 0};
        int ret;

        switch (profile) {
        case PLATFORM_PROFILE_BALANCED:
                ret_code[0] = INSPUR_TMP_PROFILE_BALANCE;
                break;
        case PLATFORM_PROFILE_PERFORMANCE:
                ret_code[0] = INSPUR_TMP_PROFILE_PERFORMANCE;
                break;
        case PLATFORM_PROFILE_LOW_POWER:
                ret_code[0] = INSPUR_TMP_PROFILE_POWERSAVE;
                break;
        default:
                return -EOPNOTSUPP;
        }

        ret = inspur_wmi_perform_query(priv->wdev, INSPUR_WMI_SET_POWERMODE,
                                       ret_code, sizeof(ret_code),
                                       sizeof(ret_code));

        if (ret < 0)
                return ret;

        if (ret_code[0])
                return -EBADRQC;

        return 0;
}

/*
 * Get Power Mode from EC RAM, If Power Mode value greater than 0x3,
 * return error
 * Method ID: 0x2
 * Return Value: 4 Bytes
 * Byte [0]: Return Code
 *         0x0: No Error
 *         0x1: Error
 * Byte [1]: Power Mode
 *         0x0: Balance Mode
 *         0x1: Performance Mode
 *         0x2: Power Saver Mode
 */
static int inspur_platform_profile_get(struct device *dev,
                                       enum platform_profile_option *profile)
{
        struct inspur_wmi_priv *priv = dev_get_drvdata(dev);
        u8 ret_code[4] = {0, 0, 0, 0};
        int ret;

        ret = inspur_wmi_perform_query(priv->wdev, INSPUR_WMI_GET_POWERMODE,
                                       &ret_code, sizeof(ret_code),
                                       sizeof(ret_code));
        if (ret < 0)
                return ret;

        if (ret_code[0])
                return -EBADRQC;

        switch (ret_code[1]) {
        case INSPUR_TMP_PROFILE_BALANCE:
                *profile = PLATFORM_PROFILE_BALANCED;
                break;
        case INSPUR_TMP_PROFILE_PERFORMANCE:
                *profile = PLATFORM_PROFILE_PERFORMANCE;
                break;
        case INSPUR_TMP_PROFILE_POWERSAVE:
                *profile = PLATFORM_PROFILE_LOW_POWER;
                break;
        default:
                return -EINVAL;
        }

        return 0;
}

static int inspur_platform_profile_probe(void *drvdata, unsigned long *choices)
{
        set_bit(PLATFORM_PROFILE_LOW_POWER, choices);
        set_bit(PLATFORM_PROFILE_BALANCED, choices);
        set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);

        return 0;
}

static const struct platform_profile_ops inspur_platform_profile_ops = {
        .probe = inspur_platform_profile_probe,
        .profile_get = inspur_platform_profile_get,
        .profile_set = inspur_platform_profile_set,
};

static int inspur_wmi_probe(struct wmi_device *wdev, const void *context)
{
        struct inspur_wmi_priv *priv;

        priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
        if (!priv)
                return -ENOMEM;

        priv->wdev = wdev;
        dev_set_drvdata(&wdev->dev, priv);

        priv->ppdev = devm_platform_profile_register(&wdev->dev, "inspur-wmi", priv,
                                                     &inspur_platform_profile_ops);

        return PTR_ERR_OR_ZERO(priv->ppdev);
}

static const struct wmi_device_id inspur_wmi_id_table[] = {
        { .guid_string = WMI_INSPUR_POWERMODE_BIOS_GUID },
        {  }
};

MODULE_DEVICE_TABLE(wmi, inspur_wmi_id_table);

static struct wmi_driver inspur_wmi_driver = {
        .driver = {
                .name = "inspur-wmi-platform-profile",
                .probe_type = PROBE_PREFER_ASYNCHRONOUS,
        },
        .id_table = inspur_wmi_id_table,
        .probe = inspur_wmi_probe,
        .no_singleton = true,
};

module_wmi_driver(inspur_wmi_driver);

MODULE_AUTHOR("Ai Chao <aichao@kylinos.cn>");
MODULE_DESCRIPTION("Platform Profile Support for Inspur");
MODULE_LICENSE("GPL");