]> git.karo-electronics.de Git - mv-sheeva.git/blobdiff - drivers/dma/amba-pl08x.c
ARM: PL08x: introduce 'phychan_hold' to hold on to physical channels
[mv-sheeva.git] / drivers / dma / amba-pl08x.c
index 660165dd945a592c91bae2b132641d8623593b4a..bf6f7d02c9f6b561b83a531a3410d5b73d69998b 100644 (file)
@@ -173,6 +173,11 @@ static inline struct pl08x_dma_chan *to_pl08x_chan(struct dma_chan *chan)
        return container_of(chan, struct pl08x_dma_chan, chan);
 }
 
+static inline struct pl08x_txd *to_pl08x_txd(struct dma_async_tx_descriptor *tx)
+{
+       return container_of(tx, struct pl08x_txd, tx);
+}
+
 /*
  * Physical channel handling
  */
@@ -348,9 +353,9 @@ static u32 pl08x_getbytes_chan(struct pl08x_dma_chan *plchan)
        }
 
        /* Sum up all queued transactions */
-       if (!list_empty(&plchan->desc_list)) {
+       if (!list_empty(&plchan->pend_list)) {
                struct pl08x_txd *txdi;
-               list_for_each_entry(txdi, &plchan->desc_list, node) {
+               list_for_each_entry(txdi, &plchan->pend_list, node) {
                        bytes += txdi->len;
                }
        }
@@ -481,38 +486,45 @@ static inline u32 pl08x_cctl_bits(u32 cctl, u8 srcwidth, u8 dstwidth,
        return retbits;
 }
 
+struct pl08x_lli_build_data {
+       struct pl08x_txd *txd;
+       struct pl08x_driver_data *pl08x;
+       struct pl08x_bus_data srcbus;
+       struct pl08x_bus_data dstbus;
+       size_t remainder;
+};
+
 /*
  * Autoselect a master bus to use for the transfer
  * this prefers the destination bus if both available
  * if fixed address on one bus the other will be chosen
  */
-static 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, u32 cctl)
+static void pl08x_choose_master_bus(struct pl08x_lli_build_data *bd,
+       struct pl08x_bus_data **mbus, struct pl08x_bus_data **sbus, u32 cctl)
 {
        if (!(cctl & PL080_CONTROL_DST_INCR)) {
-               *mbus = src_bus;
-               *sbus = dst_bus;
+               *mbus = &bd->srcbus;
+               *sbus = &bd->dstbus;
        } else if (!(cctl & PL080_CONTROL_SRC_INCR)) {
-               *mbus = dst_bus;
-               *sbus = src_bus;
+               *mbus = &bd->dstbus;
+               *sbus = &bd->srcbus;
        } else {
-               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;
+               if (bd->dstbus.buswidth == 4) {
+                       *mbus = &bd->dstbus;
+                       *sbus = &bd->srcbus;
+               } else if (bd->srcbus.buswidth == 4) {
+                       *mbus = &bd->srcbus;
+                       *sbus = &bd->dstbus;
+               } else if (bd->dstbus.buswidth == 2) {
+                       *mbus = &bd->dstbus;
+                       *sbus = &bd->srcbus;
+               } else if (bd->srcbus.buswidth == 2) {
+                       *mbus = &bd->srcbus;
+                       *sbus = &bd->dstbus;
                } else {
-                       /* src_bus->buswidth == 1 */
-                       *mbus = dst_bus;
-                       *sbus = src_bus;
+                       /* bd->srcbus.buswidth == 1 */
+                       *mbus = &bd->dstbus;
+                       *sbus = &bd->srcbus;
                }
        }
 }
@@ -521,48 +533,41 @@ static void pl08x_choose_master_bus(struct pl08x_bus_data *src_bus,
  * Fills in one LLI for a certain transfer descriptor
  * and advance the counter
  */
-static int pl08x_fill_lli_for_desc(struct pl08x_driver_data *pl08x,
-                           struct pl08x_txd *txd, int num_llis, int len,
-                           u32 cctl, u32 *remainder)
+static void pl08x_fill_lli_for_desc(struct pl08x_lli_build_data *bd,
+       int num_llis, int len, u32 cctl)
 {
-       struct pl08x_lli *llis_va = txd->llis_va;
-       dma_addr_t llis_bus = txd->llis_bus;
+       struct pl08x_lli *llis_va = bd->txd->llis_va;
+       dma_addr_t llis_bus = bd->txd->llis_bus;
 
        BUG_ON(num_llis >= MAX_NUM_TSFR_LLIS);
 
        llis_va[num_llis].cctl = cctl;
-       llis_va[num_llis].src = txd->srcbus.addr;
-       llis_va[num_llis].dst = txd->dstbus.addr;
+       llis_va[num_llis].src = bd->srcbus.addr;
+       llis_va[num_llis].dst = bd->dstbus.addr;
        llis_va[num_llis].lli = llis_bus + (num_llis + 1) * sizeof(struct pl08x_lli);
-       if (pl08x->lli_buses & PL08X_AHB2)
+       if (bd->pl08x->lli_buses & PL08X_AHB2)
                llis_va[num_llis].lli |= PL080_LLI_LM_AHB2;
 
        if (cctl & PL080_CONTROL_SRC_INCR)
-               txd->srcbus.addr += len;
+               bd->srcbus.addr += len;
        if (cctl & PL080_CONTROL_DST_INCR)
-               txd->dstbus.addr += len;
-
-       BUG_ON(*remainder < len);
+               bd->dstbus.addr += len;
 
-       *remainder -= len;
+       BUG_ON(bd->remainder < len);
 
-       return num_llis + 1;
+       bd->remainder -= len;
 }
 
 /*
- * Return number of bytes to fill to boundary, or len
+ * Return number of bytes to fill to boundary, or len.
+ * This calculation works for any value of addr.
  */
 static inline size_t pl08x_pre_boundary(u32 addr, size_t len)
 {
-       u32 boundary;
-
-       boundary = ((addr >> PL08X_BOUNDARY_SHIFT) + 1)
-               << PL08X_BOUNDARY_SHIFT;
+       size_t boundary_len = PL08X_BOUNDARY_SIZE -
+                       (addr & (PL08X_BOUNDARY_SIZE - 1));
 
-       if (boundary < addr + len)
-               return boundary - addr;
-       else
-               return len;
+       return min(boundary_len, len);
 }
 
 /*
@@ -574,7 +579,7 @@ static int pl08x_fill_llis_for_desc(struct pl08x_driver_data *pl08x,
                              struct pl08x_txd *txd)
 {
        struct pl08x_bus_data *mbus, *sbus;
-       size_t remainder;
+       struct pl08x_lli_build_data bd;
        int num_llis = 0;
        u32 cctl;
        size_t max_bytes_per_lli;
@@ -593,38 +598,43 @@ static int pl08x_fill_llis_for_desc(struct pl08x_driver_data *pl08x,
        /* Get the default CCTL */
        cctl = txd->cctl;
 
+       bd.txd = txd;
+       bd.pl08x = pl08x;
+       bd.srcbus.addr = txd->src_addr;
+       bd.dstbus.addr = txd->dst_addr;
+
        /* Find maximum width of the source bus */
-       txd->srcbus.maxwidth =
+       bd.srcbus.maxwidth =
                pl08x_get_bytes_for_cctl((cctl & PL080_CONTROL_SWIDTH_MASK) >>
                                       PL080_CONTROL_SWIDTH_SHIFT);
 
        /* Find maximum width of the destination bus */
-       txd->dstbus.maxwidth =
+       bd.dstbus.maxwidth =
                pl08x_get_bytes_for_cctl((cctl & PL080_CONTROL_DWIDTH_MASK) >>
                                       PL080_CONTROL_DWIDTH_SHIFT);
 
        /* Set up the bus widths to the maximum */
-       txd->srcbus.buswidth = txd->srcbus.maxwidth;
-       txd->dstbus.buswidth = txd->dstbus.maxwidth;
+       bd.srcbus.buswidth = bd.srcbus.maxwidth;
+       bd.dstbus.buswidth = bd.dstbus.maxwidth;
        dev_vdbg(&pl08x->adev->dev,
                 "%s source bus is %d bytes wide, dest bus is %d bytes wide\n",
-                __func__, txd->srcbus.buswidth, txd->dstbus.buswidth);
+                __func__, bd.srcbus.buswidth, bd.dstbus.buswidth);
 
 
        /*
         * Bytes transferred == tsize * MIN(buswidths), not max(buswidths)
         */
-       max_bytes_per_lli = min(txd->srcbus.buswidth, txd->dstbus.buswidth) *
+       max_bytes_per_lli = min(bd.srcbus.buswidth, bd.dstbus.buswidth) *
                PL080_CONTROL_TRANSFER_SIZE_MASK;
        dev_vdbg(&pl08x->adev->dev,
                 "%s max bytes per lli = %zu\n",
                 __func__, max_bytes_per_lli);
 
        /* We need to count this down to zero */
-       remainder = txd->len;
+       bd.remainder = txd->len;
        dev_vdbg(&pl08x->adev->dev,
                 "%s remainder = %zu\n",
-                __func__, remainder);
+                __func__, bd.remainder);
 
        /*
         * Choose bus to align to
@@ -632,23 +642,20 @@ static int pl08x_fill_llis_for_desc(struct pl08x_driver_data *pl08x,
         * - if fixed address on one bus chooses other
         * - modifies cctl to choose an appropriate master
         */
-       pl08x_choose_master_bus(&txd->srcbus, &txd->dstbus,
-                               &mbus, &sbus, cctl);
+       pl08x_choose_master_bus(&bd, &mbus, &sbus, cctl);
 
        if (txd->len < mbus->buswidth) {
                /*
                 * Less than a bus width available
                 * - send as single bytes
                 */
-               while (remainder) {
+               while (bd.remainder) {
                        dev_vdbg(&pl08x->adev->dev,
                                 "%s single byte LLIs for a transfer of "
                                 "less than a bus width (remain 0x%08x)\n",
-                                __func__, remainder);
+                                __func__, bd.remainder);
                        cctl = pl08x_cctl_bits(cctl, 1, 1, 1);
-                       num_llis =
-                               pl08x_fill_lli_for_desc(pl08x, txd, num_llis, 1,
-                                       cctl, &remainder);
+                       pl08x_fill_lli_for_desc(&bd, num_llis++, 1, cctl);
                        total_bytes++;
                }
        } else {
@@ -660,10 +667,9 @@ static int pl08x_fill_llis_for_desc(struct pl08x_driver_data *pl08x,
                        dev_vdbg(&pl08x->adev->dev,
                                "%s adjustment lli for less than bus width "
                                 "(remain 0x%08x)\n",
-                                __func__, remainder);
+                                __func__, bd.remainder);
                        cctl = pl08x_cctl_bits(cctl, 1, 1, 1);
-                       num_llis = pl08x_fill_lli_for_desc
-                               (pl08x, txd, num_llis, 1, cctl, &remainder);
+                       pl08x_fill_lli_for_desc(&bd, num_llis++, 1, cctl);
                        total_bytes++;
                }
 
@@ -683,47 +689,39 @@ static int pl08x_fill_llis_for_desc(struct pl08x_driver_data *pl08x,
                 * Make largest possible LLIs until less than one bus
                 * width left
                 */
-               while (remainder > (mbus->buswidth - 1)) {
+               while (bd.remainder > (mbus->buswidth - 1)) {
                        size_t lli_len, target_len, tsize, 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;
+                       target_len = min(bd.remainder, max_bytes_per_lli);
 
                        /*
-                        * Set bus lengths for incrementing buses
-                        * to number of bytes which fill to next memory
-                        * boundary
+                        * Set bus lengths for incrementing buses to the
+                        * number of bytes which fill to next memory boundary,
+                        * limiting on the target length calculated above.
                         */
                        if (cctl & PL080_CONTROL_SRC_INCR)
-                               txd->srcbus.fill_bytes =
-                                       pl08x_pre_boundary(
-                                               txd->srcbus.addr,
-                                               remainder);
+                               bd.srcbus.fill_bytes =
+                                       pl08x_pre_boundary(bd.srcbus.addr,
+                                               target_len);
                        else
-                               txd->srcbus.fill_bytes =
-                                       max_bytes_per_lli;
+                               bd.srcbus.fill_bytes = target_len;
 
                        if (cctl & PL080_CONTROL_DST_INCR)
-                               txd->dstbus.fill_bytes =
-                                       pl08x_pre_boundary(
-                                               txd->dstbus.addr,
-                                               remainder);
+                               bd.dstbus.fill_bytes =
+                                       pl08x_pre_boundary(bd.dstbus.addr,
+                                               target_len);
                        else
-                               txd->dstbus.fill_bytes =
-                                               max_bytes_per_lli;
+                               bd.dstbus.fill_bytes = target_len;
 
-                       /*
-                        *  Find the nearest
-                        */
-                       lli_len = min(txd->srcbus.fill_bytes,
-                               txd->dstbus.fill_bytes);
+                       /* Find the nearest */
+                       lli_len = min(bd.srcbus.fill_bytes,
+                                     bd.dstbus.fill_bytes);
 
-                       BUG_ON(lli_len > remainder);
+                       BUG_ON(lli_len > bd.remainder);
 
                        if (lli_len <= 0) {
                                dev_err(&pl08x->adev->dev,
@@ -780,16 +778,15 @@ static int pl08x_fill_llis_for_desc(struct pl08x_driver_data *pl08x,
                                }
 
                                cctl = pl08x_cctl_bits(cctl,
-                                                      txd->srcbus.buswidth,
-                                                      txd->dstbus.buswidth,
+                                                      bd.srcbus.buswidth,
+                                                      bd.dstbus.buswidth,
                                                       tsize);
 
                                dev_vdbg(&pl08x->adev->dev,
                                        "%s fill lli with single lli chunk of size 0x%08zx (remainder 0x%08zx)\n",
-                                       __func__, lli_len, remainder);
-                               num_llis = pl08x_fill_lli_for_desc(pl08x, txd,
-                                               num_llis, lli_len, cctl,
-                                               &remainder);
+                                       __func__, lli_len, bd.remainder);
+                               pl08x_fill_lli_for_desc(&bd, num_llis++,
+                                       lli_len, cctl);
                                total_bytes += lli_len;
                        }
 
@@ -801,15 +798,13 @@ static int pl08x_fill_llis_for_desc(struct pl08x_driver_data *pl08x,
                                 */
                                int j;
                                for (j = 0; (j < mbus->buswidth)
-                                               && (remainder); j++) {
+                                               && (bd.remainder); j++) {
                                        cctl = pl08x_cctl_bits(cctl, 1, 1, 1);
                                        dev_vdbg(&pl08x->adev->dev,
                                                "%s align with boundary, single byte (remain 0x%08zx)\n",
-                                               __func__, remainder);
-                                       num_llis =
-                                               pl08x_fill_lli_for_desc(pl08x,
-                                                       txd, num_llis, 1,
-                                                       cctl, &remainder);
+                                               __func__, bd.remainder);
+                                       pl08x_fill_lli_for_desc(&bd,
+                                               num_llis++, 1, cctl);
                                        total_bytes++;
                                }
                        }
@@ -818,13 +813,12 @@ static int pl08x_fill_llis_for_desc(struct pl08x_driver_data *pl08x,
                /*
                 * Send any odd bytes
                 */
-               while (remainder) {
+               while (bd.remainder) {
                        cctl = pl08x_cctl_bits(cctl, 1, 1, 1);
                        dev_vdbg(&pl08x->adev->dev,
                                "%s align with boundary, single odd byte (remain %zu)\n",
-                               __func__, remainder);
-                       num_llis = pl08x_fill_lli_for_desc(pl08x, txd, num_llis,
-                                       1, cctl, &remainder);
+                               __func__, bd.remainder);
+                       pl08x_fill_lli_for_desc(&bd, num_llis++, 1, cctl);
                        total_bytes++;
                }
        }
@@ -891,9 +885,9 @@ static void pl08x_free_txd_list(struct pl08x_driver_data *pl08x,
        struct pl08x_txd *txdi = NULL;
        struct pl08x_txd *next;
 
-       if (!list_empty(&plchan->desc_list)) {
+       if (!list_empty(&plchan->pend_list)) {
                list_for_each_entry_safe(txdi,
-                                        next, &plchan->desc_list, node) {
+                                        next, &plchan->pend_list, node) {
                        list_del(&txdi->node);
                        pl08x_free_txd(pl08x, txdi);
                }
@@ -965,6 +959,7 @@ static int prep_phy_channel(struct pl08x_dma_chan *plchan,
                 ch->signal,
                 plchan->name);
 
+       plchan->phychan_hold++;
        plchan->phychan = ch;
 
        return 0;
@@ -985,11 +980,29 @@ static void release_phy_channel(struct pl08x_dma_chan *plchan)
 static dma_cookie_t pl08x_tx_submit(struct dma_async_tx_descriptor *tx)
 {
        struct pl08x_dma_chan *plchan = to_pl08x_chan(tx->chan);
+       struct pl08x_txd *txd = to_pl08x_txd(tx);
 
        plchan->chan.cookie += 1;
        if (plchan->chan.cookie < 0)
                plchan->chan.cookie = 1;
        tx->cookie = plchan->chan.cookie;
+
+       /* Put this onto the pending list */
+       list_add_tail(&txd->node, &plchan->pend_list);
+
+       /*
+        * If there was no physical channel available for this memcpy,
+        * stack the request up and indicate that the channel is waiting
+        * for a free physical channel.
+        */
+       if (!plchan->slave && !plchan->phychan) {
+               /* Do this memcpy whenever there is a channel ready */
+               plchan->state = PL08X_CHAN_WAITING;
+               plchan->waiting = txd;
+       } else {
+               plchan->phychan_hold--;
+       }
+
        /* This unlock follows the lock in the prep() function */
        spin_unlock_irqrestore(&plchan->lock, plchan->lockflags);
 
@@ -1194,10 +1207,10 @@ static void pl08x_issue_pending(struct dma_chan *chan)
        }
 
        /* Take the first element in the queue and execute it */
-       if (!list_empty(&plchan->desc_list)) {
+       if (!list_empty(&plchan->pend_list)) {
                struct pl08x_txd *next;
 
-               next = list_first_entry(&plchan->desc_list,
+               next = list_first_entry(&plchan->pend_list,
                                        struct pl08x_txd,
                                        node);
                list_del(&next->node);
@@ -1224,8 +1237,6 @@ static int pl08x_prep_channel_resources(struct pl08x_dma_chan *plchan,
 
        spin_lock_irqsave(&plchan->lock, plchan->lockflags);
 
-       list_add_tail(&txd->node, &plchan->desc_list);
-
        /*
         * See if we already have a physical channel allocated,
         * else this is the time to try to get one.
@@ -1233,24 +1244,23 @@ static int pl08x_prep_channel_resources(struct pl08x_dma_chan *plchan,
        ret = prep_phy_channel(plchan, txd);
        if (ret) {
                /*
-                * No physical channel available, we will
-                * stack up the memcpy channels until there is a channel
-                * available to handle it whereas slave transfers may
-                * have been denied due to platform channel muxing restrictions
-                * and since there is no guarantee that this will ever be
-                * resolved, and since the signal must be acquired AFTER
-                * acquiring the physical channel, we will let them be NACK:ed
-                * with -EBUSY here. The drivers can alway retry the prep()
-                * call if they are eager on doing this using DMA.
+                * No physical channel was available.
+                *
+                * memcpy transfers can be sorted out at submission time.
+                *
+                * Slave transfers may have been denied due to platform
+                * channel muxing restrictions.  Since there is no guarantee
+                * that this will ever be resolved, and the signal must be
+                * acquired AFTER acquiring the physical channel, we will let
+                * them be NACK:ed with -EBUSY here. The drivers can retry
+                * the prep() call if they are eager on doing this using DMA.
                 */
                if (plchan->slave) {
                        pl08x_free_txd_list(pl08x, plchan);
+                       pl08x_free_txd(pl08x, txd);
                        spin_unlock_irqrestore(&plchan->lock, plchan->lockflags);
                        return -EBUSY;
                }
-               /* Do this memcpy whenever there is a channel ready */
-               plchan->state = PL08X_CHAN_WAITING;
-               plchan->waiting = txd;
        } else
                /*
                 * Else we're all set, paused and ready to roll,
@@ -1288,12 +1298,14 @@ static u32 pl08x_select_bus(struct pl08x_driver_data *pl08x, u8 src, u8 dst)
        return cctl;
 }
 
-static struct pl08x_txd *pl08x_get_txd(struct pl08x_dma_chan *plchan)
+static struct pl08x_txd *pl08x_get_txd(struct pl08x_dma_chan *plchan,
+       unsigned long flags)
 {
        struct pl08x_txd *txd = kzalloc(sizeof(struct pl08x_txd), GFP_NOWAIT);
 
        if (txd) {
                dma_async_tx_descriptor_init(&txd->tx, &plchan->chan);
+               txd->tx.flags = flags;
                txd->tx.tx_submit = pl08x_tx_submit;
                INIT_LIST_HEAD(&txd->node);
 
@@ -1316,7 +1328,7 @@ static struct dma_async_tx_descriptor *pl08x_prep_dma_memcpy(
        struct pl08x_txd *txd;
        int ret;
 
-       txd = pl08x_get_txd(plchan);
+       txd = pl08x_get_txd(plchan, flags);
        if (!txd) {
                dev_err(&pl08x->adev->dev,
                        "%s no memory for descriptor\n", __func__);
@@ -1324,8 +1336,8 @@ static struct dma_async_tx_descriptor *pl08x_prep_dma_memcpy(
        }
 
        txd->direction = DMA_NONE;
-       txd->srcbus.addr = src;
-       txd->dstbus.addr = dest;
+       txd->src_addr = src;
+       txd->dst_addr = dest;
        txd->len = len;
 
        /* Set platform data for m2m */
@@ -1374,7 +1386,7 @@ static struct dma_async_tx_descriptor *pl08x_prep_slave_sg(
        dev_dbg(&pl08x->adev->dev, "%s prepare transaction of %d bytes from %s\n",
                __func__, sgl->length, plchan->name);
 
-       txd = pl08x_get_txd(plchan);
+       txd = pl08x_get_txd(plchan, flags);
        if (!txd) {
                dev_err(&pl08x->adev->dev, "%s no txd\n", __func__);
                return NULL;
@@ -1404,21 +1416,21 @@ static struct dma_async_tx_descriptor *pl08x_prep_slave_sg(
        if (direction == DMA_TO_DEVICE) {
                txd->ccfg |= PL080_FLOW_MEM2PER << PL080_CONFIG_FLOW_CONTROL_SHIFT;
                txd->cctl |= PL080_CONTROL_SRC_INCR;
-               txd->srcbus.addr = sgl->dma_address;
+               txd->src_addr = sgl->dma_address;
                if (plchan->runtime_addr)
-                       txd->dstbus.addr = plchan->runtime_addr;
+                       txd->dst_addr = plchan->runtime_addr;
                else
-                       txd->dstbus.addr = plchan->cd->addr;
+                       txd->dst_addr = plchan->cd->addr;
                src_buses = pl08x->mem_buses;
                dst_buses = plchan->cd->periph_buses;
        } else if (direction == DMA_FROM_DEVICE) {
                txd->ccfg |= PL080_FLOW_PER2MEM << PL080_CONFIG_FLOW_CONTROL_SHIFT;
                txd->cctl |= PL080_CONTROL_DST_INCR;
                if (plchan->runtime_addr)
-                       txd->srcbus.addr = plchan->runtime_addr;
+                       txd->src_addr = plchan->runtime_addr;
                else
-                       txd->srcbus.addr = plchan->cd->addr;
-               txd->dstbus.addr = sgl->dma_address;
+                       txd->src_addr = plchan->cd->addr;
+               txd->dst_addr = sgl->dma_address;
                src_buses = plchan->cd->periph_buses;
                dst_buses = pl08x->mem_buses;
        } else {
@@ -1536,13 +1548,33 @@ static void pl08x_ensure_on(struct pl08x_driver_data *pl08x)
        writel(val, pl08x->base + PL080_CONFIG);
 }
 
+static void pl08x_unmap_buffers(struct pl08x_txd *txd)
+{
+       struct device *dev = txd->tx.chan->device->dev;
+
+       if (!(txd->tx.flags & DMA_COMPL_SKIP_SRC_UNMAP)) {
+               if (txd->tx.flags & DMA_COMPL_SRC_UNMAP_SINGLE)
+                       dma_unmap_single(dev, txd->src_addr, txd->len,
+                               DMA_TO_DEVICE);
+               else
+                       dma_unmap_page(dev, txd->src_addr, txd->len,
+                               DMA_TO_DEVICE);
+       }
+       if (!(txd->tx.flags & DMA_COMPL_SKIP_DEST_UNMAP)) {
+               if (txd->tx.flags & DMA_COMPL_DEST_UNMAP_SINGLE)
+                       dma_unmap_single(dev, txd->dst_addr, txd->len,
+                               DMA_FROM_DEVICE);
+               else
+                       dma_unmap_page(dev, txd->dst_addr, txd->len,
+                               DMA_FROM_DEVICE);
+       }
+}
+
 static void pl08x_tasklet(unsigned long data)
 {
        struct pl08x_dma_chan *plchan = (struct pl08x_dma_chan *) data;
        struct pl08x_driver_data *pl08x = plchan->host;
        struct pl08x_txd *txd;
-       dma_async_tx_callback callback = NULL;
-       void *callback_param = NULL;
        unsigned long flags;
 
        spin_lock_irqsave(&plchan->lock, flags);
@@ -1551,32 +1583,31 @@ static void pl08x_tasklet(unsigned long data)
        plchan->at = NULL;
 
        if (txd) {
-               callback = txd->tx.callback;
-               callback_param = txd->tx.callback_param;
-
                /*
                 * Update last completed
                 */
                plchan->lc = txd->tx.cookie;
-
-               /*
-                * Free the descriptor
-                */
-               pl08x_free_txd(pl08x, txd);
        }
+
        /*
         * If a new descriptor is queued, set it up
         * plchan->at is NULL here
         */
-       if (!list_empty(&plchan->desc_list)) {
+       if (!list_empty(&plchan->pend_list)) {
                struct pl08x_txd *next;
 
-               next = list_first_entry(&plchan->desc_list,
+               next = list_first_entry(&plchan->pend_list,
                                        struct pl08x_txd,
                                        node);
                list_del(&next->node);
 
                pl08x_start_txd(plchan, next);
+       } else if (plchan->phychan_hold) {
+               /*
+                * This channel is still in use - we have a new txd being
+                * prepared and will soon be queued.  Don't give up the
+                * physical channel.
+                */
        } else {
                struct pl08x_dma_chan *waiting = NULL;
 
@@ -1604,6 +1635,7 @@ static void pl08x_tasklet(unsigned long data)
                                ret = prep_phy_channel(waiting,
                                                       waiting->waiting);
                                BUG_ON(ret);
+                               waiting->phychan_hold--;
                                waiting->state = PL08X_CHAN_RUNNING;
                                waiting->waiting = NULL;
                                pl08x_issue_pending(&waiting->chan);
@@ -1614,9 +1646,23 @@ static void pl08x_tasklet(unsigned long data)
 
        spin_unlock_irqrestore(&plchan->lock, flags);
 
-       /* Callback to signal completion */
-       if (callback)
-               callback(callback_param);
+       if (txd) {
+               dma_async_tx_callback callback = txd->tx.callback;
+               void *callback_param = txd->tx.callback_param;
+
+               /* Don't try to unmap buffers on slave channels */
+               if (!plchan->slave)
+                       pl08x_unmap_buffers(txd);
+
+               /* Free the descriptor */
+               spin_lock_irqsave(&plchan->lock, flags);
+               pl08x_free_txd(pl08x, txd);
+               spin_unlock_irqrestore(&plchan->lock, flags);
+
+               /* Callback to signal completion */
+               if (callback)
+                       callback(callback_param);
+       }
 }
 
 static irqreturn_t pl08x_irq(int irq, void *dev)
@@ -1719,7 +1765,7 @@ static int pl08x_dma_init_virtual_channels(struct pl08x_driver_data *pl08x,
                chan->lc = 0;
 
                spin_lock_init(&chan->lock);
-               INIT_LIST_HEAD(&chan->desc_list);
+               INIT_LIST_HEAD(&chan->pend_list);
                tasklet_init(&chan->tasklet, pl08x_tasklet,
                             (unsigned long) chan);