summaryrefslogtreecommitdiff
path: root/drivers/dma/edma.c
diff options
context:
space:
mode:
authorJoel Fernandes <joelf@ti.com>2013-08-29 18:05:43 -0500
committerVinod Koul <vinod.koul@intel.com>2013-09-04 18:38:46 +0530
commitc5f47990aad87deea42a330433fc57065ac1ade7 (patch)
treea383bb9d89946107753374ce30f6ac3105feee0a /drivers/dma/edma.c
parent96874b9a241c543e2f32d7bd7100af4416522cfb (diff)
dma: edma: Find missed events and issue them
In an effort to move to using Scatter gather lists of any size with EDMA as discussed at [1] instead of placing limitations on the driver, we work through the limitations of the EDMAC hardware to find missed events and issue them. The sequence of events that require this are: For the scenario where MAX slots for an EDMA channel is 3: SG1 -> SG2 -> SG3 -> SG4 -> SG5 -> SG6 -> Null The above SG list will have to be DMA'd in 2 sets: (1) SG1 -> SG2 -> SG3 -> Null (2) SG4 -> SG5 -> SG6 -> Null After (1) is succesfully transferred, the events from the MMC controller donot stop coming and are missed by the time we have setup the transfer for (2). So here, we catch the events missed as an error condition and issue them manually. In the second part of the patch, we make handle the NULL slot cases: For crypto IP, we continue to receive events even continuously in NULL slot, the setup of the next set of SG elements happens after the error handler executes. This is results in some recursion problems. Due to this, we continously receive error interrupts when we manually trigger an event from the error handler. We fix this, by first detecting if the Channel is currently transferring from a NULL slot or not, that's where the edma_read_slot in the error callback from interrupt handler comes in. With this we can determine if the set up of the next SG list has completed, and we manually trigger only in this case. If the setup has _not_ completed, we are still in NULL so we just set a missed flag and allow the manual triggerring to happen in edma_execute which will be eventually called. This fixes the above mentioned race conditions seen with the crypto drivers. [1] http://marc.info/?l=linux-omap&m=137416733628831&w=2 Signed-off-by: Joel Fernandes <joelf@ti.com> Signed-off-by: Vinod Koul <vinod.koul@intel.com>
Diffstat (limited to 'drivers/dma/edma.c')
-rw-r--r--drivers/dma/edma.c50
1 files changed, 49 insertions, 1 deletions
diff --git a/drivers/dma/edma.c b/drivers/dma/edma.c
index 4c1c258a5b54..2966ef06c477 100644
--- a/drivers/dma/edma.c
+++ b/drivers/dma/edma.c
@@ -70,6 +70,7 @@ struct edma_chan {
int ch_num;
bool alloced;
int slot[EDMA_MAX_SLOTS];
+ int missed;
struct dma_slave_config cfg;
};
@@ -170,6 +171,20 @@ static void edma_execute(struct edma_chan *echan)
dev_dbg(dev, "first transfer starting %d\n", echan->ch_num);
edma_start(echan->ch_num);
}
+
+ /*
+ * This happens due to setup times between intermediate transfers
+ * in long SG lists which have to be broken up into transfers of
+ * MAX_NR_SG
+ */
+ if (echan->missed) {
+ dev_dbg(dev, "missed event in execute detected\n");
+ edma_clean_channel(echan->ch_num);
+ edma_stop(echan->ch_num);
+ edma_start(echan->ch_num);
+ edma_trigger_channel(echan->ch_num);
+ echan->missed = 0;
+ }
}
static int edma_terminate_all(struct edma_chan *echan)
@@ -387,6 +402,7 @@ static void edma_callback(unsigned ch_num, u16 ch_status, void *data)
struct device *dev = echan->vchan.chan.device->dev;
struct edma_desc *edesc;
unsigned long flags;
+ struct edmacc_param p;
/* Pause the channel */
edma_pause(echan->ch_num);
@@ -412,7 +428,39 @@ static void edma_callback(unsigned ch_num, u16 ch_status, void *data)
break;
case DMA_CC_ERROR:
- dev_dbg(dev, "transfer error on channel %d\n", ch_num);
+ spin_lock_irqsave(&echan->vchan.lock, flags);
+
+ edma_read_slot(EDMA_CHAN_SLOT(echan->slot[0]), &p);
+
+ /*
+ * Issue later based on missed flag which will be sure
+ * to happen as:
+ * (1) we finished transmitting an intermediate slot and
+ * edma_execute is coming up.
+ * (2) or we finished current transfer and issue will
+ * call edma_execute.
+ *
+ * Important note: issuing can be dangerous here and
+ * lead to some nasty recursion when we are in a NULL
+ * slot. So we avoid doing so and set the missed flag.
+ */
+ if (p.a_b_cnt == 0 && p.ccnt == 0) {
+ dev_dbg(dev, "Error occurred, looks like slot is null, just setting miss\n");
+ echan->missed = 1;
+ } else {
+ /*
+ * The slot is already programmed but the event got
+ * missed, so its safe to issue it here.
+ */
+ dev_dbg(dev, "Error occurred but slot is non-null, TRIGGERING\n");
+ edma_clean_channel(echan->ch_num);
+ edma_stop(echan->ch_num);
+ edma_start(echan->ch_num);
+ edma_trigger_channel(echan->ch_num);
+ }
+
+ spin_unlock_irqrestore(&echan->vchan.lock, flags);
+
break;
default:
break;