This driver implements support for various Fn keys (like Cut) and Xiaomi specific AI button. Signed-off-by: Gladyshev Ilya <foxido@xxxxxxxxxx> --- Changes from v1: - Use sparse-keymap instead of manual matching - Fix GUID case so it actually autoloads - Other fixes from v1 review Link to v1: https://lore.kernel.org/platform-driver-x86/20250727223516.29244-1-foxido@xxxxxxxxxx/ --- MAINTAINERS | 6 ++ drivers/platform/x86/Kconfig | 11 +++ drivers/platform/x86/Makefile | 1 + drivers/platform/x86/redmi-wmi.c | 129 +++++++++++++++++++++++++++++++ 4 files changed, 147 insertions(+) create mode 100644 drivers/platform/x86/redmi-wmi.c diff --git a/MAINTAINERS b/MAINTAINERS index c0b444e5fd5a..eb25fb10e751 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -20965,6 +20965,12 @@ S: Maintained T: git https://github.com/pkshih/rtw.git F: drivers/net/wireless/realtek/rtw89/ +REDMIBOOK WMI DRIVERS +M: Gladyshev Ilya <foxido@xxxxxxxxxx> +L: platform-driver-x86@xxxxxxxxxxxxxxx +S: Maintained +F: drivers/platform/x86/redmi-wmi.c + REDPINE WIRELESS DRIVER L: linux-wireless@xxxxxxxxxxxxxxx S: Orphan diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index e5cbd58a99f3..3c570cb398d3 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -109,6 +109,17 @@ config XIAOMI_WMI To compile this driver as a module, choose M here: the module will be called xiaomi-wmi. +config REDMI_WMI + tristate "Redmibook WMI key driver" + depends on ACPI_WMI + depends on INPUT + help + Say Y here if you want support for WMI-based hotkey events on + Xiaomi Redmibook devices. + + To compile this driver as a module, choose M here: the module will + be called redmi-wmi. + config GIGABYTE_WMI tristate "Gigabyte WMI temperature driver" depends on ACPI_WMI diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index bea87a85ae75..406dd0807ba7 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -13,6 +13,7 @@ obj-$(CONFIG_HUAWEI_WMI) += huawei-wmi.o obj-$(CONFIG_MXM_WMI) += mxm-wmi.o obj-$(CONFIG_NVIDIA_WMI_EC_BACKLIGHT) += nvidia-wmi-ec-backlight.o obj-$(CONFIG_XIAOMI_WMI) += xiaomi-wmi.o +obj-$(CONFIG_REDMI_WMI) += redmi-wmi.o obj-$(CONFIG_GIGABYTE_WMI) += gigabyte-wmi.o # Acer diff --git a/drivers/platform/x86/redmi-wmi.c b/drivers/platform/x86/redmi-wmi.c new file mode 100644 index 000000000000..732688fb94e0 --- /dev/null +++ b/drivers/platform/x86/redmi-wmi.c @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: GPL-2.0 +/* WMI driver for Xiaomi Redmibooks */ + +#include <linux/acpi.h> +#include <linux/device.h> +#include <linux/input.h> +#include <linux/input/sparse-keymap.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/unaligned.h> +#include <linux/wmi.h> + +#include <uapi/linux/input-event-codes.h> + +#define WMI_REDMIBOOK_KEYBOARD_EVENT_GUID "46C93E13-EE9B-4262-8488-563BCA757FEF" + +#define AI_KEY_VALUE_MASK 0x00000100 + +static const struct key_entry redmi_wmi_keymap[] = { + {KE_KEY, 0x00000201, {KEY_SELECTIVE_SCREENSHOT}}, + {KE_KEY, 0x00000301, {KEY_ALL_APPLICATIONS}}, + {KE_KEY, 0x00001b01, {KEY_SETUP}}, + + /* AI button has code for each position */ + {KE_KEY, 0x00011801, {KEY_ASSISTANT}}, + {KE_KEY, 0x00011901, {KEY_ASSISTANT}}, + + /* Keyboard backlight */ + {KE_IGNORE, 0x00000501, {}}, + {KE_IGNORE, 0x00800501, {}}, + {KE_IGNORE, 0x00050501, {}}, + {KE_IGNORE, 0x000a0501, {}}, + + {KE_END} +}; + +struct redmi_wmi { + struct input_dev *input_dev; + /* Protects the key event sequence */ + struct mutex key_lock; +}; + +static int redmi_wmi_probe(struct wmi_device *wdev, const void *context) +{ + struct redmi_wmi *data; + int err; + + /* Init dev */ + data = devm_kzalloc(&wdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + dev_set_drvdata(&wdev->dev, data); + + err = devm_mutex_init(&wdev->dev, &data->key_lock); + if (err) + return err; + + /* Setup input device */ + data->input_dev = devm_input_allocate_device(&wdev->dev); + if (!data->input_dev) + return -ENOMEM; + data->input_dev->name = "Redmibook WMI keys"; + data->input_dev->phys = "wmi/input0"; + + err = sparse_keymap_setup(data->input_dev, redmi_wmi_keymap, NULL); + if (err) + return err; + + return input_register_device(data->input_dev); +} + +static void redmi_wmi_notify(struct wmi_device *wdev, union acpi_object *obj) +{ + struct redmi_wmi *data = dev_get_drvdata(&wdev->dev); + int value = 1; + bool autorelease = true; + + if (obj->type != ACPI_TYPE_BUFFER) { + dev_err(&wdev->dev, "Bad response type %u\n", obj->type); + return; + } + + if (obj->buffer.length != 32) { + dev_err(&wdev->dev, "Invalid buffer length %u\n", obj->buffer.length); + return; + } + + /* For linearizability */ + guard(mutex)(&data->key_lock); + + u32 payload = get_unaligned_le32(obj->buffer.pointer); + struct key_entry *entry = sparse_keymap_entry_from_scancode(data->input_dev, payload); + + if (!entry) { + dev_dbg(&wdev->dev, "Unknown WMI event with payload %u", payload); + return; + } + + /* AI key quirk */ + if (entry->keycode == KEY_ASSISTANT) { + value = !(payload & AI_KEY_VALUE_MASK); + autorelease = false; + } + + sparse_keymap_report_entry(data->input_dev, entry, value, autorelease); +} + +static const struct wmi_device_id redmi_wmi_id_table[] = { + { .guid_string = WMI_REDMIBOOK_KEYBOARD_EVENT_GUID }, + { } +}; + +static struct wmi_driver redmi_wmi_driver = { + .driver = { + .name = "redmi-wmi", + .probe_type = PROBE_PREFER_ASYNCHRONOUS + }, + .id_table = redmi_wmi_id_table, + .probe = redmi_wmi_probe, + .notify = redmi_wmi_notify, + .no_singleton = true, +}; +module_wmi_driver(redmi_wmi_driver); + +MODULE_DEVICE_TABLE(wmi, redmi_wmi_id_table); +MODULE_AUTHOR("Gladyshev Ilya <foxido@xxxxxxxxxx>"); +MODULE_DESCRIPTION("Redmibook WMI driver"); +MODULE_LICENSE("GPL"); -- 2.50.0