Commit a424816f authored by Alex Deucher's avatar Alex Deucher Committed by Dave Airlie
Browse files

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: default avatarAlex Deucher <alexdeucher@gmail.com>
Signed-off-by: default avatarDave Airlie <airlied@redhat.com>
parent 49e02b73
Loading
Loading
Loading
Loading
+42 −24
Original line number Diff line number Diff line
@@ -145,7 +145,7 @@ void r100_get_power_state(struct radeon_device *rdev,
		 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,12 +167,27 @@ void r100_set_power_state(struct radeon_device *rdev)
		if (rdev->pm.active_crtc_count > 1)
			mclk = rdev->clock.default_mclk;

		/* set pcie lanes */
		/* TODO */

		/* set voltage */
		/* TODO */
		/* voltage, pcie lanes, etc.*/
		radeon_pm_misc(rdev);

		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);
@@ -188,12 +203,15 @@ void r100_set_power_state(struct radeon_device *rdev)
			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;
+42 −27
Original line number Diff line number Diff line
@@ -247,7 +247,7 @@ void r600_get_power_state(struct radeon_device *rdev,
		 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,16 +266,28 @@ void r600_set_power_state(struct radeon_device *rdev)
			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 */

		/* set voltage */
		/* TODO */
		/* voltage, pcie lanes, etc.*/
		radeon_pm_misc(rdev);

		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);
@@ -291,12 +303,15 @@ void r600_set_power_state(struct radeon_device *rdev)
			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;
+5 −2
Original line number Diff line number Diff line
@@ -814,7 +814,7 @@ struct radeon_asic {
	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 @@ static inline void radeon_ring_write(struct radeon_device *rdev, uint32_t v)
#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 */
+2 −2
Original line number Diff line number Diff line
@@ -127,7 +127,7 @@ void r100_enable_bm(struct radeon_device *rdev);
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 @@ void r600_hpd_set_polarity(struct radeon_device *rdev,
			   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);
+138 −1
Original line number Diff line number Diff line
@@ -34,6 +34,128 @@ static void radeon_pm_set_clocks(struct radeon_device *rdev);
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 @@ int radeon_pm_init(struct radeon_device *rdev)
		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 @@ void radeon_pm_fini(struct radeon_device *rdev)
		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 @@ static void radeon_pm_set_clocks_locked(struct radeon_device *rdev)
{
	/*radeon_fence_wait_last(rdev);*/

	radeon_set_power_state(rdev);
	radeon_set_power_state(rdev, false);
	rdev->pm.planned_action = PM_ACTION_NONE;
}