blob: 8f5da0ded85072735a663ef29ba7191acbc03806 [file] [log] [blame]
// SPDX-License-Identifier: MIT
//
// Copyright 2024 Advanced Micro Devices, Inc.
#include "dm_services.h"
#include "basics/dc_common.h"
#include "dm_helpers.h"
#include "core_types.h"
#include "resource.h"
#include "dccg.h"
#include "dce/dce_hwseq.h"
#include "reg_helper.h"
#include "abm.h"
#include "hubp.h"
#include "dchubbub.h"
#include "timing_generator.h"
#include "opp.h"
#include "ipp.h"
#include "mpc.h"
#include "mcif_wb.h"
#include "dc_dmub_srv.h"
#include "link_hwss.h"
#include "dpcd_defs.h"
#include "clk_mgr.h"
#include "dsc.h"
#include "link.h"
#include "dce/dmub_hw_lock_mgr.h"
#include "dcn10/dcn10_cm_common.h"
#include "dcn20/dcn20_optc.h"
#include "dcn30/dcn30_cm_common.h"
#include "dcn32/dcn32_hwseq.h"
#include "dcn401_hwseq.h"
#include "dcn401/dcn401_resource.h"
#include "dc_state_priv.h"
#include "link_enc_cfg.h"
#define DC_LOGGER_INIT(logger)
#define CTX \
hws->ctx
#define REG(reg)\
hws->regs->reg
#define DC_LOGGER \
dc->ctx->logger
#undef FN
#define FN(reg_name, field_name) \
hws->shifts->field_name, hws->masks->field_name
static void dcn401_initialize_min_clocks(struct dc *dc)
{
struct dc_clocks *clocks = &dc->current_state->bw_ctx.bw.dcn.clk;
clocks->dcfclk_deep_sleep_khz = DCN3_2_DCFCLK_DS_INIT_KHZ;
clocks->dcfclk_khz = dc->clk_mgr->bw_params->clk_table.entries[0].dcfclk_mhz * 1000;
clocks->socclk_khz = dc->clk_mgr->bw_params->clk_table.entries[0].socclk_mhz * 1000;
clocks->dramclk_khz = dc->clk_mgr->bw_params->clk_table.entries[0].memclk_mhz * 1000;
clocks->dppclk_khz = dc->clk_mgr->bw_params->clk_table.entries[0].dppclk_mhz * 1000;
if (dc->debug.disable_boot_optimizations) {
clocks->dispclk_khz = dc->clk_mgr->bw_params->clk_table.entries[0].dispclk_mhz * 1000;
} else {
/* Even though DPG_EN = 1 for the connected display, it still requires the
* correct timing so we cannot set DISPCLK to min freq or it could cause
* audio corruption. Read current DISPCLK from DENTIST and request the same
* freq to ensure that the timing is valid and unchanged.
*/
clocks->dispclk_khz = dc->clk_mgr->funcs->get_dispclk_from_dentist(dc->clk_mgr);
}
clocks->ref_dtbclk_khz = dc->clk_mgr->bw_params->clk_table.entries[0].dtbclk_mhz * 1000;
clocks->fclk_p_state_change_support = true;
clocks->p_state_change_support = true;
dc->clk_mgr->funcs->update_clocks(
dc->clk_mgr,
dc->current_state,
true);
}
void dcn401_program_gamut_remap(struct pipe_ctx *pipe_ctx)
{
unsigned int i = 0;
struct mpc_grph_gamut_adjustment mpc_adjust;
unsigned int mpcc_id = pipe_ctx->plane_res.mpcc_inst;
struct mpc *mpc = pipe_ctx->stream_res.opp->ctx->dc->res_pool->mpc;
//For now assert if location is not pre-blend
if (pipe_ctx->plane_state)
ASSERT(pipe_ctx->plane_state->mcm_location == MPCC_MOVABLE_CM_LOCATION_BEFORE);
// program MPCC_MCM_FIRST_GAMUT_REMAP
memset(&mpc_adjust, 0, sizeof(mpc_adjust));
mpc_adjust.gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_BYPASS;
mpc_adjust.mpcc_gamut_remap_block_id = MPCC_MCM_FIRST_GAMUT_REMAP;
if (pipe_ctx->plane_state &&
pipe_ctx->plane_state->gamut_remap_matrix.enable_remap == true) {
mpc_adjust.gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_SW;
for (i = 0; i < CSC_TEMPERATURE_MATRIX_SIZE; i++)
mpc_adjust.temperature_matrix[i] =
pipe_ctx->plane_state->gamut_remap_matrix.matrix[i];
}
mpc->funcs->set_gamut_remap(mpc, mpcc_id, &mpc_adjust);
// program MPCC_MCM_SECOND_GAMUT_REMAP for Bypass / Disable for now
mpc_adjust.gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_BYPASS;
mpc_adjust.mpcc_gamut_remap_block_id = MPCC_MCM_SECOND_GAMUT_REMAP;
mpc->funcs->set_gamut_remap(mpc, mpcc_id, &mpc_adjust);
// program MPCC_OGAM_GAMUT_REMAP same as is currently used on DCN3x
memset(&mpc_adjust, 0, sizeof(mpc_adjust));
mpc_adjust.gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_BYPASS;
mpc_adjust.mpcc_gamut_remap_block_id = MPCC_OGAM_GAMUT_REMAP;
if (pipe_ctx->top_pipe == NULL) {
if (pipe_ctx->stream->gamut_remap_matrix.enable_remap == true) {
mpc_adjust.gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_SW;
for (i = 0; i < CSC_TEMPERATURE_MATRIX_SIZE; i++)
mpc_adjust.temperature_matrix[i] =
pipe_ctx->stream->gamut_remap_matrix.matrix[i];
}
}
mpc->funcs->set_gamut_remap(mpc, mpcc_id, &mpc_adjust);
}
void dcn401_init_hw(struct dc *dc)
{
struct abm **abms = dc->res_pool->multiple_abms;
struct dce_hwseq *hws = dc->hwseq;
struct dc_bios *dcb = dc->ctx->dc_bios;
struct resource_pool *res_pool = dc->res_pool;
int i;
int edp_num;
uint32_t backlight = MAX_BACKLIGHT_LEVEL;
uint32_t user_level = MAX_BACKLIGHT_LEVEL;
int current_dchub_ref_freq = 0;
if (dc->clk_mgr && dc->clk_mgr->funcs && dc->clk_mgr->funcs->init_clocks) {
dc->clk_mgr->funcs->init_clocks(dc->clk_mgr);
// mark dcmode limits present if any clock has distinct AC and DC values from SMU
dc->caps.dcmode_power_limits_present =
(dc->clk_mgr->bw_params->clk_table.num_entries_per_clk.num_dcfclk_levels && dc->clk_mgr->bw_params->dc_mode_limit.dcfclk_mhz) ||
(dc->clk_mgr->bw_params->clk_table.num_entries_per_clk.num_dispclk_levels && dc->clk_mgr->bw_params->dc_mode_limit.dispclk_mhz) ||
(dc->clk_mgr->bw_params->clk_table.num_entries_per_clk.num_dtbclk_levels && dc->clk_mgr->bw_params->dc_mode_limit.dtbclk_mhz) ||
(dc->clk_mgr->bw_params->clk_table.num_entries_per_clk.num_fclk_levels && dc->clk_mgr->bw_params->dc_mode_limit.fclk_mhz) ||
(dc->clk_mgr->bw_params->clk_table.num_entries_per_clk.num_memclk_levels && dc->clk_mgr->bw_params->dc_mode_limit.memclk_mhz) ||
(dc->clk_mgr->bw_params->clk_table.num_entries_per_clk.num_socclk_levels && dc->clk_mgr->bw_params->dc_mode_limit.socclk_mhz);
}
// Initialize the dccg
if (res_pool->dccg->funcs->dccg_init)
res_pool->dccg->funcs->dccg_init(res_pool->dccg);
// Disable DMUB Initialization until IPS state programming is finalized
//if (!dcb->funcs->is_accelerated_mode(dcb)) {
// hws->funcs.bios_golden_init(dc);
//}
// Set default OPTC memory power states
if (dc->debug.enable_mem_low_power.bits.optc) {
// Shutdown when unassigned and light sleep in VBLANK
REG_SET_2(ODM_MEM_PWR_CTRL3, 0, ODM_MEM_UNASSIGNED_PWR_MODE, 3, ODM_MEM_VBLANK_PWR_MODE, 1);
}
if (dc->debug.enable_mem_low_power.bits.vga) {
// Power down VGA memory
REG_UPDATE(MMHUBBUB_MEM_PWR_CNTL, VGA_MEM_PWR_FORCE, 1);
}
if (dc->ctx->dc_bios->fw_info_valid) {
res_pool->ref_clocks.xtalin_clock_inKhz =
dc->ctx->dc_bios->fw_info.pll_info.crystal_frequency;
if (res_pool->hubbub) {
(res_pool->dccg->funcs->get_dccg_ref_freq)(res_pool->dccg,
dc->ctx->dc_bios->fw_info.pll_info.crystal_frequency,
&res_pool->ref_clocks.dccg_ref_clock_inKhz);
current_dchub_ref_freq = res_pool->ref_clocks.dchub_ref_clock_inKhz / 1000;
(res_pool->hubbub->funcs->get_dchub_ref_freq)(res_pool->hubbub,
res_pool->ref_clocks.dccg_ref_clock_inKhz,
&res_pool->ref_clocks.dchub_ref_clock_inKhz);
} else {
// Not all ASICs have DCCG sw component
res_pool->ref_clocks.dccg_ref_clock_inKhz =
res_pool->ref_clocks.xtalin_clock_inKhz;
res_pool->ref_clocks.dchub_ref_clock_inKhz =
res_pool->ref_clocks.xtalin_clock_inKhz;
}
} else
ASSERT_CRITICAL(false);
for (i = 0; i < dc->link_count; i++) {
/* Power up AND update implementation according to the
* required signal (which may be different from the
* default signal on connector).
*/
struct dc_link *link = dc->links[i];
link->link_enc->funcs->hw_init(link->link_enc);
/* Check for enabled DIG to identify enabled display */
if (link->link_enc->funcs->is_dig_enabled &&
link->link_enc->funcs->is_dig_enabled(link->link_enc)) {
link->link_status.link_active = true;
link->phy_state.symclk_state = SYMCLK_ON_TX_ON;
if (link->link_enc->funcs->fec_is_active &&
link->link_enc->funcs->fec_is_active(link->link_enc))
link->fec_state = dc_link_fec_enabled;
}
}
/* enable_power_gating_plane before dsc_pg_control because
* FORCEON = 1 with hw default value on bootup, resume from s3
*/
if (hws->funcs.enable_power_gating_plane)
hws->funcs.enable_power_gating_plane(dc->hwseq, true);
/* we want to turn off all dp displays before doing detection */
dc->link_srv->blank_all_dp_displays(dc);
/* If taking control over from VBIOS, we may want to optimize our first
* mode set, so we need to skip powering down pipes until we know which
* pipes we want to use.
* Otherwise, if taking control is not possible, we need to power
* everything down.
*/
if (dcb->funcs->is_accelerated_mode(dcb) || !dc->config.seamless_boot_edp_requested) {
/* Disable boot optimizations means power down everything including PHY, DIG,
* and OTG (i.e. the boot is not optimized because we do a full power down).
*/
if (dc->hwss.enable_accelerated_mode && dc->debug.disable_boot_optimizations)
dc->hwss.enable_accelerated_mode(dc, dc->current_state);
else
hws->funcs.init_pipes(dc, dc->current_state);
if (dc->res_pool->hubbub->funcs->allow_self_refresh_control)
dc->res_pool->hubbub->funcs->allow_self_refresh_control(dc->res_pool->hubbub,
!dc->res_pool->hubbub->ctx->dc->debug.disable_stutter);
dcn401_initialize_min_clocks(dc);
/* On HW init, allow idle optimizations after pipes have been turned off.
*
* In certain D3 cases (i.e. BOCO / BOMACO) it's possible that hardware state
* is reset (i.e. not in idle at the time hw init is called), but software state
* still has idle_optimizations = true, so we must disable idle optimizations first
* (i.e. set false), then re-enable (set true).
*/
dc_allow_idle_optimizations(dc, false);
dc_allow_idle_optimizations(dc, true);
}
/* In headless boot cases, DIG may be turned
* on which causes HW/SW discrepancies.
* To avoid this, power down hardware on boot
* if DIG is turned on and seamless boot not enabled
*/
if (!dc->config.seamless_boot_edp_requested) {
struct dc_link *edp_links[MAX_NUM_EDP];
struct dc_link *edp_link;
dc_get_edp_links(dc, edp_links, &edp_num);
if (edp_num) {
for (i = 0; i < edp_num; i++) {
edp_link = edp_links[i];
if (edp_link->link_enc->funcs->is_dig_enabled &&
edp_link->link_enc->funcs->is_dig_enabled(edp_link->link_enc) &&
dc->hwss.edp_backlight_control &&
hws->funcs.power_down &&
dc->hwss.edp_power_control) {
dc->hwss.edp_backlight_control(edp_link, false);
hws->funcs.power_down(dc);
dc->hwss.edp_power_control(edp_link, false);
}
}
} else {
for (i = 0; i < dc->link_count; i++) {
struct dc_link *link = dc->links[i];
if (link->link_enc->funcs->is_dig_enabled &&
link->link_enc->funcs->is_dig_enabled(link->link_enc) &&
hws->funcs.power_down) {
hws->funcs.power_down(dc);
break;
}
}
}
}
for (i = 0; i < res_pool->audio_count; i++) {
struct audio *audio = res_pool->audios[i];
audio->funcs->hw_init(audio);
}
for (i = 0; i < dc->link_count; i++) {
struct dc_link *link = dc->links[i];
if (link->panel_cntl) {
backlight = link->panel_cntl->funcs->hw_init(link->panel_cntl);
user_level = link->panel_cntl->stored_backlight_registers.USER_LEVEL;
}
}
for (i = 0; i < dc->res_pool->pipe_count; i++) {
if (abms[i] != NULL && abms[i]->funcs != NULL)
abms[i]->funcs->abm_init(abms[i], backlight, user_level);
}
/* power AFMT HDMI memory TODO: may move to dis/en output save power*/
REG_WRITE(DIO_MEM_PWR_CTRL, 0);
if (!dc->debug.disable_clock_gate) {
/* enable all DCN clock gating */
REG_WRITE(DCCG_GATE_DISABLE_CNTL, 0);
REG_WRITE(DCCG_GATE_DISABLE_CNTL2, 0);
REG_UPDATE(DCFCLK_CNTL, DCFCLK_GATE_DIS, 0);
}
dcn401_setup_hpo_hw_control(hws, true);
if (!dcb->funcs->is_accelerated_mode(dcb) && dc->res_pool->hubbub->funcs->init_watermarks)
dc->res_pool->hubbub->funcs->init_watermarks(dc->res_pool->hubbub);
if (dc->clk_mgr && dc->clk_mgr->funcs && dc->clk_mgr->funcs->notify_wm_ranges)
dc->clk_mgr->funcs->notify_wm_ranges(dc->clk_mgr);
if (dc->res_pool->hubbub->funcs->force_pstate_change_control)
dc->res_pool->hubbub->funcs->force_pstate_change_control(
dc->res_pool->hubbub, false, false);
if (dc->res_pool->hubbub->funcs->init_crb)
dc->res_pool->hubbub->funcs->init_crb(dc->res_pool->hubbub);
if (dc->res_pool->hubbub->funcs->set_request_limit && dc->config.sdpif_request_limit_words_per_umc > 0)
dc->res_pool->hubbub->funcs->set_request_limit(dc->res_pool->hubbub, dc->ctx->dc_bios->vram_info.num_chans, dc->config.sdpif_request_limit_words_per_umc);
// Get DMCUB capabilities
if (dc->ctx->dmub_srv) {
dc_dmub_srv_query_caps_cmd(dc->ctx->dmub_srv);
dc->caps.dmub_caps.psr = dc->ctx->dmub_srv->dmub->feature_caps.psr;
dc->caps.dmub_caps.mclk_sw = dc->ctx->dmub_srv->dmub->feature_caps.fw_assisted_mclk_switch_ver > 0;
dc->caps.dmub_caps.fams_ver = dc->ctx->dmub_srv->dmub->feature_caps.fw_assisted_mclk_switch_ver;
dc->debug.fams2_config.bits.enable &=
dc->caps.dmub_caps.fams_ver == dc->debug.fams_version.ver; // sw & fw fams versions must match for support
if ((!dc->debug.fams2_config.bits.enable && dc->res_pool->funcs->update_bw_bounding_box)
|| res_pool->ref_clocks.dchub_ref_clock_inKhz / 1000 != current_dchub_ref_freq) {
/* update bounding box if FAMS2 disabled, or if dchub clk has changed */
if (dc->clk_mgr)
dc->res_pool->funcs->update_bw_bounding_box(dc,
dc->clk_mgr->bw_params);
}
}
}
static void dcn401_get_mcm_lut_xable_from_pipe_ctx(struct dc *dc, struct pipe_ctx *pipe_ctx,
enum MCM_LUT_XABLE *shaper_xable,
enum MCM_LUT_XABLE *lut3d_xable,
enum MCM_LUT_XABLE *lut1d_xable)
{
enum dc_cm2_shaper_3dlut_setting shaper_3dlut_setting = DC_CM2_SHAPER_3DLUT_SETTING_BYPASS_ALL;
bool lut1d_enable = false;
struct mpc *mpc = dc->res_pool->mpc;
int mpcc_id = pipe_ctx->plane_res.hubp->inst;
if (!pipe_ctx->plane_state)
return;
shaper_3dlut_setting = pipe_ctx->plane_state->mcm_shaper_3dlut_setting;
lut1d_enable = pipe_ctx->plane_state->mcm_lut1d_enable;
mpc->funcs->set_movable_cm_location(mpc, MPCC_MOVABLE_CM_LOCATION_BEFORE, mpcc_id);
pipe_ctx->plane_state->mcm_location = MPCC_MOVABLE_CM_LOCATION_BEFORE;
*lut1d_xable = lut1d_enable ? MCM_LUT_ENABLE : MCM_LUT_DISABLE;
switch (shaper_3dlut_setting) {
case DC_CM2_SHAPER_3DLUT_SETTING_BYPASS_ALL:
*lut3d_xable = *shaper_xable = MCM_LUT_DISABLE;
break;
case DC_CM2_SHAPER_3DLUT_SETTING_ENABLE_SHAPER:
*lut3d_xable = MCM_LUT_DISABLE;
*shaper_xable = MCM_LUT_ENABLE;
break;
case DC_CM2_SHAPER_3DLUT_SETTING_ENABLE_SHAPER_3DLUT:
*lut3d_xable = *shaper_xable = MCM_LUT_ENABLE;
break;
}
}
void dcn401_populate_mcm_luts(struct dc *dc,
struct pipe_ctx *pipe_ctx,
struct dc_cm2_func_luts mcm_luts,
bool lut_bank_a)
{
struct dpp *dpp_base = pipe_ctx->plane_res.dpp;
struct hubp *hubp = pipe_ctx->plane_res.hubp;
int mpcc_id = hubp->inst;
struct mpc *mpc = dc->res_pool->mpc;
union mcm_lut_params m_lut_params;
enum dc_cm2_transfer_func_source lut3d_src = mcm_luts.lut3d_data.lut3d_src;
enum hubp_3dlut_fl_format format;
enum hubp_3dlut_fl_mode mode;
enum hubp_3dlut_fl_width width;
enum hubp_3dlut_fl_addressing_mode addr_mode;
enum hubp_3dlut_fl_crossbar_bit_slice crossbar_bit_slice_y_g;
enum hubp_3dlut_fl_crossbar_bit_slice crossbar_bit_slice_cb_b;
enum hubp_3dlut_fl_crossbar_bit_slice crossbar_bit_slice_cr_r;
enum MCM_LUT_XABLE shaper_xable = MCM_LUT_DISABLE;
enum MCM_LUT_XABLE lut3d_xable = MCM_LUT_DISABLE;
enum MCM_LUT_XABLE lut1d_xable = MCM_LUT_DISABLE;
bool is_17x17x17 = true;
bool rval;
dcn401_get_mcm_lut_xable_from_pipe_ctx(dc, pipe_ctx, &shaper_xable, &lut3d_xable, &lut1d_xable);
/* 1D LUT */
if (mcm_luts.lut1d_func) {
memset(&m_lut_params, 0, sizeof(m_lut_params));
if (mcm_luts.lut1d_func->type == TF_TYPE_HWPWL)
m_lut_params.pwl = &mcm_luts.lut1d_func->pwl;
else if (mcm_luts.lut1d_func->type == TF_TYPE_DISTRIBUTED_POINTS) {
rval = cm3_helper_translate_curve_to_hw_format(
mcm_luts.lut1d_func,
&dpp_base->regamma_params, false);
m_lut_params.pwl = rval ? &dpp_base->regamma_params : NULL;
}
if (m_lut_params.pwl) {
if (mpc->funcs->populate_lut)
mpc->funcs->populate_lut(mpc, MCM_LUT_1DLUT, m_lut_params, lut_bank_a, mpcc_id);
}
if (mpc->funcs->program_lut_mode)
mpc->funcs->program_lut_mode(mpc, MCM_LUT_1DLUT, lut1d_xable && m_lut_params.pwl, lut_bank_a, mpcc_id);
}
/* Shaper */
if (mcm_luts.shaper) {
memset(&m_lut_params, 0, sizeof(m_lut_params));
if (mcm_luts.shaper->type == TF_TYPE_HWPWL)
m_lut_params.pwl = &mcm_luts.shaper->pwl;
else if (mcm_luts.shaper->type == TF_TYPE_DISTRIBUTED_POINTS) {
ASSERT(false);
rval = cm3_helper_translate_curve_to_hw_format(
mcm_luts.shaper,
&dpp_base->regamma_params, true);
m_lut_params.pwl = rval ? &dpp_base->regamma_params : NULL;
}
if (m_lut_params.pwl) {
if (mpc->funcs->populate_lut)
mpc->funcs->populate_lut(mpc, MCM_LUT_SHAPER, m_lut_params, lut_bank_a, mpcc_id);
}
if (mpc->funcs->program_lut_mode)
mpc->funcs->program_lut_mode(mpc, MCM_LUT_SHAPER, shaper_xable, lut_bank_a, mpcc_id);
}
/* 3DLUT */
switch (lut3d_src) {
case DC_CM2_TRANSFER_FUNC_SOURCE_SYSMEM:
memset(&m_lut_params, 0, sizeof(m_lut_params));
if (hubp->funcs->hubp_enable_3dlut_fl)
hubp->funcs->hubp_enable_3dlut_fl(hubp, false);
if (mcm_luts.lut3d_data.lut3d_func && mcm_luts.lut3d_data.lut3d_func->state.bits.initialized) {
m_lut_params.lut3d = &mcm_luts.lut3d_data.lut3d_func->lut_3d;
if (mpc->funcs->populate_lut)
mpc->funcs->populate_lut(mpc, MCM_LUT_3DLUT, m_lut_params, lut_bank_a, mpcc_id);
if (mpc->funcs->program_lut_mode)
mpc->funcs->program_lut_mode(mpc, MCM_LUT_3DLUT, lut3d_xable, lut_bank_a,
mpcc_id);
}
break;
case DC_CM2_TRANSFER_FUNC_SOURCE_VIDMEM:
if (mpc->funcs->program_lut_read_write_control)
mpc->funcs->program_lut_read_write_control(mpc, MCM_LUT_3DLUT, lut_bank_a, mpcc_id);
if (mpc->funcs->program_lut_mode)
mpc->funcs->program_lut_mode(mpc, MCM_LUT_3DLUT, lut3d_xable, lut_bank_a, mpcc_id);
if (mpc->funcs->program_3dlut_size)
mpc->funcs->program_3dlut_size(mpc, is_17x17x17, mpcc_id);
if (hubp->funcs->hubp_program_3dlut_fl_addr)
hubp->funcs->hubp_program_3dlut_fl_addr(hubp, mcm_luts.lut3d_data.gpu_mem_params.addr);
switch (mcm_luts.lut3d_data.gpu_mem_params.layout) {
case DC_CM2_GPU_MEM_LAYOUT_3D_SWIZZLE_LINEAR_RGB:
mode = hubp_3dlut_fl_mode_native_1;
addr_mode = hubp_3dlut_fl_addressing_mode_sw_linear;
break;
case DC_CM2_GPU_MEM_LAYOUT_3D_SWIZZLE_LINEAR_BGR:
mode = hubp_3dlut_fl_mode_native_2;
addr_mode = hubp_3dlut_fl_addressing_mode_sw_linear;
break;
case DC_CM2_GPU_MEM_LAYOUT_1D_PACKED_LINEAR:
mode = hubp_3dlut_fl_mode_transform;
addr_mode = hubp_3dlut_fl_addressing_mode_simple_linear;
break;
default:
mode = hubp_3dlut_fl_mode_disable;
addr_mode = hubp_3dlut_fl_addressing_mode_sw_linear;
break;
}
if (hubp->funcs->hubp_program_3dlut_fl_mode)
hubp->funcs->hubp_program_3dlut_fl_mode(hubp, mode);
if (hubp->funcs->hubp_program_3dlut_fl_addressing_mode)
hubp->funcs->hubp_program_3dlut_fl_addressing_mode(hubp, addr_mode);
switch (mcm_luts.lut3d_data.gpu_mem_params.format_params.format) {
case DC_CM2_GPU_MEM_FORMAT_16161616_UNORM_12MSB:
default:
format = hubp_3dlut_fl_format_unorm_12msb_bitslice;
break;
case DC_CM2_GPU_MEM_FORMAT_16161616_UNORM_12LSB:
format = hubp_3dlut_fl_format_unorm_12lsb_bitslice;
break;
case DC_CM2_GPU_MEM_FORMAT_16161616_FLOAT_FP1_5_10:
format = hubp_3dlut_fl_format_float_fp1_5_10;
break;
}
if (hubp->funcs->hubp_program_3dlut_fl_format)
hubp->funcs->hubp_program_3dlut_fl_format(hubp, format);
if (hubp->funcs->hubp_update_3dlut_fl_bias_scale)
hubp->funcs->hubp_update_3dlut_fl_bias_scale(hubp,
mcm_luts.lut3d_data.gpu_mem_params.format_params.float_params.bias,
mcm_luts.lut3d_data.gpu_mem_params.format_params.float_params.scale);
switch (mcm_luts.lut3d_data.gpu_mem_params.component_order) {
case DC_CM2_GPU_MEM_PIXEL_COMPONENT_ORDER_RGBA:
default:
crossbar_bit_slice_cr_r = hubp_3dlut_fl_crossbar_bit_slice_0_15;
crossbar_bit_slice_y_g = hubp_3dlut_fl_crossbar_bit_slice_16_31;
crossbar_bit_slice_cb_b = hubp_3dlut_fl_crossbar_bit_slice_32_47;
break;
}
if (hubp->funcs->hubp_program_3dlut_fl_crossbar)
hubp->funcs->hubp_program_3dlut_fl_crossbar(hubp,
crossbar_bit_slice_y_g,
crossbar_bit_slice_cb_b,
crossbar_bit_slice_cr_r);
switch (mcm_luts.lut3d_data.gpu_mem_params.size) {
case DC_CM2_GPU_MEM_SIZE_171717:
default:
width = hubp_3dlut_fl_width_17;
break;
case DC_CM2_GPU_MEM_SIZE_TRANSFORMED:
width = hubp_3dlut_fl_width_transformed;
break;
}
if (hubp->funcs->hubp_program_3dlut_fl_width)
hubp->funcs->hubp_program_3dlut_fl_width(hubp, width);
if (mpc->funcs->update_3dlut_fast_load_select)
mpc->funcs->update_3dlut_fast_load_select(mpc, mpcc_id, hubp->inst);
if (hubp->funcs->hubp_enable_3dlut_fl)
hubp->funcs->hubp_enable_3dlut_fl(hubp, true);
else {
if (mpc->funcs->program_lut_mode) {
mpc->funcs->program_lut_mode(mpc, MCM_LUT_SHAPER, MCM_LUT_DISABLE, lut_bank_a, mpcc_id);
mpc->funcs->program_lut_mode(mpc, MCM_LUT_3DLUT, MCM_LUT_DISABLE, lut_bank_a, mpcc_id);
mpc->funcs->program_lut_mode(mpc, MCM_LUT_1DLUT, MCM_LUT_DISABLE, lut_bank_a, mpcc_id);
}
}
break;
}
}
void dcn401_trigger_3dlut_dma_load(struct dc *dc, struct pipe_ctx *pipe_ctx)
{
struct hubp *hubp = pipe_ctx->plane_res.hubp;
if (hubp->funcs->hubp_enable_3dlut_fl) {
hubp->funcs->hubp_enable_3dlut_fl(hubp, true);
}
}
bool dcn401_set_mcm_luts(struct pipe_ctx *pipe_ctx,
const struct dc_plane_state *plane_state)
{
struct dpp *dpp_base = pipe_ctx->plane_res.dpp;
int mpcc_id = pipe_ctx->plane_res.hubp->inst;
struct dc *dc = pipe_ctx->stream_res.opp->ctx->dc;
struct mpc *mpc = dc->res_pool->mpc;
bool result;
const struct pwl_params *lut_params = NULL;
bool rval;
if (plane_state->mcm_luts.lut3d_data.lut3d_src == DC_CM2_TRANSFER_FUNC_SOURCE_VIDMEM) {
dcn401_populate_mcm_luts(dc, pipe_ctx, plane_state->mcm_luts, plane_state->lut_bank_a);
return true;
}
mpc->funcs->set_movable_cm_location(mpc, MPCC_MOVABLE_CM_LOCATION_BEFORE, mpcc_id);
pipe_ctx->plane_state->mcm_location = MPCC_MOVABLE_CM_LOCATION_BEFORE;
// 1D LUT
if (plane_state->blend_tf.type == TF_TYPE_HWPWL)
lut_params = &plane_state->blend_tf.pwl;
else if (plane_state->blend_tf.type == TF_TYPE_DISTRIBUTED_POINTS) {
rval = cm3_helper_translate_curve_to_hw_format(&plane_state->blend_tf,
&dpp_base->regamma_params, false);
lut_params = rval ? &dpp_base->regamma_params : NULL;
}
result = mpc->funcs->program_1dlut(mpc, lut_params, mpcc_id);
lut_params = NULL;
// Shaper
if (plane_state->in_shaper_func.type == TF_TYPE_HWPWL)
lut_params = &plane_state->in_shaper_func.pwl;
else if (plane_state->in_shaper_func.type == TF_TYPE_DISTRIBUTED_POINTS) {
// TODO: dpp_base replace
rval = cm3_helper_translate_curve_to_hw_format(&plane_state->in_shaper_func,
&dpp_base->shaper_params, true);
lut_params = rval ? &dpp_base->shaper_params : NULL;
}
result &= mpc->funcs->program_shaper(mpc, lut_params, mpcc_id);
// 3D
if (mpc->funcs->program_3dlut) {
if (plane_state->lut3d_func.state.bits.initialized == 1)
result &= mpc->funcs->program_3dlut(mpc, &plane_state->lut3d_func.lut_3d, mpcc_id);
else
result &= mpc->funcs->program_3dlut(mpc, NULL, mpcc_id);
}
return result;
}
bool dcn401_set_output_transfer_func(struct dc *dc,
struct pipe_ctx *pipe_ctx,
const struct dc_stream_state *stream)
{
int mpcc_id = pipe_ctx->plane_res.hubp->inst;
struct mpc *mpc = pipe_ctx->stream_res.opp->ctx->dc->res_pool->mpc;
const struct pwl_params *params = NULL;
bool ret = false;
/* program OGAM or 3DLUT only for the top pipe*/
if (resource_is_pipe_type(pipe_ctx, OPP_HEAD)) {
/*program shaper and 3dlut in MPC*/
ret = dcn32_set_mpc_shaper_3dlut(pipe_ctx, stream);
if (ret == false && mpc->funcs->set_output_gamma) {
if (stream->out_transfer_func.type == TF_TYPE_HWPWL)
params = &stream->out_transfer_func.pwl;
else if (pipe_ctx->stream->out_transfer_func.type ==
TF_TYPE_DISTRIBUTED_POINTS &&
cm3_helper_translate_curve_to_hw_format(
&stream->out_transfer_func,
&mpc->blender_params, false))
params = &mpc->blender_params;
/* there are no ROM LUTs in OUTGAM */
if (stream->out_transfer_func.type == TF_TYPE_PREDEFINED)
BREAK_TO_DEBUGGER();
}
}
if (mpc->funcs->set_output_gamma)
mpc->funcs->set_output_gamma(mpc, mpcc_id, params);
return ret;
}
void dcn401_calculate_dccg_tmds_div_value(struct pipe_ctx *pipe_ctx,
unsigned int *tmds_div)
{
struct dc_stream_state *stream = pipe_ctx->stream;
if (dc_is_tmds_signal(stream->signal) || dc_is_virtual_signal(stream->signal)) {
if (stream->timing.pixel_encoding == PIXEL_ENCODING_YCBCR420)
*tmds_div = PIXEL_RATE_DIV_BY_2;
else
*tmds_div = PIXEL_RATE_DIV_BY_4;
} else {
*tmds_div = PIXEL_RATE_DIV_BY_1;
}
if (*tmds_div == PIXEL_RATE_DIV_NA)
ASSERT(false);
}
static void enable_stream_timing_calc(
struct pipe_ctx *pipe_ctx,
struct dc_state *context,
struct dc *dc,
unsigned int *tmds_div,
int *opp_inst,
int *opp_cnt,
struct pipe_ctx *opp_heads[MAX_PIPES],
bool *manual_mode,
struct drr_params *params,
unsigned int *event_triggers)
{
struct dc_stream_state *stream = pipe_ctx->stream;
int i;
if (dc_is_tmds_signal(stream->signal) || dc_is_virtual_signal(stream->signal))
dcn401_calculate_dccg_tmds_div_value(pipe_ctx, tmds_div);
*opp_cnt = resource_get_opp_heads_for_otg_master(pipe_ctx, &context->res_ctx, opp_heads);
for (i = 0; i < *opp_cnt; i++)
opp_inst[i] = opp_heads[i]->stream_res.opp->inst;
if (dc_is_tmds_signal(stream->signal)) {
stream->link->phy_state.symclk_ref_cnts.otg = 1;
if (stream->link->phy_state.symclk_state == SYMCLK_OFF_TX_OFF)
stream->link->phy_state.symclk_state = SYMCLK_ON_TX_OFF;
else
stream->link->phy_state.symclk_state = SYMCLK_ON_TX_ON;
}
params->vertical_total_min = stream->adjust.v_total_min;
params->vertical_total_max = stream->adjust.v_total_max;
params->vertical_total_mid = stream->adjust.v_total_mid;
params->vertical_total_mid_frame_num = stream->adjust.v_total_mid_frame_num;
// DRR should set trigger event to monitor surface update event
if (stream->adjust.v_total_min != 0 && stream->adjust.v_total_max != 0)
*event_triggers = 0x80;
}
enum dc_status dcn401_enable_stream_timing(
struct pipe_ctx *pipe_ctx,
struct dc_state *context,
struct dc *dc)
{
struct dce_hwseq *hws = dc->hwseq;
struct dc_stream_state *stream = pipe_ctx->stream;
struct drr_params params = {0};
unsigned int event_triggers = 0;
int opp_cnt = 1;
int opp_inst[MAX_PIPES] = {0};
struct pipe_ctx *opp_heads[MAX_PIPES] = {0};
struct dc_crtc_timing patched_crtc_timing = stream->timing;
bool manual_mode = false;
unsigned int tmds_div = PIXEL_RATE_DIV_NA;
unsigned int unused_div = PIXEL_RATE_DIV_NA;
int odm_slice_width;
int last_odm_slice_width;
int i;
if (!resource_is_pipe_type(pipe_ctx, OTG_MASTER))
return DC_OK;
enable_stream_timing_calc(pipe_ctx, context, dc, &tmds_div, opp_inst,
&opp_cnt, opp_heads, &manual_mode, &params, &event_triggers);
if (dc->res_pool->dccg->funcs->set_pixel_rate_div) {
dc->res_pool->dccg->funcs->set_pixel_rate_div(
dc->res_pool->dccg, pipe_ctx->stream_res.tg->inst,
tmds_div, unused_div);
}
/* TODO check if timing_changed, disable stream if timing changed */
if (opp_cnt > 1) {
odm_slice_width = resource_get_odm_slice_dst_width(pipe_ctx, false);
last_odm_slice_width = resource_get_odm_slice_dst_width(pipe_ctx, true);
pipe_ctx->stream_res.tg->funcs->set_odm_combine(
pipe_ctx->stream_res.tg,
opp_inst, opp_cnt,
odm_slice_width, last_odm_slice_width);
}
/* set DTBCLK_P */
if (dc->res_pool->dccg->funcs->set_dtbclk_p_src) {
if (dc_is_dp_signal(stream->signal) || dc_is_virtual_signal(stream->signal)) {
dc->res_pool->dccg->funcs->set_dtbclk_p_src(dc->res_pool->dccg, DPREFCLK, pipe_ctx->stream_res.tg->inst);
}
}
/* HW program guide assume display already disable
* by unplug sequence. OTG assume stop.
*/
pipe_ctx->stream_res.tg->funcs->enable_optc_clock(pipe_ctx->stream_res.tg, true);
if (false == pipe_ctx->clock_source->funcs->program_pix_clk(
pipe_ctx->clock_source,
&pipe_ctx->stream_res.pix_clk_params,
dc->link_srv->dp_get_encoding_format(&pipe_ctx->link_config.dp_link_settings),
&pipe_ctx->pll_settings)) {
BREAK_TO_DEBUGGER();
return DC_ERROR_UNEXPECTED;
}
if (dc->hwseq->funcs.PLAT_58856_wa && (!dc_is_dp_signal(stream->signal)))
dc->hwseq->funcs.PLAT_58856_wa(context, pipe_ctx);
/* if we are borrowing from hblank, h_addressable needs to be adjusted */
if (dc->debug.enable_hblank_borrow)
patched_crtc_timing.h_addressable = patched_crtc_timing.h_addressable + pipe_ctx->hblank_borrow;
pipe_ctx->stream_res.tg->funcs->program_timing(
pipe_ctx->stream_res.tg,
&patched_crtc_timing,
(unsigned int)pipe_ctx->global_sync.dcn4x.vready_offset_pixels,
(unsigned int)pipe_ctx->global_sync.dcn4x.vstartup_lines,
(unsigned int)pipe_ctx->global_sync.dcn4x.vupdate_offset_pixels,
(unsigned int)pipe_ctx->global_sync.dcn4x.vupdate_vupdate_width_pixels,
(unsigned int)pipe_ctx->global_sync.dcn4x.pstate_keepout_start_lines,
pipe_ctx->stream->signal,
true);
for (i = 0; i < opp_cnt; i++) {
opp_heads[i]->stream_res.opp->funcs->opp_pipe_clock_control(
opp_heads[i]->stream_res.opp,
true);
opp_heads[i]->stream_res.opp->funcs->opp_program_left_edge_extra_pixel(
opp_heads[i]->stream_res.opp,
stream->timing.pixel_encoding,
resource_is_pipe_type(opp_heads[i], OTG_MASTER));
}
pipe_ctx->stream_res.opp->funcs->opp_pipe_clock_control(
pipe_ctx->stream_res.opp,
true);
hws->funcs.blank_pixel_data(dc, pipe_ctx, true);
/* VTG is within DCHUB command block. DCFCLK is always on */
if (false == pipe_ctx->stream_res.tg->funcs->enable_crtc(pipe_ctx->stream_res.tg)) {
BREAK_TO_DEBUGGER();
return DC_ERROR_UNEXPECTED;
}
hws->funcs.wait_for_blank_complete(pipe_ctx->stream_res.opp);
set_drr_and_clear_adjust_pending(pipe_ctx, stream, &params);
/* Event triggers and num frames initialized for DRR, but can be
* later updated for PSR use. Note DRR trigger events are generated
* regardless of whether num frames met.
*/
if (pipe_ctx->stream_res.tg->funcs->set_static_screen_control)
pipe_ctx->stream_res.tg->funcs->set_static_screen_control(
pipe_ctx->stream_res.tg, event_triggers, 2);
/* TODO program crtc source select for non-virtual signal*/
/* TODO program FMT */
/* TODO setup link_enc */
/* TODO set stream attributes */
/* TODO program audio */
/* TODO enable stream if timing changed */
/* TODO unblank stream if DP */
if (dc_state_get_pipe_subvp_type(context, pipe_ctx) == SUBVP_PHANTOM) {
if (pipe_ctx->stream_res.tg->funcs->phantom_crtc_post_enable)
pipe_ctx->stream_res.tg->funcs->phantom_crtc_post_enable(pipe_ctx->stream_res.tg);
}
return DC_OK;
}
static enum phyd32clk_clock_source get_phyd32clk_src(struct dc_link *link)
{
switch (link->link_enc->transmitter) {
case TRANSMITTER_UNIPHY_A:
return PHYD32CLKA;
case TRANSMITTER_UNIPHY_B:
return PHYD32CLKB;
case TRANSMITTER_UNIPHY_C:
return PHYD32CLKC;
case TRANSMITTER_UNIPHY_D:
return PHYD32CLKD;
case TRANSMITTER_UNIPHY_E:
return PHYD32CLKE;
default:
return PHYD32CLKA;
}
}
static void dcn401_enable_stream_calc(
struct pipe_ctx *pipe_ctx,
int *dp_hpo_inst,
enum phyd32clk_clock_source *phyd32clk,
unsigned int *tmds_div,
uint32_t *early_control)
{
struct dc *dc = pipe_ctx->stream->ctx->dc;
struct dc_crtc_timing *timing = &pipe_ctx->stream->timing;
enum dc_lane_count lane_count =
pipe_ctx->stream->link->cur_link_settings.lane_count;
uint32_t active_total_with_borders;
if (dc->link_srv->dp_is_128b_132b_signal(pipe_ctx))
*dp_hpo_inst = pipe_ctx->stream_res.hpo_dp_stream_enc->inst;
*phyd32clk = get_phyd32clk_src(pipe_ctx->stream->link);
if (dc_is_tmds_signal(pipe_ctx->stream->signal))
dcn401_calculate_dccg_tmds_div_value(pipe_ctx, tmds_div);
else
*tmds_div = PIXEL_RATE_DIV_BY_1;
/* enable early control to avoid corruption on DP monitor*/
active_total_with_borders =
timing->h_addressable
+ timing->h_border_left
+ timing->h_border_right;
if (lane_count != 0)
*early_control = active_total_with_borders % lane_count;
if (*early_control == 0)
*early_control = lane_count;
}
void dcn401_enable_stream(struct pipe_ctx *pipe_ctx)
{
uint32_t early_control = 0;
struct timing_generator *tg = pipe_ctx->stream_res.tg;
struct dc_link *link = pipe_ctx->stream->link;
const struct link_hwss *link_hwss = get_link_hwss(link, &pipe_ctx->link_res);
struct dc *dc = pipe_ctx->stream->ctx->dc;
struct dccg *dccg = dc->res_pool->dccg;
enum phyd32clk_clock_source phyd32clk;
int dp_hpo_inst = 0;
unsigned int tmds_div = PIXEL_RATE_DIV_NA;
unsigned int unused_div = PIXEL_RATE_DIV_NA;
struct link_encoder *link_enc = pipe_ctx->link_res.dio_link_enc;
struct stream_encoder *stream_enc = pipe_ctx->stream_res.stream_enc;
if (!dc->config.unify_link_enc_assignment)
link_enc = link_enc_cfg_get_link_enc(link);
dcn401_enable_stream_calc(pipe_ctx, &dp_hpo_inst, &phyd32clk,
&tmds_div, &early_control);
if (dc_is_dp_signal(pipe_ctx->stream->signal) || dc_is_virtual_signal(pipe_ctx->stream->signal)) {
if (dc->link_srv->dp_is_128b_132b_signal(pipe_ctx)) {
dccg->funcs->set_dpstreamclk(dccg, DPREFCLK, tg->inst, dp_hpo_inst);
dccg->funcs->enable_symclk32_se(dccg, dp_hpo_inst, phyd32clk);
} else {
dccg->funcs->enable_symclk_se(dccg, stream_enc->stream_enc_inst,
link_enc->transmitter - TRANSMITTER_UNIPHY_A);
}
}
if (dc->res_pool->dccg->funcs->set_pixel_rate_div) {
dc->res_pool->dccg->funcs->set_pixel_rate_div(
dc->res_pool->dccg,
pipe_ctx->stream_res.tg->inst,
tmds_div,
unused_div);
}
link_hwss->setup_stream_encoder(pipe_ctx);
if (pipe_ctx->plane_state && pipe_ctx->plane_state->flip_immediate != 1) {
if (dc->hwss.program_dmdata_engine)
dc->hwss.program_dmdata_engine(pipe_ctx);
}
dc->hwss.update_info_frame(pipe_ctx);
if (dc_is_dp_signal(pipe_ctx->stream->signal))
dc->link_srv->dp_trace_source_sequence(link, DPCD_SOURCE_SEQ_AFTER_UPDATE_INFO_FRAME);
tg->funcs->set_early_control(tg, early_control);
}
void dcn401_setup_hpo_hw_control(const struct dce_hwseq *hws, bool enable)
{
REG_UPDATE(HPO_TOP_HW_CONTROL, HPO_IO_EN, enable);
}
void adjust_hotspot_between_slices_for_2x_magnify(uint32_t cursor_width, struct dc_cursor_position *pos_cpy)
{
if (cursor_width <= 128) {
pos_cpy->x_hotspot /= 2;
pos_cpy->x_hotspot += 1;
} else {
pos_cpy->x_hotspot /= 2;
pos_cpy->x_hotspot += 2;
}
}
static void disable_link_output_symclk_on_tx_off(struct dc_link *link, enum dp_link_encoding link_encoding)
{
struct dc *dc = link->ctx->dc;
struct pipe_ctx *pipe_ctx = NULL;
uint8_t i;
for (i = 0; i < MAX_PIPES; i++) {
pipe_ctx = &dc->current_state->res_ctx.pipe_ctx[i];
if (pipe_ctx->stream && pipe_ctx->stream->link == link && pipe_ctx->top_pipe == NULL) {
pipe_ctx->clock_source->funcs->program_pix_clk(
pipe_ctx->clock_source,
&pipe_ctx->stream_res.pix_clk_params,
link_encoding,
&pipe_ctx->pll_settings);
break;
}
}
}
void dcn401_disable_link_output(struct dc_link *link,
const struct link_resource *link_res,
enum signal_type signal)
{
struct dc *dc = link->ctx->dc;
const struct link_hwss *link_hwss = get_link_hwss(link, link_res);
struct dmcu *dmcu = dc->res_pool->dmcu;
if (signal == SIGNAL_TYPE_EDP &&
link->dc->hwss.edp_backlight_control &&
!link->skip_implict_edp_power_control)
link->dc->hwss.edp_backlight_control(link, false);
else if (dmcu != NULL && dmcu->funcs->lock_phy)
dmcu->funcs->lock_phy(dmcu);
if (dc_is_tmds_signal(signal) && link->phy_state.symclk_ref_cnts.otg > 0) {
disable_link_output_symclk_on_tx_off(link, DP_UNKNOWN_ENCODING);
link->phy_state.symclk_state = SYMCLK_ON_TX_OFF;
} else {
link_hwss->disable_link_output(link, link_res, signal);
link->phy_state.symclk_state = SYMCLK_OFF_TX_OFF;
}
if (signal == SIGNAL_TYPE_EDP &&
link->dc->hwss.edp_backlight_control &&
!link->skip_implict_edp_power_control)
link->dc->hwss.edp_power_control(link, false);
else if (dmcu != NULL && dmcu->funcs->lock_phy)
dmcu->funcs->unlock_phy(dmcu);
dc->link_srv->dp_trace_source_sequence(link, DPCD_SOURCE_SEQ_AFTER_DISABLE_LINK_PHY);
}
void dcn401_set_cursor_position(struct pipe_ctx *pipe_ctx)
{
struct dc_cursor_position pos_cpy = pipe_ctx->stream->cursor_position;
struct hubp *hubp = pipe_ctx->plane_res.hubp;
struct dpp *dpp = pipe_ctx->plane_res.dpp;
struct dc_cursor_mi_param param = {
.pixel_clk_khz = pipe_ctx->stream->timing.pix_clk_100hz / 10,
.ref_clk_khz = pipe_ctx->stream->ctx->dc->res_pool->ref_clocks.dchub_ref_clock_inKhz,
.viewport = pipe_ctx->plane_res.scl_data.viewport,
.recout = pipe_ctx->plane_res.scl_data.recout,
.h_scale_ratio = pipe_ctx->plane_res.scl_data.ratios.horz,
.v_scale_ratio = pipe_ctx->plane_res.scl_data.ratios.vert,
.rotation = pipe_ctx->plane_state->rotation,
.mirror = pipe_ctx->plane_state->horizontal_mirror,
.stream = pipe_ctx->stream
};
struct rect odm_slice_src = { 0 };
bool odm_combine_on = (pipe_ctx->next_odm_pipe != NULL) ||
(pipe_ctx->prev_odm_pipe != NULL);
int prev_odm_width = 0;
struct pipe_ctx *prev_odm_pipe = NULL;
bool mpc_combine_on = false;
int bottom_pipe_x_pos = 0;
int x_pos = pos_cpy.x;
int y_pos = pos_cpy.y;
int recout_x_pos = 0;
int recout_y_pos = 0;
if ((pipe_ctx->top_pipe != NULL) || (pipe_ctx->bottom_pipe != NULL)) {
if ((pipe_ctx->plane_state->src_rect.width != pipe_ctx->plane_res.scl_data.viewport.width) ||
(pipe_ctx->plane_state->src_rect.height != pipe_ctx->plane_res.scl_data.viewport.height)) {
mpc_combine_on = true;
}
}
/* DCN4 moved cursor composition after Scaler, so in HW it is in
* recout space and for HW Cursor position programming need to
* translate to recout space.
*
* Cursor X and Y position programmed into HW can't be negative,
* in fact it is X, Y coordinate shifted for the HW Cursor Hot spot
* position that goes into HW X and Y coordinates while HW Hot spot
* X and Y coordinates are length relative to the cursor top left
* corner, hotspot must be smaller than the cursor size.
*
* DMs/DC interface for Cursor position is in stream->src space, and
* DMs supposed to transform Cursor coordinates to stream->src space,
* then here we need to translate Cursor coordinates to stream->dst
* space, as now in HW, Cursor coordinates are in per pipe recout
* space, and for the given pipe valid coordinates are only in range
* from 0,0 - recout width, recout height space.
* If certain pipe combining is in place, need to further adjust per
* pipe to make sure each pipe enabling cursor on its part of the
* screen.
*/
x_pos = pipe_ctx->stream->dst.x + x_pos * pipe_ctx->stream->dst.width /
pipe_ctx->stream->src.width;
y_pos = pipe_ctx->stream->dst.y + y_pos * pipe_ctx->stream->dst.height /
pipe_ctx->stream->src.height;
/* If the cursor's source viewport is clipped then we need to
* translate the cursor to appear in the correct position on
* the screen.
*
* This translation isn't affected by scaling so it needs to be
* done *after* we adjust the position for the scale factor.
*
* This is only done by opt-in for now since there are still
* some usecases like tiled display that might enable the
* cursor on both streams while expecting dc to clip it.
*/
if (pos_cpy.translate_by_source) {
x_pos += pipe_ctx->plane_state->src_rect.x;
y_pos += pipe_ctx->plane_state->src_rect.y;
}
/* Adjust for ODM Combine
* next/prev_odm_offset is to account for scaled modes that have underscan
*/
if (odm_combine_on) {
prev_odm_pipe = pipe_ctx->prev_odm_pipe;
while (prev_odm_pipe != NULL) {
odm_slice_src = resource_get_odm_slice_src_rect(prev_odm_pipe);
prev_odm_width += odm_slice_src.width;
prev_odm_pipe = prev_odm_pipe->prev_odm_pipe;
}
x_pos -= (prev_odm_width);
}
/* If the position is negative then we need to add to the hotspot
* to fix cursor size between ODM slices
*/
if (x_pos < 0) {
pos_cpy.x_hotspot -= x_pos;
if (hubp->curs_attr.attribute_flags.bits.ENABLE_MAGNIFICATION)
adjust_hotspot_between_slices_for_2x_magnify(hubp->curs_attr.width, &pos_cpy);
x_pos = 0;
}
if (y_pos < 0) {
pos_cpy.y_hotspot -= y_pos;
y_pos = 0;
}
/* If the position on bottom MPC pipe is negative then we need to add to the hotspot and
* adjust x_pos on bottom pipe to make cursor visible when crossing between MPC slices.
*/
if (mpc_combine_on &&
pipe_ctx->top_pipe &&
(pipe_ctx == pipe_ctx->top_pipe->bottom_pipe)) {
bottom_pipe_x_pos = x_pos - pipe_ctx->plane_res.scl_data.recout.x;
if (bottom_pipe_x_pos < 0) {
x_pos = pipe_ctx->plane_res.scl_data.recout.x;
pos_cpy.x_hotspot -= bottom_pipe_x_pos;
if (hubp->curs_attr.attribute_flags.bits.ENABLE_MAGNIFICATION)
adjust_hotspot_between_slices_for_2x_magnify(hubp->curs_attr.width, &pos_cpy);
}
}
pos_cpy.x = (uint32_t)x_pos;
pos_cpy.y = (uint32_t)y_pos;
if (pos_cpy.enable && resource_can_pipe_disable_cursor(pipe_ctx))
pos_cpy.enable = false;
x_pos = pos_cpy.x - param.recout.x;
y_pos = pos_cpy.y - param.recout.y;
recout_x_pos = x_pos - pos_cpy.x_hotspot;
recout_y_pos = y_pos - pos_cpy.y_hotspot;
if (recout_x_pos >= (int)param.recout.width)
pos_cpy.enable = false; /* not visible beyond right edge*/
if (recout_y_pos >= (int)param.recout.height)
pos_cpy.enable = false; /* not visible beyond bottom edge*/
if (recout_x_pos + (int)hubp->curs_attr.width <= 0)
pos_cpy.enable = false; /* not visible beyond left edge*/
if (recout_y_pos + (int)hubp->curs_attr.height <= 0)
pos_cpy.enable = false; /* not visible beyond top edge*/
hubp->funcs->set_cursor_position(hubp, &pos_cpy, &param);
dpp->funcs->set_cursor_position(dpp, &pos_cpy, &param, hubp->curs_attr.width, hubp->curs_attr.height);
}
static bool dcn401_check_no_memory_request_for_cab(struct dc *dc)
{
int i;
/* First, check no-memory-request case */
for (i = 0; i < dc->current_state->stream_count; i++) {
if ((dc->current_state->stream_status[i].plane_count) &&
(dc->current_state->streams[i]->link->psr_settings.psr_version == DC_PSR_VERSION_UNSUPPORTED))
/* Fail eligibility on a visible stream */
return false;
}
return true;
}
static uint32_t dcn401_calculate_cab_allocation(struct dc *dc, struct dc_state *ctx)
{
int i;
uint8_t num_ways = 0;
uint32_t mall_ss_size_bytes = 0;
mall_ss_size_bytes = ctx->bw_ctx.bw.dcn.mall_ss_size_bytes;
// TODO add additional logic for PSR active stream exclusion optimization
// mall_ss_psr_active_size_bytes = ctx->bw_ctx.bw.dcn.mall_ss_psr_active_size_bytes;
// Include cursor size for CAB allocation
for (i = 0; i < dc->res_pool->pipe_count; i++) {
struct pipe_ctx *pipe = &ctx->res_ctx.pipe_ctx[i];
if (!pipe->stream || !pipe->plane_state)
continue;
mall_ss_size_bytes += dcn32_helper_calculate_mall_bytes_for_cursor(dc, pipe, false);
}
// Convert number of cache lines required to number of ways
if (dc->debug.force_mall_ss_num_ways > 0)
num_ways = dc->debug.force_mall_ss_num_ways;
else if (dc->res_pool->funcs->calculate_mall_ways_from_bytes)
num_ways = dc->res_pool->funcs->calculate_mall_ways_from_bytes(dc, mall_ss_size_bytes);
else
num_ways = 0;
return num_ways;
}
bool dcn401_apply_idle_power_optimizations(struct dc *dc, bool enable)
{
union dmub_rb_cmd cmd;
uint8_t ways, i;
int j;
bool mall_ss_unsupported = false;
struct dc_plane_state *plane = NULL;
if (!dc->ctx->dmub_srv || !dc->current_state)
return false;
for (i = 0; i < dc->current_state->stream_count; i++) {
/* MALL SS messaging is not supported with PSR at this time */
if (dc->current_state->streams[i] != NULL &&
dc->current_state->streams[i]->link->psr_settings.psr_version != DC_PSR_VERSION_UNSUPPORTED) {
DC_LOG_MALL("MALL SS not supported with PSR at this time\n");
return false;
}
}
memset(&cmd, 0, sizeof(cmd));
cmd.cab.header.type = DMUB_CMD__CAB_FOR_SS;
cmd.cab.header.payload_bytes = sizeof(cmd.cab) - sizeof(cmd.cab.header);
if (enable) {
if (dcn401_check_no_memory_request_for_cab(dc)) {
/* 1. Check no memory request case for CAB.
* If no memory request case, send CAB_ACTION NO_DCN_REQ DMUB message
*/
DC_LOG_MALL("sending CAB action NO_DCN_REQ\n");
cmd.cab.header.sub_type = DMUB_CMD__CAB_NO_DCN_REQ;
} else {
/* 2. Check if all surfaces can fit in CAB.
* If surfaces can fit into CAB, send CAB_ACTION_ALLOW DMUB message
* and configure HUBP's to fetch from MALL
*/
ways = dcn401_calculate_cab_allocation(dc, dc->current_state);
/* MALL not supported with Stereo3D or TMZ surface. If any plane is using stereo,
* or TMZ surface, don't try to enter MALL.
*/
for (i = 0; i < dc->current_state->stream_count; i++) {
for (j = 0; j < dc->current_state->stream_status[i].plane_count; j++) {
plane = dc->current_state->stream_status[i].plane_states[j];
if (plane->address.type == PLN_ADDR_TYPE_GRPH_STEREO ||
plane->address.tmz_surface) {
mall_ss_unsupported = true;
break;
}
}
if (mall_ss_unsupported)
break;
}
if (ways <= dc->caps.cache_num_ways && !mall_ss_unsupported) {
cmd.cab.header.sub_type = DMUB_CMD__CAB_DCN_SS_FIT_IN_CAB;
cmd.cab.cab_alloc_ways = ways;
DC_LOG_MALL("cab allocation: %d ways. CAB action: DCN_SS_FIT_IN_CAB\n", ways);
} else {
cmd.cab.header.sub_type = DMUB_CMD__CAB_DCN_SS_NOT_FIT_IN_CAB;
DC_LOG_MALL("frame does not fit in CAB: %d ways required. CAB action: DCN_SS_NOT_FIT_IN_CAB\n", ways);
}
}
} else {
/* Disable CAB */
cmd.cab.header.sub_type = DMUB_CMD__CAB_NO_IDLE_OPTIMIZATION;
DC_LOG_MALL("idle optimization disabled\n");
}
dm_execute_dmub_cmd(dc->ctx, &cmd, DM_DMUB_WAIT_TYPE_WAIT);
return true;
}
void dcn401_wait_for_dcc_meta_propagation(const struct dc *dc,
const struct pipe_ctx *top_pipe)
{
bool is_wait_needed = false;
const struct pipe_ctx *pipe_ctx = top_pipe;
/* check if any surfaces are updating address while using flip immediate and dcc */
while (pipe_ctx != NULL) {
if (pipe_ctx->plane_state &&
pipe_ctx->plane_state->dcc.enable &&
pipe_ctx->plane_state->flip_immediate &&
pipe_ctx->plane_state->update_flags.bits.addr_update) {
is_wait_needed = true;
break;
}
/* check next pipe */
pipe_ctx = pipe_ctx->bottom_pipe;
}
if (is_wait_needed && dc->debug.dcc_meta_propagation_delay_us > 0) {
udelay(dc->debug.dcc_meta_propagation_delay_us);
}
}
void dcn401_prepare_bandwidth(struct dc *dc,
struct dc_state *context)
{
struct hubbub *hubbub = dc->res_pool->hubbub;
bool p_state_change_support = context->bw_ctx.bw.dcn.clk.p_state_change_support;
unsigned int compbuf_size = 0;
/* Any transition into P-State support should disable MCLK switching first to avoid hangs */
if (p_state_change_support) {
dc->optimized_required = true;
context->bw_ctx.bw.dcn.clk.p_state_change_support = false;
}
if (dc->clk_mgr->dc_mode_softmax_enabled)
if (dc->clk_mgr->clks.dramclk_khz <= dc->clk_mgr->bw_params->dc_mode_softmax_memclk * 1000 &&
context->bw_ctx.bw.dcn.clk.dramclk_khz > dc->clk_mgr->bw_params->dc_mode_softmax_memclk * 1000)
dc->clk_mgr->funcs->set_max_memclk(dc->clk_mgr, dc->clk_mgr->bw_params->clk_table.entries[dc->clk_mgr->bw_params->clk_table.num_entries - 1].memclk_mhz);
/* Increase clocks */
dc->clk_mgr->funcs->update_clocks(
dc->clk_mgr,
context,
false);
/* program dchubbub watermarks:
* For assigning wm_optimized_required, use |= operator since we don't want
* to clear the value if the optimize has not happened yet
*/
dc->wm_optimized_required |= hubbub->funcs->program_watermarks(hubbub,
&context->bw_ctx.bw.dcn.watermarks,
dc->res_pool->ref_clocks.dchub_ref_clock_inKhz / 1000,
false);
/* update timeout thresholds */
if (hubbub->funcs->program_arbiter) {
dc->wm_optimized_required |= hubbub->funcs->program_arbiter(hubbub, &context->bw_ctx.bw.dcn.arb_regs, false);
}
/* decrease compbuf size */
if (hubbub->funcs->program_compbuf_segments) {
compbuf_size = context->bw_ctx.bw.dcn.arb_regs.compbuf_size;
dc->wm_optimized_required |= (compbuf_size != dc->current_state->bw_ctx.bw.dcn.arb_regs.compbuf_size);
hubbub->funcs->program_compbuf_segments(hubbub, compbuf_size, false);
}
if (dc->debug.fams2_config.bits.enable) {
dcn401_fams2_global_control_lock(dc, context, true);
dcn401_fams2_update_config(dc, context, false);
dcn401_fams2_global_control_lock(dc, context, false);
}
if (p_state_change_support != context->bw_ctx.bw.dcn.clk.p_state_change_support) {
/* After disabling P-State, restore the original value to ensure we get the correct P-State
* on the next optimize. */
context->bw_ctx.bw.dcn.clk.p_state_change_support = p_state_change_support;
}
}
void dcn401_optimize_bandwidth(
struct dc *dc,
struct dc_state *context)
{
int i;
struct hubbub *hubbub = dc->res_pool->hubbub;
/* enable fams2 if needed */
if (dc->debug.fams2_config.bits.enable) {
dcn401_fams2_global_control_lock(dc, context, true);
dcn401_fams2_update_config(dc, context, true);
dcn401_fams2_global_control_lock(dc, context, false);
}
/* program dchubbub watermarks */
hubbub->funcs->program_watermarks(hubbub,
&context->bw_ctx.bw.dcn.watermarks,
dc->res_pool->ref_clocks.dchub_ref_clock_inKhz / 1000,
true);
/* update timeout thresholds */
if (hubbub->funcs->program_arbiter) {
hubbub->funcs->program_arbiter(hubbub, &context->bw_ctx.bw.dcn.arb_regs, true);
}
if (dc->clk_mgr->dc_mode_softmax_enabled)
if (dc->clk_mgr->clks.dramclk_khz > dc->clk_mgr->bw_params->dc_mode_softmax_memclk * 1000 &&
context->bw_ctx.bw.dcn.clk.dramclk_khz <= dc->clk_mgr->bw_params->dc_mode_softmax_memclk * 1000)
dc->clk_mgr->funcs->set_max_memclk(dc->clk_mgr, dc->clk_mgr->bw_params->dc_mode_softmax_memclk);
/* increase compbuf size */
if (hubbub->funcs->program_compbuf_segments)
hubbub->funcs->program_compbuf_segments(hubbub, context->bw_ctx.bw.dcn.arb_regs.compbuf_size, true);
dc->clk_mgr->funcs->update_clocks(
dc->clk_mgr,
context,
true);
if (context->bw_ctx.bw.dcn.clk.zstate_support == DCN_ZSTATE_SUPPORT_ALLOW) {
for (i = 0; i < dc->res_pool->pipe_count; ++i) {
struct pipe_ctx *pipe_ctx = &context->res_ctx.pipe_ctx[i];
if (pipe_ctx->stream && pipe_ctx->plane_res.hubp->funcs->program_extended_blank
&& pipe_ctx->stream->adjust.v_total_min == pipe_ctx->stream->adjust.v_total_max
&& pipe_ctx->stream->adjust.v_total_max > pipe_ctx->stream->timing.v_total)
pipe_ctx->plane_res.hubp->funcs->program_extended_blank(pipe_ctx->plane_res.hubp,
pipe_ctx->dlg_regs.min_dst_y_next_start);
}
}
}
void dcn401_fams2_global_control_lock(struct dc *dc,
struct dc_state *context,
bool lock)
{
/* use always for now */
union dmub_inbox0_cmd_lock_hw hw_lock_cmd = { 0 };
if (!dc->ctx || !dc->ctx->dmub_srv || !dc->debug.fams2_config.bits.enable)
return;
hw_lock_cmd.bits.command_code = DMUB_INBOX0_CMD__HW_LOCK;
hw_lock_cmd.bits.hw_lock_client = HW_LOCK_CLIENT_DRIVER;
hw_lock_cmd.bits.lock = lock;
hw_lock_cmd.bits.should_release = !lock;
dmub_hw_lock_mgr_inbox0_cmd(dc->ctx->dmub_srv, hw_lock_cmd);
}
void dcn401_fams2_global_control_lock_fast(union block_sequence_params *params)
{
struct dc *dc = params->fams2_global_control_lock_fast_params.dc;
bool lock = params->fams2_global_control_lock_fast_params.lock;
if (params->fams2_global_control_lock_fast_params.is_required) {
union dmub_inbox0_cmd_lock_hw hw_lock_cmd = { 0 };
hw_lock_cmd.bits.command_code = DMUB_INBOX0_CMD__HW_LOCK;
hw_lock_cmd.bits.hw_lock_client = HW_LOCK_CLIENT_DRIVER;
hw_lock_cmd.bits.lock = lock;
hw_lock_cmd.bits.should_release = !lock;
dmub_hw_lock_mgr_inbox0_cmd(dc->ctx->dmub_srv, hw_lock_cmd);
}
}
void dcn401_fams2_update_config(struct dc *dc, struct dc_state *context, bool enable)
{
bool fams2_required;
if (!dc->ctx || !dc->ctx->dmub_srv || !dc->debug.fams2_config.bits.enable)
return;
fams2_required = context->bw_ctx.bw.dcn.fams2_global_config.features.bits.enable;
dc_dmub_srv_fams2_update_config(dc, context, enable && fams2_required);
}
static void update_dsc_for_odm_change(struct dc *dc, struct dc_state *context,
struct pipe_ctx *otg_master)
{
int i;
struct pipe_ctx *old_pipe;
struct pipe_ctx *new_pipe;
struct pipe_ctx *old_opp_heads[MAX_PIPES];
struct pipe_ctx *old_otg_master;
int old_opp_head_count = 0;
old_otg_master = &dc->current_state->res_ctx.pipe_ctx[otg_master->pipe_idx];
if (resource_is_pipe_type(old_otg_master, OTG_MASTER)) {
old_opp_head_count = resource_get_opp_heads_for_otg_master(old_otg_master,
&dc->current_state->res_ctx,
old_opp_heads);
} else {
// DC cannot assume that the current state and the new state
// share the same OTG pipe since this is not true when called
// in the context of a commit stream not checked. Hence, set
// old_otg_master to NULL to skip the DSC configuration.
old_otg_master = NULL;
}
if (otg_master->stream_res.dsc)
dcn32_update_dsc_on_stream(otg_master,
otg_master->stream->timing.flags.DSC);
if (old_otg_master && old_otg_master->stream_res.dsc) {
for (i = 0; i < old_opp_head_count; i++) {
old_pipe = old_opp_heads[i];
new_pipe = &context->res_ctx.pipe_ctx[old_pipe->pipe_idx];
if (old_pipe->stream_res.dsc && !new_pipe->stream_res.dsc)
old_pipe->stream_res.dsc->funcs->dsc_disconnect(
old_pipe->stream_res.dsc);
}
}
}
void dcn401_update_odm(struct dc *dc, struct dc_state *context,
struct pipe_ctx *otg_master)
{
struct pipe_ctx *opp_heads[MAX_PIPES];
int opp_inst[MAX_PIPES] = {0};
int opp_head_count;
int odm_slice_width = resource_get_odm_slice_dst_width(otg_master, false);
int last_odm_slice_width = resource_get_odm_slice_dst_width(otg_master, true);
int i;
opp_head_count = resource_get_opp_heads_for_otg_master(
otg_master, &context->res_ctx, opp_heads);
for (i = 0; i < opp_head_count; i++)
opp_inst[i] = opp_heads[i]->stream_res.opp->inst;
if (opp_head_count > 1)
otg_master->stream_res.tg->funcs->set_odm_combine(
otg_master->stream_res.tg,
opp_inst, opp_head_count,
odm_slice_width, last_odm_slice_width);
else
otg_master->stream_res.tg->funcs->set_odm_bypass(
otg_master->stream_res.tg,
&otg_master->stream->timing);
for (i = 0; i < opp_head_count; i++) {
opp_heads[i]->stream_res.opp->funcs->opp_pipe_clock_control(
opp_heads[i]->stream_res.opp,
true);
opp_heads[i]->stream_res.opp->funcs->opp_program_left_edge_extra_pixel(
opp_heads[i]->stream_res.opp,
opp_heads[i]->stream->timing.pixel_encoding,
resource_is_pipe_type(opp_heads[i], OTG_MASTER));
}
update_dsc_for_odm_change(dc, context, otg_master);
if (!resource_is_pipe_type(otg_master, DPP_PIPE))
/*
* blank pattern is generated by OPP, reprogram blank pattern
* due to OPP count change
*/
dc->hwseq->funcs.blank_pixel_data(dc, otg_master, true);
}
void dcn401_unblank_stream(struct pipe_ctx *pipe_ctx,
struct dc_link_settings *link_settings)
{
struct encoder_unblank_param params = {0};
struct dc_stream_state *stream = pipe_ctx->stream;
struct dc_link *link = stream->link;
struct dce_hwseq *hws = link->dc->hwseq;
/* calculate parameters for unblank */
params.opp_cnt = resource_get_odm_slice_count(pipe_ctx);
params.timing = pipe_ctx->stream->timing;
params.link_settings.link_rate = link_settings->link_rate;
params.pix_per_cycle = pipe_ctx->stream_res.pix_clk_params.dio_se_pix_per_cycle;
if (link->dc->link_srv->dp_is_128b_132b_signal(pipe_ctx)) {
pipe_ctx->stream_res.hpo_dp_stream_enc->funcs->dp_unblank(
pipe_ctx->stream_res.hpo_dp_stream_enc,
pipe_ctx->stream_res.tg->inst);
} else if (dc_is_dp_signal(pipe_ctx->stream->signal)) {
pipe_ctx->stream_res.stream_enc->funcs->dp_unblank(link, pipe_ctx->stream_res.stream_enc, &params);
}
if (link->local_sink && link->local_sink->sink_signal == SIGNAL_TYPE_EDP)
hws->funcs.edp_backlight_control(link, true);
}
void dcn401_hardware_release(struct dc *dc)
{
dc_dmub_srv_fams2_update_config(dc, dc->current_state, false);
/* If pstate unsupported, or still supported
* by firmware, force it supported by dcn
*/
if (dc->current_state) {
if ((!dc->clk_mgr->clks.p_state_change_support ||
dc->current_state->bw_ctx.bw.dcn.fams2_global_config.features.bits.enable) &&
dc->res_pool->hubbub->funcs->force_pstate_change_control)
dc->res_pool->hubbub->funcs->force_pstate_change_control(
dc->res_pool->hubbub, true, true);
dc->current_state->bw_ctx.bw.dcn.clk.p_state_change_support = true;
dc->clk_mgr->funcs->update_clocks(dc->clk_mgr, dc->current_state, true);
}
}
void dcn401_wait_for_det_buffer_update_under_otg_master(struct dc *dc, struct dc_state *context, struct pipe_ctx *otg_master)
{
struct pipe_ctx *opp_heads[MAX_PIPES];
struct pipe_ctx *dpp_pipes[MAX_PIPES];
struct hubbub *hubbub = dc->res_pool->hubbub;
int dpp_count = 0;
if (!otg_master->stream)
return;
int slice_count = resource_get_opp_heads_for_otg_master(otg_master,
&context->res_ctx, opp_heads);
for (int slice_idx = 0; slice_idx < slice_count; slice_idx++) {
if (opp_heads[slice_idx]->plane_state) {
dpp_count = resource_get_dpp_pipes_for_opp_head(
opp_heads[slice_idx],
&context->res_ctx,
dpp_pipes);
for (int dpp_idx = 0; dpp_idx < dpp_count; dpp_idx++) {
struct pipe_ctx *dpp_pipe = dpp_pipes[dpp_idx];
if (dpp_pipe && hubbub &&
dpp_pipe->plane_res.hubp &&
hubbub->funcs->wait_for_det_update)
hubbub->funcs->wait_for_det_update(hubbub, dpp_pipe->plane_res.hubp->inst);
}
} else {
if (hubbub && opp_heads[slice_idx]->plane_res.hubp && hubbub->funcs->wait_for_det_update)
hubbub->funcs->wait_for_det_update(hubbub, opp_heads[slice_idx]->plane_res.hubp->inst);
}
}
}
void dcn401_interdependent_update_lock(struct dc *dc,
struct dc_state *context, bool lock)
{
unsigned int i = 0;
struct pipe_ctx *pipe = NULL;
struct timing_generator *tg = NULL;
if (lock) {
for (i = 0; i < dc->res_pool->pipe_count; i++) {
pipe = &context->res_ctx.pipe_ctx[i];
tg = pipe->stream_res.tg;
if (!resource_is_pipe_type(pipe, OTG_MASTER) ||
!tg->funcs->is_tg_enabled(tg) ||
dc_state_get_pipe_subvp_type(context, pipe) == SUBVP_PHANTOM)
continue;
dc->hwss.pipe_control_lock(dc, pipe, true);
}
} else {
/* Need to free DET being used first and have pipe update, then unlock the remaining pipes*/
for (i = 0; i < dc->res_pool->pipe_count; i++) {
pipe = &context->res_ctx.pipe_ctx[i];
tg = pipe->stream_res.tg;
if (!resource_is_pipe_type(pipe, OTG_MASTER) ||
!tg->funcs->is_tg_enabled(tg) ||
dc_state_get_pipe_subvp_type(context, pipe) == SUBVP_PHANTOM) {
continue;
}
if (dc->scratch.pipes_to_unlock_first[i]) {
struct pipe_ctx *old_pipe = &dc->current_state->res_ctx.pipe_ctx[i];
dc->hwss.pipe_control_lock(dc, pipe, false);
/* Assumes pipe of the same index in current_state is also an OTG_MASTER pipe*/
dcn401_wait_for_det_buffer_update_under_otg_master(dc, dc->current_state, old_pipe);
}
}
/* Unlocking the rest of the pipes */
for (i = 0; i < dc->res_pool->pipe_count; i++) {
if (dc->scratch.pipes_to_unlock_first[i])
continue;
pipe = &context->res_ctx.pipe_ctx[i];
tg = pipe->stream_res.tg;
if (!resource_is_pipe_type(pipe, OTG_MASTER) ||
!tg->funcs->is_tg_enabled(tg) ||
dc_state_get_pipe_subvp_type(context, pipe) == SUBVP_PHANTOM) {
continue;
}
dc->hwss.pipe_control_lock(dc, pipe, false);
}
}
}
void dcn401_perform_3dlut_wa_unlock(struct pipe_ctx *pipe_ctx)
{
/* If 3DLUT FL is enabled and 3DLUT is in use, follow the workaround sequence for pipe unlock to make sure that
* HUBP will properly fetch 3DLUT contents after unlock.
*
* This is meant to work around a known HW issue where VREADY will cancel the pending 3DLUT_ENABLE signal regardless
* of whether OTG lock is currently being held or not.
*/
struct pipe_ctx *wa_pipes[MAX_PIPES] = { NULL };
struct pipe_ctx *odm_pipe, *mpc_pipe;
int i, wa_pipe_ct = 0;
for (odm_pipe = pipe_ctx; odm_pipe != NULL; odm_pipe = odm_pipe->next_odm_pipe) {
for (mpc_pipe = odm_pipe; mpc_pipe != NULL; mpc_pipe = mpc_pipe->bottom_pipe) {
if (mpc_pipe->plane_state && mpc_pipe->plane_state->mcm_luts.lut3d_data.lut3d_src
== DC_CM2_TRANSFER_FUNC_SOURCE_VIDMEM
&& mpc_pipe->plane_state->mcm_shaper_3dlut_setting
== DC_CM2_SHAPER_3DLUT_SETTING_ENABLE_SHAPER_3DLUT) {
wa_pipes[wa_pipe_ct++] = mpc_pipe;
}
}
}
if (wa_pipe_ct > 0) {
if (pipe_ctx->stream_res.tg->funcs->set_vupdate_keepout)
pipe_ctx->stream_res.tg->funcs->set_vupdate_keepout(pipe_ctx->stream_res.tg, true);
for (i = 0; i < wa_pipe_ct; ++i) {
if (wa_pipes[i]->plane_res.hubp->funcs->hubp_enable_3dlut_fl)
wa_pipes[i]->plane_res.hubp->funcs->hubp_enable_3dlut_fl(wa_pipes[i]->plane_res.hubp, true);
}
pipe_ctx->stream_res.tg->funcs->unlock(pipe_ctx->stream_res.tg);
if (pipe_ctx->stream_res.tg->funcs->wait_update_lock_status)
pipe_ctx->stream_res.tg->funcs->wait_update_lock_status(pipe_ctx->stream_res.tg, false);
for (i = 0; i < wa_pipe_ct; ++i) {
if (wa_pipes[i]->plane_res.hubp->funcs->hubp_enable_3dlut_fl)
wa_pipes[i]->plane_res.hubp->funcs->hubp_enable_3dlut_fl(wa_pipes[i]->plane_res.hubp, true);
}
if (pipe_ctx->stream_res.tg->funcs->set_vupdate_keepout)
pipe_ctx->stream_res.tg->funcs->set_vupdate_keepout(pipe_ctx->stream_res.tg, false);
} else {
pipe_ctx->stream_res.tg->funcs->unlock(pipe_ctx->stream_res.tg);
}
}
void dcn401_program_outstanding_updates(struct dc *dc,
struct dc_state *context)
{
struct hubbub *hubbub = dc->res_pool->hubbub;
/* update compbuf if required */
if (hubbub->funcs->program_compbuf_segments)
hubbub->funcs->program_compbuf_segments(hubbub, context->bw_ctx.bw.dcn.arb_regs.compbuf_size, true);
}
void dcn401_reset_back_end_for_pipe(
struct dc *dc,
struct pipe_ctx *pipe_ctx,
struct dc_state *context)
{
struct dc_link *link = pipe_ctx->stream->link;
const struct link_hwss *link_hwss = get_link_hwss(link, &pipe_ctx->link_res);
DC_LOGGER_INIT(dc->ctx->logger);
if (pipe_ctx->stream_res.stream_enc == NULL) {
pipe_ctx->stream = NULL;
return;
}
/* DPMS may already disable or */
/* dpms_off status is incorrect due to fastboot
* feature. When system resume from S4 with second
* screen only, the dpms_off would be true but
* VBIOS lit up eDP, so check link status too.
*/
if (!pipe_ctx->stream->dpms_off || link->link_status.link_active)
dc->link_srv->set_dpms_off(pipe_ctx);
else if (pipe_ctx->stream_res.audio)
dc->hwss.disable_audio_stream(pipe_ctx);
/* free acquired resources */
if (pipe_ctx->stream_res.audio) {
/*disable az_endpoint*/
pipe_ctx->stream_res.audio->funcs->az_disable(pipe_ctx->stream_res.audio);
/*free audio*/
if (dc->caps.dynamic_audio == true) {
/*we have to dynamic arbitrate the audio endpoints*/
/*we free the resource, need reset is_audio_acquired*/
update_audio_usage(&dc->current_state->res_ctx, dc->res_pool,
pipe_ctx->stream_res.audio, false);
pipe_ctx->stream_res.audio = NULL;
}
}
/* by upper caller loop, parent pipe: pipe0, will be reset last.
* back end share by all pipes and will be disable only when disable
* parent pipe.
*/
if (pipe_ctx->top_pipe == NULL) {
dc->hwss.set_abm_immediate_disable(pipe_ctx);
pipe_ctx->stream_res.tg->funcs->disable_crtc(pipe_ctx->stream_res.tg);
pipe_ctx->stream_res.tg->funcs->enable_optc_clock(pipe_ctx->stream_res.tg, false);
if (pipe_ctx->stream_res.tg->funcs->set_odm_bypass)
pipe_ctx->stream_res.tg->funcs->set_odm_bypass(
pipe_ctx->stream_res.tg, &pipe_ctx->stream->timing);
set_drr_and_clear_adjust_pending(pipe_ctx, pipe_ctx->stream, NULL);
/* TODO - convert symclk_ref_cnts for otg to a bit map to solve
* the case where the same symclk is shared across multiple otg
* instances
*/
if (dc_is_hdmi_tmds_signal(pipe_ctx->stream->signal))
link->phy_state.symclk_ref_cnts.otg = 0;
if (link->phy_state.symclk_state == SYMCLK_ON_TX_OFF) {
link_hwss->disable_link_output(link,
&pipe_ctx->link_res, pipe_ctx->stream->signal);
link->phy_state.symclk_state = SYMCLK_OFF_TX_OFF;
}
/* reset DTBCLK_P */
if (dc->res_pool->dccg->funcs->set_dtbclk_p_src)
dc->res_pool->dccg->funcs->set_dtbclk_p_src(dc->res_pool->dccg, REFCLK, pipe_ctx->stream_res.tg->inst);
}
/*
* In case of a dangling plane, setting this to NULL unconditionally
* causes failures during reset hw ctx where, if stream is NULL,
* it is expected that the pipe_ctx pointers to pipes and plane are NULL.
*/
pipe_ctx->stream = NULL;
pipe_ctx->top_pipe = NULL;
pipe_ctx->bottom_pipe = NULL;
pipe_ctx->next_odm_pipe = NULL;
pipe_ctx->prev_odm_pipe = NULL;
DC_LOG_DEBUG("Reset back end for pipe %d, tg:%d\n",
pipe_ctx->pipe_idx, pipe_ctx->stream_res.tg->inst);
}
void dcn401_reset_hw_ctx_wrap(
struct dc *dc,
struct dc_state *context)
{
int i;
struct dce_hwseq *hws = dc->hwseq;
/* Reset Back End*/
for (i = dc->res_pool->pipe_count - 1; i >= 0 ; i--) {
struct pipe_ctx *pipe_ctx_old =
&dc->current_state->res_ctx.pipe_ctx[i];
struct pipe_ctx *pipe_ctx = &context->res_ctx.pipe_ctx[i];
if (!pipe_ctx_old->stream)
continue;
if (pipe_ctx_old->top_pipe || pipe_ctx_old->prev_odm_pipe)
continue;
if (!pipe_ctx->stream ||
pipe_need_reprogram(pipe_ctx_old, pipe_ctx)) {
struct clock_source *old_clk = pipe_ctx_old->clock_source;
if (hws->funcs.reset_back_end_for_pipe)
hws->funcs.reset_back_end_for_pipe(dc, pipe_ctx_old, dc->current_state);
if (hws->funcs.enable_stream_gating)
hws->funcs.enable_stream_gating(dc, pipe_ctx_old);
if (old_clk)
old_clk->funcs->cs_power_down(old_clk);
}
}
}
static unsigned int dcn401_calculate_vready_offset_for_group(struct pipe_ctx *pipe)
{
struct pipe_ctx *other_pipe;
unsigned int vready_offset = pipe->global_sync.dcn4x.vready_offset_pixels;
/* Always use the largest vready_offset of all connected pipes */
for (other_pipe = pipe->bottom_pipe; other_pipe != NULL; other_pipe = other_pipe->bottom_pipe) {
if (other_pipe->global_sync.dcn4x.vready_offset_pixels > vready_offset)
vready_offset = other_pipe->global_sync.dcn4x.vready_offset_pixels;
}
for (other_pipe = pipe->top_pipe; other_pipe != NULL; other_pipe = other_pipe->top_pipe) {
if (other_pipe->global_sync.dcn4x.vready_offset_pixels > vready_offset)
vready_offset = other_pipe->global_sync.dcn4x.vready_offset_pixels;
}
for (other_pipe = pipe->next_odm_pipe; other_pipe != NULL; other_pipe = other_pipe->next_odm_pipe) {
if (other_pipe->global_sync.dcn4x.vready_offset_pixels > vready_offset)
vready_offset = other_pipe->global_sync.dcn4x.vready_offset_pixels;
}
for (other_pipe = pipe->prev_odm_pipe; other_pipe != NULL; other_pipe = other_pipe->prev_odm_pipe) {
if (other_pipe->global_sync.dcn4x.vready_offset_pixels > vready_offset)
vready_offset = other_pipe->global_sync.dcn4x.vready_offset_pixels;
}
return vready_offset;
}
static void dcn401_program_tg(
struct dc *dc,
struct pipe_ctx *pipe_ctx,
struct dc_state *context,
struct dce_hwseq *hws)
{
pipe_ctx->stream_res.tg->funcs->program_global_sync(
pipe_ctx->stream_res.tg,
dcn401_calculate_vready_offset_for_group(pipe_ctx),
(unsigned int)pipe_ctx->global_sync.dcn4x.vstartup_lines,
(unsigned int)pipe_ctx->global_sync.dcn4x.vupdate_offset_pixels,
(unsigned int)pipe_ctx->global_sync.dcn4x.vupdate_vupdate_width_pixels,
(unsigned int)pipe_ctx->global_sync.dcn4x.pstate_keepout_start_lines);
if (dc_state_get_pipe_subvp_type(context, pipe_ctx) != SUBVP_PHANTOM)
pipe_ctx->stream_res.tg->funcs->wait_for_state(pipe_ctx->stream_res.tg, CRTC_STATE_VACTIVE);
pipe_ctx->stream_res.tg->funcs->set_vtg_params(
pipe_ctx->stream_res.tg, &pipe_ctx->stream->timing, true);
if (hws->funcs.setup_vupdate_interrupt)
hws->funcs.setup_vupdate_interrupt(dc, pipe_ctx);
}
void dcn401_program_pipe(
struct dc *dc,
struct pipe_ctx *pipe_ctx,
struct dc_state *context)
{
struct dce_hwseq *hws = dc->hwseq;
/* Only need to unblank on top pipe */
if (resource_is_pipe_type(pipe_ctx, OTG_MASTER)) {
if (pipe_ctx->update_flags.bits.enable ||
pipe_ctx->update_flags.bits.odm ||
pipe_ctx->stream->update_flags.bits.abm_level)
hws->funcs.blank_pixel_data(dc, pipe_ctx,
!pipe_ctx->plane_state ||
!pipe_ctx->plane_state->visible);
}
/* Only update TG on top pipe */
if (pipe_ctx->update_flags.bits.global_sync && !pipe_ctx->top_pipe
&& !pipe_ctx->prev_odm_pipe)
dcn401_program_tg(dc, pipe_ctx, context, hws);
if (pipe_ctx->update_flags.bits.odm)
hws->funcs.update_odm(dc, context, pipe_ctx);
if (pipe_ctx->update_flags.bits.enable) {
if (hws->funcs.enable_plane)
hws->funcs.enable_plane(dc, pipe_ctx, context);
else
dc->hwss.enable_plane(dc, pipe_ctx, context);
if (dc->res_pool->hubbub->funcs->force_wm_propagate_to_pipes)
dc->res_pool->hubbub->funcs->force_wm_propagate_to_pipes(dc->res_pool->hubbub);
}
if (pipe_ctx->update_flags.bits.det_size) {
if (dc->res_pool->hubbub->funcs->program_det_size)
dc->res_pool->hubbub->funcs->program_det_size(
dc->res_pool->hubbub, pipe_ctx->plane_res.hubp->inst, pipe_ctx->det_buffer_size_kb);
if (dc->res_pool->hubbub->funcs->program_det_segments)
dc->res_pool->hubbub->funcs->program_det_segments(
dc->res_pool->hubbub, pipe_ctx->plane_res.hubp->inst, pipe_ctx->hubp_regs.det_size);
}
if (pipe_ctx->update_flags.raw ||
(pipe_ctx->plane_state && pipe_ctx->plane_state->update_flags.raw) ||
pipe_ctx->stream->update_flags.raw)
dc->hwss.update_dchubp_dpp(dc, pipe_ctx, context);
if (pipe_ctx->plane_state && (pipe_ctx->update_flags.bits.enable ||
pipe_ctx->plane_state->update_flags.bits.hdr_mult))
hws->funcs.set_hdr_multiplier(pipe_ctx);
if (hws->funcs.populate_mcm_luts) {
if (pipe_ctx->plane_state) {
hws->funcs.populate_mcm_luts(dc, pipe_ctx, pipe_ctx->plane_state->mcm_luts,
pipe_ctx->plane_state->lut_bank_a);
pipe_ctx->plane_state->lut_bank_a = !pipe_ctx->plane_state->lut_bank_a;
}
}
if (pipe_ctx->plane_state &&
(pipe_ctx->plane_state->update_flags.bits.in_transfer_func_change ||
pipe_ctx->plane_state->update_flags.bits.gamma_change ||
pipe_ctx->plane_state->update_flags.bits.lut_3d ||
pipe_ctx->update_flags.bits.enable))
hws->funcs.set_input_transfer_func(dc, pipe_ctx, pipe_ctx->plane_state);
/* dcn10_translate_regamma_to_hw_format takes 750us to finish
* only do gamma programming for powering on, internal memcmp to avoid
* updating on slave planes
*/
if (pipe_ctx->update_flags.bits.enable ||
pipe_ctx->update_flags.bits.plane_changed ||
pipe_ctx->stream->update_flags.bits.out_tf ||
(pipe_ctx->plane_state &&
pipe_ctx->plane_state->update_flags.bits.output_tf_change))
hws->funcs.set_output_transfer_func(dc, pipe_ctx, pipe_ctx->stream);
/* If the pipe has been enabled or has a different opp, we
* should reprogram the fmt. This deals with cases where
* interation between mpc and odm combine on different streams
* causes a different pipe to be chosen to odm combine with.
*/
if (pipe_ctx->update_flags.bits.enable
|| pipe_ctx->update_flags.bits.opp_changed) {
pipe_ctx->stream_res.opp->funcs->opp_set_dyn_expansion(
pipe_ctx->stream_res.opp,
COLOR_SPACE_YCBCR601,
pipe_ctx->stream->timing.display_color_depth,
pipe_ctx->stream->signal);
pipe_ctx->stream_res.opp->funcs->opp_program_fmt(
pipe_ctx->stream_res.opp,
&pipe_ctx->stream->bit_depth_params,
&pipe_ctx->stream->clamping);
}
/* Set ABM pipe after other pipe configurations done */
if ((pipe_ctx->plane_state && pipe_ctx->plane_state->visible)) {
if (pipe_ctx->stream_res.abm) {
dc->hwss.set_pipe(pipe_ctx);
pipe_ctx->stream_res.abm->funcs->set_abm_level(pipe_ctx->stream_res.abm,
pipe_ctx->stream->abm_level);
}
}
if (pipe_ctx->update_flags.bits.test_pattern_changed) {
struct output_pixel_processor *odm_opp = pipe_ctx->stream_res.opp;
struct bit_depth_reduction_params params;
memset(&params, 0, sizeof(params));
odm_opp->funcs->opp_program_bit_depth_reduction(odm_opp, &params);
dc->hwss.set_disp_pattern_generator(dc,
pipe_ctx,
pipe_ctx->stream_res.test_pattern_params.test_pattern,
pipe_ctx->stream_res.test_pattern_params.color_space,
pipe_ctx->stream_res.test_pattern_params.color_depth,
NULL,
pipe_ctx->stream_res.test_pattern_params.width,
pipe_ctx->stream_res.test_pattern_params.height,
pipe_ctx->stream_res.test_pattern_params.offset);
}
}
void dcn401_program_front_end_for_ctx(
struct dc *dc,
struct dc_state *context)
{
int i;
unsigned int prev_hubp_count = 0;
unsigned int hubp_count = 0;
struct dce_hwseq *hws = dc->hwseq;
struct pipe_ctx *pipe = NULL;
DC_LOGGER_INIT(dc->ctx->logger);
if (resource_is_pipe_topology_changed(dc->current_state, context))
resource_log_pipe_topology_update(dc, context);
if (dc->hwss.program_triplebuffer != NULL && dc->debug.enable_tri_buf) {
for (i = 0; i < dc->res_pool->pipe_count; i++) {
pipe = &context->res_ctx.pipe_ctx[i];
if (!pipe->top_pipe && !pipe->prev_odm_pipe && pipe->plane_state) {
if (pipe->plane_state->triplebuffer_flips)
BREAK_TO_DEBUGGER();
/*turn off triple buffer for full update*/
dc->hwss.program_triplebuffer(
dc, pipe, pipe->plane_state->triplebuffer_flips);
}
}
}
for (i = 0; i < dc->res_pool->pipe_count; i++) {
if (dc->current_state->res_ctx.pipe_ctx[i].plane_state)
prev_hubp_count++;
if (context->res_ctx.pipe_ctx[i].plane_state)
hubp_count++;
}
if (prev_hubp_count == 0 && hubp_count > 0) {
if (dc->res_pool->hubbub->funcs->force_pstate_change_control)
dc->res_pool->hubbub->funcs->force_pstate_change_control(
dc->res_pool->hubbub, true, false);
udelay(500);
}
/* Set pipe update flags and lock pipes */
for (i = 0; i < dc->res_pool->pipe_count; i++)
dc->hwss.detect_pipe_changes(dc->current_state, context, &dc->current_state->res_ctx.pipe_ctx[i],
&context->res_ctx.pipe_ctx[i]);
/* When disabling phantom pipes, turn on phantom OTG first (so we can get double
* buffer updates properly)
*/
for (i = 0; i < dc->res_pool->pipe_count; i++) {
struct dc_stream_state *stream = dc->current_state->res_ctx.pipe_ctx[i].stream;
pipe = &dc->current_state->res_ctx.pipe_ctx[i];
if (context->res_ctx.pipe_ctx[i].update_flags.bits.disable && stream &&
dc_state_get_pipe_subvp_type(dc->current_state, pipe) == SUBVP_PHANTOM) {
struct timing_generator *tg = dc->current_state->res_ctx.pipe_ctx[i].stream_res.tg;
if (tg->funcs->enable_crtc) {
if (dc->hwseq->funcs.blank_pixel_data)
dc->hwseq->funcs.blank_pixel_data(dc, pipe, true);
tg->funcs->enable_crtc(tg);
}
}
}
/* OTG blank before disabling all front ends */
for (i = 0; i < dc->res_pool->pipe_count; i++)
if (context->res_ctx.pipe_ctx[i].update_flags.bits.disable
&& !context->res_ctx.pipe_ctx[i].top_pipe
&& !context->res_ctx.pipe_ctx[i].prev_odm_pipe
&& context->res_ctx.pipe_ctx[i].stream)
hws->funcs.blank_pixel_data(dc, &context->res_ctx.pipe_ctx[i], true);
/* Disconnect mpcc */
for (i = 0; i < dc->res_pool->pipe_count; i++)
if (context->res_ctx.pipe_ctx[i].update_flags.bits.disable
|| context->res_ctx.pipe_ctx[i].update_flags.bits.opp_changed) {
struct hubbub *hubbub = dc->res_pool->hubbub;
/* Phantom pipe DET should be 0, but if a pipe in use is being transitioned to phantom
* then we want to do the programming here (effectively it's being disabled). If we do
* the programming later the DET won't be updated until the OTG for the phantom pipe is
* turned on (i.e. in an MCLK switch) which can come in too late and cause issues with
* DET allocation.
*/
if ((context->res_ctx.pipe_ctx[i].update_flags.bits.disable ||
(context->res_ctx.pipe_ctx[i].plane_state &&
dc_state_get_pipe_subvp_type(context, &context->res_ctx.pipe_ctx[i]) ==
SUBVP_PHANTOM))) {
if (hubbub->funcs->program_det_size)
hubbub->funcs->program_det_size(hubbub,
dc->current_state->res_ctx.pipe_ctx[i].plane_res.hubp->inst, 0);
if (dc->res_pool->hubbub->funcs->program_det_segments)
dc->res_pool->hubbub->funcs->program_det_segments(
hubbub, dc->current_state->res_ctx.pipe_ctx[i].plane_res.hubp->inst, 0);
}
hws->funcs.plane_atomic_disconnect(dc, dc->current_state,
&dc->current_state->res_ctx.pipe_ctx[i]);
DC_LOG_DC("Reset mpcc for pipe %d\n", dc->current_state->res_ctx.pipe_ctx[i].pipe_idx);
}
/* update ODM for blanked OTG master pipes */
for (i = 0; i < dc->res_pool->pipe_count; i++) {
pipe = &context->res_ctx.pipe_ctx[i];
if (resource_is_pipe_type(pipe, OTG_MASTER) &&
!resource_is_pipe_type(pipe, DPP_PIPE) &&
pipe->update_flags.bits.odm &&
hws->funcs.update_odm)
hws->funcs.update_odm(dc, context, pipe);
}
/*
* Program all updated pipes, order matters for mpcc setup. Start with
* top pipe and program all pipes that follow in order
*/
for (i = 0; i < dc->res_pool->pipe_count; i++) {
pipe = &context->res_ctx.pipe_ctx[i];
if (pipe->plane_state && !pipe->top_pipe) {
while (pipe) {
if (hws->funcs.program_pipe)
hws->funcs.program_pipe(dc, pipe, context);
else {
/* Don't program phantom pipes in the regular front end programming sequence.
* There is an MPO transition case where a pipe being used by a video plane is
* transitioned directly to be a phantom pipe when closing the MPO video.
* However the phantom pipe will program a new HUBP_VTG_SEL (update takes place
* right away) but the MPO still exists until the double buffered update of the
* main pipe so we will get a frame of underflow if the phantom pipe is
* programmed here.
*/
if (pipe->stream &&
dc_state_get_pipe_subvp_type(context, pipe) != SUBVP_PHANTOM)
dcn401_program_pipe(dc, pipe, context);
}
pipe = pipe->bottom_pipe;
}
}
/* Program secondary blending tree and writeback pipes */
pipe = &context->res_ctx.pipe_ctx[i];
if (!pipe->top_pipe && !pipe->prev_odm_pipe
&& pipe->stream && pipe->stream->num_wb_info > 0
&& (pipe->update_flags.raw || (pipe->plane_state && pipe->plane_state->update_flags.raw)
|| pipe->stream->update_flags.raw)
&& hws->funcs.program_all_writeback_pipes_in_tree)
hws->funcs.program_all_writeback_pipes_in_tree(dc, pipe->stream, context);
/* Avoid underflow by check of pipe line read when adding 2nd plane. */
if (hws->wa.wait_hubpret_read_start_during_mpo_transition &&
!pipe->top_pipe &&
pipe->stream &&
pipe->plane_res.hubp->funcs->hubp_wait_pipe_read_start &&
dc->current_state->stream_status[0].plane_count == 1 &&
context->stream_status[0].plane_count > 1) {
pipe->plane_res.hubp->funcs->hubp_wait_pipe_read_start(pipe->plane_res.hubp);
}
}
}
void dcn401_post_unlock_program_front_end(
struct dc *dc,
struct dc_state *context)
{
// Timeout for pipe enable
unsigned int timeout_us = 100000;
unsigned int polling_interval_us = 1;
struct dce_hwseq *hwseq = dc->hwseq;
int i;
DC_LOGGER_INIT(dc->ctx->logger);
for (i = 0; i < dc->res_pool->pipe_count; i++)
if (resource_is_pipe_type(&dc->current_state->res_ctx.pipe_ctx[i], OPP_HEAD) &&
!resource_is_pipe_type(&context->res_ctx.pipe_ctx[i], OPP_HEAD))
dc->hwss.post_unlock_reset_opp(dc,
&dc->current_state->res_ctx.pipe_ctx[i]);
for (i = 0; i < dc->res_pool->pipe_count; i++)
if (context->res_ctx.pipe_ctx[i].update_flags.bits.disable)
dc->hwss.disable_plane(dc, dc->current_state, &dc->current_state->res_ctx.pipe_ctx[i]);
/*
* If we are enabling a pipe, we need to wait for pending clear as this is a critical
* part of the enable operation otherwise, DM may request an immediate flip which
* will cause HW to perform an "immediate enable" (as opposed to "vsync enable") which
* is unsupported on DCN.
*/
for (i = 0; i < dc->res_pool->pipe_count; i++) {
struct pipe_ctx *pipe = &context->res_ctx.pipe_ctx[i];
// Don't check flip pending on phantom pipes
if (pipe->plane_state && !pipe->top_pipe && pipe->update_flags.bits.enable &&
dc_state_get_pipe_subvp_type(context, pipe) != SUBVP_PHANTOM) {
struct hubp *hubp = pipe->plane_res.hubp;
int j = 0;
for (j = 0; j < timeout_us / polling_interval_us
&& hubp->funcs->hubp_is_flip_pending(hubp); j++)
udelay(polling_interval_us);
}
}
for (i = 0; i < dc->res_pool->pipe_count; i++) {
struct pipe_ctx *pipe = &context->res_ctx.pipe_ctx[i];
struct pipe_ctx *old_pipe = &dc->current_state->res_ctx.pipe_ctx[i];
/* When going from a smaller ODM slice count to larger, we must ensure double
* buffer update completes before we return to ensure we don't reduce DISPCLK
* before we've transitioned to 2:1 or 4:1
*/
if (resource_is_pipe_type(old_pipe, OTG_MASTER) && resource_is_pipe_type(pipe, OTG_MASTER) &&
resource_get_odm_slice_count(old_pipe) < resource_get_odm_slice_count(pipe) &&
dc_state_get_pipe_subvp_type(context, pipe) != SUBVP_PHANTOM) {
int j = 0;
struct timing_generator *tg = pipe->stream_res.tg;
if (tg->funcs->get_optc_double_buffer_pending) {
for (j = 0; j < timeout_us / polling_interval_us
&& tg->funcs->get_optc_double_buffer_pending(tg); j++)
udelay(polling_interval_us);
}
}
}
if (dc->res_pool->hubbub->funcs->force_pstate_change_control)
dc->res_pool->hubbub->funcs->force_pstate_change_control(
dc->res_pool->hubbub, false, false);
for (i = 0; i < dc->res_pool->pipe_count; i++) {
struct pipe_ctx *pipe = &context->res_ctx.pipe_ctx[i];
if (pipe->plane_state && !pipe->top_pipe) {
/* Program phantom pipe here to prevent a frame of underflow in the MPO transition
* case (if a pipe being used for a video plane transitions to a phantom pipe, it
* can underflow due to HUBP_VTG_SEL programming if done in the regular front end
* programming sequence).
*/
while (pipe) {
if (pipe->stream && dc_state_get_pipe_subvp_type(context, pipe) == SUBVP_PHANTOM) {
/* When turning on the phantom pipe we want to run through the
* entire enable sequence, so apply all the "enable" flags.
*/
if (dc->hwss.apply_update_flags_for_phantom)
dc->hwss.apply_update_flags_for_phantom(pipe);
if (dc->hwss.update_phantom_vp_position)
dc->hwss.update_phantom_vp_position(dc, context, pipe);
dcn401_program_pipe(dc, pipe, context);
}
pipe = pipe->bottom_pipe;
}
}
}
if (!hwseq)
return;
/* P-State support transitions:
* Natural -> FPO: P-State disabled in prepare, force disallow anytime is safe
* FPO -> Natural: Unforce anytime after FW disable is safe (P-State will assert naturally)
* Unsupported -> FPO: P-State enabled in optimize, force disallow anytime is safe
* FPO -> Unsupported: P-State disabled in prepare, unforce disallow anytime is safe
* FPO <-> SubVP: Force disallow is maintained on the FPO / SubVP pipes
*/
if (hwseq->funcs.update_force_pstate)
dc->hwseq->funcs.update_force_pstate(dc, context);
/* Only program the MALL registers after all the main and phantom pipes
* are done programming.
*/
if (hwseq->funcs.program_mall_pipe_config)
hwseq->funcs.program_mall_pipe_config(dc, context);
/* WA to apply WM setting*/
if (hwseq->wa.DEGVIDCN21)
dc->res_pool->hubbub->funcs->apply_DEDCN21_147_wa(dc->res_pool->hubbub);
/* WA for stutter underflow during MPO transitions when adding 2nd plane */
if (hwseq->wa.disallow_self_refresh_during_multi_plane_transition) {
if (dc->current_state->stream_status[0].plane_count == 1 &&
context->stream_status[0].plane_count > 1) {
struct timing_generator *tg = dc->res_pool->timing_generators[0];
dc->res_pool->hubbub->funcs->allow_self_refresh_control(dc->res_pool->hubbub, false);
hwseq->wa_state.disallow_self_refresh_during_multi_plane_transition_applied = true;
hwseq->wa_state.disallow_self_refresh_during_multi_plane_transition_applied_on_frame =
tg->funcs->get_frame_count(tg);
}
}
}
bool dcn401_update_bandwidth(
struct dc *dc,
struct dc_state *context)
{
int i;
struct dce_hwseq *hws = dc->hwseq;
/* recalculate DML parameters */
if (!dc->res_pool->funcs->validate_bandwidth(dc, context, false))
return false;
/* apply updated bandwidth parameters */
dc->hwss.prepare_bandwidth(dc, context);
/* update hubp configs for all pipes */
for (i = 0; i < dc->res_pool->pipe_count; i++) {
struct pipe_ctx *pipe_ctx = &context->res_ctx.pipe_ctx[i];
if (pipe_ctx->plane_state == NULL)
continue;
if (pipe_ctx->top_pipe == NULL) {
bool blank = !is_pipe_tree_visible(pipe_ctx);
pipe_ctx->stream_res.tg->funcs->program_global_sync(
pipe_ctx->stream_res.tg,
dcn401_calculate_vready_offset_for_group(pipe_ctx),
(unsigned int)pipe_ctx->global_sync.dcn4x.vstartup_lines,
(unsigned int)pipe_ctx->global_sync.dcn4x.vupdate_offset_pixels,
(unsigned int)pipe_ctx->global_sync.dcn4x.vupdate_vupdate_width_pixels,
(unsigned int)pipe_ctx->global_sync.dcn4x.pstate_keepout_start_lines);
pipe_ctx->stream_res.tg->funcs->set_vtg_params(
pipe_ctx->stream_res.tg, &pipe_ctx->stream->timing, false);
if (pipe_ctx->prev_odm_pipe == NULL)
hws->funcs.blank_pixel_data(dc, pipe_ctx, blank);
if (hws->funcs.setup_vupdate_interrupt)
hws->funcs.setup_vupdate_interrupt(dc, pipe_ctx);
}
if (pipe_ctx->plane_res.hubp->funcs->hubp_setup2)
pipe_ctx->plane_res.hubp->funcs->hubp_setup2(
pipe_ctx->plane_res.hubp,
&pipe_ctx->hubp_regs,
&pipe_ctx->global_sync,
&pipe_ctx->stream->timing);
}
return true;
}
void dcn401_detect_pipe_changes(struct dc_state *old_state,
struct dc_state *new_state,
struct pipe_ctx *old_pipe,
struct pipe_ctx *new_pipe)
{
bool old_is_phantom = dc_state_get_pipe_subvp_type(old_state, old_pipe) == SUBVP_PHANTOM;
bool new_is_phantom = dc_state_get_pipe_subvp_type(new_state, new_pipe) == SUBVP_PHANTOM;
unsigned int old_pipe_vready_offset_pixels = old_pipe->global_sync.dcn4x.vready_offset_pixels;
unsigned int new_pipe_vready_offset_pixels = new_pipe->global_sync.dcn4x.vready_offset_pixels;
unsigned int old_pipe_vstartup_lines = old_pipe->global_sync.dcn4x.vstartup_lines;
unsigned int new_pipe_vstartup_lines = new_pipe->global_sync.dcn4x.vstartup_lines;
unsigned int old_pipe_vupdate_offset_pixels = old_pipe->global_sync.dcn4x.vupdate_offset_pixels;
unsigned int new_pipe_vupdate_offset_pixels = new_pipe->global_sync.dcn4x.vupdate_offset_pixels;
unsigned int old_pipe_vupdate_width_pixels = old_pipe->global_sync.dcn4x.vupdate_vupdate_width_pixels;
unsigned int new_pipe_vupdate_width_pixels = new_pipe->global_sync.dcn4x.vupdate_vupdate_width_pixels;
new_pipe->update_flags.raw = 0;
/* If non-phantom pipe is being transitioned to a phantom pipe,
* set disable and return immediately. This is because the pipe
* that was previously in use must be fully disabled before we
* can "enable" it as a phantom pipe (since the OTG will certainly
* be different). The post_unlock sequence will set the correct
* update flags to enable the phantom pipe.
*/
if (old_pipe->plane_state && !old_is_phantom &&
new_pipe->plane_state && new_is_phantom) {
new_pipe->update_flags.bits.disable = 1;
return;
}
if (resource_is_pipe_type(new_pipe, OTG_MASTER) &&
resource_is_odm_topology_changed(new_pipe, old_pipe))
/* Detect odm changes */
new_pipe->update_flags.bits.odm = 1;
/* Exit on unchanged, unused pipe */
if (!old_pipe->plane_state && !new_pipe->plane_state)
return;
/* Detect pipe enable/disable */
if (!old_pipe->plane_state && new_pipe->plane_state) {
new_pipe->update_flags.bits.enable = 1;
new_pipe->update_flags.bits.mpcc = 1;
new_pipe->update_flags.bits.dppclk = 1;
new_pipe->update_flags.bits.hubp_interdependent = 1;
new_pipe->update_flags.bits.hubp_rq_dlg_ttu = 1;
new_pipe->update_flags.bits.unbounded_req = 1;
new_pipe->update_flags.bits.gamut_remap = 1;
new_pipe->update_flags.bits.scaler = 1;
new_pipe->update_flags.bits.viewport = 1;
new_pipe->update_flags.bits.det_size = 1;
if (new_pipe->stream->test_pattern.type != DP_TEST_PATTERN_VIDEO_MODE &&
new_pipe->stream_res.test_pattern_params.width != 0 &&
new_pipe->stream_res.test_pattern_params.height != 0)
new_pipe->update_flags.bits.test_pattern_changed = 1;
if (!new_pipe->top_pipe && !new_pipe->prev_odm_pipe) {
new_pipe->update_flags.bits.odm = 1;
new_pipe->update_flags.bits.global_sync = 1;
}
return;
}
/* For SubVP we need to unconditionally enable because any phantom pipes are
* always removed then newly added for every full updates whenever SubVP is in use.
* The remove-add sequence of the phantom pipe always results in the pipe
* being blanked in enable_stream_timing (DPG).
*/
if (new_pipe->stream && dc_state_get_pipe_subvp_type(new_state, new_pipe) == SUBVP_PHANTOM)
new_pipe->update_flags.bits.enable = 1;
/* Phantom pipes are effectively disabled, if the pipe was previously phantom
* we have to enable
*/
if (old_pipe->plane_state && old_is_phantom &&
new_pipe->plane_state && !new_is_phantom)
new_pipe->update_flags.bits.enable = 1;
if (old_pipe->plane_state && !new_pipe->plane_state) {
new_pipe->update_flags.bits.disable = 1;
return;
}
/* Detect plane change */
if (old_pipe->plane_state != new_pipe->plane_state)
new_pipe->update_flags.bits.plane_changed = true;
/* Detect top pipe only changes */
if (resource_is_pipe_type(new_pipe, OTG_MASTER)) {
/* Detect global sync changes */
if ((old_pipe_vready_offset_pixels != new_pipe_vready_offset_pixels)
|| (old_pipe_vstartup_lines != new_pipe_vstartup_lines)
|| (old_pipe_vupdate_offset_pixels != new_pipe_vupdate_offset_pixels)
|| (old_pipe_vupdate_width_pixels != new_pipe_vupdate_width_pixels))
new_pipe->update_flags.bits.global_sync = 1;
}
if (old_pipe->det_buffer_size_kb != new_pipe->det_buffer_size_kb)
new_pipe->update_flags.bits.det_size = 1;
/*
* Detect opp / tg change, only set on change, not on enable
* Assume mpcc inst = pipe index, if not this code needs to be updated
* since mpcc is what is affected by these. In fact all of our sequence
* makes this assumption at the moment with how hubp reset is matched to
* same index mpcc reset.
*/
if (old_pipe->stream_res.opp != new_pipe->stream_res.opp)
new_pipe->update_flags.bits.opp_changed = 1;
if (old_pipe->stream_res.tg != new_pipe->stream_res.tg)
new_pipe->update_flags.bits.tg_changed = 1;
/*
* Detect mpcc blending changes, only dpp inst and opp matter here,
* mpccs getting removed/inserted update connected ones during their own
* programming
*/
if (old_pipe->plane_res.dpp != new_pipe->plane_res.dpp
|| old_pipe->stream_res.opp != new_pipe->stream_res.opp)
new_pipe->update_flags.bits.mpcc = 1;
/* Detect dppclk change */
if (old_pipe->plane_res.bw.dppclk_khz != new_pipe->plane_res.bw.dppclk_khz)
new_pipe->update_flags.bits.dppclk = 1;
/* Check for scl update */
if (memcmp(&old_pipe->plane_res.scl_data, &new_pipe->plane_res.scl_data, sizeof(struct scaler_data)))
new_pipe->update_flags.bits.scaler = 1;
/* Check for vp update */
if (memcmp(&old_pipe->plane_res.scl_data.viewport, &new_pipe->plane_res.scl_data.viewport, sizeof(struct rect))
|| memcmp(&old_pipe->plane_res.scl_data.viewport_c,
&new_pipe->plane_res.scl_data.viewport_c, sizeof(struct rect)))
new_pipe->update_flags.bits.viewport = 1;
/* Detect dlg/ttu/rq updates */
{
struct dml2_display_dlg_regs old_dlg_regs = old_pipe->hubp_regs.dlg_regs;
struct dml2_display_ttu_regs old_ttu_regs = old_pipe->hubp_regs.ttu_regs;
struct dml2_display_rq_regs old_rq_regs = old_pipe->hubp_regs.rq_regs;
struct dml2_display_dlg_regs *new_dlg_regs = &new_pipe->hubp_regs.dlg_regs;
struct dml2_display_ttu_regs *new_ttu_regs = &new_pipe->hubp_regs.ttu_regs;
struct dml2_display_rq_regs *new_rq_regs = &new_pipe->hubp_regs.rq_regs;
/* Detect pipe interdependent updates */
if ((old_dlg_regs.dst_y_prefetch != new_dlg_regs->dst_y_prefetch)
|| (old_dlg_regs.vratio_prefetch != new_dlg_regs->vratio_prefetch)
|| (old_dlg_regs.vratio_prefetch_c != new_dlg_regs->vratio_prefetch_c)
|| (old_dlg_regs.dst_y_per_vm_vblank != new_dlg_regs->dst_y_per_vm_vblank)
|| (old_dlg_regs.dst_y_per_row_vblank != new_dlg_regs->dst_y_per_row_vblank)
|| (old_dlg_regs.dst_y_per_vm_flip != new_dlg_regs->dst_y_per_vm_flip)
|| (old_dlg_regs.dst_y_per_row_flip != new_dlg_regs->dst_y_per_row_flip)
|| (old_dlg_regs.refcyc_per_meta_chunk_vblank_l != new_dlg_regs->refcyc_per_meta_chunk_vblank_l)
|| (old_dlg_regs.refcyc_per_meta_chunk_vblank_c != new_dlg_regs->refcyc_per_meta_chunk_vblank_c)
|| (old_dlg_regs.refcyc_per_meta_chunk_flip_l != new_dlg_regs->refcyc_per_meta_chunk_flip_l)
|| (old_dlg_regs.refcyc_per_line_delivery_pre_l != new_dlg_regs->refcyc_per_line_delivery_pre_l)
|| (old_dlg_regs.refcyc_per_line_delivery_pre_c != new_dlg_regs->refcyc_per_line_delivery_pre_c)
|| (old_ttu_regs.refcyc_per_req_delivery_pre_l != new_ttu_regs->refcyc_per_req_delivery_pre_l)
|| (old_ttu_regs.refcyc_per_req_delivery_pre_c != new_ttu_regs->refcyc_per_req_delivery_pre_c)
|| (old_ttu_regs.refcyc_per_req_delivery_pre_cur0 !=
new_ttu_regs->refcyc_per_req_delivery_pre_cur0)
|| (old_ttu_regs.min_ttu_vblank != new_ttu_regs->min_ttu_vblank)
|| (old_ttu_regs.qos_level_flip != new_ttu_regs->qos_level_flip)) {
old_dlg_regs.dst_y_prefetch = new_dlg_regs->dst_y_prefetch;
old_dlg_regs.vratio_prefetch = new_dlg_regs->vratio_prefetch;
old_dlg_regs.vratio_prefetch_c = new_dlg_regs->vratio_prefetch_c;
old_dlg_regs.dst_y_per_vm_vblank = new_dlg_regs->dst_y_per_vm_vblank;
old_dlg_regs.dst_y_per_row_vblank = new_dlg_regs->dst_y_per_row_vblank;
old_dlg_regs.dst_y_per_vm_flip = new_dlg_regs->dst_y_per_vm_flip;
old_dlg_regs.dst_y_per_row_flip = new_dlg_regs->dst_y_per_row_flip;
old_dlg_regs.refcyc_per_meta_chunk_vblank_l = new_dlg_regs->refcyc_per_meta_chunk_vblank_l;
old_dlg_regs.refcyc_per_meta_chunk_vblank_c = new_dlg_regs->refcyc_per_meta_chunk_vblank_c;
old_dlg_regs.refcyc_per_meta_chunk_flip_l = new_dlg_regs->refcyc_per_meta_chunk_flip_l;
old_dlg_regs.refcyc_per_line_delivery_pre_l = new_dlg_regs->refcyc_per_line_delivery_pre_l;
old_dlg_regs.refcyc_per_line_delivery_pre_c = new_dlg_regs->refcyc_per_line_delivery_pre_c;
old_ttu_regs.refcyc_per_req_delivery_pre_l = new_ttu_regs->refcyc_per_req_delivery_pre_l;
old_ttu_regs.refcyc_per_req_delivery_pre_c = new_ttu_regs->refcyc_per_req_delivery_pre_c;
old_ttu_regs.refcyc_per_req_delivery_pre_cur0 = new_ttu_regs->refcyc_per_req_delivery_pre_cur0;
old_ttu_regs.min_ttu_vblank = new_ttu_regs->min_ttu_vblank;
old_ttu_regs.qos_level_flip = new_ttu_regs->qos_level_flip;
new_pipe->update_flags.bits.hubp_interdependent = 1;
}
/* Detect any other updates to ttu/rq/dlg */
if (memcmp(&old_dlg_regs, new_dlg_regs, sizeof(old_dlg_regs)) ||
memcmp(&old_ttu_regs, new_ttu_regs, sizeof(old_ttu_regs)) ||
memcmp(&old_rq_regs, new_rq_regs, sizeof(old_rq_regs)))
new_pipe->update_flags.bits.hubp_rq_dlg_ttu = 1;
}
if (old_pipe->unbounded_req != new_pipe->unbounded_req)
new_pipe->update_flags.bits.unbounded_req = 1;
if (memcmp(&old_pipe->stream_res.test_pattern_params,
&new_pipe->stream_res.test_pattern_params, sizeof(struct test_pattern_params))) {
new_pipe->update_flags.bits.test_pattern_changed = 1;
}
}
void dcn401_plane_atomic_power_down(struct dc *dc,
struct dpp *dpp,
struct hubp *hubp)
{
struct dce_hwseq *hws = dc->hwseq;
uint32_t org_ip_request_cntl = 0;
DC_LOGGER_INIT(dc->ctx->logger);
REG_GET(DC_IP_REQUEST_CNTL, IP_REQUEST_EN, &org_ip_request_cntl);
if (org_ip_request_cntl == 0)
REG_SET(DC_IP_REQUEST_CNTL, 0,
IP_REQUEST_EN, 1);
if (hws->funcs.dpp_pg_control)
hws->funcs.dpp_pg_control(hws, dpp->inst, false);
if (hws->funcs.hubp_pg_control)
hws->funcs.hubp_pg_control(hws, hubp->inst, false);
hubp->funcs->hubp_reset(hubp);
dpp->funcs->dpp_reset(dpp);
if (org_ip_request_cntl == 0)
REG_SET(DC_IP_REQUEST_CNTL, 0,
IP_REQUEST_EN, 0);
DC_LOG_DEBUG(
"Power gated front end %d\n", hubp->inst);
if (hws->funcs.dpp_root_clock_control)
hws->funcs.dpp_root_clock_control(hws, dpp->inst, false);
}