diff options
Diffstat (limited to 'drivers/leds/trigger/ledtrig-input-events.c')
-rw-r--r-- | drivers/leds/trigger/ledtrig-input-events.c | 165 |
1 files changed, 165 insertions, 0 deletions
diff --git a/drivers/leds/trigger/ledtrig-input-events.c b/drivers/leds/trigger/ledtrig-input-events.c new file mode 100644 index 000000000000..1c79731562c2 --- /dev/null +++ b/drivers/leds/trigger/ledtrig-input-events.c @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Input Events LED trigger + * + * Copyright (C) 2024 Hans de Goede <hansg@kernel.org> + */ + +#include <linux/input.h> +#include <linux/jiffies.h> +#include <linux/leds.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/workqueue.h> +#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 <hansg@kernel.org>"); +MODULE_DESCRIPTION("Input Events LED trigger"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("ledtrig:input-events"); |