這裏具體DMA CONTROL寄存器(DCON)的配置說明,進而引出DMA的各種工作方式。
Atomic transfer:指的是DMA的單次原子操作,它可以是Unit模式(傳輸1個data size),也可以是burst模式(傳輸4個data size),具體對應DCON[28]。
Data Size:指的是單次原子操作的數據位寬,8、16、32,具體對應DCON[21:20]。
Request Source:DMA請求的來源有兩種,軟件&硬件模塊,由DCON[23]控制;當爲前者時,由軟件對DMASKTRIG寄存器的位0置位觸發一次DMA 操作。當爲後者時,具體來源由DCON[26:24]控制,不同硬件模塊的某時間觸發一次DMA操作,具體要見不同的硬件模塊。
DMA service mode:DMA的工作模式有兩種,單一服務模式&整體服務模式。前一模式下,一次DMA請求完成一項原子操作,並且transfer count的值減1。後一模式下,一次DMA請求完成一批原子操作,直到transfer count等於0表示完成一次整體服務。具體對應DCON[27]。
RELOAD:在reload模式下,當transfer count的值變爲零時,將自動加src、dst、TC的值加載到CURR_DST、 CURR_SRC、CURR_TC,並開始一次新的DMA傳輸。該模式一般和整體服務模式一起使用,也就是說當一次整體服務開始後,src、dst、TC 的值都已經被加載,因此可以更改爲下一次
服務的地址,2410說明文檔中建議加入以下語句來判斷當前的服務開始,src、dst、TC的值可以被更改了:while((rDSTATn & 0xfffff) == 0) ;
Req&Ack:DMA請求和應答的協議有兩種,Demard mode 和 Handshake mode。兩者對Request和Ack的時序定義有所不同:在Demard模式下,如果
DMA完成一次請求如果Request仍然有效,那麼DMA就認爲這是下一次DMA請求;在Handshake模式下,DMA完成一次請求後等待Request信號無效,然後把ACK也置無效,再等待下一次Request。這個設計外部DMA請求時可能要用到。
傳輸總長度:DMA一次整體服務傳輸的總長度爲:
Data Size × Atomic transfer size × TC(字節)。
2410的DMA支持四類DMA傳輸:系統總線到系統總線(ASB/AHB to ASB/AHB),系統總線到外設總線(ASB/AHB to APB),外設總線到系統總線(APB to ASB/AHB),外設總線到外設總線(APB to APB)。
2410 DMA 的服務模式:
共有兩種服務模式,一種是單一服務模式(single service),另外一種是整體服務模式(whole service)。
在單一服務模式下,不使用傳統的DMA計數器,三個DMA狀態被順序執行一次後停止,等待DMA 請求再一次來臨後再重新開始另一次循環。
在整體服務模式下,使用傳統的DMA 計數器,狀態機會停留在狀態三,直到DMA計數器的值減爲零,再回到狀態一,等待下一次DMA請求。
2410 DMA 數據傳輸模式:
共有兩種數據傳輸模式:
單位數據傳輸模式:執行一次讀操作和一次寫操作。
併發數據傳輸模式:執行四次讀操作和四次寫操作。
2410 DMA 的基本時序:
nXDREQ請求生效並經過2CLK週期同步後,nXDACK響應並開始生效,但至少還要經過3CLK的週期延遲,DMA控制器纔可獲得總線的控制權,並開始數據傳輸。
2410 DMA 的兩種協議模式:
請求模式:If XnXDREQ remains asserted, the next transfer starts immediately. Otherwise it waits for XnXDREQ to be asserted.
握手模式:If XnXDREQ is deasserted, DMA deasserts XnXDACK in 2cycles. Otherwise it waits until XnXDREQ is deasserted.
2410 DMA REQ與ACK 協議類型:
共有三種協議類型:
單一服務請求:
單一服務握手:
整體服務握手:
根據上面所說的服務模式和協議模式,很容易推知這三種協議的時序分別是什麼。
//=================================================
以下分配了四個DMA通道:
s3c2410_dma_t dma_chan[MAX_S3C2410_DMA_CHANNELS];
每個DMA通道維護着一個多緩衝區組成的單鏈表等待隊列,執行DMA操作時先更新DMA通道控制寄存器內容,再依次摘取當前緩衝區投入使用,緩衝區頭指針順次前移;需要插入新的緩衝區時,可從head或tail插入;
圖A詳細分析了數據結構關係:
DMA驅動主要函數功能分析(linux/arch/arm/mach-s3c2410/dma.c)
寫一個DMA驅動的主要工作包括:DMA通道申請、DMA中斷申請、控制寄存器設置、掛入DMA等待隊列、清除DMA中斷、釋放DMA通道。Dma.c中對這些工作作了很好的實現,以下具體分析關鍵函數:
int s3c2410_request_dma(const char *device_id, dmach_t channel,
dma_callback_t write_cb, dma_callback_t read_cb) (s3c2410_dma_queue_buffer);
函數描述:申請某通道的DMA資源,填充s3c2410_dma_t 數據結構的內容,申請DMA中斷。
輸入參數:device_id DMA 設備名;channel 通道號;
write_cb DMA寫操作完成的回調函數;read_cb DMA讀操作完成的回調函數
輸出參數:若channel通道已使用,出錯返回;否則,返回0
int s3c2410_dma_queue_buffer(dmach_t channel, void *buf_id,
dma_addr_t data, int size, int write) (s3c2410_dma_stop);
函數描述:這是DMA操作最關鍵的函數,它完成了一系列動作:分配並初始化一個DMA內核緩衝區控制結構,並將它插入DMA等待隊列,設置DMA控制寄存器內容,等待DMA操作觸發
輸入參數: channel 通道號;buf_id,緩衝區標識
dma_addr_t data DMA數據緩衝區起始物理地址;size DMA數據緩衝區大小;write 是寫還是讀操作
輸出參數:操作成功,返回0;否則,返回錯誤號
int s3c2410_dma_stop(dmach_t channel)
函數描述:停止DMA操作。
int s3c2410_dma_flush_all(dmach_t channel)
函數描述:釋放DMA通道所申請的所有內存資源
void s3c2410_free_dma(dmach_t channel)
函數描述:釋放DMA通道
因爲各函數功能強大,一個完整的DMA驅動程序中一般只需調用以上3個函數即可。可在驅動初始化中調用s3c2410_request_dma,開始DMA傳輸前調用s3c2410_dma_queue_buffer,釋放驅動模塊時調用s3c2410_free_dma。
具體的DMA實例分析
Linux下的IIS音頻驅動主要都在/kernel/drivers/sound/s3c2410-uda1341.c文件中。它定義了2個重要的數據結構audio_bufer_t, 管理audio緩衝區的數據結構;audio_stream_t 管理多緩衝區的數據結構,它爲音頻流數據組成了一個環形緩衝區。
我們先看一下加載驅動模塊時的初始化函數:int __init s3c2410_uda1341_init(void),該函數先初始化IO和UDA341芯片,然後語句s3c2410_request_dma("I2SSDO", s->dma_ch, audio_dmaout_done_callback, NULL);申請了一個DMA通道用於輸出音頻數據;
smdk2410_audio_write是音頻驅動最關鍵的函數,它從用戶進程中拷貝音頻數據流至DMA內核緩衝區,然後適用DMA通道2把音頻數據發送出去,從而輸出聲音。我們可以在smdk2410_audio_write 中發現語句s3c2410_dma_queue_buffer(s->dma_ch, (void *) b,b->dma_addr, b->size, DMA_BUF_WR);就是它爲DMA寫操作作好了一切準備,當I2SSDO中斷到來,DMA2通道執行一次寫操作(從DMA緩衝寫往IO地址0x55000010)。
smdk2410_audio_release函數中先後調用了s3c2410_dma_flush_all、s3c2410_free_dma釋放DMA2佔用的內存資源、和釋放DMA2通道.
//=========================================
前天完成了UDA1341的驅動,程序是網上下載的,僅僅小改一下而已,昨晚上
分析了其中的DMA部分,有些小小的感想,如果不正確希望讀者能夠告訴我。謝謝。
寫的很亂,沒有整理。
DMA的基礎就不介紹了,可以參考http://blog.chinaunix.net/u1/58640/showart_483567.html
和規格書。我主要根據audio的使用,介紹一下DMA的使用。
1.首先對DMA進行初始化。
如對outstream的初始化
channel = 2;
source = S3C2410_DMASRC_MEM;//源地址爲Memory
hwcfg = 3;
devaddr = 0x55000010;
dcon = 0xa0800000;
flags = S3C2410_DMAF_AUTOSTART;
/* s3c2410_dma_devconfig
*
* configure the dma source/destination hardware type and address
* 配置DMA的源和目的的硬件類型和地址
* source: S3C2410_DMASRC_HW: source is hardware 源是硬件
* S3C2410_DMASRC_MEM: source is memory 源是內存
*
* hwcfg: the value for xxxSTCn register,
* bit 0: 0=increment pointer, 1=leave pointer
* bit 1: 0=soucre is AHB, 1=soucre is APB
*
* devaddr: physical address of the source
*/
因爲我們的DMA 源是內存,目的是硬件IIS的固定地址,所以hwcfg應該
設定爲APB FIX_ADDR 即:3
int s3c2410_dma_devconfig(int channel,
s3c2410_dmasrc_t source,
int hwcfg,
unsigned long devaddr)
{
s3c2410_dma_chan_t *chan = &s3c2410_chans[channel];
check_channel(channel);
pr_debug("%s: source=%d, hwcfg=%08x, devaddr=%08lx/n",
__FUNCTION__, (int)source, hwcfg, devaddr);
chan->source = source;
chan->dev_addr = devaddr;
switch (source) {
case S3C2410_DMASRC_HW:
/* source is hardware */
pr_debug("%s: hw source, devaddr=%08lx, hwcfg=%d/n",
__FUNCTION__, devaddr, hwcfg);
dma_wrreg(chan, S3C2410_DMA_DISRCC, hwcfg & 3);
dma_wrreg(chan, S3C2410_DMA_DISRC, devaddr);
dma_wrreg(chan, S3C2410_DMA_DIDSTC, (0<<1) | (0<<0));
chan->addr_reg = dma_regaddr(chan, S3C2410_DMA_DIDST);
return 0;
case S3C2410_DMASRC_MEM:
/* source is memory */
pr_debug( "%s: mem source, devaddr=%08lx, hwcfg=%d/n",
__FUNCTION__, devaddr, hwcfg);
dma_wrreg(chan, S3C2410_DMA_DISRCC, (0<<1) | (0<<0));
dma_wrreg(chan, S3C2410_DMA_DIDST, devaddr);
dma_wrreg(chan, S3C2410_DMA_DIDSTC, hwcfg & 3);
chan->addr_reg = dma_regaddr(chan, S3C2410_DMA_DISRC);
return 0;
}
printk(KERN_ERR "dma%d: invalid source type (%d)/n", channel, source);
return -EINVAL;
}
/* s3c2410_dma_config
*
* xfersize: size of unit in bytes (1,2,4)
* dcon: base value of the DCONx register
*/
dcon = 0xa0800000;
因爲IIS是16bit的,所以unit爲2.
s3c2410_dma_config(channel, 2, dcon);
//設定DMA傳輸結束後的回調函數
s3c2410_dma_set_buffdone_fn(channel, audio_dmaout_done_callback);
//設定S3C2410_DMAF_AUTOSTART
s3c2410_dma_setflags(channel, flags);
//申請DMA通道
ret = s3c2410_dma_request(s->dma_ch, &s3c2410iis_dma_out, NULL);
以上是初始化部分,之後通過dmabuf = dma_alloc_coherent(NULL, dmasize, &dmaphys, GFP_KERNEL);
來申請緩衝,dmabuf爲虛擬地址,dmaphys是物理地址,虛擬地址用來讓驅動寫buf用,dmaphys用來傳給dma。
dma關心的是實際的物理地址。
然後通過s3c2410_dma_enqueue(s->dma_ch, (void *) b, b->dma_addr, b->size);來啓動dma傳輸,
這裏面的參數就是dmaphys,它爲實際地址。
如果dma傳輸結束,就會調用:
static void audio_dmaout_done_callback(s3c2410_dma_chan_t *ch, void *buf, int size,
s3c2410_dma_buffresult_t result)
{
audio_buf_t *b = (audio_buf_t *) buf;
DPRINTK("audio_dmaout_done_callback/n");
up(&b->sem);
wake_up(&b->sem.wait);
}
來啓動下一個buf的傳輸。