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

浙公网安备 33010602011771号