Merge branch 'imx-for-2.6.38' of git://git.pengutronix.de/git/ukl/linux-2.6 into...
[pandora-kernel.git] / drivers / dma / fsldma.c
index cea08be..286c3ac 100644 (file)
 #include <linux/dmapool.h>
 #include <linux/of_platform.h>
 
-#include <asm/fsldma.h>
 #include "fsldma.h"
 
+static const char msg_ld_oom[] = "No free memory for link descriptor\n";
+
 static void dma_init(struct fsldma_chan *chan)
 {
        /* Reset the channel */
@@ -499,7 +500,7 @@ fsl_dma_prep_interrupt(struct dma_chan *dchan, unsigned long flags)
 
        new = fsl_dma_alloc_descriptor(chan);
        if (!new) {
-               dev_err(chan->dev, "No free memory for link descriptor\n");
+               dev_err(chan->dev, msg_ld_oom);
                return NULL;
        }
 
@@ -536,8 +537,7 @@ static struct dma_async_tx_descriptor *fsl_dma_prep_memcpy(
                /* Allocate the link descriptor from DMA pool */
                new = fsl_dma_alloc_descriptor(chan);
                if (!new) {
-                       dev_err(chan->dev,
-                                       "No free memory for link descriptor\n");
+                       dev_err(chan->dev, msg_ld_oom);
                        goto fail;
                }
 #ifdef FSL_DMA_LD_DEBUG
@@ -583,223 +583,205 @@ fail:
        return NULL;
 }
 
-/**
- * fsl_dma_prep_slave_sg - prepare descriptors for a DMA_SLAVE transaction
- * @chan: DMA channel
- * @sgl: scatterlist to transfer to/from
- * @sg_len: number of entries in @scatterlist
- * @direction: DMA direction
- * @flags: DMAEngine flags
- *
- * Prepare a set of descriptors for a DMA_SLAVE transaction. Following the
- * DMA_SLAVE API, this gets the device-specific information from the
- * chan->private variable.
- */
-static struct dma_async_tx_descriptor *fsl_dma_prep_slave_sg(
-       struct dma_chan *dchan, struct scatterlist *sgl, unsigned int sg_len,
-       enum dma_data_direction direction, unsigned long flags)
+static struct dma_async_tx_descriptor *fsl_dma_prep_sg(struct dma_chan *dchan,
+       struct scatterlist *dst_sg, unsigned int dst_nents,
+       struct scatterlist *src_sg, unsigned int src_nents,
+       unsigned long flags)
 {
-       struct fsldma_chan *chan;
        struct fsl_desc_sw *first = NULL, *prev = NULL, *new = NULL;
-       struct fsl_dma_slave *slave;
-       size_t copy;
-
-       int i;
-       struct scatterlist *sg;
-       size_t sg_used;
-       size_t hw_used;
-       struct fsl_dma_hw_addr *hw;
-       dma_addr_t dma_dst, dma_src;
+       struct fsldma_chan *chan = to_fsl_chan(dchan);
+       size_t dst_avail, src_avail;
+       dma_addr_t dst, src;
+       size_t len;
 
-       if (!dchan)
+       /* basic sanity checks */
+       if (dst_nents == 0 || src_nents == 0)
                return NULL;
 
-       if (!dchan->private)
+       if (dst_sg == NULL || src_sg == NULL)
                return NULL;
 
-       chan = to_fsl_chan(dchan);
-       slave = dchan->private;
+       /*
+        * TODO: should we check that both scatterlists have the same
+        * TODO: number of bytes in total? Is that really an error?
+        */
 
-       if (list_empty(&slave->addresses))
-               return NULL;
+       /* get prepared for the loop */
+       dst_avail = sg_dma_len(dst_sg);
+       src_avail = sg_dma_len(src_sg);
 
-       hw = list_first_entry(&slave->addresses, struct fsl_dma_hw_addr, entry);
-       hw_used = 0;
+       /* run until we are out of scatterlist entries */
+       while (true) {
 
-       /*
-        * Build the hardware transaction to copy from the scatterlist to
-        * the hardware, or from the hardware to the scatterlist
-        *
-        * If you are copying from the hardware to the scatterlist and it
-        * takes two hardware entries to fill an entire page, then both
-        * hardware entries will be coalesced into the same page
-        *
-        * If you are copying from the scatterlist to the hardware and a
-        * single page can fill two hardware entries, then the data will
-        * be read out of the page into the first hardware entry, and so on
-        */
-       for_each_sg(sgl, sg, sg_len, i) {
-               sg_used = 0;
-
-               /* Loop until the entire scatterlist entry is used */
-               while (sg_used < sg_dma_len(sg)) {
-
-                       /*
-                        * If we've used up the current hardware address/length
-                        * pair, we need to load a new one
-                        *
-                        * This is done in a while loop so that descriptors with
-                        * length == 0 will be skipped
-                        */
-                       while (hw_used >= hw->length) {
-
-                               /*
-                                * If the current hardware entry is the last
-                                * entry in the list, we're finished
-                                */
-                               if (list_is_last(&hw->entry, &slave->addresses))
-                                       goto finished;
-
-                               /* Get the next hardware address/length pair */
-                               hw = list_entry(hw->entry.next,
-                                               struct fsl_dma_hw_addr, entry);
-                               hw_used = 0;
-                       }
-
-                       /* Allocate the link descriptor from DMA pool */
-                       new = fsl_dma_alloc_descriptor(chan);
-                       if (!new) {
-                               dev_err(chan->dev, "No free memory for "
-                                                      "link descriptor\n");
-                               goto fail;
-                       }
+               /* create the largest transaction possible */
+               len = min_t(size_t, src_avail, dst_avail);
+               len = min_t(size_t, len, FSL_DMA_BCR_MAX_CNT);
+               if (len == 0)
+                       goto fetch;
+
+               dst = sg_dma_address(dst_sg) + sg_dma_len(dst_sg) - dst_avail;
+               src = sg_dma_address(src_sg) + sg_dma_len(src_sg) - src_avail;
+
+               /* allocate and populate the descriptor */
+               new = fsl_dma_alloc_descriptor(chan);
+               if (!new) {
+                       dev_err(chan->dev, msg_ld_oom);
+                       goto fail;
+               }
 #ifdef FSL_DMA_LD_DEBUG
-                       dev_dbg(chan->dev, "new link desc alloc %p\n", new);
+               dev_dbg(chan->dev, "new link desc alloc %p\n", new);
 #endif
 
-                       /*
-                        * Calculate the maximum number of bytes to transfer,
-                        * making sure it is less than the DMA controller limit
-                        */
-                       copy = min_t(size_t, sg_dma_len(sg) - sg_used,
-                                            hw->length - hw_used);
-                       copy = min_t(size_t, copy, FSL_DMA_BCR_MAX_CNT);
-
-                       /*
-                        * DMA_FROM_DEVICE
-                        * from the hardware to the scatterlist
-                        *
-                        * DMA_TO_DEVICE
-                        * from the scatterlist to the hardware
-                        */
-                       if (direction == DMA_FROM_DEVICE) {
-                               dma_src = hw->address + hw_used;
-                               dma_dst = sg_dma_address(sg) + sg_used;
-                       } else {
-                               dma_src = sg_dma_address(sg) + sg_used;
-                               dma_dst = hw->address + hw_used;
-                       }
-
-                       /* Fill in the descriptor */
-                       set_desc_cnt(chan, &new->hw, copy);
-                       set_desc_src(chan, &new->hw, dma_src);
-                       set_desc_dst(chan, &new->hw, dma_dst);
-
-                       /*
-                        * If this is not the first descriptor, chain the
-                        * current descriptor after the previous descriptor
-                        */
-                       if (!first) {
-                               first = new;
-                       } else {
-                               set_desc_next(chan, &prev->hw,
-                                             new->async_tx.phys);
-                       }
-
-                       new->async_tx.cookie = 0;
-                       async_tx_ack(&new->async_tx);
-
-                       prev = new;
-                       sg_used += copy;
-                       hw_used += copy;
-
-                       /* Insert the link descriptor into the LD ring */
-                       list_add_tail(&new->node, &first->tx_list);
-               }
-       }
+               set_desc_cnt(chan, &new->hw, len);
+               set_desc_src(chan, &new->hw, src);
+               set_desc_dst(chan, &new->hw, dst);
 
-finished:
+               if (!first)
+                       first = new;
+               else
+                       set_desc_next(chan, &prev->hw, new->async_tx.phys);
 
-       /* All of the hardware address/length pairs had length == 0 */
-       if (!first || !new)
-               return NULL;
+               new->async_tx.cookie = 0;
+               async_tx_ack(&new->async_tx);
+               prev = new;
 
-       new->async_tx.flags = flags;
-       new->async_tx.cookie = -EBUSY;
+               /* Insert the link descriptor to the LD ring */
+               list_add_tail(&new->node, &first->tx_list);
 
-       /* Set End-of-link to the last link descriptor of new list */
-       set_ld_eol(chan, new);
+               /* update metadata */
+               dst_avail -= len;
+               src_avail -= len;
+
+fetch:
+               /* fetch the next dst scatterlist entry */
+               if (dst_avail == 0) {
+
+                       /* no more entries: we're done */
+                       if (dst_nents == 0)
+                               break;
+
+                       /* fetch the next entry: if there are no more: done */
+                       dst_sg = sg_next(dst_sg);
+                       if (dst_sg == NULL)
+                               break;
+
+                       dst_nents--;
+                       dst_avail = sg_dma_len(dst_sg);
+               }
 
-       /* Enable extra controller features */
-       if (chan->set_src_loop_size)
-               chan->set_src_loop_size(chan, slave->src_loop_size);
+               /* fetch the next src scatterlist entry */
+               if (src_avail == 0) {
 
-       if (chan->set_dst_loop_size)
-               chan->set_dst_loop_size(chan, slave->dst_loop_size);
+                       /* no more entries: we're done */
+                       if (src_nents == 0)
+                               break;
 
-       if (chan->toggle_ext_start)
-               chan->toggle_ext_start(chan, slave->external_start);
+                       /* fetch the next entry: if there are no more: done */
+                       src_sg = sg_next(src_sg);
+                       if (src_sg == NULL)
+                               break;
 
-       if (chan->toggle_ext_pause)
-               chan->toggle_ext_pause(chan, slave->external_pause);
+                       src_nents--;
+                       src_avail = sg_dma_len(src_sg);
+               }
+       }
 
-       if (chan->set_request_count)
-               chan->set_request_count(chan, slave->request_count);
+       new->async_tx.flags = flags; /* client is in control of this ack */
+       new->async_tx.cookie = -EBUSY;
+
+       /* Set End-of-link to the last link descriptor of new list */
+       set_ld_eol(chan, new);
 
        return &first->async_tx;
 
 fail:
-       /* If first was not set, then we failed to allocate the very first
-        * descriptor, and we're done */
        if (!first)
                return NULL;
 
+       fsldma_free_desc_list_reverse(chan, &first->tx_list);
+       return NULL;
+}
+
+/**
+ * fsl_dma_prep_slave_sg - prepare descriptors for a DMA_SLAVE transaction
+ * @chan: DMA channel
+ * @sgl: scatterlist to transfer to/from
+ * @sg_len: number of entries in @scatterlist
+ * @direction: DMA direction
+ * @flags: DMAEngine flags
+ *
+ * Prepare a set of descriptors for a DMA_SLAVE transaction. Following the
+ * DMA_SLAVE API, this gets the device-specific information from the
+ * chan->private variable.
+ */
+static struct dma_async_tx_descriptor *fsl_dma_prep_slave_sg(
+       struct dma_chan *dchan, struct scatterlist *sgl, unsigned int sg_len,
+       enum dma_data_direction direction, unsigned long flags)
+{
        /*
-        * First is set, so all of the descriptors we allocated have been added
-        * to first->tx_list, INCLUDING "first" itself. Therefore we
-        * must traverse the list backwards freeing each descriptor in turn
+        * This operation is not supported on the Freescale DMA controller
         *
-        * We're re-using variables for the loop, oh well
+        * However, we need to provide the function pointer to allow the
+        * device_control() method to work.
         */
-       fsldma_free_desc_list_reverse(chan, &first->tx_list);
        return NULL;
 }
 
 static int fsl_dma_device_control(struct dma_chan *dchan,
                                  enum dma_ctrl_cmd cmd, unsigned long arg)
 {
+       struct dma_slave_config *config;
        struct fsldma_chan *chan;
        unsigned long flags;
-
-       /* Only supports DMA_TERMINATE_ALL */
-       if (cmd != DMA_TERMINATE_ALL)
-               return -ENXIO;
+       int size;
 
        if (!dchan)
                return -EINVAL;
 
        chan = to_fsl_chan(dchan);
 
-       /* Halt the DMA engine */
-       dma_halt(chan);
+       switch (cmd) {
+       case DMA_TERMINATE_ALL:
+               /* Halt the DMA engine */
+               dma_halt(chan);
 
-       spin_lock_irqsave(&chan->desc_lock, flags);
+               spin_lock_irqsave(&chan->desc_lock, flags);
 
-       /* Remove and free all of the descriptors in the LD queue */
-       fsldma_free_desc_list(chan, &chan->ld_pending);
-       fsldma_free_desc_list(chan, &chan->ld_running);
+               /* Remove and free all of the descriptors in the LD queue */
+               fsldma_free_desc_list(chan, &chan->ld_pending);
+               fsldma_free_desc_list(chan, &chan->ld_running);
 
-       spin_unlock_irqrestore(&chan->desc_lock, flags);
+               spin_unlock_irqrestore(&chan->desc_lock, flags);
+               return 0;
+
+       case DMA_SLAVE_CONFIG:
+               config = (struct dma_slave_config *)arg;
+
+               /* make sure the channel supports setting burst size */
+               if (!chan->set_request_count)
+                       return -ENXIO;
+
+               /* we set the controller burst size depending on direction */
+               if (config->direction == DMA_TO_DEVICE)
+                       size = config->dst_addr_width * config->dst_maxburst;
+               else
+                       size = config->src_addr_width * config->src_maxburst;
+
+               chan->set_request_count(chan, size);
+               return 0;
+
+       case FSLDMA_EXTERNAL_START:
+
+               /* make sure the channel supports external start */
+               if (!chan->toggle_ext_start)
+                       return -ENXIO;
+
+               chan->toggle_ext_start(chan, arg);
+               return 0;
+
+       default:
+               return -ENXIO;
+       }
 
        return 0;
 }
@@ -1327,11 +1309,13 @@ static int __devinit fsldma_of_probe(struct platform_device *op,
 
        dma_cap_set(DMA_MEMCPY, fdev->common.cap_mask);
        dma_cap_set(DMA_INTERRUPT, fdev->common.cap_mask);
+       dma_cap_set(DMA_SG, fdev->common.cap_mask);
        dma_cap_set(DMA_SLAVE, fdev->common.cap_mask);
        fdev->common.device_alloc_chan_resources = fsl_dma_alloc_chan_resources;
        fdev->common.device_free_chan_resources = fsl_dma_free_chan_resources;
        fdev->common.device_prep_dma_interrupt = fsl_dma_prep_interrupt;
        fdev->common.device_prep_dma_memcpy = fsl_dma_prep_memcpy;
+       fdev->common.device_prep_dma_sg = fsl_dma_prep_sg;
        fdev->common.device_tx_status = fsl_tx_status;
        fdev->common.device_issue_pending = fsl_dma_memcpy_issue_pending;
        fdev->common.device_prep_slave_sg = fsl_dma_prep_slave_sg;