diff options
-rw-r--r-- | drivers/dma/edma.c | 50 |
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 { | |||
70 | int ch_num; | 70 | int ch_num; |
71 | bool alloced; | 71 | bool alloced; |
72 | int slot[EDMA_MAX_SLOTS]; | 72 | int slot[EDMA_MAX_SLOTS]; |
73 | int missed; | ||
73 | struct dma_slave_config cfg; | 74 | struct dma_slave_config cfg; |
74 | }; | 75 | }; |
75 | 76 | ||
@@ -170,6 +171,20 @@ static void edma_execute(struct edma_chan *echan) | |||
170 | dev_dbg(dev, "first transfer starting %d\n", echan->ch_num); | 171 | dev_dbg(dev, "first transfer starting %d\n", echan->ch_num); |
171 | edma_start(echan->ch_num); | 172 | edma_start(echan->ch_num); |
172 | } | 173 | } |
174 | |||
175 | /* | ||
176 | * This happens due to setup times between intermediate transfers | ||
177 | * in long SG lists which have to be broken up into transfers of | ||
178 | * MAX_NR_SG | ||
179 | */ | ||
180 | if (echan->missed) { | ||
181 | dev_dbg(dev, "missed event in execute detected\n"); | ||
182 | edma_clean_channel(echan->ch_num); | ||
183 | edma_stop(echan->ch_num); | ||
184 | edma_start(echan->ch_num); | ||
185 | edma_trigger_channel(echan->ch_num); | ||
186 | echan->missed = 0; | ||
187 | } | ||
173 | } | 188 | } |
174 | 189 | ||
175 | static int edma_terminate_all(struct edma_chan *echan) | 190 | 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) | |||
387 | struct device *dev = echan->vchan.chan.device->dev; | 402 | struct device *dev = echan->vchan.chan.device->dev; |
388 | struct edma_desc *edesc; | 403 | struct edma_desc *edesc; |
389 | unsigned long flags; | 404 | unsigned long flags; |
405 | struct edmacc_param p; | ||
390 | 406 | ||
391 | /* Pause the channel */ | 407 | /* Pause the channel */ |
392 | edma_pause(echan->ch_num); | 408 | edma_pause(echan->ch_num); |
@@ -412,7 +428,39 @@ static void edma_callback(unsigned ch_num, u16 ch_status, void *data) | |||
412 | 428 | ||
413 | break; | 429 | break; |
414 | case DMA_CC_ERROR: | 430 | case DMA_CC_ERROR: |
415 | dev_dbg(dev, "transfer error on channel %d\n", ch_num); | 431 | spin_lock_irqsave(&echan->vchan.lock, flags); |
432 | |||
433 | edma_read_slot(EDMA_CHAN_SLOT(echan->slot[0]), &p); | ||
434 | |||
435 | /* | ||
436 | * Issue later based on missed flag which will be sure | ||
437 | * to happen as: | ||
438 | * (1) we finished transmitting an intermediate slot and | ||
439 | * edma_execute is coming up. | ||
440 | * (2) or we finished current transfer and issue will | ||
441 | * call edma_execute. | ||
442 | * | ||
443 | * Important note: issuing can be dangerous here and | ||
444 | * lead to some nasty recursion when we are in a NULL | ||
445 | * slot. So we avoid doing so and set the missed flag. | ||
446 | */ | ||
447 | if (p.a_b_cnt == 0 && p.ccnt == 0) { | ||
448 | dev_dbg(dev, "Error occurred, looks like slot is null, just setting miss\n"); | ||
449 | echan->missed = 1; | ||
450 | } else { | ||
451 | /* | ||
452 | * The slot is already programmed but the event got | ||
453 | * missed, so its safe to issue it here. | ||
454 | */ | ||
455 | dev_dbg(dev, "Error occurred but slot is non-null, TRIGGERING\n"); | ||
456 | edma_clean_channel(echan->ch_num); | ||
457 | edma_stop(echan->ch_num); | ||
458 | edma_start(echan->ch_num); | ||
459 | edma_trigger_channel(echan->ch_num); | ||
460 | } | ||
461 | |||
462 | spin_unlock_irqrestore(&echan->vchan.lock, flags); | ||
463 | |||
416 | break; | 464 | break; |
417 | default: | 465 | default: |
418 | break; | 466 | break; |