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通过控制这些开关的栅极,来决定是否给这个域供电。

驱动里操作电源门控,一般要经过这几个步骤:

  1. 保存上下文:把模块的寄存器状态保存到内存里。
  2. 通知驱动:调用驱动的suspend回调,让驱动自己清理。
  3. 关闭时钟:先断时钟,再断电,顺序不能反。
  4. 拉低电源门控信号:写寄存器,让PMU切断电源。
  5. 等待断电完成:检查状态位,确认电压已经掉到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到崩溃。

好了,硬件基础就讲到这里。下一讲我们开始真正写驱动代码,从最简单的时钟门控驱动入手。你先把这些概念消化掉,后面写代码会顺手很多。