Customizing the LEDs on the Netgate SG-3100

The SG-3100 pfSense appliance from Netgate has three RGB LEDs on the front. By default, the black diamond LED slowly "breathes" blue when everything is normal, and the green circle LED slowly "breathes" red when there is a software update. During boot, the circle LED quickly "breathes" blue, followed by the square, and finally the diamond. Since I'm always curious (and because the bright blue light was beginning to bother me), I went about figuring out how to individually control each LED (nine in total, for three full RGB LEDs). My starting point was a post by @stephenw10 on the Netgate forums.

Edit (27/09/2020): It turns out that the GPIO device might not always be 0, here's a way to get the GPIO device ID (credit: github.com/justdaniel-gh):

sysctl dev.gpio | grep .led. | cut -d . -f 3 | uniq

Edit (18/02/2020): This still works for the new pfSense Plus edition (21.02), although my GPIO ID changed from 0 to 2 after the upgrade.

The LED Driver

Here's the output of dmesg that shows the LED driver being loaded:

---<<BOOT>>---
Copyright (c) 1992-2020 The FreeBSD Project.
Copyright (c) 1979, 1980, 1983, 1986, 1988, 1989, 1991, 1992, 1993, 1994
	The Regents of the University of California. All rights reserved.
FreeBSD is a registered trademark of The FreeBSD Foundation.
FreeBSD 12.2-STABLE 38a4c12973d(plus-devel-12) pfSense-SG-3100 arm
FreeBSD clang version 10.0.1 ([email protected]:llvm/llvm-project.git llvmorg-10.0.1-0-gef32c611aa2)
CPU: ARM Cortex-A9 r4p1 (ECO: 0x00000000)

...

ofwbus0: <Open Firmware Device Tree>
simplebus0: <Flattened device tree simple bus> on ofwbus0
simplebus1: <Flattened device tree simple bus> on simplebus0

...

twsi0: <Marvell Integrated I2C Bus Controller> mem 0x11000-0x1101f irq 5 on simplebus1
iicbus0: <OFW I2C bus> on twsi0
iic0: <I2C generic I/O> on iicbus0
gpio2: <NXP PCA9552 LED driver> at addr 0xc0 on iicbus0
device_attach: gpio2 attach returned 6
gpio2: <ISSI IS31FL3199 9 channel light effect LED driver> at addr 0xce on iicbus0
gpiobus2: <OFW GPIO bus> on gpio2
gpioc2: <GPIO controller> on gpio2

...

Customizing the Status LEDs

LED Groups

A LED group has a red, green, and blue led in it, making it a single RGB unit. There are three LED groups, the green circle (), blue square (), and black diamond (). Each LED group is addressed as an led, according to the following table:

Pin LED
0
1 🟦
2 🟩

LED Colours

Each LED colour is addressed as a pin, according to the following table:

LED Channel 🟩 🟦
Red 6 3 0
Green 7 4 1
Blue 8 5 2

RGB Control Mode

The LED controller has two RGB modes, PWM and One Shot Programming. PWM mode means the LEDs are only controlled by the duty cycle (brightness). One Shot Programming mode enables the timing characteristics (rising, holding, falling, & off time) of the controller IC, which allows creating patterns.

To enable PWM mode (disabling One Shot mode):

sysctl dev.gpio.<gpio>.led.<led>.pwm=1

To enable One Shot mode (disabling PWM mode):

sysctl dev.gpio.<gpio>.led.<led>.pwm=0

Double Time

There is a fourth setting for each LED group, called Double Time. Double Time is where T3 is calculated by doubling the value of T1. E.g.: T3 = 2 * (T1)

Enable Double Time:

sysctl dev.gpio.<gpio>.led.<led>.DT=1

Disable Double Time:

sysctl dev.gpio.<gpio>.led.<led>.DT=0

LED Brightness

The brightness for each individual LED colour has 256 levels, where 0 is off and the remaining 255 are brightness levels. The brightness is set using the gpioctl command to configure the PWM duty cycle.

Brightness level 0 (off):

gpioctl -f /dev/gpioc<gpio> <pin> duty 0

Brightness level 0 (full):

gpioctl -f /dev/gpioc<gpio> <pin> duty 255

If you supply a value above 255, the value is interpreted as starting from 0, with the greatest factor of 256 subtracted. E.g. 256 is the same as 256 - 256 = 0, which would be off.

LED Controller

The three groups of three LEDs (nine total) are controlled by the IS31FL3199 integrated circuit from Integrated Silicon Solution, Inc.

The most useful piece of information from the IS31FL3199 LED controller datasheet is this breathing timing graph (figure 7):

IS31FL3199 breathing timing
IS31FL3199 Breathing Timing
Register Timing Stage Maximum value Notes
T0 off time 31200 (31.2s)
T1 rising 4160 (4.16s)
T2 holding 16640 (16.64s)
T3 falling 4160 (4.16s) value will be equal to T1, unless Double Time is enabled
T4 off time 31200 (31.2s)

In case you're interested, there is more detailed timing info in tables 11, 12, and 13.

Source: http://www.issi.com/WW/pdf/IS31FL3199.pdf

Breathing Timings

Changing the timings for each of the breathing stages is done using the sysctl command.

To set the T0 (off time) timing for a single LED:

sysctl dev.gpio.<gpio>.pin.<pin>.T0=<value>

To set the T4 (off time) timing for a single LED:

sysctl dev.gpio.<gpio>.pin.<pin>.T4=<value>

To set the T2 (hold time) timing for an LED group:

sysctl dev.gpio.<gpio>.led.<led>.T2=<value>

To set the T1 (rising time) and T3 (falling time) timing for an LED group:

sysctl dev.gpio.<gpio>.led.<led>.T1-T3=<value>

Example

These commands will configure the first LED (green circle) to breathe red, green, and blue sequentially. This example assumes the GPIO device ID is 0.

# Turn up the brightness
gpioctl -f /dev/gpioc0 6 duty 128
gpioctl -f /dev/gpioc0 7 duty 128
gpioctl -f /dev/gpioc0 8 duty 128

# Green Circle LED
sysctl dev.gpio.0.led.2.T2=1040
sysctl dev.gpio.0.led.2.T1-T3=520

# Red colour
sysctl dev.gpio.0.pin.6.T0=0
sysctl dev.gpio.0.pin.6.T4=4160

# Green colour
sysctl dev.gpio.0.pin.7.T0=2080
sysctl dev.gpio.0.pin.7.T4=4160

# Blue colour
sysctl dev.gpio.0.pin.8.T0=4160
sysctl dev.gpio.0.pin.8.T4=4160

Closing Thoughts

The range of settings available for the three front status LEDs on the Netgate SG-3100 creates a lot of posibilities for extending their use. The center (blue square) LED isn't currently used after the boot process, and could be configured to indicate the WAN status, etc. Personally, I only need a dim blue LED for now, just to indicate that the appliance is powered on. To achieve this, I installed the shellcmd package, and created a command that will dim the LED near the end of the boot process.

sysctl dev.gpio.0.led.0.pwm=1; gpioctl 2 duty 2