dma: pl330: Implement the DMA channel PAUSE/RESUME interfaces
authorHuang Chao <chao7.huang@samsung.com>
Tue, 24 Jun 2014 05:01:46 +0000 (13:01 +0800)
committerChanho Park <chanho61.park@samsung.com>
Thu, 7 Aug 2014 06:16:11 +0000 (15:16 +0900)
This patch implements the DMA_PAUSE/DMA_RESUME interfaces in pl330 DMAC
driver, which will call the dma pause/resume operations to set the dma
channel status respectively.

When DMA_PAUSE is called from dma subsystem during audio playback, the
DMAC should halt the transfer execution of the channel thread and wait
for the resume event occurs, which is specified by the event number in
INTEN register. The channel thread is stalled by the DMAWFE instruction.
After that, when DMA_RESUME is called, which means that the resume event
occurs, this patch will signal an interrupt to send the DMASEV instruction
and restart the channel thread, and then the channel thread moves to the
Executing state and the DMAC clears the event.

Change-Id: I5d990f0efb31b7e37673a060fdcbc9002b6ea488
Signed-off-by: Huang Chao <chao7.huang@samsung.com>
drivers/dma/pl330.c

index cd132f3..d7da512 100644 (file)
@@ -1239,6 +1239,49 @@ static bool _start(struct pl330_thread *thrd)
        }
 }
 
+static bool _pause(struct pl330_thread *thrd)
+{
+       void __iomem *regs = thrd->dmac->pinfo->base;
+       u8 insn[6] = {0, 0, 0, 0, 0, 0};
+
+       if (_state(thrd) == PL330_STATE_FAULT_COMPLETING)
+               UNTIL(thrd, PL330_STATE_FAULTING | PL330_STATE_KILLING);
+
+       /* Return false if dma channel has fault */
+       if (_state(thrd) == PL330_STATE_COMPLETING ||
+               _state(thrd) == PL330_STATE_KILLING ||
+               _state(thrd) == PL330_STATE_FAULTING)
+               return false;
+
+       _emit_WFE(0, insn, thrd->ev, 1);
+
+       /* Stop generating interrupts for SEV */
+       writel(readl(regs + INTEN) & ~(1 << thrd->ev), regs + INTEN);
+
+       _execute_DBGINSN(thrd, insn, is_manager(thrd));
+
+       return true;
+}
+
+static bool _resume(struct pl330_thread *thrd)
+{
+       void __iomem *regs = thrd->dmac->pinfo->base;
+
+       if (_state(thrd) == PL330_STATE_FAULT_COMPLETING)
+               UNTIL(thrd, PL330_STATE_FAULTING | PL330_STATE_KILLING);
+
+       /* Return false if dma channel has fault */
+       if (_state(thrd) == PL330_STATE_COMPLETING ||
+               _state(thrd) == PL330_STATE_KILLING ||
+               _state(thrd) == PL330_STATE_FAULTING)
+               return false;
+
+       /* Start generating interrupts for SEV */
+       writel(readl(regs + INTEN) | (1 << thrd->ev), regs + INTEN);
+
+       return true;
+}
+
 static inline int _ldst_memtomem(unsigned dry_run, u8 buf[],
                const struct _xfer_spec *pxs, int cyc)
 {
@@ -1824,6 +1867,16 @@ static int pl330_chan_ctrl(void *ch_id, enum pl330_chan_op op)
                        ret = -EIO;
                break;
 
+       case PL330_OP_PAUSE:
+               if ((active != -1) && !_pause(thrd))
+                       ret = -EIO;
+               break;
+
+       case PL330_OP_RESUME:
+               if ((active != -1) && !_resume(thrd))
+                       ret = -EIO;
+               break;
+
        default:
                ret = -EINVAL;
        }
@@ -2421,10 +2474,14 @@ static int pl330_control(struct dma_chan *chan, enum dma_ctrl_cmd cmd, unsigned
                }
                break;
        case DMA_PAUSE:
-               /* TODO: set up dma channel and config register if any */
+               spin_lock_irqsave(&pch->lock, flags);
+               pl330_chan_ctrl(pch->pl330_chid, PL330_OP_PAUSE);
+               spin_unlock_irqrestore(&pch->lock, flags);
                break;
        case DMA_RESUME:
-               /* TODO: set up dma channel and config register if any */
+               spin_lock_irqsave(&pch->lock, flags);
+               pl330_chan_ctrl(pch->pl330_chid, PL330_OP_RESUME);
+               spin_unlock_irqrestore(&pch->lock, flags);
                break;
        default:
                dev_err(pch->dmac->pif.dev, "Not supported command.\n");