summaryrefslogtreecommitdiff
path: root/drivers/platform/x86/redmi-wmi.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/platform/x86/redmi-wmi.c')
-rw-r--r--drivers/platform/x86/redmi-wmi.c130
1 files changed, 130 insertions, 0 deletions
diff --git a/drivers/platform/x86/redmi-wmi.c b/drivers/platform/x86/redmi-wmi.c
new file mode 100644
index 000000000000..949236b93a32
--- /dev/null
+++ b/drivers/platform/x86/redmi-wmi.c
@@ -0,0 +1,130 @@
+// SPDX-License-Identifier: GPL-2.0
+/* WMI driver for Xiaomi Redmibooks */
+
+#include <linux/acpi.h>
+#include <linux/bits.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 BIT(8)
+
+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;
+
+ 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 key_entry *entry;
+ struct redmi_wmi *data = dev_get_drvdata(&wdev->dev);
+ bool autorelease = true;
+ u32 payload;
+ int value = 1;
+
+ 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;
+ }
+
+ payload = get_unaligned_le32(obj->buffer.pointer);
+ 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;
+ }
+
+ guard(mutex)(&data->key_lock);
+ sparse_keymap_report_entry(data->input_dev, entry, value, autorelease);
+}
+
+static const struct wmi_device_id redmi_wmi_id_table[] = {
+ { WMI_REDMIBOOK_KEYBOARD_EVENT_GUID, NULL },
+ { }
+};
+
+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@foxido.dev>");
+MODULE_DESCRIPTION("Redmibook WMI driver");
+MODULE_LICENSE("GPL");