diff mbox

[4/7] drm/msm: crank down gpu when inactive

Message ID 1394470062-27442-5-git-send-email-robdclark@gmail.com (mailing list archive)
State Accepted
Headers show

Commit Message

Rob Clark March 10, 2014, 4:47 p.m. UTC
Shut down the clks when the gpu has nothing to do.  A short inactivity
timer is used to provide a low pass filter for power transitions.

Signed-off-by: Rob Clark <robdclark@gmail.com>
---
 drivers/gpu/drm/msm/adreno/a3xx_gpu.c | 10 +++++
 drivers/gpu/drm/msm/msm_drv.c         |  7 ++-
 drivers/gpu/drm/msm/msm_gpu.c         | 85 +++++++++++++++++++++++++++++++++--
 drivers/gpu/drm/msm/msm_gpu.h         | 16 ++++++-
 4 files changed, 113 insertions(+), 5 deletions(-)

Comments

Jordan Crouse March 10, 2014, 8:24 p.m. UTC | #1
On 03/10/2014 10:47 AM, Rob Clark wrote:
> Shut down the clks when the gpu has nothing to do.  A short inactivity
> timer is used to provide a low pass filter for power transitions.

Good luck.  Power management will take years off your life.

> Signed-off-by: Rob Clark <robdclark@gmail.com>

Acked-by: Jordan Crouse <jcrouse@codeaurora.org>
> ---
>   drivers/gpu/drm/msm/adreno/a3xx_gpu.c | 10 +++++
>   drivers/gpu/drm/msm/msm_drv.c         |  7 ++-
>   drivers/gpu/drm/msm/msm_gpu.c         | 85 +++++++++++++++++++++++++++++++++--
>   drivers/gpu/drm/msm/msm_gpu.h         | 16 ++++++-
>   4 files changed, 113 insertions(+), 5 deletions(-)
>
> diff --git a/drivers/gpu/drm/msm/adreno/a3xx_gpu.c b/drivers/gpu/drm/msm/adreno/a3xx_gpu.c
> index 59ed762..e6cb2bc 100644
> --- a/drivers/gpu/drm/msm/adreno/a3xx_gpu.c
> +++ b/drivers/gpu/drm/msm/adreno/a3xx_gpu.c
> @@ -395,9 +395,15 @@ static const unsigned int a3xx_registers[] = {
>   #ifdef CONFIG_DEBUG_FS
>   static void a3xx_show(struct msm_gpu *gpu, struct seq_file *m)
>   {
> +	struct drm_device *dev = gpu->dev;
>   	int i;
>
>   	adreno_show(gpu, m);
> +
> +	mutex_lock(&dev->struct_mutex);
> +
> +	gpu->funcs->pm_resume(gpu);
> +
>   	seq_printf(m, "status:   %08x\n",
>   			gpu_read(gpu, REG_A3XX_RBBM_STATUS));
>
> @@ -413,6 +419,10 @@ static void a3xx_show(struct msm_gpu *gpu, struct seq_file *m)
>   			seq_printf(m, "IO:R %08x %08x\n", addr<<2, val);
>   		}
>   	}
> +
> +	gpu->funcs->pm_suspend(gpu);
> +
> +	mutex_unlock(&dev->struct_mutex);
>   }
>   #endif
>
> diff --git a/drivers/gpu/drm/msm/msm_drv.c b/drivers/gpu/drm/msm/msm_drv.c
> index e6adafc..e913efa 100644
> --- a/drivers/gpu/drm/msm/msm_drv.c
> +++ b/drivers/gpu/drm/msm/msm_drv.c
> @@ -311,7 +311,6 @@ static void load_gpu(struct drm_device *dev)
>   		gpu = NULL;
>   		/* not fatal */
>   	}
> -	mutex_unlock(&dev->struct_mutex);
>
>   	if (gpu) {
>   		int ret;
> @@ -321,10 +320,16 @@ static void load_gpu(struct drm_device *dev)
>   			dev_err(dev->dev, "gpu hw init failed: %d\n", ret);
>   			gpu->funcs->destroy(gpu);
>   			gpu = NULL;
> +		} else {
> +			/* give inactive pm a chance to kick in: */
> +			msm_gpu_retire(gpu);
>   		}
> +
>   	}
>
>   	priv->gpu = gpu;
> +
> +	mutex_unlock(&dev->struct_mutex);
>   }
>
>   static int msm_open(struct drm_device *dev, struct drm_file *file)
> diff --git a/drivers/gpu/drm/msm/msm_gpu.c b/drivers/gpu/drm/msm/msm_gpu.c
> index 0cfe3f4..3e667ca 100644
> --- a/drivers/gpu/drm/msm/msm_gpu.c
> +++ b/drivers/gpu/drm/msm/msm_gpu.c
> @@ -154,9 +154,18 @@ static int disable_axi(struct msm_gpu *gpu)
>
>   int msm_gpu_pm_resume(struct msm_gpu *gpu)
>   {
> +	struct drm_device *dev = gpu->dev;
>   	int ret;
>
> -	DBG("%s", gpu->name);
> +	DBG("%s: active_cnt=%d", gpu->name, gpu->active_cnt);
> +
> +	WARN_ON(!mutex_is_locked(&dev->struct_mutex));
> +
> +	if (gpu->active_cnt++ > 0)
> +		return 0;
> +
> +	if (WARN_ON(gpu->active_cnt <= 0))
> +		return -EINVAL;
>
>   	ret = enable_pwrrail(gpu);
>   	if (ret)
> @@ -175,9 +184,18 @@ int msm_gpu_pm_resume(struct msm_gpu *gpu)
>
>   int msm_gpu_pm_suspend(struct msm_gpu *gpu)
>   {
> +	struct drm_device *dev = gpu->dev;
>   	int ret;
>
> -	DBG("%s", gpu->name);
> +	DBG("%s: active_cnt=%d", gpu->name, gpu->active_cnt);
> +
> +	WARN_ON(!mutex_is_locked(&dev->struct_mutex));
> +
> +	if (--gpu->active_cnt > 0)
> +		return 0;
> +
> +	if (WARN_ON(gpu->active_cnt < 0))
> +		return -EINVAL;
>
>   	ret = disable_axi(gpu);
>   	if (ret)
> @@ -195,6 +213,55 @@ int msm_gpu_pm_suspend(struct msm_gpu *gpu)
>   }
>
>   /*
> + * Inactivity detection (for suspend):
> + */
> +
> +static void inactive_worker(struct work_struct *work)
> +{
> +	struct msm_gpu *gpu = container_of(work, struct msm_gpu, inactive_work);
> +	struct drm_device *dev = gpu->dev;
> +
> +	if (gpu->inactive)
> +		return;
> +
> +	DBG("%s: inactive!\n", gpu->name);
> +	mutex_lock(&dev->struct_mutex);
> +	if (!(msm_gpu_active(gpu) || gpu->inactive)) {
> +		disable_axi(gpu);
> +		disable_clk(gpu);
> +		gpu->inactive = true;
> +	}
> +	mutex_unlock(&dev->struct_mutex);
> +}
> +
> +static void inactive_handler(unsigned long data)
> +{
> +	struct msm_gpu *gpu = (struct msm_gpu *)data;
> +	struct msm_drm_private *priv = gpu->dev->dev_private;
> +
> +	queue_work(priv->wq, &gpu->inactive_work);
> +}
> +
> +/* cancel inactive timer and make sure we are awake: */
> +static void inactive_cancel(struct msm_gpu *gpu)
> +{
> +	DBG("%s", gpu->name);
> +	del_timer(&gpu->inactive_timer);
> +	if (gpu->inactive) {
> +		enable_clk(gpu);
> +		enable_axi(gpu);
> +		gpu->inactive = false;
> +	}
> +}
> +
> +static void inactive_start(struct msm_gpu *gpu)
> +{
> +	DBG("%s", gpu->name);
> +	mod_timer(&gpu->inactive_timer,
> +			round_jiffies_up(jiffies + DRM_MSM_INACTIVE_JIFFIES));
> +}
> +
> +/*
>    * Hangcheck detection for locked gpu:
>    */
>
> @@ -206,7 +273,10 @@ static void recover_worker(struct work_struct *work)
>   	dev_err(dev->dev, "%s: hangcheck recover!\n", gpu->name);
>
>   	mutex_lock(&dev->struct_mutex);
> -	gpu->funcs->recover(gpu);
> +	if (msm_gpu_active(gpu)) {
> +		inactive_cancel(gpu);
> +		gpu->funcs->recover(gpu);
> +	}
>   	mutex_unlock(&dev->struct_mutex);
>
>   	msm_gpu_retire(gpu);
> @@ -281,6 +351,9 @@ static void retire_worker(struct work_struct *work)
>   	}
>
>   	mutex_unlock(&dev->struct_mutex);
> +
> +	if (!msm_gpu_active(gpu))
> +		inactive_start(gpu);
>   }
>
>   /* call from irq handler to schedule work to retire bo's */
> @@ -302,6 +375,8 @@ int msm_gpu_submit(struct msm_gpu *gpu, struct msm_gem_submit *submit,
>
>   	gpu->submitted_fence = submit->fence;
>
> +	inactive_cancel(gpu);
> +
>   	ret = gpu->funcs->submit(gpu, submit, ctx);
>   	priv->lastctx = ctx;
>
> @@ -357,11 +432,15 @@ int msm_gpu_init(struct drm_device *drm, struct platform_device *pdev,
>   	gpu->dev = drm;
>   	gpu->funcs = funcs;
>   	gpu->name = name;
> +	gpu->inactive = true;
>
>   	INIT_LIST_HEAD(&gpu->active_list);
>   	INIT_WORK(&gpu->retire_work, retire_worker);
> +	INIT_WORK(&gpu->inactive_work, inactive_worker);
>   	INIT_WORK(&gpu->recover_work, recover_worker);
>
> +	setup_timer(&gpu->inactive_timer, inactive_handler,
> +			(unsigned long)gpu);
>   	setup_timer(&gpu->hangcheck_timer, hangcheck_handler,
>   			(unsigned long)gpu);
>
> diff --git a/drivers/gpu/drm/msm/msm_gpu.h b/drivers/gpu/drm/msm/msm_gpu.h
> index 458db8c..fad2700 100644
> --- a/drivers/gpu/drm/msm/msm_gpu.h
> +++ b/drivers/gpu/drm/msm/msm_gpu.h
> @@ -72,6 +72,10 @@ struct msm_gpu {
>
>   	uint32_t submitted_fence;
>
> +	/* is gpu powered/active? */
> +	int active_cnt;
> +	bool inactive;
> +
>   	/* worker for handling active-list retiring: */
>   	struct work_struct retire_work;
>
> @@ -91,7 +95,12 @@ struct msm_gpu {
>   	uint32_t bsc;
>   #endif
>
> -	/* Hang Detction: */
> +	/* Hang and Inactivity Detection:
> +	 */
> +#define DRM_MSM_INACTIVE_PERIOD   66 /* in ms (roughly four frames) */
> +#define DRM_MSM_INACTIVE_JIFFIES  msecs_to_jiffies(DRM_MSM_INACTIVE_PERIOD)
> +	struct timer_list inactive_timer;
> +	struct work_struct inactive_work;
>   #define DRM_MSM_HANGCHECK_PERIOD 500 /* in ms */
>   #define DRM_MSM_HANGCHECK_JIFFIES msecs_to_jiffies(DRM_MSM_HANGCHECK_PERIOD)
>   	struct timer_list hangcheck_timer;
> @@ -99,6 +108,11 @@ struct msm_gpu {
>   	struct work_struct recover_work;
>   };
>
> +static inline bool msm_gpu_active(struct msm_gpu *gpu)
> +{
> +	return gpu->submitted_fence > gpu->funcs->last_fence(gpu);
> +}
> +
>   static inline void gpu_write(struct msm_gpu *gpu, u32 reg, u32 data)
>   {
>   	msm_writel(data, gpu->mmio + (reg << 2));
>
Rob Clark March 10, 2014, 8:39 p.m. UTC | #2
On Mon, Mar 10, 2014 at 4:24 PM, Jordan Crouse <jcrouse@codeaurora.org> wrote:
> On 03/10/2014 10:47 AM, Rob Clark wrote:
>>
>> Shut down the clks when the gpu has nothing to do.  A short inactivity
>> timer is used to provide a low pass filter for power transitions.
>
>
> Good luck.  Power management will take years off your life.

that is why I started with something extremely simple ;-)

I've actually had this patch right around the time of the 3.14 merge
window, but didn't want to push this sort of thing in last minute.
I've been using it locally since then, and so far so good

BR,
-R

>
>> Signed-off-by: Rob Clark <robdclark@gmail.com>
>
>
> Acked-by: Jordan Crouse <jcrouse@codeaurora.org>
>
>> ---
>>   drivers/gpu/drm/msm/adreno/a3xx_gpu.c | 10 +++++
>>   drivers/gpu/drm/msm/msm_drv.c         |  7 ++-
>>   drivers/gpu/drm/msm/msm_gpu.c         | 85
>> +++++++++++++++++++++++++++++++++--
>>   drivers/gpu/drm/msm/msm_gpu.h         | 16 ++++++-
>>   4 files changed, 113 insertions(+), 5 deletions(-)
>>
>> diff --git a/drivers/gpu/drm/msm/adreno/a3xx_gpu.c
>> b/drivers/gpu/drm/msm/adreno/a3xx_gpu.c
>> index 59ed762..e6cb2bc 100644
>> --- a/drivers/gpu/drm/msm/adreno/a3xx_gpu.c
>> +++ b/drivers/gpu/drm/msm/adreno/a3xx_gpu.c
>> @@ -395,9 +395,15 @@ static const unsigned int a3xx_registers[] = {
>>   #ifdef CONFIG_DEBUG_FS
>>   static void a3xx_show(struct msm_gpu *gpu, struct seq_file *m)
>>   {
>> +       struct drm_device *dev = gpu->dev;
>>         int i;
>>
>>         adreno_show(gpu, m);
>> +
>> +       mutex_lock(&dev->struct_mutex);
>> +
>> +       gpu->funcs->pm_resume(gpu);
>> +
>>         seq_printf(m, "status:   %08x\n",
>>                         gpu_read(gpu, REG_A3XX_RBBM_STATUS));
>>
>> @@ -413,6 +419,10 @@ static void a3xx_show(struct msm_gpu *gpu, struct
>> seq_file *m)
>>                         seq_printf(m, "IO:R %08x %08x\n", addr<<2, val);
>>                 }
>>         }
>> +
>> +       gpu->funcs->pm_suspend(gpu);
>> +
>> +       mutex_unlock(&dev->struct_mutex);
>>   }
>>   #endif
>>
>> diff --git a/drivers/gpu/drm/msm/msm_drv.c b/drivers/gpu/drm/msm/msm_drv.c
>> index e6adafc..e913efa 100644
>> --- a/drivers/gpu/drm/msm/msm_drv.c
>> +++ b/drivers/gpu/drm/msm/msm_drv.c
>> @@ -311,7 +311,6 @@ static void load_gpu(struct drm_device *dev)
>>                 gpu = NULL;
>>                 /* not fatal */
>>         }
>> -       mutex_unlock(&dev->struct_mutex);
>>
>>         if (gpu) {
>>                 int ret;
>> @@ -321,10 +320,16 @@ static void load_gpu(struct drm_device *dev)
>>                         dev_err(dev->dev, "gpu hw init failed: %d\n",
>> ret);
>>                         gpu->funcs->destroy(gpu);
>>                         gpu = NULL;
>> +               } else {
>> +                       /* give inactive pm a chance to kick in: */
>> +                       msm_gpu_retire(gpu);
>>                 }
>> +
>>         }
>>
>>         priv->gpu = gpu;
>> +
>> +       mutex_unlock(&dev->struct_mutex);
>>   }
>>
>>   static int msm_open(struct drm_device *dev, struct drm_file *file)
>> diff --git a/drivers/gpu/drm/msm/msm_gpu.c b/drivers/gpu/drm/msm/msm_gpu.c
>> index 0cfe3f4..3e667ca 100644
>> --- a/drivers/gpu/drm/msm/msm_gpu.c
>> +++ b/drivers/gpu/drm/msm/msm_gpu.c
>> @@ -154,9 +154,18 @@ static int disable_axi(struct msm_gpu *gpu)
>>
>>   int msm_gpu_pm_resume(struct msm_gpu *gpu)
>>   {
>> +       struct drm_device *dev = gpu->dev;
>>         int ret;
>>
>> -       DBG("%s", gpu->name);
>> +       DBG("%s: active_cnt=%d", gpu->name, gpu->active_cnt);
>> +
>> +       WARN_ON(!mutex_is_locked(&dev->struct_mutex));
>> +
>> +       if (gpu->active_cnt++ > 0)
>> +               return 0;
>> +
>> +       if (WARN_ON(gpu->active_cnt <= 0))
>> +               return -EINVAL;
>>
>>         ret = enable_pwrrail(gpu);
>>         if (ret)
>> @@ -175,9 +184,18 @@ int msm_gpu_pm_resume(struct msm_gpu *gpu)
>>
>>   int msm_gpu_pm_suspend(struct msm_gpu *gpu)
>>   {
>> +       struct drm_device *dev = gpu->dev;
>>         int ret;
>>
>> -       DBG("%s", gpu->name);
>> +       DBG("%s: active_cnt=%d", gpu->name, gpu->active_cnt);
>> +
>> +       WARN_ON(!mutex_is_locked(&dev->struct_mutex));
>> +
>> +       if (--gpu->active_cnt > 0)
>> +               return 0;
>> +
>> +       if (WARN_ON(gpu->active_cnt < 0))
>> +               return -EINVAL;
>>
>>         ret = disable_axi(gpu);
>>         if (ret)
>> @@ -195,6 +213,55 @@ int msm_gpu_pm_suspend(struct msm_gpu *gpu)
>>   }
>>
>>   /*
>> + * Inactivity detection (for suspend):
>> + */
>> +
>> +static void inactive_worker(struct work_struct *work)
>> +{
>> +       struct msm_gpu *gpu = container_of(work, struct msm_gpu,
>> inactive_work);
>> +       struct drm_device *dev = gpu->dev;
>> +
>> +       if (gpu->inactive)
>> +               return;
>> +
>> +       DBG("%s: inactive!\n", gpu->name);
>> +       mutex_lock(&dev->struct_mutex);
>> +       if (!(msm_gpu_active(gpu) || gpu->inactive)) {
>> +               disable_axi(gpu);
>> +               disable_clk(gpu);
>> +               gpu->inactive = true;
>> +       }
>> +       mutex_unlock(&dev->struct_mutex);
>> +}
>> +
>> +static void inactive_handler(unsigned long data)
>> +{
>> +       struct msm_gpu *gpu = (struct msm_gpu *)data;
>> +       struct msm_drm_private *priv = gpu->dev->dev_private;
>> +
>> +       queue_work(priv->wq, &gpu->inactive_work);
>> +}
>> +
>> +/* cancel inactive timer and make sure we are awake: */
>> +static void inactive_cancel(struct msm_gpu *gpu)
>> +{
>> +       DBG("%s", gpu->name);
>> +       del_timer(&gpu->inactive_timer);
>> +       if (gpu->inactive) {
>> +               enable_clk(gpu);
>> +               enable_axi(gpu);
>> +               gpu->inactive = false;
>> +       }
>> +}
>> +
>> +static void inactive_start(struct msm_gpu *gpu)
>> +{
>> +       DBG("%s", gpu->name);
>> +       mod_timer(&gpu->inactive_timer,
>> +                       round_jiffies_up(jiffies +
>> DRM_MSM_INACTIVE_JIFFIES));
>> +}
>> +
>> +/*
>>    * Hangcheck detection for locked gpu:
>>    */
>>
>> @@ -206,7 +273,10 @@ static void recover_worker(struct work_struct *work)
>>         dev_err(dev->dev, "%s: hangcheck recover!\n", gpu->name);
>>
>>         mutex_lock(&dev->struct_mutex);
>> -       gpu->funcs->recover(gpu);
>> +       if (msm_gpu_active(gpu)) {
>> +               inactive_cancel(gpu);
>> +               gpu->funcs->recover(gpu);
>> +       }
>>         mutex_unlock(&dev->struct_mutex);
>>
>>         msm_gpu_retire(gpu);
>> @@ -281,6 +351,9 @@ static void retire_worker(struct work_struct *work)
>>         }
>>
>>         mutex_unlock(&dev->struct_mutex);
>> +
>> +       if (!msm_gpu_active(gpu))
>> +               inactive_start(gpu);
>>   }
>>
>>   /* call from irq handler to schedule work to retire bo's */
>> @@ -302,6 +375,8 @@ int msm_gpu_submit(struct msm_gpu *gpu, struct
>> msm_gem_submit *submit,
>>
>>         gpu->submitted_fence = submit->fence;
>>
>> +       inactive_cancel(gpu);
>> +
>>         ret = gpu->funcs->submit(gpu, submit, ctx);
>>         priv->lastctx = ctx;
>>
>> @@ -357,11 +432,15 @@ int msm_gpu_init(struct drm_device *drm, struct
>> platform_device *pdev,
>>         gpu->dev = drm;
>>         gpu->funcs = funcs;
>>         gpu->name = name;
>> +       gpu->inactive = true;
>>
>>         INIT_LIST_HEAD(&gpu->active_list);
>>         INIT_WORK(&gpu->retire_work, retire_worker);
>> +       INIT_WORK(&gpu->inactive_work, inactive_worker);
>>         INIT_WORK(&gpu->recover_work, recover_worker);
>>
>> +       setup_timer(&gpu->inactive_timer, inactive_handler,
>> +                       (unsigned long)gpu);
>>         setup_timer(&gpu->hangcheck_timer, hangcheck_handler,
>>                         (unsigned long)gpu);
>>
>> diff --git a/drivers/gpu/drm/msm/msm_gpu.h b/drivers/gpu/drm/msm/msm_gpu.h
>> index 458db8c..fad2700 100644
>> --- a/drivers/gpu/drm/msm/msm_gpu.h
>> +++ b/drivers/gpu/drm/msm/msm_gpu.h
>> @@ -72,6 +72,10 @@ struct msm_gpu {
>>
>>         uint32_t submitted_fence;
>>
>> +       /* is gpu powered/active? */
>> +       int active_cnt;
>> +       bool inactive;
>> +
>>         /* worker for handling active-list retiring: */
>>         struct work_struct retire_work;
>>
>> @@ -91,7 +95,12 @@ struct msm_gpu {
>>         uint32_t bsc;
>>   #endif
>>
>> -       /* Hang Detction: */
>> +       /* Hang and Inactivity Detection:
>> +        */
>> +#define DRM_MSM_INACTIVE_PERIOD   66 /* in ms (roughly four frames) */
>> +#define DRM_MSM_INACTIVE_JIFFIES
>> msecs_to_jiffies(DRM_MSM_INACTIVE_PERIOD)
>> +       struct timer_list inactive_timer;
>> +       struct work_struct inactive_work;
>>   #define DRM_MSM_HANGCHECK_PERIOD 500 /* in ms */
>>   #define DRM_MSM_HANGCHECK_JIFFIES
>> msecs_to_jiffies(DRM_MSM_HANGCHECK_PERIOD)
>>         struct timer_list hangcheck_timer;
>> @@ -99,6 +108,11 @@ struct msm_gpu {
>>         struct work_struct recover_work;
>>   };
>>
>> +static inline bool msm_gpu_active(struct msm_gpu *gpu)
>> +{
>> +       return gpu->submitted_fence > gpu->funcs->last_fence(gpu);
>> +}
>> +
>>   static inline void gpu_write(struct msm_gpu *gpu, u32 reg, u32 data)
>>   {
>>         msm_writel(data, gpu->mmio + (reg << 2));
>>
>
>
> --
> The Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum,
> hosted by The Linux Foundation
diff mbox

Patch

diff --git a/drivers/gpu/drm/msm/adreno/a3xx_gpu.c b/drivers/gpu/drm/msm/adreno/a3xx_gpu.c
index 59ed762..e6cb2bc 100644
--- a/drivers/gpu/drm/msm/adreno/a3xx_gpu.c
+++ b/drivers/gpu/drm/msm/adreno/a3xx_gpu.c
@@ -395,9 +395,15 @@  static const unsigned int a3xx_registers[] = {
 #ifdef CONFIG_DEBUG_FS
 static void a3xx_show(struct msm_gpu *gpu, struct seq_file *m)
 {
+	struct drm_device *dev = gpu->dev;
 	int i;
 
 	adreno_show(gpu, m);
+
+	mutex_lock(&dev->struct_mutex);
+
+	gpu->funcs->pm_resume(gpu);
+
 	seq_printf(m, "status:   %08x\n",
 			gpu_read(gpu, REG_A3XX_RBBM_STATUS));
 
@@ -413,6 +419,10 @@  static void a3xx_show(struct msm_gpu *gpu, struct seq_file *m)
 			seq_printf(m, "IO:R %08x %08x\n", addr<<2, val);
 		}
 	}
+
+	gpu->funcs->pm_suspend(gpu);
+
+	mutex_unlock(&dev->struct_mutex);
 }
 #endif
 
diff --git a/drivers/gpu/drm/msm/msm_drv.c b/drivers/gpu/drm/msm/msm_drv.c
index e6adafc..e913efa 100644
--- a/drivers/gpu/drm/msm/msm_drv.c
+++ b/drivers/gpu/drm/msm/msm_drv.c
@@ -311,7 +311,6 @@  static void load_gpu(struct drm_device *dev)
 		gpu = NULL;
 		/* not fatal */
 	}
-	mutex_unlock(&dev->struct_mutex);
 
 	if (gpu) {
 		int ret;
@@ -321,10 +320,16 @@  static void load_gpu(struct drm_device *dev)
 			dev_err(dev->dev, "gpu hw init failed: %d\n", ret);
 			gpu->funcs->destroy(gpu);
 			gpu = NULL;
+		} else {
+			/* give inactive pm a chance to kick in: */
+			msm_gpu_retire(gpu);
 		}
+
 	}
 
 	priv->gpu = gpu;
+
+	mutex_unlock(&dev->struct_mutex);
 }
 
 static int msm_open(struct drm_device *dev, struct drm_file *file)
diff --git a/drivers/gpu/drm/msm/msm_gpu.c b/drivers/gpu/drm/msm/msm_gpu.c
index 0cfe3f4..3e667ca 100644
--- a/drivers/gpu/drm/msm/msm_gpu.c
+++ b/drivers/gpu/drm/msm/msm_gpu.c
@@ -154,9 +154,18 @@  static int disable_axi(struct msm_gpu *gpu)
 
 int msm_gpu_pm_resume(struct msm_gpu *gpu)
 {
+	struct drm_device *dev = gpu->dev;
 	int ret;
 
-	DBG("%s", gpu->name);
+	DBG("%s: active_cnt=%d", gpu->name, gpu->active_cnt);
+
+	WARN_ON(!mutex_is_locked(&dev->struct_mutex));
+
+	if (gpu->active_cnt++ > 0)
+		return 0;
+
+	if (WARN_ON(gpu->active_cnt <= 0))
+		return -EINVAL;
 
 	ret = enable_pwrrail(gpu);
 	if (ret)
@@ -175,9 +184,18 @@  int msm_gpu_pm_resume(struct msm_gpu *gpu)
 
 int msm_gpu_pm_suspend(struct msm_gpu *gpu)
 {
+	struct drm_device *dev = gpu->dev;
 	int ret;
 
-	DBG("%s", gpu->name);
+	DBG("%s: active_cnt=%d", gpu->name, gpu->active_cnt);
+
+	WARN_ON(!mutex_is_locked(&dev->struct_mutex));
+
+	if (--gpu->active_cnt > 0)
+		return 0;
+
+	if (WARN_ON(gpu->active_cnt < 0))
+		return -EINVAL;
 
 	ret = disable_axi(gpu);
 	if (ret)
@@ -195,6 +213,55 @@  int msm_gpu_pm_suspend(struct msm_gpu *gpu)
 }
 
 /*
+ * Inactivity detection (for suspend):
+ */
+
+static void inactive_worker(struct work_struct *work)
+{
+	struct msm_gpu *gpu = container_of(work, struct msm_gpu, inactive_work);
+	struct drm_device *dev = gpu->dev;
+
+	if (gpu->inactive)
+		return;
+
+	DBG("%s: inactive!\n", gpu->name);
+	mutex_lock(&dev->struct_mutex);
+	if (!(msm_gpu_active(gpu) || gpu->inactive)) {
+		disable_axi(gpu);
+		disable_clk(gpu);
+		gpu->inactive = true;
+	}
+	mutex_unlock(&dev->struct_mutex);
+}
+
+static void inactive_handler(unsigned long data)
+{
+	struct msm_gpu *gpu = (struct msm_gpu *)data;
+	struct msm_drm_private *priv = gpu->dev->dev_private;
+
+	queue_work(priv->wq, &gpu->inactive_work);
+}
+
+/* cancel inactive timer and make sure we are awake: */
+static void inactive_cancel(struct msm_gpu *gpu)
+{
+	DBG("%s", gpu->name);
+	del_timer(&gpu->inactive_timer);
+	if (gpu->inactive) {
+		enable_clk(gpu);
+		enable_axi(gpu);
+		gpu->inactive = false;
+	}
+}
+
+static void inactive_start(struct msm_gpu *gpu)
+{
+	DBG("%s", gpu->name);
+	mod_timer(&gpu->inactive_timer,
+			round_jiffies_up(jiffies + DRM_MSM_INACTIVE_JIFFIES));
+}
+
+/*
  * Hangcheck detection for locked gpu:
  */
 
@@ -206,7 +273,10 @@  static void recover_worker(struct work_struct *work)
 	dev_err(dev->dev, "%s: hangcheck recover!\n", gpu->name);
 
 	mutex_lock(&dev->struct_mutex);
-	gpu->funcs->recover(gpu);
+	if (msm_gpu_active(gpu)) {
+		inactive_cancel(gpu);
+		gpu->funcs->recover(gpu);
+	}
 	mutex_unlock(&dev->struct_mutex);
 
 	msm_gpu_retire(gpu);
@@ -281,6 +351,9 @@  static void retire_worker(struct work_struct *work)
 	}
 
 	mutex_unlock(&dev->struct_mutex);
+
+	if (!msm_gpu_active(gpu))
+		inactive_start(gpu);
 }
 
 /* call from irq handler to schedule work to retire bo's */
@@ -302,6 +375,8 @@  int msm_gpu_submit(struct msm_gpu *gpu, struct msm_gem_submit *submit,
 
 	gpu->submitted_fence = submit->fence;
 
+	inactive_cancel(gpu);
+
 	ret = gpu->funcs->submit(gpu, submit, ctx);
 	priv->lastctx = ctx;
 
@@ -357,11 +432,15 @@  int msm_gpu_init(struct drm_device *drm, struct platform_device *pdev,
 	gpu->dev = drm;
 	gpu->funcs = funcs;
 	gpu->name = name;
+	gpu->inactive = true;
 
 	INIT_LIST_HEAD(&gpu->active_list);
 	INIT_WORK(&gpu->retire_work, retire_worker);
+	INIT_WORK(&gpu->inactive_work, inactive_worker);
 	INIT_WORK(&gpu->recover_work, recover_worker);
 
+	setup_timer(&gpu->inactive_timer, inactive_handler,
+			(unsigned long)gpu);
 	setup_timer(&gpu->hangcheck_timer, hangcheck_handler,
 			(unsigned long)gpu);
 
diff --git a/drivers/gpu/drm/msm/msm_gpu.h b/drivers/gpu/drm/msm/msm_gpu.h
index 458db8c..fad2700 100644
--- a/drivers/gpu/drm/msm/msm_gpu.h
+++ b/drivers/gpu/drm/msm/msm_gpu.h
@@ -72,6 +72,10 @@  struct msm_gpu {
 
 	uint32_t submitted_fence;
 
+	/* is gpu powered/active? */
+	int active_cnt;
+	bool inactive;
+
 	/* worker for handling active-list retiring: */
 	struct work_struct retire_work;
 
@@ -91,7 +95,12 @@  struct msm_gpu {
 	uint32_t bsc;
 #endif
 
-	/* Hang Detction: */
+	/* Hang and Inactivity Detection:
+	 */
+#define DRM_MSM_INACTIVE_PERIOD   66 /* in ms (roughly four frames) */
+#define DRM_MSM_INACTIVE_JIFFIES  msecs_to_jiffies(DRM_MSM_INACTIVE_PERIOD)
+	struct timer_list inactive_timer;
+	struct work_struct inactive_work;
 #define DRM_MSM_HANGCHECK_PERIOD 500 /* in ms */
 #define DRM_MSM_HANGCHECK_JIFFIES msecs_to_jiffies(DRM_MSM_HANGCHECK_PERIOD)
 	struct timer_list hangcheck_timer;
@@ -99,6 +108,11 @@  struct msm_gpu {
 	struct work_struct recover_work;
 };
 
+static inline bool msm_gpu_active(struct msm_gpu *gpu)
+{
+	return gpu->submitted_fence > gpu->funcs->last_fence(gpu);
+}
+
 static inline void gpu_write(struct msm_gpu *gpu, u32 reg, u32 data)
 {
 	msm_writel(data, gpu->mmio + (reg << 2));