4、设备树绑定:PMU设备树节点定义、regulator节点、power-domain节点、clock节点
设备树绑定,说白了就是告诉内核:你的PMU长什么样、接在哪个总线上、控制哪些电源轨。我刚开始接触PMU驱动时,总觉得设备树就是写几个属性嘛,没什么技术含量。直到有一次,一个regulator节点少了个regulator-always-on,系统休眠后就再也唤不醒了……嗯,从那以后我再也不敢小看设备树绑定了。
4.1 PMU设备树节点定义
一个典型的PMU节点,通常挂载在I2C或SPI总线下。我个人习惯把PMU节点放在i2c或spi总线的子节点中,这样内核可以自动完成设备枚举。
// 一个典型的PMU设备树节点
&i2c3 {
status = "okay";
clock-frequency = <400000>;
pmic: pmic@3c {
compatible = "myvendor,pmu-axp313";
reg = <0x3c>;
interrupt-parent = <&gpio1>;
interrupts = <20 IRQ_TYPE_LEVEL_LOW>;
interrupt-controller;
#interrupt-cells = <1>;
// 内部regulator子节点
regulators {
// ... 见下文
};
// 电源域子节点
power-domains {
// ... 见下文
};
};
};
这里有几个关键点:
- compatible:驱动匹配的关键字符串,必须与驱动中
of_device_id表一致。我见过有人把大小写写错,结果驱动死活加载不上。 - reg:I2C设备地址或SPI片选号。注意不要和总线上其他设备冲突。
- interrupts:PMU的中断引脚。PMU通常通过中断通知系统:电压异常、温度过高、电池电量低等。
- interrupt-controller 和 #interrupt-cells:如果PMU内部有多个中断源(比如每个regulator都有自己的过流保护中断),就需要声明这个PMU本身是一个中断控制器。
注意: 我曾经遇到一个坑——PMU的I2C地址是0x3c,但另一个设备也用了0x3c。结果两个设备都工作异常。所以一定要确认地址唯一性,尤其是多路复用器后面的设备。
4.2 regulator节点
regulator节点是PMU设备树中最核心的部分。每个regulator代表一个电压输出通道,比如DCDC1、LDO2等。我建议把所有的regulator放在一个regulators子节点下,这样结构清晰。
regulators {
// DCDC1: 核心供电,1.1V
dcdc1: dcdc1 {
regulator-name = "vdd_core";
regulator-min-microvolt = <1000000>;
regulator-max-microvolt = <1300000>;
regulator-always-on;
regulator-boot-on;
regulator-ramp-delay = <2500>; // 单位: uV/us
};
// LDO2: IO口供电,3.3V
ldo2: ldo2 {
regulator-name = "vdd_io";
regulator-min-microvolt = <3300000>;
regulator-max-microvolt = <3300000>;
regulator-always-on;
};
// LDO3: 摄像头供电,可关断
ldo3: ldo3 {
regulator-name = "vdd_cam";
regulator-min-microvolt = <1800000>;
regulator-max-microvolt = <2800000>;
regulator-state-mem {
regulator-off-in-suspend;
};
};
};
这里我重点说几个容易被忽略的属性:
- regulator-always-on:表示这个regulator永远不能关。比如DDR供电、核心供电,一旦关掉系统就挂了。我调试过一个板子,DDR供电的regulator没加这个属性,结果系统休眠后DDR掉电,醒来时内存数据全丢了。
- regulator-boot-on:表示bootloader已经打开了这个regulator,内核不需要再操作。这可以避免启动时电压跳变。
- regulator-ramp-delay:电压爬升速率。如果设置得太快,可能会引起过冲;太慢,系统启动时间变长。我一般参考PMU数据手册的典型值。
- regulator-state-mem:系统进入suspend-to-mem时的状态。可以指定关断或保持电压。这个在低功耗设计中非常关键。
小技巧: 如果你不确定某个regulator是否需要always-on,可以先用
regulator-boot-on,然后在内核启动后通过/sys/class/regulator/查看状态。如果发现某个regulator被意外关掉了,就加上always-on。
4.3 power-domain节点
电源域(power domain)是PMU的高级功能。一个电源域可以包含多个regulator,并且支持顺序上下电。我记得在做一个AI芯片项目时,NPU需要先上核心电压,再上IO电压,关断时顺序相反。如果没有电源域,就得在驱动里手动控制,非常麻烦。
power-domains {
pd_npu: power-domain@0 {
#power-domain-cells = <0>;
domain-id = <0>;
// 上电顺序: 先dcdc1, 再ldo2
power-on-sequence = <&dcdc1 0>, <&ldo2 10000>;
// 下电顺序: 先ldo2, 再dcdc1
power-off-sequence = <&ldo2 0>, <&dcdc1 10000>;
// 电源域所包含的regulator
regulators = <&dcdc1>, <&ldo2>;
};
pd_gpu: power-domain@1 {
#power-domain-cells = <0>;
domain-id = <1>;
power-on-sequence = <&dcdc2 0>, <&ldo4 5000>;
power-off-sequence = <&ldo4 0>, <&dcdc2 5000>;
regulators = <&dcdc2>, <&ldo4>;
};
};
这里的关键属性:
- #power-domain-cells:通常为0,表示这个电源域不需要额外的参数。
- domain-id:PMU内部的电源域编号,用于寄存器操作。
- power-on-sequence 和 power-off-sequence:每个条目是一个phandle加延时(微秒)。比如
<&dcdc1 0>表示先打开dcdc1,延时0微秒;<&ldo2 10000>表示再打开ldo2,延时10毫秒。
注意: 顺序上下电的延时不能太短。我曾经把延时设成100微秒,结果发现ldo2还没稳定,dcdc1已经开始工作了,导致NPU启动时电压毛刺。后来参考数据手册,把延时改成了5毫秒,问题解决。
4.4 clock节点
PMU通常也提供时钟输出,比如32KHz的RTC时钟、24MHz的晶振缓冲输出等。clock节点用于描述这些时钟源。
clocks {
// 32KHz RTC时钟
rtc32k: rtc32k {
compatible = "fixed-clock";
#clock-cells = <0>;
clock-frequency = <32768>;
clock-output-names = "pmu_rtc32k";
};
// 24MHz主时钟输出
osc24m: osc24m {
compatible = "fixed-clock";
#clock-cells = <0>;
clock-frequency = <24000000>;
clock-output-names = "pmu_osc24m";
};
// 可编程时钟输出
pll_out: pll_out {
compatible = "myvendor,pmu-pll-clock";
#clock-cells = <1>;
clocks = <&osc24m>;
clock-output-names = "pmu_pll_out";
// 支持的频率列表
clock-mult = <1 2 4 8>;
};
};
clock节点的几个要点:
- fixed-clock:对于固定频率的时钟(如32KHz),直接用这个compatible,内核会自动注册一个固定频率时钟。
- #clock-cells:如果为0,表示这个时钟只有一个输出;如果为1,表示有多个输出,需要通过索引选择。
- clock-output-names:给时钟起个名字,方便其他设备引用。比如RTC驱动可以通过
clocks = <&rtc32k>;来获取这个时钟。
小技巧: 如果你需要调试时钟是否正常工作,可以在内核启动后查看
/sys/kernel/debug/clk/pmu_rtc32k/clk_rate。如果频率不对,多半是PMU的时钟配置寄存器没初始化。
4.5 设备树绑定的避坑指南
最后,我总结几个实际项目中容易踩的坑:
- regulator名称冲突:不同PMU的regulator名称不能重复。比如两个PMU都有
dcdc1,内核会报错。我建议加上PMU前缀,比如pmu1_dcdc1。 - 中断号错误:PMU的中断号一定要和硬件连接一致。我曾经把GPIO中断号写错了一位,结果PMU的中断一直触发不了,系统无法检测到电池低电量。
- 电源域循环依赖:如果电源域A依赖电源域B,电源域B又依赖电源域A,内核会死锁。设计时一定要保证电源域是树形结构,不能有环。
- 时钟频率不匹配:PMU输出的时钟频率必须和下游设备匹配。比如以太网PHY需要25MHz,你给了24MHz,网络就起不来。
嗯,设备树绑定看起来就是写几个节点,但每个属性背后都有它的设计意图。我建议你在写设备树时,手里一定要有PMU的数据手册,对照着寄存器描述来写。这样写出来的设备树,才能让驱动稳定运行。