drm/radeon/kms/pm: rework power management

Add two new sysfs attributes:
- dynpm
- power_state

Echoing 0/1 to dynpm disables/enables dynamic power management.
The driver scales the sclk dynamically based on the number of
queued fences.  dynpm only scales sclk dynamically in single head
mode.

Echoing x.y to power_state selects a static power state (x) and clock
mode (y).  This allows you to statically select a power state and clock
mode.  Selecting a static clock mode will disable dynpm.

Signed-off-by: Alex Deucher <alexdeucher@gmail.com>
Signed-off-by: Dave Airlie <airlied@redhat.com>
diff --git a/drivers/gpu/drm/radeon/r100.c b/drivers/gpu/drm/radeon/r100.c
index 2106ac6..7675827 100644
--- a/drivers/gpu/drm/radeon/r100.c
+++ b/drivers/gpu/drm/radeon/r100.c
@@ -145,7 +145,7 @@
 		 pcie_lanes);
 }
 
-void r100_set_power_state(struct radeon_device *rdev)
+void r100_set_power_state(struct radeon_device *rdev, bool static_switch)
 {
 	u32 sclk, mclk;
 
@@ -167,33 +167,51 @@
 		if (rdev->pm.active_crtc_count > 1)
 			mclk = rdev->clock.default_mclk;
 
-		/* set pcie lanes */
-		/* TODO */
+		/* voltage, pcie lanes, etc.*/
+		radeon_pm_misc(rdev);
 
-		/* set voltage */
-		/* TODO */
-
-		/* set engine clock */
-		if (sclk != rdev->pm.current_sclk) {
-			radeon_sync_with_vblank(rdev);
-			radeon_pm_debug_check_in_vbl(rdev, false);
-			radeon_set_engine_clock(rdev, sclk);
-			radeon_pm_debug_check_in_vbl(rdev, true);
-			rdev->pm.current_sclk = sclk;
-			DRM_INFO("Setting: e: %d\n", sclk);
-		}
+		if (static_switch) {
+			radeon_pm_prepare(rdev);
+			/* set engine clock */
+			if (sclk != rdev->pm.current_sclk) {
+				radeon_set_engine_clock(rdev, sclk);
+				rdev->pm.current_sclk = sclk;
+				DRM_INFO("Setting: e: %d\n", sclk);
+			}
+#if 0
+			/* set memory clock */
+			if (rdev->asic->set_memory_clock && (mclk != rdev->pm.current_mclk)) {
+				radeon_set_memory_clock(rdev, mclk);
+				rdev->pm.current_mclk = mclk;
+				DRM_INFO("Setting: m: %d\n", mclk);
+			}
+#endif
+			radeon_pm_finish(rdev);
+		} else {
+			/* set engine clock */
+			if (sclk != rdev->pm.current_sclk) {
+				radeon_sync_with_vblank(rdev);
+				radeon_pm_debug_check_in_vbl(rdev, false);
+				radeon_set_engine_clock(rdev, sclk);
+				radeon_pm_debug_check_in_vbl(rdev, true);
+				rdev->pm.current_sclk = sclk;
+				DRM_INFO("Setting: e: %d\n", sclk);
+			}
 
 #if 0
-		/* set memory clock */
-		if (rdev->asic->set_memory_clock && (mclk != rdev->pm.current_mclk)) {
-			radeon_sync_with_vblank(rdev);
-			radeon_pm_debug_check_in_vbl(rdev, false);
-			radeon_set_memory_clock(rdev, mclk);
-			radeon_pm_debug_check_in_vbl(rdev, true);
-			rdev->pm.current_mclk = mclk;
-			DRM_INFO("Setting: m: %d\n", mclk);
-		}
+			/* set memory clock */
+			if (rdev->asic->set_memory_clock && (mclk != rdev->pm.current_mclk)) {
+				radeon_sync_with_vblank(rdev);
+				radeon_pm_debug_check_in_vbl(rdev, false);
+				radeon_pm_prepare(rdev);
+				radeon_set_memory_clock(rdev, mclk);
+				radeon_pm_finish(rdev);
+				radeon_pm_debug_check_in_vbl(rdev, true);
+				rdev->pm.current_mclk = mclk;
+				DRM_INFO("Setting: m: %d\n", mclk);
+			}
 #endif
+		}
 
 		rdev->pm.current_power_state_index = rdev->pm.requested_power_state_index;
 		rdev->pm.current_clock_mode_index = rdev->pm.requested_clock_mode_index;
diff --git a/drivers/gpu/drm/radeon/r600.c b/drivers/gpu/drm/radeon/r600.c
index 38f75f5..4691309 100644
--- a/drivers/gpu/drm/radeon/r600.c
+++ b/drivers/gpu/drm/radeon/r600.c
@@ -247,7 +247,7 @@
 		 pcie_lanes);
 }
 
-void r600_set_power_state(struct radeon_device *rdev)
+void r600_set_power_state(struct radeon_device *rdev, bool static_switch)
 {
 	u32 sclk, mclk;
 
@@ -266,37 +266,52 @@
 			clock_info[rdev->pm.requested_clock_mode_index].mclk;
 		if (mclk > rdev->clock.default_mclk)
 			mclk = rdev->clock.default_mclk;
-		/* don't change the mclk with multiple crtcs */
-		if (rdev->pm.active_crtc_count > 1)
-			mclk = rdev->clock.default_mclk;
 
-		/* set pcie lanes */
-		/* TODO */
+		/* voltage, pcie lanes, etc.*/
+		radeon_pm_misc(rdev);
 
-		/* set voltage */
-		/* TODO */
-
-		/* set engine clock */
-		if (sclk != rdev->pm.current_sclk) {
-			radeon_sync_with_vblank(rdev);
-			radeon_pm_debug_check_in_vbl(rdev, false);
-			radeon_set_engine_clock(rdev, sclk);
-			radeon_pm_debug_check_in_vbl(rdev, true);
-			rdev->pm.current_sclk = sclk;
-			DRM_INFO("Setting: e: %d\n", sclk);
-		}
+		if (static_switch) {
+			radeon_pm_prepare(rdev);
+			/* set engine clock */
+			if (sclk != rdev->pm.current_sclk) {
+				radeon_set_engine_clock(rdev, sclk);
+				rdev->pm.current_sclk = sclk;
+				DRM_INFO("Setting: e: %d\n", sclk);
+			}
+#if 0
+			/* set memory clock */
+			if (rdev->asic->set_memory_clock && (mclk != rdev->pm.current_mclk)) {
+				radeon_set_memory_clock(rdev, mclk);
+				rdev->pm.current_mclk = mclk;
+				DRM_INFO("Setting: m: %d\n", mclk);
+			}
+#endif
+			radeon_pm_finish(rdev);
+		} else {
+			/* set engine clock */
+			if (sclk != rdev->pm.current_sclk) {
+				radeon_sync_with_vblank(rdev);
+				radeon_pm_debug_check_in_vbl(rdev, false);
+				radeon_set_engine_clock(rdev, sclk);
+				radeon_pm_debug_check_in_vbl(rdev, true);
+				rdev->pm.current_sclk = sclk;
+				DRM_INFO("Setting: e: %d\n", sclk);
+			}
 
 #if 0
-		/* set memory clock */
-		if (rdev->asic->set_memory_clock && (mclk != rdev->pm.current_mclk)) {
-			radeon_sync_with_vblank(rdev);
-			radeon_pm_debug_check_in_vbl(rdev, false);
-			radeon_set_memory_clock(rdev, mclk);
-			radeon_pm_debug_check_in_vbl(rdev, true);
-			rdev->pm.current_mclk = mclk;
-			DRM_INFO("Setting: m: %d\n", mclk);
-		}
+			/* set memory clock */
+			if (rdev->asic->set_memory_clock && (mclk != rdev->pm.current_mclk)) {
+				radeon_sync_with_vblank(rdev);
+				radeon_pm_debug_check_in_vbl(rdev, false);
+				radeon_pm_prepare(rdev);
+				radeon_set_memory_clock(rdev, mclk);
+				radeon_pm_finish(rdev);
+				radeon_pm_debug_check_in_vbl(rdev, true);
+				rdev->pm.current_mclk = mclk;
+				DRM_INFO("Setting: m: %d\n", mclk);
+			}
 #endif
+		}
 
 		rdev->pm.current_power_state_index = rdev->pm.requested_power_state_index;
 		rdev->pm.current_clock_mode_index = rdev->pm.requested_clock_mode_index;
diff --git a/drivers/gpu/drm/radeon/radeon.h b/drivers/gpu/drm/radeon/radeon.h
index 16f0ea7..cdcf5ea 100644
--- a/drivers/gpu/drm/radeon/radeon.h
+++ b/drivers/gpu/drm/radeon/radeon.h
@@ -814,7 +814,7 @@
 	void (*ioctl_wait_idle)(struct radeon_device *rdev, struct radeon_bo *bo);
 	bool (*gui_idle)(struct radeon_device *rdev);
 	void (*get_power_state)(struct radeon_device *rdev, enum radeon_pm_action action);
-	void (*set_power_state)(struct radeon_device *rdev);
+	void (*set_power_state)(struct radeon_device *rdev, bool static_switch);
 	void (*pm_misc)(struct radeon_device *rdev);
 	void (*pm_prepare)(struct radeon_device *rdev);
 	void (*pm_finish)(struct radeon_device *rdev);
@@ -1226,7 +1226,10 @@
 #define radeon_hpd_set_polarity(rdev, hpd) (rdev)->asic->hpd_set_polarity((rdev), (hpd))
 #define radeon_gui_idle(rdev) (rdev)->asic->gui_idle((rdev))
 #define radeon_get_power_state(rdev, a) (rdev)->asic->get_power_state((rdev), (a))
-#define radeon_set_power_state(rdev) (rdev)->asic->set_power_state((rdev))
+#define radeon_set_power_state(rdev, s) (rdev)->asic->set_power_state((rdev), (s))
+#define radeon_pm_misc(rdev) (rdev)->asic->pm_misc((rdev))
+#define radeon_pm_prepare(rdev) (rdev)->asic->pm_prepare((rdev))
+#define radeon_pm_finish(rdev) (rdev)->asic->pm_finish((rdev))
 
 /* Common functions */
 /* AGP */
diff --git a/drivers/gpu/drm/radeon/radeon_asic.h b/drivers/gpu/drm/radeon/radeon_asic.h
index 378db67..8a12786 100644
--- a/drivers/gpu/drm/radeon/radeon_asic.h
+++ b/drivers/gpu/drm/radeon/radeon_asic.h
@@ -127,7 +127,7 @@
 void r100_set_common_regs(struct radeon_device *rdev);
 void r100_bm_disable(struct radeon_device *rdev);
 extern bool r100_gui_idle(struct radeon_device *rdev);
-extern void r100_set_power_state(struct radeon_device *rdev);
+extern void r100_set_power_state(struct radeon_device *rdev, bool static_switch);
 extern void r100_get_power_state(struct radeon_device *rdev,
 				 enum radeon_pm_action action);
 extern void r100_pm_misc(struct radeon_device *rdev);
@@ -281,7 +281,7 @@
 			   enum radeon_hpd_id hpd);
 extern void r600_ioctl_wait_idle(struct radeon_device *rdev, struct radeon_bo *bo);
 extern bool r600_gui_idle(struct radeon_device *rdev);
-extern void r600_set_power_state(struct radeon_device *rdev);
+extern void r600_set_power_state(struct radeon_device *rdev, bool static_switch);
 extern void r600_get_power_state(struct radeon_device *rdev,
 				 enum radeon_pm_action action);
 extern void r600_pm_misc(struct radeon_device *rdev);
diff --git a/drivers/gpu/drm/radeon/radeon_pm.c b/drivers/gpu/drm/radeon/radeon_pm.c
index 23b79eb..c703ae3 100644
--- a/drivers/gpu/drm/radeon/radeon_pm.c
+++ b/drivers/gpu/drm/radeon/radeon_pm.c
@@ -34,6 +34,128 @@
 static void radeon_pm_idle_work_handler(struct work_struct *work);
 static int radeon_debugfs_pm_init(struct radeon_device *rdev);
 
+static void radeon_pm_set_power_mode_static_locked(struct radeon_device *rdev)
+{
+	mutex_lock(&rdev->cp.mutex);
+
+	/* wait for GPU idle */
+	rdev->pm.gui_idle = false;
+	rdev->irq.gui_idle = true;
+	radeon_irq_set(rdev);
+	wait_event_interruptible_timeout(
+		rdev->irq.idle_queue, rdev->pm.gui_idle,
+		msecs_to_jiffies(RADEON_WAIT_IDLE_TIMEOUT));
+	rdev->irq.gui_idle = false;
+	radeon_irq_set(rdev);
+
+	radeon_set_power_state(rdev, true);
+
+	/* update display watermarks based on new power state */
+	radeon_update_bandwidth_info(rdev);
+	if (rdev->pm.active_crtc_count)
+		radeon_bandwidth_update(rdev);
+
+	mutex_unlock(&rdev->cp.mutex);
+}
+
+static ssize_t radeon_get_power_state_static(struct device *dev,
+					     struct device_attribute *attr,
+					     char *buf)
+{
+	struct drm_device *ddev = pci_get_drvdata(to_pci_dev(dev));
+	struct radeon_device *rdev = ddev->dev_private;
+
+	return snprintf(buf, PAGE_SIZE, "%d.%d\n", rdev->pm.current_power_state_index,
+			rdev->pm.current_clock_mode_index);
+}
+
+static ssize_t radeon_set_power_state_static(struct device *dev,
+					     struct device_attribute *attr,
+					     const char *buf,
+					     size_t count)
+{
+	struct drm_device *ddev = pci_get_drvdata(to_pci_dev(dev));
+	struct radeon_device *rdev = ddev->dev_private;
+	int ps, cm;
+
+	if (sscanf(buf, "%u.%u", &ps, &cm) != 2) {
+		DRM_ERROR("Invalid power state!\n");
+		return count;
+	}
+
+	mutex_lock(&rdev->pm.mutex);
+	if ((ps >= 0) && (ps < rdev->pm.num_power_states) &&
+	    (cm >= 0) && (cm < rdev->pm.power_state[ps].num_clock_modes)) {
+		if ((rdev->pm.active_crtc_count > 1) &&
+		    (rdev->pm.power_state[ps].flags & RADEON_PM_SINGLE_DISPLAY_ONLY)) {
+			DRM_ERROR("Invalid power state for multi-head: %d.%d\n", ps, cm);
+		} else {
+			/* disable dynpm */
+			rdev->pm.state = PM_STATE_DISABLED;
+			rdev->pm.planned_action = PM_ACTION_NONE;
+			rdev->pm.requested_power_state_index = ps;
+			rdev->pm.requested_clock_mode_index = cm;
+			radeon_pm_set_power_mode_static_locked(rdev);
+		}
+	} else
+		DRM_ERROR("Invalid power state: %d.%d\n\n", ps, cm);
+	mutex_unlock(&rdev->pm.mutex);
+
+	return count;
+}
+
+static ssize_t radeon_get_dynpm(struct device *dev,
+				struct device_attribute *attr,
+				char *buf)
+{
+	struct drm_device *ddev = pci_get_drvdata(to_pci_dev(dev));
+	struct radeon_device *rdev = ddev->dev_private;
+
+	return snprintf(buf, PAGE_SIZE, "%s\n",
+			(rdev->pm.state == PM_STATE_DISABLED) ? "disabled" : "enabled");
+}
+
+static ssize_t radeon_set_dynpm(struct device *dev,
+				struct device_attribute *attr,
+				const char *buf,
+				size_t count)
+{
+	struct drm_device *ddev = pci_get_drvdata(to_pci_dev(dev));
+	struct radeon_device *rdev = ddev->dev_private;
+	int tmp = simple_strtoul(buf, NULL, 10);
+
+	if (tmp == 0) {
+		/* update power mode info */
+		radeon_pm_compute_clocks(rdev);
+		/* disable dynpm */
+		mutex_lock(&rdev->pm.mutex);
+		rdev->pm.state = PM_STATE_DISABLED;
+		rdev->pm.planned_action = PM_ACTION_NONE;
+		mutex_unlock(&rdev->pm.mutex);
+		DRM_INFO("radeon: dynamic power management disabled\n");
+	} else if (tmp == 1) {
+		if (rdev->pm.num_power_states > 1) {
+			/* enable dynpm */
+			mutex_lock(&rdev->pm.mutex);
+			rdev->pm.state = PM_STATE_PAUSED;
+			rdev->pm.planned_action = PM_ACTION_DEFAULT;
+			radeon_get_power_state(rdev, rdev->pm.planned_action);
+			mutex_unlock(&rdev->pm.mutex);
+			/* update power mode info */
+			radeon_pm_compute_clocks(rdev);
+			DRM_INFO("radeon: dynamic power management enabled\n");
+		} else
+			DRM_ERROR("dynpm not valid on this system\n");
+	} else
+		DRM_ERROR("Invalid setting: %d\n", tmp);
+
+	return count;
+}
+
+static DEVICE_ATTR(power_state, S_IRUGO | S_IWUSR, radeon_get_power_state_static, radeon_set_power_state_static);
+static DEVICE_ATTR(dynpm, S_IRUGO | S_IWUSR, radeon_get_dynpm, radeon_set_dynpm);
+
+
 static const char *pm_state_names[4] = {
 	"PM_STATE_DISABLED",
 	"PM_STATE_MINIMUM",
@@ -111,6 +233,10 @@
 		DRM_ERROR("Failed to register debugfs file for PM!\n");
 	}
 
+	/* where's the best place to put this? */
+	device_create_file(rdev->dev, &dev_attr_power_state);
+	device_create_file(rdev->dev, &dev_attr_dynpm);
+
 	INIT_DELAYED_WORK(&rdev->pm.idle_work, radeon_pm_idle_work_handler);
 
 	if ((radeon_dynpm != -1 && radeon_dynpm) && (rdev->pm.num_power_states > 1)) {
@@ -132,8 +258,19 @@
 		rdev->pm.state = PM_STATE_DISABLED;
 		rdev->pm.planned_action = PM_ACTION_DEFAULT;
 		radeon_pm_set_clocks(rdev);
+	} else if ((rdev->pm.current_power_state_index !=
+		    rdev->pm.default_power_state_index) ||
+		   (rdev->pm.current_clock_mode_index != 0)) {
+		rdev->pm.requested_power_state_index = rdev->pm.default_power_state_index;
+		rdev->pm.requested_clock_mode_index = 0;
+		mutex_lock(&rdev->pm.mutex);
+		radeon_pm_set_power_mode_static_locked(rdev);
+		mutex_unlock(&rdev->pm.mutex);
 	}
 
+	device_remove_file(rdev->dev, &dev_attr_power_state);
+	device_remove_file(rdev->dev, &dev_attr_dynpm);
+
 	if (rdev->pm.i2c_bus)
 		radeon_i2c_destroy(rdev->pm.i2c_bus);
 }
@@ -267,7 +404,7 @@
 {
 	/*radeon_fence_wait_last(rdev);*/
 
-	radeon_set_power_state(rdev);
+	radeon_set_power_state(rdev, false);
 	rdev->pm.planned_action = PM_ACTION_NONE;
 }