搜索
bottom↓
回复: 1

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

[复制链接]

出0入234汤圆

发表于 2020-7-6 11:31:47 | 显示全部楼层 |阅读模式
本帖最后由 正点原子 于 2020-10-26 11:51 编辑

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

第五十五章设备树下的platform驱动编写


        上一章我们详细的讲解了Linux下的驱动分离与分层,以及总线、设备和驱动这样的驱动框架。基于总线、设备和驱动这样的驱动框架,Linux内核提出来platform这个虚拟总线,相应的也有platform设备和platform驱动。上一章我们讲解了传统的、未采用设备树的platform设备和驱动编写方法。最新的Linux内核已经支持了设备树,因此在设备树下如何编写platform驱动就显得尤为重要,本章我们就来学习一下如何在设备树下编写platform驱动。



55.1设备树下的platform驱动简介
        platform驱动框架分为总线、设备和驱动,其中总线不需要我们这些驱动程序员去管理,这个是Linux内核提供的,我们在编写驱动的时候只要关注于设备和驱动的具体实现即可。在未设备树的Linux内核下,我们需要分别编写并注册platform_device和platform_driver,分别代表设备和驱动。在使用设备树的时候,设备的描述被放到了设备树中,因此platform_device就不需要我们去编写了,我们只需要实现platform_driver即可。在编写基于设备树的platform驱动的时候我们需要注意一下几点:
        1、在设备树中创建设备节点
        毫无疑问,肯定要先在设备树中创建设备节点来描述设备信息,重点是要设置好compatible属性的值,因为platform总线需要通过设备节点的compatible属性值来匹配驱动!这点要切记。比如,我们可以编写如下所示的设备节点来描述我们本章实验要用到的LED这个设备:
示例代码55.1.1 gpioled设备节点
  1. 1 gpioled {
  2. 2        #address-cells =<1>;
  3. 3        #size-cells =<1>;
  4. 4        compatible ="atkalpha-gpioled";
  5. 5        pinctrl-names ="default";
  6. 6        pinctrl-0=<&pinctrl_led>;
  7. 7        led-gpio =<&gpio1 3 GPIO_ACTIVE_LOW>;
  8. 8        status ="okay";
  9. 9};
复制代码

        示例55.1.1中的gpioled节点其实就是45.4.1.2小节中创建的gpioled设备节点,我们可以直接拿过来用。注意第4行的compatible属性值为“atkalpha-gpioled”,因此,我们一会在编写platform驱动的时候一定要设置of_match_table也有此值。
        2、编写platform驱动的时候要注意兼容属性
        上一章已经详细的讲解过了,在使用设备树的时候platform驱动会通过of_match_table来保存兼容性值,也就是表明此驱动兼容哪些设备。所以,of_match_table将会尤为重要,比如本例程的platform驱动中platform_driver就可以按照如下所示设置:
示例代码55.1.2 of_match_table匹配表的设置
  1. 1        staticconststruct of_device_id leds_of_match[]={
  2. 2        {.compatible ="atkalpha-gpioled"},/* 兼容属性 */
  3. 3        {/* Sentinel */}
  4. 4        };
  5. 5
  6. 6        MODULE_DEVICE_TABLE(of, leds_of_match);
  7. 7
  8. 8        staticstruct platform_driver leds_platform_driver ={
  9. 9        .driver ={
  10. 10        .name       ="imx6ul-led",
  11. 11        .of_match_table = leds_of_match,
  12. 12        },
  13. 13        .probe          = leds_probe,
  14. 14        .remove         = leds_remove,
  15. 15        };
复制代码

        第1~4行,of_device_id表,也就是驱动的兼容表,是一个数组,每个数组元素为of_device_id类型。每个数组元素都是一个兼容属性,表示兼容的设备,一个驱动可以跟多个设备匹配。这里我们仅仅匹配了一个设备,那就是55.1.1中创建的gpioled这个设备。第2行的compatible值为“atkalpha-gpioled”,驱动中的compatible属性和设备中的compatible属性相匹配,因此驱动中对应的probe函数就会执行。注意第3行是一个空元素,在编写of_device_id的时候最后一个元素一定要为空!
        第6行,通过MODULE_DEVICE_TABLE声明一下leds_of_match这个设备匹配表。
        第11行,设置platform_driver中的of_match_table匹配表为上面创建的leds_of_match,至此我们就设置好了platform驱动的匹配表了。
        3、编写platform驱动
        基于设备树的platform驱动和上一章无设备树的platform驱动基本一样,都是当驱动和设备匹配成功以后就会执行probe函数。我们需要在probe函数里面执行字符设备驱动那一套,当注销驱动模块的时候remove函数就会执行,都是大同小异的。
55.2 硬件原理图分析
本章实验我们只使用到IMX6U-ALPHA开发板上的LED灯,因此实验硬件原理图参考8.3小节即可。
55.3 实验程序编写
本实验对应的例程路径为:开发板光盘->2、Linux驱动例程->18_dtsplatform。
本章实验我们编写基于设备树的platform驱动,所以需要在设备树中添加设备节点,然后我们只需要编写platform驱动即可。
55.3.1 修改设备树文件
        首先修改设备树文件,加上我们需要的设备信息,本章我们就使用到一个LED灯,因此可以直接使用45.4.1小节编写的gpioled子节点即可,不需要在重复添加。
55.3.2 platform驱动程序编写
        设备已经准备好了,接下来就要编写相应的platform驱动了,新建名为“18_dtsplatform”的文件夹,然后在18_dtsplatform文件夹里面创建vscode工程,工作区命名为“dtsplatform”。新建名为leddriver.c的驱动文件,在leddriver.c中输入如下所示内容:
示例代码55.3.2.1 leddriver.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_gpio.h>
  12. 12  #include <linux/semaphore.h>
  13. 13  #include <linux/timer.h>
  14. 14  #include <linux/irq.h>
  15. 15  #include <linux/wait.h>
  16. 16  #include <linux/poll.h>
  17. 17  #include <linux/fs.h>
  18. 18  #include <linux/fcntl.h>
  19. 19  #include <linux/platform_device.h>
  20. 20  #include <asm/mach/map.h>
  21. 21  #include <asm/uaccess.h>
  22. 22  #include <asm/io.h>
  23. 23/***************************************************************
  24. 24  Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
  25. 25文件名                : leddriver.c
  26. 26作者        : 左忠凯
  27. 27版本        : V1.0
  28. 28描述        : 设备树下的platform驱动
  29. 29其他        : 无
  30. 30论坛        : <a href="www.openedv.com" target="_blank">www.openedv.com</a>
  31. 31日志        : 初版V1.0 2019/8/13 左忠凯创建
  32. 32  ***************************************************************/
  33. 33  #define LEDDEV_CNT              1                /* 设备号长度        */
  34. 34  #define LEDDEV_NAME             "dtsplatled"        /* 设备名字        */
  35. 35  #define LEDOFF                  0
  36. 36  #define LEDON                   1
  37. 37
  38. 38/* leddev设备结构体 */
  39. 39struct leddev_dev{
  40. 40      dev_t                         devid;        /* 设备号                */
  41. 41struct cdev         cdev;        /* cdev                     */
  42. 42struct class         *class;        /* 类                        */
  43. 43struct device         *device;        /* 设备                */
  44. 44int                        major;        /* 主设备号                        */
  45. 45struct device_node *node;        /* LED设备节点                */
  46. 46int                        led0;        /* LED灯GPIO标号        */
  47. 47};
  48. 48
  49. 49struct leddev_dev leddev;        /* 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 led0_switch(u8 sta)
  57. 57{
  58. 58if(sta == LEDON )
  59. 59          gpio_set_value(leddev.led0,0);
  60. 60elseif(sta == LEDOFF)
  61. 61          gpio_set_value(leddev.led0,1);
  62. 62}
  63. 63
  64. 64/*
  65. 65   * @description         : 打开设备
  66. 66   * @param – inode        : 传递给驱动的inode
  67. 67   * @param - filp         : 设备文件,file结构体有个叫做private_data的成员变量
  68. 68   *                    一般在open的时候将private_data指向设备结构体。
  69. 69   * @return               : 0 成功;其他失败
  70. 70   */
  71. 71staticint led_open(struct inode *inode,struct file *filp)
  72. 72{
  73. 73      filp->private_data =&leddev;/* 设置私有数据  */
  74. 74return0;
  75. 75}
  76. 76
  77. 77/*
  78. 78   * @description          : 向设备写数据
  79. 79   * @param - filp         : 设备文件,表示打开的文件描述符
  80. 80   * @param - buf          : 要写给设备写入的数据
  81. 81   * @param - cnt          : 要写入的数据长度
  82. 82   * @param – offt        : 相对于文件首地址的偏移
  83. 83   * @return                : 写入的字节数,如果为负值,表示写入失败
  84. 84   */
  85. 85static ssize_t led_write(struct file *filp,constchar __user *buf,
  86. size_t cnt, loff_t *offt)
  87. 86{
  88. 87int retvalue;
  89. 88unsignedchar databuf[2];
  90. 89unsignedchar ledstat;
  91. 90
  92. 91      retvalue = copy_from_user(databuf, buf, cnt);
  93. 92if(retvalue <0){
  94. 93
  95. 94          printk("kernel write failed!\r\n");
  96. 95return-EFAULT;
  97. 96}
  98. 97
  99. 98      ledstat = databuf[0];
  100. 99if(ledstat == LEDON){
  101. 100         led0_switch(LEDON);
  102. 101}elseif(ledstat == LEDOFF){
  103. 102         led0_switch(LEDOFF);
  104. 103}
  105. 104return0;
  106. 105}
  107. 106
  108. 107/* 设备操作函数 */
  109. 108staticstruct file_operations led_fops ={
  110. 109.owner = THIS_MODULE,
  111. 110.open = led_open,
  112. 111.write = led_write,
  113. 112};
  114. 113
  115. 114/*
  116. 115  * @description        : flatform驱动的probe函数,当驱动与
  117. 116  *                    设备匹配以后此函数就会执行
  118. 117  * @param - dev          : platform设备
  119. 118  * @return                : 0,成功;其他负值,失败
  120. 119  */
  121. 120staticint led_probe(struct platform_device *dev)
  122. 121{
  123. 122     printk("led driver and device was matched!\r\n");
  124. 123/* 1、设置设备号 */
  125. 124if(leddev.major){
  126. 125         leddev.devid = MKDEV(leddev.major,0);
  127. 126         register_chrdev_region(leddev.devid, LEDDEV_CNT,
  128. LEDDEV_NAME);
  129. 127}else{
  130. 128         alloc_chrdev_region(&leddev.devid,0, LEDDEV_CNT,
  131. LEDDEV_NAME);
  132. 129         leddev.major = MAJOR(leddev.devid);
  133. 130}
  134. 131
  135. 132/* 2、注册设备      */
  136. 133     cdev_init(&leddev.cdev,&led_fops);
  137. 134     cdev_add(&leddev.cdev, leddev.devid, LEDDEV_CNT);
  138. 135
  139. 136/* 3、创建类      */
  140. 137     leddev.class = class_create(THIS_MODULE, LEDDEV_NAME);
  141. 138if(IS_ERR(leddev.class)){
  142. 139return PTR_ERR(leddev.class);
  143. 140}
  144. 141
  145. 142/* 4、创建设备 */
  146. 143     leddev.device = device_create(leddev.class,NULL, leddev.devid,
  147. NULL, LEDDEV_NAME);
  148. 144if(IS_ERR(leddev.device)){
  149. 145return PTR_ERR(leddev.device);
  150. 146}
  151. 147
  152. 148/* 5、初始化IO */
  153. 149     leddev.node = of_find_node_by_path("/gpioled");
  154. 150if(leddev.node ==NULL){
  155. 151         printk("gpioled node nost find!\r\n");
  156. 152return-EINVAL;
  157. 153}
  158. 154
  159. 155     leddev.led0 = of_get_named_gpio(leddev.node,"led-gpio",0);
  160. 156if(leddev.led0 <0){
  161. 157         printk("can't get led-gpio\r\n");
  162. 158return-EINVAL;
  163. 159}
  164. 160
  165. 161     gpio_request(leddev.led0,"led0");
  166. 162     gpio_direction_output(leddev.led0,1);/*设置为输出,默认高电平 */
  167. 163return0;
  168. 164}
  169. 165
  170. 166/*
  171. 167  * @description        : remove函数,移除platform驱动的时候此函数会执行
  172. 168  * @param - dev         : platform设备
  173. 169  * @return               : 0,成功;其他负值,失败
  174. 170  */
  175. 171staticint led_remove(struct platform_device *dev)
  176. 172{
  177. 173     gpio_set_value(leddev.led0,1);        /* 卸载驱动的时候关闭LED         */
  178. 174
  179. 175     cdev_del(&leddev.cdev);        /*  删除cdev                                 */
  180. 176     unregister_chrdev_region(leddev.devid, LEDDEV_CNT);
  181. 177     device_destroy(leddev.class, leddev.devid);
  182. 178     class_destroy(leddev.class);
  183. 179return0;
  184. 180}
  185. 181
  186. 182/* 匹配列表 */
  187. 183staticconststruct of_device_id led_of_match[]={
  188. 184{.compatible ="atkalpha-gpioled"},
  189. 185{/* Sentinel */}
  190. 186};
  191. 187
  192. 188/* platform驱动结构体 */
  193. 189staticstruct platform_driver led_driver ={
  194. 190.driver     ={
  195. 191.name   ="imx6ul-led",        /* 驱动名字,用于和设备匹配        */
  196. 192.of_match_table = led_of_match,        /* 设备树匹配表                */
  197. 193},
  198. 194.probe      = led_probe,
  199. 195.remove     = led_remove,
  200. 196};
  201. 197
  202. 198/*
  203. 199  * @description        : 驱动模块加载函数
  204. 200  * @param               : 无
  205. 201  * @return              : 无
  206. 202  */
  207. 203staticint __init leddriver_init(void)
  208. 204{
  209. 205return platform_driver_register(&led_driver);
  210. 206}
  211. 207
  212. 208/*
  213. 209  * @description        : 驱动模块卸载函数
  214. 210  * @param               : 无
  215. 211  * @return              : 无
  216. 212  */
  217. 213staticvoid __exit leddriver_exit(void)
  218. 214{
  219. 215     platform_driver_unregister(&led_driver);
  220. 216}
  221. 217
  222. 218 module_init(leddriver_init);
  223. 219 module_exit(leddriver_exit);
  224. 220 MODULE_LICENSE("GPL");
  225. 221 MODULE_AUTHOR("zuozhongkai");
复制代码

        第33~112行,传统的字符设备驱动,没什么要说的。
        第120~164行,platform驱动的probe函数,当设备树中的设备节点与驱动之间匹配成功以后此函数就会执行,原来在驱动加载函数里面做的工作现在全部放到probe函数里面完成。
        第171~180行,remobe函数,当卸载platform驱动的时候此函数就会执行。在此函数里面释放内存、注销字符设备等,也就是将原来驱动卸载函数里面的工作全部都放到remove函数中完成。
        第183~186行,匹配表,描述了此驱动都和什么样的设备匹配,第184行添加了一条值为"atkalpha-gpioled"的compatible属性值,当设备树中某个设备节点的compatible属性值也为“atkalpha-gpioled”的时候就会与此驱动匹配。
        第189~196行,platform_driver驱动结构体,191行设置这个platform驱动的名字为“imx6ul-led”,因此,当驱动加载成功以后就会在/sys/bus/platform/drivers/目录下存在一个名为“imx6u-led”的文件。第192行设置of_match_table为上面的led_of_match。
        第203~206行,驱动模块加载函数,在此函数里面通过platform_driver_register向Linux内核注册led_driver驱动。
        第213~216行,驱动模块卸载函数,在此函数里面通过platform_driver_unregister从Linux内核卸载led_driver驱动。
55.3.3 编写测试APP
        测试APP就直接使用上一章54.4.2小节编写的ledApp.c即可。
55.4 运行测试
55.4.1 编译驱动程序和测试APP
1、编译驱动程序
        编写Makefile文件,本章实验的Makefile文件和第四十章实验基本一样,只是将obj-m变量的值改为“leddriver.o”,Makefile内容如下所示:
示例代码55.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 := leddriver.o
  4. ......
  5. 11 clean:
  6. 12$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
复制代码

        第4行,设置obj-m变量的值为“leddriver.o”。
        输入如下命令编译出驱动模块文件:
make-j32
        编译成功以后就会生成一个名为“leddriver.o”的驱动模块文件。
        2、编译测试APP
        测试APP直接使用上一章的ledApp这个测试软件即可。
55.4.2 运行测试
        将上一小节编译出来leddriver.ko拷贝到rootfs/lib/modules/4.1.15目录中,重启开发板,进入到目录lib/modules/4.1.15中,输入如下命令加载leddriver.ko这个驱动模块。
  1. depmod                                //第一次加载驱动的时候需要运行此命令
  2. modprobeleddriver.ko        //加载驱动模块
复制代码

        驱动模块加载完成以后到/sys/bus/platform/drivers/目录下查看驱动是否存在,我们在leddriver.c中设置led_driver(platform_driver类型)的name字段为“imx6ul-led”,因此会在/sys/bus/platform/drivers/目录下存在名为“imx6ul-led”这个文件,结果如图55.4.2.1所示:
image002.gif

图55.4.2.1 imx6ul-led驱动

        同理,在/sys/bus/platform/devices/目录下也存在led的设备文件,也就是设备树中gpioled这个节点,如图55.4.2.2所示:
image004.gif

图55.4.2.2 gpioled设备

驱动和模块都存在,当驱动和设备匹配成功以后就会输出如图55.4.2.3所示一行语句:
image006.gif

图55.4.2.3 驱动和设备匹配成功

        驱动和设备匹配成功以后就可以测试LED灯驱动了,输入如下命令打开LED灯:
  1. ./ledApp /dev/dtsplatled 1        //打开LED灯
复制代码

        在输入如下命令关闭LED灯:
  1. ./ledApp /dev/dtsplatled 0        //关闭LED灯
复制代码

        观察一下LED灯能否打开和关闭,如果可以的话就说明驱动工作正常,如果要卸载驱动的话输入如下命令即可:
  1. rmmodleddriver.ko
复制代码

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

月入3000的是反美的。收入3万是亲美的。收入30万是移民美国的。收入300万是取得绿卡后回国,教唆那些3000来反美的!

出16170入6148汤圆

发表于 2020-7-6 12:43:47 来自手机 | 显示全部楼层
打赏!

庆祝论坛“打赏”功能实施, 现在开始发技术主题,可以获得打赏
https://www.amobbs.com/thread-5735948-1-1.html
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2024-4-26 17:06

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

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