linux-alsa详解12之DAPM详解5dapm kcontrol

系统中注册的各种widget需要互相连接在一起才能协调工作,连接关系通过snd_soc_dapm_route结构来定义,关于如何用snd_soc_dapm_route结构来定义路径信息,请参考:ALSA声卡驱动中的DAPM详解之三:如何定义各种widget中的"建立widget和route"一节的内容。通常,所有的路径信息会用一个snd_soc_dapm_route结构数组来定义。和widget一样,路径信息也分别存在与codec驱动,machine驱动和platform驱动中,我们一样有两种方式来注册音频路径信息:

(1)通过snd_soc_codec_driver/snd_soc_platform_driver/snd_soc_card结构中的dapm_routes和num_dapm_routes字段;
(2)在codec、platform的的probe回调中主动注册音频路径,machine驱动中则通过snd_soc_dai_link结构的init回调函数来注册音频路径;

定义一个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的初始电源状态和音频路径的初始连接状态。

1 为widget建立dapm kcontrol

讲解route之前,先看下dapm kcontrol的创建过程

 1 static int snd_soc_instantiate_card(struct snd_soc_card *card)
 2 {
 3         ......
 4         /* card bind complete so register a sound card */
 5         ret = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
 6                         card->owner, 0, &card->snd_card);
 7         ......
 8  
 9         card->dapm.bias_level = SND_SOC_BIAS_OFF;
10         card->dapm.dev = card->dev;
11         card->dapm.card = card;
12         list_add(&card->dapm.list, &card->dapm_list);
13  
14 #ifdef CONFIG_DEBUG_FS
15         snd_soc_dapm_debugfs_init(&card->dapm, card->debugfs_card_root);
16 #endif
17         ......
18         if (card->dapm_widgets)    /* 创建machine级别的widget  */
19                 snd_soc_dapm_new_controls(&card->dapm, card->dapm_widgets,
20                                           card->num_dapm_widgets);
21         ......
22         snd_soc_dapm_link_dai_widgets(card);  /*  连接dai widget  */
23  
24         if (card->controls)    /*  建立machine级别的普通kcontrol控件  */
25                 snd_soc_add_card_controls(card, card->controls, card->num_controls);
26  
27         if (card->dapm_routes)    /*  注册machine级别的路径连接信息  */
28                 snd_soc_dapm_add_routes(&card->dapm, card->dapm_routes,
29                                         card->num_dapm_routes);
30         ......
31  
32         if (card->fully_routed)    /*  如果该标志被置位,自动把codec中没有路径连接信息的引脚设置为无用widget  */
33                 list_for_each_entry(codec, &card->codec_dev_list, card_list)
34                         snd_soc_dapm_auto_nc_codec_pins(codec);
35  
36         snd_soc_dapm_new_widgets(card);    /*初始化widget包含的dapm kcontrol、电源状态和连接状态*/
37  
38         ret = snd_card_register(card->snd_card);
39         ......
40         card->instantiated = 1;
41         snd_soc_dapm_sync(&card->dapm);
42         ......
43         return 0;
44 }

正如添加的注释中所示,在完成machine级别的widget和route处理之后,调用的snd_soc_dapm_new_widgets函数,来为所有已经注册的widget初始化他们所包含的dapm kcontrol,并初始化widget的电源状态和路径连接状态。下面我们看看snd_soc_dapm_new_widgets函数的工作过程。
1.1 函数snd_soc_dapm_new_widgets

该函数通过声卡的widgets链表,遍历所有已经注册了的widget;

其中的new字段用于判断该widget是否已经执行过snd_soc_dapm_new_widgets函数;

如果num_kcontrols字段有数值,表明该widget包含有若干个dapm kcontrol,那么就需要为这些kcontrol分配一个指针数组,并把数组的首地址赋值给widget的kcontrols字段,该数组存放着指向这些kcontrol的指针,当然现在这些都是空指针,因为实际的kcontrol现在还没有被创建:

 1 /**
 2  * snd_soc_dapm_new_widgets - add new dapm widgets
 3  * @card: card to be checked for new dapm widgets
 4  *
 5  * Checks the codec for any new dapm widgets and creates them if found.
 6  *
 7  * Returns 0 for success.
 8  */
 9 int snd_soc_dapm_new_widgets(struct snd_soc_card *card)
10 {
11     struct snd_soc_dapm_widget *w;
12     unsigned int val;
13 
14     mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_INIT);
15 
16     list_for_each_entry(w, &card->widgets, list)//遍历声卡的widget 链表
17     {
18         if (w->new)//new字段用于判断该widget是否已经执行过snd_soc_dapm_new_widgets函数
19             continue;
20 
21         if (w->num_kcontrols) {//如果num_kcontrols字段有数值,表明该widget包含有若干个dapm kcontrol
22             w->kcontrols = kzalloc(w->num_kcontrols *
23                         sizeof(struct snd_kcontrol *),
24                         GFP_KERNEL);
25             if (!w->kcontrols) {
26                 mutex_unlock(&card->dapm_mutex);
27                 return -ENOMEM;
28             }
29         }
30         /*接着,对几种能影响音频路径的widget,创建并初始化它们所包含的dapm kcontrol*/api如下
31         switch(w->id) {
32         case snd_soc_dapm_switch:
33         case snd_soc_dapm_mixer:
34         case snd_soc_dapm_mixer_named_ctl:
35             dapm_new_mixer(w);
36             break;
37         case snd_soc_dapm_mux:
38         case snd_soc_dapm_demux:
39             dapm_new_mux(w);
40             break;
41         case snd_soc_dapm_pga:
42         case snd_soc_dapm_out_drv:
43             dapm_new_pga(w);
44             break;
45         case snd_soc_dapm_dai_link:
46             dapm_new_dai_link(w);
47             break;
48         default:
49             break;
50         }
51        /*然后,根据widget寄存器的当前值,初始化widget的电源状态,并设置到power字段中:*/
52         /* Read the initial power state from the device */
53         if (w->reg >= 0) {
54             soc_dapm_read(w->dapm, w->reg, &val);
55             val = val >> w->shift;
56             val &= w->mask;
57             if (val == w->on_val)
58                 w->power = 1;
59         }
60       /*接着,设置new字段,表明该widget已经初始化完成,我们还要吧该widget加入到声卡的dapm_dirty链表中,表明该widget的状态发生了变化,稍后在合适的时刻,dapm框架会扫描dapm_dirty链表,
统一处理所有已经变化的widget。为什么要统一处理?因为dapm要控制各种widget的上下电顺序,同时也是为了减少寄存器的读写次数(多个widget可能使用同一个寄存器)*/
61         w->new = 1;
62 
63         dapm_mark_dirty(w, "new widget");
64         dapm_debugfs_add_widget(w);
65     }
66     /*最后,通过dapm_power_widgets函数,统一处理所有位于dapm_dirty链表上的widget的状态改变*/
67     dapm_power_widgets(card, SND_SOC_DAPM_STREAM_NOP);
68     mutex_unlock(&card->dapm_mutex);
69     return 0;
70 }

2 创建dapm kcontrol的api

对几种能影响音频路径的widget,创建并初始化它们所包含的dapm kcontrol,需要用到的创建函数分别是:

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

 2.1 dapm mixer kcontrol 

对于mixer类型的dapm kcontrol,我们会使用dapm_new_mixer来完成具体的创建工作,代码如下:

 1 /* create new dapm mixer control */
 2 static int dapm_new_mixer(struct snd_soc_dapm_widget *w)
 3 {
 4     int i, ret;
 5     struct snd_soc_dapm_path *path;
 6     struct dapm_kcontrol_data *data;
 7 
 8     /* add kcontrol */ 因为一个mixer是由多个kcontrol组成的,每个kcontrol控制着mixer的一个输入端的开启和关闭,所以,该函数会根据kcontrol的数量做循环,逐个建立对应的kcontrol
 9     for (i = 0; i < w->num_kcontrols; i++) {
10         /* match name */widget之间使用snd_soc_path进行连接,widget的sources链表保存着所有和输入端连接的snd_soc_path结构,所以我们可以用kcontrol模板中指定的名字来匹配对应的snd_soc_path结构
11         snd_soc_dapm_widget_for_each_source_path(w, path) {
12             /* mixer/mux paths name must match control name */
13             if (path->name != (char *)w->kcontrol_news[i].name)
14                 continue;
15 
16             if (!w->kcontrols[i]) {//说明之前kcontrol没有创建
17                 ret = dapm_create_or_share_kcontrol(w, i);
18                 if (ret < 0)
19                     return ret;
20             }
21             /*说明kcontrol已经在之前创建好了,增加一个虚拟的影子widget。则通过dapm_create_or_share_mixmux_kcontrol创建这个输入端的kcontrol,同理,kcontrol对应的影子widget也会通过dapm_kcontrol_add_path判断是否需要创建*/
22             dapm_kcontrol_add_path(w->kcontrols[i], path);
23 
24             data = snd_kcontrol_chip(w->kcontrols[i]);
25             if (data->widget)
26                 snd_soc_dapm_add_path(data->widget->dapm,
27                               data->widget,
28                               path->source,
29                               NULL, NULL);
30         }
31     }
32 
33     return 0;
34 }

 第16行:因为一个输入脚可能会连接多个输入源,所以可能在上一个输入源的path关联时已经创建了这个kcontrol,所以这里判断kcontrols指针数组中对应索引中的指针值,如果已经赋值,说明kcontrol已经在之前创建好了,所以我们只要简单地把连接该输入端的path加入到kcontrol的path_list链表中,并且增加一个虚拟的影子widget,该影子widget连接和输入端对应的源widget,因为使用了kcontrol本身的reg/shift等寄存器信息,所以实际上控制的是该kcontrol的开和关,这个影子widget只有在kcontrol的autodisable字段被设置的情况下才会被创建,该特性使得source的关闭时,与之连接的mixer的输入端也可以自动关闭,这个特性通过dapm_kcontrol_add_path来实现这一点:

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

2.2  dapm mux kcontrol

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

 1 /* create new dapm mux control */
 2 static int dapm_new_mux(struct snd_soc_dapm_widget *w)
 3 {
 4     struct snd_soc_dapm_context *dapm = w->dapm;
 5     enum snd_soc_dapm_direction dir;
 6     struct snd_soc_dapm_path *path;
 7     const char *type;
 8     int ret;
 9 
10     switch (w->id) {
11     case snd_soc_dapm_mux:
12         dir = SND_SOC_DAPM_DIR_OUT;
13         type = "mux";
14         break;
15     case snd_soc_dapm_demux:
16         dir = SND_SOC_DAPM_DIR_IN;
17         type = "demux";
18         break;
19     default:
20         return -EINVAL;
21     }
22 
23     if (w->num_kcontrols != 1) {// 对于mux类型的widget,因为只会有一个kcontrol,所以在这里做一下判断
24         dev_err(dapm->dev,
25             "ASoC: %s %s has incorrect number of controls\n", type,
26             w->name);
27         return -EINVAL;
28     }
29 
30     if (list_empty(&w->edges[dir])) {
31         dev_err(dapm->dev, "ASoC: %s %s has no paths\n", type, w->name);
32         return -EINVAL;
33     }
34 
35     ret = dapm_create_or_share_kcontrol(w, 0);//同样地,和mixer类型一样,创建这个kcontrol。
36     if (ret < 0)
37         return ret;
38     /*对每个输入端所连接的path都加入dapm_kcontrol_data结构的paths链表中,并且创建一个影子widget,用于支持autodisable特性*/
39     snd_soc_dapm_widget_for_each_path(w, dir, path) {
40         if (path->name)
41             dapm_kcontrol_add_path(w->kcontrols[0], path);
42     }
43 
44     return 0;
45 }

 2.3 dapm pga kcontrol

 1 /* create new dapm volume control */
 2 static int dapm_new_pga(struct snd_soc_dapm_widget *w)
 3 {
 4     int i, ret;
 5 
 6     for (i = 0; i < w->num_kcontrols; i++) {
 7         ret = dapm_create_or_share_kcontrol(w, i);
 8         if (ret < 0)
 9             return ret;
10     }
11 
12     return 0;
13 }

2.4 函数dapm_create_or_share_kcontrol

以上创建kcontrol 最终都call此函数,完成最后的创建,下面来看此函数定义:

  1 /*
  2  * Determine if a kcontrol is shared. If it is, look it up. If it isn't,
  3  * create it. Either way, add the widget into the control's widget list
  4  */
  5 static int dapm_create_or_share_kcontrol(struct snd_soc_dapm_widget *w,
  6     int kci)
  7 {
  8     struct snd_soc_dapm_context *dapm = w->dapm;
  9     struct snd_card *card = dapm->card->snd_card;
 10     const char *prefix;
 11     size_t prefix_len;
 12     int shared;
 13     struct snd_kcontrol *kcontrol;
 14     bool wname_in_long_name, kcname_in_long_name;
 15     char *long_name = NULL;
 16     const char *name;
 17     int ret = 0;
 18 
 19     prefix = soc_dapm_prefix(dapm);
 20     if (prefix)
 21         prefix_len = strlen(prefix) + 1;
 22     else
 23         prefix_len = 0;
 24     /*为了节省内存,通过kcontrol名字的匹配查找,如果这个kcontrol已经在其他widget中已经创建好了,那不再创建,dapm_is_shared_kcontrol的参数kcontrol会返回已经创建好的kcontrol的指针*/
 25     shared = dapm_is_shared_kcontrol(dapm, w, &w->kcontrol_news[kci],
 26                      &kcontrol);
 27 
 28     if (!kcontrol) {
 29         if (shared) {
 30             wname_in_long_name = false;
 31             kcname_in_long_name = true;
 32         } else {
 33             switch (w->id) {
 34             case snd_soc_dapm_switch:
 35             case snd_soc_dapm_mixer:
 36             case snd_soc_dapm_pga:
 37             case snd_soc_dapm_out_drv:
 38                 wname_in_long_name = true;
 39                 kcname_in_long_name = true;
 40                 break;
 41             case snd_soc_dapm_mixer_named_ctl:
 42                 wname_in_long_name = false;
 43                 kcname_in_long_name = true;
 44                 break;
 45             case snd_soc_dapm_demux:
 46             case snd_soc_dapm_mux:
 47                 wname_in_long_name = true;
 48                 kcname_in_long_name = false;
 49                 break;
 50             default:
 51                 return -EINVAL;
 52             }
 53         }
 54         /*处理kcontrol的名字是否要加入codec的前缀*/
 55         if (wname_in_long_name && kcname_in_long_name) {
 56             /*
 57              * The control will get a prefix from the control
 58              * creation process but we're also using the same
 59              * prefix for widgets so cut the prefix off the
 60              * front of the widget name.
 61              */
 62             long_name = kasprintf(GFP_KERNEL, "%s %s",
 63                  w->name + prefix_len,
 64                  w->kcontrol_news[kci].name);
 65             if (long_name == NULL)
 66                 return -ENOMEM;
 67 
 68             name = long_name;
 69         } else if (wname_in_long_name) {
 70             long_name = NULL;
 71             name = w->name + prefix_len;
 72         } else {
 73             long_name = NULL;
 74             name = w->kcontrol_news[kci].name;
 75         }
 76         /*如果kcontrol指针被赋值,说明在上面的查找到了其他widget中同名的kcontrol,我们不用再次创建,只要共享该kcontrol即可*/
 77         kcontrol = snd_soc_cnew(&w->kcontrol_news[kci], NULL, name,//标准的kcontrol创建函数
 78                     prefix);
 79         if (!kcontrol) {
 80             ret = -ENOMEM;
 81             goto exit_free;
 82         }
 83 
 84         kcontrol->private_free = dapm_kcontrol_free;
 85 
 86         ret = dapm_kcontrol_data_alloc(w, kcontrol, name);//如果widget支持autodisable特性,创建与该kcontrol所对应的影子widget,该影子widget的类型是:snd_soc_dapm_kcontrol
 87         if (ret) {
 88             snd_ctl_free_one(kcontrol);
 89             goto exit_free;
 90         }
 91 
 92         ret = snd_ctl_add(card, kcontrol);//kcontrol创建函数
 93         if (ret < 0) {
 94             dev_err(dapm->dev,
 95                 "ASoC: failed to add widget %s dapm kcontrol %s: %d\n",
 96                 w->name, name, ret);
 97             goto exit_free;
 98         }
 99     }
100     /*把所有共享该kcontrol的影子widget(snd_soc_dapm_kcontrol),加入到kcontrol的private_data字段所指向的dapm_kcontrol_data结构中*/
101     ret = dapm_kcontrol_add_widget(kcontrol, w);
102     if (ret == 0)
103         w->kcontrols[kci] = kcontrol;//把创建好的kcontrol指针赋值到widget的kcontrols数组中
104 
105 exit_free:
106     kfree(long_name);
107 
108     return ret;
109 }

需要注意的是,如果kcontol支持autodisable特性,一旦kcontrol由于source的关闭而被自动关闭,则用户空间只能操作该kcontrol的cache值,只有该kcontrol再次打开时,该cache值才会被真正地更新到寄存器中。
总结一下,创建一个widget所包含的kcontrol所做的工作:
(1)循环每一个输入端,为每个输入端依次执行下面的一系列操作;
(2)为每个输入端创建一个kcontrol,能共享的则直接使用创建好的kcontrol;
(3)kcontrol的private_data字段保存着这些共享widget的信息;
(4)如果支持autodisable特性,每个输入端还要额外地创建一个虚拟的snd_soc_dapm_kcontrol类型的影子widget,该影子widget也记录在private_data字段中;
(5)创建好的kcontrol会依次存放在widget的kcontrols数组中,供路径的控制和匹配之用。

参考博文:https://blog.csdn.net/DroidPhone/java/article/details/13756651

posted @ 2020-06-24 22:48  Action_er  阅读(956)  评论(0)    收藏  举报