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
