OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 4154|回复: 0

[XILINX] 《领航者ZYNQ之嵌入式Linux开发指南_V2.0》第三十八章 platform驱动

[复制链接]

1118

主题

1129

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
4671
金钱
4671
注册时间
2019-5-8
在线时间
1224 小时
发表于 2022-3-21 15:26:48 | 显示全部楼层 |阅读模式
本帖最后由 正点原子运营 于 2022-3-21 15:26 编辑

1)实验平台:正点原子领航者V2FPGA开发板
2)  章节摘自【正点原子】《领航者ZYNQ之FPGA开发指南 V2.0》
3)购买链接:https://detail.tmall.com/item.htm?id=609032204975
4)全套实验源码+手册+视频下载地址:[url=http://www.openedv.com/docs/boards/fpga/zdyz_linhanz(V2).html]http://www.openedv.com/docs/boards/fpga/zdyz_linhanz(V2).html

5)正点原子官方B站:https://space.bilibili.com/394620890
6)正点原子FPGA技术交流QQ群:712557122



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

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

       1.1设备树下的platform驱动简介
       platform驱动框架分为总线、设备和驱动,其中总线不需要我们这些驱动程序员去管理,这个是Linux内核提供的,我们在编写驱动的时候只要关注于设备和驱动的具体实现即可。在没有设备树的Linux内核下,我们需要分别编写并注册platform_device和platform_driver,分别代表设备和驱动。在使用设备树的时候,设备的描述被放到了设备树中,因此platform_device就不需要我们去编写了,我们只需要实现platform_driver即可,当内核在解析设备树的时候会自动帮我们创建一个platform_device对象;在编写基于设备树的platform驱动的时候我们需要注意一下几点:
       1、在设备树中创建设备节点
       毫无疑问,肯定要先在设备树中创建设备节点来描述设备信息,重点是要设置好compatible属性的值,因为platform总线需要通过设备节点的compatible属性值来匹配驱动!这点要切记。比如,使用如下所示的设备节点来描述我们本章实验要用到的LED这个设备:
    1. 示例代码 38.1.1 led设备节点
    2. 37 led {
    3. 38     compatible = "alientek,led";
    4. 39     status = "okay";
    5. 40     default-state = "on";
    6. 41     led-gpio = <&gpio0 7 GPIO_ACTIVE_HIGH>;
    7. 42 };
    复制代码



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



       第1~4行,of_device_id表,也就是驱动的兼容表,是一个数组,每个数组元素为of_device_id类型。每个数组元素都是一个兼容属性,表示兼容的设备,一个驱动可以跟多个设备匹配。这里我们仅仅匹配了一个设备,那就是示例代码 38.1.1中的led这个设备。第2行的compatible值为“alientek,led”,驱动中的compatible属性和设备中的compatible属性相匹配,因此驱动中对应的probe函数就会执行。注意第3行是一个空元素,在编写of_device_id的时候最后一个元素一定要为空!
       第6行,通过MODULE_DEVICE_TABLE声明一下leds_of_match这个设备匹配表。注意这个宏一般用于热拔插设备动态地进行驱动的加载与卸载,例如USB类设备。
       第11行,将leds_of_match匹配表绑定到platform驱动结构体leds_platform_driver中,至此我们就设置好了platform驱动的匹配表了。
       3、编写platform驱动
       基于设备树的platform驱动和上一章无设备树的platform驱动基本一样,都是当驱动和设备匹配成功以后就会执行probe函数。我们需要在probe函数里面执行字符设备驱动那一套,当注销驱动模块的时候remove函数就会执行,都是大同小异的。
       1.2硬件原理图分析
       本章实验我们只使用到开发板上的PS_LED0灯,因此实验硬件原理图参考22.3小节即可。
       1.3实验程序编写
       本实验对应的例程路径为:领航者ZYNQ开发板光盘资料(A盘)\4_SourceCode\3_Embedded_Linux\Linux驱动例程\18_dtsplatform。
       本章实验我们编写基于设备树的platform驱动,所以需要在设备树中添加设备节点,然后我们只需要编写platform驱动即可。
1.3.1修改设备树文件
       首先修改设备树文件,加上我们需要的设备信息,本章我们就使用到一个LED灯,因此可以直接使用27.3.1小节编写的led节点即可,不需要再重复添加。
1.3.2platform驱动程序编写
       设备已经准备好了,接下来就要编写相应的platform驱动了,在drivers目录下新建名为“18_dtsplatform”的文件夹作为本实验目录,在“18_dtsplatform”目录下新建名为leddriver.c的驱动文件,在leddriver.c中输入如下所示内容:
    1. 示例代码 38.3.1 leddriver.c文件代码段
    2.   1 /***************************************************************
    3.   2  Copyright &#169; ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
    4.   3  文件名    : leddriver.c
    5.   4  作者      : 邓涛
    6.   5  版本      : V1.0
    7.   6  描述      : platform总线编程示例之platform驱动模块
    8.   7  其他      : 无
    9.   8  论坛      : <a href="www.openedv.com" target="_blank">www.openedv.com</a>
    10.   9  日志      : 初版V1.0 2019/1/30 邓涛创建
    11. 10  ***************************************************************/
    12. 11
    13. 12 #include <linux/module.h>
    14. 13 #include <linux/of_gpio.h>
    15. 14 #include <linux/cdev.h>
    16. 15 #include <linux/uaccess.h>
    17. 16 #include <linux/platform_device.h>
    18. 17
    19. 18 #define MYLED_CNT                1                        /* 设备号个数 */
    20. 19 #define MYLED_NAME                "myled"                /* 名字 */
    21. 20
    22. 21 /* LED设备结构体 */
    23. 22 struct myled_dev {
    24. 23     dev_t devid;                        /* 设备号 */
    25. 24     struct cdev cdev;                /* cdev结构体 */
    26. 25     struct class *class;                /* 类 */
    27. 26     struct device *device;        /* 设备 */
    28. 27     int led_gpio;                        /* GPIO号 */
    29. 28 };
    30. 29
    31. 30 static struct myled_dev myled;        /* led设备 */
    32. 31
    33. 32 /*
    34. 33  * @description                : 打开设备
    35. 34  * [url=home.php?mod=space&uid=271674]@param[/url] – inode                : 传递给驱动的inode
    36. 35  * @param – filp                : 设备文件,file结构体有个叫做private_data的成员变量
    37. 36  *                                           一般在open的时候将private_data指向设备结构体。
    38. 37  * @return                        : 0 成功;其他 失败
    39. 38  */
    40. 39 static int myled_open(struct inode *inode, struct file *filp)
    41. 40 {
    42. 41     return 0;
    43. 42 }
    44. 43
    45. 44 /*
    46. 45  * @description                : 向设备写数据
    47. 46  * @param – filp                : 设备文件,表示打开的文件描述符
    48. 47  * @param – buf                : 要写给设备写入的数据
    49. 48  * @param – cnt                : 要写入的数据长度
    50. 49  * @param – offt                : 相对于文件首地址的偏移
    51. 50  * @return                        : 写入的字节数,如果为负值,表示写入失败
    52. 51  */
    53. 52 static ssize_t myled_write(struct file *filp, const char __user *buf,
    54. 53             size_t cnt, loff_t *offt)
    55. 54 {
    56. 55     int ret;
    57. 56     char kern_buf[1];
    58. 57
    59. 58     ret = copy_from_user(kern_buf, buf, cnt);                // 得到应用层传递过来的数据
    60. 59     if(0 > ret) {
    61. 60         printk(KERN_ERR "myled: Failed to copy data from user buffer\r\n");
    62. 61         return -EFAULT;
    63. 62     }
    64. 63
    65. 64     if (0 == kern_buf[0])
    66. 65         gpio_set_value(myled.led_gpio, 0);                // 如果传递过来的数据是0则关闭led
    67. 66     else if (1 == kern_buf[0])
    68. 67         gpio_set_value(myled.led_gpio, 1);                // 如果传递过来的数据是1则点亮led
    69. 68
    70. 69     return 0;
    71. 70 }
    72. 71
    73. 72 static int myled_init(struct device_node *nd)
    74. 73 {
    75. 74     const char *str;
    76. 75     int val;
    77. 76     int ret;
    78. 77
    79. 78     /* 从设备树中获取GPIO */
    80. 79     myled.led_gpio = of_get_named_gpio(nd, "led-gpio", 0);
    81. 80     if(!gpio_is_valid(myled.led_gpio)) {
    82. 81         printk(KERN_ERR "myled: Failed to get led-gpio\n");
    83. 82         return -EINVAL;
    84. 83     }
    85. 84
    86. 85     /* 申请使用GPIO */
    87. 86     ret = gpio_request(myled.led_gpio, "PS_LED0 Gpio");
    88. 87     if (ret) {
    89. 88         printk(KERN_ERR "myled: Failed to request led-gpio\n");
    90. 89         return ret;
    91. 90     }
    92. 91
    93. 92     /* 确定LED初始状态 */
    94. 93     ret = of_property_read_string(nd, "default-state", &str);
    95. 94     if(!ret) {
    96. 95         if (!strcmp(str, "on"))
    97. 96             val = 1;
    98. 97         else
    99. 98             val = 0;
    100. 99     } else
    101. 100         val = 0;
    102. 101
    103. 102     /* 将GPIO设置为输出模式并设置GPIO初始电平状态 */
    104. 103     gpio_direction_output(myled.led_gpio, val);
    105. 104
    106. 105     return 0;
    107. 106 }
    108. 107
    109. 108 /* LED设备操作函数 */
    110. 109 static struct file_operations myled_fops = {
    111. 110     .owner = THIS_MODULE,
    112. 111     .open = myled_open,
    113. 112     .write = myled_write,
    114. 113 };
    115. 114
    116. 115 /*
    117. 116  * @description                : platform驱动的probe函数,当驱动与设备
    118. 117  *                                          匹配成功以后此函数就会执行
    119. 118  * @param – pdev                : platform设备指针
    120. 119  * @return                        : 0,成功;其他负值,失败
    121. 120  */
    122. 121 static int myled_probe(struct platform_device *pdev)
    123. 122 {
    124. 123     int ret;
    125. 124
    126. 125     printk(KERN_INFO "myled: led driver and device has matched!\r\n");
    127. 126
    128. 127     /* led初始化 */
    129. 128     ret = myled_init(pdev->dev.of_node);
    130. 129     if (ret)
    131. 130         return ret;
    132. 131
    133. 132     /* 初始化cdev */
    134. 133     ret = alloc_chrdev_region(&myled.devid, 0, MYLED_CNT, MYLED_NAME);
    135. 134     if (ret)
    136. 135         goto out1;
    137. 136
    138. 137     myled.cdev.owner = THIS_MODULE;
    139. 138     cdev_init(&myled.cdev, &myled_fops);
    140. 139
    141. 140     /* 添加cdev */
    142. 141     ret = cdev_add(&myled.cdev, myled.devid, MYLED_CNT);
    143. 142     if (ret)
    144. 143         goto out2;
    145. 144
    146. 145     /* 创建类class */
    147. 146     myled.class = class_create(THIS_MODULE, MYLED_NAME);
    148. 147     if (IS_ERR(myled.class)) {
    149. 148         ret = PTR_ERR(myled.class);
    150. 149         goto out3;
    151. 150     }
    152. 151
    153. 152     /* 创建设备 */
    154. 153     myled.device = device_create(myled.class, &pdev->dev,
    155. 154                 myled.devid, NULL, MYLED_NAME);
    156. 155     if (IS_ERR(myled.device)) {
    157. 156         ret = PTR_ERR(myled.device);
    158. 157         goto out4;
    159. 158     }
    160. 159
    161. 160     return 0;
    162. 161
    163. 162 out4:
    164. 163     class_destroy(myled.class);
    165. 164         
    166. 165 out3:
    167. 166     cdev_del(&myled.cdev);
    168. 167         
    169. 168 out2:
    170. 169     unregister_chrdev_region(myled.devid, MYLED_CNT);
    171. 170         
    172. 171 out1:
    173. 172     gpio_free(myled.led_gpio);
    174. 173         
    175. 174     return ret;
    176. 175 }
    177. 176
    178. 177 /*
    179. 178  * @description                : platform驱动模块卸载时此函数会执行
    180. 179  * @param – dev                : platform设备指针
    181. 180  * @return                        : 0,成功;其他负值,失败
    182. 181  */
    183. 182 static int myled_remove(struct platform_device *dev)
    184. 183 {
    185. 184     printk(KERN_INFO "myled: led platform driver remove!\r\n");
    186. 185
    187. 186     /* 注销设备 */
    188. 187     device_destroy(myled.class, myled.devid);
    189. 188
    190. 189     /* 注销类 */
    191. 190     class_destroy(myled.class);
    192. 191
    193. 192     /* 删除cdev */
    194. 193     cdev_del(&myled.cdev);
    195. 194
    196. 195     /* 注销设备号 */
    197. 196     unregister_chrdev_region(myled.devid, MYLED_CNT);
    198. 197
    199. 198     /* 删除地址映射 */
    200. 199     gpio_free(myled.led_gpio);
    201. 200
    202. 201     return 0;
    203. 202 }
    204. 203
    205. 204 /* 匹配列表 */
    206. 205 static const struct of_device_id led_of_match[] = {
    207. 206     { .compatible = "alientek,led" },
    208. 207     { /* Sentinel */ }
    209. 208 };
    210. 209
    211. 210 /* platform驱动结构体 */
    212. 211 static struct platform_driver myled_driver = {
    213. 212     .driver = {
    214. 213         .name                   = "zynq-led",        // 驱动名字,用于和设备匹配
    215. 214         .of_match_table = led_of_match,                        // 设备树匹配表,用于和设备树中定义的设备匹配
    216. 215     },
    217. 216     .probe          = myled_probe,                // probe函数
    218. 217     .remove         = myled_remove,        // remove函数
    219. 218 };
    220. 219
    221. 220 /*
    222. 221  * @description                : 模块入口函数
    223. 222  * @param                        : 无
    224. 223  * @return                        : 无
    225. 224  */
    226. 225 static int __init myled_driver_init(void)
    227. 226 {
    228. 227     return platform_driver_register(&myled_driver);
    229. 228 }
    230. 229
    231. 230 /*
    232. 231  * @description                : 模块出口函数
    233. 232  * @param                        : 无
    234. 233  * @return                        : 无
    235. 234  */
    236. 235 static void __exit myled_driver_exit(void)
    237. 236 {
    238. 237     platform_driver_unregister(&myled_driver);
    239. 238 }
    240. 239
    241. 240 module_init(myled_driver_init);
    242. 241 module_exit(myled_driver_exit);
    243. 242
    244. 243 MODULE_AUTHOR("DengTao <<a href="mailto:773904075@qq.com">773904075@qq.com</a>>");
    245. 244 MODULE_DESCRIPTION("Led Platform Driver");
    246. 245 MODULE_LICENSE("GPL");
    复制代码



       代码中以前讲过的知识点这里就不再重述了!
       第72~106行,自定义函数myled_init,该函数的参数是struct device_node类型的指针,也就是led对应的设备节点,当调用函数的时候传递进来。
       第121~175行,platform驱动的probe函数myled_probe,当设备树中的设备节点与驱动之间匹配成功以后此函数就会执行,第128行调用myled_init函数时,将pdev->dev.of_node作为参数传递到函数中,platform_device结构体中内置了一个struct device类型的变量dev,如示例代码 37.2.9中所示,在struct device结构体中定义了一个struct device_node类型的指针变量of_node,使用设备树方式进行匹配的情况下,当匹配成功之后,of_node会指向设备树中定义的节点,所以在这里我们不需要通过调用of_find_node_by_path("/led")函数得到led的节点。我们原来在驱动加载函数里面做的工作现在全部放到probe函数里面完成。
       第182~202行,platform驱动的remobe函数myled_remove,当platform驱动模块被卸载时此函数就会执行。在此函数里面释放内存、注销字符设备等,也就是将原来驱动卸载函数里面的工作全部都放到remove函数中完成。
       第205~208行,匹配表,描述了此驱动都和什么样的设备匹配,第206行添加了一条值为"alientek,led"的compatible属性值,当设备树中某个设备节点的compatible属性值也为“alientek,led”的时候就会与此驱动匹配。
       第211~218行,platform_driver驱动结构体变量myled_driver,213行设置这个platform驱动的名字为“zynq-led”,因此,当驱动加载成功以后就会在/sys/bus/platform/drivers/目录下存在一个名为“zynq-led”的文件。第214行绑定platform驱动的of_match_table表。
       第225~228行,platform驱动模块入口函数,在此函数里面通过platform_driver_register向Linux内核注册一个platform驱动led_driver。
       第235~238行,platform驱动驱动模块出口函数,在此函数里面通过platform_driver_unregister从Linux内核卸载一个platform驱动led_driver。
1.3.3编写测试APP
       测试APP就直接使用上一章编写的ledApp.c即可。
       1.4运行测试
1.4.1编译驱动程序和测试APP
       1、编译驱动程序
       编写Makefile文件,将上一章实验目录“17_platform”下的Makefile文件拷贝到本章实验目录中,打开Makefile文件,将obj-m变量的值改为“leddriver.o”,Makefile内容如下所示:
    1. 示例代码 38.4.1 Makefile文件
    2. 1 KERN_DIR := /home/zynq/linux/kernel/linux-xlnx-xilinx-v2018.3
    3. 2
    4. 3 obj-m := leddriver.o
    5. 4
    6. 5 all:
    7. 6                 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERN_DIR) M=`pwd` modules
    8. 7
    9. 8 clean:
    10. 9                 make -C $(KERN_DIR) M=`pwd` clean
    复制代码



       第3行,设置obj-m变量的值为“leddriver.o”。
       文件修改完成之后保存退出,输入如下命令编译出驱动模块文件:
    1. make
    复制代码



        编译成功以后就会生成一个名为“leddriver.o”的驱动模块文件,如下所示:

图 38.4.1 编译驱动模块

       2、编译测试APP
       测试APP直接使用上一章的ledApp这个测试软件即可。
1.4.2运行测试
        将上一小节编译出来leddriver.ko和ledApp拷贝到开发板根文件系统/lib/modules/4.14.0-xilinx目录中,重启开发板,进入到目录/lib/modules/4.14.0-xilinx,输入如下命令加载leddriver.ko这个驱动模块。
    1. depmod                                //第一次加载驱动的时候需要运行此命令
    2. modprobe leddriver.ko        //加载驱动模块
    复制代码



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

图 38.4.2 zynq-led驱动

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

图 38.4.3 led设备

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

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



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



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




正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2024-11-23 17:13

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

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