166 lines
3.9 KiB
C
166 lines
3.9 KiB
C
// 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");
|