diff options
author | Terje Bergstrom <tbergstrom@nvidia.com> | 2011-12-03 19:15:24 +0200 |
---|---|---|
committer | Varun Wadekar <vwadekar@nvidia.com> | 2011-12-15 11:49:22 +0530 |
commit | fcb2f71be8b30b612a642b8512f7cfe2dea9d5fc (patch) | |
tree | fd36999bc6f93f7d3903fb6167221e3e73af1955 | |
parent | 7b15aa600d2da8f4b9068b156abaf04124a91269 (diff) |
video: tegra: host: Fix race in suspend/resume
Implement proper mutex locking for nvhost_module_suspend(). At the same
time the ordering of suspend is changed to first suspend clients and
then host1x. This simplifies the power management code, and makes
nvhost_resume() a no-op.
Bug 906607
Change-Id: I60048773944369f73094140fb16682638966c731
Signed-off-by: Terje Bergstrom <tbergstrom@nvidia.com>
Reviewed-on: http://git-master/r/68084
Reviewed-by: Simone Willett <swillett@nvidia.com>
Tested-by: Simone Willett <swillett@nvidia.com>
-rw-r--r-- | drivers/video/tegra/host/dev.c | 17 | ||||
-rw-r--r-- | drivers/video/tegra/host/nvhost_acm.c | 90 | ||||
-rw-r--r-- | drivers/video/tegra/host/nvhost_acm.h | 2 | ||||
-rw-r--r-- | drivers/video/tegra/host/nvhost_channel.c | 1 | ||||
-rw-r--r-- | drivers/video/tegra/host/t20/debug_t20.c | 2 |
5 files changed, 58 insertions, 54 deletions
diff --git a/drivers/video/tegra/host/dev.c b/drivers/video/tegra/host/dev.c index fa5cbaeefa2b..ecb141fea904 100644 --- a/drivers/video/tegra/host/dev.c +++ b/drivers/video/tegra/host/dev.c @@ -776,16 +776,14 @@ static void power_on_host(struct nvhost_module *mod) container_of(mod, struct nvhost_master, mod); nvhost_intr_start(&dev->intr, clk_get_rate(mod->clk[0])); + nvhost_syncpt_reset(&dev->syncpt); } static int power_off_host(struct nvhost_module *mod) { struct nvhost_master *dev = container_of(mod, struct nvhost_master, mod); - int i; - for (i = 0; i < dev->nb_channels; i++) - nvhost_channel_suspend(&dev->channels[i]); nvhost_syncpt_save(&dev->syncpt); nvhost_intr_stop(&dev->intr); return 0; @@ -1058,23 +1056,20 @@ static int __exit nvhost_remove(struct platform_device *pdev) static int nvhost_suspend(struct platform_device *pdev, pm_message_t state) { struct nvhost_master *host = platform_get_drvdata(pdev); + int i; dev_info(&pdev->dev, "suspending\n"); + + for (i = 0; i < host->nb_channels; i++) + nvhost_channel_suspend(&host->channels[i]); + nvhost_module_suspend(&host->mod, true); - clk_enable(host->mod.clk[0]); - nvhost_syncpt_save(&host->syncpt); - clk_disable(host->mod.clk[0]); dev_info(&pdev->dev, "suspended\n"); return 0; } static int nvhost_resume(struct platform_device *pdev) { - struct nvhost_master *host = platform_get_drvdata(pdev); dev_info(&pdev->dev, "resuming\n"); - clk_enable(host->mod.clk[0]); - nvhost_syncpt_reset(&host->syncpt); - clk_disable(host->mod.clk[0]); - dev_info(&pdev->dev, "resumed\n"); return 0; } diff --git a/drivers/video/tegra/host/nvhost_acm.c b/drivers/video/tegra/host/nvhost_acm.c index 096864077b3c..6ea1466f63ce 100644 --- a/drivers/video/tegra/host/nvhost_acm.c +++ b/drivers/video/tegra/host/nvhost_acm.c @@ -44,13 +44,13 @@ struct nvhost_module_client { void *priv; }; -static void do_powergate(int id) +static void do_powergate_locked(int id) { if (id != -1 && tegra_powergate_is_powered(id)) tegra_powergate_partition(id); } -static void do_unpowergate(int id) +static void do_unpowergate_locked(int id) { if (id != -1) tegra_unpowergate_partition(id); @@ -63,6 +63,8 @@ void nvhost_module_reset(struct device *dev, struct nvhost_module *mod) __func__, mod->name, mod->desc->powergate_ids[0], mod->desc->powergate_ids[1]); + mutex_lock(&mod->lock); + /* assert module and mc client reset */ if (mod->desc->powergate_ids[0] != -1) { tegra_powergate_mc_disable(mod->desc->powergate_ids[0]); @@ -89,11 +91,13 @@ void nvhost_module_reset(struct device *dev, struct nvhost_module *mod) tegra_powergate_mc_enable(mod->desc->powergate_ids[1]); } + mutex_unlock(&mod->lock); + dev_dbg(dev, "%s: module %s out of reset\n", __func__, mod->name); } -static void to_state_clockgated(struct nvhost_module *mod) +static void to_state_clockgated_locked(struct nvhost_module *mod) { const struct nvhost_moduledesc *desc = mod->desc; if (mod->powerstate == NVHOST_POWER_STATE_RUNNING) { @@ -104,16 +108,17 @@ static void to_state_clockgated(struct nvhost_module *mod) nvhost_module_idle(mod->parent); } else if (mod->powerstate == NVHOST_POWER_STATE_POWERGATED && mod->desc->can_powergate) { - do_unpowergate(desc->powergate_ids[0]); - do_unpowergate(desc->powergate_ids[1]); + do_unpowergate_locked(desc->powergate_ids[0]); + do_unpowergate_locked(desc->powergate_ids[1]); } mod->powerstate = NVHOST_POWER_STATE_CLOCKGATED; } -static void to_state_running(struct nvhost_module *mod) +static void to_state_running_locked(struct nvhost_module *mod) { + int prev_state = mod->powerstate; if (mod->powerstate == NVHOST_POWER_STATE_POWERGATED) - to_state_clockgated(mod); + to_state_clockgated_locked(mod); if (mod->powerstate == NVHOST_POWER_STATE_CLOCKGATED) { int i; @@ -125,7 +130,8 @@ static void to_state_running(struct nvhost_module *mod) BUG_ON(err); } - if (mod->desc->finalize_poweron) + if (prev_state == NVHOST_POWER_STATE_POWERGATED + && mod->desc->finalize_poweron) mod->desc->finalize_poweron(mod); } mod->powerstate = NVHOST_POWER_STATE_RUNNING; @@ -135,39 +141,39 @@ static void to_state_running(struct nvhost_module *mod) * Module suspend is done for all modules, runtime power gating only * for modules with can_powergate set. */ -static int to_state_powergated(struct nvhost_module *mod) +static int to_state_powergated_locked(struct nvhost_module *mod) { int err = 0; if (mod->desc->prepare_poweroff && mod->powerstate != NVHOST_POWER_STATE_POWERGATED) { /* Clock needs to be on in prepare_poweroff */ - to_state_running(mod); + to_state_running_locked(mod); err = mod->desc->prepare_poweroff(mod); if (err) return err; } if (mod->powerstate == NVHOST_POWER_STATE_RUNNING) - to_state_clockgated(mod); + to_state_clockgated_locked(mod); if (mod->desc->can_powergate) { - do_powergate(mod->desc->powergate_ids[0]); - do_powergate(mod->desc->powergate_ids[1]); + do_powergate_locked(mod->desc->powergate_ids[0]); + do_powergate_locked(mod->desc->powergate_ids[1]); } mod->powerstate = NVHOST_POWER_STATE_POWERGATED; return 0; } -static void schedule_powergating(struct nvhost_module *mod) +static void schedule_powergating_locked(struct nvhost_module *mod) { if (mod->desc->can_powergate) schedule_delayed_work(&mod->powerstate_down, msecs_to_jiffies(mod->desc->powergate_delay)); } -static void schedule_clockgating(struct nvhost_module *mod) +static void schedule_clockgating_locked(struct nvhost_module *mod) { schedule_delayed_work(&mod->powerstate_down, msecs_to_jiffies(mod->desc->clockgate_delay)); @@ -175,14 +181,15 @@ static void schedule_clockgating(struct nvhost_module *mod) void nvhost_module_busy(struct nvhost_module *mod) { - mutex_lock(&mod->lock); - cancel_delayed_work(&mod->powerstate_down); if (mod->desc->busy) mod->desc->busy(mod); - if ((atomic_inc_return(&mod->refcount) > 0 - && !nvhost_module_powered(mod))) - to_state_running(mod); + mutex_lock(&mod->lock); + cancel_delayed_work(&mod->powerstate_down); + + mod->refcount++; + if (mod->refcount > 0 && !nvhost_module_powered(mod)) + to_state_running_locked(mod); mutex_unlock(&mod->lock); } @@ -195,15 +202,15 @@ static void powerstate_down_handler(struct work_struct *work) powerstate_down); mutex_lock(&mod->lock); - if ((atomic_read(&mod->refcount) == 0)) { + if (mod->refcount == 0) { switch (mod->powerstate) { case NVHOST_POWER_STATE_RUNNING: - to_state_clockgated(mod); - schedule_powergating(mod); + to_state_clockgated_locked(mod); + schedule_powergating_locked(mod); break; case NVHOST_POWER_STATE_CLOCKGATED: - if (to_state_powergated(mod)) - schedule_powergating(mod); + if (to_state_powergated_locked(mod)) + schedule_powergating_locked(mod); break; default: break; @@ -218,9 +225,10 @@ void nvhost_module_idle_mult(struct nvhost_module *mod, int refs) bool kick = false; mutex_lock(&mod->lock); - if (atomic_sub_return(refs, &mod->refcount) == 0) { + mod->refcount -= refs; + if (mod->refcount == 0) { if (nvhost_module_powered(mod)) - schedule_clockgating(mod); + schedule_clockgating_locked(mod); kick = true; } mutex_unlock(&mod->lock); @@ -358,11 +366,11 @@ void nvhost_module_preinit(const char *name, } if (desc->can_powergate) { - do_powergate(desc->powergate_ids[0]); - do_powergate(desc->powergate_ids[1]); + do_powergate_locked(desc->powergate_ids[0]); + do_powergate_locked(desc->powergate_ids[1]); } else { - do_unpowergate(desc->powergate_ids[0]); - do_unpowergate(desc->powergate_ids[1]); + do_unpowergate_locked(desc->powergate_ids[0]); + do_unpowergate_locked(desc->powergate_ids[1]); } } @@ -409,7 +417,7 @@ static int is_module_idle(struct nvhost_module *mod) { int count; mutex_lock(&mod->lock); - count = atomic_read(&mod->refcount); + count = mod->refcount; mutex_unlock(&mod->lock); return (count == 0); } @@ -420,10 +428,13 @@ static void debug_not_idle(struct nvhost_master *dev) bool lock_released = true; for (i = 0; i < dev->nb_channels; i++) { - struct nvhost_module *m = &dev->channels[i].mod; - if (m->name) - dev_warn(&dev->pdev->dev, "tegra_grhost: %s: refcnt %d\n", - m->name, atomic_read(&m->refcount)); + struct nvhost_module *mod = &dev->channels[i].mod; + mutex_lock(&mod->lock); + if (mod->name) + dev_warn(&dev->pdev->dev, + "tegra_grhost: %s: refcnt %d\n", + mod->name, mod->refcount); + mutex_unlock(&mod->lock); } for (i = 0; i < dev->nb_mlocks; i++) { @@ -460,11 +471,10 @@ void nvhost_module_suspend(struct nvhost_module *mod, bool system_suspend) if (system_suspend) dev_dbg(&dev->pdev->dev, "tegra_grhost: entered idle\n"); + mutex_lock(&mod->lock); cancel_delayed_work(&mod->powerstate_down); - to_state_powergated(mod); - - if (system_suspend) - dev_dbg(&dev->pdev->dev, "tegra_grhost: flushed delayed work\n"); + to_state_powergated_locked(mod); + mutex_unlock(&mod->lock); if (mod->desc->suspend) mod->desc->suspend(mod); diff --git a/drivers/video/tegra/host/nvhost_acm.h b/drivers/video/tegra/host/nvhost_acm.h index 06ef09ddf929..ce15c9916cd5 100644 --- a/drivers/video/tegra/host/nvhost_acm.h +++ b/drivers/video/tegra/host/nvhost_acm.h @@ -71,7 +71,7 @@ struct nvhost_module { struct clk *clk[NVHOST_MODULE_MAX_CLOCKS]; struct mutex lock; int powerstate; - atomic_t refcount; + int refcount; wait_queue_head_t idle; struct nvhost_module *parent; const struct nvhost_moduledesc *desc; diff --git a/drivers/video/tegra/host/nvhost_channel.c b/drivers/video/tegra/host/nvhost_channel.c index 8e19b582340d..262e4acdf0f2 100644 --- a/drivers/video/tegra/host/nvhost_channel.c +++ b/drivers/video/tegra/host/nvhost_channel.c @@ -127,7 +127,6 @@ void nvhost_putchannel(struct nvhost_channel *ch, struct nvhost_hwctx *ctx) void nvhost_channel_suspend(struct nvhost_channel *ch) { mutex_lock(&ch->reflock); - BUG_ON(nvhost_module_powered(&ch->mod)); BUG_ON(!channel_cdma_op(ch).stop); if (ch->refcount) { diff --git a/drivers/video/tegra/host/t20/debug_t20.c b/drivers/video/tegra/host/t20/debug_t20.c index 0c06649238d5..721d47e3acba 100644 --- a/drivers/video/tegra/host/t20/debug_t20.c +++ b/drivers/video/tegra/host/t20/debug_t20.c @@ -267,7 +267,7 @@ static void t20_debug_show_channel_cdma(struct nvhost_master *m, nvhost_debug_output(o, "%d-%s (%d): ", chid, channel->mod.name, - atomic_read(&channel->mod.refcount)); + channel->mod.refcount); if ((dmactrl & 1) || !channel->cdma.push_buffer.mapped) { nvhost_debug_output(o, "inactive\n\n"); |