video: tegra: host: use runtime pm for clock management
Mayuresh Kulkarni [Fri, 3 Feb 2012 09:36:07 +0000 (14:36 +0530)]
- use runtime pm for clock management of host1x
and its clients thus replacing ACM
- start a delayed worker after disabling the clock
if module supports power gating
- in its timeout handler power gate the module after saving
its context for next submit
- use auto-suspend mode of runtime pm for clock management
- pm core seems to keep a ref count on runtime pm thus
we cannot use runtime pm's usage_count as an idicator
of module idle during suspend
- do not use runtime pm call-backs during system suspend.
instead manage the clocks directly for context save of
modules that support it
- enable runtime pm only during boot-up as pm core disables
it before suspending the device and enables it after resume

for bug 887332

Change-Id: I3b30643e8e75c13684cf4edaaae4429c3a18d6eb
Signed-off-by: Mayuresh Kulkarni <mkulkarni@nvidia.com>
Reviewed-on: http://git-master/r/79186
Reviewed-by: Terje Bergstrom <tbergstrom@nvidia.com>

drivers/video/tegra/host/bus.c
drivers/video/tegra/host/dev.c
drivers/video/tegra/host/host1x/host1x_debug.c
drivers/video/tegra/host/host1x/host1x_syncpt.c
drivers/video/tegra/host/nvhost_acm.c
drivers/video/tegra/host/nvhost_acm.h
drivers/video/tegra/host/nvhost_channel.c
include/linux/nvhost.h

index 8234d0f..8fd4aa8 100644 (file)
@@ -19,7 +19,7 @@
 
 #include <linux/pm_runtime.h>
 #include <linux/nvhost.h>
-
+#include <mach/clk.h>
 #include "dev.h"
 
 struct nvhost_master *nvhost;
@@ -487,17 +487,34 @@ static int nvhost_pm_restore_noirq(struct device *dev)
 
 int __weak nvhost_pm_runtime_suspend(struct device *dev)
 {
-       return pm_generic_runtime_suspend(dev);
+       int i;
+       struct nvhost_device *device = to_nvhost_device(dev);
+
+       for (i = 0; i < device->num_clks; i++)
+               clk_disable(device->clk[i]);
+
+       if (device->can_powergate)
+               schedule_delayed_work(&device->powerstate_down,
+                       msecs_to_jiffies(device->powergate_delay));
+
+       return 0;
 };
 
 int __weak nvhost_pm_runtime_resume(struct device *dev)
 {
-       return pm_generic_runtime_resume(dev);
+       int i;
+       struct nvhost_device *device = to_nvhost_device(dev);
+
+       for (i = 0; i < device->num_clks; i++)
+               clk_enable(device->clk[i]);
+
+       return 0;
 };
 
 int __weak nvhost_pm_runtime_idle(struct device *dev)
 {
-       return pm_generic_runtime_idle(dev);
+       pm_runtime_autosuspend(dev);
+       return 0;
 };
 
 #else /* !CONFIG_PM_RUNTIME */
index 4cd1e4e..c754480 100644 (file)
@@ -35,7 +35,7 @@
 #include <linux/hrtimer.h>
 #define CREATE_TRACE_POINTS
 #include <trace/events/nvhost.h>
-
+#include <linux/pm_runtime.h>
 #include <linux/io.h>
 
 #include <linux/nvhost.h>
@@ -984,20 +984,24 @@ static int __devinit nvhost_probe(struct platform_device *pdev)
        if (err)
                goto fail;
 
+       pm_runtime_enable(&hostdev.dev);
        err = nvhost_module_init(&hostdev);
        if (err)
                goto fail;
 
        for (i = 0; i < host->nb_channels; i++) {
                struct nvhost_channel *ch = &host->channels[i];
+               pm_runtime_enable(&ch->dev->dev);
                nvhost_module_init(ch->dev);
        }
 
        platform_set_drvdata(pdev, host);
 
-       clk_enable(host->dev->clk[0]);
+       for (i = 0; i < host->dev->num_clks; i++)
+               clk_enable(host->dev->clk[i]);
        nvhost_syncpt_reset(&host->syncpt);
-       clk_disable(host->dev->clk[0]);
+       for (i = 0; i < host->dev->num_clks; i++)
+               clk_disable(host->dev->clk[i]);
 
        nvhost_debug_init(host);
 
@@ -1038,7 +1042,25 @@ static int nvhost_suspend(struct platform_device *pdev, pm_message_t state)
 
 static int nvhost_resume(struct platform_device *pdev)
 {
+       int i;
+       struct nvhost_master *host = platform_get_drvdata(pdev);
+
        dev_info(&pdev->dev, "resuming\n");
+
+       for (i = 0; i < host->dev->num_clks; i++)
+               clk_enable(host->dev->clk[i]);
+       if (host->dev->finalize_poweron)
+               host->dev->finalize_poweron(host->dev);
+       for (i = 0; i < host->dev->num_clks; i++)
+               clk_disable(host->dev->clk[i]);
+
+       /* enable runtime pm for host1x */
+       nvhost_module_resume(host->dev);
+
+       /* enable runtime pm for clients */
+       for (i = 0; i < host->nb_channels; i++)
+               nvhost_module_resume(host->channels[i].dev);
+
        return 0;
 }
 
index 06b09d2..c788f82 100644 (file)
@@ -275,8 +275,8 @@ static void t20_debug_show_channel_cdma(struct nvhost_master *m,
        cbstat = readl(m->sync_aperture + HOST1X_SYNC_CBSTAT_x(chid));
 
        nvhost_debug_output(o, "%d-%s (%d): ", chid,
-                           channel->dev->name,
-                           channel->dev->refcount);
+                   channel->dev->name,
+                   atomic_read(&channel->dev->dev.power.usage_count));
 
        if (HOST1X_VAL(CHANNEL_DMACTRL, DMASTOP, dmactrl)
                || !channel->cdma.push_buffer.mapped) {
index 622c8e0..00cca10 100644 (file)
@@ -74,9 +74,10 @@ static u32 t20_syncpt_update_min(struct nvhost_syncpt *sp, u32 id)
 
        if (!nvhost_syncpt_check_max(sp, id, live)) {
                dev_err(&syncpt_to_dev(sp)->pdev->dev,
-                               "%s failed: id=%u\n",
+                               "%s failed: id=%u, min=%d, max=%d\n",
                                __func__,
-                               id);
+                               id, atomic_read(&sp->min_val[id]),
+                                       atomic_read(&sp->max_val[id]));
                nvhost_debug_dump(syncpt_to_dev(sp));
                BUG();
        }
index a2386a2..351b70e 100644 (file)
 #include <linux/device.h>
 #include <linux/delay.h>
 #include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
 #include <mach/powergate.h>
 #include <mach/clk.h>
 #include <mach/hardware.h>
 
-#define ACM_SUSPEND_WAIT_FOR_IDLE_TIMEOUT (2 * HZ)
-#define POWERGATE_DELAY 10
-#define MAX_DEVID_LENGTH 16
+#define ACM_SUSPEND_WAIT_FOR_IDLE_TIMEOUT      (2 * HZ)
+#define POWERGATE_DELAY                        10
+#define MAX_DEVID_LENGTH                       16
 
 DEFINE_MUTEX(client_list_lock);
 
@@ -98,139 +99,113 @@ void nvhost_module_reset(struct nvhost_device *dev)
                __func__, dev->name);
 }
 
-static void to_state_clockgated_locked(struct nvhost_device *dev)
-{
-       if (dev->powerstate == NVHOST_POWER_STATE_RUNNING) {
-               int i;
-               for (i = 0; i < dev->num_clks; i++)
-                       clk_disable(dev->clk[i]);
-               if (dev->dev.parent)
-                       nvhost_module_idle(to_nvhost_device(dev->dev.parent));
-       } else if (dev->powerstate == NVHOST_POWER_STATE_POWERGATED
-                       && dev->can_powergate) {
-               do_unpowergate_locked(dev->powergate_ids[0]);
-               do_unpowergate_locked(dev->powergate_ids[1]);
-       }
-       dev->powerstate = NVHOST_POWER_STATE_CLOCKGATED;
-}
-
-static void to_state_running_locked(struct nvhost_device *dev)
-{
-       int prev_state = dev->powerstate;
-       if (dev->powerstate == NVHOST_POWER_STATE_POWERGATED)
-               to_state_clockgated_locked(dev);
-       if (dev->powerstate == NVHOST_POWER_STATE_CLOCKGATED) {
-               int i;
-
-               if (dev->dev.parent)
-                       nvhost_module_busy(to_nvhost_device(dev->dev.parent));
-
-               for (i = 0; i < dev->num_clks; i++) {
-                       int err = clk_enable(dev->clk[i]);
-                       BUG_ON(err);
-               }
-
-               if (prev_state == NVHOST_POWER_STATE_POWERGATED
-                               && dev->finalize_poweron)
-                       dev->finalize_poweron(dev);
-       }
-       dev->powerstate = NVHOST_POWER_STATE_RUNNING;
-}
-
 /* This gets called from powergate_handler() and from module suspend.
  * Module suspend is done for all modules, runtime power gating only
  * for modules with can_powergate set.
  */
-static int to_state_powergated_locked(struct nvhost_device *dev)
+static int to_state_powergated_locked(struct nvhost_device *dev,
+       bool system_suspend)
 {
-       int err = 0;
+       int err = 0, i = 0;
+
+       if (dev->prepare_poweroff && dev->powered) {
+               struct nvhost_device *device;
+               struct device *parent = dev->dev.parent;
+               if (parent)
+                       device = to_nvhost_device(parent);
+
+               if (system_suspend) {
+                       /* enable parent clock
+                        * host1x does not have parent */
+                       if (parent) {
+                               for (i = 0; i < device->num_clks; i++)
+                                       clk_enable(device->clk[i]);
+                       }
+
+                       /* enable module clock */
+                       for (i = 0; i < dev->num_clks; i++)
+                               clk_enable(dev->clk[i]);
+               } else
+                       pm_runtime_get_sync(&dev->dev);
 
-       if (dev->prepare_poweroff
-                       && dev->powerstate != NVHOST_POWER_STATE_POWERGATED) {
-               /* Clock needs to be on in prepare_poweroff */
-               to_state_running_locked(dev);
                err = dev->prepare_poweroff(dev);
                if (err)
                        return err;
-       }
 
-       if (dev->powerstate == NVHOST_POWER_STATE_RUNNING)
-               to_state_clockgated_locked(dev);
+               if (system_suspend) {
+                       /* disable module clock */
+                       for (i = 0; i < dev->num_clks; i++)
+                               clk_disable(dev->clk[i]);
+
+                       /* disable parent clock
+                        * host1x does not have parent */
+                       if (parent) {
+                               for (i = 0; i < device->num_clks; i++)
+                                       clk_disable(device->clk[i]);
+                       }
+               } else
+                       pm_runtime_put_sync_suspend(&dev->dev);
+       }
 
        if (dev->can_powergate) {
                do_powergate_locked(dev->powergate_ids[0]);
                do_powergate_locked(dev->powergate_ids[1]);
+               dev->powered = false;
        }
 
-       dev->powerstate = NVHOST_POWER_STATE_POWERGATED;
        return 0;
 }
 
-static void schedule_powergating_locked(struct nvhost_device *dev)
-{
-       if (dev->can_powergate)
-               schedule_delayed_work(&dev->powerstate_down,
-                               msecs_to_jiffies(dev->powergate_delay));
-}
-
-static void schedule_clockgating_locked(struct nvhost_device *dev)
-{
-       schedule_delayed_work(&dev->powerstate_down,
-                       msecs_to_jiffies(dev->clockgate_delay));
-}
-
 void nvhost_module_busy(struct nvhost_device *dev)
 {
        if (dev->busy)
                dev->busy(dev);
 
        mutex_lock(&dev->lock);
-       cancel_delayed_work(&dev->powerstate_down);
 
-       dev->refcount++;
-       if (dev->refcount > 0 && !nvhost_module_powered(dev))
-               to_state_running_locked(dev);
-       mutex_unlock(&dev->lock);
-}
-
-static void powerstate_down_handler(struct work_struct *work)
-{
-       struct nvhost_device *dev;
-
-       dev = container_of(to_delayed_work(work),
-                       struct nvhost_device,
-                       powerstate_down);
-
-       mutex_lock(&dev->lock);
-       if (dev->refcount == 0) {
-               switch (dev->powerstate) {
-               case NVHOST_POWER_STATE_RUNNING:
-                       to_state_clockgated_locked(dev);
-                       schedule_powergating_locked(dev);
-                       break;
-               case NVHOST_POWER_STATE_CLOCKGATED:
-                       if (to_state_powergated_locked(dev))
-                               schedule_powergating_locked(dev);
-                       break;
-               default:
-                       break;
+       if (dev->can_powergate) {
+               /* cancel power-gate handler */
+               cancel_delayed_work_sync(&dev->powerstate_down);
+
+               /* unpowergate the module if it was power gated */
+               if (!dev->powered) {
+                       do_unpowergate_locked(dev->powergate_ids[0]);
+                       do_unpowergate_locked(dev->powergate_ids[1]);
+                       dev->powered = true;
                }
        }
+
+       pm_runtime_get_sync(&dev->dev);
+
        mutex_unlock(&dev->lock);
 }
 
+static bool is_module_idle(struct nvhost_device *dev, bool system_suspend)
+{
+       /* for system suspend, pm core holds a reference on runtime pm.
+        * this is for kernels >= 3.x, it is not there for kernels < 3.x.
+        * for more details refer the LKML thread:
+        * https://lkml.org/lkml/2011/6/25/93
+        * https://lkml.org/lkml/2011/6/25/94
+        * https://lkml.org/lkml/2011/6/25/95 */
+       if (system_suspend)
+               return atomic_read(&dev->dev.power.usage_count) == 1;
+       else
+               return atomic_read(&dev->dev.power.usage_count) == 0;
+}
 
 void nvhost_module_idle_mult(struct nvhost_device *dev, int refs)
 {
        bool kick = false;
+       int i;
 
        mutex_lock(&dev->lock);
-       dev->refcount -= refs;
-       if (dev->refcount == 0) {
-               if (nvhost_module_powered(dev))
-                       schedule_clockgating_locked(dev);
+       for (i = 0; i < refs; i++)
+               pm_runtime_put_sync(&dev->dev);
+
+       if (is_module_idle(dev, false))
                kick = true;
-       }
+
        mutex_unlock(&dev->lock);
 
        if (kick) {
@@ -241,6 +216,19 @@ void nvhost_module_idle_mult(struct nvhost_device *dev, int refs)
        }
 }
 
+static void powerstate_down_handler(struct work_struct *work)
+{
+       struct nvhost_device *dev;
+
+       dev = container_of(to_delayed_work(work), struct nvhost_device,
+                       powerstate_down);
+
+       mutex_lock(&dev->lock);
+       if (dev->can_powergate)
+               to_state_powergated_locked(dev, false);
+       mutex_unlock(&dev->lock);
+}
+
 int nvhost_module_get_rate(struct nvhost_device *dev, unsigned long *rate,
                int index)
 {
@@ -255,7 +243,6 @@ int nvhost_module_get_rate(struct nvhost_device *dev, unsigned long *rate,
        *rate = clk_get_rate(c);
        nvhost_module_idle(dev);
        return 0;
-
 }
 
 static int nvhost_module_update_rate(struct nvhost_device *dev, int index)
@@ -293,7 +280,6 @@ int nvhost_module_set_rate(struct nvhost_device *dev, void *priv,
        ret = nvhost_module_update_rate(dev, index);
        mutex_unlock(&client_list_lock);
        return ret;
-
 }
 
 int nvhost_module_add_client(struct nvhost_device *dev, void *priv)
@@ -366,29 +352,23 @@ int nvhost_module_init(struct nvhost_device *dev)
 
        mutex_init(&dev->lock);
        init_waitqueue_head(&dev->idle_wq);
-       INIT_DELAYED_WORK(&dev->powerstate_down, powerstate_down_handler);
 
        /* power gate units that we can power gate */
        if (dev->can_powergate) {
                do_powergate_locked(dev->powergate_ids[0]);
                do_powergate_locked(dev->powergate_ids[1]);
-               dev->powerstate = NVHOST_POWER_STATE_POWERGATED;
+               INIT_DELAYED_WORK(&dev->powerstate_down, powerstate_down_handler);
+               dev->powered = false;
        } else {
                do_unpowergate_locked(dev->powergate_ids[0]);
                do_unpowergate_locked(dev->powergate_ids[1]);
-               dev->powerstate = NVHOST_POWER_STATE_CLOCKGATED;
+               dev->powered = true;
        }
 
-       return 0;
-}
+       /* enable runtime pm */
+       nvhost_module_resume(dev);
 
-static int is_module_idle(struct nvhost_device *dev)
-{
-       int count;
-       mutex_lock(&dev->lock);
-       count = dev->refcount;
-       mutex_unlock(&dev->lock);
-       return (count == 0);
+       return 0;
 }
 
 static void debug_not_idle(struct nvhost_master *host)
@@ -401,8 +381,8 @@ static void debug_not_idle(struct nvhost_master *host)
                mutex_lock(&dev->lock);
                if (dev->name)
                        dev_warn(&host->pdev->dev,
-                                       "tegra_grhost: %s: refcnt %d\n",
-                                       dev->name, dev->refcount);
+                               "tegra_grhost: %s: refcnt %d\n", dev->name,
+                               atomic_read(&dev->dev.power.usage_count));
                mutex_unlock(&dev->lock);
        }
 
@@ -424,10 +404,10 @@ int nvhost_module_suspend(struct nvhost_device *dev, bool system_suspend)
        int ret;
        struct nvhost_master *host = nvhost_get_host(dev);
 
-       if (system_suspend && !is_module_idle(dev))
+       if (system_suspend && !is_module_idle(dev, system_suspend))
                debug_not_idle(host);
 
-       ret = wait_event_timeout(dev->idle_wq, is_module_idle(dev),
+       ret = wait_event_timeout(dev->idle_wq, is_module_idle(dev, system_suspend),
                        ACM_SUSPEND_WAIT_FOR_IDLE_TIMEOUT);
        if (ret == 0) {
                dev_info(&dev->dev, "%s prevented suspend\n",
@@ -436,16 +416,20 @@ int nvhost_module_suspend(struct nvhost_device *dev, bool system_suspend)
        }
 
        if (system_suspend)
-               dev_dbg(&dev->dev, "tegra_grhost: entered idle\n");
+               dev_info(&dev->dev, "tegra_grhost: entered idle\n");
 
        mutex_lock(&dev->lock);
-       cancel_delayed_work(&dev->powerstate_down);
-       to_state_powergated_locked(dev);
+       if (dev->can_powergate)
+               cancel_delayed_work_sync(&dev->powerstate_down);
+       to_state_powergated_locked(dev, system_suspend);
        mutex_unlock(&dev->lock);
 
        if (dev->suspend)
                dev->suspend(dev);
 
+       if (system_suspend)
+               pm_runtime_set_suspended(&dev->dev);
+
        return 0;
 }
 
@@ -459,6 +443,10 @@ void nvhost_module_deinit(struct nvhost_device *dev)
        nvhost_module_suspend(dev, false);
        for (i = 0; i < dev->num_clks; i++)
                clk_put(dev->clk[i]);
-       dev->powerstate = NVHOST_POWER_STATE_DEINIT;
 }
 
+void nvhost_module_resume(struct nvhost_device *dev)
+{
+       pm_runtime_set_autosuspend_delay(&dev->dev, dev->clockgate_delay);
+       pm_runtime_use_autosuspend(&dev->dev);
+}
index 06a11ff..68b18f6 100644 (file)
 #include <linux/mutex.h>
 #include <linux/clk.h>
 #include <linux/nvhost.h>
+#include <linux/pm_runtime.h>
 
 /* Sets clocks and powergating state for a module */
 int nvhost_module_init(struct nvhost_device *ndev);
 void nvhost_module_deinit(struct nvhost_device *dev);
 int nvhost_module_suspend(struct nvhost_device *dev, bool system_suspend);
-
+void nvhost_module_resume(struct nvhost_device *dev);
 void nvhost_module_reset(struct nvhost_device *dev);
 void nvhost_module_busy(struct nvhost_device *dev);
 void nvhost_module_idle_mult(struct nvhost_device *dev, int refs);
@@ -50,7 +51,7 @@ int nvhost_module_set_rate(struct nvhost_device *dev, void *priv,
 
 static inline bool nvhost_module_powered(struct nvhost_device *dev)
 {
-       return dev->powerstate == NVHOST_POWER_STATE_RUNNING;
+       return !pm_runtime_suspended(&dev->dev);
 }
 
 static inline void nvhost_module_idle(struct nvhost_device *dev)
@@ -58,5 +59,4 @@ static inline void nvhost_module_idle(struct nvhost_device *dev)
        nvhost_module_idle_mult(dev, 1);
 }
 
-
 #endif
index c7b0c82..8457b23 100644 (file)
@@ -151,7 +151,7 @@ int nvhost_channel_suspend(struct nvhost_channel *ch)
        BUG_ON(!channel_cdma_op(ch).stop);
 
        if (ch->refcount) {
-               ret = nvhost_module_suspend(ch->dev, false);
+               ret = nvhost_module_suspend(ch->dev, true);
                if (!ret)
                        channel_cdma_op(ch).stop(&ch->cdma);
        }
index 55b75d0..9002620 100644 (file)
@@ -38,13 +38,6 @@ struct nvhost_clock {
        long default_rate;
 };
 
-enum nvhost_device_powerstate_t {
-       NVHOST_POWER_STATE_DEINIT,
-       NVHOST_POWER_STATE_RUNNING,
-       NVHOST_POWER_STATE_CLOCKGATED,
-       NVHOST_POWER_STATE_POWERGATED
-};
-
 struct nvhost_device {
        const char      *name;          /* Device name */
        struct device   dev;            /* Linux device struct */
@@ -74,8 +67,7 @@ struct nvhost_device {
        int             num_clks;       /* Number of clocks opened for dev */
        struct clk      *clk[NVHOST_MODULE_MAX_CLOCKS];
        struct mutex    lock;           /* Power management lock */
-       int             powerstate;     /* Current power state */
-       int             refcount;       /* Number of tasks active */
+       bool            powered;        /* Current power state */
        wait_queue_head_t idle_wq;      /* Work queue for idle */
        struct list_head client_list;   /* List of clients and rate requests */