[Alsa]5、dapm 的 kcontrol
在看本文之前建议先仔细看一下[Alsa Document]5, dapm.txt
本文大量借鉴droidphone大神的博客,并使之适应于4.x版本的Alsa,特此声明。
Linux 4.9.123 可从以下地址获得
https://mirrors.edge.kernel.org/pub/linux/kernel/v4.x/
本文Codec基于wm8978。
之前介绍Alsa各部分流程的时候是用wm8524来举例,但是渐渐的我发现wm8524太简单了,所以现在转到wm8978来继续做介绍,当我们打开sound/soc/codecs/wm8978.c
,会在开头看到几个结构体:static const struct snd_kcontrol_new wm8978_snd_controls[]
static const struct snd_kcontrol_new wm8978_left_out_mixer[]
static const struct snd_kcontrol_new wm8978_right_out_mixer[]
static const struct snd_kcontrol_new wm8978_left_input_mixer[]
static const struct snd_kcontrol_new wm8978_right_input_mixer[]
static const struct snd_soc_dapm_widget wm8978_dapm_widgets[]
static const struct snd_soc_dapm_route wm8978_dapm_routes[]
这么多其实就是三种struct的实例化struct snd_kcontrol_new
、struct snd_soc_dapm_widget
和struct snd_soc_dapm_route
,本篇主要介绍kcontrol。
1,snd_kcontrol_new
Control接口主要让用户空间的应用程序(alsa-lib)可以访问和控制音频codec芯片中的多路开关,滑动控件等。对于Mixer(混音)来说,Control接口显得尤为重要,从ALSA 0.9.x版本开始,所有的mixer工作都是通过control接口的API来实现的。
通常,一个kcontrol代表着一个mixer(混音器),或者是一个mux(多路开关),又或者是一个音量控制器等等。 从上述文章中我们知道,定义一个kcontrol主要就是定义一个snd_kcontrol_new结构,它能被用户空间存取,从而读写CODEC相关寄存器。其定义如下:
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 unsigned 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;
};
其中几个成员需要了解一下,这会在接下来的SOC_SINGLE,SOC_DOUBLE中看到:
iface :指出了control的类型,alsa定义了几种类型(SNDDRV_CTL_ELEM_IFACE_XXX),常用的类型是MIXER,当然也可以定义属于全局的CARD类型,也可以定义属于某类设备的类型,例如HWDEP,PCMRAWMIDI,TIMER等,这时需要在device和subdevice字段中指出卡的设备逻辑编号。
name :该control的名字,从ALSA 0.9.x开始(现在是1.1.8),control的名字是变得比较重要,因为control的作用是按名字来归类的。ALSA已经预定义了一些control的名字。name定义的标准是“SOURCE DIRECTION FUNCTION”即“源 方向 功能”,SOURCE定义了control的源,如“Master”、“PCM”等;DIRECTION 则为“Playback”、“Capture”等,如果DIRECTION忽略,意味着Playback和Capture双向;FUNCTION则可以是“Switch”、“Volume”和“Route”等。
access :access字段包含了该control的访问类型。每一个bit代表一种访问类型,这些访问类型可以多个“或”运算组合在一起。
info :info回调函数用于获取control的详细信息。它的主要工作就是填充通过参数传入的snd_ctl_elem_info对象。
get:get回调函数用于读取control的当前值,并返回给用户空间的应用程序。
put:put回调函数用于把应用程序的控制值设置到control中。
private_value :包含了一个任意的长整数类型值。该值可以通过info,get,put这几个回调函数访问。你可以自己决定如何使用该字段,例如可以把它拆分成多个位域,又或者是一个指针,指向某一个数据结构。
tlv.p:该control提供元数据。很多mixer control需要提供以dB为单位的信息,我们可以使用DECLARE_TLV_xxx宏来定义一些包含这种信息的变量,然后把control的tlv.p字段指向这些变量,最后,在access字段中加上SNDRV_CTL_ELEM_ACCESS_TLV_READ标志。
再回过头来看,snd_kcontrol_new
初始化的上面这几个结构体,里面填充的和struct snd_soc_dapm_widget
和struct snd_soc_dapm_route
样式来看都不一样,里面充斥着大量宏定义,SOC_SINGLE
,SOC_SINGLE_TLV
,SOC_DOUBLE
,SOC_ENUM
,SOC_DOUBLE_R
,SOC_DOUBLE_R_TLV
,SOC_DAPM_SINGLE
,接下来详细介绍下这几个宏:
1.1 SOC_SINGLE
include/sound/soc.h
#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) }
这应该算是最简单的控件了,这种控件只有一个控制量,比如一个开关,或者是一个数值变量(比如Codec中某个频率,FIFO大小等等)。里面又用到了SOC_SINGLE_VALUE:
#define SOC_SINGLE_VALUE(xreg, xshift, xmax, xinvert, xautodisable) \
SOC_DOUBLE_VALUE(xreg, xshift, xshift, xmax, xinvert, xautodisable)
#define SOC_DOUBLE_VALUE(xreg, shift_left, shift_right, xmax, xinvert, xautodisable) \
((unsigned long)&(struct soc_mixer_control) \
{.reg = xreg, .rreg = xreg, .shift = shift_left, \
.rshift = shift_right, .max = xmax, .platform_max = xmax, \
.invert = xinvert, .autodisable = xautodisable})
实际上是定义了一个soc_mixer_control结构,然后把该结构的地址赋值给了private_value字段,soc_mixer_control结构是这样的:
struct soc_mixer_control {
int min, max, platform_max;
int reg, rreg;
unsigned int shift, rshift;
unsigned int sign_bit;
unsigned int invert:1;
unsigned int autodisable:1;
struct snd_soc_dobj dobj;
};
看来soc_mixer_control是控件特征的真正描述者,它确定了该控件对应寄存器的地址(xreg),位移值(xshift),最大值(xmax),是否逻辑取反(xinvert)和自动失能(xautodisable)等特性,控件的put回调函数和get回调函数需要借助该结构来访问实际的寄存器。
1.2.1 snd_soc_info_volsw
sound/soc/soc-ops.c
/**
* snd_soc_info_volsw - single mixer info callback
* @kcontrol: mixer control
* @uinfo: control element information
*
* Callback to provide information about a single mixer control, or a double
* mixer control that spans 2 registers.
*
* Returns 0 for success.
*/
int snd_soc_info_volsw(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
int platform_max;
if (!mc->platform_max)
mc->platform_max = mc->max;
platform_max = mc->platform_max;
if (platform_max == 1 && !strstr(kcontrol->id.name, " Volume"))
uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
else
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = snd_soc_volsw_is_stereo(mc) ? 2 : 1;// 如果是右移
uinfo->value.integer.min = 0;
uinfo->value.integer.max = platform_max - mc->min;
return 0;
}
EXPORT_SYMBOL_GPL(snd_soc_info_volsw);
上面的代码比较清晰,从kcontrol的private_value中取出max、min等给uinfo,来完成接下来的操作。
1.2.2 snd_soc_get_volsw
sound/soc/soc-ops.c
/**
* snd_soc_get_volsw - single mixer get callback
* @kcontrol: mixer control
* @ucontrol: control element information
*
* Callback to get the value of a single mixer control, or a double mixer
* control that spans 2 registers.
*
* Returns 0 for success.
*/
int snd_soc_get_volsw(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value;
unsigned int reg = mc->reg;
unsigned int reg2 = mc->rreg;
unsigned int shift = mc->shift;
unsigned int rshift = mc->rshift;
int max = mc->max;
int min = mc->min;
int sign_bit = mc->sign_bit;
unsigned int mask = (1 << fls(max)) - 1;
unsigned int invert = mc->invert;
int val;
int ret;
if (sign_bit)
mask = BIT(sign_bit + 1) - 1;
/**
* snd_soc_read_signed - Read a codec register and interprete as signed value
* @component: component
* @reg: Register to read
* @mask: Mask to use after shifting the register value
* @shift: Right shift of register value
* @sign_bit: Bit that describes if a number is negative or not.
* @signed_val: Pointer to where the read value should be stored
*
* This functions reads a codec register. The register value is shifted right
* by 'shift' bits and masked with the given 'mask'. Afterwards it translates
* the given registervalue into a signed integer if sign_bit is non-zero.
*
* Returns 0 on sucess, otherwise an error value
*/
ret = snd_soc_read_signed(component, reg, mask, shift, sign_bit, &val);// 读寄存器的整型值
if (ret)
return ret;
ucontrol->value.integer.value[0] = val - min;
if (invert)
ucontrol->value.integer.value[0] =
max - ucontrol->value.integer.value[0];
if (snd_soc_volsw_is_stereo(mc)) {// 判断是不是右移(立体声???,见1.3)
if (reg == reg2)
ret = snd_soc_read_signed(component, reg, mask, rshift,
sign_bit, &val);// 读寄存器的整型值
else
ret = snd_soc_read_signed(component, reg2, mask, shift,
sign_bit, &val);// 读寄存器的整型值
if (ret)
return ret;
ucontrol->value.integer.value[1] = val - min;
if (invert)// 是否反转
ucontrol->value.integer.value[1] =
max - ucontrol->value.integer.value[1];
}
return 0;
}
EXPORT_SYMBOL_GPL(snd_soc_get_volsw);
get把private_value的成员基本都取出来,snd_soc_read_signed读寄存器的整型值。
1.2.3 snd_soc_put_volsw
sound/soc/soc-ops.c
/**
* snd_soc_put_volsw - single mixer put callback
* @kcontrol: mixer control
* @ucontrol: control element information
*
* Callback to set the value of a single mixer control, or a double mixer
* control that spans 2 registers.
*
* Returns 0 for success.
*/
int snd_soc_put_volsw(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
unsigned int reg = mc->reg;
unsigned int reg2 = mc->rreg;
unsigned int shift = mc->shift;
unsigned int rshift = mc->rshift;
int max = mc->max;
int min = mc->min;
unsigned int sign_bit = mc->sign_bit;
unsigned int mask = (1 << fls(max)) - 1;
unsigned int invert = mc->invert;
int err;
bool type_2r = false;
unsigned int val2 = 0;
unsigned int val, val_mask;
if (sign_bit)
mask = BIT(sign_bit + 1) - 1;
val = ((ucontrol->value.integer.value[0] + min) & mask);
if (invert)
val = max - val;
val_mask = mask << shift;
val = val << shift;
if (snd_soc_volsw_is_stereo(mc)) {
val2 = ((ucontrol->value.integer.value[1] + min) & mask);
if (invert)
val2 = max - val2;
if (reg == reg2) {
val_mask |= mask << rshift;
val |= val2 << rshift;
} else {
val2 = val2 << shift;
type_2r = true;
}
}
err = snd_soc_component_update_bits(component, reg, val_mask, val);//写寄存器reg
if (err < 0)
return err;
if (type_2r)
err = snd_soc_component_update_bits(component, reg2, val_mask,//写寄存器reg
val2);
return err;
}
EXPORT_SYMBOL_GPL(snd_soc_put_volsw);
这个函数正好和get回调反过来,get是根据指定的左移或右移,根据sign_bit设置mask,然后读寄存器。而put则是写寄存器。
1.3 SOC_SINGLE_TLV
include/sound/soc.h
#define SOC_SINGLE_TLV(xname, reg, shift, max, invert, tlv_array) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
.access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\
SNDRV_CTL_ELEM_ACCESS_READWRITE,\
.tlv.p = (tlv_array), \
.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) }
SOC_SINGLE_TLV是SOC_SINGLE的一种扩展,主要用于定义那些有增益控制的控件,例如音量控制器,EQ均衡器等等。
从他的定义可以看出,用于设定寄存器信息的private_value字段的定义和SOC_SINGLE是一样的,甚至put、get回调函数也是使用同一套,唯一不同的是增加了一个tlv_array参数,并把它赋值给了tlv.p字段。关于tlv,已经在Linux ALSA声卡驱动之四:Control设备的创建中进行了阐述。用户空间可以通过对声卡的control设备发起以下两种ioctl来访问tlv字段所指向的数组:
- SNDRV_CTL_IOCTL_TLV_READ
- SNDRV_CTL_IOCTL_TLV_WRITE
- SNDRV_CTL_IOCTL_TLV_COMMAND
通常,tlv_array用来描述寄存器的设定值与它所代表的实际意义之间的映射关系,最常用的就是用于音量控件时,设定值与对应的dB值之间的映射关系,如
static const DECLARE_TLV_DB_SCALE(mixin_boost_tlv, 0, 900, 0);
DECLARE_TLV_DB_SCALE用于定义一个dB值映射的tlv_array,上述的例子表明,该tlv的类型是SNDRV_CTL_TLVT_DB_SCALE,寄存器的最小值对应是0dB,寄存器每增加一个单位值,对应的dB数增加是9dB(0.01dB*900)。
static const struct snd_kcontrol_new wm1811_snd_controls[] = {
SOC_SINGLE_TLV("MIXINL IN1LP Boost Volume", WM8994_INPUT_MIXER_1, 7, 1, 0,
mixin_boost_tlv),
SOC_SINGLE_TLV("MIXINL IN1RP Boost Volume", WM8994_INPUT_MIXER_1, 8, 1, 0,
mixin_boost_tlv),
};
SOC_SINGLE_TLV定义可以看出,我们定义了两个boost控件,寄存器的地址都是WM8994_INPUT_MIXER_1,控制位分别是第7bit和第8bit,最大值是1,所以,该控件只能设定两个数值0和1,对应的dB值就是0dB和9dB,不反转。
基本都是对音量进行描述。
1.3 SOC_DOUBLE
include/sound/soc.h
#define SOC_DOUBLE(xname, reg, shift_left, shift_right, 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_DOUBLE_VALUE(reg, shift_left, shift_right, \
max, invert, 0)
}
与SOC_SINGLE相对应,区别是SOC_SINGLE只控制一个变量,而SOC_DOUBLE则可以同时在一个寄存器中控制两个相似的变量,最常用的就是用于一些立体声的控件,我们需要同时对左右声道进行控制,因为多了一个声道,参数也就相应地多了一个shift位移值。
-
SOC_DOUBLE_R 与SOC_DOUBLE类似,对于左右声道的控制寄存器不一样的情况,使用SOC_DOUBLE_R来定义,参数中需要指定两个寄存器地址。
-
SOC_DOUBLE_TLV 与SOC_SINGLE_TLV对应的立体声版本,通常用于立体声音量控件的定义。
-
SOC_DOUBLE_R_TLV 左右声道有独立寄存器控制的SOC_DOUBLE_TLV版本
1.4 SOC_ENUM_SINGLE 和 SOC_ENUM
include/sound/soc.h
#define SOC_ENUM_DOUBLE(xreg, xshift_l, xshift_r, xitems, xtexts) \
{ .reg = xreg, .shift_l = xshift_l, .shift_r = xshift_r, \
.items = xitems, .texts = xtexts, \
.mask = xitems ? roundup_pow_of_two(xitems) - 1 : 0}
#define SOC_ENUM_SINGLE(xreg, xshift, xitems, xtexts) \
SOC_ENUM_DOUBLE(xreg, xshift, xshift, xitems, xtexts)
#define SOC_ENUM(xname, xenum) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname,\
.info = snd_soc_info_enum_double, \
.get = snd_soc_get_enum_double, .put = snd_soc_put_enum_double, \
.private_value = (unsigned long)&xenum }
和前面SOC_SINGLE类似的,不过是info 、get、put 几个回调函数变了而已。
1.4.1 Mux控件
mux控件是多个输入端和一个输出端的组合控件,但多个输入端同时只能有一个被选中。因此,mux控件所对应的寄存器,通常可以设定一段连续的数值,每个不同的数值对应不同的输入端被打开,与上述的mixer控件不同,ASoc用soc_enum结构来描述mux控件的寄存器信息:
/* enumerated kcontrol */
struct soc_enum {
int reg;
unsigned char shift_l;
unsigned char shift_r;
unsigned int items;
unsigned int mask;
const char * const *texts;
const unsigned int *values;
unsigned int autodisable:1;
struct snd_soc_dobj dobj;
};
两个寄存器地址和位移字段:reg,reg2,shift_l,shift_r,用于描述左右声道的控制寄存器信息。字符串数组指针用于描述每个输入端对应的名字,value字段则指向一个数组,该数组定义了寄存器可以选择的值,每个值对应一个输入端,如果value是一组连续的值,通常我们可以忽略values参数。
下面我们先看看如何定义一个mux控件:
第一步,定义字符串和values数组,以下的例子因为values是连续的,所以不用定义:
static const char *wm8978_eqmode[] = {
"Capture",
"Playback"
};
第二步,利用ASoC提供的辅助宏定义SOC_ENUM_SINGLE_DECL结构,用于描述寄存器:
static SOC_ENUM_SINGLE_DECL(eqmode, WM8978_EQ1, 8, wm8978_eqmode);
第三步,利用ASoC提供的辅助宏,定义soc_kcontrol_new结构,该结构最后用于注册该mux控件:
static const struct snd_kcontrol_new wm8978_snd_controls[] = {
SOC_SINGLE("Digital Loopback Switch", WM8978_COMPANDING_CONTROL, 0, 1, 0),
......
SOC_ENUM("Equaliser Function", eqmode),
......
}
以上几步定义了一个叫Equaliser Function的mux控件,该控件具有两个输入选择,分别是来自"Capture", “Playback”,用寄存器WM8978_EQ1控制。
大家感兴趣可以回过头去看看源码,找找是不是所有SOC_ENUM都是这个作用。
可以说不是所有的SOC_ENUM都是Mux,但是所有的Enum都是按上面三步来找到实现。
SOC_ENUM_SINGLE_DECL --> SOC_ENUM_DOUBLE_DECL --> SOC_ENUM_DOUBLE
1.4.2 Mixer 控件
源码中出现大量形如下图的控件,这就是Mixer控件:
Mixer控件用于音频通道的路由控制,由多个输入和一个输出组成,多个输入可以*地混合在一起,形成混合后的输出:
对于Mixer控件,我们可以认为是多个简单控件的组合,通常,我们会为mixer的每个输入端都单独定义一个简单控件来控制该路输入的开启和关闭,反应在代码上,就是定义一个soc_kcontrol_new结构体:
static const struct snd_kcontrol_new wm8978_left_input_mixer[] = {
SOC_DAPM_SINGLE("L2 Switch", WM8978_INPUT_CONTROL, 2, 1, 0),
SOC_DAPM_SINGLE("MicN Switch", WM8978_INPUT_CONTROL, 1, 1, 0),
SOC_DAPM_SINGLE("MicP Switch", WM8978_INPUT_CONTROL, 0, 1, 0),
};
以上这个mixer使用寄存器WM8978_INPUT_CONTROL的第0,1,2位来分别控制3个输入端的开启和关闭。
1.5 SOC_DAPM_SINGLE
include/sound/soc-dapm.h
#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) }
这个也不详细说了,回过头去看看SOC_SINGLE_VALUE就明白了。