Linux ALSA音频驱动三:DAPM电源管理
结合dapm电源管理机制,codec根据自己的能力向ALSA注册自己的kcontrol, widget以及routes,可以实现寄存器通路的选择以及动态开关。
声卡注册成功后可通过amixer或tinymix查看其kcontrol信息,如下所示。
root@ubuntu:/mnt# amixer controls
numid=3,iface=MIXER,name='Master Mono Playback Switch'
numid=4,iface=MIXER,name='Master Mono Playback Volume'
numid=1,iface=MIXER,name='Master Playback Switch'
numid=2,iface=MIXER,name='Master Playback Volume'
DAPM的存在使得无需修改内核代码便可轻易切换CODEC通路,只需在用户态通过触发playback/capture数据流或tinymix配置通路实现。下面摘抄一段经典讲述。
本节来自CSDN: DAPM之二:audio paths与dapm kcontrol_sepnic的博客-CSDN博客
先看图3.1,红色线路是LINPUT1(Left Input) -> LEFT INPUT PGA -> LEFT INPUT MIXER -> LEFT OUTPUT MIXER -> LINEOUT1L,表示从LINPUT输入的信号通过这条路径送到LINEOUT输出,再具现一点,就是录音信号直接放到SPK播出。在这条路径上,有三个带+号的圆圈,那就是多路混合器mixer,用于切换输入源或将多个输入源混合输出。
土黄色部分为LEFT INPUT PGA:可选择LINPUT1、LINPUT2和LINPUT3;
绿色部分是LEFT INPUT MIXER:可选择INPUTPGA、LINPUT2、LINPUT3和AUX/LCOM;
蓝色部分是LEFT OUTPUT MIXER:可选择INPUTMIXER、LINPUT3、AUX/LCOM和LEFT DAC等。
图3.1 声卡原理图
配置声音通路时,主要是对mixer做切换输入源操作。如要实现Playback,则需要打通DAC -> OUTPUT MIXER -> LINEOUT通路。
可将图3.1抽象成图3.2。
图3.2 CODEC通路抽象图
上图一共有3条通路,即:
- 录音通路:Mic -> Input Mixer -> ADC
- 录播通路:Mic -> Input Mixer -> Output Mixer -> HP/SPK
- 播放通路:DAC -> Output Mixer -> HP/SPK
上面涉及到了DAPM通路的概念,其中涉及到很多音频驱动术语,如mixer, mux, path等,下面作进一步分析。
3.2、kcontrol定义
Kcontrol代表声卡里的各种硬件开关,滑动控件等,通过软件定义kcontrol可通过用户态配置硬件寄存器的开和关。
ALSA用snd_kcontrol_new定义kcontrol。
struct snd_kcontrol_new {
snd_ctl_elem_iface_t iface; /* interface identifier */
unsigned int device; /* device/client number */
unsigned int subdevice; /* subdevice (substream) number */
const char *name; /* ASCII name of item */
unsigned int index; /* index of item */
unsigned int access; /* access rights */
unsigned int count; /* count of same elements */
snd_kcontrol_info_t *info;
snd_kcontrol_get_t *get;
snd_kcontrol_put_t *put;
union {
snd_kcontrol_tlv_rw_t *c;
const unsigned int *p;
} tlv;
unsigned long private_value;
};
name: 表示该kcontrol的名字。
info:回调函数,用于获取control的详细信息。
get:回调函数,用于读取control的当前值。
put:回调函数,用于把应用程序的控制值设置到control中。
ALSA提供了一整套宏用于定义kcontrol控件,其中一类是通路中的某一条路径,通常是mixer的一路分支,用SOC_DAPM_XXX定义,另一类是零散的不影响通路状态,用SOC_SINGLE_XXX定义。
Dapm控件由soc-dapm.h中的一组宏来定义,如SOC_DAPM_SINGLE等。
#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) }
非dapm控件通常由include\sound\soc.h中的宏来定义,如SOC_SINGLE.
#define SOC_SINGLE(xname, reg, shift, max, invert) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
.info = snd_soc_info_volsw, .get = snd_soc_get_volsw,\
.put = snd_soc_put_volsw, \
.private_value = SOC_SINGLE_VALUE(reg, shift, max, invert, 0) }
对比发现dapm与非dapm定义大体类似,都由SOC_SINGLE_VALUE定义基本信息,reg, shift, max, invert等,而区别主要在get, put回调。
dapm控件的get, put回调会触发相邻控件加入dirty链表,继而触发整条路径的搜索,而非dapm控件仅仅是触发自身的读写,不具备传导作用。
int snd_soc_dapm_put_volsw(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kcontrol);
struct snd_soc_card *card = dapm->card;
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
int reg = mc->reg;
unsigned int shift = mc->shift;
int max = mc->max;
unsigned int width = fls(max);
unsigned int mask = (1 << width) - 1;
unsigned int invert = mc->invert;
unsigned int val, rval = 0;
int connect, rconnect = -1, change, reg_change = 0;
struct snd_soc_dapm_update update = {};
int ret = 0;
val = (ucontrol->value.integer.value[0] & mask);
connect = !!val;
if (invert)
val = max - val;
if (snd_soc_volsw_is_stereo(mc)) {
rval = (ucontrol->value.integer.value[1] & mask);
rconnect = !!rval;
if (invert)
rval = max - rval;
}
mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME);
/* This assumes field width < (bits in unsigned int / 2) */
if (width > sizeof(unsigned int) * 8 / 2)
dev_warn(dapm->dev,
"ASoC: control %s field width limit exceeded\n",
kcontrol->id.name);
change = dapm_kcontrol_set_value(kcontrol, val | (rval << width));
if (reg != SND_SOC_NOPM) {
val = val << shift;
rval = rval << mc->rshift;
reg_change = soc_dapm_test_bits(dapm, reg, mask << shift, val);
if (snd_soc_volsw_is_stereo(mc))
reg_change |= soc_dapm_test_bits(dapm, mc->rreg,
mask << mc->rshift,
rval);
}
if (change || reg_change) {
if (reg_change) {
if (snd_soc_volsw_is_stereo(mc)) {
update.has_second_set = true;
update.reg2 = mc->rreg;
update.mask2 = mask << mc->rshift;
update.val2 = rval;
}
update.kcontrol = kcontrol;
update.reg = reg;
update.mask = mask << shift;
update.val = val;
card->update = &update;
}
change |= reg_change;
ret = soc_dapm_mixer_update_power(card, kcontrol, connect,
rconnect);
card->update = NULL;
}
mutex_unlock(&card->dapm_mutex);
if (ret > 0)
snd_soc_dpcm_runtime_update(card);
return change;
}
Kcontrol的基本信息都存在private_value中,如果待写入值与当前值的差异,会触发soc_dapm_mixer_update_power更新kcontrol。
static int soc_dapm_mixer_update_power(struct snd_soc_card *card,
struct snd_kcontrol *kcontrol,
int connect, int rconnect)
{
struct snd_soc_dapm_path *path;
int found = 0;
lockdep_assert_held(&card->dapm_mutex);
/* find dapm widget path assoc with kcontrol */
dapm_kcontrol_for_each_path(path, kcontrol) {
if (found && rconnect >= 0)
soc_dapm_connect_path(path, rconnect, "mixer update");
else
soc_dapm_connect_path(path, connect, "mixer update");
found = 1;
}
if (found)
dapm_power_widgets(card, SND_SOC_DAPM_STREAM_NOP);
return found;
}
dapm_power_widgets具体状态传导作用,会将当前kcontrol相邻的kcontrol都加入dirty链表,后面会分析。
3.3、widget定义
DAPM通路切换是通过配置不同的route表来实现的,影响音频route开关的组件称为widget,下面看看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
};
影响path通路最常用的几种widget,如mixer, mux, switch等。
MIXER:多个输入源混合成一个输出,用SND_SOC_DAPM_MIXER定义这个widget,类型为snd_soc_dapm_mixer;
MUX:多路选择器,多路输入,但只能选择一路作为输出,用SND_SOC_DAPM_MUX定义这个widget,类型为snd_soc_dapm_mux;
PGA:单路输入,单路输出,带gain调整的部件,用SND_SOC_DAPM_PGA定义这个widget,类型为snd_soc_dapm_pga。
下面分别看看如何定义。
3.3.1、Mixer定义
Mixer意在将多个输入混合成一个输出,如手机同时打电话,又播放音乐,需要将两路数据混合后再输出到speaker,就需要用到mixer,如图3.3所示。
图3.3 mixer原理图
Mixer驱动定义分几个步骤,如下所述。
1、定义输入源选择
/* Speaker Mixer */
static const struct snd_kcontrol_new wm9713_speaker_mixer_controls[] = {
SOC_DAPM_SINGLE("Beep Playback Switch", AC97_AUX, 11, 1, 1),
SOC_DAPM_SINGLE("Voice Playback Switch", AC97_PCM, 11, 1, 1),
SOC_DAPM_SINGLE("Aux Playback Switch", AC97_REC_SEL, 11, 1, 1),
SOC_DAPM_SINGLE("PCM Playback Switch", AC97_PHONE, 14, 1, 1),
SOC_DAPM_SINGLE("MonoIn Playback Switch", AC97_MASTER_TONE, 14, 1, 1),
SOC_DAPM_SINGLE("Bypass Playback Switch", AC97_PC_BEEP, 14, 1, 1),
};
2、定义mixer
static const struct snd_soc_dapm_widget wm9713_dapm_widgets[] = {
……
SND_SOC_DAPM_MIXER("Speaker Mixer", AC97_EXTENDED_MID, 1, 1,
&wm9713_speaker_mixer_controls[0],
ARRAY_SIZE(wm9713_speaker_mixer_controls)),
……
};
3.3.2、mux定义
Mux控件也是多路输入,单路输出的结构,但与mixer不同的是,每次只能选择一路作为输出,相当于是单选输出。
图3.4 mux原理图
Mux驱动定义分如下几步。
1、定义输入源
static const char *wm9713_spk_pga[] =
{"Vmid", "Zh", "Headphone", "Speaker", "Inv", "Headphone Vmid",
"Speaker Vmid", "Inv Vmid"};
static const struct soc_enum wm9713_enum[] = {
……
SOC_ENUM_SINGLE(AC97_REC_GAIN, 8, 8, wm9713_spk_pga), /* speaker right input select 9 */
……
};
/* speaker right output mux */
static const struct snd_kcontrol_new wm9713_hp_spkr_mux_controls =
SOC_DAPM_ENUM("Route", wm9713_enum[9]);
2、定义mux控件
SND_SOC_DAPM_MUX("Right Speaker Out Mux", SND_SOC_NOPM, 0, 0,
&wm9713_hp_spkr_mux_controls),
3.3.3、DAC/ADC定义
有一类widget需要和stream widget连接,而且不需要在驱动里添加route连接,因为ALSA会自动为其建立连接。这类widget包括DAC/ADC,AIF IN / AIF OUT等。
SND_SOC_DAPM_DAC("Left DAC", "Left HiFi Playback", AC97_EXTENDED_MID, 7, 1),
SND_SOC_DAPM_DAC("Right DAC", "Right HiFi Playback", AC97_EXTENDED_MID, 6, 1),
SND_SOC_DAPM_ADC("Left HiFi ADC", "Left HiFi Capture", SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_ADC("Right HiFi ADC", "Right HiFi Capture", SND_SOC_NOPM, 0, 0),
这类widget除了wname外还有sname字段需要填充,sname用来和stream widget建立匹配用的,如上面代码里的"Left HiFi Playback"等。
#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) }
声卡驱动在初始化的时候就会为每个dai创建dai widget,而widget的名字就是stream name。
static int soc_probe_component(struct snd_soc_card *card,
struct snd_soc_component *component)
{
……
for_each_component_dais(component, dai) {
ret = snd_soc_dapm_new_dai_widgets(dapm, dai);
if (ret != 0) {
dev_err(component->dev,
"Failed to create DAI widgets %d\n", ret);
goto err_probe;
}
}
……
}
具体创建工作是在snd_soc_dapm_new_dai_widgets里完成的。
int snd_soc_dapm_new_dai_widgets(struct snd_soc_dapm_context *dapm,
struct snd_soc_dai *dai)
{
struct snd_soc_dapm_widget template;
struct snd_soc_dapm_widget *w;
WARN_ON(dapm->dev != dai->dev);
memset(&template, 0, sizeof(template));
template.reg = SND_SOC_NOPM;
if (dai->driver->playback.stream_name) {
template.id = snd_soc_dapm_dai_in;
template.name = dai->driver->playback.stream_name;
template.sname = dai->driver->playback.stream_name;
dev_dbg(dai->dev, "ASoC: adding %s widget\n",
template.name);
w = snd_soc_dapm_new_control_unlocked(dapm, &template);
if (IS_ERR(w))
return PTR_ERR(w);
w->priv = dai;
dai->playback_widget = w;
}
if (dai->driver->capture.stream_name) {
template.id = snd_soc_dapm_dai_out;
template.name = dai->driver->capture.stream_name;
template.sname = dai->driver->capture.stream_name;
dev_dbg(dai->dev, "ASoC: adding %s widget\n",
template.name);
w = snd_soc_dapm_new_control_unlocked(dapm, &template);
if (IS_ERR(w))
return PTR_ERR(w);
w->priv = dai;
dai->capture_widget = w;
}
return 0;
}
而建立连接是在snd_soc_dapm_link_dai_widgets里完成的。
int snd_soc_dapm_link_dai_widgets(struct snd_soc_card *card)
{
struct snd_soc_dapm_widget *dai_w, *w;
struct snd_soc_dapm_widget *src, *sink;
struct snd_soc_dai *dai;
/* For each DAI widget... */
for_each_card_widgets(card, dai_w) {
switch (dai_w->id) {
case snd_soc_dapm_dai_in:
case snd_soc_dapm_dai_out:
break;
default:
continue;
}
/* let users know there is no DAI to link */
if (!dai_w->priv) {
dev_dbg(card->dev, "dai widget %s has no DAI\n",
dai_w->name);
continue;
}
dai = dai_w->priv;
/* ...find all widgets with the same stream and link them */
for_each_card_widgets(card, w) {
if (w->dapm != dai_w->dapm)
continue;
switch (w->id) {
case snd_soc_dapm_dai_in:
case snd_soc_dapm_dai_out:
continue;
default:
break;
}
if (!w->sname || !strstr(w->sname, dai_w->sname))
continue;
if (dai_w->id == snd_soc_dapm_dai_in) {
src = dai_w;
sink = w;
} else {
src = w;
sink = dai_w;
}
dev_dbg(dai->dev, "%s -> %s\n", src->name, sink->name);
snd_soc_dapm_add_path(w->dapm, src, sink, NULL, NULL);
}
}
return 0;
}
看上面只有w->sname包括dai_w->sname才会连接成功,因此只有DAC/ADC, AIF IN / AIF OUT才能和dai in / dai out建立连接。
连接成功后能感知音频事件,
static void soc_dapm_dai_stream_event(struct snd_soc_dai *dai, int stream,
int event)
{
struct snd_soc_dapm_widget *w;
unsigned int ep;
w = snd_soc_dai_get_widget(dai, stream);
if (w) {
dapm_mark_dirty(w, "stream event");
if (w->id == snd_soc_dapm_dai_in) {
ep = SND_SOC_DAPM_EP_SOURCE;
dapm_widget_invalidate_input_paths(w);
} else {
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;
}
}
}
起播时的触发流程。
soc_pcm_prepare
snd_soc_dapm_stream_event(SND_SOC_DAPM_STREAM_START)
soc_dapm_stream_event
soc_dapm_dai_stream_event
dapm_power_widgets
结束时的触发流程。
soc_pcm_close
soc_pcm_clean
snd_soc_dapm_stream_stop
snd_soc_dapm_stream_event(SND_SOC_DAPM_STREAM_STOP)
soc_dapm_stream_event
soc_dapm_dai_stream_event
dapm_power_widgets
3.4、route定义
定义了mixer, mux及其它widget后需要定义route结构将它们连接起来,否则ALSA并不知道这些控件是如何连接的。Route结构体定义如下。
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;
};
Sink,source是要连接的控件名字,control是连接开关的名字,一条完整的route如图3.5所示。
图3.5 一条完整的端到端输出
对应的route定义如下。
static const struct snd_soc_dapm_route wm9713_audio_map[] = {
{"Speaker Mixer", "Aux Playback Switch", "Aux DAC"},
……
{"Right Speaker Out Mux", "Speaker", "Speaker Mixer"},
……
{"Right Speaker", NULL, "Right Speaker Out Mux"},
{"SPKR", NULL, "Right Speaker"},
};
♚ 余事勿取✨: tui
李卤蛋: 请问同时注册两个声卡怎么操作
baidu_40709951: 博主写的哈详细,强推