前面我們主要着重於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
- snd_soc_dapm_new_widgets
創建widget:snd_soc_dapm_new_controls
snd_soc_dapm_new_controls函數完成widget的創建工作,並把這些創建好的widget註冊在聲卡的widgets鏈表中,我們看看他的定義:
- 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;
- }
- 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的類型做不同的處理:
- 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;
- }
- 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回調函數 |
---|---|
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_out | dapm_adc_check_power |
snd_soc_dapm_dai_in | dapm_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 |
- 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鏈表
- /* machine layer set ups unconnected pins and insertions */
- w->connected = 1;
- return w;
- 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
- snd_soc_dapm_enable_pin
- snd_soc_dapm_force_enable_pin
- snd_soc_dapm_disable_pin
- snd_soc_dapm_nc_pin
- 爲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有關:
- 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;
- }
snd_soc_dapm_new_widgets函數
該函數通過聲卡的widgets鏈表,遍歷所有已經註冊了的widget,其中的new字段用於判斷該widget是否已經執行過snd_soc_dapm_new_widgets函數,如果num_kcontrols字段有數值,表明該widget包含有若干個dapm kcontrol,那麼就需要爲這些kcontrol分配一個指針數組,並把數組的首地址賦值給widget的kcontrols字段,該數組存放着指向這些kcontrol的指針,當然現在這些都是空指針,因爲實際的kcontrol現在還沒有被創建:- 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);
- ......
- }
- 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;
- /* 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;
- }
- w->new = 1;
- dapm_mark_dirty(w, "new widget");
- dapm_debugfs_add_widget(w);
最後,通過dapm_power_widgets函數,統一處理所有位於dapm_dirty鏈表上的widget的狀態改變:
- dapm_power_widgets(card, SND_SOC_DAPM_STREAM_NOP);
- ......
- return 0;
dapm mixer kcontrol
- 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 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);
- }
- }
dapm mux kcontrol
因爲一個widget最多隻會包含一個mux類型的damp kcontrol,所以他的創建方法稍有不同,dapm框架使用dapm_new_mux函數來創建mux類型的dapm kcontrol:- 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;
- }
dapm pga kcontrol
- 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中,簡化後的代碼如下:- 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;
- }
(3) 標準的kcontrol創建函數,請參看:Linux ALSA聲卡驅動之四:Control設備的創建中的“創建control“一節的內容。
現在。我們總結一下,創建一個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來註冊這些連接信息,接下來我們就是要分析該函數的具體實現方式:
- 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;
- }
- 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;
- }
- }
下面,如果在本dapm context中沒有找到,則使用別的dapm context中找到的widget:
- if (!wsink)
- wsink = wtsink;
- if (!wsource)
- wsource = wtsource;
- ret = snd_soc_dapm_add_path(dapm, wsource, wsink, route->control,
- route->connected);
- ......
- return 0;
- }
- 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);
- /* 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是一個輸入腳,如果源widget是mic、line、micbias或output,則認爲目的widget具有外部連接關係。
- 源widget是一個輸出腳,如果目的widget是spk、hp、line或input,則認爲源widget具有外部連接關係。
- 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;
- }
- /* 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;
- 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_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;
- }
- 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_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;
- }
dapm_set_path_status 該函數根據傳入widget中的kcontrol編號,讀取實際寄存器的值,根據寄存器的值來初始化這個path是否處於連接狀態,詳細的代碼這裏就不貼了。
到這裏爲止,我們爲聲卡創建並初始化好了所需的widget,各個widget也通過path連接在了一起,接下來,dapm等待用戶的指令,一旦某個dapm kcontrol被用戶空間改變,利用這些連接關係,dapm會重新創建音頻路徑,脫離音頻路徑的widget會被下電,加入音頻路徑的widget會被上電,所有的上下電動作都會自動完成,用戶空間的應用程序無需關注這些變化,它只管按需要改變某個dapm kcontrol即可