4. Linux I2C 子系统框架:三层架构详解
在 RK 平台 Sensor 驱动开发中,理解 Linux I2C 子系统的三层架构是进行通信故障排查的理论基础。该架构将复杂的 I2C 通信过程抽象为三个层次,实现了硬件无关性与驱动代码复用的设计目标。任何 I2C 通信故障,最终都可以映射到这三层中的某一层或层间接口上。
核心观点: I2C 子系统分为 I2C 核心层、I2C 总线驱动 和 I2C 设备驱动。Sensor 驱动开发者主要工作在最上层的设备驱动层,但故障排查往往需要向下穿透到总线驱动和核心层。
4.1 I2C 核心层 (I2C Core)
I2C 核心层是 Linux I2C 子系统的中枢,它不直接操作硬件,而是提供一套统一的框架和 API,用于管理 I2C 总线、设备和驱动。核心层的主要职责包括:
- 总线管理: 维护系统中的所有 I2C 总线适配器 (Adapter) 列表。
- 设备与驱动管理: 负责 I2C 设备的注册、注销以及驱动的匹配 (Match) 与绑定 (Bind)。
- 协议抽象: 提供标准的 I2C 消息传输接口 (
i2c_transfer),并兼容 SMBus 协议。 - 锁机制: 提供总线访问锁,防止多线程/多进程并发访问导致的数据混乱。
调试提示: 在 RK 平台排查 Sensor 无法被系统识别时,应首先检查
/sys/bus/i2c/devices/ 目录下是否存在对应的设备节点。如果不存在,问题通常出在核心层的设备枚举或设备树 (Device Tree) 解析阶段。
4.2 I2C 总线驱动 (I2C Bus Driver / Adapter)
I2C 总线驱动直接与 SoC 内部的 I2C 控制器硬件交互,负责最底层的时序生成、起始/停止条件、数据收发、ACK/NACK 检测等物理层操作。在 RK 平台上,常见的 I2C 控制器驱动包括:
| 控制器类型 | RK 平台常见型号 | 驱动文件 (内核源码) | 关键特性 |
|---|---|---|---|
| Synopsys DesignWare I2C | RK3568, RK3588, RK3399 | drivers/i2c/busses/i2c-designware-platdrv.c |
支持标准/快速/高速模式,内置 FIFO |
| Rockchip I2C (旧版) | RK3288, RK3128 | drivers/i2c/busses/i2c-rk3x.c |
兼容性好,时序配置灵活 |
| RISC-V I2C (新平台) | RK3562, 部分 MCU 融合芯片 | drivers/i2c/busses/i2c-rk3562.c |
低功耗设计,支持 DMA |
总线驱动通过 struct i2c_algorithm 结构体向上层提供通信能力,其中最核心的函数指针是 master_xfer:
// 定义在 include/linux/i2c.h
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);
};
故障排查重点: 如果
i2c_transfer 返回负值(如 -EIO、-ETIMEDOUT),问题大概率出在总线驱动层或硬件层。此时应使用示波器抓取 SCL/SDA 波形,检查时序是否符合 Sensor 数据手册要求,以及 ACK/NACK 信号是否正确。
4.3 I2C 设备驱动 (I2C Device Driver)
I2C 设备驱动是离 Sensor 硬件最近的软件抽象层,也是驱动开发者主要编码的地方。它通过 I2C 核心层提供的 API 与总线驱动通信,实现对 Sensor 芯片的寄存器读写控制。一个标准的 I2C 设备驱动包含以下关键部分:
- 设备树匹配表 (of_match_table): 用于与设备树中的 Sensor 节点进行匹配。
- I2C 设备 ID 表 (i2c_device_id): 用于传统非设备树方式的匹配。
- probe 函数: 设备与驱动匹配成功后调用,完成硬件初始化、数据结构和 sysfs 接口创建。
- remove 函数: 设备移除或驱动卸载时调用,释放资源。
- regmap 或直接 I2C 读写函数: 封装对 Sensor 寄存器的读写操作。
以下是一个典型的 RK 平台 Sensor I2C 设备驱动框架代码:
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/of.h>
static const struct of_device_id sensor_of_match[] = {
{ .compatible = "rockchip,example-sensor" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, sensor_of_match);
static const struct i2c_device_id sensor_id[] = {
{ "example-sensor", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, sensor_id);
static int sensor_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
struct device *dev = &client->dev;
int ret;
dev_info(dev, "Sensor probe start, addr=0x%02x\n", client->addr);
/* 1. 检查 I2C 通信是否正常:读取 WHO_AM_I 寄存器 */
ret = i2c_smbus_read_byte_data(client, 0x0F);
if (ret < 0) {
dev_err(dev, "Failed to read WHO_AM_I: %d\n", ret);
return ret;
}
dev_info(dev, "WHO_AM_I = 0x%02x\n", ret);
/* 2. 初始化 Sensor 配置 */
/* ... */
/* 3. 注册输入设备或 IIO 设备 */
/* ... */
return 0;
}
static int sensor_remove(struct i2c_client *client)
{
dev_info(&client->dev, "Sensor remove\n");
/* 释放资源 */
return 0;
}
static struct i2c_driver sensor_driver = {
.driver = {
.name = "example-sensor",
.of_match_table = sensor_of_match,
},
.probe = sensor_probe,
.remove = sensor_remove,
.id_table = sensor_id,
};
module_i2c_driver(sensor_driver);
MODULE_AUTHOR("RK Platform Expert");
MODULE_DESCRIPTION("Example Sensor I2C Driver for RK Platform");
MODULE_LICENSE("GPL");
调试技巧: 在
probe 函数中,建议首先通过 i2c_smbus_read_byte_data 读取 Sensor 的 WHO_AM_I 或 CHIP_ID 寄存器。如果该操作失败,说明 I2C 通信链路存在故障,无需继续执行后续初始化代码。这是最快速的故障定位方法。
4.4 三层架构的协作关系与数据流
当 Sensor 驱动需要读取一个寄存器时,完整的调用链如下:
| 步骤 | 所在层 | 函数/操作 | 说明 |
|---|---|---|---|
| 1 | 设备驱动 | i2c_smbus_read_byte_data(client, reg) |
Sensor 驱动发起读请求 |
| 2 | 核心层 | i2c_smbus_xfer() → i2c_transfer() |
构造 i2c_msg 消息,获取总线锁 |
| 3 | 总线驱动 | master_xfer() |
操作控制器寄存器,启动硬件传输 |
| 4 | 硬件 | SCL/SDA 时序 | 发送从机地址 + 寄存器地址 + 重启 + 读取数据 |
| 5 | 总线驱动 | 读取数据寄存器 | 从 I2C 控制器 FIFO 中取出接收到的字节 |
| 6 | 核心层 | 释放锁,返回数据 | 将数据逐层返回给设备驱动 |
4.5 故障排查中的分层定位法
在实际的 RK 平台 Sensor 调试中,建议按照以下分层策略快速定位 I2C 通信故障:
黄金法则: 永远从最底层开始排查。先确认硬件连接和时序,再检查总线驱动,最后才怀疑设备驱动代码。
- 硬件层 (物理层): 使用示波器或逻辑分析仪测量 SCL/SDA 波形。检查上拉电阻 (通常 4.7kΩ)、电平匹配 (1.8V vs 3.3V)、以及 Sensor 供电是否正常。
- 总线驱动层: 使用
i2cdetect -y <bus_number>命令扫描总线,确认 Sensor 的 I2C 地址是否出现在正确的总线上。如果地址显示为UU表示驱动已占用,显示为--表示无应答。 - 核心层: 检查
/sys/bus/i2c/devices/下是否生成了正确的设备节点。检查内核日志 (dmesg) 中是否有i2c core相关的错误信息。 - 设备驱动层: 在
probe函数中添加调试打印,确认驱动是否被成功加载。使用i2cget命令手动读取 Sensor 寄存器,验证驱动逻辑是否正确。
RK 平台专用工具: 在 Rockchip 的 BSP 中,
i2c-tools 是调试 I2C 设备最直接的工具。使用 i2cdump -y <bus> <addr> 可以批量 dump 设备所有寄存器的值,快速判断 Sensor 是否处于正常工作状态。