赞
踩
1)实验平台:正点原子ATK-DLRK3568开发板
2)平台购买地址:https://detail.tmall.com/item.htm?id=731866264428
3)全套实验源码+手册+视频下载地址: http://www.openedv.com/docs/boards/xiaoxitongban
上一章我们详细的讲解了设备树语法以及在驱动开发中常用的OF函数,本章我们就开始第一个基于设备树的Linux驱动实验。本章在第七章实验的基础上完成,只是将其驱动开发改为设备树形式而已。
9.1 设备树LED驱动原理
在《第七章 新字符设备驱动实验》中,我们直接在驱动文件newchrled.c中定义有关寄存器物理地址,然后使用io_remap函数进行内存映射,得到对应的虚拟地址,最后操作寄存器对应的虚拟地址完成对GPIO的初始化。本章我们在第七章实验基础上完成,本章我们使用设备树来向Linux内核传递相关的寄存器物理地址,Linux驱动文件使用上一章讲解的OF函数从设备树中获取所需的属性值,然后使用获取到的属性值来初始化相关的IO。本章实验还是比较简单的,本章实验重点内容如下:
①、在rk3568-atk-evb1-ddr4-v10.dtsi文件中创建相应的设备节点。
②、编写驱动程序(在第七章实验基础上完成),获取设备树中的相关属性值。
③、使用获取到的有关属性值来初始化LED所使用的GPIO。
9.2 硬件原理图分析
本实验的硬件原理参考6.2小节即可。
9.3 实验程序编写
9.3.1 修改设备树文件
在根节点“/”下创建一个名为“rk3568_led”的子节点,打开rk3568-atk-evb1-ddr4-v10.dtsi文件,在根节点“/”最后面输入如下所示内容:
示例代码9.3.1.1 rk3568_led节点
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,如图9.3.1.1所示:
图9.3.1.1 内核编译出来的zboot.img
图9.3.1.1中zboot.img就是编译出来的内核+设备树打包在一起的文件。
烧写方法很简单,分两步:
②、开发板进入LOADER模式,前提是开发板已经烧写了uboot,并且uboot正常运行。
③、打开RKDevTool工具,导入配置文件,然后将图9.3.1.1中的boot.img放到要烧写的目录,然后只烧写boot.img,详细请看《【正点原子】 ATK-DLRK3568嵌入式Linux系统开发手册》第5.2.3小节-烧录。
烧写完成以后启动开发板。Linux启动成功以后进入到/proc/device-tree/目录中查看是否有“rk3568_led”这个节点,结果如图9.3.1.3所示:
图9.3.1.2 rk3568_led节点
如果没有“rk3568_led”节点的话请重点检查下面两点:
①、检查设备树修改是否成功,也就是rk3568_led节点是否为根节点“/”的子节点。
②、检查是否使用新的设备树启动的Linux内核。
可以进入到图9.3.1.3中的rk3568_led目录中,查看一下都有哪些属性文件,结果如图9.3.1.4所示:
图9.3.1.4 rk3568_led节点文件
大家可以用cat命令查看一下compatible、status等属性值是否和我们设置的一致。
9.3.2 LED灯驱动程序编写
设备树准备好以后就可以编写驱动程序了,本章实验在第七章实验驱动文件newchrled.c的基础上修改而来。新建名为“04_dtsled”文件夹,然后在04_dtsled文件夹里面创建vscode工程,工作区命名为“dtsled”。工程创建好以后新建dtsled.c文件,在dtsled.c里面输入如下内容:
示例代码9.3.2.1 dtsled.c文件内容
1 #include <linux/types.h> 2 #include <linux/kernel.h> 3 #include <linux/delay.h> 4 #include <linux/ide.h> 5 #include <linux/init.h> 6 #include <linux/module.h> 7 #include <linux/errno.h> 8 #include <linux/gpio.h> 9 #include <linux/cdev.h> 10 #include <linux/device.h> 11 #include <linux/of.h> 12 #include <linux/of_address.h> 13 //#include <asm/mach/map.h> 14 #include <asm/uaccess.h> 15 #include <asm/io.h> 16 /*************************************************************** 17 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved. 18 文件名 : dtsled.c 19 作者 : 正点原子 20 版本 : V1.0 21 描述 : LED驱动文件。 22 其他 : 无 23 论坛 : www.openedv.com 24 日志 : 初版V1.0 2022/12/06 正点原子团队创建 25 ***************************************************************/ 26 #define DTSLED_CNT 1 /* 设备号个数 */ 27 #define DTSLED_NAME "dtsled" /* 名字 */ 28 #define LEDOFF 0 /* 关灯 */ 29 #define LEDON 1 /* 开灯 */ 30 31 /* 映射后的寄存器虚拟地址指针 */ 32 static void __iomem *PMU_GRF_GPIO0C_IOMUX_L_PI; 33 static void __iomem *PMU_GRF_GPIO0C_DS_0_PI; 34 static void __iomem *GPIO0_SWPORT_DR_H_PI; 35 static void __iomem *GPIO0_SWPORT_DDR_H_PI; 36 37 /* dtsled设备结构体 */ 38 struct dtsled_dev{ 39 dev_t devid; /* 设备号 */ 40 struct cdev cdev; /* cdev */ 41 struct class *class; /* 类 */ 42 struct device *device; /* 设备 */ 43 int major; /* 主设备号 */ 44 int minor; /* 次设备号 */ 45 struct device_node *nd; /* 设备节点 */ 46 }; 47 48 struct dtsled_dev dtsled; /* led设备 */ 49 50 /* 51 * @description : LED打开/关闭 52 * @param - sta : LEDON(0) 打开LED,LEDOFF(1) 关闭LED 53 * @return : 无 54 */ 55 void led_switch(u8 sta) 56 { 57 u32 val = 0; 58 if(sta == LEDON) { 59 val = readl(GPIO0_SWPORT_DR_H_PI); 60 val &= ~(0X1 << 0); /* bit0 清零*/ 61 val |= ((0X1 << 16) | (0X1 << 0)); /* bit16 置1,允许写bit0, 62 bit0,高电平*/ 63 writel(val, GPIO0_SWPORT_DR_H_PI); 64 }else if(sta == LEDOFF) { 65 val = readl(GPIO0_SWPORT_DR_H_PI); 66 val &= ~(0X1 << 0); /* bit0 清零*/ 67 val |= ((0X1 << 16) | (0X0 << 0)); /* bit16 置1,允许写bit0, 68 bit0,低电平 */ 69 writel(val, GPIO0_SWPORT_DR_H_PI); 70 } 71 } 72 73 /* 74 * @description : 取消映射 75 * @return : 无 76 */ 77 void led_unmap(void) 78 { 79 /* 取消映射 */ 80 iounmap(PMU_GRF_GPIO0C_IOMUX_L_PI); 81 iounmap(PMU_GRF_GPIO0C_DS_0_PI); 82 iounmap(GPIO0_SWPORT_DR_H_PI); 83 iounmap(GPIO0_SWPORT_DDR_H_PI); 84 } 85 86 /* 87 * @description : 打开设备 88 * @param - inode : 传递给驱动的inode 89 * @param - filp : 设备文件,file结构体有个叫做private_data的成员变量 90 * 一般在open的时候将private_data指向设备结构体。 91 * @return : 0 成功;其他 失败 92 */ 93 static int led_open(struct inode *inode, struct file *filp) 94 { 95 filp->private_data = &dtsled; /* 设置私有数据 */ 96 return 0; 97 } 98 99 /* 100 * @description : 从设备读取数据 101 * @param - filp : 要打开的设备文件(文件描述符) 102 * @param - buf : 返回给用户空间的数据缓冲区 103 * @param - cnt : 要读取的数据长度 104 * @param - offt : 相对于文件首地址的偏移 105 * @return : 读取的字节数,如果为负值,表示读取失败 106 */ 107 static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) 108 { 109 return 0; 110 } 111 112 /* 113 * @description : 向设备写数据 114 * @param - filp : 设备文件,表示打开的文件描述符 115 * @param - buf : 要写给设备写入的数据 116 * @param - cnt : 要写入的数据长度 117 * @param - offt : 相对于文件首地址的偏移 118 * @return : 写入的字节数,如果为负值,表示写入失败 119 */ 120 static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt) 121 { 122 int retvalue; 123 unsigned char databuf[1]; 124 unsigned char ledstat; 125 126 retvalue = copy_from_user(databuf, buf, cnt); 127 if(retvalue < 0) { 128 printk("kernel write failed!\r\n"); 129 return -EFAULT; 130 } 131 132 ledstat = databuf[0]; /* 获取状态值 */ 133 134 if(ledstat == LEDON) { 135 led_switch(LEDON); /* 打开LED灯 */ 136 } else if(ledstat == LEDOFF) { 137 led_switch(LEDOFF); /* 关闭LED灯 */ 138 } 139 return 0; 140 } 141 142 /* 143 * @description : 关闭/释放设备 144 * @param - filp : 要关闭的设备文件(文件描述符) 145 * @return : 0 成功;其他 失败 146 */ 147 static int led_release(struct inode *inode, struct file *filp) 148 { 149 return 0; 150 } 151 152 /* 设备操作函数 */ 153 static struct file_operations dtsled_fops = { 154 .owner = THIS_MODULE, 155 .open = led_open, 156 .read = led_read, 157 .write = led_write, 158 .release = led_release, 159 }; 160 161 /* 162 * @description : 驱动出口函数 163 * @param : 无 164 * @return : 无 165 */ 166 static int __init led_init(void) 167 { 168 u32 val = 0; 169 int ret; 170 u32 regdata[16]; 171 const char *str; 172 struct property *proper; 173 174 /* 获取设备树中的属性数据 */ 175 /* 1、获取设备节点:rk3568_led */ 176 dtsled.nd = of_find_node_by_path("/rk3568_led"); 177 if(dtsled.nd == NULL) { 178 printk("rk3568_led node not find!\r\n"); 179 goto fail_find_node; 180 } else { 181 printk("rk3568_led node find!\r\n"); 182 } 183 184 /* 2、获取compatible属性内容 */ 185 proper = of_find_property(dtsled.nd, "compatible", NULL); 186 if(proper == NULL) { 187 printk("compatible property find failed\r\n"); 188 } else { 189 printk("compatible = %s\r\n", (char*)proper->value); 190 } 191 192 /* 3、获取status属性内容 */ 193 ret = of_property_read_string(dtsled.nd, "status", &str); 194 if(ret < 0){ 195 printk("status read failed!\r\n"); 196 } else { 197 printk("status = %s\r\n",str); 198 } 199 200 /* 4、获取reg属性内容 */ 201 ret = of_property_read_u32_array(dtsled.nd, "reg", regdata, 16); 202 if(ret < 0) { 203 printk("reg property read failed!\r\n"); 204 } else { 205 u8 i = 0; 206 printk("reg data:\r\n"); 207 for(i = 0; i < 16; i++) 208 printk("%#X ", regdata[i]); 209 printk("\r\n"); 210 } 211 212 /* 初始化LED */ 213 /* 1、寄存器地址映射 */ 214 PMU_GRF_GPIO0C_IOMUX_L_PI = of_iomap(dtsled.nd, 0); 215 PMU_GRF_GPIO0C_DS_0_PI = of_iomap(dtsled.nd, 1); 216 GPIO0_SWPORT_DR_H_PI = of_iomap(dtsled.nd, 2); 217 GPIO0_SWPORT_DDR_H_PI = of_iomap(dtsled.nd, 3); 218 219 /* 2、设置GPIO0_C0为GPIO功能。*/ 220 val = readl(PMU_GRF_GPIO0C_IOMUX_L_PI); 221 val &= ~(0X7 << 0); /* bit2:0,清零 */ 222 val |= ((0X7 << 16) | (0X0 << 0)); /* bit18:16 置1,允许写bit2:0, 223 bit2:0:0,用作GPIO0_C0 */ 224 writel(val, PMU_GRF_GPIO0C_IOMUX_L_PI); 225 226 /* 3、设置GPIO0_C0驱动能力为level5 */ 227 val = readl(PMU_GRF_GPIO0C_DS_0_PI); 228 val &= ~(0X3F << 0); /* bit5:0清零*/ 229 val |= ((0X3F << 16) | (0X3F << 0)); /* bit21:16 置1,允许写bit5:0, 230 bit5:0:0,用作GPIO0_C0 */ 231 writel(val, PMU_GRF_GPIO0C_DS_0_PI); 232 233 /* 4、设置GPIO0_C0为输出 */ 234 val = readl(GPIO0_SWPORT_DDR_H_PI); 235 val &= ~(0X1 << 0); /* bit0 清零*/ 236 val |= ((0X1 << 16) | (0X1 << 0)); /* bit16 置1,允许写bit0, 237 bit0,高电平 */ 238 writel(val, GPIO0_SWPORT_DDR_H_PI); 239 240 /* 5、设置GPIO0_C0为低电平,关闭LED灯。*/ 241 val = readl(GPIO0_SWPORT_DR_H_PI); 242 val &= ~(0X1 << 0); /* bit0 清零*/ 243 val |= ((0X1 << 16) | (0X0 << 0)); /* bit16 置1,允许写bit0, 244 bit0,低电平 */ 245 writel(val, GPIO0_SWPORT_DR_H_PI); 246 247 /* 注册字符设备驱动 */ 248 /* 1、创建设备号 */ 249 if (dtsled.major) { /* 定义了设备号 */ 250 dtsled.devid = MKDEV(dtsled.major, 0); 251 ret = register_chrdev_region(dtsled.devid, DTSLED_CNT, DTSLED_NAME); 252 if(ret < 0) { 253 pr_err("cannot register %s char driver [ret=%d]\n",DTSLED_NAME, DTSLED_CNT); 254 goto fail_devid; 255 } 256 } else { /* 没有定义设备号 */ 257 ret = alloc_chrdev_region(&dtsled.devid, 0, DTSLED_CNT, DTSLED_NAME); /* 申请设备号 */ 258 if(ret < 0) { 259 pr_err("%s Couldn't alloc_chrdev_region, ret=%d\r\n", DTSLED_NAME, ret); 260 goto fail_devid; 261 } 262 dtsled.major = MAJOR(dtsled.devid); /* 获取分配号的主设备号 */ 263 dtsled.minor = MINOR(dtsled.devid); /* 获取分配号的次设备号 */ 264 265 } 266 267 printk("dtsled major=%d,minor=%d\r\n",dtsled.major, dtsled.minor); 268 269 /* 2、初始化cdev */ 270 dtsled.cdev.owner = THIS_MODULE; 271 cdev_init(&dtsled.cdev, &dtsled_fops); 272 273 /* 3、添加一个cdev */ 274 ret = cdev_add(&dtsled.cdev, dtsled.devid, DTSLED_CNT); 275 if(ret < 0) 276 goto del_unregister; 277 278 /* 4、创建类 */ 279 dtsled.class = class_create(THIS_MODULE, DTSLED_NAME); 280 if (IS_ERR(dtsled.class)) { 281 goto del_cdev; 282 } 283 284 /* 5、创建设备 */ 285 dtsled.device = device_create(dtsled.class, NULL, dtsled.devid, NULL, DTSLED_NAME); 286 if (IS_ERR(dtsled.device)) { 287 goto destroy_class; 288 } 289 290 return 0; 291 292 destroy_class: 293 class_destroy(dtsled.class); 294 del_cdev: 295 cdev_del(&dtsled.cdev); 296 del_unregister: 297 unregister_chrdev_region(dtsled.devid, DTSLED_CNT); 298 fail_devid: 299 led_unmap(); 300 fail_find_node: 301 return -EIO; 302 } 303 304 /* 305 * @description : 驱动出口函数 306 * @param : 无 307 * @return : 无 308 */ 309 static void __exit led_exit(void) 310 { 311 /* 取消映射 */ 312 led_unmap(); 313 314 /* 注销字符设备驱动 */ 315 cdev_del(&dtsled.cdev);/* 删除cdev */ 316 unregister_chrdev_region(dtsled.devid, DTSLED_CNT); /* 注销设备号 */ 317 318 device_destroy(dtsled.class, dtsled.devid); 319 class_destroy(dtsled.class); 320 } 321 322 module_init(led_init); 323 module_exit(led_exit); 324 MODULE_LICENSE("GPL"); 325 MODULE_AUTHOR("ALIENTEK"); 326 MODULE_INFO(intree, "Y");
dtsled.c文件中的内容和第七章的newchrled.c文件中的内容基本一样,只是dtsled.c中包含了处理设备树的代码,我们重点来看一下这部分代码。
第45行,在设备结构体dtsled_dev中添加了成员变量nd,nd是device_node结构体类型指针,表示设备节点。如果我们要读取设备树某个节点的属性值,首先要先得到这个节点,一般在设备结构体中添加device_node指针变量来存放这个节点。
第176~182行,通过of_find_node_by_path函数得到rk3568_led节点,后续其他的OF函数要使用device_node。
第185~190行,通过of_find_property函数获取rk3568_led节点的compatible属性,返回值为property结构体类型指针变量,property的成员变量value表示属性值。
第193~198行,通过of_property_read_string函数获取rk3568_led节点的status属性值。
第201~210行,通过of_property_read_u32_array函数获取rk3568_led节点的reg属性所有值,并且将获取到的值都存放到regdata数组中。第208行将获取到的reg属性值依次输出到终端上。
第214~217行,使用of_iomap函数一次性完成读取reg属性以及内存映射,of_iomap函数是设备树推荐使用的OF函数。
9.3.3 编写测试APP
本章直接使用第七章的测试APP,将上一章的ledApp.c文件复制到本章实验工程下即可。
9.4.1 编译驱动程序和测试APP
1、编译驱动程序
编写Makefile文件,本章实验的Makefile文件和第五章实验基本一样,只是将obj-m变量的值改为dtsled.o,Makefile内容如下所示:
示例代码9.4.1.1 Makefile文件
1 KERNELDIR := /home/alientek/rk3568_linux_sdk/kernel
......
4 obj-m := dtsled.o
......
11 clean:
12 $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
第4行,设置obj-m变量的值为dtsled.o。
输入如下命令编译出驱动模块文件:
make ARCH=arm64 //ARCH=arm64必须指定,否则编译会失败
编译成功以后就会生成一个名为“dtsled.ko”的驱动模块文件。
2、编译测试APP
输入如下命令编译测试ledApp.c这个测试程序:
/opt/atk-dlrk356x-toolchain/bin/aarch64-buildroot-linux-gnu-gcc ledApp.c -o ledApp
编译成功以后就会生成ledApp这个应用程序。
9.4.2 运行测试
先在开发板中输入如下命令关闭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 //加载驱动
驱动加载成功以后会在终端中输出一些信息,如图9.4.2.1所示:
图9.4.2.1 驱动加载成功以后输出的信息
从图9.4.2.1可以看出,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
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。