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

source code of linux/drivers/input/input-leds.c