summaryrefslogtreecommitdiff
path: root/drivers/dma/imx-sdma.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/dma/imx-sdma.c')
-rw-r--r--drivers/dma/imx-sdma.c60
1 files changed, 56 insertions, 4 deletions
diff --git a/drivers/dma/imx-sdma.c b/drivers/dma/imx-sdma.c
index e93c4ea1a1a8..9de94344a97f 100644
--- a/drivers/dma/imx-sdma.c
+++ b/drivers/dma/imx-sdma.c
@@ -32,6 +32,7 @@
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <linux/dmaengine.h>
+#include <linux/delay.h>
#include <asm/irq.h>
#include <mach/sdma.h>
@@ -278,6 +279,7 @@ struct sdma_channel {
enum dma_status status;
unsigned int chn_count;
unsigned int chn_real_count;
+ unsigned int irq_handling;
};
#define MAX_DMA_CHANNELS 32
@@ -324,6 +326,7 @@ struct sdma_engine {
struct dma_device dma_device;
struct clk *clk;
struct sdma_script_start_addrs *script_addrs;
+ spinlock_t irq_reg_lock;
};
#define SDMA_H_CONFIG_DSPDMA (1 << 12) /* indicates if the DSPDMA is used */
@@ -537,18 +540,33 @@ static void mxc_sdma_handle_channel(struct sdma_channel *sdmac)
static irqreturn_t sdma_int_handler(int irq, void *dev_id)
{
struct sdma_engine *sdma = dev_id;
- u32 stat;
+ struct sdma_channel *sdmac;
+ unsigned long flag;
+ int channel;
+ u32 stat, stat_bak;
+ spin_lock_irqsave(&sdma->irq_reg_lock, flag);
stat = readl_relaxed(sdma->regs + SDMA_H_INTR);
writel_relaxed(stat, sdma->regs + SDMA_H_INTR);
+ spin_unlock_irqrestore(&sdma->irq_reg_lock, flag);
+
+ stat_bak = stat;
+ while (stat_bak) {
+ channel = fls(stat_bak) - 1;
+ sdmac = &sdma->channel[channel];
+ sdmac->irq_handling = 1;
+ stat_bak &= ~(1 << channel);
+ }
while (stat) {
- int channel = fls(stat) - 1;
- struct sdma_channel *sdmac = &sdma->channel[channel];
+ channel = fls(stat) - 1;
+ sdmac = &sdma->channel[channel];
- mxc_sdma_handle_channel(sdmac);
+ if (sdmac->irq_handling)
+ mxc_sdma_handle_channel(sdmac);
stat &= ~(1 << channel);
+ sdmac->irq_handling = 0;
}
return IRQ_HANDLED;
@@ -878,6 +896,8 @@ static int sdma_request_channel(struct sdma_channel *sdmac)
sdmac->buf_tail = 0;
+ sdmac->irq_handling = 0;
+
return 0;
out:
@@ -973,11 +993,41 @@ static int sdma_alloc_chan_resources(struct dma_chan *chan)
return 0;
}
+static void sdma_irq_pending_check(struct sdma_channel *sdmac)
+{
+ struct sdma_engine *sdma = sdmac->sdma;
+ unsigned long flag;
+ u32 stat;
+
+ spin_lock_irqsave(&sdma->irq_reg_lock, flag);
+ stat = readl_relaxed(sdma->regs + SDMA_H_INTR);
+
+ /*Check if the current channel's IRQ hasn't been responded*/
+ if (stat & (1 << sdmac->channel)) {
+ /*Handle the irq manually*/
+ writel_relaxed(1 << sdmac->channel, sdma->regs + SDMA_H_INTR);
+ spin_unlock_irqrestore(&sdma->irq_reg_lock, flag);
+
+ /*Prevent irq_handler from doing handle_channel() again*/
+ sdmac->irq_handling = 0;
+ mxc_sdma_handle_channel(sdmac);
+ } else {
+ spin_unlock_irqrestore(&sdma->irq_reg_lock, flag);
+ }
+
+ /*Wait here until irq_handler's finished*/
+ while (sdmac->irq_handling)
+ udelay(100);
+}
+
static void sdma_free_chan_resources(struct dma_chan *chan)
{
struct sdma_channel *sdmac = to_sdma_chan(chan);
struct sdma_engine *sdma = sdmac->sdma;
+ /*Check if irq to the channel is still pending*/
+ sdma_irq_pending_check(sdmac);
+
sdma_disable_channel(sdmac);
if (sdmac->event_id0)
@@ -1449,6 +1499,8 @@ static int __init sdma_probe(struct platform_device *pdev)
dma_cap_set(DMA_SLAVE, sdma->dma_device.cap_mask);
dma_cap_set(DMA_CYCLIC, sdma->dma_device.cap_mask);
+ spin_lock_init(&sdma->irq_reg_lock);
+
INIT_LIST_HEAD(&sdma->dma_device.channels);
/* Initialize channel parameters */
for (i = 0; i < MAX_DMA_CHANNELS; i++) {