alsa 驅(qū)動介紹
來源:程序員人生 發(fā)布時間:2016-07-02 13:40:22 閱讀次數(shù):10075次
Machine
以裝配有CS4270的1款android 智能電視的為例
/sound/soc/samsung/exynos.c
Platform
以Samsung cpu exynos4412為例
/sound/soc/samsung/
Codec
以wolfson的Codec芯片cs4270為例
/sound/soc/codecs/cs4270.c
ALSA 框架介紹
Alsa 太多太雜,很難整理的規(guī)整,只能看到哪里寫到哪里
ASoC被分為Machine,Platform和Codec3大部件,
Platform驅(qū)動的主要作用是完成音頻數(shù)據(jù)的管理,終究通過CPU的數(shù)字音頻接口(DAI)把音頻數(shù)據(jù)傳送
給Codec進行處理,終究由Codec輸出驅(qū)動耳機或是喇叭的音信信號。在具體實現(xiàn)上,ASoC有把Platform驅(qū)動分為兩個部份:snd_soc_platform_driver
和snd_soc_dai_driver。其中,platform_driver負責管理音頻數(shù)據(jù),把音頻數(shù)據(jù)通過dma或其他操作傳送至cpu dai中,dai_driver則主要完成cpu1側(cè)的
dai的參數(shù)配置,同時也會通過1定的途徑把必要的dma等參數(shù)與snd_soc_platform_driver進行交互。
Machine 是指某1款機器,可以是某款裝備,某款開發(fā)板,又或是某款智能手機,由此可以看出Machine幾近是不可重用的,每一個Machine上的硬件實
現(xiàn)可能都不1樣,CPU不1樣,Codec不1樣,音頻的輸入、輸出裝備也不1樣,Machine為CPU、Codec、輸入輸出裝備提供了1個載體。
Platform 1般是指某1個SoC平臺,比如pxaxxx,s3cxxxx,omapxxx等等,與音頻相干的通常包括該SoC中的時鐘、DMA、I2S、PCM等等,只要指定了SoC,那末我們可以認為它會有1個對應的Platform,它只與SoC相干,與Machine無關,這樣我們就能夠把Platform抽象出來,使得同1款SoC不用做任何的改動,就能夠用在不同的Machine中。實際上,把Platform認為是某個SoC更好理解。
Codec 字面上的意思就是編解碼器,Codec里面包括了I2S接口、D/A、A/D、Mixer、PA(功放),通常包括多種輸入(Mic、Line-in、I2S、PCM)和多個
輸出(耳機、喇叭、聽筒,Line-out),Codec和Platform1樣,是可重用的部件,同1個Codec可以被不同的Machine使用。嵌入式Codec通常通過I2C對
內(nèi)部的寄存器進行控制。
Machine驅(qū)動的初始化,codec和dai的注冊,都會調(diào)用snd_soc_instantiate_cards()進行1次聲卡和codec,dai,platform的匹配綁定進程,這里所說的
綁定,正如Machine驅(qū)動1文中所描寫,就是通過3個全局鏈表,按名字進行匹配,把匹配的codec,dai和platform實例賦值給聲卡每對dai的snd_soc_pcm_runtime變量中。1旦綁定成功,將會使得codec和dai驅(qū)動的probe回調(diào)被調(diào)用
alsa架構的數(shù)據(jù)交互,是通過對PCM裝備的操作來完成的, PCM裝備分成playback和capture兩個stream, 每一個stream底下有N個substream
alsa驅(qū)動最底層需要調(diào)試的有3塊: DMA部份,IIS驅(qū)動部份,codec部份



IIS介紹
A)I2S有4根線,
1.串行時鐘SCLK,也叫位時鐘(BCLK),即對應數(shù)字音頻的每位數(shù)據(jù),SCLK都有1個脈沖。SCLK的頻率=2×采樣頻率×采樣位數(shù)。
2. 幀時鐘LRCK,(也稱WS),用于切換左右聲道的數(shù)據(jù)。LRCK為“1”表示正在傳輸?shù)氖怯衣暤赖臄?shù)據(jù),為“0”則表示正在傳輸?shù)氖亲舐暤赖臄?shù)據(jù)。LRCK
的頻率等于采樣頻率。
3.串行數(shù)據(jù)SDATA,就是用2進制補碼表示的音頻數(shù)據(jù)。
4.有時為了使系統(tǒng)間能夠更好地同步,還需要另外傳輸1個信號MCLK,稱為主時鐘,也叫系統(tǒng)時鐘(Sys Clock),是采樣頻率的256倍或384倍。
B)聲音數(shù)據(jù)DAT1般在CLK的上升沿進行采樣,有些DAC也是可以調(diào)的。每一個聲道里面可以容納的CLK數(shù)必須多于數(shù)據(jù)的位數(shù),多出來的時鐘和數(shù)據(jù)DAC會丟棄不用,比如16bit采樣的聲音數(shù)據(jù)當1個聲道是32個CLK且left-justify的時候,后面106個時鐘的數(shù)據(jù)會被DAC丟掉,不影響的。
C)I2S數(shù)據(jù)的格式分I2S, Left-justify, Right-justify。3種格式的區(qū)分在于聲音數(shù)據(jù)與WS的對應關系:
1 . I2S模式DAT的MSB在WS變化后的第2個上升沿開始傳輸
2. Left-justify模式DAT的MSB在WS變化后的第1個上升沿開始傳輸
3. Right-justify模式DAT的LSB在WS行將變換到下1聲道前的最后1個時鐘傳輸
I2S部份觸及的幾個頻率:
* 輸出采樣頻率 fs = 44.1KHz. (也有其它fs的音源, 但加了resampler后, 都變成44.1KHz輸出了). 這是個關鍵頻率.
* LRCLK, 就等于fs. (L/R聲道信號)
* BCLK = 32倍fs = 1411.2KHz = 1.4112MHz. (bit clock). 2聲道16bit, 故32倍fs. 若2聲道24bit, 則48倍fs.
* MCLK是全部audio模塊的工作頻率, 通常選fs的256, 384, 512倍. 比如: 256倍fs = 11289.6KHz = 11.2896MHz.
從頻率設置來講, MCLK是個主要頻率, 它是全部audio模塊的工作頻率.
那末, 從軟件來講要設置兩個方面的寄存器: 1是該PLL從晶振頻率如何得到PLLout頻率(比如P/M/S/k). 2是PLLout如何分頻得到audio部份的MCLK.
IIS驅(qū)動部份最重要的就是注冊以下鉤子函數(shù),掛到了alsa驅(qū)動上
static const struct snd_soc_dai_ops samsung_i2s_dai_ops = {
.trigger = i2s_trigger,
.hw_params = i2s_hw_params,
.set_fmt = i2s_set_fmt,
.set_clkdiv = i2s_set_clkdiv,
.set_sysclk = i2s_set_sysclk,
.startup = i2s_startup,
.shutdown = i2s_shutdown,
.delay = i2s_delay,
};
codec芯片介紹
cs4270的驅(qū)動要設置的參數(shù)有:
靜音,傳輸模式,比特位長度,時鐘主從模式,音量大小
cs4270驅(qū)動里面定義了snd_soc_dai_driver結(jié)構成員,里面定義了playback和capture兩個substream,同事也掛了1個snd_soc_dai_ops結(jié)構體,里面全
是操作函數(shù)指針。
alsa上面1層層的終究會調(diào)用到這些指針
static const struct snd_soc_dai_ops cs4270_dai_ops = {
.hw_params = cs4270_hw_params,
.set_sysclk = cs4270_set_dai_sysclk,
.set_fmt = cs4270_set_dai_fmt,
.digital_mute = cs4270_dai_mute,
};
static struct snd_soc_dai_driver cs4270_dai = {
.name = "cs4270-hifi",
.playback = {
.stream_name = "Playback",
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_CONTINUOUS,
.rate_min = 4000,
.rate_max = 216000,
.formats = CS4270_FORMATS,
},
.capture = {
.stream_name = "Capture",
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_CONTINUOUS,
.rate_min = 4000,
.rate_max = 216000,
.formats = CS4270_FORMATS,
},
.ops = &cs4270_dai_ops,
};
DMA介紹
IIS總線是慢速總線,相對CPU來講,太慢。所以采取DMA的方式最能節(jié)省CPU性能。
PCM playback的時候,DMA目的地址是IIS FIFO寄存器。源地址是寄存PCM數(shù)據(jù)的內(nèi)存。
DMA的驅(qū)動采取了linux pl330的驅(qū)動架構,采取中斷的方式來觸發(fā)后續(xù)DMA。
IIS中通過DMA的方式寫入FIFO寄存器,在DMA的驅(qū)動中掛接了1個回調(diào)函數(shù)audio_buffdone。DMA完成后,回函數(shù)調(diào)用,刷新alsa的環(huán),便于下1次DMA
DMA的目的地址,就是IIS發(fā)送寄存器的地址。源地址,就是申請的DMA buffer,只不過DMAbuffer被映照成了1個環(huán)
static void dma_enqueue(struct snd_pcm_substream *substream)
{
struct runtime_data *prtd = substream->runtime->private_data;
dma_addr_t pos = prtd->dma_pos;
unsigned int limit;
struct samsung_dma_prep dma_info;
pr_debug("Entered %s\n", __func__);
limit = (prtd->dma_end - prtd->dma_start) / prtd->dma_period;
pr_debug("%s: loaded %d, limit %d\n",
__func__, prtd->dma_loaded, limit);
dma_info.cap = (samsung_dma_has_circular() ? DMA_CYCLIC : DMA_SLAVE);
dma_info.direction =
(substream->stream == SNDRV_PCM_STREAM_PLAYBACK
? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM);
dma_info.fp = audio_buffdone; //回調(diào)函數(shù)
dma_info.fp_param = substream;
dma_info.period = prtd->dma_period;
dma_info.len = prtd->dma_period*limit;
while (prtd->dma_loaded < limit) {
pr_debug("dma_loaded: %d\n", prtd->dma_loaded);
if ((pos + dma_info.period) > prtd->dma_end) {
dma_info.period = prtd->dma_end - pos;
pr_debug("%s: corrected dma len %ld\n",
__func__, dma_info.period);
}
dma_info.buf = pos;
prtd->params->ops->prepare(prtd->params->ch, &dma_info); //DMA注冊
prtd->dma_loaded++;
pos += prtd->dma_period;
if (pos >= prtd->dma_end)
pos = prtd->dma_start;
}
prtd->dma_pos = pos;
}
static void audio_buffdone(void *data)
{
struct snd_pcm_substream *substream = data;
struct runtime_data *prtd = substream->runtime->private_data;
pr_debug("Entered %s\n", __func__);
if (prtd->state & ST_RUNNING) {
prtd->dma_pos += prtd->dma_period;
if (prtd->dma_pos >= prtd->dma_end)
prtd->dma_pos = prtd->dma_start;
if (substream)
snd_pcm_period_elapsed(substream);
spin_lock(&prtd->lock);
if (!samsung_dma_has_circular()) {
prtd->dma_loaded--;
dma_enqueue(substream);
}
spin_unlock(&prtd->lock);
}
}
DMA部份主要通過注冊以下鉤子函數(shù)來掛到alsa驅(qū)動里面
static struct snd_pcm_ops dma_ops = {
.open = dma_open,
.close = dma_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = dma_hw_params,
.hw_free = dma_hw_free,
.prepare = dma_prepare,
.trigger = dma_trigger,
.pointer = dma_pointer,
.mmap = dma_mmap,
};
alsa數(shù)據(jù)讀寫簡介
播放時,利用程序把音頻數(shù)據(jù)源源不斷地寫入dma buffer中,然后相應platform的dma操作則不停地從該buffer中取出數(shù)據(jù),經(jīng)dai送往codec中。錄音時
則正好相反,codec源源不斷地把A/D轉(zhuǎn)換好的音頻數(shù)據(jù)經(jīng)過dai送入dma buffer中,而利用程序則不斷地從該buffer中讀走音頻數(shù)據(jù)
以播放(playback)為例,我現(xiàn)在知道最少有3個途徑可以完成對dma buffer的寫入:
利用程序調(diào)用alsa-lib的snd_pcm_writei、snd_pcm_writen函數(shù);
利用程序使用ioctl:SNDRV_PCM_IOCTL_WRITEI_FRAMES或SNDRV_PCM_IOCTL_WRITEN_FRAMES;
利用程序使用alsa-lib的snd_pcm_mmap_begin/snd_pcm_mmap_commit;
以上幾種方式終究把數(shù)據(jù)寫入dma buffer中,然后修改runtime->control->appl_ptr的值。
播放進程中,通常會配置成每個period size生成1個dma中斷,中斷處理函數(shù)最重要的任務就是:
更新dma的硬件確當前位置,該數(shù)值通常保存在runtime->private_data中;
調(diào)用snd_pcm_period_elapsed函數(shù),該函數(shù)會進1步調(diào)用snd_pcm_update_hw_ptr0函數(shù)更新上述所說的4個緩沖區(qū)管理字段,然后喚醒相應的等待進程;
這個中斷實際上在DMA驅(qū)動內(nèi)部,給DMA驅(qū)動1個回調(diào)函數(shù)就能夠了。就是我們前面說的audio_buffdone
Playback時數(shù)據(jù)流向
/sound/soc/samsung/里面,寫入到DMA源buffer時,
用的是mmap的寫入方式,不是采取的snd_pcm_hw_writei
幾個關鍵點:
1,Pos計算方式
dma_pointer里面, res = prtd->dma_pos - prtd->dma_start;
此pos就是在DMA 源buffer中的位置
2,dma的初始化
dma_enqueue里面,把DMA源buffer切成period_size大小,掛到DMA隊列里
每次period_size傳輸完了,就會調(diào)用audio_buffdone終端處理函數(shù),更新dma_pos
同時audio_buffdone也會調(diào)用snd_pcm_update_hw_ptr0重新計算hw_ptr,從而計算是否是有足夠的可用空間,來喚醒等待的poll
從alsalib來看,要先調(diào)用snd_pcm_start,觸發(fā)DMA操作開始
snd_pcm_start---SNDRV_PCM_IOCTL_START---snd_pcm_action_lock_irq--snd_pcm_do_start----dma_trigger
每次寫入mmap buffer之前,要先snd_pcm_wait,等待有足夠可用的空間.
snd_pcm_wait----snd_pcm_wait_nocheck----poll-----snd_pcm_playback_poll---poll_wait
然后調(diào)用snd_pcm_mmap_begin獲得mmap 內(nèi)存
然后寫入,然后調(diào)用snd_pcm_mmap_commit做1下alsalib和驅(qū)動里面的環(huán)同步
DMA實際上是在不停的DMA的。有空閑的了,上層就不用wait了,就會寫入了

幾個典型調(diào)用流程
設置hw_param參數(shù)時,調(diào)用流程
snd_pcm_hw_params
_snd_pcm_hw_params
pcm->ops->hw_params----snd_pcm_hw_hw_params
SNDRV_PCM_IOCTL_HW_PARAMS
snd_pcm_common_ioctl1
snd_pcm_hw_params_user
snd_pcm_hw_params
substream->ops->hw_params
soc_pcm_hw_params
codec_dai->driver->ops->hw_params
cpu_dai->driver->ops->hw_params
cs4270_hw_params
設置mixer 參數(shù)時,volume為例,調(diào)用流程
snd_mixer_selem_set_playback_volume_all
snd_mixer_selem_set_playback_volume
set_volume_ops
_snd_mixer_selem_set_volume---selem_write
selem_write_main
elem_write_volume
snd_hctl_elem_write
snd_ctl_elem_write
snd_ctl_hw_elem_write
snd_ctl_elem_write_user
snd_ctl_elem_write
snd_soc_put_volsw
snd_soc_update_bits_locked
regmap_update_bits_check
IIS clk 設置流程
snd_pcm_common_ioctl1
snd_pcm_hw_params_user
snd_pcm_hw_params
substream->ops->hw_params
soc_pcm_hw_params
rtd->dai_link->ops->hw_params
smdk_wm8994_pcm_hw_params
snd_soc_dai_set_sysclk
dai->driver->ops->set_sysclk
i2s_set_sysclk
生活不易,碼農(nóng)辛苦
如果您覺得本網(wǎng)站對您的學習有所幫助,可以手機掃描二維碼進行捐贈