I2C设备驱动详述

来源:互联网 发布:淘宝直播安卓版可以吗 编辑:程序博客网 时间:2024/05/06 23:02

         I2C驱动需要把握两大部分内容,一个是理解linux的I2C设备驱动模型,另一个是操作I2C控制器的方法,后者还需要理解I2C硬件逻辑。但首先要理解I2C及其在linux中到底是怎么一回事:

一、I2C是怎么一回事:

CPU目前一般都集成I2C接口也就是I2C控制器,通过控制它可以产生I2C相关的电路信号诸如起始、收发、终止,要理解I2C本身到底是怎么一回事,推荐文档http://wenku.baidu.com/link?url=OtaTChBtIiIEKVcGRdTvS-nYqBpv3Qe922icGzA1k9wShriHZ6sOpIipIfUMLz4UfrlmPjqeFHAs40LwRAtYByQQvkEVMjBPenRUseQHwAG,

1.1、I2C总线协议(物理):


I2C是一种串行总线的外设接口,它采用同步方式串行接收或发送信息,两个设备在同一个时钟下工作。I2C总线只用两根线:串行数据SDASerial Data)、串行时钟SCLSerial Clock)。

I2C只有一根数据线,因此其发送信息和接收信息不能同时进行。信息的发送和接收只能分时进行。I2C串行总线工作时传输速率最高可达400K bit/s

I2C总线上的所有器件的SDA线并接在一起,所有器件的SCL线并接在一起,且SDA线和SCL线必须通过上拉电阻连接到正电源。

I2C总线器件没有片选控制线,所以I2C总线数据传输的开始必须由主器件产生通信的开始条件(SCL高电平时,SDA产生下降沿);通信结束时,由主器件产生通信的结束条件(SCL高电平时,SDA产生上升沿)

读写操作中SDA线上数据传送状态标记注释如下:

Start为启动信号(SCL为高电平,SDA产生负跳变),由主机发送。

Stop为结束信号(SCL为高电平,SDA产生正跳变),由主机发送。

AddreeeByte HAddreeeByte L为地址字节,指定哪一个片及该片的片内某单元地址,由主机发送。

data为数据字节,由数据发送方发送。

主机写操作期间,用SCL的上升沿写入数据;主机读操作期间,用SCL的下降沿读出数据

二、I2C驱动模型:

2.1、宏观模型图:

 

上面是从一个整体角度描述了linux的I2C设备驱动模型,下面从具体代码和sysfs角度描述:

首先看下需要驱动编写者实现的platform总线的设备和驱动,在sysfs中的体现:

2.2、Platform设备:

Platform设备在代码中的作用是记录I2C控制器的硬件参数,如下:


在sysfs中体现如下:


2.3、platform驱动:

platform驱动在代码中的作用是在其probe方法获取到platform设备的参数,初始化I2C控制器硬件参数和软件机制,并在I2C总线下注册一个I2C适配器设备,以被内核默认创建的I2C总线驱动dev_driver匹配,如下:


下面是probe方法的最后一步,所有platform驱动实现的probe函数都是如此,调用i2c_add_numbered_adapter或函数i2c_add_adapter在I2C总线下创建I2C适配器设备,等待被内核默认创建的I2C总线驱动dev_driver匹配,如下:


在sysfs中体现如下:


2.4、i2c适配器设备:

创建i2c适配器设备,无论函数i2c_add_numbered_adapter或函数i2c_add_adapter都会调用函数i2c_register_adapter,本质就是在i2c总线下创建设备(device_register),然后匹配内核默认创建的I2C总线驱动dev_driver,如下:



在sysfs中的体现如下:


可见在i2c总线的设备目录中创建了I2C适配器设备i2c-0,注意它的父设备是platform下的设备,但所属总线是i2c总线;

2.5、i2c适配器驱动:

在driver/i2c/i2c-dev.c文件中,内核会默认创建i2c总线驱动dev_driver,在i2c_dev_init函数中加入,如下:


I2c_add_driver函数会调用i2c_register_driver,它和i2c_register_adapter类似,在i2c总线中加入驱动(调用函数driver_register),并且匹配i2c总线下的设备,如下:



在sysfs中体现如下:


2.6、i2c总线下设备和驱动的匹配:

不论是驱动匹配设备还是设备匹配驱动,都是调用函数i2cdev_attach_adapter,如下:


函数get_free_i2c_dev最大的作用是把这个i2c适配器设备挂在i2c_dev,再把i2c_dev记录在系统中,确切的说是挂在链表i2c_dev_list中,注意这里的重点是每个i2c_dev对应一个i2c适配器设备,如下:



这样以后某个用户进程访问时,可以把所需访问的i2c适配器设备,细节如下:

2.7、用户进程访问:

在driver/i2c/i2c-dev.c文件中,module_init函数i2c_dev_init会在devfs中创建字符设备i2c,用户进程操作i2c适配器设备的也是通过访问该字符设备实现,如下:


当用户进程对该设备进行open操作时,即“open("/dev/i2c-0",O_RDWR))”时,该字符设备的open方法将根据i2c-%d的%d是多少,确定是访问哪个i2c_dev也就是哪个i2c适配器设备,并记录在该进程中,如下:

首先由%d确定是哪个i2c_dev即哪个i2c适配器设备:



进而记录在该进程中,标识该用户进程的访问情况,这样这个用户进程再做read、write、ioctl操作时,就知道是访问的是这个i2c适配器设备,如下:


比如read操作,vfs会给i2c-0字符设备的read方法传入上次记录好的client,它里边记录了在open操作中记录的i2c适配器(见上图),调用函数i2c_master_recv,函数i2c_master_recv中会由client解析出它记录的i2c适配器设备,再调用函数i2c_transfer,进而再调用“i2c算法”的master_xfer成员,如下:



对于write/ioctl操作,道理和read一样。

三、i2c适配器操作细节:

这部分描述的是本文第一部分的具体实现细节,以一个已经稳定的实际使用的示例描述,所有的i2c操作的原理都是一样的,看懂一个其他都能看懂:

3.1、用户进程open时一般都干什么:

用户进程调用如下:

if ((i2cdevfd = open("/dev/i2c-0", O_RDWR)) < 0)

{

printf("i2c_init: open i2c device error!\r\n");

return   RES_ERR;

}

Vfs会调用内核默认创建的i2c总线驱动dev_driver的open方法i2cdev_open,根据i2c-%d的%d(如i2c-0,则为0)确定是哪个i2c_dev即哪个i2c适配器设备,并记录在该进程中(2.7已描述);

3.2、用户进程然后有可能做什么:

用户进程然后一般都是调用如下:

if (ioctl(i2cdevfd,I2C_TENBIT,0)< 0)

{

printf("i2c_init: ioctl I2C_TENBITerror!\r\n");

return   RES_ERR;

}

对应内核的操作如下:

case I2C_TENBIT:

if (arg)

                  client->flags|= I2C_M_TEN;

         else

                  client->flags &= ~I2C_M_TEN;

         return 0;

即在该进程中去除I2C_M_TEN标志位,它决定是否考虑片内偏移地址,只作用于某些驱动软件实现(i2c适配器具体操作),不要特别关注;

3.3、用户进程读操作:

3.3.1、主机片选:

首先需要进行片选,I2C总线在物理上只有两根线,并没有片选线,所有i2c从设备都是挂在总线的两根线上,片选需要i2c总线主机发送地址,用户进程调用如下:


对应内核实现如下:


可见并未访问硬件,而是记录在该进程中

3.3.2、主机写入片内偏移地址:

用户进程调用write,将要访问的片内偏移地址发出,如下:


内核态的write方法的调用流程为i2c_master_send-à i2c_transfer-ài2c适配器设备的“算法”的master_xfer,驱动编写者须区分读写操作(读写标志由内核在i2c_transfer函数中设置),进而依次进行:1、令i2c控制器产生起始条件信号;2、写入片选地址,即3.3.1中仅记录在用户进程中尚未写入硬件的片选地址;3、写入片内偏移地址;

对于不同的i2c控制器,上面依次进行的三个操作的实现不一样,这依赖于具体i2c芯片实现,列出代码无意义,但道理都是一样的,必须理解。

3.3.3、主机读取数据:

在经过必要延时后(事实上往往不需要用户进程延时,而应是通过访问i2c芯片寄存器),主机从i2c总线读取数据,用户态如下:


内核态的read方法的调用流程为i2c_master_recv-à i2c_transfer-ài2c适配器设备的“算法”的master_xfer,进而进行读取相关数据寄存器,注意往往是一字节一字节的读取;全部读取完成后,写入停止信号。

3.4、用户进程写操作:

3.4.1、主机片选:

同3.3.1;

3.4.2、主机写入偏移地址、发送长度、发送内容:

用户进程把偏移地址、发送长度作为整个发送buffer的前两个字节,后面是实际发送的内容,告诉驱动的发送长度要包括发送长度这个字节,比如实际发送内容的长度为a个字节,那么告诉驱动的发送长度就是a+1个字节,即下面代码中write系统调用中之所以长度参数为num + 1,如下:


可见,第一字节是片内偏移地址,第二字节是发送长度(要包括长度这个字节本身所以要num + 1),后面是实际发送的内容;

对于内核的实现,依次进行产生起始条件、写入片选地址、写入偏移地址、写入长度、逐字节写入实际发送内容,全部写入后,写入停止信号。

4、操作细节简述:

虽然不同i2c芯片的驱动适配器具体实现不同,但i2c访问方式是共同的,如下:

1、             令i2c控制器发送起始条件后,i2c控制器的状态寄存器应为0x08或0x10意为起始条件已发送或再次起始条件已发送;返回0x00、0x70、0x90为出错;

2、             写操作时,写入片选地址后,i2c控制器的状态寄存器应为0x18或0x20,意为片选地址发送成功(区别只是是否收到i2c器件的回复);

3、             写操作时,写入每字节的数据后,i2c控制器的状态寄存器应为0x28或0x30,意为该字节发送成功(区别只是是否收到i2c器件的回复),收到0x38或其他值为出错;

4、             读操作时,写入片选地址后,i2c控制器的状态寄存器应为0x48或0x40,意为片选地址发送成功(区别只是是否收到i2c器件的回复);

5、             读操作时,每读取一个字节后,i2c控制器的状态寄存器应为 0x50或0x58,意为该字节读取成功(区别只是是否收到i2c器件的回复),返回0x38或其他值为出错;

6、             发送停止信号后,i2c控制器的状态寄存器应为 0xf8,意为停止信号发送成功,返回其他值为失败;

具体的i2c状态码可参加下表:

0x08          Startcondition transmitted.

0x10          Repeatedstart condition transmitted.

0x18          Address+ write bit transmitted, acknowledge received.

0x20           Address+ write bit transmitted, acknowledge not received.

0x28          Mastertransmitted data byte, acknowledge received.

0x30          Mastertransmitted data byte, acknowledge not received.

0x38          Master lost arbitration during addressor data transfer.

0x40        Address + read bit transmitted,acknowledge received.

0x48          Address+ read bit transmitted, acknowledge not received.

0x50          Masterreceived read data, acknowledge transmitted.

0x58          Masterreceived read data, acknowledge not transmitted.

0x60          Slave received slave address,acknowledge transmitted.

0x68          Master lost arbitration during addresstransmit, address is targeted to the slave

(write access), acknowledge transmitted.

0x70          General call received, acknowledge transmitted.

0x78          Master lost arbitration during address transmit, generalcall address received,

acknowledge transmitted.

0x80          Slave received write data after receiving slave address,acknowledge transmitted.

0x88          Slave received write data after receiving slave address,acknowledge not transmitted.

0x90          Slave received write data after receiving general call,acknowledge transmitted.

0x98          Slave received write data after receiving general call,acknowledge not transmitted.

0xA0         Slave received stop or repeatedstart condition.

0xA8          Slave received address + read bit, acknowledge transmitted.

0xB0          Master lost arbitration during address transmit, address istargeted to the slave (read

access), acknowledge transmitted.

0xB8          Slave transmitted read data, acknowledge received.

0xC0         Slave transmitted read data, acknowledge not received.

0xC8         Slave transmitted last read byte, acknowledge received.

0xD0         Second address + write bit transmitted, acknowledge received.

0xD8         Second address + write bit transmitted, acknowledge notreceived.

0xE0          Second address + read bit transmitted, acknowledge received.

0xE8         Second address + readbit transmitted, acknowledge not received.

0xF8 No relevantstatus. Interrupt flag is kept 0.

0 0
原创粉丝点击