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