snd_kcontrol探究

轉自: http://blog.csdn.net/sepnic/article/details/6150723


control控制接口


控制接口對於許多開關(switch)和調節器(slider)應用廣泛,它能被用戶空間存取,從而讀寫CODEC相關寄存器。control的主要用於mixer。它用snd_kcontrol_new結構體描述。


snd_kcontrol_new


  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.     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. };  


iface字段定義了control的類型,形式爲SNDRV_CTL_ELEM_IFACE_XXX,對於mixer是SNDRV_CTL_ELEM_IFACE_MIXER,對於不屬於mixer的全局控制,使用CARD;如果關聯到某類設備,則是PCM、RAWMIDI、TIMER或SEQUENCER。在這裏,我們主要關注mixer。

name字段是名稱標識,這個字段非常重要,因爲control的作用由名稱來區分,對於名稱相同的control,則使用index區分。下面會詳細介紹上層應用如何根據name名稱標識來找到底層相應的control。

name定義的標準是“SOURCE DIRECTION FUNCTION”即“源 方向 功能”,SOURCE定義了control的源,如“Master”、“PCM”等;DIRECTION 則爲“Playback”、“Capture”等,如果DIRECTION忽略,意味着Playback和capture雙向;FUNCTION則可以是“Switch”、“Volume”和“Route”等。

上層也可以根據numid來找到對應的control,snd_ctl_find_id()也是優先判斷上層是否傳遞了numid,是則直接返回這個numid對應的control。用戶層設置numid和control的關聯時,可用alsa-lib的snd_mixer_selem_set_enum_item()函數。snd_kcontrol_new結構體並沒有numid這個成員,是因爲numid是系統自動管理的,原則是該control的註冊次序,保存到snd_ctl_elem_value結構體中。

access字段是訪問控制權限。SNDRV_CTL_ELEM_ACCESS_READ意味着只讀,這時put()函數不必實現;SNDRV_CTL_ELEM_ACCESS_WRITE意味着只寫,這時get()函數不必實現。若control值頻繁變化,則需定義 VOLATILE標誌。當control處於非激活狀態時,應設置INACTIVE標誌。

private_value字段包含1個長整型值,可以通過它給info()、get()和put()函數傳遞參數。


kcontrol宏


在早期的ALSA創建一個新的control需要實現snd_kcontrol_new中的info、get和put這三個成員函數。現在較新版本的ALSA均定義了一些宏,如:

  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) }   
這個宏的對象是MIXER,對寄存器reg的位偏移shift可以設置0-max的數值。

又如:

  1. #define SOC_DOUBLE_R_TLV(xname, reg_left, reg_right, xshift, xmax, xinvert, 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_2r, /    
  7.     .get = snd_soc_get_volsw_2r, .put = snd_soc_put_volsw_2r, /    
  8.     .private_value = (unsigned long)&(struct soc_mixer_control) /    
  9.         {.reg = reg_left, .rreg = reg_right, .shift = xshift, /    
  10.         .max = xmax, .invert = xinvert} }  
這個宏與剛纔類似,但是它是對兩個寄存器reg_left和reg_right進行同一操作,Codec芯片中左右聲道的寄存器配置一般來說是差不多的,這就是這個宏存在的意義。

例如我們一個Playback Volume的kcontrol接口這樣定義:

SOC_DOUBLE_R_TLV("Playback Volume", REG_VOL_L, REG_VOL_R, 0, 192, 0, digital_tlv)

我們僅僅需要將Volume寄存器地址及位偏移,最大值填進去即可,當然這些數據要從Codec的datasheet取得。這裏Volume寄存器地址是REG_VOL_L(左聲道)和REG_VOL_R(右聲道),位偏移爲0,DAC Digital Gain範圍是0-192(steps)。


觸發過程


爲了探討這些kcontrol是如何觸發的,我們以SOC_DOUBLE_R_TLV的put函數爲例說明:

  1. /**  
  2.  * snd_soc_put_volsw_2r - double mixer set callback  
  3.  * @kcontrol: mixer control  
  4.  * @ucontrol: control element information  
  5.  *  
  6.  * Callback to set the value of a double mixer control that spans 2 registers.  
  7.  *  
  8.  * Returns 0 for success.  
  9.  */    
  10. int snd_soc_put_volsw_2r(struct snd_kcontrol *kcontrol,    
  11.     struct snd_ctl_elem_value *ucontrol)    
  12. {    
  13.     struct soc_mixer_control *mc =    
  14.         (struct soc_mixer_control *)kcontrol->private_value;    
  15.     struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);    
  16.     unsigned int reg = mc->reg;    
  17.     unsigned int reg2 = mc->rreg;    
  18.     unsigned int shift = mc->shift;    
  19.     int max = mc->max;    
  20.     unsigned int mask = (1 << fls(max)) - 1;    
  21.     unsigned int invert = mc->invert;    
  22.     int err;    
  23.     unsigned int val, val2, val_mask;    
  24.     
  25.     val_mask = mask << shift;    
  26.     val = (ucontrol->value.integer.value[0] & mask);    
  27.     val2 = (ucontrol->value.integer.value[1] & mask);    
  28.      
  29.     if (invert) {    
  30.         val = max - val;    
  31.         val2 = max - val2;    
  32.     }     
  33.     
  34.     val = val << shift;    
  35.     val2 = val2 << shift;    
  36.     
  37.     err = snd_soc_update_bits(codec, reg, val_mask, val);    
  38.     if (err < 0)    
  39.         return err;    
  40.     
  41.     err = snd_soc_update_bits(codec, reg2, val_mask, val2);    
  42.     return err;    
  43. }  
struct snd_ctl_elem_value *ucontrol:從用戶層傳遞下來的,這個也可以從命名看出來(kcontrol-kernel control,ucontrol-user control);

shift是位偏移,而位掩碼mask是通過宏SOC_DOUBLE_R_TLV中的xmax運算得到:unsigned int mask = (1 << fls(max)) - 1;

調用snd_soc_update_bits()->snd_soc_write()將ucontrol的value送到CODEC的寄存器上。

snd_soc_put_volsw_2r()作爲一個callback函數,用戶層要設置某些功能時,如改變Playback Volume:

#amixer cset numid=3,iface=MIXER,name='Playback Volume' 100

注:amixer相關用法見:http://hi.baidu.com/serial_story/blog/item/c4e826d82a562f3f32fa1c31.html

補充,如果想查看當前的路由信息,可以打印;kcontrol->id.name這個就是你註冊的snd_control的具體名字


到內核層時,會遍歷一個節點類型爲struct snd_kcontrol *的鏈表,找到kcontrol.id.numid與3相匹配的kctl(這個過程見snd_ctl_find_id()函數),然後調用kctl.put()函數將100寫到Playback Volume寄存器中。當然如果上層沒有提供numid,則可根據name找到kcontrol.id.name相匹配的kctl。詳細見snd_ctl_find_id函數:

  1. /**  
  2.  * snd_ctl_find_id - find the control instance with the given id  
  3.  * @card: the card instance  
  4.  * @id: the id to search  
  5.  *  
  6.  * Finds the control instance with the given id from the card.  
  7.  *  
  8.  * Returns the pointer of the instance if found, or NULL if not.  
  9.  *  
  10.  * The caller must down card->controls_rwsem before calling this function  
  11.  * (if the race condition can happen).  
  12.  */    
  13. struct snd_kcontrol *snd_ctl_find_id(struct snd_card *card,    
  14.                      struct snd_ctl_elem_id *id)    
  15. {    
  16.     struct snd_kcontrol *kctl;    
  17.     
  18.     if (snd_BUG_ON(!card || !id))    
  19.         return NULL;    
  20.     if (id->numid != 0)    
  21.         return snd_ctl_find_numid(card, id->numid);    
  22.     list_for_each_entry(kctl, &card->controls, list) {    
  23.         if (kctl->id.iface != id->iface)    
  24.             continue;    
  25.         if (kctl->id.device != id->device)    
  26.             continue;    
  27.         if (kctl->id.subdevice != id->subdevice)    
  28.             continue;    
  29.         if (strncmp(kctl->id.name, id->name, sizeof(kctl->id.name)))    
  30.             continue;    
  31.         if (kctl->id.index > id->index)    
  32.             continue;    
  33.         if (kctl->id.index + kctl->count <= id->index)    
  34.             continue;    
  35.         return kctl;    
  36.     }    
  37.     return NULL;    
  38. }   

從上往下的大致流程:

  1. amixer-用戶層    
  2.   |->snd_ctl_ioctl-系統調用    
  3.        |->snd_ctl_elem_write_user-內核鉤子函數    
  4.             |->snd_ctl_elem_wirte-    
  5.                  |->snd_ctl_find_id-遍歷kcontrol鏈表找到name字段匹配的kctl    
  6.                  |->kctl->put()-調用kctl的成員函數put()    
  7.                       |->snd_soc_put_volsw_2r   
PS:上層如何設置kctl的numid,可參考:http://blog.csdn.net/cpuwolf/archive/2009/10/17/4686830.aspx
可以使用dump_stack();函數來查看snd_soc_put_volsw_2r ;或者snd_soc_put_volsw調用的調用流程信息。


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