1 音频路径的注册
系统中注册的各种widget需要互相连接在一起才能协调工作,连接关系通过snd_soc_dapm_route结构来定义,关于如何用snd_soc_dapm_route结构来定义路径信息,参考linux-alsa详解10之DAPM详解3各种widget定义。通常,所有的路径信息会用一个snd_soc_dapm_route结构数组来定义。和widget一样,路径信息也分别存在与codec驱动,machine驱动和platform驱动中,通过两步来注册音频路径信息:
(1)通过snd_soc_codec_driver/snd_soc_platform_driver/snd_soc_card结构中component成员的dapm_routes和num_dapm_routes字段初始化;
(2)在codec、platform的的probe回调中主动注册音频路径,machine驱动中则在函数snd_soc_instantiate_card回调函数来注册音频路径;
两种方法最终都是通过调用snd_soc_dapm_add_routes函数来完成音频路径的注册工作的。
1.1 codec、platform注册音频路径
和widget注册一样,snd_soc_instantiate_card中,machine匹配对应的codec、platform后,分别调用codec和platform probe函数,probe函数中调用snd_soc_dapm_add_routes函数来完成音频路径的注册:
1 static int soc_probe_component(struct snd_soc_card *card, 2 struct snd_soc_component *component) 3 { 4 ... 5 6 if (component->controls) 7 snd_soc_add_component_controls(component, component->controls, 8 component->num_controls); 9 if (component->dapm_routes) 10 snd_soc_dapm_add_routes(dapm, component->dapm_routes, 11 component->num_dapm_routes); 12 13 list_add(&dapm->list, &card->dapm_list); 14 15 ... 16 }
1.2 machine注册音频路径
snd_soc_instantiate_card中调用函数snd_soc_dapm_add_routes注册音频路径:
1 static int snd_soc_instantiate_card(struct snd_soc_card *card) 2 { 3 ... 4 5 snd_soc_dapm_link_dai_widgets(card); 6 snd_soc_dapm_connect_dai_link_widgets(card); 7 8 if (card->controls) 9 snd_soc_add_card_controls(card, card->controls, card->num_controls); 10 11 if (card->dapm_routes) 12 snd_soc_dapm_add_routes(&card->dapm, card->dapm_routes, 13 card->num_dapm_routes); 14 15 if (card->of_dapm_routes) 16 snd_soc_dapm_add_routes(&card->dapm, card->of_dapm_routes, 17 card->num_of_dapm_routes); 18 19 ... 20 }
2 注册音频路径函数
如果widget之间没有连接关系,dapm就无法实现动态的电源管理工作,正是widget之间有了连结关系,这些连接关系形成了一条所谓的完成的音频路径,dapm可以顺着这条路径,统一控制路径上所有widget的电源状态,前面我们已经知道,widget之间是使用snd_soc_path结构进行连接的,驱动要做的是定义一个snd_soc_route结构数组,该数组的每个条目描述了目的widget的和源widget的名称,以及控制这个连接的kcontrol的名称,最终,驱动程序使用api函数snd_soc_dapm_add_routes来注册这些连接信息,接下来我们就是要分析该函数的具体实现方式:
1 int snd_soc_dapm_add_routes(struct snd_soc_dapm_context *dapm, 2 const struct snd_soc_dapm_route *route, int num) 3 { 4 int i, r, ret = 0; 5 6 mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_INIT); 7 for (i = 0; i < num; i++) { 8 r = snd_soc_dapm_add_route(dapm, route); 9 if (r < 0) { 10 dev_err(dapm->dev, "ASoC: Failed to add route %s -> %s -> %s\n", 11 route->source, 12 route->control ? route->control : "direct", 13 route->sink); 14 ret = r; 15 } 16 route++; 17 } 18 mutex_unlock(&dapm->card->dapm_mutex); 19 20 return ret; 21 }
该函数只是一个循环,依次对参数传入的数组调用snd_soc_dapm_add_route,主要的工作由snd_soc_dapm_add_route完成。
2.1 函数snd_soc_dapm_add_route
1 static int snd_soc_dapm_add_route(struct snd_soc_dapm_context *dapm, 2 const struct snd_soc_dapm_route *route) 3 { 4 struct snd_soc_dapm_widget *wsource = NULL, *wsink = NULL, *w; 5 struct snd_soc_dapm_widget *wtsource = NULL, *wtsink = NULL; 6 const char *sink; 7 const char *source; 8 char prefixed_sink[80]; 9 char prefixed_source[80]; 10 const char *prefix; 11 int ret; 12 /*名称前缀的处理部分*/ 13 prefix = soc_dapm_prefix(dapm); 14 if (prefix) { 15 snprintf(prefixed_sink, sizeof(prefixed_sink), "%s %s", 16 prefix, route->sink); 17 sink = prefixed_sink; 18 snprintf(prefixed_source, sizeof(prefixed_source), "%s %s", 19 prefix, route->source); 20 source = prefixed_source; 21 } else { 22 sink = route->sink; 23 source = route->source; 24 } 25 26 wsource = dapm_wcache_lookup(&dapm->path_source_cache, source); 27 wsink = dapm_wcache_lookup(&dapm->path_sink_cache, sink); 28 29 if (wsink && wsource) 30 goto skip_search; 31 32 /* 33 * find src and dest widgets over all widgets but favor a widget from 34 * current DAPM context 35 */用widget的名字来比较,遍历声卡的widgets链表,找出源widget和目的widget的指针 36 list_for_each_entry(w, &dapm->card->widgets, list) { 37 if (!wsink && !(strcmp(w->name, sink))) { 38 wtsink = w;//目的widget指针 39 if (w->dapm == dapm) { 40 wsink = w; 41 if (wsource) 42 break; 43 } 44 continue; 45 } 46 if (!wsource && !(strcmp(w->name, source))) { 47 wtsource = w;//源widget指针 48 if (w->dapm == dapm) { 49 wsource = w; 50 if (wsink) 51 break; 52 } 53 } 54 } 55 /* use widget from another DAPM context if not found from this */如果在本dapm context中没有找到,则使用别的dapm context中找到的widget 56 if (!wsink) 57 wsink = wtsink; 58 if (!wsource) 59 wsource = wtsource; 60 61 if (wsource == NULL) { 62 dev_err(dapm->dev, "ASoC: no source widget found for %s\n", 63 route->source); 64 return -ENODEV; 65 } 66 if (wsink == NULL) { 67 dev_err(dapm->dev, "ASoC: no sink widget found for %s\n", 68 route->sink); 69 return -ENODEV; 70 } 71 72 skip_search: 73 dapm_wcache_update(&dapm->path_sink_cache, wsink); 74 dapm_wcache_update(&dapm->path_source_cache, wsource); 75 76 ret = snd_soc_dapm_add_path(dapm, wsource, wsink, route->control,//增加一条连接信息 77 route->connected); 78 if (ret) 79 goto err; 80 81 return 0; 82 err: 83 dev_warn(dapm->dev, "ASoC: no dapm match for %s --> %s --> %s\n", 84 source, route->control, sink); 85 return ret; 86 }
2.2 函数snd_soc_dapm_add_path
snd_soc_dapm_add_path函数是整个调用链条中的关键:
1 static int snd_soc_dapm_add_path(struct snd_soc_dapm_context *dapm, 2 struct snd_soc_dapm_widget *wsource, struct snd_soc_dapm_widget *wsink, 3 const char *control, 4 int (*connected)(struct snd_soc_dapm_widget *source, 5 struct snd_soc_dapm_widget *sink)) 6 { 7 struct snd_soc_dapm_widget *widgets[2]; 8 enum snd_soc_dapm_direction dir; 9 struct snd_soc_dapm_path *path; 10 int ret; 11 12 if (wsink->is_supply && !wsource->is_supply) { 13 dev_err(dapm->dev, 14 "Connecting non-supply widget to supply widget is not supported (%s -> %s)\n", 15 wsource->name, wsink->name); 16 return -EINVAL; 17 } 18 19 if (connected && !wsource->is_supply) { 20 dev_err(dapm->dev, 21 "connected() callback only supported for supply widgets (%s -> %s)\n", 22 wsource->name, wsink->name); 23 return -EINVAL; 24 } 25 26 if (wsource->is_supply && control) { 27 dev_err(dapm->dev, 28 "Conditional paths are not supported for supply widgets (%s -> [%s] -> %s)\n", 29 wsource->name, control, wsink->name); 30 return -EINVAL; 31 } 32 33 ret = snd_soc_dapm_check_dynamic_path(dapm, wsource, wsink, control); 34 if (ret) 35 return ret; 36 /*为这个连接分配了一个snd_soc_path结构*/ 37 path = kzalloc(sizeof(struct snd_soc_dapm_path), GFP_KERNEL); 38 if (!path) 39 return -ENOMEM; 40 /*path的source和sink字段分别指向源widget和目的widget*/ 41 path->node[SND_SOC_DAPM_DIR_IN] = wsource; 42 path->node[SND_SOC_DAPM_DIR_OUT] = wsink; 43 widgets[SND_SOC_DAPM_DIR_IN] = wsource; 44 widgets[SND_SOC_DAPM_DIR_OUT] = wsink; 45 /*connected字段保存connected回调函数*/ 46 path->connected = connected; 47 INIT_LIST_HEAD(&path->list);//初始化几个snd_soc_path结构中的链表 48 INIT_LIST_HEAD(&path->list_kcontrol); 49 50 if (wsource->is_supply || wsink->is_supply) 51 path->is_supply = 1; 52 /*增加了连结关系,所以把源widget和目的widget加入到dapm_dirty链表中。如果没有kcontrol来控制该连接关系,则这是一个静态连接,直接用path把它们连接在一起*/ 53 /* connect static paths */ 54 if (control == NULL) { 55 path->connect = 1; 56 } else { 57 switch (wsource->id) { 58 case snd_soc_dapm_demux: 59 ret = dapm_connect_mux(dapm, path, control, wsource); 60 if (ret) 61 goto err; 62 break; 63 default: 64 break; 65 } 66 /*按照目的widget来判断,如果属于以上这些类型,直接把它们连接在一起即可。目的widget如果是mixer和mux类型,分别用dapm_connect_mixer和dapm_connect_mux函数完成连接工作*/ 67 switch (wsink->id) { 68 case snd_soc_dapm_mux: 69 ret = dapm_connect_mux(dapm, path, control, wsink); 70 if (ret != 0) 71 goto err; 72 break; 73 case snd_soc_dapm_switch: 74 case snd_soc_dapm_mixer: 75 case snd_soc_dapm_mixer_named_ctl: 76 ret = dapm_connect_mixer(dapm, path, control); 77 if (ret != 0) 78 goto err; 79 break; 80 default: 81 break; 82 } 83 } 84 85 list_add(&path->list, &dapm->card->paths); 86 snd_soc_dapm_for_each_direction(dir) 87 list_add(&path->list_node[dir], &widgets[dir]->edges[dir]); 88 89 snd_soc_dapm_for_each_direction(dir) { 90 dapm_update_widget_flags(widgets[dir]); 91 dapm_mark_dirty(widgets[dir], "Route added"); 92 } 93 94 if (dapm->card->instantiated && path->connect) 95 dapm_path_invalidate(path); 96 97 return 0; 98 err: 99 kfree(path); 100 return ret; 101 }
2.3 函数dapm_connect_mixer
连接一个目的widget为mixer类型的所有输入端
1 /* connect mixer widget to its interconnecting audio paths */ 2 static int dapm_connect_mixer(struct snd_soc_dapm_context *dapm, 3 struct snd_soc_dapm_path *path, const char *control_name) 4 { 5 int i; 6 7 /* search for mixer kcontrol */ 8 for (i = 0; i < path->sink->num_kcontrols; i++) { 9 if (!strcmp(control_name, path->sink->kcontrol_news[i].name)) { 10 path->name = path->sink->kcontrol_news[i].name; 11 dapm_set_mixer_path_status(path, i); 12 return 0; 13 } 14 } 15 return -ENODEV; 16 }
用需要用来连接的kcontrol的名字,和目的widget中的kcontrol模板数组中的名字相比较,找出该kcontrol在widget中的编号,path的名字设置为该kcontrol的名字,然后用dapm_set_path_status函数来初始化该输入端的连接状态。连接两个widget的链表操作和其他widget是一样的。
2.3.1 函数dapm_set_mixer_path_status
根据传入widget中的kcontrol编号,读取实际寄存器的值,根据寄存器的值来初始化这个path是否处于连接状态。
1 /* set up initial codec paths */ 2 static void dapm_set_mixer_path_status(struct snd_soc_dapm_path *p, int i) 3 { 4 struct soc_mixer_control *mc = (struct soc_mixer_control *) 5 p->sink->kcontrol_news[i].private_value; 6 unsigned int reg = mc->reg; 7 unsigned int shift = mc->shift; 8 unsigned int max = mc->max; 9 unsigned int mask = (1 << fls(max)) - 1; 10 unsigned int invert = mc->invert; 11 unsigned int val; 12 13 if (reg != SND_SOC_NOPM) { 14 soc_dapm_read(p->sink->dapm, reg, &val); 15 val = (val >> shift) & mask; 16 if (invert) 17 val = max - val; 18 p->connect = !!val; 19 } else { 20 p->connect = 0; 21 } 22 }
2.4 函数dapm_connect_mux
用该函数连接一个目的widget是mux类型的所有输入端。
1 /* connect mux widget to its interconnecting audio paths */ 2 static int dapm_connect_mux(struct snd_soc_dapm_context *dapm, 3 struct snd_soc_dapm_path *path, const char *control_name, 4 struct snd_soc_dapm_widget *w) 5 { 6 const struct snd_kcontrol_new *kcontrol = &w->kcontrol_news[0]; 7 struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; 8 unsigned int val, item; 9 int i; 10 11 if (e->reg != SND_SOC_NOPM) { 12 soc_dapm_read(dapm, e->reg, &val); 13 val = (val >> e->shift_l) & e->mask; 14 item = snd_soc_enum_val_to_item(e, val); 15 } else { 16 /* since a virtual mux has no backing registers to 17 * decide which path to connect, it will try to match 18 * with the first enumeration. This is to ensure 19 * that the default mux choice (the first) will be 20 * correctly powered up during initialization. 21 */ 22 item = 0; 23 } 24 25 for (i = 0; i < e->items; i++) { 26 if (!(strcmp(control_name, e->texts[i]))) { 27 path->name = e->texts[i]; 28 if (i == item) 29 path->connect = 1; 30 else 31 path->connect = 0; 32 return 0; 33 } 34 } 35 36 return -ENODEV; 37 }
和mixer类型一样用名字进行匹配,只不过mux类型的kcontrol只需一个,所以要通过private_value字段所指向的soc_enum结构找出匹配的输入脚编号。初始化该输入端的连接状态。
3 总结
当widget之间通过path进行连接之后,他们之间的关系就如下图所示:
到这里为止,我们为声卡创建并初始化好了所需的widget,各个widget也通过path连接在了一起,接下来,dapm等待用户的指令,一旦某个dapm kcontrol被用户空间改变,利用这些连接关系,dapm会重新创建音频路径,脱离音频路径的widget会被下电,加入音频路径的widget会被上电,所有的上下电动作都会自动完成,用户空间的应用程序无需关注这些变化,它只管按需要改变某个dapm kcontrol即可。
参考博文:https://blog.csdn.net/DroidPhone/java/article/details/14052861