[PATCH] drm/amd/powerplay: expose vega20 OD features

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Vega20 simplifies the OD logics and it can not fit old
OD interfaces. Thus we design new OD interfaces for
vega20.

Change-Id: I888faec46a81287ae24f452ce16b42c1f6d06d7d
Signed-off-by: Evan Quan <evan.quan at amd.com>
---
 drivers/gpu/drm/amd/amdgpu/amdgpu_dpm.h       |   8 +
 drivers/gpu/drm/amd/amdgpu/amdgpu_pm.c        | 125 ++++++++++++
 .../gpu/drm/amd/include/kgd_pp_interface.h    |   2 +
 drivers/gpu/drm/amd/powerplay/amd_powerplay.c |  37 ++++
 .../drm/amd/powerplay/hwmgr/vega20_hwmgr.c    | 191 +++++++++++++++++-
 drivers/gpu/drm/amd/powerplay/inc/hwmgr.h     |   2 +
 6 files changed, 362 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_dpm.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_dpm.h
index ff24e1cc5b65..84b3e6f87abf 100644
--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_dpm.h
+++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_dpm.h
@@ -357,6 +357,14 @@ enum amdgpu_pcie_gen {
 		((adev)->powerplay.pp_funcs->odn_edit_dpm_table(\
 			(adev)->powerplay.pp_handle, type, parameter, size))
 
+#define amdgpu_dpm_get_od8_settings(adev, buf) \
+		((adev)->powerplay.pp_funcs->get_od8_settings(\
+			(adev)->powerplay.pp_handle, buf))
+
+#define amdgpu_dpm_set_od8_settings(adev, parameter, size) \
+		((adev)->powerplay.pp_funcs->set_od8_settings(\
+			(adev)->powerplay.pp_handle, parameter, size))
+
 struct amdgpu_dpm {
 	struct amdgpu_ps        *ps;
 	/* number of valid power states */
diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_pm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_pm.c
index daa55fb06171..94cd7c503372 100644
--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_pm.c
+++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_pm.c
@@ -934,6 +934,121 @@ static ssize_t amdgpu_get_busy_percent(struct device *dev,
 	return snprintf(buf, PAGE_SIZE, "%d\n", value);
 }
 
+/**
+ * DOC: pp_od8_settings
+ *
+ * The amdgpu driver provides a sysfs API for adjusting the clocks, voltages,
+ * power limit, fan speed and temperature. The pp_od8_settings is used for
+ * this.
+ *
+ * Reading the file will display:
+ *
+ * - a name list of the features that are able to be adjusted
+ *
+ * - the mininum and maximum allowed value for each supported
+ *   feature labeled in format of "[mininum - maximum]"
+ *
+ * - the current value for each supported feature labeled after
+ *   ":"
+ *
+ * To manually adjust these settings:
+ *
+ * - write a string that contains the new value for each supported
+ *   feature. For those which do not need to be changed, just enter
+ *   their old value
+ *
+ * - make sure the new value is within the allowed ranges between
+ *   the minimum and maximum
+ *
+ * All the possible supported features:
+ *
+ * - Gfx clock
+ *   The min and max frequency can be specified by GfxclkFmin
+ *   and GfxclkFmax(both in Mhz). Intermediate levels are
+ *   stretched/shrunk according to ratio in original table.
+ *
+ * - Gfx voltage
+ *   With three pairs of gfx clk(in Mhz) and offset voltage(in mV)
+ *   combinations specified, smu fw can calibrate the voltage
+ *   curve automatically.
+ *     - GfxclkFreq1 <-> GfxclkOffsetVolt1
+ *     - GfxclkFreq2 <-> GfxclkOffsetVolt2
+ *     - GfxclkFreq3 <-> GfxclkOffsetVolt3
+ *
+ * - Uclk
+ *   The max memory clock can be specified by UclkFmax(in Mhz).
+ *
+ * - Power limit
+ *   The power limit can be increased or descreased by
+ *   OverDrivePct(in percent).
+ *
+ * - Fan spped
+ *   The max fan speed can be set by FanMaxinumRpm and the
+ *   min fan speed by FanMinimumPwm. Also zero rpm enablement
+ *   is specified by FanZeroRpmEnable.
+ *
+ * - Temperature
+ *   The fan target temperature can be set by FanTargetTemperature
+ *   and max Tj temperature by MaxOpTemp.
+ *
+ */
+
+static ssize_t amdgpu_get_pp_od8_settings(struct device *dev,
+		struct device_attribute *attr,
+		char *buf)
+{
+	struct drm_device *ddev = dev_get_drvdata(dev);
+	struct amdgpu_device *adev = ddev->dev_private;
+
+	if (adev->powerplay.pp_funcs->get_od8_settings)
+		return amdgpu_dpm_get_od8_settings(adev, buf);
+
+	return snprintf(buf, PAGE_SIZE, "\n");
+}
+
+static ssize_t amdgpu_set_pp_od8_settings(struct device *dev,
+		struct device_attribute *attr,
+		const char *buf,
+		size_t count)
+{
+	struct drm_device *ddev = dev_get_drvdata(dev);
+	struct amdgpu_device *adev = ddev->dev_private;
+	char *sub_str, *tmp_str, buf_cpy[128];
+	const char delimiter[3] = {' ', '\n', '\0'};
+	uint32_t parameter_size = 0;
+	long parameter[64];
+	int ret = 0xff;
+
+	if (count >= sizeof(buf_cpy) / sizeof(buf_cpy[0]))
+		return -EINVAL;
+
+	memcpy(buf_cpy, buf, count);
+	buf_cpy[count] = '\0';
+
+	tmp_str = buf_cpy;
+
+	while (isspace(*tmp_str)) tmp_str++;
+
+	while (tmp_str[0]) {
+		sub_str = strsep(&tmp_str, delimiter);
+		ret = kstrtol(sub_str, 0, &parameter[parameter_size]);
+		if (ret)
+			return -EINVAL;
+		parameter_size++;
+
+		while (isspace(*tmp_str))
+			tmp_str++;
+	}
+
+	if (adev->powerplay.pp_funcs->set_od8_settings)
+		ret = amdgpu_dpm_set_od8_settings(adev, parameter, parameter_size);
+
+	if (ret)
+		return -EINVAL;
+
+	return count;
+}
+
 static DEVICE_ATTR(power_dpm_state, S_IRUGO | S_IWUSR, amdgpu_get_dpm_state, amdgpu_set_dpm_state);
 static DEVICE_ATTR(power_dpm_force_performance_level, S_IRUGO | S_IWUSR,
 		   amdgpu_get_dpm_forced_performance_level,
@@ -969,6 +1084,9 @@ static DEVICE_ATTR(pp_od_clk_voltage, S_IRUGO | S_IWUSR,
 		amdgpu_set_pp_od_clk_voltage);
 static DEVICE_ATTR(gpu_busy_percent, S_IRUGO,
 		amdgpu_get_busy_percent, NULL);
+static DEVICE_ATTR(pp_od8_settings, S_IRUGO | S_IWUSR,
+		amdgpu_get_pp_od8_settings,
+		amdgpu_set_pp_od8_settings);
 
 static ssize_t amdgpu_hwmon_show_temp(struct device *dev,
 				      struct device_attribute *attr,
@@ -1879,6 +1997,13 @@ int amdgpu_pm_sysfs_init(struct amdgpu_device *adev)
 				"gpu_busy_level\n");
 		return ret;
 	}
+	ret = device_create_file(adev->dev,
+			&dev_attr_pp_od8_settings);
+	if (ret) {
+		DRM_ERROR("failed to create device file	"
+				"pp_od8_settings\n");
+		return ret;
+	}
 	ret = amdgpu_debugfs_pm_init(adev);
 	if (ret) {
 		DRM_ERROR("Failed to register debugfs file for dpm!\n");
diff --git a/drivers/gpu/drm/amd/include/kgd_pp_interface.h b/drivers/gpu/drm/amd/include/kgd_pp_interface.h
index 6a41b81c7325..e23746ba53bf 100644
--- a/drivers/gpu/drm/amd/include/kgd_pp_interface.h
+++ b/drivers/gpu/drm/amd/include/kgd_pp_interface.h
@@ -269,6 +269,8 @@ struct amd_pm_funcs {
 	int (*get_display_mode_validation_clocks)(void *handle,
 		struct amd_pp_simple_clock_info *clocks);
 	int (*notify_smu_enable_pwe)(void *handle);
+	int (*get_od8_settings)(void *handle, char *buf);
+	int (*set_od8_settings)(void *handle, long *input, uint32_t size);
 };
 
 #endif
diff --git a/drivers/gpu/drm/amd/powerplay/amd_powerplay.c b/drivers/gpu/drm/amd/powerplay/amd_powerplay.c
index da4ebff5b74d..4bcd06306698 100644
--- a/drivers/gpu/drm/amd/powerplay/amd_powerplay.c
+++ b/drivers/gpu/drm/amd/powerplay/amd_powerplay.c
@@ -883,6 +883,41 @@ static int pp_odn_edit_dpm_table(void *handle, uint32_t type, long *input, uint3
 	return hwmgr->hwmgr_func->odn_edit_dpm_table(hwmgr, type, input, size);
 }
 
+static int pp_get_od8_settings(void *handle, char *buf)
+{
+	struct pp_hwmgr *hwmgr = handle;
+
+	if (!hwmgr || !hwmgr->pm_en || !buf)
+		return -EINVAL;
+
+	if (hwmgr->hwmgr_func->get_od8_settings == NULL) {
+		pr_info("%s was not implemented.\n", __func__);
+		return snprintf(buf, PAGE_SIZE, "\n");
+	}
+
+	return hwmgr->hwmgr_func->get_od8_settings(hwmgr, buf);
+}
+
+static int pp_set_od8_settings(void *handle, long *input, uint32_t size)
+{
+	struct pp_hwmgr *hwmgr = handle;
+	int ret = -EINVAL;
+
+	if (!hwmgr || !hwmgr->pm_en)
+		return ret;
+
+	if (hwmgr->hwmgr_func->set_od8_settings == NULL) {
+		pr_info("%s was not implemented.\n", __func__);
+		return ret;
+	}
+
+	mutex_lock(&hwmgr->smu_lock);
+	ret = hwmgr->hwmgr_func->set_od8_settings(hwmgr, input, size);
+	mutex_unlock(&hwmgr->smu_lock);
+
+	return ret;
+}
+
 static int pp_dpm_switch_power_profile(void *handle,
 		enum PP_SMC_POWER_PROFILE type, bool en)
 {
@@ -1272,6 +1307,8 @@ static const struct amd_pm_funcs pp_dpm_funcs = {
 	.get_power_profile_mode = pp_get_power_profile_mode,
 	.set_power_profile_mode = pp_set_power_profile_mode,
 	.odn_edit_dpm_table = pp_odn_edit_dpm_table,
+	.get_od8_settings = pp_get_od8_settings,
+	.set_od8_settings = pp_set_od8_settings,
 	.set_power_limit = pp_set_power_limit,
 	.get_power_limit = pp_get_power_limit,
 /* export to DC */
diff --git a/drivers/gpu/drm/amd/powerplay/hwmgr/vega20_hwmgr.c b/drivers/gpu/drm/amd/powerplay/hwmgr/vega20_hwmgr.c
index fb32b28afa66..ececa2f7fe5f 100644
--- a/drivers/gpu/drm/amd/powerplay/hwmgr/vega20_hwmgr.c
+++ b/drivers/gpu/drm/amd/powerplay/hwmgr/vega20_hwmgr.c
@@ -1099,7 +1099,188 @@ static int vega20_od8_initialize_default_settings(
 	return 0;
 }
 
-static int vega20_od8_set_settings(
+static int vega20_get_od8_settings(
+		struct pp_hwmgr *hwmgr,
+		char *buf)
+{
+	struct vega20_hwmgr *data =
+			(struct vega20_hwmgr *)(hwmgr->backend);
+	struct vega20_od8_single_setting *od8_settings =
+			data->od8_settings.od8_settings_array;
+	OverDriveTable_t od_table;
+	uint32_t size = 0;
+	int ret = 0;
+
+	if (!buf)
+		return -EINVAL;
+
+	ret = vega20_copy_table_from_smc(hwmgr,
+			(uint8_t *)(&od_table),
+			TABLE_OVERDRIVE);
+	PP_ASSERT_WITH_CODE(!ret,
+			"Failed to export over drive table!",
+			return ret);
+
+	if (od8_settings[OD8_SETTING_GFXCLK_FMIN].feature_id)
+		size += sprintf(buf + size, "%22s[%d - %d]: %u\n", "GfxclkFmin",
+				od8_settings[OD8_SETTING_GFXCLK_FMIN].min_value,
+				od8_settings[OD8_SETTING_GFXCLK_FMIN].max_value,
+				od_table.GfxclkFmin);
+
+	if (od8_settings[OD8_SETTING_GFXCLK_FMAX].feature_id)
+		size += sprintf(buf + size, "%22s[%d - %d]: %u\n", "GfxclkFmax",
+				od8_settings[OD8_SETTING_GFXCLK_FMAX].min_value,
+				od8_settings[OD8_SETTING_GFXCLK_FMAX].max_value,
+				od_table.GfxclkFmax);
+
+	if (od8_settings[OD8_SETTING_GFXCLK_FREQ1].feature_id)
+		size += sprintf(buf + size, "%22s[%d - %d]: %u\n", "GfxclkFreq1",
+				od8_settings[OD8_SETTING_GFXCLK_FREQ1].min_value,
+				od8_settings[OD8_SETTING_GFXCLK_FREQ1].max_value,
+				od_table.GfxclkFreq1);
+
+	if (od8_settings[OD8_SETTING_GFXCLK_VOLTAGE1].feature_id)
+		size += sprintf(buf + size, "%22s[%d - %d]: %u\n", "GfxclkOffsetVolt1",
+				od8_settings[OD8_SETTING_GFXCLK_VOLTAGE1].min_value,
+				od8_settings[OD8_SETTING_GFXCLK_VOLTAGE1].max_value,
+				od_table.GfxclkOffsetVolt1);
+
+	if (od8_settings[OD8_SETTING_GFXCLK_FREQ2].feature_id)
+		size += sprintf(buf + size, "%22s[%d - %d]: %u\n", "GfxclkFreq2",
+				od8_settings[OD8_SETTING_GFXCLK_FREQ2].min_value,
+				od8_settings[OD8_SETTING_GFXCLK_FREQ2].max_value,
+				od_table.GfxclkFreq2);
+
+	if (od8_settings[OD8_SETTING_GFXCLK_VOLTAGE2].feature_id)
+		size += sprintf(buf + size, "%22s[%d - %d]: %u\n", "GfxclkOffsetVolt2",
+				od8_settings[OD8_SETTING_GFXCLK_VOLTAGE2].min_value,
+				od8_settings[OD8_SETTING_GFXCLK_VOLTAGE2].max_value,
+				od_table.GfxclkOffsetVolt2);
+
+	if (od8_settings[OD8_SETTING_GFXCLK_FREQ3].feature_id)
+		size += sprintf(buf + size, "%22s[%d - %d]: %u\n", "GfxclkFreq3",
+				od8_settings[OD8_SETTING_GFXCLK_FREQ3].min_value,
+				od8_settings[OD8_SETTING_GFXCLK_FREQ3].max_value,
+				od_table.GfxclkFreq3);
+
+	if (od8_settings[OD8_SETTING_GFXCLK_VOLTAGE3].feature_id)
+		size += sprintf(buf + size, "%22s[%d - %d]: %u\n", "GfxclkOffsetVolt3",
+				od8_settings[OD8_SETTING_GFXCLK_VOLTAGE3].min_value,
+				od8_settings[OD8_SETTING_GFXCLK_VOLTAGE3].max_value,
+				od_table.GfxclkOffsetVolt3);
+
+	if (od8_settings[OD8_SETTING_UCLK_FMAX].feature_id)
+		size += sprintf(buf + size, "%22s[%d - %d]: %u\n", "UclkFmax",
+				od8_settings[OD8_SETTING_UCLK_FMAX].min_value,
+				od8_settings[OD8_SETTING_UCLK_FMAX].max_value,
+				od_table.UclkFmax);
+
+	if (od8_settings[OD8_SETTING_POWER_PERCENTAGE].feature_id)
+		size += sprintf(buf + size, "%22s[%d - %d]: %u\n", "OverDrivePct",
+				od8_settings[OD8_SETTING_POWER_PERCENTAGE].min_value,
+				od8_settings[OD8_SETTING_POWER_PERCENTAGE].max_value,
+				od_table.OverDrivePct);
+
+	if (od8_settings[OD8_SETTING_FAN_ACOUSTIC_LIMIT].feature_id)
+		size += sprintf(buf + size, "%22s[%d - %d]: %u\n", "FanMaximumRpm",
+				od8_settings[OD8_SETTING_FAN_ACOUSTIC_LIMIT].min_value,
+				od8_settings[OD8_SETTING_FAN_ACOUSTIC_LIMIT].max_value,
+				od_table.FanMaximumRpm);
+
+	if (od8_settings[OD8_SETTING_FAN_MIN_SPEED].feature_id)
+		size += sprintf(buf + size, "%22s[%d - %d]: %u\n", "FanMinimumPwm",
+				od8_settings[OD8_SETTING_FAN_MIN_SPEED].min_value,
+				od8_settings[OD8_SETTING_FAN_MIN_SPEED].max_value,
+				od_table.FanMinimumPwm);
+
+	if (od8_settings[OD8_SETTING_FAN_TARGET_TEMP].feature_id)
+		size += sprintf(buf + size, "%22s[%d - %d]: %u\n", "FanTargetTemperautre",
+				od8_settings[OD8_SETTING_FAN_TARGET_TEMP].min_value,
+				od8_settings[OD8_SETTING_FAN_TARGET_TEMP].max_value,
+				od_table.FanTargetTemperature);
+
+	if (od8_settings[OD8_SETTING_OPERATING_TEMP_MAX].feature_id)
+		size += sprintf(buf + size, "%22s[%d - %d]: %u\n", "MaxOpTemp",
+				od8_settings[OD8_SETTING_OPERATING_TEMP_MAX].min_value,
+				od8_settings[OD8_SETTING_OPERATING_TEMP_MAX].max_value,
+				od_table.MaxOpTemp);
+
+	if (od8_settings[OD8_SETTING_FAN_ZERO_RPM_CONTROL].feature_id)
+		size += sprintf(buf + size, "%22s[%d - %d]: %u\n", "FanZeroRpmEnable",
+				od8_settings[OD8_SETTING_FAN_ZERO_RPM_CONTROL].min_value,
+				od8_settings[OD8_SETTING_FAN_ZERO_RPM_CONTROL].max_value,
+				od_table.FanZeroRpmEnable);
+
+	return size;
+}
+
+static int vega20_set_od8_settings(
+		struct pp_hwmgr *hwmgr,
+		long *input,
+		uint32_t size)
+{
+	struct vega20_hwmgr *data =
+			(struct vega20_hwmgr *)(hwmgr->backend);
+	struct vega20_od8_single_setting *od8_settings =
+			data->od8_settings.od8_settings_array;
+	OverDriveTable_t od_table;
+	uint16_t *buf = (uint16_t *)(&od_table);
+	uint32_t i, j = 0, k;
+	int ret = 0;
+
+	/* retrieve current overdrive settings */
+	ret = vega20_copy_table_from_smc(hwmgr,
+			(uint8_t *)(&od_table),
+			TABLE_OVERDRIVE);
+	PP_ASSERT_WITH_CODE(!ret,
+			"Failed to export over drive table!",
+			return ret);
+
+	for (i = 0;
+	     i < (sizeof(OverDriveTable_t) / sizeof(uint16_t)) - 1;
+	     i++) {
+		/* overdrive table supports no dram timing setting */
+		k = (i == OD8_SETTING_AC_TIMING) ?
+			OD8_SETTING_FAN_ZERO_RPM_CONTROL : i;
+		/*
+		 * Not all OD settings are supported and exported
+		 * to user.
+		 * So, updated only those which are supported and
+		 * exported to user.
+		 */
+		if (od8_settings[k].feature_id) {
+			if (input[j] < od8_settings[k].min_value ||
+			    input[j] > od8_settings[k].max_value)
+				return -EINVAL;
+
+			buf[i] = input[j];
+			if (j++ >= size - 1)
+				break;
+		}
+	}
+
+	/* enable the new overdrive settings */
+	ret = vega20_copy_table_to_smc(hwmgr,
+			(uint8_t *)(&od_table),
+			TABLE_OVERDRIVE);
+	PP_ASSERT_WITH_CODE(!ret,
+			"Failed to import over drive table!",
+			return ret);
+
+	/* update the current_value of overdrive settings */
+	for (i = 0;
+	     i < (sizeof(OverDriveTable_t) / sizeof(uint16_t)) - 1;
+	     i++) {
+		k = (i == OD8_SETTING_AC_TIMING) ?
+			OD8_SETTING_FAN_ZERO_RPM_CONTROL : i;
+		if (od8_settings[k].feature_id)
+			od8_settings[k].current_value = buf[i];
+	}
+
+	return 0;
+}
+
+static int vega20_od8_set_single_setting(
 		struct pp_hwmgr *hwmgr,
 		uint32_t index,
 		uint32_t value)
@@ -1198,7 +1379,7 @@ static int vega20_set_sclk_od(
 	do_div(od_sclk, 100);
 	od_sclk += golden_sclk_table->dpm_levels[golden_sclk_table->count - 1].value;
 
-	ret = vega20_od8_set_settings(hwmgr, OD8_SETTING_GFXCLK_FMAX, od_sclk);
+	ret = vega20_od8_set_single_setting(hwmgr, OD8_SETTING_GFXCLK_FMAX, od_sclk);
 	PP_ASSERT_WITH_CODE(!ret,
 			"[SetSclkOD] failed to set od gfxclk!",
 			return ret);
@@ -1245,7 +1426,7 @@ static int vega20_set_mclk_od(
 	do_div(od_mclk, 100);
 	od_mclk += golden_mclk_table->dpm_levels[golden_mclk_table->count - 1].value;
 
-	ret = vega20_od8_set_settings(hwmgr, OD8_SETTING_UCLK_FMAX, od_mclk);
+	ret = vega20_od8_set_single_setting(hwmgr, OD8_SETTING_UCLK_FMAX, od_mclk);
 	PP_ASSERT_WITH_CODE(!ret,
 			"[SetMclkOD] failed to set od memclk!",
 			return ret);
@@ -2966,6 +3147,10 @@ static const struct pp_hwmgr_func vega20_hwmgr_funcs = {
 		vega20_get_power_profile_mode,
 	.set_power_profile_mode =
 		vega20_set_power_profile_mode,
+	.get_od8_settings =
+		vega20_get_od8_settings,
+	.set_od8_settings =
+		vega20_set_od8_settings,
 	/* od related */
 	.set_power_limit =
 		vega20_set_power_limit,
diff --git a/drivers/gpu/drm/amd/powerplay/inc/hwmgr.h b/drivers/gpu/drm/amd/powerplay/inc/hwmgr.h
index a6d92128b19c..285af1728e2e 100644
--- a/drivers/gpu/drm/amd/powerplay/inc/hwmgr.h
+++ b/drivers/gpu/drm/amd/powerplay/inc/hwmgr.h
@@ -328,6 +328,8 @@ struct pp_hwmgr_func {
 	int (*set_power_limit)(struct pp_hwmgr *hwmgr, uint32_t n);
 	int (*powergate_mmhub)(struct pp_hwmgr *hwmgr);
 	int (*smus_notify_pwe)(struct pp_hwmgr *hwmgr);
+	int (*get_od8_settings)(struct pp_hwmgr *hwmgr, char *buf);
+	int (*set_od8_settings)(struct pp_hwmgr *hwmgr, long *input, uint32_t size);
 };
 
 struct pp_table_func {
-- 
2.18.0



[Index of Archives]     [Linux USB Devel]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux