drm/radeon: raise UVD clocks only on demand

That not only saves some power, but also solves problems with
older chips where an idle UVD block on higher clocks can
cause problems.

Signed-off-by: Christian König <christian.koenig@amd.com>
Signed-off-by: Alex Deucher <alexander.deucher@amd.com>
diff --git a/drivers/gpu/drm/radeon/radeon.h b/drivers/gpu/drm/radeon/radeon.h
index 5020c7c..7935370 100644
--- a/drivers/gpu/drm/radeon/radeon.h
+++ b/drivers/gpu/drm/radeon/radeon.h
@@ -1143,6 +1143,7 @@
 	uint64_t		gpu_addr;
 	atomic_t		handles[RADEON_MAX_UVD_HANDLES];
 	struct drm_file		*filp[RADEON_MAX_UVD_HANDLES];
+	struct delayed_work	idle_work;
 };
 
 int radeon_uvd_init(struct radeon_device *rdev);
@@ -1157,6 +1158,7 @@
 void radeon_uvd_free_handles(struct radeon_device *rdev,
 			     struct drm_file *filp);
 int radeon_uvd_cs_parse(struct radeon_cs_parser *parser);
+void radeon_uvd_note_usage(struct radeon_device *rdev);
 
 struct r600_audio {
 	int			channels;
diff --git a/drivers/gpu/drm/radeon/radeon_cs.c b/drivers/gpu/drm/radeon/radeon_cs.c
index c740707..7e265a5 100644
--- a/drivers/gpu/drm/radeon/radeon_cs.c
+++ b/drivers/gpu/drm/radeon/radeon_cs.c
@@ -549,6 +549,10 @@
 		r = radeon_cs_handle_lockup(rdev, r);
 		return r;
 	}
+
+	if (parser.ring == R600_RING_TYPE_UVD_INDEX)
+		radeon_uvd_note_usage(rdev);
+
 	r = radeon_cs_ib_chunk(rdev, &parser);
 	if (r) {
 		goto out;
diff --git a/drivers/gpu/drm/radeon/radeon_uvd.c b/drivers/gpu/drm/radeon/radeon_uvd.c
index 15580fb..0312a7f 100644
--- a/drivers/gpu/drm/radeon/radeon_uvd.c
+++ b/drivers/gpu/drm/radeon/radeon_uvd.c
@@ -36,6 +36,9 @@
 #include "radeon.h"
 #include "r600d.h"
 
+/* 1 second timeout */
+#define UVD_IDLE_TIMEOUT_MS	1000
+
 /* Firmware Names */
 #define FIRMWARE_RV710		"radeon/RV710_uvd.bin"
 #define FIRMWARE_CYPRESS	"radeon/CYPRESS_uvd.bin"
@@ -47,6 +50,8 @@
 MODULE_FIRMWARE(FIRMWARE_SUMO);
 MODULE_FIRMWARE(FIRMWARE_TAHITI);
 
+static void radeon_uvd_idle_work_handler(struct work_struct *work);
+
 int radeon_uvd_init(struct radeon_device *rdev)
 {
 	struct platform_device *pdev;
@@ -54,6 +59,8 @@
 	const char *fw_name;
 	int i, r;
 
+	INIT_DELAYED_WORK(&rdev->uvd.idle_work, radeon_uvd_idle_work_handler);
+
 	pdev = platform_device_register_simple("radeon_uvd", 0, NULL, 0);
 	r = IS_ERR(pdev);
 	if (r) {
@@ -188,8 +195,6 @@
 
 	radeon_bo_unreserve(rdev->uvd.vcpu_bo);
 
-	radeon_set_uvd_clocks(rdev, 53300, 40000);
-
 	return 0;
 }
 
@@ -666,3 +671,24 @@
 
 	return radeon_uvd_send_msg(rdev, ring, bo, fence);
 }
+
+static void radeon_uvd_idle_work_handler(struct work_struct *work)
+{
+	struct radeon_device *rdev =
+		container_of(work, struct radeon_device, uvd.idle_work.work);
+
+	if (radeon_fence_count_emitted(rdev, R600_RING_TYPE_UVD_INDEX) == 0)
+		radeon_set_uvd_clocks(rdev, 0, 0);
+	else
+		schedule_delayed_work(&rdev->uvd.idle_work,
+				      msecs_to_jiffies(UVD_IDLE_TIMEOUT_MS));
+}
+
+void radeon_uvd_note_usage(struct radeon_device *rdev)
+{
+	bool set_clocks = !cancel_delayed_work_sync(&rdev->uvd.idle_work);
+	set_clocks &= schedule_delayed_work(&rdev->uvd.idle_work,
+					    msecs_to_jiffies(UVD_IDLE_TIMEOUT_MS));
+	if (set_clocks)
+		radeon_set_uvd_clocks(rdev, 53300, 40000);
+}