Linux版本号4.1.15 芯片I.MX6ULL 大叔学Linux 品人间百味 思文短情长
三、硬件原理图分析
四、驱动开发
1、platform设备与驱动程序开发
cpp
53 /*
54 * 设备资源信息,也就是 LED0 所使用的所有寄存器
55 */
56 static struct 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 };
led_resources数组,也就是设备资源,描述了 LED 所要使用到的寄存器信息,也就是 IORESOURCE_MEM 资源。
cpp
85 /*
86 * platform 设备结构体
87 */
88 static struct 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 };
platform 设备结构体变量 leddevice ,这里要注意 name 字段为"imx6ul-led",所
以稍后编写 platform 驱动中的 name 字段也要为"imx6ul-led",否则设备和驱动匹配失败。
cpp
98 /*
99 * @description : 设备模块加载
100 * @param : 无
101 * @return : 无
102 */
103 static int __init leddevice_init(void)
104 {
105 return platform_device_register(&leddevice);
106 }
设备模块加载函数,在此函数里面通过 platform_device_register向 Linux 内核注册 leddevice 这个 platform 设备。
cpp
108 /*
109 * @description : 设备模块注销
110 * @param : 无
111 * @return : 无
112 */
113 static void __exit leddevice_exit(void)
114 {
115 platform_device_unregister(&leddevice);
116 }
设备模块卸载函数,在此函数里面通过platform_device_unregister从 Linux内核中删除掉 leddevice 这个 platform 设备。
cpp
34 #define LEDDEV_CNT 1 /* 设备号长度 */
35 #define LEDDEV_NAME "platled" /* 设备名字 */
36 #define LEDOFF 0
37 #define LEDON 1
38
39 /* 寄存器名 */
40 static void __iomem *IMX6U_CCM_CCGR1;
41 static void __iomem *SW_MUX_GPIO1_IO03;
42 static void __iomem *SW_PAD_GPIO1_IO03;
43 static void __iomem *GPIO1_DR;
44 static void __iomem *GPIO1_GDIR;
45
46 /* leddev 设备结构体 */
47 struct leddev_dev{
48 dev_t devid; /* 设备号 */
49 struct cdev cdev; /* cdev */
50 struct class *class; /* 类 */
51 struct device *device; /* 设备 */
52 int major; /* 主设备号 */
53 };
54
55 struct leddev_dev leddev; /* led 设备 */
56
57 /*
58 * @description : LED 打开/关闭
59 * @param - sta : LEDON(0) 打开 LED, LEDOFF(1) 关闭 LED
60 * @return : 无
61 */
62 void led0_switch(u8 sta)
63 {
64 u32 val = 0;
65 if(sta == LEDON){
66 val = readl(GPIO1_DR);
67 val &= ~(1 << 3);
68 writel(val, GPIO1_DR);
69 }else if(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 */
83 static int led_open(struct inode *inode, struct file *filp)
84 {
85 filp->private_data = &leddev; /* 设置私有数据 */
86 return 0;
87 }
88
89 /*
90 * @description : 向设备写数据
91 * @param -- filp : 设备文件,表示打开的文件描述符
92 * @param - buf : 要写给设备写入的数据
93 * @param - cnt : 要写入的数据长度
94 * @param - offt : 相对于文件首地址的偏移
95 * @return : 写入的字节数,如果为负值,表示写入失败
96 */
97 static ssize_t led_write(struct file *filp, const char __user *buf,
size_t cnt, loff_t *offt)
98 {
99 int retvalue;
100 unsigned char databuf[1];
101 unsigned char ledstat;
102
103 retvalue = copy_from_user(databuf, buf, cnt);
104 if(retvalue < 0) {
105 return -EFAULT;
106 }
107
108 ledstat = databuf[0]; /* 获取状态值 */
109 if(ledstat == LEDON) {
110 led0_switch(LEDON); /* 打开 LED 灯 */
111 }else if(ledstat == LEDOFF) {
112 led0_switch(LEDOFF); /* 关闭 LED 灯 */
113 }
114 return 0;
115 }
116
117 /* 设备操作函数 */
118 static struct file_operations led_fops = {
119 .owner = THIS_MODULE,
120 .open = led_open,
121 .write = led_write,
122 };
以上是传统的字符设备驱动。
cpp
124 /*
125 * @description : flatform 驱动的 probe 函数,当驱动与设备
126 * 匹配以后此函数就会执行
127 * @param - dev : platform 设备
128 * @return : 0,成功;其他负值,失败
129 */
130 static int led_probe(struct platform_device *dev)
131 {
132 int i = 0;
133 int ressize[5];
134 u32 val = 0;
135 struct resource *ledsource[5];
136
137 printk("led driver and device has matched!\r\n");
138 /* 1、获取资源 */
139 for (i = 0; i < 5; i++) {
140 ledsource[i] = platform_get_resource(dev, IORESOURCE_MEM, i);
141 if (!ledsource[i]) {
142 dev_err(&dev->dev, "No MEM resource for always on\n");
143 return -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、创建设备号 */
178 if (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);
195 if (IS_ERR(leddev.class)) {
196 return PTR_ERR(leddev.class);
197 }
198
199 /* 5、创建设备 */
200 leddev.device = device_create(leddev.class, NULL, leddev.devid,
NULL, LEDDEV_NAME);
201 if (IS_ERR(leddev.device)) {
202 return PTR_ERR(leddev.device);
203 }
204
205 return 0;
206 }
probe函数,当设备和驱动匹配以后此函数就会执行,当匹配成功以后会在终端上输出"led driver and device has matched!"这样语句。在 probe 函数里面初始化 LED、注册字符设备驱动。也就是将原来在驱动加载函数里面做的工作全部放到 probe 函数里面完成。
cpp
208 /*
209 * @description :移除 platform 驱动的时候此函数会执行
210 * @param - dev : platform 设备
211 * @return : 0,成功;其他负值,失败
212 */
213 static int 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);
225 return 0;
226 }
remove 函数,当卸载 platform 驱动的时候此函数就会执行。在此函数里面释放内存、注销字符设备等。也就是将原来驱动卸载函数里面的工作全部都放到 remove 函数中完成。
cpp
228 /* platform 驱动结构体 */
229 static struct platform_driver led_driver = {
230 .driver = {
231 .name = "imx6ul-led", /* 驱动名字,用于和设备匹配 */
232 },
233 .probe = led_probe,
234 .remove = led_remove,
235 };
platform_driver驱动结构体,注意 name 字段为"imx6ul-led",和我们在leddevice.c 文件里面设置的设备 name 字段一致。
cpp
237 /*
238 * @description : 驱动模块加载函数
239 * @param : 无
240 * @return : 无
241 */
242 static int __init leddriver_init(void)
243 {
244 return platform_driver_register(&led_driver);
245 }
驱动模块加载函数,在此函数里面通过 platform_driver_register向 Linux 内核注册 led_driver 驱动。
cpp
247 /*
248 * @description : 驱动模块卸载函数
249 * @param : 无
250 * @return : 无
251 */
252 static void __exit leddriver_exit(void)
253 {
254 platform_driver_unregister(&led_driver);
255 }
驱动模块卸载函数,在此函数里面通过 platform_driver_unregister 从 Linux
内核卸载 led_driver 驱动。
2、测试APP开发
cpp
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 }
五、运行测试
1、编译驱动程序和测试APP
cpp
4 obj-m := leddevice.o leddriver.o
设置 obj-m 变量的值为"leddevice.o leddriver.o"。
输入如下命令编译出驱动模块文件:
make -j32
编译成功以后就会生成一个名为"leddevice.ko leddriver.ko"的驱动模块文件。
输入如下命令编译测试 ledApp.c 这个测试程序:
arm-linux-gnueabihf-gcc ledApp.c -o ledApp
编译成功以后就会生成ledApp这个应用程序。
2、运行测试
cpp
depmod //第一次加载驱动的时候需要运行此命令
modprobe leddevice.ko //加载设备模块
modprobe leddriver.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"的文件,否则说明我们的设备模块加载失败,结果如图 所示:
查看/sys/bus/platform/drivers/目录,看一下驱动是否存在,我们在 leddriver.c 中设置
led_driver (platform_driver 类型)的 name 字段为"imx6ul-led",因此会在/sys/bus/platform/drivers/
目录下存在名为"imx6ul-led"这个文件,结果如图 所示:
驱动模块和设备模块加载成功以后 platform 总线就会进行匹配,当驱动和设备匹配成功以
后就会输出如图 所示一行语句:
驱动和设备匹配成功以后就可以测试 LED 灯驱动了,输入如下命令打开 LED 灯:
/ledApp /dev/platled 1 //打开 LED 灯
在输入如下命令关闭 LED 灯:
./ledApp /dev/platled 0 //关闭 LED 灯
观察一下 LED 灯能否打开和关闭,如果可以的话就说明驱动工作正常,如果要卸载驱动的
话输入如下命令即可:
rmmod leddevice.ko
rmmod leddriver.ko
六、总结:
本篇笔记主要学习了platform设备驱动开发的相关概念。将分成两次笔记进行学习。本次笔记主要学习platform设备驱动开发相关的理论知识。主要内容包括:Linux驱动的分离与分层、platform平台驱动模型简介。其中驱动的分离与分层有包括驱动的分离、驱动的分层。platform平台驱动模型简介主要包括platform总线、platform驱动与platform设备。
版权声明:本文为CSDN博主「大叔学Linux」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/jiage987450/article/details/134125677
本文为参考正点原子开发板配套教程整理而得,仅用于学习交流使用,不得用于商业用途。