文章目录
- [Linux regmap 子系统实战:在驱动中 dump PMIC 寄存器定位供电问题](#Linux regmap 子系统实战:在驱动中 dump PMIC 寄存器定位供电问题)
-
- [1. regmap 解决的不是"读寄存器",而是"统一读寄存器"](#1. regmap 解决的不是“读寄存器”,而是“统一读寄存器”)
- [2. 一个 PMIC 的 regmap 是怎么来的](#2. 一个 PMIC 的 regmap 是怎么来的)
- [3. 业务驱动怎么找到 PMIC 对应的 regmap](#3. 业务驱动怎么找到 PMIC 对应的 regmap)
- [4. 用 regmap dump 一段 PMIC 寄存器窗口](#4. 用 regmap dump 一段 PMIC 寄存器窗口)
- [5. 为什么不用 devmem 或自己封装 SPMI 读写](#5. 为什么不用 devmem 或自己封装 SPMI 读写)
- [6. regmap_config 里哪些字段最容易影响结果](#6. regmap_config 里哪些字段最容易影响结果)
- [7. 看到异常值后怎么继续排查](#7. 看到异常值后怎么继续排查)
- [8. 常见坑](#8. 常见坑)
-
- [把 bus device 当成 PMIC device](#把 bus device 当成 PMIC device)
- [把 regmap name 当成稳定 ABI](#把 regmap name 当成稳定 ABI)
- [忽略 `regmap_read()` 返回值](#忽略
regmap_read()返回值) - [大范围扫 PMIC 寄存器](#大范围扫 PMIC 寄存器)
- [忽略 cache 和 volatile](#忽略 cache 和 volatile)
- [在 public blog 里泄露项目路径和芯片信息](#在 public blog 里泄露项目路径和芯片信息)
- [9. 实战选择:遇到寄存器 dump 需求时怎么选](#9. 实战选择:遇到寄存器 dump 需求时怎么选)
- 总结
Linux regmap 子系统实战:在驱动中 dump PMIC 寄存器定位供电问题

调驱动时,经常会遇到一种很难只靠日志解释的问题:某一路电源到底有没有打开?
比如板子上有一路供电叫 L6B。从原理图看,它来自某颗 PMIC;从现象看,外设偶尔不工作;从普通日志看,只能看到驱动 probe 了、接口调用了,却看不到 PMIC 内部寄存器到底是什么状态。最后真正能落锤的,往往是把这颗 PMIC 对应的寄存器 dump 出来,对照芯片手册看某个 enable/status bit。
这就是 regmap 很适合出场的地方。
本文不从 API 清单开始背,而是围绕一个真实工作流讲清楚:
- 为什么 dump PMIC 寄存器要借助 regmap。
- PMIC 原厂驱动通常怎么把 SPMI 访问封装成
struct regmap。 - 业务驱动怎么从设备树和 device 层级里找到这个 regmap。
- 拿到 regmap 后,怎么安全地 dump 一段寄存器窗口。
- 看到
0x80或0x00这类值时,应该怎么分析。
1. regmap 解决的不是"读寄存器",而是"统一读寄存器"

先别急着看 regmap_read()。从第一性原理看,驱动访问硬件寄存器至少有三个问题:
| 问题 | 如果不用 regmap | regmap 做了什么 |
|---|---|---|
| 总线差异 | I2C、SPI、SPMI、MMIO 各写一套读写逻辑 | 用统一 API 隐藏底层 bus 操作 |
| 寄存器格式 | 地址位宽、值位宽、大小端、是否连续读写都要手动处理 | 用 struct regmap_config 描述寄存器格式 |
| 访问规则 | 哪些寄存器可读、可写、不可缓存、读了有副作用,要靠驱动自己约束 | 用 readable/writeable/volatile/precious 表和回调集中约束 |
| 性能与电源 | 慢速总线频繁读写成本高,休眠恢复后状态还要同步 | 可选 regcache,支持 cache-only、dirty、sync |
| 调试 | 每个驱动自己做 dump 接口 | regmap core/debugfs/trace 能提供统一观察点 |
所以 regmap 不是某一种总线驱动。它更像"寄存器访问适配层":
text
驱动逻辑
-> regmap API: regmap_read / regmap_write / regmap_update_bits / regmap_bulk_read
-> regmap core: 锁、访问权限、缓存、格式化、trace/debugfs
-> bus backend: i2c / spi / spmi / mmio / slimBus / sdw ...
-> 硬件寄存器
这也是为什么 PMIC、Codec、Sensor、Clock、Pinctrl、PWM、MFD 里经常能看到 regmap。它们都有大量寄存器,而且寄存器访问经常跨模块复用。
2. 一个 PMIC 的 regmap 是怎么来的
在 PMIC 场景里,业务驱动通常不直接知道 SPMI 怎么访问寄存器。芯片原厂或平台侧 PMIC 驱动会先把底层 SPMI 访问封装成 regmap。
一个简化后的 PMIC regmap 初始化大概长这样:
c
static const struct regmap_config pmic_regmap_config = {
.reg_bits = 16,
.val_bits = 8,
.max_register = 0xffff,
.fast_io = true,
};
static int vendor_pmic_probe(struct spmi_device *sdev)
{
struct regmap *map;
unsigned int type;
int ret;
map = devm_regmap_init_spmi_ext(sdev, &pmic_regmap_config);
if (IS_ERR(map))
return PTR_ERR(map);
ret = regmap_read(map, PMIC_TYPE, &type);
if (ret)
return ret;
/* 后续把 map 挂到 device 上,子模块可以通过 dev_get_regmap() 取到 */
return 0;
}
这里最关键的不是 regmap_read(),而是 devm_regmap_init_spmi_ext() 这一句。它把 struct spmi_device 背后的 SPMI 通信能力包装成统一的 regmap。
从这以后,上层模块不用关心:
- SPMI 命令怎么发。
- 寄存器地址怎么编码。
- 每次读写走哪个 bus 回调。
- 是否需要统一加锁。
上层只需要拿到 struct regmap *map,然后按寄存器地址读写。
在本地内核源码里也能看到同样的模式。include/linux/regmap.h 提供了 devm_regmap_init_i2c()、devm_regmap_init_spi()、devm_regmap_init_mmio()、devm_regmap_init_spmi_ext() 等入口;drivers/base/regmap/regmap-i2c.c 里,I2C backend 会根据 reg_bits、val_bits 和 adapter capability 选择 SMBus byte、SMBus word 或普通 I2C transfer。
换句话说,regmap API 看起来统一,真正干活的是后面的 bus backend。
3. 业务驱动怎么找到 PMIC 对应的 regmap

问题来了:PMIC regmap 已经由原厂驱动注册好了,但你的业务驱动不一定直接持有那颗 PMIC 的 struct device。这时要先找到正确的 device,再从 device 上取 regmap。
原始案例里的思路是:
- 先明确要查哪路电,比如
L6B。 - 根据原理图确定这路电来自哪颗 PMIC。
- 在设备树里找到这颗 PMIC 挂在哪条 bus 上,比如
spmi_bus下的某个 USID。 - 在自己的驱动节点里引用这个 bus 或 PMIC 相关节点。
- 驱动 probe 时解析 phandle,得到起点 device。
- 从这个起点往 child device 遍历,找到 regmap name 匹配的那个 device。
- 对这个 device 调
dev_get_regmap()。
设备树可以设计成下面这种公开可读的形式:
dts
&spmi_bus {
#address-cells = <2>;
#size-cells = <0>;
pmic_x: pmic@1 {
compatible = "vendor,spmi-pmic";
reg = <0x1 SPMI_USID>;
#address-cells = <1>;
#size-cells = <0>;
};
};
my_driver_node {
compatible = "example,my-driver";
pmic-bus = <&spmi_bus>;
};
驱动里先拿到这个 phandle 对应的 device:
c
static struct device *get_device_from_phandle(struct device *dev,
const char *name)
{
struct device_node *np;
struct platform_device *pdev;
struct device *target;
np = of_parse_phandle(dev->of_node, name, 0);
if (!np)
return NULL;
pdev = of_find_device_by_node(np);
of_node_put(np);
if (!pdev)
return NULL;
/*
* of_find_device_by_node() returns a referenced platform_device.
* Take an explicit device reference for the caller, then drop the
* temporary reference. The caller must put_device(target).
*/
target = get_device(&pdev->dev);
put_device(&pdev->dev);
return target;
}
上面这段代码只表达思路,实际工程里要注意 device 生命周期:拿到的 target 用完后要 put_device(target)。很多时候更推荐直接引用具体 PMIC 子节点,或者使用平台/原厂已经提供的 lookup API。只有在没有更稳定入口时,才退而求其次遍历 device child。
遍历 child 查找 regmap 的逻辑可以写成这样:
c
#define TARGET_REGMAP_NAME "0-01"
struct regmap_lookup_ctx {
struct regmap *map;
};
static int match_child_regmap(struct device *dev, void *data)
{
struct regmap_lookup_ctx *ctx = data;
struct regmap *map;
const char *name;
map = dev_get_regmap(dev, NULL);
if (!IS_ERR_OR_NULL(map)) {
name = regmap_name(map);
if (name && sysfs_streq(name, TARGET_REGMAP_NAME)) {
ctx->map = map;
return 1;
}
}
return device_for_each_child(dev, ctx, match_child_regmap);
}
static struct regmap *find_regmap_under(struct device *parent)
{
struct regmap_lookup_ctx ctx = { 0 };
device_for_each_child(parent, &ctx, match_child_regmap);
return ctx.map;
}
这里的 TARGET_REGMAP_NAME 要按平台实际情况来。案例里 0-01 同时是 device name 和 regmap name,所以可以用它判断是否找到了目标 PMIC 对应的 regmap。但这不是 Linux 统一保证的规则,而是该平台实现里的事实。公开文章里写代码时,最好把它当作"项目匹配条件",不要当作通用 ABI。
4. 用 regmap dump 一段 PMIC 寄存器窗口

拿到 struct regmap *map 后,最直接的 dump 方法就是按地址范围逐个读。
假设芯片手册里定义了几个 regulator 的寄存器窗口:
c
#define L13B_ADDR_BASE 0xCD00
#define L12B_ADDR_BASE 0xCC00
#define L6B_ADDR_BASE 0xC600
如果关心 L6B,就从 0xC600 开始 dump 一段窗口:
c
static void dump_regmap_window(struct regmap *map,
unsigned int base,
unsigned int len)
{
unsigned int vals[0x100];
unsigned int i;
int ret;
if (IS_ERR_OR_NULL(map)) {
pr_info("regmap is invalid\n");
return;
}
len = min_t(unsigned int, len, ARRAY_SIZE(vals));
len = round_down(len, 8);
if (!len)
return;
memset(vals, 0, sizeof(vals));
for (i = 0; i < len; i++) {
ret = regmap_read(map, base + i, &vals[i]);
if (ret) {
pr_info("%s: read %04x failed: %d\n",
regmap_name(map), base + i, ret);
vals[i] = 0xff;
}
}
pr_info("%s: addr_base=%04x\n", regmap_name(map), base);
for (i = 0; i < len; i += 8) {
pr_info("%s: %04x:%02x %04x:%02x %04x:%02x %04x:%02x "
"%04x:%02x %04x:%02x %04x:%02x %04x:%02x\n",
regmap_name(map),
base + i + 0, vals[i + 0],
base + i + 1, vals[i + 1],
base + i + 2, vals[i + 2],
base + i + 3, vals[i + 3],
base + i + 4, vals[i + 4],
base + i + 5, vals[i + 5],
base + i + 6, vals[i + 6],
base + i + 7, vals[i + 7]);
}
}
调用时:
c
dump_regmap_window(client->pmic_map, L6B_ADDR_BASE, 0x100);
原始案例里关心的是 base + 0x46 这个寄存器。对 L6B_ADDR_BASE = 0xC600 来说,就是 0xC646。
如果手册说明这个寄存器在正常打开时通常是 0x80,而异常时读到 0x00,这就比"驱动调用了 enable"更有说服力:
text
0xC646 = 0x80 -> L6B 状态符合预期
0xC646 = 0x00 -> L6B 可能没有真正打开,继续查 regulator enable 路径
这里要注意一点:dump 的输出只是事实,不是结论。结论必须回到芯片手册。PMIC 寄存器常见同名字段很多,有的是 request bit,有的是 enable bit,有的是 status bit,还有的是 fault/ocp/uvlo 状态。不要只看到 0x80 就凭直觉判断。
5. 为什么不用 devmem 或自己封装 SPMI 读写
devmem 很适合快速验证 MMIO 寄存器,但 PMIC 挂在 SPMI/I2C/SPI 这类总线上时,情况完全不同:
- 你访问的不是 CPU 物理地址,而是总线设备里的寄存器地址。
- 访问时需要走控制器、协议、设备地址、命令格式。
- PMIC 往往由 MFD/Regulator/IRQ/GPIO 等多个子模块共享。
- 有些寄存器有读副作用,不能粗暴全量扫。
直接自己封装 SPMI 读写也可以,但通常不是最短路径。既然原厂 PMIC 驱动已经把通信能力注册成 regmap,再从 device 上拿到 map 并复用它,能少踩很多坑:
| 做法 | 适合场景 | 风险 |
|---|---|---|
devmem |
CPU MMIO 寄存器快速验证 | 不适合 SPMI/I2C/SPI PMIC 内部寄存器 |
| 自己写 bus read/write | 平台没有 regmap 或在 bootloader/早期 bring-up | 重复造协议层,容易绕过原驱动锁和状态管理 |
dev_get_regmap() 复用现有 map |
原厂/平台驱动已经注册 regmap | 需要正确找到 device,注意 regmap name 和访问范围 |
| regmap debugfs | 临时调试、已有 debugfs 节点 | 线上不可依赖,读 precious register 有风险 |
所以这个案例的关键路径不是"我会调用 regmap_read()",而是:
text
原理图确定 PMIC
-> 设备树确定 bus/device
-> 从 phandle 找起点 device
-> 遍历 child 找目标 regmap
-> 按手册 dump 目标寄存器窗口
-> 对照具体 bit 判断供电状态
6. regmap_config 里哪些字段最容易影响结果

struct regmap_config 决定了 regmap 怎么解释寄存器地址和值。常见字段可以按"最小必需"和"工程增强"分两类看。
最小必需字段:
c
static const struct regmap_config example_regmap_config = {
.reg_bits = 16,
.val_bits = 8,
.max_register = 0xffff,
};
这些字段回答三个基本问题:
- 寄存器地址有多宽,比如 8 位、16 位、32 位。
- 寄存器值有多宽,比如 8 位、16 位、32 位。
- 最大合法寄存器地址是多少。
工程增强字段:
c
static const struct regmap_config example_regmap_config = {
.reg_bits = 16,
.val_bits = 8,
.max_register = 0xffff,
.rd_table = &readable_regs,
.wr_table = &writeable_regs,
.volatile_table = &volatile_regs,
.precious_table = &precious_regs,
.cache_type = REGCACHE_RBTREE,
.reg_format_endian = REGMAP_ENDIAN_BIG,
.val_format_endian = REGMAP_ENDIAN_BIG,
};
这些字段决定调试可靠性:
| 字段 | 作用 | 调试时的影响 |
|---|---|---|
rd_table |
限制哪些寄存器可读 | dump 读到 -EIO/-EINVAL 时先看是否被访问表拦住 |
wr_table |
限制哪些寄存器可写 | 防止误写只读/危险寄存器 |
volatile_table |
标记不能缓存的寄存器 | 状态寄存器、计数器、IRQ 状态一般要 volatile |
precious_table |
标记不应随便读的寄存器 | clear-on-read、FIFO、敏感状态不要全量 dump |
cache_type |
选择缓存实现 | 省慢速总线访问,但要处理休眠恢复同步 |
reg_format_endian |
地址编码大小端 | 配错会导致访问错寄存器 |
val_format_endian |
值编码大小端 | 配错会导致字节序看起来反了 |
PMIC dump 最容易踩的坑,是把缓存值当硬件实时值。状态寄存器必须被标成 volatile,或者在调试时明确绕过缓存。否则你看到的是 regcache 里的旧值,而不是 PMIC 当前状态。
7. 看到异常值后怎么继续排查

假设 L6B 对应状态寄存器正常应该是 0x80,现在读到 0x00,下一步不要马上改驱动。先把现象拆成几个可验证问题:
| 问题 | 观察点 | 下一步 |
|---|---|---|
| PMIC regmap 找对了吗 | regmap_name(map)、device name、PMIC USID |
如果 name 不对,先修正 device 查找路径 |
| 读的是对的 register window 吗 | base、offset、手册章节 |
如果 base 错,dump 另一路 regulator 只会误导 |
| enable 请求是否发出 | regulator consumer、PMIC enable register | 对比 enable 前后 dump |
| 状态 bit 是否需要延时 | 上电时序、settling time | 在 enable 后延时再读,或者查 status/fault |
| 是否被其他模块关掉 | regulator constraints、runtime PM、suspend/resume | 对比关键时间点 dump |
| 是否读到了缓存旧值 | regcache、volatile 配置 | 确认该状态寄存器是否 volatile |
| 是否读了有副作用寄存器 | clear-on-read、fault latch | 缩小 dump 范围,不要扫未知窗口 |
我习惯把 dump 放在三个时间点:
text
probe 后
-> 外设请求电源前
-> regulator enable 后
-> 外设异常发生后
如果只有异常发生后的一次 dump,很容易不知道寄存器是"从来没打开",还是"打开过又被关了"。
8. 常见坑
把 bus device 当成 PMIC device
spmi_bus 是一个很好的遍历起点,但它不一定就是注册 regmap 的 device。原始案例里需要从 bus device 往 child 里找,直到 dev_get_regmap() 成功并且 name 匹配。
把 regmap name 当成稳定 ABI
0-01 这种名字在具体平台上很有用,但不要假设所有内核、所有 PMIC 都这么命名。能用明确 phandle 或平台 API 时,优先用明确入口。
忽略 regmap_read() 返回值
dump 代码里不要只收集 val,要检查返回值。访问表、总线错误、设备未 ready、地址越界都会让读失败。
大范围扫 PMIC 寄存器
PMIC 里可能有 clear-on-read、fault latch、FIFO、一次性状态等寄存器。没有手册确认前,不要把 0x0000 到 0xffff 全扫一遍。调试时优先 dump 目标 regulator 的窗口。
忽略 cache 和 volatile
如果状态寄存器没有被标记为 volatile,regmap 可能返回缓存值。电源状态、IRQ 状态、ADC/计数器类寄存器通常不应该缓存。
在 public blog 里泄露项目路径和芯片信息
真实项目里,设备树路径、PMIC 型号、客户项目名、原厂私有 compatible 都可能不适合公开。写文章时保留技术模式,替换成 vendor,spmi-pmic、pmic@1、L6B 这类匿名名字即可。
9. 实战选择:遇到寄存器 dump 需求时怎么选
| 场景 | 推荐路径 |
|---|---|
| 单个 MMIO 控制器,寄存器地址明确 | 先用驱动里的 readl() 或短期 devmem 验证 |
| I2C/SPI/SPMI 外设已有内核驱动 | 优先找现成 struct regmap,使用 dev_get_regmap() 或子系统 API |
| PMIC/MFD 被多个子模块共享 | 不要绕过原厂驱动,复用它注册的 regmap |
| 需要连续读传感器数据 | 用 regmap_bulk_read() 或 raw read,注意字节序 |
| 需要改寄存器某几个 bit | 用 regmap_update_bits(),避免读改写时误伤其他 bit |
| suspend/resume 后寄存器丢失 | 使用 regcache:regcache_mark_dirty()、regcache_sync() |
| 临时人工查看寄存器 | 看 debugfs 是否有 /sys/kernel/debug/regmap/.../registers |
总结
这个 PMIC dump 案例背后的核心不是"写一个 for 循环读寄存器"。真正的主线是:
text
硬件问题:某路供电是否真的打开
-> 原理图:定位 PMIC 和 regulator
-> 设备树:定位 SPMI bus / PMIC device
-> Linux device model:找到注册 regmap 的 device
-> regmap:用统一接口读取寄存器
-> PMIC 手册:解释 bit 含义
regmap 把底层总线细节收起来,让驱动可以用统一 API 操作寄存器;但它不会替你判断硬件状态。0x80、0x00 这类值只有放回芯片手册、供电时序和 driver 调用链里,才有意义。
所以以后看到 PMIC、Codec、Sensor、MFD 里的寄存器问题,可以先问三件事:
- 这个设备有没有现成 regmap?
- 我能不能从稳定的 device 入口拿到它?
- 我要读的寄存器是否安全、实时、可解释?
这三个问题答清楚,dump 寄存器就不再是碰运气,而是一条可复用的调试路径。