文章目录
沉淀、分享、成长,让自己和他人都能有所收获!😄
📢我们以前都是自己编写 LED
灯驱动,其实像 LED
灯这样非常基础的设备驱动,Linux
内核已经集成了。Linux
内核的 LED
灯驱动采用 platform
框架,因此我们只需要按照要求在设备树文件中添加相应的 LED
节点即可。
一、LED 驱动使能
要使用 Linux
内核自带的 LED
灯驱动首先得先配置 Linux
内核,使能自带的 LED
灯驱动,输入如下命令打开 Linux
配置菜单:
make ARCH=arm64 menuconfig
按照如下路径打开 LED
驱动配置项:
-> Device Drivers
-> LED Support
-> 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
就会等于‘y
’,Linux
会根据 CONFIG_LEDS_GPIO
的值来选择如何编译 LED
灯驱动,如果为‘y
’就将其编译进 Linux
内核。
配置好 Linux 内核以后退出配置界面,打开.config
文件,会找到“CONFIG_LEDS_GPIO=y
” 这一行:
二、 Linux 内核自带 LED 驱动简介
2.1、LED 灯驱动框架分析
LED
灯驱动文件为/drivers/leds/leds-gpio.c
,大家可以打开/drivers/leds/Makefile
这个文件,找到如下所示内容:
1 # SPDX-License-Identifier: GPL-2.0
2
3 # LED Core
4 obj-$(CONFIG_NEW_LEDS) += led-core.o
......
29 obj-$(CONFIG_LEDS_PCA9532) += leds-pca9532.o
30 obj-$(CONFIG_LEDS_GPIO_REGISTER) += leds-gpio-register.o
31 obj-$(CONFIG_LEDS_GPIO) += leds-gpio.o
32 obj-$(CONFIG_LEDS_LP3944) += leds-lp3944.o
......
第 31 行,如果定义了 CONFIG_LEDS_GPIO 的话就会编译 leds-gpio.c 这个文件,我们选择将 LED 驱动编译进 Linux 内核,在.config 文件中就会有“CONFIG_LEDS_GPIO=y”这一行,因此 leds-gpio.c 驱动文件就会被编译。
接下来我们看一下 leds-gpio.c 这个驱动文件,找到如下所示内容:
215 static const struct of_device_id of_gpio_leds_match[] = {
216 { .compatible = "gpio-leds", },
217 {},
218 };
219
220 MODULE_DEVICE_TABLE(of, of_gpio_leds_match);
......
267 static struct platform_driver gpio_led_driver = {
268 .probe = gpio_led_probe,
269 .shutdown = gpio_led_shutdown,
270 .driver = {
271 .name = "leds-gpio",
272 .of_match_table = of_gpio_leds_match,
273 },
274 };
275
276 module_platform_driver(gpio_led_driver);
第 215~228 行,LED 驱动的匹配表,此表只有一个匹配项,compatible 内容为“gpio-leds”,因此设备树中的 LED 灯设备节点的 compatible 属性值也要为“gpio-leds”,否则设备和驱动匹配不成功,驱动就没法工作。
第 267~274 行,platform_driver 驱动结构体变量,可以看出,Linux 内核自带的 LED 驱动采用了 platform 框架。第 268 行可以看出 probe 函数为 gpio_led_probe,因此当驱动和设备匹配成功以后 gpio_led_probe 函数就会执行。从 271 行可以看出,驱动名字为“leds-gpio”,因此会在/sys/bus/platform/drivers 目录下存在一个名为“leds-gpio”的文件。
2.2、module_platform_driver 函数简析
在上一小节中我们知道 LED
驱动会采用 module_platform_driver 函数向 Linux 内核注册platform 驱动,其实在 Linux 内核中会大量采用 module_platform_driver 来完成向 Linux 内核注册 platform 驱动的操作。module_platform_driver 定义在 include/linux/platform_device.h 文件中,为一个宏,定义如下:
1 #define module_platform_driver(__platform_driver) \
2 module_driver(__platform_driver, platform_driver_register, \
3 platform_driver_unregister)
可以看出,module_platform_driver
依赖 module_driver
,module_driver
也是一个宏,定义在 include/linux/device.h
文件中,内容如下:
1 #define module_driver(__driver, __register, __unregister, ...) \
2 static int __init __driver##_init(void) \
3 { \
4 return __register(&(__driver) , ##__VA_ARGS__); \
5 } \
6 module_init(__driver##_init); \
7 static void __exit __driver##_exit(void) \
8 { \
9 __unregister(&(__driver) , ##__VA_ARGS__); \
10 } \
11 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);
2.3、gpio_led_probe 函数简析
当驱动和设备匹配以后 gpio_led_probe 函数就会执行,此函数主要是从设备树中获取 LED灯的 GPIO 信息,缩减后的函数内容如下
1 static int gpio_led_probe(struct platform_device *pdev)
2 {
3 struct gpio_led_platform_data *pdata = dev_get_platdata(&pdev->dev);
4 struct gpio_leds_priv *priv;
5 int i, ret = 0;
6
7 if (pdata && pdata->num_leds) { /* 非设备树方式 */
...... /* 获取 platform_device 信息 */
21 }
22 } else { /* 采用设备树 */
23 priv = gpio_leds_create(pdev);
24 if (IS_ERR(priv))
25 return PTR_ERR(priv);
26 }
27
28 platform_set_drvdata(pdev, priv);
29
30 return 0;
31 }
第 23~25 行,如果使用设备树的话,使用 gpio_leds_create 函数从设备树中提取设备信息,获取到的 LED 灯 GPIO 信息保存在返回值中,gpio_leds_create 函数内容如下:
1 static struct gpio_leds_priv *gpio_leds_create(struct platform_device
*pdev)
2 {
3 struct device *dev = &pdev->dev;
4 struct fwnode_handle *child;
5 struct gpio_leds_priv *priv;
6 int count, ret;
7
8 count = device_get_child_node_count(dev);
9 if (!count)
10 return ERR_PTR(-ENODEV);
11
12 priv = devm_kzalloc(dev, sizeof_gpio_leds_priv(count), GFP_KERNEL);
13 if (!priv)
14 return ERR_PTR(-ENOMEM);
15
16 device_for_each_child_node(dev, child) {
17 struct gpio_led_data *led_dat = &priv->leds[priv->num_leds];
18 struct gpio_led led = {};
19 const char *state = NULL;
20 struct device_node *np = to_of_node(child);
21
22 ret = fwnode_property_read_string(child, "label", &led.name);
23 if (ret && IS_ENABLED(CONFIG_OF) && np)
24 led.name = np->name;
25 if (!led.name) {
26 fwnode_handle_put(child);
27 return ERR_PTR(-EINVAL);
28 }
29
30 led.gpiod = devm_fwnode_get_gpiod_from_child(dev, NULL, child,
31 GPIOD_ASIS,
32 led.name);
33 if (IS_ERR(led.gpiod)) {
34 fwnode_handle_put(child);
35 return ERR_CAST(led.gpiod);
36 }
37
38 fwnode_property_read_string(child, "linux,default-trigger",
39 &led.default_trigger);
40
41 if (!fwnode_property_read_string(child, "default-state",
42 &state)) {
43 if (!strcmp(state, "keep"))
44 led.default_state = LEDS_GPIO_DEFSTATE_KEEP;
45 else if (!strcmp(state, "on"))
46 led.default_state = LEDS_GPIO_DEFSTATE_ON;
47 else
48 led.default_state = LEDS_GPIO_DEFSTATE_OFF;
49 }
50
51 if (fwnode_property_present(child, "retain-state-suspended"))
52 led.retain_state_suspended = 1;
53 if (fwnode_property_present(child, "retain-state-shutdown"))
54 led.retain_state_shutdown = 1;
55 if (fwnode_property_present(child, "panic-indicator"))
56 led.panic_indicator = 1;
57
58 ret = create_gpio_led(&led, led_dat, dev, np, NULL);
59 if (ret < 0) {
60 fwnode_handle_put(child);
61 return ERR_PTR(ret);
62 }
63 led_dat->cdev.dev->of_node = np;
64 priv->num_leds++;
65 }
66
67 return priv;
68 }
- 第 8 行,调用 device_get_child_node_count 函数统计子节点数量,一般在设备树中创建一个节点表示 LED 灯,然后在这个节点下面为每个 LED 灯创建一个子节点。因此子节点数量也是LED 灯的数量。
- 第 16 行,遍历每个子节点,获取每个子节点的信息。
- 第 30 行,获取 LED 灯所使用的 GPIO 信息。
- 第 38~39 行,获取“linux,default-trigger”属性值,可以通过此属性设置某个 LED 灯在 Linux系统中的默认功能,比如作为系统心跳指示灯等等。
- 第 41~42 行,获取“default-state”属性值,也就是 LED 灯的默认状态属性。
- 第 58 行,调用 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,设置为输出等。
三、设备树节点编写
打开文档 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
这个文档来查看可选功能,比如:
backlight
:LED 灯作为背光。
default-on
:LED 灯打开。
heartbeat
:LED 灯作为心跳指示灯,可以作为系统运行提示灯。
disk-activity
:LED 灯作为磁盘活动指示灯。
ide-disk
:LED 灯作为硬盘活动指示灯。
timer
:LED 灯周期性闪烁,由定时器驱动,闪烁频率可以修改。 -
可以设置“default-state”属性值,可以设置为 on、off 或 keep,为 on 的时候 LED 灯默认打开,为 off 的话 LED 灯默认关闭,为 keep 的话 LED 灯保持当前模式。
默认我们把RK3568开发板上的1个LED灯作为了系统运行指示灯,LED连接到GPIO0_C0引脚上。LED 用作系统指示灯,名字为“work”,。打开 rk3568-evb.dtsi 文件,找到如下内容:
1 leds: leds {
2 compatible = "gpio-leds";
3 work_led: work {
4 gpios = <&gpio0 RK_PC0 GPIO_ACTIVE_HIGH>;
5 linux,default-trigger = "heartbeat";
6 status = "okay";
7 };
8 };
第 3~7 行是开发板上的 LED,这里将 LED 用作了“heartbeat”,也就是心跳灯,因此大家烧写出厂系统会发现绿色的 LED 灯会一直闪烁。
四、测试
启动开发板,启动以后查看/sys/bus/platform/devices/leds 这个目录是否存在:
进入到 leds/leds 目录中,会显示一个work目录。
首先查看一下系统中有没有“/sys/class/leds/user-led/brightness”这个文件,通过操作这两个文件即可实现 LED 的打开和关闭。
注意!由于将 LED,也就是绿色 LED 灯作为了心跳灯,因此大家使用上述命令打开和关闭会看不出来效果,必须要先禁止掉 LED 的心跳灯功能,输入如下命令:
echo none > /sys/class/leds/work/trigger //关闭 LED 的心跳灯功能
关闭心跳灯功能后就可以使用前面命令来打开和关闭 LED 了。如果有的话就输入如下命令打开 user-led(LED1):
echo 1 > /sys/class/leds/work/brightness //打开绿色 LED
关闭 LED1 的命令如下:
echo 0 > /sys/class/leds/work/brightness //关闭绿色 LED
如果能正常的打开和关闭 LED1 灯话就说明我们 Linux 内核自带的 LED 灯驱动工作正常。