1.I2C驱动框架
Linux内核基于驱动分离和分层的思想,将I2C驱动分为
总线驱动:也就是I2C控制器驱动,也叫做适配器驱动(就是用来控制I2C设备的)
设备驱动:针对具体的I2C设备而编写的驱动
1.总线驱动
platform在前面讲过,是一条虚拟出来的总线,目的是为了实现总线,设备,驱动框架。对于I2C而言,不需要虚拟总线,直接用I2C总线即可。I2C总线驱动重点就是I2C适配器(即SOC的I2C接口控制器)驱动,这里有两个重要的数据结构:i2c_adapter 和 i2c_algorithm。Linux内核将I2C适配器抽象成了i2c_adapter结构体,这个结构体中就包括i2c_algorithm(用来I2C适配器和I2C设备之间的通信,具体使用master_xfer,就是I2C适配器的传输函数)这个结构体。
所以总线驱动或者说I2C适配器驱动的主要工作就是初始化i2c_adapter结构体变量,然后设置i2c_algorithm中的master_xfer函数。完成以后像系统注册设置好的i2c_adapter。
2.设备驱动
I2C设备驱动重点关注两个数据结构:i2c_client和i2c_driver,前者是描述设备信息,后者描述驱动内容。
i2c_client:一个设备对应一个i2c_client,每检测到一个I2C设备就会给这个I2C设备分配一个i2c_client。
i2c_driver:类似platform驱动一样,有probe函数,匹配成功后执行,同样还有设备树和非设备树的匹配列表,前者是of_match_table,后者是id_table。
对于I2C设备驱动,重点工作是构建i2c_driver,构建完成后向内核注册i2c_driver.
2.适配器驱动分析
dtsi和dts的关系,就像.h和.c的关系一样,适配器驱动就放在dtsi里面,可以自行查看,这个芯片厂商一般会给写好,重点看设备驱动
3.I2C设备驱动编写
关于设备树,我们首先要找到i2c的节点(在这个板子里面是i2c1,里面添加我们所需要的设备节点)
1.I2C设备数据收发流程
一般我们需要在probe函数里面初始化I2C设备,就是必须能够对I2C设备寄存器进行读写操作。
这里就要是i2c_transfer函数了,它会调用i2c_algorithm里面的master_xfer函数
i2c_transfer(适配器,消息,消息数量) //消息也是个结构体,也需要初始化。
使用i2c_transfer进行I2C数据收发的代码:
读取I2C设备多个寄存器数据:(这里我们先按传递一个处理)
1.定义消息变量 msg[2]
2.定义i2c_client(数据可从参数项获取)
3.读数据(先确定读地址,再读)
static int xxx_read_regs(struct xxx_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; /* I2C 器件地址 */
msg[0].flags = 0; /* 标记为发送数据 */
msg[0].buf = ® /* 读取的寄存器首地址 */
msg[0].len = 1; /* 寄存器地址长度 */
/* msg[1],第二条读消息,读取寄存器数据 */
msg[1].addr = client->addr; /* I2C 器件地址 */
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 {
ret = -EREMOTEIO;
}
return ret;
}
4.写数据
static s32 xxx_write_regs(struct xxx_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; /* I2C 器件地址 */
msg.flags = 0; /* 标记为写数据 */
msg.buf = b; /* 要发送的数据缓冲区 */
msg.len = len + 1; /* 要发送的数据长度 */
return i2c_transfer(client->adapter, &msg, 1);
}
2.具体驱动程序编写
设备树里包括:IO添加或者修改(这里恩智浦已经设置好了,不用管)/在i2c节点里面追加ap3216c子节点,因为AP3216C用到了I2C1接口
ap3216c@1e {
compatible = “alientek,ap3216c”;
reg = <0x1e>;
ap3216c子节点后面的@是ap3216c的器件地址
reg属性也是设置ap3216c器件地址的,因此reg设置为0x1e
1.头文件,里面包含AP3216C寄存器地址描述(配置寄存器,中断状态/清除寄存器,IR,ALS, PS)
2.具体流程
1.ap3216c_dev设备结构体(里面的private_data专门用来存放i2c_client)
2.读取多个寄存器数据,就在上面
3.向ap3216c多个寄存器写入数据
4.读取一个寄存器值(直接套用2)
5.写一个寄存器的值(直接套用3)
6.读取AP3216C数据,读取原始数据,包括ALS,PS,IR同时打开ALS,IR+PS的话,两次数据读取间隔要大于112.5s
(同时里面可以靠状态位来判断当前读取的数据是否有效)
7.open(主要是初始化AP3216C)
8.read(利用6读取数据,然后放到data[3]这个数组中,传回用户空间)
9.AP3216C操作函数
10.probe函数(常规流程+把i2c_client性质的变量赋值给private_data)
11.remove
12.传统匹配方式ID列表和设备树匹配列表
13.I2C驱动结构体(i2c_driver)
14.驱动出口入口函数
引入设备树后,整个设备驱动编写流程已经很清晰了,
1.创建xx设备的结构体,创建对应.open .read .write等等东西,然后构成操作函数集
2.创建probe和remove函数,创建匹配列表,然后构成驱动结构体
3.驱动入口函数里面放注册设备函数,驱动出口函数放注销设备函数