ALSA聲卡驅動中的DAPM詳解之一:kcontrol

DAPM是Dynamic Audio Power Management的縮寫,直譯過來就是動態音頻電源管理的意思,DAPM是爲了使基於linux的移動設備上的音頻子系統,在任何時候都工作在最小功耗狀態下。DAPM對用戶空間的應用程序來說是透明的,所有與電源相關的開關都在ASoc core中完成。用戶空間的應用程序無需對代碼做出修改,也無需重新編譯,DAPM根據當前激活的音頻流(playback/capture)和聲卡中的mixer等的配置來決定那些音頻控件的電源開關被打開或關閉。

/*****************************************************************************************************/
聲明:本博內容均由http://blog.csdn.net/droidphone原創,轉載請註明出處,謝謝!
/*****************************************************************************************************/

DAPM控件是由普通的soc音頻控件演變而來的,所以本章的內容我們先從普通的soc音頻控件開始。

snd_kcontrol_new結構


在正式討論DAPM之前,我們需要先搞清楚ASoc中的一個重要的概念:kcontrol,不熟悉的讀者需要瀏覽一下我之前的文章:Linux ALSA聲卡驅動之四:Control設備的創建。通常,一個kcontrol代表着一個mixer(混音器),或者是一個mux(多路開關),又或者是一個音量控制器等等。 從上述文章中我們知道,定義一個kcontrol主要就是定義一個snd_kcontrol_new結構,爲了方便討論,這裏再次給出它的定義:

[cpp] view plain copy
  1. struct snd_kcontrol_new {  
  2.         snd_ctl_elem_iface_t iface;     /* interface identifier */  
  3.         unsigned int device;            /* device/client number */  
  4.         unsigned int subdevice;         /* subdevice (substream) number */  
  5.         const unsigned char *name;      /* ASCII name of item */  
  6.         unsigned int index;             /* index of item */  
  7.         unsigned int access;            /* access rights */  
  8.         unsigned int count;             /* count of same elements */  
  9.         snd_kcontrol_info_t *info;  
  10.         snd_kcontrol_get_t *get;  
  11.         snd_kcontrol_put_t *put;  
  12.         union {  
  13.                 snd_kcontrol_tlv_rw_t *c;  
  14.                 const unsigned int *p;  
  15.         } tlv;  
  16.         unsigned long private_value;  
  17. };  

回到Linux ALSA聲卡驅動之四:Control設備的創建中,我們知道,對於每個控件,我們需要定義一個和他對應的snd_kcontrol_new結構,這些snd_kcontrol_new結構會在聲卡的初始化階段,通過snd_soc_add_codec_controls函數註冊到系統中,用戶空間就可以通過amixer或alsamixer等工具查看和設定這些控件的狀態。

snd_kcontrol_new結構中,幾個主要的字段是get,put,private_value,get回調函數用於獲取該控件當前的狀態值,而put回調函數則用於設置控件的狀態值,而private_value字段則根據不同的控件類型有不同的意義,比如對於普通的控件,private_value字段可以用來定義該控件所對應的寄存器的地址以及對應的控制位在寄存器中的位置信息。值得慶幸的是,ASoc系統已經爲我們準備了大量的宏定義,用於定義常用的控件,這些宏定義位於include/sound/soc.h中。下面我們分別討論一下如何用這些預設的宏定義來定義一些常用的控件。

簡單型的控件


SOC_SINGLE    SOC_SINGLE應該算是最簡單的控件了,這種控件只有一個控制量,比如一個開關,或者是一個數值變量(比如Codec中某個頻率,FIFO大小等等)。我們看看這個宏是如何定義的:

[cpp] view plain copy
  1. #define SOC_SINGLE(xname, reg, shift, max, invert) \  
  2. {       .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \  
  3.         .info = snd_soc_info_volsw, .get = snd_soc_get_volsw,\  
  4.         .put = snd_soc_put_volsw, \  
  5.         .private_value =  SOC_SINGLE_VALUE(reg, shift, max, invert) }  
宏定義的參數分別是:xname(該控件的名字),reg(該控件對應的寄存器的地址),shift(控制位在寄存器中的位移),max(控件可設置的最大值),invert(設定值是否邏輯取反)。這裏又使用了一個宏來定義private_value字段:SOC_SINGLE_VALUE,我們看看它的定義:

[cpp] view plain copy
  1. #define SOC_DOUBLE_VALUE(xreg, shift_left, shift_right, xmax, xinvert) \  
  2.         ((unsigned long)&(struct soc_mixer_control) \  
  3.         {.reg = xreg, .rreg = xreg, .shift = shift_left, \  
  4.         .rshift = shift_right, .max = xmax, .platform_max = xmax, \  
  5.         .invert = xinvert})  
  6. #define SOC_SINGLE_VALUE(xreg, xshift, xmax, xinvert) \  
  7.         SOC_DOUBLE_VALUE(xreg, xshift, xshift, xmax, xinvert)  
這裏實際上是定義了一個soc_mixer_control結構,然後把該結構的地址賦值給了private_value字段,soc_mixer_control結構是這樣的:

[cpp] view plain copy
  1. /* mixer control */  
  2. struct soc_mixer_control {  
  3.         int min, max, platform_max;  
  4.         unsigned int reg, rreg, shift, rshift, invert;  
  5. };  
看來soc_mixer_control是控件特徵的真正描述者,它確定了該控件對應寄存器的地址,位移值,最大值和是否邏輯取反等特性,控件的put回調函數和get回調函數需要藉助該結構來訪問實際的寄存器。我們看看這get回調函數的定義:

[cpp] view plain copy
  1. int snd_soc_get_volsw(struct snd_kcontrol *kcontrol,  
  2.         struct snd_ctl_elem_value *ucontrol)  
  3. {  
  4.         struct soc_mixer_control *mc =  
  5.                 (struct soc_mixer_control *)kcontrol->private_value;  
  6.         struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);  
  7.         unsigned int reg = mc->reg;  
  8.         unsigned int reg2 = mc->rreg;  
  9.         unsigned int shift = mc->shift;  
  10.         unsigned int rshift = mc->rshift;  
  11.         int max = mc->max;  
  12.         unsigned int mask = (1 << fls(max)) - 1;  
  13.         unsigned int invert = mc->invert;  
  14.   
  15.         ucontrol->value.integer.value[0] =  
  16.                 (snd_soc_read(codec, reg) >> shift) & mask;  
  17.         if (invert)  
  18.                 ucontrol->value.integer.value[0] =  
  19.                         max - ucontrol->value.integer.value[0];  
  20.   
  21.         if (snd_soc_volsw_is_stereo(mc)) {  
  22.                 if (reg == reg2)  
  23.                         ucontrol->value.integer.value[1] =  
  24.                                 (snd_soc_read(codec, reg) >> rshift) & mask;  
  25.                 else  
  26.                         ucontrol->value.integer.value[1] =  
  27.                                 (snd_soc_read(codec, reg2) >> shift) & mask;  
  28.                 if (invert)  
  29.                         ucontrol->value.integer.value[1] =  
  30.                                 max - ucontrol->value.integer.value[1];  
  31.         }  
  32.   
  33.         return 0;  
  34. }  
上述代碼一目瞭然,從private_value字段取出soc_mixer_control結構,利用該結構的信息,訪問對應的寄存器,返回相應的值。


SOC_SINGLE_TLV    SOC_SINGLE_TLV是SOC_SINGLE的一種擴展,主要用於定義那些有增益控制的控件,例如音量控制器,EQ均衡器等等。

[cpp] view plain copy
  1. #define SOC_SINGLE_TLV(xname, reg, shift, max, invert, tlv_array) \  
  2. {       .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \  
  3.         .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\  
  4.                  SNDRV_CTL_ELEM_ACCESS_READWRITE,\  
  5.         .tlv.p = (tlv_array), \  
  6.         .info = snd_soc_info_volsw, .get = snd_soc_get_volsw,\  
  7.         .put = snd_soc_put_volsw, \  
  8.         .private_value =  SOC_SINGLE_VALUE(reg, shift, max, invert) }  
從他的定義可以看出,用於設定寄存器信息的private_value字段的定義和SOC_SINGLE是一樣的,甚至put、get回調函數也是使用同一套,唯一不同的是增加了一個tlv_array參數,並把它賦值給了tlv.p字段。關於tlv,已經在Linux ALSA聲卡驅動之四:Control設備的創建中進行了闡述。用戶空間可以通過對聲卡的control設備發起以下兩種ioctl來訪問tlv字段所指向的數組:

  •         SNDRV_CTL_IOCTL_TLV_READ
  •         SNDRV_CTL_IOCTL_TLV_WRITE
  •         SNDRV_CTL_IOCTL_TLV_COMMAND
通常,tlv_array用來描述寄存器的設定值與它所代表的實際意義之間的映射關係,最常用的就是用於音量控件時,設定值與對應的dB值之間的映射關係,請看以下例子:
[cpp] view plain copy
  1. static const DECLARE_TLV_DB_SCALE(mixin_boost_tlv, 0, 900, 0);  
  2.   
  3. static const struct snd_kcontrol_new wm1811_snd_controls[] = {  
  4. SOC_SINGLE_TLV("MIXINL IN1LP Boost Volume", WM8994_INPUT_MIXER_1, 7, 1, 0,  
  5.                mixin_boost_tlv),  
  6. SOC_SINGLE_TLV("MIXINL IN1RP Boost Volume", WM8994_INPUT_MIXER_1, 8, 1, 0,  
  7.                mixin_boost_tlv),  
  8. };  
DECLARE_TLV_DB_SCALE用於定義一個dB值映射的tlv_array,上述的例子表明,該tlv的類型是SNDRV_CTL_TLVT_DB_SCALE,寄存器的最小值對應是0dB,寄存器每增加一個單位值,對應的dB數增加是9dB(0.01dB*900),而由接下來的兩組SOC_SINGLE_TLV定義可以看出,我們定義了兩個boost控件,寄存器的地址都是WM8994_INPUT_MIXER_1,控制位分別是第7bit和第8bit,最大值是1,所以,該控件只能設定兩個數值0和1,對應的dB值就是0dB和9dB。

SOC_DOUBLE    與SOC_SINGLE相對應,區別是SOC_SINGLE只控制一個變量,而SOC_DOUBLE則可以同時在一個寄存器中控制兩個相似的變量,最常用的就是用於一些立體聲的控件,我們需要同時對左右聲道進行控制,因爲多了一個聲道,參數也就相應地多了一個shift位移值,

[cpp] view plain copy
  1. #define SOC_DOUBLE(xname, reg, shift_left, shift_right, max, invert) \  
  2. {       .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname),\  
  3.         .info = snd_soc_info_volsw, .get = snd_soc_get_volsw, \  
  4.         .put = snd_soc_put_volsw, \  
  5.         .private_value = SOC_DOUBLE_VALUE(reg, shift_left, shift_right, \  
  6.                                           max, invert) }  

SOC_DOUBLE_R    與SOC_DOUBLE類似,對於左右聲道的控制寄存器不一樣的情況,使用SOC_DOUBLE_R來定義,參數中需要指定兩個寄存器地址。

SOC_DOUBLE_TLV    與SOC_SINGLE_TLV對應的立體聲版本,通常用於立體聲音量控件的定義。

SOC_DOUBLE_R_TLV    左右聲道有獨立寄存器控制的SOC_DOUBLE_TLV版本

Mixer控件


Mixer控件用於音頻通道的路由控制,由多個輸入和一個輸出組成,多個輸入可以自由地混合在一起,形成混合後的輸出:


          圖1     Mixer混音器

對於Mixer控件,我們可以認爲是多個簡單控件的組合,通常,我們會爲mixer的每個輸入端都單獨定義一個簡單控件來控制該路輸入的開啓和關閉,反應在代碼上,就是定義一個soc_kcontrol_new數組:

[cpp] view plain copy
  1. static const struct snd_kcontrol_new left_speaker_mixer[] = {  
  2. SOC_SINGLE("Input Switch", WM8993_SPEAKER_MIXER, 7, 1, 0),  
  3. SOC_SINGLE("IN1LP Switch", WM8993_SPEAKER_MIXER, 5, 1, 0),  
  4. SOC_SINGLE("Output Switch", WM8993_SPEAKER_MIXER, 3, 1, 0),  
  5. SOC_SINGLE("DAC Switch", WM8993_SPEAKER_MIXER, 6, 1, 0),  
  6. };  

以上這個mixer使用寄存器WM8993_SPEAKER_MIXER的第3,5,6,7位來分別控制4個輸入端的開啓和關閉。

Mux控件


mux控件與mixer控件類似,也是多個輸入端和一個輸出端的組合控件,與mixer控件不同的是,mux控件的多個輸入端同時只能有一個被選中。因此,mux控件所對應的寄存器,通常可以設定一段連續的數值,每個不同的數值對應不同的輸入端被打開,與上述的mixer控件不同,ASoc用soc_enum結構來描述mux控件的寄存器信息:

[cpp] view plain copy
  1. /* enumerated kcontrol */  
  2. struct soc_enum {  
  3.         unsigned short reg;  
  4.         unsigned short reg2;  
  5.         unsigned char shift_l;  
  6.         unsigned char shift_r;  
  7.         unsigned int max;  
  8.         unsigned int mask;  
  9.         const char * const *texts;  
  10.         const unsigned int *values;  
  11. };  
兩個寄存器地址和位移字段:reg,reg2,shift_l,shift_r,用於描述左右聲道的控制寄存器信息。字符串數組指針用於描述每個輸入端對應的名字,value字段則指向一個數組,該數組定義了寄存器可以選擇的值,每個值對應一個輸入端,如果value是一組連續的值,通常我們可以忽略values參數。下面我們先看看如何定義一個mux控件:

第一步,定義字符串和values數組,以下的例子因爲values是連續的,所以不用定義:

[cpp] view plain copy
  1. static const char *drc_path_text[] = {  
  2.         "ADC",  
  3.         "DAC"  
  4. };  
第二步,利用ASoc提供的輔助宏定義soc_enum結構,用於描述寄存器:

[cpp] view plain copy
  1. static const struct soc_enum drc_path =  
  2.         SOC_ENUM_SINGLE(WM8993_DRC_CONTROL_1, 14, 2, drc_path_text);  
第三步,利痛ASoc提供的輔助宏,定義soc_kcontrol_new結構,該結構最後用於註冊該mux控件:

[cpp] view plain copy
  1. static const struct snd_kcontrol_new wm8993_snd_controls[] = {  
  2. SOC_DOUBLE_TLV(......),  
  3. ......  
  4. SOC_ENUM("DRC Path", drc_path),  
  5. ......  
  6. }  
以上幾步定義了一個叫DRC PATH的mux控件,該控件具有兩個輸入選擇,分別是來自ADC和DAC,用寄存器WM8993_DRC_CONTROL_1控制。其中,soc_enum結構使用了輔助宏SOC_ENUM_SINGLE來定義,該宏的聲明如下:

[cpp] view plain copy
  1. #define SOC_ENUM_DOUBLE(xreg, xshift_l, xshift_r, xmax, xtexts) \  
  2. {       .reg = xreg, .shift_l = xshift_l, .shift_r = xshift_r, \  
  3.         .max = xmax, .texts = xtexts, \  
  4.         .mask = xmax ? roundup_pow_of_two(xmax) - 1 : 0}  
  5. #define SOC_ENUM_SINGLE(xreg, xshift, xmax, xtexts) \  
  6.         SOC_ENUM_DOUBLE(xreg, xshift, xshift, xmax, xtexts)  
定義soc_kcontrol_new結構時使用了SOC_ENUM,列出它的定義如下:

[cpp] view plain copy
  1. #define SOC_ENUM(xname, xenum) \  
  2. {       .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname,\  
  3.         .info = snd_soc_info_enum_double, \  
  4.         .get = snd_soc_get_enum_double, .put = snd_soc_put_enum_double, \  
  5.         .private_value = (unsigned long)&xenum }  
思想如此統一,依然是使用private_value字段記錄soc_enum結構,不過幾個回調函數變了,我們看看get回調對應的snd_soc_get_enum_double函數:

[cpp] view plain copy
  1. int snd_soc_get_enum_double(struct snd_kcontrol *kcontrol,  
  2.         struct snd_ctl_elem_value *ucontrol)  
  3. {  
  4.         struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);  
  5.         struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;  
  6.         unsigned int val;  
  7.   
  8.         val = snd_soc_read(codec, e->reg);  
  9.         ucontrol->value.enumerated.item[0]  
  10.                 = (val >> e->shift_l) & e->mask;  
  11.         if (e->shift_l != e->shift_r)  
  12.                 ucontrol->value.enumerated.item[1] =  
  13.                         (val >> e->shift_r) & e->mask;  
  14.   
  15.         return 0;  
  16. }  
通過info回調函數則可以獲取某個輸入端所對應的名字,其實就是從soc_enum結構的texts數組中獲得:

[cpp] view plain copy
  1. int snd_soc_info_enum_double(struct snd_kcontrol *kcontrol,  
  2.         struct snd_ctl_elem_info *uinfo)  
  3. {  
  4.         struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;  
  5.   
  6.         uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;  
  7.         uinfo->count = e->shift_l == e->shift_r ? 1 : 2;  
  8.         uinfo->value.enumerated.items = e->max;  
  9.   
  10.         if (uinfo->value.enumerated.item > e->max - 1)  
  11.                 uinfo->value.enumerated.item = e->max - 1;  
  12.         strcpy(uinfo->value.enumerated.name,  
  13.                 e->texts[uinfo->value.enumerated.item]);  
  14.         return 0;  
  15. }  

以下是另外幾個常用於定義mux控件的宏:

SOC_VALUE_ENUM_SINGLE    用於定義帶values字段的soc_enum結構。

SOC_VALUE_ENUM_DOUBLE    SOC_VALUE_ENUM_SINGLE的立體聲版本。

SOC_VALUE_ENUM    用於定義帶values字段snd_kcontrol_new結構,這個有點特別,我們還是看看它的定義:

[cpp] view plain copy
  1. #define SOC_VALUE_ENUM(xname, xenum) \  
  2. {       .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname,\  
  3.         .info = snd_soc_info_enum_double, \  
  4.         .get = snd_soc_get_value_enum_double, \  
  5.         .put = snd_soc_put_value_enum_double, \  
  6.         .private_value = (unsigned long)&xenum }  
從定義可以看出來,回調函數被換掉了,我們看看他的get回調:

[cpp] view plain copy
  1. int snd_soc_get_value_enum_double(struct snd_kcontrol *kcontrol,  
  2.         struct snd_ctl_elem_value *ucontrol)  
  3. {  
  4.         struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);  
  5.         struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;  
  6.         unsigned int reg_val, val, mux;  
  7.   
  8.         reg_val = snd_soc_read(codec, e->reg);  
  9.         val = (reg_val >> e->shift_l) & e->mask;  
  10.         for (mux = 0; mux < e->max; mux++) {  
  11.                 if (val == e->values[mux])  
  12.                         break;  
  13.         }  
  14.         ucontrol->value.enumerated.item[0] = mux;  
  15.         if (e->shift_l != e->shift_r) {  
  16.                 val = (reg_val >> e->shift_r) & e->mask;  
  17.                 for (mux = 0; mux < e->max; mux++) {  
  18.                         if (val == e->values[mux])  
  19.                                 break;  
  20.                 }  
  21.                 ucontrol->value.enumerated.item[1] = mux;  
  22.         }  
  23.   
  24.         return 0;  
  25. }  
與SOC_ENUM定義的mux不同,它沒有直接返回寄存器的設定值,而是通過soc_enum結構中的values字段做了一次轉換,與values數組中查找和寄存器相等的值,然後返回他在values數組中的索引值,所以,儘管寄存器的值可能是不連續的,但返回的值是連續的。

通常,我們還可以用以下幾個輔助宏定義soc_enum結構,其實和上面所說的沒什麼區別,只是可以偷一下懶,省掉struct soc_enum xxxx=幾個單詞而已:

  • SOC_ENUM_SINGLE_DECL    
  • SOC_ENUM_DOUBLE_DECL    
  • SOC_VALUE_ENUM_SINGLE_DECL    
  • SOC_VALUE_ENUM_DOUBLE_DECL    

其它控件


其實,除了以上介紹的幾種常用的控件,ASoc還爲我們提供了另外一些控件定義輔助宏,詳細的請讀者參考include/sound/soc.h。這裏列舉幾個:

需要自己定義get和put回調時,可以使用以下這些帶EXT的版本:

  • SOC_SINGLE_EXT    
  • SOC_DOUBLE_EXT
  • SOC_SINGLE_EXT_TLV
  • SOC_DOUBLE_EXT_TLV
  • SOC_DOUBLE_R_EXT_TLV
  • SOC_ENUM_EXT

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章