/**
 * xilinx_drm_dp_link_train - Train the link
 * @dp: DisplayPort IP core structure
 *
 * Return: 0 if all trains are done successfully, or corresponding error code.
 */
static int xilinx_drm_dp_train(struct xilinx_drm_dp *dp)
{
	u32 reg;
	u8 bw_code = dp->mode.bw_code;
	u8 lane_cnt = dp->mode.lane_cnt;
	u8 aux_lane_cnt = lane_cnt;
	bool enhanced;
	int ret;

	xilinx_drm_writel(dp->iomem, XILINX_DP_TX_LANE_CNT_SET, lane_cnt);

	enhanced = drm_dp_enhanced_frame_cap(dp->dpcd);
	if (enhanced) {
		xilinx_drm_writel(dp->iomem, XILINX_DP_TX_ENHANCED_FRAME_EN, 1);
		aux_lane_cnt |= DP_LANE_COUNT_ENHANCED_FRAME_EN;
	}

	if (dp->dpcd[3] & 0x1) {
		xilinx_drm_writel(dp->iomem, XILINX_DP_TX_DOWNSPREAD_CTL, 1);
		drm_dp_dpcd_writeb(&dp->aux, DP_DOWNSPREAD_CTRL,
				   DP_SPREAD_AMP_0_5);
	} else {
		xilinx_drm_writel(dp->iomem, XILINX_DP_TX_DOWNSPREAD_CTL, 0);
		drm_dp_dpcd_writeb(&dp->aux, DP_DOWNSPREAD_CTRL, 0);
	}

	ret = drm_dp_dpcd_writeb(&dp->aux, DP_LANE_COUNT_SET, aux_lane_cnt);
	if (ret < 0) {
		DRM_ERROR("failed to set lane count\n");
		return ret;
	}

	ret = drm_dp_dpcd_writeb(&dp->aux, DP_MAIN_LINK_CHANNEL_CODING_SET,
				 DP_SET_ANSI_8B10B);
	if (ret < 0) {
		DRM_ERROR("failed to set ANSI 8B/10B encoding\n");
		return ret;
	}

	ret = drm_dp_dpcd_writeb(&dp->aux, DP_LINK_BW_SET, bw_code);
	if (ret < 0) {
		DRM_ERROR("failed to set DP bandwidth\n");
		return ret;
	}

	xilinx_drm_writel(dp->iomem, XILINX_DP_TX_LINK_BW_SET, bw_code);

	switch (bw_code) {
	case DP_LINK_BW_1_62:
		reg = XILINX_DP_TX_PHY_CLOCK_FEEDBACK_SETTING_162;
		break;
	case DP_LINK_BW_2_7:
		reg = XILINX_DP_TX_PHY_CLOCK_FEEDBACK_SETTING_270;
		break;
	case DP_LINK_BW_5_4:
	default:
		reg = XILINX_DP_TX_PHY_CLOCK_FEEDBACK_SETTING_540;
		break;
	}

	xilinx_drm_writel(dp->iomem, XILINX_DP_TX_PHY_CLOCK_FEEDBACK_SETTING,
			  reg);
	ret = xilinx_drm_dp_phy_ready(dp);
	if (ret < 0)
		return ret;

	xilinx_drm_writel(dp->iomem, XILINX_DP_TX_SCRAMBLING_DISABLE, 1);

	memset(dp->train_set, 0, 4);

	ret = xilinx_drm_dp_link_train_cr(dp);
	if (ret)
		return ret;

	ret = xilinx_drm_dp_link_train_ce(dp);
	if (ret)
		return ret;

	xilinx_drm_writel(dp->iomem, XILINX_DP_TX_TRAINING_PATTERN_SET,
			  DP_TRAINING_PATTERN_DISABLE);
	ret = drm_dp_dpcd_writeb(&dp->aux, DP_TRAINING_PATTERN_SET,
				 DP_TRAINING_PATTERN_DISABLE);
	if (ret < 0) {
		DRM_ERROR("failed to disable training pattern\n");
		return ret;
	}

	xilinx_drm_writel(dp->iomem, XILINX_DP_TX_SCRAMBLING_DISABLE, 0);

	return 0;
}
/**
 * xilinx_drm_dp_mode_set_stream - Configure the main stream
 * @dp: DisplayPort IP core structure
 * @mode: requested display mode
 *
 * Configure the main stream based on the requested mode @mode. Calculation is
 * based on IP core specification.
 */
static void xilinx_drm_dp_mode_set_stream(struct xilinx_drm_dp *dp,
        struct drm_display_mode *mode)
{
    u8 lane_cnt = dp->mode.lane_cnt;
    u32 reg, wpl;

    xilinx_drm_writel(dp->iomem, XILINX_DP_TX_MAIN_STREAM_HTOTAL,
                      mode->htotal);
    xilinx_drm_writel(dp->iomem, XILINX_DP_TX_MAIN_STREAM_VTOTAL,
                      mode->vtotal);

    xilinx_drm_writel(dp->iomem, XILINX_DP_TX_MAIN_STREAM_POLARITY,
                      (!!(mode->flags & DRM_MODE_FLAG_PVSYNC) <<
                       XILINX_DP_TX_MAIN_STREAM_POLARITY_VSYNC_SHIFT) |
                      (!!(mode->flags & DRM_MODE_FLAG_PHSYNC) <<
                       XILINX_DP_TX_MAIN_STREAM_POLARITY_HSYNC_SHIFT));

    xilinx_drm_writel(dp->iomem, XILINX_DP_TX_MAIN_STREAM_HSWIDTH,
                      mode->hsync_end - mode->hsync_start);
    xilinx_drm_writel(dp->iomem, XILINX_DP_TX_MAIN_STREAM_VSWIDTH,
                      mode->vsync_end - mode->vsync_start);

    xilinx_drm_writel(dp->iomem, XILINX_DP_TX_MAIN_STREAM_HRES,
                      mode->hdisplay);
    xilinx_drm_writel(dp->iomem, XILINX_DP_TX_MAIN_STREAM_VRES,
                      mode->vdisplay);

    xilinx_drm_writel(dp->iomem, XILINX_DP_TX_MAIN_STREAM_HSTART,
                      mode->htotal - mode->hsync_start);
    xilinx_drm_writel(dp->iomem, XILINX_DP_TX_MAIN_STREAM_VSTART,
                      mode->vtotal - mode->vsync_start);

    xilinx_drm_writel(dp->iomem, XILINX_DP_TX_MAIN_STREAM_MISC0,
                      dp->config.misc0);
    xilinx_drm_writel(dp->iomem, XILINX_DP_TX_MAIN_STREAM_MISC1,
                      dp->config.misc1);

    /* In synchronous mode, set the diviers */
    if (dp->config.misc0 & XILINX_DP_TX_MAIN_STREAM_MISC0_SYNC) {
        reg = drm_dp_bw_code_to_link_rate(dp->mode.bw_code);
        xilinx_drm_writel(dp->iomem, XILINX_DP_TX_N_VID, reg);
        xilinx_drm_writel(dp->iomem, XILINX_DP_TX_M_VID, mode->clock);
        if (dp->aud_clk) {
            int aud_rate = clk_get_rate(dp->aud_clk) / 512;

            if (aud_rate != 44100 && aud_rate != 48000)
                dev_dbg(dp->dev, "Audio rate: %d\n", aud_rate);

            xilinx_drm_writel(dp->iomem, XILINX_DP_TX_AUDIO_N_AUD,
                              reg);
            xilinx_drm_writel(dp->iomem, XILINX_DP_TX_AUDIO_M_AUD,
                              aud_rate);
        }
    }

    /* Only 2 channel is supported now */
    if (dp->aud_clk)
        xilinx_drm_writel(dp->iomem, XILINX_DP_TX_AUDIO_CHANNELS, 1);

    xilinx_drm_writel(dp->iomem, XILINX_DP_TX_USER_PIXEL_WIDTH, 1);

    /* Translate to the native 16 bit datapath based on IP core spec */
    wpl = (mode->hdisplay * dp->config.bpp + 15) / 16;
    reg = wpl + wpl % lane_cnt - lane_cnt;
    xilinx_drm_writel(dp->iomem, XILINX_DP_TX_USER_DATA_CNT_PER_LANE, reg);
}