您的位置:首页 > 产品设计 > 产品经理

ALSA声卡驱动中的DAPM详解之五:建立widget之间的连接关系

2013-11-28 15:27 585 查看
转载自http://blog.csdn.net/droidphone/article/details/14052861

前面我们主要着重于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 plaincopyprint?

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,  
                                "ASoC: Failed to create DAPM control %s\n",  
                                widget->name);  
                        ret = -ENOMEM;  
                        break;  
                }                 
                widget++;  
        }  
        ......  
        return ret;  
}  

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,
"ASoC: Failed to create DAPM control %s\n",
widget->name);
ret = -ENOMEM;
break;
}
widget++;
}
......
return ret;
}
该函数只是简单的一个循环,为传入的widget模板数组依次调用snd_soc_dapm_new_control函数,实际的工作由snd_soc_dapm_new_control完成,继续进入该函数,看看它做了那些工作。
我们之前已经说过,驱动中定义的snd_soc_dapm_widget数组,只是作为一个模板,所以,snd_soc_dapm_new_control所做的第一件事,就是为该widget重新分配内存,并把模板的内容拷贝过来:

[cpp]
view plaincopyprint?

static struct snd_soc_dapm_widget *  
snd_soc_dapm_new_control(struct snd_soc_dapm_context *dapm,  
                         const struct snd_soc_dapm_widget *widget)  
{  
        struct snd_soc_dapm_widget *w;  
        int ret;  
  
        if ((w = dapm_cnew_widget(widget)) == NULL)  
                return NULL;  

static struct snd_soc_dapm_widget *
snd_soc_dapm_new_control(struct snd_soc_dapm_context *dapm,
const struct snd_soc_dapm_widget *widget)
{
struct snd_soc_dapm_widget *w;
int ret;

if ((w = dapm_cnew_widget(widget)) == NULL)
return NULL;


由dapm_cnew_widget完成内存申请和拷贝模板的动作。接下来,根据widget的类型做不同的处理:

[cpp]
view plaincopyprint?

        switch (w->id) {  
        case snd_soc_dapm_regulator_supply:  
                w->regulator = devm_regulator_get(dapm->dev, w->name);  
                ......  
  
                if (w->on_val & SND_SOC_DAPM_REGULATOR_BYPASS) {  
                        ret = regulator_allow_bypass(w->regulator, true);  
                        ......  
                }  
                break;  
        case snd_soc_dapm_clock_supply:  
#ifdef CONFIG_CLKDEV_LOOKUP
  
                w->clk = devm_clk_get(dapm->dev, w->name);  
                ......  
#else
  
                return NULL;  
#endif
  
                break;  
        default:  
                break;  
        }  

switch (w->id) {
case snd_soc_dapm_regulator_supply:
w->regulator = devm_regulator_get(dapm->dev, w->name);
......

if (w->on_val & SND_SOC_DAPM_REGULATOR_BYPASS) {
ret = regulator_allow_bypass(w->regulator, true);
......
}
break;
case snd_soc_dapm_clock_supply:
#ifdef CONFIG_CLKDEV_LOOKUP
w->clk = devm_clk_get(dapm->dev, w->name);
......
#else
return NULL;
#endif
break;
default:
break;
}
对于snd_soc_dapm_regulator_supply类型的widget,根据widget的名称获取对应的regulator结构,对于snd_soc_dapm_clock_supply类型的widget,根据widget的名称,获取对应的clock结构。接下来,根据需要,在widget的名称前加入必要的前缀:

[cpp]
view plaincopyprint?

if (dapm->codec && dapm->codec->name_prefix)  
        w->name = kasprintf(GFP_KERNEL, "%s %s",  
                dapm->codec->name_prefix, widget->name);  
else  
        w->name = kasprintf(GFP_KERNEL, "%s", widget->name);  

if (dapm->codec && dapm->codec->name_prefix)
w->name = kasprintf(GFP_KERNEL, "%s %s",
dapm->codec->name_prefix, widget->name);
else
w->name = kasprintf(GFP_KERNEL, "%s", widget->name);
然后,为不同类型的widget设置合适的power_check电源状态回调函数,widget类型和对应的power_check回调函数设置如下表所示:

widget的power_check回调函数
widget类型power_check回调函数
mixer类:

snd_soc_dapm_switch

snd_soc_dapm_mixer

snd_soc_dapm_mixer_named_ctl
dapm_generic_check_power
mux类:

snd_soc_dapm_mux

snd_soc_dapm_mux

snd_soc_dapm_mux
dapm_generic_check_power
snd_soc_dapm_dai_outdapm_adc_check_power
snd_soc_dapm_dai_indapm_dac_check_power
端点类:

snd_soc_dapm_adc

snd_soc_dapm_aif_out

snd_soc_dapm_dac

snd_soc_dapm_aif_in

snd_soc_dapm_pga

snd_soc_dapm_out_drv

snd_soc_dapm_input

snd_soc_dapm_output

snd_soc_dapm_micbias

snd_soc_dapm_spk

snd_soc_dapm_hp

snd_soc_dapm_mic

snd_soc_dapm_line

snd_soc_dapm_dai_link
dapm_generic_check_power
电源/时钟/影子widget:

snd_soc_dapm_supply

snd_soc_dapm_regulator_supply

snd_soc_dapm_clock_supply

snd_soc_dapm_kcontrol
dapm_supply_check_power
其它类型dapm_always_on_check_power
当音频路径发生变化时,power_check回调会被调用,用于检查该widget的电源状态是否需要更新。power_check设置完成后,需要设置widget所属的codec、platform和dapm context,几个用于音频路径的链表也需要初始化,然后,把该widget加入到声卡的widgets链表中:

[cpp]
view plaincopyprint?

w->dapm = dapm;  
w->codec = dapm->codec;  
w->platform = dapm->platform;  
INIT_LIST_HEAD(&w->sources);  
INIT_LIST_HEAD(&w->sinks);  
INIT_LIST_HEAD(&w->list);  
INIT_LIST_HEAD(&w->dirty);  
list_add(&w->list, &dapm->card->widgets);  

w->dapm = dapm;
w->codec = dapm->codec;
w->platform = dapm->platform;
INIT_LIST_HEAD(&w->sources);
INIT_LIST_HEAD(&w->sinks);
INIT_LIST_HEAD(&w->list);
INIT_LIST_HEAD(&w->dirty);
list_add(&w->list, &dapm->card->widgets);
几个链表的作用如下:

sources    用于链接所有连接到该widget输入端的snd_soc_path结构
sinks    用于链接所有连接到该widget输出端的snd_soc_path结构
list    用于链接到声卡的widgets链表
dirty    用于链接到声卡的dapm_dirty链表

最后,把widget设置为connect状态:

[cpp]
view plaincopyprint?

/* machine layer set ups unconnected pins and insertions */  
w->connected = 1;  
return w;  

/* machine layer set ups unconnected pins and insertions */
w->connected = 1;
return w;
}
connected字段代表着引脚的连接状态,目前,只有以下这些widget使用connected字段:

snd_soc_dapm_output

snd_soc_dapm_input

snd_soc_dapm_hp

snd_soc_dapm_spk

snd_soc_dapm_line

snd_soc_dapm_vmid

snd_soc_dapm_mic

snd_soc_dapm_siggen

驱动程序可以使用以下这些api来设置引脚的连接状态:

snd_soc_dapm_enable_pin

snd_soc_dapm_force_enable_pin

snd_soc_dapm_disable_pin

snd_soc_dapm_nc_pin

到此,widget已经被正确地创建并初始化,而且被挂在声卡的widgets链表中,以后我们就可以通过声卡的widgets链表来遍历所有的widget,再次强调一下snd_soc_dapm_new_controls函数所完成的主要功能:

为widget分配内存,并拷贝参数中传入的在驱动中定义好的模板
设置power_check回调函数
把widget挂在声卡的widgets链表中


为widget建立dapm kcontrol

定义一个widget,我们需要指定两个很重要的内容:一个是用于控制widget的电源状态的reg/shift等寄存器信息,另一个是用于控制音频路径切换的dapm kcontrol信息,这些dapm kcontrol有它们自己的reg/shift寄存器信息用于切换widget的路径连接方式。前一节的内容中,我们只是创建了widget的实例,并把它们注册到声卡的widgts链表中,但是到目前为止,包含在widget中的dapm kcontrol并没有建立起来,dapm框架在声卡的初始化阶段,等所有的widget(包括machine、platform、codec)都创建好之后,通过snd_soc_dapm_new_widgets函数,创建widget内包含的dapm
kcontrol,并初始化widget的初始电源状态和音频路径的初始连接状态。我们看看声卡的初始化函数,都有那些初始化与dapm有关:

[cpp]
view plaincopyprint?

static int snd_soc_instantiate_card(struct snd_soc_card *card)  
{  
        ......  
        /* card bind complete so register a sound card */  
        ret = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,  
                        card->owner, 0, &card->snd_card);  
        ......  
   
        card->dapm.bias_level = SND_SOC_BIAS_OFF;  
        card->dapm.dev = card->dev;  
        card->dapm.card = card;  
        list_add(&card->dapm.list, &card->dapm_list);  
  
#ifdef CONFIG_DEBUG_FS
  
        snd_soc_dapm_debugfs_init(&card->dapm, card->debugfs_card_root);  
#endif
  
        ......  
        if (card->dapm_widgets)    /* 创建machine级别的widget  */  
                snd_soc_dapm_new_controls(&card->dapm, card->dapm_widgets,  
                                          card->num_dapm_widgets);  
        ......  
        snd_soc_dapm_link_dai_widgets(card);  /*  连接dai widget  */  
  
        if (card->controls)    /*  建立machine级别的普通kcontrol控件  */  
                snd_soc_add_card_controls(card, card->controls, card->num_controls);  
  
        if (card->dapm_routes)    /*  注册machine级别的路径连接信息  */  
                snd_soc_dapm_add_routes(&card->dapm, card->dapm_routes,  
                                        card->num_dapm_routes);  
        ......  
  
        if (card->fully_routed)    /*  如果该标志被置位,自动把codec中没有路径连接信息的引脚设置为无用widget  */  
                list_for_each_entry(codec, &card->codec_dev_list, card_list)  
                        snd_soc_dapm_auto_nc_codec_pins(codec);  
  
        snd_soc_dapm_new_widgets(card);    /*初始化widget包含的dapm kcontrol、电源状态和连接状态*/  
  
        ret = snd_card_register(card->snd_card);  
        ......  
        card->instantiated = 1;  
        snd_soc_dapm_sync(&card->dapm);  
        ......  
        return 0;  
}   

static int snd_soc_instantiate_card(struct snd_soc_card *card)
{
......
/* card bind complete so register a sound card */
ret = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
card->owner, 0, &card->snd_card);
......

card->dapm.bias_level = SND_SOC_BIAS_OFF;
card->dapm.dev = card->dev;
card->dapm.card = card;
list_add(&card->dapm.list, &card->dapm_list);

#ifdef CONFIG_DEBUG_FS
snd_soc_dapm_debugfs_init(&card->dapm, card->debugfs_card_root);
#endif
......
if (card->dapm_widgets)    /* 创建machine级别的widget  */
snd_soc_dapm_new_controls(&card->dapm, card->dapm_widgets,
card->num_dapm_widgets);
......
snd_soc_dapm_link_dai_widgets(card);  /*  连接dai widget  */

if (card->controls)    /*  建立machine级别的普通kcontrol控件  */
snd_soc_add_card_controls(card, card->controls, card->num_controls);

if (card->dapm_routes)    /*  注册machine级别的路径连接信息  */
snd_soc_dapm_add_routes(&card->dapm, card->dapm_routes,
card->num_dapm_routes);
......

if (card->fully_routed)    /*  如果该标志被置位,自动把codec中没有路径连接信息的引脚设置为无用widget  */
list_for_each_entry(codec, &card->codec_dev_list, card_list)
snd_soc_dapm_auto_nc_codec_pins(codec);

snd_soc_dapm_new_widgets(card);    /*初始化widget包含的dapm kcontrol、电源状态和连接状态*/

ret = snd_card_register(card->snd_card);
......
card->instantiated = 1;
snd_soc_dapm_sync(&card->dapm);
......
return 0;
}
正如我添加的注释中所示,在完成machine级别的widget和route处理之后,调用的snd_soc_dapm_new_widgets函数,来为所有已经注册的widget初始化他们所包含的dapm kcontrol,并初始化widget的电源状态和路径连接状态。下面我们看看snd_soc_dapm_new_widgets函数的工作过程。

snd_soc_dapm_new_widgets函数    

该函数通过声卡的widgets链表,遍历所有已经注册了的widget,其中的new字段用于判断该widget是否已经执行过snd_soc_dapm_new_widgets函数,如果num_kcontrols字段有数值,表明该widget包含有若干个dapm kcontrol,那么就需要为这些kcontrol分配一个指针数组,并把数组的首地址赋值给widget的kcontrols字段,该数组存放着指向这些kcontrol的指针,当然现在这些都是空指针,因为实际的kcontrol现在还没有被创建:

[cpp]
view plaincopyprint?

int snd_soc_dapm_new_widgets(struct snd_soc_card *card)  
{  
        ......  
        list_for_each_entry(w, &card->widgets, list)  
        {                 
                if (w->new)       
                        continue;  
                                  
                if (w->num_kcontrols) {  
                        w->kcontrols = kzalloc(w->num_kcontrols *  
                                                sizeof(struct snd_kcontrol *),  
                                                GFP_KERNEL);  
                        ......  
                }  

int snd_soc_dapm_new_widgets(struct snd_soc_card *card)
{
......
list_for_each_entry(w, &card->widgets, list)
{
if (w->new)
continue;

if (w->num_kcontrols) {
w->kcontrols = kzalloc(w->num_kcontrols *
sizeof(struct snd_kcontrol *),
GFP_KERNEL);
......
}
接着,对几种能影响音频路径的widget,创建并初始化它们所包含的dapm kcontrol:

[cpp]
view plaincopyprint?

switch(w->id) {  
case snd_soc_dapm_switch:  
case snd_soc_dapm_mixer:  
case snd_soc_dapm_mixer_named_ctl:  
        dapm_new_mixer(w);  
        break;  
case snd_soc_dapm_mux:  
case snd_soc_dapm_virt_mux:  
case snd_soc_dapm_value_mux:  
        dapm_new_mux(w);  
        break;  
case snd_soc_dapm_pga:  
case snd_soc_dapm_out_drv:  
        dapm_new_pga(w);  
        break;  
default:  
        break;  
}  

switch(w->id) {
case snd_soc_dapm_switch:
case snd_soc_dapm_mixer:
case snd_soc_dapm_mixer_named_ctl:
dapm_new_mixer(w);
break;
case snd_soc_dapm_mux:
case snd_soc_dapm_virt_mux:
case snd_soc_dapm_value_mux:
dapm_new_mux(w);
break;
case snd_soc_dapm_pga:
case snd_soc_dapm_out_drv:
dapm_new_pga(w);
break;
default:
break;
}
需要用到的创建函数分别是:

dapm_new_mixer()    对于mixer类型,用该函数创建dapm kcontrol;
dapm_new_mux()   对于mux类型,用该函数创建dapm kcontrol;

dapm_new_pga()   对于pga类型,用该函数创建dapm kcontrol;

然后,根据widget寄存器的当前值,初始化widget的电源状态,并设置到power字段中:

[cpp]
view plaincopyprint?

/* Read the initial power state from the device */  
if (w->reg >= 0) {  
        val = soc_widget_read(w, w->reg) >> w->shift;  
        val &= w->mask;  
        if (val == w->on_val)  
                w->power = 1;  
}  

/* Read the initial power state from the device */
if (w->reg >= 0) {
val = soc_widget_read(w, w->reg) >> w->shift;
val &= w->mask;
if (val == w->on_val)
w->power = 1;
}
接着,设置new字段,表明该widget已经初始化完成,我们还要吧该widget加入到声卡的dapm_dirty链表中,表明该widget的状态发生了变化,稍后在合适的时刻,dapm框架会扫描dapm_dirty链表,统一处理所有已经变化的widget。为什么要统一处理?因为dapm要控制各种widget的上下电顺序,同时也是为了减少寄存器的读写次数(多个widget可能使用同一个寄存器):

[cpp]
view plaincopyprint?

w->new = 1;  
  
dapm_mark_dirty(w, "new widget");  
dapm_debugfs_add_widget(w);  

w->new = 1;

dapm_mark_dirty(w, "new widget");
dapm_debugfs_add_widget(w);
}


最后,通过dapm_power_widgets函数,统一处理所有位于dapm_dirty链表上的widget的状态改变:

[cpp]
view plaincopyprint?

dapm_power_widgets(card, SND_SOC_DAPM_STREAM_NOP);  
......  
return 0;  

dapm_power_widgets(card, SND_SOC_DAPM_STREAM_NOP);
......
return 0;
}

dapm mixer kcontrol

上一节中,我们提到,对于mixer类型的dapm kcontrol,我们会使用dapm_new_mixer来完成具体的创建工作,先看代码后分析:

[cpp]
view plaincopyprint?

static int dapm_new_mixer(struct snd_soc_dapm_widget *w)  
{  
        int i, ret;  
        struct snd_soc_dapm_path *path;  
  
        /* add kcontrol */  
<SPAN style="FONT-FAMILY: Arial,Helvetica,sans-serif">(1)</SPAN>        for (i = 0; i < w->num_kcontrols; i++) {                                  
                /* match name */  
(2)                list_for_each_entry(path, &w->sources, list_sink) {               
                        /* mixer/mux paths name must match control name */  
(3)                        if (path->name != (char *)w->kcontrol_news[i].name)       
                                continue;  
  
(4)                        if (w->kcontrols[i]) {                                   
                                dapm_kcontrol_add_path(w->kcontrols[i], path);  
                                continue;  
                        }  
  
(5)                        ret = dapm_create_or_share_mixmux_kcontrol(w, i);        
                        if (ret < 0)  
                                return ret;  
  
(6)                        dapm_kcontrol_add_path(w->kcontrols[i], path);           
                }  
        }  
  
        return 0;  
}  

static int dapm_new_mixer(struct snd_soc_dapm_widget *w)
{
int i, ret;
struct snd_soc_dapm_path *path;

/* add kcontrol */
(1)        for (i = 0; i < w->num_kcontrols; i++) {
/* match name */
(2)                list_for_each_entry(path, &w->sources, list_sink) {
/* mixer/mux paths name must match control name */
(3)                        if (path->name != (char *)w->kcontrol_news[i].name)
continue;

(4)                        if (w->kcontrols[i]) {
dapm_kcontrol_add_path(w->kcontrols[i], path);
continue;
}

(5)                        ret = dapm_create_or_share_mixmux_kcontrol(w, i);
if (ret < 0)
return ret;

(6)                        dapm_kcontrol_add_path(w->kcontrols[i], path);
}
}

return 0;
}
(1)  因为一个mixer是由多个kcontrol组成的,每个kcontrol控制着mixer的一个输入端的开启和关闭,所以,该函数会根据kcontrol的数量做循环,逐个建立对应的kcontrol。
(2)(3)  之前多次提到,widget之间使用snd_soc_path进行连接,widget的sources链表保存着所有和输入端连接的snd_soc_path结构,所以我们可以用kcontrol模板中指定的名字来匹配对应的snd_soc_path结构。
(4)  因为一个输入脚可能会连接多个输入源,所以可能在上一个输入源的path关联时已经创建了这个kcontrol,所以这里判断kcontrols指针数组中对应索引中的指针值,如果已经赋值,说明kcontrol已经在之前创建好了,所以我们只要简单地把连接该输入端的path加入到kcontrol的path_list链表中,并且增加一个虚拟的影子widget,该影子widget连接和输入端对应的源widget,因为使用了kcontrol本身的reg/shift等寄存器信息,所以实际上控制的是该kcontrol的开和关,这个影子widget只有在kcontrol的autodisable字段被设置的情况下才会被创建,该特性使得source的关闭时,与之连接的mixer的输入端也可以自动关闭,这个特性通过dapm_kcontrol_add_path来实现这一点:

[cpp]
view plaincopyprint?

static void dapm_kcontrol_add_path(const struct snd_kcontrol *kcontrol,  
        struct snd_soc_dapm_path *path)  
{  
        struct dapm_kcontrol_data *data = snd_kcontrol_chip(kcontrol);  
        /*  把kcontrol连接的path加入到paths链表中  */  
        /*  paths链表所在的dapm_kcontrol_data结构会保存在kcontrol的private_data字段中  */  
        list_add_tail(&path->list_kcontrol, &data->paths);  
  
        if (data->widget) {  
                snd_soc_dapm_add_path(data->widget->dapm, data->widget,  
                    path->source, NULL, NULL);  
        }  
}  

static void dapm_kcontrol_add_path(const struct snd_kcontrol *kcontrol,
struct snd_soc_dapm_path *path)
{
struct dapm_kcontrol_data *data = snd_kcontrol_chip(kcontrol);
/*  把kcontrol连接的path加入到paths链表中  */
/*  paths链表所在的dapm_kcontrol_data结构会保存在kcontrol的private_data字段中  */
list_add_tail(&path->list_kcontrol, &data->paths);

if (data->widget) {
snd_soc_dapm_add_path(data->widget->dapm, data->widget,
path->source, NULL, NULL);
}
}


(5)  如果kcontrol之前没有被创建,则通过dapm_create_or_share_mixmux_kcontrol创建这个输入端的kcontrol,同理,kcontrol对应的影子widget也会通过dapm_kcontrol_add_path判断是否需要创建。

dapm mux kcontrol

因为一个widget最多只会包含一个mux类型的damp kcontrol,所以他的创建方法稍有不同,dapm框架使用dapm_new_mux函数来创建mux类型的dapm kcontrol:

[cpp]
view plaincopyprint?

static int dapm_new_mux(struct snd_soc_dapm_widget *w)  
{         
        struct snd_soc_dapm_context *dapm = w->dapm;  
        struct snd_soc_dapm_path *path;  
        int ret;  
          
(1)     if (w->num_kcontrols != 1) {  
                dev_err(dapm->dev,  
                        "ASoC: mux %s has incorrect number of controls\n",  
                        w->name);  
                return -EINVAL;  
        }  
  
        if (list_empty(&w->sources)) {  
                dev_err(dapm->dev, "ASoC: mux %s has no paths\n", w->name);  
                return -EINVAL;  
        }  
  
(2)     ret = dapm_create_or_share_mixmux_kcontrol(w, 0);  
        if (ret < 0)  
                return ret;  
(3)       list_for_each_entry(path, &w->sources, list_sink)  
                dapm_kcontrol_add_path(w->kcontrols[0], path);  
        return 0;  
}  

static int dapm_new_mux(struct snd_soc_dapm_widget *w)
{
struct snd_soc_dapm_context *dapm = w->dapm;
struct snd_soc_dapm_path *path;
int ret;

(1)     if (w->num_kcontrols != 1) {
dev_err(dapm->dev,
"ASoC: mux %s has incorrect number of controls\n",
w->name);
return -EINVAL;
}

if (list_empty(&w->sources)) {
dev_err(dapm->dev, "ASoC: mux %s has no paths\n", w->name);
return -EINVAL;
}

(2)     ret = dapm_create_or_share_mixmux_kcontrol(w, 0);
if (ret < 0)
return ret;
(3)       list_for_each_entry(path, &w->sources, list_sink)
dapm_kcontrol_add_path(w->kcontrols[0], path);
return 0;
}


(1)  对于mux类型的widget,因为只会有一个kcontrol,所以在这里做一下判断。
(2)  同样地,和mixer类型一样,也使用dapm_create_or_share_mixmux_kcontrol来创建这个kcontrol。
(3)  对每个输入端所连接的path都加入dapm_kcontrol_data结构的paths链表中,并且创建一个影子widget,用于支持autodisable特性。

dapm pga kcontrol

目前对于pga类型的widget,kcontrol的创建函数是个空函数,所以我们不用太关注它:

[cpp]
view plaincopyprint?

static int dapm_new_pga(struct snd_soc_dapm_widget *w)  
{  
        if (w->num_kcontrols)  
                dev_err(w->dapm->dev,  
                        "ASoC: PGA controls not supported: '%s'\n", w->name);  
  
        return 0;  
}  

static int dapm_new_pga(struct snd_soc_dapm_widget *w)
{
if (w->num_kcontrols)
dev_err(w->dapm->dev,
"ASoC: PGA controls not supported: '%s'\n", w->name);

return 0;
}

dapm_create_or_share_mixmux_kcontrol函数

上面所说的mixer类型和mux类型的widget,在创建他们所包含的dapm kcontrol时,最后其实都是使用了dapm_create_or_share_mixmux_kcontrol函数来完成创建工作的,所以在这里我们有必要分析一下这个函数的工作原理。这个函数中有很大一部分代码实在处理kcontrol的名字是否要加入codec的前缀,我们会忽略这部分的代码,感兴趣的读者可以自己查看内核的代码,路径在:sound/soc/soc-dapm.c中,简化后的代码如下:

[cpp]
view plaincopyprint?

static int dapm_create_or_share_mixmux_kcontrol(struct snd_soc_dapm_widget *w,  
        int kci)  
{  
          ......  
(1)       shared = dapm_is_shared_kcontrol(dapm, w, &w->kcontrol_news[kci],  
                                         &kcontrol);  
     
(2)       if (!kcontrol) {  
(3)            kcontrol = snd_soc_cnew(&w->kcontrol_news[kci], NULL, name,prefix);  
               ......  
               kcontrol->private_free = dapm_kcontrol_free;  
(4)            ret = dapm_kcontrol_data_alloc(w, kcontrol);  
                ......  
(5)            ret = snd_ctl_add(card, kcontrol);  
                ......  
        }  
(6)     ret = dapm_kcontrol_add_widget(kcontrol, w);  
        ......  
(7)     w->kcontrols[kci] = kcontrol;  
        return 0;  
}  

static int dapm_create_or_share_mixmux_kcontrol(struct snd_soc_dapm_widget *w,
int kci)
{
......
(1)       shared = dapm_is_shared_kcontrol(dapm, w, &w->kcontrol_news[kci],
&kcontrol);

(2)       if (!kcontrol) {
(3)            kcontrol = snd_soc_cnew(&w->kcontrol_news[kci], NULL, name,prefix);
......
kcontrol->private_free = dapm_kcontrol_free;
(4)            ret = dapm_kcontrol_data_alloc(w, kcontrol);
......
(5)            ret = snd_ctl_add(card, kcontrol);
......
}
(6)     ret = dapm_kcontrol_add_widget(kcontrol, w);
......
(7)     w->kcontrols[kci] = kcontrol;
return 0;
}


(1)  为了节省内存,通过kcontrol名字的匹配查找,如果这个kcontrol已经在其他widget中已经创建好了,那我们不再创建,dapm_is_shared_kcontrol的参数kcontrol会返回已经创建好的kcontrol的指针。
(2)  如果kcontrol指针被赋值,说明在(1)中查找到了其他widget中同名的kcontrol,我们不用再次创建,只要共享该kcontrol即可。

(3)  标准的kcontrol创建函数,请参看:Linux ALSA声卡驱动之四:Control设备的创建中的“创建control“一节的内容。
(4)  如果widget支持autodisable特性,创建与该kcontrol所对应的影子widget,该影子widget的类型是:snd_soc_dapm_kcontrol。
(5)  标准的kcontrol创建函数,请参看:Linux ALSA声卡驱动之四:Control设备的创建中的“创建control“一节的内容。

(6)  把所有共享该kcontrol的影子widget(snd_soc_dapm_kcontrol),加入到kcontrol的private_data字段所指向的dapm_kcontrol_data结构中。
(7)  把创建好的kcontrol指针赋值到widget的kcontrols数组中。
需要注意的是,如果kcontol支持autodisable特性,一旦kcontrol由于source的关闭而被自动关闭,则用户空间只能操作该kcontrol的cache值,只有该kcontrol再次打开时,该cache值才会被真正地更新到寄存器中。

现在。我们总结一下,创建一个widget所包含的kcontrol所做的工作:

循环每一个输入端,为每个输入端依次执行下面的一系列操作
为每个输入端创建一个kcontrol,能共享的则直接使用创建好的kcontrol
kcontrol的private_data字段保存着这些共享widget的信息
如果支持autodisable特性,每个输入端还要额外地创建一个虚拟的snd_soc_dapm_kcontrol类型的影子widget,该影子widget也记录在private_data字段中

创建好的kcontrol会依次存放在widget的kcontrols数组中,供路径的控制和匹配之用。


为widget建立连接关系

如果widget之间没有连接关系,dapm就无法实现动态的电源管理工作,正是widget之间有了连结关系,这些连接关系形成了一条所谓的完成的音频路径,dapm可以顺着这条路径,统一控制路径上所有widget的电源状态,前面我们已经知道,widget之间是使用snd_soc_path结构进行连接的,驱动要做的是定义一个snd_soc_route结构数组,该数组的每个条目描述了目的widget的和源widget的名称,以及控制这个连接的kcontrol的名称,最终,驱动程序使用api函数snd_soc_dapm_add_routes来注册这些连接信息,接下来我们就是要分析该函数的具体实现方式:

[cpp]
view plaincopyprint?

int snd_soc_dapm_add_routes(struct snd_soc_dapm_context *dapm,  
                            const struct snd_soc_dapm_route *route, int num)  
{  
        int i, r, ret = 0;  
  
        mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_INIT);  
        for (i = 0; i < num; i++) {  
                r = snd_soc_dapm_add_route(dapm, route);  
                ......  
                route++;  
        }  
        mutex_unlock(&dapm->card->dapm_mutex);  
  
        return ret;  
}  

int snd_soc_dapm_add_routes(struct snd_soc_dapm_context *dapm,
const struct snd_soc_dapm_route *route, int num)
{
int i, r, ret = 0;

mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_INIT);
for (i = 0; i < num; i++) {
r = snd_soc_dapm_add_route(dapm, route);
......
route++;
}
mutex_unlock(&dapm->card->dapm_mutex);

return ret;
}
该函数只是一个循环,依次对参数传入的数组调用snd_soc_dapm_add_route,主要的工作由snd_soc_dapm_add_route完成。我们进入snd_soc_dapm_add_route函数看看:

[cpp]
view plaincopyprint?

static int snd_soc_dapm_add_route(struct snd_soc_dapm_context *dapm,  
                                  const struct snd_soc_dapm_route *route)  
{  
        struct snd_soc_dapm_widget *wsource = NULL, *wsink = NULL, *w;  
        struct snd_soc_dapm_widget *wtsource = NULL, *wtsink = NULL;  
        const char *sink;  
        const char *source;  
        ......  
        list_for_each_entry(w, &dapm->card->widgets, list) {  
                if (!wsink && !(strcmp(w->name, sink))) {  
                        wtsink = w;  
                        if (w->dapm == dapm)  
                                wsink = w;  
                        continue;  
                }  
                if (!wsource && !(strcmp(w->name, source))) {  
                        wtsource = w;  
                        if (w->dapm == dapm)  
                                wsource = w;  
                }  
        }  

static int snd_soc_dapm_add_route(struct snd_soc_dapm_context *dapm,
const struct snd_soc_dapm_route *route)
{
struct snd_soc_dapm_widget *wsource = NULL, *wsink = NULL, *w;
struct snd_soc_dapm_widget *wtsource = NULL, *wtsink = NULL;
const char *sink;
const char *source;
......
list_for_each_entry(w, &dapm->card->widgets, list) {
if (!wsink && !(strcmp(w->name, sink))) {
wtsink = w;
if (w->dapm == dapm)
wsink = w;
continue;
}
if (!wsource && !(strcmp(w->name, source))) {
wtsource = w;
if (w->dapm == dapm)
wsource = w;
}
}
上面的代码我再次省略了关于名称前缀的处理部分。我们可以看到,用widget的名字来比较,遍历声卡的widgets链表,找出源widget和目的widget的指针,这段代码虽然正确,但我总感觉少了一个判断退出循环的条件,如果链表的开头就找到了两个widget,还是要遍历整个链表才结束循环,好浪费时间。

下面,如果在本dapm context中没有找到,则使用别的dapm context中找到的widget:

[cpp]
view plaincopyprint?

if (!wsink)  
        wsink = wtsink;  
if (!wsource)  
        wsource = wtsource;  

if (!wsink)
wsink = wtsink;
if (!wsource)
wsource = wtsource;
最后,使用来增加一条连接信息:

[cpp]
view plaincopyprint?

        ret = snd_soc_dapm_add_path(dapm, wsource, wsink, route->control,  
                route->connected);  
        ......  
  
        return 0;  
}  

ret = snd_soc_dapm_add_path(dapm, wsource, wsink, route->control,
route->connected);
......

return 0;
}
snd_soc_dapm_add_path函数是整个调用链条中的关键,我们来分析一下:

[cpp]
view plaincopyprint?

static int snd_soc_dapm_add_path(struct snd_soc_dapm_context *dapm,  
        struct snd_soc_dapm_widget *wsource, struct snd_soc_dapm_widget *wsink,  
        const char *control,  
        int (*connected)(struct snd_soc_dapm_widget *source,  
                         struct snd_soc_dapm_widget *sink))  
{  
        struct snd_soc_dapm_path *path;  
        int ret;  
  
        path = kzalloc(sizeof(struct snd_soc_dapm_path), GFP_KERNEL);  
        if (!path)  
                return -ENOMEM;  
  
        path->source = wsource;  
        path->sink = wsink;  
        path->connected = connected;  
        INIT_LIST_HEAD(&path->list);  
        INIT_LIST_HEAD(&path->list_kcontrol);  
        INIT_LIST_HEAD(&path->list_source);  
        INIT_LIST_HEAD(&path->list_sink);  

static int snd_soc_dapm_add_path(struct snd_soc_dapm_context *dapm,
struct snd_soc_dapm_widget *wsource, struct snd_soc_dapm_widget *wsink,
const char *control,
int (*connected)(struct snd_soc_dapm_widget *source,
struct snd_soc_dapm_widget *sink))
{
struct snd_soc_dapm_path *path;
int ret;

path = kzalloc(sizeof(struct snd_soc_dapm_path), GFP_KERNEL);
if (!path)
return -ENOMEM;

path->source = wsource;
path->sink = wsink;
path->connected = connected;
INIT_LIST_HEAD(&path->list);
INIT_LIST_HEAD(&path->list_kcontrol);
INIT_LIST_HEAD(&path->list_source);
INIT_LIST_HEAD(&path->list_sink);
函数的一开始,首先为这个连接分配了一个snd_soc_path结构,path的source和sink字段分别指向源widget和目的widget,connected字段保存connected回调函数,初始化几个snd_soc_path结构中的几个链表。

[cpp]
view plaincopyprint?

/* check for external widgets */  
        if (wsink->id == snd_soc_dapm_input) {  
                if (wsource->id == snd_soc_dapm_micbias ||  
                        wsource->id == snd_soc_dapm_mic ||  
                        wsource->id == snd_soc_dapm_line ||  
                        wsource->id == snd_soc_dapm_output)  
                        wsink->ext = 1;  
        }  
        if (wsource->id == snd_soc_dapm_output) {  
                if (wsink->id == snd_soc_dapm_spk ||  
                        wsink->id == snd_soc_dapm_hp ||  
                        wsink->id == snd_soc_dapm_line ||  
                        wsink->id == snd_soc_dapm_input)  
                        wsource->ext = 1;  
        }  

/* check for external widgets */
if (wsink->id == snd_soc_dapm_input) {
if (wsource->id == snd_soc_dapm_micbias ||
wsource->id == snd_soc_dapm_mic ||
wsource->id == snd_soc_dapm_line ||
wsource->id == snd_soc_dapm_output)
wsink->ext = 1;
}
if (wsource->id == snd_soc_dapm_output) {
if (wsink->id == snd_soc_dapm_spk ||
wsink->id == snd_soc_dapm_hp ||
wsink->id == snd_soc_dapm_line ||
wsink->id == snd_soc_dapm_input)
wsource->ext = 1;
}
这段代码用于判断是否有外部连接关系,如果有,置位widget的ext字段。判断方法从代码中可以方便地看出:

目的widget是一个输入脚,如果源widget是mic、line、micbias或output,则认为目的widget具有外部连接关系。
源widget是一个输出脚,如果目的widget是spk、hp、line或input,则认为源widget具有外部连接关系。

[cpp]
view plaincopyprint?

dapm_mark_dirty(wsource, "Route added");  
dapm_mark_dirty(wsink, "Route added");  
  
/* connect static paths */  
if (control == NULL) {  
        list_add(&path->list, &dapm->card->paths);  
        list_add(&path->list_sink, &wsink->sources);  
        list_add(&path->list_source, &wsource->sinks);  
        path->connect = 1;  
        return 0;  
}  

dapm_mark_dirty(wsource, "Route added");
dapm_mark_dirty(wsink, "Route added");

/* connect static paths */
if (control == NULL) {
list_add(&path->list, &dapm->card->paths);
list_add(&path->list_sink, &wsink->sources);
list_add(&path->list_source, &wsource->sinks);
path->connect = 1;
return 0;
}
因为增加了连结关系,所以把源widget和目的widget加入到dapm_dirty链表中。如果没有kcontrol来控制该连接关系,则这是一个静态连接,直接用path把它们连接在一起。在接着往下看:

[cpp]
view plaincopyprint?

/* connect dynamic paths */  
switch (wsink->id) {  
case snd_soc_dapm_adc:  
case snd_soc_dapm_dac:  
case snd_soc_dapm_pga:  
case snd_soc_dapm_out_drv:  
case snd_soc_dapm_input:  
case snd_soc_dapm_output:  
case snd_soc_dapm_siggen:  
case snd_soc_dapm_micbias:  
case snd_soc_dapm_vmid:  
case snd_soc_dapm_pre:  
case snd_soc_dapm_post:  
case snd_soc_dapm_supply:  
case snd_soc_dapm_regulator_supply:  
case snd_soc_dapm_clock_supply:  
case snd_soc_dapm_aif_in:  
case snd_soc_dapm_aif_out:  
case snd_soc_dapm_dai_in:  
case snd_soc_dapm_dai_out:  
case snd_soc_dapm_dai_link:  
case snd_soc_dapm_kcontrol:  
        list_add(&path->list, &dapm->card->paths);  
        list_add(&path->list_sink, &wsink->sources);  
        list_add(&path->list_source, &wsource->sinks);  
        path->connect = 1;  
        return 0;  

/* connect dynamic paths */
switch (wsink->id) {
case snd_soc_dapm_adc:
case snd_soc_dapm_dac:
case snd_soc_dapm_pga:
case snd_soc_dapm_out_drv:
case snd_soc_dapm_input:
case snd_soc_dapm_output:
case snd_soc_dapm_siggen:
case snd_soc_dapm_micbias:
case snd_soc_dapm_vmid:
case snd_soc_dapm_pre:
case snd_soc_dapm_post:
case snd_soc_dapm_supply:
case snd_soc_dapm_regulator_supply:
case snd_soc_dapm_clock_supply:
case snd_soc_dapm_aif_in:
case snd_soc_dapm_aif_out:
case snd_soc_dapm_dai_in:
case snd_soc_dapm_dai_out:
case snd_soc_dapm_dai_link:
case snd_soc_dapm_kcontrol:
list_add(&path->list, &dapm->card->paths);
list_add(&path->list_sink, &wsink->sources);
list_add(&path->list_source, &wsource->sinks);
path->connect = 1;
return 0;
按照目的widget来判断,如果属于以上这些类型,直接把它们连接在一起即可,这段感觉有点多余,因为通常以上这些类型的widget本来也没有kcontrol,直接用上一段代码就可以了,也许是dapm的作者们想着以后可能会有所扩展吧。

[cpp]
view plaincopyprint?

case snd_soc_dapm_mux:  
case snd_soc_dapm_virt_mux:  
case snd_soc_dapm_value_mux:  
        ret = dapm_connect_mux(dapm, wsource, wsink, path, control,  
                &wsink->kcontrol_news[0]);  
        if (ret != 0)  
                goto err;  
        break;  
case snd_soc_dapm_switch:  
case snd_soc_dapm_mixer:  
case snd_soc_dapm_mixer_named_ctl:  
        ret = dapm_connect_mixer(dapm, wsource, wsink, path, control);  
        if (ret != 0)  
                goto err;  
        break;  

case snd_soc_dapm_mux:
case snd_soc_dapm_virt_mux:
case snd_soc_dapm_value_mux:
ret = dapm_connect_mux(dapm, wsource, wsink, path, control,
&wsink->kcontrol_news[0]);
if (ret != 0)
goto err;
break;
case snd_soc_dapm_switch:
case snd_soc_dapm_mixer:
case snd_soc_dapm_mixer_named_ctl:
ret = dapm_connect_mixer(dapm, wsource, wsink, path, control);
if (ret != 0)
goto err;
break;
目的widget如果是mixer和mux类型,分别用dapm_connect_mixer和dapm_connect_mux函数完成连接工作,这两个函数我们后面再讲。

[cpp]
view plaincopyprint?

        case snd_soc_dapm_hp:  
        case snd_soc_dapm_mic:  
        case snd_soc_dapm_line:  
        case snd_soc_dapm_spk:  
                list_add(&path->list, &dapm->card->paths);  
                list_add(&path->list_sink, &wsink->sources);  
                list_add(&path->list_source, &wsource->sinks);  
                path->connect = 0;  
                return 0;  
        }  
  
        return 0;  
err:  
        kfree(path);  
        return ret;  
}  

case snd_soc_dapm_hp:
case snd_soc_dapm_mic:
case snd_soc_dapm_line:
case snd_soc_dapm_spk:
list_add(&path->list, &dapm->card->paths);
list_add(&path->list_sink, &wsink->sources);
list_add(&path->list_source, &wsource->sinks);
path->connect = 0;
return 0;
}

return 0;
err:
kfree(path);
return ret;
}
hp、mic、line和spk这几种widget属于外部器件,也只是简单地连接在一起,不过connect字段默认为是未连接状态。
现在,我们回过头来看看目的widget是mixer和mux这两种类型时的连接方式:
dapm_connect_mixer  用该函数连接一个目的widget为mixer类型的所有输入端:

[cpp]
view plaincopyprint?

static int dapm_connect_mixer(struct snd_soc_dapm_context *dapm,  
        struct snd_soc_dapm_widget *src, struct snd_soc_dapm_widget *dest,  
        struct snd_soc_dapm_path *path, const char *control_name)  
{  
        int i;  
  
        /* search for mixer kcontrol */  
        for (i = 0; i < dest->num_kcontrols; i++) {  
                if (!strcmp(control_name, dest->kcontrol_news[i].name)) {  
                        list_add(&path->list, &dapm->card->paths);  
                        list_add(&path->list_sink, &dest->sources);  
                        list_add(&path->list_source, &src->sinks);  
                        path->name = dest->kcontrol_news[i].name;  
                        dapm_set_path_status(dest, path, i);  
                        return 0;  
                }  
        }  
        return -ENODEV;  
}  

static int dapm_connect_mixer(struct snd_soc_dapm_context *dapm,
struct snd_soc_dapm_widget *src, struct snd_soc_dapm_widget *dest,
struct snd_soc_dapm_path *path, const char *control_name)
{
int i;

/* search for mixer kcontrol */
for (i = 0; i < dest->num_kcontrols; i++) {
if (!strcmp(control_name, dest->kcontrol_news[i].name)) {
list_add(&path->list, &dapm->card->paths);
list_add(&path->list_sink, &dest->sources);
list_add(&path->list_source, &src->sinks);
path->name = dest->kcontrol_news[i].name;
dapm_set_path_status(dest, path, i);
return 0;
}
}
return -ENODEV;
}
用需要用来连接的kcontrol的名字,和目的widget中的kcontrol模板数组中的名字相比较,找出该kcontrol在widget中的编号,path的名字设置为该kcontrol的名字,然后用dapm_set_path_status函数来初始化该输入端的连接状态。连接两个widget的链表操作和其他widget是一样的。

dapm_connect_mux 用该函数连接一个目的widget是mux类型的所有输入端:

[cpp]
view plaincopyprint?

static int dapm_connect_mux(struct snd_soc_dapm_context *dapm,  
        struct snd_soc_dapm_widget *src, struct snd_soc_dapm_widget *dest,  
        struct snd_soc_dapm_path *path, const char *control_name,  
        const struct snd_kcontrol_new *kcontrol)  
{  
        struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;  
        int i;  
  
        for (i = 0; i < e->max; i++) {  
                if (!(strcmp(control_name, e->texts[i]))) {  
                        list_add(&path->list, &dapm->card->paths);  
                        list_add(&path->list_sink, &dest->sources);  
                        list_add(&path->list_source, &src->sinks);  
                        path->name = (char*)e->texts[i];  
                        dapm_set_path_status(dest, path, 0);  
                        return 0;  
                }  
        }  
  
        return -ENODEV;  
}  

static int dapm_connect_mux(struct snd_soc_dapm_context *dapm,
struct snd_soc_dapm_widget *src, struct snd_soc_dapm_widget *dest,
struct snd_soc_dapm_path *path, const char *control_name,
const struct snd_kcontrol_new *kcontrol)
{
struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
int i;

for (i = 0; i < e->max; i++) {
if (!(strcmp(control_name, e->texts[i]))) {
list_add(&path->list, &dapm->card->paths);
list_add(&path->list_sink, &dest->sources);
list_add(&path->list_source, &src->sinks);
path->name = (char*)e->texts[i];
dapm_set_path_status(dest, path, 0);
return 0;
}
}

return -ENODEV;
}
和mixer类型一样用名字进行匹配,只不过mux类型的kcontrol只需一个,所以要通过private_value字段所指向的soc_enum结构找出匹配的输入脚编号,最后也是通过dapm_set_path_status函数来初始化该输入端的连接状态,因为只有一个kcontrol,所以第三个参数是0。连接两个widget的链表操作和其他widget也是一样的。
dapm_set_path_status    该函数根据传入widget中的kcontrol编号,读取实际寄存器的值,根据寄存器的值来初始化这个path是否处于连接状态,详细的代码这里就不贴了。

当widget之间通过path进行连接之后,他们之间的关系就如下图所示:



到这里为止,我们为声卡创建并初始化好了所需的widget,各个widget也通过path连接在了一起,接下来,dapm等待用户的指令,一旦某个dapm kcontrol被用户空间改变,利用这些连接关系,dapm会重新创建音频路径,脱离音频路径的widget会被下电,加入音频路径的widget会被上电,所有的上下电动作都会自动完成,用户空间的应用程序无需关注这些变化,它只管按需要改变某个dapm kcontrol即可。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐