DAPM是Dynamic Audio Power Management的縮寫,直譯過來就是動態音頻電源管理的意思,DAPM是為了使基於Linux的移動設備上的音頻子系統,在任何時候都工作在最小功耗狀態下。DAPM對用戶空間的應用程序來說是透明的,所有與電源相關的開關都在ASoc core中完成。用戶空間的應用程序無需對代碼做出修改,也無需重新編譯,DAPM根據當前激活的音頻流(playback/capture)和聲卡中的mixer等的配置來決定那些音頻控件的電源開關被打開或關閉。
/*****************************************************************************************************/
聲明:本博內容均由http://blog.csdn.net/droidphone原創,轉載請注明出處,謝謝!
/*****************************************************************************************************/
DAPM控件是由普通的soc音頻控件演變而來的,所以本章的內容我們先從普通的soc音頻控件開始。
snd_kcontrol_new結構
在正式討論DAPM之前,我們需要先搞清楚ASoc中的一個重要的概念:kcontrol,不熟悉的讀者需要瀏覽一下我之前的文章:Linux ALSA聲卡驅動之四:Control設備的創建。通常,一個kcontrol代表着一個mixer(混音器),或者是一個mux(多路開關),又或者是一個音量控制器等等。 從上述文章中我們知道,定義一個kcontrol主要就是定義一個snd_kcontrol_new結構,為了方便討論,這里再次給出它的定義:
- struct snd_kcontrol_new {
- snd_ctl_elem_iface_t iface; /* interface identifier */
- unsigned int device; /* device/client number */
- unsigned int subdevice; /* subdevice (substream) number */
- const unsigned char *name; /* ASCII name of item */
- unsigned int index; /* index of item */
- unsigned int access; /* access rights */
- unsigned int count; /* count of same elements */
- snd_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;
- };
回到Linux ALSA聲卡驅動之四:Control設備的創建中,我們知道,對於每個控件,我們需要定義一個和他對應的snd_kcontrol_new結構,這些snd_kcontrol_new結構會在聲卡的初始化階段,通過snd_soc_add_codec_controls函數注冊到系統中,用戶空間就可以通過amixer或alsamixer等工具查看和設定這些控件的狀態。
snd_kcontrol_new結構中,幾個主要的字段是get,put,private_value,get回調函數用於獲取該控件當前的狀態值,而put回調函數則用於設置控件的狀態值,而private_value字段則根據不同的控件類型有不同的意義,比如對於普通的控件,private_value字段可以用來定義該控件所對應的寄存器的地址以及對應的控制位在寄存器中的位置信息。值得慶幸的是,ASoc系統已經為我們准備了大量的宏定義,用於定義常用的控件,這些宏定義位於include/sound/soc.h中。下面我們分別討論一下如何用這些預設的宏定義來定義一些常用的控件。簡單型的控件
SOC_SINGLE SOC_SINGLE應該算是最簡單的控件了,這種控件只有一個控制量,比如一個開關,或者是一個數值變量(比如Codec中某個頻率,FIFO大小等等)。我們看看這個宏是如何定義的:
- #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) }
- #define SOC_DOUBLE_VALUE(xreg, shift_left, shift_right, xmax, xinvert) \
- ((unsigned long)&(struct soc_mixer_control) \
- {.reg = xreg, .rreg = xreg, .shift = shift_left, \
- .rshift = shift_right, .max = xmax, .platform_max = xmax, \
- .invert = xinvert})
- #define SOC_SINGLE_VALUE(xreg, xshift, xmax, xinvert) \
- SOC_DOUBLE_VALUE(xreg, xshift, xshift, xmax, xinvert)
- /* mixer control */
- struct soc_mixer_control {
- int min, max, platform_max;
- unsigned int reg, rreg, shift, rshift, invert;
- };
- int snd_soc_get_volsw(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_value *ucontrol)
- {
- struct soc_mixer_control *mc =
- (struct soc_mixer_control *)kcontrol->private_value;
- struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
- unsigned int reg = mc->reg;
- unsigned int reg2 = mc->rreg;
- unsigned int shift = mc->shift;
- unsigned int rshift = mc->rshift;
- int max = mc->max;
- unsigned int mask = (1 << fls(max)) - 1;
- unsigned int invert = mc->invert;
- ucontrol->value.integer.value[0] =
- (snd_soc_read(codec, reg) >> shift) & mask;
- if (invert)
- ucontrol->value.integer.value[0] =
- max - ucontrol->value.integer.value[0];
- if (snd_soc_volsw_is_stereo(mc)) {
- if (reg == reg2)
- ucontrol->value.integer.value[1] =
- (snd_soc_read(codec, reg) >> rshift) & mask;
- else
- ucontrol->value.integer.value[1] =
- (snd_soc_read(codec, reg2) >> shift) & mask;
- if (invert)
- ucontrol->value.integer.value[1] =
- max - ucontrol->value.integer.value[1];
- }
- return 0;
- }
SOC_SINGLE_TLV SOC_SINGLE_TLV是SOC_SINGLE的一種擴展,主要用於定義那些有增益控制的控件,例如音量控制器,EQ均衡器等等。
- #define SOC_SINGLE_TLV(xname, reg, shift, max, invert, tlv_array) \
- { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
- .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\
- SNDRV_CTL_ELEM_ACCESS_READWRITE,\
- .tlv.p = (tlv_array), \
- .info = snd_soc_info_volsw, .get = snd_soc_get_volsw,\
- .put = snd_soc_put_volsw, \
- .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) }
- SNDRV_CTL_IOCTL_TLV_READ
- SNDRV_CTL_IOCTL_TLV_WRITE
- SNDRV_CTL_IOCTL_TLV_COMMAND
- static const DECLARE_TLV_DB_SCALE(mixin_boost_tlv, 0, 900, 0);
- static const struct snd_kcontrol_new wm1811_snd_controls[] = {
- SOC_SINGLE_TLV("MIXINL IN1LP Boost Volume", WM8994_INPUT_MIXER_1, 7, 1, 0,
- mixin_boost_tlv),
- SOC_SINGLE_TLV("MIXINL IN1RP Boost Volume", WM8994_INPUT_MIXER_1, 8, 1, 0,
- mixin_boost_tlv),
- };
SOC_DOUBLE 與SOC_SINGLE相對應,區別是SOC_SINGLE只控制一個變量,而SOC_DOUBLE則可以同時在一個寄存器中控制兩個相似的變量,最常用的就是用於一些立體聲的控件,我們需要同時對左右聲道進行控制,因為多了一個聲道,參數也就相應地多了一個shift位移值,
- #define SOC_DOUBLE(xname, reg, shift_left, shift_right, 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_DOUBLE_VALUE(reg, shift_left, shift_right, \
- max, invert) }
SOC_DOUBLE_R 與SOC_DOUBLE類似,對於左右聲道的控制寄存器不一樣的情況,使用SOC_DOUBLE_R來定義,參數中需要指定兩個寄存器地址。
SOC_DOUBLE_TLV 與SOC_SINGLE_TLV對應的立體聲版本,通常用於立體聲音量控件的定義。
SOC_DOUBLE_R_TLV 左右聲道有獨立寄存器控制的SOC_DOUBLE_TLV版本
Mixer控件
Mixer控件用於音頻通道的路由控制,由多個輸入和一個輸出組成,多個輸入可以自由地混合在一起,形成混合后的輸出:

圖1 Mixer混音器
對於Mixer控件,我們可以認為是多個簡單控件的組合,通常,我們會為mixer的每個輸入端都單獨定義一個簡單控件來控制該路輸入的開啟和關閉,反應在代碼上,就是定義一個soc_kcontrol_new數組:
- static const struct snd_kcontrol_new left_speaker_mixer[] = {
- SOC_SINGLE("Input Switch", WM8993_SPEAKER_MIXER, 7, 1, 0),
- SOC_SINGLE("IN1LP Switch", WM8993_SPEAKER_MIXER, 5, 1, 0),
- SOC_SINGLE("Output Switch", WM8993_SPEAKER_MIXER, 3, 1, 0),
- SOC_SINGLE("DAC Switch", WM8993_SPEAKER_MIXER, 6, 1, 0),
- };
以上這個mixer使用寄存器WM8993_SPEAKER_MIXER的第3,5,6,7位來分別控制4個輸入端的開啟和關閉。
Mux控件
mux控件與mixer控件類似,也是多個輸入端和一個輸出端的組合控件,與mixer控件不同的是,mux控件的多個輸入端同時只能有一個被選中。因此,mux控件所對應的寄存器,通常可以設定一段連續的數值,每個不同的數值對應不同的輸入端被打開,與上述的mixer控件不同,ASoc用soc_enum結構來描述mux控件的寄存器信息:
- /* enumerated kcontrol */
- struct soc_enum {
- unsigned short reg;
- unsigned short reg2;
- unsigned char shift_l;
- unsigned char shift_r;
- unsigned int max;
- unsigned int mask;
- const char * const *texts;
- const unsigned int *values;
- };
第一步,定義字符串和values數組,以下的例子因為values是連續的,所以不用定義:
- static const char *drc_path_text[] = {
- "ADC",
- "DAC"
- };
- static const struct soc_enum drc_path =
- SOC_ENUM_SINGLE(WM8993_DRC_CONTROL_1, 14, 2, drc_path_text);
- static const struct snd_kcontrol_new wm8993_snd_controls[] = {
- SOC_DOUBLE_TLV(......),
- ......
- SOC_ENUM("DRC Path", drc_path),
- ......
- }
- #define SOC_ENUM_DOUBLE(xreg, xshift_l, xshift_r, xmax, xtexts) \
- { .reg = xreg, .shift_l = xshift_l, .shift_r = xshift_r, \
- .max = xmax, .texts = xtexts, \
- .mask = xmax ? roundup_pow_of_two(xmax) - 1 : 0}
- #define SOC_ENUM_SINGLE(xreg, xshift, xmax, xtexts) \
- SOC_ENUM_DOUBLE(xreg, xshift, xshift, xmax, xtexts)
- #define SOC_ENUM(xname, xenum) \
- { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname,\
- .info = snd_soc_info_enum_double, \
- .get = snd_soc_get_enum_double, .put = snd_soc_put_enum_double, \
- .private_value = (unsigned long)&xenum }
- int snd_soc_get_enum_double(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_value *ucontrol)
- {
- struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
- struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
- unsigned int val;
- val = snd_soc_read(codec, e->reg);
- ucontrol->value.enumerated.item[0]
- = (val >> e->shift_l) & e->mask;
- if (e->shift_l != e->shift_r)
- ucontrol->value.enumerated.item[1] =
- (val >> e->shift_r) & e->mask;
- return 0;
- }
- int snd_soc_info_enum_double(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_info *uinfo)
- {
- struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
- uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
- uinfo->count = e->shift_l == e->shift_r ? 1 : 2;
- uinfo->value.enumerated.items = e->max;
- if (uinfo->value.enumerated.item > e->max - 1)
- uinfo->value.enumerated.item = e->max - 1;
- strcpy(uinfo->value.enumerated.name,
- e->texts[uinfo->value.enumerated.item]);
- return 0;
- }
以下是另外幾個常用於定義mux控件的宏:
SOC_VALUE_ENUM_SINGLE 用於定義帶values字段的soc_enum結構。
SOC_VALUE_ENUM_DOUBLE SOC_VALUE_ENUM_SINGLE的立體聲版本。
SOC_VALUE_ENUM 用於定義帶values字段snd_kcontrol_new結構,這個有點特別,我們還是看看它的定義:
- #define SOC_VALUE_ENUM(xname, xenum) \
- { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname,\
- .info = snd_soc_info_enum_double, \
- .get = snd_soc_get_value_enum_double, \
- .put = snd_soc_put_value_enum_double, \
- .private_value = (unsigned long)&xenum }
- int snd_soc_get_value_enum_double(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_value *ucontrol)
- {
- struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
- struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
- unsigned int reg_val, val, mux;
- reg_val = snd_soc_read(codec, e->reg);
- val = (reg_val >> e->shift_l) & e->mask;
- for (mux = 0; mux < e->max; mux++) {
- if (val == e->values[mux])
- break;
- }
- ucontrol->value.enumerated.item[0] = mux;
- if (e->shift_l != e->shift_r) {
- val = (reg_val >> e->shift_r) & e->mask;
- for (mux = 0; mux < e->max; mux++) {
- if (val == e->values[mux])
- break;
- }
- ucontrol->value.enumerated.item[1] = mux;
- }
- return 0;
- }
通常,我們還可以用以下幾個輔助宏定義soc_enum結構,其實和上面所說的沒什么區別,只是可以偷一下懶,省掉struct soc_enum xxxx=幾個單詞而已:
- SOC_ENUM_SINGLE_DECL
- SOC_ENUM_DOUBLE_DECL
- SOC_VALUE_ENUM_SINGLE_DECL
- SOC_VALUE_ENUM_DOUBLE_DECL
其它控件
其實,除了以上介紹的幾種常用的控件,ASoc還為我們提供了另外一些控件定義輔助宏,詳細的請讀者參考include/sound/soc.h。這里列舉幾個:
需要自己定義get和put回調時,可以使用以下這些帶EXT的版本:
- SOC_SINGLE_EXT
- SOC_DOUBLE_EXT
- SOC_SINGLE_EXT_TLV
- SOC_DOUBLE_EXT_TLV
- SOC_DOUBLE_R_EXT_TLV
- SOC_ENUM_EXT
