LED device driver
Subsystems (2)
A lot of boards have LEDs on the front, it is useful to be able to control these LEDs from userland. I have implemented a start at a device driver front-end for this that gives a generic interface. I'd appreciate comments on it. I have not tied things like the num lock LEDs to this, but that might be doable later.
Although it doesn't include it right now, this could be extended to add things like 7-segment LEDs, scrolling displays, etc.
The basic thought is to allow low-level LED drivers to register with this with a name and capabilities. This driver would give a consistent way to advertise and use those capabilities to the user application, so it could discover LEDs by name, know their capabilities, etc.
-Corey
drivers/char/led.c renamed from drivers/char/led.c.led +316 -0
@@ -0,0 +1,316 @@ +/* +* Device driver for controlling LEDs. See linux/led.h for info on +* how to use this. +* +* This currently only supports mono and bicolor LEDs, but support for +* fancier LEDs (scrolling text or numeric LEDs, for instance) could +* easily be added. +* +* Corey Minyard <[email protected]> +*/ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/version.h> +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/miscdevice.h> +#include <asm/io.h> +#include <asm/uaccess.h> +#include <asm/system.h> +#include <linux/init.h> +#include <linux/led.h> +#include <linux/string.h> +#include <linux/spinlock.h> + +#define VERSION "1.0" + +/* A lock that is held when manipulating LED info. */ +static spinlock_t led_lock; + +/* A linked list of all the LEDs registered. */ +static struct led_reg_info *leds; + +/* Register a new LED with the driver. */ +int register_led_info (struct led_reg_info *info) +{ +struct led_reg_info *led; +int rv =0 ; + +spin_lock (&led_lock); + info->next =NULL ; + led = leds; +if (led ==NULL ) + { + leds = info; + } +else + { +while (led->next !=NULL ) + { +if (strcmp (led->info->name, info->info->name) ==0 ) + { +/* Registering a duplicate. */ + rv =-1 ; + goto out; + } + led = led->next; + } + led->next = info; + } +MOD_INC_USE_COUNT ; + +out: +spin_unlock (&led_lock); + +return rv; +} + +/* Unregister an LED from the driver. */ +int unregister_led_info (struct led_reg_info *info) +{ +int rv =-1 ; +struct led_reg_info *led; + +if (info ==NULL ) + { +return -1 ; + } + +spin_lock (&led_lock); + led = leds; +if (led == info) + { +MOD_DEC_USE_COUNT ; + leds = leds->next; + } +else + { +while (led->next !=NULL ) + { +if (led->next == info) + { +MOD_DEC_USE_COUNT ; + led->next = info->next; + rv =0 ; + goto out; + } + led = led->next; + } + } +out: +spin_unlock (&led_lock); +return rv; +} + +/* Return the number of LEDs registered. */ +static int count_leds (void ) +{ +struct led_reg_info *led; +int count =0 ; + +spin_lock (&led_lock); + led = leds; +while (led !=NULL ) + { + count = count +1 ; + led = led->next; + } +spin_unlock (&led_lock); + +return count; +} + +/* Find an LED by its number. Must be called with the led spin lock +held. */ +static struct led_reg_info *get_led_by_num (int num) +{ +struct led_reg_info *led; +int count = num; + + led = leds; +while (led !=NULL ) + { +if (count ==0 ) + { + led->info->led_num = num; +return led; + } + count = count -1 ; + led = led->next; + } + +return NULL ; +} + +/* Find an LED by its name. Must be called with the led spin lock +held. */ +static struct led_reg_info *get_led_by_name (char *name) +{ +struct led_reg_info *led; +int count =0 ; + + led = leds; +while (led !=NULL ) + { +if (strcmp (name, led->info->name) ==0 ) + { + led->info->led_num = count; +return led; + } + count++; + led = led->next; + } + +return NULL ; +} + +/* The IOCTL handler for the LED driver. */ +static int led_ioctl (struct inode *inode, +struct file *file, +unsigned int cmd, +unsigned long arg) +{ +int i; +struct led_reg_info *led; +struct led_info info; +struct led_op op; + +switch (cmd) + { +case LEDIOC_GETCOUNT : +return count_leds (); + +case LEDIOC_GETINFO_BY_NUM : +case LEDIOC_GETINFO_BY_NAME : + i =copy_from_user (&info, (void *)arg,sizeof (info)); +if (i) + { +return -EFAULT ; + } + +spin_lock (&led_lock); +if (cmd ==LEDIOC_GETINFO_BY_NUM ) + led =get_led_by_num (info.led_num); +else + led =get_led_by_name (info.name); +if (led ==NULL ) + { +spin_unlock (&led_lock); +return -EINVAL ; + } + + info.type = led->info->type; + info.led_num = led->info->led_num; +strcpy (info.name, led->info->name); + info.info = led->info->info; + i =copy_to_user ((void *)arg, &info,sizeof (info)); + +spin_unlock (&led_lock); + +if (i) + { +return -EFAULT ; + } +return 0 ; + +case LEDIOC_OP : + i =copy_from_user (&op, (void *)arg,sizeof (op)); +if (i) + { +return -EFAULT ; + } + +spin_lock (&led_lock); + led =get_led_by_name (op.name); +if (led ==NULL ) + { +spin_unlock (&led_lock); +return -EINVAL ; + } + + i = led->handle_led_op (led, &op); +if (!i) + { + i =copy_to_user ((void *)arg, &op,sizeof (op)); +if (i) + { + i = -EFAULT ; + } + } +spin_unlock (&led_lock); + +return i; + +default : +return -ENOIOCTLCMD ; + } +} + +/* Not much to do for opening. */ +static int led_open (struct inode *inode,struct file *file) +{ +switch (MINOR (inode->i_rdev)) + { +case LED_MINOR : +return 0 ; + +default : +return -ENODEV ; + } +} + +/* Closing is really easy. */ +static int led_close (struct inode *inode,struct file *file) +{ +return 0 ; +} + + +static struct file_operations led_fops = { + owner:THIS_MODULE , + llseek:NULL , + read:NULL , + write:NULL , + ioctl: led_ioctl, + open: led_open, + release: led_close, +}; + +static struct miscdevice led_miscdev= +{ +LED_MINOR , +"led" , + &led_fops +}; + + +/* Remove the LED driver. */ +static void __exit led_exit(void) +{ +misc_deregister (&led_miscdev); +} + +/* Set up the LED device driver. */ +static int __init led_init (void ) +{ +int ret; + +printk ("generic LED driver: v%s Corey Minyard ([email protected])\n" , +VERSION ); + + leds =NULL ; + ret =misc_register (&led_miscdev); +if (ret) { +printk (KERN_ERR "led: can't misc_register on minor=%d\n" , +LED_MINOR ); +return -1 ; + } +return 0 ; +} + +module_init (led_init); +module_exit (led_exit); + +EXPORT_SYMBOL (register_led_info); +EXPORT_SYMBOL (unregister_led_info);
drivers/char/Config.in renamed from drivers/char/Config.in.led +2 -0
@@ -222,6 +222,8 @@ fi endmenu +tristate 'LED device driver support' CONFIG_LINUX_LED + if [ "$CONFIG_ARCH_NETWINDER" = "y" ]; then tristate 'NetWinder thermometer support' CONFIG_DS1620 tristate 'NetWinder Button' CONFIG_NWBUTTON
drivers/char/Makefile renamed from drivers/char/Makefile.led +2 -1
@@ -24,7 +24,7 @@ export-objs := busmouse.o console.o keyboard.o sysrq.o \ misc.o pty.o random.o selection.o serial.o \ sonypi.o tty_io.o tty_ioctl.o generic_serial.o \ - ppc405_gpio.o au1000_gpio.o lcd.o \ + ppc405_gpio.o au1000_gpio.o lcd.o led.o \ h3600_backpaq_fpga.o mod-subdirs := joystick ftape drm pcmcia
@@ -250,6 +250,7 @@ obj-$(CONFIG_ITE_GPIO) += ite_gpio.o obj-$(CONFIG_AU1000_GPIO) += au1000_gpio.o obj-$(CONFIG_PPC405_GPIO) += ppc405_gpio.o +obj-$(CONFIG_LINUX_LED) += led.o obj-$(CONFIG_QIC02_TAPE) += tpqic02.o
Documentation/Configure.help renamed from Documentation/Configure.help.led +6 -0
@@ -11578,6 +11578,12 @@ then also Y to the driver for your FDDI card, below). Most people will say N. +Linux LED Support +CONFIG_LINUX_LED + This is a driver that supports controlling LEDs. It is a generic + driver that provides a control framework, the actual drivers must + register with it to work. + Digital DEFEA and DEFPA adapter support CONFIG_DEFXX This is support for the DIGITAL series of EISA (DEFEA) and PCI
include/linux/led.h renamed from include/linux/led.h.led +130 -0
130 lines not on this page · raw message
include/linux/miscdevice.h renamed from include/linux/miscdevice.h.led +1 -0
@@ -15,6 +15,7 @@#define ADB_MOUSE_MINOR 10#define WATCHDOG_MINOR 130/* Watchdog timer */ #define TEMP_MINOR 131/* Temperature Sensor */ +#define LED_MINOR 132/* LED device driver */ #define RTC_MINOR 135#define EFI_RTC_MINOR 136/* EFI Time services */ #define SUN_OPENPROM_MINOR 139
Quoted 6 lines
A lot of boards have LEDs on the front, it is useful to be able to control these LEDs from userland. I have implemented a start at a device driver front-end for this that gives a generic interface. I'd appreciate comments on it. I have not tied things like the num lock LEDs to this, but that might be doable later.
At a look at the input api (include/linux/input.h). Since keyboards have leds we implemented a very basic api for this.