zoukankan      html  css  js  c++  java
  • 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"
    ",
                    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
    ", 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
    ", ret);
                goto err_probe;
            }
        }
    
        if (component->probe) {
            ret = component->probe(component);
            if (ret < 0) {
                dev_err(component->dev,
                    "ASoC: failed to probe component %d
    ", 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
    ",
                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
    ", 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)
    ",
                    widget->name, ret);
                break;
            }
            if (!w) {
                dev_err(dapm->dev,
                    "ASoC: Failed to create DAPM control %s
    ",
                    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
    ",
                    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
    ",
                         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
    ",
                    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
    ",
                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)
    ",
                    dai->driver->playback.stream_name, ret);
                return ret;
            }
            if (!w) {
                dev_err(dapm->dev, "ASoC: Failed to create %s widget
    ",
                    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
    ",
                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)
    ",
                    dai->driver->playback.stream_name, ret);
                return ret;
            }
            if (!w) {
                dev_err(dapm->dev, "ASoC: Failed to create %s widget
    ",
                    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
    ",
                    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
    ",
                route->source);
            return -ENODEV;
        }
        if (wsink == NULL) {
            dev_err(dapm->dev, "ASoC: no sink widget found for %s
    ",
                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
    ",
             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)
    ",
                wsource->name, wsink->name);
            return -EINVAL;
        }
    
        if (connected && !wsource->is_supply) {
            dev_err(dapm->dev,
                "connected() callback only supported for supply widgets (%s -> %s)
    ",
                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)
    ",
                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
    ",
                    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
    ", 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
    ",
                    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
    ",
                    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
    ",
                    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
    ", 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
    ", 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
    ",
                 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的原本设计目标。

     

  • 相关阅读:
    【BZOJ1801】【AHOI2009】中国象棋(动态规划)
    【BZOJ3436】小K的农场(差分约束)
    【BZOJ2330】【SDOI2012】糖果(差分约束,SPFA)
    【BZOJ4010】【HNOI2015】菜肴制作(拓扑排序)
    【BZOJ2684】【CEOI2004】锯木厂选址(斜率优化,动态规划)
    【BZOJ1096】【ZJOI2007】仓库建设(斜率优化,动态规划)
    吞吐量(TPS)、QPS、并发数、响应时间(RT)概念
    耐得住寂寞,才能守得住繁华
    想成功,就把这九个公式背下来!
    惊人的社会定律(建议收藏!)
  • 原文地址:https://www.cnblogs.com/fellow1988/p/12685379.html
Copyright © 2011-2022 走看看