1)实验平台:正点原子Linux开发板
2)摘自《正点原子I.MX6U嵌入式Linux驱动开发指南》关注官方微信号公众号,获取更多资料:正点原子
54.3硬件原理图分析
本章实验我们只使用到IMX6U-ALPHA开发板上的LED灯,因此实验硬件原理图参考8.3小节即可。
54.4试验程序编写
本实验对应的例程路径为:开发板光盘->2、Linux驱动例程->17_platform。
本章实验我们需要编写一个驱动模块和一个设备模块,其中驱动模块是platform驱动程序,设备模块是platform的设备信息。当这两个模块都加载成功以后就会匹配成功,然后platform驱动模块中的probe函数就会执行,probe函数中就是传统的字符设备驱动那一套。
54.4.1platform设备与驱动程序编写
新建名为“17_platform”的文件夹,然后在17_platform文件夹里面创建vscode工程,工作区命名为“platform”。新建名为leddevice.c和leddriver.c这两个文件,这两个文件分别为LED灯的platform设备文件和LED灯的platform的驱动文件。在leddevice.c中输入如下所示内容:
示例代码54.4.1.1 leddevice.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文件名 : leddevice.c
26作者 : 左忠凯
27版本 : V1.0
28描述 : platform设备
29其他 : 无
30论坛 : www.openedv.com
31日志 : 初版V1.0 2019/8/13 左忠凯创建
32 ***************************************************************/
33
34/*
35 * 寄存器地址定义
36 */
37 #define CCM_CCGR1_BASE (0X020C406C)
38 #define SW_MUX_GPIO1_IO03_BASE (0X020E0068)
39 #define SW_PAD_GPIO1_IO03_BASE (0X020E02F4)
40 #define GPIO1_DR_BASE (0X0209C000)
41 #define GPIO1_GDIR_BASE (0X0209C004)
42 #define REGISTER_LENGTH 4
43
44/* @description : 释放flatform设备模块的时候此函数会执行
45 * @param - dev : 要释放的设备
46 * @return : 无
47 */
48staticvoid led_release(struct device *dev)
49{
50 printk("led device released!");
51}
52
53/*
54 * 设备资源信息,也就是LED0所使用的所有寄存器
55 */
56staticstruct resource led_resources[]={
57[0]={
58.start = CCM_CCGR1_BASE,
59.end =(CCM_CCGR1_BASE + REGISTER_LENGTH -1),
60.flags = IORESOURCE_MEM,
61},
62[1]={
63.start = SW_MUX_GPIO1_IO03_BASE,
64.end =(SW_MUX_GPIO1_IO03_BASE + REGISTER_LENGTH -1),
65.flags = IORESOURCE_MEM,
66},
67[2]={
68.start = SW_PAD_GPIO1_IO03_BASE,
69.end =(SW_PAD_GPIO1_IO03_BASE + REGISTER_LENGTH -1),
70.flags = IORESOURCE_MEM,
71},
72[3]={
73.start = GPIO1_DR_BASE,
74.end =(GPIO1_DR_BASE + REGISTER_LENGTH -1),
75.flags = IORESOURCE_MEM,
76},
77[4]={
78.start = GPIO1_GDIR_BASE,
79.end =(GPIO1_GDIR_BASE + REGISTER_LENGTH -1),
80.flags = IORESOURCE_MEM,
81},
82};
83
84
85/*
86 * platform设备结构体
87 */
88staticstruct platform_device leddevice ={
89.name ="imx6ul-led",
90.id =-1,
91.dev ={
92.release =&led_release,
93},
94.num_resources = ARRAY_SIZE(led_resources),
95.resource = led_resources,
96};
97
98/*
99 * @description : 设备模块加载
100 * @param : 无
101 * @return : 无
102 */
103staticint __init leddevice_init(void)
104{
105return platform_device_register(&leddevice);
106}
107
108/*
109 * @description : 设备模块注销
110 * @param : 无
111 * @return : 无
112 */
113staticvoid __exit leddevice_exit(void)
114{
115 platform_device_unregister(&leddevice);
116}
117
118 module_init(leddevice_init);
119 module_exit(leddevice_exit);
120 MODULE_LICENSE("GPL");
121 MODULE_AUTHOR("zuozhongkai");
leddevice.c文件内容就是按照示例代码54.2.3.4的platform设备模板编写的。
第56~82行,led_resources数组,也就是设备资源,描述了LED所要使用到的寄存器信息,也就是IORESOURCE_MEM资源。
第88~96,platform设备结构体变量leddevice,这里要注意name字段为“imx6ul-led”,所以稍后编写platform驱动中的name字段也要为“imx6ul-led”,否则设备和驱动匹配失败。
第103~106行,设备模块加载函数,在此函数里面通过platform_device_register向Linux内核注册leddevice这个platform设备。
第113~116行,设备模块卸载函数,在此函数里面通过platform_device_unregister从Linux内核中删除掉leddevice这个platform设备。
leddevice.c文件编写完成以后就编写leddriver.c这个platform驱动文件,在leddriver.c里面输入如下内容:
示例代码54.4.1.2 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
34 #define LEDDEV_CNT 1 /* 设备号长度 */
35 #define LEDDEV_NAME "platled" /* 设备名字 */
36 #define LEDOFF 0
37 #define LEDON 1
38
39/* 寄存器名 */
40staticvoid __iomem *IMX6U_CCM_CCGR1;
41staticvoid __iomem *SW_MUX_GPIO1_IO03;
42staticvoid __iomem *SW_PAD_GPIO1_IO03;
43staticvoid __iomem *GPIO1_DR;
44staticvoid __iomem *GPIO1_GDIR;
45
46/* leddev设备结构体 */
47struct leddev_dev{
48 dev_t devid; /* 设备号 */
49struct cdev cdev; /* cdev */
50struct class *class; /* 类 */
51struct device *device; /* 设备 */
52int major; /* 主设备号 */
53};
54
55struct leddev_dev leddev; /* led设备 */
56
57/*
58 * @description : LED打开/关闭
59 * @param - sta : LEDON(0) 打开LED,LEDOFF(1) 关闭LED
60 * @return : 无
61 */
62void led0_switch(u8 sta)
63{
64 u32 val =0;
65if(sta == LEDON){
66 val = readl(GPIO1_DR);
67 val &=~(1<<3);
68 writel(val, GPIO1_DR);
69}elseif(sta == LEDOFF){
70 val = readl(GPIO1_DR);
71 val|=(1<<3);
72 writel(val, GPIO1_DR);
73}
74}
75
76/*
77 * @description : 打开设备
78 * @param – inode : 传递给驱动的inode
79 * @param - filp : 设备文件,file结构体有个叫做private_data的成员变量
80 * 一般在open的时候将private_data指向设备结构体。
81 * @return : 0 成功;其他失败
82 */
83staticint led_open(struct inode *inode,struct file *filp)
84{
85 filp->private_data =&leddev;/* 设置私有数据 */
86return0;
87}
88
89/*
90 * @description : 向设备写数据
91 * @param – filp : 设备文件,表示打开的文件描述符
92 * @param - buf : 要写给设备写入的数据
93 * @param - cnt : 要写入的数据长度
94 * @param - offt : 相对于文件首地址的偏移
95 * @return : 写入的字节数,如果为负值,表示写入失败
96 */
97static ssize_t led_write(struct file *filp,constchar __user *buf,
size_t cnt, loff_t *offt)
98{
99int retvalue;
100unsignedchar databuf[1];
101unsignedchar ledstat;
102
103 retvalue = copy_from_user(databuf, buf, cnt);
104if(retvalue <0){
105return-EFAULT;
106}
107
108 ledstat = databuf[0]; /* 获取状态值 */
109if(ledstat == LEDON){
110 led0_switch(LEDON); /* 打开LED灯 */
111}elseif(ledstat == LEDOFF){
112 led0_switch(LEDOFF); /* 关闭LED灯 */
113}
114return0;
115}
116
117/* 设备操作函数 */
118staticstruct file_operations led_fops ={
119.owner = THIS_MODULE,
120.open = led_open,
121.write = led_write,
122};
123
124/*
125 * @description : flatform驱动的probe函数,当驱动与设备
126 * 匹配以后此函数就会执行
127 * @param - dev : platform设备
128 * @return : 0,成功;其他负值,失败
129 */
130staticint led_probe(struct platform_device *dev)
131{
132int i =0;
133int ressize[5];
134 u32 val =0;
135struct resource *ledsource[5];
136
137 printk("led driver and device has matched!");
138/* 1、获取资源 */
139for(i =0; i <5; i++){
140 ledsource[i]= platform_get_resource(dev, IORESOURCE_MEM, i);
141if(!ledsource[i]){
142 dev_err(&dev->dev,"No MEM resource for always on");
143return-ENXIO;
144}
145 ressize[i]= resource_size(ledsource[i]);
146}
147
148/* 2、初始化LED */
149/* 寄存器地址映射 */
150 IMX6U_CCM_CCGR1 = ioremap(ledsource[0]->start, ressize[0]);
151 SW_MUX_GPIO1_IO03 = ioremap(ledsource[1]->start, ressize[1]);
152 SW_PAD_GPIO1_IO03 = ioremap(ledsource[2]->start, ressize[2]);
153 GPIO1_DR = ioremap(ledsource[3]->start, ressize[3]);
154 GPIO1_GDIR = ioremap(ledsource[4]->start, ressize[4]);
155
156 val = readl(IMX6U_CCM_CCGR1);
157 val &=~(3<<26); /* 清除以前的设置 */
158 val |=(3<<26); /* 设置新值 */
159 writel(val, IMX6U_CCM_CCGR1);
160
161/* 设置GPIO1_IO03复用功能,将其复用为GPIO1_IO03 */
162 writel(5, SW_MUX_GPIO1_IO03);
163 writel(0x10B0, SW_PAD_GPIO1_IO03);
164
165/* 设置GPIO1_IO03为输出功能 */
166 val = readl(GPIO1_GDIR);
167 val &=~(1<<3); /* 清除以前的设置 */
168 val |=(1<<3); /* 设置为输出 */
169 writel(val, GPIO1_GDIR);
170
171/* 默认关闭LED1 */
172 val = readl(GPIO1_DR);
173 val |=(1<<3);
174 writel(val, GPIO1_DR);
175
176/* 注册字符设备驱动 */
177/*1、创建设备号 */
178if(leddev.major){ /* 定义了设备号 */
179 leddev.devid = MKDEV(leddev.major,0);
180 register_chrdev_region(leddev.devid, LEDDEV_CNT,
LEDDEV_NAME);
181}else{ /* 没有定义设备号 */
182 alloc_chrdev_region(&leddev.devid,0, LEDDEV_CNT,
LEDDEV_NAME);
183 leddev.major = MAJOR(leddev.devid);
184}
185
186/* 2、初始化cdev */
187 leddev.cdev.owner = THIS_MODULE;
188 cdev_init(&leddev.cdev,&led_fops);
189
190/* 3、添加一个cdev */
191 cdev_add(&leddev.cdev, leddev.devid, LEDDEV_CNT);
192
193/* 4、创建类 */
194 leddev.class = class_create(THIS_MODULE, LEDDEV_NAME);
195if(IS_ERR(leddev.class)){
196return PTR_ERR(leddev.class);
197}
198
199/* 5、创建设备 */
200 leddev.device = device_create(leddev.class,NULL, leddev.devid,
NULL, LEDDEV_NAME);
201if(IS_ERR(leddev.device)){
202return PTR_ERR(leddev.device);
203}
204
205return0;
206}
207
208/*
209 * @description :移除platform驱动的时候此函数会执行
210 * @param - dev : platform设备
211 * @return : 0,成功;其他负值,失败
212 */
213staticint led_remove(struct platform_device *dev)
214{
215 iounmap(IMX6U_CCM_CCGR1);
216 iounmap(SW_MUX_GPIO1_IO03);
217 iounmap(SW_PAD_GPIO1_IO03);
218 iounmap(GPIO1_DR);
219 iounmap(GPIO1_GDIR);
220
221 cdev_del(&leddev.cdev); /* 删除cdev */
222 unregister_chrdev_region(leddev.devid, LEDDEV_CNT);
223 device_destroy(leddev.class, leddev.devid);
224 class_destroy(leddev.class);
225return0;
226}
227
228/* platform驱动结构体 */
229staticstruct platform_driver led_driver ={
230.driver ={
231.name ="imx6ul-led",/* 驱动名字,用于和设备匹配 */
232},
233.probe = led_probe,
234.remove = led_remove,
235};
236
237/*
238 * @description : 驱动模块加载函数
239 * @param : 无
240 * @return : 无
241 */
242staticint __init leddriver_init(void)
243{
244return platform_driver_register(&led_driver);
245}
246
247/*
248 * @description : 驱动模块卸载函数
249 * @param : 无
250 * @return : 无
251 */
252staticvoid __exit leddriver_exit(void)
253{
254 platform_driver_unregister(&led_driver);
255}
256
257 module_init(leddriver_init);
258 module_exit(leddriver_exit);
259 MODULE_LICENSE("GPL");
260 MODULE_AUTHOR("zuozhongkai");
leddriver.c文件内容就是按照示例代码54.2.2.5的platform驱动模板编写的。
第34~122行,传统的字符设备驱动。
第130~206行,probe函数,当设备和驱动匹配以后此函数就会执行,当匹配成功以后会在终端上输出“led driver and device has matched!”这样语句。在probe函数里面初始化LED、注册字符设备驱动。也就是将原来在驱动加载函数里面做的工作全部放到probe函数里面完成。
第213~226行,remobe函数,当卸载platform驱动的时候此函数就会执行。在此函数里面释放内存、注销字符设备等。也就是将原来驱动卸载函数里面的工作全部都放到remove函数中完成。
第229~235行,platform_driver驱动结构体,注意name字段为"imx6ul-led",和我们在leddevice.c文件里面设置的设备name字段一致。
第242~245行,驱动模块加载函数,在此函数里面通过platform_driver_register向Linux内核注册led_driver驱动。
第252~255行,驱动模块卸载函数,在此函数里面通过platform_driver_unregister从Linux内核卸载led_driver驱动。
54.4.2 测试APP编写
测试APP的内容很简单,就是打开和关闭LED灯,新建ledApp.c这个文件,然后在里面输入如下内容:
示例代码54.4.2.1 ledApp.c文件代码段
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 */
29int main(int argc,char*argv[])
30{
31 int fd, retvalue;
32 char*filename;
33 unsignedchar databuf[2];
34
35 if(argc !=3){
36 printf("Error Usage!");
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!", 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!");
52 close(fd);
53 return-1;
54 }
55
56 retvalue = close(fd);/* 关闭文件 */
57 if(retvalue <0){
58 printf("file %s close failed!", argv[1]);
59 return-1;
60 }
61 return0;
62}
ledApp.c文件内容很简单,就是控制LED灯的亮灭,和第四十一章的测试APP基本一致,这里就不重复讲解了。
54.5 运行测试
54.5.1 编译驱动程序和测试APP
1、编译驱动程序
编写Makefile文件,本章实验的Makefile文件和第四十章实验基本一样,只是将obj-m变量的值改为“leddevice.o leddriver.o”,Makefile内容如下所示:
示例代码54.5.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 := leddevice.o leddriver.o
......
11 clean:
12$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
第4行,设置obj-m变量的值为“leddevice.o leddriver.o”。
输入如下命令编译出驱动模块文件:
make-j32
编译成功以后就会生成一个名为“leddevice.ko leddriver.ko”的驱动模块文件。
2、编译测试APP
输入如下命令编译测试ledApp.c这个测试程序:
arm-linux-gnueabihf-gccledApp.c -o ledApp
编译成功以后就会生成ledApp这个应用程序。
54.4.2 运行测试
将上一小节编译出来leddevice.ko、leddriver.ko和ledApp这两个文件拷贝到rootfs/lib/modules/4.1.15目录中,重启开发板,进入到目录lib/modules/4.1.15中,输入如下命令加载leddevice.ko设备模块和leddriver.ko这个驱动模块。
depmod //第一次加载驱动的时候需要运行此命令
modprobe leddevice.ko //加载设备模块
modprobeleddriver.ko //加载驱动模块
根文件系统中/sys/bus/platform/目录下保存着当前板子platform总线下的设备和驱动,其中devices子目录为platform设备,drivers子目录为plartofm驱动。查看/sys/bus/platform/devices/目录,看看我们的设备是否存在,我们在leddevice.c中设置leddevice(platform_device类型)的name字段为“imx6ul-led”,也就是设备名字为imx6ul-led,因此肯定在/sys/bus/platform/devices/目录下存在一个名字“imx6ul-led”的文件,否则说明我们的设备模块加载失败,结果如图54.4.2.1所示:
图54.4.2.1 imx6ul-led设备
同理,查看/sys/bus/platform/drivers/目录,看一下驱动是否存在,我们在leddriver.c中设置led_driver(platform_driver类型)的name字段为“imx6ul-led”,因此会在/sys/bus/platform/drivers/目录下存在名为“imx6ul-led”这个文件,结果如图54.4.2.2所示:
图54.4.2.2 imx6ul-led驱动
驱动模块和设备模块加载成功以后platform总线就会进行匹配,当驱动和设备匹配成功以后就会输出如图54.4.2.3所示一行语句:
图54.4.2.3 驱动和设备匹配成功
驱动和设备匹配成功以后就可以测试LED灯驱动了,输入如下命令打开LED灯:
./ledApp /dev/platled 1 //打开LED灯
在输入如下命令关闭LED灯:
./ledApp /dev/platled 0 //关闭LED灯
观察一下LED灯能否打开和关闭,如果可以的话就说明驱动工作正常,如果要卸载驱动的话输入如下命令即可:
rmmod leddevice.ko
rmmodleddriver.ko