ALSA driver---DAPM flow


參考:

https://elixir.bootlin.com/linux/v4.9.218/source/sound/soc/soc-dapm.c#L804

https://blog.csdn.net/DroidPhone/article/details/14146319

https://blog.csdn.net/DroidPhone/article/details/14052861

https://blog.csdn.net/whshiyun/article/details/80889838

定義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和path:

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。

 在snd_soc_instantiate_card()會調用soc_probe_link_component()->soc_probe_component()來probe component.

static int soc_probe_component(struct snd_soc_card *card,
    struct snd_soc_component *component)
{
    struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component);
    struct snd_soc_dai *dai;
    int ret;

    if (!strcmp(component->name, "snd-soc-dummy"))
        return 0;

    if (component->card) {
        if (component->card != card) {
            dev_err(component->dev,
                "Trying to bind component to card \"%s\" but is already bound to card \"%s\"\n",
                card->name, component->card->name);
            return -ENODEV;
        }
        return 0;
    }

    if (!try_module_get(component->dev->driver->owner))
        return -ENODEV;

    component->card = card;
    dapm->card = card;
    soc_set_name_prefix(card, component);

    soc_init_component_debugfs(component);

    if (component->dapm_widgets) {
        ret = snd_soc_dapm_new_controls(dapm, component->dapm_widgets,
            component->num_dapm_widgets);

        if (ret != 0) {
            dev_err(component->dev,
                "Failed to create new controls %d\n", ret);
            goto err_probe;
        }
    }

    list_for_each_entry(dai, &component->dai_list, list) {
        ret = snd_soc_dapm_new_dai_widgets(dapm, dai);
        if (ret != 0) {
            dev_err(component->dev,
                "Failed to create DAI widgets %d\n", ret);
            goto err_probe;
        }
    }

    if (component->probe) {
        ret = component->probe(component);
        if (ret < 0) {
            dev_err(component->dev,
                "ASoC: failed to probe component %d\n", ret);
            goto err_probe;
        }

        WARN(dapm->idle_bias_off &&
            dapm->bias_level != SND_SOC_BIAS_OFF,
            "codec %s can not start from non-off bias with idle_bias_off==1\n",
            component->name);
    }

    /* machine specific init */
    if (component->init) {
        ret = component->init(component);
        if (ret < 0) {
            dev_err(component->dev,
                "Failed to do machine specific init %d\n", ret);
            goto err_probe;
        }
    }

    if (component->controls)
        snd_soc_add_component_controls(component, component->controls,
                     component->num_controls);
    if (component->dapm_routes)
        snd_soc_dapm_add_routes(dapm, component->dapm_routes,
                    component->num_dapm_routes);

    list_add(&dapm->list, &card->dapm_list);

    /* This is a HACK and will be removed soon */
    if (component->codec)
        list_add(&component->codec->card_list, &card->codec_dev_list);

    return 0;

err_probe:
    soc_cleanup_component_debugfs(component);
    component->card = NULL;
    module_put(component->dev->driver->owner);

    return ret;
}

在soc_probe_component()里面,

調用snd_soc_dapm_new_controls()對component->dapm_widgets中每個widget創建新的widget,將widget加到card->widgets鏈表中。該函數只是簡單的一個循環,為傳入的widget模板數組依次調用snd_soc_dapm_new_control函數,實際的工作由snd_soc_dapm_new_control完成

/**
 * snd_soc_dapm_new_controls - create new dapm controls
 * @dapm: DAPM context
 * @widget: widget array
 * @num: number of widgets
 *
 * Creates new DAPM controls based upon the templates.
 *
 * Returns 0 for success else error.
 */
int snd_soc_dapm_new_controls(struct snd_soc_dapm_context *dapm,
    const struct snd_soc_dapm_widget *widget,
    int num)
{
    struct snd_soc_dapm_widget *w;
    int i;
    int ret = 0;

    mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_INIT);
    for (i = 0; i < num; i++) {
        w = snd_soc_dapm_new_control_unlocked(dapm, widget);
        if (IS_ERR(w)) {
            ret = PTR_ERR(w);
            /* Do not nag about probe deferrals */
            if (ret == -EPROBE_DEFER)
                break;
            dev_err(dapm->dev,
                "ASoC: Failed to create DAPM control %s (%d)\n",
                widget->name, ret);
            break;
        }
        if (!w) {
            dev_err(dapm->dev,
                "ASoC: Failed to create DAPM control %s\n",
                widget->name);
            ret = -ENOMEM;
            break;
        }
        widget++;
    }
    mutex_unlock(&dapm->card->dapm_mutex);
    return ret;
}

創建widget由snd_soc_dapm_new_control_unlocked()完成,在此函數中調用dapm_cnew_widget()對widget分配內存,設定widget的power_check()函數(為不同類型的widget設置合適的power_check電源狀態回調函數, 當音頻路徑發生變化時,power_check回調會被調用,用於檢查該widget的電源狀態是否需要更新),最后將創建的widget加到card->widget鏈表中。

struct snd_soc_dapm_widget *
snd_soc_dapm_new_control_unlocked(struct snd_soc_dapm_context *dapm,
             const struct snd_soc_dapm_widget *widget)
{
    enum snd_soc_dapm_direction dir;
    struct snd_soc_dapm_widget *w;
    const char *prefix;
    int ret;

    if ((w = dapm_cnew_widget(widget)) == NULL)
        return NULL;

    switch (w->id) {
    case snd_soc_dapm_regulator_supply:
        w->regulator = devm_regulator_get(dapm->dev, w->name);
        if (IS_ERR(w->regulator)) {
            ret = PTR_ERR(w->regulator);
            if (ret == -EPROBE_DEFER)
                return ERR_PTR(ret);
            dev_err(dapm->dev, "ASoC: Failed to request %s: %d\n",
                w->name, ret);
            return NULL;
        }

        if (w->on_val & SND_SOC_DAPM_REGULATOR_BYPASS) {
            ret = regulator_allow_bypass(w->regulator, true);
            if (ret != 0)
                dev_warn(w->dapm->dev,
                     "ASoC: Failed to bypass %s: %d\n",
                     w->name, ret);
        }
        break;
    case snd_soc_dapm_clock_supply:
#ifdef CONFIG_CLKDEV_LOOKUP
        w->clk = devm_clk_get(dapm->dev, w->name);
        if (IS_ERR(w->clk)) {
            ret = PTR_ERR(w->clk);
            if (ret == -EPROBE_DEFER)
                return ERR_PTR(ret);
            dev_err(dapm->dev, "ASoC: Failed to request %s: %d\n",
                w->name, ret);
            return NULL;
        }
#else
        return NULL;
#endif
        break;
    default:
        break;
    }

    prefix = soc_dapm_prefix(dapm);
    if (prefix)
        w->name = kasprintf(GFP_KERNEL, "%s %s", prefix, widget->name);
    else
        w->name = kstrdup_const(widget->name, GFP_KERNEL);
    if (w->name == NULL) {
        kfree(w);
        return NULL;
    }

    switch (w->id) {
    case snd_soc_dapm_mic:
        w->is_ep = SND_SOC_DAPM_EP_SOURCE;
        w->power_check = dapm_generic_check_power;
        break;
    case snd_soc_dapm_input:
        if (!dapm->card->fully_routed)
            w->is_ep = SND_SOC_DAPM_EP_SOURCE;
        w->power_check = dapm_generic_check_power;
        break;
    case snd_soc_dapm_spk:
    case snd_soc_dapm_hp:
        w->is_ep = SND_SOC_DAPM_EP_SINK;
        w->power_check = dapm_generic_check_power;
        break;
    case snd_soc_dapm_output:
        if (!dapm->card->fully_routed)
            w->is_ep = SND_SOC_DAPM_EP_SINK;
        w->power_check = dapm_generic_check_power;
        break;
    case snd_soc_dapm_vmid:
    case snd_soc_dapm_siggen:
        w->is_ep = SND_SOC_DAPM_EP_SOURCE;
        w->power_check = dapm_always_on_check_power;
        break;
    case snd_soc_dapm_sink:
        w->is_ep = SND_SOC_DAPM_EP_SINK;
        w->power_check = dapm_always_on_check_power;
        break;

    case snd_soc_dapm_mux:
    case snd_soc_dapm_demux:
    case snd_soc_dapm_switch:
    case snd_soc_dapm_mixer:
    case snd_soc_dapm_mixer_named_ctl:
    case snd_soc_dapm_adc:
    case snd_soc_dapm_aif_out:
    case snd_soc_dapm_dac:
    case snd_soc_dapm_aif_in:
    case snd_soc_dapm_pga:
    case snd_soc_dapm_out_drv:
    case snd_soc_dapm_micbias:
    case snd_soc_dapm_line:
    case snd_soc_dapm_dai_link:
    case snd_soc_dapm_dai_out:
    case snd_soc_dapm_dai_in:
        w->power_check = dapm_generic_check_power;
        break;
    case snd_soc_dapm_supply:
    case snd_soc_dapm_regulator_supply:
    case snd_soc_dapm_clock_supply:
    case snd_soc_dapm_kcontrol:
        w->is_supply = 1;
        w->power_check = dapm_supply_check_power;
        break;
    default:
        w->power_check = dapm_always_on_check_power;
        break;
    }

    w->dapm = dapm;
    INIT_LIST_HEAD(&w->list);
    INIT_LIST_HEAD(&w->dirty);
    list_add_tail(&w->list, &dapm->card->widgets);

    snd_soc_dapm_for_each_direction(dir) {
        INIT_LIST_HEAD(&w->edges[dir]);
        w->endpoints[dir] = -1;
    }

    /* machine layer sets up unconnected pins and insertions */
    w->connected = 1;
    return w;
}

基於component->dai_list中的dai 通過調用snd_soc_dapm_new_dai_widgets()創建dai widget,也將此widget加到card->widgets鏈表中。

在創建的widget中,其name和sname都是dai driver中的stream name,后面的鏈接時會去匹配這個名字. 

int snd_soc_dapm_new_dai_widgets(struct snd_soc_dapm_context *dapm,
                 struct snd_soc_dai *dai)
{
    struct snd_soc_dapm_widget template;
    struct snd_soc_dapm_widget *w;

    WARN_ON(dapm->dev != dai->dev);

    memset(&template, 0, sizeof(template));
    template.reg = SND_SOC_NOPM;

    if (dai->driver->playback.stream_name) {
        template.id = snd_soc_dapm_dai_in;
        template.name = dai->driver->playback.stream_name;
        template.sname = dai->driver->playback.stream_name;

        dev_dbg(dai->dev, "ASoC: adding %s widget\n",
            template.name);

        w = snd_soc_dapm_new_control_unlocked(dapm, &template);
        if (IS_ERR(w)) {
            int ret = PTR_ERR(w);

            /* Do not nag about probe deferrals */
            if (ret != -EPROBE_DEFER)
                dev_err(dapm->dev,
                "ASoC: Failed to create %s widget (%d)\n",
                dai->driver->playback.stream_name, ret);
            return ret;
        }
        if (!w) {
            dev_err(dapm->dev, "ASoC: Failed to create %s widget\n",
                dai->driver->playback.stream_name);
            return -ENOMEM;
        }

        w->priv = dai;
        dai->playback_widget = w;
    }

    if (dai->driver->capture.stream_name) {
        template.id = snd_soc_dapm_dai_out;
        template.name = dai->driver->capture.stream_name;
        template.sname = dai->driver->capture.stream_name;

        dev_dbg(dai->dev, "ASoC: adding %s widget\n",
            template.name);

        w = snd_soc_dapm_new_control_unlocked(dapm, &template);
        if (IS_ERR(w)) {
            int ret = PTR_ERR(w);

            /* Do not nag about probe deferrals */
            if (ret != -EPROBE_DEFER)
                dev_err(dapm->dev,
                "ASoC: Failed to create %s widget (%d)\n",
                dai->driver->playback.stream_name, ret);
            return ret;
        }
        if (!w) {
            dev_err(dapm->dev, "ASoC: Failed to create %s widget\n",
                dai->driver->capture.stream_name);
            return -ENOMEM;
        }

        w->priv = dai;
        dai->capture_widget = w;
    }

    return 0;
}

 基於component->dapm_routes創建dapm path,並將創建的path加到card->paths.

如果widget之間沒有連接關系,dapm就無法實現動態的電源管理工作,正是widget之間有了連結關系,這些連接關系形成了一條所謂的完成的音頻路徑,dapm可以順着這條路徑,統一控制路徑上所有widget的電源狀態,widget之間是使用snd_soc_dapm_path結構進行連接的,驅動要做的是定義一個snd_soc_route結構數組,該數組的每個條目描述了目的widget的和源widget的名稱,以及控制這個連接的kcontrol的名稱,最終,驅動程序使用api函數snd_soc_dapm_add_routes來注冊這些連接信息.

/**
 * snd_soc_dapm_add_routes - Add routes between DAPM widgets
 * @dapm: DAPM context
 * @route: audio routes
 * @num: number of routes
 *
 * Connects 2 dapm widgets together via a named audio path. The sink is
 * the widget receiving the audio signal, whilst the source is the sender
 * of the audio signal.
 *
 * Returns 0 for success else error. On error all resources can be freed
 * with a call to snd_soc_card_free().
 */
int snd_soc_dapm_add_routes(struct snd_soc_dapm_context *dapm,
                const struct snd_soc_dapm_route *route, int num)
{
    int i, r, ret = 0;

    mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_INIT);
    for (i = 0; i < num; i++) {
        r = snd_soc_dapm_add_route(dapm, route);
        if (r < 0) {
            dev_err(dapm->dev, "ASoC: Failed to add route %s -> %s -> %s\n",
                route->source,
                route->control ? route->control : "direct",
                route->sink);
            ret = r;
        }
        route++;
    }
    mutex_unlock(&dapm->card->dapm_mutex);

    return ret;
}

該函數只是一個循環,依次對參數傳入的數組調用snd_soc_dapm_add_route,主要的工作由snd_soc_dapm_add_route完成。

static int snd_soc_dapm_add_route(struct snd_soc_dapm_context *dapm,
                  const struct snd_soc_dapm_route *route)
{
    struct snd_soc_dapm_widget *wsource = NULL, *wsink = NULL, *w;
    struct snd_soc_dapm_widget *wtsource = NULL, *wtsink = NULL;
    const char *sink;
    const char *source;
    char prefixed_sink[80];
    char prefixed_source[80];
    const char *prefix;
    int ret;

    prefix = soc_dapm_prefix(dapm);
    if (prefix) {
        snprintf(prefixed_sink, sizeof(prefixed_sink), "%s %s",
             prefix, route->sink);
        sink = prefixed_sink;
        snprintf(prefixed_source, sizeof(prefixed_source), "%s %s",
             prefix, route->source);
        source = prefixed_source;
    } else {
        sink = route->sink;
        source = route->source;
    }

    wsource = dapm_wcache_lookup(&dapm->path_source_cache, source);
    wsink = dapm_wcache_lookup(&dapm->path_sink_cache, sink);

    if (wsink && wsource)
        goto skip_search;

    /*
     * find src and dest widgets over all widgets but favor a widget from
     * current DAPM context
     */
    list_for_each_entry(w, &dapm->card->widgets, list) {
        if (!wsink && !(strcmp(w->name, sink))) {
            wtsink = w;
            if (w->dapm == dapm) {
                wsink = w;
                if (wsource)
                    break;
            }
            continue;
        }
        if (!wsource && !(strcmp(w->name, source))) {
            wtsource = w;
            if (w->dapm == dapm) {
                wsource = w;
                if (wsink)
                    break;
            }
        }
    }
    /* use widget from another DAPM context if not found from this */
    if (!wsink)
        wsink = wtsink;
    if (!wsource)
        wsource = wtsource;

    if (wsource == NULL) {
        dev_err(dapm->dev, "ASoC: no source widget found for %s\n",
            route->source);
        return -ENODEV;
    }
    if (wsink == NULL) {
        dev_err(dapm->dev, "ASoC: no sink widget found for %s\n",
            route->sink);
        return -ENODEV;
    }

skip_search:
    dapm_wcache_update(&dapm->path_sink_cache, wsink);
    dapm_wcache_update(&dapm->path_source_cache, wsource);

    ret = snd_soc_dapm_add_path(dapm, wsource, wsink, route->control,
        route->connected);
    if (ret)
        goto err;

    return 0;
err:
    dev_warn(dapm->dev, "ASoC: no dapm match for %s --> %s --> %s\n",
         source, route->control, sink);
    return ret;
}

snd_soc_dapm_add_route()中用widget的名字來比較,遍歷card->widgets鏈表,找出source widget和sink widget,最后調用snd_soc_dapm_add_path()來創建snd_soc_dapm_path結構體的path,將sourc widget和sink widget賦值給相應的成員,根據source 和sink widget的control(path name) 來初始化path->connect,如果有control,讀取path->sink->kcontrol_new的register值來初始化path->connect,最后將path添加到card->paths鏈表。

static int snd_soc_dapm_add_path(struct snd_soc_dapm_context *dapm,
    struct snd_soc_dapm_widget *wsource, struct snd_soc_dapm_widget *wsink,
    const char *control,
    int (*connected)(struct snd_soc_dapm_widget *source,
             struct snd_soc_dapm_widget *sink))
{
    struct snd_soc_dapm_widget *widgets[2];
    enum snd_soc_dapm_direction dir;
    struct snd_soc_dapm_path *path;
    int ret;

    if (wsink->is_supply && !wsource->is_supply) {
        dev_err(dapm->dev,
            "Connecting non-supply widget to supply widget is not supported (%s -> %s)\n",
            wsource->name, wsink->name);
        return -EINVAL;
    }

    if (connected && !wsource->is_supply) {
        dev_err(dapm->dev,
            "connected() callback only supported for supply widgets (%s -> %s)\n",
            wsource->name, wsink->name);
        return -EINVAL;
    }

    if (wsource->is_supply && control) {
        dev_err(dapm->dev,
            "Conditional paths are not supported for supply widgets (%s -> [%s] -> %s)\n",
            wsource->name, control, wsink->name);
        return -EINVAL;
    }

    ret = snd_soc_dapm_check_dynamic_path(dapm, wsource, wsink, control);
    if (ret)
        return ret;

    path = kzalloc(sizeof(struct snd_soc_dapm_path), GFP_KERNEL);
    if (!path)
        return -ENOMEM;

    path->node[SND_SOC_DAPM_DIR_IN] = wsource;
    path->node[SND_SOC_DAPM_DIR_OUT] = wsink;
    widgets[SND_SOC_DAPM_DIR_IN] = wsource;
    widgets[SND_SOC_DAPM_DIR_OUT] = wsink;

    path->connected = connected;
    INIT_LIST_HEAD(&path->list);
    INIT_LIST_HEAD(&path->list_kcontrol);

    if (wsource->is_supply || wsink->is_supply)
        path->is_supply = 1;

    /* connect static paths */
    if (control == NULL) {
        path->connect = 1;
    } else {
        switch (wsource->id) {
        case snd_soc_dapm_demux:
            ret = dapm_connect_mux(dapm, path, control, wsource);
            if (ret)
                goto err;
            break;
        default:
            break;
        }

        switch (wsink->id) {
        case snd_soc_dapm_mux:
            ret = dapm_connect_mux(dapm, path, control, wsink);
            if (ret != 0)
                goto err;
            break;
        case snd_soc_dapm_switch:
        case snd_soc_dapm_mixer:
        case snd_soc_dapm_mixer_named_ctl:
            ret = dapm_connect_mixer(dapm, path, control);
            if (ret != 0)
                goto err;
            break;
        default:
            break;
        }
    }

    list_add(&path->list, &dapm->card->paths);
    snd_soc_dapm_for_each_direction(dir)
        list_add(&path->list_node[dir], &widgets[dir]->edges[dir]);

    snd_soc_dapm_for_each_direction(dir) {
        dapm_update_widget_flags(widgets[dir]);
        dapm_mark_dirty(widgets[dir], "Route added");
    }

    if (dapm->card->instantiated && path->connect)
        dapm_path_invalidate(path);

    return 0;
err:
    kfree(path);
    return ret;
}

dapm_connect_mixer  用該函數連接一個sink widget為mixer類型的所有source端。

用需要用來連接的kcontrol的名字,和sink widget中的kcontrol模板數組中的名字相比較,path的名字設置為該kcontrol的名字,然后用dapm_set_path_status函數來初始化該輸入端的連接狀態。

/* connect mixer widget to its interconnecting audio paths */
static int dapm_connect_mixer(struct snd_soc_dapm_context *dapm,
    struct snd_soc_dapm_path *path, const char *control_name)
{
    int i;
    /* search for mixer kcontrol */
    for (i = 0; i < path->sink->num_kcontrols; i++) {
        if (!strcmp(control_name, path->sink->kcontrol_news[i].name)) {
            path->name = path->sink->kcontrol_news[i].name;
            dapm_set_mixer_path_status(path, i);
            return 0;
        }
    }
    return -ENODEV;
}

 以上是CPU DAI 和Codec內部widget建立path的過程。

在snd_soc_instantiate_card中,調用snd_soc_dapm_link_dai_widgets來建立dai widget和非dai widget(stream widget)的path

dai widget又分為cpu dai widget和codec dai widget.通常會為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字段也使用同樣的名字。
stream widget    stream widget通常是指那些要處理音頻流數據的widget,它們包含以下這幾種類型:
snd_soc_dapm_aif_in                 用SND_SOC_DAPM_AIF_IN輔助宏定義
snd_soc_dapm_aif_out               用SND_SOC_DAPM_AIF_OUT輔助宏定義
snd_soc_dapm_dac                    用SND_SOC_DAPM_DAC輔助宏定義
snd_soc_dapm_adc                    用SND_SOC_DAPM_ADC輔助宏定義

比如:為以下codec dai建立名字叫做“Playback”和“Capture"的dai widget.

static struct snd_soc_dai_driver tlv320aic23_dai = {
    .name = "tlv320aic23-hifi",
    .playback = {
             .stream_name = "Playback",
             .channels_min = 2,
             .channels_max = 2,
             .rates = AIC23_RATES,
             .formats = AIC23_FORMATS,},
    .capture = {
            .stream_name = "Capture",
            .channels_min = 2,
            .channels_max = 2,
            .rates = AIC23_RATES,
            .formats = AIC23_FORMATS,},
    .ops = &tlv320aic23_dai_ops,
};

“Playback”和“Capture"的dai widget將和如下stream widget建立path.

    SND_SOC_DAPM_DAC("DAC", "Playback", TLV320AIC23_PWR, 3, 1),
    SND_SOC_DAPM_ADC("ADC", "Capture", TLV320AIC23_PWR, 2, 1),

snd_soc_dapm_link_dai_widgets函數會去遍歷每一個dai widgets,然后遍歷所有的非dai widgets (stream widget),如果非dai widgets(stream widget)的stream name與dai widgets的name相同,則把兩個widgets建立path。這也是為什么創建dai widgets時name一定要是stream name的原因之一了。

int snd_soc_dapm_link_dai_widgets(struct snd_soc_card *card)
{
    struct snd_soc_dapm_widget *dai_w, *w;
    struct snd_soc_dapm_widget *src, *sink;
    struct snd_soc_dai *dai;

    /* For each DAI widget... */
    list_for_each_entry(dai_w, &card->widgets, list) {
        switch (dai_w->id) {
        case snd_soc_dapm_dai_in:
        case snd_soc_dapm_dai_out:
            break;
        default:
            continue;
        }

        /* let users know there is no DAI to link */
        if (!dai_w->priv) {
            dev_dbg(card->dev, "dai widget %s has no DAI\n",
                dai_w->name);
            continue;
        }

        dai = dai_w->priv;

        /* ...find all widgets with the same stream and link them */
        list_for_each_entry(w, &card->widgets, list) {
            if (w->dapm != dai_w->dapm)
                continue;

            switch (w->id) {
            case snd_soc_dapm_dai_in:
            case snd_soc_dapm_dai_out:
                continue;
            default:
                break;
            }

            if (!w->sname || !strstr(w->sname, dai_w->sname))
                continue;

            if (dai_w->id == snd_soc_dapm_dai_in) {
                src = dai_w;
                sink = w;
            } else {
                src = w;
                sink = dai_w;
            }
            dev_dbg(dai->dev, "%s -> %s\n", src->name, sink->name);
            snd_soc_dapm_add_path(w->dapm, src, sink, NULL, NULL);
        }
    }

    return 0;
}

調用snd_soc_dapm_connect_dai_link_widgets() 建立CPU BE DAI widget 和 codec DAI widget之間的path.

void snd_soc_dapm_connect_dai_link_widgets(struct snd_soc_card *card)
{
    struct snd_soc_pcm_runtime *rtd;

    /* for each BE DAI link... */
    list_for_each_entry(rtd, &card->rtd_list, list)  {
        /*
         * dynamic FE links have no fixed DAI mapping.
         * CODEC<->CODEC links have no direct connection.
         */
        if (rtd->dai_link->dynamic || rtd->dai_link->params)
            continue;

        dapm_connect_dai_link_widgets(card, rtd);
    }
}

static void dapm_connect_dai_link_widgets(struct snd_soc_card *card,
                      struct snd_soc_pcm_runtime *rtd)
{
    struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
    struct snd_soc_dapm_widget *sink, *source;
    int i;

    for (i = 0; i < rtd->num_codecs; i++) {
        struct snd_soc_dai *codec_dai = rtd->codec_dais[i];

        /* connect BE DAI playback if widgets are valid */
        if (codec_dai->playback_widget && cpu_dai->playback_widget) {
            source = cpu_dai->playback_widget;
            sink = codec_dai->playback_widget;
            dev_dbg(rtd->dev, "connected DAI link %s:%s -> %s:%s\n",
                cpu_dai->component->name, source->name,
                codec_dai->component->name, sink->name);

            snd_soc_dapm_add_path(&card->dapm, source, sink,
                NULL, NULL);
        }

        /* connect BE DAI capture if widgets are valid */
        if (codec_dai->capture_widget && cpu_dai->capture_widget) {
            source = codec_dai->capture_widget;
            sink = cpu_dai->capture_widget;
            dev_dbg(rtd->dev, "connected DAI link %s:%s -> %s:%s\n",
                codec_dai->component->name, source->name,
                cpu_dai->component->name, sink->name);

            snd_soc_dapm_add_path(&card->dapm, source, sink,
                NULL, NULL);
        }
    }
}

建立widget 的dapm kcontrol

定義一個widget,我們需要指定兩個很重要的內容:一個是用於控制widget的電源狀態的reg/shift等寄存器信息,另一個是用於控制音頻路徑切換的dapm kcontrol信息,這些dapm kcontrol有它們自己的reg/shift寄存器信息用於切換widget的路徑連接方式。前面我們只是創建了widget的實例,並把它們注冊到聲卡的widgts鏈表中,但是到目前為止,包含在widget中的dapm kcontrol並沒有建立起來,dapm框架在聲卡的初始化階段,等所有的widget(包括machine、platform、codec)都創建好之后,通過snd_soc_dapm_new_widgets函數,創建widget內包含的dapm kcontrol,並初始化widget的初始電源狀態和音頻路徑的初始連接狀態。

/**
 * snd_soc_dapm_new_widgets - add new dapm widgets
 * @card: card to be checked for new dapm widgets
 *
 * Checks the codec for any new dapm widgets and creates them if found.
 *
 * Returns 0 for success.
 */
int snd_soc_dapm_new_widgets(struct snd_soc_card *card)
{
    struct snd_soc_dapm_widget *w;
    unsigned int val;

    mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_INIT);

    list_for_each_entry(w, &card->widgets, list)
    {
        if (w->new)
            continue;

        if (w->num_kcontrols) {
            w->kcontrols = kzalloc(w->num_kcontrols *
                        sizeof(struct snd_kcontrol *),
                        GFP_KERNEL);
            if (!w->kcontrols) {
                mutex_unlock(&card->dapm_mutex);
                return -ENOMEM;
            }
        }

        switch(w->id) {
        case snd_soc_dapm_switch:
        case snd_soc_dapm_mixer:
        case snd_soc_dapm_mixer_named_ctl:
            dapm_new_mixer(w);
            break;
        case snd_soc_dapm_mux:
        case snd_soc_dapm_demux:
            dapm_new_mux(w);
            break;
        case snd_soc_dapm_pga:
        case snd_soc_dapm_out_drv:
            dapm_new_pga(w);
            break;
        case snd_soc_dapm_dai_link:
            dapm_new_dai_link(w);
            break;
        default:
            break;
        }

        /* Read the initial power state from the device */
        if (w->reg >= 0) {
            soc_dapm_read(w->dapm, w->reg, &val);
            val = val >> w->shift;
            val &= w->mask;
            if (val == w->on_val)
                w->power = 1;
        }

        w->new = 1;

        dapm_mark_dirty(w, "new widget");
        dapm_debugfs_add_widget(w);
    }

    dapm_power_widgets(card, SND_SOC_DAPM_STREAM_NOP);
    mutex_unlock(&card->dapm_mutex);
    return 0;
}

該函數通過聲卡的widgets鏈表,遍歷所有已經注冊了的widget,其中的new字段用於判斷該widget是否已經執行過snd_soc_dapm_new_widgets函數,如果num_kcontrols字段有數值,表明該widget包含有若干個dapm kcontrol,那么就需要為這些kcontrol分配一個指針數組,並把數組的首地址賦值給widget的kcontrols字段,該數組存放着指向這些kcontrol的指針,當然現在這些都是空指針,因為實際的kcontrol現在還沒有被創建.

接着,對幾種能影響音頻路徑的widget,創建並初始化它們所包含的dapm kcontrol.

需要用到的創建函數分別是:
dapm_new_mixer()    對於mixer類型,用該函數創建dapm kcontrol;
dapm_new_mux()   對於mux類型,用該函數創建dapm kcontrol;
dapm_new_pga()   對於pga類型,用該函數創建dapm kcontrol;

根據widget寄存器的當前值,初始化widget的電源狀態,並設置到power字段中.

接着,設置new字段,表明該widget已經初始化完成,我們還要吧該widget加入到聲卡的dapm_dirty鏈表中,表明該widget的狀態發生了變化,稍后在合適的時刻,dapm框架會掃描dapm_dirty鏈表,統一處理所有已經變化的widget。為什么要統一處理?因為dapm要控制各種widget的上下電順序,同時也是為了減少寄存器的讀寫次數(多個widget可能使用同一個寄存器)
最后,通過dapm_power_widgets函數,統一處理所有位於dapm_dirty鏈表上的widget的狀態改變.

damp_new_mixer:

/* create new dapm mixer control */
static int dapm_new_mixer(struct snd_soc_dapm_widget *w)
{
    int i, ret;
    struct snd_soc_dapm_path *path;
    struct dapm_kcontrol_data *data;

    /* add kcontrol */
    for (i = 0; i < w->num_kcontrols; i++) {
        /* match name */
        snd_soc_dapm_widget_for_each_source_path(w, path) {
            /* mixer/mux paths name must match control name */
            if (path->name != (char *)w->kcontrol_news[i].name)
                continue;

            if (!w->kcontrols[i]) {
                ret = dapm_create_or_share_kcontrol(w, i);
                if (ret < 0)
                    return ret;
            }

            dapm_kcontrol_add_path(w->kcontrols[i], path);

            data = snd_kcontrol_chip(w->kcontrols[i]);
            if (data->widget)
                snd_soc_dapm_add_path(data->widget->dapm,
                              data->widget,
                              path->source,
                              NULL, NULL);
        }
    }

    return 0;
}

一個mixer類型的widget是由多個kcontrol組成的,每個kcontrol控制着mixer的一個輸入端的開啟和關閉,所以,該函數會根據kcontrol的數量做循環,逐個建立對應的kcontrol。

找到mixer widget的source path, 如果kcontrol的name和source path的name一樣則創建kcontrol.

如果kcontrol之前沒有被創建,則通過dapm_create_or_share_kcontrol()創建這個輸入端的kcontrol.

/*
 * Determine if a kcontrol is shared. If it is, look it up. If it isn't,
 * create it. Either way, add the widget into the control's widget list
 */
static int dapm_create_or_share_kcontrol(struct snd_soc_dapm_widget *w,
    int kci)
{
    struct snd_soc_dapm_context *dapm = w->dapm;
    struct snd_card *card = dapm->card->snd_card;
    const char *prefix;
    size_t prefix_len;
    int shared;
    struct snd_kcontrol *kcontrol;
    bool wname_in_long_name, kcname_in_long_name;
    char *long_name = NULL;
    const char *name;
    int ret = 0;

    prefix = soc_dapm_prefix(dapm);
    if (prefix)
        prefix_len = strlen(prefix) + 1;
    else
        prefix_len = 0;

    shared = dapm_is_shared_kcontrol(dapm, w, &w->kcontrol_news[kci],
                     &kcontrol);

    if (!kcontrol) {
        if (shared) {
            wname_in_long_name = false;
            kcname_in_long_name = true;
        } else {
            switch (w->id) {
            case snd_soc_dapm_switch:
            case snd_soc_dapm_mixer:
            case snd_soc_dapm_pga:
            case snd_soc_dapm_out_drv:
                wname_in_long_name = true;
                kcname_in_long_name = true;
                break;
            case snd_soc_dapm_mixer_named_ctl:
                wname_in_long_name = false;
                kcname_in_long_name = true;
                break;
            case snd_soc_dapm_demux:
            case snd_soc_dapm_mux:
                wname_in_long_name = true;
                kcname_in_long_name = false;
                break;
            default:
                return -EINVAL;
            }
        }

        if (wname_in_long_name && kcname_in_long_name) {
            /*
             * The control will get a prefix from the control
             * creation process but we're also using the same
             * prefix for widgets so cut the prefix off the
             * front of the widget name.
             */
            long_name = kasprintf(GFP_KERNEL, "%s %s",
                 w->name + prefix_len,
                 w->kcontrol_news[kci].name);
            if (long_name == NULL)
                return -ENOMEM;

            name = long_name;
        } else if (wname_in_long_name) {
            long_name = NULL;
            name = w->name + prefix_len;
        } else {
            long_name = NULL;
            name = w->kcontrol_news[kci].name;
        }

        kcontrol = snd_soc_cnew(&w->kcontrol_news[kci], NULL, name,
                    prefix);
        if (!kcontrol) {
            ret = -ENOMEM;
            goto exit_free;
        }

        kcontrol->private_free = dapm_kcontrol_free;

        ret = dapm_kcontrol_data_alloc(w, kcontrol, name);
        if (ret) {
            snd_ctl_free_one(kcontrol);
            goto exit_free;
        }

        ret = snd_ctl_add(card, kcontrol);
        if (ret < 0) {
            dev_err(dapm->dev,
                "ASoC: failed to add widget %s dapm kcontrol %s: %d\n",
                w->name, name, ret);
            goto exit_free;
        }
    }

    ret = dapm_kcontrol_add_widget(kcontrol, w);
    if (ret == 0)
        w->kcontrols[kci] = kcontrol;

exit_free:
    kfree(long_name);

    return ret;
}

 dapm_create_or_share_kcontrol()所做的事情如下:

(1)  為了節省內存,通過kcontrol名字的匹配查找,如果這個kcontrol已經在其他widget中已經創建好了,那我們不再創建,dapm_is_shared_kcontrol的參數kcontrol會返回已經創建好的kcontrol的指針。
(2)  如果kcontrol指針被賦值,說明在(1)中查找到了其他widget中同名的kcontrol,我們不用再次創建,只要共享該kcontrol即可。
(3)  在snd_soc_cnew()中調用標准的kcontrol創建函數snd_ctl_new1()。
(4)  在dapm_kcontrol_data_alloc中,如果widget支持autodisable特性,創建與該kcontrol所對應的影子widget,該影子widget的類型是:snd_soc_dapm_kcontrol。
(5)  調用snd_ctl_add()將創建的kcontrol添加到card->snd_card->controls鏈表
(6)  把所有共享該kcontrol的影子widget(snd_soc_dapm_kcontrol),加入到kcontrol的private_data字段所指向的dapm_kcontrol_data結構中。
(7)  把創建好的kcontrol指針賦值到widget的kcontrols數組中。

增加一個虛擬的影子widget,該影子widget在dapm_new_mixer()中和path->source widget建立path,因為使用了kcontrol本身的reg/shift等寄存器信息,所以實際上控制的是該kcontrol的開和關,這個影子widget只有在kcontrol的autodisable字段被設置的情況下才會被創建,該特性使得source的關閉時,與之連接的mixer的輸入端也可以自動關閉。
在 snd_soc_dapm_new_widgets的最后,通過dapm_power_widgets()函數,統一處理所有位於dapm_dirty鏈表上的widget的狀態改變.

/*
 * Scan each dapm widget for complete audio path.
 * A complete path is a route that has valid endpoints i.e.:-
 *
 *  o DAC to output pin.
 *  o Input pin to ADC.
 *  o Input pin to Output pin (bypass, sidetone)
 *  o DAC to ADC (loopback).
 */
static int dapm_power_widgets(struct snd_soc_card *card, int event)
{
    struct snd_soc_dapm_widget *w;
    struct snd_soc_dapm_context *d;
    LIST_HEAD(up_list);
    LIST_HEAD(down_list);
    ASYNC_DOMAIN_EXCLUSIVE(async_domain);
    enum snd_soc_bias_level bias;

    lockdep_assert_held(&card->dapm_mutex);

    trace_snd_soc_dapm_start(card);

    list_for_each_entry(d, &card->dapm_list, list) {
        if (dapm_idle_bias_off(d))
            d->target_bias_level = SND_SOC_BIAS_OFF;
        else
            d->target_bias_level = SND_SOC_BIAS_STANDBY;
    }

    dapm_reset(card);

    /* Check which widgets we need to power and store them in
     * lists indicating if they should be powered up or down.  We
     * only check widgets that have been flagged as dirty but note
     * that new widgets may be added to the dirty list while we
     * iterate.
     */
    list_for_each_entry(w, &card->dapm_dirty, dirty) {
        dapm_power_one_widget(w, &up_list, &down_list);
    }

    list_for_each_entry(w, &card->widgets, list) {
        switch (w->id) {
        case snd_soc_dapm_pre:
        case snd_soc_dapm_post:
            /* These widgets always need to be powered */
            break;
        default:
            list_del_init(&w->dirty);
            break;
        }

        if (w->new_power) {
            d = w->dapm;

            /* Supplies and micbiases only bring the
             * context up to STANDBY as unless something
             * else is active and passing audio they
             * generally don't require full power.  Signal
             * generators are virtual pins and have no
             * power impact themselves.
             */
            switch (w->id) {
            case snd_soc_dapm_siggen:
            case snd_soc_dapm_vmid:
                break;
            case snd_soc_dapm_supply:
            case snd_soc_dapm_regulator_supply:
            case snd_soc_dapm_clock_supply:
            case snd_soc_dapm_micbias:
                if (d->target_bias_level < SND_SOC_BIAS_STANDBY)
                    d->target_bias_level = SND_SOC_BIAS_STANDBY;
                break;
            default:
                d->target_bias_level = SND_SOC_BIAS_ON;
                break;
            }
        }

    }

    /* Force all contexts in the card to the same bias state if
     * they're not ground referenced.
     */
    bias = SND_SOC_BIAS_OFF;
    list_for_each_entry(d, &card->dapm_list, list)
        if (d->target_bias_level > bias)
            bias = d->target_bias_level;
    list_for_each_entry(d, &card->dapm_list, list)
        if (!dapm_idle_bias_off(d))
            d->target_bias_level = bias;

    trace_snd_soc_dapm_walk_done(card);

    /* Run card bias changes at first */
    dapm_pre_sequence_async(&card->dapm, 0);
    /* Run other bias changes in parallel */
    list_for_each_entry(d, &card->dapm_list, list) {
        if (d != &card->dapm)
            async_schedule_domain(dapm_pre_sequence_async, d,
                        &async_domain);
    }
    async_synchronize_full_domain(&async_domain);

    list_for_each_entry(w, &down_list, power_list) {
        dapm_seq_check_event(card, w, SND_SOC_DAPM_WILL_PMD);
    }

    list_for_each_entry(w, &up_list, power_list) {
        dapm_seq_check_event(card, w, SND_SOC_DAPM_WILL_PMU);
    }

    /* Power down widgets first; try to avoid amplifying pops. */
    dapm_seq_run(card, &down_list, event, false);

    dapm_widget_update(card);

    /* Now power up. */
    dapm_seq_run(card, &up_list, event, true);

    /* Run all the bias changes in parallel */
    list_for_each_entry(d, &card->dapm_list, list) {
        if (d != &card->dapm)
            async_schedule_domain(dapm_post_sequence_async, d,
                        &async_domain);
    }
    async_synchronize_full_domain(&async_domain);
    /* Run card bias changes at last */
    dapm_post_sequence_async(&card->dapm, 0);

    /* do we need to notify any clients that DAPM event is complete */
    list_for_each_entry(d, &card->dapm_list, list) {
        if (d->stream_event)
            d->stream_event(d, event);
    }

    pop_dbg(card->dev, card->pop_time,
        "DAPM sequencing finished, waiting %dms\n", card->pop_time);
    pop_wait(card->pop_time);

    trace_snd_soc_dapm_done(card);

    return 0;
}

dapm_power_widgets

 當一個widget的狀態改變后,該widget會被加入dapm_dirty鏈表,然后通過dapm_power_widgets函數來改變整個音頻路徑上的電源狀態

1)該函數通過遍歷dapm_dirty鏈表,對每個鏈表中的widget調用dapm_power_one_widget,dapm_power_one_widget函數除了處理自身的狀態改變外,還把自身的變化傳遞到和它相連的鄰居widget中,結果就是,所有需要上電的widget會被放在up_list鏈表中,而所有需要下電的widget會被放在down_list鏈表中,這個函數我們稍后再討論。
2)遍歷down_list鏈表,向其中的widget發出SND_SOC_DAPM_WILL_PMD事件,感興趣該事件的widget的event回調會被調用。
3)遍歷up_list鏈表,向其中的widget發出SND_SOC_DAPM_WILL_PMU事件,感興趣該事件的widget的event回調會被調用。
4)通過dapm_seq_run函數,處理down_list中的widget,使它們按定義好的順序依次下電。
5)通過dapm_widget_update函數,切換觸發該次狀態變化的widget的kcontrol中的寄存器值,對應的結果就是:改變音頻路徑。
6)通過dapm_seq_run函數,處理up_list中的widget,使它們按定義好的順序依次上電。
7)對每個dapm context發出狀態改變回調。
8)適當的延時,防止pop-pop聲。

dapm_power_one_widget:

 dapm_power_widgets的第一步,就是遍歷dapm_dirty鏈表,對每個鏈表中的widget調用dapm_power_one_widget,把需要上電和需要下電的widget分別加入到up_list和down_list鏈表中,同時,他還會把受到影響的鄰居widget再次加入到dapm_dirty鏈表的末尾,通過這個動作,聲卡中所以受到影響的widget都會被“感染”,依次被加到dapm_dirty鏈表,然后依次被執行dapm_power_one_widget函數。 

static void dapm_power_one_widget(struct snd_soc_dapm_widget *w,
                  struct list_head *up_list,
                  struct list_head *down_list)
{
    int power;

    switch (w->id) {
    case snd_soc_dapm_pre:
        dapm_seq_insert(w, down_list, false);
        break;
    case snd_soc_dapm_post:
        dapm_seq_insert(w, up_list, true);
        break;

    default:
        power = dapm_widget_power_check(w);

        dapm_widget_set_power(w, power, up_list, down_list);
        break;
    }
}

1)通過dapm_widget_power_check,調用widget的power_check回調函數,獲得該widget新的電源狀態。

2)調用dapm_widget_set_power,“感染”與之相連的鄰居widget。
遍歷source widget,通過dapm_widget_set_peer_power函數,把處於連接狀態的source widget加入dapm_dirty鏈表中。
遍歷sink widget,通過dapm_widget_set_peer_power函數,把處於連接狀態的sink widget加入dapm_dirty鏈表中。
3)根據第一步得到的新的電源狀態,把widget加入到up_list或down_list鏈表中。
可見,通過該函數,一個widget的狀態改變,鄰居widget會受到“感染”而被加入到dapm_dirty鏈表的末尾,所以掃描到鏈表的末尾時,鄰居widget也會執行同樣的操作,從而“感染”鄰居的鄰居,直到沒有新的widget被加入dapm_dirty鏈表為止,這時,所有受到影響的widget都被加入到up_list或down_li鏈表中,等待后續的上下電操作。

power_check回調函數

在創建widget的時候,widget的power_check回調函數會根據widget的類型,設置不同的回調函數。當widget的狀態改變后,dapm會遍歷dapm_dirty鏈表,並通過power_check回調函數,決定該widget是否需要上電。

dapm要給一個widget上電的其中一個前提條件是:這個widget位於一條完整的音頻路徑上,而一條完整的音頻路徑的兩頭,必須是輸入/輸出引腳,或者是一個外部音頻設備,又或者是一個處於激活狀態的音頻流widget,它們可以位於路徑的末端,但不是構成完成音頻路徑的必要條件,我們只用它來判斷掃描一條路徑的結束條件。

大多數的widget的power_check回調被設置為:dapm_generic_check_power

/* Generic check to see if a widget should be powered. */
static int dapm_generic_check_power(struct snd_soc_dapm_widget *w)
{
    int in, out;

    DAPM_UPDATE_STAT(w, power_checks);

    in = is_connected_input_ep(w, NULL, NULL);
    out = is_connected_output_ep(w, NULL, NULL);
    return out != 0 && in != 0;
}

dapm_generic_check_power()中分別用is_connected_output_ep和is_connected_input_ep得到該widget連到多少個input endpoint和output endpoint, 從而判斷是否有同時連接到一個輸入端和一個輸出端,如果是,返回1來表示該widget需要上電。

dapm提供了兩個內部函數,用來統計一個widget連接到輸出引腳、輸入引腳、激活的音頻流widget的有效路徑個數:
is_connected_output_ep    返回連接至輸出引腳或激活狀態的輸出音頻流的路徑數量
is_connected_input_ep    返回連接至輸入引腳或激活狀態的輸入音頻流的路徑數量

/*
 * Recursively check for a completed path to an active or physically connected
 * output widget. Returns number of complete paths.
 *
 * Optionally, can be supplied with a function acting as a stopping condition.
 * This function takes the dapm widget currently being examined and the walk
 * direction as an arguments, it should return true if widgets from that point
 * in the graph onwards should not be added to the widget list.
 */
static int is_connected_output_ep(struct snd_soc_dapm_widget *widget,
    struct list_head *list,
    bool (*custom_stop_condition)(struct snd_soc_dapm_widget *i,
                      enum snd_soc_dapm_direction))
{
    return is_connected_ep(widget, list, SND_SOC_DAPM_DIR_OUT,
            is_connected_output_ep, custom_stop_condition);
}

/*
 * Recursively check for a completed path to an active or physically connected
 * input widget. Returns number of complete paths.
 *
 * Optionally, can be supplied with a function acting as a stopping condition.
 * This function takes the dapm widget currently being examined and the walk
 * direction as an arguments, it should return true if the walk should be
 * stopped and false otherwise.
 */
static int is_connected_input_ep(struct snd_soc_dapm_widget *widget,
    struct list_head *list,
    bool (*custom_stop_condition)(struct snd_soc_dapm_widget *i,
                      enum snd_soc_dapm_direction))
{
    return is_connected_ep(widget, list, SND_SOC_DAPM_DIR_IN,
            is_connected_input_ep, custom_stop_condition);
}

 

/*
 * Common implementation for is_connected_output_ep() and
 * is_connected_input_ep(). The function is inlined since the combined size of
 * the two specialized functions is only marginally larger then the size of the
 * generic function and at the same time the fast path of the specialized
 * functions is significantly smaller than the generic function.
 */
static __always_inline int is_connected_ep(struct snd_soc_dapm_widget *widget,
    struct list_head *list, enum snd_soc_dapm_direction dir,
    int (*fn)(struct snd_soc_dapm_widget *, struct list_head *,
          bool (*custom_stop_condition)(struct snd_soc_dapm_widget *,
                        enum snd_soc_dapm_direction)),
    bool (*custom_stop_condition)(struct snd_soc_dapm_widget *,
                      enum snd_soc_dapm_direction))
{
    enum snd_soc_dapm_direction rdir = SND_SOC_DAPM_DIR_REVERSE(dir);
    struct snd_soc_dapm_path *path;
    int con = 0;

    if (widget->endpoints[dir] >= 0)
        return widget->endpoints[dir];

    DAPM_UPDATE_STAT(widget, path_checks);

    /* do we need to add this widget to the list ? */
    if (list)
        list_add_tail(&widget->work_list, list);

    if (custom_stop_condition && custom_stop_condition(widget, dir)) {
        list = NULL;
        custom_stop_condition = NULL;
    }

    if ((widget->is_ep & SND_SOC_DAPM_DIR_TO_EP(dir)) && widget->connected) {
        widget->endpoints[dir] = snd_soc_dapm_suspend_check(widget);
        return widget->endpoints[dir];
    }

    snd_soc_dapm_widget_for_each_path(widget, rdir, path) {
        DAPM_UPDATE_STAT(widget, neighbour_checks);

        if (path->weak || path->is_supply)
            continue;

        if (path->walking)
            return 1;

        trace_snd_soc_dapm_path(widget, dir, path);

        if (path->connect) {
            path->walking = 1;
            con += fn(path->node[dir], list, custom_stop_condition);
            path->walking = 0;
        }
    }

    widget->endpoints[dir] = con;

    return con;
}

dapm_seq_run

當所有需要上電或下電的widget都被加入到dapm_dirty鏈表后,接着會通過dapm_seq_run處理down_list鏈表上的widget,把該鏈表上的widget按順序下電,然后通過dapm_widget_update更新widget中的kcontrol(這個kcontrol通常就是觸發本次狀態改變的觸發源),接着又通過apm_seq_run處理up_list鏈表上的widget,把該鏈表上的widget按順序上電。最終的上電或下電操作需要通過codec的寄存器來實現,因為定義widget時,如果這是一個帶電源控制的widget,我們必須提供reg/shift等字段的設置值,如果該widget無需寄存器控制電源狀態,則reg字段必須賦值為:

SND_SOC_NOPM        (該宏定義的實際值是-1)
具體實現上,dapm框架使用了一點技巧:如果位於同一個上下電順序的幾個widget使用了同一個寄存器地址(一個寄存器可能使用不同的位來控制不同的widget的電源狀態),dapm_seq_run通過dapm_seq_run_coalesced函數合並這幾個widget的變更,然后只需要把合並后的值一次寫入寄存器即可。

/* Apply a DAPM power sequence.
 *
 * We walk over a pre-sorted list of widgets to apply power to.  In
 * order to minimise the number of writes to the device required
 * multiple widgets will be updated in a single write where possible.
 * Currently anything that requires more than a single write is not
 * handled.
 */
static void dapm_seq_run(struct snd_soc_card *card,
    struct list_head *list, int event, bool power_up)
{
    struct snd_soc_dapm_widget *w, *n;
    struct snd_soc_dapm_context *d;
    LIST_HEAD(pending);
    int cur_sort = -1;
    int cur_subseq = -1;
    int cur_reg = SND_SOC_NOPM;
    struct snd_soc_dapm_context *cur_dapm = NULL;
    int ret, i;
    int *sort;

    if (power_up)
        sort = dapm_up_seq;
    else
        sort = dapm_down_seq;

    list_for_each_entry_safe(w, n, list, power_list) {
        ret = 0;

        /* Do we need to apply any queued changes? */
        if (sort[w->id] != cur_sort || w->reg != cur_reg ||
            w->dapm != cur_dapm || w->subseq != cur_subseq) {
            if (!list_empty(&pending))
                dapm_seq_run_coalesced(card, &pending);

            if (cur_dapm && cur_dapm->seq_notifier) {
                for (i = 0; i < ARRAY_SIZE(dapm_up_seq); i++)
                    if (sort[i] == cur_sort)
                        cur_dapm->seq_notifier(cur_dapm,
                                       i,
                                       cur_subseq);
            }

            if (cur_dapm && w->dapm != cur_dapm)
                soc_dapm_async_complete(cur_dapm);

            INIT_LIST_HEAD(&pending);
            cur_sort = -1;
            cur_subseq = INT_MIN;
            cur_reg = SND_SOC_NOPM;
            cur_dapm = NULL;
        }

        switch (w->id) {
        case snd_soc_dapm_pre:
            if (!w->event)
                list_for_each_entry_safe_continue(w, n, list,
                                  power_list);

            if (event == SND_SOC_DAPM_STREAM_START)
                ret = w->event(w,
                           NULL, SND_SOC_DAPM_PRE_PMU);
            else if (event == SND_SOC_DAPM_STREAM_STOP)
                ret = w->event(w,
                           NULL, SND_SOC_DAPM_PRE_PMD);
            break;

        case snd_soc_dapm_post:
            if (!w->event)
                list_for_each_entry_safe_continue(w, n, list,
                                  power_list);

            if (event == SND_SOC_DAPM_STREAM_START)
                ret = w->event(w,
                           NULL, SND_SOC_DAPM_POST_PMU);
            else if (event == SND_SOC_DAPM_STREAM_STOP)
                ret = w->event(w,
                           NULL, SND_SOC_DAPM_POST_PMD);
            break;

        default:
            /* Queue it up for application */
            cur_sort = sort[w->id];
            cur_subseq = w->subseq;
            cur_reg = w->reg;
            cur_dapm = w->dapm;
            list_move(&w->power_list, &pending);
            break;
        }

        if (ret < 0)
            dev_err(w->dapm->dev,
                "ASoC: Failed to apply widget power: %d\n", ret);
    }

    if (!list_empty(&pending))
        dapm_seq_run_coalesced(card, &pending);

    if (cur_dapm && cur_dapm->seq_notifier) {
        for (i = 0; i < ARRAY_SIZE(dapm_up_seq); i++)
            if (sort[i] == cur_sort)
                cur_dapm->seq_notifier(cur_dapm,
                               i, cur_subseq);
    }

    list_for_each_entry(d, &card->dapm_list, list) {
        soc_dapm_async_complete(d);
    }
}

 dapm kcontrol的put回調

上面我們已經討論了如何判斷一個widget是否需要上電,以及widget的上電過程,一個widget的狀態改變如何傳遞到整個音頻路徑上的所有widget。這些過程總是需要一個起始點:是誰觸動了dapm,使得它需要執行上述的掃描和上電過程?事實上,以下幾種情況可以觸發dapm發起一次掃描操作:

1)聲卡初始化階段,snd_soc_dapm_new_widgets函數創建widget包含的kcontrol后,會觸發一次掃描操作。
2)用戶空間的應用程序修改了widget中包含的dapm kcontrol的配置值時,會觸發一次掃描操作。
3)pcm的打開或關閉,會通過音頻流widget觸發一次掃描操作(在soc_pcm_prepare/close函數中,會調用snd_soc_dapm_stream_event()發出SND_SOC_DAPM_STREAM_START/START事件)。
4)驅動程序在改變了某個widget並把它加入到dapm_dirty鏈表后,主動調用snd_soc_dapm_sync函數觸發掃描操作。
這里我們主要討論一下第二種,用戶空間對kcontrol的修改,最終都會調用到kcontrol的put回調函數。對於常用的dapm kcontrol,系統已經為我們定義好了它們的put回調函數:
snd_soc_dapm_put_volsw                                  mixer類型的dapm kcontrol使用的put回調
snd_soc_dapm_put_enum_double                   mux類型的dapm kcontrol使用的put回調
snd_soc_dapm_put_enum_virt                          虛擬mux類型的dapm kcontrol使用的put回調
snd_soc_dapm_put_value_enum_double      控制值不連續的mux類型的dapm kcontrol使用的put回調
snd_soc_dapm_put_pin_switch                         引腳類dapm kcontrol使用的put回調
我們以mixer類型的dapm kcontrol的put回調講解一下觸發的過程:

/**
 * snd_soc_dapm_put_volsw - dapm mixer set callback
 * @kcontrol: mixer control
 * @ucontrol: control element information
 *
 * Callback to set the value of a dapm mixer control.
 *
 * Returns 0 for success.
 */
int snd_soc_dapm_put_volsw(struct snd_kcontrol *kcontrol,
    struct snd_ctl_elem_value *ucontrol)
{
    struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kcontrol);
    struct snd_soc_card *card = dapm->card;
    struct soc_mixer_control *mc =
        (struct soc_mixer_control *)kcontrol->private_value;
    int reg = mc->reg;
    unsigned int shift = mc->shift;
    int max = mc->max;
    unsigned int mask = (1 << fls(max)) - 1;
    unsigned int invert = mc->invert;
    unsigned int val;
    int connect, change, reg_change = 0;
    struct snd_soc_dapm_update update;
    int ret = 0;

    if (snd_soc_volsw_is_stereo(mc))
        dev_warn(dapm->dev,
             "ASoC: Control '%s' is stereo, which is not supported\n",
             kcontrol->id.name);

    val = (ucontrol->value.integer.value[0] & mask);
    connect = !!val;

    if (invert)
        val = max - val;

    mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME);

    change = dapm_kcontrol_set_value(kcontrol, val);

    if (reg != SND_SOC_NOPM) {
        mask = mask << shift;
        val = val << shift;

        reg_change = soc_dapm_test_bits(dapm, reg, mask, val);
    }

    if (change || reg_change) {
        if (reg_change) {
            update.kcontrol = kcontrol;
            update.reg = reg;
            update.mask = mask;
            update.val = val;
            card->update = &update;
        }
        change |= reg_change;

        ret = soc_dapm_mixer_update_power(card, kcontrol, connect);

        card->update = NULL;
    }

    mutex_unlock(&card->dapm_mutex);

    if (ret > 0)
        soc_dpcm_runtime_update(card);

    return change;
}

其中的dapm_kcontrol_set_value函數用於把設置值緩存到kcontrol對應的影子widget,影子widget是為了實現autodisable特性而創建的一個虛擬widget,影子widget的輸出連接到kcontrol的source widget,影子widget的寄存器被設置為和kcontrol一樣的寄存器地址,這樣當source widget被關閉時,會觸發影子widget被關閉,其作用就是kcontrol也被自動關閉從而在物理上斷開與source widget的連接,但是此時邏輯連接依然有效,dapm依然認為它們是連接在一起的。 觸發dapm進行電源狀態掃描關鍵的函數是soc_dapm_mixer_update_power: 

/* test and update the power status of a mixer or switch widget */
static int soc_dapm_mixer_update_power(struct snd_soc_card *card,
                   struct snd_kcontrol *kcontrol, int connect)
{
    struct snd_soc_dapm_path *path;
    int found = 0;

    lockdep_assert_held(&card->dapm_mutex);

    /* find dapm widget path assoc with kcontrol */
    dapm_kcontrol_for_each_path(path, kcontrol) {
        found = 1;
        soc_dapm_connect_path(path, connect, "mixer update");
    }

    if (found)
        dapm_power_widgets(card, SND_SOC_DAPM_STREAM_NOP);

    return found;
}

 最終,還是通過dapm_power_widgets函數,觸發整個音頻路徑的掃描過程,這個函數執行后,因為kcontrol的狀態改變,被斷開連接的音頻路徑上的所有widget被按順序下電,而重新連上的音頻路徑上的所有widget被順序地上電,所以,盡管我們只改變了mixer kcontrol中的一個輸入端的連接狀態,所有相關的widget的電源狀態都會被重新設定,這一切,都是自動完成的,對用戶空間的應用程序完全透明,實現了dapm的原本設計目標。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM