* [PATCH v3 1/4] dma: mxs-dma: Cleanup interrupt handler
2013-10-04 10:44 [PATCH v3 0/4] dma: mxs-dma bugfixes and cleanup Markus Pargmann
@ 2013-10-04 10:44 ` Markus Pargmann
2013-10-04 10:44 ` [PATCH v3 2/4] dma: mxs-dma: Report correct residue for cyclic DMA Markus Pargmann
` (2 subsequent siblings)
3 siblings, 0 replies; 5+ messages in thread
From: Markus Pargmann @ 2013-10-04 10:44 UTC (permalink / raw)
To: linux-arm-kernel
The DMA interrupt handler uses its controll registers to handle all
available channel interrupts it can find.
This patch changes it to handle only one interrupt by directly mapping
irq number to channel. It also includes a cleanup of the ctrl-register
usage.
Signed-off-by: Markus Pargmann <mpa@pengutronix.de>
---
drivers/dma/mxs-dma.c | 96 ++++++++++++++++++++++++++++++++-------------------
1 file changed, 60 insertions(+), 36 deletions(-)
diff --git a/drivers/dma/mxs-dma.c b/drivers/dma/mxs-dma.c
index ccd13df..13198e5 100644
--- a/drivers/dma/mxs-dma.c
+++ b/drivers/dma/mxs-dma.c
@@ -27,6 +27,7 @@
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_dma.h>
+#include <linux/list.h>
#include <asm/irq.h>
@@ -272,58 +273,81 @@ static void mxs_dma_tasklet(unsigned long data)
mxs_chan->desc.callback(mxs_chan->desc.callback_param);
}
+static int mxs_dma_irq_to_chan(struct mxs_dma_engine *mxs_dma, int irq)
+{
+ int i;
+
+ for (i = 0; i != mxs_dma->nr_channels; ++i)
+ if (mxs_dma->mxs_chans[i].chan_irq == irq)
+ return i;
+
+ return -EINVAL;
+}
+
static irqreturn_t mxs_dma_int_handler(int irq, void *dev_id)
{
struct mxs_dma_engine *mxs_dma = dev_id;
- u32 stat1, stat2;
+ struct mxs_dma_chan *mxs_chan;
+ u32 completed;
+ u32 err;
+ int chan = mxs_dma_irq_to_chan(mxs_dma, irq);
+
+ if (chan < 0)
+ return IRQ_NONE;
/* completion status */
- stat1 = readl(mxs_dma->base + HW_APBHX_CTRL1);
- stat1 &= MXS_DMA_CHANNELS_MASK;
- writel(stat1, mxs_dma->base + HW_APBHX_CTRL1 + STMP_OFFSET_REG_CLR);
+ completed = readl(mxs_dma->base + HW_APBHX_CTRL1);
+ completed = (completed >> chan) & 0x1;
+
+ /* Clear interrupt */
+ writel((1 << chan),
+ mxs_dma->base + HW_APBHX_CTRL1 + STMP_OFFSET_REG_CLR);
/* error status */
- stat2 = readl(mxs_dma->base + HW_APBHX_CTRL2);
- writel(stat2, mxs_dma->base + HW_APBHX_CTRL2 + STMP_OFFSET_REG_CLR);
+ err = readl(mxs_dma->base + HW_APBHX_CTRL2);
+ err &= (1 << (MXS_DMA_CHANNELS + chan)) | (1 << chan);
+
+ /*
+ * error status bit is in the upper 16 bits, error irq bit in the lower
+ * 16 bits. We transform it into a simpler error code:
+ * err: 0x00 = no error, 0x01 = TERMINATION, 0x02 = BUS_ERROR
+ */
+ err = (err >> (MXS_DMA_CHANNELS + chan)) + (err >> chan);
+
+ /* Clear error irq */
+ writel((1 << chan),
+ mxs_dma->base + HW_APBHX_CTRL2 + STMP_OFFSET_REG_CLR);
/*
* When both completion and error of termination bits set at the
* same time, we do not take it as an error. IOW, it only becomes
- * an error we need to handle here in case of either it's (1) a bus
- * error or (2) a termination error with no completion.
+ * an error we need to handle here in case of either it's a bus
+ * error or a termination error with no completion. 0x01 is termination
+ * error, so we can subtract err & completed to get the real error case.
*/
- stat2 = ((stat2 >> MXS_DMA_CHANNELS) & stat2) | /* (1) */
- (~(stat2 >> MXS_DMA_CHANNELS) & stat2 & ~stat1); /* (2) */
-
- /* combine error and completion status for checking */
- stat1 = (stat2 << MXS_DMA_CHANNELS) | stat1;
- while (stat1) {
- int channel = fls(stat1) - 1;
- struct mxs_dma_chan *mxs_chan =
- &mxs_dma->mxs_chans[channel % MXS_DMA_CHANNELS];
-
- if (channel >= MXS_DMA_CHANNELS) {
- dev_dbg(mxs_dma->dma_device.dev,
- "%s: error in channel %d\n", __func__,
- channel - MXS_DMA_CHANNELS);
- mxs_chan->status = DMA_ERROR;
- mxs_dma_reset_chan(mxs_chan);
- } else {
- if (mxs_chan->flags & MXS_DMA_SG_LOOP)
- mxs_chan->status = DMA_IN_PROGRESS;
- else
- mxs_chan->status = DMA_SUCCESS;
- }
+ err -= err & completed;
- stat1 &= ~(1 << channel);
+ mxs_chan = &mxs_dma->mxs_chans[chan];
- if (mxs_chan->status == DMA_SUCCESS)
- dma_cookie_complete(&mxs_chan->desc);
-
- /* schedule tasklet on this channel */
- tasklet_schedule(&mxs_chan->tasklet);
+ if (err) {
+ dev_dbg(mxs_dma->dma_device.dev,
+ "%s: error in channel %d\n", __func__,
+ chan);
+ mxs_chan->status = DMA_ERROR;
+ mxs_dma_reset_chan(mxs_chan);
+ } else {
+ if (mxs_chan->flags & MXS_DMA_SG_LOOP)
+ mxs_chan->status = DMA_IN_PROGRESS;
+ else
+ mxs_chan->status = DMA_SUCCESS;
}
+ if (mxs_chan->status == DMA_SUCCESS)
+ dma_cookie_complete(&mxs_chan->desc);
+
+ /* schedule tasklet on this channel */
+ tasklet_schedule(&mxs_chan->tasklet);
+
return IRQ_HANDLED;
}
--
1.8.4.rc3
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH v3 2/4] dma: mxs-dma: Report correct residue for cyclic DMA
2013-10-04 10:44 [PATCH v3 0/4] dma: mxs-dma bugfixes and cleanup Markus Pargmann
2013-10-04 10:44 ` [PATCH v3 1/4] dma: mxs-dma: Cleanup interrupt handler Markus Pargmann
@ 2013-10-04 10:44 ` Markus Pargmann
2013-10-04 10:44 ` [PATCH v3 3/4] dma: mxs-dma: Fix channel reset hardware bug Markus Pargmann
2013-10-04 10:44 ` [PATCH v3 4/4] dma: mxs-dma: Update state after channel reset Markus Pargmann
3 siblings, 0 replies; 5+ messages in thread
From: Markus Pargmann @ 2013-10-04 10:44 UTC (permalink / raw)
To: linux-arm-kernel
Use the channel's buffer address register to calculate correct residue
value for tx_status.
---
drivers/dma/mxs-dma.c | 19 ++++++++++++++++++-
1 file changed, 18 insertions(+), 1 deletion(-)
diff --git a/drivers/dma/mxs-dma.c b/drivers/dma/mxs-dma.c
index 13198e5..8f48245 100644
--- a/drivers/dma/mxs-dma.c
+++ b/drivers/dma/mxs-dma.c
@@ -58,6 +58,8 @@
(((dma_is_apbh(d) && apbh_is_old(d)) ? 0x050 : 0x110) + (n) * 0x70)
#define HW_APBHX_CHn_SEMA(d, n) \
(((dma_is_apbh(d) && apbh_is_old(d)) ? 0x080 : 0x140) + (n) * 0x70)
+#define HW_APBHX_CHn_BAR(d, n) \
+ (((dma_is_apbh(d) && apbh_is_old(d)) ? 0x070 : 0x130) + (n) * 0x70)
/*
* ccw bits definitions
@@ -623,8 +625,23 @@ static enum dma_status mxs_dma_tx_status(struct dma_chan *chan,
dma_cookie_t cookie, struct dma_tx_state *txstate)
{
struct mxs_dma_chan *mxs_chan = to_mxs_dma_chan(chan);
+ struct mxs_dma_engine *mxs_dma = mxs_chan->mxs_dma;
+ u32 residue = 0;
+
+ if (mxs_chan->status == DMA_IN_PROGRESS &&
+ mxs_chan->flags & MXS_DMA_SG_LOOP) {
+ struct mxs_dma_ccw *last_ccw;
+ u32 bar;
+
+ last_ccw = &mxs_chan->ccw[mxs_chan->desc_count - 1];
+ residue = last_ccw->xfer_bytes + last_ccw->bufaddr;
+
+ bar = readl(mxs_dma->base +
+ HW_APBHX_CHn_BAR(mxs_dma, chan->chan_id));
+ residue -= bar;
+ }
- dma_set_tx_state(txstate, chan->completed_cookie, chan->cookie, 0);
+ dma_set_tx_state(txstate, chan->completed_cookie, chan->cookie, residue);
return mxs_chan->status;
}
--
1.8.4.rc3
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH v3 3/4] dma: mxs-dma: Fix channel reset hardware bug
2013-10-04 10:44 [PATCH v3 0/4] dma: mxs-dma bugfixes and cleanup Markus Pargmann
2013-10-04 10:44 ` [PATCH v3 1/4] dma: mxs-dma: Cleanup interrupt handler Markus Pargmann
2013-10-04 10:44 ` [PATCH v3 2/4] dma: mxs-dma: Report correct residue for cyclic DMA Markus Pargmann
@ 2013-10-04 10:44 ` Markus Pargmann
2013-10-04 10:44 ` [PATCH v3 4/4] dma: mxs-dma: Update state after channel reset Markus Pargmann
3 siblings, 0 replies; 5+ messages in thread
From: Markus Pargmann @ 2013-10-04 10:44 UTC (permalink / raw)
To: linux-arm-kernel
This is no official errata, but I noticed that the channel reset may
stop working if the DMA state engine is in the READ_FLUSH state.
This patch uses the channel debug1 register to wait for the DMA
statemachine to leave the READ_FLUSH state. After that we can continue
to reset the channel.
Tested on i.MX28.
Signed-off-by: Markus Pargmann <mpa@pengutronix.de>
---
drivers/dma/mxs-dma.c | 28 ++++++++++++++++++++++++++--
1 file changed, 26 insertions(+), 2 deletions(-)
diff --git a/drivers/dma/mxs-dma.c b/drivers/dma/mxs-dma.c
index 8f48245..98de9a7 100644
--- a/drivers/dma/mxs-dma.c
+++ b/drivers/dma/mxs-dma.c
@@ -60,6 +60,7 @@
(((dma_is_apbh(d) && apbh_is_old(d)) ? 0x080 : 0x140) + (n) * 0x70)
#define HW_APBHX_CHn_BAR(d, n) \
(((dma_is_apbh(d) && apbh_is_old(d)) ? 0x070 : 0x130) + (n) * 0x70)
+#define HW_APBX_CHn_DEBUG1(d, n) (0x150 + (n) * 0x70)
/*
* ccw bits definitions
@@ -204,12 +205,35 @@ static void mxs_dma_reset_chan(struct mxs_dma_chan *mxs_chan)
struct mxs_dma_engine *mxs_dma = mxs_chan->mxs_dma;
int chan_id = mxs_chan->chan.chan_id;
- if (dma_is_apbh(mxs_dma) && apbh_is_old(mxs_dma))
+ if (dma_is_apbh(mxs_dma) && apbh_is_old(mxs_dma)) {
writel(1 << (chan_id + BP_APBH_CTRL0_RESET_CHANNEL),
mxs_dma->base + HW_APBHX_CTRL0 + STMP_OFFSET_REG_SET);
- else
+ } else {
+ unsigned long elapsed = 0;
+ const unsigned long max_wait = 50000; /* 50ms */
+ void __iomem *reg_dbg1 = mxs_dma->base +
+ HW_APBX_CHn_DEBUG1(mxs_dma, chan_id);
+
+ /*
+ * On i.MX28 APBX, the DMA channel can stop working if we reset
+ * the channel while it is in READ_FLUSH (0x08) state.
+ * We wait here until we leave the state. Then we trigger the
+ * reset. Waiting a maximum of 50ms, the kernel shouldn't crash
+ * because of this.
+ */
+ while ((readl(reg_dbg1) & 0xf) == 0x8 && elapsed < max_wait) {
+ udelay(100);
+ elapsed += 100;
+ }
+
+ if (elapsed >= max_wait)
+ dev_err(&mxs_chan->mxs_dma->pdev->dev,
+ "Failed waiting for the DMA channel %d to leave state READ_FLUSH, trying to reset channel in READ_FLUSH state now\n", chan_id);
+
+
writel(1 << (chan_id + BP_APBHX_CHANNEL_CTRL_RESET_CHANNEL),
mxs_dma->base + HW_APBHX_CHANNEL_CTRL + STMP_OFFSET_REG_SET);
+ }
}
static void mxs_dma_enable_chan(struct mxs_dma_chan *mxs_chan)
--
1.8.4.rc3
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH v3 4/4] dma: mxs-dma: Update state after channel reset
2013-10-04 10:44 [PATCH v3 0/4] dma: mxs-dma bugfixes and cleanup Markus Pargmann
` (2 preceding siblings ...)
2013-10-04 10:44 ` [PATCH v3 3/4] dma: mxs-dma: Fix channel reset hardware bug Markus Pargmann
@ 2013-10-04 10:44 ` Markus Pargmann
3 siblings, 0 replies; 5+ messages in thread
From: Markus Pargmann @ 2013-10-04 10:44 UTC (permalink / raw)
To: linux-arm-kernel
After a channel reset, the channel stops running automatically. The
state update was missing so that a channel perperation right after a
channel reset failed.
Signed-off-by: Markus Pargmann <mpa@pengutronix.de>
---
drivers/dma/mxs-dma.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/drivers/dma/mxs-dma.c b/drivers/dma/mxs-dma.c
index 98de9a7..e284395 100644
--- a/drivers/dma/mxs-dma.c
+++ b/drivers/dma/mxs-dma.c
@@ -234,6 +234,8 @@ static void mxs_dma_reset_chan(struct mxs_dma_chan *mxs_chan)
writel(1 << (chan_id + BP_APBHX_CHANNEL_CTRL_RESET_CHANNEL),
mxs_dma->base + HW_APBHX_CHANNEL_CTRL + STMP_OFFSET_REG_SET);
}
+
+ mxs_chan->status = DMA_SUCCESS;
}
static void mxs_dma_enable_chan(struct mxs_dma_chan *mxs_chan)
@@ -361,7 +363,7 @@ static irqreturn_t mxs_dma_int_handler(int irq, void *dev_id)
chan);
mxs_chan->status = DMA_ERROR;
mxs_dma_reset_chan(mxs_chan);
- } else {
+ } else if (mxs_chan->status != DMA_SUCCESS) {
if (mxs_chan->flags & MXS_DMA_SG_LOOP)
mxs_chan->status = DMA_IN_PROGRESS;
else
--
1.8.4.rc3
^ permalink raw reply related [flat|nested] 5+ messages in thread