• RK3568平台开发系列讲解(驱动篇)Linux自带LED子系统驱动实验


    请添加图片描述

    🚀返回专栏总目录

    沉淀、分享、成长,让自己和他人都能有所收获!😄

    📢我们以前都是自己编写 LED 灯驱动,其实像 LED 灯这样非常基础的设备驱动Linux 内核已经集成了。Linux 内核的 LED 灯驱动采用 platform 框架,因此我们只需要按照要求在设备树文件中添加相应的 LED 节点即可。

    一、LED 驱动使能


    要使用 Linux 内核自带的 LED 灯驱动首先得先配置 Linux 内核,使能自带的 LED 灯驱动,输入如下命令打开 Linux 配置菜单:

    make ARCH=arm64 menuconfig
    
    • 1

    按照如下路径打开 LED 驱动配置项:

    -> Device Drivers 
    	-> LED Support
    	-> LED Support for GPIO connected LEDs
    
    • 1
    • 2
    • 3

    按照上述路径,选择“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
    ......
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    第 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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    第 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)
    
    • 1
    • 2
    • 3

    可以看出,module_platform_driver 依赖 module_drivermodule_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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    module_platform_driver(gpio_led_driver)
    
    • 1

    展开以后就是:

    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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    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 }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    第 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 }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 第 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 自带驱动对应的设备树节点该如何编写,我们在编写设备节点的时候要注意以下几点:

    1. 创建一个节点表示 LED 灯设备,比如 dtsleds,如果板子上有多个 LED 灯的话每个 LED灯都作为 dtsleds 的子节点。

    2. dtsleds 节点的 compatible 属性值一定要为“gpio-leds”。

    3. 设置 label 属性,此属性为可选,每个子节点都有一个 label 属性,label 属性一般表示LED 灯的名字,比如以颜色区分的话就是 red、green 等等。

    4. 每个子节点必须要设置 gpios 属性值,表示此 LED 所使用的 GPIO 引脚!

    5. 可以设置“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 灯周期性闪烁,由定时器驱动,闪烁频率可以修改。

    6. 可以设置“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 };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 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 的心跳灯功能
    
    • 1

    关闭心跳灯功能后就可以使用前面命令来打开和关闭 LED 了。如果有的话就输入如下命令打开 user-led(LED1):

    echo 1 > /sys/class/leds/work/brightness //打开绿色 LED 
    
    • 1

    关闭 LED1 的命令如下:

    echo 0 > /sys/class/leds/work/brightness //关闭绿色 LED
    
    • 1

    如果能正常的打开和关闭 LED1 灯话就说明我们 Linux 内核自带的 LED 灯驱动工作正常。

  • 相关阅读:
    实战Netty!基于私有协议,怎样快速开发网络通信服务?
    kuboard项目前端展示
    【5G MAC】Msg1 TX开环功控介绍
    靶场练习——SDcms文件上传漏洞靶场
    计算机网络(六)
    新恶意软件使用 MSIX 软件包来感染 Windows
    CSS图文悬停翻转效果完整源码附注释
    毛玻璃跟随鼠标移动
    Dubbo: 基于SpringBoot+Dubbo的Provider/Consumer的实践
    .NET 7 的 AOT 到底能不能扛反编译?
  • 原文地址:https://blog.csdn.net/qq_33487044/article/details/133797260