Linux嵌入式 驱动开发(I2C)

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.驱动入口函数里面放注册设备函数,驱动出口函数放注销设备函数

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值