mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
197 lines
4.5 KiB
197 lines
4.5 KiB
// SPDX-License-Identifier: GPL-2.0-or-later |
|
/* |
|
* WMI hotkeys support for Dell All-In-One series |
|
*/ |
|
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
|
|
|
#include <linux/kernel.h> |
|
#include <linux/module.h> |
|
#include <linux/init.h> |
|
#include <linux/types.h> |
|
#include <linux/input.h> |
|
#include <linux/input/sparse-keymap.h> |
|
#include <linux/acpi.h> |
|
#include <linux/string.h> |
|
|
|
MODULE_DESCRIPTION("WMI hotkeys driver for Dell All-In-One series"); |
|
MODULE_LICENSE("GPL"); |
|
|
|
#define EVENT_GUID1 "284A0E6B-380E-472A-921F-E52786257FB4" |
|
#define EVENT_GUID2 "02314822-307C-4F66-BF0E-48AEAEB26CC8" |
|
|
|
struct dell_wmi_event { |
|
u16 length; |
|
/* 0x000: A hot key pressed or an event occurred |
|
* 0x00F: A sequence of hot keys are pressed */ |
|
u16 type; |
|
u16 event[]; |
|
}; |
|
|
|
static const char *dell_wmi_aio_guids[] = { |
|
EVENT_GUID1, |
|
EVENT_GUID2, |
|
NULL |
|
}; |
|
|
|
MODULE_ALIAS("wmi:"EVENT_GUID1); |
|
MODULE_ALIAS("wmi:"EVENT_GUID2); |
|
|
|
static const struct key_entry dell_wmi_aio_keymap[] = { |
|
{ KE_KEY, 0xc0, { KEY_VOLUMEUP } }, |
|
{ KE_KEY, 0xc1, { KEY_VOLUMEDOWN } }, |
|
{ KE_KEY, 0xe030, { KEY_VOLUMEUP } }, |
|
{ KE_KEY, 0xe02e, { KEY_VOLUMEDOWN } }, |
|
{ KE_KEY, 0xe020, { KEY_MUTE } }, |
|
{ KE_KEY, 0xe027, { KEY_DISPLAYTOGGLE } }, |
|
{ KE_KEY, 0xe006, { KEY_BRIGHTNESSUP } }, |
|
{ KE_KEY, 0xe005, { KEY_BRIGHTNESSDOWN } }, |
|
{ KE_KEY, 0xe00b, { KEY_SWITCHVIDEOMODE } }, |
|
{ KE_END, 0 } |
|
}; |
|
|
|
static struct input_dev *dell_wmi_aio_input_dev; |
|
|
|
/* |
|
* The new WMI event data format will follow the dell_wmi_event structure |
|
* So, we will check if the buffer matches the format |
|
*/ |
|
static bool dell_wmi_aio_event_check(u8 *buffer, int length) |
|
{ |
|
struct dell_wmi_event *event = (struct dell_wmi_event *)buffer; |
|
|
|
if (event == NULL || length < 6) |
|
return false; |
|
|
|
if ((event->type == 0 || event->type == 0xf) && |
|
event->length >= 2) |
|
return true; |
|
|
|
return false; |
|
} |
|
|
|
static void dell_wmi_aio_notify(u32 value, void *context) |
|
{ |
|
struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; |
|
union acpi_object *obj; |
|
struct dell_wmi_event *event; |
|
acpi_status status; |
|
|
|
status = wmi_get_event_data(value, &response); |
|
if (status != AE_OK) { |
|
pr_info("bad event status 0x%x\n", status); |
|
return; |
|
} |
|
|
|
obj = (union acpi_object *)response.pointer; |
|
if (obj) { |
|
unsigned int scancode = 0; |
|
|
|
switch (obj->type) { |
|
case ACPI_TYPE_INTEGER: |
|
/* Most All-In-One correctly return integer scancode */ |
|
scancode = obj->integer.value; |
|
sparse_keymap_report_event(dell_wmi_aio_input_dev, |
|
scancode, 1, true); |
|
break; |
|
case ACPI_TYPE_BUFFER: |
|
if (dell_wmi_aio_event_check(obj->buffer.pointer, |
|
obj->buffer.length)) { |
|
event = (struct dell_wmi_event *) |
|
obj->buffer.pointer; |
|
scancode = event->event[0]; |
|
} else { |
|
/* Broken machines return the scancode in a |
|
buffer */ |
|
if (obj->buffer.pointer && |
|
obj->buffer.length > 0) |
|
scancode = obj->buffer.pointer[0]; |
|
} |
|
if (scancode) |
|
sparse_keymap_report_event( |
|
dell_wmi_aio_input_dev, |
|
scancode, 1, true); |
|
break; |
|
} |
|
} |
|
kfree(obj); |
|
} |
|
|
|
static int __init dell_wmi_aio_input_setup(void) |
|
{ |
|
int err; |
|
|
|
dell_wmi_aio_input_dev = input_allocate_device(); |
|
|
|
if (!dell_wmi_aio_input_dev) |
|
return -ENOMEM; |
|
|
|
dell_wmi_aio_input_dev->name = "Dell AIO WMI hotkeys"; |
|
dell_wmi_aio_input_dev->phys = "wmi/input0"; |
|
dell_wmi_aio_input_dev->id.bustype = BUS_HOST; |
|
|
|
err = sparse_keymap_setup(dell_wmi_aio_input_dev, |
|
dell_wmi_aio_keymap, NULL); |
|
if (err) { |
|
pr_err("Unable to setup input device keymap\n"); |
|
goto err_free_dev; |
|
} |
|
err = input_register_device(dell_wmi_aio_input_dev); |
|
if (err) { |
|
pr_info("Unable to register input device\n"); |
|
goto err_free_dev; |
|
} |
|
return 0; |
|
|
|
err_free_dev: |
|
input_free_device(dell_wmi_aio_input_dev); |
|
return err; |
|
} |
|
|
|
static const char *dell_wmi_aio_find(void) |
|
{ |
|
int i; |
|
|
|
for (i = 0; dell_wmi_aio_guids[i] != NULL; i++) |
|
if (wmi_has_guid(dell_wmi_aio_guids[i])) |
|
return dell_wmi_aio_guids[i]; |
|
|
|
return NULL; |
|
} |
|
|
|
static int __init dell_wmi_aio_init(void) |
|
{ |
|
int err; |
|
const char *guid; |
|
|
|
guid = dell_wmi_aio_find(); |
|
if (!guid) { |
|
pr_warn("No known WMI GUID found\n"); |
|
return -ENXIO; |
|
} |
|
|
|
err = dell_wmi_aio_input_setup(); |
|
if (err) |
|
return err; |
|
|
|
err = wmi_install_notify_handler(guid, dell_wmi_aio_notify, NULL); |
|
if (err) { |
|
pr_err("Unable to register notify handler - %d\n", err); |
|
input_unregister_device(dell_wmi_aio_input_dev); |
|
return err; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static void __exit dell_wmi_aio_exit(void) |
|
{ |
|
const char *guid; |
|
|
|
guid = dell_wmi_aio_find(); |
|
wmi_remove_notify_handler(guid); |
|
input_unregister_device(dell_wmi_aio_input_dev); |
|
} |
|
|
|
module_init(dell_wmi_aio_init); |
|
module_exit(dell_wmi_aio_exit);
|
|
|