Commit | Line | Data |
---|---|---|
94ace9ed AC |
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, | |
c347fd4f | 210 | .no_singleton = true, |
94ace9ed AC |
211 | }; |
212 | ||
213 | module_wmi_driver(inspur_wmi_driver); | |
214 | ||
215 | MODULE_AUTHOR("Ai Chao <aichao@kylinos.cn>"); | |
216 | MODULE_DESCRIPTION("Platform Profile Support for Inspur"); | |
217 | MODULE_LICENSE("GPL"); |