Chinaunix首页 | 论坛 | 博客
  • 博客访问: 317569
  • 博文数量: 146
  • 博客积分: 198
  • 博客等级: 入伍新兵
  • 技术积分: 689
  • 用 户 组: 普通用户
  • 注册时间: 2010-08-24 08:35
文章分类

全部博文(146)

文章存档

2013年(46)

2012年(98)

2011年(1)

2010年(1)

我的朋友

分类: LINUX

2013-02-22 11:54:36

详解ARMAMBA设备中的DMA设备PL08XLinux驱动-2

Version: 20100423

Author: green-waste(at)163.com

 相关链接:

详解ARM的AMBA设备中的DMA设备PL08X的Linux驱动-1

http://blog.163.com/againinput4@yeah/blog/static/1227642712010323103932603/

详解ARM的AMBA设备中的DMA设备PL08X的Linux驱动-2

http://blog.163.com/againinput4@yeah/blog/static/122764271201032310434871/

详解ARM的AMBA设备中的DMA设备PL08X的Linux驱动-3

http://blog.163.com/againinput4@yeah/blog/static/1227642712010323104557214/

 

/* 根据传入的是源自增SI还是DI目的地址自增,决定谁是master bus */

/*

 * - prefers destination bus if both available

 * - if fixed address on one bus chooses other

 */

void pl08x_choose_master_bus(struct pl08x_bus_data *src_bus,

       struct pl08x_bus_data *dst_bus, struct pl08x_bus_data **mbus,

       struct pl08x_bus_data **sbus, union _cctl *cctl_parm)

{

       if (!cctl_parm->bits.di) {

/* 如果是 DI,不是目标地址自增,那么就用src作为master bus

以实际常用的情况举例,比如要从某个buffer传输数据到nand flashDATA寄存器中,

那么就是DI0,而作为主方向的buffer,就是master bus,从masterbuffer传输到slave nand flashDATA寄存器。*/

              *mbus = src_bus;

              *sbus = dst_bus;

       } else if (!cctl_parm->bits.si) {

              *mbus = dst_bus;

              *sbus = src_bus;

       } else {

/* TODO: ??? */

              if (dst_bus->buswidth == 4) {

                     *mbus = dst_bus;

                     *sbus = src_bus;

              } else if (src_bus->buswidth == 4) {

                     *mbus = src_bus;

                     *sbus = dst_bus;

              } else if (dst_bus->buswidth == 2) {

                     *mbus = dst_bus;

                     *sbus = src_bus;

              } else if (src_bus->buswidth == 2) {

                     *mbus = src_bus;

                     *sbus = dst_bus;

              } else {

                     /* src_bus->buswidth == 1 */

                     *mbus = dst_bus;

                     *sbus = src_bus;

              }

       }

}

 

int pl08x_fill_lli_for_desc(struct pl08x_txd *local_txd, int num_llis, int len,

                            union _cctl *cctl, int *remainder)

{

       struct _lli *llis_va = (struct _lli *)(local_txd->llis_va);

       struct _lli *llis_bus = (struct _lli *)(local_txd->llis_bus);

/* 填充对应的contrlsource, destination 三个寄存器对应的值 */

       llis_va[num_llis].cctl.val       = cctl->val;

       llis_va[num_llis].src             = local_txd->srcbus.addr;

       llis_va[num_llis].dst             = local_txd->dstbus.addr;

       /*

        * The bus bit is added to the next lli's address

        */

/* 下面的llis_bus[num_llis + 1]比较好理解,就是当前的LLInext的值,应该赋值为下一个LLI 的地址,需要特殊说明的是bus_bit_lli,此变量,意思为你当前使用DMA的哪个Master。因为pl080内部有两个Master主控器,你在使用DMA的时候,要制定你当前是使用哪一个DMA,详情参考datasheet

其中32位的LLI的地址,由于是4对齐的,所以bit0bit1肯定是0,正好利用第0位指示是master1 还是master2

此变量不是此处pl08x DMA驱动赋值的,而是在你最开始去注册amba设备的时候赋值的。 */

       llis_va[num_llis].next =

              (dma_addr_t)((unsigned int)&(llis_bus[num_llis + 1])

                                   + pd.pd->bus_bit_lli);

       if (cctl->bits.si)

/* 如果是SI,那么源地址在一次LLIDMA传输后,源地址就应该更新,为再加上对应的每次传输的长度 */

              local_txd->srcbus.addr += len;

       if (cctl->bits.di)

              local_txd->dstbus.addr += len;

 

       *remainder -= len;

 

       return num_llis + 1;

}

 

/*

 * Return number of bytes to fill to boundary, or len

 */

static int pl08x_pre_boundary(int addr, int len)

{

       int boundary;

 

       if (len <= 0)

              dev_err(&pd.dmac->dev, "%s - zero length\n", __func__);

/*

Pl08x.h中定义的:

#define PL08X_BOUNDARY_SHIFT         (10) /* 1KB 0x400 */

#define PL08X_BOUNDARY_SIZE           (1 << PL08X_BOUNDARY_SHIFT)

此函数,目的是为了限制每次DMA传输的字节数,要保证在PL08X_BOUNDARY_SIZE1KB范围内。之所以做此限制,是因为datasheet中写了:

Bursts do not cross the 1KB address boundary

突发传输,不能跨界超过1KB 的范围。

所以,如果你上层程序调用此pl08x驱动,希望每次传输2KB,那么此驱动会自动帮你限制为每次最多1KB,然后自动帮你拆分出对应的多个LLI

 */

       boundary = ((addr >> PL08X_BOUNDARY_SHIFT) + 1) << PL08X_BOUNDARY_SHIFT;

 

       if (boundary < addr + len)

              return boundary - addr;

       else

              return len;

}

 

/*

 * Note that we assume we never have to change the burst sizes

 * Return 0 for error

 */

int fill_LLIS_for_desc(struct pl08x_txd *local_txd, int pl08x_chan_num)

{

       struct pl08x_clientdev_data *client = local_txd->pcd;

       struct pl08x_bus_data *mbus, *sbus;

       int remainder;

       int num_llis = 0;

       union _cctl cctl_parm;

       int max_bytes_per_lli;

       int total_bytes = 0;

       struct _lli *llis_va;

       struct _lli *llis_bus;

 

       if (!local_txd) {

              dev_err(&pd.dmac->dev, "%s - no descriptor\n", __func__);

              return 0;

       }

 

       /*

        * Get some LLIs

        *  This alloc can wait if the pool is used up so we need to cleanup

        */

       local_txd->llis_va = dma_pool_alloc(pd.pool, GFP_KERNEL,

                                   &local_txd->llis_bus);

       if (!local_txd->llis_va) {

              dev_err(&pd.dmac->dev, "%s - no llis\n", __func__);

              return 0;

       }

 

       pd.pool_ctr++;

 

       /*

        * Initialize bus values for this transfer

        * from the passed optimal values

        */

       if (!client) {

              dev_err(&pd.dmac->dev, "%s - no client\n", __func__);

              return 0;

       }

 

       cctl_parm.val = client->cctl_opt;

/* 从传入的配置中,解码出bus宽度,单位字节 */

       local_txd->srcbus.maxwidth =

              pl08x_decode_widthbits(cctl_parm.bits.swidth);

 

       if (local_txd->srcbus.maxwidth == PL08X_CODING_ERR) {

              dev_err(&pd.dmac->dev,

                     "%s - local_txd->srcbus.maxwidth codeing error cctl_parm.bits.swidth %d\n",

                            __func__, cctl_parm.bits.swidth);

              return 0;

       }

 

       local_txd->srcbus.buswidth = local_txd->srcbus.maxwidth;

       local_txd->dstbus.maxwidth =

              pl08x_decode_widthbits(cctl_parm.bits.dwidth);

 

       if (local_txd->dstbus.maxwidth == PL08X_CODING_ERR) {

              dev_err(&pd.dmac->dev,

                     "%s - local_txd->dstbus.maxwidth coding error - cctl_parm.bits.dwidth %d\n",

                            __func__, cctl_parm.bits.dwidth);

              return 0;

       }

 

       local_txd->dstbus.buswidth = local_txd->dstbus.maxwidth;

 

/* 从源总线和目的总线选出一个最大带宽,然后乘与一个传输的个数,得到单个LLI的最大允许的字节数 */

       /*

        *  Note bytes transferred == tsize * MIN(buswidths), not max(buswidths)

        */

       max_bytes_per_lli = min(local_txd->srcbus.maxwidth,

                                   local_txd->dstbus.maxwidth) *

                                          cctl_parm.bits.tsize;

 

       remainder = local_txd->len;

       /*

        * Choose bus to align to

        * - prefers destination bus if both available

        * - if fixed address on one bus chooses other

        */

       pl08x_choose_master_bus(&local_txd->srcbus,

              &local_txd->dstbus, &mbus, &sbus, &cctl_parm);

 

       if (local_txd->len < mbus->buswidth) {

/* 要传输的数据,比带宽还小,那简单地分成几个LLI就搞定了 */

              /*

               * Less than a bus width available

               * - send as single bytes

               */

              while (remainder) {

                     cctl_parm.bits.swidth = pl08x_encode_width(1);

                     cctl_parm.bits.dwidth = pl08x_encode_width(1);

                     cctl_parm.bits.tsize = 1;

                     num_llis =

                            pl08x_fill_lli_for_desc(local_txd, num_llis, 1,

                                   &cctl_parm, &remainder);

                     total_bytes++;

              }

       } else {

              /*

               *  Make one byte LLIs until master bus is aligned

               *  - slave will then be aligned also

               */

              while ((mbus->addr) % (mbus->buswidth)) {

                     cctl_parm.bits.swidth = pl08x_encode_width(1);

                     cctl_parm.bits.dwidth = pl08x_encode_width(1);

                     cctl_parm.bits.tsize = 1;

                     num_llis = pl08x_fill_lli_for_desc

                            (local_txd, num_llis, 1, &cctl_parm,

                                   &remainder);

                     total_bytes++;

              }

              /*

               *  Master now aligned

               * - if slave is not then we must set its width down

               */

              if (sbus->addr % sbus->buswidth)

                     sbus->buswidth = 1;

 

              /*

               * Make largest possible LLIs until less than one bus width left

               */

              while (remainder > (mbus->buswidth - 1)) {

                     int lli_len, target_len;

                     int tsize;

                     int odd_bytes;

                     /*

                      * If enough left try to send max possible,

                      * otherwise try to send the remainder

                      */

                     target_len = remainder;

                     if (remainder > max_bytes_per_lli)

                            target_len = max_bytes_per_lli;

                     /*

                      *  Set bus lengths for incrementing busses

                      *  to number of bytes which fill

                      *  to next memory boundary

                      */

                     if (cctl_parm.bits.si)

/* 检查数据有没有超过允许的范围,如果超过了,

就用PL08X_BOUNDARY_SIZE=0x400=1KB */

                            local_txd->srcbus.fill_bytes =

                                   pl08x_pre_boundary(

                                          local_txd->srcbus.addr,

                                          remainder);

                     else

                            local_txd->srcbus.fill_bytes =

                                   max_bytes_per_lli;

                     if (cctl_parm.bits.di)

                            local_txd->dstbus.fill_bytes =

                                   pl08x_pre_boundary(

                                          local_txd->dstbus.addr,

                                          remainder);

                     else

                            local_txd->dstbus.fill_bytes =

                                          max_bytes_per_lli;

                     /*

                      *  Find the nearest

                      */

                     lli_len     = min(local_txd->srcbus.fill_bytes,

                                          local_txd->dstbus.fill_bytes);

 

                     if (lli_len <= 0) {

                            dev_err(&pd.dmac->dev,

                                   "%s - lli_len is %d, <= 0\n",

                                          __func__, lli_len);

                            return 0;

                     }

 

                     if (lli_len == target_len) {

                            /*

                             * Can send what we wanted

                             */

                            /*

                             *  Maintain alignment

                             */

                            lli_len     = (lli_len/mbus->buswidth) *

                                                 mbus->buswidth;

                            odd_bytes = 0;

                     } else {

                            /*

                             * So now we know how many bytes to transfer

                             * to get to the nearest boundary

                             * The next lli will past the boundary

                             * - however we may be working to a boundary

                             *   on the slave bus

                             *   We need to ensure the master stays aligned

                             */

                            odd_bytes = lli_len % mbus->buswidth;

                            /*

                             * - and that we are working in multiples

                             *   of the bus widths

                             */

                            lli_len -= odd_bytes;

 

                     }

                     if (lli_len) {

                            /*

                             * Check against minimum bus alignment

                             */

                            target_len = lli_len;

                            tsize = lli_len/min(mbus->buswidth,

                                                 sbus->buswidth);

                            lli_len     = tsize * min(mbus->buswidth,

                                                 sbus->buswidth);

 

                            if (target_len != lli_len) {

                                   dev_err(&pd.dmac->dev,

                                   "%s - can't send what we want. Desired %d, sent %d in transfer of %d\n",

                                   __func__, target_len, lli_len, local_txd->len);

                                   return 0;

                            }

 

                            cctl_parm.bits.swidth = pl08x_encode_width

                                   (local_txd->srcbus.buswidth);

                            cctl_parm.bits.dwidth = pl08x_encode_width

                                   (local_txd->dstbus.buswidth);

                            if ((cctl_parm.bits.swidth == PL08X_CODING_ERR) ||

                                   (cctl_parm.bits.dwidth == PL08X_CODING_ERR)) {

                                   dev_err(&pd.dmac->dev,

                                   "%s - cctl_parm.bits.swidth or dwidth coding error - local_txd->dstbus.buswidth %d, local_txd->srcbus.buswidth %d\n",

                                   __func__,

                                   local_txd->dstbus.buswidth,

                                   local_txd->srcbus.buswidth

                                   );

                                   return 0;

                            }

                            cctl_parm.bits.tsize = tsize;

                            num_llis = pl08x_fill_lli_for_desc(local_txd,

                                          num_llis, lli_len, &cctl_parm,

                                          &remainder);

                            total_bytes += lli_len;

                     }

                     if (odd_bytes) {

                            /*

                             * Creep past the boundary,

                             * maintaining master alignment

                             */

                            int j;

                            for (j = 0; (j < mbus->buswidth)

                                          && (remainder); j++) {

                                   cctl_parm.bits.swidth =

                                          pl08x_encode_width(1);

                                   cctl_parm.bits.dwidth =

                                          pl08x_encode_width(1);

 

                                   cctl_parm.bits.tsize = 1;

                                   num_llis =

                                          pl08x_fill_lli_for_desc(

                                                 local_txd, num_llis, 1,

                                                 &cctl_parm, &remainder);

                                   total_bytes++;

                            }

                     }

              }

 

              /*

               * Send any odd bytes

               */

              if (remainder < 0) {

                     dev_err(&pd.dmac->dev, "%s - -ve remainder 0x%08x\n",

                                   __func__, remainder);

                     return 0;

              }

 

              while (remainder) {

                     cctl_parm.bits.swidth = pl08x_encode_width(1);

                     cctl_parm.bits.dwidth = pl08x_encode_width(1);

                     cctl_parm.bits.tsize = 1;

                     num_llis = pl08x_fill_lli_for_desc(local_txd, num_llis,

                                   1, &cctl_parm, &remainder);

                     total_bytes++;

              }

       }

       if (total_bytes != local_txd->len) {

              dev_err(&pd.dmac->dev,

                     "%s - only transferred 0x%08x from size 0x%08x\n",

                            __func__, total_bytes, local_txd->len);

              return 0;

       }

/* 如果你一次要求传输数据太多,然后拆分成了太多个LLI,那么这里会告诉你超过限制了。 */

       if (num_llis >= MAX_NUM_TSFR_LLIS) {

              dev_err(&pd.dmac->dev,

                     "%s - need to increase MAX_NUM_TSFR_LLIS from 0x%08x\n",

                            __func__, MAX_NUM_TSFR_LLIS);

              return 0;

       }

       /*

        * Decide whether this is a loop or a terminated transfer

        */

       llis_va = ((struct _lli *)local_txd->llis_va);

       llis_bus = ((struct _lli *)local_txd->llis_bus);

/* 如果是循环缓存circular buffer,那么就告诉DMA传完最后一个LLI的时候,继续从最这个LLI的链表的最开始一个传,这样就周而复始地传输了,一般适用于音频流数据 */

       if (client->circular_buffer) {

              llis_va[num_llis - 1].next =

                     (dma_addr_t)((unsigned int)&(llis_bus[0]) +

                                          pd.pd->bus_bit_lli);

       } else {

              /*

               * Final LLI terminates

               */

/* 最后一个LLInext LLI指针的值,一定要设置为NULL,表示DMA传输完这个LLI之后,就结束了。 */

              llis_va[num_llis - 1].next = 0;

              /*

               * Final LLI interrupts

               */

/* 最后DMA传完所有的数据了,肯定要发生中断,然后此出pl08xirq函数会被调用,然后会再接着调用你的驱动做DMA请求时候挂载的callback函数 */

              llis_va[num_llis - 1].cctl.bits.intr = PL08X_CCTL_INTR_YES;

       }

 

       /* Now store the channel register values */

       local_txd->csrc = llis_va[0].src;

       local_txd->cdst = llis_va[0].dst;

       if (num_llis > 1)

              local_txd->clli = llis_va[0].next;

       else

              local_txd->clli = 0;

 

       local_txd->cctl = llis_va[0].cctl.val;

       local_txd->ccfg = client->config_base;

 

       /*

        * TODO: Change to use /proc data

        */

       if (pd.max_num_llis < num_llis)

              pd.max_num_llis = num_llis;

 

       return num_llis;

}

 

/*

 * Set the initial DMA register values i.e. those for the first LLI

 * The next lli pointer and the configuration interrupt bit have

 * been set when the LLIs were constructed

 */

void pl08x_set_cregs(struct pl08x_txd *entry, int pl08x_chan_num)

{

       unsigned int reg;

       unsigned int chan_base = (unsigned int)pd.base

                                   + PL08X_OS_CHAN_BASE;

/* 找到当前使用的channel的基地址 */

       chan_base += pl08x_chan_num * PL08X_OS_CHAN;

/* 如果之前正有人用此channelDMA,就等待直到不在用,即inactive为止

注意,此处没有直接去Halt或者disable对应的channel,因为有可能之前的某个DMA正在传输过程中,所以应该等待其完成,当然,大多数情况都是已经是inactive */

       /* Wait for channel inactive */

       reg = readl(chan_base + PL08X_OS_CCFG);

       while (reg & PL08X_MASK_ACTIVE)

              reg = readl(chan_base + PL08X_OS_CCFG);

 

       writel(entry->csrc, chan_base + PL08X_OS_CSRC);

       writel(entry->cdst, chan_base + PL08X_OS_CDST);

       writel(entry->clli, chan_base + PL08X_OS_CLLI);

       writel(entry->cctl, chan_base + PL08X_OS_CCTL);

       writel(entry->ccfg, chan_base + PL08X_OS_CCFG);

       mb();

}

 

/* 释放DMA内存 */

/*

 *  Might take too long to do all at once

 *  if so - free some then hand it off

 */

void pl08x_memrelease(void)

{

       struct pl08x_txd *local_txd, *next;

       unsigned long flags;

       int chan;

 

       for (chan = 0; chan < MAX_DMA_CHANNELS; chan++) {

 

              list_for_each_entry_safe(local_txd, next, &pd.chanwrap[chan]->release_list, txd_list)

              {

                     spin_lock_irqsave(&pd.lock, flags);

                     if (!local_txd)

                            dev_err(&pd.dmac->dev,

                                   "%s - no descriptor\n",  __func__);

 

                     if (local_txd->tx.callback) {

                            ((struct pl08x_callback_param *)

                                   local_txd->tx.callback_param)->act =

                                          PL08X_RELEASE;

                            local_txd->tx.callback(

                                   local_txd->tx.callback_param);

                     }

 

                     list_del(&local_txd->txd_list);

                     dma_pool_free(pd.pool, local_txd->llis_va,

                            local_txd->llis_bus);

 

                     /*

                      * p/m data not mapped - uses coherent or io mem

                      */

                     if (!local_txd->slave) {

                            dma_unmap_single(dmac.dev,

                                   local_txd->srcbus.addr, local_txd->len,

                                          local_txd->srcdir);

 

                            dma_unmap_single(dmac.dev,

                                   local_txd->dstbus.addr, local_txd->len,

                                          local_txd->dstdir);

                     }

                     kfree(local_txd);

                     spin_unlock_irqrestore(&pd.lock, flags);

              }

       }

       pd.pool_ctr = 0;

}

 

/*

 * Overall DMAC remains enabled always.

 *

 * Disabling individual channels could lose data.

 *

 * Disable the peripheral DMA after disabling the DMAC

 * in order to allow the DMAC FIFO to drain, and

 * hence allow the channel to show inactive

 *

 */

void pl08x_disable_dmac_chan(unsigned int ci)

{

       unsigned int reg;

       unsigned int chan_base = (unsigned int)pd.base

                                   + PL08X_OS_CHAN_BASE;

 

       chan_base += ci * PL08X_OS_CHAN;

 

/* 下面对于disable一个DMAchannel的做法,是根据datasheet中的描述:

Disabling a DMA channel without losing data in the FIFO

To disable a DMA channel without losing data in the FIFO:

1. Set the Halt bit in the relevant channel Configuration Register. See Channel

Configuration Registers on page 3-27. This causes any subsequent DMA requests

to be ignored.

2. Poll the Active bit in the relevant channel Configuration Register until it reaches

0. This bit indicates whether there is any data in the channel that has to be

transferred.

3. Clear the Channel Enable bit in the relevant channel Configuration Register.

即先设置Halt位,然后一直轮询检测对应channel,直到inactive,然后再清空对应的Channel Enable位,如果直接不做任何检测判断,上来就去清空对应的Enable位,那么就会“losing data in the FIFO ”,丢失了FIFO中的数据,是不太安全的 */

       /*

        * Ignore subsequent requests

        */

       reg = readl(chan_base + PL08X_OS_CCFG);

       reg |= PL08X_MASK_HALT;

       writel(reg, chan_base + PL08X_OS_CCFG);

 

       /* Wait for channel inactive */

       reg = readl(chan_base + PL08X_OS_CCFG);

       while (reg & PL08X_MASK_ACTIVE)

              reg = readl(chan_base + PL08X_OS_CCFG);

 

       reg = readl(chan_base + PL08X_OS_CCFG);

       reg &= ~PL08X_MASK_CEN;

 

       writel(reg, chan_base + PL08X_OS_CCFG);

       mb();

 

       return;

}

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