1. 程式人生 > >ALSA音效卡驅動中的DAPM詳解之五:建立widget之間的連線關係

ALSA音效卡驅動中的DAPM詳解之五:建立widget之間的連線關係

前面我們主要著重於codec、platform、machine驅動程式中如何使用和建立dapm所需要的widget,route,這些是音訊驅動開發人員必須要了解的內容,經過前幾章的介紹,我們應該知道如何在alsa音訊驅動的3大部分(codec、platform、machine)中,按照所使用的音訊硬體結構,定義出相應的widget,kcontrol,以及必要的音訊路徑,而在本章中,我們將會深入dapm的核心部分,看看各個widget之間是如何建立連線關係,形成一條完整的音訊路徑。

/*****************************************************************************************************/



/*****************************************************************************************************/

前面我們已經簡單地介紹過,驅動程式需要使用以下api函式建立widget:

  • snd_soc_dapm_new_controls
實際上,這個函式只是建立widget的第一步,它為每個widget分配記憶體,初始化必要的欄位,然後把這些widget掛在代表音效卡的snd_soc_card的widgets連結串列欄位中。要使widget之間具備連線能力,我們還需要第二個函式:
  • snd_soc_dapm_new_widgets
這個函式會根據widget的資訊,建立widget所需要的dapm kcontrol,這些dapm kcontol的狀態變化,代表著音訊路徑的變化,從而影響著各個widget的電源狀態。看到函式的名稱可能會迷惑一下,實際上,snd_soc_dapm_new_controls的作用更多地是建立widget,而snd_soc_dapm_new_widget的作用則更多地是建立widget所包含的kcontrol,所以在我看來,這兩個函式名稱應該換過來叫更好!下面我們分別介紹一下這兩個函式是如何工作的。

建立widget:snd_soc_dapm_new_controls

snd_soc_dapm_new_controls函式完成widget的建立工作,並把這些建立好的widget註冊在音效卡的widgets連結串列中,我們看看他的定義:
int snd_soc_dapm_new_controls(struct snd_soc_dapm_context *dapm,
        const struct snd_soc_dapm_widget *widget,
        int num)
{       
        ...... 
        for (i = 0; i < num; i++) {
                w = snd_soc_dapm_new_control(dapm, widget);
                if (!w) {
                        dev_err(dapm->dev,
                                "ASoC: Failed to create DAPM control %s\n",
                                widget->name);
                        ret = -ENOMEM;
                        break;
                }               
                widget++;
        }
        ......
        return ret;
}
該函式只是簡單的一個迴圈,為傳入的widget模板陣列依次呼叫snd_soc_dapm_new_control函式,實際的工作由snd_soc_dapm_new_control完成,繼續進入該函式,看看它做了那些工作。 我們之前已經說過,驅動中定義的snd_soc_dapm_widget陣列,只是作為一個模板,所以,snd_soc_dapm_new_control所做的第一件事,就是為該widget重新分配記憶體,並把模板的內容拷貝過來:
static struct snd_soc_dapm_widget *
snd_soc_dapm_new_control(struct snd_soc_dapm_context *dapm,
                         const struct snd_soc_dapm_widget *widget)
{
        struct snd_soc_dapm_widget *w;
        int ret;

        if ((w = dapm_cnew_widget(widget)) == NULL)
                return NULL;

由dapm_cnew_widget完成記憶體申請和拷貝模板的動作。接下來,根據widget的型別做不同的處理:
        switch (w->id) {
        case snd_soc_dapm_regulator_supply:
                w->regulator = devm_regulator_get(dapm->dev, w->name);
                ......

                if (w->on_val & SND_SOC_DAPM_REGULATOR_BYPASS) {
                        ret = regulator_allow_bypass(w->regulator, true);
                        ......
                }
                break;
        case snd_soc_dapm_clock_supply:
#ifdef CONFIG_CLKDEV_LOOKUP
                w->clk = devm_clk_get(dapm->dev, w->name);
                ......
#else
                return NULL;
#endif
                break;
        default:
                break;
        }
對於snd_soc_dapm_regulator_supply型別的widget,根據widget的名稱獲取對應的regulator結構,對於snd_soc_dapm_clock_supply型別的widget,根據widget的名稱,獲取對應的clock結構。接下來,根據需要,在widget的名稱前加入必要的字首:
        if (dapm->codec && dapm->codec->name_prefix)
                w->name = kasprintf(GFP_KERNEL, "%s %s",
                        dapm->codec->name_prefix, widget->name);
        else
                w->name = kasprintf(GFP_KERNEL, "%s", widget->name);
然後,為不同型別的widget設定合適的power_check電源狀態回撥函式,widget型別和對應的power_check回撥函式設定如下表所示:
widget的power_check回撥函式
widget型別 power_check回撥函式
mixer類:
snd_soc_dapm_switch
snd_soc_dapm_mixer
snd_soc_dapm_mixer_named_ctl
dapm_generic_check_power
mux類:
snd_soc_dapm_mux
snd_soc_dapm_mux
snd_soc_dapm_mux
dapm_generic_check_power
snd_soc_dapm_dai_out dapm_adc_check_power
snd_soc_dapm_dai_in dapm_dac_check_power
端點類:
snd_soc_dapm_adc
snd_soc_dapm_aif_out
snd_soc_dapm_dac
snd_soc_dapm_aif_in
snd_soc_dapm_pga
snd_soc_dapm_out_drv
snd_soc_dapm_input
snd_soc_dapm_output
snd_soc_dapm_micbias
snd_soc_dapm_spk
snd_soc_dapm_hp
snd_soc_dapm_mic
snd_soc_dapm_line
snd_soc_dapm_dai_link
dapm_generic_check_power
電源/時鐘/影子widget:
snd_soc_dapm_supply
snd_soc_dapm_regulator_supply
snd_soc_dapm_clock_supply
snd_soc_dapm_kcontrol
dapm_supply_check_power
其它型別 dapm_always_on_check_power
當音訊路徑發生變化時,power_check回撥會被呼叫,用於檢查該widget的電源狀態是否需要更新。power_check設定完成後,需要設定widget所屬的codec、platform和dapm context,幾個用於音訊路徑的連結串列也需要初始化,然後,把該widget加入到音效卡的widgets連結串列中:
        w->dapm = dapm;
        w->codec = dapm->codec;
        w->platform = dapm->platform;
        INIT_LIST_HEAD(&w->sources);
        INIT_LIST_HEAD(&w->sinks);
        INIT_LIST_HEAD(&w->list);
        INIT_LIST_HEAD(&w->dirty);
        list_add(&w->list, &dapm->card->widgets);
幾個連結串列的作用如下:
  • sources    用於連結所有連線到該widget輸入端的snd_soc_path結構
  • sinks    用於連結所有連線到該widget輸出端的snd_soc_path結構
  • list    用於連結到音效卡的widgets連結串列
  • dirty    用於連結到音效卡的dapm_dirty連結串列
最後,把widget設定為connect狀態:
        /* machine layer set ups unconnected pins and insertions */
        w->connected = 1;
        return w;
}
connected欄位代表著引腳的連線狀態,目前,只有以下這些widget使用connected欄位:
  • snd_soc_dapm_output
  • snd_soc_dapm_input
  • snd_soc_dapm_hp
  • snd_soc_dapm_spk
  • snd_soc_dapm_line
  • snd_soc_dapm_vmid
  • snd_soc_dapm_mic
  • snd_soc_dapm_siggen
驅動程式可以使用以下這些api來設定引腳的連線狀態:
  • snd_soc_dapm_enable_pin
  • snd_soc_dapm_force_enable_pin
  • snd_soc_dapm_disable_pin
  • snd_soc_dapm_nc_pin
到此,widget已經被正確地建立並初始化,而且被掛在音效卡的widgets連結串列中,以後我們就可以通過音效卡的widgets連結串列來遍歷所有的widget,再次強調一下snd_soc_dapm_new_controls函式所完成的主要功能:
  • 為widget分配記憶體,並拷貝引數中傳入的在驅動中定義好的模板
  • 設定power_check回撥函式
  • 把widget掛在音效卡的widgets連結串列中

為widget建立dapm kcontrol

定義一個widget,我們需要指定兩個很重要的內容:一個是用於控制widget的電源狀態的reg/shift等暫存器資訊,另一個是用於控制音訊路徑切換的dapm kcontrol資訊,這些dapm kcontrol有它們自己的reg/shift暫存器資訊用於切換widget的路徑連線方式。前一節的內容中,我們只是建立了widget的例項,並把它們註冊到音效卡的widgts連結串列中,但是到目前為止,包含在widget中的dapm kcontrol並沒有建立起來,dapm框架在音效卡的初始化階段,等所有的widget(包括machine、platform、codec)都建立好之後,通過snd_soc_dapm_new_widgets函式,建立widget內包含的dapm kcontrol,並初始化widget的初始電源狀態和音訊路徑的初始連線狀態。我們看看音效卡的初始化函式,都有那些初始化與dapm有關:
static int snd_soc_instantiate_card(struct snd_soc_card *card)
{
        ......
        /* card bind complete so register a sound card */
        ret = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
                        card->owner, 0, &card->snd_card);
        ......
 
        card->dapm.bias_level = SND_SOC_BIAS_OFF;
        card->dapm.dev = card->dev;
        card->dapm.card = card;
        list_add(&card->dapm.list, &card->dapm_list);

#ifdef CONFIG_DEBUG_FS
        snd_soc_dapm_debugfs_init(&card->dapm, card->debugfs_card_root);
#endif
        ......
        if (card->dapm_widgets)    /* 建立machine級別的widget  */
                snd_soc_dapm_new_controls(&card->dapm, card->dapm_widgets,
                                          card->num_dapm_widgets);
        ......
        snd_soc_dapm_link_dai_widgets(card);  /*  連線dai widget  */

        if (card->controls)    /*  建立machine級別的普通kcontrol控制元件  */
                snd_soc_add_card_controls(card, card->controls, card->num_controls);

        if (card->dapm_routes)    /*  註冊machine級別的路徑連線資訊  */
                snd_soc_dapm_add_routes(&card->dapm, card->dapm_routes,
                                        card->num_dapm_routes);
        ......

        if (card->fully_routed)    /*  如果該標誌被置位,自動把codec中沒有路徑連線資訊的引腳設定為無用widget  */
                list_for_each_entry(codec, &card->codec_dev_list, card_list)
                        snd_soc_dapm_auto_nc_codec_pins(codec);

        snd_soc_dapm_new_widgets(card);    /*初始化widget包含的dapm kcontrol、電源狀態和連線狀態*/

        ret = snd_card_register(card->snd_card);
        ......
        card->instantiated = 1;
        snd_soc_dapm_sync(&card->dapm);
        ......
        return 0;
} 
正如我新增的註釋中所示,在完成machine級別的widget和route處理之後,呼叫的snd_soc_dapm_new_widgets函式,來為所有已經註冊的widget初始化他們所包含的dapm kcontrol,並初始化widget的電源狀態和路徑連線狀態。下面我們看看snd_soc_dapm_new_widgets函式的工作過程。

snd_soc_dapm_new_widgets函式    

該函式通過音效卡的widgets連結串列,遍歷所有已經註冊了的widget,其中的new欄位用於判斷該widget是否已經執行過snd_soc_dapm_new_widgets函式,如果num_kcontrols欄位有數值,表明該widget包含有若干個dapm kcontrol,那麼就需要為這些kcontrol分配一個指標陣列,並把陣列的首地址賦值給widget的kcontrols欄位,該陣列存放著指向這些kcontrol的指標,當然現在這些都是空指標,因為實際的kcontrol現在還沒有被建立:
int snd_soc_dapm_new_widgets(struct snd_soc_card *card)
{
        ......
        list_for_each_entry(w, &card->widgets, list)
        {               
                if (w->new)     
                        continue;
                                
                if (w->num_kcontrols) {
                        w->kcontrols = kzalloc(w->num_kcontrols *
                                                sizeof(struct snd_kcontrol *),
                                                GFP_KERNEL);
                        ......
                }
接著,對幾種能影響音訊路徑的widget,建立並初始化它們所包含的dapm kcontrol:
                switch(w->id) {
                case snd_soc_dapm_switch:
                case snd_soc_dapm_mixer:
                case snd_soc_dapm_mixer_named_ctl:
                        dapm_new_mixer(w);
                        break;
                case snd_soc_dapm_mux:
                case snd_soc_dapm_virt_mux:
                case snd_soc_dapm_value_mux:
                        dapm_new_mux(w);
                        break;
                case snd_soc_dapm_pga:
                case snd_soc_dapm_out_drv:
                        dapm_new_pga(w);
                        break;
                default:
                        break;
                }
需要用到的建立函式分別是:
  • dapm_new_mixer()    對於mixer型別,用該函式建立dapm kcontrol;
  • dapm_new_mux()   對於mux型別,用該函式建立dapm kcontrol;
  • dapm_new_pga()   對於pga型別,用該函式建立dapm kcontrol;
然後,根據widget暫存器的當前值,初始化widget的電源狀態,並設定到power欄位中:
                /* Read the initial power state from the device */
                if (w->reg >= 0) {
                        val = soc_widget_read(w, w->reg) >> w->shift;
                        val &= w->mask;
                        if (val == w->on_val)
                                w->power = 1;
                }
接著,設定new欄位,表明該widget已經初始化完成,我們還要吧該widget加入到音效卡的dapm_dirty連結串列中,表明該widget的狀態發生了變化,稍後在合適的時刻,dapm框架會掃描dapm_dirty連結串列,統一處理所有已經變化的widget。為什麼要統一處理?因為dapm要控制各種widget的上下電順序,同時也是為了減少暫存器的讀寫次數(多個widget可能使用同一個暫存器):
                w->new = 1;

                dapm_mark_dirty(w, "new widget");
                dapm_debugfs_add_widget(w);
        }

最後,通過dapm_power_widgets函式,統一處理所有位於dapm_dirty連結串列上的widget的狀態改變:
        dapm_power_widgets(card, SND_SOC_DAPM_STREAM_NOP);
        ......
        return 0;
}

dapm mixer kcontrol

上一節中,我們提到,對於mixer型別的dapm kcontrol,我們會使用dapm_new_mixer來完成具體的建立工作,先看程式碼後分析:
static int dapm_new_mixer(struct snd_soc_dapm_widget *w)
{
        int i, ret;
        struct snd_soc_dapm_path *path;

        /* add kcontrol */
(1)        for (i = 0; i < w->num_kcontrols; i++) {                                
                /* match name */
(2)                list_for_each_entry(path, &w->sources, list_sink) {             
                        /* mixer/mux paths name must match control name */
(3)                        if (path->name != (char *)w->kcontrol_news[i].name)     
                                continue;

(4)                        if (w->kcontrols[i]) {                                 
                                dapm_kcontrol_add_path(w->kcontrols[i], path);
                                continue;
                        }

(5)                        ret = dapm_create_or_share_mixmux_kcontrol(w, i);      
                        if (ret < 0)
                                return ret;

(6)                        dapm_kcontrol_add_path(w->kcontrols[i], path);         
                }
        }

        return 0;
}
(1)  因為一個mixer是由多個kcontrol組成的,每個kcontrol控制著mixer的一個輸入端的開啟和關閉,所以,該函式會根據kcontrol的數量做迴圈,逐個建立對應的kcontrol。 (2)(3)  之前多次提到,widget之間使用snd_soc_path進行連線,widget的sources連結串列儲存著所有和輸入端連線的snd_soc_path結構,所以我們可以用kcontrol模板中指定的名字來匹配對應的snd_soc_path結構。 (4)  因為一個輸入腳可能會連線多個輸入源,所以可能在上一個輸入源的path關聯時已經建立了這個kcontrol,所以這裡判斷kcontrols指標陣列中對應索引中的指標值,如果已經賦值,說明kcontrol已經在之前建立好了,所以我們只要簡單地把連線該輸入端的path加入到kcontrol的path_list連結串列中,並且增加一個虛擬的影子widget,該影子widget連線和輸入端對應的源widget,因為使用了kcontrol本身的reg/shift等暫存器資訊,所以實際上控制的是該kcontrol的開和關,這個影子widget只有在kcontrol的autodisable欄位被設定的情況下才會被建立,該特性使得source的關閉時,與之連線的mixer的輸入端也可以自動關閉,這個特性通過dapm_kcontrol_add_path來實現這一點:
static void dapm_kcontrol_add_path(const struct snd_kcontrol *kcontrol,
        struct snd_soc_dapm_path *path)
{
        struct dapm_kcontrol_data *data = snd_kcontrol_chip(kcontrol);
        /*  把kcontrol連線的path加入到paths連結串列中  */
        /*  paths連結串列所在的dapm_kcontrol_data結構會儲存在kcontrol的private_data欄位中  */
        list_add_tail(&path->list_kcontrol, &data->paths);

        if (data->widget) {
                snd_soc_dapm_add_path(data->widget->dapm, data->widget,
                    path->source, NULL, NULL);
        }
}

(5)  如果kcontrol之前沒有被建立,則通過dapm_create_or_share_mixmux_kcontrol建立這個輸入端的kcontrol,同理,kcontrol對應的影子widget也會通過dapm_kcontrol_add_path判斷是否需要建立。

dapm mux kcontrol

因為一個widget最多隻會包含一個mux型別的damp kcontrol,所以他的建立方法稍有不同,dapm框架使用dapm_new_mux函式來建立mux型別的dapm kcontrol:
static int dapm_new_mux(struct snd_soc_dapm_widget *w)
{       
        struct snd_soc_dapm_context *dapm = w->dapm;
        struct snd_soc_dapm_path *path;
        int ret;
        
(1)     if (w->num_kcontrols != 1) {
                dev_err(dapm->dev,
                        "ASoC: mux %s has incorrect number of controls\n",
                        w->name);
                return -EINVAL;
        }

        if (list_empty(&w->sources)) {
                dev_err(dapm->dev, "ASoC: mux %s has no paths\n", w->name);
                return -EINVAL;
        }

(2)     ret = dapm_create_or_share_mixmux_kcontrol(w, 0);
        if (ret < 0)
                return ret;
(3)       list_for_each_entry(path, &w->sources, list_sink)
                dapm_kcontrol_add_path(w->kcontrols[0], path);
        return 0;
}


(1)  對於mux型別的widget,因為只會有一個kcontrol,所以在這裡做一下判斷。 (2)  同樣地,和mixer型別一樣,也使用dapm_create_or_share_mixmux_kcontrol來建立這個kcontrol。 (3)  對每個輸入端所連線的path都加入dapm_kcontrol_data結構的paths連結串列中,並且建立一個影子widget,用於支援autodisable特性。

dapm pga kcontrol

目前對於pga型別的widget,kcontrol的建立函式是個空函式,所以我們不用太關注它:
static int dapm_new_pga(struct snd_soc_dapm_widget *w)
{
        if (w->num_kcontrols)
                dev_err(w->dapm->dev,
                        "ASoC: PGA controls not supported: '%s'\n", w->name);

        return 0;
}

dapm_create_or_share_mixmux_kcontrol函式

上面所說的mixer型別和mux型別的widget,在建立他們所包含的dapm kcontrol時,最後其實都是使用了dapm_create_or_share_mixmux_kcontrol函式來完成建立工作的,所以在這裡我們有必要分析一下這個函式的工作原理。這個函式中有很大一部分程式碼實在處理kcontrol的名字是否要加入codec的字首,我們會忽略這部分的程式碼,感興趣的讀者可以自己檢視核心的程式碼,路徑在:sound/soc/soc-dapm.c中,簡化後的程式碼如下:
static int dapm_create_or_share_mixmux_kcontrol(struct snd_soc_dapm_widget *w,
        int kci)
{
          ......
(1)       shared = dapm_is_shared_kcontrol(dapm, w, &w->kcontrol_news[kci],
                                         &kcontrol);
   
(2)       if (!kcontrol) {
(3)            kcontrol = snd_soc_cnew(&w->kcontrol_news[kci], NULL, name,prefix);
               ......
               kcontrol->private_free = dapm_kcontrol_free;
(4)            ret = dapm_kcontrol_data_alloc(w, kcontrol);
                ......
(5)            ret = snd_ctl_add(card, kcontrol);
                ......
        }
(6)     ret = dapm_kcontrol_add_widget(kcontrol, w);
        ......
(7)     w->kcontrols[kci] = kcontrol;
        return 0;
}

(1)  為了節省記憶體,通過kcontrol名字的匹配查詢,如果這個kcontrol已經在其他widget中已經建立好了,那我們不再建立,dapm_is_shared_kcontrol的引數kcontrol會返回已經建立好的kcontrol的指標。 (2)  如果kcontrol指標被賦值,說明在(1)中查詢到了其他widget中同名的kcontrol,我們不用再次建立,只要共享該kcontrol即可。
(3)  標準的kcontrol建立函式,請參看:Linux ALSA音效卡驅動之四:Control裝置的建立中的“建立control“一節的內容。 (4)  如果widget支援autodisable特性,建立與該kcontrol所對應的影子widget,該影子widget的型別是:snd_soc_dapm_kcontrol。 (5)  標準的kcontrol建立函式,請參看:Linux ALSA音效卡驅動之四:Control裝置的建立中的“建立control“一節的內容。
(6)  把所有共享該kcontrol的影子widget(snd_soc_dapm_kcontrol),加入到kcontrol的private_data欄位所指向的dapm_kcontrol_data結構中。 (7)  把建立好的kcontrol指標賦值到widget的kcontrols陣列中。 需要注意的是,如果kcontol支援autodisable特性,一旦kcontrol由於source的關閉而被自動關閉,則使用者空間只能操作該kcontrol的cache值,只有該kcontrol再次開啟時,該cache值才會被真正地更新到暫存器中。
現在。我們總結一下,建立一個widget所包含的kcontrol所做的工作:
  • 迴圈每一個輸入端,為每個輸入端依次執行下面的一系列操作
  • 為每個輸入端建立一個kcontrol,能共享的則直接使用建立好的kcontrol
  • kcontrol的private_data欄位儲存著這些共享widget的資訊
  • 如果支援autodisable特性,每個輸入端還要額外地建立一個虛擬的snd_soc_dapm_kcontrol型別的影子widget,該影子widget也記錄在private_data欄位中
  • 建立好的kcontrol會依次存放在widget的kcontrols陣列中,供路徑的控制和匹配之用。

為widget建立連線關係

如果widget之間沒有連線關係,dapm就無法實現動態的電源管理工作,正是widget之間有了連結關係,這些連線關係形成了一條所謂的完成的音訊路徑,dapm可以順著這條路徑,統一控制路徑上所有widget的電源狀態,前面我們已經知道,widget之間是使用snd_soc_path結構進行連線的,驅動要做的是定義一個snd_soc_route結構陣列,該陣列的每個條目描述了目的widget的和源widget的名稱,以及控制這個連線的kcontrol的名稱,最終,驅動程式使用api函式snd_soc_dapm_add_routes來註冊這些連線資訊,接下來我們就是要分析該函式的具體實現方式:
int snd_soc_dapm_add_routes(struct snd_soc_dapm_context *dapm,
                            const struct snd_soc_dapm_route *route, int num)
{
        int i, r, ret = 0;

        mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_INIT);
        for (i = 0; i < num; i++) {
                r = snd_soc_dapm_add_route(dapm, route);
                ......
                route++;
        }
        mutex_unlock(&dapm->card->dapm_mutex);

        return ret;
}
該函式只是一個迴圈,依次對引數傳入的陣列呼叫snd_soc_dapm_add_route,主要的工作由snd_soc_dapm_add_route完成。我們進入snd_soc_dapm_add_route函式看看:
static int snd_soc_dapm_add_route(struct snd_soc_dapm_context *dapm,
                                  const struct snd_soc_dapm_route *route)
{
        struct snd_soc_dapm_widget *wsource = NULL, *wsink = NULL, *w;
        struct snd_soc_dapm_widget *wtsource = NULL, *wtsink = NULL;
        const char *sink;
        const char *source;
        ......
        list_for_each_entry(w, &dapm->card->widgets, list) {
                if (!wsink && !(strcmp(w->name, sink))) {
                        wtsink = w;
                        if (w->dapm == dapm)
                                wsink = w;
                        continue;
                }
                if (!wsource && !(strcmp(w->name, source))) {
                        wtsource = w;
                        if (w->dapm == dapm)
                                wsource = w;
                }
        }
上面的程式碼我再次省略了關於名稱字首的處理部分。我們可以看到,用widget的名字來比較,遍歷音效卡的widgets連結串列,找出源widget和目的widget的指標,這段程式碼雖然正確,但我總感覺少了一個判斷退出迴圈的條件,如果連結串列的開頭就找到了兩個widget,還是要遍歷整個連結串列才結束迴圈,好浪費時間。
下面,如果在本dapm context中沒有找到,則使用別的dapm context中找到的widget:
        if (!wsink)
                wsink = wtsink;
        if (!wsource)
                wsource = wtsource;
最後,使用來增加一條連線資訊:
        ret = snd_soc_dapm_add_path(dapm, wsource, wsink, route->control,
                route->connected);
        ......

        return 0;
}
snd_soc_dapm_add_path函式是整個呼叫鏈條中的關鍵,我們來分析一下:
static int snd_soc_dapm_add_path(struct snd_soc_dapm_context *dapm,
        struct snd_soc_dapm_widget *wsource, struct snd_soc_dapm_widget *wsink,
        const char *control,
        int (*connected)(struct snd_soc_dapm_widget *source,
                         struct snd_soc_dapm_widget *sink))
{
        struct snd_soc_dapm_path *path;
        int ret;

        path = kzalloc(sizeof(struct snd_soc_dapm_path), GFP_KERNEL);
        if (!path)
                return -ENOMEM;

        path->source = wsource;
        path->sink = wsink;
        path->connected = connected;
        INIT_LIST_HEAD(&path->list);
        INIT_LIST_HEAD(&path->list_kcontrol);
        INIT_LIST_HEAD(&path->list_source);
        INIT_LIST_HEAD(&path->list_sink);
函式的一開始,首先為這個連線分配了一個snd_soc_path結構,path的source和sink欄位分別指向源widget和目的widget,connected欄位儲存connected回撥函式,初始化幾個snd_soc_path結構中的幾個連結串列。
/* check for external widgets */
        if (wsink->id == snd_soc_dapm_input) {
                if (wsource->id == snd_soc_dapm_micbias ||
                        wsource->id == snd_soc_dapm_mic ||
                        wsource->id == snd_soc_dapm_line ||
                        wsource->id == snd_soc_dapm_output)
                        wsink->ext = 1;
        }
        if (wsource->id == snd_soc_dapm_output) {
                if (wsink->id == snd_soc_dapm_spk ||
                        wsink->id == snd_soc_dapm_hp ||
                        wsink->id == snd_soc_dapm_line ||
                        wsink->id == snd_soc_dapm_input)
                        wsource->ext = 1;
        }
這段程式碼用於判斷是否有外部連線關係,如果有,置位widget的ext欄位。判斷方法從程式碼中可以方便地看出:
  • 目的widget是一個輸入腳,如果源widget是mic、line、micbias或output,則認為目的widget具有外部連線關係。
  • 源widget是一個輸出腳,如果目的widget是spk、hp、line或input,則認為源widget具有外部連線關係。
        dapm_mark_dirty(wsource, "Route added");
        dapm_mark_dirty(wsink, "Route added");

        /* connect static paths */
        if (control == NULL) {
                list_add(&path->list, &dapm->card->paths);
                list_add(&path->list_sink, &wsink->sources);
                list_add(&path->list_source, &wsource->sinks);
                path->connect = 1;
                return 0;
        }
因為增加了連結關係,所以把源widget和目的widget加入到dapm_dirty連結串列中。如果沒有kcontrol來控制該連線關係,則這是一個靜態連線,直接用path把它們連線在一起。在接著往下看:
        /* connect dynamic paths */
        switch (wsink->id) {
        case snd_soc_dapm_adc:
        case snd_soc_dapm_dac:
        case snd_soc_dapm_pga:
        case snd_soc_dapm_out_drv:
        case snd_soc_dapm_input:
        case snd_soc_dapm_output:
        case snd_soc_dapm_siggen:
        case snd_soc_dapm_micbias:
        case snd_soc_dapm_vmid:
        case snd_soc_dapm_pre:
        case snd_soc_dapm_post:
        case snd_soc_dapm_supply:
        case snd_soc_dapm_regulator_supply:
        case snd_soc_dapm_clock_supply:
        case snd_soc_dapm_aif_in:
        case snd_soc_dapm_aif_out:
        case snd_soc_dapm_dai_in:
        case snd_soc_dapm_dai_out:
        case snd_soc_dapm_dai_link:
        case snd_soc_dapm_kcontrol:
                list_add(&path->list, &dapm->card->paths);
                list_add(&path->list_sink, &wsink->sources);
                list_add(&path->list_source, &wsource->sinks);
                path->connect = 1;
                return 0;
按照目的widget來判斷,如果屬於以上這些型別,直接把它們連線在一起即可,這段感覺有點多餘,因為通常以上這些型別的widget本來也沒有kcontrol,直接用上一段程式碼就可以了,也許是dapm的作者們想著以後可能會有所擴充套件吧。
        case snd_soc_dapm_mux:
        case snd_soc_dapm_virt_mux:
        case snd_soc_dapm_value_mux:
                ret = dapm_connect_mux(dapm, wsource, wsink, path, control,
                        &wsink->kcontrol_news[0]);
                if (ret != 0)
                        goto err;
                break;
        case snd_soc_dapm_switch:
        case snd_soc_dapm_mixer:
        case snd_soc_dapm_mixer_named_ctl:
                ret = dapm_connect_mixer(dapm, wsource, wsink, path, control);
                if (ret != 0)
                        goto err;
                break;
目的widget如果是mixer和mux型別,分別用dapm_connect_mixer和dapm_connect_mux函式完成連線工作,這兩個函式我們後面再講。
        case snd_soc_dapm_hp:
        case snd_soc_dapm_mic:
        case snd_soc_dapm_line:
        case snd_soc_dapm_spk:
                list_add(&path->list, &dapm->card->paths);
                list_add(&path->list_sink, &wsink->sources);
                list_add(&path->list_source, &wsource->sinks);
                path->connect = 0;
                return 0;
        }

        return 0;
err:
        kfree(path);
        return ret;
}
hp、mic、line和spk這幾種widget屬於外部器件,也只是簡單地連線在一起,不過connect欄位預設為是未連線狀態。 現在,我們回過頭來看看目的widget是mixer和mux這兩種型別時的連線方式: dapm_connect_mixer  用該函式連線一個目的widget為mixer型別的所有輸入端:
static int dapm_connect_mixer(struct snd_soc_dapm_context *dapm,
        struct snd_soc_dapm_widget *src, struct snd_soc_dapm_widget *dest,
        struct snd_soc_dapm_path *path, const char *control_name)
{
        int i;

        /* search for mixer kcontrol */
        for (i = 0; i < dest->num_kcontrols; i++) {
                if (!strcmp(control_name, dest->kcontrol_news[i].name)) {
                        list_add(&path->list, &dapm->card->paths);
                        list_add(&path->list_sink, &dest->sources);
                        list_add(&path->list_source, &src->sinks);
                        path->name = dest->kcontrol_news[i].name;
                        dapm_set_path_status(dest, path, i);
                        return 0;
                }
        }
        return -ENODEV;
}
用需要用來連線的kcontrol的名字,和目的widget中的kcontrol模板陣列中的名字相比較,找出該kcontrol在widget中的編號,path的名字設定為該kcontrol的名字,然後用dapm_set_path_status函式來初始化該輸入端的連線狀態。連線兩個widget的連結串列操作和其他widget是一樣的。

dapm_connect_mux 用該函式連線一個目的widget是mux型別的所有輸入端:
static int dapm_connect_mux(struct snd_soc_dapm_context *dapm,
        struct snd_soc_dapm_widget *src, struct snd_soc_dapm_widget *dest,
        struct snd_soc_dapm_path *path, const char *control_name,
        const struct snd_kcontrol_new *kcontrol)
{
        struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
        int i;

        for (i = 0; i < e->max; i++) {
                if (!(strcmp(control_name, e->texts[i]))) {
                        list_add(&path->list, &dapm->card->paths);
                        list_add(&path->list_sink, &dest->sources);
                        list_add(&path->list_source, &src->sinks);
                        path->name = (char*)e->texts[i];
                        dapm_set_path_status(dest, path, 0);
                        return 0;
                }
        }

        return -ENODEV;
}
和mixer型別一樣用名字進行匹配,只不過mux型別的kcontrol只需一個,所以要通過private_value欄位所指向的soc_enum結構找出匹配的輸入腳編號,最後也是通過dapm_set_path_status函式來初始化該輸入端的連線狀態,因為只有一個kcontrol,所以第三個引數是0。連線兩個widget的連結串列操作和其他widget也是一樣的。
dapm_set_path_status    該函式根據傳入widget中的kcontrol編號,讀取實際暫存器的值,根據暫存器的值來初始化這個path是否處於連線狀態,詳細的程式碼這裡就不貼了。
當widget之間通過path進行連線之後,他們之間的關係就如下圖所示: widget通過path連線



到這裡為止,我們為音效卡建立並初始化好了所需的widget,各個widget也通過path連線在了一起,接下來,dapm等待使用者的指令,一旦某個dapm kcontrol被使用者空間改變,利用這些連線關係,dapm會重新建立音訊路徑,脫離音訊路徑的widget會被下電,加入音訊路徑的widget會被上電,所有的上下電動作都會自動完成,使用者空間的應用程式無需關注這些變化,它只管按需要改變某個dapm kcontrol即可。