From: prylowski@metasoft.pl (Rafal Prylowski)
To: linux-arm-kernel@lists.infradead.org
Subject: [PATCH] ep93xx: Implement double buffering for M2M DMA channels
Date: Tue, 20 Mar 2012 09:09:26 +0100 [thread overview]
Message-ID: <4F683B36.8090101@metasoft.pl> (raw)
Implement double buffering for M2M DMA channels.
Signed-off-by: Rafal Prylowski <prylowski@metasoft.pl>
Index: slave-dma/drivers/dma/ep93xx_dma.c
===================================================================
--- slave-dma.orig/drivers/dma/ep93xx_dma.c
+++ slave-dma/drivers/dma/ep93xx_dma.c
@@ -69,6 +69,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)
@@ -77,7 +78,23 @@
#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 +443,6 @@ static int m2p_hw_interrupt(struct ep93x
/*
* 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 +551,11 @@ static void m2m_hw_submit(struct ep93xx_
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 +573,85 @@ static void m2m_hw_submit(struct ep93xx_
}
}
+/*
+ * 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@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;
u32 control;
- 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.
+ */
+ last = list_first_entry(edmac->active.next,
+ struct ep93xx_dma_desc, node)->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 was proven not to be
+ * reliable.
*/
- if (ep93xx_dma_advance_active(edmac)) {
- edmac->edma->hw_submit(edmac);
+ if (!last &&
+ (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.
+ */
+ 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;
}
- return INTERRUPT_DONE;
+ /*
+ * Disable the channel only when Buffer FSM is in DMA_NO_BUF state
+ * and Control FSM is in DMA_STALL state.
+ */
+ if (last &&
+ 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;
+ }
+
+ /*
+ * Nothing to do this time.
+ */
+ return INTERRUPT_NEXT_BUFFER;
}
/*
WARNING: multiple messages have this Message-ID (diff)
From: Rafal Prylowski <prylowski@metasoft.pl>
To: "linux-arm-kernel@lists.infradead.org"
<linux-arm-kernel@lists.infradead.org>
Cc: "linux-kernel@vger.kernel.org" <linux-kernel@vger.kernel.org>,
"vinod.koul@intel.com" <vinod.koul@intel.com>,
Mika Westerberg <mika.westerberg@iki.fi>,
H Hartley Sweeten <hartleys@visionengravers.com>,
"rmallon@gmail.com" <rmallon@gmail.com>
Subject: [PATCH] ep93xx: Implement double buffering for M2M DMA channels
Date: Tue, 20 Mar 2012 09:09:26 +0100 [thread overview]
Message-ID: <4F683B36.8090101@metasoft.pl> (raw)
Implement double buffering for M2M DMA channels.
Signed-off-by: Rafal Prylowski <prylowski@metasoft.pl>
Index: slave-dma/drivers/dma/ep93xx_dma.c
===================================================================
--- slave-dma.orig/drivers/dma/ep93xx_dma.c
+++ slave-dma/drivers/dma/ep93xx_dma.c
@@ -69,6 +69,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)
@@ -77,7 +78,23 @@
#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 +443,6 @@ static int m2p_hw_interrupt(struct ep93x
/*
* 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 +551,11 @@ static void m2m_hw_submit(struct ep93xx_
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 +573,85 @@ static void m2m_hw_submit(struct ep93xx_
}
}
+/*
+ * 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;
u32 control;
- 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.
+ */
+ last = list_first_entry(edmac->active.next,
+ struct ep93xx_dma_desc, node)->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 was proven not to be
+ * reliable.
*/
- if (ep93xx_dma_advance_active(edmac)) {
- edmac->edma->hw_submit(edmac);
+ if (!last &&
+ (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.
+ */
+ 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;
}
- return INTERRUPT_DONE;
+ /*
+ * Disable the channel only when Buffer FSM is in DMA_NO_BUF state
+ * and Control FSM is in DMA_STALL state.
+ */
+ if (last &&
+ 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;
+ }
+
+ /*
+ * Nothing to do this time.
+ */
+ return INTERRUPT_NEXT_BUFFER;
}
/*
next reply other threads:[~2012-03-20 8:09 UTC|newest]
Thread overview: 70+ messages / expand[flat|nested] mbox.gz Atom feed top
2012-03-20 8:09 Rafal Prylowski [this message]
2012-03-20 8:09 ` [PATCH] ep93xx: Implement double buffering for M2M DMA channels Rafal Prylowski
2012-03-21 7:07 ` Mika Westerberg
2012-03-21 7:07 ` Mika Westerberg
2012-03-21 7:47 ` Rafal Prylowski
2012-03-21 7:47 ` Rafal Prylowski
2012-03-21 19:33 ` Mika Westerberg
2012-03-21 19:33 ` Mika Westerberg
2012-03-21 17:12 ` H Hartley Sweeten
2012-03-21 17:12 ` H Hartley Sweeten
2012-03-21 19:32 ` Mika Westerberg
2012-03-21 19:32 ` Mika Westerberg
2012-03-22 0:47 ` H Hartley Sweeten
2012-03-22 0:47 ` H Hartley Sweeten
2012-03-22 7:37 ` Mika Westerberg
2012-03-22 7:37 ` Mika Westerberg
2012-03-22 18:52 ` H Hartley Sweeten
2012-03-22 18:52 ` H Hartley Sweeten
2012-03-22 20:03 ` Mika Westerberg
2012-03-22 20:03 ` Mika Westerberg
2012-03-22 21:36 ` H Hartley Sweeten
2012-03-22 21:36 ` H Hartley Sweeten
2012-03-22 23:56 ` H Hartley Sweeten
2012-03-22 23:56 ` H Hartley Sweeten
2012-03-23 7:00 ` Mika Westerberg
2012-03-23 7:00 ` Mika Westerberg
2012-03-22 10:16 ` Rafal Prylowski
2012-03-22 10:16 ` Rafal Prylowski
2012-03-21 19:38 ` Mika Westerberg
2012-03-21 19:38 ` Mika Westerberg
2012-03-23 2:19 ` H Hartley Sweeten
2012-03-23 2:19 ` H Hartley Sweeten
2012-03-23 7:04 ` Mika Westerberg
2012-03-23 7:04 ` Mika Westerberg
2012-03-23 16:09 ` H Hartley Sweeten
2012-03-23 16:09 ` H Hartley Sweeten
2012-03-24 7:32 ` Mika Westerberg
2012-03-24 7:32 ` Mika Westerberg
2012-03-26 6:44 ` Rafal Prylowski
2012-03-26 6:44 ` Rafal Prylowski
2012-03-29 22:33 ` H Hartley Sweeten
2012-03-29 22:33 ` H Hartley Sweeten
2012-04-01 18:49 ` Mika Westerberg
2012-04-01 18:49 ` Mika Westerberg
2012-04-10 17:28 ` Mika Westerberg
2012-04-10 17:28 ` Mika Westerberg
2012-04-10 17:55 ` H Hartley Sweeten
2012-04-10 17:55 ` H Hartley Sweeten
2012-04-11 7:18 ` Rafal Prylowski
2012-04-11 7:18 ` Rafal Prylowski
2012-04-16 18:59 ` H Hartley Sweeten
2012-04-16 18:59 ` H Hartley Sweeten
2012-04-17 7:15 ` Rafal Prylowski
2012-04-17 7:15 ` Rafal Prylowski
2012-04-17 15:46 ` H Hartley Sweeten
2012-04-17 15:46 ` H Hartley Sweeten
2012-04-17 20:51 ` H Hartley Sweeten
2012-04-17 20:51 ` H Hartley Sweeten
2012-04-18 16:41 ` Rafal Prylowski
2012-04-18 16:41 ` Rafal Prylowski
2012-04-18 17:01 ` H Hartley Sweeten
2012-04-18 17:01 ` H Hartley Sweeten
2012-03-22 0:57 ` Ryan Mallon
2012-03-22 0:57 ` Ryan Mallon
2012-03-22 10:00 ` Rafal Prylowski
2012-03-22 10:00 ` Rafal Prylowski
2012-03-22 13:14 ` Sergei Shtylyov
2012-03-22 13:14 ` Sergei Shtylyov
2012-03-22 14:13 ` Rafal Prylowski
2012-03-22 14:13 ` Rafal Prylowski
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=4F683B36.8090101@metasoft.pl \
--to=prylowski@metasoft.pl \
--cc=linux-arm-kernel@lists.infradead.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.