Some controllers have the reduced functionality where the LLP multi block
transfers are not supported. This patch introduces a support of such
controllers. In case of memory copy or scatter-gather lists it emulates LLP
transfers via bunch of the regular single block ones.

Signed-off-by: Andy Shevchenko <[email protected]>
---
 drivers/dma/dw_dmac.c      |   83 +++++++++++++++++++++++++++++++++++++-------
 drivers/dma/dw_dmac_regs.h |    5 +++
 2 files changed, 75 insertions(+), 13 deletions(-)

diff --git a/drivers/dma/dw_dmac.c b/drivers/dma/dw_dmac.c
index 00958ad..fa23711 100644
--- a/drivers/dma/dw_dmac.c
+++ b/drivers/dma/dw_dmac.c
@@ -229,10 +229,29 @@ static inline void dwc_chan_disable(struct dw_dma *dw, 
struct dw_dma_chan *dwc)
 
 /*----------------------------------------------------------------------*/
 
+/* Perform single block transfer */
+static inline void dwc_do_single_block(struct dw_dma_chan *dwc,
+                                      struct dw_desc *desc)
+{
+       struct dw_dma   *dw = to_dw_dma(dwc->chan.device);
+       u32             ctllo;
+
+       /* Software emulation of LLP mode relies on interrupts to continue
+        * multi block transfer. */
+       ctllo = desc->lli.ctllo | DWC_CTLL_INT_EN;
+
+       channel_writel(dwc, SAR, desc->lli.sar);
+       channel_writel(dwc, DAR, desc->lli.dar);
+       channel_writel(dwc, CTL_LO, ctllo);
+       channel_writel(dwc, CTL_HI, desc->lli.ctlhi);
+       channel_set_bit(dw, CH_EN, dwc->mask);
+}
+
 /* Called with dwc->lock held and bh disabled */
 static void dwc_dostart(struct dw_dma_chan *dwc, struct dw_desc *first)
 {
        struct dw_dma   *dw = to_dw_dma(dwc->chan.device);
+       unsigned long   was_soft_llp;
 
        /* ASSERT:  channel is idle */
        if (dma_readl(dw, CH_EN) & dwc->mask) {
@@ -244,6 +263,26 @@ static void dwc_dostart(struct dw_dma_chan *dwc, struct 
dw_desc *first)
                return;
        }
 
+       if (dwc->nollp) {
+               was_soft_llp = test_and_set_bit(DW_DMA_IS_SOFT_LLP,
+                                               &dwc->flags);
+               if (was_soft_llp) {
+                       dev_err(chan2dev(&dwc->chan),
+                               "BUG: Attempted to start new LLP transfer "
+                               "inside ongoing one\n");
+                       return;
+               }
+
+               dwc_initialize(dwc);
+
+               dwc->tx_list = &first->tx_list;
+               dwc->tx_node_active = first->tx_list.next;
+
+               dwc_do_single_block(dwc, first);
+
+               return;
+       }
+
        dwc_initialize(dwc);
 
        channel_writel(dwc, LLP, first->txd.phys);
@@ -555,8 +594,36 @@ static void dw_dma_tasklet(unsigned long data)
                        dwc_handle_cyclic(dw, dwc, status_err, status_xfer);
                else if (status_err & (1 << i))
                        dwc_handle_error(dw, dwc);
-               else if (status_xfer & (1 << i))
+               else if (status_xfer & (1 << i)) {
+                       unsigned long flags;
+
+                       spin_lock_irqsave(&dwc->lock, flags);
+                       if (test_bit(DW_DMA_IS_SOFT_LLP, &dwc->flags)) {
+                               if (dwc->tx_node_active != dwc->tx_list) {
+                                       struct dw_desc *desc =
+                                               list_entry(dwc->tx_node_active,
+                                                          struct dw_desc,
+                                                          desc_node);
+
+                                       dma_writel(dw, CLEAR.XFER, dwc->mask);
+
+                                       /* move pointer to next descriptor */
+                                       dwc->tx_node_active =
+                                               dwc->tx_node_active->next;
+
+                                       dwc_do_single_block(dwc, desc);
+
+                                       spin_unlock_irqrestore(&dwc->lock, 
flags);
+                                       continue;
+                               } else {
+                                       /* we are done here */
+                                       clear_bit(DW_DMA_IS_SOFT_LLP, 
&dwc->flags);
+                               }
+                       }
+                       spin_unlock_irqrestore(&dwc->lock, flags);
+
                        dwc_scan_descriptors(dw, dwc);
+               }
        }
 
        /*
@@ -647,12 +714,6 @@ dwc_prep_dma_memcpy(struct dma_chan *chan, dma_addr_t 
dest, dma_addr_t src,
        unsigned int            dst_width;
        u32                     ctllo;
 
-       if (dwc->nollp) {
-               dev_dbg(chan2dev(&dwc->chan),
-                               "channel doesn't support LLP transfers\n");
-               return NULL;
-       }
-
        dev_vdbg(chan2dev(chan),
                        "%s: d0x%llx s0x%llx l0x%zx f0x%lx\n", __func__,
                        (unsigned long long)dest, (unsigned long long)src,
@@ -747,12 +808,6 @@ dwc_prep_slave_sg(struct dma_chan *chan, struct 
scatterlist *sgl,
        if (unlikely(!dws || !sg_len))
                return NULL;
 
-       if (dwc->nollp) {
-               dev_dbg(chan2dev(&dwc->chan),
-                               "channel doesn't support LLP transfers\n");
-               return NULL;
-       }
-
        prev = first = NULL;
 
        switch (direction) {
@@ -972,6 +1027,8 @@ static int dwc_control(struct dma_chan *chan, enum 
dma_ctrl_cmd cmd,
        } else if (cmd == DMA_TERMINATE_ALL) {
                spin_lock_irqsave(&dwc->lock, flags);
 
+               clear_bit(DW_DMA_IS_SOFT_LLP, &dwc->flags);
+
                dwc_chan_disable(dw, dwc);
 
                dwc->paused = false;
diff --git a/drivers/dma/dw_dmac_regs.h b/drivers/dma/dw_dmac_regs.h
index b66a716..297ab18 100644
--- a/drivers/dma/dw_dmac_regs.h
+++ b/drivers/dma/dw_dmac_regs.h
@@ -174,6 +174,7 @@ struct dw_dma_regs {
 
 enum dw_dmac_flags {
        DW_DMA_IS_CYCLIC = 0,
+       DW_DMA_IS_SOFT_LLP = 1,
 };
 
 struct dw_dma_chan {
@@ -184,6 +185,10 @@ struct dw_dma_chan {
        bool                    paused;
        bool                    initialized;
 
+       /* software emulation of the LLP transfers */
+       struct list_head        *tx_list;
+       struct list_head        *tx_node_active;
+
        spinlock_t              lock;
 
        /* these other elements are all protected by lock */
-- 
1.7.10.4

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Reply via email to