mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-01-20 05:16:14 -05:00
drm/amd/display: Support long vblank feature
[WHY] We want to support low hz case, but the original vtotal/vtotal_min/vtotal_max can't support more than 0x7FFF. [HOW] We use the 2 HW reg to contorl long vblank case. 1. OTG_V_COUNT_STOP_CONTROL -> vcount_stop 2. OTG_V_COUNT_STOP_CONTROL2 -> vcount_stop_timer vcount_stop define from which line we stop using vcount and start using vcount2. vcount_stop_timer define how long we use vcount2. Ex: Vtotal = 7 OTG_V_COUNT_STOP_CONTROL = 4 OTG_V_COUNT_STOP_CONTROL2 = 5 time : 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 vcount : 0, 1, 2, 3, - - - - - 4, 5, 6 vcount2 : 0, 1, 2, 3, 4, Reviewed-by: Jun Lei <jun.lei@amd.com> Acked-by: Alex Hung <alex.hung@amd.com> Signed-off-by: ChunTao Tso <chuntao.tso@amd.com> Signed-off-by: Robin Chen<robin.chen@amd.com> Tested-by: Daniel Wheeler <daniel.wheeler@amd.com> Signed-off-by: Alex Deucher <alexander.deucher@amd.com>
This commit is contained in:
@@ -386,6 +386,30 @@ static void dc_perf_trace_destroy(struct dc_perf_trace **perf_trace)
|
||||
*perf_trace = NULL;
|
||||
}
|
||||
|
||||
static bool set_long_vtotal(struct dc *dc, struct dc_stream_state *stream, struct dc_crtc_timing_adjust *adjust)
|
||||
{
|
||||
if (!dc || !stream || !adjust)
|
||||
return false;
|
||||
|
||||
if (!dc->current_state)
|
||||
return false;
|
||||
|
||||
int i;
|
||||
|
||||
for (i = 0; i < MAX_PIPES; i++) {
|
||||
struct pipe_ctx *pipe = &dc->current_state->res_ctx.pipe_ctx[i];
|
||||
|
||||
if (pipe->stream == stream && pipe->stream_res.tg) {
|
||||
if (dc->hwss.set_long_vtotal)
|
||||
dc->hwss.set_long_vtotal(&pipe, 1, adjust->v_total_min, adjust->v_total_max);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* dc_stream_adjust_vmin_vmax - look up pipe context & update parts of DRR
|
||||
* @dc: dc reference
|
||||
@@ -420,6 +444,15 @@ bool dc_stream_adjust_vmin_vmax(struct dc *dc,
|
||||
stream->adjust.v_total_mid = adjust->v_total_mid;
|
||||
stream->adjust.v_total_mid_frame_num = adjust->v_total_mid_frame_num;
|
||||
stream->adjust.v_total_min = adjust->v_total_min;
|
||||
stream->adjust.allow_otg_v_count_halt = adjust->allow_otg_v_count_halt;
|
||||
|
||||
if (dc->caps.max_v_total != 0 &&
|
||||
(adjust->v_total_max > dc->caps.max_v_total || adjust->v_total_min > dc->caps.max_v_total)) {
|
||||
if (adjust->allow_otg_v_count_halt)
|
||||
return set_long_vtotal(dc, stream, adjust);
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
for (i = 0; i < MAX_PIPES; i++) {
|
||||
struct pipe_ctx *pipe = &dc->current_state->res_ctx.pipe_ctx[i];
|
||||
|
||||
@@ -974,6 +974,7 @@ struct dc_crtc_timing_adjust {
|
||||
uint32_t v_total_max;
|
||||
uint32_t v_total_mid;
|
||||
uint32_t v_total_mid_frame_num;
|
||||
uint32_t allow_otg_v_count_halt;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -1394,3 +1394,31 @@ void dcn35_set_static_screen_control(struct pipe_ctx **pipe_ctx,
|
||||
set_static_screen_control(pipe_ctx[i]->stream_res.tg,
|
||||
triggers, params->num_frames);
|
||||
}
|
||||
|
||||
void dcn35_set_long_vblank(struct pipe_ctx **pipe_ctx,
|
||||
int num_pipes, uint32_t v_total_min, uint32_t v_total_max)
|
||||
{
|
||||
int i = 0;
|
||||
struct long_vtotal_params params = {0};
|
||||
|
||||
params.vertical_total_max = v_total_max;
|
||||
params.vertical_total_min = v_total_min;
|
||||
|
||||
for (i = 0; i < num_pipes; i++) {
|
||||
if (!pipe_ctx[i])
|
||||
continue;
|
||||
|
||||
if (pipe_ctx[i]->stream) {
|
||||
struct dc_crtc_timing *timing = &pipe_ctx[i]->stream->timing;
|
||||
|
||||
if (timing)
|
||||
params.vertical_blank_start = timing->v_total - timing->v_front_porch;
|
||||
else
|
||||
params.vertical_blank_start = 0;
|
||||
|
||||
if ((pipe_ctx[i]->stream_res.tg != NULL) && pipe_ctx[i]->stream_res.tg->funcs &&
|
||||
pipe_ctx[i]->stream_res.tg->funcs->set_long_vtotal)
|
||||
pipe_ctx[i]->stream_res.tg->funcs->set_long_vtotal(pipe_ctx[i]->stream_res.tg, ¶ms);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,4 +93,7 @@ void dcn35_set_drr(struct pipe_ctx **pipe_ctx,
|
||||
void dcn35_set_static_screen_control(struct pipe_ctx **pipe_ctx,
|
||||
int num_pipes, const struct dc_static_screen_params *params);
|
||||
|
||||
void dcn35_set_long_vblank(struct pipe_ctx **pipe_ctx,
|
||||
int num_pipes, uint32_t v_total_min, uint32_t v_total_max);
|
||||
|
||||
#endif /* __DC_HWSS_DCN35_H__ */
|
||||
|
||||
@@ -122,7 +122,8 @@ static const struct hw_sequencer_funcs dcn35_funcs = {
|
||||
.hw_block_power_down = dcn35_hw_block_power_down,
|
||||
.root_clock_control = dcn35_root_clock_control,
|
||||
.set_idle_state = dcn35_set_idle_state,
|
||||
.get_idle_state = dcn35_get_idle_state
|
||||
.get_idle_state = dcn35_get_idle_state,
|
||||
.set_long_vtotal = dcn35_set_long_vblank,
|
||||
};
|
||||
|
||||
static const struct hwseq_private_funcs dcn35_private_funcs = {
|
||||
|
||||
@@ -429,6 +429,7 @@ struct hw_sequencer_funcs {
|
||||
bool (*is_pipe_topology_transition_seamless)(struct dc *dc,
|
||||
const struct dc_state *cur_ctx,
|
||||
const struct dc_state *new_ctx);
|
||||
void (*set_long_vtotal)(struct pipe_ctx **pipe_ctx, int num_pipes, uint32_t v_total_min, uint32_t v_total_max);
|
||||
};
|
||||
|
||||
void color_space_to_black_color(
|
||||
|
||||
@@ -64,6 +64,12 @@ struct drr_params {
|
||||
bool immediate_flip;
|
||||
};
|
||||
|
||||
struct long_vtotal_params {
|
||||
uint32_t vertical_total_min;
|
||||
uint32_t vertical_total_max;
|
||||
uint32_t vertical_blank_start;
|
||||
};
|
||||
|
||||
#define LEFT_EYE_3D_PRIMARY_SURFACE 1
|
||||
#define RIGHT_EYE_3D_PRIMARY_SURFACE 0
|
||||
|
||||
@@ -331,6 +337,7 @@ struct timing_generator_funcs {
|
||||
|
||||
void (*init_odm)(struct timing_generator *tg);
|
||||
void (*wait_drr_doublebuffer_pending_clear)(struct timing_generator *tg);
|
||||
void (*set_long_vtotal)(struct timing_generator *optc, const struct long_vtotal_params *params);
|
||||
void (*wait_odm_doublebuffer_pending_clear)(struct timing_generator *tg);
|
||||
};
|
||||
|
||||
|
||||
@@ -129,6 +129,8 @@ struct dcn_optc_registers {
|
||||
uint32_t OTG_V_TOTAL_MID;
|
||||
uint32_t OTG_V_TOTAL_MIN;
|
||||
uint32_t OTG_V_TOTAL_CONTROL;
|
||||
uint32_t OTG_V_COUNT_STOP_CONTROL;
|
||||
uint32_t OTG_V_COUNT_STOP_CONTROL2;
|
||||
uint32_t OTG_TRIGA_CNTL;
|
||||
uint32_t OTG_TRIGA_MANUAL_TRIG;
|
||||
uint32_t OTG_MANUAL_FLOW_CONTROL;
|
||||
@@ -581,7 +583,9 @@ struct dcn_optc_registers {
|
||||
type OTG_CRC1_WINDOWB_X_END_READBACK;\
|
||||
type OTG_CRC1_WINDOWB_Y_START_READBACK;\
|
||||
type OTG_CRC1_WINDOWB_Y_END_READBACK;\
|
||||
type OPTC_FGCG_REP_DIS;
|
||||
type OPTC_FGCG_REP_DIS;\
|
||||
type OTG_V_COUNT_STOP;\
|
||||
type OTG_V_COUNT_STOP_TIMER;
|
||||
|
||||
struct dcn_optc_shift {
|
||||
TG_REG_FIELD_LIST(uint8_t)
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include "reg_helper.h"
|
||||
#include "dc.h"
|
||||
#include "dcn_calc_math.h"
|
||||
#include "dc_dmub_srv.h"
|
||||
|
||||
#define REG(reg)\
|
||||
optc1->tg_regs->reg
|
||||
@@ -213,6 +214,167 @@ static bool optc35_configure_crc(struct timing_generator *optc,
|
||||
return true;
|
||||
}
|
||||
|
||||
static void optc35_setup_manual_trigger(struct timing_generator *optc)
|
||||
{
|
||||
if (!optc || !optc->ctx)
|
||||
return;
|
||||
|
||||
struct optc *optc1 = DCN10TG_FROM_TG(optc);
|
||||
struct dc *dc = optc->ctx->dc;
|
||||
|
||||
if (dc->caps.dmub_caps.mclk_sw && !dc->debug.disable_fams)
|
||||
dc_dmub_srv_set_drr_manual_trigger_cmd(dc, optc->inst);
|
||||
else {
|
||||
/*
|
||||
* MIN_MASK_EN is gone and MASK is now always enabled.
|
||||
*
|
||||
* To get it to it work with manual trigger we need to make sure
|
||||
* we program the correct bit.
|
||||
*/
|
||||
REG_UPDATE_4(OTG_V_TOTAL_CONTROL,
|
||||
OTG_V_TOTAL_MIN_SEL, 1,
|
||||
OTG_V_TOTAL_MAX_SEL, 1,
|
||||
OTG_FORCE_LOCK_ON_EVENT, 0,
|
||||
OTG_SET_V_TOTAL_MIN_MASK, (1 << 1)); /* TRIGA */
|
||||
|
||||
// Setup manual flow control for EOF via TRIG_A
|
||||
if (optc->funcs && optc->funcs->setup_manual_trigger)
|
||||
optc->funcs->setup_manual_trigger(optc);
|
||||
}
|
||||
}
|
||||
|
||||
void optc35_set_drr(
|
||||
struct timing_generator *optc,
|
||||
const struct drr_params *params)
|
||||
{
|
||||
if (!optc || !params)
|
||||
return;
|
||||
|
||||
struct optc *optc1 = DCN10TG_FROM_TG(optc);
|
||||
uint32_t max_otg_v_total = optc1->max_v_total - 1;
|
||||
|
||||
if (params != NULL &&
|
||||
params->vertical_total_max > 0 &&
|
||||
params->vertical_total_min > 0) {
|
||||
|
||||
if (params->vertical_total_mid != 0) {
|
||||
|
||||
REG_SET(OTG_V_TOTAL_MID, 0,
|
||||
OTG_V_TOTAL_MID, params->vertical_total_mid - 1);
|
||||
|
||||
REG_UPDATE_2(OTG_V_TOTAL_CONTROL,
|
||||
OTG_VTOTAL_MID_REPLACING_MAX_EN, 1,
|
||||
OTG_VTOTAL_MID_FRAME_NUM,
|
||||
(uint8_t)params->vertical_total_mid_frame_num);
|
||||
|
||||
}
|
||||
|
||||
if (optc->funcs && optc->funcs->set_vtotal_min_max)
|
||||
optc->funcs->set_vtotal_min_max(optc,
|
||||
params->vertical_total_min - 1, params->vertical_total_max - 1);
|
||||
optc35_setup_manual_trigger(optc);
|
||||
} else {
|
||||
REG_UPDATE_4(OTG_V_TOTAL_CONTROL,
|
||||
OTG_SET_V_TOTAL_MIN_MASK, 0,
|
||||
OTG_V_TOTAL_MIN_SEL, 0,
|
||||
OTG_V_TOTAL_MAX_SEL, 0,
|
||||
OTG_FORCE_LOCK_ON_EVENT, 0);
|
||||
|
||||
if (optc->funcs && optc->funcs->set_vtotal_min_max)
|
||||
optc->funcs->set_vtotal_min_max(optc, 0, 0);
|
||||
}
|
||||
|
||||
REG_WRITE(OTG_V_COUNT_STOP_CONTROL, max_otg_v_total);
|
||||
REG_WRITE(OTG_V_COUNT_STOP_CONTROL2, 0);
|
||||
}
|
||||
|
||||
static void optc35_set_long_vtotal(
|
||||
struct timing_generator *optc,
|
||||
const struct long_vtotal_params *params)
|
||||
{
|
||||
if (!optc || !params)
|
||||
return;
|
||||
|
||||
struct optc *optc1 = DCN10TG_FROM_TG(optc);
|
||||
uint32_t vcount_stop_timer = 0, vcount_stop = 0;
|
||||
uint32_t max_otg_v_total = optc1->max_v_total - 1;
|
||||
|
||||
if (params->vertical_total_min <= max_otg_v_total && params->vertical_total_max <= max_otg_v_total)
|
||||
return;
|
||||
|
||||
if (params->vertical_total_max == 0 || params->vertical_total_min == 0) {
|
||||
REG_UPDATE_4(OTG_V_TOTAL_CONTROL,
|
||||
OTG_SET_V_TOTAL_MIN_MASK, 0,
|
||||
OTG_V_TOTAL_MIN_SEL, 0,
|
||||
OTG_V_TOTAL_MAX_SEL, 0,
|
||||
OTG_FORCE_LOCK_ON_EVENT, 0);
|
||||
|
||||
if (optc->funcs && optc->funcs->set_vtotal_min_max)
|
||||
optc->funcs->set_vtotal_min_max(optc, 0, 0);
|
||||
} else if (params->vertical_total_max == params->vertical_total_min) {
|
||||
vcount_stop = params->vertical_blank_start;
|
||||
vcount_stop_timer = params->vertical_total_max - max_otg_v_total;
|
||||
|
||||
REG_UPDATE_4(OTG_V_TOTAL_CONTROL,
|
||||
OTG_V_TOTAL_MIN_SEL, 1,
|
||||
OTG_V_TOTAL_MAX_SEL, 1,
|
||||
OTG_FORCE_LOCK_ON_EVENT, 0,
|
||||
OTG_SET_V_TOTAL_MIN_MASK, 0);
|
||||
|
||||
if (optc->funcs && optc->funcs->set_vtotal_min_max)
|
||||
optc->funcs->set_vtotal_min_max(optc, max_otg_v_total, max_otg_v_total);
|
||||
|
||||
REG_WRITE(OTG_V_COUNT_STOP_CONTROL, vcount_stop);
|
||||
REG_WRITE(OTG_V_COUNT_STOP_CONTROL2, vcount_stop_timer);
|
||||
} else {
|
||||
// Variable rate, keep DRR trigger mask
|
||||
if (params->vertical_total_min > max_otg_v_total) {
|
||||
// cannot be supported
|
||||
// If MAX_OTG_V_COUNT < DRR trigger < v_total_min < v_total_max,
|
||||
// DRR trigger will drop the vtotal counting directly to a new frame.
|
||||
// But it should trigger between v_total_min and v_total_max.
|
||||
ASSERT(0);
|
||||
|
||||
REG_UPDATE_4(OTG_V_TOTAL_CONTROL,
|
||||
OTG_SET_V_TOTAL_MIN_MASK, 0,
|
||||
OTG_V_TOTAL_MIN_SEL, 0,
|
||||
OTG_V_TOTAL_MAX_SEL, 0,
|
||||
OTG_FORCE_LOCK_ON_EVENT, 0);
|
||||
|
||||
if (optc->funcs && optc->funcs->set_vtotal_min_max)
|
||||
optc->funcs->set_vtotal_min_max(optc, 0, 0);
|
||||
|
||||
REG_WRITE(OTG_V_COUNT_STOP_CONTROL, max_otg_v_total);
|
||||
REG_WRITE(OTG_V_COUNT_STOP_CONTROL2, 0);
|
||||
} else {
|
||||
// For total_min <= MAX_OTG_V_COUNT and total_max > MAX_OTG_V_COUNT
|
||||
vcount_stop = params->vertical_total_min;
|
||||
vcount_stop_timer = params->vertical_total_max - max_otg_v_total;
|
||||
|
||||
// Example:
|
||||
// params->vertical_total_min 1000
|
||||
// params->vertical_total_max 2000
|
||||
// MAX_OTG_V_COUNT_STOP = 1500
|
||||
//
|
||||
// If DRR event not happened,
|
||||
// time 0,1,2,3,4,...1000,1001,........,1500,1501,1502, ...1999
|
||||
// vcount 0,1,2,3,4....1000...................,1001,1002,1003,...1399
|
||||
// vcount2 0,1,2,3,4,..499,
|
||||
// else (DRR event happened, ex : at line 1004)
|
||||
// time 0,1,2,3,4,...1000,1001.....1004, 0
|
||||
// vcount 0,1,2,3,4....1000,.............. 0 (new frame)
|
||||
// vcount2 0,1,2, 3, -
|
||||
if (optc->funcs && optc->funcs->set_vtotal_min_max)
|
||||
optc->funcs->set_vtotal_min_max(optc,
|
||||
params->vertical_total_min - 1, max_otg_v_total);
|
||||
optc35_setup_manual_trigger(optc);
|
||||
|
||||
REG_WRITE(OTG_V_COUNT_STOP_CONTROL, vcount_stop);
|
||||
REG_WRITE(OTG_V_COUNT_STOP_CONTROL2, vcount_stop_timer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static struct timing_generator_funcs dcn35_tg_funcs = {
|
||||
.validate_timing = optc1_validate_timing,
|
||||
.program_timing = optc1_program_timing,
|
||||
@@ -245,7 +407,7 @@ static struct timing_generator_funcs dcn35_tg_funcs = {
|
||||
.lock_doublebuffer_enable = optc3_lock_doublebuffer_enable,
|
||||
.lock_doublebuffer_disable = optc3_lock_doublebuffer_disable,
|
||||
.enable_optc_clock = optc1_enable_optc_clock,
|
||||
.set_drr = optc31_set_drr,
|
||||
.set_drr = optc35_set_drr,
|
||||
.get_last_used_drr_vtotal = optc2_get_last_used_drr_vtotal,
|
||||
.set_vtotal_min_max = optc1_set_vtotal_min_max,
|
||||
.set_static_screen_control = optc1_set_static_screen_control,
|
||||
@@ -275,6 +437,7 @@ static struct timing_generator_funcs dcn35_tg_funcs = {
|
||||
.setup_manual_trigger = optc2_setup_manual_trigger,
|
||||
.get_hw_timing = optc1_get_hw_timing,
|
||||
.init_odm = optc3_init_odm,
|
||||
.set_long_vtotal = optc35_set_long_vtotal,
|
||||
};
|
||||
|
||||
void dcn35_timing_generator_init(struct optc *optc1)
|
||||
|
||||
@@ -65,10 +65,14 @@
|
||||
SF(OTG0_OTG_CRC1_WINDOWB_X_CONTROL_READBACK, OTG_CRC1_WINDOWB_X_END_READBACK, mask_sh),\
|
||||
SF(OTG0_OTG_CRC1_WINDOWB_Y_CONTROL_READBACK, OTG_CRC1_WINDOWB_Y_START_READBACK, mask_sh),\
|
||||
SF(OTG0_OTG_CRC1_WINDOWB_Y_CONTROL_READBACK, OTG_CRC1_WINDOWB_Y_END_READBACK, mask_sh),\
|
||||
SF(OPTC_CLOCK_CONTROL, OPTC_FGCG_REP_DIS, mask_sh)
|
||||
SF(OPTC_CLOCK_CONTROL, OPTC_FGCG_REP_DIS, mask_sh),\
|
||||
SF(OTG0_OTG_V_COUNT_STOP_CONTROL, OTG_V_COUNT_STOP, mask_sh),\
|
||||
SF(OTG0_OTG_V_COUNT_STOP_CONTROL2, OTG_V_COUNT_STOP_TIMER, mask_sh)
|
||||
|
||||
void dcn35_timing_generator_init(struct optc *optc1);
|
||||
|
||||
void dcn35_timing_generator_set_fgcg(struct optc *optc1, bool enable);
|
||||
|
||||
void optc35_set_drr(struct timing_generator *optc, const struct drr_params *params);
|
||||
|
||||
#endif /* __DC_OPTC_DCN35_H__ */
|
||||
|
||||
@@ -240,6 +240,8 @@ struct resource_pool *dcn35_create_resource_pool(
|
||||
SRI_ARR(OTG_V_TOTAL_MAX, OTG, inst),\
|
||||
SRI_ARR(OTG_V_TOTAL_MIN, OTG, inst),\
|
||||
SRI_ARR(OTG_V_TOTAL_CONTROL, OTG, inst),\
|
||||
SRI_ARR(OTG_V_COUNT_STOP_CONTROL, OTG, inst),\
|
||||
SRI_ARR(OTG_V_COUNT_STOP_CONTROL2, OTG, inst),\
|
||||
SRI_ARR(OTG_TRIGA_CNTL, OTG, inst),\
|
||||
SRI_ARR(OTG_FORCE_COUNT_NOW_CNTL, OTG, inst),\
|
||||
SRI_ARR(OTG_STATIC_SCREEN_CONTROL, OTG, inst),\
|
||||
|
||||
@@ -1126,6 +1126,8 @@ void mod_freesync_build_vrr_params(struct mod_freesync *mod_freesync,
|
||||
in_out_vrr->adjust.v_total_min = stream->timing.v_total;
|
||||
in_out_vrr->adjust.v_total_max = stream->timing.v_total;
|
||||
}
|
||||
|
||||
in_out_vrr->adjust.allow_otg_v_count_halt = (in_config->state == VRR_STATE_ACTIVE_FIXED) ? true : false;
|
||||
}
|
||||
|
||||
void mod_freesync_handle_preflip(struct mod_freesync *mod_freesync,
|
||||
|
||||
Reference in New Issue
Block a user