/*
 * This file is part of the coreboot project.
 *
 * Copyright 2014 Google Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc.
 */
#include <console/console.h>
#include <arch/io.h>
#include <stdint.h>
#include <lib.h>
#include <stdlib.h>
#include <delay.h>
#include <timer.h>
#include <soc/addressmap.h>
#include <soc/clock.h>
#include <device/device.h>
#include <edid.h>
#include <soc/nvidia/tegra/types.h>
#include <soc/nvidia/tegra/dc.h>
#include "chip.h"
#include <soc/display.h>
#include <soc/mipi_dsi.h>
#include <soc/mipi_display.h>
#include <soc/tegra_dsi.h>
#include <soc/mipi-phy.h>
#include "jdi_25x18_display/panel-jdi-lpm102a188a.h"

struct tegra_mipi_device mipi_device_data[NUM_DSI];

struct tegra_dsi dsi_data[NUM_DSI] = {
	{
		.regs = (void *)TEGRA_DSIA_BASE,
		.channel = 0,
		.slave = &dsi_data[DSI_B],
		.master = NULL,
		.video_fifo_depth = MAX_DSI_VIDEO_FIFO_DEPTH,
		.host_fifo_depth = MAX_DSI_HOST_FIFO_DEPTH,
	},
	{
		.regs = (void *)TEGRA_DSIB_BASE,
		.channel = 0,
		.slave = NULL,
		.master = &dsi_data[DSI_A],
		.video_fifo_depth = MAX_DSI_VIDEO_FIFO_DEPTH,
		.host_fifo_depth = MAX_DSI_HOST_FIFO_DEPTH,
	},
};

static inline struct tegra_dsi *host_to_tegra(struct mipi_dsi_host *host)
{
	return container_of(host, struct tegra_dsi, host);
}

/*
 * non-burst mode with sync pulses
 */
static const u32 pkt_seq_video_non_burst_sync_pulses[NUM_PKT_SEQ] = {
	[ 0] = PKT_ID0(MIPI_DSI_V_SYNC_START) | PKT_LEN0(0) |
	       PKT_ID1(MIPI_DSI_BLANKING_PACKET) | PKT_LEN1(1) |
	       PKT_ID2(MIPI_DSI_H_SYNC_END) | PKT_LEN2(0) |
	       PKT_LP,
	[ 1] = 0,
	[ 2] = PKT_ID0(MIPI_DSI_V_SYNC_END) | PKT_LEN0(0) |
	       PKT_ID1(MIPI_DSI_BLANKING_PACKET) | PKT_LEN1(1) |
	       PKT_ID2(MIPI_DSI_H_SYNC_END) | PKT_LEN2(0) |
	       PKT_LP,
	[ 3] = 0,
	[ 4] = PKT_ID0(MIPI_DSI_H_SYNC_START) | PKT_LEN0(0) |
	       PKT_ID1(MIPI_DSI_BLANKING_PACKET) | PKT_LEN1(1) |
	       PKT_ID2(MIPI_DSI_H_SYNC_END) | PKT_LEN2(0) |
	       PKT_LP,
	[ 5] = 0,
	[ 6] = PKT_ID0(MIPI_DSI_H_SYNC_START) | PKT_LEN0(0) |
	       PKT_ID1(MIPI_DSI_BLANKING_PACKET) | PKT_LEN1(1) |
	       PKT_ID2(MIPI_DSI_H_SYNC_END) | PKT_LEN2(0),
	[ 7] = PKT_ID0(MIPI_DSI_BLANKING_PACKET) | PKT_LEN0(2) |
	       PKT_ID1(MIPI_DSI_PACKED_PIXEL_STREAM_24) | PKT_LEN1(3) |
	       PKT_ID2(MIPI_DSI_BLANKING_PACKET) | PKT_LEN2(4),
	[ 8] = PKT_ID0(MIPI_DSI_H_SYNC_START) | PKT_LEN0(0) |
	       PKT_ID1(MIPI_DSI_BLANKING_PACKET) | PKT_LEN1(1) |
	       PKT_ID2(MIPI_DSI_H_SYNC_END) | PKT_LEN2(0) |
	       PKT_LP,
	[ 9] = 0,
	[10] = PKT_ID0(MIPI_DSI_H_SYNC_START) | PKT_LEN0(0) |
	       PKT_ID1(MIPI_DSI_BLANKING_PACKET) | PKT_LEN1(1) |
	       PKT_ID2(MIPI_DSI_H_SYNC_END) | PKT_LEN2(0),
	[11] = PKT_ID0(MIPI_DSI_BLANKING_PACKET) | PKT_LEN0(2) |
	       PKT_ID1(MIPI_DSI_PACKED_PIXEL_STREAM_24) | PKT_LEN1(3) |
	       PKT_ID2(MIPI_DSI_BLANKING_PACKET) | PKT_LEN2(4),
};

/*
 * non-burst mode with sync events
 */
static const u32 pkt_seq_video_non_burst_sync_events[NUM_PKT_SEQ] = {
	[ 0] = PKT_ID0(MIPI_DSI_V_SYNC_START) | PKT_LEN0(0) |
	       PKT_ID1(MIPI_DSI_END_OF_TRANSMISSION) | PKT_LEN1(7) |
	       PKT_LP,
	[ 1] = 0,
	[ 2] = PKT_ID0(MIPI_DSI_H_SYNC_START) | PKT_LEN0(0) |
	       PKT_ID1(MIPI_DSI_END_OF_TRANSMISSION) | PKT_LEN1(7) |
	       PKT_LP,
	[ 3] = 0,
	[ 4] = PKT_ID0(MIPI_DSI_H_SYNC_START) | PKT_LEN0(0) |
	       PKT_ID1(MIPI_DSI_END_OF_TRANSMISSION) | PKT_LEN1(7) |
	       PKT_LP,
	[ 5] = 0,

	[ 6] = PKT_ID0(MIPI_DSI_H_SYNC_START) | PKT_LEN0(0) |
	       PKT_ID1(MIPI_DSI_BLANKING_PACKET) | PKT_LEN1(2) |
	       PKT_ID2(MIPI_DSI_PACKED_PIXEL_STREAM_24) | PKT_LEN2(3),

	[ 7] = PKT_ID0(MIPI_DSI_BLANKING_PACKET) | PKT_LEN0(4),
	[ 8] = PKT_ID0(MIPI_DSI_H_SYNC_START) | PKT_LEN0(0) |
	       PKT_ID1(MIPI_DSI_END_OF_TRANSMISSION) | PKT_LEN1(7) |
	       PKT_LP,
	[ 9] = 0,

	[10] = PKT_ID0(MIPI_DSI_H_SYNC_START) | PKT_LEN0(0) |
	       PKT_ID1(MIPI_DSI_BLANKING_PACKET) | PKT_LEN1(2) |
	       PKT_ID2(MIPI_DSI_PACKED_PIXEL_STREAM_24) | PKT_LEN2(3),

	[11] = PKT_ID0(MIPI_DSI_BLANKING_PACKET) | PKT_LEN0(4),
};

static const u32 pkt_seq_command_mode[NUM_PKT_SEQ] = {
	[ 0] = 0,
	[ 1] = 0,
	[ 2] = 0,
	[ 3] = 0,
	[ 4] = 0,
	[ 5] = 0,
	[ 6] = PKT_ID0(MIPI_DSI_DCS_LONG_WRITE) | PKT_LEN0(3) | PKT_LP,
	[ 7] = 0,
	[ 8] = 0,
	[ 9] = 0,
	[10] = PKT_ID0(MIPI_DSI_DCS_LONG_WRITE) | PKT_LEN0(5) | PKT_LP,
	[11] = 0,
};

static int tegra_dsi_set_phy_timing(struct tegra_dsi *dsi)
{
	int err;

	err = mipi_dphy_set_timing(dsi);
	if (err < 0) {
		printk(BIOS_ERR, "failed to set D-PHY timing: %d\n", err);
		return err;
	}

	if (dsi->slave)
		tegra_dsi_set_phy_timing(dsi->slave);
	return 0;
}

static int tegra_dsi_get_muldiv(enum mipi_dsi_pixel_format format,
				unsigned int *mulp, unsigned int *divp)
{
	switch (format) {
	case MIPI_DSI_FMT_RGB666_PACKED:
	case MIPI_DSI_FMT_RGB888:
		*mulp = 3;
		*divp = 1;
		break;

	case MIPI_DSI_FMT_RGB565:
		*mulp = 2;
		*divp = 1;
		break;

	case MIPI_DSI_FMT_RGB666:
		*mulp = 9;
		*divp = 4;
		break;

	default:
		return -EINVAL;
	}
	return 0;
}

static int tegra_dsi_get_format(enum mipi_dsi_pixel_format format,
				enum tegra_dsi_format *fmt)
{
	switch (format) {
	case MIPI_DSI_FMT_RGB888:
		*fmt = TEGRA_DSI_FORMAT_24P;
		break;

	case MIPI_DSI_FMT_RGB666:
		*fmt = TEGRA_DSI_FORMAT_18NP;
		break;

	case MIPI_DSI_FMT_RGB666_PACKED:
		*fmt = TEGRA_DSI_FORMAT_18P;
		break;

	case MIPI_DSI_FMT_RGB565:
		*fmt = TEGRA_DSI_FORMAT_16P;
		break;

	default:
		return -EINVAL;
	}
	return 0;
}

static void tegra_dsi_ganged_enable(struct tegra_dsi *dsi, unsigned int start,
				    unsigned int size)
{
	u32 value;

	tegra_dsi_writel(dsi, start, DSI_GANGED_MODE_START);
	tegra_dsi_writel(dsi, size << 16 | size, DSI_GANGED_MODE_SIZE);

	value = DSI_GANGED_MODE_CONTROL_ENABLE;
	tegra_dsi_writel(dsi, value, DSI_GANGED_MODE_CONTROL);
}

static void tegra_dsi_enable(struct tegra_dsi *dsi)
{
	u32 value;

	value = tegra_dsi_readl(dsi, DSI_POWER_CONTROL);
	value |= DSI_POWER_CONTROL_ENABLE;
	tegra_dsi_writel(dsi, value, DSI_POWER_CONTROL);

	if (dsi->slave)
		tegra_dsi_enable(dsi->slave);
}

static int tegra_dsi_configure(struct tegra_dsi *dsi, unsigned int pipe,
			const struct soc_nvidia_tegra132_config *mode)
{
	unsigned int hact, hsw, hbp, hfp, i, mul, div;
	enum tegra_dsi_format format;
	const u32 *pkt_seq;
	u32 value;
	int err;

	if (dsi->flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) {
		printk(BIOS_SPEW, "Non-burst video mode with sync pulses\n");
		pkt_seq = pkt_seq_video_non_burst_sync_pulses;
	} else if (dsi->flags & MIPI_DSI_MODE_VIDEO) {
		printk(BIOS_SPEW, "Non-burst video mode with sync events\n");
		pkt_seq = pkt_seq_video_non_burst_sync_events;
	} else {
		printk(BIOS_SPEW, "Command mode\n");
		pkt_seq = pkt_seq_command_mode;
	}

	err = tegra_dsi_get_muldiv(dsi->format, &mul, &div);
	if (err < 0)
		return err;

	err = tegra_dsi_get_format(dsi->format, &format);
	if (err < 0)
		return err;

	value = DSI_CONTROL_CHANNEL(0) | DSI_CONTROL_FORMAT(format) |
		DSI_CONTROL_LANES(dsi->lanes - 1) |
		DSI_CONTROL_SOURCE(pipe);
	tegra_dsi_writel(dsi, value, DSI_CONTROL);

	tegra_dsi_writel(dsi, dsi->video_fifo_depth, DSI_MAX_THRESHOLD);

	value = DSI_HOST_CONTROL_HS;
	tegra_dsi_writel(dsi, value, DSI_HOST_CONTROL);

	value = tegra_dsi_readl(dsi, DSI_CONTROL);

	if (dsi->flags & MIPI_DSI_CLOCK_NON_CONTINUOUS)
		value |= DSI_CONTROL_HS_CLK_CTRL;

	value &= ~DSI_CONTROL_TX_TRIG(3);

	/* enable DCS commands for command mode */
	if (dsi->flags & MIPI_DSI_MODE_VIDEO)
		value &= ~DSI_CONTROL_DCS_ENABLE;
	else
		value |= DSI_CONTROL_DCS_ENABLE;

	value |= DSI_CONTROL_VIDEO_ENABLE;
	value &= ~DSI_CONTROL_HOST_ENABLE;
	tegra_dsi_writel(dsi, value, DSI_CONTROL);

	for (i = 0; i < NUM_PKT_SEQ; i++)
		tegra_dsi_writel(dsi, pkt_seq[i], DSI_PKT_SEQ_0_LO + i);

	if (dsi->flags & MIPI_DSI_MODE_VIDEO) {
		/* horizontal active pixels */
		hact = mode->xres * mul / div;

		/* horizontal sync width */
		hsw = (hsync_end(mode) - hsync_start(mode)) * mul / div;
		hsw -= 10;

		/* horizontal back porch */
		hbp = (htotal(mode) - hsync_end(mode)) * mul / div;
		hbp -= 14;

		/* horizontal front porch */
		hfp = (hsync_start(mode) - mode->xres) * mul / div;
		hfp -= 8;

		tegra_dsi_writel(dsi, hsw << 16 | 0, DSI_PKT_LEN_0_1);
		tegra_dsi_writel(dsi, hact << 16 | hbp, DSI_PKT_LEN_2_3);
		tegra_dsi_writel(dsi, hfp, DSI_PKT_LEN_4_5);
		tegra_dsi_writel(dsi, 0x0f0f << 16, DSI_PKT_LEN_6_7);

		/* set SOL delay (for non-burst mode only) */
		tegra_dsi_writel(dsi, 8 * mul / div, DSI_SOL_DELAY);

		/* TODO: implement ganged mode */
	} else {
		u16 bytes;
		if (dsi->ganged_mode) {
			/*
			 * For ganged mode, assume symmetric left-right mode.
			 */
			bytes = 1 + (mode->xres / 2) * mul / div;
		} else {
			/* 1 byte (DCS command) + pixel data */
			bytes = 1 + mode->xres * mul / div;
		}

		tegra_dsi_writel(dsi, 0, DSI_PKT_LEN_0_1);
		tegra_dsi_writel(dsi, bytes << 16, DSI_PKT_LEN_2_3);
		tegra_dsi_writel(dsi, bytes << 16, DSI_PKT_LEN_4_5);
		tegra_dsi_writel(dsi, 0, DSI_PKT_LEN_6_7);

		value = MIPI_DCS_WRITE_MEMORY_START << 8 |
			MIPI_DCS_WRITE_MEMORY_CONTINUE;
		tegra_dsi_writel(dsi, value, DSI_DCS_CMDS);

		/* set SOL delay */
		if (dsi->ganged_mode) {
			unsigned long delay, bclk, bclk_ganged;
			unsigned int lanes = dsi->ganged_lanes;

			/* SOL to valid, valid to FIFO and FIFO write delay */
			delay = 4 + 4 + 2;
			delay = DIV_ROUND_UP(delay * mul, div * lanes);
			/* FIFO read delay */
			delay = delay + 6;

			bclk = DIV_ROUND_UP(htotal(mode) * mul, div * lanes);
			bclk_ganged = DIV_ROUND_UP(bclk * lanes / 2, lanes);
			value = bclk - bclk_ganged + delay + 20;
		} else {
			/* TODO: revisit for non-ganged mode */
			value = 8 * mul / div;
		}

		tegra_dsi_writel(dsi, value, DSI_SOL_DELAY);
	}

	if (dsi->slave) {
		err = tegra_dsi_configure(dsi->slave, pipe, mode);
		if (err < 0)
			return err;

		/*
		 * enable ganged mode
		 */
		if (dsi->ganged_mode) {
			tegra_dsi_ganged_enable(dsi, mode->xres / 2,
					mode->xres / 2);
			tegra_dsi_ganged_enable(dsi->slave, 0, mode->xres / 2);
		}
	}
	return 0;
}

static int tegra_output_dsi_enable(struct tegra_dsi *dsi,
			const struct soc_nvidia_tegra132_config *config)
{
	int err;

	if (dsi->enabled)
		return 0;

	err = tegra_dsi_configure(dsi, 0, config);
	if (err < 0)
		return err;

	/* enable DSI controller */
	tegra_dsi_enable(dsi);

	dsi->enabled = true;
	return 0;
}


static void tegra_dsi_set_timeout(struct tegra_dsi *dsi, unsigned long bclk,
				  unsigned int vrefresh)
{
	unsigned int timeout;
	u32 value;

	/* one frame high-speed transmission timeout */
	timeout = (bclk / vrefresh) / 512;
	value = DSI_TIMEOUT_LRX(0x2000) | DSI_TIMEOUT_HTX(timeout);
	tegra_dsi_writel(dsi, value, DSI_TIMEOUT_0);

	/* 2 ms peripheral timeout for panel */
	timeout = 2 * bclk / 512 * 1000;
	value = DSI_TIMEOUT_PR(timeout) | DSI_TIMEOUT_TA(0x2000);
	tegra_dsi_writel(dsi, value, DSI_TIMEOUT_1);

	value = DSI_TALLY_TA(0) | DSI_TALLY_LRX(0) | DSI_TALLY_HTX(0);
	tegra_dsi_writel(dsi, value, DSI_TO_TALLY);

	if (dsi->slave)
		tegra_dsi_set_timeout(dsi->slave, bclk, vrefresh);
}

static int tegra_output_dsi_setup_clock(struct tegra_dsi *dsi,
			const struct soc_nvidia_tegra132_config *config)
{
	unsigned int mul, div, num_lanes;
	unsigned long bclk;
	unsigned long pclk = config->pixel_clock;
	int plld;
	int err;
	struct display_controller *disp_ctrl =
			(void *)config->display_controller;
	unsigned int shift_clk_div;

	err = tegra_dsi_get_muldiv(dsi->format, &mul, &div);
	if (err < 0)
		return err;

	/*
	 * In ganged mode, account for the total number of lanes across both
	 * DSI channels so that the bit clock is properly computed.
	 */
	if (dsi->ganged_mode)
		num_lanes = dsi->ganged_lanes;
	else
		num_lanes = dsi->lanes;

	/* compute byte clock */
	bclk = (pclk * mul) / (div * num_lanes);

	/*
	 * Compute bit clock and round up to the next MHz.
	 */
	plld = DIV_ROUND_UP(bclk * 8, USECS_PER_SEC) * USECS_PER_SEC;

	/*
	 * the actual rate on PLLD_OUT0 is 1/2 plld
	 */
	dsi->clk_rate = plld / 2;
	if (dsi->slave)
		dsi->slave->clk_rate = dsi->clk_rate;

	/* set up plld */
	plld = clock_configure_plld(plld);
	if (plld == 0) {
                printk(BIOS_ERR, "%s: clock init failed\n", __func__);
                return -1;
        }

	/*
	 * Derive pixel clock from bit clock using the shift clock divider.
	 * Note that this is only half of what we would expect, but we need
	 * that to make up for the fact that we divided the bit clock by a
	 * factor of two above.
	 */
	shift_clk_div = ((8 * mul) / (div * num_lanes)) - 2;
	update_display_shift_clock_divider(disp_ctrl, shift_clk_div);

	tegra_dsi_set_timeout(dsi, bclk, config->refresh);
	return plld/1000000;
}



static int tegra_dsi_pad_enable(struct tegra_dsi *dsi)
{
	unsigned long value;

	value = DSI_PAD_CONTROL_VS1_PULLDN(0) | DSI_PAD_CONTROL_VS1_PDIO(0);
	tegra_dsi_writel(dsi, value, DSI_PAD_CONTROL_0);
	return 0;
}

static int tegra_dsi_pad_calibrate(struct tegra_dsi *dsi)
{
	u32 value;

	tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_0);
	tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_1);
	tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_2);
	tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_3);
	tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_4);

	/* start calibration */
	tegra_dsi_pad_enable(dsi);

	value = DSI_PAD_SLEW_UP(0x7) | DSI_PAD_SLEW_DN(0x7) |
		DSI_PAD_LP_UP(0x1) | DSI_PAD_LP_DN(0x1) |
		DSI_PAD_OUT_CLK(0x0);
	tegra_dsi_writel(dsi, value, DSI_PAD_CONTROL_2);

	return tegra_mipi_calibrate(dsi->mipi);
}

static const char * const error_report[16] = {
	"SoT Error",
	"SoT Sync Error",
	"EoT Sync Error",
	"Escape Mode Entry Command Error",
	"Low-Power Transmit Sync Error",
	"Peripheral Timeout Error",
	"False Control Error",
	"Contention Detected",
	"ECC Error, single-bit",
	"ECC Error, multi-bit",
	"Checksum Error",
	"DSI Data Type Not Recognized",
	"DSI VC ID Invalid",
	"Invalid Transmission Length",
	"Reserved",
	"DSI Protocol Violation",
};

static int tegra_dsi_read_response(struct tegra_dsi *dsi,
				   const struct mipi_dsi_msg *msg,
				   unsigned int count)
{
	u8 *rx = msg->rx_buf;
	unsigned int i, j, k;
	size_t size = 0;
	u16 errors;
	u32 value;

	/* read and parse packet header */
	value = tegra_dsi_readl(dsi, DSI_RD_DATA);

	switch (value & 0x3f) {
	case MIPI_DSI_RX_ACKNOWLEDGE_AND_ERROR_REPORT:
		errors = (value >> 8) & 0xffff;
		printk(BIOS_ERR, "Acknowledge and error report: %04x\n",
			errors);
		for (i = 0; i < ARRAY_SIZE(error_report); i++)
			if (errors & BIT(i))
				printk(BIOS_INFO, "  %2u: %s\n", i,
					error_report[i]);
		break;

	case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_1BYTE:
		rx[0] = (value >> 8) & 0xff;
		break;

	case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_2BYTE:
		rx[0] = (value >>  8) & 0xff;
		rx[1] = (value >> 16) & 0xff;
		break;

	case MIPI_DSI_RX_DCS_LONG_READ_RESPONSE:
		size = ((value >> 8) & 0xff00) | ((value >> 8) & 0xff);
		break;

	case MIPI_DSI_RX_GENERIC_LONG_READ_RESPONSE:
		size = ((value >> 8) & 0xff00) | ((value >> 8) & 0xff);
		break;

	default:
		printk(BIOS_ERR, "unhandled response type: %02x\n",
			value & 0x3f);
		break;
	}

	size = MIN(size, msg->rx_len);

	if (msg->rx_buf && size > 0) {
		for (i = 0, j = 0; i < count - 1; i++, j += 4) {
			value = tegra_dsi_readl(dsi, DSI_RD_DATA);

			for (k = 0; k < 4 && (j + k) < msg->rx_len; k++)
				rx[j + k] = (value >> (k << 3)) & 0xff;
		}
	}
	return 0;
}

static int tegra_dsi_transmit(struct tegra_dsi *dsi, unsigned long timeout_ms)
{
	u32 poll_interval_us = 2000;
	u32 timeout_us = timeout_ms * 1000;

	tegra_dsi_writel(dsi, DSI_TRIGGER_HOST, DSI_TRIGGER);
	udelay(poll_interval_us);

	do {
		u32 value = tegra_dsi_readl(dsi, DSI_TRIGGER);
		if ((value & DSI_TRIGGER_HOST) == 0)
			return 0;

		//usleep_range(1000, 2000);
		if (timeout_us > poll_interval_us)
			timeout_us -= poll_interval_us;
		else
			break;

		udelay(poll_interval_us);
	} while (1);

	printk(BIOS_ERR, "%s: ERROR: timeout waiting for transmission"
			" to complete\n", __func__);
	return -ETIMEDOUT;
}

static int tegra_dsi_wait_for_response(struct tegra_dsi *dsi,
				       unsigned long timeout_ms)
{
	u32 poll_interval_us = 2000;
	u32 timeout_us = timeout_ms * 1000;

	do {
		u32 value = tegra_dsi_readl(dsi, DSI_STATUS);
		u8 count = value & 0x1f;

		if (count > 0)
			return count;

		if (timeout_us > poll_interval_us)
			timeout_us -= poll_interval_us;
		else
			break;

		udelay(poll_interval_us);
	} while (1);

	printk(BIOS_ERR, "%s: ERROR: timeout\n", __func__);

	return -ETIMEDOUT;
}

static ssize_t tegra_dsi_host_transfer(struct mipi_dsi_host *host,
				const struct mipi_dsi_msg *msg)
{
	struct tegra_dsi *dsi = host_to_tegra(host);
	const u8 *tx = msg->tx_buf;
	unsigned int count, i, j;
	u32 value;
	int err;

	if (msg->tx_len > dsi->video_fifo_depth * 4)
		return -ENOSPC;

	/* reset underflow/overflow flags */
	value = tegra_dsi_readl(dsi, DSI_STATUS);
	if (value & (DSI_STATUS_UNDERFLOW | DSI_STATUS_OVERFLOW)) {
		value = DSI_HOST_CONTROL_FIFO_RESET;
		tegra_dsi_writel(dsi, value, DSI_HOST_CONTROL);
		udelay(20);	// usleep_range(10, 20);
	}

	value = tegra_dsi_readl(dsi, DSI_POWER_CONTROL);
	value |= DSI_POWER_CONTROL_ENABLE;
	tegra_dsi_writel(dsi, value, DSI_POWER_CONTROL);

	udelay(7000);	//usleep_range(5000, 10000);

	value = DSI_HOST_CONTROL_CRC_RESET | DSI_HOST_CONTROL_TX_TRIG_HOST |
		DSI_HOST_CONTROL_CS | DSI_HOST_CONTROL_ECC;

	if ((msg->flags & MIPI_DSI_MSG_USE_LPM) == 0)
		value |= DSI_HOST_CONTROL_HS;

	/*
	 * The host FIFO has a maximum of 64 words, so larger transmissions
	 * need to use the video FIFO.
	 */
	if (msg->tx_len > dsi->host_fifo_depth * 4)
		value |= DSI_HOST_CONTROL_FIFO_SEL;

	tegra_dsi_writel(dsi, value, DSI_HOST_CONTROL);

	/*
	 * For reads and messages with explicitly requested ACK, generate a
	 * BTA sequence after the transmission of the packet.
	 */
	if ((msg->flags & MIPI_DSI_MSG_REQ_ACK) ||
	    (msg->rx_buf && msg->rx_len > 0)) {
		value = tegra_dsi_readl(dsi, DSI_HOST_CONTROL);
		value |= DSI_HOST_CONTROL_PKT_BTA;
		tegra_dsi_writel(dsi, value, DSI_HOST_CONTROL);
	}

	value = DSI_CONTROL_LANES(0) | DSI_CONTROL_HOST_ENABLE;
	tegra_dsi_writel(dsi, value, DSI_CONTROL);

	/* write packet header */
	value = ((msg->channel & 0x3) << 6) | (msg->type & 0x3f);

	if (tx && msg->tx_len > 0)
		value |= tx[0] <<  8;

	if (tx && msg->tx_len > 1)
		value |= tx[1] << 16;

	tegra_dsi_writel(dsi, value, DSI_WR_DATA);

	/* write payload (if any) */
	if (msg->tx_len > 2) {
		for (j = 2; j < msg->tx_len; j += 4) {
			value = 0;

			for (i = 0; i < 4 && j + i < msg->tx_len; i++)
				value |= tx[j + i] << (i << 3);

			tegra_dsi_writel(dsi, value, DSI_WR_DATA);
		}
	}

	err = tegra_dsi_transmit(dsi, 250);
	if (err < 0)
		return err;

	if ((msg->flags & MIPI_DSI_MSG_REQ_ACK) ||
	    (msg->rx_buf && msg->rx_len > 0)) {
		err = tegra_dsi_wait_for_response(dsi, 250);
		if (err < 0)
			return err;

		count = err;

		value = tegra_dsi_readl(dsi, DSI_RD_DATA);
		switch (value) {
		case 0x84:
			/*
			dev_dbg(dsi->dev, "ACK\n");
			*/
			break;

		case 0x87:
			/*
			dev_dbg(dsi->dev, "ESCAPE\n");
			*/
			break;

		default:
			printk(BIOS_INFO, "unknown status: %08x\n", value);
			break;
		}

		if (count > 1) {
			err = tegra_dsi_read_response(dsi, msg, count);
			if (err < 0)
				printk(BIOS_INFO,
					"failed to parse response: %d\n",
					err);
		}
	}
	return 0;
}

static int tegra_dsi_ganged_setup(struct tegra_dsi *dsi,
			  struct tegra_dsi *slave)
{
	/*
	 * The number of ganged lanes is the sum of lanes of all peripherals
	 * in the gang.
	 */
	dsi->slave->ganged_lanes = dsi->lanes + dsi->slave->lanes;
	dsi->slave->ganged_mode = 1;

	dsi->ganged_lanes = dsi->lanes + dsi->slave->lanes;
	dsi->ganged_mode = 1;
	return 0;
}

static int tegra_dsi_host_attach(struct mipi_dsi_host *host,
				struct mipi_dsi_device *device)
{
	struct tegra_dsi *dsi = host_to_tegra(host);
	int err;

	dsi->flags = device->mode_flags;
	dsi->format = device->format;
	dsi->lanes = device->lanes;

	if (dsi->master) {
		err = tegra_dsi_ganged_setup(dsi->master, dsi);
		if (err < 0) {
			printk(BIOS_ERR, "failed to set up ganged mode: %d\n",
				err);
			return err;
		}
	}
	return 0;
}

static const struct mipi_dsi_host_ops tegra_dsi_host_ops = {
	.attach = tegra_dsi_host_attach,
	.transfer = tegra_dsi_host_transfer,
};

static int dsi_probe_if(int dsi_index,
			struct soc_nvidia_tegra132_config *config)
{
	struct tegra_dsi *dsi = &dsi_data[dsi_index];
	int err;

	/*
	 * Set default value. Will be taken from attached device once detected
	 */
        dsi->flags = 0;
        dsi->format = MIPI_DSI_FMT_RGB888;
        dsi->lanes = 4;

	/* get tegra_mipi_device */
        dsi->mipi = tegra_mipi_request(&mipi_device_data[dsi_index], dsi_index);

	/* calibrate */
	err = tegra_dsi_pad_calibrate(dsi);
	if (err < 0) {
		printk(BIOS_ERR, "MIPI calibration failed: %d\n", err);
		return err;
	}

	dsi->host.ops = &tegra_dsi_host_ops;
	err = mipi_dsi_host_register(&dsi->host);
	if (err < 0) {
		printk(BIOS_ERR, "failed to register DSI host: %d\n", err);
		return err;
	}

	/* get panel */
	dsi->panel = panel_jdi_dsi_probe((struct mipi_dsi_device *)dsi->host.dev);
	if (IS_ERR_PTR(dsi->panel)) {
		printk(BIOS_ERR, "failed to get dsi panel\n");
		return -EPTR;
	}
	dsi->panel->mode = config;
	return 0;
}

static int dsi_probe(struct soc_nvidia_tegra132_config *config)
{
	dsi_probe_if(DSI_A, config);
	dsi_probe_if(DSI_B, config);
	return 0;
}

static int dsi_enable(struct soc_nvidia_tegra132_config *config)
{
	struct tegra_dsi *dsi_a = &dsi_data[DSI_A];

	dsi_probe(config);

	/* set up clock and TimeOutRegisters */
	tegra_output_dsi_setup_clock(dsi_a, config);

	/* configure APB_MISC_GP_MIPI_PAD_CTRL_0 */
	write32((unsigned int *)APB_MISC_GP_MIPI_PAD_CTRL_0, DSIB_MODE_DSI);

	/* configure phy interface timing registers */
	tegra_dsi_set_phy_timing(dsi_a);

	/* prepare panel */
	panel_jdi_prepare(dsi_a->panel);

	/* enable dsi */
	if (tegra_output_dsi_enable(dsi_a, config)) {
		printk(BIOS_ERR,"%s: Error: failed to enable dsi output.\n",
			__func__);
		return -1;
	}

	return 0;
}

void dsi_display_startup(device_t dev)
{
	struct soc_nvidia_tegra132_config *config = dev->chip_info;
	struct display_controller *disp_ctrl =
			(void *)config->display_controller;
	u32 plld_rate;

	u32 framebuffer_size_mb = config->framebuffer_size / MiB;
	u32 framebuffer_base_mb= config->framebuffer_base / MiB;

	printk(BIOS_INFO, "%s: entry: disp_ctrl: %p.\n",
		 __func__, disp_ctrl);

	if (disp_ctrl == NULL) {
		printk(BIOS_ERR, "Error: No dc is assigned by dt.\n");
		return;
	}

	if (framebuffer_size_mb == 0){
		framebuffer_size_mb = ALIGN_UP(config->display_xres *
			config->display_yres *
			(config->framebuffer_bits_per_pixel / 8), MiB)/MiB;
	}

	config->framebuffer_size = framebuffer_size_mb * MiB;
	config->framebuffer_base = framebuffer_base_mb * MiB;

	/*
	 * The plld is programmed with the assumption of the SHIFT_CLK_DIVIDER
	 * and PIXEL_CLK_DIVIDER are zero (divide by 1). See the
	 * update_display_mode() for detail.
	 */
	/* set default plld */
	plld_rate = clock_configure_plld(config->pixel_clock * 2);
	if (plld_rate == 0) {
		printk(BIOS_ERR, "dc: clock init failed\n");
		return;
	}

	/* set disp1's clock source to PLLD_OUT0 */
	clock_configure_source(disp1, PLLD, (plld_rate/KHz)/2);

	/* Init dc */
	if (tegra_dc_init(disp_ctrl)) {
		printk(BIOS_ERR, "dc: init failed\n");
		return;
	}

	/* Configure dc mode */
	if (update_display_mode(disp_ctrl, config)) {
		printk(BIOS_ERR, "dc: failed to configure display mode.\n");
		return;
	}

	/* Configure and enable dsi controller and panel */
	if (dsi_enable(config)) {
		printk(BIOS_ERR, "%s: failed to enable dsi controllers.\n",
			__func__);
		return;
	}

	/* Set up window */
	update_window(config);
	printk(BIOS_INFO, "%s: display init done.\n", __func__);

	/* Save panel information to cb tables */
	pass_mode_info_to_payload(config);

	/*
	 * After this point, it is payload's responsibility to allocate
	 * framebuffer and sets the base address to dc's
	 * WINBUF_START_ADDR register and enables window by setting dc's
	 * DISP_DISP_WIN_OPTIONS register.
	 */
}
