目录
6.1 snd_soc_add_codec_controls注册kcontrol
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控件用于音频通道的路由控制,由多个输入和一个输出组成,多个输入可以自由地混合在一起,形成混合后的输出:

对于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, ®_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
这个用的不多,暂不分析。