Skip to contentSkip to conversation

dmaengine: sirf: add PM entries for sleep and runtime

PATCH Superseded Barry Song 3 participants 6 messages Jul 30, 2013 – Aug 14, 2013 1 message not in the archive dmaengine

lore
Share & tools
lore

Barry Song

author
#0 lore raw

PATCH v2 1/1 dmaengine: sirf: add PM entries for sleep and runtime

this patch adds PM ops entries in sirf-dma drivers, so that this driver can support suspend/resume, hibernation and runtime PM.

while suspending, sirf-dma will lose all registers, so we save them at suspend and restore in resume for active channels.

---
-v2: add comments for PM state machine;
don't always put dma to suspended after resuming from sleep as it depends on the runtime pm status;

 drivers/dma/sirf-dma.c | 132 +++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 129 insertions(+), 3 deletions(-)
drivers/dma/sirf-dma.c +129 -3
index 716b23e..ff3cdd2 100644
@@ -9,6 +9,7 @@
 #include <linux/module.h>
 #include <linux/dmaengine.h>
 #include <linux/dma-mapping.h>
+#include <linux/pm_runtime.h>
 #include <linux/interrupt.h>
 #include <linux/io.h>
 #include <linux/slab.h>
@@ -73,6 +74,11 @@ struct sirfsoc_dma_chan {
 	int				mode;
 };
 
+struct sirfsoc_dma_regs {
+	u32				ctrl[SIRFSOC_DMA_CHANNELS];
+	u32				interrupt_en;
+};
+
 struct sirfsoc_dma {
 	struct dma_device		dma;
 	struct tasklet_struct		tasklet;
@@ -81,10 +87,13 @@ struct sirfsoc_dma {
 	int				irq;
 	struct clk			*clk;
 	bool				is_marco;
+	struct sirfsoc_dma_regs		regs_save;
 };
 
 #define DRV_NAME	"sirfsoc_dma"
 
+static int sirfsoc_dma_runtime_suspend(struct device *dev);
+
 /* Convert struct dma_chan to struct sirfsoc_dma_chan */
 static inline
 struct sirfsoc_dma_chan *dma_chan_to_sirfsoc_dma_chan(struct dma_chan *c)
@@ -393,6 +402,8 @@ static int sirfsoc_dma_alloc_chan_resources(struct dma_chan *chan)
 	LIST_HEAD(descs);
 	int i;
 
+	pm_runtime_get_sync(sdma->dma.dev);
+
 	/* Alloc descriptors for this channel */
 	for (i = 0; i < SIRFSOC_DMA_DESCRIPTORS; i++) {
 		sdesc = kzalloc(sizeof(*sdesc), GFP_KERNEL);
@@ -425,6 +436,7 @@ static int sirfsoc_dma_alloc_chan_resources(struct dma_chan *chan)
 static void sirfsoc_dma_free_chan_resources(struct dma_chan *chan)
 {
 	struct sirfsoc_dma_chan *schan = dma_chan_to_sirfsoc_dma_chan(chan);
+	struct sirfsoc_dma *sdma = dma_chan_to_sirfsoc_dma(chan);
 	struct sirfsoc_dma_desc *sdesc, *tmp;
 	unsigned long flags;
 	LIST_HEAD(descs);
@@ -445,6 +457,8 @@ static void sirfsoc_dma_free_chan_resources(struct dma_chan *chan)
 	/* Free descriptors */
 	list_for_each_entry_safe(sdesc, tmp, &descs, node)
 		kfree(sdesc);
+
+	pm_runtime_put(sdma->dma.dev);
 }
 
 /* Send pending descriptor to hardware */
@@ -723,14 +737,14 @@ static int sirfsoc_dma_probe(struct platform_device *op)
 
 	tasklet_init(&sdma->tasklet, sirfsoc_dma_tasklet, (unsigned long)sdma);
 
-	clk_prepare_enable(sdma->clk);
-
 	/* Register DMA engine */
 	dev_set_drvdata(dev, sdma);
+
 	ret = dma_async_device_register(dma);
 	if (ret)
 		goto free_irq;
 
+	pm_runtime_enable(&op->dev);
 	dev_info(dev, "initialized SIRFSOC DMAC driver\n");
 
 	return 0;
@@ -747,13 +761,124 @@ static int sirfsoc_dma_remove(struct platform_device *op)
 	struct device *dev = &op->dev;
 	struct sirfsoc_dma *sdma = dev_get_drvdata(dev);
 
-	clk_disable_unprepare(sdma->clk);
 	dma_async_device_unregister(&sdma->dma);
 	free_irq(sdma->irq, sdma);
 	irq_dispose_mapping(sdma->irq);
+	pm_runtime_disable(&op->dev);
+	if (!pm_runtime_status_suspended(&op->dev))
+		sirfsoc_dma_runtime_suspend(&op->dev);
+
+	return 0;
+}
+
+static int sirfsoc_dma_runtime_suspend(struct device *dev)
+{
+	struct sirfsoc_dma *sdma = dev_get_drvdata(dev);
+
+	clk_disable_unprepare(sdma->clk);
+	return 0;
+}
+
+static int sirfsoc_dma_runtime_resume(struct device *dev)
+{
+	struct sirfsoc_dma *sdma = dev_get_drvdata(dev);
+	int ret;
+
+	ret = clk_prepare_enable(sdma->clk);
+	if (ret < 0) {
+		dev_err(dev, "clk_enable failed: %d\n", ret);
+		return ret;
+	}
+	return 0;
+}
+
+static int sirfsoc_dma_pm_suspend(struct device *dev)
+{
+	struct sirfsoc_dma *sdma = dev_get_drvdata(dev);
+	struct sirfsoc_dma_regs *save = &sdma->regs_save;
+	struct sirfsoc_dma_desc *sdesc;
+	struct sirfsoc_dma_chan *schan;
+	int ch;
+	int ret;
+
+	/*
+	 * if we were runtime-suspended before, resume to enable clock
+	 * before accessing register
+	 */
+	if (pm_runtime_status_suspended(dev)) {
+		ret = sirfsoc_dma_runtime_resume(dev);
+		if (ret < 0)
+			return ret;
+	}
+
+	/*
+	 * DMA controller will lose all registers while suspending
+	 * so we need to save registers for active channels
+	 */
+	for (ch = 0; ch < SIRFSOC_DMA_CHANNELS; ch++) {
+		schan = &sdma->channels[ch];
+		if (list_empty(&schan->active))
+			continue;
+		sdesc = list_first_entry(&schan->active,
+			struct sirfsoc_dma_desc,
+			node);
+		save->ctrl[ch] = readl_relaxed(sdma->base +
+			ch * 0x10 + SIRFSOC_DMA_CH_CTRL);
+	}
+	save->interrupt_en = readl_relaxed(sdma->base + SIRFSOC_DMA_INT_EN);
+
+	/* Disable clock */
+	sirfsoc_dma_runtime_suspend(dev);
+
+	return 0;
+}
+
+static int sirfsoc_dma_pm_resume(struct device *dev)
+{
+	struct sirfsoc_dma *sdma = dev_get_drvdata(dev);
+	struct sirfsoc_dma_regs *save = &sdma->regs_save;
+	struct sirfsoc_dma_desc *sdesc;
+	struct sirfsoc_dma_chan *schan;
+	int ch;
+	int ret;
+
+	/* Enable clock before accessing register */
+	ret = sirfsoc_dma_runtime_resume(dev);
+	if (ret < 0)
+		return ret;
+
+	writel_relaxed(save->interrupt_en, sdma->base + SIRFSOC_DMA_INT_EN);
+	for (ch = 0; ch < SIRFSOC_DMA_CHANNELS; ch++) {
+		schan = &sdma->channels[ch];
+		if (list_empty(&schan->active))
+			continue;
+		sdesc = list_first_entry(&schan->active,
+			struct sirfsoc_dma_desc,
+			node);
+		writel_relaxed(sdesc->width,
+			sdma->base + SIRFSOC_DMA_WIDTH_0 + ch * 4);
+		writel_relaxed(sdesc->xlen,
+			sdma->base + ch * 0x10 + SIRFSOC_DMA_CH_XLEN);
+		writel_relaxed(sdesc->ylen,
+			sdma->base + ch * 0x10 + SIRFSOC_DMA_CH_YLEN);
+		writel_relaxed(save->ctrl[ch],
+			sdma->base + ch * 0x10 + SIRFSOC_DMA_CH_CTRL);
+		writel_relaxed(sdesc->addr >> 2,
+			sdma->base + ch * 0x10 + SIRFSOC_DMA_CH_ADDR);
+	}
+
+	/* if we were runtime-suspended before, suspend again */
+	if (pm_runtime_status_suspended(dev))
+		sirfsoc_dma_runtime_suspend(dev);
+
 	return 0;
 }
 
+static const struct dev_pm_ops sirfsoc_dma_pm_ops = {
+	SET_RUNTIME_PM_OPS(sirfsoc_dma_runtime_suspend, sirfsoc_dma_runtime_resume, NULL)
+	SET_SYSTEM_SLEEP_PM_OPS(sirfsoc_dma_pm_suspend, sirfsoc_dma_pm_resume)
+};
+
 static struct of_device_id sirfsoc_dma_match[] = {
 	{ .compatible = "sirf,prima2-dmac", },
 	{ .compatible = "sirf,marco-dmac", },
@@ -766,6 +891,7 @@ static struct platform_driver sirfsoc_dma_driver = {
 	.driver = {
 		.name = DRV_NAME,
 		.owner = THIS_MODULE,
+		.pm = &sirfsoc_dma_pm_ops,
 		.of_match_table	= sirfsoc_dma_match,
 	},
 };
Signature · 1 line
1.8.2.3
author
#1 lore raw

PATCH v2 1/1 dmaengine: sirf: add PM entries for sleep and runtime

2013/7/30 Barry Song <[email protected]>:

Quoted 8 lines

this patch adds PM ops entries in sirf-dma drivers, so that this driver can support suspend/resume, hibernation and runtime PM.

while suspending, sirf-dma will lose all registers, so we save them at suspend and restore in resume for active channels.

ping Vinod.

-barry

Applied
#2 lore raw

PATCH v2 1/1 dmaengine: sirf: add PM entries for sleep and runtime

In reply to Barry Song
Quoted 8 lines · Barry Song

this patch adds PM ops entries in sirf-dma drivers, so that this driver can support suspend/resume, hibernation and runtime PM.

while suspending, sirf-dma will lose all registers, so we save them at suspend and restore in resume for active channels.

Applied, thanks

Although putting runtime_get/put in submit and callback would be much more better than alloc/free implementation

~Vinod

#3 lore raw

PATCH v2 1/1 dmaengine: sirf: add PM entries for sleep and runtime

Quoted 7 lines · Vinod Koul
Quoted 6 lines · Barry Song

this patch adds PM ops entries in sirf-dma drivers, so that this driver can support suspend/resume, hibernation and runtime PM.

while suspending, sirf-dma will lose all registers, so we save them at suspend and restore in resume for active channels.

Quoted 6 lines
Quoted 2 lines

Applied, thanks

Although putting runtime_get/put in submit and callback would be much more better than alloc/free implementation

When the prepare/submit is called from non-process context, the runtime API tends to complain, so it's not that easy.

Also you can end up in situations which cause lockdep to complain, especially when some of the runtime stuff calls out to an i2c controller which then uses the dmaengine, but the dma engine also calls out to the runtime api. (I don't rememeber the exact details, I just remember having to drop the runtime API from such places in the past.)

#4 lore raw

PATCH v2 1/1 dmaengine: sirf: add PM entries for sleep and runtime

Quoted 7 lines · Russell King - ARM Linux
Quoted 3 lines · Vinod Koul

On Tue, Jul 30, 2013 at 05:44:34PM +0800, Barry Song wrote:
Although putting runtime_get/put in submit and callback would be much more better than alloc/free implementation

When the prepare/submit is called from non-process context, the runtime API tends to complain, so it's not that easy.

only the pm_runtime_get_sync() is the problem here. you can do pm_runtime_get() and submit the descriptor

Then on .runtime_resume you can start the pending transaction.

I found it very useful when working with serial dma's where the serial driver would like to keep the dma channel and not free it.

Quoted 5 lines

Also you can end up in situations which cause lockdep to complain, especially when some of the runtime stuff calls out to an i2c controller which then uses the dmaengine, but the dma engine also calls out to the runtime api. (I don't rememeber the exact details, I just remember having to drop the runtime API from such places in the past.)

hmmm, i havent seen such dependency. Can you recall what were you testing in that scenario

~Vinod

A message that is not in the archive (sent off-list or not yet received): CAGsJ_4yPgXhUO65tAyU4ngMRUzqa6uZWqEM2NoD-HWy9DL-rnQ@mail.gmail.com

#6 lore raw

PATCH v2 1/1 dmaengine: sirf: add PM entries for sleep and runtime

Quoted 16 lines · Barry Song

2013/8/13 Vinod Koul <[email protected]>

Quoted 14 lines · Barry Song
Quoted 8 lines

this patch adds PM ops entries in sirf-dma drivers, so that this driver can support suspend/resume, hibernation and runtime PM.

while suspending, sirf-dma will lose all registers, so we save them at suspend and restore in resume for active channels.

Applied, thanks

Although putting runtime_get/put in submit and callback would be much more better than alloc/free implementation

Quoted 8 lines
Quoted 1 line

i do agree. i did want to make the runtime pm happen in submit and finish

callback. then i found it was buggy. and we need more work to make it really stable, then i moved to runtime PM with more coarse accuracy. that will lose some power saving. but i think we can have increased patches for that if we measure and find dma power consumption is large. BTW, where is the active dmaengine tree located in now? it seems that
https://git.kernel.org/cgit/linux/kernel/git/djbw/dmaengine.git/ is not active for a long time.

mine is at git.infradead.org/users/vkoul/slave-dma.git

Dan seem busy so he acks patches and I carry them

~Vinod