// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2016-2020, The Linux Foundation. All rights reserved.
 */

#include "dsi_phy_timing_calc.h"

static const u32 bits_per_pixel[DSI_PIXEL_FORMAT_MAX] = {
	16, 18, 18, 24, 3, 8, 12 };

static int dsi_phy_cmn_validate_and_set(struct timing_entry *t,
	char const *t_name)
{
	if (t->rec & 0xffffff00) {
		/* Output value can only be 8 bits */
		DSI_ERR("Incorrect %s rec value - %d\n", t_name, t->rec);
		return -EINVAL;
	}
	t->reg_value = t->rec;
	return 0;
}

/**
 * calc_clk_prepare - calculates prepare timing params for clk lane.
 */
static int calc_clk_prepare(struct dsi_phy_hw *phy,
				struct phy_clk_params *clk_params,
			    struct phy_timing_desc *desc,
			    s32 *actual_frac,
			    s64 *actual_intermediate)
{
	u64 multiplier = BIT(20);
	struct timing_entry *t = &desc->clk_prepare;
	int rc = 0;
	u64 dividend, temp, temp_multiple;
	s32 frac = 0;
	s64 intermediate;
	s64 clk_prep_actual;

	t->rec_min = DIV_ROUND_UP((t->mipi_min * clk_params->bitclk_mbps),
			(8 * clk_params->tlpx_numer_ns));
	t->rec_max = rounddown(
		mult_frac((t->mipi_max * clk_params->bitclk_mbps),
			1, (8 * clk_params->tlpx_numer_ns)), 1);

	dividend = ((t->rec_max - t->rec_min) *
		clk_params->clk_prep_buf * multiplier);
	temp  = roundup(div_s64(dividend, 100), multiplier);
	temp += (t->rec_min * multiplier);
	t->rec = div_s64(temp, multiplier);

	rc = dsi_phy_cmn_validate_and_set(t, "clk_prepare");
	if (rc)
		goto error;

	/* calculate theoretical value */
	temp_multiple = 8 * t->reg_value * clk_params->tlpx_numer_ns
			 * multiplier;
	intermediate = div_s64(temp_multiple, clk_params->bitclk_mbps);
	div_s64_rem(temp_multiple, clk_params->bitclk_mbps, &frac);
	clk_prep_actual = div_s64((intermediate + frac), multiplier);

	DSI_PHY_DBG(phy, "CLK_PREPARE:mipi_min=%d, mipi_max=%d, rec_min=%d, rec_max=%d\n",
		 t->mipi_min, t->mipi_max, t->rec_min, t->rec_max);
	DSI_PHY_DBG(phy, " reg_value=%d, actual=%lld\n", t->reg_value,
			clk_prep_actual);

	*actual_frac = frac;
	*actual_intermediate = intermediate;

error:
	return rc;
}

/**
 * calc_clk_zero - calculates zero timing params for clk lane.
 */
static int calc_clk_zero(struct dsi_phy_hw *phy,
			struct phy_clk_params *clk_params,
			struct phy_timing_desc *desc,
			s32 actual_frac, s64 actual_intermediate)
{
	u64 const multiplier = BIT(20);
	int rc = 0;
	struct timing_entry *t = &desc->clk_zero;
	s64 mipi_min, rec_temp1;
	struct phy_timing_ops *ops = phy->ops.timing_ops;

	mipi_min = ((300 * multiplier) - (actual_intermediate + actual_frac));
	t->mipi_min = div_s64(mipi_min, multiplier);

	rec_temp1 = div_s64((mipi_min * clk_params->bitclk_mbps),
			    clk_params->tlpx_numer_ns);

	if (ops->calc_clk_zero) {
		t->rec_min = ops->calc_clk_zero(rec_temp1, multiplier);
	} else {
		rc = -EINVAL;
		goto error;
	}
	t->rec_max = ((t->rec_min > 255) ? 511 : 255);

	t->rec = DIV_ROUND_UP((((t->rec_max - t->rec_min) *
		clk_params->clk_zero_buf) + (t->rec_min * 100)), 100);

	rc = dsi_phy_cmn_validate_and_set(t, "clk_zero");
	if (rc)
		goto error;


	DSI_PHY_DBG(phy, "CLK_ZERO:mipi_min=%d, mipi_max=%d, rec_min=%d, rec_max=%d, reg_val=%d\n",
		 t->mipi_min, t->mipi_max, t->rec_min, t->rec_max,
		 t->reg_value);
error:
	return rc;
}

/**
 * calc_clk_trail - calculates prepare trail params for clk lane.
 */
static int calc_clk_trail(struct dsi_phy_hw *phy,
			struct phy_clk_params *clk_params,
			struct phy_timing_desc *desc,
			s64 *teot_clk_lane)
{
	u64 const multiplier = BIT(20);
	int rc = 0;
	struct timing_entry *t = &desc->clk_trail;
	u64 temp_multiple;
	s32 frac;
	s64 mipi_max_tr, rec_temp1, mipi_max;
	s64 teot_clk_lane1;
	struct phy_timing_ops *ops = phy->ops.timing_ops;

	temp_multiple = div_s64(
			(12 * multiplier * clk_params->tlpx_numer_ns),
			clk_params->bitclk_mbps);
	div_s64_rem(temp_multiple, multiplier, &frac);

	mipi_max_tr = ((105 * multiplier) +
		       (temp_multiple + frac));
	teot_clk_lane1 = div_s64(mipi_max_tr, multiplier);

	mipi_max = (mipi_max_tr - (clk_params->treot_ns * multiplier));
	t->mipi_max = div_s64(mipi_max, multiplier);

	temp_multiple = div_s64(
			(t->mipi_min * multiplier * clk_params->bitclk_mbps),
			clk_params->tlpx_numer_ns);

	div_s64_rem(temp_multiple, multiplier, &frac);
	if (ops->calc_clk_trail_rec_min) {
		t->rec_min = ops->calc_clk_trail_rec_min(temp_multiple,
			frac, multiplier);
	} else {
		rc = -EINVAL;
		goto error;
	}

	/* recommended max */
	rec_temp1 = div_s64((mipi_max * clk_params->bitclk_mbps),
			    clk_params->tlpx_numer_ns);
	if (ops->calc_clk_trail_rec_max) {
		t->rec_max = ops->calc_clk_trail_rec_max(rec_temp1, multiplier);
	} else {
		rc = -EINVAL;
		goto error;
	}

	t->rec = DIV_ROUND_UP(
		(((t->rec_max - t->rec_min) * clk_params->clk_trail_buf) +
		 (t->rec_min * 100)), 100);

	rc = dsi_phy_cmn_validate_and_set(t, "clk_trail");
	if (rc)
		goto error;

	*teot_clk_lane = teot_clk_lane1;
	DSI_PHY_DBG(phy, "CLK_TRAIL:mipi_min=%d, mipi_max=%d, rec_min=%d, rec_max=%d, reg_val=%d\n",
		 t->mipi_min, t->mipi_max, t->rec_min, t->rec_max,
		 t->reg_value);

error:
	return rc;

}

/**
 * calc_hs_prepare - calculates prepare timing params for data lanes in HS.
 */
static int calc_hs_prepare(struct dsi_phy_hw *phy,
			struct phy_clk_params *clk_params,
			struct phy_timing_desc *desc,
			u64 *temp_mul)
{
	u64 multiplier = BIT(20);
	int rc = 0;
	struct timing_entry *t = &desc->hs_prepare;
	u64 temp_multiple, dividend, temp;
	s32 frac;
	s64 rec_temp1, rec_temp2, mipi_max, mipi_min;
	u32 low_clk_multiplier = 0;

	if (clk_params->bitclk_mbps <= 120)
		low_clk_multiplier = 2;
	/* mipi min */
	temp_multiple = div_s64((4 * multiplier * clk_params->tlpx_numer_ns),
				clk_params->bitclk_mbps);
	div_s64_rem(temp_multiple, multiplier, &frac);
	mipi_min = (40 * multiplier) + (temp_multiple + frac);
	t->mipi_min = div_s64(mipi_min, multiplier);

	/* mipi_max */
	temp_multiple = div_s64(
			(6 * multiplier * clk_params->tlpx_numer_ns),
			clk_params->bitclk_mbps);
	div_s64_rem(temp_multiple, multiplier, &frac);
	mipi_max = (85 * multiplier) + temp_multiple;
	t->mipi_max = div_s64(mipi_max, multiplier);

	/* recommended min */
	temp_multiple = div_s64((mipi_min * clk_params->bitclk_mbps),
				clk_params->tlpx_numer_ns);
	temp_multiple -= (low_clk_multiplier * multiplier);
	div_s64_rem(temp_multiple, multiplier, &frac);
	rec_temp1 = roundup(((temp_multiple + frac) / 8), multiplier);
	t->rec_min = div_s64(rec_temp1, multiplier);

	/* recommended max */
	temp_multiple = div_s64((mipi_max * clk_params->bitclk_mbps),
				clk_params->tlpx_numer_ns);
	temp_multiple -= (low_clk_multiplier * multiplier);
	div_s64_rem(temp_multiple, multiplier, &frac);
	rec_temp2 = rounddown((temp_multiple / 8), multiplier);
	t->rec_max = div_s64(rec_temp2, multiplier);

	/* register value */
	dividend = ((rec_temp2 - rec_temp1) * clk_params->hs_prep_buf);
	temp = roundup(div_u64(dividend, 100), multiplier);
	t->rec = div_s64((temp + rec_temp1), multiplier);

	rc = dsi_phy_cmn_validate_and_set(t, "hs_prepare");
	if (rc)
		goto error;

	temp_multiple = div_s64(
			(8 * (temp + rec_temp1) * clk_params->tlpx_numer_ns),
			clk_params->bitclk_mbps);

	*temp_mul = temp_multiple;
	DSI_PHY_DBG(phy, "HS_PREP:mipi_min=%d, mipi_max=%d, rec_min=%d, rec_max=%d, reg_val=%d\n",
		 t->mipi_min, t->mipi_max, t->rec_min, t->rec_max,
		 t->reg_value);
error:
	return rc;
}

/**
 * calc_hs_zero - calculates zero timing params for data lanes in HS.
 */
static int calc_hs_zero(struct dsi_phy_hw *phy,
			struct phy_clk_params *clk_params,
			struct phy_timing_desc *desc,
			u64 temp_multiple)
{
	u64 const multiplier = BIT(20);
	int rc = 0;
	struct timing_entry *t = &desc->hs_zero;
	s64 rec_temp1, mipi_min;
	struct phy_timing_ops *ops = phy->ops.timing_ops;

	mipi_min = div_s64((10 * clk_params->tlpx_numer_ns * multiplier),
			   clk_params->bitclk_mbps);
	rec_temp1 = (145 * multiplier) + mipi_min - temp_multiple;
	t->mipi_min = div_s64(rec_temp1, multiplier);

	/* recommended min */
	rec_temp1 = div_s64((rec_temp1 * clk_params->bitclk_mbps),
			    clk_params->tlpx_numer_ns);

	if (ops->calc_hs_zero) {
		t->rec_min = ops->calc_hs_zero(rec_temp1, multiplier);
	} else {
		rc = -EINVAL;
		goto error;
	}

	t->rec_max = ((t->rec_min > 255) ? 511 : 255);
	t->rec = DIV_ROUND_UP(
			(((t->rec_max - t->rec_min) * clk_params->hs_zero_buf) +
			 (t->rec_min * 100)),
			100);

	rc = dsi_phy_cmn_validate_and_set(t, "hs_zero");
	if (rc)
		goto error;

	DSI_PHY_DBG(phy, "HS_ZERO:mipi_min=%d, mipi_max=%d, rec_min=%d, rec_max=%d, reg_val=%d\n",
		 t->mipi_min, t->mipi_max, t->rec_min, t->rec_max,
		 t->reg_value);

error:
	return rc;
}

/**
 * calc_hs_trail - calculates trail timing params for data lanes in HS.
 */
static int calc_hs_trail(struct dsi_phy_hw *phy,
			struct phy_clk_params *clk_params,
			struct phy_timing_desc *desc,
			u64 teot_clk_lane)
{
	int rc = 0;
	struct timing_entry *t = &desc->hs_trail;
	s64 rec_temp1;
	struct phy_timing_ops *ops = phy->ops.timing_ops;

	t->mipi_min = 60 +
			mult_frac(clk_params->tlpx_numer_ns, 4,
				  clk_params->bitclk_mbps);

	t->mipi_max = teot_clk_lane - clk_params->treot_ns;

	if (ops->calc_hs_trail) {
		ops->calc_hs_trail(clk_params, desc);
	} else {
		rc = -EINVAL;
		goto error;
	}

	rec_temp1 = DIV_ROUND_UP(
			((t->rec_max - t->rec_min) * clk_params->hs_trail_buf),
			100);
	t->rec = rec_temp1 + t->rec_min;

	rc = dsi_phy_cmn_validate_and_set(t, "hs_trail");
	if (rc)
		goto error;

	DSI_PHY_DBG(phy, "HS_TRAIL:mipi_min=%d, mipi_max=%d, rec_min=%d, rec_max=%d, reg_val=%d\n",
		 t->mipi_min, t->mipi_max, t->rec_min, t->rec_max,
		 t->reg_value);

error:
	return rc;
}

/**
 * calc_hs_rqst - calculates rqst timing params for data lanes in HS.
 */
static int calc_hs_rqst(struct dsi_phy_hw *phy,
			struct phy_clk_params *clk_params,
			struct phy_timing_desc *desc)
{
	int rc = 0;
	struct timing_entry *t = &desc->hs_rqst;

	t->rec = DIV_ROUND_UP(
		((t->mipi_min * clk_params->bitclk_mbps) -
		 (8 * clk_params->tlpx_numer_ns)),
		(8 * clk_params->tlpx_numer_ns));

	rc = dsi_phy_cmn_validate_and_set(t, "hs_rqst");
	if (rc)
		goto error;

	DSI_PHY_DBG(phy, "HS_RQST:mipi_min=%d, mipi_max=%d, rec_min=%d, rec_max=%d, reg_val=%d\n",
		 t->mipi_min, t->mipi_max, t->rec_min, t->rec_max,
		 t->reg_value);

error:
	return rc;
}

/**
 * calc_hs_exit - calculates exit timing params for data lanes in HS.
 */
static int calc_hs_exit(struct dsi_phy_hw *phy,
			struct phy_clk_params *clk_params,
			struct phy_timing_desc *desc)
{
	int rc = 0;
	struct timing_entry *t = &desc->hs_exit;

	t->rec_min = (DIV_ROUND_UP(
			(t->mipi_min * clk_params->bitclk_mbps),
			(8 * clk_params->tlpx_numer_ns)) - 1);

	t->rec = DIV_ROUND_UP(
		(((t->rec_max - t->rec_min) * clk_params->hs_exit_buf) +
		 (t->rec_min * 100)), 100);

	rc = dsi_phy_cmn_validate_and_set(t, "hs_exit");
	if (rc)
		goto error;


	DSI_PHY_DBG(phy, "HS_EXIT:mipi_min=%d, mipi_max=%d, rec_min=%d, rec_max=%d, reg_val=%d\n",
		 t->mipi_min, t->mipi_max, t->rec_min, t->rec_max,
		 t->reg_value);

error:
	return rc;
}

/**
 * calc_hs_rqst_clk - calculates rqst timing params for clock lane..
 */
static int calc_hs_rqst_clk(struct dsi_phy_hw *phy,
			struct phy_clk_params *clk_params,
			struct phy_timing_desc *desc)
{
	int rc = 0;
	struct timing_entry *t = &desc->hs_rqst_clk;

	t->rec = DIV_ROUND_UP(
		((t->mipi_min * clk_params->bitclk_mbps) -
		 (8 * clk_params->tlpx_numer_ns)),
		(8 * clk_params->tlpx_numer_ns));

	rc = dsi_phy_cmn_validate_and_set(t, "hs_rqst_clk");
	if (rc)
		goto error;

	DSI_PHY_DBG(phy, "HS_RQST_CLK:mipi_min=%d, mipi_max=%d, rec_min=%d, rec_max=%d, reg_val=%d\n",
		 t->mipi_min, t->mipi_max, t->rec_min, t->rec_max,
		 t->reg_value);

error:
	return rc;
}

/**
 * cal_clk_pulse_time - calculates clk pulse time in nsec
 */
static s64 cal_clk_pulse_time(u32 inp1, u32 inp2, u32 bitclk_mbps)
{
	u64 const multiplier = BIT(20);
	u64 clk_multiple;
	s32 frac;
	s64 temp, result;

	clk_multiple = div_s64((inp1 * multiplier * 1000), bitclk_mbps);
	div_s64_rem(clk_multiple, multiplier, &frac);
	temp = (inp2 * multiplier) + (clk_multiple + frac);
	result = div_s64(temp, multiplier);

	return result;
}

/**
 * calc_clk_post - calculates clk_post timing params for data lanes in HS.
 */
static int calc_clk_post(struct dsi_phy_hw *phy,
			struct phy_clk_params *clk_params,
			struct phy_timing_desc *desc)
{
	int rc = 0;
	struct timing_entry *t = &desc->clk_post;
	s64 rec_cal1, rec_cal2;
	u32 input1;

	/* mipi min */
	t->mipi_min = cal_clk_pulse_time(52, 60, clk_params->bitclk_mbps);

	/* recommended min
	 * = roundup((mipi_min_ns + t_hs_trail_ns)/(16*bit_clk_ns), 0) - 1
	 */
	rec_cal1 = cal_clk_pulse_time(16, 0, clk_params->bitclk_mbps);

	input1 = (desc->hs_trail.reg_value + 1) * 8;
	rec_cal2 = cal_clk_pulse_time(input1, 0, clk_params->bitclk_mbps);
	rec_cal2 += t->mipi_min;

	t->rec_min = div_s64(rec_cal2, rec_cal1) - 1;

	/* recommended max */
	t->rec_max = 255;

	/* register value */
	t->rec = DIV_ROUND_UP((((t->rec_max - t->rec_min) *
		clk_params->clk_post_buf) + (t->rec_min * 100)), 100);

	rc = dsi_phy_cmn_validate_and_set(t, "clk_post");
	if (rc)
		goto error;

	DSI_PHY_DBG(phy, "CLK_POST:mipi_min=%d, mipi_max=%d, rec_min=%d, rec_max=%d, reg_val=%d\n",
		 t->mipi_min, t->mipi_max, t->rec_min, t->rec_max,
		 t->reg_value);
error:
	return rc;
}

/**
 * calc_clk_pre - calculates clk_pre timing params for data lanes in HS.
 */
static int calc_clk_pre(struct dsi_phy_hw *phy,
			struct phy_clk_params *clk_params,
			struct phy_timing_desc *desc)
{
	int rc = 0;
	struct timing_entry *t = &desc->clk_pre;
	s64 rec_temp1;
	s64 clk_prepare, clk_zero, clk_16;
	u32 input1;

	/* mipi min */
	t->mipi_min = cal_clk_pulse_time(8, 0, clk_params->bitclk_mbps);

	/* recommended min
	 * val1 = (tlpx_ns + clk_prepare_ns + clk_zero_ns + hs_rqst_ns)
	 * val2 = (16 * bit_clk_ns)
	 * final = roundup(val1/val2, 0) - 1
	 */
	input1 = desc->clk_prepare.reg_value * 8;
	clk_prepare = cal_clk_pulse_time(input1, 0, clk_params->bitclk_mbps);

	input1 = (desc->clk_zero.reg_value + 1) * 8;
	clk_zero = cal_clk_pulse_time(input1, 0, clk_params->bitclk_mbps);

	clk_16 = cal_clk_pulse_time(16, 0, clk_params->bitclk_mbps);

	rec_temp1 = 52 + clk_prepare + clk_zero + 54;
	t->rec_min = div_s64(rec_temp1, clk_16) - 1;

	/* recommended max */
	t->rec_max = 255;

	/* register value */
	t->rec =DIV_ROUND_UP((((t->rec_max - t->rec_min) *
		125) + (t->rec_min * 100 * 100)), 100 * 100);

	rc = dsi_phy_cmn_validate_and_set(t, "clk_pre");
	if (rc)
		goto error;

	DSI_PHY_DBG(phy, "CLK_PRE:mipi_min=%d, mipi_max=%d, rec_min=%d, rec_max=%d, reg_val=%d\n",
		 t->mipi_min, t->mipi_max, t->rec_min, t->rec_max,
		 t->reg_value);
error:
	return rc;
}

/**
 * dsi_phy_calc_timing_params - calculates timing paramets for a given bit clock
 */
static int dsi_phy_cmn_calc_timing_params(struct dsi_phy_hw *phy,
	struct phy_clk_params *clk_params, struct phy_timing_desc *desc)
{
	int rc = 0;
	s32 actual_frac = 0;
	s64 actual_intermediate = 0;
	u64 temp_multiple;
	s64 teot_clk_lane;

	rc = calc_clk_prepare(phy, clk_params, desc, &actual_frac,
			      &actual_intermediate);
	if (rc) {
		DSI_PHY_ERR(phy, "clk_prepare calculations failed, rc=%d\n",
				rc);
		goto error;
	}

	rc = calc_clk_zero(phy, clk_params, desc,
		actual_frac, actual_intermediate);
	if (rc) {
		DSI_PHY_ERR(phy, "clk_zero calculations failed, rc=%d\n", rc);
		goto error;
	}

	rc = calc_clk_trail(phy, clk_params, desc, &teot_clk_lane);
	if (rc) {
		DSI_PHY_ERR(phy, "clk_trail calculations failed, rc=%d\n", rc);
		goto error;
	}

	rc = calc_hs_prepare(phy, clk_params, desc, &temp_multiple);
	if (rc) {
		DSI_PHY_ERR(phy, "hs_prepare calculations failed, rc=%d\n", rc);
		goto error;
	}

	rc = calc_hs_zero(phy, clk_params, desc, temp_multiple);
	if (rc) {
		DSI_PHY_ERR(phy, "hs_zero calculations failed, rc=%d\n", rc);
		goto error;
	}

	rc = calc_hs_trail(phy, clk_params, desc, teot_clk_lane);
	if (rc) {
		DSI_PHY_ERR(phy, "hs_trail calculations failed, rc=%d\n", rc);
		goto error;
	}

	rc = calc_hs_rqst(phy, clk_params, desc);
	if (rc) {
		DSI_PHY_ERR(phy, "hs_rqst calculations failed, rc=%d\n", rc);
		goto error;
	}

	rc = calc_hs_exit(phy, clk_params, desc);
	if (rc) {
		DSI_PHY_ERR(phy, "hs_exit calculations failed, rc=%d\n", rc);
		goto error;
	}

	rc = calc_hs_rqst_clk(phy, clk_params, desc);
	if (rc) {
		DSI_PHY_ERR(phy, "hs_rqst_clk calculations failed, rc=%d\n",
				rc);
		goto error;
	}

	rc = calc_clk_post(phy, clk_params, desc);
	if (rc) {
		DSI_PHY_ERR(phy, "clk_post calculations failed, rc=%d\n", rc);
		goto error;
	}

	rc = calc_clk_pre(phy, clk_params, desc);
	if (rc) {
		DSI_PHY_ERR(phy, "clk_pre calculations failed, rc=%d\n", rc);
		goto error;
	}
error:
	return rc;
}

/**
 * calc_cphy_clk_prepare - calculates cphy_clk_prepare parameter for cphy.
 */
static int calc_cphy_clk_prepare(struct dsi_phy_hw *phy,
			struct phy_clk_params *clk_params,
			struct phy_timing_desc *desc)
{
	u64 multiplier = BIT(20);
	struct timing_entry *t = &desc->clk_prepare;
	int rc = 0;
	u64 dividend, temp;

	t->rec_min = DIV_ROUND_UP((t->mipi_min * clk_params->bitclk_mbps),
			(7 * clk_params->tlpx_numer_ns));
	t->rec_max = rounddown(
		mult_frac((t->mipi_max * clk_params->bitclk_mbps),
			1, (7 * clk_params->tlpx_numer_ns)), 1);

	dividend = ((t->rec_max - t->rec_min) *
		clk_params->clk_prep_buf * multiplier);
	temp  = roundup(div_s64(dividend, 100), multiplier);
	temp += (t->rec_min * multiplier);
	t->rec = div_s64(temp, multiplier);

	rc = dsi_phy_cmn_validate_and_set(t, "cphy_clk_prepare");

	DSI_DEBUG("CPHY_CLK_PREPARE: rec_min=%d, rec_max=%d, reg_val=%d\n",
		t->rec_min, t->rec_max, t->reg_value);

	return rc;
}

/**
 * calc_cphy_clk_pre - calculates cphy_clk_pre parameter for cphy.
 */
static int calc_cphy_clk_pre(struct dsi_phy_hw *phy,
			struct phy_clk_params *clk_params,
			struct phy_timing_desc *desc)
{
	u64 multiplier = BIT(20);
	struct timing_entry *t = &desc->clk_pre;
	int rc = 0;
	u64 dividend, temp;

	t->mipi_min = min(300 - 38 - mult_frac(7, clk_params->tlpx_numer_ns,
			clk_params->bitclk_mbps),
			mult_frac(448, clk_params->tlpx_numer_ns,
			clk_params->bitclk_mbps));
	t->mipi_max = mult_frac(448, clk_params->tlpx_numer_ns,
			clk_params->bitclk_mbps);

	t->rec_min = DIV_ROUND_UP((t->mipi_min * clk_params->bitclk_mbps),
			(7 * clk_params->tlpx_numer_ns));
	t->rec_max = rounddown(
		mult_frac((t->mipi_max * clk_params->bitclk_mbps),
			1, (7 * clk_params->tlpx_numer_ns)), 1);

	dividend = ((t->rec_max - t->rec_min) * clk_params->clk_pre_buf
			* multiplier);
	temp  = roundup(div_s64(dividend, 100), multiplier);
	temp += (t->rec_min * multiplier);
	t->rec = div_s64(temp, multiplier);

	rc = dsi_phy_cmn_validate_and_set(t, "cphy_clk_pre");

	DSI_DEBUG("CPHY_CLK_PRE: rec_min=%d, rec_max=%d, reg_val=%d\n",
		t->rec_min, t->rec_max, t->reg_value);

	return rc;
}

/**
 * calc_cphy_clk_post - calculates cphy_clk_post parameter for cphy.
 */
static int calc_cphy_clk_post(struct dsi_phy_hw *phy,
			struct phy_clk_params *clk_params,
			struct phy_timing_desc *desc)
{
	u64 multiplier = BIT(20);
	struct timing_entry *t = &desc->clk_post;
	int rc = 0;
	u64 dividend, temp;

	t->mipi_min = mult_frac(7, clk_params->tlpx_numer_ns,
			clk_params->bitclk_mbps);
	t->mipi_max = mult_frac(224, clk_params->tlpx_numer_ns,
			clk_params->bitclk_mbps);

	t->rec_min = DIV_ROUND_UP((t->mipi_min * clk_params->bitclk_mbps),
			(7 * clk_params->tlpx_numer_ns));
	t->rec_max = rounddown(
		mult_frac((t->mipi_max * clk_params->bitclk_mbps),
			  1, (7 * clk_params->tlpx_numer_ns)), 1);

	dividend = ((t->rec_max - t->rec_min) * clk_params->clk_post_buf
			* multiplier);
	temp  = roundup(div_s64(dividend, 100), multiplier);
	temp += (t->rec_min * multiplier);
	t->rec = div_s64(temp, multiplier);

	rc = dsi_phy_cmn_validate_and_set(t, "cphy_clk_post");

	DSI_DEBUG("CPHY_CLK_POST: rec_min=%d, rec_max=%d, reg_val=%d\n",
		t->rec_min, t->rec_max, t->reg_value);

	return rc;
}

/**
 * calc_cphy_hs_rqst - calculates cphy_hs_rqst parameter for cphy.
 */
static int calc_cphy_hs_rqst(struct dsi_phy_hw *phy,
			struct phy_clk_params *clk_params,
			struct phy_timing_desc *desc)
{
	u64 multiplier = BIT(20);
	struct timing_entry *t = &desc->hs_rqst;
	int rc = 0;
	u64 dividend, temp;

	t->rec_min = DIV_ROUND_UP(
		((t->mipi_min * clk_params->bitclk_mbps) -
		 (7 * clk_params->tlpx_numer_ns)),
		(7 * clk_params->tlpx_numer_ns));

	dividend = ((t->rec_max - t->rec_min) *
		clk_params->hs_rqst_buf * multiplier);
	temp  = roundup(div_s64(dividend, 100), multiplier);
	temp += t->rec_min * multiplier;
	t->rec = div_s64(temp, multiplier);

	rc = dsi_phy_cmn_validate_and_set(t, "cphy_hs_rqst");

	DSI_DEBUG("CPHY_HS_RQST: rec_min=%d, rec_max=%d, reg_val=%d\n",
		t->rec_min, t->rec_max, t->reg_value);

	return rc;
}

/**
 * calc_cphy_hs_exit - calculates cphy_hs_exit parameter for cphy.
 */
static int calc_cphy_hs_exit(struct dsi_phy_hw *phy,
			struct phy_clk_params *clk_params,
			struct phy_timing_desc *desc)
{
	int rc = 0;
	u64 multiplier = BIT(20);
	u64 dividend, temp;
	struct timing_entry *t = &desc->hs_exit;

	t->rec_min = (DIV_ROUND_UP(
			(t->mipi_min * clk_params->bitclk_mbps),
			(7 * clk_params->tlpx_numer_ns)) - 1);

	dividend = ((t->rec_max - t->rec_min) *
		clk_params->hs_exit_buf * multiplier);
	temp  = roundup(div_s64(dividend, 100), multiplier);
	temp += t->rec_min * multiplier;
	t->rec = div_s64(temp, multiplier);

	rc = dsi_phy_cmn_validate_and_set(t, "cphy_hs_exit");

	DSI_DEBUG("CPHY_HS_EXIT: rec_min=%d, rec_max=%d, reg_val=%d\n",
		t->rec_min, t->rec_max, t->reg_value);

	return rc;
}

/**
 * dsi_phy_calc_cphy_timing_params - calculates cphy timing parameters
 *					for a given bit clock
 */
static int dsi_phy_cmn_calc_cphy_timing_params(struct dsi_phy_hw *phy,
	struct phy_clk_params *clk_params, struct phy_timing_desc *desc)
{
	int rc = 0;

	rc = calc_cphy_clk_prepare(phy, clk_params, desc);
	if (rc) {
		DSI_ERR("clk_prepare calculations failed, rc=%d\n", rc);
		goto error;
	}

	rc = calc_cphy_clk_pre(phy, clk_params, desc);
	if (rc) {
		DSI_ERR("clk_pre calculations failed, rc=%d\n", rc);
		goto error;
	}

	rc = calc_cphy_clk_post(phy, clk_params, desc);
	if (rc) {
		DSI_ERR("clk_zero calculations failed, rc=%d\n", rc);
		goto error;
	}

	rc = calc_cphy_hs_rqst(phy, clk_params, desc);
	if (rc) {
		DSI_ERR("hs_rqst calculations failed, rc=%d\n", rc);
		goto error;
	}

	rc = calc_cphy_hs_exit(phy, clk_params, desc);
	if (rc) {
		DSI_ERR("hs_exit calculations failed, rc=%d\n", rc);
		goto error;
	}

error:
	return rc;
}

/**
 * calculate_timing_params() - calculates timing parameters.
 * @phy:      Pointer to DSI PHY hardware object.
 * @mode:     Mode information for which timing has to be calculated.
 * @config:   DSI host configuration for this mode.
 * @timing:   Timing parameters for each lane which will be returned.
 * @use_mode_bit_clk: Boolean to indicate whether reacalculate dsi
 *		bit clk or use the existing bit clk(for dynamic clk case).
 */
int dsi_phy_hw_calculate_timing_params(struct dsi_phy_hw *phy,
				       struct dsi_mode_info *mode,
				       struct dsi_host_common_cfg *host,
				       struct dsi_phy_per_lane_cfgs *timing,
				       bool use_mode_bit_clk)
{
	/* constants */
	u32 const esc_clk_mhz = 192; /* TODO: esc clock is hardcoded */
	u32 const esc_clk_mmss_cc_prediv = 10;
	u32 const tlpx_numer = 1000;
	u32 const tr_eot = 20;
	u32 const clk_prepare_spec_min = 38;
	u32 const clk_prepare_spec_max = 95;
	u32 const clk_trail_spec_min = 60;
	u32 const hs_exit_spec_min = 100;
	u32 const hs_exit_reco_max = 255;
	u32 const hs_rqst_spec_min = 50;
	u32 const hs_rqst_reco_max = 255;

	/* local vars */
	int rc = 0;
	u32 h_total, v_total;
	u32 inter_num;
	u32 num_of_lanes = 0;
	u32 bpp;
	u64 x, y;
	struct phy_timing_desc desc;
	struct phy_clk_params clk_params = {0};
	struct phy_timing_ops *ops = phy->ops.timing_ops;

	u32 phy_type = host->phy_type;

	memset(&desc, 0x0, sizeof(desc));
	h_total = DSI_H_TOTAL_DSC(mode);
	v_total = DSI_V_TOTAL(mode);

	bpp = bits_per_pixel[host->dst_format];

	inter_num = bpp * mode->refresh_rate;

	if (host->data_lanes & DSI_DATA_LANE_0)
		num_of_lanes++;
	if (host->data_lanes & DSI_DATA_LANE_1)
		num_of_lanes++;
	if (host->data_lanes & DSI_DATA_LANE_2)
		num_of_lanes++;
	if (host->data_lanes & DSI_DATA_LANE_3)
		num_of_lanes++;


	if (use_mode_bit_clk)
		x = mode->clk_rate_hz;
	else {
		x = mult_frac(v_total * h_total, inter_num, num_of_lanes);
		if (phy_type == DSI_PHY_TYPE_CPHY)
			x = mult_frac(x, 7, 16);
	}
	y = rounddown(x, 1);

	clk_params.bitclk_mbps = rounddown(DIV_ROUND_UP_ULL(y, 1000000), 1);
	clk_params.escclk_numer = esc_clk_mhz;
	clk_params.escclk_denom = esc_clk_mmss_cc_prediv;
	clk_params.tlpx_numer_ns = tlpx_numer;
	clk_params.treot_ns = tr_eot;


	/* Setup default parameters */
	desc.clk_prepare.mipi_min = clk_prepare_spec_min;
	desc.clk_prepare.mipi_max = clk_prepare_spec_max;
	desc.clk_trail.mipi_min = clk_trail_spec_min;
	desc.hs_exit.mipi_min = hs_exit_spec_min;
	desc.hs_exit.rec_max = hs_exit_reco_max;
	desc.hs_rqst.mipi_min = hs_rqst_spec_min;
	desc.hs_rqst_clk.mipi_min = hs_rqst_spec_min;
	desc.hs_rqst.rec_max = hs_rqst_reco_max;

	if (ops->get_default_phy_params) {
		ops->get_default_phy_params(&clk_params, phy_type);
	} else {
		rc = -EINVAL;
		goto error;
	}

	DSI_PHY_DBG(phy, "BIT CLOCK = %d, tlpx_numer_ns=%d, treot_ns=%d\n",
	       clk_params.bitclk_mbps, clk_params.tlpx_numer_ns,
	       clk_params.treot_ns);

	if (phy_type == DSI_PHY_TYPE_CPHY)
		rc = dsi_phy_cmn_calc_cphy_timing_params(phy, &clk_params,
							&desc);
	else
		rc = dsi_phy_cmn_calc_timing_params(phy, &clk_params, &desc);
	if (rc) {
		DSI_PHY_ERR(phy, "Timing calc failed, rc=%d\n", rc);
		goto error;
	}

	if (ops->update_timing_params) {
		ops->update_timing_params(timing, &desc, phy_type);
	} else {
		rc = -EINVAL;
		goto error;
	}

error:
	return rc;
}

int dsi_phy_timing_calc_init(struct dsi_phy_hw *phy,
			enum dsi_phy_version version)
{
	struct phy_timing_ops *ops = NULL;

	if (version == DSI_PHY_VERSION_UNKNOWN ||
	    version >= DSI_PHY_VERSION_MAX || !phy) {
		DSI_PHY_ERR(phy, "Unsupported version: %d\n", version);
		return -ENOTSUPP;
	}

	ops = kzalloc(sizeof(struct phy_timing_ops), GFP_KERNEL);
	if (!ops)
		return -EINVAL;
	phy->ops.timing_ops = ops;

	switch (version) {
	case DSI_PHY_VERSION_2_0:
		ops->get_default_phy_params =
			dsi_phy_hw_v2_0_get_default_phy_params;
		ops->calc_clk_zero =
			dsi_phy_hw_v2_0_calc_clk_zero;
		ops->calc_clk_trail_rec_min =
			dsi_phy_hw_v2_0_calc_clk_trail_rec_min;
		ops->calc_clk_trail_rec_max =
			dsi_phy_hw_v2_0_calc_clk_trail_rec_max;
		ops->calc_hs_zero =
			dsi_phy_hw_v2_0_calc_hs_zero;
		ops->calc_hs_trail =
			dsi_phy_hw_v2_0_calc_hs_trail;
		ops->update_timing_params =
			dsi_phy_hw_v2_0_update_timing_params;
		break;
	case DSI_PHY_VERSION_3_0:
		ops->get_default_phy_params =
			dsi_phy_hw_v3_0_get_default_phy_params;
		ops->calc_clk_zero =
			dsi_phy_hw_v3_0_calc_clk_zero;
		ops->calc_clk_trail_rec_min =
			dsi_phy_hw_v3_0_calc_clk_trail_rec_min;
		ops->calc_clk_trail_rec_max =
			dsi_phy_hw_v3_0_calc_clk_trail_rec_max;
		ops->calc_hs_zero =
			dsi_phy_hw_v3_0_calc_hs_zero;
		ops->calc_hs_trail =
			dsi_phy_hw_v3_0_calc_hs_trail;
		ops->update_timing_params =
			dsi_phy_hw_v3_0_update_timing_params;
		break;
	case DSI_PHY_VERSION_4_0:
	case DSI_PHY_VERSION_4_1:
		ops->get_default_phy_params =
			dsi_phy_hw_v4_0_get_default_phy_params;
		ops->calc_clk_zero =
			dsi_phy_hw_v4_0_calc_clk_zero;
		ops->calc_clk_trail_rec_min =
			dsi_phy_hw_v4_0_calc_clk_trail_rec_min;
		ops->calc_clk_trail_rec_max =
			dsi_phy_hw_v4_0_calc_clk_trail_rec_max;
		ops->calc_hs_zero =
			dsi_phy_hw_v4_0_calc_hs_zero;
		ops->calc_hs_trail =
			dsi_phy_hw_v4_0_calc_hs_trail;
		ops->update_timing_params =
			dsi_phy_hw_v4_0_update_timing_params;
		break;
	case DSI_PHY_VERSION_0_0_HPM:
	case DSI_PHY_VERSION_0_0_LPM:
	case DSI_PHY_VERSION_1_0:
	default:
		kfree(ops);
		return -ENOTSUPP;
	}

	return 0;
}

