完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
1)实验平台:正点原子Linux开发板
2)摘自《正点原子I.MX6U嵌入式Linux驱动开发指南》 关注官方微信号公众号,获取更多资料:正点原子 上一章我们详细的讲解了设备树语法以及在驱动开发中常用的OF函数,本章我们就开始第一个基于设备树的Linux驱动实验。本章在第四十二章实验的基础上完成,只是将其驱动开发改为设备树形式而已。 44.1 设备树LED驱动原理在《第四十二章新字符设备驱动实验》中,我们直接在驱动文件newchrled.c中定义有关寄存器物理地址,然后使用io_remap函数进行内存映射,得到对应的虚拟地址,最后操作寄存器对应的虚拟地址完成对GPIO的初始化。本章我们在第四十二章实验基础上完成,本章我们使用设备树来向Linux内核传递相关的寄存器物理地址,Linux驱动文件使用上一章讲解的OF函数从设备树中获取所需的属性值,然后使用获取到的属性值来初始化相关的IO。本章实验还是比较简单的,本章实验重点内容如下: ①、在imx6ull-alientek-emmc.dts文件中创建相应的设备节点。 ②、编写驱动程序(在第四十二章实验基础上完成),获取设备树中的相关属性值。 ③、使用获取到的有关属性值来初始化LED所使用的GPIO。 44.2硬件原理图分析本章实验硬件原理图参考8.3小节即可。 44.3实验程序编写本实验对应的例程路径为:开发板光盘->2、Linux驱动例程->4_dtsled。 本章实验在四十二章实验的基础上完成,重点是将驱动改为基于设备树的. 44.3.1 修改设备树文件在根节"/"下创建一个名为"alphaled"的子节点,打开imx6ull-alientek-emmc.dts文件,在根节点"/"最后面输入如下所示内容: 示例代码44.3.1.1 alphaled节点 1 alphaled { 2 #address-cells =<1>; 3 #size-cells =<1>; 4 compatible ="atkalpha-led"; 5 status ="okay"; 6 reg =< 0X020C406C0X04 /* CCM_CCGR1_BASE */ 7 0X020E00680X04 /* SW_MUX_GPIO1_IO03_BASE */ 8 0X020E02F40X04 /* SW_PAD_GPIO1_IO03_BASE */ 9 0X0209C0000X04 /* GPIO1_DR_BASE */ 10 0X0209C0040X04>; /* GPIO1_GDIR_BASE */ 11}; 第2、3行,属性#address-cells和#size-cells都为1,表示reg属性中起始地址占用一个字长(cell),地址长度也占用一个字长(cell)。 第4行,属性compatbile设置alphaled节点兼容性为"atkalpha-led"。 第5行,属性status设置状态为"okay"。 第6~10行,reg属性,非常重要!reg属性设置了驱动里面所要使用的寄存器物理地址,比如第6行的"0X020C406C 0X04"表示I.MX6ULL的CCM_CCGR1寄存器,其中寄存器首地址为0X020C406C,长度为4个字节。 设备树修改完成以后输入如下命令重新编译一下imx6ull-alientek-emmc.dts: makedtbs 编译完成以后得到imx6ull-alientek-emmc.dtb,使用新的imx6ull-alientek-emmc.dtb启动Linux内核。Linux启动成功以后进入到/proc/device-tree/目录中查看是否有"alphaled"这个节点,结果如图44.3.1.1所示: 图44.3.1.1 alphaled节点 如果没有"alphaled"节点的话请重点下面两点: ①、检查设备树修改是否成功,也就是alphaled节点是否为根节点"/"的子节点。 ②、检查是否使用新的设备树启动的Linux内核。 可以进入到图44.3.1中的alphaled目录中,查看一下都有哪些属性文件,结果如图44.3.1.2所示: 图44.3.1.2 alphaled节点文件 大家可以查看一下compatible、status等属性值是否和我们设置的一致。 44.3.2 LED灯驱动程序编写设备树准备好以后就可以编写驱动程序了,本章实验在第四十二章实验驱动文件newchrled.c的基础上修改而来。新建名为"4_dtsled"文件夹,然后在4_dtsled文件夹里面创建vscode工程,工作区命名为"dtsled"。工程创建好以后新建dtsled.c文件,在dtsled.c里面输入如下内容: 示例代码44.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 2019/7/9 左忠凯创建 25 ***************************************************************/ 26 #define DTSLED_CNT 1 /* 设备号个数 */ 27 #define DTSLED_NAME "dtsled" /* 名字 */ 28 #define LEDOFF 0 /* 关灯 */ 29 #define LEDON 1 /* 开灯 */ 30 31/* 映射后的寄存器虚拟地址指针 */ 32staticvoid __iomem *IMX6U_CCM_CCGR1; 33staticvoid __iomem *SW_MUX_GPIO1_IO03; 34staticvoid __iomem *SW_PAD_GPIO1_IO03; 35staticvoid __iomem *GPIO1_DR; 36staticvoid __iomem *GPIO1_GDIR; 37 38/* dtsled设备结构体 */ 39struct dtsled_dev{ 40 dev_t devid; /* 设备号 */ 41struct cdev cdev; /* cdev */ 42struct class *class; /* 类 */ 43struct device *device; /* 设备 */ 44int major; /* 主设备号 */ 45int minor; /* 次设备号 */ 46struct device_node *nd;/* 设备节点 */ 47}; 48 49struct dtsled_dev dtsled;/* led设备 */ 50 51/* 52 * @description : LED打开/关闭 53 * @param - sta : LEDON(0) 打开LED,LEDOFF(1) 关闭LED 54 * @Return : 无 55 */ 56void led_switch(u8 sta) 57{ 58 u32 val =0; 59if(sta == LEDON){ 60 val = readl(GPIO1_DR); 61 val &=~(1<<3); 62 writel(val, GPIO1_DR); 63}elseif(sta == LEDOFF){ 64 val = readl(GPIO1_DR); 65 val|=(1<<3); 66 writel(val, GPIO1_DR); 67} 68} 69 70/* 71 * @description : 打开设备 72 * @param – inode : 传递给驱动的inode 73 * @param – filp : 设备文件,file结构体有个叫做private_data的成员变量 74 * 一般在open的时候将private_data指向设备结构体。 75 * @return : 0 成功;其他失败 76 */ 77staticint led_open(struct inode *inode,struct file *filp) 78{ 79 filp->private_data =&dtsled;/* 设置私有数据 */ 80return0; 81} 82 83/* 84 * @description : 从设备读取数据 85 * @param – filp : 要打开的设备文件(文件描述符) 86 * @param - buf : 返回给用户空间的数据缓冲区 87 * @param - cnt : 要读取的数据长度 88 * @param – offt : 相对于文件首地址的偏移 89 * @return : 读取的字节数,如果为负值,表示读取失败 90 */ 91static ssize_t led_read(struct file *filp,char __user *buf,size_t cnt, loff_t *offt) 92{ 93return0; 94} 95 96/* 97 * @description : 向设备写数据 98 * @param - filp : 设备文件,表示打开的文件描述符 99 * @param - buf : 要写给设备写入的数据 100 * @param - cnt : 要写入的数据长度 101 * @param – offt : 相对于文件首地址的偏移 102 * @return : 写入的字节数,如果为负值,表示写入失败 103 */ 104static ssize_t led_write(struct file *filp,constchar __user *buf,size_t cnt, loff_t *offt) 105{ 106int retvalue; 107unsignedchar databuf[1]; 108unsignedchar ledstat; 109 110 retvalue = copy_from_user(databuf, buf, cnt); 111if(retvalue <0){ 112 printk("kernel write failed!rn"); 113return-EFAULT; 114} 115 116 ledstat = databuf[0]; /* 获取状态值 */ 117 118if(ledstat == LEDON){ 119 led_switch(LEDON); /* 打开LED灯 */ 120}elseif(ledstat == LEDOFF){ 121 led_switch(LEDOFF); /* 关闭LED灯 */ 122} 123return0; 124} 125 126/* 127 * @description : 关闭/释放设备 128 * @param – filp : 要关闭的设备文件(文件描述符) 129 * @return : 0 成功;其他失败 130 */ 131staticint led_release(struct inode *inode,struct file *filp) 132{ 133return0; 134} 135 136/* 设备操作函数 */ 137staticstruct file_operations dtsled_fops ={ 138.owner = THIS_MODULE, 139.open = led_open, 140.read = led_read, 141.write = led_write, 142.release = led_release, 143}; 144 145/* 146 * @description : 驱动入口函数 147 * @param : 无 148 * @return : 无 149 */ 150staticint __init led_init(void) 151{ 152 u32 val =0; 153int ret; 154 u32 regdata[14]; 155constchar*str; 156struct property *proper; 157 158/* 获取设备树中的属性数据 */ 159/* 1、获取设备节点:alphaled */ 160 dtsled.nd = of_find_node_by_path("/alphaled"); 161if(dtsled.nd ==NULL){ 162 printk("alphaled node not find!rn"); 163return-EINVAL; 164}else{ 165 printk("alphaled node find!rn"); 166} 167 168/* 2、获取compatible属性内容 */ 169 proper = of_find_property(dtsled.nd,"compatible",NULL); 170if(proper ==NULL){ 171 printk("compatible property find failedrn"); 172}else{ 173 printk("compatible = %srn",(char*)proper->value); 174} 175 176/* 3、获取status属性内容 */ 177 ret = of_property_read_string(dtsled.nd,"status",&str); 178if(ret <0){ 179 printk("status read failed!rn"); 180}else{ 181 printk("status = %srn",str); 182} 183 184/* 4、获取reg属性内容 */ 185 ret = of_property_read_u32_array(dtsled.nd,"reg", regdata,10); 186if(ret <0){ 187 printk("reg property read failed!rn"); 188}else{ 189 u8 i =0; 190 printk("reg data:rn"); 191for(i =0; i <10; i++) 192 printk("%#X ", regdata); 193 printk("rn"); 194} 195 196/* 初始化LED */ 197 #if0 198/* 1、寄存器地址映射 */ 199 IMX6U_CCM_CCGR1 = ioremap(regdata[0], regdata[1]); 200 SW_MUX_GPIO1_IO03 = ioremap(regdata[2], regdata[3]); 201 SW_PAD_GPIO1_IO03 = ioremap(regdata[4], regdata[5]); 202 GPIO1_DR = ioremap(regdata[6], regdata[7]); 203 GPIO1_GDIR = ioremap(regdata[8], regdata[9]); 204 #else 205 IMX6U_CCM_CCGR1 = of_iomap(dtsled.nd,0); 206 SW_MUX_GPIO1_IO03 = of_iomap(dtsled.nd,1); 207 SW_PAD_GPIO1_IO03 = of_iomap(dtsled.nd,2); 208 GPIO1_DR = of_iomap(dtsled.nd,3); 209 GPIO1_GDIR = of_iomap(dtsled.nd,4); 210 #endif 211 212/* 2、使能GPIO1时钟 */ 213 val = readl(IMX6U_CCM_CCGR1); 214 val &=~(3<<26);/* 清楚以前的设置 */ 215 val |=(3<<26);/* 设置新值 */ 216 writel(val, IMX6U_CCM_CCGR1); 217 218/* 3、设置GPIO1_IO03的复用功能,将其复用为 219 * GPIO1_IO03,最后设置IO属性。 220 */ 221 writel(5, SW_MUX_GPIO1_IO03); 222 223/* 寄存器SW_PAD_GPIO1_IO03设置IO属性 */ 224 writel(0x10B0, SW_PAD_GPIO1_IO03); 225 226/* 4、设置GPIO1_IO03为输出功能 */ 227 val = readl(GPIO1_GDIR); 228 val &=~(1<<3);/* 清除以前的设置 */ 229 val |=(1<<3);/* 设置为输出 */ 230 writel(val, GPIO1_GDIR); 231 232/* 5、默认关闭LED */ 233 val = readl(GPIO1_DR); 234 val |=(1<<3); 235 writel(val, GPIO1_DR); 236 237/* 注册字符设备驱动 */ 238/* 1、创建设备号 */ 239if(dtsled.major){ /* 定义了设备号 */ 240 dtsled.devid = MKDEV(dtsled.major,0); 241 register_chrdev_region(dtsled.devid, DTSLED_CNT, DTSLED_NAME); 242}else{/* 没有定义设备号 */ 243 alloc_chrdev_region(&dtsled.devid,0, DTSLED_CNT, DTSLED_NAME);/* 申请设备号 */ 244 dtsled.major = MAJOR(dtsled.devid);/* 获取分配号的主设备号 */ 245 dtsled.minor = MINOR(dtsled.devid);/* 获取分配号的次设备号 */ 246} 247 printk("dtsled major=%d,minor=%drn",dtsled.major, dtsled.minor); 248 249/* 2、初始化cdev */ 250 dtsled.cdev.owner = THIS_MODULE; 251 cdev_init(&dtsled.cdev,&dtsled_fops); 252 253/* 3、添加一个cdev */ 254 cdev_add(&dtsled.cdev, dtsled.devid, DTSLED_CNT); 255 256/* 4、创建类 */ 257 dtsled.class = class_create(THIS_MODULE, DTSLED_NAME); 258if(IS_ERR(dtsled.class)){ 259return PTR_ERR(dtsled.class); 260} 261 262/* 5、创建设备 */ 263 dtsled.device = device_create(dtsled.class,NULL, dtsled.devid,NULL,DTSLED_NAME); 264if(IS_ERR(dtsled.device)){ 265return PTR_ERR(dtsled.device); 266} 267 268return0; 269} 270 271/* 272 * @description : 驱动出口函数 273 * @param : 无 274 * @return : 无 275 */ 276staticvoid __exit led_exit(void) 277{ 278/* 取消映射 */ 279 iounmap(IMX6U_CCM_CCGR1); 280 iounmap(SW_MUX_GPIO1_IO03); 281 iounmap(SW_PAD_GPIO1_IO03); 282 iounmap(GPIO1_DR); 283 iounmap(GPIO1_GDIR); 284 285/* 注销字符设备驱动 */ 286 cdev_del(&dtsled.cdev);/* 删除cdev */ 287 unregister_chrdev_region(dtsled.devid, DTSLED_CNT);/*注销设备号*/ 288 289 device_destroy(dtsled.class, dtsled.devid); 290 class_destroy(dtsled.class); 291} 292 293 module_init(led_init); 294 module_exit(led_exit); 295 MODULE_LICENSE("GPL"); 296 MODULE_AUTHOR("zuozhongkai"); dtsled.c文件中的内容和第四十二章的newchrled.c文件中的内容基本一样,只是dtsled.c中包含了处理设备树的代码,我们重点来看一下这部分代码。 第46行,在设备结构体dtsled_dev中添加了成员变量nd,nd是device_node结构体类型指针,表示设备节点。如果我们要读取设备树某个节点的属性值,首先要先得到这个节点,一般在设备结构体中添加device_node指针变量来存放这个节点。 第160~166行,通过of_find_node_by_path函数得到alphaled节点,后续其他的OF函数要使用device_node。 第169~174行,通过of_find_property函数获取alphaled节点的compatible属性,返回值为property结构体类型指针变量,property的成员变量value表示属性值。 第177~182行,通过of_property_read_string函数获取alphaled节点的status属性值。 第185~194行,通过of_property_read_u32_array函数获取alphaled节点的reg属性所有值,并且将获取到的值都存放到regdata数组中。第192行将获取到的reg属性值依次输出到终端上。 第199~203行,使用"古老"的ioremap函数完成内存映射,将获取到的regdata数组中的寄存器物理地址转换为虚拟地址。 第205~209行,使用of_iomap函数一次性完成读取reg属性以及内存映射,of_iomap函数是设备树推荐使用的OF函数。 44.3.3 编写测试APP本章直接使用第四十二章的测试APP,将上一章的ledApp.c文件复制到本章实验工程下即可。 44.4 运行测试44.4.1 编译驱动程序和测试APP1、编译驱动程序 编写Makefile文件,本章实验的Makefile文件和第四十章实验基本一样,只是将obj-m变量的值改为dtsled.o,Makefile内容如下所示: 示例代码44.4.1.1 Makefile文件 1 KERNELDIR:= /home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek ...... 4 obj-m := dtsled.o.o ...... 11 clean: 12$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean 第4行,设置obj-m变量的值为dtsled.o。 输入如下命令编译出驱动模块文件: make-j32 编译成功以后就会生成一个名为"dtsled.ko"的驱动模块文件。 2、编译测试APP 输入如下命令编译测试ledApp.c这个测试程序: arm-linux-gnueabihf-gcc ledApp.c -o ledApp 编译成功以后就会生成ledApp这个应用程序。 44.4.2 运行测试将上一小节编译出来的dtsled.ko和ledApp这两个文件拷贝到rootfs/lib/modules/4.1.15目录中,重启开发板,进入到目录lib/modules/4.1.15中,输入如下命令加载dtsled.ko驱动模块: depmod //第一次加载驱动的时候需要运行此命令 modprobe dtsled.ko //加载驱动 驱动加载成功以后会在终端中输出一些信息,如图44.4.2.1所示: 图44.4.2.1 驱动加载成功以后输出的信息 从图44.4.2.1可以看出,alpahled这个节点找到了,并且compatible属性值为"atkalpha-led",status属性值为"okay",reg属性的值为"0X20C406C 0X4 0X20E0068 0X4 0X20E02F4 0X4 0X209C000 0X4 0X209C004 0X4",这些都和我们设置的设备树一致。 驱动加载成功以后就可以使用ledApp软件来测试驱动是否工作正常,输入如下命令打开LED灯: ./ledApp /dev/dtsled 1 //打开LED灯 输入上述命令以后观察I.MX6U-ALPHA开发板上的红色LED灯是否点亮,如果点亮的话说明驱动工作正常。在输入如下命令关闭LED灯: ./ledApp /dev/dtsled 0 //关闭LED灯 输入上述命令以后观察I.MX6U-ALPHA开发板上的红色LED灯是否熄灭。如果要卸载驱动的话输入如下命令即可: rmmoddtsled.ko |
|
相关推荐
|
|
590 浏览 0 评论
AI模型部署边缘设备的奇妙之旅:如何在边缘端部署OpenCV
2241 浏览 0 评论
tms320280021 adc采样波形,为什么adc采样频率上来波形就不好了?
1233 浏览 0 评论
1788 浏览 0 评论
1464 浏览 0 评论
74811 浏览 21 评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-12-22 21:55 , Processed in 0.561520 second(s), Total 64, Slave 46 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号