1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Inspur WMI Platform Profile 4 * 5 * Copyright (C) 2018 Ai Chao <aichao@kylinos.cn> 6 */ 7 8 #include <linux/acpi.h> 9 #include <linux/device.h> 10 #include <linux/module.h> 11 #include <linux/platform_profile.h> 12 #include <linux/wmi.h> 13 14 #define WMI_INSPUR_POWERMODE_BIOS_GUID "596C31E3-332D-43C9-AEE9-585493284F5D" 15 16 enum inspur_wmi_method_ids { 17 INSPUR_WMI_GET_POWERMODE = 0x02, 18 INSPUR_WMI_SET_POWERMODE = 0x03, 19 }; 20 21 /* 22 * Power Mode: 23 * 0x0: Balance Mode 24 * 0x1: Performance Mode 25 * 0x2: Power Saver Mode 26 */ 27 enum inspur_tmp_profile { 28 INSPUR_TMP_PROFILE_BALANCE = 0, 29 INSPUR_TMP_PROFILE_PERFORMANCE = 1, 30 INSPUR_TMP_PROFILE_POWERSAVE = 2, 31 }; 32 33 struct inspur_wmi_priv { 34 struct wmi_device *wdev; 35 struct device *ppdev; 36 }; 37 38 static int inspur_wmi_perform_query(struct wmi_device *wdev, 39 enum inspur_wmi_method_ids query_id, 40 void *buffer, size_t insize, 41 size_t outsize) 42 { 43 struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; 44 struct acpi_buffer input = { insize, buffer}; 45 union acpi_object *obj; 46 acpi_status status; 47 int ret = 0; 48 49 status = wmidev_evaluate_method(wdev, 0, query_id, &input, &output); 50 if (ACPI_FAILURE(status)) { 51 dev_err(&wdev->dev, "EC Powermode control failed: %s\n", 52 acpi_format_exception(status)); 53 return -EIO; 54 } 55 56 obj = output.pointer; 57 if (!obj) 58 return -EINVAL; 59 60 if (obj->type != ACPI_TYPE_BUFFER || 61 obj->buffer.length != outsize) { 62 ret = -EINVAL; 63 goto out_free; 64 } 65 66 memcpy(buffer, obj->buffer.pointer, obj->buffer.length); 67 68 out_free: 69 kfree(obj); 70 return ret; 71 } 72 73 /* 74 * Set Power Mode to EC RAM. If Power Mode value greater than 0x3, 75 * return error 76 * Method ID: 0x3 77 * Arg: 4 Bytes 78 * Byte [0]: Power Mode: 79 * 0x0: Balance Mode 80 * 0x1: Performance Mode 81 * 0x2: Power Saver Mode 82 * Return Value: 4 Bytes 83 * Byte [0]: Return Code 84 * 0x0: No Error 85 * 0x1: Error 86 */ 87 static int inspur_platform_profile_set(struct device *dev, 88 enum platform_profile_option profile) 89 { 90 struct inspur_wmi_priv *priv = dev_get_drvdata(dev); 91 u8 ret_code[4] = {0, 0, 0, 0}; 92 int ret; 93 94 switch (profile) { 95 case PLATFORM_PROFILE_BALANCED: 96 ret_code[0] = INSPUR_TMP_PROFILE_BALANCE; 97 break; 98 case PLATFORM_PROFILE_PERFORMANCE: 99 ret_code[0] = INSPUR_TMP_PROFILE_PERFORMANCE; 100 break; 101 case PLATFORM_PROFILE_LOW_POWER: 102 ret_code[0] = INSPUR_TMP_PROFILE_POWERSAVE; 103 break; 104 default: 105 return -EOPNOTSUPP; 106 } 107 108 ret = inspur_wmi_perform_query(priv->wdev, INSPUR_WMI_SET_POWERMODE, 109 ret_code, sizeof(ret_code), 110 sizeof(ret_code)); 111 112 if (ret < 0) 113 return ret; 114 115 if (ret_code[0]) 116 return -EBADRQC; 117 118 return 0; 119 } 120 121 /* 122 * Get Power Mode from EC RAM, If Power Mode value greater than 0x3, 123 * return error 124 * Method ID: 0x2 125 * Return Value: 4 Bytes 126 * Byte [0]: Return Code 127 * 0x0: No Error 128 * 0x1: Error 129 * Byte [1]: Power Mode 130 * 0x0: Balance Mode 131 * 0x1: Performance Mode 132 * 0x2: Power Saver Mode 133 */ 134 static int inspur_platform_profile_get(struct device *dev, 135 enum platform_profile_option *profile) 136 { 137 struct inspur_wmi_priv *priv = dev_get_drvdata(dev); 138 u8 ret_code[4] = {0, 0, 0, 0}; 139 int ret; 140 141 ret = inspur_wmi_perform_query(priv->wdev, INSPUR_WMI_GET_POWERMODE, 142 &ret_code, sizeof(ret_code), 143 sizeof(ret_code)); 144 if (ret < 0) 145 return ret; 146 147 if (ret_code[0]) 148 return -EBADRQC; 149 150 switch (ret_code[1]) { 151 case INSPUR_TMP_PROFILE_BALANCE: 152 *profile = PLATFORM_PROFILE_BALANCED; 153 break; 154 case INSPUR_TMP_PROFILE_PERFORMANCE: 155 *profile = PLATFORM_PROFILE_PERFORMANCE; 156 break; 157 case INSPUR_TMP_PROFILE_POWERSAVE: 158 *profile = PLATFORM_PROFILE_LOW_POWER; 159 break; 160 default: 161 return -EINVAL; 162 } 163 164 return 0; 165 } 166 167 static int inspur_platform_profile_probe(void *drvdata, unsigned long *choices) 168 { 169 set_bit(PLATFORM_PROFILE_LOW_POWER, choices); 170 set_bit(PLATFORM_PROFILE_BALANCED, choices); 171 set_bit(PLATFORM_PROFILE_PERFORMANCE, choices); 172 173 return 0; 174 } 175 176 static const struct platform_profile_ops inspur_platform_profile_ops = { 177 .probe = inspur_platform_profile_probe, 178 .profile_get = inspur_platform_profile_get, 179 .profile_set = inspur_platform_profile_set, 180 }; 181 182 static int inspur_wmi_probe(struct wmi_device *wdev, const void *context) 183 { 184 struct inspur_wmi_priv *priv; 185 186 priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL); 187 if (!priv) 188 return -ENOMEM; 189 190 priv->wdev = wdev; 191 dev_set_drvdata(&wdev->dev, priv); 192 193 priv->ppdev = devm_platform_profile_register(&wdev->dev, "inspur-wmi", priv, 194 &inspur_platform_profile_ops); 195 196 return PTR_ERR_OR_ZERO(priv->ppdev); 197 } 198 199 static const struct wmi_device_id inspur_wmi_id_table[] = { 200 { .guid_string = WMI_INSPUR_POWERMODE_BIOS_GUID }, 201 { } 202 }; 203 204 MODULE_DEVICE_TABLE(wmi, inspur_wmi_id_table); 205 206 static struct wmi_driver inspur_wmi_driver = { 207 .driver = { 208 .name = "inspur-wmi-platform-profile", 209 .probe_type = PROBE_PREFER_ASYNCHRONOUS, 210 }, 211 .id_table = inspur_wmi_id_table, 212 .probe = inspur_wmi_probe, 213 .no_singleton = true, 214 }; 215 216 module_wmi_driver(inspur_wmi_driver); 217 218 MODULE_AUTHOR("Ai Chao <aichao@kylinos.cn>"); 219 MODULE_DESCRIPTION("Platform Profile Support for Inspur"); 220 MODULE_LICENSE("GPL"); 221