drm/radeon/dpm: add infrastructure to force performance levels
authorAlex Deucher <alexander.deucher@amd.com>
Tue, 2 Jul 2013 22:38:02 +0000 (18:38 -0400)
committerAlex Deucher <alexander.deucher@amd.com>
Fri, 5 Jul 2013 22:09:19 +0000 (18:09 -0400)
This allows you to force specific power levels within a power
state.  Due to hardware restrictions between generations, the
interface is limited to the following 3 selections:

auto: all levels enabled
low: forced to the lowest power level
high: forced to the highest power level

Signed-off-by: Alex Deucher <alexander.deucher@amd.com>
drivers/gpu/drm/radeon/radeon.h
drivers/gpu/drm/radeon/radeon_pm.c

index 08cececb376de565186776863f79c2d9805457ea..b4681313646c8b2720e37173a82e588542c4342b 100644 (file)
@@ -1335,6 +1335,12 @@ enum radeon_pcie_gen {
        RADEON_PCIE_GEN_INVALID = 0xffff
 };
 
+enum radeon_dpm_forced_level {
+       RADEON_DPM_FORCED_LEVEL_AUTO = 0,
+       RADEON_DPM_FORCED_LEVEL_LOW = 1,
+       RADEON_DPM_FORCED_LEVEL_HIGH = 2,
+};
+
 struct radeon_dpm {
        struct radeon_ps        *ps;
        /* number of valid power states */
@@ -1374,6 +1380,8 @@ struct radeon_dpm {
        bool                    uvd_active;
        /* thermal handling */
        struct radeon_dpm_thermal thermal;
+       /* forced levels */
+       enum radeon_dpm_forced_level forced_level;
 };
 
 void radeon_dpm_enable_power_state(struct radeon_device *rdev,
@@ -1669,6 +1677,7 @@ struct radeon_asic {
                u32 (*get_mclk)(struct radeon_device *rdev, bool low);
                void (*print_power_state)(struct radeon_device *rdev, struct radeon_ps *ps);
                void (*debugfs_print_current_performance_level)(struct radeon_device *rdev, struct seq_file *m);
+               int (*force_performance_level)(struct radeon_device *rdev, enum radeon_dpm_forced_level level);
        } dpm;
        /* pageflipping */
        struct {
@@ -2436,6 +2445,7 @@ void radeon_ring_write(struct radeon_ring *ring, uint32_t v);
 #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))
 #define radeon_dpm_debugfs_print_current_performance_level(rdev, m) rdev->asic->dpm.debugfs_print_current_performance_level((rdev), (m))
+#define radeon_dpm_force_performance_level(rdev, l) rdev->asic->dpm.force_performance_level((rdev), (l))
 
 /* Common functions */
 /* AGP */
index aaafb931922faf88d3d29eb52fb629cd0d00bfdc..2557e8bab5cc9416c7fa0b42d9700d0f239d480c 100644 (file)
@@ -468,9 +468,57 @@ fail:
        return count;
 }
 
+static ssize_t radeon_get_dpm_forced_performance_level(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_dpm_forced_level level = rdev->pm.dpm.forced_level;
+
+       return snprintf(buf, PAGE_SIZE, "%s\n",
+                       (level == RADEON_DPM_FORCED_LEVEL_AUTO) ? "auto" :
+                       (level == RADEON_DPM_FORCED_LEVEL_LOW) ? "low" : "high");
+}
+
+static ssize_t radeon_set_dpm_forced_performance_level(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;
+       enum radeon_dpm_forced_level level;
+       int ret = 0;
+
+       mutex_lock(&rdev->pm.mutex);
+       if (strncmp("low", buf, strlen("low")) == 0) {
+               level = RADEON_DPM_FORCED_LEVEL_LOW;
+       } else if (strncmp("high", buf, strlen("high")) == 0) {
+               level = RADEON_DPM_FORCED_LEVEL_HIGH;
+       } else if (strncmp("auto", buf, strlen("auto")) == 0) {
+               level = RADEON_DPM_FORCED_LEVEL_AUTO;
+       } else {
+               mutex_unlock(&rdev->pm.mutex);
+               count = -EINVAL;
+               goto fail;
+       }
+       if (rdev->asic->dpm.force_performance_level) {
+               ret = radeon_dpm_force_performance_level(rdev, level);
+               if (ret)
+                       count = -EINVAL;
+       }
+       mutex_unlock(&rdev->pm.mutex);
+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 DEVICE_ATTR(power_dpm_force_performance_level, S_IRUGO | S_IWUSR,
+                  radeon_get_dpm_forced_performance_level,
+                  radeon_set_dpm_forced_performance_level);
 
 static ssize_t radeon_hwmon_show_temp(struct device *dev,
                                      struct device_attribute *attr,
@@ -1064,6 +1112,9 @@ static int radeon_pm_init_dpm(struct radeon_device *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");
+               ret = device_create_file(rdev->dev, &dev_attr_power_dpm_force_performance_level);
                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 */
@@ -1170,6 +1221,7 @@ static void radeon_pm_fini_dpm(struct radeon_device *rdev)
                mutex_unlock(&rdev->pm.mutex);
 
                device_remove_file(rdev->dev, &dev_attr_power_dpm_state);
+               device_remove_file(rdev->dev, &dev_attr_power_dpm_force_performance_level);
                /* XXX backwards compat */
                device_remove_file(rdev->dev, &dev_attr_power_profile);
                device_remove_file(rdev->dev, &dev_attr_power_method);