The Linux LED subsystem

Although it is perfectly possible to drive a LED from a standard GPIO port, Linux provides a dedicated LED API1 too.

Using the LED API does make it clearer which pins are associated with LEDs, but that’s not the main benefit. Rather, it allows the kernel to manage the output in more complicated ways.

For example, userspace can ask the kernel to turn the LED on for a fixed period of time without having to wait around to turn the LED off again afterwards. As an aside, we can also be confident that the LED won’t stay on for ever, even if the userspace program crashes.

Moving such logic inside the kernel, also makes it easier to drive LEDs from internal kernel information. For example, we might want to indicate disk or network activity.

Kernel modules which implement such behaviour are called LED Triggers, and you can find them in the drivers/leds/trigger directory2 of the kernel.

An LED driver for the MinnowBoard Max

It’s probably obvious that we need to have some way to tell the kernel to associate a particular GPIO pin with the LED before it can control it. In fact the situation is more general: although we’re using a GPIO pin to control the LED, other people might have more complicated hardware.

So our real task will be to identify a driver for ‘LEDs attached to a GPIO pin’, then associate a Linux LED device with that.

The module is easy: it’s helpfully called leds-gpio.c3 You will need a kernel which includes this module: you can grab the config I used4 from GitHub.

I found the simplest way to make the LED device was with a simple kernel module of my own. You can get this from github.5

Just as with the SPI6 device, I suspect you could make the connections by suitably tweaking Device Tree7 or the ACPI8 tables.

Much of the module code is just boilerplate. The key definition is small enough to contemplate though:

static struct gpio_led seabass_led[] = {
  {					
    .name = "seabass::user",		
    .default_trigger = "heartbeat",	
    .gpio = 474,			
    .active_low = 0,			
  },					

The name should match the devicename:colour:function pattern, but is otherwise arbitrary. More importantly:

Note: GPIO 474 assumes that the 102-entry GPIO block starts at GPIO 410.

Instructions

Hardware

Set up the LED on pin 20.

Build the module

$ git clone https://github.com/mjoldfield/seabass.git			
Cloning into 'seabass'...						
remote: Counting objects: 21, done.					
remote: Compressing objects: 100% (19/19), done.			
remote: Total 21 (delta 4), reused 14 (delta 1)				
Unpacking objects: 100% (21/21), done.					
Checking connectivity... done.						
$ cd seabass/seabass-leds/						
$ make 									
...

Load the modules

$ sudo modprobe ledtrig_heartbeat leds_gpio				
$ sudo insmod seabass-leds.ko 						

The LED should now start beating.

The sysfs API

You can configure the LED system via a sysfs API.

Query the current trigger:

# cat /sys/class/leds/seabass::user/trigger
none mmc0 mmc1 mmc2 gpio [heartbeat]

Disable the heartbeat:

# echo none > /sys/class/leds/seabass::user/trigger
# cat /sys/class/leds/seabass::user/trigger
[none] mmc0 mmc1 mmc2 gpio heartbeat

Load the single shot trigger, and select it:

# modprobe ledtrig_oneshot
# echo oneshot > /sys/class/leds/seabass::user/trigger
# cat /sys/class/leds/seabass::user/trigger
none mmc0 mmc1 mmc2 gpio heartbeat [oneshot]

Fire!

# echo 1 > /sys/class/leds/seabass::user/shot

Make the pulse last longer:

# cat /sys/class/leds/seabass::user/delay_on
100
# echo 1000 > /sys/class/leds/seabass::user/delay_on
# echo 1 > /sys/class/leds/seabass::user/shot

Restore the heartbeat:

# echo heartbeat > /sys/class/leds/seabass::user/trigger

If you now stress the machine, you will see the heart rate rise.