在Regmap API被开发出来之前,处理SPI、I2C或内存映射设备的设备驱动程序中存在冗余代码。许多设备驱动程序包含的用于访问硬件设备寄存器的代码非常相似。


Regmap API是在Linux内核的3.1版本中引入的,它提供了一种解决方案,旨在对相似的硬件寄存器访问代码进行分解和统一,避免代码冗余,并使共享基础设施变得更加容易。因此,只需要初始化和配置Regmap API,并流畅地处理任何读取/写入/修改操作,无论是SPI、I2C还是内存映射,都可以轻松完成。


Regmap是通过CONFIG_REGMAP内核配置选项启用的,它由一些数据结构组成,其中最为重要的是struct regmap_config(表示Regmap的配置)和structregmap(表示Regmap实例本身)。也就是说,Regmap的所有数据结构都定义在include/linux/regmap.h中。因此,所有基于Regmap的驱动程序都必须包含这个头文件:

通过包含这个头文件,许多数据结构都将可用,其中最重要的是struct regmap_config。
struct regmap_config数据结构
struct regmap_config数据结构用于存储驱动程序生命周期中寄存器映射的配置。其中设置的内容会影响内存读写操作。这是Regmap最重要的数据结构,定义如下:


reg_bits
是必填字段,表示寄存器地址中的有效位数量
reg_stride
有效的寄存器地址必须是reg_stride值的倍数。如果reg_stride设置为4,那么只有当寄存器地址是4的倍数时,该寄存器地址才被视为有效地址。
pad_bits
是寄存器和寄存器值之间的填充位数,也是在格式化时需要将寄存器值左移的位数。
假设我们有一个 16 位的寄存器,规则如下:
寄存器的 bit0~bit3:保留位(填充位,对应 pad_bits=4)
寄存器的 bit4~bit7:我们要设置的「数据字段」(占 4 位)
现在你想给这个数据字段赋值0b1010(十进制 10),如果直接把0b1010写入寄存器,它会占据 bit0~bit3(保留位),这是错误的。
0b1010 << 4 = 0b10100000,此时这个值正好占据寄存器的 bit4~bit7(目标区间),完全符合寄存器的位布局要求。
如果反过来右移:0b1010 >> 4 = 0,值会被清零,根本无法填充到目标位置;即使是更大的数,右移也只会把值往更低的 bit 位移动,完全偏离目标字段的位置。
val_bits
表示用于存放寄存器值的位数,也是必填字段。
例子:如果val_bits=4,那么能存储的有效值只能是 0~15(二进制 0000~1111);如果强行写入 16(二进制 10000,占 5 位),要么被硬件截断(只保留低 4 位 0000),要么触发错误,val_bits就是用来提前校验这种非法值的。
writable_reg
可选的回调函数,如果提供了该回调函数,那么当需要写入寄存器时,Regmap子系统将使用它,在写入寄存器之前内核调用此函数检查寄存器是否可写。

readable_reg
readable_reg与writeable_reg相似,但适用于寄存器读取操作。
volatile_reg
也是可选的回调函数,每当通过Regmap缓存读取或写入寄存器时,就会调用它。如果寄存器是易失性的,该回调函数应返回true,然后在寄存器上直接进行读取/写入。如果返回false,则表示寄存器是可缓存的。在这种情况下,使用缓存执行读取操作,并在执行写入操作时写入缓存:

该回调函数是 Regmap 的 "缓存策略裁判",每次读写寄存器都会调用,用于逐寄存器判断是否启用缓存;
返回true(易失性寄存器):跳过缓存,直接读写硬件(因值易变,缓存无意义);
返回false(可缓存寄存器):读走缓存、写更缓存(减少硬件访问,提升操作效率)。
- 返回 true → 寄存器是「易失性」的
什么是易失性寄存器?
这类寄存器的值不固定、会被硬件自动修改,内存里的缓存副本根本跟不上实际值的变化。典型例子:
状态寄存器:比如 "中断标志位"(硬件触发中断后自动置 1,CPU 读取后又自动清 0);
实时数据寄存器:比如温度传感器的实时温度值(每读一次可能都不一样)。
操作逻辑:
因为缓存里的值是 "过期的、不准的",所以必须跳过缓存,直接操作硬件寄存器本身:
读操作:直接从硬件寄存器读取最新值,不碰内存缓存;
写操作:直接把值写入硬件寄存器,不更新缓存(缓存对它无意义)。 - 返回 false → 寄存器是「可缓存」的
什么是可缓存寄存器?
这类寄存器的值稳定、只有软件主动写入才会改变,硬件不会偷偷修改它。典型例子:
配置寄存器:比如设置串口波特率、GPIO 方向的寄存器;
使能寄存器:比如开启 / 关闭某个外设功能的寄存器。
操作逻辑:
优先使用内存中的缓存,避免频繁操作硬件(硬件操作通常慢且耗资源):
读操作:不访问硬件,直接从内存缓存里读取值(因为缓存和硬件值是一致的);
写操作:先把值写入内存缓存(更新副本),再根据配置(比如立即同步 / 批量同步)把缓存的值同步到硬件寄存器(保证缓存和硬件一致)。
precious_reg
有些设备对寄存器的读取非常敏感,尤其是那些在读取时清除中断状态的寄存器。这个可选的回调函数必须返回true,以便当指定的寄存器属于这种情况时,阻止核心(如debugfs)从内部生成任何对该寄存器的读取。这样,只有驱动程序的显式读取才会被允许。
为什么这类寄存器 "读取敏感"?
首先要理解这类寄存器的特殊硬件设计:读取操作本身不是单纯的 "查值",而是会触发硬件的关键行为 ------ 清除中断状态。典型例子:中断状态寄存器(比如 "UART 接收中断寄存器"),硬件设计规则是:
当 CPU 读取这个寄存器的瞬间,硬件会自动把寄存器里的 "中断标志位" 清零(表示 "中断已被读取 / 处理")。
这个设计本身是合理的(读了 = 确认处理中断),但风险在于:如果是 "非预期的读取"(比如不是驱动去读),会提前清零中断标志,导致驱动程序完全不知道有中断发生,进而丢失中断、设备功能异常。
disable_locking
用于指明是否应该使用锁定/解锁回调函数。如果这个布尔字段为false,则意味着不使用任何锁定机制。Regmap对象要么通过外部手段受到保护,要么保证不被从多个线程访问。

lock/unlock
是可选的锁定/解锁回调函数,用于覆盖Regmap默认的锁定/解锁函数(基于自旋锁或互斥锁,并且取决于访问底层设备是否可能使调用者进入睡眠状态)。
Regmap 有默认的锁定实现,但不是所有场景都适用,这两个回调函数的作用是 "替换默认锁":
默认锁定逻辑:Regmap 会根据 "底层设备访问是否会让调用者睡眠" 自动选锁:
如果访问设备(比如 I2C/SPI)可能睡眠 → 用互斥锁(mutex)(适合可能阻塞的场景);
如果访问设备不会睡眠 → 用自旋锁(spinlock)(适合快速、无睡眠的场景)。
自定义 lock/unlock 回调:当默认锁不满足需求时(比如驱动有自己的锁机制、需要和其他模块的锁联动),可以实现这两个函数:
lock回调:线程访问寄存器前执行,自定义 "加锁" 逻辑(比如获取驱动私有的互斥锁);
unlock回调:线程访问寄存器后执行,自定义 "解锁" 逻辑(比如释放上述互斥锁)。
lock_arg
作为可选锁定/解锁函数的唯一参数使用(如果未覆盖Regmap默认的锁定/解锁函数,它将被忽略)。
reg_read
如果设备不支持简单的I2C/SPI读取操作,则不得不编写定制的读取函数,reg_read应指向该函数。但大多数设备不需要这样做。
reg_write
reg_write与reg_read相似,但用于写操作。
fast_io
表示寄存器I/O速度很快。如果设置了这个字段,Regmap将使用自旋锁而非互斥锁执行锁定。如果使用定制的锁定/解锁函数,该字段将被忽略(请参阅内核源代码的struct regmap_config数据结构中的lock/unlock字段)。它应该仅用于"无总线"情况(MMIO设备),因为访问I2C、SPI或其他类似的总线可能会使调用者进入睡眠状态。
max_register
此可选字段指定不允许执行任何操作的最大有效寄存器地址。
每个硬件设备的寄存器都有硬件固化的地址范围(比如一款传感器的寄存器地址是 0x00~0x3F,共 64 个有效寄存器),max_register就是这个范围的 "上限值"(比如 0x3F)。
wr_table
你可以提供一个regmap_access_table对象,而不是提供writeable_reg回调函数。regmap_access_table对象是一个包含yes_ranges和no_ranges字段的结构体实例,这两个字段都是指向regmap_range对象的指针。属于yes_ranges条目的任何寄存器都将视为可写,而属于no_ranges条目的寄存器则视为不可写。
在此之前,Regmap 判断寄存器是否可写的方式是writeable_reg回调函数:每次要读写某个寄存器时,调用这个回调,传入寄存器地址,回调返回true/false表示 "可写 / 不可写"。这种方式的问题是:如果设备的寄存器可写性是连续区间式的(比如 0x00~0x1F 可写、0x20~0x3F 不可写),逐寄存器判断会让代码繁琐且低效。而wr_table就是为解决这个问题而生 ------用 "地址范围" 批量定义可写 / 不可写规则,替代逐寄存器判断,更简洁、高效。
wr_table指向的regmap_access_table是一个 "范围规则表",核心结构如下
C
struct regmap_access_table {
const struct regmap_range *yes_ranges; // 可写的寄存器地址范围列表
unsigned int n_yes_ranges; // yes_ranges的条目数量
const struct regmap_range *no_ranges; // 不可写的寄存器地址范围列表
unsigned int n_no_ranges; // no_ranges的条目数量
};
其中regmap_range是 "单个连续地址区间" 的定义,包含两个关键值:
start:区间起始寄存器地址(偏移地址);
length:区间长度(覆盖的寄存器数量)。
比如start=0x00,length=0x20 → 覆盖 0x00~0x1F 的连续寄存器。
rd_table
rd_table与wr_table相似,但用于读取操作。
volatile_table
你可以提供volatile_table,而不是提供volatile_reg。原则与wr_table或rd_table相同,但用于缓存机制。
precious_table
precious_table与volatile_table相似,但用于重要的寄存器。
reg_defaults
是一个reg_default元素的数组,其中的每个reg_default元素所使用的{reg,value}结构表示寄存器的上电复位值。请将它与缓存一起使用,以便在对位于此数组中并且自上次上电复位以来尚未写入的地址进行读取时,返回此数组中的默认寄存器值,而无须执行设备上的任何读取事务。
reg_defaults是一个reg_default类型的数组,每个元素是{reg, value}键值对:
reg:寄存器偏移地址(比如 0x01);
value:该寄存器的上电复位值(比如 0x01)。
它的作用是:把设备所有关键寄存器的复位值 "告诉" Regmap,让 Regmap 缓存提前加载这些值。
核心作用:避免 "无意义的硬件读取"
如果没有这两个配置,Regmap 读取一个 "从未被写入过" 的寄存器时,会去访问硬件(I2C/SPI 读、MMIO 读);但因为寄存器没被写过,值就是复位值 ------ 这种硬件访问完全是 "做无用功"(浪费总线资源、增加延迟)。配置reg_defaults后,Regmap 会把这些默认值载入缓存:
读取 "未被修改过的寄存器" → 直接返回缓存里的默认值 → 不执行任何硬件读取操作。
num_reg_defaults
num_reg_defaults是reg_defaults数组中的元素数量。
cache_type
表示实际的缓存类型,可以是REGCACHE_NONE、REGCACHE_RBTREE、REGCACHE_COMPRESSED或REGCACHE_FLAT。
cache_type定义了 Regmap 缓存(包括reg_defaults载入的默认值、后续写入的寄存器值)在内存中的存储结构,不同类型适配不同的寄存器分布场景,核心有 4 种:
REGCACHE_NONE 无缓存 无存储结构 禁用缓存(比如全是易失性寄存器),reg_defaults无效
REGCACHE_FLAT 扁平缓存(数组式)存储结构为连续数组 寄存器地址连续、数量少(比如 0x00~0x3F) 最常用
REGCACHE_RBTREE 红黑树缓存 存储结构为红黑树 寄存器地址稀疏、范围大(比如 0x00、0x100、0x200)
REGCACHE_COMPRESSED 压缩缓存 存储结构为稀疏数组 + 压缩 大部分寄存器是默认值、仅少数被修改(省内存)
read_flag_mask
是进行读取时要应用于寄存器顶部字节的掩码。通常,在针对SPI或I2C的写入或读取操作的顶部字节中,最高位被设置用于区分写入和读取操作。
write_flag_mask
是进行写入时要应用于寄存器顶部字节的掩码。
use_single_rw
是一个布尔字段,用于指示寄存器映射将设备上的任何批量写入或读取操作转换为一系列的单个写入或读取操作。它对于不支持批量读取或写入的设备非常有用。
布尔值,全局强制:把设备的「所有批量读 / 批量写」都拆成单个操作(读 + 写都管)
can_multi_write
仅针对写操作。如果设置为true,则表示支持批量写入操作的多写模式。如果设置为false,多写请求将被拆分为单独的写入操作。
布尔值,仅写操作专属:控制「批量写」是否允许执行(仅管写,不管读)
regmap_config初始化示例
你可以在include/linux/regmap.h中查找有关上述每个字段的更多详细信息。以下是regmap_config的初始化示例:

Regmap初始化
Regmap支持SPI、I2C和内存映射寄存器访问,它们各自的支持可以通过CONFIG_REGMAP_SPI、CONFIG_REGMAP_I2C和CONFIG_ REGMAP_MMIO内核配置选项在内核中启用。根据你在驱动程序中需要支持的内存访问方法,需要在探测函数中调用devm_regmap_init_i2c()、devm_regmap_init_spi()或devm_regmap_init_mmio()。要编写通用驱动程序,Regmap是最佳选择。
Regmap初始化仅在总线类型之间有所不同,其他都是相同的。在探测函数中始终初始化寄存器映射是一个很好的习惯,而且你必须在使用以下API之一初始化寄存器映射之前始终填充regmap_config元素:

以上资源管理API所分配的资源在设备离开系统或驱动程序卸载时会被自动释放。返回值将是一个指向有效Regmap对象的指针,或在失败时返回ERR_PTR()错误。regs是指向MMIO区域的指针(通过devm_ioremap_resource()或任何ioremap*函数返回)。dev是与之交互的设备,在内存映射Regmap的情况下使用;spi和i2c分别是在SPI或I2C基础上进行交互的SPI或I2C设备。使用其中一个资源管理API足以开始与底层设备进行交互。无论Regmap是I2C、SPI还是内存映射寄存器映射,只要还没有使用资源管理API(devm)的变体进行初始化,就必须使用regmap_exit()函数进行释放:

使用Regmap寄存器访问函数
重新映射寄存器访问方法处理数据的解析、格式化和传输。在大多数情况下,设备访问是使用regmap_read()、regmap_write()和regmap_update_bits()函数进行的,这3个重要的函数用于将数据写入设备或者从设备上读取数据。它们的原型分别如下:

regmap_write()
regmap_write()函数将数据写入设备。如果在regmap_config中设置了max_register,则检查需要访问的寄存器地址是大于还是小于max_register。如果传递的寄存器地址小于或等于max_register,则执行下一步操作;否则,Regmap核心将返回无效的I/O错误(-EIO)。接下来调用writeable_reg回调函数。该回调函数必须在继续下一步之前返回true,如果返回false,则产生-EIO并停止写操作。如果设置了wr_table而不是writeable_reg,则会发生以下情况。
· 如果寄存器地址在no_ranges中,则返回-EIO。
· 如果寄存器地址在yes_ranges中,则执行下一步操作。
· 如果寄存器地址不在yes_ranges或no_ranges中,则返回-EIO并终止操作。
如果cache_type != REGCACHE_NONE,则启用缓存。在这种情况下,首先使用新值更新缓存条目,然后执行对硬件的写入操作,否则不执行任何缓存操作。如果提供了reg_write回调函数,则用它执行写入操作;否则执行通用Regmap的写入函数,将数据写入指定的寄存器地址。
regmap_read()
regmap_read()函数从设备上读取数据。它的工作方式与regmap_write()函数完全相同,也具有相应的数据结构(readable_reg和rd_table)。因此,如果提供了reg_read回调函数,则用它来执行读取操作,否则执行通用Reagmap的读取函数。
regmap_update_bits()
regmap_update_bits()函数在指定的寄存器地址执行读取/修改/写入循环。它是_regmap_update_bits()函数的封装器,该函数的原型如下:


这样,需要更新的位就必须在mask中设置为1,并且相应的位应该在val中设置为需要赋给它们的值。作为示例,要将第1位和第3位设置为1,则mask应为0b00000101,val应为0bxxxxx1x1。要清除第7位,则mask应为0b01000000,val应为0bx0xxxxxx,依此类推。
C
// 初始化的Regmap对象(假设已完成初始化)
struct regmap *rmap;
// 需求:把寄存器0x02的bit1和bit3置1,其他位不变
unsigned int reg = 0x02;
unsigned int mask = 0x0A; // 二进制00001010,bit1和bit3设1
unsigned int value = 0x0A; // 要设置的新值
// 调用封装函数,自动完成读→改→写
int ret = regmap_update_bits(rmap, reg, mask, value);
if (ret != 0) {
pr_err("寄存器位修改失败!\n");
return ret;
}
批量和多寄存器读写函数
regmap_multi_reg_write()
regmap_multi_reg_write()是允许你将多个寄存器写入设备的函数之一,其原型如下:

其中,regs是一个reg_sequence元素的数组,表示写入序列的寄存器/寄存器值对,每次写入之后可选的延迟以微秒为单位。struct reg_sequence数据结构的定义如下:

其中,reg是寄存器地址;def是寄存器值;delay_us是寄存器写入后应用的延迟,以微秒为单位。
假设我们要初始化一款温湿度传感器,需要按顺序写入 3 个寄存器:
配置采样率寄存器(0x01):值 0x08(8Hz 采样),无延迟;
配置校准寄存器(0x02):值 0x10(校准系数),写入后需延迟 50 微秒(等校准生效);
使能传感器寄存器(0x03):值 0x01(开启传感器),无延迟。
C
#include <linux/regmap.h>
#include <linux/delay.h>
// 1. 定义传感器初始化的寄存器写入序列数组
static const struct reg_sequence sensor_init_seq[] = {
// ① 采样率寄存器:地址0x01,值0x08,无延迟
{.reg = 0x01, .def = 0x08, .delay_us = 0},
// ② 校准寄存器:地址0x02,值0x10,写入后延迟50微秒
{.reg = 0x02, .def = 0x10, .delay_us = 50},
// ③ 使能寄存器:地址0x03,值0x01,无延迟
{.reg = 0x03, .def = 0x01, .delay_us = 0},
};
// 2. 驱动probe函数中调用regmap_multi_reg_write()
static int sensor_probe(struct i2c_client *client) {
struct regmap *rmap;
int ret;
// 第一步:初始化Regmap(以I2C设备为例)
rmap = devm_regmap_init_i2c(client, &sensor_regmap_cfg);
if (IS_ERR(rmap)) {
dev_err(&client->dev, "Regmap初始化失败!\n");
return PTR_ERR(rmap);
}
// 第二步:调用批量写入函数,执行初始化序列
// 参数1:Regmap对象;参数2:写入序列数组;参数3:数组元素数量(3个)
ret = regmap_multi_reg_write(rmap, sensor_init_seq, ARRAY_SIZE(sensor_init_seq));
if (ret != 0) {
dev_err(&client->dev, "批量写入寄存器失败,错误码:%d\n", ret);
return ret;
}
dev_info(&client->dev, "传感器寄存器批量初始化完成!\n");
return 0;
}
regmap_bulk_read()和regmap_bulk_write()函数
regmap_bulk_read()和regmap_bulk_write()函数则从设备读取/写入多个寄存器,它们适用于大块数据。这两个函数定义如下:

其中,map是要操作的寄存器映射,reg表示读/写操作必须从哪个寄存器地址开始。在读取的情况下,val将包含读取的值;在写入的情况下,val必须指向要写入设备的数据数组。最后,val_count是val中的元素数量。


理解Regmap缓存系统
显然,Regmap支持数据缓存。是否使用缓存系统取决于regmap_config中cache_type字段的值。查看include/linux/regmap.h,该字段可能的值如下:

reg_default
缓存类型默认设置为REGCACHE_NONE,这意味着缓存被禁用。其他值只是定义了如何存储缓存。你的设备可能在某些寄存器中具有预定义的上电复位值。这些值可以存储在数组中,以便任何读取操作都返回该数组中包含的值。然而,任何写入操作都会影响设备中的实际寄存器,并更新该数组中的内容。这是一种可以用来加速访问设备的缓存。这个数组就是struct reg_default。该数据结构定义如下:

其中,reg是寄存器地址,def是寄存器的默认值。如果cache_type字段设置为none,reg_default元素将被忽略。如果未设置reg_default元素但仍启用缓存,则创建相应的缓存结构。它非常简单易用,只需要声明它并将它作为参数传递给regmap_config即可。让我们看一下位于drivers/regulator/ltc3589.c中的LTC3589稳压器驱动程序:


对ltc3589_req_defaults数组中任何一个寄存器的读操作都会立即返回该数组中的值。但是,写操作将在设备本身执行,并且将更新该数组中受影响的寄存器。这样,读取LTC3589_VRRCR寄存器将返回0xff,并在该寄存器中写入一个值,同时更新该数组中的条目,以便任何新的读操作都将直接从缓存中返回最后写入的值。
基于Regmap的SPI设备驱动程序示例
从配置到访问设备寄存器,设置Regmap涉及的所有步骤如下。
· 根据设备特性设置regmap_config对象,定义寄存器范围(如果需要的话)、默认值(如果有的话)、缓存类型(如果需要的话)等。如果需要自定义读写函数,请将它们传递给reg_read/reg_write字段。
· 在探测函数中,使用devm_regmap_init_i2c()、devm_regmap_init_spi()或devm_ regmap_init_mmio(),分别根据与底层设备连接的方式------I2C、SPI或内存映射------分配一个寄存器映射。
· 每当需要读取/写入寄存器时,调用regmap_[read|write]函数。· 完成寄存器映射后,假设使用了资源管理函数,则不需要执行任何其他操作,因为devres核心会释放Regmap资源,否则需要调用regmap_exit()以释放探测函数分配的寄存器映射。
Regmap示例
为了实现我们的目标,下面首先描述一个虚拟的SPI设备,我们可以使用Regmap为它编写驱动程序。这个SPI设备具有以下特性。
· 支持8位寄存器寻址和8位寄存器值。
· 可以访问的最大寄存器地址为0x80(但这不一定意味着这个SPI设备具有0x80个寄存器)。· 写掩码为0x80,有效的地址范围如下。
❏ 0x20~0x4F。
❏ 0x60~0x7F。
· 由于支持简单的SPI读/写操作,因此不需要提供自定义的读/写函数。
C
#include <linux/regmap.h>
//定义私有数据结构
struct private_struct
{
struct regmap *map;
int foo;
};
//定义读/写寄存器范围,即允许访问的寄存器
static const struct regmap_range wr_rd_range[] =
{
{.start = 0x20, .length = 0x30}, // 0x20~0x4F
{.start = 0x60, .length = 0x20}, // 0x60~0x7F
};
struct regmap_access_table drv_wr_table =
{
.yes_ranges = wr_rd_range,
.n_yes_ranges = ARRAY_SIZE(wr_rd_range),
};
struct regmap_access_table drv_rd_table =
{
.yes_ranges = wr_rd_range,
.n_yes_ranges = ARRAY_SIZE(wr_rd_range),
};
//如果设置了writable_reg和readable_reg,则不需要提供wr_table或rd_table
static bool writeable_reg(struct device *dev, unsigned int reg)
{
if (reg >= 0x20 && reg <= 0x4F)
return true;
if (reg >= 0x60 && reg <= 0x7F)
return true;
return false;
}
static bool readable_reg(struct device *dev, unsigned int reg)
{
if (reg >= 0x20 && reg <= 0x4F)
return true;
if (reg >= 0x60 && reg <= 0x7F)
return true;
return false;
}
//实现驱动程序探测函数
static int my_spi_drv_probe(struct spi_device *dev)
{
struct regmap_config config;
struct private_struct *priv;
unsigned char data;
memset(&config, 0, sizeof(config));
config.reg_bits = 8;
config.val_bits = 8;
config.write_flag_mask = 0x80;
config.max_register = 0x80;
config.fast_io = true;
config.writeable_reg = drv_writeable_reg;
config.readable_reg = drv_readable_reg;
//config.wr_table = drv_wr_table;
//config.rd_table = drv_rd_table;
priv->map = devm_regmap_init_spi(dev, &config);
regmap_read(priv->map, 0x30, &data);
[...]/*Process data*/
data = 0x24;
regmap_write(priv->map, 0x23, data); /*write new value*/
regmap_update_bits(priv->map, 0x44, 0b00100010, 0xFF);
[...]
return 0;
}
用户空间利用Regmap
从用户空间可以通过debugfs文件系统监视寄存器映射。为此,首先需要通过CONFIG_DEBUG_FS内核配置选项启用debugfs,然后使用以下命令挂载debugfs:
debugfs挂载
bash
mount -t debugfs none /sys/kernel/debug

之后,你可以在/sys/kernel/debug/regmap/目录下找到debugfs寄存器映射实现。这个由内核源代码中的drivers/base/regmap/regmap-debugfs.c实现的debugfs视图包含了基于Regmap API的驱动程序/外设的寄存器缓存(镜像)。从Regmap的主debugfs目录中,可以使用以下命令获取基于Regmap API的驱动程序的设备列表:

在上面的每个目录下,可以有以下一个或多个文件。
access
(1)access:对于每个寄存器,根据模式(readable、writable、volatile、precious)编码各种访问权限。


例如,第5e行的y y y n表示地址为5e的寄存器是可读、可写、易失的。
name
与寄存器映射关联的驱动程序的名称。如何查看与寄存器映射关联的驱动程序的名称?以702d3000.amx寄存器映射目录项为例:

但是,有些寄存器映射目录项是以"dummy--"开头的,如下所示:

当基于devtmpfs文件系统的/dev目录下没有相关的目录项时,就会设置这种目录项。你可以通过打印底层设备名称来进行检查,打印出来的底层设备名为nodev。
debugfs 中 Regmap 目录下以 dummy-- 开头的项,是内核的 "标记" ------ 表示这个 Regmap 绑定的底层设备(比如你开发的 SPI 设备),没有在 /dev 目录下生成对应的设备文件(由 devtmpfs 管理),所以内核用 dummy-- 命名该 Regmap 调试目录,同时底层设备名会显示 nodev(no device,无设备文件)。

在设备树中,可以通过搜索前缀为"dummy-"的名称来查找相关的节点,例如dummy-avs-monitor@fd5d2000:

cache_bypass
将寄存器映射置于仅缓存(cache-only)模式。如果启用cache_bypass,则写入寄存器的映射将只更新硬件而不直接进行缓存。

为了启用cache_bypass,你应该回显Y,如下所示:


这将在内核日志缓冲区中另外打印一条消息。
cache_dirty
表示硬件寄存器已重置为默认值,并且硬件寄存器与缓存状态不匹配。读取的值可以是Y或N。
cache_only
写入N将禁用对寄存器映射的缓存,同时触发缓存同步;而写入Y将强制仅对寄存器映射中的寄存器进行缓存。读取的值将根据当前缓存启用状态返回Y或N。当读取的值为Y时,所执行的任何写入都将被缓存(只更新寄存器缓存,而不会发生硬件更改)。
打开cache_only(即向/sys/kernel/debug/regmap/xxx/cache_only写入 Y)后,后续执行的任何regmap_write()/regmap_update_bits()等写操作,只会更新 Regmap 缓存里的值,完全不会向硬件寄存器发送任何写请求(硬件寄存器的值保持不变);只有写入 N 时,才会恢复 "写缓存 + 写硬件" 的正常逻辑,还会触发缓存同步。
range
表示寄存器映射的有效寄存器范围。

rbtree
表示提供rbtree缓存需要增加多少内存开销。


registers
用于读写与寄存器映射关联的实际寄存器的文件。

以上输出首先显示了寄存器地址,然后显示了对应的内容。
0x20 是寄存器地址(你代码里读的 0x30 就在这个列表里);
0x01 是 0x20 地址对应的硬件寄存器当前值;
只显示你 wr_rd_range 里允许访问的地址(0x200x4F、0x600x7F),其他地址可能显示为 0x00 或不显示(受 readable_reg/wr_table 限制)。
bash
# 例子1:把0x30寄存器的值改成0x12(对应你代码里读的0x30)
echo "0x30 0x12" > /sys/kernel/debug/regmap/dummy--xxxx/registers
# 例子2:把0x23寄存器的值改成0x24(对应你代码里写的0x23)
echo "0x23 0x24" > /sys/kernel/debug/regmap/dummy--xxxx/registers