文章目錄
- 概述
- 1)codec對象-WM8960
- 2)ALSA下的kcontrol的構造與使用
- 3)ASOC-ALSA下的kcontrol構造與使用
- 1、通用寄存器對象 - kcontrol
- 2、DAPM下的寄存器對象-widget
- 3、如何構造widget?
- 4、抽象對象widget、route與path
- 1)route與path的關系?
- 2)widget與path的關系
- 3)一條完整的path - complete path
- 3)complete path是如何生成的?
- 4)DAPM下的數據結構
- 5)DAPM場景分析
- 1、widget什么時候會被打開?
- 2、tinymix/tinyplay/tinymix調用DAPM
概述
ALSA聲卡驅動中的DAPM詳解之一 :https://blog.csdn.net/DroidPhone/article/details/12793293基本概念
1、DAPM : Dynamic Audio Power Management
>>可以當做ASoc-ALSA系統下的一個子系統,構造也是相當復雜,但聲卡驅動需要熟悉此框架,使用其框架的宏和API來構造(電源)寄存器對象;產生的背景:
1)ASOC框架中動態音頻電源管理子系統 - 設計的目的是省電
2)同時也達到減少暴露給應用的操作(寄存器操作)效果 - 比如封裝打開某個input通道、MUX、mixer操作等等;
3)
DAPM是Dynamic Audio Power Management的縮寫,直譯過來就是動態音頻電源管理的意思,DAPM是為了使基于linux的移動設備上的音頻子系統,在任何時候都工作在最小功耗狀態下。DAPM對用戶空間的應用程序來說是透明的,所有與電源相關的開關都在ASoc core中完成。用戶空間的應用程序無需對代碼做出修改,也無需重新編譯,DAPM根據當前激活的音頻流(playback/capture)和聲卡中的mixer等的配置來決定那些音頻控件的電源開關被打開或關閉。
4)
在嵌入式領域中,由于數字功放內部的寄存器很多,沒有DAPM之前,我們可以在init里面逐一將其設置好,即完全打開數字功放的所有功能,雖然簡單,但比較費電,因此設計DAPM來控制按需打開(這套框架也相當復雜);
5)
此前在ASOC-ALSA分析中,看到很多DAPM的影子,現在來看看DAPM子系統如何嵌入到ASOC框架control中的,注意數字功放里面很多寄存器,DAPM主要控制電源相關的寄存器(Mixer、MUX、switch),音量volume則不需要。2、DAPM Widget用于表示音頻路徑中的物理連接點或控制點,根據一定條件(自動路由管理)觸發關閉/打開 控制點以達到節能狀態3、kcontrol : 代表一個控制寄存器(可能是寄存器中某幾個bit),即功能控制4、coalesced : 合并
DAPM框架有較多的抽象概念,理解起來比較費勁和枯燥,我們先逐個拆解"對象",并梳理這些對象之間的關系,最后從宏觀上看應用的效果,接下來使用WM8960 (有豐富的寄存器) 功放作為硬件聲卡展開說明
1)codec對象-WM8960
Input Signal Path
1、PGA 是 “可編程增益放大器”(Programmable Gain Amplifier);
2、以上是Left Input/Left Output示意圖,三路輸入對應三條音頻路徑,輸入到輸出需要經過內部多個器件(寄存器);
1)比如:LINPUT1 -> LMN1 -> Left Input PGA -> LMICBOOST -> LMIC2B -> Left Boose Mixer -> Left ADC
2)可以看到有些部件是功能部件,有些是開關部件;
3、假如用戶只適用LINPUT1,其它兩路不適用,全部寄存器都設置打開,而有些模擬器件即使沒有輸入源信號也會耗電,而DAPM的任務是按需打開;
4、哪些寄存器對象是需要DAPM控制?
1)不是所有器件都需要DAPM控制,跟電源相關且可以通過寄存器關閉的器件,則需要DAPM參與把控,比如LIN1BOOST/LIN2BOOST/LIN3BOOST/Left Boost Mixer,而LINVOL則不需要DAPM參與把控;
2)ALSA下的kcontrol的構造與使用
1、kcontrol對應的結構體 - snd_kcontrol_new 與 snd_kcontrol
struct snd_kcontrol_new {snd_ctl_elem_iface_t iface; /* interface identifier */const unsigned char *name; /* ASCII name of item */snd_kcontrol_info_t *info;snd_kcontrol_get_t *get;snd_kcontrol_put_t *put;unsigned long private_value;
};以snd_kcontrol_new為模版,構造實際的寄存器對象-snd_kcontrol
struct snd_kcontrol {struct list_head list; /* list of controls */struct snd_ctl_elem_id id;unsigned int count; /* count of same elements */ 相同元素-比如左右聲道,在鏈表里面會占用兩個idsnd_kcontrol_info_t *info;snd_kcontrol_get_t *get;snd_kcontrol_put_t *put;union {snd_kcontrol_tlv_rw_t *c;const unsigned int *p;} tlv;unsigned long private_value;void *private_data;void (*private_free)(struct snd_kcontrol *kcontrol);struct snd_kcontrol_volatile vd[0]; /* volatile data */
};info/get/put這些函數怎么使用,直接看用戶層怎么使用即可一目了然2、tinymix的使用
1)查看tinymix可控的寄存器列表
root@# tinymix
Mixer name: 'audiocodec' //const unsigned char *name;
Number of controls: 16
ctl type num name value
1 INT 1 "digital volume" 0 //snd_kcontrol_info_t *info;
2 INT 1 "LINEIN to output mixer gain control" 3
3 BOOL 1 "LINEOUT Switch" On
...2)設置音量
1、根據name來操作
tinymix "LINEOUT volume" "2" //snd_kcontrol_put_t *put;
2、根據ctl id來操作 - 使用成員struct snd_ctl_elem_id id表示,數值代表加入card list里面的順序。
tinymix 1 "2" //snd_kcontrol_put_t *put; //
3、private_value字段
可以用來定義該控件所對應的寄存器的地址以及對應的控制位在寄存器中的位置信息,供給put/get/info函數所使用!
以Bass Boost 寄存器為例:
1)一個寄存器會有多個功能,一個kcontrol代表某個功能,shift代表從第幾個bit開始,max表示占多少bit;
2)snd_kcontrol_put_t *put; 會將用戶傳下來的值 經過轉換 最終寫到對應寄存器中去;
3)ASOC-ALSA下的kcontrol構造與使用
ASOC-ALSA相對ALSA,進一步封裝,但最終還是會調用ALSA那套kcontrol邏輯
1、通用寄存器對象 - kcontrol
內核提供一堆宏供開發者定義kcontrol(用來定義所有寄存器對象)
1)android\kernel\fusion\4.19\include\sound\soc.h
/** Convenience kcontrol builders*/
#define SOC_SINGLE(xname, reg, shift, max, invert) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \.info = snd_soc_info_volsw, .get = snd_soc_get_volsw,\.put = snd_soc_put_volsw, \.private_value = SOC_SINGLE_VALUE(reg, shift, max, invert, 0) }2)android\kernel\fusion\4.19\sound\soc\soc-ops.c
通用info/put/get對應的操作函數3)開發者需要根據寄存器屬于哪一種分類,每種不同分類的寄存器所對應的put/get不一樣,然后使用對應的內核宏來進行構造
常用的寄存器對象 - 基本可以覆蓋所有功放寄存器類型
1、SOC_SINGLE 寄存器對象 - 操作一個寄存器
2、SOC_SINGLE_TLV 寄存器對象 - 用于音量,增益寄存器
3、SOC_DOUBLE 寄存器對象 - 操作兩個寄存器
4、Mixer 寄存器對象 - 多合一寄存器
5、Mux 寄存器對象 - 多選一寄存器6、如果需要自定義put/get函數,可以使用EXT后綴的內核宏
SOC_SINGLE_EXT
SOC_DOUBLE_EXT4)構造例子
static const struct snd_kcontrol_new wm8960_snd_controls[] = {
SOC_DOUBLE_R("Capture Volume ZC Switch", WM8960_LINVOL, WM8960_RINVOL,6, 1, 0),
SOC_SINGLE_TLV("Left Input Boost Mixer LINPUT1 Volume",WM8960_LINPATH, 4, 3, 0, micboost_tlv),
SOC_SINGLE("Speaker DC Volume", WM8960_CLASSD3, 3, 5, 0),
SOC_ENUM("ADC Polarity", wm8960_enum[0]),
...
}細節可以展開代碼進行研究
2、DAPM下的寄存器對象-widget
1、DAMP子系統下 基本操作單元不是kcontrol,而是widget,一個widget可以包含一個kcontrol,也可以包含多個kcontrol;
2、widget是對kcontrol的進一個封裝,使其具備更多的能力(連接、狀態),使其與電源管理關聯起來;
3、DAPM框架下 定義kcontrol(主要是電源相關的寄存器)的宏,分類很多!android\kernel\fusion\4.19\include\sound\soc-dapm.h
/** SoC dynamic audio power management** We can have up to 4 power domains* 1. Codec domain - VREF, VMID* Usually controlled at codec probe/remove, although can be set* at stream time if power is not needed for sidetone, etc.* 2. Platform/Machine domain - physically connected inputs and outputs* Is platform/machine and user action specific, is set in the machine* driver and by userspace e.g when HP are inserted* 3. Path domain - Internal codec path mixers* Are automatically set when mixer and mux settings are* changed by the user.* 4. Stream domain - DAC's and ADC's.* Enabled when stream playback/capture is started.*//* codec domain */
#define SND_SOC_DAPM_VMID(wname) \
{ .id = snd_soc_dapm_vmid, .name = wname, .kcontrol_news = NULL, \.num_kcontrols = 0}/* platform domain */
#define SND_SOC_DAPM_INPUT(wname) \
{ .id = snd_soc_dapm_input, .name = wname, .kcontrol_news = NULL, \.num_kcontrols = 0, .reg = SND_SOC_NOPM }/* path domain */
#define SND_SOC_DAPM_MIXER(wname, wreg, wshift, winvert, \wcontrols, wncontrols)\
{ .id = snd_soc_dapm_mixer, .name = wname, \SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \.kcontrol_news = wcontrols, .num_kcontrols = wncontrols}
3、如何構造widget?
widget是DAPM下的基本操作單元,widget實際上是一個抽象感念,
與kcontrol的關系,一個widget可以包含一個kcontrol,也可以包含多個kcontrol,也可不包含kcontrol
構造例子
widget的構造分為兩個階段,
第一階段是使用內核宏進行靜態構造(name、reg等賦值);
第二階段則使用內核api snd_soc_dapm_new_controls進行動態構造1、內核宏(codc驅動)
static const struct snd_soc_dapm_widget wm8960_dapm_widgets[] = {
SND_SOC_DAPM_INPUT("LINPUT1"), //虛擬端點,不包含kcontrolSND_SOC_DAPM_SUPPLY("MICB", WM8960_POWER1, 1, 0, NULL, 0), //包含一個kcontrolSND_SOC_DAPM_MIXER("Left Boost Mixer", WM8960_POWER1, 5, 0, //包含多個kcontrolwm8960_lin_boost, ARRAY_SIZE(wm8960_lin_boost)),SND_SOC_DAPM_MIXER("Left Input Mixer", WM8960_POWER3, 5, 0,wm8960_lin, ARRAY_SIZE(wm8960_lin)),SND_SOC_DAPM_ADC("Left ADC", "Capture", WM8960_POWER1, 3, 0),SND_SOC_DAPM_DAC("Left DAC", "Playback", WM8960_POWER2, 8, 0),SND_SOC_DAPM_MIXER("Left Output Mixer", WM8960_POWER3, 3, 0,&wm8960_loutput_mixer[0],ARRAY_SIZE(wm8960_loutput_mixer)),SND_SOC_DAPM_PGA("LOUT1 PGA", WM8960_POWER2, 6, 0, NULL, 0),SND_SOC_DAPM_PGA("Left Speaker PGA", WM8960_POWER2, 4, 0, NULL, 0),SND_SOC_DAPM_PGA("Left Speaker Output", WM8960_CLASSD1, 6, 0, NULL, 0),SND_SOC_DAPM_OUTPUT("SPK_LP"), //虛擬端點,不包含kcontrol
};static const struct snd_kcontrol_new wm8960_lin_boost[] = {
SOC_DAPM_SINGLE("LINPUT2 Switch", WM8960_LINPATH, 6, 1, 0),
SOC_DAPM_SINGLE("LINPUT3 Switch", WM8960_LINPATH, 7, 1, 0),
SOC_DAPM_SINGLE("LINPUT1 Switch", WM8960_LINPATH, 8, 1, 0),
};注意與kcontrol不同的get/put函數
.get = snd_soc_dapm_get_volsw,
.put = snd_soc_dapm_put_volsw, //調用很深!簡單看看snd_soc_dapm_put_volsw的調用
--dapm_kcontrol_set_value(kcontrol, val | (rval << width));
--soc_dapm_mixer_update_power(card, kcontrol, connect, rconnect);
----soc_dapm_connect_path(path, rconnect, "mixer update");
----dapm_power_widgets(card, SND_SOC_DAPM_STREAM_NOP);
------dapm_seq_run(card, &down_list, event, false);
------dapm_widget_update(card);
------dapm_seq_run(card, &up_list, event, true);2、內核api snd_soc_dapm_new_controls (machine驅動)
wm8960_probe
--wm8960_add_widgets
----snd_soc_dapm_new_controls
------snd_soc_dapm_new_control_unlocked(dapm, widget);//涉及很多變量設置,需要花費一定精力去梳理
//根據dapm type,掛上對應check_power方法,用于判斷該widget是否需要上電
//上電條件:1、widget位于complete path上;2、有APP在使用聲卡;
--------w->is_ep = SND_SOC_DAPM_EP_SOURCE; //當type=snd_soc_dapm_mic,會設置endpoint
--------w->power_check = dapm_generic_check_power;
--------w->connected = 1;
----snd_soc_dapm_link_dai_widgets(card);
----snd_soc_dapm_connect_dai_link_widgets(card);
----snd_soc_dapm_add_routes()
----snd_soc_dapm_new_widgets(card);
------dapm_new_mixer(w);
--------dapm_create_or_share_kcontrol(w, i);
----------snd_soc_cnew()
----------snd_ctl_add(card, kcontrol); //添加kcontrol到card鏈表里面去,kcontrol的name=(widget name + kcontrol name)
------dapm_new_mux(w);3、實際開發時,操作相近的例子進行構造
4、幾點疑問:
1)通用寄存器對象與DAPM下的寄存器對象會重復嗎? 不會重復
2)DAPM的kcontrol也需要給用戶使用? 需要,在machine probe中會掛到card下的kcontrol鏈表中,在哪里觸發?在machine代碼probe一路下來,最終在snd_soc_dapm_new_widgets()
4、抽象對象widget、route與path
1)route與path的關系?
1、內核設計為了方便path的表示,使用route用來表示path的鏈接,然后在初始化代碼中自動構造出path。2、比如:
struct snd_soc_dapm_route {const char *sink; //終點widget的nameconst char *control; //連接兩個widget的kcontrolconst char *source; //起始widget的name
};static const struct snd_soc_dapm_route audio_paths[] = {{ "Left Boost Mixer", "LINPUT1 Switch", "LINPUT1" },
}
3、可以看出一個path只能連接相鄰的兩個widget4、route如何轉化為path?
wm8960_probe
--wm8960_add_widgets
----snd_soc_dapm_add_routes()
------snd_soc_dapm_add_route(dapm, route);
//最終添加到snd_soc_dapm_context下path鏈表中
--------snd_soc_dapm_add_path(dapm, wsource, wsink, route->control,route->connected); snd_soc_dapm_add_path()的工作
1)找到source/sink,構造path
2)設置path中的connect,當path中的kcontrol為空 (即path中間沒有switch),則connect恒為1;不為空,則根據kcontrol 寄存器的值設置connect;
3)將path放進鏈表里面去,構造出complete path
2)widget與path的關系
1、一個path相當于一個跳線,但只能連上 物理硬件上相鄰的widget;
2、complete path : 多個path連起來,完整實現一條音頻流通路 即為一個complete path;
3)一條完整的path - complete path
1、多個path組成完整的一條通路,叫做完全路徑,比如一個錄音功能需要一條完全路徑來實現
2、舉個例子
static const struct snd_soc_dapm_route audio_paths[] = {{ "Left Boost Mixer", "LINPUT1 Switch", "LINPUT1" }, //path1{ "Left Input Mixer", "Boost Switch", "Left Boost Mixer" }, //path2{ "Left ADC", NULL, "Left Input Mixer" }, //path3
}
path1->path2->path3 組成了一條完整的complete path
鏈表結構圖
3)complete path是如何生成的?
1、complete path其實是抽象出來的概念,不是靜態定義的,而是利用鏈表算法動態生成,通過遞歸遍歷算法 動態探索出一條complete path。2、怎么從一個widget,遍歷所有跟此widget在同一條complete path上的widget?在構造path時,每個widget都有記錄此path的變量,稱為edges,區分是in還是out
snd_soc_dapm_add_path(){list_add(&path->list, &dapm->card->paths);snd_soc_dapm_for_each_direction(dir)list_add(&path->list_node[dir], &widgets[dir]->edges[dir]);
}#define snd_soc_dapm_for_each_direction(dir) \for ((dir) = SND_SOC_DAPM_DIR_IN; (dir) <= SND_SOC_DAPM_DIR_OUT; \(dir)++)
1、舉個例子
widget1的拓撲關系
完全展開widget的拓撲關系,可以包含此widget在同一條complete path上的所有widget;
最后利用遞歸算法(利用edges[in] 實現向后遍歷,利用edges[out] 實現向前遍歷)
4)DAPM下的數據結構
1、DAPM數據結構里存在大量鏈表;
2、DAPM結構體指向混亂,但有個總的源頭 - snd_soc_card;
5)DAPM場景分析
1、widget什么時候會被打開?
上層發出請求后(指tinymix / tinyplay),會觸發一次事件,判斷widget的成員connect為1時,打開此widget
那connect在哪里被設置?
1)注冊route,path時會設置一次;
2)后續上層發出請求后 會調用check_power進行動態更新;
1、check power的實現
android\kernel\fusion\4.19\sound\soc\soc-dapm.c
dapm_generic_check_power() //DAPM實現的關鍵,也是重難點
--is_connected_input_ep() //ep endpoint,如 input1/input2
--is_connected_output_ep() //ep endpoint,如 speaker/headphone
----is_connected_ep()
------snd_soc_dapm_widget_for_each_path(widget, rdir, path) //涉及內核算法,遍歷每一個widget,確認該widget位于complete path,位于的話就需要被打開
2、tinymix/tinyplay/tinymix調用DAPM
1)DAPM中重點的概念和算法都已經清楚,接下來從應用層的角度看看如何使用DAMP;
2)關鍵的實現都集中的soc-dapm.c文件中,這里使用樹狀圖來進行記錄說明1)tinyplay,tinycap
播放/錄音前都會調用 soc_pcm_prepare
android\kernel\fusion\4.19\sound\soc\soc-pcm.c
soc_pcm_prepare// stream's name = Playback or Capturesnd_soc_dapm_stream_event(rtd, stream's name, SND_SOC_DAPM_STREAM_START)soc_dapm_stream_event()// 找出每一個widget// 如果strstr(w->sname, stream) // w->sname中含有Playback or Capture// w->active = 1 dapm_power_widgets()2)tinymix調用過程:
設置寄存器,會調用其put函數
android\kernel\fusion\4.19\sound\soc\soc-dapm.c
snd_soc_dapm_put_volsw()
--dapm_kcontrol_set_value(kcontrol, val | (rval << width));
--soc_dapm_mixer_update_power(card, kcontrol, connect, rconnect);
----soc_dapm_connect_path(path, rconnect, "mixer update");
----dapm_power_widgets(card, SND_SOC_DAPM_STREAM_NOP);3)tinyplay,tinycap,tinymix最終都會調用dapm_power_widgets
dapm_power_widgets()// 對于每一個widget// power = w->power_check(w); // 確定是否要上電list_for_each_entry(w, &card->dapm_dirty, dirty) {dapm_power_one_widget(w, &up_list, &down_list);--dapm_widget_power_check(w);----w->power_check(w); //調用power check設置power字段--dapm_widget_set_power(w, power, up_list, down_list);----if (power) // 放入不同的鏈表, 以后統一上是或關閉dapm_seq_insert(w, &up_list, true); //上電widget listelsedapm_seq_insert(w, &down_list, false); //掉電widget list} //給down_list上的所有widget掉電dapm_seq_run(dapm, &down_list, event, false);//根據dapm->update設置kcontrol, // update來自tinymix的調用dapm_widget_update(dapm);//給up_list上的所有widget上電dapm_seq_run(dapm, &up_list, event, true);4)給鏈表中的widget上電或關閉
dapm_seq_rundapm_seq_run_coalescedsnd_soc_update_bits