Linux regmap:把寄存器和物理总线解耦

为什么内核喜欢 regmap?从 SPI/I2C 驱动差异理解抽象层价值。

写 Linux 驱动时,同样一份寄存器读写逻辑经常要在 SPI 和 I2C 之间复制。regmap 提供的是“寄存器语义”的统一接口:驱动关心寄存器编号与位域,底层的总线事务交给 regmap 完成。

传统写法的问题

/* SPI 版 */
static int chip_read_reg(struct spi_device *spi, u8 reg, u8 *val) {
    u8 tx[2] = { reg, 0 };
    return spi_write_then_read(spi, tx, 1, val, 1);
}
/* I2C 版 */
static int chip_read_reg(struct i2c_client *client, u8 reg, u8 *val) {
    return i2c_smbus_read_byte_data(client, reg);
}

业务代码如果直接调用上面两个函数,切换总线时就要改调用点。

regmap 统一视角

static const struct regmap_config cfg = {
    .reg_bits = 8,
    .val_bits = 8,
    .max_register = 0xff,
};

regmap = devm_regmap_init_spi(spi, &cfg);
// 或 regmap = devm_regmap_init_i2c(client, &cfg);

regmap_read(regmap, 0x10, &value);
regmap_update_bits(regmap, 0x10, 0x0f, 0x03);

驱动剩下的代码与总线类型无关:寄存器 map、读写缓存、并发锁、位域更新都收敛到 regmap。

值得记住的收益

  • 并发安全regmap 内置 lock,多线程访问不会裸奔;
  • 调试友好:打开动态调试可观察每次总线事务;
  • 缓存与脏位regmap_cache 能显著减少无谓总线读;
  • 可测:测试代码可以替换 regmap bus 做软件仿真。

这个模式对理解“抽象层边界在哪里”很有帮助:当一种资源的访问方式存在多种物理实现、且业务逻辑与实现无关时,就值得抽层。

本文遵循 CC BY-NC-SA 4.0 · 源码见 GitHub 仓库