4、下电时序控制:硬件下电序列、软件控制下电、安全下电策略

各位同学,咱们接着聊。上电时序搞定了,下电时序其实更让人头疼。为什么?因为上电出问题,顶多系统起不来,你还能debug。下电出问题,轻则数据丢失,重则把PMIC或者SoC烧了。我在MT8676的项目里,就亲眼见过一块板子因为下电时序乱掉,直接把eMMC给搞挂了。嗯,今天咱们就把下电这件事彻底讲透。

4.1 硬件下电序列:为什么不能“一刀切”

很多人觉得,下电嘛,把电源关了不就行了?你想想看,SoC内部有那么多电压域,CPU核、GPU、DDR、IO、PLL……每个模块对电压下降的速率和顺序都有要求。

硬件下电序列,说白了就是按照一个严格的时间窗口,把各路电源依次关掉。MT8676的PMIC(比如MT6365)内部有硬件状态机,专门干这个事。

典型的下电顺序(以MT8676为例):

  1. VDD_GPU → VDD_CORE → VDD_MODEM(先关大功耗域)
  2. VDD_IO_1.8V → VDD_IO_3.3V(再关IO域)
  3. VDD_DDR → VDD_PLL(最后关DDR和PLL)
  4. 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状态后再断电。

嗯,下电时序这块,说白了就是“优雅地死去”。硬件给你画好了跑道,软件要确保每个模块都能安全着陆。下一章咱们聊聊休眠唤醒中的“唤醒源管理”,那个更刺激——你得知道是谁把系统叫醒的,以及怎么处理假唤醒。