nixos/spec/reimaden/ac71/events.c
514fpv 748722b55c
chore(flake)!: bump inputs
As a part of this update, lanzaboote tag is also bumped up one patch
revision, and the ac71 driver is updated to have the new function
signature.
2025-02-06 10:47:13 +08:00

345 lines
7.6 KiB
C

// SPDX-License-Identifier: GPL-2.0
#include "pr.h"
#include <acpi/video.h>
#include <dt-bindings/leds/common.h>
#include <linux/acpi.h>
#include <linux/init.h>
#include <linux/input.h>
#include <linux/input/sparse-keymap.h>
#include <linux/leds.h>
#include "misc.h"
#include "pdev.h"
#include "wmi.h"
/* ========================================================================== */
#define KBD_BL_LED_SUFFIX ":" LED_FUNCTION_KBD_BACKLIGHT
/* ========================================================================== */
static struct {
const char *guid;
bool handler_installed;
} ac71_wmi_event_guids[] = {
{ .guid = AC71_WMI_EVENT0_GUID },
{ .guid = AC71_WMI_EVENT1_GUID },
{ .guid = AC71_WMI_EVENT2_GUID },
};
static const struct key_entry ac71_wmi_hotkeys[] = {
/* reported via keyboard controller */
{ KE_IGNORE, 0x01, { KEY_CAPSLOCK }},
{ KE_IGNORE, 0x02, { KEY_NUMLOCK }},
{ KE_IGNORE, 0x03, { KEY_SCROLLLOCK }},
/* reported via "video bus" */
{ KE_IGNORE, 0x14, { KEY_BRIGHTNESSUP }},
{ KE_IGNORE, 0x15, { KEY_BRIGHTNESSDOWN }},
/* reported via keyboard controller */
{ KE_IGNORE, 0x35, { KEY_MUTE }},
{ KE_IGNORE, 0x36, { KEY_VOLUMEDOWN }},
{ KE_IGNORE, 0x37, { KEY_VOLUMEUP }},
/*
* not reported by other means when in manual mode,
* handled automatically when it automatic mode
*/
{ KE_KEY, 0xb1, { KEY_KBDILLUMDOWN }},
{ KE_KEY, 0xb2, { KEY_KBDILLUMUP }},
{ KE_KEY, 0xb8, { KEY_FN_ESC }},
{ KE_END }
};
/* ========================================================================== */
static struct input_dev *ac71_input_dev;
/* ========================================================================== */
static void toggle_fn_lock_from_event_handler(void)
{
int status = ac71_fn_lock_get_state();
if (status < 0)
return;
/* seemingly the returned status in the WMI event handler is not the current */
pr_info("setting Fn lock state from %d to %d\n", !status, status);
ac71_fn_lock_set_state(status);
}
#if IS_ENABLED(CONFIG_LEDS_BRIGHTNESS_HW_CHANGED)
extern struct rw_semaphore leds_list_lock;
extern struct list_head leds_list;
static void emit_keyboard_led_hw_changed(void)
{
struct led_classdev *led;
if (down_read_killable(&leds_list_lock))
return;
list_for_each_entry (led, &leds_list, node) {
size_t name_length;
const char *suffix;
if (!(led->flags & LED_BRIGHT_HW_CHANGED))
continue;
name_length = strlen(led->name);
if (name_length < strlen(KBD_BL_LED_SUFFIX))
continue;
suffix = led->name + name_length - strlen(KBD_BL_LED_SUFFIX);
if (strcmp(suffix, KBD_BL_LED_SUFFIX) == 0) {
if (mutex_lock_interruptible(&led->led_access))
break;
if (led_update_brightness(led) >= 0)
led_classdev_notify_brightness_hw_changed(led, led->brightness);
mutex_unlock(&led->led_access);
break;
}
}
up_read(&leds_list_lock);
}
#endif
static void ac71_wmi_event_d2_handler(const union acpi_object *obj)
{
bool do_report = true;
if (!obj || obj->type != ACPI_TYPE_INTEGER)
return;
switch (obj->integer.value) {
/* caps lock */
case 1:
pr_info("caps lock\n");
break;
/* num lock */
case 2:
pr_info("num lock\n");
break;
/* scroll lock */
case 3:
pr_info("scroll lock\n");
break;
/* touchpad on */
case 4:
pr_info("touchpad on\n");
break;
/* touchpad off */
case 5:
pr_info("touchpad off\n");
break;
/* increase screen brightness */
case 20:
pr_info("increase screen brightness\n");
/* do_report = !acpi_video_handles_brightness_key_presses() */
break;
/* decrease screen brightness */
case 21:
pr_info("decrease screen brightness\n");
/* do_report = !acpi_video_handles_brightness_key_presses() */
break;
/* mute/unmute */
case 53:
pr_info("toggle mute\n");
break;
/* decrease volume */
case 54:
pr_info("decrease volume\n");
break;
/* increase volume */
case 55:
pr_info("increase volume\n");
break;
/* enable super key (win key) lock */
case 64:
pr_info("enable super key lock\n");
break;
/* decrease volume */
case 65:
pr_info("disable super key lock\n");
break;
/* enable/disable airplane mode */
case 164:
pr_info("toggle airplane mode\n");
break;
/* super key (win key) lock state changed */
case 165:
pr_info("super key lock state changed\n");
sysfs_notify(&ac71_platform_dev->dev.kobj, NULL, "super_key_lock");
break;
case 166:
pr_info("lightbar state changed\n");
break;
/* fan boost state changed */
case 167:
pr_info("fan boost state changed\n");
break;
/* charger unplugged/plugged in */
case 171:
pr_info("AC plugged/unplugged\n");
break;
/* perf mode button pressed */
case 176:
pr_info("change perf mode\n");
/* TODO: should it be handled here? */
break;
/* increase keyboard backlight */
case 177:
pr_info("keyboard backlight decrease\n");
/* TODO: should it be handled here? */
break;
/* decrease keyboard backlight */
case 178:
pr_info("keyboard backlight increase\n");
/* TODO: should it be handled here? */
break;
/* toggle Fn lock (Fn+ESC)*/
case 184:
pr_info("toggle Fn lock\n");
toggle_fn_lock_from_event_handler();
sysfs_notify(&ac71_platform_dev->dev.kobj, NULL, "fn_lock");
break;
/* keyboard backlight brightness changed */
case 240:
pr_info("keyboard backlight changed\n");
#if IS_ENABLED(CONFIG_LEDS_BRIGHTNESS_HW_CHANGED)
emit_keyboard_led_hw_changed();
#endif
break;
default:
pr_warn("unknown code: %u\n", (unsigned int) obj->integer.value);
break;
}
if (do_report && ac71_input_dev)
sparse_keymap_report_event(ac71_input_dev,
obj->integer.value, 1, true);
}
static void ac71_wmi_event_handler(union acpi_object *obj, void *context)
{
if (!obj)
return;
pr_info("obj->type = %d\n", (int) obj->type);
if (obj->type == ACPI_TYPE_INTEGER) {
pr_info("int = %u\n", (unsigned int) obj->integer.value);
} else if (obj->type == ACPI_TYPE_STRING) {
pr_info("string = '%s'\n", obj->string.pointer);
} else if (obj->type == ACPI_TYPE_BUFFER) {
pr_info("buffer = %u %*ph", obj->buffer.length,
(int) obj->buffer.length, (void *) obj->buffer.pointer);
}
if (strcmp((char *)context, AC71_WMI_EVENT2_GUID) == 0)
ac71_wmi_event_d2_handler(obj);
}
static int __init setup_input_dev(void)
{
int err = 0;
ac71_input_dev = input_allocate_device();
if (!ac71_input_dev)
return -ENOMEM;
ac71_input_dev->name = "AC71 laptop input device";
ac71_input_dev->phys = "ac71/input0";
ac71_input_dev->id.bustype = BUS_HOST;
ac71_input_dev->dev.parent = &ac71_platform_dev->dev;
err = sparse_keymap_setup(ac71_input_dev, ac71_wmi_hotkeys, NULL);
if (err)
goto err_free_device;
err = input_register_device(ac71_input_dev);
if (err)
goto err_free_device;
return err;
err_free_device:
input_free_device(ac71_input_dev);
ac71_input_dev = NULL;
return err;
}
/* ========================================================================== */
int __init ac71_wmi_events_setup(void)
{
int err = 0, i;
(void) setup_input_dev();
for (i = 0; i < ARRAY_SIZE(ac71_wmi_event_guids); i++) {
const char *guid = ac71_wmi_event_guids[i].guid;
acpi_status status =
wmi_install_notify_handler(guid, ac71_wmi_event_handler, NULL);
if (ACPI_FAILURE(status)) {
pr_warn("could not install WMI notify handler for '%s': [%#010lx] %s\n",
guid, (unsigned long) status, acpi_format_exception(status));
} else {
ac71_wmi_event_guids[i].handler_installed = true;
}
}
return err;
}
void ac71_wmi_events_cleanup(void)
{
int i;
for (i = 0; i < ARRAY_SIZE(ac71_wmi_event_guids); i++) {
if (ac71_wmi_event_guids[i].handler_installed) {
wmi_remove_notify_handler(ac71_wmi_event_guids[i].guid);
ac71_wmi_event_guids[i].handler_installed = false;
}
}
if (ac71_input_dev)
input_unregister_device(ac71_input_dev);
}