alsa 驅動介紹

Machine
以裝配有CS4270的一款android 智能電視的爲例
/sound/soc/samsung/exynos.c


Platform
以Samsung cpu exynos4412爲例
/sound/soc/samsung/


Codec
以wolfson的Codec芯片cs4270爲例
/sound/soc/codecs/cs4270.c


ALSA 框架介紹



Alsa 太多太雜,很難整理的規整,只能看到哪裏寫到哪裏

ASoC被分爲Machine,Platform和Codec三大部件,


Platform驅動的主要作用是完成音頻數據的管理,最終通過CPU的數字音頻接口(DAI)把音頻數據傳送給Codec進行處理,最終由Codec輸出驅動耳機或者是喇叭的音信信號。在具體實現上,ASoC有把Platform驅動分爲兩個部分:snd_soc_platform_driver和snd_soc_dai_driver。其中,platform_driver負責管理音頻數據,把音頻數據通過dma或其他操作傳送至cpu dai中,dai_driver則主要完成cpu一側的dai的參數配置,同時也會通過一定的途徑把必要的dma等參數與snd_soc_platform_driver進行交互。


Machine  是指某一款機器,可以是某款設備,某款開發板,又或者是某款智能手機,由此可以看出Machine幾乎是不可重用的,每個Machine上的硬件實現可能都不一樣,CPU不一樣,Codec不一樣,音頻的輸入、輸出設備也不一樣,Machine爲CPU、Codec、輸入輸出設備提供了一個載體。

Platform  一般是指某一個SoC平臺,比如pxaxxx,s3cxxxx,omapxxx等等,與音頻相關的通常包含該SoC中的時鐘、DMA、I2S、PCM等等,只要指定了SoC,那麼我們可以認爲它會有一個對應的Platform,它只與SoC相關,與Machine無關,這樣我們就可以把Platform抽象出來,使得同一款SoC不用做任何的改動,就可以用在不同的Machine中。實際上,把Platform認爲是某個SoC更好理解。


Codec  字面上的意思就是編解碼器,Codec裏面包含了I2S接口、D/A、A/D、Mixer、PA(功放),通常包含多種輸入(Mic、Line-in、I2S、PCM)和多個輸出(耳機、喇叭、聽筒,Line-out),Codec和Platform一樣,是可重用的部件,同一個Codec可以被不同的Machine使用。嵌入式Codec通常通過I2C對內部的寄存器進行控制。 


Machine驅動的初始化,codec和dai的註冊,都會調用snd_soc_instantiate_cards()進行一次聲卡和codec,dai,platform的匹配綁定過程,這裏所說的綁定,正如Machine驅動一文中所描述,就是通過3個全局鏈表,按名字進行匹配,把匹配的codec,dai和platform實例賦值給聲卡每對dai的snd_soc_pcm_runtime變量中。一旦綁定成功,將會使得codec和dai驅動的probe回調被調用

alsa架構的數據交互,是通過對PCM設備的操作來完成的, PCM設備分成playback和capture兩個stream, 每個stream底下有N個substream。

alsa驅動最底層需要調試的有三塊: DMA部分,IIS驅動部分,codec部分










IIS介紹

A)I2S有四根線,
1.串行時鐘SCLK,也叫位時鐘(BCLK),即對應數字音頻的每一位數據,SCLK都有1個脈衝。SCLK的頻率=2×採樣頻率×採樣位數。
2. 幀時鐘LRCK,(也稱WS),用於切換左右聲道的數據。LRCK爲“1”表示正在傳輸的是右聲道的數據,爲“0”則表示正在傳輸的是左聲道的數據。LRCK的頻率等於採樣頻率。
3.串行數據SDATA,就是用二進制補碼錶示的音頻數據。
4.有時爲了使系統間能夠更好地同步,還需要另外傳輸一個信號MCLK,稱爲主時鐘,也叫系統時鐘(Sys Clock),是採樣頻率的256倍或384倍。


B)聲音數據DAT一般在CLK的上升沿進行採樣,有些DAC也是可以調的。每個聲道里面可以容納的CLK數必須多於數據的位數,多出來的時鐘和數據DAC會丟棄不用,比如16bit採樣的聲音數據當一個聲道是32個CLK且left-justify的時候,後面十六個時鐘的數據會被DAC丟掉,不影響的。



C)I2S數據的格式分I2S, Left-justify, Right-justify。三種格式的區別在於聲音數據與WS的對應關係:
1 .  I2S模式DAT的MSB在WS變化後的第二個上升沿開始傳輸
2.  Left-justify模式DAT的MSB在WS變化後的第一個上升沿開始傳輸
3.   Right-justify模式DAT的LSB在WS即將變換到下一聲道前的最後一個時鐘傳輸


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模塊的工作頻率.

那麼, 從軟件來說要設置兩個方面的寄存器: 一是該PLL從晶振頻率如何得到PLLout頻率(比如P/M/S/k). 二是PLLout如何分頻得到audio部分的MCLK.


IIS驅動部分最重要的就是註冊以下鉤子函數,掛到了alsa驅動上
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的驅動要設置的參數有:
靜音,傳輸模式,比特位長度,時鐘主從模式,音量大小
cs4270驅動裏面定義了snd_soc_dai_driver結構成員,裏面定義了playback和capture兩個substream,同事也掛了一個snd_soc_dai_ops結構體,裏面全是操作函數指針。
alsa上面一層層的最終會調用到這些指針

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的方式最能節省CPU性能。
PCM playback的時候,DMA目的地址是IIS FIFO寄存器。源地址是存放PCM數據的內存。
DMA的驅動採用了linux pl330的驅動架構,採用中斷的方式來觸發後續DMA。

IIS中通過DMA的方式寫入FIFO寄存器,在DMA的驅動中掛接了一個回調函數audio_buffdone。DMA完成後,回函數調用,刷新alsa的環,便於下一次DMA。

DMA的目的地址,就是IIS發送寄存器的地址。源地址,就是申請的DMA buffer,只不過DMAbuffer被映射成了一個環


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;   //回調函數
	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部分主要通過註冊以下鉤子函數來掛到alsa驅動裏面

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數據讀寫簡介

播放時,應用程序把音頻數據源源不斷地寫入dma buffer中,然後相應platform的dma操作則不停地從該buffer中取出數據,經dai送往codec中。錄音時則正好相反,codec源源不斷地把A/D轉換好的音頻數據經過dai送入dma buffer中,而應用程序則不斷地從該buffer中讀走音頻數據。

以播放(playback)爲例,我現在知道至少有3個途徑可以完成對dma buffer的寫入:
應用程序調用alsa-lib的snd_pcm_writei、snd_pcm_writen函數;
應用程序使用ioctl:SNDRV_PCM_IOCTL_WRITEI_FRAMES或SNDRV_PCM_IOCTL_WRITEN_FRAMES;
應用程序使用alsa-lib的snd_pcm_mmap_begin/snd_pcm_mmap_commit;

以上幾種方式最終把數據寫入dma buffer中,然後修改runtime->control->appl_ptr的值。
播放過程中,通常會配置成每一個period size生成一個dma中斷,中斷處理函數最重要的任務就是:
更新dma的硬件的當前位置,該數值通常保存在runtime->private_data中;
調用snd_pcm_period_elapsed函數,該函數會進一步調用snd_pcm_update_hw_ptr0函數更新上述所說的4個緩衝區管理字段,然後喚醒相應的等待進程;
這個中斷實際上在DMA驅動內部,給DMA驅動一個回調函數就可以了。就是我們前面說的audio_buffdone

Playback時數據流向

/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傳輸完了,就會調用audio_buffdone終端處理函數,更新dma_pos
同時audio_buffdone也會調用snd_pcm_update_hw_ptr0重新計算hw_ptr,從而計算是不是有足夠的可用空間,來喚醒等待的poll

從alsalib來看,要先調用snd_pcm_start,觸發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
然後調用snd_pcm_mmap_begin獲取mmap 內存
然後寫入,然後調用snd_pcm_mmap_commit做一下alsalib和驅動裏面的環同步

DMA實際上是在不停的DMA的。有空閒的了,上層就不用wait了,就會寫入了


幾個典型調用流程

設置hw_param參數時,調用流程
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 參數時,volume爲例,調用流程
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































發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章