4. I2C核心层API:i2c_add_adapter、i2c_add_driver、i2c_new_device、i2c_transfer、i2c_master_send/recv接口详解

I2C核心层(I2C Core)是Linux内核中I2C子系统的心脏,它向上为设备驱动提供统一的操作接口,向下为适配器驱动提供注册与消息传递的框架。本章将深入剖析五个最核心的API:i2c_add_adapter、i2c_add_driver、i2c_new_device、i2c_transfer以及i2c_master_send/recv。理解这些接口的调用时机、参数含义与内部流转,是编写高质量I2C驱动的基石。

学习提示: 这五个API分别对应I2C子系统的四个关键阶段:适配器注册(硬件层)、设备注册(设备层)、驱动注册(驱动层)和数据传输(通信层)。建议结合实际的硬件时序图来理解它们之间的依赖关系。

4.1 适配器注册:i2c_add_adapter

i2c_add_adapter 是I2C适配器驱动(通常由SoC的I2C控制器驱动实现)向核心层注册一个I2C总线适配器的入口。适配器代表一个物理的I2C控制器,它负责管理一条I2C总线上的所有通信时序。

4.1.1 函数原型与参数

#include <linux/i2c.h>

int i2c_add_adapter(struct i2c_adapter *adapter);
参数类型说明
adapterstruct i2c_adapter *指向已填充的适配器结构体,包含总线号、算法指针、超时设置等
返回值int成功返回0;失败返回负的错误码(如 -ENOMEM、-EBUSY)

4.1.2 核心数据结构:struct i2c_adapter

在调用 i2c_add_adapter 之前,驱动必须初始化 struct i2c_adapter 的关键字段:

struct i2c_adapter {
    struct module *owner;           // 通常设为 THIS_MODULE
    unsigned int class;             // 设备类标志,如 I2C_CLASS_HWMON
    const struct i2c_algorithm *algo; // 算法指针,包含 master_xfer 等函数
    struct device dev;              // 内核设备结构
    unsigned int nr;                // 总线编号(可动态分配或静态指定)
    char name[48];                  // 适配器名称,用于 /dev/i2c-N 的命名
    // ... 其他内部字段
};

4.1.3 内部流程

  1. 编号分配: 如果 adapter->nr 为 -1,核心层自动分配一个空闲的总线号;否则使用指定的编号。
  2. 设备注册: 将适配器作为一个内核设备注册到设备模型,创建 /dev/i2c-N 设备节点。
  3. 扫描设备: 如果适配器声明了 class,核心层会扫描该总线上预注册的设备列表,并尝试绑定驱动。
  4. sysfs 接口: 在 /sys/class/i2c-adapter/i2c-N/ 下创建属性文件。
关键点: i2c_add_adapter 必须在适配器硬件初始化完成(时钟使能、中断注册、IO资源映射)之后调用。调用成功后,该适配器即可被上层驱动使用。

4.2 设备注册:i2c_new_device

i2c_new_device 用于在指定的I2C总线上创建一个新的I2C从设备。它通常在两种场景下使用:一是板级代码(Board Code)中静态声明设备;二是设备树(Device Tree)解析后动态创建设备。

4.2.1 函数原型与参数

struct i2c_client *i2c_new_device(struct i2c_adapter *adap,
                                  struct i2c_board_info const *info);
参数类型说明
adapstruct i2c_adapter *设备挂载的适配器指针
infostruct i2c_board_info *板级信息,包含设备地址、名称、IRQ号、平台数据等
返回值struct i2c_client *成功返回创建的client指针;失败返回 NULL

4.2.2 核心数据结构:struct i2c_board_info

struct i2c_board_info {
    char type[I2C_NAME_SIZE];   // 设备名称,用于驱动匹配
    unsigned short addr;        // 7位或10位从设备地址
    unsigned short flags;       // 标志位,如 I2C_CLIENT_TEN 表示10位地址
    int irq;                    // 中断号(可选)
    void *platform_data;        // 平台特定数据
    struct device_node *of_node; // 设备树节点指针(可选)
    // ... 其他字段
};

4.2.3 内部流程

  1. 地址校验: 检查地址是否合法(7位地址范围 0x03-0x77,保留地址除外)。
  2. client 分配: 分配并初始化 struct i2c_client,填充适配器指针、地址、名称等。
  3. 设备注册: 将 client 作为适配器的子设备注册到内核设备模型。
  4. 触发探测: 如果已有匹配的驱动注册,立即调用驱动的 probe 函数。
注意事项: i2c_new_device 不会检测总线上是否真的存在该设备。它只是在内核中创建一个设备对象。真正的硬件存在性检测由驱动的 probe 函数或单独的检测函数完成。

4.3 驱动注册:i2c_add_driver

i2c_add_driver 是I2C设备驱动向核心层注册的入口。它封装了内核标准的驱动注册机制,并自动处理与I2C设备模型的匹配逻辑。

4.3.1 函数原型与参数

int i2c_add_driver(struct i2c_driver *driver);

// 常用简化宏:
module_i2c_driver(my_i2c_driver);
参数类型说明
driverstruct i2c_driver *指向已填充的I2C驱动结构体
返回值int成功返回0;失败返回负的错误码

4.3.2 核心数据结构:struct i2c_driver

struct i2c_driver {
    unsigned int class;                 // 驱动支持的设备类
    int (*probe)(struct i2c_client *);  // 设备匹配成功时调用
    int (*remove)(struct i2c_client *); // 设备移除时调用
    void (*shutdown)(struct i2c_client *);
    struct device_driver driver;        // 内核标准驱动结构
    const struct i2c_device_id *id_table; // 设备ID匹配表
    // ... 其他回调
};

4.3.3 匹配流程

当 i2c_add_driver 被调用时,核心层会遍历所有已注册的I2C适配器,对每个适配器上的设备进行匹配:

  • ID表匹配: 比较 i2c_driver->id_table 中的 name 与 i2c_client->name。
  • 设备树匹配: 如果使用设备树,比较 compatible 属性。
  • ACPI匹配: 在ACPI系统上使用 HID 匹配。
  • 匹配成功后调用 probe 函数,传入匹配的 i2c_client 指针。
最佳实践: 推荐使用 module_i2c_driver() 宏,它自动生成 module_init 和 module_exit 函数,简化驱动注册代码。

4.4 数据传输核心:i2c_transfer

i2c_transfer 是I2C核心层提供的最底层数据传输接口。它接收一个消息数组,并将它们组合成一个完整的I2C传输事务(支持重复起始条件)。

4.4.1 函数原型与参数

int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
参数类型说明
adapstruct i2c_adapter *适配器指针
msgsstruct i2c_msg *消息数组指针
numint消息数量
返回值int成功返回传输的消息数量;失败返回负的错误码

4.4.2 核心数据结构:struct i2c_msg

struct i2c_msg {
    __u16 addr;     // 从设备地址(7位或10位)
    __u16 flags;    // 标志位:I2C_M_RD(读)、I2C_M_TEN(10位地址)等
    __u16 len;      // 数据长度(字节)
    __u8 *buf;      // 数据缓冲区指针
};

4.4.3 组合事务示例

以下代码演示如何使用 i2c_transfer 实现一个典型的“先写寄存器地址,再读数据”的复合操作:

int i2c_read_reg(struct i2c_client *client, u8 reg_addr, u8 *data, u16 len)
{
    struct i2c_msg msgs[2];
    int ret;

    // 第一条消息:写寄存器地址
    msgs[0].addr = client->addr;
    msgs[0].flags = 0;              // 写操作
    msgs[0].len = 1;
    msgs[0].buf = &reg_addr;

    // 第二条消息:读数据(自动插入重复起始条件)
    msgs[1].addr = client->addr;
    msgs[1].flags = I2C_M_RD;       // 读操作
    msgs[1].len = len;
    msgs[1].buf = data;

    ret = i2c_transfer(client->adapter, msgs, 2);
    if (ret < 0)
        return ret;
    return (ret == 2) ? 0 : -EIO;
}
性能要点: i2c_transfer 是同步阻塞调用。在中断上下文中调用时,如果适配器不支持原子操作,可能导致休眠。对于实时性要求高的场景,应考虑使用异步传输或确保适配器支持 master_xfer_atomic。

4.5 便捷封装:i2c_master_send 与 i2c_master_recv

i2c_master_send 和 i2c_master_recv 是核心层对 i2c_transfer 的简化封装,专门用于单条消息的发送和接收操作。它们隐藏了消息结构体的构造细节,适合简单的读写场景。

4.5.1 函数原型

int i2c_master_send(const struct i2c_client *client, const char *buf, int count);
int i2c_master_recv(const struct i2c_client *client, char *buf, int count);
参数说明
clientI2C从设备指针,包含适配器和地址信息
buf数据缓冲区(发送时为待发送数据,接收时为接收缓冲区)
count数据长度(字节)
返回值成功返回实际传输的字节数;失败返回负的错误码

4.5.2 内部实现

这两个函数内部直接调用 i2c_transfer,并自动构造单条 i2c_msg:

// i2c_master_send 的内部逻辑(简化)
int i2c_master_send(const struct i2c_client *client, const char *buf, int count)
{
    struct i2c_msg msg;
    msg.addr = client->addr;
    msg.flags = client->flags & I2C_M_TEN;
    msg.len = count;
    msg.buf = (char *)buf;
    return i2c_transfer(client->adapter, &msg, 1);
}

4.5.3 使用对比

场景推荐接口原因
单次写数据i2c_master_send代码简洁,无需构造msg结构
单次读数据i2c_master_recv同上,适合简单读取
先写地址再读数据i2c_transfer需要组合消息,支持重复起始条件
批量或复杂事务i2c_transfer支持多条消息、不同方向、不同地址
避坑指南: i2c_master_send/recv 不支持10位地址的自动处理。如果使用10位地址,必须手动设置 client->flags 中的 I2C_M_TEN 标志,或直接使用 i2c_transfer。

4.6 API关系总览

下图展示了五个核心API在I2C子系统中的层级关系与调用链路:

I2C核心层API关系与调用链路 设备驱动层(I2C Client Driver) i2c_add_driver / i2c_new_device / i2c_master_send / i2c_master_recv I2C核心层(I2C Core) i2c_transfer(核心传输引擎) 设备模型管理 / 总线匹配 / 消息队列调度 i2c_add_adapter(适配器注册入口) 适配器驱动层(I2C Adapter Driver) struct i2c_algorithm master_xfer / smbus_xfer / functionality 硬件层(I2C Controller) SCL / SDA 时序 / 中断处理 / DMA传输 API调用关系说明 1. i2c_add_adapter → 注册硬件控制器 2. i2c_new_device → 创建从设备对象 3. i2c_add_driver → 注册设备驱动 4. i2c_transfer → 核心传输接口 5. i2c_master_send/recv → 便捷封装 所有传输最终调用 master_xfer

4.7 典型调用序列

在一个完整的I2C设备驱动生命周期中,上述API的典型调用顺序如下:

  1. 系统启动阶段: 适配器驱动调用 i2c_add_adapter 注册I2C控制器。
  2. 设备枚举阶段: 板级代码或设备树解析器调用 i2c_new_device 创建从设备。
  3. 驱动加载阶段: 设备驱动调用 i2c_add_driver 注册自身,触发 probe。
  4. 运行阶段: 驱动在 probe 或文件操作中使用 i2c_master_send/recv 或 i2c_transfer 进行数据交换。
  5. 卸载阶段: 驱动调用 i2c_del_driver,适配器调用 i2c_del_adapter 清理资源。
总结: i2c_add_adapter 和 i2c_add_driver 是注册接口,i2c_new_device 是设备创建接口,i2c_transfer 是传输核心,i2c_master_send/recv 是便捷封装。掌握这五个API,就掌握了I2C驱动开发的80%的接口知识。下一章将深入探讨中断处理与DMA在I2C传输中的集成优化。