]> git.kernelconcepts.de Git - karo-tx-linux.git/blobdiff - drivers/dma/pl330.c
Merge branch 'for-linus' of git://ftp.arm.linux.org.uk/~rmk/linux-arm
[karo-tx-linux.git] / drivers / dma / pl330.c
index 025b905f6db299fa5c172c1251e22f588ec942cd..bdf40b5300328886f139801548542723330218c2 100644 (file)
@@ -27,6 +27,7 @@
 #include <linux/of.h>
 #include <linux/of_dma.h>
 #include <linux/err.h>
+#include <linux/pm_runtime.h>
 
 #include "dmaengine.h"
 #define PL330_MAX_CHAN         8
@@ -265,6 +266,9 @@ static unsigned cmd_line;
 
 #define NR_DEFAULT_DESC        16
 
+/* Delay for runtime PM autosuspend, ms */
+#define PL330_AUTOSUSPEND_DELAY 20
+
 /* Populated by the PL330 core driver for DMA API driver's info */
 struct pl330_config {
        u32     periph_id;
@@ -1958,6 +1962,7 @@ static void pl330_tasklet(unsigned long data)
        struct dma_pl330_chan *pch = (struct dma_pl330_chan *)data;
        struct dma_pl330_desc *desc, *_dt;
        unsigned long flags;
+       bool power_down = false;
 
        spin_lock_irqsave(&pch->lock, flags);
 
@@ -1972,10 +1977,17 @@ static void pl330_tasklet(unsigned long data)
        /* Try to submit a req imm. next to the last completed cookie */
        fill_queue(pch);
 
-       /* Make sure the PL330 Channel thread is active */
-       spin_lock(&pch->thread->dmac->lock);
-       _start(pch->thread);
-       spin_unlock(&pch->thread->dmac->lock);
+       if (list_empty(&pch->work_list)) {
+               spin_lock(&pch->thread->dmac->lock);
+               _stop(pch->thread);
+               spin_unlock(&pch->thread->dmac->lock);
+               power_down = true;
+       } else {
+               /* Make sure the PL330 Channel thread is active */
+               spin_lock(&pch->thread->dmac->lock);
+               _start(pch->thread);
+               spin_unlock(&pch->thread->dmac->lock);
+       }
 
        while (!list_empty(&pch->completed_list)) {
                dma_async_tx_callback callback;
@@ -1990,6 +2002,12 @@ static void pl330_tasklet(unsigned long data)
                if (pch->cyclic) {
                        desc->status = PREP;
                        list_move_tail(&desc->node, &pch->work_list);
+                       if (power_down) {
+                               spin_lock(&pch->thread->dmac->lock);
+                               _start(pch->thread);
+                               spin_unlock(&pch->thread->dmac->lock);
+                               power_down = false;
+                       }
                } else {
                        desc->status = FREE;
                        list_move_tail(&desc->node, &pch->dmac->desc_pool);
@@ -2004,6 +2022,12 @@ static void pl330_tasklet(unsigned long data)
                }
        }
        spin_unlock_irqrestore(&pch->lock, flags);
+
+       /* If work list empty, power down */
+       if (power_down) {
+               pm_runtime_mark_last_busy(pch->dmac->ddma.dev);
+               pm_runtime_put_autosuspend(pch->dmac->ddma.dev);
+       }
 }
 
 bool pl330_filter(struct dma_chan *chan, void *param)
@@ -2073,6 +2097,7 @@ static int pl330_control(struct dma_chan *chan, enum dma_ctrl_cmd cmd, unsigned
 
        switch (cmd) {
        case DMA_TERMINATE_ALL:
+               pm_runtime_get_sync(pl330->ddma.dev);
                spin_lock_irqsave(&pch->lock, flags);
 
                spin_lock(&pl330->lock);
@@ -2099,10 +2124,15 @@ static int pl330_control(struct dma_chan *chan, enum dma_ctrl_cmd cmd, unsigned
                        dma_cookie_complete(&desc->txd);
                }
 
+               if (!list_empty(&pch->work_list))
+                       pm_runtime_put(pl330->ddma.dev);
+
                list_splice_tail_init(&pch->submitted_list, &pl330->desc_pool);
                list_splice_tail_init(&pch->work_list, &pl330->desc_pool);
                list_splice_tail_init(&pch->completed_list, &pl330->desc_pool);
                spin_unlock_irqrestore(&pch->lock, flags);
+               pm_runtime_mark_last_busy(pl330->ddma.dev);
+               pm_runtime_put_autosuspend(pl330->ddma.dev);
                break;
        case DMA_SLAVE_CONFIG:
                slave_config = (struct dma_slave_config *)arg;
@@ -2138,6 +2168,7 @@ static void pl330_free_chan_resources(struct dma_chan *chan)
 
        tasklet_kill(&pch->task);
 
+       pm_runtime_get_sync(pch->dmac->ddma.dev);
        spin_lock_irqsave(&pch->lock, flags);
 
        pl330_release_channel(pch->thread);
@@ -2147,6 +2178,8 @@ static void pl330_free_chan_resources(struct dma_chan *chan)
                list_splice_tail_init(&pch->work_list, &pch->dmac->desc_pool);
 
        spin_unlock_irqrestore(&pch->lock, flags);
+       pm_runtime_mark_last_busy(pch->dmac->ddma.dev);
+       pm_runtime_put_autosuspend(pch->dmac->ddma.dev);
 }
 
 static enum dma_status
@@ -2162,6 +2195,15 @@ static void pl330_issue_pending(struct dma_chan *chan)
        unsigned long flags;
 
        spin_lock_irqsave(&pch->lock, flags);
+       if (list_empty(&pch->work_list)) {
+               /*
+                * Warn on nothing pending. Empty submitted_list may
+                * break our pm_runtime usage counter as it is
+                * updated on work_list emptiness status.
+                */
+               WARN_ON(list_empty(&pch->submitted_list));
+               pm_runtime_get_sync(pch->dmac->ddma.dev);
+       }
        list_splice_tail_init(&pch->submitted_list, &pch->work_list);
        spin_unlock_irqrestore(&pch->lock, flags);
 
@@ -2594,6 +2636,46 @@ static int pl330_dma_device_slave_caps(struct dma_chan *dchan,
        return 0;
 }
 
+/*
+ * Runtime PM callbacks are provided by amba/bus.c driver.
+ *
+ * It is assumed here that IRQ safe runtime PM is chosen in probe and amba
+ * bus driver will only disable/enable the clock in runtime PM callbacks.
+ */
+static int __maybe_unused pl330_suspend(struct device *dev)
+{
+       struct amba_device *pcdev = to_amba_device(dev);
+
+       pm_runtime_disable(dev);
+
+       if (!pm_runtime_status_suspended(dev)) {
+               /* amba did not disable the clock */
+               amba_pclk_disable(pcdev);
+       }
+       amba_pclk_unprepare(pcdev);
+
+       return 0;
+}
+
+static int __maybe_unused pl330_resume(struct device *dev)
+{
+       struct amba_device *pcdev = to_amba_device(dev);
+       int ret;
+
+       ret = amba_pclk_prepare(pcdev);
+       if (ret)
+               return ret;
+
+       if (!pm_runtime_status_suspended(dev))
+               ret = amba_pclk_enable(pcdev);
+
+       pm_runtime_enable(dev);
+
+       return ret;
+}
+
+static SIMPLE_DEV_PM_OPS(pl330_pm, pl330_suspend, pl330_resume);
+
 static int
 pl330_probe(struct amba_device *adev, const struct amba_id *id)
 {
@@ -2748,6 +2830,12 @@ pl330_probe(struct amba_device *adev, const struct amba_id *id)
                pcfg->data_buf_dep, pcfg->data_bus_width / 8, pcfg->num_chan,
                pcfg->num_peri, pcfg->num_events);
 
+       pm_runtime_irq_safe(&adev->dev);
+       pm_runtime_use_autosuspend(&adev->dev);
+       pm_runtime_set_autosuspend_delay(&adev->dev, PL330_AUTOSUSPEND_DELAY);
+       pm_runtime_mark_last_busy(&adev->dev);
+       pm_runtime_put_autosuspend(&adev->dev);
+
        return 0;
 probe_err3:
        /* Idle the DMAC */
@@ -2774,6 +2862,8 @@ static int pl330_remove(struct amba_device *adev)
        struct pl330_dmac *pl330 = amba_get_drvdata(adev);
        struct dma_pl330_chan *pch, *_p;
 
+       pm_runtime_get_noresume(pl330->ddma.dev);
+
        if (adev->dev.of_node)
                of_dma_controller_free(adev->dev.of_node);
 
@@ -2812,6 +2902,7 @@ static struct amba_driver pl330_driver = {
        .drv = {
                .owner = THIS_MODULE,
                .name = "dma-pl330",
+               .pm = &pl330_pm,
        },
        .id_table = pl330_ids,
        .probe = pl330_probe,