2014年5月6日 星期二

Machine driver route map 的客製化 -- 以 SND_SOC_DAPM_SPK 為例

[文章重點] 透過此文章會了解到, Machine driver 中的 SND_SOC_DAPM_SPK 是如何與 DAPM 做結合並提供客製化的功能

[文章目錄]
  1. route map 在 machine driver 中的功能
  2. 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 的可能性.