summaryrefslogtreecommitdiffstats
path: root/drivers/input/input-leds.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/input/input-leds.c')
-rw-r--r--drivers/input/input-leds.c220
1 files changed, 220 insertions, 0 deletions
diff --git a/drivers/input/input-leds.c b/drivers/input/input-leds.c
new file mode 100644
index 000000000..0b11990ad
--- /dev/null
+++ b/drivers/input/input-leds.c
@@ -0,0 +1,220 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * LED support for the input layer
+ *
+ * Copyright 2010-2015 Samuel Thibault <samuel.thibault@ens-lyon.org>
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/leds.h>
+#include <linux/input.h>
+
+#if IS_ENABLED(CONFIG_VT)
+#define VT_TRIGGER(_name) .trigger = _name
+#else
+#define VT_TRIGGER(_name) .trigger = NULL
+#endif
+
+static const struct {
+ const char *name;
+ const char *trigger;
+} input_led_info[LED_CNT] = {
+ [LED_NUML] = { "numlock", VT_TRIGGER("kbd-numlock") },
+ [LED_CAPSL] = { "capslock", VT_TRIGGER("kbd-capslock") },
+ [LED_SCROLLL] = { "scrolllock", VT_TRIGGER("kbd-scrolllock") },
+ [LED_COMPOSE] = { "compose" },
+ [LED_KANA] = { "kana", VT_TRIGGER("kbd-kanalock") },
+ [LED_SLEEP] = { "sleep" } ,
+ [LED_SUSPEND] = { "suspend" },
+ [LED_MUTE] = { "mute" },
+ [LED_MISC] = { "misc" },
+ [LED_MAIL] = { "mail" },
+ [LED_CHARGING] = { "charging" },
+};
+
+struct input_led {
+ struct led_classdev cdev;
+ struct input_handle *handle;
+ unsigned int code; /* One of LED_* constants */
+};
+
+struct input_leds {
+ struct input_handle handle;
+ unsigned int num_leds;
+ struct input_led leds[];
+};
+
+static enum led_brightness input_leds_brightness_get(struct led_classdev *cdev)
+{
+ struct input_led *led = container_of(cdev, struct input_led, cdev);
+ struct input_dev *input = led->handle->dev;
+
+ return test_bit(led->code, input->led) ? cdev->max_brightness : 0;
+}
+
+static void input_leds_brightness_set(struct led_classdev *cdev,
+ enum led_brightness brightness)
+{
+ struct input_led *led = container_of(cdev, struct input_led, cdev);
+
+ input_inject_event(led->handle, EV_LED, led->code, !!brightness);
+}
+
+static void input_leds_event(struct input_handle *handle, unsigned int type,
+ unsigned int code, int value)
+{
+}
+
+static int input_leds_get_count(struct input_dev *dev)
+{
+ unsigned int led_code;
+ int count = 0;
+
+ for_each_set_bit(led_code, dev->ledbit, LED_CNT)
+ if (input_led_info[led_code].name)
+ count++;
+
+ return count;
+}
+
+static int input_leds_connect(struct input_handler *handler,
+ struct input_dev *dev,
+ const struct input_device_id *id)
+{
+ struct input_leds *leds;
+ struct input_led *led;
+ unsigned int num_leds;
+ unsigned int led_code;
+ int led_no;
+ int error;
+
+ num_leds = input_leds_get_count(dev);
+ if (!num_leds)
+ return -ENXIO;
+
+ leds = kzalloc(struct_size(leds, leds, num_leds), GFP_KERNEL);
+ if (!leds)
+ return -ENOMEM;
+
+ leds->num_leds = num_leds;
+
+ leds->handle.dev = dev;
+ leds->handle.handler = handler;
+ leds->handle.name = "leds";
+ leds->handle.private = leds;
+
+ error = input_register_handle(&leds->handle);
+ if (error)
+ goto err_free_mem;
+
+ error = input_open_device(&leds->handle);
+ if (error)
+ goto err_unregister_handle;
+
+ led_no = 0;
+ for_each_set_bit(led_code, dev->ledbit, LED_CNT) {
+ if (!input_led_info[led_code].name)
+ continue;
+
+ led = &leds->leds[led_no];
+ led->handle = &leds->handle;
+ led->code = led_code;
+
+ led->cdev.name = kasprintf(GFP_KERNEL, "%s::%s",
+ dev_name(&dev->dev),
+ input_led_info[led_code].name);
+ if (!led->cdev.name) {
+ error = -ENOMEM;
+ goto err_unregister_leds;
+ }
+
+ led->cdev.max_brightness = 1;
+ led->cdev.brightness_get = input_leds_brightness_get;
+ led->cdev.brightness_set = input_leds_brightness_set;
+ led->cdev.default_trigger = input_led_info[led_code].trigger;
+
+ error = led_classdev_register(&dev->dev, &led->cdev);
+ if (error) {
+ dev_err(&dev->dev, "failed to register LED %s: %d\n",
+ led->cdev.name, error);
+ kfree(led->cdev.name);
+ goto err_unregister_leds;
+ }
+
+ led_no++;
+ }
+
+ return 0;
+
+err_unregister_leds:
+ while (--led_no >= 0) {
+ struct input_led *led = &leds->leds[led_no];
+
+ led_classdev_unregister(&led->cdev);
+ kfree(led->cdev.name);
+ }
+
+ input_close_device(&leds->handle);
+
+err_unregister_handle:
+ input_unregister_handle(&leds->handle);
+
+err_free_mem:
+ kfree(leds);
+ return error;
+}
+
+static void input_leds_disconnect(struct input_handle *handle)
+{
+ struct input_leds *leds = handle->private;
+ int i;
+
+ for (i = 0; i < leds->num_leds; i++) {
+ struct input_led *led = &leds->leds[i];
+
+ led_classdev_unregister(&led->cdev);
+ kfree(led->cdev.name);
+ }
+
+ input_close_device(handle);
+ input_unregister_handle(handle);
+
+ kfree(leds);
+}
+
+static const struct input_device_id input_leds_ids[] = {
+ {
+ .flags = INPUT_DEVICE_ID_MATCH_EVBIT,
+ .evbit = { BIT_MASK(EV_LED) },
+ },
+ { },
+};
+MODULE_DEVICE_TABLE(input, input_leds_ids);
+
+static struct input_handler input_leds_handler = {
+ .event = input_leds_event,
+ .connect = input_leds_connect,
+ .disconnect = input_leds_disconnect,
+ .name = "leds",
+ .id_table = input_leds_ids,
+};
+
+static int __init input_leds_init(void)
+{
+ return input_register_handler(&input_leds_handler);
+}
+module_init(input_leds_init);
+
+static void __exit input_leds_exit(void)
+{
+ input_unregister_handler(&input_leds_handler);
+}
+module_exit(input_leds_exit);
+
+MODULE_AUTHOR("Samuel Thibault <samuel.thibault@ens-lyon.org>");
+MODULE_AUTHOR("Dmitry Torokhov <dmitry.torokhov@gmail.com>");
+MODULE_DESCRIPTION("Input -> LEDs Bridge");
+MODULE_LICENSE("GPL v2");