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

Linux I2C 子系统采用经典的分层设计思想,将复杂的 I2C 通信控制抽象为三个职责清晰的层次:I2C 核心层、I2C 总线驱动层和I2C 设备驱动层。这种分层架构使得驱动开发者无需关心底层硬件寄存器操作,只需专注于各自层次的接口实现,极大地提高了代码的可移植性和复用性。

Linux I2C 子系统三层架构总览 用户空间 (User Space) 应用程序 / i2c-tools (i2cget, i2cset, i2cdetect) 内核空间 (Kernel Space) I2C 设备驱动层 (I2C Device Driver) i2c_driver / i2c_client / 具体外设驱动 (如 EEPROM, 传感器) 实现 probe()、remove()、id_table、对用户空间的接口 I2C 核心层 (I2C Core) i2c-core.c / i2c-dev.c / 设备与驱动注册/注销管理 提供 i2c_add_driver()、i2c_new_device()、i2c_transfer() 等核心 API I2C 总线驱动层 (I2C Bus Driver / Adapter) i2c_adapter / i2c_algorithm / 硬件寄存器操作 实现 master_xfer()、smbus_xfer(),直接操作 I2C 控制器寄存器 硬件层:I2C 控制器 (如 i.MX6, STM32MP1)

3.1 I2C 核心层 (I2C Core)

I2C 核心层是 Linux I2C 子系统的中枢神经,位于 drivers/i2c/ 目录下,主要由 i2c-core.c、i2c-dev.c 和 i2c-boardinfo.c 等文件实现。它不直接操作硬件,而是提供一套统一的框架和 API,向上对接设备驱动,向下适配总线驱动。

核心职责:
  • 设备与驱动管理:维护 i2c_bus_type 总线类型,负责 i2c_driver 和 i2c_client 的注册、注销与匹配。
  • 通信抽象:提供 i2c_transfer()、i2c_master_send()、i2c_master_recv() 等高层 API,屏蔽底层硬件差异。
  • SMBus 兼容:实现 SMBus 协议栈,提供 i2c_smbus_read_byte()、i2c_smbus_write_word() 等接口。
  • 用户空间接口:通过 i2c-dev.c 创建 /dev/i2c-N 设备节点,允许用户空间直接访问 I2C 总线。
I2C 核心层关键文件与功能
文件名 核心功能 关键 API / 数据结构
i2c-core.c 核心框架:总线注册、驱动匹配、设备生命周期 i2c_add_driver(), i2c_new_device(), i2c_transfer()
i2c-dev.c 用户空间字符设备接口 i2cdev_read(), i2cdev_write(), i2cdev_ioctl()
i2c-boardinfo.c 板级设备信息注册(非设备树平台) i2c_register_board_info()
i2c-smbus.c SMBus 协议实现 i2c_smbus_xfer(), i2c_smbus_read_byte_data()
开发提示:在设备树时代,核心层的 i2c_new_device() 通常由系统在解析设备树时自动调用。驱动开发者一般不需要直接调用它,而是通过设备树节点描述外设,核心层自动完成 i2c_client 的创建。

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

总线驱动层是 I2C 子系统中最接近硬件的层次,直接操作 SoC 内部 I2C 控制器的寄存器。它负责实现 I2C 总线的时序控制、起始/停止条件生成、数据收发、时钟同步等底层协议细节。

每个 I2C 控制器对应一个 struct i2c_adapter 实例,适配器内部封装了 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 总线驱动层内部结构 struct i2c_adapter nr: 总线编号 (如 0, 1, 2) name: 适配器名称 algo: struct i2c_algorithm * dev: struct device struct i2c_algorithm master_xfer() → 核心传输入口 smbus_xfer() → SMBus 传输 functionality() → 功能掩码 硬件寄存器操作 (SoC 相关) I2C_CON / I2C_STAT / I2C_DAT / I2C_IF / 时钟分频 / 中断处理 如 i.MX6: I2C_IADR, I2C_IFDR, I2C_I2CR
移植要点:总线驱动层是 I2C 驱动移植中最需要修改的部分。不同 SoC 的 I2C 控制器寄存器布局、时钟配置、中断处理方式差异巨大。移植时通常需要重写 master_xfer() 函数,并配置正确的时钟分频系数以满足 I2C 总线速率(标准模式 100kHz / 快速模式 400kHz)。

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

设备驱动层是开发者最常接触的层次,它负责驱动具体的 I2C 外设芯片,如 EEPROM (AT24Cxx)、温度传感器 (LM75)、加速度计 (MPU6050) 等。设备驱动通过核心层提供的 API 与总线驱动层交互,无需关心底层时序细节。

一个标准的 I2C 设备驱动需要实现以下核心结构:

/* I2C 设备驱动结构体 */
static struct i2c_driver my_sensor_driver = {
    .driver = {
        .name = "my_sensor",
        .of_match_table = my_sensor_of_match,  /* 设备树匹配表 */
    },
    .probe    = my_sensor_probe,    /* 设备探测函数 */
    .remove   = my_sensor_remove,   /* 设备移除函数 */
    .id_table = my_sensor_id,       /* 传统 ID 匹配表(备用) */
};

/* 设备树匹配表 */
static const struct of_device_id my_sensor_of_match[] = {
    { .compatible = "vendor,my-sensor", },
    { /* sentinel */ }
};

/* probe 函数:设备被发现时调用 */
static int my_sensor_probe(struct i2c_client *client,
                           const struct i2c_device_id *id)
{
    struct my_sensor_data *data;
    struct device *dev = &client->dev;

    /* 1. 分配私有数据结构 */
    data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
    if (!data)
        return -ENOMEM;

    /* 2. 初始化 I2C 通信 */
    data->client = client;
    i2c_set_clientdata(client, data);

    /* 3. 读取设备 ID 确认通信正常 */
    ret = i2c_smbus_read_byte_data(client, REG_DEVICE_ID);
    if (ret < 0) {
        dev_err(dev, "failed to read device ID\n");
        return ret;
    }

    /* 4. 注册设备到子系统(如 IIO、HWMON、Input) */
    return my_sensor_register_device(dev, data);
}
I2C 设备驱动层工作流程 设备树解析 of_match_table 匹配 probe() 调用 i2c_client 已就绪 设备初始化 配置寄存器 / 读取 ID 注册到内核子系统 IIO / HWMON / Input / Misc 用户空间交互 (sysfs / ioctl)

3.4 三层架构的协作流程

当用户空间发起一次 I2C 读操作时,数据流在三层架构中的完整路径如下:

  1. 用户空间调用:应用程序通过 /dev/i2c-N 设备节点发起 read() 系统调用,或通过 i2c-tools 的 i2cget 命令。
  2. 核心层转发:i2c-dev.c 中的 i2cdev_read() 将用户请求封装为 struct i2c_msg,调用 i2c_transfer() 进入核心层。
  3. 核心层调度:i2c-core.c 根据 adapter 找到对应的总线驱动,调用 adapter->algo->master_xfer()。
  4. 总线驱动执行:master_xfer() 操作 I2C 控制器寄存器,在 SCL/SDA 线上产生时序,完成数据收发。
  5. 数据返回:数据沿原路径返回:总线驱动 → 核心层 → 用户空间。
三层架构职责对比
层次 主要文件 核心数据结构 开发者关注点
设备驱动层 drivers/i2c/chips/ 或 drivers/misc/ struct i2c_driver, struct i2c_client 外设协议、probe/remove、用户接口
核心层 drivers/i2c/i2c-core.c struct i2c_bus_type API 使用、设备树绑定、驱动匹配规则
总线驱动层 drivers/i2c/busses/ struct i2c_adapter, struct i2c_algorithm 硬件寄存器、时钟配置、中断处理
工程实践建议:在移植 I2C 驱动时,建议按以下顺序排查问题:
1. 确认总线驱动层已正确注册 adapter(查看 /sys/class/i2c-adapter/)。
2. 使用 i2cdetect -l 列出所有 I2C 总线,确认硬件地址映射正确。
3. 使用 i2cdetect -y <bus> 扫描设备,验证外设是否应答。
4. 最后才调试设备驱动层的 probe 函数和通信逻辑。

3.5 设备树与三层架构的关联

设备树(Device Tree)是连接三层架构的关键纽带。在设备树中,I2C 控制器节点描述总线驱动层的硬件信息,而子节点描述设备驱动层的外设信息。核心层在系统启动时解析设备树,自动完成 i2c_adapter 和 i2c_client 的创建与匹配。

/* 设备树中的 I2C 节点示例 */
&i2c1 {
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_i2c1>;
    status = "okay";
    clock-frequency = <100000>;  /* 100kHz 标准模式 */

    /* 子节点:I2C 设备 */
    temperature-sensor@48 {
        compatible = "national,lm75";
        reg = <0x48>;           /* 7 位 I2C 地址 */
        status = "okay";
    };

    eeprom@50 {
        compatible = "atmel,24c02";
        reg = <0x50>;
        pagesize = <8>;
        status = "okay";
    };
};

设备树中的 compatible 属性与设备驱动中的 of_match_table 进行匹配,匹配成功后核心层调用驱动的 probe() 函数,完成设备初始化。这种机制使得驱动代码与硬件平台解耦,同一份驱动代码可以支持多个硬件平台。