diff options
author | Elaine K. Tam <etam@nvidia.com> | 2011-07-05 15:29:09 -0700 |
---|---|---|
committer | Manish Tuteja <mtuteja@nvidia.com> | 2011-07-26 08:46:33 -0700 |
commit | 6a434f3128c10e1abaa36724f70c692a4fb5640d (patch) | |
tree | 171e02f2374788a3e23ae8e53569825dd7b7d3a0 | |
parent | 505e7035f0bd6545eb578d85382dc5fdf8c53f42 (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-x | drivers/video/tegra/host/nvhost_acm.c | 91 | ||||
-rw-r--r-- | drivers/video/tegra/host/nvhost_acm.h | 1 | ||||
-rw-r--r-- | drivers/video/tegra/host/nvhost_syncpt.h | 17 |
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. */ |