diff options
| author | Dave Airlie <airlied@redhat.com> | 2015-06-05 11:14:18 +1000 | 
|---|---|---|
| committer | Dave Airlie <airlied@redhat.com> | 2015-06-05 11:14:18 +1000 | 
| commit | a93fe8f8b444499119261914b8870d7f5ac173f8 (patch) | |
| tree | be15a66542177a9d863fd61c9e46deaece1134f7 /drivers/gpu/drm/amd/amdgpu/kv_dpm.c | |
| parent | ef1316961048988c6d044a298b5842c8288cc35b (diff) | |
| parent | 3ccec53c294cbec2af44b6b24f70349637c45428 (diff) | |
Merge branch 'drm-next-4.2-amdgpu' of git://people.freedesktop.org/~agd5f/linux into drm-next
This is the big pull request for amdgpu, the new driver for VI+ AMD
asics.  I currently supports Tonga, Iceland, and Carrizo and also
contains a Kconfig option to build support for CI parts for testing.
All major functionality is supported (displays, gfx, compute, dma,
video decode/encode, etc.).  Power management is working on Carrizo,
but is still being worked on for Tonga and Iceland.
* 'drm-next-4.2-amdgpu' of git://people.freedesktop.org/~agd5f/linux: (106 commits)
  drm/amdgpu: only support IBs in the buffer list (v2)
  drm/amdgpu: add vram_type and vram_bit_width for interface query (v2)
  drm/amdgpu: add ib_size/start_alignment interface query
  drm/amdgpu: add me/ce/pfp_feature_version interface query
  drm/amdgpu add ce_ram_size for interface query
  drm/amdgpu add max_memory_clock for interface query (v2)
  drm/amdgpu: add hdp flush for gfx8 compute ring
  drm/amdgpu: fix no hdp flush for compute ring
  drm/amdgpu: add HEVC/H.265 UVD support
  drm/amdgpu: stop loading firmware with pm.mutex locked
  drm/amdgpu: remove mclk_lock
  drm/amdgpu: fix description of vm_size module parameter (v2)
  drm/amdgpu: remove all sh mem register modification in vm flush
  drm/amdgpu: rename GEM_OP_SET_INITIAL_DOMAIN -> GEM_OP_SET_PLACEMENT
  drm/amdgpu: fence should be added to shared slot
  drm/amdgpu: sync fence of clear_invalids (v2)
  drm/amdgpu: max_pde_used usage should be under protect
  drm/amdgpu: fix bug of vm_bo_map (v2)
  drm/amdgpu: implement the allocation range (v3)
  drm/amdgpu: rename amdgpu_ip_funcs to amd_ip_funcs (v2)
  ...
Diffstat (limited to 'drivers/gpu/drm/amd/amdgpu/kv_dpm.c')
| -rw-r--r-- | drivers/gpu/drm/amd/amdgpu/kv_dpm.c | 3343 | 
1 files changed, 3343 insertions, 0 deletions
diff --git a/drivers/gpu/drm/amd/amdgpu/kv_dpm.c b/drivers/gpu/drm/amd/amdgpu/kv_dpm.c new file mode 100644 index 000000000000..94ec04a9c4d5 --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/kv_dpm.c @@ -0,0 +1,3343 @@ +/* + * Copyright 2013 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. + * + */ + +#include "drmP.h" +#include "amdgpu.h" +#include "amdgpu_pm.h" +#include "cikd.h" +#include "atom.h" +#include "amdgpu_atombios.h" +#include "amdgpu_dpm.h" +#include "kv_dpm.h" +#include "gfx_v7_0.h" +#include <linux/seq_file.h> + +#include "smu/smu_7_0_0_d.h" +#include "smu/smu_7_0_0_sh_mask.h" + +#include "gca/gfx_7_2_d.h" +#include "gca/gfx_7_2_sh_mask.h" + +#define KV_MAX_DEEPSLEEP_DIVIDER_ID     5 +#define KV_MINIMUM_ENGINE_CLOCK         800 +#define SMC_RAM_END                     0x40000 + +static void kv_dpm_set_dpm_funcs(struct amdgpu_device *adev); +static void kv_dpm_set_irq_funcs(struct amdgpu_device *adev); +static int kv_enable_nb_dpm(struct amdgpu_device *adev, +			    bool enable); +static void kv_init_graphics_levels(struct amdgpu_device *adev); +static int kv_calculate_ds_divider(struct amdgpu_device *adev); +static int kv_calculate_nbps_level_settings(struct amdgpu_device *adev); +static int kv_calculate_dpm_settings(struct amdgpu_device *adev); +static void kv_enable_new_levels(struct amdgpu_device *adev); +static void kv_program_nbps_index_settings(struct amdgpu_device *adev, +					   struct amdgpu_ps *new_rps); +static int kv_set_enabled_level(struct amdgpu_device *adev, u32 level); +static int kv_set_enabled_levels(struct amdgpu_device *adev); +static int kv_force_dpm_highest(struct amdgpu_device *adev); +static int kv_force_dpm_lowest(struct amdgpu_device *adev); +static void kv_apply_state_adjust_rules(struct amdgpu_device *adev, +					struct amdgpu_ps *new_rps, +					struct amdgpu_ps *old_rps); +static int kv_set_thermal_temperature_range(struct amdgpu_device *adev, +					    int min_temp, int max_temp); +static int kv_init_fps_limits(struct amdgpu_device *adev); + +static void kv_dpm_powergate_uvd(struct amdgpu_device *adev, bool gate); +static void kv_dpm_powergate_vce(struct amdgpu_device *adev, bool gate); +static void kv_dpm_powergate_samu(struct amdgpu_device *adev, bool gate); +static void kv_dpm_powergate_acp(struct amdgpu_device *adev, bool gate); + + +static u32 kv_convert_vid2_to_vid7(struct amdgpu_device *adev, +				   struct sumo_vid_mapping_table *vid_mapping_table, +				   u32 vid_2bit) +{ +	struct amdgpu_clock_voltage_dependency_table *vddc_sclk_table = +		&adev->pm.dpm.dyn_state.vddc_dependency_on_sclk; +	u32 i; + +	if (vddc_sclk_table && vddc_sclk_table->count) { +		if (vid_2bit < vddc_sclk_table->count) +			return vddc_sclk_table->entries[vid_2bit].v; +		else +			return vddc_sclk_table->entries[vddc_sclk_table->count - 1].v; +	} else { +		for (i = 0; i < vid_mapping_table->num_entries; i++) { +			if (vid_mapping_table->entries[i].vid_2bit == vid_2bit) +				return vid_mapping_table->entries[i].vid_7bit; +		} +		return vid_mapping_table->entries[vid_mapping_table->num_entries - 1].vid_7bit; +	} +} + +static u32 kv_convert_vid7_to_vid2(struct amdgpu_device *adev, +				   struct sumo_vid_mapping_table *vid_mapping_table, +				   u32 vid_7bit) +{ +	struct amdgpu_clock_voltage_dependency_table *vddc_sclk_table = +		&adev->pm.dpm.dyn_state.vddc_dependency_on_sclk; +	u32 i; + +	if (vddc_sclk_table && vddc_sclk_table->count) { +		for (i = 0; i < vddc_sclk_table->count; i++) { +			if (vddc_sclk_table->entries[i].v == vid_7bit) +				return i; +		} +		return vddc_sclk_table->count - 1; +	} else { +		for (i = 0; i < vid_mapping_table->num_entries; i++) { +			if (vid_mapping_table->entries[i].vid_7bit == vid_7bit) +				return vid_mapping_table->entries[i].vid_2bit; +		} + +		return vid_mapping_table->entries[vid_mapping_table->num_entries - 1].vid_2bit; +	} +} + +static void sumo_take_smu_control(struct amdgpu_device *adev, bool enable) +{ +/* This bit selects who handles display phy powergating. + * Clear the bit to let atom handle it. + * Set it to let the driver handle it. + * For now we just let atom handle it. + */ +#if 0 +	u32 v = RREG32(mmDOUT_SCRATCH3); + +	if (enable) +		v |= 0x4; +	else +		v &= 0xFFFFFFFB; + +	WREG32(mmDOUT_SCRATCH3, v); +#endif +} + +static u32 sumo_get_sleep_divider_from_id(u32 id) +{ +	return 1 << id; +} + +static void sumo_construct_sclk_voltage_mapping_table(struct amdgpu_device *adev, +						      struct sumo_sclk_voltage_mapping_table *sclk_voltage_mapping_table, +						      ATOM_AVAILABLE_SCLK_LIST *table) +{ +	u32 i; +	u32 n = 0; +	u32 prev_sclk = 0; + +	for (i = 0; i < SUMO_MAX_HARDWARE_POWERLEVELS; i++) { +		if (table[i].ulSupportedSCLK > prev_sclk) { +			sclk_voltage_mapping_table->entries[n].sclk_frequency = +				table[i].ulSupportedSCLK; +			sclk_voltage_mapping_table->entries[n].vid_2bit = +				table[i].usVoltageIndex; +			prev_sclk = table[i].ulSupportedSCLK; +			n++; +		} +	} + +	sclk_voltage_mapping_table->num_max_dpm_entries = n; +} + +static void sumo_construct_vid_mapping_table(struct amdgpu_device *adev, +					     struct sumo_vid_mapping_table *vid_mapping_table, +					     ATOM_AVAILABLE_SCLK_LIST *table) +{ +	u32 i, j; + +	for (i = 0; i < SUMO_MAX_HARDWARE_POWERLEVELS; i++) { +		if (table[i].ulSupportedSCLK != 0) { +			vid_mapping_table->entries[table[i].usVoltageIndex].vid_7bit = +				table[i].usVoltageID; +			vid_mapping_table->entries[table[i].usVoltageIndex].vid_2bit = +				table[i].usVoltageIndex; +		} +	} + +	for (i = 0; i < SUMO_MAX_NUMBER_VOLTAGES; i++) { +		if (vid_mapping_table->entries[i].vid_7bit == 0) { +			for (j = i + 1; j < SUMO_MAX_NUMBER_VOLTAGES; j++) { +				if (vid_mapping_table->entries[j].vid_7bit != 0) { +					vid_mapping_table->entries[i] = +						vid_mapping_table->entries[j]; +					vid_mapping_table->entries[j].vid_7bit = 0; +					break; +				} +			} + +			if (j == SUMO_MAX_NUMBER_VOLTAGES) +				break; +		} +	} + +	vid_mapping_table->num_entries = i; +} + +static const struct kv_lcac_config_values sx_local_cac_cfg_kv[] = +{ +	{  0,       4,        1    }, +	{  1,       4,        1    }, +	{  2,       5,        1    }, +	{  3,       4,        2    }, +	{  4,       1,        1    }, +	{  5,       5,        2    }, +	{  6,       6,        1    }, +	{  7,       9,        2    }, +	{ 0xffffffff } +}; + +static const struct kv_lcac_config_values mc0_local_cac_cfg_kv[] = +{ +	{  0,       4,        1    }, +	{ 0xffffffff } +}; + +static const struct kv_lcac_config_values mc1_local_cac_cfg_kv[] = +{ +	{  0,       4,        1    }, +	{ 0xffffffff } +}; + +static const struct kv_lcac_config_values mc2_local_cac_cfg_kv[] = +{ +	{  0,       4,        1    }, +	{ 0xffffffff } +}; + +static const struct kv_lcac_config_values mc3_local_cac_cfg_kv[] = +{ +	{  0,       4,        1    }, +	{ 0xffffffff } +}; + +static const struct kv_lcac_config_values cpl_local_cac_cfg_kv[] = +{ +	{  0,       4,        1    }, +	{  1,       4,        1    }, +	{  2,       5,        1    }, +	{  3,       4,        1    }, +	{  4,       1,        1    }, +	{  5,       5,        1    }, +	{  6,       6,        1    }, +	{  7,       9,        1    }, +	{  8,       4,        1    }, +	{  9,       2,        1    }, +	{  10,      3,        1    }, +	{  11,      6,        1    }, +	{  12,      8,        2    }, +	{  13,      1,        1    }, +	{  14,      2,        1    }, +	{  15,      3,        1    }, +	{  16,      1,        1    }, +	{  17,      4,        1    }, +	{  18,      3,        1    }, +	{  19,      1,        1    }, +	{  20,      8,        1    }, +	{  21,      5,        1    }, +	{  22,      1,        1    }, +	{  23,      1,        1    }, +	{  24,      4,        1    }, +	{  27,      6,        1    }, +	{  28,      1,        1    }, +	{ 0xffffffff } +}; + +static const struct kv_lcac_config_reg sx0_cac_config_reg[] = +{ +	{ 0xc0400d00, 0x003e0000, 17, 0x3fc00000, 22, 0x0001fffe, 1, 0x00000001, 0 } +}; + +static const struct kv_lcac_config_reg mc0_cac_config_reg[] = +{ +	{ 0xc0400d30, 0x003e0000, 17, 0x3fc00000, 22, 0x0001fffe, 1, 0x00000001, 0 } +}; + +static const struct kv_lcac_config_reg mc1_cac_config_reg[] = +{ +	{ 0xc0400d3c, 0x003e0000, 17, 0x3fc00000, 22, 0x0001fffe, 1, 0x00000001, 0 } +}; + +static const struct kv_lcac_config_reg mc2_cac_config_reg[] = +{ +	{ 0xc0400d48, 0x003e0000, 17, 0x3fc00000, 22, 0x0001fffe, 1, 0x00000001, 0 } +}; + +static const struct kv_lcac_config_reg mc3_cac_config_reg[] = +{ +	{ 0xc0400d54, 0x003e0000, 17, 0x3fc00000, 22, 0x0001fffe, 1, 0x00000001, 0 } +}; + +static const struct kv_lcac_config_reg cpl_cac_config_reg[] = +{ +	{ 0xc0400d80, 0x003e0000, 17, 0x3fc00000, 22, 0x0001fffe, 1, 0x00000001, 0 } +}; + +static const struct kv_pt_config_reg didt_config_kv[] = +{ +	{ 0x10, 0x000000ff, 0, 0x0, KV_CONFIGREG_DIDT_IND }, +	{ 0x10, 0x0000ff00, 8, 0x0, KV_CONFIGREG_DIDT_IND }, +	{ 0x10, 0x00ff0000, 16, 0x0, KV_CONFIGREG_DIDT_IND }, +	{ 0x10, 0xff000000, 24, 0x0, KV_CONFIGREG_DIDT_IND }, +	{ 0x11, 0x000000ff, 0, 0x0, KV_CONFIGREG_DIDT_IND }, +	{ 0x11, 0x0000ff00, 8, 0x0, KV_CONFIGREG_DIDT_IND }, +	{ 0x11, 0x00ff0000, 16, 0x0, KV_CONFIGREG_DIDT_IND }, +	{ 0x11, 0xff000000, 24, 0x0, KV_CONFIGREG_DIDT_IND }, +	{ 0x12, 0x000000ff, 0, 0x0, KV_CONFIGREG_DIDT_IND }, +	{ 0x12, 0x0000ff00, 8, 0x0, KV_CONFIGREG_DIDT_IND }, +	{ 0x12, 0x00ff0000, 16, 0x0, KV_CONFIGREG_DIDT_IND }, +	{ 0x12, 0xff000000, 24, 0x0, KV_CONFIGREG_DIDT_IND }, +	{ 0x2, 0x00003fff, 0, 0x4, KV_CONFIGREG_DIDT_IND }, +	{ 0x2, 0x03ff0000, 16, 0x80, KV_CONFIGREG_DIDT_IND }, +	{ 0x2, 0x78000000, 27, 0x3, KV_CONFIGREG_DIDT_IND }, +	{ 0x1, 0x0000ffff, 0, 0x3FFF, KV_CONFIGREG_DIDT_IND }, +	{ 0x1, 0xffff0000, 16, 0x3FFF, KV_CONFIGREG_DIDT_IND }, +	{ 0x0, 0x00000001, 0, 0x0, KV_CONFIGREG_DIDT_IND }, +	{ 0x30, 0x000000ff, 0, 0x0, KV_CONFIGREG_DIDT_IND }, +	{ 0x30, 0x0000ff00, 8, 0x0, KV_CONFIGREG_DIDT_IND }, +	{ 0x30, 0x00ff0000, 16, 0x0, KV_CONFIGREG_DIDT_IND }, +	{ 0x30, 0xff000000, 24, 0x0, KV_CONFIGREG_DIDT_IND }, +	{ 0x31, 0x000000ff, 0, 0x0, KV_CONFIGREG_DIDT_IND }, +	{ 0x31, 0x0000ff00, 8, 0x0, KV_CONFIGREG_DIDT_IND }, +	{ 0x31, 0x00ff0000, 16, 0x0, KV_CONFIGREG_DIDT_IND }, +	{ 0x31, 0xff000000, 24, 0x0, KV_CONFIGREG_DIDT_IND }, +	{ 0x32, 0x000000ff, 0, 0x0, KV_CONFIGREG_DIDT_IND }, +	{ 0x32, 0x0000ff00, 8, 0x0, KV_CONFIGREG_DIDT_IND }, +	{ 0x32, 0x00ff0000, 16, 0x0, KV_CONFIGREG_DIDT_IND }, +	{ 0x32, 0xff000000, 24, 0x0, KV_CONFIGREG_DIDT_IND }, +	{ 0x22, 0x00003fff, 0, 0x4, KV_CONFIGREG_DIDT_IND }, +	{ 0x22, 0x03ff0000, 16, 0x80, KV_CONFIGREG_DIDT_IND }, +	{ 0x22, 0x78000000, 27, 0x3, KV_CONFIGREG_DIDT_IND }, +	{ 0x21, 0x0000ffff, 0, 0x3FFF, KV_CONFIGREG_DIDT_IND }, +	{ 0x21, 0xffff0000, 16, 0x3FFF, KV_CONFIGREG_DIDT_IND }, +	{ 0x20, 0x00000001, 0, 0x0, KV_CONFIGREG_DIDT_IND }, +	{ 0x50, 0x000000ff, 0, 0x0, KV_CONFIGREG_DIDT_IND }, +	{ 0x50, 0x0000ff00, 8, 0x0, KV_CONFIGREG_DIDT_IND }, +	{ 0x50, 0x00ff0000, 16, 0x0, KV_CONFIGREG_DIDT_IND }, +	{ 0x50, 0xff000000, 24, 0x0, KV_CONFIGREG_DIDT_IND }, +	{ 0x51, 0x000000ff, 0, 0x0, KV_CONFIGREG_DIDT_IND }, +	{ 0x51, 0x0000ff00, 8, 0x0, KV_CONFIGREG_DIDT_IND }, +	{ 0x51, 0x00ff0000, 16, 0x0, KV_CONFIGREG_DIDT_IND }, +	{ 0x51, 0xff000000, 24, 0x0, KV_CONFIGREG_DIDT_IND }, +	{ 0x52, 0x000000ff, 0, 0x0, KV_CONFIGREG_DIDT_IND }, +	{ 0x52, 0x0000ff00, 8, 0x0, KV_CONFIGREG_DIDT_IND }, +	{ 0x52, 0x00ff0000, 16, 0x0, KV_CONFIGREG_DIDT_IND }, +	{ 0x52, 0xff000000, 24, 0x0, KV_CONFIGREG_DIDT_IND }, +	{ 0x42, 0x00003fff, 0, 0x4, KV_CONFIGREG_DIDT_IND }, +	{ 0x42, 0x03ff0000, 16, 0x80, KV_CONFIGREG_DIDT_IND }, +	{ 0x42, 0x78000000, 27, 0x3, KV_CONFIGREG_DIDT_IND }, +	{ 0x41, 0x0000ffff, 0, 0x3FFF, KV_CONFIGREG_DIDT_IND }, +	{ 0x41, 0xffff0000, 16, 0x3FFF, KV_CONFIGREG_DIDT_IND }, +	{ 0x40, 0x00000001, 0, 0x0, KV_CONFIGREG_DIDT_IND }, +	{ 0x70, 0x000000ff, 0, 0x0, KV_CONFIGREG_DIDT_IND }, +	{ 0x70, 0x0000ff00, 8, 0x0, KV_CONFIGREG_DIDT_IND }, +	{ 0x70, 0x00ff0000, 16, 0x0, KV_CONFIGREG_DIDT_IND }, +	{ 0x70, 0xff000000, 24, 0x0, KV_CONFIGREG_DIDT_IND }, +	{ 0x71, 0x000000ff, 0, 0x0, KV_CONFIGREG_DIDT_IND }, +	{ 0x71, 0x0000ff00, 8, 0x0, KV_CONFIGREG_DIDT_IND }, +	{ 0x71, 0x00ff0000, 16, 0x0, KV_CONFIGREG_DIDT_IND }, +	{ 0x71, 0xff000000, 24, 0x0, KV_CONFIGREG_DIDT_IND }, +	{ 0x72, 0x000000ff, 0, 0x0, KV_CONFIGREG_DIDT_IND }, +	{ 0x72, 0x0000ff00, 8, 0x0, KV_CONFIGREG_DIDT_IND }, +	{ 0x72, 0x00ff0000, 16, 0x0, KV_CONFIGREG_DIDT_IND }, +	{ 0x72, 0xff000000, 24, 0x0, KV_CONFIGREG_DIDT_IND }, +	{ 0x62, 0x00003fff, 0, 0x4, KV_CONFIGREG_DIDT_IND }, +	{ 0x62, 0x03ff0000, 16, 0x80, KV_CONFIGREG_DIDT_IND }, +	{ 0x62, 0x78000000, 27, 0x3, KV_CONFIGREG_DIDT_IND }, +	{ 0x61, 0x0000ffff, 0, 0x3FFF, KV_CONFIGREG_DIDT_IND }, +	{ 0x61, 0xffff0000, 16, 0x3FFF, KV_CONFIGREG_DIDT_IND }, +	{ 0x60, 0x00000001, 0, 0x0, KV_CONFIGREG_DIDT_IND }, +	{ 0xFFFFFFFF } +}; + +static struct kv_ps *kv_get_ps(struct amdgpu_ps *rps) +{ +	struct kv_ps *ps = rps->ps_priv; + +	return ps; +} + +static struct kv_power_info *kv_get_pi(struct amdgpu_device *adev) +{ +	struct kv_power_info *pi = adev->pm.dpm.priv; + +	return pi; +} + +#if 0 +static void kv_program_local_cac_table(struct amdgpu_device *adev, +				       const struct kv_lcac_config_values *local_cac_table, +				       const struct kv_lcac_config_reg *local_cac_reg) +{ +	u32 i, count, data; +	const struct kv_lcac_config_values *values = local_cac_table; + +	while (values->block_id != 0xffffffff) { +		count = values->signal_id; +		for (i = 0; i < count; i++) { +			data = ((values->block_id << local_cac_reg->block_shift) & +				local_cac_reg->block_mask); +			data |= ((i << local_cac_reg->signal_shift) & +				 local_cac_reg->signal_mask); +			data |= ((values->t << local_cac_reg->t_shift) & +				 local_cac_reg->t_mask); +			data |= ((1 << local_cac_reg->enable_shift) & +				 local_cac_reg->enable_mask); +			WREG32_SMC(local_cac_reg->cntl, data); +		} +		values++; +	} +} +#endif + +static int kv_program_pt_config_registers(struct amdgpu_device *adev, +					  const struct kv_pt_config_reg *cac_config_regs) +{ +	const struct kv_pt_config_reg *config_regs = cac_config_regs; +	u32 data; +	u32 cache = 0; + +	if (config_regs == NULL) +		return -EINVAL; + +	while (config_regs->offset != 0xFFFFFFFF) { +		if (config_regs->type == KV_CONFIGREG_CACHE) { +			cache |= ((config_regs->value << config_regs->shift) & config_regs->mask); +		} else { +			switch (config_regs->type) { +			case KV_CONFIGREG_SMC_IND: +				data = RREG32_SMC(config_regs->offset); +				break; +			case KV_CONFIGREG_DIDT_IND: +				data = RREG32_DIDT(config_regs->offset); +				break; +			default: +				data = RREG32(config_regs->offset); +				break; +			} + +			data &= ~config_regs->mask; +			data |= ((config_regs->value << config_regs->shift) & config_regs->mask); +			data |= cache; +			cache = 0; + +			switch (config_regs->type) { +			case KV_CONFIGREG_SMC_IND: +				WREG32_SMC(config_regs->offset, data); +				break; +			case KV_CONFIGREG_DIDT_IND: +				WREG32_DIDT(config_regs->offset, data); +				break; +			default: +				WREG32(config_regs->offset, data); +				break; +			} +		} +		config_regs++; +	} + +	return 0; +} + +static void kv_do_enable_didt(struct amdgpu_device *adev, bool enable) +{ +	struct kv_power_info *pi = kv_get_pi(adev); +	u32 data; + +	if (pi->caps_sq_ramping) { +		data = RREG32_DIDT(ixDIDT_SQ_CTRL0); +		if (enable) +			data |= DIDT_SQ_CTRL0__DIDT_CTRL_EN_MASK; +		else +			data &= ~DIDT_SQ_CTRL0__DIDT_CTRL_EN_MASK; +		WREG32_DIDT(ixDIDT_SQ_CTRL0, data); +	} + +	if (pi->caps_db_ramping) { +		data = RREG32_DIDT(ixDIDT_DB_CTRL0); +		if (enable) +			data |= DIDT_DB_CTRL0__DIDT_CTRL_EN_MASK; +		else +			data &= ~DIDT_DB_CTRL0__DIDT_CTRL_EN_MASK; +		WREG32_DIDT(ixDIDT_DB_CTRL0, data); +	} + +	if (pi->caps_td_ramping) { +		data = RREG32_DIDT(ixDIDT_TD_CTRL0); +		if (enable) +			data |= DIDT_TD_CTRL0__DIDT_CTRL_EN_MASK; +		else +			data &= ~DIDT_TD_CTRL0__DIDT_CTRL_EN_MASK; +		WREG32_DIDT(ixDIDT_TD_CTRL0, data); +	} + +	if (pi->caps_tcp_ramping) { +		data = RREG32_DIDT(ixDIDT_TCP_CTRL0); +		if (enable) +			data |= DIDT_TCP_CTRL0__DIDT_CTRL_EN_MASK; +		else +			data &= ~DIDT_TCP_CTRL0__DIDT_CTRL_EN_MASK; +		WREG32_DIDT(ixDIDT_TCP_CTRL0, data); +	} +} + +static int kv_enable_didt(struct amdgpu_device *adev, bool enable) +{ +	struct kv_power_info *pi = kv_get_pi(adev); +	int ret; + +	if (pi->caps_sq_ramping || +	    pi->caps_db_ramping || +	    pi->caps_td_ramping || +	    pi->caps_tcp_ramping) { +		gfx_v7_0_enter_rlc_safe_mode(adev); + +		if (enable) { +			ret = kv_program_pt_config_registers(adev, didt_config_kv); +			if (ret) { +				gfx_v7_0_exit_rlc_safe_mode(adev); +				return ret; +			} +		} + +		kv_do_enable_didt(adev, enable); + +		gfx_v7_0_exit_rlc_safe_mode(adev); +	} + +	return 0; +} + +#if 0 +static void kv_initialize_hardware_cac_manager(struct amdgpu_device *adev) +{ +	struct kv_power_info *pi = kv_get_pi(adev); + +	if (pi->caps_cac) { +		WREG32_SMC(ixLCAC_SX0_OVR_SEL, 0); +		WREG32_SMC(ixLCAC_SX0_OVR_VAL, 0); +		kv_program_local_cac_table(adev, sx_local_cac_cfg_kv, sx0_cac_config_reg); + +		WREG32_SMC(ixLCAC_MC0_OVR_SEL, 0); +		WREG32_SMC(ixLCAC_MC0_OVR_VAL, 0); +		kv_program_local_cac_table(adev, mc0_local_cac_cfg_kv, mc0_cac_config_reg); + +		WREG32_SMC(ixLCAC_MC1_OVR_SEL, 0); +		WREG32_SMC(ixLCAC_MC1_OVR_VAL, 0); +		kv_program_local_cac_table(adev, mc1_local_cac_cfg_kv, mc1_cac_config_reg); + +		WREG32_SMC(ixLCAC_MC2_OVR_SEL, 0); +		WREG32_SMC(ixLCAC_MC2_OVR_VAL, 0); +		kv_program_local_cac_table(adev, mc2_local_cac_cfg_kv, mc2_cac_config_reg); + +		WREG32_SMC(ixLCAC_MC3_OVR_SEL, 0); +		WREG32_SMC(ixLCAC_MC3_OVR_VAL, 0); +		kv_program_local_cac_table(adev, mc3_local_cac_cfg_kv, mc3_cac_config_reg); + +		WREG32_SMC(ixLCAC_CPL_OVR_SEL, 0); +		WREG32_SMC(ixLCAC_CPL_OVR_VAL, 0); +		kv_program_local_cac_table(adev, cpl_local_cac_cfg_kv, cpl_cac_config_reg); +	} +} +#endif + +static int kv_enable_smc_cac(struct amdgpu_device *adev, bool enable) +{ +	struct kv_power_info *pi = kv_get_pi(adev); +	int ret = 0; + +	if (pi->caps_cac) { +		if (enable) { +			ret = amdgpu_kv_notify_message_to_smu(adev, PPSMC_MSG_EnableCac); +			if (ret) +				pi->cac_enabled = false; +			else +				pi->cac_enabled = true; +		} else if (pi->cac_enabled) { +			amdgpu_kv_notify_message_to_smu(adev, PPSMC_MSG_DisableCac); +			pi->cac_enabled = false; +		} +	} + +	return ret; +} + +static int kv_process_firmware_header(struct amdgpu_device *adev) +{ +	struct kv_power_info *pi = kv_get_pi(adev); +	u32 tmp; +	int ret; + +	ret = amdgpu_kv_read_smc_sram_dword(adev, SMU7_FIRMWARE_HEADER_LOCATION + +				     offsetof(SMU7_Firmware_Header, DpmTable), +				     &tmp, pi->sram_end); + +	if (ret == 0) +		pi->dpm_table_start = tmp; + +	ret = amdgpu_kv_read_smc_sram_dword(adev, SMU7_FIRMWARE_HEADER_LOCATION + +				     offsetof(SMU7_Firmware_Header, SoftRegisters), +				     &tmp, pi->sram_end); + +	if (ret == 0) +		pi->soft_regs_start = tmp; + +	return ret; +} + +static int kv_enable_dpm_voltage_scaling(struct amdgpu_device *adev) +{ +	struct kv_power_info *pi = kv_get_pi(adev); +	int ret; + +	pi->graphics_voltage_change_enable = 1; + +	ret = amdgpu_kv_copy_bytes_to_smc(adev, +				   pi->dpm_table_start + +				   offsetof(SMU7_Fusion_DpmTable, GraphicsVoltageChangeEnable), +				   &pi->graphics_voltage_change_enable, +				   sizeof(u8), pi->sram_end); + +	return ret; +} + +static int kv_set_dpm_interval(struct amdgpu_device *adev) +{ +	struct kv_power_info *pi = kv_get_pi(adev); +	int ret; + +	pi->graphics_interval = 1; + +	ret = amdgpu_kv_copy_bytes_to_smc(adev, +				   pi->dpm_table_start + +				   offsetof(SMU7_Fusion_DpmTable, GraphicsInterval), +				   &pi->graphics_interval, +				   sizeof(u8), pi->sram_end); + +	return ret; +} + +static int kv_set_dpm_boot_state(struct amdgpu_device *adev) +{ +	struct kv_power_info *pi = kv_get_pi(adev); +	int ret; + +	ret = amdgpu_kv_copy_bytes_to_smc(adev, +				   pi->dpm_table_start + +				   offsetof(SMU7_Fusion_DpmTable, GraphicsBootLevel), +				   &pi->graphics_boot_level, +				   sizeof(u8), pi->sram_end); + +	return ret; +} + +static void kv_program_vc(struct amdgpu_device *adev) +{ +	WREG32_SMC(ixCG_FREQ_TRAN_VOTING_0, 0x3FFFC100); +} + +static void kv_clear_vc(struct amdgpu_device *adev) +{ +	WREG32_SMC(ixCG_FREQ_TRAN_VOTING_0, 0); +} + +static int kv_set_divider_value(struct amdgpu_device *adev, +				u32 index, u32 sclk) +{ +	struct kv_power_info *pi = kv_get_pi(adev); +	struct atom_clock_dividers dividers; +	int ret; + +	ret = amdgpu_atombios_get_clock_dividers(adev, COMPUTE_ENGINE_PLL_PARAM, +						 sclk, false, ÷rs); +	if (ret) +		return ret; + +	pi->graphics_level[index].SclkDid = (u8)dividers.post_div; +	pi->graphics_level[index].SclkFrequency = cpu_to_be32(sclk); + +	return 0; +} + +static u16 kv_convert_8bit_index_to_voltage(struct amdgpu_device *adev, +					    u16 voltage) +{ +	return 6200 - (voltage * 25); +} + +static u16 kv_convert_2bit_index_to_voltage(struct amdgpu_device *adev, +					    u32 vid_2bit) +{ +	struct kv_power_info *pi = kv_get_pi(adev); +	u32 vid_8bit = kv_convert_vid2_to_vid7(adev, +					       &pi->sys_info.vid_mapping_table, +					       vid_2bit); + +	return kv_convert_8bit_index_to_voltage(adev, (u16)vid_8bit); +} + + +static int kv_set_vid(struct amdgpu_device *adev, u32 index, u32 vid) +{ +	struct kv_power_info *pi = kv_get_pi(adev); + +	pi->graphics_level[index].VoltageDownH = (u8)pi->voltage_drop_t; +	pi->graphics_level[index].MinVddNb = +		cpu_to_be32(kv_convert_2bit_index_to_voltage(adev, vid)); + +	return 0; +} + +static int kv_set_at(struct amdgpu_device *adev, u32 index, u32 at) +{ +	struct kv_power_info *pi = kv_get_pi(adev); + +	pi->graphics_level[index].AT = cpu_to_be16((u16)at); + +	return 0; +} + +static void kv_dpm_power_level_enable(struct amdgpu_device *adev, +				      u32 index, bool enable) +{ +	struct kv_power_info *pi = kv_get_pi(adev); + +	pi->graphics_level[index].EnabledForActivity = enable ? 1 : 0; +} + +static void kv_start_dpm(struct amdgpu_device *adev) +{ +	u32 tmp = RREG32_SMC(ixGENERAL_PWRMGT); + +	tmp |= GENERAL_PWRMGT__GLOBAL_PWRMGT_EN_MASK; +	WREG32_SMC(ixGENERAL_PWRMGT, tmp); + +	amdgpu_kv_smc_dpm_enable(adev, true); +} + +static void kv_stop_dpm(struct amdgpu_device *adev) +{ +	amdgpu_kv_smc_dpm_enable(adev, false); +} + +static void kv_start_am(struct amdgpu_device *adev) +{ +	u32 sclk_pwrmgt_cntl = RREG32_SMC(ixSCLK_PWRMGT_CNTL); + +	sclk_pwrmgt_cntl &= ~(SCLK_PWRMGT_CNTL__RESET_SCLK_CNT_MASK | +			SCLK_PWRMGT_CNTL__RESET_BUSY_CNT_MASK); +	sclk_pwrmgt_cntl |= SCLK_PWRMGT_CNTL__DYNAMIC_PM_EN_MASK; + +	WREG32_SMC(ixSCLK_PWRMGT_CNTL, sclk_pwrmgt_cntl); +} + +static void kv_reset_am(struct amdgpu_device *adev) +{ +	u32 sclk_pwrmgt_cntl = RREG32_SMC(ixSCLK_PWRMGT_CNTL); + +	sclk_pwrmgt_cntl |= (SCLK_PWRMGT_CNTL__RESET_SCLK_CNT_MASK | +			SCLK_PWRMGT_CNTL__RESET_BUSY_CNT_MASK); + +	WREG32_SMC(ixSCLK_PWRMGT_CNTL, sclk_pwrmgt_cntl); +} + +static int kv_freeze_sclk_dpm(struct amdgpu_device *adev, bool freeze) +{ +	return amdgpu_kv_notify_message_to_smu(adev, freeze ? +					PPSMC_MSG_SCLKDPM_FreezeLevel : PPSMC_MSG_SCLKDPM_UnfreezeLevel); +} + +static int kv_force_lowest_valid(struct amdgpu_device *adev) +{ +	return kv_force_dpm_lowest(adev); +} + +static int kv_unforce_levels(struct amdgpu_device *adev) +{ +	if (adev->asic_type == CHIP_KABINI || adev->asic_type == CHIP_MULLINS) +		return amdgpu_kv_notify_message_to_smu(adev, PPSMC_MSG_NoForcedLevel); +	else +		return kv_set_enabled_levels(adev); +} + +static int kv_update_sclk_t(struct amdgpu_device *adev) +{ +	struct kv_power_info *pi = kv_get_pi(adev); +	u32 low_sclk_interrupt_t = 0; +	int ret = 0; + +	if (pi->caps_sclk_throttle_low_notification) { +		low_sclk_interrupt_t = cpu_to_be32(pi->low_sclk_interrupt_t); + +		ret = amdgpu_kv_copy_bytes_to_smc(adev, +					   pi->dpm_table_start + +					   offsetof(SMU7_Fusion_DpmTable, LowSclkInterruptT), +					   (u8 *)&low_sclk_interrupt_t, +					   sizeof(u32), pi->sram_end); +	} +	return ret; +} + +static int kv_program_bootup_state(struct amdgpu_device *adev) +{ +	struct kv_power_info *pi = kv_get_pi(adev); +	u32 i; +	struct amdgpu_clock_voltage_dependency_table *table = +		&adev->pm.dpm.dyn_state.vddc_dependency_on_sclk; + +	if (table && table->count) { +		for (i = pi->graphics_dpm_level_count - 1; i > 0; i--) { +			if (table->entries[i].clk == pi->boot_pl.sclk) +				break; +		} + +		pi->graphics_boot_level = (u8)i; +		kv_dpm_power_level_enable(adev, i, true); +	} else { +		struct sumo_sclk_voltage_mapping_table *table = +			&pi->sys_info.sclk_voltage_mapping_table; + +		if (table->num_max_dpm_entries == 0) +			return -EINVAL; + +		for (i = pi->graphics_dpm_level_count - 1; i > 0; i--) { +			if (table->entries[i].sclk_frequency == pi->boot_pl.sclk) +				break; +		} + +		pi->graphics_boot_level = (u8)i; +		kv_dpm_power_level_enable(adev, i, true); +	} +	return 0; +} + +static int kv_enable_auto_thermal_throttling(struct amdgpu_device *adev) +{ +	struct kv_power_info *pi = kv_get_pi(adev); +	int ret; + +	pi->graphics_therm_throttle_enable = 1; + +	ret = amdgpu_kv_copy_bytes_to_smc(adev, +				   pi->dpm_table_start + +				   offsetof(SMU7_Fusion_DpmTable, GraphicsThermThrottleEnable), +				   &pi->graphics_therm_throttle_enable, +				   sizeof(u8), pi->sram_end); + +	return ret; +} + +static int kv_upload_dpm_settings(struct amdgpu_device *adev) +{ +	struct kv_power_info *pi = kv_get_pi(adev); +	int ret; + +	ret = amdgpu_kv_copy_bytes_to_smc(adev, +				   pi->dpm_table_start + +				   offsetof(SMU7_Fusion_DpmTable, GraphicsLevel), +				   (u8 *)&pi->graphics_level, +				   sizeof(SMU7_Fusion_GraphicsLevel) * SMU7_MAX_LEVELS_GRAPHICS, +				   pi->sram_end); + +	if (ret) +		return ret; + +	ret = amdgpu_kv_copy_bytes_to_smc(adev, +				   pi->dpm_table_start + +				   offsetof(SMU7_Fusion_DpmTable, GraphicsDpmLevelCount), +				   &pi->graphics_dpm_level_count, +				   sizeof(u8), pi->sram_end); + +	return ret; +} + +static u32 kv_get_clock_difference(u32 a, u32 b) +{ +	return (a >= b) ? a - b : b - a; +} + +static u32 kv_get_clk_bypass(struct amdgpu_device *adev, u32 clk) +{ +	struct kv_power_info *pi = kv_get_pi(adev); +	u32 value; + +	if (pi->caps_enable_dfs_bypass) { +		if (kv_get_clock_difference(clk, 40000) < 200) +			value = 3; +		else if (kv_get_clock_difference(clk, 30000) < 200) +			value = 2; +		else if (kv_get_clock_difference(clk, 20000) < 200) +			value = 7; +		else if (kv_get_clock_difference(clk, 15000) < 200) +			value = 6; +		else if (kv_get_clock_difference(clk, 10000) < 200) +			value = 8; +		else +			value = 0; +	} else { +		value = 0; +	} + +	return value; +} + +static int kv_populate_uvd_table(struct amdgpu_device *adev) +{ +	struct kv_power_info *pi = kv_get_pi(adev); +	struct amdgpu_uvd_clock_voltage_dependency_table *table = +		&adev->pm.dpm.dyn_state.uvd_clock_voltage_dependency_table; +	struct atom_clock_dividers dividers; +	int ret; +	u32 i; + +	if (table == NULL || table->count == 0) +		return 0; + +	pi->uvd_level_count = 0; +	for (i = 0; i < table->count; i++) { +		if (pi->high_voltage_t && +		    (pi->high_voltage_t < table->entries[i].v)) +			break; + +		pi->uvd_level[i].VclkFrequency = cpu_to_be32(table->entries[i].vclk); +		pi->uvd_level[i].DclkFrequency = cpu_to_be32(table->entries[i].dclk); +		pi->uvd_level[i].MinVddNb = cpu_to_be16(table->entries[i].v); + +		pi->uvd_level[i].VClkBypassCntl = +			(u8)kv_get_clk_bypass(adev, table->entries[i].vclk); +		pi->uvd_level[i].DClkBypassCntl = +			(u8)kv_get_clk_bypass(adev, table->entries[i].dclk); + +		ret = amdgpu_atombios_get_clock_dividers(adev, COMPUTE_ENGINE_PLL_PARAM, +							 table->entries[i].vclk, false, ÷rs); +		if (ret) +			return ret; +		pi->uvd_level[i].VclkDivider = (u8)dividers.post_div; + +		ret = amdgpu_atombios_get_clock_dividers(adev, COMPUTE_ENGINE_PLL_PARAM, +							 table->entries[i].dclk, false, ÷rs); +		if (ret) +			return ret; +		pi->uvd_level[i].DclkDivider = (u8)dividers.post_div; + +		pi->uvd_level_count++; +	} + +	ret = amdgpu_kv_copy_bytes_to_smc(adev, +				   pi->dpm_table_start + +				   offsetof(SMU7_Fusion_DpmTable, UvdLevelCount), +				   (u8 *)&pi->uvd_level_count, +				   sizeof(u8), pi->sram_end); +	if (ret) +		return ret; + +	pi->uvd_interval = 1; + +	ret = amdgpu_kv_copy_bytes_to_smc(adev, +				   pi->dpm_table_start + +				   offsetof(SMU7_Fusion_DpmTable, UVDInterval), +				   &pi->uvd_interval, +				   sizeof(u8), pi->sram_end); +	if (ret) +		return ret; + +	ret = amdgpu_kv_copy_bytes_to_smc(adev, +				   pi->dpm_table_start + +				   offsetof(SMU7_Fusion_DpmTable, UvdLevel), +				   (u8 *)&pi->uvd_level, +				   sizeof(SMU7_Fusion_UvdLevel) * SMU7_MAX_LEVELS_UVD, +				   pi->sram_end); + +	return ret; + +} + +static int kv_populate_vce_table(struct amdgpu_device *adev) +{ +	struct kv_power_info *pi = kv_get_pi(adev); +	int ret; +	u32 i; +	struct amdgpu_vce_clock_voltage_dependency_table *table = +		&adev->pm.dpm.dyn_state.vce_clock_voltage_dependency_table; +	struct atom_clock_dividers dividers; + +	if (table == NULL || table->count == 0) +		return 0; + +	pi->vce_level_count = 0; +	for (i = 0; i < table->count; i++) { +		if (pi->high_voltage_t && +		    pi->high_voltage_t < table->entries[i].v) +			break; + +		pi->vce_level[i].Frequency = cpu_to_be32(table->entries[i].evclk); +		pi->vce_level[i].MinVoltage = cpu_to_be16(table->entries[i].v); + +		pi->vce_level[i].ClkBypassCntl = +			(u8)kv_get_clk_bypass(adev, table->entries[i].evclk); + +		ret = amdgpu_atombios_get_clock_dividers(adev, COMPUTE_ENGINE_PLL_PARAM, +							 table->entries[i].evclk, false, ÷rs); +		if (ret) +			return ret; +		pi->vce_level[i].Divider = (u8)dividers.post_div; + +		pi->vce_level_count++; +	} + +	ret = amdgpu_kv_copy_bytes_to_smc(adev, +				   pi->dpm_table_start + +				   offsetof(SMU7_Fusion_DpmTable, VceLevelCount), +				   (u8 *)&pi->vce_level_count, +				   sizeof(u8), +				   pi->sram_end); +	if (ret) +		return ret; + +	pi->vce_interval = 1; + +	ret = amdgpu_kv_copy_bytes_to_smc(adev, +				   pi->dpm_table_start + +				   offsetof(SMU7_Fusion_DpmTable, VCEInterval), +				   (u8 *)&pi->vce_interval, +				   sizeof(u8), +				   pi->sram_end); +	if (ret) +		return ret; + +	ret = amdgpu_kv_copy_bytes_to_smc(adev, +				   pi->dpm_table_start + +				   offsetof(SMU7_Fusion_DpmTable, VceLevel), +				   (u8 *)&pi->vce_level, +				   sizeof(SMU7_Fusion_ExtClkLevel) * SMU7_MAX_LEVELS_VCE, +				   pi->sram_end); + +	return ret; +} + +static int kv_populate_samu_table(struct amdgpu_device *adev) +{ +	struct kv_power_info *pi = kv_get_pi(adev); +	struct amdgpu_clock_voltage_dependency_table *table = +		&adev->pm.dpm.dyn_state.samu_clock_voltage_dependency_table; +	struct atom_clock_dividers dividers; +	int ret; +	u32 i; + +	if (table == NULL || table->count == 0) +		return 0; + +	pi->samu_level_count = 0; +	for (i = 0; i < table->count; i++) { +		if (pi->high_voltage_t && +		    pi->high_voltage_t < table->entries[i].v) +			break; + +		pi->samu_level[i].Frequency = cpu_to_be32(table->entries[i].clk); +		pi->samu_level[i].MinVoltage = cpu_to_be16(table->entries[i].v); + +		pi->samu_level[i].ClkBypassCntl = +			(u8)kv_get_clk_bypass(adev, table->entries[i].clk); + +		ret = amdgpu_atombios_get_clock_dividers(adev, COMPUTE_ENGINE_PLL_PARAM, +							 table->entries[i].clk, false, ÷rs); +		if (ret) +			return ret; +		pi->samu_level[i].Divider = (u8)dividers.post_div; + +		pi->samu_level_count++; +	} + +	ret = amdgpu_kv_copy_bytes_to_smc(adev, +				   pi->dpm_table_start + +				   offsetof(SMU7_Fusion_DpmTable, SamuLevelCount), +				   (u8 *)&pi->samu_level_count, +				   sizeof(u8), +				   pi->sram_end); +	if (ret) +		return ret; + +	pi->samu_interval = 1; + +	ret = amdgpu_kv_copy_bytes_to_smc(adev, +				   pi->dpm_table_start + +				   offsetof(SMU7_Fusion_DpmTable, SAMUInterval), +				   (u8 *)&pi->samu_interval, +				   sizeof(u8), +				   pi->sram_end); +	if (ret) +		return ret; + +	ret = amdgpu_kv_copy_bytes_to_smc(adev, +				   pi->dpm_table_start + +				   offsetof(SMU7_Fusion_DpmTable, SamuLevel), +				   (u8 *)&pi->samu_level, +				   sizeof(SMU7_Fusion_ExtClkLevel) * SMU7_MAX_LEVELS_SAMU, +				   pi->sram_end); +	if (ret) +		return ret; + +	return ret; +} + + +static int kv_populate_acp_table(struct amdgpu_device *adev) +{ +	struct kv_power_info *pi = kv_get_pi(adev); +	struct amdgpu_clock_voltage_dependency_table *table = +		&adev->pm.dpm.dyn_state.acp_clock_voltage_dependency_table; +	struct atom_clock_dividers dividers; +	int ret; +	u32 i; + +	if (table == NULL || table->count == 0) +		return 0; + +	pi->acp_level_count = 0; +	for (i = 0; i < table->count; i++) { +		pi->acp_level[i].Frequency = cpu_to_be32(table->entries[i].clk); +		pi->acp_level[i].MinVoltage = cpu_to_be16(table->entries[i].v); + +		ret = amdgpu_atombios_get_clock_dividers(adev, COMPUTE_ENGINE_PLL_PARAM, +							 table->entries[i].clk, false, ÷rs); +		if (ret) +			return ret; +		pi->acp_level[i].Divider = (u8)dividers.post_div; + +		pi->acp_level_count++; +	} + +	ret = amdgpu_kv_copy_bytes_to_smc(adev, +				   pi->dpm_table_start + +				   offsetof(SMU7_Fusion_DpmTable, AcpLevelCount), +				   (u8 *)&pi->acp_level_count, +				   sizeof(u8), +				   pi->sram_end); +	if (ret) +		return ret; + +	pi->acp_interval = 1; + +	ret = amdgpu_kv_copy_bytes_to_smc(adev, +				   pi->dpm_table_start + +				   offsetof(SMU7_Fusion_DpmTable, ACPInterval), +				   (u8 *)&pi->acp_interval, +				   sizeof(u8), +				   pi->sram_end); +	if (ret) +		return ret; + +	ret = amdgpu_kv_copy_bytes_to_smc(adev, +				   pi->dpm_table_start + +				   offsetof(SMU7_Fusion_DpmTable, AcpLevel), +				   (u8 *)&pi->acp_level, +				   sizeof(SMU7_Fusion_ExtClkLevel) * SMU7_MAX_LEVELS_ACP, +				   pi->sram_end); +	if (ret) +		return ret; + +	return ret; +} + +static void kv_calculate_dfs_bypass_settings(struct amdgpu_device *adev) +{ +	struct kv_power_info *pi = kv_get_pi(adev); +	u32 i; +	struct amdgpu_clock_voltage_dependency_table *table = +		&adev->pm.dpm.dyn_state.vddc_dependency_on_sclk; + +	if (table && table->count) { +		for (i = 0; i < pi->graphics_dpm_level_count; i++) { +			if (pi->caps_enable_dfs_bypass) { +				if (kv_get_clock_difference(table->entries[i].clk, 40000) < 200) +					pi->graphics_level[i].ClkBypassCntl = 3; +				else if (kv_get_clock_difference(table->entries[i].clk, 30000) < 200) +					pi->graphics_level[i].ClkBypassCntl = 2; +				else if (kv_get_clock_difference(table->entries[i].clk, 26600) < 200) +					pi->graphics_level[i].ClkBypassCntl = 7; +				else if (kv_get_clock_difference(table->entries[i].clk , 20000) < 200) +					pi->graphics_level[i].ClkBypassCntl = 6; +				else if (kv_get_clock_difference(table->entries[i].clk , 10000) < 200) +					pi->graphics_level[i].ClkBypassCntl = 8; +				else +					pi->graphics_level[i].ClkBypassCntl = 0; +			} else { +				pi->graphics_level[i].ClkBypassCntl = 0; +			} +		} +	} else { +		struct sumo_sclk_voltage_mapping_table *table = +			&pi->sys_info.sclk_voltage_mapping_table; +		for (i = 0; i < pi->graphics_dpm_level_count; i++) { +			if (pi->caps_enable_dfs_bypass) { +				if (kv_get_clock_difference(table->entries[i].sclk_frequency, 40000) < 200) +					pi->graphics_level[i].ClkBypassCntl = 3; +				else if (kv_get_clock_difference(table->entries[i].sclk_frequency, 30000) < 200) +					pi->graphics_level[i].ClkBypassCntl = 2; +				else if (kv_get_clock_difference(table->entries[i].sclk_frequency, 26600) < 200) +					pi->graphics_level[i].ClkBypassCntl = 7; +				else if (kv_get_clock_difference(table->entries[i].sclk_frequency, 20000) < 200) +					pi->graphics_level[i].ClkBypassCntl = 6; +				else if (kv_get_clock_difference(table->entries[i].sclk_frequency, 10000) < 200) +					pi->graphics_level[i].ClkBypassCntl = 8; +				else +					pi->graphics_level[i].ClkBypassCntl = 0; +			} else { +				pi->graphics_level[i].ClkBypassCntl = 0; +			} +		} +	} +} + +static int kv_enable_ulv(struct amdgpu_device *adev, bool enable) +{ +	return amdgpu_kv_notify_message_to_smu(adev, enable ? +					PPSMC_MSG_EnableULV : PPSMC_MSG_DisableULV); +} + +static void kv_reset_acp_boot_level(struct amdgpu_device *adev) +{ +	struct kv_power_info *pi = kv_get_pi(adev); + +	pi->acp_boot_level = 0xff; +} + +static void kv_update_current_ps(struct amdgpu_device *adev, +				 struct amdgpu_ps *rps) +{ +	struct kv_ps *new_ps = kv_get_ps(rps); +	struct kv_power_info *pi = kv_get_pi(adev); + +	pi->current_rps = *rps; +	pi->current_ps = *new_ps; +	pi->current_rps.ps_priv = &pi->current_ps; +} + +static void kv_update_requested_ps(struct amdgpu_device *adev, +				   struct amdgpu_ps *rps) +{ +	struct kv_ps *new_ps = kv_get_ps(rps); +	struct kv_power_info *pi = kv_get_pi(adev); + +	pi->requested_rps = *rps; +	pi->requested_ps = *new_ps; +	pi->requested_rps.ps_priv = &pi->requested_ps; +} + +static void kv_dpm_enable_bapm(struct amdgpu_device *adev, bool enable) +{ +	struct kv_power_info *pi = kv_get_pi(adev); +	int ret; + +	if (pi->bapm_enable) { +		ret = amdgpu_kv_smc_bapm_enable(adev, enable); +		if (ret) +			DRM_ERROR("amdgpu_kv_smc_bapm_enable failed\n"); +	} +} + +static int kv_dpm_enable(struct amdgpu_device *adev) +{ +	struct kv_power_info *pi = kv_get_pi(adev); +	int ret; + +	ret = kv_process_firmware_header(adev); +	if (ret) { +		DRM_ERROR("kv_process_firmware_header failed\n"); +		return ret; +	} +	kv_init_fps_limits(adev); +	kv_init_graphics_levels(adev); +	ret = kv_program_bootup_state(adev); +	if (ret) { +		DRM_ERROR("kv_program_bootup_state failed\n"); +		return ret; +	} +	kv_calculate_dfs_bypass_settings(adev); +	ret = kv_upload_dpm_settings(adev); +	if (ret) { +		DRM_ERROR("kv_upload_dpm_settings failed\n"); +		return ret; +	} +	ret = kv_populate_uvd_table(adev); +	if (ret) { +		DRM_ERROR("kv_populate_uvd_table failed\n"); +		return ret; +	} +	ret = kv_populate_vce_table(adev); +	if (ret) { +		DRM_ERROR("kv_populate_vce_table failed\n"); +		return ret; +	} +	ret = kv_populate_samu_table(adev); +	if (ret) { +		DRM_ERROR("kv_populate_samu_table failed\n"); +		return ret; +	} +	ret = kv_populate_acp_table(adev); +	if (ret) { +		DRM_ERROR("kv_populate_acp_table failed\n"); +		return ret; +	} +	kv_program_vc(adev); +#if 0 +	kv_initialize_hardware_cac_manager(adev); +#endif +	kv_start_am(adev); +	if (pi->enable_auto_thermal_throttling) { +		ret = kv_enable_auto_thermal_throttling(adev); +		if (ret) { +			DRM_ERROR("kv_enable_auto_thermal_throttling failed\n"); +			return ret; +		} +	} +	ret = kv_enable_dpm_voltage_scaling(adev); +	if (ret) { +		DRM_ERROR("kv_enable_dpm_voltage_scaling failed\n"); +		return ret; +	} +	ret = kv_set_dpm_interval(adev); +	if (ret) { +		DRM_ERROR("kv_set_dpm_interval failed\n"); +		return ret; +	} +	ret = kv_set_dpm_boot_state(adev); +	if (ret) { +		DRM_ERROR("kv_set_dpm_boot_state failed\n"); +		return ret; +	} +	ret = kv_enable_ulv(adev, true); +	if (ret) { +		DRM_ERROR("kv_enable_ulv failed\n"); +		return ret; +	} +	kv_start_dpm(adev); +	ret = kv_enable_didt(adev, true); +	if (ret) { +		DRM_ERROR("kv_enable_didt failed\n"); +		return ret; +	} +	ret = kv_enable_smc_cac(adev, true); +	if (ret) { +		DRM_ERROR("kv_enable_smc_cac failed\n"); +		return ret; +	} + +	kv_reset_acp_boot_level(adev); + +	ret = amdgpu_kv_smc_bapm_enable(adev, false); +	if (ret) { +		DRM_ERROR("amdgpu_kv_smc_bapm_enable failed\n"); +		return ret; +	} + +	kv_update_current_ps(adev, adev->pm.dpm.boot_ps); + +	if (adev->irq.installed && +	    amdgpu_is_internal_thermal_sensor(adev->pm.int_thermal_type)) { +		ret = kv_set_thermal_temperature_range(adev, KV_TEMP_RANGE_MIN, KV_TEMP_RANGE_MAX); +		if (ret) { +			DRM_ERROR("kv_set_thermal_temperature_range failed\n"); +			return ret; +		} +		amdgpu_irq_get(adev, &adev->pm.dpm.thermal.irq, +			       AMDGPU_THERMAL_IRQ_LOW_TO_HIGH); +		amdgpu_irq_get(adev, &adev->pm.dpm.thermal.irq, +			       AMDGPU_THERMAL_IRQ_HIGH_TO_LOW); +	} + +	return ret; +} + +static void kv_dpm_disable(struct amdgpu_device *adev) +{ +	amdgpu_irq_put(adev, &adev->pm.dpm.thermal.irq, +		       AMDGPU_THERMAL_IRQ_LOW_TO_HIGH); +	amdgpu_irq_put(adev, &adev->pm.dpm.thermal.irq, +		       AMDGPU_THERMAL_IRQ_HIGH_TO_LOW); + +	amdgpu_kv_smc_bapm_enable(adev, false); + +	if (adev->asic_type == CHIP_MULLINS) +		kv_enable_nb_dpm(adev, false); + +	/* powerup blocks */ +	kv_dpm_powergate_acp(adev, false); +	kv_dpm_powergate_samu(adev, false); +	kv_dpm_powergate_vce(adev, false); +	kv_dpm_powergate_uvd(adev, false); + +	kv_enable_smc_cac(adev, false); +	kv_enable_didt(adev, false); +	kv_clear_vc(adev); +	kv_stop_dpm(adev); +	kv_enable_ulv(adev, false); +	kv_reset_am(adev); + +	kv_update_current_ps(adev, adev->pm.dpm.boot_ps); +} + +#if 0 +static int kv_write_smc_soft_register(struct amdgpu_device *adev, +				      u16 reg_offset, u32 value) +{ +	struct kv_power_info *pi = kv_get_pi(adev); + +	return amdgpu_kv_copy_bytes_to_smc(adev, pi->soft_regs_start + reg_offset, +				    (u8 *)&value, sizeof(u16), pi->sram_end); +} + +static int kv_read_smc_soft_register(struct amdgpu_device *adev, +				     u16 reg_offset, u32 *value) +{ +	struct kv_power_info *pi = kv_get_pi(adev); + +	return amdgpu_kv_read_smc_sram_dword(adev, pi->soft_regs_start + reg_offset, +				      value, pi->sram_end); +} +#endif + +static void kv_init_sclk_t(struct amdgpu_device *adev) +{ +	struct kv_power_info *pi = kv_get_pi(adev); + +	pi->low_sclk_interrupt_t = 0; +} + +static int kv_init_fps_limits(struct amdgpu_device *adev) +{ +	struct kv_power_info *pi = kv_get_pi(adev); +	int ret = 0; + +	if (pi->caps_fps) { +		u16 tmp; + +		tmp = 45; +		pi->fps_high_t = cpu_to_be16(tmp); +		ret = amdgpu_kv_copy_bytes_to_smc(adev, +					   pi->dpm_table_start + +					   offsetof(SMU7_Fusion_DpmTable, FpsHighT), +					   (u8 *)&pi->fps_high_t, +					   sizeof(u16), pi->sram_end); + +		tmp = 30; +		pi->fps_low_t = cpu_to_be16(tmp); + +		ret = amdgpu_kv_copy_bytes_to_smc(adev, +					   pi->dpm_table_start + +					   offsetof(SMU7_Fusion_DpmTable, FpsLowT), +					   (u8 *)&pi->fps_low_t, +					   sizeof(u16), pi->sram_end); + +	} +	return ret; +} + +static void kv_init_powergate_state(struct amdgpu_device *adev) +{ +	struct kv_power_info *pi = kv_get_pi(adev); + +	pi->uvd_power_gated = false; +	pi->vce_power_gated = false; +	pi->samu_power_gated = false; +	pi->acp_power_gated = false; + +} + +static int kv_enable_uvd_dpm(struct amdgpu_device *adev, bool enable) +{ +	return amdgpu_kv_notify_message_to_smu(adev, enable ? +					PPSMC_MSG_UVDDPM_Enable : PPSMC_MSG_UVDDPM_Disable); +} + +static int kv_enable_vce_dpm(struct amdgpu_device *adev, bool enable) +{ +	return amdgpu_kv_notify_message_to_smu(adev, enable ? +					PPSMC_MSG_VCEDPM_Enable : PPSMC_MSG_VCEDPM_Disable); +} + +static int kv_enable_samu_dpm(struct amdgpu_device *adev, bool enable) +{ +	return amdgpu_kv_notify_message_to_smu(adev, enable ? +					PPSMC_MSG_SAMUDPM_Enable : PPSMC_MSG_SAMUDPM_Disable); +} + +static int kv_enable_acp_dpm(struct amdgpu_device *adev, bool enable) +{ +	return amdgpu_kv_notify_message_to_smu(adev, enable ? +					PPSMC_MSG_ACPDPM_Enable : PPSMC_MSG_ACPDPM_Disable); +} + +static int kv_update_uvd_dpm(struct amdgpu_device *adev, bool gate) +{ +	struct kv_power_info *pi = kv_get_pi(adev); +	struct amdgpu_uvd_clock_voltage_dependency_table *table = +		&adev->pm.dpm.dyn_state.uvd_clock_voltage_dependency_table; +	int ret; +	u32 mask; + +	if (!gate) { +		if (table->count) +			pi->uvd_boot_level = table->count - 1; +		else +			pi->uvd_boot_level = 0; + +		if (!pi->caps_uvd_dpm || pi->caps_stable_p_state) { +			mask = 1 << pi->uvd_boot_level; +		} else { +			mask = 0x1f; +		} + +		ret = amdgpu_kv_copy_bytes_to_smc(adev, +					   pi->dpm_table_start + +					   offsetof(SMU7_Fusion_DpmTable, UvdBootLevel), +					   (uint8_t *)&pi->uvd_boot_level, +					   sizeof(u8), pi->sram_end); +		if (ret) +			return ret; + +		amdgpu_kv_send_msg_to_smc_with_parameter(adev, +						  PPSMC_MSG_UVDDPM_SetEnabledMask, +						  mask); +	} + +	return kv_enable_uvd_dpm(adev, !gate); +} + +static u8 kv_get_vce_boot_level(struct amdgpu_device *adev, u32 evclk) +{ +	u8 i; +	struct amdgpu_vce_clock_voltage_dependency_table *table = +		&adev->pm.dpm.dyn_state.vce_clock_voltage_dependency_table; + +	for (i = 0; i < table->count; i++) { +		if (table->entries[i].evclk >= evclk) +			break; +	} + +	return i; +} + +static int kv_update_vce_dpm(struct amdgpu_device *adev, +			     struct amdgpu_ps *amdgpu_new_state, +			     struct amdgpu_ps *amdgpu_current_state) +{ +	struct kv_power_info *pi = kv_get_pi(adev); +	struct amdgpu_vce_clock_voltage_dependency_table *table = +		&adev->pm.dpm.dyn_state.vce_clock_voltage_dependency_table; +	int ret; + +	if (amdgpu_new_state->evclk > 0 && amdgpu_current_state->evclk == 0) { +		kv_dpm_powergate_vce(adev, false); +		/* turn the clocks on when encoding */ +		ret = amdgpu_set_clockgating_state(adev, AMD_IP_BLOCK_TYPE_VCE, +						    AMD_CG_STATE_UNGATE); +		if (ret) +			return ret; +		if (pi->caps_stable_p_state) +			pi->vce_boot_level = table->count - 1; +		else +			pi->vce_boot_level = kv_get_vce_boot_level(adev, amdgpu_new_state->evclk); + +		ret = amdgpu_kv_copy_bytes_to_smc(adev, +					   pi->dpm_table_start + +					   offsetof(SMU7_Fusion_DpmTable, VceBootLevel), +					   (u8 *)&pi->vce_boot_level, +					   sizeof(u8), +					   pi->sram_end); +		if (ret) +			return ret; + +		if (pi->caps_stable_p_state) +			amdgpu_kv_send_msg_to_smc_with_parameter(adev, +							  PPSMC_MSG_VCEDPM_SetEnabledMask, +							  (1 << pi->vce_boot_level)); + +		kv_enable_vce_dpm(adev, true); +	} else if (amdgpu_new_state->evclk == 0 && amdgpu_current_state->evclk > 0) { +		kv_enable_vce_dpm(adev, false); +		/* turn the clocks off when not encoding */ +		ret = amdgpu_set_clockgating_state(adev, AMD_IP_BLOCK_TYPE_VCE, +						    AMD_CG_STATE_GATE); +		if (ret) +			return ret; +		kv_dpm_powergate_vce(adev, true); +	} + +	return 0; +} + +static int kv_update_samu_dpm(struct amdgpu_device *adev, bool gate) +{ +	struct kv_power_info *pi = kv_get_pi(adev); +	struct amdgpu_clock_voltage_dependency_table *table = +		&adev->pm.dpm.dyn_state.samu_clock_voltage_dependency_table; +	int ret; + +	if (!gate) { +		if (pi->caps_stable_p_state) +			pi->samu_boot_level = table->count - 1; +		else +			pi->samu_boot_level = 0; + +		ret = amdgpu_kv_copy_bytes_to_smc(adev, +					   pi->dpm_table_start + +					   offsetof(SMU7_Fusion_DpmTable, SamuBootLevel), +					   (u8 *)&pi->samu_boot_level, +					   sizeof(u8), +					   pi->sram_end); +		if (ret) +			return ret; + +		if (pi->caps_stable_p_state) +			amdgpu_kv_send_msg_to_smc_with_parameter(adev, +							  PPSMC_MSG_SAMUDPM_SetEnabledMask, +							  (1 << pi->samu_boot_level)); +	} + +	return kv_enable_samu_dpm(adev, !gate); +} + +static u8 kv_get_acp_boot_level(struct amdgpu_device *adev) +{ +	u8 i; +	struct amdgpu_clock_voltage_dependency_table *table = +		&adev->pm.dpm.dyn_state.acp_clock_voltage_dependency_table; + +	for (i = 0; i < table->count; i++) { +		if (table->entries[i].clk >= 0) /* XXX */ +			break; +	} + +	if (i >= table->count) +		i = table->count - 1; + +	return i; +} + +static void kv_update_acp_boot_level(struct amdgpu_device *adev) +{ +	struct kv_power_info *pi = kv_get_pi(adev); +	u8 acp_boot_level; + +	if (!pi->caps_stable_p_state) { +		acp_boot_level = kv_get_acp_boot_level(adev); +		if (acp_boot_level != pi->acp_boot_level) { +			pi->acp_boot_level = acp_boot_level; +			amdgpu_kv_send_msg_to_smc_with_parameter(adev, +							  PPSMC_MSG_ACPDPM_SetEnabledMask, +							  (1 << pi->acp_boot_level)); +		} +	} +} + +static int kv_update_acp_dpm(struct amdgpu_device *adev, bool gate) +{ +	struct kv_power_info *pi = kv_get_pi(adev); +	struct amdgpu_clock_voltage_dependency_table *table = +		&adev->pm.dpm.dyn_state.acp_clock_voltage_dependency_table; +	int ret; + +	if (!gate) { +		if (pi->caps_stable_p_state) +			pi->acp_boot_level = table->count - 1; +		else +			pi->acp_boot_level = kv_get_acp_boot_level(adev); + +		ret = amdgpu_kv_copy_bytes_to_smc(adev, +					   pi->dpm_table_start + +					   offsetof(SMU7_Fusion_DpmTable, AcpBootLevel), +					   (u8 *)&pi->acp_boot_level, +					   sizeof(u8), +					   pi->sram_end); +		if (ret) +			return ret; + +		if (pi->caps_stable_p_state) +			amdgpu_kv_send_msg_to_smc_with_parameter(adev, +							  PPSMC_MSG_ACPDPM_SetEnabledMask, +							  (1 << pi->acp_boot_level)); +	} + +	return kv_enable_acp_dpm(adev, !gate); +} + +static void kv_dpm_powergate_uvd(struct amdgpu_device *adev, bool gate) +{ +	struct kv_power_info *pi = kv_get_pi(adev); +	int ret; + +	if (pi->uvd_power_gated == gate) +		return; + +	pi->uvd_power_gated = gate; + +	if (gate) { +		if (pi->caps_uvd_pg) { +			/* disable clockgating so we can properly shut down the block */ +			ret = amdgpu_set_clockgating_state(adev, AMD_IP_BLOCK_TYPE_UVD, +							    AMD_CG_STATE_UNGATE); +			/* shutdown the UVD block */ +			ret = amdgpu_set_powergating_state(adev, AMD_IP_BLOCK_TYPE_UVD, +							    AMD_PG_STATE_GATE); +			/* XXX: check for errors */ +		} +		kv_update_uvd_dpm(adev, gate); +		if (pi->caps_uvd_pg) +			/* power off the UVD block */ +			amdgpu_kv_notify_message_to_smu(adev, PPSMC_MSG_UVDPowerOFF); +	} else { +		if (pi->caps_uvd_pg) { +			/* power on the UVD block */ +			amdgpu_kv_notify_message_to_smu(adev, PPSMC_MSG_UVDPowerON); +			/* re-init the UVD block */ +			ret = amdgpu_set_powergating_state(adev, AMD_IP_BLOCK_TYPE_UVD, +							    AMD_PG_STATE_UNGATE); +			/* enable clockgating. hw will dynamically gate/ungate clocks on the fly */ +			ret = amdgpu_set_clockgating_state(adev, AMD_IP_BLOCK_TYPE_UVD, +							    AMD_CG_STATE_GATE); +			/* XXX: check for errors */ +		} +		kv_update_uvd_dpm(adev, gate); +	} +} + +static void kv_dpm_powergate_vce(struct amdgpu_device *adev, bool gate) +{ +	struct kv_power_info *pi = kv_get_pi(adev); +	int ret; + +	if (pi->vce_power_gated == gate) +		return; + +	pi->vce_power_gated = gate; + +	if (gate) { +		if (pi->caps_vce_pg) { +			/* shutdown the VCE block */ +			ret = amdgpu_set_powergating_state(adev, AMD_IP_BLOCK_TYPE_VCE, +							    AMD_PG_STATE_GATE); +			/* XXX: check for errors */ +			/* power off the VCE block */ +			amdgpu_kv_notify_message_to_smu(adev, PPSMC_MSG_VCEPowerOFF); +		} +	} else { +		if (pi->caps_vce_pg) { +			/* power on the VCE block */ +			amdgpu_kv_notify_message_to_smu(adev, PPSMC_MSG_VCEPowerON); +			/* re-init the VCE block */ +			ret = amdgpu_set_powergating_state(adev, AMD_IP_BLOCK_TYPE_VCE, +							    AMD_PG_STATE_UNGATE); +			/* XXX: check for errors */ +		} +	} +} + +static void kv_dpm_powergate_samu(struct amdgpu_device *adev, bool gate) +{ +	struct kv_power_info *pi = kv_get_pi(adev); + +	if (pi->samu_power_gated == gate) +		return; + +	pi->samu_power_gated = gate; + +	if (gate) { +		kv_update_samu_dpm(adev, true); +		if (pi->caps_samu_pg) +			amdgpu_kv_notify_message_to_smu(adev, PPSMC_MSG_SAMPowerOFF); +	} else { +		if (pi->caps_samu_pg) +			amdgpu_kv_notify_message_to_smu(adev, PPSMC_MSG_SAMPowerON); +		kv_update_samu_dpm(adev, false); +	} +} + +static void kv_dpm_powergate_acp(struct amdgpu_device *adev, bool gate) +{ +	struct kv_power_info *pi = kv_get_pi(adev); + +	if (pi->acp_power_gated == gate) +		return; + +	if (adev->asic_type == CHIP_KABINI || adev->asic_type == CHIP_MULLINS) +		return; + +	pi->acp_power_gated = gate; + +	if (gate) { +		kv_update_acp_dpm(adev, true); +		if (pi->caps_acp_pg) +			amdgpu_kv_notify_message_to_smu(adev, PPSMC_MSG_ACPPowerOFF); +	} else { +		if (pi->caps_acp_pg) +			amdgpu_kv_notify_message_to_smu(adev, PPSMC_MSG_ACPPowerON); +		kv_update_acp_dpm(adev, false); +	} +} + +static void kv_set_valid_clock_range(struct amdgpu_device *adev, +				     struct amdgpu_ps *new_rps) +{ +	struct kv_ps *new_ps = kv_get_ps(new_rps); +	struct kv_power_info *pi = kv_get_pi(adev); +	u32 i; +	struct amdgpu_clock_voltage_dependency_table *table = +		&adev->pm.dpm.dyn_state.vddc_dependency_on_sclk; + +	if (table && table->count) { +		for (i = 0; i < pi->graphics_dpm_level_count; i++) { +			if ((table->entries[i].clk >= new_ps->levels[0].sclk) || +			    (i == (pi->graphics_dpm_level_count - 1))) { +				pi->lowest_valid = i; +				break; +			} +		} + +		for (i = pi->graphics_dpm_level_count - 1; i > 0; i--) { +			if (table->entries[i].clk <= new_ps->levels[new_ps->num_levels - 1].sclk) +				break; +		} +		pi->highest_valid = i; + +		if (pi->lowest_valid > pi->highest_valid) { +			if ((new_ps->levels[0].sclk - table->entries[pi->highest_valid].clk) > +			    (table->entries[pi->lowest_valid].clk - new_ps->levels[new_ps->num_levels - 1].sclk)) +				pi->highest_valid = pi->lowest_valid; +			else +				pi->lowest_valid =  pi->highest_valid; +		} +	} else { +		struct sumo_sclk_voltage_mapping_table *table = +			&pi->sys_info.sclk_voltage_mapping_table; + +		for (i = 0; i < (int)pi->graphics_dpm_level_count; i++) { +			if (table->entries[i].sclk_frequency >= new_ps->levels[0].sclk || +			    i == (int)(pi->graphics_dpm_level_count - 1)) { +				pi->lowest_valid = i; +				break; +			} +		} + +		for (i = pi->graphics_dpm_level_count - 1; i > 0; i--) { +			if (table->entries[i].sclk_frequency <= +			    new_ps->levels[new_ps->num_levels - 1].sclk) +				break; +		} +		pi->highest_valid = i; + +		if (pi->lowest_valid > pi->highest_valid) { +			if ((new_ps->levels[0].sclk - +			     table->entries[pi->highest_valid].sclk_frequency) > +			    (table->entries[pi->lowest_valid].sclk_frequency - +			     new_ps->levels[new_ps->num_levels -1].sclk)) +				pi->highest_valid = pi->lowest_valid; +			else +				pi->lowest_valid =  pi->highest_valid; +		} +	} +} + +static int kv_update_dfs_bypass_settings(struct amdgpu_device *adev, +					 struct amdgpu_ps *new_rps) +{ +	struct kv_ps *new_ps = kv_get_ps(new_rps); +	struct kv_power_info *pi = kv_get_pi(adev); +	int ret = 0; +	u8 clk_bypass_cntl; + +	if (pi->caps_enable_dfs_bypass) { +		clk_bypass_cntl = new_ps->need_dfs_bypass ? +			pi->graphics_level[pi->graphics_boot_level].ClkBypassCntl : 0; +		ret = amdgpu_kv_copy_bytes_to_smc(adev, +					   (pi->dpm_table_start + +					    offsetof(SMU7_Fusion_DpmTable, GraphicsLevel) + +					    (pi->graphics_boot_level * sizeof(SMU7_Fusion_GraphicsLevel)) + +					    offsetof(SMU7_Fusion_GraphicsLevel, ClkBypassCntl)), +					   &clk_bypass_cntl, +					   sizeof(u8), pi->sram_end); +	} + +	return ret; +} + +static int kv_enable_nb_dpm(struct amdgpu_device *adev, +			    bool enable) +{ +	struct kv_power_info *pi = kv_get_pi(adev); +	int ret = 0; + +	if (enable) { +		if (pi->enable_nb_dpm && !pi->nb_dpm_enabled) { +			ret = amdgpu_kv_notify_message_to_smu(adev, PPSMC_MSG_NBDPM_Enable); +			if (ret == 0) +				pi->nb_dpm_enabled = true; +		} +	} else { +		if (pi->enable_nb_dpm && pi->nb_dpm_enabled) { +			ret = amdgpu_kv_notify_message_to_smu(adev, PPSMC_MSG_NBDPM_Disable); +			if (ret == 0) +				pi->nb_dpm_enabled = false; +		} +	} + +	return ret; +} + +static int kv_dpm_force_performance_level(struct amdgpu_device *adev, +					  enum amdgpu_dpm_forced_level level) +{ +	int ret; + +	if (level == AMDGPU_DPM_FORCED_LEVEL_HIGH) { +		ret = kv_force_dpm_highest(adev); +		if (ret) +			return ret; +	} else if (level == AMDGPU_DPM_FORCED_LEVEL_LOW) { +		ret = kv_force_dpm_lowest(adev); +		if (ret) +			return ret; +	} else if (level == AMDGPU_DPM_FORCED_LEVEL_AUTO) { +		ret = kv_unforce_levels(adev); +		if (ret) +			return ret; +	} + +	adev->pm.dpm.forced_level = level; + +	return 0; +} + +static int kv_dpm_pre_set_power_state(struct amdgpu_device *adev) +{ +	struct kv_power_info *pi = kv_get_pi(adev); +	struct amdgpu_ps requested_ps = *adev->pm.dpm.requested_ps; +	struct amdgpu_ps *new_ps = &requested_ps; + +	kv_update_requested_ps(adev, new_ps); + +	kv_apply_state_adjust_rules(adev, +				    &pi->requested_rps, +				    &pi->current_rps); + +	return 0; +} + +static int kv_dpm_set_power_state(struct amdgpu_device *adev) +{ +	struct kv_power_info *pi = kv_get_pi(adev); +	struct amdgpu_ps *new_ps = &pi->requested_rps; +	struct amdgpu_ps *old_ps = &pi->current_rps; +	int ret; + +	if (pi->bapm_enable) { +		ret = amdgpu_kv_smc_bapm_enable(adev, adev->pm.dpm.ac_power); +		if (ret) { +			DRM_ERROR("amdgpu_kv_smc_bapm_enable failed\n"); +			return ret; +		} +	} + +	if (adev->asic_type == CHIP_KABINI || adev->asic_type == CHIP_MULLINS) { +		if (pi->enable_dpm) { +			kv_set_valid_clock_range(adev, new_ps); +			kv_update_dfs_bypass_settings(adev, new_ps); +			ret = kv_calculate_ds_divider(adev); +			if (ret) { +				DRM_ERROR("kv_calculate_ds_divider failed\n"); +				return ret; +			} +			kv_calculate_nbps_level_settings(adev); +			kv_calculate_dpm_settings(adev); +			kv_force_lowest_valid(adev); +			kv_enable_new_levels(adev); +			kv_upload_dpm_settings(adev); +			kv_program_nbps_index_settings(adev, new_ps); +			kv_unforce_levels(adev); +			kv_set_enabled_levels(adev); +			kv_force_lowest_valid(adev); +			kv_unforce_levels(adev); + +			ret = kv_update_vce_dpm(adev, new_ps, old_ps); +			if (ret) { +				DRM_ERROR("kv_update_vce_dpm failed\n"); +				return ret; +			} +			kv_update_sclk_t(adev); +			if (adev->asic_type == CHIP_MULLINS) +				kv_enable_nb_dpm(adev, true); +		} +	} else { +		if (pi->enable_dpm) { +			kv_set_valid_clock_range(adev, new_ps); +			kv_update_dfs_bypass_settings(adev, new_ps); +			ret = kv_calculate_ds_divider(adev); +			if (ret) { +				DRM_ERROR("kv_calculate_ds_divider failed\n"); +				return ret; +			} +			kv_calculate_nbps_level_settings(adev); +			kv_calculate_dpm_settings(adev); +			kv_freeze_sclk_dpm(adev, true); +			kv_upload_dpm_settings(adev); +			kv_program_nbps_index_settings(adev, new_ps); +			kv_freeze_sclk_dpm(adev, false); +			kv_set_enabled_levels(adev); +			ret = kv_update_vce_dpm(adev, new_ps, old_ps); +			if (ret) { +				DRM_ERROR("kv_update_vce_dpm failed\n"); +				return ret; +			} +			kv_update_acp_boot_level(adev); +			kv_update_sclk_t(adev); +			kv_enable_nb_dpm(adev, true); +		} +	} + +	return 0; +} + +static void kv_dpm_post_set_power_state(struct amdgpu_device *adev) +{ +	struct kv_power_info *pi = kv_get_pi(adev); +	struct amdgpu_ps *new_ps = &pi->requested_rps; + +	kv_update_current_ps(adev, new_ps); +} + +static void kv_dpm_setup_asic(struct amdgpu_device *adev) +{ +	sumo_take_smu_control(adev, true); +	kv_init_powergate_state(adev); +	kv_init_sclk_t(adev); +} + +#if 0 +static void kv_dpm_reset_asic(struct amdgpu_device *adev) +{ +	struct kv_power_info *pi = kv_get_pi(adev); + +	if (adev->asic_type == CHIP_KABINI || adev->asic_type == CHIP_MULLINS) { +		kv_force_lowest_valid(adev); +		kv_init_graphics_levels(adev); +		kv_program_bootup_state(adev); +		kv_upload_dpm_settings(adev); +		kv_force_lowest_valid(adev); +		kv_unforce_levels(adev); +	} else { +		kv_init_graphics_levels(adev); +		kv_program_bootup_state(adev); +		kv_freeze_sclk_dpm(adev, true); +		kv_upload_dpm_settings(adev); +		kv_freeze_sclk_dpm(adev, false); +		kv_set_enabled_level(adev, pi->graphics_boot_level); +	} +} +#endif + +static void kv_construct_max_power_limits_table(struct amdgpu_device *adev, +						struct amdgpu_clock_and_voltage_limits *table) +{ +	struct kv_power_info *pi = kv_get_pi(adev); + +	if (pi->sys_info.sclk_voltage_mapping_table.num_max_dpm_entries > 0) { +		int idx = pi->sys_info.sclk_voltage_mapping_table.num_max_dpm_entries - 1; +		table->sclk = +			pi->sys_info.sclk_voltage_mapping_table.entries[idx].sclk_frequency; +		table->vddc = +			kv_convert_2bit_index_to_voltage(adev, +							 pi->sys_info.sclk_voltage_mapping_table.entries[idx].vid_2bit); +	} + +	table->mclk = pi->sys_info.nbp_memory_clock[0]; +} + +static void kv_patch_voltage_values(struct amdgpu_device *adev) +{ +	int i; +	struct amdgpu_uvd_clock_voltage_dependency_table *uvd_table = +		&adev->pm.dpm.dyn_state.uvd_clock_voltage_dependency_table; +	struct amdgpu_vce_clock_voltage_dependency_table *vce_table = +		&adev->pm.dpm.dyn_state.vce_clock_voltage_dependency_table; +	struct amdgpu_clock_voltage_dependency_table *samu_table = +		&adev->pm.dpm.dyn_state.samu_clock_voltage_dependency_table; +	struct amdgpu_clock_voltage_dependency_table *acp_table = +		&adev->pm.dpm.dyn_state.acp_clock_voltage_dependency_table; + +	if (uvd_table->count) { +		for (i = 0; i < uvd_table->count; i++) +			uvd_table->entries[i].v = +				kv_convert_8bit_index_to_voltage(adev, +								 uvd_table->entries[i].v); +	} + +	if (vce_table->count) { +		for (i = 0; i < vce_table->count; i++) +			vce_table->entries[i].v = +				kv_convert_8bit_index_to_voltage(adev, +								 vce_table->entries[i].v); +	} + +	if (samu_table->count) { +		for (i = 0; i < samu_table->count; i++) +			samu_table->entries[i].v = +				kv_convert_8bit_index_to_voltage(adev, +								 samu_table->entries[i].v); +	} + +	if (acp_table->count) { +		for (i = 0; i < acp_table->count; i++) +			acp_table->entries[i].v = +				kv_convert_8bit_index_to_voltage(adev, +								 acp_table->entries[i].v); +	} + +} + +static void kv_construct_boot_state(struct amdgpu_device *adev) +{ +	struct kv_power_info *pi = kv_get_pi(adev); + +	pi->boot_pl.sclk = pi->sys_info.bootup_sclk; +	pi->boot_pl.vddc_index = pi->sys_info.bootup_nb_voltage_index; +	pi->boot_pl.ds_divider_index = 0; +	pi->boot_pl.ss_divider_index = 0; +	pi->boot_pl.allow_gnb_slow = 1; +	pi->boot_pl.force_nbp_state = 0; +	pi->boot_pl.display_wm = 0; +	pi->boot_pl.vce_wm = 0; +} + +static int kv_force_dpm_highest(struct amdgpu_device *adev) +{ +	int ret; +	u32 enable_mask, i; + +	ret = amdgpu_kv_dpm_get_enable_mask(adev, &enable_mask); +	if (ret) +		return ret; + +	for (i = SMU7_MAX_LEVELS_GRAPHICS - 1; i > 0; i--) { +		if (enable_mask & (1 << i)) +			break; +	} + +	if (adev->asic_type == CHIP_KABINI || adev->asic_type == CHIP_MULLINS) +		return amdgpu_kv_send_msg_to_smc_with_parameter(adev, PPSMC_MSG_DPM_ForceState, i); +	else +		return kv_set_enabled_level(adev, i); +} + +static int kv_force_dpm_lowest(struct amdgpu_device *adev) +{ +	int ret; +	u32 enable_mask, i; + +	ret = amdgpu_kv_dpm_get_enable_mask(adev, &enable_mask); +	if (ret) +		return ret; + +	for (i = 0; i < SMU7_MAX_LEVELS_GRAPHICS; i++) { +		if (enable_mask & (1 << i)) +			break; +	} + +	if (adev->asic_type == CHIP_KABINI || adev->asic_type == CHIP_MULLINS) +		return amdgpu_kv_send_msg_to_smc_with_parameter(adev, PPSMC_MSG_DPM_ForceState, i); +	else +		return kv_set_enabled_level(adev, i); +} + +static u8 kv_get_sleep_divider_id_from_clock(struct amdgpu_device *adev, +					     u32 sclk, u32 min_sclk_in_sr) +{ +	struct kv_power_info *pi = kv_get_pi(adev); +	u32 i; +	u32 temp; +	u32 min = (min_sclk_in_sr > KV_MINIMUM_ENGINE_CLOCK) ? +		min_sclk_in_sr : KV_MINIMUM_ENGINE_CLOCK; + +	if (sclk < min) +		return 0; + +	if (!pi->caps_sclk_ds) +		return 0; + +	for (i = KV_MAX_DEEPSLEEP_DIVIDER_ID; i > 0; i--) { +		temp = sclk / sumo_get_sleep_divider_from_id(i); +		if (temp >= min) +			break; +	} + +	return (u8)i; +} + +static int kv_get_high_voltage_limit(struct amdgpu_device *adev, int *limit) +{ +	struct kv_power_info *pi = kv_get_pi(adev); +	struct amdgpu_clock_voltage_dependency_table *table = +		&adev->pm.dpm.dyn_state.vddc_dependency_on_sclk; +	int i; + +	if (table && table->count) { +		for (i = table->count - 1; i >= 0; i--) { +			if (pi->high_voltage_t && +			    (kv_convert_8bit_index_to_voltage(adev, table->entries[i].v) <= +			     pi->high_voltage_t)) { +				*limit = i; +				return 0; +			} +		} +	} else { +		struct sumo_sclk_voltage_mapping_table *table = +			&pi->sys_info.sclk_voltage_mapping_table; + +		for (i = table->num_max_dpm_entries - 1; i >= 0; i--) { +			if (pi->high_voltage_t && +			    (kv_convert_2bit_index_to_voltage(adev, table->entries[i].vid_2bit) <= +			     pi->high_voltage_t)) { +				*limit = i; +				return 0; +			} +		} +	} + +	*limit = 0; +	return 0; +} + +static void kv_apply_state_adjust_rules(struct amdgpu_device *adev, +					struct amdgpu_ps *new_rps, +					struct amdgpu_ps *old_rps) +{ +	struct kv_ps *ps = kv_get_ps(new_rps); +	struct kv_power_info *pi = kv_get_pi(adev); +	u32 min_sclk = 10000; /* ??? */ +	u32 sclk, mclk = 0; +	int i, limit; +	bool force_high; +	struct amdgpu_clock_voltage_dependency_table *table = +		&adev->pm.dpm.dyn_state.vddc_dependency_on_sclk; +	u32 stable_p_state_sclk = 0; +	struct amdgpu_clock_and_voltage_limits *max_limits = +		&adev->pm.dpm.dyn_state.max_clock_voltage_on_ac; + +	if (new_rps->vce_active) { +		new_rps->evclk = adev->pm.dpm.vce_states[adev->pm.dpm.vce_level].evclk; +		new_rps->ecclk = adev->pm.dpm.vce_states[adev->pm.dpm.vce_level].ecclk; +	} else { +		new_rps->evclk = 0; +		new_rps->ecclk = 0; +	} + +	mclk = max_limits->mclk; +	sclk = min_sclk; + +	if (pi->caps_stable_p_state) { +		stable_p_state_sclk = (max_limits->sclk * 75) / 100; + +		for (i = table->count - 1; i >= 0; i++) { +			if (stable_p_state_sclk >= table->entries[i].clk) { +				stable_p_state_sclk = table->entries[i].clk; +				break; +			} +		} + +		if (i > 0) +			stable_p_state_sclk = table->entries[0].clk; + +		sclk = stable_p_state_sclk; +	} + +	if (new_rps->vce_active) { +		if (sclk < adev->pm.dpm.vce_states[adev->pm.dpm.vce_level].sclk) +			sclk = adev->pm.dpm.vce_states[adev->pm.dpm.vce_level].sclk; +	} + +	ps->need_dfs_bypass = true; + +	for (i = 0; i < ps->num_levels; i++) { +		if (ps->levels[i].sclk < sclk) +			ps->levels[i].sclk = sclk; +	} + +	if (table && table->count) { +		for (i = 0; i < ps->num_levels; i++) { +			if (pi->high_voltage_t && +			    (pi->high_voltage_t < +			     kv_convert_8bit_index_to_voltage(adev, ps->levels[i].vddc_index))) { +				kv_get_high_voltage_limit(adev, &limit); +				ps->levels[i].sclk = table->entries[limit].clk; +			} +		} +	} else { +		struct sumo_sclk_voltage_mapping_table *table = +			&pi->sys_info.sclk_voltage_mapping_table; + +		for (i = 0; i < ps->num_levels; i++) { +			if (pi->high_voltage_t && +			    (pi->high_voltage_t < +			     kv_convert_8bit_index_to_voltage(adev, ps->levels[i].vddc_index))) { +				kv_get_high_voltage_limit(adev, &limit); +				ps->levels[i].sclk = table->entries[limit].sclk_frequency; +			} +		} +	} + +	if (pi->caps_stable_p_state) { +		for (i = 0; i < ps->num_levels; i++) { +			ps->levels[i].sclk = stable_p_state_sclk; +		} +	} + +	pi->video_start = new_rps->dclk || new_rps->vclk || +		new_rps->evclk || new_rps->ecclk; + +	if ((new_rps->class & ATOM_PPLIB_CLASSIFICATION_UI_MASK) == +	    ATOM_PPLIB_CLASSIFICATION_UI_BATTERY) +		pi->battery_state = true; +	else +		pi->battery_state = false; + +	if (adev->asic_type == CHIP_KABINI || adev->asic_type == CHIP_MULLINS) { +		ps->dpm0_pg_nb_ps_lo = 0x1; +		ps->dpm0_pg_nb_ps_hi = 0x0; +		ps->dpmx_nb_ps_lo = 0x1; +		ps->dpmx_nb_ps_hi = 0x0; +	} else { +		ps->dpm0_pg_nb_ps_lo = 0x3; +		ps->dpm0_pg_nb_ps_hi = 0x0; +		ps->dpmx_nb_ps_lo = 0x3; +		ps->dpmx_nb_ps_hi = 0x0; + +		if (pi->sys_info.nb_dpm_enable) { +			force_high = (mclk >= pi->sys_info.nbp_memory_clock[3]) || +				pi->video_start || (adev->pm.dpm.new_active_crtc_count >= 3) || +				pi->disable_nb_ps3_in_battery; +			ps->dpm0_pg_nb_ps_lo = force_high ? 0x2 : 0x3; +			ps->dpm0_pg_nb_ps_hi = 0x2; +			ps->dpmx_nb_ps_lo = force_high ? 0x2 : 0x3; +			ps->dpmx_nb_ps_hi = 0x2; +		} +	} +} + +static void kv_dpm_power_level_enabled_for_throttle(struct amdgpu_device *adev, +						    u32 index, bool enable) +{ +	struct kv_power_info *pi = kv_get_pi(adev); + +	pi->graphics_level[index].EnabledForThrottle = enable ? 1 : 0; +} + +static int kv_calculate_ds_divider(struct amdgpu_device *adev) +{ +	struct kv_power_info *pi = kv_get_pi(adev); +	u32 sclk_in_sr = 10000; /* ??? */ +	u32 i; + +	if (pi->lowest_valid > pi->highest_valid) +		return -EINVAL; + +	for (i = pi->lowest_valid; i <= pi->highest_valid; i++) { +		pi->graphics_level[i].DeepSleepDivId = +			kv_get_sleep_divider_id_from_clock(adev, +							   be32_to_cpu(pi->graphics_level[i].SclkFrequency), +							   sclk_in_sr); +	} +	return 0; +} + +static int kv_calculate_nbps_level_settings(struct amdgpu_device *adev) +{ +	struct kv_power_info *pi = kv_get_pi(adev); +	u32 i; +	bool force_high; +	struct amdgpu_clock_and_voltage_limits *max_limits = +		&adev->pm.dpm.dyn_state.max_clock_voltage_on_ac; +	u32 mclk = max_limits->mclk; + +	if (pi->lowest_valid > pi->highest_valid) +		return -EINVAL; + +	if (adev->asic_type == CHIP_KABINI || adev->asic_type == CHIP_MULLINS) { +		for (i = pi->lowest_valid; i <= pi->highest_valid; i++) { +			pi->graphics_level[i].GnbSlow = 1; +			pi->graphics_level[i].ForceNbPs1 = 0; +			pi->graphics_level[i].UpH = 0; +		} + +		if (!pi->sys_info.nb_dpm_enable) +			return 0; + +		force_high = ((mclk >= pi->sys_info.nbp_memory_clock[3]) || +			      (adev->pm.dpm.new_active_crtc_count >= 3) || pi->video_start); + +		if (force_high) { +			for (i = pi->lowest_valid; i <= pi->highest_valid; i++) +				pi->graphics_level[i].GnbSlow = 0; +		} else { +			if (pi->battery_state) +				pi->graphics_level[0].ForceNbPs1 = 1; + +			pi->graphics_level[1].GnbSlow = 0; +			pi->graphics_level[2].GnbSlow = 0; +			pi->graphics_level[3].GnbSlow = 0; +			pi->graphics_level[4].GnbSlow = 0; +		} +	} else { +		for (i = pi->lowest_valid; i <= pi->highest_valid; i++) { +			pi->graphics_level[i].GnbSlow = 1; +			pi->graphics_level[i].ForceNbPs1 = 0; +			pi->graphics_level[i].UpH = 0; +		} + +		if (pi->sys_info.nb_dpm_enable && pi->battery_state) { +			pi->graphics_level[pi->lowest_valid].UpH = 0x28; +			pi->graphics_level[pi->lowest_valid].GnbSlow = 0; +			if (pi->lowest_valid != pi->highest_valid) +				pi->graphics_level[pi->lowest_valid].ForceNbPs1 = 1; +		} +	} +	return 0; +} + +static int kv_calculate_dpm_settings(struct amdgpu_device *adev) +{ +	struct kv_power_info *pi = kv_get_pi(adev); +	u32 i; + +	if (pi->lowest_valid > pi->highest_valid) +		return -EINVAL; + +	for (i = pi->lowest_valid; i <= pi->highest_valid; i++) +		pi->graphics_level[i].DisplayWatermark = (i == pi->highest_valid) ? 1 : 0; + +	return 0; +} + +static void kv_init_graphics_levels(struct amdgpu_device *adev) +{ +	struct kv_power_info *pi = kv_get_pi(adev); +	u32 i; +	struct amdgpu_clock_voltage_dependency_table *table = +		&adev->pm.dpm.dyn_state.vddc_dependency_on_sclk; + +	if (table && table->count) { +		u32 vid_2bit; + +		pi->graphics_dpm_level_count = 0; +		for (i = 0; i < table->count; i++) { +			if (pi->high_voltage_t && +			    (pi->high_voltage_t < +			     kv_convert_8bit_index_to_voltage(adev, table->entries[i].v))) +				break; + +			kv_set_divider_value(adev, i, table->entries[i].clk); +			vid_2bit = kv_convert_vid7_to_vid2(adev, +							   &pi->sys_info.vid_mapping_table, +							   table->entries[i].v); +			kv_set_vid(adev, i, vid_2bit); +			kv_set_at(adev, i, pi->at[i]); +			kv_dpm_power_level_enabled_for_throttle(adev, i, true); +			pi->graphics_dpm_level_count++; +		} +	} else { +		struct sumo_sclk_voltage_mapping_table *table = +			&pi->sys_info.sclk_voltage_mapping_table; + +		pi->graphics_dpm_level_count = 0; +		for (i = 0; i < table->num_max_dpm_entries; i++) { +			if (pi->high_voltage_t && +			    pi->high_voltage_t < +			    kv_convert_2bit_index_to_voltage(adev, table->entries[i].vid_2bit)) +				break; + +			kv_set_divider_value(adev, i, table->entries[i].sclk_frequency); +			kv_set_vid(adev, i, table->entries[i].vid_2bit); +			kv_set_at(adev, i, pi->at[i]); +			kv_dpm_power_level_enabled_for_throttle(adev, i, true); +			pi->graphics_dpm_level_count++; +		} +	} + +	for (i = 0; i < SMU7_MAX_LEVELS_GRAPHICS; i++) +		kv_dpm_power_level_enable(adev, i, false); +} + +static void kv_enable_new_levels(struct amdgpu_device *adev) +{ +	struct kv_power_info *pi = kv_get_pi(adev); +	u32 i; + +	for (i = 0; i < SMU7_MAX_LEVELS_GRAPHICS; i++) { +		if (i >= pi->lowest_valid && i <= pi->highest_valid) +			kv_dpm_power_level_enable(adev, i, true); +	} +} + +static int kv_set_enabled_level(struct amdgpu_device *adev, u32 level) +{ +	u32 new_mask = (1 << level); + +	return amdgpu_kv_send_msg_to_smc_with_parameter(adev, +						 PPSMC_MSG_SCLKDPM_SetEnabledMask, +						 new_mask); +} + +static int kv_set_enabled_levels(struct amdgpu_device *adev) +{ +	struct kv_power_info *pi = kv_get_pi(adev); +	u32 i, new_mask = 0; + +	for (i = pi->lowest_valid; i <= pi->highest_valid; i++) +		new_mask |= (1 << i); + +	return amdgpu_kv_send_msg_to_smc_with_parameter(adev, +						 PPSMC_MSG_SCLKDPM_SetEnabledMask, +						 new_mask); +} + +static void kv_program_nbps_index_settings(struct amdgpu_device *adev, +					   struct amdgpu_ps *new_rps) +{ +	struct kv_ps *new_ps = kv_get_ps(new_rps); +	struct kv_power_info *pi = kv_get_pi(adev); +	u32 nbdpmconfig1; + +	if (adev->asic_type == CHIP_KABINI || adev->asic_type == CHIP_MULLINS) +		return; + +	if (pi->sys_info.nb_dpm_enable) { +		nbdpmconfig1 = RREG32_SMC(ixNB_DPM_CONFIG_1); +		nbdpmconfig1 &= ~(NB_DPM_CONFIG_1__Dpm0PgNbPsLo_MASK | +				NB_DPM_CONFIG_1__Dpm0PgNbPsHi_MASK | +				NB_DPM_CONFIG_1__DpmXNbPsLo_MASK | +				NB_DPM_CONFIG_1__DpmXNbPsHi_MASK); +		nbdpmconfig1 |= (new_ps->dpm0_pg_nb_ps_lo << NB_DPM_CONFIG_1__Dpm0PgNbPsLo__SHIFT) | +				(new_ps->dpm0_pg_nb_ps_hi << NB_DPM_CONFIG_1__Dpm0PgNbPsHi__SHIFT) | +				(new_ps->dpmx_nb_ps_lo << NB_DPM_CONFIG_1__DpmXNbPsLo__SHIFT) | +				(new_ps->dpmx_nb_ps_hi << NB_DPM_CONFIG_1__DpmXNbPsHi__SHIFT); +		WREG32_SMC(ixNB_DPM_CONFIG_1, nbdpmconfig1); +	} +} + +static int kv_set_thermal_temperature_range(struct amdgpu_device *adev, +					    int min_temp, int max_temp) +{ +	int low_temp = 0 * 1000; +	int high_temp = 255 * 1000; +	u32 tmp; + +	if (low_temp < min_temp) +		low_temp = min_temp; +	if (high_temp > max_temp) +		high_temp = max_temp; +	if (high_temp < low_temp) { +		DRM_ERROR("invalid thermal range: %d - %d\n", low_temp, high_temp); +		return -EINVAL; +	} + +	tmp = RREG32_SMC(ixCG_THERMAL_INT_CTRL); +	tmp &= ~(CG_THERMAL_INT_CTRL__DIG_THERM_INTH_MASK | +		CG_THERMAL_INT_CTRL__DIG_THERM_INTL_MASK); +	tmp |= ((49 + (high_temp / 1000)) << CG_THERMAL_INT_CTRL__DIG_THERM_INTH__SHIFT) | +		((49 + (low_temp / 1000)) << CG_THERMAL_INT_CTRL__DIG_THERM_INTL__SHIFT); +	WREG32_SMC(ixCG_THERMAL_INT_CTRL, tmp); + +	adev->pm.dpm.thermal.min_temp = low_temp; +	adev->pm.dpm.thermal.max_temp = high_temp; + +	return 0; +} + +union igp_info { +	struct _ATOM_INTEGRATED_SYSTEM_INFO info; +	struct _ATOM_INTEGRATED_SYSTEM_INFO_V2 info_2; +	struct _ATOM_INTEGRATED_SYSTEM_INFO_V5 info_5; +	struct _ATOM_INTEGRATED_SYSTEM_INFO_V6 info_6; +	struct _ATOM_INTEGRATED_SYSTEM_INFO_V1_7 info_7; +	struct _ATOM_INTEGRATED_SYSTEM_INFO_V1_8 info_8; +}; + +static int kv_parse_sys_info_table(struct amdgpu_device *adev) +{ +	struct kv_power_info *pi = kv_get_pi(adev); +	struct amdgpu_mode_info *mode_info = &adev->mode_info; +	int index = GetIndexIntoMasterTable(DATA, IntegratedSystemInfo); +	union igp_info *igp_info; +	u8 frev, crev; +	u16 data_offset; +	int i; + +	if (amdgpu_atom_parse_data_header(mode_info->atom_context, index, NULL, +				   &frev, &crev, &data_offset)) { +		igp_info = (union igp_info *)(mode_info->atom_context->bios + +					      data_offset); + +		if (crev != 8) { +			DRM_ERROR("Unsupported IGP table: %d %d\n", frev, crev); +			return -EINVAL; +		} +		pi->sys_info.bootup_sclk = le32_to_cpu(igp_info->info_8.ulBootUpEngineClock); +		pi->sys_info.bootup_uma_clk = le32_to_cpu(igp_info->info_8.ulBootUpUMAClock); +		pi->sys_info.bootup_nb_voltage_index = +			le16_to_cpu(igp_info->info_8.usBootUpNBVoltage); +		if (igp_info->info_8.ucHtcTmpLmt == 0) +			pi->sys_info.htc_tmp_lmt = 203; +		else +			pi->sys_info.htc_tmp_lmt = igp_info->info_8.ucHtcTmpLmt; +		if (igp_info->info_8.ucHtcHystLmt == 0) +			pi->sys_info.htc_hyst_lmt = 5; +		else +			pi->sys_info.htc_hyst_lmt = igp_info->info_8.ucHtcHystLmt; +		if (pi->sys_info.htc_tmp_lmt <= pi->sys_info.htc_hyst_lmt) { +			DRM_ERROR("The htcTmpLmt should be larger than htcHystLmt.\n"); +		} + +		if (le32_to_cpu(igp_info->info_8.ulSystemConfig) & (1 << 3)) +			pi->sys_info.nb_dpm_enable = true; +		else +			pi->sys_info.nb_dpm_enable = false; + +		for (i = 0; i < KV_NUM_NBPSTATES; i++) { +			pi->sys_info.nbp_memory_clock[i] = +				le32_to_cpu(igp_info->info_8.ulNbpStateMemclkFreq[i]); +			pi->sys_info.nbp_n_clock[i] = +				le32_to_cpu(igp_info->info_8.ulNbpStateNClkFreq[i]); +		} +		if (le32_to_cpu(igp_info->info_8.ulGPUCapInfo) & +		    SYS_INFO_GPUCAPS__ENABEL_DFS_BYPASS) +			pi->caps_enable_dfs_bypass = true; + +		sumo_construct_sclk_voltage_mapping_table(adev, +							  &pi->sys_info.sclk_voltage_mapping_table, +							  igp_info->info_8.sAvail_SCLK); + +		sumo_construct_vid_mapping_table(adev, +						 &pi->sys_info.vid_mapping_table, +						 igp_info->info_8.sAvail_SCLK); + +		kv_construct_max_power_limits_table(adev, +						    &adev->pm.dpm.dyn_state.max_clock_voltage_on_ac); +	} +	return 0; +} + +union power_info { +	struct _ATOM_POWERPLAY_INFO info; +	struct _ATOM_POWERPLAY_INFO_V2 info_2; +	struct _ATOM_POWERPLAY_INFO_V3 info_3; +	struct _ATOM_PPLIB_POWERPLAYTABLE pplib; +	struct _ATOM_PPLIB_POWERPLAYTABLE2 pplib2; +	struct _ATOM_PPLIB_POWERPLAYTABLE3 pplib3; +}; + +union pplib_clock_info { +	struct _ATOM_PPLIB_R600_CLOCK_INFO r600; +	struct _ATOM_PPLIB_RS780_CLOCK_INFO rs780; +	struct _ATOM_PPLIB_EVERGREEN_CLOCK_INFO evergreen; +	struct _ATOM_PPLIB_SUMO_CLOCK_INFO sumo; +}; + +union pplib_power_state { +	struct _ATOM_PPLIB_STATE v1; +	struct _ATOM_PPLIB_STATE_V2 v2; +}; + +static void kv_patch_boot_state(struct amdgpu_device *adev, +				struct kv_ps *ps) +{ +	struct kv_power_info *pi = kv_get_pi(adev); + +	ps->num_levels = 1; +	ps->levels[0] = pi->boot_pl; +} + +static void kv_parse_pplib_non_clock_info(struct amdgpu_device *adev, +					  struct amdgpu_ps *rps, +					  struct _ATOM_PPLIB_NONCLOCK_INFO *non_clock_info, +					  u8 table_rev) +{ +	struct kv_ps *ps = kv_get_ps(rps); + +	rps->caps = le32_to_cpu(non_clock_info->ulCapsAndSettings); +	rps->class = le16_to_cpu(non_clock_info->usClassification); +	rps->class2 = le16_to_cpu(non_clock_info->usClassification2); + +	if (ATOM_PPLIB_NONCLOCKINFO_VER1 < table_rev) { +		rps->vclk = le32_to_cpu(non_clock_info->ulVCLK); +		rps->dclk = le32_to_cpu(non_clock_info->ulDCLK); +	} else { +		rps->vclk = 0; +		rps->dclk = 0; +	} + +	if (rps->class & ATOM_PPLIB_CLASSIFICATION_BOOT) { +		adev->pm.dpm.boot_ps = rps; +		kv_patch_boot_state(adev, ps); +	} +	if (rps->class & ATOM_PPLIB_CLASSIFICATION_UVDSTATE) +		adev->pm.dpm.uvd_ps = rps; +} + +static void kv_parse_pplib_clock_info(struct amdgpu_device *adev, +				      struct amdgpu_ps *rps, int index, +					union pplib_clock_info *clock_info) +{ +	struct kv_power_info *pi = kv_get_pi(adev); +	struct kv_ps *ps = kv_get_ps(rps); +	struct kv_pl *pl = &ps->levels[index]; +	u32 sclk; + +	sclk = le16_to_cpu(clock_info->sumo.usEngineClockLow); +	sclk |= clock_info->sumo.ucEngineClockHigh << 16; +	pl->sclk = sclk; +	pl->vddc_index = clock_info->sumo.vddcIndex; + +	ps->num_levels = index + 1; + +	if (pi->caps_sclk_ds) { +		pl->ds_divider_index = 5; +		pl->ss_divider_index = 5; +	} +} + +static int kv_parse_power_table(struct amdgpu_device *adev) +{ +	struct amdgpu_mode_info *mode_info = &adev->mode_info; +	struct _ATOM_PPLIB_NONCLOCK_INFO *non_clock_info; +	union pplib_power_state *power_state; +	int i, j, k, non_clock_array_index, clock_array_index; +	union pplib_clock_info *clock_info; +	struct _StateArray *state_array; +	struct _ClockInfoArray *clock_info_array; +	struct _NonClockInfoArray *non_clock_info_array; +	union power_info *power_info; +	int index = GetIndexIntoMasterTable(DATA, PowerPlayInfo); +	u16 data_offset; +	u8 frev, crev; +	u8 *power_state_offset; +	struct kv_ps *ps; + +	if (!amdgpu_atom_parse_data_header(mode_info->atom_context, index, NULL, +				   &frev, &crev, &data_offset)) +		return -EINVAL; +	power_info = (union power_info *)(mode_info->atom_context->bios + data_offset); + +	amdgpu_add_thermal_controller(adev); + +	state_array = (struct _StateArray *) +		(mode_info->atom_context->bios + data_offset + +		 le16_to_cpu(power_info->pplib.usStateArrayOffset)); +	clock_info_array = (struct _ClockInfoArray *) +		(mode_info->atom_context->bios + data_offset + +		 le16_to_cpu(power_info->pplib.usClockInfoArrayOffset)); +	non_clock_info_array = (struct _NonClockInfoArray *) +		(mode_info->atom_context->bios + data_offset + +		 le16_to_cpu(power_info->pplib.usNonClockInfoArrayOffset)); + +	adev->pm.dpm.ps = kzalloc(sizeof(struct amdgpu_ps) * +				  state_array->ucNumEntries, GFP_KERNEL); +	if (!adev->pm.dpm.ps) +		return -ENOMEM; +	power_state_offset = (u8 *)state_array->states; +	for (i = 0; i < state_array->ucNumEntries; i++) { +		u8 *idx; +		power_state = (union pplib_power_state *)power_state_offset; +		non_clock_array_index = power_state->v2.nonClockInfoIndex; +		non_clock_info = (struct _ATOM_PPLIB_NONCLOCK_INFO *) +			&non_clock_info_array->nonClockInfo[non_clock_array_index]; +		ps = kzalloc(sizeof(struct kv_ps), GFP_KERNEL); +		if (ps == NULL) { +			kfree(adev->pm.dpm.ps); +			return -ENOMEM; +		} +		adev->pm.dpm.ps[i].ps_priv = ps; +		k = 0; +		idx = (u8 *)&power_state->v2.clockInfoIndex[0]; +		for (j = 0; j < power_state->v2.ucNumDPMLevels; j++) { +			clock_array_index = idx[j]; +			if (clock_array_index >= clock_info_array->ucNumEntries) +				continue; +			if (k >= SUMO_MAX_HARDWARE_POWERLEVELS) +				break; +			clock_info = (union pplib_clock_info *) +				((u8 *)&clock_info_array->clockInfo[0] + +				 (clock_array_index * clock_info_array->ucEntrySize)); +			kv_parse_pplib_clock_info(adev, +						  &adev->pm.dpm.ps[i], k, +						  clock_info); +			k++; +		} +		kv_parse_pplib_non_clock_info(adev, &adev->pm.dpm.ps[i], +					      non_clock_info, +					      non_clock_info_array->ucEntrySize); +		power_state_offset += 2 + power_state->v2.ucNumDPMLevels; +	} +	adev->pm.dpm.num_ps = state_array->ucNumEntries; + +	/* fill in the vce power states */ +	for (i = 0; i < AMDGPU_MAX_VCE_LEVELS; i++) { +		u32 sclk; +		clock_array_index = adev->pm.dpm.vce_states[i].clk_idx; +		clock_info = (union pplib_clock_info *) +			&clock_info_array->clockInfo[clock_array_index * clock_info_array->ucEntrySize]; +		sclk = le16_to_cpu(clock_info->sumo.usEngineClockLow); +		sclk |= clock_info->sumo.ucEngineClockHigh << 16; +		adev->pm.dpm.vce_states[i].sclk = sclk; +		adev->pm.dpm.vce_states[i].mclk = 0; +	} + +	return 0; +} + +static int kv_dpm_init(struct amdgpu_device *adev) +{ +	struct kv_power_info *pi; +	int ret, i; + +	pi = kzalloc(sizeof(struct kv_power_info), GFP_KERNEL); +	if (pi == NULL) +		return -ENOMEM; +	adev->pm.dpm.priv = pi; + +	ret = amdgpu_get_platform_caps(adev); +	if (ret) +		return ret; + +	ret = amdgpu_parse_extended_power_table(adev); +	if (ret) +		return ret; + +	for (i = 0; i < SUMO_MAX_HARDWARE_POWERLEVELS; i++) +		pi->at[i] = TRINITY_AT_DFLT; + +	pi->sram_end = SMC_RAM_END; + +	pi->enable_nb_dpm = true; + +	pi->caps_power_containment = true; +	pi->caps_cac = true; +	pi->enable_didt = false; +	if (pi->enable_didt) { +		pi->caps_sq_ramping = true; +		pi->caps_db_ramping = true; +		pi->caps_td_ramping = true; +		pi->caps_tcp_ramping = true; +	} + +	pi->caps_sclk_ds = true; +	pi->enable_auto_thermal_throttling = true; +	pi->disable_nb_ps3_in_battery = false; +	if (amdgpu_bapm == 0) +		pi->bapm_enable = false; +	else +		pi->bapm_enable = true; +	pi->voltage_drop_t = 0; +	pi->caps_sclk_throttle_low_notification = false; +	pi->caps_fps = false; /* true? */ +	pi->caps_uvd_pg = (adev->pg_flags & AMDGPU_PG_SUPPORT_UVD) ? true : false; +	pi->caps_uvd_dpm = true; +	pi->caps_vce_pg = (adev->pg_flags & AMDGPU_PG_SUPPORT_VCE) ? true : false; +	pi->caps_samu_pg = (adev->pg_flags & AMDGPU_PG_SUPPORT_SAMU) ? true : false; +	pi->caps_acp_pg = (adev->pg_flags & AMDGPU_PG_SUPPORT_ACP) ? true : false; +	pi->caps_stable_p_state = false; + +	ret = kv_parse_sys_info_table(adev); +	if (ret) +		return ret; + +	kv_patch_voltage_values(adev); +	kv_construct_boot_state(adev); + +	ret = kv_parse_power_table(adev); +	if (ret) +		return ret; + +	pi->enable_dpm = true; + +	return 0; +} + +static void +kv_dpm_debugfs_print_current_performance_level(struct amdgpu_device *adev, +					       struct seq_file *m) +{ +	struct kv_power_info *pi = kv_get_pi(adev); +	u32 current_index = +		(RREG32_SMC(ixTARGET_AND_CURRENT_PROFILE_INDEX) & +		TARGET_AND_CURRENT_PROFILE_INDEX__CURR_SCLK_INDEX_MASK) >> +		TARGET_AND_CURRENT_PROFILE_INDEX__CURR_SCLK_INDEX__SHIFT; +	u32 sclk, tmp; +	u16 vddc; + +	if (current_index >= SMU__NUM_SCLK_DPM_STATE) { +		seq_printf(m, "invalid dpm profile %d\n", current_index); +	} else { +		sclk = be32_to_cpu(pi->graphics_level[current_index].SclkFrequency); +		tmp = (RREG32_SMC(ixSMU_VOLTAGE_STATUS) & +			SMU_VOLTAGE_STATUS__SMU_VOLTAGE_CURRENT_LEVEL_MASK) >> +			SMU_VOLTAGE_STATUS__SMU_VOLTAGE_CURRENT_LEVEL__SHIFT; +		vddc = kv_convert_8bit_index_to_voltage(adev, (u16)tmp); +		seq_printf(m, "uvd    %sabled\n", pi->uvd_power_gated ? "dis" : "en"); +		seq_printf(m, "vce    %sabled\n", pi->vce_power_gated ? "dis" : "en"); +		seq_printf(m, "power level %d    sclk: %u vddc: %u\n", +			   current_index, sclk, vddc); +	} +} + +static void +kv_dpm_print_power_state(struct amdgpu_device *adev, +			 struct amdgpu_ps *rps) +{ +	int i; +	struct kv_ps *ps = kv_get_ps(rps); + +	amdgpu_dpm_print_class_info(rps->class, rps->class2); +	amdgpu_dpm_print_cap_info(rps->caps); +	printk("\tuvd    vclk: %d dclk: %d\n", rps->vclk, rps->dclk); +	for (i = 0; i < ps->num_levels; i++) { +		struct kv_pl *pl = &ps->levels[i]; +		printk("\t\tpower level %d    sclk: %u vddc: %u\n", +		       i, pl->sclk, +		       kv_convert_8bit_index_to_voltage(adev, pl->vddc_index)); +	} +	amdgpu_dpm_print_ps_status(adev, rps); +} + +static void kv_dpm_fini(struct amdgpu_device *adev) +{ +	int i; + +	for (i = 0; i < adev->pm.dpm.num_ps; i++) { +		kfree(adev->pm.dpm.ps[i].ps_priv); +	} +	kfree(adev->pm.dpm.ps); +	kfree(adev->pm.dpm.priv); +	amdgpu_free_extended_power_table(adev); +} + +static void kv_dpm_display_configuration_changed(struct amdgpu_device *adev) +{ + +} + +static u32 kv_dpm_get_sclk(struct amdgpu_device *adev, bool low) +{ +	struct kv_power_info *pi = kv_get_pi(adev); +	struct kv_ps *requested_state = kv_get_ps(&pi->requested_rps); + +	if (low) +		return requested_state->levels[0].sclk; +	else +		return requested_state->levels[requested_state->num_levels - 1].sclk; +} + +static u32 kv_dpm_get_mclk(struct amdgpu_device *adev, bool low) +{ +	struct kv_power_info *pi = kv_get_pi(adev); + +	return pi->sys_info.bootup_uma_clk; +} + +/* get temperature in millidegrees */ +static int kv_dpm_get_temp(struct amdgpu_device *adev) +{ +	u32 temp; +	int actual_temp = 0; + +	temp = RREG32_SMC(0xC0300E0C); + +	if (temp) +		actual_temp = (temp / 8) - 49; +	else +		actual_temp = 0; + +	actual_temp = actual_temp * 1000; + +	return actual_temp; +} + +static int kv_dpm_early_init(void *handle) +{ +	struct amdgpu_device *adev = (struct amdgpu_device *)handle; + +	kv_dpm_set_dpm_funcs(adev); +	kv_dpm_set_irq_funcs(adev); + +	return 0; +} + +static int kv_dpm_late_init(void *handle) +{ +	/* powerdown unused blocks for now */ +	struct amdgpu_device *adev = (struct amdgpu_device *)handle; + +	kv_dpm_powergate_acp(adev, true); +	kv_dpm_powergate_samu(adev, true); +	kv_dpm_powergate_vce(adev, true); +	kv_dpm_powergate_uvd(adev, true); + +	return 0; +} + +static int kv_dpm_sw_init(void *handle) +{ +	int ret; +	struct amdgpu_device *adev = (struct amdgpu_device *)handle; + +	ret = amdgpu_irq_add_id(adev, 230, &adev->pm.dpm.thermal.irq); +	if (ret) +		return ret; + +	ret = amdgpu_irq_add_id(adev, 231, &adev->pm.dpm.thermal.irq); +	if (ret) +		return ret; + +	/* default to balanced state */ +	adev->pm.dpm.state = POWER_STATE_TYPE_BALANCED; +	adev->pm.dpm.user_state = POWER_STATE_TYPE_BALANCED; +	adev->pm.dpm.forced_level = AMDGPU_DPM_FORCED_LEVEL_AUTO; +	adev->pm.default_sclk = adev->clock.default_sclk; +	adev->pm.default_mclk = adev->clock.default_mclk; +	adev->pm.current_sclk = adev->clock.default_sclk; +	adev->pm.current_mclk = adev->clock.default_mclk; +	adev->pm.int_thermal_type = THERMAL_TYPE_NONE; + +	if (amdgpu_dpm == 0) +		return 0; + +	INIT_WORK(&adev->pm.dpm.thermal.work, amdgpu_dpm_thermal_work_handler); +	mutex_lock(&adev->pm.mutex); +	ret = kv_dpm_init(adev); +	if (ret) +		goto dpm_failed; +	adev->pm.dpm.current_ps = adev->pm.dpm.requested_ps = adev->pm.dpm.boot_ps; +	if (amdgpu_dpm == 1) +		amdgpu_pm_print_power_states(adev); +	ret = amdgpu_pm_sysfs_init(adev); +	if (ret) +		goto dpm_failed; +	mutex_unlock(&adev->pm.mutex); +	DRM_INFO("amdgpu: dpm initialized\n"); + +	return 0; + +dpm_failed: +	kv_dpm_fini(adev); +	mutex_unlock(&adev->pm.mutex); +	DRM_ERROR("amdgpu: dpm initialization failed\n"); +	return ret; +} + +static int kv_dpm_sw_fini(void *handle) +{ +	struct amdgpu_device *adev = (struct amdgpu_device *)handle; + +	mutex_lock(&adev->pm.mutex); +	amdgpu_pm_sysfs_fini(adev); +	kv_dpm_fini(adev); +	mutex_unlock(&adev->pm.mutex); + +	return 0; +} + +static int kv_dpm_hw_init(void *handle) +{ +	int ret; +	struct amdgpu_device *adev = (struct amdgpu_device *)handle; + +	mutex_lock(&adev->pm.mutex); +	kv_dpm_setup_asic(adev); +	ret = kv_dpm_enable(adev); +	if (ret) +		adev->pm.dpm_enabled = false; +	else +		adev->pm.dpm_enabled = true; +	mutex_unlock(&adev->pm.mutex); + +	return ret; +} + +static int kv_dpm_hw_fini(void *handle) +{ +	struct amdgpu_device *adev = (struct amdgpu_device *)handle; + +	if (adev->pm.dpm_enabled) { +		mutex_lock(&adev->pm.mutex); +		kv_dpm_disable(adev); +		mutex_unlock(&adev->pm.mutex); +	} + +	return 0; +} + +static int kv_dpm_suspend(void *handle) +{ +	struct amdgpu_device *adev = (struct amdgpu_device *)handle; + +	if (adev->pm.dpm_enabled) { +		mutex_lock(&adev->pm.mutex); +		/* disable dpm */ +		kv_dpm_disable(adev); +		/* reset the power state */ +		adev->pm.dpm.current_ps = adev->pm.dpm.requested_ps = adev->pm.dpm.boot_ps; +		mutex_unlock(&adev->pm.mutex); +	} +	return 0; +} + +static int kv_dpm_resume(void *handle) +{ +	int ret; +	struct amdgpu_device *adev = (struct amdgpu_device *)handle; + +	if (adev->pm.dpm_enabled) { +		/* asic init will reset to the boot state */ +		mutex_lock(&adev->pm.mutex); +		kv_dpm_setup_asic(adev); +		ret = kv_dpm_enable(adev); +		if (ret) +			adev->pm.dpm_enabled = false; +		else +			adev->pm.dpm_enabled = true; +		mutex_unlock(&adev->pm.mutex); +		if (adev->pm.dpm_enabled) +			amdgpu_pm_compute_clocks(adev); +	} +	return 0; +} + +static bool kv_dpm_is_idle(void *handle) +{ +	return true; +} + +static int kv_dpm_wait_for_idle(void *handle) +{ +	return 0; +} + +static void kv_dpm_print_status(void *handle) +{ +	struct amdgpu_device *adev = (struct amdgpu_device *)handle; + +	dev_info(adev->dev, "KV/KB DPM registers\n"); +	dev_info(adev->dev, "  DIDT_SQ_CTRL0=0x%08X\n", +		 RREG32_DIDT(ixDIDT_SQ_CTRL0)); +	dev_info(adev->dev, "  DIDT_DB_CTRL0=0x%08X\n", +		 RREG32_DIDT(ixDIDT_DB_CTRL0)); +	dev_info(adev->dev, "  DIDT_TD_CTRL0=0x%08X\n", +		 RREG32_DIDT(ixDIDT_TD_CTRL0)); +	dev_info(adev->dev, "  DIDT_TCP_CTRL0=0x%08X\n", +		 RREG32_DIDT(ixDIDT_TCP_CTRL0)); +	dev_info(adev->dev, "  LCAC_SX0_OVR_SEL=0x%08X\n", +		 RREG32_SMC(ixLCAC_SX0_OVR_SEL)); +	dev_info(adev->dev, "  LCAC_SX0_OVR_VAL=0x%08X\n", +		 RREG32_SMC(ixLCAC_SX0_OVR_VAL)); +	dev_info(adev->dev, "  LCAC_MC0_OVR_SEL=0x%08X\n", +		 RREG32_SMC(ixLCAC_MC0_OVR_SEL)); +	dev_info(adev->dev, "  LCAC_MC0_OVR_VAL=0x%08X\n", +		 RREG32_SMC(ixLCAC_MC0_OVR_VAL)); +	dev_info(adev->dev, "  LCAC_MC1_OVR_SEL=0x%08X\n", +		 RREG32_SMC(ixLCAC_MC1_OVR_SEL)); +	dev_info(adev->dev, "  LCAC_MC1_OVR_VAL=0x%08X\n", +		 RREG32_SMC(ixLCAC_MC1_OVR_VAL)); +	dev_info(adev->dev, "  LCAC_MC2_OVR_SEL=0x%08X\n", +		 RREG32_SMC(ixLCAC_MC2_OVR_SEL)); +	dev_info(adev->dev, "  LCAC_MC2_OVR_VAL=0x%08X\n", +		 RREG32_SMC(ixLCAC_MC2_OVR_VAL)); +	dev_info(adev->dev, "  LCAC_MC3_OVR_SEL=0x%08X\n", +		 RREG32_SMC(ixLCAC_MC3_OVR_SEL)); +	dev_info(adev->dev, "  LCAC_MC3_OVR_VAL=0x%08X\n", +		 RREG32_SMC(ixLCAC_MC3_OVR_VAL)); +	dev_info(adev->dev, "  LCAC_CPL_OVR_SEL=0x%08X\n", +		 RREG32_SMC(ixLCAC_CPL_OVR_SEL)); +	dev_info(adev->dev, "  LCAC_CPL_OVR_VAL=0x%08X\n", +		 RREG32_SMC(ixLCAC_CPL_OVR_VAL)); +	dev_info(adev->dev, "  CG_FREQ_TRAN_VOTING_0=0x%08X\n", +		 RREG32_SMC(ixCG_FREQ_TRAN_VOTING_0)); +	dev_info(adev->dev, "  GENERAL_PWRMGT=0x%08X\n", +		 RREG32_SMC(ixGENERAL_PWRMGT)); +	dev_info(adev->dev, "  SCLK_PWRMGT_CNTL=0x%08X\n", +		 RREG32_SMC(ixSCLK_PWRMGT_CNTL)); +	dev_info(adev->dev, "  SMC_MESSAGE_0=0x%08X\n", +		 RREG32(mmSMC_MESSAGE_0)); +	dev_info(adev->dev, "  SMC_RESP_0=0x%08X\n", +		 RREG32(mmSMC_RESP_0)); +	dev_info(adev->dev, "  SMC_MSG_ARG_0=0x%08X\n", +		 RREG32(mmSMC_MSG_ARG_0)); +	dev_info(adev->dev, "  SMC_IND_INDEX_0=0x%08X\n", +		 RREG32(mmSMC_IND_INDEX_0)); +	dev_info(adev->dev, "  SMC_IND_DATA_0=0x%08X\n", +		 RREG32(mmSMC_IND_DATA_0)); +	dev_info(adev->dev, "  SMC_IND_ACCESS_CNTL=0x%08X\n", +		 RREG32(mmSMC_IND_ACCESS_CNTL)); +} + +static int kv_dpm_soft_reset(void *handle) +{ +	return 0; +} + +static int kv_dpm_set_interrupt_state(struct amdgpu_device *adev, +				      struct amdgpu_irq_src *src, +				      unsigned type, +				      enum amdgpu_interrupt_state state) +{ +	u32 cg_thermal_int; + +	switch (type) { +	case AMDGPU_THERMAL_IRQ_LOW_TO_HIGH: +		switch (state) { +		case AMDGPU_IRQ_STATE_DISABLE: +			cg_thermal_int = RREG32_SMC(ixCG_THERMAL_INT_CTRL); +			cg_thermal_int &= ~CG_THERMAL_INT_CTRL__THERM_INTH_MASK_MASK; +			WREG32_SMC(ixCG_THERMAL_INT_CTRL, cg_thermal_int); +			break; +		case AMDGPU_IRQ_STATE_ENABLE: +			cg_thermal_int = RREG32_SMC(ixCG_THERMAL_INT_CTRL); +			cg_thermal_int |= CG_THERMAL_INT_CTRL__THERM_INTH_MASK_MASK; +			WREG32_SMC(ixCG_THERMAL_INT_CTRL, cg_thermal_int); +			break; +		default: +			break; +		} +		break; + +	case AMDGPU_THERMAL_IRQ_HIGH_TO_LOW: +		switch (state) { +		case AMDGPU_IRQ_STATE_DISABLE: +			cg_thermal_int = RREG32_SMC(ixCG_THERMAL_INT_CTRL); +			cg_thermal_int &= ~CG_THERMAL_INT_CTRL__THERM_INTL_MASK_MASK; +			WREG32_SMC(ixCG_THERMAL_INT_CTRL, cg_thermal_int); +			break; +		case AMDGPU_IRQ_STATE_ENABLE: +			cg_thermal_int = RREG32_SMC(ixCG_THERMAL_INT_CTRL); +			cg_thermal_int |= CG_THERMAL_INT_CTRL__THERM_INTL_MASK_MASK; +			WREG32_SMC(ixCG_THERMAL_INT_CTRL, cg_thermal_int); +			break; +		default: +			break; +		} +		break; + +	default: +		break; +	} +	return 0; +} + +static int kv_dpm_process_interrupt(struct amdgpu_device *adev, +				    struct amdgpu_irq_src *source, +				    struct amdgpu_iv_entry *entry) +{ +	bool queue_thermal = false; + +	if (entry == NULL) +		return -EINVAL; + +	switch (entry->src_id) { +	case 230: /* thermal low to high */ +		DRM_DEBUG("IH: thermal low to high\n"); +		adev->pm.dpm.thermal.high_to_low = false; +		queue_thermal = true; +		break; +	case 231: /* thermal high to low */ +		DRM_DEBUG("IH: thermal high to low\n"); +		adev->pm.dpm.thermal.high_to_low = true; +		queue_thermal = true; +		break; +	default: +		break; +	} + +	if (queue_thermal) +		schedule_work(&adev->pm.dpm.thermal.work); + +	return 0; +} + +static int kv_dpm_set_clockgating_state(void *handle, +					  enum amd_clockgating_state state) +{ +	return 0; +} + +static int kv_dpm_set_powergating_state(void *handle, +					  enum amd_powergating_state state) +{ +	return 0; +} + +const struct amd_ip_funcs kv_dpm_ip_funcs = { +	.early_init = kv_dpm_early_init, +	.late_init = kv_dpm_late_init, +	.sw_init = kv_dpm_sw_init, +	.sw_fini = kv_dpm_sw_fini, +	.hw_init = kv_dpm_hw_init, +	.hw_fini = kv_dpm_hw_fini, +	.suspend = kv_dpm_suspend, +	.resume = kv_dpm_resume, +	.is_idle = kv_dpm_is_idle, +	.wait_for_idle = kv_dpm_wait_for_idle, +	.soft_reset = kv_dpm_soft_reset, +	.print_status = kv_dpm_print_status, +	.set_clockgating_state = kv_dpm_set_clockgating_state, +	.set_powergating_state = kv_dpm_set_powergating_state, +}; + +static const struct amdgpu_dpm_funcs kv_dpm_funcs = { +	.get_temperature = &kv_dpm_get_temp, +	.pre_set_power_state = &kv_dpm_pre_set_power_state, +	.set_power_state = &kv_dpm_set_power_state, +	.post_set_power_state = &kv_dpm_post_set_power_state, +	.display_configuration_changed = &kv_dpm_display_configuration_changed, +	.get_sclk = &kv_dpm_get_sclk, +	.get_mclk = &kv_dpm_get_mclk, +	.print_power_state = &kv_dpm_print_power_state, +	.debugfs_print_current_performance_level = &kv_dpm_debugfs_print_current_performance_level, +	.force_performance_level = &kv_dpm_force_performance_level, +	.powergate_uvd = &kv_dpm_powergate_uvd, +	.enable_bapm = &kv_dpm_enable_bapm, +}; + +static void kv_dpm_set_dpm_funcs(struct amdgpu_device *adev) +{ +	if (adev->pm.funcs == NULL) +		adev->pm.funcs = &kv_dpm_funcs; +} + +static const struct amdgpu_irq_src_funcs kv_dpm_irq_funcs = { +	.set = kv_dpm_set_interrupt_state, +	.process = kv_dpm_process_interrupt, +}; + +static void kv_dpm_set_irq_funcs(struct amdgpu_device *adev) +{ +	adev->pm.dpm.thermal.irq.num_types = AMDGPU_THERMAL_IRQ_LAST; +	adev->pm.dpm.thermal.irq.funcs = &kv_dpm_irq_funcs; +}  | 
