Chinaunix首页 | 论坛 | 博客
  • 博客访问: 392815
  • 博文数量: 200
  • 博客积分: 10
  • 博客等级: 民兵
  • 技术积分: 810
  • 用 户 组: 普通用户
  • 注册时间: 2013-01-07 12:12
文章分类

全部博文(200)

文章存档

2015年(2)

2013年(198)

分类:

2013-01-07 14:31:25

原文地址:s3c2410 DMA驱动源码分析2 作者:garyybl

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就是源和目的的偏移地址.可参考2410datasheet.  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

       }

 

    //resetchannel的相关信息

       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) {  //失败则resetchannel

                     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.

   Channel在运行过程中会有很多的状态, 所有状态如下:

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

/* enum s3c2410_dma_loadst

 *

 * This represents the state of the DMA engine, wrt to the loaded / running

 * transfers. Since we don't have any way of knowing exactly the state of

 * the DMA transfers, we need to know the state to make decisions on wether

 * we can

 *

 * S3C2410_DMA_NONE

 *

 * There are no buffers loaded (the channel should be inactive)

 *

 * S3C2410_DMA_1LOADED

 *

 * There is one buffer loaded, however it has not been confirmed to be

 * loaded by the DMA engine. This may be because the channel is not

 * yet running, or the DMA driver decided that it was too costly to

 * sit and wait for it to happen.

 *

 * S3C2410_DMA_1RUNNING

 *

 * The buffer has been confirmed running, and not finisged

 *

 * S3C2410_DMA_1LOADED_1RUNNING

 *

 * There is a buffer waiting to be loaded by the DMA engine, and one

 * currently running.

*/

 

enum s3c2410_dma_loadst {

       S3C2410_DMALOAD_NONE,

       S3C2410_DMALOAD_1LOADED,

       S3C2410_DMALOAD_1RUNNING,

       S3C2410_DMALOAD_1LOADED_1RUNNING,

};

各种装态注释的很明显了, 我就不再罗索了.

Channel运行时会有一个正在传输的buf, 一个已经加载的buf, 还有很多等待加载的buf.

我们来把这个函数中调用的函数也逐个分析下:

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

/* s3c2410_dma_waitforload

 *

 * wait for the DMA engine to load a buffer, and update the state accordingly

*/

 

static int

s3c2410_dma_waitforload(struct s3c2410_dma_chan *chan, int line)

{

       int timeout = chan->load_timeout; //初始化时load_timeout 被设成了 1 << 18

       int took;

 

     //该函数只在S3C2410_DMALOAD_1LOADED状态下被调用

       if (chan->load_state != S3C2410_DMALOAD_1LOADED) {

              printk(KERN_ERR "dma%d: s3c2410_dma_waitforload() called in loadstate %d from line %d/n", chan->number, chan->load_state, line);

              return 0;

       }

 

       if (chan->stats != NULL)

              chan->stats->loads++;  //更新统计信息

 

       while (--timeout > 0) {

       //获取还剩的传输量, 左移(32-20) 只是把[21:20]位移调, 因为它仅和0比较,所以无需确切的数据

              if ((dma_rdreg(chan, S3C2410_DMA_DSTAT) << (32-20)) != 0) {

                     took = chan->load_timeout - timeout;  //等待了这么长时间

 

                     //保存统计信息,该函数更新最长,最短超时时间, 并更新总超时时间

s3c2410_dma_stats_timeout(chan->stats, took); 

 

                     switch (chan->load_state) {

                     case S3C2410_DMALOAD_1LOADED:

                  //因为有数据在传输了, 因此更新channel的状态, 从这我们也能看到, 一次只能有一个

//bufload

                            chan->load_state = S3C2410_DMALOAD_1RUNNING; 

                            break;

 

                     default:

                            printk(KERN_ERR "dma%d: unknown load_state in s3c2410_dma_waitforload() %d/n", chan->number, chan->load_state);

                     }

 

                     return 1;

              }

       }

 

       if (chan->stats != NULL) {

              chan->stats->timeout_failed++;

       }

 

       return 0;

}

该函数很简单, 它等待已经loadbufstart传输, 然后更新相关统计信息, 也正因为loadbuf被开始传输了, 因此该函数完后, 应该会有一个新的bufload. 至于原先loadbuf是如何被start,我们以后在看.

接下来我们看s3c2410_dma_canload函数:

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

/* s3c2410_dma_canload

 *

 * work out if we can queue another buffer into the DMA engine

*/

static int

s3c2410_dma_canload(struct s3c2410_dma_chan *chan)

{ 

   //在这2个状态下是可以load

       if (chan->load_state == S3C2410_DMALOAD_NONE ||

           chan->load_state == S3C2410_DMALOAD_1RUNNING)

              return 1;

 

       return 0;

}

跑完s3c2410_dma_waitforload后如果正确, 则状态应该是S3C2410_DMALOAD_1RUNNING, 所以这里就是可以加载了,  那当然要看加载函数了

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

/* s3c2410_dma_loadbuffer

 *

 * load a buffer, and update the channel state

*/

 

static inline int

s3c2410_dma_loadbuffer(struct s3c2410_dma_chan *chan,

                     struct s3c2410_dma_buf *buf)

{

       unsigned long reload;

 

       pr_debug("s3c2410_chan_loadbuffer: loading buff %p (0x%08lx,0x%06x)/n",

               buf, (unsigned long)buf->data, buf->size);

 

       if (buf == NULL) {

              dmawarn("buffer is NULL/n");

              return -EINVAL;

       }

 

       /* check the state of the channel before we do anything */

    //状态错误, 只能有1loadedbuf

       if (chan->load_state == S3C2410_DMALOAD_1LOADED) { 

              dmawarn("load_state is S3C2410_DMALOAD_1LOADED/n");

       }

         

 //状态错误, 只能有1loadedbuf

       if (chan->load_state == S3C2410_DMALOAD_1LOADED_1RUNNING) {

              dmawarn("state is S3C2410_DMALOAD_1LOADED_1RUNNING/n");

       }

 

       /* it would seem sensible if we are the last buffer to not bother

        * with the auto-reload bit, so that the DMA engine will not try

        * and load another transfer after this one has finished...

        */

    //判断是否要自动加载后续的buf, 如果有后续的buf则自动加载

       if (chan->load_state == S3C2410_DMALOAD_NONE) {

              pr_debug("load_state is none, checking for noreload (next=%p)/n",

                      buf->next);

              reload = (buf->next == NULL) ? S3C2410_DCON_NORELOAD : 0;

       } else {

              //pr_debug("load_state is %d => autoreload/n", chan->load_state);

              reload = S3C2410_DCON_AUTORELOAD;

       }

 

       if ((buf->data & 0xf0000000) != 0x30000000) {

              dmawarn("dmaload: buffer is %p/n", (void *)buf->data);

       }

 

       writel(buf->data, chan->addr_reg);  //写地址寄存器

   

    //不解释了, 看寄存器说明吧

       dma_wrreg(chan, S3C2410_DMA_DCON,

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

 

       chan->next = buf->next;  //更新链表

 

       /* update the state of the channel */

  

    //更新状态

       switch (chan->load_state) {

       case S3C2410_DMALOAD_NONE:

              chan->load_state = S3C2410_DMALOAD_1LOADED;

              break;

 

       case S3C2410_DMALOAD_1RUNNING:

              chan->load_state = S3C2410_DMALOAD_1LOADED_1RUNNING;

              break;

 

       default:

              dmawarn("dmaload: unknown state %d in loadbuffer/n",

                     chan->load_state);

              break;

       }

 

       return 0;

}

该函数主要是把要传输的数据的地址先存入寄存器中, 等当前的传输完成后会根据时候auto reload的情况来确定是否开始这次的传输.

OK, 到目前为止讲完了所有的export的函数, 现在还剩下dma的操作函数和中断函数没讲了, let’s go!

我们先看中断函数, 该函数在一次传输完成后被调用

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

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;  //当前传输完毕的buf

 

       dbg_showchan(chan);

 

       /* modify the channel state */

  //修改当前状态

       switch (chan->load_state) {

       case S3C2410_DMALOAD_1RUNNING:

              /* TODO - if we are running only one buffer, we probably

               * want to reload here, and then worry about the buffer

               * callback */

 

              chan->load_state = S3C2410_DMALOAD_NONE;

              break;

 

       case S3C2410_DMALOAD_1LOADED:

              /* iirc, we should go back to NONE loaded here, we

               * had a buffer, and it was never verified as being

               * loaded.

               */

 

              chan->load_state = S3C2410_DMALOAD_NONE;

              break;

 

       case S3C2410_DMALOAD_1LOADED_1RUNNING:

              /* we'll worry about checking to see if another buffer is

               * ready after we've called back the owner. This should

               * ensure we do not wait around too long for the DMA

               * engine to start the next transfer

               */

 

              chan->load_state = S3C2410_DMALOAD_1LOADED;

              break;

 

       case S3C2410_DMALOAD_NONE:

              printk(KERN_ERR "dma%d: IRQ with no loaded buffer?/n",

                     chan->number);

              break;

 

       default:

              printk(KERN_ERR "dma%d: IRQ in invalid load_state %d/n",

                     chan->number, chan->load_state);

              break;

       }

 

       if (buf != NULL) {

              /* update the chain to make sure that if we load any more

               * buffers when we call the callback function, things should

               * work properly */

 

              chan->curr = buf->next;   //更新传输的buf

              buf->next  = NULL;

 

              if (buf->magic != BUF_MAGIC) {

                     printk(KERN_ERR "dma%d: %s: buf %p incorrect magic/n",

                            chan->number, __FUNCTION__, buf);

                     return IRQ_HANDLED;

              }

 

              s3c2410_dma_buffdone(chan, buf, S3C2410_RES_OK);  //buf传输完成后的操作

 

              /* free resouces */

              s3c2410_dma_freebuf(buf);  //释放buf, 我们看到传输前有申请buf

       } else {

       }

 

       /* only reload if the channel is still running... our buffer done

        * routine may have altered the state by requesting the dma channel

        * to stop or shutdown... */

 

       /* todo: check that when the channel is shut-down from inside this

        * function, we cope with unsetting reload, etc */

   //还有要传输的buf,则继续传输

       if (chan->next != NULL && chan->state != S3C2410_DMA_IDLE) {

              unsigned long flags;

 

              switch (chan->load_state) {

              case S3C2410_DMALOAD_1RUNNING:

                     /* don't need to do anything for this state */

                     break;

 

              case S3C2410_DMALOAD_NONE:

                     /* can load buffer immediately */

                     break;

 

              case S3C2410_DMALOAD_1LOADED:

                     if (s3c2410_dma_waitforload(chan, __LINE__) == 0) {  //等待被传输

                            /* flag error? */

                            printk(KERN_ERR "dma%d: timeout waiting for load (%s)/n",

                                   chan->number, __FUNCTION__);

                            return IRQ_HANDLED;

                     }

 

                     break;

 

              case S3C2410_DMALOAD_1LOADED_1RUNNING:

                     goto no_load;

 

              default:

                     printk(KERN_ERR "dma%d: unknown load_state in irq, %d/n",

                            chan->number, chan->load_state);

                     return IRQ_HANDLED;

              }

 

              local_irq_save(flags);

              s3c2410_dma_loadbuffer(chan, chan->next);   //加载下一个buf

              local_irq_restore(flags);

       } else {  //所有的传输完成

              s3c2410_dma_lastxfer(chan);   //完成处理工作

 

              /* see if we can stop this channel.. */

              if (chan->load_state == S3C2410_DMALOAD_NONE) {

                     pr_debug("dma%d: end of transfer, stopping channel (%ld)/n",

                             chan->number, jiffies);

                     s3c2410_dma_ctrl(chan->number | DMACH_LOW_LEVEL,

                                    S3C2410_DMAOP_STOP);  //停止dma传输

              }

       }

 

 no_load:

       return IRQ_HANDLED;

}

我们看到当传输队列中还有buf要传输时, 没有看到start的操作, 这是为什么呢? 因为在load的时候我们分析过, 如果后续还有buf要传输, 则自动加载运行,  所以这里没有必要手工start.

s3c2410_dma_buffdone()函数仅仅是调用前面注册的回调函数, 这里不列出来了.

s3c2410_dma_freebuf()也很简单, 就是把buf归还到缓冲池去.

我们看下s3c2410_dma_lastxfer

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

/* s3c2410_dma_lastxfer

 *

 * called when the system is out of buffers, to ensure that the channel

 * is prepared for shutdown.

*/

 

static inline void

s3c2410_dma_lastxfer(struct s3c2410_dma_chan *chan)

{

#if 0

       pr_debug("dma%d: s3c2410_dma_lastxfer: load_state %d/n",

               chan->number, chan->load_state);

#endif

 

       switch (chan->load_state) {

       case S3C2410_DMALOAD_NONE:

              break;

 

       case S3C2410_DMALOAD_1LOADED:

              if (s3c2410_dma_waitforload(chan, __LINE__) == 0) {  //等待加载的buf被执行

                            /* flag error? */

                     printk(KERN_ERR "dma%d: timeout waiting for load (%s)/n",

                            chan->number, __FUNCTION__);

                     return;

              }

              break;

 

       case S3C2410_DMALOAD_1LOADED_1RUNNING:

              /* I belive in this case we do not have anything to do

               * until the next buffer comes along, and we turn off the

               * reload */

              return;

 

       default:

              pr_debug("dma%d: lastxfer: unhandled load_state %d with no next/n",

                      chan->number, chan->load_state);

              return;

 

       }

 

       /* hopefully this'll shut the damned thing up after the transfer... */

    //清楚自动加载标记, 因为无后续要传输的buf, 所以要清这个标记

       dma_wrreg(chan, S3C2410_DMA_DCON, chan->dcon | S3C2410_DCON_NORELOAD);

}

很简单的一个函数, 这里不多说了.

好了, 到这个该着中分析操作函数了.

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

/* s3c2410_dma_start

 *

 * start a dma channel going

*/

 

static int s3c2410_dma_start(struct s3c2410_dma_chan *chan)

{

       unsigned long tmp;

       unsigned long flags;

 

       pr_debug("s3c2410_start_dma: channel=%d/n", chan->number);

 

       local_irq_save(flags);

 

       if (chan->state == S3C2410_DMA_RUNNING) {  //已经有run的了

              pr_debug("s3c2410_start_dma: already running (%d)/n", chan->state);

              local_irq_restore(flags);

              return 0;

       }

 

       chan->state = S3C2410_DMA_RUNNING;  //更新状态

 

       /* check wether there is anything to load, and if not, see

        * if we can find anything to load

        */

 

       if (chan->load_state == S3C2410_DMALOAD_NONE) { //没有加载过buf

              if (chan->next == NULL) {  //没有buf要传送的

                     printk(KERN_ERR "dma%d: channel has nothing loaded/n",

                            chan->number);

                     chan->state = S3C2410_DMA_IDLE;

                     local_irq_restore(flags);

                     return -EINVAL;

              }

 

              s3c2410_dma_loadbuffer(chan, chan->next);  //加载buf, 加载状态也会相应更新

       }

 

       dbg_showchan(chan);

 

       /* enable the channel */

 

       if (!chan->irq_enabled) {

              enable_irq(chan->irq);  //使能中断

              chan->irq_enabled = 1;

       }

 

       /* start the channel going */

    //启动DMA传输,

       tmp = dma_rdreg(chan, S3C2410_DMA_DMASKTRIG);

       tmp &= ~S3C2410_DMASKTRIG_STOP;

       tmp |= S3C2410_DMASKTRIG_ON;

       dma_wrreg(chan, S3C2410_DMA_DMASKTRIG, tmp);

 

       pr_debug("dma%d: %08lx to DMASKTRIG/n", chan->number, tmp);

 

#if 0

       /* the dma buffer loads should take care of clearing the AUTO

        * reloading feature */

       tmp = dma_rdreg(chan, S3C2410_DMA_DCON);

       tmp &= ~S3C2410_DCON_NORELOAD;

       dma_wrreg(chan, S3C2410_DMA_DCON, tmp);

#endif

 

       s3c2410_dma_call_op(chan, S3C2410_DMAOP_START);  //调用注册的op回调函数

 

       dbg_showchan(chan);

 

       /* if we've only loaded one buffer onto the channel, then chec

        * to see if we have another, and if so, try and load it so when

        * the first buffer is finished, the new one will be loaded onto

        * the channel */

    //由于当前load的已经在运行了, 因此如果还有要传输的bufload进来

       if (chan->next != NULL) {

              if (chan->load_state == S3C2410_DMALOAD_1LOADED) {

            //等待该buf被运行, 别忘了我们设了自动加载运行.

                     if (s3c2410_dma_waitforload(chan, __LINE__) == 0) {

                            pr_debug("%s: buff not yet loaded, no more todo/n",

                                    __FUNCTION__);

                     } else {

                            chan->load_state = S3C2410_DMALOAD_1RUNNING;

                            s3c2410_dma_loadbuffer(chan, chan->next);  //加载buf

                     }

 

              } else if (chan->load_state == S3C2410_DMALOAD_1RUNNING) {

                     s3c2410_dma_loadbuffer(chan, chan->next); //加载buf

              }

       }

 

 

       local_irq_restore(flags);

 

       return 0;

}

整个启动流程就这样完了.

 

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

static int s3c2410_dma_dostop(struct s3c2410_dma_chan *chan)

{

       unsigned long flags;

       unsigned long tmp;

 

       pr_debug("%s:/n", __FUNCTION__);

 

       dbg_showchan(chan);

 

       local_irq_save(flags);

 

       s3c2410_dma_call_op(chan,  S3C2410_DMAOP_STOP);  //通知用户该操作

   

    //停止DMA传输

       tmp = dma_rdreg(chan, S3C2410_DMA_DMASKTRIG);

       tmp |= S3C2410_DMASKTRIG_STOP;

       //tmp &= ~S3C2410_DMASKTRIG_ON;

       dma_wrreg(chan, S3C2410_DMA_DMASKTRIG, tmp);

 

#if 0

       /* should also clear interrupts, according to WinCE BSP */

       tmp = dma_rdreg(chan, S3C2410_DMA_DCON);

       tmp |= S3C2410_DCON_NORELOAD;

       dma_wrreg(chan, S3C2410_DMA_DCON, tmp);

#endif

   

    //更新状态

       /* should stop do this, or should we wait for flush? */

       chan->state      = S3C2410_DMA_IDLE;

       chan->load_state = S3C2410_DMALOAD_NONE;

 

       local_irq_restore(flags);

 

       return 0;

}

该函数比较简单, 接着看

 

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

/* s3c2410_dma_flush

 *

 * stop the channel, and remove all current and pending transfers

*/

 

static int s3c2410_dma_flush(struct s3c2410_dma_chan *chan)

{

       struct s3c2410_dma_buf *buf, *next;

       unsigned long flags;

 

       pr_debug("%s: chan %p (%d)/n", __FUNCTION__, chan, chan->number);

 

       dbg_showchan(chan);

 

       local_irq_save(flags);

 

       if (chan->state != S3C2410_DMA_IDLE) {

              pr_debug("%s: stopping channel.../n", __FUNCTION__ );

              s3c2410_dma_ctrl(chan->number, S3C2410_DMAOP_STOP);  //停调传输

       }

 

       buf = chan->curr;

       if (buf == NULL)

              buf = chan->next;

 

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

 

       if (buf != NULL) {

              for ( ; buf != NULL; buf = next) {

                     next = buf->next;

 

                     pr_debug("%s: free buffer %p, next %p/n",

                            __FUNCTION__, buf, buf->next);

 

                     s3c2410_dma_buffdone(chan, buf, S3C2410_RES_ABORT);  //通知用户中断了传输

                     s3c2410_dma_freebuf(buf);  //释放buf

              }

       }

 

       dbg_showregs(chan);

 

       s3c2410_dma_waitforstop(chan);  

 

#if 0

       /* should also clear interrupts, according to WinCE BSP */

       {

              unsigned long tmp;

 

              tmp = dma_rdreg(chan, S3C2410_DMA_DCON);

              tmp |= S3C2410_DCON_NORELOAD;

              dma_wrreg(chan, S3C2410_DMA_DCON, tmp);

       }

#endif

 

       dbg_showregs(chan);

 

       local_irq_restore(flags);

 

       return 0;

}

  该函数的作用就是中断所有的传输, 并把所有队列中等待传输的buf都清掉.

 

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

static int s3c2410_dma_started(struct s3c2410_dma_chan *chan)

{

       unsigned long flags;

 

       local_irq_save(flags);

 

       dbg_showchan(chan);

 

       /* if we've only loaded one buffer onto the channel, then chec

        * to see if we have another, and if so, try and load it so when

        * the first buffer is finished, the new one will be loaded onto

        * the channel */

    //看注释吧, 不解释了

       if (chan->next != NULL) {

              if (chan->load_state == S3C2410_DMALOAD_1LOADED) {

 

                     if (s3c2410_dma_waitforload(chan, __LINE__) == 0) {

                            pr_debug("%s: buff not yet loaded, no more todo/n",

                                    __FUNCTION__);

                     } else {

                            chan->load_state = S3C2410_DMALOAD_1RUNNING;

                            s3c2410_dma_loadbuffer(chan, chan->next);

                     }

 

              } else if (chan->load_state == S3C2410_DMALOAD_1RUNNING) {

                     s3c2410_dma_loadbuffer(chan, chan->next);

              }

       }

 

 

       local_irq_restore(flags);

 

       return 0;

 

}

最后的这个函数就由大家自己分析吧.

s3c2410_dma_config函数可以看出, 该驱动只支持硬件请求模式, 而从s3c2410_dma_devconfig函数可以看出, 该驱动只支持设备和memory之间的DMA传输.

至于如何使用的问题, 可以去代码里搜一下哪些地方调用了export出来的函数就懂了, 2410的板子上PCM会用到DMA传输,  使用流程为:

    s3c2410_dma_request ->

s3c2410_dma_devconfig ->

s3c2410_dma_config ->            

s3c2410_dma_ctrl(prtd->params->channel, S3C2410_DMAOP_START);

当然一般还会注册回调函数的.

到此为止整个DMA的操作流程都分析完了, 希望对你有所帮助, 

以后会写些cache, mmu, write buffer等方面的驱动分析.

http://blog.csdn.net/aaronychen/article/details/4056167

阅读(792) | 评论(0) | 转发(0) |
0

上一篇:基于ARM的电源管理

下一篇:S3C2410:DMA介紹

给主人留下些什么吧!~~