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驱动的基石。
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);
| 参数 | 类型 | 说明 |
|---|---|---|
adapter | struct 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 内部流程
- 编号分配: 如果
adapter->nr为-1,核心层自动分配一个空闲的总线号;否则使用指定的编号。 - 设备注册: 将适配器作为一个内核设备注册到设备模型,创建
/dev/i2c-N设备节点。 - 扫描设备: 如果适配器声明了
class,核心层会扫描该总线上预注册的设备列表,并尝试绑定驱动。 - 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);
| 参数 | 类型 | 说明 |
|---|---|---|
adap | struct i2c_adapter * | 设备挂载的适配器指针 |
info | struct 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 内部流程
- 地址校验: 检查地址是否合法(7位地址范围 0x03-0x77,保留地址除外)。
- client 分配: 分配并初始化
struct i2c_client,填充适配器指针、地址、名称等。 - 设备注册: 将 client 作为适配器的子设备注册到内核设备模型。
- 触发探测: 如果已有匹配的驱动注册,立即调用驱动的
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);
| 参数 | 类型 | 说明 |
|---|---|---|
driver | struct 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);
| 参数 | 类型 | 说明 |
|---|---|---|
adap | struct i2c_adapter * | 适配器指针 |
msgs | struct i2c_msg * | 消息数组指针 |
num | int | 消息数量 |
| 返回值 | 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 = ®_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);
| 参数 | 说明 |
|---|---|
client | I2C从设备指针,包含适配器和地址信息 |
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子系统中的层级关系与调用链路:
4.7 典型调用序列
在一个完整的I2C设备驱动生命周期中,上述API的典型调用顺序如下:
- 系统启动阶段: 适配器驱动调用
i2c_add_adapter注册I2C控制器。 - 设备枚举阶段: 板级代码或设备树解析器调用
i2c_new_device创建从设备。 - 驱动加载阶段: 设备驱动调用
i2c_add_driver注册自身,触发probe。 - 运行阶段: 驱动在
probe或文件操作中使用i2c_master_send/recv或i2c_transfer进行数据交换。 - 卸载阶段: 驱动调用
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传输中的集成优化。