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 函数。

I2C tools

选择工具 i2c_tools

1
2
Utilities-->
i2c-tools-->

用法

  • i2cdetect:

    1
    2
    3
    4
    5
    Usage: i2cdetect [-y] [-a] [-q|-r] I2CBUS [FIRST LAST]
    i2cdetect -F I2CBUS
    i2cdetect -l
    I2CBUS is an integer or an I2C bus name
    If provided, FIRST and LAST limit the probing range.

  • i2cdump:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    Usage: i2cdump [-f] [-y] [-r first-last] I2CBUS ADDRESS [MODE [BANK [BANKREG]]]
    I2CBUS is an integer or an I2C bus name
    ADDRESS is an integer (0x03 - 0x77)
    MODE is one of:
    b (byte, default)
    w (word)
    W (word on even register addresses)
    s (SMBus block)
    i (I2C block)
    c (consecutive byte)
    Append p for SMBus PEC

  • i2cget:

    1
    2
    3
    4
    5
    6
    7
    8
    Usage: i2cget [-f] [-y] I2CBUS CHIP-ADDRESS [DATA-ADDRESS [MODE]]
    I2CBUS is an integer or an I2C bus name
    ADDRESS is an integer (0x03 - 0x77)
    MODE is one of:
    b (read byte data, default)
    w (read word data)
    c (write byte/read byte)
    Append p for SMBus PEC

  • i2cset:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    Usage: i2cset [-f] [-y] [-m MASK] [-r] I2CBUS CHIP-ADDRESS DATA-ADDRESS [VALUE] ... [MODE]
    I2CBUS is an integer or an I2C bus name
    ADDRESS is an integer (0x03 - 0x77)
    MODE is one of:
    c (byte, no value)
    b (byte data, default)
    w (word data)
    i (I2C block data)
    s (SMBus block data)
    Append p for SMBus PEC

参考文献:

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