3. regulator框架:电压调节器核心API、regulator设备驱动、动态电压频率调整(DVFS)基础
好,咱们今天来聊聊regulator框架。说实话,这个框架在Linux内核里属于那种「平时不太显眼,但一旦出问题就让你抓狂」的模块。我最早接触它是在做一款平板电脑的功耗优化时,发现CPU降频后电压没跟着降,板子直接热到烫手——嗯,从那以后我就把regulator框架好好啃了一遍。
3.1 为什么需要regulator框架?
你想想看,一个SoC上挂着多少路电源?CPU核心、GPU、DDR、PLL、IO……每一路都需要不同的电压。有的要1.1V,有的要1.8V,有的还要动态调整。如果让每个驱动自己去操作PMIC寄存器,那不乱套了?
regulator框架干的事,说白了就是:统一管理电压调节器。它提供了一套标准API,让消费者驱动(比如CPUfreq、GPU驱动)可以优雅地申请、设置、释放电压,而不需要关心底层是PMIC还是DC-DC转换器。
核心思想: regulator框架把电压调节器抽象为「 regulator 设备」,消费者通过句柄(struct regulator *)来操作,底层硬件差异由驱动开发者搞定。
3.2 核心数据结构
先看几个关键结构体,我习惯把它们记成「三件套」:
| 结构体 | 作用 | 谁实现 |
|---|---|---|
struct regulator_dev |
代表一个物理regulator设备 | 驱动开发者创建 |
struct regulator_ops |
操作函数集(设置电压、使能等) | 驱动开发者实现 |
struct regulator |
消费者看到的句柄 | 内核分配,消费者使用 |
这里有个容易混淆的点:regulator_dev 是给驱动用的,regulator 是给消费者用的。我见过有新手把这两个搞混,结果传参传错了,内核直接oops。
3.3 核心API:消费者视角
作为驱动开发者,你更多时候是消费者——比如你要写一个GPU驱动,需要动态调压。那你会用到这些API:
3.3.1 获取与释放
struct regulator *regulator_get(struct device *dev, const char *id);
void regulator_put(struct regulator *regulator);
注意这个 id 参数,它对应设备树里的 regulator-name 属性。我踩过坑:设备树里名字写的是 cpu-vdd,代码里传的是 cpu_vdd,下划线和中划线不匹配,结果 regulator_get 返回NULL。查了半天才发现是这种低级错误。
3.3.2 电压设置
int regulator_set_voltage(struct regulator *regulator, int min_uV, int max_uV);
int regulator_get_voltage(struct regulator *regulator);
这里有个设计哲学:你告诉内核一个「电压范围」,而不是一个精确值。为什么?因为regulator硬件可能只能以步进方式调整(比如12.5mV一步),内核会在范围内选一个最合适的值。我个人习惯把min和max设成一样,除非你明确允许一定波动。
3.3.3 使能与禁用
int regulator_enable(struct regulator *regulator);
int regulator_disable(struct regulator *regulator);
int regulator_is_enabled(struct regulator *regulator);
使能操作是有引用计数的。你调一次 enable,引用计数+1;调一次 disable,引用计数-1。只有计数降到0时,硬件才会真正关掉。这个机制是为了防止多个消费者共享一路电源时,一个消费者关了导致其他消费者掉电。
小技巧: 调试时可以用 regulator_is_enabled 检查当前状态。我曾经在suspend/resume流程里发现regulator没关,就是用这个API定位到的。
3.4 regulator设备驱动:如何编写一个regulator驱动
好,现在换个角色——你要写一个regulator驱动,让其他模块能用你的PMIC。核心步骤就三步:
3.4.1 实现 struct regulator_ops
这是最关键的。你至少要实现这几个回调:
static struct regulator_ops my_regulator_ops = {
.enable = my_reg_enable,
.disable = my_reg_disable,
.is_enabled = my_reg_is_enabled,
.set_voltage = my_reg_set_voltage,
.get_voltage = my_reg_get_voltage,
};
每个回调里操作你的硬件寄存器。比如 set_voltage 可能是写I2C给PMIC发命令。我记得第一次写时,忘了在 set_voltage 里做范围检查,结果上层传了个超出硬件能力的电压值,硬件直接没反应。后来我加了一段:
if (min_uV < my_reg->min_uV || max_uV > my_reg->max_uV)
return -EINVAL;
3.4.2 定义regulator描述符
static struct regulator_desc my_reg_desc = {
.name = "my-vdd",
.id = 0,
.type = REGULATOR_VOLTAGE,
.ops = &my_regulator_ops,
.n_voltages = 64,
.min_uV = 800000,
.uV_step = 12500,
.vsel_reg = 0x10,
.vsel_mask = 0x3f,
};
这里 n_voltages、min_uV、uV_step 定义了电压表。比如800mV起步,每步12.5mV,共64档,最高就是800000 + 63*12500 = 1.5875V。内核会用这个表来做电压映射。
3.4.3 注册regulator设备
struct regulator_dev *rdev;
rdev = devm_regulator_register(&pdev->dev, &my_reg_desc, &config);
用 devm_ 版本的好处是,驱动卸载时内核自动帮你清理。我早期用非devm版本,漏了 regulator_unregister,导致模块卸载后重新加载时注册失败——嗯,血的教训。
3.5 DVFS基础:动态电压频率调整
DVFS,说白了就是「跑得快时给高电压,跑得慢时给低电压」。为什么电压要和频率联动?因为CMOS电路的功耗公式:P ∝ C × V² × f。电压降一点,功耗是平方级下降。但频率高了,电压不够,电路就跑不动了。
在Linux里,DVFS通常由 cpufreq 框架驱动,它通过regulator API来调压。典型的流程是:
- 用户或内核策略决定要升频
- cpufreq驱动先调高电压(通过regulator_set_voltage)
- 等电压稳定后,再提高PLL频率
- 降频时顺序相反:先降频,再降压
这个顺序很重要。我曾经在项目里把顺序搞反了——先升频再升压,结果CPU在电压到位前就跑了高频,直接死机。后来我在代码里加了 udelay 等待电压稳定,才解决。
注意: 电压切换是有延迟的。PMIC的爬坡速率(slew rate)通常在几mV/µs到几十mV/µs之间。如果你的频率切换很快,但电压跟不上,系统就会不稳定。我建议在切换频率前,用 regulator_set_voltage_time 查询需要的等待时间。
3.6 设备树中的regulator配置
实际项目中,regulator的配置大多在设备树里完成。一个典型例子:
cpu_vdd: regulator@0 {
compatible = "my-pmic,regulator";
regulator-name = "cpu-vdd";
regulator-min-microvolt = <800000>;
regulator-max-microvolt = <1587500>;
regulator-always-on;
regulator-boot-on;
};
然后消费者节点引用它:
cpu0: cpu@0 {
cpu-supply = <&cpu_vdd>;
operating-points = <
1000000 1100000
1400000 1200000
1800000 1300000
>;
};
这里 operating-points 定义了频率-电压对。内核的cpufreq驱动会解析它,并在调频时自动调用regulator API。嗯,这套机制用起来确实省心。
3.7 调试与常见问题
最后分享几个调试技巧:
- 查看当前电压:
cat /sys/class/regulator/regulator.X/microvolts - 查看使能状态:
cat /sys/class/regulator/regulator.X/state - 打开regulator调试日志: 在
drivers/regulator/core.c里加#define DEBUG,或者动态调整debugfs级别
常见问题我列一下:
| 现象 | 可能原因 | 解决 |
|---|---|---|
| regulator_get返回NULL | 设备树名称不匹配 | 检查regulator-name和id参数 |
| 电压设置失败 | 超出硬件范围 | 检查min_uV/max_uV和n_voltages |
| 系统在调频时死机 | 电压切换时序不对 | 先升压再升频,加延迟等待 |
| regulator无法关闭 | 引用计数不为0 | 检查是否有其他消费者 |
好了,regulator框架的核心内容就这些。说白了,它就是一个「电压中间层」,让上层驱动不用关心底层硬件细节。下一章我们会深入DVFS的具体实现,包括如何与cpufreq、devfreq框架配合。到时候再聊。