// SPDX-License-Identifier: GPL-2.0 #include "pr.h" #include #include #include #include #include #include #include #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); }