imx6ull-驱动开发篇44——Linux I2C 驱动实验
目录
实验程序编写
修改设备树
IO 设置
追加 ap3216c 子节点
AP3216C 驱动编写
ap3216creg.h
ap3216c.c
ap3216cApp.c
Makefile 文件
运行测试
在之前的文章里:Linux I2C 驱动框架简介、I.MX6U 的 I2C 驱动分析,我们已经大概了解怎么编写IIC驱动,本讲内容,以正点原子 I.MX6U-ALPHA 开发板上的 AP3216C 三合一环境光传感器为例,编写 Linux 下的 I2C 设备驱动程序。
实验程序编写
硬件原理图可参考之前文章:裸机学习实验16——I2C 实验。
修改设备树
IO 设置
I.MX6U-ALPHA 开发板上的AP3216C 用到了 I2C1 接口, I2C1 接口使用到了 UART4_TXD 和 UART4_RXD,需要在设备树里设置这两个IO。
打开设备树文件 imx6ull-alientek-emmc.dts, 找到如下内容:
pinctrl_i2c1 就是 I2C1 的 IO 节点,这里将 UART4_TXD 和 UART4_RXD 这两个 IO 分别复用为 I2C1_SCL 和 I2C1_SDA,电气属性都设置为 0x4001b8b0。
追加 ap3216c 子节点
AP3216C 是连接到 I2C1 上的,因此需要在 i2c1 节点下添加 ap3216c 的设备子节点。
在imx6ull-alientek-emmc.dts 文件中,找到 i2c1 节点,原内容如下:
NXP 官方 EVK 开发板有mag3110 磁力计子节点、fxls8471子节点,正点原子开发板没有这两个器件,所以将 mag3110 和 fxls8471 这两个 I2C 子节点删除。
然后添加 ap3216c子节点信息,完成以后的 i2c1 节点内容如下所示:
&i2c1 {clock-frequency = <100000>;pinctrl-names = "default";pinctrl-0 = <&pinctrl_i2c1>;status = "okay";ap3216c@1e {compatible = "alientek,ap3216c";reg = <0x1e>;};};
- ap3216c 子节点, @后面的“1e”是 ap3216c 的器件地址。
- 设置 compatible 值为“alientek,ap3216c”。
- reg 属性也是设置 ap3216c 器件地址的,因此 reg 设置为 0x1e。
设备树修改完成以后使用“make dtbs”重新编译一下,然后使用新的设备树启动 Linux 内核。
在/sys/bus/i2c/devices 目录下存放着所有 I2C 设备,如果设备树修改正确的话,在该目录下能看到一个名为“0-001e”的子目录,如图:
0-001e”就是 ap3216c 的设备目录,“1e”就是 ap3216c 器件地址。
进入0-001e 目录,可以看到“name”文件:
AP3216C 驱动编写
新建 ap3216c.c 和 ap3216creg.h 这两个文件, ap3216c.c 为 AP3216C 的驱动代码, ap3216creg.h 是 AP3216C 寄存器头文件。
ap3216creg.h
在 ap3216creg.h 中定义好 AP3216C 的寄存器,输入如下内容,
#ifndef AP3216C_H
#define AP3216C_H#define AP3216C_ADDR 0X1E /* AP3216C器件地址 *//* AP3316C寄存器 */
#define AP3216C_SYSTEMCONG 0x00 /* 配置寄存器 */
#define AP3216C_INTSTATUS 0X01 /* 中断状态寄存器 */
#define AP3216C_INTCLEAR 0X02 /* 中断清除寄存器 */
#define AP3216C_IRDATALOW 0x0A /* IR数据低字节 */
#define AP3216C_IRDATAHIGH 0x0B /* IR数据高字节 */
#define AP3216C_ALSDATALOW 0x0C /* ALS数据低字节 */
#define AP3216C_ALSDATAHIGH 0X0D /* ALS数据高字节 */
#define AP3216C_PSDATALOW 0X0E /* PS数据低字节 */
#define AP3216C_PSDATAHIGH 0X0F /* PS数据高字节 */#endif
ap3216c.c
在 ap3216c.c 编写具体驱动函数,内容如下:
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/i2c.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include "ap3216creg.h"#define AP3216C_CNT 1
#define AP3216C_NAME "ap3216c"struct ap3216c_dev {dev_t devid; /* 设备号 */struct cdev cdev; /* cdev */struct class *class; /* 类 */struct device *device; /* 设备 */struct device_node *nd; /* 设备节点 */int major; /* 主设备号 */void *private_data; /* 私有数据 */unsigned short ir, als, ps; /* 三个光传感器数据 */
};static struct ap3216c_dev ap3216cdev;/** @description : 从ap3216c读取多个寄存器数据* @param - dev: ap3216c设备* @param - reg: 要读取的寄存器首地址* @param - val: 读取到的数据* @param - len: 要读取的数据长度* @return : 操作结果*/
static int ap3216c_read_regs(struct ap3216c_dev *dev, u8 reg, void *val, int len)
{int ret;struct i2c_msg msg[2];struct i2c_client *client = (struct i2c_client *)dev->private_data;/* msg[0]为发送要读取的首地址 */msg[0].addr = client->addr; /* ap3216c地址 */msg[0].flags = 0; /* 标记为发送数据 */msg[0].buf = ® /* 读取的首地址 */msg[0].len = 1; /* reg长度*//* msg[1]读取数据 */msg[1].addr = client->addr; /* ap3216c地址 */msg[1].flags = I2C_M_RD; /* 标记为读取数据*/msg[1].buf = val; /* 读取数据缓冲区 */msg[1].len = len; /* 要读取的数据长度*/ret = i2c_transfer(client->adapter, msg, 2);if(ret == 2) {ret = 0;} else {printk("i2c rd failed=%d reg=%06x len=%d\n",ret, reg, len);ret = -EREMOTEIO;}return ret;
}/** @description : 向ap3216c多个寄存器写入数据* @param - dev: ap3216c设备* @param - reg: 要写入的寄存器首地址* @param - val: 要写入的数据缓冲区* @param - len: 要写入的数据长度* @return : 操作结果*/
static s32 ap3216c_write_regs(struct ap3216c_dev *dev, u8 reg, u8 *buf, u8 len)
{u8 b[256];struct i2c_msg msg;struct i2c_client *client = (struct i2c_client *)dev->private_data;b[0] = reg; /* 寄存器首地址 */memcpy(&b[1],buf,len); /* 将要写入的数据拷贝到数组b里面 */msg.addr = client->addr; /* ap3216c地址 */msg.flags = 0; /* 标记为写数据 */msg.buf = b; /* 要写入的数据缓冲区 */msg.len = len + 1; /* 要写入的数据长度 */return i2c_transfer(client->adapter, &msg, 1);
}/** @description : 读取ap3216c指定寄存器值,读取一个寄存器* @param - dev: ap3216c设备* @param - reg: 要读取的寄存器* @return : 读取到的寄存器值*/
static unsigned char ap3216c_read_reg(struct ap3216c_dev *dev, u8 reg)
{u8 data = 0;ap3216c_read_regs(dev, reg, &data, 1);return data;#if 0struct i2c_client *client = (struct i2c_client *)dev->private_data;return i2c_smbus_read_byte_data(client, reg);
#endif
}/** @description : 向ap3216c指定寄存器写入指定的值,写一个寄存器* @param - dev: ap3216c设备* @param - reg: 要写的寄存器* @param - data: 要写入的值* @return : 无*/
static void ap3216c_write_reg(struct ap3216c_dev *dev, u8 reg, u8 data)
{u8 buf = 0;buf = data;ap3216c_write_regs(dev, reg, &buf, 1);
}/** @description : 读取AP3216C的数据,读取原始数据,包括ALS,PS和IR, 注意!* : 如果同时打开ALS,IR+PS的话两次数据读取的时间间隔要大于112.5ms* @param - ir : ir数据* @param - ps : ps数据* @param - ps : als数据 * @return : 无。*/
void ap3216c_readdata(struct ap3216c_dev *dev)
{unsigned char i =0;unsigned char buf[6];/* 循环读取所有传感器数据 */for(i = 0; i < 6; i++) {buf[i] = ap3216c_read_reg(dev, AP3216C_IRDATALOW + i); }if(buf[0] & 0X80) /* IR_OF位为1,则数据无效 */dev->ir = 0; else /* 读取IR传感器的数据 */dev->ir = ((unsigned short)buf[1] << 2) | (buf[0] & 0X03); dev->als = ((unsigned short)buf[3] << 8) | buf[2]; /* 读取ALS传感器的数据 */ if(buf[4] & 0x40) /* IR_OF位为1,则数据无效 */dev->ps = 0; else /* 读取PS传感器的数据 */dev->ps = ((unsigned short)(buf[5] & 0X3F) << 4) | (buf[4] & 0X0F);
}/** @description : 打开设备* @param - inode : 传递给驱动的inode* @param - filp : 设备文件,file结构体有个叫做private_data的成员变量* 一般在open的时候将private_data指向设备结构体。* @return : 0 成功;其他 失败*/
static int ap3216c_open(struct inode *inode, struct file *filp)
{filp->private_data = &ap3216cdev;/* 初始化AP3216C */ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0x04); /* 复位AP3216C */mdelay(50); /* AP3216C复位最少10ms */ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0X03); /* 开启ALS、PS+IR */return 0;
}/** @description : 从设备读取数据 * @param - filp : 要打开的设备文件(文件描述符)* @param - buf : 返回给用户空间的数据缓冲区* @param - cnt : 要读取的数据长度* @param - offt : 相对于文件首地址的偏移* @return : 读取的字节数,如果为负值,表示读取失败*/
static ssize_t ap3216c_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{short data[3];long err = 0;struct ap3216c_dev *dev = (struct ap3216c_dev *)filp->private_data;ap3216c_readdata(dev);data[0] = dev->ir;data[1] = dev->als;data[2] = dev->ps;err = copy_to_user(buf, data, sizeof(data));return 0;
}/** @description : 关闭/释放设备* @param - filp : 要关闭的设备文件(文件描述符)* @return : 0 成功;其他 失败*/
static int ap3216c_release(struct inode *inode, struct file *filp)
{return 0;
}/* AP3216C操作函数 */
static const struct file_operations ap3216c_ops = {.owner = THIS_MODULE,.open = ap3216c_open,.read = ap3216c_read,.release = ap3216c_release,
};/** @description : i2c驱动的probe函数,当驱动与* 设备匹配以后此函数就会执行* @param - client : i2c设备* @param - id : i2c设备ID* @return : 0,成功;其他负值,失败*/
static int ap3216c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{/* 1、构建设备号 */if (ap3216cdev.major) {ap3216cdev.devid = MKDEV(ap3216cdev.major, 0);register_chrdev_region(ap3216cdev.devid, AP3216C_CNT, AP3216C_NAME);} else {alloc_chrdev_region(&ap3216cdev.devid, 0, AP3216C_CNT, AP3216C_NAME);ap3216cdev.major = MAJOR(ap3216cdev.devid);}/* 2、注册设备 */cdev_init(&ap3216cdev.cdev, &ap3216c_ops);cdev_add(&ap3216cdev.cdev, ap3216cdev.devid, AP3216C_CNT);/* 3、创建类 */ap3216cdev.class = class_create(THIS_MODULE, AP3216C_NAME);if (IS_ERR(ap3216cdev.class)) {return PTR_ERR(ap3216cdev.class);}/* 4、创建设备 */ap3216cdev.device = device_create(ap3216cdev.class, NULL, ap3216cdev.devid, NULL, AP3216C_NAME);if (IS_ERR(ap3216cdev.device)) {return PTR_ERR(ap3216cdev.device);}ap3216cdev.private_data = client;return 0;
}/** @description : i2c驱动的remove函数,移除i2c驱动的时候此函数会执行* @param - client : i2c设备* @return : 0,成功;其他负值,失败*/
static int ap3216c_remove(struct i2c_client *client)
{/* 删除设备 */cdev_del(&ap3216cdev.cdev);unregister_chrdev_region(ap3216cdev.devid, AP3216C_CNT);/* 注销掉类和设备 */device_destroy(ap3216cdev.class, ap3216cdev.devid);class_destroy(ap3216cdev.class);return 0;
}/* 传统匹配方式ID列表 */
static const struct i2c_device_id ap3216c_id[] = {{"alientek,ap3216c", 0}, {}
};/* 设备树匹配列表 */
static const struct of_device_id ap3216c_of_match[] = {{ .compatible = "alientek,ap3216c" },{ /* Sentinel */ }
};/* i2c驱动结构体 */
static struct i2c_driver ap3216c_driver = {.probe = ap3216c_probe,.remove = ap3216c_remove,.driver = {.owner = THIS_MODULE,.name = "ap3216c",.of_match_table = ap3216c_of_match, },.id_table = ap3216c_id,
};/** @description : 驱动入口函数* @param : 无* @return : 无*/
static int __init ap3216c_init(void)
{int ret = 0;ret = i2c_add_driver(&ap3216c_driver);return ret;
}/** @description : 驱动出口函数* @param : 无* @return : 无*/
static void __exit ap3216c_exit(void)
{i2c_del_driver(&ap3216c_driver);
}/* module_i2c_driver(ap3216c_driver) */module_init(ap3216c_init);
module_exit(ap3216c_exit);
MODULE_LICENSE("GPL");
关键代码分析如下:
ap3216c 设备结构体ap3216c_dev,其中:
- private_data 成员变量用于存放 ap3216c 对应的 i2c_client。
- ir、 als 和 ps 分别存储 AP3216C 的 IR、 ALS 和 PS 数据。
struct ap3216c_dev {dev_t devid; /* 设备号 */struct cdev cdev; /* cdev */struct class *class; /* 类 */struct device *device; /* 设备 */struct device_node *nd; /* 设备节点 */int major; /* 主设备号 */void *private_data; /* 私有数据 */unsigned short ir, als, ps; /* 三个光传感器数据 */
};
ap3216c_read_regs 函数,实现多字节读取,但是 AP3216C 好像不支持连续多字节读取。
static int ap3216c_read_regs(struct ap3216c_dev *dev, u8 reg, void *val, int len)
{int ret;struct i2c_msg msg[2];struct i2c_client *client = (struct i2c_client *)dev->private_data;/* msg[0]为发送要读取的首地址 */msg[0].addr = client->addr; /* ap3216c地址 */msg[0].flags = 0; /* 标记为发送数据 */msg[0].buf = ® /* 读取的首地址 */msg[0].len = 1; /* reg长度*//* msg[1]读取数据 */msg[1].addr = client->addr; /* ap3216c地址 */msg[1].flags = I2C_M_RD; /* 标记为读取数据*/msg[1].buf = val; /* 读取数据缓冲区 */msg[1].len = len; /* 要读取的数据长度*/ret = i2c_transfer(client->adapter, msg, 2);if(ret == 2) {ret = 0;} else {printk("i2c rd failed=%d reg=%06x len=%d\n",ret, reg, len);ret = -EREMOTEIO;}return ret;
}
ap3216c_write_regs 函数,实现连续多字节写操作。
static s32 ap3216c_write_regs(struct ap3216c_dev *dev, u8 reg, u8 *buf, u8 len)
{u8 b[256];struct i2c_msg msg;struct i2c_client *client = (struct i2c_client *)dev->private_data;b[0] = reg; /* 寄存器首地址 */memcpy(&b[1],buf,len); /* 将要写入的数据拷贝到数组b里面 */msg.addr = client->addr; /* ap3216c地址 */msg.flags = 0; /* 标记为写数据 */msg.buf = b; /* 要写入的数据缓冲区 */msg.len = len + 1; /* 要写入的数据长度 */return i2c_transfer(client->adapter, &msg, 1);
}
ap3216c_read_reg 函数,用于读取 AP3216C 的指定寄存器数据,用于一个寄存器的数据读取。
static unsigned char ap3216c_read_reg(struct ap3216c_dev *dev, u8 reg)
{u8 data = 0;ap3216c_read_regs(dev, reg, &data, 1);return data;#if 0struct i2c_client *client = (struct i2c_client *)dev->private_data;return i2c_smbus_read_byte_data(client, reg);
#endif
}
ap3216c_write_reg 函数,用于向 AP3216C 的指定寄存器写入数据,用于一个寄存器的数据写操作。
static void ap3216c_write_reg(struct ap3216c_dev *dev, u8 reg, u8 data)
{u8 buf = 0;buf = data;ap3216c_write_regs(dev, reg, &buf, 1);
}
ap3216c_readdata函数,读取 AP3216C 的 PS、 ALS 和 IR 等传感器原始数据值。
void ap3216c_readdata(struct ap3216c_dev *dev)
{unsigned char i =0;unsigned char buf[6];/* 循环读取所有传感器数据 */for(i = 0; i < 6; i++) {buf[i] = ap3216c_read_reg(dev, AP3216C_IRDATALOW + i); }if(buf[0] & 0X80) /* IR_OF位为1,则数据无效 */dev->ir = 0; else /* 读取IR传感器的数据 */dev->ir = ((unsigned short)buf[1] << 2) | (buf[0] & 0X03); dev->als = ((unsigned short)buf[3] << 8) | buf[2]; /* 读取ALS传感器的数据 */ if(buf[4] & 0x40) /* IR_OF位为1,则数据无效 */dev->ps = 0; else /* 读取PS传感器的数据 */dev->ps = ((unsigned short)(buf[5] & 0X3F) << 4) | (buf[4] & 0X0F);
}
ap3216c_probe 函数,当 I2C 设备和驱动匹配成功以后此函数就会执行,最后面会将此函数的第一个参数 client 传递给 ap3216cdev 的 private_data 成员变量。
static int ap3216c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{/* 1、构建设备号 */if (ap3216cdev.major) {ap3216cdev.devid = MKDEV(ap3216cdev.major, 0);register_chrdev_region(ap3216cdev.devid, AP3216C_CNT, AP3216C_NAME);} else {alloc_chrdev_region(&ap3216cdev.devid, 0, AP3216C_CNT, AP3216C_NAME);ap3216cdev.major = MAJOR(ap3216cdev.devid);}/* 2、注册设备 */cdev_init(&ap3216cdev.cdev, &ap3216c_ops);cdev_add(&ap3216cdev.cdev, ap3216cdev.devid, AP3216C_CNT);/* 3、创建类 */ap3216cdev.class = class_create(THIS_MODULE, AP3216C_NAME);if (IS_ERR(ap3216cdev.class)) {return PTR_ERR(ap3216cdev.class);}/* 4、创建设备 */ap3216cdev.device = device_create(ap3216cdev.class, NULL, ap3216cdev.devid, NULL, AP3216C_NAME);if (IS_ERR(ap3216cdev.device)) {return PTR_ERR(ap3216cdev.device);}ap3216cdev.private_data = client;return 0;
}
ap3216c_id 匹配表, i2c_device_id 类型。用于传统的设备和驱动匹配,也就是没有使用设备树的时候。 ap3216c_of_match 匹配表的compatible 属性,值为“alientek,ap3216c”。
static const struct i2c_device_id ap3216c_id[] = {{"alientek,ap3216c", 0}, {}
};/* 设备树匹配列表 */
static const struct of_device_id ap3216c_of_match[] = {{ .compatible = "alientek,ap3216c" },{ /* Sentinel */ }
};
ap3216c_driver 结构体变量, i2c_driver 类型。
static struct i2c_driver ap3216c_driver = {.probe = ap3216c_probe,.remove = ap3216c_remove,.driver = {.owner = THIS_MODULE,.name = "ap3216c",.of_match_table = ap3216c_of_match, },.id_table = ap3216c_id,
};
驱动入口函数 ap3216c_init,调用 i2c_add_driver 来向 Linux 内核注册 i2c_driver,也就是 ap3216c_driver。
static int __init ap3216c_init(void)
{int ret = 0;ret = i2c_add_driver(&ap3216c_driver);return ret;
}
驱动出口函数 ap3216c_exit,调用 i2c_del_driver 来注销掉前面注册的 ap3216c_driver。
static void __exit ap3216c_exit(void)
{i2c_del_driver(&ap3216c_driver);
}
ap3216cApp.c
新建测试app, ap3216cApp.c 文件,代码如下:
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "sys/ioctl.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include <poll.h>
#include <sys/select.h>
#include <sys/time.h>
#include <signal.h>
#include <fcntl.h>
/** @description : main主程序* @param - argc : argv数组元素个数* @param - argv : 具体参数* @return : 0 成功;其他 失败*/
int main(int argc, char *argv[])
{int fd;char *filename;unsigned short databuf[3];unsigned short ir, als, ps;int ret = 0;if (argc != 2) {printf("Error Usage!\r\n");return -1;}filename = argv[1];fd = open(filename, O_RDWR);if(fd < 0) {printf("can't open file %s\r\n", filename);return -1;}while (1) {ret = read(fd, databuf, sizeof(databuf));if(ret == 0) { /* 数据读取成功 */ir = databuf[0]; /* ir传感器数据 */als = databuf[1]; /* als传感器数据 */ps = databuf[2]; /* ps传感器数据 */printf("ir = %d, als = %d, ps = %d\r\n", ir, als, ps);}usleep(200000); /*100ms */}close(fd); /* 关闭文件 */ return 0;
}
在 while 循环中,不断读取 AP3216C 的设备文件,从而得到 ir、 als 和 ps 这三个数据值,然后将其输出到终端上。
Makefile 文件
makefile文件只需要修改 obj-m 变量的值,改为ap3216c.o。
代码如下:
KERNELDIR := /home/huax/linux/linux_test/linux-imx-rel_imx_4.1.15_2.1.0_gaCURRENT_PATH := $(shell pwd)
obj-m := ap3216c.obuild: kernel_modules
kernel_modules:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
运行测试
编译代码:
make -j32 //编译makefile文件
arm-linux-gnueabihf-gcc ap3216cApp.c -o ap3216cApp //编译测试app
编译成功以后,就会生成一个名为“ap3216c.ko”的驱动模块文件,和ap3216cApp 这个应用程序。
将编译出来 ap3216c.ko 和 ap3216cApp 这两个文件拷贝到 rootfs/lib/modules/4.1.15目录中,重启开发板。
进入到目录 lib/modules/4.1.15 中,输入如下命令加载 ap3216c.ko 这个驱动模块:
depmod //第一次加载驱动的时候需要运行此命令
modprobe ap3216c.ko //加载驱动模块
当驱动模块加载成功以后,使用 ap3216cApp 来测试,输入如下命令:
./ap3216cApp /dev/ap3216c
测试 APP 会不断从 AP3216C 中读取数据,然后输出到终端上:
AP3216C 是一款集成环境光传感器(ALS)、接近传感器(PS)和红外线(IR)LED的三合一传感器芯片。
可以通过下面的方法改变als、ps、ir的值:
ALS:可见光强度
-
改变环境光照(开/关灯、拉窗帘、用手遮挡传感器)
-
用白光光源直接照射传感器(如手机手电筒)
PS:物体接近距离
- 将物体靠近/远离传感器(手指、手掌、书本等)
IR:测试红外线强度
-
用红外光源照射传感器(如红外遥控器对准传感器发射)
-
用手靠近传感器(人体会发射红外线)
-
环境温度变化(影响红外辐射强度)
然后观察串口打印,是否随着环境变化而改变读取到的值。