Linux ALSA架构:DPAM概念详解(三)
一、DAPM简介
DAPM是Dynamic Audio Power Management的缩写,直译过来就是动态音频电源管理的意思,DAPM是为了使基于linux的移动设备上的音频子系统,在任何时候都工作在最小功耗状态下。DAPM对用户空间的应用程序来说是透明的,所有与电源相关的开关都在ASOC core中完成。用户空间的应用程序无需对代码做出修改,也无需重新编译,DAPM根据当前激活的音频流(playback/capture)和声卡中的mixer等的配置来决定那些音频控件的电源开关被打开或关闭。
DAPM是基于kcontrol改进过后的相应框架,增加了相应的电源管理机制,其电源管理机制其实就是按照相应的音频路径,完美的对各种部件的电源进行控制,而且按照某种顺序进行。
二、DAPM的kcontrol单位
通常,一个kcontrol代表着一个mixer(混音器),或者是一个mux(多路开关),又或者是一个音量控制器等等。 从之前知识中我们知道,定义一个kcontrol主要就是定义一个snd_kcontrol_new结构。下面介绍一下ASoC 系统已经准备的大量宏定义,用于定义常用的控件。
2.1 简单的控件SOC_SINGLE
SOC_SINGLE应该算是最简单的控件了,这种控件只有一个控制量,比如一个开关,或者是一个数值变量(比如Codec中某个频率,FIFO大小等等)。宏定义如下:
#define SOC_SINGLE(xname, reg, shift, max, invert) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
.info = snd_soc_info_volsw, .get = snd_soc_get_volsw,\
.put = snd_soc_put_volsw, \
.private_value = SOC_SINGLE_VALUE(reg, shift, max, invert, 0)
}
宏定义的参数分别是:xname(该控件的名字),reg(该控件对应的寄存器的地址),shift(控制位在寄存器中的位移),max(控件可设置的最大值),invert(设定值是否逻辑取反)。这里又使用了一个宏来定义private_value字段:SOC_SINGLE_VALUE。
#define SOC_SINGLE_VALUE(xreg, xshift, xmax, xinvert, xautodisable) \
SOC_DOUBLE_VALUE(xreg, xshift, xshift, xmax, xinvert, xautodisable)
#define SOC_DOUBLE_VALUE(xreg, shift_left, shift_right, xmax, xinvert, xautodisable) \
((unsigned long)&(struct soc_mixer_control) \
{.reg = xreg, .rreg = xreg, .shift = shift_left, \
.rshift = shift_right, .max = xmax, .platform_max = xmax, \
.invert = xinvert, .autodisable = xautodisable})
如定义所示,这里实际上是定义了一个 soc_mixer_control 结构,然后把该结构的地址赋给了 private_value 字段,其定义如下:
struct soc_mixer_control {
int min, max, platform_max;
int reg, rreg;
unsigned int shift, rshift;
unsigned int sign_bit;
unsigned int invert:1;
unsigned int autodisable:1;
#ifdef CONFIG_SND_SOC_TOPOLOGY
struct snd_soc_dobj dobj;
#endif
};
从其定义可以看出 soc_mixer_control 是控件特征的真正描述者,它确定了该控件对应寄存器的地址,位移值,最大值和是否逻辑取反等特性,控件的 put/get 回到函数则会借助该结构来访问实际的寄存器。
int snd_soc_get_volsw(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
unsigned int reg = mc->reg;
unsigned int reg2 = mc->rreg;
unsigned int shift = mc->shift;
unsigned int rshift = mc->rshift;
int max = mc->max;
int min = mc->min;
int sign_bit = mc->sign_bit;
unsigned int mask = (1 << fls(max)) - 1;
unsigned int invert = mc->invert;
int val;
int ret;
if (sign_bit)
mask = BIT(sign_bit + 1) - 1;
ret = snd_soc_read_signed(component, reg, mask, shift, sign_bit, &val);
if (ret)
return ret;
ucontrol->value.integer.value[0] = val - min;
if (invert)
ucontrol->value.integer.value[0] =
max - ucontrol->value.integer.value[0];
if (snd_soc_volsw_is_stereo(mc)) {
if (reg == reg2)
ret = snd_soc_read_signed(component, reg, mask, rshift,
sign_bit, &val);
else
ret = snd_soc_read_signed(component, reg2, mask, shift,
sign_bit, &val);
if (ret)
return ret;
ucontrol->value.integer.value[1] = val - min;
if (invert)
ucontrol->value.integer.value[1] =
max - ucontrol->value.integer.value[1];
}
return 0;
}
从 private_value 字段取出 soc_mixer_kcontrol 结构,利用该结构的信息访问对应的寄存器,返回相应的值。
2.2 SOC_SINGLE_TLV
该宏是 SOC_SINGLE 的一种扩展,主要用于定义那些有增益控制的控件,例如音量控制器,EQ均衡器等等,定义如下:
#define SOC_SINGLE_TLV(xname, reg, shift, max, invert, tlv_array) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
.access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\
SNDRV_CTL_ELEM_ACCESS_READWRITE,\
.tlv.p = (tlv_array), \
.info = snd_soc_info_volsw, .get = snd_soc_get_volsw,\
.put = snd_soc_put_volsw, \
.private_value = SOC_SINGLE_VALUE(reg, shift, max, invert, 0) }
从定义可以看出,用于设置寄存器信息的 private_value 字段的定义和 SOC_SINGLE 是一样的,甚至 put、get 回到函数也是使用同一套,唯一不同的是增加了一个 tlv_arry 参数,并把它赋值给了 tlv.p 字段。用户空间可以通过声卡的 control 设备发起以下两种 ioctl 来访问 tlv 字段所指向的数组:
#define SNDRV_CTL_ELEM_ACCESS_TLV_READ (1<<4) /* TLV read is possible */
#define SNDRV_CTL_ELEM_ACCESS_TLV_WRITE (1<<5) /* TLV write is possible */
#define SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE (SNDRV_CTL_ELEM_ACCESS_TLV_READ|SNDRV_CTL_ELEM_ACCESS_TLV_WRITE)
#define SNDRV_CTL_ELEM_ACCESS_TLV_COMMAND (1<<6) /* TLV command is possible */
通常,tlv_arry 用来描述寄存器的设定值与它代表的实际意义之间的映射关系,最常用的就是用于音量控件时,设定值与对应的 dB 值之间的映射关系,请看以下例子:
static const DECLARE_TLV_DB_SCALE(mixin_boost_tlv, 0, 900, 0);
static const struct snd_kcontrol_new wm1811_snd_controls[] = {
SOC_SINGLE_TLV("MIXINL IN1LP Boost Volume", WM8994_INPUT_MIXER_1, 7, 1, 0,
mixin_boost_tlv),
SOC_SINGLE_TLV("MIXINL IN1RP Boost Volume", WM8994_INPUT_MIXER_1, 8, 1, 0,
mixin_boost_tlv),
};
DECLARE_TLV_DB_SCALE用于定义一个dB值映射的tlv_array,上述的例子表明,该tlv的类型是SNDRV_CTL_TLVT_DB_SCALE,寄存器的最小值对应是0dB,寄存器每增加一个单位值,对应的dB数增加是9dB(0.01dB*900),而由接下来的两组SOC_SINGLE_TLV定义可以看出,我们定义了两个boost控件,寄存器的地址都是WM8994_INPUT_MIXER_1,控制位分别是第7bit和第8bit,最大值是1,所以,该控件只能设定两个数值0和1,对应的dB值就是0dB和9dB。
2.3 SOC_DOUBLE
与SOC_SINGLE相对应,区别是SOC_SINGLE只控制一个变量,而SOC_DOUBLE则可以同时在一个寄存器中控制两个相似的变量,最常用的就是用于一些立体声的控件,我们需要同时对左右声道进行控制,因为多了一个声道,参数也就相应地多了一个shift位移值
#define SOC_DOUBLE(xname, reg, shift_left, shift_right, max, invert) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname),\
.info = snd_soc_info_volsw, .get = snd_soc_get_volsw, \
.put = snd_soc_put_volsw, \
.private_value = SOC_DOUBLE_VALUE(reg, shift_left, shift_right, \
max, invert, 0) }
SOC_DOUBLE_R :与SOC_DOUBLE类似,对于左右声道的控制寄存器不一样的情况,使用SOC_DOUBLE_R来定义,参数中需要指定两个寄存器地址。
SOC_DOUBLE_TLV : 与SOC_SINGLE_TLV对应的立体声版本,通常用于立体声音量控件的定义。
SOC_DOUBLE_R_TLV: 左右声道有独立寄存器控制的SOC_DOUBLE_TLV版本。
2.4 Mixer控件
用于音频通道的路由控制,由多个输入和一个输出组成,多个输入可以自由地混合在一起,形成混合后的输出:

对于Mixer控件,我们可以认为是多个简单控件的组合,通常,我们会为mixer的每个输入端都单独定义一个简单控件来控制该路输入的开启和关闭,反应在代码上,就是定义一个soc_kcontrol_new数组:
static const struct snd_kcontrol_new left_speaker_mixer[] = {
SOC_DAPM_SINGLE("DAC2 Switch", WM8994_SPEAKER_MIXER, 9, 1, 0),
SOC_DAPM_SINGLE("Input Switch", WM8994_SPEAKER_MIXER, 7, 1, 0),
SOC_DAPM_SINGLE("IN1LP Switch", WM8994_SPEAKER_MIXER, 5, 1, 0),
SOC_DAPM_SINGLE("Output Switch", WM8994_SPEAKER_MIXER, 3, 1, 0),
SOC_DAPM_SINGLE("DAC1 Switch", WM8994_SPEAKER_MIXER, 1, 1, 0),
};
以上这个mixer使用寄存器WM8993_SPEAKER_MIXER的第3,5,6,7位来分别控制4个输入端的开启和关闭。
2.5 mux控件
与mixer控件类似,也是多个输入端和一个输出端的组合控件,与mixer控件不同的是,mux控件的多个输入端同时只能有一个被选中。因此,mux控件所对应的寄存器,通常可以设定一段连续的数值,每个不同的数值对应不同的输入端被打开,与上述的mixer控件不同,ASoc用soc_enum结构来描述mux控件的寄存器信息:
struct soc_enum {
int reg;
unsigned char shift_l;
unsigned char shift_r;
unsigned int items;
unsigned int mask;
const char * const *texts;
const unsigned int *values;
unsigned int autodisable:1;
#ifdef CONFIG_SND_SOC_TOPOLOGY
struct snd_soc_dobj dobj;
#endif
};
两个寄存器地址和位移字段:reg,reg2,shift_l,shift_r,用于描述左右声道的控制寄存器信息。字符串数组指针用于描述每个输入端对应的名字,value字段则指向一个数组,该数组定义了寄存器可以选择的值,每个值对应一个输入端,如果value是一组连续的值,通常我们可以忽略values参数。下面我们先看看如何定义一个mux控件:
第一步,定义字符串和values数组,以下的例子因为values是连续的,所以不用定义:
static const char *drc_path_text[] = {
"ADC",
"DAC"
};
第二步,利用ASoc提供的辅助宏定义soc_enum结构,用于描述寄存器:
static SOC_ENUM_SINGLE_DECL(drc_path,
WM8993_DRC_CONTROL_1, 14, drc_path_text);
第三步,利痛ASoc提供的辅助宏,定义soc_kcontrol_new结构,该结构最后用于注册该mux控件:
static const struct snd_kcontrol_new wm8993_snd_controls[] = {
SOC_DOUBLE_TLV(...),
...
SOC_ENUM("DRC Path", drc_path),
};
以上几步定义了一个叫DRC PATH的mux控件,该控件具有两个输入选择,分别是来自ADC和DAC,用寄存器WM8993_DRC_CONTROL_1控制。其中,soc_enum结构使用了辅助宏SOC_ENUM_SINGLE来定义,该宏的声明如下:
#define SOC_ENUM_DOUBLE(xreg, xshift_l, xshift_r, xitems, xtexts) \
{ .reg = xreg, .shift_l = xshift_l, .shift_r = xshift_r, \
.items = xitems, .texts = xtexts, \
.mask = xitems ? roundup_pow_of_two(xitems) - 1 : 0}
#define SOC_ENUM_SINGLE(xreg, xshift, xitems, xtexts) \
SOC_ENUM_DOUBLE(xreg, xshift, xshift, xitems, xtexts)
定义soc_kcontrol_new结构时使用了SOC_ENUM,列出它的定义如下:
#define SOC_ENUM(xname, xenum) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname,\
.info = snd_soc_info_enum_double, \
.get = snd_soc_get_enum_double, .put = snd_soc_put_enum_double, \
.private_value = (unsigned long)&xenum }
思想如此统一,依然是使用private_value字段记录soc_enum结构,不过几个回调函数变了,我们看看get回调对应的snd_soc_get_enum_double函数:
int snd_soc_get_enum_double(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
unsigned int val, item;
unsigned int reg_val;
reg_val = snd_soc_component_read(component, e->reg);
val = (reg_val >> e->shift_l) & e->mask;
item = snd_soc_enum_val_to_item(e, val);
ucontrol->value.enumerated.item[0] = item;
if (e->shift_l != e->shift_r) {
val = (reg_val >> e->shift_r) & e->mask;
item = snd_soc_enum_val_to_item(e, val);
ucontrol->value.enumerated.item[1] = item;
}
return 0;
}
通过info回调函数则可以获取某个输入端所对应的名字,其实就是从soc_enum结构的texts数组中获得:
int snd_soc_info_enum_double(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
return snd_ctl_enum_info(uinfo, e->shift_l == e->shift_r ? 1 : 2,
e->items, e->texts);
}
三、DAPM的基本单位widget:
widget把kcontrol和动态电源管理进行了有机的结合,同时还具备音频路径的连结功能,一个widget可以与它相邻的widget有某种动态的连结关系。在DAPM框架中,widget用结构体snd_soc_dapm_widget来描述:
/* dapm widget */
struct snd_soc_dapm_widget {
enum snd_soc_dapm_type id;该widget的类型值,比如snd_soc_dapm_output,snd_soc_dapm_mixer等
const char *name; /* widget name */
const char *sname; /* stream name */代表该widget所在stream的名字,比如对于snd_soc_dapm_dai_in类型的widget,会使用该字段
struct list_head list;所有注册到系统中的widget都会通过该list,链接到代表声卡的snd_soc_card结构的widgets链表头字段中
struct snd_soc_dapm_context *dapm;snd_soc_dapm_context结构指针,ASoc把系统划分为多个dapm域,每个widget属于某个dapm域,同一个域代表着同样的偏置电压供电策略,
比如,同一个codec中的widget通常位于同一个dapm域,而平台上的widget可能又会位于另外一个platform域中
void *priv; /* widget specific data */有些widget可能需要一些专有的数据,可以使用该字段来保存,像snd_soc_dapm_dai_in类型的widget,会使用该字段来记住与之相关联的snd_soc_dai结构指针
struct regulator *regulator; /* attached regulator */对于snd_soc_dapm_regulator_supply类型的widget,该字段指向与之相关的regulator结构指针
const struct snd_soc_pcm_stream *params; /* params for dai links */
unsigned int num_params; /* number of params for dai links */
unsigned int params_select; /* currently selected param for dai link */
/* dapm control */reg/shift/mask这3个字段用来控制该widget的电源状态,分别对应控制信息所在的寄存器地址,位移值和屏蔽值
int reg; /* negative reg = no direct dapm */
unsigned char shift; /* bits to shift */
unsigned int mask; /* non-shifted mask */
unsigned int on_val; /* on state value */电源开启时的值
unsigned int off_val; /* off state value */电源关闭时的值
unsigned char power:1; /* block power status */表示当前widget是否处于上电
unsigned char active:1; /* active stream on DAC, ADC's */表示当前widget是否处于激活状态
unsigned char connected:1; /* connected codec pin */表示当前widget是否处于连接状态
unsigned char new:1; /* cnew complete */我们定义好的widget(snd_soc_dapm_widget结构),在注册到声卡中时需要进行实例化,该字段用来表示该widget是否已经被实例化
unsigned char force:1; /* force state */该位被设置后,将会不管widget当前的状态,强制更新至新的电源状态
unsigned char ignore_suspend:1; /* kept enabled over suspend */
unsigned char new_power:1; /* power from this run */
unsigned char power_checked:1; /* power checked this run */用于检查该widget是否应该上电或下电的回调函数指针
unsigned char is_supply:1; /* Widget is a supply type widget */
unsigned char is_ep:2; /* Widget is a endpoint type widget */
int subseq; /* sort within widget type */
int (*power_check)(struct snd_soc_dapm_widget *w);
/* external events */
unsigned short event_flags; /* flags to specify event types */
int (*event)(struct snd_soc_dapm_widget*, struct snd_kcontrol *, int);
/* kcontrols that relate to this widget */
int num_kcontrols;
const struct snd_kcontrol_new *kcontrol_news;
struct snd_kcontrol **kcontrols;
struct snd_soc_dobj dobj;
/* widget input and output edges */
struct list_head edges[2];
/* used during DAPM updates */
struct list_head work_list;
struct list_head power_list;
struct list_head dirty;
int endpoints[2];
struct clk *clk;
}
3.1 widget的种类:
在DAPM框架中,把各种不同的widget划分为不同的种类,snd_soc_dapm_widget结构中的id字段用来表示该widget的种类,可选的种类都定义在一个枚举中:
/* dapm widget types */
enum snd_soc_dapm_type {
snd_soc_dapm_input = 0, /* input pin */该widget对应一个输入引脚
snd_soc_dapm_output, /* output pin */ 该widget对应一个输出引脚
snd_soc_dapm_mux, /* selects 1 analog signal from many inputs *该widget对应一个mux控件/
snd_soc_dapm_demux, /* connects the input to one of multiple outputs */
snd_soc_dapm_mixer, /* mixes several analog signals together */
snd_soc_dapm_mixer_named_ctl, /* mixer with named controls */对应一个mixer控件,但是对应的kcontrol的名字不会加入widget的名字作为前缀
snd_soc_dapm_pga, /* programmable gain/attenuation (volume) */对应一个pga控件(可编程增益控件)
snd_soc_dapm_out_drv, /* output driver */对应一个输出驱动控件
snd_soc_dapm_adc, /* analog to digital converter */对应一个ADC
snd_soc_dapm_dac, /* digital to analog converter */对应一个DAC
snd_soc_dapm_micbias, /* microphone bias (power) - DEPRECATED: use snd_soc_dapm_supply */对应一个麦克风偏置电压控件
snd_soc_dapm_mic, /* microphone */麦克风
snd_soc_dapm_hp, /* headphones */耳机
snd_soc_dapm_spk, /* speaker */扬声器
snd_soc_dapm_line, /* line input/output */线路输入输出
snd_soc_dapm_switch, /* analog switch */模拟开关
snd_soc_dapm_vmid, /* codec bias/vmid - to minimise pops */对应一个codec的vmid偏置电压
snd_soc_dapm_pre, /* machine specific pre widget - exec first */machine级别的专用widget,会先于其它widget执行检查操作
snd_soc_dapm_post, /* machine specific post widget - exec last */machine级别的专用widget,会后于其它widget执行检查操作
snd_soc_dapm_supply, /* power/clock supply */对应一个电源或是时钟源
snd_soc_dapm_regulator_supply, /* external regulator */对应一个外部regulator稳压器
snd_soc_dapm_clock_supply, /* external clock */对应一个外部时钟源
snd_soc_dapm_aif_in, /* audio interface input */对应一个数字音频输入接口,比如I2S接口的输入端
snd_soc_dapm_aif_out, /* audio interface output */对应一个数字音频输出接口,比如I2S接口的输出端
snd_soc_dapm_siggen, /* signal generator */对应一个信号发生器
snd_soc_dapm_sink,
snd_soc_dapm_dai_in, /* link to DAI structure */对应一个platform或codec域的输入DAI结构
snd_soc_dapm_dai_out, 对应一个platform或codec域的输出DAI结构
snd_soc_dapm_dai_link, /* link between two DAI structures */用于链接一对输入/输出DAI结构
snd_soc_dapm_kcontrol, /* Auto-disabled kcontrol */
}
3.2 widget之间的连接器path:
DAPM为我们提出了path这一概念,path相当于电路中的一根跳线,它把一个widget的输出端和另一个widget的输入端连接在一起,path用snd_soc_dapm_path结构来描述:
struct snd_soc_dapm_path {
const char *name;
/*
* source (input) and sink (output) widgets
* The union is for convience, since it is a lot nicer to type
* p->source, rather than p->node[SND_SOC_DAPM_DIR_IN]
*/
union {
struct {
struct snd_soc_dapm_widget *source;
struct snd_soc_dapm_widget *sink;
};
struct snd_soc_dapm_widget *node[2];
};
/* status */
u32 connect:1; /* source and sink widgets are connected */
u32 walking:1; /* path is in the process of being walked */
u32 weak:1; /* path ignored for power management */
u32 is_supply:1; /* At least one of the connected widgets is a supply */
int (*connected)(struct snd_soc_dapm_widget *source,
struct snd_soc_dapm_widget *sink);
struct list_head list_node[2];
struct list_head list_kcontrol;
struct list_head list;
};
当widget之间发生连接关系时,snd_soc_dapm_path作为连接者,它的source字段会指向该连接的起始端widget,而它的sink字段会指向该连接的到达端widget。widget的输入端和输出端可能连接着多个path,所有输入端的snd_soc_dapm_path结构通过list_sink字段挂在widget的souces链表中,同样,所有输出端的snd_soc_dapm_path结构通过list_source字段挂在widget的sinks链表中。连接的路径是这样的:起始端widget的输出–>path的输入–>path的输出–>到达端widget输入。
snd_soc_dapm_path结构的list字段用于把所有的path注册到声卡中,其实就是挂在snd_soc_card结构的paths链表头字段中。
3.3 widget的连接关系route
一个路径的连接至少包含以下几个元素:起始端widget,跳线path,到达端widget,在DAPM中,用snd_soc_dapm_route结构来描述这样一个连接关系:
struct snd_soc_dapm_route {
const char *sink;
const char *control;
const char *source;
/* Note: currently only supported for links where source is a supply */
int (*connected)(struct snd_soc_dapm_widget *source,
struct snd_soc_dapm_widget *sink);
struct snd_soc_dobj dobj;
};
sink指向到达端widget的名字字符串,source指向起始端widget的名字字符串,control指向负责控制该连接所对应的kcontrol名字字符串,connected回调则定义了上一节所提到的自定义连接检查回调函数。该结构的意义很明显就是:source通过一个kcontrol,和sink连接在一起,现在是否处于连接状态,请调用connected回调函数检查。
四、定义各种widget
和普通的kcontrol一样,DAPM框架为我们提供了大量的辅助宏用来定义各种各样的widget控件,这些宏定义根据widget的类型,按照它们的电源所在的域,被分为了几个域,他们分别是:
4.1 codec域
比如VREF和VMID等提供参考电压的widget,这些widget通常在codec的probe/remove回调中进行控制,当然,在工作中如果没有音频流时,也可以适当地进行控制它们的开启与关闭。目前,DAPM框架只提供了定义一个codec域widget的辅助宏。
#define SND_SOC_DAPM_VMID(wname) \
{ .id = snd_soc_dapm_vmid, .name = wname, .kcontrol_news = NULL, \
.num_kcontrols = 0}
4.2 platform域
位于该域上的widget通常是针对平台或板子的一些需要物理连接的输入/输出接口,例如耳机、扬声器、麦克风,因为这些接口在每块板子上都可能不一样,所以通常它们是在machine驱动中进行定义和控制,并且也可以由用户空间的应用程序通过某种方式来控制它们的打开和关闭。
#define SND_SOC_DAPM_SIGGEN(wname) \
{ .id = snd_soc_dapm_siggen, .name = wname, .kcontrol_news = NULL, \
.num_kcontrols = 0, .reg = SND_SOC_NOPM }
#define SND_SOC_DAPM_SINK(wname) \
{ .id = snd_soc_dapm_sink, .name = wname, .kcontrol_news = NULL, \
.num_kcontrols = 0, .reg = SND_SOC_NOPM }
#define SND_SOC_DAPM_INPUT(wname) \
{ .id = snd_soc_dapm_input, .name = wname, .kcontrol_news = NULL, \
.num_kcontrols = 0, .reg = SND_SOC_NOPM }
#define SND_SOC_DAPM_OUTPUT(wname) \
{ .id = snd_soc_dapm_output, .name = wname, .kcontrol_news = NULL, \
.num_kcontrols = 0, .reg = SND_SOC_NOPM }
#define SND_SOC_DAPM_MIC(wname, wevent) \
{ .id = snd_soc_dapm_mic, .name = wname, .kcontrol_news = NULL, \
.num_kcontrols = 0, .reg = SND_SOC_NOPM, .event = wevent, \
.event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD}
#define SND_SOC_DAPM_HP(wname, wevent) \
{ .id = snd_soc_dapm_hp, .name = wname, .kcontrol_news = NULL, \
.num_kcontrols = 0, .reg = SND_SOC_NOPM, .event = wevent, \
.event_flags = SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD}
#define SND_SOC_DAPM_SPK(wname, wevent) \
{ .id = snd_soc_dapm_spk, .name = wname, .kcontrol_news = NULL, \
.num_kcontrols = 0, .reg = SND_SOC_NOPM, .event = wevent, \
.event_flags = SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD}
#define SND_SOC_DAPM_LINE(wname, wevent) \
{ .id = snd_soc_dapm_line, .name = wname, .kcontrol_news = NULL, \
.num_kcontrols = 0, .reg = SND_SOC_NOPM, .event = wevent, \
.event_flags = SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD}
以上这些widget分别对应信号发生器,输入引脚,输出引脚,麦克风,耳机,扬声器,线路输入接口。其中的reg字段被设置为SND_SOC_NOPM(-1),表明这些widget是没有寄存器控制位来控制widget的电源状态的。麦克风,耳机,扬声器,线路输入接口这几种widget,还可以定义一个dapm事件回调函数wevent,从event_flags字段的设置可以看出,他们只会响应SND_SOC_DAPM_POST_PMU(上电后)和SND_SOC_DAPM_PMD(下电前)事件,这几个widget通常会在machine驱动中定义,而SND_SOC_DAPM_INPUT和SND_SOC_DAPM_OUTPUT则用来定义codec芯片的输出输入脚,通常在codec驱动中定义,最后,在machine驱动中增加相应的route,把麦克风和耳机等widget与相应的codec输入输出引脚的widget连接起来。
4.3 音频路径域
一般是指codec内部的mixer、mux等控制音频路径的widget,这些widget可以根据用户空间的设定连接关系,自动设定他们的电源状态。
#define SND_SOC_DAPM_PGA(wname, wreg, wshift, winvert,\
wcontrols, wncontrols) \
{ .id = snd_soc_dapm_pga, .name = wname, \
SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \
.kcontrol_news = wcontrols, .num_kcontrols = wncontrols}
#define SND_SOC_DAPM_OUT_DRV(wname, wreg, wshift, winvert,\
wcontrols, wncontrols) \
{ .id = snd_soc_dapm_out_drv, .name = wname, \
SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \
.kcontrol_news = wcontrols, .num_kcontrols = wncontrols}
#define SND_SOC_DAPM_MIXER(wname, wreg, wshift, winvert, \
wcontrols, wncontrols)\
{ .id = snd_soc_dapm_mixer, .name = wname, \
SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \
.kcontrol_news = wcontrols, .num_kcontrols = wncontrols}
#define SND_SOC_DAPM_MIXER_NAMED_CTL(wname, wreg, wshift, winvert, \
wcontrols, wncontrols)\
{ .id = snd_soc_dapm_mixer_named_ctl, .name = wname, \
SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \
.kcontrol_news = wcontrols, .num_kcontrols = wncontrols}
/* DEPRECATED: use SND_SOC_DAPM_SUPPLY */
#define SND_SOC_DAPM_MICBIAS(wname, wreg, wshift, winvert) \
{ .id = snd_soc_dapm_micbias, .name = wname, \
SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \
.kcontrol_news = NULL, .num_kcontrols = 0}
#define SND_SOC_DAPM_SWITCH(wname, wreg, wshift, winvert, wcontrols) \
{ .id = snd_soc_dapm_switch, .name = wname, \
SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \
.kcontrol_news = wcontrols, .num_kcontrols = 1}
#define SND_SOC_DAPM_MUX(wname, wreg, wshift, winvert, wcontrols) \
{ .id = snd_soc_dapm_mux, .name = wname, \
SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \
.kcontrol_news = wcontrols, .num_kcontrols = 1}
#define SND_SOC_DAPM_DEMUX(wname, wreg, wshift, winvert, wcontrols) \
{ .id = snd_soc_dapm_demux, .name = wname, \
SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \
.kcontrol_news = wcontrols, .num_kcontrols = 1}
这些widget的reg和shift字段是需要赋值的,说明这些widget是有相应的电源控制寄存器的,DAPM框架在扫描和更新音频路径时,会利用这些寄存器来控制widget的电源状态,使得它们的供电状态是按需分配的,需要的时候(在有效的音频路径上)上电,不需要的时候(不再有效的音频路径上)下电。这些widget需要完成和之前介绍的mixer、mux等控件同样的功能,实际上,这是通过它们包含的kcontrol控件来完成的,这些kcontrol需要在定义widget前先定义好,然后通过wcontrols和num_kcontrols参数传递给这些辅助定义宏。
4.4 音频数据流域
是指那些需要处理音频数据流的widget,例如ADC、DAC等等。
#define SND_SOC_DAPM_AIF_IN(wname, stname, wchan, wreg, wshift, winvert) \
{ .id = snd_soc_dapm_aif_in, .name = wname, .sname = stname, \
.channel = wchan, SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), }
#define SND_SOC_DAPM_AIF_IN_E(wname, stname, wchan, wreg, wshift, winvert, \
wevent, wflags) \
{ .id = snd_soc_dapm_aif_in, .name = wname, .sname = stname, \
.channel = wchan, SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \
.event = wevent, .event_flags = wflags }
#define SND_SOC_DAPM_AIF_OUT(wname, stname, wchan, wreg, wshift, winvert) \
{ .id = snd_soc_dapm_aif_out, .name = wname, .sname = stname, \
.channel = wchan, SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), }
#define SND_SOC_DAPM_AIF_OUT_E(wname, stname, wchan, wreg, wshift, winvert, \
wevent, wflags) \
{ .id = snd_soc_dapm_aif_out, .name = wname, .sname = stname, \
.channel = wchan, SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \
.event = wevent, .event_flags = wflags }
#define SND_SOC_DAPM_DAC(wname, stname, wreg, wshift, winvert) \
{ .id = snd_soc_dapm_dac, .name = wname, .sname = stname, \
SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert) }
#define SND_SOC_DAPM_DAC_E(wname, stname, wreg, wshift, winvert, \
wevent, wflags) \
{ .id = snd_soc_dapm_dac, .name = wname, .sname = stname, \
SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \
.event = wevent, .event_flags = wflags}
#define SND_SOC_DAPM_ADC(wname, stname, wreg, wshift, winvert) \
{ .id = snd_soc_dapm_adc, .name = wname, .sname = stname, \
SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), }
#define SND_SOC_DAPM_ADC_E(wname, stname, wreg, wshift, winvert, \
wevent, wflags) \
{ .id = snd_soc_dapm_adc, .name = wname, .sname = stname, \
SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \
.event = wevent, .event_flags = wflags}
#define SND_SOC_DAPM_CLOCK_SUPPLY(wname) \
{ .id = snd_soc_dapm_clock_supply, .name = wname, \
.reg = SND_SOC_NOPM, .event = dapm_clock_event, \
.event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD }
除了上面这些widget,还有另外三种widget没有提供显示的定义方法,它们的种类id分别是:
snd_soc_dapm_dai_in
snd_soc_dapm_dai_out
snd_soc_dapm_dai_link
每个codec有多个dai,而cpu(通常就是指某个soc cpu芯片)也会有多个dai,dai注册时,dapm系统会为每个dai创建一个snd_soc_dapm_dai_in或snd_soc_dapm_dai_out类型的widget,通常,这两种widget会和codec中具有相同的stream name的widget进行连接。另外一种情况,当系统中具有多个音频处理器(比如多个codec)时,他们之间可能会通过某两个dai进行连接,当machine驱动确认有这种配置时(通过判断dai_links结构中的param字段),会为他们建立一个dai link把他们绑定在一起,因为有连接关系,两个音频处理器之间的widget的电源状态就可以互相传递。
4.5 dapm kcontrol的定义
上一节提到,对于音频路径上的mixer或mux类型的widget,它们包含了若干个kcontrol,这些被包含的kcontrol实际上就是我们之前讨论的mixer和mux等,dapm利用这些kcontrol完成音频路径的控制。不过,对于widget来说,它的任务还不止这些,dapm还要动态地管理这些音频路径的连结关系,以便可以根据这些连接关系来控制这些widget的电源状态,如果按照普通的方法定义这些kcontrol,是无法达到这个目的的,因此,dapm为我们提供了另外一套定义宏,由它们完成这些被widget包含的kcontrol的定义。
所以和上面的 widget以及普通的kcontrol不一样。
#define SOC_DAPM_DOUBLE(xname, reg, lshift, rshift, max, invert) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
.info = snd_soc_info_volsw, \
.get = snd_soc_dapm_get_volsw, .put = snd_soc_dapm_put_volsw, \
.private_value = SOC_DOUBLE_VALUE(reg, lshift, rshift, max, invert, 0) }
#define SOC_DAPM_DOUBLE_R(xname, lreg, rreg, shift, max, invert) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
.info = snd_soc_info_volsw, \
.get = snd_soc_dapm_get_volsw, .put = snd_soc_dapm_put_volsw, \
.private_value = SOC_DOUBLE_R_VALUE(lreg, rreg, shift, max, invert) }
#define SOC_DAPM_SINGLE(xname, reg, shift, max, invert) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
.info = snd_soc_info_volsw, \
.get = snd_soc_dapm_get_volsw, .put = snd_soc_dapm_put_volsw, \
.private_value = SOC_SINGLE_VALUE(reg, shift, max, invert, 0) }
#define SOC_DAPM_SINGLE_AUTODISABLE(xname, reg, shift, max, invert) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
.info = snd_soc_info_volsw, \
.get = snd_soc_dapm_get_volsw, .put = snd_soc_dapm_put_volsw, \
.private_value = SOC_SINGLE_VALUE(reg, shift, max, invert, 1) }
#define SOC_DAPM_SINGLE_VIRT(xname, max) \
SOC_DAPM_SINGLE(xname, SND_SOC_NOPM, 0, max, 0)
#define SOC_DAPM_SINGLE_TLV(xname, reg, shift, max, invert, tlv_array) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
.info = snd_soc_info_volsw, \
.access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | SNDRV_CTL_ELEM_ACCESS_READWRITE,\
.tlv.p = (tlv_array), \
.get = snd_soc_dapm_get_volsw, .put = snd_soc_dapm_put_volsw, \
.private_value = SOC_SINGLE_VALUE(reg, shift, max, invert, 0) }
#define SOC_DAPM_SINGLE_TLV_AUTODISABLE(xname, reg, shift, max, invert, tlv_array) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
.info = snd_soc_info_volsw, \
.access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | SNDRV_CTL_ELEM_ACCESS_READWRITE,\
.tlv.p = (tlv_array), \
.get = snd_soc_dapm_get_volsw, .put = snd_soc_dapm_put_volsw, \
.private_value = SOC_SINGLE_VALUE(reg, shift, max, invert, 1) }
#define SOC_DAPM_SINGLE_TLV_VIRT(xname, max, tlv_array) \
SOC_DAPM_SINGLE(xname, SND_SOC_NOPM, 0, max, 0, tlv_array)
#define SOC_DAPM_ENUM(xname, xenum) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
.info = snd_soc_info_enum_double, \
.get = snd_soc_dapm_get_enum_double, \
.put = snd_soc_dapm_put_enum_double, \
.private_value = (unsigned long)&xenum }
#define SOC_DAPM_ENUM_EXT(xname, xenum, xget, xput) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
.info = snd_soc_info_enum_double, \
.get = xget, \
.put = xput, \
.private_value = (unsigned long)&xenum }
#define SOC_DAPM_PIN_SWITCH(xname) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname " Switch", \
.info = snd_soc_dapm_info_pin_switch, \
.get = snd_soc_dapm_get_pin_switch, \
.put = snd_soc_dapm_put_pin_switch, \
.private_value = (unsigned long)xname }
SOC_DAPM_SINGLE对应与普通控件的SOC_SINGLE,SOC_DAPM_SINGLE_TLV对应SOC_SINGLE_TLV…,相比普通的kcontrol控件,dapm的kcontrol控件只是把info,get,put回调函数换掉了。dapm kcontrol的put回调函数不仅仅会更新控件本身的状态,他还会把这种变化传递到相邻的dapm kcontrol,相邻的dapm kcontrol又会传递这个变化到他自己相邻的dapm kcontrol,知道音频路径的末端,通过这种机制,只要改变其中一个widget的连接状态,与之相关的所有widget都会被扫描并测试一下自身是否还在有效的音频路径中,从而可以动态地改变自身的电源状态,这就是dapm的精髓所在。