| 1 | |
| 2 | |
| 3 | * LED support for the input layer
|
| 4 | *
|
| 5 | * Copyright 2010-2015 Samuel Thibault <[email protected]>
|
| 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;
|
| 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_flex(*leds, leds, num_leds);
|
| 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 <[email protected]>");
|
| 224 | MODULE_AUTHOR("Dmitry Torokhov <[email protected]>");
|
| 225 | MODULE_DESCRIPTION("Input -> LEDs Bridge");
|
| 226 | MODULE_LICENSE("GPL v2");
|