4、Power Domain框架:电源域驱动模型、设备树配置与层级关系
电源域(Power Domain)这个概念,说白了就是把芯片上的一组硬件模块打包管理。它们共享同一个电源开关,要么一起上电,要么一起断电。我在做第一款SoC的电源管理时,就被这个“一起”坑过——某个外设明明不需要电,却因为和别的模块绑在同一个域里,白白浪费功耗。
嗯,今天我们就来聊聊Power Domain框架。我会从驱动模型讲起,再到设备树怎么配,最后说说层级关系怎么搭。
4.1 Power Domain驱动模型
Linux内核里的Power Domain框架,核心是struct generic_pm_domain。这个结构体描述了一个电源域的所有行为。我习惯把它理解成“电源域的身份证”。
struct generic_pm_domain {
struct device dev;
struct list_head master_links;
struct list_head slave_links;
struct list_head dev_list;
struct mutex lock;
int (*power_off)(struct generic_pm_domain *domain);
int (*power_on)(struct generic_pm_domain *domain);
unsigned int state_count;
unsigned int state_idx;
...
};
关键回调就两个:power_on和power_off。你想想看,硬件上电不就是拉高某个GPIO或者写寄存器吗?但实际项目中,我遇到过需要先给父域上电,才能给子域上电的情况。这个顺序,驱动模型是管不了的,得你自己在回调里处理。
核心要点:Power Domain驱动模型的核心是“引用计数”。每个设备被使用时,会调用pm_runtime_get,对应的电源域引用计数+1。当所有设备都释放了,引用计数归零,内核自动调用power_off。
我曾经踩过一个坑:某个外设的驱动在probe时调了pm_runtime_get,但remove时忘了调pm_runtime_put。结果设备拔掉后,电源域一直关不掉,整块芯片的漏电流多了几十毫安。嗯,这种问题排查起来特别头疼。
4.2 设备树中Power Domain节点配置
设备树里配电源域,其实就两件事:定义域、绑定设备。
先看怎么定义一个电源域。以TI的AM335x为例:
/* 定义一个电源域 */
power_domain: power-domain@0x48307000 {
compatible = "ti,am335x-power-domain";
reg = <0x48307000 0x100>;
#power-domain-cells = <0>;
ti,domain-id = <0>;
};
这里#power-domain-cells很关键。它表示引用这个域时需要几个参数。如果是0,那引用时直接写&power_domain就行。如果配成1,那引用时就得带个参数,比如指定某个子域。
再看设备怎么绑定到电源域:
&uart0 {
status = "okay";
power-domains = <&power_domain>;
};
就这么简单。但实际项目中,我见过有人把power-domains写成了power-domain(少了个s)。内核不会报错,只是设备永远没法被正确管理电源。这种拼写错误,排查起来真的很费时间。
个人习惯:我每次写完设备树,都会用dtc -I dtb -O dts反编译一下,确认power-domains属性确实被解析进去了。这一步能避免很多低级错误。
4.3 电源域层级关系
电源域不是孤立的。它们可以嵌套,形成树形结构。父域没电,子域肯定也没电。反过来,子域要上电,父域必须先上电。
设备树里怎么表达这种关系?用power-domains属性引用父域:
parent_domain: power-domain@0x48308000 {
compatible = "ti,am335x-power-domain";
reg = <0x48308000 0x100>;
#power-domain-cells = <0>;
ti,domain-id = <1>;
};
child_domain: power-domain@0x48309000 {
compatible = "ti,am335x-power-domain";
reg = <0x48309000 0x100>;
#power-domain-cells = <0>;
power-domains = <&parent_domain>;
ti,domain-id = <2>;
};
你看,child_domain自己也是一个电源域,但它同时引用了parent_domain作为父域。这样内核在管理child_domain时,会自动先处理父域的上电/断电。
我画了一张图,帮你理解这个层级关系:
从图里你能看到:Root Domain是永远不关的。Parent Domain可以独立开关。Child Domain 1和2都依赖Parent Domain——父域断电,它们也跟着掉电。Sibling Domain和Parent Domain平级,互不影响。
注意:层级关系不能有环。我曾经见过一个设计,Domain A依赖Domain B,Domain B又依赖Domain A。内核在初始化时直接死锁了。这种问题在硬件设计阶段就要避免。
4.4 实际项目中的经验
我做过一个车载信息娱乐系统,芯片上有十几个电源域。当时遇到一个典型问题:摄像头模块在休眠后无法唤醒。排查了很久,发现是摄像头所在的电源域,它的父域在休眠时被关掉了。但父域还挂着别的设备(比如CAN控制器),那些设备不需要唤醒,所以父域就一直没被重新上电。
解决方案其实不复杂:给摄像头域加一个wakeup-source属性,并在父域的power_on回调里检查是否有子域需要唤醒。嗯,这种跨域依赖的问题,设备树配得再漂亮,也得驱动层配合好。
最后分享一个调试技巧:内核启动时加pm_debug_messages参数,能看到每个电源域的上电/断电日志。我习惯用这个来验证设备树配置是否正确。
避坑指南:我曾经在设备树里把power-domains配成了循环引用。内核启动时直接panic。后来我写了个脚本,在编译设备树前自动检查依赖关系是否有环。这个脚本现在还在用,推荐你也搞一个。
好了,Power Domain框架的核心内容就这些。驱动模型、设备树配置、层级关系,这三块搞明白了,大部分电源域问题都能应对。
公众号:蓝海资料掘金营,微信deep3321