0%

Linux驱动之IIC子系统

IIC协议

写操作

流程如下:

  • 主芯片要发出一个start信号
  • 然后发出一个设备地址(用来确定是往哪一个芯片写数据),方向(读/写,0表示写,1表示读)
  • 从设备回应(用来确定这个设备是否存在),然后就可以传输数据
  • 主设备发送一个字节数据给从设备,并等待回应
  • 每传输一字节数据,接收方要有一个回应信号(确定数据是否接受完成),然后再传输下一个数据
  • 数据发送完之后,主芯片就会发送一个停止信号

下图:白色背景表示"主→从",灰色背景表示"从→主"

读操作

流程如下:

  • 主芯片要发出一个start信号
  • 然后发出一个设备地址(用来确定是往哪一个芯片写数据),方向(读/写,0表示写,1表示读)
  • 从设备回应(用来确定这个设备是否存在),然后就可以传输数据
  • 从设备发送一个字节数据给主设备,并等待回应
  • 每传输一字节数据,接收方要有一个回应信号(确定数据是否接受完成),然后再传输下一个数据
  • 数据发送完之后,主芯片就会发送一个停止信号

下图:白色背景表示"主→从",灰色背景表示"从→主"

IIC信号

IIC协议中数据传输的单位是字节,也就是8位。但是要用到9个时钟:前面8个时钟用来传输8数据,第9个时钟用来传输回应信号。传输时,先传输最高位(MSB)。

  • 开始信号(S):SCL为高电平时,SDA从高电平向低电平跳变,开始传送数据
  • 结束信号(P):SCL为高电平时,SDA由低电平向高电平跳变,结束传送数据
  • 响应信号(ACK):接收器在接收到8位数据后,在第9个时钟周期,拉低SDA
  • SDA上传输的数据必须在SCL为高电平期间保持稳定,SDA上的数据只能在SCL为低电平期间变化

IIC协议信号如下:

硬件实现

scl和sda管脚都采用开漏输出的驱动方式,需要外接上拉电阻来输出高电平。

真值表如下:

从真值表和电路图我们可以知道:

  • 当某一个芯片不想影响SDA线时,那就不驱动这个三极管
  • 想让SDA输出高电平,双方都不驱动三极管(SDA通过上拉电阻变为高电平)
  • 想让SDA输出低电平,就驱动三极管

硬件拓扑

soc中的iic控制器与挂载在iic总线上的设备的拓扑图如下:

SMBus协议

SMBus是基于IIC协议的,SMBus要求更严格,SMBus是IIC协议的子集,做了相关限制,例如VDD的极限值不一样、地址回应(Address Acknowledge)、时钟频率等都有相关限制,这里不做过多说明。

数据结构

首先通过一张图来展示这些数据结构之间的关系,如下图:原图

  • bus_type
    iic是一个总线设备,linux启动阶段会创建一个iic总线i2c_bus_type,该总线用于管理i2c_client设备和i2c_driver驱动。该结构跟linux驱动之设备模型里的总线相同,这里实现了它自己的match和probe函数

  • i2c_adapter
    每个soc内部都有自己的多个iic控制器,i2c_adapter结构体就是用于描述iic控制器的结构体(就是iic controler),结构体里的nr用于表述该控制器是第几个控制器例如iic-0,iic-1等。这里面有一个最重要的结构就是i2c_algorithm,该结构是具体controler实际iic传输的实现,由具体的平台实现自己的i2c_algorithm

  • i2c_client
    一个iic下会挂载一个或多个设备,每个设备的信息由i2c_client结构体描述,例如设备地址等信息。该结构体还有一个指针指向i2c_adapter,表示该设备挂载在哪个iic controler下以便在iic通信的时候通过i2c_adapter提供的i2c_algorithm提供的接口完成实际的数据传输

  • i2c_driver
    iic下挂载的设备例如tp、温湿度传感器等设备的驱动由该数据结构来描述,这个是我们实际编写驱动中最常用的,通过iic驱动具体的挂载在iic总线上的设备。i2c_driver跟i2c_client匹配成功后,就调用i2c_driver.probe函数

  • i2c_msg
    有了iic设备和iic驱动了,iic是通信总线,那就是要用来传输消息的,还有一个描述具体消息的结构体就是i2c_msg,该结构体描述了消息方向,传输地址,消息长度以及消息本身内容

可以看到我们的iic驱动也是使用之前描述的设备驱动模型,bus下挂载链条结构一个挂载设备一个挂载驱动通过总线提供的match函数进行匹配实现device找到device_driver或则device_driver找到要驱动的device,然后在match成功后通过总线的probe函数调用到驱动的probe函数,实际驱动开发人员只需要填充i2c_driver结构体并将其注册到总线就可以了。区别在于iic总线是一个具体实实在在的总线,soc内部有iic控制器,通过i2c_adapter描述提供统一的接口,i2c_driver通过i2c_adapter提供的iic传输函数来操作i2c_client设备,这就是iic总线驱动的整体框架了。

流程分析

i2c_register_adapter函数流程如下:原图

这里面有一个函数i2c_detect需要注意,该函数会遍历总线下的每一个i2c_driver并调用i2c_driver的detect函数,具体的i2c_driver的detect函数由具体的驱动实现。

i2c_register_driver函数流程如下:原图

i2c_driver同adapt的注册有一些相同的流程,其中detect函数就是其中之一,注册i2c_driver的时候也会遍历bus下的i2c_driver并调用相应的detect函数。

参考文献 :

http://www.wowotech.net/sort/comm
https://www.cnblogs.com/lknlfy/p/3265108.html
https://www.cxybb.com/article/rockrockwu/7435817
《Linux设备驱动开发》