drm/amd/display: Add user_regamma to color module
authorKrunoslav Kovac <Krunoslav.Kovac@amd.com>
Fri, 13 Apr 2018 20:06:24 +0000 (16:06 -0400)
committerAlex Deucher <alexander.deucher@amd.com>
Tue, 15 May 2018 18:44:10 +0000 (13:44 -0500)
Signed-off-by: Krunoslav Kovac <Krunoslav.Kovac@amd.com>
Reviewed-by: Anthony Koo <Anthony.Koo@amd.com>
Acked-by: Harry Wentland <harry.wentland@amd.com>
Signed-off-by: Alex Deucher <alexander.deucher@amd.com>
drivers/gpu/drm/amd/display/modules/color/color_gamma.c
drivers/gpu/drm/amd/display/modules/color/color_gamma.h

index e7e374f5686449d5ef6f596c8a4aa92a9e40ec2a..ad0ff50305ce7cd7d09606ede869dc35236f5760 100644 (file)
@@ -185,14 +185,14 @@ struct dividers {
 
 static void build_coefficients(struct gamma_coefficients *coefficients, bool is_2_4)
 {
-               static const int32_t numerator01[] = { 31308, 180000};
-               static const int32_t numerator02[] = { 12920, 4500};
-               static const int32_t numerator03[] = { 55, 99};
-               static const int32_t numerator04[] = { 55, 99};
-               static const int32_t numerator05[] = { 2400, 2200};
+       static const int32_t numerator01[] = { 31308, 180000};
+       static const int32_t numerator02[] = { 12920, 4500};
+       static const int32_t numerator03[] = { 55, 99};
+       static const int32_t numerator04[] = { 55, 99};
+       static const int32_t numerator05[] = { 2400, 2200};
 
-               uint32_t i = 0;
-               uint32_t index = is_2_4 == true ? 0:1;
+       uint32_t i = 0;
+       uint32_t index = is_2_4 == true ? 0:1;
 
        do {
                coefficients->a0[i] = dal_fixed31_32_from_fraction(
@@ -691,7 +691,7 @@ static void build_degamma(struct pwl_float_data_ex *curve,
        }
 }
 
-static bool scale_gamma(struct pwl_float_data *pwl_rgb,
+static void scale_gamma(struct pwl_float_data *pwl_rgb,
                const struct dc_gamma *ramp,
                struct dividers dividers)
 {
@@ -752,11 +752,9 @@ static bool scale_gamma(struct pwl_float_data *pwl_rgb,
                        dividers.divider3);
        rgb->b = dal_fixed31_32_mul(rgb_last->b,
                        dividers.divider3);
-
-       return true;
 }
 
-static bool scale_gamma_dx(struct pwl_float_data *pwl_rgb,
+static void scale_gamma_dx(struct pwl_float_data *pwl_rgb,
                const struct dc_gamma *ramp,
                struct dividers dividers)
 {
@@ -818,8 +816,71 @@ static bool scale_gamma_dx(struct pwl_float_data *pwl_rgb,
                                pwl_rgb[i-1].g, 2), pwl_rgb[i-2].g);
        pwl_rgb[i].b =  dal_fixed31_32_sub(dal_fixed31_32_mul_int(
                                pwl_rgb[i-1].b, 2), pwl_rgb[i-2].b);
+}
 
-       return true;
+/* 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 = dal_fixed31_32_from_fraction(
+                               ramp->gamma[i], scaler);
+               rgb->g = dal_fixed31_32_from_fraction(
+                               ramp->gamma[i + 256], scaler);
+               rgb->b = dal_fixed31_32_from_fraction(
+                               ramp->gamma[i + 512], scaler);
+
+               ++rgb;
+               ++i;
+       } while (i != GAMMA_RGB_256_ENTRIES);
+
+       rgb->r = dal_fixed31_32_mul(rgb_last->r,
+                       dividers.divider1);
+       rgb->g = dal_fixed31_32_mul(rgb_last->g,
+                       dividers.divider1);
+       rgb->b = dal_fixed31_32_mul(rgb_last->b,
+                       dividers.divider1);
+
+       ++rgb;
+
+       rgb->r = dal_fixed31_32_mul(rgb_last->r,
+                       dividers.divider2);
+       rgb->g = dal_fixed31_32_mul(rgb_last->g,
+                       dividers.divider2);
+       rgb->b = dal_fixed31_32_mul(rgb_last->b,
+                       dividers.divider2);
+
+       ++rgb;
+
+       rgb->r = dal_fixed31_32_mul(rgb_last->r,
+                       dividers.divider3);
+       rgb->g = dal_fixed31_32_mul(rgb_last->g,
+                       dividers.divider3);
+       rgb->b = dal_fixed31_32_mul(rgb_last->b,
+                       dividers.divider3);
 }
 
 /*
@@ -949,7 +1010,7 @@ static inline void copy_rgb_regamma_to_coordinates_x(
        uint32_t i = 0;
        const struct pwl_float_data_ex *rgb_regamma = rgb_ex;
 
-       while (i <= hw_points_num) {
+       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;
@@ -1002,6 +1063,102 @@ static bool calculate_interpolated_hardware_curve(
        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 =
+                       dal_fixed31_32_from_int_nonconst(255);
+       struct fixed31_32 norm_x;
+       struct fixed31_32 index_f;
+       struct fixed31_32 lut1;
+       struct fixed31_32 lut2;
+       struct fixed31_32 delta_lut;
+       struct fixed31_32 delta_index;
+
+       i = 0;
+       /* fixed_pt library has problems handling too small values */
+       while (i != 32) {
+               tf_pts->red[i] = dal_fixed31_32_zero;
+               tf_pts->green[i] = dal_fixed31_32_zero;
+               tf_pts->blue[i] = dal_fixed31_32_zero;
+               ++i;
+       }
+       while (i <= hw_points_num + 1) {
+               for (color = 0; color < 3; color++) {
+                       if (color == 0)
+                               tf_point = &tf_pts->red[i];
+                       else if (color == 1)
+                               tf_point = &tf_pts->green[i];
+                       else
+                               tf_point = &tf_pts->blue[i];
+
+                       if (apply_degamma) {
+                               if (color == 0)
+                                       hw_x = coordinates_x[i].regamma_y_red;
+                               else if (color == 1)
+                                       hw_x = coordinates_x[i].regamma_y_green;
+                               else
+                                       hw_x = coordinates_x[i].regamma_y_blue;
+                       } else
+                               hw_x = coordinates_x[i].x;
+
+                       norm_x = dal_fixed31_32_mul(norm_factor, hw_x);
+                       index = dal_fixed31_32_floor(norm_x);
+                       if (index < 0 || index > 255)
+                               continue;
+
+                       index_f = dal_fixed31_32_from_int_nonconst(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 = dal_fixed31_32_sub(lut2, lut1);
+                       delta_index = dal_fixed31_32_sub(norm_x, index_f);
+
+                       *tf_point = dal_fixed31_32_add(lut1,
+                               dal_fixed31_32_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)
@@ -1025,6 +1182,29 @@ static void build_new_custom_resulted_curve(
        }
 }
 
+static void apply_degamma_for_user_regamma(struct pwl_float_data_ex *rgb_regamma,
+               uint32_t hw_points_num)
+{
+       uint32_t i;
+
+       struct gamma_coefficients coeff;
+       struct pwl_float_data_ex *rgb = rgb_regamma;
+       const struct hw_x_point *coord_x = coordinates_x;
+
+       build_coefficients(&coeff, true);
+
+       i = 0;
+       while (i != hw_points_num + 1) {
+               rgb->r = translate_from_linear_space_ex(
+                               coord_x->x, &coeff, 0);
+               rgb->g = rgb->r;
+               rgb->b = rgb->r;
+               ++coord_x;
+               ++rgb;
+               ++i;
+       }
+}
+
 static bool map_regamma_hw_to_x_user(
        const struct dc_gamma *ramp,
        struct pixel_gamma_point *coeff128,
@@ -1062,6 +1242,7 @@ static bool map_regamma_hw_to_x_user(
                }
        }
 
+       /* 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;
@@ -1168,6 +1349,113 @@ rgb_user_alloc_fail:
        return ret;
 }
 
+bool calculate_user_regamma_coeff(struct dc_transfer_func *output_tf,
+               const struct regamma_lut *regamma)
+{
+       struct gamma_coefficients coeff;
+       const struct hw_x_point *coord_x = coordinates_x;
+       uint32_t i = 0;
+
+       do {
+               coeff.a0[i] = dal_fixed31_32_from_fraction(
+                               regamma->coeff.A0[i], 10000000);
+               coeff.a1[i] = dal_fixed31_32_from_fraction(
+                               regamma->coeff.A1[i], 1000);
+               coeff.a2[i] = dal_fixed31_32_from_fraction(
+                               regamma->coeff.A2[i], 1000);
+               coeff.a3[i] = dal_fixed31_32_from_fraction(
+                               regamma->coeff.A3[i], 1000);
+               coeff.user_gamma[i] = dal_fixed31_32_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] = dal_fixed31_32_zero;
+               output_tf->tf_pts.green[i] = dal_fixed31_32_zero;
+               output_tf->tf_pts.blue[i] = dal_fixed31_32_zero;
+               ++coord_x;
+               ++i;
+       }
+       while (i != MAX_HW_POINTS + 1) {
+               output_tf->tf_pts.red[i] = translate_from_linear_space_ex(
+                               coord_x->x, &coeff, 0);
+               output_tf->tf_pts.green[i] = translate_from_linear_space_ex(
+                               coord_x->x, &coeff, 1);
+               output_tf->tf_pts.blue[i] = translate_from_linear_space_ex(
+                               coord_x->x, &coeff, 2);
+               ++coord_x;
+               ++i;
+       }
+
+       // this function just clamps output to 0-1
+       build_new_custom_resulted_curve(MAX_HW_POINTS, &output_tf->tf_pts);
+       output_tf->type = TF_TYPE_DISTRIBUTED_POINTS;
+
+       return true;
+}
+
+bool calculate_user_regamma_ramp(struct dc_transfer_func *output_tf,
+               const struct regamma_lut *regamma)
+{
+       struct dc_transfer_func_distributed_points *tf_pts = &output_tf->tf_pts;
+       struct dividers dividers;
+
+       struct pwl_float_data *rgb_user = NULL;
+       struct pwl_float_data_ex *rgb_regamma = NULL;
+       bool ret = false;
+
+       if (regamma == NULL)
+               return false;
+
+       output_tf->type = TF_TYPE_DISTRIBUTED_POINTS;
+
+       rgb_user = kzalloc(sizeof(*rgb_user) * (GAMMA_RGB_256_ENTRIES + _EXTRA_POINTS),
+                       GFP_KERNEL);
+       if (!rgb_user)
+               goto rgb_user_alloc_fail;
+
+       rgb_regamma = kzalloc(sizeof(*rgb_regamma) * (MAX_HW_POINTS + _EXTRA_POINTS),
+                       GFP_KERNEL);
+       if (!rgb_regamma)
+               goto rgb_regamma_alloc_fail;
+
+       dividers.divider1 = dal_fixed31_32_from_fraction(3, 2);
+       dividers.divider2 = dal_fixed31_32_from_int(2);
+       dividers.divider3 = dal_fixed31_32_from_fraction(5, 2);
+
+       scale_user_regamma_ramp(rgb_user, &regamma->ramp, dividers);
+
+       if (regamma->flags.bits.applyDegamma == 1) {
+               apply_degamma_for_user_regamma(rgb_regamma, MAX_HW_POINTS);
+               copy_rgb_regamma_to_coordinates_x(coordinates_x,
+                               MAX_HW_POINTS, rgb_regamma);
+       }
+
+       interpolate_user_regamma(MAX_HW_POINTS, rgb_user,
+                       regamma->flags.bits.applyDegamma, tf_pts);
+
+       // no custom HDR curves!
+       tf_pts->end_exponent = 0;
+       tf_pts->x_point_at_y1_red = 1;
+       tf_pts->x_point_at_y1_green = 1;
+       tf_pts->x_point_at_y1_blue = 1;
+
+       // this function just clamps output to 0-1
+       build_new_custom_resulted_curve(MAX_HW_POINTS, tf_pts);
+
+       ret = true;
+
+       kfree(rgb_regamma);
+rgb_regamma_alloc_fail:
+       kfree(rgb_user);
+rgb_user_alloc_fail:
+       return ret;
+}
+
 bool mod_color_calculate_degamma_params(struct dc_transfer_func *input_tf,
                const struct dc_gamma *ramp, bool mapUserRamp)
 {
index b7f9bc27d1019423ef850b57ca4f6d40dc06587c..b64048991a95d7485106f7b2ebdcaa913d34fde6 100644 (file)
@@ -32,6 +32,47 @@ struct dc_transfer_func_distributed_points;
 struct dc_rgb_fixed;
 enum dc_transfer_func_predefined;
 
+/* For SetRegamma ADL interface support
+ * Must match escape type
+ */
+union regamma_flags {
+       unsigned int raw;
+       struct {
+               unsigned int gammaRampArray       :1;    // RegammaRamp is in use
+               unsigned int gammaFromEdid        :1;    //gamma from edid is in use
+               unsigned int gammaFromEdidEx      :1;    //gamma from edid is in use , but only for Display Id 1.2
+               unsigned int gammaFromUser        :1;    //user custom gamma is used
+               unsigned int coeffFromUser        :1;    //coeff. A0-A3 from user is in use
+               unsigned int coeffFromEdid        :1;    //coeff. A0-A3 from edid is in use
+               unsigned int applyDegamma         :1;    //flag for additional degamma correction in driver
+               unsigned int gammaPredefinedSRGB  :1;    //flag for SRGB gamma
+               unsigned int gammaPredefinedPQ    :1;    //flag for PQ gamma
+               unsigned int gammaPredefinedPQ2084Interim :1;    //flag for PQ gamma, lower max nits
+               unsigned int gammaPredefined36    :1;    //flag for 3.6 gamma
+               unsigned int gammaPredefinedReset :1;    //flag to return to previous gamma
+       } bits;
+};
+
+struct regamma_ramp {
+       unsigned short gamma[256*3];  // gamma ramp packed  in same way as OS windows ,r , g & b
+};
+
+struct regamma_coeff {
+       int    gamma[3];
+       int    A0[3];
+       int    A1[3];
+       int    A2[3];
+       int    A3[3];
+};
+
+struct regamma_lut {
+       union regamma_flags flags;
+       union {
+               struct regamma_ramp ramp;
+               struct regamma_coeff coeff;
+       };
+};
+
 void setup_x_points_distribution(void);
 void precompute_pq(void);
 void precompute_de_pq(void);
@@ -45,9 +86,14 @@ bool mod_color_calculate_degamma_params(struct dc_transfer_func *output_tf,
 bool mod_color_calculate_curve(enum dc_transfer_func_predefined  trans,
                struct dc_transfer_func_distributed_points *points);
 
-bool  mod_color_calculate_degamma_curve(enum dc_transfer_func_predefined trans,
+bool mod_color_calculate_degamma_curve(enum dc_transfer_func_predefined trans,
                                struct dc_transfer_func_distributed_points *points);
 
+bool calculate_user_regamma_coeff(struct dc_transfer_func *output_tf,
+               const struct regamma_lut *regamma);
+
+bool calculate_user_regamma_ramp(struct dc_transfer_func *output_tf,
+               const struct regamma_lut *regamma);
 
 
 #endif /* COLOR_MOD_COLOR_GAMMA_H_ */