Linux I2C核心、总线与设备驱动
1.1 IIC结构
I2C核心提供了I2C总线驱动和设备驱动的注册、注销方法,I2C通信方法(即Algorithm)上层的与具体适配器无关的代码以及探测设备、检测设备地址的上层代码等,如图所示
IIC(Inter Integrated Circuit,集成电路总线)是一种由 PHILIPS 公司开发的两线式串行总线,用于连接微控制器及其外围设备。它是由数据线 SDA 和时钟 SCL 构成的串行总线,可发送和接收数据。在 CPU (单片机)与IIC模块之间、IIC模块与IIC模块之间进行双向传送。
IIC的主要构成只有两个双向的信号线,一个是数据线SDA,一个是时钟线SCL。
IIC的一些特点:
- IIC是半双工,而不是全双工
- IIC是真正的多主机总线,(对比SPI在每次通信前都需要把主机定死,而IIC可以在通讯过程中,改变主机),如果两个或更多的主机同时请求总线,可以通过冲突检测和仲裁防止总线数据被破坏
- 起始和终止信号都是由主机发出的,连接到I2C总线上的器件,若具有I2C总线的硬件接口,则很容易检测到起始和终止信号
- 在起始信号后必须发送一个7位从机地址+1位方向位,用“0”表示主机发送数据,“1”表示主机接收数据。
- 每当主机向从机发送完一个字节的数据,主机总是需要等待从机给出一个应答信号,以确认从机是否成功接收到了数据
- 起始信号是必需的,结束信号和应答信号,都可以不要
注:实际使用中,一般是单片机作为主机,其它器件作为从机,单片机先向器件发送信息表示要读取数据,之后转变传输方向,器件发送数据到单片机。
1.2 IIC物理连接
使用IIC通信的IIC器件有很多,比如陀螺仪加速度计MPU6050,EEPROM存储芯片AT24C02等,通过IIC总线,可以与单片机之间进行数据传输。
- IIC通信线只有只有两根,数据线SDA的高低电平传输2进制的数据,时钟线SCL通过方波信号提供时钟节拍
- 多个IIC器件可以并联在IIC总线上,每个器件有特定的地址,分时共享IIC总线
- 实际使用IIC当然还要连接电源以及共地
1.3 IIC时序
网上查找IIC的基础知识,可能会搜到这样的时序图:
1.3.1 IIC起始结束信号
这张图看起来更简单一些,描述了IIC的起始和停止条件:
- 起始:时钟线SCL为高时,数据线SDA由高到低
- 停止:时钟线SCL为高时,数据线SDA由低到高
注:SDA和SCL同时为高时,为IIC总线的空闲状态
1.3.2 IIC应答
再来看下面这张图:
这表示IIC的应答机制
- 下面的波形:SCL,主机产生的时钟脉冲
- 上面的波形:SDA,主机发送的8位数据
- 中间的波形:SDA,从机在第9个时钟信号进行拉低回应,表示收到了主机发来的数据,拉高则表示不应答
注:实际上,上面和中间是同样的SDA线,这里只是分开示意。因为IIC应答是一种相互关系,单片机发数据给IIC器件,IIC器件要进行应答,表示收到了数据,同样,单片机接收IIC器件的数据后,也要给IIC器件一个应答。
既然发送完都需要对方回应,那什么时候使用不应答呢?就是在读取到本次数据后,如果不需要继续读取,则发送非应答,对方以为你没收到这次数据,则就不会继续发送了。(这里是拉低应答吗?)
1.3.3 IIC完整传输时序
- 开始标志(S)发出后,主设备会传送一个7 位的Slave 地址,并且后面跟着一个第8位,称为Read/Write 位。
- R/W 位表示主设备是在接受从设备的数据还是在向其写数据。
- 然后,主设备释放SDA 线,等待从设备的应答信号(ACK)。每个字节的传输都要跟随有一个应答位。
- 应答产生时,从设备将SDA 线拉低并且在SCL 为高电平时保持低。
- 数据传输以停止标志(P)结束,然后释放总线。但主设备也可以产生重复的开始信号去操作另一台从设备,而不发出结束标志。
- 所有的SDA 信号变化都要在SCL 时钟为低电平时进行,除了开始和结束标志
SCL | SDA(主) | SDA(从) | |
0 | 0(在SCL为低时候变化信号) | (在SCL为低时候变化信号,响应ACK) | |
0 | 1(在SCL为低时候变化信号) | ||
1 | 0(在SCL=1时候拉低,开始) | ||
1 | 1(在SCL=1时候拉高,停止;或者空闲) |
1.4 常用的数据收发方式(时序)
上面1.3小节是IIC的基础时序,在实际使用中,一般是对某个IIC器件的某个寄存器进行读写操作,因此,对于寄存器的读写操作,还要遵循下面的组合时序逻辑。
1.4.1 写一个字节
用于对IIC器件某个寄存器的配置,如对MPU6050的某些参数进行设置。
- 写寄存器时,主设备除了发出开始标志和地址位,还要加一个R/W 位,0 为写,1 为读
- 在第9个时钟周期(高电平时),MPU6050 产生应答信号
- 主设备开始传送寄存器地址,并接到应答
- 然后开始传送寄存器数据,仍然要有应答信号
- 最后主设备发送停止信号。
1.4.2 连续写多个字节
对连续地址的写入,这个用的较少。
- 首先由主设备产生开始信号,然后发送从设备地址位和一个写数据位,等待应答
- 然后发送寄存器地址,才能开始读寄存器
- 收到应答信号后,主设备再发一个开始信号,然后发送从设备地址位和一个读数据位
- 然后,作为从设备的MPU6050 产生应答信号并开始发送寄存器中的数据
- 通信以主设备产生的拒绝应答信号(nACK)和结束标志(Stop)结束
- 拒绝应答信号(nACK)产生定义为SDA 数据在第9 个时钟周期一直为高
1.4.3 读一个字节
用于读取IIC器件某个寄存器的数值。
- 首先由主设备产生开始信号,然后发送从设备地址位和一个写数据位,等待应答
- 然后发送寄存器地址,才能开始读寄存器
- 收到应答信号后,主设备再发一个开始信号,然后发送从设备地址位和一个读数据位
- 然后,作为从设备的MPU6050 产生应答信号并开始发送寄存器中的数据
- 通信以主设备产生的拒绝应答信号(nACK)和结束标志(Stop)结束
- 拒绝应答信号(nACK)产生定义为SDA 数据在第9 个时钟周期一直为高
1.4.4 连续读多个字节
也是用于读取IIC器件某个寄存器的数值,当某些数据一位字节不够表示,或有一组连续的数据需要读时,可以使用该模式。
通信时序与上面的“读一个字节”类似,上面是读一个字节后就nAck叫停,若要连续写,则发送Ack,直到不需要继续读时再回复nAck。
复习了这么多,之前对IIC懵懵懂懂的是否依然犯迷糊,好了,现在从理论进入实践,看看真实的IIC是什么样子。
2 初识IIC真实波形
下面这张图是通过示波器抓取的IIC波形,可以看到:
- 时钟线SCL是一种间歇性的方波(需要通信时才产生方波)
- 数据线SDA根据SCL提供的节拍,高电平代表数据1,低电平代表数据0
- 没有数据传输时,SDA和SCL均为高电平状态
- 起始信号后,数据是9个一组,包括8位的数据和另一方的1位回应
图中红色数字表示单片机发送的8位数据,黄色数字表示IIC器件回应的信号,低电平0表示器件收到了单片机发来的数据。
现在对IIC波形有没有多了一些直观的认识?下面再进入编程阶段,看看程序是怎么控制这两根线的。
3 编写IIC通信函数
IIC通信可以使用单片机自带的硬件IIC,它提供了固定的引脚接口和函数库。也可以自己通过软件编写来实现IIC时序,这时就可以任选引脚,也方便其它硬件平台的移植。
下面通过软件IIC的编写,从软件角度理解IIC通信逻辑。
以下函数都是单片机在执行,即主机发出的动作,所以一定要从单片机的角度思考哦~
另外,不要看到程序就匆匆掠过,为帮助理解,我对代码进行了一定的注解,仔细分析每条代码,想想与IIC的逻辑如何对应起来,IIC逻辑还没懂的,读完本篇,分析过真实的IIC波形后,再来看看代码,会有不一样的体会。
3.1 起始IIC_Start()
//==================================
//产生IIC起始信号
//==================================
void IIC_Start(void)
{
SDA_OUT(); //SDA线输出 ?????
IIC_SDA=1;
delay_us(2);
IIC_SCL=1; //时钟线为高时
delay_us(2);
IIC_SDA=0; //数据线由高到低
delay_us(4);
IIC_SCL=0; //时钟线拉低,钳住IIC总线,准备发送数据
}
最后一句SCL拉低,然后就准备产生时钟信号,发送数据了。
3.2 停止IIC_Stop()
//==================================
//产生IIC停止信号
//==================================
void IIC_Stop(void)
{
SDA_OUT(); //sda线输出
IIC_SCL=0; //确保时钟线为低时,数据线才能变化为0,否则这就可能成起始信号了!
delay_us(2);
IIC_SDA=0;
delay_us(2);
IIC_SCL=1; //时钟线为高时
IIC_SDA=1; //数据线由低到高
delay_us(4);
}
停止前也要确保SCL是拉低的状态。
最后SDA和SCL都为高,即释放IIC总线,IIC总线进入空闲状态。
3.3 等待应答IIC_wait_Ack()
//==================================
//等待应答信号到来
//用于发送模式下,发送8位后,等待器件应答第9位
//返回值:1,接收应答失败
// 0,接收应答成功
//==================================
u8 IIC_Wait_Ack(void)
{
u8 ucErrTime=0;
SDA_IN(); //SDA设置为输入
IIC_SDA=1;delay_us(1); //SDA先拉高,若被从机拉低则说明收到应答信号
IIC_SCL=1;delay_us(1); //SCL拉高,产生第9位的脉冲
while(READ_SDA)
{
ucErrTime++;
if(ucErrTime>250)
{
IIC_Stop();
return 1;
}
}
IIC_SCL=0;//时钟输出0 //SCL拉低,结束第9位的脉冲
return 0;
}
在一定是时间内检测SDA是否被从机拉低,被拉低则说明从机收到了数据。
3.4 产生应答IIC_Ack()
//==================================
//产生ACK应答
//用于读取模式(SDA为in)读了8位器件数据后,在第9位给出一个应答,我还要继续读
//==================================
void IIC_Ack(void)
{
IIC_SCL=0; //确保时钟线为低时,数据线才能变化为0,否则这就可能成起始信号了!
SDA_OUT(); //SDA由读取改为发送
delay_us(2);
IIC_SDA=0; //拉低SDA,表示应答
delay_us(2);
IIC_SCL=1; //SCL先上升
delay_us(2);
IIC_SCL=0; //SCL再下降,形成一个脉冲,应答才生效
}
单片机在接收器件数据后,进行回应,表示接收到了器件的数据。
该函数用在连续读取多个字节时,每读完一个字节(8位),产生回应,表示还要进行读,这时器件就可以继续发数据了。
当单片机不需要继续读,如连续读的最后一个字节,或只读一个字节,单片机发送非应答信号,这时器件以为单片机没有收到数据,接下来就不会再发数据了。
非应答函数如下,就是拉高SDA:
3.5 不产生应答IIC_nAck()
//==================================
//不产生ACK应答
//用于读取模式(SDA为in)读了8位器件数据后,在第9位给出一个应答,我不想读了
//==================================
void IIC_NAck(void)
{
IIC_SCL=0; //确保时钟线为低时,数据线才能变化为0,否则这就可能成起始信号了!
SDA_OUT(); //SDA由读取改为发送
IIC_SDA=1; //拉高SDA,表示不应答
delay_us(2);
IIC_SCL=1; //SCL先上升
delay_us(2);
IIC_SCL=0; //SCL再下降,形成一个脉冲,不应答才生效
}
3.6 IIC发送一个字节
//==================================
//IIC发送一个字节
//返回从机有无应答
//1,有应答
//0,无应答
//==================================
void IIC_Send_Byte(u8 txd)
{
u8 t;
SDA_OUT(); //SDA发送模式
IIC_SCL=0; //拉低时钟开始数据传输
for(t=0;t<8;t++)
{
IIC_SDA=(txd&0x80)>>7; //SDA高低电平表示数据1和0
txd<<=1;
delay_us(2); //对TEA5767这三个延时都是必须的
IIC_SCL=1; //SCL先上升
delay_us(2);
IIC_SCL=0; //SCL再下降,形成一个脉冲,发送一位数据生效
delay_us(2);
}
}
发送一个字节,就是分8次循环,产生8个时钟信号,并将SDA赋值为0或1。
3.7 IIC读取一个字节
#define SDA_in {GPIOB->MODER&=~(3<<(11*2));GPIOB->MODER|=0<<11*2;}
#define SDA_out {GPIOB->MODER&=~(3<<(11*2));GPIOB->MODER|=1<<11*2;}
//==================================
//读1个字节
//ack=1时,发送ACK,ack=0,发送nACK
//==================================
u8 IIC_Read_Byte(unsigned char ack)
{
unsigned char i,receive=0;
SDA_IN(); //SDA输入模式
for(i=0;i<8;i++ )
{
IIC_SCL=0; //SCL先下降,通过循环,形成时钟脉冲
delay_us(2);
IIC_SCL=1; //SCL上升
receive<<=1;
if(READ_SDA)
receive++; //读取并组合记录数据,++表示读到1了,最低位置1
delay_us(1);
}
//读取8位后,主机需要变为发送模式,在第9位进行应答或不应答
//此时CLK还是高电平状态,不过下面的应答会先将CLK拉低的
if (!ack)
{
//读1个字节,或读多个字节读到最后一个字节时,使用nACK
//然后配合使用IIC停止信号
IIC_NAck();//发送nACK
}
else
{
//读多个字节还没读完时,使用ACK,表示现在读的ok,还要继续读
IIC_Ack(); //发送ACK
}
return receive;
}
读取一个字节,也是分8次循环,产生8个时钟信号,并读取SDA的高低电平信号,最后,根据要不要继续读下一个字节,发送第9位的Ack或nACK。
4.1 真实IIC波形详细分析
4.1.1 读取从机数据(单字节读)
这张图展示IIC读某个器件的寄存器的一个字节的真实波形(注:实际是读了2个不同寄存器的值,每个寄存器读了1个字节,所以,可以先只看前半部分哦~),我已对波形进行了详细的注解。
对照着图,再来温习一下各个信号的特点:
- 起始信号:时钟线SCL为高时,数据线SDA由高到低
- 停止信号:时钟线SCL为高时,数据线SDA由低到高
- 数据信号:连续的8位,每一个SCL脉冲时钟对应的SDA,高电平为数据1,低电平为数据0
- 应答信号:第9位(数据信号后),由对方产生的回应,0为产生回应,1为不产生回应
这幅图中,单片机先产生起始信号,然后发送7位器件地址+1位写标志(绿色的0),并等待从机回应(从机拉低SDA表示收到数据),接着发送8位寄存器地址,并等待从机回应。然后,单片机先再次产生起始信号,发送7位器件地址+1位读标志(绿色的1),并等待从机回应。从机收到读的信号后,从机开始发送8位数据,主机接收到数据后,主机发送nAck不应答信号(图中的Ack(1),主机将SDA拉高,从机则认为主机刚才没有收到它发送的数据,从机将不再继续发送),接着主机发送结束信号,读取完成。
此图后半部分是以相同方式读了另一个寄存器的值。
上图中,SCL信号都是由单片机产生,SDA信号由单片机和IIC器件(从机)共同产生,当需要对IIC器件的寄存器写时,单片机产生SDA数据,当需要读取IIC器件的寄存器数据时,改变传输方向,IIC器件产生SDA数据。
对于主机和从机什么时候控制SDA,还可以参考这个图帮助理解:
4.1.2 读取从机数据(多字节读)
上面是单字节读的波形,再来看看多字节的波形,前面的写器件地址、写寄存器地址1与单字节读一样,这张图只显示了后面不一样的部分,主要区别在于单片机接收到数据1后,产生低电平的应答,从而可以继续读取数据2。
(注意,因为传感器这次测得的数据不一样,所以读出的数据也不一样哦~)
注:以上的IIC真实波形,是使用是硬件IIC,自己编写的软件IIC测得的波形,可能在两个信号的前后延时时间上稍有差别,但整体的时序逻辑肯定是一样的。
4.1.3 配置从机寄存器(单字节写与多字节写)
对于寄存器的配置,也就是IIC的写寄存器操作,我就不放图了,参考上面的“常用的数据收发方式(时序)”以及上面的IIC读寄存器的真实波形,IIC的写寄存器的真实波形,应该可以脑补出哦,哈哈~
最后
通过真实的IIC波形分析,对IIC通信逻辑有没有更加直观的认识呢?
Linux I2C GPIO驱动是在没有专用I2C芯片的情况下,用GPIO口来模拟I2C总线时序,完成Linux与I2C设备的通信过程。用两根GPIO,分别模拟SDA和SCL。它与使用i2c芯片的驱动有所不同的是传输算法的实现,GPIO模拟i2c驱动中有自己的一套传输算法。GPIO模拟I2C是要占用CPU资源的,而用I2C芯片是不占CPU资源的。使用i2c子系统,而不使用普通字符设备,有以下好处:
1) 使用Linux I2C子系统,不需要去过于详细了解I2C操作。
2) 编写驱动可移植性强。
3) 可以使用内核资源,当面对复杂I2C器件,工作量相对少得多。
I2C工作原理:I2C总线标准的两根传输线,SDA是数据线,Scl是时钟线,当SCL为高,SDA由高变低时,发送启动信息,发送9个脉冲,1-7是地址,8是读写控制位,9是ACK应答位,所以挂在I2C上的被控设备都接受所发送的信息,并把接收到的7位地址与自己的地址进行比较,如果相同ACK就会反馈应答。当SCL为高,SDA由低变高,则发送停止信号。
2 架构
Linux的I2C构架分为三个部分:
1)I2C core框架
提供了核心数据结构的定义和相关接口函数,用来实现I2C适配器
驱动和设备驱动的注册、注销管理,以及I2C通信方法上层的、与具体适配器无关的代码,为系统中每个I2C总线增加相应的读写方法。
I2C core框架具体实现在/drivers/i2c目录下的i2c-core.c和i2c-dev.c
2) I2C总线驱动
定义描述具体I2C总线适配器的i2c_adapter数据结构、实现在具体I2C适配器上的I2C总线通信方法,并由i2c_algorithm数据结构进行描述。 经过I2C总线驱动的的代码,可以为我们控制I2C产生开始位、停止位、读写周期以及从设备的读写、产生ACK等。
I2C总线驱动具体实现在/drivers/i2c目录下busses文件夹。例如:Linux I2C GPIO总线驱动为i2c_gpio.c. I2C总线算法在/drivers/i2c目录下algos文件夹。例如:Linux I2C GPIO总线驱动算法实现在i2c_algo_bit.c.
3) I2C 设备驱动
是对具体I2C硬件驱动的实现。I2C 设备驱动通过I2C适配器与CPU通信。其中主要包含i2c_driver和i2c_client数据结构,i2c_driver结构对应一套具体的驱动方法,例如:probe、remove、suspend等,需要自己申明。i2c_client数据结构由内核根据具体的设备注册信息自动生成,设备驱动根据硬件具体情况填充。具体使用下面介绍。
I2C 设备驱动具体实现放在在/drivers/i2c目录下chips文件夹。
3 设备注册
下面以GPIO模拟i2c总线的驱动为例,来介绍设备注册,对于使用i2c芯片的驱动都是大同小异,主要在传输算法上的区别。首先make menuconfig把i2c-gpio选上,让它能编进内核。设备注册包括两种设备的注册,i2c-gpio总线和i2c设备驱动。
1) i2c-gpio总线注册
/drivers/i2c/busses/i2c_gpio.c是i2c-gpio总线驱动源码。在这里可以看到i2c-gpio的注册:
static struct platform_driver i2c_gpio_driver = {
.driver = {
.name = "i2c-gpio", //驱动名字
.owner = THIS_MODULE,
},
.probe = i2c_gpio_probe,
.remove = __devexit_p(i2c_gpio_remove),
};
static int __init i2c_gpio_init(void)
{
int ret;
ret = platform_driver_register(&i2c_gpio_driver);//注册成平台设备
if (ret)
printk(KERN_ERR "i2c-gpio: probe failed: %d\n", ret);
return ret;
}
module_init(i2c_gpio_init);
platform是linux虚拟的总线,称为platform总线,相应的设备称为platform_device,相应的驱动称为platform_driver。我们知道i2c总线也对应一个设备,在这里就是对应的i2c_adapte结构,这在后面会有详细介绍。在这里可以看到它将i2c总线驱动注册成平台设备驱动platform_driver_register(&i2c_gpio_driver)。
把i2c_gpio设备注册为平台设备,需要在mach_xxx的板级文件(devices.c)中添加i2c-gpio需要用到的资源定义,即将i2c总线设备封装成平台设备,下面首先定义总线占用的系统资源:
static struct i2c_gpio_platform_data i2c3_data = {
.sda_pin = CONFIG_SDA_PIN;
.scl_pin = CONFIG_SCL_PIN; //设置需要用到的gpio引脚
.udelay = 0, //设置I2C工作频率,如果没有默认值为50
.timeout = 0, //设置I2C工作超时,如果没有默认值为10
};
由于i2c_gpio驱动需要注册到platform总线上面,还需要在mach_xxx的板级文件中添加i2c-gpio的platform_device结构。
static struct platform_device i2c3_device = {
.name = "i2c-gpio", //必须和i2c-gpio驱动的名字相同
.id = 2, //总线ID号
.dev = {
.platform_data = &i2c3_data,
},
};
注册i2c-gpio驱动前要有一个GPIO的设置过程,设置过程如下:
{
//SDA
Pnx_gpio_set_mode(GPIO_F8,GPIO_MODE_MUX1)
Pnx_gpio_set_direction(GPIO_F8,GPIO_DIR_OUTPUT)
//SCL
Pnx_gpio_set_mode(GPIO_F7,GPIO_MODE_MUX1)
Pnx_gpio_set_direction(GPIO_F7,GPIO_DIR_OUTPUT)
};
最后把i2c-gpio设备注册进platform总线。
platform_device_register(&i2c3_device);
2) 把i2c设备驱动注册到i2c-gpio总线
例如:设备驱动源码在/drivers/i2c/chips/lis35de.c,其注册到i2c总线需要的
做法如下。首先定义设备ID:
static const struct i2c_device_id lis35de_id[] = {
{ "lis35de", 0 },//设备名和设备是有数据长度
{ }
};
然后声明i2c_driver结构:
static struct i2c_driver st_lis35de_driver = {
.probe = st_lis35de_probe,
.remove = st_lis35de_remove,
.suspend = st_lis35de_suspend,
.resume = st_lis35de_resume,//上面4个函数根据具体情况取舍
.id_table = lis35de_id,
.driver = {
.name = "lis35de", //驱动名字
},
};
最后调用static inline int i2c_add_driver(struct i2c_driver *driver)注册lis35de驱动到I2C总线,如下:
static int __init st_lis35de_init(void)
{
return i2c_add_driver(&st_lis35de_driver);//注册st_lis35de_driver
};
module_init(st_lis35de_init);
但是到目前还不知道注册到那根I2C总线,现在把lis35de设备驱动添加到我们想要的i2c-gpio总线上。使用内核提供的函数i2c_register_board_info,在mach_xxx的板级文件中把设备信息注册到需要注册的I2C总线上面。
int __init i2c_register_board_info(int busnum,//设备需要注册到的总线ID
struct i2c_board_info const *info,//设备信息包括设备名,地址等
unsigned len)
例如:把lis35de驱动注册到i2c-gpio总线,总线ID为2。
static struct i2c_board_info i2c_devices_lis35de[] = {
{
I2C_BOARD_INFO("lis35de", 0x1C), //设备名和地址
},
};
i2c_register_board_info(2,i2c_devices_lis35de,ARRAY_SIZE(i2c_devices_lis35de));
arch/arm/mach-pnx67xx/board_pnx67xx_wavex.c中unsigned int pnx_modem_gpio_reserved[]下注释掉GPIO_F7,GPIO_F8,防止内核认为F8,F7已经使用过了,至此已经把i2c-gpio总线注册到系统,把设备驱动注册到i2c-gpio总线。
前面说了那么多,是不是有点乱了,这里我们在来理一下:
(一)i2c总线驱动
1)在那个devices.c文件中,声明平台设备占用的系统资源,然后定义一个平台设备,并注册这个平台设备到平台总线上
2)在i2c-gpio.c文件中,声明该驱动支持的设备列表,然后定义一个平台驱动结构,并注册这个平台驱动到平台总线上
(二)i2c设备驱动
1)同样在devices.c文件下,在对应总线的设备列表中声明一个i2c设备结构,然后通过i2c_register_board_info()函数,将这个设备列表注册到i2c总线上
2)在lis35de.c文件中,声明支持的i2c设备列表和一个i2c设备驱动结构体i2c_driver,然后将其注册到i2c总线上
注意:这里不管是设备还是驱动先注册到总线上,他们都会自动请求匹配总线上的所有驱动或设备。
4 I2C关键数据结构和详细注册流程
上面的描述都是i2c系统的框架,具体的数据结构注册流程下面会详细介绍。
4.1 关键数据结构
在i2c.h头文件中定义了i2c_adapter、i2c_algorithm、i2c_driver和i2c_client 4个比较关键的数据结构。
1)i2c_algorithm对应一套通信方法。
用来实现具体的收发算法,此数据结构非常重要,通过其中的收发函数会调用具体的硬件收发操作,对于i2c-gpio总线的通信方法实现在/drivers/i2c目录下algos文件夹i2c_algo_bit.c。
struct i2c_algorithm {
int (*master_xfer)(struct i2c_adapter *adap,struct i2c_msg *msgs,int num);
//i2c传输函数指针
int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
unsigned short flags, char read_write,
u8 command, int size, union i2c_smbus_data * data);
//smbus传输函数指针
u32 (*functionality) (struct i2c_adapter *);
//返回适配器支持功能
};
2)i2c_adapter
用来定义总线上的每一个adapter(适配器),每一个adapter都需要i2c_algorithm中提供的通信函数来控制适配器的访问周期,因此在i2c_adapter中包含i2c_algorithm指针。i2c_algorithm的关键函数master_xfer用于产生I2C访问信号,以i2c_msg为单位。
struct i2c_adapter {
struct module *owner; //所属模块
unsigned int id; //algorithm类型,定义在i2c-id.h以I2C_ALGO_开始
unsigned int class; /* classes to allow probing for */
const struct i2c_algorithm *algo;
void *algo_data; //algorithm数据
int (*client_register)(struct i2c_client *); //client注册时调用
int (*client_unregister)(struct i2c_client *);
/* data fields that are valid for all devices */
u8 level; /* nesting level for lockdep */
struct mutex bus_lock;
struct mutex clist_lock;
int timeout; /* in jiffies */
int retries;
struct device dev; /* 适配器设备 */
int nr;
struct list_head clients; /* DEPRECATED */
char name[48];
struct completion dev_released;
};
struct i2c_msg {
__u16 addr; /* 从机地址 */
__u16 flags;
#define I2C_M_TEN 0x0010 /* this is a ten bit chip address */
#define I2C_M_RD 0x0001 /* read data, from slave to master */
#define I2C_M_NOSTART 0x4000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_REV_DIR_ADDR 0x2000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK 0x1000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NO_RD_ACK 0x0800 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_RECV_LEN 0x0400 /* length will be first received byte */
__u16 len; /* msg length */
__u8 *buf; /* pointer to msg data */
};
3) i2c_driver结构体
struct i2c_driver {
int id;
unsigned int class;
int (*attach_adapter)(struct i2c_adapter *);//依附i2c_adapter
int (*detach_adapter)(struct i2c_adapter *);//脱离i2c_adapter
int (*detach_client)(struct i2c_client *) __deprecated; //脱离i2c_ client
int (*probe)(struct i2c_client *, const struct i2c_device_id *);
int (*remove)(struct i2c_client *);
void (*shutdown)(struct i2c_client *);
int (*suspend)(struct i2c_client *, pm_message_t mesg);
int (*resume)(struct i2c_client *);
int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);
//类似ioctl
struct device_driver driver;
const struct i2c_device_id *id_table;
int (*detect)(struct i2c_client *, int kind, struct i2c_board_info *);
/* Device detection callback for automatic device creation */
const struct i2c_client_address_data *address_data;
struct list_head clients;
};
4)i2c_client结构体
struct i2c_client {
unsigned short flags; /* 标志 */
unsigned short addr; /* 低7位为芯片地址 */
char name[I2C_NAME_SIZE]; //设备名
struct i2c_adapter *adapter; /*依附i2c_adapter */
struct i2c_driver *driver; /*依附i2c_ driver */
struct device dev; /* the device structure */
int irq; /* irq issued by device */
struct list_head list; /* DEPRECATED */
struct list_head detected;
struct completion released;
};
4.2详细注册流程
在平台设备驱动注册时,i2c-gpio总线被注册后,第一个执行的函数是i2c_gpio_probe:在这里主要做了3件事,首先构筑了一个i2c_adapter,然后申请gpio,最后关键调用i2c_bit_add_bus。虽然i2c总线被注册成平台设备,但这里主要讲解i2c总线部分内容,对平台设备和驱动的注册过程不做详述,感兴趣的可以查阅平台驱动部分内容。
static int __devinit i2c_gpio_probe(struct platform_device *pdev)
{
pdata = pdev->dev.platform_data; //获得具体硬件结构
if (!pdata)
return -ENXIO;
ret = -ENOMEM;
adap = kzalloc(sizeof(struct i2c_adapter), GFP_KERNEL);//在这里获得了适配器
if (!adap)
goto err_alloc_adap;
bit_data = kzalloc(sizeof(struct i2c_algo_bit_data), GFP_KERNEL);//在这里定义了具体硬件的实现
if (!bit_data)
goto err_alloc_bit_data;
ret = gpio_request(pdata->sda_pin, "sda");
if (ret)
goto err_request_sda;
ret = gpio_request(pdata->scl_pin, "scl");
if (ret)
goto err_request_scl;
if (pdata->sda_is_open_drain) { //如果集电极开路
gpio_direction_output(pdata->sda_pin, 1);//设定方向为输出
bit_data->setsda = i2c_gpio_setsda_val;//设定setsda实现函数
} else {
gpio_direction_input(pdata->sda_pin);//如果集电极不是开路,那么设定方向为输入
bit_data->setsda = i2c_gpio_setsda_dir;//设定setsda的实现函数
}
if (pdata->scl_is_open_drain || pdata->scl_is_output_only) {//集电极开路并仅做输出
gpio_direction_output(pdata->scl_pin, 1);//设定方向
bit_data->setscl = i2c_gpio_setscl_val; //设定setscl的实现函数
} else { //集电极不是开路时候
gpio_direction_input(pdata->scl_pin);
bit_data->setscl = i2c_gpio_setscl_dir;
}
if (!pdata->scl_is_output_only) //仅仅作为输出
bit_data->getscl = i2c_gpio_getscl;
bit_data->getsda = i2c_gpio_getsda;
if (pdata->udelay) //关于延时的设定
bit_data->udelay = pdata->udelay;
else if (pdata->scl_is_output_only)
bit_data->udelay = 50; /* 10 kHz */
else
bit_data->udelay = 5; /* 100 kHz */
if (pdata->timeout)
bit_data->timeout = pdata->timeout;
else
bit_data->timeout = HZ / 10; /* 100 ms */
bit_data->data = pdata; //让bit_data与platform_data相关联
adap->owner = THIS_MODULE;
snprintf(adap->name, sizeof(adap->name), "i2c-gpio%d", pdev->id);
adap->algo_data = bit_data; //algo_data中加入具体的实现方法,被algo中函数调用
adap->class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
adap->dev.parent = &pdev->dev;
/*
* If "dev->id" is negative we consider it as zero.
* The reason to do so is to avoid sysfs names that only make
* sense when there are multiple adapters.
*/
adap->nr = (pdev->id != -1) ? pdev->id : 0;
ret = i2c_bit_add_numbered_bus(adap); //添加进入总线,在i2c-algo-bit.c中定义
if (ret)
goto err_add_bus;
platform_set_drvdata(pdev, adap);//加定适配器到platform设备中去
return 0;
}
此函数是整个i2c-gpio.c的关键,几乎整个文件函数的注册以及algo中相关函数的注册都与他相关。i2c_bit_add_bus:在这里调用i2c_bit_prepare_bus。
int i2c_bit_add_numbered_bus(struct i2c_adapter *adap)
{
int err;
err = i2c_bit_prepare_bus(adap); //这里就是算法和适配器的关联函数,算法在别处已有实现
if (err)
return err;
return i2c_add_numbered_adapter(adap);
}
i2c_bit_prepare_bus:可以看到原来分配到的适配器在这里和i2c_bit_algo算法结合了。
static int i2c_bit_prepare_bus(struct i2c_adapter *adap)
{
struct i2c_algo_bit_data *bit_adap = adap->algo_data;
if (bit_test) {
int ret = test_bus(bit_adap, adap->name);
if (ret < 0)
return -ENODEV;
}
/* register new adapter to i2c module... */
adap->algo = &i2c_bit_algo; //没错,就是这里的关联
adap->timeout = 100; /* default values, should */
adap->retries = 3; /* be replaced by defines */
return 0;
}
再回头看i2c_bit_add_bus中调用的i2c_add_numbered_adapter(adap):
int i2c_add_adapter(struct i2c_adapter *adapter)
{
int id, res = 0;
retry:
if (idr_pre_get(&i2c_adapter_idr, GFP_KERNEL) == 0)
return -ENOMEM;
mutex_lock(&core_lock);
/* "above" here means "above or equal to", sigh */
res = idr_get_new_above(&i2c_adapter_idr, adapter,
__i2c_first_dynamic_bus_num, &id);
mutex_unlock(&core_lock);
if (res < 0) {
if (res == -EAGAIN)
goto retry;
return res;
}
adapter->nr = id;
return i2c_register_adapter(adapter);
}
在i2c_add_numbered_adapter最后调用了i2c_register_adapter(adapter);
static int i2c_register_adapter(struct i2c_adapter *adap)
{
int res = 0, dummy;
/* Can't register until after driver model init */
if (unlikely(WARN_ON(!i2c_bus_type.p)))
return -EAGAIN;
mutex_init(&adap->bus_lock);
mutex_init(&adap->clist_lock);
INIT_LIST_HEAD(&adap->clients);
mutex_lock(&core_lock);
/* Add the adapter to the driver core.
* If the parent pointer is not set up,
* we add this adapter to the host bus.
*/
if (adap->dev.parent == NULL) {
adap->dev.parent = &platform_bus;
pr_debug("I2C adapter driver [%s] forgot to specify "
"physical device\n", adap->name);
}
dev_set_name(&adap->dev, "i2c-%d", adap->nr);
adap->dev.release = &i2c_adapter_dev_release;
adap->dev.class = &i2c_adapter_class;
res = device_register(&adap->dev);
if (res)
goto out_list;
dev_dbg(&adap->dev, "adapter [%s] registered\n", adap->name);
/* create pre-declared device nodes for new-style drivers */
if (adap->nr < __i2c_first_dynamic_bus_num)
i2c_scan_static_board_info(adap); //静态扫描挂在总线上的所有i2c设备,然后逐一创建一个i2c_client结构
/* Notify drivers */
dummy = bus_for_each_drv(&i2c_bus_type, NULL, adap,
i2c_do_add_adapter);
out_unlock:
mutex_unlock(&core_lock);
return res;
out_list:
idr_remove(&i2c_adapter_idr, adap->nr);
goto out_unlock;
}
其中的i2c_scan_static_board_info(adap)调用i2c_new_device:
static void i2c_scan_static_board_info(struct i2c_adapter *adapter)
{
struct i2c_devinfo *devinfo;
mutex_lock(&__i2c_board_lock);
list_for_each_entry(devinfo, &__i2c_board_list, list) {
if (devinfo->busnum == adapter->nr
&& !i2c_new_device(adapter,
&devinfo->board_info)) //就是这里,它根据devinfo来创建一个新的i2c_client设备结构
printk(KERN_ERR "i2c-core: can't create i2c%d-%04x\n",
i2c_adapter_id(adapter),
devinfo->board_info.addr);
}
mutex_unlock(&__i2c_board_lock);
}
i2c_new_device:
struct i2c_client *
i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{
struct i2c_client *client;
int status;
client = kzalloc(sizeof *client, GFP_KERNEL);
if (!client)
return NULL;
client->adapter = adap;
client->dev.platform_data = info->platform_data;
if (info->archdata)
client->dev.archdata = *info->archdata;
client->flags = info->flags;
client->addr = info->addr;
client->irq = info->irq;
strlcpy(client->name, info->type, sizeof(client->name));
/* a new style driver may be bound to this device when we
* return from this function, or any later moment (e.g. maybe
* hotplugging will load the driver module). and the device
* refcount model is the standard driver model one.
*/
status = i2c_attach_client(client);
if (status < 0) {
kfree(client);
client = NULL;
}
return client;
}
在i2c_new_device中,内核为我们分配i2c_client,把我们在板级文件中申明的设备名和地址:
static struct i2c_board_info i2c_devices[] = {
{
I2C_BOARD_INFO("lis35de", 0x1C), //设备名和地址
},
填充到i2c_client。一个i2c_client就代表着一个位于adapter适配器上,地址为client->addr,使用设备驱动的一个i2c设备。到这里就可以通过 Linux I2C核心提供的不依赖硬件接口的函数了,接受/发送函数等。
也许你会疑惑在创建i2c_client设备时,是根据devinfo来创建的,那么这个devinfo是从那来的?我们在devices.c中不是注册的是i2c_board_info这个结构吗?没错,在devices.c中的确是注册的i2c_board_info结构,下面我们来看看注册这个结构数组的这个i2c_regester_board_info()函数:
int __init
i2c_register_board_info(int busnum,
struct i2c_board_info const *info, unsigned len)
{
int status;
down_write(&__i2c_board_lock);
/* dynamic bus numbers will be assigned after the last static one */
if (busnum >= __i2c_first_dynamic_bus_num)
__i2c_first_dynamic_bus_num = busnum + 1;
for (status = 0; len; len--, info++) {
struct i2c_devinfo *devinfo;
devinfo = kzalloc(sizeof(*devinfo), GFP_KERNEL);
if (!devinfo) {
pr_debug("i2c-core: can't register boardinfo!\n");
status = -ENOMEM;
break;
}
devinfo->busnum = busnum;
devinfo->board_info = *info;
list_add_tail(&devinfo->list, &__i2c_board_list);
}
up_write(&__i2c_board_lock);
return status;
}
看到了吧,在这个注册函数里,它创建一个devinfo结构变量,并用总线号和i2c_board_info结构来初始化这个devinfo变量,然后加入一个全局的devinfo链表,来看看这个devinfo结构体的定义:
struct i2c_devinfo {
struct list_head list;
int busnum;
struct i2c_board_info board_info;
};
这个注册函数就是在i2c-boardinfo.c中定义的,它维护一个全局的devinfo链表,在创建i2c_client的时候,就是通过这个devinfo链表来逐一创建的。
5 使用I2C子系统资源函数操作I2C设备
Linux I2C 核心提供的函数主要有:
1)增加/删除i2c_adapter
Int i2c_add_adapter(struct i2c_adapter *adap);
Int i2c_del_adapter(struct i2c_adapter *adap);
2) 增加/删除i2c_driver
Int i2c_register_driver(struct module *owner,struct i2c_driver *driver);
Int i2c_del_driver(struct i2c_driver *driver);
3)i2c_client依附和脱离
Int i2c_attach_client(struct i2c_client *client);
Int i2c_detach_client(struct i2c_client *client);
4)i2c传送\发送\接收
Int i2c_transfer(struct i2c_adaper *adap,struct i2c_msg *msgs,int num);
Int i2c_master_send(struct i2c_client *client,const char *buf,int count);
Int i2c_master_recv(struct i2c_client *client,const char *buf,int count);
以上三个函数必须首先在设备驱动中的xxx_probe中提供指向i2c_client的指针,注意在提取出i2c_client之前不要使用,否则出现空指针。
具体使用方法:eg:
struct i2c_client *this_client;//申明全局变量;
static int __init st_lis35de_probe(struct i2c_client *client, const struct i2c_device_id * devid)
{
……………………
I2c_set_clientdata(client,&lis35de);
Client->addr = 0x1c;
this_client = client;//提取出client,然后使用,这三句要放到acc_init之前。
acc_init();//这个函数最终会调用LIS35DE_init(),把这个函数注释掉,因为这个函数的gpio的设定已经被放到devices.c gpio-i2c注册之前,这里不用再来一遍。
……………………
}
void LIS35DE_IICWrite(u_int8_t RegAdd, u_int8_t Data ,u_int8_t *result)
{
char buffer[2];
*result = 1;
// buffer[0]=LIS35DE_AddW;//使用i2c_master_send等函数,不再需要传送地址
buffer[0]=RegAdd;
buffer[1]=Data;
if(i2c_master_send(this_client, buffer,2)<0)
{
printk(KERN_ERR "LIS35DE_IICWrite: i2c_master_send error\n");
return;
}
*result = 0;
return;
}
int8_t LIS35DE_IICRead(u_int8_t RegAdd,u_int8_t *result)
{
S8 Data;
*result = 1;
char buffer[0];
//buffer[0]=LIS35DE_AddW;
//使用i2c_master_send等函数,不再需要传送地址
buffer[0]=RegAdd;
//buffer[2]=LIS35DE_AddR; //使用i2c_master_send等函数,不再需要传送地址
if(i2c_master_send(this_client, buffer,1)<0)
{
printk(KERN_ERR "LIS35DE_IICRead: i2c_master_send error\n");
return -1;
}
if( i2c_master_recv(this_client, &Data,1)<0)
{
printk(KERN_ERR "LIS35DE_IICRead: i2c_master_recv error\n");
return -1;
}
*result = 0;
return Data;
}
下面是使用i2c_transfer()函数来实现上面函数的例子:
static int LIS35DE_RxData(char *rxData, int length)
{
struct i2c_msg msgs[] = {
{
.addr = this_client->addr,
.flags = 0,
.len = 1,
.buf = rxData,
},
{
.addr = this_client->addr,
.flags = I2C_M_RD,
.len = length,
.buf = rxData,
},
};
#if DEBUG
printk(KERN_INFO "%s\n", __FUNCTION__);
#endif
if (i2c_transfer(this_client->adapter, msgs, 2) < 0) {
printk(KERN_ERR "LISI2C_RxData: transfer error\n");
return -EIO;
} else
return 0;
}
static int LIS35DE_TxData(char *txData, int length)
{
struct i2c_msg msg[] = {
{
.addr = this_client->addr,
.flags = 0,
.len = length,
.buf = txData,
},
};
#if DEBUG
printk(KERN_INFO "%s\n", __FUNCTION__);
#endif
if (i2c_transfer(this_client->adapter, msg, 1) < 0) {
printk(KERN_ERR "LISI2C_TxData: transfer error\n");
return -EIO;
} else
return 0;
}
void LIS35DE_IICWrite(u_int8_t RegAdd, u_int8_t Data ,u_int8_t *result)
{
char buff[2];
*result = 1;
// client->addr = LIS35DE_AddW;
buff[0] = RegAdd;
buff[1] = Data;
if( LIS35DE_TxData(buff, 2) < 0 ) {
#if DEBUG
printk(KERN_INFO "%s\n", __FUNCTION__);
printk("# LIS35IIC Write Error #\r\n");
#endif
return;
}
*result = 0;
}
int8_t LIS35DE_IICRead(u_int8_t RegAdd,u_int8_t *result)
{
S8 Data;
*result = 1;
char buff[2];
// client->addr = LIS35DE_AddR;
buff[0] = RegAdd;
if( LIS35DE_RxData(buff, 1) < 0 ) {
#if DEBUG
printk("# LIS35IIC Read Error #\r\n");
#endif
return 1;
}
*result = 0;
Data = *buff;
return Data;
}
其中有一部分/* */是由代码模拟时序来模拟i2c的。
6 Gpio模拟i2c总线的通用传输算法
/drivers/i2c/i2c-algo-bit.c
init i2c_bit_add_numbered_bus(struct i2c_adapter *adap)
int i2c_bit_add_numbered_bus(struct i2c_adapter *adap)
{
int err;
err = i2c_bit_prepare_bus(adap);
//加入adaoter类之前的一些操作,包括设定超时和重试,以及设定 i2c_algorithm的具体设定方法。
if (err)
return err;
return i2c_add_numbered_adapter(adap);
}
|
static int bit_xfer(struct i2c_adapter *i2c_adap,struct i2c_msg msgs[], int num)
static int sendbytes(struct i2c_adapter *i2c_adap, struct i2c_msg *msg)
static int sendbytes(struct i2c_adapter *i2c_adap, struct i2c_msg *msg)
{
while (count > 0) {
retval = i2c_outb(i2c_adap, *temp); //发送一个字节的数据
/* OK/ACK; or ignored NAK */
/*一个字节一个字节的往后移动*/
if ((retval > 0) || (nak_ok && (retval == 0))) {
count--;
temp++;
wrcount++;
}
/*参数:具体的适配器
需要传送的数据
数据数据
*/
static int bit_xfer(struct i2c_adapter *i2c_adap,struct i2c_msg msgs[], int num)
{
i2c_start(adap);
//启动总线
for (i = 0; i < num; i++) {
pmsg = &msgs[i];
nak_ok = pmsg->flags & I2C_M_IGNORE_NAK; //检测是否忽略响应
if (!(pmsg->flags & I2C_M_NOSTART)) {
if (i) {
i2c_repstart(adap);
//如果是混合模式,则重新启动传输
}
ret = bit_doAddress(i2c_adap, pmsg);
if ((ret != 0) && !nak_ok) {
//如果出错了,则出错处理
goto bailout;
}
}
if (pmsg->flags & I2C_M_RD) {
//收数据
} else {
//发送数据
/* write bytes from buffer */
ret = sendbytes(i2c_adap, pmsg);
if (ret >= 1)
if (ret < pmsg->len) {
if (ret >= 0)
ret = -EREMOTEIO;
goto bailout;
}
}
}
ret = i;
bailout:
bit_dbg(3, &i2c_adap->dev, "emitting stop condition\n");
i2c_stop(adap);
return ret;
}
7 总结
7.1 理清i2c中的个结构体关系
通过上面的讲解,已基本上简单地介绍完i2c驱动的方方面面,或许你还是对这里面的众多结构体之间的联系很迷惑,下面就来分析一下 i2c_driver 、 i2c_client 、 i2c_adapter 和 i2c_algorithm 这 4 个数据结构的作用及其盘根错节的关系。
(1)i2c_adapter 与 i2c_algorithm
i2c_adapter 对应于物理上的一个适配器,而 i2c_algorithm 对应一套通信方法。一个 I2C 适配器需要 i2c_algorithm 中提供的通信函数来控制适配器上产生特定的访问周期。缺少 i2c_algorithm 的 i2c_adapter 什么也做不了,因此 i2c_adapter 中包含其使用的 i2c_algorithm 的指针。
i2c_algorithm 中的关键函数 master_xfer() 用于产生 I2C 访问周期需要的信号,以 i2c_msg (即 I2C 消息)为单位。 i2c_msg 结构体也非常关键,代码清单给出了它的定义。
1 struct i2c_msg {
2 __u16 addr; /* 设备地址 */
3 __u16 flags; /* 标志 */
4 __u16 len; /* 消息长度 */
5 __u8 *buf; /* 消息数据 */
6 };
(2)i2c_driver 与 i2c_client
i2c_driver 对应一套驱动方法,是纯粹的用于辅助作用的数据结构,它不对应于任何的物理实体。 i2c_client 对应于真实的物理设备,每个 I2C 设备都需要一个 i2c_client 来描述。 i2c_client 一般被包含在 i2c 字符设备的私有信息结构体中。
i2c_driver 与 i2c_client 发生关联的时刻在 i2c_driver 的 attach_adapter() 函数被运行时。 attach_adapter() 会探测物理设备,当确定一个 client 存在时,把该 client 使用的 i2c_client 数据结构的 adapter 指针指向对应的 i2c_adapter 。
driver 指针指向该 i2c_driver ,并会调用 i2c_adapter 的 client_register() 函数。相反的过程发生在 i2c_driver 的 detach_client() 函数被调用的时候。
(3)i2c_adpater 与 i2c_client
i2c_adpater 与 i2c_client 的关系与 I2C 硬件体系中适配器和设备的关系一致,即 i2c_client 依附于 i2c_adpater 。由于一个适配器上可以连接多个 I2C 设备,所以一个 i2c_adpater 也可以被多个 i2c_client 依附, i2c_adpater 中包括依附于它的 i2c_client 的链表。
7.2 i2c驱动的编写建议
那么对于一个驱动工程师,如何编写自己的i2c相关的驱动,下面仅提供个参考方案:
(1)提供 I2C 适配器的硬件驱动,探测、初始化 I2C 适配器(如申请 I2C 的 I/O 地址和中断号)、驱动 CPU 控制的 I2C 适配器从硬件上产生各种信号以及处理 I2C 中断等。
(2)提供 I2C 适配器的 algorithm ,用具体适配器的 xxx_xfer() 函数填充 i2c_algorithm 的 master_xfer 指针,并把 i2c_algorithm 指针赋值给 i2c_adapter 的 algo 指针。
(3)实现 I2C 设备驱动与 i2c_driver 接口,用具体设备 yyy 的 yyy_attach_adapter() 函数指针、 yyy_detach_client() 函数指针和 yyy_command() 函数指针的赋值给 i2c_driver 的 attach_adapter 、 detach_adapter 和 detach_client 指针。
(4)实现 I2C 设备驱动的文件操作接口,即实现具体设备 yyy 的 yyy_read() 、 yyy_write() 和 yyy_ioctl() 函数等。
上述工作中 1 、 2 属于 I2C 总线驱动, 3 、 4 属于 I2C 设备驱动,做完这些工作,系统会增加两个内核模块。