printf: Remove unused 'bprintf'
[drm/drm-misc.git] / drivers / platform / x86 / msi-wmi-platform.c
blob9b5c7f8c79b0ddd410f90593f0d0022f38d6d147
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Linux driver for WMI platform features on MSI notebooks.
5 * Copyright (C) 2024 Armin Wolf <W_Armin@gmx.de>
6 */
8 #define pr_format(fmt) KBUILD_MODNAME ": " fmt
10 #include <linux/acpi.h>
11 #include <linux/bits.h>
12 #include <linux/bitfield.h>
13 #include <linux/debugfs.h>
14 #include <linux/device.h>
15 #include <linux/device/driver.h>
16 #include <linux/errno.h>
17 #include <linux/hwmon.h>
18 #include <linux/kernel.h>
19 #include <linux/module.h>
20 #include <linux/printk.h>
21 #include <linux/rwsem.h>
22 #include <linux/types.h>
23 #include <linux/wmi.h>
25 #include <linux/unaligned.h>
27 #define DRIVER_NAME "msi-wmi-platform"
29 #define MSI_PLATFORM_GUID "ABBC0F6E-8EA1-11d1-00A0-C90629100000"
31 #define MSI_WMI_PLATFORM_INTERFACE_VERSION 2
33 #define MSI_PLATFORM_WMI_MAJOR_OFFSET 1
34 #define MSI_PLATFORM_WMI_MINOR_OFFSET 2
36 #define MSI_PLATFORM_EC_FLAGS_OFFSET 1
37 #define MSI_PLATFORM_EC_MINOR_MASK GENMASK(3, 0)
38 #define MSI_PLATFORM_EC_MAJOR_MASK GENMASK(5, 4)
39 #define MSI_PLATFORM_EC_CHANGED_PAGE BIT(6)
40 #define MSI_PLATFORM_EC_IS_TIGERLAKE BIT(7)
41 #define MSI_PLATFORM_EC_VERSION_OFFSET 2
43 static bool force;
44 module_param_unsafe(force, bool, 0);
45 MODULE_PARM_DESC(force, "Force loading without checking for supported WMI interface versions");
47 enum msi_wmi_platform_method {
48 MSI_PLATFORM_GET_PACKAGE = 0x01,
49 MSI_PLATFORM_SET_PACKAGE = 0x02,
50 MSI_PLATFORM_GET_EC = 0x03,
51 MSI_PLATFORM_SET_EC = 0x04,
52 MSI_PLATFORM_GET_BIOS = 0x05,
53 MSI_PLATFORM_SET_BIOS = 0x06,
54 MSI_PLATFORM_GET_SMBUS = 0x07,
55 MSI_PLATFORM_SET_SMBUS = 0x08,
56 MSI_PLATFORM_GET_MASTER_BATTERY = 0x09,
57 MSI_PLATFORM_SET_MASTER_BATTERY = 0x0a,
58 MSI_PLATFORM_GET_SLAVE_BATTERY = 0x0b,
59 MSI_PLATFORM_SET_SLAVE_BATTERY = 0x0c,
60 MSI_PLATFORM_GET_TEMPERATURE = 0x0d,
61 MSI_PLATFORM_SET_TEMPERATURE = 0x0e,
62 MSI_PLATFORM_GET_THERMAL = 0x0f,
63 MSI_PLATFORM_SET_THERMAL = 0x10,
64 MSI_PLATFORM_GET_FAN = 0x11,
65 MSI_PLATFORM_SET_FAN = 0x12,
66 MSI_PLATFORM_GET_DEVICE = 0x13,
67 MSI_PLATFORM_SET_DEVICE = 0x14,
68 MSI_PLATFORM_GET_POWER = 0x15,
69 MSI_PLATFORM_SET_POWER = 0x16,
70 MSI_PLATFORM_GET_DEBUG = 0x17,
71 MSI_PLATFORM_SET_DEBUG = 0x18,
72 MSI_PLATFORM_GET_AP = 0x19,
73 MSI_PLATFORM_SET_AP = 0x1a,
74 MSI_PLATFORM_GET_DATA = 0x1b,
75 MSI_PLATFORM_SET_DATA = 0x1c,
76 MSI_PLATFORM_GET_WMI = 0x1d,
79 struct msi_wmi_platform_debugfs_data {
80 struct wmi_device *wdev;
81 enum msi_wmi_platform_method method;
82 struct rw_semaphore buffer_lock; /* Protects debugfs buffer */
83 size_t length;
84 u8 buffer[32];
87 static const char * const msi_wmi_platform_debugfs_names[] = {
88 "get_package",
89 "set_package",
90 "get_ec",
91 "set_ec",
92 "get_bios",
93 "set_bios",
94 "get_smbus",
95 "set_smbus",
96 "get_master_battery",
97 "set_master_battery",
98 "get_slave_battery",
99 "set_slave_battery",
100 "get_temperature",
101 "set_temperature",
102 "get_thermal",
103 "set_thermal",
104 "get_fan",
105 "set_fan",
106 "get_device",
107 "set_device",
108 "get_power",
109 "set_power",
110 "get_debug",
111 "set_debug",
112 "get_ap",
113 "set_ap",
114 "get_data",
115 "set_data",
116 "get_wmi"
119 static int msi_wmi_platform_parse_buffer(union acpi_object *obj, u8 *output, size_t length)
121 if (obj->type != ACPI_TYPE_BUFFER)
122 return -ENOMSG;
124 if (obj->buffer.length != length)
125 return -EPROTO;
127 if (!obj->buffer.pointer[0])
128 return -EIO;
130 memcpy(output, obj->buffer.pointer, obj->buffer.length);
132 return 0;
135 static int msi_wmi_platform_query(struct wmi_device *wdev, enum msi_wmi_platform_method method,
136 u8 *input, size_t input_length, u8 *output, size_t output_length)
138 struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
139 struct acpi_buffer in = {
140 .length = input_length,
141 .pointer = input
143 union acpi_object *obj;
144 acpi_status status;
145 int ret;
147 if (!input_length || !output_length)
148 return -EINVAL;
150 status = wmidev_evaluate_method(wdev, 0x0, method, &in, &out);
151 if (ACPI_FAILURE(status))
152 return -EIO;
154 obj = out.pointer;
155 if (!obj)
156 return -ENODATA;
158 ret = msi_wmi_platform_parse_buffer(obj, output, output_length);
159 kfree(obj);
161 return ret;
164 static umode_t msi_wmi_platform_is_visible(const void *drvdata, enum hwmon_sensor_types type,
165 u32 attr, int channel)
167 return 0444;
170 static int msi_wmi_platform_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
171 int channel, long *val)
173 struct wmi_device *wdev = dev_get_drvdata(dev);
174 u8 input[32] = { 0 };
175 u8 output[32];
176 u16 data;
177 int ret;
179 ret = msi_wmi_platform_query(wdev, MSI_PLATFORM_GET_FAN, input, sizeof(input), output,
180 sizeof(output));
181 if (ret < 0)
182 return ret;
184 data = get_unaligned_be16(&output[channel * 2 + 1]);
185 if (!data)
186 *val = 0;
187 else
188 *val = 480000 / data;
190 return 0;
193 static const struct hwmon_ops msi_wmi_platform_ops = {
194 .is_visible = msi_wmi_platform_is_visible,
195 .read = msi_wmi_platform_read,
198 static const struct hwmon_channel_info * const msi_wmi_platform_info[] = {
199 HWMON_CHANNEL_INFO(fan,
200 HWMON_F_INPUT,
201 HWMON_F_INPUT,
202 HWMON_F_INPUT,
203 HWMON_F_INPUT
205 NULL
208 static const struct hwmon_chip_info msi_wmi_platform_chip_info = {
209 .ops = &msi_wmi_platform_ops,
210 .info = msi_wmi_platform_info,
213 static ssize_t msi_wmi_platform_write(struct file *fp, const char __user *input, size_t length,
214 loff_t *offset)
216 struct seq_file *seq = fp->private_data;
217 struct msi_wmi_platform_debugfs_data *data = seq->private;
218 u8 payload[32] = { };
219 ssize_t ret;
221 /* Do not allow partial writes */
222 if (*offset != 0)
223 return -EINVAL;
225 /* Do not allow incomplete command buffers */
226 if (length != data->length)
227 return -EINVAL;
229 ret = simple_write_to_buffer(payload, sizeof(payload), offset, input, length);
230 if (ret < 0)
231 return ret;
233 down_write(&data->buffer_lock);
234 ret = msi_wmi_platform_query(data->wdev, data->method, payload, data->length, data->buffer,
235 data->length);
236 up_write(&data->buffer_lock);
238 if (ret < 0)
239 return ret;
241 return length;
244 static int msi_wmi_platform_show(struct seq_file *seq, void *p)
246 struct msi_wmi_platform_debugfs_data *data = seq->private;
247 int ret;
249 down_read(&data->buffer_lock);
250 ret = seq_write(seq, data->buffer, data->length);
251 up_read(&data->buffer_lock);
253 return ret;
256 static int msi_wmi_platform_open(struct inode *inode, struct file *fp)
258 struct msi_wmi_platform_debugfs_data *data = inode->i_private;
260 /* The seq_file uses the last byte of the buffer for detecting buffer overflows */
261 return single_open_size(fp, msi_wmi_platform_show, data, data->length + 1);
264 static const struct file_operations msi_wmi_platform_debugfs_fops = {
265 .owner = THIS_MODULE,
266 .open = msi_wmi_platform_open,
267 .read = seq_read,
268 .write = msi_wmi_platform_write,
269 .llseek = seq_lseek,
270 .release = single_release,
273 static void msi_wmi_platform_debugfs_remove(void *data)
275 struct dentry *dir = data;
277 debugfs_remove_recursive(dir);
280 static void msi_wmi_platform_debugfs_add(struct wmi_device *wdev, struct dentry *dir,
281 const char *name, enum msi_wmi_platform_method method)
283 struct msi_wmi_platform_debugfs_data *data;
284 struct dentry *entry;
286 data = devm_kzalloc(&wdev->dev, sizeof(*data), GFP_KERNEL);
287 if (!data)
288 return;
290 data->wdev = wdev;
291 data->method = method;
292 init_rwsem(&data->buffer_lock);
294 /* The ACPI firmware for now always requires a 32 byte input buffer due to
295 * a peculiarity in how Windows handles the CreateByteField() ACPI operator.
297 data->length = 32;
299 entry = debugfs_create_file(name, 0600, dir, data, &msi_wmi_platform_debugfs_fops);
300 if (IS_ERR(entry))
301 devm_kfree(&wdev->dev, data);
304 static void msi_wmi_platform_debugfs_init(struct wmi_device *wdev)
306 struct dentry *dir;
307 char dir_name[64];
308 int ret, method;
310 scnprintf(dir_name, ARRAY_SIZE(dir_name), "%s-%s", DRIVER_NAME, dev_name(&wdev->dev));
312 dir = debugfs_create_dir(dir_name, NULL);
313 if (IS_ERR(dir))
314 return;
316 ret = devm_add_action_or_reset(&wdev->dev, msi_wmi_platform_debugfs_remove, dir);
317 if (ret < 0)
318 return;
320 for (method = MSI_PLATFORM_GET_PACKAGE; method <= MSI_PLATFORM_GET_WMI; method++)
321 msi_wmi_platform_debugfs_add(wdev, dir, msi_wmi_platform_debugfs_names[method - 1],
322 method);
325 static int msi_wmi_platform_hwmon_init(struct wmi_device *wdev)
327 struct device *hdev;
329 hdev = devm_hwmon_device_register_with_info(&wdev->dev, "msi_wmi_platform", wdev,
330 &msi_wmi_platform_chip_info, NULL);
332 return PTR_ERR_OR_ZERO(hdev);
335 static int msi_wmi_platform_ec_init(struct wmi_device *wdev)
337 u8 input[32] = { 0 };
338 u8 output[32];
339 u8 flags;
340 int ret;
342 ret = msi_wmi_platform_query(wdev, MSI_PLATFORM_GET_EC, input, sizeof(input), output,
343 sizeof(output));
344 if (ret < 0)
345 return ret;
347 flags = output[MSI_PLATFORM_EC_FLAGS_OFFSET];
349 dev_dbg(&wdev->dev, "EC RAM version %lu.%lu\n",
350 FIELD_GET(MSI_PLATFORM_EC_MAJOR_MASK, flags),
351 FIELD_GET(MSI_PLATFORM_EC_MINOR_MASK, flags));
352 dev_dbg(&wdev->dev, "EC firmware version %.28s\n",
353 &output[MSI_PLATFORM_EC_VERSION_OFFSET]);
355 if (!(flags & MSI_PLATFORM_EC_IS_TIGERLAKE)) {
356 if (!force)
357 return -ENODEV;
359 dev_warn(&wdev->dev, "Loading on a non-Tigerlake platform\n");
362 return 0;
365 static int msi_wmi_platform_init(struct wmi_device *wdev)
367 u8 input[32] = { 0 };
368 u8 output[32];
369 int ret;
371 ret = msi_wmi_platform_query(wdev, MSI_PLATFORM_GET_WMI, input, sizeof(input), output,
372 sizeof(output));
373 if (ret < 0)
374 return ret;
376 dev_dbg(&wdev->dev, "WMI interface version %u.%u\n",
377 output[MSI_PLATFORM_WMI_MAJOR_OFFSET],
378 output[MSI_PLATFORM_WMI_MINOR_OFFSET]);
380 if (output[MSI_PLATFORM_WMI_MAJOR_OFFSET] != MSI_WMI_PLATFORM_INTERFACE_VERSION) {
381 if (!force)
382 return -ENODEV;
384 dev_warn(&wdev->dev, "Loading despite unsupported WMI interface version (%u.%u)\n",
385 output[MSI_PLATFORM_WMI_MAJOR_OFFSET],
386 output[MSI_PLATFORM_WMI_MINOR_OFFSET]);
389 return 0;
392 static int msi_wmi_platform_probe(struct wmi_device *wdev, const void *context)
394 int ret;
396 ret = msi_wmi_platform_init(wdev);
397 if (ret < 0)
398 return ret;
400 ret = msi_wmi_platform_ec_init(wdev);
401 if (ret < 0)
402 return ret;
404 msi_wmi_platform_debugfs_init(wdev);
406 return msi_wmi_platform_hwmon_init(wdev);
409 static const struct wmi_device_id msi_wmi_platform_id_table[] = {
410 { MSI_PLATFORM_GUID, NULL },
413 MODULE_DEVICE_TABLE(wmi, msi_wmi_platform_id_table);
415 static struct wmi_driver msi_wmi_platform_driver = {
416 .driver = {
417 .name = DRIVER_NAME,
418 .probe_type = PROBE_PREFER_ASYNCHRONOUS,
420 .id_table = msi_wmi_platform_id_table,
421 .probe = msi_wmi_platform_probe,
422 .no_singleton = true,
424 module_wmi_driver(msi_wmi_platform_driver);
426 MODULE_AUTHOR("Armin Wolf <W_Armin@gmx.de>");
427 MODULE_DESCRIPTION("MSI WMI platform features");
428 MODULE_LICENSE("GPL");