
嵌入式设备的低功耗需求很少在原型阶段暴露。开发板上插着调试器、串口和网线,功耗数字本来就没什么参考意义。等到产品定义要求“待机 30 天”或“电池供电 8 小时”时,团队才会发现 echo mem > /sys/power/state 能让系统停下来,但停下来之后的电流远高于预期,而且唤醒之后某些外设再也回不到工作状态。suspend-to-RAM 的工程难点不在于“能不能进入休眠”,而在于驱动回调的对称性、电源管理芯片的协同,以及唤醒源的精确配置。RK3588 平台上一组 suspend 补丁把模块待机功耗从 1.09 W 降到 0.43 W,降幅超过一半,靠的不是某个单一改动,而是三件事同时做对。
Linux 设备模型的电源管理回调有严格的执行顺序:suspend 阶段从 prepare 到 suspend 再到 suspend_late 和 suspend_noirq,resume 阶段完全逆序。很多驱动只在 suspend 里做了时钟关闭或寄存器保存,却在 resume 里遗漏了对应的恢复动作,或者恢复顺序与硬件要求相反。系统能进入 suspend,也能被唤醒,但醒来后 I2C 总线没有时钟、SPI 控制器没有重新初始化,外设表现为“装死”。
一个正确的驱动 PM ops 必须保证 suspend 和 resume 严格镜像:
static int mydev_suspend(struct device *dev)
{
struct mydev *priv = dev_get_drvdata(dev);
disable_irq(priv->irq);
clk_disable_unprepare(priv->clk);
regulator_disable(priv->vdd);
return 0;
}
static int mydev_resume(struct device *dev)
{
struct mydev *priv = dev_get_drvdata(dev);
int ret;
ret = regulator_enable(priv->vdd);
if (ret)
return ret;
ret = clk_prepare_enable(priv->clk);
if (ret) {
regulator_disable(priv->vdd);
return ret;
}
enable_irq(priv->irq);
return 0;
}
static const struct dev_pm_ops mydev_pm_ops = {
.suspend = mydev_suspend,
.resume = mydev_resume,
};这里的关键不是代码本身,而是顺序必须与硬件数据手册一致。如果芯片要求“先上电、再给时钟、最后释放复位”,resume 就不能反过来。clk_disable_unprepare 和 clk_prepare_enable 的配对使用也是硬性要求:只调用 clk_disable 而不 unprepare,时钟框架的引用计数会失衡,后续其他驱动再想使能同一路时钟时会拿到一个已经失效的句柄。RK3588 的 suspend 补丁中,Patch 3 专门为 SPI 变体补上 system sleep PM ops,正是因为此前 RK8xx 的 SPI 路径缺少这组回调,导致 PMIC 在 suspend 期间无法正确进入低功耗状态。
系统 suspend 的功耗大头往往不在 SoC 本身,而在电源管理芯片是否被同步置于低功耗模式。RK3588 搭配 RK806 PMIC 时,SoC 的 pmic_sleep1 引脚连接到 PMIC 的 PWRCTRL1,固件在 SoC 进入 suspend 时拉高该信号,触发 PMIC 的 sleep 状态。但如果设备树没有把 PWRCTRL1 配置为 sleep 功能,这个信号就不会产生任何效果,各路 regulator 继续以正常工作模式供电,待机电流居高不下。
RK3588 Tiger 的补丁中,Patch 4 做的事情就是在设备树里把 PMIC 的 PWRCTRL1 引脚显式配置为 sleep 功能,让 regulator 的 suspend 状态真正生效。实测下来,仅这一项配置就使模块的 suspend-to-RAM 功耗从 1.09 W 降至 0.43 W。设备树中的关键配置通常位于 PMIC 节点下,形式如下:
&rk806 {
pinctrl-names = "default";
pinctrl-0 = <&rk806_pins>;
regulators {
/* 各路 regulator 的 regulator-state-mem 定义 suspend 时的电压/模式 */
vcc_3v3_s0: DCDC_REG4 {
regulator-state-mem {
regulator-off-in-suspend;
};
};
};
};
&pinctrl {
rk806 {
rk806_pins: rk806-pins {
rockchip,pins =
/* PWRCTRL1 引脚功能配置为 sleep 控制 */
<1 RK_PB4 1 &pcfg_pull_none>;
};
};
};regulator-off-in-suspend 告诉 regulator 框架:系统 suspend 时这一路电可以关闭。如果设备树里漏掉这个属性,框架默认保持 regulator 开启,PMIC sleep 信号即使正确触发,功耗也降不下来。PMIC 的 sleep 配置和 regulator 的 suspend 状态是两件独立但必须同时正确的事情,缺一不可。
suspend 的调试中有一个经典场景:echo mem > /sys/power/state 执行成功,串口输出显示 PM: suspend entry,然后设备彻底安静了——按电源键没有任何反应,只能拔电重启。原因通常是唤醒源没有在 suspend 之前被使能,或者使能的时机不对。
对于 GPIO 按键唤醒,驱动需要在 suspend 回调中调用 enable_irq_wake;对于 PMIC 电源键,RK3588 的 Patch 1 做的事情是在常规 suspend 阶段将电源键中断武装为唤醒源,因为 SPI 总线在 noirq 阶段已经不可用,不能等到 suspend_noirq 才去操作 PMIC。一个典型的按键唤醒配置如下:
static int pwrkey_suspend(struct device *dev)
{
struct pwrkey *key = dev_get_drvdata(dev);
if (device_may_wakeup(dev))
enable_irq_wake(key->irq);
return 0;
}
static int pwrkey_resume(struct device *dev)
{
struct pwrkey *key = dev_get_drvdata(dev);
if (device_may_wakeup(dev))
disable_irq_wake(key->irq);
return 0;
}device_may_wakeup(dev) 的结果取决于设备树中是否设置了 wakeup-source 属性,或者用户空间是否通过 /sys/devices/.../power/wakeup 使能了唤醒能力。调试阶段建议先检查 /sys/kernel/debug/suspend_stats 中的 failed_wake 和 last_failed_dev,它能直接告诉你哪个设备在 suspend 过程中失败,以及唤醒是否被正确记录。RK3588 的补丁系列在测试中完成了 30 次连续的 suspend-to-RAM 循环,每次都由 PMIC 电源键唤醒,内核日志无报错,USB 设备在 suspend 前后保持连接——这种可重复的唤醒验证本身就是 suspend 工程化的一部分,而不是“能唤醒一次就算通过”。
suspend 功耗偏高时,最忌讳直接去改驱动。正确的顺序是先测量各路电源轨在 suspend 下的电流,确认哪一路没有按预期掉电。很多平台的 PMIC 或外部电流监测芯片通过 IIO 子系统暴露每路电流,读 /sys/bus/iio/devices/iio:device0/in_current*_input 即可获得原始值。如果某一路在 suspend 下的电流与 active 状态相差无几,就回到设备树检查对应的 regulator-state-mem 配置,而不是去怀疑 SoC 的 suspend 深度。
在此基础上,/sys/power/pm_debug_messages 和 pm_test 可以逐级验证 suspend 的每个阶段:先测 freezer,再测 devices,最后测 platform。如果 devices 阶段就失败,问题一定在某个驱动的 suspend 回调里,dmesg 会给出具体的设备名。把功耗测量和阶段测试结合起来,才能在改动驱动之前把问题收敛到最小的范围。
suspend-to-RAM 在嵌入式 Linux 上不是一个“打开一个内核选项”的功能,而是一条横跨设备驱动、设备树、PMIC 固件和唤醒源管理的链路。驱动回调必须镜像对称,否则唤醒后外设不可用;PMIC 的 sleep 信号和 regulator 的 suspend 状态必须同时配置,否则 SoC 睡了电源树还在耗电;唤醒源必须在正确的 suspend 阶段武装,否则设备进入无法唤醒的死状态。RK3588 上从 1.09 W 到 0.43 W 的差距说明,低功耗的工程回报往往藏在设备树和 PMIC 协同配置这些“看起来不像代码”的地方,而不是在业务逻辑里。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。