2、Linux内核电源管理基础:CPU Freq与CPU Idle框架、Clock框架与Gate操作、Regulator框架与电压控制

好,咱们进入正题。这一章讲的是Linux内核里电源管理的几个核心框架。说实话,这些框架是MTK平台低功耗优化的地基。地基不稳,上面盖的楼再漂亮也得塌。我个人习惯是,拿到一个新平台,先不看上层应用怎么调,先把这几个框架摸清楚。

2.1 CPU Freq与CPU Idle框架

这两个东西,一个管“跑多快”,一个管“歇多久”。你想想看,CPU就像个运动员,CPU Freq决定他冲刺的速度,CPU Idle决定他休息的深度。

2.1.1 CPU Freq:动态调频

CPU Freq框架,说白了就是根据系统负载动态调整CPU的主频和电压。负载高了,频率拉上去;负载低了,频率降下来。这个机制在MTK平台上特别重要,因为手机不像服务器,电池就那么点容量。

框架里有个核心概念叫 governor(调频策略)。常见的几个:

  • performance:一直跑最高频,性能拉满,功耗也拉满。调试阶段偶尔用用。
  • powersave:一直跑最低频,省电但卡顿。基本没人用。
  • ondemand:负载高了立刻升频,负载降了慢慢降。老牌策略,响应快但不够平滑。
  • interactive:Google当年推的,对触控响应优化过。MTK早期平台常用。
  • schedutil:现在的主流。直接跟调度器挂钩,用调度器的负载信息来做调频决策。更精准,延迟更低。

我在项目中遇到过一个问题:某个MTK平板,播放视频时偶尔卡顿。查了半天,发现是schedutil的调频响应太慢,CPU没及时升频。后来我调整了 up_rate_limit_us 参数,把升频延迟从10ms降到2ms,问题就解决了。嗯,这里要注意,调频延迟不是越小越好,太激进反而会增加功耗抖动。

调试小技巧: 在MTK平台上,你可以通过 /sys/devices/system/cpu/cpufreq/ 下的节点查看当前策略和频率。比如 cat scaling_governor 看当前策略,cat scaling_cur_freq 看当前频率。

2.1.2 CPU Idle:深度睡眠

CPU Idle框架管的是CPU没事干的时候怎么省电。ARM架构的CPU通常有多个 idle state(也叫C-state),从浅到深:

State 名称 功耗 唤醒延迟 说明
C0 运行态 0 CPU正在干活
C1 WFI 较低 几微秒 关闭时钟,保持电压
C2 Retention 几十微秒 保持寄存器状态
C3 Power Down 极低 几百微秒 关闭电源域,丢失状态

我曾经踩过一个坑:某个项目待机电流偏大,怎么都降不下去。后来发现是某个驱动在CPU进入C3之前,没有释放一个锁,导致CPU只能停在C1。改完之后,待机电流直接降了5mA。所以,驱动里对idle state的约束一定要检查清楚。

注意: 在MTK平台上,CPU Idle的深度和唤醒延迟是强相关的。如果某个外设的中断响应要求很及时(比如USB),就不能让CPU睡太深。否则唤醒延迟会导致数据丢包。

2.2 Clock框架与Gate操作

Clock框架,管的是芯片里各个模块的时钟。你想想,芯片里那么多模块,不是每个模块时时刻刻都需要时钟的。比如音频模块,你不放歌的时候,它的时钟完全可以关掉。这就是Gate操作——把时钟“门控”掉。

2.2.1 Clock框架的基本结构

Linux的Clock框架用 struct clk 来表示一个时钟。每个时钟可以有自己的父时钟,形成一棵时钟树。MTK的时钟树特别复杂,一个SoC里可能有几百个时钟节点。

常用的API:

  • clk_get(dev, "name"):获取时钟句柄
  • clk_prepare_enable():准备并使能时钟
  • clk_disable_unprepare():禁能并清理时钟
  • clk_set_rate():设置时钟频率
  • clk_get_rate():获取当前频率

代码示例:

/* 获取时钟 */
struct clk *clk = clk_get(dev, "uart_clk");
if (IS_ERR(clk)) {
    dev_err(dev, "Failed to get uart clock\n");
    return PTR_ERR(clk);
}

/* 使能时钟 */
clk_prepare_enable(clk);

/* 设置频率 */
clk_set_rate(clk, 115200 * 16);

/* 使用完后关闭 */
clk_disable_unprepare(clk);
clk_put(clk);

我个人习惯是,在驱动的probe函数里就把所有需要的时钟都获取好,然后在suspend/resume里做enable/disable。这样逻辑清晰,也方便调试。

2.2.2 Gate操作:精准控钟

Gate操作,说白了就是“用的时候开,不用的时候关”。但这里有个讲究:不是所有时钟都能随便关。比如某个模块的时钟关了,但它的子模块还在工作,那就会出问题。

MTK平台在Clock框架里做了很多硬件级别的Gate控制。比如 clk_gate 结构体,可以直接操作硬件寄存器来开关时钟。这样做的好处是延迟极低,适合频繁开关的场景。

关键点: 在MTK平台上,很多外设的时钟是默认关闭的。驱动必须显式调用 clk_prepare_enable() 才能工作。如果某个外设不工作,第一件事就是检查它的时钟有没有开。

我曾经遇到一个案例:I2C总线在某个频率下通信不稳定。查了半天,发现是I2C模块的时钟源被其他模块抢占了,导致实际频率跟设置的不一致。后来用 clk_get_rate() 一查,果然差了20%。所以,调完时钟一定要确认实际频率。

2.3 Regulator框架与电压控制

Regulator框架,管的是电压。CPU要降频,通常也得跟着降压,不然功耗降不下去。这就是DVFS(动态电压频率调整)的核心思想。

2.3.1 Regulator的基本概念

Regulator分为两种:

  • LDO(低压差线性稳压器):输出纹波小,但效率低。适合给模拟电路供电。
  • DC-DC(开关电源):效率高,但纹波大。适合给数字电路供电。

在MTK平台上,CPU核心电压通常由DC-DC提供,而一些外设(比如摄像头、音频)的电压由LDO提供。

常用的API:

  • regulator_get(dev, "name"):获取regulator句柄
  • regulator_enable():使能输出
  • regulator_disable():禁能输出
  • regulator_set_voltage():设置电压值
  • regulator_get_voltage():获取当前电压

代码示例:

/* 获取CPU核心电压的regulator */
struct regulator *cpu_reg = regulator_get(cpu_dev, "cpu-vcore");
if (IS_ERR(cpu_reg)) {
    dev_err(cpu_dev, "Failed to get CPU regulator\n");
    return PTR_ERR(cpu_reg);
}

/* 使能输出 */
regulator_enable(cpu_reg);

/* 设置电压为1.0V */
regulator_set_voltage(cpu_reg, 1000000, 1000000);

/* 使用完后关闭 */
regulator_disable(cpu_reg);
regulator_put(cpu_reg);

2.3.2 电压控制中的坑

电压控制看起来简单,但实际项目中坑不少。我总结几个常见的:

  • 电压裕量不足:有些芯片在低温下需要更高的电压才能稳定工作。如果只按常温的电压设置,低温下可能会死机。
  • 电压切换速度:regulator的电压切换不是瞬间完成的。如果切换太快,可能导致电压过冲或下冲。MTK平台通常有硬件自动控制,但驱动里也要留够时间。
  • 依赖关系:有些模块的电压依赖于其他模块。比如某个外设需要先有核心电压,才能有IO电压。顺序搞反了,芯片可能烧掉。
血的教训: 我曾经在某个项目里,为了省电,把某个外设的电压从1.8V降到了1.2V。结果发现该外设在1.2V下无法正常工作,导致系统频繁重启。后来查datasheet才知道,这个外设的最低工作电压是1.5V。所以,调电压之前一定要看datasheet,不要想当然。

2.4 三个框架的协同工作

CPU Freq、Clock、Regulator这三个框架不是孤立的。它们之间通过 OPP(Operating Performance Point) 来协同。OPP定义了一组“频率-电压”对,比如:

OPP 频率 电压
0 500 MHz 0.8 V
1 1.0 GHz 0.9 V
2 1.5 GHz 1.0 V
3 2.0 GHz 1.1 V

当CPU Freq决定切换到某个频率时,它会通过OPP找到对应的电压,然后通知Regulator框架去调整电压。同时,Clock框架也会确保时钟频率的切换是平滑的。

在MTK平台上,这个协同过程通常由 MTK CPUFreq 驱动来完成。它会注册一个 cpufreq_driver,然后在 target() 回调里依次执行:

  1. 设置新的时钟频率
  2. 等待时钟稳定
  3. 设置新的电压
  4. 等待电压稳定
  5. 更新状态

嗯,这里要注意,步骤的顺序不能搞反。如果先降压再降频,可能会导致CPU在低电压下跑高频,直接死机。

总结一下: CPU Freq管速度,CPU Idle管休息,Clock管时钟,Regulator管电压。这四个东西配合好了,系统的功耗才能降下来。在MTK平台上,这些框架都已经封装好了,但理解它们的原理,才能在遇到问题时快速定位。

好了,这一章就到这里。下一章我们讲MTK平台特有的电源管理硬件,比如PMIC、PWM、EINT这些。到时候我会结合具体的项目案例,讲讲怎么用这些硬件来优化功耗。