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 如何选择正确的初始化方式?
我总结了一个简单的选择流程:
- 先看总线类型:I2C就用
devm_regmap_init_i2c,SPI就用devm_regmap_init_spi - 再看地址映射:如果是MMIO设备,用
devm_regmap_init_mmio - 最后看特殊需求:如果以上都不满足,才考虑
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初始化的整体脉络:
这张图很直观。你从设备类型出发,沿着分支走,就能找到正确的初始化函数。说白了,就是先定总线,再选函数。
3.7 总结
Regmap初始化这块,其实没那么复杂。记住三点:
- 总线匹配:I2C、SPI、MMIO各有专用函数
- 配置准确:reg_bits、val_bits这些参数不能错
- 错误检查:用IS_ERR检查返回值
我在项目中见过太多人,初始化函数选对了,但配置参数搞错了。结果驱动跑起来,数据全是乱的。嗯,所以配置参数这块,一定要仔细看数据手册。
最后说一句:devm_前缀的函数,会自动管理资源释放。你不需要手动调用regmap_exit,设备移除时会自动清理。这省了不少事。
可可精品资料库