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 platform_profile_handler handler; 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 platform_profile_handler *pprof, 88 enum platform_profile_option profile) 89 { 90 struct inspur_wmi_priv *priv = container_of(pprof, struct inspur_wmi_priv, 91 handler); 92 u8 ret_code[4] = {0, 0, 0, 0}; 93 int ret; 94 95 switch (profile) { 96 case PLATFORM_PROFILE_BALANCED: 97 ret_code[0] = INSPUR_TMP_PROFILE_BALANCE; 98 break; 99 case PLATFORM_PROFILE_PERFORMANCE: 100 ret_code[0] = INSPUR_TMP_PROFILE_PERFORMANCE; 101 break; 102 case PLATFORM_PROFILE_LOW_POWER: 103 ret_code[0] = INSPUR_TMP_PROFILE_POWERSAVE; 104 break; 105 default: 106 return -EOPNOTSUPP; 107 } 108 109 ret = inspur_wmi_perform_query(priv->wdev, INSPUR_WMI_SET_POWERMODE, 110 ret_code, sizeof(ret_code), 111 sizeof(ret_code)); 112 113 if (ret < 0) 114 return ret; 115 116 if (ret_code[0]) 117 return -EBADRQC; 118 119 return 0; 120 } 121 122 /* 123 * Get Power Mode from EC RAM, If Power Mode value greater than 0x3, 124 * return error 125 * Method ID: 0x2 126 * Return Value: 4 Bytes 127 * Byte [0]: Return Code 128 * 0x0: No Error 129 * 0x1: Error 130 * Byte [1]: Power Mode 131 * 0x0: Balance Mode 132 * 0x1: Performance Mode 133 * 0x2: Power Saver Mode 134 */ 135 static int inspur_platform_profile_get(struct platform_profile_handler *pprof, 136 enum platform_profile_option *profile) 137 { 138 struct inspur_wmi_priv *priv = container_of(pprof, struct inspur_wmi_priv, 139 handler); 140 u8 ret_code[4] = {0, 0, 0, 0}; 141 int ret; 142 143 ret = inspur_wmi_perform_query(priv->wdev, INSPUR_WMI_GET_POWERMODE, 144 &ret_code, sizeof(ret_code), 145 sizeof(ret_code)); 146 if (ret < 0) 147 return ret; 148 149 if (ret_code[0]) 150 return -EBADRQC; 151 152 switch (ret_code[1]) { 153 case INSPUR_TMP_PROFILE_BALANCE: 154 *profile = PLATFORM_PROFILE_BALANCED; 155 break; 156 case INSPUR_TMP_PROFILE_PERFORMANCE: 157 *profile = PLATFORM_PROFILE_PERFORMANCE; 158 break; 159 case INSPUR_TMP_PROFILE_POWERSAVE: 160 *profile = PLATFORM_PROFILE_LOW_POWER; 161 break; 162 default: 163 return -EINVAL; 164 } 165 166 return 0; 167 } 168 169 static int inspur_wmi_probe(struct wmi_device *wdev, const void *context) 170 { 171 struct inspur_wmi_priv *priv; 172 173 priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL); 174 if (!priv) 175 return -ENOMEM; 176 177 priv->wdev = wdev; 178 dev_set_drvdata(&wdev->dev, priv); 179 180 priv->handler.profile_get = inspur_platform_profile_get; 181 priv->handler.profile_set = inspur_platform_profile_set; 182 183 set_bit(PLATFORM_PROFILE_LOW_POWER, priv->handler.choices); 184 set_bit(PLATFORM_PROFILE_BALANCED, priv->handler.choices); 185 set_bit(PLATFORM_PROFILE_PERFORMANCE, priv->handler.choices); 186 187 return platform_profile_register(&priv->handler); 188 } 189 190 static void inspur_wmi_remove(struct wmi_device *wdev) 191 { 192 platform_profile_remove(); 193 } 194 195 static const struct wmi_device_id inspur_wmi_id_table[] = { 196 { .guid_string = WMI_INSPUR_POWERMODE_BIOS_GUID }, 197 { } 198 }; 199 200 MODULE_DEVICE_TABLE(wmi, inspur_wmi_id_table); 201 202 static struct wmi_driver inspur_wmi_driver = { 203 .driver = { 204 .name = "inspur-wmi-platform-profile", 205 .probe_type = PROBE_PREFER_ASYNCHRONOUS, 206 }, 207 .id_table = inspur_wmi_id_table, 208 .probe = inspur_wmi_probe, 209 .remove = inspur_wmi_remove, 210 }; 211 212 module_wmi_driver(inspur_wmi_driver); 213 214 MODULE_AUTHOR("Ai Chao <aichao@kylinos.cn>"); 215 MODULE_DESCRIPTION("Platform Profile Support for Inspur"); 216 MODULE_LICENSE("GPL"); 217