4. Linux 内核 I2C 子系统架构

Linux 内核的 I2C 子系统采用经典的分层架构设计,将复杂的 I2C 通信控制抽象为三个独立的层次:I2C 核心层、I2C 总线驱动层和I2C 设备驱动层。这种分层设计极大地提高了代码的可移植性和复用性,使得驱动开发者无需关心底层硬件细节,只需专注于设备特有的逻辑。

核心概念:I2C 子系统通过 i2c_adapter 和 i2c_client 两个核心数据结构,将物理总线控制器与具体的外设设备解耦。适配器代表硬件总线控制器,客户端代表挂载在该总线上的从设备。

4.1 三层架构总览

下图展示了 I2C 子系统的三层架构及其数据流关系。用户空间通过 VFS 访问设备驱动,设备驱动层调用核心层 API,核心层再通过适配器驱动与硬件寄存器交互。

Linux I2C 子系统三层架构 用户空间 (应用程序 / VFS) I2C 设备驱动层 i2c_driver / i2c_client file_operations / 设备节点 I2C 核心层 i2c-core / i2c-dev 总线管理 / 设备注册 / 通信算法 I2C 总线驱动层 i2c_adapter / i2c_algorithm 硬件寄存器操作 / 时序控制 硬件 (I2C 控制器 + 外设) 分层职责 设备驱动: 实现设备特有逻辑 核心层: 提供统一 API 接口 总线驱动: 操作硬件寄存器

4.2 I2C 核心层 (I2C Core)

I2C 核心层是子系统的中枢,位于 drivers/i2c/i2c-core.c 中。它不直接操作硬件,而是提供一套标准化的框架和 API,供上层设备驱动和下层总线驱动调用。

核心功能 说明 关键 API / 机制
总线注册与管理 管理系统中所有的 I2C 总线适配器,维护总线编号与适配器的映射关系 i2c_add_adapter() / i2c_del_adapter()
设备驱动注册 管理 I2C 设备驱动的注册与注销,维护驱动与设备的匹配关系 i2c_add_driver() / i2c_del_driver()
通信算法封装 封装底层适配器的通信算法,提供统一的读写接口 i2c_transfer() / i2c_master_send() / i2c_master_recv()
设备节点创建 通过 i2c-dev 模块在 /dev 下创建字符设备节点,支持用户空间直接访问 i2cdev_attach_adapter() / i2cdev_detach_adapter()
SMBus 协议支持 提供 SMBus 协议的封装实现,兼容 SMBus 设备 i2c_smbus_read_byte() / i2c_smbus_write_word_data()
核心层关键数据结构:
struct bus_type i2c_bus_type — 定义 I2C 总线类型,负责驱动与设备的匹配流程。
struct i2c_adapter — 代表一个 I2C 总线适配器。
struct i2c_client — 代表一个 I2C 从设备。
struct i2c_driver — 代表一个 I2C 设备驱动。

4.3 I2C 总线驱动层 (I2C Bus Driver)

总线驱动层直接与硬件 I2C 控制器交互,负责实现具体的时序控制和寄存器操作。每个 I2C 控制器硬件(如 SoC 内部的 I2C IP 核)都需要一个对应的总线驱动。

4.3.1 核心数据结构:i2c_adapter

struct i2c_adapter 是总线驱动层的核心,它抽象了一个 I2C 总线控制器。驱动开发者需要填充该结构体并注册到核心层。

struct i2c_adapter {
    struct module *owner;
    unsigned int class;               // 适配器支持的设备类
    const struct i2c_algorithm *algo; // 通信算法指针
    void *algo_data;                  // 算法私有数据

    int nr;                           // 总线编号
    struct device dev;                // 内核设备结构体
    struct list_head userspace_clients; // 用户空间客户端列表

    /* 其他字段省略 */
};

4.3.2 通信算法:i2c_algorithm

struct i2c_algorithm 定义了适配器与从设备通信的具体方法。总线驱动必须实现 master_xfer 回调,核心层通过该回调发起实际的数据传输。

struct i2c_algorithm {
    int (*master_xfer)(struct i2c_adapter *adap,
                       struct i2c_msg *msgs,
                       int num);
    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);
};
开发注意事项:总线驱动层通常由 SoC 厂商或 BSP 团队提供,应用层驱动开发者一般不需要编写总线驱动。但在移植新硬件平台时,必须确保总线驱动正确实现了 master_xfer 函数,并处理好时钟延展、仲裁、错误恢复等硬件细节。

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

设备驱动层是嵌入式工程师最常接触的层次。它负责实现具体外设(如温度传感器、加速度计、触摸屏控制器)的驱动逻辑,通过核心层提供的 API 与硬件通信。

4.4.1 核心数据结构:i2c_driver 与 i2c_client

struct i2c_driver 代表一个设备驱动,struct i2c_client 代表一个具体的从设备实例。两者通过设备树或板级代码进行绑定。

// i2c_driver 结构体(简化)
struct i2c_driver {
    int (*probe)(struct i2c_client *client,
                 const struct i2c_device_id *id);
    int (*remove)(struct i2c_client *client);
    struct device_driver driver;
    const struct i2c_device_id *id_table;
    const struct of_device_id *of_match_table;
};

// i2c_client 结构体(简化)
struct i2c_client {
    unsigned short addr;          // 从设备地址(7位或10位)
    char name[I2C_NAME_SIZE];    // 设备名称
    struct i2c_adapter *adapter; // 所属适配器
    struct device dev;           // 内核设备结构体
    int irq;                     // 中断号(如支持)
};

4.4.2 设备驱动注册流程

  1. 定义匹配表:使用 of_match_table 或 id_table 声明驱动支持的设备。
  2. 实现 probe 函数:在 probe 中完成设备初始化、数据结构分配、中断注册等操作。
  3. 实现 remove 函数:在 remove 中释放资源、注销中断、清理设备状态。
  4. 注册驱动:调用 i2c_add_driver() 将驱动注册到核心层。
static const struct of_device_id bme280_of_match[] = {
    { .compatible = "bosch,bme280" },
    { }
};
MODULE_DEVICE_TABLE(of, bme280_of_match);

static int bme280_probe(struct i2c_client *client)
{
    struct bme280_data *data;
    // 分配私有数据结构
    data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
    if (!data)
        return -ENOMEM;

    i2c_set_clientdata(client, data);
    // 初始化设备、注册中断等
    return 0;
}

static struct i2c_driver bme280_driver = {
    .probe    = bme280_probe,
    .remove   = bme280_remove,
    .id_table = bme280_id,
    .driver = {
        .name = "bme280",
        .of_match_table = bme280_of_match,
    },
};

module_i2c_driver(bme280_driver);

4.5 i2c_adapter 与 i2c_client 的关系

i2c_adapter 和 i2c_client 是 I2C 子系统中最重要的两个对象,它们之间的关系可以用“总线 — 设备”模型来理解。

i2c_adapter 与 i2c_client 关系模型 i2c_adapter I2C 总线控制器 #0 i2c_client #1 addr=0x48 (温度传感器) i2c_client #2 addr=0x76 (气压计) i2c_client #3 addr=0x3C (OLED 屏) 关系说明 一个 i2c_adapter 可以挂载多个 i2c_client,每个 client 通过唯一的 7 位或 10 位地址区分。
对比维度 i2c_adapter i2c_client
角色 总线主控端(Master) 总线从设备端(Slave)
对应硬件 SoC 内部的 I2C 控制器 外部的 I2C 从设备芯片
核心字段 nr(总线编号)、algo(算法) addr(从设备地址)、adapter(所属适配器)
注册时机 系统启动时,由总线驱动注册 设备树解析或板级代码枚举时创建
生命周期 与硬件控制器共存亡 与具体设备绑定,可热插拔
驱动类型 总线驱动(BSP 层) 设备驱动(应用层)

4.6 数据流与消息传递

当设备驱动需要与从设备通信时,数据流经过以下路径:

  1. 设备驱动调用 i2c_master_send() 或 i2c_transfer()。
  2. 核心层将请求封装为 struct i2c_msg 消息数组,调用适配器的 master_xfer 回调。
  3. 总线驱动解析消息,操作硬件寄存器产生 SCL/SDA 时序。
  4. 硬件控制器完成物理层传输,返回 ACK/NACK 状态。
  5. 结果逐层返回,设备驱动获得传输状态或数据。
// i2c_msg 结构体定义
struct i2c_msg {
    __u16 addr;    // 从设备地址
    __u16 flags;   // 传输标志(读/写)
    __u16 len;     // 数据长度
    __u8 *buf;     // 数据缓冲区
};

// 设备驱动典型调用示例
struct i2c_msg msgs[2];
int ret;

// 写寄存器地址
msgs[0].addr = client->addr;
msgs[0].flags = 0;          // 写标志
msgs[0].len = 1;
msgs[0].buf = ®_addr;

// 读数据
msgs[1].addr = client->addr;
msgs[1].flags = I2C_M_RD;  // 读标志
msgs[1].len = 2;
msgs[1].buf = data_buf;

ret = i2c_transfer(client->adapter, msgs, 2);
if (ret < 0)
    dev_err(&client->dev, "I2C transfer failed\n");
工程实践建议:在编写设备驱动时,优先使用 i2c_smbus_read_byte_data() 等 SMBus 封装函数,它们比直接构造 i2c_msg 更简洁、更安全。只有在需要组合传输(如先写地址再读数据)或传输大数据块时,才使用 i2c_transfer()。

4.7 本章小结

Linux I2C 子系统的三层架构设计清晰地将硬件操作与设备逻辑分离:

  • 核心层提供总线管理、驱动注册和通信 API 的统一框架。
  • 总线驱动层通过 i2c_adapter 和 i2c_algorithm 封装硬件时序控制。
  • 设备驱动层通过 i2c_driver 和 i2c_client 实现具体外设功能。