1 // SPDX-License-Identifier: GPL-2.0
2 /*
3 * Inspur WMI Platform Profile
4 *
5 * Copyright (C) 2018 Ai Chao <[email protected]>
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
inspur_wmi_perform_query(struct wmi_device * wdev,enum inspur_wmi_method_ids query_id,void * buffer,size_t insize,size_t outsize)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 */
inspur_platform_profile_set(struct device * dev,enum platform_profile_option profile)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 */
inspur_platform_profile_get(struct device * dev,enum platform_profile_option * profile)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
inspur_platform_profile_probe(void * drvdata,unsigned long * choices)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
inspur_wmi_probe(struct wmi_device * wdev,const void * context)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 <[email protected]>");
219 MODULE_DESCRIPTION("Platform Profile Support for Inspur");
220 MODULE_LICENSE("GPL");
221