3. Linux I2C 子系统架构

Linux I2C 子系统采用分层的软件架构设计,将复杂的 I2C 总线通信抽象为三个清晰的层次:I2C 核心层、I2C 总线驱动和I2C 设备驱动。这种分层设计使得驱动开发者无需关心底层硬件寄存器操作,只需专注于各自层次的接口实现。对于显示驱动开发而言,理解这一架构是正确配置设备树和编写 I2C 通信代码的前提。

Linux I2C 子系统分层架构 用户空间 (User Space) 内核空间 (Kernel Space) I2C 设备驱动层 (I2C Device Driver) 例:显示驱动中的触摸控制器、DDC/CI 通信、音频编解码器 I2C 核心层 (I2C Core) i2c-core.c / i2c-dev.c — 算法注册、设备模型管理、ioctl 接口 I2C 总线驱动层 (I2C Bus Driver / Adapter) i2c-imx.c / i2c-s3c2410.c / i2c-designware.c — 硬件寄存器操作 硬件层 (Hardware) — I2C 控制器 / I2C 从设备

3.1 I2C 核心层 (I2C Core)

I2C 核心层是 Linux I2C 子系统的中枢,位于 drivers/i2c/ 目录下,主要文件包括 i2c-core.c、i2c-dev.c 和 i2c-boardinfo.c。它向上为设备驱动提供统一的 API,向下为总线驱动提供注册和管理框架。

核心功能 说明 关键接口/文件
设备与驱动管理 维护 i2c_client 与 i2c_driver 的匹配、绑定、解绑 i2c_add_driver() / i2c_register_board_info()
数据传输抽象 提供主从设备间的读写事务接口 i2c_transfer() / i2c_smbus_xfer()
适配器管理 管理 i2c_adapter 的注册、注销和算法绑定 i2c_add_adapter() / i2c_del_adapter()
ioctl 接口 提供用户空间访问 I2C 设备的字符设备接口 /dev/i2c-N / i2c-dev.c

💡 显示驱动开发要点:在显示驱动中,I2C 核心层主要负责将设备树中解析出的 i2c_client 与对应的 i2c_driver 进行匹配。驱动开发者通常不需要修改核心层代码,但需要理解其匹配机制以正确配置设备树。

3.2 I2C 总线驱动 (I2C Bus Driver / Adapter Driver)

I2C 总线驱动直接与硬件 I2C 控制器交互,负责实现底层时序和寄存器操作。每个 SoC 平台都有对应的总线驱动,例如 i2c-imx.c(i.MX 系列)、i2c-s3c2410.c(Samsung)、i2c-designware.c(Synopsys DesignWare)。

总线驱动的核心任务是实现 struct i2c_algorithm 结构体中的函数指针:

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)(struct i2c_adapter *adap);
};
I2C 总线驱动与硬件交互流程 I2C 设备驱动 I2C 核心层 I2C 总线驱动 master_xfer() 硬件 struct i2c_algorithm 实现示例 (i2c-imx.c) static struct i2c_algorithm imx_i2c_algorithm = { .master_xfer = imx_i2c_xfer, .functionality = imx_i2c_functionality, };

3.3 I2C 设备驱动 (I2C Device Driver)

I2C 设备驱动是离开发者最近的层次,负责与具体的 I2C 从设备(如触摸控制器、音频编解码器、显示器的 DDC/CI 接口)进行通信。在显示驱动场景中,I2C 设备驱动通常用于读取 EDID 数据、配置 HDMI 发送器或与触控面板通信。

设备驱动的核心数据结构是 struct i2c_driver:

struct i2c_driver {
    /* 驱动基本信息 */
    struct device_driver driver;
    
    /* 设备匹配与初始化 */
    int (*probe)(struct i2c_client *client, 
                 const struct i2c_device_id *id);
    int (*remove)(struct i2c_client *client);
    
    /* 设备树匹配表 */
    const struct of_device_id *of_match_table;
    
    /* 传统 ID 匹配表 */
    const struct i2c_device_id *id_table;
};

🔑 关键理解:I2C 设备驱动不直接操作硬件寄存器,而是通过 i2c_client 结构体调用核心层提供的 i2c_transfer() 或 i2c_smbus_read/write_byte_data() 等 API 完成数据收发。设备树中的 compatible 属性是驱动匹配的核心依据。

3.4 i2c_adapter 与 i2c_client 数据结构

这两个结构体是 I2C 子系统中最核心的数据抽象,分别代表 I2C 总线控制器(主设备端)和 I2C 从设备。

3.4.1 struct i2c_adapter — I2C 总线控制器抽象

i2c_adapter 对应一个物理的 I2C 控制器,每个 SoC 的 I2C 外设实例都会注册为一个 adapter。它封装了算法、锁机制和电源管理信息。

struct i2c_adapter {
    struct module *owner;
    unsigned int class;               /* 适配器支持的设备类 */
    const struct i2c_algorithm *algo; /* 算法实现指针 */
    void *algo_data;                  /* 算法私有数据 */
    
    struct device dev;                /* 内核设备模型 */
    int nr;                           /* 适配器编号,如 i2c-0 */
    struct list_head userspace_clients; /* 用户空间设备列表 */
    
    /* 总线锁,防止并发访问 */
    struct mutex bus_lock;
    struct mutex mux_lock;
    
    /* 超时与重试配置 */
    int timeout;                      /* 传输超时 (jiffies) */
    int retries;                      /* 重试次数 */
};
字段 说明 典型值/用途
nr 适配器编号,对应 /dev/i2c-N 0, 1, 2 ... 由系统分配或设备树指定
algo 指向 i2c_algorithm 的指针 由总线驱动填充 master_xfer
timeout 传输超时时间 通常为 1 秒 (HZ)
retries 传输失败后的重试次数 通常为 3 次

3.4.2 struct i2c_client — I2C 从设备抽象

i2c_client 代表一个连接到 I2C 总线上的从设备。它由设备树解析或板级代码注册生成,是设备驱动与硬件通信的桥梁。

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;       /* 检测链表 */
    
    /* 标志位 */
    unsigned int flags:16;           /* I2C_CLIENT_TEN 等标志 */
};
i2c_adapter 与 i2c_client 关系模型 struct i2c_adapter I2C 总线控制器 (i2c-0) nr = 0 algo → master_xfer() timeout = 1s, retries = 3 dev → 设备模型节点 clients 链表 → 挂载多个 i2c_client struct i2c_client I2C 从设备 (HDMI 发送器) addr = 0x39 (HDMI EDID) name = "hdmi-encoder" adapter → 指向 i2c-0 irq = 123 (中断引脚) flags = 0 (7位地址)

3.5 设备树与 I2C 子系统的绑定

在设备树中,I2C 控制器节点下挂载的子节点会被内核解析为 i2c_client 实例。以下是一个典型的显示驱动相关设备树片段:

&i2c1 {
    status = "okay";
    clock-frequency = <100000>;  /* 100kHz 标准模式 */
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_i2c1>;

    /* HDMI 发送器,地址 0x39 */
    hdmi-encoder@39 {
        compatible = "sil,sii9022";
        reg = <0x39>;
        interrupts = <GIC_SPI 45 IRQ_TYPE_EDGE_RISING>;
        interrupt-parent = <&gic>;
        #sound-dai-cells = <0>;
    };

    /* 触摸控制器,地址 0x38 */
    touchscreen@38 {
        compatible = "edt,edt-ft5406";
        reg = <0x38>;
        interrupt-parent = <&gpio1>;
        interrupts = <9 IRQ_TYPE_EDGE_FALLING>;
        touchscreen-size-x = <800>;
        touchscreen-size-y = <480>;
    };
};

⚠️ 常见错误:设备树中 reg 属性指定的 I2C 地址必须与硬件从设备地址一致。7 位地址范围是 0x08~0x77,地址 0x00~0x07 和 0x78~0x7F 为保留地址。如果地址冲突或超出范围,驱动将无法匹配到设备。

3.6 数据传输流程总结

当显示驱动需要从 HDMI 发送器读取 EDID 数据时,完整的 I2C 通信流程如下:

  1. 设备驱动调用:调用 i2c_smbus_read_block_data(client, 0xA0, buf, 128) 或构造 i2c_msg 数组。
  2. 核心层路由:i2c-core.c 根据 client->adapter 找到对应的 i2c_adapter,获取其 algo->master_xfer 函数指针。
  3. 总线驱动执行:调用 master_xfer(),该函数操作 SoC 的 I2C 控制器寄存器,产生 SCL/SDA 时序。
  4. 硬件传输:I2C 控制器通过 GPIO 或专用引脚完成字节级传输,从设备响应 ACK/NACK。
  5. 数据返回:总线驱动将接收到的数据写回 i2c_msg 的 buf 中,逐层返回给设备驱动。
I2C 读 EDID 数据调用链 ① 设备驱动 i2c_smbus_read() ② I2C 核心层 i2c_transfer() ③ 总线驱动 master_xfer() ④ 硬件控制器 SCL/SDA 时序 ⑤ 从设备 返回数据 数据逐层返回

3.7 本章小结

层次 核心数据结构 主要职责 显示驱动关注点
I2C 设备驱动 i2c_driver / i2c_client 与具体从设备通信,实现设备功能 编写 probe/remove,调用 i2c_transfer
I2C 核心层 i2c_adapter 管理 设备匹配、数据传输路由、ioctl 理解匹配机制,无需修改
I2C 总线驱动 i2c_algorithm 硬件寄存器操作,产生时序 由 SoC 厂商提供,驱动开发者不涉及