blob: 1c79731562c25320ae89788f82dd19ee9feaaf3d [file] [log] [blame]
// 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");