前几篇文章我们从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