3. Linux I2C 子系统架构
Linux I2C 子系统采用分层的软件架构设计,将复杂的 I2C 总线通信抽象为三个清晰的层次:I2C 核心层、I2C 总线驱动和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);
};
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 等标志 */
};
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 通信流程如下:
- 设备驱动调用:调用
i2c_smbus_read_block_data(client, 0xA0, buf, 128)或构造i2c_msg数组。 - 核心层路由:
i2c-core.c根据client->adapter找到对应的i2c_adapter,获取其algo->master_xfer函数指针。 - 总线驱动执行:调用
master_xfer(),该函数操作 SoC 的 I2C 控制器寄存器,产生 SCL/SDA 时序。 - 硬件传输:I2C 控制器通过 GPIO 或专用引脚完成字节级传输,从设备响应 ACK/NACK。
- 数据返回:总线驱动将接收到的数据写回
i2c_msg的buf中,逐层返回给设备驱动。
3.7 本章小结
| 层次 | 核心数据结构 | 主要职责 | 显示驱动关注点 |
|---|---|---|---|
| I2C 设备驱动 | i2c_driver / i2c_client |
与具体从设备通信,实现设备功能 | 编写 probe/remove,调用 i2c_transfer |
| I2C 核心层 | i2c_adapter 管理 |
设备匹配、数据传输路由、ioctl | 理解匹配机制,无需修改 |
| I2C 总线驱动 | i2c_algorithm |
硬件寄存器操作,产生时序 | 由 SoC 厂商提供,驱动开发者不涉及 |