ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Linux 自带的 LED 灯驱动实验

Linux 自带的 LED 灯驱动实验 一、Linux内核自带LED驱动使能上一章节我们编写基于设备树的 platform LED 灯驱动其实 Linux 内核已经自带了 LED 灯驱动要使用 Linux 内核自带的 LED 灯驱动首先得配置 Linux 内核使能自带的 LED 灯驱动输入如下命令打开 Linux 配置菜单make menuconfig按照如下路径打开 LED 驱动配置项- Device Drivers - LED Support (NEW_LEDS [y]) -LED Support for GPIO connected LEDs按照上述路径选择 “LED Support for GPIO connected LEDs”将其编译进 Linux 内核也即是在此选项上按下 “Y” 键使此选项前面变为 “*”如图在“LED Support for GPIO connected LEDs”上按下‘’可以打开此选项的帮助信息如图所示从图可以看出把 Linux 内部自带的 LED 灯驱动编译进内核以后CONFIG_LEDS_GPIO就会等于yLinux 会根据CONFIG_LEDS_GPIO的值来选择如何编译 LED 灯驱动如果为y就将其编译进 Linux 内核。配置好 Linux 内核以后退出配置界面打开.config文件会找到CONFIG_LEDS_GPIOy这一行如图所示重新编译Linux内核然后使用新编译出来的zImage镜像启动开发板。二、Linux内核自带LED驱动简介1、LED灯驱动框架分析LED 灯驱动文件为/drivers/leds/leds‑gpio.c打开/drivers/leds/Makefile这个文件找到如下所示内容# LED Core obj‑$(CONFIG_NEW_LEDS) led‑core.o …… 23 obj‑$(CONFIG_LEDS_GPIO_REGISTER) leds‑gpio‑register.o 24 obj‑$(CONFIG_LEDS_GPIO) leds‑gpio.o 25 obj‑$(CONFIG_LEDS_LP3944) leds‑lp3944.o ……第 24 行如果定义了CONFIG_LEDS_GPIO的话就会编译leds‑gpio.c这个文件在上一小节我们选择将 LED 驱动编译进 Linux 内核在.config文件中就会有CONFIG_LEDS_GPIOy这一行因此leds‑gpio.c驱动文件就会被编译。接下来我们看一下leds‑gpio.c这个驱动文件找到如下所示内容236 static const struct of_device_id of_gpio_leds_match[] { 237 { .compatible gpio‑leds, }, 238 {}, 239 }; ...... 290 static struct platform_driver gpio_led_driver { 291 .probe gpio_led_probe, 292 .remove gpio_led_remove, 293 .driver { 294 .name leds‑gpio, 295 .of_match_table of_gpio_leds_match, 296 }, 297 }; 299 module_platform_driver(gpio_led_driver);第 236~239 行LED 驱动的匹配表此表只有一个匹配项compatible内容为gpio‑leds因此设备树中的 LED 设备节点的compatible属性值也要为gpio‑leds否则设备和驱动匹配不成功驱动就没法工作。第 290~296 行platform_driver驱动结构体变量可以看出Linux 内核自带的 LED 驱动采用了 platform 框架。第 291 行可以看出probe函数为gpio_led_probe因此当驱动和设备匹配成功以后gpio_led_probe函数就会执行。从 294 行可以看出驱动名字为leds‑gpio因此会在/sys/bus/platform/drivers目录下存在一个名为leds‑gpio的文件如图所示第299行通过module_platform_driver函数向Linux内核注册gpio_led_driver这个platform 驱动。2、module_platform_driver函数简析在上一小节中我们知道 LED 驱动会采用module_platform_driver宏向 Linux 内核注册 platform 驱动其实在 Linux 内核中会大量采用module_platform_driver来完成向 Linux 内核注册 platform 驱动的操作。module_platform_driver定义在include/linux/platform_device.h文件中为一个宏定义如下#define module_platform_driver(__platform_driver) \ module_driver(__platform_driver, platform_driver_register, \ platform_driver_unregister)可以看出module_platform_driver依赖module_drivermodule_driver也是一个宏定义在include/linux/device.h文件中内容如下#define module_driver(__driver, __register, __unregister, ...) \ static int __init __driver##_init(void) \ { \ return __register((__driver) , ##__VA_ARGS__); \ } \ module_init(__driver##_init); \ static void __exit __driver##_exit(void) \ { \ __unregister((__driver) , ##__VA_ARGS__); \ } \ module_exit(__driver##_exit);所以将module_platform_driver(gpio_led_driver)展开以后就是static int __init gpio_led_driver_init(void) { return platform_driver_register ((gpio_led_driver)); } module_init(gpio_led_driver_init); static void __exit gpio_led_driver_exit(void) { platform_driver_unregister ((gpio_led_driver)); } module_exit(gpio_led_driver_exit);上面的代码就是标准的注册和删除 platform 驱动。因此module_platform_driver宏的功能就是完成 platform 驱动的注册和删除。3、gpio_led_probe 函数简析当驱动和设备匹配以后gpio_led_probe函数就会执行此函数主要是从设备树中获取 LED 灯的 GPIO 信息缩减后的函数内容如下所示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) { /* 非设备树方式 */ /* 获取platform_device信息 */ …… } 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; struct device_node *np; 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 led {}; const char *state NULL; led.gpiod devm_get_gpiod_from_child(dev, NULL, child); if (IS_ERR(led.gpiod)) { fwnode_handle_put(child); ret PTR_ERR(led.gpiod); goto err; } np of_node(child); if (fwnode_property_present(child, label)) { fwnode_property_read_string(child, label, led.name); } else { if (IS_ENABLED(CONFIG_OF) !led.name np) led.name np-name; if (!led.name) return ERR_PTR(-EINVAL); } …… }调用device_get_child_node_count函数统计子节点数量一般在设备树中创建一个节点表示 LED 灯然后在这个节点下面为每个 LED 灯创建一个子节点。因此子节点数量也是 LED 灯的数量。遍历每个子节点获取每个子节点的信息。获取 LED 灯所使用的 GPIO 信息。读取子节点label属性值因为使用label属性作为 LED 的名字。获取linux,default‑trigger属性值可以通过此属性设置某个 LED 灯在 Linux 系统中的默认功能比如作为系统心跳指示灯等等。获取default‑state属性值也就是 LED 灯的默认状态属性。调用create_gpio_led函数创建 LED 相关的 io其实就是设置 LED 所使用的 io 为输出之类的。create_gpio_led函数主要是初始化led_dat这个gpio_led_data结构体类型变量led_dat保存了 LED 的操作函数等内容。关于gpio_led_probe函数就分析到这里gpio_led_probe函数主要功能就是获取 LED 灯的设备信息然后根据这些信息来初始化对应的 IO设置为输出等。函数作用devm_kzalloc设备托管内存设备 detach 自动释放内存防止内存泄漏device_for_each_child_node遍历设备树子节点devm_get_gpiod_from_child从子节点解析 gpios 属性返回struct gpio_desc *新内核推荐使用 gpiod 一套 API不再直接操作 gpio 编号fwnode_property_read_string读取设备树字符串属性 (label)IS_ERR()/PTR_ERR()内核指针错误判断把错误码转为指针把错误指针转回错误码三、设备树节点编写打开文档Documentation/devicetree/bindings/leds/leds‑gpio.txt此文档详细的讲解了 Linux 自带驱动对应的设备树节点该如何编写我们在编写设备节点的时候要注意以下几点 ①、创建一个节点表示 LED 灯设备比如dtsleds如果板子上有多个 LED 灯的话每个 LED 灯都作为dtsleds的子节点。 ②、dtsleds节点的compatible属性值一定要为gpio‑leds。 ③、设置label属性此属性为可选每个子节点都有一个label属性label属性一般表示 LED 灯的名字比如以颜色区分的话就是red、green等等。 ④、每个子节点必须要设置gpios属性值表示此 LED 所使用的 GPIO 引脚 ⑤、可以设置linux,default‑trigger属性值也就是设置 LED 灯的默认功能可以查阅Documentation/devicetree/bindings/leds/common.txt这个文档来查看可选功能比如backlightLED 灯作为背光。default‑onLED 灯打开。heartbeatLED 灯作为心跳指示灯可以作为系统运行提示灯。ide‑diskLED 灯作为硬盘活动指示灯。timerLED 灯周期性闪烁由定时器驱动闪烁频率可以修改⑥、可以设置default‑state属性值可以设置为on、off或keep为on的时候 LED 灯默认打开为off的话 LED 灯默认关闭为keep的话 LED 灯保持当前模式。根据上述几条要求在imx6ull‑alientek‑emmc.dts中添加如下所示 LED 灯设备节点dtsleds { compatible gpio‑leds; led0 { label red; gpios gpio1 3 GPIO_ACTIVE_LOW; default‑state off; }; };因为 I.MX6U‑ALPHA 开发板只有一个 LED0因此在dtsleds这个节点下只有一个子节点led0LED0 名字为red默认关闭。修改完成以后保存并重新编译设备树然后用新的设备树启动开发板。四、运行测试用新的zImage和imx6ull-alientek-emmc.dtb启动开发板启动以后看/sys/bus/platform/devices/dtsleds 这个目录是否存在如果存在的话就如到此目录中如图所示进入到leds目录中此目录中的内容如图所示此目录下有一个名为red子目录这个子目录的名字就是我们在设备树中设置的label属性值。我们的设置究竟有没有用最终是要通过测试才能知道的首先查看一下系统中有没有/sys/class/leds/red/brightness这个文件如果有的话就输入如下命令打开 RED 这个 LED 灯echo 1 /sys/class/leds/red/brightness //打开LED0关闭 RED 这个 LED 灯的命令如下echo 0 /sys/class/leds/red/brightness //关闭LED0我们一般会使用一个 LED 灯作为系统指示灯系统运行正常的话这个 LED 指示灯就会一闪一闪的。里我们设置 LED0 作为系统指示灯在dtsleds这个设备节点中加入linux,default‑trigger属性信息即可属性值为heartbeat修改完以后的dtsleds节点内容如下dtsleds { compatible gpio‑leds; led0 { label red; gpios gpio1 3 GPIO_ACTIVE_LOW; linux,default‑trigger heartbeat; default‑state on; }; };第 7 行设置 LED0 为heartbeat。 第 8 行默认打开 LED0。重新编译设备树并且使用新的设备树启动 Linux 系统启动以后 LED0 就会闪烁作为系统心跳指示灯表示系统正在运行。觉得有帮助的话打赏一下呗。。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进