2. Linux I2C 子系统架构:三层模型与数据结构关系
Linux I2C 子系统采用经典的三层分离架构,将协议实现、硬件适配与设备逻辑解耦。理解这三层模型及其核心数据结构,是进行 I2C 设备驱动开发与调试的基础。
2.1 三层模型概述
I2C 子系统从下到上分为:I2C 核心层、I2C 总线驱动(适配器驱动)、I2C 设备驱动。每一层职责清晰,通过标准接口交互。
| 层次 | 职责 | 核心数据结构 | 典型文件 |
|---|---|---|---|
| I2C 核心层 | 提供通用协议处理、设备注册/注销、总线管理、sysfs 接口 | i2c_adapter、i2c_client、i2c_driver |
drivers/i2c/i2c-core.c |
| I2C 总线驱动 | 直接操作 I2C 控制器硬件,实现主机传输时序(SDA/SCL) | i2c_algorithm、i2c_adapter |
drivers/i2c/busses/i2c-xxx.c |
| I2C 设备驱动 | 绑定具体 I2C 从设备,实现设备功能逻辑(如触摸屏、传感器) | i2c_driver、i2c_client |
drivers/input/touchscreen/xxx.c |
调试要点: 在调试显示驱动中的 I2C 通信时,通常需要同时关注总线驱动层(确认硬件时序正确)和设备驱动层(确认寄存器读写逻辑)。核心层一般不需要修改,但可以通过
i2cdetect、i2cget、i2cset 等用户态工具验证。
2.2 核心数据结构关系图
下图展示了 I2C 子系统中四个关键数据结构之间的关联关系:
2.3 各层核心数据结构详解
2.3.1 I2C 核心层:i2c_adapter 与 i2c_client
i2c_adapter 代表一个 I2C 控制器(主机),i2c_client 代表一个挂载在该总线上的从设备。核心层维护这两者的注册与生命周期。
// i2c_adapter 关键字段(include/linux/i2c.h)
struct i2c_adapter {
struct module *owner;
unsigned int class; // 设备类,用于自动探测
const struct i2c_algorithm *algo; // 算法实现
struct device dev; // 内核设备模型
struct list_head clients; // 挂接的 client 链表
int nr; // 总线编号(如 i2c-0)
char name[48]; // 适配器名称
};
// i2c_client 关键字段
struct i2c_client {
unsigned short addr; // 7 位从设备地址
char name[I2C_NAME_SIZE]; // 设备名称
struct i2c_adapter *adapter; // 所属适配器
struct device dev; // 内核设备模型
struct i2c_driver *driver; // 绑定的驱动
};
调试技巧: 在
/sys/bus/i2c/devices/ 目录下可以查看所有注册的 I2C 设备。例如 i2c-0/0-0050 表示总线 0 上地址为 0x50 的设备。通过 cat /sys/bus/i2c/devices/i2c-0/name 可查看适配器名称。
2.3.2 I2C 总线驱动:i2c_algorithm
总线驱动必须实现 i2c_algorithm 中的 master_xfer 函数,该函数负责在硬件上执行实际的 I2C 传输时序。
struct i2c_algorithm {
// 标准 I2C 传输:发送起始条件、地址、数据、停止条件
int (*master_xfer)(struct i2c_adapter *adap,
struct i2c_msg *msgs,
int num);
// SMBus 兼容传输(可选)
int (*smbus_xfer)(struct i2c_adapter *adap, u16 addr,
unsigned short flags, char read_write,
u8 command, int size,
union i2c_smbus_data *data);
u32 functionality; // 功能掩码
};
2.3.3 I2C 设备驱动:i2c_driver
设备驱动通过 i2c_driver 结构体向核心层注册,核心层在匹配到对应 i2c_client 后调用 probe 函数。
struct i2c_driver {
int (*probe)(struct i2c_client *client, const struct i2c_device_id *id);
void (*remove)(struct i2c_client *client);
struct device_driver driver; // 内核驱动模型
const struct i2c_device_id *id_table; // 传统 ID 匹配表
const struct of_device_id *of_match_table; // 设备树匹配表
};
2.4 设备注册流程与匹配机制
I2C 设备驱动的注册与匹配遵循以下步骤:
- 总线驱动注册: 调用
i2c_add_adapter()或i2c_add_numbered_adapter()注册适配器,核心层为其分配总线编号并创建/dev/i2c-N设备节点。 - 设备信息注册: 设备信息可以通过三种方式提供:
- 设备树(DT): 在 DTS 文件中描述 I2C 从设备,内核解析后自动创建
i2c_client。 - ACPI: 在 ACPI 表中声明 I2C 设备,主要用于 x86 平台。
- 板级代码: 通过
i2c_board_info结构体静态注册,常用于旧版内核。
- 设备树(DT): 在 DTS 文件中描述 I2C 从设备,内核解析后自动创建
- 设备驱动注册: 调用
i2c_add_driver()注册驱动,核心层将其加入总线驱动链表。 - 匹配与 probe: 核心层遍历所有
i2c_client,使用id_table或of_match_table匹配驱动,匹配成功则调用probe()。
常见调试陷阱: 在显示驱动中,如果 I2C 触摸屏或显示控制芯片无法 probe,请优先检查:
1. 设备树中 I2C 地址是否正确(7 位地址,左对齐)。
2. 总线驱动是否已注册(
3. 驱动
1. 设备树中 I2C 地址是否正确(7 位地址,左对齐)。
2. 总线驱动是否已注册(
i2cdetect -l 查看)。3. 驱动
of_match_table 中的 compatible 字符串是否与 DTS 完全一致。
2.5 三层模型交互流程图
下图展示了从用户态到硬件寄存器的完整 I2C 通信路径:
2.6 关键数据结构对比表
| 数据结构 | 所属层次 | 创建时机 | 销毁时机 | 典型数量 |
|---|---|---|---|---|
i2c_adapter |
核心层 / 总线驱动 | 总线驱动 probe | 总线驱动 remove | 每个 I2C 控制器 1 个 |
i2c_algorithm |
总线驱动 | 静态定义 | 随 adapter 销毁 | 每个 adapter 1 个 |
i2c_client |
核心层 | 设备信息注册时 | 设备移除或驱动卸载 | 每个从设备 1 个 |
i2c_driver |
设备驱动 | 模块加载时 | 模块卸载时 | 每个设备驱动 1 个 |
显示驱动场景提示: 在 HDMI 或 DP 显示驱动中,DDC 通道通常使用 I2C 总线(地址 0x50 读取 EDID)。此时
i2c_adapter 由 GPU 或显示控制器驱动注册,i2c_client 由 DRM 核心或 EDID 读取逻辑动态创建,无需单独编写设备驱动。