linux-alsa详解11之DAPM详解4驱动中widget初始化


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

端点类:
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

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


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM