[本篇文章重點] 希望各位能透過本篇文章了解到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狀態.
沒有留言:
張貼留言