root/drivers/leds/trigger/ledtrig-input-events.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_percpu_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_obj(*handle);
        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");