3. I2C 核心数据结构:struct i2c_adapter、struct i2c_algorithm、struct i2c_client、struct i2c_driver
在 Linux I2C 子系统中,四个核心数据结构构成了设备模型与通信的骨架。理解它们之间的关系,是编写显示驱动 I2C 调试代码的前提。本章将逐一剖析每个结构体的职责、关键成员以及它们在驱动注册流程中的协作方式。
3.1 整体架构关系图
下面的 SVG 图展示了四个核心结构体在 I2C 总线系统中的分层关系与数据流向:
3.2 struct i2c_adapter —— I2C 总线适配器
struct i2c_adapter 代表一个物理的 I2C 控制器实例。在显示驱动中,它通常对应 SoC 内部的一个 I2C 外设(如 i2c-0、i2c-1),负责管理总线时钟、仲裁和消息传输。
关键成员
| 成员 | 类型 | 说明 |
|---|---|---|
owner | struct module * | 模块引用计数,防止驱动被卸载时仍在使用 |
class | unsigned int | 适配器类型,如 I2C_CLASS_DDC 用于显示 DDC 通道 |
algo | const struct i2c_algorithm * | 指向通信算法实现,必须初始化 |
algo_data | void * | 算法私有数据,通常指向 SoC 寄存器基址 |
dev | struct device | 内核设备模型实例,用于 sysfs 和电源管理 |
nr | int | 总线编号(如 i2c-1 的 nr=1) |
name | char[48] | 适配器名称,在 /sys/bus/i2c/devices/ 中可见 |
显示驱动提示: 在 HDMI/DP 显示驱动中,DDC 通道通常使用
I2C_CLASS_DDC 标记适配器,这样 EDID 读取工具(如 i2c-dev)才能正确识别。
3.3 struct i2c_algorithm —— 通信算法
struct i2c_algorithm 定义了适配器如何执行实际的 I2C 总线传输。它是硬件与上层协议之间的桥梁。
核心函数指针
| 函数指针 | 原型 | 说明 |
|---|---|---|
master_xfer | int (*)(struct i2c_adapter *, struct i2c_msg *, int num) | 标准 I2C 消息传输,返回成功传输的消息数 |
smbus_xfer | int (*)(struct i2c_adapter *, u16 addr, unsigned short flags, char rw, u8 command, int size, union i2c_smbus_data *) | SMBus 协议传输,用于兼容 SMBus 设备 |
functionality | u32 (*)(struct i2c_adapter *) | 返回适配器支持的功能掩码(如 I2C_FUNC_I2C) |
调试要点: 在显示驱动调试中,
master_xfer 是最常被调用的接口。如果 EDID 读取失败,应首先检查 master_xfer 是否返回正确的消息数,以及是否设置了正确的从设备地址(7 位左对齐)。
3.4 struct i2c_client —— I2C 从设备
struct i2c_client 代表一个连接到 I2C 总线上的从设备。在显示驱动中,它对应 HDMI 接收器、触摸控制器、背光调节芯片等。
关键成员
| 成员 | 类型 | 说明 |
|---|---|---|
addr | unsigned short | 7 位从设备地址(左对齐,即 addr << 1) |
adapter | struct i2c_adapter * | 指向所属适配器,client 通过它发起传输 |
driver | const struct i2c_driver * | 指向匹配成功的驱动(probe 时设置) |
dev | struct device | 内核设备实例,用于电源管理和 sysfs |
name | char[20] | 设备名称,通常与 i2c_device_id 中的名称一致 |
irq | int | 中断号,如果设备支持中断则有效 |
常见错误: 地址不匹配是 I2C 调试中最常见的问题。确认设备地址时,务必区分 7 位地址和 8 位读写地址。内核使用 7 位左对齐格式,例如设备地址 0x50(EDID EEPROM)在代码中写为
0x50,而非 0xA0。
3.5 struct i2c_driver —— I2C 设备驱动
struct i2c_driver 是驱动开发者主要实现的结构体。它定义了驱动与内核 I2C 核心的交互接口。
关键成员
| 成员 | 类型 | 说明 |
|---|---|---|
probe | int (*)(struct i2c_client *, const struct i2c_device_id *) | 设备匹配成功时调用,驱动初始化入口 |
remove | int (*)(struct i2c_client *) | 设备移除或驱动卸载时调用 |
shutdown | void (*)(struct i2c_client *) | 系统关机时调用 |
id_table | const struct i2c_device_id * | 驱动支持的设备 ID 列表,用于匹配 client |
driver | struct device_driver | 内核通用驱动结构,包含 name、of_match_table 等 |
设备匹配流程
- 注册驱动: 调用
i2c_add_driver()将i2c_driver注册到 I2C 总线。 - 扫描设备: 内核遍历该适配器上的所有
i2c_client,调用i2c_device_match()。 - 匹配方式: 优先使用
of_match_table(设备树),其次使用id_table(传统方式)。 - 调用 probe: 匹配成功后,调用驱动的
probe函数,传入匹配的client和id。
3.6 数据结构协作流程
下面的 SVG 图展示了从驱动注册到设备 probe 的完整数据流:
3.7 典型代码示例:显示驱动中的 I2C 注册
以下代码展示了一个显示驱动中 I2C 子设备的注册与 probe 流程:
/* 定义 i2c_device_id 表 */
static const struct i2c_device_id hdmi_i2c_id[] = {
{ "hdmi_tx", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, hdmi_i2c_id);
/* 定义 of_match_table(设备树匹配) */
static const struct of_device_id hdmi_of_match[] = {
{ .compatible = "vendor,hdmi-tx" },
{ }
};
MODULE_DEVICE_TABLE(of, hdmi_of_match);
/* probe 函数 */
static int hdmi_i2c_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct device *dev = &client->dev;
struct hdmi_priv *priv;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->client = client;
i2c_set_clientdata(client, priv);
/* 读取 EDID 示例 */
u8 edid[128];
struct i2c_msg msg[2];
u8 addr = 0x00;
msg[0].addr = 0x50; /* EDID EEPROM 地址 */
msg[0].flags = 0; /* 写 */
msg[0].len = 1;
msg[0].buf = &addr;
msg[1].addr = 0x50;
msg[1].flags = I2C_M_RD; /* 读 */
msg[1].len = 128;
msg[1].buf = edid;
if (i2c_transfer(client->adapter, msg, 2) != 2) {
dev_err(dev, "EDID read failed\n");
return -EIO;
}
return 0;
}
/* 定义 i2c_driver */
static struct i2c_driver hdmi_i2c_driver = {
.driver = {
.name = "hdmi_tx",
.of_match_table = hdmi_of_match,
},
.probe = hdmi_i2c_probe,
.id_table = hdmi_i2c_id,
};
/* 模块入口 */
module_i2c_driver(hdmi_i2c_driver);
调试检查清单:
- 确认
i2c_device_id中的名称与设备树或板级代码中的名称一致。 - 确认
of_match_table的 compatible 字符串与设备树节点完全匹配。 - 在 probe 中打印
client->addr和client->adapter->nr,验证设备地址和总线编号。 - 使用
i2cdetect -y <bus>工具验证设备是否在总线上响应。
3.8 数据结构对比总结
| 结构体 | 职责 | 生命周期 | 显示驱动中的典型角色 |
|---|---|---|---|
i2c_adapter | I2C 控制器硬件抽象 | 与 SoC 外设同生命周期 | DDC 通道、触摸控制器总线 |
i2c_algorithm | 底层传输函数实现 | 与 adapter 绑定 | master_xfer 实现 SoC 寄存器操作 |
i2c_client | 从设备抽象 | 设备存在期间 | HDMI 接收器、背光 IC、触摸屏 |
i2c_driver | 驱动逻辑入口 | 模块加载/卸载 | probe 中初始化显示子模块 |
避坑指南: 在 probe 中不要直接使用
client->adapter->algo->master_xfer(),应使用 i2c_transfer() 封装函数。直接调用算法层函数会绕过 I2C 核心的锁定和重试机制,可能导致竞态条件。