platform 设备驱动2(有设备树)

** 设备树下的 platform 驱动简介**
platform 驱动框架分为总线、设备和驱动,其中总线不需要我们这些驱动程序员去管理,这个是 Linux 内核提供的,我们在编写驱动的时候只要关注于设备和驱动的具体实现即可。在没 有设备树的 Linux 内核下,我们需要分别编写并注册 platform_device 和 platform_driver,分别代表设备和驱动。在使用设备树的时候,设备的描述被放到了设备树中,因此 platform_device 就不需要我们去编写了,我们只需要实现 platform_driver 即可。在编写基于设备树的 platform 驱动的时候我们需要注意一下几点:
1、在设备树中创建设备节点
毫无疑问,肯定要先在设备树中创建设备节点来描述设备信息,重点是要设置好 compatible
属性的值,因为 platform 总线需要通过设备节点的 compatible 属性值来匹配驱动!这点要切记。比如,我们可以编写如下所示的设备节点来描述我们本章实验要用到的 LED 这个设备:

1 gpioled { 
2 #address-cells = <1>;
3 #size-cells = <1>;
4 compatible = "atkalpha-gpioled";
5 pinctrl-names = "default"; 
6 pinctrl-0 = <&pinctrl_led>;
7 led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
8 status = "okay"; 
9 };

示例 中的 gpioled 节点其实就是 45.4.1.2 小节中创建的 gpioled 设备节点,我们可以直接拿过来用。注意第 4 行的 compatible 属性值为“atkalpha-gpioled”,因此一会在编写 platform驱动的时候 of_match_table 属性表中要有“atkalpha-gpioled”。
2、编写 platform 驱动的时候要注意兼容属性
在使用设备树的时候 platform 驱动会通过 of_match_table 来保存兼容性值,也就是表明此驱动兼容哪些设备。所以,of_match_table 将会尤为重要,比如本例程的 platform 驱动中 platform_driver 就可以按照如下所示设置:

1 static const struct of_device_id leds_of_match[] = { 
2 { .compatible = "atkalpha-gpioled" }, /* 兼容属性 */
3 { /* Sentinel */ } 
4 };
5 
6 MODULE_DEVICE_TABLE(of, leds_of_match);
7 
8 static struct platform_driver leds_platform_driver = { 9 .driver = {
10 .name = "imx6ul-led",
11 .of_match_table = leds_of_match,
12 },
13 .probe = leds_probe,
14 .remove = leds_remove,
15 };

第 1~4 行,of_device_id 表,也就是驱动的兼容表,是一个数组,每个数组元素为 of_device_id类型。每个数组元素都是一个兼容属性,表示兼容的设备,一个驱动可以跟多个设备匹配。这里我们仅仅匹配了一个设备,那就是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 函数就会执行,都是大同小异的。
3.1 修改设备树文件

1 gpioled { 
2 #address-cells = <1>;
3 #size-cells = <1>;
4 compatible = "atkalpha-gpioled";
5 pinctrl-names = "default"; 
6 pinctrl-0 = <&pinctrl_led>;
7 led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
8 status = "okay"; 
9 };

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

第 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 驱动的名字为“imx6ulled”,因此,当驱动加载成功以后就会在/sys/bus/platform/drivers/目录下存在一个名为“imx6uled”的文件。 第 192 行设置 of_match_table 为上面的led_of_match。
第 203~206 行,驱动模块加载函数,在此函数里面通过 platform_driver_register 向 Linux 内
核注册 led_driver 驱动。
第 213~216 行,驱动模块卸载函数,在此函数里面通过 platform_driver_unregister 从 Linux
内核卸载 led_driver 驱动。
编译驱动程序和测试 APP

1 #include "stdio.h"
2 #include "unistd.h"
3 #include "sys/types.h"
4 #include "sys/stat.h"
5 #include "fcntl.h"
6 #include "stdlib.h"
7 #include "string.h"
8 /***************************************************************
9 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
10 文件名 : ledApp.c
11 作者 : 左忠凯
12 版本 : V1.0
13 描述 : platform 驱动驱测试 APP。
14 其他 : 无
15 使用方法 :./ledApp /dev/platled 0 关闭 LED
16 ./ledApp /dev/platled 1 打开 LED 
17 论坛 : www.openedv.com
18 日志 : 初版 V1.0 2019/8/16 左忠凯创建
19 ***************************************************************/
20 #define LEDOFF 0
21 #define LEDON 1
22
23 /*
24 * @description : main 主程序
25 * @param - argc : argv 数组元素个数
26 * @param - argv : 具体参数
27 * @return : 0 成功;其他 失败
28 */
29 int main(int argc, char *argv[])
30 {
31 int fd, retvalue;
32 char *filename;
33 unsigned char databuf[2];
34
35 if(argc != 3){
36 printf("Error Usage!\r\n");
37 return -1;
38 }
39
40 filename = argv[1];
41 /* 打开 led 驱动 */
42 fd = open(filename, O_RDWR);
43 if(fd < 0){
44 printf("file %s open failed!\r\n", argv[1]);
45 return -1;
46 }
47 
48 databuf[0] = atoi(argv[2]); /* 要执行的操作:打开或关闭 */
49 retvalue = write(fd, databuf, sizeof(databuf));
50 if(retvalue < 0){
51 printf("LED Control Failed!\r\n");
52 close(fd);
53 return -1;
54 }
55
56 retvalue = close(fd); /* 关闭文件 */
57 if(retvalue < 0){
58 printf("file %s close failed!\r\n", argv[1]);
59 return -1;
60 }
61 return 0;
62 }

ledApp.c 文件内容很简单,就是控制 LED 灯的亮灭。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

那肯定是很多年以后!

你的鼓励就我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值