diff --git a/Documentation/ABI/testing/debugfs-msi-wmi-platform b/Documentation/ABI/testing/debugfs-msi-wmi-platform new file mode 100644 index 000000000000..71f9992168d8 --- /dev/null +++ b/Documentation/ABI/testing/debugfs-msi-wmi-platform @@ -0,0 +1,14 @@ +What: /sys/kernel/debug/msi-wmi-platform-/* +Date: April 2024 +KernelVersion: 6.10 +Contact: Armin Wolf +Description: + This file allows to execute the associated WMI method with the same name. + + To start the execution, write a buffer containing the method arguments + at file offset 0. Partial writes or writes at a different offset are not + supported. + + The buffer returned by the WMI method can then be read from the file. + + See Documentation/wmi/devices/msi-wmi-platform.rst for details. diff --git a/Documentation/wmi/devices/msi-wmi-platform.rst b/Documentation/wmi/devices/msi-wmi-platform.rst new file mode 100644 index 000000000000..29b1b2e6d42c --- /dev/null +++ b/Documentation/wmi/devices/msi-wmi-platform.rst @@ -0,0 +1,194 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +=================================================== +MSI WMI Platform Features driver (msi-wmi-platform) +=================================================== + +Introduction +============ + +Many MSI notebooks support various features like reading fan sensors. This features are controlled +by the embedded controller, with the ACPI firmware exposing a standard ACPI WMI interface on top +of the embedded controller interface. + +WMI interface description +========================= + +The WMI interface description can be decoded from the embedded binary MOF (bmof) +data using the `bmfdec `_ utility: + +:: + + [WMI, Locale("MS\0x409"), + Description("This class contains the definition of the package used in other classes"), + guid("{ABBC0F60-8EA1-11d1-00A0-C90629100000}")] + class Package { + [WmiDataId(1), read, write, Description("16 bytes of data")] uint8 Bytes[16]; + }; + + [WMI, Locale("MS\0x409"), + Description("This class contains the definition of the package used in other classes"), + guid("{ABBC0F63-8EA1-11d1-00A0-C90629100000}")] + class Package_32 { + [WmiDataId(1), read, write, Description("32 bytes of data")] uint8 Bytes[32]; + }; + + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\0x409"), + Description("Class used to operate methods on a package"), + guid("{ABBC0F6E-8EA1-11d1-00A0-C90629100000}")] + class MSI_ACPI { + [key, read] string InstanceName; + [read] boolean Active; + + [WmiMethodId(1), Implemented, read, write, Description("Return the contents of a package")] + void GetPackage([out, id(0)] Package Data); + + [WmiMethodId(2), Implemented, read, write, Description("Set the contents of a package")] + void SetPackage([in, id(0)] Package Data); + + [WmiMethodId(3), Implemented, read, write, Description("Return the contents of a package")] + void Get_EC([out, id(0)] Package_32 Data); + + [WmiMethodId(4), Implemented, read, write, Description("Set the contents of a package")] + void Set_EC([in, id(0)] Package_32 Data); + + [WmiMethodId(5), Implemented, read, write, Description("Return the contents of a package")] + void Get_BIOS([in, out, id(0)] Package_32 Data); + + [WmiMethodId(6), Implemented, read, write, Description("Set the contents of a package")] + void Set_BIOS([in, out, id(0)] Package_32 Data); + + [WmiMethodId(7), Implemented, read, write, Description("Return the contents of a package")] + void Get_SMBUS([in, out, id(0)] Package_32 Data); + + [WmiMethodId(8), Implemented, read, write, Description("Set the contents of a package")] + void Set_SMBUS([in, out, id(0)] Package_32 Data); + + [WmiMethodId(9), Implemented, read, write, Description("Return the contents of a package")] + void Get_MasterBattery([in, out, id(0)] Package_32 Data); + + [WmiMethodId(10), Implemented, read, write, Description("Set the contents of a package")] + void Set_MasterBattery([in, out, id(0)] Package_32 Data); + + [WmiMethodId(11), Implemented, read, write, Description("Return the contents of a package")] + void Get_SlaveBattery([in, out, id(0)] Package_32 Data); + + [WmiMethodId(12), Implemented, read, write, Description("Set the contents of a package")] + void Set_SlaveBattery([in, out, id(0)] Package_32 Data); + + [WmiMethodId(13), Implemented, read, write, Description("Return the contents of a package")] + void Get_Temperature([in, out, id(0)] Package_32 Data); + + [WmiMethodId(14), Implemented, read, write, Description("Set the contents of a package")] + void Set_Temperature([in, out, id(0)] Package_32 Data); + + [WmiMethodId(15), Implemented, read, write, Description("Return the contents of a package")] + void Get_Thermal([in, out, id(0)] Package_32 Data); + + [WmiMethodId(16), Implemented, read, write, Description("Set the contents of a package")] + void Set_Thermal([in, out, id(0)] Package_32 Data); + + [WmiMethodId(17), Implemented, read, write, Description("Return the contents of a package")] + void Get_Fan([in, out, id(0)] Package_32 Data); + + [WmiMethodId(18), Implemented, read, write, Description("Set the contents of a package")] + void Set_Fan([in, out, id(0)] Package_32 Data); + + [WmiMethodId(19), Implemented, read, write, Description("Return the contents of a package")] + void Get_Device([in, out, id(0)] Package_32 Data); + + [WmiMethodId(20), Implemented, read, write, Description("Set the contents of a package")] + void Set_Device([in, out, id(0)] Package_32 Data); + + [WmiMethodId(21), Implemented, read, write, Description("Return the contents of a package")] + void Get_Power([in, out, id(0)] Package_32 Data); + + [WmiMethodId(22), Implemented, read, write, Description("Set the contents of a package")] + void Set_Power([in, out, id(0)] Package_32 Data); + + [WmiMethodId(23), Implemented, read, write, Description("Return the contents of a package")] + void Get_Debug([in, out, id(0)] Package_32 Data); + + [WmiMethodId(24), Implemented, read, write, Description("Set the contents of a package")] + void Set_Debug([in, out, id(0)] Package_32 Data); + + [WmiMethodId(25), Implemented, read, write, Description("Return the contents of a package")] + void Get_AP([in, out, id(0)] Package_32 Data); + + [WmiMethodId(26), Implemented, read, write, Description("Set the contents of a package")] + void Set_AP([in, out, id(0)] Package_32 Data); + + [WmiMethodId(27), Implemented, read, write, Description("Return the contents of a package")] + void Get_Data([in, out, id(0)] Package_32 Data); + + [WmiMethodId(28), Implemented, read, write, Description("Set the contents of a package")] + void Set_Data([in, out, id(0)] Package_32 Data); + + [WmiMethodId(29), Implemented, read, write, Description("Return the contents of a package")] + void Get_WMI([out, id(0)] Package_32 Data); + }; + +Due to a peculiarity in how Windows handles the ``CreateByteField()`` ACPI operator (errors only +happen when a invalid byte field is ultimately accessed), all methods require a 32 byte input +buffer, even if the Binay MOF says otherwise. + +The input buffer contains a single byte to select the subfeature to be accessed and 31 bytes of +input data, the meaning of which depends on the subfeature being accessed. + +The output buffer contains a singe byte which signals success or failure (``0x00`` on failure) +and 31 bytes of output data, the meaning if which depends on the subfeature being accessed. + +WMI method Get_EC() +------------------- + +Returns embedded controller information, the selected subfeature does not matter. The output +data contains a flag byte and a 28 byte controller firmware version string. + +The first 4 bits of the flag byte contain the minor version of the embedded controller interface, +with the next 2 bits containing the major version of the embedded controller interface. + +The 7th bit signals if the embedded controller page chaged (exact meaning is unknown), and the +last bit signals if the platform is a Tigerlake platform. + +The MSI software seems to only use this interface when the last bit is set. + +WMI method Get_Fan() +-------------------- + +Fan speed sensors can be accessed by selecting subfeature ``0x00``. The output data contains +up to four 16-bit fan speed readings in big-endian format. Most machines do not support all +four fan speed sensors, so the remaining reading are hardcoded to ``0x0000``. + +The fan RPM readings can be calculated with the following formula: + + RPM = 480000 / + +If the fan speed reading is zero, then the fan RPM is zero too. + +WMI method Get_WMI() +-------------------- + +Returns the version of the ACPI WMI interface, the selected subfeature does not matter. +The output data contains two bytes, the first one contains the major version and the last one +contains the minor revision of the ACPI WMI interface. + +The MSI software seems to only use this interface when the major version is greater than two. + +Reverse-Engineering the MSI WMI Platform interface +================================================== + +.. warning:: Randomly poking the embedded controller interface can potentially cause damage + to the machine and other unwanted side effects, please be careful. + +The underlying embedded controller interface is used by the ``msi-ec`` driver, and it seems +that many methods just copy a part of the embedded controller memory into the output buffer. + +This means that the remaining WMI methods can be reverse-engineered by looking which part of +the embedded controller memory is accessed by the ACPI AML code. The driver also supports a +debugfs interface for directly executing WMI methods. Additionally, any safety checks regarding +unsupported hardware can be disabled by loading the module with ``force=true``. + +More information about the MSI embedded controller interface can be found at the +`msi-ec project `_. + +Special thanks go to github user `glpnk` for showing how to decode the fan speed readings. diff --git a/MAINTAINERS b/MAINTAINERS index 3fb0fa67576d..846187625552 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -15041,6 +15041,14 @@ L: platform-driver-x86@vger.kernel.org S: Orphan F: drivers/platform/x86/msi-wmi.c +MSI WMI PLATFORM FEATURES +M: Armin Wolf +L: platform-driver-x86@vger.kernel.org +S: Maintained +F: Documentation/ABI/testing/debugfs-msi-wmi-platform +F: Documentation/wmi/devices/msi-wmi-platform.rst +F: drivers/platform/x86/msi-wmi-platform.c + MSI001 MEDIA DRIVER L: linux-media@vger.kernel.org S: Orphan diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 168b57df0a6a..b744f18bbfa7 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -698,6 +698,17 @@ config MSI_WMI To compile this driver as a module, choose M here: the module will be called msi-wmi. +config MSI_WMI_PLATFORM + tristate "MSI WMI Platform features" + depends on ACPI_WMI + depends on HWMON + help + Say Y here if you want to have support for WMI-based platform features + like fan sensor access on MSI machines. + + To compile this driver as a module, choose M here: the module will + be called msi-wmi-platform. + config XO15_EBOOK tristate "OLPC XO-1.5 ebook switch" depends on OLPC || COMPILE_TEST diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 8076bf3a7e83..26b8af55738b 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -78,6 +78,7 @@ obj-$(CONFIG_ACPI_QUICKSTART) += quickstart.o obj-$(CONFIG_MSI_EC) += msi-ec.o obj-$(CONFIG_MSI_LAPTOP) += msi-laptop.o obj-$(CONFIG_MSI_WMI) += msi-wmi.o +obj-$(CONFIG_MSI_WMI_PLATFORM) += msi-wmi-platform.o # OLPC obj-$(CONFIG_XO15_EBOOK) += xo15-ebook.o diff --git a/drivers/platform/x86/msi-wmi-platform.c b/drivers/platform/x86/msi-wmi-platform.c new file mode 100644 index 000000000000..436fb91a47db --- /dev/null +++ b/drivers/platform/x86/msi-wmi-platform.c @@ -0,0 +1,428 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Linux driver for WMI platform features on MSI notebooks. + * + * Copyright (C) 2024 Armin Wolf + */ + +#define pr_format(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define DRIVER_NAME "msi-wmi-platform" + +#define MSI_PLATFORM_GUID "ABBC0F6E-8EA1-11d1-00A0-C90629100000" + +#define MSI_WMI_PLATFORM_INTERFACE_VERSION 2 + +#define MSI_PLATFORM_WMI_MAJOR_OFFSET 1 +#define MSI_PLATFORM_WMI_MINOR_OFFSET 2 + +#define MSI_PLATFORM_EC_FLAGS_OFFSET 1 +#define MSI_PLATFORM_EC_MINOR_MASK GENMASK(3, 0) +#define MSI_PLATFORM_EC_MAJOR_MASK GENMASK(5, 4) +#define MSI_PLATFORM_EC_CHANGED_PAGE BIT(6) +#define MSI_PLATFORM_EC_IS_TIGERLAKE BIT(7) +#define MSI_PLATFORM_EC_VERSION_OFFSET 2 + +static bool force; +module_param_unsafe(force, bool, 0); +MODULE_PARM_DESC(force, "Force loading without checking for supported WMI interface versions"); + +enum msi_wmi_platform_method { + MSI_PLATFORM_GET_PACKAGE = 0x01, + MSI_PLATFORM_SET_PACKAGE = 0x02, + MSI_PLATFORM_GET_EC = 0x03, + MSI_PLATFORM_SET_EC = 0x04, + MSI_PLATFORM_GET_BIOS = 0x05, + MSI_PLATFORM_SET_BIOS = 0x06, + MSI_PLATFORM_GET_SMBUS = 0x07, + MSI_PLATFORM_SET_SMBUS = 0x08, + MSI_PLATFORM_GET_MASTER_BATTERY = 0x09, + MSI_PLATFORM_SET_MASTER_BATTERY = 0x0a, + MSI_PLATFORM_GET_SLAVE_BATTERY = 0x0b, + MSI_PLATFORM_SET_SLAVE_BATTERY = 0x0c, + MSI_PLATFORM_GET_TEMPERATURE = 0x0d, + MSI_PLATFORM_SET_TEMPERATURE = 0x0e, + MSI_PLATFORM_GET_THERMAL = 0x0f, + MSI_PLATFORM_SET_THERMAL = 0x10, + MSI_PLATFORM_GET_FAN = 0x11, + MSI_PLATFORM_SET_FAN = 0x12, + MSI_PLATFORM_GET_DEVICE = 0x13, + MSI_PLATFORM_SET_DEVICE = 0x14, + MSI_PLATFORM_GET_POWER = 0x15, + MSI_PLATFORM_SET_POWER = 0x16, + MSI_PLATFORM_GET_DEBUG = 0x17, + MSI_PLATFORM_SET_DEBUG = 0x18, + MSI_PLATFORM_GET_AP = 0x19, + MSI_PLATFORM_SET_AP = 0x1a, + MSI_PLATFORM_GET_DATA = 0x1b, + MSI_PLATFORM_SET_DATA = 0x1c, + MSI_PLATFORM_GET_WMI = 0x1d, +}; + +struct msi_wmi_platform_debugfs_data { + struct wmi_device *wdev; + enum msi_wmi_platform_method method; + struct rw_semaphore buffer_lock; /* Protects debugfs buffer */ + size_t length; + u8 buffer[32]; +}; + +static const char * const msi_wmi_platform_debugfs_names[] = { + "get_package", + "set_package", + "get_ec", + "set_ec", + "get_bios", + "set_bios", + "get_smbus", + "set_smbus", + "get_master_battery", + "set_master_battery", + "get_slave_battery", + "set_slave_battery", + "get_temperature", + "set_temperature", + "get_thermal", + "set_thermal", + "get_fan", + "set_fan", + "get_device", + "set_device", + "get_power", + "set_power", + "get_debug", + "set_debug", + "get_ap", + "set_ap", + "get_data", + "set_data", + "get_wmi" +}; + +static int msi_wmi_platform_parse_buffer(union acpi_object *obj, u8 *output, size_t length) +{ + if (obj->type != ACPI_TYPE_BUFFER) + return -ENOMSG; + + if (obj->buffer.length != length) + return -EPROTO; + + if (!obj->buffer.pointer[0]) + return -EIO; + + memcpy(output, obj->buffer.pointer, obj->buffer.length); + + return 0; +} + +static int 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) +{ + struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL }; + struct acpi_buffer in = { + .length = input_length, + .pointer = input + }; + union acpi_object *obj; + acpi_status status; + int ret; + + if (!input_length || !output_length) + return -EINVAL; + + status = wmidev_evaluate_method(wdev, 0x0, method, &in, &out); + if (ACPI_FAILURE(status)) + return -EIO; + + obj = out.pointer; + if (!obj) + return -ENODATA; + + ret = msi_wmi_platform_parse_buffer(obj, output, output_length); + kfree(obj); + + return ret; +} + +static umode_t msi_wmi_platform_is_visible(const void *drvdata, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + return 0444; +} + +static int msi_wmi_platform_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, + int channel, long *val) +{ + struct wmi_device *wdev = dev_get_drvdata(dev); + u8 input[32] = { 0 }; + u8 output[32]; + u16 data; + int ret; + + ret = msi_wmi_platform_query(wdev, MSI_PLATFORM_GET_FAN, input, sizeof(input), output, + sizeof(output)); + if (ret < 0) + return ret; + + data = get_unaligned_be16(&output[channel * 2 + 1]); + if (!data) + *val = 0; + else + *val = 480000 / data; + + return 0; +} + +static const struct hwmon_ops msi_wmi_platform_ops = { + .is_visible = msi_wmi_platform_is_visible, + .read = msi_wmi_platform_read, +}; + +static const struct hwmon_channel_info * const msi_wmi_platform_info[] = { + HWMON_CHANNEL_INFO(fan, + HWMON_F_INPUT, + HWMON_F_INPUT, + HWMON_F_INPUT, + HWMON_F_INPUT + ), + NULL +}; + +static const struct hwmon_chip_info msi_wmi_platform_chip_info = { + .ops = &msi_wmi_platform_ops, + .info = msi_wmi_platform_info, +}; + +static ssize_t msi_wmi_platform_write(struct file *fp, const char __user *input, size_t length, + loff_t *offset) +{ + struct seq_file *seq = fp->private_data; + struct msi_wmi_platform_debugfs_data *data = seq->private; + u8 payload[32] = { }; + ssize_t ret; + + /* Do not allow partial writes */ + if (*offset != 0) + return -EINVAL; + + /* Do not allow incomplete command buffers */ + if (length != data->length) + return -EINVAL; + + ret = simple_write_to_buffer(payload, sizeof(payload), offset, input, length); + if (ret < 0) + return ret; + + down_write(&data->buffer_lock); + ret = msi_wmi_platform_query(data->wdev, data->method, payload, data->length, data->buffer, + data->length); + up_write(&data->buffer_lock); + + if (ret < 0) + return ret; + + return length; +} + +static int msi_wmi_platform_show(struct seq_file *seq, void *p) +{ + struct msi_wmi_platform_debugfs_data *data = seq->private; + int ret; + + down_read(&data->buffer_lock); + ret = seq_write(seq, data->buffer, data->length); + up_read(&data->buffer_lock); + + return ret; +} + +static int msi_wmi_platform_open(struct inode *inode, struct file *fp) +{ + struct msi_wmi_platform_debugfs_data *data = inode->i_private; + + /* The seq_file uses the last byte of the buffer for detecting buffer overflows */ + return single_open_size(fp, msi_wmi_platform_show, data, data->length + 1); +} + +static const struct file_operations msi_wmi_platform_debugfs_fops = { + .owner = THIS_MODULE, + .open = msi_wmi_platform_open, + .read = seq_read, + .write = msi_wmi_platform_write, + .llseek = seq_lseek, + .release = single_release, +}; + +static void msi_wmi_platform_debugfs_remove(void *data) +{ + struct dentry *dir = data; + + debugfs_remove_recursive(dir); +} + +static void msi_wmi_platform_debugfs_add(struct wmi_device *wdev, struct dentry *dir, + const char *name, enum msi_wmi_platform_method method) +{ + struct msi_wmi_platform_debugfs_data *data; + struct dentry *entry; + + data = devm_kzalloc(&wdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return; + + data->wdev = wdev; + data->method = method; + init_rwsem(&data->buffer_lock); + + /* The ACPI firmware for now always requires a 32 byte input buffer due to + * a peculiarity in how Windows handles the CreateByteField() ACPI operator. + */ + data->length = 32; + + entry = debugfs_create_file(name, 0600, dir, data, &msi_wmi_platform_debugfs_fops); + if (IS_ERR(entry)) + devm_kfree(&wdev->dev, data); +} + +static void msi_wmi_platform_debugfs_init(struct wmi_device *wdev) +{ + struct dentry *dir; + char dir_name[64]; + int ret, method; + + scnprintf(dir_name, ARRAY_SIZE(dir_name), "%s-%s", DRIVER_NAME, dev_name(&wdev->dev)); + + dir = debugfs_create_dir(dir_name, NULL); + if (IS_ERR(dir)) + return; + + ret = devm_add_action_or_reset(&wdev->dev, msi_wmi_platform_debugfs_remove, dir); + if (ret < 0) + return; + + for (method = MSI_PLATFORM_GET_PACKAGE; method <= MSI_PLATFORM_GET_WMI; method++) + msi_wmi_platform_debugfs_add(wdev, dir, msi_wmi_platform_debugfs_names[method - 1], + method); +} + +static int msi_wmi_platform_hwmon_init(struct wmi_device *wdev) +{ + struct device *hdev; + + hdev = devm_hwmon_device_register_with_info(&wdev->dev, "msi_wmi_platform", wdev, + &msi_wmi_platform_chip_info, NULL); + + return PTR_ERR_OR_ZERO(hdev); +} + +static int msi_wmi_platform_ec_init(struct wmi_device *wdev) +{ + u8 input[32] = { 0 }; + u8 output[32]; + u8 flags; + int ret; + + ret = msi_wmi_platform_query(wdev, MSI_PLATFORM_GET_EC, input, sizeof(input), output, + sizeof(output)); + if (ret < 0) + return ret; + + flags = output[MSI_PLATFORM_EC_FLAGS_OFFSET]; + + dev_dbg(&wdev->dev, "EC RAM version %lu.%lu\n", + FIELD_GET(MSI_PLATFORM_EC_MAJOR_MASK, flags), + FIELD_GET(MSI_PLATFORM_EC_MINOR_MASK, flags)); + dev_dbg(&wdev->dev, "EC firmware version %.28s\n", + &output[MSI_PLATFORM_EC_VERSION_OFFSET]); + + if (!(flags & MSI_PLATFORM_EC_IS_TIGERLAKE)) { + if (!force) + return -ENODEV; + + dev_warn(&wdev->dev, "Loading on a non-Tigerlake platform\n"); + } + + return 0; +} + +static int msi_wmi_platform_init(struct wmi_device *wdev) +{ + u8 input[32] = { 0 }; + u8 output[32]; + int ret; + + ret = msi_wmi_platform_query(wdev, MSI_PLATFORM_GET_WMI, input, sizeof(input), output, + sizeof(output)); + if (ret < 0) + return ret; + + dev_dbg(&wdev->dev, "WMI interface version %u.%u\n", + output[MSI_PLATFORM_WMI_MAJOR_OFFSET], + output[MSI_PLATFORM_WMI_MINOR_OFFSET]); + + if (output[MSI_PLATFORM_WMI_MAJOR_OFFSET] != MSI_WMI_PLATFORM_INTERFACE_VERSION) { + if (!force) + return -ENODEV; + + dev_warn(&wdev->dev, "Loading despite unsupported WMI interface version (%u.%u)\n", + output[MSI_PLATFORM_WMI_MAJOR_OFFSET], + output[MSI_PLATFORM_WMI_MINOR_OFFSET]); + } + + return 0; +} + +static int msi_wmi_platform_probe(struct wmi_device *wdev, const void *context) +{ + int ret; + + ret = msi_wmi_platform_init(wdev); + if (ret < 0) + return ret; + + ret = msi_wmi_platform_ec_init(wdev); + if (ret < 0) + return ret; + + msi_wmi_platform_debugfs_init(wdev); + + return msi_wmi_platform_hwmon_init(wdev); +} + +static const struct wmi_device_id msi_wmi_platform_id_table[] = { + { MSI_PLATFORM_GUID, NULL }, + { } +}; +MODULE_DEVICE_TABLE(wmi, msi_wmi_platform_id_table); + +static struct wmi_driver msi_wmi_platform_driver = { + .driver = { + .name = DRIVER_NAME, + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, + .id_table = msi_wmi_platform_id_table, + .probe = msi_wmi_platform_probe, + .no_singleton = true, +}; +module_wmi_driver(msi_wmi_platform_driver); + +MODULE_AUTHOR("Armin Wolf "); +MODULE_DESCRIPTION("MSI WMI platform features"); +MODULE_LICENSE("GPL");