ARM: 8206/1: dmaengine: pl330: Add PM sleep support
[pandora-kernel.git] / drivers / dma / pl330.c
index d5149aa..2d324f7 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;
@@ -1367,17 +1371,10 @@ static int pl330_submit_req(struct pl330_thread *thrd,
        struct pl330_dmac *pl330 = thrd->dmac;
        struct _xfer_spec xs;
        unsigned long flags;
-       void __iomem *regs;
        unsigned idx;
        u32 ccr;
        int ret = 0;
 
-       /* No Req or Unacquired Channel or DMAC */
-       if (!desc || !thrd || thrd->free)
-               return -EINVAL;
-
-       regs = thrd->dmac->base;
-
        if (pl330->state == DYING
                || pl330->dmac_tbd.reset_chan & (1 << thrd->id)) {
                dev_info(thrd->dmac->ddma.dev, "%s:%d\n",
@@ -1965,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);
 
@@ -1979,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;
@@ -1997,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);
@@ -2011,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)
@@ -2080,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);
@@ -2106,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;
@@ -2145,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);
@@ -2154,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
@@ -2169,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);
 
@@ -2592,6 +2627,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)
 {
@@ -2745,6 +2820,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 */
@@ -2755,8 +2836,10 @@ probe_err3:
                list_del(&pch->chan.device_node);
 
                /* Flush the channel */
-               pl330_control(&pch->chan, DMA_TERMINATE_ALL, 0);
-               pl330_free_chan_resources(&pch->chan);
+               if (pch->thread) {
+                       pl330_control(&pch->chan, DMA_TERMINATE_ALL, 0);
+                       pl330_free_chan_resources(&pch->chan);
+               }
        }
 probe_err2:
        pl330_del(pl330);
@@ -2769,6 +2852,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);
 
@@ -2782,8 +2867,10 @@ static int pl330_remove(struct amba_device *adev)
                list_del(&pch->chan.device_node);
 
                /* Flush the channel */
-               pl330_control(&pch->chan, DMA_TERMINATE_ALL, 0);
-               pl330_free_chan_resources(&pch->chan);
+               if (pch->thread) {
+                       pl330_control(&pch->chan, DMA_TERMINATE_ALL, 0);
+                       pl330_free_chan_resources(&pch->chan);
+               }
        }
 
        pl330_del(pl330);
@@ -2805,6 +2892,7 @@ static struct amba_driver pl330_driver = {
        .drv = {
                .owner = THIS_MODULE,
                .name = "dma-pl330",
+               .pm = &pl330_pm,
        },
        .id_table = pl330_ids,
        .probe = pl330_probe,