xref: /linux-6.15/drivers/input/input-leds.c (revision 849c34e6)
1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
2f60c8ba7SSamuel Thibault /*
3f60c8ba7SSamuel Thibault  * LED support for the input layer
4f60c8ba7SSamuel Thibault  *
5f60c8ba7SSamuel Thibault  * Copyright 2010-2015 Samuel Thibault <[email protected]>
6f60c8ba7SSamuel Thibault  */
7f60c8ba7SSamuel Thibault 
8f60c8ba7SSamuel Thibault #include <linux/kernel.h>
9f60c8ba7SSamuel Thibault #include <linux/slab.h>
10f60c8ba7SSamuel Thibault #include <linux/module.h>
11f60c8ba7SSamuel Thibault #include <linux/init.h>
12f60c8ba7SSamuel Thibault #include <linux/leds.h>
13f60c8ba7SSamuel Thibault #include <linux/input.h>
14f60c8ba7SSamuel Thibault 
15f60c8ba7SSamuel Thibault #if IS_ENABLED(CONFIG_VT)
16f60c8ba7SSamuel Thibault #define VT_TRIGGER(_name)	.trigger = _name
17f60c8ba7SSamuel Thibault #else
18f60c8ba7SSamuel Thibault #define VT_TRIGGER(_name)	.trigger = NULL
19f60c8ba7SSamuel Thibault #endif
20f60c8ba7SSamuel Thibault 
21*849c34e6SHeiner Kallweit #if IS_ENABLED(CONFIG_SND_CTL_LED)
22698b4378SBernhard Seibold #define AUDIO_TRIGGER(_name)	.trigger = _name
23698b4378SBernhard Seibold #else
24698b4378SBernhard Seibold #define AUDIO_TRIGGER(_name)	.trigger = NULL
25698b4378SBernhard Seibold #endif
26698b4378SBernhard Seibold 
27f60c8ba7SSamuel Thibault static const struct {
28f60c8ba7SSamuel Thibault 	const char *name;
29f60c8ba7SSamuel Thibault 	const char *trigger;
30f60c8ba7SSamuel Thibault } input_led_info[LED_CNT] = {
31f60c8ba7SSamuel Thibault 	[LED_NUML]	= { "numlock", VT_TRIGGER("kbd-numlock") },
32f60c8ba7SSamuel Thibault 	[LED_CAPSL]	= { "capslock", VT_TRIGGER("kbd-capslock") },
33f60c8ba7SSamuel Thibault 	[LED_SCROLLL]	= { "scrolllock", VT_TRIGGER("kbd-scrolllock") },
34f60c8ba7SSamuel Thibault 	[LED_COMPOSE]	= { "compose" },
35f60c8ba7SSamuel Thibault 	[LED_KANA]	= { "kana", VT_TRIGGER("kbd-kanalock") },
36f60c8ba7SSamuel Thibault 	[LED_SLEEP]	= { "sleep" } ,
37f60c8ba7SSamuel Thibault 	[LED_SUSPEND]	= { "suspend" },
38698b4378SBernhard Seibold 	[LED_MUTE]	= { "mute", AUDIO_TRIGGER("audio-mute") },
39f60c8ba7SSamuel Thibault 	[LED_MISC]	= { "misc" },
40f60c8ba7SSamuel Thibault 	[LED_MAIL]	= { "mail" },
41f60c8ba7SSamuel Thibault 	[LED_CHARGING]	= { "charging" },
42f60c8ba7SSamuel Thibault };
43f60c8ba7SSamuel Thibault 
44f60c8ba7SSamuel Thibault struct input_led {
45f60c8ba7SSamuel Thibault 	struct led_classdev cdev;
46f60c8ba7SSamuel Thibault 	struct input_handle *handle;
47f60c8ba7SSamuel Thibault 	unsigned int code; /* One of LED_* constants */
48f60c8ba7SSamuel Thibault };
49f60c8ba7SSamuel Thibault 
50f60c8ba7SSamuel Thibault struct input_leds {
51f60c8ba7SSamuel Thibault 	struct input_handle handle;
52f60c8ba7SSamuel Thibault 	unsigned int num_leds;
5321d7ec79SKees Cook 	struct input_led leds[] __counted_by(num_leds);
54f60c8ba7SSamuel Thibault };
55f60c8ba7SSamuel Thibault 
input_leds_brightness_get(struct led_classdev * cdev)56f60c8ba7SSamuel Thibault static enum led_brightness input_leds_brightness_get(struct led_classdev *cdev)
57f60c8ba7SSamuel Thibault {
58f60c8ba7SSamuel Thibault 	struct input_led *led = container_of(cdev, struct input_led, cdev);
59f60c8ba7SSamuel Thibault 	struct input_dev *input = led->handle->dev;
60f60c8ba7SSamuel Thibault 
61f60c8ba7SSamuel Thibault 	return test_bit(led->code, input->led) ? cdev->max_brightness : 0;
62f60c8ba7SSamuel Thibault }
63f60c8ba7SSamuel Thibault 
input_leds_brightness_set(struct led_classdev * cdev,enum led_brightness brightness)64f60c8ba7SSamuel Thibault static void input_leds_brightness_set(struct led_classdev *cdev,
65f60c8ba7SSamuel Thibault 				      enum led_brightness brightness)
66f60c8ba7SSamuel Thibault {
67f60c8ba7SSamuel Thibault 	struct input_led *led = container_of(cdev, struct input_led, cdev);
68f60c8ba7SSamuel Thibault 
69f60c8ba7SSamuel Thibault 	input_inject_event(led->handle, EV_LED, led->code, !!brightness);
70f60c8ba7SSamuel Thibault }
71f60c8ba7SSamuel Thibault 
input_leds_event(struct input_handle * handle,unsigned int type,unsigned int code,int value)72f60c8ba7SSamuel Thibault static void input_leds_event(struct input_handle *handle, unsigned int type,
73f60c8ba7SSamuel Thibault 			     unsigned int code, int value)
74f60c8ba7SSamuel Thibault {
75f60c8ba7SSamuel Thibault }
76f60c8ba7SSamuel Thibault 
input_leds_get_count(struct input_dev * dev)77b38ebd1dSDmitry Torokhov static int input_leds_get_count(struct input_dev *dev)
78b38ebd1dSDmitry Torokhov {
79b38ebd1dSDmitry Torokhov 	unsigned int led_code;
80b38ebd1dSDmitry Torokhov 	int count = 0;
81b38ebd1dSDmitry Torokhov 
82b38ebd1dSDmitry Torokhov 	for_each_set_bit(led_code, dev->ledbit, LED_CNT)
83b38ebd1dSDmitry Torokhov 		if (input_led_info[led_code].name)
84b38ebd1dSDmitry Torokhov 			count++;
85b38ebd1dSDmitry Torokhov 
86b38ebd1dSDmitry Torokhov 	return count;
87b38ebd1dSDmitry Torokhov }
88b38ebd1dSDmitry Torokhov 
input_leds_connect(struct input_handler * handler,struct input_dev * dev,const struct input_device_id * id)89f60c8ba7SSamuel Thibault static int input_leds_connect(struct input_handler *handler,
90f60c8ba7SSamuel Thibault 			      struct input_dev *dev,
91f60c8ba7SSamuel Thibault 			      const struct input_device_id *id)
92f60c8ba7SSamuel Thibault {
93f60c8ba7SSamuel Thibault 	struct input_leds *leds;
946bd6ae63SDmitry Torokhov 	struct input_led *led;
95f60c8ba7SSamuel Thibault 	unsigned int num_leds;
96f60c8ba7SSamuel Thibault 	unsigned int led_code;
97f60c8ba7SSamuel Thibault 	int led_no;
98f60c8ba7SSamuel Thibault 	int error;
99f60c8ba7SSamuel Thibault 
100b38ebd1dSDmitry Torokhov 	num_leds = input_leds_get_count(dev);
101f60c8ba7SSamuel Thibault 	if (!num_leds)
102f60c8ba7SSamuel Thibault 		return -ENXIO;
103f60c8ba7SSamuel Thibault 
104acafe7e3SKees Cook 	leds = kzalloc(struct_size(leds, leds, num_leds), GFP_KERNEL);
105f60c8ba7SSamuel Thibault 	if (!leds)
106f60c8ba7SSamuel Thibault 		return -ENOMEM;
107f60c8ba7SSamuel Thibault 
108f60c8ba7SSamuel Thibault 	leds->num_leds = num_leds;
109f60c8ba7SSamuel Thibault 
110f60c8ba7SSamuel Thibault 	leds->handle.dev = dev;
111f60c8ba7SSamuel Thibault 	leds->handle.handler = handler;
112f60c8ba7SSamuel Thibault 	leds->handle.name = "leds";
113f60c8ba7SSamuel Thibault 	leds->handle.private = leds;
114f60c8ba7SSamuel Thibault 
115f60c8ba7SSamuel Thibault 	error = input_register_handle(&leds->handle);
116f60c8ba7SSamuel Thibault 	if (error)
117f60c8ba7SSamuel Thibault 		goto err_free_mem;
118f60c8ba7SSamuel Thibault 
119f60c8ba7SSamuel Thibault 	error = input_open_device(&leds->handle);
120f60c8ba7SSamuel Thibault 	if (error)
121f60c8ba7SSamuel Thibault 		goto err_unregister_handle;
122f60c8ba7SSamuel Thibault 
123f60c8ba7SSamuel Thibault 	led_no = 0;
124f60c8ba7SSamuel Thibault 	for_each_set_bit(led_code, dev->ledbit, LED_CNT) {
125b38ebd1dSDmitry Torokhov 		if (!input_led_info[led_code].name)
126f60c8ba7SSamuel Thibault 			continue;
127f60c8ba7SSamuel Thibault 
1286bd6ae63SDmitry Torokhov 		led = &leds->leds[led_no];
1296bd6ae63SDmitry Torokhov 		led->handle = &leds->handle;
1306bd6ae63SDmitry Torokhov 		led->code = led_code;
1316bd6ae63SDmitry Torokhov 
132f60c8ba7SSamuel Thibault 		led->cdev.name = kasprintf(GFP_KERNEL, "%s::%s",
133f60c8ba7SSamuel Thibault 					   dev_name(&dev->dev),
134f60c8ba7SSamuel Thibault 					   input_led_info[led_code].name);
135f60c8ba7SSamuel Thibault 		if (!led->cdev.name) {
136f60c8ba7SSamuel Thibault 			error = -ENOMEM;
137f60c8ba7SSamuel Thibault 			goto err_unregister_leds;
138f60c8ba7SSamuel Thibault 		}
139f60c8ba7SSamuel Thibault 
140f60c8ba7SSamuel Thibault 		led->cdev.max_brightness = 1;
141f60c8ba7SSamuel Thibault 		led->cdev.brightness_get = input_leds_brightness_get;
142f60c8ba7SSamuel Thibault 		led->cdev.brightness_set = input_leds_brightness_set;
143f60c8ba7SSamuel Thibault 		led->cdev.default_trigger = input_led_info[led_code].trigger;
144f60c8ba7SSamuel Thibault 
145f60c8ba7SSamuel Thibault 		error = led_classdev_register(&dev->dev, &led->cdev);
146f60c8ba7SSamuel Thibault 		if (error) {
147f60c8ba7SSamuel Thibault 			dev_err(&dev->dev, "failed to register LED %s: %d\n",
148f60c8ba7SSamuel Thibault 				led->cdev.name, error);
149f60c8ba7SSamuel Thibault 			kfree(led->cdev.name);
150f60c8ba7SSamuel Thibault 			goto err_unregister_leds;
151f60c8ba7SSamuel Thibault 		}
152f60c8ba7SSamuel Thibault 
153f60c8ba7SSamuel Thibault 		led_no++;
154f60c8ba7SSamuel Thibault 	}
155f60c8ba7SSamuel Thibault 
156f60c8ba7SSamuel Thibault 	return 0;
157f60c8ba7SSamuel Thibault 
158f60c8ba7SSamuel Thibault err_unregister_leds:
159f60c8ba7SSamuel Thibault 	while (--led_no >= 0) {
160f60c8ba7SSamuel Thibault 		struct input_led *led = &leds->leds[led_no];
161f60c8ba7SSamuel Thibault 
162f60c8ba7SSamuel Thibault 		led_classdev_unregister(&led->cdev);
163f60c8ba7SSamuel Thibault 		kfree(led->cdev.name);
164f60c8ba7SSamuel Thibault 	}
165f60c8ba7SSamuel Thibault 
166f60c8ba7SSamuel Thibault 	input_close_device(&leds->handle);
167f60c8ba7SSamuel Thibault 
168f60c8ba7SSamuel Thibault err_unregister_handle:
169f60c8ba7SSamuel Thibault 	input_unregister_handle(&leds->handle);
170f60c8ba7SSamuel Thibault 
171f60c8ba7SSamuel Thibault err_free_mem:
172f60c8ba7SSamuel Thibault 	kfree(leds);
173f60c8ba7SSamuel Thibault 	return error;
174f60c8ba7SSamuel Thibault }
175f60c8ba7SSamuel Thibault 
input_leds_disconnect(struct input_handle * handle)176f60c8ba7SSamuel Thibault static void input_leds_disconnect(struct input_handle *handle)
177f60c8ba7SSamuel Thibault {
178f60c8ba7SSamuel Thibault 	struct input_leds *leds = handle->private;
179f60c8ba7SSamuel Thibault 	int i;
180f60c8ba7SSamuel Thibault 
181f60c8ba7SSamuel Thibault 	for (i = 0; i < leds->num_leds; i++) {
182f60c8ba7SSamuel Thibault 		struct input_led *led = &leds->leds[i];
183f60c8ba7SSamuel Thibault 
184f60c8ba7SSamuel Thibault 		led_classdev_unregister(&led->cdev);
185f60c8ba7SSamuel Thibault 		kfree(led->cdev.name);
186f60c8ba7SSamuel Thibault 	}
187f60c8ba7SSamuel Thibault 
188f60c8ba7SSamuel Thibault 	input_close_device(handle);
189f60c8ba7SSamuel Thibault 	input_unregister_handle(handle);
190f60c8ba7SSamuel Thibault 
191f60c8ba7SSamuel Thibault 	kfree(leds);
192f60c8ba7SSamuel Thibault }
193f60c8ba7SSamuel Thibault 
194f60c8ba7SSamuel Thibault static const struct input_device_id input_leds_ids[] = {
195f60c8ba7SSamuel Thibault 	{
196f60c8ba7SSamuel Thibault 		.flags = INPUT_DEVICE_ID_MATCH_EVBIT,
197f60c8ba7SSamuel Thibault 		.evbit = { BIT_MASK(EV_LED) },
198f60c8ba7SSamuel Thibault 	},
199f60c8ba7SSamuel Thibault 	{ },
200f60c8ba7SSamuel Thibault };
201f60c8ba7SSamuel Thibault MODULE_DEVICE_TABLE(input, input_leds_ids);
202f60c8ba7SSamuel Thibault 
203f60c8ba7SSamuel Thibault static struct input_handler input_leds_handler = {
204f60c8ba7SSamuel Thibault 	.event =	input_leds_event,
205f60c8ba7SSamuel Thibault 	.connect =	input_leds_connect,
206f60c8ba7SSamuel Thibault 	.disconnect =	input_leds_disconnect,
207f60c8ba7SSamuel Thibault 	.name =		"leds",
208f60c8ba7SSamuel Thibault 	.id_table =	input_leds_ids,
209f60c8ba7SSamuel Thibault };
210f60c8ba7SSamuel Thibault 
input_leds_init(void)211f60c8ba7SSamuel Thibault static int __init input_leds_init(void)
212f60c8ba7SSamuel Thibault {
213f60c8ba7SSamuel Thibault 	return input_register_handler(&input_leds_handler);
214f60c8ba7SSamuel Thibault }
215f60c8ba7SSamuel Thibault module_init(input_leds_init);
216f60c8ba7SSamuel Thibault 
input_leds_exit(void)217f60c8ba7SSamuel Thibault static void __exit input_leds_exit(void)
218f60c8ba7SSamuel Thibault {
219f60c8ba7SSamuel Thibault 	input_unregister_handler(&input_leds_handler);
220f60c8ba7SSamuel Thibault }
221f60c8ba7SSamuel Thibault module_exit(input_leds_exit);
222f60c8ba7SSamuel Thibault 
223f60c8ba7SSamuel Thibault MODULE_AUTHOR("Samuel Thibault <[email protected]>");
224f60c8ba7SSamuel Thibault MODULE_AUTHOR("Dmitry Torokhov <[email protected]>");
225f60c8ba7SSamuel Thibault MODULE_DESCRIPTION("Input -> LEDs Bridge");
226f60c8ba7SSamuel Thibault MODULE_LICENSE("GPL v2");
227