/* * Copyright 2016 Advanced Micro Devices, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * * Authors: AMD *
*/
/* * On pipe unlock and programming, indicate pipe will be busy * until some frame and line (vupdate), this is required for consecutive * full updates, need to wait for updates * to latch to try and program the next update
*/ void dcn10_set_wait_for_update_needed_for_pipe(struct dc *dc, struct pipe_ctx *pipe_ctx)
{
uint32_t vupdate_start, vupdate_end; struct crtc_position position; unsignedint vpos, cur_frame;
if (!pipe_ctx->stream ||
!pipe_ctx->stream_res.tg ||
!pipe_ctx->stream_res.stream_enc) return;
for (i = 0; i < dc->res_pool->pipe_count; i++) {
old_pipe_ctx = &dc->current_state->res_ctx.pipe_ctx[i];
pipe_ctx = &context->res_ctx.pipe_ctx[i];
tg = pipe_ctx->stream_res.tg;
/* * Only lock the top pipe's tg to prevent redundant * (un)locking. Also skip if pipe is disabled.
*/ if (pipe_ctx->top_pipe ||
!pipe_ctx->stream ||
(!pipe_ctx->plane_state && !old_pipe_ctx->plane_state) ||
!tg->funcs->is_tg_enabled(tg) ||
dc_state_get_pipe_subvp_type(context, pipe_ctx) == SUBVP_PHANTOM) continue;
for (i = 0; i < pool->timing_generator_count; i++) { struct timing_generator *tg = pool->timing_generators[i]; struct dcn_otg_state s = {0}; /* Read shared OTG state registers for all DCNx */ if (tg->funcs->read_otg_state)
tg->funcs->read_otg_state(tg, &s);
/* * For DCN2 and greater, a register on the OPP is used to * determine if the CRTC is blanked instead of the OTG. So use * dpg_is_blanked() if exists, otherwise fallback on otg. * * TODO: Implement DCN-specific read_otg_state hooks.
*/ if (pool->opps[i]->funcs->dpg_is_blanked)
s.blank_enabled = pool->opps[i]->funcs->dpg_is_blanked(pool->opps[i]); else
s.blank_enabled = tg->funcs->is_blanked(tg);
//only print if OTG master is enabled if ((s.otg_enabled & 1) == 0) continue;
// Clear underflow for debug purposes // We want to keep underflow sticky bit on for the longevity tests outside of test environment. // This function is called only from Windows or Diags test environment, hence it's safe to clear // it from here without affecting the original intent.
tg->funcs->clear_optc_underflow(tg);
}
DTN_INFO("\n");
// dcn_dsc_state struct field bytes_per_pixel was renamed to bits_per_pixel // TODO: Update golden log header to reflect this name change
DTN_INFO("DSC: CLOCK_EN SLICE_WIDTH Bytes_pp\n"); for (i = 0; i < pool->res_cap->num_dsc; i++) { struct display_stream_compressor *dsc = pool->dscs[i]; struct dcn_dsc_state s = {0};
/* HW Engineer's Notes: * During switch from vga->extended, if we set the VGA_TEST_ENABLE and * then hit the VGA_TEST_RENDER_START, then the DCHUBP timing gets updated correctly. * * Then vBIOS will have it poll for the VGA_TEST_RENDER_DONE and unset * VGA_TEST_ENABLE, to leave it in the same state as before.
*/
REG_UPDATE(VGA_TEST_CONTROL, VGA_TEST_ENABLE, 1);
REG_UPDATE(VGA_TEST_CONTROL, VGA_TEST_RENDER_START, 1);
}
/** * dcn10_dpp_pg_control - DPP power gate control. * * @hws: dce_hwseq reference. * @dpp_inst: DPP instance reference. * @power_on: true if we want to enable power gate, false otherwise. * * Enable or disable power gate in the specific DPP instance.
*/ void dcn10_dpp_pg_control( struct dce_hwseq *hws, unsignedint dpp_inst, bool power_on)
{
uint32_t power_gate = power_on ? 0 : 1;
uint32_t pwr_status = power_on ? PGFSM_POWER_ON : PGFSM_POWER_OFF;
if (hws->ctx->dc->debug.disable_dpp_power_gate) return; if (REG(DOMAIN1_PG_CONFIG) == 0) return;
if (hws->funcs.s0i3_golden_init_wa && hws->funcs.s0i3_golden_init_wa(dc)) return;
if (dc->res_pool->hubbub->funcs->is_allow_self_refresh_enabled)
allow_self_fresh_force_enable =
dc->res_pool->hubbub->funcs->is_allow_self_refresh_enabled(dc->res_pool->hubbub);
/* WA for making DF sleep when idle after resume from S0i3. * DCHUBBUB_ARB_ALLOW_SELF_REFRESH_FORCE_ENABLE is set to 1 by * command table, if DCHUBBUB_ARB_ALLOW_SELF_REFRESH_FORCE_ENABLE = 0 * before calling command table and it changed to 1 after, * it should be set back to 0.
*/
/* initialize dcn global */
bp->funcs->enable_disp_power_gating(bp,
CONTROLLER_ID_D0, ASIC_PIPE_INIT);
for (i = 0; i < dc->res_pool->pipe_count; i++) { /* initialize dcn per pipe */
bp->funcs->enable_disp_power_gating(bp,
CONTROLLER_ID_D0 + i, ASIC_PIPE_DISABLE);
}
if (dc->res_pool->hubbub->funcs->allow_self_refresh_control) if (allow_self_fresh_force_enable == false &&
dc->res_pool->hubbub->funcs->is_allow_self_refresh_enabled(dc->res_pool->hubbub))
dc->res_pool->hubbub->funcs->allow_self_refresh_control(dc->res_pool->hubbub,
!dc->res_pool->hubbub->ctx->dc->debug.disable_stutter);
}
staticvoid false_optc_underflow_wa( struct dc *dc, conststruct dc_stream_state *stream, struct timing_generator *tg)
{ int i; bool underflow;
/* by upper caller loop, pipe0 is parent pipe and be called first. * back end is set up by for pipe0. Other children pipe share back end * with pipe 0. No program is needed.
*/ if (pipe_ctx->top_pipe != NULL) return DC_OK;
/* TODO check if timing_changed, disable stream if timing changed */
/* 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);
pipe_ctx->stream_res.opp->funcs->opp_program_fmt(
pipe_ctx->stream_res.opp,
&stream->bit_depth_params,
&stream->clamping); #endif /* program otg blank color */
color_space = stream->output_color_space;
color_space_to_black_color(dc, color_space, &black_color);
/* * The way 420 is packed, 2 channels carry Y component, 1 channel * alternate between Cb and Cr, so both channels need the pixel * value for Y
*/ if (stream->timing.pixel_encoding == PIXEL_ENCODING_YCBCR420)
black_color.color_r_cr = black_color.color_g_y;
if (pipe_ctx->stream_res.tg->funcs->set_blank_color)
pipe_ctx->stream_res.tg->funcs->set_blank_color(
pipe_ctx->stream_res.tg,
&black_color);
/* 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;
}
/* 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 */
return DC_OK;
}
staticvoid dcn10_reset_back_end_for_pipe( struct dc *dc, struct pipe_ctx *pipe_ctx, struct dc_state *context)
{ int i; struct dc_link *link;
DC_LOGGER_INIT(dc->ctx->logger); if (pipe_ctx->stream_res.stream_enc == NULL) {
pipe_ctx->stream = NULL; return;
}
link = pipe_ctx->stream->link; /* 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); elseif (pipe_ctx->stream_res.audio)
dc->hwss.disable_audio_stream(pipe_ctx);
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) {
if (pipe_ctx->stream_res.abm)
dc->hwss.set_abm_immediate_disable(pipe_ctx);
for (i = 0; i < dc->res_pool->pipe_count; i++) { struct pipe_ctx *pipe_ctx =
&dc->current_state->res_ctx.pipe_ctx[i]; if (pipe_ctx != NULL) {
hubp = pipe_ctx->plane_res.hubp; /*DCHUBP_CNTL:HUBP_BLANK_EN=1*/ if (hubp != NULL && hubp->funcs->set_hubp_blank_en)
hubp->funcs->set_hubp_blank_en(hubp, true);
}
} /*DCHUBBUB_SOFT_RESET:DCHUBBUB_GLOBAL_SOFT_RESET=1*/
hubbub1_soft_reset(dc->res_pool->hubbub, true);
for (i = 0; i < dc->res_pool->pipe_count; i++) { struct pipe_ctx *pipe_ctx =
&dc->current_state->res_ctx.pipe_ctx[i]; if (pipe_ctx != NULL) {
hubp = pipe_ctx->plane_res.hubp; /*DCHUBP_CNTL:HUBP_DISABLE=1*/ if (hubp != NULL && hubp->funcs->hubp_disable_control)
hubp->funcs->hubp_disable_control(hubp, true);
}
} for (i = 0; i < dc->res_pool->pipe_count; i++) { struct pipe_ctx *pipe_ctx =
&dc->current_state->res_ctx.pipe_ctx[i]; if (pipe_ctx != NULL) {
hubp = pipe_ctx->plane_res.hubp; /*DCHUBP_CNTL:HUBP_DISABLE=0*/ if (hubp != NULL && hubp->funcs->hubp_disable_control)
hubp->funcs->hubp_disable_control(hubp, true);
}
} /*DCHUBBUB_SOFT_RESET:DCHUBBUB_GLOBAL_SOFT_RESET=0*/
hubbub1_soft_reset(dc->res_pool->hubbub, false); for (i = 0; i < dc->res_pool->pipe_count; i++) { struct pipe_ctx *pipe_ctx =
&dc->current_state->res_ctx.pipe_ctx[i]; if (pipe_ctx != NULL) {
hubp = pipe_ctx->plane_res.hubp; /*DCHUBP_CNTL:HUBP_BLANK_EN=0*/ if (hubp != NULL && hubp->funcs->set_hubp_blank_en)
hubp->funcs->set_hubp_blank_en(hubp, true);
}
} returntrue;
}
void dcn10_verify_allow_pstate_change_high(struct dc *dc)
{ struct hubbub *hubbub = dc->res_pool->hubbub; staticbool should_log_hw_state; /* prevent hw state log by default */
if (!hubbub->funcs->verify_allow_pstate_change_high) return;
if (!hubbub->funcs->verify_allow_pstate_change_high(hubbub)) { int i = 0;
if (should_log_hw_state)
dcn10_log_hw_state(dc, NULL);
TRACE_DC_PIPE_STATE(pipe_ctx, i, MAX_PIPES);
BREAK_TO_DEBUGGER(); if (dcn10_hw_wa_force_recovery(dc)) { /*check again*/ if (!hubbub->funcs->verify_allow_pstate_change_high(hubbub))
BREAK_TO_DEBUGGER();
}
}
}
/* trigger HW to start disconnect plane from stream on the next vsync */ void dcn10_plane_atomic_disconnect(struct dc *dc, struct dc_state *state, struct pipe_ctx *pipe_ctx)
{ struct dce_hwseq *hws = dc->hwseq; struct hubp *hubp = pipe_ctx->plane_res.hubp; int dpp_id = pipe_ctx->plane_res.dpp->inst; struct mpc *mpc = dc->res_pool->mpc; struct mpc_tree *mpc_tree_params; struct mpcc *mpcc_to_remove = NULL; struct output_pixel_processor *opp = pipe_ctx->stream_res.opp;
/*Already reset*/ if (mpcc_to_remove == NULL) return;
mpc->funcs->remove_mpcc(mpc, mpc_tree_params, mpcc_to_remove); // Phantom pipes have OTG disabled by default, so MPCC_STATUS will never assert idle, // so don't wait for MPCC_IDLE in the programming sequence if (dc_state_get_pipe_subvp_type(state, pipe_ctx) != SUBVP_PHANTOM)
opp->mpcc_disconnect_pending[pipe_ctx->plane_res.mpcc_inst] = true;
dc->optimized_required = true;
if (hubp->funcs->hubp_disconnect)
hubp->funcs->hubp_disconnect(hubp);
if (dc->debug.sanity_checks)
hws->funcs.verify_allow_pstate_change_high(dc);
}
/** * dcn10_plane_atomic_power_down - Power down plane components. * * @dc: dc struct reference. used for grab hwseq. * @dpp: dpp struct reference. * @hubp: hubp struct reference. * * Keep in mind that this operation requires a power gate configuration; * however, requests for switch power gate are precisely controlled to avoid * problems. For this reason, power gate request is usually disabled. This * function first needs to enable the power gate request before disabling DPP * and HUBP. Finally, it disables the power gate request again.
*/ void dcn10_plane_atomic_power_down(struct dc *dc, struct dpp *dpp, struct hubp *hubp)
{ struct dce_hwseq *hws = dc->hwseq;
DC_LOGGER_INIT(dc->ctx->logger);
if (REG(DC_IP_REQUEST_CNTL)) {
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);
for (i = 0; i < context->stream_count; i++) { if (context->streams[i]->apply_seamless_boot_optimization) {
can_apply_seamless_boot = true; break;
}
}
for (i = 0; i < dc->res_pool->pipe_count; i++) { struct timing_generator *tg = dc->res_pool->timing_generators[i]; struct pipe_ctx *pipe_ctx = &context->res_ctx.pipe_ctx[i];
/* There is assumption that pipe_ctx is not mapping irregularly * to non-preferred front end. If pipe_ctx->stream is not NULL, * we will use the pipe, so don't disable
*/ if (pipe_ctx->stream != NULL && can_apply_seamless_boot) continue;
/* Blank controller using driver code instead of * command table.
*/ if (tg->funcs->is_tg_enabled(tg)) { if (hws->funcs.init_blank != NULL) {
hws->funcs.init_blank(dc, tg);
tg->funcs->lock(tg);
} else {
tg->funcs->lock(tg);
tg->funcs->set_blank(tg, true);
hwss_wait_for_blank_complete(tg);
}
}
}
/* Reset det size */ for (i = 0; i < dc->res_pool->pipe_count; i++) { struct pipe_ctx *pipe_ctx = &context->res_ctx.pipe_ctx[i]; struct hubp *hubp = dc->res_pool->hubps[i];
/* Do not need to reset for seamless boot */ if (pipe_ctx->stream != NULL && can_apply_seamless_boot) continue;
if (hubbub && hubp) { if (hubbub->funcs->program_det_size)
hubbub->funcs->program_det_size(hubbub, hubp->inst, 0); if (hubbub->funcs->program_det_segments)
hubbub->funcs->program_det_segments(hubbub, hubp->inst, 0);
}
}
/* num_opp will be equal to number of mpcc */ for (i = 0; i < dc->res_pool->res_cap->num_opp; i++) { struct pipe_ctx *pipe_ctx = &context->res_ctx.pipe_ctx[i];
/* Cannot reset the MPC mux if seamless boot */ if (pipe_ctx->stream != NULL && can_apply_seamless_boot) continue;
for (i = 0; i < dc->res_pool->pipe_count; i++) { struct timing_generator *tg = dc->res_pool->timing_generators[i]; struct hubp *hubp = dc->res_pool->hubps[i]; struct dpp *dpp = dc->res_pool->dpps[i]; struct pipe_ctx *pipe_ctx = &context->res_ctx.pipe_ctx[i];
/* There is assumption that pipe_ctx is not mapping irregularly * to non-preferred front end. If pipe_ctx->stream is not NULL, * we will use the pipe, so don't disable
*/ if (can_apply_seamless_boot &&
pipe_ctx->stream != NULL &&
pipe_ctx->stream_res.tg->funcs->is_tg_enabled(
pipe_ctx->stream_res.tg)) { // Enable double buffering for OTG_BLANK no matter if // seamless boot is enabled or not to suppress global sync // signals when OTG blanked. This is to prevent pipe from // requesting data while in PSR.
tg->funcs->tg_init(tg);
hubp->power_gated = true;
tg_enabled[i] = true; continue;
}
/* Disable on the current state so the new one isn't cleared. */
pipe_ctx = &dc->current_state->res_ctx.pipe_ctx[i];
if (tg->funcs->is_tg_enabled(tg)) { if (tg->funcs->init_odm)
tg->funcs->init_odm(tg);
}
tg->funcs->tg_init(tg);
}
/* Clean up MPC tree */ for (i = 0; i < dc->res_pool->pipe_count; i++) { if (tg_enabled[i]) { if (dc->res_pool->opps[i]->mpc_tree_params.opp_list) { if (dc->res_pool->opps[i]->mpc_tree_params.opp_list->mpcc_bot) { int bot_id = dc->res_pool->opps[i]->mpc_tree_params.opp_list->mpcc_bot->mpcc_id;
// Step 1: To find out which OPTC is running & OPTC DSC is ON // We can't use res_pool->res_cap->num_timing_generator to check // Because it records display pipes default setting built in driver, // not display pipes of the current chip. // Some ASICs would be fused display pipes less than the default setting. // In dcnxx_resource_construct function, driver would obatin real information. for (i = 0; i < dc->res_pool->timing_generator_count; i++) {
uint32_t optc_dsc_state = 0; struct timing_generator *tg = dc->res_pool->timing_generators[i];
if (tg->funcs->is_tg_enabled(tg)) { if (tg->funcs->get_dsc_status)
tg->funcs->get_dsc_status(tg, &optc_dsc_state); // Only one OPTC with DSC is ON, so if we got one result, we would exit this block. // non-zero value is DSC enabled if (optc_dsc_state != 0) {
tg->funcs->get_optc_source(tg, &num_opps, &opp_id_src0, &opp_id_src1); break;
}
}
}
// Step 2: To power down DSC but skip DSC of running OPTC for (i = 0; i < dc->res_pool->res_cap->num_dsc; i++) { struct dcn_dsc_state s = {0};
(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];
if (!is_optimized_init_done)
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; 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;
}
}
/* we want to turn off all dp displays before doing detection */
dc->link_srv->blank_all_dp_displays(dc);
if (hws->funcs.enable_power_gating_plane)
hws->funcs.enable_power_gating_plane(dc->hwseq, true);
/* 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) { if (!is_optimized_init_done) {
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);
}
}
if (!is_optimized_init_done) {
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];
/* power AFMT HDMI memory TODO: may move to dis/en output save power*/ if (!is_optimized_init_done)
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);
}
if (dc->clk_mgr && dc->clk_mgr->funcs->notify_wm_ranges)
dc->clk_mgr->funcs->notify_wm_ranges(dc->clk_mgr);
}
/* 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
*/ void dcn10_power_down_on_boot(struct dc *dc)
{ struct dc_link *edp_links[MAX_NUM_EDP]; struct dc_link *edp_link = NULL; int edp_num; int i = 0;
dc_get_edp_links(dc, edp_links, &edp_num); if (edp_num)
edp_link = edp_links[0];
if (edp_link && edp_link->link_enc->funcs->is_dig_enabled &&
edp_link->link_enc->funcs->is_dig_enabled(edp_link->link_enc) &&
dc->hwseq->funcs.edp_backlight_control &&
dc->hwseq->funcs.power_down &&
dc->hwss.edp_power_control) {
dc->hwseq->funcs.edp_backlight_control(edp_link, false);
dc->hwseq->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];
/* * Call update_clocks with empty context * to send DISPLAY_OFF * Otherwise DISPLAY_OFF may not be asserted
*/ if (dc->clk_mgr->funcs->set_low_power_state)
dc->clk_mgr->funcs->set_low_power_state(dc->clk_mgr);
}
void dcn10_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 (!dpp_base->ctx->dc->debug.always_use_regamma
&& !plane_state->gamma_correction.is_identity
&& dce_use_lut(plane_state->format))
dpp_base->funcs->dpp_program_input_lut(dpp_base, &plane_state->gamma_correction);
if (tf->type == TF_TYPE_PREDEFINED) { switch (tf->tf) { case TRANSFER_FUNCTION_SRGB:
dpp_base->funcs->dpp_set_degamma(dpp_base, IPP_DEGAMMA_MODE_HW_sRGB); break; case TRANSFER_FUNCTION_BT709:
dpp_base->funcs->dpp_set_degamma(dpp_base, IPP_DEGAMMA_MODE_HW_xvYCC); break; case TRANSFER_FUNCTION_LINEAR:
dpp_base->funcs->dpp_set_degamma(dpp_base, IPP_DEGAMMA_MODE_BYPASS); break; case TRANSFER_FUNCTION_PQ:
dpp_base->funcs->dpp_set_degamma(dpp_base, IPP_DEGAMMA_MODE_USER_PWL);
cm_helper_translate_curve_to_degamma_hw_format(tf, &dpp_base->degamma_params);
dpp_base->funcs->dpp_program_degamma_pwl(dpp_base, &dpp_base->degamma_params);
result = true; break; default:
result = false; break;
}
} elseif (tf->type == TF_TYPE_BYPASS) {
dpp_base->funcs->dpp_set_degamma(dpp_base, IPP_DEGAMMA_MODE_BYPASS);
} else {
cm_helper_translate_curve_to_degamma_hw_format(tf,
&dpp_base->degamma_params);
dpp_base->funcs->dpp_program_degamma_pwl(dpp_base,
&dpp_base->degamma_params);
result = true;
}
return result;
}
#define MAX_NUM_HW_POINTS 0x200
staticvoid log_tf(struct dc_context *ctx, conststruct dc_transfer_func *tf, uint32_t hw_points_num)
{ // DC_LOG_GAMMA is default logging of all hw points // DC_LOG_ALL_GAMMA logs all points, not only hw points // DC_LOG_ALL_TF_POINTS logs all channels of the tf int i = 0;
DC_LOG_GAMMA("Gamma Correction TF");
DC_LOG_ALL_GAMMA("Logging all tf points...");
DC_LOG_ALL_TF_CHANNELS("Logging all channels...");
for (i = 0; i < hw_points_num; i++) {
DC_LOG_GAMMA("R\t%d\t%llu", i, tf->tf_pts.red[i].value);
DC_LOG_ALL_TF_CHANNELS("G\t%d\t%llu", i, tf->tf_pts.green[i].value);
DC_LOG_ALL_TF_CHANNELS("B\t%d\t%llu", i, tf->tf_pts.blue[i].value);
}
for (i = hw_points_num; i < MAX_NUM_HW_POINTS; i++) {
DC_LOG_ALL_GAMMA("R\t%d\t%llu", i, tf->tf_pts.red[i].value);
DC_LOG_ALL_TF_CHANNELS("G\t%d\t%llu", i, tf->tf_pts.green[i].value);
DC_LOG_ALL_TF_CHANNELS("B\t%d\t%llu", i, tf->tf_pts.blue[i].value);
}
}
/* use TG master update lock to lock everything on the TG * therefore only top pipe need to lock
*/ if (!pipe || pipe->top_pipe) return;
if (dc->debug.sanity_checks)
hws->funcs.verify_allow_pstate_change_high(dc);
if (lock)
pipe->stream_res.tg->funcs->lock(pipe->stream_res.tg); else
pipe->stream_res.tg->funcs->unlock(pipe->stream_res.tg);
if (dc->debug.sanity_checks)
hws->funcs.verify_allow_pstate_change_high(dc);
}
/** * delay_cursor_until_vupdate() - Delay cursor update if too close to VUPDATE. * * Software keepout workaround to prevent cursor update locking from stalling * out cursor updates indefinitely or from old values from being retained in * the case where the viewport changes in the same frame as the cursor. * * The idea is to calculate the remaining time from VPOS to VUPDATE. If it's * too close to VUPDATE, then stall out until VUPDATE finishes. * * TODO: Optimize cursor programming to be once per frame before VUPDATE * to avoid the need for this workaround. * * @dc: Current DC state * @pipe_ctx: Pipe_ctx pointer for delayed cursor update * * Return: void
*/ staticvoid delay_cursor_until_vupdate(struct dc *dc, struct pipe_ctx *pipe_ctx)
{ struct dc_stream_state *stream = pipe_ctx->stream; struct crtc_position position;
uint32_t vupdate_start, vupdate_end; unsignedint lines_to_vupdate, us_to_vupdate, vpos; unsignedint us_per_line, us_vupdate;
if (!dc->hwss.calc_vupdate_position || !dc->hwss.get_position) return;
if (!pipe_ctx->stream_res.stream_enc || !pipe_ctx->stream_res.tg) return;
void dcn10_cursor_lock(struct dc *dc, struct pipe_ctx *pipe, bool lock)
{ /* cursor lock is per MPCC tree, so only need to lock one pipe per stream */ if (!pipe || pipe->top_pipe) return;
/* Prevent cursor lock from stalling out cursor updates. */ if (lock)
delay_cursor_until_vupdate(dc, pipe);
if (pipe->stream && should_use_dmub_lock(pipe->stream->link)) { union dmub_hw_lock_flags hw_locks = { 0 }; struct dmub_hw_lock_inst_flags inst_flags = { 0 };
/* To avoid endless loop we wait at most
* frames_to_wait_on_triggered_reset frames for the reset to occur. */ const uint32_t frames_to_wait_on_triggered_reset = 10; int i;
for (i = 0; i < frames_to_wait_on_triggered_reset; i++) {
if (!tg->funcs->is_counter_moving(tg)) {
DC_ERROR("TG counter is not moving!\n"); break;
}
if (tg->funcs->did_triggered_reset_occur(tg)) {
rc = true; /* usually occurs at i=1 */
DC_SYNC_INFO("GSL: reset occurred at wait count: %d\n",
i); break;
}
/* Wait for one frame. */
tg->funcs->wait_for_state(tg, CRTC_STATE_VACTIVE);
tg->funcs->wait_for_state(tg, CRTC_STATE_VBLANK);
}
if (false == rc)
DC_ERROR("GSL: Timeout on reset trigger!\n");
if (reduceSizeAndFraction(&phase[i],
&modulo[i], true) == false) { /* * this will help to stop reporting * this timing synchronizable
*/
DC_SYNC_INFO("Failed to reduce DTO parameters\n");
grouped_pipes[i]->stream->has_non_synchronizable_pclk = true;
}
}
}
for (i = 0; i < group_size; i++) { if (i != embedded && !grouped_pipes[i]->stream->has_non_synchronizable_pclk) {
dc->res_pool->dp_clock_source->funcs->override_dp_pix_clk(
dc->res_pool->dp_clock_source,
grouped_pipes[i]->stream_res.tg->inst,
phase[i], modulo[i]);
dc->res_pool->dp_clock_source->funcs->get_pixel_clk_frequency_100hz(
dc->res_pool->dp_clock_source,
grouped_pipes[i]->stream_res.tg->inst, &pclk);
grouped_pipes[i]->stream->timing.pix_clk_100hz =
pclk*get_clock_divider(grouped_pipes[i], false); if (master == -1)
master = i;
}
}
}
kfree(hw_crtc_timing); return master;
}
void dcn10_enable_vblanks_synchronization( struct dc *dc, int group_index, int group_size, struct pipe_ctx *grouped_pipes[])
{ struct dc_context *dc_ctx = dc->ctx; struct output_pixel_processor *opp; struct timing_generator *tg; int i, width = 0, height = 0, master;
DC_LOGGER_INIT(dc_ctx->logger);
for (i = 1; i < group_size; i++) {
opp = grouped_pipes[i]->stream_res.opp;
tg = grouped_pipes[i]->stream_res.tg;
tg->funcs->get_otg_active_size(tg, &width, &height);
if (!tg->funcs->is_tg_enabled(tg)) {
DC_SYNC_INFO("Skipping timing sync on disabled OTG\n"); return;
}
if (opp->funcs->opp_program_dpg_dimensions)
opp->funcs->opp_program_dpg_dimensions(opp, width, 2*(height) + 1);
}
for (i = 0; i < group_size; i++) { if (grouped_pipes[i]->stream == NULL) continue;
grouped_pipes[i]->stream->vblank_synchronized = false;
grouped_pipes[i]->stream->has_non_synchronizable_pclk = false;
}
if (master >= 0) { for (i = 0; i < group_size; i++) { if (i != master && !grouped_pipes[i]->stream->has_non_synchronizable_pclk)
grouped_pipes[i]->stream_res.tg->funcs->align_vblanks(
grouped_pipes[master]->stream_res.tg,
grouped_pipes[i]->stream_res.tg,
grouped_pipes[master]->stream->timing.pix_clk_100hz,
grouped_pipes[i]->stream->timing.pix_clk_100hz,
get_clock_divider(grouped_pipes[master], false),
get_clock_divider(grouped_pipes[i], false));
grouped_pipes[i]->stream->vblank_synchronized = true;
}
grouped_pipes[master]->stream->vblank_synchronized = true;
DC_SYNC_INFO("Sync complete\n");
}
for (i = 1; i < group_size; i++) {
opp = grouped_pipes[i]->stream_res.opp;
tg = grouped_pipes[i]->stream_res.tg;
tg->funcs->get_otg_active_size(tg, &width, &height); if (opp->funcs->opp_program_dpg_dimensions)
opp->funcs->opp_program_dpg_dimensions(opp, width, height);
}
}
void dcn10_enable_timing_synchronization( struct dc *dc, struct dc_state *state, int group_index, int group_size, struct pipe_ctx *grouped_pipes[])
{ struct dc_context *dc_ctx = dc->ctx; struct output_pixel_processor *opp; struct timing_generator *tg; int i, width = 0, height = 0;
DC_LOGGER_INIT(dc_ctx->logger);
DC_SYNC_INFO("Setting up OTG reset trigger\n");
for (i = 1; i < group_size; i++) { if (grouped_pipes[i]->stream && dc_state_get_pipe_subvp_type(state, grouped_pipes[i]) == SUBVP_PHANTOM) continue;
opp = grouped_pipes[i]->stream_res.opp;
tg = grouped_pipes[i]->stream_res.tg;
tg->funcs->get_otg_active_size(tg, &width, &height);
if (!tg->funcs->is_tg_enabled(tg)) {
DC_SYNC_INFO("Skipping timing sync on disabled OTG\n"); return;
}
if (opp->funcs->opp_program_dpg_dimensions)
opp->funcs->opp_program_dpg_dimensions(opp, width, 2*(height) + 1);
}
for (i = 0; i < group_size; i++) { if (grouped_pipes[i]->stream == NULL) continue;
if (grouped_pipes[i]->stream && dc_state_get_pipe_subvp_type(state, grouped_pipes[i]) == SUBVP_PHANTOM) continue;
for (i = 1; i < group_size; i++) { if (dc_state_get_pipe_subvp_type(state, grouped_pipes[i]) == SUBVP_PHANTOM) continue;
opp = grouped_pipes[i]->stream_res.opp;
tg = grouped_pipes[i]->stream_res.tg;
tg->funcs->get_otg_active_size(tg, &width, &height); if (opp->funcs->opp_program_dpg_dimensions)
opp->funcs->opp_program_dpg_dimensions(opp, width, height);
}
DC_SYNC_INFO("Sync complete\n");
}
void dcn10_enable_per_frame_crtc_position_reset( struct dc *dc, int group_size, struct pipe_ctx *grouped_pipes[])
{ struct dc_context *dc_ctx = dc->ctx; int i;
DC_LOGGER_INIT(dc_ctx->logger);
DC_SYNC_INFO("Setting up\n"); for (i = 0; i < group_size; i++) if (grouped_pipes[i]->stream_res.tg->funcs->enable_crtc_reset)
grouped_pipes[i]->stream_res.tg->funcs->enable_crtc_reset(
grouped_pipes[i]->stream_res.tg,
0,
&grouped_pipes[i]->stream->triggered_crtc_reset);
DC_SYNC_INFO("Waiting for trigger\n");
for (i = 0; i < group_size; i++)
wait_for_reset_trigger_to_occur(dc_ctx, grouped_pipes[i]->stream_res.tg);
DC_SYNC_INFO("Multi-display sync is complete\n");
}
/* * The values in VM_CONTEXT0_PAGE_TABLE_BASE_ADDR is in UMA space. * Therefore we need to do * DCN_VM_CONTEXT0_PAGE_TABLE_BASE_ADDR = VM_CONTEXT0_PAGE_TABLE_BASE_ADDR * - DCHUBBUB_SDPIF_FB_OFFSET + DCHUBBUB_SDPIF_FB_BASE
*/
fb_base.quad_part = (uint64_t)fb_base_value << 24;
fb_offset.quad_part = (uint64_t)fb_offset_value << 24;
vm0->pte_base.quad_part += fb_base.quad_part;
vm0->pte_base.quad_part -= fb_offset.quad_part;
}
while (top->top_pipe)
top = top->top_pipe; // Traverse to top pipe_ctx if (top->plane_state && top->plane_state->layer_index == 0 && !top->plane_state->global_alpha) // Global alpha used by top plane for PIP overlay // Pre-multiplied/per-pixel alpha used by MPO // Check top plane's global alpha to ensure layer_index > 0 not caused by PIP returntrue; // MPO in use and front plane not hidden
}
} returnfalse;
}
void dcn10_program_output_csc(struct dc *dc, struct pipe_ctx *pipe_ctx, enum dc_color_space colorspace,
uint16_t *matrix, int opp_id)
{ if (pipe_ctx->stream->csc_color_matrix.enable_adjustment == true) { if (pipe_ctx->plane_res.dpp->funcs->dpp_set_csc_adjustment != NULL) {
/* MPO is broken with RGB colorspaces when OCSC matrix * brightness offset >= 0 on DCN1 due to OCSC before MPC * Blending adds offsets from front + rear to rear plane * * Fix is to set RGB bias to 0 on rear plane, top plane * black value pixels add offset instead of rear + front
*/
int16_t rgb_bias = matrix[3]; // matrix[3/7/11] are all the same offset value
if (per_pixel_alpha) { /* DCN1.0 has output CM before MPC which seems to screw with * pre-multiplied alpha.
*/
blnd_cfg.pre_multiplied_alpha = (is_rgb_cspace(
pipe_ctx->stream->output_color_space)
&& pipe_ctx->plane_state->pre_multiplied_alpha); if (pipe_ctx->plane_state->global_alpha) {
blnd_cfg.alpha_mode = MPCC_ALPHA_BLEND_MODE_PER_PIXEL_ALPHA_COMBINED_GLOBAL_GAIN;
blnd_cfg.global_gain = pipe_ctx->plane_state->global_alpha_value;
} else {
blnd_cfg.alpha_mode = MPCC_ALPHA_BLEND_MODE_PER_PIXEL_ALPHA;
}
} else {
blnd_cfg.pre_multiplied_alpha = false;
blnd_cfg.alpha_mode = MPCC_ALPHA_BLEND_MODE_GLOBAL_ALPHA;
}
if (pipe_ctx->plane_state->global_alpha)
blnd_cfg.global_alpha = pipe_ctx->plane_state->global_alpha_value; else
blnd_cfg.global_alpha = 0xff;
/* * TODO: remove hack * Note: currently there is a bug in init_hw such that * on resume from hibernate, BIOS sets up MPCC0, and * we do mpcc_remove but the mpcc cannot go to idle * after remove. This cause us to pick mpcc1 here, * which causes a pstate hang for yet unknown reason.
*/
mpcc_id = hubp->inst;
/* If there is no full update, don't need to touch MPC tree*/ if (!pipe_ctx->plane_state->update_flags.bits.full_update) {
mpc->funcs->update_blending(mpc, &blnd_cfg, mpcc_id);
dc->hwss.update_visual_confirm_color(dc, pipe_ctx, mpcc_id); return;
}
/* check if this MPCC is already being used */
new_mpcc = mpc->funcs->get_mpcc_for_dpp(mpc_tree_params, mpcc_id); /* remove MPCC if being used */ if (new_mpcc != NULL)
mpc->funcs->remove_mpcc(mpc, mpc_tree_params, new_mpcc); else if (dc->debug.sanity_checks)
mpc->funcs->assert_mpcc_idle_before_connect(
dc->res_pool->mpc, mpcc_id);
/* Call MPC to insert new plane */
new_mpcc = mpc->funcs->insert_plane(dc->res_pool->mpc,
mpc_tree_params,
&blnd_cfg,
NULL,
NULL,
hubp->inst,
mpcc_id);
dc->hwss.update_visual_confirm_color(dc, pipe_ctx, mpcc_id);
/* depends on DML calculation, DPP clock value may change dynamically */ /* If request max dpp clk is lower than current dispclk, no need to * divided by 2
*/ if (plane_state->update_flags.bits.full_update) {
/* new calculated dispclk, dppclk are stored in * context->bw_ctx.bw.dcn.clk.dispclk_khz / dppclk_khz. current * dispclk, dppclk are from dc->clk_mgr->clks.dispclk_khz. * dcn10_validate_bandwidth compute new dispclk, dppclk. * dispclk will put in use after optimize_bandwidth when * ramp_up_dispclk_with_dpp is called. * there are two places for dppclk be put in use. One location * is the same as the location as dispclk. Another is within * update_dchubp_dpp which happens between pre_bandwidth and * optimize_bandwidth. * dppclk updated within update_dchubp_dpp will cause new * clock values of dispclk and dppclk not be in use at the same * time. when clocks are decreased, this may cause dppclk is * lower than previous configuration and let pipe stuck. * for example, eDP + external dp, change resolution of DP from * 1920x1080x144hz to 1280x960x60hz. * before change: dispclk = 337889 dppclk = 337889 * change mode, dcn10_validate_bandwidth calculate * dispclk = 143122 dppclk = 143122 * update_dchubp_dpp be executed before dispclk be updated, * dispclk = 337889, but dppclk use new value dispclk /2 = * 168944. this will cause pipe pstate warning issue. * solution: between pre_bandwidth and optimize_bandwidth, while * dispclk is going to be decreased, keep dppclk = dispclk
**/ if (context->bw_ctx.bw.dcn.clk.dispclk_khz <
dc->clk_mgr->clks.dispclk_khz)
should_divided_by_2 = false; else
should_divided_by_2 =
context->bw_ctx.bw.dcn.clk.dppclk_khz <=
dc->clk_mgr->clks.dispclk_khz / 2;
/* TODO: Need input parameter to tell current DCHUB pipe tie to which OTG * VTG is within DCHUBBUB which is commond block share by each pipe HUBP. * VTG is 1:1 mapping with OTG. Each pipe HUBP will select which VTG
*/ if (plane_state->update_flags.bits.full_update) {
hubp->funcs->hubp_vtg_sel(hubp, pipe_ctx->stream_res.tg->inst);
/* program otg blank color */
color_space = stream->output_color_space;
color_space_to_black_color(dc, color_space, &black_color);
/* * The way 420 is packed, 2 channels carry Y component, 1 channel * alternate between Cb and Cr, so both channels need the pixel * value for Y
*/ if (stream->timing.pixel_encoding == PIXEL_ENCODING_YCBCR420)
black_color.color_r_cr = black_color.color_g_y;
if (stream_res->tg->funcs->set_blank_color)
stream_res->tg->funcs->set_blank_color(
stream_res->tg,
&black_color);
if (!blank) { if (stream_res->tg->funcs->set_blank)
stream_res->tg->funcs->set_blank(stream_res->tg, blank); if (stream_res->abm) {
dc->hwss.set_pipe(pipe_ctx);
stream_res->abm->funcs->set_abm_level(stream_res->abm, stream->abm_level);
}
} else {
dc->hwss.set_abm_immediate_disable(pipe_ctx); if (stream_res->tg->funcs->set_blank) {
stream_res->tg->funcs->wait_for_state(stream_res->tg, CRTC_STATE_VBLANK);
stream_res->tg->funcs->set_blank(stream_res->tg, blank);
}
}
}
if (pipe_ctx->plane_state->update_flags.bits.full_update)
dcn10_enable_plane(dc, pipe_ctx, context);
dcn10_update_dchubp_dpp(dc, pipe_ctx, context);
hws->funcs.set_hdr_multiplier(pipe_ctx);
if (pipe_ctx->plane_state->update_flags.bits.full_update ||
pipe_ctx->plane_state->update_flags.bits.in_transfer_func_change ||
pipe_ctx->plane_state->update_flags.bits.gamma_change)
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 full update. * TODO: This can be further optimized/cleaned up * Always call this for now since it does memcmp inside before * doing heavy calculation and programming
*/ if (pipe_ctx->plane_state->update_flags.bits.full_update)
hws->funcs.set_output_transfer_func(dc, pipe_ctx, pipe_ctx->stream);
}
void dcn10_wait_for_pending_cleared(struct dc *dc, struct dc_state *context)
{ struct pipe_ctx *pipe_ctx; struct timing_generator *tg; int i;
for (i = 0; i < dc->res_pool->pipe_count; i++) {
pipe_ctx = &context->res_ctx.pipe_ctx[i];
tg = pipe_ctx->stream_res.tg;
/* * Only wait for top pipe's tg penindg bit * Also skip if pipe is disabled.
*/ if (pipe_ctx->top_pipe ||
!pipe_ctx->stream || !pipe_ctx->plane_state ||
!tg->funcs->is_tg_enabled(tg)) continue;
/* * Wait for VBLANK then VACTIVE to ensure we get VUPDATE. * For some reason waiting for OTG_UPDATE_PENDING cleared * seems to not trigger the update right away, and if we * lock again before VUPDATE then we don't get a separated * operation.
*/
pipe_ctx->stream_res.tg->funcs->wait_for_state(pipe_ctx->stream_res.tg, CRTC_STATE_VBLANK);
pipe_ctx->stream_res.tg->funcs->wait_for_state(pipe_ctx->stream_res.tg, CRTC_STATE_VACTIVE);
}
}
void dcn10_post_unlock_program_front_end( struct dc *dc, struct dc_state *context)
{ int i;
for (i = 0; i < dc->res_pool->pipe_count; i++) { struct pipe_ctx *pipe_ctx = &context->res_ctx.pipe_ctx[i];
if (context->stream_status[i].plane_count == 0)
false_optc_underflow_wa(dc, pipe_ctx->stream, tg);
}
}
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]);
for (i = 0; i < dc->res_pool->pipe_count; i++) if (context->res_ctx.pipe_ctx[i].update_flags.bits.disable) {
dc->hwss.optimize_bandwidth(dc, context); break;
}
if (dc->hwseq->wa.DEGVIDCN10_254)
hubbub1_wm_change_req_wa(dc->res_pool->hubbub);
}
staticvoid dcn10_stereo_hw_frame_pack_wa(struct dc *dc, struct dc_state *context)
{
uint8_t i;
for (i = 0; i < context->stream_count; i++) { if (context->streams[i]->timing.timing_3d_format
== TIMING_3D_FORMAT_HW_FRAME_PACKING) { /* * Disable stutter
*/
hubbub1_allow_self_refresh_control(dc->res_pool->hubbub, false); break;
}
}
}
if (dc->debug.sanity_checks)
hws->funcs.verify_allow_pstate_change_high(dc);
}
void dcn10_set_drr(struct pipe_ctx **pipe_ctx, int num_pipes, struct dc_crtc_timing_adjust adjust)
{ int i = 0; struct drr_params params = {0}; // DRR set trigger event mapped to OTG_TRIG_A (bit 11) for manual control flow unsignedint event_triggers = 0x800; // Note DRR trigger events are generated regardless of whether num frames met. unsignedint num_frames = 2;
params.vertical_total_max = adjust.v_total_max;
params.vertical_total_min = adjust.v_total_min;
params.vertical_total_mid = adjust.v_total_mid;
params.vertical_total_mid_frame_num = adjust.v_total_mid_frame_num; /* TODO: If multiple pipes are to be supported, you need * some GSL stuff. Static screen triggers may be programmed differently * as well.
*/ for (i = 0; i < num_pipes; i++) { /* dc_state_destruct() might null the stream resources, so fetch tg * here first to avoid a race condition. The lifetime of the pointee * itself (the timing_generator object) is not a problem here.
*/ struct timing_generator *tg = pipe_ctx[i]->stream_res.tg;
if ((tg != NULL) && tg->funcs) {
set_drr_and_clear_adjust_pending(pipe_ctx[i], pipe_ctx[i]->stream, ¶ms); if (adjust.v_total_max != 0 && adjust.v_total_min != 0) if (tg->funcs->set_static_screen_control)
tg->funcs->set_static_screen_control(
tg, event_triggers, num_frames);
}
}
}
void dcn10_get_position(struct pipe_ctx **pipe_ctx, int num_pipes, struct crtc_position *position)
{ int i = 0;
/* TODO: handle pipes > 1
*/ for (i = 0; i < num_pipes; i++)
pipe_ctx[i]->stream_res.tg->funcs->get_position(pipe_ctx[i]->stream_res.tg, position);
}
if (params->triggers.surface_update)
triggers |= 0x80; if (params->triggers.cursor_update)
triggers |= 0x2; if (params->triggers.force_trigger)
triggers |= 0x1;
for (i = 0; i < num_pipes; i++)
pipe_ctx[i]->stream_res.tg->funcs->
set_static_screen_control(pipe_ctx[i]->stream_res.tg,
triggers, params->num_frames);
}
int x_plane = pipe_ctx->plane_state->dst_rect.x; int y_plane = pipe_ctx->plane_state->dst_rect.y; int x_pos = pos_cpy.x; int y_pos = pos_cpy.y; int clip_x = pipe_ctx->plane_state->clip_rect.x; int clip_width = pipe_ctx->plane_state->clip_rect.width;
/** * DC cursor is stream space, HW cursor is plane space and drawn * as part of the framebuffer. * * Cursor position can't be negative, but hotspot can be used to * shift cursor out of the plane bounds. Hotspot must be smaller * than the cursor size.
*/
/** * Translate cursor and clip offset from stream space to plane space. * * If the cursor is scaled then we need to scale the position * to be in the approximately correct place. We can't do anything * about the actual size being incorrect, that's a limitation of * the hardware.
*/ if (param.rotation == ROTATION_ANGLE_90 || param.rotation == ROTATION_ANGLE_270) {
x_pos = (x_pos - x_plane) * pipe_ctx->plane_state->src_rect.height /
pipe_ctx->plane_state->dst_rect.width;
y_pos = (y_pos - y_plane) * pipe_ctx->plane_state->src_rect.width /
pipe_ctx->plane_state->dst_rect.height;
} else {
x_pos = (x_pos - x_plane) * pipe_ctx->plane_state->src_rect.width /
pipe_ctx->plane_state->dst_rect.width;
y_pos = (y_pos - y_plane) * pipe_ctx->plane_state->src_rect.height /
pipe_ctx->plane_state->dst_rect.height;
clip_x = (clip_x - x_plane) * pipe_ctx->plane_state->src_rect.width /
pipe_ctx->plane_state->dst_rect.width;
clip_width = clip_width * pipe_ctx->plane_state->src_rect.width /
pipe_ctx->plane_state->dst_rect.width;
}
/** * 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;
}
/** * If the position is negative then we need to add to the hotspot * to shift the cursor outside the plane.
*/
if (pipe_ctx->plane_state->address.type
== PLN_ADDR_TYPE_VIDEO_PROGRESSIVE)
pos_cpy.enable = false;
if (pos_cpy.enable && resource_can_pipe_disable_cursor(pipe_ctx))
pos_cpy.enable = false;
if (param.rotation == ROTATION_ANGLE_0) {
if (param.mirror) { /* * The plane is split into multiple viewports. * The combination of all viewports span the * entirety of the clip rect. * * For no pipe_split, viewport_width is represents * the full width of the clip_rect, so we can just * mirror it.
*/
pos_cpy.x = clip_width - pos_cpy.x + 2 * clip_x;
}
} // Swap axis and mirror horizontally elseif (param.rotation == ROTATION_ANGLE_90) {
uint32_t temp_x = pos_cpy.x;
/** * Display groups that are 1xnY, have pos_cpy.x > 2 * viewport.height * For pipe split cases: * - apply offset of viewport.y to normalize pos_cpy.x * - calculate the pos_cpy.y as before * - shift pos_cpy.y back by same offset to get final value * - since we iterate through both pipes, use the lower * viewport.y for offset * For non pipe split cases, use the same calculation for * pos_cpy.y as the 180 degree rotation case below, * but use pos_cpy.x as our input because we are rotating * 270 degrees
*/ if (pipe_split_on || odm_combine_on) { int pos_cpy_x_offset; int other_pipe_viewport_y;
if (pipe_split_on) { if (pipe_ctx->bottom_pipe) {
other_pipe_viewport_y =
pipe_ctx->bottom_pipe->plane_res.scl_data.viewport.y;
} else {
other_pipe_viewport_y =
pipe_ctx->top_pipe->plane_res.scl_data.viewport.y;
}
} else { if (pipe_ctx->next_odm_pipe) {
other_pipe_viewport_y =
pipe_ctx->next_odm_pipe->plane_res.scl_data.viewport.y;
} else {
other_pipe_viewport_y =
pipe_ctx->prev_odm_pipe->plane_res.scl_data.viewport.y;
}
}
pos_cpy_x_offset = (viewport_y > other_pipe_viewport_y) ?
other_pipe_viewport_y : viewport_y;
pos_cpy.x -= pos_cpy_x_offset; if (pos_cpy.x > viewport_height) {
pos_cpy.x = pos_cpy.x - viewport_height;
pos_cpy.y = viewport_height - pos_cpy.x;
} else {
pos_cpy.y = 2 * viewport_height - pos_cpy.x;
}
pos_cpy.y += pos_cpy_x_offset;
} else {
pos_cpy.y = (2 * viewport_y) + viewport_height - pos_cpy.x;
}
pos_cpy.x = temp_y;
} // Mirror horizontally and vertically elseif (param.rotation == ROTATION_ANGLE_180) { if (!param.mirror) { /* * The plane is split into multiple viewports. * The combination of all viewports span the * entirety of the clip rect. * * For no pipe_split, viewport_width is represents * the full width of the clip_rect, so we can just * mirror it.
*/
pos_cpy.x = clip_width - pos_cpy.x + 2 * clip_x;
}
/* * apply_front_porch_workaround TODO FPGA still need? * * This is a workaround for a bug that has existed since R5xx and has not been * fixed keep Front porch at minimum 2 for Interlaced mode or 1 for progressive.
*/ staticvoid apply_front_porch_workaround( struct dc_crtc_timing *timing)
{ if (timing->flags.INTERLACE == 1) { if (timing->v_front_porch < 2)
timing->v_front_porch = 2;
} else { if (timing->v_front_porch < 1)
timing->v_front_porch = 1;
}
}
int dcn10_get_vupdate_offset_from_vsync(struct pipe_ctx *pipe_ctx)
{ conststruct dc_crtc_timing *dc_crtc_timing = &pipe_ctx->stream->timing; struct dc_crtc_timing patched_crtc_timing; int vesa_sync_start; int asic_blank_end; int interlace_factor;
/** * dcn10_reset_surface_dcc_and_tiling - Set DCC and tiling in DCN to their disable mode. * * @pipe_ctx: Pointer to the pipe context structure. * @plane_state: Surface state * @clear_tiling: If true set tiling to Linear, otherwise does not change tiling * * This function is responsible for call the HUBP block to disable DCC and set * tiling to the linear mode.
*/ void dcn10_reset_surface_dcc_and_tiling(struct pipe_ctx *pipe_ctx, struct dc_plane_state *plane_state, bool clear_tiling)
{ struct hubp *hubp = pipe_ctx->plane_res.hubp;
if (!hubp) return;
/* if framebuffer is tiled, disable tiling */ if (clear_tiling && hubp->funcs->hubp_clear_tiling)
hubp->funcs->hubp_clear_tiling(hubp);
/* force page flip to see the new content of the framebuffer */
hubp->funcs->hubp_program_surface_flip_and_addr(hubp,
&plane_state->address, true);
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.52 Sekunden
(vorverarbeitet am 2026-04-28)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.