前阵子调一块 RK3562 + RK817 的带电池主板,遇到一个特别折磨人的现象:插着充电器的时候系统能正常关机,但关机后马上按电源键,整机就像彻底断电一样,串口一个字符都没有,U-Boot 完全没有跑起来。拔掉充电器再按,一切又恢复正常。一开始我以为是哪颗料虚焊了,拿示波器翻来覆去量了半天,最后发现根本不是硬件问题,而是关机时 PMIC 的电源状态分支搞错了。
这块板子的电源方案是 RK3562 主控配 RK817 PMIC,电池供电,支持边充边用。问题稳定复现,影响面却不小:只要用户插着充电器关机,下次按键就无法开机,只能靠拔充电器或者按复位键救回来。对量产设备来说这就是妥妥的售后问题。这篇文章把整个排查过程和修复方案完整写出来,适合正在做 RK3562、RK817 相关 BSP、底层驱动或者整机电源设计的工程师参考。如果你还没遇到这个 bug,也建议收藏一下,这类“充电时关机后无法启动”的问题在带电池 Linux 设备上非常典型,早晚会碰上。
1. 问题现象与故障复现分析
1.1 复现条件:只有“充电时关机”会触发
先说现场。设备跑的是标准 Linux 系统,内核里已经注册了电源管理相关驱动,串口可以在 U-Boot 和内核阶段正常输出。触发路径非常简单:
- 设备电量保持在 50% 以上,插入原装充电器,系统正常进入桌面或命令行。
- 正常执行关机,例如
poweroff或者通过上层设置里的“关机”入口。 - 等待屏幕完全熄灭,电源指示灯常亮,说明 PMIC 还在工作。
- 按一下电源键,或者长按电源键,屏幕不亮,串口没有任何输出,U-Boot 没有任何启动打印。
- 拔掉充电器,再按电源键,设备正常开机,U-Boot 打印、内核启动全都正常。
这个现象我连续复现了好几次,不是偶发。最初怀疑过 U-Boot 镜像损坏、eMMC 启动分区异常、按键 GPIO 配置错误,但“拔掉充电器就能开机”这一条基本把这些问题全排除了——如果 U-Boot 被写坏或者 eMMC 引导有问题,不可能拔掉充电器就恢复。
另一个值得注意的细节是:充电状态下关机后,如果按的不是电源键,而是复位键(板上预留的 reset 测试点),设备也能正常开机。这说明 SoC、DDR、eMMC 以及 U-Boot 本身都是好的,问题是出在“按键开机”这条电源唤醒路径上。
1.2 RK3562 与 RK817 的电源链路回顾
要理解这个问题,得先把 RK3562 和 RK817 的电源关系捋清楚。RK817 是一颗集成了充放电管理、多路 DCDC/LDO 输出的 PMIC,在很多带电池的嵌入式设备上都能看到它的身影。它承担了两件关键事情:
- 充电管理:适配器的 VBUS 输入到 RK817 内部的充电器路径,给电池充电,同时电池也会通过 RK817 输出给系统供电。
- 系统供电:RK817 输出多路电源轨,包括给 SoC 核心供电的 VCC_SYS、DDR 供电、IO 供电、RTC 供电等。电源按键(PWRON)也直接接在 RK817 上,由 PMIC 负责检测。
正常开机流程是:按下电源键 → RK817 检测到 PWRON 事件 → 打开各路 DCDC/LDO 输出 → 等待电源稳定后释放 SoC 的复位信号 → RK3562 开始执行 BootROM → 加载 U-Boot。也就是说,U-Boot 能不能跑,前提是 PMIC 先把电供上、把复位时序走完。
关机流程正好相反:内核执行pm_power_off→ 系统调用 PMIC 驱动里注册的关机回调 → 通过 I2C 写寄存器,让 RK817 关闭系统电源输出 → SoC 彻底掉电。
问题就出在这个“彻底掉电”和“充电器在线”之间。充电器插着的时候,RK817 检测到外部电源存在,充电通路是活跃的,PMIC 内部逻辑也保持在待机状态。如果关机回调只是简单地执行了一套“纯电池模式”的 PowerOff 命令,PMIC 可能进入了某个既没有完全断电、也没有监听电源键的中间状态。
1.3 用排除法缩小故障范围
现场排查时,我先把能想到的硬件原因全部过了一遍,避免带着错误假设去改软件。
第一组测试:更换充电器和数据线。原装适配器、普通 USB 口、电脑 USB 口都试了,现象一致。排除适配器协议握手异常导致的电源波动。
第二组测试:拆掉电池,只用充电器供电,关机后再开机。这个场景其实很难直接复现,因为很多板子电池拆掉后系统供电路径会变化,不能完全等价。但至少验证了一点:电池本身没有损坏。
第三组测试:不同电量下复现。电量 80%、50%、20% 都试过,只要插着充电器关机,按键都无法开机。电量 5% 的时候更特殊,充电器插入瞬间电压跌落明显,还伴随复位抖动,属于另一个问题,后面会提到。
最终确定一个结论:这个故障和硬件虚焊、U-Boot 镜像损坏无关,是软件在“充电状态下关机”这个分支里没有把 PMIC 配置到正确的状态。排查方向从硬件转向了内核 PMIC 驱动的关机路径。
2. 根因定位:关机时充电器在线带来的状态分支
2.1 为什么充电关机后整机会“假死”
普通关机时拔掉充电器,RK817 没有外部电源,系统一旦关闭所有输出,PMIC 本身也进入低功耗的关机状态。这时候按键,PMIC 从头开始走上电时序,干干净净。
但插着充电器时情况完全不同。RK817 检测到 VBUS 在线,即使 SoC 关机了,PMIC 仍然要保持充电管理功能在工作,内部不少电路是带电的。这种状态其实更接近“充电待机”,而不是真正的 PowerOff。如果关机代码没有把这种情况和纯电池模式区分开,就会出现一个尴尬的局面:
PMIC 有一定输出保持,SoC 却没有完全按照冷启动流程去执行;同时 PMIC 的 PWRON 唤醒功能没有被使能,或者被关机命令覆盖了。于是按键按下去,PMIC 能感应到引脚变化,但它既不会重新打开系统主电源,也不会去拉高 SoC 的复位信号,整机就卡在“半睡半醒”的状态里。
打个比方:普通关机等于把房间配电箱的总闸拉了,按键开机相当于重新合闸,一切从头开始;插充电器关机则像是只关掉了部分灯光,走廊门禁还通着电,但你设定的门禁系统恰好把“开门按钮”给禁用了,所以按按钮没人理你。
2.2 锁定关键嫌疑点:PWRON 唤醒源与 PMIC 工作模式
确定了问题方向后,我去内核源码里翻 RK817 的驱动实现。重点看两个地方:
一是pm_power_off回调。很多 SDK 版本的 RK817 驱动里,关机回调会直接写一个 PowerOff 相关的寄存器位,让 PMIC 进入完全关机模式。这个操作没有区分当前是否有外部充电器输入。
二是 PWRON 唤醒源配置。RK817 的寄存器里通常有控制 PowerKey 是否作为唤醒源的位。正常情况下系统运行时这个位是配好的,但在关机流程里,如果后来又写入了关机命令,或者把部分寄存器恢复成了默认值,这个唤醒源可能就被关掉了。
我当时做了一个很直接的验证:在关机之后,通过调试工具直接往 RK817 的寄存器里写入“使能 PWRON 唤醒 + 进入 Standby 模式”的配置,然后再按键,设备马上就能正常开机。这就基本锁定了问题。
值得多说一句的是,RK817 在不同 SDK 版本里寄存器名称和位定义差异很大,甚至同一颗芯片,Linux 内核里的驱动和 U-Boot 里的初始化代码对“关机模式”的理解都可能不一样。不要死记寄存器地址,关键是把 datasheet 里关于 PowerOff Mode、Standby Mode、PowerKey Wake-Up 的描述吃透,再对照当前 SDK 的实际代码去查。
2.3 用示波器和串口日志确定故障卡在哪一级
排查这类问题时,示波器比日志更可靠。我把探头分别挂到 PWRON、VCC_SYS、SoC 的 RESET 引脚上,复现故障时观察到的波形很典型:
- 按下电源键,PWRON 引脚确实有正常的按键脉冲,说明按键信号已经送到 PMIC。
- VCC_SYS 没有任何反应,一直没有爬起来。
- SoC 的 RESET 引脚也维持在高电平没有任何变化,因为 PMIC 压根没打算触发复位释放时序。
这说明整个故障卡在 PMIC 这一层:电源键事件没有被转换成“打开系统电源 + 释放复位”的信号。SoC 根本没有上电,自然也谈不上执行 BootROM,U-Boot 更不可能打印任何东西。
串口方面也做了一个对照实验:拔掉充电器关机,再按键,串口会先出现一段 BootROM 的早期打印,然后是 U-Boot banner。而在故障状态下,串口从头到尾完全安静。这个对比让我更加确认,问题不在 U-Boot,而在 U-Boot 之前的电源时序。
3. 修复实现:让 PMIC 在充电关机时进入可唤醒 Standby
3.1 修复思路与方案选型:为什么不只改 DTS
明确了根因后,修复方向就很清晰了:关机时如果检测到充电器在线,不能让 PMIC 进入完全 PowerOff,而是应该让它进入“充电 Standby”模式,并且保证 PWRON 是有效的唤醒源;没有充电器时,才走正常的彻底断电流程。
有人可能会说,直接在设备树里把某个寄存器配死不就行了?不行。设备树配置的是初始状态,而“关机时要怎么处理”是一个动态行为,必须根据关机那一刻充电器是否在线来决定。设备树无法表达这种条件分支。
也有人会想,改 U-Boot 里的启动逻辑行不行?可以作为一种临时规避,比如在 U-Boot 阶段强制初始化 PMIC 并打开所有输出,但这样处理不优雅,而且也没有解决 PMIC 在关机状态下的错误配置问题。正确的做法是在内核 PMIC 驱动的关机回调里做判断。
3.2 内核驱动补丁:在 poweroff 回调中按充电状态分支处理
我修改的核心逻辑如下。这段代码没有直接复制某个具体 SDK 的寄存器操作,而是给出一个可参考的框架,实际移植时以你自己用的头文件和 datasheet 为准:
static struct rk817 *g_rk817; static void rk817_power_off(void) { unsigned int val = 0; struct rk817 *rk817 = g_rk817; if (!rk817) return; /* 读取充电器插入状态,具体寄存器位以实际SDK头文件为准 */ regmap_read(rk817->regmap, RK817_CHG_STS, &val); if (val & RK817_CHG_STS_PLUG_IN) { /* * 充电器在线: * 进入可被PowerKey唤醒的Standby模式,而不是完全PowerOff。 */ regmap_update_bits(rk817->regmap, RK817_SYS_CFG, RK817_SYS_CFG_POWER_OFF_EN, RK817_SYS_CFG_STANDBY_EN); /* 使能PWRON作为唤醒源 */ regmap_update_bits(rk817->regmap, RK817_SYS_CFG_WAKEUP, RK817_SYS_CFG_WAKEUP_MASK, RK817_SYS_CFG_WAKEUP_PWRON); } else { /* 无充电器:正常关机,关断所有输出 */ regmap_update_bits(rk817->regmap, RK817_SYS_CFG, RK817_SYS_CFG_POWER_OFF_EN, RK817_SYS_CFG_POWER_OFF_EN); } }几个实现细节值得展开:
第一,g_rk817这个全局指针要在驱动 probe 成功时保存,然后在关机回调里使用。不要在关机回调里重新去i2c_get_clientdata或者到处找全局引用,否则在系统关机阶段搞不好会拿到一个已经被释放的资源。
第二,充电器在线状态尽量复用充电子驱动已有的接口,比如通过power_supply_get_property查询在线状态,而不是在关机回调里再直接读寄存器。这样既不会和充电驱动内部的缓存状态冲突,也方便维护。如果你们内核版本比较老,充电驱动没有导出方便的查询接口,那直接读一次寄存器也可以,但要注意寄存器地址要和驱动里其它地方保持一致。
第三,驱动里原本可能已经有一个pm_power_off赋值点,比如在 probe 函数尾部:
static int rk817_i2c_probe(struct i2c_client *client) { ... g_rk817 = rk817; pm_power_off = rk817_power_off; ... }要确认没有其它驱动在后面覆盖这个指针。我曾经遇到过一个问题:主板上的另一颗 RTC 芯片驱动也注册了pm_power_off,把 PMIC 的覆盖掉了,导致我改的关机回调根本没被调用。排查方法是在关机回调里加一条早期打印,如果关机时串口看不到这条打印,就说明回调没生效。
3.3 U-Boot 与设备树的配套调整
修改完内核,我还顺手检查了设备树和 U-Boot 侧,确保整条链路是通的。
设备树方面,RK817 节点通常需要声明为系统的 power controller,这样内核才会在关机时调用它的回调。具体属性名称各 SDK 有差异,常见的是system-power-controller或者rockchip,system-power-controller。如果没有这个属性,内核心想调用 PMIC 关机回调也没法把这个设备和电源管理框架关联起来。
U-Boot 方面,重点确认了两点:
一是 U-Boot 早期 I2C 访问 RK817 是否正常。U-Boot 启动时通常会在power_init_board或者类似函数里初始化 PMIC 寄存器。如果 I2C 通信失败,启动会直接卡在前面,表现为串口有早期打印但无法继续。我在这块板子上没遇到这个问题,但对于量产板维修场景来说,这是一个很容易踩的坑。
二是我在 U-Boot 的 PMIC 初始化代码里临时加了一条打印,把 RK817 的充电状态寄存器打出来,方便判断上电时 PMIC 到底处于什么状态。这样每次按键开机,可以从串口直接看到当时的场景:
/* U-Boot 调试代码,打印充电状态寄存器 */ printf("RK817 CHG_STS = 0x%02x\n", read_reg(RK817_CHG_STS));有了这条打印,缩 root cause 的速度会快很多。正式版本里可以去掉。
另外,如果你们的产品有“关机充电显示”功能,也就是关机后插充电器会亮一个充电图标,需要格外小心。这个功能通常也依赖 RK817 在关机状态下的充电通路和唤醒逻辑,如果和 PowerKey 唤醒源配置冲突,就会出现“插充电器显示充电,但一按电源键就死机”的现象。我这次没有开启这个功能,所以没有和它叠加测试,如果你们项目里有,建议把两者放在一起验证。
3.4 编译、烧录与完整验证矩阵
修改完成后重新编译内核和 dtb,烧录到设备,然后按照下面的验证矩阵逐项测试:
| 场景 | 操作 | 预期结果 |
|---|---|---|
| 充电关机 | 插充电器状态下正常关机,2秒后按电源键 | 能进入 U-Boot,正常启动系统 |
| 非充电关机 | 拔掉充电器,正常关机,按电源键 | 能正常启动,与修改前一致 |
| 充电关机后拔充电器 | 插充电器关机,拔掉充电器,按电源键 | 能正常启动 |
| 关机后插充电器 | 无充电器关机,插上充电器,按电源键 | 能正常启动 |
| 电量100% | 满电充电状态,关机后按键 | 能正常启动 |
| 电量20% | 低电量充电状态,关机后按键 | 能正常启动 |
| 短按/长按 | 短按一下、长按2秒,各自测试 | 都能进入 U-Boot |
| 连续开关机 | 重复20次充电关机再开机 | 全部成功 |
| 插拔充电器 | 关机状态下反复插拔充电器,再按键 | 按键能正常唤醒,不会自动开机 |
这个矩阵里每一行我都实际跑过。最花时间的是最后一项“连续开关机”,因为每次都要等系统完全关掉再按键开机、等 U-Boot 起来、等内核起来再关机,一轮循环就要半分钟,20次就是十分钟上下,中间还得盯着串口看有没有一次异常。这也是我长期养成的习惯:改动电源相关代码,验证次数一定要够多,不能看一两次正常就收工。
4. 排坑实录与长期维护经验
4.1 调试过程中遇到的其他坑
第一个坑:误伤电池模式关机。第一次改代码时,我在分支判断里把“充电器不在线”的 else 分支写成了同样进入 Standby 模式,结果电池供电状态下关机后系统并没有彻底断电,功耗明显异常,一晚上掉电很多。后来重新检查逻辑,把 else 分支改回完全 PowerOff,问题消失。
第二个坑:充电器在线状态读错了寄存器。RK817 的充电状态寄存器里有好几个位,分别是 VBUS 存在、USB 枚举状态、ACIN 状态等。一开始我读的是 USB 枚举完成标志位,结果用普通充电器(不走 USB 协议枚举)测试时,这个位一直是 0,导致关机回调走了 else 分支,故障依旧。后来改为读取 VBUS 在线状态位,才算真正命中条件。
第三个坑:按键唤醒后设备偶发复位。修改后大部分场景正常了,但偶尔按键开机后跑一会儿又重启。用示波器抓了一遍,发现是 DDR 电源轨的爬升时序跟不上 SoC 复位释放的节奏,VCC_SYS 已经稳定了,DDR 供电还在慢慢爬,SoC 初始化 DDR 的时候电压不够,直接挂掉。这个问题的修复方式和 PMIC 关机回调无关,是在设备树里调整对应 DCDC regulator 的上升沿时间参数,让电源轨按顺序配合好。这也提醒我:充电关机后再开机,属于一种特殊的冷启动路径,它和正常按按键开机的时序不完全一样,不能只看 PMIC 是否输出,还要看各条电源轨之间的相对时序。
4.2 问题速查表与一次定位模板
把这次的经验整理成一个速查表,遇到类似“无法进入 U-Boot”的问题时可以直接对照:
| 现象 | 可能原因 | 排查手段 |
|---|---|---|
| 插充电器关机后按键完全无反应 | 关机回调让 PMIC 进入了无法监听 PowerKey 的状态 | 检查 PMIC 驱动 poweroff 分支和唤醒源配置 |
| 串口完全无打印,但按键能看到 PWRON 波形 | SoC 没有上电或没有脱离复位 | 示波器抓 VCC_SYS、RESET,确认电源时序 |
| U-Boot 早期有打印,随后卡住 | I2C 访问 PMIC 失败或 DDR 初始化异常 | 检查 U-Boot 里 PMIC 初始化、DDR 电源爬升 |
| 按键后反复重启 | 各路电源轨建立顺序不对 | 调整 regulator ramp delay,观察波形 |
| 不插充电器关机后也无法开机 | PMIC 关机配置被其它驱动覆盖或寄存器被误写 | 确认pm_power_off最终生效者,对比出厂配置 |
| 电量很低时失败概率高 | 充电建立导致欠压复位或预充条件不满足 | 抓 VBUS/VBAT 波形,检查预充/涓流参数 |
这套排查思路可以抽象成一个通用定位模板,不再局限于某颗芯片:
- 先看串口有没有任何输出,有输出说明 SoC 已经跑起来了,问题在执行层面;没有输出则基本是电源或复位问题。
- 示波器同时抓 PWRON、系统主电源、RESET,还原一整个按键上电过程。
- 查看 PMIC 在关机后处于哪种模式:完全 PowerOff、Standby、还是充电待机。
- 确认 PowerKey 唤醒源有没有被使能,有没有被后续写入的寄存器覆盖。
- 确认所有关键电源轨是否按顺序建立,特别是 DDR 和 IO 电源。
4.3 平台迁移时如何复用这套排查思路
这次的平台是 RK3562 + RK817,但这类问题的本质其实是通用的:带电池设备在关机状态下仍然有充电通路在跑,只要 PMIC 的关机处理逻辑没有把“充电器在线”当成一种特殊状态,就可能出现按键无法唤醒的隐患。换到别的 SoC 搭配别的 PMIC,排查方法完全一样。
我后来在另一个项目上换过一套平台,PMIC 完全不同。当时也出现了类似的“关机充电后无法开机”,直接用同一个模板去定位:先抓按键波形,再查 PMIC 的关机状态寄存器,然后看唤醒源配置,半小时就定位到了问题。
另外有一个建议:在做量产固件之前,一定把“关机 + 充电器在线 + 按键开机”这种情况写进测试用例。很多产品的出厂测试只关注正常开机、休眠唤醒、关机充电,偏偏漏了“充电时关机再开机”这种组合。用户在实际使用中很容易触发,一旦触发就是完全死机级别的问题,体验非常糟糕。把这个场景纳入常规回归,是很值得的投入。