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
16enum 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 */
27enum inspur_tmp_profile {
28	INSPUR_TMP_PROFILE_BALANCE	= 0,
29	INSPUR_TMP_PROFILE_PERFORMANCE	= 1,
30	INSPUR_TMP_PROFILE_POWERSAVE	= 2,
31};
32
33struct inspur_wmi_priv {
34	struct wmi_device *wdev;
35	struct platform_profile_handler handler;
36};
37
38static 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
68out_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 */
87static 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 */
135static 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
169static 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
190static void inspur_wmi_remove(struct wmi_device *wdev)
191{
192	platform_profile_remove();
193}
194
195static const struct wmi_device_id inspur_wmi_id_table[] = {
196	{ .guid_string = WMI_INSPUR_POWERMODE_BIOS_GUID },
197	{  }
198};
199
200MODULE_DEVICE_TABLE(wmi, inspur_wmi_id_table);
201
202static 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
212module_wmi_driver(inspur_wmi_driver);
213
214MODULE_AUTHOR("Ai Chao <aichao@kylinos.cn>");
215MODULE_DESCRIPTION("Platform Profile Support for Inspur");
216MODULE_LICENSE("GPL");
217