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