4. I2C 核心数据结构:i2c_adapter、i2c_algorithm、i2c_client、i2c_driver 结构体详解

在车载 Linux 的 I2C 子系统里,驱动开发的核心就是理解并正确使用四个关键数据结构:i2c_adapter(适配器)、i2c_algorithm(通信算法)、i2c_client(从设备客户端)和 i2c_driver(从设备驱动)。它们构成了 I2C 总线驱动模型的主干,也是设备挂载与调试的基石。

核心关系速览: i2c_adapter 代表 I2C 控制器硬件,通过 i2c_algorithm 实现具体的总线通信协议;i2c_driver 绑定到具体的 i2c_client,后者代表挂载在总线上的物理从设备。适配器与客户端之间通过总线号或设备树进行匹配。

4.1 i2c_adapter:I2C 总线适配器

i2c_adapter 是 I2C 控制器在软件层面的抽象,负责管理一条物理 I2C 总线。每个 I2C 控制器(如 SoC 内部的 I2C IP 核)对应一个 adapter 实例。

成员 类型 说明
owner struct module * 所属模块,通常设为 THIS_MODULE
class unsigned int 适配器类型,如 I2C_CLASS_HWMON
algo const struct i2c_algorithm * 指向通信算法实现,adapter 的核心操作
algo_data void * 算法私有数据,通常指向控制器寄存器基址
bus_lock struct mutex 总线访问互斥锁,防止并发竞态
timeout int 传输超时时间(单位 jiffies)
retries int 传输失败时的重试次数
dev struct device 内核设备模型实例,用于 sysfs 和设备树关联
nr int 适配器编号(总线号),如 /dev/i2c-1 的 nr=1
name char[48] 适配器名称,用于调试和日志
list struct list_head 全局 adapter 链表节点

在车载场景中,一个典型的 SoC 可能包含 4~8 个 I2C 控制器,每个控制器注册为一个 adapter,分别对应 /dev/i2c-0 到 /dev/i2c-7。adapter 的注册通过 i2c_add_adapter() 或 i2c_add_numbered_adapter() 完成。

4.2 i2c_algorithm:通信算法的抽象

i2c_algorithm 定义了 adapter 与从设备进行数据交换的具体方法。它是硬件操作与上层协议之间的桥梁。

struct i2c_algorithm {
    /* 主传输函数:发送或接收一个完整消息 */
    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)(struct i2c_adapter *adap);
};
回调 必须实现? 车载典型用途
master_xfer 是 发送传感器配置、读取 ADC 数据、控制触摸屏
smbus_xfer 否 与 PMIC、电池管理芯片的 SMBus 通信
functionality 是 返回 I2C_FUNC_I2C | I2C_FUNC_SMBUS_BYTE 等
车载调试提示: 如果 adapter 的 master_xfer 返回负值(如 -EIO、-ETIMEDOUT),通常表示硬件时序问题或从设备未应答。此时应使用示波器检查 SCL/SDA 波形,而非盲目修改软件。

4.3 i2c_client:从设备客户端

i2c_client 代表挂载在 I2C 总线上的一个具体从设备。它由内核在设备枚举或设备树解析时创建,并绑定到对应的 i2c_driver。

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; /* 检测链表 */
};

在车载设备树中,client 的创建通常由以下节点触发:

&i2c3 {
    status = "okay";
    clock-frequency = <400000>;  /* 400kHz 快速模式 */

    temperature_sensor: tmp102@48 {
        compatible = "ti,tmp102";
        reg = <0x48>;           /* 7 位从地址 */
        interrupt-parent = <&gpio2>;
        interrupts = <5 IRQ_TYPE_EDGE_FALLING>;
    };
};

内核解析上述节点后,会调用 i2c_new_client_device() 创建一个 i2c_client,其中 addr=0x48,adapter 指向 i2c3 对应的 adapter。

4.4 i2c_driver:从设备驱动

i2c_driver 是驱动开发者主要实现的结构体,它描述了如何与特定类型的从设备交互。

struct i2c_driver {
    unsigned int class;          /* 驱动支持的设备类 */
    int (*probe)(struct i2c_client *client,
                 const struct i2c_device_id *id);
    int (*remove)(struct i2c_client *client);
    void (*shutdown)(struct i2c_client *client);
    struct device_driver driver; /* 内核驱动基类 */
    const struct i2c_device_id *id_table; /* 设备 ID 表 */
    const struct of_device_id *of_match_table; /* 设备树匹配表 */
};
回调/成员 作用 车载实现要点
probe 设备匹配成功后调用,完成初始化 注册输入设备、创建 sysfs 属性、启动定时轮询
remove 设备移除或驱动卸载时调用 释放中断、取消工作队列、注销设备
id_table 传统板级匹配方式 用于非设备树平台或兼容性匹配
of_match_table 设备树 compatible 匹配 车载主流方式,必须与 dts 节点一致

4.5 四大结构体协作关系

下图展示了 adapter、algorithm、client、driver 在 I2C 子系统中的完整协作流程:

I2C 核心数据结构协作关系 i2c_adapter I2C 控制器抽象 nr=3, name="i2c-3" i2c_algorithm master_xfer() functionality() i2c_client 从设备客户端 addr=0x48, irq=67 i2c_driver probe() / remove() of_match_table 通过总线号 / 设备树匹配 i2c_msg 消息传递 driver → client → adapter → algorithm → 硬件 车载调试要点: 1. 使用 i2cdetect -y <bus> 验证 adapter 和 client 地址是否正确 2. 检查 /sys/bus/i2c/devices/ 目录确认驱动绑定状态

4.6 驱动绑定与设备挂载流程

在车载 Linux 启动过程中,I2C 设备从硬件识别到驱动绑定的完整步骤如下:

  1. Adapter 注册: I2C 控制器驱动在 probe 中调用 i2c_add_numbered_adapter(),将 adapter 加入全局链表,并在 /dev 下创建 i2c-N 设备节点。
  2. 设备树解析: 内核遍历 /soc/i2c@.../ 下的子节点,为每个 compatible 匹配的节点调用 of_i2c_register_devices()。
  3. Client 创建: 对每个有效子节点,调用 i2c_new_client_device() 创建 i2c_client,并挂载到 adapter 的 clients 链表。
  4. Driver 匹配: 内核遍历已注册的 i2c_driver,通过 of_match_table 或 id_table 进行匹配。
  5. Probe 调用: 匹配成功后,内核调用 driver 的 probe() 函数,传入对应的 i2c_client 指针。
  6. 数据传输: 驱动通过 i2c_transfer() 或 i2c_smbus_read_byte() 等 API 发起通信,最终调用 adapter 的 master_xfer。
常见车载挂载失败原因:
  • 设备树中 reg 地址与硬件原理图不一致(7 位地址左移错误)
  • clock-frequency 超过从设备支持的最大速率(如 400kHz 设备被配置为 1MHz)
  • adapter 的 master_xfer 未正确处理 I2C_M_RD 标志位
  • 多个从设备地址冲突(同一总线上两个设备使用相同地址)

4.7 数据结构对比总结

结构体 角色 谁实现? 车载典型文件
i2c_adapter 总线控制器抽象 SoC 厂商 BSP 工程师 drivers/i2c/busses/i2c-imx.c
i2c_algorithm 硬件通信方法 SoC 厂商 BSP 工程师 同上,i2c_imx_xfer()
i2c_client 从设备实例 内核(设备树/板级代码) 自动创建,无需手动编写
i2c_driver 从设备驱动 车载驱动开发工程师 drivers/input/touchscreen/ 等

理解这四个结构体的职责与协作关系,是编写可靠车载 I2C 驱动的基础。在实际调试中,通过 /sys/kernel/debug/i2c/ 和 i2cdump 工具可以直观验证每个结构体的运行时状态。