4、I2C驱动核心结构:i2c_adapter/i2c_client结构体解析、i2c_msg消息传递机制、SMBus兼容层
好,咱们今天来啃I2C驱动里最核心的几个数据结构。说实话,我刚接触I2C子系统时,也被i2c_adapter、i2c_client、i2c_msg这几个东西绕得有点晕。后来调了一个触摸屏驱动,才真正把它们理清楚。
你想想看,I2C总线上有两类角色:一个是主控端(也就是SoC里的I2C控制器),另一个是从设备端(比如传感器、EEPROM)。在Linux内核里,主控端用i2c_adapter表示,从设备端用i2c_client表示。而它们之间怎么传数据?靠的就是i2c_msg消息。
4.1 i2c_adapter:I2C总线主控的“代言人”
i2c_adapter说白了就是I2C控制器的软件抽象。每个I2C总线控制器,在系统里都会对应一个i2c_adapter实例。我习惯把它理解成“总线管家”——它知道怎么发START信号、怎么发STOP信号、怎么控制时钟。
来看它的核心定义(我精简了,只留关键字段):
struct i2c_adapter {
struct module *owner;
unsigned int class; // 适配器支持的设备类型
const struct i2c_algorithm *algo; // 算法:收发数据的底层操作
struct device dev; // 内核设备模型
int nr; // 总线编号,比如i2c-0、i2c-1
struct list_head userspace_clients;
struct mutex bus_lock;
// ... 还有其他字段
};
这里最重要的就是algo指针,它指向i2c_algorithm结构体。这个结构体里定义了master_xfer()函数——这才是真正干活的地方。
关键点:你写I2C控制器驱动时,主要工作就是实现i2c_algorithm里的master_xfer()。内核通过这个函数,把i2c_msg数组交给你,你要负责在硬件上完成这些传输。
我遇到过一个问题:有个平台I2C控制器不支持重复START条件,但挂在上面的传感器偏偏需要复合消息(写寄存器地址+读数据)。当时我只能在master_xfer()里拆消息,先发写,再发读,中间加STOP再START。虽然能用,但效率低了不少。
4.2 i2c_client:从设备的“身份证”
每个挂在I2C总线上的从设备,内核都会给它分配一个i2c_client。你可以把它看作从设备的“身份证”——记录了设备地址、所挂接的适配器、驱动绑定信息等。
struct i2c_client {
unsigned short addr; // 7位或10位从机地址
char name[I2C_NAME_SIZE]; // 设备名称
struct i2c_adapter *adapter; // 指向所属的适配器
struct device dev; // 内核设备模型
int irq; // 中断号(如果有)
struct list_head detected; // 用于设备检测链表
// ...
};
i2c_client是怎么创建的?通常有两种方式:
- 板级静态注册:在设备树或板文件里声明,内核启动时自动创建。
- 动态检测:通过
i2c_new_device()或I2C总线探测机制生成。
我个人建议:能用设备树就别用动态检测。设备树清晰、可维护,而且不容易出现地址冲突。我之前在一个项目里偷懒用了动态检测,结果同一个地址被两个驱动抢着注册,调试了一下午才发现。
4.3 i2c_msg:消息传递的“基本单元”
I2C通信的本质,就是主控和从设备之间交换消息。每个消息用i2c_msg描述:
struct i2c_msg {
__u16 addr; // 从设备地址
__u16 flags; // 标志位:读/写、10位地址等
__u16 len; // 数据长度
__u8 *buf; // 数据缓冲区
};
标志位flags里最常用的几个:
| 标志 | 值 | 含义 |
|---|---|---|
I2C_M_RD |
0x0001 | 读消息(不设置则为写消息) |
I2C_M_TEN |
0x0010 | 使用10位地址 |
I2C_M_NOSTART |
0x4000 | 不发送START条件 |
I2C_M_REV_DIR_ADDR |
0x2000 | 反转读写方向 |
多个i2c_msg可以组成一个消息数组,通过一次master_xfer()调用完成。这就是所谓的“复合消息”或“组合传输”。内核保证这些消息在总线上是连续的——中间只发重复START,不发STOP。
实战技巧:读写EEPROM时,我习惯把“写寄存器地址”和“读数据”放在同一个i2c_msg数组里。这样能保证操作的原子性,不会被其他设备打断。
举个例子,读一个传感器寄存器:
struct i2c_msg msgs[2];
u8 reg_addr = 0x10;
u8 data;
// 消息0:写寄存器地址
msgs[0].addr = client->addr;
msgs[0].flags = 0; // 写操作
msgs[0].len = 1;
msgs[0].buf = ®_addr;
// 消息1:读数据
msgs[1].addr = client->addr;
msgs[1].flags = I2C_M_RD; // 读操作
msgs[1].len = 1;
msgs[1].buf = &data;
// 一次性传输
i2c_transfer(client->adapter, msgs, 2);
4.4 SMBus兼容层:让生活更简单
SMBus(System Management Bus)是I2C的一个子集,主要用在电源管理、温度传感器等场景。它定义了更严格的时序和协议规范。Linux内核提供了一套SMBus兼容API,让你不用直接操作i2c_msg。
常用的SMBus API:
i2c_smbus_read_byte():读一个字节i2c_smbus_write_byte():写一个字节i2c_smbus_read_byte_data():先写寄存器地址,再读一个字节i2c_smbus_write_byte_data():先写寄存器地址,再写一个字节i2c_smbus_read_word_data():读两个字节i2c_smbus_write_word_data():写两个字节i2c_smbus_read_block_data():读块数据i2c_smbus_write_block_data():写块数据
注意:SMBus API虽然方便,但有些I2C设备不严格遵循SMBus协议。比如某些设备要求时钟延展,或者不支持PEC(包错误校验)。遇到这种情况,老老实实用i2c_transfer()加i2c_msg自己拼。
我曾经在一个电池管理芯片上踩过坑。芯片手册说支持SMBus,我用i2c_smbus_read_word_data()读数据,结果死活不对。后来用逻辑分析仪抓波形才发现,芯片要求读操作前必须发一个“命令码”,而SMBus的read_word协议默认不发这个。最后改用i2c_msg手动拼消息才搞定。
4.5 知识结构总览
下面这张图把I2C驱动的核心结构串起来了:
4.6 核心API调用链
驱动开发中,你经常用到的API调用链是这样的:
- 设备驱动层:调用
i2c_smbus_read_byte_data()或i2c_transfer() - 核心层:
i2c_transfer()→__i2c_transfer()→ 加锁 → 调用adapter->algo->master_xfer() - 控制器驱动层:
master_xfer()解析i2c_msg数组,操作硬件寄存器完成时序
记住:你写从设备驱动时,只用i2c_client和i2c_msg(或SMBus API)。你写控制器驱动时,才需要实现i2c_adapter和master_xfer()。分工很明确。
嗯,到这里I2C的核心结构就讲完了。这几个结构体你写驱动时会反复打交道,尤其是i2c_msg的flags处理,容易出错。建议你实际调试时,配合逻辑分析仪看波形,能帮你快速定位问题。