DMA通道的使用

DMA通道的使用
2010-11-25 16:43

一,

要使用DMA先要初始化一个结构体这个结构体就只有一个字段name,在DMA中断请求时这个name

将传递个dev_name。int request_irq( , , , const char *dev_name, );。

struct s3c2410_dma_client {
char                *name;
};

还得知道要使用的DMA源。然后就可以调用下面函数来请求该DMA源对应的DMA通道了。

int s3c2410_dma_request(unsigned int channel, struct s3c2410_dma_client *client, void *dev)

在上面请求函数中做了两项重要工作:

(1)

调用函数chan = s3c2410_dma_map_channel(channel); 根据DMA源在 源-----通道 管理结构体

dma_sel.map(该结构体在《DMA原理》中讲过)中找出一个该DMA源所能请求并且空闲的通道,再根据

该通道号在数组s3c2410_chans[ch]中获取一个被部分初始化过的DMA通道管理结构体s3c2410_dma_chan。

然后将这个结构体指针放到数组dma_chan_map[channel] 中,该数组中存放的都是在使用的DMA通道的管理结构体

指针,获取DMA通道管理结构体用下面函数来实现:

static struct s3c2410_dma_chan *lookup_dma_channel(unsigned int channel)
{

。。。。。。
   return dma_chan_map[channel];
}

下面是函数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通道之后注册该通道的中断,并关联中断函数s3c2410_dma_irq(),该函数在后面讲解。

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: //源在外设,既是从外设读数据到内存,源的地址是确定的。
   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)); //目的地址在系统总线AHB上

   chan->addr_reg = dma_regaddr(chan, S3C2410_DMA_DIDST); //将目的初始寄存器地址存于chan->addr_reg。

//外设的地址是确定的,内存地址不确定,chan->addr_reg上存的地址或是初始源寄存器,初始目的寄存器

//根据从外设读写数据的不同而不同,但内存的地址始终是写到chan->addr_reg所指向的寄存器中。
   break;

case S3C2410_DMASRC_MEM: 源在内存,既是将内存数据写到外设,目的地址是确定的。
   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对应的DMA通道管理结构体。

dcon |= chan->dcon & dma_sel.dcon_mask; //设定DMA源选择位

switch (xferunit) { //数据传输的单位大小
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; //源触发,不是软件触发
dcon |= S3C2410_DCON_INTREQ; //中断使能

//将DMA通道控制寄存器的配置存于chan->dcon,到现在DMA通道控制寄存器中还有传输计数的值没有配置了,

//当内存加载到初始源寄存器或是初始目的寄存器时再配置该值,并将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);

。。。。。。

//设置回调函数。待传输数据的内存空间可能是不连续的,有很多段,当一段内存用完后

//调用该回调函数进行处理。

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; //设定flags的值比如S3C2410_DMAF_AUTOSTART,这个值在后面会用到。

return 0;
}

六,

s3c2410_dma_ctrl(host->dma, S3C2410_DMAOP_FLUSH); //下面讲解

//函数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 */
}

此处我们传输的值是S3C2410_DMAOP_FLUSH,应该执行函数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); //循环等待屏蔽触发寄存器中的DMA通道开关位的关闭。

return 0;
}

七,

//先将各内存段挂到sg连上,调用函数dma_map_sg()映射一个发散/汇聚 DMA 操作,返回合并后的内存段数。

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++) {

//分配一个数据段管理结构体,并将各数据段穿成单向链表,以及加载一个数据段到DMA通道

//并开启DMA数据传输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;

//从内存池dma_kmem为内存管理结构体s3c2410_dma_buf分配内存。

buf = kmem_cache_alloc(dma_kmem, GFP_ATOMIC);

buf->next = NULL;
buf->data = buf->ptr = data; //该段内存的物理地址
buf->size = size; //该段内存的大小
buf->id    = id; 
buf->magic = BUF_MAGIC;

local_irq_save(flags);

if (chan->curr == NULL) { //该函数第一次被调用也就是加载该通道的第一段内存会进入下面分支。

//在多段内存工作过程中chan->curr指向当前 目的/源 寄存器中加载的内存段,也表示内存段链表

//中的第一个内存段。chan->next指向初始 目的/源 寄存器中加载的内存段。也表示内存段链表

//中的第二个内存段。chan->end 表示内存段链表中最后一个内存段。

   chan->curr = buf; 
   chan->end = buf; //此时只有一段内存。
   chan->next = NULL;
} else {

   chan->end->next = buf; //以后内存段都是从链表尾插入链表的。
   chan->end = buf;
}

//第一个内存段加入chan->next指向第一个内存段,第二个内存段加入chan->next指向第二个内存段,

第三个内存段加入chan->next还是指向第二个内存段,
if (chan->next == NULL)
   chan->next = buf;

//只有函数s3c2410_dma_ctrl()中调用的各函数可以改变chan->state的值,该值代表DMA通道的工作状态。

//chan->load_state表示内存段的在DMA寄存器中的加载情况,一般在存储段加载函数中改变

//该值。在第一次调用该函数时是不会进入下面分支的,第一次调用本函数会在下面调用函数

//s3c2410_dma_ctrl(chan->number | DMACH_LOW_LEVEL,S3C2410_DMAOP_START);开启DMA数据传输,

//第二次调用该函数可能就会进入下面分支了。
if (chan->state == S3C2410_DMA_RUNNING) {
   if (chan->load_state == S3C2410_DMALOAD_1LOADED && 1) {

//如果开启了DMA数据传输有一段内存加载到了初始 目的/源 地址寄存器,但DMA当前 目的/源 地址寄存器中还没有

//加载内存地址,则等待初始 目的/源 地址寄存器中的内存地址加载到当前 目的/源 地址寄存器中。
    if (s3c2410_dma_waitforload(chan, __LINE__) == 0) { 
。。。。。。
    }
   }

   while (s3c2410_dma_canload(chan) && chan->next != NULL) {

//如果DMA当前 目的/源 地址寄存器 或 DMA初始 目的/源 地址寄存器中没有加载内存地址则,加载一段内存

/*

函数s3c2410_dma_loadbuffer(chan, chan->next);的主要工作是将内存段地址写入chan->addr_reg指向

的寄存器,配置DMA控制寄存器,改变内存加载状态chan->load_state。

writel(buf->data, chan->addr_reg);

dma_wrreg(chan, S3C2410_DMA_DCON,
    chan->dcon | reload | (buf->size/chan->xfer_unit));

chan->next = buf->next;

*/
    s3c2410_dma_loadbuffer(chan, chan->next);  
   }
} else if (chan->state == S3C2410_DMA_IDLE) {

//chan->flags的值由函数s3c2410_dma_setflags(unsigned int channel, unsigned int flags)来设置如步骤五。

//如果没有设置S3C2410_DMAF_AUTOSTART此时不会开启DMA,必须在以后调用函数.

// s3c2410_dma_ctrl(chan->number | DMACH_LOW_LEVEL, S3C2410_DMAOP_START);来开启DMA。

//该函数设置DMA通道工作状态为S3C2410_DMA_RUNNING,chan->state = S3C2410_DMA_RUNNING;
   if (chan->flags & S3C2410_DMAF_AUTOSTART) { 
    s3c2410_dma_ctrl(chan->number | DMACH_LOW_LEVEL,
      S3C2410_DMAOP_START);
   }
}

local_irq_restore(flags);
return 0;
}

DMA中断处理函数:

在步骤一中注册了DMA通道的中断,当一段数据处理完后中断被触发,中断处理函数如下:

static irqreturn_t s3c2410_dma_irq(int irq, void *devpw)
{
struct s3c2410_dma_chan *chan = (struct s3c2410_dma_chan *)devpw;
struct s3c2410_dma_buf *buf;

buf = chan->curr;

dbg_showchan(chan);

switch (chan->load_state) {
case S3C2410_DMALOAD_1RUNNING:

   chan->load_state = S3C2410_DMALOAD_NONE;
   break;

case S3C2410_DMALOAD_1LOADED:

   chan->load_state = S3C2410_DMALOAD_NONE;
   break;

case S3C2410_DMALOAD_1LOADED_1RUNNING:

   chan->load_state = S3C2410_DMALOAD_1LOADED;
   break;

case S3C2410_DMALOAD_NONE:
   break;

default:
   break;
}

if (buf != NULL) {

   chan->curr = buf->next;
   buf->next = NULL;

   if (buf->magic != BUF_MAGIC) {
    return IRQ_HANDLED;
   }

//下面函数将调用回调函数chan->callback_fn,该函数完成将内存段计数减一等工作。

   s3c2410_dma_buffdone(chan, buf, S3C2410_RES_OK);
   s3c2410_dma_freebuf(buf); //释放已使用完的内存段。
} else {  

//这里else分支什么也没做。
}

if (chan->next != NULL && chan->state != S3C2410_DMA_IDLE) {
   unsigned long flags;

   switch (chan->load_state) {
   case S3C2410_DMALOAD_1RUNNING:
    break;

   case S3C2410_DMALOAD_NONE:
    break;

   case S3C2410_DMALOAD_1LOADED:
    if (s3c2410_dma_waitforload(chan, __LINE__) == 0) {
     return IRQ_HANDLED;
    }

    break;

   case S3C2410_DMALOAD_1LOADED_1RUNNING:
    goto no_load;

   default:
    return IRQ_HANDLED;
   }

   local_irq_save(flags);
   s3c2410_dma_loadbuffer(chan, chan->next);   //加载一段内存。
   local_irq_restore(flags);
} else {
   s3c2410_dma_lastxfer(chan);
   if (chan->load_state == S3C2410_DMALOAD_NONE) {

/*

函数s3c2410_dma_ctrl(chan->number | DMACH_LOW_LEVEL, S3C2410_DMAOP_STOP);完成的

主要工作如下

tmp = dma_rdreg(chan, S3C2410_DMA_DMASKTRIG);
tmp |= S3C2410_DMASKTRIG_STOP;                             //停止DMA操作
dma_wrreg(chan, S3C2410_DMA_DMASKTRIG, tmp);

chan->state      = S3C2410_DMA_IDLE;         
chan->load_state = S3C2410_DMALOAD_NONE;

*/
    s3c2410_dma_ctrl(chan->number | DMACH_LOW_LEVEL,
      S3C2410_DMAOP_STOP);
   }
}

no_load:
return IRQ_HANDLED;
}

你可能感兴趣的:(Linux)