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結構,為了方便討論,這裡再次給出它的定義: [cpp] view plain copy 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大小等等)。我們看看這個宏是如何定義的: [cpp] view plain copy #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) } 宏定義的參數分別是:xname(該控制項的名字),reg(該控制項對應的寄存器的地址),shift(控制位在寄存器中的位移),max(控制項可設定的最大值),invert(設定值是否邏輯取反)。這裡又使用了一個宏來定義private_value欄位:SOC_SINGLE_VALUE,我們看看它的定義: [cpp] view plain copy #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) 這裡實際上是定義了一個soc_mixer_control結構,然後把該結構的地址賦值給了private_value欄位,soc_mixer_control結構是這樣的: [cpp] view plain copy /* mixer control */ struct soc_mixer_control { int min, max, platform_max; unsigned int reg, rreg, shift, rshift, invert; }; 看來soc_mixer_control是控制項特徵的真正描述者,它確定了該控制項對應寄存器的地址,位移值,最大值和是否邏輯取反等特性,控制項的put回呼函數和get回呼函數需要藉助該結構來訪問實際的寄存器。我們看看這get回呼函數的定義: [cpp] view plain copy 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; } 上述代碼一目瞭然,從private_value欄位取出soc_mixer_control結構,利用該結構的資訊,訪問對應的寄存器,返回相應的值。
SOC_SINGLE_TLV SOC_SINGLE_TLV是SOC_SINGLE的一種擴充,主要用於定義那些有增益控制的控制項,例如音量控制器,EQ均衡器等等。 [cpp] view plain copy #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) } 從他的定義可以看出,用於設定寄存器資訊的private_value欄位的定義和SOC_SINGLE是一樣的,甚至put、get回呼函數也是使用同一套,唯一不同的是增加了一個tlv_array參數,並把它賦值給了tlv.p欄位。關於tlv,已經在 Linux ALSA音效卡驅動之四:Control裝置的建立中進行了闡述。使用者空間可以通過對音效卡的control裝置發起以下兩種ioctl來訪問tlv欄位所指向的數組: SNDRV_CTL_IOCTL_TLV_READ SNDRV_CTL_IOCTL_TLV_WRITE SNDRV_CTL_IOCTL_TLV_COMMAND 通常,tlv_array用來描述寄存器的設定值與它所代表的實際意義之間的映射關係,最常用的就是用於音量控制項時,設定值與對應的dB值之間的映射關係,請看以下例子: [cpp] view plain copy 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), }; DECLARE_TLV_DB_SCALE用於定義一個dB值對應的tlv_array,上述的例子表明,該tlv的類型是SNDRV_CTL_TLVT_DB_SCALE,寄存器的最小值對應是0dB,寄存器每增加一個單位值,對應的dB數增加是9dB(0.01dB*900),而由接下來的兩組SOC_SINGLE_TLV定義可以看出,我們定義了兩個boost控制項,寄存器的地址都是WM8994_INPUT_MIXER_1,控制位分別是第7bit和第8bit,最大值是1,所以,該控制項只能設定兩個數值0和1,對應的dB值就是0dB和9dB。
SOC_DOUBLE 與SOC_SINGLE相對應,區別是SOC_SINGLE只控制一個變數,而SOC_DOUBLE則可以同時在一個寄存器中控制兩個相似的變數,最常用的就是用於一些立體聲的控制項,我們需要同時對左右聲道進行控制,因為多了一個聲道,參數也就相應地多了一個shift位移值, [cpp] view plain copy #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數組: [cpp] view plain copy 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控制項