3. Linux I2C 子系统架构:三层模型详解

Linux I2C 子系统采用经典的三层软件架构设计,将硬件控制、总线管理与设备交互解耦。理解这一分层模型是进行 I2C 控制器驱动移植与中断绑定的基础。三层分别为:I2C 核心层、I2C 总线驱动层(即控制器驱动层)与 I2C 设备驱动层。

核心要点: 三层模型的设计哲学是“分离关注点”。总线驱动层负责与硬件寄存器打交道,设备驱动层负责与具体外设通信,核心层则提供统一的注册、注销、数据传输与适配器管理接口。
Linux I2C 子系统三层架构模型 I2C 设备驱动层 (I2C Device Driver) i2c_driver | i2c_client | 用户空间接口 (sysfs/chardev) 例:eeprom_driver, sensor_driver, audio_codec_driver I2C 核心层 (I2C Core) i2c-core.c | i2c-dev.c | 适配器管理 | 总线注册 提供 i2c_transfer()、i2c_add_adapter()、i2c_add_driver() 等 API I2C 总线驱动层 (I2C Bus/Adapter Driver) i2c_adapter | i2c_algorithm | 硬件寄存器操作 例:i2c-imx.c, i2c-omap.c, i2c-designware.c 注册/注销 调用 algorithm 完成回调 💡 移植 I2C 控制器时,主要工作集中在“总线驱动层”,即实现 i2c_algorithm 中的 master_xfer 函数。 中断绑定通常在此层完成,用于提升传输效率。

3.1 I2C 核心层 (I2C Core)

核心层是 I2C 子系统的中枢,位于 drivers/i2c/ 目录下,主要文件包括 i2c-core.c、i2c-dev.c 和 i2c-boardinfo.c。它不直接操作硬件,而是提供一套统一的框架和 API,供上下两层调用。

核心功能 关键 API / 数据结构 说明
适配器管理 i2c_add_adapter() / i2c_del_adapter() 注册/注销 I2C 总线控制器
驱动管理 i2c_register_driver() / i2c_del_driver() 注册/注销 I2C 设备驱动
数据传输 i2c_transfer() / i2c_master_send() / i2c_master_recv() 发起 I2C 总线读写事务
设备枚举 i2c_new_device() / i2c_new_probed_device() 动态创建 I2C 设备节点
用户空间接口 /dev/i2c-N (通过 i2c-dev) 提供字符设备访问方式
移植提示: 核心层代码通常不需要修改。移植工作主要围绕“如何让核心层找到并正确调用你的控制器驱动”展开,即正确实现 i2c_adapter 和 i2c_algorithm。

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

总线驱动层直接面向硬件,是驱动移植的核心战场。它负责实现 I2C 控制器的初始化、时钟配置、中断处理、以及底层时序的生成。该层主要实现两个关键数据结构:struct i2c_adapter 和 struct i2c_algorithm。

I2C 总线驱动层内部结构 struct i2c_adapter nr: 总线编号 name: 控制器名称 algo: 指向 i2c_algorithm struct i2c_algorithm master_xfer(): 传输函数 smbus_xfer(): SMBus 传输 functionality(): 能力标志 指针 硬件寄存器操作层 writel() / readl() | 中断处理函数 | 时钟使能 调用

总线驱动层需要完成的核心任务清单:

  1. 硬件初始化: 使能 I2C 控制器时钟、配置引脚复用 (Pinmux)、设置波特率。
  2. 中断注册: 申请 IRQ 并绑定中断处理函数,用于处理传输完成、仲裁丢失、错误等事件。
  3. 实现 master_xfer: 这是最关键的函数,负责将上层下发的 struct i2c_msg 数组转换为硬件时序。
  4. 实现 functionality: 返回控制器支持的操作类型(如 I2C_FUNC_I2C、I2C_FUNC_SMBUS_EMUL)。
  5. 注册适配器: 调用 i2c_add_adapter() 将适配器加入核心层管理。
常见陷阱: 在 master_xfer 中如果使用轮询方式等待传输完成,会阻塞调用进程并浪费 CPU。正确的做法是使用中断 + 完成量 (completion) 机制,在中断处理函数中唤醒等待的传输线程。这是中断绑定的核心价值所在。

3.3 I2C 设备驱动层 (I2C Device Driver)

设备驱动层面向具体的 I2C 从设备(如 EEPROM、温度传感器、音频编解码器)。它通过核心层提供的 API 与总线驱动层交互,无需关心底层硬件时序细节。

设备驱动层的关键数据结构与流程:

组件 描述
struct i2c_driver 驱动对象,包含 probe()、remove()、id_table 等成员
struct i2c_client 设备对象,由核心层根据板级信息或设备树创建
struct i2c_device_id 用于传统板级匹配的设备 ID 表
of_match_table 用于设备树匹配的 compatible 字符串表

一个典型的 I2C 设备驱动 probe 函数模板如下:

static int my_sensor_probe(struct i2c_client *client)
{
    struct my_sensor_data *data;
    int ret;

    data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
    if (!data)
        return -ENOMEM;

    data->client = client;
    i2c_set_clientdata(client, data);

    /* 读取设备 ID 确认连接 */
    ret = i2c_smbus_read_byte_data(client, REG_DEVICE_ID);
    if (ret < 0) {
        dev_err(&client->dev, "failed to read device id\n");
        return ret;
    }

    /* 注册输入设备 / 硬件监控设备 / 音频设备等 */
    return 0;
}

static const struct i2c_device_id my_sensor_id[] = {
    { "my-sensor", 0 },
    { }
};
MODULE_DEVICE_TABLE(i2c, my_sensor_id);

static const struct of_device_id my_sensor_of_match[] = {
    { .compatible = "vendor,my-sensor" },
    { }
};
MODULE_DEVICE_TABLE(of, my_sensor_of_match);

static struct i2c_driver my_sensor_driver = {
    .driver = {
        .name = "my_sensor",
        .of_match_table = my_sensor_of_match,
    },
    .probe    = my_sensor_probe,
    .id_table = my_sensor_id,
};
module_i2c_driver(my_sensor_driver);

3.4 三层之间的数据流与协作

当用户空间或内核其他子系统发起一次 I2C 读取操作时,数据在三层之间的流动路径如下:

I2C 读取操作数据流 (以中断方式为例) 设备驱动层 核心层 总线驱动层 ① 调用 i2c_master_recv() ② i2c_transfer() 构造 msg ③ master_xfer() 启动硬件传输 写从机地址 + 寄存器地址 + 启动读 ④ 硬件产生中断 中断处理函数读取数据寄存器 调用 complete() 唤醒等待 返回数据 返回数据 ⏱ 中断方式下,步骤 ③ 发起传输后进程进入休眠,步骤 ④ 中断处理函数唤醒进程,实现高效 CPU 利用。

3.5 三层模型在驱动移植中的指导意义

理解三层模型后,驱动移植的边界变得非常清晰:

  • 不需要修改: I2C 核心层代码。它由内核社区维护,与具体硬件无关。
  • 需要重写或适配: I2C 总线驱动层。这是移植工作的主体,包括实现 i2c_algorithm、处理中断、配置时钟。
  • 通常不需要修改: 现有的 I2C 设备驱动。只要从设备兼容,设备驱动可以直接复用。但需要确保设备树或板级文件中正确描述了设备连接关系。
移植工作清单(基于三层模型):
1. 在总线驱动层实现 i2c_algorithm->master_xfer(),并绑定中断。
2. 在总线驱动层调用 i2c_add_adapter() 注册适配器。
3. 在设备树中描述 I2C 控制器节点(地址、中断、时钟、DMA)。
4. 在设备树中描述挂载在该总线上的从设备节点(compatible、reg)。
5. 确保从设备的 i2c_driver 能够匹配设备树中的 compatible 字符串。