4、下电时序控制:硬件下电序列、软件控制下电、安全下电策略
各位同学,咱们接着聊。上电时序搞定了,下电时序其实更让人头疼。为什么?因为上电出问题,顶多系统起不来,你还能debug。下电出问题,轻则数据丢失,重则把PMIC或者SoC烧了。我在MT8676的项目里,就亲眼见过一块板子因为下电时序乱掉,直接把eMMC给搞挂了。嗯,今天咱们就把下电这件事彻底讲透。
4.1 硬件下电序列:为什么不能“一刀切”
很多人觉得,下电嘛,把电源关了不就行了?你想想看,SoC内部有那么多电压域,CPU核、GPU、DDR、IO、PLL……每个模块对电压下降的速率和顺序都有要求。
硬件下电序列,说白了就是按照一个严格的时间窗口,把各路电源依次关掉。MT8676的PMIC(比如MT6365)内部有硬件状态机,专门干这个事。
典型的下电顺序(以MT8676为例):
- VDD_GPU → VDD_CORE → VDD_MODEM(先关大功耗域)
- VDD_IO_1.8V → VDD_IO_3.3V(再关IO域)
- VDD_DDR → VDD_PLL(最后关DDR和PLL)
- VDD_BAT(最后切断电池通路)
这里有个坑。DDR的电源不能掉得太快,否则DDR里残留的数据会瞬间丢失,甚至导致DDR控制器进入未知状态。我建议在硬件设计时,给DDR电源加一个放电电阻,让它的下降斜率控制在100mV/ms以内。
警告: 千万不要在DDR还在工作时直接拉低VDD_DDR。我曾经见过一个方案,为了省成本没加放电电阻,结果DDR掉电太快,下次上电时DDR初始化失败,系统直接黑屏。
4.2 软件控制下电:从“通知”到“断电”的完整流程
硬件序列是骨架,软件控制才是灵魂。MT8676的软件下电,我习惯把它分成三个阶段:
- 阶段一:通知阶段——告诉所有驱动“要关机了,赶紧收拾东西”
- 阶段二:准备阶段——保存上下文、刷脏数据、关外设
- 阶段三:执行阶段——触发PMIC的硬件下电序列
在MTK平台上,这个流程是通过 mtk_power_off_sequence() 这个函数来驱动的。咱们看看核心代码逻辑:
// 伪代码:MT8676 软件下电主流程
void mtk_power_off_sequence(void)
{
// 1. 通知所有注册的驱动
notifier_call_chain(&power_off_chain, POWER_OFF_PHASE1, NULL);
// 2. 等待所有驱动确认(超时机制)
wait_for_completion_timeout(&power_off_done, msecs_to_jiffies(500));
// 3. 关闭所有非必要外设时钟
mtk_clk_disable_unused();
// 4. 保存DDR控制器上下文到内部SRAM
ddr_save_context();
// 5. 触发PMIC硬件下电
pmic_write_reg(PMIC_PWR_OFF_CMD, 0x1);
}
这里有个细节。第2步的等待超时,我建议设成500ms。太短了,有些驱动来不及响应;太长了,用户长按关机键会感觉卡顿。我在项目中遇到过,某个外设驱动在关机回调里做了个耗时的flash擦除操作,导致超时。后来我强制要求所有驱动的关机回调必须在100ms内完成。
小技巧: 在调试阶段,可以在 power_off_chain 里打印每个驱动的执行时间。用 ktime_get() 打点,很容易找出哪个驱动在拖后腿。
4.3 安全下电策略:别让系统“死”得不明不白
安全下电,说白了就是处理异常情况。比如:
- 电池电量突然掉到0%
- SoC过热触发硬件保护
- 看门狗超时导致系统复位
- 用户强制长按电源键10秒
这些场景下,软件可能来不及走完正常的关机流程。那怎么办?
我的策略是分级处理:
| 优先级 | 触发条件 | 处理方式 | 数据安全 |
|---|---|---|---|
| P0 | 硬件过热、电池过放 | PMIC直接切断电源,不经过软件 | 无保障,仅保护硬件 |
| P1 | 看门狗复位 | 硬件复位,DDR内容保留(如果支持自刷新) | 部分保障 |
| P2 | 用户长按关机 | 软件走快速关机流程(跳过非必要步骤) | 尽量保障 |
| P3 | 系统正常关机 | 完整软件下电流程 | 完全保障 |
对于P0场景,硬件设计上要保证PMIC的OVP(过压保护)和UVP(欠压保护)阈值设置正确。我建议在PMIC的寄存器里,把 BAT_UVLO_THRESHOLD 设为3.0V,低于这个值直接断电,别犹豫。
对于P1场景,DDR自刷新是关键。MT8676的DDR控制器支持 self-refresh 模式,在复位信号到来之前,软件要确保DDR进入自刷新。我在代码里加了一个钩子:
// 看门狗复位前的最后一道防线
void watchdog_panic_handler(void)
{
// 强制DDR进入自刷新
ddr_enter_self_refresh();
// 等待50us确保DDR稳定
udelay(50);
// 然后硬件复位才会发生
}
注意: 这个处理器的执行时间只有几百微秒。如果DDR自刷新没来得及进入,复位后DDR里的数据就全丢了。我曾经在量产前发现这个问题,最后在硬件上加了复位延迟电路,给软件多争取了200us的时间。
4.4 实战中的“坑”与“填坑”
最后,分享几个我在MT8676项目中踩过的坑:
- 坑一:PMIC的Power Good信号没处理好。 下电时,如果某个电源域掉电太快,PMIC的PG信号会先于其他电源域拉低,导致SoC误判。解决方案是在PMIC的PG输出上加RC滤波,延迟50us。
- 坑二:GPIO状态在下电时乱跳。 有些GPIO在SoC掉电后,会变成高阻态,导致外部电路误动作。我建议在关机前,把所有关键GPIO强制拉到已知状态(比如拉低)。
- 坑三:eMMC在掉电时还在写数据。 这个最致命。我后来在文件系统层加了
shutdown回调,确保所有脏数据在关机前刷入eMMC,并且等待eMMC进入idle状态后再断电。
嗯,下电时序这块,说白了就是“优雅地死去”。硬件给你画好了跑道,软件要确保每个模块都能安全着陆。下一章咱们聊聊休眠唤醒中的“唤醒源管理”,那个更刺激——你得知道是谁把系统叫醒的,以及怎么处理假唤醒。