i.MX6ULL终结者Linux I2C驱动实验实验程序编写

文章目录

    • 1 硬件原理图
    • 2 修改设备树
    • 3 驱动程序编写
    • 4 应用测试程序

1 硬件原理图

i.MX6ULL终结者Linux I2C驱动实验实验程序编写_第1张图片

图 1.1

2 修改设备树

1、添加pinctrl信息
首先在设备树文件中添加AP3216C设备的引脚信息,AP3216C使用的是I2C1接口,对应的是UART4_TXD 和 UART4_RXD两个引脚,所以需要在设备树中将这两个引脚复用成I2C1功能,AP3216C设备上还有一个中断引脚,使用了GIO1_IO01,如果用到中断功能的话,同样需要添加引脚信息。在本实验中暂时不使用中断。
打开topeet_emmc_4_3.dts文件,有如下内容:

1 pinctrl_i2c1: i2c1grp { 
2 	fsl,pins = < 
3 		MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 0x4001b8b0 
4	 	MX6UL_PAD_UART4_RX_DATA__I2C1_SDA 0x4001b8b0 
5 		>; 
6 };

这里将 UART4_TXD 和 UART4_RXD 这两个 IO 分别复用为 I2C1_SCL 和 I2C1_SDA,电气属性都设置为 0x4001b8b0。
2、在I2C1的设备节点中添加AP3216C设备子节点
打开topeet_emmc_4_3.dts文件,I2C1节点下默认有如下内容:

1 &i2c1 { 
2      clock-frequency = <100000>; 
3      pinctrl-names = "default"; 
4      pinctrl-0 = <&pinctrl_i2c1>; 
5      status = "okay"; 
6 
7      mag3110@0e { 
8          compatible = "fsl,mag3110"; 
9          reg = <0x0e>; 
10             position = <2>; 
11         }; 
12 
13       fxls8471@1e { 
14             compatible = "fsl,fxls8471"; 
15             reg = <0x1e>; 
16             position = <0>; 
17             interrupt-parent = <&gpio5>; 
18             interrupts = <0 8>; 
19         }; 
20 }; 

第 2 行,clock-frequency 属性为 I2C 频率,这里设置为 100KHz。
第 4 行,pinctrl-0 属性指定 I2C 所使用的 IO 为pinctrl_i2c1 子节点。
第 7~11 行,mag3110 是个磁力计,NXP 官方的 EVK 开发板上接了 mag3110,因此 NXP在 i2c1 节点下添加了 mag3110 这个子节点。i.MX6UL终结者开发板上没有用到mag3110,因此可以将此节点删除掉。
第 13~19 行,NXP 官方 EVK 开发板也接了一个 fxls8471,i.MX6UL终结者开发板上同样没有此器件,所以也要将其删除掉。将 i2c1 节点里面原有的 mag3110 和 fxls8471 这两个 I2C 子节点删除,然后添加 ap3216c子节点信息,完成以后的 i2c1 节点内容如下所示:

1 &i2c1 { 
2      clock-frequency = <100000>; 
3      pinctrl-names = "default"; 
4      pinctrl-0 = <&pinctrl_i2c1>; 
5      status = "okay"; 
6 
7      ap3216c@1e { 
8          compatible = "ap3216c"; 
9          reg = <0x1e>; 
10         }; 
11 }; 

第 7 行,ap3216c 子节点,@后面的“1e”是 ap3216c 的器件地址。
第 8 行,设置 compatible 值为“ap3216c”。
第 9 行,reg 属性也是设置 ap3216c 器件地址的,因此 reg 设置为 0x1e。
设备树文件修改完成后,使用“make dtbs”命令重新编译下载到开发板上,启动Linux系统。在/sys/bus/i2c/devices 目录下存放着所有 I2C 设备,如果设备树修改正确的话,会在sys/bus/i2c/devices 目录下看到一个名为“0-001e”的子目录,如图 2.1所示:
在这里插入图片描述

图 2.1

上图中的“0-001e”目录就是ap3216c的设备目录,“1e”是ap3216c设备的地址,进入0-001e目录可以看到ap3216c设备的相关信息,有一个name文件,用于保存设备名称,可以使用“cat”命令查看,如图 2.2所示:
在这里插入图片描述

图 2.2

3 驱动程序编写

本实验例程路径:i.MX6UL终结者光盘资料/06_Linux驱动例程/18_ap3216
创建ap3216c_iic.c和ap3216c_reg.h两个文件,ap3216c_reg.h用于保存ap3216c设备的寄存器信息,ap3216c_iic.c是驱动文件。
ap3216c_reg.h文件内容如下:

 1 #ifndef AP3216C_H
  2 #define AP3216C_H
  3 
  4 #define AP3216C_ADDR            0X1E    /* AP3216C器件地址  */
  5 
  6 /* AP3316C寄存器 */
  7 #define AP3216C_SYSTEMCONG      0x00    /* 配置寄存器       */
  8 #define AP3216C_INTSTATUS       0X01    /* 中断状态寄存器   */
  9 #define AP3216C_INTCLEAR        0X02    /* 中断清除寄存器   */
 10 #define AP3216C_IRDATALOW       0x0A    /* IR数据低字节     */
 11 #define AP3216C_IRDATAHIGH      0x0B    /* IR数据高字节     */
 12 #define AP3216C_ALSDATALOW      0x0C    /* ALS数据低字节    */
 13 #define AP3216C_ALSDATAHIGH     0X0D    /* ALS数据高字节    */
 14 #define AP3216C_PSDATALOW       0X0E    /* PS数据低字节     */
 15 #define AP3216C_PSDATAHIGH      0X0F    /* PS数据高字节     */
 16 
 17 #endif

ap3216c_iic.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/i2c.h>
 15 #include <asm/mach/map.h>
 16 #include <asm/uaccess.h>
 17 #include <asm/io.h>
 18 #include "ap3216c_reg.h"
 19 
 20 #define AP3216C_CNT     1
 21 #define AP3216C_NAME    "ap3216c"
 22 
 23 struct ap3216c_dev {
 24         dev_t devid;                    /* 设备号 */
 25         struct cdev cdev;               /* cdev  */
 26         struct class *class;    /* 类  */
 27         struct device *device;  /* 设备 */
 28         struct device_node      *nd; /* 设备节点 */
 29         int major;                      /* 主设备号 */
 30         void *private_data;     /* 私有数据 */
 31         unsigned short ir, als, ps;       /* 三个光传感器数据 */
 32 };
 33 
 34 static struct ap3216c_dev ap3216cdev;
 35 
 36 /*
 37  * @description : 从ap3216c读取多个寄存器数据
 38  * @param - dev:  ap3216c设备
 39  * @param - reg:  要读取的寄存器首地址
 40  * @param - val:  读取到的数据
 41  * @param - len:  要读取的数据长度
 42  * @return              : 操作结果
 43  */
 44 static int ap3216c_read_regs(struct ap3216c_dev *dev, u8 reg, void *val, int len)
 45 {
 46         int ret;
 47         struct i2c_msg msg[2];
 48         struct i2c_client *client = (struct i2c_client *)dev->private_data;
 49 
 50         /* msg[0]为发送要读取的首地址 */
 51         msg[0].addr = client->addr;                     /* ap3216c地址 */
 52         msg[0].flags = 0;                             /* 标记为发送数据 */
 53         msg[0].buf = &reg;                            /* 读取的首地址 */
 54         msg[0].len = 1;                               /* reg长度*/
 55 
 56         /* msg[1]读取数据 */
 57         msg[1].addr = client->addr;                     /* ap3216c地址 */
 58         msg[1].flags = I2C_M_RD;                      /* 标记为读取数据*/
 59         msg[1].buf = val;                             /* 读取数据缓冲区 */
 60         msg[1].len = len;                           /* 要读取的数据长度*/
 61 
 62         ret = i2c_transfer(client->adapter, msg, 2);
 63         if(ret == 2) {
 64                 ret = 0;
 65         } else {
 66                 printk("i2c rd failed=%d reg=%06x len=%d\n",ret, reg, len);
 67                 ret = -EREMOTEIO;
 68         }
 69         return ret;
 70 }
 71 
 72 /*
 73  * @description : 向ap3216c多个寄存器写入数据
 74  * @param - dev:  ap3216c设备
 75  * @param - reg:  要写入的寄存器首地址
 76  * @param - val:  要写入的数据缓冲区
 77  * @param - len:  要写入的数据长度
 78  * @return        :   操作结果
80 static s32 ap3216c_write_regs(struct ap3216c_dev *dev, u8 reg, u8 *buf, u8 len)
 81 {
 82         u8 b[256];
 83         struct i2c_msg msg;
 84         struct i2c_client *client = (struct i2c_client *)dev->private_data;
 85 
 86         b[0] = reg;                                     /* 寄存器首地址 */
 87         memcpy(&b[1],buf,len);        /* 将要写入的数据拷贝到数组b里面 */
 88 
 89         msg.addr = client->addr;        /* ap3216c地址 */
 90         msg.flags = 0;                          /* 标记为写数据 */
 91 
 92         msg.buf = b;                            /* 要写入的数据缓冲区 */
 93         msg.len = len + 1;                      /* 要写入的数据长度 */
 94 
 95         return i2c_transfer(client->adapter, &msg, 1);
 96 }
 97 
 98 /*
 99  * @description : 读取ap3216c指定寄存器值,读取一个寄存器
100  * @param - dev:  ap3216c设备
101  * @param - reg:  要读取的寄存器
102  * @return        :   读取到的寄存器值
103  */
104 static unsigned char ap3216c_read_reg(struct ap3216c_dev *dev, u8 reg)
105 {
106         u8 data = 0;
107 
108         ap3216c_read_regs(dev, reg, &data, 1);
109         return data;
110 
111 #if 0
112         struct i2c_client *client = (struct i2c_client *)dev->private_data;
113         return i2c_smbus_read_byte_data(client, reg);
114 #endif
115 }
116 
117 /*
118  * @description : 向ap3216c指定寄存器写入指定的值,写一个寄存器
119  * @param - dev:  ap3216c设备
120  * @param - reg:  要写的寄存器
121  * @param - data: 要写入的值
122  * @return   :    无
123  */
124 static void ap3216c_write_reg(struct ap3216c_dev *dev, u8 reg, u8 data)
125 {
126         u8 buf = 0;
127         buf = data;
128         ap3216c_write_regs(dev, reg, &buf, 1);
129 }
130 
131 /*
132  * @description : 读取AP3216C的数据,读取原始数据,包括ALS,PS和IR, 注意!
133  * : 如果同时打开ALS,IR+PS的话两次数据读取的时间间隔要大于112.5ms
134  * @param - ir  : ir数据
135  * @param - ps  : ps数据
136  * @param - ps  : als数据 
137  * @return              : 无。
138  */
139 void ap3216c_readdata(struct ap3216c_dev *dev)
140 {
141         unsigned char i =0;
142     unsigned char buf[6];
143 
144         /* 循环读取所有传感器数据 */
145     for(i = 0; i < 6; i++)
146     {
147         buf[i] = ap3216c_read_reg(dev, AP3216C_IRDATALOW + i);
148     }
149 
150     if(buf[0] & 0X80)   /* IR_OF位为1,则数据无效 */
151                 dev->ir = 0;
152         else                 /* 读取IR传感器的数据  */
153                 dev->ir = ((unsigned short)buf[1] << 2) | (buf[0] & 0X03);
154 
155        dev->als = ((unsigned short)buf[3] << 8) | buf[2];/* 读取ALS传感器的数据*/
156 
157     if(buf[4] & 0x40)   /* IR_OF位为1,则数据无效 */
158                 dev->ps = 0;
159         else                    /* 读取PS传感器的数据    */
160                 dev->ps = ((unsigned short)(buf[5] & 0X3F) << 4) | (buf[4] & 0X0F);
161 }
162 
163 /*
164  * @description         : 打开设备
165  * @param - inode       : 传递给驱动的inode
166  * @param - filp        : 设备文件,file结构体有个叫做private_data的成员变量
167  *                    一般在open的时候将private_data指向设备结构体。
168  * @return                      : 0 成功;其他 失败
169  */
170 static int ap3216c_open(struct inode *inode, struct file *filp)
171 {
172         filp->private_data = &ap3216cdev;
173 
174         /* 初始化AP3216C */
175       ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0x04);/* 复位
AP3216C */
176         mdelay(50);       /* AP3216C复位最少10ms  */
177     ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0X03); /* 开启ALS、
PS+IR  */
178         return 0;
179 }
180 
181 /*
182  * @description         : 从设备读取数据 
183  * @param - filp        : 要打开的设备文件(文件描述符)
184  * @param - buf         : 返回给用户空间的数据缓冲区
185  * @param - cnt         : 要读取的数据长度
186  * @param - offt        : 相对于文件首地址的偏移
187  * @return                      : 读取的字节数,如果为负值,表示读取失败
188  */
189 static ssize_t ap3216c_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
190 {
191         short data[3];
192         long err = 0;
193 
194         struct ap3216c_dev *dev = (struct ap3216c_dev *)filp->private_data;
195 
196         ap3216c_readdata(dev);
197 
198         data[0] = dev->ir;
199         data[1] = dev->als;
200         data[2] = dev->ps;
201         err = copy_to_user(buf, data, sizeof(data));
202         return 0;
203 }
204 
205 /*
206  * @description         : 关闭/释放设备
207  * @param - filp        : 要关闭的设备文件(文件描述符)
208  * @return                      : 0 成功;其他 失败
209  */
210 static int ap3216c_release(struct inode *inode, struct file *filp)
211 {
212         return 0;
213 }
214 
215 /* AP3216C操作函数 */
216 static const struct file_operations ap3216c_ops = {
217         .owner = THIS_MODULE,
218         .open = ap3216c_open,
219         .read = ap3216c_read,
220         .release = ap3216c_release,
221 };
222 
223  /*
224   * @description     : i2c驱动的probe函数,当驱动与
225   *                    设备匹配以后此函数就会执行
226   * @param - client  : i2c设备
227   * @param - id      : i2c设备ID
228   * @return          : 0,成功;其他负值,失败
229   */
230 static int ap3216c_probe(struct i2c_client *client, const struct i2c_device_id *id)
231 {
232         /* 1、构建设备号 */
233         if (ap3216cdev.major) {
234                 ap3216cdev.devid = MKDEV(ap3216cdev.major, 0);
235        register_chrdev_region(ap3216cdev.devid, AP3216C_CNT, AP3216C_NAME);
236         } else {
237        alloc_chrdev_region(&ap3216cdev.devid, 0, AP3216C_CNT, AP3216C_NAME);
238                 ap3216cdev.major = MAJOR(ap3216cdev.devid);
239         }
240 
241         /* 2、注册设备 */
242         cdev_init(&ap3216cdev.cdev, &ap3216c_ops);
243         cdev_add(&ap3216cdev.cdev, ap3216cdev.devid, AP3216C_CNT);
244 
245         /* 3、创建类 */
246         ap3216cdev.class = class_create(THIS_MODULE, AP3216C_NAME);
247         if (IS_ERR(ap3216cdev.class)) {
248                 return PTR_ERR(ap3216cdev.class);
249         }
250 
251         /* 4、创建设备 */
252         ap3216cdev.device = device_create(ap3216cdev.class, NULL, 
ap3216cdev.devid, NULL, AP3216C_NAME);
253         if (IS_ERR(ap3216cdev.device)) {
254                 return PTR_ERR(ap3216cdev.device);
255         }
256 
257         ap3216cdev.private_data = client;
258 
259         return 0;
260 }
261 
262 /*
263  * @description     : i2c驱动的remove函数,移除i2c驱动的时候此函数会执行
264  * @param - client      : i2c设备
265  * @return          : 0,成功;其他负值,失败
266  */
267 static int ap3216c_remove(struct i2c_client *client)
268 {
269         /* 删除设备 */
270         cdev_del(&ap3216cdev.cdev);
271         unregister_chrdev_region(ap3216cdev.devid, AP3216C_CNT);
272 
273         /* 注销掉类和设备 */
274         device_destroy(ap3216cdev.class, ap3216cdev.devid);
275         class_destroy(ap3216cdev.class);
276         return 0;
277 }
278 
279 /* 传统匹配方式ID列表 */
280 static const struct i2c_device_id ap3216c_id[] = {
281         {"ap3216c", 0},
282         {}
283 };
284 
285 /* 设备树匹配列表 */
286 static const struct of_device_id ap3216c_of_match[] = {
287         { .compatible = "ap3216c" },
288         { /* Sentinel */ }
289 };
290 
291 /* i2c驱动结构体 */
292 static struct i2c_driver ap3216c_driver = {
293         .probe = ap3216c_probe,
294         .remove = ap3216c_remove,
295         .driver = {
296                         .owner = THIS_MODULE,
297                         .name = "ap3216c",
298                         .of_match_table = ap3216c_of_match,
299                    },
300         .id_table = ap3216c_id,
301 };
302 
303 /*
304  * @description : 驱动入口函数
305  * @param               : 无
306  * @return              : 无
307  */
308 static int __init ap3216c_init(void)
309 {
310         int ret = 0;
311 
312         ret = i2c_add_driver(&ap3216c_driver);
313         return ret;
314 }
315 
316 /*
317  * @description : 驱动出口函数
318  * @param               : 无
319  * @return              : 无
320  */
321 static void __exit ap3216c_exit(void)
322 {
323         i2c_del_driver(&ap3216c_driver);
324 }
325 
326 /* module_i2c_driver(ap3216c_driver) */
327 
328 module_init(ap3216c_init);
329 module_exit(ap3216c_exit);
330 MODULE_LICENSE("GPL");
331 MODULE_AUTHOR("topeet");

第 23~32 行,ap3216c 设备结构体,第 30 行的 private_data 成员变量用于存放 ap3216c 对应的 i2c_client。第 31 行的 ir、als 和 ps 分别存储 AP3216C 的 IR、ALS 和 PS 数据。
第 34 行,定义一个 ap3216c_dev 类型的设备结构体变量 ap3216cdev。
第 44~70 行,ap3216c_read_regs 函数实现多字节读取,但是 AP3216C 好像不支持连续多字节读取,此函数在测试其他 I2C 设备的时候可以实现多给字节连续读取,但是在 AP3216C 上不能连续读取多个字节。不过读取一个字节没有问题的。
第 80~96 行,ap3216c_write_regs 函数实现连续多字节写操作。
第 104~115 行,ap3216c_read_reg 函数用于读取 AP3216C 的指定寄存器数据,用于一个寄存器的数据读取。
第 124~129 行,ap3216c_write_reg 函数用于向 AP3216C 的指定寄存器写入数据,用于一个寄存器的数据写操作。
第 139~161 行,读取 AP3216C 的 PS、ALS 和 IR 等传感器原始数据值。
第 170~221 行,标准的字符设备驱动框架。
第 230~260 行,ap3216c_probe 函数,当 I2C 设备和驱动匹配成功以后此函数就会执行,和platform 驱动框架一样。此函数前面都是标准的字符设备注册代码,最后面会将此函数的第一个参数 client 传递给 ap3216cdev 的 private_data 成员变量。
第 280~283 行,ap3216c_id 匹配表,i2c_device_id 类型。用于传统的设备和驱动匹配,也就是没有使用设备树的时候。
第 286~289 行,ap3216c_of_match 匹配表,of_device_id 类型,用于设备树设备和驱动匹配。这里只写了一个 compatible 属性,值为“ap3216c”。
第 292~301 行,ap3216c_driver 结构体变量,i2c_driver 类型。
第 308~314 行,驱动入口函数 ap3216c_init,此函数通过调用 i2c_add_driver 来向 Linux 内核注册 i2c_driver,也就是 ap3216c_driver。
第 321~324 行,驱动出口函数 ap3216c_exit,此函数通过调用 i2c_del_driver 来注销掉前面注册的 ap3216c_driver。

4 应用测试程序

创建ap3216c_test.c文件,具体内容如下:

 1 #include "stdio.h"
  2 #include "unistd.h"
  3 #include "sys/types.h"
  4 #include "sys/stat.h"
  5 #include "sys/ioctl.h"
  6 #include "fcntl.h"
  7 #include "stdlib.h"
  8 #include "string.h"
  9 #include <poll.h>
 10 #include <sys/select.h>
 11 #include <sys/time.h>
 12 #include <signal.h>
 13 #include <fcntl.h>
 14 
 15 /*
 16  * @description         : main主程序
 17  * @param - argc        : argv数组元素个数
 18  * @param - argv        : 具体参数
 19  * @return                      : 0 成功;其他 失败
 20  */
 21 int main(int argc, char *argv[])
 22 {
 23         int fd;
 24         char *filename;
 25         unsigned short databuf[3];
 26         unsigned short ir, als, ps;
 27         int ret = 0;
 28 
 29         if (argc != 2) {
 30                 printf("Error Usage!\r\n");
 31                 return -1;
 32         }
 33 
 34         filename = argv[1];
 35         fd = open(filename, O_RDWR);
 36         if(fd < 0) {
 37                 printf("can't open file %s\r\n", filename);
 38                 return -1;
 39         }
 40 
 41         while (1) {
 42                 ret = read(fd, databuf, sizeof(databuf));
 43                 if(ret == 0) {                  /* 数据读取成功 */
 44                         ir =  databuf[0];       /* ir传感器数据 */
 45                         als = databuf[1];       /* als传感器数据 */
 46                         ps =  databuf[2];       /* ps传感器数据 */
 47                         printf("ir = %d, als = %d, ps = %d\r\n", ir, als, ps);
 48                 }
 49                 usleep(200000); /*100ms */
 50         }
 51         close(fd);      /* 关闭文件 */
 52         return 0;
 53 }

应用测试程序就是在 while 循环中不断的读取 AP3216C 的设备文件,从而得到 ir、als 和 ps 这三个数据值,然后将其输出到终端上。

i.MX6ULL终结者Linux I2C驱动实验实验程序编写_第2张图片

你可能感兴趣的:(i.MX6ULL终结者,#,第四部分,Linux驱动开发,linux,嵌入式,开发)