2、硬件基础:PMU硬件架构、电压调节器(VR)、电源门控(Power Gating)、时钟门控(Clock Gating)
好,咱们直接进入正题。做PMU驱动,说白了就是跟芯片的“供电系统”打交道。你如果不了解硬件长什么样,写出来的代码就是瞎蒙。我刚开始做这行的时候,就吃过这个亏——对着寄存器手册猛写,结果板子一跑就挂,查了三天才发现是电源域没搞清楚。
今天这一讲,我把PMU相关的硬件基础给你拆开揉碎。你理解了这些,后面写驱动才能心里有底。
2.1 PMU硬件架构概览
PMU,全称Power Management Unit。它不是一颗独立的芯片,而是SoC内部的一个模块。你可以把它想象成整个芯片的“电力调度中心”。
一个典型的PMU内部包含这些关键部件:
- 电压调节器控制器(VR Controller):负责跟外部VR芯片通信,调整核心电压。
- 电源门控控制器(Power Gating Controller):控制每个电源域的开关。
- 时钟门控控制器(Clock Gating Controller):控制每个模块的时钟通断。
- 状态机(FSM):管理各种电源状态之间的切换,比如运行、休眠、深度睡眠。
- 寄存器组(Register File):驱动就是通过读写这些寄存器来控制PMU的。
我个人习惯,拿到一块新芯片,第一件事就是看PMU的地址映射表。你想想看,如果连控制寄存器在哪个地址都不知道,后面全是白搭。
核心要点:PMU本身也是一个外设,它挂在系统总线上。驱动通过总线访问PMU的寄存器,从而控制整个芯片的功耗。
2.2 电压调节器(Voltage Regulator, VR)
电压调节器,就是给芯片供电的那个家伙。在嵌入式系统里,我们通常用DC-DC转换器或者LDO(低压差线性稳压器)。
PMU驱动需要做的事情,就是告诉VR:“现在我要跑1.2GHz,你给我1.1V的电压。”或者“我要睡觉了,降到0.9V就行。”
这里有个关键概念——动态电压频率调整(DVFS)。说白了就是根据负载动态调整电压和频率。我在项目中遇到过,有些芯片的VR响应速度很慢,你刚把电压调上去,CPU就开始跑高频指令,结果直接崩了。嗯,这里要注意,调压之后一定要等VR稳定了,再提升频率。
VR的通信接口常见的有两种:
| 接口类型 | 特点 | 典型场景 |
|---|---|---|
| I2C/SMBus | 慢,但灵活,支持多路VR | 服务器、笔记本 |
| PWM/VID | 快,直接硬件控制 | 手机、嵌入式SoC |
驱动里怎么处理?我给你看一段伪代码,意思到了就行:
/* 设置核心电压为1.1V */
int pmu_set_core_voltage(int mv) {
/* 1. 计算VID值(电压识别码) */
u8 vid = voltage_to_vid(mv);
/* 2. 通过I2C写入VR芯片 */
i2c_write(vr_addr, VR_VID_REG, vid);
/* 3. 等待VR稳定(关键!) */
mdelay(10); /* 我曾经因为这里只等了1ms,导致系统随机死机 */
/* 4. 读取状态寄存器确认 */
return i2c_read(vr_addr, VR_STATUS_REG);
}
避坑指南:我曾经在调试DVFS时,发现电压调上去之后CPU反而更慢了。查了半天,原来是VR的电流输出能力不够,电压一高反而掉电了。所以驱动里最好加一个电流限制检查,别光看电压。
2.3 电源门控(Power Gating)
电源门控,就是把暂时不用的模块彻底断电。比如你的GPU不渲染画面了,直接给它断电,省电效果立竿见影。
硬件上怎么实现的?每个电源域都有一个电源开关(Power Switch),通常是PMOS管。PMU通过控制这些开关的栅极,来决定是否给这个域供电。
驱动里操作电源门控,一般要经过这几个步骤:
- 保存上下文:把模块的寄存器状态保存到内存里。
- 通知驱动:调用驱动的suspend回调,让驱动自己清理。
- 关闭时钟:先断时钟,再断电,顺序不能反。
- 拉低电源门控信号:写寄存器,让PMU切断电源。
- 等待断电完成:检查状态位,确认电压已经掉到0。
你可能会问:“为什么先断时钟再断电?” 嗯,这个问题我当年也问过。因为如果先断电,时钟还在跑,模块内部可能会产生毛刺信号,把别的模块搞崩了。所以顺序很重要。
个人经验:我建议你在驱动里加一个电源门控的计时器。如果某个模块频繁开关(比如每秒钟开关几十次),那还不如一直开着。因为开关电源本身也有功耗开销,这叫“开关损耗”。
2.4 时钟门控(Clock Gating)
时钟门控比电源门控更轻量级。它不切断电源,只是把时钟信号停掉。模块没有时钟,就不翻转,动态功耗就降下来了。
硬件上,时钟门控通常用一个与门(AND gate)实现。时钟信号和使能信号相与,使能为0时,时钟就被挡住了。
驱动里控制时钟门控,一般就是读写一个寄存器位:
/* 开启UART模块的时钟 */
void uart_clock_enable(void) {
writel(1, CLK_GATE_REG + UART_OFFSET);
}
/* 关闭UART模块的时钟 */
void uart_clock_disable(void) {
writel(0, CLK_GATE_REG + UART_OFFSET);
}
看起来很简单对吧?但坑也不少。我曾经遇到过一个bug:关闭某个外设的时钟后,再打开,发现外设不工作了。查了三天,发现是时钟门控之后,模块内部的同步逻辑没有复位,状态卡住了。解决办法是在关时钟之前,先给模块发一个软复位。
关键区别:时钟门控只省动态功耗,漏电流还在。电源门控连漏电流都省了,但开关代价大。所以实际项目中,短时间空闲用时钟门控,长时间空闲用电源门控。
2.5 三者协同工作
实际系统中,VR、电源门控、时钟门控是配合使用的。我给你画个典型场景:
- 轻度空闲:只做时钟门控,关掉外设时钟,CPU进入WFI(等待中断)状态。
- 中度空闲:降低电压(通过VR),同时关掉部分模块的电源。
- 深度睡眠:电压降到保留值,大部分电源域断电,只留唤醒逻辑。
驱动里怎么实现这种协同?我一般会设计一个状态机:
enum pm_state {
PM_STATE_ACTIVE,
PM_STATE_IDLE,
PM_STATE_SLEEP,
PM_STATE_DEEP_SLEEP,
};
int pm_set_state(enum pm_state state) {
switch (state) {
case PM_STATE_IDLE:
clock_gate_all_peripherals();
break;
case PM_STATE_SLEEP:
power_gate_unused_domains();
vr_set_voltage(0.9V);
break;
case PM_STATE_DEEP_SLEEP:
power_gate_all_except_wake();
vr_set_voltage(0.7V);
break;
}
}
你想想看,如果每个模块各自为政,有的关时钟有的不关,系统功耗肯定降不下来。所以PMU驱动要有一个全局视角,统一调度。
我的建议:刚开始写PMU驱动时,别急着优化。先把每个功能单独调通——VR能调压、电源门控能开关、时钟门控能控制。然后再把它们组合起来。我见过太多人一上来就想搞DVFS+电源门控联动,结果debug到崩溃。
好了,硬件基础就讲到这里。下一讲我们开始真正写驱动代码,从最简单的时钟门控驱动入手。你先把这些概念消化掉,后面写代码会顺手很多。