{ .id = snd_soc_dapm_aif_out, .name = wname, .sname = stname, \\
.reg = wreg, .shift = wshift, .invert = 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, .reg = wreg, \\
.shift = wshift, .invert = winvert}
#define SND_SOC_DAPM_DAC_E(wname, stname, wreg, wshift, winvert, \\
wevent, wflags) \\
{ .id = snd_soc_dapm_dac, .name = wname, .sname = stname, .reg = wreg, \\
.shift = wshift, .invert = 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, .reg = wreg, \\
.shift = wshift, .invert = winvert}
#define SND_SOC_DAPM_ADC_E(wname, stname, wreg, wshift, winvert, \\
wevent, wflags) \\
{ .id = snd_soc_dapm_adc, .name = wname, .sname = stname, .reg = wreg, \\
.shift = wshift, .invert = 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
还记得我们在Linux ALSA声卡驱动之七:ASoC架构中的Codec中的
snd_soc_dai结构吗?每个codec有多个dai,而cpu(通常就是指某个soc cpu芯片)也会有多个dai,dai注册时,dapm系统会为每个dai创建一个
21
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的电源状态就可以互相传递。 除了还有几个通用的widget,他们的定义方法如下: [cpp] view plaincopy
#define SND_SOC_DAPM_REG(wid, wname, wreg, wshift, wmask, won_val, woff_val) \\
{ .id = wid, .name = wname, .kcontrol_news = NULL, .num_kcontrols = 0, \\
.reg = -((wreg) + 1), .shift = wshift, .mask = wmask, \\ .on_val = won_val, .off_val = woff_val, .event = dapm_reg_event, \\
.event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD}
#define SND_SOC_DAPM_SUPPLY(wname, wreg, wshift, winvert, wevent, wflags) \\
{ .id = snd_soc_dapm_supply, .name = wname, .reg = wreg, \\
.shift = wshift, .invert = winvert, .event = wevent, \\ .event_flags = wflags}
#define SND_SOC_DAPM_REGULATOR_SUPPLY(wname, wdelay, wflags) \\
{ .id = snd_soc_dapm_regulator_supply, .name = wname, \\ .reg = SND_SOC_NOPM, .shift = wdelay, .event = dapm_regulator_event, \\
.event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD, \\
.invert = wflags} 定义dapm kcontrol
上一节提到,对于音频路径上的mixer或mux类型的widget,它们包含了若干个kcontrol,这些被包含的kcontrol实际上就是我们之前讨论的mixer和mux等,dapm利用这些kcontrol完成音频路径的控制。不过,对于widget来说,它的任务还不止这些,dapm还要动态地管理这些音频路径的连结关系,以便可以根据这些连接关系来控制这些widget的电源状态,如果按照普通的方法定义这些
kcontrol,是无法达到这个目的的,因此,dapm为我们提供了另外一套定义宏,由它们完成这些被widget包含的kcontrol的定义。 [cpp] view plaincopy
#define SOC_DAPM_SINGLE(xname, reg, shift, max, invert) \\
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \\
22
.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) }
#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) }
#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_VIRT(xname, xenum) \\ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \\ .info = snd_soc_info_enum_double, \\ .get = snd_soc_dapm_get_enum_virt, \\ .put = snd_soc_dapm_put_enum_virt, \\
.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_VALUE_ENUM(xname, xenum) \\
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \\ .info = snd_soc_info_enum_double, \\
.get = snd_soc_dapm_get_value_enum_double, \\ .put = snd_soc_dapm_put_value_enum_double, \\ .private_value = (unsigned long)&xenum } #define SOC_DAPM_PIN_SWITCH(xname) \\
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname \ch\, \\
.info = snd_soc_dapm_info_pin_switch, \\ .get = snd_soc_dapm_get_pin_switch, \\
23
.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的精髓所在。这里我只提一下这种概念,后续的章节会有较为详细的代码分析过程。
建立widget和route
上面介绍了一大堆的辅助宏,那么,对于一个实际的系统,我们怎么定义我们需要的widget?怎样定义widget的连接关系?下面我们还是以Wolfson公司的codec芯片WM8993为例子来了解这个过程。
第一步,利用辅助宏定义widget所需要的dapm kcontrol: [cpp] view plaincopy
static const struct snd_kcontrol_new left_speaker_mixer[] = { SOC_DAPM_SINGLE(\, WM8993_SPEAKER_MIXER, 7, 1, 0), SOC_DAPM_SINGLE(\, WM8993_SPEAKER_MIXER, 5, 1, 0), SOC_DAPM_SINGLE(\, WM8993_SPEAKER_MIXER, 3, 1, 0),
SOC_DAPM_SINGLE(\, WM8993_SPEAKER_MIXER, 6, 1, 0), };
static const struct snd_kcontrol_new right_speaker_mixer[] = { SOC_DAPM_SINGLE(\, WM8993_SPEAKER_MIXER, 6, 1, 0), SOC_DAPM_SINGLE(\, WM8993_SPEAKER_MIXER, 4, 1, 0), SOC_DAPM_SINGLE(\, WM8993_SPEAKER_MIXER, 2, 1, 0),
SOC_DAPM_SINGLE(\, WM8993_SPEAKER_MIXER, 0, 1, 0), };
static const char *aif_text[] = { \, \ };
static const struct soc_enum aifinl_enum =
SOC_ENUM_SINGLE(WM8993_AUDIO_INTERFACE_2, 15, 2, aif_text);
24
static const struct snd_kcontrol_new aifinl_mux = SOC_DAPM_ENUM(\, aifinl_enum);
static const struct soc_enum aifinr_enum =
SOC_ENUM_SINGLE(WM8993_AUDIO_INTERFACE_2, 14, 2, aif_text);
static const struct snd_kcontrol_new aifinr_mux = SOC_DAPM_ENUM(\, aifinr_enum); 以上,我们定义了wm8993中左右声道的speaker mixer控件:
left_speaker_mixer和right_speaker_mixer,同时还为左右声道各定义了一个叫做AIFINL Mux和AIFINR Mux的输入选择mux控件。
第二步,定义真正的widget,包含第一步定义好的dapm控件: [cpp] view plaincopy
static const struct snd_soc_dapm_widget wm8993_dapm_widgets[] = {
......
SND_SOC_DAPM_AIF_IN(\, \, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN(\, \, 1, SND_SOC_NOPM, 0, 0),
......
SND_SOC_DAPM_MUX(\, SND_SOC_NOPM, 0, 0, &aifinl_mux),
SND_SOC_DAPM_MUX(\, SND_SOC_NOPM, 0, 0, &aifinr_mux),
SND_SOC_DAPM_MIXER(\, WM8993_POWER_MANAGEMENT_3, 8, 0, left_speaker_mixer, ARRAY_SIZE(left_speaker_mixer)),
SND_SOC_DAPM_MIXER(\, WM8993_POWER_MANAGEMENT_3, 9, 0, right_speaker_mixer, ARRAY_SIZE(right_speaker_mixer)), ...... };
这一步,为左右声道各自定义了一个mux widget:DACL Mux和DACR Mux,实际的多路选择由dapm kcontrol:aifinl_mux和aifinr_mux,来完成,因为传入了SND_SOC_NOPM参数,这两个widget不具备电源属性,但是mux的切换会影响到与之相连的其它具备电源属性的电源状态。我们还为左右声道的扬声器各自定义了一个mixer widget:SPKL和SPKR,具体的mixer控制由上一步定义的
25