Subject: Re: [PATCH] drm/etnaviv: add etnaviv cooling device

From: Russell King <[email-masked]>
Date: Mon, 15 Aug 2016 18:57:37 +0100

From: Russell King <[email-masked]>

Each Vivante GPU contains a clock divider which can divide the GPU clock
by 2^n, which can lower the power dissipation from the GPU.  It has been
suggested that the GC600 on Dove can be responsible for 20-30% of the
power dissipation from the SoC.

Signed-off-by: Russell King <[email-masked]>
---
 drivers/gpu/drm/etnaviv/etnaviv_gpu.c | 85 ++++++++++++++++++++++++++++-------
 drivers/gpu/drm/etnaviv/etnaviv_gpu.h |  2 +
 2 files changed, 72 insertions(+), 15 deletions(-)

diff --git a/drivers/gpu/drm/etnaviv/etnaviv_gpu.c b/drivers/gpu/drm/etnaviv/etnaviv_gpu.c
index ff6aa5dfb2d7..ee989a1aa413 100644
--- a/drivers/gpu/drm/etnaviv/etnaviv_gpu.c
+++ b/drivers/gpu/drm/etnaviv/etnaviv_gpu.c
@@ -18,6 +18,8 @@
 #include <linux/fence.h>
 #include <linux/moduleparam.h>
 #include <linux/of_device.h>
+#include <linux/thermal.h>
+
 #include "etnaviv_dump.h"
 #include "etnaviv_gpu.h"
 #include "etnaviv_gem.h"
@@ -397,6 +399,17 @@ static void etnaviv_gpu_load_clock(struct etnaviv_gpu *gpu, u32 clock)
 	gpu_write(gpu, VIVS_HI_CLOCK_CONTROL, clock);
 }
 
+static void etnaviv_gpu_update_clock(struct etnaviv_gpu *gpu)
+{
+	unsigned int fscale = 1 << (6 - gpu->freq_scale);
+	u32 clock;
+
+	clock = VIVS_HI_CLOCK_CONTROL_DISABLE_DEBUG_REGISTERS |
+		VIVS_HI_CLOCK_CONTROL_FSCALE_VAL(fscale);
+
+	etnaviv_gpu_load_clock(gpu, clock);
+}
+
 static int etnaviv_hw_reset(struct etnaviv_gpu *gpu)
 {
 	u32 control, idle;
@@ -414,11 +427,10 @@ static int etnaviv_hw_reset(struct etnaviv_gpu *gpu)
 	timeout = jiffies + msecs_to_jiffies(1000);
 
 	while (time_is_after_jiffies(timeout)) {
-		control = VIVS_HI_CLOCK_CONTROL_DISABLE_DEBUG_REGISTERS |
-			  VIVS_HI_CLOCK_CONTROL_FSCALE_VAL(0x40);
-
 		/* enable clock */
-		etnaviv_gpu_load_clock(gpu, control);
+		etnaviv_gpu_update_clock(gpu);
+
+		control = gpu_read(gpu, VIVS_HI_CLOCK_CONTROL);
 
 		/* Wait for stable clock.  Vivante's code waited for 1ms */
 		usleep_range(1000, 10000);
@@ -478,11 +490,7 @@ static int etnaviv_hw_reset(struct etnaviv_gpu *gpu)
 	}
 
 	/* We rely on the GPU running, so program the clock */
-	control = VIVS_HI_CLOCK_CONTROL_DISABLE_DEBUG_REGISTERS |
-		  VIVS_HI_CLOCK_CONTROL_FSCALE_VAL(0x40);
-
-	/* enable clock */
-	etnaviv_gpu_load_clock(gpu, control);
+	etnaviv_gpu_update_clock(gpu);
 
 	return 0;
 }
@@ -1481,17 +1489,13 @@ static int etnaviv_gpu_hw_suspend(struct etnaviv_gpu *gpu)
 #ifdef CONFIG_PM
 static int etnaviv_gpu_hw_resume(struct etnaviv_gpu *gpu)
 {
-	u32 clock;
 	int ret;
 
 	ret = mutex_lock_killable(&gpu->lock);
 	if (ret)
 		return ret;
 
-	clock = VIVS_HI_CLOCK_CONTROL_DISABLE_DEBUG_REGISTERS |
-		VIVS_HI_CLOCK_CONTROL_FSCALE_VAL(0x40);
-
-	etnaviv_gpu_load_clock(gpu, clock);
+	etnaviv_gpu_update_clock(gpu);
 	etnaviv_gpu_hw_init(gpu);
 
 	gpu->switch_context = true;
@@ -1503,6 +1507,47 @@ static int etnaviv_gpu_hw_resume(struct etnaviv_gpu *gpu)
 }
 #endif
 
+static int
+etnaviv_gpu_cooling_get_max_state(struct thermal_cooling_device *cdev,
+				  unsigned long *state)
+{
+	*state = 6;
+
+	return 0;
+}
+
+static int
+etnaviv_gpu_cooling_get_cur_state(struct thermal_cooling_device *cdev,
+				  unsigned long *state)
+{
+	struct etnaviv_gpu *gpu = cdev->devdata;
+
+	*state = gpu->freq_scale;
+
+	return 0;
+}
+
+static int
+etnaviv_gpu_cooling_set_cur_state(struct thermal_cooling_device *cdev,
+				  unsigned long state)
+{
+	struct etnaviv_gpu *gpu = cdev->devdata;
+
+	mutex_lock(&gpu->lock);
+	gpu->freq_scale = state;
+	if (!pm_runtime_suspended(gpu->dev))
+		etnaviv_gpu_update_clock(gpu);
+	mutex_unlock(&gpu->lock);
+
+	return 0;
+}
+
+static struct thermal_cooling_device_ops cooling_ops = {
+	.get_max_state = etnaviv_gpu_cooling_get_max_state,
+	.get_cur_state = etnaviv_gpu_cooling_get_cur_state,
+	.set_cur_state = etnaviv_gpu_cooling_set_cur_state,
+};
+
 static int etnaviv_gpu_bind(struct device *dev, struct device *master,
 	void *data)
 {
@@ -1511,13 +1556,20 @@ static int etnaviv_gpu_bind(struct device *dev, struct device *master,
 	struct etnaviv_gpu *gpu = dev_get_drvdata(dev);
 	int ret;
 
+	gpu->cooling = thermal_of_cooling_device_register(dev->of_node,
+				(char *)dev_name(dev), gpu, &cooling_ops);
+	if (IS_ERR(gpu->cooling))
+		return PTR_ERR(gpu->cooling);
+
 #ifdef CONFIG_PM
 	ret = pm_runtime_get_sync(gpu->dev);
 #else
 	ret = etnaviv_gpu_clk_enable(gpu);
 #endif
-	if (ret < 0)
+	if (ret < 0) {
+		thermal_cooling_device_unregister(gpu->cooling);
 		return ret;
+	}
 
 	gpu->drm = drm;
 	gpu->fence_context = fence_context_alloc(1);
@@ -1566,6 +1618,9 @@ static void etnaviv_gpu_unbind(struct device *dev, struct device *master,
 	}
 
 	gpu->drm = NULL;
+
+	thermal_cooling_device_unregister(gpu->cooling);
+	gpu->cooling = NULL;
 }
 
 static const struct component_ops gpu_ops = {
diff --git a/drivers/gpu/drm/etnaviv/etnaviv_gpu.h b/drivers/gpu/drm/etnaviv/etnaviv_gpu.h
index f5321e2f25ff..0c7457d834dd 100644
--- a/drivers/gpu/drm/etnaviv/etnaviv_gpu.h
+++ b/drivers/gpu/drm/etnaviv/etnaviv_gpu.h
@@ -96,6 +96,7 @@ struct etnaviv_cmdbuf;
 
 struct etnaviv_gpu {
 	struct drm_device *drm;
+	struct thermal_cooling_device *cooling;
 	struct device *dev;
 	struct mutex lock;
 	struct etnaviv_chip_identity identity;
@@ -148,6 +149,7 @@ struct etnaviv_gpu {
 	u32 hangcheck_fence;
 	u32 hangcheck_dma_addr;
 	struct work_struct recover_work;
+	unsigned int freq_scale;
 };
 
 struct etnaviv_cmdbuf {

---

From: Russell King <[email-masked]>
Date: Mon, 15 Aug 2016 18:57:37 +0100

From: Russell King <[email-masked]>

A previous commit was dropped which allows unprivileged userspace to
query the capabilities of the GPU (eg, the viv_info tool.):

    staging: etnaviv: allow get_param without auth

    There is no need to restrict access to the get_param ioctl; this is
    used to obtain information about the device and has no side effects.

    Signed-off-by: Russell King <[email-masked]>

Signed-off-by: Russell King <[email-masked]>
---
 drivers/gpu/drm/etnaviv/etnaviv_drv.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/etnaviv/etnaviv_drv.c b/drivers/gpu/drm/etnaviv/etnaviv_drv.c
index 3d4f56df8359..0c4d63150c31 100644
--- a/drivers/gpu/drm/etnaviv/etnaviv_drv.c
+++ b/drivers/gpu/drm/etnaviv/etnaviv_drv.c
@@ -458,7 +458,7 @@ static int etnaviv_ioctl_gem_wait(struct drm_device *dev, void *data,
 static const struct drm_ioctl_desc etnaviv_ioctls[] = {
 #define ETNA_IOCTL(n, func, flags) \
 	DRM_IOCTL_DEF_DRV(ETNAVIV_##n, etnaviv_ioctl_##func, flags)
-	ETNA_IOCTL(GET_PARAM,    get_param,    DRM_AUTH|DRM_RENDER_ALLOW),
+	ETNA_IOCTL(GET_PARAM,    get_param,    DRM_RENDER_ALLOW),
 	ETNA_IOCTL(GEM_NEW,      gem_new,      DRM_AUTH|DRM_RENDER_ALLOW),
 	ETNA_IOCTL(GEM_INFO,     gem_info,     DRM_AUTH|DRM_RENDER_ALLOW),
 	ETNA_IOCTL(GEM_CPU_PREP, gem_cpu_prep, DRM_AUTH|DRM_RENDER_ALLOW),

---

From: Russell King <[email-masked]>
Date: Mon, 15 Aug 2016 18:57:37 +0100

Enable GPU module level hardware clock gating, using the conditions
found in the galcore v5 driver.

Signed-off-by: Russell King <[email-masked]>
---
 drivers/gpu/drm/etnaviv/etnaviv_gpu.c  | 44 ++++++++++++++++++++++++++++++++++
 drivers/gpu/drm/etnaviv/state_hi.xml.h |  7 ++++++
 2 files changed, 51 insertions(+)

diff --git a/drivers/gpu/drm/etnaviv/etnaviv_gpu.c b/drivers/gpu/drm/etnaviv/etnaviv_gpu.c
index ee989a1aa413..1942e56d1ef2 100644
--- a/drivers/gpu/drm/etnaviv/etnaviv_gpu.c
+++ b/drivers/gpu/drm/etnaviv/etnaviv_gpu.c
@@ -495,6 +495,47 @@ static int etnaviv_hw_reset(struct etnaviv_gpu *gpu)
 	return 0;
 }
 
+static void etnaviv_gpu_enable_mlcg(struct etnaviv_gpu *gpu)
+{
+	u32 pmc, ppc;
+
+	/* enable clock gating */
+	ppc = gpu_read(gpu, VIVS_PM_POWER_CONTROLS);
+	ppc |= VIVS_PM_POWER_CONTROLS_ENABLE_MODULE_CLOCK_GATING;
+
+	/* Disable stall module clock gating for 4.3.0.1 and 4.3.0.2 revs */
+	if (gpu->identity.revision == 0x4301 ||
+	    gpu->identity.revision == 0x4302)
+		ppc |= VIVS_PM_POWER_CONTROLS_DISABLE_STALL_MODULE_CLOCK_GATING;
+
+	gpu_write(gpu, VIVS_PM_POWER_CONTROLS, ppc);
+
+	pmc = gpu_read(gpu, VIVS_PM_MODULE_CONTROLS);
+
+	/* Disable PA clock gating for GC400+ except for GC420 */
+	if (gpu->identity.model >= chipModel_GC400 &&
+	    gpu->identity.model != chipModel_GC420)
+		pmc |= VIVS_PM_MODULE_CONTROLS_DISABLE_MODULE_CLOCK_GATING_PA;
+
+	/*
+	 * Disable PE clock gating on revs < 5.0.0.0 when HZ is
+	 * present without a bug fix.
+	 */
+	if (gpu->identity.revision < 0x5000 &&
+	    gpu->identity.minor_features0 & chipMinorFeatures0_HZ &&
+	    !(gpu->identity.minor_features1 &
+	      chipMinorFeatures1_DISABLE_PE_GATING))
+		pmc |= VIVS_PM_MODULE_CONTROLS_DISABLE_MODULE_CLOCK_GATING_PE;
+
+	if (gpu->identity.revision < 0x5422)
+		pmc |= BIT(15); /* Unknown bit */
+
+	pmc |= VIVS_PM_MODULE_CONTROLS_DISABLE_MODULE_CLOCK_GATING_RA_HZ;
+	pmc |= VIVS_PM_MODULE_CONTROLS_DISABLE_MODULE_CLOCK_GATING_RA_EZ;
+
+	gpu_write(gpu, VIVS_PM_MODULE_CONTROLS, pmc);
+}
+
 static void etnaviv_gpu_hw_init(struct etnaviv_gpu *gpu)
 {
 	u16 prefetch;
@@ -514,6 +555,9 @@ static void etnaviv_gpu_hw_init(struct etnaviv_gpu *gpu)
 		gpu_write(gpu, VIVS_MC_DEBUG_MEMORY, mc_memory_debug);
 	}
 
+	/* enable module-level clock gating */
+	etnaviv_gpu_enable_mlcg(gpu);
+
 	/*
 	 * Update GPU AXI cache atttribute to "cacheable, no allocate".
 	 * This is necessary to prevent the iMX6 SoC locking up.
diff --git a/drivers/gpu/drm/etnaviv/state_hi.xml.h b/drivers/gpu/drm/etnaviv/state_hi.xml.h
index 6a7de5f1454a..807a3d9e0dd5 100644
--- a/drivers/gpu/drm/etnaviv/state_hi.xml.h
+++ b/drivers/gpu/drm/etnaviv/state_hi.xml.h
@@ -218,6 +218,13 @@ Copyright (C) 2015
 #define VIVS_PM_MODULE_CONTROLS_DISABLE_MODULE_CLOCK_GATING_FE	0x00000001
 #define VIVS_PM_MODULE_CONTROLS_DISABLE_MODULE_CLOCK_GATING_DE	0x00000002
 #define VIVS_PM_MODULE_CONTROLS_DISABLE_MODULE_CLOCK_GATING_PE	0x00000004
+#define VIVS_PM_MODULE_CONTROLS_DISABLE_MODULE_CLOCK_GATING_SH	0x00000008
+#define VIVS_PM_MODULE_CONTROLS_DISABLE_MODULE_CLOCK_GATING_PA	0x00000010
+#define VIVS_PM_MODULE_CONTROLS_DISABLE_MODULE_CLOCK_GATING_SE	0x00000020
+#define VIVS_PM_MODULE_CONTROLS_DISABLE_MODULE_CLOCK_GATING_RA	0x00000040
+#define VIVS_PM_MODULE_CONTROLS_DISABLE_MODULE_CLOCK_GATING_TX	0x00000080
+#define VIVS_PM_MODULE_CONTROLS_DISABLE_MODULE_CLOCK_GATING_RA_EZ	0x00010000
+#define VIVS_PM_MODULE_CONTROLS_DISABLE_MODULE_CLOCK_GATING_RA_HZ	0x00020000
 
 #define VIVS_PM_MODULE_STATUS					0x00000108
 #define VIVS_PM_MODULE_STATUS_MODULE_CLOCK_GATED_FE		0x00000001

---

From: Russell King <[email-masked]>
Date: Mon, 15 Aug 2016 18:57:37 +0100

Parsing the command buffer is an expensive operation, as the command
buffer is uncached.  Work-around this by providing a control to allow
the command buffer parsing to be skipped.

A better solution would be to move to cached buffers between userspace
and kernelspace, and copying the data in kernel space to the final
command buffer.

Signed-off-by: Russell King <[email-masked]>
---
 drivers/gpu/drm/etnaviv/etnaviv_gem_submit.c |  3 ++-
 drivers/gpu/drm/etnaviv/etnaviv_gpu.c        | 27 +++++++++++++++++++++++++++
 drivers/gpu/drm/etnaviv/etnaviv_gpu.h        |  1 +
 3 files changed, 30 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/etnaviv/etnaviv_gem_submit.c b/drivers/gpu/drm/etnaviv/etnaviv_gem_submit.c
index afdd55ddf821..7bae554d6315 100644
--- a/drivers/gpu/drm/etnaviv/etnaviv_gem_submit.c
+++ b/drivers/gpu/drm/etnaviv/etnaviv_gem_submit.c
@@ -377,7 +377,8 @@ int etnaviv_ioctl_gem_submit(struct drm_device *dev, void *data,
 	if (ret)
 		goto err_submit_objects;
 
-	if (!etnaviv_cmd_validate_one(gpu, stream, args->stream_size / 4,
+	if (gpu->validate_cmdbuf &&
+	    !etnaviv_cmd_validate_one(gpu, stream, args->stream_size / 4,
 				      relocs, args->nr_relocs)) {
 		ret = -EINVAL;
 		goto err_submit_objects;
diff --git a/drivers/gpu/drm/etnaviv/etnaviv_gpu.c b/drivers/gpu/drm/etnaviv/etnaviv_gpu.c
index 1942e56d1ef2..0870ab01332a 100644
--- a/drivers/gpu/drm/etnaviv/etnaviv_gpu.c
+++ b/drivers/gpu/drm/etnaviv/etnaviv_gpu.c
@@ -1679,6 +1679,27 @@ static const struct of_device_id etnaviv_gpu_match[] = {
 	{ /* sentinel */ }
 };
 
+static ssize_t etnaviv_gpu_check_cmdbuf_show(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+	struct etnaviv_gpu *gpu = dev_get_drvdata(dev);
+
+	return snprintf(buf, PAGE_SIZE, "%u\n", gpu->validate_cmdbuf);
+}
+
+static ssize_t etnaviv_gpu_check_cmdbuf_store(struct device *dev,
+	struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct etnaviv_gpu *gpu = dev_get_drvdata(dev);
+
+	if (strtobool(buf, &gpu->validate_cmdbuf))
+		return -EINVAL;
+
+	return count;
+}
+
+static DEVICE_ATTR(check_cmdbuf, 0600, etnaviv_gpu_check_cmdbuf_show, etnaviv_gpu_check_cmdbuf_store);
+
 static int etnaviv_gpu_platform_probe(struct platform_device *pdev)
 {
 	struct device *dev = &pdev->dev;
@@ -1731,6 +1752,8 @@ static int etnaviv_gpu_platform_probe(struct platform_device *pdev)
 	/* TODO: figure out max mapped size */
 	dev_set_drvdata(dev, gpu);
 
+	gpu->validate_cmdbuf = true;
+
 	/*
 	 * We treat the device as initially suspended.  The runtime PM
 	 * autosuspend delay is rather arbitary: no measurements have
@@ -1740,9 +1763,12 @@ static int etnaviv_gpu_platform_probe(struct platform_device *pdev)
 	pm_runtime_set_autosuspend_delay(gpu->dev, 200);
 	pm_runtime_enable(gpu->dev);
 
+	device_create_file(gpu->dev, &dev_attr_check_cmdbuf);
+
 	err = component_add(&pdev->dev, &gpu_ops);
 	if (err < 0) {
 		dev_err(&pdev->dev, "failed to register component: %d\n", err);
+		device_remove_file(gpu->dev, &dev_attr_check_cmdbuf);
 		goto fail;
 	}
 
@@ -1756,6 +1782,7 @@ static int etnaviv_gpu_platform_remove(struct platform_device *pdev)
 {
 	component_del(&pdev->dev, &gpu_ops);
 	pm_runtime_disable(&pdev->dev);
+	device_remove_file(&pdev->dev, &dev_attr_check_cmdbuf);
 	return 0;
 }
 
diff --git a/drivers/gpu/drm/etnaviv/etnaviv_gpu.h b/drivers/gpu/drm/etnaviv/etnaviv_gpu.h
index 0c7457d834dd..1332802aa831 100644
--- a/drivers/gpu/drm/etnaviv/etnaviv_gpu.h
+++ b/drivers/gpu/drm/etnaviv/etnaviv_gpu.h
@@ -150,6 +150,7 @@ struct etnaviv_gpu {
 	u32 hangcheck_dma_addr;
 	struct work_struct recover_work;
 	unsigned int freq_scale;
+	bool validate_cmdbuf;
 };
 
 struct etnaviv_cmdbuf {

---

From: Russell King <[email-masked]>
Date: Mon, 15 Aug 2016 18:57:37 +0100

Signed-off-by: Russell King <[email-masked]>
---
 drivers/gpu/drm/etnaviv/etnaviv_gpu.c | 65 +++++++++++++++++++++++++++++------
 1 file changed, 55 insertions(+), 10 deletions(-)

diff --git a/drivers/gpu/drm/etnaviv/etnaviv_gpu.c b/drivers/gpu/drm/etnaviv/etnaviv_gpu.c
index 0870ab01332a..8948ab07ff36 100644
--- a/drivers/gpu/drm/etnaviv/etnaviv_gpu.c
+++ b/drivers/gpu/drm/etnaviv/etnaviv_gpu.c
@@ -1530,7 +1530,6 @@ static int etnaviv_gpu_hw_suspend(struct etnaviv_gpu *gpu)
 	return etnaviv_gpu_clk_disable(gpu);
 }
 
-#ifdef CONFIG_PM
 static int etnaviv_gpu_hw_resume(struct etnaviv_gpu *gpu)
 {
 	int ret;
@@ -1549,7 +1548,6 @@ static int etnaviv_gpu_hw_resume(struct etnaviv_gpu *gpu)
 
 	return 0;
 }
-#endif
 
 static int
 etnaviv_gpu_cooling_get_max_state(struct thermal_cooling_device *cdev,
@@ -1786,26 +1784,32 @@ static int etnaviv_gpu_platform_remove(struct platform_device *pdev)
 	return 0;
 }
 
-#ifdef CONFIG_PM
-static int etnaviv_gpu_rpm_suspend(struct device *dev)
+static bool etnaviv_gpu_is_idle(struct etnaviv_gpu *gpu, u32 mask)
+{
+	u32 idle;
+
+	mask = mask & gpu->idle_mask;
+	idle = gpu_read(gpu, VIVS_HI_IDLE_STATE) & mask;
+
+	return idle == mask;
+}
+
+static int __maybe_unused etnaviv_gpu_rpm_suspend(struct device *dev)
 {
 	struct etnaviv_gpu *gpu = dev_get_drvdata(dev);
-	u32 idle, mask;
 
 	/* If we have outstanding fences, we're not idle */
 	if (gpu->completed_fence != gpu->active_fence)
 		return -EBUSY;
 
 	/* Check whether the hardware (except FE) is idle */
-	mask = gpu->idle_mask & ~VIVS_HI_IDLE_STATE_FE;
-	idle = gpu_read(gpu, VIVS_HI_IDLE_STATE) & mask;
-	if (idle != mask)
+	if (!etnaviv_gpu_is_idle(gpu, ~VIVS_HI_IDLE_STATE_FE))
 		return -EBUSY;
 
 	return etnaviv_gpu_hw_suspend(gpu);
 }
 
-static int etnaviv_gpu_rpm_resume(struct device *dev)
+static int __maybe_unused etnaviv_gpu_rpm_resume(struct device *dev)
 {
 	struct etnaviv_gpu *gpu = dev_get_drvdata(dev);
 	int ret;
@@ -1825,11 +1829,52 @@ static int etnaviv_gpu_rpm_resume(struct device *dev)
 
 	return 0;
 }
-#endif
+
+static int __maybe_unused etnaviv_gpu_suspend(struct device *dev)
+{
+	struct etnaviv_gpu *gpu = dev_get_drvdata(dev);
+	int ret = 0;
+
+	if (!pm_runtime_suspended(dev) && gpu->drm) {
+		/*
+		 * Wait for the GPU to finish processing any commands
+		 * userspace has submitted before trying to suspend it; we
+		 * can't suspend the GPU with an incomplete command stream.
+		 * Note that this does not wait for the fences to be retired.
+		 */
+		ret = wait_event_timeout(gpu->fence_event,
+				 gpu->completed_fence == gpu->active_fence,
+				 msecs_to_jiffies(5000));
+		if (ret == 0) {
+			dev_crit(dev,
+				 "timeout while waiting for GPU to idle - rendering operations will be lost, suspending anyway\n");
+		} else {
+			/* Check whether the hardware (except FE) is idle */
+			if (!etnaviv_gpu_is_idle(gpu, ~VIVS_HI_IDLE_STATE_FE))
+				dev_crit(dev,
+					 "GPU is not idle - rendering operations will be lost, suspending anyway");
+		}
+
+		ret = etnaviv_gpu_hw_suspend(gpu);
+	}
+	return ret;
+}
+
+static int __maybe_unused etnaviv_gpu_resume(struct device *dev)
+{
+	struct etnaviv_gpu *gpu = dev_get_drvdata(dev);
+	int ret = 0;
+
+	if (!pm_runtime_suspended(dev) && gpu->drm)
+		ret = etnaviv_gpu_rpm_resume(dev);
+
+	return ret;
+}
 
 static const struct dev_pm_ops etnaviv_gpu_pm_ops = {
 	SET_RUNTIME_PM_OPS(etnaviv_gpu_rpm_suspend, etnaviv_gpu_rpm_resume,
 			   NULL)
+	SET_SYSTEM_SLEEP_PM_OPS(etnaviv_gpu_suspend, etnaviv_gpu_resume)
 };
 
 struct platform_driver etnaviv_gpu_driver = {

---

From: Russell King <[email-masked]>
Date: Mon, 15 Aug 2016 18:57:37 +0100

Try harder to reset the GPU at probe time by allowing the GPU to enter
runtime PM even if it indicates that it is busy.  Where platforms
implement PM domains, this allows the GPU domain to be power cycled,
which can recover a hung GPU.

This can't be done in normal operation as we need to drop the mutex,
and dropping the mutex will permit GPU submissions which will keep the
GPU from idling.

Signed-off-by: Russell King <[email-masked]>
---
 drivers/gpu/drm/etnaviv/etnaviv_gpu.c | 22 ++++++++++++++++++++--
 drivers/gpu/drm/etnaviv/etnaviv_gpu.h |  1 +
 2 files changed, 21 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/etnaviv/etnaviv_gpu.c b/drivers/gpu/drm/etnaviv/etnaviv_gpu.c
index 8948ab07ff36..57aa177ac348 100644
--- a/drivers/gpu/drm/etnaviv/etnaviv_gpu.c
+++ b/drivers/gpu/drm/etnaviv/etnaviv_gpu.c
@@ -599,7 +599,7 @@ static void etnaviv_gpu_hw_init(struct etnaviv_gpu *gpu)
 
 int etnaviv_gpu_init(struct etnaviv_gpu *gpu)
 {
-	int ret, i;
+	int ret, i, try;
 	struct iommu_domain *iommu;
 	enum etnaviv_iommu_version version;
 	bool mmuv2;
@@ -642,7 +642,20 @@ int etnaviv_gpu_init(struct etnaviv_gpu *gpu)
 			gpu->memory_base = dma_mask - SZ_2G + 1;
 	}
 
-	ret = etnaviv_hw_reset(gpu);
+	for (try = 0; try < 2; try++) {
+		ret = etnaviv_hw_reset(gpu);
+		if (ret == 0)
+			break;
+
+		dev_warn(gpu->dev, "GPU failed to reset, trying to power cycle\n");
+		gpu->force_rpm_idle = true;
+		pm_runtime_put_sync(gpu->dev);
+		msleep(100);
+		ret = pm_runtime_get_sync(gpu->dev);
+		gpu->force_rpm_idle = false;
+		if (ret)
+			break;
+	}
 	if (ret)
 		goto fail;
 
@@ -1798,6 +1811,11 @@ static int __maybe_unused etnaviv_gpu_rpm_suspend(struct device *dev)
 {
 	struct etnaviv_gpu *gpu = dev_get_drvdata(dev);
 
+	if (gpu->force_rpm_idle) {
+		etnaviv_gpu_clk_disable(gpu);
+		return 0;
+	}
+
 	/* If we have outstanding fences, we're not idle */
 	if (gpu->completed_fence != gpu->active_fence)
 		return -EBUSY;
diff --git a/drivers/gpu/drm/etnaviv/etnaviv_gpu.h b/drivers/gpu/drm/etnaviv/etnaviv_gpu.h
index 1332802aa831..f36c75ed3470 100644
--- a/drivers/gpu/drm/etnaviv/etnaviv_gpu.h
+++ b/drivers/gpu/drm/etnaviv/etnaviv_gpu.h
@@ -101,6 +101,7 @@ struct etnaviv_gpu {
 	struct mutex lock;
 	struct etnaviv_chip_identity identity;
 	struct etnaviv_file_private *lastctx;
+	bool force_rpm_idle;
 	bool switch_context;
 
 	/* 'ring'-buffer: */