2. Regmap核心数据结构:struct regmap_config深度解析
好,咱们直接进入正题。上一章我聊了聊Regmap是什么,能干什么。这一章,咱们得把它的“心脏”挖出来看看——就是那个struct regmap_config结构体。
说实话,我刚开始用Regmap的时候,看到这个结构体里密密麻麻的字段,头都大了。心想:“这玩意儿怎么比我家路由器设置还复杂?”但用久了你会发现,理解了它,你就掌握了Regmap的命脉。
核心观点:struct regmap_config 是Regmap的“配置清单”。你告诉它你的设备怎么读写,它就能帮你搞定一切。
2.1 结构体全景图
先看个全貌。我习惯把它的字段分成几类:
- 总线相关:告诉Regmap怎么跟硬件打交道
- 寄存器布局:描述你的寄存器长什么样
- 缓存策略:要不要缓存?怎么缓存?
- 读写控制:哪些寄存器能读?哪些能写?
- 锁机制:多线程访问怎么办?
下面这张图,是我自己画的一个结构图,帮你理清思路:
2.2 总线相关配置——你的设备怎么“说话”
这部分是基础中的基础。你得告诉Regmap,你的设备挂在什么总线上。
| 字段名 | 类型 | 说明 |
|---|---|---|
| reg_bus | const struct regmap_bus * | 总线操作函数集,比如i2c、spi |
| reg_read | int (*)(void *context, unsigned int reg, unsigned int *val) | 自定义读函数(可选) |
| reg_write | int (*)(void *context, unsigned int reg, unsigned int val) | 自定义写函数(可选) |
| max_register | unsigned int | 最大寄存器地址 |
我个人习惯:能用reg_bus就别自己写reg_read/reg_write。为什么?因为Regmap自带的i2c/spi实现已经帮你处理了很多边界情况,比如错误重试、时序控制。我自己写过一个驱动,一开始图省事自己实现了读写函数,结果发现漏掉了I2C的NACK处理,调试了一整天。
小技巧:如果你非要自己实现读写函数,记得检查返回值。我曾经见过一个驱动,reg_write永远返回0,结果缓存和硬件不同步,设备工作得“时好时坏”。
2.3 寄存器布局——你的寄存器“长什么样”
这部分决定了Regmap怎么解析你的寄存器地址和数据。
| 字段名 | 说明 |
|---|---|
| reg_bits | 寄存器地址的位数。8位?16位? |
| val_bits | 寄存器值的位数。常见8位、16位、32位 |
| reg_stride | 寄存器地址步长。比如0x00, 0x02, 0x04...步长就是2 |
| pad_bits | 地址和值之间的填充位 |
嗯,这里要注意一个坑。reg_stride这个字段,很多人会忽略。你想想看,如果你的设备寄存器地址是0x00、0x04、0x08,步长是4。如果你不设置reg_stride,Regmap默认步长是1。那它就会认为0x01、0x02、0x03也是有效地址,缓存里会多出很多“幽灵寄存器”。
我在项目中遇到过这样一个案例:一个音频Codec芯片,寄存器地址步长是2。驱动工程师没设置reg_stride,结果Regmap缓存了128个寄存器,实际上硬件只有64个。读寄存器的时候,有一半数据是缓存里的垃圾值。音频输出全是噪声,听起来像“外星人在说话”。
警告:reg_bits + val_bits + pad_bits 的总和必须能被8整除。否则Regmap在构造总线传输数据时会出错。这是很多新手容易踩的坑。
2.4 缓存策略——要不要“记笔记”?
缓存是Regmap的一大亮点。它让你不用每次都去读硬件,直接从缓存拿数据,快得很。
| 字段名 | 说明 |
|---|---|
| cache_type | 缓存类型:REG CACHE NONE(无缓存)、REG CACHE RB TREE(红黑树)、REG CACHE FLAT(平坦数组) |
| reg_defaults | 寄存器默认值数组 |
| num_reg_defaults | 默认值数量 |
我建议:对于大多数设备,用REG CACHE RB TREE就够了。红黑树在查找和插入上都很平衡。除非你的寄存器数量很少(比如少于32个),那用REG CACHE FLAT更省内存。
但是!不是所有寄存器都适合缓存。比如状态寄存器、中断标志位,这些是“一次性”的,读一次就变了。如果你缓存了它们,下次读到的还是旧值。那怎么办?别急,下一节就说。
关键点:设置reg_defaults时,一定要确保和硬件复位值一致。否则第一次缓存同步时,Regmap会把错误的值写进硬件,设备直接“罢工”。
2.5 读写控制——哪些寄存器能碰?
这部分是“安全护栏”。防止你误操作那些不该碰的寄存器。
| 字段名 | 类型 | 说明 |
|---|---|---|
| readable_reg | bool (*)(struct device *dev, unsigned int reg) | 判断寄存器是否可读 |
| writable_reg | bool (*)(struct device *dev, unsigned int reg) | 判断寄存器是否可写 |
| volatile_reg | bool (*)(struct device *dev, unsigned int reg) | 判断寄存器是否为“易失性”的 |
| precious_reg | bool (*)(struct device *dev, unsigned int reg) | 判断寄存器是否为“珍贵”的(读后不清零) |
这里volatile_reg特别重要。你想想看,如果一个寄存器是易失性的(比如硬件计数器),每次读它值都在变。如果你把它缓存了,Regmap会认为它没变,直接返回缓存里的旧值。那你的计数器就永远“卡住”了。
我曾经调试过一个网卡驱动,死活读不到正确的链路状态。查了两天,最后发现是volatile_reg没设置。Regmap把链路状态寄存器缓存了,每次读到的都是“已连接”。实际上网线早就拔了。
避坑指南:如果你不确定一个寄存器是不是易失性的,那就把它标记为volatile。顶多多读几次硬件,但至少数据是对的。
2.6 锁机制——多线程访问怎么办?
如果你的驱动会被多个线程访问(比如一个线程在配置,另一个在读状态),那锁就很重要了。
| 字段名 | 说明 |
|---|---|
| disable_locking | 是否禁用锁。默认不禁用 |
| lock | 自定义加锁函数 |
| unlock | 自定义解锁函数 |
| fast_io | 是否用于原子上下文(比如中断处理函数) |
我个人习惯:除非你非常确定只有一个线程访问,否则别禁用锁。Regmap默认的锁机制已经够用了。如果你在中断上下文使用Regmap,记得设置fast_io = true,这样Regmap会使用自旋锁而不是互斥锁。
注意:在中断上下文使用Regmap时,如果fast_io没设置,Regmap会尝试获取互斥锁,导致内核调度。这在中断上下文是绝对不允许的,系统会直接崩溃。
2.7 一个完整的配置示例
说了这么多,来个实际的例子。假设我们有一个I2C音频Codec芯片,寄存器地址8位,值8位,步长1,有64个寄存器:
static const struct reg_defaults codec_reg_defaults[] = {
{ 0x00, 0x80 }, // 复位值
{ 0x01, 0x00 },
// ... 其他默认值
};
static bool codec_readable_reg(struct device *dev, unsigned int reg)
{
return reg < 0x40; // 只有前64个寄存器可读
}
static bool codec_volatile_reg(struct device *dev, unsigned int reg)
{
// 状态寄存器0x10和中断寄存器0x11是易失性的
return reg == 0x10 || reg == 0x11;
}
static const struct regmap_config codec_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.max_register = 0x3F,
.cache_type = REGCACHE_RBTREE,
.reg_defaults = codec_reg_defaults,
.num_reg_defaults = ARRAY_SIZE(codec_reg_defaults),
.readable_reg = codec_readable_reg,
.volatile_reg = codec_volatile_reg,
};
你看,配置起来其实不复杂。关键是每个字段都要想清楚:我的设备需要什么?
好了,这一章的内容就到这。struct regmap_config 就像一把钥匙,打开了Regmap的大门。下一章咱们聊聊怎么用这把钥匙——也就是如何初始化和注册Regmap实例。
本章总结:
- struct regmap_config 是Regmap的配置核心,包含总线、布局、缓存、读写控制、锁五大类字段
- reg_stride 容易被忽略,但会导致缓存膨胀
- volatile_reg 决定哪些寄存器不走缓存,直接影响数据正确性
- 中断上下文使用Regmap必须设置 fast_io = true