summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMayuresh Kulkarni <mkulkarni@nvidia.com>2012-02-03 15:06:07 +0530
committerSimone Willett <swillett@nvidia.com>2012-02-13 09:27:15 -0800
commit410041d0247db2434a3013f16930d2fcd16256c8 (patch)
treebf70ce748d00dad01f80f199a1d82b5f31a0ba26
parente8dc4bd80bf2a31b46bf2bad034dcd514c1e11f5 (diff)
video: tegra: host: use runtime pm for clock management
- 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>
-rw-r--r--drivers/video/tegra/host/bus.c25
-rw-r--r--drivers/video/tegra/host/dev.c28
-rw-r--r--drivers/video/tegra/host/host1x/host1x_debug.c4
-rw-r--r--drivers/video/tegra/host/host1x/host1x_syncpt.c5
-rw-r--r--drivers/video/tegra/host/nvhost_acm.c228
-rw-r--r--drivers/video/tegra/host/nvhost_acm.h6
-rw-r--r--drivers/video/tegra/host/nvhost_channel.c2
-rw-r--r--include/linux/nvhost.h10
8 files changed, 164 insertions, 144 deletions
diff --git a/drivers/video/tegra/host/bus.c b/drivers/video/tegra/host/bus.c
index 8234d0fa64c3..8fd4aa830afe 100644
--- a/drivers/video/tegra/host/bus.c
+++ b/drivers/video/tegra/host/bus.c
@@ -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 */
diff --git a/drivers/video/tegra/host/dev.c b/drivers/video/tegra/host/dev.c
index 4cd1e4eaf843..c75448021484 100644
--- a/drivers/video/tegra/host/dev.c
+++ b/drivers/video/tegra/host/dev.c
@@ -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;
}
diff --git a/drivers/video/tegra/host/host1x/host1x_debug.c b/drivers/video/tegra/host/host1x/host1x_debug.c
index 06b09d20c55b..c788f82288c9 100644
--- a/drivers/video/tegra/host/host1x/host1x_debug.c
+++ b/drivers/video/tegra/host/host1x/host1x_debug.c
@@ -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) {
diff --git a/drivers/video/tegra/host/host1x/host1x_syncpt.c b/drivers/video/tegra/host/host1x/host1x_syncpt.c
index 622c8e049a7b..00cca103d18f 100644
--- a/drivers/video/tegra/host/host1x/host1x_syncpt.c
+++ b/drivers/video/tegra/host/host1x/host1x_syncpt.c
@@ -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();
}
diff --git a/drivers/video/tegra/host/nvhost_acm.c b/drivers/video/tegra/host/nvhost_acm.c
index a2386a257c8f..351b70e0b6fb 100644
--- a/drivers/video/tegra/host/nvhost_acm.c
+++ b/drivers/video/tegra/host/nvhost_acm.c
@@ -29,13 +29,14 @@
#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);
+}
diff --git a/drivers/video/tegra/host/nvhost_acm.h b/drivers/video/tegra/host/nvhost_acm.h
index 06a11ff840f2..68b18f6f65eb 100644
--- a/drivers/video/tegra/host/nvhost_acm.h
+++ b/drivers/video/tegra/host/nvhost_acm.h
@@ -28,12 +28,13 @@
#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
diff --git a/drivers/video/tegra/host/nvhost_channel.c b/drivers/video/tegra/host/nvhost_channel.c
index c7b0c82b3e45..8457b2398cc7 100644
--- a/drivers/video/tegra/host/nvhost_channel.c
+++ b/drivers/video/tegra/host/nvhost_channel.c
@@ -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);
}
diff --git a/include/linux/nvhost.h b/include/linux/nvhost.h
index 55b75d048791..9002620a834a 100644
--- a/include/linux/nvhost.h
+++ b/include/linux/nvhost.h
@@ -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 */