ALSA driver---DAPM 2

Posted fellow1988

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ALSA driver---DAPM 2相关的知识,希望对你有一定的参考价值。

定义widget

There are 4 power domains within DAPM:

  1. Codec domain – VREF, VMID (core codec and audio power). Usually controlled at codec probe/remove and suspend/resume, although can be set at stream time if power is not needed for sidetone, etc.
  2. Platform/Machine domain – physically connected inputs and outputs. Is platform/machine and user action specific, is configured by the machine driver and responds to asynchronous events. e.g when HP are inserted
  3. Path domain – audio subsystem signal paths. Automatically set when mixer and mux settings are changed by the user. e.g. alsamixer, amixer.
  4. Stream domain – DAC‘s and ADC‘s. Enabled and disabled when stream playback/capture is started and stopped respectively. e.g. aplay, arecord.

DAPM框架为我们提供了大量的辅助宏用来定义各种各样的widget控件.

widget 结构体如下:

* dapm widget */
struct snd_soc_dapm_widget {
    enum snd_soc_dapm_type id;
    const char *name;        /* widget name */
    const char *sname;    /* stream name */
    struct list_head list;
    struct snd_soc_dapm_context *dapm;

    void *priv;                /* widget specific data */
    struct regulator *regulator;        /* attached 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 */
    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 */
    unsigned char active:1;            /* active stream on DAC, ADC‘s */
    unsigned char connected:1;        /* connected codec pin */
    unsigned char new:1;            /* cnew complete */
    unsigned char force:1;            /* force state */
    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 */
    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;
};

widget的type:

/* dapm widget types */
enum snd_soc_dapm_type {
    snd_soc_dapm_input = 0,        /* input pin */
    snd_soc_dapm_output,        /* output pin */
    snd_soc_dapm_mux,            /* selects 1 analog signal from many inputs */
    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 */
    snd_soc_dapm_pga,            /* programmable gain/attenuation (volume) */
    snd_soc_dapm_out_drv,            /* output driver */
    snd_soc_dapm_adc,            /* analog to digital converter */
    snd_soc_dapm_dac,            /* digital to analog converter */
    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 */
    snd_soc_dapm_pre,            /* machine specific pre widget - exec first */
    snd_soc_dapm_post,            /* machine specific post widget - exec last */
    snd_soc_dapm_supply,        /* power/clock supply */
    snd_soc_dapm_regulator_supply,    /* external regulator */
    snd_soc_dapm_clock_supply,    /* external clock */
    snd_soc_dapm_aif_in,        /* audio interface input */
    snd_soc_dapm_aif_out,        /* audio interface output */
    snd_soc_dapm_siggen,        /* signal generator */
    snd_soc_dapm_sink,
    snd_soc_dapm_dai_in,        /* link to DAI structure */
    snd_soc_dapm_dai_out,
    snd_soc_dapm_dai_link,        /* link between two DAI structures */
    snd_soc_dapm_kcontrol,        /* Auto-disabled kcontrol */
};

 

codec domain:

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

 

platform domain:

platform domain的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连接起来。

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

  

path domain:

path domain 的widget通常是对普通kcontrols控件的再封装,增加音频路径和电源管理功能,所以这种widget会包含一个或多个kcontrol,这些widget的reg和shift字段是需要赋值的,说明这些widget是有相应的电源控制寄存器的,DAPM框架在扫描和更新音频路径时,会利用这些寄存器来控制widget的电源状态,使得它们的供电状态是按需分配的,需要的时候(在有效的音频路径上)上电,不需要的时候(不再有效的音频路径上)下电。

#define SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert) 
    .reg = wreg, .mask = 1, .shift = wshift,     .on_val = winvert ? 0 : 1, .off_val = winvert ? 1 : 0

/* 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需要完成和的mixer、mux等控件同样的功能,实际上,这是通过它们包含的kcontrol控件来完成的,这些kcontrol我们需要在定义widget前先定义好,然后通过wcontrols和num_kcontrols参数传递给这些辅助定义宏。dapm利用这些kcontrol完成音频路径的控制。不过,对于widget来说,它的任务还不止这些,dapm还要动态地管理这些音频路径的连结关系,以便可以根据这些连接关系来控制这些widget的电源状态,如果按照普通的方法定义这些kcontrol,是无法达到这个目的的,因此,dapm为我们提供了另外一套定义宏,由它们完成这些被widget包含的kcontrol的定义。

/* dapm kcontrol types */
#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的精髓所在。

 

stream domain:

这些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 }

定义route

route 表示widget的连接路径(Destination Widget <=== Path Name <=== Source Widget)。route结构体如下:

/*
 * DAPM audio route definition.
 *
 * Defines an audio route originating at source via control and finishing
 * at sink.
 */
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);
};

 

以sound/soc/codecs/tlv320aic23.c 为例,以下是tlv320 codec driver定义的widget定义的widgets和route

DAPM Widgets:

static const struct snd_soc_dapm_widget tlv320aic23_dapm_widgets[] = {
    SND_SOC_DAPM_DAC("DAC", "Playback", TLV320AIC23_PWR, 3, 1),
    SND_SOC_DAPM_ADC("ADC", "Capture", TLV320AIC23_PWR, 2, 1),
    SND_SOC_DAPM_MUX("Capture Source", SND_SOC_NOPM, 0, 0,
             &tlv320aic23_rec_src_mux_controls),
    SND_SOC_DAPM_MIXER("Output Mixer", TLV320AIC23_PWR, 4, 1,
               &tlv320aic23_output_mixer_controls[0],
               ARRAY_SIZE(tlv320aic23_output_mixer_controls)),
    SND_SOC_DAPM_PGA("Line Input", TLV320AIC23_PWR, 0, 1, NULL, 0),
    SND_SOC_DAPM_PGA("Mic Input", TLV320AIC23_PWR, 1, 1, NULL, 0),

    SND_SOC_DAPM_OUTPUT("LHPOUT"),
    SND_SOC_DAPM_OUTPUT("RHPOUT"),
    SND_SOC_DAPM_OUTPUT("LOUT"),
    SND_SOC_DAPM_OUTPUT("ROUT"),

    SND_SOC_DAPM_INPUT("LLINEIN"),
    SND_SOC_DAPM_INPUT("RLINEIN"),

    SND_SOC_DAPM_INPUT("MICIN"),
};

widget kcontrol:

/* PGA Mixer controls for Line and Mic switch */
static const struct snd_kcontrol_new tlv320aic23_output_mixer_controls[] = {
    SOC_DAPM_SINGLE("Line Bypass Switch", TLV320AIC23_ANLG, 3, 1, 0),
    SOC_DAPM_SINGLE("Mic Sidetone Switch", TLV320AIC23_ANLG, 5, 1, 0),
    SOC_DAPM_SINGLE("Playback Switch", TLV320AIC23_ANLG, 4, 1, 0),
};

DAPM routes:

static const struct snd_soc_dapm_route tlv320aic23_intercon[] = {
    /* Output Mixer */
    {"Output Mixer", "Line Bypass Switch", "Line Input"},
    {"Output Mixer", "Playback Switch", "DAC"},
    {"Output Mixer", "Mic Sidetone Switch", "Mic Input"},

    /* Outputs */
    {"RHPOUT", NULL, "Output Mixer"},
    {"LHPOUT", NULL, "Output Mixer"},
    {"LOUT", NULL, "Output Mixer"},
    {"ROUT", NULL, "Output Mixer"},

    /* Inputs */
    {"Line Input", "NULL", "LLINEIN"},
    {"Line Input", "NULL", "RLINEIN"},

    {"Mic Input", "NULL", "MICIN"},

    /* input mux */
    {"Capture Source", "Line", "Line Input"},
    {"Capture Source", "Mic", "Mic Input"},
    {"ADC", NULL, "Capture Source"},

};

capture audio path:

LLININ->Line Input->Catpture source ->ADC

MICIN->Mic Input->Catpute source->ADC

playback audio path:

DAC->Ouptput Mixer->LOUT/LHPOUT/ROUT/RHPOUT

LLININ->Line Input->Output Mixer->LOUT

MICIN->Mic Input ->Output Mixer->LOUT/LHPOUT/ROUT/RHPOUT

 

创建widget:

DAPM widget和route定义在CPU DAI driver和Codec driver的component driver.当调用snd_soc_register_component()注册CPU DAI , 调用snd_soc_register_codec()注册Codec时,都会创建snd_soc_component类型的component, 并调用snd_soc_component_initialize()将component driver中定义的widgets和route赋值给component。

 

以上是关于ALSA driver---DAPM 2的主要内容,如果未能解决你的问题,请参考以下文章

从 C 代码设置 ALSA 主音量

找不到 alsa/asoundlib.h

linux 音频子系统代码分析

Linux音频编程声卡介绍

Linux ALSA源码分析(基于Linux 5.18)

Linux ALSA源码分析(基于Linux 5.18)