树莓派的Linux 内核已经集成了LED 灯驱动。 Linux 内核的 LED 灯驱动采用 platform 框架。编译树莓派linux内核时,会输入配置命令构建配置linux内核,如:
cd linux
KERNEL=kernel7l
make 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 set
CONFIG_LEDS_PCA9532=m
# CONFIG_LEDS_PCA9532_GPIO is not set
CONFIG_LEDS_GPIO=y
一、Linux 内核自带 LED 驱动
打开/drivers/leds/Makefile 这个文件,有:
obj-$(CONFIG_LEDS_GPIO_REGISTER) += leds-gpio-register.o
obj-$(CONFIG_LEDS_GPIO) += leds-gpio.o
obj-$(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;
else
led_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;
else
led.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 platforms
ifeq ($(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)。