电源域划分:MTK8678电源域架构、电源域开关控制、电源域状态迁移管理

各位同学,今天我们来聊聊电源域划分。说实话,这块内容在芯片设计里属于「看不见但摸得着」的关键环节。你想想看,一颗SoC上集成了CPU、GPU、NPU、ISP、Modem等十几个功能模块,如果所有模块一直通电,那功耗简直不敢想。所以,电源域划分就是要把这些模块「分而治之」——该关的关,该睡的睡。

一、MTK8678电源域架构概览

MTK8678的电源域架构,我个人习惯把它分成三个层级:

  • 顶层电源域(Top-Level Domain):永远在线,包括PMU控制器、唤醒逻辑、RTC等。这部分一旦掉电,芯片就「死」了。
  • 功能电源域(Functional Domain):按功能模块划分,比如CPU域、GPU域、NPU域、ISP域、Display域等。每个域可以独立开关。
  • 子电源域(Sub-Domain):在功能域内部进一步细分。比如CPU域里,每个CPU核心可以单独关掉,甚至L2缓存也可以独立供电。

我曾在项目中遇到过一个问题:某个模块的电源域划分太粗,导致一个子模块需要工作时,整个大域都得唤醒,白白浪费了功耗。后来我们重新做了子域划分,功耗直接降了15%。所以,电源域划分的粒度,直接决定了你的低功耗设计上限。

核心原则:电源域划分要遵循「最小唤醒集」原则——只唤醒必须工作的模块,其他模块保持休眠。

二、电源域开关控制

电源域开关控制,说白了就是「谁来决定这个域什么时候通电,什么时候断电」。MTK8678里,这个决策由硬件和软件协同完成。

2.1 硬件控制层面

每个电源域都有一个电源开关控制器(Power Switch Controller, PSC)。PSC负责接收来自PMU(电源管理单元)的指令,控制该域的电源门控(Power Gating)晶体管。

举个例子,当CPU核心进入WFI(Wait For Interrupt)状态时,硬件会自动触发该核心所在电源域的断电流程。这个过程不需要软件干预,延迟极低——通常在几十纳秒内完成。

// 伪代码:电源域开关控制流程
void power_domain_switch(domain_id_t domain, bool on) {
    if (on) {
        // 1. 检查该域是否允许上电(比如温度、电压条件)
        if (!pmu_check_conditions(domain)) return;
        // 2. 逐步上电,防止电流浪涌
        pmu_ramp_up(domain, STEP_50mV);
        // 3. 等待电压稳定
        pmu_wait_voltage_ready(domain);
        // 4. 释放复位信号
        pmu_release_reset(domain);
    } else {
        // 1. 发送断电请求
        pmu_request_power_down(domain);
        // 2. 等待模块完成当前操作(比如DMA传输)
        pmu_wait_idle(domain);
        // 3. 切断电源
        pmu_cut_power(domain);
    }
}

我的经验:电源开关控制最怕的是「电流浪涌」。我曾经在一个项目中,因为上电速度太快,导致电源域内的电容瞬间充电,产生了超过10A的浪涌电流,直接把PMU的LDO烧了。后来我们加了一个「软启动」机制,上电时逐步增加电压,问题就解决了。

2.2 软件控制层面

软件层面,主要由电源管理固件(PM Firmware)操作系统内核协同完成。MTK8678的PM Firmware运行在专用的M0+核心上,负责处理电源状态迁移的底层逻辑。

软件控制的典型场景是:当系统检测到某个外设长时间空闲时,内核会调用pm_runtime_put_sync(),通知PM Firmware关闭该外设的电源域。

// Linux内核中的电源域控制示例
static int mtk_power_domain_off(struct generic_pm_domain *genpd) {
    struct mtk_power_domain *pd = to_mtk_pd(genpd);
    
    // 通知PM Firmware关闭电源域
    mtk_pm_ipi_send(IPI_POWER_DOWN, pd->id);
    
    // 等待硬件确认
    if (wait_for_completion_timeout(&pd->done, msecs_to_jiffies(100))) {
        dev_dbg(pd->dev, "Power domain %d off\n", pd->id);
        return 0;
    }
    
    dev_err(pd->dev, "Power domain %d off timeout\n", pd->id);
    return -ETIMEDOUT;
}

三、电源域状态迁移管理

电源域的状态迁移,是低功耗设计的「灵魂」。MTK8678定义了四种电源域状态:

状态 描述 功耗 唤醒延迟
Active 正常工作,时钟和电源都开启 0
Idle 时钟门控,但电源保持 几微秒
Sleep 电源门控,但保留上下文(如寄存器状态保存在SRAM中) 几十微秒
Off 完全断电,上下文丢失 极低 几百微秒到几毫秒

状态迁移的路径不是任意的。比如,你不能直接从Active跳到Off,必须经过Sleep状态。为什么?因为直接断电会导致数据丢失,而且可能产生电流浪涌。

注意:状态迁移过程中,必须保证「先保存后断电,先上电后恢复」。我曾经见过一个案例,因为状态机设计有bug,导致在Sleep状态下突然断电,寄存器内容没保存,系统恢复后直接跑飞了。

3.1 状态迁移的硬件实现

MTK8678里,每个电源域都有一个状态机(FSM),专门管理状态迁移。这个FSM由硬件实现,不需要软件干预,延迟极低。

举个例子,当CPU核心执行WFI指令后,硬件FSM会自动执行以下流程:

  1. 等待CPU流水线排空(Drain Pipeline)
  2. 保存CPU寄存器到保留SRAM
  3. 关闭CPU核心时钟
  4. 关闭CPU核心电源
  5. 进入Sleep状态

当外部中断到来时,FSM又会自动反向执行:上电、恢复时钟、恢复寄存器、开始执行指令。整个过程对软件完全透明。

3.2 状态迁移的软件管理

软件层面,PM Firmware负责管理多个电源域之间的协同迁移。比如,当系统进入Suspend-to-RAM时,PM Firmware会按照「先外设后核心」的顺序,逐个关闭电源域。

// 系统挂起时的电源域迁移顺序
static const int suspend_order[] = {
    DOMAIN_DISPLAY,   // 先关显示
    DOMAIN_ISP,       // 再关图像处理
    DOMAIN_NPU,       // 然后关NPU
    DOMAIN_GPU,       // 再关GPU
    DOMAIN_CPU_CLUSTER, // 最后关CPU集群
};

void system_suspend_power_down(void) {
    for (int i = 0; i < ARRAY_SIZE(suspend_order); i++) {
        power_domain_switch(suspend_order[i], false);
        // 等待该域完全断电
        pmu_wait_power_down(suspend_order[i]);
    }
}

我的建议:电源域状态迁移的时序非常关键。我习惯在芯片设计阶段,就用SystemVerilog写一个「电源域状态迁移检查器」,自动验证所有可能的迁移路径是否合法。这比后期在测试板上抓波形要高效得多。

四、避坑指南

最后,分享几个我在实际项目中踩过的坑:

  • 我曾经因为电源域划分太细,导致模块之间的「跨域通信」延迟过大,性能反而下降了。后来我们做了权衡:高频通信的模块放在同一个域里,低频通信的模块才分开。
  • 我曾经遇到过电源域唤醒时,因为多个域同时上电,导致PMU输出电流过载。解决方案是给每个域设置「上电优先级」,错峰上电。
  • 我记得有一次,某个电源域在Sleep状态下,因为漏电太大,电池续航直接砍半。后来我们加了「反向偏置」技术,在Sleep时给晶体管加一个反向电压,漏电降了80%。

好了,关于电源域划分的内容就讲到这里。下一章我们会深入讨论「动态电压频率调整(DVFS)」的具体实现。记住,电源域划分是低功耗设计的「地基」,地基打不好,上面盖的房子再漂亮也没用。