一、基本概念
DVFS(Dynamic Voltage and Frequency Scaling)动态电压频率调节,是一种实时的电压和频率调节技术。在 CMOS 电路中功率消耗主要可以分为动态功率消耗和静态功率消耗,公式如下:
其中 C 代表负载电容的容值,V 是工作电压,α 是当前频率下的翻转率,f为工作频率,I_dq 代表静态电流。公式的前部分代表的是动态功率消耗,后部分则代表的是静态功率消耗。从公式中可以看出,想要降低动态功率消耗可以从C、V、α、f着手,对于软件来讲常用的调节方式只涉及到V、f 两个因素。
二、代码解析
1、数据结构
系统中存在 7 大总线:ARM_CLK、AXI_CLK、DSP_CLK、APP_CLK、MPH_CLK、GE_CLK、VS_CLK,DVFS 系统的所有工作都围绕这几大总线以及核心电压CORE_VOLTAGE展开,代码里面用到的数据结构有:DVFS_REQ、DVFS_CMD、DVFS_NODE、DVFS_CDB,定义如下:
- typedef struct {
- const char* module;
- unsigned char flag;
- DVFS_CLK_e clk;
- int freq;
- } DVFS_REQ;
-
- typedef struct {
- DVFS_CBF cb;
- int arg;
- int num;
- DVFS_REQ reqs[MAX_REQ_NUM];
- } DVFS_CMD;
-
- typedef struct {
- struct list_head head;
- DVFS_REQ req;
- } DVFS_NODE;
-
- typedef struct {
-
- DVFS_CMD q_array[CMD_Q_LENGTH];
- uint8_t q_idx_r;
- uint8_t q_idx_w;
- uint8_t q_idx_p;
- spinlock_t q_lock;
-
-
- wait_queue_head_t q_wait;
- struct task_struct* task;
-
-
- DVFS_NODE* req_lists[DVFS_CLK_ALL];
-
-
- int curr_freq_idx[DVFS_CLK_ALL];
- int trgt_freq_idx[DVFS_CLK_ALL];
- struct clk* hw_clock[DVFS_CLK_ALL];
- uint8_t curr_vol_idx;
- uint8_t trgt_vol_idx;
- unsigned long vol_jiffies;
- } DVFS_CDB;
其中
DVFS_REQ
用于描述一个调节请求,存放了相关信息;
DVFS_CMD
用于多个请求构造的一次调节操作;
DVFS_NODE
作为链表节点,每一条总线的调节请求都由各自的链表维护;
DVFS_CDB
则存放了 DVFS 系统的大部分信息,系统中只存在一个全局的 DVFS_CDB 变量。
2、函数接口
当要调用 DVFS 系统时,通常的调用方法如下:
- DVFS_CREATE_REQ(reqs[0], "camera", AXI_CLK, axi_freq, DVFS_REQ_MIN);
- init_completion(&cam_complete);
- dvfs_send_reqs(reqs, 1, suspend_cb, 0);
- wait_for_completion(&cam_complete);
通过
DVFS_CREATE_REQ
宏定义创建一个调节请求,宏定义原型如下:
- #define DVFS_CREATE_REQ(req, MODULE, CLK, FREQ, FLAG) { \
- req.module = MODULE;\
- req.clk = CLK;\
- req.flag = FLAG;\
- req.freq = FREQ;\
- }
宏主要用于构造一个 DVFS_REQ 结构,然后通过
dvfs_send_reqs
构造 DVFS_CMD 完成调节并执行回调函数 suspend_cb。
3、代码解析
在 DVFS 系统的初始化过程中,做的工作有如下几个:a、初始化 cdb->hw_clock;b、创建每条总线的链表;c、创建 proc 文件节点;d、创建等待队列以及内核线程专门用于 DVFS。当初始化完成之后就启动内核线程 dvfs_thread_func,在这里面会等待 dvfs_send_reqs 发出唤醒事件,然后进行电压和频率的调节并执行回调函数。下面我们从发送调节请求开始解析,函数 dvfs_send_reqs实现如下:
- int dvfs_send_reqs(DVFS_REQ *reqs, int num, DVFS_CBF cb, int arg)
- {
- int ret;
- DVFS_CMD *cmd;
- unsigned long irq_flags;
-
- if ((ret = sanity_check(reqs, num))) {
- return ret;
- }
-
- if (num == 1 && reqs[0].clk == APP_CLK) {
- mutex_lock(&app_lock);
- ret = dvfs_process_req(&dvfs_cdb, reqs);
- if (ret) {
- dvfs_cdb.curr_freq_idx[APP_CLK] = dvfs_cdb.trgt_freq_idx[APP_CLK];
- clk_set_rate(dvfs_cdb.hw_clock[APP_CLK], dvfs_get_rate(APP_CLK, dvfs_cdb.curr_freq_idx[APP_CLK]));
- }
- mutex_unlock(&app_lock);
- return 0;
- }
-
- spin_lock_irqsave(&dvfs_cdb.q_lock, irq_flags);
- cmd = &dvfs_cdb.q_array[dvfs_cdb.q_idx_w & CMD_Q_MASK];
- cmd->cb = cb;
- cmd->num = num;
- cmd->arg = arg;
- memcpy(cmd->reqs, reqs, num * sizeof(DVFS_REQ));
- smp_mb();
- dvfs_cdb.q_idx_w++;
- spin_unlock_irqrestore(&dvfs_cdb.q_lock, irq_flags);
-
- wake_up_all(&dvfs_cdb.q_wait);
- return 0;
- }
- EXPORT_SYMBOL(dvfs_send_reqs);
函数首先进行参数有效性验证,然后判断是否只调节 APP_CLK,因为 APP_CLK 用于外设不影响系统运行,所以可以直接调节。如果不只调节 APP_CLK 接下来将会构造 DVFS_CMD,然后唤醒内核线程
dvfs_thread_func
,实现如下:
- int dvfs_thread_func(void *data)
- {
- DVFS_CDB *cdb = (DVFS_CDB*)data;
- DVFS_CMD *cmd;
-
- do {
-
- if(wait_event_interruptible(cdb->q_wait, (cdb->q_idx_w != cdb->q_idx_r)) != 0)
- continue;
-
-
- if (cal_target_freq(cdb)) {
- dvfs_reset_clk(cdb);
- }
-
-
- if (cmd->cb != NULL) {
- cmd->cb(cmd->arg);
- }
- } while (1);
- }
当线程被唤醒后会根据函数
cal_target_freq
判断当前是否需要进行电压和频率的调节,cal_target_freq 里面首先根据传递的 CMD 更新链表,然后根据链表记录去判断是否需要调节频率,如果需要调节就会根据目标频率值通过查表(
电压 - 频率表
)查找系统支持的最合适的频率值。接着就来到了 DVFS 系统最重要的函数
dvfs_reset_clk
,电压和频率调节都将在这个函数里面完成:
- void dvfs_reset_clk(DVFS_CDB *cdb)
- {
- int idx;
- int vol;
-
- reqs_updated:
- cal_target_vol_idx(cdb);
-
-
- if (cdb->trgt_vol_idx > cdb->curr_vol_idx && arm_regulator) {
- for (idx = cdb->curr_vol_idx + 1; idx <= cdb->trgt_vol_idx; ++idx) {
- unsigned int interval_ms = jiffies_to_msecs(jiffies - cdb->vol_jiffies);
- if (interval_ms < 100) {
- msleep(100 - interval_ms);
- if (cal_target_freq(cdb))
- goto reqs_updated;
- }
-
- if (idx + 1 <= cdb->trgt_vol_idx)
- ++idx;
- vol = voltage_table[idx].voltage;
- regulator_set_voltage(arm_regulator, vol, vol);
- cdb->curr_vol_idx = idx;
- cdb->vol_jiffies = jiffies;
- dvfs_dbg("DVFS increase arm voltage to %d uV\n", vol);
- }
- }
-
- do_reset_clk(cdb);
- if (cal_target_freq(cdb))
- goto reqs_updated;
-
-
- if (cdb->trgt_vol_idx < cdb->curr_vol_idx && arm_regulator) {
- for (idx = cdb->curr_vol_idx - 1; idx >= cdb->trgt_vol_idx; --idx) {
- unsigned int interval_ms = jiffies_to_msecs(jiffies - cdb->vol_jiffies);
- if (interval_ms < 100) {
- msleep(100 - interval_ms);
- if (cal_target_freq(cdb))
- goto reqs_updated;
- }
-
- vol = voltage_table[idx].voltage;
- regulator_set_voltage(arm_regulator, vol, vol);
- cdb->curr_vol_idx = idx;
- cdb->vol_jiffies = jiffies;
- dvfs_dbg("DVFS decrease arm voltage to %d uV\n", vol);
- }
- }
- }
函数里面先进行电压调节满足“升频率时先升电压,降频率时后降电压”的准则并且两次电压的调节需要间隔 100ms以上,然后通过 do_reset_clk 进行频率调节,实现如下:
- static void do_reset_clk(DVFS_CDB *cdb)
- {
- int clk, idx;
-
- for (clk = 0; clk < DVFS_CLK_ALL; clk++) {
- int curr = cdb->curr_freq_idx[clk];
- int trgt = cdb->trgt_freq_idx[clk];
-
- if (ARM_CLK == clk && curr != trgt) {
- unsigned int i;
- struct cpufreq_freqs freqs = {
- .flags = 0,
- .old = frequency_table[clk][curr].freq / 1000,
- .new = frequency_table[clk][trgt].freq / 1000,
- };
- for_each_online_cpu(i) {
- freqs.cpu = i;
-
- cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);
- }
- }
-
- if (curr < trgt) {
- for (idx = curr+1; idx <= trgt; idx++) {
- if (idx == trgt) {
- clk_set_rate(cdb->hw_clock[clk], frequency_table[clk][idx].freq);
- } else if (frequency_table[clk][idx].need_delay) {
- clk_set_rate(cdb->hw_clock[clk], frequency_table[clk][idx].freq);
- msleep(1);
- }
- }
- } else if (curr > trgt) {
- for (idx = curr-1; idx >= trgt; idx--) {
- if (idx == trgt) {
- clk_set_rate(cdb->hw_clock[clk], frequency_table[clk][idx].freq);
- } else if (frequency_table[clk][idx].need_delay) {
- clk_set_rate(cdb->hw_clock[clk], frequency_table[clk][idx].freq);
- msleep(1);
- }
- }
- }
- cdb->curr_freq_idx[clk] = trgt;
-
- if (ARM_CLK == clk && curr != trgt) {
- unsigned int i;
- struct cpufreq_freqs freqs = {
- .flags = 0,
- .old = frequency_table[clk][curr].freq / 1000,
- .new = frequency_table[clk][trgt].freq / 1000,
- };
- if (freqs.new >= 806000) {
- freqs.new = 1200000;
- }
- for_each_online_cpu(i) {
- freqs.cpu = i;
-
- cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
- }
- }
- }
- }
对于频率需要满足逐级调节的准则以保证系统的稳定性,ARM_CLK 的调节需要经过 cpufreq 子系统。至此,电压频率调节完成,线程将会执行调节请求的回调函数然后进入睡眠。
* 调试注意:
1、处理器需要可靠的电压 - 频率对应关系,这个需要较长时间的测试
2、升频率时先升电压,降频率时后降电压
3、逐级调节电压和频率有助于提升系统稳定性
4、每次调节电压和频率后,尤其是升电压之后等待一定时间再升频率
5、通常是在各模块的驱动程序里面进行电压和频率的调节,用户程序通过对各模块驱动的调用以达到调节的目的
6、某些情况下为了获得更好的用户体验还可以对一些用户程序进行特殊处理让其直接将电压频率调节到最优值
7、驱动程序里面升高电压频率时需要同步等待操作完成才能继续后续工作,因为一些硬件模块的工作对电压频率比较敏感
8、用户程序为了获得更好的体验而升高电压频率,则不必同步等待其完成,因为用户程序对时序基本无要求
* 调试心得:
对于所有的软件bug,不能怀疑程序被处理器错误执行了,这个是可以保证的,唯一需要怀疑的就是程序的逻辑是否正确,一切bug都可以看做是没有完善的错误处理引起的,这是指导思想。(个人经验)
转自http://blog.csdn.net/g_salamander/article/details/17009055