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_voltageget_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-microvoltregulator-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节点和驱动里的消费者关联起来。说白了,就是告诉内核:“这个外设要用那个调节器”。

绑定的方式有两种:

  1. 通过phandle直接引用:在消费者节点里用vmmc-supplyvqmmc-supply等属性
  2. 通过regulator别名:在驱动里用regulator_get(dev, "vmmc")获取

举个例子,MMC控制器需要两个电源:

&mmc0 {
    vmmc-supply = <&regulator_3v3>;   /* 主电源 */
    vqmmc-supply = <&regulator_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/目录下查看所有注册的调节器。每个调节器都有microvoltsstate等文件,可以直接cat查看当前状态。这比翻代码快多了。

3.4 核心逻辑框架图

下面这张图,是我自己总结的Regulator框架核心逻辑。它把驱动模型、设备树配置和消费者绑定串在了一起:

Regulator框架核心逻辑 设备树配置层 regulator-fixed / pmic-specific 节点 regulator-min-microvolt / regulator-max-microvolt / gpio / regulator-always-on Regulator驱动模型 核心层 regulator_register/unregister 物理设备驱动层 regulator_ops (set/get_voltage) 消费者接口层 regulator_get/enable/disable 电压调节器绑定 vmmc-supply / vqmmc-supply / 其他-supply devm_regulator_get(dev, "vmmc") → regulator_enable() 消费者驱动(MMC / USB / GPU / ...)

从这张图可以看得很清楚:设备树负责描述“有什么电源”,驱动模型负责“怎么控制电源”,绑定负责“谁用哪个电源”。三层各司其职,缺一不可。

3.5 避坑指南

最后分享几个我实际踩过的坑:

  • 电压范围不匹配:消费者请求的电压超出了Regulator的范围,内核会返回-EINVAL。我建议在设备树里把范围设宽一点,然后在驱动里做校验
  • 使能顺序问题:有些外设需要先上IO电源再上核心电源。如果顺序反了,芯片可能会锁死。可以用regulator-coupled-with来指定耦合关系
  • 漏掉regulator-boot-on:如果Bootloader已经打开了某个电源,但内核不知道,可能会在驱动初始化时把它关掉。加上regulator-boot-on就能避免

调试小技巧:在/sys/kernel/debug/regulator/目录下,可以看到每个调节器的详细状态,包括当前电压、使能计数、消费者列表。这是排查电源问题的第一站。

Regulator框架说复杂也复杂,说简单也简单。核心就是记住:设备树描述硬件,驱动模型提供能力,绑定建立连接。搞懂这三者的关系,电源管理这块就通了八成。


公众号:蓝海资料掘金营,微信deep3321