一、DAPM简介
DAPM是Dynamic Audio Power Management的缩写,直译过来就是动态音频电源管理的意思,DAPM是为了使基于linux的移动设备上的音频子系统,在任何时候都工作在最小功耗状态下。DAPM对用户空间的应用程序来说是透明的,所有与电源相关的开关都在ASoc core中完成。DAPM根据当前激活的音频流(playback/capture)和声卡中的mixer等的配置来决定那些音频控件的电源开关被打开或关闭。
二、DAPM的抽象
1. kcontrol
通常,一个kcontrol代表着一个mixer(混音器),或者是一个mux(多路开关),又或者是一个音量控制器等等。定义一个kcontrol主要就是定义一个snd_kcontrol_new结构.对于每个控件,都需要定义一个和他对应的snd_kcontrol_new结构,这些snd_kcontrol_new结构会在声卡的初始化阶段,通过snd_soc_add_codec_controls()注册到系统中,用户空间就可以通过amixer或alsamixer(Linux ala-slib)或tinymix(Android tinyAlsa)等工具查看和设定这些控件的状态。
定义控件使用的宏举例:
SOC_SINGLE 简单型的控件
SOC_SINGLE_TLV 是SOC_SINGLE的一种扩展,主要用于定义那些有增益控制的控件,例如音量控制器,EQ均衡器等等。
SOC_DOUBLE 与SOC_SINGLE相对应,区别是SOC_SINGLE只控制一个变量,而SOC_DOUBLE则可以同时在一个寄存器中控制两个相似的变量,最常用的就是用于一些立体声的控件,需要同时对左右声道进行控制的情况,因为多了一个声道,参数也就相应地多了一个shift位移值。
SOC_SINGLE_EXT 像这样的后面带_EXT的可以指定自己的put和get成员回调函数而不是默认的。
这些宏中使用soc_mixer_control结构来描述mixer控件的寄存器信息,soc_enum结构来描述mux控件的寄存器信息。
用户空间对kcontrol的修改,最终都会调用到kcontrol的put回调函数。
2. widget
音频控件单元抽象为kcontrol,DAPM的基本单元是widget,使用snd_soc_dapm_widget结构表示,它是对kcontrol进一步的封装,是kcontrol和开关(有的是组件前面的Switch和组件组成一个widget,有的是后面的)的组合体。
一个widget用snd_soc_dapm_widget结构体来描述,通常,会根据音频硬件的组成,分别在声卡的codec驱动、platform驱动和machine驱动中定义一组widget,这些widget用数组进行组织,我们一般会使用dapm框架提供的大量的辅助宏来定义这些widget数组。
widget把kcontrol和动态电源管理进行了有机的结合,同时还具备音频路径的连结功能,一个widget可以与它相邻的widget有某种动态的连结关系。
widget所包含的kcontrol与普通的kcontrol有所不同,它们的定义方法与标准的kcontrol也有所不同。普通snd_kcontrol_new结构可以使用宏例如SOC_SINGLE("ALC Max Gain", WM8960_ALC1, 4, 7, 0)进行初始化,而widget的snd_kcontrol_new结构使用SOC_DAPM_SINGLE("LINPUT2 Switch", WM8960_LINPATH, 6, 1, 0)初始化,区别是初始化回调函数不同。
SoC动态音频电源管理,最多可以拥有4个电源域的宏来初始化snd_kcontrol_new结构
(1). Codec domain - VREF,VMID通常在codec的probe/remove时控制。但如果侧音不需要上电,可以在stream time设置。
(2). Platform/Machine domain - 物理的输入和输出连接是平台/机器和用户操作特定的,在Machine驱动程序和用户空间中设置,例如插入HP时的处理。
(3). Path domain - 在用户更改混音器(mix)和多路复用器(mux)设置时自动设置内部编解码器路径混合器。
(4). Stream domain - DAC和ADC。 启用流playback/capture时启用。
从soc-dapm.h中宏的reg看出,只有Path domain和Stream domain宏中指定了电源管理寄存器。
一个电源域对应一个dapm context,使用snd_soc_dapm_context结构表示。属于codec中的widget位于一个dapm context中;属于platform的widget位于一个dapm context中;属于整个声卡的widget位于一个dapm context中。
对于音频系统的硬件来说,通常要提供合适的偏置电压才能正常地工作,有了dapm context这种组织方式,我们可以方便地对同一组widget进行统一的偏置电压管理。
snd_soc_dapm_context被实体内嵌到代表codec(snd_soc_codec )、platform(snd_soc_platform )、card(snd_soc_card )、dai(snd_soc_dai )的结构体中:
定义麦克风,耳机,扬声器,线路输入接口这几种widget,还可以定义一个dapm事件回调函数wevent,因为其是外接设备,提供回调用于事件通知。
/* codec domain */
SND_SOC_DAPM_VMID(wname)
/* platform domain */
SND_SOC_DAPM_INPUT(wname)
SND_SOC_DAPM_OUTPUT(wname)
SND_SOC_DAPM_MIC(wname, wevent)
SND_SOC_DAPM_HP(wname, wevent)
SND_SOC_DAPM_SPK(wname, wevent)
SND_SOC_DAPM_LINE(wname, wevent)
如果需要自定义这些widget的dapm事件处理回调函数,也可以使用下面这些带“_E”后缀的版本,eg: SND_SOC_DAPM_PGA_E,这类的宏可以指定event_flag用于表示对哪些事件感兴趣。
为了防止pop-pop声,需要用户程序关注各个widget上电和下电的顺序。各个widget的上下电次序表示在两个全局数组dapm_up_seq/dapm_down_seq 中。消pop音延时的时长导出到debugfs了,文件名为dapm_pop_time
dai widget应该是用于抽象Soc的dai接口到path中:
dai widget又分为cpu dai widget和codec dai widget,它们在machine驱动分别匹配上相应的codec和platform后,由soc_probe_platform和soc_probe_codec这两个函数通过调用dapm的api函数snd_soc_dapm_new_dai_widgets()来创建的,通常会为playback和capture各自创建一个dai widget,他们的类型分别是:
snd_soc_dapm_dai_in 对应playback dai
snd_soc_dapm_dai_out 对应capture dai
另外,dai widget的名字是使用stream name来命名的,他通常来自snd_soc_dai_driver中的stream_name字段。dai widget的sname字段也使用同样的名字。
dai widget和stream widget是通过stream name进行匹配的
用struct snd_soc_pcm_stream表示,Codec驱动中的struct snd_soc_dai_driver结构中动态指定playback/capture两个stream widget。
把dai widget和stream widget连接在一起,就是为了能把ASoc中的pcm处理部分和dapm进行关联,pcm的处理过程中,会通过发出stream event来通知dapm系统,重新扫描并调整音频路径上各个widget的电源状态。
因为dai widget和codec上的stream widget是相连的,所以,dai widget的激活状态改变,会沿着音频路径传递到路径上的所有widget,等dapm_power_widgets返回后,如果发出的是SND_SOC_DAPM_STREAM_START事件,路径上的所有widget会处于上电状态,保证音频数据流的顺利播放,如果发出的是SND_SOC_DAPM_STREAM_STOP事件,路径上的所有widget会处于下电状态,保证最小的功耗水平。
3. route
route用于指定两个widget之间的连接关系,使用snd_soc_dapm_route结构表示,通常,所有的路径信息会用一个snd_soc_dapm_route结构数组来定义。和widget一样,路径信息也分别存在与codec驱动,machine驱动和platform驱动中。
snd_soc_dapm_route在定义的时候若是两个widget之间是直连的,没有经过开关Switch,那么kcontrol字符串就初始化为NULL。
4. path
route注册后被解析成path,使用snd_soc_dapm_path结构表示。kcontrol和widget都可以通过静态定义。但是path只能通过route解析而来。
一个path表示一段连接,至少包含:source widget,跳线path,sink widget。在DAPM中,用snd_soc_dapm_route结构来描述这样一个连接关系。
5. complete path
dapm要给一个widget上电的其中一个前提条件是:这个widget位于一条完整的音频路径上,而一条完整的音频路径的两头,必须是输入/输出端,或者是一个外部音频设备,又或者是一个处于激活状态的音频流widget。分别用is_connected_output_ep()和is_connected_input_ep()得到该widget是否有同时连接到一个输入端和一个输出端,如果是,返回1来表示该widget需要上电。
在扫描dapm_dirty链表时,dapm使用两个链表来分别保存需要上电和需要下电的widget,扫描完毕后按照上电次序,统一对各个widget上电。
up_list 保存需要上电的widget
down_list 保存需要下电的widget。
三、参考
ALSA声卡驱动中的DAPM详解之一:kcontrol
ALSA声卡驱动中的DAPM详解之二:widget-具备路径和电源管理信息的kcontrol
ALSA声卡驱动中的DAPM详解之三:如何定义各种widget
ALSA声卡驱动中的DAPM详解之四:在驱动程序中初始化并注册widget和route
ALSA声卡驱动中的DAPM详解之五:建立widget之间的连接关系
ALSA声卡驱动中的DAPM详解之六:精髓所在,牵一发而动全身
ALSA声卡驱动中的DAPM详解之七:dapm事件机制(dapm event)