diff options
author | Mayuresh Kulkarni <mkulkarni@nvidia.com> | 2012-02-03 15:06:07 +0530 |
---|---|---|
committer | Simone Willett <swillett@nvidia.com> | 2012-02-13 09:27:15 -0800 |
commit | 410041d0247db2434a3013f16930d2fcd16256c8 (patch) | |
tree | bf70ce748d00dad01f80f199a1d82b5f31a0ba26 | |
parent | e8dc4bd80bf2a31b46bf2bad034dcd514c1e11f5 (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.c | 25 | ||||
-rw-r--r-- | drivers/video/tegra/host/dev.c | 28 | ||||
-rw-r--r-- | drivers/video/tegra/host/host1x/host1x_debug.c | 4 | ||||
-rw-r--r-- | drivers/video/tegra/host/host1x/host1x_syncpt.c | 5 | ||||
-rw-r--r-- | drivers/video/tegra/host/nvhost_acm.c | 228 | ||||
-rw-r--r-- | drivers/video/tegra/host/nvhost_acm.h | 6 | ||||
-rw-r--r-- | drivers/video/tegra/host/nvhost_channel.c | 2 | ||||
-rw-r--r-- | include/linux/nvhost.h | 10 |
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 */ |