diff options
Diffstat (limited to 'drivers/gpu/drm/amd/display/modules')
-rw-r--r-- | drivers/gpu/drm/amd/display/modules/color/Makefile | 31 | ||||
-rw-r--r-- | drivers/gpu/drm/amd/display/modules/color/color_gamma.c | 1840 | ||||
-rw-r--r-- | drivers/gpu/drm/amd/display/modules/color/color_gamma.h | 99 | ||||
-rw-r--r-- | drivers/gpu/drm/amd/display/modules/color/luts_1d.h | 51 | ||||
-rw-r--r-- | drivers/gpu/drm/amd/display/modules/freesync/Makefile | 31 | ||||
-rw-r--r-- | drivers/gpu/drm/amd/display/modules/freesync/freesync.c | 1555 | ||||
-rw-r--r-- | drivers/gpu/drm/amd/display/modules/inc/mod_freesync.h | 176 | ||||
-rw-r--r-- | drivers/gpu/drm/amd/display/modules/inc/mod_stats.h | 69 | ||||
-rw-r--r-- | drivers/gpu/drm/amd/display/modules/stats/stats.c | 452 |
9 files changed, 4304 insertions, 0 deletions
diff --git a/drivers/gpu/drm/amd/display/modules/color/Makefile b/drivers/gpu/drm/amd/display/modules/color/Makefile new file mode 100644 index 000000000..65c33a769 --- /dev/null +++ b/drivers/gpu/drm/amd/display/modules/color/Makefile @@ -0,0 +1,31 @@ +# +# Copyright 2018 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. +# +# +# Makefile for the color sub-module of DAL. +# + +MOD_COLOR = color_gamma.o + +AMD_DAL_MOD_COLOR = $(addprefix $(AMDDALPATH)/modules/color/,$(MOD_COLOR)) +#$(info ************ DAL COLOR MODULE MAKEFILE ************) + +AMD_DISPLAY_FILES += $(AMD_DAL_MOD_COLOR) diff --git a/drivers/gpu/drm/amd/display/modules/color/color_gamma.c b/drivers/gpu/drm/amd/display/modules/color/color_gamma.c new file mode 100644 index 000000000..11ea1a0e6 --- /dev/null +++ b/drivers/gpu/drm/amd/display/modules/color/color_gamma.c @@ -0,0 +1,1840 @@ +/* + * 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 "dc.h" +#include "opp.h" +#include "color_gamma.h" + + +#define NUM_PTS_IN_REGION 16 +#define NUM_REGIONS 32 +#define MAX_HW_POINTS (NUM_PTS_IN_REGION*NUM_REGIONS) + +static struct hw_x_point coordinates_x[MAX_HW_POINTS + 2]; + +static struct fixed31_32 pq_table[MAX_HW_POINTS + 2]; +static struct fixed31_32 de_pq_table[MAX_HW_POINTS + 2]; + +static bool pq_initialized; /* = false; */ +static bool de_pq_initialized; /* = false; */ + +/* one-time setup of X points */ +void setup_x_points_distribution(void) +{ + struct fixed31_32 region_size = dc_fixpt_from_int(128); + int32_t segment; + uint32_t seg_offset; + uint32_t index; + struct fixed31_32 increment; + + coordinates_x[MAX_HW_POINTS].x = region_size; + coordinates_x[MAX_HW_POINTS + 1].x = region_size; + + for (segment = 6; segment > (6 - NUM_REGIONS); segment--) { + region_size = dc_fixpt_div_int(region_size, 2); + increment = dc_fixpt_div_int(region_size, + NUM_PTS_IN_REGION); + seg_offset = (segment + (NUM_REGIONS - 7)) * NUM_PTS_IN_REGION; + coordinates_x[seg_offset].x = region_size; + + for (index = seg_offset + 1; + index < seg_offset + NUM_PTS_IN_REGION; + index++) { + coordinates_x[index].x = dc_fixpt_add + (coordinates_x[index-1].x, increment); + } + } +} + +static void compute_pq(struct fixed31_32 in_x, struct fixed31_32 *out_y) +{ + /* consts for PQ gamma formula. */ + const struct fixed31_32 m1 = + dc_fixpt_from_fraction(159301758, 1000000000); + const struct fixed31_32 m2 = + dc_fixpt_from_fraction(7884375, 100000); + const struct fixed31_32 c1 = + dc_fixpt_from_fraction(8359375, 10000000); + const struct fixed31_32 c2 = + dc_fixpt_from_fraction(188515625, 10000000); + const struct fixed31_32 c3 = + dc_fixpt_from_fraction(186875, 10000); + + struct fixed31_32 l_pow_m1; + struct fixed31_32 base; + + if (dc_fixpt_lt(in_x, dc_fixpt_zero)) + in_x = dc_fixpt_zero; + + l_pow_m1 = dc_fixpt_pow(in_x, m1); + base = dc_fixpt_div( + dc_fixpt_add(c1, + (dc_fixpt_mul(c2, l_pow_m1))), + dc_fixpt_add(dc_fixpt_one, + (dc_fixpt_mul(c3, l_pow_m1)))); + *out_y = dc_fixpt_pow(base, m2); +} + +static void compute_de_pq(struct fixed31_32 in_x, struct fixed31_32 *out_y) +{ + /* consts for dePQ gamma formula. */ + const struct fixed31_32 m1 = + dc_fixpt_from_fraction(159301758, 1000000000); + const struct fixed31_32 m2 = + dc_fixpt_from_fraction(7884375, 100000); + const struct fixed31_32 c1 = + dc_fixpt_from_fraction(8359375, 10000000); + const struct fixed31_32 c2 = + dc_fixpt_from_fraction(188515625, 10000000); + const struct fixed31_32 c3 = + dc_fixpt_from_fraction(186875, 10000); + + struct fixed31_32 l_pow_m1; + struct fixed31_32 base, div; + + + if (dc_fixpt_lt(in_x, dc_fixpt_zero)) + in_x = dc_fixpt_zero; + + l_pow_m1 = dc_fixpt_pow(in_x, + dc_fixpt_div(dc_fixpt_one, m2)); + base = dc_fixpt_sub(l_pow_m1, c1); + + if (dc_fixpt_lt(base, dc_fixpt_zero)) + base = dc_fixpt_zero; + + div = dc_fixpt_sub(c2, dc_fixpt_mul(c3, l_pow_m1)); + + *out_y = dc_fixpt_pow(dc_fixpt_div(base, div), + dc_fixpt_div(dc_fixpt_one, m1)); + +} + +/*de gamma, none linear to linear*/ +static void compute_hlg_oetf(struct fixed31_32 in_x, bool is_light0_12, struct fixed31_32 *out_y) +{ + struct fixed31_32 a; + struct fixed31_32 b; + struct fixed31_32 c; + struct fixed31_32 threshold; + struct fixed31_32 reference_white_level; + + a = dc_fixpt_from_fraction(17883277, 100000000); + if (is_light0_12) { + /*light 0-12*/ + b = dc_fixpt_from_fraction(28466892, 100000000); + c = dc_fixpt_from_fraction(55991073, 100000000); + threshold = dc_fixpt_one; + reference_white_level = dc_fixpt_half; + } else { + /*light 0-1*/ + b = dc_fixpt_from_fraction(2372241, 100000000); + c = dc_fixpt_add(dc_fixpt_one, dc_fixpt_from_fraction(429347, 100000000)); + threshold = dc_fixpt_from_fraction(1, 12); + reference_white_level = dc_fixpt_pow(dc_fixpt_from_fraction(3, 1), dc_fixpt_half); + } + if (dc_fixpt_lt(threshold, in_x)) + *out_y = dc_fixpt_add(c, dc_fixpt_mul(a, dc_fixpt_log(dc_fixpt_sub(in_x, b)))); + else + *out_y = dc_fixpt_mul(dc_fixpt_pow(in_x, dc_fixpt_half), reference_white_level); +} + +/*re gamma, linear to none linear*/ +static void compute_hlg_eotf(struct fixed31_32 in_x, bool is_light0_12, struct fixed31_32 *out_y) +{ + struct fixed31_32 a; + struct fixed31_32 b; + struct fixed31_32 c; + struct fixed31_32 reference_white_level; + + a = dc_fixpt_from_fraction(17883277, 100000000); + if (is_light0_12) { + /*light 0-12*/ + b = dc_fixpt_from_fraction(28466892, 100000000); + c = dc_fixpt_from_fraction(55991073, 100000000); + reference_white_level = dc_fixpt_from_fraction(4, 1); + } else { + /*light 0-1*/ + b = dc_fixpt_from_fraction(2372241, 100000000); + c = dc_fixpt_add(dc_fixpt_one, dc_fixpt_from_fraction(429347, 100000000)); + reference_white_level = dc_fixpt_from_fraction(1, 3); + } + if (dc_fixpt_lt(dc_fixpt_half, in_x)) + *out_y = dc_fixpt_add(dc_fixpt_exp(dc_fixpt_div(dc_fixpt_sub(in_x, c), a)), b); + else + *out_y = dc_fixpt_mul(dc_fixpt_pow(in_x, dc_fixpt_from_fraction(2, 1)), reference_white_level); +} + + +/* one-time pre-compute PQ values - only for sdr_white_level 80 */ +void precompute_pq(void) +{ + int i; + struct fixed31_32 x; + const struct hw_x_point *coord_x = coordinates_x + 32; + struct fixed31_32 scaling_factor = + dc_fixpt_from_fraction(80, 10000); + + /* pow function has problems with arguments too small */ + for (i = 0; i < 32; i++) + pq_table[i] = dc_fixpt_zero; + + for (i = 32; i <= MAX_HW_POINTS; i++) { + x = dc_fixpt_mul(coord_x->x, scaling_factor); + compute_pq(x, &pq_table[i]); + ++coord_x; + } +} + +/* one-time pre-compute dePQ values - only for max pixel value 125 FP16 */ +void precompute_de_pq(void) +{ + int i; + struct fixed31_32 y; + uint32_t begin_index, end_index; + + struct fixed31_32 scaling_factor = dc_fixpt_from_int(125); + + /* X points is 2^-25 to 2^7 + * De-gamma X is 2^-12 to 2^0 – we are skipping first -12-(-25) = 13 regions + */ + begin_index = 13 * NUM_PTS_IN_REGION; + end_index = begin_index + 12 * NUM_PTS_IN_REGION; + + for (i = 0; i <= begin_index; i++) + de_pq_table[i] = dc_fixpt_zero; + + for (; i <= end_index; i++) { + compute_de_pq(coordinates_x[i].x, &y); + de_pq_table[i] = dc_fixpt_mul(y, scaling_factor); + } + + for (; i <= MAX_HW_POINTS; i++) + de_pq_table[i] = de_pq_table[i-1]; +} +struct dividers { + struct fixed31_32 divider1; + struct fixed31_32 divider2; + struct fixed31_32 divider3; +}; + +static void build_coefficients(struct gamma_coefficients *coefficients, bool is_2_4) +{ + static const int32_t numerator01[] = { 31308, 180000}; + static const int32_t numerator02[] = { 12920, 4500}; + static const int32_t numerator03[] = { 55, 99}; + static const int32_t numerator04[] = { 55, 99}; + static const int32_t numerator05[] = { 2400, 2200}; + + uint32_t i = 0; + uint32_t index = is_2_4 == true ? 0:1; + + do { + coefficients->a0[i] = dc_fixpt_from_fraction( + numerator01[index], 10000000); + coefficients->a1[i] = dc_fixpt_from_fraction( + numerator02[index], 1000); + coefficients->a2[i] = dc_fixpt_from_fraction( + numerator03[index], 1000); + coefficients->a3[i] = dc_fixpt_from_fraction( + numerator04[index], 1000); + coefficients->user_gamma[i] = dc_fixpt_from_fraction( + numerator05[index], 1000); + + ++i; + } while (i != ARRAY_SIZE(coefficients->a0)); +} + +static struct fixed31_32 translate_from_linear_space( + struct fixed31_32 arg, + struct fixed31_32 a0, + struct fixed31_32 a1, + struct fixed31_32 a2, + struct fixed31_32 a3, + struct fixed31_32 gamma) +{ + const struct fixed31_32 one = dc_fixpt_from_int(1); + + if (dc_fixpt_lt(one, arg)) + return one; + + if (dc_fixpt_le(arg, dc_fixpt_neg(a0))) + return dc_fixpt_sub( + a2, + dc_fixpt_mul( + dc_fixpt_add( + one, + a3), + dc_fixpt_pow( + dc_fixpt_neg(arg), + dc_fixpt_recip(gamma)))); + else if (dc_fixpt_le(a0, arg)) + return dc_fixpt_sub( + dc_fixpt_mul( + dc_fixpt_add( + one, + a3), + dc_fixpt_pow( + arg, + dc_fixpt_recip(gamma))), + a2); + else + return dc_fixpt_mul( + arg, + a1); +} + +static struct fixed31_32 translate_to_linear_space( + struct fixed31_32 arg, + struct fixed31_32 a0, + struct fixed31_32 a1, + struct fixed31_32 a2, + struct fixed31_32 a3, + struct fixed31_32 gamma) +{ + struct fixed31_32 linear; + + a0 = dc_fixpt_mul(a0, a1); + if (dc_fixpt_le(arg, dc_fixpt_neg(a0))) + + linear = dc_fixpt_neg( + dc_fixpt_pow( + dc_fixpt_div( + dc_fixpt_sub(a2, arg), + dc_fixpt_add( + dc_fixpt_one, a3)), gamma)); + + else if (dc_fixpt_le(dc_fixpt_neg(a0), arg) && + dc_fixpt_le(arg, a0)) + linear = dc_fixpt_div(arg, a1); + else + linear = dc_fixpt_pow( + dc_fixpt_div( + dc_fixpt_add(a2, arg), + dc_fixpt_add( + dc_fixpt_one, a3)), gamma); + + return linear; +} + +static inline struct fixed31_32 translate_from_linear_space_ex( + struct fixed31_32 arg, + struct gamma_coefficients *coeff, + uint32_t color_index) +{ + return translate_from_linear_space( + arg, + coeff->a0[color_index], + coeff->a1[color_index], + coeff->a2[color_index], + coeff->a3[color_index], + coeff->user_gamma[color_index]); +} + + +static inline struct fixed31_32 translate_to_linear_space_ex( + struct fixed31_32 arg, + struct gamma_coefficients *coeff, + uint32_t color_index) +{ + return translate_to_linear_space( + arg, + coeff->a0[color_index], + coeff->a1[color_index], + coeff->a2[color_index], + coeff->a3[color_index], + coeff->user_gamma[color_index]); +} + + +static bool find_software_points( + const struct dc_gamma *ramp, + const struct gamma_pixel *axis_x, + struct fixed31_32 hw_point, + enum channel_name channel, + uint32_t *index_to_start, + uint32_t *index_left, + uint32_t *index_right, + enum hw_point_position *pos) +{ + const uint32_t max_number = ramp->num_entries + 3; + + struct fixed31_32 left, right; + + uint32_t i = *index_to_start; + + while (i < max_number) { + if (channel == CHANNEL_NAME_RED) { + left = axis_x[i].r; + + if (i < max_number - 1) + right = axis_x[i + 1].r; + else + right = axis_x[max_number - 1].r; + } else if (channel == CHANNEL_NAME_GREEN) { + left = axis_x[i].g; + + if (i < max_number - 1) + right = axis_x[i + 1].g; + else + right = axis_x[max_number - 1].g; + } else { + left = axis_x[i].b; + + if (i < max_number - 1) + right = axis_x[i + 1].b; + else + right = axis_x[max_number - 1].b; + } + + if (dc_fixpt_le(left, hw_point) && + dc_fixpt_le(hw_point, right)) { + *index_to_start = i; + *index_left = i; + + if (i < max_number - 1) + *index_right = i + 1; + else + *index_right = max_number - 1; + + *pos = HW_POINT_POSITION_MIDDLE; + + return true; + } else if ((i == *index_to_start) && + dc_fixpt_le(hw_point, left)) { + *index_to_start = i; + *index_left = i; + *index_right = i; + + *pos = HW_POINT_POSITION_LEFT; + + return true; + } else if ((i == max_number - 1) && + dc_fixpt_le(right, hw_point)) { + *index_to_start = i; + *index_left = i; + *index_right = i; + + *pos = HW_POINT_POSITION_RIGHT; + + return true; + } + + ++i; + } + + return false; +} + +static bool build_custom_gamma_mapping_coefficients_worker( + const struct dc_gamma *ramp, + struct pixel_gamma_point *coeff, + const struct hw_x_point *coordinates_x, + const struct gamma_pixel *axis_x, + enum channel_name channel, + uint32_t number_of_points) +{ + uint32_t i = 0; + + while (i <= number_of_points) { + struct fixed31_32 coord_x; + + uint32_t index_to_start = 0; + uint32_t index_left = 0; + uint32_t index_right = 0; + + enum hw_point_position hw_pos; + + struct gamma_point *point; + + struct fixed31_32 left_pos; + struct fixed31_32 right_pos; + + if (channel == CHANNEL_NAME_RED) + coord_x = coordinates_x[i].regamma_y_red; + else if (channel == CHANNEL_NAME_GREEN) + coord_x = coordinates_x[i].regamma_y_green; + else + coord_x = coordinates_x[i].regamma_y_blue; + + if (!find_software_points( + ramp, axis_x, coord_x, channel, + &index_to_start, &index_left, &index_right, &hw_pos)) { + BREAK_TO_DEBUGGER(); + return false; + } + + if (index_left >= ramp->num_entries + 3) { + BREAK_TO_DEBUGGER(); + return false; + } + + if (index_right >= ramp->num_entries + 3) { + BREAK_TO_DEBUGGER(); + return false; + } + + if (channel == CHANNEL_NAME_RED) { + point = &coeff[i].r; + + left_pos = axis_x[index_left].r; + right_pos = axis_x[index_right].r; + } else if (channel == CHANNEL_NAME_GREEN) { + point = &coeff[i].g; + + left_pos = axis_x[index_left].g; + right_pos = axis_x[index_right].g; + } else { + point = &coeff[i].b; + + left_pos = axis_x[index_left].b; + right_pos = axis_x[index_right].b; + } + + if (hw_pos == HW_POINT_POSITION_MIDDLE) + point->coeff = dc_fixpt_div( + dc_fixpt_sub( + coord_x, + left_pos), + dc_fixpt_sub( + right_pos, + left_pos)); + else if (hw_pos == HW_POINT_POSITION_LEFT) + point->coeff = dc_fixpt_zero; + else if (hw_pos == HW_POINT_POSITION_RIGHT) + point->coeff = dc_fixpt_from_int(2); + else { + BREAK_TO_DEBUGGER(); + return false; + } + + point->left_index = index_left; + point->right_index = index_right; + point->pos = hw_pos; + + ++i; + } + + return true; +} + +static struct fixed31_32 calculate_mapped_value( + struct pwl_float_data *rgb, + const struct pixel_gamma_point *coeff, + enum channel_name channel, + uint32_t max_index) +{ + const struct gamma_point *point; + + struct fixed31_32 result; + + if (channel == CHANNEL_NAME_RED) + point = &coeff->r; + else if (channel == CHANNEL_NAME_GREEN) + point = &coeff->g; + else + point = &coeff->b; + + if ((point->left_index < 0) || (point->left_index > max_index)) { + BREAK_TO_DEBUGGER(); + return dc_fixpt_zero; + } + + if ((point->right_index < 0) || (point->right_index > max_index)) { + BREAK_TO_DEBUGGER(); + return dc_fixpt_zero; + } + + if (point->pos == HW_POINT_POSITION_MIDDLE) + if (channel == CHANNEL_NAME_RED) + result = dc_fixpt_add( + dc_fixpt_mul( + point->coeff, + dc_fixpt_sub( + rgb[point->right_index].r, + rgb[point->left_index].r)), + rgb[point->left_index].r); + else if (channel == CHANNEL_NAME_GREEN) + result = dc_fixpt_add( + dc_fixpt_mul( + point->coeff, + dc_fixpt_sub( + rgb[point->right_index].g, + rgb[point->left_index].g)), + rgb[point->left_index].g); + else + result = dc_fixpt_add( + dc_fixpt_mul( + point->coeff, + dc_fixpt_sub( + rgb[point->right_index].b, + rgb[point->left_index].b)), + rgb[point->left_index].b); + else if (point->pos == HW_POINT_POSITION_LEFT) { + BREAK_TO_DEBUGGER(); + result = dc_fixpt_zero; + } else { + BREAK_TO_DEBUGGER(); + result = dc_fixpt_one; + } + + return result; +} + +static void build_pq(struct pwl_float_data_ex *rgb_regamma, + uint32_t hw_points_num, + const struct hw_x_point *coordinate_x, + uint32_t sdr_white_level) +{ + uint32_t i, start_index; + + struct pwl_float_data_ex *rgb = rgb_regamma; + const struct hw_x_point *coord_x = coordinate_x; + struct fixed31_32 x; + struct fixed31_32 output; + struct fixed31_32 scaling_factor = + dc_fixpt_from_fraction(sdr_white_level, 10000); + + if (!pq_initialized && sdr_white_level == 80) { + precompute_pq(); + pq_initialized = true; + } + + /* TODO: start index is from segment 2^-24, skipping first segment + * due to x values too small for power calculations + */ + start_index = 32; + rgb += start_index; + coord_x += start_index; + + for (i = start_index; i <= hw_points_num; i++) { + /* Multiply 0.008 as regamma is 0-1 and FP16 input is 0-125. + * FP 1.0 = 80nits + */ + if (sdr_white_level == 80) { + output = pq_table[i]; + } else { + x = dc_fixpt_mul(coord_x->x, scaling_factor); + compute_pq(x, &output); + } + + /* should really not happen? */ + if (dc_fixpt_lt(output, dc_fixpt_zero)) + output = dc_fixpt_zero; + else if (dc_fixpt_lt(dc_fixpt_one, output)) + output = dc_fixpt_one; + + rgb->r = output; + rgb->g = output; + rgb->b = output; + + ++coord_x; + ++rgb; + } +} + +static void build_de_pq(struct pwl_float_data_ex *de_pq, + uint32_t hw_points_num, + const struct hw_x_point *coordinate_x) +{ + uint32_t i; + struct fixed31_32 output; + + struct fixed31_32 scaling_factor = dc_fixpt_from_int(125); + + if (!de_pq_initialized) { + precompute_de_pq(); + de_pq_initialized = true; + } + + + for (i = 0; i <= hw_points_num; i++) { + output = de_pq_table[i]; + /* should really not happen? */ + if (dc_fixpt_lt(output, dc_fixpt_zero)) + output = dc_fixpt_zero; + else if (dc_fixpt_lt(scaling_factor, output)) + output = scaling_factor; + de_pq[i].r = output; + de_pq[i].g = output; + de_pq[i].b = output; + } +} + +static void build_regamma(struct pwl_float_data_ex *rgb_regamma, + uint32_t hw_points_num, + const struct hw_x_point *coordinate_x, bool is_2_4) +{ + uint32_t i; + + struct gamma_coefficients coeff; + struct pwl_float_data_ex *rgb = rgb_regamma; + const struct hw_x_point *coord_x = coordinate_x; + + build_coefficients(&coeff, is_2_4); + + i = 0; + + while (i != hw_points_num + 1) { + /*TODO use y vs r,g,b*/ + rgb->r = translate_from_linear_space_ex( + coord_x->x, &coeff, 0); + rgb->g = rgb->r; + rgb->b = rgb->r; + ++coord_x; + ++rgb; + ++i; + } +} + +static void build_degamma(struct pwl_float_data_ex *curve, + uint32_t hw_points_num, + const struct hw_x_point *coordinate_x, bool is_2_4) +{ + uint32_t i; + struct gamma_coefficients coeff; + uint32_t begin_index, end_index; + + build_coefficients(&coeff, is_2_4); + i = 0; + + /* X points is 2^-25 to 2^7 + * De-gamma X is 2^-12 to 2^0 – we are skipping first -12-(-25) = 13 regions + */ + begin_index = 13 * NUM_PTS_IN_REGION; + end_index = begin_index + 12 * NUM_PTS_IN_REGION; + + while (i != begin_index) { + curve[i].r = dc_fixpt_zero; + curve[i].g = dc_fixpt_zero; + curve[i].b = dc_fixpt_zero; + i++; + } + + while (i != end_index) { + curve[i].r = translate_to_linear_space_ex( + coordinate_x[i].x, &coeff, 0); + curve[i].g = curve[i].r; + curve[i].b = curve[i].r; + i++; + } + while (i != hw_points_num + 1) { + curve[i].r = dc_fixpt_one; + curve[i].g = dc_fixpt_one; + curve[i].b = dc_fixpt_one; + i++; + } +} + +static void build_hlg_degamma(struct pwl_float_data_ex *degamma, + uint32_t hw_points_num, + const struct hw_x_point *coordinate_x, bool is_light0_12) +{ + uint32_t i; + + struct pwl_float_data_ex *rgb = degamma; + const struct hw_x_point *coord_x = coordinate_x; + + i = 0; + + while (i != hw_points_num + 1) { + compute_hlg_oetf(coord_x->x, is_light0_12, &rgb->r); + rgb->g = rgb->r; + rgb->b = rgb->r; + ++coord_x; + ++rgb; + ++i; + } +} + +static void build_hlg_regamma(struct pwl_float_data_ex *regamma, + uint32_t hw_points_num, + const struct hw_x_point *coordinate_x, bool is_light0_12) +{ + uint32_t i; + + struct pwl_float_data_ex *rgb = regamma; + const struct hw_x_point *coord_x = coordinate_x; + + i = 0; + + while (i != hw_points_num + 1) { + compute_hlg_eotf(coord_x->x, is_light0_12, &rgb->r); + rgb->g = rgb->r; + rgb->b = rgb->r; + ++coord_x; + ++rgb; + ++i; + } +} + +static void scale_gamma(struct pwl_float_data *pwl_rgb, + const struct dc_gamma *ramp, + struct dividers dividers) +{ + const struct fixed31_32 max_driver = dc_fixpt_from_int(0xFFFF); + const struct fixed31_32 max_os = dc_fixpt_from_int(0xFF00); + struct fixed31_32 scaler = max_os; + uint32_t i; + struct pwl_float_data *rgb = pwl_rgb; + struct pwl_float_data *rgb_last = rgb + ramp->num_entries - 1; + + i = 0; + + do { + if (dc_fixpt_lt(max_os, ramp->entries.red[i]) || + dc_fixpt_lt(max_os, ramp->entries.green[i]) || + dc_fixpt_lt(max_os, ramp->entries.blue[i])) { + scaler = max_driver; + break; + } + ++i; + } while (i != ramp->num_entries); + + i = 0; + + do { + rgb->r = dc_fixpt_div( + ramp->entries.red[i], scaler); + rgb->g = dc_fixpt_div( + ramp->entries.green[i], scaler); + rgb->b = dc_fixpt_div( + ramp->entries.blue[i], scaler); + + ++rgb; + ++i; + } while (i != ramp->num_entries); + + rgb->r = dc_fixpt_mul(rgb_last->r, + dividers.divider1); + rgb->g = dc_fixpt_mul(rgb_last->g, + dividers.divider1); + rgb->b = dc_fixpt_mul(rgb_last->b, + dividers.divider1); + + ++rgb; + + rgb->r = dc_fixpt_mul(rgb_last->r, + dividers.divider2); + rgb->g = dc_fixpt_mul(rgb_last->g, + dividers.divider2); + rgb->b = dc_fixpt_mul(rgb_last->b, + dividers.divider2); + + ++rgb; + + rgb->r = dc_fixpt_mul(rgb_last->r, + dividers.divider3); + rgb->g = dc_fixpt_mul(rgb_last->g, + dividers.divider3); + rgb->b = dc_fixpt_mul(rgb_last->b, + dividers.divider3); +} + +static void scale_gamma_dx(struct pwl_float_data *pwl_rgb, + const struct dc_gamma *ramp, + struct dividers dividers) +{ + uint32_t i; + struct fixed31_32 min = dc_fixpt_zero; + struct fixed31_32 max = dc_fixpt_one; + + struct fixed31_32 delta = dc_fixpt_zero; + struct fixed31_32 offset = dc_fixpt_zero; + + for (i = 0 ; i < ramp->num_entries; i++) { + if (dc_fixpt_lt(ramp->entries.red[i], min)) + min = ramp->entries.red[i]; + + if (dc_fixpt_lt(ramp->entries.green[i], min)) + min = ramp->entries.green[i]; + + if (dc_fixpt_lt(ramp->entries.blue[i], min)) + min = ramp->entries.blue[i]; + + if (dc_fixpt_lt(max, ramp->entries.red[i])) + max = ramp->entries.red[i]; + + if (dc_fixpt_lt(max, ramp->entries.green[i])) + max = ramp->entries.green[i]; + + if (dc_fixpt_lt(max, ramp->entries.blue[i])) + max = ramp->entries.blue[i]; + } + + if (dc_fixpt_lt(min, dc_fixpt_zero)) + delta = dc_fixpt_neg(min); + + offset = dc_fixpt_add(min, max); + + for (i = 0 ; i < ramp->num_entries; i++) { + pwl_rgb[i].r = dc_fixpt_div( + dc_fixpt_add( + ramp->entries.red[i], delta), offset); + pwl_rgb[i].g = dc_fixpt_div( + dc_fixpt_add( + ramp->entries.green[i], delta), offset); + pwl_rgb[i].b = dc_fixpt_div( + dc_fixpt_add( + ramp->entries.blue[i], delta), offset); + + } + + pwl_rgb[i].r = dc_fixpt_sub(dc_fixpt_mul_int( + pwl_rgb[i-1].r, 2), pwl_rgb[i-2].r); + pwl_rgb[i].g = dc_fixpt_sub(dc_fixpt_mul_int( + pwl_rgb[i-1].g, 2), pwl_rgb[i-2].g); + pwl_rgb[i].b = dc_fixpt_sub(dc_fixpt_mul_int( + pwl_rgb[i-1].b, 2), pwl_rgb[i-2].b); + ++i; + pwl_rgb[i].r = dc_fixpt_sub(dc_fixpt_mul_int( + pwl_rgb[i-1].r, 2), pwl_rgb[i-2].r); + pwl_rgb[i].g = dc_fixpt_sub(dc_fixpt_mul_int( + pwl_rgb[i-1].g, 2), pwl_rgb[i-2].g); + pwl_rgb[i].b = dc_fixpt_sub(dc_fixpt_mul_int( + pwl_rgb[i-1].b, 2), pwl_rgb[i-2].b); +} + +/* todo: all these scale_gamma functions are inherently the same but + * take different structures as params or different format for ramp + * values. We could probably implement it in a more generic fashion + */ +static void scale_user_regamma_ramp(struct pwl_float_data *pwl_rgb, + const struct regamma_ramp *ramp, + struct dividers dividers) +{ + unsigned short max_driver = 0xFFFF; + unsigned short max_os = 0xFF00; + unsigned short scaler = max_os; + uint32_t i; + struct pwl_float_data *rgb = pwl_rgb; + struct pwl_float_data *rgb_last = rgb + GAMMA_RGB_256_ENTRIES - 1; + + i = 0; + do { + if (ramp->gamma[i] > max_os || + ramp->gamma[i + 256] > max_os || + ramp->gamma[i + 512] > max_os) { + scaler = max_driver; + break; + } + i++; + } while (i != GAMMA_RGB_256_ENTRIES); + + i = 0; + do { + rgb->r = dc_fixpt_from_fraction( + ramp->gamma[i], scaler); + rgb->g = dc_fixpt_from_fraction( + ramp->gamma[i + 256], scaler); + rgb->b = dc_fixpt_from_fraction( + ramp->gamma[i + 512], scaler); + + ++rgb; + ++i; + } while (i != GAMMA_RGB_256_ENTRIES); + + rgb->r = dc_fixpt_mul(rgb_last->r, + dividers.divider1); + rgb->g = dc_fixpt_mul(rgb_last->g, + dividers.divider1); + rgb->b = dc_fixpt_mul(rgb_last->b, + dividers.divider1); + + ++rgb; + + rgb->r = dc_fixpt_mul(rgb_last->r, + dividers.divider2); + rgb->g = dc_fixpt_mul(rgb_last->g, + dividers.divider2); + rgb->b = dc_fixpt_mul(rgb_last->b, + dividers.divider2); + + ++rgb; + + rgb->r = dc_fixpt_mul(rgb_last->r, + dividers.divider3); + rgb->g = dc_fixpt_mul(rgb_last->g, + dividers.divider3); + rgb->b = dc_fixpt_mul(rgb_last->b, + dividers.divider3); +} + +/* + * RS3+ color transform DDI - 1D LUT adjustment is composed with regamma here + * Input is evenly distributed in the output color space as specified in + * SetTimings + * + * Interpolation details: + * 1D LUT has 4096 values which give curve correction in 0-1 float range + * for evenly spaced points in 0-1 range. lut1D[index] gives correction + * for index/4095. + * First we find index for which: + * index/4095 < regamma_y < (index+1)/4095 => + * index < 4095*regamma_y < index + 1 + * norm_y = 4095*regamma_y, and index is just truncating to nearest integer + * lut1 = lut1D[index], lut2 = lut1D[index+1] + * + * adjustedY is then linearly interpolating regamma Y between lut1 and lut2 + * + * Custom degamma on Linux uses the same interpolation math, so is handled here + */ +static void apply_lut_1d( + const struct dc_gamma *ramp, + uint32_t num_hw_points, + struct dc_transfer_func_distributed_points *tf_pts) +{ + int i = 0; + int color = 0; + struct fixed31_32 *regamma_y; + struct fixed31_32 norm_y; + struct fixed31_32 lut1; + struct fixed31_32 lut2; + const int max_lut_index = 4095; + const struct fixed31_32 max_lut_index_f = + dc_fixpt_from_int(max_lut_index); + int32_t index = 0, index_next = 0; + struct fixed31_32 index_f; + struct fixed31_32 delta_lut; + struct fixed31_32 delta_index; + + if (ramp->type != GAMMA_CS_TFM_1D && ramp->type != GAMMA_CUSTOM) + return; // this is not expected + + for (i = 0; i < num_hw_points; i++) { + for (color = 0; color < 3; color++) { + if (color == 0) + regamma_y = &tf_pts->red[i]; + else if (color == 1) + regamma_y = &tf_pts->green[i]; + else + regamma_y = &tf_pts->blue[i]; + + norm_y = dc_fixpt_mul(max_lut_index_f, + *regamma_y); + index = dc_fixpt_floor(norm_y); + index_f = dc_fixpt_from_int(index); + + if (index < 0 || index > max_lut_index) + continue; + + index_next = (index == max_lut_index) ? index : index+1; + + if (color == 0) { + lut1 = ramp->entries.red[index]; + lut2 = ramp->entries.red[index_next]; + } else if (color == 1) { + lut1 = ramp->entries.green[index]; + lut2 = ramp->entries.green[index_next]; + } else { + lut1 = ramp->entries.blue[index]; + lut2 = ramp->entries.blue[index_next]; + } + + // we have everything now, so interpolate + delta_lut = dc_fixpt_sub(lut2, lut1); + delta_index = dc_fixpt_sub(norm_y, index_f); + + *regamma_y = dc_fixpt_add(lut1, + dc_fixpt_mul(delta_index, delta_lut)); + } + } +} + +static void build_evenly_distributed_points( + struct gamma_pixel *points, + uint32_t numberof_points, + struct dividers dividers) +{ + struct gamma_pixel *p = points; + struct gamma_pixel *p_last; + + uint32_t i = 0; + + // This function should not gets called with 0 as a parameter + ASSERT(numberof_points > 0); + p_last = p + numberof_points - 1; + + do { + struct fixed31_32 value = dc_fixpt_from_fraction(i, + numberof_points - 1); + + p->r = value; + p->g = value; + p->b = value; + + ++p; + ++i; + } while (i < numberof_points); + + p->r = dc_fixpt_div(p_last->r, dividers.divider1); + p->g = dc_fixpt_div(p_last->g, dividers.divider1); + p->b = dc_fixpt_div(p_last->b, dividers.divider1); + + ++p; + + p->r = dc_fixpt_div(p_last->r, dividers.divider2); + p->g = dc_fixpt_div(p_last->g, dividers.divider2); + p->b = dc_fixpt_div(p_last->b, dividers.divider2); + + ++p; + + p->r = dc_fixpt_div(p_last->r, dividers.divider3); + p->g = dc_fixpt_div(p_last->g, dividers.divider3); + p->b = dc_fixpt_div(p_last->b, dividers.divider3); +} + +static inline void copy_rgb_regamma_to_coordinates_x( + struct hw_x_point *coordinates_x, + uint32_t hw_points_num, + const struct pwl_float_data_ex *rgb_ex) +{ + struct hw_x_point *coords = coordinates_x; + uint32_t i = 0; + const struct pwl_float_data_ex *rgb_regamma = rgb_ex; + + while (i <= hw_points_num + 1) { + coords->regamma_y_red = rgb_regamma->r; + coords->regamma_y_green = rgb_regamma->g; + coords->regamma_y_blue = rgb_regamma->b; + + ++coords; + ++rgb_regamma; + ++i; + } +} + +static bool calculate_interpolated_hardware_curve( + const struct dc_gamma *ramp, + struct pixel_gamma_point *coeff128, + struct pwl_float_data *rgb_user, + const struct hw_x_point *coordinates_x, + const struct gamma_pixel *axis_x, + uint32_t number_of_points, + struct dc_transfer_func_distributed_points *tf_pts) +{ + + const struct pixel_gamma_point *coeff = coeff128; + uint32_t max_entries = 3 - 1; + + uint32_t i = 0; + + for (i = 0; i < 3; i++) { + if (!build_custom_gamma_mapping_coefficients_worker( + ramp, coeff128, coordinates_x, axis_x, i, + number_of_points)) + return false; + } + + i = 0; + max_entries += ramp->num_entries; + + /* TODO: float point case */ + + while (i <= number_of_points) { + tf_pts->red[i] = calculate_mapped_value( + rgb_user, coeff, CHANNEL_NAME_RED, max_entries); + tf_pts->green[i] = calculate_mapped_value( + rgb_user, coeff, CHANNEL_NAME_GREEN, max_entries); + tf_pts->blue[i] = calculate_mapped_value( + rgb_user, coeff, CHANNEL_NAME_BLUE, max_entries); + + ++coeff; + ++i; + } + + return true; +} + +/* The "old" interpolation uses a complicated scheme to build an array of + * coefficients while also using an array of 0-255 normalized to 0-1 + * Then there's another loop using both of the above + new scaled user ramp + * and we concatenate them. It also searches for points of interpolation and + * uses enums for positions. + * + * This function uses a different approach: + * user ramp is always applied on X with 0/255, 1/255, 2/255, ..., 255/255 + * To find index for hwX , we notice the following: + * i/255 <= hwX < (i+1)/255 <=> i <= 255*hwX < i+1 + * See apply_lut_1d which is the same principle, but on 4K entry 1D LUT + * + * Once the index is known, combined Y is simply: + * user_ramp(index) + (hwX-index/255)*(user_ramp(index+1) - user_ramp(index) + * + * We should switch to this method in all cases, it's simpler and faster + * ToDo one day - for now this only applies to ADL regamma to avoid regression + * for regular use cases (sRGB and PQ) + */ +static void interpolate_user_regamma(uint32_t hw_points_num, + struct pwl_float_data *rgb_user, + bool apply_degamma, + struct dc_transfer_func_distributed_points *tf_pts) +{ + uint32_t i; + uint32_t color = 0; + int32_t index; + int32_t index_next; + struct fixed31_32 *tf_point; + struct fixed31_32 hw_x; + struct fixed31_32 norm_factor = + dc_fixpt_from_int(255); + struct fixed31_32 norm_x; + struct fixed31_32 index_f; + struct fixed31_32 lut1; + struct fixed31_32 lut2; + struct fixed31_32 delta_lut; + struct fixed31_32 delta_index; + + i = 0; + /* fixed_pt library has problems handling too small values */ + while (i != 32) { + tf_pts->red[i] = dc_fixpt_zero; + tf_pts->green[i] = dc_fixpt_zero; + tf_pts->blue[i] = dc_fixpt_zero; + ++i; + } + while (i <= hw_points_num + 1) { + for (color = 0; color < 3; color++) { + if (color == 0) + tf_point = &tf_pts->red[i]; + else if (color == 1) + tf_point = &tf_pts->green[i]; + else + tf_point = &tf_pts->blue[i]; + + if (apply_degamma) { + if (color == 0) + hw_x = coordinates_x[i].regamma_y_red; + else if (color == 1) + hw_x = coordinates_x[i].regamma_y_green; + else + hw_x = coordinates_x[i].regamma_y_blue; + } else + hw_x = coordinates_x[i].x; + + norm_x = dc_fixpt_mul(norm_factor, hw_x); + index = dc_fixpt_floor(norm_x); + if (index < 0 || index > 255) + continue; + + index_f = dc_fixpt_from_int(index); + index_next = (index == 255) ? index : index + 1; + + if (color == 0) { + lut1 = rgb_user[index].r; + lut2 = rgb_user[index_next].r; + } else if (color == 1) { + lut1 = rgb_user[index].g; + lut2 = rgb_user[index_next].g; + } else { + lut1 = rgb_user[index].b; + lut2 = rgb_user[index_next].b; + } + + // we have everything now, so interpolate + delta_lut = dc_fixpt_sub(lut2, lut1); + delta_index = dc_fixpt_sub(norm_x, index_f); + + *tf_point = dc_fixpt_add(lut1, + dc_fixpt_mul(delta_index, delta_lut)); + } + ++i; + } +} + +static void build_new_custom_resulted_curve( + uint32_t hw_points_num, + struct dc_transfer_func_distributed_points *tf_pts) +{ + uint32_t i; + + i = 0; + + while (i != hw_points_num + 1) { + tf_pts->red[i] = dc_fixpt_clamp( + tf_pts->red[i], dc_fixpt_zero, + dc_fixpt_one); + tf_pts->green[i] = dc_fixpt_clamp( + tf_pts->green[i], dc_fixpt_zero, + dc_fixpt_one); + tf_pts->blue[i] = dc_fixpt_clamp( + tf_pts->blue[i], dc_fixpt_zero, + dc_fixpt_one); + + ++i; + } +} + +static void apply_degamma_for_user_regamma(struct pwl_float_data_ex *rgb_regamma, + uint32_t hw_points_num) +{ + uint32_t i; + + struct gamma_coefficients coeff; + struct pwl_float_data_ex *rgb = rgb_regamma; + const struct hw_x_point *coord_x = coordinates_x; + + build_coefficients(&coeff, true); + + i = 0; + while (i != hw_points_num + 1) { + rgb->r = translate_from_linear_space_ex( + coord_x->x, &coeff, 0); + rgb->g = rgb->r; + rgb->b = rgb->r; + ++coord_x; + ++rgb; + ++i; + } +} + +static bool map_regamma_hw_to_x_user( + const struct dc_gamma *ramp, + struct pixel_gamma_point *coeff128, + struct pwl_float_data *rgb_user, + struct hw_x_point *coords_x, + const struct gamma_pixel *axis_x, + const struct pwl_float_data_ex *rgb_regamma, + uint32_t hw_points_num, + struct dc_transfer_func_distributed_points *tf_pts, + bool mapUserRamp) +{ + /* setup to spare calculated ideal regamma values */ + + int i = 0; + struct hw_x_point *coords = coords_x; + const struct pwl_float_data_ex *regamma = rgb_regamma; + + if (mapUserRamp) { + copy_rgb_regamma_to_coordinates_x(coords, + hw_points_num, + rgb_regamma); + + calculate_interpolated_hardware_curve( + ramp, coeff128, rgb_user, coords, axis_x, + hw_points_num, tf_pts); + } else { + /* just copy current rgb_regamma into tf_pts */ + while (i <= hw_points_num) { + tf_pts->red[i] = regamma->r; + tf_pts->green[i] = regamma->g; + tf_pts->blue[i] = regamma->b; + + ++regamma; + ++i; + } + } + + /* this should be named differently, all it does is clamp to 0-1 */ + build_new_custom_resulted_curve(hw_points_num, tf_pts); + + return true; +} + +#define _EXTRA_POINTS 3 + +bool mod_color_calculate_regamma_params(struct dc_transfer_func *output_tf, + const struct dc_gamma *ramp, bool mapUserRamp) +{ + struct dc_transfer_func_distributed_points *tf_pts = &output_tf->tf_pts; + struct dividers dividers; + + struct pwl_float_data *rgb_user = NULL; + struct pwl_float_data_ex *rgb_regamma = NULL; + struct gamma_pixel *axix_x = NULL; + struct pixel_gamma_point *coeff = NULL; + enum dc_transfer_func_predefined tf = TRANSFER_FUNCTION_SRGB; + bool ret = false; + + if (output_tf->type == TF_TYPE_BYPASS) + return false; + + /* we can use hardcoded curve for plain SRGB TF */ + if (output_tf->type == TF_TYPE_PREDEFINED && + output_tf->tf == TRANSFER_FUNCTION_SRGB && + (!mapUserRamp && ramp->type == GAMMA_RGB_256)) + return true; + + output_tf->type = TF_TYPE_DISTRIBUTED_POINTS; + + rgb_user = kvcalloc(ramp->num_entries + _EXTRA_POINTS, + sizeof(*rgb_user), + GFP_KERNEL); + if (!rgb_user) + goto rgb_user_alloc_fail; + rgb_regamma = kvcalloc(MAX_HW_POINTS + _EXTRA_POINTS, + sizeof(*rgb_regamma), + GFP_KERNEL); + if (!rgb_regamma) + goto rgb_regamma_alloc_fail; + axix_x = kvcalloc(ramp->num_entries + 3, sizeof(*axix_x), + GFP_KERNEL); + if (!axix_x) + goto axix_x_alloc_fail; + coeff = kvcalloc(MAX_HW_POINTS + _EXTRA_POINTS, sizeof(*coeff), + GFP_KERNEL); + if (!coeff) + goto coeff_alloc_fail; + + dividers.divider1 = dc_fixpt_from_fraction(3, 2); + dividers.divider2 = dc_fixpt_from_int(2); + dividers.divider3 = dc_fixpt_from_fraction(5, 2); + + tf = output_tf->tf; + + build_evenly_distributed_points( + axix_x, + ramp->num_entries, + dividers); + + if (ramp->type == GAMMA_RGB_256 && mapUserRamp) + scale_gamma(rgb_user, ramp, dividers); + else if (ramp->type == GAMMA_RGB_FLOAT_1024) + scale_gamma_dx(rgb_user, ramp, dividers); + + if (tf == TRANSFER_FUNCTION_PQ) { + tf_pts->end_exponent = 7; + tf_pts->x_point_at_y1_red = 125; + tf_pts->x_point_at_y1_green = 125; + tf_pts->x_point_at_y1_blue = 125; + + build_pq(rgb_regamma, + MAX_HW_POINTS, + coordinates_x, + output_tf->sdr_ref_white_level); + } else { + tf_pts->end_exponent = 0; + tf_pts->x_point_at_y1_red = 1; + tf_pts->x_point_at_y1_green = 1; + tf_pts->x_point_at_y1_blue = 1; + + build_regamma(rgb_regamma, + MAX_HW_POINTS, + coordinates_x, tf == TRANSFER_FUNCTION_SRGB ? true:false); + } + + map_regamma_hw_to_x_user(ramp, coeff, rgb_user, + coordinates_x, axix_x, rgb_regamma, + MAX_HW_POINTS, tf_pts, + (mapUserRamp || ramp->type != GAMMA_RGB_256) && + ramp->type != GAMMA_CS_TFM_1D); + + if (ramp->type == GAMMA_CS_TFM_1D) + apply_lut_1d(ramp, MAX_HW_POINTS, tf_pts); + + ret = true; + + kvfree(coeff); +coeff_alloc_fail: + kvfree(axix_x); +axix_x_alloc_fail: + kvfree(rgb_regamma); +rgb_regamma_alloc_fail: + kvfree(rgb_user); +rgb_user_alloc_fail: + return ret; +} + +bool calculate_user_regamma_coeff(struct dc_transfer_func *output_tf, + const struct regamma_lut *regamma) +{ + struct gamma_coefficients coeff; + const struct hw_x_point *coord_x = coordinates_x; + uint32_t i = 0; + + do { + coeff.a0[i] = dc_fixpt_from_fraction( + regamma->coeff.A0[i], 10000000); + coeff.a1[i] = dc_fixpt_from_fraction( + regamma->coeff.A1[i], 1000); + coeff.a2[i] = dc_fixpt_from_fraction( + regamma->coeff.A2[i], 1000); + coeff.a3[i] = dc_fixpt_from_fraction( + regamma->coeff.A3[i], 1000); + coeff.user_gamma[i] = dc_fixpt_from_fraction( + regamma->coeff.gamma[i], 1000); + + ++i; + } while (i != 3); + + i = 0; + /* fixed_pt library has problems handling too small values */ + while (i != 32) { + output_tf->tf_pts.red[i] = dc_fixpt_zero; + output_tf->tf_pts.green[i] = dc_fixpt_zero; + output_tf->tf_pts.blue[i] = dc_fixpt_zero; + ++coord_x; + ++i; + } + while (i != MAX_HW_POINTS + 1) { + output_tf->tf_pts.red[i] = translate_from_linear_space_ex( + coord_x->x, &coeff, 0); + output_tf->tf_pts.green[i] = translate_from_linear_space_ex( + coord_x->x, &coeff, 1); + output_tf->tf_pts.blue[i] = translate_from_linear_space_ex( + coord_x->x, &coeff, 2); + ++coord_x; + ++i; + } + + // this function just clamps output to 0-1 + build_new_custom_resulted_curve(MAX_HW_POINTS, &output_tf->tf_pts); + output_tf->type = TF_TYPE_DISTRIBUTED_POINTS; + + return true; +} + +bool calculate_user_regamma_ramp(struct dc_transfer_func *output_tf, + const struct regamma_lut *regamma) +{ + struct dc_transfer_func_distributed_points *tf_pts = &output_tf->tf_pts; + struct dividers dividers; + + struct pwl_float_data *rgb_user = NULL; + struct pwl_float_data_ex *rgb_regamma = NULL; + bool ret = false; + + if (regamma == NULL) + return false; + + output_tf->type = TF_TYPE_DISTRIBUTED_POINTS; + + rgb_user = kcalloc(GAMMA_RGB_256_ENTRIES + _EXTRA_POINTS, + sizeof(*rgb_user), + GFP_KERNEL); + if (!rgb_user) + goto rgb_user_alloc_fail; + + rgb_regamma = kcalloc(MAX_HW_POINTS + _EXTRA_POINTS, + sizeof(*rgb_regamma), + GFP_KERNEL); + if (!rgb_regamma) + goto rgb_regamma_alloc_fail; + + dividers.divider1 = dc_fixpt_from_fraction(3, 2); + dividers.divider2 = dc_fixpt_from_int(2); + dividers.divider3 = dc_fixpt_from_fraction(5, 2); + + scale_user_regamma_ramp(rgb_user, ®amma->ramp, dividers); + + if (regamma->flags.bits.applyDegamma == 1) { + apply_degamma_for_user_regamma(rgb_regamma, MAX_HW_POINTS); + copy_rgb_regamma_to_coordinates_x(coordinates_x, + MAX_HW_POINTS, rgb_regamma); + } + + interpolate_user_regamma(MAX_HW_POINTS, rgb_user, + regamma->flags.bits.applyDegamma, tf_pts); + + // no custom HDR curves! + tf_pts->end_exponent = 0; + tf_pts->x_point_at_y1_red = 1; + tf_pts->x_point_at_y1_green = 1; + tf_pts->x_point_at_y1_blue = 1; + + // this function just clamps output to 0-1 + build_new_custom_resulted_curve(MAX_HW_POINTS, tf_pts); + + ret = true; + + kfree(rgb_regamma); +rgb_regamma_alloc_fail: + kfree(rgb_user); +rgb_user_alloc_fail: + return ret; +} + +bool mod_color_calculate_degamma_params(struct dc_transfer_func *input_tf, + const struct dc_gamma *ramp, bool mapUserRamp) +{ + struct dc_transfer_func_distributed_points *tf_pts = &input_tf->tf_pts; + struct dividers dividers; + + struct pwl_float_data *rgb_user = NULL; + struct pwl_float_data_ex *curve = NULL; + struct gamma_pixel *axix_x = NULL; + struct pixel_gamma_point *coeff = NULL; + enum dc_transfer_func_predefined tf = TRANSFER_FUNCTION_SRGB; + bool ret = false; + + if (input_tf->type == TF_TYPE_BYPASS) + return false; + + /* we can use hardcoded curve for plain SRGB TF */ + if (input_tf->type == TF_TYPE_PREDEFINED && + input_tf->tf == TRANSFER_FUNCTION_SRGB && + (!mapUserRamp && ramp->type == GAMMA_RGB_256)) + return true; + + input_tf->type = TF_TYPE_DISTRIBUTED_POINTS; + + rgb_user = kvcalloc(ramp->num_entries + _EXTRA_POINTS, + sizeof(*rgb_user), + GFP_KERNEL); + if (!rgb_user) + goto rgb_user_alloc_fail; + curve = kvcalloc(MAX_HW_POINTS + _EXTRA_POINTS, sizeof(*curve), + GFP_KERNEL); + if (!curve) + goto curve_alloc_fail; + axix_x = kvcalloc(ramp->num_entries + _EXTRA_POINTS, sizeof(*axix_x), + GFP_KERNEL); + if (!axix_x) + goto axix_x_alloc_fail; + coeff = kvcalloc(MAX_HW_POINTS + _EXTRA_POINTS, sizeof(*coeff), + GFP_KERNEL); + if (!coeff) + goto coeff_alloc_fail; + + dividers.divider1 = dc_fixpt_from_fraction(3, 2); + dividers.divider2 = dc_fixpt_from_int(2); + dividers.divider3 = dc_fixpt_from_fraction(5, 2); + + tf = input_tf->tf; + + build_evenly_distributed_points( + axix_x, + ramp->num_entries, + dividers); + + if (ramp->type == GAMMA_RGB_256 && mapUserRamp) + scale_gamma(rgb_user, ramp, dividers); + else if (ramp->type == GAMMA_RGB_FLOAT_1024) + scale_gamma_dx(rgb_user, ramp, dividers); + + if (tf == TRANSFER_FUNCTION_PQ) + build_de_pq(curve, + MAX_HW_POINTS, + coordinates_x); + else + build_degamma(curve, + MAX_HW_POINTS, + coordinates_x, + tf == TRANSFER_FUNCTION_SRGB ? true:false); + + tf_pts->end_exponent = 0; + tf_pts->x_point_at_y1_red = 1; + tf_pts->x_point_at_y1_green = 1; + tf_pts->x_point_at_y1_blue = 1; + + map_regamma_hw_to_x_user(ramp, coeff, rgb_user, + coordinates_x, axix_x, curve, + MAX_HW_POINTS, tf_pts, + mapUserRamp && ramp->type != GAMMA_CUSTOM); + if (ramp->type == GAMMA_CUSTOM) + apply_lut_1d(ramp, MAX_HW_POINTS, tf_pts); + + ret = true; + + kvfree(coeff); +coeff_alloc_fail: + kvfree(axix_x); +axix_x_alloc_fail: + kvfree(curve); +curve_alloc_fail: + kvfree(rgb_user); +rgb_user_alloc_fail: + + return ret; + +} + + +bool mod_color_calculate_curve(enum dc_transfer_func_predefined trans, + struct dc_transfer_func_distributed_points *points) +{ + uint32_t i; + bool ret = false; + struct pwl_float_data_ex *rgb_regamma = NULL; + + if (trans == TRANSFER_FUNCTION_UNITY || + trans == TRANSFER_FUNCTION_LINEAR) { + points->end_exponent = 0; + points->x_point_at_y1_red = 1; + points->x_point_at_y1_green = 1; + points->x_point_at_y1_blue = 1; + + for (i = 0; i <= MAX_HW_POINTS ; i++) { + points->red[i] = coordinates_x[i].x; + points->green[i] = coordinates_x[i].x; + points->blue[i] = coordinates_x[i].x; + } + ret = true; + } else if (trans == TRANSFER_FUNCTION_PQ) { + rgb_regamma = kvcalloc(MAX_HW_POINTS + _EXTRA_POINTS, + sizeof(*rgb_regamma), + GFP_KERNEL); + if (!rgb_regamma) + goto rgb_regamma_alloc_fail; + points->end_exponent = 7; + points->x_point_at_y1_red = 125; + points->x_point_at_y1_green = 125; + points->x_point_at_y1_blue = 125; + + + build_pq(rgb_regamma, + MAX_HW_POINTS, + coordinates_x, + 80); + for (i = 0; i <= MAX_HW_POINTS ; i++) { + points->red[i] = rgb_regamma[i].r; + points->green[i] = rgb_regamma[i].g; + points->blue[i] = rgb_regamma[i].b; + } + ret = true; + + kvfree(rgb_regamma); + } else if (trans == TRANSFER_FUNCTION_SRGB || + trans == TRANSFER_FUNCTION_BT709) { + rgb_regamma = kvcalloc(MAX_HW_POINTS + _EXTRA_POINTS, + sizeof(*rgb_regamma), + GFP_KERNEL); + if (!rgb_regamma) + goto rgb_regamma_alloc_fail; + points->end_exponent = 0; + points->x_point_at_y1_red = 1; + points->x_point_at_y1_green = 1; + points->x_point_at_y1_blue = 1; + + build_regamma(rgb_regamma, + MAX_HW_POINTS, + coordinates_x, trans == TRANSFER_FUNCTION_SRGB ? true:false); + for (i = 0; i <= MAX_HW_POINTS ; i++) { + points->red[i] = rgb_regamma[i].r; + points->green[i] = rgb_regamma[i].g; + points->blue[i] = rgb_regamma[i].b; + } + ret = true; + + kvfree(rgb_regamma); + } else if (trans == TRANSFER_FUNCTION_HLG || + trans == TRANSFER_FUNCTION_HLG12) { + rgb_regamma = kvcalloc(MAX_HW_POINTS + _EXTRA_POINTS, + sizeof(*rgb_regamma), + GFP_KERNEL); + if (!rgb_regamma) + goto rgb_regamma_alloc_fail; + + build_hlg_regamma(rgb_regamma, + MAX_HW_POINTS, + coordinates_x, + trans == TRANSFER_FUNCTION_HLG12 ? true:false); + for (i = 0; i <= MAX_HW_POINTS ; i++) { + points->red[i] = rgb_regamma[i].r; + points->green[i] = rgb_regamma[i].g; + points->blue[i] = rgb_regamma[i].b; + } + ret = true; + kvfree(rgb_regamma); + } +rgb_regamma_alloc_fail: + return ret; +} + + +bool mod_color_calculate_degamma_curve(enum dc_transfer_func_predefined trans, + struct dc_transfer_func_distributed_points *points) +{ + uint32_t i; + bool ret = false; + struct pwl_float_data_ex *rgb_degamma = NULL; + + if (trans == TRANSFER_FUNCTION_UNITY || + trans == TRANSFER_FUNCTION_LINEAR) { + + for (i = 0; i <= MAX_HW_POINTS ; i++) { + points->red[i] = coordinates_x[i].x; + points->green[i] = coordinates_x[i].x; + points->blue[i] = coordinates_x[i].x; + } + ret = true; + } else if (trans == TRANSFER_FUNCTION_PQ) { + rgb_degamma = kvcalloc(MAX_HW_POINTS + _EXTRA_POINTS, + sizeof(*rgb_degamma), + GFP_KERNEL); + if (!rgb_degamma) + goto rgb_degamma_alloc_fail; + + + build_de_pq(rgb_degamma, + MAX_HW_POINTS, + coordinates_x); + for (i = 0; i <= MAX_HW_POINTS ; i++) { + points->red[i] = rgb_degamma[i].r; + points->green[i] = rgb_degamma[i].g; + points->blue[i] = rgb_degamma[i].b; + } + ret = true; + + kvfree(rgb_degamma); + } else if (trans == TRANSFER_FUNCTION_SRGB || + trans == TRANSFER_FUNCTION_BT709) { + rgb_degamma = kvcalloc(MAX_HW_POINTS + _EXTRA_POINTS, + sizeof(*rgb_degamma), + GFP_KERNEL); + if (!rgb_degamma) + goto rgb_degamma_alloc_fail; + + build_degamma(rgb_degamma, + MAX_HW_POINTS, + coordinates_x, trans == TRANSFER_FUNCTION_SRGB ? true:false); + for (i = 0; i <= MAX_HW_POINTS ; i++) { + points->red[i] = rgb_degamma[i].r; + points->green[i] = rgb_degamma[i].g; + points->blue[i] = rgb_degamma[i].b; + } + ret = true; + + kvfree(rgb_degamma); + } else if (trans == TRANSFER_FUNCTION_HLG || + trans == TRANSFER_FUNCTION_HLG12) { + rgb_degamma = kvcalloc(MAX_HW_POINTS + _EXTRA_POINTS, + sizeof(*rgb_degamma), + GFP_KERNEL); + if (!rgb_degamma) + goto rgb_degamma_alloc_fail; + + build_hlg_degamma(rgb_degamma, + MAX_HW_POINTS, + coordinates_x, + trans == TRANSFER_FUNCTION_HLG12 ? true:false); + for (i = 0; i <= MAX_HW_POINTS ; i++) { + points->red[i] = rgb_degamma[i].r; + points->green[i] = rgb_degamma[i].g; + points->blue[i] = rgb_degamma[i].b; + } + ret = true; + kvfree(rgb_degamma); + } + points->end_exponent = 0; + points->x_point_at_y1_red = 1; + points->x_point_at_y1_green = 1; + points->x_point_at_y1_blue = 1; + +rgb_degamma_alloc_fail: + return ret; +} + + diff --git a/drivers/gpu/drm/amd/display/modules/color/color_gamma.h b/drivers/gpu/drm/amd/display/modules/color/color_gamma.h new file mode 100644 index 000000000..b64048991 --- /dev/null +++ b/drivers/gpu/drm/amd/display/modules/color/color_gamma.h @@ -0,0 +1,99 @@ +/* + * 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 + * + */ + +#ifndef COLOR_MOD_COLOR_GAMMA_H_ +#define COLOR_MOD_COLOR_GAMMA_H_ + +struct dc_transfer_func; +struct dc_gamma; +struct dc_transfer_func_distributed_points; +struct dc_rgb_fixed; +enum dc_transfer_func_predefined; + +/* For SetRegamma ADL interface support + * Must match escape type + */ +union regamma_flags { + unsigned int raw; + struct { + unsigned int gammaRampArray :1; // RegammaRamp is in use + unsigned int gammaFromEdid :1; //gamma from edid is in use + unsigned int gammaFromEdidEx :1; //gamma from edid is in use , but only for Display Id 1.2 + unsigned int gammaFromUser :1; //user custom gamma is used + unsigned int coeffFromUser :1; //coeff. A0-A3 from user is in use + unsigned int coeffFromEdid :1; //coeff. A0-A3 from edid is in use + unsigned int applyDegamma :1; //flag for additional degamma correction in driver + unsigned int gammaPredefinedSRGB :1; //flag for SRGB gamma + unsigned int gammaPredefinedPQ :1; //flag for PQ gamma + unsigned int gammaPredefinedPQ2084Interim :1; //flag for PQ gamma, lower max nits + unsigned int gammaPredefined36 :1; //flag for 3.6 gamma + unsigned int gammaPredefinedReset :1; //flag to return to previous gamma + } bits; +}; + +struct regamma_ramp { + unsigned short gamma[256*3]; // gamma ramp packed in same way as OS windows ,r , g & b +}; + +struct regamma_coeff { + int gamma[3]; + int A0[3]; + int A1[3]; + int A2[3]; + int A3[3]; +}; + +struct regamma_lut { + union regamma_flags flags; + union { + struct regamma_ramp ramp; + struct regamma_coeff coeff; + }; +}; + +void setup_x_points_distribution(void); +void precompute_pq(void); +void precompute_de_pq(void); + +bool mod_color_calculate_regamma_params(struct dc_transfer_func *output_tf, + const struct dc_gamma *ramp, bool mapUserRamp); + +bool mod_color_calculate_degamma_params(struct dc_transfer_func *output_tf, + const struct dc_gamma *ramp, bool mapUserRamp); + +bool mod_color_calculate_curve(enum dc_transfer_func_predefined trans, + struct dc_transfer_func_distributed_points *points); + +bool mod_color_calculate_degamma_curve(enum dc_transfer_func_predefined trans, + struct dc_transfer_func_distributed_points *points); + +bool calculate_user_regamma_coeff(struct dc_transfer_func *output_tf, + const struct regamma_lut *regamma); + +bool calculate_user_regamma_ramp(struct dc_transfer_func *output_tf, + const struct regamma_lut *regamma); + + +#endif /* COLOR_MOD_COLOR_GAMMA_H_ */ diff --git a/drivers/gpu/drm/amd/display/modules/color/luts_1d.h b/drivers/gpu/drm/amd/display/modules/color/luts_1d.h new file mode 100644 index 000000000..66b1fad57 --- /dev/null +++ b/drivers/gpu/drm/amd/display/modules/color/luts_1d.h @@ -0,0 +1,51 @@ +/* + * 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 + * + */ +#ifndef LUTS_1D_H +#define LUTS_1D_H + +#include "hw_shared.h" + +struct point_config { + uint32_t custom_float_x; + uint32_t custom_float_y; + uint32_t custom_float_slope; +}; + +struct lut_point { + uint32_t red; + uint32_t green; + uint32_t blue; + uint32_t delta_red; + uint32_t delta_green; + uint32_t delta_blue; +}; + +struct pwl_1dlut_parameter { + struct gamma_curve arr_curve_points[34]; + struct point_config arr_points[2]; + struct lut_point rgb_resulted[256]; + uint32_t hw_points_num; +}; +#endif // LUTS_1D_H diff --git a/drivers/gpu/drm/amd/display/modules/freesync/Makefile b/drivers/gpu/drm/amd/display/modules/freesync/Makefile new file mode 100644 index 000000000..fb9a49978 --- /dev/null +++ b/drivers/gpu/drm/amd/display/modules/freesync/Makefile @@ -0,0 +1,31 @@ +# +# Copyright 2017 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. +# +# +# Makefile for the 'freesync' sub-module of DAL. +# + +FREESYNC = freesync.o + +AMD_DAL_FREESYNC = $(addprefix $(AMDDALPATH)/modules/freesync/,$(FREESYNC)) +#$(info ************ DAL-FREE SYNC_MAKEFILE ************) + +AMD_DISPLAY_FILES += $(AMD_DAL_FREESYNC) diff --git a/drivers/gpu/drm/amd/display/modules/freesync/freesync.c b/drivers/gpu/drm/amd/display/modules/freesync/freesync.c new file mode 100644 index 000000000..fa344ceaf --- /dev/null +++ b/drivers/gpu/drm/amd/display/modules/freesync/freesync.c @@ -0,0 +1,1555 @@ +/* + * 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" + +#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 10 +/* Threshold to exit BTR (to avoid frequent enter-exits at the lower limit) */ +#define BTR_EXIT_MARGIN 2000 +/* Number of consecutive frames to check before entering/exiting fixed refresh*/ +#define FIXED_REFRESH_ENTER_FRAME_COUNT 5 +#define FIXED_REFRESH_EXIT_FRAME_COUNT 5 + +#define FREESYNC_REGISTRY_NAME "freesync_v1" + +#define FREESYNC_NO_STATIC_FOR_EXTERNAL_DP_REGKEY "DalFreeSyncNoStaticForExternalDp" + +#define FREESYNC_NO_STATIC_FOR_INTERNAL_REGKEY "DalFreeSyncNoStaticForInternal" + +#define FREESYNC_DEFAULT_REGKEY "LCDFreeSyncDefault" + +struct gradual_static_ramp { + bool ramp_is_active; + bool ramp_direction_is_up; + unsigned int ramp_current_frame_duration_in_ns; +}; + +struct freesync_time { + /* 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]; + + unsigned int min_window; + unsigned int max_window; +}; + +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_active; + bool program_fixed; + unsigned int frame_counter; +}; + +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 vmin; + unsigned int vmax; + + struct freesync_time time; + + unsigned int nominal_refresh_rate_in_micro_hz; + bool windowed_fullscreen; + + struct gradual_static_ramp static_ramp; + struct below_the_range btr; + struct fixed_refresh fixed_refresh; + struct freesync_range freesync_range; +}; + +struct freesync_entity { + struct dc_stream_state *stream; + struct mod_freesync_caps *caps; + struct freesync_state state; + struct mod_freesync_user_enable user_enable; +}; + +struct freesync_registry_options { + bool drr_external_supported; + bool drr_internal_supported; + bool lcd_freesync_default_set; + int lcd_freesync_default_value; +}; + +struct core_freesync { + struct mod_freesync public; + struct dc *dc; + struct freesync_registry_options opts; + struct freesync_entity *map; + int num_entities; +}; + +#define MOD_FREESYNC_TO_CORE(mod_freesync)\ + container_of(mod_freesync, struct core_freesync, public) + +struct mod_freesync *mod_freesync_create(struct dc *dc) +{ + struct core_freesync *core_freesync = + kzalloc(sizeof(struct core_freesync), GFP_KERNEL); + + + struct persistent_data_flag flag; + + int i, data = 0; + + if (core_freesync == NULL) + goto fail_alloc_context; + + core_freesync->map = kcalloc(MOD_FREESYNC_MAX_CONCURRENT_STREAMS, + sizeof(struct freesync_entity), + GFP_KERNEL); + + 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; + + /* Create initial module folder in registry for freesync enable data */ + flag.save_per_edid = true; + flag.save_per_link = false; + dm_write_persistent_data(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(dc->ctx, NULL, NULL, + FREESYNC_NO_STATIC_FOR_INTERNAL_REGKEY, + &data, sizeof(data), &flag)) { + core_freesync->opts.drr_internal_supported = + (data & 1) ? false : true; + } + + if (dm_read_persistent_data(dc->ctx, NULL, NULL, + FREESYNC_NO_STATIC_FOR_EXTERNAL_DP_REGKEY, + &data, sizeof(data), &flag)) { + core_freesync->opts.drr_external_supported = + (data & 1) ? false : true; + } + + if (dm_read_persistent_data(dc->ctx, NULL, NULL, + FREESYNC_DEFAULT_REGKEY, + &data, sizeof(data), &flag)) { + core_freesync->opts.lcd_freesync_default_set = true; + core_freesync->opts.lcd_freesync_default_value = data; + } else { + core_freesync->opts.lcd_freesync_default_set = false; + core_freesync->opts.lcd_freesync_default_value = 0; + } + + return &core_freesync->public; + +fail_construct: + kfree(core_freesync->map); + +fail_alloc_map: + kfree(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); + + kfree(core_freesync->map); + + kfree(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, + struct dc_stream_state *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, + struct dc_stream_state *stream, struct mod_freesync_caps *caps) +{ + struct dc *dc = NULL; + struct core_freesync *core_freesync = NULL; + int persistent_freesync_enable = 0; + struct persistent_data_flag flag; + unsigned int nom_refresh_rate_uhz; + unsigned long long temp; + + if (mod_freesync == NULL) + return false; + + core_freesync = MOD_FREESYNC_TO_CORE(mod_freesync); + dc = 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_uhz = (unsigned int) temp; + + 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(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; + /* If FreeSync display and LCDFreeSyncDefault is set, use as default values write back to userenable */ + } else if (caps->supported && (core_freesync->opts.lcd_freesync_default_set)) { + core_freesync->map[core_freesync->num_entities].user_enable.enable_for_gaming = + (core_freesync->opts.lcd_freesync_default_value & 1) ? true : false; + core_freesync->map[core_freesync->num_entities].user_enable.enable_for_static = + (core_freesync->opts.lcd_freesync_default_value & 2) ? true : false; + core_freesync->map[core_freesync->num_entities].user_enable.enable_for_video = + (core_freesync->opts.lcd_freesync_default_value & 4) ? true : false; + dm_write_persistent_data(dc->ctx, stream->sink, + FREESYNC_REGISTRY_NAME, + "userenable", &core_freesync->opts.lcd_freesync_default_value, + sizeof(int), &flag); + } 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_uhz >= caps->min_refresh_in_micro_hz && + nom_refresh_rate_uhz <= caps->max_refresh_in_micro_hz) + stream->ignore_msa_timing_param = 1; + + core_freesync->num_entities++; + return true; + } + return false; +} + +bool mod_freesync_remove_stream(struct mod_freesync *mod_freesync, + struct dc_stream_state *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 adjust_vmin_vmax(struct core_freesync *core_freesync, + struct dc_stream_state **streams, + int num_streams, + int map_index, + unsigned int v_total_min, + unsigned int v_total_max) +{ + if (num_streams == 0 || streams == NULL || num_streams > 1) + return; + + core_freesync->map[map_index].state.vmin = v_total_min; + core_freesync->map[map_index].state.vmax = v_total_max; + + dc_stream_adjust_vmin_vmax(core_freesync->dc, streams, + num_streams, v_total_min, + v_total_max); +} + + +static void update_stream_freesync_context(struct core_freesync *core_freesync, + struct dc_stream_state *stream) +{ + unsigned int index; + struct freesync_context *ctx; + + ctx = &stream->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, + struct dc_stream_state *stream) +{ + unsigned int index = map_index_from_stream(core_freesync, stream); + if (core_freesync->map[index].caps->supported) { + stream->ignore_msa_timing_param = 1; + update_stream_freesync_context(core_freesync, stream); + } +} + +static void calc_freesync_range(struct core_freesync *core_freesync, + struct dc_stream_state *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); + + /* vmin/vmax cannot be less than vtotal */ + if (state->freesync_range.vmin < vtotal) { + /* Error of 1 is permissible */ + ASSERT((state->freesync_range.vmin + 1) >= vtotal); + state->freesync_range.vmin = vtotal; + } + + if (state->freesync_range.vmax < vtotal) { + /* Error of 1 is permissible */ + ASSERT((state->freesync_range.vmax + 1) >= vtotal); + 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(struct dc_stream_state *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, + struct dc_stream_state *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_active = false; + state->fixed_refresh.program_fixed = false; +} +/* + * Sets freesync mode on a stream depending on current freesync state. + */ +static bool set_freesync_on_streams(struct core_freesync *core_freesync, + struct dc_stream_state **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_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]); + + adjust_vmin_vmax(core_freesync, streams, + num_streams, map_index, + 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]); + + adjust_vmin_vmax( + core_freesync, streams, + num_streams, map_index, + 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]); + + adjust_vmin_vmax(core_freesync, streams, + num_streams, map_index, + 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 + */ + adjust_vmin_vmax(core_freesync, streams, + num_streams, map_index, + 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; + unsigned int nominal_refresh_rate = core_freesync->map[index].state. + nominal_refresh_rate_in_micro_hz; + unsigned int min_refresh_rate= core_freesync->map[index].caps-> + min_refresh_in_micro_hz; + struct gradual_static_ramp *static_ramp_variables = + &core_freesync->map[index].state.static_ramp; + + /* If we are ENABLING static screen, refresh rate should go DOWN. + * If we are DISABLING static screen, refresh rate should go UP. + */ + if (enable_static_screen) + static_ramp_variables->ramp_direction_is_up = false; + else + static_ramp_variables->ramp_direction_is_up = true; + + /* 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) { + 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), + nominal_refresh_rate))); + } else { + /* Going to higher refresh rate, so start from min + * refresh rate (max frame duration) + */ + frame_duration = ((unsigned int) (div64_u64( + (1000000000ULL * 1000000), + min_refresh_rate))); + } + static_ramp_variables-> + ramp_current_frame_duration_in_ns = frame_duration; + + static_ramp_variables->ramp_is_active = true; + } +} + +void mod_freesync_handle_v_update(struct mod_freesync *mod_freesync, + struct dc_stream_state **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 */ + adjust_vmin_vmax(core_freesync, streams, + num_streams, index, + 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 */ + adjust_vmin_vmax(core_freesync, streams, + num_streams, index, + v_total, + v_total); + + triggers.overlay_update = true; + triggers.surface_update = true; + + dc_stream_set_static_screen_events(core_freesync->dc, streams, + num_streams, &triggers); + } +} + +void mod_freesync_update_state(struct mod_freesync *mod_freesync, + struct dc_stream_state **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; + 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; + + for(stream_index = 0; stream_index < num_streams; stream_index++) { + + unsigned int map_index = map_index_from_stream(core_freesync, + streams[stream_index]); + + bool is_embedded = dc_is_embedded_signal( + streams[stream_index]->sink->sink_signal); + + struct freesync_registry_options *opts = &core_freesync->opts; + + 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 disabled by default, but can + * be enabled through regkey. + */ + if ((is_embedded && opts->drr_internal_supported) || + (!is_embedded && opts->drr_external_supported)) + + if (state->static_screen != + freesync_params->enable) { + + /* Change the state flag */ + state->static_screen = + freesync_params->enable; + + /* Update static screen ramp */ + 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; + } + } + + /* Update mask */ + triggers.overlay_update = true; + triggers.surface_update = true; + + dc_stream_set_static_screen_events(core_freesync->dc, streams, + num_streams, &triggers); + + 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, + struct dc_stream_state *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, + struct dc_stream_state **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 dc *dc = NULL; + struct core_freesync *core_freesync = NULL; + + if (mod_freesync == NULL) + return false; + + core_freesync = MOD_FREESYNC_TO_CORE(mod_freesync); + dc = 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(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, + struct dc_stream_state *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_get_static_ramp_active(struct mod_freesync *mod_freesync, + struct dc_stream_state *stream, + bool *is_ramp_active) +{ + 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); + + *is_ramp_active = + core_freesync->map[index].state.static_ramp.ramp_is_active; + + return true; +} + +bool mod_freesync_override_min_max(struct mod_freesync *mod_freesync, + struct dc_stream_state *streams, + unsigned int min_refresh, + unsigned int max_refresh, + struct mod_freesync_caps *caps) +{ + 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 (max_refresh == 0) + max_refresh = state->nominal_refresh_rate_in_micro_hz; + + if (min_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 */ + adjust_vmin_vmax(core_freesync, &streams, 1, index, + state->freesync_range.vmin, + state->freesync_range.vmax); + } + + if (min_refresh != 0 && + dc_is_embedded_signal(streams->sink->sink_signal) && + (max_refresh - min_refresh >= 10000000)) { + caps->supported = true; + caps->min_refresh_in_micro_hz = min_refresh; + caps->max_refresh_in_micro_hz = max_refresh; + } + + /* Update the stream */ + update_stream(core_freesync, streams); + + return true; +} + +bool mod_freesync_get_min_max(struct mod_freesync *mod_freesync, + struct dc_stream_state *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, + struct dc_stream_state *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, + struct dc_stream_state *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 (dc_stream_get_crtc_position(core_freesync->dc, &stream, 1, + &position.vertical_count, + &position.nominal_vcount)) { + + *nom_v_pos = position.nominal_vcount; + *v_pos = position.vertical_count; + + return true; + } + + return false; +} + +void mod_freesync_notify_mode_change(struct mod_freesync *mod_freesync, + struct dc_stream_state **streams, int num_streams) +{ + unsigned int stream_index, map_index; + struct freesync_state *state; + struct core_freesync *core_freesync = NULL; + struct dc_static_screen_events triggers = {0}; + unsigned long long temp = 0; + + 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; + + /* Update the field rate for new timing */ + 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; + + if (core_freesync->map[map_index].caps->supported) { + + /* 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); + + /* Update mask */ + triggers.overlay_update = true; + triggers.surface_update = true; + + dc_stream_set_static_screen_events(core_freesync->dc, + streams, num_streams, + &triggers); + } + } + + /* 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_state *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_active) { + + state->fixed_refresh.frame_counter++; + + if (state->fixed_refresh.frame_counter > + FIXED_REFRESH_EXIT_FRAME_COUNT) { + state->fixed_refresh.frame_counter = 0; + state->fixed_refresh.program_fixed = true; + state->fixed_refresh.fixed_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_active && + !core_freesync->map[map_index].caps->btr_supported) { + + state->fixed_refresh.frame_counter++; + + if (state->fixed_refresh.frame_counter > + FIXED_REFRESH_ENTER_FRAME_COUNT) { + state->fixed_refresh.frame_counter = 0; + state->fixed_refresh.program_fixed = true; + state->fixed_refresh.fixed_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, + struct dc_stream_state *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, + struct dc_stream_state *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) + return; + + state->fixed_refresh.program_fixed = false; + + /* Program Fixed Refresh */ + + /* Fixed Refresh set to "not active" so disengage */ + if (!state->fixed_refresh.fixed_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; + adjust_vmin_vmax(core_freesync, &stream, map_index, + 1, vmin, vmax); + } +} + +void mod_freesync_pre_update_plane_addresses(struct mod_freesync *mod_freesync, + struct dc_stream_state **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; + } + + } +} + +void mod_freesync_get_settings(struct mod_freesync *mod_freesync, + struct dc_stream_state **streams, int num_streams, + unsigned int *v_total_min, unsigned int *v_total_max, + unsigned int *event_triggers, + unsigned int *window_min, unsigned int *window_max, + unsigned int *lfc_mid_point_in_us, + unsigned int *inserted_frames, + unsigned int *inserted_duration_in_us) +{ + unsigned int stream_index, map_index; + 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) { + struct freesync_state state = + core_freesync->map[map_index].state; + *v_total_min = state.vmin; + *v_total_max = state.vmax; + *event_triggers = 0; + *window_min = state.time.min_window; + *window_max = state.time.max_window; + *lfc_mid_point_in_us = state.btr.mid_point_in_us; + *inserted_frames = state.btr.frames_to_insert; + *inserted_duration_in_us = + state.btr.inserted_frame_duration_in_us; + } + + } +} + diff --git a/drivers/gpu/drm/amd/display/modules/inc/mod_freesync.h b/drivers/gpu/drm/amd/display/modules/inc/mod_freesync.h new file mode 100644 index 000000000..f083e1619 --- /dev/null +++ b/drivers/gpu/drm/amd/display/modules/inc/mod_freesync.h @@ -0,0 +1,176 @@ +/* + * 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 + * + */ + + + + +/* + * 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 + * + */ + +#ifndef MOD_FREESYNC_H_ +#define MOD_FREESYNC_H_ + +#include "dm_services.h" + +struct mod_freesync *mod_freesync_create(struct dc *dc); +void mod_freesync_destroy(struct mod_freesync *mod_freesync); + +struct mod_freesync { + int dummy; +}; + +enum mod_freesync_state { + FREESYNC_STATE_NONE, + FREESYNC_STATE_FULLSCREEN, + FREESYNC_STATE_STATIC_SCREEN, + FREESYNC_STATE_VIDEO +}; + +enum mod_freesync_user_enable_mask { + FREESYNC_USER_ENABLE_STATIC = 0x1, + FREESYNC_USER_ENABLE_VIDEO = 0x2, + FREESYNC_USER_ENABLE_GAMING = 0x4 +}; + +struct mod_freesync_user_enable { + bool enable_for_static; + bool enable_for_video; + bool enable_for_gaming; +}; + +struct mod_freesync_caps { + bool supported; + unsigned int min_refresh_in_micro_hz; + unsigned int max_refresh_in_micro_hz; + + bool btr_supported; +}; + +struct mod_freesync_params { + enum mod_freesync_state state; + bool enable; + unsigned int update_duration_in_ns; + bool windowed_fullscreen; +}; + +/* + * Add stream to be tracked by module + */ +bool mod_freesync_add_stream(struct mod_freesync *mod_freesync, + struct dc_stream_state *stream, struct mod_freesync_caps *caps); + +/* + * Remove stream to be tracked by module + */ +bool mod_freesync_remove_stream(struct mod_freesync *mod_freesync, + struct dc_stream_state *stream); + +/* + * Update the freesync state flags for each display and program + * freesync accordingly + */ +void mod_freesync_update_state(struct mod_freesync *mod_freesync, + struct dc_stream_state **streams, int num_streams, + struct mod_freesync_params *freesync_params); + +bool mod_freesync_get_state(struct mod_freesync *mod_freesync, + struct dc_stream_state *stream, + struct mod_freesync_params *freesync_params); + +bool mod_freesync_set_user_enable(struct mod_freesync *mod_freesync, + struct dc_stream_state **streams, int num_streams, + struct mod_freesync_user_enable *user_enable); + +bool mod_freesync_get_user_enable(struct mod_freesync *mod_freesync, + struct dc_stream_state *stream, + struct mod_freesync_user_enable *user_enable); + +bool mod_freesync_get_static_ramp_active(struct mod_freesync *mod_freesync, + struct dc_stream_state *stream, + bool *is_ramp_active); + +bool mod_freesync_override_min_max(struct mod_freesync *mod_freesync, + struct dc_stream_state *streams, + unsigned int min_refresh, + unsigned int max_refresh, + struct mod_freesync_caps *caps); + +bool mod_freesync_get_min_max(struct mod_freesync *mod_freesync, + struct dc_stream_state *stream, + unsigned int *min_refresh, + unsigned int *max_refresh); + +bool mod_freesync_get_vmin_vmax(struct mod_freesync *mod_freesync, + struct dc_stream_state *stream, + unsigned int *vmin, + unsigned int *vmax); + +bool mod_freesync_get_v_position(struct mod_freesync *mod_freesync, + struct dc_stream_state *stream, + unsigned int *nom_v_pos, + unsigned int *v_pos); + +void mod_freesync_handle_v_update(struct mod_freesync *mod_freesync, + struct dc_stream_state **streams, int num_streams); + +void mod_freesync_notify_mode_change(struct mod_freesync *mod_freesync, + struct dc_stream_state **streams, int num_streams); + +void mod_freesync_pre_update_plane_addresses(struct mod_freesync *mod_freesync, + struct dc_stream_state **streams, int num_streams, + unsigned int curr_time_stamp); + +void mod_freesync_get_settings(struct mod_freesync *mod_freesync, + struct dc_stream_state **streams, int num_streams, + unsigned int *v_total_min, unsigned int *v_total_max, + unsigned int *event_triggers, + unsigned int *window_min, unsigned int *window_max, + unsigned int *lfc_mid_point_in_us, + unsigned int *inserted_frames, + unsigned int *inserted_duration_in_us); + +#endif diff --git a/drivers/gpu/drm/amd/display/modules/inc/mod_stats.h b/drivers/gpu/drm/amd/display/modules/inc/mod_stats.h new file mode 100644 index 000000000..3812094b5 --- /dev/null +++ b/drivers/gpu/drm/amd/display/modules/inc/mod_stats.h @@ -0,0 +1,69 @@ +/* + * 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 + * + */ + +#ifndef MODULES_INC_MOD_STATS_H_ +#define MODULES_INC_MOD_STATS_H_ + +#include "dm_services.h" + +struct mod_stats { + int dummy; +}; + +struct mod_stats_caps { + bool dummy; +}; + +struct mod_stats *mod_stats_create(struct dc *dc); + +void mod_stats_destroy(struct mod_stats *mod_stats); + +bool mod_stats_init(struct mod_stats *mod_stats); + +void mod_stats_dump(struct mod_stats *mod_stats); + +void mod_stats_reset_data(struct mod_stats *mod_stats); + +void mod_stats_update_event(struct mod_stats *mod_stats, + char *event_string, + unsigned int length); + +void mod_stats_update_flip(struct mod_stats *mod_stats, + unsigned long timestamp_in_ns); + +void mod_stats_update_vupdate(struct mod_stats *mod_stats, + unsigned long timestamp_in_ns); + +void mod_stats_update_freesync(struct mod_stats *mod_stats, + unsigned int v_total_min, + unsigned int v_total_max, + unsigned int event_triggers, + unsigned int window_min, + unsigned int window_max, + unsigned int lfc_mid_point_in_us, + unsigned int inserted_frames, + unsigned int inserted_frame_duration_in_us); + +#endif /* MODULES_INC_MOD_STATS_H_ */ diff --git a/drivers/gpu/drm/amd/display/modules/stats/stats.c b/drivers/gpu/drm/amd/display/modules/stats/stats.c new file mode 100644 index 000000000..3d4c1b1ab --- /dev/null +++ b/drivers/gpu/drm/amd/display/modules/stats/stats.c @@ -0,0 +1,452 @@ +/* + * 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 "mod_stats.h" +#include "dm_services.h" +#include "dc.h" +#include "core_types.h" + +#define DAL_STATS_ENABLE_REGKEY "DalStatsEnable" +#define DAL_STATS_ENABLE_REGKEY_DEFAULT 0x00000000 +#define DAL_STATS_ENABLE_REGKEY_ENABLED 0x00000001 + +#define DAL_STATS_ENTRIES_REGKEY "DalStatsEntries" +#define DAL_STATS_ENTRIES_REGKEY_DEFAULT 0x00350000 +#define DAL_STATS_ENTRIES_REGKEY_MAX 0x01000000 + +#define DAL_STATS_EVENT_ENTRIES_DEFAULT 0x00000100 + +#define MOD_STATS_NUM_VSYNCS 5 +#define MOD_STATS_EVENT_STRING_MAX 512 + +struct stats_time_cache { + unsigned int entry_id; + + unsigned long flip_timestamp_in_ns; + unsigned long vupdate_timestamp_in_ns; + + unsigned int render_time_in_us; + unsigned int avg_render_time_in_us_last_ten; + unsigned int v_sync_time_in_us[MOD_STATS_NUM_VSYNCS]; + unsigned int num_vsync_between_flips; + + unsigned int flip_to_vsync_time_in_us; + unsigned int vsync_to_flip_time_in_us; + + unsigned int min_window; + unsigned int max_window; + unsigned int v_total_min; + unsigned int v_total_max; + unsigned int event_triggers; + + unsigned int lfc_mid_point_in_us; + unsigned int num_frames_inserted; + unsigned int inserted_duration_in_us; + + unsigned int flags; +}; + +struct stats_event_cache { + unsigned int entry_id; + char event_string[MOD_STATS_EVENT_STRING_MAX]; +}; + +struct core_stats { + struct mod_stats public; + struct dc *dc; + + bool enabled; + unsigned int entries; + unsigned int event_entries; + unsigned int entry_id; + + struct stats_time_cache *time; + unsigned int index; + + struct stats_event_cache *events; + unsigned int event_index; + +}; + +#define MOD_STATS_TO_CORE(mod_stats)\ + container_of(mod_stats, struct core_stats, public) + +bool mod_stats_init(struct mod_stats *mod_stats) +{ + bool result = false; + struct core_stats *core_stats = NULL; + struct dc *dc = NULL; + + if (mod_stats == NULL) + return false; + + core_stats = MOD_STATS_TO_CORE(mod_stats); + dc = core_stats->dc; + + return result; +} + +struct mod_stats *mod_stats_create(struct dc *dc) +{ + struct core_stats *core_stats = NULL; + struct persistent_data_flag flag; + unsigned int reg_data; + int i = 0; + + if (dc == NULL) + goto fail_construct; + + core_stats = kzalloc(sizeof(struct core_stats), GFP_KERNEL); + + if (core_stats == NULL) + goto fail_construct; + + core_stats->dc = dc; + + core_stats->enabled = DAL_STATS_ENABLE_REGKEY_DEFAULT; + if (dm_read_persistent_data(dc->ctx, NULL, NULL, + DAL_STATS_ENABLE_REGKEY, + ®_data, sizeof(unsigned int), &flag)) + core_stats->enabled = reg_data; + + if (core_stats->enabled) { + core_stats->entries = DAL_STATS_ENTRIES_REGKEY_DEFAULT; + if (dm_read_persistent_data(dc->ctx, NULL, NULL, + DAL_STATS_ENTRIES_REGKEY, + ®_data, sizeof(unsigned int), &flag)) { + if (reg_data > DAL_STATS_ENTRIES_REGKEY_MAX) + core_stats->entries = DAL_STATS_ENTRIES_REGKEY_MAX; + else + core_stats->entries = reg_data; + } + core_stats->time = kcalloc(core_stats->entries, + sizeof(struct stats_time_cache), + GFP_KERNEL); + + if (core_stats->time == NULL) + goto fail_construct_time; + + core_stats->event_entries = DAL_STATS_EVENT_ENTRIES_DEFAULT; + core_stats->events = kcalloc(core_stats->event_entries, + sizeof(struct stats_event_cache), + GFP_KERNEL); + + if (core_stats->events == NULL) + goto fail_construct_events; + + } else { + core_stats->entries = 0; + } + + /* Purposely leave index 0 unused so we don't need special logic to + * handle calculation cases that depend on previous flip data. + */ + core_stats->index = 1; + core_stats->event_index = 0; + + // Keeps track of ordering within the different stats structures + core_stats->entry_id = 0; + + return &core_stats->public; + +fail_construct_events: + kfree(core_stats->time); + +fail_construct_time: + kfree(core_stats); + +fail_construct: + return NULL; +} + +void mod_stats_destroy(struct mod_stats *mod_stats) +{ + if (mod_stats != NULL) { + struct core_stats *core_stats = MOD_STATS_TO_CORE(mod_stats); + + if (core_stats->time != NULL) + kfree(core_stats->time); + + if (core_stats->events != NULL) + kfree(core_stats->events); + + kfree(core_stats); + } +} + +void mod_stats_dump(struct mod_stats *mod_stats) +{ + struct dc *dc = NULL; + struct dal_logger *logger = NULL; + struct core_stats *core_stats = NULL; + struct stats_time_cache *time = NULL; + struct stats_event_cache *events = NULL; + unsigned int time_index = 1; + unsigned int event_index = 0; + unsigned int index = 0; + struct log_entry log_entry; + + if (mod_stats == NULL) + return; + + core_stats = MOD_STATS_TO_CORE(mod_stats); + dc = core_stats->dc; + logger = dc->ctx->logger; + time = core_stats->time; + events = core_stats->events; + + DISPLAY_STATS_BEGIN(log_entry); + + DISPLAY_STATS("==Display Caps==\n"); + + DISPLAY_STATS("==Display Stats==\n"); + + DISPLAY_STATS("%10s %10s %10s %10s %10s" + " %11s %11s %17s %10s %14s" + " %10s %10s %10s %10s %10s" + " %10s %10s %10s %10s\n", + "render", "avgRender", + "minWindow", "midPoint", "maxWindow", + "vsyncToFlip", "flipToVsync", "vsyncsBetweenFlip", + "numFrame", "insertDuration", + "vTotalMin", "vTotalMax", "eventTrigs", + "vSyncTime1", "vSyncTime2", "vSyncTime3", + "vSyncTime4", "vSyncTime5", "flags"); + + for (int i = 0; i < core_stats->entry_id; i++) { + if (event_index < core_stats->event_index && + i == events[event_index].entry_id) { + DISPLAY_STATS("==Event==%s\n", events[event_index].event_string); + event_index++; + } else if (time_index < core_stats->index && + i == time[time_index].entry_id) { + DISPLAY_STATS("%10u %10u %10u %10u %10u" + " %11u %11u %17u %10u %14u" + " %10u %10u %10u %10u %10u" + " %10u %10u %10u %10u\n", + time[time_index].render_time_in_us, + time[time_index].avg_render_time_in_us_last_ten, + time[time_index].min_window, + time[time_index].lfc_mid_point_in_us, + time[time_index].max_window, + time[time_index].vsync_to_flip_time_in_us, + time[time_index].flip_to_vsync_time_in_us, + time[time_index].num_vsync_between_flips, + time[time_index].num_frames_inserted, + time[time_index].inserted_duration_in_us, + time[time_index].v_total_min, + time[time_index].v_total_max, + time[time_index].event_triggers, + time[time_index].v_sync_time_in_us[0], + time[time_index].v_sync_time_in_us[1], + time[time_index].v_sync_time_in_us[2], + time[time_index].v_sync_time_in_us[3], + time[time_index].v_sync_time_in_us[4], + time[time_index].flags); + + time_index++; + } + } + + DISPLAY_STATS_END(log_entry); +} + +void mod_stats_reset_data(struct mod_stats *mod_stats) +{ + struct core_stats *core_stats = NULL; + struct stats_time_cache *time = NULL; + unsigned int index = 0; + + if (mod_stats == NULL) + return; + + core_stats = MOD_STATS_TO_CORE(mod_stats); + + memset(core_stats->time, 0, + sizeof(struct stats_time_cache) * core_stats->entries); + + memset(core_stats->events, 0, + sizeof(struct stats_event_cache) * core_stats->event_entries); + + core_stats->index = 1; + core_stats->event_index = 0; + + // Keeps track of ordering within the different stats structures + core_stats->entry_id = 0; +} + +void mod_stats_update_event(struct mod_stats *mod_stats, + char *event_string, + unsigned int length) +{ + struct core_stats *core_stats = NULL; + struct stats_event_cache *events = NULL; + unsigned int index = 0; + unsigned int copy_length = 0; + + if (mod_stats == NULL) + return; + + core_stats = MOD_STATS_TO_CORE(mod_stats); + + if (core_stats->event_index >= core_stats->event_entries) + return; + + events = core_stats->events; + index = core_stats->event_index; + + copy_length = length; + if (length > MOD_STATS_EVENT_STRING_MAX) + copy_length = MOD_STATS_EVENT_STRING_MAX; + + memcpy(&events[index].event_string, event_string, copy_length); + events[index].event_string[copy_length - 1] = '\0'; + + events[index].entry_id = core_stats->entry_id; + core_stats->event_index++; + core_stats->entry_id++; +} + +void mod_stats_update_flip(struct mod_stats *mod_stats, + unsigned long timestamp_in_ns) +{ + struct core_stats *core_stats = NULL; + struct stats_time_cache *time = NULL; + unsigned int index = 0; + + if (mod_stats == NULL) + return; + + core_stats = MOD_STATS_TO_CORE(mod_stats); + + if (core_stats->index >= core_stats->entries) + return; + + time = core_stats->time; + index = core_stats->index; + + time[index].flip_timestamp_in_ns = timestamp_in_ns; + time[index].render_time_in_us = + (timestamp_in_ns - time[index - 1].flip_timestamp_in_ns) / 1000; + + if (index >= 10) { + for (unsigned int i = 0; i < 10; i++) + time[index].avg_render_time_in_us_last_ten += + time[index - i].render_time_in_us; + time[index].avg_render_time_in_us_last_ten /= 10; + } + + if (time[index].num_vsync_between_flips > 0) + time[index].vsync_to_flip_time_in_us = + (timestamp_in_ns - + time[index].vupdate_timestamp_in_ns) / 1000; + else + time[index].vsync_to_flip_time_in_us = + (timestamp_in_ns - + time[index - 1].vupdate_timestamp_in_ns) / 1000; + + time[index].entry_id = core_stats->entry_id; + core_stats->index++; + core_stats->entry_id++; +} + +void mod_stats_update_vupdate(struct mod_stats *mod_stats, + unsigned long timestamp_in_ns) +{ + struct core_stats *core_stats = NULL; + struct stats_time_cache *time = NULL; + unsigned int index = 0; + unsigned int num_vsyncs = 0; + unsigned int prev_vsync_in_ns = 0; + + if (mod_stats == NULL) + return; + + core_stats = MOD_STATS_TO_CORE(mod_stats); + + if (core_stats->index >= core_stats->entries) + return; + + time = core_stats->time; + index = core_stats->index; + num_vsyncs = time[index].num_vsync_between_flips; + + if (num_vsyncs < MOD_STATS_NUM_VSYNCS) { + if (num_vsyncs == 0) { + prev_vsync_in_ns = + time[index - 1].vupdate_timestamp_in_ns; + + time[index].flip_to_vsync_time_in_us = + (timestamp_in_ns - + time[index - 1].flip_timestamp_in_ns) / + 1000; + } else { + prev_vsync_in_ns = + time[index].vupdate_timestamp_in_ns; + } + + time[index].v_sync_time_in_us[num_vsyncs] = + (timestamp_in_ns - prev_vsync_in_ns) / 1000; + } + + time[index].vupdate_timestamp_in_ns = timestamp_in_ns; + time[index].num_vsync_between_flips++; +} + +void mod_stats_update_freesync(struct mod_stats *mod_stats, + unsigned int v_total_min, + unsigned int v_total_max, + unsigned int event_triggers, + unsigned int window_min, + unsigned int window_max, + unsigned int lfc_mid_point_in_us, + unsigned int inserted_frames, + unsigned int inserted_duration_in_us) +{ + struct core_stats *core_stats = NULL; + struct stats_time_cache *time = NULL; + unsigned int index = 0; + + if (mod_stats == NULL) + return; + + core_stats = MOD_STATS_TO_CORE(mod_stats); + + if (core_stats->index >= core_stats->entries) + return; + + time = core_stats->time; + index = core_stats->index; + + time[index].v_total_min = v_total_min; + time[index].v_total_max = v_total_max; + time[index].event_triggers = event_triggers; + time[index].min_window = window_min; + time[index].max_window = window_max; + time[index].lfc_mid_point_in_us = lfc_mid_point_in_us; + time[index].num_frames_inserted = inserted_frames; + time[index].inserted_duration_in_us = inserted_duration_in_us; +} + |