Thread (13 messages) flat view 13 messages, 2 authors, 2011-08-01

[PATCH 2/3] dmaengine: at_hdmac: improve power management routines

From: Nicolas Ferre <hidden>
Date: 2011-07-25 21:07:53
Also in: lkml

On 07/07/2011 04:20 AM, Vinod Koul wrote:
On Tue, 2011-06-28 at 13:17 +0200, Nicolas Ferre wrote:
quoted
Save/restore dma controller state across a suspend-resume sequence.
The prepare() function will wait for the non-cyclic channels to become idle.
It also deals with cyclic operations with the start at next period while
resuming.

Signed-off-by: Nicolas Ferre<redacted>
Signed-off-by: Uwe Kleine-K?nig<redacted>
---
  drivers/dma/at_hdmac.c      |   88 ++++++++++++++++++++++++++++++++++++++++++-
  drivers/dma/at_hdmac_regs.h |    7 +++
  2 files changed, 94 insertions(+), 1 deletions(-)
diff --git a/drivers/dma/at_hdmac.c b/drivers/dma/at_hdmac.c
index fd87b96..7096adb 100644
--- a/drivers/dma/at_hdmac.c
+++ b/drivers/dma/at_hdmac.c
@@ -1385,27 +1385,113 @@ static void at_dma_shutdown(struct platform_device *pdev)
  	clk_disable(atdma->clk);
  }

+static int at_dma_prepare(struct device *dev)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct at_dma *atdma = platform_get_drvdata(pdev);
+	struct dma_chan *chan, *_chan;
+
+	list_for_each_entry_safe(chan, _chan,&atdma->dma_common.channels,
+			device_node) {
+		struct at_dma_chan *atchan = to_at_dma_chan(chan);
+		/* wait for transaction completion (except in cyclic case) */
+		if (atc_chan_is_enabled(atchan)&&
+			!test_bit(ATC_IS_CYCLIC,&atchan->status))
+			return -EAGAIN;
pls fix indent here
Fixed by replacement of test_bit() with a wrapper in a patch to come (as 
you suggest hereafter).
quoted
+	}
+	return 0;
+}
+
+static void atc_suspend_cyclic(struct at_dma_chan *atchan)
+{
+	struct dma_chan	*chan =&atchan->chan_common;
+
+	/* Channel should be paused by user
+	 * do it anyway even if it is not done already */
+	if (!test_bit(ATC_IS_PAUSED,&atchan->status)) {
+		dev_warn(chan2dev(chan),
+		"cyclic channel not paused, should be done by channel user\n");
+		atc_control(chan, DMA_PAUSE, 0);
+	}
+
+	/* now preserve additional data for cyclic operations */
+	/* next descriptor address in the cyclic list */
+	atchan->save_dscr = channel_readl(atchan, DSCR);
+
+	vdbg_dump_regs(atchan);
+}
+
  static int at_dma_suspend_noirq(struct device *dev)
  {
  	struct platform_device *pdev = to_platform_device(dev);
  	struct at_dma *atdma = platform_get_drvdata(pdev);
+	struct dma_chan *chan, *_chan;

-	at_dma_off(platform_get_drvdata(pdev));
+	/* preserve data */
+	list_for_each_entry_safe(chan, _chan,&atdma->dma_common.channels,
+			device_node) {
+		struct at_dma_chan *atchan = to_at_dma_chan(chan);
+
+		if (test_bit(ATC_IS_CYCLIC,&atchan->status))
+			atc_suspend_cyclic(atchan);
+		atchan->save_cfg = channel_readl(atchan, CFG);
+	}
+	atdma->save_imr = dma_readl(atdma, EBCIMR);
+
+	/* disable DMA controller */
+	at_dma_off(atdma);
  	clk_disable(atdma->clk);
  	return 0;
  }

+static void atc_resume_cyclic(struct at_dma_chan *atchan)
+{
+	struct at_dma	*atdma = to_at_dma(atchan->chan_common.device);
+
+	/* restore channel status for cyclic descriptors list:
+	 * next descriptor in the cyclic list at the time of suspend */
+	channel_writel(atchan, SADDR, 0);
+	channel_writel(atchan, DADDR, 0);
+	channel_writel(atchan, CTRLA, 0);
+	channel_writel(atchan, CTRLB, 0);
+	channel_writel(atchan, DSCR, atchan->save_dscr);
+	dma_writel(atdma, CHER, atchan->mask);
+
+	/* channel pause status should be removed by channel user
+	 * We cannot take the initiative to do it here */
+
+	vdbg_dump_regs(atchan);
+}
+
  static int at_dma_resume_noirq(struct device *dev)
  {
  	struct platform_device *pdev = to_platform_device(dev);
  	struct at_dma *atdma = platform_get_drvdata(pdev);
+	struct dma_chan *chan, *_chan;

+	/* bring back DMA controller */
  	clk_enable(atdma->clk);
  	dma_writel(atdma, EN, AT_DMA_ENABLE);
+
+	/* clear any pending interrupt */
+	while (dma_readl(atdma, EBCISR))
+		cpu_relax();
+
+	/* restore saved data */
+	dma_writel(atdma, EBCIER, atdma->save_imr);
+	list_for_each_entry_safe(chan, _chan,&atdma->dma_common.channels,
+			device_node) {
+		struct at_dma_chan *atchan = to_at_dma_chan(chan);
+
+		channel_writel(atchan, CFG, atchan->save_cfg);
+		if (test_bit(ATC_IS_CYCLIC,&atchan->status))
+			atc_resume_cyclic(atchan);
This testing on bits seems to be reused few times how about wrapping it
up in a routine?
True: I write a little patch for this and the "PAUSE" state. I send a 
patch for this now:
dmaengine: at_hdmac: add wrappers for testing channel state
So can you:
1/ queue 1/3 and 2/3 of this patch series
2/ queue the following patch named
"dmaengine: at_hdmac: add wrappers for testing channel state"
on top of that

3/ drop the patch 3/3 of this series: it certainly have to be reworked 
with all slave config infrastructure implementation in the at_hdmac driver.
quoted
+	}
  	return 0;
  }

  static const struct dev_pm_ops at_dma_dev_pm_ops = {
+	.prepare = at_dma_prepare,
  	.suspend_noirq = at_dma_suspend_noirq,
  	.resume_noirq = at_dma_resume_noirq,
  };
diff --git a/drivers/dma/at_hdmac_regs.h b/drivers/dma/at_hdmac_regs.h
index 087dbf1..6f0c4a3 100644
--- a/drivers/dma/at_hdmac_regs.h
+++ b/drivers/dma/at_hdmac_regs.h
@@ -204,6 +204,9 @@ enum atc_status {
   * @status: transmit status information from irq/prep* functions
   *                to tasklet (use atomic operations)
   * @tasklet: bottom half to finish transaction work
+ * @save_cfg: configuration register that is saved on suspend/resume cycle
+ * @save_dscr: for cyclic operations, preserve next descriptor address in
+ *             the cyclic list on suspend/resume cycle
   * @lock: serializes enqueue/dequeue operations to descriptors lists
   * @completed_cookie: identifier for the most recently completed operation
   * @active_list: list of descriptors dmaengine is being running on
@@ -218,6 +221,8 @@ struct at_dma_chan {
  	u8			mask;
  	unsigned long		status;
  	struct tasklet_struct	tasklet;
+	u32			save_cfg;
+	u32			save_dscr;

  	spinlock_t		lock;
@@ -248,6 +253,7 @@ static inline struct at_dma_chan *to_at_dma_chan(struct dma_chan *dchan)
   * @chan_common: common dmaengine dma_device object members
   * @ch_regs: memory mapped register base
   * @clk: dma controller clock
+ * @save_imr: interrupt mask register that is saved on suspend/resume cycle
   * @all_chan_mask: all channels availlable in a mask
   * @dma_desc_pool: base of DMA descriptor region (DMA address)
   * @chan: channels table to store at_dma_chan structures
@@ -256,6 +262,7 @@ struct at_dma {
  	struct dma_device	dma_common;
  	void __iomem		*regs;
  	struct clk		*clk;
+	u32			save_imr;

  	u8			all_chan_mask;
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help