一、基本概念
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; /* 申請調節的時鐘總線,有7大總線 */
- int freq; /* 期望頻率值 */
- } DVFS_REQ;
- typedef struct {
- DVFS_CBF cb; /* 回調函數 */
- int arg; /* 參數 */
- int num; /* 本次請求的數目 */
- DVFS_REQ reqs[MAX_REQ_NUM]; /* 請求存放的數組,最多8個請求 */
- } DVFS_CMD;
- typedef struct {
- struct list_head head; /* 鏈表節點 */
- DVFS_REQ req; /* 當前請求 */
- } DVFS_NODE;
- typedef struct {
- // cmd queue managment
- 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;
- // worker thread management
- wait_queue_head_t q_wait; /* 等待隊列 */
- struct task_struct* task; /* 任務 */
- // list of request for each clock
- DVFS_NODE* req_lists[DVFS_CLK_ALL]; /* 指向每條總線的請求鏈表 */
- // clock/voltage setttings
- 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;
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); /* 等待調節完成 */
- #define DVFS_CREATE_REQ(req, MODULE, CLK, FREQ, FLAG) { \
- req.module = MODULE;\
- req.clk = CLK;\
- req.flag = FLAG;\
- req.freq = FREQ;\
- }
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) { /* APP_CLK 可以快速處理 */
- mutex_lock(&app_lock); /* 互斥體 */
- ret = dvfs_process_req(&dvfs_cdb, reqs); /* 處理調節請求,如果需要調節返回非 0 值 */
- 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 */
- 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);
- 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);
- }
- 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) { /* 距離上次調節小於 100ms 則需等待 */
- 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) { /* 調整 arm 頻率,需要經過 cpufreq 子系統 */
- 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;
- /* 函數向掛載在這個 cpu 上所有的驅動發出一個信號,驅動接收到這個信號則調用相應的處理函數 */
- 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;
- /* 通知函數在 cpu 頻率的調節過程中調用兩次,驅動處理函數通過 cpufreq_register_notifier 註冊 */
- 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