3. Regulator框架:Regulator驱动模型、设备树中Regulator节点配置、电压调节器绑定
电源管理里,Regulator是个绕不开的话题。说白了,它就是负责给各个外设提供稳定电压的“管家”。CPU要1.1V,DDR要1.35V,外设要3.3V——谁给?Regulator来给。
我刚开始接触嵌入式Linux时,总觉得Regulator就是个简单的电压输出。后来踩了几个坑才明白,它背后有一套完整的驱动模型和设备树绑定规则。搞不懂这些,板子跑起来不是电压不对就是外设不工作。
3.1 Regulator驱动模型
Linux内核里的Regulator框架,分三层:
- 核心层:提供注册、注销、电压查询、电压设置等通用API
- 物理设备驱动层:对应具体的PMIC或DC-DC芯片驱动
- 消费者接口层:给其他驱动(如MMC、USB、GPU)调用
我习惯把Regulator驱动模型想象成“供电公司”的运作方式:
- 核心层 = 电力局,制定规则和接口
- 物理驱动 = 发电厂,知道怎么调电压
- 消费者 = 你家电器,只管插电用
这种分层设计的好处很明显——外设驱动不需要知道电压是怎么产生的,它只需要说“我要3.3V”,Regulator框架就会去协调。
关键数据结构:
struct regulator_dev:代表一个物理调节器struct regulator:消费者拿到的句柄struct regulator_ops:操作函数集,比如set_voltage、get_voltage
3.2 设备树中Regulator节点配置
设备树里配Regulator,其实就是在描述“哪个引脚控制哪个电压输出”。我见过不少工程师直接把PMIC的参考设计搬过来,结果板子一上电就烧外设——就是因为设备树里的电压范围没配对。
一个典型的Regulator节点长这样:
regulator_3v3: regulator-3v3 {
compatible = "regulator-fixed";
regulator-name = "3.3V";
regulator-min-microvolt = <3300000>;
regulator-max-microvolt = <3300000>;
regulator-always-on;
gpio = <&gpio1 10 GPIO_ACTIVE_HIGH>;
enable-active-high;
};
这里有几个属性我特别想强调:
regulator-min-microvolt和regulator-max-microvolt:定义了可调范围。固定输出的话,两个值设成一样就行regulator-always-on:表示这个调节器永远不关。我建议只在关键电源轨上用,比如DDR供电gpio:控制使能引脚的GPIO。注意极性要和enable-active-high匹配
注意:我曾经遇到过一个问题——设备树里配了regulator-always-on,但GPIO对应的引脚被其他外设复用了。结果系统启动时,这个调节器永远无法使能,导致整个系统卡在电源初始化阶段。排查了整整两天才找到原因。
对于可调电压的Regulator(比如PMIC内部的BUCK),配置会更复杂一些:
vdd_core: regulator-vdd-core {
compatible = "regulator-fixed"; /* 实际可能是pmic-specific */
regulator-name = "VDD_CORE";
regulator-min-microvolt = <900000>;
regulator-max-microvolt = <1300000>;
regulator-boot-on;
regulator-ramp-delay = <5000>; /* 5ms */
regulator-coupled-with = <&vdd_mem>;
regulator-coupled-max-spread = <100000>;
};
嗯,这里要注意regulator-ramp-delay。它表示电压变化时的爬坡时间。设得太短,可能引起过冲;设得太长,CPU频率切换时会卡顿。我一般根据PMIC数据手册来设,保守一点取中间值。
3.3 电压调节器绑定
绑定,就是把设备树里的Regulator节点和驱动里的消费者关联起来。说白了,就是告诉内核:“这个外设要用那个调节器”。
绑定的方式有两种:
- 通过phandle直接引用:在消费者节点里用
vmmc-supply、vqmmc-supply等属性 - 通过regulator别名:在驱动里用
regulator_get(dev, "vmmc")获取
举个例子,MMC控制器需要两个电源:
&mmc0 {
vmmc-supply = <®ulator_3v3>; /* 主电源 */
vqmmc-supply = <®ulator_1v8>; /* IO电源 */
status = "okay";
};
然后在MMC驱动里:
struct regulator *vmmc;
vmmc = devm_regulator_get(dev, "vmmc");
if (IS_ERR(vmmc)) {
dev_err(dev, "failed to get vmmc regulator\n");
return PTR_ERR(vmmc);
}
regulator_enable(vmmc);
你想想看,如果没有这套绑定机制,驱动就得硬编码GPIO或者I2C地址去控制电压。一旦换了PMIC,所有驱动都得改。有了设备树绑定,换PMIC只需要改设备树,驱动完全不用动。
小技巧:我习惯在调试阶段,在/sys/class/regulator/目录下查看所有注册的调节器。每个调节器都有microvolts、state等文件,可以直接cat查看当前状态。这比翻代码快多了。
3.4 核心逻辑框架图
下面这张图,是我自己总结的Regulator框架核心逻辑。它把驱动模型、设备树配置和消费者绑定串在了一起:
从这张图可以看得很清楚:设备树负责描述“有什么电源”,驱动模型负责“怎么控制电源”,绑定负责“谁用哪个电源”。三层各司其职,缺一不可。
3.5 避坑指南
最后分享几个我实际踩过的坑:
- 电压范围不匹配:消费者请求的电压超出了Regulator的范围,内核会返回-EINVAL。我建议在设备树里把范围设宽一点,然后在驱动里做校验
- 使能顺序问题:有些外设需要先上IO电源再上核心电源。如果顺序反了,芯片可能会锁死。可以用
regulator-coupled-with来指定耦合关系 - 漏掉regulator-boot-on:如果Bootloader已经打开了某个电源,但内核不知道,可能会在驱动初始化时把它关掉。加上
regulator-boot-on就能避免
调试小技巧:在/sys/kernel/debug/regulator/目录下,可以看到每个调节器的详细状态,包括当前电压、使能计数、消费者列表。这是排查电源问题的第一站。
Regulator框架说复杂也复杂,说简单也简单。核心就是记住:设备树描述硬件,驱动模型提供能力,绑定建立连接。搞懂这三者的关系,电源管理这块就通了八成。
公众号:蓝海资料掘金营,微信deep3321