]> git.samba.org - sfrench/cifs-2.6.git/commitdiff
dmaengine: hdmac: Implement interleaved transfers
authorMaxime Ripard <maxime.ripard@free-electrons.com>
Wed, 27 May 2015 14:01:53 +0000 (16:01 +0200)
committerVinod Koul <vinod.koul@intel.com>
Fri, 12 Jun 2015 12:43:36 +0000 (18:13 +0530)
The AT91 HDMAC controller supports interleaved transfers through what's
called the Picture-in-Picture mode, which allows to transfer a squared
portion of a framebuffer.

This means that this interleaved transfer only supports interleaved
transfers which have a transfer size and ICGs that are fixed across all the
chunks.

While this is a quite drastic restriction of the interleaved transfers
compared to what the dmaengine API allows, this is still useful, and our
driver will only reject transfers that do not conform to this.

Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
Acked-by: Ludovic Desroches <ludovic.desroches@atmel.com>
Signed-off-by: Vinod Koul <vinod.koul@intel.com>
drivers/dma/at_hdmac.c
drivers/dma/at_hdmac_regs.h

index 57b2141ddddc1abca862197b78f8bd2e1cb51f57..59892126d1758f9d0e0821fa88ff62094a0c6e74 100644 (file)
@@ -247,6 +247,10 @@ static void atc_dostart(struct at_dma_chan *atchan, struct at_desc *first)
        channel_writel(atchan, CTRLA, 0);
        channel_writel(atchan, CTRLB, 0);
        channel_writel(atchan, DSCR, first->txd.phys);
+       channel_writel(atchan, SPIP, ATC_SPIP_HOLE(first->src_hole) |
+                      ATC_SPIP_BOUNDARY(first->boundary));
+       channel_writel(atchan, DPIP, ATC_DPIP_HOLE(first->dst_hole) |
+                      ATC_DPIP_BOUNDARY(first->boundary));
        dma_writel(atdma, CHER, atchan->mask);
 
        vdbg_dump_regs(atchan);
@@ -634,6 +638,104 @@ static dma_cookie_t atc_tx_submit(struct dma_async_tx_descriptor *tx)
        return cookie;
 }
 
+/**
+ * atc_prep_dma_interleaved - prepare memory to memory interleaved operation
+ * @chan: the channel to prepare operation on
+ * @xt: Interleaved transfer template
+ * @flags: tx descriptor status flags
+ */
+static struct dma_async_tx_descriptor *
+atc_prep_dma_interleaved(struct dma_chan *chan,
+                        struct dma_interleaved_template *xt,
+                        unsigned long flags)
+{
+       struct at_dma_chan      *atchan = to_at_dma_chan(chan);
+       struct data_chunk       *first = xt->sgl;
+       struct at_desc          *desc = NULL;
+       size_t                  xfer_count;
+       unsigned int            dwidth;
+       u32                     ctrla;
+       u32                     ctrlb;
+       size_t                  len = 0;
+       int                     i;
+
+       dev_info(chan2dev(chan),
+                "%s: src=0x%08x, dest=0x%08x, numf=%d, frame_size=%d, flags=0x%lx\n",
+               __func__, xt->src_start, xt->dst_start, xt->numf,
+               xt->frame_size, flags);
+
+       if (unlikely(!xt || xt->numf != 1 || !xt->frame_size))
+               return NULL;
+
+       /*
+        * The controller can only "skip" X bytes every Y bytes, so we
+        * need to make sure we are given a template that fit that
+        * description, ie a template with chunks that always have the
+        * same size, with the same ICGs.
+        */
+       for (i = 0; i < xt->frame_size; i++) {
+               struct data_chunk *chunk = xt->sgl + i;
+
+               if ((chunk->size != xt->sgl->size) ||
+                   (dmaengine_get_dst_icg(xt, chunk) != dmaengine_get_dst_icg(xt, first)) ||
+                   (dmaengine_get_src_icg(xt, chunk) != dmaengine_get_src_icg(xt, first))) {
+                       dev_err(chan2dev(chan),
+                               "%s: the controller can transfer only identical chunks\n",
+                               __func__);
+                       return NULL;
+               }
+
+               len += chunk->size;
+       }
+
+       dwidth = atc_get_xfer_width(xt->src_start,
+                                   xt->dst_start, len);
+
+       xfer_count = len >> dwidth;
+       if (xfer_count > ATC_BTSIZE_MAX) {
+               dev_err(chan2dev(chan), "%s: buffer is too big\n", __func__);
+               return NULL;
+       }
+
+       ctrla = ATC_SRC_WIDTH(dwidth) |
+               ATC_DST_WIDTH(dwidth);
+
+       ctrlb =   ATC_DEFAULT_CTRLB | ATC_IEN
+               | ATC_SRC_ADDR_MODE_INCR
+               | ATC_DST_ADDR_MODE_INCR
+               | ATC_SRC_PIP
+               | ATC_DST_PIP
+               | ATC_FC_MEM2MEM;
+
+       /* create the transfer */
+       desc = atc_desc_get(atchan);
+       if (!desc) {
+               dev_err(chan2dev(chan),
+                       "%s: couldn't allocate our descriptor\n", __func__);
+               return NULL;
+       }
+
+       desc->lli.saddr = xt->src_start;
+       desc->lli.daddr = xt->dst_start;
+       desc->lli.ctrla = ctrla | xfer_count;
+       desc->lli.ctrlb = ctrlb;
+
+       desc->boundary = first->size >> dwidth;
+       desc->dst_hole = (dmaengine_get_dst_icg(xt, first) >> dwidth) + 1;
+       desc->src_hole = (dmaengine_get_src_icg(xt, first) >> dwidth) + 1;
+
+       desc->txd.cookie = -EBUSY;
+       desc->total_len = desc->len = len;
+       desc->tx_width = dwidth;
+
+       /* set end-of-link to the last link descriptor of list*/
+       set_desc_eol(desc);
+
+       desc->txd.flags = flags; /* client is in control of this ack */
+
+       return &desc->txd;
+}
+
 /**
  * atc_prep_dma_memcpy - prepare a memcpy operation
  * @chan: the channel to prepare operation on
@@ -1609,6 +1711,7 @@ static int __init at_dma_probe(struct platform_device *pdev)
        /* setup platform data for each SoC */
        dma_cap_set(DMA_MEMCPY, at91sam9rl_config.cap_mask);
        dma_cap_set(DMA_SG, at91sam9rl_config.cap_mask);
+       dma_cap_set(DMA_INTERLEAVE, at91sam9g45_config.cap_mask);
        dma_cap_set(DMA_MEMCPY, at91sam9g45_config.cap_mask);
        dma_cap_set(DMA_SLAVE, at91sam9g45_config.cap_mask);
        dma_cap_set(DMA_SG, at91sam9g45_config.cap_mask);
@@ -1713,6 +1816,9 @@ static int __init at_dma_probe(struct platform_device *pdev)
        atdma->dma_common.dev = &pdev->dev;
 
        /* set prep routines based on capability */
+       if (dma_has_cap(DMA_INTERLEAVE, atdma->dma_common.cap_mask))
+               atdma->dma_common.device_prep_interleaved_dma = atc_prep_dma_interleaved;
+
        if (dma_has_cap(DMA_MEMCPY, atdma->dma_common.cap_mask))
                atdma->dma_common.device_prep_dma_memcpy = atc_prep_dma_memcpy;
 
index 2727ca560572586482cb53a0f38522e0b66abfe3..bc8d5ebedd192f12f9ed32f220c45892c064a8d0 100644 (file)
@@ -196,6 +196,11 @@ struct at_desc {
        size_t                          len;
        u32                             tx_width;
        size_t                          total_len;
+
+       /* Interleaved data */
+       size_t                          boundary;
+       size_t                          dst_hole;
+       size_t                          src_hole;
 };
 
 static inline struct at_desc *