summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorElaine K. Tam <etam@nvidia.com>2011-07-05 15:29:09 -0700
committerManish Tuteja <mtuteja@nvidia.com>2011-07-26 08:46:33 -0700
commit6a434f3128c10e1abaa36724f70c692a4fb5640d (patch)
tree171e02f2374788a3e23ae8e53569825dd7b7d3a0
parent505e7035f0bd6545eb578d85382dc5fdf8c53f42 (diff)
video: tegra: host: Clear refcnts+syncpts on suspend
Forcibly clear channel refcounts and syncpoints on suspend. Non-zero refcounts and stuck syncpoints remain to be root-caused. - Refcount clear portion is based on fix for Bug 820450 (but with fixup from var mod to m); - Increase wait timeout (6s) for module idle at suspend, wait indefinitely for module idle on retry (otherwise fatal); - Ensure that syncpoint fixups apply only to non-client-managed syncpoints, for case system_suspend == TRUE only (not ACM idle); - Add extra debug logging (see force_suspend) to track potential powerdown races, etc. Bug 834337 Change-Id: I513d77dd841f5ec499c8bb7a08806bbe08a733e5 Reviewed-on: http://git-master/r/39716 Tested-by: Elaine Tam <etam@nvidia.com> Reviewed-by: Terje Bergstrom <tbergstrom@nvidia.com> Reviewed-by: Bharat Nihalani <bnihalani@nvidia.com>
-rwxr-xr-xdrivers/video/tegra/host/nvhost_acm.c91
-rw-r--r--drivers/video/tegra/host/nvhost_acm.h1
-rw-r--r--drivers/video/tegra/host/nvhost_syncpt.h17
3 files changed, 95 insertions, 14 deletions
diff --git a/drivers/video/tegra/host/nvhost_acm.c b/drivers/video/tegra/host/nvhost_acm.c
index 88b0c0e46751..1168a380e105 100755
--- a/drivers/video/tegra/host/nvhost_acm.c
+++ b/drivers/video/tegra/host/nvhost_acm.c
@@ -27,10 +27,12 @@
#include <linux/device.h>
#include <mach/powergate.h>
#include <mach/clk.h>
+#include <nvhost_syncpt.h>
#include "dev.h"
#define ACM_TIMEOUT_MSEC 25
+#define SUSPEND_TIMEOUT 6*HZ /* As per __device_suspend timer.expires */
#define DISABLE_MPE_POWERGATING
@@ -38,6 +40,13 @@ void nvhost_module_busy(struct nvhost_module *mod)
{
mutex_lock(&mod->lock);
cancel_delayed_work(&mod->powerdown);
+
+ if (mod->force_suspend) {
+ pr_warn("tegra_grhost: module_busy despite %s force_suspend!\n",
+ mod->name);
+ WARN_ON(1);
+ }
+
if ((atomic_inc_return(&mod->refcount) == 1) && !mod->powered) {
if (mod->parent)
nvhost_module_busy(mod->parent);
@@ -60,9 +69,12 @@ void nvhost_module_busy(struct nvhost_module *mod)
static void powerdown_handler(struct work_struct *work)
{
struct nvhost_module *mod;
+ int refcount;
+
mod = container_of(to_delayed_work(work), struct nvhost_module, powerdown);
mutex_lock(&mod->lock);
- if ((atomic_read(&mod->refcount) == 0) && mod->powered) {
+ refcount = atomic_read(&mod->refcount);
+ if ((refcount == 0) && mod->powered) {
int i;
if (mod->func)
mod->func(mod, NVHOST_POWER_ACTION_OFF);
@@ -77,6 +89,12 @@ static void powerdown_handler(struct work_struct *work)
if (mod->parent)
nvhost_module_idle(mod->parent);
}
+ else if (mod->force_suspend) {
+ pr_warn("tegra_grhost: module %s (refcnt %d)"
+ " force_suspend powerdown skipped!\n",
+ mod->name, refcount);
+ }
+ mod->force_suspend = false;
mutex_unlock(&mod->lock);
}
@@ -150,6 +168,7 @@ int nvhost_module_init(struct nvhost_module *mod, const char *name,
mod->parent = parent;
mod->powered = false;
mod->powergate_id = get_module_powergate_id(name);
+ mod->force_suspend = false;
#if CONFIG_DISABLE_3D_POWERGATING
/*
@@ -203,39 +222,83 @@ static void debug_not_idle(struct nvhost_module *mod)
for (i = 0; i < NVHOST_NUMCHANNELS; i++) {
struct nvhost_module *m = &dev->channels[i].mod;
if (m->name)
- printk("tegra_grhost: %s: refcnt %d\n",
+ pr_warn("tegra_grhost: %s: refcnt %d\n",
m->name, atomic_read(&m->refcount));
}
for (i = 0; i < NV_HOST1X_SYNC_MLOCK_NUM; i++) {
int c = atomic_read(&dev->cpuaccess.lock_counts[i]);
if (c) {
- printk("tegra_grhost: lock id %d: refcnt %d\n", i, c);
+ pr_warn("tegra_grhost: lock id %d: refcnt %d\n", i, c);
lock_released = false;
}
}
if (lock_released)
- printk("tegra_grhost: all locks released\n");
+ pr_warn("tegra_grhost: all locks released\n");
+
+ nvhost_debug_dump();
}
void nvhost_module_suspend(struct nvhost_module *mod, bool system_suspend)
{
- int ret;
+ int i = 0, idle_timeout;
+ struct nvhost_master *dev;
+
+ if (system_suspend) {
+ idle_timeout = SUSPEND_TIMEOUT;
+ dev = container_of(mod, struct nvhost_master, mod);
+ }
+ else {
+ idle_timeout = msecs_to_jiffies(ACM_TIMEOUT_MSEC + 500);
+ dev = container_of(mod, struct nvhost_channel, mod)->dev;
+ }
- if (system_suspend && (!is_module_idle(mod)))
- debug_not_idle(mod);
+ if (!wait_event_timeout(mod->idle, is_module_idle(mod), idle_timeout)) {
+ /* Timeout occurred: clear refcnt forcibly */
+ mod->force_suspend = true;
+ for (i = 0; i < NVHOST_NUMCHANNELS; i++) {
+ struct nvhost_module *m = &dev->channels[i].mod;
+ if (m->name) {
+ int refcount = atomic_read(&m->refcount);
+ if (refcount != 0) {
+ pr_warn("tegra_grhost: %s: force refcnt %d to zero\n",
+ m->name, refcount);
+ nvhost_module_idle_mult(m, refcount);
+ flush_delayed_work(&m->powerdown);
+ }
+ }
+ }
+ wait_event(mod->idle, is_module_idle(mod)); /* no timeout: failure fatal */
+ }
- ret = wait_event_timeout(mod->idle, is_module_idle(mod),
- msecs_to_jiffies(ACM_TIMEOUT_MSEC + 500));
- if (ret == 0)
- nvhost_debug_dump();
+ if (system_suspend) {
+ /* Ensure that syncpoints are not stuck
+ *
+ * (In theory this check should apply even in the ACM case (i.e., when
+ * !system_suspend) due to module_idle, but at such times there have
+ * occasionally been shown to be a huge number of syncpoint fixups needed,
+ * bogging everything down. Since this is not fatal, until this is
+ * investigated further, we limit to system_suspend for now.) */
+ for (i = 0; i < NV_HOST1X_SYNCPT_NB_PTS; i++) {
+ if (BIT(i) & ~(NVSYNCPTS_HOST_MANAGED))
+ continue;
+ while (!nvhost_syncpt_min_eq_max(&dev->syncpt, i)) {
+ pr_warn("tegra_grhost: force syncpt id %d (%s) = %d + 1\n",
+ i, nvhost_syncpt_name(i),
+ nvhost_syncpt_read_min(&dev->syncpt, i));
+ nvhost_syncpt_incr_min(&dev->syncpt, i, 1);
+ }
+ }
- if (system_suspend)
- printk("tegra_grhost: entered idle\n");
+ if (!is_module_idle(mod))
+ debug_not_idle(mod);
+ else
+ pr_info("tegra_grhost: entered idle\n");
+ }
flush_delayed_work(&mod->powerdown);
if (system_suspend)
- printk("tegra_grhost: flushed delayed work\n");
+ pr_info("tegra_grhost: flushed delayed work\n");
BUG_ON(mod->powered);
}
diff --git a/drivers/video/tegra/host/nvhost_acm.h b/drivers/video/tegra/host/nvhost_acm.h
index f7e28af8e9cb..04bf018cfb05 100644
--- a/drivers/video/tegra/host/nvhost_acm.h
+++ b/drivers/video/tegra/host/nvhost_acm.h
@@ -51,6 +51,7 @@ struct nvhost_module {
wait_queue_head_t idle;
struct nvhost_module *parent;
int powergate_id;
+ bool force_suspend;
};
int nvhost_module_init(struct nvhost_module *mod, const char *name,
diff --git a/drivers/video/tegra/host/nvhost_syncpt.h b/drivers/video/tegra/host/nvhost_syncpt.h
index 992162b17e93..ef6785139b86 100644
--- a/drivers/video/tegra/host/nvhost_syncpt.h
+++ b/drivers/video/tegra/host/nvhost_syncpt.h
@@ -56,6 +56,14 @@
/*#define NVSYNCPT_2D_TINYBLT_WAR (30)*/
/*#define NVSYNCPT_2D_TINYBLT_RESTORE_CLASS_ID (30)*/
+#define NVSYNCPTS_VALID_MASK ( \
+ BIT(NVSYNCPT_GRAPHICS_HOST) | BIT(NVSYNCPT_VI_ISP_0) | BIT(NVSYNCPT_VI_ISP_1) | \
+ BIT(NVSYNCPT_VI_ISP_2) | BIT(NVSYNCPT_VI_ISP_3) | BIT(NVSYNCPT_VI_ISP_4) | \
+ BIT(NVSYNCPT_VI_ISP_5) | BIT(NVSYNCPT_2D_0) | BIT(NVSYNCPT_2D_1) | \
+ BIT(NVSYNCPT_3D) | BIT(NVSYNCPT_MPE) | BIT(NVSYNCPT_DISP0) | \
+ BIT(NVSYNCPT_DISP1) | BIT(NVSYNCPT_VBLANK0) | BIT(NVSYNCPT_VBLANK1) | \
+ BIT(NVSYNCPT_MPE_EBM_EOF) | BIT(NVSYNCPT_MPE_WR_SAFE) | BIT(NVSYNCPT_DSI))
+
/* sync points that are wholly managed by the client */
#define NVSYNCPTS_CLIENT_MANAGED ( \
BIT(NVSYNCPT_DISP0) | BIT(NVSYNCPT_DISP1) | BIT(NVSYNCPT_DSI) | \
@@ -64,6 +72,9 @@
BIT(NVSYNCPT_MPE_EBM_EOF) | BIT(NVSYNCPT_MPE_WR_SAFE) | \
BIT(NVSYNCPT_2D_1))
+/* Non-client-managed syncpoints */
+#define NVSYNCPTS_HOST_MANAGED (NVSYNCPTS_VALID_MASK & ~(NVSYNCPTS_CLIENT_MANAGED))
+
#define NVWAITBASE_2D_0 (1)
#define NVWAITBASE_2D_1 (2)
#define NVWAITBASE_3D (3)
@@ -84,6 +95,12 @@ static inline u32 nvhost_syncpt_incr_max(struct nvhost_syncpt *sp,
return (u32)atomic_add_return(incrs, &sp->max_val[id]);
}
+static inline u32 nvhost_syncpt_incr_min(struct nvhost_syncpt *sp,
+ u32 id, u32 incrs)
+{
+ return (u32)atomic_add_return(incrs, &sp->min_val[id]);
+}
+
/**
* Updated the value sent to hardware.
*/