From 2c3c1048746a4622d8c89a29670120dc8fab93c4 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 20:49:45 +0200 Subject: Adding upstream version 6.1.76. Signed-off-by: Daniel Baumann --- drivers/gpu/drm/amd/display/modules/color/Makefile | 31 + .../drm/amd/display/modules/color/color_gamma.c | 2303 ++++++++++++++++++++ .../drm/amd/display/modules/color/color_gamma.h | 132 ++ .../drm/amd/display/modules/color/color_table.c | 64 + .../drm/amd/display/modules/color/color_table.h | 47 + .../gpu/drm/amd/display/modules/color/luts_1d.h | 51 + .../gpu/drm/amd/display/modules/freesync/Makefile | 31 + .../drm/amd/display/modules/freesync/freesync.c | 1435 ++++++++++++ drivers/gpu/drm/amd/display/modules/hdcp/Makefile | 33 + drivers/gpu/drm/amd/display/modules/hdcp/hdcp.c | 598 +++++ drivers/gpu/drm/amd/display/modules/hdcp/hdcp.h | 584 +++++ .../drm/amd/display/modules/hdcp/hdcp1_execution.c | 535 +++++ .../amd/display/modules/hdcp/hdcp1_transition.c | 322 +++ .../drm/amd/display/modules/hdcp/hdcp2_execution.c | 889 ++++++++ .../amd/display/modules/hdcp/hdcp2_transition.c | 677 ++++++ .../gpu/drm/amd/display/modules/hdcp/hdcp_ddc.c | 664 ++++++ .../gpu/drm/amd/display/modules/hdcp/hdcp_log.c | 361 +++ .../gpu/drm/amd/display/modules/hdcp/hdcp_log.h | 134 ++ .../gpu/drm/amd/display/modules/hdcp/hdcp_psp.c | 1025 +++++++++ .../gpu/drm/amd/display/modules/hdcp/hdcp_psp.h | 532 +++++ .../gpu/drm/amd/display/modules/inc/mod_freesync.h | 200 ++ drivers/gpu/drm/amd/display/modules/inc/mod_hdcp.h | 324 +++ .../drm/amd/display/modules/inc/mod_info_packet.h | 43 + .../gpu/drm/amd/display/modules/inc/mod_shared.h | 108 + .../gpu/drm/amd/display/modules/inc/mod_stats.h | 75 + drivers/gpu/drm/amd/display/modules/inc/mod_vmid.h | 46 + .../drm/amd/display/modules/info_packet/Makefile | 31 + .../amd/display/modules/info_packet/info_packet.c | 517 +++++ drivers/gpu/drm/amd/display/modules/power/Makefile | 31 + .../drm/amd/display/modules/power/power_helpers.c | 948 ++++++++ .../drm/amd/display/modules/power/power_helpers.h | 65 + drivers/gpu/drm/amd/display/modules/vmid/vmid.c | 174 ++ 32 files changed, 13010 insertions(+) create mode 100644 drivers/gpu/drm/amd/display/modules/color/Makefile create mode 100644 drivers/gpu/drm/amd/display/modules/color/color_gamma.c create mode 100644 drivers/gpu/drm/amd/display/modules/color/color_gamma.h create mode 100644 drivers/gpu/drm/amd/display/modules/color/color_table.c create mode 100644 drivers/gpu/drm/amd/display/modules/color/color_table.h create mode 100644 drivers/gpu/drm/amd/display/modules/color/luts_1d.h create mode 100644 drivers/gpu/drm/amd/display/modules/freesync/Makefile create mode 100644 drivers/gpu/drm/amd/display/modules/freesync/freesync.c create mode 100644 drivers/gpu/drm/amd/display/modules/hdcp/Makefile create mode 100644 drivers/gpu/drm/amd/display/modules/hdcp/hdcp.c create mode 100644 drivers/gpu/drm/amd/display/modules/hdcp/hdcp.h create mode 100644 drivers/gpu/drm/amd/display/modules/hdcp/hdcp1_execution.c create mode 100644 drivers/gpu/drm/amd/display/modules/hdcp/hdcp1_transition.c create mode 100644 drivers/gpu/drm/amd/display/modules/hdcp/hdcp2_execution.c create mode 100644 drivers/gpu/drm/amd/display/modules/hdcp/hdcp2_transition.c create mode 100644 drivers/gpu/drm/amd/display/modules/hdcp/hdcp_ddc.c create mode 100644 drivers/gpu/drm/amd/display/modules/hdcp/hdcp_log.c create mode 100644 drivers/gpu/drm/amd/display/modules/hdcp/hdcp_log.h create mode 100644 drivers/gpu/drm/amd/display/modules/hdcp/hdcp_psp.c create mode 100644 drivers/gpu/drm/amd/display/modules/hdcp/hdcp_psp.h create mode 100644 drivers/gpu/drm/amd/display/modules/inc/mod_freesync.h create mode 100644 drivers/gpu/drm/amd/display/modules/inc/mod_hdcp.h create mode 100644 drivers/gpu/drm/amd/display/modules/inc/mod_info_packet.h create mode 100644 drivers/gpu/drm/amd/display/modules/inc/mod_shared.h create mode 100644 drivers/gpu/drm/amd/display/modules/inc/mod_stats.h create mode 100644 drivers/gpu/drm/amd/display/modules/inc/mod_vmid.h create mode 100644 drivers/gpu/drm/amd/display/modules/info_packet/Makefile create mode 100644 drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c create mode 100644 drivers/gpu/drm/amd/display/modules/power/Makefile create mode 100644 drivers/gpu/drm/amd/display/modules/power/power_helpers.c create mode 100644 drivers/gpu/drm/amd/display/modules/power/power_helpers.h create mode 100644 drivers/gpu/drm/amd/display/modules/vmid/vmid.c (limited to 'drivers/gpu/drm/amd/display/modules') 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..e66c19a84 --- /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 color_table.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..447a0ec9c --- /dev/null +++ b/drivers/gpu/drm/amd/display/modules/color/color_gamma.c @@ -0,0 +1,2303 @@ +/* + * 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" + +/* When calculating LUT values the first region and at least one subsequent + * region are calculated with full precision. These defines are a demarcation + * of where the second region starts and ends. + * These are hardcoded values to avoid recalculating them in loops. + */ +#define PRECISE_LUT_REGION_START 224 +#define PRECISE_LUT_REGION_END 239 + +static struct hw_x_point coordinates_x[MAX_HW_POINTS + 2]; + +// these are helpers for calculations to reduce stack usage +// do not depend on these being preserved across calls + +/* Helper to optimize gamma calculation, only use in translate_from_linear, in + * particular the dc_fixpt_pow function which is very expensive + * The idea is that our regions for X points are exponential and currently they all use + * the same number of points (NUM_PTS_IN_REGION) and in each region every point + * is exactly 2x the one at the same index in the previous region. In other words + * X[i] = 2 * X[i-NUM_PTS_IN_REGION] for i>=16 + * The other fact is that (2x)^gamma = 2^gamma * x^gamma + * So we compute and save x^gamma for the first 16 regions, and for every next region + * just multiply with 2^gamma which can be computed once, and save the result so we + * recursively compute all the values. + */ + +/* + * Regamma coefficients are used for both regamma and degamma. Degamma + * coefficients are calculated in our formula using the regamma coefficients. + */ + /*sRGB 709 2.2 2.4 P3*/ +static const int32_t numerator01[] = { 31308, 180000, 0, 0, 0}; +static const int32_t numerator02[] = { 12920, 4500, 0, 0, 0}; +static const int32_t numerator03[] = { 55, 99, 0, 0, 0}; +static const int32_t numerator04[] = { 55, 99, 0, 0, 0}; +static const int32_t numerator05[] = { 2400, 2200, 2200, 2400, 2600}; + +/* 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); + } + } +} + +void log_x_points_distribution(struct dal_logger *logger) +{ + int i = 0; + + if (logger != NULL) { + LOG_GAMMA_WRITE("Log X Distribution\n"); + + for (i = 0; i < MAX_HW_POINTS; i++) + LOG_GAMMA_WRITE("%llu\n", coordinates_x[i].x.value); + } +} + +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; + struct fixed31_32 base2; + + + 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); + + div = dc_fixpt_sub(c2, dc_fixpt_mul(c3, l_pow_m1)); + + base2 = dc_fixpt_div(base, div); + // avoid complex numbers + if (dc_fixpt_lt(base2, dc_fixpt_zero)) + base2 = dc_fixpt_sub(dc_fixpt_zero, base2); + + + *out_y = dc_fixpt_pow(base2, dc_fixpt_div(dc_fixpt_one, m1)); + +} + + +/* de gamma, non-linear to linear */ +static void compute_hlg_eotf(struct fixed31_32 in_x, + struct fixed31_32 *out_y, + uint32_t sdr_white_level, uint32_t max_luminance_nits) +{ + struct fixed31_32 a; + struct fixed31_32 b; + struct fixed31_32 c; + struct fixed31_32 threshold; + struct fixed31_32 x; + + struct fixed31_32 scaling_factor = + dc_fixpt_from_fraction(max_luminance_nits, sdr_white_level); + a = dc_fixpt_from_fraction(17883277, 100000000); + b = dc_fixpt_from_fraction(28466892, 100000000); + c = dc_fixpt_from_fraction(55991073, 100000000); + threshold = dc_fixpt_from_fraction(1, 2); + + if (dc_fixpt_lt(in_x, threshold)) { + x = dc_fixpt_mul(in_x, in_x); + x = dc_fixpt_div_int(x, 3); + } else { + x = dc_fixpt_sub(in_x, c); + x = dc_fixpt_div(x, a); + x = dc_fixpt_exp(x); + x = dc_fixpt_add(x, b); + x = dc_fixpt_div_int(x, 12); + } + *out_y = dc_fixpt_mul(x, scaling_factor); + +} + +/* re gamma, linear to non-linear */ +static void compute_hlg_oetf(struct fixed31_32 in_x, struct fixed31_32 *out_y, + uint32_t sdr_white_level, uint32_t max_luminance_nits) +{ + struct fixed31_32 a; + struct fixed31_32 b; + struct fixed31_32 c; + struct fixed31_32 threshold; + struct fixed31_32 x; + + struct fixed31_32 scaling_factor = + dc_fixpt_from_fraction(sdr_white_level, max_luminance_nits); + a = dc_fixpt_from_fraction(17883277, 100000000); + b = dc_fixpt_from_fraction(28466892, 100000000); + c = dc_fixpt_from_fraction(55991073, 100000000); + threshold = dc_fixpt_from_fraction(1, 12); + x = dc_fixpt_mul(in_x, scaling_factor); + + + if (dc_fixpt_lt(x, threshold)) { + x = dc_fixpt_mul(x, dc_fixpt_from_fraction(3, 1)); + *out_y = dc_fixpt_pow(x, dc_fixpt_half); + } else { + x = dc_fixpt_mul(x, dc_fixpt_from_fraction(12, 1)); + x = dc_fixpt_sub(x, b); + x = dc_fixpt_log(x); + x = dc_fixpt_mul(a, x); + *out_y = dc_fixpt_add(x, c); + } +} + + +/* 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); + + struct fixed31_32 *pq_table = mod_color_get_table(type_pq_table); + + /* 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); + struct fixed31_32 *de_pq_table = mod_color_get_table(type_de_pq_table); + /* 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 bool build_coefficients(struct gamma_coefficients *coefficients, + enum dc_transfer_func_predefined type) +{ + + uint32_t i = 0; + uint32_t index = 0; + bool ret = true; + + if (type == TRANSFER_FUNCTION_SRGB) + index = 0; + else if (type == TRANSFER_FUNCTION_BT709) + index = 1; + else if (type == TRANSFER_FUNCTION_GAMMA22) + index = 2; + else if (type == TRANSFER_FUNCTION_GAMMA24) + index = 3; + else if (type == TRANSFER_FUNCTION_GAMMA26) + index = 4; + else { + ret = false; + goto release; + } + + 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)); +release: + return ret; +} + +static struct fixed31_32 translate_from_linear_space( + struct translate_from_linear_space_args *args) +{ + const struct fixed31_32 one = dc_fixpt_from_int(1); + + struct fixed31_32 scratch_1, scratch_2; + struct calculate_buffer *cal_buffer = args->cal_buffer; + + if (dc_fixpt_le(one, args->arg)) + return one; + + if (dc_fixpt_le(args->arg, dc_fixpt_neg(args->a0))) { + scratch_1 = dc_fixpt_add(one, args->a3); + scratch_2 = dc_fixpt_pow( + dc_fixpt_neg(args->arg), + dc_fixpt_recip(args->gamma)); + scratch_1 = dc_fixpt_mul(scratch_1, scratch_2); + scratch_1 = dc_fixpt_sub(args->a2, scratch_1); + + return scratch_1; + } else if (dc_fixpt_le(args->a0, args->arg)) { + if (cal_buffer->buffer_index == 0) { + cal_buffer->gamma_of_2 = dc_fixpt_pow(dc_fixpt_from_int(2), + dc_fixpt_recip(args->gamma)); + } + scratch_1 = dc_fixpt_add(one, args->a3); + /* In the first region (first 16 points) and in the + * region delimited by START/END we calculate with + * full precision to avoid error accumulation. + */ + if ((cal_buffer->buffer_index >= PRECISE_LUT_REGION_START && + cal_buffer->buffer_index <= PRECISE_LUT_REGION_END) || + (cal_buffer->buffer_index < 16)) + scratch_2 = dc_fixpt_pow(args->arg, + dc_fixpt_recip(args->gamma)); + else + scratch_2 = dc_fixpt_mul(cal_buffer->gamma_of_2, + cal_buffer->buffer[cal_buffer->buffer_index%16]); + + if (cal_buffer->buffer_index != -1) { + cal_buffer->buffer[cal_buffer->buffer_index%16] = scratch_2; + cal_buffer->buffer_index++; + } + + scratch_1 = dc_fixpt_mul(scratch_1, scratch_2); + scratch_1 = dc_fixpt_sub(scratch_1, args->a2); + + return scratch_1; + } + else + return dc_fixpt_mul(args->arg, args->a1); +} + + +static struct fixed31_32 translate_from_linear_space_long( + struct translate_from_linear_space_args *args) +{ + const struct fixed31_32 one = dc_fixpt_from_int(1); + + if (dc_fixpt_lt(one, args->arg)) + return one; + + if (dc_fixpt_le(args->arg, dc_fixpt_neg(args->a0))) + return dc_fixpt_sub( + args->a2, + dc_fixpt_mul( + dc_fixpt_add( + one, + args->a3), + dc_fixpt_pow( + dc_fixpt_neg(args->arg), + dc_fixpt_recip(args->gamma)))); + else if (dc_fixpt_le(args->a0, args->arg)) + return dc_fixpt_sub( + dc_fixpt_mul( + dc_fixpt_add( + one, + args->a3), + dc_fixpt_pow( + args->arg, + dc_fixpt_recip(args->gamma))), + args->a2); + else + return dc_fixpt_mul(args->arg, args->a1); +} + +static struct fixed31_32 calculate_gamma22(struct fixed31_32 arg, bool use_eetf, struct calculate_buffer *cal_buffer) +{ + struct fixed31_32 gamma = dc_fixpt_from_fraction(22, 10); + struct translate_from_linear_space_args scratch_gamma_args; + + scratch_gamma_args.arg = arg; + scratch_gamma_args.a0 = dc_fixpt_zero; + scratch_gamma_args.a1 = dc_fixpt_zero; + scratch_gamma_args.a2 = dc_fixpt_zero; + scratch_gamma_args.a3 = dc_fixpt_zero; + scratch_gamma_args.cal_buffer = cal_buffer; + scratch_gamma_args.gamma = gamma; + + if (use_eetf) + return translate_from_linear_space_long(&scratch_gamma_args); + + return translate_from_linear_space(&scratch_gamma_args); +} + + +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 struct fixed31_32 translate_from_linear_space_ex( + struct fixed31_32 arg, + struct gamma_coefficients *coeff, + uint32_t color_index, + struct calculate_buffer *cal_buffer) +{ + struct translate_from_linear_space_args scratch_gamma_args; + + scratch_gamma_args.arg = arg; + scratch_gamma_args.a0 = coeff->a0[color_index]; + scratch_gamma_args.a1 = coeff->a1[color_index]; + scratch_gamma_args.a2 = coeff->a2[color_index]; + scratch_gamma_args.a3 = coeff->a3[color_index]; + scratch_gamma_args.gamma = coeff->user_gamma[color_index]; + scratch_gamma_args.cal_buffer = cal_buffer; + + return translate_from_linear_space(&scratch_gamma_args); +} + + +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 { + 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); + struct fixed31_32 *pq_table = mod_color_get_table(type_pq_table); + + if (!mod_color_is_table_init(type_pq_table) && sdr_white_level == 80) { + precompute_pq(); + mod_color_set_table_init_state(type_pq_table, 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 *de_pq_table = mod_color_get_table(type_de_pq_table); + struct fixed31_32 scaling_factor = dc_fixpt_from_int(125); + + if (!mod_color_is_table_init(type_de_pq_table)) { + precompute_de_pq(); + mod_color_set_table_init_state(type_de_pq_table, 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 bool build_regamma(struct pwl_float_data_ex *rgb_regamma, + uint32_t hw_points_num, + const struct hw_x_point *coordinate_x, + enum dc_transfer_func_predefined type, + struct calculate_buffer *cal_buffer) +{ + uint32_t i; + bool ret = false; + + struct gamma_coefficients *coeff; + struct pwl_float_data_ex *rgb = rgb_regamma; + const struct hw_x_point *coord_x = coordinate_x; + + coeff = kvzalloc(sizeof(*coeff), GFP_KERNEL); + if (!coeff) + goto release; + + if (!build_coefficients(coeff, type)) + goto release; + + memset(cal_buffer->buffer, 0, NUM_PTS_IN_REGION * sizeof(struct fixed31_32)); + cal_buffer->buffer_index = 0; // see variable definition for more info + + i = 0; + while (i <= hw_points_num) { + /* TODO use y vs r,g,b */ + rgb->r = translate_from_linear_space_ex( + coord_x->x, coeff, 0, cal_buffer); + rgb->g = rgb->r; + rgb->b = rgb->r; + ++coord_x; + ++rgb; + ++i; + } + cal_buffer->buffer_index = -1; + ret = true; +release: + kvfree(coeff); + return ret; +} + +static void hermite_spline_eetf(struct fixed31_32 input_x, + struct fixed31_32 max_display, + struct fixed31_32 min_display, + struct fixed31_32 max_content, + struct fixed31_32 *out_x) +{ + struct fixed31_32 min_lum_pq; + struct fixed31_32 max_lum_pq; + struct fixed31_32 max_content_pq; + struct fixed31_32 ks; + struct fixed31_32 E1; + struct fixed31_32 E2; + struct fixed31_32 E3; + struct fixed31_32 t; + struct fixed31_32 t2; + struct fixed31_32 t3; + struct fixed31_32 two; + struct fixed31_32 three; + struct fixed31_32 temp1; + struct fixed31_32 temp2; + struct fixed31_32 a = dc_fixpt_from_fraction(15, 10); + struct fixed31_32 b = dc_fixpt_from_fraction(5, 10); + struct fixed31_32 epsilon = dc_fixpt_from_fraction(1, 1000000); // dc_fixpt_epsilon is a bit too small + + if (dc_fixpt_eq(max_content, dc_fixpt_zero)) { + *out_x = dc_fixpt_zero; + return; + } + + compute_pq(input_x, &E1); + compute_pq(dc_fixpt_div(min_display, max_content), &min_lum_pq); + compute_pq(dc_fixpt_div(max_display, max_content), &max_lum_pq); + compute_pq(dc_fixpt_one, &max_content_pq); // always 1? DAL2 code is weird + a = dc_fixpt_div(dc_fixpt_add(dc_fixpt_one, b), max_content_pq); // (1+b)/maxContent + ks = dc_fixpt_sub(dc_fixpt_mul(a, max_lum_pq), b); // a * max_lum_pq - b + + if (dc_fixpt_lt(E1, ks)) + E2 = E1; + else if (dc_fixpt_le(ks, E1) && dc_fixpt_le(E1, dc_fixpt_one)) { + if (dc_fixpt_lt(epsilon, dc_fixpt_sub(dc_fixpt_one, ks))) + // t = (E1 - ks) / (1 - ks) + t = dc_fixpt_div(dc_fixpt_sub(E1, ks), + dc_fixpt_sub(dc_fixpt_one, ks)); + else + t = dc_fixpt_zero; + + two = dc_fixpt_from_int(2); + three = dc_fixpt_from_int(3); + + t2 = dc_fixpt_mul(t, t); + t3 = dc_fixpt_mul(t2, t); + temp1 = dc_fixpt_mul(two, t3); + temp2 = dc_fixpt_mul(three, t2); + + // (2t^3 - 3t^2 + 1) * ks + E2 = dc_fixpt_mul(ks, dc_fixpt_add(dc_fixpt_one, + dc_fixpt_sub(temp1, temp2))); + + // (-2t^3 + 3t^2) * max_lum_pq + E2 = dc_fixpt_add(E2, dc_fixpt_mul(max_lum_pq, + dc_fixpt_sub(temp2, temp1))); + + temp1 = dc_fixpt_mul(two, t2); + temp2 = dc_fixpt_sub(dc_fixpt_one, ks); + + // (t^3 - 2t^2 + t) * (1-ks) + E2 = dc_fixpt_add(E2, dc_fixpt_mul(temp2, + dc_fixpt_add(t, dc_fixpt_sub(t3, temp1)))); + } else + E2 = dc_fixpt_one; + + temp1 = dc_fixpt_sub(dc_fixpt_one, E2); + temp2 = dc_fixpt_mul(temp1, temp1); + temp2 = dc_fixpt_mul(temp2, temp2); + // temp2 = (1-E2)^4 + + E3 = dc_fixpt_add(E2, dc_fixpt_mul(min_lum_pq, temp2)); + compute_de_pq(E3, out_x); + + *out_x = dc_fixpt_div(*out_x, dc_fixpt_div(max_display, max_content)); +} + +static bool build_freesync_hdr(struct pwl_float_data_ex *rgb_regamma, + uint32_t hw_points_num, + const struct hw_x_point *coordinate_x, + const struct hdr_tm_params *fs_params, + struct calculate_buffer *cal_buffer) +{ + uint32_t i; + struct pwl_float_data_ex *rgb = rgb_regamma; + const struct hw_x_point *coord_x = coordinate_x; + const struct hw_x_point *prv_coord_x = coord_x; + struct fixed31_32 scaledX = dc_fixpt_zero; + struct fixed31_32 scaledX1 = dc_fixpt_zero; + struct fixed31_32 max_display; + struct fixed31_32 min_display; + struct fixed31_32 max_content; + struct fixed31_32 clip = dc_fixpt_one; + struct fixed31_32 output; + bool use_eetf = false; + bool is_clipped = false; + struct fixed31_32 sdr_white_level; + struct fixed31_32 coordX_diff; + struct fixed31_32 out_dist_max; + struct fixed31_32 bright_norm; + + if (fs_params->max_content == 0 || + fs_params->max_display == 0) + return false; + + max_display = dc_fixpt_from_int(fs_params->max_display); + min_display = dc_fixpt_from_fraction(fs_params->min_display, 10000); + max_content = dc_fixpt_from_int(fs_params->max_content); + sdr_white_level = dc_fixpt_from_int(fs_params->sdr_white_level); + + if (fs_params->min_display > 1000) // cap at 0.1 at the bottom + min_display = dc_fixpt_from_fraction(1, 10); + if (fs_params->max_display < 100) // cap at 100 at the top + max_display = dc_fixpt_from_int(100); + + // only max used, we don't adjust min luminance + if (fs_params->max_content > fs_params->max_display) + use_eetf = true; + else + max_content = max_display; + + if (!use_eetf) + cal_buffer->buffer_index = 0; // see var definition for more info + rgb += 32; // first 32 points have problems with fixed point, too small + coord_x += 32; + + for (i = 32; i <= hw_points_num; i++) { + if (!is_clipped) { + if (use_eetf) { + /* max content is equal 1 */ + scaledX1 = dc_fixpt_div(coord_x->x, + dc_fixpt_div(max_content, sdr_white_level)); + hermite_spline_eetf(scaledX1, max_display, min_display, + max_content, &scaledX); + } else + scaledX = dc_fixpt_div(coord_x->x, + dc_fixpt_div(max_display, sdr_white_level)); + + if (dc_fixpt_lt(scaledX, clip)) { + if (dc_fixpt_lt(scaledX, dc_fixpt_zero)) + output = dc_fixpt_zero; + else + output = calculate_gamma22(scaledX, use_eetf, cal_buffer); + + // Ensure output respects reasonable boundaries + output = dc_fixpt_clamp(output, dc_fixpt_zero, dc_fixpt_one); + + rgb->r = output; + rgb->g = output; + rgb->b = output; + } else { + /* Here clipping happens for the first time */ + is_clipped = true; + + /* The next few lines implement the equation + * output = prev_out + + * (coord_x->x - prev_coord_x->x) * + * (1.0 - prev_out) / + * (maxDisp/sdr_white_level - prevCoordX) + * + * This equation interpolates the first point + * after max_display/80 so that the slope from + * hw_x_before_max and hw_x_after_max is such + * that we hit Y=1.0 at max_display/80. + */ + + coordX_diff = dc_fixpt_sub(coord_x->x, prv_coord_x->x); + out_dist_max = dc_fixpt_sub(dc_fixpt_one, output); + bright_norm = dc_fixpt_div(max_display, sdr_white_level); + + output = dc_fixpt_add( + output, dc_fixpt_mul( + coordX_diff, dc_fixpt_div( + out_dist_max, + dc_fixpt_sub(bright_norm, prv_coord_x->x) + ) + ) + ); + + /* Relaxing the maximum boundary to 1.07 (instead of 1.0) + * because the last point in the curve must be such that + * the maximum display pixel brightness interpolates to + * exactly 1.0. The worst case scenario was calculated + * around 1.057, so the limit of 1.07 leaves some safety + * margin. + */ + output = dc_fixpt_clamp(output, dc_fixpt_zero, + dc_fixpt_from_fraction(107, 100)); + + rgb->r = output; + rgb->g = output; + rgb->b = output; + } + } else { + /* Every other clipping after the first + * one is dealt with here + */ + rgb->r = clip; + rgb->g = clip; + rgb->b = clip; + } + + prv_coord_x = coord_x; + ++coord_x; + ++rgb; + } + cal_buffer->buffer_index = -1; + + return true; +} + +static bool build_degamma(struct pwl_float_data_ex *curve, + uint32_t hw_points_num, + const struct hw_x_point *coordinate_x, enum dc_transfer_func_predefined type) +{ + uint32_t i; + struct gamma_coefficients coeff; + uint32_t begin_index, end_index; + bool ret = false; + + if (!build_coefficients(&coeff, type)) + goto release; + + 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++; + } + ret = true; +release: + return ret; +} + + + + + +static void build_hlg_degamma(struct pwl_float_data_ex *degamma, + uint32_t hw_points_num, + const struct hw_x_point *coordinate_x, + uint32_t sdr_white_level, uint32_t max_luminance_nits) +{ + uint32_t i; + + struct pwl_float_data_ex *rgb = degamma; + const struct hw_x_point *coord_x = coordinate_x; + + i = 0; + // check when i == 434 + while (i != hw_points_num + 1) { + compute_hlg_eotf(coord_x->x, &rgb->r, sdr_white_level, max_luminance_nits); + 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, + uint32_t sdr_white_level, uint32_t max_luminance_nits) +{ + uint32_t i; + + struct pwl_float_data_ex *rgb = regamma; + const struct hw_x_point *coord_x = coordinate_x; + + i = 0; + + // when i == 471 + while (i != hw_points_num + 1) { + compute_hlg_oetf(coord_x->x, &rgb->r, sdr_white_level, max_luminance_nits); + 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 penult_lut_index_f = + dc_fixpt_from_int(max_lut_index-1); + 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) + continue; + + if (index <= max_lut_index) + index_next = (index == max_lut_index) ? index : index+1; + else { + /* Here we are dealing with the last point in the curve, + * which in some cases might exceed the range given by + * max_lut_index. So we interpolate the value using + * max_lut_index and max_lut_index - 1. + */ + index = max_lut_index - 1; + index_next = max_lut_index; + index_f = penult_lut_index_f; + } + + 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; + const struct fixed31_32 one = dc_fixpt_from_int(1); + + 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; + + if (dc_fixpt_le(one, hw_x)) + hw_x = one; + + 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 = 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, struct calculate_buffer *cal_buffer) +{ + 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, TRANSFER_FUNCTION_SRGB); + + i = 0; + while (i != hw_points_num + 1) { + rgb->r = translate_from_linear_space_ex( + coord_x->x, &coeff, 0, cal_buffer); + 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, + bool doClamping) +{ + /* 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 (ramp && 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; + } + } + + if (doClamping) { + /* 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 calculate_user_regamma_coeff(struct dc_transfer_func *output_tf, + const struct regamma_lut *regamma, + struct calculate_buffer *cal_buffer, + const struct dc_gamma *ramp) +{ + 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, cal_buffer); + output_tf->tf_pts.green[i] = translate_from_linear_space_ex( + coord_x->x, &coeff, 1, cal_buffer); + output_tf->tf_pts.blue[i] = translate_from_linear_space_ex( + coord_x->x, &coeff, 2, cal_buffer); + ++coord_x; + ++i; + } + + if (ramp && ramp->type == GAMMA_CS_TFM_1D) + apply_lut_1d(ramp, MAX_HW_POINTS, &output_tf->tf_pts); + + // 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 calculate_buffer *cal_buffer, + const struct dc_gamma *ramp) +{ + 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, cal_buffer); + 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; + + if (ramp && ramp->type == GAMMA_CS_TFM_1D) + apply_lut_1d(ramp, MAX_HW_POINTS, &output_tf->tf_pts); + + // 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_color_caps *dc_caps, + 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 *axis_x = NULL; + struct pixel_gamma_point *coeff = NULL; + enum dc_transfer_func_predefined tf = TRANSFER_FUNCTION_SRGB; + uint32_t i; + bool ret = false; + + if (input_tf->type == TF_TYPE_BYPASS) + return false; + + /* we can use hardcoded curve for plain SRGB TF + * If linear, it's bypass if on user ramp + */ + if (input_tf->type == TF_TYPE_PREDEFINED) { + if ((input_tf->tf == TRANSFER_FUNCTION_SRGB || + input_tf->tf == TRANSFER_FUNCTION_LINEAR) && + !mapUserRamp) + return true; + + if (dc_caps != NULL && + dc_caps->dpp.dcn_arch == 1) { + + if (input_tf->tf == TRANSFER_FUNCTION_PQ && + dc_caps->dpp.dgam_rom_caps.pq == 1) + return true; + + if (input_tf->tf == TRANSFER_FUNCTION_GAMMA22 && + dc_caps->dpp.dgam_rom_caps.gamma2_2 == 1) + return true; + + // HLG OOTF not accounted for + if (input_tf->tf == TRANSFER_FUNCTION_HLG && + dc_caps->dpp.dgam_rom_caps.hlg == 1) + return true; + } + } + + input_tf->type = TF_TYPE_DISTRIBUTED_POINTS; + + if (mapUserRamp && ramp && ramp->type == GAMMA_RGB_256) { + rgb_user = kvcalloc(ramp->num_entries + _EXTRA_POINTS, + sizeof(*rgb_user), + GFP_KERNEL); + if (!rgb_user) + goto rgb_user_alloc_fail; + + axis_x = kvcalloc(ramp->num_entries + _EXTRA_POINTS, sizeof(*axis_x), + GFP_KERNEL); + if (!axis_x) + goto axis_x_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); + + build_evenly_distributed_points( + axis_x, + ramp->num_entries, + dividers); + + scale_gamma(rgb_user, ramp, dividers); + } + + curve = kvcalloc(MAX_HW_POINTS + _EXTRA_POINTS, sizeof(*curve), + GFP_KERNEL); + if (!curve) + goto curve_alloc_fail; + + coeff = kvcalloc(MAX_HW_POINTS + _EXTRA_POINTS, sizeof(*coeff), + GFP_KERNEL); + if (!coeff) + goto coeff_alloc_fail; + + tf = input_tf->tf; + + if (tf == TRANSFER_FUNCTION_PQ) + build_de_pq(curve, + MAX_HW_POINTS, + coordinates_x); + else if (tf == TRANSFER_FUNCTION_SRGB || + tf == TRANSFER_FUNCTION_BT709 || + tf == TRANSFER_FUNCTION_GAMMA22 || + tf == TRANSFER_FUNCTION_GAMMA24 || + tf == TRANSFER_FUNCTION_GAMMA26) + build_degamma(curve, + MAX_HW_POINTS, + coordinates_x, + tf); + else if (tf == TRANSFER_FUNCTION_HLG) + build_hlg_degamma(curve, + MAX_HW_POINTS, + coordinates_x, + 80, 1000); + else if (tf == TRANSFER_FUNCTION_LINEAR) { + // just copy coordinates_x into curve + i = 0; + while (i != MAX_HW_POINTS + 1) { + curve[i].r = coordinates_x[i].x; + curve[i].g = curve[i].r; + curve[i].b = curve[i].r; + i++; + } + } else + goto invalid_tf_fail; + + 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; + + if (input_tf->tf == TRANSFER_FUNCTION_PQ) { + /* just copy current rgb_regamma into tf_pts */ + struct pwl_float_data_ex *curvePt = curve; + int i = 0; + + while (i <= MAX_HW_POINTS) { + tf_pts->red[i] = curvePt->r; + tf_pts->green[i] = curvePt->g; + tf_pts->blue[i] = curvePt->b; + ++curvePt; + ++i; + } + } else { + // clamps to 0-1 + map_regamma_hw_to_x_user(ramp, coeff, rgb_user, + coordinates_x, axis_x, curve, + MAX_HW_POINTS, tf_pts, + mapUserRamp && ramp && ramp->type == GAMMA_RGB_256, + true); + } + + + + if (ramp && ramp->type == GAMMA_CUSTOM) + apply_lut_1d(ramp, MAX_HW_POINTS, tf_pts); + + ret = true; + +invalid_tf_fail: + kvfree(coeff); +coeff_alloc_fail: + kvfree(curve); +curve_alloc_fail: + kvfree(axis_x); +axis_x_alloc_fail: + kvfree(rgb_user); +rgb_user_alloc_fail: + + return ret; +} + +static bool calculate_curve(enum dc_transfer_func_predefined trans, + struct dc_transfer_func_distributed_points *points, + struct pwl_float_data_ex *rgb_regamma, + const struct hdr_tm_params *fs_params, + uint32_t sdr_ref_white_level, + struct calculate_buffer *cal_buffer) +{ + uint32_t i; + bool ret = false; + + 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++) { + rgb_regamma[i].r = coordinates_x[i].x; + rgb_regamma[i].g = coordinates_x[i].x; + rgb_regamma[i].b = coordinates_x[i].x; + } + + ret = true; + } else if (trans == TRANSFER_FUNCTION_PQ) { + 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, + sdr_ref_white_level); + + ret = true; + } else if (trans == TRANSFER_FUNCTION_GAMMA22 && + fs_params != NULL && fs_params->skip_tm == 0) { + build_freesync_hdr(rgb_regamma, + MAX_HW_POINTS, + coordinates_x, + fs_params, + cal_buffer); + + ret = true; + } else if (trans == TRANSFER_FUNCTION_HLG) { + points->end_exponent = 4; + points->x_point_at_y1_red = 12; + points->x_point_at_y1_green = 12; + points->x_point_at_y1_blue = 12; + + build_hlg_regamma(rgb_regamma, + MAX_HW_POINTS, + coordinates_x, + 80, 1000); + + ret = true; + } else { + // trans == TRANSFER_FUNCTION_SRGB + // trans == TRANSFER_FUNCTION_BT709 + // trans == TRANSFER_FUNCTION_GAMMA22 + // trans == TRANSFER_FUNCTION_GAMMA24 + // trans == TRANSFER_FUNCTION_GAMMA26 + 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, + cal_buffer); + + ret = true; + } + + return ret; +} + +bool mod_color_calculate_regamma_params(struct dc_transfer_func *output_tf, + const struct dc_gamma *ramp, bool mapUserRamp, bool canRomBeUsed, + const struct hdr_tm_params *fs_params, + struct calculate_buffer *cal_buffer) +{ + 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 *axis_x = NULL; + struct pixel_gamma_point *coeff = NULL; + enum dc_transfer_func_predefined tf = TRANSFER_FUNCTION_SRGB; + bool doClamping = true; + 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 && canRomBeUsed == true && + output_tf->tf == TRANSFER_FUNCTION_SRGB) { + if (ramp == NULL) + return true; + if ((ramp->is_identity && ramp->type != GAMMA_CS_TFM_1D) || + (!mapUserRamp && ramp->type == GAMMA_RGB_256)) + return true; + } + + output_tf->type = TF_TYPE_DISTRIBUTED_POINTS; + + if (ramp && ramp->type != GAMMA_CS_TFM_1D && + (mapUserRamp || ramp->type != GAMMA_RGB_256)) { + rgb_user = kvcalloc(ramp->num_entries + _EXTRA_POINTS, + sizeof(*rgb_user), + GFP_KERNEL); + if (!rgb_user) + goto rgb_user_alloc_fail; + + axis_x = kvcalloc(ramp->num_entries + 3, sizeof(*axis_x), + GFP_KERNEL); + if (!axis_x) + goto axis_x_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); + + build_evenly_distributed_points( + axis_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); + } + + rgb_regamma = kvcalloc(MAX_HW_POINTS + _EXTRA_POINTS, + sizeof(*rgb_regamma), + GFP_KERNEL); + if (!rgb_regamma) + goto rgb_regamma_alloc_fail; + + coeff = kvcalloc(MAX_HW_POINTS + _EXTRA_POINTS, sizeof(*coeff), + GFP_KERNEL); + if (!coeff) + goto coeff_alloc_fail; + + tf = output_tf->tf; + + ret = calculate_curve(tf, + tf_pts, + rgb_regamma, + fs_params, + output_tf->sdr_ref_white_level, + cal_buffer); + + if (ret) { + doClamping = !(output_tf->tf == TRANSFER_FUNCTION_GAMMA22 && + fs_params != NULL && fs_params->skip_tm == 0); + + map_regamma_hw_to_x_user(ramp, coeff, rgb_user, + coordinates_x, axis_x, rgb_regamma, + MAX_HW_POINTS, tf_pts, + (mapUserRamp || (ramp && ramp->type != GAMMA_RGB_256)) && + (ramp && ramp->type != GAMMA_CS_TFM_1D), + doClamping); + + if (ramp && ramp->type == GAMMA_CS_TFM_1D) + apply_lut_1d(ramp, MAX_HW_POINTS, tf_pts); + } + + kvfree(coeff); +coeff_alloc_fail: + kvfree(rgb_regamma); +rgb_regamma_alloc_fail: + kvfree(axis_x); +axis_x_alloc_fail: + kvfree(rgb_user); +rgb_user_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 || + trans == TRANSFER_FUNCTION_GAMMA22 || + trans == TRANSFER_FUNCTION_GAMMA24 || + trans == TRANSFER_FUNCTION_GAMMA26) { + 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); + 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) { + 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, + 80, 1000); + 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..2893abf48 --- /dev/null +++ b/drivers/gpu/drm/amd/display/modules/color/color_gamma.h @@ -0,0 +1,132 @@ +/* + * 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_ + +#include "color_table.h" + +struct dc_transfer_func; +struct dc_gamma; +struct dc_transfer_func_distributed_points; +struct dc_rgb_fixed; +struct dc_color_caps; +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; + }; +}; + +struct hdr_tm_params { + unsigned int sdr_white_level; + unsigned int min_content; // luminance in 1/10000 nits + unsigned int max_content; // luminance in nits + unsigned int min_display; // luminance in 1/10000 nits + unsigned int max_display; // luminance in nits + unsigned int skip_tm; // skip tm +}; + +struct calculate_buffer { + int buffer_index; + struct fixed31_32 buffer[NUM_PTS_IN_REGION]; + struct fixed31_32 gamma_of_2; +}; + +struct translate_from_linear_space_args { + 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 calculate_buffer *cal_buffer; +}; + +void setup_x_points_distribution(void); +void log_x_points_distribution(struct dal_logger *logger); +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 canRomBeUsed, + const struct hdr_tm_params *fs_params, + struct calculate_buffer *cal_buffer); + +bool mod_color_calculate_degamma_params(struct dc_color_caps *dc_caps, + struct dc_transfer_func *output_tf, + const struct dc_gamma *ramp, bool mapUserRamp); + +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, + struct calculate_buffer *cal_buffer, + const struct dc_gamma *ramp); + +bool calculate_user_regamma_ramp(struct dc_transfer_func *output_tf, + const struct regamma_lut *regamma, + struct calculate_buffer *cal_buffer, + const struct dc_gamma *ramp); + + +#endif /* COLOR_MOD_COLOR_GAMMA_H_ */ diff --git a/drivers/gpu/drm/amd/display/modules/color/color_table.c b/drivers/gpu/drm/amd/display/modules/color/color_table.c new file mode 100644 index 000000000..410f2a82b --- /dev/null +++ b/drivers/gpu/drm/amd/display/modules/color/color_table.c @@ -0,0 +1,64 @@ +/* + * Copyright 2019 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 "color_table.h" + +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; +static bool de_pg_initialized; + +bool mod_color_is_table_init(enum table_type type) +{ + bool ret = false; + + if (type == type_pq_table) + ret = pq_initialized; + if (type == type_de_pq_table) + ret = de_pg_initialized; + + return ret; +} + +struct fixed31_32 *mod_color_get_table(enum table_type type) +{ + struct fixed31_32 *table = NULL; + + if (type == type_pq_table) + table = pq_table; + if (type == type_de_pq_table) + table = de_pq_table; + + return table; +} + +void mod_color_set_table_init_state(enum table_type type, bool state) +{ + if (type == type_pq_table) + pq_initialized = state; + if (type == type_de_pq_table) + de_pg_initialized = state; +} + diff --git a/drivers/gpu/drm/amd/display/modules/color/color_table.h b/drivers/gpu/drm/amd/display/modules/color/color_table.h new file mode 100644 index 000000000..2621dd619 --- /dev/null +++ b/drivers/gpu/drm/amd/display/modules/color/color_table.h @@ -0,0 +1,47 @@ +/* + * 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_TABLE_H_ +#define COLOR_MOD_COLOR_TABLE_H_ + +#include "dc_types.h" + +#define NUM_PTS_IN_REGION 16 +#define NUM_REGIONS 32 +#define MAX_HW_POINTS (NUM_PTS_IN_REGION*NUM_REGIONS) + +enum table_type { + type_pq_table, + type_de_pq_table +}; + +bool mod_color_is_table_init(enum table_type type); + +struct fixed31_32 *mod_color_get_table(enum table_type type); + +void mod_color_set_table_init_state(enum table_type type, bool state); + +#endif /* COLOR_MOD_COLOR_TABLE_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..9e74b6e8b --- /dev/null +++ b/drivers/gpu/drm/amd/display/modules/freesync/freesync.c @@ -0,0 +1,1435 @@ +/* + * 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 + +#define MIN_REFRESH_RANGE 10 +/* 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/exit BTR (to avoid frequent enter-exits at the lower limit) */ +#define BTR_MAX_MARGIN 2500 +/* Threshold to change BTR multiplier (to avoid frequent changes) */ +#define BTR_DRIFT_MARGIN 2000 +/* Threshold to exit fixed refresh rate */ +#define FIXED_REFRESH_EXIT_MARGIN_IN_HZ 1 +/* Number of consecutive frames to check before entering/exiting fixed refresh */ +#define FIXED_REFRESH_ENTER_FRAME_COUNT 5 +#define FIXED_REFRESH_EXIT_FRAME_COUNT 10 +/* Flip interval workaround constants */ +#define VSYNCS_BETWEEN_FLIP_THRESHOLD 2 +#define FREESYNC_CONSEC_FLIP_AFTER_VSYNC 5 +#define FREESYNC_VSYNC_TO_FLIP_DELTA_IN_US 500 + +struct core_freesync { + struct mod_freesync public; + struct dc *dc; +}; + +#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); + + if (core_freesync == NULL) + goto fail_alloc_context; + + if (dc == NULL) + goto fail_construct; + + core_freesync->dc = dc; + return &core_freesync->public; + +fail_construct: + kfree(core_freesync); + +fail_alloc_context: + return NULL; +} + +void mod_freesync_destroy(struct mod_freesync *mod_freesync) +{ + struct core_freesync *core_freesync = NULL; + if (mod_freesync == NULL) + return; + core_freesync = MOD_FREESYNC_TO_CORE(mod_freesync); + kfree(core_freesync); +} + +#if 0 /* Unused currently */ +static unsigned int calc_refresh_in_uhz_from_duration( + unsigned int duration_in_ns) +{ + unsigned int refresh_in_uhz = + ((unsigned int)(div64_u64((1000000000ULL * 1000000), + duration_in_ns))); + return refresh_in_uhz; +} +#endif + +static unsigned int calc_duration_in_us_from_refresh_in_uhz( + unsigned int refresh_in_uhz) +{ + unsigned int duration_in_us = + ((unsigned int)(div64_u64((1000000000ULL * 1000), + refresh_in_uhz))); + return duration_in_us; +} + +static unsigned int calc_duration_in_us_from_v_total( + const struct dc_stream_state *stream, + const struct mod_vrr_params *in_vrr, + unsigned int v_total) +{ + unsigned int duration_in_us = + (unsigned int)(div64_u64(((unsigned long long)(v_total) + * 10000) * stream->timing.h_total, + stream->timing.pix_clk_100hz)); + + return duration_in_us; +} + +unsigned int mod_freesync_calc_v_total_from_refresh( + const struct dc_stream_state *stream, + unsigned int refresh_in_uhz) +{ + unsigned int v_total; + unsigned int frame_duration_in_ns; + + frame_duration_in_ns = + ((unsigned int)(div64_u64((1000000000ULL * 1000000), + refresh_in_uhz))); + + v_total = div64_u64(div64_u64(((unsigned long long)( + frame_duration_in_ns) * (stream->timing.pix_clk_100hz / 10)), + stream->timing.h_total), 1000000); + + /* v_total cannot be less than nominal */ + if (v_total < stream->timing.v_total) { + ASSERT(v_total < stream->timing.v_total); + v_total = stream->timing.v_total; + } + + return v_total; +} + +static unsigned int calc_v_total_from_duration( + const struct dc_stream_state *stream, + const struct mod_vrr_params *vrr, + unsigned int duration_in_us) +{ + unsigned int v_total = 0; + + if (duration_in_us < vrr->min_duration_in_us) + duration_in_us = vrr->min_duration_in_us; + + if (duration_in_us > vrr->max_duration_in_us) + duration_in_us = vrr->max_duration_in_us; + + if (dc_is_hdmi_signal(stream->signal)) { + uint32_t h_total_up_scaled; + + h_total_up_scaled = stream->timing.h_total * 10000; + v_total = div_u64((unsigned long long)duration_in_us + * stream->timing.pix_clk_100hz + (h_total_up_scaled - 1), + h_total_up_scaled); + } else { + v_total = div64_u64(div64_u64(((unsigned long long)( + duration_in_us) * (stream->timing.pix_clk_100hz / 10)), + stream->timing.h_total), 1000); + } + + /* v_total cannot be less than nominal */ + if (v_total < stream->timing.v_total) { + ASSERT(v_total < stream->timing.v_total); + v_total = stream->timing.v_total; + } + + return v_total; +} + +static void update_v_total_for_static_ramp( + struct core_freesync *core_freesync, + const struct dc_stream_state *stream, + struct mod_vrr_params *in_out_vrr) +{ + unsigned int v_total = 0; + unsigned int current_duration_in_us = + calc_duration_in_us_from_v_total( + stream, in_out_vrr, + in_out_vrr->adjust.v_total_max); + unsigned int target_duration_in_us = + calc_duration_in_us_from_refresh_in_uhz( + in_out_vrr->fixed.target_refresh_in_uhz); + bool ramp_direction_is_up = (current_duration_in_us > + target_duration_in_us) ? true : false; + + /* Calculate 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) * + current_duration_in_us), + 1000000))); + + /* Calculate delta between new and current frame duration in us */ + unsigned int frame_duration_delta = div64_u64(((unsigned long long)( + current_duration_in_us) * + (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) * current_duration_in_us), 16666); + + /* Going to a higher refresh rate (lower frame duration) */ + if (ramp_direction_is_up) { + /* Reduce frame duration */ + current_duration_in_us -= ramp_rate_interpolated; + + /* Adjust for frame duration below min */ + if (current_duration_in_us <= target_duration_in_us) { + in_out_vrr->fixed.ramping_active = false; + in_out_vrr->fixed.ramping_done = true; + current_duration_in_us = + calc_duration_in_us_from_refresh_in_uhz( + in_out_vrr->fixed.target_refresh_in_uhz); + } + /* Going to a lower refresh rate (larger frame duration) */ + } else { + /* Increase frame duration */ + current_duration_in_us += ramp_rate_interpolated; + + /* Adjust for frame duration above max */ + if (current_duration_in_us >= target_duration_in_us) { + in_out_vrr->fixed.ramping_active = false; + in_out_vrr->fixed.ramping_done = true; + current_duration_in_us = + calc_duration_in_us_from_refresh_in_uhz( + in_out_vrr->fixed.target_refresh_in_uhz); + } + } + + v_total = div64_u64(div64_u64(((unsigned long long)( + current_duration_in_us) * (stream->timing.pix_clk_100hz / 10)), + stream->timing.h_total), 1000); + + /* v_total cannot be less than nominal */ + if (v_total < stream->timing.v_total) + v_total = stream->timing.v_total; + + in_out_vrr->adjust.v_total_min = v_total; + in_out_vrr->adjust.v_total_max = v_total; +} + +static void apply_below_the_range(struct core_freesync *core_freesync, + const struct dc_stream_state *stream, + unsigned int last_render_time_in_us, + struct mod_vrr_params *in_out_vrr) +{ + 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 delta_from_mid_point_delta_in_us; + unsigned int max_render_time_in_us = + in_out_vrr->max_duration_in_us - in_out_vrr->btr.margin_in_us; + + /* Program BTR */ + if ((last_render_time_in_us + in_out_vrr->btr.margin_in_us / 2) < max_render_time_in_us) { + /* Exit Below the Range */ + if (in_out_vrr->btr.btr_active) { + in_out_vrr->btr.frame_counter = 0; + in_out_vrr->btr.btr_active = false; + } + } else if (last_render_time_in_us > (max_render_time_in_us + in_out_vrr->btr.margin_in_us / 2)) { + /* Enter Below the Range */ + if (!in_out_vrr->btr.btr_active) { + in_out_vrr->btr.btr_active = true; + } + } + + /* BTR set to "not active" so disengage */ + if (!in_out_vrr->btr.btr_active) { + in_out_vrr->btr.inserted_duration_in_us = 0; + in_out_vrr->btr.frames_to_insert = 0; + in_out_vrr->btr.frame_counter = 0; + + /* Restore FreeSync */ + in_out_vrr->adjust.v_total_min = + mod_freesync_calc_v_total_from_refresh(stream, + in_out_vrr->max_refresh_in_uhz); + in_out_vrr->adjust.v_total_max = + mod_freesync_calc_v_total_from_refresh(stream, + in_out_vrr->min_refresh_in_uhz); + /* 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 + + in_out_vrr->btr.mid_point_in_us - 1) / + in_out_vrr->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 = + (in_out_vrr->btr.mid_point_in_us > + frame_time_in_us) ? + (in_out_vrr->btr.mid_point_in_us - frame_time_in_us) : + (frame_time_in_us - in_out_vrr->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 / + in_out_vrr->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 = + (in_out_vrr->btr.mid_point_in_us > + frame_time_in_us) ? + (in_out_vrr->btr.mid_point_in_us - frame_time_in_us) : + (frame_time_in_us - in_out_vrr->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. + * - Delta for CEIL: delta_from_mid_point_in_us_1 + * - Delta for FLOOR: delta_from_mid_point_in_us_2 + */ + if (mid_point_frames_ceil && + (last_render_time_in_us / mid_point_frames_ceil) < + in_out_vrr->min_duration_in_us) { + /* Check for out of range. + * If using CEIL produces a value that is out of range, + * then we are forced to use FLOOR. + */ + frames_to_insert = mid_point_frames_floor; + } else if (mid_point_frames_floor < 2) { + /* Check if FLOOR would result in non-LFC. In this case + * choose to use CEIL + */ + frames_to_insert = mid_point_frames_ceil; + } else if (delta_from_mid_point_in_us_1 < delta_from_mid_point_in_us_2) { + /* If choosing CEIL results in a frame duration that is + * closer to the mid point of the range. + * Choose CEIL + */ + frames_to_insert = mid_point_frames_ceil; + } else { + /* If choosing FLOOR results in a frame duration that is + * closer to the mid point of the range. + * Choose FLOOR + */ + frames_to_insert = mid_point_frames_floor; + } + + /* Prefer current frame multiplier when BTR is enabled unless it drifts + * too far from the midpoint + */ + if (delta_from_mid_point_in_us_1 < delta_from_mid_point_in_us_2) { + delta_from_mid_point_delta_in_us = delta_from_mid_point_in_us_2 - + delta_from_mid_point_in_us_1; + } else { + delta_from_mid_point_delta_in_us = delta_from_mid_point_in_us_1 - + delta_from_mid_point_in_us_2; + } + if (in_out_vrr->btr.frames_to_insert != 0 && + delta_from_mid_point_delta_in_us < BTR_DRIFT_MARGIN) { + if (((last_render_time_in_us / in_out_vrr->btr.frames_to_insert) < + max_render_time_in_us) && + ((last_render_time_in_us / in_out_vrr->btr.frames_to_insert) > + in_out_vrr->min_duration_in_us)) + frames_to_insert = in_out_vrr->btr.frames_to_insert; + } + + /* Either we've calculated the number of frames to insert, + * or we need to insert min duration frames + */ + if (frames_to_insert && + (last_render_time_in_us / frames_to_insert) < + in_out_vrr->min_duration_in_us){ + frames_to_insert -= (frames_to_insert > 1) ? + 1 : 0; + } + + if (frames_to_insert > 0) + inserted_frame_duration_in_us = last_render_time_in_us / + frames_to_insert; + + if (inserted_frame_duration_in_us < in_out_vrr->min_duration_in_us) + inserted_frame_duration_in_us = in_out_vrr->min_duration_in_us; + + /* Cache the calculated variables */ + in_out_vrr->btr.inserted_duration_in_us = + inserted_frame_duration_in_us; + in_out_vrr->btr.frames_to_insert = frames_to_insert; + in_out_vrr->btr.frame_counter = frames_to_insert; + } +} + +static void apply_fixed_refresh(struct core_freesync *core_freesync, + const struct dc_stream_state *stream, + unsigned int last_render_time_in_us, + struct mod_vrr_params *in_out_vrr) +{ + bool update = false; + unsigned int max_render_time_in_us = in_out_vrr->max_duration_in_us; + + /* Compute the exit refresh rate and exit frame duration */ + unsigned int exit_refresh_rate_in_milli_hz = ((1000000000/max_render_time_in_us) + + (1000*FIXED_REFRESH_EXIT_MARGIN_IN_HZ)); + unsigned int exit_frame_duration_in_us = 1000000000/exit_refresh_rate_in_milli_hz; + + if (last_render_time_in_us < exit_frame_duration_in_us) { + /* Exit Fixed Refresh mode */ + if (in_out_vrr->fixed.fixed_active) { + in_out_vrr->fixed.frame_counter++; + + if (in_out_vrr->fixed.frame_counter > + FIXED_REFRESH_EXIT_FRAME_COUNT) { + in_out_vrr->fixed.frame_counter = 0; + in_out_vrr->fixed.fixed_active = false; + in_out_vrr->fixed.target_refresh_in_uhz = 0; + update = true; + } + } else + in_out_vrr->fixed.frame_counter = 0; + } else if (last_render_time_in_us > max_render_time_in_us) { + /* Enter Fixed Refresh mode */ + if (!in_out_vrr->fixed.fixed_active) { + in_out_vrr->fixed.frame_counter++; + + if (in_out_vrr->fixed.frame_counter > + FIXED_REFRESH_ENTER_FRAME_COUNT) { + in_out_vrr->fixed.frame_counter = 0; + in_out_vrr->fixed.fixed_active = true; + in_out_vrr->fixed.target_refresh_in_uhz = + in_out_vrr->max_refresh_in_uhz; + update = true; + } + } else + in_out_vrr->fixed.frame_counter = 0; + } + + if (update) { + if (in_out_vrr->fixed.fixed_active) { + in_out_vrr->adjust.v_total_min = + mod_freesync_calc_v_total_from_refresh( + stream, in_out_vrr->max_refresh_in_uhz); + in_out_vrr->adjust.v_total_max = + in_out_vrr->adjust.v_total_min; + } else { + in_out_vrr->adjust.v_total_min = + mod_freesync_calc_v_total_from_refresh(stream, + in_out_vrr->max_refresh_in_uhz); + in_out_vrr->adjust.v_total_max = + mod_freesync_calc_v_total_from_refresh(stream, + in_out_vrr->min_refresh_in_uhz); + } + } +} + +static void determine_flip_interval_workaround_req(struct mod_vrr_params *in_vrr, + unsigned int curr_time_stamp_in_us) +{ + in_vrr->flip_interval.vsync_to_flip_in_us = curr_time_stamp_in_us - + in_vrr->flip_interval.v_update_timestamp_in_us; + + /* Determine conditions for stopping workaround */ + if (in_vrr->flip_interval.flip_interval_workaround_active && + in_vrr->flip_interval.vsyncs_between_flip < VSYNCS_BETWEEN_FLIP_THRESHOLD && + in_vrr->flip_interval.vsync_to_flip_in_us > FREESYNC_VSYNC_TO_FLIP_DELTA_IN_US) { + in_vrr->flip_interval.flip_interval_detect_counter = 0; + in_vrr->flip_interval.program_flip_interval_workaround = true; + in_vrr->flip_interval.flip_interval_workaround_active = false; + } else { + /* Determine conditions for starting workaround */ + if (in_vrr->flip_interval.vsyncs_between_flip >= VSYNCS_BETWEEN_FLIP_THRESHOLD && + in_vrr->flip_interval.vsync_to_flip_in_us < FREESYNC_VSYNC_TO_FLIP_DELTA_IN_US) { + /* Increase flip interval counter we have 2 vsyncs between flips and + * vsync to flip interval is less than 500us + */ + in_vrr->flip_interval.flip_interval_detect_counter++; + if (in_vrr->flip_interval.flip_interval_detect_counter > FREESYNC_CONSEC_FLIP_AFTER_VSYNC) { + /* Start workaround if we detect 5 consecutive instances of the above case */ + in_vrr->flip_interval.program_flip_interval_workaround = true; + in_vrr->flip_interval.flip_interval_workaround_active = true; + } + } else { + /* Reset the flip interval counter if we condition is no longer met */ + in_vrr->flip_interval.flip_interval_detect_counter = 0; + } + } + + in_vrr->flip_interval.vsyncs_between_flip = 0; +} + +static bool vrr_settings_require_update(struct core_freesync *core_freesync, + struct mod_freesync_config *in_config, + unsigned int min_refresh_in_uhz, + unsigned int max_refresh_in_uhz, + struct mod_vrr_params *in_vrr) +{ + if (in_vrr->state != in_config->state) { + return true; + } else if (in_vrr->state == VRR_STATE_ACTIVE_FIXED && + in_vrr->fixed.target_refresh_in_uhz != + in_config->fixed_refresh_in_uhz) { + return true; + } else if (in_vrr->min_refresh_in_uhz != min_refresh_in_uhz) { + return true; + } else if (in_vrr->max_refresh_in_uhz != max_refresh_in_uhz) { + return true; + } + + return false; +} + +bool mod_freesync_get_vmin_vmax(struct mod_freesync *mod_freesync, + const struct dc_stream_state *stream, + unsigned int *vmin, + unsigned int *vmax) +{ + *vmin = stream->adjust.v_total_min; + *vmax = stream->adjust.v_total_max; + + 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) +{ + struct core_freesync *core_freesync = NULL; + struct crtc_position position; + + if (mod_freesync == NULL) + return false; + + core_freesync = MOD_FREESYNC_TO_CORE(mod_freesync); + + 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; +} + +static void build_vrr_infopacket_data_v1(const struct mod_vrr_params *vrr, + struct dc_info_packet *infopacket, + bool freesync_on_desktop) +{ + /* PB1 = 0x1A (24bit AMD IEEE OUI (0x00001A) - Byte 0) */ + infopacket->sb[1] = 0x1A; + + /* PB2 = 0x00 (24bit AMD IEEE OUI (0x00001A) - Byte 1) */ + infopacket->sb[2] = 0x00; + + /* PB3 = 0x00 (24bit AMD IEEE OUI (0x00001A) - Byte 2) */ + infopacket->sb[3] = 0x00; + + /* PB4 = Reserved */ + + /* PB5 = Reserved */ + + /* PB6 = [Bits 7:3 = Reserved] */ + + /* PB6 = [Bit 0 = FreeSync Supported] */ + if (vrr->state != VRR_STATE_UNSUPPORTED) + infopacket->sb[6] |= 0x01; + + /* PB6 = [Bit 1 = FreeSync Enabled] */ + if (vrr->state != VRR_STATE_DISABLED && + vrr->state != VRR_STATE_UNSUPPORTED) + infopacket->sb[6] |= 0x02; + + if (freesync_on_desktop) { + /* PB6 = [Bit 2 = FreeSync Active] */ + if (vrr->state != VRR_STATE_DISABLED && + vrr->state != VRR_STATE_UNSUPPORTED) + infopacket->sb[6] |= 0x04; + } else { + if (vrr->state == VRR_STATE_ACTIVE_VARIABLE || + vrr->state == VRR_STATE_ACTIVE_FIXED) + infopacket->sb[6] |= 0x04; + } + + // For v1 & 2 infoframes program nominal if non-fs mode, otherwise full range + /* PB7 = FreeSync Minimum refresh rate (Hz) */ + if (vrr->state == VRR_STATE_ACTIVE_VARIABLE || + vrr->state == VRR_STATE_ACTIVE_FIXED) { + infopacket->sb[7] = (unsigned char)((vrr->min_refresh_in_uhz + 500000) / 1000000); + } else { + infopacket->sb[7] = (unsigned char)((vrr->max_refresh_in_uhz + 500000) / 1000000); + } + + /* PB8 = FreeSync Maximum refresh rate (Hz) + * Note: We should never go above the field rate of the mode timing set. + */ + infopacket->sb[8] = (unsigned char)((vrr->max_refresh_in_uhz + 500000) / 1000000); +} + +static void build_vrr_infopacket_data_v3(const struct mod_vrr_params *vrr, + struct dc_info_packet *infopacket) +{ + unsigned int min_refresh; + unsigned int max_refresh; + unsigned int fixed_refresh; + unsigned int min_programmed; + unsigned int max_programmed; + + /* PB1 = 0x1A (24bit AMD IEEE OUI (0x00001A) - Byte 0) */ + infopacket->sb[1] = 0x1A; + + /* PB2 = 0x00 (24bit AMD IEEE OUI (0x00001A) - Byte 1) */ + infopacket->sb[2] = 0x00; + + /* PB3 = 0x00 (24bit AMD IEEE OUI (0x00001A) - Byte 2) */ + infopacket->sb[3] = 0x00; + + /* PB4 = Reserved */ + + /* PB5 = Reserved */ + + /* PB6 = [Bits 7:3 = Reserved] */ + + /* PB6 = [Bit 0 = FreeSync Supported] */ + if (vrr->state != VRR_STATE_UNSUPPORTED) + infopacket->sb[6] |= 0x01; + + /* PB6 = [Bit 1 = FreeSync Enabled] */ + if (vrr->state != VRR_STATE_DISABLED && + vrr->state != VRR_STATE_UNSUPPORTED) + infopacket->sb[6] |= 0x02; + + /* PB6 = [Bit 2 = FreeSync Active] */ + if (vrr->state == VRR_STATE_ACTIVE_VARIABLE || + vrr->state == VRR_STATE_ACTIVE_FIXED) + infopacket->sb[6] |= 0x04; + + min_refresh = (vrr->min_refresh_in_uhz + 500000) / 1000000; + max_refresh = (vrr->max_refresh_in_uhz + 500000) / 1000000; + fixed_refresh = (vrr->fixed_refresh_in_uhz + 500000) / 1000000; + + min_programmed = (vrr->state == VRR_STATE_ACTIVE_FIXED) ? fixed_refresh : + (vrr->state == VRR_STATE_ACTIVE_VARIABLE) ? min_refresh : + (vrr->state == VRR_STATE_INACTIVE) ? min_refresh : + max_refresh; // Non-fs case, program nominal range + + max_programmed = (vrr->state == VRR_STATE_ACTIVE_FIXED) ? fixed_refresh : + (vrr->state == VRR_STATE_ACTIVE_VARIABLE) ? max_refresh : + max_refresh;// Non-fs case, program nominal range + + /* PB7 = FreeSync Minimum refresh rate (Hz) */ + infopacket->sb[7] = min_programmed & 0xFF; + + /* PB8 = FreeSync Maximum refresh rate (Hz) */ + infopacket->sb[8] = max_programmed & 0xFF; + + /* PB11 : MSB FreeSync Minimum refresh rate [Hz] - bits 9:8 */ + infopacket->sb[11] = (min_programmed >> 8) & 0x03; + + /* PB12 : MSB FreeSync Maximum refresh rate [Hz] - bits 9:8 */ + infopacket->sb[12] = (max_programmed >> 8) & 0x03; + + /* PB16 : Reserved bits 7:1, FixedRate bit 0 */ + infopacket->sb[16] = (vrr->state == VRR_STATE_ACTIVE_FIXED) ? 1 : 0; +} + +static void build_vrr_infopacket_fs2_data(enum color_transfer_func app_tf, + struct dc_info_packet *infopacket) +{ + if (app_tf != TRANSFER_FUNC_UNKNOWN) { + infopacket->valid = true; + + infopacket->sb[6] |= 0x08; // PB6 = [Bit 3 = Native Color Active] + + if (app_tf == TRANSFER_FUNC_GAMMA_22) { + infopacket->sb[9] |= 0x04; // PB6 = [Bit 2 = Gamma 2.2 EOTF Active] + } + } +} + +static void build_vrr_infopacket_header_v1(enum signal_type signal, + struct dc_info_packet *infopacket, + unsigned int *payload_size) +{ + if (dc_is_hdmi_signal(signal)) { + + /* HEADER */ + + /* HB0 = Packet Type = 0x83 (Source Product + * Descriptor InfoFrame) + */ + infopacket->hb0 = DC_HDMI_INFOFRAME_TYPE_SPD; + + /* HB1 = Version = 0x01 */ + infopacket->hb1 = 0x01; + + /* HB2 = [Bits 7:5 = 0] [Bits 4:0 = Length = 0x08] */ + infopacket->hb2 = 0x08; + + *payload_size = 0x08; + + } else if (dc_is_dp_signal(signal)) { + + /* HEADER */ + + /* HB0 = Secondary-data Packet ID = 0 - Only non-zero + * when used to associate audio related info packets + */ + infopacket->hb0 = 0x00; + + /* HB1 = Packet Type = 0x83 (Source Product + * Descriptor InfoFrame) + */ + infopacket->hb1 = DC_HDMI_INFOFRAME_TYPE_SPD; + + /* HB2 = [Bits 7:0 = Least significant eight bits - + * For INFOFRAME, the value must be 1Bh] + */ + infopacket->hb2 = 0x1B; + + /* HB3 = [Bits 7:2 = INFOFRAME SDP Version Number = 0x1] + * [Bits 1:0 = Most significant two bits = 0x00] + */ + infopacket->hb3 = 0x04; + + *payload_size = 0x1B; + } +} + +static void build_vrr_infopacket_header_v2(enum signal_type signal, + struct dc_info_packet *infopacket, + unsigned int *payload_size) +{ + if (dc_is_hdmi_signal(signal)) { + + /* HEADER */ + + /* HB0 = Packet Type = 0x83 (Source Product + * Descriptor InfoFrame) + */ + infopacket->hb0 = DC_HDMI_INFOFRAME_TYPE_SPD; + + /* HB1 = Version = 0x02 */ + infopacket->hb1 = 0x02; + + /* HB2 = [Bits 7:5 = 0] [Bits 4:0 = Length = 0x09] */ + infopacket->hb2 = 0x09; + + *payload_size = 0x09; + } else if (dc_is_dp_signal(signal)) { + + /* HEADER */ + + /* HB0 = Secondary-data Packet ID = 0 - Only non-zero + * when used to associate audio related info packets + */ + infopacket->hb0 = 0x00; + + /* HB1 = Packet Type = 0x83 (Source Product + * Descriptor InfoFrame) + */ + infopacket->hb1 = DC_HDMI_INFOFRAME_TYPE_SPD; + + /* HB2 = [Bits 7:0 = Least significant eight bits - + * For INFOFRAME, the value must be 1Bh] + */ + infopacket->hb2 = 0x1B; + + /* HB3 = [Bits 7:2 = INFOFRAME SDP Version Number = 0x2] + * [Bits 1:0 = Most significant two bits = 0x00] + */ + infopacket->hb3 = 0x08; + + *payload_size = 0x1B; + } +} + +static void build_vrr_infopacket_header_v3(enum signal_type signal, + struct dc_info_packet *infopacket, + unsigned int *payload_size) +{ + unsigned char version; + + version = 3; + if (dc_is_hdmi_signal(signal)) { + + /* HEADER */ + + /* HB0 = Packet Type = 0x83 (Source Product + * Descriptor InfoFrame) + */ + infopacket->hb0 = DC_HDMI_INFOFRAME_TYPE_SPD; + + /* HB1 = Version = 0x03 */ + infopacket->hb1 = version; + + /* HB2 = [Bits 7:5 = 0] [Bits 4:0 = Length] */ + infopacket->hb2 = 0x10; + + *payload_size = 0x10; + } else if (dc_is_dp_signal(signal)) { + + /* HEADER */ + + /* HB0 = Secondary-data Packet ID = 0 - Only non-zero + * when used to associate audio related info packets + */ + infopacket->hb0 = 0x00; + + /* HB1 = Packet Type = 0x83 (Source Product + * Descriptor InfoFrame) + */ + infopacket->hb1 = DC_HDMI_INFOFRAME_TYPE_SPD; + + /* HB2 = [Bits 7:0 = Least significant eight bits - + * For INFOFRAME, the value must be 1Bh] + */ + infopacket->hb2 = 0x1B; + + /* HB3 = [Bits 7:2 = INFOFRAME SDP Version Number = 0x2] + * [Bits 1:0 = Most significant two bits = 0x00] + */ + + infopacket->hb3 = (version & 0x3F) << 2; + + *payload_size = 0x1B; + } +} + +static void build_vrr_infopacket_checksum(unsigned int *payload_size, + struct dc_info_packet *infopacket) +{ + /* Calculate checksum */ + unsigned int idx = 0; + unsigned char checksum = 0; + + checksum += infopacket->hb0; + checksum += infopacket->hb1; + checksum += infopacket->hb2; + checksum += infopacket->hb3; + + for (idx = 1; idx <= *payload_size; idx++) + checksum += infopacket->sb[idx]; + + /* PB0 = Checksum (one byte complement) */ + infopacket->sb[0] = (unsigned char)(0x100 - checksum); + + infopacket->valid = true; +} + +static void build_vrr_infopacket_v1(enum signal_type signal, + const struct mod_vrr_params *vrr, + struct dc_info_packet *infopacket, + bool freesync_on_desktop) +{ + /* SPD info packet for FreeSync */ + unsigned int payload_size = 0; + + build_vrr_infopacket_header_v1(signal, infopacket, &payload_size); + build_vrr_infopacket_data_v1(vrr, infopacket, freesync_on_desktop); + build_vrr_infopacket_checksum(&payload_size, infopacket); + + infopacket->valid = true; +} + +static void build_vrr_infopacket_v2(enum signal_type signal, + const struct mod_vrr_params *vrr, + enum color_transfer_func app_tf, + struct dc_info_packet *infopacket, + bool freesync_on_desktop) +{ + unsigned int payload_size = 0; + + build_vrr_infopacket_header_v2(signal, infopacket, &payload_size); + build_vrr_infopacket_data_v1(vrr, infopacket, freesync_on_desktop); + + build_vrr_infopacket_fs2_data(app_tf, infopacket); + + build_vrr_infopacket_checksum(&payload_size, infopacket); + + infopacket->valid = true; +} +#ifndef TRIM_FSFT +static void build_vrr_infopacket_fast_transport_data( + bool ftActive, + unsigned int ftOutputRate, + struct dc_info_packet *infopacket) +{ + /* PB9 : bit7 - fast transport Active*/ + unsigned char activeBit = (ftActive) ? 1 << 7 : 0; + + infopacket->sb[1] &= ~activeBit; //clear bit + infopacket->sb[1] |= activeBit; //set bit + + /* PB13 : Target Output Pixel Rate [kHz] - bits 7:0 */ + infopacket->sb[13] = ftOutputRate & 0xFF; + + /* PB14 : Target Output Pixel Rate [kHz] - bits 15:8 */ + infopacket->sb[14] = (ftOutputRate >> 8) & 0xFF; + + /* PB15 : Target Output Pixel Rate [kHz] - bits 23:16 */ + infopacket->sb[15] = (ftOutputRate >> 16) & 0xFF; + +} +#endif + +static void build_vrr_infopacket_v3(enum signal_type signal, + const struct mod_vrr_params *vrr, +#ifndef TRIM_FSFT + bool ftActive, unsigned int ftOutputRate, +#endif + enum color_transfer_func app_tf, + struct dc_info_packet *infopacket) +{ + unsigned int payload_size = 0; + + build_vrr_infopacket_header_v3(signal, infopacket, &payload_size); + build_vrr_infopacket_data_v3(vrr, infopacket); + + build_vrr_infopacket_fs2_data(app_tf, infopacket); + +#ifndef TRIM_FSFT + build_vrr_infopacket_fast_transport_data( + ftActive, + ftOutputRate, + infopacket); +#endif + + build_vrr_infopacket_checksum(&payload_size, infopacket); + + infopacket->valid = true; +} + +static void build_vrr_infopacket_sdp_v1_3(enum vrr_packet_type packet_type, + struct dc_info_packet *infopacket) +{ + uint8_t idx = 0, size = 0; + + size = ((packet_type == PACKET_TYPE_FS_V1) ? 0x08 : + (packet_type == PACKET_TYPE_FS_V3) ? 0x10 : + 0x09); + + for (idx = infopacket->hb2; idx > 1; idx--) // Data Byte Count: 0x1B + infopacket->sb[idx] = infopacket->sb[idx-1]; + + infopacket->sb[1] = size; // Length + infopacket->sb[0] = (infopacket->hb3 >> 2) & 0x3F;//Version + infopacket->hb3 = (0x13 << 2); // Header,SDP 1.3 + infopacket->hb2 = 0x1D; +} + +void mod_freesync_build_vrr_infopacket(struct mod_freesync *mod_freesync, + const struct dc_stream_state *stream, + const struct mod_vrr_params *vrr, + enum vrr_packet_type packet_type, + enum color_transfer_func app_tf, + struct dc_info_packet *infopacket, + bool pack_sdp_v1_3) +{ + /* SPD info packet for FreeSync + * VTEM info packet for HdmiVRR + * Check if Freesync is supported. Return if false. If true, + * set the corresponding bit in the info packet + */ + if (!vrr->send_info_frame) + return; + + switch (packet_type) { + case PACKET_TYPE_FS_V3: +#ifndef TRIM_FSFT + // always populate with pixel rate. + build_vrr_infopacket_v3( + stream->signal, vrr, + stream->timing.flags.FAST_TRANSPORT, + (stream->timing.flags.FAST_TRANSPORT) ? + stream->timing.fast_transport_output_rate_100hz : + stream->timing.pix_clk_100hz, + app_tf, infopacket); +#else + build_vrr_infopacket_v3(stream->signal, vrr, app_tf, infopacket); +#endif + break; + case PACKET_TYPE_FS_V2: + build_vrr_infopacket_v2(stream->signal, vrr, app_tf, infopacket, stream->freesync_on_desktop); + break; + case PACKET_TYPE_VRR: + case PACKET_TYPE_FS_V1: + default: + build_vrr_infopacket_v1(stream->signal, vrr, infopacket, stream->freesync_on_desktop); + } + + if (true == pack_sdp_v1_3 && + true == dc_is_dp_signal(stream->signal) && + packet_type != PACKET_TYPE_VRR && + packet_type != PACKET_TYPE_VTEM) + build_vrr_infopacket_sdp_v1_3(packet_type, infopacket); +} + +void mod_freesync_build_vrr_params(struct mod_freesync *mod_freesync, + const struct dc_stream_state *stream, + struct mod_freesync_config *in_config, + struct mod_vrr_params *in_out_vrr) +{ + struct core_freesync *core_freesync = NULL; + unsigned long long nominal_field_rate_in_uhz = 0; + unsigned long long rounded_nominal_in_uhz = 0; + unsigned int refresh_range = 0; + unsigned long long min_refresh_in_uhz = 0; + unsigned long long max_refresh_in_uhz = 0; + + if (mod_freesync == NULL) + return; + + core_freesync = MOD_FREESYNC_TO_CORE(mod_freesync); + + /* Calculate nominal field rate for stream */ + nominal_field_rate_in_uhz = + mod_freesync_calc_nominal_field_rate(stream); + + min_refresh_in_uhz = in_config->min_refresh_in_uhz; + max_refresh_in_uhz = in_config->max_refresh_in_uhz; + + /* Full range may be larger than current video timing, so cap at nominal */ + if (max_refresh_in_uhz > nominal_field_rate_in_uhz) + max_refresh_in_uhz = nominal_field_rate_in_uhz; + + /* Full range may be larger than current video timing, so cap at nominal */ + if (min_refresh_in_uhz > max_refresh_in_uhz) + min_refresh_in_uhz = max_refresh_in_uhz; + + /* If a monitor reports exactly max refresh of 2x of min, enforce it on nominal */ + rounded_nominal_in_uhz = + div_u64(nominal_field_rate_in_uhz + 50000, 100000) * 100000; + if (in_config->max_refresh_in_uhz == (2 * in_config->min_refresh_in_uhz) && + in_config->max_refresh_in_uhz == rounded_nominal_in_uhz) + min_refresh_in_uhz = div_u64(nominal_field_rate_in_uhz, 2); + + if (!vrr_settings_require_update(core_freesync, + in_config, (unsigned int)min_refresh_in_uhz, (unsigned int)max_refresh_in_uhz, + in_out_vrr)) + return; + + in_out_vrr->state = in_config->state; + in_out_vrr->send_info_frame = in_config->vsif_supported; + + if (in_config->state == VRR_STATE_UNSUPPORTED) { + in_out_vrr->state = VRR_STATE_UNSUPPORTED; + in_out_vrr->supported = false; + in_out_vrr->adjust.v_total_min = stream->timing.v_total; + in_out_vrr->adjust.v_total_max = stream->timing.v_total; + + return; + + } else { + in_out_vrr->min_refresh_in_uhz = (unsigned int)min_refresh_in_uhz; + in_out_vrr->max_duration_in_us = + calc_duration_in_us_from_refresh_in_uhz( + (unsigned int)min_refresh_in_uhz); + + in_out_vrr->max_refresh_in_uhz = (unsigned int)max_refresh_in_uhz; + in_out_vrr->min_duration_in_us = + calc_duration_in_us_from_refresh_in_uhz( + (unsigned int)max_refresh_in_uhz); + + if (in_config->state == VRR_STATE_ACTIVE_FIXED) + in_out_vrr->fixed_refresh_in_uhz = in_config->fixed_refresh_in_uhz; + else + in_out_vrr->fixed_refresh_in_uhz = 0; + + refresh_range = div_u64(in_out_vrr->max_refresh_in_uhz + 500000, 1000000) - ++ div_u64(in_out_vrr->min_refresh_in_uhz + 500000, 1000000); + + in_out_vrr->supported = true; + } + + in_out_vrr->fixed.ramping_active = in_config->ramping; + + in_out_vrr->btr.btr_enabled = in_config->btr; + + if (in_out_vrr->max_refresh_in_uhz < (2 * in_out_vrr->min_refresh_in_uhz)) + in_out_vrr->btr.btr_enabled = false; + else { + in_out_vrr->btr.margin_in_us = in_out_vrr->max_duration_in_us - + 2 * in_out_vrr->min_duration_in_us; + if (in_out_vrr->btr.margin_in_us > BTR_MAX_MARGIN) + in_out_vrr->btr.margin_in_us = BTR_MAX_MARGIN; + } + + in_out_vrr->btr.btr_active = false; + in_out_vrr->btr.inserted_duration_in_us = 0; + in_out_vrr->btr.frames_to_insert = 0; + in_out_vrr->btr.frame_counter = 0; + in_out_vrr->fixed.fixed_active = false; + in_out_vrr->fixed.target_refresh_in_uhz = 0; + + in_out_vrr->btr.mid_point_in_us = + (in_out_vrr->min_duration_in_us + + in_out_vrr->max_duration_in_us) / 2; + + if (in_out_vrr->state == VRR_STATE_UNSUPPORTED) { + in_out_vrr->adjust.v_total_min = stream->timing.v_total; + in_out_vrr->adjust.v_total_max = stream->timing.v_total; + } else if (in_out_vrr->state == VRR_STATE_DISABLED) { + in_out_vrr->adjust.v_total_min = stream->timing.v_total; + in_out_vrr->adjust.v_total_max = stream->timing.v_total; + } else if (in_out_vrr->state == VRR_STATE_INACTIVE) { + in_out_vrr->adjust.v_total_min = stream->timing.v_total; + in_out_vrr->adjust.v_total_max = stream->timing.v_total; + } else if (in_out_vrr->state == VRR_STATE_ACTIVE_VARIABLE && + refresh_range >= MIN_REFRESH_RANGE) { + + in_out_vrr->adjust.v_total_min = + mod_freesync_calc_v_total_from_refresh(stream, + in_out_vrr->max_refresh_in_uhz); + in_out_vrr->adjust.v_total_max = + mod_freesync_calc_v_total_from_refresh(stream, + in_out_vrr->min_refresh_in_uhz); + } else if (in_out_vrr->state == VRR_STATE_ACTIVE_FIXED) { + in_out_vrr->fixed.target_refresh_in_uhz = + in_out_vrr->fixed_refresh_in_uhz; + if (in_out_vrr->fixed.ramping_active && + in_out_vrr->fixed.fixed_active) { + /* Do not update vtotals if ramping is already active + * in order to continue ramp from current refresh. + */ + in_out_vrr->fixed.fixed_active = true; + } else { + in_out_vrr->fixed.fixed_active = true; + in_out_vrr->adjust.v_total_min = + mod_freesync_calc_v_total_from_refresh(stream, + in_out_vrr->fixed.target_refresh_in_uhz); + in_out_vrr->adjust.v_total_max = + in_out_vrr->adjust.v_total_min; + } + } else { + in_out_vrr->state = VRR_STATE_INACTIVE; + in_out_vrr->adjust.v_total_min = stream->timing.v_total; + in_out_vrr->adjust.v_total_max = stream->timing.v_total; + } +} + +void mod_freesync_handle_preflip(struct mod_freesync *mod_freesync, + const struct dc_plane_state *plane, + const struct dc_stream_state *stream, + unsigned int curr_time_stamp_in_us, + struct mod_vrr_params *in_out_vrr) +{ + struct core_freesync *core_freesync = NULL; + unsigned int last_render_time_in_us = 0; + unsigned int average_render_time_in_us = 0; + + if (mod_freesync == NULL) + return; + + core_freesync = MOD_FREESYNC_TO_CORE(mod_freesync); + + if (in_out_vrr->supported && + in_out_vrr->state == VRR_STATE_ACTIVE_VARIABLE) { + unsigned int i = 0; + unsigned int oldest_index = plane->time.index + 1; + + if (oldest_index >= DC_PLANE_UPDATE_TIMES_MAX) + oldest_index = 0; + + last_render_time_in_us = curr_time_stamp_in_us - + plane->time.prev_update_time_in_us; + + /* Sum off all entries except oldest one */ + for (i = 0; i < DC_PLANE_UPDATE_TIMES_MAX; i++) { + average_render_time_in_us += + plane->time.time_elapsed_in_us[i]; + } + average_render_time_in_us -= + plane->time.time_elapsed_in_us[oldest_index]; + + /* Add render time for current flip */ + average_render_time_in_us += last_render_time_in_us; + average_render_time_in_us /= DC_PLANE_UPDATE_TIMES_MAX; + + if (in_out_vrr->btr.btr_enabled) { + apply_below_the_range(core_freesync, + stream, + last_render_time_in_us, + in_out_vrr); + } else { + apply_fixed_refresh(core_freesync, + stream, + last_render_time_in_us, + in_out_vrr); + } + + determine_flip_interval_workaround_req(in_out_vrr, + curr_time_stamp_in_us); + + } +} + +void mod_freesync_handle_v_update(struct mod_freesync *mod_freesync, + const struct dc_stream_state *stream, + struct mod_vrr_params *in_out_vrr) +{ + struct core_freesync *core_freesync = NULL; + unsigned int cur_timestamp_in_us; + unsigned long long cur_tick; + + if ((mod_freesync == NULL) || (stream == NULL) || (in_out_vrr == NULL)) + return; + + core_freesync = MOD_FREESYNC_TO_CORE(mod_freesync); + + if (in_out_vrr->supported == false) + return; + + cur_tick = dm_get_timestamp(core_freesync->dc->ctx); + cur_timestamp_in_us = (unsigned int) + div_u64(dm_get_elapse_time_in_ns(core_freesync->dc->ctx, cur_tick, 0), 1000); + + in_out_vrr->flip_interval.vsyncs_between_flip++; + in_out_vrr->flip_interval.v_update_timestamp_in_us = cur_timestamp_in_us; + + if (in_out_vrr->state == VRR_STATE_ACTIVE_VARIABLE && + (in_out_vrr->flip_interval.flip_interval_workaround_active || + (!in_out_vrr->flip_interval.flip_interval_workaround_active && + in_out_vrr->flip_interval.program_flip_interval_workaround))) { + // set freesync vmin vmax to nominal for workaround + in_out_vrr->adjust.v_total_min = + mod_freesync_calc_v_total_from_refresh( + stream, in_out_vrr->max_refresh_in_uhz); + in_out_vrr->adjust.v_total_max = + in_out_vrr->adjust.v_total_min; + in_out_vrr->flip_interval.program_flip_interval_workaround = false; + in_out_vrr->flip_interval.do_flip_interval_workaround_cleanup = true; + return; + } + + if (in_out_vrr->state != VRR_STATE_ACTIVE_VARIABLE && + in_out_vrr->flip_interval.do_flip_interval_workaround_cleanup) { + in_out_vrr->flip_interval.do_flip_interval_workaround_cleanup = false; + in_out_vrr->flip_interval.flip_interval_detect_counter = 0; + in_out_vrr->flip_interval.vsyncs_between_flip = 0; + in_out_vrr->flip_interval.vsync_to_flip_in_us = 0; + } + + /* Below the Range Logic */ + + /* Only execute if in fullscreen mode */ + if (in_out_vrr->state == VRR_STATE_ACTIVE_VARIABLE && + in_out_vrr->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 (in_out_vrr->btr.frames_to_insert == + in_out_vrr->btr.frame_counter) { + in_out_vrr->adjust.v_total_min = + calc_v_total_from_duration(stream, + in_out_vrr, + in_out_vrr->btr.inserted_duration_in_us); + in_out_vrr->adjust.v_total_max = + in_out_vrr->adjust.v_total_min; + } + + if (in_out_vrr->btr.frame_counter > 0) + in_out_vrr->btr.frame_counter--; + + /* Restore FreeSync */ + if (in_out_vrr->btr.frame_counter == 0) { + in_out_vrr->adjust.v_total_min = + mod_freesync_calc_v_total_from_refresh(stream, + in_out_vrr->max_refresh_in_uhz); + in_out_vrr->adjust.v_total_max = + mod_freesync_calc_v_total_from_refresh(stream, + in_out_vrr->min_refresh_in_uhz); + } + } + + /* If in fullscreen freesync mode or in video, do not program + * static screen ramp values + */ + if (in_out_vrr->state == VRR_STATE_ACTIVE_VARIABLE) + in_out_vrr->fixed.ramping_active = false; + + /* Gradual Static Screen Ramping Logic + * Execute if ramp is active and user enabled freesync static screen + */ + if (in_out_vrr->state == VRR_STATE_ACTIVE_FIXED && + in_out_vrr->fixed.ramping_active) { + update_v_total_for_static_ramp( + core_freesync, stream, in_out_vrr); + } +} + +void mod_freesync_get_settings(struct mod_freesync *mod_freesync, + const struct mod_vrr_params *vrr, + 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) +{ + if (mod_freesync == NULL) + return; + + if (vrr->supported) { + *v_total_min = vrr->adjust.v_total_min; + *v_total_max = vrr->adjust.v_total_max; + *event_triggers = 0; + *lfc_mid_point_in_us = vrr->btr.mid_point_in_us; + *inserted_frames = vrr->btr.frames_to_insert; + *inserted_duration_in_us = vrr->btr.inserted_duration_in_us; + } +} + +unsigned long long mod_freesync_calc_nominal_field_rate( + const struct dc_stream_state *stream) +{ + unsigned long long nominal_field_rate_in_uhz = 0; + unsigned int total = stream->timing.h_total * stream->timing.v_total; + + /* Calculate nominal field rate for stream, rounded up to nearest integer */ + nominal_field_rate_in_uhz = stream->timing.pix_clk_100hz; + nominal_field_rate_in_uhz *= 100000000ULL; + + nominal_field_rate_in_uhz = div_u64(nominal_field_rate_in_uhz, total); + + return nominal_field_rate_in_uhz; +} + +unsigned long long mod_freesync_calc_field_rate_from_timing( + unsigned int vtotal, unsigned int htotal, unsigned int pix_clk) +{ + unsigned long long field_rate_in_uhz = 0; + unsigned int total = htotal * vtotal; + + /* Calculate nominal field rate for stream, rounded up to nearest integer */ + field_rate_in_uhz = pix_clk; + field_rate_in_uhz *= 1000000ULL; + + field_rate_in_uhz = div_u64(field_rate_in_uhz, total); + + return field_rate_in_uhz; +} + +bool mod_freesync_get_freesync_enabled(struct mod_vrr_params *pVrr) +{ + return (pVrr->state != VRR_STATE_UNSUPPORTED) && (pVrr->state != VRR_STATE_DISABLED); +} + +bool mod_freesync_is_valid_range(uint32_t min_refresh_cap_in_uhz, + uint32_t max_refresh_cap_in_uhz, + uint32_t nominal_field_rate_in_uhz) +{ + + /* Typically nominal refresh calculated can have some fractional part. + * Allow for some rounding error of actual video timing by taking floor + * of caps and request. Round the nominal refresh rate. + * + * Dividing will convert everything to units in Hz although input + * variable name is in uHz! + * + * Also note, this takes care of rounding error on the nominal refresh + * so by rounding error we only expect it to be off by a small amount, + * such as < 0.1 Hz. i.e. 143.9xxx or 144.1xxx. + * + * Example 1. Caps Min = 40 Hz, Max = 144 Hz + * Request Min = 40 Hz, Max = 144 Hz + * Nominal = 143.5x Hz rounded to 144 Hz + * This function should allow this as valid request + * + * Example 2. Caps Min = 40 Hz, Max = 144 Hz + * Request Min = 40 Hz, Max = 144 Hz + * Nominal = 144.4x Hz rounded to 144 Hz + * This function should allow this as valid request + * + * Example 3. Caps Min = 40 Hz, Max = 144 Hz + * Request Min = 40 Hz, Max = 144 Hz + * Nominal = 120.xx Hz rounded to 120 Hz + * This function should return NOT valid since the requested + * max is greater than current timing's nominal + * + * Example 4. Caps Min = 40 Hz, Max = 120 Hz + * Request Min = 40 Hz, Max = 120 Hz + * Nominal = 144.xx Hz rounded to 144 Hz + * This function should return NOT valid since the nominal + * is greater than the capability's max refresh + */ + nominal_field_rate_in_uhz = + div_u64(nominal_field_rate_in_uhz + 500000, 1000000); + min_refresh_cap_in_uhz /= 1000000; + max_refresh_cap_in_uhz /= 1000000; + + /* Check nominal is within range */ + if (nominal_field_rate_in_uhz > max_refresh_cap_in_uhz || + nominal_field_rate_in_uhz < min_refresh_cap_in_uhz) + return false; + + /* If nominal is less than max, limit the max allowed refresh rate */ + if (nominal_field_rate_in_uhz < max_refresh_cap_in_uhz) + max_refresh_cap_in_uhz = nominal_field_rate_in_uhz; + + /* Check min is within range */ + if (min_refresh_cap_in_uhz > max_refresh_cap_in_uhz) + return false; + + /* For variable range, check for at least 10 Hz range */ + if (nominal_field_rate_in_uhz - min_refresh_cap_in_uhz < 10) + return false; + + return true; +} diff --git a/drivers/gpu/drm/amd/display/modules/hdcp/Makefile b/drivers/gpu/drm/amd/display/modules/hdcp/Makefile new file mode 100644 index 000000000..904424da0 --- /dev/null +++ b/drivers/gpu/drm/amd/display/modules/hdcp/Makefile @@ -0,0 +1,33 @@ +# +# Copyright 2019 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 'hdcp' sub-module of DAL. +# + +HDCP = hdcp_ddc.o hdcp_log.o hdcp_psp.o hdcp.o \ + hdcp1_execution.o hdcp1_transition.o \ + hdcp2_execution.o hdcp2_transition.o + +AMD_DAL_HDCP = $(addprefix $(AMDDALPATH)/modules/hdcp/,$(HDCP)) +#$(info ************ DAL-HDCP_MAKEFILE ************) + +AMD_DISPLAY_FILES += $(AMD_DAL_HDCP) diff --git a/drivers/gpu/drm/amd/display/modules/hdcp/hdcp.c b/drivers/gpu/drm/amd/display/modules/hdcp/hdcp.c new file mode 100644 index 000000000..5e01c6e24 --- /dev/null +++ b/drivers/gpu/drm/amd/display/modules/hdcp/hdcp.c @@ -0,0 +1,598 @@ +/* + * Copyright 2019 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 "hdcp.h" + +static void push_error_status(struct mod_hdcp *hdcp, + enum mod_hdcp_status status) +{ + struct mod_hdcp_trace *trace = &hdcp->connection.trace; + + if (trace->error_count < MAX_NUM_OF_ERROR_TRACE) { + trace->errors[trace->error_count].status = status; + trace->errors[trace->error_count].state_id = hdcp->state.id; + trace->error_count++; + HDCP_ERROR_TRACE(hdcp, status); + } + + if (is_hdcp1(hdcp)) { + hdcp->connection.hdcp1_retry_count++; + if (hdcp->connection.hdcp1_retry_count == MAX_NUM_OF_ATTEMPTS) + hdcp->connection.link.adjust.hdcp1.disable = 1; + } else if (is_hdcp2(hdcp)) { + hdcp->connection.hdcp2_retry_count++; + if (hdcp->connection.hdcp2_retry_count == MAX_NUM_OF_ATTEMPTS) + hdcp->connection.link.adjust.hdcp2.disable = 1; + } +} + +static uint8_t is_cp_desired_hdcp1(struct mod_hdcp *hdcp) +{ + int i, is_auth_needed = 0; + + /* if all displays on the link don't need authentication, + * hdcp is not desired + */ + for (i = 0; i < MAX_NUM_OF_DISPLAYS; i++) { + if (hdcp->displays[i].state != MOD_HDCP_DISPLAY_INACTIVE && + hdcp->displays[i].adjust.disable != MOD_HDCP_DISPLAY_DISABLE_AUTHENTICATION) { + is_auth_needed = 1; + break; + } + } + + return is_auth_needed && + !hdcp->connection.link.adjust.hdcp1.disable && + !hdcp->connection.is_hdcp1_revoked; +} + +static uint8_t is_cp_desired_hdcp2(struct mod_hdcp *hdcp) +{ + int i, is_auth_needed = 0; + + /* if all displays on the link don't need authentication, + * hdcp is not desired + */ + for (i = 0; i < MAX_NUM_OF_DISPLAYS; i++) { + if (hdcp->displays[i].state != MOD_HDCP_DISPLAY_INACTIVE && + hdcp->displays[i].adjust.disable != MOD_HDCP_DISPLAY_DISABLE_AUTHENTICATION) { + is_auth_needed = 1; + break; + } + } + + return is_auth_needed && + !hdcp->connection.link.adjust.hdcp2.disable && + !hdcp->connection.is_hdcp2_revoked; +} + +static enum mod_hdcp_status execution(struct mod_hdcp *hdcp, + struct mod_hdcp_event_context *event_ctx, + union mod_hdcp_transition_input *input) +{ + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + if (is_in_initialized_state(hdcp)) { + if (event_ctx->event != MOD_HDCP_EVENT_CALLBACK) { + event_ctx->unexpected_event = 1; + goto out; + } + /* initialize transition input */ + memset(input, 0, sizeof(union mod_hdcp_transition_input)); + } else if (is_in_cp_not_desired_state(hdcp)) { + if (event_ctx->event != MOD_HDCP_EVENT_CALLBACK) { + event_ctx->unexpected_event = 1; + goto out; + } + } else if (is_in_hdcp1_states(hdcp)) { + status = mod_hdcp_hdcp1_execution(hdcp, event_ctx, &input->hdcp1); + } else if (is_in_hdcp1_dp_states(hdcp)) { + status = mod_hdcp_hdcp1_dp_execution(hdcp, + event_ctx, &input->hdcp1); + } else if (is_in_hdcp2_states(hdcp)) { + status = mod_hdcp_hdcp2_execution(hdcp, event_ctx, &input->hdcp2); + } else if (is_in_hdcp2_dp_states(hdcp)) { + status = mod_hdcp_hdcp2_dp_execution(hdcp, + event_ctx, &input->hdcp2); + } else { + event_ctx->unexpected_event = 1; + goto out; + } +out: + return status; +} + +static enum mod_hdcp_status transition(struct mod_hdcp *hdcp, + struct mod_hdcp_event_context *event_ctx, + union mod_hdcp_transition_input *input, + struct mod_hdcp_output *output) +{ + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + if (event_ctx->unexpected_event) + goto out; + + if (is_in_initialized_state(hdcp)) { + if (is_dp_hdcp(hdcp)) + if (is_cp_desired_hdcp2(hdcp)) { + callback_in_ms(0, output); + set_state_id(hdcp, output, D2_A0_DETERMINE_RX_HDCP_CAPABLE); + } else if (is_cp_desired_hdcp1(hdcp)) { + callback_in_ms(0, output); + set_state_id(hdcp, output, D1_A0_DETERMINE_RX_HDCP_CAPABLE); + } else { + callback_in_ms(0, output); + set_state_id(hdcp, output, HDCP_CP_NOT_DESIRED); + set_auth_complete(hdcp, output); + } + else if (is_hdmi_dvi_sl_hdcp(hdcp)) + if (is_cp_desired_hdcp2(hdcp)) { + callback_in_ms(0, output); + set_state_id(hdcp, output, H2_A0_KNOWN_HDCP2_CAPABLE_RX); + } else if (is_cp_desired_hdcp1(hdcp)) { + callback_in_ms(0, output); + set_state_id(hdcp, output, H1_A0_WAIT_FOR_ACTIVE_RX); + } else { + callback_in_ms(0, output); + set_state_id(hdcp, output, HDCP_CP_NOT_DESIRED); + set_auth_complete(hdcp, output); + } + else { + callback_in_ms(0, output); + set_state_id(hdcp, output, HDCP_CP_NOT_DESIRED); + set_auth_complete(hdcp, output); + } + } else if (is_in_cp_not_desired_state(hdcp)) { + increment_stay_counter(hdcp); + } else if (is_in_hdcp1_states(hdcp)) { + status = mod_hdcp_hdcp1_transition(hdcp, + event_ctx, &input->hdcp1, output); + } else if (is_in_hdcp1_dp_states(hdcp)) { + status = mod_hdcp_hdcp1_dp_transition(hdcp, + event_ctx, &input->hdcp1, output); + } else if (is_in_hdcp2_states(hdcp)) { + status = mod_hdcp_hdcp2_transition(hdcp, + event_ctx, &input->hdcp2, output); + } else if (is_in_hdcp2_dp_states(hdcp)) { + status = mod_hdcp_hdcp2_dp_transition(hdcp, + event_ctx, &input->hdcp2, output); + } else { + status = MOD_HDCP_STATUS_INVALID_STATE; + } +out: + return status; +} + +static enum mod_hdcp_status reset_authentication(struct mod_hdcp *hdcp, + struct mod_hdcp_output *output) +{ + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + if (is_hdcp1(hdcp)) { + if (hdcp->auth.trans_input.hdcp1.create_session != UNKNOWN) { + /* TODO - update psp to unify create session failure + * recovery between hdcp1 and 2. + */ + mod_hdcp_hdcp1_destroy_session(hdcp); + + } + + HDCP_TOP_RESET_AUTH_TRACE(hdcp); + memset(&hdcp->auth, 0, sizeof(struct mod_hdcp_authentication)); + memset(&hdcp->state, 0, sizeof(struct mod_hdcp_state)); + set_state_id(hdcp, output, HDCP_INITIALIZED); + } else if (is_hdcp2(hdcp)) { + if (hdcp->auth.trans_input.hdcp2.create_session == PASS) { + status = mod_hdcp_hdcp2_destroy_session(hdcp); + if (status != MOD_HDCP_STATUS_SUCCESS) { + output->callback_needed = 0; + output->watchdog_timer_needed = 0; + goto out; + } + } + + HDCP_TOP_RESET_AUTH_TRACE(hdcp); + memset(&hdcp->auth, 0, sizeof(struct mod_hdcp_authentication)); + memset(&hdcp->state, 0, sizeof(struct mod_hdcp_state)); + set_state_id(hdcp, output, HDCP_INITIALIZED); + } else if (is_in_cp_not_desired_state(hdcp)) { + HDCP_TOP_RESET_AUTH_TRACE(hdcp); + memset(&hdcp->auth, 0, sizeof(struct mod_hdcp_authentication)); + memset(&hdcp->state, 0, sizeof(struct mod_hdcp_state)); + set_state_id(hdcp, output, HDCP_INITIALIZED); + } + +out: + /* stop callback and watchdog requests from previous authentication*/ + output->watchdog_timer_stop = 1; + output->callback_stop = 1; + return status; +} + +static enum mod_hdcp_status reset_connection(struct mod_hdcp *hdcp, + struct mod_hdcp_output *output) +{ + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + memset(output, 0, sizeof(struct mod_hdcp_output)); + + status = reset_authentication(hdcp, output); + if (status != MOD_HDCP_STATUS_SUCCESS) + goto out; + + if (current_state(hdcp) != HDCP_UNINITIALIZED) { + HDCP_TOP_RESET_CONN_TRACE(hdcp); + set_state_id(hdcp, output, HDCP_UNINITIALIZED); + } + memset(&hdcp->connection, 0, sizeof(hdcp->connection)); +out: + return status; +} + +static enum mod_hdcp_status update_display_adjustments(struct mod_hdcp *hdcp, + struct mod_hdcp_display *display, + struct mod_hdcp_display_adjustment *adj) +{ + enum mod_hdcp_status status = MOD_HDCP_STATUS_NOT_IMPLEMENTED; + + if (is_in_authenticated_states(hdcp) && + is_dp_mst_hdcp(hdcp) && + display->adjust.disable == true && + adj->disable == false) { + display->adjust.disable = false; + if (is_hdcp1(hdcp)) + status = mod_hdcp_hdcp1_enable_dp_stream_encryption(hdcp); + else if (is_hdcp2(hdcp)) + status = mod_hdcp_hdcp2_enable_dp_stream_encryption(hdcp); + + if (status != MOD_HDCP_STATUS_SUCCESS) + display->adjust.disable = true; + } + + if (status == MOD_HDCP_STATUS_SUCCESS && + memcmp(adj, &display->adjust, + sizeof(struct mod_hdcp_display_adjustment)) != 0) + status = MOD_HDCP_STATUS_NOT_IMPLEMENTED; + + return status; +} +/* + * Implementation of functions in mod_hdcp.h + */ +size_t mod_hdcp_get_memory_size(void) +{ + return sizeof(struct mod_hdcp); +} + +enum mod_hdcp_status mod_hdcp_setup(struct mod_hdcp *hdcp, + struct mod_hdcp_config *config) +{ + struct mod_hdcp_output output; + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + memset(&output, 0, sizeof(output)); + hdcp->config = *config; + HDCP_TOP_INTERFACE_TRACE(hdcp); + status = reset_connection(hdcp, &output); + if (status != MOD_HDCP_STATUS_SUCCESS) + push_error_status(hdcp, status); + return status; +} + +enum mod_hdcp_status mod_hdcp_teardown(struct mod_hdcp *hdcp) +{ + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + struct mod_hdcp_output output; + + HDCP_TOP_INTERFACE_TRACE(hdcp); + memset(&output, 0, sizeof(output)); + status = reset_connection(hdcp, &output); + if (status == MOD_HDCP_STATUS_SUCCESS) + memset(hdcp, 0, sizeof(struct mod_hdcp)); + else + push_error_status(hdcp, status); + return status; +} + +enum mod_hdcp_status mod_hdcp_add_display(struct mod_hdcp *hdcp, + struct mod_hdcp_link *link, struct mod_hdcp_display *display, + struct mod_hdcp_output *output) +{ + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + struct mod_hdcp_display *display_container = NULL; + + HDCP_TOP_INTERFACE_TRACE_WITH_INDEX(hdcp, display->index); + memset(output, 0, sizeof(struct mod_hdcp_output)); + + /* skip inactive display */ + if (display->state != MOD_HDCP_DISPLAY_ACTIVE) { + status = MOD_HDCP_STATUS_SUCCESS; + goto out; + } + + /* check existing display container */ + if (get_active_display_at_index(hdcp, display->index)) { + status = MOD_HDCP_STATUS_SUCCESS; + goto out; + } + + /* find an empty display container */ + display_container = get_empty_display_container(hdcp); + if (!display_container) { + status = MOD_HDCP_STATUS_DISPLAY_OUT_OF_BOUND; + goto out; + } + + /* reset existing authentication status */ + status = reset_authentication(hdcp, output); + if (status != MOD_HDCP_STATUS_SUCCESS) + goto out; + + /* reset retry counters */ + reset_retry_counts(hdcp); + + /* reset error trace */ + memset(&hdcp->connection.trace, 0, sizeof(hdcp->connection.trace)); + + /* add display to connection */ + hdcp->connection.link = *link; + *display_container = *display; + status = mod_hdcp_add_display_to_topology(hdcp, display_container); + + if (status != MOD_HDCP_STATUS_SUCCESS) + goto out; + + /* request authentication */ + if (current_state(hdcp) != HDCP_INITIALIZED) + set_state_id(hdcp, output, HDCP_INITIALIZED); + callback_in_ms(hdcp->connection.link.adjust.auth_delay * 1000, output); +out: + if (status != MOD_HDCP_STATUS_SUCCESS) + push_error_status(hdcp, status); + + return status; +} + +enum mod_hdcp_status mod_hdcp_remove_display(struct mod_hdcp *hdcp, + uint8_t index, struct mod_hdcp_output *output) +{ + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + struct mod_hdcp_display *display = NULL; + + HDCP_TOP_INTERFACE_TRACE_WITH_INDEX(hdcp, index); + memset(output, 0, sizeof(struct mod_hdcp_output)); + + /* find display in connection */ + display = get_active_display_at_index(hdcp, index); + if (!display) { + status = MOD_HDCP_STATUS_SUCCESS; + goto out; + } + + /* stop current authentication */ + status = reset_authentication(hdcp, output); + if (status != MOD_HDCP_STATUS_SUCCESS) + goto out; + + /* clear retry counters */ + reset_retry_counts(hdcp); + + /* reset error trace */ + memset(&hdcp->connection.trace, 0, sizeof(hdcp->connection.trace)); + + /* remove display */ + status = mod_hdcp_remove_display_from_topology(hdcp, index); + if (status != MOD_HDCP_STATUS_SUCCESS) + goto out; + memset(display, 0, sizeof(struct mod_hdcp_display)); + + /* request authentication when connection is not reset */ + if (current_state(hdcp) != HDCP_UNINITIALIZED) + callback_in_ms(hdcp->connection.link.adjust.auth_delay * 1000, + output); +out: + if (status != MOD_HDCP_STATUS_SUCCESS) + push_error_status(hdcp, status); + return status; +} + +enum mod_hdcp_status mod_hdcp_update_display(struct mod_hdcp *hdcp, + uint8_t index, + struct mod_hdcp_link_adjustment *link_adjust, + struct mod_hdcp_display_adjustment *display_adjust, + struct mod_hdcp_output *output) +{ + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + struct mod_hdcp_display *display = NULL; + + HDCP_TOP_INTERFACE_TRACE_WITH_INDEX(hdcp, index); + memset(output, 0, sizeof(struct mod_hdcp_output)); + + /* find display in connection */ + display = get_active_display_at_index(hdcp, index); + if (!display) { + status = MOD_HDCP_STATUS_DISPLAY_NOT_FOUND; + goto out; + } + + /* skip if no changes */ + if (memcmp(link_adjust, &hdcp->connection.link.adjust, + sizeof(struct mod_hdcp_link_adjustment)) == 0 && + memcmp(display_adjust, &display->adjust, + sizeof(struct mod_hdcp_display_adjustment)) == 0) { + status = MOD_HDCP_STATUS_SUCCESS; + goto out; + } + + if (memcmp(link_adjust, &hdcp->connection.link.adjust, + sizeof(struct mod_hdcp_link_adjustment)) == 0 && + memcmp(display_adjust, &display->adjust, + sizeof(struct mod_hdcp_display_adjustment)) != 0) { + status = update_display_adjustments(hdcp, display, display_adjust); + if (status != MOD_HDCP_STATUS_NOT_IMPLEMENTED) + goto out; + } + + /* stop current authentication */ + status = reset_authentication(hdcp, output); + if (status != MOD_HDCP_STATUS_SUCCESS) + goto out; + + /* clear retry counters */ + reset_retry_counts(hdcp); + + /* reset error trace */ + memset(&hdcp->connection.trace, 0, sizeof(hdcp->connection.trace)); + + /* set new adjustment */ + hdcp->connection.link.adjust = *link_adjust; + display->adjust = *display_adjust; + + /* request authentication when connection is not reset */ + if (current_state(hdcp) != HDCP_UNINITIALIZED) + /* wait 100ms to debounce simultaneous updates for different indices */ + callback_in_ms(100, output); + +out: + if (status != MOD_HDCP_STATUS_SUCCESS) + push_error_status(hdcp, status); + return status; +} + +enum mod_hdcp_status mod_hdcp_query_display(struct mod_hdcp *hdcp, + uint8_t index, struct mod_hdcp_display_query *query) +{ + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + struct mod_hdcp_display *display = NULL; + + /* find display in connection */ + display = get_active_display_at_index(hdcp, index); + if (!display) { + status = MOD_HDCP_STATUS_DISPLAY_NOT_FOUND; + goto out; + } + + /* populate query */ + query->link = &hdcp->connection.link; + query->display = display; + query->trace = &hdcp->connection.trace; + query->encryption_status = MOD_HDCP_ENCRYPTION_STATUS_HDCP_OFF; + + if (is_display_encryption_enabled(display)) { + if (is_hdcp1(hdcp)) { + query->encryption_status = MOD_HDCP_ENCRYPTION_STATUS_HDCP1_ON; + } else if (is_hdcp2(hdcp)) { + if (query->link->adjust.hdcp2.force_type == MOD_HDCP_FORCE_TYPE_0) + query->encryption_status = MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE0_ON; + else if (query->link->adjust.hdcp2.force_type == MOD_HDCP_FORCE_TYPE_1) + query->encryption_status = MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON; + else + query->encryption_status = MOD_HDCP_ENCRYPTION_STATUS_HDCP2_ON; + } + } else { + query->encryption_status = MOD_HDCP_ENCRYPTION_STATUS_HDCP_OFF; + } + +out: + return status; +} + +enum mod_hdcp_status mod_hdcp_reset_connection(struct mod_hdcp *hdcp, + struct mod_hdcp_output *output) +{ + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + HDCP_TOP_INTERFACE_TRACE(hdcp); + status = reset_connection(hdcp, output); + if (status != MOD_HDCP_STATUS_SUCCESS) + push_error_status(hdcp, status); + + return status; +} + +enum mod_hdcp_status mod_hdcp_process_event(struct mod_hdcp *hdcp, + enum mod_hdcp_event event, struct mod_hdcp_output *output) +{ + enum mod_hdcp_status exec_status, trans_status, reset_status, status; + struct mod_hdcp_event_context event_ctx; + + HDCP_EVENT_TRACE(hdcp, event); + memset(output, 0, sizeof(struct mod_hdcp_output)); + memset(&event_ctx, 0, sizeof(struct mod_hdcp_event_context)); + event_ctx.event = event; + + /* execute and transition */ + exec_status = execution(hdcp, &event_ctx, &hdcp->auth.trans_input); + trans_status = transition( + hdcp, &event_ctx, &hdcp->auth.trans_input, output); + if (trans_status == MOD_HDCP_STATUS_SUCCESS) { + status = MOD_HDCP_STATUS_SUCCESS; + } else if (exec_status == MOD_HDCP_STATUS_SUCCESS) { + status = MOD_HDCP_STATUS_INTERNAL_POLICY_FAILURE; + push_error_status(hdcp, status); + } else { + status = exec_status; + push_error_status(hdcp, status); + } + + /* reset authentication if needed */ + if (trans_status == MOD_HDCP_STATUS_RESET_NEEDED) { + mod_hdcp_log_ddc_trace(hdcp); + reset_status = reset_authentication(hdcp, output); + if (reset_status != MOD_HDCP_STATUS_SUCCESS) + push_error_status(hdcp, reset_status); + } + + /* Clear CP_IRQ status if needed */ + if (event_ctx.event == MOD_HDCP_EVENT_CPIRQ) { + status = mod_hdcp_clear_cp_irq_status(hdcp); + if (status != MOD_HDCP_STATUS_SUCCESS) + push_error_status(hdcp, status); + } + + return status; +} + +enum mod_hdcp_operation_mode mod_hdcp_signal_type_to_operation_mode( + enum signal_type signal) +{ + enum mod_hdcp_operation_mode mode = MOD_HDCP_MODE_OFF; + + switch (signal) { + case SIGNAL_TYPE_DVI_SINGLE_LINK: + case SIGNAL_TYPE_HDMI_TYPE_A: + mode = MOD_HDCP_MODE_DEFAULT; + break; + case SIGNAL_TYPE_EDP: + case SIGNAL_TYPE_DISPLAY_PORT: + case SIGNAL_TYPE_DISPLAY_PORT_MST: + mode = MOD_HDCP_MODE_DP; + break; + default: + break; + } + + return mode; +} diff --git a/drivers/gpu/drm/amd/display/modules/hdcp/hdcp.h b/drivers/gpu/drm/amd/display/modules/hdcp/hdcp.h new file mode 100644 index 000000000..55c7d8731 --- /dev/null +++ b/drivers/gpu/drm/amd/display/modules/hdcp/hdcp.h @@ -0,0 +1,584 @@ +/* + * Copyright 2019 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 HDCP_H_ +#define HDCP_H_ + +#include "mod_hdcp.h" +#include "hdcp_log.h" + +#include +#include + +enum mod_hdcp_trans_input_result { + UNKNOWN = 0, + PASS, + FAIL +}; + +struct mod_hdcp_transition_input_hdcp1 { + uint8_t bksv_read; + uint8_t bksv_validation; + uint8_t create_session; + uint8_t an_write; + uint8_t aksv_write; + uint8_t ainfo_write; + uint8_t bcaps_read; + uint8_t r0p_read; + uint8_t rx_validation; + uint8_t encryption; + uint8_t link_maintenance; + uint8_t ready_check; + uint8_t bstatus_read; + uint8_t max_cascade_check; + uint8_t max_devs_check; + uint8_t device_count_check; + uint8_t ksvlist_read; + uint8_t vp_read; + uint8_t ksvlist_vp_validation; + + uint8_t hdcp_capable_dp; + uint8_t binfo_read_dp; + uint8_t r0p_available_dp; + uint8_t link_integrity_check; + uint8_t reauth_request_check; + uint8_t stream_encryption_dp; +}; + +struct mod_hdcp_transition_input_hdcp2 { + uint8_t hdcp2version_read; + uint8_t hdcp2_capable_check; + uint8_t create_session; + uint8_t ake_init_prepare; + uint8_t ake_init_write; + uint8_t rxstatus_read; + uint8_t ake_cert_available; + uint8_t ake_cert_read; + uint8_t ake_cert_validation; + uint8_t stored_km_write; + uint8_t no_stored_km_write; + uint8_t h_prime_available; + uint8_t h_prime_read; + uint8_t pairing_available; + uint8_t pairing_info_read; + uint8_t h_prime_validation; + uint8_t lc_init_prepare; + uint8_t lc_init_write; + uint8_t l_prime_available_poll; + uint8_t l_prime_read; + uint8_t l_prime_validation; + uint8_t eks_prepare; + uint8_t eks_write; + uint8_t enable_encryption; + uint8_t reauth_request_check; + uint8_t rx_id_list_read; + uint8_t device_count_check; + uint8_t rx_id_list_validation; + uint8_t repeater_auth_ack_write; + uint8_t prepare_stream_manage; + uint8_t stream_manage_write; + uint8_t stream_ready_available; + uint8_t stream_ready_read; + uint8_t stream_ready_validation; + + uint8_t rx_caps_read_dp; + uint8_t content_stream_type_write; + uint8_t link_integrity_check_dp; + uint8_t stream_encryption_dp; +}; + +union mod_hdcp_transition_input { + struct mod_hdcp_transition_input_hdcp1 hdcp1; + struct mod_hdcp_transition_input_hdcp2 hdcp2; +}; + +struct mod_hdcp_message_hdcp1 { + uint8_t an[8]; + uint8_t aksv[5]; + uint8_t ainfo; + uint8_t bksv[5]; + uint16_t r0p; + uint8_t bcaps; + uint16_t bstatus; + uint8_t ksvlist[635]; + uint16_t ksvlist_size; + uint8_t vp[20]; + + uint16_t binfo_dp; +}; + +struct mod_hdcp_message_hdcp2 { + uint8_t hdcp2version_hdmi; + uint8_t rxcaps_dp[3]; + uint8_t rxstatus[2]; + + uint8_t ake_init[12]; + uint8_t ake_cert[534]; + uint8_t ake_no_stored_km[129]; + uint8_t ake_stored_km[33]; + uint8_t ake_h_prime[33]; + uint8_t ake_pairing_info[17]; + uint8_t lc_init[9]; + uint8_t lc_l_prime[33]; + uint8_t ske_eks[25]; + uint8_t rx_id_list[177]; // 22 + 5 * 31 + uint16_t rx_id_list_size; + uint8_t repeater_auth_ack[17]; + uint8_t repeater_auth_stream_manage[68]; // 6 + 2 * 31 + uint16_t stream_manage_size; + uint8_t repeater_auth_stream_ready[33]; + uint8_t rxstatus_dp; + uint8_t content_stream_type_dp[2]; +}; + +union mod_hdcp_message { + struct mod_hdcp_message_hdcp1 hdcp1; + struct mod_hdcp_message_hdcp2 hdcp2; +}; + +struct mod_hdcp_auth_counters { + uint8_t stream_management_retry_count; +}; + +/* contains values per connection */ +struct mod_hdcp_connection { + struct mod_hdcp_link link; + uint8_t is_repeater; + uint8_t is_km_stored; + uint8_t is_hdcp1_revoked; + uint8_t is_hdcp2_revoked; + struct mod_hdcp_trace trace; + uint8_t hdcp1_retry_count; + uint8_t hdcp2_retry_count; +}; + +/* contains values per authentication cycle */ +struct mod_hdcp_authentication { + uint32_t id; + union mod_hdcp_message msg; + union mod_hdcp_transition_input trans_input; + struct mod_hdcp_auth_counters count; +}; + +/* contains values per state change */ +struct mod_hdcp_state { + uint8_t id; + uint32_t stay_count; +}; + +/* per event in a state */ +struct mod_hdcp_event_context { + enum mod_hdcp_event event; + uint8_t rx_id_list_ready; + uint8_t unexpected_event; +}; + +struct mod_hdcp { + /* per link */ + struct mod_hdcp_config config; + /* per connection */ + struct mod_hdcp_connection connection; + /* per displays */ + struct mod_hdcp_display displays[MAX_NUM_OF_DISPLAYS]; + /* per authentication attempt */ + struct mod_hdcp_authentication auth; + /* per state in an authentication */ + struct mod_hdcp_state state; + /* reserved memory buffer */ + uint8_t buf[2025]; +}; + +enum mod_hdcp_initial_state_id { + HDCP_UNINITIALIZED = 0x0, + HDCP_INITIAL_STATE_START = HDCP_UNINITIALIZED, + HDCP_INITIALIZED, + HDCP_CP_NOT_DESIRED, + HDCP_INITIAL_STATE_END = HDCP_CP_NOT_DESIRED +}; + +enum mod_hdcp_hdcp1_state_id { + HDCP1_STATE_START = HDCP_INITIAL_STATE_END, + H1_A0_WAIT_FOR_ACTIVE_RX, + H1_A1_EXCHANGE_KSVS, + H1_A2_COMPUTATIONS_A3_VALIDATE_RX_A6_TEST_FOR_REPEATER, + H1_A45_AUTHENTICATED, + H1_A8_WAIT_FOR_READY, + H1_A9_READ_KSV_LIST, + HDCP1_STATE_END = H1_A9_READ_KSV_LIST +}; + +enum mod_hdcp_hdcp1_dp_state_id { + HDCP1_DP_STATE_START = HDCP1_STATE_END, + D1_A0_DETERMINE_RX_HDCP_CAPABLE, + D1_A1_EXCHANGE_KSVS, + D1_A23_WAIT_FOR_R0_PRIME, + D1_A2_COMPUTATIONS_A3_VALIDATE_RX_A5_TEST_FOR_REPEATER, + D1_A4_AUTHENTICATED, + D1_A6_WAIT_FOR_READY, + D1_A7_READ_KSV_LIST, + HDCP1_DP_STATE_END = D1_A7_READ_KSV_LIST, +}; + +enum mod_hdcp_hdcp2_state_id { + HDCP2_STATE_START = HDCP1_DP_STATE_END, + H2_A0_KNOWN_HDCP2_CAPABLE_RX, + H2_A1_SEND_AKE_INIT, + H2_A1_VALIDATE_AKE_CERT, + H2_A1_SEND_NO_STORED_KM, + H2_A1_READ_H_PRIME, + H2_A1_READ_PAIRING_INFO_AND_VALIDATE_H_PRIME, + H2_A1_SEND_STORED_KM, + H2_A1_VALIDATE_H_PRIME, + H2_A2_LOCALITY_CHECK, + H2_A3_EXCHANGE_KS_AND_TEST_FOR_REPEATER, + H2_ENABLE_ENCRYPTION, + H2_A5_AUTHENTICATED, + H2_A6_WAIT_FOR_RX_ID_LIST, + H2_A78_VERIFY_RX_ID_LIST_AND_SEND_ACK, + H2_A9_SEND_STREAM_MANAGEMENT, + H2_A9_VALIDATE_STREAM_READY, + HDCP2_STATE_END = H2_A9_VALIDATE_STREAM_READY, +}; + +enum mod_hdcp_hdcp2_dp_state_id { + HDCP2_DP_STATE_START = HDCP2_STATE_END, + D2_A0_DETERMINE_RX_HDCP_CAPABLE, + D2_A1_SEND_AKE_INIT, + D2_A1_VALIDATE_AKE_CERT, + D2_A1_SEND_NO_STORED_KM, + D2_A1_READ_H_PRIME, + D2_A1_READ_PAIRING_INFO_AND_VALIDATE_H_PRIME, + D2_A1_SEND_STORED_KM, + D2_A1_VALIDATE_H_PRIME, + D2_A2_LOCALITY_CHECK, + D2_A34_EXCHANGE_KS_AND_TEST_FOR_REPEATER, + D2_SEND_CONTENT_STREAM_TYPE, + D2_ENABLE_ENCRYPTION, + D2_A5_AUTHENTICATED, + D2_A6_WAIT_FOR_RX_ID_LIST, + D2_A78_VERIFY_RX_ID_LIST_AND_SEND_ACK, + D2_A9_SEND_STREAM_MANAGEMENT, + D2_A9_VALIDATE_STREAM_READY, + HDCP2_DP_STATE_END = D2_A9_VALIDATE_STREAM_READY, + HDCP_STATE_END = HDCP2_DP_STATE_END, +}; + +/* hdcp1 executions and transitions */ +typedef enum mod_hdcp_status (*mod_hdcp_action)(struct mod_hdcp *hdcp); +uint8_t mod_hdcp_execute_and_set( + mod_hdcp_action func, uint8_t *flag, + enum mod_hdcp_status *status, struct mod_hdcp *hdcp, char *str); +enum mod_hdcp_status mod_hdcp_hdcp1_execution(struct mod_hdcp *hdcp, + struct mod_hdcp_event_context *event_ctx, + struct mod_hdcp_transition_input_hdcp1 *input); +enum mod_hdcp_status mod_hdcp_hdcp1_dp_execution(struct mod_hdcp *hdcp, + struct mod_hdcp_event_context *event_ctx, + struct mod_hdcp_transition_input_hdcp1 *input); +enum mod_hdcp_status mod_hdcp_hdcp1_transition(struct mod_hdcp *hdcp, + struct mod_hdcp_event_context *event_ctx, + struct mod_hdcp_transition_input_hdcp1 *input, + struct mod_hdcp_output *output); +enum mod_hdcp_status mod_hdcp_hdcp1_dp_transition(struct mod_hdcp *hdcp, + struct mod_hdcp_event_context *event_ctx, + struct mod_hdcp_transition_input_hdcp1 *input, + struct mod_hdcp_output *output); + +/* hdcp2 executions and transitions */ +enum mod_hdcp_status mod_hdcp_hdcp2_execution(struct mod_hdcp *hdcp, + struct mod_hdcp_event_context *event_ctx, + struct mod_hdcp_transition_input_hdcp2 *input); +enum mod_hdcp_status mod_hdcp_hdcp2_dp_execution(struct mod_hdcp *hdcp, + struct mod_hdcp_event_context *event_ctx, + struct mod_hdcp_transition_input_hdcp2 *input); +enum mod_hdcp_status mod_hdcp_hdcp2_transition(struct mod_hdcp *hdcp, + struct mod_hdcp_event_context *event_ctx, + struct mod_hdcp_transition_input_hdcp2 *input, + struct mod_hdcp_output *output); +enum mod_hdcp_status mod_hdcp_hdcp2_dp_transition(struct mod_hdcp *hdcp, + struct mod_hdcp_event_context *event_ctx, + struct mod_hdcp_transition_input_hdcp2 *input, + struct mod_hdcp_output *output); + +/* log functions */ +void mod_hdcp_dump_binary_message(uint8_t *msg, uint32_t msg_size, + uint8_t *buf, uint32_t buf_size); +void mod_hdcp_log_ddc_trace(struct mod_hdcp *hdcp); +/* TODO: add adjustment log */ + +/* psp functions */ +enum mod_hdcp_status mod_hdcp_add_display_to_topology( + struct mod_hdcp *hdcp, struct mod_hdcp_display *display); +enum mod_hdcp_status mod_hdcp_remove_display_from_topology( + struct mod_hdcp *hdcp, uint8_t index); +enum mod_hdcp_status mod_hdcp_hdcp1_create_session(struct mod_hdcp *hdcp); +enum mod_hdcp_status mod_hdcp_hdcp1_destroy_session(struct mod_hdcp *hdcp); +enum mod_hdcp_status mod_hdcp_hdcp1_validate_rx(struct mod_hdcp *hdcp); +enum mod_hdcp_status mod_hdcp_hdcp1_enable_encryption(struct mod_hdcp *hdcp); +enum mod_hdcp_status mod_hdcp_hdcp1_validate_ksvlist_vp(struct mod_hdcp *hdcp); +enum mod_hdcp_status mod_hdcp_hdcp1_enable_dp_stream_encryption( + struct mod_hdcp *hdcp); +enum mod_hdcp_status mod_hdcp_hdcp1_link_maintenance(struct mod_hdcp *hdcp); +enum mod_hdcp_status mod_hdcp_hdcp2_create_session(struct mod_hdcp *hdcp); +enum mod_hdcp_status mod_hdcp_hdcp2_destroy_session(struct mod_hdcp *hdcp); +enum mod_hdcp_status mod_hdcp_hdcp2_prepare_ake_init(struct mod_hdcp *hdcp); +enum mod_hdcp_status mod_hdcp_hdcp2_validate_ake_cert(struct mod_hdcp *hdcp); +enum mod_hdcp_status mod_hdcp_hdcp2_validate_h_prime(struct mod_hdcp *hdcp); +enum mod_hdcp_status mod_hdcp_hdcp2_prepare_lc_init(struct mod_hdcp *hdcp); +enum mod_hdcp_status mod_hdcp_hdcp2_validate_l_prime(struct mod_hdcp *hdcp); +enum mod_hdcp_status mod_hdcp_hdcp2_prepare_eks(struct mod_hdcp *hdcp); +enum mod_hdcp_status mod_hdcp_hdcp2_enable_encryption(struct mod_hdcp *hdcp); +enum mod_hdcp_status mod_hdcp_hdcp2_validate_rx_id_list(struct mod_hdcp *hdcp); +enum mod_hdcp_status mod_hdcp_hdcp2_enable_dp_stream_encryption( + struct mod_hdcp *hdcp); +enum mod_hdcp_status mod_hdcp_hdcp2_prepare_stream_management( + struct mod_hdcp *hdcp); +enum mod_hdcp_status mod_hdcp_hdcp2_validate_stream_ready( + struct mod_hdcp *hdcp); + +/* ddc functions */ +enum mod_hdcp_status mod_hdcp_read_bksv(struct mod_hdcp *hdcp); +enum mod_hdcp_status mod_hdcp_read_bcaps(struct mod_hdcp *hdcp); +enum mod_hdcp_status mod_hdcp_read_bstatus(struct mod_hdcp *hdcp); +enum mod_hdcp_status mod_hdcp_read_r0p(struct mod_hdcp *hdcp); +enum mod_hdcp_status mod_hdcp_read_ksvlist(struct mod_hdcp *hdcp); +enum mod_hdcp_status mod_hdcp_read_vp(struct mod_hdcp *hdcp); +enum mod_hdcp_status mod_hdcp_read_binfo(struct mod_hdcp *hdcp); +enum mod_hdcp_status mod_hdcp_write_aksv(struct mod_hdcp *hdcp); +enum mod_hdcp_status mod_hdcp_write_ainfo(struct mod_hdcp *hdcp); +enum mod_hdcp_status mod_hdcp_write_an(struct mod_hdcp *hdcp); +enum mod_hdcp_status mod_hdcp_read_hdcp2version(struct mod_hdcp *hdcp); +enum mod_hdcp_status mod_hdcp_read_rxcaps(struct mod_hdcp *hdcp); +enum mod_hdcp_status mod_hdcp_read_rxstatus(struct mod_hdcp *hdcp); +enum mod_hdcp_status mod_hdcp_read_ake_cert(struct mod_hdcp *hdcp); +enum mod_hdcp_status mod_hdcp_read_h_prime(struct mod_hdcp *hdcp); +enum mod_hdcp_status mod_hdcp_read_pairing_info(struct mod_hdcp *hdcp); +enum mod_hdcp_status mod_hdcp_read_l_prime(struct mod_hdcp *hdcp); +enum mod_hdcp_status mod_hdcp_read_rx_id_list(struct mod_hdcp *hdcp); +enum mod_hdcp_status mod_hdcp_read_stream_ready(struct mod_hdcp *hdcp); +enum mod_hdcp_status mod_hdcp_write_ake_init(struct mod_hdcp *hdcp); +enum mod_hdcp_status mod_hdcp_write_no_stored_km(struct mod_hdcp *hdcp); +enum mod_hdcp_status mod_hdcp_write_stored_km(struct mod_hdcp *hdcp); +enum mod_hdcp_status mod_hdcp_write_lc_init(struct mod_hdcp *hdcp); +enum mod_hdcp_status mod_hdcp_write_eks(struct mod_hdcp *hdcp); +enum mod_hdcp_status mod_hdcp_write_repeater_auth_ack(struct mod_hdcp *hdcp); +enum mod_hdcp_status mod_hdcp_write_stream_manage(struct mod_hdcp *hdcp); +enum mod_hdcp_status mod_hdcp_write_content_type(struct mod_hdcp *hdcp); +enum mod_hdcp_status mod_hdcp_clear_cp_irq_status(struct mod_hdcp *hdcp); + +/* hdcp version helpers */ +static inline uint8_t is_dp_hdcp(struct mod_hdcp *hdcp) +{ + return (hdcp->connection.link.mode == MOD_HDCP_MODE_DP); +} + +static inline uint8_t is_dp_mst_hdcp(struct mod_hdcp *hdcp) +{ + return (hdcp->connection.link.mode == MOD_HDCP_MODE_DP && + hdcp->connection.link.dp.mst_enabled); +} + +static inline uint8_t is_hdmi_dvi_sl_hdcp(struct mod_hdcp *hdcp) +{ + return (hdcp->connection.link.mode == MOD_HDCP_MODE_DEFAULT); +} + +/* hdcp state helpers */ +static inline uint8_t current_state(struct mod_hdcp *hdcp) +{ + return hdcp->state.id; +} + +static inline void set_state_id(struct mod_hdcp *hdcp, + struct mod_hdcp_output *output, uint8_t id) +{ + memset(&hdcp->state, 0, sizeof(hdcp->state)); + hdcp->state.id = id; + /* callback timer should be reset per state */ + output->callback_stop = 1; + output->watchdog_timer_stop = 1; + HDCP_NEXT_STATE_TRACE(hdcp, id, output); +} + +static inline uint8_t is_in_hdcp1_states(struct mod_hdcp *hdcp) +{ + return (current_state(hdcp) > HDCP1_STATE_START && + current_state(hdcp) <= HDCP1_STATE_END); +} + +static inline uint8_t is_in_hdcp1_dp_states(struct mod_hdcp *hdcp) +{ + return (current_state(hdcp) > HDCP1_DP_STATE_START && + current_state(hdcp) <= HDCP1_DP_STATE_END); +} + +static inline uint8_t is_in_hdcp2_states(struct mod_hdcp *hdcp) +{ + return (current_state(hdcp) > HDCP2_STATE_START && + current_state(hdcp) <= HDCP2_STATE_END); +} + +static inline uint8_t is_in_hdcp2_dp_states(struct mod_hdcp *hdcp) +{ + return (current_state(hdcp) > HDCP2_DP_STATE_START && + current_state(hdcp) <= HDCP2_DP_STATE_END); +} + +static inline uint8_t is_in_authenticated_states(struct mod_hdcp *hdcp) +{ + return (current_state(hdcp) == D1_A4_AUTHENTICATED || + current_state(hdcp) == H1_A45_AUTHENTICATED || + current_state(hdcp) == D2_A5_AUTHENTICATED || + current_state(hdcp) == H2_A5_AUTHENTICATED); +} + +static inline uint8_t is_hdcp1(struct mod_hdcp *hdcp) +{ + return (is_in_hdcp1_states(hdcp) || is_in_hdcp1_dp_states(hdcp)); +} + +static inline uint8_t is_hdcp2(struct mod_hdcp *hdcp) +{ + return (is_in_hdcp2_states(hdcp) || is_in_hdcp2_dp_states(hdcp)); +} + +static inline uint8_t is_in_cp_not_desired_state(struct mod_hdcp *hdcp) +{ + return current_state(hdcp) == HDCP_CP_NOT_DESIRED; +} + +static inline uint8_t is_in_initialized_state(struct mod_hdcp *hdcp) +{ + return current_state(hdcp) == HDCP_INITIALIZED; +} + +/* transition operation helpers */ +static inline void increment_stay_counter(struct mod_hdcp *hdcp) +{ + hdcp->state.stay_count++; +} + +static inline void fail_and_restart_in_ms(uint16_t time, + enum mod_hdcp_status *status, + struct mod_hdcp_output *output) +{ + output->callback_needed = 1; + output->callback_delay = time; + output->watchdog_timer_needed = 0; + output->watchdog_timer_delay = 0; + *status = MOD_HDCP_STATUS_RESET_NEEDED; +} + +static inline void callback_in_ms(uint16_t time, struct mod_hdcp_output *output) +{ + output->callback_needed = 1; + output->callback_delay = time; +} + +static inline void set_watchdog_in_ms(struct mod_hdcp *hdcp, uint16_t time, + struct mod_hdcp_output *output) +{ + output->watchdog_timer_needed = 1; + output->watchdog_timer_delay = time; +} + +static inline void set_auth_complete(struct mod_hdcp *hdcp, + struct mod_hdcp_output *output) +{ + output->auth_complete = 1; + mod_hdcp_log_ddc_trace(hdcp); +} + +/* connection topology helpers */ +static inline uint8_t is_display_active(struct mod_hdcp_display *display) +{ + return display->state >= MOD_HDCP_DISPLAY_ACTIVE; +} + +static inline uint8_t is_display_encryption_enabled(struct mod_hdcp_display *display) +{ + return display->state >= MOD_HDCP_DISPLAY_ENCRYPTION_ENABLED; +} + +static inline uint8_t get_active_display_count(struct mod_hdcp *hdcp) +{ + uint8_t active_count = 0; + uint8_t i; + + for (i = 0; i < MAX_NUM_OF_DISPLAYS; i++) + if (is_display_active(&hdcp->displays[i])) + active_count++; + return active_count; +} + +static inline struct mod_hdcp_display *get_first_active_display( + struct mod_hdcp *hdcp) +{ + uint8_t i; + struct mod_hdcp_display *display = NULL; + + for (i = 0; i < MAX_NUM_OF_DISPLAYS; i++) + if (is_display_active(&hdcp->displays[i])) { + display = &hdcp->displays[i]; + break; + } + return display; +} + +static inline struct mod_hdcp_display *get_active_display_at_index( + struct mod_hdcp *hdcp, uint8_t index) +{ + uint8_t i; + struct mod_hdcp_display *display = NULL; + + for (i = 0; i < MAX_NUM_OF_DISPLAYS; i++) + if (hdcp->displays[i].index == index && + is_display_active(&hdcp->displays[i])) { + display = &hdcp->displays[i]; + break; + } + return display; +} + +static inline struct mod_hdcp_display *get_empty_display_container( + struct mod_hdcp *hdcp) +{ + uint8_t i; + struct mod_hdcp_display *display = NULL; + + for (i = 0; i < MAX_NUM_OF_DISPLAYS; i++) + if (!is_display_active(&hdcp->displays[i])) { + display = &hdcp->displays[i]; + break; + } + return display; +} + +static inline void reset_retry_counts(struct mod_hdcp *hdcp) +{ + hdcp->connection.hdcp1_retry_count = 0; + hdcp->connection.hdcp2_retry_count = 0; +} + +#endif /* HDCP_H_ */ diff --git a/drivers/gpu/drm/amd/display/modules/hdcp/hdcp1_execution.c b/drivers/gpu/drm/amd/display/modules/hdcp/hdcp1_execution.c new file mode 100644 index 000000000..1ddb4f5ea --- /dev/null +++ b/drivers/gpu/drm/amd/display/modules/hdcp/hdcp1_execution.c @@ -0,0 +1,535 @@ +/* + * Copyright 2019 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 "hdcp.h" + +static inline enum mod_hdcp_status validate_bksv(struct mod_hdcp *hdcp) +{ + uint64_t n = 0; + uint8_t count = 0; + u8 bksv[sizeof(n)] = { }; + + memcpy(bksv, hdcp->auth.msg.hdcp1.bksv, sizeof(hdcp->auth.msg.hdcp1.bksv)); + n = *(uint64_t *)bksv; + + while (n) { + count++; + n &= (n - 1); + } + return (count == 20) ? MOD_HDCP_STATUS_SUCCESS : + MOD_HDCP_STATUS_HDCP1_INVALID_BKSV; +} + +static inline enum mod_hdcp_status check_ksv_ready(struct mod_hdcp *hdcp) +{ + if (is_dp_hdcp(hdcp)) + return (hdcp->auth.msg.hdcp1.bstatus & DP_BSTATUS_READY) ? + MOD_HDCP_STATUS_SUCCESS : + MOD_HDCP_STATUS_HDCP1_KSV_LIST_NOT_READY; + return (hdcp->auth.msg.hdcp1.bcaps & DRM_HDCP_DDC_BCAPS_KSV_FIFO_READY) ? + MOD_HDCP_STATUS_SUCCESS : + MOD_HDCP_STATUS_HDCP1_KSV_LIST_NOT_READY; +} + +static inline enum mod_hdcp_status check_hdcp_capable_dp(struct mod_hdcp *hdcp) +{ + return (hdcp->auth.msg.hdcp1.bcaps & DP_BCAPS_HDCP_CAPABLE) ? + MOD_HDCP_STATUS_SUCCESS : + MOD_HDCP_STATUS_HDCP1_NOT_CAPABLE; +} + +static inline enum mod_hdcp_status check_r0p_available_dp(struct mod_hdcp *hdcp) +{ + enum mod_hdcp_status status; + if (is_dp_hdcp(hdcp)) { + status = (hdcp->auth.msg.hdcp1.bstatus & + DP_BSTATUS_R0_PRIME_READY) ? + MOD_HDCP_STATUS_SUCCESS : + MOD_HDCP_STATUS_HDCP1_R0_PRIME_PENDING; + } else { + status = MOD_HDCP_STATUS_INVALID_OPERATION; + } + return status; +} + +static inline enum mod_hdcp_status check_link_integrity_dp( + struct mod_hdcp *hdcp) +{ + return (hdcp->auth.msg.hdcp1.bstatus & + DP_BSTATUS_LINK_FAILURE) ? + MOD_HDCP_STATUS_HDCP1_LINK_INTEGRITY_FAILURE : + MOD_HDCP_STATUS_SUCCESS; +} + +static inline enum mod_hdcp_status check_no_reauthentication_request_dp( + struct mod_hdcp *hdcp) +{ + return (hdcp->auth.msg.hdcp1.bstatus & DP_BSTATUS_REAUTH_REQ) ? + MOD_HDCP_STATUS_HDCP1_REAUTH_REQUEST_ISSUED : + MOD_HDCP_STATUS_SUCCESS; +} + +static inline enum mod_hdcp_status check_no_max_cascade(struct mod_hdcp *hdcp) +{ + enum mod_hdcp_status status; + + if (is_dp_hdcp(hdcp)) + status = DRM_HDCP_MAX_CASCADE_EXCEEDED(hdcp->auth.msg.hdcp1.binfo_dp >> 8) + ? MOD_HDCP_STATUS_HDCP1_MAX_CASCADE_EXCEEDED_FAILURE + : MOD_HDCP_STATUS_SUCCESS; + else + status = DRM_HDCP_MAX_CASCADE_EXCEEDED(hdcp->auth.msg.hdcp1.bstatus >> 8) + ? MOD_HDCP_STATUS_HDCP1_MAX_CASCADE_EXCEEDED_FAILURE + : MOD_HDCP_STATUS_SUCCESS; + return status; +} + +static inline enum mod_hdcp_status check_no_max_devs(struct mod_hdcp *hdcp) +{ + enum mod_hdcp_status status; + + if (is_dp_hdcp(hdcp)) + status = DRM_HDCP_MAX_DEVICE_EXCEEDED(hdcp->auth.msg.hdcp1.binfo_dp) ? + MOD_HDCP_STATUS_HDCP1_MAX_DEVS_EXCEEDED_FAILURE : + MOD_HDCP_STATUS_SUCCESS; + else + status = DRM_HDCP_MAX_DEVICE_EXCEEDED(hdcp->auth.msg.hdcp1.bstatus) ? + MOD_HDCP_STATUS_HDCP1_MAX_DEVS_EXCEEDED_FAILURE : + MOD_HDCP_STATUS_SUCCESS; + return status; +} + +static inline uint8_t get_device_count(struct mod_hdcp *hdcp) +{ + return is_dp_hdcp(hdcp) ? + DRM_HDCP_NUM_DOWNSTREAM(hdcp->auth.msg.hdcp1.binfo_dp) : + DRM_HDCP_NUM_DOWNSTREAM(hdcp->auth.msg.hdcp1.bstatus); +} + +static inline enum mod_hdcp_status check_device_count(struct mod_hdcp *hdcp) +{ + /* Avoid device count == 0 to do authentication */ + if (0 == get_device_count(hdcp)) { + return MOD_HDCP_STATUS_HDCP1_DEVICE_COUNT_MISMATCH_FAILURE; + } + + /* Some MST display may choose to report the internal panel as an HDCP RX. + * To update this condition with 1(because the immediate repeater's internal + * panel is possibly not included in DEVICE_COUNT) + get_device_count(hdcp). + * Device count must be greater than or equal to tracked hdcp displays. + */ + return ((1 + get_device_count(hdcp)) < get_active_display_count(hdcp)) ? + MOD_HDCP_STATUS_HDCP1_DEVICE_COUNT_MISMATCH_FAILURE : + MOD_HDCP_STATUS_SUCCESS; +} + +static enum mod_hdcp_status wait_for_active_rx(struct mod_hdcp *hdcp, + struct mod_hdcp_event_context *event_ctx, + struct mod_hdcp_transition_input_hdcp1 *input) +{ + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + if (event_ctx->event != MOD_HDCP_EVENT_CALLBACK) { + event_ctx->unexpected_event = 1; + goto out; + } + + if (!mod_hdcp_execute_and_set(mod_hdcp_read_bksv, + &input->bksv_read, &status, + hdcp, "bksv_read")) + goto out; + if (!mod_hdcp_execute_and_set(mod_hdcp_read_bcaps, + &input->bcaps_read, &status, + hdcp, "bcaps_read")) + goto out; +out: + return status; +} + +static enum mod_hdcp_status exchange_ksvs(struct mod_hdcp *hdcp, + struct mod_hdcp_event_context *event_ctx, + struct mod_hdcp_transition_input_hdcp1 *input) +{ + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + if (event_ctx->event != MOD_HDCP_EVENT_CALLBACK) { + event_ctx->unexpected_event = 1; + goto out; + } + + if (!mod_hdcp_execute_and_set(mod_hdcp_hdcp1_create_session, + &input->create_session, &status, + hdcp, "create_session")) + goto out; + if (!mod_hdcp_execute_and_set(mod_hdcp_write_an, + &input->an_write, &status, + hdcp, "an_write")) + goto out; + if (!mod_hdcp_execute_and_set(mod_hdcp_write_aksv, + &input->aksv_write, &status, + hdcp, "aksv_write")) + goto out; + if (!mod_hdcp_execute_and_set(mod_hdcp_read_bksv, + &input->bksv_read, &status, + hdcp, "bksv_read")) + goto out; + if (!mod_hdcp_execute_and_set(validate_bksv, + &input->bksv_validation, &status, + hdcp, "bksv_validation")) + goto out; + if (hdcp->auth.msg.hdcp1.ainfo) { + if (!mod_hdcp_execute_and_set(mod_hdcp_write_ainfo, + &input->ainfo_write, &status, + hdcp, "ainfo_write")) + goto out; + } +out: + return status; +} + +static enum mod_hdcp_status computations_validate_rx_test_for_repeater( + struct mod_hdcp *hdcp, + struct mod_hdcp_event_context *event_ctx, + struct mod_hdcp_transition_input_hdcp1 *input) +{ + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + if (event_ctx->event != MOD_HDCP_EVENT_CALLBACK) { + event_ctx->unexpected_event = 1; + goto out; + } + + if (!mod_hdcp_execute_and_set(mod_hdcp_read_r0p, + &input->r0p_read, &status, + hdcp, "r0p_read")) + goto out; + if (!mod_hdcp_execute_and_set(mod_hdcp_hdcp1_validate_rx, + &input->rx_validation, &status, + hdcp, "rx_validation")) + goto out; + if (hdcp->connection.is_repeater) { + if (!hdcp->connection.link.adjust.hdcp1.postpone_encryption) + if (!mod_hdcp_execute_and_set( + mod_hdcp_hdcp1_enable_encryption, + &input->encryption, &status, + hdcp, "encryption")) + goto out; + } else { + if (!mod_hdcp_execute_and_set(mod_hdcp_hdcp1_enable_encryption, + &input->encryption, &status, + hdcp, "encryption")) + goto out; + if (is_dp_mst_hdcp(hdcp)) + if (!mod_hdcp_execute_and_set( + mod_hdcp_hdcp1_enable_dp_stream_encryption, + &input->stream_encryption_dp, &status, + hdcp, "stream_encryption_dp")) + goto out; + } +out: + return status; +} + +static enum mod_hdcp_status authenticated(struct mod_hdcp *hdcp, + struct mod_hdcp_event_context *event_ctx, + struct mod_hdcp_transition_input_hdcp1 *input) +{ + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + if (event_ctx->event != MOD_HDCP_EVENT_CALLBACK) { + event_ctx->unexpected_event = 1; + goto out; + } + + mod_hdcp_execute_and_set(mod_hdcp_hdcp1_link_maintenance, + &input->link_maintenance, &status, + hdcp, "link_maintenance"); +out: + return status; +} + +static enum mod_hdcp_status wait_for_ready(struct mod_hdcp *hdcp, + struct mod_hdcp_event_context *event_ctx, + struct mod_hdcp_transition_input_hdcp1 *input) +{ + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + if (event_ctx->event != MOD_HDCP_EVENT_CALLBACK && + event_ctx->event != MOD_HDCP_EVENT_CPIRQ && + event_ctx->event != MOD_HDCP_EVENT_WATCHDOG_TIMEOUT) { + event_ctx->unexpected_event = 1; + goto out; + } + + if (is_dp_hdcp(hdcp)) { + if (!mod_hdcp_execute_and_set(mod_hdcp_read_bstatus, + &input->bstatus_read, &status, + hdcp, "bstatus_read")) + goto out; + if (!mod_hdcp_execute_and_set(check_link_integrity_dp, + &input->link_integrity_check, &status, + hdcp, "link_integrity_check")) + goto out; + if (!mod_hdcp_execute_and_set(check_no_reauthentication_request_dp, + &input->reauth_request_check, &status, + hdcp, "reauth_request_check")) + goto out; + } else { + if (!mod_hdcp_execute_and_set(mod_hdcp_read_bcaps, + &input->bcaps_read, &status, + hdcp, "bcaps_read")) + goto out; + } + if (!mod_hdcp_execute_and_set(check_ksv_ready, + &input->ready_check, &status, + hdcp, "ready_check")) + goto out; +out: + return status; +} + +static enum mod_hdcp_status read_ksv_list(struct mod_hdcp *hdcp, + struct mod_hdcp_event_context *event_ctx, + struct mod_hdcp_transition_input_hdcp1 *input) +{ + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + uint8_t device_count; + + if (event_ctx->event != MOD_HDCP_EVENT_CALLBACK) { + event_ctx->unexpected_event = 1; + goto out; + } + + if (is_dp_hdcp(hdcp)) { + if (!mod_hdcp_execute_and_set(mod_hdcp_read_binfo, + &input->binfo_read_dp, &status, + hdcp, "binfo_read_dp")) + goto out; + } else { + if (!mod_hdcp_execute_and_set(mod_hdcp_read_bstatus, + &input->bstatus_read, &status, + hdcp, "bstatus_read")) + goto out; + } + if (!mod_hdcp_execute_and_set(check_no_max_cascade, + &input->max_cascade_check, &status, + hdcp, "max_cascade_check")) + goto out; + if (!mod_hdcp_execute_and_set(check_no_max_devs, + &input->max_devs_check, &status, + hdcp, "max_devs_check")) + goto out; + if (!mod_hdcp_execute_and_set(check_device_count, + &input->device_count_check, &status, + hdcp, "device_count_check")) + goto out; + device_count = get_device_count(hdcp); + hdcp->auth.msg.hdcp1.ksvlist_size = device_count*5; + if (!mod_hdcp_execute_and_set(mod_hdcp_read_ksvlist, + &input->ksvlist_read, &status, + hdcp, "ksvlist_read")) + goto out; + if (!mod_hdcp_execute_and_set(mod_hdcp_read_vp, + &input->vp_read, &status, + hdcp, "vp_read")) + goto out; + if (!mod_hdcp_execute_and_set(mod_hdcp_hdcp1_validate_ksvlist_vp, + &input->ksvlist_vp_validation, &status, + hdcp, "ksvlist_vp_validation")) + goto out; + if (input->encryption != PASS) + if (!mod_hdcp_execute_and_set(mod_hdcp_hdcp1_enable_encryption, + &input->encryption, &status, + hdcp, "encryption")) + goto out; + if (is_dp_mst_hdcp(hdcp)) + if (!mod_hdcp_execute_and_set( + mod_hdcp_hdcp1_enable_dp_stream_encryption, + &input->stream_encryption_dp, &status, + hdcp, "stream_encryption_dp")) + goto out; +out: + return status; +} + +static enum mod_hdcp_status determine_rx_hdcp_capable_dp(struct mod_hdcp *hdcp, + struct mod_hdcp_event_context *event_ctx, + struct mod_hdcp_transition_input_hdcp1 *input) +{ + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + if (event_ctx->event != MOD_HDCP_EVENT_CALLBACK) { + event_ctx->unexpected_event = 1; + goto out; + } + + if (!mod_hdcp_execute_and_set(mod_hdcp_read_bcaps, + &input->bcaps_read, &status, + hdcp, "bcaps_read")) + goto out; + if (!mod_hdcp_execute_and_set(check_hdcp_capable_dp, + &input->hdcp_capable_dp, &status, + hdcp, "hdcp_capable_dp")) + goto out; +out: + return status; +} + +static enum mod_hdcp_status wait_for_r0_prime_dp(struct mod_hdcp *hdcp, + struct mod_hdcp_event_context *event_ctx, + struct mod_hdcp_transition_input_hdcp1 *input) +{ + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + if (event_ctx->event != MOD_HDCP_EVENT_CPIRQ && + event_ctx->event != MOD_HDCP_EVENT_WATCHDOG_TIMEOUT) { + event_ctx->unexpected_event = 1; + goto out; + } + + if (!mod_hdcp_execute_and_set(mod_hdcp_read_bstatus, + &input->bstatus_read, &status, + hdcp, "bstatus_read")) + goto out; + if (!mod_hdcp_execute_and_set(check_r0p_available_dp, + &input->r0p_available_dp, &status, + hdcp, "r0p_available_dp")) + goto out; +out: + return status; +} + +static enum mod_hdcp_status authenticated_dp(struct mod_hdcp *hdcp, + struct mod_hdcp_event_context *event_ctx, + struct mod_hdcp_transition_input_hdcp1 *input) +{ + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + if (event_ctx->event != MOD_HDCP_EVENT_CPIRQ) { + event_ctx->unexpected_event = 1; + goto out; + } + + if (status == MOD_HDCP_STATUS_SUCCESS) + mod_hdcp_execute_and_set(mod_hdcp_read_bstatus, + &input->bstatus_read, &status, + hdcp, "bstatus_read"); + if (status == MOD_HDCP_STATUS_SUCCESS) + mod_hdcp_execute_and_set(check_link_integrity_dp, + &input->link_integrity_check, &status, + hdcp, "link_integrity_check"); + if (status == MOD_HDCP_STATUS_SUCCESS) + mod_hdcp_execute_and_set(check_no_reauthentication_request_dp, + &input->reauth_request_check, &status, + hdcp, "reauth_request_check"); +out: + return status; +} + +uint8_t mod_hdcp_execute_and_set( + mod_hdcp_action func, uint8_t *flag, + enum mod_hdcp_status *status, struct mod_hdcp *hdcp, char *str) +{ + *status = func(hdcp); + if (*status == MOD_HDCP_STATUS_SUCCESS && *flag != PASS) { + HDCP_INPUT_PASS_TRACE(hdcp, str); + *flag = PASS; + } else if (*status != MOD_HDCP_STATUS_SUCCESS && *flag != FAIL) { + HDCP_INPUT_FAIL_TRACE(hdcp, str); + *flag = FAIL; + } + return (*status == MOD_HDCP_STATUS_SUCCESS); +} + +enum mod_hdcp_status mod_hdcp_hdcp1_execution(struct mod_hdcp *hdcp, + struct mod_hdcp_event_context *event_ctx, + struct mod_hdcp_transition_input_hdcp1 *input) +{ + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + switch (current_state(hdcp)) { + case H1_A0_WAIT_FOR_ACTIVE_RX: + status = wait_for_active_rx(hdcp, event_ctx, input); + break; + case H1_A1_EXCHANGE_KSVS: + status = exchange_ksvs(hdcp, event_ctx, input); + break; + case H1_A2_COMPUTATIONS_A3_VALIDATE_RX_A6_TEST_FOR_REPEATER: + status = computations_validate_rx_test_for_repeater(hdcp, + event_ctx, input); + break; + case H1_A45_AUTHENTICATED: + status = authenticated(hdcp, event_ctx, input); + break; + case H1_A8_WAIT_FOR_READY: + status = wait_for_ready(hdcp, event_ctx, input); + break; + case H1_A9_READ_KSV_LIST: + status = read_ksv_list(hdcp, event_ctx, input); + break; + default: + status = MOD_HDCP_STATUS_INVALID_STATE; + break; + } + + return status; +} + +enum mod_hdcp_status mod_hdcp_hdcp1_dp_execution(struct mod_hdcp *hdcp, + struct mod_hdcp_event_context *event_ctx, + struct mod_hdcp_transition_input_hdcp1 *input) +{ + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + switch (current_state(hdcp)) { + case D1_A0_DETERMINE_RX_HDCP_CAPABLE: + status = determine_rx_hdcp_capable_dp(hdcp, event_ctx, input); + break; + case D1_A1_EXCHANGE_KSVS: + status = exchange_ksvs(hdcp, event_ctx, input); + break; + case D1_A23_WAIT_FOR_R0_PRIME: + status = wait_for_r0_prime_dp(hdcp, event_ctx, input); + break; + case D1_A2_COMPUTATIONS_A3_VALIDATE_RX_A5_TEST_FOR_REPEATER: + status = computations_validate_rx_test_for_repeater( + hdcp, event_ctx, input); + break; + case D1_A4_AUTHENTICATED: + status = authenticated_dp(hdcp, event_ctx, input); + break; + case D1_A6_WAIT_FOR_READY: + status = wait_for_ready(hdcp, event_ctx, input); + break; + case D1_A7_READ_KSV_LIST: + status = read_ksv_list(hdcp, event_ctx, input); + break; + default: + status = MOD_HDCP_STATUS_INVALID_STATE; + break; + } + + return status; +} diff --git a/drivers/gpu/drm/amd/display/modules/hdcp/hdcp1_transition.c b/drivers/gpu/drm/amd/display/modules/hdcp/hdcp1_transition.c new file mode 100644 index 000000000..7f011196c --- /dev/null +++ b/drivers/gpu/drm/amd/display/modules/hdcp/hdcp1_transition.c @@ -0,0 +1,322 @@ +/* + * Copyright 2019 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 "hdcp.h" + +enum mod_hdcp_status mod_hdcp_hdcp1_transition(struct mod_hdcp *hdcp, + struct mod_hdcp_event_context *event_ctx, + struct mod_hdcp_transition_input_hdcp1 *input, + struct mod_hdcp_output *output) +{ + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + struct mod_hdcp_connection *conn = &hdcp->connection; + struct mod_hdcp_link_adjustment *adjust = &hdcp->connection.link.adjust; + + switch (current_state(hdcp)) { + case H1_A0_WAIT_FOR_ACTIVE_RX: + if (input->bksv_read != PASS || input->bcaps_read != PASS) { + /* 1A-04: repeatedly attempts on port access failure */ + callback_in_ms(500, output); + increment_stay_counter(hdcp); + break; + } + callback_in_ms(0, output); + set_state_id(hdcp, output, H1_A1_EXCHANGE_KSVS); + break; + case H1_A1_EXCHANGE_KSVS: + if (input->create_session != PASS) { + /* out of sync with psp state */ + adjust->hdcp1.disable = 1; + fail_and_restart_in_ms(0, &status, output); + break; + } else if (input->an_write != PASS || + input->aksv_write != PASS || + input->bksv_read != PASS || + input->bksv_validation != PASS || + input->ainfo_write == FAIL) { + /* 1A-05: consider invalid bksv a failure */ + fail_and_restart_in_ms(0, &status, output); + break; + } + callback_in_ms(300, output); + set_state_id(hdcp, output, + H1_A2_COMPUTATIONS_A3_VALIDATE_RX_A6_TEST_FOR_REPEATER); + break; + case H1_A2_COMPUTATIONS_A3_VALIDATE_RX_A6_TEST_FOR_REPEATER: + if (input->bcaps_read != PASS || + input->r0p_read != PASS) { + fail_and_restart_in_ms(0, &status, output); + break; + } else if (input->rx_validation != PASS) { + /* 1A-06: consider invalid r0' a failure */ + /* 1A-08: consider bksv listed in SRM a failure */ + /* + * some slow RX will fail rx validation when it is + * not ready. give it more time to react before retry. + */ + fail_and_restart_in_ms(1000, &status, output); + break; + } else if (!conn->is_repeater && input->encryption != PASS) { + fail_and_restart_in_ms(0, &status, output); + break; + } + if (conn->is_repeater) { + callback_in_ms(0, output); + set_watchdog_in_ms(hdcp, 5000, output); + set_state_id(hdcp, output, H1_A8_WAIT_FOR_READY); + } else { + callback_in_ms(0, output); + set_state_id(hdcp, output, H1_A45_AUTHENTICATED); + set_auth_complete(hdcp, output); + } + break; + case H1_A45_AUTHENTICATED: + if (input->link_maintenance == FAIL) { + /* 1A-07: consider invalid ri' a failure */ + /* 1A-07a: consider read ri' not returned a failure */ + fail_and_restart_in_ms(0, &status, output); + break; + } + callback_in_ms(500, output); + increment_stay_counter(hdcp); + break; + case H1_A8_WAIT_FOR_READY: + if (input->ready_check != PASS) { + if (event_ctx->event == + MOD_HDCP_EVENT_WATCHDOG_TIMEOUT) { + /* 1B-03: fail hdcp on ksv list READY timeout */ + /* prevent black screen in next attempt */ + adjust->hdcp1.postpone_encryption = 1; + fail_and_restart_in_ms(0, &status, output); + } else { + /* continue ksv list READY polling*/ + callback_in_ms(500, output); + increment_stay_counter(hdcp); + } + break; + } + callback_in_ms(0, output); + set_state_id(hdcp, output, H1_A9_READ_KSV_LIST); + break; + case H1_A9_READ_KSV_LIST: + if (input->bstatus_read != PASS || + input->max_cascade_check != PASS || + input->max_devs_check != PASS || + input->device_count_check != PASS || + input->ksvlist_read != PASS || + input->vp_read != PASS || + input->ksvlist_vp_validation != PASS || + input->encryption != PASS) { + /* 1B-06: consider MAX_CASCADE_EXCEEDED a failure */ + /* 1B-05: consider MAX_DEVS_EXCEEDED a failure */ + /* 1B-04: consider invalid v' a failure */ + fail_and_restart_in_ms(0, &status, output); + break; + } + callback_in_ms(0, output); + set_state_id(hdcp, output, H1_A45_AUTHENTICATED); + set_auth_complete(hdcp, output); + break; + default: + status = MOD_HDCP_STATUS_INVALID_STATE; + fail_and_restart_in_ms(0, &status, output); + break; + } + + return status; +} + +enum mod_hdcp_status mod_hdcp_hdcp1_dp_transition(struct mod_hdcp *hdcp, + struct mod_hdcp_event_context *event_ctx, + struct mod_hdcp_transition_input_hdcp1 *input, + struct mod_hdcp_output *output) +{ + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + struct mod_hdcp_connection *conn = &hdcp->connection; + struct mod_hdcp_link_adjustment *adjust = &hdcp->connection.link.adjust; + + switch (current_state(hdcp)) { + case D1_A0_DETERMINE_RX_HDCP_CAPABLE: + if (input->bcaps_read != PASS) { + /* 1A-04: no authentication on bcaps read failure */ + fail_and_restart_in_ms(0, &status, output); + break; + } else if (input->hdcp_capable_dp != PASS) { + adjust->hdcp1.disable = 1; + fail_and_restart_in_ms(0, &status, output); + break; + } + callback_in_ms(0, output); + set_state_id(hdcp, output, D1_A1_EXCHANGE_KSVS); + break; + case D1_A1_EXCHANGE_KSVS: + if (input->create_session != PASS) { + /* out of sync with psp state */ + adjust->hdcp1.disable = 1; + fail_and_restart_in_ms(0, &status, output); + break; + } else if (input->an_write != PASS || + input->aksv_write != PASS || + input->bksv_read != PASS || + input->bksv_validation != PASS || + input->ainfo_write == FAIL) { + /* 1A-05: consider invalid bksv a failure */ + fail_and_restart_in_ms(0, &status, output); + break; + } + set_watchdog_in_ms(hdcp, 100, output); + set_state_id(hdcp, output, D1_A23_WAIT_FOR_R0_PRIME); + break; + case D1_A23_WAIT_FOR_R0_PRIME: + if (input->bstatus_read != PASS) { + fail_and_restart_in_ms(0, &status, output); + break; + } else if (input->r0p_available_dp != PASS) { + if (event_ctx->event == MOD_HDCP_EVENT_WATCHDOG_TIMEOUT) + fail_and_restart_in_ms(0, &status, output); + else + increment_stay_counter(hdcp); + break; + } + callback_in_ms(0, output); + set_state_id(hdcp, output, D1_A2_COMPUTATIONS_A3_VALIDATE_RX_A5_TEST_FOR_REPEATER); + break; + case D1_A2_COMPUTATIONS_A3_VALIDATE_RX_A5_TEST_FOR_REPEATER: + if (input->r0p_read != PASS) { + fail_and_restart_in_ms(0, &status, output); + break; + } else if (input->rx_validation != PASS) { + if (hdcp->state.stay_count < 2 && + !hdcp->connection.is_hdcp1_revoked) { + /* allow 2 additional retries */ + callback_in_ms(0, output); + increment_stay_counter(hdcp); + } else { + /* + * 1A-06: consider invalid r0' a failure + * after 3 attempts. + * 1A-08: consider bksv listed in SRM a failure + */ + /* + * some slow RX will fail rx validation when it is + * not ready. give it more time to react before retry. + */ + fail_and_restart_in_ms(1000, &status, output); + } + break; + } else if ((!conn->is_repeater && input->encryption != PASS) || + (!conn->is_repeater && is_dp_mst_hdcp(hdcp) && input->stream_encryption_dp != PASS)) { + fail_and_restart_in_ms(0, &status, output); + break; + } else if (conn->hdcp1_retry_count < conn->link.adjust.hdcp1.min_auth_retries_wa) { + fail_and_restart_in_ms(200, &status, output); + break; + } + if (conn->is_repeater) { + set_watchdog_in_ms(hdcp, 5000, output); + set_state_id(hdcp, output, D1_A6_WAIT_FOR_READY); + } else { + set_state_id(hdcp, output, D1_A4_AUTHENTICATED); + set_auth_complete(hdcp, output); + } + break; + case D1_A4_AUTHENTICATED: + if (input->link_integrity_check == FAIL || + input->reauth_request_check == FAIL) { + /* 1A-07: restart hdcp on a link integrity failure */ + fail_and_restart_in_ms(0, &status, output); + break; + } + break; + case D1_A6_WAIT_FOR_READY: + if (input->link_integrity_check == FAIL || + input->reauth_request_check == FAIL) { + fail_and_restart_in_ms(0, &status, output); + break; + } else if (input->ready_check != PASS) { + if (event_ctx->event == + MOD_HDCP_EVENT_WATCHDOG_TIMEOUT) { + /* 1B-04: fail hdcp on ksv list READY timeout */ + /* prevent black screen in next attempt */ + adjust->hdcp1.postpone_encryption = 1; + fail_and_restart_in_ms(0, &status, output); + } else { + increment_stay_counter(hdcp); + } + break; + } + callback_in_ms(0, output); + set_state_id(hdcp, output, D1_A7_READ_KSV_LIST); + break; + case D1_A7_READ_KSV_LIST: + if (input->binfo_read_dp != PASS || + input->max_cascade_check != PASS || + input->max_devs_check != PASS) { + /* 1B-06: consider MAX_DEVS_EXCEEDED a failure */ + /* 1B-07: consider MAX_CASCADE_EXCEEDED a failure */ + fail_and_restart_in_ms(0, &status, output); + break; + } else if (input->device_count_check != PASS) { + /* + * some slow dongle doesn't update + * device count as soon as downstream is connected. + * give it more time to react. + */ + adjust->hdcp1.postpone_encryption = 1; + fail_and_restart_in_ms(1000, &status, output); + break; + } else if (input->ksvlist_read != PASS || + input->vp_read != PASS) { + fail_and_restart_in_ms(0, &status, output); + break; + } else if (input->ksvlist_vp_validation != PASS) { + if (hdcp->state.stay_count < 2 && + !hdcp->connection.is_hdcp1_revoked) { + /* allow 2 additional retries */ + callback_in_ms(0, output); + increment_stay_counter(hdcp); + } else { + /* + * 1B-05: consider invalid v' a failure + * after 3 attempts. + */ + fail_and_restart_in_ms(0, &status, output); + } + break; + } else if (input->encryption != PASS || + (is_dp_mst_hdcp(hdcp) && input->stream_encryption_dp != PASS)) { + fail_and_restart_in_ms(0, &status, output); + break; + } + set_state_id(hdcp, output, D1_A4_AUTHENTICATED); + set_auth_complete(hdcp, output); + break; + default: + fail_and_restart_in_ms(0, &status, output); + break; + } + + return status; +} diff --git a/drivers/gpu/drm/amd/display/modules/hdcp/hdcp2_execution.c b/drivers/gpu/drm/amd/display/modules/hdcp/hdcp2_execution.c new file mode 100644 index 000000000..91c22b96e --- /dev/null +++ b/drivers/gpu/drm/amd/display/modules/hdcp/hdcp2_execution.c @@ -0,0 +1,889 @@ +/* + * 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. + * + * Authors: AMD + * + */ + +#include + +#include "hdcp.h" + +static inline enum mod_hdcp_status check_receiver_id_list_ready(struct mod_hdcp *hdcp) +{ + uint8_t is_ready = 0; + + if (is_dp_hdcp(hdcp)) + is_ready = HDCP_2_2_DP_RXSTATUS_READY(hdcp->auth.msg.hdcp2.rxstatus_dp) ? 1 : 0; + else + is_ready = (HDCP_2_2_HDMI_RXSTATUS_READY(hdcp->auth.msg.hdcp2.rxstatus[1]) && + (HDCP_2_2_HDMI_RXSTATUS_MSG_SZ_HI(hdcp->auth.msg.hdcp2.rxstatus[1]) << 8 | + hdcp->auth.msg.hdcp2.rxstatus[0])) ? 1 : 0; + return is_ready ? MOD_HDCP_STATUS_SUCCESS : + MOD_HDCP_STATUS_HDCP2_RX_ID_LIST_NOT_READY; +} + +static inline enum mod_hdcp_status check_hdcp2_capable(struct mod_hdcp *hdcp) +{ + enum mod_hdcp_status status; + + if (is_dp_hdcp(hdcp)) + status = (hdcp->auth.msg.hdcp2.rxcaps_dp[0] == HDCP_2_2_RX_CAPS_VERSION_VAL) && + HDCP_2_2_DP_HDCP_CAPABLE(hdcp->auth.msg.hdcp2.rxcaps_dp[2]) ? + MOD_HDCP_STATUS_SUCCESS : + MOD_HDCP_STATUS_HDCP2_NOT_CAPABLE; + else + status = (hdcp->auth.msg.hdcp2.hdcp2version_hdmi & HDCP_2_2_HDMI_SUPPORT_MASK) ? + MOD_HDCP_STATUS_SUCCESS : + MOD_HDCP_STATUS_HDCP2_NOT_CAPABLE; + return status; +} + +static inline enum mod_hdcp_status check_reauthentication_request( + struct mod_hdcp *hdcp) +{ + uint8_t ret = 0; + + if (is_dp_hdcp(hdcp)) + ret = HDCP_2_2_DP_RXSTATUS_REAUTH_REQ(hdcp->auth.msg.hdcp2.rxstatus_dp) ? + MOD_HDCP_STATUS_HDCP2_REAUTH_REQUEST : + MOD_HDCP_STATUS_SUCCESS; + else + ret = HDCP_2_2_HDMI_RXSTATUS_REAUTH_REQ(hdcp->auth.msg.hdcp2.rxstatus[1]) ? + MOD_HDCP_STATUS_HDCP2_REAUTH_REQUEST : + MOD_HDCP_STATUS_SUCCESS; + return ret; +} + +static inline enum mod_hdcp_status check_link_integrity_failure_dp( + struct mod_hdcp *hdcp) +{ + return HDCP_2_2_DP_RXSTATUS_LINK_FAILED(hdcp->auth.msg.hdcp2.rxstatus_dp) ? + MOD_HDCP_STATUS_HDCP2_REAUTH_LINK_INTEGRITY_FAILURE : + MOD_HDCP_STATUS_SUCCESS; +} + +static enum mod_hdcp_status check_ake_cert_available(struct mod_hdcp *hdcp) +{ + enum mod_hdcp_status status; + uint16_t size; + + if (is_dp_hdcp(hdcp)) { + status = MOD_HDCP_STATUS_SUCCESS; + } else { + status = mod_hdcp_read_rxstatus(hdcp); + if (status == MOD_HDCP_STATUS_SUCCESS) { + size = HDCP_2_2_HDMI_RXSTATUS_MSG_SZ_HI(hdcp->auth.msg.hdcp2.rxstatus[1]) << 8 | + hdcp->auth.msg.hdcp2.rxstatus[0]; + status = (size == sizeof(hdcp->auth.msg.hdcp2.ake_cert)) ? + MOD_HDCP_STATUS_SUCCESS : + MOD_HDCP_STATUS_HDCP2_AKE_CERT_PENDING; + } + } + return status; +} + +static enum mod_hdcp_status check_h_prime_available(struct mod_hdcp *hdcp) +{ + enum mod_hdcp_status status; + uint8_t size; + + status = mod_hdcp_read_rxstatus(hdcp); + if (status != MOD_HDCP_STATUS_SUCCESS) + goto out; + + if (is_dp_hdcp(hdcp)) { + status = HDCP_2_2_DP_RXSTATUS_H_PRIME(hdcp->auth.msg.hdcp2.rxstatus_dp) ? + MOD_HDCP_STATUS_SUCCESS : + MOD_HDCP_STATUS_HDCP2_H_PRIME_PENDING; + } else { + size = HDCP_2_2_HDMI_RXSTATUS_MSG_SZ_HI(hdcp->auth.msg.hdcp2.rxstatus[1]) << 8 | + hdcp->auth.msg.hdcp2.rxstatus[0]; + status = (size == sizeof(hdcp->auth.msg.hdcp2.ake_h_prime)) ? + MOD_HDCP_STATUS_SUCCESS : + MOD_HDCP_STATUS_HDCP2_H_PRIME_PENDING; + } +out: + return status; +} + +static enum mod_hdcp_status check_pairing_info_available(struct mod_hdcp *hdcp) +{ + enum mod_hdcp_status status; + uint8_t size; + + status = mod_hdcp_read_rxstatus(hdcp); + if (status != MOD_HDCP_STATUS_SUCCESS) + goto out; + + if (is_dp_hdcp(hdcp)) { + status = HDCP_2_2_DP_RXSTATUS_PAIRING(hdcp->auth.msg.hdcp2.rxstatus_dp) ? + MOD_HDCP_STATUS_SUCCESS : + MOD_HDCP_STATUS_HDCP2_PAIRING_INFO_PENDING; + } else { + size = HDCP_2_2_HDMI_RXSTATUS_MSG_SZ_HI(hdcp->auth.msg.hdcp2.rxstatus[1]) << 8 | + hdcp->auth.msg.hdcp2.rxstatus[0]; + status = (size == sizeof(hdcp->auth.msg.hdcp2.ake_pairing_info)) ? + MOD_HDCP_STATUS_SUCCESS : + MOD_HDCP_STATUS_HDCP2_PAIRING_INFO_PENDING; + } +out: + return status; +} + +static enum mod_hdcp_status poll_l_prime_available(struct mod_hdcp *hdcp) +{ + enum mod_hdcp_status status; + uint8_t size; + uint16_t max_wait = 20; // units of ms + uint16_t num_polls = 5; + uint16_t wait_time = max_wait / num_polls; + + if (is_dp_hdcp(hdcp)) + status = MOD_HDCP_STATUS_INVALID_OPERATION; + else + for (; num_polls; num_polls--) { + msleep(wait_time); + + status = mod_hdcp_read_rxstatus(hdcp); + if (status != MOD_HDCP_STATUS_SUCCESS) + break; + + size = HDCP_2_2_HDMI_RXSTATUS_MSG_SZ_HI(hdcp->auth.msg.hdcp2.rxstatus[1]) << 8 | + hdcp->auth.msg.hdcp2.rxstatus[0]; + status = (size == sizeof(hdcp->auth.msg.hdcp2.lc_l_prime)) ? + MOD_HDCP_STATUS_SUCCESS : + MOD_HDCP_STATUS_HDCP2_L_PRIME_PENDING; + if (status == MOD_HDCP_STATUS_SUCCESS) + break; + } + return status; +} + +static enum mod_hdcp_status check_stream_ready_available(struct mod_hdcp *hdcp) +{ + enum mod_hdcp_status status; + uint8_t size; + + if (is_dp_hdcp(hdcp)) { + status = MOD_HDCP_STATUS_INVALID_OPERATION; + } else { + status = mod_hdcp_read_rxstatus(hdcp); + if (status != MOD_HDCP_STATUS_SUCCESS) + goto out; + size = HDCP_2_2_HDMI_RXSTATUS_MSG_SZ_HI(hdcp->auth.msg.hdcp2.rxstatus[1]) << 8 | + hdcp->auth.msg.hdcp2.rxstatus[0]; + status = (size == sizeof(hdcp->auth.msg.hdcp2.repeater_auth_stream_ready)) ? + MOD_HDCP_STATUS_SUCCESS : + MOD_HDCP_STATUS_HDCP2_STREAM_READY_PENDING; + } +out: + return status; +} + +static inline uint8_t get_device_count(struct mod_hdcp *hdcp) +{ + return HDCP_2_2_DEV_COUNT_LO(hdcp->auth.msg.hdcp2.rx_id_list[2]) + + (HDCP_2_2_DEV_COUNT_HI(hdcp->auth.msg.hdcp2.rx_id_list[1]) << 4); +} + +static enum mod_hdcp_status check_device_count(struct mod_hdcp *hdcp) +{ + /* Avoid device count == 0 to do authentication */ + if (0 == get_device_count(hdcp)) { + return MOD_HDCP_STATUS_HDCP1_DEVICE_COUNT_MISMATCH_FAILURE; + } + + /* Some MST display may choose to report the internal panel as an HDCP RX. */ + /* To update this condition with 1(because the immediate repeater's internal */ + /* panel is possibly not included in DEVICE_COUNT) + get_device_count(hdcp). */ + /* Device count must be greater than or equal to tracked hdcp displays. */ + return ((1 + get_device_count(hdcp)) < get_active_display_count(hdcp)) ? + MOD_HDCP_STATUS_HDCP2_DEVICE_COUNT_MISMATCH_FAILURE : + MOD_HDCP_STATUS_SUCCESS; +} + +static uint8_t process_rxstatus(struct mod_hdcp *hdcp, + struct mod_hdcp_event_context *event_ctx, + struct mod_hdcp_transition_input_hdcp2 *input, + enum mod_hdcp_status *status) +{ + if (!mod_hdcp_execute_and_set(mod_hdcp_read_rxstatus, + &input->rxstatus_read, status, + hdcp, "rxstatus_read")) + goto out; + if (!mod_hdcp_execute_and_set(check_reauthentication_request, + &input->reauth_request_check, status, + hdcp, "reauth_request_check")) + goto out; + if (is_dp_hdcp(hdcp)) { + if (!mod_hdcp_execute_and_set(check_link_integrity_failure_dp, + &input->link_integrity_check_dp, status, + hdcp, "link_integrity_check_dp")) + goto out; + } + if (hdcp->connection.is_repeater) + if (check_receiver_id_list_ready(hdcp) == + MOD_HDCP_STATUS_SUCCESS) { + HDCP_INPUT_PASS_TRACE(hdcp, "rx_id_list_ready"); + event_ctx->rx_id_list_ready = 1; + if (is_dp_hdcp(hdcp)) + hdcp->auth.msg.hdcp2.rx_id_list_size = + sizeof(hdcp->auth.msg.hdcp2.rx_id_list); + else + hdcp->auth.msg.hdcp2.rx_id_list_size = + HDCP_2_2_HDMI_RXSTATUS_MSG_SZ_HI(hdcp->auth.msg.hdcp2.rxstatus[1]) << 8 | + hdcp->auth.msg.hdcp2.rxstatus[0]; + } +out: + return (*status == MOD_HDCP_STATUS_SUCCESS); +} + +static enum mod_hdcp_status known_hdcp2_capable_rx(struct mod_hdcp *hdcp, + struct mod_hdcp_event_context *event_ctx, + struct mod_hdcp_transition_input_hdcp2 *input) +{ + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + if (event_ctx->event != MOD_HDCP_EVENT_CALLBACK) { + event_ctx->unexpected_event = 1; + goto out; + } + + if (!mod_hdcp_execute_and_set(mod_hdcp_read_hdcp2version, + &input->hdcp2version_read, &status, + hdcp, "hdcp2version_read")) + goto out; + if (!mod_hdcp_execute_and_set(check_hdcp2_capable, + &input->hdcp2_capable_check, &status, + hdcp, "hdcp2_capable")) + goto out; +out: + return status; +} + +static enum mod_hdcp_status send_ake_init(struct mod_hdcp *hdcp, + struct mod_hdcp_event_context *event_ctx, + struct mod_hdcp_transition_input_hdcp2 *input) +{ + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + if (event_ctx->event != MOD_HDCP_EVENT_CALLBACK) { + event_ctx->unexpected_event = 1; + goto out; + } + + if (!mod_hdcp_execute_and_set(mod_hdcp_hdcp2_create_session, + &input->create_session, &status, + hdcp, "create_session")) + goto out; + if (!mod_hdcp_execute_and_set(mod_hdcp_hdcp2_prepare_ake_init, + &input->ake_init_prepare, &status, + hdcp, "ake_init_prepare")) + goto out; + if (!mod_hdcp_execute_and_set(mod_hdcp_write_ake_init, + &input->ake_init_write, &status, + hdcp, "ake_init_write")) + goto out; +out: + return status; +} + +static enum mod_hdcp_status validate_ake_cert(struct mod_hdcp *hdcp, + struct mod_hdcp_event_context *event_ctx, + struct mod_hdcp_transition_input_hdcp2 *input) +{ + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + + if (event_ctx->event != MOD_HDCP_EVENT_CALLBACK && + event_ctx->event != MOD_HDCP_EVENT_WATCHDOG_TIMEOUT) { + event_ctx->unexpected_event = 1; + goto out; + } + + if (is_hdmi_dvi_sl_hdcp(hdcp)) + if (!mod_hdcp_execute_and_set(check_ake_cert_available, + &input->ake_cert_available, &status, + hdcp, "ake_cert_available")) + goto out; + if (!mod_hdcp_execute_and_set(mod_hdcp_read_ake_cert, + &input->ake_cert_read, &status, + hdcp, "ake_cert_read")) + goto out; + if (!mod_hdcp_execute_and_set(mod_hdcp_hdcp2_validate_ake_cert, + &input->ake_cert_validation, &status, + hdcp, "ake_cert_validation")) + goto out; +out: + return status; +} + +static enum mod_hdcp_status send_no_stored_km(struct mod_hdcp *hdcp, + struct mod_hdcp_event_context *event_ctx, + struct mod_hdcp_transition_input_hdcp2 *input) +{ + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + if (event_ctx->event != MOD_HDCP_EVENT_CALLBACK) { + event_ctx->unexpected_event = 1; + goto out; + } + + if (!mod_hdcp_execute_and_set(mod_hdcp_write_no_stored_km, + &input->no_stored_km_write, &status, + hdcp, "no_stored_km_write")) + goto out; +out: + return status; +} + +static enum mod_hdcp_status read_h_prime(struct mod_hdcp *hdcp, + struct mod_hdcp_event_context *event_ctx, + struct mod_hdcp_transition_input_hdcp2 *input) +{ + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + if (event_ctx->event != MOD_HDCP_EVENT_CALLBACK && + event_ctx->event != MOD_HDCP_EVENT_CPIRQ && + event_ctx->event != MOD_HDCP_EVENT_WATCHDOG_TIMEOUT) { + event_ctx->unexpected_event = 1; + goto out; + } + + if (!mod_hdcp_execute_and_set(check_h_prime_available, + &input->h_prime_available, &status, + hdcp, "h_prime_available")) + goto out; + + if (!mod_hdcp_execute_and_set(mod_hdcp_read_h_prime, + &input->h_prime_read, &status, + hdcp, "h_prime_read")) + goto out; +out: + return status; +} + +static enum mod_hdcp_status read_pairing_info_and_validate_h_prime( + struct mod_hdcp *hdcp, + struct mod_hdcp_event_context *event_ctx, + struct mod_hdcp_transition_input_hdcp2 *input) +{ + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + if (event_ctx->event != MOD_HDCP_EVENT_CALLBACK && + event_ctx->event != MOD_HDCP_EVENT_CPIRQ && + event_ctx->event != MOD_HDCP_EVENT_WATCHDOG_TIMEOUT) { + event_ctx->unexpected_event = 1; + goto out; + } + + if (!mod_hdcp_execute_and_set(check_pairing_info_available, + &input->pairing_available, &status, + hdcp, "pairing_available")) + goto out; + if (!mod_hdcp_execute_and_set(mod_hdcp_read_pairing_info, + &input->pairing_info_read, &status, + hdcp, "pairing_info_read")) + goto out; + if (!mod_hdcp_execute_and_set(mod_hdcp_hdcp2_validate_h_prime, + &input->h_prime_validation, &status, + hdcp, "h_prime_validation")) + goto out; +out: + return status; +} + +static enum mod_hdcp_status send_stored_km(struct mod_hdcp *hdcp, + struct mod_hdcp_event_context *event_ctx, + struct mod_hdcp_transition_input_hdcp2 *input) +{ + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + if (event_ctx->event != MOD_HDCP_EVENT_CALLBACK) { + event_ctx->unexpected_event = 1; + goto out; + } + + if (!mod_hdcp_execute_and_set(mod_hdcp_write_stored_km, + &input->stored_km_write, &status, + hdcp, "stored_km_write")) + goto out; +out: + return status; +} + +static enum mod_hdcp_status validate_h_prime(struct mod_hdcp *hdcp, + struct mod_hdcp_event_context *event_ctx, + struct mod_hdcp_transition_input_hdcp2 *input) +{ + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + if (event_ctx->event != MOD_HDCP_EVENT_CALLBACK && + event_ctx->event != MOD_HDCP_EVENT_CPIRQ && + event_ctx->event != MOD_HDCP_EVENT_WATCHDOG_TIMEOUT) { + event_ctx->unexpected_event = 1; + goto out; + } + + if (!mod_hdcp_execute_and_set(check_h_prime_available, + &input->h_prime_available, &status, + hdcp, "h_prime_available")) + goto out; + if (!mod_hdcp_execute_and_set(mod_hdcp_read_h_prime, + &input->h_prime_read, &status, + hdcp, "h_prime_read")) + goto out; + if (!mod_hdcp_execute_and_set(mod_hdcp_hdcp2_validate_h_prime, + &input->h_prime_validation, &status, + hdcp, "h_prime_validation")) + goto out; +out: + return status; +} + +static enum mod_hdcp_status locality_check(struct mod_hdcp *hdcp, + struct mod_hdcp_event_context *event_ctx, + struct mod_hdcp_transition_input_hdcp2 *input) +{ + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + if (event_ctx->event != MOD_HDCP_EVENT_CALLBACK) { + event_ctx->unexpected_event = 1; + goto out; + } + + if (!mod_hdcp_execute_and_set(mod_hdcp_hdcp2_prepare_lc_init, + &input->lc_init_prepare, &status, + hdcp, "lc_init_prepare")) + goto out; + if (!mod_hdcp_execute_and_set(mod_hdcp_write_lc_init, + &input->lc_init_write, &status, + hdcp, "lc_init_write")) + goto out; + if (is_dp_hdcp(hdcp)) + msleep(16); + else + if (!mod_hdcp_execute_and_set(poll_l_prime_available, + &input->l_prime_available_poll, &status, + hdcp, "l_prime_available_poll")) + goto out; + if (!mod_hdcp_execute_and_set(mod_hdcp_read_l_prime, + &input->l_prime_read, &status, + hdcp, "l_prime_read")) + goto out; + if (!mod_hdcp_execute_and_set(mod_hdcp_hdcp2_validate_l_prime, + &input->l_prime_validation, &status, + hdcp, "l_prime_validation")) + goto out; +out: + return status; +} + +static enum mod_hdcp_status exchange_ks_and_test_for_repeater(struct mod_hdcp *hdcp, + struct mod_hdcp_event_context *event_ctx, + struct mod_hdcp_transition_input_hdcp2 *input) +{ + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + if (event_ctx->event != MOD_HDCP_EVENT_CALLBACK) { + event_ctx->unexpected_event = 1; + goto out; + } + + if (!mod_hdcp_execute_and_set(mod_hdcp_hdcp2_prepare_eks, + &input->eks_prepare, &status, + hdcp, "eks_prepare")) + goto out; + if (!mod_hdcp_execute_and_set(mod_hdcp_write_eks, + &input->eks_write, &status, + hdcp, "eks_write")) + goto out; +out: + return status; +} + +static enum mod_hdcp_status enable_encryption(struct mod_hdcp *hdcp, + struct mod_hdcp_event_context *event_ctx, + struct mod_hdcp_transition_input_hdcp2 *input) +{ + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + if (event_ctx->event != MOD_HDCP_EVENT_CALLBACK && + event_ctx->event != MOD_HDCP_EVENT_CPIRQ) { + event_ctx->unexpected_event = 1; + goto out; + } + if (event_ctx->event == MOD_HDCP_EVENT_CPIRQ) { + process_rxstatus(hdcp, event_ctx, input, &status); + goto out; + } + + if (is_hdmi_dvi_sl_hdcp(hdcp)) { + if (!process_rxstatus(hdcp, event_ctx, input, &status)) + goto out; + if (event_ctx->rx_id_list_ready) + goto out; + } + if (!mod_hdcp_execute_and_set(mod_hdcp_hdcp2_enable_encryption, + &input->enable_encryption, &status, + hdcp, "enable_encryption")) + goto out; + if (is_dp_mst_hdcp(hdcp)) { + if (!mod_hdcp_execute_and_set( + mod_hdcp_hdcp2_enable_dp_stream_encryption, + &input->stream_encryption_dp, &status, + hdcp, "stream_encryption_dp")) + goto out; + } +out: + return status; +} + +static enum mod_hdcp_status authenticated(struct mod_hdcp *hdcp, + struct mod_hdcp_event_context *event_ctx, + struct mod_hdcp_transition_input_hdcp2 *input) +{ + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + if (event_ctx->event != MOD_HDCP_EVENT_CALLBACK && + event_ctx->event != MOD_HDCP_EVENT_CPIRQ) { + event_ctx->unexpected_event = 1; + goto out; + } + + process_rxstatus(hdcp, event_ctx, input, &status); +out: + return status; +} + +static enum mod_hdcp_status wait_for_rx_id_list(struct mod_hdcp *hdcp, + struct mod_hdcp_event_context *event_ctx, + struct mod_hdcp_transition_input_hdcp2 *input) +{ + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + if (event_ctx->event != MOD_HDCP_EVENT_CALLBACK && + event_ctx->event != MOD_HDCP_EVENT_CPIRQ && + event_ctx->event != MOD_HDCP_EVENT_WATCHDOG_TIMEOUT) { + event_ctx->unexpected_event = 1; + goto out; + } + + if (!process_rxstatus(hdcp, event_ctx, input, &status)) + goto out; + if (!event_ctx->rx_id_list_ready) { + status = MOD_HDCP_STATUS_HDCP2_RX_ID_LIST_NOT_READY; + goto out; + } +out: + return status; +} + +static enum mod_hdcp_status verify_rx_id_list_and_send_ack(struct mod_hdcp *hdcp, + struct mod_hdcp_event_context *event_ctx, + struct mod_hdcp_transition_input_hdcp2 *input) +{ + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + if (event_ctx->event != MOD_HDCP_EVENT_CALLBACK && + event_ctx->event != MOD_HDCP_EVENT_CPIRQ) { + event_ctx->unexpected_event = 1; + goto out; + } + if (event_ctx->event == MOD_HDCP_EVENT_CPIRQ) { + process_rxstatus(hdcp, event_ctx, input, &status); + goto out; + } + + if (!mod_hdcp_execute_and_set(mod_hdcp_read_rx_id_list, + &input->rx_id_list_read, + &status, hdcp, "receiver_id_list_read")) + goto out; + if (!mod_hdcp_execute_and_set(check_device_count, + &input->device_count_check, + &status, hdcp, "device_count_check")) + goto out; + if (!mod_hdcp_execute_and_set(mod_hdcp_hdcp2_validate_rx_id_list, + &input->rx_id_list_validation, + &status, hdcp, "rx_id_list_validation")) + goto out; + if (!mod_hdcp_execute_and_set(mod_hdcp_write_repeater_auth_ack, + &input->repeater_auth_ack_write, + &status, hdcp, "repeater_auth_ack_write")) + goto out; +out: + return status; +} + +static enum mod_hdcp_status send_stream_management(struct mod_hdcp *hdcp, + struct mod_hdcp_event_context *event_ctx, + struct mod_hdcp_transition_input_hdcp2 *input) +{ + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + if (event_ctx->event != MOD_HDCP_EVENT_CALLBACK && + event_ctx->event != MOD_HDCP_EVENT_CPIRQ) { + event_ctx->unexpected_event = 1; + goto out; + } + if (event_ctx->event == MOD_HDCP_EVENT_CPIRQ) { + process_rxstatus(hdcp, event_ctx, input, &status); + goto out; + } + + if (is_hdmi_dvi_sl_hdcp(hdcp)) { + if (!process_rxstatus(hdcp, event_ctx, input, &status)) + goto out; + if (event_ctx->rx_id_list_ready) + goto out; + } + if (!mod_hdcp_execute_and_set(mod_hdcp_hdcp2_prepare_stream_management, + &input->prepare_stream_manage, + &status, hdcp, "prepare_stream_manage")) + goto out; + + if (!mod_hdcp_execute_and_set(mod_hdcp_write_stream_manage, + &input->stream_manage_write, + &status, hdcp, "stream_manage_write")) + goto out; +out: + return status; +} + +static enum mod_hdcp_status validate_stream_ready(struct mod_hdcp *hdcp, + struct mod_hdcp_event_context *event_ctx, + struct mod_hdcp_transition_input_hdcp2 *input) +{ + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + if (event_ctx->event != MOD_HDCP_EVENT_CALLBACK && + event_ctx->event != MOD_HDCP_EVENT_CPIRQ && + event_ctx->event != MOD_HDCP_EVENT_WATCHDOG_TIMEOUT) { + event_ctx->unexpected_event = 1; + goto out; + } + if (event_ctx->event == MOD_HDCP_EVENT_CPIRQ) { + process_rxstatus(hdcp, event_ctx, input, &status); + goto out; + } + + if (is_hdmi_dvi_sl_hdcp(hdcp)) { + if (!process_rxstatus(hdcp, event_ctx, input, &status)) + goto out; + if (event_ctx->rx_id_list_ready) { + goto out; + } + } + if (is_hdmi_dvi_sl_hdcp(hdcp)) + if (!mod_hdcp_execute_and_set(check_stream_ready_available, + &input->stream_ready_available, + &status, hdcp, "stream_ready_available")) + goto out; + if (!mod_hdcp_execute_and_set(mod_hdcp_read_stream_ready, + &input->stream_ready_read, + &status, hdcp, "stream_ready_read")) + goto out; + if (!mod_hdcp_execute_and_set(mod_hdcp_hdcp2_validate_stream_ready, + &input->stream_ready_validation, + &status, hdcp, "stream_ready_validation")) + goto out; + +out: + return status; +} + +static enum mod_hdcp_status determine_rx_hdcp_capable_dp(struct mod_hdcp *hdcp, + struct mod_hdcp_event_context *event_ctx, + struct mod_hdcp_transition_input_hdcp2 *input) +{ + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + if (event_ctx->event != MOD_HDCP_EVENT_CALLBACK) { + event_ctx->unexpected_event = 1; + goto out; + } + + if (!mod_hdcp_execute_and_set(mod_hdcp_read_rxcaps, + &input->rx_caps_read_dp, + &status, hdcp, "rx_caps_read_dp")) + goto out; + if (!mod_hdcp_execute_and_set(check_hdcp2_capable, + &input->hdcp2_capable_check, &status, + hdcp, "hdcp2_capable_check")) + goto out; +out: + return status; +} + +static enum mod_hdcp_status send_content_stream_type_dp(struct mod_hdcp *hdcp, + struct mod_hdcp_event_context *event_ctx, + struct mod_hdcp_transition_input_hdcp2 *input) +{ + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + if (event_ctx->event != MOD_HDCP_EVENT_CALLBACK && + event_ctx->event != MOD_HDCP_EVENT_CPIRQ) { + event_ctx->unexpected_event = 1; + goto out; + } + + if (!process_rxstatus(hdcp, event_ctx, input, &status)) + goto out; + if (!mod_hdcp_execute_and_set(mod_hdcp_write_content_type, + &input->content_stream_type_write, &status, + hdcp, "content_stream_type_write")) + goto out; +out: + return status; +} + +enum mod_hdcp_status mod_hdcp_hdcp2_execution(struct mod_hdcp *hdcp, + struct mod_hdcp_event_context *event_ctx, + struct mod_hdcp_transition_input_hdcp2 *input) +{ + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + switch (current_state(hdcp)) { + case H2_A0_KNOWN_HDCP2_CAPABLE_RX: + status = known_hdcp2_capable_rx(hdcp, event_ctx, input); + break; + case H2_A1_SEND_AKE_INIT: + status = send_ake_init(hdcp, event_ctx, input); + break; + case H2_A1_VALIDATE_AKE_CERT: + status = validate_ake_cert(hdcp, event_ctx, input); + break; + case H2_A1_SEND_NO_STORED_KM: + status = send_no_stored_km(hdcp, event_ctx, input); + break; + case H2_A1_READ_H_PRIME: + status = read_h_prime(hdcp, event_ctx, input); + break; + case H2_A1_READ_PAIRING_INFO_AND_VALIDATE_H_PRIME: + status = read_pairing_info_and_validate_h_prime(hdcp, + event_ctx, input); + break; + case H2_A1_SEND_STORED_KM: + status = send_stored_km(hdcp, event_ctx, input); + break; + case H2_A1_VALIDATE_H_PRIME: + status = validate_h_prime(hdcp, event_ctx, input); + break; + case H2_A2_LOCALITY_CHECK: + status = locality_check(hdcp, event_ctx, input); + break; + case H2_A3_EXCHANGE_KS_AND_TEST_FOR_REPEATER: + status = exchange_ks_and_test_for_repeater(hdcp, event_ctx, input); + break; + case H2_ENABLE_ENCRYPTION: + status = enable_encryption(hdcp, event_ctx, input); + break; + case H2_A5_AUTHENTICATED: + status = authenticated(hdcp, event_ctx, input); + break; + case H2_A6_WAIT_FOR_RX_ID_LIST: + status = wait_for_rx_id_list(hdcp, event_ctx, input); + break; + case H2_A78_VERIFY_RX_ID_LIST_AND_SEND_ACK: + status = verify_rx_id_list_and_send_ack(hdcp, event_ctx, input); + break; + case H2_A9_SEND_STREAM_MANAGEMENT: + status = send_stream_management(hdcp, event_ctx, input); + break; + case H2_A9_VALIDATE_STREAM_READY: + status = validate_stream_ready(hdcp, event_ctx, input); + break; + default: + status = MOD_HDCP_STATUS_INVALID_STATE; + break; + } + + return status; +} + +enum mod_hdcp_status mod_hdcp_hdcp2_dp_execution(struct mod_hdcp *hdcp, + struct mod_hdcp_event_context *event_ctx, + struct mod_hdcp_transition_input_hdcp2 *input) +{ + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + switch (current_state(hdcp)) { + case D2_A0_DETERMINE_RX_HDCP_CAPABLE: + status = determine_rx_hdcp_capable_dp(hdcp, event_ctx, input); + break; + case D2_A1_SEND_AKE_INIT: + status = send_ake_init(hdcp, event_ctx, input); + break; + case D2_A1_VALIDATE_AKE_CERT: + status = validate_ake_cert(hdcp, event_ctx, input); + break; + case D2_A1_SEND_NO_STORED_KM: + status = send_no_stored_km(hdcp, event_ctx, input); + break; + case D2_A1_READ_H_PRIME: + status = read_h_prime(hdcp, event_ctx, input); + break; + case D2_A1_READ_PAIRING_INFO_AND_VALIDATE_H_PRIME: + status = read_pairing_info_and_validate_h_prime(hdcp, + event_ctx, input); + break; + case D2_A1_SEND_STORED_KM: + status = send_stored_km(hdcp, event_ctx, input); + break; + case D2_A1_VALIDATE_H_PRIME: + status = validate_h_prime(hdcp, event_ctx, input); + break; + case D2_A2_LOCALITY_CHECK: + status = locality_check(hdcp, event_ctx, input); + break; + case D2_A34_EXCHANGE_KS_AND_TEST_FOR_REPEATER: + status = exchange_ks_and_test_for_repeater(hdcp, + event_ctx, input); + break; + case D2_SEND_CONTENT_STREAM_TYPE: + status = send_content_stream_type_dp(hdcp, event_ctx, input); + break; + case D2_ENABLE_ENCRYPTION: + status = enable_encryption(hdcp, event_ctx, input); + break; + case D2_A5_AUTHENTICATED: + status = authenticated(hdcp, event_ctx, input); + break; + case D2_A6_WAIT_FOR_RX_ID_LIST: + status = wait_for_rx_id_list(hdcp, event_ctx, input); + break; + case D2_A78_VERIFY_RX_ID_LIST_AND_SEND_ACK: + status = verify_rx_id_list_and_send_ack(hdcp, event_ctx, input); + break; + case D2_A9_SEND_STREAM_MANAGEMENT: + status = send_stream_management(hdcp, event_ctx, input); + break; + case D2_A9_VALIDATE_STREAM_READY: + status = validate_stream_ready(hdcp, event_ctx, input); + break; + default: + status = MOD_HDCP_STATUS_INVALID_STATE; + break; + } + + return status; +} diff --git a/drivers/gpu/drm/amd/display/modules/hdcp/hdcp2_transition.c b/drivers/gpu/drm/amd/display/modules/hdcp/hdcp2_transition.c new file mode 100644 index 000000000..c5f6c11de --- /dev/null +++ b/drivers/gpu/drm/amd/display/modules/hdcp/hdcp2_transition.c @@ -0,0 +1,677 @@ +/* + * 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. + * + * Authors: AMD + * + */ + +#include "hdcp.h" + +enum mod_hdcp_status mod_hdcp_hdcp2_transition(struct mod_hdcp *hdcp, + struct mod_hdcp_event_context *event_ctx, + struct mod_hdcp_transition_input_hdcp2 *input, + struct mod_hdcp_output *output) +{ + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + struct mod_hdcp_connection *conn = &hdcp->connection; + struct mod_hdcp_link_adjustment *adjust = &hdcp->connection.link.adjust; + + switch (current_state(hdcp)) { + case H2_A0_KNOWN_HDCP2_CAPABLE_RX: + if (input->hdcp2version_read != PASS || + input->hdcp2_capable_check != PASS) { + adjust->hdcp2.disable = 1; + callback_in_ms(0, output); + set_state_id(hdcp, output, HDCP_INITIALIZED); + } else { + callback_in_ms(0, output); + set_state_id(hdcp, output, H2_A1_SEND_AKE_INIT); + } + break; + case H2_A1_SEND_AKE_INIT: + if (input->create_session != PASS || + input->ake_init_prepare != PASS) { + /* out of sync with psp state */ + adjust->hdcp2.disable = 1; + fail_and_restart_in_ms(0, &status, output); + break; + } else if (input->ake_init_write != PASS) { + fail_and_restart_in_ms(0, &status, output); + break; + } + set_watchdog_in_ms(hdcp, 100, output); + callback_in_ms(0, output); + set_state_id(hdcp, output, H2_A1_VALIDATE_AKE_CERT); + break; + case H2_A1_VALIDATE_AKE_CERT: + if (input->ake_cert_available != PASS) { + if (event_ctx->event == + MOD_HDCP_EVENT_WATCHDOG_TIMEOUT) { + /* 1A-08: consider ake timeout a failure */ + /* some hdmi receivers are not ready for HDCP + * immediately after video becomes active, + * delay 1s before retry on first HDCP message + * timeout. + */ + fail_and_restart_in_ms(1000, &status, output); + } else { + /* continue ake cert polling*/ + callback_in_ms(10, output); + increment_stay_counter(hdcp); + } + break; + } else if (input->ake_cert_read != PASS || + input->ake_cert_validation != PASS) { + /* + * 1A-09: consider invalid ake cert a failure + * 1A-10: consider receiver id listed in SRM a failure + */ + fail_and_restart_in_ms(0, &status, output); + break; + } + if (conn->is_km_stored && + !adjust->hdcp2.force_no_stored_km) { + callback_in_ms(0, output); + set_state_id(hdcp, output, H2_A1_SEND_STORED_KM); + } else { + callback_in_ms(0, output); + set_state_id(hdcp, output, H2_A1_SEND_NO_STORED_KM); + } + break; + case H2_A1_SEND_NO_STORED_KM: + if (input->no_stored_km_write != PASS) { + fail_and_restart_in_ms(0, &status, output); + break; + } + if (adjust->hdcp2.increase_h_prime_timeout) + set_watchdog_in_ms(hdcp, 2000, output); + else + set_watchdog_in_ms(hdcp, 1000, output); + callback_in_ms(0, output); + set_state_id(hdcp, output, H2_A1_READ_H_PRIME); + break; + case H2_A1_READ_H_PRIME: + if (input->h_prime_available != PASS) { + if (event_ctx->event == + MOD_HDCP_EVENT_WATCHDOG_TIMEOUT) { + /* 1A-11-3: consider h' timeout a failure */ + fail_and_restart_in_ms(1000, &status, output); + } else { + /* continue h' polling */ + callback_in_ms(100, output); + increment_stay_counter(hdcp); + } + break; + } else if (input->h_prime_read != PASS) { + fail_and_restart_in_ms(0, &status, output); + break; + } + set_watchdog_in_ms(hdcp, 200, output); + callback_in_ms(0, output); + set_state_id(hdcp, output, H2_A1_READ_PAIRING_INFO_AND_VALIDATE_H_PRIME); + break; + case H2_A1_READ_PAIRING_INFO_AND_VALIDATE_H_PRIME: + if (input->pairing_available != PASS) { + if (event_ctx->event == + MOD_HDCP_EVENT_WATCHDOG_TIMEOUT) { + /* 1A-12: consider pairing info timeout + * a failure + */ + fail_and_restart_in_ms(0, &status, output); + } else { + /* continue pairing info polling */ + callback_in_ms(20, output); + increment_stay_counter(hdcp); + } + break; + } else if (input->pairing_info_read != PASS || + input->h_prime_validation != PASS) { + /* 1A-11-1: consider invalid h' a failure */ + fail_and_restart_in_ms(0, &status, output); + break; + } + callback_in_ms(0, output); + set_state_id(hdcp, output, H2_A2_LOCALITY_CHECK); + break; + case H2_A1_SEND_STORED_KM: + if (input->stored_km_write != PASS) { + fail_and_restart_in_ms(0, &status, output); + break; + } + set_watchdog_in_ms(hdcp, 200, output); + callback_in_ms(0, output); + set_state_id(hdcp, output, H2_A1_VALIDATE_H_PRIME); + break; + case H2_A1_VALIDATE_H_PRIME: + if (input->h_prime_available != PASS) { + if (event_ctx->event == + MOD_HDCP_EVENT_WATCHDOG_TIMEOUT) { + /* 1A-11-2: consider h' timeout a failure */ + fail_and_restart_in_ms(1000, &status, output); + } else { + /* continue h' polling */ + callback_in_ms(20, output); + increment_stay_counter(hdcp); + } + break; + } else if (input->h_prime_read != PASS) { + fail_and_restart_in_ms(0, &status, output); + break; + } else if (input->h_prime_validation != PASS) { + /* 1A-11-1: consider invalid h' a failure */ + adjust->hdcp2.force_no_stored_km = 1; + fail_and_restart_in_ms(0, &status, output); + break; + } + callback_in_ms(0, output); + set_state_id(hdcp, output, H2_A2_LOCALITY_CHECK); + break; + case H2_A2_LOCALITY_CHECK: + if (hdcp->state.stay_count > 10 || + input->lc_init_prepare != PASS || + input->lc_init_write != PASS || + input->l_prime_available_poll != PASS || + input->l_prime_read != PASS) { + /* + * 1A-05: consider disconnection after LC init a failure + * 1A-13-1: consider invalid l' a failure + * 1A-13-2: consider l' timeout a failure + */ + fail_and_restart_in_ms(0, &status, output); + break; + } else if (input->l_prime_validation != PASS) { + callback_in_ms(0, output); + increment_stay_counter(hdcp); + break; + } + callback_in_ms(0, output); + set_state_id(hdcp, output, H2_A3_EXCHANGE_KS_AND_TEST_FOR_REPEATER); + break; + case H2_A3_EXCHANGE_KS_AND_TEST_FOR_REPEATER: + if (input->eks_prepare != PASS || + input->eks_write != PASS) { + fail_and_restart_in_ms(0, &status, output); + break; + } + if (conn->is_repeater) { + set_watchdog_in_ms(hdcp, 3000, output); + callback_in_ms(0, output); + set_state_id(hdcp, output, H2_A6_WAIT_FOR_RX_ID_LIST); + } else { + /* some CTS equipment requires a delay GREATER than + * 200 ms, so delay 210 ms instead of 200 ms + */ + callback_in_ms(210, output); + set_state_id(hdcp, output, H2_ENABLE_ENCRYPTION); + } + break; + case H2_ENABLE_ENCRYPTION: + if (input->rxstatus_read != PASS || + input->reauth_request_check != PASS) { + /* + * 1A-07: restart hdcp on REAUTH_REQ + * 1B-08: restart hdcp on REAUTH_REQ + */ + fail_and_restart_in_ms(0, &status, output); + break; + } else if (event_ctx->rx_id_list_ready && conn->is_repeater) { + callback_in_ms(0, output); + set_state_id(hdcp, output, H2_A78_VERIFY_RX_ID_LIST_AND_SEND_ACK); + break; + } else if (input->enable_encryption != PASS) { + fail_and_restart_in_ms(0, &status, output); + break; + } + callback_in_ms(0, output); + set_state_id(hdcp, output, H2_A5_AUTHENTICATED); + set_auth_complete(hdcp, output); + break; + case H2_A5_AUTHENTICATED: + if (input->rxstatus_read == FAIL || + input->reauth_request_check == FAIL) { + fail_and_restart_in_ms(0, &status, output); + break; + } else if (event_ctx->rx_id_list_ready && conn->is_repeater) { + callback_in_ms(0, output); + set_state_id(hdcp, output, H2_A78_VERIFY_RX_ID_LIST_AND_SEND_ACK); + break; + } + callback_in_ms(500, output); + increment_stay_counter(hdcp); + break; + case H2_A6_WAIT_FOR_RX_ID_LIST: + if (input->rxstatus_read != PASS || + input->reauth_request_check != PASS) { + fail_and_restart_in_ms(0, &status, output); + break; + } else if (!event_ctx->rx_id_list_ready) { + if (event_ctx->event == MOD_HDCP_EVENT_WATCHDOG_TIMEOUT) { + /* 1B-02: consider rx id list timeout a failure */ + /* some CTS equipment's actual timeout + * measurement is slightly greater than 3000 ms. + * Delay 100 ms to ensure it is fully timeout + * before re-authentication. + */ + fail_and_restart_in_ms(100, &status, output); + } else { + callback_in_ms(300, output); + increment_stay_counter(hdcp); + } + break; + } + callback_in_ms(0, output); + set_state_id(hdcp, output, H2_A78_VERIFY_RX_ID_LIST_AND_SEND_ACK); + break; + case H2_A78_VERIFY_RX_ID_LIST_AND_SEND_ACK: + if (input->rxstatus_read != PASS || + input->reauth_request_check != PASS || + input->rx_id_list_read != PASS || + input->device_count_check != PASS || + input->rx_id_list_validation != PASS || + input->repeater_auth_ack_write != PASS) { + /* 1B-03: consider invalid v' a failure + * 1B-04: consider MAX_DEVS_EXCEEDED a failure + * 1B-05: consider MAX_CASCADE_EXCEEDED a failure + * 1B-06: consider invalid seq_num_V a failure + * 1B-09: consider seq_num_V rollover a failure + */ + fail_and_restart_in_ms(0, &status, output); + break; + } + callback_in_ms(0, output); + set_state_id(hdcp, output, H2_A9_SEND_STREAM_MANAGEMENT); + break; + case H2_A9_SEND_STREAM_MANAGEMENT: + if (input->rxstatus_read != PASS || + input->reauth_request_check != PASS) { + fail_and_restart_in_ms(0, &status, output); + break; + } else if (event_ctx->rx_id_list_ready && conn->is_repeater) { + callback_in_ms(0, output); + set_state_id(hdcp, output, H2_A78_VERIFY_RX_ID_LIST_AND_SEND_ACK); + break; + } else if (input->prepare_stream_manage != PASS || + input->stream_manage_write != PASS) { + fail_and_restart_in_ms(0, &status, output); + break; + } + set_watchdog_in_ms(hdcp, 100, output); + callback_in_ms(0, output); + set_state_id(hdcp, output, H2_A9_VALIDATE_STREAM_READY); + break; + case H2_A9_VALIDATE_STREAM_READY: + if (input->rxstatus_read != PASS || + input->reauth_request_check != PASS) { + fail_and_restart_in_ms(0, &status, output); + break; + } else if (event_ctx->rx_id_list_ready && conn->is_repeater) { + callback_in_ms(0, output); + set_state_id(hdcp, output, H2_A78_VERIFY_RX_ID_LIST_AND_SEND_ACK); + break; + } else if (input->stream_ready_available != PASS) { + if (event_ctx->event == MOD_HDCP_EVENT_WATCHDOG_TIMEOUT) { + /* 1B-10-2: restart content stream management on + * stream ready timeout + */ + hdcp->auth.count.stream_management_retry_count++; + callback_in_ms(0, output); + set_state_id(hdcp, output, H2_A9_SEND_STREAM_MANAGEMENT); + } else { + callback_in_ms(10, output); + increment_stay_counter(hdcp); + } + break; + } else if (input->stream_ready_read != PASS || + input->stream_ready_validation != PASS) { + /* + * 1B-10-1: restart content stream management + * on invalid M' + */ + if (hdcp->auth.count.stream_management_retry_count > 10) { + fail_and_restart_in_ms(0, &status, output); + } else { + hdcp->auth.count.stream_management_retry_count++; + callback_in_ms(0, output); + set_state_id(hdcp, output, H2_A9_SEND_STREAM_MANAGEMENT); + } + break; + } + callback_in_ms(200, output); + set_state_id(hdcp, output, H2_ENABLE_ENCRYPTION); + break; + default: + status = MOD_HDCP_STATUS_INVALID_STATE; + fail_and_restart_in_ms(0, &status, output); + break; + } + + return status; +} + +enum mod_hdcp_status mod_hdcp_hdcp2_dp_transition(struct mod_hdcp *hdcp, + struct mod_hdcp_event_context *event_ctx, + struct mod_hdcp_transition_input_hdcp2 *input, + struct mod_hdcp_output *output) +{ + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + struct mod_hdcp_connection *conn = &hdcp->connection; + struct mod_hdcp_link_adjustment *adjust = &hdcp->connection.link.adjust; + + switch (current_state(hdcp)) { + case D2_A0_DETERMINE_RX_HDCP_CAPABLE: + if (input->rx_caps_read_dp != PASS || + input->hdcp2_capable_check != PASS) { + adjust->hdcp2.disable = 1; + callback_in_ms(0, output); + set_state_id(hdcp, output, HDCP_INITIALIZED); + } else { + callback_in_ms(0, output); + set_state_id(hdcp, output, D2_A1_SEND_AKE_INIT); + } + break; + case D2_A1_SEND_AKE_INIT: + if (input->create_session != PASS || + input->ake_init_prepare != PASS) { + /* out of sync with psp state */ + adjust->hdcp2.disable = 1; + fail_and_restart_in_ms(0, &status, output); + break; + } else if (input->ake_init_write != PASS) { + /* possibly display not ready */ + fail_and_restart_in_ms(0, &status, output); + break; + } + callback_in_ms(100, output); + set_state_id(hdcp, output, D2_A1_VALIDATE_AKE_CERT); + break; + case D2_A1_VALIDATE_AKE_CERT: + if (input->ake_cert_read != PASS || + input->ake_cert_validation != PASS) { + /* + * 1A-08: consider invalid ake cert a failure + * 1A-09: consider receiver id listed in SRM a failure + */ + fail_and_restart_in_ms(0, &status, output); + break; + } + if (conn->is_km_stored && + !adjust->hdcp2.force_no_stored_km) { + callback_in_ms(0, output); + set_state_id(hdcp, output, D2_A1_SEND_STORED_KM); + } else { + callback_in_ms(0, output); + set_state_id(hdcp, output, D2_A1_SEND_NO_STORED_KM); + } + break; + case D2_A1_SEND_NO_STORED_KM: + if (input->no_stored_km_write != PASS) { + fail_and_restart_in_ms(0, &status, output); + break; + } + if (adjust->hdcp2.increase_h_prime_timeout) + set_watchdog_in_ms(hdcp, 2000, output); + else + set_watchdog_in_ms(hdcp, 1000, output); + set_state_id(hdcp, output, D2_A1_READ_H_PRIME); + break; + case D2_A1_READ_H_PRIME: + if (input->h_prime_available != PASS) { + if (event_ctx->event == + MOD_HDCP_EVENT_WATCHDOG_TIMEOUT) + /* 1A-10-3: consider h' timeout a failure */ + fail_and_restart_in_ms(1000, &status, output); + else + increment_stay_counter(hdcp); + break; + } else if (input->h_prime_read != PASS) { + fail_and_restart_in_ms(0, &status, output); + break; + } + set_watchdog_in_ms(hdcp, 200, output); + set_state_id(hdcp, output, D2_A1_READ_PAIRING_INFO_AND_VALIDATE_H_PRIME); + break; + case D2_A1_READ_PAIRING_INFO_AND_VALIDATE_H_PRIME: + if (input->pairing_available != PASS) { + if (event_ctx->event == + MOD_HDCP_EVENT_WATCHDOG_TIMEOUT) + /* + * 1A-11: consider pairing info timeout + * a failure + */ + fail_and_restart_in_ms(0, &status, output); + else + increment_stay_counter(hdcp); + break; + } else if (input->pairing_info_read != PASS || + input->h_prime_validation != PASS) { + /* 1A-10-1: consider invalid h' a failure */ + fail_and_restart_in_ms(0, &status, output); + break; + } + callback_in_ms(0, output); + set_state_id(hdcp, output, D2_A2_LOCALITY_CHECK); + break; + case D2_A1_SEND_STORED_KM: + if (input->stored_km_write != PASS) { + fail_and_restart_in_ms(0, &status, output); + break; + } + set_watchdog_in_ms(hdcp, 200, output); + set_state_id(hdcp, output, D2_A1_VALIDATE_H_PRIME); + break; + case D2_A1_VALIDATE_H_PRIME: + if (input->h_prime_available != PASS) { + if (event_ctx->event == + MOD_HDCP_EVENT_WATCHDOG_TIMEOUT) + /* 1A-10-2: consider h' timeout a failure */ + fail_and_restart_in_ms(1000, &status, output); + else + increment_stay_counter(hdcp); + break; + } else if (input->h_prime_read != PASS) { + fail_and_restart_in_ms(0, &status, output); + break; + } else if (input->h_prime_validation != PASS) { + /* 1A-10-1: consider invalid h' a failure */ + adjust->hdcp2.force_no_stored_km = 1; + fail_and_restart_in_ms(0, &status, output); + break; + } + callback_in_ms(0, output); + set_state_id(hdcp, output, D2_A2_LOCALITY_CHECK); + break; + case D2_A2_LOCALITY_CHECK: + if (hdcp->state.stay_count > 10 || + input->lc_init_prepare != PASS || + input->lc_init_write != PASS || + input->l_prime_read != PASS) { + /* 1A-12: consider invalid l' a failure */ + fail_and_restart_in_ms(0, &status, output); + break; + } else if (input->l_prime_validation != PASS) { + callback_in_ms(0, output); + increment_stay_counter(hdcp); + break; + } + callback_in_ms(0, output); + set_state_id(hdcp, output, D2_A34_EXCHANGE_KS_AND_TEST_FOR_REPEATER); + break; + case D2_A34_EXCHANGE_KS_AND_TEST_FOR_REPEATER: + if (input->eks_prepare != PASS || + input->eks_write != PASS) { + fail_and_restart_in_ms(0, &status, output); + break; + } + if (conn->is_repeater) { + set_watchdog_in_ms(hdcp, 3000, output); + set_state_id(hdcp, output, D2_A6_WAIT_FOR_RX_ID_LIST); + } else { + callback_in_ms(1, output); + set_state_id(hdcp, output, D2_SEND_CONTENT_STREAM_TYPE); + } + break; + case D2_SEND_CONTENT_STREAM_TYPE: + if (input->rxstatus_read != PASS || + input->reauth_request_check != PASS || + input->link_integrity_check_dp != PASS || + input->content_stream_type_write != PASS) { + fail_and_restart_in_ms(0, &status, output); + break; + } + callback_in_ms(210, output); + set_state_id(hdcp, output, D2_ENABLE_ENCRYPTION); + break; + case D2_ENABLE_ENCRYPTION: + if (input->rxstatus_read != PASS || + input->reauth_request_check != PASS || + input->link_integrity_check_dp != PASS) { + /* + * 1A-07: restart hdcp on REAUTH_REQ + * 1B-08: restart hdcp on REAUTH_REQ + */ + fail_and_restart_in_ms(0, &status, output); + break; + } else if (event_ctx->rx_id_list_ready && conn->is_repeater) { + callback_in_ms(0, output); + set_state_id(hdcp, output, D2_A78_VERIFY_RX_ID_LIST_AND_SEND_ACK); + break; + } else if (input->enable_encryption != PASS || + (is_dp_mst_hdcp(hdcp) && input->stream_encryption_dp != PASS)) { + fail_and_restart_in_ms(0, &status, output); + break; + } + set_state_id(hdcp, output, D2_A5_AUTHENTICATED); + set_auth_complete(hdcp, output); + break; + case D2_A5_AUTHENTICATED: + if (input->rxstatus_read == FAIL || + input->reauth_request_check == FAIL) { + fail_and_restart_in_ms(100, &status, output); + break; + } else if (input->link_integrity_check_dp == FAIL) { + if (hdcp->connection.hdcp2_retry_count >= 1) + adjust->hdcp2.force_type = MOD_HDCP_FORCE_TYPE_0; + fail_and_restart_in_ms(0, &status, output); + break; + } else if (event_ctx->rx_id_list_ready && conn->is_repeater) { + callback_in_ms(0, output); + set_state_id(hdcp, output, D2_A78_VERIFY_RX_ID_LIST_AND_SEND_ACK); + break; + } + increment_stay_counter(hdcp); + break; + case D2_A6_WAIT_FOR_RX_ID_LIST: + if (input->rxstatus_read != PASS || + input->reauth_request_check != PASS || + input->link_integrity_check_dp != PASS) { + fail_and_restart_in_ms(0, &status, output); + break; + } else if (!event_ctx->rx_id_list_ready) { + if (event_ctx->event == MOD_HDCP_EVENT_WATCHDOG_TIMEOUT) + /* 1B-02: consider rx id list timeout a failure */ + fail_and_restart_in_ms(0, &status, output); + else + increment_stay_counter(hdcp); + break; + } + callback_in_ms(0, output); + set_state_id(hdcp, output, D2_A78_VERIFY_RX_ID_LIST_AND_SEND_ACK); + break; + case D2_A78_VERIFY_RX_ID_LIST_AND_SEND_ACK: + if (input->rxstatus_read != PASS || + input->reauth_request_check != PASS || + input->link_integrity_check_dp != PASS || + input->rx_id_list_read != PASS || + input->device_count_check != PASS || + input->rx_id_list_validation != PASS || + input->repeater_auth_ack_write != PASS) { + /* + * 1B-03: consider invalid v' a failure + * 1B-04: consider MAX_DEVS_EXCEEDED a failure + * 1B-05: consider MAX_CASCADE_EXCEEDED a failure + * 1B-06: consider invalid seq_num_V a failure + * 1B-09: consider seq_num_V rollover a failure + */ + fail_and_restart_in_ms(0, &status, output); + break; + } + callback_in_ms(0, output); + set_state_id(hdcp, output, D2_A9_SEND_STREAM_MANAGEMENT); + break; + case D2_A9_SEND_STREAM_MANAGEMENT: + if (input->rxstatus_read != PASS || + input->reauth_request_check != PASS || + input->link_integrity_check_dp != PASS) { + fail_and_restart_in_ms(0, &status, output); + break; + } else if (event_ctx->rx_id_list_ready) { + callback_in_ms(0, output); + set_state_id(hdcp, output, D2_A78_VERIFY_RX_ID_LIST_AND_SEND_ACK); + break; + } else if (input->prepare_stream_manage != PASS || + input->stream_manage_write != PASS) { + if (event_ctx->event == MOD_HDCP_EVENT_CALLBACK) + fail_and_restart_in_ms(0, &status, output); + else + increment_stay_counter(hdcp); + break; + } + callback_in_ms(100, output); + set_state_id(hdcp, output, D2_A9_VALIDATE_STREAM_READY); + break; + case D2_A9_VALIDATE_STREAM_READY: + if (input->rxstatus_read != PASS || + input->reauth_request_check != PASS || + input->link_integrity_check_dp != PASS) { + fail_and_restart_in_ms(0, &status, output); + break; + } else if (event_ctx->rx_id_list_ready) { + callback_in_ms(0, output); + set_state_id(hdcp, output, D2_A78_VERIFY_RX_ID_LIST_AND_SEND_ACK); + break; + } else if (input->stream_ready_read != PASS || + input->stream_ready_validation != PASS) { + /* + * 1B-10-1: restart content stream management + * on invalid M' + * 1B-10-2: consider stream ready timeout a failure + */ + if (hdcp->auth.count.stream_management_retry_count > 10) { + fail_and_restart_in_ms(0, &status, output); + } else if (event_ctx->event == MOD_HDCP_EVENT_CALLBACK) { + hdcp->auth.count.stream_management_retry_count++; + callback_in_ms(0, output); + set_state_id(hdcp, output, D2_A9_SEND_STREAM_MANAGEMENT); + } else { + increment_stay_counter(hdcp); + } + break; + } + callback_in_ms(200, output); + set_state_id(hdcp, output, D2_ENABLE_ENCRYPTION); + break; + default: + status = MOD_HDCP_STATUS_INVALID_STATE; + fail_and_restart_in_ms(0, &status, output); + break; + } + return status; +} diff --git a/drivers/gpu/drm/amd/display/modules/hdcp/hdcp_ddc.c b/drivers/gpu/drm/amd/display/modules/hdcp/hdcp_ddc.c new file mode 100644 index 000000000..f7b5583ee --- /dev/null +++ b/drivers/gpu/drm/amd/display/modules/hdcp/hdcp_ddc.c @@ -0,0 +1,664 @@ +/* + * Copyright 2019 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 "hdcp.h" + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define HDCP_I2C_ADDR 0x3a /* 0x74 >> 1*/ +#define KSV_READ_SIZE 0xf /* 0x6803b - 0x6802c */ +#define HDCP_MAX_AUX_TRANSACTION_SIZE 16 + +#define DP_CP_IRQ (1 << 2) + +enum mod_hdcp_ddc_message_id { + MOD_HDCP_MESSAGE_ID_INVALID = -1, + + /* HDCP 1.4 */ + + MOD_HDCP_MESSAGE_ID_READ_BKSV = 0, + MOD_HDCP_MESSAGE_ID_READ_RI_R0, + MOD_HDCP_MESSAGE_ID_WRITE_AKSV, + MOD_HDCP_MESSAGE_ID_WRITE_AINFO, + MOD_HDCP_MESSAGE_ID_WRITE_AN, + MOD_HDCP_MESSAGE_ID_READ_VH_X, + MOD_HDCP_MESSAGE_ID_READ_VH_0, + MOD_HDCP_MESSAGE_ID_READ_VH_1, + MOD_HDCP_MESSAGE_ID_READ_VH_2, + MOD_HDCP_MESSAGE_ID_READ_VH_3, + MOD_HDCP_MESSAGE_ID_READ_VH_4, + MOD_HDCP_MESSAGE_ID_READ_BCAPS, + MOD_HDCP_MESSAGE_ID_READ_BSTATUS, + MOD_HDCP_MESSAGE_ID_READ_KSV_FIFO, + MOD_HDCP_MESSAGE_ID_READ_BINFO, + + /* HDCP 2.2 */ + + MOD_HDCP_MESSAGE_ID_HDCP2VERSION, + MOD_HDCP_MESSAGE_ID_RX_CAPS, + MOD_HDCP_MESSAGE_ID_WRITE_AKE_INIT, + MOD_HDCP_MESSAGE_ID_READ_AKE_SEND_CERT, + MOD_HDCP_MESSAGE_ID_WRITE_AKE_NO_STORED_KM, + MOD_HDCP_MESSAGE_ID_WRITE_AKE_STORED_KM, + MOD_HDCP_MESSAGE_ID_READ_AKE_SEND_H_PRIME, + MOD_HDCP_MESSAGE_ID_READ_AKE_SEND_PAIRING_INFO, + MOD_HDCP_MESSAGE_ID_WRITE_LC_INIT, + MOD_HDCP_MESSAGE_ID_READ_LC_SEND_L_PRIME, + MOD_HDCP_MESSAGE_ID_WRITE_SKE_SEND_EKS, + MOD_HDCP_MESSAGE_ID_READ_REPEATER_AUTH_SEND_RECEIVERID_LIST, + MOD_HDCP_MESSAGE_ID_READ_REPEATER_AUTH_SEND_RECEIVERID_LIST_PART2, + MOD_HDCP_MESSAGE_ID_WRITE_REPEATER_AUTH_SEND_ACK, + MOD_HDCP_MESSAGE_ID_WRITE_REPEATER_AUTH_STREAM_MANAGE, + MOD_HDCP_MESSAGE_ID_READ_REPEATER_AUTH_STREAM_READY, + MOD_HDCP_MESSAGE_ID_READ_RXSTATUS, + MOD_HDCP_MESSAGE_ID_WRITE_CONTENT_STREAM_TYPE, + + MOD_HDCP_MESSAGE_ID_MAX +}; + +static const uint8_t hdcp_i2c_offsets[] = { + [MOD_HDCP_MESSAGE_ID_READ_BKSV] = 0x0, + [MOD_HDCP_MESSAGE_ID_READ_RI_R0] = 0x8, + [MOD_HDCP_MESSAGE_ID_WRITE_AKSV] = 0x10, + [MOD_HDCP_MESSAGE_ID_WRITE_AINFO] = 0x15, + [MOD_HDCP_MESSAGE_ID_WRITE_AN] = 0x18, + [MOD_HDCP_MESSAGE_ID_READ_VH_X] = 0x20, + [MOD_HDCP_MESSAGE_ID_READ_VH_0] = 0x20, + [MOD_HDCP_MESSAGE_ID_READ_VH_1] = 0x24, + [MOD_HDCP_MESSAGE_ID_READ_VH_2] = 0x28, + [MOD_HDCP_MESSAGE_ID_READ_VH_3] = 0x2C, + [MOD_HDCP_MESSAGE_ID_READ_VH_4] = 0x30, + [MOD_HDCP_MESSAGE_ID_READ_BCAPS] = 0x40, + [MOD_HDCP_MESSAGE_ID_READ_BSTATUS] = 0x41, + [MOD_HDCP_MESSAGE_ID_READ_KSV_FIFO] = 0x43, + [MOD_HDCP_MESSAGE_ID_READ_BINFO] = 0xFF, + [MOD_HDCP_MESSAGE_ID_HDCP2VERSION] = 0x50, + [MOD_HDCP_MESSAGE_ID_WRITE_AKE_INIT] = 0x60, + [MOD_HDCP_MESSAGE_ID_READ_AKE_SEND_CERT] = 0x80, + [MOD_HDCP_MESSAGE_ID_WRITE_AKE_NO_STORED_KM] = 0x60, + [MOD_HDCP_MESSAGE_ID_WRITE_AKE_STORED_KM] = 0x60, + [MOD_HDCP_MESSAGE_ID_READ_AKE_SEND_H_PRIME] = 0x80, + [MOD_HDCP_MESSAGE_ID_READ_AKE_SEND_PAIRING_INFO] = 0x80, + [MOD_HDCP_MESSAGE_ID_WRITE_LC_INIT] = 0x60, + [MOD_HDCP_MESSAGE_ID_READ_LC_SEND_L_PRIME] = 0x80, + [MOD_HDCP_MESSAGE_ID_WRITE_SKE_SEND_EKS] = 0x60, + [MOD_HDCP_MESSAGE_ID_READ_REPEATER_AUTH_SEND_RECEIVERID_LIST] = 0x80, + [MOD_HDCP_MESSAGE_ID_READ_REPEATER_AUTH_SEND_RECEIVERID_LIST_PART2] = 0x80, + [MOD_HDCP_MESSAGE_ID_WRITE_REPEATER_AUTH_SEND_ACK] = 0x60, + [MOD_HDCP_MESSAGE_ID_WRITE_REPEATER_AUTH_STREAM_MANAGE] = 0x60, + [MOD_HDCP_MESSAGE_ID_READ_REPEATER_AUTH_STREAM_READY] = 0x80, + [MOD_HDCP_MESSAGE_ID_READ_RXSTATUS] = 0x70, + [MOD_HDCP_MESSAGE_ID_WRITE_CONTENT_STREAM_TYPE] = 0x0 +}; + +static const uint32_t hdcp_dpcd_addrs[] = { + [MOD_HDCP_MESSAGE_ID_READ_BKSV] = 0x68000, + [MOD_HDCP_MESSAGE_ID_READ_RI_R0] = 0x68005, + [MOD_HDCP_MESSAGE_ID_WRITE_AKSV] = 0x68007, + [MOD_HDCP_MESSAGE_ID_WRITE_AINFO] = 0x6803B, + [MOD_HDCP_MESSAGE_ID_WRITE_AN] = 0x6800c, + [MOD_HDCP_MESSAGE_ID_READ_VH_X] = 0x68014, + [MOD_HDCP_MESSAGE_ID_READ_VH_0] = 0x68014, + [MOD_HDCP_MESSAGE_ID_READ_VH_1] = 0x68018, + [MOD_HDCP_MESSAGE_ID_READ_VH_2] = 0x6801c, + [MOD_HDCP_MESSAGE_ID_READ_VH_3] = 0x68020, + [MOD_HDCP_MESSAGE_ID_READ_VH_4] = 0x68024, + [MOD_HDCP_MESSAGE_ID_READ_BCAPS] = 0x68028, + [MOD_HDCP_MESSAGE_ID_READ_BSTATUS] = 0x68029, + [MOD_HDCP_MESSAGE_ID_READ_KSV_FIFO] = 0x6802c, + [MOD_HDCP_MESSAGE_ID_READ_BINFO] = 0x6802a, + [MOD_HDCP_MESSAGE_ID_RX_CAPS] = 0x6921d, + [MOD_HDCP_MESSAGE_ID_WRITE_AKE_INIT] = 0x69000, + [MOD_HDCP_MESSAGE_ID_READ_AKE_SEND_CERT] = 0x6900b, + [MOD_HDCP_MESSAGE_ID_WRITE_AKE_NO_STORED_KM] = 0x69220, + [MOD_HDCP_MESSAGE_ID_WRITE_AKE_STORED_KM] = 0x692a0, + [MOD_HDCP_MESSAGE_ID_READ_AKE_SEND_H_PRIME] = 0x692c0, + [MOD_HDCP_MESSAGE_ID_READ_AKE_SEND_PAIRING_INFO] = 0x692e0, + [MOD_HDCP_MESSAGE_ID_WRITE_LC_INIT] = 0x692f0, + [MOD_HDCP_MESSAGE_ID_READ_LC_SEND_L_PRIME] = 0x692f8, + [MOD_HDCP_MESSAGE_ID_WRITE_SKE_SEND_EKS] = 0x69318, + [MOD_HDCP_MESSAGE_ID_READ_REPEATER_AUTH_SEND_RECEIVERID_LIST] = 0x69330, + [MOD_HDCP_MESSAGE_ID_READ_REPEATER_AUTH_SEND_RECEIVERID_LIST_PART2] = 0x69340, + [MOD_HDCP_MESSAGE_ID_WRITE_REPEATER_AUTH_SEND_ACK] = 0x693e0, + [MOD_HDCP_MESSAGE_ID_WRITE_REPEATER_AUTH_STREAM_MANAGE] = 0x693f0, + [MOD_HDCP_MESSAGE_ID_READ_REPEATER_AUTH_STREAM_READY] = 0x69473, + [MOD_HDCP_MESSAGE_ID_READ_RXSTATUS] = 0x69493, + [MOD_HDCP_MESSAGE_ID_WRITE_CONTENT_STREAM_TYPE] = 0x69494 +}; + +static enum mod_hdcp_status read(struct mod_hdcp *hdcp, + enum mod_hdcp_ddc_message_id msg_id, + uint8_t *buf, + uint32_t buf_len) +{ + bool success = true; + uint32_t cur_size = 0; + uint32_t data_offset = 0; + + if (is_dp_hdcp(hdcp)) { + while (buf_len > 0) { + cur_size = MIN(buf_len, HDCP_MAX_AUX_TRANSACTION_SIZE); + success = hdcp->config.ddc.funcs.read_dpcd(hdcp->config.ddc.handle, + hdcp_dpcd_addrs[msg_id] + data_offset, + buf + data_offset, + cur_size); + + if (!success) + break; + + buf_len -= cur_size; + data_offset += cur_size; + } + } else { + success = hdcp->config.ddc.funcs.read_i2c( + hdcp->config.ddc.handle, + HDCP_I2C_ADDR, + hdcp_i2c_offsets[msg_id], + buf, + (uint32_t)buf_len); + } + + return success ? MOD_HDCP_STATUS_SUCCESS : MOD_HDCP_STATUS_DDC_FAILURE; +} + +static enum mod_hdcp_status read_repeatedly(struct mod_hdcp *hdcp, + enum mod_hdcp_ddc_message_id msg_id, + uint8_t *buf, + uint32_t buf_len, + uint8_t read_size) +{ + enum mod_hdcp_status status = MOD_HDCP_STATUS_DDC_FAILURE; + uint32_t cur_size = 0; + uint32_t data_offset = 0; + + while (buf_len > 0) { + cur_size = MIN(buf_len, read_size); + status = read(hdcp, msg_id, buf + data_offset, cur_size); + + if (status != MOD_HDCP_STATUS_SUCCESS) + break; + + buf_len -= cur_size; + data_offset += cur_size; + } + + return status; +} + +static enum mod_hdcp_status write(struct mod_hdcp *hdcp, + enum mod_hdcp_ddc_message_id msg_id, + uint8_t *buf, + uint32_t buf_len) +{ + bool success = true; + uint32_t cur_size = 0; + uint32_t data_offset = 0; + + if (is_dp_hdcp(hdcp)) { + while (buf_len > 0) { + cur_size = MIN(buf_len, HDCP_MAX_AUX_TRANSACTION_SIZE); + success = hdcp->config.ddc.funcs.write_dpcd( + hdcp->config.ddc.handle, + hdcp_dpcd_addrs[msg_id] + data_offset, + buf + data_offset, + cur_size); + + if (!success) + break; + + buf_len -= cur_size; + data_offset += cur_size; + } + } else { + hdcp->buf[0] = hdcp_i2c_offsets[msg_id]; + memmove(&hdcp->buf[1], buf, buf_len); + success = hdcp->config.ddc.funcs.write_i2c( + hdcp->config.ddc.handle, + HDCP_I2C_ADDR, + hdcp->buf, + (uint32_t)(buf_len+1)); + } + + return success ? MOD_HDCP_STATUS_SUCCESS : MOD_HDCP_STATUS_DDC_FAILURE; +} + +enum mod_hdcp_status mod_hdcp_read_bksv(struct mod_hdcp *hdcp) +{ + return read(hdcp, MOD_HDCP_MESSAGE_ID_READ_BKSV, + hdcp->auth.msg.hdcp1.bksv, + sizeof(hdcp->auth.msg.hdcp1.bksv)); +} + +enum mod_hdcp_status mod_hdcp_read_bcaps(struct mod_hdcp *hdcp) +{ + return read(hdcp, MOD_HDCP_MESSAGE_ID_READ_BCAPS, + &hdcp->auth.msg.hdcp1.bcaps, + sizeof(hdcp->auth.msg.hdcp1.bcaps)); +} + +enum mod_hdcp_status mod_hdcp_read_bstatus(struct mod_hdcp *hdcp) +{ + enum mod_hdcp_status status; + + if (is_dp_hdcp(hdcp)) + status = read(hdcp, MOD_HDCP_MESSAGE_ID_READ_BSTATUS, + (uint8_t *)&hdcp->auth.msg.hdcp1.bstatus, + 1); + else + status = read(hdcp, MOD_HDCP_MESSAGE_ID_READ_BSTATUS, + (uint8_t *)&hdcp->auth.msg.hdcp1.bstatus, + sizeof(hdcp->auth.msg.hdcp1.bstatus)); + return status; +} + +enum mod_hdcp_status mod_hdcp_read_r0p(struct mod_hdcp *hdcp) +{ + return read(hdcp, MOD_HDCP_MESSAGE_ID_READ_RI_R0, + (uint8_t *)&hdcp->auth.msg.hdcp1.r0p, + sizeof(hdcp->auth.msg.hdcp1.r0p)); +} + +/* special case, reading repeatedly at the same address, don't use read() */ +enum mod_hdcp_status mod_hdcp_read_ksvlist(struct mod_hdcp *hdcp) +{ + enum mod_hdcp_status status; + + if (is_dp_hdcp(hdcp)) + status = read_repeatedly(hdcp, MOD_HDCP_MESSAGE_ID_READ_KSV_FIFO, + hdcp->auth.msg.hdcp1.ksvlist, + hdcp->auth.msg.hdcp1.ksvlist_size, + KSV_READ_SIZE); + else + status = read(hdcp, MOD_HDCP_MESSAGE_ID_READ_KSV_FIFO, + (uint8_t *)&hdcp->auth.msg.hdcp1.ksvlist, + hdcp->auth.msg.hdcp1.ksvlist_size); + return status; +} + +enum mod_hdcp_status mod_hdcp_read_vp(struct mod_hdcp *hdcp) +{ + enum mod_hdcp_status status; + + status = read(hdcp, MOD_HDCP_MESSAGE_ID_READ_VH_0, + &hdcp->auth.msg.hdcp1.vp[0], 4); + if (status != MOD_HDCP_STATUS_SUCCESS) + goto out; + + status = read(hdcp, MOD_HDCP_MESSAGE_ID_READ_VH_1, + &hdcp->auth.msg.hdcp1.vp[4], 4); + if (status != MOD_HDCP_STATUS_SUCCESS) + goto out; + + status = read(hdcp, MOD_HDCP_MESSAGE_ID_READ_VH_2, + &hdcp->auth.msg.hdcp1.vp[8], 4); + if (status != MOD_HDCP_STATUS_SUCCESS) + goto out; + + status = read(hdcp, MOD_HDCP_MESSAGE_ID_READ_VH_3, + &hdcp->auth.msg.hdcp1.vp[12], 4); + if (status != MOD_HDCP_STATUS_SUCCESS) + goto out; + + status = read(hdcp, MOD_HDCP_MESSAGE_ID_READ_VH_4, + &hdcp->auth.msg.hdcp1.vp[16], 4); +out: + return status; +} + +enum mod_hdcp_status mod_hdcp_read_binfo(struct mod_hdcp *hdcp) +{ + enum mod_hdcp_status status; + + if (is_dp_hdcp(hdcp)) + status = read(hdcp, MOD_HDCP_MESSAGE_ID_READ_BINFO, + (uint8_t *)&hdcp->auth.msg.hdcp1.binfo_dp, + sizeof(hdcp->auth.msg.hdcp1.binfo_dp)); + else + status = MOD_HDCP_STATUS_INVALID_OPERATION; + + return status; +} + +enum mod_hdcp_status mod_hdcp_write_aksv(struct mod_hdcp *hdcp) +{ + return write(hdcp, MOD_HDCP_MESSAGE_ID_WRITE_AKSV, + hdcp->auth.msg.hdcp1.aksv, + sizeof(hdcp->auth.msg.hdcp1.aksv)); +} + +enum mod_hdcp_status mod_hdcp_write_ainfo(struct mod_hdcp *hdcp) +{ + return write(hdcp, MOD_HDCP_MESSAGE_ID_WRITE_AINFO, + &hdcp->auth.msg.hdcp1.ainfo, + sizeof(hdcp->auth.msg.hdcp1.ainfo)); +} + +enum mod_hdcp_status mod_hdcp_write_an(struct mod_hdcp *hdcp) +{ + return write(hdcp, MOD_HDCP_MESSAGE_ID_WRITE_AN, + hdcp->auth.msg.hdcp1.an, + sizeof(hdcp->auth.msg.hdcp1.an)); +} + +enum mod_hdcp_status mod_hdcp_read_hdcp2version(struct mod_hdcp *hdcp) +{ + enum mod_hdcp_status status; + + if (is_dp_hdcp(hdcp)) + status = MOD_HDCP_STATUS_INVALID_OPERATION; + else + status = read(hdcp, MOD_HDCP_MESSAGE_ID_HDCP2VERSION, + &hdcp->auth.msg.hdcp2.hdcp2version_hdmi, + sizeof(hdcp->auth.msg.hdcp2.hdcp2version_hdmi)); + + return status; +} + +enum mod_hdcp_status mod_hdcp_read_rxcaps(struct mod_hdcp *hdcp) +{ + enum mod_hdcp_status status; + + if (!is_dp_hdcp(hdcp)) + status = MOD_HDCP_STATUS_INVALID_OPERATION; + else + status = read(hdcp, MOD_HDCP_MESSAGE_ID_RX_CAPS, + hdcp->auth.msg.hdcp2.rxcaps_dp, + sizeof(hdcp->auth.msg.hdcp2.rxcaps_dp)); + + return status; +} + +enum mod_hdcp_status mod_hdcp_read_rxstatus(struct mod_hdcp *hdcp) +{ + enum mod_hdcp_status status; + + if (is_dp_hdcp(hdcp)) { + status = read(hdcp, MOD_HDCP_MESSAGE_ID_READ_RXSTATUS, + &hdcp->auth.msg.hdcp2.rxstatus_dp, + 1); + } else { + status = read(hdcp, MOD_HDCP_MESSAGE_ID_READ_RXSTATUS, + (uint8_t *)&hdcp->auth.msg.hdcp2.rxstatus, + sizeof(hdcp->auth.msg.hdcp2.rxstatus)); + } + return status; +} + +enum mod_hdcp_status mod_hdcp_read_ake_cert(struct mod_hdcp *hdcp) +{ + enum mod_hdcp_status status; + + if (is_dp_hdcp(hdcp)) { + hdcp->auth.msg.hdcp2.ake_cert[0] = HDCP_2_2_AKE_SEND_CERT; + status = read(hdcp, MOD_HDCP_MESSAGE_ID_READ_AKE_SEND_CERT, + hdcp->auth.msg.hdcp2.ake_cert+1, + sizeof(hdcp->auth.msg.hdcp2.ake_cert)-1); + + } else { + status = read(hdcp, MOD_HDCP_MESSAGE_ID_READ_AKE_SEND_CERT, + hdcp->auth.msg.hdcp2.ake_cert, + sizeof(hdcp->auth.msg.hdcp2.ake_cert)); + } + return status; +} + +enum mod_hdcp_status mod_hdcp_read_h_prime(struct mod_hdcp *hdcp) +{ + enum mod_hdcp_status status; + + if (is_dp_hdcp(hdcp)) { + hdcp->auth.msg.hdcp2.ake_h_prime[0] = HDCP_2_2_AKE_SEND_HPRIME; + status = read(hdcp, MOD_HDCP_MESSAGE_ID_READ_AKE_SEND_H_PRIME, + hdcp->auth.msg.hdcp2.ake_h_prime+1, + sizeof(hdcp->auth.msg.hdcp2.ake_h_prime)-1); + + } else { + status = read(hdcp, MOD_HDCP_MESSAGE_ID_READ_AKE_SEND_H_PRIME, + hdcp->auth.msg.hdcp2.ake_h_prime, + sizeof(hdcp->auth.msg.hdcp2.ake_h_prime)); + } + return status; +} + +enum mod_hdcp_status mod_hdcp_read_pairing_info(struct mod_hdcp *hdcp) +{ + enum mod_hdcp_status status; + + if (is_dp_hdcp(hdcp)) { + hdcp->auth.msg.hdcp2.ake_pairing_info[0] = HDCP_2_2_AKE_SEND_PAIRING_INFO; + status = read(hdcp, MOD_HDCP_MESSAGE_ID_READ_AKE_SEND_PAIRING_INFO, + hdcp->auth.msg.hdcp2.ake_pairing_info+1, + sizeof(hdcp->auth.msg.hdcp2.ake_pairing_info)-1); + + } else { + status = read(hdcp, MOD_HDCP_MESSAGE_ID_READ_AKE_SEND_PAIRING_INFO, + hdcp->auth.msg.hdcp2.ake_pairing_info, + sizeof(hdcp->auth.msg.hdcp2.ake_pairing_info)); + } + return status; +} + +enum mod_hdcp_status mod_hdcp_read_l_prime(struct mod_hdcp *hdcp) +{ + enum mod_hdcp_status status; + + if (is_dp_hdcp(hdcp)) { + hdcp->auth.msg.hdcp2.lc_l_prime[0] = HDCP_2_2_LC_SEND_LPRIME; + status = read(hdcp, MOD_HDCP_MESSAGE_ID_READ_LC_SEND_L_PRIME, + hdcp->auth.msg.hdcp2.lc_l_prime+1, + sizeof(hdcp->auth.msg.hdcp2.lc_l_prime)-1); + + } else { + status = read(hdcp, MOD_HDCP_MESSAGE_ID_READ_LC_SEND_L_PRIME, + hdcp->auth.msg.hdcp2.lc_l_prime, + sizeof(hdcp->auth.msg.hdcp2.lc_l_prime)); + } + return status; +} + +enum mod_hdcp_status mod_hdcp_read_rx_id_list(struct mod_hdcp *hdcp) +{ + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + if (is_dp_hdcp(hdcp)) { + uint32_t device_count = 0; + uint32_t rx_id_list_size = 0; + uint32_t bytes_read = 0; + + hdcp->auth.msg.hdcp2.rx_id_list[0] = HDCP_2_2_REP_SEND_RECVID_LIST; + status = read(hdcp, MOD_HDCP_MESSAGE_ID_READ_REPEATER_AUTH_SEND_RECEIVERID_LIST, + hdcp->auth.msg.hdcp2.rx_id_list+1, + HDCP_MAX_AUX_TRANSACTION_SIZE); + if (status == MOD_HDCP_STATUS_SUCCESS) { + bytes_read = HDCP_MAX_AUX_TRANSACTION_SIZE; + device_count = HDCP_2_2_DEV_COUNT_LO(hdcp->auth.msg.hdcp2.rx_id_list[2]) + + (HDCP_2_2_DEV_COUNT_HI(hdcp->auth.msg.hdcp2.rx_id_list[1]) << 4); + rx_id_list_size = MIN((21 + 5 * device_count), + (sizeof(hdcp->auth.msg.hdcp2.rx_id_list) - 1)); + status = read(hdcp, MOD_HDCP_MESSAGE_ID_READ_REPEATER_AUTH_SEND_RECEIVERID_LIST_PART2, + hdcp->auth.msg.hdcp2.rx_id_list + 1 + bytes_read, + (rx_id_list_size - 1) / HDCP_MAX_AUX_TRANSACTION_SIZE * HDCP_MAX_AUX_TRANSACTION_SIZE); + } + } else { + status = read(hdcp, MOD_HDCP_MESSAGE_ID_READ_REPEATER_AUTH_SEND_RECEIVERID_LIST, + hdcp->auth.msg.hdcp2.rx_id_list, + hdcp->auth.msg.hdcp2.rx_id_list_size); + } + return status; +} + +enum mod_hdcp_status mod_hdcp_read_stream_ready(struct mod_hdcp *hdcp) +{ + enum mod_hdcp_status status; + + if (is_dp_hdcp(hdcp)) { + hdcp->auth.msg.hdcp2.repeater_auth_stream_ready[0] = HDCP_2_2_REP_STREAM_READY; + status = read(hdcp, MOD_HDCP_MESSAGE_ID_READ_REPEATER_AUTH_STREAM_READY, + hdcp->auth.msg.hdcp2.repeater_auth_stream_ready+1, + sizeof(hdcp->auth.msg.hdcp2.repeater_auth_stream_ready)-1); + + } else { + status = read(hdcp, MOD_HDCP_MESSAGE_ID_READ_REPEATER_AUTH_STREAM_READY, + hdcp->auth.msg.hdcp2.repeater_auth_stream_ready, + sizeof(hdcp->auth.msg.hdcp2.repeater_auth_stream_ready)); + } + return status; +} + +enum mod_hdcp_status mod_hdcp_write_ake_init(struct mod_hdcp *hdcp) +{ + enum mod_hdcp_status status; + + if (is_dp_hdcp(hdcp)) + status = write(hdcp, MOD_HDCP_MESSAGE_ID_WRITE_AKE_INIT, + hdcp->auth.msg.hdcp2.ake_init+1, + sizeof(hdcp->auth.msg.hdcp2.ake_init)-1); + else + status = write(hdcp, MOD_HDCP_MESSAGE_ID_WRITE_AKE_INIT, + hdcp->auth.msg.hdcp2.ake_init, + sizeof(hdcp->auth.msg.hdcp2.ake_init)); + return status; +} + +enum mod_hdcp_status mod_hdcp_write_no_stored_km(struct mod_hdcp *hdcp) +{ + enum mod_hdcp_status status; + + if (is_dp_hdcp(hdcp)) + status = write(hdcp, MOD_HDCP_MESSAGE_ID_WRITE_AKE_NO_STORED_KM, + hdcp->auth.msg.hdcp2.ake_no_stored_km+1, + sizeof(hdcp->auth.msg.hdcp2.ake_no_stored_km)-1); + else + status = write(hdcp, MOD_HDCP_MESSAGE_ID_WRITE_AKE_NO_STORED_KM, + hdcp->auth.msg.hdcp2.ake_no_stored_km, + sizeof(hdcp->auth.msg.hdcp2.ake_no_stored_km)); + return status; +} + +enum mod_hdcp_status mod_hdcp_write_stored_km(struct mod_hdcp *hdcp) +{ + enum mod_hdcp_status status; + + if (is_dp_hdcp(hdcp)) + status = write(hdcp, MOD_HDCP_MESSAGE_ID_WRITE_AKE_STORED_KM, + hdcp->auth.msg.hdcp2.ake_stored_km+1, + sizeof(hdcp->auth.msg.hdcp2.ake_stored_km)-1); + else + status = write(hdcp, MOD_HDCP_MESSAGE_ID_WRITE_AKE_STORED_KM, + hdcp->auth.msg.hdcp2.ake_stored_km, + sizeof(hdcp->auth.msg.hdcp2.ake_stored_km)); + return status; +} + +enum mod_hdcp_status mod_hdcp_write_lc_init(struct mod_hdcp *hdcp) +{ + enum mod_hdcp_status status; + + if (is_dp_hdcp(hdcp)) + status = write(hdcp, MOD_HDCP_MESSAGE_ID_WRITE_LC_INIT, + hdcp->auth.msg.hdcp2.lc_init+1, + sizeof(hdcp->auth.msg.hdcp2.lc_init)-1); + else + status = write(hdcp, MOD_HDCP_MESSAGE_ID_WRITE_LC_INIT, + hdcp->auth.msg.hdcp2.lc_init, + sizeof(hdcp->auth.msg.hdcp2.lc_init)); + return status; +} + +enum mod_hdcp_status mod_hdcp_write_eks(struct mod_hdcp *hdcp) +{ + enum mod_hdcp_status status; + + if (is_dp_hdcp(hdcp)) + status = write(hdcp, + MOD_HDCP_MESSAGE_ID_WRITE_SKE_SEND_EKS, + hdcp->auth.msg.hdcp2.ske_eks+1, + sizeof(hdcp->auth.msg.hdcp2.ske_eks)-1); + else + status = write(hdcp, + MOD_HDCP_MESSAGE_ID_WRITE_SKE_SEND_EKS, + hdcp->auth.msg.hdcp2.ske_eks, + sizeof(hdcp->auth.msg.hdcp2.ske_eks)); + return status; +} + +enum mod_hdcp_status mod_hdcp_write_repeater_auth_ack(struct mod_hdcp *hdcp) +{ + enum mod_hdcp_status status; + + if (is_dp_hdcp(hdcp)) + status = write(hdcp, MOD_HDCP_MESSAGE_ID_WRITE_REPEATER_AUTH_SEND_ACK, + hdcp->auth.msg.hdcp2.repeater_auth_ack+1, + sizeof(hdcp->auth.msg.hdcp2.repeater_auth_ack)-1); + else + status = write(hdcp, MOD_HDCP_MESSAGE_ID_WRITE_REPEATER_AUTH_SEND_ACK, + hdcp->auth.msg.hdcp2.repeater_auth_ack, + sizeof(hdcp->auth.msg.hdcp2.repeater_auth_ack)); + return status; +} + +enum mod_hdcp_status mod_hdcp_write_stream_manage(struct mod_hdcp *hdcp) +{ + enum mod_hdcp_status status; + + if (is_dp_hdcp(hdcp)) + status = write(hdcp, + MOD_HDCP_MESSAGE_ID_WRITE_REPEATER_AUTH_STREAM_MANAGE, + hdcp->auth.msg.hdcp2.repeater_auth_stream_manage+1, + hdcp->auth.msg.hdcp2.stream_manage_size-1); + else + status = write(hdcp, + MOD_HDCP_MESSAGE_ID_WRITE_REPEATER_AUTH_STREAM_MANAGE, + hdcp->auth.msg.hdcp2.repeater_auth_stream_manage, + hdcp->auth.msg.hdcp2.stream_manage_size); + return status; +} + +enum mod_hdcp_status mod_hdcp_write_content_type(struct mod_hdcp *hdcp) +{ + enum mod_hdcp_status status; + + if (is_dp_hdcp(hdcp)) + status = write(hdcp, MOD_HDCP_MESSAGE_ID_WRITE_CONTENT_STREAM_TYPE, + hdcp->auth.msg.hdcp2.content_stream_type_dp+1, + sizeof(hdcp->auth.msg.hdcp2.content_stream_type_dp)-1); + else + status = MOD_HDCP_STATUS_INVALID_OPERATION; + return status; +} + +enum mod_hdcp_status mod_hdcp_clear_cp_irq_status(struct mod_hdcp *hdcp) +{ + uint8_t clear_cp_irq_bit = DP_CP_IRQ; + uint32_t size = 1; + + if (is_dp_hdcp(hdcp)) { + uint32_t cp_irq_addrs = (hdcp->connection.link.dp.rev >= 0x14) + ? DP_DEVICE_SERVICE_IRQ_VECTOR_ESI0:DP_DEVICE_SERVICE_IRQ_VECTOR; + return hdcp->config.ddc.funcs.write_dpcd(hdcp->config.ddc.handle, cp_irq_addrs, + &clear_cp_irq_bit, size) ? MOD_HDCP_STATUS_SUCCESS : MOD_HDCP_STATUS_DDC_FAILURE; + } + + return MOD_HDCP_STATUS_INVALID_OPERATION; +} diff --git a/drivers/gpu/drm/amd/display/modules/hdcp/hdcp_log.c b/drivers/gpu/drm/amd/display/modules/hdcp/hdcp_log.c new file mode 100644 index 000000000..6b3b5f610 --- /dev/null +++ b/drivers/gpu/drm/amd/display/modules/hdcp/hdcp_log.c @@ -0,0 +1,361 @@ +/* + * Copyright 2019 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 "hdcp.h" + +void mod_hdcp_dump_binary_message(uint8_t *msg, uint32_t msg_size, + uint8_t *buf, uint32_t buf_size) +{ + const uint8_t bytes_per_line = 16, + byte_size = 3, + newline_size = 1, + terminator_size = 1; + uint32_t line_count = msg_size / bytes_per_line, + trailing_bytes = msg_size % bytes_per_line; + uint32_t target_size = (byte_size * bytes_per_line + newline_size) * line_count + + byte_size * trailing_bytes + newline_size + terminator_size; + uint32_t buf_pos = 0; + uint32_t i = 0; + + if (buf_size >= target_size) { + for (i = 0; i < msg_size; i++) { + if (i % bytes_per_line == 0) + buf[buf_pos++] = '\n'; + sprintf(&buf[buf_pos], "%02X ", msg[i]); + buf_pos += byte_size; + } + buf[buf_pos++] = '\0'; + } +} + +void mod_hdcp_log_ddc_trace(struct mod_hdcp *hdcp) +{ + if (is_hdcp1(hdcp)) { + HDCP_DDC_READ_TRACE(hdcp, "BKSV", hdcp->auth.msg.hdcp1.bksv, + sizeof(hdcp->auth.msg.hdcp1.bksv)); + HDCP_DDC_READ_TRACE(hdcp, "BCAPS", &hdcp->auth.msg.hdcp1.bcaps, + sizeof(hdcp->auth.msg.hdcp1.bcaps)); + HDCP_DDC_READ_TRACE(hdcp, "BSTATUS", + (uint8_t *)&hdcp->auth.msg.hdcp1.bstatus, + sizeof(hdcp->auth.msg.hdcp1.bstatus)); + HDCP_DDC_WRITE_TRACE(hdcp, "AN", hdcp->auth.msg.hdcp1.an, + sizeof(hdcp->auth.msg.hdcp1.an)); + HDCP_DDC_WRITE_TRACE(hdcp, "AKSV", hdcp->auth.msg.hdcp1.aksv, + sizeof(hdcp->auth.msg.hdcp1.aksv)); + HDCP_DDC_WRITE_TRACE(hdcp, "AINFO", &hdcp->auth.msg.hdcp1.ainfo, + sizeof(hdcp->auth.msg.hdcp1.ainfo)); + HDCP_DDC_READ_TRACE(hdcp, "RI' / R0'", + (uint8_t *)&hdcp->auth.msg.hdcp1.r0p, + sizeof(hdcp->auth.msg.hdcp1.r0p)); + HDCP_DDC_READ_TRACE(hdcp, "BINFO", + (uint8_t *)&hdcp->auth.msg.hdcp1.binfo_dp, + sizeof(hdcp->auth.msg.hdcp1.binfo_dp)); + HDCP_DDC_READ_TRACE(hdcp, "KSVLIST", hdcp->auth.msg.hdcp1.ksvlist, + hdcp->auth.msg.hdcp1.ksvlist_size); + HDCP_DDC_READ_TRACE(hdcp, "V'", hdcp->auth.msg.hdcp1.vp, + sizeof(hdcp->auth.msg.hdcp1.vp)); + } else if (is_hdcp2(hdcp)) { + HDCP_DDC_READ_TRACE(hdcp, "HDCP2Version", + &hdcp->auth.msg.hdcp2.hdcp2version_hdmi, + sizeof(hdcp->auth.msg.hdcp2.hdcp2version_hdmi)); + HDCP_DDC_READ_TRACE(hdcp, "Rx Caps", hdcp->auth.msg.hdcp2.rxcaps_dp, + sizeof(hdcp->auth.msg.hdcp2.rxcaps_dp)); + HDCP_DDC_WRITE_TRACE(hdcp, "AKE Init", hdcp->auth.msg.hdcp2.ake_init, + sizeof(hdcp->auth.msg.hdcp2.ake_init)); + HDCP_DDC_READ_TRACE(hdcp, "AKE Cert", hdcp->auth.msg.hdcp2.ake_cert, + sizeof(hdcp->auth.msg.hdcp2.ake_cert)); + HDCP_DDC_WRITE_TRACE(hdcp, "Stored KM", + hdcp->auth.msg.hdcp2.ake_stored_km, + sizeof(hdcp->auth.msg.hdcp2.ake_stored_km)); + HDCP_DDC_WRITE_TRACE(hdcp, "No Stored KM", + hdcp->auth.msg.hdcp2.ake_no_stored_km, + sizeof(hdcp->auth.msg.hdcp2.ake_no_stored_km)); + HDCP_DDC_READ_TRACE(hdcp, "H'", hdcp->auth.msg.hdcp2.ake_h_prime, + sizeof(hdcp->auth.msg.hdcp2.ake_h_prime)); + HDCP_DDC_READ_TRACE(hdcp, "Pairing Info", + hdcp->auth.msg.hdcp2.ake_pairing_info, + sizeof(hdcp->auth.msg.hdcp2.ake_pairing_info)); + HDCP_DDC_WRITE_TRACE(hdcp, "LC Init", hdcp->auth.msg.hdcp2.lc_init, + sizeof(hdcp->auth.msg.hdcp2.lc_init)); + HDCP_DDC_READ_TRACE(hdcp, "L'", hdcp->auth.msg.hdcp2.lc_l_prime, + sizeof(hdcp->auth.msg.hdcp2.lc_l_prime)); + HDCP_DDC_WRITE_TRACE(hdcp, "Exchange KS", hdcp->auth.msg.hdcp2.ske_eks, + sizeof(hdcp->auth.msg.hdcp2.ske_eks)); + HDCP_DDC_READ_TRACE(hdcp, "Rx Status", + (uint8_t *)&hdcp->auth.msg.hdcp2.rxstatus, + sizeof(hdcp->auth.msg.hdcp2.rxstatus)); + HDCP_DDC_READ_TRACE(hdcp, "Rx Id List", + hdcp->auth.msg.hdcp2.rx_id_list, + hdcp->auth.msg.hdcp2.rx_id_list_size); + HDCP_DDC_WRITE_TRACE(hdcp, "Rx Id List Ack", + hdcp->auth.msg.hdcp2.repeater_auth_ack, + sizeof(hdcp->auth.msg.hdcp2.repeater_auth_ack)); + HDCP_DDC_WRITE_TRACE(hdcp, "Content Stream Management", + hdcp->auth.msg.hdcp2.repeater_auth_stream_manage, + hdcp->auth.msg.hdcp2.stream_manage_size); + HDCP_DDC_READ_TRACE(hdcp, "Stream Ready", + hdcp->auth.msg.hdcp2.repeater_auth_stream_ready, + sizeof(hdcp->auth.msg.hdcp2.repeater_auth_stream_ready)); + HDCP_DDC_WRITE_TRACE(hdcp, "Content Stream Type", + hdcp->auth.msg.hdcp2.content_stream_type_dp, + sizeof(hdcp->auth.msg.hdcp2.content_stream_type_dp)); + } +} + +char *mod_hdcp_status_to_str(int32_t status) +{ + switch (status) { + case MOD_HDCP_STATUS_SUCCESS: + return "MOD_HDCP_STATUS_SUCCESS"; + case MOD_HDCP_STATUS_FAILURE: + return "MOD_HDCP_STATUS_FAILURE"; + case MOD_HDCP_STATUS_RESET_NEEDED: + return "MOD_HDCP_STATUS_RESET_NEEDED"; + case MOD_HDCP_STATUS_DISPLAY_OUT_OF_BOUND: + return "MOD_HDCP_STATUS_DISPLAY_OUT_OF_BOUND"; + case MOD_HDCP_STATUS_DISPLAY_NOT_FOUND: + return "MOD_HDCP_STATUS_DISPLAY_NOT_FOUND"; + case MOD_HDCP_STATUS_INVALID_STATE: + return "MOD_HDCP_STATUS_INVALID_STATE"; + case MOD_HDCP_STATUS_NOT_IMPLEMENTED: + return "MOD_HDCP_STATUS_NOT_IMPLEMENTED"; + case MOD_HDCP_STATUS_INTERNAL_POLICY_FAILURE: + return "MOD_HDCP_STATUS_INTERNAL_POLICY_FAILURE"; + case MOD_HDCP_STATUS_UPDATE_TOPOLOGY_FAILURE: + return "MOD_HDCP_STATUS_UPDATE_TOPOLOGY_FAILURE"; + case MOD_HDCP_STATUS_CREATE_PSP_SERVICE_FAILURE: + return "MOD_HDCP_STATUS_CREATE_PSP_SERVICE_FAILURE"; + case MOD_HDCP_STATUS_DESTROY_PSP_SERVICE_FAILURE: + return "MOD_HDCP_STATUS_DESTROY_PSP_SERVICE_FAILURE"; + case MOD_HDCP_STATUS_HDCP1_CREATE_SESSION_FAILURE: + return "MOD_HDCP_STATUS_HDCP1_CREATE_SESSION_FAILURE"; + case MOD_HDCP_STATUS_HDCP1_DESTROY_SESSION_FAILURE: + return "MOD_HDCP_STATUS_HDCP1_DESTROY_SESSION_FAILURE"; + case MOD_HDCP_STATUS_HDCP1_VALIDATE_ENCRYPTION_FAILURE: + return "MOD_HDCP_STATUS_HDCP1_VALIDATE_ENCRYPTION_FAILURE"; + case MOD_HDCP_STATUS_HDCP1_NOT_HDCP_REPEATER: + return "MOD_HDCP_STATUS_HDCP1_NOT_HDCP_REPEATER"; + case MOD_HDCP_STATUS_HDCP1_NOT_CAPABLE: + return "MOD_HDCP_STATUS_HDCP1_NOT_CAPABLE"; + case MOD_HDCP_STATUS_HDCP1_R0_PRIME_PENDING: + return "MOD_HDCP_STATUS_HDCP1_R0_PRIME_PENDING"; + case MOD_HDCP_STATUS_HDCP1_VALIDATE_RX_FAILURE: + return "MOD_HDCP_STATUS_HDCP1_VALIDATE_RX_FAILURE"; + case MOD_HDCP_STATUS_HDCP1_BKSV_REVOKED: + return "MOD_HDCP_STATUS_HDCP1_BKSV_REVOKED"; + case MOD_HDCP_STATUS_HDCP1_KSV_LIST_NOT_READY: + return "MOD_HDCP_STATUS_HDCP1_KSV_LIST_NOT_READY"; + case MOD_HDCP_STATUS_HDCP1_VALIDATE_KSV_LIST_FAILURE: + return "MOD_HDCP_STATUS_HDCP1_VALIDATE_KSV_LIST_FAILURE"; + case MOD_HDCP_STATUS_HDCP1_KSV_LIST_REVOKED: + return "MOD_HDCP_STATUS_HDCP1_KSV_LIST_REVOKED"; + case MOD_HDCP_STATUS_HDCP1_ENABLE_ENCRYPTION_FAILURE: + return "MOD_HDCP_STATUS_HDCP1_ENABLE_ENCRYPTION_FAILURE"; + case MOD_HDCP_STATUS_HDCP1_ENABLE_STREAM_ENCRYPTION_FAILURE: + return "MOD_HDCP_STATUS_HDCP1_ENABLE_STREAM_ENCRYPTION_FAILURE"; + case MOD_HDCP_STATUS_HDCP1_MAX_CASCADE_EXCEEDED_FAILURE: + return "MOD_HDCP_STATUS_HDCP1_MAX_CASCADE_EXCEEDED_FAILURE"; + case MOD_HDCP_STATUS_HDCP1_MAX_DEVS_EXCEEDED_FAILURE: + return "MOD_HDCP_STATUS_HDCP1_MAX_DEVS_EXCEEDED_FAILURE"; + case MOD_HDCP_STATUS_HDCP1_DEVICE_COUNT_MISMATCH_FAILURE: + return "MOD_HDCP_STATUS_HDCP1_DEVICE_COUNT_MISMATCH_FAILURE"; + case MOD_HDCP_STATUS_HDCP1_LINK_INTEGRITY_FAILURE: + return "MOD_HDCP_STATUS_HDCP1_LINK_INTEGRITY_FAILURE"; + case MOD_HDCP_STATUS_HDCP1_REAUTH_REQUEST_ISSUED: + return "MOD_HDCP_STATUS_HDCP1_REAUTH_REQUEST_ISSUED"; + case MOD_HDCP_STATUS_HDCP1_LINK_MAINTENANCE_FAILURE: + return "MOD_HDCP_STATUS_HDCP1_LINK_MAINTENANCE_FAILURE"; + case MOD_HDCP_STATUS_HDCP1_INVALID_BKSV: + return "MOD_HDCP_STATUS_HDCP1_INVALID_BKSV"; + case MOD_HDCP_STATUS_DDC_FAILURE: + return "MOD_HDCP_STATUS_DDC_FAILURE"; + case MOD_HDCP_STATUS_INVALID_OPERATION: + return "MOD_HDCP_STATUS_INVALID_OPERATION"; + case MOD_HDCP_STATUS_HDCP2_NOT_CAPABLE: + return "MOD_HDCP_STATUS_HDCP2_NOT_CAPABLE"; + case MOD_HDCP_STATUS_HDCP2_CREATE_SESSION_FAILURE: + return "MOD_HDCP_STATUS_HDCP2_CREATE_SESSION_FAILURE"; + case MOD_HDCP_STATUS_HDCP2_DESTROY_SESSION_FAILURE: + return "MOD_HDCP_STATUS_HDCP2_DESTROY_SESSION_FAILURE"; + case MOD_HDCP_STATUS_HDCP2_PREP_AKE_INIT_FAILURE: + return "MOD_HDCP_STATUS_HDCP2_PREP_AKE_INIT_FAILURE"; + case MOD_HDCP_STATUS_HDCP2_AKE_CERT_PENDING: + return "MOD_HDCP_STATUS_HDCP2_AKE_CERT_PENDING"; + case MOD_HDCP_STATUS_HDCP2_H_PRIME_PENDING: + return "MOD_HDCP_STATUS_HDCP2_H_PRIME_PENDING"; + case MOD_HDCP_STATUS_HDCP2_PAIRING_INFO_PENDING: + return "MOD_HDCP_STATUS_HDCP2_PAIRING_INFO_PENDING"; + case MOD_HDCP_STATUS_HDCP2_VALIDATE_AKE_CERT_FAILURE: + return "MOD_HDCP_STATUS_HDCP2_VALIDATE_AKE_CERT_FAILURE"; + case MOD_HDCP_STATUS_HDCP2_AKE_CERT_REVOKED: + return "MOD_HDCP_STATUS_HDCP2_AKE_CERT_REVOKED"; + case MOD_HDCP_STATUS_HDCP2_VALIDATE_H_PRIME_FAILURE: + return "MOD_HDCP_STATUS_HDCP2_VALIDATE_H_PRIME_FAILURE"; + case MOD_HDCP_STATUS_HDCP2_VALIDATE_PAIRING_INFO_FAILURE: + return "MOD_HDCP_STATUS_HDCP2_VALIDATE_PAIRING_INFO_FAILURE"; + case MOD_HDCP_STATUS_HDCP2_PREP_LC_INIT_FAILURE: + return "MOD_HDCP_STATUS_HDCP2_PREP_LC_INIT_FAILURE"; + case MOD_HDCP_STATUS_HDCP2_L_PRIME_PENDING: + return "MOD_HDCP_STATUS_HDCP2_L_PRIME_PENDING"; + case MOD_HDCP_STATUS_HDCP2_VALIDATE_L_PRIME_FAILURE: + return "MOD_HDCP_STATUS_HDCP2_VALIDATE_L_PRIME_FAILURE"; + case MOD_HDCP_STATUS_HDCP2_PREP_EKS_FAILURE: + return "MOD_HDCP_STATUS_HDCP2_PREP_EKS_FAILURE"; + case MOD_HDCP_STATUS_HDCP2_ENABLE_ENCRYPTION_FAILURE: + return "MOD_HDCP_STATUS_HDCP2_ENABLE_ENCRYPTION_FAILURE"; + case MOD_HDCP_STATUS_HDCP2_VALIDATE_RX_ID_LIST_FAILURE: + return "MOD_HDCP_STATUS_HDCP2_VALIDATE_RX_ID_LIST_FAILURE"; + case MOD_HDCP_STATUS_HDCP2_RX_ID_LIST_REVOKED: + return "MOD_HDCP_STATUS_HDCP2_RX_ID_LIST_REVOKED"; + case MOD_HDCP_STATUS_HDCP2_RX_ID_LIST_NOT_READY: + return "MOD_HDCP_STATUS_HDCP2_RX_ID_LIST_NOT_READY"; + case MOD_HDCP_STATUS_HDCP2_ENABLE_STREAM_ENCRYPTION_FAILURE: + return "MOD_HDCP_STATUS_HDCP2_ENABLE_STREAM_ENCRYPTION_FAILURE"; + case MOD_HDCP_STATUS_HDCP2_STREAM_READY_PENDING: + return "MOD_HDCP_STATUS_HDCP2_STREAM_READY_PENDING"; + case MOD_HDCP_STATUS_HDCP2_VALIDATE_STREAM_READY_FAILURE: + return "MOD_HDCP_STATUS_HDCP2_VALIDATE_STREAM_READY_FAILURE"; + case MOD_HDCP_STATUS_HDCP2_PREPARE_STREAM_MANAGEMENT_FAILURE: + return "MOD_HDCP_STATUS_HDCP2_PREPARE_STREAM_MANAGEMENT_FAILURE"; + case MOD_HDCP_STATUS_HDCP2_REAUTH_REQUEST: + return "MOD_HDCP_STATUS_HDCP2_REAUTH_REQUEST"; + case MOD_HDCP_STATUS_HDCP2_REAUTH_LINK_INTEGRITY_FAILURE: + return "MOD_HDCP_STATUS_HDCP2_REAUTH_LINK_INTEGRITY_FAILURE"; + case MOD_HDCP_STATUS_HDCP2_DEVICE_COUNT_MISMATCH_FAILURE: + return "MOD_HDCP_STATUS_HDCP2_DEVICE_COUNT_MISMATCH_FAILURE"; + case MOD_HDCP_STATUS_UNSUPPORTED_PSP_VER_FAILURE: + return "MOD_HDCP_STATUS_UNSUPPORTED_PSP_VER_FAILURE"; + default: + return "MOD_HDCP_STATUS_UNKNOWN"; + } +} + +char *mod_hdcp_state_id_to_str(int32_t id) +{ + switch (id) { + case HDCP_UNINITIALIZED: + return "HDCP_UNINITIALIZED"; + case HDCP_INITIALIZED: + return "HDCP_INITIALIZED"; + case HDCP_CP_NOT_DESIRED: + return "HDCP_CP_NOT_DESIRED"; + case H1_A0_WAIT_FOR_ACTIVE_RX: + return "H1_A0_WAIT_FOR_ACTIVE_RX"; + case H1_A1_EXCHANGE_KSVS: + return "H1_A1_EXCHANGE_KSVS"; + case H1_A2_COMPUTATIONS_A3_VALIDATE_RX_A6_TEST_FOR_REPEATER: + return "H1_A2_COMPUTATIONS_A3_VALIDATE_RX_A6_TEST_FOR_REPEATER"; + case H1_A45_AUTHENTICATED: + return "H1_A45_AUTHENTICATED"; + case H1_A8_WAIT_FOR_READY: + return "H1_A8_WAIT_FOR_READY"; + case H1_A9_READ_KSV_LIST: + return "H1_A9_READ_KSV_LIST"; + case D1_A0_DETERMINE_RX_HDCP_CAPABLE: + return "D1_A0_DETERMINE_RX_HDCP_CAPABLE"; + case D1_A1_EXCHANGE_KSVS: + return "D1_A1_EXCHANGE_KSVS"; + case D1_A23_WAIT_FOR_R0_PRIME: + return "D1_A23_WAIT_FOR_R0_PRIME"; + case D1_A2_COMPUTATIONS_A3_VALIDATE_RX_A5_TEST_FOR_REPEATER: + return "D1_A2_COMPUTATIONS_A3_VALIDATE_RX_A5_TEST_FOR_REPEATER"; + case D1_A4_AUTHENTICATED: + return "D1_A4_AUTHENTICATED"; + case D1_A6_WAIT_FOR_READY: + return "D1_A6_WAIT_FOR_READY"; + case D1_A7_READ_KSV_LIST: + return "D1_A7_READ_KSV_LIST"; + case H2_A0_KNOWN_HDCP2_CAPABLE_RX: + return "H2_A0_KNOWN_HDCP2_CAPABLE_RX"; + case H2_A1_SEND_AKE_INIT: + return "H2_A1_SEND_AKE_INIT"; + case H2_A1_VALIDATE_AKE_CERT: + return "H2_A1_VALIDATE_AKE_CERT"; + case H2_A1_SEND_NO_STORED_KM: + return "H2_A1_SEND_NO_STORED_KM"; + case H2_A1_READ_H_PRIME: + return "H2_A1_READ_H_PRIME"; + case H2_A1_READ_PAIRING_INFO_AND_VALIDATE_H_PRIME: + return "H2_A1_READ_PAIRING_INFO_AND_VALIDATE_H_PRIME"; + case H2_A1_SEND_STORED_KM: + return "H2_A1_SEND_STORED_KM"; + case H2_A1_VALIDATE_H_PRIME: + return "H2_A1_VALIDATE_H_PRIME"; + case H2_A2_LOCALITY_CHECK: + return "H2_A2_LOCALITY_CHECK"; + case H2_A3_EXCHANGE_KS_AND_TEST_FOR_REPEATER: + return "H2_A3_EXCHANGE_KS_AND_TEST_FOR_REPEATER"; + case H2_ENABLE_ENCRYPTION: + return "H2_ENABLE_ENCRYPTION"; + case H2_A5_AUTHENTICATED: + return "H2_A5_AUTHENTICATED"; + case H2_A6_WAIT_FOR_RX_ID_LIST: + return "H2_A6_WAIT_FOR_RX_ID_LIST"; + case H2_A78_VERIFY_RX_ID_LIST_AND_SEND_ACK: + return "H2_A78_VERIFY_RX_ID_LIST_AND_SEND_ACK"; + case H2_A9_SEND_STREAM_MANAGEMENT: + return "H2_A9_SEND_STREAM_MANAGEMENT"; + case H2_A9_VALIDATE_STREAM_READY: + return "H2_A9_VALIDATE_STREAM_READY"; + case D2_A0_DETERMINE_RX_HDCP_CAPABLE: + return "D2_A0_DETERMINE_RX_HDCP_CAPABLE"; + case D2_A1_SEND_AKE_INIT: + return "D2_A1_SEND_AKE_INIT"; + case D2_A1_VALIDATE_AKE_CERT: + return "D2_A1_VALIDATE_AKE_CERT"; + case D2_A1_SEND_NO_STORED_KM: + return "D2_A1_SEND_NO_STORED_KM"; + case D2_A1_READ_H_PRIME: + return "D2_A1_READ_H_PRIME"; + case D2_A1_READ_PAIRING_INFO_AND_VALIDATE_H_PRIME: + return "D2_A1_READ_PAIRING_INFO_AND_VALIDATE_H_PRIME"; + case D2_A1_SEND_STORED_KM: + return "D2_A1_SEND_STORED_KM"; + case D2_A1_VALIDATE_H_PRIME: + return "D2_A1_VALIDATE_H_PRIME"; + case D2_A2_LOCALITY_CHECK: + return "D2_A2_LOCALITY_CHECK"; + case D2_A34_EXCHANGE_KS_AND_TEST_FOR_REPEATER: + return "D2_A34_EXCHANGE_KS_AND_TEST_FOR_REPEATER"; + case D2_SEND_CONTENT_STREAM_TYPE: + return "D2_SEND_CONTENT_STREAM_TYPE"; + case D2_ENABLE_ENCRYPTION: + return "D2_ENABLE_ENCRYPTION"; + case D2_A5_AUTHENTICATED: + return "D2_A5_AUTHENTICATED"; + case D2_A6_WAIT_FOR_RX_ID_LIST: + return "D2_A6_WAIT_FOR_RX_ID_LIST"; + case D2_A78_VERIFY_RX_ID_LIST_AND_SEND_ACK: + return "D2_A78_VERIFY_RX_ID_LIST_AND_SEND_ACK"; + case D2_A9_SEND_STREAM_MANAGEMENT: + return "D2_A9_SEND_STREAM_MANAGEMENT"; + case D2_A9_VALIDATE_STREAM_READY: + return "D2_A9_VALIDATE_STREAM_READY"; + default: + return "UNKNOWN_STATE_ID"; + } +} + diff --git a/drivers/gpu/drm/amd/display/modules/hdcp/hdcp_log.h b/drivers/gpu/drm/amd/display/modules/hdcp/hdcp_log.h new file mode 100644 index 000000000..eb6f9b9c5 --- /dev/null +++ b/drivers/gpu/drm/amd/display/modules/hdcp/hdcp_log.h @@ -0,0 +1,134 @@ +/* + * Copyright 2019 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_HDCP_LOG_H_ +#define MOD_HDCP_LOG_H_ + +#ifdef CONFIG_DRM_AMD_DC_HDCP +#define HDCP_LOG_ERR(hdcp, ...) DRM_DEBUG_KMS(__VA_ARGS__) +#define HDCP_LOG_VER(hdcp, ...) DRM_DEBUG_KMS(__VA_ARGS__) +#define HDCP_LOG_FSM(hdcp, ...) DRM_DEBUG_KMS(__VA_ARGS__) +#define HDCP_LOG_TOP(hdcp, ...) pr_debug("[HDCP_TOP]:"__VA_ARGS__) +#define HDCP_LOG_DDC(hdcp, ...) pr_debug("[HDCP_DDC]:"__VA_ARGS__) +#endif + +/* default logs */ +#define HDCP_ERROR_TRACE(hdcp, status) \ + HDCP_LOG_ERR(hdcp, \ + "[Link %d] WARNING %s IN STATE %s STAY COUNT %d", \ + hdcp->config.index, \ + mod_hdcp_status_to_str(status), \ + mod_hdcp_state_id_to_str(hdcp->state.id), \ + hdcp->state.stay_count) +#define HDCP_HDCP1_ENABLED_TRACE(hdcp, displayIndex) \ + HDCP_LOG_VER(hdcp, \ + "[Link %d] HDCP 1.4 enabled on display %d", \ + hdcp->config.index, displayIndex) +#define HDCP_HDCP2_ENABLED_TRACE(hdcp, displayIndex) \ + HDCP_LOG_VER(hdcp, \ + "[Link %d] HDCP 2.2 enabled on display %d", \ + hdcp->config.index, displayIndex) +#define HDCP_HDCP1_DISABLED_TRACE(hdcp, displayIndex) \ + HDCP_LOG_VER(hdcp, \ + "[Link %d] HDCP 1.4 disabled on display %d", \ + hdcp->config.index, displayIndex) +#define HDCP_HDCP2_DISABLED_TRACE(hdcp, displayIndex) \ + HDCP_LOG_VER(hdcp, \ + "[Link %d] HDCP 2.2 disabled on display %d", \ + hdcp->config.index, displayIndex) + +/* state machine logs */ +#define HDCP_REMOVE_DISPLAY_TRACE(hdcp, displayIndex) \ + HDCP_LOG_FSM(hdcp, \ + "[Link %d] HDCP_REMOVE_DISPLAY index %d", \ + hdcp->config.index, displayIndex) +#define HDCP_INPUT_PASS_TRACE(hdcp, str) \ + HDCP_LOG_FSM(hdcp, \ + "[Link %d]\tPASS %s", \ + hdcp->config.index, str) +#define HDCP_INPUT_FAIL_TRACE(hdcp, str) \ + HDCP_LOG_FSM(hdcp, \ + "[Link %d]\tFAIL %s", \ + hdcp->config.index, str) +#define HDCP_NEXT_STATE_TRACE(hdcp, id, output) do { \ + if (output->watchdog_timer_needed) \ + HDCP_LOG_FSM(hdcp, \ + "[Link %d] > %s with %d ms watchdog", \ + hdcp->config.index, \ + mod_hdcp_state_id_to_str(id), output->watchdog_timer_delay); \ + else \ + HDCP_LOG_FSM(hdcp, \ + "[Link %d] > %s", hdcp->config.index, \ + mod_hdcp_state_id_to_str(id)); \ +} while (0) +#define HDCP_TIMEOUT_TRACE(hdcp) \ + HDCP_LOG_FSM(hdcp, "[Link %d] --> TIMEOUT", hdcp->config.index) +#define HDCP_CPIRQ_TRACE(hdcp) \ + HDCP_LOG_FSM(hdcp, "[Link %d] --> CPIRQ", hdcp->config.index) +#define HDCP_EVENT_TRACE(hdcp, event) \ + if (event == MOD_HDCP_EVENT_WATCHDOG_TIMEOUT) \ + HDCP_TIMEOUT_TRACE(hdcp); \ + else if (event == MOD_HDCP_EVENT_CPIRQ) \ + HDCP_CPIRQ_TRACE(hdcp) +/* TODO: find some way to tell if logging is off to save time */ +#define HDCP_DDC_READ_TRACE(hdcp, msg_name, msg, msg_size) do { \ + mod_hdcp_dump_binary_message(msg, msg_size, hdcp->buf, \ + sizeof(hdcp->buf)); \ + HDCP_LOG_DDC(hdcp, "[Link %d] Read %s%s", hdcp->config.index, \ + msg_name, hdcp->buf); \ +} while (0) +#define HDCP_DDC_WRITE_TRACE(hdcp, msg_name, msg, msg_size) do { \ + mod_hdcp_dump_binary_message(msg, msg_size, hdcp->buf, \ + sizeof(hdcp->buf)); \ + HDCP_LOG_DDC(hdcp, "[Link %d] Write %s%s", \ + hdcp->config.index, msg_name,\ + hdcp->buf); \ +} while (0) +#define HDCP_TOP_ADD_DISPLAY_TRACE(hdcp, i) \ + HDCP_LOG_TOP(hdcp, "[Link %d]\tadd display %d", \ + hdcp->config.index, i) +#define HDCP_TOP_REMOVE_DISPLAY_TRACE(hdcp, i) \ + HDCP_LOG_TOP(hdcp, "[Link %d]\tremove display %d", \ + hdcp->config.index, i) +#define HDCP_TOP_HDCP1_DESTROY_SESSION_TRACE(hdcp) \ + HDCP_LOG_TOP(hdcp, "[Link %d]\tdestroy hdcp1 session", \ + hdcp->config.index) +#define HDCP_TOP_HDCP2_DESTROY_SESSION_TRACE(hdcp) \ + HDCP_LOG_TOP(hdcp, "[Link %d]\tdestroy hdcp2 session", \ + hdcp->config.index) +#define HDCP_TOP_RESET_AUTH_TRACE(hdcp) \ + HDCP_LOG_TOP(hdcp, "[Link %d]\treset authentication", hdcp->config.index) +#define HDCP_TOP_RESET_CONN_TRACE(hdcp) \ + HDCP_LOG_TOP(hdcp, "[Link %d]\treset connection", hdcp->config.index) +#define HDCP_TOP_INTERFACE_TRACE(hdcp) do { \ + HDCP_LOG_TOP(hdcp, "\n"); \ + HDCP_LOG_TOP(hdcp, "[Link %d] %s", hdcp->config.index, __func__); \ +} while (0) +#define HDCP_TOP_INTERFACE_TRACE_WITH_INDEX(hdcp, i) do { \ + HDCP_LOG_TOP(hdcp, "\n"); \ + HDCP_LOG_TOP(hdcp, "[Link %d] %s display %d", hdcp->config.index, __func__, i); \ +} while (0) + +#endif // MOD_HDCP_LOG_H_ diff --git a/drivers/gpu/drm/amd/display/modules/hdcp/hdcp_psp.c b/drivers/gpu/drm/amd/display/modules/hdcp/hdcp_psp.c new file mode 100644 index 000000000..ee67a35c2 --- /dev/null +++ b/drivers/gpu/drm/amd/display/modules/hdcp/hdcp_psp.c @@ -0,0 +1,1025 @@ +/* + * 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. + * + * Authors: AMD + * + */ + +#define MAX_NUM_DISPLAYS 24 + + +#include "hdcp.h" + +#include "amdgpu.h" +#include "hdcp_psp.h" + +static void hdcp2_message_init(struct mod_hdcp *hdcp, + struct ta_hdcp_cmd_hdcp2_process_prepare_authentication_message_input_v2 *in) +{ + in->session_handle = hdcp->auth.id; + in->prepare.msg1_id = TA_HDCP_HDCP2_MSG_ID__NULL_MESSAGE; + in->prepare.msg2_id = TA_HDCP_HDCP2_MSG_ID__NULL_MESSAGE; + in->process.msg1_desc.msg_id = TA_HDCP_HDCP2_MSG_ID__NULL_MESSAGE; + in->process.msg1_desc.msg_size = 0; + in->process.msg2_desc.msg_id = TA_HDCP_HDCP2_MSG_ID__NULL_MESSAGE; + in->process.msg2_desc.msg_size = 0; + in->process.msg3_desc.msg_id = TA_HDCP_HDCP2_MSG_ID__NULL_MESSAGE; + in->process.msg3_desc.msg_size = 0; +} + +static enum mod_hdcp_status remove_display_from_topology_v2( + struct mod_hdcp *hdcp, uint8_t index) +{ + struct psp_context *psp = hdcp->config.psp.handle; + struct ta_dtm_shared_memory *dtm_cmd; + struct mod_hdcp_display *display = + get_active_display_at_index(hdcp, index); + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + dtm_cmd = (struct ta_dtm_shared_memory *)psp->dtm_context.context.mem_context.shared_buf; + + if (!display || !is_display_active(display)) + return MOD_HDCP_STATUS_DISPLAY_NOT_FOUND; + + mutex_lock(&psp->dtm_context.mutex); + + memset(dtm_cmd, 0, sizeof(struct ta_dtm_shared_memory)); + + dtm_cmd->cmd_id = TA_DTM_COMMAND__TOPOLOGY_UPDATE_V2; + dtm_cmd->dtm_in_message.topology_update_v2.display_handle = display->index; + dtm_cmd->dtm_in_message.topology_update_v2.is_active = 0; + dtm_cmd->dtm_status = TA_DTM_STATUS__GENERIC_FAILURE; + + psp_dtm_invoke(psp, dtm_cmd->cmd_id); + + if (dtm_cmd->dtm_status != TA_DTM_STATUS__SUCCESS) { + status = MOD_HDCP_STATUS_UPDATE_TOPOLOGY_FAILURE; + } else { + display->state = MOD_HDCP_DISPLAY_ACTIVE; + HDCP_TOP_REMOVE_DISPLAY_TRACE(hdcp, display->index); + } + + mutex_unlock(&psp->dtm_context.mutex); + return status; +} + +static enum mod_hdcp_status remove_display_from_topology_v3( + struct mod_hdcp *hdcp, uint8_t index) +{ + struct psp_context *psp = hdcp->config.psp.handle; + struct ta_dtm_shared_memory *dtm_cmd; + struct mod_hdcp_display *display = + get_active_display_at_index(hdcp, index); + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + dtm_cmd = (struct ta_dtm_shared_memory *)psp->dtm_context.context.mem_context.shared_buf; + + if (!display || !is_display_active(display)) + return MOD_HDCP_STATUS_DISPLAY_NOT_FOUND; + + mutex_lock(&psp->dtm_context.mutex); + + memset(dtm_cmd, 0, sizeof(struct ta_dtm_shared_memory)); + + dtm_cmd->cmd_id = TA_DTM_COMMAND__TOPOLOGY_UPDATE_V3; + dtm_cmd->dtm_in_message.topology_update_v3.display_handle = display->index; + dtm_cmd->dtm_in_message.topology_update_v3.is_active = 0; + dtm_cmd->dtm_status = TA_DTM_STATUS__GENERIC_FAILURE; + + psp_dtm_invoke(psp, dtm_cmd->cmd_id); + mutex_unlock(&psp->dtm_context.mutex); + + if (dtm_cmd->dtm_status != TA_DTM_STATUS__SUCCESS) { + status = remove_display_from_topology_v2(hdcp, index); + if (status != MOD_HDCP_STATUS_SUCCESS) + display->state = MOD_HDCP_DISPLAY_INACTIVE; + } else { + display->state = MOD_HDCP_DISPLAY_ACTIVE; + HDCP_TOP_REMOVE_DISPLAY_TRACE(hdcp, display->index); + } + + return status; +} + +static enum mod_hdcp_status add_display_to_topology_v2( + struct mod_hdcp *hdcp, struct mod_hdcp_display *display) +{ + struct psp_context *psp = hdcp->config.psp.handle; + struct ta_dtm_shared_memory *dtm_cmd; + struct mod_hdcp_link *link = &hdcp->connection.link; + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + if (!psp->dtm_context.context.initialized) { + DRM_INFO("Failed to add display topology, DTM TA is not initialized."); + display->state = MOD_HDCP_DISPLAY_INACTIVE; + return MOD_HDCP_STATUS_FAILURE; + } + + dtm_cmd = (struct ta_dtm_shared_memory *)psp->dtm_context.context.mem_context.shared_buf; + + mutex_lock(&psp->dtm_context.mutex); + memset(dtm_cmd, 0, sizeof(struct ta_dtm_shared_memory)); + + dtm_cmd->cmd_id = TA_DTM_COMMAND__TOPOLOGY_UPDATE_V2; + dtm_cmd->dtm_in_message.topology_update_v2.display_handle = display->index; + dtm_cmd->dtm_in_message.topology_update_v2.is_active = 1; + dtm_cmd->dtm_in_message.topology_update_v2.controller = display->controller; + dtm_cmd->dtm_in_message.topology_update_v2.ddc_line = link->ddc_line; + dtm_cmd->dtm_in_message.topology_update_v2.dig_be = link->dig_be; + dtm_cmd->dtm_in_message.topology_update_v2.dig_fe = display->dig_fe; + if (is_dp_hdcp(hdcp)) + dtm_cmd->dtm_in_message.topology_update_v2.is_assr = link->dp.assr_enabled; + + dtm_cmd->dtm_in_message.topology_update_v2.dp_mst_vcid = display->vc_id; + dtm_cmd->dtm_in_message.topology_update_v2.max_hdcp_supported_version = + TA_DTM_HDCP_VERSION_MAX_SUPPORTED__2_2; + dtm_cmd->dtm_status = TA_DTM_STATUS__GENERIC_FAILURE; + + psp_dtm_invoke(psp, dtm_cmd->cmd_id); + + if (dtm_cmd->dtm_status != TA_DTM_STATUS__SUCCESS) { + display->state = MOD_HDCP_DISPLAY_INACTIVE; + status = MOD_HDCP_STATUS_UPDATE_TOPOLOGY_FAILURE; + } else { + HDCP_TOP_ADD_DISPLAY_TRACE(hdcp, display->index); + } + + mutex_unlock(&psp->dtm_context.mutex); + return status; +} + +static enum mod_hdcp_status add_display_to_topology_v3( + struct mod_hdcp *hdcp, struct mod_hdcp_display *display) +{ + struct psp_context *psp = hdcp->config.psp.handle; + struct ta_dtm_shared_memory *dtm_cmd; + struct mod_hdcp_link *link = &hdcp->connection.link; + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + if (!psp->dtm_context.context.initialized) { + DRM_INFO("Failed to add display topology, DTM TA is not initialized."); + display->state = MOD_HDCP_DISPLAY_INACTIVE; + return MOD_HDCP_STATUS_FAILURE; + } + + dtm_cmd = (struct ta_dtm_shared_memory *)psp->dtm_context.context.mem_context.shared_buf; + + mutex_lock(&psp->dtm_context.mutex); + memset(dtm_cmd, 0, sizeof(struct ta_dtm_shared_memory)); + + dtm_cmd->cmd_id = TA_DTM_COMMAND__TOPOLOGY_UPDATE_V3; + dtm_cmd->dtm_in_message.topology_update_v3.display_handle = display->index; + dtm_cmd->dtm_in_message.topology_update_v3.is_active = 1; + dtm_cmd->dtm_in_message.topology_update_v3.controller = display->controller; + dtm_cmd->dtm_in_message.topology_update_v3.ddc_line = link->ddc_line; + dtm_cmd->dtm_in_message.topology_update_v3.link_enc = link->link_enc_idx; + dtm_cmd->dtm_in_message.topology_update_v3.stream_enc = display->stream_enc_idx; + if (is_dp_hdcp(hdcp)) + dtm_cmd->dtm_in_message.topology_update_v3.is_assr = link->dp.assr_enabled; + + dtm_cmd->dtm_in_message.topology_update_v3.dp_mst_vcid = display->vc_id; + dtm_cmd->dtm_in_message.topology_update_v3.max_hdcp_supported_version = + TA_DTM_HDCP_VERSION_MAX_SUPPORTED__2_3; + dtm_cmd->dtm_in_message.topology_update_v3.encoder_type = TA_DTM_ENCODER_TYPE__DIG; + dtm_cmd->dtm_status = TA_DTM_STATUS__GENERIC_FAILURE; + dtm_cmd->dtm_in_message.topology_update_v3.phy_id = link->phy_idx; + dtm_cmd->dtm_in_message.topology_update_v3.link_hdcp_cap = link->hdcp_supported_informational; + dtm_cmd->dtm_in_message.topology_update_v3.dio_output_type = link->dp.usb4_enabled ? + TA_DTM_DIO_OUTPUT_TYPE__DPIA : + TA_DTM_DIO_OUTPUT_TYPE__DIRECT; + dtm_cmd->dtm_in_message.topology_update_v3.dio_output_id = link->dio_output_id; + + psp_dtm_invoke(psp, dtm_cmd->cmd_id); + mutex_unlock(&psp->dtm_context.mutex); + + if (dtm_cmd->dtm_status != TA_DTM_STATUS__SUCCESS) { + status = add_display_to_topology_v2(hdcp, display); + if (status != MOD_HDCP_STATUS_SUCCESS) + display->state = MOD_HDCP_DISPLAY_INACTIVE; + } else { + HDCP_TOP_ADD_DISPLAY_TRACE(hdcp, display->index); + } + + return status; +} + +enum mod_hdcp_status mod_hdcp_remove_display_from_topology( + struct mod_hdcp *hdcp, uint8_t index) +{ + enum mod_hdcp_status status = MOD_HDCP_STATUS_UPDATE_TOPOLOGY_FAILURE; + + if (hdcp->config.psp.caps.dtm_v3_supported) + status = remove_display_from_topology_v3(hdcp, index); + else + status = remove_display_from_topology_v2(hdcp, index); + + return status; +} + +enum mod_hdcp_status mod_hdcp_add_display_to_topology(struct mod_hdcp *hdcp, + struct mod_hdcp_display *display) +{ + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + if (hdcp->config.psp.caps.dtm_v3_supported) + status = add_display_to_topology_v3(hdcp, display); + else + status = add_display_to_topology_v2(hdcp, display); + + return status; +} + +enum mod_hdcp_status mod_hdcp_hdcp1_create_session(struct mod_hdcp *hdcp) +{ + + struct psp_context *psp = hdcp->config.psp.handle; + struct mod_hdcp_display *display = get_first_active_display(hdcp); + struct ta_hdcp_shared_memory *hdcp_cmd; + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + if (!psp->hdcp_context.context.initialized) { + DRM_ERROR("Failed to create hdcp session. HDCP TA is not initialized."); + return MOD_HDCP_STATUS_FAILURE; + } + + hdcp_cmd = (struct ta_hdcp_shared_memory *)psp->hdcp_context.context.mem_context.shared_buf; + + mutex_lock(&psp->hdcp_context.mutex); + memset(hdcp_cmd, 0, sizeof(struct ta_hdcp_shared_memory)); + + hdcp_cmd->in_msg.hdcp1_create_session.display_handle = display->index; + hdcp_cmd->cmd_id = TA_HDCP_COMMAND__HDCP1_CREATE_SESSION; + + psp_hdcp_invoke(psp, hdcp_cmd->cmd_id); + + hdcp->auth.id = hdcp_cmd->out_msg.hdcp1_create_session.session_handle; + + if (hdcp_cmd->hdcp_status != TA_HDCP_STATUS__SUCCESS) { + status = MOD_HDCP_STATUS_HDCP1_CREATE_SESSION_FAILURE; + } else { + hdcp->auth.msg.hdcp1.ainfo = hdcp_cmd->out_msg.hdcp1_create_session.ainfo_primary; + memcpy(hdcp->auth.msg.hdcp1.aksv, hdcp_cmd->out_msg.hdcp1_create_session.aksv_primary, + sizeof(hdcp->auth.msg.hdcp1.aksv)); + memcpy(hdcp->auth.msg.hdcp1.an, hdcp_cmd->out_msg.hdcp1_create_session.an_primary, + sizeof(hdcp->auth.msg.hdcp1.an)); + } + + mutex_unlock(&psp->hdcp_context.mutex); + return status; +} + +enum mod_hdcp_status mod_hdcp_hdcp1_destroy_session(struct mod_hdcp *hdcp) +{ + + struct psp_context *psp = hdcp->config.psp.handle; + struct ta_hdcp_shared_memory *hdcp_cmd; + uint8_t i = 0; + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + mutex_lock(&psp->hdcp_context.mutex); + hdcp_cmd = (struct ta_hdcp_shared_memory *)psp->hdcp_context.context.mem_context.shared_buf; + memset(hdcp_cmd, 0, sizeof(struct ta_hdcp_shared_memory)); + + hdcp_cmd->in_msg.hdcp1_destroy_session.session_handle = hdcp->auth.id; + hdcp_cmd->cmd_id = TA_HDCP_COMMAND__HDCP1_DESTROY_SESSION; + + psp_hdcp_invoke(psp, hdcp_cmd->cmd_id); + + if (hdcp_cmd->hdcp_status != TA_HDCP_STATUS__SUCCESS) { + status = MOD_HDCP_STATUS_HDCP1_DESTROY_SESSION_FAILURE; + } else { + HDCP_TOP_HDCP1_DESTROY_SESSION_TRACE(hdcp); + for (i = 0; i < MAX_NUM_OF_DISPLAYS; i++) + if (is_display_encryption_enabled(&hdcp->displays[i])) { + hdcp->displays[i].state = + MOD_HDCP_DISPLAY_ACTIVE; + HDCP_HDCP1_DISABLED_TRACE( + hdcp, hdcp->displays[i].index); + } + } + + mutex_unlock(&psp->hdcp_context.mutex); + return status; +} + +enum mod_hdcp_status mod_hdcp_hdcp1_validate_rx(struct mod_hdcp *hdcp) +{ + struct psp_context *psp = hdcp->config.psp.handle; + struct ta_hdcp_shared_memory *hdcp_cmd; + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + mutex_lock(&psp->hdcp_context.mutex); + hdcp_cmd = (struct ta_hdcp_shared_memory *)psp->hdcp_context.context.mem_context.shared_buf; + memset(hdcp_cmd, 0, sizeof(struct ta_hdcp_shared_memory)); + + hdcp_cmd->in_msg.hdcp1_first_part_authentication.session_handle = hdcp->auth.id; + + memcpy(hdcp_cmd->in_msg.hdcp1_first_part_authentication.bksv_primary, hdcp->auth.msg.hdcp1.bksv, + TA_HDCP__HDCP1_KSV_SIZE); + + hdcp_cmd->in_msg.hdcp1_first_part_authentication.r0_prime_primary = hdcp->auth.msg.hdcp1.r0p; + hdcp_cmd->in_msg.hdcp1_first_part_authentication.bcaps = hdcp->auth.msg.hdcp1.bcaps; + hdcp_cmd->cmd_id = TA_HDCP_COMMAND__HDCP1_FIRST_PART_AUTHENTICATION; + + psp_hdcp_invoke(psp, hdcp_cmd->cmd_id); + + if (hdcp_cmd->hdcp_status != TA_HDCP_STATUS__SUCCESS) { + status = MOD_HDCP_STATUS_HDCP1_VALIDATE_RX_FAILURE; + } else if (hdcp_cmd->out_msg.hdcp1_first_part_authentication.authentication_status == + TA_HDCP_AUTHENTICATION_STATUS__HDCP1_FIRST_PART_COMPLETE) { + /* needs second part of authentication */ + hdcp->connection.is_repeater = 1; + } else if (hdcp_cmd->out_msg.hdcp1_first_part_authentication.authentication_status == + TA_HDCP_AUTHENTICATION_STATUS__HDCP1_AUTHENTICATED) { + hdcp->connection.is_repeater = 0; + } else if (hdcp_cmd->out_msg.hdcp1_first_part_authentication.authentication_status == + TA_HDCP_AUTHENTICATION_STATUS__HDCP1_KSV_REVOKED) { + hdcp->connection.is_hdcp1_revoked = 1; + status = MOD_HDCP_STATUS_HDCP1_BKSV_REVOKED; + } else + status = MOD_HDCP_STATUS_HDCP1_VALIDATE_RX_FAILURE; + + mutex_unlock(&psp->hdcp_context.mutex); + return status; +} + +enum mod_hdcp_status mod_hdcp_hdcp1_enable_encryption(struct mod_hdcp *hdcp) +{ + struct psp_context *psp = hdcp->config.psp.handle; + struct ta_hdcp_shared_memory *hdcp_cmd; + struct mod_hdcp_display *display = get_first_active_display(hdcp); + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + mutex_lock(&psp->hdcp_context.mutex); + hdcp_cmd = (struct ta_hdcp_shared_memory *)psp->hdcp_context.context.mem_context.shared_buf; + memset(hdcp_cmd, 0, sizeof(struct ta_hdcp_shared_memory)); + + hdcp_cmd->in_msg.hdcp1_enable_encryption.session_handle = hdcp->auth.id; + hdcp_cmd->cmd_id = TA_HDCP_COMMAND__HDCP1_ENABLE_ENCRYPTION; + + psp_hdcp_invoke(psp, hdcp_cmd->cmd_id); + + if (hdcp_cmd->hdcp_status != TA_HDCP_STATUS__SUCCESS) { + status = MOD_HDCP_STATUS_HDCP1_ENABLE_ENCRYPTION_FAILURE; + } else if (!is_dp_mst_hdcp(hdcp)) { + display->state = MOD_HDCP_DISPLAY_ENCRYPTION_ENABLED; + HDCP_HDCP1_ENABLED_TRACE(hdcp, display->index); + } + + mutex_unlock(&psp->hdcp_context.mutex); + return status; +} + +enum mod_hdcp_status mod_hdcp_hdcp1_validate_ksvlist_vp(struct mod_hdcp *hdcp) +{ + struct psp_context *psp = hdcp->config.psp.handle; + struct ta_hdcp_shared_memory *hdcp_cmd; + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + mutex_lock(&psp->hdcp_context.mutex); + hdcp_cmd = (struct ta_hdcp_shared_memory *)psp->hdcp_context.context.mem_context.shared_buf; + memset(hdcp_cmd, 0, sizeof(struct ta_hdcp_shared_memory)); + + hdcp_cmd->in_msg.hdcp1_second_part_authentication.session_handle = hdcp->auth.id; + + hdcp_cmd->in_msg.hdcp1_second_part_authentication.ksv_list_size = hdcp->auth.msg.hdcp1.ksvlist_size; + memcpy(hdcp_cmd->in_msg.hdcp1_second_part_authentication.ksv_list, hdcp->auth.msg.hdcp1.ksvlist, + hdcp->auth.msg.hdcp1.ksvlist_size); + + memcpy(hdcp_cmd->in_msg.hdcp1_second_part_authentication.v_prime, hdcp->auth.msg.hdcp1.vp, + sizeof(hdcp->auth.msg.hdcp1.vp)); + + hdcp_cmd->in_msg.hdcp1_second_part_authentication.bstatus_binfo = + is_dp_hdcp(hdcp) ? hdcp->auth.msg.hdcp1.binfo_dp : hdcp->auth.msg.hdcp1.bstatus; + hdcp_cmd->cmd_id = TA_HDCP_COMMAND__HDCP1_SECOND_PART_AUTHENTICATION; + + psp_hdcp_invoke(psp, hdcp_cmd->cmd_id); + + if (hdcp_cmd->hdcp_status == TA_HDCP_STATUS__SUCCESS && + hdcp_cmd->out_msg.hdcp1_second_part_authentication.authentication_status == + TA_HDCP_AUTHENTICATION_STATUS__HDCP1_AUTHENTICATED) { + status = MOD_HDCP_STATUS_SUCCESS; + } else if (hdcp_cmd->out_msg.hdcp1_second_part_authentication.authentication_status == + TA_HDCP_AUTHENTICATION_STATUS__HDCP1_KSV_REVOKED) { + hdcp->connection.is_hdcp1_revoked = 1; + status = MOD_HDCP_STATUS_HDCP1_KSV_LIST_REVOKED; + } else { + status = MOD_HDCP_STATUS_HDCP1_VALIDATE_KSV_LIST_FAILURE; + } + + mutex_unlock(&psp->hdcp_context.mutex); + return status; +} + +enum mod_hdcp_status mod_hdcp_hdcp1_enable_dp_stream_encryption(struct mod_hdcp *hdcp) +{ + + struct psp_context *psp = hdcp->config.psp.handle; + struct ta_hdcp_shared_memory *hdcp_cmd; + int i = 0; + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + mutex_lock(&psp->hdcp_context.mutex); + hdcp_cmd = (struct ta_hdcp_shared_memory *)psp->hdcp_context.context.mem_context.shared_buf; + + for (i = 0; i < MAX_NUM_OF_DISPLAYS; i++) { + + if (hdcp->displays[i].adjust.disable || hdcp->displays[i].state != MOD_HDCP_DISPLAY_ACTIVE) + continue; + + memset(hdcp_cmd, 0, sizeof(struct ta_hdcp_shared_memory)); + + hdcp_cmd->in_msg.hdcp1_enable_dp_stream_encryption.session_handle = hdcp->auth.id; + hdcp_cmd->in_msg.hdcp1_enable_dp_stream_encryption.display_handle = hdcp->displays[i].index; + hdcp_cmd->cmd_id = TA_HDCP_COMMAND__HDCP1_ENABLE_DP_STREAM_ENCRYPTION; + + psp_hdcp_invoke(psp, hdcp_cmd->cmd_id); + + if (hdcp_cmd->hdcp_status != TA_HDCP_STATUS__SUCCESS) { + status = MOD_HDCP_STATUS_HDCP1_ENABLE_STREAM_ENCRYPTION_FAILURE; + break; + } + + hdcp->displays[i].state = MOD_HDCP_DISPLAY_ENCRYPTION_ENABLED; + HDCP_HDCP1_ENABLED_TRACE(hdcp, hdcp->displays[i].index); + } + + mutex_unlock(&psp->hdcp_context.mutex); + return status; +} + +enum mod_hdcp_status mod_hdcp_hdcp1_link_maintenance(struct mod_hdcp *hdcp) +{ + struct psp_context *psp = hdcp->config.psp.handle; + struct ta_hdcp_shared_memory *hdcp_cmd; + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + mutex_lock(&psp->hdcp_context.mutex); + hdcp_cmd = (struct ta_hdcp_shared_memory *)psp->hdcp_context.context.mem_context.shared_buf; + + memset(hdcp_cmd, 0, sizeof(struct ta_hdcp_shared_memory)); + + hdcp_cmd->in_msg.hdcp1_get_encryption_status.session_handle = hdcp->auth.id; + + hdcp_cmd->out_msg.hdcp1_get_encryption_status.protection_level = 0; + hdcp_cmd->cmd_id = TA_HDCP_COMMAND__HDCP1_GET_ENCRYPTION_STATUS; + + psp_hdcp_invoke(psp, hdcp_cmd->cmd_id); + + if (hdcp_cmd->hdcp_status != TA_HDCP_STATUS__SUCCESS || + hdcp_cmd->out_msg.hdcp1_get_encryption_status.protection_level != 1) + status = MOD_HDCP_STATUS_HDCP1_LINK_MAINTENANCE_FAILURE; + + mutex_unlock(&psp->hdcp_context.mutex); + return status; +} + +enum mod_hdcp_status mod_hdcp_hdcp2_create_session(struct mod_hdcp *hdcp) +{ + struct psp_context *psp = hdcp->config.psp.handle; + struct ta_hdcp_shared_memory *hdcp_cmd; + struct mod_hdcp_display *display = get_first_active_display(hdcp); + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + + if (!psp->hdcp_context.context.initialized) { + DRM_ERROR("Failed to create hdcp session, HDCP TA is not initialized"); + return MOD_HDCP_STATUS_FAILURE; + } + + if (!display) + return MOD_HDCP_STATUS_DISPLAY_NOT_FOUND; + + mutex_lock(&psp->hdcp_context.mutex); + + hdcp_cmd = (struct ta_hdcp_shared_memory *)psp->hdcp_context.context.mem_context.shared_buf; + memset(hdcp_cmd, 0, sizeof(struct ta_hdcp_shared_memory)); + + hdcp_cmd->in_msg.hdcp2_create_session_v2.display_handle = display->index; + + if (hdcp->connection.link.adjust.hdcp2.force_type == MOD_HDCP_FORCE_TYPE_0) + hdcp_cmd->in_msg.hdcp2_create_session_v2.negotiate_content_type = + TA_HDCP2_CONTENT_TYPE_NEGOTIATION_TYPE__FORCE_TYPE0; + else if (hdcp->connection.link.adjust.hdcp2.force_type == MOD_HDCP_FORCE_TYPE_1) + hdcp_cmd->in_msg.hdcp2_create_session_v2.negotiate_content_type = + TA_HDCP2_CONTENT_TYPE_NEGOTIATION_TYPE__FORCE_TYPE1; + else if (hdcp->connection.link.adjust.hdcp2.force_type == MOD_HDCP_FORCE_TYPE_MAX) + hdcp_cmd->in_msg.hdcp2_create_session_v2.negotiate_content_type = + TA_HDCP2_CONTENT_TYPE_NEGOTIATION_TYPE__MAX_SUPPORTED; + + hdcp_cmd->cmd_id = TA_HDCP_COMMAND__HDCP2_CREATE_SESSION_V2; + + psp_hdcp_invoke(psp, hdcp_cmd->cmd_id); + + + if (hdcp_cmd->hdcp_status != TA_HDCP_STATUS__SUCCESS) + status = MOD_HDCP_STATUS_HDCP2_CREATE_SESSION_FAILURE; + else + hdcp->auth.id = hdcp_cmd->out_msg.hdcp2_create_session_v2.session_handle; + + mutex_unlock(&psp->hdcp_context.mutex); + return status; +} + +enum mod_hdcp_status mod_hdcp_hdcp2_destroy_session(struct mod_hdcp *hdcp) +{ + struct psp_context *psp = hdcp->config.psp.handle; + struct ta_hdcp_shared_memory *hdcp_cmd; + uint8_t i = 0; + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + mutex_lock(&psp->hdcp_context.mutex); + hdcp_cmd = (struct ta_hdcp_shared_memory *)psp->hdcp_context.context.mem_context.shared_buf; + memset(hdcp_cmd, 0, sizeof(struct ta_hdcp_shared_memory)); + + hdcp_cmd->in_msg.hdcp2_destroy_session.session_handle = hdcp->auth.id; + hdcp_cmd->cmd_id = TA_HDCP_COMMAND__HDCP2_DESTROY_SESSION; + + psp_hdcp_invoke(psp, hdcp_cmd->cmd_id); + + if (hdcp_cmd->hdcp_status != TA_HDCP_STATUS__SUCCESS) { + status = MOD_HDCP_STATUS_HDCP2_DESTROY_SESSION_FAILURE; + } else { + HDCP_TOP_HDCP2_DESTROY_SESSION_TRACE(hdcp); + for (i = 0; i < MAX_NUM_OF_DISPLAYS; i++) + if (is_display_encryption_enabled(&hdcp->displays[i])) { + hdcp->displays[i].state = + MOD_HDCP_DISPLAY_ACTIVE; + HDCP_HDCP2_DISABLED_TRACE( + hdcp, hdcp->displays[i].index); + } + } + + mutex_unlock(&psp->hdcp_context.mutex); + return status; +} + +enum mod_hdcp_status mod_hdcp_hdcp2_prepare_ake_init(struct mod_hdcp *hdcp) +{ + struct psp_context *psp = hdcp->config.psp.handle; + struct ta_hdcp_shared_memory *hdcp_cmd; + struct ta_hdcp_cmd_hdcp2_process_prepare_authentication_message_input_v2 *msg_in; + struct ta_hdcp_cmd_hdcp2_process_prepare_authentication_message_output_v2 *msg_out; + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + mutex_lock(&psp->hdcp_context.mutex); + hdcp_cmd = (struct ta_hdcp_shared_memory *)psp->hdcp_context.context.mem_context.shared_buf; + memset(hdcp_cmd, 0, sizeof(struct ta_hdcp_shared_memory)); + + msg_in = &hdcp_cmd->in_msg.hdcp2_prepare_process_authentication_message_v2; + msg_out = &hdcp_cmd->out_msg.hdcp2_prepare_process_authentication_message_v2; + + hdcp2_message_init(hdcp, msg_in); + + hdcp_cmd->cmd_id = TA_HDCP_COMMAND__HDCP2_PREPARE_PROCESS_AUTHENTICATION_MSG_V2; + msg_in->prepare.msg1_id = TA_HDCP_HDCP2_MSG_ID__AKE_INIT; + + psp_hdcp_invoke(psp, hdcp_cmd->cmd_id); + + if (hdcp_cmd->hdcp_status != TA_HDCP_STATUS__SUCCESS) + status = MOD_HDCP_STATUS_HDCP2_PREP_AKE_INIT_FAILURE; + else + memcpy(&hdcp->auth.msg.hdcp2.ake_init[0], &msg_out->prepare.transmitter_message[0], + sizeof(hdcp->auth.msg.hdcp2.ake_init)); + + mutex_unlock(&psp->hdcp_context.mutex); + return status; +} + +enum mod_hdcp_status mod_hdcp_hdcp2_validate_ake_cert(struct mod_hdcp *hdcp) +{ + struct psp_context *psp = hdcp->config.psp.handle; + struct ta_hdcp_shared_memory *hdcp_cmd; + struct ta_hdcp_cmd_hdcp2_process_prepare_authentication_message_input_v2 *msg_in; + struct ta_hdcp_cmd_hdcp2_process_prepare_authentication_message_output_v2 *msg_out; + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + mutex_lock(&psp->hdcp_context.mutex); + hdcp_cmd = (struct ta_hdcp_shared_memory *)psp->hdcp_context.context.mem_context.shared_buf; + memset(hdcp_cmd, 0, sizeof(struct ta_hdcp_shared_memory)); + + msg_in = &hdcp_cmd->in_msg.hdcp2_prepare_process_authentication_message_v2; + msg_out = &hdcp_cmd->out_msg.hdcp2_prepare_process_authentication_message_v2; + + hdcp2_message_init(hdcp, msg_in); + + msg_in->process.msg1_desc.msg_id = TA_HDCP_HDCP2_MSG_ID__AKE_SEND_CERT; + msg_in->process.msg1_desc.msg_size = TA_HDCP_HDCP2_MSG_ID_MAX_SIZE__AKE_SEND_CERT; + + memcpy(&msg_in->process.receiver_message[0], hdcp->auth.msg.hdcp2.ake_cert, + sizeof(hdcp->auth.msg.hdcp2.ake_cert)); + + msg_in->prepare.msg1_id = TA_HDCP_HDCP2_MSG_ID__AKE_NO_STORED_KM; + msg_in->prepare.msg2_id = TA_HDCP_HDCP2_MSG_ID__AKE_STORED_KM; + + hdcp_cmd->cmd_id = TA_HDCP_COMMAND__HDCP2_PREPARE_PROCESS_AUTHENTICATION_MSG_V2; + + psp_hdcp_invoke(psp, hdcp_cmd->cmd_id); + + if (hdcp_cmd->hdcp_status != TA_HDCP_STATUS__SUCCESS) { + status = MOD_HDCP_STATUS_HDCP2_VALIDATE_AKE_CERT_FAILURE; + } else { + memcpy(hdcp->auth.msg.hdcp2.ake_no_stored_km, + &msg_out->prepare.transmitter_message[0], + sizeof(hdcp->auth.msg.hdcp2.ake_no_stored_km)); + + memcpy(hdcp->auth.msg.hdcp2.ake_stored_km, + &msg_out->prepare.transmitter_message[sizeof(hdcp->auth.msg.hdcp2.ake_no_stored_km)], + sizeof(hdcp->auth.msg.hdcp2.ake_stored_km)); + + if (msg_out->process.msg1_status == + TA_HDCP2_MSG_AUTHENTICATION_STATUS__SUCCESS) { + hdcp->connection.is_km_stored = + msg_out->process.is_km_stored ? 1 : 0; + hdcp->connection.is_repeater = + msg_out->process.is_repeater ? 1 : 0; + status = MOD_HDCP_STATUS_SUCCESS; + } else if (msg_out->process.msg1_status == + TA_HDCP2_MSG_AUTHENTICATION_STATUS__RECEIVERID_REVOKED) { + hdcp->connection.is_hdcp2_revoked = 1; + status = MOD_HDCP_STATUS_HDCP2_AKE_CERT_REVOKED; + } else { + status = MOD_HDCP_STATUS_HDCP2_VALIDATE_AKE_CERT_FAILURE; + } + } + mutex_unlock(&psp->hdcp_context.mutex); + return status; +} + +enum mod_hdcp_status mod_hdcp_hdcp2_validate_h_prime(struct mod_hdcp *hdcp) +{ + struct psp_context *psp = hdcp->config.psp.handle; + struct ta_hdcp_shared_memory *hdcp_cmd; + struct ta_hdcp_cmd_hdcp2_process_prepare_authentication_message_input_v2 *msg_in; + struct ta_hdcp_cmd_hdcp2_process_prepare_authentication_message_output_v2 *msg_out; + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + mutex_lock(&psp->hdcp_context.mutex); + hdcp_cmd = (struct ta_hdcp_shared_memory *)psp->hdcp_context.context.mem_context.shared_buf; + memset(hdcp_cmd, 0, sizeof(struct ta_hdcp_shared_memory)); + + msg_in = &hdcp_cmd->in_msg.hdcp2_prepare_process_authentication_message_v2; + msg_out = &hdcp_cmd->out_msg.hdcp2_prepare_process_authentication_message_v2; + + hdcp2_message_init(hdcp, msg_in); + + msg_in->process.msg1_desc.msg_id = TA_HDCP_HDCP2_MSG_ID__AKE_SEND_H_PRIME; + msg_in->process.msg1_desc.msg_size = TA_HDCP_HDCP2_MSG_ID_MAX_SIZE__AKE_SEND_H_PRIME; + + memcpy(&msg_in->process.receiver_message[0], hdcp->auth.msg.hdcp2.ake_h_prime, + sizeof(hdcp->auth.msg.hdcp2.ake_h_prime)); + + if (!hdcp->connection.is_km_stored) { + msg_in->process.msg2_desc.msg_id = TA_HDCP_HDCP2_MSG_ID__AKE_SEND_PAIRING_INFO; + msg_in->process.msg2_desc.msg_size = TA_HDCP_HDCP2_MSG_ID_MAX_SIZE__AKE_SEND_PAIRING_INFO; + memcpy(&msg_in->process.receiver_message[sizeof(hdcp->auth.msg.hdcp2.ake_h_prime)], + hdcp->auth.msg.hdcp2.ake_pairing_info, sizeof(hdcp->auth.msg.hdcp2.ake_pairing_info)); + } + + hdcp_cmd->cmd_id = TA_HDCP_COMMAND__HDCP2_PREPARE_PROCESS_AUTHENTICATION_MSG_V2; + + psp_hdcp_invoke(psp, hdcp_cmd->cmd_id); + + if (hdcp_cmd->hdcp_status != TA_HDCP_STATUS__SUCCESS) + status = MOD_HDCP_STATUS_HDCP2_VALIDATE_H_PRIME_FAILURE; + else if (msg_out->process.msg1_status != TA_HDCP2_MSG_AUTHENTICATION_STATUS__SUCCESS) + status = MOD_HDCP_STATUS_HDCP2_VALIDATE_H_PRIME_FAILURE; + else if (!hdcp->connection.is_km_stored && + msg_out->process.msg2_status != TA_HDCP2_MSG_AUTHENTICATION_STATUS__SUCCESS) + status = MOD_HDCP_STATUS_HDCP2_VALIDATE_PAIRING_INFO_FAILURE; + + mutex_unlock(&psp->hdcp_context.mutex); + return status; +} + +enum mod_hdcp_status mod_hdcp_hdcp2_prepare_lc_init(struct mod_hdcp *hdcp) +{ + struct psp_context *psp = hdcp->config.psp.handle; + struct ta_hdcp_shared_memory *hdcp_cmd; + struct ta_hdcp_cmd_hdcp2_process_prepare_authentication_message_input_v2 *msg_in; + struct ta_hdcp_cmd_hdcp2_process_prepare_authentication_message_output_v2 *msg_out; + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + mutex_lock(&psp->hdcp_context.mutex); + hdcp_cmd = (struct ta_hdcp_shared_memory *)psp->hdcp_context.context.mem_context.shared_buf; + memset(hdcp_cmd, 0, sizeof(struct ta_hdcp_shared_memory)); + + msg_in = &hdcp_cmd->in_msg.hdcp2_prepare_process_authentication_message_v2; + msg_out = &hdcp_cmd->out_msg.hdcp2_prepare_process_authentication_message_v2; + + hdcp2_message_init(hdcp, msg_in); + + msg_in->prepare.msg1_id = TA_HDCP_HDCP2_MSG_ID__LC_INIT; + + hdcp_cmd->cmd_id = TA_HDCP_COMMAND__HDCP2_PREPARE_PROCESS_AUTHENTICATION_MSG_V2; + + psp_hdcp_invoke(psp, hdcp_cmd->cmd_id); + + if (hdcp_cmd->hdcp_status != TA_HDCP_STATUS__SUCCESS) + status = MOD_HDCP_STATUS_HDCP2_PREP_LC_INIT_FAILURE; + else + memcpy(hdcp->auth.msg.hdcp2.lc_init, &msg_out->prepare.transmitter_message[0], + sizeof(hdcp->auth.msg.hdcp2.lc_init)); + + mutex_unlock(&psp->hdcp_context.mutex); + return status; +} + +enum mod_hdcp_status mod_hdcp_hdcp2_validate_l_prime(struct mod_hdcp *hdcp) +{ + struct psp_context *psp = hdcp->config.psp.handle; + struct ta_hdcp_shared_memory *hdcp_cmd; + struct ta_hdcp_cmd_hdcp2_process_prepare_authentication_message_input_v2 *msg_in; + struct ta_hdcp_cmd_hdcp2_process_prepare_authentication_message_output_v2 *msg_out; + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + mutex_lock(&psp->hdcp_context.mutex); + hdcp_cmd = (struct ta_hdcp_shared_memory *)psp->hdcp_context.context.mem_context.shared_buf; + memset(hdcp_cmd, 0, sizeof(struct ta_hdcp_shared_memory)); + + msg_in = &hdcp_cmd->in_msg.hdcp2_prepare_process_authentication_message_v2; + msg_out = &hdcp_cmd->out_msg.hdcp2_prepare_process_authentication_message_v2; + + hdcp2_message_init(hdcp, msg_in); + + msg_in->process.msg1_desc.msg_id = TA_HDCP_HDCP2_MSG_ID__LC_SEND_L_PRIME; + msg_in->process.msg1_desc.msg_size = TA_HDCP_HDCP2_MSG_ID_MAX_SIZE__LC_SEND_L_PRIME; + + memcpy(&msg_in->process.receiver_message[0], hdcp->auth.msg.hdcp2.lc_l_prime, + sizeof(hdcp->auth.msg.hdcp2.lc_l_prime)); + + hdcp_cmd->cmd_id = TA_HDCP_COMMAND__HDCP2_PREPARE_PROCESS_AUTHENTICATION_MSG_V2; + + psp_hdcp_invoke(psp, hdcp_cmd->cmd_id); + + if (hdcp_cmd->hdcp_status != TA_HDCP_STATUS__SUCCESS || + msg_out->process.msg1_status != TA_HDCP2_MSG_AUTHENTICATION_STATUS__SUCCESS) + status = MOD_HDCP_STATUS_HDCP2_VALIDATE_L_PRIME_FAILURE; + + mutex_unlock(&psp->hdcp_context.mutex); + return status; +} + +enum mod_hdcp_status mod_hdcp_hdcp2_prepare_eks(struct mod_hdcp *hdcp) +{ + struct psp_context *psp = hdcp->config.psp.handle; + struct ta_hdcp_shared_memory *hdcp_cmd; + struct ta_hdcp_cmd_hdcp2_process_prepare_authentication_message_input_v2 *msg_in; + struct ta_hdcp_cmd_hdcp2_process_prepare_authentication_message_output_v2 *msg_out; + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + mutex_lock(&psp->hdcp_context.mutex); + hdcp_cmd = (struct ta_hdcp_shared_memory *)psp->hdcp_context.context.mem_context.shared_buf; + memset(hdcp_cmd, 0, sizeof(struct ta_hdcp_shared_memory)); + + msg_in = &hdcp_cmd->in_msg.hdcp2_prepare_process_authentication_message_v2; + msg_out = &hdcp_cmd->out_msg.hdcp2_prepare_process_authentication_message_v2; + + hdcp2_message_init(hdcp, msg_in); + + msg_in->prepare.msg1_id = TA_HDCP_HDCP2_MSG_ID__SKE_SEND_EKS; + + if (is_dp_hdcp(hdcp)) + msg_in->prepare.msg2_id = TA_HDCP_HDCP2_MSG_ID__SIGNAL_CONTENT_STREAM_TYPE_DP; + + hdcp_cmd->cmd_id = TA_HDCP_COMMAND__HDCP2_PREPARE_PROCESS_AUTHENTICATION_MSG_V2; + psp_hdcp_invoke(psp, hdcp_cmd->cmd_id); + + if (hdcp_cmd->hdcp_status != TA_HDCP_STATUS__SUCCESS) { + status = MOD_HDCP_STATUS_HDCP2_PREP_EKS_FAILURE; + } else { + memcpy(hdcp->auth.msg.hdcp2.ske_eks, + &msg_out->prepare.transmitter_message[0], + sizeof(hdcp->auth.msg.hdcp2.ske_eks)); + msg_out->prepare.msg1_desc.msg_size = + sizeof(hdcp->auth.msg.hdcp2.ske_eks); + + if (is_dp_hdcp(hdcp)) { + memcpy(hdcp->auth.msg.hdcp2.content_stream_type_dp, + &msg_out->prepare.transmitter_message[sizeof(hdcp->auth.msg.hdcp2.ske_eks)], + sizeof(hdcp->auth.msg.hdcp2.content_stream_type_dp)); + } + } + mutex_unlock(&psp->hdcp_context.mutex); + + return status; +} + +enum mod_hdcp_status mod_hdcp_hdcp2_enable_encryption(struct mod_hdcp *hdcp) +{ + struct psp_context *psp = hdcp->config.psp.handle; + struct ta_hdcp_shared_memory *hdcp_cmd; + struct mod_hdcp_display *display = get_first_active_display(hdcp); + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + if (!display) + return MOD_HDCP_STATUS_DISPLAY_NOT_FOUND; + + mutex_lock(&psp->hdcp_context.mutex); + + hdcp_cmd = (struct ta_hdcp_shared_memory *)psp->hdcp_context.context.mem_context.shared_buf; + memset(hdcp_cmd, 0, sizeof(struct ta_hdcp_shared_memory)); + + hdcp_cmd->in_msg.hdcp2_set_encryption.session_handle = hdcp->auth.id; + + hdcp_cmd->cmd_id = TA_HDCP_COMMAND__HDCP2_SET_ENCRYPTION; + psp_hdcp_invoke(psp, hdcp_cmd->cmd_id); + + if (hdcp_cmd->hdcp_status != TA_HDCP_STATUS__SUCCESS) { + status = MOD_HDCP_STATUS_HDCP2_ENABLE_ENCRYPTION_FAILURE; + } else if (!is_dp_mst_hdcp(hdcp)) { + display->state = MOD_HDCP_DISPLAY_ENCRYPTION_ENABLED; + HDCP_HDCP2_ENABLED_TRACE(hdcp, display->index); + } + + mutex_unlock(&psp->hdcp_context.mutex); + return status; +} + +enum mod_hdcp_status mod_hdcp_hdcp2_validate_rx_id_list(struct mod_hdcp *hdcp) +{ + struct psp_context *psp = hdcp->config.psp.handle; + struct ta_hdcp_shared_memory *hdcp_cmd; + struct ta_hdcp_cmd_hdcp2_process_prepare_authentication_message_input_v2 *msg_in; + struct ta_hdcp_cmd_hdcp2_process_prepare_authentication_message_output_v2 *msg_out; + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + mutex_lock(&psp->hdcp_context.mutex); + + hdcp_cmd = (struct ta_hdcp_shared_memory *)psp->hdcp_context.context.mem_context.shared_buf; + memset(hdcp_cmd, 0, sizeof(struct ta_hdcp_shared_memory)); + + msg_in = &hdcp_cmd->in_msg.hdcp2_prepare_process_authentication_message_v2; + msg_out = &hdcp_cmd->out_msg.hdcp2_prepare_process_authentication_message_v2; + + hdcp2_message_init(hdcp, msg_in); + + msg_in->process.msg1_desc.msg_id = TA_HDCP_HDCP2_MSG_ID__REPEATERAUTH_SEND_RECEIVERID_LIST; + msg_in->process.msg1_desc.msg_size = sizeof(hdcp->auth.msg.hdcp2.rx_id_list); + memcpy(&msg_in->process.receiver_message[0], hdcp->auth.msg.hdcp2.rx_id_list, + sizeof(hdcp->auth.msg.hdcp2.rx_id_list)); + + msg_in->prepare.msg1_id = TA_HDCP_HDCP2_MSG_ID__REPEATERAUTH_SEND_ACK; + + hdcp_cmd->cmd_id = TA_HDCP_COMMAND__HDCP2_PREPARE_PROCESS_AUTHENTICATION_MSG_V2; + + psp_hdcp_invoke(psp, hdcp_cmd->cmd_id); + + if (hdcp_cmd->hdcp_status != TA_HDCP_STATUS__SUCCESS) { + status = MOD_HDCP_STATUS_HDCP2_VALIDATE_RX_ID_LIST_FAILURE; + } else { + memcpy(hdcp->auth.msg.hdcp2.repeater_auth_ack, + &msg_out->prepare.transmitter_message[0], + sizeof(hdcp->auth.msg.hdcp2.repeater_auth_ack)); + + if (msg_out->process.msg1_status == + TA_HDCP2_MSG_AUTHENTICATION_STATUS__SUCCESS) { + hdcp->connection.is_km_stored = msg_out->process.is_km_stored ? 1 : 0; + hdcp->connection.is_repeater = msg_out->process.is_repeater ? 1 : 0; + status = MOD_HDCP_STATUS_SUCCESS; + } else if (msg_out->process.msg1_status == + TA_HDCP2_MSG_AUTHENTICATION_STATUS__RECEIVERID_REVOKED) { + hdcp->connection.is_hdcp2_revoked = 1; + status = MOD_HDCP_STATUS_HDCP2_RX_ID_LIST_REVOKED; + } else { + status = MOD_HDCP_STATUS_HDCP2_VALIDATE_RX_ID_LIST_FAILURE; + } + } + mutex_unlock(&psp->hdcp_context.mutex); + return status; +} + +enum mod_hdcp_status mod_hdcp_hdcp2_enable_dp_stream_encryption(struct mod_hdcp *hdcp) +{ + struct psp_context *psp = hdcp->config.psp.handle; + struct ta_hdcp_shared_memory *hdcp_cmd; + struct ta_hdcp_cmd_hdcp2_process_prepare_authentication_message_input_v2 *msg_in; + uint8_t i; + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + mutex_lock(&psp->hdcp_context.mutex); + hdcp_cmd = (struct ta_hdcp_shared_memory *)psp->hdcp_context.context.mem_context.shared_buf; + memset(hdcp_cmd, 0, sizeof(struct ta_hdcp_shared_memory)); + + msg_in = &hdcp_cmd->in_msg.hdcp2_prepare_process_authentication_message_v2; + + hdcp2_message_init(hdcp, msg_in); + + + for (i = 0; i < MAX_NUM_OF_DISPLAYS; i++) { + if (hdcp->displays[i].adjust.disable || hdcp->displays[i].state != MOD_HDCP_DISPLAY_ACTIVE) + continue; + + hdcp_cmd->in_msg.hdcp2_enable_dp_stream_encryption.display_handle = hdcp->displays[i].index; + hdcp_cmd->in_msg.hdcp2_enable_dp_stream_encryption.session_handle = hdcp->auth.id; + + hdcp_cmd->cmd_id = TA_HDCP_COMMAND__HDCP2_ENABLE_DP_STREAM_ENCRYPTION; + psp_hdcp_invoke(psp, hdcp_cmd->cmd_id); + + if (hdcp_cmd->hdcp_status != TA_HDCP_STATUS__SUCCESS) + break; + + hdcp->displays[i].state = MOD_HDCP_DISPLAY_ENCRYPTION_ENABLED; + HDCP_HDCP2_ENABLED_TRACE(hdcp, hdcp->displays[i].index); + } + + if (hdcp_cmd->hdcp_status == TA_HDCP_STATUS__SUCCESS) + status = MOD_HDCP_STATUS_SUCCESS; + else + status = MOD_HDCP_STATUS_HDCP2_ENABLE_STREAM_ENCRYPTION_FAILURE; + + mutex_unlock(&psp->hdcp_context.mutex); + return status; +} + +enum mod_hdcp_status mod_hdcp_hdcp2_prepare_stream_management(struct mod_hdcp *hdcp) +{ + + struct psp_context *psp = hdcp->config.psp.handle; + struct ta_hdcp_shared_memory *hdcp_cmd; + struct ta_hdcp_cmd_hdcp2_process_prepare_authentication_message_input_v2 *msg_in; + struct ta_hdcp_cmd_hdcp2_process_prepare_authentication_message_output_v2 *msg_out; + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + mutex_lock(&psp->hdcp_context.mutex); + hdcp_cmd = (struct ta_hdcp_shared_memory *)psp->hdcp_context.context.mem_context.shared_buf; + memset(hdcp_cmd, 0, sizeof(struct ta_hdcp_shared_memory)); + + msg_in = &hdcp_cmd->in_msg.hdcp2_prepare_process_authentication_message_v2; + msg_out = &hdcp_cmd->out_msg.hdcp2_prepare_process_authentication_message_v2; + + hdcp2_message_init(hdcp, msg_in); + + msg_in->prepare.msg1_id = TA_HDCP_HDCP2_MSG_ID__REPEATERAUTH_STREAM_MANAGE; + + + hdcp_cmd->cmd_id = TA_HDCP_COMMAND__HDCP2_PREPARE_PROCESS_AUTHENTICATION_MSG_V2; + psp_hdcp_invoke(psp, hdcp_cmd->cmd_id); + + if (hdcp_cmd->hdcp_status != TA_HDCP_STATUS__SUCCESS) { + status = MOD_HDCP_STATUS_HDCP2_PREPARE_STREAM_MANAGEMENT_FAILURE; + } else { + hdcp->auth.msg.hdcp2.stream_manage_size = msg_out->prepare.msg1_desc.msg_size; + + memcpy(hdcp->auth.msg.hdcp2.repeater_auth_stream_manage, + &msg_out->prepare.transmitter_message[0], + sizeof(hdcp->auth.msg.hdcp2.repeater_auth_stream_manage)); + } + mutex_unlock(&psp->hdcp_context.mutex); + return status; +} + +enum mod_hdcp_status mod_hdcp_hdcp2_validate_stream_ready(struct mod_hdcp *hdcp) +{ + struct psp_context *psp = hdcp->config.psp.handle; + struct ta_hdcp_shared_memory *hdcp_cmd; + struct ta_hdcp_cmd_hdcp2_process_prepare_authentication_message_input_v2 *msg_in; + struct ta_hdcp_cmd_hdcp2_process_prepare_authentication_message_output_v2 *msg_out; + enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; + + mutex_lock(&psp->hdcp_context.mutex); + hdcp_cmd = (struct ta_hdcp_shared_memory *)psp->hdcp_context.context.mem_context.shared_buf; + memset(hdcp_cmd, 0, sizeof(struct ta_hdcp_shared_memory)); + + msg_in = &hdcp_cmd->in_msg.hdcp2_prepare_process_authentication_message_v2; + msg_out = &hdcp_cmd->out_msg.hdcp2_prepare_process_authentication_message_v2; + + hdcp2_message_init(hdcp, msg_in); + + msg_in->process.msg1_desc.msg_id = TA_HDCP_HDCP2_MSG_ID__REPEATERAUTH_STREAM_READY; + + msg_in->process.msg1_desc.msg_size = sizeof(hdcp->auth.msg.hdcp2.repeater_auth_stream_ready); + + memcpy(&msg_in->process.receiver_message[0], hdcp->auth.msg.hdcp2.repeater_auth_stream_ready, + sizeof(hdcp->auth.msg.hdcp2.repeater_auth_stream_ready)); + + hdcp_cmd->cmd_id = TA_HDCP_COMMAND__HDCP2_PREPARE_PROCESS_AUTHENTICATION_MSG_V2; + psp_hdcp_invoke(psp, hdcp_cmd->cmd_id); + + if (hdcp_cmd->hdcp_status == TA_HDCP_STATUS__SUCCESS && + msg_out->process.msg1_status == TA_HDCP2_MSG_AUTHENTICATION_STATUS__SUCCESS) + status = MOD_HDCP_STATUS_SUCCESS; + else + status = MOD_HDCP_STATUS_HDCP2_VALIDATE_STREAM_READY_FAILURE; + + mutex_unlock(&psp->hdcp_context.mutex); + return status; +} diff --git a/drivers/gpu/drm/amd/display/modules/hdcp/hdcp_psp.h b/drivers/gpu/drm/amd/display/modules/hdcp/hdcp_psp.h new file mode 100644 index 000000000..5b71bc96b --- /dev/null +++ b/drivers/gpu/drm/amd/display/modules/hdcp/hdcp_psp.h @@ -0,0 +1,532 @@ +/* + * Copyright 2019 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_HDCP_HDCP_PSP_H_ +#define MODULES_HDCP_HDCP_PSP_H_ + +/* + * NOTE: These parameters are a one-to-one copy of the + * parameters required by PSP + */ +enum bgd_security_hdcp_encryption_level { + HDCP_ENCRYPTION_LEVEL__INVALID = 0, + HDCP_ENCRYPTION_LEVEL__OFF, + HDCP_ENCRYPTION_LEVEL__ON +}; + +enum bgd_security_hdcp2_content_type { + HDCP2_CONTENT_TYPE__INVALID = 0, + HDCP2_CONTENT_TYPE__TYPE0, + HDCP2_CONTENT_TYPE__TYPE1 +}; +enum ta_dtm_command { + TA_DTM_COMMAND__UNUSED_1 = 1, + TA_DTM_COMMAND__TOPOLOGY_UPDATE_V2, + TA_DTM_COMMAND__TOPOLOGY_ASSR_ENABLE, + TA_DTM_COMMAND__TOPOLOGY_UPDATE_V3 +}; + +/* DTM related enumerations */ +/**********************************************************/ + +enum ta_dtm_status { + TA_DTM_STATUS__SUCCESS = 0x00, + TA_DTM_STATUS__GENERIC_FAILURE = 0x01, + TA_DTM_STATUS__INVALID_PARAMETER = 0x02, + TA_DTM_STATUS__NULL_POINTER = 0x3 +}; + +/* input/output structures for DTM commands */ +/**********************************************************/ +/** + * Input structures + */ +enum ta_dtm_hdcp_version_max_supported { + TA_DTM_HDCP_VERSION_MAX_SUPPORTED__NONE = 0, + TA_DTM_HDCP_VERSION_MAX_SUPPORTED__1_x = 10, + TA_DTM_HDCP_VERSION_MAX_SUPPORTED__2_0 = 20, + TA_DTM_HDCP_VERSION_MAX_SUPPORTED__2_1 = 21, + TA_DTM_HDCP_VERSION_MAX_SUPPORTED__2_2 = 22, + TA_DTM_HDCP_VERSION_MAX_SUPPORTED__2_3 = 23 +}; + +struct ta_dtm_topology_update_input_v2 { + /* display handle is unique across the driver and is used to identify a display */ + /* for all security interfaces which reference displays such as HDCP */ + uint32_t display_handle; + uint32_t is_active; + uint32_t is_miracast; + uint32_t controller; + uint32_t ddc_line; + uint32_t dig_be; + uint32_t dig_fe; + uint32_t dp_mst_vcid; + uint32_t is_assr; + uint32_t max_hdcp_supported_version; +}; + +/* For security reason/HW may change value, these encoder type enum values are not HW register values */ +/* Security code will check real HW register values and these SW enum values */ +enum ta_dtm_encoder_type { + TA_DTM_ENCODER_TYPE__INVALID = 0, + TA_DTM_ENCODER_TYPE__DIG = 0x10 +}; + +/* @enum ta_dtm_dio_output_type + * This enum defines software value for dio_output_type + */ +typedef enum { + TA_DTM_DIO_OUTPUT_TYPE__INVALID, + TA_DTM_DIO_OUTPUT_TYPE__DIRECT, + TA_DTM_DIO_OUTPUT_TYPE__DPIA +} ta_dtm_dio_output_type; + +struct ta_dtm_topology_update_input_v3 { + /* display handle is unique across the driver and is used to identify a display */ + /* for all security interfaces which reference displays such as HDCP */ + /* link_hdcp_cap means link is HDCP-capable for audio HDCP capable property(informational), not for other logic(e.g. Crossbar) */ + uint32_t display_handle; + uint32_t is_active; + uint32_t is_miracast; + uint32_t controller; + uint32_t ddc_line; + uint32_t link_enc; + uint32_t stream_enc; + uint32_t dp_mst_vcid; + uint32_t is_assr; + uint32_t max_hdcp_supported_version; + enum ta_dtm_encoder_type encoder_type; + uint32_t phy_id; + uint32_t link_hdcp_cap; + ta_dtm_dio_output_type dio_output_type; + uint32_t dio_output_id; +}; + +struct ta_dtm_topology_assr_enable { + uint32_t display_topology_dig_be_index; +}; + +/** + * Output structures + */ + +/* No output structures yet */ + +union ta_dtm_cmd_input { + struct ta_dtm_topology_update_input_v2 topology_update_v2; + struct ta_dtm_topology_assr_enable topology_assr_enable; + struct ta_dtm_topology_update_input_v3 topology_update_v3; +}; + +union ta_dtm_cmd_output { + uint32_t reserved; +}; + +struct ta_dtm_shared_memory { + uint32_t cmd_id; + uint32_t resp_id; + enum ta_dtm_status dtm_status; + uint32_t reserved; + union ta_dtm_cmd_input dtm_in_message; + union ta_dtm_cmd_output dtm_out_message; +}; + +int psp_cmd_submit_buf(struct psp_context *psp, struct amdgpu_firmware_info *ucode, struct psp_gfx_cmd_resp *cmd, + uint64_t fence_mc_addr); + +enum { PSP_HDCP_SRM_FIRST_GEN_MAX_SIZE = 5120 }; + +enum ta_hdcp_command { + TA_HDCP_COMMAND__INITIALIZE, + TA_HDCP_COMMAND__HDCP1_CREATE_SESSION, + TA_HDCP_COMMAND__HDCP1_DESTROY_SESSION, + TA_HDCP_COMMAND__HDCP1_FIRST_PART_AUTHENTICATION, + TA_HDCP_COMMAND__HDCP1_SECOND_PART_AUTHENTICATION, + TA_HDCP_COMMAND__HDCP1_ENABLE_ENCRYPTION, + TA_HDCP_COMMAND__HDCP1_ENABLE_DP_STREAM_ENCRYPTION, + TA_HDCP_COMMAND__HDCP1_GET_ENCRYPTION_STATUS, + TA_HDCP_COMMAND__UNUSED_1, + TA_HDCP_COMMAND__HDCP2_DESTROY_SESSION, + TA_HDCP_COMMAND__UNUSED_2, + TA_HDCP_COMMAND__HDCP2_SET_ENCRYPTION, + TA_HDCP_COMMAND__HDCP2_GET_ENCRYPTION_STATUS, + TA_HDCP_COMMAND__UNUSED_3, + TA_HDCP_COMMAND__HDCP2_CREATE_SESSION_V2, + TA_HDCP_COMMAND__HDCP2_PREPARE_PROCESS_AUTHENTICATION_MSG_V2, + TA_HDCP_COMMAND__HDCP2_ENABLE_DP_STREAM_ENCRYPTION, + TA_HDCP_COMMAND__HDCP_DESTROY_ALL_SESSIONS, + TA_HDCP_COMMAND__HDCP_SET_SRM, + TA_HDCP_COMMAND__HDCP_GET_SRM +}; + +enum ta_hdcp2_msg_id { + TA_HDCP_HDCP2_MSG_ID__NULL_MESSAGE = 1, + TA_HDCP_HDCP2_MSG_ID__AKE_INIT = 2, + TA_HDCP_HDCP2_MSG_ID__AKE_SEND_CERT = 3, + TA_HDCP_HDCP2_MSG_ID__AKE_NO_STORED_KM = 4, + TA_HDCP_HDCP2_MSG_ID__AKE_STORED_KM = 5, + TA_HDCP_HDCP2_MSG_ID__AKE_SEND_RRX = 6, + TA_HDCP_HDCP2_MSG_ID__AKE_SEND_H_PRIME = 7, + TA_HDCP_HDCP2_MSG_ID__AKE_SEND_PAIRING_INFO = 8, + TA_HDCP_HDCP2_MSG_ID__LC_INIT = 9, + TA_HDCP_HDCP2_MSG_ID__LC_SEND_L_PRIME = 10, + TA_HDCP_HDCP2_MSG_ID__SKE_SEND_EKS = 11, + TA_HDCP_HDCP2_MSG_ID__REPEATERAUTH_SEND_RECEIVERID_LIST = 12, + TA_HDCP_HDCP2_MSG_ID__RTT_READY = 13, + TA_HDCP_HDCP2_MSG_ID__RTT_CHALLENGE = 14, + TA_HDCP_HDCP2_MSG_ID__REPEATERAUTH_SEND_ACK = 15, + TA_HDCP_HDCP2_MSG_ID__REPEATERAUTH_STREAM_MANAGE = 16, + TA_HDCP_HDCP2_MSG_ID__REPEATERAUTH_STREAM_READY = 17, + TA_HDCP_HDCP2_MSG_ID__RECEIVER_AUTH_STATUS = 18, + TA_HDCP_HDCP2_MSG_ID__AKE_TRANSMITTER_INFO = 19, + TA_HDCP_HDCP2_MSG_ID__AKE_RECEIVER_INFO = 20, + TA_HDCP_HDCP2_MSG_ID__SIGNAL_CONTENT_STREAM_TYPE_DP = 129 +}; + +enum ta_hdcp2_hdcp2_msg_id_max_size { + TA_HDCP_HDCP2_MSG_ID_MAX_SIZE__NULL_MESSAGE = 0, + TA_HDCP_HDCP2_MSG_ID_MAX_SIZE__AKE_INIT = 12, + TA_HDCP_HDCP2_MSG_ID_MAX_SIZE__AKE_SEND_CERT = 534, + TA_HDCP_HDCP2_MSG_ID_MAX_SIZE__AKE_NO_STORED_KM = 129, + TA_HDCP_HDCP2_MSG_ID_MAX_SIZE__AKE_STORED_KM = 33, + TA_HDCP_HDCP2_MSG_ID_MAX_SIZE__AKE_SEND_RRX = 9, + TA_HDCP_HDCP2_MSG_ID_MAX_SIZE__AKE_SEND_H_PRIME = 33, + TA_HDCP_HDCP2_MSG_ID_MAX_SIZE__AKE_SEND_PAIRING_INFO = 17, + TA_HDCP_HDCP2_MSG_ID_MAX_SIZE__LC_INIT = 9, + TA_HDCP_HDCP2_MSG_ID_MAX_SIZE__LC_SEND_L_PRIME = 33, + TA_HDCP_HDCP2_MSG_ID_MAX_SIZE__SKE_SEND_EKS = 25, + TA_HDCP_HDCP2_MSG_ID_MAX_SIZE__REPEATERAUTH_SEND_RECEIVERID_LIST = 181, + TA_HDCP_HDCP2_MSG_ID_MAX_SIZE__RTT_READY = 1, + TA_HDCP_HDCP2_MSG_ID_MAX_SIZE__RTT_CHALLENGE = 17, + TA_HDCP_HDCP2_MSG_ID_MAX_SIZE__REPEATERAUTH_SEND_RACK = 17, + TA_HDCP_HDCP2_MSG_ID_MAX_SIZE__REPEATERAUTH_STREAM_MANAGE = 13, + TA_HDCP_HDCP2_MSG_ID_MAX_SIZE__REPEATERAUTH_STREAM_READY = 33, + TA_HDCP_HDCP2_MSG_ID_MAX_SIZE__RECEIVER_AUTH_STATUS = 4, + TA_HDCP_HDCP2_MSG_ID_MAX_SIZE__AKE_TRANSMITTER_INFO = 6, + TA_HDCP_HDCP2_MSG_ID_MAX_SIZE__AKE_RECEIVER_INFO = 6, + TA_HDCP_HDCP2_MSG_ID_MAX_SIZE__SIGNAL_CONTENT_STREAM_TYPE_DP = 1 +}; + +/* HDCP related enumerations */ +/**********************************************************/ +#define TA_HDCP__INVALID_SESSION 0xFFFF +#define TA_HDCP__HDCP1_AN_SIZE 8 +#define TA_HDCP__HDCP1_KSV_SIZE 5 +#define TA_HDCP__HDCP1_KSV_LIST_MAX_ENTRIES 127 +#define TA_HDCP__HDCP1_V_PRIME_SIZE 20 +#define TA_HDCP__HDCP2_TX_BUF_MAX_SIZE \ + TA_HDCP_HDCP2_MSG_ID_MAX_SIZE__AKE_NO_STORED_KM + TA_HDCP_HDCP2_MSG_ID_MAX_SIZE__AKE_STORED_KM + 6 + +// 64 bits boundaries +#define TA_HDCP__HDCP2_RX_BUF_MAX_SIZE \ + TA_HDCP_HDCP2_MSG_ID_MAX_SIZE__AKE_SEND_CERT + TA_HDCP_HDCP2_MSG_ID_MAX_SIZE__AKE_RECEIVER_INFO + 4 + +enum ta_hdcp_status { + TA_HDCP_STATUS__SUCCESS = 0x00, + TA_HDCP_STATUS__GENERIC_FAILURE = 0x01, + TA_HDCP_STATUS__NULL_POINTER = 0x02, + TA_HDCP_STATUS__FAILED_ALLOCATING_SESSION = 0x03, + TA_HDCP_STATUS__FAILED_SETUP_TX = 0x04, + TA_HDCP_STATUS__INVALID_PARAMETER = 0x05, + TA_HDCP_STATUS__VHX_ERROR = 0x06, + TA_HDCP_STATUS__SESSION_NOT_CLOSED_PROPERLY = 0x07, + TA_HDCP_STATUS__SRM_FAILURE = 0x08, + TA_HDCP_STATUS__MST_AUTHENTICATED_ALREADY_STARTED = 0x09, + TA_HDCP_STATUS__AKE_SEND_CERT_FAILURE = 0x0A, + TA_HDCP_STATUS__AKE_NO_STORED_KM_FAILURE = 0x0B, + TA_HDCP_STATUS__AKE_SEND_HPRIME_FAILURE = 0x0C, + TA_HDCP_STATUS__LC_SEND_LPRIME_FAILURE = 0x0D, + TA_HDCP_STATUS__SKE_SEND_EKS_FAILURE = 0x0E, + TA_HDCP_STATUS__REPAUTH_SEND_RXIDLIST_FAILURE = 0x0F, + TA_HDCP_STATUS__REPAUTH_STREAM_READY_FAILURE = 0x10, + TA_HDCP_STATUS__ASD_GENERIC_FAILURE = 0x11, + TA_HDCP_STATUS__UNWRAP_SECRET_FAILURE = 0x12, + TA_HDCP_STATUS__ENABLE_ENCR_FAILURE = 0x13, + TA_HDCP_STATUS__DISABLE_ENCR_FAILURE = 0x14, + TA_HDCP_STATUS__NOT_ENOUGH_MEMORY_FAILURE = 0x15, + TA_HDCP_STATUS__UNKNOWN_MESSAGE = 0x16, + TA_HDCP_STATUS__TOO_MANY_STREAM = 0x17 +}; + +enum ta_hdcp_authentication_status { + TA_HDCP_AUTHENTICATION_STATUS__NOT_STARTED = 0x00, + TA_HDCP_AUTHENTICATION_STATUS__HDCP1_FIRST_PART_FAILED = 0x01, + TA_HDCP_AUTHENTICATION_STATUS__HDCP1_FIRST_PART_COMPLETE = 0x02, + TA_HDCP_AUTHENTICATION_STATUS__HDCP1_SECOND_PART_FAILED = 0x03, + TA_HDCP_AUTHENTICATION_STATUS__HDCP1_AUTHENTICATED = 0x04, + TA_HDCP_AUTHENTICATION_STATUS__HDCP22_AUTHENTICATION_PENDING = 0x06, + TA_HDCP_AUTHENTICATION_STATUS__HDCP22_AUTHENTICATION_FAILED = 0x07, + TA_HDCP_AUTHENTICATION_STATUS__HDCP22_AUTHENTICATED = 0x08, + TA_HDCP_AUTHENTICATION_STATUS__HDCP1_KSV_VALIDATION_FAILED = 0x09, + TA_HDCP_AUTHENTICATION_STATUS__HDCP1_KSV_REVOKED = 0x0A +}; + +enum ta_hdcp2_msg_authentication_status { + TA_HDCP2_MSG_AUTHENTICATION_STATUS__SUCCESS = 0, + TA_HDCP2_MSG_AUTHENTICATION_STATUS__KM_NOT_AVAILABLE, + TA_HDCP2_MSG_AUTHENTICATION_STATUS__UNUSED, + TA_HDCP2_MSG_AUTHENTICATION_STATUS__INVALID = 100, // everything above does not fail the request + TA_HDCP2_MSG_AUTHENTICATION_STATUS__NOT_ENOUGH_MEMORY, + TA_HDCP2_MSG_AUTHENTICATION_STATUS__NOT_EXPECTED_MSG, + TA_HDCP2_MSG_AUTHENTICATION_STATUS__SIGNATURE_CERTIFICAT_ERROR, + TA_HDCP2_MSG_AUTHENTICATION_STATUS__INCORRECT_HDCP_VERSION, + TA_HDCP2_MSG_AUTHENTICATION_STATUS__UNKNOWN_MESSAGE, + TA_HDCP2_MSG_AUTHENTICATION_STATUS__INVALID_HMAC, + TA_HDCP2_MSG_AUTHENTICATION_STATUS__INVALID_TOPOLOGY, + TA_HDCP2_MSG_AUTHENTICATION_STATUS__INVALID_SEQ_NUM, + TA_HDCP2_MSG_AUTHENTICATION_STATUS__INVALID_SIZE, + TA_HDCP2_MSG_AUTHENTICATION_STATUS__INVALID_LENGTH, + TA_HDCP2_MSG_AUTHENTICATION_STATUS__REAUTH_REQUEST, + TA_HDCP2_MSG_AUTHENTICATION_STATUS__RECEIVERID_REVOKED +}; + +enum ta_hdcp_content_type { + TA_HDCP2_CONTENT_TYPE__TYPE0 = 1, + TA_HDCP2_CONTENT_TYPE__TYPE1, +}; + +enum ta_hdcp_content_type_negotiation_type { + TA_HDCP2_CONTENT_TYPE_NEGOTIATION_TYPE__FORCE_TYPE0 = 1, + TA_HDCP2_CONTENT_TYPE_NEGOTIATION_TYPE__FORCE_TYPE1, + TA_HDCP2_CONTENT_TYPE_NEGOTIATION_TYPE__MAX_SUPPORTED +}; + +enum ta_hdcp2_version { + TA_HDCP2_VERSION_UNKNOWN = 0, + TA_HDCP2_VERSION_2_0 = 20, + TA_HDCP2_VERSION_2_1 = 21, + TA_HDCP2_VERSION_2_2 = 22, + TA_HDCP2_VERSION_2_3 = 23, +}; + +/* input/output structures for HDCP commands */ +/**********************************************************/ +struct ta_hdcp_cmd_hdcp1_create_session_input { + uint8_t display_handle; +}; + +struct ta_hdcp_cmd_hdcp1_create_session_output { + uint32_t session_handle; + uint8_t an_primary[TA_HDCP__HDCP1_AN_SIZE]; + uint8_t aksv_primary[TA_HDCP__HDCP1_KSV_SIZE]; + uint8_t ainfo_primary; + uint8_t an_secondary[TA_HDCP__HDCP1_AN_SIZE]; + uint8_t aksv_secondary[TA_HDCP__HDCP1_KSV_SIZE]; + uint8_t ainfo_secondary; +}; + +struct ta_hdcp_cmd_hdcp1_destroy_session_input { + uint32_t session_handle; +}; + +struct ta_hdcp_cmd_hdcp1_first_part_authentication_input { + uint32_t session_handle; + uint8_t bksv_primary[TA_HDCP__HDCP1_KSV_SIZE]; + uint8_t bksv_secondary[TA_HDCP__HDCP1_KSV_SIZE]; + uint8_t bcaps; + uint16_t r0_prime_primary; + uint16_t r0_prime_secondary; +}; + +struct ta_hdcp_cmd_hdcp1_first_part_authentication_output { + enum ta_hdcp_authentication_status authentication_status; +}; + +struct ta_hdcp_cmd_hdcp1_second_part_authentication_input { + uint32_t session_handle; + uint16_t bstatus_binfo; + uint8_t ksv_list[TA_HDCP__HDCP1_KSV_LIST_MAX_ENTRIES][TA_HDCP__HDCP1_KSV_SIZE]; + uint32_t ksv_list_size; + uint8_t pj_prime; + uint8_t v_prime[TA_HDCP__HDCP1_V_PRIME_SIZE]; +}; + +struct ta_hdcp_cmd_hdcp1_second_part_authentication_output { + enum ta_hdcp_authentication_status authentication_status; +}; + +struct ta_hdcp_cmd_hdcp1_enable_encryption_input { + uint32_t session_handle; +}; + +struct ta_hdcp_cmd_hdcp1_enable_dp_stream_encryption_input { + uint32_t session_handle; + uint32_t display_handle; +}; + +struct ta_hdcp_cmd_hdcp1_get_encryption_status_input { + uint32_t session_handle; +}; + +struct ta_hdcp_cmd_hdcp1_get_encryption_status_output { + uint32_t protection_level; +}; + +struct ta_hdcp_cmd_hdcp2_create_session_input_v2 { + uint32_t display_handle; + enum ta_hdcp_content_type_negotiation_type negotiate_content_type; +}; + +struct ta_hdcp_cmd_hdcp2_create_session_output_v2 { + uint32_t session_handle; +}; + +struct ta_hdcp_cmd_hdcp2_destroy_session_input { + uint32_t session_handle; +}; + +struct ta_hdcp_cmd_hdcp2_authentication_message_v2 { + enum ta_hdcp2_msg_id msg_id; + uint32_t msg_size; +}; + +struct ta_hdcp_cmd_hdcp2_process_authentication_message_input_v2 { + struct ta_hdcp_cmd_hdcp2_authentication_message_v2 msg1_desc; + struct ta_hdcp_cmd_hdcp2_authentication_message_v2 msg2_desc; + struct ta_hdcp_cmd_hdcp2_authentication_message_v2 msg3_desc; + uint8_t receiver_message[TA_HDCP__HDCP2_RX_BUF_MAX_SIZE]; +}; + +struct ta_hdcp_cmd_hdcp2_process_authentication_message_output_v2 { + uint32_t hdcp_version; + uint32_t is_km_stored; + uint32_t is_locality_precompute_support; + uint32_t is_repeater; + enum ta_hdcp2_msg_authentication_status msg1_status; + enum ta_hdcp2_msg_authentication_status msg2_status; + enum ta_hdcp2_msg_authentication_status msg3_status; +}; + +struct ta_hdcp_cmd_hdcp2_prepare_authentication_message_input_v2 { + enum ta_hdcp2_msg_id msg1_id; + enum ta_hdcp2_msg_id msg2_id; +}; + +struct ta_hdcp_cmd_hdcp2_prepare_authentication_message_output_v2 { + enum ta_hdcp2_msg_authentication_status msg1_status; + enum ta_hdcp2_msg_authentication_status msg2_status; + struct ta_hdcp_cmd_hdcp2_authentication_message_v2 msg1_desc; + struct ta_hdcp_cmd_hdcp2_authentication_message_v2 msg2_desc; + uint8_t transmitter_message[TA_HDCP__HDCP2_TX_BUF_MAX_SIZE]; +}; + +struct ta_hdcp_cmd_hdcp2_process_prepare_authentication_message_input_v2 { + uint32_t session_handle; + struct ta_hdcp_cmd_hdcp2_process_authentication_message_input_v2 process; + struct ta_hdcp_cmd_hdcp2_prepare_authentication_message_input_v2 prepare; +}; + +struct ta_hdcp_cmd_hdcp2_process_prepare_authentication_message_output_v2 { + uint32_t authentication_status; + struct ta_hdcp_cmd_hdcp2_process_authentication_message_output_v2 process; + struct ta_hdcp_cmd_hdcp2_prepare_authentication_message_output_v2 prepare; +}; + +struct ta_hdcp_cmd_hdcp2_set_encryption_input { + uint32_t session_handle; +}; + +struct ta_hdcp_cmd_hdcp2_get_encryption_status_input { + uint32_t session_handle; +}; + +struct ta_hdcp_cmd_hdcp2_get_encryption_status_output { + enum ta_hdcp_content_type hdcp2_type; + uint32_t protection_level; +}; + +struct ta_hdcp_cmd_hdcp2_enable_dp_stream_encryption_input { + uint32_t session_handle; + uint32_t display_handle; +}; + +struct ta_hdcp_cmd_set_srm_input { + uint32_t srm_buf_size; + uint8_t srm_buf[PSP_HDCP_SRM_FIRST_GEN_MAX_SIZE]; +}; + +struct ta_hdcp_cmd_set_srm_output { + uint8_t valid_signature; + uint32_t srm_version; +}; + +struct ta_hdcp_cmd_get_srm_output { + uint32_t srm_version; + uint32_t srm_buf_size; + uint8_t srm_buf[PSP_HDCP_SRM_FIRST_GEN_MAX_SIZE]; +}; + +/**********************************************************/ +/* Common input structure for HDCP callbacks */ +union ta_hdcp_cmd_input { + struct ta_hdcp_cmd_hdcp1_create_session_input hdcp1_create_session; + struct ta_hdcp_cmd_hdcp1_destroy_session_input hdcp1_destroy_session; + struct ta_hdcp_cmd_hdcp1_first_part_authentication_input hdcp1_first_part_authentication; + struct ta_hdcp_cmd_hdcp1_second_part_authentication_input hdcp1_second_part_authentication; + struct ta_hdcp_cmd_hdcp1_enable_encryption_input hdcp1_enable_encryption; + struct ta_hdcp_cmd_hdcp1_enable_dp_stream_encryption_input hdcp1_enable_dp_stream_encryption; + struct ta_hdcp_cmd_hdcp1_get_encryption_status_input hdcp1_get_encryption_status; + struct ta_hdcp_cmd_hdcp2_destroy_session_input hdcp2_destroy_session; + struct ta_hdcp_cmd_hdcp2_set_encryption_input hdcp2_set_encryption; + struct ta_hdcp_cmd_hdcp2_get_encryption_status_input hdcp2_get_encryption_status; + struct ta_hdcp_cmd_hdcp2_create_session_input_v2 hdcp2_create_session_v2; + struct ta_hdcp_cmd_hdcp2_process_prepare_authentication_message_input_v2 + hdcp2_prepare_process_authentication_message_v2; + struct ta_hdcp_cmd_hdcp2_enable_dp_stream_encryption_input hdcp2_enable_dp_stream_encryption; + struct ta_hdcp_cmd_set_srm_input hdcp_set_srm; +}; + +/* Common output structure for HDCP callbacks */ +union ta_hdcp_cmd_output { + struct ta_hdcp_cmd_hdcp1_create_session_output hdcp1_create_session; + struct ta_hdcp_cmd_hdcp1_first_part_authentication_output hdcp1_first_part_authentication; + struct ta_hdcp_cmd_hdcp1_second_part_authentication_output hdcp1_second_part_authentication; + struct ta_hdcp_cmd_hdcp1_get_encryption_status_output hdcp1_get_encryption_status; + struct ta_hdcp_cmd_hdcp2_get_encryption_status_output hdcp2_get_encryption_status; + struct ta_hdcp_cmd_hdcp2_create_session_output_v2 hdcp2_create_session_v2; + struct ta_hdcp_cmd_hdcp2_process_prepare_authentication_message_output_v2 + hdcp2_prepare_process_authentication_message_v2; + struct ta_hdcp_cmd_set_srm_output hdcp_set_srm; + struct ta_hdcp_cmd_get_srm_output hdcp_get_srm; +}; +/**********************************************************/ + +struct ta_hdcp_shared_memory { + uint32_t cmd_id; + enum ta_hdcp_status hdcp_status; + uint32_t reserved; + union ta_hdcp_cmd_input in_msg; + union ta_hdcp_cmd_output out_msg; +}; + +enum psp_status { + PSP_STATUS__SUCCESS = 0, + PSP_STATUS__ERROR_INVALID_PARAMS, + PSP_STATUS__ERROR_GENERIC, + PSP_STATUS__ERROR_OUT_OF_MEMORY, + PSP_STATUS__ERROR_UNSUPPORTED_FEATURE +}; + +#endif /* MODULES_HDCP_HDCP_PSP_H_ */ 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..afe1f6cce --- /dev/null +++ b/drivers/gpu/drm/amd/display/modules/inc/mod_freesync.h @@ -0,0 +1,200 @@ +/* + * 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 "mod_shared.h" + +// Access structures +struct mod_freesync { + int dummy; +}; + +// TODO: References to this should be removed +struct mod_freesync_caps { + bool supported; + unsigned int min_refresh_in_micro_hz; + unsigned int max_refresh_in_micro_hz; +}; + +enum mod_vrr_state { + VRR_STATE_UNSUPPORTED = 0, + VRR_STATE_DISABLED, + VRR_STATE_INACTIVE, + VRR_STATE_ACTIVE_VARIABLE, + VRR_STATE_ACTIVE_FIXED +}; + +struct mod_freesync_config { + enum mod_vrr_state state; + bool vsif_supported; + bool ramping; + bool btr; + unsigned int min_refresh_in_uhz; + unsigned int max_refresh_in_uhz; + unsigned int fixed_refresh_in_uhz; + +}; + +struct mod_vrr_params_btr { + bool btr_enabled; + bool btr_active; + uint32_t mid_point_in_us; + uint32_t inserted_duration_in_us; + uint32_t frames_to_insert; + uint32_t frame_counter; + uint32_t margin_in_us; +}; + +struct mod_vrr_params_fixed_refresh { + bool fixed_active; + bool ramping_active; + bool ramping_done; + uint32_t target_refresh_in_uhz; + uint32_t frame_counter; +}; + +struct mod_vrr_params_flip_interval { + bool flip_interval_workaround_active; + bool program_flip_interval_workaround; + bool do_flip_interval_workaround_cleanup; + uint32_t flip_interval_detect_counter; + uint32_t vsyncs_between_flip; + uint32_t vsync_to_flip_in_us; + uint32_t v_update_timestamp_in_us; +}; + +struct mod_vrr_params { + bool supported; + bool send_info_frame; + enum mod_vrr_state state; + + uint32_t min_refresh_in_uhz; + uint32_t max_duration_in_us; + uint32_t max_refresh_in_uhz; + uint32_t min_duration_in_us; + uint32_t fixed_refresh_in_uhz; + + struct dc_crtc_timing_adjust adjust; + + struct mod_vrr_params_fixed_refresh fixed; + + struct mod_vrr_params_btr btr; + + struct mod_vrr_params_flip_interval flip_interval; +}; + +struct mod_freesync *mod_freesync_create(struct dc *dc); +void mod_freesync_destroy(struct mod_freesync *mod_freesync); + +bool mod_freesync_get_vmin_vmax(struct mod_freesync *mod_freesync, + const 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_get_settings(struct mod_freesync *mod_freesync, + const struct mod_vrr_params *vrr, + 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); + +void mod_freesync_build_vrr_infopacket(struct mod_freesync *mod_freesync, + const struct dc_stream_state *stream, + const struct mod_vrr_params *vrr, + enum vrr_packet_type packet_type, + enum color_transfer_func app_tf, + struct dc_info_packet *infopacket, + bool pack_sdp_v1_3); + +void mod_freesync_build_vrr_params(struct mod_freesync *mod_freesync, + const struct dc_stream_state *stream, + struct mod_freesync_config *in_config, + struct mod_vrr_params *in_out_vrr); + +void mod_freesync_handle_preflip(struct mod_freesync *mod_freesync, + const struct dc_plane_state *plane, + const struct dc_stream_state *stream, + unsigned int curr_time_stamp_in_us, + struct mod_vrr_params *in_out_vrr); + +void mod_freesync_handle_v_update(struct mod_freesync *mod_freesync, + const struct dc_stream_state *stream, + struct mod_vrr_params *in_out_vrr); + +unsigned long long mod_freesync_calc_nominal_field_rate( + const struct dc_stream_state *stream); + +unsigned long long mod_freesync_calc_field_rate_from_timing( + unsigned int vtotal, unsigned int htotal, unsigned int pix_clk); + +bool mod_freesync_is_valid_range(uint32_t min_refresh_cap_in_uhz, + uint32_t max_refresh_cap_in_uhz, + uint32_t nominal_field_rate_in_uhz); + +unsigned int mod_freesync_calc_v_total_from_refresh( + const struct dc_stream_state *stream, + unsigned int refresh_in_uhz); + +// Returns true when FreeSync is supported and enabled (even if it is inactive) +bool mod_freesync_get_freesync_enabled(struct mod_vrr_params *pVrr); + +#endif diff --git a/drivers/gpu/drm/amd/display/modules/inc/mod_hdcp.h b/drivers/gpu/drm/amd/display/modules/inc/mod_hdcp.h new file mode 100644 index 000000000..3348bb97e --- /dev/null +++ b/drivers/gpu/drm/amd/display/modules/inc/mod_hdcp.h @@ -0,0 +1,324 @@ +/* + * 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. + * + * Authors: AMD + * + */ + +#ifndef MOD_HDCP_H_ +#define MOD_HDCP_H_ + +#include "os_types.h" +#include "signal_types.h" + +/* Forward Declarations */ +struct mod_hdcp; + +#define MAX_NUM_OF_DISPLAYS 6 +#define MAX_NUM_OF_ATTEMPTS 4 +#define MAX_NUM_OF_ERROR_TRACE 10 + +/* detailed return status */ +enum mod_hdcp_status { + MOD_HDCP_STATUS_SUCCESS = 0, + MOD_HDCP_STATUS_FAILURE, + MOD_HDCP_STATUS_RESET_NEEDED, + MOD_HDCP_STATUS_DISPLAY_OUT_OF_BOUND, + MOD_HDCP_STATUS_DISPLAY_NOT_FOUND, + MOD_HDCP_STATUS_INVALID_STATE, + MOD_HDCP_STATUS_NOT_IMPLEMENTED, + MOD_HDCP_STATUS_INTERNAL_POLICY_FAILURE, + MOD_HDCP_STATUS_UPDATE_TOPOLOGY_FAILURE, + MOD_HDCP_STATUS_CREATE_PSP_SERVICE_FAILURE, + MOD_HDCP_STATUS_DESTROY_PSP_SERVICE_FAILURE, + MOD_HDCP_STATUS_HDCP1_CREATE_SESSION_FAILURE, + MOD_HDCP_STATUS_HDCP1_DESTROY_SESSION_FAILURE, + MOD_HDCP_STATUS_HDCP1_VALIDATE_ENCRYPTION_FAILURE, + MOD_HDCP_STATUS_HDCP1_NOT_HDCP_REPEATER, + MOD_HDCP_STATUS_HDCP1_NOT_CAPABLE, + MOD_HDCP_STATUS_HDCP1_R0_PRIME_PENDING, + MOD_HDCP_STATUS_HDCP1_VALIDATE_RX_FAILURE, + MOD_HDCP_STATUS_HDCP1_BKSV_REVOKED, + MOD_HDCP_STATUS_HDCP1_KSV_LIST_NOT_READY, + MOD_HDCP_STATUS_HDCP1_VALIDATE_KSV_LIST_FAILURE, + MOD_HDCP_STATUS_HDCP1_KSV_LIST_REVOKED, + MOD_HDCP_STATUS_HDCP1_ENABLE_ENCRYPTION_FAILURE, + MOD_HDCP_STATUS_HDCP1_ENABLE_STREAM_ENCRYPTION_FAILURE, + MOD_HDCP_STATUS_HDCP1_MAX_CASCADE_EXCEEDED_FAILURE, + MOD_HDCP_STATUS_HDCP1_MAX_DEVS_EXCEEDED_FAILURE, + MOD_HDCP_STATUS_HDCP1_DEVICE_COUNT_MISMATCH_FAILURE, + MOD_HDCP_STATUS_HDCP1_LINK_INTEGRITY_FAILURE, + MOD_HDCP_STATUS_HDCP1_REAUTH_REQUEST_ISSUED, + MOD_HDCP_STATUS_HDCP1_LINK_MAINTENANCE_FAILURE, + MOD_HDCP_STATUS_HDCP1_INVALID_BKSV, + MOD_HDCP_STATUS_DDC_FAILURE, /* TODO: specific errors */ + MOD_HDCP_STATUS_INVALID_OPERATION, + MOD_HDCP_STATUS_HDCP2_NOT_CAPABLE, + MOD_HDCP_STATUS_HDCP2_CREATE_SESSION_FAILURE, + MOD_HDCP_STATUS_HDCP2_DESTROY_SESSION_FAILURE, + MOD_HDCP_STATUS_HDCP2_PREP_AKE_INIT_FAILURE, + MOD_HDCP_STATUS_HDCP2_AKE_CERT_PENDING, + MOD_HDCP_STATUS_HDCP2_H_PRIME_PENDING, + MOD_HDCP_STATUS_HDCP2_PAIRING_INFO_PENDING, + MOD_HDCP_STATUS_HDCP2_VALIDATE_AKE_CERT_FAILURE, + MOD_HDCP_STATUS_HDCP2_AKE_CERT_REVOKED, + MOD_HDCP_STATUS_HDCP2_VALIDATE_H_PRIME_FAILURE, + MOD_HDCP_STATUS_HDCP2_VALIDATE_PAIRING_INFO_FAILURE, + MOD_HDCP_STATUS_HDCP2_PREP_LC_INIT_FAILURE, + MOD_HDCP_STATUS_HDCP2_L_PRIME_PENDING, + MOD_HDCP_STATUS_HDCP2_VALIDATE_L_PRIME_FAILURE, + MOD_HDCP_STATUS_HDCP2_PREP_EKS_FAILURE, + MOD_HDCP_STATUS_HDCP2_ENABLE_ENCRYPTION_FAILURE, + MOD_HDCP_STATUS_HDCP2_RX_ID_LIST_NOT_READY, + MOD_HDCP_STATUS_HDCP2_VALIDATE_RX_ID_LIST_FAILURE, + MOD_HDCP_STATUS_HDCP2_RX_ID_LIST_REVOKED, + MOD_HDCP_STATUS_HDCP2_ENABLE_STREAM_ENCRYPTION_FAILURE, + MOD_HDCP_STATUS_HDCP2_STREAM_READY_PENDING, + MOD_HDCP_STATUS_HDCP2_VALIDATE_STREAM_READY_FAILURE, + MOD_HDCP_STATUS_HDCP2_PREPARE_STREAM_MANAGEMENT_FAILURE, + MOD_HDCP_STATUS_HDCP2_REAUTH_REQUEST, + MOD_HDCP_STATUS_HDCP2_REAUTH_LINK_INTEGRITY_FAILURE, + MOD_HDCP_STATUS_HDCP2_DEVICE_COUNT_MISMATCH_FAILURE, + MOD_HDCP_STATUS_UNSUPPORTED_PSP_VER_FAILURE, +}; + +struct mod_hdcp_displayport { + uint8_t rev; + uint8_t assr_enabled; + uint8_t mst_enabled; + uint8_t usb4_enabled; +}; + +struct mod_hdcp_hdmi { + uint8_t reserved; +}; +enum mod_hdcp_operation_mode { + MOD_HDCP_MODE_OFF, + MOD_HDCP_MODE_DEFAULT, + MOD_HDCP_MODE_DP +}; + +enum mod_hdcp_display_state { + MOD_HDCP_DISPLAY_INACTIVE = 0, + MOD_HDCP_DISPLAY_ACTIVE, + MOD_HDCP_DISPLAY_ENCRYPTION_ENABLED +}; + +struct mod_hdcp_psp_caps { + uint8_t dtm_v3_supported; +}; + +enum mod_hdcp_display_disable_option { + MOD_HDCP_DISPLAY_NOT_DISABLE = 0, + MOD_HDCP_DISPLAY_DISABLE_AUTHENTICATION, + MOD_HDCP_DISPLAY_DISABLE_ENCRYPTION, +}; + +struct mod_hdcp_ddc { + void *handle; + struct { + bool (*read_i2c)(void *handle, + uint32_t address, + uint8_t offset, + uint8_t *data, + uint32_t size); + bool (*write_i2c)(void *handle, + uint32_t address, + const uint8_t *data, + uint32_t size); + bool (*read_dpcd)(void *handle, + uint32_t address, + uint8_t *data, + uint32_t size); + bool (*write_dpcd)(void *handle, + uint32_t address, + const uint8_t *data, + uint32_t size); + } funcs; +}; + +struct mod_hdcp_psp { + void *handle; + void *funcs; + struct mod_hdcp_psp_caps caps; +}; + +struct mod_hdcp_display_adjustment { + uint8_t disable : 2; + uint8_t reserved : 6; +}; + +struct mod_hdcp_link_adjustment_hdcp1 { + uint8_t disable : 1; + uint8_t postpone_encryption : 1; + uint8_t min_auth_retries_wa : 1; + uint8_t reserved : 5; +}; + +enum mod_hdcp_force_hdcp_type { + MOD_HDCP_FORCE_TYPE_MAX = 0, + MOD_HDCP_FORCE_TYPE_0, + MOD_HDCP_FORCE_TYPE_1 +}; + +struct mod_hdcp_link_adjustment_hdcp2 { + uint8_t disable : 1; + uint8_t force_type : 2; + uint8_t force_no_stored_km : 1; + uint8_t increase_h_prime_timeout: 1; + uint8_t reserved : 3; +}; + +struct mod_hdcp_link_adjustment { + uint8_t auth_delay; + struct mod_hdcp_link_adjustment_hdcp1 hdcp1; + struct mod_hdcp_link_adjustment_hdcp2 hdcp2; +}; + +struct mod_hdcp_error { + enum mod_hdcp_status status; + uint8_t state_id; +}; + +struct mod_hdcp_trace { + struct mod_hdcp_error errors[MAX_NUM_OF_ERROR_TRACE]; + uint8_t error_count; +}; + +enum mod_hdcp_encryption_status { + MOD_HDCP_ENCRYPTION_STATUS_HDCP_OFF = 0, + MOD_HDCP_ENCRYPTION_STATUS_HDCP1_ON, + MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE0_ON, + MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON, + MOD_HDCP_ENCRYPTION_STATUS_HDCP2_ON +}; + +/* per link events dm has to notify to hdcp module */ +enum mod_hdcp_event { + MOD_HDCP_EVENT_CALLBACK = 0, + MOD_HDCP_EVENT_WATCHDOG_TIMEOUT, + MOD_HDCP_EVENT_CPIRQ +}; + +/* output flags from module requesting timer operations */ +struct mod_hdcp_output { + uint8_t callback_needed; + uint8_t callback_stop; + uint8_t watchdog_timer_needed; + uint8_t watchdog_timer_stop; + uint16_t callback_delay; + uint16_t watchdog_timer_delay; + uint8_t auth_complete; +}; + +/* used to represent per display info */ +struct mod_hdcp_display { + enum mod_hdcp_display_state state; + uint8_t index; + uint8_t controller; + uint8_t dig_fe; + uint8_t stream_enc_idx; + union { + uint8_t vc_id; + }; + struct mod_hdcp_display_adjustment adjust; +}; + +/* used to represent per link info */ +/* in case a link has multiple displays, they share the same link info */ +struct mod_hdcp_link { + enum mod_hdcp_operation_mode mode; + uint8_t dig_be; + uint8_t ddc_line; + uint8_t link_enc_idx; + uint8_t phy_idx; + uint8_t dio_output_id; + uint8_t hdcp_supported_informational; + union { + struct mod_hdcp_displayport dp; + struct mod_hdcp_hdmi hdmi; + }; + struct mod_hdcp_link_adjustment adjust; +}; + +/* a query structure for a display's hdcp information */ +struct mod_hdcp_display_query { + const struct mod_hdcp_display *display; + const struct mod_hdcp_link *link; + const struct mod_hdcp_trace *trace; + enum mod_hdcp_encryption_status encryption_status; +}; + +/* contains values per on external display configuration change */ +struct mod_hdcp_config { + struct mod_hdcp_psp psp; + struct mod_hdcp_ddc ddc; + uint8_t index; +}; + +/* dm allocates memory of mod_hdcp per dc_link on dm init based on memory size*/ +size_t mod_hdcp_get_memory_size(void); + +/* called per link on link creation */ +enum mod_hdcp_status mod_hdcp_setup(struct mod_hdcp *hdcp, + struct mod_hdcp_config *config); + +/* called per link on link destroy */ +enum mod_hdcp_status mod_hdcp_teardown(struct mod_hdcp *hdcp); + +/* called per display after stream is enabled */ +enum mod_hdcp_status mod_hdcp_add_display(struct mod_hdcp *hdcp, + struct mod_hdcp_link *link, struct mod_hdcp_display *display, + struct mod_hdcp_output *output); + +/* called per display before stream is disabled */ +enum mod_hdcp_status mod_hdcp_remove_display(struct mod_hdcp *hdcp, + uint8_t index, struct mod_hdcp_output *output); + +/* called per display to apply new authentication adjustment */ +enum mod_hdcp_status mod_hdcp_update_display(struct mod_hdcp *hdcp, + uint8_t index, + struct mod_hdcp_link_adjustment *link_adjust, + struct mod_hdcp_display_adjustment *display_adjust, + struct mod_hdcp_output *output); + +/* called to query hdcp information on a specific index */ +enum mod_hdcp_status mod_hdcp_query_display(struct mod_hdcp *hdcp, + uint8_t index, struct mod_hdcp_display_query *query); + +/* called per link on connectivity change */ +enum mod_hdcp_status mod_hdcp_reset_connection(struct mod_hdcp *hdcp, + struct mod_hdcp_output *output); + +/* called per link on events (i.e. callback, watchdog, CP_IRQ) */ +enum mod_hdcp_status mod_hdcp_process_event(struct mod_hdcp *hdcp, + enum mod_hdcp_event event, struct mod_hdcp_output *output); + +/* called to convert enum mod_hdcp_status to c string */ +char *mod_hdcp_status_to_str(int32_t status); + +/* called to convert state id to c string */ +char *mod_hdcp_state_id_to_str(int32_t id); + +/* called to convert signal type to operation mode */ +enum mod_hdcp_operation_mode mod_hdcp_signal_type_to_operation_mode( + enum signal_type signal); +#endif /* MOD_HDCP_H_ */ diff --git a/drivers/gpu/drm/amd/display/modules/inc/mod_info_packet.h b/drivers/gpu/drm/amd/display/modules/inc/mod_info_packet.h new file mode 100644 index 000000000..1d8b746b0 --- /dev/null +++ b/drivers/gpu/drm/amd/display/modules/inc/mod_info_packet.h @@ -0,0 +1,43 @@ +/* + * 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. + * + * Authors: AMD + * + */ + +#ifndef MOD_INFO_PACKET_H_ +#define MOD_INFO_PACKET_H_ + +#include "dm_services.h" +#include "mod_shared.h" +//Forward Declarations +struct dc_stream_state; +struct dc_info_packet; +struct mod_vrr_params; + +void mod_build_vsc_infopacket(const struct dc_stream_state *stream, + struct dc_info_packet *info_packet, + enum dc_color_space cs); + +void mod_build_hf_vsif_infopacket(const struct dc_stream_state *stream, + struct dc_info_packet *info_packet); + +#endif diff --git a/drivers/gpu/drm/amd/display/modules/inc/mod_shared.h b/drivers/gpu/drm/amd/display/modules/inc/mod_shared.h new file mode 100644 index 000000000..198c0e64d --- /dev/null +++ b/drivers/gpu/drm/amd/display/modules/inc/mod_shared.h @@ -0,0 +1,108 @@ +/* + * 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_SHARED_H_ +#define MOD_SHARED_H_ + +enum color_transfer_func { + TRANSFER_FUNC_UNKNOWN, + TRANSFER_FUNC_SRGB, + TRANSFER_FUNC_BT709, + TRANSFER_FUNC_PQ2084, + TRANSFER_FUNC_PQ2084_INTERIM, + TRANSFER_FUNC_LINEAR_0_1, + TRANSFER_FUNC_LINEAR_0_125, + TRANSFER_FUNC_GAMMA_22, + TRANSFER_FUNC_GAMMA_26 +}; + +enum vrr_packet_type { + PACKET_TYPE_VRR, + PACKET_TYPE_FS_V1, + PACKET_TYPE_FS_V2, + PACKET_TYPE_FS_V3, + PACKET_TYPE_VTEM +}; + +union lut3d_control_flags { + unsigned int raw; + struct { + unsigned int do_chroma_scale :1; + unsigned int spec_version :3; + unsigned int use_zero_display_black :1; + unsigned int use_zero_source_black :1; + unsigned int force_display_black :6; + unsigned int apply_display_gamma :1; + unsigned int exp_shaper_max :6; + unsigned int unity_3dlut :1; + unsigned int bypass_3dlut :1; + unsigned int use_3dlut :1; + unsigned int less_than_dcip3 :1; + unsigned int override_lum :1; + unsigned int use_gamut_map_lib :1; + unsigned int chromatic_adaptation_src :1; + unsigned int chromatic_adaptation_dst :1; + unsigned int do_blender_lut_degamma :1; + unsigned int reseved :4; + } bits; +}; + +enum tm_show_option_internal { + tm_show_option_internal_single_file = 0,/*flags2 not in use*/ + tm_show_option_internal_duplicate_file, /*use flags2*/ + tm_show_option_internal_duplicate_sidebyside/*use flags2*/ +}; + +enum lut3d_control_gamut_map { + lut3d_control_gamut_map_none = 0, + lut3d_control_gamut_map_tonemap, + lut3d_control_gamut_map_chto, + lut3d_control_gamut_map_chso, + lut3d_control_gamut_map_chci +}; + +enum lut3d_control_rotation_mode { + lut3d_control_rotation_mode_none = 0, + lut3d_control_rotation_mode_hue, + lut3d_control_rotation_mode_cc, + lut3d_control_rotation_mode_hue_cc +}; + +struct lut3d_settings { + unsigned char version; + union lut3d_control_flags flags; + union lut3d_control_flags flags2; + enum tm_show_option_internal option; + unsigned int min_lum;/*multiplied by 100*/ + unsigned int max_lum; + unsigned int min_lum2; + unsigned int max_lum2; + enum lut3d_control_gamut_map map; + enum lut3d_control_rotation_mode rotation; + enum lut3d_control_gamut_map map2; + enum lut3d_control_rotation_mode rotation2; +}; + +#endif /* MOD_SHARED_H_ */ 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..4220fd8fd --- /dev/null +++ b/drivers/gpu/drm/amd/display/modules/inc/mod_stats.h @@ -0,0 +1,75 @@ +/* + * 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_init_params { + unsigned int stats_enable; + unsigned int stats_entries; +}; + +struct mod_stats *mod_stats_create(struct dc *dc, + struct mod_stats_init_params *init_params); + +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/inc/mod_vmid.h b/drivers/gpu/drm/amd/display/modules/inc/mod_vmid.h new file mode 100644 index 000000000..a3787fdf0 --- /dev/null +++ b/drivers/gpu/drm/amd/display/modules/inc/mod_vmid.h @@ -0,0 +1,46 @@ +/* + * Copyright 2019 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_VMID_H_ +#define MOD_VMID_H_ + +#define MAX_VMID 16 + +#include "dc.h" + +struct mod_vmid { + int dummy; +}; + +uint8_t mod_vmid_get_for_ptb(struct mod_vmid *mod_vmid, uint64_t ptb); +void mod_vmid_reset(struct mod_vmid *mod_vmid); +struct mod_vmid *mod_vmid_create( + struct dc *dc, + unsigned int num_vmid, + struct dc_virtual_addr_space_config *va_config); + +void mod_vmid_destroy(struct mod_vmid *mod_vmid); + +#endif /* MOD_VMID_H_ */ diff --git a/drivers/gpu/drm/amd/display/modules/info_packet/Makefile b/drivers/gpu/drm/amd/display/modules/info_packet/Makefile new file mode 100644 index 000000000..4c382d728 --- /dev/null +++ b/drivers/gpu/drm/amd/display/modules/info_packet/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 'info_packet' sub-module of DAL. +# + +INFO_PACKET = info_packet.o + +AMD_DAL_INFO_PACKET = $(addprefix $(AMDDALPATH)/modules/info_packet/,$(INFO_PACKET)) +#$(info ************ DAL INFO_PACKET MODULE MAKEFILE ************) + +AMD_DISPLAY_FILES += $(AMD_DAL_INFO_PACKET) diff --git a/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c b/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c new file mode 100644 index 000000000..27ceba9d6 --- /dev/null +++ b/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c @@ -0,0 +1,517 @@ +/* + * 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. + * + * Authors: AMD + * + */ + +#include "mod_info_packet.h" +#include "core_types.h" +#include "dc_types.h" +#include "mod_shared.h" +#include "mod_freesync.h" +#include "dc.h" + +enum vsc_packet_revision { + vsc_packet_undefined = 0, + //01h = VSC SDP supports only 3D stereo. + vsc_packet_rev1 = 1, + //02h = 3D stereo + PSR. + vsc_packet_rev2 = 2, + //03h = 3D stereo + PSR2. + vsc_packet_rev3 = 3, + //04h = 3D stereo + PSR/PSR2 + Y-coordinate. + vsc_packet_rev4 = 4, + //05h = 3D stereo + PSR/PSR2 + Y-coordinate + Pixel Encoding/Colorimetry Format + vsc_packet_rev5 = 5, +}; + +#define HDMI_INFOFRAME_TYPE_VENDOR 0x81 +#define HF_VSIF_VERSION 1 + +// VTEM Byte Offset +#define VTEM_PB0 0 +#define VTEM_PB1 1 +#define VTEM_PB2 2 +#define VTEM_PB3 3 +#define VTEM_PB4 4 +#define VTEM_PB5 5 +#define VTEM_PB6 6 + +#define VTEM_MD0 7 +#define VTEM_MD1 8 +#define VTEM_MD2 9 +#define VTEM_MD3 10 + + +// VTEM Byte Masks +//PB0 +#define MASK_VTEM_PB0__RESERVED0 0x01 +#define MASK_VTEM_PB0__SYNC 0x02 +#define MASK_VTEM_PB0__VFR 0x04 +#define MASK_VTEM_PB0__AFR 0x08 +#define MASK_VTEM_PB0__DS_TYPE 0x30 + //0: Periodic pseudo-static EM Data Set + //1: Periodic dynamic EM Data Set + //2: Unique EM Data Set + //3: Reserved +#define MASK_VTEM_PB0__END 0x40 +#define MASK_VTEM_PB0__NEW 0x80 + +//PB1 +#define MASK_VTEM_PB1__RESERVED1 0xFF + +//PB2 +#define MASK_VTEM_PB2__ORGANIZATION_ID 0xFF + //0: This is a Vendor Specific EM Data Set + //1: This EM Data Set is defined by This Specification (HDMI 2.1 r102.clean) + //2: This EM Data Set is defined by CTA-861-G + //3: This EM Data Set is defined by VESA +//PB3 +#define MASK_VTEM_PB3__DATA_SET_TAG_MSB 0xFF +//PB4 +#define MASK_VTEM_PB4__DATA_SET_TAG_LSB 0xFF +//PB5 +#define MASK_VTEM_PB5__DATA_SET_LENGTH_MSB 0xFF +//PB6 +#define MASK_VTEM_PB6__DATA_SET_LENGTH_LSB 0xFF + + + +//PB7-27 (20 bytes): +//PB7 = MD0 +#define MASK_VTEM_MD0__VRR_EN 0x01 +#define MASK_VTEM_MD0__M_CONST 0x02 +#define MASK_VTEM_MD0__QMS_EN 0x04 +#define MASK_VTEM_MD0__RESERVED2 0x08 +#define MASK_VTEM_MD0__FVA_FACTOR_M1 0xF0 + +//MD1 +#define MASK_VTEM_MD1__BASE_VFRONT 0xFF + +//MD2 +#define MASK_VTEM_MD2__BASE_REFRESH_RATE_98 0x03 +#define MASK_VTEM_MD2__RB 0x04 +#define MASK_VTEM_MD2__NEXT_TFR 0xF8 + +//MD3 +#define MASK_VTEM_MD3__BASE_REFRESH_RATE_07 0xFF + +enum ColorimetryRGBDP { + ColorimetryRGB_DP_sRGB = 0, + ColorimetryRGB_DP_AdobeRGB = 3, + ColorimetryRGB_DP_P3 = 4, + ColorimetryRGB_DP_CustomColorProfile = 5, + ColorimetryRGB_DP_ITU_R_BT2020RGB = 6, +}; +enum ColorimetryYCCDP { + ColorimetryYCC_DP_ITU601 = 0, + ColorimetryYCC_DP_ITU709 = 1, + ColorimetryYCC_DP_AdobeYCC = 5, + ColorimetryYCC_DP_ITU2020YCC = 6, + ColorimetryYCC_DP_ITU2020YCbCr = 7, +}; + +void mod_build_vsc_infopacket(const struct dc_stream_state *stream, + struct dc_info_packet *info_packet, + enum dc_color_space cs) +{ + unsigned int vsc_packet_revision = vsc_packet_undefined; + unsigned int i; + unsigned int pixelEncoding = 0; + unsigned int colorimetryFormat = 0; + bool stereo3dSupport = false; + + if (stream->timing.timing_3d_format != TIMING_3D_FORMAT_NONE && stream->view_format != VIEW_3D_FORMAT_NONE) { + vsc_packet_revision = vsc_packet_rev1; + stereo3dSupport = true; + } + + /* VSC packet set to 4 for PSR-SU, or 2 for PSR1 */ + if (stream->link->psr_settings.psr_version == DC_PSR_VERSION_SU_1) + vsc_packet_revision = vsc_packet_rev4; + else if (stream->link->psr_settings.psr_version == DC_PSR_VERSION_1) + vsc_packet_revision = vsc_packet_rev2; + + /* Update to revision 5 for extended colorimetry support */ + if (stream->use_vsc_sdp_for_colorimetry) + vsc_packet_revision = vsc_packet_rev5; + + /* VSC packet not needed based on the features + * supported by this DP display + */ + if (vsc_packet_revision == vsc_packet_undefined) + return; + + if (vsc_packet_revision == vsc_packet_rev4) { + /* Secondary-data Packet ID = 0*/ + info_packet->hb0 = 0x00; + /* 07h - Packet Type Value indicating Video + * Stream Configuration packet + */ + info_packet->hb1 = 0x07; + /* 04h = VSC SDP supporting 3D stereo + PSR/PSR2 + Y-coordinate + * (applies to eDP v1.4 or higher). + */ + info_packet->hb2 = 0x04; + /* 0Eh = VSC SDP supporting 3D stereo + PSR2 + * (HB2 = 04h), with Y-coordinate of first scan + * line of the SU region + */ + info_packet->hb3 = 0x0E; + + for (i = 0; i < 28; i++) + info_packet->sb[i] = 0; + + info_packet->valid = true; + } + + if (vsc_packet_revision == vsc_packet_rev2) { + /* Secondary-data Packet ID = 0*/ + info_packet->hb0 = 0x00; + /* 07h - Packet Type Value indicating Video + * Stream Configuration packet + */ + info_packet->hb1 = 0x07; + /* 02h = VSC SDP supporting 3D stereo and PSR + * (applies to eDP v1.3 or higher). + */ + info_packet->hb2 = 0x02; + /* 08h = VSC packet supporting 3D stereo + PSR + * (HB2 = 02h). + */ + info_packet->hb3 = 0x08; + + for (i = 0; i < 28; i++) + info_packet->sb[i] = 0; + + info_packet->valid = true; + } + + if (vsc_packet_revision == vsc_packet_rev1) { + + info_packet->hb0 = 0x00; // Secondary-data Packet ID = 0 + info_packet->hb1 = 0x07; // 07h = Packet Type Value indicating Video Stream Configuration packet + info_packet->hb2 = 0x01; // 01h = Revision number. VSC SDP supporting 3D stereo only + info_packet->hb3 = 0x01; // 01h = VSC SDP supporting 3D stereo only (HB2 = 01h). + + info_packet->valid = true; + } + + if (stereo3dSupport) { + /* ==============================================================================================================| + * A. STEREO 3D + * ==============================================================================================================| + * VSC Payload (1 byte) From DP1.2 spec + * + * Bits 3:0 (Stereo Interface Method Code) | Bits 7:4 (Stereo Interface Method Specific Parameter) + * ----------------------------------------------------------------------------------------------------- + * 0 = Non Stereo Video | Must be set to 0x0 + * ----------------------------------------------------------------------------------------------------- + * 1 = Frame/Field Sequential | 0x0: L + R view indication based on MISC1 bit 2:1 + * | 0x1: Right when Stereo Signal = 1 + * | 0x2: Left when Stereo Signal = 1 + * | (others reserved) + * ----------------------------------------------------------------------------------------------------- + * 2 = Stacked Frame | 0x0: Left view is on top and right view on bottom + * | (others reserved) + * ----------------------------------------------------------------------------------------------------- + * 3 = Pixel Interleaved | 0x0: horiz interleaved, right view pixels on even lines + * | 0x1: horiz interleaved, right view pixels on odd lines + * | 0x2: checker board, start with left view pixel + * | 0x3: vertical interleaved, start with left view pixels + * | 0x4: vertical interleaved, start with right view pixels + * | (others reserved) + * ----------------------------------------------------------------------------------------------------- + * 4 = Side-by-side | 0x0: left half represents left eye view + * | 0x1: left half represents right eye view + */ + switch (stream->timing.timing_3d_format) { + case TIMING_3D_FORMAT_HW_FRAME_PACKING: + case TIMING_3D_FORMAT_SW_FRAME_PACKING: + case TIMING_3D_FORMAT_TOP_AND_BOTTOM: + case TIMING_3D_FORMAT_TB_SW_PACKED: + info_packet->sb[0] = 0x02; // Stacked Frame, Left view is on top and right view on bottom. + break; + case TIMING_3D_FORMAT_DP_HDMI_INBAND_FA: + case TIMING_3D_FORMAT_INBAND_FA: + info_packet->sb[0] = 0x01; // Frame/Field Sequential, L + R view indication based on MISC1 bit 2:1 + break; + case TIMING_3D_FORMAT_SIDE_BY_SIDE: + case TIMING_3D_FORMAT_SBS_SW_PACKED: + info_packet->sb[0] = 0x04; // Side-by-side + break; + default: + info_packet->sb[0] = 0x00; // No Stereo Video, Shall be cleared to 0x0. + break; + } + + } + + /* 05h = VSC SDP supporting 3D stereo, PSR2, and Pixel Encoding/Colorimetry Format indication. + * Added in DP1.3, a DP Source device is allowed to indicate the pixel encoding/colorimetry + * format to the DP Sink device with VSC SDP only when the DP Sink device supports it + * (i.e., VSC_SDP_EXTENSION_FOR_COLORIMETRY_SUPPORTED bit in the DPRX_FEATURE_ENUMERATION_LIST + * register (DPCD Address 02210h, bit 3) is set to 1). + * (Requires VSC_SDP_EXTENSION_FOR_COLORIMETRY_SUPPORTED bit set to 1 in DPCD 02210h. This + * DPCD register is exposed in the new Extended Receiver Capability field for DPCD Rev. 1.4 + * (and higher). When MISC1. bit 6. is Set to 1, a Source device uses a VSC SDP to indicate + * the Pixel Encoding/Colorimetry Format and that a Sink device must ignore MISC1, bit 7, and + * MISC0, bits 7:1 (MISC1, bit 7. and MISC0, bits 7:1 become "don't care").) + */ + if (vsc_packet_revision == vsc_packet_rev5) { + /* Secondary-data Packet ID = 0 */ + info_packet->hb0 = 0x00; + /* 07h - Packet Type Value indicating Video Stream Configuration packet */ + info_packet->hb1 = 0x07; + /* 05h = VSC SDP supporting 3D stereo, PSR2, and Pixel Encoding/Colorimetry Format indication. */ + info_packet->hb2 = 0x05; + /* 13h = VSC SDP supporting 3D stereo, + PSR2, + Pixel Encoding/Colorimetry Format indication (HB2 = 05h). */ + info_packet->hb3 = 0x13; + + info_packet->valid = true; + + /* Set VSC SDP fields for pixel encoding and colorimetry format from DP 1.3 specs + * Data Bytes DB 18~16 + * Bits 3:0 (Colorimetry Format) | Bits 7:4 (Pixel Encoding) + * ---------------------------------------------------------------------------------------------------- + * 0x0 = sRGB | 0 = RGB + * 0x1 = RGB Wide Gamut Fixed Point + * 0x2 = RGB Wide Gamut Floating Point + * 0x3 = AdobeRGB + * 0x4 = DCI-P3 + * 0x5 = CustomColorProfile + * (others reserved) + * ---------------------------------------------------------------------------------------------------- + * 0x0 = ITU-R BT.601 | 1 = YCbCr444 + * 0x1 = ITU-R BT.709 + * 0x2 = xvYCC601 + * 0x3 = xvYCC709 + * 0x4 = sYCC601 + * 0x5 = AdobeYCC601 + * 0x6 = ITU-R BT.2020 Y'cC'bcC'rc + * 0x7 = ITU-R BT.2020 Y'C'bC'r + * (others reserved) + * ---------------------------------------------------------------------------------------------------- + * 0x0 = ITU-R BT.601 | 2 = YCbCr422 + * 0x1 = ITU-R BT.709 + * 0x2 = xvYCC601 + * 0x3 = xvYCC709 + * 0x4 = sYCC601 + * 0x5 = AdobeYCC601 + * 0x6 = ITU-R BT.2020 Y'cC'bcC'rc + * 0x7 = ITU-R BT.2020 Y'C'bC'r + * (others reserved) + * ---------------------------------------------------------------------------------------------------- + * 0x0 = ITU-R BT.601 | 3 = YCbCr420 + * 0x1 = ITU-R BT.709 + * 0x2 = xvYCC601 + * 0x3 = xvYCC709 + * 0x4 = sYCC601 + * 0x5 = AdobeYCC601 + * 0x6 = ITU-R BT.2020 Y'cC'bcC'rc + * 0x7 = ITU-R BT.2020 Y'C'bC'r + * (others reserved) + * ---------------------------------------------------------------------------------------------------- + * 0x0 =DICOM Part14 Grayscale | 4 = Yonly + * Display Function + * (others reserved) + */ + + /* Set Pixel Encoding */ + switch (stream->timing.pixel_encoding) { + case PIXEL_ENCODING_RGB: + pixelEncoding = 0x0; /* RGB = 0h */ + break; + case PIXEL_ENCODING_YCBCR444: + pixelEncoding = 0x1; /* YCbCr444 = 1h */ + break; + case PIXEL_ENCODING_YCBCR422: + pixelEncoding = 0x2; /* YCbCr422 = 2h */ + break; + case PIXEL_ENCODING_YCBCR420: + pixelEncoding = 0x3; /* YCbCr420 = 3h */ + break; + default: + pixelEncoding = 0x0; /* default RGB = 0h */ + break; + } + + /* Set Colorimetry format based on pixel encoding */ + switch (stream->timing.pixel_encoding) { + case PIXEL_ENCODING_RGB: + if ((cs == COLOR_SPACE_SRGB) || + (cs == COLOR_SPACE_SRGB_LIMITED)) + colorimetryFormat = ColorimetryRGB_DP_sRGB; + else if (cs == COLOR_SPACE_ADOBERGB) + colorimetryFormat = ColorimetryRGB_DP_AdobeRGB; + else if ((cs == COLOR_SPACE_2020_RGB_FULLRANGE) || + (cs == COLOR_SPACE_2020_RGB_LIMITEDRANGE)) + colorimetryFormat = ColorimetryRGB_DP_ITU_R_BT2020RGB; + break; + + case PIXEL_ENCODING_YCBCR444: + case PIXEL_ENCODING_YCBCR422: + case PIXEL_ENCODING_YCBCR420: + /* Note: xvYCC probably not supported correctly here on DP since colorspace translation + * loses distinction between BT601 vs xvYCC601 in translation + */ + if (cs == COLOR_SPACE_YCBCR601) + colorimetryFormat = ColorimetryYCC_DP_ITU601; + else if (cs == COLOR_SPACE_YCBCR709) + colorimetryFormat = ColorimetryYCC_DP_ITU709; + else if (cs == COLOR_SPACE_ADOBERGB) + colorimetryFormat = ColorimetryYCC_DP_AdobeYCC; + else if (cs == COLOR_SPACE_2020_YCBCR) + colorimetryFormat = ColorimetryYCC_DP_ITU2020YCbCr; + break; + + default: + colorimetryFormat = ColorimetryRGB_DP_sRGB; + break; + } + + info_packet->sb[16] = (pixelEncoding << 4) | colorimetryFormat; + + /* Set color depth */ + switch (stream->timing.display_color_depth) { + case COLOR_DEPTH_666: + /* NOTE: This is actually not valid for YCbCr pixel encoding to have 6 bpc + * as of DP1.4 spec, but value of 0 probably reserved here for potential future use. + */ + info_packet->sb[17] = 0; + break; + case COLOR_DEPTH_888: + info_packet->sb[17] = 1; + break; + case COLOR_DEPTH_101010: + info_packet->sb[17] = 2; + break; + case COLOR_DEPTH_121212: + info_packet->sb[17] = 3; + break; + /*case COLOR_DEPTH_141414: -- NO SUCH FORMAT IN DP SPEC */ + case COLOR_DEPTH_161616: + info_packet->sb[17] = 4; + break; + default: + info_packet->sb[17] = 0; + break; + } + + /* all YCbCr are always limited range */ + if ((cs == COLOR_SPACE_SRGB_LIMITED) || + (cs == COLOR_SPACE_2020_RGB_LIMITEDRANGE) || + (pixelEncoding != 0x0)) { + info_packet->sb[17] |= 0x80; /* DB17 bit 7 set to 1 for CEA timing. */ + } + + /* Content Type (Bits 2:0) + * 0 = Not defined. + * 1 = Graphics. + * 2 = Photo. + * 3 = Video. + * 4 = Game. + */ + info_packet->sb[18] = 0; + } +} + +/** + * mod_build_hf_vsif_infopacket - Prepare HDMI Vendor Specific info frame. + * Follows HDMI Spec to build up Vendor Specific info frame + * + * @stream: contains data we may need to construct VSIF (i.e. timing_3d_format, etc.) + * @info_packet: output structure where to store VSIF + */ +void mod_build_hf_vsif_infopacket(const struct dc_stream_state *stream, + struct dc_info_packet *info_packet) +{ + unsigned int length = 5; + bool hdmi_vic_mode = false; + uint8_t checksum = 0; + uint32_t i = 0; + enum dc_timing_3d_format format; + + info_packet->valid = false; + format = stream->timing.timing_3d_format; + if (stream->view_format == VIEW_3D_FORMAT_NONE) + format = TIMING_3D_FORMAT_NONE; + + if (stream->timing.hdmi_vic != 0 + && stream->timing.h_total >= 3840 + && stream->timing.v_total >= 2160 + && format == TIMING_3D_FORMAT_NONE) + hdmi_vic_mode = true; + + if ((format == TIMING_3D_FORMAT_NONE) && !hdmi_vic_mode) + return; + + info_packet->sb[1] = 0x03; + info_packet->sb[2] = 0x0C; + info_packet->sb[3] = 0x00; + + if (format != TIMING_3D_FORMAT_NONE) + info_packet->sb[4] = (2 << 5); + + else if (hdmi_vic_mode) + info_packet->sb[4] = (1 << 5); + + switch (format) { + case TIMING_3D_FORMAT_HW_FRAME_PACKING: + case TIMING_3D_FORMAT_SW_FRAME_PACKING: + info_packet->sb[5] = (0x0 << 4); + break; + + case TIMING_3D_FORMAT_SIDE_BY_SIDE: + case TIMING_3D_FORMAT_SBS_SW_PACKED: + info_packet->sb[5] = (0x8 << 4); + length = 6; + break; + + case TIMING_3D_FORMAT_TOP_AND_BOTTOM: + case TIMING_3D_FORMAT_TB_SW_PACKED: + info_packet->sb[5] = (0x6 << 4); + break; + + default: + break; + } + + if (hdmi_vic_mode) + info_packet->sb[5] = stream->timing.hdmi_vic; + + info_packet->hb0 = HDMI_INFOFRAME_TYPE_VENDOR; + info_packet->hb1 = 0x01; + info_packet->hb2 = (uint8_t) (length); + + checksum += info_packet->hb0; + checksum += info_packet->hb1; + checksum += info_packet->hb2; + + for (i = 1; i <= length; i++) + checksum += info_packet->sb[i]; + + info_packet->sb[0] = (uint8_t) (0x100 - checksum); + + info_packet->valid = true; +} + diff --git a/drivers/gpu/drm/amd/display/modules/power/Makefile b/drivers/gpu/drm/amd/display/modules/power/Makefile new file mode 100644 index 000000000..9d1b22d35 --- /dev/null +++ b/drivers/gpu/drm/amd/display/modules/power/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 'power' sub-module of DAL. +# + +MOD_POWER = power_helpers.o + +AMD_DAL_MOD_POWER = $(addprefix $(AMDDALPATH)/modules/power/,$(MOD_POWER)) +#$(info ************ DAL POWER MODULE MAKEFILE ************) + +AMD_DISPLAY_FILES += $(AMD_DAL_MOD_POWER) diff --git a/drivers/gpu/drm/amd/display/modules/power/power_helpers.c b/drivers/gpu/drm/amd/display/modules/power/power_helpers.c new file mode 100644 index 000000000..e2f436dea --- /dev/null +++ b/drivers/gpu/drm/amd/display/modules/power/power_helpers.c @@ -0,0 +1,948 @@ +/* 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. + * + * Authors: AMD + * + */ + +#include "power_helpers.h" +#include "dc/inc/hw/dmcu.h" +#include "dc/inc/hw/abm.h" +#include "dc.h" +#include "core_types.h" +#include "dmub_cmd.h" + +#define DIV_ROUNDUP(a, b) (((a)+((b)/2))/(b)) +#define bswap16_based_on_endian(big_endian, value) \ + (big_endian) ? cpu_to_be16(value) : cpu_to_le16(value) + +/* Possible Min Reduction config from least aggressive to most aggressive + * 0 1 2 3 4 5 6 7 8 9 10 11 12 + * 100 98.0 94.1 94.1 85.1 80.3 75.3 69.4 60.0 57.6 50.2 49.8 40.0 % + */ +static const unsigned char min_reduction_table[13] = { +0xff, 0xfa, 0xf0, 0xf0, 0xd9, 0xcd, 0xc0, 0xb1, 0x99, 0x93, 0x80, 0x82, 0x66}; + +/* Possible Max Reduction configs from least aggressive to most aggressive + * 0 1 2 3 4 5 6 7 8 9 10 11 12 + * 96.1 89.8 85.1 80.3 69.4 64.7 64.7 50.2 39.6 30.2 30.2 30.2 19.6 % + */ +static const unsigned char max_reduction_table[13] = { +0xf5, 0xe5, 0xd9, 0xcd, 0xb1, 0xa5, 0xa5, 0x80, 0x65, 0x4d, 0x4d, 0x4d, 0x32}; + +/* Possible ABM 2.2 Min Reduction configs from least aggressive to most aggressive + * 0 1 2 3 4 5 6 7 8 9 10 11 12 + * 100 100 100 100 100 100 100 100 100 92.2 83.1 75.3 75.3 % + */ +static const unsigned char min_reduction_table_v_2_2[13] = { +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xeb, 0xd4, 0xc0, 0xc0}; + +/* Possible ABM 2.2 Max Reduction configs from least aggressive to most aggressive + * 0 1 2 3 4 5 6 7 8 9 10 11 12 + * 96.1 89.8 74.9 69.4 64.7 52.2 48.6 39.6 30.2 25.1 19.6 12.5 12.5 % + */ +static const unsigned char max_reduction_table_v_2_2[13] = { +0xf5, 0xe5, 0xbf, 0xb1, 0xa5, 0x85, 0x7c, 0x65, 0x4d, 0x40, 0x32, 0x20, 0x20}; + +/* Predefined ABM configuration sets. We may have different configuration sets + * in order to satisfy different power/quality requirements. + */ +static const unsigned char abm_config[abm_defines_max_config][abm_defines_max_level] = { +/* ABM Level 1, ABM Level 2, ABM Level 3, ABM Level 4 */ +{ 2, 5, 7, 8 }, /* Default - Medium aggressiveness */ +{ 2, 5, 8, 11 }, /* Alt #1 - Increased aggressiveness */ +{ 0, 2, 4, 8 }, /* Alt #2 - Minimal aggressiveness */ +{ 3, 6, 10, 12 }, /* Alt #3 - Super aggressiveness */ +}; + +struct abm_parameters { + unsigned char min_reduction; + unsigned char max_reduction; + unsigned char bright_pos_gain; + unsigned char dark_pos_gain; + unsigned char brightness_gain; + unsigned char contrast_factor; + unsigned char deviation_gain; + unsigned char min_knee; + unsigned char max_knee; + unsigned short blRampReduction; + unsigned short blRampStart; +}; + +static const struct abm_parameters abm_settings_config0[abm_defines_max_level] = { +// min_red max_red bright_pos dark_pos bright_gain contrast dev min_knee max_knee blRed blStart + {0xff, 0xbf, 0x20, 0x00, 0xff, 0x99, 0xb3, 0x40, 0xe0, 0xf777, 0xcccc}, + {0xde, 0x85, 0x20, 0x00, 0xe0, 0x90, 0xa8, 0x40, 0xc8, 0xf777, 0xcccc}, + {0xb0, 0x50, 0x20, 0x00, 0xc0, 0x88, 0x78, 0x70, 0xa0, 0xeeee, 0x9999}, + {0x82, 0x40, 0x20, 0x00, 0x00, 0xb8, 0xb3, 0x70, 0x70, 0xe333, 0xb333}, +}; + +static const struct abm_parameters abm_settings_config1[abm_defines_max_level] = { +// min_red max_red bright_pos dark_pos bright_gain contrast dev min_knee max_knee blRed blStart + {0xf0, 0xd9, 0x20, 0x00, 0x00, 0xff, 0xb3, 0x70, 0x70, 0xcccc, 0xcccc}, + {0xcd, 0xa5, 0x20, 0x00, 0x00, 0xff, 0xb3, 0x70, 0x70, 0xcccc, 0xcccc}, + {0x99, 0x65, 0x20, 0x00, 0x00, 0xff, 0xb3, 0x70, 0x70, 0xcccc, 0xcccc}, + {0x82, 0x4d, 0x20, 0x00, 0x00, 0xff, 0xb3, 0x70, 0x70, 0xcccc, 0xcccc}, +}; + +static const struct abm_parameters * const abm_settings[] = { + abm_settings_config0, + abm_settings_config1, +}; + +#define NUM_AMBI_LEVEL 5 +#define NUM_AGGR_LEVEL 4 +#define NUM_POWER_FN_SEGS 8 +#define NUM_BL_CURVE_SEGS 16 +#define IRAM_SIZE 256 + +#define IRAM_RESERVE_AREA_START_V2 0xF0 // reserve 0xF0~0xF6 are write by DMCU only +#define IRAM_RESERVE_AREA_END_V2 0xF6 // reserve 0xF0~0xF6 are write by DMCU only + +#define IRAM_RESERVE_AREA_START_V2_2 0xF0 // reserve 0xF0~0xFF are write by DMCU only +#define IRAM_RESERVE_AREA_END_V2_2 0xFF // reserve 0xF0~0xFF are write by DMCU only + +#pragma pack(push, 1) +/* NOTE: iRAM is 256B in size */ +struct iram_table_v_2 { + /* flags */ + uint16_t min_abm_backlight; /* 0x00 U16 */ + + /* parameters for ABM2.0 algorithm */ + uint8_t min_reduction[NUM_AMBI_LEVEL][NUM_AGGR_LEVEL]; /* 0x02 U0.8 */ + uint8_t max_reduction[NUM_AMBI_LEVEL][NUM_AGGR_LEVEL]; /* 0x16 U0.8 */ + uint8_t bright_pos_gain[NUM_AMBI_LEVEL][NUM_AGGR_LEVEL]; /* 0x2a U2.6 */ + uint8_t bright_neg_gain[NUM_AMBI_LEVEL][NUM_AGGR_LEVEL]; /* 0x3e U2.6 */ + uint8_t dark_pos_gain[NUM_AMBI_LEVEL][NUM_AGGR_LEVEL]; /* 0x52 U2.6 */ + uint8_t dark_neg_gain[NUM_AMBI_LEVEL][NUM_AGGR_LEVEL]; /* 0x66 U2.6 */ + uint8_t iir_curve[NUM_AMBI_LEVEL]; /* 0x7a U0.8 */ + uint8_t deviation_gain; /* 0x7f U0.8 */ + + /* parameters for crgb conversion */ + uint16_t crgb_thresh[NUM_POWER_FN_SEGS]; /* 0x80 U3.13 */ + uint16_t crgb_offset[NUM_POWER_FN_SEGS]; /* 0x90 U1.15 */ + uint16_t crgb_slope[NUM_POWER_FN_SEGS]; /* 0xa0 U4.12 */ + + /* parameters for custom curve */ + /* thresholds for brightness --> backlight */ + uint16_t backlight_thresholds[NUM_BL_CURVE_SEGS]; /* 0xb0 U16.0 */ + /* offsets for brightness --> backlight */ + uint16_t backlight_offsets[NUM_BL_CURVE_SEGS]; /* 0xd0 U16.0 */ + + /* For reading PSR State directly from IRAM */ + uint8_t psr_state; /* 0xf0 */ + uint8_t dmcu_mcp_interface_version; /* 0xf1 */ + uint8_t dmcu_abm_feature_version; /* 0xf2 */ + uint8_t dmcu_psr_feature_version; /* 0xf3 */ + uint16_t dmcu_version; /* 0xf4 */ + uint8_t dmcu_state; /* 0xf6 */ + + uint16_t blRampReduction; /* 0xf7 */ + uint16_t blRampStart; /* 0xf9 */ + uint8_t dummy5; /* 0xfb */ + uint8_t dummy6; /* 0xfc */ + uint8_t dummy7; /* 0xfd */ + uint8_t dummy8; /* 0xfe */ + uint8_t dummy9; /* 0xff */ +}; + +struct iram_table_v_2_2 { + /* flags */ + uint16_t flags; /* 0x00 U16 */ + + /* parameters for ABM2.2 algorithm */ + uint8_t min_reduction[NUM_AMBI_LEVEL][NUM_AGGR_LEVEL]; /* 0x02 U0.8 */ + uint8_t max_reduction[NUM_AMBI_LEVEL][NUM_AGGR_LEVEL]; /* 0x16 U0.8 */ + uint8_t bright_pos_gain[NUM_AMBI_LEVEL][NUM_AGGR_LEVEL]; /* 0x2a U2.6 */ + uint8_t dark_pos_gain[NUM_AMBI_LEVEL][NUM_AGGR_LEVEL]; /* 0x3e U2.6 */ + uint8_t hybrid_factor[NUM_AGGR_LEVEL]; /* 0x52 U0.8 */ + uint8_t contrast_factor[NUM_AGGR_LEVEL]; /* 0x56 U0.8 */ + uint8_t deviation_gain[NUM_AGGR_LEVEL]; /* 0x5a U0.8 */ + uint8_t iir_curve[NUM_AMBI_LEVEL]; /* 0x5e U0.8 */ + uint8_t min_knee[NUM_AGGR_LEVEL]; /* 0x63 U0.8 */ + uint8_t max_knee[NUM_AGGR_LEVEL]; /* 0x67 U0.8 */ + uint16_t min_abm_backlight; /* 0x6b U16 */ + uint8_t pad[19]; /* 0x6d U0.8 */ + + /* parameters for crgb conversion */ + uint16_t crgb_thresh[NUM_POWER_FN_SEGS]; /* 0x80 U3.13 */ + uint16_t crgb_offset[NUM_POWER_FN_SEGS]; /* 0x90 U1.15 */ + uint16_t crgb_slope[NUM_POWER_FN_SEGS]; /* 0xa0 U4.12 */ + + /* parameters for custom curve */ + /* thresholds for brightness --> backlight */ + uint16_t backlight_thresholds[NUM_BL_CURVE_SEGS]; /* 0xb0 U16.0 */ + /* offsets for brightness --> backlight */ + uint16_t backlight_offsets[NUM_BL_CURVE_SEGS]; /* 0xd0 U16.0 */ + + /* For reading PSR State directly from IRAM */ + uint8_t psr_state; /* 0xf0 */ + uint8_t dmcu_mcp_interface_version; /* 0xf1 */ + uint8_t dmcu_abm_feature_version; /* 0xf2 */ + uint8_t dmcu_psr_feature_version; /* 0xf3 */ + uint16_t dmcu_version; /* 0xf4 */ + uint8_t dmcu_state; /* 0xf6 */ + + uint8_t dummy1; /* 0xf7 */ + uint8_t dummy2; /* 0xf8 */ + uint8_t dummy3; /* 0xf9 */ + uint8_t dummy4; /* 0xfa */ + uint8_t dummy5; /* 0xfb */ + uint8_t dummy6; /* 0xfc */ + uint8_t dummy7; /* 0xfd */ + uint8_t dummy8; /* 0xfe */ + uint8_t dummy9; /* 0xff */ +}; +#pragma pack(pop) + +static void fill_backlight_transform_table(struct dmcu_iram_parameters params, + struct iram_table_v_2 *table) +{ + unsigned int i; + unsigned int num_entries = NUM_BL_CURVE_SEGS; + unsigned int lut_index; + + table->backlight_thresholds[0] = 0; + table->backlight_offsets[0] = params.backlight_lut_array[0]; + table->backlight_thresholds[num_entries-1] = 0xFFFF; + table->backlight_offsets[num_entries-1] = + params.backlight_lut_array[params.backlight_lut_array_size - 1]; + + /* Setup all brightness levels between 0% and 100% exclusive + * Fills brightness-to-backlight transform table. Backlight custom curve + * describes transform from brightness to backlight. It will be defined + * as set of thresholds and set of offsets, together, implying + * extrapolation of custom curve into 16 uniformly spanned linear + * segments. Each threshold/offset represented by 16 bit entry in + * format U4.10. + */ + for (i = 1; i+1 < num_entries; i++) { + lut_index = (params.backlight_lut_array_size - 1) * i / (num_entries - 1); + ASSERT(lut_index < params.backlight_lut_array_size); + + table->backlight_thresholds[i] = + cpu_to_be16(DIV_ROUNDUP((i * 65536), num_entries)); + table->backlight_offsets[i] = + cpu_to_be16(params.backlight_lut_array[lut_index]); + } +} + +static void fill_backlight_transform_table_v_2_2(struct dmcu_iram_parameters params, + struct iram_table_v_2_2 *table, bool big_endian) +{ + unsigned int i; + unsigned int num_entries = NUM_BL_CURVE_SEGS; + unsigned int lut_index; + + table->backlight_thresholds[0] = 0; + table->backlight_offsets[0] = params.backlight_lut_array[0]; + table->backlight_thresholds[num_entries-1] = 0xFFFF; + table->backlight_offsets[num_entries-1] = + params.backlight_lut_array[params.backlight_lut_array_size - 1]; + + /* Setup all brightness levels between 0% and 100% exclusive + * Fills brightness-to-backlight transform table. Backlight custom curve + * describes transform from brightness to backlight. It will be defined + * as set of thresholds and set of offsets, together, implying + * extrapolation of custom curve into 16 uniformly spanned linear + * segments. Each threshold/offset represented by 16 bit entry in + * format U4.10. + */ + for (i = 1; i+1 < num_entries; i++) { + lut_index = DIV_ROUNDUP((i * params.backlight_lut_array_size), num_entries); + ASSERT(lut_index < params.backlight_lut_array_size); + + table->backlight_thresholds[i] = (big_endian) ? + cpu_to_be16(DIV_ROUNDUP((i * 65536), num_entries)) : + cpu_to_le16(DIV_ROUNDUP((i * 65536), num_entries)); + table->backlight_offsets[i] = (big_endian) ? + cpu_to_be16(params.backlight_lut_array[lut_index]) : + cpu_to_le16(params.backlight_lut_array[lut_index]); + } +} + +static void fill_iram_v_2(struct iram_table_v_2 *ram_table, struct dmcu_iram_parameters params) +{ + unsigned int set = params.set; + + ram_table->min_abm_backlight = + cpu_to_be16(params.min_abm_backlight); + ram_table->deviation_gain = 0xb3; + + ram_table->blRampReduction = + cpu_to_be16(params.backlight_ramping_reduction); + ram_table->blRampStart = + cpu_to_be16(params.backlight_ramping_start); + + ram_table->min_reduction[0][0] = min_reduction_table[abm_config[set][0]]; + ram_table->min_reduction[1][0] = min_reduction_table[abm_config[set][0]]; + ram_table->min_reduction[2][0] = min_reduction_table[abm_config[set][0]]; + ram_table->min_reduction[3][0] = min_reduction_table[abm_config[set][0]]; + ram_table->min_reduction[4][0] = min_reduction_table[abm_config[set][0]]; + ram_table->max_reduction[0][0] = max_reduction_table[abm_config[set][0]]; + ram_table->max_reduction[1][0] = max_reduction_table[abm_config[set][0]]; + ram_table->max_reduction[2][0] = max_reduction_table[abm_config[set][0]]; + ram_table->max_reduction[3][0] = max_reduction_table[abm_config[set][0]]; + ram_table->max_reduction[4][0] = max_reduction_table[abm_config[set][0]]; + + ram_table->min_reduction[0][1] = min_reduction_table[abm_config[set][1]]; + ram_table->min_reduction[1][1] = min_reduction_table[abm_config[set][1]]; + ram_table->min_reduction[2][1] = min_reduction_table[abm_config[set][1]]; + ram_table->min_reduction[3][1] = min_reduction_table[abm_config[set][1]]; + ram_table->min_reduction[4][1] = min_reduction_table[abm_config[set][1]]; + ram_table->max_reduction[0][1] = max_reduction_table[abm_config[set][1]]; + ram_table->max_reduction[1][1] = max_reduction_table[abm_config[set][1]]; + ram_table->max_reduction[2][1] = max_reduction_table[abm_config[set][1]]; + ram_table->max_reduction[3][1] = max_reduction_table[abm_config[set][1]]; + ram_table->max_reduction[4][1] = max_reduction_table[abm_config[set][1]]; + + ram_table->min_reduction[0][2] = min_reduction_table[abm_config[set][2]]; + ram_table->min_reduction[1][2] = min_reduction_table[abm_config[set][2]]; + ram_table->min_reduction[2][2] = min_reduction_table[abm_config[set][2]]; + ram_table->min_reduction[3][2] = min_reduction_table[abm_config[set][2]]; + ram_table->min_reduction[4][2] = min_reduction_table[abm_config[set][2]]; + ram_table->max_reduction[0][2] = max_reduction_table[abm_config[set][2]]; + ram_table->max_reduction[1][2] = max_reduction_table[abm_config[set][2]]; + ram_table->max_reduction[2][2] = max_reduction_table[abm_config[set][2]]; + ram_table->max_reduction[3][2] = max_reduction_table[abm_config[set][2]]; + ram_table->max_reduction[4][2] = max_reduction_table[abm_config[set][2]]; + + ram_table->min_reduction[0][3] = min_reduction_table[abm_config[set][3]]; + ram_table->min_reduction[1][3] = min_reduction_table[abm_config[set][3]]; + ram_table->min_reduction[2][3] = min_reduction_table[abm_config[set][3]]; + ram_table->min_reduction[3][3] = min_reduction_table[abm_config[set][3]]; + ram_table->min_reduction[4][3] = min_reduction_table[abm_config[set][3]]; + ram_table->max_reduction[0][3] = max_reduction_table[abm_config[set][3]]; + ram_table->max_reduction[1][3] = max_reduction_table[abm_config[set][3]]; + ram_table->max_reduction[2][3] = max_reduction_table[abm_config[set][3]]; + ram_table->max_reduction[3][3] = max_reduction_table[abm_config[set][3]]; + ram_table->max_reduction[4][3] = max_reduction_table[abm_config[set][3]]; + + ram_table->bright_pos_gain[0][0] = 0x20; + ram_table->bright_pos_gain[0][1] = 0x20; + ram_table->bright_pos_gain[0][2] = 0x20; + ram_table->bright_pos_gain[0][3] = 0x20; + ram_table->bright_pos_gain[1][0] = 0x20; + ram_table->bright_pos_gain[1][1] = 0x20; + ram_table->bright_pos_gain[1][2] = 0x20; + ram_table->bright_pos_gain[1][3] = 0x20; + ram_table->bright_pos_gain[2][0] = 0x20; + ram_table->bright_pos_gain[2][1] = 0x20; + ram_table->bright_pos_gain[2][2] = 0x20; + ram_table->bright_pos_gain[2][3] = 0x20; + ram_table->bright_pos_gain[3][0] = 0x20; + ram_table->bright_pos_gain[3][1] = 0x20; + ram_table->bright_pos_gain[3][2] = 0x20; + ram_table->bright_pos_gain[3][3] = 0x20; + ram_table->bright_pos_gain[4][0] = 0x20; + ram_table->bright_pos_gain[4][1] = 0x20; + ram_table->bright_pos_gain[4][2] = 0x20; + ram_table->bright_pos_gain[4][3] = 0x20; + ram_table->bright_neg_gain[0][0] = 0x00; + ram_table->bright_neg_gain[0][1] = 0x00; + ram_table->bright_neg_gain[0][2] = 0x00; + ram_table->bright_neg_gain[0][3] = 0x00; + ram_table->bright_neg_gain[1][0] = 0x00; + ram_table->bright_neg_gain[1][1] = 0x00; + ram_table->bright_neg_gain[1][2] = 0x00; + ram_table->bright_neg_gain[1][3] = 0x00; + ram_table->bright_neg_gain[2][0] = 0x00; + ram_table->bright_neg_gain[2][1] = 0x00; + ram_table->bright_neg_gain[2][2] = 0x00; + ram_table->bright_neg_gain[2][3] = 0x00; + ram_table->bright_neg_gain[3][0] = 0x00; + ram_table->bright_neg_gain[3][1] = 0x00; + ram_table->bright_neg_gain[3][2] = 0x00; + ram_table->bright_neg_gain[3][3] = 0x00; + ram_table->bright_neg_gain[4][0] = 0x00; + ram_table->bright_neg_gain[4][1] = 0x00; + ram_table->bright_neg_gain[4][2] = 0x00; + ram_table->bright_neg_gain[4][3] = 0x00; + ram_table->dark_pos_gain[0][0] = 0x00; + ram_table->dark_pos_gain[0][1] = 0x00; + ram_table->dark_pos_gain[0][2] = 0x00; + ram_table->dark_pos_gain[0][3] = 0x00; + ram_table->dark_pos_gain[1][0] = 0x00; + ram_table->dark_pos_gain[1][1] = 0x00; + ram_table->dark_pos_gain[1][2] = 0x00; + ram_table->dark_pos_gain[1][3] = 0x00; + ram_table->dark_pos_gain[2][0] = 0x00; + ram_table->dark_pos_gain[2][1] = 0x00; + ram_table->dark_pos_gain[2][2] = 0x00; + ram_table->dark_pos_gain[2][3] = 0x00; + ram_table->dark_pos_gain[3][0] = 0x00; + ram_table->dark_pos_gain[3][1] = 0x00; + ram_table->dark_pos_gain[3][2] = 0x00; + ram_table->dark_pos_gain[3][3] = 0x00; + ram_table->dark_pos_gain[4][0] = 0x00; + ram_table->dark_pos_gain[4][1] = 0x00; + ram_table->dark_pos_gain[4][2] = 0x00; + ram_table->dark_pos_gain[4][3] = 0x00; + ram_table->dark_neg_gain[0][0] = 0x00; + ram_table->dark_neg_gain[0][1] = 0x00; + ram_table->dark_neg_gain[0][2] = 0x00; + ram_table->dark_neg_gain[0][3] = 0x00; + ram_table->dark_neg_gain[1][0] = 0x00; + ram_table->dark_neg_gain[1][1] = 0x00; + ram_table->dark_neg_gain[1][2] = 0x00; + ram_table->dark_neg_gain[1][3] = 0x00; + ram_table->dark_neg_gain[2][0] = 0x00; + ram_table->dark_neg_gain[2][1] = 0x00; + ram_table->dark_neg_gain[2][2] = 0x00; + ram_table->dark_neg_gain[2][3] = 0x00; + ram_table->dark_neg_gain[3][0] = 0x00; + ram_table->dark_neg_gain[3][1] = 0x00; + ram_table->dark_neg_gain[3][2] = 0x00; + ram_table->dark_neg_gain[3][3] = 0x00; + ram_table->dark_neg_gain[4][0] = 0x00; + ram_table->dark_neg_gain[4][1] = 0x00; + ram_table->dark_neg_gain[4][2] = 0x00; + ram_table->dark_neg_gain[4][3] = 0x00; + + ram_table->iir_curve[0] = 0x65; + ram_table->iir_curve[1] = 0x65; + ram_table->iir_curve[2] = 0x65; + ram_table->iir_curve[3] = 0x65; + ram_table->iir_curve[4] = 0x65; + + //Gamma 2.4 + ram_table->crgb_thresh[0] = cpu_to_be16(0x13b6); + ram_table->crgb_thresh[1] = cpu_to_be16(0x1648); + ram_table->crgb_thresh[2] = cpu_to_be16(0x18e3); + ram_table->crgb_thresh[3] = cpu_to_be16(0x1b41); + ram_table->crgb_thresh[4] = cpu_to_be16(0x1d46); + ram_table->crgb_thresh[5] = cpu_to_be16(0x1f21); + ram_table->crgb_thresh[6] = cpu_to_be16(0x2167); + ram_table->crgb_thresh[7] = cpu_to_be16(0x2384); + ram_table->crgb_offset[0] = cpu_to_be16(0x2999); + ram_table->crgb_offset[1] = cpu_to_be16(0x3999); + ram_table->crgb_offset[2] = cpu_to_be16(0x4666); + ram_table->crgb_offset[3] = cpu_to_be16(0x5999); + ram_table->crgb_offset[4] = cpu_to_be16(0x6333); + ram_table->crgb_offset[5] = cpu_to_be16(0x7800); + ram_table->crgb_offset[6] = cpu_to_be16(0x8c00); + ram_table->crgb_offset[7] = cpu_to_be16(0xa000); + ram_table->crgb_slope[0] = cpu_to_be16(0x3147); + ram_table->crgb_slope[1] = cpu_to_be16(0x2978); + ram_table->crgb_slope[2] = cpu_to_be16(0x23a2); + ram_table->crgb_slope[3] = cpu_to_be16(0x1f55); + ram_table->crgb_slope[4] = cpu_to_be16(0x1c63); + ram_table->crgb_slope[5] = cpu_to_be16(0x1a0f); + ram_table->crgb_slope[6] = cpu_to_be16(0x178d); + ram_table->crgb_slope[7] = cpu_to_be16(0x15ab); + + fill_backlight_transform_table( + params, ram_table); +} + +static void fill_iram_v_2_2(struct iram_table_v_2_2 *ram_table, struct dmcu_iram_parameters params) +{ + unsigned int set = params.set; + + ram_table->flags = 0x0; + + ram_table->min_abm_backlight = + cpu_to_be16(params.min_abm_backlight); + + ram_table->deviation_gain[0] = 0xb3; + ram_table->deviation_gain[1] = 0xa8; + ram_table->deviation_gain[2] = 0x98; + ram_table->deviation_gain[3] = 0x68; + + ram_table->min_reduction[0][0] = min_reduction_table_v_2_2[abm_config[set][0]]; + ram_table->min_reduction[1][0] = min_reduction_table_v_2_2[abm_config[set][0]]; + ram_table->min_reduction[2][0] = min_reduction_table_v_2_2[abm_config[set][0]]; + ram_table->min_reduction[3][0] = min_reduction_table_v_2_2[abm_config[set][0]]; + ram_table->min_reduction[4][0] = min_reduction_table_v_2_2[abm_config[set][0]]; + ram_table->max_reduction[0][0] = max_reduction_table_v_2_2[abm_config[set][0]]; + ram_table->max_reduction[1][0] = max_reduction_table_v_2_2[abm_config[set][0]]; + ram_table->max_reduction[2][0] = max_reduction_table_v_2_2[abm_config[set][0]]; + ram_table->max_reduction[3][0] = max_reduction_table_v_2_2[abm_config[set][0]]; + ram_table->max_reduction[4][0] = max_reduction_table_v_2_2[abm_config[set][0]]; + + ram_table->min_reduction[0][1] = min_reduction_table_v_2_2[abm_config[set][1]]; + ram_table->min_reduction[1][1] = min_reduction_table_v_2_2[abm_config[set][1]]; + ram_table->min_reduction[2][1] = min_reduction_table_v_2_2[abm_config[set][1]]; + ram_table->min_reduction[3][1] = min_reduction_table_v_2_2[abm_config[set][1]]; + ram_table->min_reduction[4][1] = min_reduction_table_v_2_2[abm_config[set][1]]; + ram_table->max_reduction[0][1] = max_reduction_table_v_2_2[abm_config[set][1]]; + ram_table->max_reduction[1][1] = max_reduction_table_v_2_2[abm_config[set][1]]; + ram_table->max_reduction[2][1] = max_reduction_table_v_2_2[abm_config[set][1]]; + ram_table->max_reduction[3][1] = max_reduction_table_v_2_2[abm_config[set][1]]; + ram_table->max_reduction[4][1] = max_reduction_table_v_2_2[abm_config[set][1]]; + + ram_table->min_reduction[0][2] = min_reduction_table_v_2_2[abm_config[set][2]]; + ram_table->min_reduction[1][2] = min_reduction_table_v_2_2[abm_config[set][2]]; + ram_table->min_reduction[2][2] = min_reduction_table_v_2_2[abm_config[set][2]]; + ram_table->min_reduction[3][2] = min_reduction_table_v_2_2[abm_config[set][2]]; + ram_table->min_reduction[4][2] = min_reduction_table_v_2_2[abm_config[set][2]]; + ram_table->max_reduction[0][2] = max_reduction_table_v_2_2[abm_config[set][2]]; + ram_table->max_reduction[1][2] = max_reduction_table_v_2_2[abm_config[set][2]]; + ram_table->max_reduction[2][2] = max_reduction_table_v_2_2[abm_config[set][2]]; + ram_table->max_reduction[3][2] = max_reduction_table_v_2_2[abm_config[set][2]]; + ram_table->max_reduction[4][2] = max_reduction_table_v_2_2[abm_config[set][2]]; + + ram_table->min_reduction[0][3] = min_reduction_table_v_2_2[abm_config[set][3]]; + ram_table->min_reduction[1][3] = min_reduction_table_v_2_2[abm_config[set][3]]; + ram_table->min_reduction[2][3] = min_reduction_table_v_2_2[abm_config[set][3]]; + ram_table->min_reduction[3][3] = min_reduction_table_v_2_2[abm_config[set][3]]; + ram_table->min_reduction[4][3] = min_reduction_table_v_2_2[abm_config[set][3]]; + ram_table->max_reduction[0][3] = max_reduction_table_v_2_2[abm_config[set][3]]; + ram_table->max_reduction[1][3] = max_reduction_table_v_2_2[abm_config[set][3]]; + ram_table->max_reduction[2][3] = max_reduction_table_v_2_2[abm_config[set][3]]; + ram_table->max_reduction[3][3] = max_reduction_table_v_2_2[abm_config[set][3]]; + ram_table->max_reduction[4][3] = max_reduction_table_v_2_2[abm_config[set][3]]; + + ram_table->bright_pos_gain[0][0] = 0x20; + ram_table->bright_pos_gain[0][1] = 0x20; + ram_table->bright_pos_gain[0][2] = 0x20; + ram_table->bright_pos_gain[0][3] = 0x20; + ram_table->bright_pos_gain[1][0] = 0x20; + ram_table->bright_pos_gain[1][1] = 0x20; + ram_table->bright_pos_gain[1][2] = 0x20; + ram_table->bright_pos_gain[1][3] = 0x20; + ram_table->bright_pos_gain[2][0] = 0x20; + ram_table->bright_pos_gain[2][1] = 0x20; + ram_table->bright_pos_gain[2][2] = 0x20; + ram_table->bright_pos_gain[2][3] = 0x20; + ram_table->bright_pos_gain[3][0] = 0x20; + ram_table->bright_pos_gain[3][1] = 0x20; + ram_table->bright_pos_gain[3][2] = 0x20; + ram_table->bright_pos_gain[3][3] = 0x20; + ram_table->bright_pos_gain[4][0] = 0x20; + ram_table->bright_pos_gain[4][1] = 0x20; + ram_table->bright_pos_gain[4][2] = 0x20; + ram_table->bright_pos_gain[4][3] = 0x20; + + ram_table->dark_pos_gain[0][0] = 0x00; + ram_table->dark_pos_gain[0][1] = 0x00; + ram_table->dark_pos_gain[0][2] = 0x00; + ram_table->dark_pos_gain[0][3] = 0x00; + ram_table->dark_pos_gain[1][0] = 0x00; + ram_table->dark_pos_gain[1][1] = 0x00; + ram_table->dark_pos_gain[1][2] = 0x00; + ram_table->dark_pos_gain[1][3] = 0x00; + ram_table->dark_pos_gain[2][0] = 0x00; + ram_table->dark_pos_gain[2][1] = 0x00; + ram_table->dark_pos_gain[2][2] = 0x00; + ram_table->dark_pos_gain[2][3] = 0x00; + ram_table->dark_pos_gain[3][0] = 0x00; + ram_table->dark_pos_gain[3][1] = 0x00; + ram_table->dark_pos_gain[3][2] = 0x00; + ram_table->dark_pos_gain[3][3] = 0x00; + ram_table->dark_pos_gain[4][0] = 0x00; + ram_table->dark_pos_gain[4][1] = 0x00; + ram_table->dark_pos_gain[4][2] = 0x00; + ram_table->dark_pos_gain[4][3] = 0x00; + + ram_table->hybrid_factor[0] = 0xff; + ram_table->hybrid_factor[1] = 0xff; + ram_table->hybrid_factor[2] = 0xff; + ram_table->hybrid_factor[3] = 0xc0; + + ram_table->contrast_factor[0] = 0x99; + ram_table->contrast_factor[1] = 0x99; + ram_table->contrast_factor[2] = 0x90; + ram_table->contrast_factor[3] = 0x80; + + ram_table->iir_curve[0] = 0x65; + ram_table->iir_curve[1] = 0x65; + ram_table->iir_curve[2] = 0x65; + ram_table->iir_curve[3] = 0x65; + ram_table->iir_curve[4] = 0x65; + + //Gamma 2.2 + ram_table->crgb_thresh[0] = cpu_to_be16(0x127c); + ram_table->crgb_thresh[1] = cpu_to_be16(0x151b); + ram_table->crgb_thresh[2] = cpu_to_be16(0x17d5); + ram_table->crgb_thresh[3] = cpu_to_be16(0x1a56); + ram_table->crgb_thresh[4] = cpu_to_be16(0x1c83); + ram_table->crgb_thresh[5] = cpu_to_be16(0x1e72); + ram_table->crgb_thresh[6] = cpu_to_be16(0x20f0); + ram_table->crgb_thresh[7] = cpu_to_be16(0x232b); + ram_table->crgb_offset[0] = cpu_to_be16(0x2999); + ram_table->crgb_offset[1] = cpu_to_be16(0x3999); + ram_table->crgb_offset[2] = cpu_to_be16(0x4666); + ram_table->crgb_offset[3] = cpu_to_be16(0x5999); + ram_table->crgb_offset[4] = cpu_to_be16(0x6333); + ram_table->crgb_offset[5] = cpu_to_be16(0x7800); + ram_table->crgb_offset[6] = cpu_to_be16(0x8c00); + ram_table->crgb_offset[7] = cpu_to_be16(0xa000); + ram_table->crgb_slope[0] = cpu_to_be16(0x3609); + ram_table->crgb_slope[1] = cpu_to_be16(0x2dfa); + ram_table->crgb_slope[2] = cpu_to_be16(0x27ea); + ram_table->crgb_slope[3] = cpu_to_be16(0x235d); + ram_table->crgb_slope[4] = cpu_to_be16(0x2042); + ram_table->crgb_slope[5] = cpu_to_be16(0x1dc3); + ram_table->crgb_slope[6] = cpu_to_be16(0x1b1a); + ram_table->crgb_slope[7] = cpu_to_be16(0x1910); + + fill_backlight_transform_table_v_2_2( + params, ram_table, true); +} + +static void fill_iram_v_2_3(struct iram_table_v_2_2 *ram_table, struct dmcu_iram_parameters params, bool big_endian) +{ + unsigned int i, j; + unsigned int set = params.set; + + ram_table->flags = 0x0; + ram_table->min_abm_backlight = (big_endian) ? + cpu_to_be16(params.min_abm_backlight) : + cpu_to_le16(params.min_abm_backlight); + + for (i = 0; i < NUM_AGGR_LEVEL; i++) { + ram_table->hybrid_factor[i] = abm_settings[set][i].brightness_gain; + ram_table->contrast_factor[i] = abm_settings[set][i].contrast_factor; + ram_table->deviation_gain[i] = abm_settings[set][i].deviation_gain; + ram_table->min_knee[i] = abm_settings[set][i].min_knee; + ram_table->max_knee[i] = abm_settings[set][i].max_knee; + + for (j = 0; j < NUM_AMBI_LEVEL; j++) { + ram_table->min_reduction[j][i] = abm_settings[set][i].min_reduction; + ram_table->max_reduction[j][i] = abm_settings[set][i].max_reduction; + ram_table->bright_pos_gain[j][i] = abm_settings[set][i].bright_pos_gain; + ram_table->dark_pos_gain[j][i] = abm_settings[set][i].dark_pos_gain; + } + } + + ram_table->iir_curve[0] = 0x65; + ram_table->iir_curve[1] = 0x65; + ram_table->iir_curve[2] = 0x65; + ram_table->iir_curve[3] = 0x65; + ram_table->iir_curve[4] = 0x65; + + //Gamma 2.2 + ram_table->crgb_thresh[0] = bswap16_based_on_endian(big_endian, 0x127c); + ram_table->crgb_thresh[1] = bswap16_based_on_endian(big_endian, 0x151b); + ram_table->crgb_thresh[2] = bswap16_based_on_endian(big_endian, 0x17d5); + ram_table->crgb_thresh[3] = bswap16_based_on_endian(big_endian, 0x1a56); + ram_table->crgb_thresh[4] = bswap16_based_on_endian(big_endian, 0x1c83); + ram_table->crgb_thresh[5] = bswap16_based_on_endian(big_endian, 0x1e72); + ram_table->crgb_thresh[6] = bswap16_based_on_endian(big_endian, 0x20f0); + ram_table->crgb_thresh[7] = bswap16_based_on_endian(big_endian, 0x232b); + ram_table->crgb_offset[0] = bswap16_based_on_endian(big_endian, 0x2999); + ram_table->crgb_offset[1] = bswap16_based_on_endian(big_endian, 0x3999); + ram_table->crgb_offset[2] = bswap16_based_on_endian(big_endian, 0x4666); + ram_table->crgb_offset[3] = bswap16_based_on_endian(big_endian, 0x5999); + ram_table->crgb_offset[4] = bswap16_based_on_endian(big_endian, 0x6333); + ram_table->crgb_offset[5] = bswap16_based_on_endian(big_endian, 0x7800); + ram_table->crgb_offset[6] = bswap16_based_on_endian(big_endian, 0x8c00); + ram_table->crgb_offset[7] = bswap16_based_on_endian(big_endian, 0xa000); + ram_table->crgb_slope[0] = bswap16_based_on_endian(big_endian, 0x3609); + ram_table->crgb_slope[1] = bswap16_based_on_endian(big_endian, 0x2dfa); + ram_table->crgb_slope[2] = bswap16_based_on_endian(big_endian, 0x27ea); + ram_table->crgb_slope[3] = bswap16_based_on_endian(big_endian, 0x235d); + ram_table->crgb_slope[4] = bswap16_based_on_endian(big_endian, 0x2042); + ram_table->crgb_slope[5] = bswap16_based_on_endian(big_endian, 0x1dc3); + ram_table->crgb_slope[6] = bswap16_based_on_endian(big_endian, 0x1b1a); + ram_table->crgb_slope[7] = bswap16_based_on_endian(big_endian, 0x1910); + + fill_backlight_transform_table_v_2_2( + params, ram_table, big_endian); +} + +bool dmub_init_abm_config(struct resource_pool *res_pool, + struct dmcu_iram_parameters params, + unsigned int inst) +{ + struct iram_table_v_2_2 ram_table; + struct abm_config_table config; + unsigned int set = params.set; + bool result = false; + uint32_t i, j = 0; + +#if defined(CONFIG_DRM_AMD_DC_DCN) + if (res_pool->abm == NULL && res_pool->multiple_abms[inst] == NULL) + return false; +#else + if (res_pool->abm == NULL) + return false; +#endif + + memset(&ram_table, 0, sizeof(ram_table)); + memset(&config, 0, sizeof(config)); + + fill_iram_v_2_3(&ram_table, params, false); + + // We must copy to structure that is aligned to 32-bit + for (i = 0; i < NUM_POWER_FN_SEGS; i++) { + config.crgb_thresh[i] = ram_table.crgb_thresh[i]; + config.crgb_offset[i] = ram_table.crgb_offset[i]; + config.crgb_slope[i] = ram_table.crgb_slope[i]; + } + + for (i = 0; i < NUM_BL_CURVE_SEGS; i++) { + config.backlight_thresholds[i] = ram_table.backlight_thresholds[i]; + config.backlight_offsets[i] = ram_table.backlight_offsets[i]; + } + + for (i = 0; i < NUM_AMBI_LEVEL; i++) + config.iir_curve[i] = ram_table.iir_curve[i]; + + for (i = 0; i < NUM_AMBI_LEVEL; i++) { + for (j = 0; j < NUM_AGGR_LEVEL; j++) { + config.min_reduction[i][j] = ram_table.min_reduction[i][j]; + config.max_reduction[i][j] = ram_table.max_reduction[i][j]; + config.bright_pos_gain[i][j] = ram_table.bright_pos_gain[i][j]; + config.dark_pos_gain[i][j] = ram_table.dark_pos_gain[i][j]; + } + } + + for (i = 0; i < NUM_AGGR_LEVEL; i++) { + config.hybrid_factor[i] = ram_table.hybrid_factor[i]; + config.contrast_factor[i] = ram_table.contrast_factor[i]; + config.deviation_gain[i] = ram_table.deviation_gain[i]; + config.min_knee[i] = ram_table.min_knee[i]; + config.max_knee[i] = ram_table.max_knee[i]; + } + + if (params.backlight_ramping_override) { + for (i = 0; i < NUM_AGGR_LEVEL; i++) { + config.blRampReduction[i] = params.backlight_ramping_reduction; + config.blRampStart[i] = params.backlight_ramping_start; + } + } else { + for (i = 0; i < NUM_AGGR_LEVEL; i++) { + config.blRampReduction[i] = abm_settings[set][i].blRampReduction; + config.blRampStart[i] = abm_settings[set][i].blRampStart; + } + } + + config.min_abm_backlight = ram_table.min_abm_backlight; + +#if defined(CONFIG_DRM_AMD_DC_DCN) + if (res_pool->multiple_abms[inst]) { + result = res_pool->multiple_abms[inst]->funcs->init_abm_config( + res_pool->multiple_abms[inst], (char *)(&config), sizeof(struct abm_config_table), inst); + } else +#endif + result = res_pool->abm->funcs->init_abm_config( + res_pool->abm, (char *)(&config), sizeof(struct abm_config_table), 0); + + return result; +} + +bool dmcu_load_iram(struct dmcu *dmcu, + struct dmcu_iram_parameters params) +{ + unsigned char ram_table[IRAM_SIZE]; + bool result = false; + + if (dmcu == NULL) + return false; + + if (dmcu && !dmcu->funcs->is_dmcu_initialized(dmcu)) + return true; + + memset(&ram_table, 0, sizeof(ram_table)); + + if (dmcu->dmcu_version.abm_version == 0x24) { + fill_iram_v_2_3((struct iram_table_v_2_2 *)ram_table, params, true); + result = dmcu->funcs->load_iram( + dmcu, 0, (char *)(&ram_table), IRAM_RESERVE_AREA_START_V2_2); + } else if (dmcu->dmcu_version.abm_version == 0x23) { + fill_iram_v_2_3((struct iram_table_v_2_2 *)ram_table, params, true); + + result = dmcu->funcs->load_iram( + dmcu, 0, (char *)(&ram_table), IRAM_RESERVE_AREA_START_V2_2); + } else if (dmcu->dmcu_version.abm_version == 0x22) { + fill_iram_v_2_2((struct iram_table_v_2_2 *)ram_table, params); + + result = dmcu->funcs->load_iram( + dmcu, 0, (char *)(&ram_table), IRAM_RESERVE_AREA_START_V2_2); + } else { + fill_iram_v_2((struct iram_table_v_2 *)ram_table, params); + + result = dmcu->funcs->load_iram( + dmcu, 0, (char *)(&ram_table), IRAM_RESERVE_AREA_START_V2); + + if (result) + result = dmcu->funcs->load_iram( + dmcu, IRAM_RESERVE_AREA_END_V2 + 1, + (char *)(&ram_table) + IRAM_RESERVE_AREA_END_V2 + 1, + sizeof(ram_table) - IRAM_RESERVE_AREA_END_V2 - 1); + } + + return result; +} + +/* + * is_psr_su_specific_panel() - check if sink is AMD vendor-specific PSR-SU + * supported eDP device. + * + * @link: dc link pointer + * + * Return: true if AMDGPU vendor specific PSR-SU eDP panel + */ +bool is_psr_su_specific_panel(struct dc_link *link) +{ + bool isPSRSUSupported = false; + struct dpcd_caps *dpcd_caps = &link->dpcd_caps; + + if (dpcd_caps->edp_rev >= DP_EDP_14) { + if (dpcd_caps->psr_info.psr_version >= DP_PSR2_WITH_Y_COORD_ET_SUPPORTED) + isPSRSUSupported = true; + /* + * Some panels will report PSR capabilities over additional DPCD bits. + * Such panels are approved despite reporting only PSR v3, as long as + * the additional bits are reported. + */ + if (dpcd_caps->sink_dev_id == DP_BRANCH_DEVICE_ID_001CF8) { + /* + * This is the temporary workaround to disable PSRSU when system turned on + * DSC function on the sepcific sink. + */ + if (dpcd_caps->psr_info.psr_version < DP_PSR2_WITH_Y_COORD_IS_SUPPORTED) + isPSRSUSupported = false; + else if (dpcd_caps->dsc_caps.dsc_basic_caps.fields.dsc_support.DSC_SUPPORT && + ((dpcd_caps->sink_dev_id_str[1] == 0x08 && dpcd_caps->sink_dev_id_str[0] == 0x08) || + (dpcd_caps->sink_dev_id_str[1] == 0x08 && dpcd_caps->sink_dev_id_str[0] == 0x07))) + isPSRSUSupported = false; + else if (dpcd_caps->sink_dev_id_str[1] == 0x08 && dpcd_caps->sink_dev_id_str[0] == 0x03) + isPSRSUSupported = false; + else if (dpcd_caps->sink_dev_id_str[1] == 0x08 && dpcd_caps->sink_dev_id_str[0] == 0x03) + isPSRSUSupported = false; + else if (dpcd_caps->psr_info.force_psrsu_cap == 0x1) + isPSRSUSupported = true; + } + } + + return isPSRSUSupported; +} + +/** + * mod_power_calc_psr_configs() - calculate/update generic psr configuration fields. + * @psr_config: [output], psr configuration structure to be updated + * @link: [input] dc link pointer + * @stream: [input] dc stream state pointer + * + * calculate and update the psr configuration fields that are not DM specific, i.e. such + * fields which are based on DPCD caps or timing information. To setup PSR in DMUB FW, + * this helper is assumed to be called before the call of the DC helper dc_link_setup_psr(). + * + * PSR config fields to be updated within the helper: + * - psr_rfb_setup_time + * - psr_sdp_transmit_line_num_deadline + * - line_time_in_us + * - su_y_granularity + * - su_granularity_required + * - psr_frame_capture_indication_req + * - psr_exit_link_training_required + * + * PSR config fields that are DM specific and NOT updated within the helper: + * - allow_smu_optimizations + * - allow_multi_disp_optimizations + */ +void mod_power_calc_psr_configs(struct psr_config *psr_config, + struct dc_link *link, + const struct dc_stream_state *stream) +{ + unsigned int num_vblank_lines = 0; + unsigned int vblank_time_in_us = 0; + unsigned int sdp_tx_deadline_in_us = 0; + unsigned int line_time_in_us = 0; + struct dpcd_caps *dpcd_caps = &link->dpcd_caps; + const int psr_setup_time_step_in_us = 55; /* refer to eDP spec DPCD 0x071h */ + + /* timing parameters */ + num_vblank_lines = stream->timing.v_total - + stream->timing.v_addressable - + stream->timing.v_border_top - + stream->timing.v_border_bottom; + + vblank_time_in_us = (stream->timing.h_total * num_vblank_lines * 1000) / (stream->timing.pix_clk_100hz / 10); + + line_time_in_us = ((stream->timing.h_total * 1000) / (stream->timing.pix_clk_100hz / 10)) + 1; + + /** + * psr configuration fields + * + * as per eDP 1.5 pg. 377 of 459, DPCD 0x071h bits [3:1], psr setup time bits interpreted as below + * 000b <--> 330 us (default) + * 001b <--> 275 us + * 010b <--> 220 us + * 011b <--> 165 us + * 100b <--> 110 us + * 101b <--> 055 us + * 110b <--> 000 us + */ + psr_config->psr_rfb_setup_time = + (6 - dpcd_caps->psr_info.psr_dpcd_caps.bits.PSR_SETUP_TIME) * psr_setup_time_step_in_us; + + if (psr_config->psr_rfb_setup_time > vblank_time_in_us) { + link->psr_settings.psr_frame_capture_indication_req = true; + link->psr_settings.psr_sdp_transmit_line_num_deadline = num_vblank_lines; + } else { + sdp_tx_deadline_in_us = vblank_time_in_us - psr_config->psr_rfb_setup_time; + + /* Set the last possible line SDP may be transmitted without violating the RFB setup time */ + link->psr_settings.psr_frame_capture_indication_req = false; + link->psr_settings.psr_sdp_transmit_line_num_deadline = sdp_tx_deadline_in_us / line_time_in_us; + } + + psr_config->psr_sdp_transmit_line_num_deadline = link->psr_settings.psr_sdp_transmit_line_num_deadline; + psr_config->line_time_in_us = line_time_in_us; + psr_config->su_y_granularity = dpcd_caps->psr_info.psr2_su_y_granularity_cap; + psr_config->su_granularity_required = dpcd_caps->psr_info.psr_dpcd_caps.bits.SU_GRANULARITY_REQUIRED; + psr_config->psr_frame_capture_indication_req = link->psr_settings.psr_frame_capture_indication_req; + psr_config->psr_exit_link_training_required = + !link->dpcd_caps.psr_info.psr_dpcd_caps.bits.LINK_TRAINING_ON_EXIT_NOT_REQUIRED; +} + +bool mod_power_only_edp(const struct dc_state *context, const struct dc_stream_state *stream) +{ + return context && context->stream_count == 1 && dc_is_embedded_signal(stream->signal); +} + +bool psr_su_set_y_granularity(struct dc *dc, struct dc_link *link, + struct dc_stream_state *stream, + struct psr_config *config) +{ + uint16_t pic_height; + uint8_t slice_height; + + if ((link->connector_signal & SIGNAL_TYPE_EDP) && + (!dc->caps.edp_dsc_support || + link->panel_config.dsc.disable_dsc_edp || + !link->dpcd_caps.dsc_caps.dsc_basic_caps.fields.dsc_support.DSC_SUPPORT || + !stream->timing.dsc_cfg.num_slices_v)) + return true; + + pic_height = stream->timing.v_addressable + + stream->timing.v_border_top + stream->timing.v_border_bottom; + + if (stream->timing.dsc_cfg.num_slices_v == 0) + return false; + + slice_height = pic_height / stream->timing.dsc_cfg.num_slices_v; + + if (slice_height) { + if (config->su_y_granularity && + (slice_height % config->su_y_granularity)) { + ASSERT(0); + return false; + } + + config->su_y_granularity = slice_height; + } + + return true; +} diff --git a/drivers/gpu/drm/amd/display/modules/power/power_helpers.h b/drivers/gpu/drm/amd/display/modules/power/power_helpers.h new file mode 100644 index 000000000..bb16b37b8 --- /dev/null +++ b/drivers/gpu/drm/amd/display/modules/power/power_helpers.h @@ -0,0 +1,65 @@ +/* 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. + * + * Authors: AMD + * + */ + +#ifndef MODULES_POWER_POWER_HELPERS_H_ +#define MODULES_POWER_POWER_HELPERS_H_ + +#include "dc/inc/hw/dmcu.h" +#include "dc/inc/hw/abm.h" +#include "dc/inc/core_types.h" + +struct resource_pool; + + +enum abm_defines { + abm_defines_max_level = 4, + abm_defines_max_config = 4, +}; + +struct dmcu_iram_parameters { + unsigned int *backlight_lut_array; + unsigned int backlight_lut_array_size; + bool backlight_ramping_override; + unsigned int backlight_ramping_reduction; + unsigned int backlight_ramping_start; + unsigned int min_abm_backlight; + unsigned int set; +}; + +bool dmcu_load_iram(struct dmcu *dmcu, + struct dmcu_iram_parameters params); +bool dmub_init_abm_config(struct resource_pool *res_pool, + struct dmcu_iram_parameters params, + unsigned int inst); + +bool is_psr_su_specific_panel(struct dc_link *link); +void mod_power_calc_psr_configs(struct psr_config *psr_config, + struct dc_link *link, + const struct dc_stream_state *stream); +bool mod_power_only_edp(const struct dc_state *context, + const struct dc_stream_state *stream); +bool psr_su_set_y_granularity(struct dc *dc, struct dc_link *link, + struct dc_stream_state *stream, + struct psr_config *config); +#endif /* MODULES_POWER_POWER_HELPERS_H_ */ diff --git a/drivers/gpu/drm/amd/display/modules/vmid/vmid.c b/drivers/gpu/drm/amd/display/modules/vmid/vmid.c new file mode 100644 index 000000000..2c40212d8 --- /dev/null +++ b/drivers/gpu/drm/amd/display/modules/vmid/vmid.c @@ -0,0 +1,174 @@ +/* + * Copyright 2019 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_vmid.h" + +struct core_vmid { + struct mod_vmid public; + struct dc *dc; + + unsigned int num_vmid; + unsigned int num_vmids_available; + uint64_t ptb_assigned_to_vmid[MAX_VMID]; + struct dc_virtual_addr_space_config base_config; +}; + +#define MOD_VMID_TO_CORE(mod_vmid)\ + container_of(mod_vmid, struct core_vmid, public) + +static void add_ptb_to_table(struct core_vmid *core_vmid, unsigned int vmid, uint64_t ptb) +{ + if (vmid < MAX_VMID) { + core_vmid->ptb_assigned_to_vmid[vmid] = ptb; + core_vmid->num_vmids_available--; + } +} + +static void clear_entry_from_vmid_table(struct core_vmid *core_vmid, unsigned int vmid) +{ + if (vmid < MAX_VMID) { + core_vmid->ptb_assigned_to_vmid[vmid] = 0; + core_vmid->num_vmids_available++; + } +} + +static void evict_vmids(struct core_vmid *core_vmid) +{ + int i; + uint16_t ord = dc_get_vmid_use_vector(core_vmid->dc); + + // At this point any positions with value 0 are unused vmids, evict them + for (i = 1; i < core_vmid->num_vmid; i++) { + if (!(ord & (1u << i))) + clear_entry_from_vmid_table(core_vmid, i); + } +} + +// Return value of -1 indicates vmid table uninitialized or ptb dne in the table +static int get_existing_vmid_for_ptb(struct core_vmid *core_vmid, uint64_t ptb) +{ + int i; + + for (i = 0; i < core_vmid->num_vmid; i++) { + if (core_vmid->ptb_assigned_to_vmid[i] == ptb) + return i; + } + + return -1; +} + +// Expected to be called only when there's an available vmid +static int get_next_available_vmid(struct core_vmid *core_vmid) +{ + int i; + + for (i = 1; i < core_vmid->num_vmid; i++) { + if (core_vmid->ptb_assigned_to_vmid[i] == 0) + return i; + } + + return -1; +} + +uint8_t mod_vmid_get_for_ptb(struct mod_vmid *mod_vmid, uint64_t ptb) +{ + struct core_vmid *core_vmid = MOD_VMID_TO_CORE(mod_vmid); + int vmid = 0; + + // Physical address gets vmid 0 + if (ptb == 0) + return 0; + + vmid = get_existing_vmid_for_ptb(core_vmid, ptb); + + if (vmid == -1) { + struct dc_virtual_addr_space_config va_config = core_vmid->base_config; + + va_config.page_table_base_addr = ptb; + + if (core_vmid->num_vmids_available == 0) + evict_vmids(core_vmid); + + vmid = get_next_available_vmid(core_vmid); + if (vmid != -1) { + add_ptb_to_table(core_vmid, vmid, ptb); + + dc_setup_vm_context(core_vmid->dc, &va_config, vmid); + } else + ASSERT(0); + } + + return vmid; +} + +void mod_vmid_reset(struct mod_vmid *mod_vmid) +{ + struct core_vmid *core_vmid = MOD_VMID_TO_CORE(mod_vmid); + + core_vmid->num_vmids_available = core_vmid->num_vmid - 1; + memset(core_vmid->ptb_assigned_to_vmid, 0, sizeof(core_vmid->ptb_assigned_to_vmid[0]) * MAX_VMID); +} + +struct mod_vmid *mod_vmid_create( + struct dc *dc, + unsigned int num_vmid, + struct dc_virtual_addr_space_config *va_config) +{ + struct core_vmid *core_vmid; + + if (num_vmid <= 1) + goto fail_no_vm_ctx; + + if (dc == NULL) + goto fail_dc_null; + + core_vmid = kzalloc(sizeof(struct core_vmid), GFP_KERNEL); + + if (core_vmid == NULL) + goto fail_alloc_context; + + core_vmid->dc = dc; + core_vmid->num_vmid = num_vmid; + core_vmid->num_vmids_available = num_vmid - 1; + core_vmid->base_config = *va_config; + + memset(core_vmid->ptb_assigned_to_vmid, 0, sizeof(core_vmid->ptb_assigned_to_vmid[0]) * MAX_VMID); + + return &core_vmid->public; + +fail_no_vm_ctx: +fail_alloc_context: +fail_dc_null: + return NULL; +} + +void mod_vmid_destroy(struct mod_vmid *mod_vmid) +{ + if (mod_vmid != NULL) { + struct core_vmid *core_vmid = MOD_VMID_TO_CORE(mod_vmid); + + kfree(core_vmid); + } +} -- cgit v1.2.3