3. Regmap初始化与注册:devm_regmap_init_*系列函数,选择正确的初始化方式

说实话,Regmap的初始化这块,我见过太多人踩坑了。

很多人一上来就抄代码,看到别人用devm_regmap_init_i2c,自己也跟着用。结果呢?总线类型不对,驱动死活跑不起来。嗯,今天我们就好好聊聊这个初始化问题。

3.1 为什么要有这么多初始化函数?

你想想看,Regmap要支持I2C、SPI、MMIO这么多总线类型。每种总线的通信方式都不一样,自然要有不同的初始化入口。

我个人习惯把devm_regmap_init_*系列函数分成三类:

  • 总线专用型:devm_regmap_init_i2c、devm_regmap_init_spi等
  • 通用型:devm_regmap_init,需要自己提供读写回调
  • 特殊型:devm_regmap_init_mmio,用于内存映射设备

核心原则:能用总线专用型,就别用通用型。因为总线专用型帮你处理了很多底层细节,比如I2C的地址格式、SPI的片选控制等。

3.2 各初始化函数详解

3.2.1 devm_regmap_init_i2c

这是I2C设备最常用的初始化方式。我在项目中遇到过一个问题:某颗音频编解码芯片,用devm_regmap_init_i2c初始化后,读写总是超时。查了半天,发现是I2C地址配置错了。

static const struct regmap_config my_i2c_regmap_config = {
    .reg_bits = 8,      // 寄存器地址宽度
    .val_bits = 8,      // 寄存器值宽度
    .max_register = 0xFF,
    .cache_type = REGCACHE_RBTREE,
};

struct regmap *regmap = devm_regmap_init_i2c(client, &my_i2c_regmap_config);
if (IS_ERR(regmap)) {
    dev_err(&client->dev, "Failed to init regmap: %ld\n", PTR_ERR(regmap));
    return PTR_ERR(regmap);
}

小技巧:I2C设备的reg_bits和val_bits一定要看数据手册。有些芯片用16位寄存器地址,你设成8位,那读写就全乱套了。

3.2.2 devm_regmap_init_spi

SPI设备的初始化,和I2C类似,但多了一个read_flag_mask参数。这个参数用来控制SPI读操作时的命令字节。

static const struct regmap_config my_spi_regmap_config = {
    .reg_bits = 8,
    .val_bits = 8,
    .read_flag_mask = 0x80,  // 读操作时,最高位置1
    .write_flag_mask = 0x00, // 写操作时,最高位清0
    .max_register = 0x7F,
};

struct regmap *regmap = devm_regmap_init_spi(spi, &my_spi_regmap_config);

我曾经调试过一个SPI加速度传感器,死活读不到正确的数据。后来发现是read_flag_mask设反了。嗯,这种错误真的很隐蔽。

3.2.3 devm_regmap_init_mmio

这个函数用于内存映射设备,比如挂在AHB/APB总线上的外设。说白了,就是直接操作物理地址。

static const struct regmap_config my_mmio_regmap_config = {
    .reg_bits = 32,
    .val_bits = 32,
    .reg_stride = 4,  // 寄存器间隔4字节
    .max_register = 0x1000,
};

struct regmap *regmap = devm_regmap_init_mmio(dev, base_addr, &my_mmio_regmap_config);

注意:使用devm_regmap_init_mmio时,reg_stride一定要和设备手册一致。如果设错了,你读到的寄存器值全是错的。我有个同事就因为这个,排查了整整两天。

3.2.4 devm_regmap_init

这个通用函数,适合那些非标准总线的设备。你需要自己实现读写回调函数。

static int my_reg_read(void *context, unsigned int reg, unsigned int *val)
{
    struct my_device *dev = context;
    // 自定义读操作
    *val = readl(dev->base + reg);
    return 0;
}

static int my_reg_write(void *context, unsigned int reg, unsigned int val)
{
    struct my_device *dev = context;
    // 自定义写操作
    writel(val, dev->base + reg);
    return 0;
}

static const struct regmap_config my_custom_config = {
    .reg_bits = 32,
    .val_bits = 32,
    .reg_read = my_reg_read,
    .reg_write = my_reg_write,
};

struct regmap *regmap = devm_regmap_init(dev, NULL, my_device, &my_custom_config);

3.3 如何选择正确的初始化方式?

我总结了一个简单的选择流程:

  1. 先看总线类型:I2C就用devm_regmap_init_i2c,SPI就用devm_regmap_init_spi
  2. 再看地址映射:如果是MMIO设备,用devm_regmap_init_mmio
  3. 最后看特殊需求:如果以上都不满足,才考虑devm_regmap_init

避坑指南:我曾经在一个项目中,为了图省事,把所有设备都用devm_regmap_init。结果代码量反而增加了,因为每个设备都要写读写回调。后来全部改成总线专用型,代码简洁多了。

3.4 regmap_config配置要点

不管用哪个初始化函数,regmap_config的配置都至关重要。这里有几个关键参数:

参数 说明 常见错误
reg_bits 寄存器地址的位宽 设小了,访问不到高地址寄存器
val_bits 寄存器值的位宽 设错了,读写值会截断
max_register 最大有效寄存器地址 设大了,浪费内存;设小了,访问越界
cache_type 缓存类型 选错了,可能导致数据不一致

3.5 初始化后的错误处理

初始化函数返回的是struct regmap *,但要用IS_ERR来检查错误。为什么?因为Regmap内部用ERR_PTR来返回错误码。

struct regmap *regmap = devm_regmap_init_i2c(client, &config);
if (IS_ERR(regmap)) {
    dev_err(&client->dev, "regmap init failed: %d\n", PTR_ERR(regmap));
    return PTR_ERR(regmap);
}

个人经验:我习惯在初始化失败时,打印出具体的错误码。这样调试起来方便很多。比如-EINVAL表示参数错误,-ENOMEM表示内存不足。

3.6 知识体系图

下面这张图,帮你理清Regmap初始化的整体脉络:

Regmap初始化函数选择流程 设备类型判断 I2C设备 devm_regmap_init_i2c SPI设备 devm_regmap_init_spi MMIO设备 devm_regmap_init_mmio 其他/自定义总线 devm_regmap_init 选择原则:优先使用总线专用型,最后才考虑通用型

这张图很直观。你从设备类型出发,沿着分支走,就能找到正确的初始化函数。说白了,就是先定总线,再选函数。

3.7 总结

Regmap初始化这块,其实没那么复杂。记住三点:

  • 总线匹配:I2C、SPI、MMIO各有专用函数
  • 配置准确:reg_bits、val_bits这些参数不能错
  • 错误检查:用IS_ERR检查返回值

我在项目中见过太多人,初始化函数选对了,但配置参数搞错了。结果驱动跑起来,数据全是乱的。嗯,所以配置参数这块,一定要仔细看数据手册。

最后说一句:devm_前缀的函数,会自动管理资源释放。你不需要手动调用regmap_exit,设备移除时会自动清理。这省了不少事。


可可精品资料库