4. Linux I2C 子系统框架:三层架构详解

在 RK 平台 Sensor 驱动开发中,理解 Linux I2C 子系统的三层架构是进行通信故障排查的理论基础。该架构将复杂的 I2C 通信过程抽象为三个层次,实现了硬件无关性与驱动代码复用的设计目标。任何 I2C 通信故障,最终都可以映射到这三层中的某一层或层间接口上。

核心观点: I2C 子系统分为 I2C 核心层、I2C 总线驱动 和 I2C 设备驱动。Sensor 驱动开发者主要工作在最上层的设备驱动层,但故障排查往往需要向下穿透到总线驱动和核心层。
Linux I2C 子系统三层架构总览 Linux I2C 子系统三层架构总览 用户空间 (User Space) 应用程序 / Sensor HAL / i2c-tools (i2cget, i2cset, i2cdetect) I2C 设备驱动层 (I2C Device Driver) Sensor 驱动 (如: mpu6050, bma250, lsm6ds3) I2C 核心层 (I2C Core) 协议解析、设备注册/注销、SMBus 兼容、锁机制 I2C 总线驱动层 (I2C Bus Driver / Adapter) I2C 控制器硬件操作 (如: RK808, DesignWare I2C) 硬件层 (Hardware) RK SoC I2C Controller → SCL/SDA 物理线 → Sensor 芯片 故障排查视角 1. 设备驱动:寄存器读写异常 2. 核心层:设备枚举失败 3. 总线驱动:时钟/数据线异常 4. 硬件层:电平/上拉/连接

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 信号是否正确。
I2C 消息传输流程 (从设备驱动到硬件) I2C 消息传输流程 (设备驱动 → 硬件) Sensor 设备驱动 i2c_master_send/recv I2C 核心层 i2c_transfer I2C 总线驱动 master_xfer I2C 控制器硬件 写/读 IC_DATA_CMD 寄存器 SCL / SDA 物理线 返回数据/错误码

4.3 I2C 设备驱动 (I2C Device Driver)

I2C 设备驱动是离 Sensor 硬件最近的软件抽象层,也是驱动开发者主要编码的地方。它通过 I2C 核心层提供的 API 与总线驱动通信,实现对 Sensor 芯片的寄存器读写控制。一个标准的 I2C 设备驱动包含以下关键部分:

  1. 设备树匹配表 (of_match_table): 用于与设备树中的 Sensor 节点进行匹配。
  2. I2C 设备 ID 表 (i2c_device_id): 用于传统非设备树方式的匹配。
  3. probe 函数: 设备与驱动匹配成功后调用,完成硬件初始化、数据结构和 sysfs 接口创建。
  4. remove 函数: 设备移除或驱动卸载时调用,释放资源。
  5. 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 核心层 释放锁,返回数据 将数据逐层返回给设备驱动
I2C 三层协作数据流 (读操作) I2C 三层协作数据流 (读寄存器操作) 设备驱动层 i2c_smbus_read_byte_data() 核心层 i2c_transfer() + 锁 总线驱动层 master_xfer() 硬件层 I2C 控制器 + Sensor 芯片 返回读取到的数据或错误码

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 是否处于正常工作状态。