/* * Copyright 2016 Advanced Micro Devices, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * * Author: Huang Rui <ray.huang@amd.com> *
*/ #include"pp_debug.h" #include <linux/types.h> #include <linux/kernel.h> #include <linux/pci.h> #include <linux/slab.h> #include <linux/gfp.h>
if (!smu7_is_smc_ram_running(hwmgr)) {
result = iceland_smu_upload_firmware_image(hwmgr); if (result) return result;
iceland_smu_start_smc(hwmgr);
}
/* Setup SoftRegsStart here to visit the register UcodeLoadStatus * to check fw loading state
*/
smu7_read_smc_sram_dword(hwmgr,
SMU71_FIRMWARE_HEADER_LOCATION +
offsetof(SMU71_Firmware_Header, SoftRegisters),
&(priv->smu7_data.soft_regs_start), 0x40000);
PP_ASSERT_WITH_CODE(NULL != hwmgr->dyn_state.cac_leakage_table, "The CAC Leakage table does not exist!", return -EINVAL);
PP_ASSERT_WITH_CODE(hwmgr->dyn_state.cac_leakage_table->count <= 8, "There should never be more than 8 entries for BapmVddcVid!!!", return -EINVAL);
PP_ASSERT_WITH_CODE(hwmgr->dyn_state.cac_leakage_table->count == hwmgr->dyn_state.vddc_dependency_on_sclk->count, "CACLeakageTable->count and VddcDependencyOnSCLk->count not equal", return -EINVAL);
if (phm_cap_enabled(hwmgr->platform_descriptor.platformCaps, PHM_PlatformCaps_EVV)) { for (i = 0; (uint32_t) i < hwmgr->dyn_state.cac_leakage_table->count; i++) {
lo_vid[i] = convert_to_vid(hwmgr->dyn_state.cac_leakage_table->entries[i].Vddc1);
hi_vid[i] = convert_to_vid(hwmgr->dyn_state.cac_leakage_table->entries[i].Vddc2);
}
} else {
PP_ASSERT_WITH_CODE(false, "Iceland should always support EVV", return -EINVAL);
}
if (phm_cap_enabled(hwmgr->platform_descriptor.platformCaps,
PHM_PlatformCaps_PowerContainment)) { if (smu7_read_smc_sram_dword(hwmgr,
SMU71_FIRMWARE_HEADER_LOCATION +
offsetof(SMU71_Firmware_Header, PmFuseTable),
&pm_fuse_table_offset, SMC_RAM_END))
PP_ASSERT_WITH_CODE(false, "Attempt to get pm_fuse_table_offset Failed!", return -EINVAL);
/* DW0 - DW3 */ if (iceland_populate_bapm_vddc_vid_sidd(hwmgr))
PP_ASSERT_WITH_CODE(false, "Attempt to populate bapm vddc vid Failed!", return -EINVAL);
/* DW4 - DW5 */ if (iceland_populate_vddc_vid(hwmgr))
PP_ASSERT_WITH_CODE(false, "Attempt to populate vddc vid Failed!", return -EINVAL);
/* DW6 */ if (iceland_populate_svi_load_line(hwmgr))
PP_ASSERT_WITH_CODE(false, "Attempt to populate SviLoadLine Failed!", return -EINVAL); /* DW7 */ if (iceland_populate_tdc_limit(hwmgr))
PP_ASSERT_WITH_CODE(false, "Attempt to populate TDCLimit Failed!", return -EINVAL); /* DW8 */ if (iceland_populate_dw8(hwmgr, pm_fuse_table_offset))
PP_ASSERT_WITH_CODE(false, "Attempt to populate TdcWaterfallCtl, " "LPMLTemperature Min and Max Failed!", return -EINVAL);
/* DW9-DW12 */ if (0 != iceland_populate_temperature_scaler(hwmgr))
PP_ASSERT_WITH_CODE(false, "Attempt to populate LPMLTemperatureScaler Failed!", return -EINVAL);
/* DW13-DW16 */ if (iceland_populate_gnb_lpml(hwmgr))
PP_ASSERT_WITH_CODE(false, "Attempt to populate GnbLPML Failed!", return -EINVAL);
/* DW18 */ if (iceland_populate_bapm_vddc_base_leakage_sidd(hwmgr))
PP_ASSERT_WITH_CODE(false, "Attempt to populate BapmVddCBaseLeakage Hi and Lo Sidd Failed!", return -EINVAL);
if (smu7_copy_bytes_to_smc(hwmgr, pm_fuse_table_offset,
(uint8_t *)&smu_data->power_tune_table, sizeof(struct SMU71_Discrete_PmFuses), SMC_RAM_END))
PP_ASSERT_WITH_CODE(false, "Attempt to download PmFuseTable Failed!", return -EINVAL);
} return 0;
}
/* clock - voltage dependency table is empty table */ if (allowed_clock_voltage_table->count == 0) return -EINVAL;
for (i = 0; i < allowed_clock_voltage_table->count; i++) { /* find first sclk bigger than request */ if (allowed_clock_voltage_table->entries[i].clk >= clock) {
*vol = allowed_clock_voltage_table->entries[i].v; return 0;
}
}
/* sclk is bigger than max sclk in the dependence table */
*vol = allowed_clock_voltage_table->entries[i - 1].v;
/* SCLK/VDDC Dependency Table has to exist. */
PP_ASSERT_WITH_CODE(NULL != hwmgr->dyn_state.vddc_dependency_on_sclk, "The SCLK/VDDC Dependency Table does not exist.", return -EINVAL);
if (NULL == hwmgr->dyn_state.cac_leakage_table) {
pr_warn("CAC Leakage Table does not exist, using vddc.\n"); return 0;
}
/* * Since voltage in the sclk/vddc dependency table is not * necessarily in ascending order because of ELB voltage * patching, loop through entire list to find exact voltage.
*/ for (v_index = 0; (uint32_t)v_index < hwmgr->dyn_state.vddc_dependency_on_sclk->count; v_index++) { if (tab->value == hwmgr->dyn_state.vddc_dependency_on_sclk->entries[v_index].v) {
vol_found = true; if ((uint32_t)v_index < hwmgr->dyn_state.cac_leakage_table->count) {
*lo = hwmgr->dyn_state.cac_leakage_table->entries[v_index].Vddc * VOLTAGE_SCALE;
*hi = (uint16_t)(hwmgr->dyn_state.cac_leakage_table->entries[v_index].Leakage * VOLTAGE_SCALE);
} else {
pr_warn("Index from SCLK/VDDC Dependency Table exceeds the CAC Leakage Table index, using maximum index from CAC table.\n");
*lo = hwmgr->dyn_state.cac_leakage_table->entries[hwmgr->dyn_state.cac_leakage_table->count - 1].Vddc * VOLTAGE_SCALE;
*hi = (uint16_t)(hwmgr->dyn_state.cac_leakage_table->entries[hwmgr->dyn_state.cac_leakage_table->count - 1].Leakage * VOLTAGE_SCALE);
} break;
}
}
/* * If voltage is not found in the first pass, loop again to * find the best match, equal or higher value.
*/ if (!vol_found) { for (v_index = 0; (uint32_t)v_index < hwmgr->dyn_state.vddc_dependency_on_sclk->count; v_index++) { if (tab->value <= hwmgr->dyn_state.vddc_dependency_on_sclk->entries[v_index].v) {
vol_found = true; if ((uint32_t)v_index < hwmgr->dyn_state.cac_leakage_table->count) {
*lo = hwmgr->dyn_state.cac_leakage_table->entries[v_index].Vddc * VOLTAGE_SCALE;
*hi = (uint16_t)(hwmgr->dyn_state.cac_leakage_table->entries[v_index].Leakage) * VOLTAGE_SCALE;
} else {
pr_warn("Index from SCLK/VDDC Dependency Table exceeds the CAC Leakage Table index in second look up, using maximum index from CAC table.");
*lo = hwmgr->dyn_state.cac_leakage_table->entries[hwmgr->dyn_state.cac_leakage_table->count - 1].Vddc * VOLTAGE_SCALE;
*hi = (uint16_t)(hwmgr->dyn_state.cac_leakage_table->entries[hwmgr->dyn_state.cac_leakage_table->count - 1].Leakage * VOLTAGE_SCALE);
} break;
}
}
if (!vol_found)
pr_warn("Unable to get std_vddc from SCLK/VDDC Dependency Table, using vddc.\n");
}
staticint iceland_populate_smc_voltage_tables(struct pp_hwmgr *hwmgr,
SMU71_Discrete_DpmTable *table)
{ int result;
result = iceland_populate_smc_vddc_table(hwmgr, table);
PP_ASSERT_WITH_CODE(0 == result, "can not populate VDDC voltage table to SMC", return -EINVAL);
result = iceland_populate_smc_vdd_ci_table(hwmgr, table);
PP_ASSERT_WITH_CODE(0 == result, "can not populate VDDCI voltage table to SMC", return -EINVAL);
result = iceland_populate_smc_mvdd_table(hwmgr, table);
PP_ASSERT_WITH_CODE(0 == result, "can not populate MVDD voltage table to SMC", return -EINVAL);
result = pp_tables_get_response_times(hwmgr, &voltage_response_time, &ulv_voltage);
PP_ASSERT_WITH_CODE((0 == result), "can not get ULV voltage value", return result;);
if (data->voltage_control != SMU7_VOLTAGE_CONTROL_BY_SVID2) { /* use minimum voltage if ulv voltage in pptable is bigger than minimum voltage */ if (ulv_voltage > hwmgr->dyn_state.vddc_dependency_on_sclk->entries[0].v)
state->VddcOffset = 0; else /* used in SMIO Mode. not implemented for now. this is backup only for CI. */
state->VddcOffset = (uint16_t)(hwmgr->dyn_state.vddc_dependency_on_sclk->entries[0].v - ulv_voltage);
} else { /* use minimum voltage if ulv voltage in pptable is bigger than minimum voltage */ if (ulv_voltage > hwmgr->dyn_state.vddc_dependency_on_sclk->entries[0].v)
state->VddcOffsetVid = 0; else/* used in SVI2 Mode */
state->VddcOffsetVid = (uint8_t)(
(hwmgr->dyn_state.vddc_dependency_on_sclk->entries[0].v - ulv_voltage)
* VOLTAGE_VID_OFFSET_SCALE2
/ VOLTAGE_VID_OFFSET_SCALE1);
}
state->VddcPhase = 1;
result = iceland_calculate_sclk_params(hwmgr, engine_clock, graphic_level);
/* populate graphics levels*/
result = iceland_get_dependency_volt_by_clk(hwmgr,
hwmgr->dyn_state.vddc_dependency_on_sclk, engine_clock,
&graphic_level->MinVddc);
PP_ASSERT_WITH_CODE((0 == result), "can not find VDDC voltage value for VDDC engine clock dependency table", return result);
/* SCLK frequency in units of 10KHz*/
graphic_level->SclkFrequency = engine_clock;
graphic_level->MinVddcPhases = 1;
if (data->vddc_phase_shed_control)
iceland_populate_phase_value_based_on_sclk(hwmgr,
hwmgr->dyn_state.vddc_phase_shed_limits_table,
engine_clock,
&graphic_level->MinVddcPhases);
/* Indicates maximum activity level for this performance level. 50% for now*/
graphic_level->ActivityLevel = data->current_profile_setting.sclk_activity;
graphic_level->CcPwrDynRm = 0;
graphic_level->CcPwrDynRm1 = 0; /* this level can be used if activity is high enough.*/
graphic_level->EnabledForActivity = 0; /* this level can be used for throttling.*/
graphic_level->EnabledForThrottle = 1;
graphic_level->UpHyst = data->current_profile_setting.sclk_up_hyst;
graphic_level->DownHyst = data->current_profile_setting.sclk_down_hyst;
graphic_level->VoltageDownHyst = 0;
graphic_level->PowerThrottle = 0;
if (phm_cap_enabled(hwmgr->platform_descriptor.platformCaps,
PHM_PlatformCaps_SclkDeepSleep))
graphic_level->DeepSleepDivId =
smu7_get_sleep_divider_id_from_clock(engine_clock,
data->display_timing.min_clock_in_sr);
/* Default to slow, highest DPM level will be set to PPSMC_DISPLAY_WATERMARK_LOW later.*/
graphic_level->DisplayWatermark = PPSMC_DISPLAY_WATERMARK_LOW;
for (i = 0; i < dpm_table->sclk_table.count; i++) {
result = iceland_populate_single_graphic_level(hwmgr,
dpm_table->sclk_table.dpm_levels[i].value,
&(smu_data->smc_state_table.GraphicsLevel[i])); if (result != 0) return result;
/* Making sure only DPM level 0-1 have Deep Sleep Div ID populated. */ if (i > 1)
smu_data->smc_state_table.GraphicsLevel[i].DeepSleepDivId = 0;
}
/* Only enable level 0 for now. */
smu_data->smc_state_table.GraphicsLevel[0].EnabledForActivity = 1;
/* set highest level watermark to high */ if (dpm_table->sclk_table.count > 1)
smu_data->smc_state_table.GraphicsLevel[dpm_table->sclk_table.count-1].DisplayWatermark =
PPSMC_DISPLAY_WATERMARK_HIGH;
/* set pcieDpmLevel to highest_pcie_level_enabled*/ for (i = 2; i < dpm_table->sclk_table.count; i++) {
smu_data->smc_state_table.GraphicsLevel[i].pcieDpmLevel = highest_pcie_level_enabled;
}
/* set pcieDpmLevel to lowest_pcie_level_enabled*/
smu_data->smc_state_table.GraphicsLevel[0].pcieDpmLevel = lowest_pcie_level_enabled;
/* set pcieDpmLevel to mid_pcie_level_enabled*/
smu_data->smc_state_table.GraphicsLevel[1].pcieDpmLevel = mid_pcie_level_enabled;
/* level count will send to smc once at init smc table and never change*/
result = smu7_copy_bytes_to_smc(hwmgr, level_array_adress,
(uint8_t *)levels, (uint32_t)level_array_size,
SMC_RAM_END);
if (hwmgr->dyn_state.vddc_dependency_on_mclk != NULL) {
result = iceland_get_dependency_volt_by_clk(hwmgr,
hwmgr->dyn_state.vddc_dependency_on_mclk, memory_clock, &memory_level->MinVddc);
PP_ASSERT_WITH_CODE((0 == result), "can not find MinVddc voltage value from memory VDDC voltage dependency table", return result);
}
if (data->vddci_control == SMU7_VOLTAGE_CONTROL_NONE) {
memory_level->MinVddci = memory_level->MinVddc;
} elseif (NULL != hwmgr->dyn_state.vddci_dependency_on_mclk) {
result = iceland_get_dependency_volt_by_clk(hwmgr,
hwmgr->dyn_state.vddci_dependency_on_mclk,
memory_clock,
&memory_level->MinVddci);
PP_ASSERT_WITH_CODE((0 == result), "can not find MinVddci voltage value from memory VDDCI voltage dependency table", return result);
}
memory_level->MinVddcPhases = 1;
if (data->vddc_phase_shed_control) {
iceland_populate_phase_value_based_on_mclk(hwmgr, hwmgr->dyn_state.vddc_phase_shed_limits_table,
memory_clock, &memory_level->MinVddcPhases);
}
/* decide EDC mode and memory clock ratio*/ if (data->is_memory_gddr5) {
memory_level->StrobeRatio = iceland_get_mclk_frequency_ratio(memory_clock,
memory_level->StrobeEnable);
for (i = 0; i < dpm_table->mclk_table.count; i++) {
PP_ASSERT_WITH_CODE((0 != dpm_table->mclk_table.dpm_levels[i].value), "can not populate memory level as memory clock is zero", return -EINVAL);
result = iceland_populate_single_memory_level(hwmgr, dpm_table->mclk_table.dpm_levels[i].value,
&(smu_data->smc_state_table.MemoryLevel[i])); if (0 != result) { return result;
}
}
/* Only enable level 0 for now.*/
smu_data->smc_state_table.MemoryLevel[0].EnabledForActivity = 1;
/* * in order to prevent MC activity from stutter mode to push DPM up. * the UVD change complements this by putting the MCLK in a higher state * by default such that we are not effected by up threshold or and MCLK DPM latency.
*/
smu_data->smc_state_table.MemoryLevel[0].ActivityLevel = 0x1F;
CONVERT_FROM_HOST_TO_SMC_US(smu_data->smc_state_table.MemoryLevel[0].ActivityLevel);
smu_data->smc_state_table.MemoryDpmLevelCount = (uint8_t)dpm_table->mclk_table.count;
data->dpm_level_enable_mask.mclk_dpm_enable_mask = phm_get_dpm_level_enable_mask_value(&dpm_table->mclk_table); /* set highest level watermark to high*/
smu_data->smc_state_table.MemoryLevel[dpm_table->mclk_table.count-1].DisplayWatermark = PPSMC_DISPLAY_WATERMARK_HIGH;
/* level count will send to smc once at init smc table and never change*/
result = smu7_copy_bytes_to_smc(hwmgr,
level_array_adress, (uint8_t *)levels, (uint32_t)level_array_size,
SMC_RAM_END);
if (SMU7_VOLTAGE_CONTROL_NONE != data->mvdd_control) { /* find mvdd value which clock is more than request */ for (i = 0; i < hwmgr->dyn_state.mvdd_dependency_on_mclk->count; i++) { if (mclk <= hwmgr->dyn_state.mvdd_dependency_on_mclk->entries[i].clk) { /* Always round to higher voltage. */
voltage->Voltage = data->mvdd_voltage_table.entries[i].value; break;
}
}
PP_ASSERT_WITH_CODE(i < hwmgr->dyn_state.mvdd_dependency_on_mclk->count, "MVDD Voltage is outside the supported range.", return -EINVAL);
table->ACPILevel.MinVddcPhases = vddc_phase_shed_control ? 0 : 1; /* assign zero for now*/
table->ACPILevel.SclkFrequency = atomctrl_get_reference_clock(hwmgr);
/* get the engine clock dividers for this clock value*/
result = atomctrl_get_engine_pll_dividers_vi(hwmgr,
table->ACPILevel.SclkFrequency, ÷rs);
/* For various features to be enabled/disabled while this level is active.*/
CONVERT_FROM_HOST_TO_SMC_UL(table->ACPILevel.Flags); /* SCLK frequency in units of 10KHz*/
CONVERT_FROM_HOST_TO_SMC_UL(table->ACPILevel.SclkFrequency);
CONVERT_FROM_HOST_TO_SMC_UL(table->ACPILevel.CgSpllFuncCntl);
CONVERT_FROM_HOST_TO_SMC_UL(table->ACPILevel.CgSpllFuncCntl2);
CONVERT_FROM_HOST_TO_SMC_UL(table->ACPILevel.CgSpllFuncCntl3);
CONVERT_FROM_HOST_TO_SMC_UL(table->ACPILevel.CgSpllFuncCntl4);
CONVERT_FROM_HOST_TO_SMC_UL(table->ACPILevel.SpllSpreadSpectrum);
CONVERT_FROM_HOST_TO_SMC_UL(table->ACPILevel.SpllSpreadSpectrum2);
CONVERT_FROM_HOST_TO_SMC_UL(table->ACPILevel.CcPwrDynRm);
CONVERT_FROM_HOST_TO_SMC_UL(table->ACPILevel.CcPwrDynRm1);
for (i = 0; i < data->dpm_table.sclk_table.count; i++) { for (j = 0; j < data->dpm_table.mclk_table.count; j++) {
result = iceland_populate_memory_timing_parameters
(hwmgr, data->dpm_table.sclk_table.dpm_levels[i].value,
data->dpm_table.mclk_table.dpm_levels[j].value,
&arb_regs.entries[i][j]);
if (0 != result) { break;
}
}
}
if (0 == result) {
result = smu7_copy_bytes_to_smc(
hwmgr,
smu_data->smu7_data.arb_table_start,
(uint8_t *)&arb_regs, sizeof(SMU71_Discrete_MCArbDramTimingTable),
SMC_RAM_END
);
}
/* find boot level from dpm table*/
result = phm_find_boot_level(&(data->dpm_table.sclk_table),
data->vbios_boot_state.sclk_bootup_value,
(uint32_t *)&(smu_data->smc_state_table.GraphicsBootLevel));
if (0 != result) {
smu_data->smc_state_table.GraphicsBootLevel = 0;
pr_err("VBIOS did not find boot engine clock value in dependency table. Using Graphics DPM level 0!\n");
result = 0;
}
result = phm_find_boot_level(&(data->dpm_table.mclk_table),
data->vbios_boot_state.mclk_bootup_value,
(uint32_t *)&(smu_data->smc_state_table.MemoryBootLevel));
if (0 != result) {
smu_data->smc_state_table.MemoryBootLevel = 0;
pr_err("VBIOS did not find boot engine clock value in dependency table. Using Memory DPM level 0!\n");
result = 0;
}
staticint iceland_convert_mc_reg_table_to_smc(struct pp_hwmgr *hwmgr,
SMU71_Discrete_MCRegisters *mc_regs)
{ int result = 0; struct smu7_hwmgr *data = (struct smu7_hwmgr *)(hwmgr->backend); int res;
uint32_t i;
for (i = 0; i < data->dpm_table.mclk_table.count; i++) {
res = iceland_convert_mc_reg_table_entry_to_smc(
hwmgr,
data->dpm_table.mclk_table.dpm_levels[i].value,
&mc_regs->data[i]
);
memset(&smu_data->mc_regs, 0x00, sizeof(SMU71_Discrete_MCRegisters));
result = iceland_populate_mc_reg_address(hwmgr, &(smu_data->mc_regs));
PP_ASSERT_WITH_CODE(0 == result, "Failed to initialize MCRegTable for the MC register addresses!", return result;);
result = iceland_convert_mc_reg_table_to_smc(hwmgr, &smu_data->mc_regs);
PP_ASSERT_WITH_CODE(0 == result, "Failed to initialize MCRegTable for driver state!", return result;);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.