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

本文档详细介绍了在RK3568平台上如何利用Linux内核自带的LED子系统驱动进行开发。内容包括LED驱动的使能、Linux内核LED驱动框架分析、设备树节点编写以及测试步骤。通过配置内核、理解module_platform_driver函数和gpio_led_probe函数的工作原理,以及编写和测试设备树节点,实现了LED驱动的集成与功能验证。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

请添加图片描述

🚀返回专栏总目录

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

📢我们以前都是自己编写 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_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);
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 自带驱动对应的设备树节点该如何编写,我们在编写设备节点的时候要注意以下几点:

  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 };

第 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 灯驱动工作正常。

rk3568平台是一款针对高性能应用的芯片集成电路,被广泛应用于智能家居、智能终端等领域。在安卓开发中,传感器系统是其中非常重要的一部分,主要用于检测设备的姿态、位置、加速度、角速度等物理量,为应用提供相关的数据。本文将就rk3568平台安卓系统中的传感器系统进行分析。 首先,rk3568平台支持多种常见的传感器类型,包括加速度传感器、陀螺仪、磁场传感器、光线传感器、环境温湿度传感器等。这些传感器的数据在应用中可以被收集并解析,以展现设备在不同物理环境下的状态。 其次,rk3568平台上的传感器系统也支持传感器的扩展。用户可以通过添加更多的传感器模块来扩展设备的功能,从而实现更丰富的应用场景。这种扩展性在智能家居、智能终端等领域非常有用。 除了传感器的收集和解析,rk3568平台在安卓系统中还提供了传感器管理服务。这些服务可用于控制传感器的采样频率和精度,从而实现更好的能耗管理和性能优化。用户可以根据自己的应用需求,通过调整传感器管理服务的配置,达到最佳的使用效果。 最后,值得注意的是,rk3568平台上的传感器系统也存在一定的局限性。例如,在某些物理环境下,传感器数据可能会受到干扰和误差,导致应用使用不正常。因此,在应用开发中,需要对传感器数据进行有效处理和校准,以保证应用的稳定性和准确性。 总之,rk3568平台在安卓系统中的传感器系统具有非常重要的作用。它不仅为应用提供了丰富的物理数据,还通过传感器管理服务实现了能耗管理和性能优化。但是,开发者需要注意传感器数据的处理和校准,以确保应用的稳定性和准确性。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

内核笔记

你的鼓励是我的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值