4. Linux 内核 I2C 子系统架构
Linux 内核的 I2C 子系统采用经典的分层架构设计,将复杂的 I2C 通信控制抽象为三个独立的层次:I2C 核心层、I2C 总线驱动层和I2C 设备驱动层。这种分层设计极大地提高了代码的可移植性和复用性,使得驱动开发者无需关心底层硬件细节,只需专注于设备特有的逻辑。
i2c_adapter 和 i2c_client 两个核心数据结构,将物理总线控制器与具体的外设设备解耦。适配器代表硬件总线控制器,客户端代表挂载在该总线上的从设备。
4.1 三层架构总览
下图展示了 I2C 子系统的三层架构及其数据流关系。用户空间通过 VFS 访问设备驱动,设备驱动层调用核心层 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);
};
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 设备驱动注册流程
- 定义匹配表:使用
of_match_table或id_table声明驱动支持的设备。 - 实现 probe 函数:在 probe 中完成设备初始化、数据结构分配、中断注册等操作。
- 实现 remove 函数:在 remove 中释放资源、注销中断、清理设备状态。
- 注册驱动:调用
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 |
|---|---|---|
| 角色 | 总线主控端(Master) | 总线从设备端(Slave) |
| 对应硬件 | SoC 内部的 I2C 控制器 | 外部的 I2C 从设备芯片 |
| 核心字段 | nr(总线编号)、algo(算法) |
addr(从设备地址)、adapter(所属适配器) |
| 注册时机 | 系统启动时,由总线驱动注册 | 设备树解析或板级代码枚举时创建 |
| 生命周期 | 与硬件控制器共存亡 | 与具体设备绑定,可热插拔 |
| 驱动类型 | 总线驱动(BSP 层) | 设备驱动(应用层) |
4.6 数据流与消息传递
当设备驱动需要与从设备通信时,数据流经过以下路径:
- 设备驱动调用
i2c_master_send()或i2c_transfer()。 - 核心层将请求封装为
struct i2c_msg消息数组,调用适配器的master_xfer回调。 - 总线驱动解析消息,操作硬件寄存器产生 SCL/SDA 时序。
- 硬件控制器完成物理层传输,返回 ACK/NACK 状态。
- 结果逐层返回,设备驱动获得传输状态或数据。
// 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实现具体外设功能。