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 };
上面的枚举值实际上是与不同的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}
以上这些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} ......
可以看出,这些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 }
需要注意的是:我们在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 }
在完成所有的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 }
可以看出,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); }
代码的主要执行流程:
- 为该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; }
该函数内部调用了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; }
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]中。
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定义如下:
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的名称;
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);那么在执行该函数的过程中:
- 判断widget B是否处于complete path上,由于widget B处于complete path上,因此需要对widget B进行上电操作;
- 首先会查找widget B的相邻widget,也就是widget A、widget D、widget F;
- 比较widget A、widget D、widget F的电源状态是否是出于上电状态,由于都不是处于上电状态;
- 将widget A、widget D、widget F添加到dapm_dirty链表;
- 将widget B按照一定顺序添加到up_list链表中;
- 遍历dapm_dirty链表下一个元素,也就是widget A,同样执行dapm_power_one_widget方法;即跳转到第1步执行;
- .....
- 执行完之后,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; }
内部调用了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的尾部 }
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, };
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, };
(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; }
(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; }
通过之前的介绍,我们大概已经知道混音控制(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子系统笔记(二)