Linux regmap 子系统实战:在驱动中 dump PMIC 寄存器定位供电问题
文章目录Linux regmap 子系统实战在驱动中 dump PMIC 寄存器定位供电问题1. regmap 解决的不是“读寄存器”而是“统一读寄存器”2. 一个 PMIC 的 regmap 是怎么来的3. 业务驱动怎么找到 PMIC 对应的 regmap4. 用 regmap dump 一段 PMIC 寄存器窗口5. 为什么不用 devmem 或自己封装 SPMI 读写6. regmap_config 里哪些字段最容易影响结果7. 看到异常值后怎么继续排查8. 常见坑把 bus device 当成 PMIC device把 regmap name 当成稳定 ABI忽略 regmap_read() 返回值大范围扫 PMIC 寄存器忽略 cache 和 volatile在 public blog 里泄露项目路径和芯片信息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()。从第一性原理看驱动访问硬件寄存器至少有三个问题问题如果不用 regmapregmap 做了什么总线差异I2C、SPI、SPMI、MMIO 各写一套读写逻辑用统一 API 隐藏底层 bus 操作寄存器格式地址位宽、值位宽、大小端、是否连续读写都要手动处理用struct regmap_config描述寄存器格式访问规则哪些寄存器可读、可写、不可缓存、读了有副作用要靠驱动自己约束用 readable/writeable/volatile/precious 表和回调集中约束性能与电源慢速总线频繁读写成本高休眠恢复后状态还要同步可选 regcache支持 cache-only、dirty、sync调试每个驱动自己做 dump 接口regmap core/debugfs/trace 能提供统一观察点所以 regmap 不是某一种总线驱动。它更像“寄存器访问适配层”驱动逻辑 - 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 初始化大概长这样staticconststructregmap_configpmic_regmap_config{.reg_bits16,.val_bits8,.max_register0xffff,.fast_iotrue,};staticintvendor_pmic_probe(structspmi_device*sdev){structregmap*map;unsignedinttype;intret;mapdevm_regmap_init_spmi_ext(sdev,pmic_regmap_config);if(IS_ERR(map))returnPTR_ERR(map);retregmap_read(map,PMIC_TYPE,type);if(ret)returnret;/* 后续把 map 挂到 device 上子模块可以通过 dev_get_regmap() 取到 */return0;}这里最关键的不是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()。设备树可以设计成下面这种公开可读的形式spmi_bus { #address-cells 2; #size-cells 0; pmic_x: pmic1 { 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 对应的 devicestaticstructdevice*get_device_from_phandle(structdevice*dev,constchar*name){structdevice_node*np;structplatform_device*pdev;structdevice*target;npof_parse_phandle(dev-of_node,name,0);if(!np)returnNULL;pdevof_find_device_by_node(np);of_node_put(np);if(!pdev)returnNULL;/* * 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). */targetget_device(pdev-dev);put_device(pdev-dev);returntarget;}上面这段代码只表达思路实际工程里要注意 device 生命周期拿到的target用完后要put_device(target)。很多时候更推荐直接引用具体 PMIC 子节点或者使用平台/原厂已经提供的 lookup API。只有在没有更稳定入口时才退而求其次遍历 device child。遍历 child 查找 regmap 的逻辑可以写成这样#defineTARGET_REGMAP_NAME0-01structregmap_lookup_ctx{structregmap*map;};staticintmatch_child_regmap(structdevice*dev,void*data){structregmap_lookup_ctx*ctxdata;structregmap*map;constchar*name;mapdev_get_regmap(dev,NULL);if(!IS_ERR_OR_NULL(map)){nameregmap_name(map);if(namesysfs_streq(name,TARGET_REGMAP_NAME)){ctx-mapmap;return1;}}returndevice_for_each_child(dev,ctx,match_child_regmap);}staticstructregmap*find_regmap_under(structdevice*parent){structregmap_lookup_ctxctx{0};device_for_each_child(parent,ctx,match_child_regmap);returnctx.map;}这里的TARGET_REGMAP_NAME要按平台实际情况来。案例里0-01同时是 device name 和 regmap name所以可以用它判断是否找到了目标 PMIC 对应的 regmap。但这不是 Linux 统一保证的规则而是该平台实现里的事实。公开文章里写代码时最好把它当作“项目匹配条件”不要当作通用 ABI。4. 用 regmap dump 一段 PMIC 寄存器窗口拿到struct regmap *map后最直接的 dump 方法就是按地址范围逐个读。假设芯片手册里定义了几个 regulator 的寄存器窗口#defineL13B_ADDR_BASE0xCD00#defineL12B_ADDR_BASE0xCC00#defineL6B_ADDR_BASE0xC600如果关心L6B就从0xC600开始 dump 一段窗口staticvoiddump_regmap_window(structregmap*map,unsignedintbase,unsignedintlen){unsignedintvals[0x100];unsignedinti;intret;if(IS_ERR_OR_NULL(map)){pr_info(regmap is invalid\n);return;}lenmin_t(unsignedint,len,ARRAY_SIZE(vals));lenround_down(len,8);if(!len)return;memset(vals,0,sizeof(vals));for(i0;ilen;i){retregmap_read(map,basei,vals[i]);if(ret){pr_info(%s: read %04x failed: %d\n,regmap_name(map),basei,ret);vals[i]0xff;}}pr_info(%s: addr_base%04x\n,regmap_name(map),base);for(i0;ilen;i8){pr_info(%s: %04x:%02x %04x:%02x %04x:%02x %04x:%02x %04x:%02x %04x:%02x %04x:%02x %04x:%02x\n,regmap_name(map),basei0,vals[i0],basei1,vals[i1],basei2,vals[i2],basei3,vals[i3],basei4,vals[i4],basei5,vals[i5],basei6,vals[i6],basei7,vals[i7]);}}调用时dump_regmap_window(client-pmic_map,L6B_ADDR_BASE,0x100);原始案例里关心的是base 0x46这个寄存器。对L6B_ADDR_BASE 0xC600来说就是0xC646。如果手册说明这个寄存器在正常打开时通常是0x80而异常时读到0x00这就比“驱动调用了 enable”更有说服力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 并复用它能少踩很多坑做法适合场景风险devmemCPU 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()”而是原理图确定 PMIC - 设备树确定 bus/device - 从 phandle 找起点 device - 遍历 child 找目标 regmap - 按手册 dump 目标寄存器窗口 - 对照具体 bit 判断供电状态6. regmap_config 里哪些字段最容易影响结果struct regmap_config决定了 regmap 怎么解释寄存器地址和值。常见字段可以按“最小必需”和“工程增强”分两类看。最小必需字段staticconststructregmap_configexample_regmap_config{.reg_bits16,.val_bits8,.max_register0xffff,};这些字段回答三个基本问题寄存器地址有多宽比如 8 位、16 位、32 位。寄存器值有多宽比如 8 位、16 位、32 位。最大合法寄存器地址是多少。工程增强字段staticconststructregmap_configexample_regmap_config{.reg_bits16,.val_bits8,.max_register0xffff,.rd_tablereadable_regs,.wr_tablewriteable_regs,.volatile_tablevolatile_regs,.precious_tableprecious_regs,.cache_typeREGCACHE_RBTREE,.reg_format_endianREGMAP_ENDIAN_BIG,.val_format_endianREGMAP_ENDIAN_BIG,};这些字段决定调试可靠性字段作用调试时的影响rd_table限制哪些寄存器可读dump 读到-EIO/-EINVAL时先看是否被访问表拦住wr_table限制哪些寄存器可写防止误写只读/危险寄存器volatile_table标记不能缓存的寄存器状态寄存器、计数器、IRQ 状态一般要 volatileprecious_table标记不应随便读的寄存器clear-on-read、FIFO、敏感状态不要全量 dumpcache_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 放在三个时间点probe 后 - 外设请求电源前 - regulator enable 后 - 外设异常发生后如果只有异常发生后的一次 dump很容易不知道寄存器是“从来没打开”还是“打开过又被关了”。8. 常见坑把 bus device 当成 PMIC devicespmi_bus是一个很好的遍历起点但它不一定就是注册 regmap 的 device。原始案例里需要从 bus device 往 child 里找直到dev_get_regmap()成功并且 name 匹配。把 regmap name 当成稳定 ABI0-01这种名字在具体平台上很有用但不要假设所有内核、所有 PMIC 都这么命名。能用明确 phandle 或平台 API 时优先用明确入口。忽略regmap_read()返回值dump 代码里不要只收集val要检查返回值。访问表、总线错误、设备未 ready、地址越界都会让读失败。大范围扫 PMIC 寄存器PMIC 里可能有 clear-on-read、fault latch、FIFO、一次性状态等寄存器。没有手册确认前不要把0x0000到0xffff全扫一遍。调试时优先 dump 目标 regulator 的窗口。忽略 cache 和 volatile如果状态寄存器没有被标记为 volatileregmap 可能返回缓存值。电源状态、IRQ 状态、ADC/计数器类寄存器通常不应该缓存。在 public blog 里泄露项目路径和芯片信息真实项目里设备树路径、PMIC 型号、客户项目名、原厂私有 compatible 都可能不适合公开。写文章时保留技术模式替换成vendor,spmi-pmic、pmic1、L6B这类匿名名字即可。9. 实战选择遇到寄存器 dump 需求时怎么选场景推荐路径单个 MMIO 控制器寄存器地址明确先用驱动里的readl()或短期devmem验证I2C/SPI/SPMI 外设已有内核驱动优先找现成struct regmap使用dev_get_regmap()或子系统 APIPMIC/MFD 被多个子模块共享不要绕过原厂驱动复用它注册的 regmap需要连续读传感器数据用regmap_bulk_read()或 raw read注意字节序需要改寄存器某几个 bit用regmap_update_bits()避免读改写时误伤其他 bitsuspend/resume 后寄存器丢失使用 regcacheregcache_mark_dirty()、regcache_sync()临时人工查看寄存器看 debugfs 是否有/sys/kernel/debug/regmap/.../registers总结这个 PMIC dump 案例背后的核心不是“写一个 for 循环读寄存器”。真正的主线是硬件问题某路供电是否真的打开 - 原理图定位 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 寄存器就不再是碰运气而是一条可复用的调试路径。