DVFS解析

一、基本概念

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,定義如下:

  1. typedef struct {  
  2.     const char*   module;   /* 發出調節請求的模塊名稱 */  
  3.     unsigned char flag;     /* 調節頻率的標記: 有最低、最高、鎖定三種 */  
  4.     DVFS_CLK_e    clk;      /* 申請調節的時鐘總線,有7大總線 */  
  5.     int           freq;     /* 期望頻率值 */  
  6. } DVFS_REQ;  
  7.   
  8. typedef struct {  
  9.     DVFS_CBF    cb;     /* 回調函數 */  
  10.     int         arg;    /* 參數 */  
  11.     int         num;    /* 本次請求的數目 */  
  12.     DVFS_REQ    reqs[MAX_REQ_NUM];   /* 請求存放的數組,最多8個請求 */  
  13. } DVFS_CMD;  
  14.   
  15. typedef struct {  
  16.     struct list_head head;  /* 鏈表節點 */  
  17.     DVFS_REQ         req;   /* 當前請求 */  
  18. } DVFS_NODE;  
  19.   
  20. typedef struct {  
  21.     // cmd queue managment  
  22.     DVFS_CMD    q_array[CMD_Q_LENGTH];  
  23.     uint8_t     q_idx_r;  
  24.     uint8_t     q_idx_w;  
  25.     uint8_t     q_idx_p;  
  26.     spinlock_t  q_lock;  
  27.   
  28.     // worker thread management  
  29.     wait_queue_head_t q_wait;  /* 等待隊列 */  
  30.     struct task_struct* task;  /* 任務 */  
  31.   
  32.     // list of request for each clock  
  33.     DVFS_NODE*  req_lists[DVFS_CLK_ALL];       /* 指向每條總線的請求鏈表 */  
  34.   
  35.     // clock/voltage setttings  
  36.     int         curr_freq_idx[DVFS_CLK_ALL];   /* 當前頻率的索引值 */  
  37.     int         trgt_freq_idx[DVFS_CLK_ALL];   /* 目標頻率的索引值 */  
  38.     struct clk* hw_clock[DVFS_CLK_ALL];        /* 時鐘設置 */  
  39.     uint8_t curr_vol_idx;        /* 當前電壓索引值 */  
  40.     uint8_t trgt_vol_idx;        /* 目標電壓索引值 */  
  41.     unsigned long vol_jiffies;   /* 電壓調節時間記錄,用於記錄調節間隔 */  
  42. } DVFS_CDB;  
其中 DVFS_REQ 用於描述一個調節請求,存放了相關信息;DVFS_CMD 用於多個請求構造的一次調節操作;DVFS_NODE 作爲鏈表節點,每一條總線的調節請求都由各自的鏈表維護;DVFS_CDB則存放了 DVFS 系統的大部分信息,系統中只存在一個全局的 DVFS_CDB 變量。

2、函數接口

當要調用 DVFS 系統時,通常的調用方法如下:

  1. DVFS_CREATE_REQ(reqs[0], "camera", AXI_CLK, axi_freq, DVFS_REQ_MIN);  /* 創建一個調節請求 */  
  2. init_completion(&cam_complete);  
  3. dvfs_send_reqs(reqs, 1, suspend_cb, 0);     /* 發送調節請求 */  
  4. wait_for_completion(&cam_complete);         /* 等待調節完成 */  
通過 DVFS_CREATE_REQ 宏定義創建一個調節請求,宏定義原型如下:

  1. #define DVFS_CREATE_REQ(req, MODULE, CLK, FREQ, FLAG) { \  
  2.     req.module = MODULE;\  
  3.     req.clk = CLK;\  
  4.     req.flag = FLAG;\  
  5.     req.freq = FREQ;\  
  6.     }  
宏主要用於構造一個 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實現如下:

  1. int dvfs_send_reqs(DVFS_REQ *reqs, int num, DVFS_CBF cb, int arg)  
  2. {  
  3.     int      ret;  
  4.     DVFS_CMD *cmd;  
  5.     unsigned long irq_flags;  
  6.   
  7.     if ((ret = sanity_check(reqs, num))) {  /* 檢查參數有效性 */  
  8.         return ret;  
  9.     }  
  10.   
  11.     if (num == 1 && reqs[0].clk == APP_CLK) {  /* APP_CLK 可以快速處理 */  
  12.         mutex_lock(&app_lock); /* 互斥體 */  
  13.         ret = dvfs_process_req(&dvfs_cdb, reqs);  /* 處理調節請求,如果需要調節返回非 0 值 */  
  14.         if (ret) {  
  15.             dvfs_cdb.curr_freq_idx[APP_CLK] = dvfs_cdb.trgt_freq_idx[APP_CLK];  
  16.             clk_set_rate(dvfs_cdb.hw_clock[APP_CLK], dvfs_get_rate(APP_CLK, dvfs_cdb.curr_freq_idx[APP_CLK]));  
  17.         }  
  18.         mutex_unlock(&app_lock);  
  19.         return 0;  
  20.     }  
  21.   
  22.     spin_lock_irqsave(&dvfs_cdb.q_lock, irq_flags);  /* 數據更新 */  
  23.     cmd = &dvfs_cdb.q_array[dvfs_cdb.q_idx_w & CMD_Q_MASK]; /* 構造 cmd */  
  24.     cmd->cb     = cb;  
  25.     cmd->num    = num;  
  26.     cmd->arg    = arg;  
  27.     memcpy(cmd->reqs, reqs, num * sizeof(DVFS_REQ));   
  28.     smp_mb();  
  29.     dvfs_cdb.q_idx_w++;  
  30.     spin_unlock_irqrestore(&dvfs_cdb.q_lock, irq_flags);  
  31.   
  32.     wake_up_all(&dvfs_cdb.q_wait);  /* 喚醒等待隊列 */  
  33.     return 0;  
  34. }  
  35. EXPORT_SYMBOL(dvfs_send_reqs);  
函數首先進行參數有效性驗證,然後判斷是否只調節 APP_CLK,因爲 APP_CLK 用於外設不影響系統運行,所以可以直接調節。如果不只調節 APP_CLK 接下來將會構造 DVFS_CMD,然後喚醒內核線程 dvfs_thread_func,實現如下:

  1. int dvfs_thread_func(void *data)  
  2. {  
  3.     DVFS_CDB *cdb = (DVFS_CDB*)data;  
  4.     DVFS_CMD *cmd;  
  5.   
  6.     do {  
  7.         /* 等待喚醒 */  
  8.         if(wait_event_interruptible(cdb->q_wait, (cdb->q_idx_w != cdb->q_idx_r)) != 0)  
  9.             continue;  
  10.   
  11.         /* 判斷是否需要調節電壓和頻率 */  
  12.         if (cal_target_freq(cdb)) {  
  13.             dvfs_reset_clk(cdb);  /* 開始調節 */  
  14.         }  
  15.           
  16.         /* 觸發回調函數 */  
  17.         if (cmd->cb != NULL) {  
  18.             cmd->cb(cmd->arg);  
  19.         }  
  20.     } while (1);  
  21. }  
當線程被喚醒後會根據函數 cal_target_freq 判斷當前是否需要進行電壓和頻率的調節,cal_target_freq 裏面首先根據傳遞的 CMD 更新鏈表,然後根據鏈表記錄去判斷是否需要調節頻率,如果需要調節就會根據目標頻率值通過查表(電壓 - 頻率表)查找系統支持的最合適的頻率值。接着就來到了 DVFS 系統最重要的函數 dvfs_reset_clk,電壓和頻率調節都將在這個函數裏面完成:

  1. void dvfs_reset_clk(DVFS_CDB *cdb)  
  2. {  
  3.     int idx;  
  4.     int vol;  
  5.   
  6. reqs_updated:  
  7.     cal_target_vol_idx(cdb);  /* 首先計算電壓是否需要調節 */  
  8.   
  9.     /* 需要升高電壓 */  
  10.     if (cdb->trgt_vol_idx > cdb->curr_vol_idx && arm_regulator) {  
  11.         for (idx = cdb->curr_vol_idx + 1; idx <= cdb->trgt_vol_idx; ++idx) {  
  12.             unsigned int interval_ms = jiffies_to_msecs(jiffies - cdb->vol_jiffies);  
  13.             if (interval_ms < 100) { /* 距離上次調節小於 100ms 則需等待 */  
  14.                 msleep(100 - interval_ms);  
  15.                 if (cal_target_freq(cdb))  
  16.                     goto reqs_updated;  
  17.             }  
  18.   
  19.             if (idx + 1 <= cdb->trgt_vol_idx)   
  20.                 ++idx;  
  21.             vol = voltage_table[idx].voltage;  
  22.             regulator_set_voltage(arm_regulator, vol, vol);  
  23.             cdb->curr_vol_idx = idx;  
  24.             cdb->vol_jiffies = jiffies;  
  25.             dvfs_dbg("DVFS increase arm voltage to %d uV\n", vol);  
  26.         }  
  27.     }  
  28.   
  29.     do_reset_clk(cdb);  /* 調節頻率 */  
  30.     if (cal_target_freq(cdb))  /* 查看當前是否有新的調節請求 */  
  31.         goto reqs_updated;  
  32.   
  33.     /* 需要降低電壓 */  
  34.     if (cdb->trgt_vol_idx < cdb->curr_vol_idx && arm_regulator) {  
  35.         for (idx = cdb->curr_vol_idx - 1; idx >= cdb->trgt_vol_idx; --idx) {  
  36.             unsigned int interval_ms = jiffies_to_msecs(jiffies - cdb->vol_jiffies);  
  37.             if (interval_ms < 100) {  
  38.                 msleep(100 - interval_ms);  
  39.                 if (cal_target_freq(cdb))  
  40.                     goto reqs_updated;  
  41.             }  
  42.   
  43.             vol = voltage_table[idx].voltage;  
  44.             regulator_set_voltage(arm_regulator, vol, vol);  
  45.             cdb->curr_vol_idx = idx;  
  46.             cdb->vol_jiffies = jiffies;  
  47.             dvfs_dbg("DVFS decrease arm voltage to %d uV\n", vol);  
  48.         }  
  49.     }  
  50. }  

函數裏面先進行電壓調節滿足“升頻率時先升電壓,降頻率時後降電壓”的準則並且兩次電壓的調節需要間隔 100ms以上,然後通過 do_reset_clk 進行頻率調節,實現如下:

  1. static void do_reset_clk(DVFS_CDB *cdb)  
  2. {  
  3.     int clk, idx;  
  4.   
  5.     for (clk = 0; clk < DVFS_CLK_ALL; clk++) { /* 依次調節頻率 */  
  6.         int curr = cdb->curr_freq_idx[clk];  
  7.         int trgt = cdb->trgt_freq_idx[clk];  
  8.   
  9.         if (ARM_CLK == clk && curr != trgt) {  /* 調整 arm 頻率,需要經過 cpufreq 子系統 */  
  10.             unsigned int i;  
  11.             struct cpufreq_freqs freqs = {  
  12.                 .flags = 0,  
  13.                 .old = frequency_table[clk][curr].freq / 1000,  
  14.                 .new = frequency_table[clk][trgt].freq / 1000,  
  15.             };  
  16.             for_each_online_cpu(i) {  
  17.                 freqs.cpu = i;  
  18.                 /* 函數向掛載在這個 cpu 上所有的驅動發出一個信號,驅動接收到這個信號則調用相應的處理函數 */  
  19.                 cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);  
  20.             }  
  21.         }  
  22.   
  23.         if (curr < trgt) {  /* 如果當前頻率比目標頻率低 */  
  24.             for (idx = curr+1; idx <= trgt; idx++) {  /* 逐級調節頻率可以保證系統的穩定性 */  
  25.                 if (idx == trgt) {  
  26.                     clk_set_rate(cdb->hw_clock[clk], frequency_table[clk][idx].freq);  
  27.                 } else if (frequency_table[clk][idx].need_delay) {  
  28.                     clk_set_rate(cdb->hw_clock[clk], frequency_table[clk][idx].freq);  
  29.                     msleep(1);  
  30.                 }  
  31.             }  
  32.         } else if (curr > trgt) { /* 如果當前比目標頻率高 */  
  33.             for (idx = curr-1; idx >= trgt; idx--) {  /* 逐級調節頻率可以保證系統的穩定性 */  
  34.                 if (idx == trgt) {  
  35.                     clk_set_rate(cdb->hw_clock[clk], frequency_table[clk][idx].freq);  
  36.                 } else if (frequency_table[clk][idx].need_delay) {  
  37.                     clk_set_rate(cdb->hw_clock[clk], frequency_table[clk][idx].freq);  
  38.                     msleep(1);  
  39.                 }  
  40.             }  
  41.         }  
  42.         cdb->curr_freq_idx[clk] = trgt;  /* 更新索引記錄 */  
  43.   
  44.         if (ARM_CLK == clk && curr != trgt) {  
  45.             unsigned int i;  
  46.             struct cpufreq_freqs freqs = {  
  47.                 .flags = 0,  
  48.                 .old = frequency_table[clk][curr].freq / 1000,  
  49.                 .new = frequency_table[clk][trgt].freq / 1000,  
  50.             };  
  51.             if (freqs.new >= 806000) {  
  52.                 freqs.new = 1200000;  
  53.             }  
  54.             for_each_online_cpu(i) {  
  55.                 freqs.cpu = i;  
  56.                 /* 通知函數在 cpu 頻率的調節過程中調用兩次,驅動處理函數通過 cpufreq_register_notifier 註冊 */  
  57.                 cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);  
  58.             }  
  59.         }  
  60.     }  
  61. }  

對於頻率需要滿足逐級調節的準則以保證系統的穩定性,ARM_CLK 的調節需要經過 cpufreq 子系統。至此,電壓頻率調節完成,線程將會執行調節請求的回調函數然後進入睡眠。


* 調試注意:

1、處理器需要可靠的電壓 - 頻率對應關係,這個需要較長時間的測試

2、升頻率時先升電壓,降頻率時後降電壓

3、逐級調節電壓和頻率有助於提升系統穩定性

4、每次調節電壓和頻率後,尤其是升電壓之後等待一定時間再升頻率

5、通常是在各模塊的驅動程序裏面進行電壓和頻率的調節,用戶程序通過對各模塊驅動的調用以達到調節的目的

6、某些情況下爲了獲得更好的用戶體驗還可以對一些用戶程序進行特殊處理讓其直接將電壓頻率調節到最優值

7、驅動程序裏面升高電壓頻率時需要同步等待操作完成才能繼續後續工作,因爲一些硬件模塊的工作對電壓頻率比較敏感

8、用戶程序爲了獲得更好的體驗而升高電壓頻率,則不必同步等待其完成,因爲用戶程序對時序基本無要求


* 調試心得:

對於所有的軟件bug,不能懷疑程序被處理器錯誤執行了,這個是可以保證的,唯一需要懷疑的就是程序的邏輯是否正確,一切bug都可以看做是沒有完善的錯誤處理引起的,這是指導思想。(個人經驗)


轉自:http://blog.csdn.net/g_salamander/article/details/17009055

發佈了10 篇原創文章 · 獲贊 13 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章