/* drivers/video/msm/mdp_lcdc.c
 *
 * Copyright (c) 2009 Google Inc.
 * Copyright (c) 2009 QUALCOMM Incorporated
 *
 * This software is licensed under the terms of the GNU General Public
 * License version 2, as published by the Free Software Foundation, and
 * may be copied, distributed, and modified under those terms.
 *
 * 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.
 *
 * Author: Dima Zavin <dima@android.com>
 */

#include <linux/clk.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/sched.h>
#include <linux/wait.h>
#include <linux/delay.h>
#include <linux/slab.h>

#include <asm/io.h>
#include <asm/mach-types.h>
#include <linux/msm_mdp.h>
#include <mach/msm_fb.h>

#include "mdp_hw.h"
#ifdef CONFIG_MSM_MDP40
#include "mdp4.h"
#endif
#ifdef CONFIG_PANEL_SELF_REFRESH
#include <linux/kthread.h>
#endif

#if 0
#define D(fmt, args...) printk(KERN_INFO "Dispaly: " fmt, ##args)
#else
#define D(fmt, args...) do {} while (0)
#endif

#if defined(CONFIG_ARCH_MSM7227)
#define LCDC_MUX_CTL (MSM_TGPIO1_BASE + 0x278)
#endif

static struct mdp_device *mdp_dev;

#ifdef CONFIG_MSM_MDP40
static struct mdp4_overlay_pipe *lcdc_pipe;
#endif

#ifdef CONFIG_PANEL_SELF_REFRESH
#if 0
#define ICM_DBG(s...) printk("[icm]" s)
#else
#define ICM_DBG(s...) do {} while (0)
#endif

/* set the timeout to 200 milliseconds */
#define PANEL_ENTER_IDLE_TIMEOUT HZ/5
/* Afetr setting ICM=1, we need to keep sending the RGB signal more than 2-frame */
#define PANEL_IDLE_STABLE_TIMEOUT 48

static struct task_struct *th_display;
struct panel_icm_info *panel_icm;
DECLARE_WAIT_QUEUE_HEAD(panel_update_wait_queue);
#endif

#ifdef CONFIG_PANEL_SELF_REFRESH
static int icm_check_panel_update(void)
{
	int ret;
	unsigned long irq_flags = 0;

	spin_lock_irqsave(&panel_icm->lock, irq_flags);
	ret = panel_icm->panel_update;
	spin_unlock_irqrestore(&panel_icm->lock, irq_flags);
	return ret;
}

static int icm_thread(void *data)
{
	struct mdp_lcdc_info *lcdc;
	struct msm_lcdc_panel_ops *panel_ops;
	int rc;
	unsigned long irq_flags = 0;

	lcdc = data;
	panel_ops = lcdc->pdata->panel_ops;
	while (1) {
		rc = wait_event_timeout(panel_update_wait_queue, icm_check_panel_update() == 1, PANEL_ENTER_IDLE_TIMEOUT);
		ICM_DBG("ICM Thread:wake up rc=%d \n", rc);
		mutex_lock(&panel_icm->icm_lock);
		if (rc == 0 && icm_check_panel_update() != 1) {/* wait_timeout */
			ICM_DBG("EnterICM: icm_mode=%d icm_doable=%d \n", panel_icm->icm_mode, panel_icm->icm_doable);
			if (panel_icm->icm_mode == false && panel_icm->icm_doable == true) {

				if (panel_ops->refresh_enable)
					panel_ops->refresh_enable(panel_ops);

				panel_icm->icm_mode = true;
				msleep(PANEL_IDLE_STABLE_TIMEOUT);

				mdp_writel(lcdc->mdp, 0, MDP_LCDC_EN);
				clk_disable(lcdc->pad_pclk);
				clk_disable(lcdc->pclk);
				clk_disable(lcdc->mdp_clk);
				panel_icm->clock_enabled = false;
				pr_info("EnterICM: enter ICM MODE done!!!\n");
			}
		} else {/* get update event, no timeout */
			ICM_DBG("Leave ICM: icm_mode=%d icm_doable=%d \n", panel_icm->icm_mode, panel_icm->icm_doable);
			if (panel_icm->icm_mode == true && panel_icm->icm_doable == true) {
				clk_enable(lcdc->mdp_clk);
				clk_enable(lcdc->pclk);
				clk_enable(lcdc->pad_pclk);
				mdp_writel(lcdc->mdp, 1, MDP_LCDC_EN);
				panel_icm->clock_enabled = true;

				if (panel_ops->refresh_disable)
					panel_ops->refresh_disable(panel_ops);

				panel_icm->icm_mode = false;
				pr_info("LeaveICM: leave ICM MODE done !!!\n");
			}
			spin_lock_irqsave(&panel_icm->lock, irq_flags);
			panel_icm->panel_update = 0;
			spin_unlock_irqrestore(&panel_icm->lock, irq_flags);
		}
		mutex_unlock(&panel_icm->icm_lock);
	} /* end while */
	return 0;
}

static void icm_force_leave(void)
{
	struct msm_lcdc_panel_ops *panel_ops;
	unsigned long irq_flags = 0;

	panel_ops = panel_icm->lcdc->pdata->panel_ops;

	mutex_lock(&panel_icm->icm_lock);
	ICM_DBG("Force Leave ICM: icm_mode=%d icm_doable=%d \n", panel_icm->icm_mode, panel_icm->icm_doable);
	if (panel_icm->icm_mode == true) {
		clk_enable(panel_icm->lcdc->mdp_clk);
		clk_enable(panel_icm->lcdc->pclk);
		clk_enable(panel_icm->lcdc->pad_pclk);
		mdp_writel(panel_icm->lcdc->mdp, 1, MDP_LCDC_EN);
		panel_icm->clock_enabled = true;
		if (panel_ops->refresh_disable)
			panel_ops->refresh_disable(panel_ops);
		panel_icm->icm_mode = false;
		panel_icm->icm_doable = true;
                pr_info("ForceLeaveICM: leave ICM MODE done !!!\n");
	}
	spin_lock_irqsave(&panel_icm->lock, irq_flags);
        panel_icm->panel_update = 0;
        spin_unlock_irqrestore(&panel_icm->lock, irq_flags);
	mutex_unlock(&panel_icm->icm_lock);
}

static int icm_init(struct mdp_lcdc_info *lcdc)
{
	int ret = 0;

	/* init panel_icm_info */
	panel_icm = kzalloc(sizeof(struct panel_icm_info), GFP_KERNEL);
	if (!panel_icm)
		return -ENOMEM;
	panel_icm->icm_doable = 1;
	panel_icm->clock_enabled = true;
	panel_icm->lcdc = lcdc;
	panel_icm->force_leave = icm_force_leave;
	panel_icm->icm_suspend = false;
	mutex_init(&panel_icm->icm_lock);
	th_display = kthread_run(icm_thread, lcdc, "panel-enterIdle");
	if (IS_ERR(th_display)) {
		ret = PTR_ERR(th_display);
		pr_err("%s: panel_icm_thread  create fail:%d!!!\n", __func__, ret);
		goto	error_create_thread;
	}
	return ret;
error_create_thread:
	kfree(panel_icm);
	return ret;
}
#endif

static int lcdc_unblank(struct msm_panel_data *fb_panel)
{
	struct mdp_lcdc_info *lcdc = panel_to_lcdc(fb_panel);
	struct msm_lcdc_panel_ops *panel_ops = lcdc->pdata->panel_ops;

	pr_info("%s: ()\n", __func__);

	if (panel_ops->unblank)
		panel_ops->unblank(panel_ops);

	return 0;
}

static int lcdc_blank(struct msm_panel_data *fb_panel)
{
	struct mdp_lcdc_info *lcdc = panel_to_lcdc(fb_panel);
	struct msm_lcdc_panel_ops *panel_ops = lcdc->pdata->panel_ops;

	pr_info("%s: ()\n", __func__);

	if (panel_ops->blank)
		panel_ops->blank(panel_ops);

	return 0;
}

static int lcdc_shutdown(struct msm_panel_data *fb_panel)
{
        struct mdp_lcdc_info *lcdc = panel_to_lcdc(fb_panel);
        struct msm_lcdc_panel_ops *panel_ops = lcdc->pdata->panel_ops;

        pr_info("%s: ()\n", __func__);

	if (panel_ops->shutdown)
	        panel_ops->shutdown(panel_ops);

        return 0;
}

static int lcdc_suspend(struct msm_panel_data *fb_panel)
{
	struct mdp_lcdc_info *lcdc = panel_to_lcdc(fb_panel);
	struct msm_lcdc_panel_ops *panel_ops = lcdc->pdata->panel_ops;

	pr_info("%s: suspending\n", __func__);

#if defined(CONFIG_ARCH_MSM7227)
	writel(0x0, LCDC_MUX_CTL);
	D("suspend_lcdc_mux_ctl = %x\n", readl(LCDC_MUX_CTL));
#endif
#ifdef CONFIG_PANEL_SELF_REFRESH
	if (lcdc->mdp->mdp_dev.overrides & MSM_MDP_RGB_PANEL_SELE_REFRESH) {
		mutex_lock(&panel_icm->icm_lock);
		panel_icm->icm_doable = false;
		pr_info("[ICM %s]: icm mode=%d, clock_enabled=%d\n", __func__, panel_icm->icm_mode, panel_icm->clock_enabled);
		if (panel_icm->icm_mode == true && panel_icm->clock_enabled == false) {
			if (panel_ops->refresh_disable)
				panel_ops->refresh_disable(panel_ops);
			panel_icm->icm_mode = false;
		} else {
			mdp_writel(lcdc->mdp, 0, MDP_LCDC_EN);
			clk_disable(lcdc->pad_pclk);
			clk_disable(lcdc->pclk);
			clk_disable(lcdc->mdp_clk);
		}
		panel_icm->clock_enabled = false;
		mutex_unlock(&panel_icm->icm_lock);
	} else {
		mdp_writel(lcdc->mdp, 0, MDP_LCDC_EN);
		clk_disable(lcdc->pad_pclk);
		clk_disable(lcdc->pclk);
		clk_disable(lcdc->mdp_clk);
	}
#else
	mdp_writel(lcdc->mdp, 0, MDP_LCDC_EN);
	clk_disable(lcdc->pad_pclk);
	clk_disable(lcdc->pclk);
	if (lcdc->mdp_pclk)
		clk_disable(lcdc->mdp_pclk);
	clk_disable(lcdc->mdp_clk);
#endif
	if (panel_ops->uninit)
		panel_ops->uninit(panel_ops);

	return 0;
}

static int lcdc_resume(struct msm_panel_data *fb_panel)
{
	struct mdp_lcdc_info *lcdc = panel_to_lcdc(fb_panel);
	struct msm_lcdc_panel_ops *panel_ops = lcdc->pdata->panel_ops;

	pr_info("%s: resuming\n", __func__);

	if (panel_ops->init) {
		if (panel_ops->init(panel_ops) < 0)
			printk(KERN_ERR "LCD init fail!\n");
	}
	clk_enable(lcdc->mdp_clk);
	if (lcdc->mdp_pclk)
		clk_enable(lcdc->mdp_pclk);
	clk_enable(lcdc->pclk);
	clk_enable(lcdc->pad_pclk);
#if defined(CONFIG_ARCH_MSM7227)
	writel(0x1, LCDC_MUX_CTL);
	D("resume_lcdc_mux_ctl = %x\n", readl(LCDC_MUX_CTL));
#endif

	mdp_writel(lcdc->mdp, 1, MDP_LCDC_EN);
#ifdef CONFIG_PANEL_SELF_REFRESH
	if (lcdc->mdp->mdp_dev.overrides & MSM_MDP_RGB_PANEL_SELE_REFRESH) {
		mutex_lock(&panel_icm->icm_lock);
		panel_icm->icm_doable = true;
		panel_icm->clock_enabled = true;
		panel_icm->icm_suspend = false;
		mutex_unlock(&panel_icm->icm_lock);
	}
#endif

	return 0;
}

static int lcdc_hw_init(struct mdp_lcdc_info *lcdc)
{
	struct msm_panel_data *fb_panel = &lcdc->fb_panel_data;
	uint32_t dma_cfg;

	clk_enable(lcdc->mdp_clk);
	if (lcdc->mdp_pclk)
		clk_enable(lcdc->mdp_pclk);
	clk_enable(lcdc->pclk);
	clk_enable(lcdc->pad_pclk);

	clk_set_rate(lcdc->pclk, lcdc->parms.clk_rate);
	clk_set_rate(lcdc->pad_pclk, lcdc->parms.clk_rate);
	/* write the lcdc params */
	mdp_writel(lcdc->mdp, lcdc->parms.hsync_ctl, MDP_LCDC_HSYNC_CTL);
	mdp_writel(lcdc->mdp, lcdc->parms.vsync_period, MDP_LCDC_VSYNC_PERIOD);
	mdp_writel(lcdc->mdp, lcdc->parms.vsync_pulse_width,
		   MDP_LCDC_VSYNC_PULSE_WIDTH);
	mdp_writel(lcdc->mdp, lcdc->parms.display_hctl, MDP_LCDC_DISPLAY_HCTL);
	mdp_writel(lcdc->mdp, lcdc->parms.display_vstart,
		   MDP_LCDC_DISPLAY_V_START);
	mdp_writel(lcdc->mdp, lcdc->parms.display_vend, MDP_LCDC_DISPLAY_V_END);
	mdp_writel(lcdc->mdp, lcdc->parms.hsync_skew, MDP_LCDC_HSYNC_SKEW);

	mdp_writel(lcdc->mdp, 0, MDP_LCDC_BORDER_CLR);
	mdp_writel(lcdc->mdp, 0x80000000 | 0xff, MDP_LCDC_UNDERFLOW_CTL);
	mdp_writel(lcdc->mdp, 0, MDP_LCDC_ACTIVE_HCTL);
	mdp_writel(lcdc->mdp, 0, MDP_LCDC_ACTIVE_V_START);
	mdp_writel(lcdc->mdp, 0, MDP_LCDC_ACTIVE_V_END);
	mdp_writel(lcdc->mdp, lcdc->parms.polarity, MDP_LCDC_CTL_POLARITY);
	/* config the dma_p block that drives the lcdc data */
	mdp_writel(lcdc->mdp, lcdc->fb_start, MDP_DMA_P_IBUF_ADDR);
	mdp_writel(lcdc->mdp, (((fb_panel->fb_data->yres & 0x7ff) << 16) |
			       (fb_panel->fb_data->xres & 0x7ff)),
		   MDP_DMA_P_SIZE);
	mdp_writel(lcdc->mdp, 0, MDP_DMA_P_OUT_XY);

	dma_cfg = mdp_readl(lcdc->mdp, MDP_DMA_P_CONFIG);
	dma_cfg &= ~(DMA_PACK_PATTERN_MASK | DMA_PACK_ALIGN_MASK);
	dma_cfg |= (DMA_PACK_ALIGN_MSB |
		    DMA_PACK_PATTERN_RGB |
		    DMA_DITHER_EN);
	dma_cfg |= DMA_OUT_SEL_LCDC;
	//zeusk: dma_cfg |= DMA_IBUF_FORMAT_RGB565;
	dma_cfg &= ~DMA_DST_BITS_MASK;

	if (fb_panel->fb_data->output_format == MSM_MDP_OUT_IF_FMT_RGB666)
		dma_cfg |= DMA_DSTC0G_6BITS |
			   DMA_DSTC1B_6BITS |
			   DMA_DSTC2R_6BITS;
	else if (fb_panel->fb_data->output_format == MSM_MDP_OUT_IF_FMT_RGB888)
		dma_cfg |= DMA_DSTC0G_8BITS |
			   DMA_DSTC1B_8BITS |
			   DMA_DSTC2R_8BITS;
	else
		dma_cfg |= DMA_DSTC0G_6BITS |
			   DMA_DSTC1B_5BITS |
			   DMA_DSTC2R_5BITS;

	mdp_writel(lcdc->mdp, dma_cfg, MDP_DMA_P_CONFIG);

	/* enable the lcdc timing generation */
	mdp_writel(lcdc->mdp, 1, MDP_LCDC_EN);

	return 0;
}

static void lcdc_wait_vsync(struct msm_panel_data *panel)
{
	struct mdp_lcdc_info *lcdc = panel_to_lcdc(panel);
	int ret;

	ret = wait_event_timeout(lcdc->vsync_waitq, lcdc->got_vsync, HZ / 2);
	if (!ret && !lcdc->got_vsync)
		pr_err("%s: timeout waiting for VSYNC\n", __func__);
	lcdc->got_vsync = 0;
}

static void lcdc_request_vsync(struct msm_panel_data *fb_panel,
			       struct msmfb_callback *vsync_cb)
{
	struct mdp_lcdc_info *lcdc = panel_to_lcdc(fb_panel);

	/* the vsync callback will start the dma */
	vsync_cb->func(vsync_cb);
// CotullaFIX start
// FUCK, who make calls from console with disabled interrupts, FUCK THEM!
	if (irqs_disabled())
	{
	    	struct mdp_lcdc_info *lcdc = panel_to_lcdc(fb_panel);
		uint32_t status;
		uint32_t i;
		// do it via polling
		for (i = 0; i < 20; i++) 
		{
        		status = mdp_readl(lcdc->mdp, MDP_INTR_STATUS);
			if (status & MDP_LCDC_FRAME_START)
		            	break;
		        mdelay(1);
        	}                
		// clear intr at the end
		mdp_writel(lcdc->mdp, MDP_LCDC_FRAME_START, MDP_INTR_CLEAR);
//		vsync_cb->func(vsync_cb);
	}
	else
	{
		lcdc->got_vsync = 0;
		mdp_out_if_req_irq(mdp_dev, MSM_LCDC_INTERFACE, MDP_LCDC_FRAME_START,
			  &lcdc->frame_start_cb);
		lcdc_wait_vsync(fb_panel);
	}
// CotullaFIX end       
}

static void lcdc_clear_vsync(struct msm_panel_data *fb_panel)
{
	struct mdp_lcdc_info *lcdc = panel_to_lcdc(fb_panel);
	lcdc->got_vsync = 0;
	mdp_out_if_req_irq(mdp_dev, MSM_LCDC_INTERFACE, 0, NULL);
}

/* called in irq context with mdp lock held, when mdp gets the
 * MDP_LCDC_FRAME_START interrupt */
static void lcdc_frame_start(struct msmfb_callback *cb)
{
	struct mdp_lcdc_info *lcdc;

	lcdc = container_of(cb, struct mdp_lcdc_info, frame_start_cb);

	lcdc->got_vsync = 1;
	wake_up(&lcdc->vsync_waitq);
}

static void lcdc_dma_start(void *priv, uint32_t addr, uint32_t stride,
			   uint32_t width, uint32_t height, uint32_t x,
			   uint32_t y)
{
	struct mdp_lcdc_info *lcdc = priv;
	struct mdp_info *mdp = container_of(mdp_dev, struct mdp_info, mdp_dev);
	if (mdp->dma_config_dirty)
	{
		mdp_writel(lcdc->mdp, 0, MDP_LCDC_EN);
		mdelay(30);
		mdp_dev->configure_dma(mdp_dev);
		mdp_writel(lcdc->mdp, 1, MDP_LCDC_EN);
	}
	mdp_writel(lcdc->mdp, stride, MDP_DMA_P_IBUF_Y_STRIDE);
	mdp_writel(lcdc->mdp, addr, MDP_DMA_P_IBUF_ADDR);
}

static void precompute_timing_parms(struct mdp_lcdc_info *lcdc)
{
	struct msm_lcdc_timing *timing = lcdc->pdata->timing;
	struct msm_fb_data *fb_data = lcdc->pdata->fb_data;
	unsigned int hsync_period;
	unsigned int hsync_start_x;
	unsigned int hsync_end_x;
	unsigned int vsync_period;
	unsigned int display_vstart;
	unsigned int display_vend;

	hsync_period = (timing->hsync_pulse_width + timing->hsync_back_porch +
			fb_data->xres + timing->hsync_front_porch);
	hsync_start_x = (timing->hsync_pulse_width + timing->hsync_back_porch);
	hsync_end_x = hsync_start_x + fb_data->xres - 1;

	vsync_period = (timing->vsync_pulse_width + timing->vsync_back_porch +
			fb_data->yres + timing->vsync_front_porch);
	vsync_period *= hsync_period;

	display_vstart = timing->vsync_pulse_width + timing->vsync_back_porch;
	display_vstart *= hsync_period;
	display_vstart += timing->hsync_skew;

	display_vend = (timing->vsync_pulse_width + timing->vsync_back_porch +
			 fb_data->yres) * hsync_period;
	display_vend += timing->hsync_skew - 1;

	/* register values we pre-compute at init time from the timing
	 * information in the panel info */
	lcdc->parms.hsync_ctl = (((hsync_period & 0xfff) << 16) |
				 (timing->hsync_pulse_width & 0xfff));
	lcdc->parms.vsync_period = vsync_period & 0xffffff;
	lcdc->parms.vsync_pulse_width = (timing->vsync_pulse_width *
					 hsync_period) & 0xffffff;

	lcdc->parms.display_hctl = (((hsync_end_x & 0xfff) << 16) |
				    (hsync_start_x & 0xfff));
	lcdc->parms.display_vstart = display_vstart & 0xffffff;
	lcdc->parms.display_vend = display_vend & 0xffffff;
	lcdc->parms.hsync_skew = timing->hsync_skew & 0xfff;
	lcdc->parms.polarity = ((timing->hsync_act_low << 0) |
				(timing->vsync_act_low << 1) |
				(timing->den_act_low << 2));
	lcdc->parms.clk_rate = timing->clk_rate;
}

static int mdp_lcdc_probe(struct platform_device *pdev)
{
	struct msm_lcdc_platform_data *pdata = pdev->dev.platform_data;
	struct mdp_lcdc_info *lcdc;
	int ret = 0;
#ifdef CONFIG_MSM_MDP40
	struct mdp4_overlay_pipe *pipe;
	int ptype;
#endif

	if (!pdata) {
		pr_err("%s: no LCDC platform data found\n", __func__);
		return -EINVAL;
	}

	lcdc = kzalloc(sizeof(struct mdp_lcdc_info), GFP_KERNEL);
	if (!lcdc)
		return -ENOMEM;

	/* We don't actually own the clocks, the mdp does. */
	lcdc->mdp_clk = clk_get(mdp_dev->dev.parent, "mdp_clk");
	if (IS_ERR(lcdc->mdp_clk)) {
		pr_err("%s: failed to get mdp_clk\n", __func__);
		ret = PTR_ERR(lcdc->mdp_clk);
		goto err_get_mdp_clk;
	}

	lcdc->mdp_pclk = clk_get(mdp_dev->dev.parent, "mdp_pclk");
	if (IS_ERR(lcdc->mdp_pclk))
		lcdc->mdp_pclk = NULL;

	lcdc->pclk = clk_get(mdp_dev->dev.parent, "lcdc_pclk_clk");
	if (IS_ERR(lcdc->pclk)) {
		pr_err("%s: failed to get lcdc_pclk\n", __func__);
		ret = PTR_ERR(lcdc->pclk);
		goto err_get_pclk;
	}

	lcdc->pad_pclk = clk_get(mdp_dev->dev.parent, "lcdc_pad_pclk_clk");
	if (IS_ERR(lcdc->pad_pclk)) {
		pr_err("%s: failed to get lcdc_pad_pclk\n", __func__);
		ret = PTR_ERR(lcdc->pad_pclk);
		goto err_get_pad_pclk;
	}

	init_waitqueue_head(&lcdc->vsync_waitq);
	lcdc->pdata = pdata;
	lcdc->frame_start_cb.func = lcdc_frame_start;

	platform_set_drvdata(pdev, lcdc);
#ifdef CONFIG_MSM_MDP40
	mdp_out_if_register(mdp_dev, MSM_LCDC_INTERFACE, lcdc, INTR_OVERLAY0_DONE,
			    lcdc_overlay_start);
#else
	mdp_out_if_register(mdp_dev, MSM_LCDC_INTERFACE, lcdc, MDP_DMA_P_DONE,
			    lcdc_dma_start);
#endif
	precompute_timing_parms(lcdc);

	lcdc->fb_start = pdata->fb_resource->start;
	lcdc->mdp = container_of(mdp_dev, struct mdp_info, mdp_dev);
	if(lcdc->mdp->mdp_dev.color_format)
		lcdc->color_format = lcdc->mdp->mdp_dev.color_format;
	else
		lcdc->color_format = MSM_MDP_OUT_IF_FMT_RGB565;

#ifdef CONFIG_MSM_MDP40
	if (lcdc_pipe == NULL) {
		ptype = mdp4_overlay_format2type(MDP_RGB_565);
		pipe = mdp4_overlay_pipe_alloc(ptype);
		if (!pipe)
			goto err_mdp4_overlay_pipe_alloc;
		pipe->mixer_stage  = MDP4_MIXER_STAGE_BASE;
		pipe->pipe_used = 1;
		pipe->mixer_num  = MDP4_MIXER0;
		pipe->src_format = MDP_RGB_565;
		mdp4_overlay_format2pipe(pipe);
		pipe->mdp = lcdc->mdp;

		lcdc_pipe = pipe; /* keep it */
	} else {
		pipe = lcdc_pipe;
	}

	pipe->src_height = pdata->fb_data->yres;
	pipe->src_width = pdata->fb_data->xres;
	pipe->src_h = pdata->fb_data->yres;
	pipe->src_w = pdata->fb_data->xres;
	pipe->src_y = 0;
	pipe->src_x = 0;
	pipe->srcp0_addr = (uint32_t) lcdc->fb_start;
	pipe->srcp0_ystride = pdata->fb_data->xres * 2;

	mdp4_overlay_rgb_setup(pipe);
	mdp4_mixer_stage_up(pipe);
#endif

	lcdc->fb_panel_data.suspend = lcdc_suspend;
	lcdc->fb_panel_data.resume = lcdc_resume;
	lcdc->fb_panel_data.wait_vsync = lcdc_wait_vsync;
	lcdc->fb_panel_data.request_vsync = lcdc_request_vsync;
	lcdc->fb_panel_data.clear_vsync = lcdc_clear_vsync;
	lcdc->fb_panel_data.blank = lcdc_blank;
	lcdc->fb_panel_data.unblank = lcdc_unblank;
	lcdc->fb_panel_data.fb_data = pdata->fb_data;
	lcdc->fb_panel_data.interface_type = MSM_LCDC_INTERFACE;
	lcdc->fb_panel_data.shutdown = lcdc_shutdown;
	ret = lcdc_hw_init(lcdc);
	if (ret) {
		pr_err("%s: Cannot initialize the mdp_lcdc\n", __func__);
		goto err_hw_init;
	}
	lcdc->fb_pdev.name = "msm_panel";
	lcdc->fb_pdev.id = pdata->fb_id;
	lcdc->fb_pdev.resource = pdata->fb_resource;
	lcdc->fb_pdev.num_resources = 1;
	lcdc->fb_pdev.dev.platform_data = &lcdc->fb_panel_data;


	ret = platform_device_register(&lcdc->fb_pdev);
	if (ret) {
		pr_err("%s: Cannot register msm_panel pdev\n", __func__);
		goto err_plat_dev_reg;
	}

	pr_info("%s: initialized\n", __func__);
#ifdef CONFIG_PANEL_SELF_REFRESH
	if (lcdc->mdp->mdp_dev.overrides & MSM_MDP_RGB_PANEL_SELE_REFRESH) {
		ret = icm_init(lcdc);
		if (ret) {
			pr_err("%s: Cannot init dispaly selfrefresh \n", __func__);
			goto err_plat_dev_reg;
		}
	}
#endif

	return 0;

err_plat_dev_reg:
err_hw_init:
#ifdef CONFIG_MSM_MDP40
err_mdp4_overlay_pipe_alloc:
#endif
	platform_set_drvdata(pdev, NULL);
	clk_put(lcdc->pad_pclk);
err_get_pad_pclk:
	clk_put(lcdc->pclk);
err_get_pclk:
	if (lcdc->mdp_pclk)
		clk_put(lcdc->mdp_pclk);
	clk_put(lcdc->mdp_clk);
err_get_mdp_clk:
	kfree(lcdc);
	return ret;
}

static int mdp_lcdc_remove(struct platform_device *pdev)
{
	struct mdp_lcdc_info *lcdc = platform_get_drvdata(pdev);

	platform_set_drvdata(pdev, NULL);

	clk_put(lcdc->pclk);
	clk_put(lcdc->pad_pclk);
	kfree(lcdc);

	return 0;
}

static struct platform_driver mdp_lcdc_driver = {
	.probe = mdp_lcdc_probe,
	.remove = mdp_lcdc_remove,
	.driver = {
		.name	= "msm_mdp_lcdc",
		.owner	= THIS_MODULE,
	},
};

static int mdp_lcdc_add_mdp_device(struct device *dev,
				   struct class_interface *class_intf)
{
	/* might need locking if mulitple mdp devices */
	if (mdp_dev)
		return 0;
	mdp_dev = container_of(dev, struct mdp_device, dev);
	return platform_driver_register(&mdp_lcdc_driver);
}

static void mdp_lcdc_remove_mdp_device(struct device *dev,
				       struct class_interface *class_intf)
{
	/* might need locking if mulitple mdp devices */
	if (dev != &mdp_dev->dev)
		return;
	platform_driver_unregister(&mdp_lcdc_driver);
	mdp_dev = NULL;
}

static struct class_interface mdp_lcdc_interface = {
	.add_dev = &mdp_lcdc_add_mdp_device,
	.remove_dev = &mdp_lcdc_remove_mdp_device,
};

static int __init mdp_lcdc_init(void)
{
	return register_mdp_client(&mdp_lcdc_interface);
}

module_init(mdp_lcdc_init);