diff mbox

[069/165] drm/radeon/kms: add common dpm infrastructure

Message ID 1372253045-17042-70-git-send-email-alexdeucher@gmail.com (mailing list archive)
State New, archived
Headers show

Commit Message

Alex Deucher June 26, 2013, 1:22 p.m. UTC
From: Alex Deucher <alexander.deucher@amd.com>

This adds the common dpm (dynamic power management)
infrastructure:
- dpm callbacks
- dpm init/fini/suspend/resume
- dpm power state selection

No device specific code is enabled yet.

Signed-off-by: Alex Deucher <alexander.deucher@amd.com>
---
 drivers/gpu/drm/radeon/radeon.h     |  100 +++++++-
 drivers/gpu/drm/radeon/radeon_drv.c |    4 +
 drivers/gpu/drm/radeon/radeon_pm.c  |  496 ++++++++++++++++++++++++++++++++++-
 3 files changed, 591 insertions(+), 9 deletions(-)

Comments

Jerome Glisse June 26, 2013, 10:27 a.m. UTC | #1
On Wed, Jun 26, 2013 at 09:22:29AM -0400, alexdeucher@gmail.com wrote:
> From: Alex Deucher <alexander.deucher@amd.com>
> 
> This adds the common dpm (dynamic power management)
> infrastructure:
> - dpm callbacks
> - dpm init/fini/suspend/resume
> - dpm power state selection
> 
> No device specific code is enabled yet.
> 
> Signed-off-by: Alex Deucher <alexander.deucher@amd.com>

Reviewed-by: Jerome Glisse <jglisse@redhat.com>

> ---
>  drivers/gpu/drm/radeon/radeon.h     |  100 +++++++-
>  drivers/gpu/drm/radeon/radeon_drv.c |    4 +
>  drivers/gpu/drm/radeon/radeon_pm.c  |  496 ++++++++++++++++++++++++++++++++++-
>  3 files changed, 591 insertions(+), 9 deletions(-)
> 
> diff --git a/drivers/gpu/drm/radeon/radeon.h b/drivers/gpu/drm/radeon/radeon.h
> index 6c445f5..c43673c 100644
> --- a/drivers/gpu/drm/radeon/radeon.h
> +++ b/drivers/gpu/drm/radeon/radeon.h
> @@ -96,6 +96,7 @@ extern int radeon_pcie_gen2;
>  extern int radeon_msi;
>  extern int radeon_lockup_timeout;
>  extern int radeon_fastfb;
> +extern int radeon_dpm;
>  
>  /*
>   * Copy from radeon_drv.h so we don't have to include both and have conflicting
> @@ -1048,6 +1049,7 @@ struct radeon_wb {
>  enum radeon_pm_method {
>  	PM_METHOD_PROFILE,
>  	PM_METHOD_DYNPM,
> +	PM_METHOD_DPM,
>  };
>  
>  enum radeon_dynpm_state {
> @@ -1073,11 +1075,23 @@ enum radeon_voltage_type {
>  };
>  
>  enum radeon_pm_state_type {
> +	/* not used for dpm */
>  	POWER_STATE_TYPE_DEFAULT,
>  	POWER_STATE_TYPE_POWERSAVE,
> +	/* user selectable states */
>  	POWER_STATE_TYPE_BATTERY,
>  	POWER_STATE_TYPE_BALANCED,
>  	POWER_STATE_TYPE_PERFORMANCE,
> +	/* internal states */
> +	POWER_STATE_TYPE_INTERNAL_UVD,
> +	POWER_STATE_TYPE_INTERNAL_UVD_SD,
> +	POWER_STATE_TYPE_INTERNAL_UVD_HD,
> +	POWER_STATE_TYPE_INTERNAL_UVD_HD2,
> +	POWER_STATE_TYPE_INTERNAL_UVD_MVC,
> +	POWER_STATE_TYPE_INTERNAL_BOOT,
> +	POWER_STATE_TYPE_INTERNAL_THERMAL,
> +	POWER_STATE_TYPE_INTERNAL_ACPI,
> +	POWER_STATE_TYPE_INTERNAL_ULV,
>  };
>  
>  enum radeon_pm_profile_type {
> @@ -1106,12 +1120,16 @@ struct radeon_pm_profile {
>  
>  enum radeon_int_thermal_type {
>  	THERMAL_TYPE_NONE,
> +	THERMAL_TYPE_EXTERNAL,
> +	THERMAL_TYPE_EXTERNAL_GPIO,
>  	THERMAL_TYPE_RV6XX,
>  	THERMAL_TYPE_RV770,
> +	THERMAL_TYPE_ADT7473_WITH_INTERNAL,
>  	THERMAL_TYPE_EVERGREEN,
>  	THERMAL_TYPE_SUMO,
>  	THERMAL_TYPE_NI,
>  	THERMAL_TYPE_SI,
> +	THERMAL_TYPE_EMC2103_WITH_INTERNAL,
>  	THERMAL_TYPE_CI,
>  };
>  
> @@ -1166,6 +1184,60 @@ struct radeon_power_state {
>   */
>  #define RADEON_MODE_OVERCLOCK_MARGIN 500 /* 5 MHz */
>  
> +struct radeon_ps {
> +	u32 caps; /* vbios flags */
> +	u32 class; /* vbios flags */
> +	u32 class2; /* vbios flags */
> +	/* UVD clocks */
> +	u32 vclk;
> +	u32 dclk;
> +	/* asic priv */
> +	void *ps_priv;
> +};
> +
> +struct radeon_dpm_thermal {
> +	/* thermal interrupt work */
> +	struct work_struct work;
> +	/* low temperature threshold */
> +	int                min_temp;
> +	/* high temperature threshold */
> +	int                max_temp;
> +	/* was interrupt low to high or high to low */
> +	bool               high_to_low;
> +};
> +
> +struct radeon_dpm {
> +	struct radeon_ps        *ps;
> +	/* number of valid power states */
> +	int                     num_ps;
> +	/* current power state that is active */
> +	struct radeon_ps        *current_ps;
> +	/* requested power state */
> +	struct radeon_ps        *requested_ps;
> +	/* boot up power state */
> +	struct radeon_ps        *boot_ps;
> +	/* default uvd power state */
> +	struct radeon_ps        *uvd_ps;
> +	enum radeon_pm_state_type state;
> +	enum radeon_pm_state_type user_state;
> +	u32                     platform_caps;
> +	u32                     voltage_response_time;
> +	u32                     backbias_response_time;
> +	void                    *priv;

Just nitpick all of the above have broken indentation space instead of tab.

> +	u32			new_active_crtcs;
> +	int			new_active_crtc_count;
> +	u32			current_active_crtcs;
> +	int			current_active_crtc_count;
> +	/* special states active */
> +	bool                    thermal_active;
> +	/* thermal handling */
> +	struct radeon_dpm_thermal thermal;
> +};
> +
> +void radeon_dpm_enable_power_state(struct radeon_device *rdev,
> +				    enum radeon_pm_state_type dpm_state);
> +
> +
>  struct radeon_pm {
>  	struct mutex		mutex;
>  	/* write locked while reprogramming mclk */
> @@ -1219,6 +1291,9 @@ struct radeon_pm {
>  	/* internal thermal controller on rv6xx+ */
>  	enum radeon_int_thermal_type int_thermal_type;
>  	struct device	        *int_hwmon_dev;
> +	/* dpm */
> +	bool                    dpm_enabled;
> +	struct radeon_dpm       dpm;
>  };
>  
>  int radeon_pm_get_type_index(struct radeon_device *rdev,
> @@ -1416,7 +1491,7 @@ struct radeon_asic {
>  		bool (*sense)(struct radeon_device *rdev, enum radeon_hpd_id hpd);
>  		void (*set_polarity)(struct radeon_device *rdev, enum radeon_hpd_id hpd);
>  	} hpd;
> -	/* power management */
> +	/* static power management */
>  	struct {
>  		void (*misc)(struct radeon_device *rdev);
>  		void (*prepare)(struct radeon_device *rdev);
> @@ -1433,6 +1508,19 @@ struct radeon_asic {
>  		int (*set_uvd_clocks)(struct radeon_device *rdev, u32 vclk, u32 dclk);
>  		int (*get_temperature)(struct radeon_device *rdev);
>  	} pm;
> +	/* dynamic power management */
> +	struct {
> +		int (*init)(struct radeon_device *rdev);
> +		void (*setup_asic)(struct radeon_device *rdev);
> +		int (*enable)(struct radeon_device *rdev);
> +		void (*disable)(struct radeon_device *rdev);
> +		int (*set_power_state)(struct radeon_device *rdev);
> +		void (*display_configuration_changed)(struct radeon_device *rdev);
> +		void (*fini)(struct radeon_device *rdev);
> +		u32 (*get_sclk)(struct radeon_device *rdev, bool low);
> +		u32 (*get_mclk)(struct radeon_device *rdev, bool low);
> +		void (*print_power_state)(struct radeon_device *rdev, struct radeon_ps *ps);
> +	} dpm;
>  	/* pageflipping */
>  	struct {
>  		void (*pre_page_flip)(struct radeon_device *rdev, int crtc);
> @@ -2122,6 +2210,16 @@ void radeon_ring_write(struct radeon_ring *ring, uint32_t v);
>  #define radeon_mc_wait_for_idle(rdev) (rdev)->asic->mc_wait_for_idle((rdev))
>  #define radeon_get_xclk(rdev) (rdev)->asic->get_xclk((rdev))
>  #define radeon_get_gpu_clock_counter(rdev) (rdev)->asic->get_gpu_clock_counter((rdev))
> +#define radeon_dpm_init(rdev) rdev->asic->dpm.init((rdev))
> +#define radeon_dpm_setup_asic(rdev) rdev->asic->dpm.setup_asic((rdev))
> +#define radeon_dpm_enable(rdev) rdev->asic->dpm.enable((rdev))
> +#define radeon_dpm_disable(rdev) rdev->asic->dpm.disable((rdev))
> +#define radeon_dpm_set_power_state(rdev) rdev->asic->dpm.set_power_state((rdev))
> +#define radeon_dpm_display_configuration_changed(rdev) rdev->asic->dpm.display_configuration_changed((rdev))
> +#define radeon_dpm_fini(rdev) rdev->asic->dpm.fini((rdev))
> +#define radeon_dpm_get_sclk(rdev, l) rdev->asic->dpm.get_sclk((rdev), (l))
> +#define radeon_dpm_get_mclk(rdev, l) rdev->asic->dpm.get_mclk((rdev), (l))
> +#define radeon_dpm_print_power_state(rdev, ps) rdev->asic->dpm.print_power_state((rdev), (ps))
>  
>  /* Common functions */
>  /* AGP */
> diff --git a/drivers/gpu/drm/radeon/radeon_drv.c b/drivers/gpu/drm/radeon/radeon_drv.c
> index 02709e4..00cc52e 100644
> --- a/drivers/gpu/drm/radeon/radeon_drv.c
> +++ b/drivers/gpu/drm/radeon/radeon_drv.c
> @@ -165,6 +165,7 @@ int radeon_pcie_gen2 = -1;
>  int radeon_msi = -1;
>  int radeon_lockup_timeout = 10000;
>  int radeon_fastfb = 0;
> +int radeon_dpm = -1;
>  
>  MODULE_PARM_DESC(no_wb, "Disable AGP writeback for scratch registers");
>  module_param_named(no_wb, radeon_no_wb, int, 0444);
> @@ -220,6 +221,9 @@ module_param_named(lockup_timeout, radeon_lockup_timeout, int, 0444);
>  MODULE_PARM_DESC(fastfb, "Direct FB access for IGP chips (0 = disable, 1 = enable)");
>  module_param_named(fastfb, radeon_fastfb, int, 0444);
>  
> +MODULE_PARM_DESC(dpm, "DPM support (1 = enable, 0 = disable, -1 = auto)");
> +module_param_named(dpm, radeon_dpm, int, 0444);
> +
>  static struct pci_device_id pciidlist[] = {
>  	radeon_PCI_IDS
>  };
> diff --git a/drivers/gpu/drm/radeon/radeon_pm.c b/drivers/gpu/drm/radeon/radeon_pm.c
> index e8c1bea..4f5422e 100644
> --- a/drivers/gpu/drm/radeon/radeon_pm.c
> +++ b/drivers/gpu/drm/radeon/radeon_pm.c
> @@ -388,7 +388,8 @@ static ssize_t radeon_get_pm_method(struct device *dev,
>  	int pm = rdev->pm.pm_method;
>  
>  	return snprintf(buf, PAGE_SIZE, "%s\n",
> -			(pm == PM_METHOD_DYNPM) ? "dynpm" : "profile");
> +			(pm == PM_METHOD_DYNPM) ? "dynpm" :
> +			(pm == PM_METHOD_PROFILE) ? "profile" : "dpm");
>  }
>  
>  static ssize_t radeon_set_pm_method(struct device *dev,
> @@ -399,6 +400,11 @@ static ssize_t radeon_set_pm_method(struct device *dev,
>  	struct drm_device *ddev = pci_get_drvdata(to_pci_dev(dev));
>  	struct radeon_device *rdev = ddev->dev_private;
>  
> +	/* we don't support the legacy modes with dpm */
> +	if (rdev->pm.pm_method == PM_METHOD_DPM) {
> +		count = -EINVAL;
> +		goto fail;
> +	}
>  
>  	if (strncmp("dynpm", buf, strlen("dynpm")) == 0) {
>  		mutex_lock(&rdev->pm.mutex);
> @@ -423,8 +429,48 @@ fail:
>  	return count;
>  }
>  
> +static ssize_t radeon_get_dpm_state(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;
> +	enum radeon_pm_state_type pm = rdev->pm.dpm.user_state;
> +
> +	return snprintf(buf, PAGE_SIZE, "%s\n",
> +			(pm == POWER_STATE_TYPE_BATTERY) ? "battery" :
> +			(pm == POWER_STATE_TYPE_BALANCED) ? "balanced" : "performance");
> +}
> +
> +static ssize_t radeon_set_dpm_state(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;
> +
> +	mutex_lock(&rdev->pm.mutex);
> +	if (strncmp("battery", buf, strlen("battery")) == 0)
> +		rdev->pm.dpm.user_state = POWER_STATE_TYPE_BATTERY;
> +	else if (strncmp("balanced", buf, strlen("balanced")) == 0)
> +		rdev->pm.dpm.user_state = POWER_STATE_TYPE_BALANCED;
> +	else if (strncmp("performance", buf, strlen("performance")) == 0)
> +		rdev->pm.dpm.user_state = POWER_STATE_TYPE_PERFORMANCE;
> +	else {
> +		mutex_unlock(&rdev->pm.mutex);
> +		count = -EINVAL;
> +		goto fail;
> +	}
> +	mutex_unlock(&rdev->pm.mutex);
> +	radeon_pm_compute_clocks(rdev);
> +fail:
> +	return count;
> +}
> +
>  static DEVICE_ATTR(power_profile, S_IRUGO | S_IWUSR, radeon_get_pm_profile, radeon_set_pm_profile);
>  static DEVICE_ATTR(power_method, S_IRUGO | S_IWUSR, radeon_get_pm_method, radeon_set_pm_method);
> +static DEVICE_ATTR(power_dpm_state, S_IRUGO | S_IWUSR, radeon_get_dpm_state, radeon_set_dpm_state);
>  
>  static ssize_t radeon_hwmon_show_temp(struct device *dev,
>  				      struct device_attribute *attr,
> @@ -508,7 +554,228 @@ static void radeon_hwmon_fini(struct radeon_device *rdev)
>  	}
>  }
>  
> -void radeon_pm_suspend(struct radeon_device *rdev)
> +static void radeon_dpm_thermal_work_handler(struct work_struct *work)
> +{
> +	struct radeon_device *rdev =
> +		container_of(work, struct radeon_device,
> +			     pm.dpm.thermal.work);
> +	/* switch to the thermal state */
> +	enum radeon_pm_state_type dpm_state = POWER_STATE_TYPE_INTERNAL_THERMAL;
> +
> +	if (!rdev->pm.dpm_enabled)
> +		return;
> +
> +	if (rdev->asic->pm.get_temperature) {
> +		int temp = radeon_get_temperature(rdev);
> +
> +		if (temp < rdev->pm.dpm.thermal.min_temp)
> +			/* switch back the user state */
> +			dpm_state = rdev->pm.dpm.user_state;
> +	} else {
> +		if (rdev->pm.dpm.thermal.high_to_low)
> +			/* switch back the user state */
> +			dpm_state = rdev->pm.dpm.user_state;
> +	}
> +	radeon_dpm_enable_power_state(rdev, dpm_state);
> +}
> +
> +static struct radeon_ps *radeon_dpm_pick_power_state(struct radeon_device *rdev,
> +						     enum radeon_pm_state_type dpm_state)
> +{
> +	int i;
> +	struct radeon_ps *ps;
> +	u32 ui_class;
> +
> +restart_search:
> +	/* balanced states don't exist at the moment */
> +	if (dpm_state == POWER_STATE_TYPE_BALANCED)
> +		dpm_state = POWER_STATE_TYPE_PERFORMANCE;
> +
> +	/* Pick the best power state based on current conditions */
> +	for (i = 0; i < rdev->pm.dpm.num_ps; i++) {
> +		ps = &rdev->pm.dpm.ps[i];
> +		ui_class = ps->class & ATOM_PPLIB_CLASSIFICATION_UI_MASK;
> +		switch (dpm_state) {
> +		/* user states */
> +		case POWER_STATE_TYPE_BATTERY:
> +			if (ui_class == ATOM_PPLIB_CLASSIFICATION_UI_BATTERY) {
> +				if (ps->caps & ATOM_PPLIB_SINGLE_DISPLAY_ONLY) {
> +					if (rdev->pm.dpm.new_active_crtc_count < 2)
> +						return ps;
> +				} else
> +					return ps;
> +			}
> +			break;
> +		case POWER_STATE_TYPE_BALANCED:
> +			if (ui_class == ATOM_PPLIB_CLASSIFICATION_UI_BALANCED) {
> +				if (ps->caps & ATOM_PPLIB_SINGLE_DISPLAY_ONLY) {
> +					if (rdev->pm.dpm.new_active_crtc_count < 2)
> +						return ps;
> +				} else
> +					return ps;
> +			}
> +			break;
> +		case POWER_STATE_TYPE_PERFORMANCE:
> +			if (ui_class == ATOM_PPLIB_CLASSIFICATION_UI_PERFORMANCE) {
> +				if (ps->caps & ATOM_PPLIB_SINGLE_DISPLAY_ONLY) {
> +					if (rdev->pm.dpm.new_active_crtc_count < 2)
> +						return ps;
> +				} else
> +					return ps;
> +			}
> +			break;
> +		/* internal states */
> +		case POWER_STATE_TYPE_INTERNAL_UVD:
> +			return rdev->pm.dpm.uvd_ps;
> +		case POWER_STATE_TYPE_INTERNAL_UVD_SD:
> +			if (ps->class & ATOM_PPLIB_CLASSIFICATION_SDSTATE)
> +				return ps;
> +			break;
> +		case POWER_STATE_TYPE_INTERNAL_UVD_HD:
> +			if (ps->class & ATOM_PPLIB_CLASSIFICATION_HDSTATE)
> +				return ps;
> +			break;
> +		case POWER_STATE_TYPE_INTERNAL_UVD_HD2:
> +			if (ps->class & ATOM_PPLIB_CLASSIFICATION_HD2STATE)
> +				return ps;
> +			break;
> +		case POWER_STATE_TYPE_INTERNAL_UVD_MVC:
> +			if (ps->class2 & ATOM_PPLIB_CLASSIFICATION2_MVC)
> +				return ps;
> +			break;
> +		case POWER_STATE_TYPE_INTERNAL_BOOT:
> +			return rdev->pm.dpm.boot_ps;
> +		case POWER_STATE_TYPE_INTERNAL_THERMAL:
> +			if (ps->class & ATOM_PPLIB_CLASSIFICATION_THERMAL)
> +				return ps;
> +			break;
> +		case POWER_STATE_TYPE_INTERNAL_ACPI:
> +			if (ps->class & ATOM_PPLIB_CLASSIFICATION_ACPI)
> +				return ps;
> +			break;
> +		case POWER_STATE_TYPE_INTERNAL_ULV:
> +			if (ps->class2 & ATOM_PPLIB_CLASSIFICATION2_ULV)
> +				return ps;
> +			break;
> +		default:
> +			break;
> +		}
> +	}
> +	/* use a fallback state if we didn't match */
> +	switch (dpm_state) {
> +	case POWER_STATE_TYPE_INTERNAL_UVD_SD:
> +	case POWER_STATE_TYPE_INTERNAL_UVD_HD:
> +	case POWER_STATE_TYPE_INTERNAL_UVD_HD2:
> +	case POWER_STATE_TYPE_INTERNAL_UVD_MVC:
> +		return rdev->pm.dpm.uvd_ps;
> +	case POWER_STATE_TYPE_INTERNAL_THERMAL:
> +		dpm_state = POWER_STATE_TYPE_INTERNAL_ACPI;
> +		goto restart_search;
> +	case POWER_STATE_TYPE_INTERNAL_ACPI:
> +		dpm_state = POWER_STATE_TYPE_BATTERY;
> +		goto restart_search;
> +	case POWER_STATE_TYPE_BATTERY:
> +		dpm_state = POWER_STATE_TYPE_PERFORMANCE;
> +		goto restart_search;
> +	default:
> +		break;
> +	}
> +
> +	return NULL;
> +}
> +
> +static void radeon_dpm_change_power_state_locked(struct radeon_device *rdev)
> +{
> +	int i;
> +	struct radeon_ps *ps;
> +	enum radeon_pm_state_type dpm_state;
> +
> +	/* if dpm init failed */
> +	if (!rdev->pm.dpm_enabled)
> +		return;
> +
> +	if (rdev->pm.dpm.user_state != rdev->pm.dpm.state) {
> +		/* add other state override checks here */
> +		if (!rdev->pm.dpm.thermal_active)
> +			rdev->pm.dpm.state = rdev->pm.dpm.user_state;
> +	}
> +	dpm_state = rdev->pm.dpm.state;
> +
> +	ps = radeon_dpm_pick_power_state(rdev, dpm_state);
> +	if (ps)
> +		rdev->pm.dpm.requested_ps = ps;
> +	else
> +		return;
> +
> +	/* no need to reprogram if nothing changed */
> +	if (rdev->pm.dpm.current_ps == rdev->pm.dpm.requested_ps) {
> +		/* update display watermarks based on new power state */
> +		if (rdev->pm.dpm.new_active_crtcs != rdev->pm.dpm.current_active_crtcs) {
> +			radeon_bandwidth_update(rdev);
> +			/* update displays */
> +			radeon_dpm_display_configuration_changed(rdev);
> +			rdev->pm.dpm.current_active_crtcs = rdev->pm.dpm.new_active_crtcs;
> +			rdev->pm.dpm.current_active_crtc_count = rdev->pm.dpm.new_active_crtc_count;
> +		}
> +		return;
> +	}
> +
> +	printk("switching from power state:\n");
> +	radeon_dpm_print_power_state(rdev, rdev->pm.dpm.current_ps);
> +	printk("switching to power state:\n");
> +	radeon_dpm_print_power_state(rdev, rdev->pm.dpm.requested_ps);
> +
> +	mutex_lock(&rdev->ddev->struct_mutex);
> +	down_write(&rdev->pm.mclk_lock);
> +	mutex_lock(&rdev->ring_lock);
> +
> +	/* update display watermarks based on new power state */
> +	radeon_bandwidth_update(rdev);
> +	/* update displays */
> +	radeon_dpm_display_configuration_changed(rdev);
> +
> +	rdev->pm.dpm.current_active_crtcs = rdev->pm.dpm.new_active_crtcs;
> +	rdev->pm.dpm.current_active_crtc_count = rdev->pm.dpm.new_active_crtc_count;
> +
> +	/* wait for the rings to drain */
> +	for (i = 0; i < RADEON_NUM_RINGS; i++) {
> +		struct radeon_ring *ring = &rdev->ring[i];
> +		if (ring->ready)
> +			radeon_fence_wait_empty_locked(rdev, i);
> +	}
> +
> +	/* program the new power state */
> +	radeon_dpm_set_power_state(rdev);
> +
> +	/* update current power state */
> +	rdev->pm.dpm.current_ps = rdev->pm.dpm.requested_ps;
> +
> +	mutex_unlock(&rdev->ring_lock);
> +	up_write(&rdev->pm.mclk_lock);
> +	mutex_unlock(&rdev->ddev->struct_mutex);
> +}
> +
> +void radeon_dpm_enable_power_state(struct radeon_device *rdev,
> +				   enum radeon_pm_state_type dpm_state)
> +{
> +	if (!rdev->pm.dpm_enabled)
> +		return;
> +
> +	mutex_lock(&rdev->pm.mutex);
> +	switch (dpm_state) {
> +	case POWER_STATE_TYPE_INTERNAL_THERMAL:
> +		rdev->pm.dpm.thermal_active = true;
> +		break;
> +	default:
> +		rdev->pm.dpm.thermal_active = false;
> +		break;
> +	}
> +	rdev->pm.dpm.state = dpm_state;
> +	mutex_unlock(&rdev->pm.mutex);
> +	radeon_pm_compute_clocks(rdev);
> +}
> +
> +static void radeon_pm_suspend_old(struct radeon_device *rdev)
>  {
>  	mutex_lock(&rdev->pm.mutex);
>  	if (rdev->pm.pm_method == PM_METHOD_DYNPM) {
> @@ -520,7 +787,26 @@ void radeon_pm_suspend(struct radeon_device *rdev)
>  	cancel_delayed_work_sync(&rdev->pm.dynpm_idle_work);
>  }
>  
> -void radeon_pm_resume(struct radeon_device *rdev)
> +static void radeon_pm_suspend_dpm(struct radeon_device *rdev)
> +{
> +	mutex_lock(&rdev->pm.mutex);
> +	/* disable dpm */
> +	radeon_dpm_disable(rdev);
> +	/* reset the power state */
> +	rdev->pm.dpm.current_ps = rdev->pm.dpm.requested_ps = rdev->pm.dpm.boot_ps;
> +	rdev->pm.dpm_enabled = false;
> +	mutex_unlock(&rdev->pm.mutex);
> +}
> +
> +void radeon_pm_suspend(struct radeon_device *rdev)
> +{
> +	if (rdev->pm.pm_method == PM_METHOD_DPM)
> +		radeon_pm_suspend_dpm(rdev);
> +	else
> +		radeon_pm_suspend_old(rdev);
> +}
> +
> +static void radeon_pm_resume_old(struct radeon_device *rdev)
>  {
>  	/* set up the default clocks if the MC ucode is loaded */
>  	if ((rdev->family >= CHIP_BARTS) &&
> @@ -555,12 +841,50 @@ void radeon_pm_resume(struct radeon_device *rdev)
>  	radeon_pm_compute_clocks(rdev);
>  }
>  
> -int radeon_pm_init(struct radeon_device *rdev)
> +static void radeon_pm_resume_dpm(struct radeon_device *rdev)
> +{
> +	int ret;
> +
> +	/* asic init will reset to the boot state */
> +	mutex_lock(&rdev->pm.mutex);
> +	rdev->pm.dpm.current_ps = rdev->pm.dpm.requested_ps = rdev->pm.dpm.boot_ps;
> +	radeon_dpm_setup_asic(rdev);
> +	ret = radeon_dpm_enable(rdev);
> +	mutex_unlock(&rdev->pm.mutex);
> +	if (ret) {
> +		DRM_ERROR("radeon: dpm resume failed\n");
> +		if ((rdev->family >= CHIP_BARTS) &&
> +		    (rdev->family <= CHIP_CAYMAN) &&
> +		    rdev->mc_fw) {
> +			if (rdev->pm.default_vddc)
> +				radeon_atom_set_voltage(rdev, rdev->pm.default_vddc,
> +							SET_VOLTAGE_TYPE_ASIC_VDDC);
> +			if (rdev->pm.default_vddci)
> +				radeon_atom_set_voltage(rdev, rdev->pm.default_vddci,
> +							SET_VOLTAGE_TYPE_ASIC_VDDCI);
> +			if (rdev->pm.default_sclk)
> +				radeon_set_engine_clock(rdev, rdev->pm.default_sclk);
> +			if (rdev->pm.default_mclk)
> +				radeon_set_memory_clock(rdev, rdev->pm.default_mclk);
> +		}
> +	} else {
> +		rdev->pm.dpm_enabled = true;
> +		radeon_pm_compute_clocks(rdev);
> +	}
> +}
> +
> +void radeon_pm_resume(struct radeon_device *rdev)
> +{
> +	if (rdev->pm.pm_method == PM_METHOD_DPM)
> +		radeon_pm_resume_dpm(rdev);
> +	else
> +		radeon_pm_resume_old(rdev);
> +}
> +
> +static int radeon_pm_init_old(struct radeon_device *rdev)
>  {
>  	int ret;
>  
> -	/* default to profile method */
> -	rdev->pm.pm_method = PM_METHOD_PROFILE;
>  	rdev->pm.profile = PM_PROFILE_DEFAULT;
>  	rdev->pm.dynpm_state = DYNPM_STATE_DISABLED;
>  	rdev->pm.dynpm_planned_action = DYNPM_ACTION_NONE;
> @@ -622,7 +946,103 @@ int radeon_pm_init(struct radeon_device *rdev)
>  	return 0;
>  }
>  
> -void radeon_pm_fini(struct radeon_device *rdev)
> +static void radeon_dpm_print_power_states(struct radeon_device *rdev)
> +{
> +	int i;
> +
> +	for (i = 0; i < rdev->pm.dpm.num_ps; i++) {
> +		printk("== power state %d ==\n", i);
> +		radeon_dpm_print_power_state(rdev, &rdev->pm.dpm.ps[i]);
> +	}
> +}
> +
> +static int radeon_pm_init_dpm(struct radeon_device *rdev)
> +{
> +	int ret;
> +
> +	/* default to performance state */
> +	rdev->pm.dpm.state = POWER_STATE_TYPE_PERFORMANCE;
> +	rdev->pm.dpm.user_state = POWER_STATE_TYPE_PERFORMANCE;
> +	rdev->pm.default_sclk = rdev->clock.default_sclk;
> +	rdev->pm.default_mclk = rdev->clock.default_mclk;
> +	rdev->pm.current_sclk = rdev->clock.default_sclk;
> +	rdev->pm.current_mclk = rdev->clock.default_mclk;
> +	rdev->pm.int_thermal_type = THERMAL_TYPE_NONE;
> +
> +	if (rdev->bios && rdev->is_atom_bios)
> +		radeon_atombios_get_power_modes(rdev);
> +	else
> +		return -EINVAL;
> +
> +	/* set up the internal thermal sensor if applicable */
> +	ret = radeon_hwmon_init(rdev);
> +	if (ret)
> +		return ret;
> +
> +	INIT_WORK(&rdev->pm.dpm.thermal.work, radeon_dpm_thermal_work_handler);
> +	mutex_lock(&rdev->pm.mutex);
> +	radeon_dpm_init(rdev);
> +	rdev->pm.dpm.current_ps = rdev->pm.dpm.requested_ps = rdev->pm.dpm.boot_ps;
> +	radeon_dpm_print_power_states(rdev);
> +	radeon_dpm_setup_asic(rdev);
> +	ret = radeon_dpm_enable(rdev);
> +	mutex_unlock(&rdev->pm.mutex);
> +	if (ret) {
> +		rdev->pm.dpm_enabled = false;
> +		if ((rdev->family >= CHIP_BARTS) &&
> +		    (rdev->family <= CHIP_CAYMAN) &&
> +		    rdev->mc_fw) {
> +			if (rdev->pm.default_vddc)
> +				radeon_atom_set_voltage(rdev, rdev->pm.default_vddc,
> +							SET_VOLTAGE_TYPE_ASIC_VDDC);
> +			if (rdev->pm.default_vddci)
> +				radeon_atom_set_voltage(rdev, rdev->pm.default_vddci,
> +							SET_VOLTAGE_TYPE_ASIC_VDDCI);
> +			if (rdev->pm.default_sclk)
> +				radeon_set_engine_clock(rdev, rdev->pm.default_sclk);
> +			if (rdev->pm.default_mclk)
> +				radeon_set_memory_clock(rdev, rdev->pm.default_mclk);
> +		}
> +		DRM_ERROR("radeon: dpm initialization failed\n");
> +		return ret;
> +	}
> +	rdev->pm.dpm_enabled = true;
> +	radeon_pm_compute_clocks(rdev);
> +
> +	if (rdev->pm.num_power_states > 1) {
> +		ret = device_create_file(rdev->dev, &dev_attr_power_dpm_state);
> +		if (ret)
> +			DRM_ERROR("failed to create device file for dpm state\n");
> +		/* XXX: these are noops for dpm but are here for backwards compat */
> +		ret = device_create_file(rdev->dev, &dev_attr_power_profile);
> +		if (ret)
> +			DRM_ERROR("failed to create device file for power profile\n");
> +		ret = device_create_file(rdev->dev, &dev_attr_power_method);
> +		if (ret)
> +			DRM_ERROR("failed to create device file for power method\n");
> +		DRM_INFO("radeon: dpm initialized\n");
> +	}
> +
> +	return 0;
> +}
> +
> +int radeon_pm_init(struct radeon_device *rdev)
> +{
> +	/* enable dpm on rv6xx+ */
> +	switch (rdev->family) {
> +	default:
> +		/* default to profile method */
> +		rdev->pm.pm_method = PM_METHOD_PROFILE;
> +		break;
> +	}
> +
> +	if (rdev->pm.pm_method == PM_METHOD_DPM)
> +		return radeon_pm_init_dpm(rdev);
> +	else
> +		return radeon_pm_init_old(rdev);
> +}
> +
> +static void radeon_pm_fini_old(struct radeon_device *rdev)
>  {
>  	if (rdev->pm.num_power_states > 1) {
>  		mutex_lock(&rdev->pm.mutex);
> @@ -650,7 +1070,35 @@ void radeon_pm_fini(struct radeon_device *rdev)
>  	radeon_hwmon_fini(rdev);
>  }
>  
> -void radeon_pm_compute_clocks(struct radeon_device *rdev)
> +static void radeon_pm_fini_dpm(struct radeon_device *rdev)
> +{
> +	if (rdev->pm.num_power_states > 1) {
> +		mutex_lock(&rdev->pm.mutex);
> +		radeon_dpm_disable(rdev);
> +		mutex_unlock(&rdev->pm.mutex);
> +
> +		device_remove_file(rdev->dev, &dev_attr_power_dpm_state);
> +		/* XXX backwards compat */
> +		device_remove_file(rdev->dev, &dev_attr_power_profile);
> +		device_remove_file(rdev->dev, &dev_attr_power_method);
> +	}
> +	radeon_dpm_fini(rdev);
> +
> +	if (rdev->pm.power_state)
> +		kfree(rdev->pm.power_state);
> +
> +	radeon_hwmon_fini(rdev);
> +}
> +
> +void radeon_pm_fini(struct radeon_device *rdev)
> +{
> +	if (rdev->pm.pm_method == PM_METHOD_DPM)
> +		radeon_pm_fini_dpm(rdev);
> +	else
> +		radeon_pm_fini_old(rdev);
> +}
> +
> +static void radeon_pm_compute_clocks_old(struct radeon_device *rdev)
>  {
>  	struct drm_device *ddev = rdev->ddev;
>  	struct drm_crtc *crtc;
> @@ -721,6 +1169,38 @@ void radeon_pm_compute_clocks(struct radeon_device *rdev)
>  	mutex_unlock(&rdev->pm.mutex);
>  }
>  
> +static void radeon_pm_compute_clocks_dpm(struct radeon_device *rdev)
> +{
> +	struct drm_device *ddev = rdev->ddev;
> +	struct drm_crtc *crtc;
> +	struct radeon_crtc *radeon_crtc;
> +
> +	mutex_lock(&rdev->pm.mutex);
> +
> +	rdev->pm.dpm.new_active_crtcs = 0;
> +	rdev->pm.dpm.new_active_crtc_count = 0;
> +	list_for_each_entry(crtc,
> +		&ddev->mode_config.crtc_list, head) {
> +		radeon_crtc = to_radeon_crtc(crtc);
> +		if (crtc->enabled) {
> +			rdev->pm.dpm.new_active_crtcs |= (1 << radeon_crtc->crtc_id);
> +			rdev->pm.dpm.new_active_crtc_count++;
> +		}
> +	}
> +
> +	radeon_dpm_change_power_state_locked(rdev);
> +
> +	mutex_unlock(&rdev->pm.mutex);
> +}
> +
> +void radeon_pm_compute_clocks(struct radeon_device *rdev)
> +{
> +	if (rdev->pm.pm_method == PM_METHOD_DPM)
> +		radeon_pm_compute_clocks_dpm(rdev);
> +	else
> +		radeon_pm_compute_clocks_old(rdev);
> +}
> +
>  static bool radeon_pm_in_vbl(struct radeon_device *rdev)
>  {
>  	int  crtc, vpos, hpos, vbl_status;
> -- 
> 1.7.7.5
> 
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> http://lists.freedesktop.org/mailman/listinfo/dri-devel
K. Schnass June 27, 2013, 1:52 p.m. UTC | #2
Thanks for the work the whole thing seems to work fine on my RV770, although I 
cannot really say if does anything as I found no way to query the current clk 
or voltage?! There is one little gripe though, the rest of the code uses 
DRM_INFO() for printing whereas this series uses plain printk() resulting in 
some weird looking dmesg output:

Jun 27 14:41:54 my-little-devil kernel: [drm]   VGA-1
Jun 27 14:41:54 my-little-devil kernel: [drm]   DDC: 0x7e60 0x7e60 0x7e64 
0x7e64 0x7e68 0x7e68 0x7e6c 0x7e6c
Jun 27 14:41:54 my-little-devil kernel: [drm]   Encoders:
Jun 27 14:41:54 my-little-devil kernel: [drm]     CRT2: INTERNAL_KLDSCP_DAC2
Jun 27 14:41:54 my-little-devil kernel: [drm] Internal thermal controller with 
fan control
Jun 27 14:41:54 my-little-devil kernel: == power state 0 ==
Jun 27 14:41:54 my-little-devil kernel:         ui class: none
Jun 27 14:41:54 my-little-devil kernel:         internal class: boot 
Jun 27 14:41:54 my-little-devil kernel:         caps: video 
Jun 27 14:41:54 my-little-devil kernel:         uvd    vclk: 0 dclk: 0
Jun 27 14:41:54 my-little-devil kernel:                 power level 0    sclk: 
62500 mclk: 99300 vddc: 1123
Jun 27 14:41:54 my-little-devil kernel:                 power level 1    sclk: 
62500 mclk: 99300 vddc: 1123
Jun 27 14:41:54 my-little-devil kernel:                 power level 2    sclk: 
62500 mclk: 99300 vddc: 1123

best regards
K. Schnass
diff mbox

Patch

diff --git a/drivers/gpu/drm/radeon/radeon.h b/drivers/gpu/drm/radeon/radeon.h
index 6c445f5..c43673c 100644
--- a/drivers/gpu/drm/radeon/radeon.h
+++ b/drivers/gpu/drm/radeon/radeon.h
@@ -96,6 +96,7 @@  extern int radeon_pcie_gen2;
 extern int radeon_msi;
 extern int radeon_lockup_timeout;
 extern int radeon_fastfb;
+extern int radeon_dpm;
 
 /*
  * Copy from radeon_drv.h so we don't have to include both and have conflicting
@@ -1048,6 +1049,7 @@  struct radeon_wb {
 enum radeon_pm_method {
 	PM_METHOD_PROFILE,
 	PM_METHOD_DYNPM,
+	PM_METHOD_DPM,
 };
 
 enum radeon_dynpm_state {
@@ -1073,11 +1075,23 @@  enum radeon_voltage_type {
 };
 
 enum radeon_pm_state_type {
+	/* not used for dpm */
 	POWER_STATE_TYPE_DEFAULT,
 	POWER_STATE_TYPE_POWERSAVE,
+	/* user selectable states */
 	POWER_STATE_TYPE_BATTERY,
 	POWER_STATE_TYPE_BALANCED,
 	POWER_STATE_TYPE_PERFORMANCE,
+	/* internal states */
+	POWER_STATE_TYPE_INTERNAL_UVD,
+	POWER_STATE_TYPE_INTERNAL_UVD_SD,
+	POWER_STATE_TYPE_INTERNAL_UVD_HD,
+	POWER_STATE_TYPE_INTERNAL_UVD_HD2,
+	POWER_STATE_TYPE_INTERNAL_UVD_MVC,
+	POWER_STATE_TYPE_INTERNAL_BOOT,
+	POWER_STATE_TYPE_INTERNAL_THERMAL,
+	POWER_STATE_TYPE_INTERNAL_ACPI,
+	POWER_STATE_TYPE_INTERNAL_ULV,
 };
 
 enum radeon_pm_profile_type {
@@ -1106,12 +1120,16 @@  struct radeon_pm_profile {
 
 enum radeon_int_thermal_type {
 	THERMAL_TYPE_NONE,
+	THERMAL_TYPE_EXTERNAL,
+	THERMAL_TYPE_EXTERNAL_GPIO,
 	THERMAL_TYPE_RV6XX,
 	THERMAL_TYPE_RV770,
+	THERMAL_TYPE_ADT7473_WITH_INTERNAL,
 	THERMAL_TYPE_EVERGREEN,
 	THERMAL_TYPE_SUMO,
 	THERMAL_TYPE_NI,
 	THERMAL_TYPE_SI,
+	THERMAL_TYPE_EMC2103_WITH_INTERNAL,
 	THERMAL_TYPE_CI,
 };
 
@@ -1166,6 +1184,60 @@  struct radeon_power_state {
  */
 #define RADEON_MODE_OVERCLOCK_MARGIN 500 /* 5 MHz */
 
+struct radeon_ps {
+	u32 caps; /* vbios flags */
+	u32 class; /* vbios flags */
+	u32 class2; /* vbios flags */
+	/* UVD clocks */
+	u32 vclk;
+	u32 dclk;
+	/* asic priv */
+	void *ps_priv;
+};
+
+struct radeon_dpm_thermal {
+	/* thermal interrupt work */
+	struct work_struct work;
+	/* low temperature threshold */
+	int                min_temp;
+	/* high temperature threshold */
+	int                max_temp;
+	/* was interrupt low to high or high to low */
+	bool               high_to_low;
+};
+
+struct radeon_dpm {
+	struct radeon_ps        *ps;
+	/* number of valid power states */
+	int                     num_ps;
+	/* current power state that is active */
+	struct radeon_ps        *current_ps;
+	/* requested power state */
+	struct radeon_ps        *requested_ps;
+	/* boot up power state */
+	struct radeon_ps        *boot_ps;
+	/* default uvd power state */
+	struct radeon_ps        *uvd_ps;
+	enum radeon_pm_state_type state;
+	enum radeon_pm_state_type user_state;
+	u32                     platform_caps;
+	u32                     voltage_response_time;
+	u32                     backbias_response_time;
+	void                    *priv;
+	u32			new_active_crtcs;
+	int			new_active_crtc_count;
+	u32			current_active_crtcs;
+	int			current_active_crtc_count;
+	/* special states active */
+	bool                    thermal_active;
+	/* thermal handling */
+	struct radeon_dpm_thermal thermal;
+};
+
+void radeon_dpm_enable_power_state(struct radeon_device *rdev,
+				    enum radeon_pm_state_type dpm_state);
+
+
 struct radeon_pm {
 	struct mutex		mutex;
 	/* write locked while reprogramming mclk */
@@ -1219,6 +1291,9 @@  struct radeon_pm {
 	/* internal thermal controller on rv6xx+ */
 	enum radeon_int_thermal_type int_thermal_type;
 	struct device	        *int_hwmon_dev;
+	/* dpm */
+	bool                    dpm_enabled;
+	struct radeon_dpm       dpm;
 };
 
 int radeon_pm_get_type_index(struct radeon_device *rdev,
@@ -1416,7 +1491,7 @@  struct radeon_asic {
 		bool (*sense)(struct radeon_device *rdev, enum radeon_hpd_id hpd);
 		void (*set_polarity)(struct radeon_device *rdev, enum radeon_hpd_id hpd);
 	} hpd;
-	/* power management */
+	/* static power management */
 	struct {
 		void (*misc)(struct radeon_device *rdev);
 		void (*prepare)(struct radeon_device *rdev);
@@ -1433,6 +1508,19 @@  struct radeon_asic {
 		int (*set_uvd_clocks)(struct radeon_device *rdev, u32 vclk, u32 dclk);
 		int (*get_temperature)(struct radeon_device *rdev);
 	} pm;
+	/* dynamic power management */
+	struct {
+		int (*init)(struct radeon_device *rdev);
+		void (*setup_asic)(struct radeon_device *rdev);
+		int (*enable)(struct radeon_device *rdev);
+		void (*disable)(struct radeon_device *rdev);
+		int (*set_power_state)(struct radeon_device *rdev);
+		void (*display_configuration_changed)(struct radeon_device *rdev);
+		void (*fini)(struct radeon_device *rdev);
+		u32 (*get_sclk)(struct radeon_device *rdev, bool low);
+		u32 (*get_mclk)(struct radeon_device *rdev, bool low);
+		void (*print_power_state)(struct radeon_device *rdev, struct radeon_ps *ps);
+	} dpm;
 	/* pageflipping */
 	struct {
 		void (*pre_page_flip)(struct radeon_device *rdev, int crtc);
@@ -2122,6 +2210,16 @@  void radeon_ring_write(struct radeon_ring *ring, uint32_t v);
 #define radeon_mc_wait_for_idle(rdev) (rdev)->asic->mc_wait_for_idle((rdev))
 #define radeon_get_xclk(rdev) (rdev)->asic->get_xclk((rdev))
 #define radeon_get_gpu_clock_counter(rdev) (rdev)->asic->get_gpu_clock_counter((rdev))
+#define radeon_dpm_init(rdev) rdev->asic->dpm.init((rdev))
+#define radeon_dpm_setup_asic(rdev) rdev->asic->dpm.setup_asic((rdev))
+#define radeon_dpm_enable(rdev) rdev->asic->dpm.enable((rdev))
+#define radeon_dpm_disable(rdev) rdev->asic->dpm.disable((rdev))
+#define radeon_dpm_set_power_state(rdev) rdev->asic->dpm.set_power_state((rdev))
+#define radeon_dpm_display_configuration_changed(rdev) rdev->asic->dpm.display_configuration_changed((rdev))
+#define radeon_dpm_fini(rdev) rdev->asic->dpm.fini((rdev))
+#define radeon_dpm_get_sclk(rdev, l) rdev->asic->dpm.get_sclk((rdev), (l))
+#define radeon_dpm_get_mclk(rdev, l) rdev->asic->dpm.get_mclk((rdev), (l))
+#define radeon_dpm_print_power_state(rdev, ps) rdev->asic->dpm.print_power_state((rdev), (ps))
 
 /* Common functions */
 /* AGP */
diff --git a/drivers/gpu/drm/radeon/radeon_drv.c b/drivers/gpu/drm/radeon/radeon_drv.c
index 02709e4..00cc52e 100644
--- a/drivers/gpu/drm/radeon/radeon_drv.c
+++ b/drivers/gpu/drm/radeon/radeon_drv.c
@@ -165,6 +165,7 @@  int radeon_pcie_gen2 = -1;
 int radeon_msi = -1;
 int radeon_lockup_timeout = 10000;
 int radeon_fastfb = 0;
+int radeon_dpm = -1;
 
 MODULE_PARM_DESC(no_wb, "Disable AGP writeback for scratch registers");
 module_param_named(no_wb, radeon_no_wb, int, 0444);
@@ -220,6 +221,9 @@  module_param_named(lockup_timeout, radeon_lockup_timeout, int, 0444);
 MODULE_PARM_DESC(fastfb, "Direct FB access for IGP chips (0 = disable, 1 = enable)");
 module_param_named(fastfb, radeon_fastfb, int, 0444);
 
+MODULE_PARM_DESC(dpm, "DPM support (1 = enable, 0 = disable, -1 = auto)");
+module_param_named(dpm, radeon_dpm, int, 0444);
+
 static struct pci_device_id pciidlist[] = {
 	radeon_PCI_IDS
 };
diff --git a/drivers/gpu/drm/radeon/radeon_pm.c b/drivers/gpu/drm/radeon/radeon_pm.c
index e8c1bea..4f5422e 100644
--- a/drivers/gpu/drm/radeon/radeon_pm.c
+++ b/drivers/gpu/drm/radeon/radeon_pm.c
@@ -388,7 +388,8 @@  static ssize_t radeon_get_pm_method(struct device *dev,
 	int pm = rdev->pm.pm_method;
 
 	return snprintf(buf, PAGE_SIZE, "%s\n",
-			(pm == PM_METHOD_DYNPM) ? "dynpm" : "profile");
+			(pm == PM_METHOD_DYNPM) ? "dynpm" :
+			(pm == PM_METHOD_PROFILE) ? "profile" : "dpm");
 }
 
 static ssize_t radeon_set_pm_method(struct device *dev,
@@ -399,6 +400,11 @@  static ssize_t radeon_set_pm_method(struct device *dev,
 	struct drm_device *ddev = pci_get_drvdata(to_pci_dev(dev));
 	struct radeon_device *rdev = ddev->dev_private;
 
+	/* we don't support the legacy modes with dpm */
+	if (rdev->pm.pm_method == PM_METHOD_DPM) {
+		count = -EINVAL;
+		goto fail;
+	}
 
 	if (strncmp("dynpm", buf, strlen("dynpm")) == 0) {
 		mutex_lock(&rdev->pm.mutex);
@@ -423,8 +429,48 @@  fail:
 	return count;
 }
 
+static ssize_t radeon_get_dpm_state(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;
+	enum radeon_pm_state_type pm = rdev->pm.dpm.user_state;
+
+	return snprintf(buf, PAGE_SIZE, "%s\n",
+			(pm == POWER_STATE_TYPE_BATTERY) ? "battery" :
+			(pm == POWER_STATE_TYPE_BALANCED) ? "balanced" : "performance");
+}
+
+static ssize_t radeon_set_dpm_state(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;
+
+	mutex_lock(&rdev->pm.mutex);
+	if (strncmp("battery", buf, strlen("battery")) == 0)
+		rdev->pm.dpm.user_state = POWER_STATE_TYPE_BATTERY;
+	else if (strncmp("balanced", buf, strlen("balanced")) == 0)
+		rdev->pm.dpm.user_state = POWER_STATE_TYPE_BALANCED;
+	else if (strncmp("performance", buf, strlen("performance")) == 0)
+		rdev->pm.dpm.user_state = POWER_STATE_TYPE_PERFORMANCE;
+	else {
+		mutex_unlock(&rdev->pm.mutex);
+		count = -EINVAL;
+		goto fail;
+	}
+	mutex_unlock(&rdev->pm.mutex);
+	radeon_pm_compute_clocks(rdev);
+fail:
+	return count;
+}
+
 static DEVICE_ATTR(power_profile, S_IRUGO | S_IWUSR, radeon_get_pm_profile, radeon_set_pm_profile);
 static DEVICE_ATTR(power_method, S_IRUGO | S_IWUSR, radeon_get_pm_method, radeon_set_pm_method);
+static DEVICE_ATTR(power_dpm_state, S_IRUGO | S_IWUSR, radeon_get_dpm_state, radeon_set_dpm_state);
 
 static ssize_t radeon_hwmon_show_temp(struct device *dev,
 				      struct device_attribute *attr,
@@ -508,7 +554,228 @@  static void radeon_hwmon_fini(struct radeon_device *rdev)
 	}
 }
 
-void radeon_pm_suspend(struct radeon_device *rdev)
+static void radeon_dpm_thermal_work_handler(struct work_struct *work)
+{
+	struct radeon_device *rdev =
+		container_of(work, struct radeon_device,
+			     pm.dpm.thermal.work);
+	/* switch to the thermal state */
+	enum radeon_pm_state_type dpm_state = POWER_STATE_TYPE_INTERNAL_THERMAL;
+
+	if (!rdev->pm.dpm_enabled)
+		return;
+
+	if (rdev->asic->pm.get_temperature) {
+		int temp = radeon_get_temperature(rdev);
+
+		if (temp < rdev->pm.dpm.thermal.min_temp)
+			/* switch back the user state */
+			dpm_state = rdev->pm.dpm.user_state;
+	} else {
+		if (rdev->pm.dpm.thermal.high_to_low)
+			/* switch back the user state */
+			dpm_state = rdev->pm.dpm.user_state;
+	}
+	radeon_dpm_enable_power_state(rdev, dpm_state);
+}
+
+static struct radeon_ps *radeon_dpm_pick_power_state(struct radeon_device *rdev,
+						     enum radeon_pm_state_type dpm_state)
+{
+	int i;
+	struct radeon_ps *ps;
+	u32 ui_class;
+
+restart_search:
+	/* balanced states don't exist at the moment */
+	if (dpm_state == POWER_STATE_TYPE_BALANCED)
+		dpm_state = POWER_STATE_TYPE_PERFORMANCE;
+
+	/* Pick the best power state based on current conditions */
+	for (i = 0; i < rdev->pm.dpm.num_ps; i++) {
+		ps = &rdev->pm.dpm.ps[i];
+		ui_class = ps->class & ATOM_PPLIB_CLASSIFICATION_UI_MASK;
+		switch (dpm_state) {
+		/* user states */
+		case POWER_STATE_TYPE_BATTERY:
+			if (ui_class == ATOM_PPLIB_CLASSIFICATION_UI_BATTERY) {
+				if (ps->caps & ATOM_PPLIB_SINGLE_DISPLAY_ONLY) {
+					if (rdev->pm.dpm.new_active_crtc_count < 2)
+						return ps;
+				} else
+					return ps;
+			}
+			break;
+		case POWER_STATE_TYPE_BALANCED:
+			if (ui_class == ATOM_PPLIB_CLASSIFICATION_UI_BALANCED) {
+				if (ps->caps & ATOM_PPLIB_SINGLE_DISPLAY_ONLY) {
+					if (rdev->pm.dpm.new_active_crtc_count < 2)
+						return ps;
+				} else
+					return ps;
+			}
+			break;
+		case POWER_STATE_TYPE_PERFORMANCE:
+			if (ui_class == ATOM_PPLIB_CLASSIFICATION_UI_PERFORMANCE) {
+				if (ps->caps & ATOM_PPLIB_SINGLE_DISPLAY_ONLY) {
+					if (rdev->pm.dpm.new_active_crtc_count < 2)
+						return ps;
+				} else
+					return ps;
+			}
+			break;
+		/* internal states */
+		case POWER_STATE_TYPE_INTERNAL_UVD:
+			return rdev->pm.dpm.uvd_ps;
+		case POWER_STATE_TYPE_INTERNAL_UVD_SD:
+			if (ps->class & ATOM_PPLIB_CLASSIFICATION_SDSTATE)
+				return ps;
+			break;
+		case POWER_STATE_TYPE_INTERNAL_UVD_HD:
+			if (ps->class & ATOM_PPLIB_CLASSIFICATION_HDSTATE)
+				return ps;
+			break;
+		case POWER_STATE_TYPE_INTERNAL_UVD_HD2:
+			if (ps->class & ATOM_PPLIB_CLASSIFICATION_HD2STATE)
+				return ps;
+			break;
+		case POWER_STATE_TYPE_INTERNAL_UVD_MVC:
+			if (ps->class2 & ATOM_PPLIB_CLASSIFICATION2_MVC)
+				return ps;
+			break;
+		case POWER_STATE_TYPE_INTERNAL_BOOT:
+			return rdev->pm.dpm.boot_ps;
+		case POWER_STATE_TYPE_INTERNAL_THERMAL:
+			if (ps->class & ATOM_PPLIB_CLASSIFICATION_THERMAL)
+				return ps;
+			break;
+		case POWER_STATE_TYPE_INTERNAL_ACPI:
+			if (ps->class & ATOM_PPLIB_CLASSIFICATION_ACPI)
+				return ps;
+			break;
+		case POWER_STATE_TYPE_INTERNAL_ULV:
+			if (ps->class2 & ATOM_PPLIB_CLASSIFICATION2_ULV)
+				return ps;
+			break;
+		default:
+			break;
+		}
+	}
+	/* use a fallback state if we didn't match */
+	switch (dpm_state) {
+	case POWER_STATE_TYPE_INTERNAL_UVD_SD:
+	case POWER_STATE_TYPE_INTERNAL_UVD_HD:
+	case POWER_STATE_TYPE_INTERNAL_UVD_HD2:
+	case POWER_STATE_TYPE_INTERNAL_UVD_MVC:
+		return rdev->pm.dpm.uvd_ps;
+	case POWER_STATE_TYPE_INTERNAL_THERMAL:
+		dpm_state = POWER_STATE_TYPE_INTERNAL_ACPI;
+		goto restart_search;
+	case POWER_STATE_TYPE_INTERNAL_ACPI:
+		dpm_state = POWER_STATE_TYPE_BATTERY;
+		goto restart_search;
+	case POWER_STATE_TYPE_BATTERY:
+		dpm_state = POWER_STATE_TYPE_PERFORMANCE;
+		goto restart_search;
+	default:
+		break;
+	}
+
+	return NULL;
+}
+
+static void radeon_dpm_change_power_state_locked(struct radeon_device *rdev)
+{
+	int i;
+	struct radeon_ps *ps;
+	enum radeon_pm_state_type dpm_state;
+
+	/* if dpm init failed */
+	if (!rdev->pm.dpm_enabled)
+		return;
+
+	if (rdev->pm.dpm.user_state != rdev->pm.dpm.state) {
+		/* add other state override checks here */
+		if (!rdev->pm.dpm.thermal_active)
+			rdev->pm.dpm.state = rdev->pm.dpm.user_state;
+	}
+	dpm_state = rdev->pm.dpm.state;
+
+	ps = radeon_dpm_pick_power_state(rdev, dpm_state);
+	if (ps)
+		rdev->pm.dpm.requested_ps = ps;
+	else
+		return;
+
+	/* no need to reprogram if nothing changed */
+	if (rdev->pm.dpm.current_ps == rdev->pm.dpm.requested_ps) {
+		/* update display watermarks based on new power state */
+		if (rdev->pm.dpm.new_active_crtcs != rdev->pm.dpm.current_active_crtcs) {
+			radeon_bandwidth_update(rdev);
+			/* update displays */
+			radeon_dpm_display_configuration_changed(rdev);
+			rdev->pm.dpm.current_active_crtcs = rdev->pm.dpm.new_active_crtcs;
+			rdev->pm.dpm.current_active_crtc_count = rdev->pm.dpm.new_active_crtc_count;
+		}
+		return;
+	}
+
+	printk("switching from power state:\n");
+	radeon_dpm_print_power_state(rdev, rdev->pm.dpm.current_ps);
+	printk("switching to power state:\n");
+	radeon_dpm_print_power_state(rdev, rdev->pm.dpm.requested_ps);
+
+	mutex_lock(&rdev->ddev->struct_mutex);
+	down_write(&rdev->pm.mclk_lock);
+	mutex_lock(&rdev->ring_lock);
+
+	/* update display watermarks based on new power state */
+	radeon_bandwidth_update(rdev);
+	/* update displays */
+	radeon_dpm_display_configuration_changed(rdev);
+
+	rdev->pm.dpm.current_active_crtcs = rdev->pm.dpm.new_active_crtcs;
+	rdev->pm.dpm.current_active_crtc_count = rdev->pm.dpm.new_active_crtc_count;
+
+	/* wait for the rings to drain */
+	for (i = 0; i < RADEON_NUM_RINGS; i++) {
+		struct radeon_ring *ring = &rdev->ring[i];
+		if (ring->ready)
+			radeon_fence_wait_empty_locked(rdev, i);
+	}
+
+	/* program the new power state */
+	radeon_dpm_set_power_state(rdev);
+
+	/* update current power state */
+	rdev->pm.dpm.current_ps = rdev->pm.dpm.requested_ps;
+
+	mutex_unlock(&rdev->ring_lock);
+	up_write(&rdev->pm.mclk_lock);
+	mutex_unlock(&rdev->ddev->struct_mutex);
+}
+
+void radeon_dpm_enable_power_state(struct radeon_device *rdev,
+				   enum radeon_pm_state_type dpm_state)
+{
+	if (!rdev->pm.dpm_enabled)
+		return;
+
+	mutex_lock(&rdev->pm.mutex);
+	switch (dpm_state) {
+	case POWER_STATE_TYPE_INTERNAL_THERMAL:
+		rdev->pm.dpm.thermal_active = true;
+		break;
+	default:
+		rdev->pm.dpm.thermal_active = false;
+		break;
+	}
+	rdev->pm.dpm.state = dpm_state;
+	mutex_unlock(&rdev->pm.mutex);
+	radeon_pm_compute_clocks(rdev);
+}
+
+static void radeon_pm_suspend_old(struct radeon_device *rdev)
 {
 	mutex_lock(&rdev->pm.mutex);
 	if (rdev->pm.pm_method == PM_METHOD_DYNPM) {
@@ -520,7 +787,26 @@  void radeon_pm_suspend(struct radeon_device *rdev)
 	cancel_delayed_work_sync(&rdev->pm.dynpm_idle_work);
 }
 
-void radeon_pm_resume(struct radeon_device *rdev)
+static void radeon_pm_suspend_dpm(struct radeon_device *rdev)
+{
+	mutex_lock(&rdev->pm.mutex);
+	/* disable dpm */
+	radeon_dpm_disable(rdev);
+	/* reset the power state */
+	rdev->pm.dpm.current_ps = rdev->pm.dpm.requested_ps = rdev->pm.dpm.boot_ps;
+	rdev->pm.dpm_enabled = false;
+	mutex_unlock(&rdev->pm.mutex);
+}
+
+void radeon_pm_suspend(struct radeon_device *rdev)
+{
+	if (rdev->pm.pm_method == PM_METHOD_DPM)
+		radeon_pm_suspend_dpm(rdev);
+	else
+		radeon_pm_suspend_old(rdev);
+}
+
+static void radeon_pm_resume_old(struct radeon_device *rdev)
 {
 	/* set up the default clocks if the MC ucode is loaded */
 	if ((rdev->family >= CHIP_BARTS) &&
@@ -555,12 +841,50 @@  void radeon_pm_resume(struct radeon_device *rdev)
 	radeon_pm_compute_clocks(rdev);
 }
 
-int radeon_pm_init(struct radeon_device *rdev)
+static void radeon_pm_resume_dpm(struct radeon_device *rdev)
+{
+	int ret;
+
+	/* asic init will reset to the boot state */
+	mutex_lock(&rdev->pm.mutex);
+	rdev->pm.dpm.current_ps = rdev->pm.dpm.requested_ps = rdev->pm.dpm.boot_ps;
+	radeon_dpm_setup_asic(rdev);
+	ret = radeon_dpm_enable(rdev);
+	mutex_unlock(&rdev->pm.mutex);
+	if (ret) {
+		DRM_ERROR("radeon: dpm resume failed\n");
+		if ((rdev->family >= CHIP_BARTS) &&
+		    (rdev->family <= CHIP_CAYMAN) &&
+		    rdev->mc_fw) {
+			if (rdev->pm.default_vddc)
+				radeon_atom_set_voltage(rdev, rdev->pm.default_vddc,
+							SET_VOLTAGE_TYPE_ASIC_VDDC);
+			if (rdev->pm.default_vddci)
+				radeon_atom_set_voltage(rdev, rdev->pm.default_vddci,
+							SET_VOLTAGE_TYPE_ASIC_VDDCI);
+			if (rdev->pm.default_sclk)
+				radeon_set_engine_clock(rdev, rdev->pm.default_sclk);
+			if (rdev->pm.default_mclk)
+				radeon_set_memory_clock(rdev, rdev->pm.default_mclk);
+		}
+	} else {
+		rdev->pm.dpm_enabled = true;
+		radeon_pm_compute_clocks(rdev);
+	}
+}
+
+void radeon_pm_resume(struct radeon_device *rdev)
+{
+	if (rdev->pm.pm_method == PM_METHOD_DPM)
+		radeon_pm_resume_dpm(rdev);
+	else
+		radeon_pm_resume_old(rdev);
+}
+
+static int radeon_pm_init_old(struct radeon_device *rdev)
 {
 	int ret;
 
-	/* default to profile method */
-	rdev->pm.pm_method = PM_METHOD_PROFILE;
 	rdev->pm.profile = PM_PROFILE_DEFAULT;
 	rdev->pm.dynpm_state = DYNPM_STATE_DISABLED;
 	rdev->pm.dynpm_planned_action = DYNPM_ACTION_NONE;
@@ -622,7 +946,103 @@  int radeon_pm_init(struct radeon_device *rdev)
 	return 0;
 }
 
-void radeon_pm_fini(struct radeon_device *rdev)
+static void radeon_dpm_print_power_states(struct radeon_device *rdev)
+{
+	int i;
+
+	for (i = 0; i < rdev->pm.dpm.num_ps; i++) {
+		printk("== power state %d ==\n", i);
+		radeon_dpm_print_power_state(rdev, &rdev->pm.dpm.ps[i]);
+	}
+}
+
+static int radeon_pm_init_dpm(struct radeon_device *rdev)
+{
+	int ret;
+
+	/* default to performance state */
+	rdev->pm.dpm.state = POWER_STATE_TYPE_PERFORMANCE;
+	rdev->pm.dpm.user_state = POWER_STATE_TYPE_PERFORMANCE;
+	rdev->pm.default_sclk = rdev->clock.default_sclk;
+	rdev->pm.default_mclk = rdev->clock.default_mclk;
+	rdev->pm.current_sclk = rdev->clock.default_sclk;
+	rdev->pm.current_mclk = rdev->clock.default_mclk;
+	rdev->pm.int_thermal_type = THERMAL_TYPE_NONE;
+
+	if (rdev->bios && rdev->is_atom_bios)
+		radeon_atombios_get_power_modes(rdev);
+	else
+		return -EINVAL;
+
+	/* set up the internal thermal sensor if applicable */
+	ret = radeon_hwmon_init(rdev);
+	if (ret)
+		return ret;
+
+	INIT_WORK(&rdev->pm.dpm.thermal.work, radeon_dpm_thermal_work_handler);
+	mutex_lock(&rdev->pm.mutex);
+	radeon_dpm_init(rdev);
+	rdev->pm.dpm.current_ps = rdev->pm.dpm.requested_ps = rdev->pm.dpm.boot_ps;
+	radeon_dpm_print_power_states(rdev);
+	radeon_dpm_setup_asic(rdev);
+	ret = radeon_dpm_enable(rdev);
+	mutex_unlock(&rdev->pm.mutex);
+	if (ret) {
+		rdev->pm.dpm_enabled = false;
+		if ((rdev->family >= CHIP_BARTS) &&
+		    (rdev->family <= CHIP_CAYMAN) &&
+		    rdev->mc_fw) {
+			if (rdev->pm.default_vddc)
+				radeon_atom_set_voltage(rdev, rdev->pm.default_vddc,
+							SET_VOLTAGE_TYPE_ASIC_VDDC);
+			if (rdev->pm.default_vddci)
+				radeon_atom_set_voltage(rdev, rdev->pm.default_vddci,
+							SET_VOLTAGE_TYPE_ASIC_VDDCI);
+			if (rdev->pm.default_sclk)
+				radeon_set_engine_clock(rdev, rdev->pm.default_sclk);
+			if (rdev->pm.default_mclk)
+				radeon_set_memory_clock(rdev, rdev->pm.default_mclk);
+		}
+		DRM_ERROR("radeon: dpm initialization failed\n");
+		return ret;
+	}
+	rdev->pm.dpm_enabled = true;
+	radeon_pm_compute_clocks(rdev);
+
+	if (rdev->pm.num_power_states > 1) {
+		ret = device_create_file(rdev->dev, &dev_attr_power_dpm_state);
+		if (ret)
+			DRM_ERROR("failed to create device file for dpm state\n");
+		/* XXX: these are noops for dpm but are here for backwards compat */
+		ret = device_create_file(rdev->dev, &dev_attr_power_profile);
+		if (ret)
+			DRM_ERROR("failed to create device file for power profile\n");
+		ret = device_create_file(rdev->dev, &dev_attr_power_method);
+		if (ret)
+			DRM_ERROR("failed to create device file for power method\n");
+		DRM_INFO("radeon: dpm initialized\n");
+	}
+
+	return 0;
+}
+
+int radeon_pm_init(struct radeon_device *rdev)
+{
+	/* enable dpm on rv6xx+ */
+	switch (rdev->family) {
+	default:
+		/* default to profile method */
+		rdev->pm.pm_method = PM_METHOD_PROFILE;
+		break;
+	}
+
+	if (rdev->pm.pm_method == PM_METHOD_DPM)
+		return radeon_pm_init_dpm(rdev);
+	else
+		return radeon_pm_init_old(rdev);
+}
+
+static void radeon_pm_fini_old(struct radeon_device *rdev)
 {
 	if (rdev->pm.num_power_states > 1) {
 		mutex_lock(&rdev->pm.mutex);
@@ -650,7 +1070,35 @@  void radeon_pm_fini(struct radeon_device *rdev)
 	radeon_hwmon_fini(rdev);
 }
 
-void radeon_pm_compute_clocks(struct radeon_device *rdev)
+static void radeon_pm_fini_dpm(struct radeon_device *rdev)
+{
+	if (rdev->pm.num_power_states > 1) {
+		mutex_lock(&rdev->pm.mutex);
+		radeon_dpm_disable(rdev);
+		mutex_unlock(&rdev->pm.mutex);
+
+		device_remove_file(rdev->dev, &dev_attr_power_dpm_state);
+		/* XXX backwards compat */
+		device_remove_file(rdev->dev, &dev_attr_power_profile);
+		device_remove_file(rdev->dev, &dev_attr_power_method);
+	}
+	radeon_dpm_fini(rdev);
+
+	if (rdev->pm.power_state)
+		kfree(rdev->pm.power_state);
+
+	radeon_hwmon_fini(rdev);
+}
+
+void radeon_pm_fini(struct radeon_device *rdev)
+{
+	if (rdev->pm.pm_method == PM_METHOD_DPM)
+		radeon_pm_fini_dpm(rdev);
+	else
+		radeon_pm_fini_old(rdev);
+}
+
+static void radeon_pm_compute_clocks_old(struct radeon_device *rdev)
 {
 	struct drm_device *ddev = rdev->ddev;
 	struct drm_crtc *crtc;
@@ -721,6 +1169,38 @@  void radeon_pm_compute_clocks(struct radeon_device *rdev)
 	mutex_unlock(&rdev->pm.mutex);
 }
 
+static void radeon_pm_compute_clocks_dpm(struct radeon_device *rdev)
+{
+	struct drm_device *ddev = rdev->ddev;
+	struct drm_crtc *crtc;
+	struct radeon_crtc *radeon_crtc;
+
+	mutex_lock(&rdev->pm.mutex);
+
+	rdev->pm.dpm.new_active_crtcs = 0;
+	rdev->pm.dpm.new_active_crtc_count = 0;
+	list_for_each_entry(crtc,
+		&ddev->mode_config.crtc_list, head) {
+		radeon_crtc = to_radeon_crtc(crtc);
+		if (crtc->enabled) {
+			rdev->pm.dpm.new_active_crtcs |= (1 << radeon_crtc->crtc_id);
+			rdev->pm.dpm.new_active_crtc_count++;
+		}
+	}
+
+	radeon_dpm_change_power_state_locked(rdev);
+
+	mutex_unlock(&rdev->pm.mutex);
+}
+
+void radeon_pm_compute_clocks(struct radeon_device *rdev)
+{
+	if (rdev->pm.pm_method == PM_METHOD_DPM)
+		radeon_pm_compute_clocks_dpm(rdev);
+	else
+		radeon_pm_compute_clocks_old(rdev);
+}
+
 static bool radeon_pm_in_vbl(struct radeon_device *rdev)
 {
 	int  crtc, vpos, hpos, vbl_status;