前面我们主要着重于codec、platform、machine驱动程序中如何使用和建立dapm所需要的widget,route,这些是音频驱动开发人员必须要了解的内容,经过前几章的介绍,我们应该知道如何在alsa音频驱动的3大部分(codec、platform、machine)中,按照所使用的音频硬件结构,定义出相应的widget,kcontrol,以及必要的音频路径,而在本章中,我们将会深入dapm的核心部分,看看各个widget之间是如何建立连接关系,形成一条完整的音频路径。
/*****************************************************************************************************/
声明:本博内容均由http://blog.csdn.net/droidphone原创,转载请注明出处,谢谢!
/*****************************************************************************************************/
前面我们已经简单地介绍过,驱动程序需要使用以下api函数创建widget:
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完成,继续进入该函数,看看它做了那些工作。
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;
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回调函数 |
---|---|
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 |
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);几个链表的作用如下:
/* machine layer set ups unconnected pins and insertions */ w->connected = 1; return w; }connected字段代表着引脚的连接状态, 目前, 只有以下这些widget使用connected字段:
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函数的工作过程。
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; }需要用到的创建函数分别是:
/* 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(card, SND_SOC_DAPM_STREAM_NOP); ...... return 0; }
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。
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); } }
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; }
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; }
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; }
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,还是要遍历整个链表才结束循环,好浪费时间。
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字段。判断方法从代码中可以方便地看出:
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字段默认为是未连接状态。
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是一样的。
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也是一样的。