sata_dwc_460ex: fix resource leak on error path
[pandora-kernel.git] / drivers / dma / pl330.c
index 19a9974..bdf40b5 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)
 {
@@ -2619,6 +2701,9 @@ pl330_probe(struct amba_device *adev, const struct amba_id *id)
                return -ENOMEM;
        }
 
+       pd = &pl330->ddma;
+       pd->dev = &adev->dev;
+
        pl330->mcbufsz = pdat ? pdat->mcbuf_sz : 0;
 
        res = &adev->res;
@@ -2655,7 +2740,6 @@ pl330_probe(struct amba_device *adev, const struct amba_id *id)
        if (!add_desc(pl330, GFP_KERNEL, NR_DEFAULT_DESC))
                dev_warn(&adev->dev, "unable to allocate desc\n");
 
-       pd = &pl330->ddma;
        INIT_LIST_HEAD(&pd->channels);
 
        /* Initialize channel parameters */
@@ -2692,7 +2776,6 @@ pl330_probe(struct amba_device *adev, const struct amba_id *id)
                list_add_tail(&pch->chan.device_node, &pd->channels);
        }
 
-       pd->dev = &adev->dev;
        if (pdat) {
                pd->cap_mask = pdat->cap_mask;
        } else {
@@ -2747,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 */
@@ -2773,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);
 
@@ -2811,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,
@@ -2819,6 +2911,6 @@ static struct amba_driver pl330_driver = {
 
 module_amba_driver(pl330_driver);
 
-MODULE_AUTHOR("Jaswinder Singh <jassi.brar@samsung.com>");
+MODULE_AUTHOR("Jaswinder Singh <jassisinghbrar@gmail.com>");
 MODULE_DESCRIPTION("API Driver for PL330 DMAC");
 MODULE_LICENSE("GPL");