4、设备树中的电源管理:电源域、regulator、operating-points-v2、power-domains 属性详解

设备树,说白了就是一块“硬件描述板”。你告诉内核,我这颗芯片上哪个模块用多少电、谁给谁供电、电压怎么调。电源管理相关的属性,就是这块板子上最关键的几根“电线”。

我个人习惯把设备树里的电源描述分成三层:供电来源(regulator)、电压频率表(operating-points-v2)、电源域归属(power-domains)。这三层搞清楚了,移植电源管理框架就成功了一半。

4.1 regulator:谁在供电?

regulator 就是“稳压器”。它可以是芯片内部的 LDO,也可以是板子上的 PMIC。设备树里用 regulator-* 属性来描述它的行为。

举个例子,我做过一个项目,CPU 核心供电来自一个叫 vdd_core 的 regulator:

vdd_core: regulator-vdd-core {
    compatible = "regulator-fixed";
    regulator-name = "vdd_core";
    regulator-min-microvolt = <800000>;
    regulator-max-microvolt = <1200000>;
    regulator-always-on;
    regulator-boot-on;
};

这里要注意几个点:

  • regulator-min-microvoltregulator-max-microvolt 定义了可调范围。如果硬件只支持固定电压,这两个值设成一样就行。
  • regulator-always-on 表示这个 regulator 永远不掉电。我建议只在关键电源上用它,比如 DDR 供电。普通外设别乱加,否则系统永远进不了深度睡眠。
  • regulator-boot-on 表示 bootloader 阶段已经打开了,内核不需要再操作硬件。这个属性能省掉一次 I2C 通信,对启动时间敏感的项目很有用。
注意: 我曾经踩过一个坑——regulator 的节点名不能跟其他节点重名,否则内核会报 duplicate regulator name。建议用 regulator-xxx 的命名格式,一目了然。

4.2 operating-points-v2:电压频率表

operating-points-v2 描述的是“在某个频率下,需要多少电压”。这是 DVFS(动态电压频率调整)的核心数据。

我习惯把它理解成一张二维表:

cpu_opp_table: opp-table {
    compatible = "operating-points-v2";
    opp-shared;

    opp-200000000 {
        opp-hz = /bits/ 64 <200000000>;
        opp-microvolt = <900000>;
    };

    opp-400000000 {
        opp-hz = /bits/ 64 <400000000>;
        opp-microvolt = <1000000>;
    };

    opp-800000000 {
        opp-hz = /bits/ 64 <800000000>;
        opp-microvolt = <1100000>;
    };
};

这里有几个关键点:

  • opp-shared 表示这些 OPP 是共享的。比如双核 CPU,两个核共用一个电压域,那这个标志必须加。否则内核会以为每个核可以独立调压,结果就是电压打架。
  • opp-hz 的单位是 Hz,但用 /bits/ 64 强制 64 位。为什么?因为 32 位最大只能表示 4.29GHz,现在很多芯片的 turbo 频率已经超过这个值了。嗯,提前留好余量。
  • opp-microvolt 可以是一个值,也可以是三个值(最小、典型、最大)。我一般只填典型值,因为芯片出厂时已经做了 trim,偏差不大。
小技巧: 如果你在调试 DVFS,可以在 opp-xxx 节点里加一个 opp-suspend 属性,表示这个 OPP 是系统挂起时用的。这样内核在进入 suspend 时会自动切到这个频率,避免醒来时电压不够。

4.3 power-domains:谁管谁?

power-domains 描述的是“电源域”的层级关系。一个电源域可以控制一组模块的开关,比如 GPU 域、ISP 域、DDR 域。

设备树里用 power-domains 属性来绑定:

gpu: gpu@13000000 {
    compatible = "arm,mali-g72";
    reg = <0x13000000 0x10000>;
    power-domains = <&pd_gpu>;
    operating-points-v2 = <&gpu_opp_table>;
};

这里 &pd_gpu 指向一个电源域控制器节点:

pd_gpu: power-domain@10002000 {
    compatible = "mediatek,power-controller";
    reg = <0x10002000 0x1000>;
    #power-domain-cells = <0>;
    clocks = <&topckgen CLK_TOP_GPU>;
};

我个人觉得,power-domains 最难理解的是“嵌套”。比如一个 SoC 里,整个芯片有一个顶层电源域,下面分 CPU 域、GPU 域、多媒体域。多媒体域里又分 ISP 域、VPU 域。这种层级关系在设备树里用 power-domains 属性链起来:

pd_multimedia: power-domain@10003000 {
    compatible = "mediatek,power-controller";
    reg = <0x10003000 0x1000>;
    #power-domain-cells = <0>;
    power-domains = <&pd_top>;  // 父域是顶层电源域
};

pd_isp: power-domain@10004000 {
    compatible = "mediatek,power-controller";
    reg = <0x10004000 0x1000>;
    #power-domain-cells = <0>;
    power-domains = <&pd_multimedia>;  // 父域是多媒体域
};

为什么要嵌套?你想想看,如果 ISP 要上电,必须先保证多媒体域已经上电。内核在操作电源域时,会先递归打开父域,再打开子域。关电时顺序反过来。这个逻辑在 pm_domain 框架里已经实现了,你只需要在设备树里把关系描述清楚。

核心要点: power-domains 描述的是“依赖关系”,不是“供电关系”。供电关系由 regulator 描述。两者不要搞混。

4.4 三者如何配合?

我画了一张图,帮你理清这三者的关系:

设备树电源管理三层结构 第一层:regulator(供电来源) 描述:谁提供电压?电压范围是多少? 典型属性:regulator-min-microvolt, regulator-max-microvolt, regulator-always-on 示例:vdd_core, vdd_gpu, vdd_ddr 提供电压 第二层:operating-points-v2(电压频率表) 描述:在某个频率下,需要多少电压? 典型属性:opp-hz, opp-microvolt, opp-shared 示例:cpu_opp_table, gpu_opp_table 引用OPP表 第三层:power-domains(电源域归属) 描述:这个设备属于哪个电源域?父域是谁? 典型属性:power-domains, #power-domain-cells 示例:pd_gpu, pd_multimedia, pd_isp 三者配合:regulator 提供电压 → OPP 表定义电压频率组合 → power-domains 管理开关顺序

实际工作中,这三者是这样配合的:

  1. 内核在启动时,解析设备树,建立 regulator、OPP 表、power-domain 的拓扑结构。
  2. 当某个设备要改变频率时(比如 CPU 从 200MHz 升到 800MHz),内核先查 OPP 表,找到对应的电压值。
  3. 然后通过 regulator 接口设置电压。
  4. 最后通过 power-domain 接口确保该域已经上电。

嗯,这里要注意:如果 regulator 和 power-domain 之间有冲突(比如 regulator 要求先上电再调压,但 power-domain 要求先调压再上电),那就要在驱动里做时序控制。我遇到过这种问题,最后是在 struct dev_pm_opp 里加了一个 set_voltage_first 标志才搞定。

我的建议: 在写设备树时,先把 regulator 节点写好,再写 OPP 表,最后写 power-domains。这个顺序跟硬件上电流程一致,不容易出错。调试时,先看 /sys/kernel/debug/regulator/ 确认电压对不对,再看 /sys/kernel/debug/opp/ 确认 OPP 表有没有加载成功。

好了,这一章的内容就到这里。设备树里的电源管理属性,说白了就是告诉内核三件事:谁供电、供多少电、谁管谁。把这三点理清楚,移植电源管理框架就不会抓瞎。