/*
 * Copyright © 2010 Intel Corporation
 *
 * 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 (including the next
 * paragraph) 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 AUTHORS OR COPYRIGHT HOLDERS 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:
 * jim liu <jim.liu@intel.com>
 * Jackie Li<yaodong.li@intel.com>
 */

#include "mdfld_dsi_output.h"
#include "mdfld_dsi_dbi.h"
#include "mdfld_dsi_dpi.h"
#include "mdfld_output.h"
#include <asm/intel_scu_ipc.h>
#include "mdfld_dsi_pkg_sender.h"
#include <linux/pm_runtime.h>
#include <linux/moduleparam.h>

#define MDFLD_DSI_BRIGHTNESS_MAX_LEVEL 100

static int CABC_control = 1;
static int LABC_control = 1;

module_param (CABC_control, int, 0644);
module_param (LABC_control, int, 0644);

/**
 * make these MCS command global 
 * we don't need 'movl' everytime we send them.
 * FIXME: these datas were provided by OEM, we should get them from GCT.
 **/
static u32 mdfld_dbi_mcs_hysteresis[] = {
	0x42000f57, 0x8c006400, 0xff00bf00, 0xffffffff,
	0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
	0x38000aff, 0x82005000, 0xff00ab00, 0xffffffff,
	0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
	0x000000ff,
};

static u32 mdfld_dbi_mcs_display_profile[] = {
	0x50281450, 0x0000c882, 0x00000000, 0x00000000,
	0x00000000,
};

static u32 mdfld_dbi_mcs_kbbc_profile[] = {
	0x00ffcc60, 0x00000000, 0x00000000, 0x00000000,
}; 
	
static u32 mdfld_dbi_mcs_gamma_profile[] = {
	0x81111158, 0x88888888, 0x88888888,
}; 

/*
 * write hysteresis values.
 */
static void mdfld_dsi_write_hysteresis (struct mdfld_dsi_config *dsi_config,
                                                                int pipe)
{
	struct mdfld_dsi_pkg_sender *sender = mdfld_dsi_get_pkg_sender(dsi_config);

	if(!sender) {
	        WARN_ON(1);
		return;
	}
	mdfld_dsi_send_mcs_long_hs(sender,
				   mdfld_dbi_mcs_hysteresis,
				   17,
				   MDFLD_DSI_SEND_PACKAGE);
}

/*
 * write display profile values.
 */
static void mdfld_dsi_write_display_profile(struct mdfld_dsi_config *dsi_config, int pipe)
{
	struct mdfld_dsi_pkg_sender *sender = mdfld_dsi_get_pkg_sender(dsi_config);

	if(!sender) {
	        WARN_ON(1);
		return;
        }
	mdfld_dsi_send_mcs_long_hs(sender,
				   mdfld_dbi_mcs_display_profile,
				   5,
				   MDFLD_DSI_SEND_PACKAGE);
}

/*
 * write KBBC profile values.
 */
static void mdfld_dsi_write_kbbc_profile (struct mdfld_dsi_config * dsi_config, int pipe)
{
	struct mdfld_dsi_pkg_sender *sender = mdfld_dsi_get_pkg_sender(dsi_config);

	if(!sender) {
	        WARN_ON(1);
		return;
        }
	mdfld_dsi_send_mcs_long_hs(sender,
				   mdfld_dbi_mcs_kbbc_profile,
				   4,
				   MDFLD_DSI_SEND_PACKAGE);
}

/*
 * write gamma setting.
 */
static void mdfld_dsi_write_gamma_setting (struct mdfld_dsi_config *dsi_config, int pipe)
{
	struct mdfld_dsi_pkg_sender *sender = mdfld_dsi_get_pkg_sender(dsi_config);

	if(!sender) {
	        WARN_ON(1);
		return;
	}
	mdfld_dsi_send_mcs_long_hs(sender,
				   mdfld_dbi_mcs_gamma_profile,
				   3,
				   MDFLD_DSI_SEND_PACKAGE);
}

/*
 * Check and see if the generic control or data buffer is empty and ready.
 */
void mdfld_dsi_gen_fifo_ready (struct drm_device *dev, u32 gen_fifo_stat_reg, u32 fifo_stat)
{
	u32 GEN_BF_time_out_count = 0;
	
	/* Check MIPI Adatper command registers */
	for (GEN_BF_time_out_count = 0; GEN_BF_time_out_count < GEN_FB_TIME_OUT; GEN_BF_time_out_count++)
	{
		if ((REG_READ(gen_fifo_stat_reg) & fifo_stat) == fifo_stat)
			break;
		udelay (100);
	}

	if (GEN_BF_time_out_count == GEN_FB_TIME_OUT)
		dev_err(dev->dev,
        "mdfld_dsi_gen_fifo_ready, Timeout. gen_fifo_stat_reg = 0x%x. \n",
                                                gen_fifo_stat_reg);
}

/*
 * Manage the DSI MIPI keyboard and display brightness.
 * FIXME: this is exported to OSPM code. should work out an specific 
 * display interface to OSPM. 
 */
void mdfld_dsi_brightness_init(struct mdfld_dsi_config *dsi_config, int pipe)
{
	struct mdfld_dsi_pkg_sender *sender = mdfld_dsi_get_pkg_sender(dsi_config);
	struct drm_device *dev = sender->dev;
	struct drm_psb_private *dev_priv = dev->dev_private;
	u32 gen_ctrl_val;
	
	if(!sender) {
	        WARN_ON(1);
	        return;
	}
	/* Set default display backlight value to 85% (0xd8)*/
	mdfld_dsi_send_mcs_short_hs(sender,
				    write_display_brightness,
				    0xd8,
				    1,
				    MDFLD_DSI_SEND_PACKAGE);

	/* Set minimum brightness setting of CABC function to 20% (0x33)*/
	mdfld_dsi_send_mcs_short_hs(sender,
				    write_cabc_min_bright,
				    0x33,
				    1,
				    MDFLD_DSI_SEND_PACKAGE);

	mdfld_dsi_write_hysteresis(dsi_config, pipe);
	mdfld_dsi_write_display_profile (dsi_config, pipe);
	mdfld_dsi_write_kbbc_profile (dsi_config, pipe);
	mdfld_dsi_write_gamma_setting (dsi_config, pipe);

	/* Enable backlight or/and LABC */
	gen_ctrl_val = BRIGHT_CNTL_BLOCK_ON | DISPLAY_DIMMING_ON| BACKLIGHT_ON;
	if (LABC_control == 1 || CABC_control == 1)
		gen_ctrl_val |= DISPLAY_DIMMING_ON| DISPLAY_BRIGHTNESS_AUTO | GAMMA_AUTO;

	if (LABC_control == 1)
		gen_ctrl_val |= AMBIENT_LIGHT_SENSE_ON;

	dev_priv->mipi_ctrl_display = gen_ctrl_val;

	mdfld_dsi_send_mcs_short_hs(sender,
				    write_ctrl_display,
				    (u8)gen_ctrl_val,
				    1,
				    MDFLD_DSI_SEND_PACKAGE);

	if (CABC_control == 0)
		return;
	mdfld_dsi_send_mcs_short_hs(sender,
				    write_ctrl_cabc,
				    UI_IMAGE,
				    1,
				    MDFLD_DSI_SEND_PACKAGE);
}

/*
 * Manage the mipi display brightness.
 * TODO: refine this interface later
 */
void mdfld_dsi_brightness_control(struct drm_device *dev, int pipe, int level)
{
	struct mdfld_dsi_pkg_sender *sender;
	struct drm_psb_private *dev_priv;
	struct mdfld_dsi_config *dsi_config;
	u32 gen_ctrl_val;
	int p_type;	
	
	if (!dev || (pipe != 0 && pipe != 2)) {
		dev_err(dev->dev, "Invalid parameter\n");
		return;
	}

	p_type = mdfld_get_panel_type(dev, 0);

	dev_priv = dev->dev_private;

	if(pipe)
		dsi_config = dev_priv->dsi_configs[1];
	else
		dsi_config = dev_priv->dsi_configs[0];

	sender = mdfld_dsi_get_pkg_sender(dsi_config);

	if(!sender) {
	        WARN_ON(1);
		return;
	}

	gen_ctrl_val = ((level * 0xff) / MDFLD_DSI_BRIGHTNESS_MAX_LEVEL) & 0xff;

	dev_dbg(dev->dev,
                "pipe = %d, gen_ctrl_val = %d.  \n", pipe, gen_ctrl_val);
	
	if(p_type == TMD_VID || p_type == TMD_CMD){
		/* Set display backlight value */
		mdfld_dsi_send_mcs_short_hs(sender, 
					tmd_write_display_brightness, 
					(u8)gen_ctrl_val, 
	                                 1, 
	                        	MDFLD_DSI_SEND_PACKAGE);		
	} else {			
		/* Set display backlight value */
		mdfld_dsi_send_mcs_short_hs(sender,
				    write_display_brightness,
				    (u8)gen_ctrl_val,
                                    1,
                                    MDFLD_DSI_SEND_PACKAGE);


		/* Enable backlight control */
		if (level == 0)
			gen_ctrl_val = 0;
		else 
			gen_ctrl_val = dev_priv->mipi_ctrl_display;

		mdfld_dsi_send_mcs_short_hs(sender,
                                    write_ctrl_display,
                                   (u8)gen_ctrl_val,
                                   1,
                                   MDFLD_DSI_SEND_PACKAGE);
	}
}

/*
 * shut down DSI controller
 */ 
void mdfld_dsi_controller_shutdown(struct mdfld_dsi_config * dsi_config, int pipe)
{
	struct drm_device * dev;
	u32 reg_offset = pipe ? MIPIC_REG_OFFSET : 0;
	int retry = 100;
	
	if (!dsi_config) {
	        WARN_ON(1);
		return;
	}
	
	dev = dsi_config->dev;
	
	if (!gma_power_begin(dev, true)) {
		dev_err(dev->dev, "hw begin failed\n");
		return;
	}
		
	if(!(REG_READ(MIPIA_DEVICE_READY_REG + reg_offset) &  DSI_DEVICE_READY)) 
		goto shutdown_out;
	
	/* Send shut down package, clean packet send bit first */
	if(REG_READ(MIPIA_INTR_STAT_REG + reg_offset) & DSI_INTR_STATE_SPL_PKG_SENT) {
		REG_WRITE((MIPIA_INTR_STAT_REG + reg_offset), 
				(REG_READ(MIPIA_INTR_STAT_REG + reg_offset) | DSI_INTR_STATE_SPL_PKG_SENT));
	}
	
	/*send shut down package in HS*/
	REG_WRITE((MIPIA_DPI_CONTROL_REG + reg_offset), DSI_DPI_CTRL_HS_SHUTDOWN);
	
	
	/*
	 * make sure shut down is sent.
	 * FIXME: add max retry counter
	 */
	while(!(REG_READ(MIPIA_INTR_STAT_REG + reg_offset) & DSI_INTR_STATE_SPL_PKG_SENT)) {
		retry--;
		
		if(!retry) {
			dev_err(dev->dev, "timeout\n");
			break;
		}
	}
	
	/*sleep 1 ms to ensure shutdown finished*/
	msleep(100);
	
	/*un-ready device*/
	REG_WRITE((MIPIA_DEVICE_READY_REG + reg_offset),
			   (REG_READ(MIPIA_DEVICE_READY_REG + reg_offset) & ~DSI_DEVICE_READY));

shutdown_out:			   
	gma_power_end(dev);
}

void mdfld_dsi_controller_startup(struct mdfld_dsi_config * dsi_config, int pipe)
{
	struct drm_device * dev;
	u32 reg_offset = pipe ? MIPIC_REG_OFFSET : 0;
	int retry = 100;
	
	
	if (!dsi_config) {
		WARN_ON(1);
		return;
	}
	
	dev = dsi_config->dev;
	dev_dbg(dev->dev, "starting up DSI controller on pipe %d...\n", pipe);
	
	if (!gma_power_begin(dev, true)) {
		dev_err(dev->dev, "hw begin failed\n");
		return;
	}
	
	if((REG_READ(MIPIA_DEVICE_READY_REG + reg_offset) & DSI_DEVICE_READY)) 
		goto startup_out;
	
	/*if config DPI, turn on DPI interface*/
	if(dsi_config->type == MDFLD_DSI_ENCODER_DPI) {
		if(REG_READ(MIPIA_INTR_STAT_REG + reg_offset) & DSI_INTR_STATE_SPL_PKG_SENT) {
			REG_WRITE((MIPIA_INTR_STAT_REG + reg_offset), DSI_INTR_STATE_SPL_PKG_SENT);
		}
		
		REG_WRITE((MIPIA_DPI_CONTROL_REG + reg_offset), DSI_DPI_CTRL_HS_TURN_ON);
		
		/*
		 * make sure shut down is sent.
		 * FIXME: add max retry counter
		 */
		while(!(REG_READ(MIPIA_INTR_STAT_REG + reg_offset) & DSI_INTR_STATE_SPL_PKG_SENT)) {
			retry--;
			if(!retry) {
				dev_err(dev->dev, "timeout\n");
				break;
			}
		}
		
		msleep(100);
	}
	
	/*set device ready*/
	REG_WRITE((MIPIA_DEVICE_READY_REG + reg_offset),
			   (REG_READ(MIPIA_DEVICE_READY_REG + reg_offset) | DSI_DEVICE_READY));

startup_out:	
	gma_power_end(dev);
}


static int mdfld_dsi_get_panel_status(struct mdfld_dsi_config *dsi_config,
					u8 dcs,
					u32 *data,
					u8 transmission)
{
	struct mdfld_dsi_pkg_sender *sender
		= mdfld_dsi_get_pkg_sender(dsi_config);

	if (!sender || !data) {
		DRM_ERROR("Invalid parameter\n");
		return -EINVAL;
	}

	if (transmission == MDFLD_DSI_HS_TRANSMISSION)
		return mdfld_dsi_read_mcs_hs(sender, dcs, data, 1);
	else if (transmission == MDFLD_DSI_LP_TRANSMISSION)
		return mdfld_dsi_read_mcs_lp(sender, dcs, data, 1);
	else
		return -EINVAL;
}

int mdfld_dsi_get_power_mode(struct mdfld_dsi_config *dsi_config,
				u32 *mode,
				u8 transmission)
{
	if (!dsi_config || !mode) {
		DRM_ERROR("Invalid parameter\n");
		return -EINVAL;
	}

	return mdfld_dsi_get_panel_status(dsi_config, 0x0a, mode, transmission);
}

int mdfld_dsi_get_diagnostic_result(struct mdfld_dsi_config *dsi_config,
					u32 *result,
					u8 transmission)
{
	if (!dsi_config || !result) {
		DRM_ERROR("Invalid parameter\n");
		return -EINVAL;
	}

	return mdfld_dsi_get_panel_status(dsi_config, 0x0f, result,
					  transmission);
}

/*
 * NOTE: this function was used by OSPM.
 * TODO: will be removed later, should work out display interfaces for OSPM
 */
void mdfld_dsi_controller_init(struct mdfld_dsi_config * dsi_config, int pipe)
{
	if(!dsi_config || ((pipe != 0) && (pipe != 2))) {
	        WARN_ON(1);
		return;
	}

	if(dsi_config->type)
		mdfld_dsi_dpi_controller_init(dsi_config, pipe);
	else
		mdfld_dsi_controller_dbi_init(dsi_config, pipe);
}

static void mdfld_dsi_connector_save(struct drm_connector * connector)
{
}

static void mdfld_dsi_connector_restore(struct drm_connector * connector)
{
}

static enum drm_connector_status mdfld_dsi_connector_detect(struct drm_connector * connector, bool force)
{
	struct psb_intel_output *psb_output
					= to_psb_intel_output(connector);
	struct mdfld_dsi_connector *dsi_connector
	                                = MDFLD_DSI_CONNECTOR(psb_output);
	return dsi_connector->status;
}

static int mdfld_dsi_connector_set_property(struct drm_connector *connector,
					struct drm_property *property,
					uint64_t value)
{
	struct drm_encoder *encoder = connector->encoder;

	if (!strcmp(property->name, "scaling mode") && encoder) {
		struct psb_intel_crtc * psb_crtc = to_psb_intel_crtc(encoder->crtc);
		bool bTransitionFromToCentered;
		uint64_t curValue;

		if (!psb_crtc)
			goto set_prop_error;

		switch (value) {
		case DRM_MODE_SCALE_FULLSCREEN:
			break;
		case DRM_MODE_SCALE_NO_SCALE:
			break;
		case DRM_MODE_SCALE_ASPECT:
			break;
		default:
			goto set_prop_error;
		}

		if (drm_connector_property_get_value(connector, property, &curValue))
			goto set_prop_error;

		if (curValue == value)
			goto set_prop_done;

		if (drm_connector_property_set_value(connector, property, value))
			goto set_prop_error;

		bTransitionFromToCentered = (curValue == DRM_MODE_SCALE_NO_SCALE) ||
			(value == DRM_MODE_SCALE_NO_SCALE);

		if (psb_crtc->saved_mode.hdisplay != 0 &&
		    psb_crtc->saved_mode.vdisplay != 0) {
			if (bTransitionFromToCentered) {
				if (!drm_crtc_helper_set_mode(encoder->crtc, &psb_crtc->saved_mode,
					    encoder->crtc->x, encoder->crtc->y, encoder->crtc->fb))
					goto set_prop_error;
			} else {
				struct drm_encoder_helper_funcs *pEncHFuncs  = encoder->helper_private;
				pEncHFuncs->mode_set(encoder, &psb_crtc->saved_mode,
						     &psb_crtc->saved_adjusted_mode);
			}
		}
#ifdef CONFIG_BACKLIGHT_CLASS_DEVICE
	} else if (!strcmp(property->name, "backlight") && encoder) {
		struct drm_psb_private *dev_priv = encoder->dev->dev_private;
		struct backlight_device *psb_bd = dev_priv->backlight_device;
		dev_dbg(encoder->dev->dev, "backlight level = %d\n", (int)value);
		if (drm_connector_property_set_value(connector, property, value))
			goto set_prop_error;
		else {
			dev_dbg(encoder->dev->dev,
			                "set brightness to %d", (int)value);
			if (psb_bd) {
				psb_bd->props.brightness = value;
				backlight_update_status(psb_bd);
			}
		}
#endif
	}
set_prop_done:
    return 0;
set_prop_error:
    return -1;
}

static void mdfld_dsi_connector_destroy(struct drm_connector *connector)
{
	struct psb_intel_output * psb_output = to_psb_intel_output(connector);
	struct mdfld_dsi_connector * dsi_connector = MDFLD_DSI_CONNECTOR(psb_output);
	struct mdfld_dsi_pkg_sender * sender;
	
	if(!dsi_connector)
	        return;
	
	drm_sysfs_connector_remove(connector);
	drm_connector_cleanup(connector);
	
	sender = dsi_connector->pkg_sender;

	mdfld_dsi_pkg_sender_destroy(sender);

	kfree(dsi_connector);
}

static int mdfld_dsi_connector_get_modes(struct drm_connector * connector)
{
	struct psb_intel_output * psb_output = to_psb_intel_output(connector);
	struct mdfld_dsi_connector * dsi_connector = MDFLD_DSI_CONNECTOR(psb_output);
	struct mdfld_dsi_config * dsi_config = mdfld_dsi_get_config(dsi_connector);
	struct drm_display_mode * fixed_mode = dsi_config->fixed_mode;
	struct drm_display_mode * dup_mode = NULL;
	struct drm_device * dev = connector->dev;
	
	connector->display_info.min_vfreq = 0;
	connector->display_info.max_vfreq = 200;
	connector->display_info.min_hfreq = 0;
	connector->display_info.max_hfreq = 200;

	if(fixed_mode) {
		dev_dbg(dev->dev, "fixed_mode %dx%d\n",
		        fixed_mode->hdisplay, fixed_mode->vdisplay);
		
		dup_mode = drm_mode_duplicate(dev, fixed_mode);
		drm_mode_probed_add(connector, dup_mode);
		return 1;
	}
	dev_err(dev->dev, "Didn't get any modes!\n");
	return 0;
}

static int mdfld_dsi_connector_mode_valid(struct drm_connector * connector, struct drm_display_mode * mode)
{
	struct psb_intel_output * psb_output = to_psb_intel_output(connector);
	struct mdfld_dsi_connector * dsi_connector = MDFLD_DSI_CONNECTOR(psb_output);
	struct mdfld_dsi_config * dsi_config = mdfld_dsi_get_config(dsi_connector);
	struct drm_display_mode * fixed_mode = dsi_config->fixed_mode;

	dev_dbg(connector->dev->dev, "mode %p, fixed mode %p\n",
	                                                mode, fixed_mode);

	if(mode->flags & DRM_MODE_FLAG_DBLSCAN) 
		return MODE_NO_DBLESCAN;

	if(mode->flags & DRM_MODE_FLAG_INTERLACE)
		return MODE_NO_INTERLACE;

	/**
	 * FIXME: current DC has no fitting unit, reject any mode setting request
	 * will figure out a way to do up-scaling(pannel fitting) later.  
	 **/
	if(fixed_mode) {
		if(mode->hdisplay != fixed_mode->hdisplay)
			return MODE_PANEL;

		if(mode->vdisplay != fixed_mode->vdisplay)
			return MODE_PANEL;
	}
	dev_dbg(connector->dev->dev, "mode ok\n");

	return MODE_OK;
}

static void mdfld_dsi_connector_dpms(struct drm_connector *connector, int mode)
{
#ifdef CONFIG_PM_RUNTIME
	struct drm_device * dev = connector->dev;
	struct drm_psb_private * dev_priv = dev->dev_private;
	bool panel_on, panel_on2;
#endif
	/* First, execute DPMS */
	drm_helper_connector_dpms(connector, mode);

#ifdef CONFIG_PM_RUNTIME
	if(mdfld_panel_dpi(dev)) {
		/* DPI panel */
		panel_on = dev_priv->dpi_panel_on;
		panel_on2 = dev_priv->dpi_panel_on2;
	} else {
		/* DBI panel */
		panel_on = dev_priv->dbi_panel_on;
		panel_on2 = dev_priv->dbi_panel_on2;
	}

	/* Then check all display panels + monitors status */
	/* Make sure that the Display (B) sub-system status isn't i3 when
	 * R/W the DC register, otherwise "Fabric error" issue would occur
	 * during S0i3 state. */
	if(!panel_on && !panel_on2 && !(REG_READ(HDMIB_CONTROL)
	                                        & HDMIB_PORT_EN)) {
		/* Request rpm idle */
		if(dev_priv->rpm_enabled)
			pm_request_idle(&dev->pdev->dev);
	}
	/*
	 * if rpm wasn't enabled yet, try to allow it
	 * FIXME: won't enable rpm for DPI since DPI
	 * CRTC setting is a little messy now.
	 * Enable it later!
	 */
#if 0
	if(!dev_priv->rpm_enabled && !mdfld_panel_dpi(dev))
		ospm_runtime_pm_allow(dev);
#endif
#endif
}

static struct drm_encoder *mdfld_dsi_connector_best_encoder(
                                        struct drm_connector *connector) 
{
	struct psb_intel_output * psb_output = to_psb_intel_output(connector);
	struct mdfld_dsi_connector * dsi_connector = MDFLD_DSI_CONNECTOR(psb_output);
	struct mdfld_dsi_config * dsi_config = mdfld_dsi_get_config(dsi_connector);
	struct mdfld_dsi_encoder * encoder = NULL;
	
	if(dsi_config->type == MDFLD_DSI_ENCODER_DBI) 
		encoder = dsi_config->encoders[MDFLD_DSI_ENCODER_DBI];
	else if (dsi_config->type == MDFLD_DSI_ENCODER_DPI) 
		encoder = dsi_config->encoders[MDFLD_DSI_ENCODER_DPI];
	
	dev_dbg(connector->dev->dev, "get encoder %p\n", encoder);
	
	if(!encoder) {
		dev_err(connector->dev->dev,
                        "Invalid encoder for type %d\n", dsi_config->type);
		return NULL;
	}
	dsi_config->encoder = encoder;	
	return &encoder->base;	
}

/* DSI connector funcs */
static const struct drm_connector_funcs mdfld_dsi_connector_funcs = {
	.dpms = /*drm_helper_connector_dpms*/mdfld_dsi_connector_dpms,
	.save = mdfld_dsi_connector_save,
	.restore = mdfld_dsi_connector_restore,
	.detect = mdfld_dsi_connector_detect,
	.fill_modes = drm_helper_probe_single_connector_modes,
	.set_property = mdfld_dsi_connector_set_property,
	.destroy = mdfld_dsi_connector_destroy,
};

/* DSI connector helper funcs */
static const struct drm_connector_helper_funcs mdfld_dsi_connector_helper_funcs = {
	.get_modes = mdfld_dsi_connector_get_modes,
	.mode_valid = mdfld_dsi_connector_mode_valid,
	.best_encoder = mdfld_dsi_connector_best_encoder,
};

static int mdfld_dsi_get_default_config(struct drm_device * dev, 
										struct mdfld_dsi_config * config, int pipe)
{
	if(!dev || !config) {
	        WARN_ON(1);
		return -EINVAL;
	}
	
	config->bpp = 24;
	config->type = mdfld_panel_dpi(dev);
	config->lane_count = 2;
	config->channel_num = 0;
	/*NOTE: video mode is ignored when type is MDFLD_DSI_ENCODER_DBI*/
	if (mdfld_get_panel_type(dev, pipe) == TMD_VID) {
		config->video_mode = MDFLD_DSI_VIDEO_NON_BURST_MODE_SYNC_PULSE;
	} else {
		config->video_mode = MDFLD_DSI_VIDEO_BURST_MODE;
	}
	
	return 0;
}

/*
 * Returns the panel fixed mode from configuration. 
 */
struct drm_display_mode *
mdfld_dsi_get_configuration_mode(struct mdfld_dsi_config * dsi_config, int pipe)
{
	struct drm_device *dev = dsi_config->dev;
	struct drm_display_mode *mode;
	struct drm_psb_private *dev_priv = dev->dev_private;
	struct mrst_timing_info *ti = &dev_priv->gct_data.DTD;
	bool use_gct = false;

	mode = kzalloc(sizeof(*mode), GFP_KERNEL);
	if (!mode) {
	        dev_err(dev->dev, "Out of memory for mode\n");
		return NULL;
        }
	if (use_gct) {
		dev_dbg(dev->dev, "gct find MIPI panel.\n");

		mode->hdisplay = (ti->hactive_hi << 8) | ti->hactive_lo;
		mode->vdisplay = (ti->vactive_hi << 8) | ti->vactive_lo;
		mode->hsync_start = mode->hdisplay + \
				((ti->hsync_offset_hi << 8) | \
				ti->hsync_offset_lo);
		mode->hsync_end = mode->hsync_start + \
				((ti->hsync_pulse_width_hi << 8) | \
				ti->hsync_pulse_width_lo);
		mode->htotal = mode->hdisplay + ((ti->hblank_hi << 8) | \
								ti->hblank_lo);
		mode->vsync_start = \
			mode->vdisplay + ((ti->vsync_offset_hi << 8) | \
						ti->vsync_offset_lo);
		mode->vsync_end = \
			mode->vsync_start + ((ti->vsync_pulse_width_hi << 8) | \
						ti->vsync_pulse_width_lo);
		mode->vtotal = mode->vdisplay + \
				((ti->vblank_hi << 8) | ti->vblank_lo);
		mode->clock = ti->pixel_clock * 10;
	} else {
		if(dsi_config->type == MDFLD_DSI_ENCODER_DPI) { 
			if (mdfld_get_panel_type(dev, pipe) == TMD_VID) {
				mode->hdisplay = 480;
				mode->vdisplay = 854;
				mode->hsync_start = 487;
				mode->hsync_end = 490;
				mode->htotal = 499;
				mode->vsync_start = 861;
				mode->vsync_end = 865;
				mode->vtotal = 873;
				mode->clock = 33264;
			} else {
				mode->hdisplay = 864;
				mode->vdisplay = 480;
				mode->hsync_start = 873;
				mode->hsync_end = 876;
				mode->htotal = 887;
				mode->vsync_start = 487;
				mode->vsync_end = 490;
				mode->vtotal = 499;
				mode->clock = 33264;
			}
		} else if(dsi_config->type == MDFLD_DSI_ENCODER_DBI) {
			mode->hdisplay = 864;
			mode->vdisplay = 480;
			mode->hsync_start = 872;
			mode->hsync_end = 876;
			mode->htotal = 884;
			mode->vsync_start = 482;
			mode->vsync_end = 494;
			mode->vtotal = 486;
			mode->clock = 25777;
			
		}
	}

	drm_mode_set_name(mode);
	drm_mode_set_crtcinfo(mode, 0);
	
	mode->type |= DRM_MODE_TYPE_PREFERRED;

	return mode;
}

int mdfld_dsi_panel_reset(int pipe)
{
	unsigned gpio;
	int ret = 0;

	switch (pipe) {
	case 0:
		gpio = 128;
		break;
	case 2:
		gpio = 34;
		break;
	default:
		DRM_ERROR("Invalid output\n");
		return -EINVAL;
	}

	ret = gpio_request(gpio, "gfx");
	if (ret) {
		DRM_ERROR("gpio_rqueset failed\n");
		return ret;
	}

	ret = gpio_direction_output(gpio, 1);
	if (ret) {
		DRM_ERROR("gpio_direction_output failed\n");
		goto gpio_error;
	}

	gpio_get_value(128);

gpio_error:
	if (gpio_is_valid(gpio))
		gpio_free(gpio);

	return ret;
}

/*
 * MIPI output init
 * @dev drm device
 * @pipe pipe number. 0 or 2
 * @config 
 * 
 * Do the initialization of a MIPI output, including create DRM mode objects
 * initialization of DSI output on @pipe 
 */
void mdfld_dsi_output_init(struct drm_device *dev,
			   int pipe, 
			   struct mdfld_dsi_config *config,
			   struct panel_funcs* p_cmd_funcs,
			   struct panel_funcs* p_vid_funcs)
{
	struct mdfld_dsi_config * dsi_config;
	struct mdfld_dsi_connector * dsi_connector;
	struct psb_intel_output * psb_output;
	struct drm_connector * connector;
	struct mdfld_dsi_encoder * encoder;
	struct drm_psb_private * dev_priv = dev->dev_private;
	struct panel_info dsi_panel_info;
	u32 width_mm, height_mm;

	dev_dbg(dev->dev, "init DSI output on pipe %d\n", pipe);
	
	if(!dev || ((pipe != 0) && (pipe != 2))) {
	        WARN_ON(1);
		return;
	}
	
	/*create a new connetor*/
	dsi_connector = kzalloc(sizeof(struct mdfld_dsi_connector), GFP_KERNEL);
	if(!dsi_connector) {
		DRM_ERROR("No memory");
		return;
	}
	
	dsi_connector->pipe =  pipe;
	
	/*set DSI config*/
	if(config) { 
		dsi_config = config;
	} else {
		dsi_config = kzalloc(sizeof(struct mdfld_dsi_config), GFP_KERNEL);
		if(!dsi_config) {
			dev_err(dev->dev,
			        "cannot allocate memory for DSI config\n");
			goto dsi_init_err0;
		}
		
		mdfld_dsi_get_default_config(dev, dsi_config, pipe);
	}
	
	dsi_connector->private = dsi_config;
	
	dsi_config->changed = 1;
	dsi_config->dev = dev;
	
	/* Init fixed mode basing on DSI config type */
	if(dsi_config->type == MDFLD_DSI_ENCODER_DBI) {
		dsi_config->fixed_mode = p_cmd_funcs->get_config_mode(dev);
		if(p_cmd_funcs->get_panel_info(dev, pipe, &dsi_panel_info))
			goto dsi_init_err0;
	} else if(dsi_config->type == MDFLD_DSI_ENCODER_DPI) {
		dsi_config->fixed_mode = p_vid_funcs->get_config_mode(dev);
		if(p_vid_funcs->get_panel_info(dev, pipe, &dsi_panel_info))
			goto dsi_init_err0;
	}

	width_mm = dsi_panel_info.width_mm;
	height_mm = dsi_panel_info.height_mm;

	dsi_config->mode = dsi_config->fixed_mode;
	dsi_config->connector = dsi_connector;
	
	if(!dsi_config->fixed_mode) {
		dev_err(dev->dev, "No pannel fixed mode was found\n");
		goto dsi_init_err0;
	}
	
	if(pipe && dev_priv->dsi_configs[0]) {
		dsi_config->dvr_ic_inited = 0;
		dev_priv->dsi_configs[1] = dsi_config;
	} else if(pipe == 0) {
		dsi_config->dvr_ic_inited = 1;
		dev_priv->dsi_configs[0] = dsi_config;
	} else {
		dev_err(dev->dev, "Trying to init MIPI1 before MIPI0\n");
		goto dsi_init_err0;
	}

	/*init drm connector object*/
	psb_output = &dsi_connector->base;
	
	psb_output->type = (pipe == 0) ? INTEL_OUTPUT_MIPI : INTEL_OUTPUT_MIPI2;

	connector = &psb_output->base;
	/* Revisit type if MIPI/HDMI bridges ever appear on Medfield */
	drm_connector_init(dev, connector, &mdfld_dsi_connector_funcs,
						DRM_MODE_CONNECTOR_LVDS);
	drm_connector_helper_add(connector, &mdfld_dsi_connector_helper_funcs);
	
	connector->display_info.subpixel_order = SubPixelHorizontalRGB;
	connector->display_info.width_mm = width_mm;
	connector->display_info.height_mm = height_mm;
	connector->interlace_allowed = false;
	connector->doublescan_allowed = false;
	
	/* Attach properties */
	drm_connector_attach_property(connector, dev->mode_config.scaling_mode_property, DRM_MODE_SCALE_FULLSCREEN);
	drm_connector_attach_property(connector, dev_priv->backlight_property, MDFLD_DSI_BRIGHTNESS_MAX_LEVEL);

	/* Init DSI package sender on this output */
	if (mdfld_dsi_pkg_sender_init(dsi_connector, pipe)) {
		DRM_ERROR("Package Sender initialization failed on pipe %d\n", pipe);
		goto dsi_init_err0;
	}

	/* Init DBI & DPI encoders */
	if (p_cmd_funcs) {
		encoder = mdfld_dsi_dbi_init(dev, dsi_connector, p_cmd_funcs);
		if(!encoder) {
			dev_err(dev->dev, "Create DBI encoder failed\n");
			goto dsi_init_err1;
		}
		encoder->private = dsi_config;
		dsi_config->encoders[MDFLD_DSI_ENCODER_DBI] = encoder;
	}
	
	if(p_vid_funcs) {
		encoder = mdfld_dsi_dpi_init(dev, dsi_connector, p_vid_funcs);
		if(!encoder) {
			dev_err(dev->dev, "Create DPI encoder failed\n");
			goto dsi_init_err1;
		}
		encoder->private = dsi_config;
		dsi_config->encoders[MDFLD_DSI_ENCODER_DPI] = encoder;
	}
	
	drm_sysfs_connector_add(connector);
	return;
	
	/*TODO: add code to destroy outputs on error*/
dsi_init_err1:
	/*destroy sender*/
	mdfld_dsi_pkg_sender_destroy(dsi_connector->pkg_sender);

	drm_connector_cleanup(connector);
	kfree(dsi_config->fixed_mode);
	kfree(dsi_config);
dsi_init_err0:
	kfree(dsi_connector);
}
