上一节中,介绍了DAPM框架中几个重要的数据结构:snd_soc_dapm_widget,snd_soc_dapm_path,snd_soc_dapm_route。其中snd_soc_dapm_path无需我们自己定义,它会在注册snd_soc_dapm_route时动态地生成,但是对于系统中的widget和route,我们是需要自己进行定义的,另外,widget所包含的kcontrol与普通的kcontrol有所不同,它们的定义方法与标准的kcontrol也有所不同。本节的内容我将会介绍如何使用DAPM系统提供的一些辅助宏定义来定义各种类型的widget和它所用到的kcontrol。
声明:本博内容均由http://blog.csdn.net/droidphone原创,转载请注明出处,谢谢!
/*****************************************************************************************************/
定义widget
和普通的kcontrol一样,DAPM框架为我们提供了大量的辅助宏用来定义各种各样的widget控件,这些宏定义根据widget的类型,按照它们的电源所在的域,被分为了几个域,他们分别是:
- codec域 比如VREF和VMID等提供参考电压的widget,这些widget通常在codec的probe/remove回调中进行控制,当然,在工作中如果没有音频流时,也可以适当地进行控制它们的开启与关闭。
- platform域 位于该域上的widget通常是针对平台或板子的一些需要物理连接的输入/输出接口,例如耳机、扬声器、麦克风,因为这些接口在每块板子上都可能不一样,所以通常它们是在machine驱动中进行定义和控制,并且也可以由用户空间的应用程序通过某种方式来控制它们的打开和关闭。
- 音频路径域 一般是指codec内部的mixer、mux等控制音频路径的widget,这些widget可以根据用户空间的设定连接关系,自动设定他们的电源状态。
- 音频数据流域 是指那些需要处理音频数据流的widget,例如ADC、DAC等等。
codec域widget的定义
目前,DAPM框架只提供了定义一个codec域widget的辅助宏:
- #define SND_SOC_DAPM_VMID(wname) \
- { .id = snd_soc_dapm_vmid, .name = wname, .kcontrol_news = NULL, \
- .num_kcontrols = 0}
platform域widget的定义
DAPM框架为我们提供了多种platform域widget的辅助定义宏:- #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_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}
音频路径(path)域widget的定义
这种widget通常是对普通kcontrols控件的再封装,增加音频路径和电源管理功能,所以这种widget会包含一个或多个kcontrol,普通kcontrol的定义方法我们在 ALSA声卡驱动中的DAPM详解之一:kcontrol中已经介绍过,不过这些被包含的kcontrol不能使用这种方法定义,它们需要使用dapm框架提供的定义宏来定义,详细的讨论我们后面有介绍。这里先列出这些widget的定义宏:- #define SND_SOC_DAPM_PGA(wname, wreg, wshift, winvert,\
- wcontrols, wncontrols) \
- { .id = snd_soc_dapm_pga, .name = wname, .reg = wreg, .shift = wshift, \
- .invert = 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, .reg = wreg, .shift = wshift, \
- .invert = 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, .reg = wreg, .shift = wshift, \
- .invert = 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, .reg = wreg, \
- .shift = wshift, .invert = winvert, .kcontrol_news = wcontrols, \
- .num_kcontrols = wncontrols}
- #define SND_SOC_DAPM_MICBIAS(wname, wreg, wshift, winvert) \
- { .id = snd_soc_dapm_micbias, .name = wname, .reg = wreg, .shift = wshift, \
- .invert = winvert, .kcontrol_news = NULL, .num_kcontrols = 0}
- #define SND_SOC_DAPM_SWITCH(wname, wreg, wshift, winvert, wcontrols) \
- { .id = snd_soc_dapm_switch, .name = wname, .reg = wreg, .shift = wshift, \
- .invert = winvert, .kcontrol_news = wcontrols, .num_kcontrols = 1}
- #define SND_SOC_DAPM_MUX(wname, wreg, wshift, winvert, wcontrols) \
- { .id = snd_soc_dapm_mux, .name = wname, .reg = wreg, .shift = wshift, \
- .invert = winvert, .kcontrol_news = wcontrols, .num_kcontrols = 1}
- #define SND_SOC_DAPM_VIRT_MUX(wname, wreg, wshift, winvert, wcontrols) \
- { .id = snd_soc_dapm_virt_mux, .name = wname, .reg = wreg, .shift = wshift, \
- .invert = winvert, .kcontrol_news = wcontrols, .num_kcontrols = 1}
- #define SND_SOC_DAPM_VALUE_MUX(wname, wreg, wshift, winvert, wcontrols) \
- { .id = snd_soc_dapm_value_mux, .name = wname, .reg = wreg, \
- .shift = wshift, .invert = winvert, .kcontrol_news = wcontrols, \
- .num_kcontrols = 1}
如果需要自定义这些widget的dapm事件处理回调函数,也可以使用下面这些带“_E”后缀的版本:
- SND_SOC_DAPM_PGA_E
- SND_SOC_DAPM_OUT_DRV_E
- SND_SOC_DAPM_MIXER_E
- SND_SOC_DAPM_MIXER_NAMED_CTL_E
- SND_SOC_DAPM_SWITCH_E
- SND_SOC_DAPM_MUX_E
- SND_SOC_DAPM_VIRT_MUX_E
音频数据流(stream)域widget的定义
这些widget主要包含音频输入/输出接口,ADC/DAC等等:- #define SND_SOC_DAPM_AIF_IN(wname, stname, wslot, wreg, wshift, winvert) \
- { .id = snd_soc_dapm_aif_in, .name = wname, .sname = stname, \
- .reg = wreg, .shift = wshift, .invert = winvert }
- #define SND_SOC_DAPM_AIF_IN_E(wname, stname, wslot, wreg, wshift, winvert, \
- wevent, wflags) \
- { .id = snd_soc_dapm_aif_in, .name = wname, .sname = stname, \
- .reg = wreg, .shift = wshift, .invert = winvert, \
- .event = wevent, .event_flags = wflags }
- #define SND_SOC_DAPM_AIF_OUT(wname, stname, wslot, wreg, wshift, winvert) \
- { .id = snd_soc_dapm_aif_out, .name = wname, .sname = stname, \
- .reg = wreg, .shift = wshift, .invert = winvert }
- #define SND_SOC_DAPM_AIF_OUT_E(wname, stname, wslot, wreg, wshift, winvert, \
- wevent, wflags) \
- { .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 }
- snd_soc_dapm_dai_in
- snd_soc_dapm_dai_out
- snd_soc_dapm_dai_link
除了还有几个通用的widget,他们的定义方法如下:
- #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的定义。
- #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) }
- #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 " 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 }
建立widget和route
上面介绍了一大堆的辅助宏,那么,对于一个实际的系统,我们怎么定义我们需要的widget?怎样定义widget的连接关系?下面我们还是以Wolfson公司的codec芯片WM8993为例子来了解这个过程。
第一步,利用辅助宏定义widget所需要的dapm kcontrol:
- static const struct snd_kcontrol_new left_speaker_mixer[] = {
- SOC_DAPM_SINGLE("Input Switch", WM8993_SPEAKER_MIXER, 7, 1, 0),
- SOC_DAPM_SINGLE("IN1LP Switch", WM8993_SPEAKER_MIXER, 5, 1, 0),
- SOC_DAPM_SINGLE("Output Switch", WM8993_SPEAKER_MIXER, 3, 1, 0),
- SOC_DAPM_SINGLE("DAC Switch", WM8993_SPEAKER_MIXER, 6, 1, 0),
- };
- static const struct snd_kcontrol_new right_speaker_mixer[] = {
- SOC_DAPM_SINGLE("Input Switch", WM8993_SPEAKER_MIXER, 6, 1, 0),
- SOC_DAPM_SINGLE("IN1RP Switch", WM8993_SPEAKER_MIXER, 4, 1, 0),
- SOC_DAPM_SINGLE("Output Switch", WM8993_SPEAKER_MIXER, 2, 1, 0),
- SOC_DAPM_SINGLE("DAC Switch", WM8993_SPEAKER_MIXER, 0, 1, 0),
- };
- static const char *aif_text[] = {
- "Left", "Right"
- };
- static const struct soc_enum aifinl_enum =
- SOC_ENUM_SINGLE(WM8993_AUDIO_INTERFACE_2, 15, 2, aif_text);
- static const struct snd_kcontrol_new aifinl_mux =
- SOC_DAPM_ENUM("AIFINL Mux", 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 Mux", aifinr_enum);
第二步,定义真正的widget,包含第一步定义好的dapm控件:
- static const struct snd_soc_dapm_widget wm8993_dapm_widgets[] = {
- ......
- SND_SOC_DAPM_AIF_IN("AIFINL", "Playback", 0, SND_SOC_NOPM, 0, 0),
- SND_SOC_DAPM_AIF_IN("AIFINR", "Playback", 1, SND_SOC_NOPM, 0, 0),
- ......
- SND_SOC_DAPM_MUX("DACL Mux", SND_SOC_NOPM, 0, 0, &aifinl_mux),
- SND_SOC_DAPM_MUX("DACR Mux", SND_SOC_NOPM, 0, 0, &aifinr_mux),
- SND_SOC_DAPM_MIXER("SPKL", WM8993_POWER_MANAGEMENT_3, 8, 0,
- left_speaker_mixer, ARRAY_SIZE(left_speaker_mixer)),
- SND_SOC_DAPM_MIXER("SPKR", 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控制由上一步定义的left_speaker_mixer和right_speaker_mixer来完成,两个widget具备电源属性,所以,当这两个widget在一条有效的音频路径上时,dapm框架可以通过寄存器WM8993_POWER_MANAGEMENT_3的第8位和第9位控制它的电源状态。
- static const struct snd_soc_dapm_route routes[] = {
- ......
- { "DACL Mux", "Left", "AIFINL" },
- { "DACL Mux", "Right", "AIFINR" },
- { "DACR Mux", "Left", "AIFINL" },
- { "DACR Mux", "Right", "AIFINR" },
- ......
- { "SPKL", "DAC Switch", "DACL" },
- { "SPKL", NULL, "CLK_SYS" },
- { "SPKR", "DAC Switch", "DACR" },
- { "SPKR", NULL, "CLK_SYS" },
- };
- Left
- Right
而SPKL和SPKR有四个输入选择引脚,分别是:
- Input Switch
- IN1LP Switch/IN1RP Switch
- Output Switch
- DAC Switch
- AIFINL连接到DACL Mux的Left输入脚
- AIFINR连接到DACL Mux的Right输入脚
- AIFINL连接到DACR Mux的Left输入脚
- AIFINR连接到DACR Mux的Right输入脚
- DACL连接到SPKL的DAC Switch输入脚
- DACR连接到SPKR的DAC Switch输入脚
- static int wm8993_probe(struct snd_soc_codec *codec)
- {
- ......
- snd_soc_dapm_new_controls(dapm, wm8993_dapm_widgets,
- ARRAY_SIZE(wm8993_dapm_widgets));
- ......
- snd_soc_dapm_add_routes(dapm, routes, ARRAY_SIZE(routes));
- ......
- }
在machine驱动中,我们可以用同样的方式定义和注册板子特有的widget和路径信息。