LEDs on the BBB

From Crashcourse Wiki

Jump to: navigation, search

Contents

[edit] Overview

Quick(?) discussion of how to mess with the LEDs on the BBB, based on running a core-image-minimal.

[edit] Device tree stuff

From am335x-bone-common.dtsi:

        am33xx_pinmux: pinmux@44e10800 {
                pinctrl-names = "default";
                pinctrl-0 = <&userled_pins>;

                userled_pins: pinmux_userled_pins {
                        pinctrl-single,pins = <
                                0x54 0x7        /* gpmc_a5.gpio1_21, OUTPUT | MODE7 */
                                0x58 0x17       /* gpmc_a6.gpio1_22, OUTPUT_PULLUP | MODE7 */
                                0x5c 0x7        /* gpmc_a7.gpio1_23, OUTPUT | MODE7 */
                                0x60 0x17       /* gpmc_a8.gpio1_24, OUTPUT_PULLUP | MODE7 */
                        >;
                };
... snip ...
        ocp: ocp {

                uart1: serial@44e09000 {
                        status = "okay";
                };

                gpio-leds {
                        compatible = "gpio-leds";
                        pinctrl-names = "default";
                        pinctrl-0 = <&userled_pins>;

                        led0 {
                                label = "beaglebone:green:usr0";
                                gpios = <&gpio2 21 0>;
                                linux,default-trigger = "heartbeat";
                                default-state = "off";
                        };

                        led1 {
                                label = "beaglebone:green:usr1";
                                gpios = <&gpio2 22 0>;
                                linux,default-trigger = "mmc0";
                                default-state = "off";
                        };

                        led2 {
                                label = "beaglebone:green:usr2";
                                gpios = <&gpio2 23 0>;
                                linux,default-trigger = "cpu0";
                                default-state = "off";
                        };

                        led3 {
                                label = "beaglebone:green:usr3";
                                gpios = <&gpio2 24 0>;
                                default-state = "off";
                                linux,default-trigger = "mmc1";
                        };
                };

[edit] LED code that depends on CONFIG_OF

$ grep CONFIG_OF *
leds-88pm860x.c:#ifdef CONFIG_OF
leds-gpio.c:#ifdef CONFIG_OF_GPIO
leds-gpio.c:#else /* CONFIG_OF_GPIO */
leds-gpio.c:#endif /* CONFIG_OF_GPIO */
leds-lp5521.c:#ifdef CONFIG_OF
leds-lp5523.c:#ifdef CONFIG_OF
leds-lp5562.c:#ifdef CONFIG_OF
leds-lp8501.c:#ifdef CONFIG_OF
leds-ns2.c:#ifdef CONFIG_OF_GPIO
leds-ns2.c:#endif /* CONFIG_OF_GPIO */
leds-ns2.c:#ifdef CONFIG_OF_GPIO
leds-ns2.c:#endif /* CONFIG_OF_GPIO */
leds-pca963x.c:#if IS_ENABLED(CONFIG_OF)
leds-tca6507.c:#ifdef CONFIG_OF
$

[edit] Matching against the device tree

From drivers/leds/leds-gpio.c:

static const struct of_device_id of_gpio_leds_match[] = {
        { .compatible = "gpio-leds", },
        {},
};
static struct platform_driver gpio_led_driver = {
        .probe          = gpio_led_probe,
        .remove         = gpio_led_remove,
        .driver         = {
                .name   = "leds-gpio",
                .owner  = THIS_MODULE,
                .of_match_table = of_match_ptr(of_gpio_leds_match),
        },
};
static int gpio_led_probe(struct platform_device *pdev)
{
        struct gpio_led_platform_data *pdata = pdev->dev.platform_data;
        struct gpio_leds_priv *priv;
        struct pinctrl *pinctrl;
        int i, ret = 0;

        pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
        if (IS_ERR(pinctrl))
                dev_warn(&pdev->dev,
                        "pins are not configured from the driver\n");

        if (pdata && pdata->num_leds) {
                priv = devm_kzalloc(&pdev->dev,
                                sizeof_gpio_leds_priv(pdata->num_leds),
                                        GFP_KERNEL);
                if (!priv)
                        return -ENOMEM;

                priv->num_leds = pdata->num_leds;
                for (i = 0; i < priv->num_leds; i++) {
                        ret = create_gpio_led(&pdata->leds[i],
                                              &priv->leds[i],
                                              &pdev->dev, pdata->gpio_blink_set);
                        if (ret < 0) {
                                /* On failure: unwind the led creations */
                                for (i = i - 1; i >= 0; i--)
                                        delete_gpio_led(&priv->leds[i]);
                                return ret;
                        }
                }
        } else {
                priv = gpio_leds_create_of(pdev);
                if (IS_ERR(priv))
                        return PTR_ERR(priv);
        }

        platform_set_drvdata(pdev, priv);

        return 0;
}

[edit] On the running system

Here's /sys/kernel/debug/gpio:

# cat /sys/kernel/debug/gpio
GPIOs 0-31, gpio:

GPIOs 32-63, gpio:
 gpio-52  (eMMC_RSTn           ) out lo
 gpio-53  (beaglebone:green:usr) out lo
 gpio-54  (beaglebone:green:usr) out lo
 gpio-55  (beaglebone:green:usr) out hi
 gpio-56  (beaglebone:green:usr) out lo
 gpio-59  (McASP Clock Enable P) out hi

GPIOs 64-95, gpio:

GPIOs 96-127, gpio:
#

And here's /sys/class/leds:

lrwxrwxrwx 1 root root 0 Jan  1 00:00 beaglebone:green:usr0 -> ../../devices/ocp.2/gpio-leds.7/leds/beaglebone:green:usr0
lrwxrwxrwx 1 root root 0 Jan  1 00:00 beaglebone:green:usr1 -> ../../devices/ocp.2/gpio-leds.7/leds/beaglebone:green:usr1
lrwxrwxrwx 1 root root 0 Jan  1 00:00 beaglebone:green:usr2 -> ../../devices/ocp.2/gpio-leds.7/leds/beaglebone:green:usr2
lrwxrwxrwx 1 root root 0 Jan  1 00:00 beaglebone:green:usr3 -> ../../devices/ocp.2/gpio-leds.7/leds/beaglebone:green:usr3

And if we follow the link for usr0 (heartbeat), we find:

/sys/devices/ocp.2/gpio-leds.7/leds/beaglebone:green:usr0# ls -l
-rw-r--r--    1 root     root          4096 Jul  2 11:59 brightness
lrwxrwxrwx    1 root     root             0 Jul  2 11:59 device -> ../../../gpio-leds.7
-r--r--r--    1 root     root          4096 Jul  2 11:59 max_brightness
drwxr-xr-x    2 root     root             0 Jul  2 11:59 power
lrwxrwxrwx    1 root     root             0 Jan  1  2000 subsystem -> ../../../../../class/leds
-rw-r--r--    1 root     root          4096 Jul  2 12:12 trigger
-rw-r--r--    1 root     root          4096 Jan  1  2000 uevent
#

We can check the trigger:

# cat trigger
none nand-disk mmc0 mmc1 timer oneshot [heartbeat] backlight gpio cpu0 default-on transient 
#

then turn it off and on again:

# echo none > trigger
# echo heartbeat > trigger

[edit] More issues to tease out ...

[edit] LEDs and GPIO

The file drivers/leds/ledtrig-gpio.c allows LED triggers to be based on GPIO events, and defines an interrupt for that:

        ret = request_irq(gpio_to_irq(gpio), gpio_trig_irq,
                        IRQF_SHARED | IRQF_TRIGGER_RISING
                        | IRQF_TRIGGER_FALLING, "ledtrig-gpio", led);
        if (ret) {
                dev_err(dev, "request_irq failed with error %d\n", ret);
        } else {
                if (gpio_data->gpio != 0)
                        free_irq(gpio_to_irq(gpio_data->gpio), led);
                gpio_data->gpio = gpio;
        }

Currently on this system, the file /proc/interrupts has no entry for "ledtrig-gpio", but I suspect that's because you need to set the LED trigger to "gpio", which is one of the valid values. More research here, but this might be a nice example of interrupts.

[edit] Kernel LED configuration

From drivers/leds/Makefile:

# LED Core
obj-$(CONFIG_NEW_LEDS)                  += led-core.o
obj-$(CONFIG_LEDS_CLASS)                += led-class.o
obj-$(CONFIG_LEDS_TRIGGERS)             += led-triggers.o
... snip ...
# LED Platform Drivers
obj-$(CONFIG_LEDS_GPIO_REGISTER)        += leds-gpio-register.o
obj-$(CONFIG_LEDS_GPIO)                 += leds-gpio.o
... snip ...
# LED Triggers
obj-$(CONFIG_LEDS_TRIGGER_TIMER)        += ledtrig-timer.o
obj-$(CONFIG_LEDS_TRIGGER_ONESHOT)      += ledtrig-oneshot.o
obj-$(CONFIG_LEDS_TRIGGER_IDE_DISK)     += ledtrig-ide-disk.o
obj-$(CONFIG_LEDS_TRIGGER_HEARTBEAT)    += ledtrig-heartbeat.o
obj-$(CONFIG_LEDS_TRIGGER_BACKLIGHT)    += ledtrig-backlight.o
obj-$(CONFIG_LEDS_TRIGGER_GPIO)         += ledtrig-gpio.o
obj-$(CONFIG_LEDS_TRIGGER_CPU)          += ledtrig-cpu.o
obj-$(CONFIG_LEDS_TRIGGER_DEFAULT_ON)   += ledtrig-default-on.o
obj-$(CONFIG_LEDS_TRIGGER_TRANSIENT)    += ledtrig-transient.o

and from .config:

CONFIG_NEW_LEDS=y
CONFIG_LEDS_CLASS=y

#
# LED drivers
#
# CONFIG_LEDS_LM3530 is not set
CONFIG_LEDS_LM3642=m
# CONFIG_LEDS_PCA9532 is not set
CONFIG_LEDS_GPIO=y
# CONFIG_LEDS_LP3944 is not set
# CONFIG_LEDS_LP5521 is not set
# CONFIG_LEDS_LP5523 is not set
# CONFIG_LEDS_PCA955X is not set
# CONFIG_LEDS_PCA9633 is not set
# CONFIG_LEDS_DAC124S085 is not set
# CONFIG_LEDS_REGULATOR is not set
# CONFIG_LEDS_BD2802 is not set
# CONFIG_LEDS_LT3593 is not set
# CONFIG_LEDS_RENESAS_TPU is not set
# CONFIG_LEDS_TCA6507 is not set
CONFIG_LEDS_LM355x=m
# CONFIG_LEDS_OT200 is not set
CONFIG_LEDS_BLINKM=m
CONFIG_LEDS_TRIGGERS=y

#
# LED Triggers
#
CONFIG_LEDS_TRIGGER_TIMER=y
CONFIG_LEDS_TRIGGER_ONESHOT=y
CONFIG_LEDS_TRIGGER_HEARTBEAT=y
CONFIG_LEDS_TRIGGER_BACKLIGHT=y
CONFIG_LEDS_TRIGGER_CPU=y
CONFIG_LEDS_TRIGGER_GPIO=y
CONFIG_LEDS_TRIGGER_DEFAULT_ON=y

#
# iptables trigger is under Netfilter config (LED target)
#
CONFIG_LEDS_TRIGGER_TRANSIENT=y

and, FWIW, the resulting object files so I can see what's being built in (although I'm not convinced some of those chip files are necessary):

built-in.o
led-class.o
led-core.o
leds-blinkm.mod.o
leds-blinkm.o
leds-gpio.o
leds-lm355x.mod.o
leds-lm355x.o
leds-lm3642.mod.o
leds-lm3642.o
ledtrig-backlight.o
ledtrig-cpu.o
ledtrig-default-on.o
led-triggers.o
ledtrig-gpio.o
ledtrig-heartbeat.o
ledtrig-oneshot.o
ledtrig-timer.o
ledtrig-transient.o

[edit] Triggers

[edit] The trigger file

For each LED:

# cat trigger
none nand-disk mmc0 mmc1 timer oneshot [heartbeat] backlight gpio cpu0 default-on transient 
#

[edit] Registering a trigger (led-triggers.c)

/* LED Trigger Interface */

int led_trigger_register(struct led_trigger *trig)
{
        struct led_classdev *led_cdev;
        struct led_trigger *_trig;

        rwlock_init(&trig->leddev_list_lock);
        INIT_LIST_HEAD(&trig->led_cdevs);

        down_write(&triggers_list_lock);
        /* Make sure the trigger's name isn't already in use */
        list_for_each_entry(_trig, &trigger_list, next_trig) {
                if (!strcmp(_trig->name, trig->name)) {
                        up_write(&triggers_list_lock);
                        return -EEXIST;
                }
        }
        /* Add to the list of led triggers */
        list_add_tail(&trig->next_trig, &trigger_list);
        up_write(&triggers_list_lock);

        /* Register with any LEDs that have this as a default trigger */
        down_read(&leds_list_lock);
        list_for_each_entry(led_cdev, &leds_list, node) {
                down_write(&led_cdev->trigger_lock);
                if (!led_cdev->trigger && led_cdev->default_trigger &&
                            !strcmp(led_cdev->default_trigger, trig->name))
                        led_trigger_set(led_cdev, trig);
                up_write(&led_cdev->trigger_lock);
        }
        up_read(&leds_list_lock);

        return 0;
}
EXPORT_SYMBOL_GPL(led_trigger_register);
... snip ...
void led_trigger_register_simple(const char *name, struct led_trigger **tp)
{
        struct led_trigger *trig;
        int err;

        trig = kzalloc(sizeof(struct led_trigger), GFP_KERNEL);

        if (trig) {
                trig->name = name;
                err = led_trigger_register(trig);
                if (err < 0) {
                        kfree(trig);
                        trig = NULL;
                        pr_warn("LED trigger %s failed to register (%d)\n",
                                name, err);
                }
        } else {
                pr_warn("LED trigger %s failed to register (no memory)\n",
                        name);
        }
        *tp = trig;
}
EXPORT_SYMBOL_GPL(led_trigger_register_simple);

and you can see how many triggers are being registered:

$ grep led_trigger_register ledtrig-*.c
ledtrig-backlight.c:	return led_trigger_register(&bl_led_trigger);
ledtrig-cpu.c:		led_trigger_register_simple(trig->name, &trig->_trig);
ledtrig-default-on.c:	return led_trigger_register(&defon_led_trigger);
ledtrig-gpio.c:	return led_trigger_register(&gpio_led_trigger);
ledtrig-heartbeat.c:	int rc = led_trigger_register(&heartbeat_led_trigger);
ledtrig-ide-disk.c:	led_trigger_register_simple("ide-disk", &ledtrig_ide);
ledtrig-oneshot.c:	return led_trigger_register(&oneshot_led_trigger);
ledtrig-timer.c:	return led_trigger_register(&timer_led_trigger);
ledtrig-transient.c:	return led_trigger_register(&transient_trigger);

[edit] LEDs and GPIO

This is tentative, I'm trying to figure out how this works. One of the LED triggers is "gpio". If I look at LED usr0 with "heartbeat", this is the contents of the directory:

brightness      max_brightness  subsystem       uevent
device          power           trigger

If I set it to "gpio", the contents become:

brightness          gpio                power               uevent
desired_brightness  inverted            subsystem
device              max_brightness      trigger

given the following code from ledtrig-gpio.c:

static DEVICE_ATTR(gpio, 0644, gpio_trig_gpio_show, gpio_trig_gpio_store);

static void gpio_trig_activate(struct led_classdev *led)
{
        struct gpio_trig_data *gpio_data;
        int ret;

        gpio_data = kzalloc(sizeof(*gpio_data), GFP_KERNEL);
        if (!gpio_data)
                return;

        ret = device_create_file(led->dev, &dev_attr_gpio);
        if (ret)
                goto err_gpio;

        ret = device_create_file(led->dev, &dev_attr_inverted);
        if (ret)
                goto err_inverted;

        ret = device_create_file(led->dev, &dev_attr_desired_brightness);
        if (ret)
                goto err_brightness;

        gpio_data->led = led;
        led->trigger_data = gpio_data;
        INIT_WORK(&gpio_data->work, gpio_trig_work);
        led->activated = true;

        return;

err_brightness:
        device_remove_file(led->dev, &dev_attr_inverted);

err_inverted:
        device_remove_file(led->dev, &dev_attr_gpio);

err_gpio:
        kfree(gpio_data);
}

The initial value of that gpio file is zero, but that can clearly be changed:

static ssize_t gpio_trig_gpio_store(struct device *dev,
                struct device_attribute *attr, const char *buf, size_t n)
{
        struct led_classdev *led = dev_get_drvdata(dev);
        struct gpio_trig_data *gpio_data = led->trigger_data;
        unsigned gpio;
        int ret;

        ret = sscanf(buf, "%u", &gpio);
        if (ret < 1) {
                dev_err(dev, "couldn't read gpio number\n");
                flush_work(&gpio_data->work);
                return -EINVAL;
        }

        if (gpio_data->gpio == gpio)
                return n;

        if (!gpio) {
                if (gpio_data->gpio != 0)
                        free_irq(gpio_to_irq(gpio_data->gpio), led);
                gpio_data->gpio = 0;
                return n;
        }

        ret = request_irq(gpio_to_irq(gpio), gpio_trig_irq,
                        IRQF_SHARED | IRQF_TRIGGER_RISING
                        | IRQF_TRIGGER_FALLING, "ledtrig-gpio", led);
        if (ret) {
                dev_err(dev, "request_irq failed with error %d\n", ret);
        } else {
                if (gpio_data->gpio != 0)
                        free_irq(gpio_to_irq(gpio_data->gpio), led);
                gpio_data->gpio = gpio;
        }

        return ret ? ret : n;
}


(Back to BeagleBone_Black page.)

Personal tools