static int wm8993_i2c_probe(struct i2c_client *i2c,
const struct i2c_device_id *id) {
......
ret = snd_soc_register_codec(&i2c->dev,
&soc_codec_dev_wm8993, &wm8993_dai, 1); ...... }
这回使得ASoc把codec的dai注册到系统中,并把这些dai都挂在全局链表变量dai_list中,然后,在codec被machine驱动匹配后,soc_probe_codec函数会被调用,他会通过全局链表变量dai_list查找所有属于该codec的dai,调用
snd_soc_dapm_new_dai_widgets函数来生成该dai的播放流widget和录音流widget:
[cpp] view plaincopy
static int soc_probe_codec(struct snd_soc_card *card, struct snd_soc_codec *codec) {
......
/* Create DAPM widgets for each DAI stream */ list_for_each_entry(dai, &dai_list, list) { if (dai->dev != codec->dev) continue;
snd_soc_dapm_new_dai_widgets(&codec->dapm, dai); }
...... }
我们看看snd_soc_dapm_new_dai_widgets的代码: [cpp] view plaincopy
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;
// 创建播放 dai widget if (dai->driver->playback.stream_name) { template.id = snd_soc_dapm_dai_in;
36
template.name = dai->driver->playback.stream_name;
template.sname = dai->driver->playback.stream_name;
w = snd_soc_dapm_new_control(dapm, &template);
w->priv = dai;
dai->playback_widget = w; }
// 创建录音 dai widget
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;
w = snd_soc_dapm_new_control(dapm, &template);
w->priv = dai;
dai->capture_widget = w; }
return 0; }
分别为Playback和Capture创建了一个widget,widget的priv字段指向了该dai,这样通过widget就可以找到相应的dai,并且widget的名字就是snd_soc_dai_driver结构的stream_name。
cpu dai widget
这里顺便说一个小意外,昨天晚上手贱,执行了一下git pull,版本升级到了3.12 rc7,结果发现ASoc的代码有所变化,于是稍稍纠结了一下,用新的代码继续还是恢复之前的3.10 rc5?经过查看了一些变化后,发现还是新的版本改进得更合理,现在决定,后面的内容都是基于3.12 rc7了。如果大家发现后面贴的代码和之前贴的有差异的地方,自己比较一下这两个版本的代码吧!
回到cpu dai,以前的内核版本由驱动通过snd_soc_register_dais注册,新的版本中,这个函数变为了soc-core的内部函数,驱动改为使用
snd_soc_register_component注册,snd_soc_register_component函数再通过调用snd_soc_register_dai/snd_soc_register_dais来完成实际的注册工作。和codec dai widget一样,cpu dai widget也发生在machine驱动匹配上相应的platform驱动之后,soc_probe_platform会被调用,在soc_probe_platform函数
37
中,通过比较dai->dev和platform->dev,挑选出属于该platform的dai,然后通过snd_soc_dapm_new_dai_widgets为cpu dai创建相应的widget: [cpp] view plaincopy
static int soc_probe_platform(struct snd_soc_card *card,
struct snd_soc_platform *platform) {
int ret = 0;
const struct snd_soc_platform_driver *driver = platform->driver;
struct snd_soc_dai *dai;
......
if (driver->dapm_widgets)
snd_soc_dapm_new_controls(&platform->dapm, driver->dapm_widgets, driver->num_dapm_widgets);
/* Create DAPM widgets for each DAI stream */ list_for_each_entry(dai, &dai_list, list) { if (dai->dev != platform->dev) continue;
snd_soc_dapm_new_dai_widgets(&platform->dapm, dai); }
platform->dapm.idle_bias_off = 1;
......
if (driver->controls)
snd_soc_add_platform_controls(platform, driver->controls,
driver->num_controls); if (driver->dapm_routes)
snd_soc_dapm_add_routes(&platform->dapm, driver->dapm_routes,
driver->num_dapm_routes); ......
return 0; }
38
从上面的代码我们也可以看出,在上面的”创建和注册widget“一节提到的第一种方法,即通过给snd_soc_platform_driver结构的dapm_widgets和
num_dapm_widgets字段赋值,ASoc会自动为我们创建所需的widget,真正执行创建工作就在上面所列的soc_probe_platform函数中完成的,普通的kcontrol和音频路径也是一样的原理。反推回来,codec的widget也是一样的,在
soc_probe_codec中会做同样的事情,只是我上面贴出来soc_probe_codec的代码里没有贴出来,有兴趣的读者自己查看一下它的代码即可。
花了这么多篇幅来讲解dai widget,好像现在看来它还没有什么用处。嗯,不要着急,实际上dai widget是一条完整dapm音频路径的重要元素,没有她,我们无法完成dapm的动态电源管理工作,因为它是音频流和其他widget的纽带,细节我们要留到下一篇文章中来阐述了。 端点widget
一条完整的dapm音频路径,必然有起点和终点,我们把位于这些起点和终点的widget称之为端点widget。以下这些类型的widget可以成为端点widget: codec的输入输出引脚: snd_soc_dapm_output snd_soc_dapm_input 外接的音频设备: snd_soc_dapm_hp snd_soc_dapm_spk snd_soc_dapm_line
音频流(stream domain): snd_soc_dapm_adc snd_soc_dapm_dac snd_soc_dapm_aif_out snd_soc_dapm_aif_in snd_soc_dapm_dai_out snd_soc_dapm_dai_in 电源、时钟和其它: snd_soc_dapm_supply
snd_soc_dapm_regulator_supply snd_soc_dapm_clock_supply snd_soc_dapm_kcontrol
当声卡上的其中一个widget的状态发生改变时,从这个widget开始,dapm框架会向前和向后遍历路径上的所有widget,判断每个widget的状态是否需要跟着变更,到达这些端点widget就会认为它是一条完整音频路径的开始和结束,从而结束一次扫描动作。至于代码的分析,先让我歇一会......,我会在后面的文章中讨论。
1.ALSA声卡驱动中的DAPM详解之五:建立widget之间的连接关系 39
前面我们主要着重于codec、platform、machine驱动程序中如何使用和建立dapm所需要的widget,route,这些是音频驱动开发人员必须要了解的内容,经过前几章的介绍,我们应该知道如何在alsa音频驱动的3大部分(codec、platform、machine)中,按照所使用的音频硬件结构,定义出相应的widget,kcontrol,以及必要的音频路径,而在本章中,我们将会深入dapm的核心部分,看看各个widget之间是如何建立连接关系,形成一条完整的音频路径。
/*****************************************************************************************************/
声明:本博内容均由http://blog.csdn.net/droidphone原创,转载请注明出处,谢谢!
/*****************************************************************************************************/
前面我们已经简单地介绍过,驱动程序需要使用以下api函数创建widget: snd_soc_dapm_new_controls
实际上,这个函数只是创建widget的第一步,它为每个widget分配内存,初始化必要的字段,然后把这些widget挂在代表声卡的snd_soc_card的widgets链表字段中。要使widget之间具备连接能力,我们还需要第二个函数: snd_soc_dapm_new_widgets
这个函数会根据widget的信息,创建widget所需要的dapm kcontrol,这些
dapm kcontol的状态变化,代表着音频路径的变化,从而影响着各个widget的电源状态。看到函数的名称可能会迷惑一下,实际上,
snd_soc_dapm_new_controls的作用更多地是创建widget,而
snd_soc_dapm_new_widget的作用则更多地是创建widget所包含的kcontrol,所以在我看来,这两个函数名称应该换过来叫更好!下面我们分别介绍一下这两个函数是如何工作的。
创建widget:snd_soc_dapm_new_controls
snd_soc_dapm_new_controls函数完成widget的创建工作,并把这些创建好的widget注册在声卡的widgets链表中,我们看看他的定义: [cpp] view plaincopy
int snd_soc_dapm_new_controls(struct snd_soc_dapm_context *dapm,
const struct snd_soc_dapm_widget *widget, int num) {
......
for (i = 0; i < num; i++) {
w = snd_soc_dapm_new_control(dapm, widget); if (!w) {
dev_err(dapm->dev,
40