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