搜索
bottom↓
回复: 0

【正点原子Linux连载】第四十四章设备树下的LED驱动实验--摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南

[复制链接]

出0入234汤圆

发表于 2020-6-30 10:30:14 | 显示全部楼层 |阅读模式
本帖最后由 正点原子 于 2020-10-24 16:17 编辑

1)实验平台:正点原子阿尔法Linux开发板
2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-300792-1-1.html  
4)对正点原子Linux感兴趣的同学可以加群讨论:
876919289 QQ群头像.png
5)关注正点原子公众号,获取最新资料

100846rel79a9p4uelap24.jpg

100846f1ce1fg14zbg0va4.png
第四十四章设备树下的LED驱动实验



上一章我们详细的讲解了设备树语法以及在驱动开发中常用的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. 1 alphaled {
  2. 2                #address-cells =<1>;
  3. 3        #size-cells =<1>;
  4. 4        compatible ="atkalpha-led";
  5. 5        status ="okay";
  6. 6        reg =<        0X020C406C0X04                /* CCM_CCGR1_BASE                    */
  7. 7                0X020E00680X04        /* SW_MUX_GPIO1_IO03_BASE           */
  8. 8                0X020E02F40X04        /* SW_PAD_GPIO1_IO03_BASE           */
  9. 9                0X0209C0000X04        /* GPIO1_DR_BASE                    */
  10. 10                0X0209C0040X04>;        /* GPIO1_GDIR_BASE                  */
  11. 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:
  1. makedtbs
复制代码

        编译完成以后得到imx6ull-alientek-emmc.dtb,使用新的imx6ull-alientek-emmc.dtb启动Linux内核。Linux启动成功以后进入到/proc/device-tree/目录中查看是否有“alphaled”这个节点,结果如图44.3.1.1所示:
image002.gif

图44.3.1.1 alphaled节点

        如果没有“alphaled”节点的话请重点下面两点:
①、检查设备树修改是否成功,也就是alphaled节点是否为根节点“/”的子节点。
②、检查是否使用新的设备树启动的Linux内核。
可以进入到图44.3.1中的alphaled目录中,查看一下都有哪些属性文件,结果如图44.3.1.2所示:
image004.jpg

图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. 1   #include <linux/types.h>
  2. 2   #include <linux/kernel.h>
  3. 3   #include <linux/delay.h>
  4. 4   #include <linux/ide.h>
  5. 5   #include <linux/init.h>
  6. 6   #include <linux/module.h>
  7. 7   #include <linux/errno.h>
  8. 8   #include <linux/gpio.h>
  9. 9   #include <linux/cdev.h>
  10. 10  #include <linux/device.h>
  11. 11  #include <linux/of.h>
  12. 12  #include <linux/of_address.h>
  13. 13  #include <asm/mach/map.h>
  14. 14  #include <asm/uaccess.h>
  15. 15  #include <asm/io.h>
  16. 16/***************************************************************
  17. 17  Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
  18. 18文件名        : dtsled.c
  19. 19作者        : 左忠凯
  20. 20版本        : V1.0
  21. 21描述        : LED驱动文件。
  22. 22其他        : 无
  23. 23论坛        : <a href="www.openedv.com" target="_blank">www.openedv.com</a>
  24. 24日志        : 初版V1.0 2019/7/9 左忠凯创建
  25. 25  ***************************************************************/
  26. 26  #define DTSLED_CNT            1        /* 设备号个数        */
  27. 27  #define DTSLED_NAME             "dtsled"        /* 名字                */
  28. 28  #define LEDOFF                   0        /* 关灯                */
  29. 29  #define LEDON                    1        /* 开灯                */
  30. 30
  31. 31/* 映射后的寄存器虚拟地址指针 */
  32. 32staticvoid __iomem *IMX6U_CCM_CCGR1;
  33. 33staticvoid __iomem *SW_MUX_GPIO1_IO03;
  34. 34staticvoid __iomem *SW_PAD_GPIO1_IO03;
  35. 35staticvoid __iomem *GPIO1_DR;
  36. 36staticvoid __iomem *GPIO1_GDIR;
  37. 37
  38. 38/* dtsled设备结构体 */
  39. 39struct dtsled_dev{
  40. 40      dev_t devid;        /* 设备号        */
  41. 41struct cdev cdev;        /* cdev             */
  42. 42struct class *class;        /* 类                */
  43. 43struct device *device;        /* 设备                */
  44. 44int major;        /* 主设备号        */
  45. 45int minor;        /* 次设备号        */
  46. 46struct device_node  *nd;/* 设备节点        */
  47. 47};
  48. 48
  49. 49struct dtsled_dev dtsled;/* led设备 */
  50. 50
  51. 51/*
  52. 52   * @description          : LED打开/关闭
  53. 53   * @param - sta          : LEDON(0) 打开LED,LEDOFF(1) 关闭LED
  54. 54   * @return               : 无
  55. 55   */
  56. 56void led_switch(u8 sta)
  57. 57{
  58. 58      u32 val =0;
  59. 59if(sta == LEDON){
  60. 60          val = readl(GPIO1_DR);
  61. 61          val &=~(1<<3);
  62. 62          writel(val, GPIO1_DR);
  63. 63}elseif(sta == LEDOFF){
  64. 64          val = readl(GPIO1_DR);
  65. 65          val|=(1<<3);
  66. 66          writel(val, GPIO1_DR);
  67. 67}
  68. 68}
  69. 69
  70. 70/*
  71. 71   * @description          : 打开设备
  72. 72   * @param – inode        : 传递给驱动的inode
  73. 73   * @param – filp        : 设备文件,file结构体有个叫做private_data的成员变量
  74. 74   *                    一般在open的时候将private_data指向设备结构体。
  75. 75   * @return               : 0 成功;其他失败
  76. 76   */
  77. 77staticint led_open(struct inode *inode,struct file *filp)
  78. 78{
  79. 79      filp->private_data =&dtsled;/* 设置私有数据 */
  80. 80return0;
  81. 81}
  82. 82
  83. 83/*
  84. 84   * @description          : 从设备读取数据
  85. 85   * @param – filp        : 要打开的设备文件(文件描述符)
  86. 86   * @param - buf         : 返回给用户空间的数据缓冲区
  87. 87   * @param - cnt          : 要读取的数据长度
  88. 88   * @param – offt        : 相对于文件首地址的偏移
  89. 89   * @return               : 读取的字节数,如果为负值,表示读取失败
  90. 90   */
  91. 91static ssize_t led_read(struct file *filp,char __user *buf,size_t cnt, loff_t *offt)
  92. 92{
  93. 93return0;
  94. 94}
  95. 95
  96. 96/*
  97. 97   * @description         : 向设备写数据
  98. 98   * @param - filp         : 设备文件,表示打开的文件描述符
  99. 99   * @param - buf         : 要写给设备写入的数据
  100. 100  * @param - cnt         : 要写入的数据长度
  101. 101  * @param – offt        : 相对于文件首地址的偏移
  102. 102  * @return               : 写入的字节数,如果为负值,表示写入失败
  103. 103  */
  104. 104static ssize_t led_write(struct file *filp,constchar __user *buf,size_t cnt, loff_t *offt)
  105. 105{
  106. 106int retvalue;
  107. 107unsignedchar databuf[1];
  108. 108unsignedchar ledstat;
  109. 109
  110. 110     retvalue = copy_from_user(databuf, buf, cnt);
  111. 111if(retvalue <0){
  112. 112         printk("kernel write failed!\r\n");
  113. 113return-EFAULT;
  114. 114}
  115. 115
  116. 116     ledstat = databuf[0];        /* 获取状态值        */
  117. 117
  118. 118if(ledstat == LEDON){
  119. 119         led_switch(LEDON);        /* 打开LED灯        */
  120. 120}elseif(ledstat == LEDOFF){
  121. 121         led_switch(LEDOFF);                /* 关闭LED灯        */
  122. 122}
  123. 123return0;
  124. 124}
  125. 125
  126. 126/*
  127. 127  * @description         : 关闭/释放设备
  128. 128  * @param – filp        : 要关闭的设备文件(文件描述符)
  129. 129  * @return               : 0 成功;其他失败
  130. 130  */
  131. 131staticint led_release(struct inode *inode,struct file *filp)
  132. 132{
  133. 133return0;
  134. 134}
  135. 135
  136. 136/* 设备操作函数 */
  137. 137staticstruct file_operations dtsled_fops ={
  138. 138.owner = THIS_MODULE,
  139. 139.open = led_open,
  140. 140.read = led_read,
  141. 141.write = led_write,
  142. 142.release =  led_release,
  143. 143};
  144. 144
  145. 145/*
  146. 146  * @description        : 驱动入口函数
  147. 147  * @param               : 无
  148. 148  * @return              : 无
  149. 149  */
  150. 150staticint __init led_init(void)
  151. 151{
  152. 152     u32 val =0;
  153. 153int ret;
  154. 154     u32 regdata[14];
  155. 155constchar*str;
  156. 156struct property *proper;
  157. 157
  158. 158/* 获取设备树中的属性数据 */
  159. 159/* 1、获取设备节点:alphaled */
  160. 160     dtsled.nd = of_find_node_by_path("/alphaled");
  161. 161if(dtsled.nd ==NULL){
  162. 162         printk("alphaled node not find!\r\n");
  163. 163return-EINVAL;
  164. 164}else{
  165. 165         printk("alphaled node find!\r\n");
  166. 166}
  167. 167
  168. 168/* 2、获取compatible属性内容 */
  169. 169     proper = of_find_property(dtsled.nd,"compatible",NULL);
  170. 170if(proper ==NULL){
  171. 171         printk("compatible property find failed\r\n");
  172. 172}else{
  173. 173         printk("compatible = %s\r\n",(char*)proper->value);
  174. 174}
  175. 175
  176. 176/* 3、获取status属性内容 */
  177. 177     ret = of_property_read_string(dtsled.nd,"status",&str);
  178. 178if(ret <0){
  179. 179         printk("status read failed!\r\n");
  180. 180}else{
  181. 181         printk("status = %s\r\n",str);
  182. 182}
  183. 183
  184. 184/* 4、获取reg属性内容 */
  185. 185     ret = of_property_read_u32_array(dtsled.nd,"reg", regdata,10);
  186. 186if(ret <0){
  187. 187         printk("reg property read failed!\r\n");
  188. 188}else{
  189. 189         u8 i =0;
  190. 190         printk("reg data:\r\n");
  191. 191for(i =0; i <10; i++)
  192. 192             printk("%#X ", regdata);
  193. 193         printk("\r\n");
  194. 194}
  195. 195
  196. 196/* 初始化LED */
  197. 197 #if0
  198. 198/* 1、寄存器地址映射 */
  199. 199     IMX6U_CCM_CCGR1 = ioremap(regdata[0], regdata[1]);
  200. 200     SW_MUX_GPIO1_IO03 = ioremap(regdata[2], regdata[3]);
  201. 201     SW_PAD_GPIO1_IO03 = ioremap(regdata[4], regdata[5]);
  202. 202     GPIO1_DR = ioremap(regdata[6], regdata[7]);
  203. 203     GPIO1_GDIR = ioremap(regdata[8], regdata[9]);
  204. 204 #else
  205. 205     IMX6U_CCM_CCGR1 = of_iomap(dtsled.nd,0);
  206. 206     SW_MUX_GPIO1_IO03 = of_iomap(dtsled.nd,1);
  207. 207     SW_PAD_GPIO1_IO03 = of_iomap(dtsled.nd,2);
  208. 208     GPIO1_DR = of_iomap(dtsled.nd,3);
  209. 209     GPIO1_GDIR = of_iomap(dtsled.nd,4);
  210. 210 #endif
  211. 211
  212. 212/* 2、使能GPIO1时钟 */
  213. 213     val = readl(IMX6U_CCM_CCGR1);
  214. 214     val &=~(3<<26);/* 清楚以前的设置 */
  215. 215     val |=(3<<26);/* 设置新值 */
  216. 216     writel(val, IMX6U_CCM_CCGR1);
  217. 217
  218. 218/* 3、设置GPIO1_IO03的复用功能,将其复用为
  219. 219      *    GPIO1_IO03,最后设置IO属性。
  220. 220      */
  221. 221     writel(5, SW_MUX_GPIO1_IO03);
  222. 222
  223. 223/* 寄存器SW_PAD_GPIO1_IO03设置IO属性 */
  224. 224     writel(0x10B0, SW_PAD_GPIO1_IO03);
  225. 225
  226. 226/* 4、设置GPIO1_IO03为输出功能 */
  227. 227     val = readl(GPIO1_GDIR);
  228. 228     val &=~(1<<3);/* 清除以前的设置 */
  229. 229     val |=(1<<3);/* 设置为输出 */
  230. 230     writel(val, GPIO1_GDIR);
  231. 231
  232. 232/* 5、默认关闭LED */
  233. 233     val = readl(GPIO1_DR);
  234. 234     val |=(1<<3);
  235. 235     writel(val, GPIO1_DR);
  236. 236
  237. 237/* 注册字符设备驱动 */
  238. 238/* 1、创建设备号 */
  239. 239if(dtsled.major){                /*  定义了设备号 */
  240. 240         dtsled.devid = MKDEV(dtsled.major,0);
  241. 241         register_chrdev_region(dtsled.devid, DTSLED_CNT,            DTSLED_NAME);
  242. 242}else{/* 没有定义设备号 */
  243. 243         alloc_chrdev_region(&dtsled.devid,0, DTSLED_CNT,  DTSLED_NAME);/* 申请设备号 */
  244. 244         dtsled.major = MAJOR(dtsled.devid);/* 获取分配号的主设备号 */
  245. 245         dtsled.minor = MINOR(dtsled.devid);/* 获取分配号的次设备号 */
  246. 246}
  247. 247     printk("dtsled major=%d,minor=%d\r\n",dtsled.major, dtsled.minor);
  248. 248
  249. 249/* 2、初始化cdev */
  250. 250     dtsled.cdev.owner = THIS_MODULE;
  251. 251     cdev_init(&dtsled.cdev,&dtsled_fops);
  252. 252
  253. 253/* 3、添加一个cdev */
  254. 254     cdev_add(&dtsled.cdev, dtsled.devid, DTSLED_CNT);
  255. 255
  256. 256/* 4、创建类 */
  257. 257     dtsled.class = class_create(THIS_MODULE, DTSLED_NAME);
  258. 258if(IS_ERR(dtsled.class)){
  259. 259return PTR_ERR(dtsled.class);
  260. 260}
  261. 261
  262. 262/* 5、创建设备 */
  263. 263     dtsled.device = device_create(dtsled.class,NULL, dtsled.devid,NULL, DTSLED_NAME);
  264. 264if(IS_ERR(dtsled.device)){
  265. 265return PTR_ERR(dtsled.device);
  266. 266}
  267. 267
  268. 268return0;
  269. 269}
  270. 270
  271. 271/*
  272. 272  * @description        : 驱动出口函数
  273. 273  * @param               : 无
  274. 274  * @return              : 无
  275. 275  */
  276. 276staticvoid __exit led_exit(void)
  277. 277{
  278. 278/* 取消映射 */
  279. 279     iounmap(IMX6U_CCM_CCGR1);
  280. 280     iounmap(SW_MUX_GPIO1_IO03);
  281. 281     iounmap(SW_PAD_GPIO1_IO03);
  282. 282     iounmap(GPIO1_DR);
  283. 283     iounmap(GPIO1_GDIR);
  284. 284
  285. 285/* 注销字符设备驱动 */
  286. 286     cdev_del(&dtsled.cdev);/* 删除cdev */
  287. 287     unregister_chrdev_region(dtsled.devid, DTSLED_CNT);/*注销设备号*/
  288. 288
  289. 289     device_destroy(dtsled.class, dtsled.devid);
  290. 290     class_destroy(dtsled.class);
  291. 291}
  292. 292
  293. 293 module_init(led_init);
  294. 294 module_exit(led_exit);
  295. 295 MODULE_LICENSE("GPL");
  296. 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 编译驱动程序和测试APP
        1、编译驱动程序
        编写Makefile文件,本章实验的Makefile文件和第四十章实验基本一样,只是将obj-m变量的值改为dtsled.o,Makefile内容如下所示:
示例代码44.4.1.1 Makefile文件
  1. 1  KERNELDIR:= /home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
  2. ......
  3. 4  obj-m := dtsled.o.o
  4. ......
  5. 11 clean:
  6. 12$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
复制代码

        第4行,设置obj-m变量的值为dtsled.o。
        输入如下命令编译出驱动模块文件:
  1. make-j32
复制代码

        编译成功以后就会生成一个名为“dtsled.ko”的驱动模块文件。
        2、编译测试APP
        输入如下命令编译测试ledApp.c这个测试程序:
  1. 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驱动模块:
  1. depmod                                //第一次加载驱动的时候需要运行此命令
  2. modprobe dtsled.ko                //加载驱动
复制代码

        驱动加载成功以后会在终端中输出一些信息,如图44.4.2.1所示:
image006.jpg

图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灯:
  1. ./ledApp /dev/dtsled 1                //打开LED灯
复制代码

        输入上述命令以后观察I.MX6U-ALPHA开发板上的红色LED灯是否点亮,如果点亮的话说明驱动工作正常。在输入如下命令关闭LED灯:
  1. ./ledApp  /dev/dtsled 0                //关闭LED灯
复制代码

        输入上述命令以后观察I.MX6U-ALPHA开发板上的红色LED灯是否熄灭。如果要卸载驱动的话输入如下命令即可:
  1. rmmoddtsled.ko
复制代码







阿莫论坛20周年了!感谢大家的支持与爱护!!

月入3000的是反美的。收入3万是亲美的。收入30万是移民美国的。收入300万是取得绿卡后回国,教唆那些3000来反美的!
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-4-26 23:18

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表