Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1695821
  • 博文数量: 584
  • 博客积分: 13857
  • 博客等级: 上将
  • 技术积分: 11883
  • 用 户 组: 普通用户
  • 注册时间: 2009-12-16 09:34

分类:

2010-08-19 16:18:35

同 时我们也不难推测 , 使用 DMA 的函数应该都在 Arch\arm\plat-s3c24xx\dma.c . 没错 , 说的更具体些就是这个文件下被 EXPORT_SYMBOL 出来的函数都是提供给外部使用的 , 也就是其他部分使用 DMA 的接口 . 知道了这些我们接着来分析这些被 EXPORT_SYMBOL 的函数吧 .

 

 

Arch\arm\plat-s3c24xx\dma.c:

/* s3c2410_dma_getposition

  *

  * returns the current transfer points for the dma source and destination

*/

int s3c2410_dma_getposition(dmach_t channel, dma_addr_t *src, dma_addr_t *dst)

{

获取保存该 channel 信息的对象 , 初始化的时候讲过

       struct s3c2410_dma_chan *chan = lookup_dma_channel(channel);  

 

       if (chan == NULL)

              return -EINVAL;

 

       if (src != NULL)   // 获取源地址

              *src = dma_rdreg(chan, S3C2410_DMA_DCSRC); 

 

       if (dst != NULL)  // 获取目的地址

              *dst = dma_rdreg(chan, S3C2410_DMA_DCDST);

 

       return 0;

}

 

EXPORT_SYMBOL(s3c2410_dma_getposition);

这 个函数获取某个 channel 当前正在传输的源地址和目的地址 . 主要就是通过读该 channel 的源和目的寄存器获得的 . S3C2410_DMA_DCSRC, S3C2410_DMA_DCDST 就是源和目的的偏移地址 . 可参考 2410 datasheet.  dma_rdreg 就是读寄存器 .

Arch\arm\plat-s3c24xx\dma.c:

#define dma_rdreg(chan, reg) readl((chan)->regs + (reg))

接着看下一个 export 的函数

/* s3c2410_dma_devconfig

  *

  * configure the dma source/destination hardware type and address

  *

  * 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=source is AHB, 1=source is APB

  *

  * devaddr:   physical address of the source

*/

Arch\arm\plat-s3c24xx\dma.c:

int s3c2410_dma_devconfig(int channel,

                       enum s3c2410_dmasrc source,

                       int hwcfg,

                       unsigned long devaddr)

{

    // 获取保存该 channel 信息的对象 , 初始化的时候讲过

       struct s3c2410_dma_chan *chan = lookup_dma_channel(channel);

 

       if (chan == NULL)

              return -EINVAL;

 

       pr_debug("%s: source=%d, hwcfg=%08x, devaddr=%08lx\n",

                __FUNCTION__, (int)source, hwcfg, devaddr);

 

       chan->source = source;  // 保存 DMA

       chan->dev_addr = devaddr;   // 保存源地址

 

    // 根据不同的 DMA 源来初始化 DMA channel

       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;

}

 

EXPORT_SYMBOL(s3c2410_dma_devconfig);

这个函数用来配置某个 channel 的源的类型及源地址 , 然后为某种源设置好地址增长方式 , 具体寄存器含义参考 2410 datasheet, 2410 DMA 的各种操作模式可参考我的另一篇文章 .

Arch\arm\plat-s3c24xx\dma.c:

int s3c2410_dma_set_buffdone_fn(dmach_t channel, s3c2410_dma_cbfn_t rtn)

{

       struct s3c2410_dma_chan *chan = lookup_dma_channel(channel);

 

       if (chan == NULL)

              return -EINVAL;

 

       pr_debug("%s: chan=%p, callback rtn=%p\n", __FUNCTION__, chan, rtn);

 

       chan->callback_fn = rtn;   // 设置回调函数

 

       return 0;

}

 

EXPORT_SYMBOL(s3c2410_dma_set_buffdone_fn);

该函数主要为某个 channel 设置一个 done 的回调函数 . 该回调函数会在传输完成后被调用 .

 

Arch\arm\plat-s3c24xx\dma.c:

/* do we need to protect the settings of the fields from

  * irq?

*/

int s3c2410_dma_set_opfn(dmach_t channel, s3c2410_dma_opfn_t rtn)

{

       struct s3c2410_dma_chan *chan = lookup_dma_channel(channel);

 

       if (chan == NULL)

              return -EINVAL;

 

       pr_debug("%s: chan=%p, op rtn=%p\n", __FUNCTION__, chan, rtn);

 

       chan->op_fn = rtn;

 

       return 0;

}

 

EXPORT_SYMBOL(s3c2410_dma_set_opfn);

该函数主要为某个 channel 设置一个操作的回调函数 . 该回调函数会在操作该 channel 时被调用 ( 有哪些操作会在 s3c2410_dma_ctrl 里看到 )

 

Arch\arm\plat-s3c24xx\dma.c:

int s3c2410_dma_setflags(dmach_t channel, unsigned int flags)

{

       struct s3c2410_dma_chan *chan = lookup_dma_channel(channel);

 

       if (chan == NULL)

              return -EINVAL;

 

       pr_debug("%s: chan=%p, flags=%08x\n", __FUNCTION__, chan, flags);

 

       chan->flags = flags;   // 设置标记

 

       return 0;

}

 

EXPORT_SYMBOL(s3c2410_dma_setflags);

该函数主要为某个 channel 设置一个标记 , 标记有 :

Include\asm-arm\arch-s3c2410\dma.h:

/* flags */

 

#define S3C2410_DMAF_SLOW         (1<<0)   /* slow, so don't worry about

                                       * waiting for reloads */

#define S3C2410_DMAF_AUTOSTART    (1<<1)   /* auto-start if buffer queued */

我们会在后面看到 flag 的使用

 

Arch\arm\plat-s3c24xx\dma.c:

/* DMA configuration for each channel

  *

  * DISRCC -> source of the DMA (AHB,APB)

  * DISRC  -> source address of the DMA

  * DIDSTC -> destination of the DMA (AHB,APD)

  * DIDST  -> destination address of the DMA

*/

 

/* s3c2410_dma_config

  *

  * xfersize:     size of unit in bytes (1,2,4)

  * dcon:         base value of the DCONx register

*/

 

int s3c2410_dma_config(dmach_t channel,

                     int xferunit,

                     int dcon)

{

       struct s3c2410_dma_chan *chan = lookup_dma_channel(channel);

 

       pr_debug("%s: chan=%d, xfer_unit=%d, dcon=%08x\n",

                __FUNCTION__, channel, xferunit, dcon);

 

       if (chan == NULL)

              return -EINVAL;

 

       pr_debug("%s: Initial dcon is %08x\n", __FUNCTION__, dcon);

 

       dcon |= chan->dcon & dma_sel.dcon_mask;

 

       pr_debug("%s: New dcon is %08x\n", __FUNCTION__, dcon);

 

       // 设置每个传输单元的大小

       switch (xferunit) {

       case 1:

              dcon |= S3C2410_DCON_BYTE;

              break;

 

       case 2:

              dcon |= S3C2410_DCON_HALFWORD;

              break;

 

       case 4:

              dcon |= S3C2410_DCON_WORD;

              break;

 

       default:

              pr_debug("%s: bad transfer size %d\n", __FUNCTION__, xferunit);

              return -EINVAL;

       }

 

       dcon |= S3C2410_DCON_HWTRIG;   // 硬件请求模式

       dcon |= S3C2410_DCON_INTREQ;   // 打开中断

 

       pr_debug("%s: dcon now %08x\n", __FUNCTION__, dcon);

   

    // 保存配置到全局变量中

       chan->dcon = dcon;

       chan->xfer_unit = xferunit;

 

       return 0;

}

 

EXPORT_SYMBOL(s3c2410_dma_config);

该函数主要用来配置某个 channel 的请求模式 , 传输单元大小等 . 从中可以看出目前只支持硬件请求模式

 

Arch\arm\plat-s3c24xx\dma.c:

int

s3c2410_dma_ctrl(dmach_t channel, enum s3c2410_chan_op op)

{

       struct s3c2410_dma_chan *chan = lookup_dma_channel(channel);

 

       if (chan == NULL)

              return -EINVAL;

 

       switch (op) {

       case S3C2410_DMAOP_START:

              return s3c2410_dma_start(chan);   // 开始一个 DMA 传输

 

       case S3C2410_DMAOP_STOP:

              return s3c2410_dma_dostop(chan);  // 停止一个 DMA 传输

 

       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 */

}

 

EXPORT_SYMBOL(s3c2410_dma_ctrl);

OK, 这个函数主要就是用来启用 , 停止 DMA 操作了 ,  比较重要的一个函数 . 等分析完了 export 的接口后 , 我们在来逐个分析每个 DMA 操作 .

 

 

Arch\arm\plat-s3c24xx\dma.c:

/* s3c2410_dma_free

  *

  * release the given channel back to the system, will stop and flush

  * any outstanding transfers, and ensure the channel is ready for the

  * next claimant.

  *

  * Note, although a warning is currently printed if the freeing client

  * info is not the same as the registrant's client info, the free is still

  * allowed to go through.

*/

 

int s3c2410_dma_free(dmach_t channel, struct s3c2410_dma_client *client)

{

       struct s3c2410_dma_chan *chan = lookup_dma_channel(channel);

       unsigned long flags;

 

       if (chan == NULL)

              return -EINVAL;

 

       local_irq_save(flags);

 

       if (chan->client != client) { 

              printk(KERN_WARNING "dma%d: possible free from different client (channel %p, passed %p)\n",

                     channel, chan->client, client);

       }

 

       /* sort out stopping and freeing the channel */

 

       if (chan->state != S3C2410_DMA_IDLE) {  // channel 正在使用中

              pr_debug("%s: need to stop dma channel %p\n",

                     __FUNCTION__, chan);

 

              /* possibly flush the channel */

              s3c2410_dma_ctrl(channel, S3C2410_DMAOP_STOP);   // 停止该 channel

       }

 

    //reset channel 的相关信息

       chan->client = NULL;

       chan->in_use = 0;

 

       if (chan->irq_claimed)

              free_irq(chan->irq, (void *)chan);  // 释放该中断

 

       chan->irq_claimed = 0;

 

       if (!(channel & DMACH_LOW_LEVEL))

              dma_chan_map[channel] = NULL;

 

       local_irq_restore(flags);

 

       return 0;

}

 

EXPORT_SYMBOL(s3c2410_dma_free);

根据注释我们很清楚了 ,  该函数主要就是释放一个 channel, 使其处于 ready 状态 ,

 

Arch\arm\plat-s3c24xx\dma.c:

/* s3c2410_request_dma

  *

  * get control of an dma channel

*/

 

int s3c2410_dma_request(unsigned int channel,

                     struct s3c2410_dma_client *client,

                     void *dev)

{

       struct s3c2410_dma_chan *chan;

       unsigned long flags;

       int err;

 

       pr_debug("dma%d: s3c2410_request_dma: client=%s, dev=%p\n",

                channel, client->name, dev);

 

       local_irq_save(flags);

    

    // 获取空闲的 channel

       chan = s3c2410_dma_map_channel(channel);

       if (chan == NULL) {   // 无空闲 channel 则返回失败

              local_irq_restore(flags);

              return -EBUSY;

       }

 

       dbg_showchan(chan);

   

    // 保存使用该 channel 的用户等信息

       chan->client = client;

       chan->in_use = 1;

 

       if (!chan->irq_claimed) {   // 该中断没注册

              pr_debug("dma%d: %s : requesting irq %d\n",

                       channel, __FUNCTION__, chan->irq);

 

              chan->irq_claimed = 1;   // 标记注册

              local_irq_restore(flags);

 

              err = request_irq(chan->irq, s3c2410_dma_irq, IRQF_DISABLED,

                              client->name, (void *)chan);  // 注册该中断

 

              local_irq_save(flags);

 

              if (err) {  // 失败则 reset channel

                     chan->in_use = 0;

                     chan->irq_claimed = 0;

                     local_irq_restore(flags);

 

                     printk(KERN_ERR "%s: cannot get IRQ %d for DMA %d\n",

                            client->name, chan->irq, chan->number);

                     return err;

              }

 

              chan->irq_enabled = 1;

       }

 

       local_irq_restore(flags);

 

       /* need to setup */

 

       pr_debug("%s: channel initialised, %p\n", __FUNCTION__, chan);

 

       return 0;

}

 

EXPORT_SYMBOL(s3c2410_dma_request);

  该函数主要就是为请求的用户找到一个空闲的 channel, 并把它分配给该用户 , 同时打开中断 , 保存相关信息 .

 

Arch\arm\plat-s3c24xx\dma.c:

/* s3c2410_dma_enqueue

  *

  * queue an given buffer for dma transfer.

  *

  * id         the device driver's id information for this buffer

  * data       the physical address of the buffer data

  * size       the size of the buffer in bytes

  *

  * If the channel is not running, then the flag S3C2410_DMAF_AUTOSTART

  * is checked, and if set, the channel is started. If this flag isn't set,

  * then an error will be returned.

  *

  * It is possible to queue more than one DMA buffer onto a channel at

  * once, and the code will deal with the re-loading of the next buffer

  * when necessary.

*/

 

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;

 

       if (chan == NULL)

              return -EINVAL;

 

       pr_debug("%s: id=%p, data=%08x, size=%d\n",

                __FUNCTION__, id, (unsigned int)data, size);

   

    // 从高速缓冲中分配一块 buffer 用于 DMA 传输 , dma_kmem 是我们在初始化的时候就创建好的

       buf = kmem_cache_alloc(dma_kmem, GFP_ATOMIC);

       if (buf == NULL) {

              pr_debug("%s: out of memory (%ld alloc)\n",

                       __FUNCTION__, (long)sizeof(*buf));

              return -ENOMEM;

       }

 

       //pr_debug("%s: new buffer %p\n", __FUNCTION__, buf);

       //dbg_showchan(chan);

   

    // 初始化这块要被传输的 buf

       buf->next  = NULL;

       buf->data  = buf->ptr = data;  // 指向要传输的 data

       buf->size  = size;  // 传输大小

       buf->id    = id;

       buf->magic = BUF_MAGIC;

 

       local_irq_save(flags);

 

       if (chan->curr == NULL) {    // 当前 channel 没有在传输

              /* we've got nothing loaded... */

              pr_debug("%s: buffer %p queued onto empty channel\n",

                       __FUNCTION__, buf);

 

              chan->curr = buf;   // 直接挂在 curr

              chan->end  = buf;

              chan->next = NULL;

       } else {  // 当前 channel 正在传输

              pr_debug("dma%d: %s: buffer %p queued onto non-empty channel\n",

                       chan->number, __FUNCTION__, buf);

 

              if (chan->end == NULL)

                     pr_debug("dma%d: %s: %p not empty, and chan->end==NULL?\n",

                              chan->number, __FUNCTION__, chan);

 

              // buffer 挂到队列的最后面 , 并重设 end

              chan->end->next = buf;   

              chan->end = buf;

       }

 

       /* if necessary, update the next buffer field */

       if (chan->next == NULL)

              chan->next = buf;

 

       /* check to see if we can load a buffer */

       if (chan->state == S3C2410_DMA_RUNNING) {  // channel 正在运行

              if (chan->load_state == S3C2410_DMALOAD_1LOADED && 1) {  // 已有 buf load

                     if (s3c2410_dma_waitforload(chan, __LINE__) == 0) {  // 等待 load

                            printk(KERN_ERR "dma%d: loadbuffer:"

                                   "timeout loading buffer\n",

                                   chan->number);

                            dbg_showchan(chan);

                            local_irq_restore(flags);

                            return -EINVAL;

                     }

              }

 

              while (s3c2410_dma_canload(chan) && chan->next != NULL) { // 检查能否 load

                     s3c2410_dma_loadbuffer(chan, chan->next);  //load buffer

              }

       } else if (chan->state == S3C2410_DMA_IDLE) {  // channel 空闲着

              if (chan->flags & S3C2410_DMAF_AUTOSTART) { // 如果设了自动启动标记 , 则直接启动该次传输

                     s3c2410_dma_ctrl(chan->number, S3C2410_DMAOP_START); // 启动传输

              }

       }

 

       local_irq_restore(flags);

       return 0;

}

 

EXPORT_SYMBOL(s3c2410_dma_enqueue);

 

   该函数首先从先前创建的高速缓冲池中获取一个 buf, 并把要传输的 data 保存在该 buf , 然后根据当前 channel 的运行状态来选择是 load buf, 还是直接传输该 buf.

 

阅读(1305) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~