4. RK Sensor I2C 驱动框架:Linux I2C 子系统、设备树绑定、probe 流程、regmap 接口
在瑞芯微平台上开发 Sensor 驱动,核心是理解 Linux I2C 子系统的分层架构,以及如何通过设备树描述硬件、利用 probe 机制完成驱动初始化、借助 regmap 简化寄存器访问。本章将拆解这四个关键环节,并给出 RK 平台上的典型实现模式。
4.1 Linux I2C 子系统三层架构
Linux I2C 子系统自下而上分为三层:I2C 控制器驱动层、I2C 核心层、I2C 设备驱动层。Sensor 驱动属于最上层的设备驱动,通过核心层提供的 API 与底层控制器交互。
关键理解:Sensor 驱动开发者只需关注设备驱动层,调用 i2c_transfer()、i2c_smbus_read_byte_data() 等标准接口,无需关心 I2C 控制器是 RK3399 的、RK3588 的还是其他 SoC 的。
4.2 设备树绑定:描述 Sensor 硬件拓扑
在 RK 平台,Sensor 的硬件连接信息通过设备树(Device Tree)描述。设备树节点需包含:compatible(匹配驱动)、reg(I2C 从设备地址)、中断引脚(若使用中断)、电源控制(上电时序相关)。
| 属性 | 说明 | 示例值 |
|---|---|---|
| compatible | 驱动匹配字符串,格式 "厂商,型号" | "bosch,bma423" |
| reg | I2C 从设备 7 位地址 | 0x18 |
| interrupt-parent | 中断控制器 phandle | &gpio1 |
| interrupts | 中断号与触发类型 | <RK_PB2 IRQ_TYPE_EDGE_RISING> |
| vdd-supply | 数字电源 regulator | &vcc_3v3 |
| vddio-supply | IO 电源 regulator | &vcc_1v8 |
典型设备树节点示例:
&i2c1 {
status = "okay";
clock-frequency = <400000>; /* 400kHz 快速模式 */
bma423: accelerometer@18 {
compatible = "bosch,bma423";
reg = <0x18>;
interrupt-parent = <&gpio1>;
interrupts = <RK_PB2 IRQ_TYPE_EDGE_RISING>;
vdd-supply = <&vcc_3v3>;
vddio-supply = <&vcc_1v8>;
mount-matrix = "0", "1", "0",
"-1", "0", "0",
"0", "0", "1";
};
};
RK 平台提示:RK 的 I2C 控制器驱动 i2c-rk3x 支持标准模式(100kHz)和快速模式(400kHz)。Sensor 通常使用 400kHz,但若 Sensor 不支持快速模式,需在设备树中降低 clock-frequency。
4.3 probe 流程:从设备树到驱动实例
当内核启动时,I2C 核心层会遍历设备树中每个 I2C 子节点,与已注册的 I2C 驱动进行 compatible 匹配。匹配成功后,调用驱动的 probe 函数。以下是 RK Sensor 驱动 probe 的典型流程:
- 获取设备树资源:读取 reg、interrupts、电源 regulator 等属性。
- 电源上电与复位:通过 regulator 框架使能 vdd/vddio,等待 Sensor 稳定(通常 1-10ms)。
- I2C 通信验证:读取 Sensor 的 CHIP_ID 寄存器,确认通信正常。
- 中断申请:若使用中断,调用 devm_request_threaded_irq() 注册中断处理函数。
- 初始化 Sensor 配置:设置量程、采样率、工作模式等寄存器。
- 注册输入子系统 / IIO 设备:将 Sensor 注册到内核框架,供用户空间访问。
4.4 regmap 接口:简化 I2C 寄存器访问
regmap 是 Linux 内核提供的一套通用寄存器映射框架,封装了 I2C、SPI、MMIO 等总线访问细节。在 RK Sensor 驱动中使用 regmap,可以显著减少样板代码,并自动获得缓存、批量读写、调试文件系统导出等能力。
使用 regmap 的典型步骤:
- 定义 regmap_config 结构体,指定寄存器位宽、读写标志等。
- 调用 devm_regmap_init_i2c() 创建 regmap 实例。
- 使用 regmap_read() / regmap_write() / regmap_update_bits() 访问寄存器。
代码示例:
static const struct regmap_config bma423_regmap_config = {
.reg_bits = 8, /* 寄存器地址 8 位 */
.val_bits = 8, /* 寄存器值 8 位 */
.read_flag_mask = 0x80, /* 某些 Sensor 读操作需置位最高位 */
.max_register = 0x7F, /* 最大寄存器地址 */
};
static int bma423_probe(struct i2c_client *client)
{
struct regmap *regmap;
unsigned int chip_id;
int ret;
/* 创建 regmap 实例 */
regmap = devm_regmap_init_i2c(client, &bma423_regmap_config);
if (IS_ERR(regmap)) {
dev_err(&client->dev, "regmap init failed\n");
return PTR_ERR(regmap);
}
/* 读取 CHIP_ID 寄存器验证通信 */
ret = regmap_read(regmap, BMA423_CHIP_ID_REG, &chip_id);
if (ret) {
dev_err(&client->dev, "failed to read chip id: %d\n", ret);
return ret;
}
if (chip_id != BMA423_CHIP_ID_VALUE) {
dev_err(&client->dev, "chip id mismatch: 0x%02x\n", chip_id);
return -ENODEV;
}
/* 使用 regmap_update_bits 修改特定位 */
regmap_update_bits(regmap, BMA423_PWR_CTRL_REG,
BMA423_PWR_EN_MASK, BMA423_PWR_ENABLE);
/* 后续初始化... */
return 0;
}
regmap 优势总结:
- 自动处理 I2C 读写的协议细节(如重复起始条件)。
- 支持缓存,减少不必要的 I2C 总线访问。
- 通过 debugfs 导出所有寄存器,方便调试。
- 驱动代码可在 I2C 和 SPI 之间轻松移植(只需修改 regmap 初始化函数)。
4.5 框架对比:传统 I2C 接口 vs regmap 接口
| 对比维度 | 传统 i2c_transfer / i2c_smbus | regmap 接口 |
|---|---|---|
| 代码量 | 需手动构造 msg 数组,代码冗长 | 一行 regmap_read/write 完成 |
| 缓存支持 | 无,每次读写都发起总线事务 | 支持 volatile 与 cache 区分 |
| 调试能力 | 需手动添加 dev_dbg 打印 | 自动导出 /sys/kernel/debug/regmap |
| 总线抽象 | 强依赖 I2C 总线 | 可切换 I2C/SPI/MMIO 不改核心逻辑 |
| 批量读写 | 需自行实现循环 | regmap_bulk_read/write 原生支持 |
| RK 平台推荐 | 仅用于简单验证 | 生产驱动首选 |
注意事项:部分 Sensor 的 I2C 协议有特殊要求(如读操作前需先写地址、或使用重复起始条件)。regmap 的 read_flag_mask 和 write_flag_mask 可满足大多数场景,但若遇到非标准协议,仍需回退到 i2c_transfer 手动构造消息。
4.6 RK 平台 I2C 驱动调试要点
- 确认 I2C 控制器时钟已使能:RK 平台 I2C 控制器时钟由 CLK 框架管理,若时钟未使能,I2C 传输会超时。
- 检查 I2C 引脚复用:通过 pinctrl 确认 I2C 的 SDA/SCL 引脚已正确配置为 I2C 功能,而非 GPIO。
- 使用 i2c-detect 工具:在用户空间运行 i2c-detect -y <bus> 确认 Sensor 是否在预期地址响应。
- 开启 I2C 调试日志:动态调试 i2c-core 或 i2c-rk3x 驱动,观察每次传输的细节。
- regmap 调试:挂载 debugfs 后查看 /sys/kernel/debug/regmap/<设备名>/registers 确认寄存器值。