Skip to contentSkip to conversation

LED device driver

PATCH Corey Minyard 2 participants 2 messages Jan 25, 2002

Subsystems (2)
lore
Share & tools
lore
#0 lore raw

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
#1 lore raw
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.