From 7f22a1394f4d09e63423b6f384be8ba3ad8a446e Mon Sep 17 00:00:00 2001
From: root <root@linux-g0th.site>
Date: Thu, 10 Dec 2009 01:53:17 +0100
Subject: [PATCH] drm/radeon/kms: add dynamic engine reclocking (v2)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Rafał Miłecki <zajec5@gmail.com>
---
 drivers/gpu/drm/radeon/r600.c            |    4 +
 drivers/gpu/drm/radeon/radeon.h          |   22 ++++
 drivers/gpu/drm/radeon/radeon_device.c   |    1 +
 drivers/gpu/drm/radeon/radeon_encoders.c |    7 +
 drivers/gpu/drm/radeon/radeon_pm.c       |  180 +++++++++++++++++++++++++++++-
 5 files changed, 212 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/radeon/r600.c b/drivers/gpu/drm/radeon/r600.c
index 26947e8..1531c70 100644
--- a/drivers/gpu/drm/radeon/r600.c
+++ b/drivers/gpu/drm/radeon/r600.c
@@ -2286,6 +2286,8 @@ restart_ih:
 			case 0: /* D1 vblank */
 				if (disp_int & LB_D1_VBLANK_INTERRUPT) {
 					drm_handle_vblank(rdev->ddev, 0);
+					if (rdev->pm.vblank_callback)
+						queue_work(rdev->pm.wq, &rdev->pm.reclock_work);
 					disp_int &= ~LB_D1_VBLANK_INTERRUPT;
 					DRM_DEBUG("IH: D1 vblank\n");
 				}
@@ -2306,6 +2308,8 @@ restart_ih:
 			case 0: /* D2 vblank */
 				if (disp_int & LB_D2_VBLANK_INTERRUPT) {
 					drm_handle_vblank(rdev->ddev, 1);
+					if (rdev->pm.vblank_callback)
+						queue_work(rdev->pm.wq, &rdev->pm.reclock_work);
 					disp_int &= ~LB_D2_VBLANK_INTERRUPT;
 					DRM_DEBUG("IH: D2 vblank\n");
 				}
diff --git a/drivers/gpu/drm/radeon/radeon.h b/drivers/gpu/drm/radeon/radeon.h
index 57416d2..1da8137 100644
--- a/drivers/gpu/drm/radeon/radeon.h
+++ b/drivers/gpu/drm/radeon/radeon.h
@@ -146,6 +146,7 @@ struct radeon_clock {
  * Power management
  */
 int radeon_pm_init(struct radeon_device *rdev);
+void radeon_pm_compute_clocks(struct radeon_device *rdev);
 
 /*
  * Fences.
@@ -558,7 +559,28 @@ struct radeon_wb {
  * Equation between gpu/memory clock and available bandwidth is hw dependent
  * (type of memory, bus size, efficiency, ...)
  */
+enum radeon_pm_state {
+	RADEON_PM_STATE_DISABLED,
+	RADEON_PM_STATE_PAUSED,
+	RADEON_PM_STATE_ACTIVE
+};
+enum radeon_pm_action {
+	RADEON_PM_ACTION_NONE,
+	RADEON_PM_ACTION_DOWNCLOCK,
+	RADEON_PM_ACTION_UPCLOCK
+};
 struct radeon_pm {
+	struct mutex		mutex;
+	struct workqueue_struct	*wq;
+	struct work_struct	reclock_work;
+	struct delayed_work	idle_work;
+	enum radeon_pm_state	state;
+	enum radeon_pm_action	planned_action;
+	unsigned long		action_timeout;
+	bool 			downclocked;
+	bool			vblank_callback;
+	uint32_t		min_mode_engine_clock;
+	uint32_t		min_mode_memory_clock;
 	fixed20_12		max_bandwidth;
 	fixed20_12		igp_sideport_mclk;
 	fixed20_12		igp_system_mclk;
diff --git a/drivers/gpu/drm/radeon/radeon_device.c b/drivers/gpu/drm/radeon/radeon_device.c
index 60ee6a8..9c3a015 100644
--- a/drivers/gpu/drm/radeon/radeon_device.c
+++ b/drivers/gpu/drm/radeon/radeon_device.c
@@ -567,6 +567,7 @@ int radeon_device_init(struct radeon_device *rdev,
 	if (rdev->family >= CHIP_R600)
 		spin_lock_init(&rdev->ih.lock);
 	mutex_init(&rdev->gem.mutex);
+	mutex_init(&rdev->pm.mutex);
 	rwlock_init(&rdev->fence_drv.lock);
 	INIT_LIST_HEAD(&rdev->gem.objects);
 
diff --git a/drivers/gpu/drm/radeon/radeon_encoders.c b/drivers/gpu/drm/radeon/radeon_encoders.c
index b4f23ec..93e88f9 100644
--- a/drivers/gpu/drm/radeon/radeon_encoders.c
+++ b/drivers/gpu/drm/radeon/radeon_encoders.c
@@ -216,6 +216,9 @@ static bool radeon_atom_mode_fixup(struct drm_encoder *encoder,
 	struct drm_device *dev = encoder->dev;
 	struct radeon_device *rdev = dev->dev_private;
 
+	/* adjust pm to upcoming mode change */
+	radeon_pm_compute_clocks(rdev);
+
 	/* set the active encoder to connector routing */
 	radeon_encoder_set_active_device(encoder);
 	drm_mode_set_crtcinfo(adjusted_mode, 0);
@@ -1022,6 +1025,10 @@ radeon_atom_encoder_dpms(struct drm_encoder *encoder, int mode)
 		atom_execute_table(rdev->mode_info.atom_context, index, (uint32_t *)&args);
 	}
 	radeon_atombios_encoder_dpms_scratch_regs(encoder, (mode == DRM_MODE_DPMS_ON) ? true : false);
+	
+	/* adjust pm to mode change */
+	if (mode == DRM_MODE_DPMS_OFF)
+		radeon_pm_compute_clocks(rdev);
 }
 
 union crtc_sourc_param {
diff --git a/drivers/gpu/drm/radeon/radeon_pm.c b/drivers/gpu/drm/radeon/radeon_pm.c
index 46146c6..f8e7ba8 100644
--- a/drivers/gpu/drm/radeon/radeon_pm.c
+++ b/drivers/gpu/drm/radeon/radeon_pm.c
@@ -22,7 +22,12 @@
 #include "drmP.h"
 #include "radeon.h"
 
-int radeon_debugfs_pm_init(struct radeon_device *rdev);
+#define RADEON_IDLE_LOOP_MS 100
+#define RADEON_RECLOCK_DELAY_MS 200
+
+static void radeon_pm_reclock_work_handler(struct work_struct *work);
+static void radeon_pm_idle_work_handler(struct work_struct *work);
+static int radeon_debugfs_pm_init(struct radeon_device *rdev);
 
 int radeon_pm_init(struct radeon_device *rdev)
 {
@@ -30,9 +35,180 @@ int radeon_pm_init(struct radeon_device *rdev)
 		DRM_ERROR("Failed to register debugfs file for CP !\n");
 	}
 
+	rdev->pm.state = RADEON_PM_STATE_DISABLED;
+	rdev->pm.planned_action = RADEON_PM_ACTION_NONE;
+	rdev->pm.downclocked = false;
+	rdev->pm.vblank_callback = false;
+
+	rdev->pm.wq = create_workqueue("radeon");
+	if (!rdev->pm.wq) {
+		DRM_ERROR("Failed to create workqueue\n");
+		return -EINVAL;
+	}
+	INIT_WORK(&rdev->pm.reclock_work, radeon_pm_reclock_work_handler);
+	INIT_DELAYED_WORK(&rdev->pm.idle_work, radeon_pm_idle_work_handler);
+
+	if (radeon_dynclks != -1 && radeon_dynclks) {
+		rdev->pm.state = RADEON_PM_STATE_PAUSED;
+		DRM_INFO("radeon: dynamic clocking enabled\n");
+	}
+
+	DRM_INFO("radeon: power management initialized\n");
+
 	return 0;
 }
 
+void radeon_pm_compute_clocks(struct radeon_device *rdev)
+{
+	struct drm_device *ddev = rdev->ddev;
+	struct drm_connector *connector;
+	int count = 0;
+
+	if (rdev->pm.state == RADEON_PM_STATE_DISABLED)
+		return;
+
+	mutex_lock(&rdev->pm.mutex);
+
+	list_for_each_entry(connector,
+		&ddev->mode_config.connector_list, head) {
+		if (connector->encoder &&
+			connector->dpms != DRM_MODE_DPMS_OFF) {
+			++count;
+		}
+	}
+
+	if (count > 1) {
+		if (rdev->pm.state == RADEON_PM_STATE_ACTIVE) {
+			wait_queue_head_t wait;
+			init_waitqueue_head(&wait);
+
+			cancel_delayed_work(&rdev->pm.idle_work);
+
+			rdev->pm.state = RADEON_PM_STATE_PAUSED;
+			rdev->pm.planned_action = RADEON_PM_ACTION_UPCLOCK;
+			rdev->pm.vblank_callback = true;
+
+			mutex_unlock(&rdev->pm.mutex);
+
+			wait_event_timeout(wait, !rdev->pm.downclocked,
+				msecs_to_jiffies(300));
+			if (!rdev->pm.downclocked) {
+				queue_work(rdev->pm.wq, &rdev->pm.reclock_work);
+				wait_event_timeout(wait, !rdev->pm.downclocked,
+					msecs_to_jiffies(300));
+			}
+
+			DRM_DEBUG("radeon: dynamic clocking deactivated\n");
+		} else {
+			mutex_unlock(&rdev->pm.mutex);
+		}
+	} else {
+		rdev->pm.min_mode_engine_clock = rdev->clock.default_sclk - 5000;
+		rdev->pm.min_mode_memory_clock = rdev->clock.default_mclk - 5000;
+		if (rdev->pm.state == RADEON_PM_STATE_PAUSED) {
+			rdev->pm.state = RADEON_PM_STATE_ACTIVE;
+			queue_delayed_work(rdev->pm.wq, &rdev->pm.idle_work,
+				msecs_to_jiffies(RADEON_IDLE_LOOP_MS));
+			DRM_DEBUG("radeon: dynamic clocking activated\n");
+		}
+		mutex_unlock(&rdev->pm.mutex);
+	}
+}
+
+static void radeon_pm_set_clocks_locked(struct radeon_device *rdev)
+{
+	/*radeon_fence_wait_last(rdev);*/
+	switch (rdev->pm.planned_action) {
+	case RADEON_PM_ACTION_UPCLOCK:
+		radeon_set_engine_clock(rdev, rdev->clock.default_sclk);
+		rdev->pm.downclocked = false;
+		break;
+	case RADEON_PM_ACTION_DOWNCLOCK:
+		radeon_set_engine_clock(rdev,
+			rdev->pm.min_mode_engine_clock);
+		rdev->pm.downclocked = true;
+		break;
+	case RADEON_PM_ACTION_NONE:
+		DRM_ERROR("%s: RADEON_PM_ACTION_NONE\n", __func__);
+		break;
+	}
+
+	rdev->pm.planned_action = RADEON_PM_ACTION_NONE;
+}
+
+static void radeon_pm_reclock_work_handler(struct work_struct *work)
+{
+	struct radeon_device *rdev;
+	rdev = container_of(work, struct radeon_device,
+				pm.reclock_work);
+
+	mutex_lock(&rdev->pm.mutex);
+	/* new VBLANK irq may come before handling previous one */
+	if (rdev->pm.vblank_callback) {
+		mutex_lock(&rdev->cp.mutex);
+		rdev->pm.vblank_callback = false;
+		radeon_pm_set_clocks_locked(rdev);
+		mutex_unlock(&rdev->cp.mutex);
+	}
+	mutex_unlock(&rdev->pm.mutex);
+}
+
+static void radeon_pm_idle_work_handler(struct work_struct *work)
+{
+	struct radeon_device *rdev;
+	rdev = container_of(work, struct radeon_device,
+				pm.idle_work.work);
+
+	mutex_lock(&rdev->pm.mutex);
+	if (rdev->pm.state == RADEON_PM_STATE_ACTIVE &&
+		!rdev->pm.vblank_callback) {
+		unsigned long irq_flags;
+		int notProcessed = 0;
+
+		read_lock_irqsave(&rdev->fence_drv.lock, irq_flags);
+		if (!list_empty(&rdev->fence_drv.emited)) {
+			struct list_head *ptr;
+			list_for_each(ptr, &rdev->fence_drv.emited) {
+				/* count up to 3, that's enought info */
+				if (++notProcessed >= 3)
+					break;
+			}
+		}
+		read_unlock_irqrestore(&rdev->fence_drv.lock, irq_flags);
+
+		if (notProcessed >= 3) { /* should upclock */
+			if (rdev->pm.planned_action == RADEON_PM_ACTION_DOWNCLOCK) {
+				rdev->pm.planned_action = RADEON_PM_ACTION_NONE;
+			} else if (rdev->pm.planned_action == RADEON_PM_ACTION_NONE &&
+				rdev->pm.downclocked) {
+				rdev->pm.planned_action =
+					RADEON_PM_ACTION_UPCLOCK;
+				rdev->pm.action_timeout = jiffies +
+				msecs_to_jiffies(RADEON_RECLOCK_DELAY_MS);
+			}
+		} else if (notProcessed == 0) { /* should downclock */
+			if (rdev->pm.planned_action == RADEON_PM_ACTION_UPCLOCK) {
+				rdev->pm.planned_action = RADEON_PM_ACTION_NONE;
+			} else if (rdev->pm.planned_action == RADEON_PM_ACTION_NONE &&
+				!rdev->pm.downclocked) {
+				rdev->pm.planned_action =
+					RADEON_PM_ACTION_DOWNCLOCK;
+				rdev->pm.action_timeout = jiffies +
+				msecs_to_jiffies(RADEON_RECLOCK_DELAY_MS);
+			}
+		}
+
+		if (rdev->pm.planned_action != RADEON_PM_ACTION_NONE &&
+			jiffies > rdev->pm.action_timeout) {
+			rdev->pm.vblank_callback = true;
+		}
+	}
+	mutex_unlock(&rdev->pm.mutex);
+
+	queue_delayed_work(rdev->pm.wq, &rdev->pm.idle_work,
+					msecs_to_jiffies(RADEON_IDLE_LOOP_MS));
+}
+
 /*
  * Debugfs info
  */
@@ -55,7 +231,7 @@ static struct drm_info_list radeon_pm_info_list[] = {
 };
 #endif
 
-int radeon_debugfs_pm_init(struct radeon_device *rdev)
+static int radeon_debugfs_pm_init(struct radeon_device *rdev)
 {
 #if defined(CONFIG_DEBUG_FS)
 	return radeon_debugfs_add_files(rdev, radeon_pm_info_list, ARRAY_SIZE(radeon_pm_info_list));
-- 
1.6.4.2

