drm/msm: crank down gpu when inactive
authorRob Clark <robdclark@gmail.com>
Sat, 11 Jan 2014 21:25:08 +0000 (16:25 -0500)
committerRob Clark <robdclark@gmail.com>
Mon, 31 Mar 2014 14:27:46 +0000 (10:27 -0400)
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
drivers/gpu/drm/msm/msm_drv.c
drivers/gpu/drm/msm/msm_gpu.c
drivers/gpu/drm/msm/msm_gpu.h

index 59ed7620fc1a07f1e12c900aeacafeb9d47ad3e9..e6cb2bc40ebe73791bdee7e1e1892e6696b89301 100644 (file)
@@ -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
 
index e6adafc7eff3177b789395f6bc81f4ab62042e11..e913efa4ea0a8a4f94fbbb8bd6778bee4a42bbd5 100644 (file)
@@ -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)
index 0cfe3f426ee4f4523d9e34c85427cd7cd37649a1..3e667ca1f2b9360d5904a8c5bb7f0e4b48e7f8fc 100644 (file)
@@ -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)
@@ -194,6 +212,55 @@ int msm_gpu_pm_suspend(struct msm_gpu *gpu)
        return 0;
 }
 
+/*
+ * 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);
 
index 458db8c64c28873f260aa59b44bbd96da7a3b18f..fad27008922fa0829f25af10d3cde5198530c87d 100644 (file)
@@ -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));