/*
 * 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];

// Hardcoded table that depends on setup_x_points_distribution and sdr_level=80
// If x points are changed, then PQ Y points will be misaligned and a new
// table would need to be generated. Or use old method that calls compute_pq.
// The last point is above PQ formula range (0-125 in normalized FP16)
// The value for the last point (128) is such that interpolation from
// 120 to 128 will give 1.0 for X = 125.0
// first couple points are 0 - HW LUT is mirrored around zero, so making first
// segment 0 to 0 will effectively clip it, and these are very low PQ codes
// min nonzero value below (216825) is a little under 12-bit PQ code 1.
static const unsigned long long pq_divider = 1000000000;
static const unsigned long long pq_numerator[MAX_HW_POINTS + 1] = {
		0, 0, 0, 0, 216825, 222815,
		228691, 234460, 240128, 245702, 251187, 256587,
		261908, 267152, 272324, 277427, 282465, 292353,
		302011, 311456, 320704, 329768, 338661, 347394,
		355975, 364415, 372721, 380900, 388959, 396903,
		404739, 412471, 420104, 435089, 449727, 464042,
		478060, 491800, 505281, 518520, 531529, 544324,
		556916, 569316, 581533, 593576, 605454, 617175,
		628745, 651459, 673643, 695337, 716578, 737395,
		757817, 777869, 797572, 816947, 836012, 854782,
		873274, 891500, 909474, 927207, 944709, 979061,
		1012601, 1045391, 1077485, 1108931, 1139770, 1170042,
		1199778, 1229011, 1257767, 1286071, 1313948, 1341416,
		1368497, 1395207, 1421563, 1473272, 1523733, 1573041,
		1621279, 1668520, 1714828, 1760262, 1804874, 1848710,
		1891814, 1934223, 1975973, 2017096, 2057622, 2097578,
		2136989, 2214269, 2289629, 2363216, 2435157, 2505564,
		2574539, 2642169, 2708536, 2773711, 2837760, 2900742,
		2962712, 3023719, 3083810, 3143025, 3201405, 3315797,
		3427246, 3535974, 3642181, 3746038, 3847700, 3947305,
		4044975, 4140823, 4234949, 4327445, 4418394, 4507872,
		4595951, 4682694, 4768161, 4935487, 5098326, 5257022,
		5411878, 5563161, 5711107, 5855928, 5997812, 6136929,
		6273436, 6407471, 6539163, 6668629, 6795976, 6921304,
		7044703, 7286050, 7520623, 7748950, 7971492, 8188655,
		8400800, 8608247, 8811286, 9010175, 9205149, 9396421,
		9584186, 9768620, 9949889, 10128140, 10303513, 10646126,
		10978648, 11301874, 11616501, 11923142, 12222340, 12514578,
		12800290, 13079866, 13353659, 13621988, 13885144, 14143394,
		14396982, 14646132, 14891052, 15368951, 15832050, 16281537,
		16718448, 17143696, 17558086, 17962337, 18357092, 18742927,
		19120364, 19489877, 19851894, 20206810, 20554983, 20896745,
		21232399, 21886492, 22519276, 23132491, 23727656, 24306104,
		24869013, 25417430, 25952292, 26474438, 26984626, 27483542,
		27971811, 28450000, 28918632, 29378184, 29829095, 30706591,
		31554022, 32373894, 33168387, 33939412, 34688657, 35417620,
		36127636, 36819903, 37495502, 38155408, 38800507, 39431607,
		40049446, 40654702, 41247996, 42400951, 43512407, 44585892,
		45624474, 46630834, 47607339, 48556082, 49478931, 50377558,
		51253467, 52108015, 52942436, 53757848, 54555277, 55335659,
		56099856, 57582802, 59009766, 60385607, 61714540, 63000246,
		64245964, 65454559, 66628579, 67770304, 68881781, 69964856,
		71021203, 72052340, 73059655, 74044414, 75007782, 76874537,
		78667536, 80393312, 82057522, 83665098, 85220372, 86727167,
		88188883, 89608552, 90988895, 92332363, 93641173, 94917336,
		96162685, 97378894, 98567496, 100867409, 103072439, 105191162,
		107230989, 109198368, 111098951, 112937723, 114719105, 116447036,
		118125045, 119756307, 121343688, 122889787, 124396968, 125867388,
		127303021, 130077030, 132731849, 135278464, 137726346, 140083726,
		142357803, 144554913, 146680670, 148740067, 150737572, 152677197,
		154562560, 156396938, 158183306, 159924378, 161622632, 164899602,
		168030318, 171028513, 173906008, 176673051, 179338593, 181910502,
		184395731, 186800463, 189130216, 191389941, 193584098, 195716719,
		197791463, 199811660, 201780351, 205574133, 209192504, 212652233,
		215967720, 219151432, 222214238, 225165676, 228014163, 230767172,
		233431363, 236012706, 238516569, 240947800, 243310793, 245609544,
		247847696, 252155270, 256257056, 260173059, 263920427, 267513978,
		270966613, 274289634, 277493001, 280585542, 283575118, 286468763,
		289272796, 291992916, 294634284, 297201585, 299699091, 304500003,
		309064541, 313416043, 317574484, 321557096, 325378855, 329052864,
		332590655, 336002433, 339297275, 342483294, 345567766, 348557252,
		351457680, 354274432, 357012407, 362269536, 367260561, 372012143,
		376547060, 380884936, 385042798, 389035522, 392876185, 396576344,
		400146265, 403595112, 406931099, 410161619, 413293351, 416332348,
		419284117, 424945627, 430313203, 435416697, 440281572, 444929733,
		449380160, 453649415, 457752035, 461700854, 465507260, 469181407,
		472732388, 476168376, 479496748, 482724188, 485856764, 491858986,
		497542280, 502939446, 508078420, 512983199, 517674549, 522170569,
		526487126, 530638214, 534636233, 538492233, 542216094, 545816693,
		549302035, 552679362, 555955249, 562226134, 568156709, 573782374,
		579133244, 584235153, 589110430, 593778512, 598256421, 602559154,
		606699989, 610690741, 614541971, 618263157, 621862836, 625348729,
		628727839, 635190643, 641295921, 647081261, 652578597, 657815287,
		662814957, 667598146, 672182825, 676584810, 680818092, 684895111,
		688826974, 692623643, 696294085, 699846401, 703287935, 709864782,
		716071394, 721947076, 727525176, 732834238, 737898880, 742740485,
		747377745, 751827095, 756103063, 760218552, 764185078, 768012958,
		771711474, 775289005, 778753144, 785368225, 791604988, 797503949,
		803099452, 808420859, 813493471, 818339244, 822977353, 827424644,
		831695997, 835804619, 839762285, 843579541, 847265867, 850829815,
		854279128, 860861356, 867061719, 872921445, 878475444, 883753534,
		888781386, 893581259, 898172578, 902572393, 906795754, 910856010,
		914765057, 918533538, 922171018, 925686119, 929086644, 935571664,
		941675560, 947439782, 952899395, 958084324, 963020312, 967729662,
		972231821, 976543852, 980680801, 984656009, 988481353, 992167459,
		995723865, 999159168, 1002565681};

// 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,    2222,   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 *pq_table = mod_color_get_table(type_pq_table);

	for (i = 0; i <= MAX_HW_POINTS; i++)
		pq_table[i] = dc_fixpt_from_fraction(pq_numerator[i], pq_divider);

	/* below is old method that uses run-time calculation in fixed pt space */
	/* pow function has problems with arguments too small */
	/*
	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);

	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;

		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 = dc_fixpt_zero;
	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 map_user_ramp,
	bool do_clamping)
{
	/* 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 && map_user_ramp) {
		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 (do_clamping) {
		/* 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, &regamma->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 map_user_ramp)
{
	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;
	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 no user ramp
	 */
	if (input_tf->type == TF_TYPE_PREDEFINED) {
		if ((input_tf->tf == TRANSFER_FUNCTION_SRGB ||
				input_tf->tf == TRANSFER_FUNCTION_LINEAR) &&
				!map_user_ramp)
			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 (map_user_ramp && 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,
				map_user_ramp && 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 map_user_ramp,
					bool can_rom_be_used,
					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;
	bool do_clamping = 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 && can_rom_be_used == true &&
			output_tf->tf == TRANSFER_FUNCTION_SRGB) {
		if (ramp == NULL)
			return true;
		if ((ramp->is_identity && ramp->type != GAMMA_CS_TFM_1D) ||
		    (!map_user_ramp && ramp->type == GAMMA_RGB_256))
			return true;
	}

	output_tf->type = TF_TYPE_DISTRIBUTED_POINTS;

	if (ramp && ramp->type != GAMMA_CS_TFM_1D &&
	    (map_user_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 + 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 && map_user_ramp)
			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) {
		do_clamping = !(output_tf->tf == TRANSFER_FUNCTION_PQ) &&
				!(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,
					 (map_user_ramp || (ramp && ramp->type != GAMMA_RGB_256)) &&
					 (ramp && ramp->type != GAMMA_CS_TFM_1D),
					 do_clamping);

		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;
}
