]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - drivers/dma/ep93xx_dma.c
Merge tag 'drivers' of git://git.kernel.org/pub/scm/linux/kernel/git/arm/arm-soc
[karo-tx-linux.git] / drivers / dma / ep93xx_dma.c
index f6e9b572b998919ee117cf154e560788da439de3..c64917ec313dc25d501ad41312fa8e898df8e003 100644 (file)
@@ -71,6 +71,7 @@
 #define M2M_CONTROL_TM_SHIFT           13
 #define M2M_CONTROL_TM_TX              (1 << M2M_CONTROL_TM_SHIFT)
 #define M2M_CONTROL_TM_RX              (2 << M2M_CONTROL_TM_SHIFT)
+#define M2M_CONTROL_NFBINT             BIT(21)
 #define M2M_CONTROL_RSS_SHIFT          22
 #define M2M_CONTROL_RSS_SSPRX          (1 << M2M_CONTROL_RSS_SHIFT)
 #define M2M_CONTROL_RSS_SSPTX          (2 << M2M_CONTROL_RSS_SHIFT)
 #define M2M_CONTROL_PWSC_SHIFT         25
 
 #define M2M_INTERRUPT                  0x0004
-#define M2M_INTERRUPT_DONEINT          BIT(1)
+#define M2M_INTERRUPT_MASK             6
+
+#define M2M_STATUS                     0x000c
+#define M2M_STATUS_CTL_SHIFT           1
+#define M2M_STATUS_CTL_IDLE            (0 << M2M_STATUS_CTL_SHIFT)
+#define M2M_STATUS_CTL_STALL           (1 << M2M_STATUS_CTL_SHIFT)
+#define M2M_STATUS_CTL_MEMRD           (2 << M2M_STATUS_CTL_SHIFT)
+#define M2M_STATUS_CTL_MEMWR           (3 << M2M_STATUS_CTL_SHIFT)
+#define M2M_STATUS_CTL_BWCWAIT         (4 << M2M_STATUS_CTL_SHIFT)
+#define M2M_STATUS_CTL_MASK            (7 << M2M_STATUS_CTL_SHIFT)
+#define M2M_STATUS_BUF_SHIFT           4
+#define M2M_STATUS_BUF_NO              (0 << M2M_STATUS_BUF_SHIFT)
+#define M2M_STATUS_BUF_ON              (1 << M2M_STATUS_BUF_SHIFT)
+#define M2M_STATUS_BUF_NEXT            (2 << M2M_STATUS_BUF_SHIFT)
+#define M2M_STATUS_BUF_MASK            (3 << M2M_STATUS_BUF_SHIFT)
+#define M2M_STATUS_DONE                        BIT(6)
 
 #define M2M_BCR0                       0x0010
 #define M2M_BCR1                       0x0014
@@ -426,15 +442,6 @@ static int m2p_hw_interrupt(struct ep93xx_dma_chan *edmac)
 
 /*
  * M2M DMA implementation
- *
- * For the M2M transfers we don't use NFB at all. This is because it simply
- * doesn't work well with memcpy transfers. When you submit both buffers it is
- * extremely unlikely that you get an NFB interrupt, but it instead reports
- * DONE interrupt and both buffers are already transferred which means that we
- * weren't able to update the next buffer.
- *
- * So for now we "simulate" NFB by just submitting buffer after buffer
- * without double buffering.
  */
 
 static int m2m_hw_setup(struct ep93xx_dma_chan *edmac)
@@ -543,6 +550,11 @@ static void m2m_hw_submit(struct ep93xx_dma_chan *edmac)
        m2m_fill_desc(edmac);
        control |= M2M_CONTROL_DONEINT;
 
+       if (ep93xx_dma_advance_active(edmac)) {
+               m2m_fill_desc(edmac);
+               control |= M2M_CONTROL_NFBINT;
+       }
+
        /*
         * Now we can finally enable the channel. For M2M channel this must be
         * done _after_ the BCRx registers are programmed.
@@ -560,32 +572,89 @@ static void m2m_hw_submit(struct ep93xx_dma_chan *edmac)
        }
 }
 
+/*
+ * According to EP93xx User's Guide, we should receive DONE interrupt when all
+ * M2M DMA controller transactions complete normally. This is not always the
+ * case - sometimes EP93xx M2M DMA asserts DONE interrupt when the DMA channel
+ * is still running (channel Buffer FSM in DMA_BUF_ON state, and channel
+ * Control FSM in DMA_MEM_RD state, observed at least in IDE-DMA operation).
+ * In effect, disabling the channel when only DONE bit is set could stop
+ * currently running DMA transfer. To avoid this, we use Buffer FSM and
+ * Control FSM to check current state of DMA channel.
+ */
 static int m2m_hw_interrupt(struct ep93xx_dma_chan *edmac)
 {
+       u32 status = readl(edmac->regs + M2M_STATUS);
+       u32 ctl_fsm = status & M2M_STATUS_CTL_MASK;
+       u32 buf_fsm = status & M2M_STATUS_BUF_MASK;
+       bool done = status & M2M_STATUS_DONE;
+       bool last_done;
        u32 control;
+       struct ep93xx_dma_desc *desc;
 
-       if (!(readl(edmac->regs + M2M_INTERRUPT) & M2M_INTERRUPT_DONEINT))
+       /* Accept only DONE and NFB interrupts */
+       if (!(readl(edmac->regs + M2M_INTERRUPT) & M2M_INTERRUPT_MASK))
                return INTERRUPT_UNKNOWN;
 
-       /* Clear the DONE bit */
-       writel(0, edmac->regs + M2M_INTERRUPT);
+       if (done) {
+               /* Clear the DONE bit */
+               writel(0, edmac->regs + M2M_INTERRUPT);
+       }
 
-       /* Disable interrupts and the channel */
-       control = readl(edmac->regs + M2M_CONTROL);
-       control &= ~(M2M_CONTROL_DONEINT | M2M_CONTROL_ENABLE);
-       writel(control, edmac->regs + M2M_CONTROL);
+       /*
+        * Check whether we are done with descriptors or not. This, together
+        * with DMA channel state, determines action to take in interrupt.
+        */
+       desc = ep93xx_dma_get_active(edmac);
+       last_done = !desc || desc->txd.cookie;
 
        /*
-        * Since we only get DONE interrupt we have to find out ourselves
-        * whether there still is something to process. So we try to advance
-        * the chain an see whether it succeeds.
+        * Use M2M DMA Buffer FSM and Control FSM to check current state of
+        * DMA channel. Using DONE and NFB bits from channel status register
+        * or bits from channel interrupt register is not reliable.
         */
-       if (ep93xx_dma_advance_active(edmac)) {
-               edmac->edma->hw_submit(edmac);
-               return INTERRUPT_NEXT_BUFFER;
+       if (!last_done &&
+           (buf_fsm == M2M_STATUS_BUF_NO ||
+            buf_fsm == M2M_STATUS_BUF_ON)) {
+               /*
+                * Two buffers are ready for update when Buffer FSM is in
+                * DMA_NO_BUF state. Only one buffer can be prepared without
+                * disabling the channel or polling the DONE bit.
+                * To simplify things, always prepare only one buffer.
+                */
+               if (ep93xx_dma_advance_active(edmac)) {
+                       m2m_fill_desc(edmac);
+                       if (done && !edmac->chan.private) {
+                               /* Software trigger for memcpy channel */
+                               control = readl(edmac->regs + M2M_CONTROL);
+                               control |= M2M_CONTROL_START;
+                               writel(control, edmac->regs + M2M_CONTROL);
+                       }
+                       return INTERRUPT_NEXT_BUFFER;
+               } else {
+                       last_done = true;
+               }
+       }
+
+       /*
+        * Disable the channel only when Buffer FSM is in DMA_NO_BUF state
+        * and Control FSM is in DMA_STALL state.
+        */
+       if (last_done &&
+           buf_fsm == M2M_STATUS_BUF_NO &&
+           ctl_fsm == M2M_STATUS_CTL_STALL) {
+               /* Disable interrupts and the channel */
+               control = readl(edmac->regs + M2M_CONTROL);
+               control &= ~(M2M_CONTROL_DONEINT | M2M_CONTROL_NFBINT
+                           | M2M_CONTROL_ENABLE);
+               writel(control, edmac->regs + M2M_CONTROL);
+               return INTERRUPT_DONE;
        }
 
-       return INTERRUPT_DONE;
+       /*
+        * Nothing to do this time.
+        */
+       return INTERRUPT_NEXT_BUFFER;
 }
 
 /*