RK3568 Linux驱动学习——设备树下 LED 驱动
前言
上一章详细的讲解了设备树语法以及在驱动开发中常用的 OF 函数,本章就开始
第一个基于设备树的 Linux 驱动实验。
设备树 LED 驱动原理
之前直接在驱动文件 newchrled.c 中定义有关寄存器物理地址,然后使用 io_remap 函数进行内存映射,得到对应的虚拟地址,最后操作寄存器对应的虚拟地址完成对 GPIO 的初始化。本章使用设备树来向 Linux 内核传递相关的寄存器物理地址,Linux 驱动文件使用上一章讲解的 OF 函数从设备树中获取所需的属性值,然后使用获取到的属性值来初始化相关的 IO。本章实验还是比较简单的,本章实验重点内容如下:
- 在 rk3568-atk-evb1-ddr4-v10.dtsi 文件中创建相应的设备节点。
- 编写驱动程序(在第七章实验基础上完成),获取设备树中的相关属性值。
- 使用获取到的有关属性值来初始化 LED 所使用的 GPIO。
硬件原理图分析
之前已经有了。
实验程序编写
修改设备树文件
在根节点 “/” 下创建一个名为“rk3568_led”的子节点,打开 rk3568-atk-evb1-ddr4-v10.dtsi文件,在根节点“/”最后面输入如下所示内容:
1 rk3568_led {
2 compatible = "atkrk3568-led";
3 status = "okay";
4 reg = <0x0 0xFDC20010 0x0 0x08 /* PMU_GRF_GPIO0C_IOMUX_L */
5 0x0 0xFDC20090 0x0 0x08 /* PMU_GRF_GPIO0C_DS_0 */
6 0x0 0xFDD60004 0x0 0x08 /* GPIO0_SWPORT_DR_H */
7 0x0 0xFDD6000C 0x0 0x08 >;/* GPIO0_SWPORT_DDR_H */
8 };
第 2 行,属性 compatible 设置 rk3568_led 节点兼容为“atkrk3568-led”。
第 3 行,属性 status 设置状态为“okay”。
第 4~7 行,reg 属性,非常重要!reg 属性设置了驱动里面所要使用的寄存器物理地址,比如第 4 行的“0x0 0xFDC20010 0x0 0x08”表示 RK3568 的 PMU_GRF_GPIO0C_IOMUX_L 寄存器,其中寄存器地址为 0xFDC20010,长度为 8 个字节。
设备树修改完成以后在 SDK 顶层目录输入如下命令重新编译一下内核:
./build.sh kernel
编译完成以后得到 boot.img,如下图所示:
上图中 boot.img 就是编译出来的内核+设备树打包在一起的文件。烧写方法很简单,分两步:
- 开发板进入 LOADER 模式,前提是开发板已经烧写了 uboot,并且 uboot 正常运行。
- 打开 RKDevTool 工具,导入配置文件,然后将上图中的 boot.img 放到要烧写的
目录,然后只烧写 boot.img。
或者可以在 Ubuntu 内如下完成烧写并重启:
cd ~/rk3568_linux_sdk/rockdev
sudo ../tools/linux/Linux_Upgrade_Tool/Linux_Upgrade_Tool/upgrade_tool UL MiniLoaderAll.bin -noreset
sudo ../tools/linux/Linux_Upgrade_Tool/Linux_Upgrade_Tool/upgrade_tool DI -p parameter.txt
sudo ../tools/linux/Linux_Upgrade_Tool/Linux_Upgrade_Tool/upgrade_tool DI -boot boot.img
sudo ../tools/linux/Linux_Upgrade_Tool/Linux_Upgrade_Tool/upgrade_tool RD
烧写完成以后启动开发板。Linux 启动成功以后进入到/proc/device-tree/目录中查看是否有 “rk3568_led”这个节点,结果如下图所示:
如果没有“rk3568_led”节点的话请重点检查下面两点:
- 检查设备树修改是否成功,也就是 rk3568_led 节点是否为根节点“/”的子节点。
- 检查是否使用新的设备树启动的 Linux 内核。
可以进入到上图中的 rk3568_led 目录中,查看一下都有哪些属性文件,结果如下图所示:
可以用 cat 命令查看一下 compatible、status 等属性值是否和设置的一致。
LED 灯驱动程序编写
与之前的 LED 很像,改了一些内容。
/* dtsled设备结构体 */
struct dtsled_dev{dev_t devid; /* 设备号 */struct cdev cdev; /* cdev */struct class *class; /* 类 */struct device *device; /* 设备 */int major; /* 主设备号 */int minor; /* 次设备号 */struct device_node *nd; /* 设备节点 */
};struct dtsled_dev dtsled; /* led设备 */
在设备结构体 dtsled_dev 中添加了成员变量 nd,nd 是 device_node 结构体类型指
针,表示设备节点。如果要读取设备树某个节点的属性值,首先要先得到这个节点,一般在设备结构体中添加 device_node 指针变量来存放这个节点。
/* 获取设备树中的属性数据 *//* 1、获取设备节点:rk3568_led */dtsled.nd = of_find_node_by_path("/rk3568_led");if(dtsled.nd == NULL) {printk("rk3568_led node not find!\r\n");goto fail_find_node;} else {printk("rk3568_led node find!\r\n");}/* 2、获取compatible属性内容 */proper = of_find_property(dtsled.nd, "compatible", NULL);if(proper == NULL) {printk("compatible property find failed\r\n");} else {printk("compatible = %s\r\n", (char*)proper->value);}/* 3、获取status属性内容 */ret = of_property_read_string(dtsled.nd, "status", &str);if(ret < 0){printk("status read failed!\r\n");} else {printk("status = %s\r\n",str);}/* 4、获取reg属性内容 */ret = of_property_read_u32_array(dtsled.nd, "reg", regdata, 16);if(ret < 0) {printk("reg property read failed!\r\n");} else {u8 i = 0;printk("reg data:\r\n");for(i = 0; i < 16; i++)printk("%#X ", regdata[i]);printk("\r\n");}/* 初始化LED *//* 1、寄存器地址映射 */PMU_GRF_GPIO0C_IOMUX_L_PI = of_iomap(dtsled.nd, 0);PMU_GRF_GPIO0C_DS_0_PI = of_iomap(dtsled.nd, 1);GPIO0_SWPORT_DR_H_PI = of_iomap(dtsled.nd, 2);GPIO0_SWPORT_DDR_H_PI = of_iomap(dtsled.nd, 3);
通过 of_find_node_by_path 函数得到 rk3568_led 节点,后续其他的 OF 函数要使用 device_node。
通过 of_find_property 函数获取 rk3568_led 节点的 compatible 属性,返回值为 property 结构体类型指针变量,property 的成员变量 value 表示属性值。
通过 of_property_read_string 函数获取 rk3568_led 节点的 status 属性值。
通过 of_property_read_u32_array 函数获取 rk3568_led 节点的 reg 属性所有值,并且将获取到的值都存放到 regdata 数组中。将获取到的 reg 属性值依次输出到终端上。
使用 of_iomap 函数一次性完成读取 reg 属性以及内存映射,of_iomap 函数是设备树推荐使用的 OF 函数。
编写测试 APP
这个可以直接用之前一个已经写好的。
编译驱动程序和测试 APP
编译驱动程序
编写 Makefile 文件,本章实验的 Makefile 文件和之前基本一样,只是将 obj-m 变量的值改为 dtsled.o,Makefile 内容如下所示:
KERNELDIR := /home/xhj/rk3568_linux_sdk/kernel
CURRENT_PATH := $(shell pwd)
obj-m := dtsled.obuild: kernel_moduleskernel_modules:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
输入如下命令编译出驱动模块文件:
make ARCH=arm64 //ARCH=arm64 必须指定,否则编译会失败
编译成功以后就会生成一个名为“dtsled.ko”的驱动模块文件。
编译测试 APP
输入如下命令编译测试 ledApp.c 这个测试程序:
/opt/atk-dlrk356x-toolchain/bin/aarch64-buildroot-linux-gnu-gcc ledApp.c -o ledApp
编译成功以后就会生成 ledApp 这个应用程序。
运行测试
先在开发板中输入如下命令关闭 LED 的心跳灯功能:
echo none > /sys/class/leds/work/trigger
在 Ubuntu 中将上一小节编译出来的 dtsled.ko 和 ledApp 这两个文件通过 adb 命令发送到开发板的/lib/modules/4.19.232 目录下,命令如下:
adb push dtsled.ko ledApp /lib/modules/4.19.232
发送成功以后进入到开发板目录 lib/modules/4.19.232 中,输入如下命令加载 dtsled.ko 驱动模块:
depmod //第一次加载驱动的时候需要运行此命令
modprobe dtsled //加载驱动
驱动加载成功以后会在终端中输出一些信息,如下图所示:
从上图可以看出,rk3568_led 这个节点找到了,并且 compatible 属性值为“atkrk3568-led”,status 属性值为“okay”,reg 属性的值为“0x0 0xFDC20010 0x0 0x08 0x0 0xFDC20090 0x0 0x08 0x0 0xFDD60004 0x0 0x08 0x0 0xFDD6000C 0x0 0x08”,这些都和设置的设备树一致。
驱动加载成功以后就可以使用 ledApp 软件来测试驱动是否工作正常,输入如下命令打开 LED 灯:
./ledApp /dev/dtsled 1 //打开 LED 灯
输入上述命令以后观察开发板上的红色 LED 灯是否点亮,如果点亮的话说明驱动工作正常。在输入如下命令关闭 LED 灯:
./ledApp /dev/dtsled 0 //关闭 LED 灯
输入上述命令以后观察开发板上的红色 LED 灯是否熄灭。如果要卸载驱动的话输入如下命令即可:
rmmod dtsled.ko