dapm最核心的部分大概就是widgets、paths和routes三驾马车,其中widgets是DAPM的基本单元,paths是widget之间的连接器,routes表示widget的连接关系。下面仔细介绍一下这几部分:
Linux 4.9.123 可从以下地址获得
https://mirrors.edge.kernel.org/pub/linux/kernel/v4.x/
本文Codec基于wm8978。
之前介绍Alsa各部分流程的时候是用wm8524来举例,但是渐渐的我发现wm8524太简单了,所以现在转到wm8978来继续做介绍,当我们打开sound/soc/codecs/wm8978.c
,会在开头看到几个结构体:
static const struct snd_kcontrol_new wm8978_snd_controls[]
static const struct snd_kcontrol_new wm8978_left_out_mixer[]
static const struct snd_kcontrol_new wm8978_right_out_mixer[]
static const struct snd_kcontrol_new wm8978_left_input_mixer[]
static const struct snd_kcontrol_new wm8978_right_input_mixer[]
static const struct snd_soc_dapm_widget wm8978_dapm_widgets[]
static const struct snd_soc_dapm_route wm8978_dapm_routes[]
这么多其实就是三种struct的实例化
struct snd_kcontrol_new、struct snd_soc_dapm_widget和struct snd_soc_dapm_route,本篇主要介绍widget和route。
其实不光你们晕,我自己也有点晕,因为alsa架构一直在变,有的驱动及时改成了新的架构,但是当你看到两个乃至三四个不同的架构时。。。我是谁?从哪里来?要到哪里去?虽然这样,但是还是要坚持下去,因为了解到了不同的原因,其实再追下去,万变不离其宗。
一直以来我都烦了一个错误,就是拼命的想把各部分串联起来,但是岂不知地基都没打牢,越串联越乱。。。
widget是dapm所控制的最小单元,如果把n个widgets比作是n个村庄,那么在这n个村庄之间修铁路就是route所需要做的工作,相同等级的村庄之间也没必要修铁路,修铁路的目的当然是为了能从起始地(source)到目的地(sink),而到底走哪条路,也不是随机的,这个可以理解成kcontrol所干的活。前面已经讲过[Alsa]5, dapm之kcontrol,此处不再赘述,我们来看看有几个村,这几个村之间怎么修路。
1 widget
在DAPM框架中,widget用结构体snd_soc_dapm_widget来描述. include/sound/soc-dapm.h
static const struct snd_soc_dapm_widget wm8978_dapm_widgets[] = {
SND_SOC_DAPM_DAC("Left DAC", "Left HiFi Playback",
WM8978_POWER_MANAGEMENT_3, 0, 0),
SND_SOC_DAPM_DAC("Right DAC", "Right HiFi Playback",
WM8978_POWER_MANAGEMENT_3, 1, 0),
SND_SOC_DAPM_ADC("Left ADC", "Left HiFi Capture",
WM8978_POWER_MANAGEMENT_2, 0, 0),
SND_SOC_DAPM_ADC("Right ADC", "Right HiFi Capture",
WM8978_POWER_MANAGEMENT_2, 1, 0),
/* Mixer #1: OUT1,2 */
SOC_MIXER_ARRAY("Left Output Mixer", WM8978_POWER_MANAGEMENT_3,
2, 0, wm8978_left_out_mixer),
SOC_MIXER_ARRAY("Right Output Mixer", WM8978_POWER_MANAGEMENT_3,
3, 0, wm8978_right_out_mixer),
SOC_MIXER_ARRAY("Left Input Mixer", WM8978_POWER_MANAGEMENT_2,
2, 0, wm8978_left_input_mixer),
SOC_MIXER_ARRAY("Right Input Mixer", WM8978_POWER_MANAGEMENT_2,
3, 0, wm8978_right_input_mixer),
SND_SOC_DAPM_PGA("Left Boost Mixer", WM8978_POWER_MANAGEMENT_2,
4, 0, NULL, 0),
SND_SOC_DAPM_PGA("Right Boost Mixer", WM8978_POWER_MANAGEMENT_2,
5, 0, NULL, 0),
SND_SOC_DAPM_PGA("Left Capture PGA", WM8978_LEFT_INP_PGA_CONTROL,
6, 1, NULL, 0),
SND_SOC_DAPM_PGA("Right Capture PGA", WM8978_RIGHT_INP_PGA_CONTROL,
6, 1, NULL, 0),
SND_SOC_DAPM_PGA("Left Headphone Out", WM8978_POWER_MANAGEMENT_2,
7, 0, NULL, 0),
SND_SOC_DAPM_PGA("Right Headphone Out", WM8978_POWER_MANAGEMENT_2,
8, 0, NULL, 0),
SND_SOC_DAPM_PGA("Left Speaker Out", WM8978_POWER_MANAGEMENT_3,
6, 0, NULL, 0),
SND_SOC_DAPM_PGA("Right Speaker Out", WM8978_POWER_MANAGEMENT_3,
5, 0, NULL, 0),
SND_SOC_DAPM_MIXER("OUT4 VMID", WM8978_POWER_MANAGEMENT_3,
8, 0, NULL, 0),
SND_SOC_DAPM_MICBIAS("Mic Bias", WM8978_POWER_MANAGEMENT_1, 4, 0),
SND_SOC_DAPM_INPUT("LMICN"),
SND_SOC_DAPM_INPUT("LMICP"),
SND_SOC_DAPM_INPUT("RMICN"),
SND_SOC_DAPM_INPUT("RMICP"),
SND_SOC_DAPM_INPUT("LAUX"),
SND_SOC_DAPM_INPUT("RAUX"),
SND_SOC_DAPM_INPUT("L2"),
SND_SOC_DAPM_INPUT("R2"),
SND_SOC_DAPM_OUTPUT("LHP"),
SND_SOC_DAPM_OUTPUT("RHP"),
SND_SOC_DAPM_OUTPUT("LSPK"),
SND_SOC_DAPM_OUTPUT("RSPK"),
};
追一下SND_SOC_DAPM_PGA
、SND_SOC_DAPM_INPUT
和SND_SOC_DAPM_OUTPUT
,可以看到他们就是kcontrol的再一次封装。
2 route
在DAPM框架中,widget用结构体snd_soc_dapm_route来描述. include/sound/soc-dapm.h
static const struct snd_soc_dapm_route wm8978_dapm_routes[] = {
/* Output mixer */
{"Right Output Mixer", "PCM Playback Switch", "Right DAC"},
{"Right Output Mixer", "Aux Playback Switch", "RAUX"},
{"Right Output Mixer", "Line Bypass Switch", "Right Boost Mixer"},
{"Left Output Mixer", "PCM Playback Switch", "Left DAC"},
{"Left Output Mixer", "Aux Playback Switch", "LAUX"},
{"Left Output Mixer", "Line Bypass Switch", "Left Boost Mixer"},
/* Outputs */
{"Right Headphone Out", NULL, "Right Output Mixer"},
{"RHP", NULL, "Right Headphone Out"},
{"Left Headphone Out", NULL, "Left Output Mixer"},
{"LHP", NULL, "Left Headphone Out"},
{"Right Speaker Out", NULL, "Right Output Mixer"},
{"RSPK", NULL, "Right Speaker Out"},
{"Left Speaker Out", NULL, "Left Output Mixer"},
{"LSPK", NULL, "Left Speaker Out"},
/* Boost Mixer */
{"Right ADC", NULL, "Right Boost Mixer"},
{"Right Boost Mixer", NULL, "RAUX"},
{"Right Boost Mixer", NULL, "Right Capture PGA"},
{"Right Boost Mixer", NULL, "R2"},
{"Left ADC", NULL, "Left Boost Mixer"},
{"Left Boost Mixer", NULL, "LAUX"},
{"Left Boost Mixer", NULL, "Left Capture PGA"},
{"Left Boost Mixer", NULL, "L2"},
/* Input PGA */
{"Right Capture PGA", NULL, "Right Input Mixer"},
{"Left Capture PGA", NULL, "Left Input Mixer"},
{"Right Input Mixer", "R2 Switch", "R2"},
{"Right Input Mixer", "MicN Switch", "RMICN"},
{"Right Input Mixer", "MicP Switch", "RMICP"},
{"Left Input Mixer", "L2 Switch", "L2"},
{"Left Input Mixer", "MicN Switch", "LMICN"},
{"Left Input Mixer", "MicP Switch", "LMICP"},
};
可以看到每一个route都是有三部分组成的以下面这一个为例
...
{"Left Output Mixer", "PCM Playback Switch", "Left DAC"},
...
两边的"Left Output Mixer", “Left DAC"都可以在上面定义的widget中找到,敢情他们是用“字符串”来相互称呼的,而中间的"PCM Playback Switch”,猜到了吗,是kcontrol,他们的名字分别是{source name, control name, sink name},他们的作用就是这里直接使用名字字符串来描述连接关系,所有定义好的route,最后都要注册到dapm系统中,dapm会根据这些名字找出相应的widget,并动态地生成所需要的snd_soc_dapm_path结构,动态修path了解一下?正确地处理各个链表和指针的关系,实现两个widget之间的连接。
3 add_widgets
这次我们来到sound/soc/codecs/wm8960.c
,看到里面有一个wm8960_add_widgets,这个函数就用来向系统添加widget,里面有几个重要的函数
- snd_soc_dapm_new_controls,用来把widget添加到dapm上下文。
- snd_soc_dapm_add_routes,系统中注册的各种widget需要互相连接在一起才能协调工作,连接关系通过此结构来定义
snd_soc_dapm_new_controls
实际上,要使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,
static int wm8960_add_widgets(struct snd_soc_codec *codec)
{
struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
struct wm8960_data *pdata = &wm8960->pdata;
struct snd_soc_dapm_context *dapm = snd_soc_codec_get_dapm(codec);
struct snd_soc_dapm_widget *w;
snd_soc_dapm_new_controls(dapm, wm8960_dapm_widgets,
ARRAY_SIZE(wm8960_dapm_widgets));
snd_soc_dapm_add_routes(dapm, audio_paths, ARRAY_SIZE(audio_paths));
...
}
return 0;
}
3.1 snd_soc_dapm_new_controls
snd_soc_dapm_new_controls函数创建新的dapm control。
/**
* snd_soc_dapm_new_controls - create new dapm controls
* @dapm: DAPM context
* @widget: widget array
* @num: number of widgets
*
* Creates new DAPM controls based upon the templates.
*
* Returns 0 for success else error.
*/
int snd_soc_dapm_new_controls(struct snd_soc_dapm_context *dapm,
const struct snd_soc_dapm_widget *widget,
int num)
{
struct snd_soc_dapm_widget *w;
int i;
int ret = 0;
mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_INIT);
for (i = 0; i < num; i++) {
w = snd_soc_dapm_new_control_unlocked(dapm, widget);
if (IS_ERR(w)) {
ret = PTR_ERR(w);
/* Do not nag about probe deferrals */
if (ret == -EPROBE_DEFER)
break;
dev_err(dapm->dev,
"ASoC: Failed to create DAPM control %s (%d)\n",
widget->name, ret);
break;
}
if (!w) {
dev_err(dapm->dev,
"ASoC: Failed to create DAPM control %s\n",
widget->name);
ret = -ENOMEM;
break;
}
widget++;
}
mutex_unlock(&dapm->card->dapm_mutex);
return ret;
}
EXPORT_SYMBOL_GPL(snd_soc_dapm_new_controls);
这其中有三个参数struct snd_soc_dapm_context *dapm, const struct snd_soc_dapm_widget *widget, int num
snd_soc_dapm_context
是DAPM的上下文,这个好像不好理解,其实我们可以这么理解:dapm把整个音频系统,按照功能和偏置电压级别,划分为若干个电源域,每个域包含各自的widget,每个域中的所有widget通常都处于同一个偏置电压级别上,而一个电源域就是一个dapm context,通常会有以下几种dapm context:
- 属于codec中的widget位于一个dapm context中
- 属于platform的widget位于一个dapm context中
- 属于整个声卡的widget位于一个dapm context中
对于音频系统的硬件来说,通常要提供合适的偏置电压才能正常地工作,有了dapm context这种组织方式,我们可以方便地对同一组widget进行统一的偏置电压管理。代表widget结构snd_soc_dapm_widget中,有一个snd_soc_dapm_context结构指针,指向所属的codec、platform、card、或dai的dapm结构。同时,所有的dapm结构,通过它的list字段,链接到代表声卡的snd_soc_card结构的dapm_list链表头字段。
3.2 snd_soc_dapm_add_routes
/**
* snd_soc_dapm_add_routes - Add routes between DAPM widgets
* @dapm: DAPM context
* @route: audio routes
* @num: number of routes
*
* Connects 2 dapm widgets together via a named audio path. The sink is
* the widget receiving the audio signal, whilst the source is the sender
* of the audio signal.
*
* Returns 0 for success else error. On error all resources can be freed
* with a call to snd_soc_card_free().
*/
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);
if (r < 0) {
dev_err(dapm->dev, "ASoC: Failed to add route %s -> %s -> %s\n",
route->source,
route->control ? route->control : "direct",
route->sink);
ret = r;
}
route++;
}
mutex_unlock(&dapm->card->dapm_mutex);
return ret;
}
EXPORT_SYMBOL_GPL(snd_soc_dapm_add_routes);
通过一个循环把num个snd_soc_dapm_add_route添加到系统中。