| // 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 platform_profile_handler handler; |
| }; |
| |
| 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 platform_profile_handler *pprof, |
| enum platform_profile_option profile) |
| { |
| struct inspur_wmi_priv *priv = container_of(pprof, struct inspur_wmi_priv, |
| handler); |
| 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 platform_profile_handler *pprof, |
| enum platform_profile_option *profile) |
| { |
| struct inspur_wmi_priv *priv = container_of(pprof, struct inspur_wmi_priv, |
| handler); |
| 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_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->handler.profile_get = inspur_platform_profile_get; |
| priv->handler.profile_set = inspur_platform_profile_set; |
| |
| set_bit(PLATFORM_PROFILE_LOW_POWER, priv->handler.choices); |
| set_bit(PLATFORM_PROFILE_BALANCED, priv->handler.choices); |
| set_bit(PLATFORM_PROFILE_PERFORMANCE, priv->handler.choices); |
| |
| return platform_profile_register(&priv->handler); |
| } |
| |
| static void inspur_wmi_remove(struct wmi_device *wdev) |
| { |
| platform_profile_remove(); |
| } |
| |
| 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, |
| .remove = inspur_wmi_remove, |
| .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"); |