程序项目代做,有需求私信(vue、React、Java、爬虫、电路板设计、嵌入式linux等)

Rockchip RK3399 - DAPM Widget&Route&Path

----------------------------------------------------------------------------------------------------------------------------

开发板   :NanoPC-T4开发板
eMMC    :16GB
LPDDR3 :4GB
显示屏    :15.6英寸HDMI接口显示屏
u-boot      :2023.04
linux        :6.3
----------------------------------------------------------------------------------------------------------------------------

DAPM是Dynamic Audio Power Management 的缩写,即动态音频电源管理,旨在允许便携式Linux设备在任何时候使用音频子系统中的最小电量。它独立于其他内核Power Manager,故可以很容易地与其他PM系统共存。

DAPM对所有用户空间应用程序来说也是完全透明的,因为所有电源切换都是在ASoC核心内完成。对于用户空间应用程序,不需要更改代码或重新编译。DAPM 根据当前激活的音频流(playback/capture)和声卡中的Mixer等的配置来决定哪些音频控件的电源开关被打开或关闭。

DAPM贯穿了整个音频子系统的电源控制,它包括了内部codec的电源模块以及machine级别的电源系统。

声卡之Control设备我们介绍过,通过Control设备我们可以完成对音频系统中的某个kcontrol的控制,比如音量控制、混音控制(Mixer)、开关控制(Mux)等,使得音频控件能够按照我们预想的结果进行工作。同时我们可以看到,kcontrol还是有以下几点不足:

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

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

一、Widget

DAPM框架为了解决前面kcontrol不足的问题,引入了widget这一概念。所谓widget,具备路径和电源管理的kcontrol,其实可以理解为是kcontrol的进一步升级和封装,它同样是指音频系统中的某个部件,比如Mixer,Mux,DAC、ADC、Input等等。

widget把kcontrol和动态电源管理进行了有机的结合,同时还具备音频路径的连结功能,一个widget可以和它相邻的widget有某种动态的连结关系。

1.1 struct snd_soc_dapm_widget 

在DAPM框架中,widget用结构体snd_soc_dapm_widget 来描述,定义位于include/sound/soc-dapm.h文件;

/* dapm widget */
struct snd_soc_dapm_widget {
        enum snd_soc_dapm_type id;
        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 */
        struct pinctrl *pinctrl;                /* attached pinctrl */

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

        int channel;
};

该结构体包含了以下成员:

  •  id:widget的类型值,比如snd_soc_dapm_output,snd_soc_dapm_mixer等;
  • name:widget的名称;
  • sname:widget所在stream的名字,比如对于snd_soc_dapm_dai_in、snd_soc_dapm_dai_out、snd_soc_dapm_aif_in、snd_soc_dapm_aif_out、snd_soc_dapm_dac、snd_soc_dapm_adc类型的widget,会使用该字段;
  • list :所有注册到系统中的widget都会通过该list,链接到代表声卡的snd_soc_card结构的widgets链表中;
  • dapm:snd_soc_dapm_context结构指针,ASoc把系统划分为多个dapm域,每个widget属于某个dapm域,同一个域代表着同样的偏置电压供电策略;比如,同一个codec中的widget通常位于同一个dapm域,而platform上的widget可能又会位于另外一个dapm域中;
  • priv:有些widget可能需要一些专有的数据,可以使用该字段来保存,像snd_soc_dapm_dai_in类型的widget,会使用该字段来记住与之相关联的snd_soc_dai结构指针;
  • regulator:对于snd_soc_dapm_regulator_supply类型的widget,该字段指向与之相关的regulator结构指针;
  • pinctrl:指向snd_soc_dapm_pinctrl类型的widget,该字段指向与与之相关的pinctrl结构指针;
  • reg:用于直接DAPM控制的寄存器地址,负值表示没有直接DAPM控制;
  • shift:需要进行位移的位数;
  • mask:未经过位移的屏蔽掩码;
  • on_val:电源开启时的值;
  • off_val:电源关闭时的值;
  • power:表示widget的电源状态,上电/下电状态;
  • active:表示当前widget是否处于激活状态;
  • connected:表示当前widget是否处于连接状态,新创建的widget默认都是处于连接状态的;需要注意的是这个和path的连接状态没有任何关系,path的连接状态描述的是两个widget之间的连接状态,而widget的连接状态可以看做widget内部有个虚拟开关,用于控制当前widget是否可用的;这个主要的应用场景:判断一个widget是否处于一个complete path上时要求路径上的widget必须是连接状态;
  • new:我们定义好的widget(snd_soc_dapm_widget结构),在注册到声卡中时需要进行实例化,该字段用来表示该widget是否已经执行过snd_soc_dapm_new_widgets函数;
  • force:该位被设置后,将会不管widget当前的状态,强制更新至新的电源状态;
  • ignore_suspend:保持在挂起期间启用;
  • new_power:电源状态,运行时的电源状态;
  • power_checked:用作标志位,标志当前widget的电源状态是否已经检查过;如果为false表明需要执行power_check方法检查电源状态;
  • is_supply:该widget是供电类型的组件;
  • is_ep:该widget是端点类型的组件,取值一般为SND_SOC_DAPM_EP_SOURCE、SND_SOC_DAPM_EP_SINK;
  • subseq:序号,用于在上电/下电时对widget进行排序;
  • int (*power_check)(struct snd_soc_dapm_widget *w):指向函数的指针,用于检查电源状态;
  • event_flags:用于指定事件类型的标志;
  • int (*event)(struct snd_soc_dapm_widget*, struct snd_kcontrol *, int):指向函数的指针,用于处理外部事件;
  • num_kcontrols:kcontrol数量;
  • kcontrol_news:kcontrol模板数组,长度等于num_kcontrols,每一个成员都是struct snd_kcontrol_new;对于Mixer、Mux等类型的widget需要在使用辅助宏定义widget的时候指定该参数;
  • kcontrols: 指向一个指针数组,指针数组长度等于num_kcontrols。指针数组每一个成员都指向一个struct snd_kcontrol,是以kcontrol模板为参数,调用snd_soc_cnew函数创建得到的;可以简单理解为kcontrols[i] = snd_soc_cnew(kcontrol_news[i],NULL,name,prefix);
  • dobj,
  • edges:widget的输入端和输出端,输入端和输出端中都是保存snd_soc_dapm_path的链表,区别在于输入端中的snd_soc_dapm_path其source为当前widget,而输出端中的snd_soc_dapm_path其sink为当前widget;
  • work_list:链表节点,在widget连接状态发生改变时使用;
  • power_list:链表节点,在widget连接状态发生改变时使用;
  • dirty:链表节点,用于链接到声卡card的dapm_dirty链表;
  • endpoints:元素0保存从当前widget向后遍历,连接至SND_SOC_DAPM_EP_SOURCE类型端点的路径数量;元素1保存从当前widget向后遍历,连接至SND_SOC_DAPM_EP_SINK类型端点的路径数量;如果元素值为-1,表示该成员尚未初始化;
  • clk:对于snd_soc_dapm_clock_supply类型的widget,该字段指向与之相关的clk结构指针;
  • channel:通道数;
1.1.1 widaget类型

在DAPM框架中,把各种不同的widget划分为不同的种类,snd_soc_dapm_widget 结构中的id字段用来表示该widget 的种类,可选的种类都定义在一个枚举中,如下:

/* 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_pinctrl,           /* pinctrl */
        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 */
        snd_soc_dapm_buffer,            /* DSP/CODEC internal buffer */
        snd_soc_dapm_scheduler,         /* DSP/CODEC internal scheduler */
        snd_soc_dapm_effect,            /* DSP/CODEC effect component */
        snd_soc_dapm_src,               /* DSP/CODEC SRC component */
        snd_soc_dapm_asrc,              /* DSP/CODEC ASRC component */
        snd_soc_dapm_encoder,           /* FW/SW audio encoder component */
        snd_soc_dapm_decoder,           /* FW/SW audio decoder component */

        /* Don't edit below this line */
        SND_SOC_DAPM_TYPE_COUNT
};
View Code 

上面的枚举值实际上是与不同的widget一一对应的,具体如下;

  • Mixer:Mixes:several analog signals into a single analog signal;
  • Mux:An analog switch that outputs only one of many inputs;
  • PGA:A programmable gain amplifier or attenuation widget;
  • ADC:Analog to Digital Converter;
  • DAC:Digital to Analog Converter;
  • Switch:An analog switch;
  • Input:A codec input pin;
  • Output:A codec output pin;
  • Headphone:Headphone (and optional Jack);
  • Mic:Mic (and optional Jack);
  • Line:Line Input/Output (and optional Jack);
  • Speaker:Speaker;
  • Supply:Power or clock supply widget used by other widgets;
  • Regulator:External regulator that supplies power to audio components;
  • Clock:External clock that supplies clock to audio components;
  • AIF IN:Audio Interface Input (with TDM slot mask);
  • AIF OUT:Audio Interface Output (with TDM slot mask);
  • Siggen:Signal Generator;
  • DAI IN:Digital Audio Interface Input;
  • DAI OUT:Digital Audio Interface Output;
  • DAI Link:DAI Link between two DAI structures;
  • Pre:Special PRE widget (exec before all others);
  • Post:Special POST widget (exec after all others);
  • Buffer:Inter widget audio data buffer within a DSP;
  • Scheduler:DSP internal scheduler that schedules component/pipeline processing work;
  • Effect:Widget that performs an audio processing effect;
  • SRC:Sample Rate Converter within DSP or CODEC;
  • ASRC:Asynchronous Sample Rate Converter within DSP or CODEC;
  • Encoder:Widget that encodes audio data from one format (usually PCM) to another usually more compressed format;
  • Decoder:Widget that decodes audio data from a compressed format to an uncompressed format like PCM;
1.1.2 snd_soc_dapm_context 

ASoc把系统划分为多个dapm域(会或者说上下文),我们应该如何去理解dapm域的概念?其实我们可以这么理解:dapm把整个音频系统,按照功能和偏置电压级别,划分为若干个电源域,每个域包含各自的widget,每个域中的所有widget通常都处于同一个偏置电压级别上,而一个电源域就是一个dapm context,通常会有以下几种dapm context:

  • 属于codec中的widget位于一个dapm context中;
  • 属于platform的widget位于一个dapm context中;
  • 属于整个声卡的widget位于一个dapm context中;

对于音频系统的硬件来说,通常要提供合适的偏置电压才能正常地工作,有了dapm context这种组织方式,我们可以方便地对同一组widget进行统一的偏置电压管理。

dapm域在ASoC中使用struct snd_soc_dapm_context表示;

/* DAPM context */
struct snd_soc_dapm_context {
        enum snd_soc_bias_level bias_level;

        /* bit field */
        unsigned int idle_bias_off:1;           /* Use BIAS_OFF instead of STANDBY */
        unsigned int suspend_bias_off:1;        /* Use BIAS_OFF in suspend if the DAPM is idle */

        struct device *dev;                     /* from parent - for debug */
        struct snd_soc_component *component;    /* parent component */
        struct snd_soc_card *card;              /* parent card */

        /* used during DAPM updates */
        enum snd_soc_bias_level target_bias_level;
        struct list_head list;  // 链表节点,用于添加到声卡card的dapm_list链表中

        struct snd_soc_dapm_widget *wcache_sink;
        struct snd_soc_dapm_widget *wcache_source;

#ifdef CONFIG_DEBUG_FS
        struct dentry *debugfs_dapm;
#endif
};

snd_soc_bias_level的取值范围是以下几种:

  • SND_SOC_BIAS_OFF;
  • SND_SOC_BIAS_STANDBY;
  • SND_SOC_BIAS_PREPARE;
  • SND_SOC_BIAS_ON。

1.2 辅助宏定义widget

一个widget用snd_soc_dapm_widget结构体来描述,通常,我们会根据音频硬件的组成,分别在声卡的codec驱动、platform驱动和machine驱动中定义一组widget,这些widget用数组进行组织,DAPM系统提供的一些辅助宏定义来定义各种类型的widget和它所用到的dapm kcontrol(这些由DAPM系统提供的辅助宏定义的kcontrol后面我们统一称为dapm kcontrol,以便和普通的kcontrol进行区分)。

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

1.2.1 Codec域widget

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

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

位于该域上的widget通常是针对platform/machine的一些需要物理连接的输入/输出接口,例如耳机、扬声器、麦克风。

这些widget是没有寄存器控制位来控制widget的电源状态的,因此reg字段均被设置为 SND_SOC_NOPM(-1)。

DAPM 框架为我们提供了多种Platform/Machine域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}
View Code

以上这些widget类型对应:

(1) snd_soc_dapm_siggen:信号发生器(SIGGEN;;

(2) snd_soc_dapm_sink:我也不晓得是干啥用的,未碰到过;

(3)snd_soc_dapm_input:对应的实就是Codec芯片的输入引脚,比如为ALC5651 IN1P、IN2P等输入引脚定义的widget:

SND_SOC_DAPM_INPUT("MIC1"),  // 这个是ALC5651驱动源码给出的,MIC1/2/3个人认为是多余的,并不与实际引脚对应
SND_SOC_DAPM_INPUT("MIC2"),
SND_SOC_DAPM_INPUT("MIC3"),

SND_SOC_DAPM_INPUT("IN1P"),
SND_SOC_DAPM_INPUT("IN2P"),
SND_SOC_DAPM_INPUT("IN2N"),
SND_SOC_DAPM_INPUT("IN3P"),
SND_SOC_DAPM_INPUT("DMIC L1"),
SND_SOC_DAPM_INPUT("DMIC R1")

(4) snd_soc_dapm_output:对应的实就是codec芯片的输出引脚,比如为ALC5651 LOUTL、HPOL等输出引脚定义的widget:

SND_SOC_DAPM_OUTPUT("HPOL"),
SND_SOC_DAPM_OUTPUT("HPOR"),
SND_SOC_DAPM_OUTPUT("LOUTL"),
SND_SOC_DAPM_OUTPUT("LOUTR"),
SND_SOC_DAPM_OUTPUT("PDML"),
SND_SOC_DAPM_OUTPUT("PDMR"),

(5) snd_soc_dapm_mic:对应的就是麦克风(and optional Jack),比如定义与ALC5651 IN2P、IN2N引脚连接的麦克风:

SND_SOC_DAPM_MIC("Microphone", NULL),

(6) snd_soc_dapm_hp:对应的就是耳机(and optional Jack),比如定义与ALC5651的HPOL、HPOR引脚连接的耳机;

SND_SOC_DAPM_HP("Headphone", NULL)

(7) snd_soc_dapm_spk:对应的就是扬声器:ALC5651好像没有speaker输出相关引脚,因此不介绍了;

SND_SOC_DAPM_SPK("Speaker", NULL)

(8) snd_soc_dapm_line:对应的就是线路输入/输出(and optional Jack),比如定义与ALC5651的IN2P、IN2N引脚(这俩引脚是可以功能复用的)连接的音频输入设备、与LOUTL、LOUTR引脚连接的接音频输出设备;

SND_SOC_DAPM_LINE("Line", NULL)

麦克风(snd_soc_dapm_mic),耳机(snd_soc_dapm_hp),扬声器(snd_soc_dapm_spk),线路输入/输出(snd_soc_dapm_line)这几种widget,还可以定义一个dapm事件回调函数wevent,从event flags字段设置可以看出,它们只会响应SND_SOC_DAPM_POST_PMU(上电后)和 SND_SOC_DAPM_POST_PMD(下电前)事件,这几个widget通常会在Machine驱动中定义。

而输入(snd_soc_dapm_input)和输出(snd_soc_dapm_output)则用来定义codec芯片的输入输出引脚,通常在codec驱动中定义。

最后,在machine驱动中增加相应的route,把麦克风和耳机等widget与相应的codec输入输出引脚的widget连接起来。

例如,对于一个外部麦克风的Jack connetcor widget,在麦克风插入的时候启用Mic Bias:

static int spitz_mic_bias(struct snd_soc_dapm_widget* w, int event)
{
      gpio_set_value(SPITZ_GPIO_MIC_BIAS, SND_SOC_DAPM_EVENT_ON(event));
      return 0;
}

SND_SOC_DAPM_MIC("Mic Jack", spitz_mic_bias),
1.2.3 path域widget

一般是指codec内部的Mixer、Mux、Switch、Demux等控制音频路径的widget,这些widget可以根据用户空间的设定连接关系,自动设定它们的电源状态。

DAPM 框架为我们提供了多种path域widget的辅助定义宏:

/* path domain */
#define SND_SOC_DAPM_PGA(wname, wreg, wshift, winvert,\
         wcontrols, wncontrols) \
{       .id = snd_soc_dapm_pga, .name = wname, \
        SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \
        .kcontrol_news = wcontrols, .num_kcontrols = wncontrols}
#define SND_SOC_DAPM_OUT_DRV(wname, wreg, wshift, winvert,\
         wcontrols, wncontrols) \
{       .id = snd_soc_dapm_out_drv, .name = wname, \
        SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \
        .kcontrol_news = wcontrols, .num_kcontrols = wncontrols}
#define SND_SOC_DAPM_MIXER(wname, wreg, wshift, winvert, \
         wcontrols, wncontrols)\
{       .id = snd_soc_dapm_mixer, .name = wname, \
        SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \
        .kcontrol_news = wcontrols, .num_kcontrols = wncontrols}
#define SND_SOC_DAPM_MIXER_NAMED_CTL(wname, wreg, wshift, winvert, \
         wcontrols, wncontrols)\
{       .id = snd_soc_dapm_mixer_named_ctl, .name = wname, \
        SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \
        .kcontrol_news = wcontrols, .num_kcontrols = wncontrols}
/* DEPRECATED: use SND_SOC_DAPM_SUPPLY */
#define SND_SOC_DAPM_MICBIAS(wname, wreg, wshift, winvert) \
{       .id = snd_soc_dapm_micbias, .name = wname, \
        SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \
        .kcontrol_news = NULL, .num_kcontrols = 0}
#define SND_SOC_DAPM_SWITCH(wname, wreg, wshift, winvert, wcontrols) \
{       .id = snd_soc_dapm_switch, .name = wname, \
        SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \
        .kcontrol_news = wcontrols, .num_kcontrols = 1}
#define SND_SOC_DAPM_MUX(wname, wreg, wshift, winvert, wcontrols) \
{       .id = snd_soc_dapm_mux, .name = wname, \
        SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \
        .kcontrol_news = wcontrols, .num_kcontrols = 1}
#define SND_SOC_DAPM_DEMUX(wname, wreg, wshift, winvert, wcontrols) \
{       .id = snd_soc_dapm_demux, .name = wname, \
        SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \
        .kcontrol_news = wcontrols, .num_kcontrols = 1}

......
View Code

可以看出,这些widget的reg和shift字段是需要赋值的,说明这些widget是有相应的电源控制寄存器的,DAPM框架在扫描和更新音频路径时,会利用这些寄存器来控制widget的电源状态,使得它们的供电状态是按需分配的,需要的时候(在有效的音频路径上)上电,不需要的时候(不在有效的音频路径上)下电。

这些widget需要完成和之前介绍的Mixer、Mux等控件同样的功能,实际上,这是通过它们包含的dapm kcontrol控件来完成的,这些dapm kcontrol我们需要在定义widget前先定义好,然后通过wcontrols和num_kcontrols参数传递给这些辅助定义宏。

例如,Mixer widget配置如下:

static const struct snd_kcontrol_new wm8960_loutput_mixer[] = {
    SOC_DAPM_SINGLE("PCM Playback Switch", WM8960_LOUTMIX, 8, 1, 0),
    SOC_DAPM_SINGLE("LINPUT3 Switch", WM8960_LOUTMIX, 7, 1, 0),
    SOC_DAPM_SINGLE("Boost Bypass Switch", WM8960_BYPASS1, 7, 1, 0),
};

SND_SOC_DAPM_MIXER("Left Output Mixer", WM8960_POWER3, 3, 0,      // Left Output Mixer
        &wm8960_loutput_mixer[0],   // Left Output Mixer包含3个kcontrol,每个kcontrol控制着Mixer的一个输入端的开启和关闭
        ARRAY_SIZE(wm8960_loutput_mixer)),

留意WM8960_POWER3寄存器的位3正是控制 Left Output Mixer上下电的。

如果需要自定义这些widget的dapm事件处理回调函数,也可以使用下面这些带 "_E" 后缀的版本:

  • SND_SOC_DAPM_PGA_E;
  • SND_SOC_DAPM_OUT_DRV_E;
  • SND_SOC_DAPM_MIXER_E;
  • SND_SOC_DAPM_MIXER_NAMED_CTL_E;
  • SND_SOC_DAPM_SWITCH_E;
  • SND_SOC_DAPM_MUX_E;
  • SND_SOC_DAPM_VIRT_MUX_E;
1.2.4 stream域widget

是指那些需要处理音频数据流的widget,例如 ADC、DAC、AIF IN、AIF OUT等等。DAPM 框架为我们提供了多种stream域widget的辅助定义宏:

/* stream domain */
#define SND_SOC_DAPM_AIF_IN(wname, stname, wchan, wreg, wshift, winvert) \
{       .id = snd_soc_dapm_aif_in, .name = wname, .sname = stname, \
        .channel = wchan, SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), }
#define SND_SOC_DAPM_AIF_IN_E(wname, stname, wchan, wreg, wshift, winvert, \
                              wevent, wflags)                           \
{       .id = snd_soc_dapm_aif_in, .name = wname, .sname = stname, \
        .channel = wchan, SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \
        .event = wevent, .event_flags = wflags }
#define SND_SOC_DAPM_AIF_OUT(wname, stname, wchan, wreg, wshift, winvert) \
{       .id = snd_soc_dapm_aif_out, .name = wname, .sname = stname, \
        .channel = wchan, SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), }
#define SND_SOC_DAPM_AIF_OUT_E(wname, stname, wchan, wreg, wshift, winvert, \
                             wevent, wflags)                            \
{       .id = snd_soc_dapm_aif_out, .name = wname, .sname = stname, \
        .channel = wchan, 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 }
View Code

需要注意的是:我们在codec驱动中定义stream widget时,他们的sname必须要包含codec dai的sname,这样才能让ASoC自动的把codec dai widget和stream widget连接在一起,只有把它们连接在一起,ASoc中的播放、录音和停止等事件,才能通过dai widget传递到codec中,使得codec中的widget能根据目前的播放状态,动态地开启或关闭音频路径complete path上所有widget的电源。

例如,针对HiFi音频播放和捕获,可以使用如下的stream widget配置:

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

针对AIF,可以使用如下的stream widget配置:

SND_SOC_DAPM_AIF_IN("AIF1RX", "AIF1 Playback", 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_OUT("AIF1TX", "AIF1 Capture", 0, SND_SOC_NOPM, 0, 0),

除了上述这些 widget,还有另外三种widget没有提供显示的定义方法,其种类id分别为:

  • snd_soc_dapm_dai_in;
  • snd_soc_dapm_dai_out;
  • snd_soc_dapm_dai_link;

还记得ASOC架构中Codec中的snd_soc_dai结构吗?每个codec有多个dai,而cpu也会有多个dai,dai注册时,dapm系统会为每个dai创建一个snd_soc_dapm_dai_in或snd_soc_dapm_dai_out类型的widget,通常,这两种widget会和codec中具有相同的stream name的widget进行连接。

另外一种情况,当系统中具有多个音频处理器(比如多个Codec)时,它们之间可能会通过某两个dai进行连接,当Machine驱动确认有这种配置时(通过判断dai_links结构中的param字段),会为它们建立一个dai_link把它们绑定在一起,因为有连接关系,两个音频处理器之间的widget的电源状态就可以互相传递。

1.2.5 虚拟widget

有时候在codec和machine中存在一些没有widget,它们没有对应的软件电源控制位。在这种情景下,有必要创建虚拟的widget,例如:

SND_SOC_DAPM_MIXER("AC97 Mixer", SND_SOC_DAPM_NOPM, 0, 0, NULL, 0),
1.2.6 其它

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

/* generic widgets */
#define SND_SOC_DAPM_REG(wid, wname, wreg, wshift, wmask, won_val, woff_val) \
{       .id = wid, .name = wname, .kcontrol_news = NULL, .num_kcontrols = 0, \
        .reg = wreg, .shift = wshift, .mask = wmask, \
        .on_val = won_val, .off_val = woff_val, }
#define SND_SOC_DAPM_SUPPLY(wname, wreg, wshift, winvert, wevent, wflags) \
{       .id = snd_soc_dapm_supply, .name = wname, \
        SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \
        .event = wevent, .event_flags = wflags}
#define SND_SOC_DAPM_REGULATOR_SUPPLY(wname, wdelay, wflags)        \
{       .id = snd_soc_dapm_regulator_supply, .name = wname, \
        .reg = SND_SOC_NOPM, .shift = wdelay, .event = dapm_regulator_event, \
        .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD, \
        .on_val = wflags}
#define SND_SOC_DAPM_PINCTRL(wname, active, sleep) \
{       .id = snd_soc_dapm_pinctrl, .name = wname, \
        .priv = (&(struct snd_soc_dapm_pinctrl_priv) \
                { .active_state = active, .sleep_state = sleep,}), \
        .reg = SND_SOC_NOPM, .event = dapm_pinctrl_event, \
        .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD }
View Code

在完成所有的widget定义之后,我们可以使用snd_soc_dapm_new_control方法将这些widget添加到DAPM子系统。

1.3 辅助宏定义dapm kcontrol

对于音频路径上的Mixer或Mux类型的widget,它们包含了若干个kcontrol,这些被包含的kcontrol实际上就是我们之前讨论的Mixer和Mux等,dapm利用这些kcontrol完成音频的控制。

static const struct snd_kcontrol_new wm8960_lin_boost[] = { // 这个Mixer使用寄存器WM8960_LINPATH的第6,7、8位来分别控制3个输入端的开启和关闭。
    SOC_SINGLE("LINPUT2 Switch", WM8960_LINPATH, 6, 1, 0),
    SOC_SINGLE("LINPUT3 Switch", WM8960_LINPATH, 7, 1, 0),
    SOC_SINGLE("LINPUT1 Switch", WM8960_LINPATH, 8, 1, 0),
};

不过,对于widget来说,它的任务还不止这些,dapm还要动态地管理这些音频路径的连结关系,以便可以根据这些连接关系来控制这些widget的电源状态,如果按照普通的方法定义这些kcontrol,是无法达到这个目的的,因此,dapm为我们提供另外一套定义宏,由它们完成这些被widget包含的kcontrol的定义,如下:

/* dapm kcontrol types */
#define SOC_DAPM_DOUBLE(xname, reg, lshift, rshift, max, invert) \
{       .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
        .info = snd_soc_info_volsw, \
        .get = snd_soc_dapm_get_volsw, .put = snd_soc_dapm_put_volsw, \
        .private_value = SOC_DOUBLE_VALUE(reg, lshift, rshift, max, invert, 0) }
#define SOC_DAPM_DOUBLE_R(xname, lreg, rreg, shift, max, invert) \
{       .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
        .info = snd_soc_info_volsw, \
        .get = snd_soc_dapm_get_volsw, .put = snd_soc_dapm_put_volsw, \
        .private_value = SOC_DOUBLE_R_VALUE(lreg, rreg, shift, max, invert) }
#define SOC_DAPM_SINGLE(xname, reg, shift, max, invert) \
{       .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
        .info = snd_soc_info_volsw, \
        .get = snd_soc_dapm_get_volsw, .put = snd_soc_dapm_put_volsw, \
        .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert, 0) }
#define SOC_DAPM_SINGLE_AUTODISABLE(xname, reg, shift, max, invert) \
{       .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
        .info = snd_soc_info_volsw, \
        .get = snd_soc_dapm_get_volsw, .put = snd_soc_dapm_put_volsw, \
        .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert, 1) }
#define SOC_DAPM_SINGLE_VIRT(xname, max) \
        SOC_DAPM_SINGLE(xname, SND_SOC_NOPM, 0, max, 0)
#define SOC_DAPM_SINGLE_TLV(xname, reg, shift, max, invert, tlv_array) \
{       .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
        .info = snd_soc_info_volsw, \
        .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | SNDRV_CTL_ELEM_ACCESS_READWRITE,\
        .tlv.p = (tlv_array), \
        .get = snd_soc_dapm_get_volsw, .put = snd_soc_dapm_put_volsw, \
        .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert, 0) }
#define SOC_DAPM_SINGLE_TLV_AUTODISABLE(xname, reg, shift, max, invert, tlv_array) \
{       .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
        .info = snd_soc_info_volsw, \
        .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | SNDRV_CTL_ELEM_ACCESS_READWRITE,\
        .tlv.p = (tlv_array), \
        .get = snd_soc_dapm_get_volsw, .put = snd_soc_dapm_put_volsw, \
        .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert, 1) }
#define SOC_DAPM_SINGLE_TLV_VIRT(xname, max, tlv_array) \
        SOC_DAPM_SINGLE(xname, SND_SOC_NOPM, 0, max, 0, tlv_array)
#define SOC_DAPM_ENUM(xname, xenum) \
{       .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
        .info = snd_soc_info_enum_double, \
        .get = snd_soc_dapm_get_enum_double, \
        .put = snd_soc_dapm_put_enum_double, \
        .private_value = (unsigned long)&xenum }
#define SOC_DAPM_ENUM_EXT(xname, xenum, xget, xput) \
{       .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
        .info = snd_soc_info_enum_double, \
        .get = xget, \
        .put = xput, \
        .private_value = (unsigned long)&xenum }
#define SOC_DAPM_PIN_SWITCH(xname) \
{       .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname " Switch", \
        .info = snd_soc_dapm_info_pin_switch, \
        .get = snd_soc_dapm_get_pin_switch, \
        .put = snd_soc_dapm_put_pin_switch, \
        .private_value = (unsigned long)xname }
View Code

可以看出,SOC_DAPM_SINGLE对应普通控件的SOC_SINGLE,SOC_DAPM_SINGLE_TLV对应SOC_SINGLE_TLV…,相比普通的kcontrol控件,dapm的kcontrol只是把info,get,put回调函数换掉了。

为了和普通的kcontrol进行区分,这些由DAPM系统提供的辅助宏定义的kcontrol我们统一称为dapm kcontrol。

dapm kcontrol的put回调函数不仅仅会更新控件本身的状态,它还会把这种变化传递到相邻的dapm kcontrol,相邻的dapm kcontrol又会传递到这个变化到它自己相邻的kcontrol,直到音频路径的末端,通过这种机制,只要改变其中一个widget的链接状态,与之相关的所有widget都会被扫描并测试一下自身是否还在有效的音频路径中,从而可以动态地改变自身的电源状态,这就是dapm的精髓所在。

二、Path

之前我们说过一个widget是可以和其它widget互联的,既然可以互联,那么不难想象widget应该是由输入和输出的,那么我们用什么来连接两个widget呢?

DAPM为我们提出了path这一概念,path相当于电路中的一根跳线,它把一个widget的输出端和另一个widget的输入端连接在一起,path用snd_soc_dapm_path结构来描述。

2.1 struct snd_soc_dapm_path

struct snd_soc_dapm_path定义在include/sound/soc-dapm.h:

/* dapm audio path between two widgets */
struct snd_soc_dapm_path {
        const char *name;

        /*
         * source (input) and sink (output) widgets
         * The union is for convience, since it is a lot nicer to type
         * p->source, rather than p->node[SND_SOC_DAPM_DIR_IN]
         */
        union {
                struct {
                        struct snd_soc_dapm_widget *source;  // 输入端widget
                        struct snd_soc_dapm_widget *sink;    // 输出端widget
                };
                struct snd_soc_dapm_widget *node[2];
        };

        /* status */
        u32 connect:1;          /* source and sink widgets are connected */
        u32 walking:1;          /* path is in the process of being walked */
        u32 weak:1;             /* path ignored for power management */
        u32 is_supply:1;        /* At least one of the connected widgets is a supply */

        int (*connected)(struct snd_soc_dapm_widget *source,
                         struct snd_soc_dapm_widget *sink);

        struct list_head list_node[2];   // 用于构建链表节点
        struct list_head list_kcontrol;
        struct list_head list;      // 用于构建链表节点,添加到card->paths链表中
};

当widge之间发生连接关系时,snd_soc_dapm_path作为连接者,它的source字段会指向路径起始端widget,而它的sink会指向路径目标端的widget。

还记得snd_soc_dapm_widget结构中的edges字段么?widget的输入端和输出端可能连接多个path:

  • widget输出端连接的snd_soc_dapm_path结构通过list_node[0]成员挂在widget的edges[0]链表中;
  • widget输入端连接的snd_soc_dapm_path结构通过list_node[1]成员挂在widget的edges[1]链表中;

因此,不难看出完整的路径包括:起始端widget   --> path的输入 --> path的输出 ---> 目标端widget;

另外,snd_soc_dapm_path结构的list字段用于把所有的path注册到声卡中,其实就是挂在snd_soc_card结构的paths链表头字段中。如果你要自己定义方法来检查path的当前连接状态,你可以提供自己的connected回调函数指针。

connect用来表示source widget和sink widget之间的连接状态,如果在source widget和sink widget中间有kcontrol可以控制通断,那么connect表示的就是kcontrol的通断状态,对于两个直连的widget,connect值始终为1。

walked,walking,weak是几个辅助字段,用于帮助所有path的遍历。

2.2 complete path

当音频路径发生改变(比如上层使用tinymix工具设置音频通路)时,或发生数据流事件(比如启动或停止播放)时,都会触发dapm去遍历所有邻近的widget,检查是否存在完整的音频路径,如果存在完整的音频路径,则该路径上面的所有部件都是需要上电的,其他部件则下电。

那什么是完整的音频路径呢?一条完整的音频路径它必须有起点和终点,我们把位于这些起点和终点的widget称之为端点widget。以下这些类型的widget可以称为端点widget:

分类 widget类型
codec的输入输出引脚

snd_soc_dapm_input(SND_SOC_DAPM_EP_SOURCE类型端点)

snd_soc_dapm_output (SND_SOC_DAPM_EP_SINK类型端点)

codec外接的音频设备 

snd_soc_dapm_mic(SND_SOC_DAPM_EP_SOURCE类型端点)

snd_soc_dapm_hp(SND_SOC_DAPM_EP_SINK类型端点)

snd_soc_dapm_spk(SND_SOC_DAPM_EP_SINK类型端点)

snd_soc_dapm_line(如果该widget作为输入,则是SND_SOC_DAPM_EP_SOURCE类型端点,否则是SND_SOC_DAPM_EP_SINK类型端点)

dai widget

snd_soc_dapm_dai_in(在SND_SOC_DAPM_STREAM_START操作时,设置为SND_SOC_DAPM_EP_SOURCE类型端点)

snd_soc_dapm_dai_out(在SND_SOC_DAPM_STREAM_START操作时,设置为SND_SOC_DAPM_EP_SINK类型端点)

具体参考方法snd_soc_dapm_dai_stream_event;

 

因此我们我们可以推断出dapm要给一个widget上电的其中一个前提条件是:这个widget位于一条完整的音频路径上,即向后/向前查找连接的widget均能够找到一个端点widget,并且路径上的widget、path都是处于连接状态的(需要注意的是,compolete path与路径上的widget的电源状态没有任何关系)。

dapm提供了两个内部函数,用来统计一个widget连接到codec的输入/输出引脚、codec外接的音频设备、以及dai widget的有效路径个数;

  • is_connected_output_ep :从当前widget向后遍历(包含当前widget),返回连接至SND_SOC_DAPM_EP_SINK类型端点的路径数量;
  • is_connected_input_ep :从当前wiget向前遍历(包含当前widget),返回连接至SND_SOC_DAPM_EP_SOURCE类型端点路径数量;
2.2.1 is_connected_output_ep 

is_connected_output_ep函数定义在sound/soc/soc-dapm.c;

/*
 * 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,  // 假设dir=1,即输出
        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);  // 方向反转 比如1->0
        struct snd_soc_dapm_path *path;
        int con = 0;

        if (widget->endpoints[dir] >= 0)     // 如果>=0,表示已经统计了以当前widget作为起点的完全路径个数
                return widget->endpoints[dir];

        DAPM_UPDATE_STAT(widget, path_checks);

        /* do we need to add this widget to the list ? */ /* work_list用于将当前widget添加到完整路径链表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; }
 // 判断该widget是否是端点等
if ((widget->is_ep & SND_SOC_DAPM_DIR_TO_EP(dir)) && widget->connected) { // 如果是widget是SND_SOC_DAPM_EP_SINK类型的端点,且widget->connected=1,进入 widget->endpoints[dir] = snd_soc_dapm_suspend_check(widget); // 默认返回1 return widget->endpoints[dir]; } snd_soc_dapm_widget_for_each_path(widget, rdir, path) { // 遍历以widget作为输入端的path,比如:widget -- path -- widget1
DAPM_UPDATE_STAT(widget, neighbour_checks);
if (path->weak || path->is_supply) // 如果path两端连接的有电源类型的widget,结束该路径的扫描 扫描下一个路径 continue; if (path->walking) // 出现再次遍历的情况,一般应该是path存在循环导致的,所以这里直接返回 return 1; trace_snd_soc_dapm_path(widget, dir, path); if (path->connect) { // path是连通状态 path->walking = 1; // 标志位,表示正在遍历 con += fn(path->node[dir], list, custom_stop_condition); // fn=is_connected_output_ep, 递归计算path.sink指向的widget path->walking = 0; // 标志位,表示正在遍历 } }      // 设置从当前widget出发到输出端点的路径个数 widget->endpoints[dir] = con; return con; } /* * 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, // SND_SOC_DAPM_DIR_OUT=1 is_connected_output_ep, custom_stop_condition); }

该函数使用了递归算法,直到遇到端点widget为止才停止扫描,把统计到的输出路径个数保存在con字段中并返回。

这段代码可能不是很好理解,我们举个例子如下图,假设图中每个path、widget都是处于连通状态的,即path->connect=1,widget->connected=1:

上图从widget A开始遍历,一共有三条路径:

  • 第一条路径遍历到widget D时,发现widget D是一个端点widget(即is_ep=1),则停止该条路径的遍历,con=1;
  • 第二条路径一直遍历到最后一个widget,也没有找到一个端点widget,因此con依然=1;
  • 第三条路径遍历到widget C时,发现widget C是一个端点widget(即is_ep=1),则停止该条路径的遍历,con=2;

该函数执行完成后,会更新位于每条路径上的widget的成员endpoints:

  • 元素0保存从当前widget向后遍历,连接至SND_SOC_DAPM_EP_SOURCE类型端点的路径数量;
  • 元素1保存从当前widget向后遍历,连接至SND_SOC_DAPM_EP_SINK类型端点的路径数量;

需要注意的是在路径遍历时如果遇到snd_soc_dapm_supply、snd_soc_dapm_regulator_supply、snd_soc_dapm_pinctrl、snd_soc_dapm_clock_supply、snd_soc_dapm_kcontrol类型的widget,则widget之后的路径都不再继续扫描。

2.2.2 is_connected_input_ep 

is_connected_input_ep函数定义在sound/soc/soc-dapm.c;

/*
 * 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,   // SND_SOC_DAPM_DIR_IN=0
                        is_connected_input_ep, custom_stop_condition);
}

三、Route

我们已经知道一个完整的路径包括:起始端widget   --> path的输入 --> path的输出 ---> 目标端widget。DAPM使用strcut snd_soc_dapm_route结构来描述这样一个完整路径,定义在include/sound/soc-dapm.h:

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

        struct snd_soc_dobj dobj;
};

其中:

  • source:指向起始端widget的名称;
  • sink:指向目标端widget的名称;
  • control:指向负责控制该连接所对应的kcontrol的名称;
  • connected:自定义连接检查回调函数;

这里直接使用名称来描述连接关系,所有定义好的route,最后都要注册到dapm系统中,dapm会根据这些名字找出相应的widget,并动态地生成所需要的snd_soc_dapm_path结构,正确地处理各个链表和指针的关系,实现两个widget之间的连接。

四、核心API

4.1 创建widget(snd_soc_dapm_new_controls)

DAPM提供了snd_soc_dapm_new_controls函数用于创建新的widget,注意这里我说还要创建这个词,你可能比较奇怪,既然代表widget的widget数组已经通过辅助宏定义好了,为什么还要在创建?

事实上,采用辅助宏定义的widget数组只是一个只是作为一个模板,snd_soc_dapm_new_controls会根据该模板重新申请内存并初始化各个widget。

此外,还会将每一个widget添加到声卡card的widgets链表中,函数定义在sound/soc/soc-dapm.c:

/**
 * 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,  // dapm域
        const struct snd_soc_dapm_widget *widget, // widget数组
        int num)         // widget数量
{
        int i;
        int ret = 0;

        mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_INIT);  // 获取互斥锁
        for (i = 0; i < num; i++) {
                struct snd_soc_dapm_widget *w = snd_soc_dapm_new_control_unlocked(dapm, widget); // 创建一个新的widget,根据widget->id标识的不同,初始化widget部分成员
                if (IS_ERR(w)) {
                        ret = PTR_ERR(w);
                        break;
                }
                widget++; // 后移1
        }
        mutex_unlock(&dapm->card->dapm_mutex);  // 释放互斥锁
        return ret;
}

该函数拥有三个参数,第一个参数为dapm域,第二个参数为widget数组,第三个参数为widget数组的长度。

函数内部遍历widget数组中的每一个widget,将其作为参数传递给snd_soc_dapm_new_control_unlocked函数,实际的工作由snd_soc_dapm_new_control_unlocked完成。

snd_soc_dapm_new_control_unlocked函数代码还是比较长的,之所以长主要是因为它根据widget类型的不同去执行不同的初始化操作,由于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 = -ENOMEM;

        if ((w = dapm_cnew_widget(widget)) == NULL)  // 为该widget重新分配内存,并把模板的内容拷贝过来
                goto cnew_failed;

        prefix = soc_dapm_prefix(dapm);          // dapm->component存在,则返回dapm->component->name_prefix
        if (prefix)                              // 在widget的名称前加入必要的前缀
                w->name = kasprintf(GFP_KERNEL, "%s %s", prefix, widget->name);
        else
                w->name = kstrdup_const(widget->name, GFP_KERNEL);
        if (!w->name)
                goto name_failed;

        switch (w->id) {  // 根据widget的类型,执行不同的流程
        case snd_soc_dapm_regulator_supply:      // 外部电源调节器,用于给声卡设备供电   根据widget的名称获取对应的regulator结构
                w->regulator = devm_regulator_get(dapm->dev, widget->name);
                if (IS_ERR(w->regulator)) {
                        ret = PTR_ERR(w->regulator);
                        goto request_failed;
                }

                if (w->on_val & SND_SOC_DAPM_REGULATOR_BYPASS) {
                        ret = regulator_allow_bypass(w->regulator, true);
                        if (ret != 0)
                                dev_warn(dapm->dev,
                                         "ASoC: Failed to bypass %s: %d\n",
                                         w->name, ret);
                }
                break;
        case snd_soc_dapm_pinctrl:       // 引脚控制器
                w->pinctrl = devm_pinctrl_get(dapm->dev);
                if (IS_ERR(w->pinctrl)) {
                        ret = PTR_ERR(w->pinctrl);
                        goto request_failed;
                }

                /* set to sleep_state when initializing */
                dapm_pinctrl_event(w, NULL, SND_SOC_DAPM_POST_PMD);
                break;
        case snd_soc_dapm_clock_supply:   // 外部使用,用于给声卡设备提供时钟信号 根据widget的名称,获取对应的clk结构
                w->clk = devm_clk_get(dapm->dev, w->name);
                if (IS_ERR(w->clk)) {
                        ret = PTR_ERR(w->clk);
                        goto request_failed;
                }
                break;
        default:
                break;
        }

        switch (w->id) {  // 根据widget的类型,执行不同的流程   为不同类型的widget设置合适的power_check电源状态回调函数
        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_buffer:
        case snd_soc_dapm_scheduler:
        case snd_soc_dapm_effect:
        case snd_soc_dapm_src:
        case snd_soc_dapm_asrc:
        case snd_soc_dapm_encoder:
        case snd_soc_dapm_decoder:
        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_pinctrl:
        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; // 设置widget所属的codec、platform和dapm context
        INIT_LIST_HEAD(&w->list);
        INIT_LIST_HEAD(&w->dirty);
        /* see for_each_card_widgets */
        list_add_tail(&w->list, &dapm->card->widgets);  // 将当前widget节点添加到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;

request_failed:
        dev_err_probe(dapm->dev, ret, "ASoC: Failed to request %s\n",
                      w->name);
        kfree_const(w->name);
name_failed:
        kfree_const(w->sname);
        kfree(w);
cnew_failed:
        return ERR_PTR(ret);
}
View Code

代码的主要执行流程:

  • 为该widget重新分配内存,并把模板的内容拷贝过来:
  • 在widget的名称前加入必要的前缀;
  • 根据widget的类型做不同的处理:比如对于snd_soc_dapm_regulator_supply类型的widget,根据widget的名称获取对应的regulator结构,对于snd_soc_dapm_clock_supply类型的widget,根据widget的名称,获取对应的clock结构;
  • 为不同类型的widget设置合适的power_check电源状态回调函数,当音频路径发生变化时,power_check回调会被调用,用于检查该widget的电源状态;
  • 如果widget的类型为snd_soc_dapm_mic、snd_soc_dapm_input、snd_soc_dapm_spk、snd_soc_dapm_hp、snd_soc_dapm_output、snd_soc_dapm_vmid、snd_soc_dapm_siggen、snd_soc_dapm_sink,则设置is_ep的值(SND_SOC_DAPM_EP_SINK:表明这是输出类型的端点、SND_SOC_DAPM_EP_SOURCE:表明这是输入类型的端点);
  • widget所属的codec、platform和dapm context;
  • 初始化各个链表(list、dirty),并把该widget加入到声卡card的widgets链表中:
  • 最后,把widget设置为连接状态;

到此,widget已经被正确地创建并初始化,而且被挂在声卡card的widgets链表中,以后我们就可以通过声卡的widgets链表来遍历所有的widget。

4.1.1 dapm_generic_check_power

这里我们以snd_soc_dapm_mixer、snd_soc_dapm_mic、snd_soc_dapm_input、snd_soc_dapm_hp等类型的widget为例,其power_check被设置为了dapm_generic_check_power;该函数会判断当前wiget是否在complete path上,如果是则需要设置widget的电源状态为上电,否则设置widget的电源状态为下电;

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

函数首先调用is_connected_input_ep从当前wiget向前遍历,获取连接至SND_SOC_DAPM_EP_SOURCE类型端点路径数量;

然后调用is_connected_output_ep从当前widget向后遍历,获取连接至SND_SOC_DAPM_EP_SINK类型端点的路径数量;

如果out!=0 && in!=0,表示存在完整路径,因此需要设置widget的电源状态为上电,否则设置widget的电源状态为下电。

4.1.2 dapm_supply_check_power

这里我们以snd_soc_dapm_supply、snd_soc_dapm_regulator_supply、snd_soc_dapm_kcontrol等类型的widget为例,其power_check被设置为了dapm_supply_check_power;该函数内部也是调用了dapm_generic_check_power,具体就不介绍了;

/* Check to see if a power supply is needed */
static int dapm_supply_check_power(struct snd_soc_dapm_widget *w)
{
        struct snd_soc_dapm_path *path;

        DAPM_UPDATE_STAT(w, power_checks);

        /* Check if one of our outputs is connected */
        snd_soc_dapm_widget_for_each_sink_path(w, path) {
                DAPM_UPDATE_STAT(w, neighbour_checks);

                if (path->weak)
                        continue;

                if (path->connected &&
                    !path->connected(path->source, path->sink))
                        continue;

                if (dapm_widget_power_check(path->sink))
                        return 1;
        }

        return 0;
}

4.2 创建dai widget(snd_soc_dapm_new_dai_widgets)

snd_soc_dapm_new_dai_widgets函数用来生成dai的播放流widget和录音流widget,并保存到dai->stream[i].widget,函数定义在sound/soc/soc-dapm.c:

/**
 * snd_soc_dapm_new_dai_widgets - Create new DAPM widgets
 * @dapm: DAPM context
 * @dai: parent DAI
 *
 * Returns 0 on success, error code otherwise.
 */
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;
        // 创建播放dai widget
        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); // 根据widget模板创建一个新的widget,并添加到声卡card的widgets链表
                if (IS_ERR(w))
                        return PTR_ERR(w);

                w->priv = dai;     // 对于snd_soc_dapm_dai_in类型的widget,priv字段保存的是之相关联的snd_soc_dai结构指针
                snd_soc_dai_set_widget_playback(dai, w);  // dai->stream[0].widget=w
        }
     // 创建录音dai widget 
        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))
                        return PTR_ERR(w);

                w->priv = dai;    // 对于snd_soc_dapm_dai_out类型的widget,priv字段保存的是之相关联的snd_soc_dai结构指针
                snd_soc_dai_set_widget_capture(dai, w);  // dai->stream[1].widget=w
        }

        return 0;
}

分别为playback和capture创建了一个widget,widget的priv字段指向了该dai,这样通过widget就可以找到相应的dai,并且widget的名字就是snd_soc_dai_driver结构的stream_name。

4.3 widget创建dapm kcontrol(snd_soc_dapm_new_widgets)

我们通过snd_soc_dapm_new_controls创建了widget之后,要使widget之间具备连接能力,我们还需要调用snd_soc_dapm_new_widgets函数。

snd_soc_dapm_new_widgets这个函数会根据widget的信息,创建widget所需要的kcontrol,这些kcontol的状态变化,代表着音频路径的变化,从而影响着各个widget的电源状态。

看到函数的名称可能会迷惑一下,实际上snd_soc_dapm_new_controls的作用更多地是创建widget,而snd_soc_dapm_new_widget的作用则更多地是创建widget所包含的kcontrol,所以 在我看来,这两个函数名称应该换过来叫更好。

snd_soc_dapm_new_widgets函数定义在sound/soc/soc-dapm.c,该函数会通过声卡card的widgets链表,遍历所有已经注册了的widget,其中的new字段用于判断该widget是否已经执行过snd_soc_dapm_new_widgets函数。代码如下:

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

        for_each_card_widgets(card, w)     // 遍历card->widgets链表,赋值给w
        {
                if (w->new)
                        continue;

                if (w->num_kcontrols) {    // widget包含的kcontrol数量 > 0
                        w->kcontrols = kcalloc(w->num_kcontrols,
                                                sizeof(struct snd_kcontrol *), // 动态分配指针数组,数组长度为num_kcontrols,每一个成员类型为struct snd_kcontrol *
                                                GFP_KERNEL);
                        if (!w->kcontrols) {
                                mutex_unlock(&card->dapm_mutex);
                                return -ENOMEM;
                        }
                }

                switch(w->id) {  // 根据widget类型不同,调用不同的函数创建kcontrol
                case snd_soc_dapm_switch:
                case snd_soc_dapm_mixer:
                case snd_soc_dapm_mixer_named_ctl:
                        dapm_new_mixer(w);  // 主要流程就是初始化w成员kcontrols,w->kcontrols[i] = snd_soc_cnew(w->kcontrol_news[i],NULL,name,prefix);
                        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_effect:
                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) {  // 如果wiget配置了寄存器地址,表示可以通过寄存器控制widget的电源状态  根据widget寄存器的当前值,初始化widget的电源状态,并设置到power字段中
                        val = soc_dapm_read(w->dapm, w->reg);  // 读取widget寄存器的当前值
                        val = val >> w->shift;                 // 寄存器移位,获取当前位值
                        val &= w->mask;                        // &运算,获取有效值 
                        if (val == w->on_val)   // 当前电源已经开启
                                w->power = 1;   // 设置widget为上电状态
                }

                w->new = 1;   // 设置new标志位,表示该widget已经执行snd_soc_dapm_new_widgets,即已经完成了kcontrol的创建工作

                dapm_mark_dirty(w, "new widget");
                dapm_debugfs_add_widget(w);   // 在debugfs创建以w->name命名的文件
        }

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

函数执行流程:

(1) 如果num_kcontrols字段有数值,表明该widget包含有若干个kcontrol,那么就需要为这些kcontrol分配一个指针(struct snd_kcontrol *类型)数组,并把数组的首地址赋值给widget的kcontrols字段,该数组存放着指向这些kcontrol的指针,当然现在这些都是空指针,因为实际的kcontrol现在还没有被创建:

(2) 对几种能够影响音频路径的widget,创建并初始化它们所包含的kcontrol;需要用到的创建函数分别是:

  • dapm_new_mixer:对于mixer类型,用该函数创建kcontrol;
  • dapm_new_mux:对于mux类型,用该函数创建kcontrol;
  • dapm_new_pga:对于pga类型,用该函数创建kcontrol;
  • dapm_new_dai_link:对于dal_link类型,用该函数创建kcontrol;

(3) 然后,如果wiget配置了寄存器地址,表示可以通过寄存器控制widget的电源状态;因此根据widget寄存器的当前值,初始化widget的电源状态,并设置到power字段中:

(4) 设置new标志位,表示该widget已经执行snd_soc_dapm_new_widgets,即已经完成了kcontrol的创建工作;

(5) 将widget加入到声卡card的dapm_dirty链表中,表明该widget的状态(包括连接状态和电源状态)发生了变化,稍后在合适的时刻,dapm框架会扫描dapm_dirty链表,统一处理所有状态已经变化的widget。为什么要统一处理?因为dapm要控制各种widget的上下电顺序,同时也是为了减少寄存器的读写次数(多个widget可能使用同一个寄存器):

dapm_mark_dirty函数用于将widget加入到声卡card的dapm_dirty链表中;

static bool dapm_dirty_widget(struct snd_soc_dapm_widget *w)
{
        return !list_empty(&w->dirty);  // list_empty函数:w->dirty.next == w->dirty
}

static void dapm_mark_dirty(struct snd_soc_dapm_widget *w, const char *reason)
{
        dapm_assert_locked(w->dapm);

        if (!dapm_dirty_widget(w)) {   // list_empty(&w->dirty), 如果w没有添加到任何链表,则将widget加入到声卡card的dapm_dirty链表中
                dev_vdbg(w->dapm->dev, "Marking %s dirty due to %s\n",
                         w->name, reason);
                list_add_tail(&w->dirty, &w->dapm->card->dapm_dirty);
        }
}

(6) 最后,通过dapm_power_widgets函数,统一处理所有位于dapm_dirty链表上状态已经变化的widget。

4.3.1 dapm_new_mixer

这里我们以snd_soc_dapm_mixer类型的widget为例来说,说一说dapm_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++) {   // 创建每一个kcontrol
                /* match name */
                snd_soc_dapm_widget_for_each_source_path(w, path) {  // 遍历w->edges[1]链表,赋值给path
                        /* mixer/mux paths name must match control name */
                        if (path->name != (char *)w->kcontrol_news[i].name)  // path中的名称和kcontrol模板名称不匹配
                                continue;

                        if (!w->kcontrols[i]) {    // 还没创建
                                ret = dapm_create_or_share_kcontrol(w, i);  // 创建kcontrol,并使用kcontrol模板进行初始化;                                                                           
// w->kcontrols[i] = snd_soc_cnew(w->kcontrol_news[i],NULL,name,prefix); if (ret < 0) return ret; } dapm_kcontrol_add_path(w->kcontrols[i], path); // 添加path节点到data的paths链表的尾部 data = snd_kcontrol_chip(w->kcontrols[i]); if (data->widget) // 如果创建了影子widget snd_soc_dapm_add_path(data->widget->dapm, // 创建一个snd_soc_dapm_path,并将其添加到声卡card的paths链表中 data->widget, // 影子widget作为新创建的path的输入端 path->source, // path的输入端作为新创建的path的输出端 NULL, NULL); } } return 0; }

(1) 由于一个Mixer是由多个kcontrol组成的,每个kcontrol控制着Mixer的一个输入端的开启和关闭,所以该函数会根据kcontrol的数量做循环,逐个建立对应的kcontrol;

(2 ) 因为一个Mixter可能会连接多个输入源,如下图所示,Mixer widget的edges[1]链表保存着所有和它的输入端连接的snd_soc_dapm_path结构,对下图来说,存放着就是下图Mixer widget左侧连接的3个path。接着对这3个path做循环;

Left Output Mixer widget、Left Output Mixer widget所用到的dapm kcontrol、以及以Left Output Mixer widget输入端连接的3条path的定义如下:

static const struct snd_kcontrol_new wm8960_loutput_mixer[] = {  // kcontrol模板
    SOC_DAPM_SINGLE("PCM Playback Switch", WM8960_LOUTMIX, 8, 1, 0),
    SOC_DAPM_SINGLE("LINPUT3 Switch", WM8960_LOUTMIX, 7, 1, 0),
    SOC_DAPM_SINGLE("Boost Bypass Switch", WM8960_BYPASS1, 7, 1, 0),
};

SND_SOC_DAPM_MIXER("Left Output Mixer", WM8960_POWER3, 3, 0,      // Left Output Mixer widget
        &wm8960_loutput_mixer[0],
        ARRAY_SIZE(wm8960_loutput_mixer)),

static const struct snd_soc_dapm_route audio_paths[] = {  // 这里使用的route,你把它想象成path就好了,它俩表示的意义一样的
       .....
        { "Left Output Mixer", "LINPUT3 Switch", "LINPUT3" },      // 依次初始化sink control source
        { "Left Output Mixer", "Boost Bypass Switch", "Left Boost Mixer" },
        { "Left Output Mixer", "PCM Playback Switch", "Left DAC" },           // route 1
        ....
}    

(a) 首先判断kcontrol模板中指定的名字来是否和snd_soc_dapm_path中的名称匹配(这里为什么要这么做呢,这么做的目的实际上就是为了判断当前kcontrol是否负责控制当前path);如果不匹配,直接匹配下一个snd_soc_dapm_path;

(b) 由于Mixer widget的edges[1]链表存放着3个path。所以可能在上一个输入源的path关联时已经创建了这个kcontrol,因此这里判断kcontrols指针数组中对应索引中的指针值,如果已经赋值,说明kcontrol已经在之前创建好了;

否则调用dapm_create_or_share_kcontrol(w,i)创建kcontrol:

  • 为了节省内存,遍历声卡card的widgets链表,查找每个widget的kcontrol_news数组中是否已经存在&w->kcontrol_news[i]这个kcontrol模板,如果存在的话,表明这个kcontrol已经在其他widget中已经创建好了,那我们不再创建;dapm_is_shared_kcontrol的参数kcontrol会返回已经创建好的kcontrol的指针;
  • 调用snd_soc_cnew函数创建snd_kcontrol实例,并把w->kcontrol_news[i]模板中相应的值复制到该kcontrol;
  • 同时初始化kcontrol->private_data(指向一个struct dapm_kcontrol_data,由dapm_kcontrol_data_alloc函数创建);
  • 然后调用snd_ctl_add将kcontrol添加到声卡card的controls链表中;
  • 最后保存新创建的kcontrol搭配w->kcontrols[i]中;
/*
 * 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);       // dapm->component存在,则返回dapm->component->name_prefix
        if (prefix)
                prefix_len = strlen(prefix) + 1;
        else
                prefix_len = 0;

        shared = dapm_is_shared_kcontrol(dapm, w, &w->kcontrol_news[kci],  // 遍历声卡card的widgets链表,查找每个widget的kcontrol_news数组中是
                                                     // 否已经存在&w->kcontrol_news[kci]这个kcontrol模板,如果存在的话返回1,
                                                     // 同时直接返回已经创建的kcontrol(也是说我们不用调用snd_soc_cnew啊含糊创建了)  否则返回0
                                         &kcontrol);

        if (!kcontrol) {   // 如果不存在,则需要我们创建该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_effect:
                        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, // 以&w->kcontrol_news[kci]为模板,创建kcontrol,并把模板中相应的值复制到该kcontrol中
                                        prefix);
                if (!kcontrol) {
                        ret = -ENOMEM;
                        goto exit_free;
                }

                kcontrol->private_free = dapm_kcontrol_free;

                ret = dapm_kcontrol_data_alloc(w, kcontrol, name); // 动态分配struct dapm_kcontrol_data,并赋值给kcontrol->private_data
                if (ret) {
                        snd_ctl_free_one(kcontrol);
                        goto exit_free;
                }

                ret = snd_ctl_add(card, kcontrol);  // 将kcontrol添加到声卡card的controls链表中
                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); // 初始化kcontrol->private_data->wlist(struct snd_soc_dapm_widget_list *n类型),
                                                     // 并将当前w保存到wlist->widget数组中
        if (ret == 0)
                w->kcontrols[kci] = kcontrol; // 保存kcontrol

exit_free:
        kfree(long_name);

        return ret;
}
View Code

该函数内部调用了dapm_kcontrol_data_alloc函数:

static int dapm_kcontrol_data_alloc(struct snd_soc_dapm_widget *widget,
        struct snd_kcontrol *kcontrol, const char *ctrl_name)
{
        struct dapm_kcontrol_data *data;
        struct soc_mixer_control *mc;
        struct soc_enum *e;
        const char *name;
        int ret;

        data = kzalloc(sizeof(*data), GFP_KERNEL);  // 动态分配 struct dapm_kcontrol_data
        if (!data)
                return -ENOMEM;

        INIT_LIST_HEAD(&data->paths);

        switch (widget->id) {
        case snd_soc_dapm_switch:
        case snd_soc_dapm_mixer:
        case snd_soc_dapm_mixer_named_ctl:
                mc = (struct soc_mixer_control *)kcontrol->private_value;

                if (mc->autodisable) {  // 如果在使用了SOC_DAPM_SINGLE_AUTODISABLE定义dapm kcontrol会走这里,而SOC_DAPM_SINGLE并不会走这里
                        struct snd_soc_dapm_widget template;

                        if (snd_soc_volsw_is_stereo(mc))
                                dev_warn(widget->dapm->dev,
                                         "ASoC: Unsupported stereo autodisable control '%s'\n",
                                         ctrl_name);

                        name = kasprintf(GFP_KERNEL, "%s %s", ctrl_name,
                                         "Autodisable");
                        if (!name) {
                                ret = -ENOMEM;
                                goto err_data;
                        }

                        memset(&template, 0, sizeof(template));
                        template.reg = mc->reg;
                        template.mask = (1 << fls(mc->max)) - 1;
                        template.shift = mc->shift;
                        if (mc->invert)
                                template.off_val = mc->max;
                        else
                                template.off_val = 0;
                        template.on_val = template.off_val;
                        template.id = snd_soc_dapm_kcontrol;
                        template.name = name;

                        data->value = template.on_val;

                        data->widget =
                                snd_soc_dapm_new_control_unlocked(widget->dapm, // 创建一个新的widget,根据template->id标识的不同,初始化widget部分成员
                                &template);
                        kfree(name);
                        if (IS_ERR(data->widget)) {
                                ret = PTR_ERR(data->widget);
                                goto err_data;
                        }
                }
                break;
        case snd_soc_dapm_demux:
        case snd_soc_dapm_mux:
                e = (struct soc_enum *)kcontrol->private_value;

                if (e->autodisable) {
                        struct snd_soc_dapm_widget template;

                        name = kasprintf(GFP_KERNEL, "%s %s", ctrl_name,
                                         "Autodisable");
                        if (!name) {
                                ret = -ENOMEM;
                                goto err_data;
                        }

                        memset(&template, 0, sizeof(template));
                        template.reg = e->reg;
                        template.mask = e->mask;
                        template.shift = e->shift_l;
                        template.off_val = snd_soc_enum_item_to_val(e, 0);
                        template.on_val = template.off_val;
                        template.id = snd_soc_dapm_kcontrol;
                        template.name = name;

                        data->value = template.on_val;

                        data->widget = snd_soc_dapm_new_control_unlocked(
                                                widget->dapm, &template);
                        kfree(name);
                        if (IS_ERR(data->widget)) {
                                ret = PTR_ERR(data->widget);
                                goto err_data;
                        }

                        snd_soc_dapm_add_path(widget->dapm, data->widget,
                                              widget, NULL, NULL);
                } else if (e->reg != SND_SOC_NOPM) {
                        data->value = soc_dapm_read(widget->dapm, e->reg) &
                                      (e->mask << e->shift_l);
                }
                break;
        default:
                break;
        }

        kcontrol->private_data = data;

        return 0;

err_data:
        kfree(data);
        return ret;
}
View Code
dapm_kcontrol_data_alloc函数除了动态分配struct dapm_kcontrol_data之外,如果使用SOC_DAPM_SINGLE_AUTODISABLE定义的kcontrolm模板,即kcontrol的autodisable字段被设置的情况下,还会创建一个与该kcontrol所对应的影子widget,该影子widget的类型是:snd_soc_dapm_kcontrol并被保存到dapm_kcontrol_data的widget成员中;
struct dapm_kcontrol_data {  // 保存当前kcontrol的私有数据
        unsigned int value;
        struct snd_soc_dapm_widget *widget;      // 用于保存创建的影子widget
        struct list_head paths;                  // 保存与当前kcontrol输入端连接的path的链表,每个成员都是struct snd_soc_dapm_path 
        struct snd_soc_dapm_widget_list *wlist;  // 数组,长度默认为1,用于保存当前当前kcontrol所属的widget
};

(c) 接着把当前path加入到kcontrol->private_data的paths链表中;

static void dapm_kcontrol_add_path(const struct snd_kcontrol *kcontrol,
        struct snd_soc_dapm_path *path)
{
        struct dapm_kcontrol_data *data = snd_kcontrol_chip(kcontrol); // kcontrol->private_data

        list_add_tail(&path->list_kcontrol, &data->paths);  // 将当前path添加到data的paths链表中
}

(d) 如果存在影子widget,通过dapm_kcontrol_add_path函数将该影子widget和path输入端的widget连接在一起,因为影子widget使用了先创建的kcontrol本身的reg/shift等寄存器信息,所以实际上控制的是该kcontrol的开和关。

4.3.2 dapm_new_mixer

这里我们以snd_soc_dapm_mux类型的widget为例来说,说一说dapm_new_mux函数;

/* create new dapm mux control */
static int dapm_new_mux(struct snd_soc_dapm_widget *w)
{
        struct snd_soc_dapm_context *dapm = w->dapm;      // dapm域
        enum snd_soc_dapm_direction dir;
        struct snd_soc_dapm_path *path;
        const char *type;
        int ret;

        switch (w->id) {    // 根据widget的类型,执行不同的流程
        case snd_soc_dapm_mux:
                dir = SND_SOC_DAPM_DIR_OUT;    // 1
                type = "mux";
                break;
        case snd_soc_dapm_demux:
                dir = SND_SOC_DAPM_DIR_IN;    // 0
                type = "demux";
                break;
        default:
                return -EINVAL;
        }

        if (w->num_kcontrols != 1) {  // 对于Mux,必须要有一个kcontrol
                dev_err(dapm->dev,
                        "ASoC: %s %s has incorrect number of controls\n", type,
                        w->name);
                return -EINVAL;
        }

        if (list_empty(&w->edges[dir])) {   // 输入端/输出端连接的path不能为空
                dev_err(dapm->dev, "ASoC: %s %s has no paths\n", type, w->name);
                return -EINVAL;
        }

        ret = dapm_create_or_share_kcontrol(w, 0);  // 创建kcontrol,并使用kcontrol模板进行初始化
        if (ret < 0)
                return ret;

        snd_soc_dapm_widget_for_each_path(w, dir, path) {  // 遍历w->edges[dir]链表,赋值给path
                if (path->name)
                        dapm_kcontrol_add_path(w->kcontrols[0], path);
        }

        return 0;
}

(1) 对于Mux类型的widget,因为只会有一个kcontrol,所以在这里做一下判断;实际上Mux可以细分为两类snd_soc_dapm_mux和snd_soc_dapm_demux,如下图所示

(2) 同样地,和Mixer类型一样,也使用dapm_create_or_share_kcontrol来创建这个kcontrol;如果kcontrol的autodisable字段被设置的情况下,还会创建一个虚拟的影子widget,保存到dapm_kcontrol_data的widget成员中;

dapm_create_or_share_kcontrol(w,0)创建kcontrol:其内部实际上以kcontrol模板为参数,调用snd_soc_cnew函数创建kcontrol,同时初始化kcontrol->private_data(指向一个struct dapm_kcontrol_data,由dapm_kcontrol_data_alloc函数创建),然后调用snd_ctl_add将kcontrol添加到声卡card的controls链表中,最后保存新创建的kcontrol搭配w->kcontrols[0]中;

(3) 将widget输入端所连接的path都加入dapm_kcontrol_data结构的paths链表中。

4.3.3 dapm_new_pga

这里我们以snd_soc_dapm_pga类型的widget为例来说,说一说dapm_new_mux函数;

/* create new dapm volume control */
static int dapm_new_pga(struct snd_soc_dapm_widget *w)
{
        int i;

        for (i = 0; i < w->num_kcontrols; i++) {    // 创建每一个kcontrol
                int ret = dapm_create_or_share_kcontrol(w, i);
                if (ret < 0)
                        return ret;
        }

        return 0;
}

可以看到这里很简单,仅仅是根据kcontrol的数量做循环,调用dapm_create_or_share_kcontrol逐个建立对应的kcontrol。

4.3.4 dapm_new_dai_link

这里我们以snd_soc_dapm_dai_link类型的widget为例来说,说一说dapm_new_dai_link函数;

/* create new dapm dai link control */
static int dapm_new_dai_link(struct snd_soc_dapm_widget *w)
{
        int i;
        struct snd_soc_pcm_runtime *rtd = w->priv;

        /* create control for links with > 1 config */
        if (rtd->dai_link->num_params <= 1)
                return 0;

        /* add kcontrol */ 
        for (i = 0; i < w->num_kcontrols; i++) {    // 创建每一个kcontrol
                struct snd_soc_dapm_context *dapm = w->dapm;
                struct snd_card *card = dapm->card->snd_card;
                struct snd_kcontrol *kcontrol = snd_soc_cnew(&w->kcontrol_news[i],  // 创建snd_kcontrol实例,并把w->kcontrol_news[i]模板中相应的值复制到该kcontrol
                                                             w, w->name, NULL);
                int ret = snd_ctl_add(card, kcontrol);  //  将kcontrol添加到声卡card的controls链表中

                if (ret < 0) {
                        dev_err(dapm->dev,
                                "ASoC: failed to add widget %s dapm kcontrol %s: %d\n",
                                w->name, w->kcontrol_news[i].name, ret);
                        return ret;
                }
                kcontrol->private_data = w;  // 初始化kcontrol->private_data为当前widget
                w->kcontrols[i] = kcontrol;  // 保存新创建的kcontrol搭配w->kcontrols[i]中
        }

        return 0;
}

可以看到这里很简单,仅仅是根据kcontrol的数量做循环:

  • 调用snd_soc_cnew函数创建snd_kcontrol实例,并把w->kcontrol_news[i]模板中相应的值复制到该kcontrol;
  • 调用snd_ctl_add将kcontrol添加到声卡card的controls链表中;
  • 同时初始化kcontrol->private_data(指向当前widget)
  • 最后保存新创建的kcontrol搭配w->kcontrols[i]中。
4.3.5 dapm_debugfs_add_widget

dapm_debugfs_add_widget函数用于在debugfs创建以w->name命名的文件,函数定义在sound/soc/soc-dapm.c;

static void dapm_debugfs_add_widget(struct snd_soc_dapm_widget *w)
{
        struct snd_soc_dapm_context *dapm = w->dapm;

        if (!dapm->debugfs_dapm || !w->name)
                return;

        debugfs_create_file(w->name, 0444, dapm->debugfs_dapm, w,
                            &dapm_widget_power_fops);
}

其中:

  • 文件名为w->name;

  • 0444描述了该文件应该具有的访问权限;

  • dapm->debugfs_dapm表示应该保存该文件的目录,比如/sys/kernel/debug/asoc/realtek,rt5651-codec/rt5651.1-001a/dapm、/sys/kernel/debug/asoc/realtek,rt5651-codec/ff880000.i2s/dapm等;

  • w将被保存在产生的inode结构的i_private字段中;

  • dapm_widget_power_fops是一组实现该文件行为的文件操作函数。

dapm_widget_power_fops定义如下:

static const struct file_operations dapm_widget_power_fops = {
        .open = simple_open,
        .read = dapm_widget_power_read_file,
        .llseek = default_llseek,
};

其中read方法定义如下:

static ssize_t dapm_widget_power_read_file(struct file *file,
                                           char __user *user_buf,
                                           size_t count, loff_t *ppos)
{
        struct snd_soc_dapm_widget *w = file->private_data;
        struct snd_soc_card *card = w->dapm->card;
        enum snd_soc_dapm_direction dir, rdir;
        char *buf;
        int in, out;
        ssize_t ret;
        struct snd_soc_dapm_path *p = NULL;

        buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
        if (!buf)
                return -ENOMEM;

        mutex_lock(&card->dapm_mutex);

        /* Supply widgets are not handled by is_connected_{input,output}_ep() */
        if (w->is_supply) {  // 该widget是供电类型的组件
                in = 0;
                out = 0;
        } else {
                in = is_connected_input_ep(w, NULL, NULL);  // 从当前wiget向前遍历(包含当前节点),返回连接至SND_SOC_DAPM_EP_SOURCE类型端点路径数量
                out = is_connected_output_ep(w, NULL, NULL);  // 从当前widget向后遍历(包含当前节点),返回连接至SND_SOC_DAPM_EP_SINK类型端点的路径数量
        }

        ret = scnprintf(buf, PAGE_SIZE, "%s: %s%s  in %d out %d", // 输出类似:AIF1 Playback: Off  in 0 out 10
                       w->name, w->power ? "On" : "Off",
                       w->force ? " (forced)" : "", in, out);

        if (w->reg >= 0)  // 如果wiget配置了寄存器地址,表示可以通过寄存器控制widget的电源状态
                ret += scnprintf(buf + ret, PAGE_SIZE - ret,
                                " - R%d(0x%x) mask 0x%x",
                                w->reg, w->reg, w->mask << w->shift);

        ret += scnprintf(buf + ret, PAGE_SIZE - ret, "\n");

        if (w->sname)  // 指定了sname,即针对stream widget,输入类似:stream AIF1 Playback inactive
                ret += scnprintf(buf + ret, PAGE_SIZE - ret, " stream %s %s\n",
                                w->sname,
                                w->active ? "active" : "inactive");

        snd_soc_dapm_for_each_direction(dir) {    // 0 输入端、1 输出端,  假设dir=1,即输出
                rdir = SND_SOC_DAPM_DIR_REVERSE(dir);    // 方向反转 比如1->0
                snd_soc_dapm_widget_for_each_path(w, dir, p) {   // 遍历以widget作为输入端的path,比如:widget -- path -- widget1
                        if (p->connected && !p->connected(p->source, p->sink))  // 检查path的当前连接状态如果是断开,进入
                                continue;

                        if (!p->connect)   // 路径断开,进入
                                continue;

                        ret += scnprintf(buf + ret, PAGE_SIZE - ret,
                                        " %s  \"%s\" \"%s\"\n",
                                        (rdir == SND_SOC_DAPM_DIR_IN) ? "in" : "out",
                                        p->name ? p->name : "static",
                                        p->node[rdir]->name);
                }
        }
        mutex_unlock(&card->dapm_mutex);

        ret = simple_read_from_buffer(user_buf, count, ppos, buf, ret);

        kfree(buf);
        return ret;
}

比如,如果NanoPC-T4开发板在音频播放的时候查看:

root@rk3399:/# cat /sys/kernel/debug/asoc/realtek,rt5651-codec/ff880000.i2s/dapm/Playback
Playback: On  in 1 out 10    
 stream Playback active      
 out  "static" "AIF1 Playback"

其中Playback是snd_soc_dapm_dai_in类型widget,其sname设置为Playback,这里输出的每行内容的含义如下:

  • widget名称:电源状态(即widget->power) in    从当前widget向前遍历连接至SND_SOC_DAPM_EP_SOURCE类型端点路径数量   out   从当前widget向后遍历连接至SND_SOC_DAPM_EP_SINK类型端点的路径数量;

  • stream widget->sname 激活状态(即widget->active);

  • 与widget相连的widget属于哪一端(输入端?输出端) 路径名称(未指定,则为static) 相连的widget的名称;

需要注意的是路径Playback --> AIF1 Playback上的这两个widget、以及path都要处于连接状态(即widget->connected=1,path->connect=1);

4.3.6 dapm_power_widgets

dapm_power_widget函数触发整个音频路径的扫描过程,检查链表中dapm_dirty每个widget,对其进行上电/下电操作。函数定义在sound/soc/soc-dapm.c:

/*
 * 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;
        
        // 1. 初始化两个链表up_list和down_list,up_list指向要power up的widgets,down_list指向要power down的widgets  
        LIST_HEAD(up_list);      // 上电链表
        LIST_HEAD(down_list);    // 下电链表
        ASYNC_DOMAIN_EXCLUSIVE(async_domain);
        enum snd_soc_bias_level bias;
        int ret;

        lockdep_assert_held(&card->dapm_mutex);

        trace_snd_soc_dapm_start(card);
        // 遍历声dapm_list链表中的dapm域
        for_each_card_dapms(card, d) {
                if (dapm_idle_bias_off(d))  // d->idle_bias_off
                        d->target_bias_level = SND_SOC_BIAS_OFF;
                else
                        d->target_bias_level = SND_SOC_BIAS_STANDBY;
        }

     // 2. 遍历所有widgets,将声卡中的每一个widget的power_checked设置为false,表明需要重新检查电源状态
        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.
         */
        // 3. 遍历声卡card的dapm_dirty链表中的widget,检查是否需要对widget进行power操作;要power up则把该widget插入到up_list要power down'则插入到down_list;
        list_for_each_entry(w, &card->dapm_dirty, dirty) {
                dapm_power_one_widget(w, &up_list, &down_list);
        }
        // 遍历声卡card的widgets链表中的widget,这里针对一些特殊类型的widget进行一些处理;忽略这块
        for_each_card_widgets(card, w) {
                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_pinctrl:
                        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;
        for_each_card_dapms(card, d)
                if (d->target_bias_level > bias)
                        bias = d->target_bias_level;
        for_each_card_dapms(card, d)
                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 */
        for_each_card_dapms(card, d) {   // 遍历声卡的dapm链表, 里面存放的就是card、codec、platfrom的dapm域
                if (d != &card->dapm && d->bias_level != d->target_bias_level)
                        async_schedule_domain(dapm_pre_sequence_async, d, // 4. 设置当前dapm域的偏置电压
                                                &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);   // 5.1 先执行down_list
      // 仅仅只有配置widget kcontrols触发DAPM机制时才会调用此函数。此函数流程如下:
      // 1. 遍历kcontrol相关的所有widget,调用每个widget的event()回调函数,发送SND_SOC_DAPM_PRE_REG事件。
      // 2. 更新kcontrol寄存器值,设置音频路径。
      // 3. 遍历kcontrol相关的所有widget,调用每个widget的event()回调函数,发送SND_SOC_DAPM_POST_REG事件。
        dapm_widget_update(card);

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

        /* Run all the bias changes in parallel */
        for_each_card_dapms(card, d) {
                if (d != &card->dapm && d->bias_level != d->target_bias_level)
                        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 */
        for_each_card_dapms(card, d) {
                if (!d->component)
                        continue;

                ret = snd_soc_component_stream_event(d->component, event);
                if (ret < 0)
                        return ret;
        }

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

(1) 初始化两个链表up_list和down_list,up_list指向要power up的widget,down_list指向要power down的widget;

(2) 调用dapm_reset将声卡中的每一个widget的power_checked设置为false,表明需要重新检查电源状态;

static void dapm_reset(struct snd_soc_card *card)
{
        struct snd_soc_dapm_widget *w;

        lockdep_assert_held(&card->dapm_mutex);
     // 把card的card->dapm_stats置0
        memset(&card->dapm_stats, 0, sizeof(card->dapm_stats));
        // 遍历声卡card的widgets链表,把每一个widget的power_checked设置为未检查状态
        for_each_card_widgets(card, w) {
                w->new_power = w->power;
                w->power_checked = false;
        }
}

(3) 遍历声卡card的dapm_dirty链表中的widget,调用dapm_power_one_widget检查是否需要对widget进行power操作;要power up则把该widget插入到up_list,要power down则插入到down_list;dapm_power_one_widget函数其实比较有意思,它会检查与w邻近的widget的电源状态,如果也需要对其进行power操作,将会将相邻的widget添加到dapm_dirty链表;

这样总结下来就是在遍历dapm_dirty链表中widget的同时,又在向dapm_dirty链表链表中添加元素。为了更加生动描述这个过程,我们举个例子,音频链路如下图所示,图中有两条complete path,使用红色箭头标记,并且图中每个widget电源状态都处于下电状态,每个widget以及path都处于连接状态:

此时我们调用dapm_power_one_widget(widget B,up_list,dowdn_list);那么在执行该函数的过程中:

  1. 判断widget B是否处于complete path上,由于widget B处于complete path上,因此需要对widget B进行上电操作;
  2. 首先会查找widget B的相邻widget,也就是widget A、widget D、widget F;
  3. 比较widget A、widget D、widget F的电源状态是否是出于上电状态,由于都不是处于上电状态;
  4. 将widget A、widget D、widget F添加到dapm_dirty链表;
  5. 将widget B按照一定顺序添加到up_list链表中;
  6. 遍历dapm_dirty链表下一个元素,也就是widget A,同样执行dapm_power_one_widget方法;即跳转到第1步执行;
  7. .....
  8. 执行完之后,up_list链表上会有widget A、widget B、widget C、widget D;因此我们可以看出来处于complete path的widget都会被上电;

函数代码如下:

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

        switch (w->id) {
        case snd_soc_dapm_pre: // 特殊的widget(第一个执行)
                power = 0;     // 期望电源状态为下电
                goto end;
        case snd_soc_dapm_post: // 特殊的widget(最后一个执行)
                power = 1;      // 期望电源状态为上电
                goto end;
        default:
                break;
        }

        // 调用自身的power_check来获取widget期望的电源状态  判断一个widget是否上电或下电基于这个widget是否在完整音频路径上
        power = dapm_widget_power_check(w);

        //如果w当前电源状态和需要设置的电源状态power一致,那么显然不用重复设置widget的电源状态了
        if (w->power == power)
                return;

        trace_snd_soc_dapm_widget_power(w, power);

        /*
         * If we changed our power state perhaps our neigbours
         * changed also.
         */
         // 遍历w->edges[1]链表(source--path--w,保存着所有和w的输入端连接的snd_soc_dapm_path结构),赋值给path
        snd_soc_dapm_widget_for_each_source_path(w, path)  
                // 如果path处于连接状态,并且与w相连的widget的电源状态不等于power,则将该widget添加到dapm_dirty链表尾部
                dapm_widget_set_peer_power(path->source, power, path->connect);

        /*
         * Supplies can't affect their outputs, only their inputs
         */
        if (!w->is_supply)
                 // 遍历w->edges[0]链表(w--path--snk,保存着所有和w的输出端连接的snd_soc_dapm_path结构),赋值给path
                snd_soc_dapm_widget_for_each_sink_path(w, path)
                        // 如果path处于连接状态,并且与w相连的widget的电源状态不等于power,则将该widget添加到dapm_dirty链表尾部
                        dapm_widget_set_peer_power(path->sink, power, path->connect);

end:
        if (power)  // 上电
                // 会按照widget上电顺序将w插入到up_list链表中
                dapm_seq_insert(w, up_list, true);
        else    // 下电
                // 会按照widget下电顺序将w插入到down_list链表中 
                dapm_seq_insert(w, down_list, false);
}

内部调用了dapm_widget_power_check函数,如下:

static int dapm_widget_power_check(struct snd_soc_dapm_widget *w)
{
        if (w->power_checked) // 如果电源状态已经检测过了,就返回
                return w->new_power;

        if (w->force)         // 如果设置了强制更新电源状态,返回
                w->new_power = 1;
        else                  // 如果没有就调用power_check函数,这个在初始化的会根据不同的widget,设置不同的回调函数
                w->new_power = w->power_check(w);  // 检查电源状态

        w->power_checked = true;  // 设置电源检查标志位

        return w->new_power;
}
View Code

内部调用了dapm_seq_insert函数,如下:

// 比较widget a和widget b的顺序,如果返回负数表示a在前,如果返回正数表示b在后
static int dapm_seq_compare(struct snd_soc_dapm_widget *a, // widget a
                            struct snd_soc_dapm_widget *b, // widget b
                            bool power_up)      // 上电/下电
{
        int *sort;

        BUILD_BUG_ON(ARRAY_SIZE(dapm_up_seq) != SND_SOC_DAPM_TYPE_COUNT);
        BUILD_BUG_ON(ARRAY_SIZE(dapm_down_seq) != SND_SOC_DAPM_TYPE_COUNT);

        if (power_up)  // 上电
                sort = dapm_up_seq;    // widget上电顺序列表
        else          // 下电
                sort = dapm_down_seq;  // widget下电顺序列表

        WARN_ONCE(sort[a->id] == 0, "offset a->id %d not initialized\n", a->id);
        WARN_ONCE(sort[b->id] == 0, "offset b->id %d not initialized\n", b->id);

        if (sort[a->id] != sort[b->id])  // 如果他们在列表中顺序不一样
                return sort[a->id] - sort[b->id];
        if (a->subseq != b->subseq) {   // 如果a->id和b->id一样的话,判断子顺序
                if (power_up)   // 如果是上电,值小的widget排在前面
                        return a->subseq - b->subseq;
                else            // 如果是下电,值大的widget排至前面
                        return b->subseq - a->subseq;
        }
        if (a->reg != b->reg)  // 如果前面都相等,比较reg 
                return a->reg - b->reg;
        if (a->dapm != b->dapm) // 不同的dapm域
                return (unsigned long)a->dapm - (unsigned long)b->dapm;

        return 0;
}


/* Insert a widget in order into a DAPM power sequence. */
static void dapm_seq_insert(struct snd_soc_dapm_widget *new_widget, // 待插入的new_widget,会按照widget上电/下电顺序将new_widget插入到list链表中
                            struct list_head *list,  // 列表中的widget按顺序排放的
                            bool power_up)
{
        struct snd_soc_dapm_widget *w;

        list_for_each_entry(w, list, power_list) // 遍历list链表, 赋值给w
                if (dapm_seq_compare(new_widget, w, power_up) < 0) { //如果new_widget在前,w在后
                        list_add_tail(&new_widget->power_list, &w->power_list); // 该操作执行之后:new_widget会被添加到连败list w节点的前面
                        return;
                }

        list_add_tail(&new_widget->power_list, list);  // 把new_widget添加到list的尾部
}
View Code

dapm_up_seq定义了widget的上电顺序,widget就是按照这个0开始的顺序是上电的;

/* dapm power sequences - make this per codec in the future */
static int dapm_up_seq[] = {
        [snd_soc_dapm_pre] = 1,
        [snd_soc_dapm_regulator_supply] = 2,
        [snd_soc_dapm_pinctrl] = 2,
        [snd_soc_dapm_clock_supply] = 2,
        [snd_soc_dapm_supply] = 3,
        [snd_soc_dapm_dai_link] = 3,
        [snd_soc_dapm_micbias] = 4,
        [snd_soc_dapm_vmid] = 4,
        [snd_soc_dapm_dai_in] = 5,
        [snd_soc_dapm_dai_out] = 5,
        [snd_soc_dapm_aif_in] = 5,
        [snd_soc_dapm_aif_out] = 5,
        [snd_soc_dapm_mic] = 6,
        [snd_soc_dapm_siggen] = 6,
        [snd_soc_dapm_input] = 6,
        [snd_soc_dapm_output] = 6,
        [snd_soc_dapm_mux] = 7,
        [snd_soc_dapm_demux] = 7,
        [snd_soc_dapm_dac] = 8,
        [snd_soc_dapm_switch] = 9,
        [snd_soc_dapm_mixer] = 9,
        [snd_soc_dapm_mixer_named_ctl] = 9,
        [snd_soc_dapm_pga] = 10,
        [snd_soc_dapm_buffer] = 10,
        [snd_soc_dapm_scheduler] = 10,
        [snd_soc_dapm_effect] = 10,
        [snd_soc_dapm_src] = 10,
        [snd_soc_dapm_asrc] = 10,
        [snd_soc_dapm_encoder] = 10,
        [snd_soc_dapm_decoder] = 10,
        [snd_soc_dapm_adc] = 11,
        [snd_soc_dapm_out_drv] = 12,
        [snd_soc_dapm_hp] = 12,
        [snd_soc_dapm_spk] = 12,
        [snd_soc_dapm_line] = 12,
        [snd_soc_dapm_sink] = 12,
        [snd_soc_dapm_kcontrol] = 13,
        [snd_soc_dapm_post] = 14,
};
View Code

dapm_down_seq定义了widget的下电顺序,widget就是按照这个0开始的顺序是下电的;

static int dapm_down_seq[] = {
        [snd_soc_dapm_pre] = 1,
        [snd_soc_dapm_kcontrol] = 2,
        [snd_soc_dapm_adc] = 3,
        [snd_soc_dapm_hp] = 4,
        [snd_soc_dapm_spk] = 4,
        [snd_soc_dapm_line] = 4,
        [snd_soc_dapm_out_drv] = 4,
        [snd_soc_dapm_sink] = 4,
        [snd_soc_dapm_pga] = 5,
        [snd_soc_dapm_buffer] = 5,
        [snd_soc_dapm_scheduler] = 5,
        [snd_soc_dapm_effect] = 5,
        [snd_soc_dapm_src] = 5,
        [snd_soc_dapm_asrc] = 5,
        [snd_soc_dapm_encoder] = 5,
        [snd_soc_dapm_decoder] = 5,
        [snd_soc_dapm_switch] = 6,
        [snd_soc_dapm_mixer_named_ctl] = 6,
        [snd_soc_dapm_mixer] = 6,
        [snd_soc_dapm_dac] = 7,
        [snd_soc_dapm_mic] = 8,
        [snd_soc_dapm_siggen] = 8,
        [snd_soc_dapm_input] = 8,
        [snd_soc_dapm_output] = 8,
        [snd_soc_dapm_micbias] = 9,
        [snd_soc_dapm_vmid] = 9,
        [snd_soc_dapm_mux] = 10,
        [snd_soc_dapm_demux] = 10,
        [snd_soc_dapm_aif_in] = 11,
        [snd_soc_dapm_aif_out] = 11,
        [snd_soc_dapm_dai_in] = 11,
        [snd_soc_dapm_dai_out] = 11,
        [snd_soc_dapm_dai_link] = 12,
        [snd_soc_dapm_supply] = 13,
        [snd_soc_dapm_clock_supply] = 14,
        [snd_soc_dapm_pinctrl] = 14,
        [snd_soc_dapm_regulator_supply] = 14,
        [snd_soc_dapm_post] = 15,
};
View Code

(4) 调用dapm_pre_sequence_async设置dapm域的偏置电压;

/* Async callback run prior to DAPM sequences - brings to _PREPARE if
 * they're changing state.
 */
static void dapm_pre_sequence_async(void *data, async_cookie_t cookie)
{
        struct snd_soc_dapm_context *d = data;
        int ret;

        /* If we're off and we're not supposed to go into STANDBY */
        if (d->bias_level == SND_SOC_BIAS_OFF &&
            d->target_bias_level != SND_SOC_BIAS_OFF) {
                if (d->dev && cookie)
                        pm_runtime_get_sync(d->dev);

                ret = snd_soc_dapm_set_bias_level(d, SND_SOC_BIAS_STANDBY);  // 设置当前dapm域的偏置电压
                if (ret != 0)
                        dev_err(d->dev,
                                "ASoC: Failed to turn on bias: %d\n", ret);
        }

        /* Prepare for a transition to ON or away from ON */
        if ((d->target_bias_level == SND_SOC_BIAS_ON &&
             d->bias_level != SND_SOC_BIAS_ON) ||
            (d->target_bias_level != SND_SOC_BIAS_ON &&
             d->bias_level == SND_SOC_BIAS_ON)) {
                ret = snd_soc_dapm_set_bias_level(d, SND_SOC_BIAS_PREPARE);
                if (ret != 0)
                        dev_err(d->dev,
                                "ASoC: Failed to prepare bias: %d\n", ret);
        }
}

snd_soc_dapm_set_bias_level函数内部实际上就是调用声卡的set_bias_level或者dapm域所属的component 驱动中的set_bias_level方法;

/**
 * snd_soc_dapm_set_bias_level - set the bias level for the system
 * @dapm: DAPM context
 * @level: level to configure
 *
 * Configure the bias (power) levels for the SoC audio device.
 *
 * Returns 0 for success else error.
 */
static int snd_soc_dapm_set_bias_level(struct snd_soc_dapm_context *dapm,
                                       enum snd_soc_bias_level level)
{
        struct snd_soc_card *card = dapm->card;
        int ret = 0;

        trace_snd_soc_bias_level_start(card, level);

     // 如果设置了card->set_bias_level,执行 card->set_bias_level(card, dapm, level);
        ret = snd_soc_card_set_bias_level(card, dapm, level);
        if (ret != 0)
                goto out;

        if (!card || dapm != &card->dapm)   // 如果不是card dapm域、即属于codec或者pfatform的dapm域
                // 如果设置了dapm->component->driver->set_bias_level,执行dapm->component->driver->set_bias_level(component, level)
                ret = snd_soc_dapm_force_bias_level(dapm, level);

        if (ret != 0)
                goto out;
       // 如果设置了card->set_bias_level,执行 card->set_bias_level(card, dapm, level);
        ret = snd_soc_card_set_bias_level_post(card, dapm, level);
out:
        trace_snd_soc_bias_level_done(card, level);

        return ret;
}
View Code

(5) 先power down down_list上widget,再power up up_list上的widget;无论是哪一种power操作,都是通过调用dapm_seq_run函数实现的;

/* 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)  // 假设list传入的是down_list,则power_up=false
{
        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 i;
        int *sort;

        if (power_up)  // 上电
                sort = dapm_up_seq;  // widget上电顺序列表
        else
                sort = dapm_down_seq;  // widget下电顺序列表

        list_for_each_entry_safe(w, n, list, power_list) { // 遍历list列表中每一个widget,赋值给w
                int ret = 0;

                /* Do we need to apply any queued changes? */
                if (sort[w->id] != cur_sort || w->reg != cur_reg ||   // 顺序不是-1,并且该widget可以通过寄存器配置进行电源管理
                    w->dapm != cur_dapm || w->subseq != cur_subseq) {
                        if (!list_empty(&pending))
                                dapm_seq_run_coalesced(card, &pending);

                        if (cur_dapm && cur_dapm->component) {
                                for (i = 0; i < ARRAY_SIZE(dapm_up_seq); i++)
                                        if (sort[i] == cur_sort)
                                                snd_soc_component_seq_notifier(
                                                        cur_dapm->component,
                                                        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)
                                continue;

                        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)
                                continue;

                        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->component) {
                for (i = 0; i < ARRAY_SIZE(dapm_up_seq); i++)
                        if (sort[i] == cur_sort)
                                snd_soc_component_seq_notifier(
                                        cur_dapm->component,
                                        i, cur_subseq);
        }

        for_each_card_dapms(card, d)
                soc_dapm_async_complete(d);
}

该函数它遍历之前已排序好(dapm_seq_insert)的链表,把相同优先级、且相同widget register的、相同dapm域、相同power sequence的widgets添加到pending链表上,然后调用dapm_seq_run_coalesced对该链表上的widgets进行设置。这样做的目的是尽可能减少对widget registers的读写。

/* Apply the coalesced changes from a DAPM sequence */
static void dapm_seq_run_coalesced(struct snd_soc_card *card,
                                   struct list_head *pending)
{
        struct snd_soc_dapm_context *dapm;
        struct snd_soc_dapm_widget *w;
        int reg;
        unsigned int value = 0;
        unsigned int mask = 0;

        w = list_first_entry(pending, struct snd_soc_dapm_widget, power_list);
        reg = w->reg;
        dapm = w->dapm;

        list_for_each_entry(w, pending, power_list) {   // 遍历pending链表中的widget,赋值给w
                WARN_ON(reg != w->reg || dapm != w->dapm);
                w->power = w->new_power;

                mask |= w->mask << w->shift;
                if (w->power)
                        value |= w->on_val << w->shift;
                else
                        value |= w->off_val << w->shift;

                pop_dbg(dapm->dev, card->pop_time,
                        "pop test : Queue %s: reg=0x%x, 0x%x/0x%x\n",
                        w->name, reg, value, mask);

                /* Check for events */
                dapm_seq_check_event(card, w, SND_SOC_DAPM_PRE_PMU);
                dapm_seq_check_event(card, w, SND_SOC_DAPM_PRE_PMD);
        }

        if (reg >= 0) {
                /* Any widget will do, they should all be updating the
                 * same register.
                 */

                pop_dbg(dapm->dev, card->pop_time,
                        "pop test : Applying 0x%x/0x%x to %x in %dms\n",
                        value, mask, reg, card->pop_time);
                pop_wait(card->pop_time);
                soc_dapm_update_bits(dapm, reg, mask, value);  // 硬件寄存器操作,更新指定寄存器reg、指定位mask的值
        }

        list_for_each_entry(w, pending, power_list) {
                dapm_seq_check_event(card, w, SND_SOC_DAPM_POST_PMU);
                dapm_seq_check_event(card, w, SND_SOC_DAPM_POST_PMD);
        }
}

4.4 dapm_kcontrol_add_path

dapm_kcontrol_add_path函数用于将path节点添加到data->paths链表的尾部;

static void dapm_kcontrol_add_path(const struct snd_kcontrol *kcontrol,
        struct snd_soc_dapm_path *path)
{
        struct dapm_kcontrol_data *data = snd_kcontrol_chip(kcontrol);  // kcontrol->private_data

        list_add_tail(&path->list_kcontrol, &data->paths);
}

4.5 snd_soc_dapm_add_path

snd_soc_dapm_add_path函数根据参数创建一个snd_soc_dapm_path,并将其添加到声卡card的paths链表中;函数参数分别为dapm域、输入端widget、输出端widget、名称、以及connected回调函数.

函数定义在sound/soc/soc-dapm.c;

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,  // 对于静态链接,该参数为NULL
        int (*connected)(struct snd_soc_dapm_widget *source,
                         struct snd_soc_dapm_widget *sink))
{
        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);  // 检查一条path是否是动态连接    
                                                      // 即源widget和目的widget之间构成的path包含kcontrol,可以用来开关通断、音量增大减小等
        if (ret)
                return ret;

        path = kzalloc(sizeof(struct snd_soc_dapm_path), GFP_KERNEL);   // 动态分配一个struct snd_soc_dapm_path
        if (!path)
                return -ENOMEM;

        path->node[SND_SOC_DAPM_DIR_IN] = wsource;    // 0 设置输入端widget 即path->source
        path->node[SND_SOC_DAPM_DIR_OUT] = wsink;     // 1 设置输出端widget 即path->sink

        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;   // 静态连接 源widget和目的widget之间构成的path是静态连接,可以理解为直连,中间没有任何kcontrol
        } else {
                switch (wsource->id) {      // 输入端widget类型
                case snd_soc_dapm_demux:
                        ret = dapm_connect_mux(dapm, path, control, wsource);
                        if (ret)
                                goto err;
                        break;
                default:
                        break;
                }

                switch (wsink->id) {     // 输出端widget类型
                case snd_soc_dapm_mux:
              //多路选择,且只有一个kcontrol,选择不同,路径不同,路径名也不同,路径名在kcontrol的枚举中
                        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:
               // 混音器:如果路径的kcontrol名在路径path的目的widget的kcontrol里有对应存在,
               // 那么读取该kcontrol配置的寄存器值,如果非零path的连接状态设置为连接
                        ret = dapm_connect_mixer(dapm, path, control);
                        if (ret != 0)
                                goto err;
                        break;
                default:
                        break;
                }
        }

        list_add(&path->list, &dapm->card->paths);  // 添加当前节点到声卡card的paths链表中

        snd_soc_dapm_for_each_direction(dir)   // 0 输入端、1 输出端
                list_add(&path->list_node[dir], &path->node[dir]->edges[dir]);  // 通过链表构建path和widget之间的关系

        snd_soc_dapm_for_each_direction(dir) {  // 0 输入端 1 输出端
                //判断某个路径上的源widget和目的widget是否是源端点和目的端点,如果是更新widget的is_ep属性
                dapm_update_widget_flags(path->node[dir]);  
                dapm_mark_dirty(path->node[dir], "Route added");  // 判断widget的dirty链表是否为空,如果为空,
                                                             // 添加path->node[dir]到声卡card的dapm_dirty链表
        }

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

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

snd_soc_dapm_check_dynamic_path,检查一条path是否是动态连接。

static int snd_soc_dapm_check_dynamic_path(struct snd_soc_dapm_context *dapm,
        struct snd_soc_dapm_widget *source, struct snd_soc_dapm_widget *sink,
        const char *control)
{
        bool dynamic_source = false;
        bool dynamic_sink = false;

        if (!control)
                return 0;

        switch (source->id) {
        case snd_soc_dapm_demux:
                dynamic_source = true;
                break;
        default:
                break;
        }

        switch (sink->id) {
        case snd_soc_dapm_mux:
        case snd_soc_dapm_switch:
        case snd_soc_dapm_mixer:
        case snd_soc_dapm_mixer_named_ctl:
                dynamic_sink = true;
                break;
        default:
                break;
        }

        if (dynamic_source && dynamic_sink) {
                dev_err(dapm->dev,
                        "Direct connection between demux and mixer/mux not supported for path %s -> [%s] -> %s\n",
                        source->name, control, sink->name);
                return -EINVAL;
        } else if (!dynamic_source && !dynamic_sink) {
                dev_err(dapm->dev,
                        "Control not supported for path %s -> [%s] -> %s\n",
                        source->name, control, sink->name);
                return -EINVAL;
        }

        return 0;
}
View Code

通过之前的介绍,我们大概已经知道混音控制(Mixer)、开关控制(Mux)都是存在多个kcontrol的,比如Mixer widget输入端通过path连接多个widget;

因此如果一条path定义了kcontrol(用于控制该连接关系),那么目的widget必须是以下类型:

  • snd_soc_dapm_mux;
  • snd_soc_dapm_switch;
  • snd_soc_dapm_mixer;
  • snd_soc_dapm_mixer_named_ctl;

或者源widget必须是以下类型:

  • snd_soc_dapm_demux;

否则报错认为定义的path是不合法,继续下一个path注册。

如果一个path没有定义kontrol,这个path默认为connect状态。

4.5.2 dapm_connect_mux

目的widget如果是Mux类型,使用用dapm_connect_mux函数完成连接工作;Mux多路选择,只有一个kcontrol,选择不同,路径不同(路径名也不同,路径名是该kconrol的其它别名)。

dapm_connect_mux函数如下:

/* connect mux widget to its interconnecting audio paths */
static int dapm_connect_mux(struct snd_soc_dapm_context *dapm,
        struct snd_soc_dapm_path *path, const char *control_name,
        struct snd_soc_dapm_widget *w)
{
        const struct snd_kcontrol_new *kcontrol = &w->kcontrol_news[0];
        struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;  // kcontrol的私有值
        unsigned int item;
        int i;

        if (e->reg != SND_SOC_NOPM) {
                unsigned int val;
                val = soc_dapm_read(dapm, e->reg);   // 获取寄存器值
                val = (val >> e->shift_l) & e->mask;  // 获取相应位的值
                item = snd_soc_enum_val_to_item(e, val);
        } else {
                /* since a virtual mux has no backing registers to
                 * decide which path to connect, it will try to match
                 * with the first enumeration.  This is to ensure
                 * that the default mux choice (the first) will be
                 * correctly powered up during initialization.
                 */
                item = 0;
        }

        i = match_string(e->texts, e->items, control_name);  // 遍历e-texts中,查找与control_name配的索引
        if (i < 0)
                return -ENODEV;

        path->name = e->texts[i];    // 设置路径名
        path->connect = (i == item);
        return 0;

}

snd_soc_enum_val_to_item:如果没有定义values数组就直接返回传入的值;如果定义了values数组,表明数据可能是不连续,需要用数组离散表示,这时判断传入的值在该数组中是否存在,如果存在,返回对应的数组下标;

static inline unsigned int snd_soc_enum_val_to_item(struct soc_enum *e,
        unsigned int val)
{
        unsigned int i;

        if (!e->values)
                return val;

        for (i = 0; i < e->items; i++)
                if (val == e->values[i])
                        return i;

        return 0;
}
4.5.3 dapm_connect_mixer

目的widget如果是Mixer类型,使用dapm_connect_mixer函数完成连接工作;连接到Mixer的路径已经固定,每条路径都有Mixer的一个kcontrol控制开关,path名称就是kcontrol名称。注意与Mux区分开,Mux可以选择与前面任意一个widget相连,但是与Mixer相连的widget是固定的,只有打不打开。

dapm_connect_mixer函数如下:

/* 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, nth_path = 0;

        /* search for mixer kcontrol */
        for (i = 0; i < path->sink->num_kcontrols; i++) {  // Mixer widget,有多个kcontrol
                if (!strcmp(control_name, path->sink->kcontrol_news[i].name)) { // 用需要用来连接的kcontrol的名字,
// 和目的widget中的kcontrol模板数组中的名字相比较 path
->name = path->sink->kcontrol_news[i].name; // path的名字设置为该kcontrol的名字 dapm_set_mixer_path_status(path, i, nth_path++); // 来初始化该输入端的连接状态 return 0; } } return -ENODEV; }

dapm_set_mixer_path_status函数:判断Mixer某个kcontrol中对应的寄存器值是否是0,是0则设置path连接状态connect为未连接,否则为已连接。 

/* set up initial codec paths */
static void dapm_set_mixer_path_status(struct snd_soc_dapm_path *p, int i,
                                       int nth_path)
{
        struct soc_mixer_control *mc = (struct soc_mixer_control *)
                p->sink->kcontrol_news[i].private_value;
        unsigned int reg = mc->reg;
        unsigned int invert = mc->invert;

        if (reg != SND_SOC_NOPM) {  // 配置了寄存器
                unsigned int shift = mc->shift;
                unsigned int max = mc->max;
                unsigned int mask = (1 << fls(max)) - 1;
                unsigned int val = soc_dapm_read(p->sink->dapm, reg);  // 读取寄存器值

                /*
                 * The nth_path argument allows this function to know
                 * which path of a kcontrol it is setting the initial
                 * status for. Ideally this would support any number
                 * of paths and channels. But since kcontrols only come
                 * in mono and stereo variants, we are limited to 2
                 * channels.
                 *
                 * The following code assumes for stereo controls the
                 * first path is the left channel, and all remaining
                 * paths are the right channel.
                 */
                if (snd_soc_volsw_is_stereo(mc) && nth_path > 0) {
                        if (reg != mc->rreg)
                                val = soc_dapm_read(p->sink->dapm, mc->rreg);
                        val = (val >> mc->rshift) & mask;
                } else {
                        val = (val >> shift) & mask;
                }
                if (invert)
                        val = max - val;
                p->connect = !!val;
        } else {
                /* since a virtual mixer has no backing registers to
                 * decide which path to connect, it will try to match
                 * with initial state.  This is to ensure
                 * that the default mixer choice will be
                 * correctly powered up during initialization.
                 */
                p->connect = invert;
        }
}
4.5.4 dapm_update_widget_flags

dapm_update_widget_flags根据widget以及连接的源/目的widget类型来更新widget的is_ep属性;is_ep属性用来描述widget否是是端点类型的widget;

/*
 * dapm_update_widget_flags() - Re-compute widget sink and source flags
 * @w: The widget for which to update the flags
 *
 * Some widgets have a dynamic category which depends on which neighbors they
 * are connected to. This function update the category for these widgets.
 *
 * This function must be called whenever a path is added or removed to a widget.
 */
static void dapm_update_widget_flags(struct snd_soc_dapm_widget *w)
{
        enum snd_soc_dapm_direction dir;
        struct snd_soc_dapm_path *p;
        unsigned int ep;

        switch (w->id) {
        case snd_soc_dapm_input:  // w是Codec输入引脚类型的widget
                /* On a fully routed card an input is never a source */
                if (w->dapm->card->fully_routed)
                        return;
                ep = SND_SOC_DAPM_EP_SOURCE;                          // 1<<0 
                snd_soc_dapm_widget_for_each_source_path(w, p) {      // 遍历w->edges[1]链表(source--path--w,保存着所有和w的输入端连接的snd_soc_dapm_path结构),赋值给path
                        if (p->source->id == snd_soc_dapm_micbias ||  // 如果源p->source是mic、line、micbias或output,则设置ep=0
                                p->source->id == snd_soc_dapm_mic ||
                                p->source->id == snd_soc_dapm_line ||
                                p->source->id == snd_soc_dapm_output) {
                                        ep = 0;
                                        break;
                        }
                }
                break;
        case snd_soc_dapm_output:
                /* On a fully routed card a output is never a sink */
                if (w->dapm->card->fully_routed)
                        return;
                ep = SND_SOC_DAPM_EP_SINK;                      // 1<<1 
                snd_soc_dapm_widget_for_each_sink_path(w, p) {  // 遍历w->edges[0]链表(w--path--sink,保存着所有和w的输出端连接的snd_soc_dapm_path结构),赋值给path
                        if (p->sink->id == snd_soc_dapm_spk ||   // 如果p->sink是spk、hp、line或input,则设置ep=0
                                p->sink->id == snd_soc_dapm_hp ||
                                p->sink->id == snd_soc_dapm_line ||
                                p->sink->id == snd_soc_dapm_input) {
                                        ep = 0;
                                        break;
                        }
                }
                break;
        case snd_soc_dapm_line:
                ep = 0;
           //line类型,如果有输入方向的路径,那就是源端点,如果是输出方向的路径,那就是目的端点
                snd_soc_dapm_for_each_direction(dir) {
                        if (!list_empty(&w->edges[dir]))
                                ep |= SND_SOC_DAPM_DIR_TO_EP(dir); // 1<<dir
                }
                break;
        default:
                return;
        }

        w->is_ep = ep;  // 该widget是端点类型的组件
}

4.6 注册route

如果widget之间没有连接关系,DAPM就无法实现动态的电源管理工作,正是widget之间有了连结关系,这些连接关系形成了一条所谓的完成的音频路径,DAPM可以顺着这条路径,统一控制路径上所有widget的电源状态。

前面我们已经知道,widget之间是使用snd_soc_path结构进行连接的,驱动要做的是定义一个snd_soc_route结构数组,该数组的每个条目描述了目的widget的和源widget的名称,以及控制这个连接的kcontrol的名称,最终驱动程序使用API函数snd_soc_dapm_add_routes来注册这些连接信息。

其具体流程是遍历route数组,调用snd_soc_dapm_add_routes将snd_soc_dapm_route动态地生成所需要的snd_soc_dapm_path结构,然后将snd_soc_dapm_path注册到声卡card的paths链表。函数定义在sound/soc/soc-dapm.c:

/**
 * 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, ret = 0;

        mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME);   // 获取互斥锁
        for (i = 0; i < num; i++) {
                int r = snd_soc_dapm_add_route(dapm, route);
                if (r < 0)
                        ret = r;
                route++;
        }
        mutex_unlock(&dapm->card->dapm_mutex);  // 释放互斥锁

        return ret;
}

该函数拥有三个参数,第一个参数为dapm域,第二个参数为route数组,第三个参数为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;
        unsigned int sink_ref = 0;
        unsigned int source_ref = 0;
        int ret;

        prefix = soc_dapm_prefix(dapm);      // dapm->component存在,则返回dapm->component->name_prefix
        if (prefix) {                        // 在widget的名称前加入必要的前缀
                snprintf(prefixed_sink, sizeof(prefixed_sink), "%s %s",
                         prefix, route->sink);      // 前缀 目标端widget的名称
                sink = prefixed_sink;
                snprintf(prefixed_source, sizeof(prefixed_source), "%s %s",
                         prefix, route->source);    // 前缀 起始端widget的名称
                source = prefixed_source;
        } else {
                sink = route->sink;
                source = route->source;
        }

        wsource = dapm_wcache_lookup(dapm->wcache_source, source);   // 从声卡的card的widgets链表中查找名称为source的widegt
        wsink   = dapm_wcache_lookup(dapm->wcache_sink,   sink);     // 从声卡的card的widgets链表中查找名称为sink的widegt

        if (wsink && wsource)   // 如果均找到,直接返回
                goto skip_search;

        /*
         * find src and dest widgets over all widgets but favor a widget from
         * current DAPM context
         */
        for_each_card_widgets(dapm->card, w) {              // 遍历声卡card的widgets链表,赋值给w
                if (!wsink && !(strcmp(w->name, sink))) {   // 查找名字为sink的widget,如果找到赋值给wtsink
                        wtsink = w;
                        if (w->dapm == dapm) {
                                wsink = w;
                                if (wsource)
                                        break;
                        }
                        sink_ref++;
                        if (sink_ref > 1)
                                dev_warn(dapm->dev,
                                        "ASoC: sink widget %s overwritten\n",
                                        w->name);
                        continue;
                }
                if (!wsource && !(strcmp(w->name, source))) {   // 查找名字为source的widget,如果找到赋值给wtsource
                        wtsource = w;
                        if (w->dapm == dapm) {
                                wsource = w;
                                if (wsink)
                                        break;
                        }
                        source_ref++;
                        if (source_ref > 1)
                                dev_warn(dapm->dev,
                                        "ASoC: source widget %s overwritten\n",
                                        w->name);
                }
        }
        /* use widget from another DAPM context if not found from this */
        if (!wsink)
                wsink = wtsink;
        if (!wsource)
                wsource = wtsource;

        ret = -ENODEV;
        if (!wsource)
                goto err;
        if (!wsink)
                goto err;

skip_search:
        /* update cache */
        dapm->wcache_sink       = wsink;
        dapm->wcache_source     = wsource;

        ret = snd_soc_dapm_add_path(dapm, wsource, wsink, route->control,  // 创建一个snd_soc_dapm_path,并将其添加到声卡card的paths链表中;
                route->connected);                                         // 函数参数分别为dapm域、输入端widget、输出端widget、名称、以及connected回调函数
err:
        if (ret)
                dev_err(dapm->dev, "ASoC: Failed to add route %s%s -%s%s%s> %s%s\n",
                        source, !wsource ? "(*)" : "",
                        !route->control ? "" : "> [",
                        !route->control ? "" : route->control,
                        !route->control ? "" : "] -",
                        sink,  !wsink ? "(*)" : "");
        return ret;
}

(1) 遍历声卡的widgets链表,用widget的名字来比较,找出源widget和目的widget的指针;为了解决时间,该段代码在开头调用dapm_wcache_lookup从widgets链表的开头查找这两个widget,如果找到了,就不用遍历整个链表了。

dapm_wcache_lookup函数用于从声卡的card的widgets链表中查找名称为name的widegt,需要注意的是:链表查找深度默认为2:

static struct snd_soc_dapm_widget *
dapm_wcache_lookup(struct snd_soc_dapm_widget *w, const char *name)
{
        if (w) {
                struct list_head *wlist = &w->dapm->card->widgets;  // 声卡card的widget连表
                const int depth = 2;                                // 链表查找深度
                int i = 0;

                list_for_each_entry_from(w, wlist, list) {  // 遍历widget连表,保存到w
                        if (!strcmp(name, w->name))  // 匹配名称,找到匹配的widget,并返回
                                return w;

                        if (++i == depth)  // 查找到某一深度
                                break;
                }
        }

        return NULL;
}

(2) 最后,调用snd_soc_dapm_add_path根据route构建一个snd_soc_dapm_path,并将其添加到声卡card的paths链表中。

4.7 snd_soc_dapm_enable_pin

snd_soc_dapm_enable_pin函数位于sound/soc/soc-dapm.c,用于设置名称为pin的widget为连接状态;如果没有找到该widget,会打印ASoC: DAPM unknown pin %s错误;

/*
 * similar as __snd_soc_dapm_set_pin(), but returns 0 when successful;
 * called from several API functions below
 */
static int snd_soc_dapm_set_pin(struct snd_soc_dapm_context *dapm,
                                const char *pin, int status)
{
        int ret = __snd_soc_dapm_set_pin(dapm, pin, status);

        return ret < 0 ? ret : 0;
}


/**
 * snd_soc_dapm_enable_pin - enable pin.
 * @dapm: DAPM context
 * @pin: pin name
 *
 * Enables input/output pin and its parents or children widgets iff there is
 * a valid audio route and active audio stream.
 *
 * NOTE: snd_soc_dapm_sync() needs to be called after this for DAPM to
 * do any widget power switching.
 */
int snd_soc_dapm_enable_pin(struct snd_soc_dapm_context *dapm, const char *pin)
{
        int ret;

        mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME);

        ret = snd_soc_dapm_set_pin(dapm, pin, 1);

        mutex_unlock(&dapm->card->dapm_mutex);

        return ret;
}

在该函数的注释中明确指出如果widget的连接状态发生了改变,该widget会被加入到声卡的dapm_dirty链表后,需要主动调用snd_soc_dapm_sync(其内部实际就是调用的dapm_power_widgets)扫描dapm_dirty链表来改变整个音频路径上的电源状态。

函数内部调用了__snd_soc_dapm_set_pin:

/*
 * set the DAPM pin status:
 * returns 1 when the value has been updated, 0 when unchanged, or a negative
 * error code; called from kcontrol put callback
 */
static int __snd_soc_dapm_set_pin(struct snd_soc_dapm_context *dapm, //dapm域
                                  const char *pin, int status) // pin为widget的名称,假设pin为"Headphones"  status为连接状态,1连接,0断开
{
        struct snd_soc_dapm_widget *w = dapm_find_widget(dapm, pin, true); // 遍历声卡card的widgets链表,查找名字为pin的widget
        int ret = 0;

        dapm_assert_locked(dapm);

        if (!w) {
                dev_err(dapm->dev, "ASoC: DAPM unknown pin %s\n", pin);
                return -EINVAL;
        }

        if (w->connected != status) {  // 如果连接状态不一样
                dapm_mark_dirty(w, "pin configuration");  // 将w添加到声卡card的dapm_dirty链表的尾部
                dapm_widget_invalidate_input_paths(w);
                dapm_widget_invalidate_output_paths(w);
                ret = 1;
        }

        w->connected = status; // 更新连接状态
        if (status == 0)
                w->force = 0; 

        return ret;
}

函数执行流程分为两步:

(1) 到声卡设备的widgets链表查找名称为pin的widget,如果没有找到该widget,会打印ASoC: DAPM unknown pin %s错误;

(2) 如果widget的连接状态发生了改变;

  • 则将状态发生改变的widget,也就是当前widget添加到声卡card的dapm_dirty链表的尾部;
  • 同时调用dapm_widget_invalidate_input_paths、dapm_widget_invalidate_output_paths将widget所在路径上的所有widget的endpoints[0]、endpoints[1]设置为-1;换句话说就是只要一条音频路径上包含当前widget,就将音频路径上每个widget的endpoints成员清空(当然有一些特殊的widget会跳过,具体看源码分析)。
  • 更新连接状态,即设置connected=status;
4.7.1 dapm_widget_invalidate_input/output_paths

dapm_widget_invalidate_input_paths、以及dapm_widget_invalidate_output_paths都是通过调用dapm_widget_invalidate_paths实现的;

/*
 * dapm_widget_invalidate_input_paths() - Invalidate the cached number of
 *  input paths
 * @w: The widget for which to invalidate the cached number of input paths
 *
 * Resets the cached number of inputs for the specified widget and all widgets
 * that can be reached via outcoming paths from the widget.
 *
 * This function must be called if the number of output paths for a widget might
 * have changed. E.g. if the source state of a widget changes or a path is added
 * or activated with the widget as the sink.
 */
static void dapm_widget_invalidate_input_paths(struct snd_soc_dapm_widget *w)
{
        dapm_widget_invalidate_paths(w, SND_SOC_DAPM_DIR_IN);  // SND_SOC_DAPM_DIR_IN=0
}

/*
 * dapm_widget_invalidate_output_paths() - Invalidate the cached number of
 *  output paths
 * @w: The widget for which to invalidate the cached number of output paths
 *
 * Resets the cached number of outputs for the specified widget and all widgets
 * that can be reached via incoming paths from the widget.
 *
 * This function must be called if the number of output paths for a widget might
 * have changed. E.g. if the sink state of a widget changes or a path is added
 * or activated with the widget as the source.
 */
static void dapm_widget_invalidate_output_paths(struct snd_soc_dapm_widget *w)
{
        dapm_widget_invalidate_paths(w, SND_SOC_DAPM_DIR_OUT);  // SND_SOC_DAPM_DIR_OUT=1
}
4.7.2 dapm_widget_invalidate_paths

dapm_widget_invalidate_paths函数定义如下:

/*
 * Common implementation for dapm_widget_invalidate_input_paths() and
 * dapm_widget_invalidate_output_paths(). 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 void dapm_widget_invalidate_paths(
        struct snd_soc_dapm_widget *w, enum snd_soc_dapm_direction dir)  // 假设dir=0,即输入
{
        enum snd_soc_dapm_direction rdir = SND_SOC_DAPM_DIR_REVERSE(dir);  // 方向反转 比如0->1
        struct snd_soc_dapm_widget *node;
        struct snd_soc_dapm_path *p;
        LIST_HEAD(list);  // 初始化链表头

        dapm_assert_locked(w->dapm);

        if (w->endpoints[dir] == -1)   // 元素0保存从当前widget向前遍历,连接至SND_SOC_DAPM_EP_SOURCE类型端点的路径数量 
                return;

        list_add_tail(&w->work_list, &list);  // 将当前widget添加到链表list
        w->endpoints[dir] = -1;    // 清空当前widget的endpoints

        list_for_each_entry(w, &list, work_list) {
                snd_soc_dapm_widget_for_each_path(w, dir, p) {  // 遍历以w作为输入端的path,比如:w -- path -- w1
                        if (p->is_supply || p->weak || !p->connect) // 如果path两端连接的有电源类型的widget,或者路径是断开状态,
                                         // 结束该路径的扫描  扫描下一个路径
continue; node = p->node[rdir]; // 获取path输出端的widget if (node->endpoints[dir] != -1) { // 当前node还未处理 node->endpoints[dir] = -1; // 清空node的endpoints list_add_tail(&node->work_list, &list); // 添加到到链表list } } } }

这段代码可能不是很好理解,我们举个例子如下图,假设图中每个path都是处于连通状态的,即path->connect=1:

上图从Widget A开始遍历,一共有三条路径:

  • 第一条路径遍历到widget E;
  • 第一条路径遍历到widget F;
  • 第一条路径遍历到widget C;

该函数执行完成后,会更新位于每条路径上的widget的成员endpoints,就将当前widget的endpoints[dir]设置为-1。

需要注意的是在路径遍历时如果遇到snd_soc_dapm_supply、snd_soc_dapm_regulator_supply、snd_soc_dapm_pinctrl、snd_soc_dapm_clock_supply、snd_soc_dapm_kcontrol类型的widget,或者widget的endpoints[0]=-1,则widget之后的路径都不再继续扫描。

分析之后,我们发现dapm_widget_invalidate_paths函数与is_connected_ep函数的有点类似,is_connected_ep是遍历路径,更新路径上每个widget的endpoints成员,而dapm_widget_invalidate_paths则是清空endpoints成员的值。

五、dapm事件

5.1 event的种类

dapm目前为我们定义了10种事件,具体如下,定义在include/sound/soc-dapm.h:

事件类型 说明
SND_SOC_DAPM_PRE_PMU widget上电前发出的事件

SND_SOC_DAPM_POST_PMU

widget上电后发出的事件
SND_SOC_DAPM_PRE_PMD widget下电前发出的事件
SND_SOC_DAPM_POST_PMD widget下电后发出的事件
SND_SOC_DAPM_PRE_REG 音频路径设置之前发出的事件
SND_SOC_DAPM_POST_REG 音频路径设置之后发出的事件
SND_SOC_DAPM_WILL_PMU 在处理up_list链表之前发出的事件
SND_SOC_DAPM_WILL_PMD 在处理down_list链表之前发出的事件
SND_SOC_DAPM_PRE_POST_PMD SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD
SND_SOC_DAPM_PRE_POST_PMU SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU

前8种每种占据一个位,所以,我们可以在一个整数中表达多个我们需要关心的dapm事件,只要把它们按位或进行合并即可。

5.2 widget的event

我们在介绍struct snd_soc_dapm_widget数据结构时说到了两个与dapm事件相关的字段:

  • event_flags:用于指定事件类型的标志,保存该widget需要关心的dapm事件种类,只有event_flags字段中相应的事件位被设置了的事件才会发到event回调函数中进行处理;
  • int (*event)(struct snd_soc_dapm_widget*, struct snd_kcontrol *, int):指向函数的指针,用于处理dapm事件;

在辅助宏定义widget小节中我们提过,麦克风(snd_soc_dapm_mic),耳机(snd_soc_dapm_hp),扬声器(snd_soc_dapm_spk),线路输入/输出(snd_soc_dapm_line)这几种widget,还可以定义一个dapm事件回调函数wevent,从event flags字段设置可以看出,它们只会响应SND_SOC_DAPM_POST_PMU(上电后)和 SND_SOC_DAPM_PMD(下电前)事件;

/* platform domain */#define SND_SOC_DAPM_MIC(wname, wevent) \
{       .id = snd_soc_dapm_mic, .name = wname, .kcontrol_news = NULL, \
        .num_kcontrols = 0, .reg = SND_SOC_NOPM, .event = wevent, \
        .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD}
#define SND_SOC_DAPM_HP(wname, wevent) \
{       .id = snd_soc_dapm_hp, .name = wname, .kcontrol_news = NULL, \
        .num_kcontrols = 0, .reg = SND_SOC_NOPM, .event = wevent, \
        .event_flags = SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD}
#define SND_SOC_DAPM_SPK(wname, wevent) \
{       .id = snd_soc_dapm_spk, .name = wname, .kcontrol_news = NULL, \
        .num_kcontrols = 0, .reg = SND_SOC_NOPM, .event = wevent, \
        .event_flags = SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD}
#define SND_SOC_DAPM_LINE(wname, wevent) \
{       .id = snd_soc_dapm_line, .name = wname, .kcontrol_news = NULL, \
        .num_kcontrols = 0, .reg = SND_SOC_NOPM, .event = wevent, \
        .event_flags = SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD}

这些widget都是位于codec外部的器件,通常定义在machine驱动中。它们无法使用通用的寄存器操作来控制widget的电源状态,所以需要我们提供event回调函数,

例如,对于一个外部麦克风的Jack connetcor widget,在麦克风插入的时候通过SPITZ_GPIO_MIC_BIAS这个gpio来控制它的电源状态:

static int spitz_mic_bias(struct snd_soc_dapm_widget* w, int event)
{
      gpio_set_value(SPITZ_GPIO_MIC_BIAS, SND_SOC_DAPM_EVENT_ON(event));
      return 0;
}

SND_SOC_DAPM_MIC("Mic Jack", spitz_mic_bias),

另外,我们也可以通过以下这些带"_E"后缀的辅助宏版本来定义需要dapm事件的widget:

  • SND_SOC_DAPM_PGA_E;
  • SND_SOC_DAPM_OUT_DRV_E;
  • SND_SOC_DAPM_MIXER_E;
  • SND_SOC_DAPM_MIXER_NAMED_CTL_E;
  • SND_SOC_DAPM_SWITCH_E;
  • SND_SOC_DAPM_MUX_E;
  • SND_SOC_DAPM_VIRT_MUX_E;

5.3 触发dapm event

我们已经定义好了带有event回调的widget,那么,在哪里触发这些dapm event?

在之前介绍的dapm_power_widgets函数中,在将连接状态或者电源状态发生改变的的widget放入up_list或者down_list链表后,会相应地发出各种dapm事件。

5.3.1 SND_SOC_DAPM_WILL_PMD/PMU事件

在真正地进行上电和下电之前,dapm向down_list链表中的每个widget发出SND_SOC_DAPM_WILL_PMD事件,而向up_list链表中的每个widget发出SND_SOC_DAPM_WILL_PMU事件;

list_for_each_entry(w, &down_list, power_list) {  // 发布SND_SOC_DAPM_WILL_PMD事件
        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);  // 发布SND_SOC_DAPM_WILL_PMU事件
} 

具体实现是在dapm_seq_check_event函数完成的,该函数实际上就是执行widget->event回调方法;

static void dapm_seq_check_event(struct snd_soc_card *card,
                                 struct snd_soc_dapm_widget *w, int event)
{
        const char *ev_name;
        int power;

        switch (event) {
        case SND_SOC_DAPM_PRE_PMU:    // 上电前
                ev_name = "PRE_PMU";
                power = 1;
                break;
        case SND_SOC_DAPM_POST_PMU:   // 上电后
                ev_name = "POST_PMU";
                power = 1;
                break;
        case SND_SOC_DAPM_PRE_PMD:  // 下点前
                ev_name = "PRE_PMD";
                power = 0;
                break;
        case SND_SOC_DAPM_POST_PMD:  // 下电后
                ev_name = "POST_PMD";
                power = 0;
                break;
        case SND_SOC_DAPM_WILL_PMU: // 在处理up_list链表之前发出的事件
                ev_name = "WILL_PMU";
                power = 1;
                break;
        case SND_SOC_DAPM_WILL_PMD: // 在处理down_list链表之前发出的事件
                ev_name = "WILL_PMD";
                power = 0;
                break;
        default:
                WARN(1, "Unknown event %d\n", event);
                return;
        }

        if (w->new_power != power)  // 没太明白啥意思
                return;

        if (w->event && (w->event_flags & event)) { // 只有event_flags字段中相应的事件位被设置了的事件才会发到event回调函数中进行处理
                int ret;

                pop_dbg(w->dapm->dev, card->pop_time, "pop test : %s %s\n",
                        w->name, ev_name);
                soc_dapm_async_complete(w->dapm);
                trace_snd_soc_dapm_widget_event_start(w, event);
                ret = w->event(w, NULL, event);
                trace_snd_soc_dapm_widget_event_done(w, event);
                if (ret < 0)
                        dev_err(w->dapm->dev, "ASoC: %s: %s event failed: %d\n",
                               ev_name, w->name, ret);
        }
}
5.3.2 SND_SOC_DAPM_PRE_PMU/PMD等事件

在处理上下电的函数dapm_seq_run中,会调用dapm_seq_run_coalesced函数执行真正的寄存器操作,进行widget的电源控制,dapm_seq_run_coalesced也会发出另外几种dapm事件(SND_SOC_DAPM_PRE_PMU、SND_SOC_DAPM_PRE_PMD、SND_SOC_DAPM_POST_PMU、SND_SOC_DAPM_POST_PMD):

list_for_each_entry(w, pending, power_list) {   // 遍历pending链表中的widget,赋值给w
        ......

        /* Check for events */
        dapm_seq_check_event(card, w, SND_SOC_DAPM_PRE_PMU);
        dapm_seq_check_event(card, w, SND_SOC_DAPM_PRE_PMD);
}

if (reg >= 0) {
        ......    
        soc_dapm_update_bits(dapm, reg, mask, value);  // 硬件寄存器操作,更新指定寄存器reg、指定位mask的值
}

list_for_each_entry(w, pending, power_list) {
        dapm_seq_check_event(card, w, SND_SOC_DAPM_POST_PMU);
        dapm_seq_check_event(card, w, SND_SOC_DAPM_POST_PMD);
}
5.3.3 SND_SOC_DAPM_PRE/POST_REG事件

另外,负责更新音频路径的dapm_widget_update函数中也会发出dapm事件(SND_SOC_DAPM_PRE_REG事件和SND_SOC_DAPM_POST_REG);

static void dapm_widget_update(struct snd_soc_card *card)
{
        struct snd_soc_dapm_update *update = card->update;
        struct snd_soc_dapm_widget_list *wlist;
        struct snd_soc_dapm_widget *w = NULL;
        unsigned int wi;
        int ret;

        if (!update || !dapm_kcontrol_is_powered(update->kcontrol))
                return;

        wlist = dapm_kcontrol_get_wlist(update->kcontrol);

        for_each_dapm_widgets(wlist, wi, w) {
                if (w->event && (w->event_flags & SND_SOC_DAPM_PRE_REG)) {
                        ret = w->event(w, update->kcontrol, SND_SOC_DAPM_PRE_REG);
                        if (ret != 0)
                                dev_err(w->dapm->dev, "ASoC: %s DAPM pre-event failed: %d\n",
                                           w->name, ret);
                }
        }

        if (!w)
                return;

        ret = soc_dapm_update_bits(w->dapm, update->reg, update->mask,
                update->val);
        if (ret < 0)
                dev_err(w->dapm->dev, "ASoC: %s DAPM update failed: %d\n",
                        w->name, ret);

        if (update->has_second_set) {
                ret = soc_dapm_update_bits(w->dapm, update->reg2,
                                           update->mask2, update->val2);
                if (ret < 0)
                        dev_err(w->dapm->dev,
                                "ASoC: %s DAPM update failed: %d\n",
                                w->name, ret);
        }

        for_each_dapm_widgets(wlist, wi, w) {
                if (w->event && (w->event_flags & SND_SOC_DAPM_POST_REG)) {
                        ret = w->event(w, update->kcontrol, SND_SOC_DAPM_POST_REG);
                        if (ret != 0)
                                dev_err(w->dapm->dev, "ASoC: %s DAPM post-event failed: %d\n",
                                           w->name, ret);
                }
        }
}

5.4 stream operation

在ASoC声卡注册的时候,会通过stream name匹配codec dai widget和stream widget,并将其连接起来。其就是为了能把ASoC中的pcm处理部分和dapm进行关联,pcm的处理过程中,会通过发出stream operation来通知dapm系统,重新扫描并调整音频路径上各个widget的电源状态,目前dapm提供了以下几种stream event,定义在include/sound/soc-dapm.h:

/* dapm stream operations */
#define SND_SOC_DAPM_STREAM_NOP                 0x0
#define SND_SOC_DAPM_STREAM_START               0x1
#define SND_SOC_DAPM_STREAM_STOP                0x2
#define SND_SOC_DAPM_STREAM_SUSPEND             0x4
#define SND_SOC_DAPM_STREAM_RESUME              0x8
#define SND_SOC_DAPM_STREAM_PAUSE_PUSH          0x10
#define SND_SOC_DAPM_STREAM_PAUSE_RELEASE       0x20

比如在soc_pcm_prepare函数中,会发出SND_SOC_DAPM_STREAM_START操作,函数定义在sound/soc/soc-pcm.c:

snd_soc_dapm_stream_event(rtd, substream->stream,
                SND_SOC_DAPM_STREAM_START);
5.4.1 snd_soc_dapm_stream_event

snd_soc_dapm_stream_event函数定义在sound/soc/soc-dapm.c,snd_soc_dapm_stream_event函数最终会使用soc_dapm_stream_event函数来完成具体的工作:

static void soc_dapm_stream_event(struct snd_soc_pcm_runtime *rtd, int stream,
        int event)
{
        struct snd_soc_dai *dai;
        int i;

        for_each_rtd_dais(rtd, i, dai)   // 遍历pcm runtime的dais数组,dai = (rtd)->dais[i]
                soc_dapm_dai_stream_event(dai, stream, event);

        dapm_power_widgets(rtd->card, event);
}

/**
 * snd_soc_dapm_stream_event - send a stream event to the dapm core
 * @rtd: PCM runtime data
 * @stream: stream name
 * @event: stream event
 *
 * Sends a stream event to the dapm core. The core then makes any
 * necessary widget power changes.
 *
 * Returns 0 for success else error.
 */
void snd_soc_dapm_stream_event(struct snd_soc_pcm_runtime *rtd, int stream,
                              int event)
{
        struct snd_soc_card *card = rtd->card;

        mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME);
        soc_dapm_stream_event(rtd, stream, event);
        mutex_unlock(&card->dapm_mutex);
}

该函数会遍历snd_soc_pcm_runtime结构中dais链表,即cpu dai和codec dai,然后调用soc_dapm_dai_stream_event函数。

5.4.2 soc_dapm_dai_stream_event

soc_dapm_dai_stream_event函数定义在sound/soc/soc-dapm.c,soc_dapm_dai_stream_event从cpu dai和codec dai中取出playback、capture widge,然后调用dapm_widget_invalidate_input_paths、dapm_widget_invalidate_output_paths将widget所在路径上的所有widget的endpoints[0]、endpoints[1]设置为-1;换句话说就是只要一条音频路径上包含当前widget,就将音频路径上每个widget的endpoints成员清空。

此外如果是SND_SOC_DAPM_STREAM_START事件,这个函数会:

  • 设置playback dai widget的is_ep=SND_SOC_DAPM_EP_SOURCE、active=1;
  • 设置capture dai widget的is_ep=SND_SOC_DAPM_EP_SINK、active=1;

如果是SND_SOC_DAPM_STREAM_STOP事件,这个函数会设置playback/capture dai widget的is_ep=0、active=0;

static void soc_dapm_dai_stream_event(struct snd_soc_dai *dai, int stream,
        int event)
{
        struct snd_soc_dapm_widget *w;

        w = snd_soc_dai_get_widget(dai, stream);  // 获取dai->stream[stream].widget

        if (w) {
                unsigned int ep;

                dapm_mark_dirty(w, "stream event");

                if (w->id == snd_soc_dapm_dai_in) {   // playback dai widget
                        ep = SND_SOC_DAPM_EP_SOURCE;
                        dapm_widget_invalidate_input_paths(w);
                } else {                             // capture dai widget
                        ep = SND_SOC_DAPM_EP_SINK;
                        dapm_widget_invalidate_output_paths(w);
                }

                switch (event) {
                case SND_SOC_DAPM_STREAM_START:    // 开始
                        w->active = 1;
                        w->is_ep = ep;
                        break;
                case SND_SOC_DAPM_STREAM_STOP:    // 停止
                        w->active = 0;
                        w->is_ep = 0;
                        break;
                case SND_SOC_DAPM_STREAM_SUSPEND:
                case SND_SOC_DAPM_STREAM_RESUME:
                case SND_SOC_DAPM_STREAM_PAUSE_PUSH:
                case SND_SOC_DAPM_STREAM_PAUSE_RELEASE:
                        break;
                }
        }
}

六、案例

上面我们介绍了widget、path、route,那么对于一个实际的系统,我们怎么定义我们需要的widget?怎样定义widget 的连接关系?下面我们以Wolfson 公司的codec芯片WM8960为例,相关源代码位于sound/soc/codecs/wm8960.c;

这里以音频播放为例,Codec侧音频complete path: Playback(snd_soc_dapm_dai_in类型的playback dai widget)   ——>  Left / Right DAC  ——> Left / Right  Output Mixer ——> L/ROUT1 PGA ——> HP_L/R ——> Headphones (最后一个path会定义在Machine驱动中)

其中红色部分表示有相应的kcontrol,即需要switch打开,在该路径中Headphones为SND_SOC_DAPM_EP_SINK类型端点,Playback为SND_SOC_DAPM_EP_SOURCE类型端点。

6.1 定义dapm kcontrol

利用辅助宏定义widget所需要的dapm kcontrol,由于sound/soc/codecs/wm8960.c文件中定义了大量的kcontrol(包括普通kcontrol和dapm kcontrol),因此这里我们只列出与音频播放相关的dapm kcontrol;

static const struct snd_kcontrol_new wm8960_loutput_mixer[] = {
  SOC_DAPM_SINGLE("PCM Playback Switch", WM8960_LOUTMIX, 8, 1, 0),
  SOC_DAPM_SINGLE("LINPUT3 Switch", WM8960_LOUTMIX, 7, 1, 0),
  SOC_DAPM_SINGLE("Boost Bypass Switch", WM8960_BYPASS1, 7, 1, 0),
};

static const struct snd_kcontrol_new wm8960_routput_mixer[] = {
  SOC_DAPM_SINGLE("PCM Playback Switch", WM8960_ROUTMIX, 8, 1, 0),
  SOC_DAPM_SINGLE("RINPUT3 Switch", WM8960_ROUTMIX, 7, 1, 0),
  SOC_DAPM_SINGLE("Boost Bypass Switch", WM8960_BYPASS2, 7, 1, 0),
};

以上定义了wm8960中左右声道的Output Mixer。

6.2 定义widget 

利用辅助宏定义真正的widget:

static const struct snd_soc_dapm_widget wm8960_dapm_widgets[] = {
SND_SOC_DAPM_INPUT("LINPUT1"),
SND_SOC_DAPM_INPUT("RINPUT1"),
SND_SOC_DAPM_INPUT("LINPUT2"),
SND_SOC_DAPM_INPUT("RINPUT2"),
SND_SOC_DAPM_INPUT("LINPUT3"),
SND_SOC_DAPM_INPUT("RINPUT3"),

SND_SOC_DAPM_SUPPLY("MICB", WM8960_POWER1, 1, 0, NULL, 0),

SND_SOC_DAPM_MIXER("Left Boost Mixer", WM8960_POWER1, 5, 0,
                   wm8960_lin_boost, ARRAY_SIZE(wm8960_lin_boost)),
SND_SOC_DAPM_MIXER("Right Boost Mixer", WM8960_POWER1, 4, 0,
                   wm8960_rin_boost, ARRAY_SIZE(wm8960_rin_boost)),

SND_SOC_DAPM_MIXER("Left Input Mixer", WM8960_POWER3, 5, 0,
                   wm8960_lin, ARRAY_SIZE(wm8960_lin)),
SND_SOC_DAPM_MIXER("Right Input Mixer", WM8960_POWER3, 4, 0,
                   wm8960_rin, ARRAY_SIZE(wm8960_rin)),

SND_SOC_DAPM_ADC("Left ADC", "Capture", WM8960_POWER1, 3, 0),
SND_SOC_DAPM_ADC("Right ADC", "Capture", WM8960_POWER1, 2, 0),

SND_SOC_DAPM_DAC("Left DAC", "Playback", WM8960_POWER2, 8, 0),     // Left DAC
SND_SOC_DAPM_DAC("Right DAC", "Playback", WM8960_POWER2, 7, 0),    // Right DAC

SND_SOC_DAPM_MIXER("Left Output Mixer", WM8960_POWER3, 3, 0,      // Left Output Mixer
        &wm8960_loutput_mixer[0],
        ARRAY_SIZE(wm8960_loutput_mixer)),
SND_SOC_DAPM_MIXER("Right Output Mixer", WM8960_POWER3, 2, 0,      // Right Output Mixer
        &wm8960_routput_mixer[0],
        ARRAY_SIZE(wm8960_routput_mixer)),

SND_SOC_DAPM_PGA("LOUT1 PGA", WM8960_POWER2, 6, 0, NULL, 0),      // LOUT1 PGA
SND_SOC_DAPM_PGA("ROUT1 PGA", WM8960_POWER2, 5, 0, NULL, 0),      // LOUT1 PGA

SND_SOC_DAPM_PGA("Left Speaker PGA", WM8960_POWER2, 4, 0, NULL, 0),
SND_SOC_DAPM_PGA("Right Speaker PGA", WM8960_POWER2, 3, 0, NULL, 0),

SND_SOC_DAPM_PGA("Right Speaker Output", WM8960_CLASSD1, 7, 0, NULL, 0),
SND_SOC_DAPM_PGA("Left Speaker Output", WM8960_CLASSD1, 6, 0, NULL, 0),

SND_SOC_DAPM_OUTPUT("SPK_LP"),
SND_SOC_DAPM_OUTPUT("SPK_LN"),
SND_SOC_DAPM_OUTPUT("HP_L"),    // HP_L
SND_SOC_DAPM_OUTPUT("HP_R"),    // HP_R 
SND_SOC_DAPM_OUTPUT("SPK_RP"),
SND_SOC_DAPM_OUTPUT("SPK_RN"),
SND_SOC_DAPM_OUTPUT("OUT3"),
};

这一步,分别为左右声道定义了Left/Right DAC、L/ROUT1 PGA、HP_L/R Output Widget,也分别定义了 Left/Right Output Mixer Widget具体Mixer 控制由上一步定义的wm8960_loutput_mixer和wm8960_routput_mixer来完成。

上述中对于非SND_SOC_NOPM参数的(即除 HP_L/R 以外),都是具备电源属性,即当这些widget在一条有效的音频路径上时,DAPM框架可以通过寄存器来控制它们的电源状态。

6.3 route

利用辅助宏定义定义widget的链接路径:

static const struct snd_soc_dapm_route audio_paths[] = {
        { "Left Boost Mixer", "LINPUT1 Switch", "LINPUT1" },
        { "Left Boost Mixer", "LINPUT2 Switch", "LINPUT2" },
        { "Left Boost Mixer", "LINPUT3 Switch", "LINPUT3" },

        { "Left Input Mixer", "Boost Switch", "Left Boost Mixer" },
        { "Left Input Mixer", "Boost Switch", "LINPUT1" },  /* Really Boost Switch */
        { "Left Input Mixer", NULL, "LINPUT2" },
        { "Left Input Mixer", NULL, "LINPUT3" },

        { "Right Boost Mixer", "RINPUT1 Switch", "RINPUT1" },
        { "Right Boost Mixer", "RINPUT2 Switch", "RINPUT2" },
        { "Right Boost Mixer", "RINPUT3 Switch", "RINPUT3" },

        { "Right Input Mixer", "Boost Switch", "Right Boost Mixer" },
        { "Right Input Mixer", "Boost Switch", "RINPUT1" },  /* Really Boost Switch */
        { "Right Input Mixer", NULL, "RINPUT2" },
        { "Right Input Mixer", NULL, "RINPUT3" },

        { "Left ADC", NULL, "Left Input Mixer" },      
        { "Right ADC", NULL, "Right Input Mixer" },   

        { "Left Output Mixer", "LINPUT3 Switch", "LINPUT3" },
        { "Left Output Mixer", "Boost Bypass Switch", "Left Boost Mixer" },
        { "Left Output Mixer", "PCM Playback Switch", "Left DAC" },           // route 1

        { "Right Output Mixer", "RINPUT3 Switch", "RINPUT3" },
        { "Right Output Mixer", "Boost Bypass Switch", "Right Boost Mixer" },
        { "Right Output Mixer", "PCM Playback Switch", "Right DAC" },          // route 1 

        { "LOUT1 PGA", NULL, "Left Output Mixer" },     // route 2
        { "ROUT1 PGA", NULL, "Right Output Mixer" },    // route 2 

        { "HP_L", NULL, "LOUT1 PGA" },              // route 3
        { "HP_R", NULL, "ROUT1 PGA" },              // route 3 

        { "Left Speaker PGA", NULL, "Left Output Mixer" },
        { "Right Speaker PGA", NULL, "Right Output Mixer" },

        { "Left Speaker Output", NULL, "Left Speaker PGA" },
        { "Right Speaker Output", NULL, "Right Speaker PGA" },

        { "SPK_LN", NULL, "Left Speaker Output" },
        { "SPK_LP", NULL, "Left Speaker Output" },
        { "SPK_RN", NULL, "Right Speaker Output" },
        { "SPK_RP", NULL, "Right Speaker Output" },
};

由该 snd_soc_dapm_route 定义知道其route即为上述所述:Left / Right DAC  ——> Left / Right  Output Mixer ——> L/ROUT1 PGA ——> HP_L/R。

6.4 Codec driver

在Codec驱动的probe回到函数中调用snd_soc_dapm_new_controls以及snd_soc_dapm_add_routes函数完成这些widge和route的注册;

static int wm8960_add_widgets(struct snd_soc_component *component)
{
        struct wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component);
        struct wm8960_data *pdata = &wm8960->pdata;
        struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component);
        struct snd_soc_dapm_widget *w;

        snd_soc_dapm_new_controls(dapm, wm8960_dapm_widgets,
                                  ARRAY_SIZE(wm8960_dapm_widgets));         // 初始化widget成员

        snd_soc_dapm_add_routes(dapm, audio_paths, ARRAY_SIZE(audio_paths));

        /* In capless mode OUT3 is used to provide VMID for the
         * headphone outputs, otherwise it is used as a mono mixer.
         */
        if (pdata && pdata->capless) {
                snd_soc_dapm_new_controls(dapm, wm8960_dapm_widgets_capless, // 创建新的widget,并将将每一个widget添加到声卡card的widgets链表中
                                          ARRAY_SIZE(wm8960_dapm_widgets_capless));

         // 将snd_soc_dapm_route动态地生成所需要的snd_soc_dapm_path结构,然后将snd_soc_dapm_path注册到声卡card的paths链表 snd_soc_dapm_add_routes(dapm, audio_paths_capless, ARRAY_SIZE(audio_paths_capless)); }
else { snd_soc_dapm_new_controls(dapm, wm8960_dapm_widgets_out3, ARRAY_SIZE(wm8960_dapm_widgets_out3)); snd_soc_dapm_add_routes(dapm, audio_paths_out3, ARRAY_SIZE(audio_paths_out3)); } /* We need to power up the headphone output stage out of * sequence for capless mode. To save scanning the widget * list each time to find the desired power state do so now * and save the result. */ list_for_each_entry(w, &component->card->widgets, list) { if (w->dapm != dapm) continue; if (strcmp(w->name, "LOUT1 PGA") == 0) wm8960->lout1 = w; if (strcmp(w->name, "ROUT1 PGA") == 0) wm8960->rout1 = w; if (strcmp(w->name, "OUT3 VMID") == 0) wm8960->out3 = w; } return 0; }

在 Machine驱动中,我们可以用同样的方式定义和注册板子特有的widget和route信息。

6.5 总结

这里我们绘制一下codec侧音频播放左通道的路径如下图所示:

从上图我们知道如果我们需要进行音频播放,上图红色标记的路径(这个是一个cpmplete path)中的widget需要满足以下条件才能实现音频的播放;

  • 所有widget的电源状态为上电(widget->power=1,对于有寄存器控制电源状态的widget,需要配置寄存器使其上电);
  • 所有的widget设置为连接状态(widget->connected=1,这个是程序中使用的中间变量,);
  • 同时需要将名字为PCM Playback Switch的kcontrol接通,即满足所有path处于连接状态(path->connect=1,对于Mixer、Mux、Switch等类型的widget,需要配置寄存器使其接通);

widget的上下电都是dapm根据策略自主控制的,外部无法干预。假设上图中红色路径上的所有widget均处于下电状态,以插入耳机为例;当我们插入耳机时,耳机检测引脚将会触发中断,中断处理函数会调用snd_soc_jack_report上报GPIO引脚有耳机插入的事件,在snd_soc_jack_report函数内会完成以下工作:

  • 到声卡card的widgets链表中查找耳机对应的的widget(名字为Headphones),如果widget连接状态发生了变更,则将widget添加到声卡card的dapm_dirty链表的尾部,并更新widget->connected字段值为1;
  • 调用snd_soc_dapm_sync统一处理状态(包括连接状态和电源状态)发生改变的widget(在snd_soc_dapm_sync函数内部会将一些新的widget添加到card的dapm_dirty链表中),对其进行上下电管理;complete path(红色路径)上的widget都会上电(widget->power=1);
  • 通过input_report_key/input_report_switch来向上层汇报EV_KEY、EV_SW事件。

如果将耳机拔出流程也是一样的,只是需要更改widget的connected状态为0,同时对complete path上的widget进行下电(widget->power=0)。

参考文章

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

[2] linux-alsa详解8之DAPM详解1kcontrol

[3] linux-alsa详解9之DAPM详解2widget基本知识

[4] Dynamic Audio Power Management for Portable Devices

[5] ALSA声卡驱动中的DAPM详解之四:在驱动程序中初始化并注册widget和route

[6] ALSA声卡驱动中的DAPM详解之五:建立widget之间的连接关系

[7] ALSA声卡驱动中的DAPM详解之六:精髓所在,牵一发而动全身

[8] audio子系统笔记(二)

[9] DAPM之六:dapm机制深入分析(下)

[10] rk音频驱动分析之widget的上下电过程

[11 ] Linux ALSA 之十一:ALSA ASOC Path 完整路径追踪

[12] Linux ALSA音频驱动三:DAPM电源管理

[13] ALSA声卡驱动中的DAPM详解之七:dapm事件机制(dapm event)

posted @ 2023-07-23 18:06  大奥特曼打小怪兽  阅读(563)  评论(0编辑  收藏  举报
如果有任何技术小问题,欢迎大家交流沟通,共同进步