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, instance: 0, method_id: query_id, in: &input, out: &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(objp: 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(wdev: priv->wdev, query_id: INSPUR_WMI_SET_POWERMODE, |
110 | buffer: ret_code, insize: sizeof(ret_code), |
111 | outsize: 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(wdev: priv->wdev, query_id: INSPUR_WMI_GET_POWERMODE, |
144 | buffer: &ret_code, insize: sizeof(ret_code), |
145 | outsize: 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(dev: &wdev->dev, size: sizeof(*priv), GFP_KERNEL); |
174 | if (!priv) |
175 | return -ENOMEM; |
176 | |
177 | priv->wdev = wdev; |
178 | dev_set_drvdata(dev: &wdev->dev, data: priv); |
179 | |
180 | priv->handler.profile_get = inspur_platform_profile_get; |
181 | priv->handler.profile_set = inspur_platform_profile_set; |
182 | |
183 | set_bit(nr: PLATFORM_PROFILE_LOW_POWER, addr: priv->handler.choices); |
184 | set_bit(nr: PLATFORM_PROFILE_BALANCED, addr: priv->handler.choices); |
185 | set_bit(nr: PLATFORM_PROFILE_PERFORMANCE, addr: priv->handler.choices); |
186 | |
187 | return platform_profile_register(pprof: &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 | |