/* * 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 * */ #include "dm_services.h" #include "dc.h" #include "mod_freesync.h" #include "core_types.h" #include "core_dc.h" #define MOD_FREESYNC_MAX_CONCURRENT_STREAMS 32 /* Refresh rate ramp at a fixed rate of 65 Hz/second */ #define STATIC_SCREEN_RAMP_DELTA_REFRESH_RATE_PER_FRAME ((1000 / 60) * 65) /* Number of elements in the render times cache array */ #define RENDER_TIMES_MAX_COUNT 20 /* Threshold to exit BTR (to avoid frequent enter-exits at the lower limit) */ #define BTR_EXIT_MARGIN 2000 #define FREESYNC_REGISTRY_NAME "freesync_v1" struct gradual_static_ramp { bool ramp_is_active; bool ramp_direction_is_up; unsigned int ramp_current_frame_duration_in_ns; }; struct time_cache { /* video (48Hz feature) related */ unsigned int update_duration_in_ns; /* BTR/fixed refresh related */ unsigned int prev_time_stamp_in_us; unsigned int min_render_time_in_us; unsigned int max_render_time_in_us; unsigned int render_times_index; unsigned int render_times[RENDER_TIMES_MAX_COUNT]; }; struct below_the_range { bool btr_active; bool program_btr; unsigned int mid_point_in_us; unsigned int inserted_frame_duration_in_us; unsigned int frames_to_insert; unsigned int frame_counter; }; struct fixed_refresh { bool fixed_refresh_active; bool program_fixed_refresh; }; struct freesync_range { unsigned int min_refresh; unsigned int max_frame_duration; unsigned int vmax; unsigned int max_refresh; unsigned int min_frame_duration; unsigned int vmin; }; struct freesync_state { bool fullscreen; bool static_screen; bool video; unsigned int nominal_refresh_rate_in_micro_hz; bool windowed_fullscreen; struct time_cache time; struct gradual_static_ramp static_ramp; struct below_the_range btr; struct fixed_refresh fixed_refresh; struct freesync_range freesync_range; }; struct freesync_entity { const struct dc_stream *stream; struct mod_freesync_caps *caps; struct freesync_state state; struct mod_freesync_user_enable user_enable; }; struct freesync_registry_options { unsigned int min_refresh_from_edid; }; struct core_freesync { struct mod_freesync public; struct dc *dc; struct freesync_entity *map; int num_entities; struct freesync_registry_options opts; }; #define MOD_FREESYNC_TO_CORE(mod_freesync)\ container_of(mod_freesync, struct core_freesync, public) static bool check_dc_support(const struct dc *dc) { if (dc->stream_funcs.adjust_vmin_vmax == NULL) return false; return true; } struct mod_freesync *mod_freesync_create(struct dc *dc) { struct core_freesync *core_freesync = dm_alloc(sizeof(struct core_freesync)); struct core_dc *core_dc = DC_TO_CORE(dc); struct persistent_data_flag flag; int i, data = 0; if (core_freesync == NULL) goto fail_alloc_context; core_freesync->map = dm_alloc(sizeof(struct freesync_entity) * MOD_FREESYNC_MAX_CONCURRENT_STREAMS); if (core_freesync->map == NULL) goto fail_alloc_map; for (i = 0; i < MOD_FREESYNC_MAX_CONCURRENT_STREAMS; i++) core_freesync->map[i].stream = NULL; core_freesync->num_entities = 0; if (dc == NULL) goto fail_construct; core_freesync->dc = dc; if (!check_dc_support(dc)) goto fail_construct; /* Create initial module folder in registry for freesync enable data */ flag.save_per_edid = true; flag.save_per_link = false; dm_write_persistent_data(core_dc->ctx, NULL, FREESYNC_REGISTRY_NAME, NULL, NULL, 0, &flag); flag.save_per_edid = false; flag.save_per_link = false; if (dm_read_persistent_data(core_dc->ctx, NULL, NULL, "DalDrrSupport", &data, sizeof(data), &flag)) { core_freesync->opts.min_refresh_from_edid = data; } return &core_freesync->public; fail_construct: dm_free(core_freesync->map); fail_alloc_map: dm_free(core_freesync); fail_alloc_context: return NULL; } void mod_freesync_destroy(struct mod_freesync *mod_freesync) { if (mod_freesync != NULL) { int i; struct core_freesync *core_freesync = MOD_FREESYNC_TO_CORE(mod_freesync); for (i = 0; i < core_freesync->num_entities; i++) if (core_freesync->map[i].stream) dc_stream_release(core_freesync->map[i].stream); dm_free(core_freesync->map); dm_free(core_freesync); } } /* Given a specific dc_stream* this function finds its equivalent * on the core_freesync->map and returns the corresponding index */ static unsigned int map_index_from_stream(struct core_freesync *core_freesync, const struct dc_stream *stream) { unsigned int index = 0; for (index = 0; index < core_freesync->num_entities; index++) { if (core_freesync->map[index].stream == stream) { return index; } } /* Could not find stream requested */ ASSERT(false); return index; } bool mod_freesync_add_stream(struct mod_freesync *mod_freesync, const struct dc_stream *stream, struct mod_freesync_caps *caps) { struct core_stream *core_stream = NULL; struct core_dc *core_dc = NULL; struct core_freesync *core_freesync = NULL; int persistent_freesync_enable = 0; struct persistent_data_flag flag; unsigned int nom_refresh_rate_micro_hz; unsigned long long temp; if (mod_freesync == NULL) return false; core_freesync = MOD_FREESYNC_TO_CORE(mod_freesync); core_stream = DC_STREAM_TO_CORE(stream); core_dc = DC_TO_CORE(core_freesync->dc); flag.save_per_edid = true; flag.save_per_link = false; if (core_freesync->num_entities < MOD_FREESYNC_MAX_CONCURRENT_STREAMS) { dc_stream_retain(stream); temp = stream->timing.pix_clk_khz; temp *= 1000ULL * 1000ULL * 1000ULL; temp = div_u64(temp, stream->timing.h_total); temp = div_u64(temp, stream->timing.v_total); nom_refresh_rate_micro_hz = (unsigned int) temp; if (core_freesync->opts.min_refresh_from_edid != 0 && dc_is_embedded_signal( stream->sink->sink_signal)) { caps->supported = true; caps->min_refresh_in_micro_hz = core_freesync->opts.min_refresh_from_edid * 1000000; caps->max_refresh_in_micro_hz = nom_refresh_rate_micro_hz; } core_freesync->map[core_freesync->num_entities].stream = stream; core_freesync->map[core_freesync->num_entities].caps = caps; core_freesync->map[core_freesync->num_entities].state. fullscreen = false; core_freesync->map[core_freesync->num_entities].state. static_screen = false; core_freesync->map[core_freesync->num_entities].state. video = false; core_freesync->map[core_freesync->num_entities].state.time. update_duration_in_ns = 0; core_freesync->map[core_freesync->num_entities].state. static_ramp.ramp_is_active = false; /* get persistent data from registry */ if (dm_read_persistent_data(core_dc->ctx, stream->sink, FREESYNC_REGISTRY_NAME, "userenable", &persistent_freesync_enable, sizeof(int), &flag)) { core_freesync->map[core_freesync->num_entities].user_enable. enable_for_gaming = (persistent_freesync_enable & 1) ? true : false; core_freesync->map[core_freesync->num_entities].user_enable. enable_for_static = (persistent_freesync_enable & 2) ? true : false; core_freesync->map[core_freesync->num_entities].user_enable. enable_for_video = (persistent_freesync_enable & 4) ? true : false; } else { core_freesync->map[core_freesync->num_entities].user_enable. enable_for_gaming = false; core_freesync->map[core_freesync->num_entities].user_enable. enable_for_static = false; core_freesync->map[core_freesync->num_entities].user_enable. enable_for_video = false; } if (caps->supported && nom_refresh_rate_micro_hz >= caps->min_refresh_in_micro_hz && nom_refresh_rate_micro_hz <= caps->max_refresh_in_micro_hz) core_stream->public.ignore_msa_timing_param = 1; core_freesync->num_entities++; return true; } return false; } bool mod_freesync_remove_stream(struct mod_freesync *mod_freesync, const struct dc_stream *stream) { int i = 0; struct core_freesync *core_freesync = NULL; unsigned int index = 0; if (mod_freesync == NULL) return false; core_freesync = MOD_FREESYNC_TO_CORE(mod_freesync); index = map_index_from_stream(core_freesync, stream); dc_stream_release(core_freesync->map[index].stream); core_freesync->map[index].stream = NULL; /* To remove this entity, shift everything after down */ for (i = index; i < core_freesync->num_entities - 1; i++) core_freesync->map[i] = core_freesync->map[i + 1]; core_freesync->num_entities--; return true; } static void update_stream_freesync_context(struct core_freesync *core_freesync, const struct dc_stream *stream) { unsigned int index; struct freesync_context *ctx; struct core_stream *core_stream; core_stream = DC_STREAM_TO_CORE(stream); ctx = &core_stream->public.freesync_ctx; index = map_index_from_stream(core_freesync, stream); ctx->supported = core_freesync->map[index].caps->supported; ctx->enabled = (core_freesync->map[index].user_enable.enable_for_gaming || core_freesync->map[index].user_enable.enable_for_video || core_freesync->map[index].user_enable.enable_for_static); ctx->active = (core_freesync->map[index].state.fullscreen || core_freesync->map[index].state.video || core_freesync->map[index].state.static_ramp.ramp_is_active); ctx->min_refresh_in_micro_hz = core_freesync->map[index].caps->min_refresh_in_micro_hz; ctx->nominal_refresh_in_micro_hz = core_freesync-> map[index].state.nominal_refresh_rate_in_micro_hz; } static void update_stream(struct core_freesync *core_freesync, const struct dc_stream *stream) { struct core_stream *core_stream = DC_STREAM_TO_CORE(stream); unsigned int index = map_index_from_stream(core_freesync, stream); if (core_freesync->map[index].caps->supported) { core_stream->public.ignore_msa_timing_param = 1; update_stream_freesync_context(core_freesync, stream); } } static void calc_freesync_range(struct core_freesync *core_freesync, const struct dc_stream *stream, struct freesync_state *state, unsigned int min_refresh_in_uhz, unsigned int max_refresh_in_uhz) { unsigned int min_frame_duration_in_ns = 0, max_frame_duration_in_ns = 0; unsigned int index = map_index_from_stream(core_freesync, stream); uint32_t vtotal = stream->timing.v_total; if ((min_refresh_in_uhz == 0) || (max_refresh_in_uhz == 0)) { state->freesync_range.min_refresh = state->nominal_refresh_rate_in_micro_hz; state->freesync_range.max_refresh = state->nominal_refresh_rate_in_micro_hz; state->freesync_range.max_frame_duration = 0; state->freesync_range.min_frame_duration = 0; state->freesync_range.vmax = vtotal; state->freesync_range.vmin = vtotal; return; } min_frame_duration_in_ns = ((unsigned int) (div64_u64( (1000000000ULL * 1000000), max_refresh_in_uhz))); max_frame_duration_in_ns = ((unsigned int) (div64_u64( (1000000000ULL * 1000000), min_refresh_in_uhz))); state->freesync_range.min_refresh = min_refresh_in_uhz; state->freesync_range.max_refresh = max_refresh_in_uhz; state->freesync_range.max_frame_duration = max_frame_duration_in_ns; state->freesync_range.min_frame_duration = min_frame_duration_in_ns; state->freesync_range.vmax = div64_u64(div64_u64(((unsigned long long)( max_frame_duration_in_ns) * stream->timing.pix_clk_khz), stream->timing.h_total), 1000000); state->freesync_range.vmin = div64_u64(div64_u64(((unsigned long long)( min_frame_duration_in_ns) * stream->timing.pix_clk_khz), stream->timing.h_total), 1000000); /* In case of 4k free sync monitor, vmin or vmax cannot be less than vtotal */ if (state->freesync_range.vmin < vtotal) { ASSERT(false); state->freesync_range.vmin = vtotal; } if (state->freesync_range.vmax < vtotal) { ASSERT(false); state->freesync_range.vmax = vtotal; } /* Determine whether BTR can be supported */ if (max_frame_duration_in_ns >= 2 * min_frame_duration_in_ns) core_freesync->map[index].caps->btr_supported = true; else core_freesync->map[index].caps->btr_supported = false; /* Cache the time variables */ state->time.max_render_time_in_us = max_frame_duration_in_ns / 1000; state->time.min_render_time_in_us = min_frame_duration_in_ns / 1000; state->btr.mid_point_in_us = (max_frame_duration_in_ns + min_frame_duration_in_ns) / 2000; } static void calc_v_total_from_duration(const struct dc_stream *stream, unsigned int duration_in_ns, int *v_total_nominal) { *v_total_nominal = div64_u64(div64_u64(((unsigned long long)( duration_in_ns) * stream->timing.pix_clk_khz), stream->timing.h_total), 1000000); } static void calc_v_total_for_static_ramp(struct core_freesync *core_freesync, const struct dc_stream *stream, unsigned int index, int *v_total) { unsigned int frame_duration = 0; struct gradual_static_ramp *static_ramp_variables = &core_freesync->map[index].state.static_ramp; /* Calc ratio between new and current frame duration with 3 digit */ unsigned int frame_duration_ratio = div64_u64(1000000, (1000 + div64_u64(((unsigned long long)( STATIC_SCREEN_RAMP_DELTA_REFRESH_RATE_PER_FRAME) * static_ramp_variables->ramp_current_frame_duration_in_ns), 1000000000))); /* Calculate delta between new and current frame duration in ns */ unsigned int frame_duration_delta = div64_u64(((unsigned long long)( static_ramp_variables->ramp_current_frame_duration_in_ns) * (1000 - frame_duration_ratio)), 1000); /* Adjust frame duration delta based on ratio between current and * standard frame duration (frame duration at 60 Hz refresh rate). */ unsigned int ramp_rate_interpolated = div64_u64(((unsigned long long)( frame_duration_delta) * static_ramp_variables-> ramp_current_frame_duration_in_ns), 16666666); /* Going to a higher refresh rate (lower frame duration) */ if (static_ramp_variables->ramp_direction_is_up) { /* reduce frame duration */ static_ramp_variables->ramp_current_frame_duration_in_ns -= ramp_rate_interpolated; /* min frame duration */ frame_duration = ((unsigned int) (div64_u64( (1000000000ULL * 1000000), core_freesync->map[index].state. nominal_refresh_rate_in_micro_hz))); /* adjust for frame duration below min */ if (static_ramp_variables->ramp_current_frame_duration_in_ns <= frame_duration) { static_ramp_variables->ramp_is_active = false; static_ramp_variables-> ramp_current_frame_duration_in_ns = frame_duration; } /* Going to a lower refresh rate (larger frame duration) */ } else { /* increase frame duration */ static_ramp_variables->ramp_current_frame_duration_in_ns += ramp_rate_interpolated; /* max frame duration */ frame_duration = ((unsigned int) (div64_u64( (1000000000ULL * 1000000), core_freesync->map[index].caps->min_refresh_in_micro_hz))); /* adjust for frame duration above max */ if (static_ramp_variables->ramp_current_frame_duration_in_ns >= frame_duration) { static_ramp_variables->ramp_is_active = false; static_ramp_variables-> ramp_current_frame_duration_in_ns = frame_duration; } } calc_v_total_from_duration(stream, static_ramp_variables-> ramp_current_frame_duration_in_ns, v_total); } static void reset_freesync_state_variables(struct freesync_state* state) { state->static_ramp.ramp_is_active = false; if (state->nominal_refresh_rate_in_micro_hz) state->static_ramp.ramp_current_frame_duration_in_ns = ((unsigned int) (div64_u64( (1000000000ULL * 1000000), state->nominal_refresh_rate_in_micro_hz))); state->btr.btr_active = false; state->btr.frame_counter = 0; state->btr.frames_to_insert = 0; state->btr.inserted_frame_duration_in_us = 0; state->btr.program_btr = false; state->fixed_refresh.fixed_refresh_active = false; state->fixed_refresh.program_fixed_refresh = false; } /* * Sets freesync mode on a stream depending on current freesync state. */ static bool set_freesync_on_streams(struct core_freesync *core_freesync, const struct dc_stream **streams, int num_streams) { int v_total_nominal = 0, v_total_min = 0, v_total_max = 0; unsigned int stream_idx, map_index = 0; struct freesync_state *state; if (num_streams == 0 || streams == NULL || num_streams > 1) return false; for (stream_idx = 0; stream_idx < num_streams; stream_idx++) { map_index = map_index_from_stream(core_freesync, streams[stream_idx]); state = &core_freesync->map[map_index].state; if (core_freesync->map[map_index].caps->supported) { /* Fullscreen has the topmost priority. If the * fullscreen bit is set, we are in a fullscreen * application where it should not matter if it is * static screen. We should not check the static_screen * or video bit. * * Special cases of fullscreen include btr and fixed * refresh. We program btr on every flip and involves * programming full range right before the last inserted frame. * However, we do not want to program the full freesync range * when fixed refresh is active, because we only program * that logic once and this will override it. */ if (core_freesync->map[map_index].user_enable. enable_for_gaming == true && state->fullscreen == true && state->fixed_refresh.fixed_refresh_active == false) { /* Enable freesync */ v_total_min = state->freesync_range.vmin; v_total_max = state->freesync_range.vmax; /* Update the freesync context for the stream */ update_stream_freesync_context(core_freesync, streams[stream_idx]); core_freesync->dc->stream_funcs. adjust_vmin_vmax(core_freesync->dc, streams, num_streams, v_total_min, v_total_max); return true; } else if (core_freesync->map[map_index].user_enable. enable_for_video && state->video == true) { /* Enable 48Hz feature */ calc_v_total_from_duration(streams[stream_idx], state->time.update_duration_in_ns, &v_total_nominal); /* Program only if v_total_nominal is in range*/ if (v_total_nominal >= streams[stream_idx]->timing.v_total) { /* Update the freesync context for * the stream */ update_stream_freesync_context( core_freesync, streams[stream_idx]); core_freesync->dc->stream_funcs. adjust_vmin_vmax( core_freesync->dc, streams, num_streams, v_total_nominal, v_total_nominal); } return true; } else { /* Disable freesync */ v_total_nominal = streams[stream_idx]-> timing.v_total; /* Update the freesync context for * the stream */ update_stream_freesync_context( core_freesync, streams[stream_idx]); core_freesync->dc->stream_funcs. adjust_vmin_vmax( core_freesync->dc, streams, num_streams, v_total_nominal, v_total_nominal); /* Reset the cached variables */ reset_freesync_state_variables(state); return true; } } else { /* Disable freesync */ v_total_nominal = streams[stream_idx]-> timing.v_total; /* * we have to reset drr always even sink does * not support freesync because a former stream has * be programmed */ core_freesync->dc->stream_funcs. adjust_vmin_vmax( core_freesync->dc, streams, num_streams, v_total_nominal, v_total_nominal); /* Reset the cached variables */ reset_freesync_state_variables(state); } } return false; } static void set_static_ramp_variables(struct core_freesync *core_freesync, unsigned int index, bool enable_static_screen) { unsigned int frame_duration = 0; struct gradual_static_ramp *static_ramp_variables = &core_freesync->map[index].state.static_ramp; /* If ramp is not active, set initial frame duration depending on * whether we are enabling/disabling static screen mode. If the ramp is * already active, ramp should continue in the opposite direction * starting with the current frame duration */ if (!static_ramp_variables->ramp_is_active) { static_ramp_variables->ramp_is_active = true; if (enable_static_screen == true) { /* Going to lower refresh rate, so start from max * refresh rate (min frame duration) */ frame_duration = ((unsigned int) (div64_u64( (1000000000ULL * 1000000), core_freesync->map[index].state. nominal_refresh_rate_in_micro_hz))); } else { /* Going to higher refresh rate, so start from min * refresh rate (max frame duration) */ frame_duration = ((unsigned int) (div64_u64( (1000000000ULL * 1000000), core_freesync->map[index].caps->min_refresh_in_micro_hz))); } static_ramp_variables-> ramp_current_frame_duration_in_ns = frame_duration; } /* If we are ENABLING static screen, refresh rate should go DOWN. * If we are DISABLING static screen, refresh rate should go UP. */ static_ramp_variables->ramp_direction_is_up = !enable_static_screen; } void mod_freesync_handle_v_update(struct mod_freesync *mod_freesync, const struct dc_stream **streams, int num_streams) { unsigned int index, v_total, inserted_frame_v_total = 0; unsigned int min_frame_duration_in_ns, vmax, vmin = 0; struct freesync_state *state; struct core_freesync *core_freesync = NULL; struct dc_static_screen_events triggers = {0}; if (mod_freesync == NULL) return; core_freesync = MOD_FREESYNC_TO_CORE(mod_freesync); if (core_freesync->num_entities == 0) return; index = map_index_from_stream(core_freesync, streams[0]); if (core_freesync->map[index].caps->supported == false) return; state = &core_freesync->map[index].state; /* Below the Range Logic */ /* Only execute if in fullscreen mode */ if (state->fullscreen == true && core_freesync->map[index].user_enable.enable_for_gaming && core_freesync->map[index].caps->btr_supported && state->btr.btr_active) { /* TODO: pass in flag for Pre-DCE12 ASIC * in order for frame variable duration to take affect, * it needs to be done one VSYNC early, which is at * frameCounter == 1. * For DCE12 and newer updates to V_TOTAL_MIN/MAX * will take affect on current frame */ if (state->btr.frames_to_insert == state->btr.frame_counter) { min_frame_duration_in_ns = ((unsigned int) (div64_u64( (1000000000ULL * 1000000), state->nominal_refresh_rate_in_micro_hz))); vmin = state->freesync_range.vmin; inserted_frame_v_total = vmin; if (min_frame_duration_in_ns / 1000) inserted_frame_v_total = state->btr.inserted_frame_duration_in_us * vmin / (min_frame_duration_in_ns / 1000); /* Set length of inserted frames as v_total_max*/ vmax = inserted_frame_v_total; vmin = inserted_frame_v_total; /* Program V_TOTAL */ core_freesync->dc->stream_funcs.adjust_vmin_vmax( core_freesync->dc, streams, num_streams, vmin, vmax); } if (state->btr.frame_counter > 0) state->btr.frame_counter--; /* Restore FreeSync */ if (state->btr.frame_counter == 0) set_freesync_on_streams(core_freesync, streams, num_streams); } /* If in fullscreen freesync mode or in video, do not program * static screen ramp values */ if (state->fullscreen == true || state->video == true) { state->static_ramp.ramp_is_active = false; return; } /* Gradual Static Screen Ramping Logic */ /* Execute if ramp is active and user enabled freesync static screen*/ if (state->static_ramp.ramp_is_active && core_freesync->map[index].user_enable.enable_for_static) { calc_v_total_for_static_ramp(core_freesync, streams[0], index, &v_total); /* Update the freesync context for the stream */ update_stream_freesync_context(core_freesync, streams[0]); /* Program static screen ramp values */ core_freesync->dc->stream_funcs.adjust_vmin_vmax( core_freesync->dc, streams, num_streams, v_total, v_total); triggers.overlay_update = true; triggers.surface_update = true; core_freesync->dc->stream_funcs.set_static_screen_events( core_freesync->dc, streams, num_streams, &triggers); } } void mod_freesync_update_state(struct mod_freesync *mod_freesync, const struct dc_stream **streams, int num_streams, struct mod_freesync_params *freesync_params) { bool freesync_program_required = false; unsigned int stream_index; struct freesync_state *state; struct core_freesync *core_freesync = NULL; if (mod_freesync == NULL) return; core_freesync = MOD_FREESYNC_TO_CORE(mod_freesync); if (core_freesync->num_entities == 0) return; for(stream_index = 0; stream_index < num_streams; stream_index++) { unsigned int map_index = map_index_from_stream(core_freesync, streams[stream_index]); state = &core_freesync->map[map_index].state; switch (freesync_params->state){ case FREESYNC_STATE_FULLSCREEN: state->fullscreen = freesync_params->enable; freesync_program_required = true; state->windowed_fullscreen = freesync_params->windowed_fullscreen; break; case FREESYNC_STATE_STATIC_SCREEN: /* Static screen ramp is only enabled for embedded * panels. Also change core variables only if there * is a change. */ if (dc_is_embedded_signal( streams[stream_index]->sink->sink_signal) && state->static_screen != freesync_params->enable) { /* Change the state flag */ state->static_screen = freesync_params->enable; /* Change static screen ramp variables */ set_static_ramp_variables(core_freesync, map_index, freesync_params->enable); } /* We program the ramp starting next VUpdate */ break; case FREESYNC_STATE_VIDEO: /* Change core variables only if there is a change*/ if(freesync_params->update_duration_in_ns != state->time.update_duration_in_ns) { state->video = freesync_params->enable; state->time.update_duration_in_ns = freesync_params->update_duration_in_ns; freesync_program_required = true; } break; case FREESYNC_STATE_NONE: /* handle here to avoid warning */ break; } } if (freesync_program_required) /* Program freesync according to current state*/ set_freesync_on_streams(core_freesync, streams, num_streams); } bool mod_freesync_get_state(struct mod_freesync *mod_freesync, const struct dc_stream *stream, struct mod_freesync_params *freesync_params) { unsigned int index = 0; struct core_freesync *core_freesync = NULL; if (mod_freesync == NULL) return false; core_freesync = MOD_FREESYNC_TO_CORE(mod_freesync); index = map_index_from_stream(core_freesync, stream); if (core_freesync->map[index].state.fullscreen) { freesync_params->state = FREESYNC_STATE_FULLSCREEN; freesync_params->enable = true; } else if (core_freesync->map[index].state.static_screen) { freesync_params->state = FREESYNC_STATE_STATIC_SCREEN; freesync_params->enable = true; } else if (core_freesync->map[index].state.video) { freesync_params->state = FREESYNC_STATE_VIDEO; freesync_params->enable = true; } else { freesync_params->state = FREESYNC_STATE_NONE; freesync_params->enable = false; } freesync_params->update_duration_in_ns = core_freesync->map[index].state.time.update_duration_in_ns; freesync_params->windowed_fullscreen = core_freesync->map[index].state.windowed_fullscreen; return true; } bool mod_freesync_set_user_enable(struct mod_freesync *mod_freesync, const struct dc_stream **streams, int num_streams, struct mod_freesync_user_enable *user_enable) { unsigned int stream_index, map_index; int persistent_data = 0; struct persistent_data_flag flag; struct core_dc *core_dc = NULL; struct core_freesync *core_freesync = NULL; if (mod_freesync == NULL) return false; core_freesync = MOD_FREESYNC_TO_CORE(mod_freesync); core_dc = DC_TO_CORE(core_freesync->dc); flag.save_per_edid = true; flag.save_per_link = false; for(stream_index = 0; stream_index < num_streams; stream_index++){ map_index = map_index_from_stream(core_freesync, streams[stream_index]); core_freesync->map[map_index].user_enable = *user_enable; /* Write persistent data in registry*/ if (core_freesync->map[map_index].user_enable. enable_for_gaming) persistent_data = persistent_data | 1; if (core_freesync->map[map_index].user_enable. enable_for_static) persistent_data = persistent_data | 2; if (core_freesync->map[map_index].user_enable. enable_for_video) persistent_data = persistent_data | 4; dm_write_persistent_data(core_dc->ctx, streams[stream_index]->sink, FREESYNC_REGISTRY_NAME, "userenable", &persistent_data, sizeof(int), &flag); } set_freesync_on_streams(core_freesync, streams, num_streams); return true; } bool mod_freesync_get_user_enable(struct mod_freesync *mod_freesync, const struct dc_stream *stream, struct mod_freesync_user_enable *user_enable) { unsigned int index = 0; struct core_freesync *core_freesync = NULL; if (mod_freesync == NULL) return false; core_freesync = MOD_FREESYNC_TO_CORE(mod_freesync); index = map_index_from_stream(core_freesync, stream); *user_enable = core_freesync->map[index].user_enable; return true; } bool mod_freesync_override_min_max(struct mod_freesync *mod_freesync, const struct dc_stream *streams, unsigned int min_refresh, unsigned int max_refresh) { unsigned int index = 0; struct core_freesync *core_freesync; struct freesync_state *state; if (mod_freesync == NULL) return false; core_freesync = MOD_FREESYNC_TO_CORE(mod_freesync); index = map_index_from_stream(core_freesync, streams); state = &core_freesync->map[index].state; if (min_refresh == 0 || max_refresh == 0) { /* Restore defaults */ calc_freesync_range(core_freesync, streams, state, core_freesync->map[index].caps-> min_refresh_in_micro_hz, state->nominal_refresh_rate_in_micro_hz); } else { calc_freesync_range(core_freesync, streams, state, min_refresh, max_refresh); /* Program vtotal min/max */ core_freesync->dc->stream_funcs.adjust_vmin_vmax( core_freesync->dc, &streams, 1, state->freesync_range.vmin, state->freesync_range.vmax); } return true; } bool mod_freesync_get_min_max(struct mod_freesync *mod_freesync, const struct dc_stream *stream, unsigned int *min_refresh, unsigned int *max_refresh) { unsigned int index = 0; struct core_freesync *core_freesync = NULL; if (mod_freesync == NULL) return false; core_freesync = MOD_FREESYNC_TO_CORE(mod_freesync); index = map_index_from_stream(core_freesync, stream); *min_refresh = core_freesync->map[index].state.freesync_range.min_refresh; *max_refresh = core_freesync->map[index].state.freesync_range.max_refresh; return true; } bool mod_freesync_get_vmin_vmax(struct mod_freesync *mod_freesync, const struct dc_stream *stream, unsigned int *vmin, unsigned int *vmax) { unsigned int index = 0; struct core_freesync *core_freesync = NULL; if (mod_freesync == NULL) return false; core_freesync = MOD_FREESYNC_TO_CORE(mod_freesync); index = map_index_from_stream(core_freesync, stream); *vmin = core_freesync->map[index].state.freesync_range.vmin; *vmax = core_freesync->map[index].state.freesync_range.vmax; return true; } bool mod_freesync_get_v_position(struct mod_freesync *mod_freesync, const struct dc_stream *stream, unsigned int *nom_v_pos, unsigned int *v_pos) { unsigned int index = 0; struct core_freesync *core_freesync = NULL; struct crtc_position position; if (mod_freesync == NULL) return false; core_freesync = MOD_FREESYNC_TO_CORE(mod_freesync); index = map_index_from_stream(core_freesync, stream); if (core_freesync->dc->stream_funcs.get_crtc_position( core_freesync->dc, &stream, 1, &position.vertical_count, &position.nominal_vcount)) { *nom_v_pos = position.vertical_count; *v_pos = position.nominal_vcount; return true; } return false; } void mod_freesync_notify_mode_change(struct mod_freesync *mod_freesync, const struct dc_stream **streams, int num_streams) { unsigned int stream_index, map_index; struct freesync_state *state; struct core_freesync *core_freesync = NULL; if (mod_freesync == NULL) return; core_freesync = MOD_FREESYNC_TO_CORE(mod_freesync); for (stream_index = 0; stream_index < num_streams; stream_index++) { map_index = map_index_from_stream(core_freesync, streams[stream_index]); state = &core_freesync->map[map_index].state; if (core_freesync->map[map_index].caps->supported) { /* Update the field rate for new timing */ unsigned long long temp; temp = streams[stream_index]->timing.pix_clk_khz; temp *= 1000ULL * 1000ULL * 1000ULL; temp = div_u64(temp, streams[stream_index]->timing.h_total); temp = div_u64(temp, streams[stream_index]->timing.v_total); state->nominal_refresh_rate_in_micro_hz = (unsigned int) temp; /* Update the stream */ update_stream(core_freesync, streams[stream_index]); /* Calculate vmin/vmax and refresh rate for * current mode */ calc_freesync_range(core_freesync, *streams, state, core_freesync->map[map_index].caps-> min_refresh_in_micro_hz, state->nominal_refresh_rate_in_micro_hz); } } /* Program freesync according to current state*/ set_freesync_on_streams(core_freesync, streams, num_streams); } /* Add the timestamps to the cache and determine whether BTR programming * is required, depending on the times calculated */ static void update_timestamps(struct core_freesync *core_freesync, const struct dc_stream *stream, unsigned int map_index, unsigned int last_render_time_in_us) { struct freesync_state *state = &core_freesync->map[map_index].state; state->time.render_times[state->time.render_times_index] = last_render_time_in_us; state->time.render_times_index++; if (state->time.render_times_index >= RENDER_TIMES_MAX_COUNT) state->time.render_times_index = 0; if (last_render_time_in_us + BTR_EXIT_MARGIN < state->time.max_render_time_in_us) { /* Exit Below the Range */ if (state->btr.btr_active) { state->btr.program_btr = true; state->btr.btr_active = false; state->btr.frame_counter = 0; /* Exit Fixed Refresh mode */ } else if (state->fixed_refresh.fixed_refresh_active) { state->fixed_refresh.program_fixed_refresh = true; state->fixed_refresh.fixed_refresh_active = false; } } else if (last_render_time_in_us > state->time.max_render_time_in_us) { /* Enter Below the Range */ if (!state->btr.btr_active && core_freesync->map[map_index].caps->btr_supported) { state->btr.program_btr = true; state->btr.btr_active = true; /* Enter Fixed Refresh mode */ } else if (!state->fixed_refresh.fixed_refresh_active && !core_freesync->map[map_index].caps->btr_supported) { state->fixed_refresh.program_fixed_refresh = true; state->fixed_refresh.fixed_refresh_active = true; } } /* When Below the Range is active, must react on every frame */ if (state->btr.btr_active) state->btr.program_btr = true; } static void apply_below_the_range(struct core_freesync *core_freesync, const struct dc_stream *stream, unsigned int map_index, unsigned int last_render_time_in_us) { unsigned int inserted_frame_duration_in_us = 0; unsigned int mid_point_frames_ceil = 0; unsigned int mid_point_frames_floor = 0; unsigned int frame_time_in_us = 0; unsigned int delta_from_mid_point_in_us_1 = 0xFFFFFFFF; unsigned int delta_from_mid_point_in_us_2 = 0xFFFFFFFF; unsigned int frames_to_insert = 0; unsigned int min_frame_duration_in_ns = 0; struct freesync_state *state = &core_freesync->map[map_index].state; if (!state->btr.program_btr) return; state->btr.program_btr = false; min_frame_duration_in_ns = ((unsigned int) (div64_u64( (1000000000ULL * 1000000), state->nominal_refresh_rate_in_micro_hz))); /* Program BTR */ /* BTR set to "not active" so disengage */ if (!state->btr.btr_active) /* Restore FreeSync */ set_freesync_on_streams(core_freesync, &stream, 1); /* BTR set to "active" so engage */ else { /* Calculate number of midPoint frames that could fit within * the render time interval- take ceil of this value */ mid_point_frames_ceil = (last_render_time_in_us + state->btr.mid_point_in_us- 1) / state->btr.mid_point_in_us; if (mid_point_frames_ceil > 0) { frame_time_in_us = last_render_time_in_us / mid_point_frames_ceil; delta_from_mid_point_in_us_1 = (state->btr.mid_point_in_us > frame_time_in_us) ? (state->btr.mid_point_in_us - frame_time_in_us): (frame_time_in_us - state->btr.mid_point_in_us); } /* Calculate number of midPoint frames that could fit within * the render time interval- take floor of this value */ mid_point_frames_floor = last_render_time_in_us / state->btr.mid_point_in_us; if (mid_point_frames_floor > 0) { frame_time_in_us = last_render_time_in_us / mid_point_frames_floor; delta_from_mid_point_in_us_2 = (state->btr.mid_point_in_us > frame_time_in_us) ? (state->btr.mid_point_in_us - frame_time_in_us): (frame_time_in_us - state->btr.mid_point_in_us); } /* Choose number of frames to insert based on how close it * can get to the mid point of the variable range. */ if (delta_from_mid_point_in_us_1 < delta_from_mid_point_in_us_2) frames_to_insert = mid_point_frames_ceil; else frames_to_insert = mid_point_frames_floor; /* Either we've calculated the number of frames to insert, * or we need to insert min duration frames */ if (frames_to_insert > 0) inserted_frame_duration_in_us = last_render_time_in_us / frames_to_insert; if (inserted_frame_duration_in_us < state->time.min_render_time_in_us) inserted_frame_duration_in_us = state->time.min_render_time_in_us; /* Cache the calculated variables */ state->btr.inserted_frame_duration_in_us = inserted_frame_duration_in_us; state->btr.frames_to_insert = frames_to_insert; state->btr.frame_counter = frames_to_insert; } } static void apply_fixed_refresh(struct core_freesync *core_freesync, const struct dc_stream *stream, unsigned int map_index) { unsigned int vmin = 0, vmax = 0; struct freesync_state *state = &core_freesync->map[map_index].state; if (!state->fixed_refresh.program_fixed_refresh) return; state->fixed_refresh.program_fixed_refresh = false; /* Program Fixed Refresh */ /* Fixed Refresh set to "not active" so disengage */ if (!state->fixed_refresh.fixed_refresh_active) { set_freesync_on_streams(core_freesync, &stream, 1); /* Fixed Refresh set to "active" so engage (fix to max) */ } else { vmin = state->freesync_range.vmin; vmax = vmin; core_freesync->dc->stream_funcs.adjust_vmin_vmax( core_freesync->dc, &stream, 1, vmin, vmax); } } void mod_freesync_pre_update_plane_addresses(struct mod_freesync *mod_freesync, const struct dc_stream **streams, int num_streams, unsigned int curr_time_stamp_in_us) { unsigned int stream_index, map_index, last_render_time_in_us = 0; struct core_freesync *core_freesync = NULL; if (mod_freesync == NULL) return; core_freesync = MOD_FREESYNC_TO_CORE(mod_freesync); for (stream_index = 0; stream_index < num_streams; stream_index++) { map_index = map_index_from_stream(core_freesync, streams[stream_index]); if (core_freesync->map[map_index].caps->supported) { last_render_time_in_us = curr_time_stamp_in_us - core_freesync->map[map_index].state.time. prev_time_stamp_in_us; /* Add the timestamps to the cache and determine * whether BTR program is required */ update_timestamps(core_freesync, streams[stream_index], map_index, last_render_time_in_us); if (core_freesync->map[map_index].state.fullscreen && core_freesync->map[map_index].user_enable. enable_for_gaming) { if (core_freesync->map[map_index].caps->btr_supported) { apply_below_the_range(core_freesync, streams[stream_index], map_index, last_render_time_in_us); } else { apply_fixed_refresh(core_freesync, streams[stream_index], map_index); } } core_freesync->map[map_index].state.time. prev_time_stamp_in_us = curr_time_stamp_in_us; } } }