Linux LED Subsystem
2011/09/21 10 Comments
LEDs… Everyone likes that! Those little shiny electronic devices are mounted on any well-made electronic equipment to indicate at a glance its working status. They tell you when your network has activity, when your laptop battery is empty, when your hard-drive is working, when your amplifier is overloading… they may even light up your bedroom!
In embedded systems the proper design of the front panel, with the right LED illuminated icons, is an essential feature and if you are familiar with network troubleshooting you can understand why!
Well-made devices should have a panel that instantly gives you an idea of what’s working and what’s not just by looking at it.
If you are using Linux as your kernel on a SoC design, you’ll be glad to know that it has an entire subsystem dedicated to LEDs!
In this post I’ll show how you to check if your system has some controllable LEDs, and how to use that from userspace applications and kernel drivers.
Host Drivers
Kernel controllable LEDs are commonly found on embedded systems, where the CPU usually has some LEDs controlled by its GPIO pins or by some external peripheral, such as I2C/SPI port expander.
There are many LED-enabled device drivers in the kernel sources, some of which are dedicated drivers, such as the one for the LEDs in PC Engines ALIX boards, while others are just drivers for devices with integrated LEDs, such as the one for RaLink wireless network cards.
Just run a search on the kernel code for the led_classdev_register function to see if you find something to play with.
The most common driver you’ll find in modern ARM-based SoC is “leds-gpio“, which is used to control LEDs connected to GPIO of any gpiolib enabled CPU, including most ARM SoC.
Registering a GPIO with the leds-gpio driver is easy, take a look at this code from the nslu2-setup.c driver:
#include <linux/leds.h> ... static struct gpio_led nslu2_led_pins[] = { { .name = "nslu2:green:ready", .gpio = NSLU2_LED_GRN_GPIO, }, ... }; static struct gpio_led_platform_data nslu2_led_data = { .num_leds = ARRAY_SIZE(nslu2_led_pins), .leds = nslu2_led_pins, }; static struct platform_device nslu2_leds = { .name = "leds-gpio", .id = -1, .dev.platform_data = &nslu2_led_data, }; static struct platform_device *nslu2_devices[] __initdata = { ... &nslu2_leds, ... };
On these platforms, when all the structures are in place, if you want to add a new GPIO controlled LED just add the appropriate structure with the name, pin and properties and light it up! If it’s not working, check that all the necessary modules are compiled in and be sure that your pin is configured as a GPIO, since in many CPU you have to do that explicitly.
If you’re a lucky owner of an embedded PowerPC board, you might like to know that the leds-gpio driver is openfirmware enabled!
Check out this configuration for the MPC8315E-RDB board:
leds { compatible = "gpio-leds"; pwr { gpios = <&mcu_pio 0 0>; default-state = "on"; }; hdd { gpios = <&mcu_pio 1 0>; linux,default-trigger = "ide-disk"; }; };
Note that while the platform driver registers as “leds-gpio”, the openfirmware one matches with “gpio-leds”.
Also, when you are naming your LEDs, you might want to follow the convention suggested in the official documentation, which tells you to name your led as: “devicename:colour:function”.
Controlling LEDs from Userspace
So, you have your kernel configured and the system booted. Now what?
First, be sure that the kernel registered the LEDs, as in:
balto@balto-mpc:~# dmesg | grep "led device" Registered led device: pwr Registered led device: hdd
Now, go to the “/sys/class/leds/” directory and look at the content:
balto@balto-mpc:~# cd /sys/class/leds/hdd balto@balto-mpc:/sys/class/leds/hdd# ls brightness device max_brightness power subsystem trigger uevent
You should see what’s coming: to turn on the LED, just use:
balto-mpc:/sys/class/leds/hdd# echo 1 > brightness
and to turn it off use:
balto-mpc:/sys/class/leds/hdd# echo 0 > brightness
Also note that, as suggested by the content of the “max_brightness” file, the brightness parameter goes from 0 to 255, and on some platforms you may have a PWM controlled LED with variable brightness.
Now, what’s that “trigger” file for?
Triggers
Triggers are the API used to link a LED to an event in kernel space.
That’s the idea: the platform registers some LED device, the drivers register some LED trigger, you configure the device to use a certain trigger and the led will be controlled from the appropriate driver.
To see the list of the available triggers, just read the “trigger” file:
balto@balto-mpc:/sys/class/leds/hdd# cat trigger [none] nand-disk timer heartbeat gpio rfkill0 phy0rx phy0tx phy0assoc phy0radio
you see that the trigger is disabled. To associate the LED with a trigger, just write its name in the “trigger” file:
balto@balto-mpc:/sys/class/leds/hdd# echo heartbeat > trigger balto@balto-mpc:/sys/class/leds/hdd# cat trigger none nand-disk timer [heartbeat] gpio rfkill0 phy0rx phy0tx phy0assoc phy0radio
Now the LED should pulse like an heart with a period proportional to the system load average.
When you change trigger, check again the content of the LED directory, as many triggers also register some parameter files.
Hacking the kernel to add new triggers is pretty easy and good fun, just take some time and read a couple of trigger source files to see how they work!
Also, you can find the official documentation in the kernel’s “Documentation/leds” directory.
Happy blinkin’!
very interesting!
Thanks mate!
Thanks for this article! It really helped me understand what the heck was going on in an Android app that I wrote that controls the brightness of the capacitive buttons backlight on the HTC One X. I’ve got a bunch of bug reports about weird behaviour and I definitely have a better understanding of what might be going on now. https://code.google.com/p/hox-cap-butn-brightness
cercando “gpio leds ethernet activity” su google indovina qual’era il primo risultato? Credo che tu sia il maggior esperto mondiale al mondo di gpio leds al mondo
Mah ho paura dipenda da chi lo cerca… se lo googlo io sono il secondo. E io odio essere il secondo.
looooooooooool
Si, ma sei secondo solo dopo la documentazione ufficiale. È come essere il maggior esperto di Divina Commedia dopo Dante! Ciao Balto
Haha! Grazie Diego! :-)
Thank you very much.
Is it possible to configure brightness with sysfs , for example through userspace or kernel code ?
Thanks!
Yes, both, the API takes an 8 bit unsigned: http://lxr.free-electrons.com/source/include/linux/leds.h#L28 (enum led_brightness), but the driver has to support that, and most are either on or off.