4. Linux I2C核心层架构
I2C 核心层(I2C Core)是 Linux 内核中 I2C 子系统的心脏,它向上为 I2C 设备驱动提供统一的注册与通信接口,向下通过 i2c_adapter 抽象物理控制器,并通过 i2c_algorithm 封装具体的硬件收发方法。同时,i2c_client 代表挂接在总线上的每一个从设备。理解这三个数据结构之间的协作关系,是掌握 I2C 驱动框架的关键。
核心关系速览: i2c_adapter 是“适配器”(控制器),它内部持有 i2c_algorithm(算法)来执行实际的 SCL/SDA 时序;i2c_client 是“客户端”(从设备),它绑定到某个 adapter 上,并通过 adapter 的 algorithm 与硬件通信。
4.1 i2c_adapter:I2C 控制器抽象
i2c_adapter 是 I2C 核心层对物理 I2C 控制器的软件抽象。每个 I2C 总线控制器(如 SoC 内部的 I2C 外设、GPIO 模拟的 I2C 总线)在内核中都对应一个 i2c_adapter 实例。它负责管理总线编号、设备链表、锁机制以及底层的通信算法。
| 成员 | 类型 | 描述 |
|---|---|---|
nr |
int | 总线编号,用于在系统中唯一标识该 I2C 总线 |
name |
char[48] | 适配器名称,通常与硬件平台设备名对应 |
algo |
struct i2c_algorithm * | 指向该适配器使用的通信算法结构体 |
algo_data |
void * | 算法私有数据,通常指向控制器寄存器基址或平台数据 |
dev |
struct device | 内核设备模型中的 device 对象,用于挂接到驱动模型 |
userspace_clients |
struct list_head | 用户空间创建的 client 链表头 |
bus_lock |
struct mutex | 总线访问互斥锁,防止并发访问 |
timeout |
int | 通信超时时间(单位 jiffies) |
retries |
int | 通信失败时的重试次数 |
编程要点: 在注册 adapter 时,核心层会自动分配总线编号(
nr),开发者也可以指定静态编号。adapter 注册成功后,系统会在 /sys/bus/i2c/devices/ 下创建对应的目录,例如 i2c-0、i2c-1。
4.2 i2c_algorithm:硬件通信方法集
i2c_algorithm 是 I2C 核心层与底层硬件之间的“契约”。它定义了一组函数指针,用于实现 I2C 总线的标准通信协议。不同的硬件平台(如 IMX、STM32、GPIO 模拟)通过实现这些回调函数来适配 I2C 核心层。
struct i2c_algorithm {
/* 主模式发送/接收:发送一个 i2c_msg 数组 */
int (*master_xfer)(struct i2c_adapter *adap,
struct i2c_msg *msgs,
int num);
/* 从模式发送/接收(较少使用) */
int (*slave_xfer)(struct i2c_adapter *adap,
struct i2c_msg *msgs,
int num);
/* 查询适配器功能(如是否支持 10 位地址、SMBus 等) */
u32 (*functionality)(struct i2c_adapter *adap);
/* 注册从设备回调(用于从模式) */
int (*reg_slave)(struct i2c_adapter *adap,
struct i2c_client *client);
int (*unreg_slave)(struct i2c_adapter *adap,
struct i2c_client *client);
};
| 回调函数 | 作用 | 必须实现? |
|---|---|---|
master_xfer |
核心函数,执行一次主设备传输(包含起始、数据、停止条件) | 是 |
functionality |
返回适配器支持的能力掩码,如 I2C_FUNC_I2C、I2C_FUNC_SMBUS_EMUL | 是 |
slave_xfer |
从模式传输,用于适配器作为从设备时 | 否 |
reg_slave / unreg_slave |
注册/注销从设备,用于支持从模式的适配器 | 否 |
注意:
master_xfer 是 I2C 适配器驱动必须实现的核心回调。该函数接收一个 i2c_msg 数组,每个 msg 包含从设备地址、读写标志、数据缓冲区及长度。驱动需要按照 I2C 协议在硬件上产生对应的 SCL/SDA 时序。
4.3 i2c_client:I2C 从设备抽象
i2c_client 代表一个挂接在 I2C 总线上的从设备(如温度传感器、加速度计、EEPROM)。每个 client 都绑定到一个特定的 i2c_adapter 和一个 7 位或 10 位从设备地址。设备驱动通过 client 结构体与硬件进行数据交换。
struct i2c_client {
unsigned short addr; /* 7 位从设备地址(低 7 位有效) */
unsigned short flags; /* 标志位:I2C_CLIENT_TEN(10 位地址)、
I2C_CLIENT_SLAVE(从模式)等 */
char name[I2C_NAME_SIZE]; /* 设备名称,用于匹配驱动 */
struct i2c_adapter *adapter; /* 指向所属的适配器 */
struct device dev; /* 内核设备模型 device 对象 */
int irq; /* 从设备的中断号(可选) */
struct list_head detected; /* 用于挂接到 adapter 的 detected 链表 */
};
| 成员 | 说明 |
|---|---|
addr |
从设备地址,7 位地址存储在低 7 位,10 位地址需配合 I2C_CLIENT_TEN 标志 |
flags |
控制地址模式、读写方向、从模式等属性 |
adapter |
反向指针,指向该设备所连接的 I2C 总线适配器 |
dev |
嵌入的 device 结构,用于驱动模型匹配(vendor/device ID) |
irq |
如果从设备支持中断,则在此记录中断号 |
关键设计: client 的
adapter 指针将设备驱动与控制器驱动连接起来。当设备驱动调用 i2c_transfer() 时,核心层通过 client->adapter->algo->master_xfer() 最终调用到具体的硬件发送函数。
4.4 三大数据结构协作关系图
下面的 SVG 图展示了 i2c_adapter、i2c_algorithm 和 i2c_client 之间的静态组合关系与动态调用路径。
4.5 数据结构初始化与注册流程
在实际的 I2C 控制器驱动中,这三个数据结构的初始化与注册遵循固定的步骤。下面以平台驱动(platform driver)为例,展示典型的初始化流程:
- 分配并初始化 i2c_adapter: 设置
nr、name、algo、algo_data、timeout、retries等字段。 - 实现 i2c_algorithm: 编写
master_xfer()和functionality()回调函数,操作硬件寄存器完成时序。 - 注册 adapter: 调用
i2c_add_adapter()或i2c_add_numbered_adapter()将 adapter 注册到核心层。 - 创建设备节点: 核心层根据设备树(Device Tree)或板级代码中的 I2C 设备信息,自动创建
i2c_client实例。 - 驱动匹配: 当 client 创建后,核心层遍历已注册的 I2C 设备驱动,通过
i2c_device_id或设备树 compatible 属性进行匹配。
/* 典型的 I2C 适配器驱动 probe 函数片段 */
static int my_i2c_probe(struct platform_device *pdev)
{
struct i2c_adapter *adap;
struct i2c_algorithm *algo;
adap = devm_kzalloc(&pdev->dev, sizeof(*adap), GFP_KERNEL);
algo = devm_kzalloc(&pdev->dev, sizeof(*algo), GFP_KERNEL);
adap->nr = pdev->id; /* 使用平台设备 ID 作为总线编号 */
adap->name = "my_i2c_adapter";
adap->algo = algo;
adap->algo_data = platform_get_drvdata(pdev);
adap->timeout = HZ; /* 1 秒超时 */
adap->retries = 3;
algo->master_xfer = my_i2c_master_xfer; /* 硬件发送函数 */
algo->functionality = my_i2c_functionality;
return i2c_add_numbered_adapter(adap); /* 注册到核心层 */
}
调试技巧: 注册成功后,可以在
/sys/bus/i2c/devices/ 下看到 i2c-N 目录。通过 i2cdetect -l 命令可以列出所有已注册的 I2C 适配器及其编号。
4.6 数据结构对比总结
| 数据结构 | 角色 | 核心成员 | 生命周期 |
|---|---|---|---|
| i2c_adapter | I2C 总线控制器抽象 | nr, algo, algo_data, dev, bus_lock | 与硬件控制器共存亡 |
| i2c_algorithm | 硬件通信方法集 | master_xfer, functionality | 通常与 adapter 同时创建 |
| i2c_client | I2C 从设备抽象 | addr, adapter, dev, irq | 设备探测时创建,移除时销毁 |
常见错误: 在实现
master_xfer() 时,必须正确处理 i2c_msg 数组中的 flags 字段(尤其是 I2C_M_RD 读标志)。如果忽略该标志,会导致读写方向错误,造成设备无响应或数据错乱。