2014年4月14日 星期一

DAPM - snd_soc_dapm_add_route() 與 snd_soc_dapm_path 的建立

此系列文章主要是在閱讀 http://blog.csdn.net/azloong 之後,再針對我有興趣的部分,再做深入探討.

[本篇文章重點] 希望各位能透過本篇文章了解到path (struct snd_soc_dapm_path)的建立,以及它與dapm kcontrol之間的關係,並為未來dapm觸發的流程鋪路.

在snd_soc_dapm_add_route()中會一一將route map轉換成struct snd_soc_dapm_path並新增至snd_soc_card裡的paths串列(list)裡面. 當dapm觸發時,會去走遍所有widget,並透過paths串列找出同時有連到output end point與input end point的widget,並將該widget的電源打開. 換句話說,dapm希望達到在音訊通道上的所有widget電源是開著,其餘用不到的widget電源是關閉的. 在音訊通道上,最常看到的widget型態就是非switch與mixer莫屬了. 所以接下將利用mixer來說明path的建立過程.


1:  static const struct snd_kcontrol_new wm8900_loutmix_controls[] = {  
2:  SOC_DAPM_SINGLE("LINPUT3 Bypass Switch", WM8900_REG_LOUTMIXCTL1, 7, 1, 0),  
3:  SOC_DAPM_SINGLE("AUX Bypass Switch", WM8900_REG_AUXOUT_CTL, 7, 1, 0),  
4:  SOC_DAPM_SINGLE("Left Input Mixer Switch", WM8900_REG_BYPASS1, 7, 1, 0),  
5:  SOC_DAPM_SINGLE("Right Input Mixer Switch", WM8900_REG_BYPASS2, 3, 1, 0),  
6:  SOC_DAPM_SINGLE("DACL Switch", WM8900_REG_LOUTMIXCTL1, 8, 1, 0),  
7:  };   

1:  static const struct snd_soc_dapm_widget wm8900_dapm_widgets[] = {  
2:  ...  
3:  SND_SOC_DAPM_MIXER("Left Output Mixer", WM8900_REG_POWER3, 3, 0,  
4:              wm8900_loutmix_controls,  
5:              ARRAY_SIZE(wm8900_loutmix_controls)),  
6:  ...  
7:  };  


經過一連串的宣告後會建立此關係,接下來是route map的宣告

1:  /* Target, Path, Source */  
2:  static const struct snd_soc_dapm_route wm8900_dapm_routes[] = {  
3:  ...  
4:  {"Left Output Mixer", "LINPUT3 Bypass Switch", "LINPUT3"},  
5:  {"Left Output Mixer", "AUX Bypass Switch", "AUX"},  
6:  {"Left Output Mixer", "Left Input Mixer Switch", "Left Input Mixer"},  
7:  {"Left Output Mixer", "Right Input Mixer Switch", "Right Input Mixer"},  
8:  {"Left Output Mixer", "DACL Switch", "DACL"},  
9:  ...  
10:  }  

我們可以看到routes順序是 { sink, control, source}, 所以,以這為例
{"Left Output Mixer", "LINPUT3 Bypass Switch", "LINPUT3"},
sink = "Left Output Mixer"
control = "LINPUT3 Bypass Switch"
source = "LINPUT3"
換成文字上的說明就是 audio stream 從 "LINPUT3"過來,經過 "LINPUT3 Bypass Switch", 接著流向 "Left Output Mixer"

1:  snd_soc_dapm_add_routes()  
2:   -->snd_soc_dapm_add_route() (添加一個snd_soc_dapm_mixer類型的route)  
3:      -->配置snd_soc_dapm_path記憶體,令path->source=source; path->sink=sink  
4:      -->如果sink是switch或mixer這類型的widget  
5:        -->dapm_connect_mixer(dapm, source, sink, path, control);  
6:          --> 檢查control是否也可以在sink->kcontol_news[i].name中找到  
7:             if (!strcmp(control_name, sink->kcontrol_news[i].name))  
8:          -->如果找到,則將path分別加到card->paths,sink與source widget串列中,
                    並將path的name設定成sink->kcontrol_news[i].name
9:                  list_add(&path->list, &dapm->card->paths);  
10:                 list_add(&path->list_sink, &sink->sources);  
11:                 list_add(&path->list_source, &source->sinks);  
12:                 path->name = sink->kcontrol_news[i].name;  
13:                 --> dapm_set_path_status(sink, path, i);  
14:                    -->取出 sink->kcontrol_news[i].private_value 的值,  
15:                       如果該值表示是unmute時(對於mixer來說,unmute就是代表connected),
16:                       則 path->connect = 1,反之亦然
17:      /*NOTE:為了與route map的解說有一貫性,我將souece code裡的dest換成sink;src換成了source.*/  













(為了方便說明與排版,將不會精準地畫上指標(pointer)真正指向的記憶體位置, 例如: prev指標是指向snd_soc_dapm_path->list->next開頭的記憶體位置上,而不是snd_soc_dapm_path->list->prev, 此外,像name指標是指到存到字池(String Literal Pool)裡的字串)上圖說明了snd_soc_dapm_path建立好之後都全都被加進了snd_soc_card裡的paths串列裡面.













上圖說明了sink widget也把snd_soc_dapm_path的list_sink加到自己的sources串列裡面.換句話說,widget (此時當sink角色時)會知道可以連到它的source path資訊.再回顧開頭所說的,當要判斷該widget是否有連到input end point時(is_connected_input_ep( )),那該widget的sources串列就扮演資訊來源的角色




同樣地,上圖說明了source widget也把snd_soc_dapm_path的list_source加到自己的sinks串列裡面.換句話說,widget (此時當source角色時)會知道它可以連到的sink path資訊.當要判斷該widget是否有連到output end point時(is_connected_output_ep( )),那該widget的sinks串列就扮演資訊來源的角色.

    至於snd_soc_dapm_path的long_name是如何產生的? 以及它與dapm kcontrol之間的關係是如何?這篇文章已有說明 http://blog.csdn.net/azloong/article/details/6334922. 到目前為止,都是在codec probe的時候所做的事,當audio codec probe時的audio codec register此時的值就直接影響了snd_soc_dapm_path ->connect的狀態,所以,大部分的audio codec driver都會maintain一個initial table,在執行audio codec probe時會將initial table裡的值先寫到audio codec register裡,再做kcontrol,widget與route map的註冊.之後就會依use case透過amixer去控制kcontrol而改變相對應的snd_soc_dapm_path ->connect的狀態,在http://blog.csdn.net/azloong/article/details/6428885中有說明.

    關於kcontrol, widget與route map的註冊,有些audio codec driver已經移到soc-core.c裡的snd_soc_instantiate_card( )或soc_probe_codec( )或其他相關的function去做了.
所以有些audio codec driver改成下述方式達成
1:  static struct snd_soc_codec_driver soc_codec_dev_wm8900 = {  
2:       .probe =     wm8900_probe,  
3:       .remove =     wm8900_remove,  
4:       .suspend =     wm8900_suspend,  
5:       .resume =     wm8900_resume,  
6:       .set_bias_level = wm8900_set_bias_level,  
7:       .volatile_register = wm8900_volatile_register,  
8:       .reg_cache_size = ARRAY_SIZE(wm8900_reg_defaults),  
9:       .reg_word_size = sizeof(u16),  
10:      .reg_cache_default = wm8900_reg_defaults,  
11:      .controls = wm8900_snd_controls,  
12:      .num_controls = ARRAY_SIZE(wm8900_snd_controls),  
13:      .dapm_widgets = wm8900_dapm_widgets,  
14:      .num_dapm_widgets = ARRAY_SIZE(wm8900_dapm_widgets),  
15:      .dapm_routes = wm8900_dapm_routes,  
16:      .num_dapm_routes = ARRAY_SIZE(wm8900_dapm_routes),  
17:  };  

 但是關於snd_soc_dapm_path的long_name的產生還是由snd_soc_dapm_new_widget( )來負責,在我的code base裡,它的call stack是snd_soc_register_card( )->snd_soc_instantiate_card( )-> soc_post_component_init( ) ->snd_soc_dapm_new_widget( ),它會走訪所有widget,如果是mixer或switch類型的widget就會call dapm_new_mixer( ),而dapm_new_mixer( )有兩個重要的工作,就是為連到該widget的所有path賦予long_name與建立kcontrol.

1:  snd_soc_dapm_new_widgets()  
2:       它會走訪所有widget,如果是mixer或switch類型的widget就會call   
3:       dapm_new_mixer( ) , 如果是其他型態,會call相對應的function  
4:    -->dapm_new_mixer( )  
5:       1.每個widget都有sources串列記錄著連到它的所有path的資訊,而每個  
6:        path->name都是分別指到相對應的control的name  
7:        (回顧一下: path->name = sink->kcontrol_news[i].name; )  
8:        所以,在這裡再把這關係找出來  
9:        snd_soc_dapm_mixer : path->long_name=sink->name + sink->kcontrol_news[i].name  
10:       snd_soc_dapm_mixer_named_ctl: path->long_name= sink->kcontrol_news[i].name  
11:      2. path->kcontrol = snd_soc_cnew(&sink->kcontrol_news[i],  
12:                                   sink, path->long_name, prefix);  
13:        為該path建立一個snd_kcontrol物件,而這物件保有了sink->kcontrol_news[i]的資訊. 再回顧一下,kcontrol的宣告  
14:        SOC_DAPM_SINGLE("LINPUT3 Bypass Switch", WM8900_REG_LOUTMIXCTL1, 7, 1, 0),  
15:        這暗示了該path將會控制到相對應的kcontrol的register的值 .  
16:      3. snd_ctl_add(card, path->kcontrol);  
17:        把path->kcontrol加到card (snd_card)的controls串列裡面  

經過上述步驟之後,我們就可以透過alsa_amixer來切換path. 先透過alsa_amixer controls列出所有的kcontrols
 ~ # alsa_amixer controls  
 ......省略......  
 numid=57,iface=MIXER,name='Left Output Mixer AUX Bypass Switch'  
 numid=60,iface=MIXER,name='Left Output Mixer DACL Switch'  
 numid=56,iface=MIXER,name='Left Output Mixer LINPUT3 Bypass Switch'  
 numid=58,iface=MIXER,name='Left Output Mixer Left Input Mixer Switch'  
 numid=59,iface=MIXER,name='Left Output Mixer Right Input Mixer Switch'  

再透過alsa_amixer cset去切換path的狀態
 alsa_amixer cset numid=58,iface=MIXER,name='Left Output Mixer Left Input Mixer Switch' 1   

接下來,我們看一下alsa_amix如何改變kcontrol相對應的register
1:  alsa_amixer - @user space   
2:    (alsa_amixer cset name='Left Output Mixer Left Input Mixer Switch' 1)   
3:    |-> snd_ctl_ioctl( ) cmd ==SNDRV_IOCTL_ELEM_WRITE   
4:      |-> snd_ctl_elem_write_user( )   
5:        |-> snd_ctl_elem_write( )   
6:          |-> snd_ctl_find_id( ) 透過 list_for_each_entry(kctl, &card->controls, list)  
7:            遍歷kcontrol,找到相對應的kcontrol (其中包含透過kctl->id.name的字串比較與numid的比較)    
8:            |-> kctl->put( ) 呼叫當初kcontrol宣告時,所註冊的put( )函式   
9:              |-> snd_soc_dapm_put_volsw( ) 根據user所設定的值去判斷相對   
10:                應的register是否需要 更新(透過nsd_soc_test_bits( )去檢查) 與   
11:                connect狀態, 如果需要更新   
12:               |-> dapm_mixer_update_power(widget, kcontrol, connect)   
13:                 |-> 遍歷所有path, 如果該path->kcontrol與user修改的kcontrol  
14:                   一致, 則 path->connect = connect;   
15:                   |-> dapm_power_widgets( )    
16:                     |-> dapm_generic_check_power( ) 找出是否有同時連接到  
17:                       output end point與input end point的widget, 如果有,則打  
18:                       開電源. 不過,如果現在user沒有在播音樂, audio interface   
19:                       active為 0,則不會有打開電源的情況發生, 這我們之後再討論.  

再回顧一下,當初dapm kcontrol是如何註冊的,
1:  static const struct snd_kcontrol_new wm8900_loutmix_controls[] = {  
2:  SOC_DAPM_SINGLE("LINPUT3 Bypass Switch", WM8900_REG_LOUTMIXCTL1, 7, 1, 0),  
3:  SOC_DAPM_SINGLE("AUX Bypass Switch", WM8900_REG_AUXOUT_CTL, 7, 1, 0),  
4:  SOC_DAPM_SINGLE("Left Input Mixer Switch", WM8900_REG_BYPASS1, 7, 1, 0),  
5:  SOC_DAPM_SINGLE("Right Input Mixer Switch", WM8900_REG_BYPASS2, 3, 1, 0),  
6:  SOC_DAPM_SINGLE("DACL Switch", WM8900_REG_LOUTMIXCTL1, 8, 1, 0),  
7:  };  
所以當初在註冊時,就把相對應的register給帶進去了

SOC_DAPM_SINGLE在soc-dapm.h中的定義, 我們可以看到 .put = snd_soc_dapm_put_volsw
1:  /* dapm kcontrol types */  
2:  #define SOC_DAPM_SINGLE(xname, reg, shift, max, invert) \  
3:  {     .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \  
4:       .info = snd_soc_info_volsw, \  
5:       .get = snd_soc_dapm_get_volsw, .put = snd_soc_dapm_put_volsw, \  
6:       .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert)  
7:   }  

[結論]從這篇文章,我們可以知道path是如何被建立, path與kcontrol之間的關係, user如何透過alsa_amixer控制dapm kcontrol並更新kcontrol對應的register,以及如何影響path的connect狀態.

沒有留言:

張貼留言