树莓派的Linux 内核已经集成了LED 灯驱动。 Linux 内核的 LED 灯驱动采用 platform 框架。编译树莓派linux内核时,会输入配置命令构建配置linux内核,如:
cd linuxKERNEL=kernel7lmake bcm2711_defconfig
linux内核的makefile会从arch/arm/configs 目录中寻找默认配置文件: bcm2711_defconfig,配置完成后会生成.config 文件。打开.config 文件,有“CONFIG_LEDS_GPIO=y”,说明配置了LED灯驱动。
## LED drivers## CONFIG_LEDS_AN30259A is not set# CONFIG_LEDS_BCM6328 is not set# CONFIG_LEDS_BCM6358 is not set# CONFIG_LEDS_CR0014114 is not set# CONFIG_LEDS_LM3530 is not set# CONFIG_LEDS_LM3532 is not set# CONFIG_LEDS_LM3642 is not set# CONFIG_LEDS_LM3692X is not setCONFIG_LEDS_PCA9532=m# CONFIG_LEDS_PCA9532_GPIO is not setCONFIG_LEDS_GPIO=y
一、Linux 内核自带 LED 驱动
打开/drivers/leds/Makefile 这个文件,有:
obj-$(CONFIG_LEDS_GPIO_REGISTER)+= leds-gpio-register.oobj-$(CONFIG_LEDS_GPIO)+= leds-gpio.oobj-$(CONFIG_LEDS_LP3944)+= leds-lp3944.o
说明如果定义了 CONFIG_LEDS_GPIO 的话就会编译 leds-gpio.c 这个文件,而在.config 文件中有“CONFIG_LEDS_GPIO=y”这一行,因此 leds-gpio.c 驱动文件会被编译进linux内核。
打开LED 灯驱动文件/drivers/leds/leds-gpio.c,有如下所示内容:
static const struct of_device_id of_gpio_leds_match[] = {{.compatible = "gpio-leds", },{},};
LED 驱动的匹配表, compatible 内容为“gpio-leds”,因此设备树中的 LED 灯设备节点的 compatible 属性值也要为“gpio-leds”,否则设备和驱动匹配不成功,驱动就没法工作。
static struct platform_driver gpio_led_driver = {.probe= gpio_led_probe,.shutdown= gpio_led_shutdown,.driver= {.name= "leds-gpio",.of_match_table = of_gpio_leds_match,},};module_platform_driver(gpio_led_driver);
platform_driver 驱动结构体变量,可以看出, Linux 内核自带的 LED 驱动采用了 platform 框架。 probe 函数为 gpio_led_probe,当驱动和设备匹配成功以后 gpio_led_probe 函数就会执行。驱动名字为“leds-gpio”,因此会在/sys/bus/platform/drivers 目录下存在一个名为“leds-gpio”的文件。
当驱动和设备匹配以后 gpio_led_probe 函数就会执行,此函数主要是从设备树中获取 LED灯的 GPIO 信息。gpio_led_probe内容为:
static int gpio_led_probe(struct platform_device *pdev){struct gpio_led_platform_data *pdata = dev_get_platdata(&pdev->dev);struct gpio_leds_priv *priv;int i, ret = 0;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++) {const struct gpio_led *template = &pdata->leds[i];struct gpio_led_data *led_dat = &priv->leds[i];if (template->gpiod)led_dat->gpiod = template->gpiod;elseled_dat->gpiod =gpio_led_get_gpiod(&pdev->dev,i, template);if (IS_ERR(led_dat->gpiod)) {dev_info(&pdev->dev, "Skipping unavailable LED gpio %d (%s)\n",template->gpio, template->name);continue;}ret = create_gpio_led(template, led_dat,&pdev->dev, NULL,pdata->gpio_blink_set);if (ret < 0)return ret;}} else {/* 采用设备树 */priv = gpio_leds_create(pdev);if (IS_ERR(priv))return PTR_ERR(priv);}platform_set_drvdata(pdev, priv);return 0;}
如果使用设备树的话,会使用 gpio_leds_create 函数从设备树中提取设备信息,获取到的 LED 灯 GPIO 信息保存在返回值中,gpio_leds_create 函数内容如下:
static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev){struct device *dev = &pdev->dev;struct fwnode_handle *child;struct gpio_leds_priv *priv;int count, ret;count = device_get_child_node_count(dev);if (!count)return ERR_PTR(-ENODEV);priv = devm_kzalloc(dev, sizeof_gpio_leds_priv(count), GFP_KERNEL);if (!priv)return ERR_PTR(-ENOMEM);device_for_each_child_node(dev, child) {struct gpio_led_data *led_dat = &priv->leds[priv->num_leds];struct gpio_led led = {};const char *state = NULL;/** Acquire gpiod from DT with uninitialized label, which* will be updated after LED class device is registered,* Only then the final LED name is known.*/led.gpiod = devm_fwnode_get_gpiod_from_child(dev, NULL, child,GPIOD_ASIS,NULL);if (IS_ERR(led.gpiod)) {fwnode_handle_put(child);return ERR_CAST(led.gpiod);}led_dat->gpiod = led.gpiod;fwnode_property_read_string(child, "linux,default-trigger",&led.default_trigger);if (!fwnode_property_read_string(child, "default-state",&state)) {if (!strcmp(state, "keep"))led.default_state = LEDS_GPIO_DEFSTATE_KEEP;else if (!strcmp(state, "on"))led.default_state = LEDS_GPIO_DEFSTATE_ON;elseled.default_state = LEDS_GPIO_DEFSTATE_OFF;}if (fwnode_property_present(child, "retain-state-suspended"))led.retain_state_suspended = 1;if (fwnode_property_present(child, "retain-state-shutdown"))led.retain_state_shutdown = 1;if (fwnode_property_present(child, "panic-indicator"))led.panic_indicator = 1;ret = create_gpio_led(&led, led_dat, dev, child, NULL);if (ret < 0) {fwnode_handle_put(child);return ERR_PTR(ret);}/* Set gpiod label to match the corresponding LED name. */gpiod_set_consumer_name(led_dat->gpiod,led_dat->cdev.dev->kobj.name);priv->num_leds++;}return priv;}
device_get_child_node_count 函数统计子节点数量,一般在在设备树中创建一个节点表示 LED 灯,然后在这个节点下面为每个 LED 灯创建一个子节点。因此子节点数量也是 LED 灯的数量。
二、树莓派4b的设备树节点
在linux内核的.config 配置文件文件中有
## Other Architectures#CONFIG_ARCH_BCM2835=y# CONFIG_ARCH_BCM_53573 is not set# CONFIG_ARCH_BCM_63XX is not set# CONFIG_ARCH_BRCMSTB is not set
说明会编译“CONFIG_ARCH_BCM2835”配置下的设备树文件。
在 arch/arm/boot/dts/Makefile文件中有如下内容:
dtb-$(CONFIG_ARCH_BCM2835) += \bcm2708-rpi-b.dtb \bcm2708-rpi-b-rev1.dtb \bcm2708-rpi-b-plus.dtb \bcm2708-rpi-cm.dtb \bcm2708-rpi-zero.dtb \bcm2708-rpi-zero-w.dtb \bcm2709-rpi-2-b.dtb \bcm2710-rpi-2-b.dtb \bcm2710-rpi-3-b.dtb \bcm2710-rpi-3-b-plus.dtb \bcm2711-rpi-4-b.dtb \bcm2710-rpi-cm3.dtb \bcm2711-rpi-cm4.dtb..........dtb-$(CONFIG_ARCH_BCM2835) += \bcm2835-rpi-b.dtb \bcm2835-rpi-a.dtb \bcm2835-rpi-b-rev2.dtb \bcm2835-rpi-b-plus.dtb \bcm2835-rpi-a-plus.dtb \bcm2835-rpi-cm1-io1.dtb \bcm2836-rpi-2-b.dtb \bcm2837-rpi-3-a-plus.dtb \bcm2837-rpi-3-b.dtb \bcm2837-rpi-3-b-plus.dtb \bcm2837-rpi-cm3-io3.dtb \bcm2711-rpi-4-b.dtb \bcm2835-rpi-zero.dtb \bcm2835-rpi-zero-w.dtb........# Enable fixups to support overlays on BCM2835 platformsifeq ($(CONFIG_ARCH_BCM2835),y)DTC_FLAGS ?= -@endif
编译设备树的时候会将这些.dts文件 编译为二进制的.dtb文件。如果使用uboot 启动linux内核,则uboot 中会使用 bootz 或 bootm命令向 Linux 内核传递二进制设备树文件(.dtb))。树莓派的bootloader会根据板子选择加载具体的设备树文件。
在arch/arm/boot/dts/bcm2711-rpi-4-b.dts文件中有:
#include "bcm2711.dtsi"#include "bcm2835-rpi.dtsi"......leds {act {gpios = <&gpio 42 GPIO_ACTIVE_HIGH>;};pwr {label = "PWR";gpios = <&expgpio 2 GPIO_ACTIVE_LOW>;default-state = "keep";linux,default-trigger = "default-on";};};......&leds {act_led: act {label = "led0";linux,default-trigger = "mmc0";gpios = <&gpio 42 GPIO_ACTIVE_HIGH>;};pwr_led: pwr {label = "led1";linux,default-trigger = "default-on";gpios = <&expgpio 2 GPIO_ACTIVE_LOW>;};};
在arch/arm/boot/dts/bcm2835-rpi.dtsi文件中有:
leds {compatible = "gpio-leds";act {label = "ACT";default-state = "keep";linux,default-trigger = "heartbeat";};};
说明:
①、创建了一个节点leds表示 LED 灯设备。
②、 leds节点的 compatible 属性值为“gpio-leds”。
③、 leds节点的两个子节点,都有一个 label 属性“led0”和“led0”, label 属性一般表示LED 灯的名字。
④、这两个子节点都有 gpios 属性值,表示此 LED 所使用的 GPIO 引脚。
⑤、这两个子节点都设置“linux,default-trigger”属性值,也就是设置 LED 灯的默认功能。
三、运行测试
启动树莓派以后查看/sys/bus/platform/devices/leds目录。证明在platform总线上加载了节点leds。在/sys/bus/platform/devices/leds目录下进入子目录leds。在 leds 目录下有两个个名为“led0”和“led1”的子目录,这两个子目录的名字就是设备树中两个子节点的 label 属性值。
同时在目录sys/devices/platform/leds中也有相同的内容。
测试时通过/sys/class/leds/led[LED_ID]/trigger文件进行配置。其中[LED_ID]需要替换为 0(代表 ACT LED)或 1(代表 PWR LED)。