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_algorithmi2c_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 | Utilities--> |
用法
i2cdetect:
1
2
3
4
5Usage: 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
11Usage: 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 PECi2cget:
1
2
3
4
5
6
7
8Usage: 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 PECi2cset:
1
2
3
4
5
6
7
8
9
10Usage: 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 设备驱动开发》