在上一篇文章中,我们重点介绍了widget、path、route之间的关系及其widget的注册;
http://www.cnblogs.com/linhaostudy/p/8509899.html
在最后一章中,我们已经简单介绍了snd_soc_dapm_new_controls函数用来创建widget。
实际上,这个函数只是创建widget的第一步,它为每一个widget分配内存,初始化;
要使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,所以在我看来,这两个函数名称应该换过来叫更好!下面我们分别介绍一下这两个函数是如何工作的。
snd_soc_dapm_new_controls函数完成widget的创建工作,并把这些创建好的widget注册在声卡的widgets链表中,我们看看他的定义:
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(dapm, widget);
22 if (!w) {
23 dev_err(dapm->dev,
24 "ASoC: Failed to create DAPM control %s\n",
25 widget->name);
26 ret = -ENOMEM;
27 break;
28 }
29 widget++;
30 }
31 mutex_unlock(&dapm->card->dapm_mutex);
32 return ret;
33 }
该函数只是简单的一个循环,为传入的widget模板数组依次调用snd_soc_dapm_new_control函数,实际的工作由snd_soc_dapm_new_control完成,继续进入该函数,看看它做了那些工作。
我们之前已经说过,驱动中定义的snd_soc_dapm_widget数组,只是作为一个模板,所以,snd_soc_dapm_new_control所做的第一件事,就是为该widget重新分配内存,并把模板的内容拷贝过来:
1 static struct snd_soc_dapm_widget *
2 snd_soc_dapm_new_control(struct snd_soc_dapm_context *dapm,
3 const struct snd_soc_dapm_widget *widget)
4 {
5 struct snd_soc_dapm_widget *w;
6 int ret;
7
8 if ((w = dapm_cnew_widget(widget)) == NULL)
9 return NULL;
由dapm_cnew_widget完成内存申请和拷贝模板的动作。接下来,根据widget的类型做不同的处理:
1 switch (w->id) {
2 case snd_soc_dapm_regulator_supply:
3 w->regulator = devm_regulator_get(dapm->dev, w->name);
4 ......
5
6 if (w->on_val & SND_SOC_DAPM_REGULATOR_BYPASS) {
7 ret = regulator_allow_bypass(w->regulator, true);
8 ......
9 }
10 break;
11 case snd_soc_dapm_clock_supply:
12 #ifdef CONFIG_CLKDEV_LOOKUP
13 w->clk = devm_clk_get(dapm->dev, w->name);
14 ......
15 #else
16 return NULL;
17 #endif
18 break;
19 default:
20 break;
21 }
对于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_switchsnd_soc_dapm_mixersnd_soc_dapm_mixer_named_ctl | dapm_generic_check_power |
mux类:snd_soc_dapm_muxsnd_soc_dapm_muxsnd_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_adcsnd_soc_dapm_aif_outsnd_soc_dapm_dacsnd_soc_dapm_aif_insnd_soc_dapm_pgasnd_soc_dapm_out_drvsnd_soc_dapm_inputsnd_soc_dapm_outputsnd_soc_dapm_micbiassnd_soc_dapm_spksnd_soc_dapm_hpsnd_soc_dapm_micsnd_soc_dapm_linesnd_soc_dapm_dai_link | dapm_generic_check_power |
电源/时钟/影子widget:snd_soc_dapm_supplysnd_soc_dapm_regulator_supplysnd_soc_dapm_clock_supplysnd_soc_dapm_kcontrol | dapm_supply_check_power |
其它类型 | dapm_always_on_check_power |
当音频路径发生变化时,power_check回调会被调用,用于检查该widget的电源状态是否需要更新。power_check设置完成后,需要设置widget所属的codec、platform和context,几个用于音频路径的链表也需要初始化,然后,把该widget加入到声卡的widgets链表中:
1 w->dapm = dapm;
2 w->codec = dapm->codec;
3 w->platform = dapm->platform;
4 INIT_LIST_HEAD(&w->sources);
5 INIT_LIST_HEAD(&w->sinks);
6 INIT_LIST_HEAD(&w->list);
7 INIT_LIST_HEAD(&w->dirty);
8 list_add(&w->list, &dapm->card->widgets);
几个链表的作用如下:
最后,把widget设置为connect状态:
1 /* machine layer set ups unconnected pins and insertions */
2 w->connected = 1;
3 return w;
4 }
connected字段代表着引脚的连接状态,目前,只有以下这些widget使用connected字段:
驱动程序可以使用以下这些api来设置引脚的连接状态:
到此,widget已经被正确地创建并初始化,而且被挂在声卡的widgets链表中,以后我们就可以通过声卡的widgets链表来遍历所有的widget,再次强调一下snd_soc_dapm_new_controls函数所完成的主要功能:
定义一个widget,我们需要指定两个很重要的内容:一个是用于控制widget的电源状态的reg/shift等寄存器信息,另一个是用于控制音频路径切换的dapm kcontrol信息,这些dapm kcontrol有它们自己的reg/shift寄存器信息用于切换widget的路径连接方式。
1 static int snd_soc_instantiate_card(struct snd_soc_card *card)
2 {
3 ......
4 /* card bind complete so register a sound card */
5 ret = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
6 card->owner, 0, &card->snd_card);
7 ......
8
9 card->dapm.bias_level = SND_SOC_BIAS_OFF;
10 card->dapm.dev = card->dev;
11 card->dapm.card = card;
12 list_add(&card->dapm.list, &card->dapm_list);
13
14 #ifdef CONFIG_DEBUG_FS
15 snd_soc_dapm_debugfs_init(&card->dapm, card->debugfs_card_root);
16 #endif
17 ......
18 if (card->dapm_widgets) /* 创建machine级别的widget */
19 snd_soc_dapm_new_controls(&card->dapm, card->dapm_widgets,
20 card->num_dapm_widgets);
21 ......
22 snd_soc_dapm_link_dai_widgets(card); /* 连接dai widget */
23
24 if (card->controls) /* 建立machine级别的普通kcontrol控件 */
25 snd_soc_add_card_controls(card, card->controls, card->num_controls);
26
27 if (card->dapm_routes) /* 注册machine级别的路径连接信息 */
28 snd_soc_dapm_add_routes(&card->dapm, card->dapm_routes,
29 card->num_dapm_routes);
30 ......
31
32 if (card->fully_routed) /* 如果该标志被置位,自动把codec中没有路径连接信息的引脚设置为无用widget */
33 list_for_each_entry(codec, &card->codec_dev_list, card_list)
34 snd_soc_dapm_auto_nc_codec_pins(codec);
35
36 snd_soc_dapm_new_widgets(card); /*初始化widget包含的dapm kcontrol、电源状态和连接状态*/
37
38 ret = snd_card_register(card->snd_card);
39 ......
40 card->instantiated = 1;
41 snd_soc_dapm_sync(&card->dapm);
42 ......
43 return 0;
44 }
正如我添加的注释中所示,在完成machine级别的widget和route处理之后,调用的snd_soc_dapm_new_widgets函数,来为所有已经注册的widget初始化他们所包含的dapm kcontrol,并初始化widget的电源状态和路径连接状态。下面我们看看snd_soc_dapm_new_widgets函数的工作过程。
1 int snd_soc_dapm_new_widgets(struct snd_soc_card *card)
2 {
3 ......
4 list_for_each_entry(w, &card->widgets, list)
5 {
6 if (w->new)
7 continue;
8
9 if (w->num_kcontrols) {
10 w->kcontrols = kzalloc(w->num_kcontrols *
11 sizeof(struct snd_kcontrol *),
12 GFP_KERNEL);
13 ......
14 }
接着,对几种能影响音频路径的widget,创建并初始化它们所包含的dapm kcontrol:
1 switch(w->id) {
2 case snd_soc_dapm_switch:
3 case snd_soc_dapm_mixer:
4 case snd_soc_dapm_mixer_named_ctl:
5 dapm_new_mixer(w);
6 break;
7 case snd_soc_dapm_mux:
8 case snd_soc_dapm_virt_mux:
9 case snd_soc_dapm_value_mux:
10 dapm_new_mux(w);
11 break;
12 case snd_soc_dapm_pga:
13 case snd_soc_dapm_out_drv:
14 dapm_new_pga(w);
15 break;
16 default:
17 break;
18 }
需要用到的创建函数分别是:
接着,设置new字段,表明该widget已经初始化完成,我们还要把该widget加入到声卡的dapm_dirty链表中,表明该widget的状态发生了变化,稍后在合适的时刻,dapm框架会扫描dapm_dirty链表,统一处理所有已经变化的widget。为什么要统一处理?因为dapm要控制各种widget的上下电顺序,同时也是为了减少寄存器的读写次数(多个widget可能使用同一个寄存器):
1 w->new = 1;
2
3 dapm_mark_dirty(w, "new widget");
4 dapm_debugfs_add_widget(w);
5 }
最后,通过dapm_power_widgets函数,统一处理所有位于dapm_dirty链表上的widget的状态改变:
1 dapm_power_widgets(card, SND_SOC_DAPM_STREAM_NOP);
2 ......
3 return 0;
如果widget之间没有连接关系,dapm就无法实现动态的电源管理工作,正是widget之间有了连结关系,这些连接关系形成了一条所谓的完成的音频路径,dapm可以顺着这条路径,统一控制路径上所有widget的电源状态,前面我们已经知道,widget之间是使用snd_soc_path结构进行连接的,驱动要做的是定义一个snd_soc_route结构数组,该数组的每个条目描述了目的widget的和源widget的名称,以及控制这个连接的kcontrol的名称,最终,驱动程序使用api函数snd_soc_dapm_add_routes来注册这些连接信息,接下来我们就是要分析该函数的具体实现方式:
1 int snd_soc_dapm_add_routes(struct snd_soc_dapm_context *dapm,
2 const struct snd_soc_dapm_route *route, int num)
3 {
4 int i, r, ret = 0;
5
6 mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_INIT);
7 for (i = 0; i < num; i++) {
8 r = snd_soc_dapm_add_route(dapm, route);
9 ......
10 route++;
11 }
12 mutex_unlock(&dapm->card->dapm_mutex);
13
14 return ret;
15 }
该函数只是一个循环,依次对参数传入的数组调用snd_soc_dapm_add_route,主要的工作由snd_soc_dapm_add_route完成。我们进入snd_soc_dapm_add_route函数看看:
1 static int snd_soc_dapm_add_route(struct snd_soc_dapm_context *dapm,
2 const struct snd_soc_dapm_route *route)
3 {
4 struct snd_soc_dapm_widget *wsource = NULL, *wsink = NULL, *w;
5 struct snd_soc_dapm_widget *wtsource = NULL, *wtsink = NULL;
6 const char *sink;
7 const char *source;
8 ......
9 list_for_each_entry(w, &dapm->card->widgets, list) {
10 if (!wsink && !(strcmp(w->name, sink))) {
11 wtsink = w;
12 if (w->dapm == dapm)
13 wsink = w;
14 continue;
15 }
16 if (!wsource && !(strcmp(w->name, source))) {
17 wtsource = w;
18 if (w->dapm == dapm)
19 wsource = w;
20 }
21 }
上面的代码我再次省略了关于名称前缀的处理部分。我们可以看到,用widget的名字来比较,遍历声卡的widgets链表,找出源widget和目的widget的指针,这段代码虽然正确,但我总感觉少了一个判断退出循环的条件,如果链表的开头就找到了两个widget,还是要遍历整个链表才结束循环,好浪费时间。
下面,如果在本dapm context中没有找到,则使用别的dapm context中找到的widget:
1 if (!wsink)
2 wsink = wtsink;
3 if (!wsource)
4 wsource = wtsource;
最后,使用来增加一条连接信息:
1 ret = snd_soc_dapm_add_path(dapm, wsource, wsink, route->control,
2 route->connected);
3 ......
4
5 return 0;
6 }
snd_soc_dapm_add_path函数是整个调用链条中的关键,我们来分析一下:
1 static int snd_soc_dapm_add_path(struct snd_soc_dapm_context *dapm,
2 struct snd_soc_dapm_widget *wsource, struct snd_soc_dapm_widget *wsink,
3 const char *control,
4 int (*connected)(struct snd_soc_dapm_widget *source,
5 struct snd_soc_dapm_widget *sink))
6 {
7 struct snd_soc_dapm_path *path;
8 int ret;
9
10 path = kzalloc(sizeof(struct snd_soc_dapm_path), GFP_KERNEL);
11 if (!path)
12 return -ENOMEM;
13
14 path->source = wsource;
15 path->sink = wsink;
16 path->connected = connected;
17 INIT_LIST_HEAD(&path->list);
18 INIT_LIST_HEAD(&path->list_kcontrol);
19 INIT_LIST_HEAD(&path->list_source);
20 INIT_LIST_HEAD(&path->list_sink);
最后,使用来增加一条连接信息:
1 ret = snd_soc_dapm_add_path(dapm, wsource, wsink, route->control,
2 route->connected);
3 ......
4
5 return 0;
6 }
snd_soc_dapm_add_path函数是整个调用链条中的关键,我们来分析一下:(注意linux3.10.28代码没有相应的snd_soc_dapm_add_path函数,在linux3.12才有设计snd_soc_dapm_add_path函数)
1 static int snd_soc_dapm_add_path(struct snd_soc_dapm_context *dapm,
2 struct snd_soc_dapm_widget *wsource, struct snd_soc_dapm_widget *wsink,
3 const char *control,
4 int (*connected)(struct snd_soc_dapm_widget *source,
5 struct snd_soc_dapm_widget *sink))
6 {
7 struct snd_soc_dapm_path *path;
8 int ret;
9
10 path = kzalloc(sizeof(struct snd_soc_dapm_path), GFP_KERNEL);
11 if (!path)
12 return -ENOMEM;
13
14 path->source = wsource;
15 path->sink = wsink;
16 path->connected = connected;
17 INIT_LIST_HEAD(&path->list);
18 INIT_LIST_HEAD(&path->list_kcontrol);
19 INIT_LIST_HEAD(&path->list_source);
20 INIT_LIST_HEAD(&path->list_sink);
函数的一开始,首先为这个连接分配了一个snd_soc_path结构,path的source和sink字段分别指向源widget和目的widget,connected字段保存connected回调函数,初始化几个snd_soc_path结构中的几个链表。
1 /* check for external widgets */
2 if (wsink->id == snd_soc_dapm_input) {
3 if (wsource->id == snd_soc_dapm_micbias ||
4 wsource->id == snd_soc_dapm_mic ||
5 wsource->id == snd_soc_dapm_line ||
6 wsource->id == snd_soc_dapm_output)
7 wsink->ext = 1;
8 }
9 if (wsource->id == snd_soc_dapm_output) {
10 if (wsink->id == snd_soc_dapm_spk ||
11 wsink->id == snd_soc_dapm_hp ||
12 wsink->id == snd_soc_dapm_line ||
13 wsink->id == snd_soc_dapm_input)
14 wsource->ext = 1;
15 }
这段代码用于判断是否有外部连接关系,如果有,置位widget的ext字段。判断方法从代码中可以方便地看出:
1 dapm_mark_dirty(wsource, "Route added");
2 dapm_mark_dirty(wsink, "Route added");
3
4 /* connect static paths */
5 if (control == NULL) {
6 list_add(&path->list, &dapm->card->paths);
7 list_add(&path->list_sink, &wsink->sources);
8 list_add(&path->list_source, &wsource->sinks);
9 path->connect = 1;
10 return 0;
11 }
因为增加了连结关系,所以把源widget和目的widget加入到dapm_dirty链表中。如果没有kcontrol来控制该连接关系,则这是一个静态连接,直接用path把它们连接在一起。在接着往下看:
1 /* connect dynamic paths */
2 switch (wsink->id) {
3 case snd_soc_dapm_adc:
4 case snd_soc_dapm_dac:
5 case snd_soc_dapm_pga:
6 case snd_soc_dapm_out_drv:
7 case snd_soc_dapm_input:
8 case snd_soc_dapm_output:
9 case snd_soc_dapm_siggen:
10 case snd_soc_dapm_micbias:
11 case snd_soc_dapm_vmid:
12 case snd_soc_dapm_pre:
13 case snd_soc_dapm_post:
14 case snd_soc_dapm_supply:
15 case snd_soc_dapm_regulator_supply:
16 case snd_soc_dapm_clock_supply:
17 case snd_soc_dapm_aif_in:
18 case snd_soc_dapm_aif_out:
19 case snd_soc_dapm_dai_in:
20 case snd_soc_dapm_dai_out:
21 case snd_soc_dapm_dai_link:
22 case snd_soc_dapm_kcontrol:
23 list_add(&path->list, &dapm->card->paths);
24 list_add(&path->list_sink, &wsink->sources);
25 list_add(&path->list_source, &wsource->sinks);
26 path->connect = 1;
27 return 0;
按照目的widget来判断,如果属于以上这些类型,直接把它们连接在一起即可,这段感觉有点多余,因为通常以上这些类型的widget本来也没有kcontrol,直接用上一段代码就可以了,也许是dapm的作者们想着以后可能会有所扩展吧。
1 case snd_soc_dapm_mux:
2 case snd_soc_dapm_virt_mux:
3 case snd_soc_dapm_value_mux:
4 ret = dapm_connect_mux(dapm, wsource, wsink, path, control,
5 &wsink->kcontrol_news[0]);
6 if (ret != 0)
7 goto err;
8 break;
9 case snd_soc_dapm_switch:
10 case snd_soc_dapm_mixer:
11 case snd_soc_dapm_mixer_named_ctl:
12 ret = dapm_connect_mixer(dapm, wsource, wsink, path, control);
13 if (ret != 0)
14 goto err;
15 break;
当widget之间通过path进行连接之后,他们之间的关系就如下图所示:
到这里为止,我们为声卡创建并初始化好了所需的widget,各个widget也通过path连接在了一起,接下来,dapm等待用户的指令,一旦某个dapm kcontrol被用户空间改变,利用这些连接关系,dapm会重新创建音频路径,脱离音频路径的widget会被下电,加入音频路径的widget会被上电,所有的上下电动作都会自动完成,用户空间的应用程序无需关注这些变化,它只管按需要改变某个dapm kcontrol即可。