xref: /linux/drivers/leds/trigger/ledtrig-input-events.c (revision c94cd9508b1335b949fd13ebd269313c65492df0)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * Input Events LED trigger
4  *
5  * Copyright (C) 2024 Hans de Goede <hansg@kernel.org>
6  */
7 
8 #include <linux/input.h>
9 #include <linux/jiffies.h>
10 #include <linux/leds.h>
11 #include <linux/module.h>
12 #include <linux/moduleparam.h>
13 #include <linux/slab.h>
14 #include <linux/spinlock.h>
15 #include <linux/workqueue.h>
16 #include "../leds.h"
17 
18 static unsigned long led_off_delay_ms = 5000;
19 module_param(led_off_delay_ms, ulong, 0644);
20 MODULE_PARM_DESC(led_off_delay_ms,
21 	"Specify delay in ms for turning LEDs off after last input event");
22 
23 static struct input_events_data {
24 	struct delayed_work work;
25 	spinlock_t lock;
26 	/* To avoid repeatedly setting the brightness while there are events */
27 	bool led_on;
28 	unsigned long led_off_time;
29 } input_events_data;
30 
31 static struct led_trigger *input_events_led_trigger;
32 
33 static void led_input_events_work(struct work_struct *work)
34 {
35 	struct input_events_data *data =
36 		container_of(work, struct input_events_data, work.work);
37 
38 	spin_lock_irq(&data->lock);
39 
40 	/*
41 	 * This time_after_eq() check avoids a race where this work starts
42 	 * running before a new event pushed led_off_time back.
43 	 */
44 	if (time_after_eq(jiffies, data->led_off_time)) {
45 		led_trigger_event(input_events_led_trigger, LED_OFF);
46 		data->led_on = false;
47 	}
48 
49 	spin_unlock_irq(&data->lock);
50 }
51 
52 static void input_events_event(struct input_handle *handle, unsigned int type,
53 			       unsigned int code, int val)
54 {
55 	struct input_events_data *data = &input_events_data;
56 	unsigned long led_off_delay = msecs_to_jiffies(led_off_delay_ms);
57 	unsigned long flags;
58 
59 	spin_lock_irqsave(&data->lock, flags);
60 
61 	if (!data->led_on) {
62 		led_trigger_event(input_events_led_trigger, LED_FULL);
63 		data->led_on = true;
64 	}
65 	data->led_off_time = jiffies + led_off_delay;
66 
67 	spin_unlock_irqrestore(&data->lock, flags);
68 
69 	mod_delayed_work(system_wq, &data->work, led_off_delay);
70 }
71 
72 static int input_events_connect(struct input_handler *handler, struct input_dev *dev,
73 				const struct input_device_id *id)
74 {
75 	struct input_handle *handle;
76 	int ret;
77 
78 	handle = kzalloc(sizeof(*handle), GFP_KERNEL);
79 	if (!handle)
80 		return -ENOMEM;
81 
82 	handle->dev = dev;
83 	handle->handler = handler;
84 	handle->name = KBUILD_MODNAME;
85 
86 	ret = input_register_handle(handle);
87 	if (ret)
88 		goto err_free_handle;
89 
90 	ret = input_open_device(handle);
91 	if (ret)
92 		goto err_unregister_handle;
93 
94 	return 0;
95 
96 err_unregister_handle:
97 	input_unregister_handle(handle);
98 err_free_handle:
99 	kfree(handle);
100 	return ret;
101 }
102 
103 static void input_events_disconnect(struct input_handle *handle)
104 {
105 	input_close_device(handle);
106 	input_unregister_handle(handle);
107 	kfree(handle);
108 }
109 
110 static const struct input_device_id input_events_ids[] = {
111 	{
112 		.flags = INPUT_DEVICE_ID_MATCH_EVBIT,
113 		.evbit = { BIT_MASK(EV_KEY) },
114 	},
115 	{
116 		.flags = INPUT_DEVICE_ID_MATCH_EVBIT,
117 		.evbit = { BIT_MASK(EV_REL) },
118 	},
119 	{
120 		.flags = INPUT_DEVICE_ID_MATCH_EVBIT,
121 		.evbit = { BIT_MASK(EV_ABS) },
122 	},
123 	{ }
124 };
125 
126 static struct input_handler input_events_handler = {
127 	.name = KBUILD_MODNAME,
128 	.event = input_events_event,
129 	.connect = input_events_connect,
130 	.disconnect = input_events_disconnect,
131 	.id_table = input_events_ids,
132 };
133 
134 static int __init input_events_init(void)
135 {
136 	int ret;
137 
138 	INIT_DELAYED_WORK(&input_events_data.work, led_input_events_work);
139 	spin_lock_init(&input_events_data.lock);
140 
141 	led_trigger_register_simple("input-events", &input_events_led_trigger);
142 
143 	ret = input_register_handler(&input_events_handler);
144 	if (ret) {
145 		led_trigger_unregister_simple(input_events_led_trigger);
146 		return ret;
147 	}
148 
149 	return 0;
150 }
151 
152 static void __exit input_events_exit(void)
153 {
154 	input_unregister_handler(&input_events_handler);
155 	cancel_delayed_work_sync(&input_events_data.work);
156 	led_trigger_unregister_simple(input_events_led_trigger);
157 }
158 
159 module_init(input_events_init);
160 module_exit(input_events_exit);
161 
162 MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>");
163 MODULE_DESCRIPTION("Input Events LED trigger");
164 MODULE_LICENSE("GPL");
165 MODULE_ALIAS("ledtrig:input-events");
166