mirror of
https://github.com/torvalds/linux.git
synced 2024-11-22 12:11:40 +00:00
Input: export LEDs as class devices in sysfs
This change creates a new input handler called "leds" that exports LEDs on input devices as standard LED class devices in sysfs and allows controlling their state via sysfs or via any of the standard LED triggers. This allows to re-purpose and reassign LDEs on the keyboards to represent states other than the standard keyboard states (CapsLock, NumLock, etc). The old API of controlling input LEDs by writing into /dev/input/eventX devices is still present and will take precedence over accessing via LEDs subsystem (i.e. it may override state set by a trigger). If input device is "grabbed" then requests coming through LED subsystem will be ignored. Signed-off-by: Samuel Thibault <samuel.thibault@ens-lyon.org> Tested-by: Pavel Machek <pavel@ucw.cz> Acked-by: Pavel Machek <pavel@ucw.cz> Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
This commit is contained in:
parent
cf99289d67
commit
f60c8ba77d
@ -2,9 +2,6 @@
|
||||
LED handling under Linux
|
||||
========================
|
||||
|
||||
If you're reading this and thinking about keyboard leds, these are
|
||||
handled by the input subsystem and the led class is *not* needed.
|
||||
|
||||
In its simplest form, the LED class just allows control of LEDs from
|
||||
userspace. LEDs appear in /sys/class/leds/. The maximum brightness of the
|
||||
LED is defined in max_brightness file. The brightness file will set the brightness
|
||||
|
@ -25,6 +25,19 @@ config INPUT
|
||||
|
||||
if INPUT
|
||||
|
||||
config INPUT_LEDS
|
||||
tristate "Export input device LEDs in sysfs"
|
||||
depends on LEDS_CLASS
|
||||
default INPUT
|
||||
help
|
||||
Say Y here if you would like to export LEDs on input devices
|
||||
as standard LED class devices in sysfs.
|
||||
|
||||
If unsure, say Y.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called input-leds.
|
||||
|
||||
config INPUT_FF_MEMLESS
|
||||
tristate "Support for memoryless force-feedback devices"
|
||||
help
|
||||
|
@ -12,6 +12,7 @@ obj-$(CONFIG_INPUT_POLLDEV) += input-polldev.o
|
||||
obj-$(CONFIG_INPUT_SPARSEKMAP) += sparse-keymap.o
|
||||
obj-$(CONFIG_INPUT_MATRIXKMAP) += matrix-keymap.o
|
||||
|
||||
obj-$(CONFIG_INPUT_LEDS) += input-leds.o
|
||||
obj-$(CONFIG_INPUT_MOUSEDEV) += mousedev.o
|
||||
obj-$(CONFIG_INPUT_JOYDEV) += joydev.o
|
||||
obj-$(CONFIG_INPUT_EVDEV) += evdev.o
|
||||
|
212
drivers/input/input-leds.c
Normal file
212
drivers/input/input-leds.c
Normal file
@ -0,0 +1,212 @@
|
||||
/*
|
||||
* LED support for the input layer
|
||||
*
|
||||
* Copyright 2010-2015 Samuel Thibault <samuel.thibault@ens-lyon.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#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_connect(struct input_handler *handler,
|
||||
struct input_dev *dev,
|
||||
const struct input_device_id *id)
|
||||
{
|
||||
struct input_leds *leds;
|
||||
unsigned int num_leds;
|
||||
unsigned int led_code;
|
||||
int led_no;
|
||||
int error;
|
||||
|
||||
num_leds = bitmap_weight(dev->ledbit, LED_CNT);
|
||||
if (!num_leds)
|
||||
return -ENXIO;
|
||||
|
||||
leds = kzalloc(sizeof(*leds) + num_leds * sizeof(*leds->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) {
|
||||
struct input_led *led = &leds->leds[led_no];
|
||||
|
||||
led->handle = &leds->handle;
|
||||
led->code = led_code;
|
||||
|
||||
if (WARN_ON(!input_led_info[led_code].name))
|
||||
continue;
|
||||
|
||||
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");
|
@ -11,9 +11,6 @@ menuconfig NEW_LEDS
|
||||
Say Y to enable Linux LED support. This allows control of supported
|
||||
LEDs from both userspace and optionally, by kernel events (triggers).
|
||||
|
||||
This is not related to standard keyboard LEDs which are controlled
|
||||
via the input system.
|
||||
|
||||
if NEW_LEDS
|
||||
|
||||
config LEDS_CLASS
|
||||
|
Loading…
Reference in New Issue
Block a user