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_onpower_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 Power Domain always-on Parent Domain 可独立开关 Child Domain 1 依赖父域 Child Domain 2 依赖父域 Sibling 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