3. I2C 核心数据结构:struct i2c_adapter、struct i2c_algorithm、struct i2c_client、struct i2c_driver

在 Linux I2C 子系统中,四个核心数据结构构成了设备模型与通信的骨架。理解它们之间的关系,是编写显示驱动 I2C 调试代码的前提。本章将逐一剖析每个结构体的职责、关键成员以及它们在驱动注册流程中的协作方式。

3.1 整体架构关系图

下面的 SVG 图展示了四个核心结构体在 I2C 总线系统中的分层关系与数据流向:

I2C 核心数据结构分层关系 硬件抽象层 struct i2c_adapter(适配器:I2C 控制器硬件实例) 通信算法层 struct i2c_algorithm(master_xfer / smbus_xfer 函数指针) 设备抽象层 struct i2c_client(从设备地址 + 适配器绑定) 驱动逻辑层 struct i2c_driver(probe / remove / id_table) client 与 driver 通过 i2c_device_id 匹配 核心数据流 adapter 持有 algorithm → client 绑定 adapter → driver 匹配 client → probe 中通过 client 调用 adapter 收发数据

3.2 struct i2c_adapter —— I2C 总线适配器

struct i2c_adapter 代表一个物理的 I2C 控制器实例。在显示驱动中,它通常对应 SoC 内部的一个 I2C 外设(如 i2c-0、i2c-1),负责管理总线时钟、仲裁和消息传输。

关键成员

成员类型说明
ownerstruct module *模块引用计数,防止驱动被卸载时仍在使用
classunsigned int适配器类型,如 I2C_CLASS_DDC 用于显示 DDC 通道
algoconst struct i2c_algorithm *指向通信算法实现,必须初始化
algo_datavoid *算法私有数据,通常指向 SoC 寄存器基址
devstruct device内核设备模型实例,用于 sysfs 和电源管理
nrint总线编号(如 i2c-1 的 nr=1)
namechar[48]适配器名称,在 /sys/bus/i2c/devices/ 中可见
显示驱动提示: 在 HDMI/DP 显示驱动中,DDC 通道通常使用 I2C_CLASS_DDC 标记适配器,这样 EDID 读取工具(如 i2c-dev)才能正确识别。

3.3 struct i2c_algorithm —— 通信算法

struct i2c_algorithm 定义了适配器如何执行实际的 I2C 总线传输。它是硬件与上层协议之间的桥梁。

核心函数指针

函数指针原型说明
master_xferint (*)(struct i2c_adapter *, struct i2c_msg *, int num)标准 I2C 消息传输,返回成功传输的消息数
smbus_xferint (*)(struct i2c_adapter *, u16 addr, unsigned short flags, char rw, u8 command, int size, union i2c_smbus_data *)SMBus 协议传输,用于兼容 SMBus 设备
functionalityu32 (*)(struct i2c_adapter *)返回适配器支持的功能掩码(如 I2C_FUNC_I2C)
调试要点: 在显示驱动调试中,master_xfer 是最常被调用的接口。如果 EDID 读取失败,应首先检查 master_xfer 是否返回正确的消息数,以及是否设置了正确的从设备地址(7 位左对齐)。

3.4 struct i2c_client —— I2C 从设备

struct i2c_client 代表一个连接到 I2C 总线上的从设备。在显示驱动中,它对应 HDMI 接收器、触摸控制器、背光调节芯片等。

关键成员

成员类型说明
addrunsigned short7 位从设备地址(左对齐,即 addr << 1)
adapterstruct i2c_adapter *指向所属适配器,client 通过它发起传输
driverconst struct i2c_driver *指向匹配成功的驱动(probe 时设置)
devstruct device内核设备实例,用于电源管理和 sysfs
namechar[20]设备名称,通常与 i2c_device_id 中的名称一致
irqint中断号,如果设备支持中断则有效
常见错误: 地址不匹配是 I2C 调试中最常见的问题。确认设备地址时,务必区分 7 位地址和 8 位读写地址。内核使用 7 位左对齐格式,例如设备地址 0x50(EDID EEPROM)在代码中写为 0x50,而非 0xA0。

3.5 struct i2c_driver —— I2C 设备驱动

struct i2c_driver 是驱动开发者主要实现的结构体。它定义了驱动与内核 I2C 核心的交互接口。

关键成员

成员类型说明
probeint (*)(struct i2c_client *, const struct i2c_device_id *)设备匹配成功时调用,驱动初始化入口
removeint (*)(struct i2c_client *)设备移除或驱动卸载时调用
shutdownvoid (*)(struct i2c_client *)系统关机时调用
id_tableconst struct i2c_device_id *驱动支持的设备 ID 列表,用于匹配 client
driverstruct device_driver内核通用驱动结构,包含 name、of_match_table 等

设备匹配流程

  1. 注册驱动: 调用 i2c_add_driver() 将 i2c_driver 注册到 I2C 总线。
  2. 扫描设备: 内核遍历该适配器上的所有 i2c_client,调用 i2c_device_match()。
  3. 匹配方式: 优先使用 of_match_table(设备树),其次使用 id_table(传统方式)。
  4. 调用 probe: 匹配成功后,调用驱动的 probe 函数,传入匹配的 client 和 id。

3.6 数据结构协作流程

下面的 SVG 图展示了从驱动注册到设备 probe 的完整数据流:

I2C 驱动注册与设备匹配流程 1. 注册驱动 i2c_add_driver() 2. 扫描适配器 遍历 adapter 上所有 client 3. 匹配设备 of_match / id_table 4. 调用 probe(client, id) 驱动初始化:注册子设备、分配数据结构、初始化硬件 5. 数据传输 i2c_master_send() / i2c_master_recv() / i2c_transfer() 关键数据结构关系 adapter 持有 algorithm → client 绑定 adapter → driver 匹配 client → probe 中通过 client 调用 adapter 收发数据

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_adapterI2C 控制器硬件抽象与 SoC 外设同生命周期DDC 通道、触摸控制器总线
i2c_algorithm底层传输函数实现与 adapter 绑定master_xfer 实现 SoC 寄存器操作
i2c_client从设备抽象设备存在期间HDMI 接收器、背光 IC、触摸屏
i2c_driver驱动逻辑入口模块加载/卸载probe 中初始化显示子模块
避坑指南: 在 probe 中不要直接使用 client->adapter->algo->master_xfer(),应使用 i2c_transfer() 封装函数。直接调用算法层函数会绕过 I2C 核心的锁定和重试机制,可能导致竞态条件。