diff options
author | Russell King - ARM Linux <linux@arm.linux.org.uk> | 2011-01-27 07:32:53 -0500 |
---|---|---|
committer | Dan Williams <dan.j.williams@intel.com> | 2011-01-31 01:00:48 -0500 |
commit | fb526210b2b961b5d590b89fd8f45c0ca5769688 (patch) | |
tree | 4998a89c11eed597b3197d013ff7b558c9566f2d /drivers/dma/amba-pl08x.c | |
parent | d718f4ebddcb0bebdbf771a6672756b666e5c31b (diff) |
DMA: PL08x: fix infinite wait when terminating transfers
If we try to pause a channel when terminating a transfer, we could end
up spinning for it to become inactive indefinitely, and can result in
an uninterruptible wait requiring a reset to recover from.
Terminating a transfer is supposed to take effect immediately, but may
result in data loss.
To make this clear, rename the function to pl08x_terminate_phy_chan().
Also, make sure it is always consistently called - with the spinlock
held and IRQs disabled, and ensure that the TC and ERR interrupt status
is always cleared.
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
Acked-by: Linus Walleij <linus.walleij@stericsson.com>
Signed-off-by: Dan Williams <dan.j.williams@intel.com>
Diffstat (limited to 'drivers/dma/amba-pl08x.c')
-rw-r--r-- | drivers/dma/amba-pl08x.c | 32 |
1 files changed, 18 insertions, 14 deletions
diff --git a/drivers/dma/amba-pl08x.c b/drivers/dma/amba-pl08x.c index 297f48b0cba9..8321a3997c95 100644 --- a/drivers/dma/amba-pl08x.c +++ b/drivers/dma/amba-pl08x.c | |||
@@ -267,19 +267,24 @@ static void pl08x_resume_phy_chan(struct pl08x_phy_chan *ch) | |||
267 | } | 267 | } |
268 | 268 | ||
269 | 269 | ||
270 | /* Stops the channel */ | 270 | /* |
271 | static void pl08x_stop_phy_chan(struct pl08x_phy_chan *ch) | 271 | * pl08x_terminate_phy_chan() stops the channel, clears the FIFO and |
272 | * clears any pending interrupt status. This should not be used for | ||
273 | * an on-going transfer, but as a method of shutting down a channel | ||
274 | * (eg, when it's no longer used) or terminating a transfer. | ||
275 | */ | ||
276 | static void pl08x_terminate_phy_chan(struct pl08x_driver_data *pl08x, | ||
277 | struct pl08x_phy_chan *ch) | ||
272 | { | 278 | { |
273 | u32 val; | 279 | u32 val = readl(ch->base + PL080_CH_CONFIG); |
274 | 280 | ||
275 | pl08x_pause_phy_chan(ch); | 281 | val &= ~(PL080_CONFIG_ENABLE | PL080_CONFIG_ERR_IRQ_MASK | |
282 | PL080_CONFIG_TC_IRQ_MASK); | ||
276 | 283 | ||
277 | /* Disable channel */ | ||
278 | val = readl(ch->base + PL080_CH_CONFIG); | ||
279 | val &= ~PL080_CONFIG_ENABLE; | ||
280 | val &= ~PL080_CONFIG_ERR_IRQ_MASK; | ||
281 | val &= ~PL080_CONFIG_TC_IRQ_MASK; | ||
282 | writel(val, ch->base + PL080_CH_CONFIG); | 284 | writel(val, ch->base + PL080_CH_CONFIG); |
285 | |||
286 | writel(1 << ch->id, pl08x->base + PL080_ERR_CLEAR); | ||
287 | writel(1 << ch->id, pl08x->base + PL080_TC_CLEAR); | ||
283 | } | 288 | } |
284 | 289 | ||
285 | static inline u32 get_bytes_in_cctl(u32 cctl) | 290 | static inline u32 get_bytes_in_cctl(u32 cctl) |
@@ -404,13 +409,12 @@ static inline void pl08x_put_phy_channel(struct pl08x_driver_data *pl08x, | |||
404 | { | 409 | { |
405 | unsigned long flags; | 410 | unsigned long flags; |
406 | 411 | ||
412 | spin_lock_irqsave(&ch->lock, flags); | ||
413 | |||
407 | /* Stop the channel and clear its interrupts */ | 414 | /* Stop the channel and clear its interrupts */ |
408 | pl08x_stop_phy_chan(ch); | 415 | pl08x_terminate_phy_chan(pl08x, ch); |
409 | writel((1 << ch->id), pl08x->base + PL080_ERR_CLEAR); | ||
410 | writel((1 << ch->id), pl08x->base + PL080_TC_CLEAR); | ||
411 | 416 | ||
412 | /* Mark it as free */ | 417 | /* Mark it as free */ |
413 | spin_lock_irqsave(&ch->lock, flags); | ||
414 | ch->serving = NULL; | 418 | ch->serving = NULL; |
415 | spin_unlock_irqrestore(&ch->lock, flags); | 419 | spin_unlock_irqrestore(&ch->lock, flags); |
416 | } | 420 | } |
@@ -1449,7 +1453,7 @@ static int pl08x_control(struct dma_chan *chan, enum dma_ctrl_cmd cmd, | |||
1449 | plchan->state = PL08X_CHAN_IDLE; | 1453 | plchan->state = PL08X_CHAN_IDLE; |
1450 | 1454 | ||
1451 | if (plchan->phychan) { | 1455 | if (plchan->phychan) { |
1452 | pl08x_stop_phy_chan(plchan->phychan); | 1456 | pl08x_terminate_phy_chan(pl08x, plchan->phychan); |
1453 | 1457 | ||
1454 | /* | 1458 | /* |
1455 | * Mark physical channel as free and free any slave | 1459 | * Mark physical channel as free and free any slave |