Linux内核4.14版本——alsa框架分析(10)——DAPM(1)——kcontrol的简介和注册

本文详细介绍了ASoC(Advanced Linux Sound Architecture)音频框架中的kcontrol结构及其在创建控制设备时的作用,包括如何使用预定义的宏如SOC_SINGLE、SOC_DOUBLE_TLV等来定义常见的音频控件。此外,还讲解了DAPM(Dynamic Audio Power Management)的概念,它是ASoC中用于实现动态电源管理的部分,以降低移动设备的功耗。通过对寄存器手册的引用,解释了如何在实际的codec驱动中定义和注册kcontrol,以及它们与硬件寄存器的交互过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

1. snd_kcontrol_new结构

2. 简单型的控件

2.1 SOC_SINGLE (控制器/开关等)

2.2 SOC_SINGLE_TLV(有增益控制的控件)

2.3 SOC_DOUBLE(声道控制等)

3. Mixer控件

4. Mux控件(struct soc_enum)

5. 其它控件

6. kcontrol的注册

6.1 snd_soc_add_codec_controls注册kcontrol

6.2 snd_soc_register_codec

6.3 snd_soc_add_platform_control


      DAPM是Dynamic Audio Power Management的缩写,直译过来就是动态音频电源管理的意思,DAPM是为了使基于linux的移动设备上的音频子系统,在任何时候都工作在最小功耗状态下。DAPM对用户空间的应用程序来说是透明的,所有与电源相关的开关都在ASoc core中完成。用户空间的应用程序无需对代码做出修改,也无需重新编译,DAPM根据当前激活的音频流(playback/capture)和声卡中的mixer等的配置来决定那些音频控件的电源开关被打开或关闭

      DAPM控件是由普通的soc音频控件演变而来的,所以本章的内容我们先从普通的soc音频控件开始。

es8316的寄存器手册:ES8316 user Guide - 道客巴巴

1. snd_kcontrol_new结构

      在正式讨论DAPM之前,我们需要先搞清楚ASoc中的一个重要的概念:kcontrol,不熟悉的读者需要浏览一下我之前的文章。通常,一个kcontrol代表着一个mixer(混音器),或者是一个mux(多路开关),又或者是一个音量控制器等等。 从上述文章中我们知道,定义一个kcontrol主要就是定义一个snd_kcontrol_new结构,为了方便讨论,这里再次给出它的定义:

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

      回到Control设备的创建中,我们知道,对于每个控件,我们需要定义一个和他对应的snd_kcontrol_new结构,这些snd_kcontrol_new结构会在声卡的初始化阶段,通过snd_soc_add_codec_controls函数注册到系统中,用户空间就可以通过amixer或alsamixer等工具查看和设定这些控件的状态,以sound\soc\codecs\es8316.c为例,我还找了es8316的寄存器手册,其地址在ES8316 user Guide - 道客巴巴

static const struct snd_kcontrol_new es8316_snd_controls[] = {
	SOC_DOUBLE_TLV("Headphone Playback Volume", ES8316_CPHP_ICAL_VOL,
		       4, 0, 3, 1, hpout_vol_tlv),
	SOC_DOUBLE_TLV("Headphone Mixer Volume", ES8316_HPMIX_VOL,
		       0, 4, 11, 0, hpmixer_gain_tlv),

	SOC_ENUM("Playback Polarity", dacpol),
	SOC_DOUBLE_R_TLV("DAC Playback Volume", ES8316_DAC_VOLL,
			 ES8316_DAC_VOLR, 0, 0xc0, 1, dac_vol_tlv),
	SOC_SINGLE("DAC Soft Ramp Switch", ES8316_DAC_SET1, 4, 1, 1),
	SOC_SINGLE("DAC Soft Ramp Rate", ES8316_DAC_SET1, 2, 4, 0),
	SOC_SINGLE("DAC Notch Filter Switch", ES8316_DAC_SET2, 6, 1, 0),
	SOC_SINGLE("DAC Double Fs Switch", ES8316_DAC_SET2, 7, 1, 0),
	SOC_SINGLE("DAC Stereo Enhancement", ES8316_DAC_SET3, 0, 7, 0),

	SOC_ENUM("Capture Polarity", adcpol),
	SOC_SINGLE("Mic Boost Switch", ES8316_ADC_D2SEPGA, 0, 1, 0),
	SOC_SINGLE_TLV("ADC Capture Volume", ES8316_ADC_VOLUME,
		       0, 0xc0, 1, adc_vol_tlv),
	SOC_SINGLE_TLV("ADC PGA Gain Volume", ES8316_ADC_PGAGAIN,
		       4, 10, 0, adc_pga_gain_tlv),
	SOC_SINGLE("ADC Soft Ramp Switch", ES8316_ADC_MUTE, 4, 1, 0),
	SOC_SINGLE("ADC Double Fs Switch", ES8316_ADC_DMIC, 4, 1, 0),

	SOC_SINGLE("ALC Capture Switch", ES8316_ADC_ALC1, 6, 1, 0),
	SOC_SINGLE_TLV("ALC Capture Max Volume", ES8316_ADC_ALC1, 0, 28, 0,
		       alc_max_gain_tlv),
	SOC_SINGLE_TLV("ALC Capture Min Volume", ES8316_ADC_ALC2, 0, 28, 0,
		       alc_min_gain_tlv),
	SOC_SINGLE_TLV("ALC Capture Target Volume", ES8316_ADC_ALC3, 4, 10, 0,
		       alc_target_tlv),
	SOC_SINGLE("ALC Capture Hold Time", ES8316_ADC_ALC3, 0, 10, 0),
	SOC_SINGLE("ALC Capture Decay Time", ES8316_ADC_ALC4, 4, 10, 0),
	SOC_SINGLE("ALC Capture Attack Time", ES8316_ADC_ALC4, 0, 10, 0),
	SOC_SINGLE("ALC Capture Noise Gate Switch", ES8316_ADC_ALC_NG,
		   5, 1, 0),
	SOC_SINGLE("ALC Capture Noise Gate Threshold", ES8316_ADC_ALC_NG,
		   0, 31, 0),
	SOC_ENUM("ALC Capture Noise Gate Type", ng_type),
};

static const struct snd_soc_codec_driver soc_codec_dev_es8316 = {
	.probe		= es8316_probe,
	.idle_bias_off	= true,

	.component_driver = {
		.controls		= es8316_snd_controls,
		.num_controls		= ARRAY_SIZE(es8316_snd_controls),
		.dapm_widgets		= es8316_dapm_widgets,
		.num_dapm_widgets	= ARRAY_SIZE(es8316_dapm_widgets),
		.dapm_routes		= es8316_dapm_routes,
		.num_dapm_routes	= ARRAY_SIZE(es8316_dapm_routes),
	},
};

static int es8316_i2c_probe(struct i2c_client *i2c_client,
			    const struct i2c_device_id *id)
{
	struct es8316_priv *es8316;
	struct regmap *regmap;

	es8316 = devm_kzalloc(&i2c_client->dev, sizeof(struct es8316_priv),
			      GFP_KERNEL);
	if (es8316 == NULL)
		return -ENOMEM;

	i2c_set_clientdata(i2c_client, es8316);

	regmap = devm_regmap_init_i2c(i2c_client, &es8316_regmap);
	if (IS_ERR(regmap))
		return PTR_ERR(regmap);

	return snd_soc_register_codec(&i2c_client->dev, &soc_codec_dev_es8316,
				      &es8316_dai, 1);
}

      snd_kcontrol_new结构中,几个主要的字段是get,put,private_value。

get回调函数用于获取该控件当前的状态值;

put回调函数则用于设置控件的状态值;

private_value字段则根据不同的控件类型有不同的意义,比如对于普通的控件,private_value字段可以用来定义该控件所对应的寄存器的地址以及对应的控制位在寄存器中的位置信息。

      值得庆幸的是,ASoc系统已经为我们准备了大量的宏定义,用于定义常用的控件,这些宏定义位于include/sound/soc.h中。下面我们分别讨论一下如何用这些预设的宏定义来定义一些常用的控件。

2. 简单型的控件

2.1 SOC_SINGLE (控制器/开关等)

       SOC_SINGLE应该算是最简单的控件了,这种控件只有一个控制量,比如一个开关,或者是一个数值变量(比如Codec中某个频率,FIFO大小等等)。我们看看这个宏是如何定义的:

#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) }

      宏定义的参数分别是:xname(该控件的名字),reg(该控件对应的寄存器的地址),shift(控制位在寄存器中的位移),max(控件可设置的最大值),invert(设定值是否逻辑取反)。这里又使用了一个宏来定义private_value字段: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结构是这样的

/* 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是控件特征的真正描述者,它确定了该控件对应寄存器的地址,位移值,最大值和是否逻辑取反等特性,控件的put回调函数和get回调函数需要借助该结构来访问实际的寄存器。我们看看这get回调函数的定义:

/**
 * snd_soc_get_volsw_range - single mixer get callback with range
 * @kcontrol: mixer control
 * @ucontrol: control element information
 *
 * Callback to get the value, within a range, of a single mixer control.
 *
 * Returns 0 for success.
 */
int snd_soc_get_volsw_range(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 rreg = mc->rreg;
	unsigned int shift = mc->shift;
	int min = mc->min;
	int max = mc->max;
	unsigned int mask = (1 << fls(max)) - 1;
	unsigned int invert = mc->invert;
	unsigned int val;
	int ret;

	ret = snd_soc_component_read(component, reg, &val);
	if (ret)
		return ret;

	ucontrol->value.integer.value[0] = (val >> shift) & mask;
	if (invert)
		ucontrol->value.integer.value[0] =
			max - ucontrol->value.integer.value[0];
	else
		ucontrol->value.integer.value[0] =
			ucontrol->value.integer.value[0] - min;

	if (snd_soc_volsw_is_stereo(mc)) {
		ret = snd_soc_component_read(component, rreg, &val);
		if (ret)
			return ret;

		ucontrol->value.integer.value[1] = (val >> shift) & mask;
		if (invert)
			ucontrol->value.integer.value[1] =
				max - ucontrol->value.integer.value[1];
		else
			ucontrol->value.integer.value[1] =
				ucontrol->value.integer.value[1] - min;
	}

	return 0;
}

      上述代码一目了然,从private_value字段取出soc_mixer_control结构,利用该结构的信息,访问对应的寄存器,返回相应的值。

2.2 SOC_SINGLE_TLV(有增益控制的控件

      SOC_SINGLE_TLV是SOC_SINGLE的一种扩展,主要用于定义那些有增益控制的控件,例如音量控制器,EQ均衡器等等。在es8316中,也有定义:

static const struct snd_kcontrol_new es8316_snd_controls[] = {
    .......
	SOC_SINGLE_TLV("ADC Capture Volume", ES8316_ADC_VOLUME,
		       0, 0xc0, 1, adc_vol_tlv),
	........
};
#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) }

      从他的定义可以看出,用于设定寄存器信息的private_value字段的定义和SOC_SINGLE是一样的,甚至put、get回调函数也是使用同一套,唯一不同的是增加了一个tlv_array参数,并把它赋值给了tlv.p字段。关于tlv,已经在Control设备的创建中进行了阐述,所谓tlv,就是Type-Lenght-Value的意思,数组的第0个元素代表数据的类型,第1个元素代表数据的长度,第三个元素和之后的元素保存该变量的数据用户空间可以通过对声卡的control设备发起以下两种ioctl来访问tlv字段所指向的数组:

  •         SNDRV_CTL_IOCTL_TLV_READ
  •         SNDRV_CTL_IOCTL_TLV_WRITE
  •         SNDRV_CTL_IOCTL_TLV_COMMAND

      通常,tlv_array用来描述寄存器的设定值与它所代表的实际意义之间的映射关系,最常用的就是用于音量控件时,设定值与对应的dB值之间的映射关系,请看以下例子(sound\soc\codecs\es8316.c):

static const DECLARE_TLV_DB_SCALE(mixin_boost_tlv, 0, 900, 0);
 
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),
};

       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。

2.3 SOC_DOUBLE(声道控制等)

      与SOC_SINGLE相对应,区别是SOC_SINGLE只控制一个变量,而SOC_DOUBLE则可以同时在一个寄存器中控制两个相似的变量,最常用的就是用于一些立体声的控件,我们需要同时对左右声道进行控制,因为多了一个声道,参数也就相应地多了一个shift位移值.

#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) }

3. Mixer控件

      Mixer控件用于音频通道的路由控制,由多个输入和一个输出组成,多个输入可以自由地混合在一起,形成混合后的输出:

图1     Mixer混音器

      对于Mixer控件,我们可以认为是多个简单控件的组合通常,我们会为mixer的每个输入端都单独定义一个简单控件来控制该路输入的开启和关闭,反应在代码上,就是定义一个soc_kcontrol_new数组。以sound\soc\codecs\es8316.c为例。

/* headphone Output Mixer */
static const struct snd_kcontrol_new es8316_out_left_mix[] = {
	SOC_DAPM_SINGLE("LLIN Switch", ES8316_HPMIX_SWITCH, 6, 1, 0),
	SOC_DAPM_SINGLE("Left DAC Switch", ES8316_HPMIX_SWITCH, 7, 1, 0),
};

      以上这个mixer使用寄存器ES8316_HPMIX_SWITCH的第6,7位来分别控制2个输入端的开启和关闭。

4. Mux控件(struct soc_enum)

      mux控件与mixer控件类似,也是多个输入端和一个输出端的组合控件,与mixer控件不同的是,mux控件的多个输入端同时只能有一个被选中。因此,mux控件所对应的寄存器,通常可以设定一段连续的数值,每个不同的数值对应不同的输入端被打开,与上述的mixer控件不同,ASoc用soc_enum结构来描述mux控件的寄存器信息:

kernel\include\sound\soc.h
/* 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控件,参考es8316.c中的一个mux定义:

 第一步,定义字符串和values数组,以下的例子因为values是连续的,所以不用定义

static const char * const ng_type_txt[] =
	{ "Constant PGA Gain", "Mute ADC Output" };

第二步,利用ASoc提供的辅助宏定义soc_enum结构,用于描述寄存器

static const struct soc_enum ng_type =
	SOC_ENUM_SINGLE(ES8316_ADC_ALC_NG, 6, 2, ng_type_txt);

第三步,利痛ASoc提供的辅助宏,定义soc_kcontrol_new结构,该结构最后用于注册该mux控件:

static const struct snd_kcontrol_new es8316_snd_controls[] = {
	.......
	SOC_ENUM("ALC Capture Noise Gate Type", ng_type),
};

       以上几步定义了一个叫ng_type 的mux控件,该控件具有两个输入选择,分别是来自Constant PGA Gain和Mute ADC Output,用寄存器ES8316_ADC_ALC_NG控制。其中,soc_enum结构使用了辅助宏SOC_ENUM_SINGLE来定义,该宏的声明如下:

#define SOC_ENUM_DOUBLE(xreg, xshift_l, xshift_r, xmax, xtexts) \
{       .reg = xreg, .shift_l = xshift_l, .shift_r = xshift_r, \
        .max = xmax, .texts = xtexts, \
        .mask = xmax ? roundup_pow_of_two(xmax) - 1 : 0}
#define SOC_ENUM_SINGLE(xreg, xshift, xmax, xtexts) \
        SOC_ENUM_DOUBLE(xreg, xshift, xshift, xmax, xtexts)

      定义soc_kcontrol_new结构时使用了SOC_ENUM,列出它的定义如下:

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

      思想如此统一,依然是使用private_value字段记录soc_enum结构,不过几个回调函数变了,我们看看get回调对应的snd_soc_get_enum_double函数:


/**
 * snd_soc_get_enum_double - enumerated double mixer get callback
 * @kcontrol: mixer control
 * @ucontrol: control element information
 *
 * Callback to get the value of a double enumerated mixer.
 *
 * Returns 0 for success.
 */
int snd_soc_get_enum_double(struct snd_kcontrol *kcontrol,
	struct snd_ctl_elem_value *ucontrol)
{
	struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
	struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
	unsigned int val, item;
	unsigned int reg_val;
	int ret;

	ret = snd_soc_component_read(component, e->reg, &reg_val);
	if (ret)
		return ret;
	val = (reg_val >> e->shift_l) & e->mask;
	item = snd_soc_enum_val_to_item(e, val);
	ucontrol->value.enumerated.item[0] = item;
	if (e->shift_l != e->shift_r) {
		val = (reg_val >> e->shift_r) & e->mask;
		item = snd_soc_enum_val_to_item(e, val);
		ucontrol->value.enumerated.item[1] = item;
	}

	return 0;
}

     通过info回调函数则可以获取某个输入端所对应的名字,其实就是从soc_enum结构的texts数组中获得:

int snd_ctl_enum_info(struct snd_ctl_elem_info *info, unsigned int channels,
		      unsigned int items, const char *const names[])
{
	info->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
	info->count = channels;
	info->value.enumerated.items = items;
	if (!items)
		return 0;
	if (info->value.enumerated.item >= items)
		info->value.enumerated.item = items - 1;
	WARN(strlen(names[info->value.enumerated.item]) >= sizeof(info->value.enumerated.name),
	     "ALSA: too long item name '%s'\n",
	     names[info->value.enumerated.item]);
	strlcpy(info->value.enumerated.name,
		names[info->value.enumerated.item],
		sizeof(info->value.enumerated.name));
	return 0;
}

int snd_soc_info_enum_double(struct snd_kcontrol *kcontrol,
	struct snd_ctl_elem_info *uinfo)
{
	struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;

	return snd_ctl_enum_info(uinfo, e->shift_l == e->shift_r ? 1 : 2,
				 e->items, e->texts);
}

以下是另外几个常用于定义mux控件的宏:

SOC_VALUE_ENUM_SINGLE    用于定义带values字段的soc_enum结构。

SOC_VALUE_ENUM_DOUBLE    SOC_VALUE_ENUM_SINGLE的立体声版本。

5. 其它控件

      其实,除了以上介绍的几种常用的控件,ASoc还为我们提供了另外一些控件定义辅助宏,详细的请读者参考include/sound/soc.h。这里列举几个:

需要自己定义get和put回调时,可以使用以下这些带EXT的版本:

SOC_SINGLE_EXT    
SOC_DOUBLE_EXT
SOC_SINGLE_EXT_TLV
SOC_DOUBLE_EXT_TLV
SOC_DOUBLE_R_EXT_TLV
SOC_ENUM_EXT

6. kcontrol的注册

       kcontrol有两种注册方式,一个是通过struct snd_soc_codec_driver的probe函数注册,probe函数中调用snd_soc_add_codec_controls注册kcontrol,以sound\soc\codecs\wm8960.c为例;一个是通过snd_soc_register_codec先添加到codec_dai的component链表中,以sound\soc\codecs\es8328.c为例。

6.1 snd_soc_add_codec_controls注册kcontrol

参考sound\soc\codecs\wm8960.c。

static const struct snd_kcontrol_new wm8960_snd_controls[] = {
.......
SOC_SINGLE("Speaker DC Volume", WM8960_CLASSD3, 3, 5, 0),
SOC_SINGLE("Speaker AC Volume", WM8960_CLASSD3, 0, 5, 0),

.......
};

static int wm8960_probe(struct snd_soc_codec *codec)
{
	.......
	snd_soc_add_codec_controls(codec, wm8960_snd_controls,
				     ARRAY_SIZE(wm8960_snd_controls));
	wm8960_add_widgets(codec);

	return 0;
}

static const struct snd_soc_codec_driver soc_codec_dev_wm8960 = {
	.probe =	wm8960_probe,
	.set_bias_level = wm8960_set_bias_level,
	.suspend_bias_off = true,
};

      snd_soc_add_codec_controls的调用过程如下,最终把kcontrol加到了soc_card的controls链表中。

snd_soc_add_codec_controls
    snd_soc_add_component_controls(&codec->component, controls,num_controls);
        snd_soc_add_controls(card, component->dev, controls,
                num_controls, component->name_prefix, component);
            snd_ctl_add
                __snd_ctl_add
                    list_add_tail(&kcontrol->list, &card->controls);

6.2 snd_soc_register_codec

以sound\soc\codecs\es8328.c为例。

static const struct snd_soc_codec_driver es8328_codec_driver = {
	.......
	.component_driver = {
		.controls		= es8328_snd_controls,
		.num_controls		= ARRAY_SIZE(es8328_snd_controls),
		.dapm_widgets		= es8328_dapm_widgets,
		.num_dapm_widgets	= ARRAY_SIZE(es8328_dapm_widgets),
		.dapm_routes		= es8328_dapm_routes,
		.num_dapm_routes	= ARRAY_SIZE(es8328_dapm_routes),
	},
};

return snd_soc_register_codec(dev,
		&es8328_codec_driver, &es8328_dai, 1);
snd_soc_register_codec(dev, &es8328_codec_driver, &es8328_dai, 1);
    ret = snd_soc_component_initialize(&codec->component, 
			&codec_drv->component_driver, dev);  ---->  component->driver = driver; 
		ret = snd_soc_register_dais(&codec->component, dai_drv, num_dai, false);
			component->dai_drv = dai_drv;
			dai = soc_add_dai(component, dai_drv + i,count == 1 && legacy_dai_naming);
				struct snd_soc_dai *dai;
				dai->component = component;
				dai->driver = dai_drv;
				list_add(&dai->list, &component->dai_list);
    snd_soc_component_add_unlocked(&codec->component);
         list_add(&component->list, &component_list);

      最终es8328_codec_driver中的component_driver被幅值给dai->component的component_driver。这里面既包含kcontrol又包含widget。这个dai->component又最终被什么调用呢。

snd_soc_register_card
	snd_soc_instantiate_card
		struct snd_soc_pcm_runtime *rtd;
			soc_bind_dai_link
				struct snd_soc_pcm_runtime *rtd;
				rtd = soc_new_pcm_runtime(card, dai_link);
				
				snd_soc_rtdcom_add(rtd, rtd->cpu_dai->component);
				snd_soc_rtdcom_add(rtd, codec_dais[i]->component);
				snd_soc_rtdcom_add(rtd, component);
				
				soc_add_pcm_runtime(card, rtd);
					list_add_tail(&rtd->list, &card->rtd_list);
		soc_probe_link_components
			soc_probe_component
				snd_soc_add_component_controls
					snd_soc_add_controls
						snd_ctl_add
							__snd_ctl_add
								list_add_tail(&kcontrol->list, &card->controls);

6.3 snd_soc_add_platform_controls

这个用的不多,暂不分析。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值