4. Linux I2C 核心框架:数据结构与核心 API

Linux I2C 核心框架是连接 I2C 总线控制器硬件与上层设备驱动的中间层。它通过三个核心数据结构——i2c_adapter、i2c_algorithm 和 i2c_client——实现了硬件无关的抽象。理解这些结构及其 API 是编写正确、高效的 I2C 从设备驱动的基石。

核心概念: 在 Linux 内核中,i2c_adapter 代表一个 I2C 总线控制器(主设备端),i2c_algorithm 是控制器的通信方法集,而 i2c_client 则代表一个挂载在该总线上的从设备。驱动开发者主要与 i2c_client 和 i2c_msg 打交道。
Linux I2C 核心框架三层结构 Linux I2C 核心框架三层结构 驱动层:i2c_driver + i2c_client 从设备驱动注册、探测、读写操作 核心层:i2c-core 总线管理、设备枚举、消息传递、API 实现 适配器层:i2c_adapter + i2c_algorithm 硬件控制器抽象、底层通信方法实现 i2c_msg 是跨层传递的数据载体

4.1 i2c_adapter:总线适配器

i2c_adapter 是 I2C 总线控制器的内核抽象。每个物理 I2C 控制器(如 SoC 中的 I2C 外设)在系统中对应一个 i2c_adapter 实例。它负责管理总线编号、关联的算法、设备链表以及电源管理。

成员 类型 描述
nr int 总线编号,在系统中唯一,如 /dev/i2c-1 中的 1
name char[48] 适配器名称,用于调试和用户空间识别
algo struct i2c_algorithm * 指向该适配器使用的通信算法
algo_data void * 算法私有数据,通常指向控制器硬件信息
dev struct device 内核设备模型结构体
userspace_clients struct list_head 从用户空间创建的客户端列表
bus_lock struct mutex 总线访问互斥锁
timeout int 通信超时时间(单位:jiffies)
retries int 通信失败时的重试次数
关键点: 适配器的 nr 字段决定了其在用户空间中的设备节点编号。在 Android 系统中,I2C 控制器通常从 0 开始编号,例如 /dev/i2c-0、/dev/i2c-1 等。BSP 工程师在设备树中通过 reg 属性指定适配器编号。

4.2 i2c_algorithm:通信算法

i2c_algorithm 定义了适配器与 I2C 从设备进行通信的具体方法。它是硬件控制器驱动需要实现的核心接口。该结构体包含两组关键函数指针:主传输函数和从模式函数(较少用)。

struct i2c_algorithm {
    /* 主传输函数:发送 i2c_msg 数组 */
    int (*master_xfer)(struct i2c_adapter *adap,
                       struct i2c_msg *msgs,
                       int num);
    /* 主接收函数(已废弃,使用 master_xfer) */
    int (*master_recv)(struct i2c_adapter *adap,
                       struct i2c_msg *msg);
    /* 从模式传输函数 */
    int (*slave_xfer)(struct i2c_adapter *adap,
                      struct i2c_msg *msgs,
                      int num);
    /* 功能支持查询 */
    u32 (*functionality)(struct i2c_adapter *adap);
};
函数指针 用途 实现要求
master_xfer 核心传输入口,处理一次 I2C 事务(可能包含多个消息) 必须实现;返回成功传输的消息数,负值表示错误
functionality 返回适配器支持的功能掩码 必须实现;如 I2C_FUNC_I2C、I2C_FUNC_SMBUS_*
slave_xfer 从模式传输,适配器作为从设备时使用 可选,Android 中极少使用
注意事项: master_xfer 是 I2C 核心框架中最关键的底层函数。它接收一个 i2c_msg 数组,每个消息包含从设备地址、读写标志、数据缓冲区和长度。控制器驱动必须按顺序处理这些消息,并正确处理 START、STOP 和重复起始条件(RESTART)。
i2c_algorithm master_xfer 传输流程 i2c_algorithm master_xfer 传输流程 i2c_transfer() 调用 i2c-core 路由 adap->algo->master_xfer() 处理 i2c_msg 数组 硬件控制器寄存器

4.3 i2c_client:从设备客户端

i2c_client 是 I2C 从设备在内核中的代表。每个物理 I2C 从芯片在驱动探测成功后都会生成一个 i2c_client 实例。驱动开发者通过该结构体与设备进行所有交互。

struct i2c_client {
    unsigned short flags;       /* 标志位,如 I2C_CLIENT_TEN */
    unsigned short addr;        /* 从设备地址(7位或10位) */
    char name[I2C_NAME_SIZE];   /* 设备名称 */
    struct i2c_adapter *adapter; /* 所属适配器 */
    struct device dev;          /* 内核设备模型 */
    int irq;                    /* 中断号(若使用中断) */
    struct list_head detected;  /* 检测链表 */
    int (*detect)(struct i2c_client *, struct i2c_board_info *);
};
成员 说明 驱动开发关注点
addr 从设备地址,7 位模式范围为 0x03~0x77 必须与硬件原理图一致,设备树中通过 reg 属性指定
adapter 指向设备所在总线的适配器 用于调用 i2c_transfer() 时传入
irq 设备中断号,-1 表示不使用中断 在 probe() 中通过 client->irq 获取并注册中断处理函数
flags 设备标志,如 10 位地址标志 通常为 0,特殊场景设置 I2C_CLIENT_TEN
dev 内核设备结构体,用于电源管理和 sysfs 通过 &client->dev 传递给其他内核 API
最佳实践: 在驱动 probe() 函数中,应始终检查 client->irq 是否大于 0 再注册中断。对于不支持中断的从设备,应使用轮询方式或通过其他 GPIO 中断线间接获取状态变化。

4.4 核心 API 函数

Linux I2C 核心框架提供了一套丰富的 API,用于设备注册、消息传输和系统级管理。以下是最常用的核心 API:

4.4.1 消息传输 API

/* 核心传输函数:发送一组 I2C 消息 */
int i2c_transfer(struct i2c_adapter *adap,
                 struct i2c_msg *msgs,
                 int num);

/* 便捷函数:发送单条消息 */
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);

/* SMBus 兼容函数 */
s32 i2c_smbus_read_byte_data(const struct i2c_client *client,
                             u8 command);
s32 i2c_smbus_write_byte_data(const struct i2c_client *client,
                              u8 command, u8 value);
API 函数 适用场景 返回值
i2c_transfer() 需要组合消息(如先写寄存器地址再读数据) 成功传输的消息数,负数为错误
i2c_master_send() 向设备写入连续数据 成功写入的字节数,负数为错误
i2c_master_recv() 从设备读取连续数据 成功读取的字节数,负数为错误
i2c_smbus_read_byte_data() 读取指定寄存器的单字节值 读取到的值(正数),负数为错误
i2c_smbus_write_byte_data() 向指定寄存器写入单字节值 0 成功,负数为错误

4.4.2 设备注册与注销 API

/* 驱动注册/注销 */
int i2c_add_driver(struct i2c_driver *driver);
void i2c_del_driver(struct i2c_driver *driver);

/* 设备节点注册(通常由设备树或板级代码调用) */
struct i2c_client *i2c_new_client_device(struct i2c_adapter *adap,
                                         struct i2c_board_info const *info);
void i2c_unregister_device(struct i2c_client *client);

/* 设备检测与地址扫描 */
int i2c_register_board_info(int busnum,
                            struct i2c_board_info const *info,
                            unsigned len);
驱动开发流程: 在 Android 内核中,I2C 从设备驱动通常遵循以下步骤:
1. 定义 i2c_driver 结构体,填充 probe、remove、id_table 等回调。
2. 在 probe() 中通过 client->adapter 和 client->addr 确认设备存在。
3. 使用 i2c_master_send/recv 或 i2c_transfer 进行数据读写。
4. 若设备支持中断,通过 client->irq 注册中断处理函数。
5. 调用 i2c_add_driver() 注册驱动。
I2C 从设备驱动 probe 流程 I2C 从设备驱动 probe 流程 1. i2c_add_driver() 注册驱动到核心 2. 核心匹配 id_table 设备树/ACPI 匹配 3. 调用 probe() 驱动入口 4. 获取 client->irq 注册中断 5. 使用 i2c_transfer 初始化设备 6. 注册 misc/input 设备 probe() 返回 0 表示成功,返回负值表示驱动加载失败

4.5 i2c_msg:消息传递单元

i2c_msg 是 I2C 通信的最小数据单元。一次 I2C 事务可以包含多个 i2c_msg,核心框架会按顺序处理它们,并在消息之间自动插入重复起始条件(RESTART)。

struct i2c_msg {
    __u16 addr;    /* 从设备地址 */
    __u16 flags;   /* 标志位:I2C_M_RD 表示读,0 表示写 */
    __u16 len;     /* 消息数据长度 */
    __u8 *buf;     /* 数据缓冲区指针 */
};
标志位 值 含义
I2C_M_RD 0x0001 读消息,数据从设备传输到主机
I2C_M_TEN 0x0010 使用 10 位地址
I2C_M_NOSTART 0x4000 不发送起始条件,用于组合消息中的连续片段
I2C_M_STOP 0x8000 强制在消息后发送停止条件
典型用法: 读取 I2C 从设备寄存器的标准模式是组合两个消息:
消息 0:写操作,发送寄存器地址(flags = 0)。
消息 1:读操作,读取寄存器值(flags = I2C_M_RD)。
核心框架会在两个消息之间自动插入 RESTART 条件,无需驱动开发者手动处理。
/* 示例:读取从设备 0x48 的寄存器 0x05 */
struct i2c_msg msgs[2];
u8 reg_addr = 0x05;
u8 reg_value;

msgs[0].addr = 0x48;
msgs[0].flags = 0;          /* 写 */
msgs[0].len = 1;
msgs[0].buf = ®_addr;

msgs[1].addr = 0x48;
msgs[1].flags = I2C_M_RD;   /* 读 */
msgs[1].len = 1;
msgs[1].buf = ®_value;

ret = i2c_transfer(client->adapter, msgs, 2);
if (ret != 2) {
    dev_err(&client->dev, "I2C read failed: %d\n", ret);
    return -EIO;
}
/* reg_value 现在包含寄存器 0x05 的值 */
常见错误: 在组合消息中,所有消息的 addr 字段必须相同,因为 I2C 总线在 RESTART 后不会改变从设备选择。如果地址不同,必须拆分为独立的 i2c_transfer() 调用,每次调用之间总线会释放。

4.6 数据结构关系总览

以下表格总结了三个核心数据结构在 I2C 框架中的角色和关系:

数据结构 角色 创建者 使用者 生命周期
i2c_adapter 总线控制器抽象 SoC 平台驱动 核心框架、所有从设备驱动 系统启动时创建,关机时销毁
i2c_algorithm 通信方法实现 控制器驱动 i2c_adapter 内部使用 与适配器相同
i2c_client 从设备实例 设备树/板级代码/用户空间 从设备驱动 设备探测时创建,移除时销毁
i2c_msg 数据传输单元 驱动开发者 i2c_transfer() 栈上分配,传输完成后失效
学习建议: 在阅读或编写 I2C 从设备驱动时,始终牢记 i2c_client 是驱动与硬件之间的桥梁。通过 client->adapter 可以访问总线,通过 client->addr 确定设备地址,通过 client->irq 处理中断。这三个字段覆盖了 I2C 从设备驱动 90% 以上的交互需求。