linux-alsa详解8之DAPM详解1kcontrol
DAPM是Dynamic Audio Power Management的缩写,直译过来就是动态音频电源管理的意思,DAPM是为了使基于linux的移动设备上的音频子系统,在任何时候都工作在最小功耗状态下。DAPM对用户空间的应用程序来说是透明的,所有与电源相关的开关都在ASoc core中完成。用户空间的应用程序无需对代码做出修改,也无需重新编译,DAPM根据当前激活的音频流(playback/capture)和声卡中的mixer等的配置来决定那些音频控件的电源开关被打开或关闭。
DAPM控件是由普通的soc音频控件演变而来的,所以本章的内容我们先从普通的soc音频控件开始。
1 结构体snd_kcontrol_new
定义位于:linux-4.9.73\include\sound\control.h
在正式讨论DAPM之前,我们需要先搞清楚ASoc中的一个重要的概念:Control设备的创建,之前已讲解。通常,一个kcontrol代表着一个mixer(混音器),或者是一个mux(多路开关),又或者是一个音量控制器等等。 从之前知识中我们知道,定义一个kcontrol主要就是定义一个snd_kcontrol_new结构,为了方便讨论,这里再次给出它的定义:
1 struct snd_kcontrol_new { 2 snd_ctl_elem_iface_t iface; /* interface identifier */ 3 unsigned int device; /* device/client number */ 4 unsigned int subdevice; /* subdevice (substream) number */ 5 const unsigned char *name; /* ASCII name of item */ 6 unsigned int index; /* index of item */ 7 unsigned int access; /* access rights */ 8 unsigned int count; /* count of same elements */ 9 snd_kcontrol_info_t *info; 10 snd_kcontrol_get_t *get; 11 snd_kcontrol_put_t *put; 12 union { 13 snd_kcontrol_tlv_rw_t *c; 14 const unsigned int *p; 15 } tlv; 16 unsigned long private_value; 17 }
我们知道,对于每个控件,我们需要定义一个和他对应的snd_kcontrol_new结构,这些snd_kcontrol_new结构会在声卡的初始化阶段,通过snd_soc_add_codec_controls函数注册到系统中,用户空间就可以通过amixer或alsamixer等工具查看和设定这些控件的状态。
snd_kcontrol_new结构中,几个主要的字段是get,put,private_value,get回调函数用于获取该控件当前的状态值,而put回调函数则用于设置控件的状态值,而private_value字段则根据不同的控件类型有不同的意义,比如对于普通的控件,private_value字段可以用来定义该控件所对应的寄存器的地址以及对应的控制位在寄存器中的位置信息。值得庆幸的是,ASoc系统已经为我们准备了大量的宏定义,用于定义常用的控件,这些宏定义位于include/sound/soc.h中。下面我们分别讨论一下如何用这些预设的宏定义来定义一些常用的控件。
2 简单的控件SOC_SINGLE
SOC_SINGLE应该算是最简单的控件了,这种控件只有一个控制量,比如一个开关,或者是一个数值变量(比如Codec中某个频率,FIFO大小等等)。我们看看这个宏是如何定义的:
1 #define SOC_SINGLE(xname, reg, shift, max, invert) \ 2 { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ 3 .info = snd_soc_info_volsw, .get = snd_soc_get_volsw,\ 4 .put = snd_soc_put_volsw, \ 5 .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert, 0) }
宏定义的参数分别是:xname(该控件的名字),reg(该控件对应的寄存器的地址),shift(控制位在寄存器中的位移),max(控件可设置的最大值),invert(设定值是否逻辑取反)。这里又使用了一个宏来定义private_value字段:SOC_SINGLE_VALUE,我们看看它的定义:
1 #define SOC_SINGLE_VALUE(xreg, xshift, xmax, xinvert, xautodisable) \ 2 SOC_DOUBLE_VALUE(xreg, xshift, xshift, xmax, xinvert, xautodisable) 3 4 #define SOC_DOUBLE_VALUE(xreg, shift_left, shift_right, xmax, xinvert, xautodisable) \ 5 ((unsigned long)&(struct soc_mixer_control) \ 6 {.reg = xreg, .rreg = xreg, .shift = shift_left, \ 7 .rshift = shift_right, .max = xmax, .platform_max = xmax, \ 8 .invert = xinvert, .autodisable = xautodisable})
这里实际上是定义了一个soc_mixer_control结构,然后把该结构的地址赋值给了private_value字段,soc_mixer_control结构是这样的:
1 /* mixer control */ 2 struct soc_mixer_control { 3 int min, max, platform_max; 4 int reg, rreg; 5 unsigned int shift, rshift; 6 unsigned int sign_bit; 7 unsigned int invert:1; 8 unsigned int autodisable:1; 9 struct snd_soc_dobj dobj; 10 };
看来soc_mixer_control是控件特征的真正描述者,它确定了该控件对应寄存器的地址,位移值,最大值和是否逻辑取反等特性,控件的put回调函数和get回调函数需要借助该结构来访问实际的寄存器。我们看看这get回调函数的定义: 定义位于:sound\soc\soc-ops.c
1 int snd_soc_get_volsw(struct snd_kcontrol *kcontrol, 2 struct snd_ctl_elem_value *ucontrol) 3 { 4 struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); 5 struct soc_mixer_control *mc = 6 (struct soc_mixer_control *)kcontrol->private_value; 7 unsigned int reg = mc->reg; 8 unsigned int reg2 = mc->rreg; 9 unsigned int shift = mc->shift; 10 unsigned int rshift = mc->rshift; 11 int max = mc->max; 12 int min = mc->min; 13 int sign_bit = mc->sign_bit; 14 unsigned int mask = (1 << fls(max)) - 1; 15 unsigned int invert = mc->invert; 16 int val; 17 int ret; 18 19 if (sign_bit) 20 mask = BIT(sign_bit + 1) - 1; 21 22 ret = snd_soc_read_signed(component, reg, mask, shift, sign_bit, &val); 23 if (ret) 24 return ret; 25 26 ucontrol->value.integer.value[0] = val - min; 27 if (invert) 28 ucontrol->value.integer.value[0] = 29 max - ucontrol->value.integer.value[0]; 30 31 if (snd_soc_volsw_is_stereo(mc)) { 32 if (reg == reg2) 33 ret = snd_soc_read_signed(component, reg, mask, rshift, 34 sign_bit, &val); 35 else 36 ret = snd_soc_read_signed(component, reg2, mask, shift, 37 sign_bit, &val); 38 if (ret) 39 return ret; 40 41 ucontrol->value.integer.value[1] = val - min; 42 if (invert) 43 ucontrol->value.integer.value[1] = 44 max - ucontrol->value.integer.value[1]; 45 } 46 47 return 0; 48 }
上述代码一目了然,从private_value字段取出soc_mixer_control结构,利用该结构的信息,访问对应的寄存器,返回相应的值。
3 SOC_SINGLE_TLV
是SOC_SINGLE的一种扩展,主要用于定义那些有增益控制的控件,例如音量控制器,EQ均衡器等等。
1 #define SOC_SINGLE_TLV(xname, reg, shift, max, invert, tlv_array) \ 2 { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ 3 .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\ 4 SNDRV_CTL_ELEM_ACCESS_READWRITE,\ 5 .tlv.p = (tlv_array), \ 6 .info = snd_soc_info_volsw, .get = snd_soc_get_volsw,\ 7 .put = snd_soc_put_volsw, \ 8 .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert, 0) }
从他的定义可以看出,用于设定寄存器信息的private_value字段的定义和SOC_SINGLE是一样的,甚至put、get回调函数也是使用同一套,唯一不同的是增加了一个tlv_array参数,并把它赋值给了tlv.p字段。关于tlv,已经在Linux ALSA声卡驱动之四:Control设备的创建中进行了阐述。用户空间可以通过对声卡的control设备发起以下两种ioctl来访问tlv字段所指向的数组:
1 SNDRV_CTL_IOCTL_TLV_READ 2 SNDRV_CTL_IOCTL_TLV_WRITE 3 SNDRV_CTL_IOCTL_TLV_COMMAND
1 static const DECLARE_TLV_DB_SCALE(mixin_boost_tlv, 0, 900, 0); 2 3 static const struct snd_kcontrol_new wm1811_snd_controls[] = { 4 SOC_SINGLE_TLV("MIXINL IN1LP Boost Volume", WM8994_INPUT_MIXER_1, 7, 1, 0, 5 mixin_boost_tlv), 6 SOC_SINGLE_TLV("MIXINL IN1RP Boost Volume", WM8994_INPUT_MIXER_1, 8, 1, 0, 7 mixin_boost_tlv), 8 };
DECLARE_TLV_DB_SCALE用于定义一个dB值映射的tlv_array,上述的例子表明,该tlv的类型是SNDRV_CTL_TLVT_DB_SCALE,寄存器的最小值对应是0dB,寄存器每增加一个单位值,对应的dB数增加是9dB(0.01dB*900),而由接下来的两组SOC_SINGLE_TLV定义可以看出,我们定义了两个boost控件,寄存器的地址都是WM8994_INPUT_MIXER_1,控制位分别是第7bit和第8bit,最大值是1,所以,该控件只能设定两个数值0和1,对应的dB值就是0dB和9dB。
4 SOC_DOUBLE
与SOC_SINGLE相对应,区别是SOC_SINGLE只控制一个变量,而SOC_DOUBLE则可以同时在一个寄存器中控制两个相似的变量,最常用的就是用于一些立体声的控件,我们需要同时对左右声道进行控制,因为多了一个声道,参数也就相应地多了一个shift位移值
1 #define SOC_DOUBLE(xname, reg, shift_left, shift_right, max, invert) \ 2 { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname),\ 3 .info = snd_soc_info_volsw, .get = snd_soc_get_volsw, \ 4 .put = snd_soc_put_volsw, \ 5 .private_value = SOC_DOUBLE_VALUE(reg, shift_left, shift_right, \ 6 max, invert, 0) }
SOC_DOUBLE_R :与SOC_DOUBLE类似,对于左右声道的控制寄存器不一样的情况,使用SOC_DOUBLE_R来定义,参数中需要指定两个寄存器地址。
SOC_DOUBLE_TLV : 与SOC_SINGLE_TLV对应的立体声版本,通常用于立体声音量控件的定义。
SOC_DOUBLE_R_TLV: 左右声道有独立寄存器控制的SOC_DOUBLE_TLV版本。
5 Mixer控件
用于音频通道的路由控制,由多个输入和一个输出组成,多个输入可以自由地混合在一起,形成混合后的输出:
对于Mixer控件,我们可以认为是多个简单控件的组合,通常,我们会为mixer的每个输入端都单独定义一个简单控件来控制该路输入的开启和关闭,反应在代码上,就是定义一个soc_kcontrol_new数组:
1 static const struct snd_kcontrol_new left_speaker_mixer[] = { 2 SOC_SINGLE("Input Switch", WM8993_SPEAKER_MIXER, 7, 1, 0), 3 SOC_SINGLE("IN1LP Switch", WM8993_SPEAKER_MIXER, 5, 1, 0), 4 SOC_SINGLE("Output Switch", WM8993_SPEAKER_MIXER, 3, 1, 0), 5 SOC_SINGLE("DAC Switch", WM8993_SPEAKER_MIXER, 6, 1, 0), 6 };
以上这个mixer使用寄存器WM8993_SPEAKER_MIXER的第3,5,6,7位来分别控制4个输入端的开启和关闭。
6 mux控件
与mixer控件类似,也是多个输入端和一个输出端的组合控件,与mixer控件不同的是,mux控件的多个输入端同时只能有一个被选中。因此,mux控件所对应的寄存器,通常可以设定一段连续的数值,每个不同的数值对应不同的输入端被打开,与上述的mixer控件不同,ASoc用soc_enum结构来描述mux控件的寄存器信息:
1 /* enumerated kcontrol */ 2 struct soc_enum { 3 int reg; 4 unsigned char shift_l; 5 unsigned char shift_r; 6 unsigned int items; 7 unsigned int mask; 8 const char * const *texts; 9 const unsigned int *values; 10 unsigned int autodisable:1; 11 struct snd_soc_dobj dobj; 12 };
两个寄存器地址和位移字段:reg,reg2,shift_l,shift_r,用于描述左右声道的控制寄存器信息。字符串数组指针用于描述每个输入端对应的名字,value字段则指向一个数组,该数组定义了寄存器可以选择的值,每个值对应一个输入端,如果value是一组连续的值,通常我们可以忽略values参数。下面我们先看看如何定义一个mux控件:
第一步,定义字符串和values数组,以下的例子因为values是连续的,所以不用定义:
1 static const char *drc_path_text[] = { 2 "ADC", 3 "DAC" 4 };
第二步,利用ASoc提供的辅助宏定义soc_enum结构,用于描述寄存器:
1 static const struct soc_enum drc_path = 2 SOC_ENUM_SINGLE(WM8993_DRC_CONTROL_1, 14, 2, drc_path_text);
第三步,利痛ASoc提供的辅助宏,定义soc_kcontrol_new结构,该结构最后用于注册该mux控件:
1 static const struct snd_kcontrol_new wm8993_snd_controls[] = { 2 SOC_DOUBLE_TLV(......), 3 ...... 4 SOC_ENUM("DRC Path", drc_path), 5 ...... 6 }
以上几步定义了一个叫DRC PATH的mux控件,该控件具有两个输入选择,分别是来自ADC和DAC,用寄存器WM8993_DRC_CONTROL_1控制。其中,soc_enum结构使用了辅助宏SOC_ENUM_SINGLE来定义,该宏的声明如下:
1 #define SOC_ENUM_DOUBLE(xreg, xshift_l, xshift_r, xmax, xtexts) \ 2 { .reg = xreg, .shift_l = xshift_l, .shift_r = xshift_r, \ 3 .max = xmax, .texts = xtexts, \ 4 .mask = xmax ? roundup_pow_of_two(xmax) - 1 : 0} 5 #define SOC_ENUM_SINGLE(xreg, xshift, xmax, xtexts) \ 6 SOC_ENUM_DOUBLE(xreg, xshift, xshift, xmax, xtexts)
定义soc_kcontrol_new结构时使用了SOC_ENUM,列出它的定义如下:
1 #define SOC_ENUM(xname, xenum) \ 2 { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname,\ 3 .info = snd_soc_info_enum_double, \ 4 .get = snd_soc_get_enum_double, .put = snd_soc_put_enum_double, \ 5 .private_value = (unsigned long)&xenum }
思想如此统一,依然是使用private_value字段记录soc_enum结构,不过几个回调函数变了,我们看看get回调对应的snd_soc_get_enum_double函数:
1 int snd_soc_get_enum_double(struct snd_kcontrol *kcontrol, 2 struct snd_ctl_elem_value *ucontrol) 3 { 4 struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); 5 struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; 6 unsigned int val; 7 8 val = snd_soc_read(codec, e->reg); 9 ucontrol->value.enumerated.item[0] 10 = (val >> e->shift_l) & e->mask; 11 if (e->shift_l != e->shift_r) 12 ucontrol->value.enumerated.item[1] = 13 (val >> e->shift_r) & e->mask; 14 15 return 0; 16 }
通过info回调函数则可以获取某个输入端所对应的名字,其实就是从soc_enum结构的texts数组中获得:
1 int snd_soc_info_enum_double(struct snd_kcontrol *kcontrol, 2 struct snd_ctl_elem_info *uinfo) 3 { 4 struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; 5 6 uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; 7 uinfo->count = e->shift_l == e->shift_r ? 1 : 2; 8 uinfo->value.enumerated.items = e->max; 9 10 if (uinfo->value.enumerated.item > e->max - 1) 11 uinfo->value.enumerated.item = e->max - 1; 12 strcpy(uinfo->value.enumerated.name, 13 e->texts[uinfo->value.enumerated.item]); 14 return 0; 15 }
以下是另外几个常用于定义mux控件的宏:
SOC_VALUE_ENUM_SINGLE 用于定义带values字段的soc_enum结构。
SOC_VALUE_ENUM_DOUBLE SOC_VALUE_ENUM_SINGLE的立体声版本。
SOC_VALUE_ENUM 用于定义带values字段snd_kcontrol_new结构,这个有点特别,我们还是看看它的定义:
1 #define SOC_VALUE_ENUM(xname, xenum) \ 2 { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname,\ 3 .info = snd_soc_info_enum_double, \ 4 .get = snd_soc_get_value_enum_double, \ 5 .put = snd_soc_put_value_enum_double, \ 6 .private_value = (unsigned long)&xenum }
从定义可以看出来,回调函数被换掉了,我们看看他的get回调:
1 int snd_soc_get_value_enum_double(struct snd_kcontrol *kcontrol, 2 struct snd_ctl_elem_value *ucontrol) 3 { 4 struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); 5 struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; 6 unsigned int reg_val, val, mux; 7 8 reg_val = snd_soc_read(codec, e->reg); 9 val = (reg_val >> e->shift_l) & e->mask; 10 for (mux = 0; mux < e->max; mux++) { 11 if (val == e->values[mux]) 12 break; 13 } 14 ucontrol->value.enumerated.item[0] = mux; 15 if (e->shift_l != e->shift_r) { 16 val = (reg_val >> e->shift_r) & e->mask; 17 for (mux = 0; mux < e->max; mux++) { 18 if (val == e->values[mux]) 19 break; 20 } 21 ucontrol->value.enumerated.item[1] = mux; 22 } 23 24 return 0; 25 }
与SOC_ENUM定义的mux不同,它没有直接返回寄存器的设定值,而是通过soc_enum结构中的values字段做了一次转换,与values数组中查找和寄存器相等的值,然后返回他在values数组中的索引值,所以,尽管寄存器的值可能是不连续的,但返回的值是连续的。
通常,我们还可以用以下几个辅助宏定义soc_enum结构,其实和上面所说的没什么区别,只是可以偷一下懒,省掉struct soc_enum xxxx=几个单词而已:
1 SOC_ENUM_SINGLE_DECL 2 SOC_ENUM_DOUBLE_DECL 3 SOC_VALUE_ENUM_SINGLE_DECL 4 SOC_VALUE_ENUM_DOUBLE_DECL
7 其他控件
其实,除了以上介绍的几种常用的控件,ASoc还为我们提供了另外一些控件定义辅助宏,详细的请读者参考include/sound/soc.h。这里列举几个:
需要自己定义get和put回调时,可以使用以下这些带EXT的版本:
1 SOC_SINGLE_EXT 2 SOC_DOUBLE_EXT 3 SOC_SINGLE_EXT_TLV 4 SOC_DOUBLE_EXT_TLV 5 SOC_DOUBLE_R_EXT_TLV 6 SOC_ENUM_EXT
参考博文:https://blog.csdn.net/DroidPhone/java/article/details/12793293