Linux ALSA 之十三:ASOC DAPM 简介 & Widget/Kcontrol 定义


一、什么是 DAPM

DAPM 是 Dynamic Audio Power Management 的缩写,即动态音频电源管理,旨在允许便携式 Linux 设备在任何时候使用音频子系统中的最小电量。它独立于其他内核 Power Manager,故可以很容易地与其他 PM 系统共存。DAPM 对所有用户空间应用程序来说也是完全透明的,因为所有电源切换都是在 ASoC 核心内完成。对于用户空间应用程序,不需要更改代码或重新编译。DAPM 根据当前激活的音频流(playback/capture)和声卡中的 mixer 等的配置来决定哪些音频控件的电源开关被打开或关闭。

在前面 SoC Kcontrol 中知道,利用 Kcontrol,我们可以完成对音频系统中的 mixer、mux、音量控制、音效控制,以及各种开关量的控制,通过对各种 kontrol 的控制,使得音频硬件能够按照我们预想的结果进行工作。同时我们可以看到,kcontrol 还是有以下几点不足:

  • 只能描述自身,无法描述各个 kcontrol 之间的连接关系;
  • 没有相应的电源管理机制;
  • 没有相应的时间处理机制来响应播放、停止、上电、下电等音频事件;
  • 为了防止 pop-pop 声,需要用户程序关注各个 kcontrol 上电和下电的顺序;
  • 当一个音频路径不再有效时,不能自动关闭该路径上的所有的 kcontrol.

为此,DAPM 框架正是为了解决以上这些问题而诞生的。

二、DAPM 的基本单元 Widget

2.1 Widget 的定义

如前面所述,DAPM 框架为了解决前面提到的 kcontrol 不足的问题,引入了 widget 这一概念。所谓 widget具备路径和电源管理的 kcontrol,其实可以理解为是 kcontrol 的进一步升级和封装,它同样是指音频系统中的某个部件,比如 mixer,mux,输入输出引脚,电源供应器等等,甚至,我们可以定义虚拟的 widget,例如 playback stream widget。widget 把 kcontrol 和动态电源管理进行了有机的结合,同时还具备音频路径的连结功能,一个 widget 可以和它相邻的 widget 有某种动态的连结关系。在 DAPM 框架中,widget 用结构体 snd_soc_dapm_widget 来描述:
定义位于:include/sound/soc-dapm.h

/* 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;
}

2.2 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 */
}

三、DAPM 辅助定义 Widget & Kcontrol

本节的内容将会介绍如何使用 DAPM 系统提供的一些辅助宏定义来定义各种类型的 widget 和它所用到的 kcontrol。

3.1 辅助宏定义 Widget

和普通的 kcontrol 一样,DAPM 框架为我们提供了大量的辅助宏来定义各种各样的 widget 控件,这些宏定义根据 widget 的类型,按照它们电源所在的域,被分为了几个域,它们分别是:

(1)Codec 域 Widget
比如 VREF 和 VMID 等提供参考电压的 widget,这些 widget 通常在 codec 的 probe/remove() 回调函数中进行控制,当然,在工作中如果没有音频流时,也可以适当地进行控制它们的开启与关闭。
目前 DAPM 框架只提供了一个 Codec 域 Widget 的辅助宏:

/* codec domain */
#define SND_SOC_DAPM_VMID(wname) \
{	.id = snd_soc_dapm_vmid, .name = wname, .kcontrol_news = NULL, \
	.num_kcontrols = 0}

(2)Platform 域 Widget
位于该域上的 widget 通常是针对平台或板子的一些需要物理连接的输入/输出接口,例如耳机、扬声器、麦克风, 因为这些接口在每块板子上都可能不一样,所以通常它们是在 machine 驱动中进行定义和控制,并且也可以由用户空间的应用程序通过某种方式来控制它们的打开和关闭。
DAPM 框架为我们提供了多种 platform 域 widget 的辅助定义宏:

/* platform domain */
#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 连接起来。

(3)音频路径域 Widget
一般是指 codec 内部的 mixer、mux 等控制音频路径的 widget,这些 widget 可以根据用户空间的设定连接关系,自动设定它们的电源状态。这种 widget 通常是对普通 kcontrols 控件的再封装,增加音频路径和电源管理功能,所以这种 widget 会包含一个或多个 kcontrol,普通 kcontrol 的定义方法在前面 “ALSA ASOC Kcontrol” 小节中已经介绍过,不过这些被包含的 kcontrol 不能使用这种方法定义,它们需要使用 dapm 框架提供的定义宏来定义,详细的讨论后面有介绍,这里先列出这些 widget 的定义宏:

/* path domain */
#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 参数传递给这些辅助定义宏。

如果需要自定义这些 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

(4)音频数据流域 Widget
是指那些需要处理音频数据流的 widget,例如 ADC、DAC 等等。这些 Widget 主要包含音频输入/输出接口,ADC/DAC 等等:

/* stream domain */
#define SND_SOC_DAPM_AIF_IN(wname, stname, wslot, wreg, wshift, winvert) \
{	.id = snd_soc_dapm_aif_in, .name = wname, .sname = stname, \
	SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, 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, \
	SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, 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, \
	SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, 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, \
	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

还记得 ASOC 架构中 Codec 中的 snd_soc_dai 结构吗?每个 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 的电源状态就可以互相传递。

除了上面的还有几个通用的 widget,它们的定义方法如下:

/* generic widgets */
#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, .shift = wshift, .mask = wmask, \
	.on_val = won_val, .off_val = woff_val, }
#define SND_SOC_DAPM_SUPPLY(wname, wreg, wshift, winvert, wevent, wflags) \
{	.id = snd_soc_dapm_supply, .name = wname, \
	SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, 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, \
	.on_val = wflags}
#define SND_SOC_DAPM_PINCTRL(wname, active, sleep) \
{	.id = snd_soc_dapm_pinctrl, .name = wname, \
	.priv = (&(struct snd_soc_dapm_pinctrl_priv) \
		{ .active_state = active, .sleep_state = sleep,}), \
	.reg = SND_SOC_NOPM, .event = dapm_pinctrl_event, \
	.event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD }

3.2 辅助宏定义 DAPM Kcontrol

上一节提到,对于音频路径上的 mixer 或 mux 类型的 widget,它们包含了若干个 kcontrol,这些被包含的 kcontrol 实际上就是我们之前讨论的 mixer 和 mux 等,dapm 利用这些 kcontrol 完成音频的控制。不过,对于 widget 来说,它的任务还不止这些,dapm 还要动态地管理这些音频路径的连结关系,以便可以根据这些连接关系来控制这些 widget 的电源状态,如果按照普通的方法定义这些 kcontrol,是无法达到这个目的的,因此,dapm 为我们提供另外一套定义宏,由它们完成这些被 widget 包含的 kcontrol 的定义,如下:

/* dapm kcontrol types */
#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 的精髓所在。这里只是提一下这种概念,后续的章节会有较为详细的结合代码的分析过程。

3.3 建议 Widget & Route 举例

上面介绍了一大堆的辅助宏,那么,对于一个实际的系统,我们怎么定义我们需要的 widget?怎样定义 widget 的连接关系?下面我们还是以 Wolfson 公司的 codec 芯片 WM8960 为例:
在这里插入图片描述
Left/Right DAC->Left/Right Output Mixer->L/ROUT1 PGA->HP_L/R

  1. 利用辅助宏定义 widget 所需要的 dapm kcontrol;在这里插入图片描述
    以上,定义了 wm8960 中左右声道的 output mixer 控件:
    wm8960_l/routput_mixer;

  2. 定义真正的 widget,包含第一步定义好的 dapm 控件;
    在这里插入图片描述
    这一步,分别为左右声道定义了 Left/Right DAC、L/ROUT1 PGA、HP_L/R Output Widget,也分别定义了 Left/Right Output Mixer Widget,具体 mixer 控制由上一步定义的 wm8960_l/routput_mixer kcontrol 来完成,上述中对于非 SND_SOC_NOPM 参数的(即除 HP_L/R 以外),都是具备电源属性,即当这些 Widget 在一条有效的音频路径上时,dapm 框架可以通过寄存器来控制它们的电源状态。

  3. 定义这些 Widget 的连接路径;
    在这里插入图片描述
    由该 snd_soc_dapm_route 定义知道其 route 即为上述所述:
    Left/Right DAC->Left/Right Output Mixer->L/ROUT1 PGA->HP_L/R

  4. 在 Codec 驱动的 probe 回调中注册这些 Widget 和路径。
    在这里插入图片描述
    在这里插入图片描述
    在 Machine 驱动中,我们可以用同样的方式定义和注册板子特有的 Widget 和路径信息。

参考链接:linux-alsa详解9之DAPM详解2widget基本知识

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值