DMA通道的使用
一,
要使用DMA先要初始化一個結(jié)構(gòu)體這個結(jié)構(gòu)體就只有一個字段name,在DMA中斷請求時這個name
將傳遞個dev_name。intrequest_irq( , , ,const char *dev_name,);。
struct s3c2410_dma_client {
char *name;
};
還得知道要使用的DMA源。然后就可以調(diào)用下面函數(shù)來請求該DMA源對應(yīng)的DMA通道了。
int s3c2410_dma_request(unsigned int channel,struct s3c2410_dma_client *client,void *dev)
在上面請求函數(shù)中做了兩項(xiàng)重要工作:
(1)
調(diào)用函數(shù)chan = s3c2410_dma_map_channel(channel); 根據(jù)DMA源在 源-----通道 管理結(jié)構(gòu)體
dma_sel.map(該結(jié)構(gòu)體在《DMA原理》中講過)中找出一個該DMA源所能請求并且空閑的通道,再根據(jù)
該通道號在數(shù)組s3c2410_chans[ch]中獲取一個被部分初始化過的DMA通道管理結(jié)構(gòu)體s3c2410_dma_chan。
然后將這個結(jié)構(gòu)體指針放到數(shù)組dma_chan_map[channel] 中,該數(shù)組中存放的都是在使用的DMA通道的管理結(jié)構(gòu)體
指針,獲取DMA通道管理結(jié)構(gòu)體用下面函數(shù)來實(shí)現(xiàn):
static struct s3c2410_dma_chan *lookup_dma_channel(unsigned int channel)
{
。。。。。。
return dma_chan_map[channel];
}
下面是函數(shù)s3c2410_dma_map_channel()中的主要工作。
ch_map = dma_sel.map + channel;
。。。。。。
dmach = &s3c2410_chans[ch];
dmach->map = ch_map;
dma_chan_map[channel] = dmach;
(2)
獲取DMA通道之后注冊該通道的中斷,并關(guān)聯(lián)中斷函數(shù)s3c2410_dma_irq(),該函數(shù)在后面講解。
request_irq(chan->irq, s3c2410_dma_irq, IRQF_DISABLED,client->name, (void *)chan);
二,
int s3c2410_dma_devconfig(int channel,enum s3c2410_dmasrc source,int hwcfg,unsigned long devaddr)
{
struct s3c2410_dma_chan *chan = lookup_dma_channel(channel);
。。。。。
chan->source = source;
chan->dev_addr = devaddr;
chan->hw_cfg = hwcfg;
switch (source) {
case S3C2410_DMASRC_HW://源在外設(shè),既是從外設(shè)讀數(shù)據(jù)到內(nèi)存,源的地址是確定的。
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));//目的地址在系統(tǒng)總線AHB上
chan->addr_reg = dma_regaddr(chan, S3C2410_DMA_DIDST);//將目的初始寄存器地址存于chan->addr_reg。
//外設(shè)的地址是確定的,內(nèi)存地址不確定,chan->addr_reg上存的地址或是初始源寄存器,初始目的寄存器
//根據(jù)從外設(shè)讀寫數(shù)據(jù)的不同而不同,但內(nèi)存的地址始終是寫到chan->addr_reg所指向的寄存器中。
break;
case S3C2410_DMASRC_MEM:////源在內(nèi)存,既是將內(nèi)存數(shù)據(jù)寫到外設(shè),目的地址是確定的。
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);
break;
default:
return -EINVAL;
}
return 0;
}
三,
int s3c2410_dma_config(unsigned int channel, int xferunit,int dcon)
{
struct s3c2410_dma_chan *chan = lookup_dma_channel(channel);//找出源channel對應(yīng)的DMA通道管理結(jié)構(gòu)體。
dcon |= chan->dcon & dma_sel.dcon_mask;//設(shè)定DMA源選擇位
switch (xferunit) {//數(shù)據(jù)傳輸?shù)膯挝淮笮?br/>case 1:
dcon |= S3C2410_DCON_BYTE;
break;
case 2:
dcon |= S3C2410_DCON_HALFWORD;
break;
case 4:
dcon |= S3C2410_DCON_WORD;
break;
default:
return -EINVAL;
}
dcon |= S3C2410_DCON_HWTRIG;//源觸發(fā),不是軟件觸發(fā)
dcon |= S3C2410_DCON_INTREQ;//中斷使能
//將DMA通道控制寄存器的配置存于chan->dcon,到現(xiàn)在DMA通道控制寄存器中還有傳輸計(jì)數(shù)的值沒有配置了,
//當(dāng)內(nèi)存加載到初始源寄存器或是初始目的寄存器時再配置該值,并將chan->dcon中的值一并寫入DMA控制寄存器。
chan->dcon = dcon;
chan->xfer_unit = xferunit;
return 0;
}
四,
int s3c2410_dma_set_buffdone_fn(unsigned int channel, s3c2410_dma_cbfn_t rtn)
{
struct s3c2410_dma_chan *chan = lookup_dma_channel(channel);
。。。。。。
//設(shè)置回調(diào)函數(shù)。待傳輸數(shù)據(jù)的內(nèi)存空間可能是不連續(xù)的,有很多段,當(dāng)一段內(nèi)存用完后
//調(diào)用該回調(diào)函數(shù)進(jìn)行處理。
chan->callback_fn = rtn;
return 0;
}
五,
int s3c2410_dma_setflags(unsigned int channel, unsigned int flags)
{
struct s3c2410_dma_chan *chan = lookup_dma_channel(channel);
chan->flags = flags;//設(shè)定flags的值比如S3C2410_DMAF_AUTOSTART,這個值在后面會用到。
return 0;
}
六,
s3c2410_dma_ctrl(host->dma, S3C2410_DMAOP_FLUSH);//下面講解
//函數(shù)s3c2410_dma_ctrl()的原型如下:
int s3c2410_dma_ctrl(unsigned int channel, enum s3c2410_chan_op op)
{
struct s3c2410_dma_chan *chan = lookup_dma_channel(channel);
switch (op) {
case S3C2410_DMAOP_START:
return s3c2410_dma_start(chan);
case S3C2410_DMAOP_STOP:
return s3c2410_dma_dostop(chan);
case S3C2410_DMAOP_PAUSE:
case S3C2410_DMAOP_RESUME:
return -ENOENT;
case S3C2410_DMAOP_FLUSH:
return s3c2410_dma_flush(chan);
case S3C2410_DMAOP_STARTED:
return s3c2410_dma_started(chan);
case S3C2410_DMAOP_TIMEOUT:
return 0;
}
return -ENOENT; /* unknown, don't bother */
}
此處我們傳輸?shù)闹凳荢3C2410_DMAOP_FLUSH,應(yīng)該執(zhí)行函數(shù)s3c2410_dma_flush(chan);。
static int s3c2410_dma_flush(struct s3c2410_dma_chan *chan)
{
。。。。。。
chan->curr = chan->next = chan->end = NULL; ////
。。。。。。
s3c2410_dma_waitforstop(chan);//循環(huán)等待屏蔽觸發(fā)寄存器中的DMA通道開關(guān)位的關(guān)閉。
return 0;
}
七,
//先將各內(nèi)存段掛到sg連上,調(diào)用函數(shù)dma_map_sg()映射一個發(fā)散/匯聚 DMA 操作,返回合并后的內(nèi)存段數(shù)。
dma_len =dma_map_sg(struct device *dev, struct scatterlist *sg, int nents, enum dma_data_direction direction)
for (i = 0; i < dma_len; i++) {
//分配一個數(shù)據(jù)段管理結(jié)構(gòu)體,并將各數(shù)據(jù)段穿成單向鏈表,以及加載一個數(shù)據(jù)段到DMA通道
//并開啟DMA數(shù)據(jù)傳輸s3c2410_dma_ctrl(chan->number | DMACH_LOW_LEVEL,S3C2410_DMAOP_START);
res = s3c2410_dma_enqueue(unsigned int channel, void *id,dma_addr_t data, int size)
}
int s3c2410_dma_enqueue(unsigned int channel, void *id,
dma_addr_t data, int size)
{
struct s3c2410_dma_chan *chan = lookup_dma_channel(channel);
struct s3c2410_dma_buf *buf;
unsigned long flags;
//從內(nèi)存池dma_kmem為內(nèi)存管理結(jié)構(gòu)體s3c2410_dma_buf分配內(nèi)存。
buf = kmem_cache_alloc(dma_kmem, GFP_ATOMIC);
buf->next = NULL;
buf->data = buf->ptr = data;//該段內(nèi)存的物理地址
buf->size = size;//該段內(nèi)存的大小
buf->id = id;
buf->magic = BUF_MAGIC;
local_irq_save(flags);
if (chan->curr == NULL) {//該函數(shù)第一次被調(diào)用也就是加載該通道的第一段內(nèi)存會進(jìn)入下面分支。
//在多段內(nèi)存工作過程中chan->curr指向當(dāng)前 目的/源 寄存器中加載的內(nèi)存段,也表示內(nèi)存段鏈表
//中的第一個內(nèi)存段。chan->next指向初始 目的/源 寄存器中加載的內(nèi)存段。也表示內(nèi)存段鏈表
//中的第二個內(nèi)存段。chan->end表示內(nèi)存段鏈表中最后一個內(nèi)存段。
chan->curr = buf;
chan->end = buf;//此時只有一段內(nèi)存。
chan->next = NULL;
} else {
chan->end->next = buf;//以后內(nèi)存段都是從鏈表尾插入鏈表的。
chan->end = buf;
}
//第一個內(nèi)存段加入chan->next指向第一個內(nèi)存段,第二個內(nèi)存段加入chan->next指向第二個內(nèi)存段,
////第三個內(nèi)存段加入chan->next還是指向第二個內(nèi)存段,
if (chan->next == NULL)
chan->next = buf;
//只有函數(shù)s3c2410_dma_ctrl()中調(diào)用的各函數(shù)可以改變chan->state的值,該值代表DMA通道的工作狀態(tài)。
//chan->load_state表示內(nèi)存段的在DMA寄存器中的加載情況,一般在存儲段加載函數(shù)中改變
//該值。在第一次調(diào)用該函數(shù)時是不會進(jìn)入下面分支的,第一次調(diào)用本函數(shù)會在下面調(diào)用函數(shù)
//s3c2410_dma_ctrl(chan->number | DMACH_LOW_LEVEL,S3C2410_DMAOP_START);開啟DMA數(shù)據(jù)傳輸,
//第二次調(diào)用該函數(shù)可能就會進(jìn)入下面分支了。
if (chan->state == S3C2410_DMA_RUNNING) {
if (chan->load_state == S3C2410_DMALOAD_1LOADED && 1) {
//如果開啟了DMA數(shù)據(jù)傳輸有一段內(nèi)存加載到了初始 目的/源 地址寄存器,但DMA當(dāng)前 目的/源 地址寄存器中還沒有
//加載內(nèi)存地址,則等待初始 目的/源 地址寄存器中的內(nèi)存地址加載到當(dāng)前 目的/源 地址寄存器中。
if (s3c2410_dma_waitforload(chan, __LINE__) == 0) {
。。。。。。
}
}
while (s3c2410_dma_canload(chan) && chan->next != NULL) {
//如果DMA當(dāng)前 目的/源 地址寄存器 或 DMA初始 目的/源 地址寄存器中沒有加載內(nèi)存地址則,加載一段內(nèi)存
/*
函數(shù)s3c2410_dma_loadbuf