前幾篇文章我們從dapm的數據結構入手,了解了代表音頻控件的widget,代表連接路徑的route以及用於連接兩個widget的path。之前都是一些概念的講解以及對數據結構中各個字段的說明,從本章開始,我們要從代碼入手,分析dapm的詳細工作原理,主要以下幾個方面:
(1)如何注冊widget;
(2)如何連接兩個widget;
(3)一個widget的狀態裱畫如何傳遞到整個音頻路徑中。
1 dapm context
在討論widget的注冊之前,我們先了解另一個概念:dapm context,直譯過來的意思是dapm上下文,這個好像不好理解,其實我們可以這么理解:dapm把整個音頻系統,按照功能和偏置電壓級別,划分為若干個電源域,每個域包含各自的widget,每個域中的所有widget通常都處於同一個偏置電壓級別上,而一個電源域就是一個dapm context,通常會有以下幾種dapm context:
(1)屬於codec中的widget位於一個dapm context中;
(2)屬於platform的widget位於一個dapm context中;
(3)屬於整個聲卡的widget位於一個dapm context中。
對於音頻系統的硬件來說,通常要提供合適的偏置電壓才能正常地工作,有了dapm context這種組織方式,我們可以方便地對同一組widget進行統一的偏置電壓管理,ASoc用snd_soc_dapm_context結構來表示一個dapm context:
1 /* DAPM context */ 2 struct snd_soc_dapm_context { 3 enum snd_soc_bias_level bias_level; 4 unsigned int idle_bias_off:1; /* Use BIAS_OFF instead of STANDBY */ 5 /* Go to BIAS_OFF in suspend if the DAPM context is idle */ 6 unsigned int suspend_bias_off:1; 7 void (*seq_notifier)(struct snd_soc_dapm_context *, 8 enum snd_soc_dapm_type, int); 9 10 struct device *dev; /* from parent - for debug */ 11 struct snd_soc_component *component; /* parent component */ 12 struct snd_soc_card *card; /* parent card */ 13 14 /* used during DAPM updates */ 15 enum snd_soc_bias_level target_bias_level; 16 struct list_head list; 17 18 int (*stream_event)(struct snd_soc_dapm_context *dapm, int event); 19 int (*set_bias_level)(struct snd_soc_dapm_context *dapm, 20 enum snd_soc_bias_level level); 21 22 struct snd_soc_dapm_wcache path_sink_cache; 23 struct snd_soc_dapm_wcache path_source_cache; 24 25 #ifdef CONFIG_DEBUG_FS 26 struct dentry *debugfs_dapm; 27 #endif 28 }
枚舉變量snd_soc_bias_level
1 enum snd_soc_bias_level { 2 SND_SOC_BIAS_OFF = 0, 3 SND_SOC_BIAS_STANDBY = 1, 4 SND_SOC_BIAS_PREPARE = 2, 5 SND_SOC_BIAS_ON = 3, 6 };
snd_soc_dapm_context被內嵌到代表codec、platform和dai中成員結構體snd_soc_component中 :
struct snd_soc_component { ... const struct snd_soc_component_driver *driver;
.... /* Don't use these, use snd_soc_component_get_dapm() */ struct snd_soc_dapm_context dapm; const struct snd_kcontrol_new *controls; unsigned int num_controls; const struct snd_soc_dapm_widget *dapm_widgets; unsigned int num_dapm_widgets; const struct snd_soc_dapm_route *dapm_routes; unsigned int num_dapm_routes; struct snd_soc_codec *codec; ... }
結構體struct snd_soc_component_driver
/* component interface */ struct snd_soc_component_driver { const char *name; ... /* Default control and setup, added after probe() is run */ const struct snd_kcontrol_new *controls; unsigned int num_controls; const struct snd_soc_dapm_widget *dapm_widgets; unsigned int num_dapm_widgets; const struct snd_soc_dapm_route *dapm_routes; unsigned int num_dapm_routes; ... }
card(machine)結構體中是直接內嵌的snd_soc_dapm_context:
1 struct snd_soc_card { 2 ... 3 4 const struct snd_kcontrol_new *controls; 5 int num_controls; 6 7 /* 8 * Card-specific routes and widgets. 9 * Note: of_dapm_xxx for Device Tree; Otherwise for driver build-in. 10 */ 11 const struct snd_soc_dapm_widget *dapm_widgets; 12 int num_dapm_widgets; 13 const struct snd_soc_dapm_route *dapm_routes; 14 int num_dapm_routes; 15 const struct snd_soc_dapm_widget *of_dapm_widgets; 16 int num_of_dapm_widgets; 17 const struct snd_soc_dapm_route *of_dapm_routes; 18 int num_of_dapm_routes; 19 bool fully_routed; 20 21 struct work_struct deferred_resume_work; 22 23 /* lists of probed devices belonging to this card */ 24 struct list_head codec_dev_list; 25 26 struct list_head widgets; 27 struct list_head paths; 28 struct list_head dapm_list; 29 struct list_head dapm_dirty; 30 31 /* attached dynamic objects */ 32 struct list_head dobj_list; 33 34 /* Generic DAPM context for the card */ 35 struct snd_soc_dapm_context dapm; 36 struct snd_soc_dapm_stats dapm_stats; 37 struct snd_soc_dapm_update *update; 38 39 ... 40 }
代表widget結構snd_soc_dapm_widget中,有一個snd_soc_dapm_context結構指針,指向所屬的codec、platform、card、或dai的dapm結構。同時,所有的dapm結構,通過它的list字段,鏈接到代表聲卡的snd_soc_card結構的dapm_list鏈表頭字段。
2 創建和注冊widget
我們已經知道,一個widget用snd_soc_dapm_widget結構體來描述,通常,我們會根據音頻硬件的組成,分別在聲卡的codec驅動、platform驅動和machine驅動中定義一組widget,這些widget用數組進行組織,我們一般會使用dapm框架提供的大量的輔助宏來定義這些widget數組,輔助宏的說明請參考前一偏文章linux-alsa詳解10之DAPM詳解3各種widget定義。
2.1 codec中widget的注冊
我們知道,我們會通過ASoc提供的api函數snd_soc_register_codec來注冊一個codec驅動。繼續以linux-alsa詳解6 ASOC-codec /sound/soc/codecs/wm8994.c為例。
當machine中匹配codec之后會調用codec的probe函數,然后會創建和注冊widget:
1 static int wm8994_codec_probe(struct snd_soc_codec *codec) 2 { 3 struct snd_soc_dapm_context *dapm = snd_soc_codec_get_dapm(codec); 4 struct wm8994 *control = dev_get_drvdata(codec->dev->parent); 5 ... 6 switch (control->type) { 7 case WM8994: 8 snd_soc_dapm_new_controls(dapm, wm8994_specific_dapm_widgets, 9 ARRAY_SIZE(wm8994_specific_dapm_widgets)); 10 if (control->revision < 4) { 11 snd_soc_dapm_new_controls(dapm, wm8994_lateclk_revd_widgets, 12 ARRAY_SIZE(wm8994_lateclk_revd_widgets)); 13 snd_soc_dapm_new_controls(dapm, wm8994_adc_revd_widgets, 14 ARRAY_SIZE(wm8994_adc_revd_widgets)); 15 snd_soc_dapm_new_controls(dapm, wm8994_dac_revd_widgets, 16 ARRAY_SIZE(wm8994_dac_revd_widgets)); 17 } else { 18 snd_soc_dapm_new_controls(dapm, wm8994_lateclk_widgets, 19 ARRAY_SIZE(wm8994_lateclk_widgets)); 20 snd_soc_dapm_new_controls(dapm, wm8994_adc_widgets, 21 ARRAY_SIZE(wm8994_adc_widgets)); 22 snd_soc_dapm_new_controls(dapm, wm8994_dac_widgets, 23 ARRAY_SIZE(wm8994_dac_widgets)); 24 } 25 break; 26 ... 27 }
結構體struct snd_soc_dapm_widget的定義初始化如下,其他沒有貼出來,詳見code。
1 static const struct snd_soc_dapm_widget wm8994_specific_dapm_widgets[] = { 2 SND_SOC_DAPM_MUX("AIF3ADC Mux", SND_SOC_NOPM, 0, 0, &wm8994_aif3adc_mux), 3 };
2.2 platform中widget注冊
和codec驅動一樣,不再重復
2.3 card中widget注冊
在card驅動中創建和初始化widget,然后注冊card(machine)時會調用snd_soc_instantiate_card,然后注冊widget:
1 static int snd_soc_instantiate_card(struct snd_soc_card *card) 2 { 3 ... 4 if (card->dapm_widgets) 5 snd_soc_dapm_new_controls(&card->dapm, card->dapm_widgets, 6 card->num_dapm_widgets); 7 8 if (card->of_dapm_widgets) 9 snd_soc_dapm_new_controls(&card->dapm, card->of_dapm_widgets, 10 card->num_of_dapm_widgets); 11 .... 12 }
只要把定義好的widget數組和數量賦值給dapm_widgets指針和num_dapm_widgets即可,注冊聲卡使用的api:snd_soc_register_card(),也會通過snd_soc_dapm_new_controls來完成widget的創建工作。
3 注冊widget的api
以上codec、platform和machine的widget注冊最終都調用了widget注冊的api snd_soc_dapm_new_controls,定義位於:linux-4.9.73\sound\soc\soc-dapm.c
1 /** 2 * snd_soc_dapm_new_controls - create new dapm controls 3 * @dapm: DAPM context 4 * @widget: widget array 5 * @num: number of widgets 6 * 7 * Creates new DAPM controls based upon the templates. 8 * 9 * Returns 0 for success else error. 10 */ 11 int snd_soc_dapm_new_controls(struct snd_soc_dapm_context *dapm, 12 const struct snd_soc_dapm_widget *widget, 13 int num) 14 { 15 struct snd_soc_dapm_widget *w; 16 int i; 17 int ret = 0; 18 19 mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_INIT); 20 for (i = 0; i < num; i++) { 21 w = snd_soc_dapm_new_control_unlocked(dapm, widget); 22 if (IS_ERR(w)) { 23 ret = PTR_ERR(w); 24 /* Do not nag about probe deferrals */ 25 if (ret == -EPROBE_DEFER) 26 break; 27 dev_err(dapm->dev, 28 "ASoC: Failed to create DAPM control %s (%d)\n", 29 widget->name, ret); 30 break; 31 } 32 if (!w) { 33 dev_err(dapm->dev, 34 "ASoC: Failed to create DAPM control %s\n", 35 widget->name); 36 ret = -ENOMEM; 37 break; 38 } 39 widget++; 40 } 41 mutex_unlock(&dapm->card->dapm_mutex); 42 return ret; 43 }
注:這個函數只是創建widget的第一步,它為每個widget分配內存,初始化必要的字段,然后把這些widget掛在代表聲卡的snd_soc_card的widgets鏈表字段中。要使widget之間具備連接能力。
該函數只是簡單的一個循環,為傳入的widget模板數組依次調用snd_soc_dapm_new_control_unlocked函數,實際的工作由snd_soc_dapm_new_control_unlocked完成,繼續進入該函數,看看它做了那些工作。
驅動中定義的snd_soc_dapm_widget數組,只是作為一個模板,所以,snd_soc_dapm_new_control所做的第一件事,就是為該widget重新分配內存,並把模板的內容拷貝過來:
1 struct snd_soc_dapm_widget * 2 snd_soc_dapm_new_control_unlocked(struct snd_soc_dapm_context *dapm, 3 const struct snd_soc_dapm_widget *widget) 4 { 5 enum snd_soc_dapm_direction dir; 6 struct snd_soc_dapm_widget *w; 7 const char *prefix; 8 int ret; 9 10 if ((w = dapm_cnew_widget(widget)) == NULL)//由dapm_cnew_widget完成內存申請和拷貝模板的動作。接下來,根據widget的類型做不同的處理 11 return NULL; 12 /*接下來,根據widget的不同類型做不同的處理*/ 13 switch (w->id) { 14 case snd_soc_dapm_regulator_supply: 15 w->regulator = devm_regulator_get(dapm->dev, w->name);//對於snd_soc_dapm_regulator_supply類型的widget,根據widget的名稱獲取對應的regulator結構 16 if (IS_ERR(w->regulator)) { 17 ret = PTR_ERR(w->regulator); 18 if (ret == -EPROBE_DEFER) 19 return ERR_PTR(ret); 20 dev_err(dapm->dev, "ASoC: Failed to request %s: %d\n", 21 w->name, ret); 22 return NULL; 23 } 24 25 if (w->on_val & SND_SOC_DAPM_REGULATOR_BYPASS) { 26 ret = regulator_allow_bypass(w->regulator, true); 27 if (ret != 0) 28 dev_warn(w->dapm->dev, 29 "ASoC: Failed to bypass %s: %d\n", 30 w->name, ret); 31 } 32 break; 33 case snd_soc_dapm_clock_supply: 34 #ifdef CONFIG_CLKDEV_LOOKUP 35 w->clk = devm_clk_get(dapm->dev, w->name);//對於snd_soc_dapm_clock_supply類型的widget,根據widget的名稱,獲取對應的clock結構 36 if (IS_ERR(w->clk)) { 37 ret = PTR_ERR(w->clk); 38 if (ret == -EPROBE_DEFER) 39 return ERR_PTR(ret); 40 dev_err(dapm->dev, "ASoC: Failed to request %s: %d\n", 41 w->name, ret); 42 return NULL; 43 } 44 #else 45 return NULL; 46 #endif 47 break; 48 default: 49 break; 50 } 51 /*根據需要,在widget的名稱前加入必要的前綴*/ 52 prefix = soc_dapm_prefix(dapm); 53 if (prefix) 54 w->name = kasprintf(GFP_KERNEL, "%s %s", prefix, widget->name); 55 else 56 w->name = kstrdup_const(widget->name, GFP_KERNEL); 57 if (w->name == NULL) { 58 kfree(w); 59 return NULL; 60 } 61 /*然后,為不同類型的widget設置合適的power_check電源狀態回調函數,widget類型和對應的power_check回調函數設置如下表所示:*/ 62 switch (w->id) { 63 case snd_soc_dapm_mic: 64 w->is_ep = SND_SOC_DAPM_EP_SOURCE; 65 w->power_check = dapm_generic_check_power; 66 break; 67 case snd_soc_dapm_input: 68 if (!dapm->card->fully_routed) 69 w->is_ep = SND_SOC_DAPM_EP_SOURCE; 70 w->power_check = dapm_generic_check_power; 71 break; 72 case snd_soc_dapm_spk: 73 case snd_soc_dapm_hp: 74 w->is_ep = SND_SOC_DAPM_EP_SINK; 75 w->power_check = dapm_generic_check_power; 76 break; 77 case snd_soc_dapm_output: 78 if (!dapm->card->fully_routed) 79 w->is_ep = SND_SOC_DAPM_EP_SINK; 80 w->power_check = dapm_generic_check_power; 81 break; 82 case snd_soc_dapm_vmid: 83 case snd_soc_dapm_siggen: 84 w->is_ep = SND_SOC_DAPM_EP_SOURCE; 85 w->power_check = dapm_always_on_check_power; 86 break; 87 case snd_soc_dapm_sink: 88 w->is_ep = SND_SOC_DAPM_EP_SINK; 89 w->power_check = dapm_always_on_check_power; 90 break; 91 92 case snd_soc_dapm_mux: 93 case snd_soc_dapm_demux: 94 case snd_soc_dapm_switch: 95 case snd_soc_dapm_mixer: 96 case snd_soc_dapm_mixer_named_ctl: 97 case snd_soc_dapm_adc: 98 case snd_soc_dapm_aif_out: 99 case snd_soc_dapm_dac: 100 case snd_soc_dapm_aif_in: 101 case snd_soc_dapm_pga: 102 case snd_soc_dapm_out_drv: 103 case snd_soc_dapm_micbias: 104 case snd_soc_dapm_line: 105 case snd_soc_dapm_dai_link: 106 case snd_soc_dapm_dai_out: 107 case snd_soc_dapm_dai_in: 108 w->power_check = dapm_generic_check_power; 109 break; 110 case snd_soc_dapm_supply: 111 case snd_soc_dapm_regulator_supply: 112 case snd_soc_dapm_clock_supply: 113 case snd_soc_dapm_kcontrol: 114 w->is_supply = 1; 115 w->power_check = dapm_supply_check_power; 116 break; 117 default: 118 w->power_check = dapm_always_on_check_power; 119 break; 120 } 121 /*初始化widget中的鏈表*/ 122 w->dapm = dapm; 123 INIT_LIST_HEAD(&w->list);//用於鏈接到聲卡的widgets鏈表 124 INIT_LIST_HEAD(&w->dirty);//用於鏈接到聲卡的dapm_dirty鏈表 125 list_add_tail(&w->list, &dapm->card->widgets);//將此widget加入所屬的聲卡結構體中 126 127 snd_soc_dapm_for_each_direction(dir) { 128 INIT_LIST_HEAD(&w->edges[dir]); 129 w->endpoints[dir] = -1; 130 } 131 132 /* machine layer sets up unconnected pins and insertions */ 133 w->connected = 1;//最后,把widget設置為connect狀態,contect字段代表的意義如下詳述 134 return w; 135 }
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 |
端點類: |
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 |
connected字段代表着引腳的連接狀態,目前,只有以下這些widget使用connected字段:
1 snd_soc_dapm_output 2 snd_soc_dapm_input 3 snd_soc_dapm_hp 4 snd_soc_dapm_spk 5 snd_soc_dapm_line 6 snd_soc_dapm_vmid 7 snd_soc_dapm_mic 8 snd_soc_dapm_siggen
驅動程序可以使用以下這些api來設置引腳的連接狀態:
1 snd_soc_dapm_enable_pin 2 snd_soc_dapm_force_enable_pin 3 snd_soc_dapm_disable_pin 4 snd_soc_dapm_nc_pin
到此,widget已經被正確地創建並初始化,而且被掛在聲卡的widgets鏈表中,以后我們就可以通過聲卡的widgets鏈表來遍歷所有的widget,再次強調一下snd_soc_dapm_new_controls函數所完成的主要功能:
(1)為widget分配內存,並拷貝參數中傳入的在驅動中定義好的模板
(2)設置power_check回調函數,當音頻路徑發生變化時,power_check回調會被調用,用於檢查該widget的電源狀態是否需要更新。
(3)把widget掛在聲卡的widgets鏈表中
3 dai widget
上面幾節的內容介紹了codec、platform以及machine級別的widget的注冊方法,在dapm框架中,還有另外一種widget,它代表了一個dai(數字音頻接口),dai按所在的位置,又分為cpu dai和codec dai。在硬件上,通常一個cpu dai會連接一個codec dai,而在machine驅動中,我們要在snd_soc_card結構中指定一個叫做snd_soc_dai_link的結構,該結構定義了聲卡使用哪一個cpu dai和codec dai進行連接。在Asoc中,一個dai用snd_soc_dai結構來表述,其中component成員有幾個字段和dapm框架有關:
1 struct snd_soc_dai { 2 const char *name; 3 ... 4 5 struct snd_soc_dapm_widget *playback_widget; 6 struct snd_soc_dapm_widget *capture_widget; 7 8 ... 9 10 /* parent platform/codec */ 11 struct snd_soc_codec *codec; 12 struct snd_soc_component *component; 13 14 ... 15 }
1 struct snd_soc_component { 2 const char *name; 3 ... 4 5 /* Don't use these, use snd_soc_component_get_dapm() */ 6 struct snd_soc_dapm_context dapm; 7 8 const struct snd_kcontrol_new *controls; 9 unsigned int num_controls; 10 const struct snd_soc_dapm_widget *dapm_widgets; 11 unsigned int num_dapm_widgets; 12 const struct snd_soc_dapm_route *dapm_routes; 13 unsigned int num_dapm_routes; 14 struct snd_soc_codec *codec; 15 16 ... 17 }
dai由codec驅動和平台代碼中的I2S或pcm接口驅動注冊,machine驅動負責找到snd_soc_dai_link中指定的一對cpu/codec dai,並把它們進行綁定。不管是cpu dai還是codec dai,通常會同時傳輸播放和錄音的音頻流的能力,所以我們可以看到,snd_soc_dai中有兩個widget指針,分別代表播放流和錄音流。這兩個dai widget是何時創建的呢?下面我們逐一進行分析。
3.1 codec dai widget
首先,codec驅動在注冊codec時,會傳入該codec所支持的dai個數和記錄dai信息的snd_soc_dai_driver結構指針。使得ASoc把codec的dai注冊到系統中,並把這些dai都掛在全局鏈表變量dai_list中。然后在聲卡初始化函數snd_soc_instantiate_card中,會通過全局鏈表變量dai_list查找所有屬於該codec的dai,codec被machine驅動匹配后,soc_probe_codec函數會被調用,這個時候回去調用snd_soc_dapm_new_dai_widgets函數來生成該dai的播放流widget和錄音流widget:
1 static int snd_soc_instantiate_card(struct snd_soc_card *card) 2 { 3 ... 4 5 /* probe all components used by DAI links on this card */ 6 for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST; 7 order++) { 8 list_for_each_entry(rtd, &card->rtd_list, list) { 9 ret = soc_probe_link_components(card, rtd, order); 10 if (ret < 0) { 11 dev_err(card->dev, 12 "ASoC: failed to instantiate card %d\n", 13 ret); 14 goto probe_dai_err; 15 } 16 } 17 } 18 19 ... 20 }
函數soc_probe_link_component,根據codec中的component找到codec的probe函數
1 static int soc_probe_link_components(struct snd_soc_card *card, 2 struct snd_soc_pcm_runtime *rtd, 3 int order) 4 { 5 ... 6 7 /* probe the CODEC-side components */ 8 for (i = 0; i < rtd->num_codecs; i++) { 9 component = rtd->codec_dais[i]->component; 10 if (component->driver->probe_order == order) { 11 ret = soc_probe_component(card, component); 12 if (ret < 0) 13 return ret; 14 } 15 } 16 17 ... 18 }
調用codec的probe,soc_probe_component(card, component),調用snd_soc_dapm_new_dai_widgets函數來生成該dai的播放流widget和錄音流widget:
1 static int soc_probe_component(struct snd_soc_card *card, 2 struct snd_soc_component *component) 3 { 4 ... 5 list_for_each_entry(dai, &component->dai_list, list) { 6 ret = snd_soc_dapm_new_dai_widgets(dapm, dai); 7 if (ret != 0) { 8 dev_err(component->dev, 9 "Failed to create DAI widgets %d\n", ret); 10 goto err_probe; 11 } 12 } 13 14 ... 15 }
3.1.2 函數snd_soc_dapm_new_dai_widgets
1 int snd_soc_dapm_new_dai_widgets(struct snd_soc_dapm_context *dapm, 2 struct snd_soc_dai *dai) 3 { 4 struct snd_soc_dapm_widget template; 5 struct snd_soc_dapm_widget *w; 6 7 WARN_ON(dapm->dev != dai->dev); 8 9 memset(&template, 0, sizeof(template)); 10 template.reg = SND_SOC_NOPM; 11 /*創建播放 dai widget*/ 12 if (dai->driver->playback.stream_name) { 13 template.id = snd_soc_dapm_dai_in; 14 template.name = dai->driver->playback.stream_name; 15 template.sname = dai->driver->playback.stream_name; 16 17 dev_dbg(dai->dev, "ASoC: adding %s widget\n", 18 template.name); 19 20 w = snd_soc_dapm_new_control_unlocked(dapm, &template); 21 if (IS_ERR(w)) { 22 int ret = PTR_ERR(w); 23 24 /* Do not nag about probe deferrals */ 25 if (ret != -EPROBE_DEFER) 26 dev_err(dapm->dev, 27 "ASoC: Failed to create %s widget (%d)\n", 28 dai->driver->playback.stream_name, ret); 29 return ret; 30 } 31 if (!w) { 32 dev_err(dapm->dev, "ASoC: Failed to create %s widget\n", 33 dai->driver->playback.stream_name); 34 return -ENOMEM; 35 } 36 37 w->priv = dai; 38 dai->playback_widget = w; 39 } 40 /*創建錄音的dai widget*/ 41 if (dai->driver->capture.stream_name) { 42 template.id = snd_soc_dapm_dai_out; 43 template.name = dai->driver->capture.stream_name; 44 template.sname = dai->driver->capture.stream_name; 45 46 dev_dbg(dai->dev, "ASoC: adding %s widget\n", 47 template.name); 48 49 w = snd_soc_dapm_new_control_unlocked(dapm, &template); 50 if (IS_ERR(w)) { 51 int ret = PTR_ERR(w); 52 53 /* Do not nag about probe deferrals */ 54 if (ret != -EPROBE_DEFER) 55 dev_err(dapm->dev, 56 "ASoC: Failed to create %s widget (%d)\n", 57 dai->driver->playback.stream_name, ret); 58 return ret; 59 } 60 if (!w) { 61 dev_err(dapm->dev, "ASoC: Failed to create %s widget\n", 62 dai->driver->capture.stream_name); 63 return -ENOMEM; 64 } 65 66 w->priv = dai; 67 dai->capture_widget = w; 68 } 69 70 return 0; 71 }
分別為Playback和Capture創建了一個widget,widget的priv字段指向了該dai,這樣通過widget就可以找到相應的dai,並且widget的名字就是snd_soc_dai_driver結構的stream_name。
3.2 cpu dai widget
和codec dai widget一樣,cpu dai widget也發生在machine驅動匹配上相應的platform驅動之后,platform的probe函數會被調用, soc_probe_component(card, component),調用snd_soc_dapm_new_dai_widgets函數來生成該dai的播放流widget和錄音流widget,同codec dai widget。
dai widget是一條完整dapm音頻路徑的重要元素,沒有她,我們無法完成dapm的動態電源管理工作,因為它是音頻流和其他widget的紐帶。
4 端點widget
一條完整的dapm音頻路徑,必然有起點和終點,我們把位於這些起點和終點的widget稱之為端點widget。以下這些類型的widget可以成為端點widget:
codec的輸入輸出引腳:
1 snd_soc_dapm_output 2 snd_soc_dapm_input
外接的音頻設備:
1 snd_soc_dapm_hp 2 snd_soc_dapm_spk 3 snd_soc_dapm_line
音頻流 stream domain:
1 snd_soc_dapm_adc 2 snd_soc_dapm_dac 3 snd_soc_dapm_aif_out 4 snd_soc_dapm_aif_in 5 snd_soc_dapm_dai_out 6 snd_soc_dapm_dai_in
電源、時鍾和其他:
1 snd_soc_dapm_supply 2 snd_soc_dapm_regulator_supply 3 snd_soc_dapm_clock_supply 4 snd_soc_dapm_kcontrol
當聲卡上的其中一個widget的狀態發生改變時,從這個widget開始,dapm框架會向前和向后遍歷路徑上的所有widget,判斷每個widget的狀態是否需要跟着變更,到達這些端點widget就會認為它是一條完整音頻路徑的開始和結束,從而結束一次掃描動作。
參考博文:https://blog.csdn.net/DroidPhone/java/article/details/13756651