// SPDX-License-Identifier: GPL-2.0-only /* * Input Events LED trigger * * Copyright (C) 2024 Hans de Goede */ #include #include #include #include #include #include #include #include #include "../leds.h" static unsigned long led_off_delay_ms = 5000; module_param(led_off_delay_ms, ulong, 0644); MODULE_PARM_DESC(led_off_delay_ms, "Specify delay in ms for turning LEDs off after last input event"); static struct input_events_data { struct delayed_work work; spinlock_t lock; /* To avoid repeatedly setting the brightness while there are events */ bool led_on; unsigned long led_off_time; } input_events_data; static struct led_trigger *input_events_led_trigger; static void led_input_events_work(struct work_struct *work) { struct input_events_data *data = container_of(work, struct input_events_data, work.work); spin_lock_irq(&data->lock); /* * This time_after_eq() check avoids a race where this work starts * running before a new event pushed led_off_time back. */ if (time_after_eq(jiffies, data->led_off_time)) { led_trigger_event(input_events_led_trigger, LED_OFF); data->led_on = false; } spin_unlock_irq(&data->lock); } static void input_events_event(struct input_handle *handle, unsigned int type, unsigned int code, int val) { struct input_events_data *data = &input_events_data; unsigned long led_off_delay = msecs_to_jiffies(led_off_delay_ms); unsigned long flags; spin_lock_irqsave(&data->lock, flags); if (!data->led_on) { led_trigger_event(input_events_led_trigger, LED_FULL); data->led_on = true; } data->led_off_time = jiffies + led_off_delay; spin_unlock_irqrestore(&data->lock, flags); mod_delayed_work(system_wq, &data->work, led_off_delay); } static int input_events_connect(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id) { struct input_handle *handle; int ret; handle = kzalloc(sizeof(*handle), GFP_KERNEL); if (!handle) return -ENOMEM; handle->dev = dev; handle->handler = handler; handle->name = KBUILD_MODNAME; ret = input_register_handle(handle); if (ret) goto err_free_handle; ret = input_open_device(handle); if (ret) goto err_unregister_handle; return 0; err_unregister_handle: input_unregister_handle(handle); err_free_handle: kfree(handle); return ret; } static void input_events_disconnect(struct input_handle *handle) { input_close_device(handle); input_unregister_handle(handle); kfree(handle); } static const struct input_device_id input_events_ids[] = { { .flags = INPUT_DEVICE_ID_MATCH_EVBIT, .evbit = { BIT_MASK(EV_KEY) }, }, { .flags = INPUT_DEVICE_ID_MATCH_EVBIT, .evbit = { BIT_MASK(EV_REL) }, }, { .flags = INPUT_DEVICE_ID_MATCH_EVBIT, .evbit = { BIT_MASK(EV_ABS) }, }, { } }; static struct input_handler input_events_handler = { .name = KBUILD_MODNAME, .event = input_events_event, .connect = input_events_connect, .disconnect = input_events_disconnect, .id_table = input_events_ids, }; static int __init input_events_init(void) { int ret; INIT_DELAYED_WORK(&input_events_data.work, led_input_events_work); spin_lock_init(&input_events_data.lock); led_trigger_register_simple("input-events", &input_events_led_trigger); ret = input_register_handler(&input_events_handler); if (ret) { led_trigger_unregister_simple(input_events_led_trigger); return ret; } return 0; } static void __exit input_events_exit(void) { input_unregister_handler(&input_events_handler); cancel_delayed_work_sync(&input_events_data.work); led_trigger_unregister_simple(input_events_led_trigger); } module_init(input_events_init); module_exit(input_events_exit); MODULE_AUTHOR("Hans de Goede "); MODULE_DESCRIPTION("Input Events LED trigger"); MODULE_LICENSE("GPL"); MODULE_ALIAS("ledtrig:input-events");