LED Subystem in Kernel
- Pavel Machek, Denx [Open Source Summit EU 2019]

The LED subsystem was introduced in 2006 for the Sharp Zaurus. Originally intended for on/off only but had 255 values.

The first interface had a brightness and a trigger value, in sysfs. The brightness can be 0 or 255 for off or on. The trigger is a string, which gives control to the kernel. Also from the beginning there was support for hardware-accelerated blinking. There is also support for an in-kernel trigger to drive the LED from interrupt context. Also to turn it off after a while, because you can’t turn it off immediately (then a human can’t see it).

Originally the triggers were simple functions. E.g. ide-disk triggers on disk activity. But now complicated triggers, like BAT0-charging-blink-full-solid. Also non-scalable: e.g. cpu trigger for specific cpu, so there can be thousands of these triggers in the kernel. Triggers can have parameters, in a separate sysfs file. That would have been a better solution for the cpu trigger.

SoC pins are expensive, and since LEDs don’t need to be toggled quickly, it’s OK to put them on GPIO extenders. However, then it can no longer be used from interrupt, because I2C transactions are non-atomic.

The first problems appeared with PWM. Brightness is non-linear. If 128 means half power, there is almost no visible difference between 200 and 256. When there is a trigger, which is typically on/off, the brightness no longer means the value, but the maximum value used by the trigger. Because of this, also when the brightness is set to 0, it turns the trigger off. But this makes the code more complicated than needed.

Some LEDs predated the infrastructure. For example, keyboard LEDs are controlled through ioctls on the input device. Video4Linux has its own interface for flashes. For real cameras, this is fine because it really a flash. But on a phone it is just a powerful LED. Network ports also typically have a green and orange/green LED. Currently there is no way to control them, but that’s going to change. But then it has to switch between controlled by network hardware and controlled by software. There are also fake LEDs, e.g. for haptic feedback, because the LED subsystem is so easy to use. It would be better to use the input subsystem for that.

RGB LEDs are three LEDs. But if we treat them as 3 separate LEDs, we get weird artifacts. For example, 255/255/255 does not give white. You need some non-linear transformation to get the actual colour. You would also like to get smooth transitions on such LEDs, but that’s time consuming to do from software. Many LEDs have specific driver hardware to handle this. E.g. Qualcomm has a simple pattern generator that you program a sequence of values.

The pattern trigger is programmed with a series of (time, brightness) point. The kernel plays this pattern accordingly. It cannot yet be offloaded to hardware acceleration, but it should.

Naming the LEDs is hard. Names should avoid device details in the name, e.g. which controller is used. Instead, the first part should specify the subsystem, the final bit should specify the function. E.g. capslock, kbd_backlight.

LEDs can be defined with no driver at all, if it is pure GPIO. In the device tree:

gpio-leds {
  compatible = "gpio-leds";
  led-foo {
    label = "device:part:function";
    gpios = <&gpio2 18 GPIO_ACTIVE_HIGH>;
    default-state = "on";

For a full driver, the core function is _led_set which gets a brightness value as argument. It can also have a default trigger. There is a devm interface to avoid having to manage the details.

The future is in colour. Legacy triggers only work with a fixed colour. Colour computations can probably be done outside of the kernel. So each channel can get full brightness, and userspace should set the maximum brightness in each channel.

Many LEDs are optionally software controlled, e.g. charging LED, network LED. Backlight LEDs can be changed from hardware too. To solve these, LED-specific triggers could be defined. Also a good interface to userspace must be defined.

Much more standardisation is needed. E.g. on the meaning of brightness, on which functions are safe to call in atomic, and on the naming.