[文章目錄]
- route map 在 machine driver 中的功能
- SND_SOC_DAPM_SPK 如何與 DAPM 做結合
[ route map 在 machine driver 中的功能]
除了 codec driver 中會有 snd_soc_dapm_route 的宣告外, 在 machine driver 也有同樣的宣告.
1: static const struct snd_soc_dapm_widget rt5639_dapm_widgets[] = {
2: SND_SOC_DAPM_MIC("Mic Jack", NULL),
3: SND_SOC_DAPM_MIC("Headset Jack", NULL),
4: SND_SOC_DAPM_SPK("Ext Spk", NULL),
5: SND_SOC_DAPM_HP("Headphone Jack", NULL),
6: };
7: static const struct snd_soc_dapm_route audio_map[]={
8: /* Mic Jack --> MIC_IN*/
9: {"micbias1", NULL, "Mic Jack"},
10: {"MIC1", NULL, "micbias1"},
11: // HP MIC
12: {"micbias1", NULL, "Headset Jack"},
13: {"MIC3", NULL, "micbias1"},
14: {"Ext Spk", NULL, "SPOLP"},
15: {"Ext Spk", NULL, "SPOLN"},
16: {"Ext Spk", NULL, "SPORP"},
17: {"Ext Spk", NULL, "SPORN"},
18: {"Headphone Jack", NULL, "HPOL"},
19: {"Headphone Jack", NULL, "HPOR"},
20: } ;
21: static const struct snd_kcontrol_new rk_controls[] = {
22: SOC_DAPM_PIN_SWITCH("Mic Jack"),
23: SOC_DAPM_PIN_SWITCH("Headset Jack"),
24: SOC_DAPM_PIN_SWITCH("Ext Spk"),
25: SOC_DAPM_PIN_SWITCH("Headphone Jack"),
26: };
這是因為Machine driver 也要負責描述 machine 實際的配置狀況, 例如: speaker 是接到 audio codec 的 SPOL/SPOR 還是 LOUTL/LOUTR ... 等等. 藉由 snd_soc_dapm_route 的宣告就可以達到客製化的彈性. 先複習一下 route map 的宣告方式,
{ sink, control, source},
1: {"Ext Spk", NULL, "SPOLP"},
2: {"Ext Spk", NULL, "SPOLN"},
3: {"Ext Spk", NULL, "SPORP"},
4: {"Ext Spk", NULL, "SPORN"},
因為 speaker 是當 sink 的角色, 所以要放在 sink 的位置上, 又由於 speaker 是直接接到 SPOL/SPOR , 所以 control 是 "NULL" . 那我們再看一下 mic 的宣告
1: {"micbias1", NULL, "Mic Jack"},
2: {"MIC1", NULL, "micbias1"},
3: // HP MIC
4: {"micbias1", NULL, "Headset Jack"},
5: {"MIC3", NULL, "micbias1"},
同理, 因為 mic 是當 source 的角色, 所以要放在 source 位置上.
[ SND_SOC_DAPM_SPK 如何與 DAPM 做結合]
先看一下 widget 的宣告方式
1: static const struct snd_soc_dapm_widget rt3261_dapm_widgets[] = {
2: SND_SOC_DAPM_MIC("Mic Jack", NULL),
3: SND_SOC_DAPM_MIC("Headset Jack", NULL),
4: SND_SOC_DAPM_SPK("Ext Spk", NULL),
5: SND_SOC_DAPM_HP("Headphone Jack", NULL),
6: };
1: #define SND_SOC_DAPM_SPK(wname, wevent) \
2: { .id = snd_soc_dapm_spk, .name = wname, .kcontrol_news = NULL, \
3: .num_kcontrols = 0, .reg = SND_SOC_NOPM, .event = wevent, \
4: .event_flags = SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD}
這裡最重要的就是註冊了event callback function. 而這 event callback function 最常做的事就是當speaker 有外掛 amp 就可以在這做 amp enable/disable 的工作. 不過在這例子是 NULL, 因為 audio codec 本身就內建 2 w class D amp, ( codec driver 會將 這 class D amp 用 SND_SOC_DAPM_PGA_S 去註冊一個 widget, 並加到 route map 裡. 當然啦, 這要設計在 speaker 會通過的路徑上) 所以不需要額外的 callback function 去做這件事.
我們希望在 user space 可以控制 speaker 的行為, 所以要利用 kcontrol 來達到此目的.
1: static const struct snd_kcontrol_new rk_controls[] = {
2: SOC_DAPM_PIN_SWITCH("Mic Jack"),
3: SOC_DAPM_PIN_SWITCH("Headset Jack"),
4: SOC_DAPM_PIN_SWITCH("Ext Spk"),
5: SOC_DAPM_PIN_SWITCH("Headphone Jack"),
6: };
所以, 如果要只從 speaker 發出聲音
1: alsa_amixer cset iface=MIXER,name='Ext Spk Switch' 1
如果要同時從 speaker 與 headset 同時發出聲音
1: alsa_amixer cset iface=MIXER,name='Ext Spk Switch' 1
2: alsa_amixer cset iface=MIXER,name='Headphone Jack Switch' 1
那在 user space 做了這樣動作有什麼影響呢 ? 先看一下, SOC_DAPM_PIN_SWITCH 的定義
1: #define SOC_DAPM_PIN_SWITCH(xname) \
2: { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname " Switch", \
3: .info = snd_soc_dapm_info_pin_switch, \
4: .get = snd_soc_dapm_get_pin_switch, \
5: .put = snd_soc_dapm_put_pin_switch, \
6: .private_value = (unsigned long)xname }
1: snd_soc_dapm_put_pin_switch(struct snd_kcontrol *kcontrol, ... )
2: const char *pin = (const char *)kcontrol->private_value;
3: --> snd_soc_dapm_enable_pin(struct snd_soc_dapm_context *dapm, const char *pin )
4: --> snd_soc_dapm_set_pin(dapm, pin, 1);
5: struct snd_soc_dapm_widget *w = dapm_find_widget(dapm, pin, true);
6: w->connected = status;
當我們透過 alsa_amixer 向 SOC_DAPM_PIN_SWITCH 這類 kcontrol 去做設定時, 它會去搜尋跟自己同名的 widget, 並對該 widget 的 connected 屬性做設定. 換句話說, 我們對SOC_DAPM_PIN_SWITCH("Ext Spk") 這 kcontrol 做設定時, 就是相當於對SND_SOC_DAPM_SPK("Ext Spk", NULL), 這 widget 的 connected 做設定
接下來進入主題, 它是如何與 DAPM 做結合 ? 首先,在 widget 註冊時, 其 connected 屬性都被設成 1.
1: int snd_soc_dapm_new_control(struct snd_soc_dapm_context *dapm,
2: const struct snd_soc_dapm_widget *widget)
3: {
4: ...
5: /* machine layer set ups unconnected pins and insertions */
6: w->connected = 1;
7: return 0;
8: }
9: EXPORT_SYMBOL_GPL(snd_soc_dapm_new_control);
從註解得知, 如果要修改該 widget 的 connected 屬性可以從 machine layer 去著手. 在這兒提醒一下, 這邊的 connected 屬性不要跟 path 的 connect 屬性搞混囉. 我猜 widget 的 connected 屬性是為了專門 提供 SND_SOC_DAPM_SPK 這類 widget ( 直接連接到 audio codec 所以無法從 path 去控制) 所提供的設計.又由於無法從 path 上去控制, 所以在設計 route map 時, 針對這些 input end point ( snd_soc_dapm_input ) 與 output end point ( snd_soc_dapm_output ) 用 ext 屬性來標記這些 end point 外面是否還有外接 widget.
1: static int snd_soc_dapm_add_route(struct snd_soc_dapm_context *dapm,
2: const struct snd_soc_dapm_route *route)
3: {
4: ...
5: /* check for external widgets */
6: if (wsink->id == snd_soc_dapm_input) {
7: if (wsource->id == snd_soc_dapm_micbias ||
8: wsource->id == snd_soc_dapm_mic ||
9: wsource->id == snd_soc_dapm_line ||
10: wsource->id == snd_soc_dapm_output)
11: wsink->ext = 1;
12: }
13: if (wsource->id == snd_soc_dapm_output) {
14: if (wsink->id == snd_soc_dapm_spk ||
15: wsink->id == snd_soc_dapm_hp ||
16: wsink->id == snd_soc_dapm_line ||
17: wsink->id == snd_soc_dapm_input)
18: wsource->ext = 1;
19: }
20: ...
21: switch (wsink->id) {
22: ...
23: case snd_soc_dapm_hp:
24: case snd_soc_dapm_mic:
25: case snd_soc_dapm_line:
26: case snd_soc_dapm_spk:
27: list_add(&path->list, &dapm->card->paths);
28: list_add(&path->list_sink, &wsink->sources);
29: list_add(&path->list_source, &wsource->sinks);
30: path->connect = 0;
31: return 0;
32: }
33: ...
34: }
我們以 speaker 作為例子, 當 wsource->id == snd_soc_dapm_output 的情況下, wsink->id == snd_soc_dapm_spk , 那 wsource->ext = 1;
再回顧一下 audio codec driver 對於 SPOL/SPOR 的宣告
widget 的宣告
1: static const struct snd_soc_dapm_widget rt5639_dapm_widgets[] = {
2: ...
3: /* Output Lines */
4: SND_SOC_DAPM_OUTPUT("SPOLP"),
5: SND_SOC_DAPM_OUTPUT("SPOLN"),
6: SND_SOC_DAPM_OUTPUT("SPORP"),
7: SND_SOC_DAPM_OUTPUT("SPORN"),
8: ...
9: }
SND_SOC_DAPM_OUTP 的定義
1: #define SND_SOC_DAPM_OUTPUT(wname) \
2: { .id = snd_soc_dapm_output, .name = wname, .kcontrol_news = NULL, \
3: .num_kcontrols = 0, .reg = SND_SOC_NOPM }
route map 的宣告
1: {"Ext Spk", NULL, "SPOLP"},
2: {"Ext Spk", NULL, "SPOLN"},
3: {"Ext Spk", NULL, "SPORP"},
4: {"Ext Spk", NULL, "SPORN"},
所以, 當 Machine driver 在做 rout map 宣告時 ( snd_soc_dapm_add_route( ) ), 會對 wsource -> ext = 1 ( 例如:{"Ext Spk", NULL, "SPOLP"}, 那 "SPOLP" widget 的 ext 屬性會被設定成1)
在 is_connected_input_ep( ) 與 is_connected_output_ep( ) 中, 當遇到 input end pointer 與 output end pointer 就會去檢查 ext 屬性來決定是否繼續走下去, 就以 is_connected_output_ep( ) 來說, ( Line 6 ~ Line 7)如果走訪到 snd_soc_dapm_output ( 也就是 output end pointer) 它沒有 external widget ( widget->ext == 0 ) 也不是處於 suspend mode 就 return 1, 表示有連到 output end pointer. 如果該 output end pointer 有 external widget, 那它會繼續往下走 ( Line 13 ~ Line 18) 遞迴地呼叫 is_connected_output_ep( ), 所以, 輪到檢查 SND_SOC_DAPM_SPK , 如果 widget->connected 是 0, 那 return 0 ( con 預設值是 0 ), 表示沒有連到 output end pointer. 反之, 如果 widget->connected 是 1又不是處於suspend mode 就 return 1, 表示有連到 output end pointer.
1: static int is_connected_output_ep(struct snd_soc_dapm_widget *widget)
2: {
3: ...
4: if (widget->connected) {
5: /* connected pin ? */
6: if (widget->id == snd_soc_dapm_output && !widget->ext)
7: return snd_soc_dapm_suspend_check(widget);
8: /* connected jack or spk ? */
9: if (widget->id == snd_soc_dapm_hp || widget->id == snd_soc_dapm_spk ||
10: (widget->id == snd_soc_dapm_line && !list_empty(&widget->sources)))
11: return snd_soc_dapm_suspend_check(widget);
12: }
13: list_for_each_entry(path, &widget->sinks, list_source) {
14: if (path->walked)
15: continue;
16: if (path->sink && path->connect) {
17: path->walked = 1;
18: con += is_connected_output_ep(path->sink);
19: }
20: }
21: return con;
22: }
[結論]
DAPM 透過 widget->connected 來表示 SND_SOC_DAPM_SPK 這類 widget 是否有要發出聲音( 透過 kcontrol - SOC_DAPM_PIN_SWITCH 來搭配), 此外, 用 widget->ext 屬性讓 output end pointer 與 input end pointer 有外接 external widget 的可能性.