2. Regmap核心数据结构:struct regmap_config深度解析

好,咱们直接进入正题。上一章我聊了聊Regmap是什么,能干什么。这一章,咱们得把它的“心脏”挖出来看看——就是那个struct regmap_config结构体。

说实话,我刚开始用Regmap的时候,看到这个结构体里密密麻麻的字段,头都大了。心想:“这玩意儿怎么比我家路由器设置还复杂?”但用久了你会发现,理解了它,你就掌握了Regmap的命脉。

核心观点:struct regmap_config 是Regmap的“配置清单”。你告诉它你的设备怎么读写,它就能帮你搞定一切。

2.1 结构体全景图

先看个全貌。我习惯把它的字段分成几类:

  • 总线相关:告诉Regmap怎么跟硬件打交道
  • 寄存器布局:描述你的寄存器长什么样
  • 缓存策略:要不要缓存?怎么缓存?
  • 读写控制:哪些寄存器能读?哪些能写?
  • 锁机制:多线程访问怎么办?

下面这张图,是我自己画的一个结构图,帮你理清思路:

struct regmap_config 总线相关 reg_bus reg_read / reg_write max_register 寄存器布局 reg_stride reg_bits / val_bits pad_bits 缓存策略 cache_type reg_defaults num_reg_defaults 读写控制 readable_reg / writable_reg volatile_reg precious_reg 锁机制 disable_locking lock / unlock fast_io 图2-1:struct regmap_config 核心字段分类

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

可可精品资料库