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