/* Copyright (c) 2011-2013, The Linux Foundation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
 * only version 2 as published by the Free Software Foundation.
 *
 * 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.
 *
 */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/time.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/hrtimer.h>
#include <linux/delay.h>
#include <mach/hardware.h>
#include <linux/io.h>
#include <mach/iommu_domains.h>
#include <asm/system.h>
#include <asm/mach-types.h>
#include <linux/semaphore.h>
#include <linux/spinlock.h>

#include <linux/fb.h>

#include "mdp.h"
#include "msm_fb.h"
#include "mdp4.h"
enum {
	WB_OPEN,
	WB_START,
	WB_STOPING,
	WB_STOP
};
enum {
	REGISTERED,
	IN_FREE_QUEUE,
	IN_BUSY_QUEUE,
	WITH_CLIENT
};

#define MAX_CONTROLLER	1
#define VSYNC_EXPIRE_TICK 0

static struct vsycn_ctrl {
	struct device *dev;
	int inited;
	int update_ndx;
	u32 ov_koff;
	u32 ov_done;
	atomic_t suspend;
	struct mutex update_lock;
	struct completion ov_comp;
	spinlock_t spin_lock;
	struct msm_fb_data_type *mfd;
	struct mdp4_overlay_pipe *base_pipe;
	struct vsync_update vlist[2];
	struct work_struct clk_work;
} vsync_ctrl_db[MAX_CONTROLLER];

static void vsync_irq_enable(int intr, int term)
{
	unsigned long flag;

	spin_lock_irqsave(&mdp_spin_lock, flag);
	/* no need to clrear other interrupts for comamnd mode */
	mdp_intr_mask |= intr;
	outp32(MDP_INTR_ENABLE, mdp_intr_mask);
	mdp_enable_irq(term);
	spin_unlock_irqrestore(&mdp_spin_lock, flag);
}

static void vsync_irq_disable(int intr, int term)
{
	unsigned long flag;

	spin_lock_irqsave(&mdp_spin_lock, flag);
	/* no need to clrear other interrupts for comamnd mode */
	mdp_intr_mask &= ~intr;
	outp32(MDP_INTR_ENABLE, mdp_intr_mask);
	mdp_disable_irq_nosync(term);
	spin_unlock_irqrestore(&mdp_spin_lock, flag);
}

static int mdp4_overlay_writeback_update(struct msm_fb_data_type *mfd);
static void mdp4_wfd_queue_wakeup(struct msm_fb_data_type *mfd,
		struct msmfb_writeback_data_list *node);
static int mdp4_wfd_dequeue_update(struct msm_fb_data_type *mfd,
		struct msmfb_writeback_data_list **wfdnode);

int mdp4_overlay_writeback_on(struct platform_device *pdev)
{
	struct msm_fb_data_type *mfd;
	struct fb_info *fbi;
	uint8 *buf;
	struct mdp4_overlay_pipe *pipe;
	int bpp;
	int ret;
	uint32 data;
	struct vsycn_ctrl *vctrl;
	int cndx = 0;

	mfd = (struct msm_fb_data_type *)platform_get_drvdata(pdev);

	if (!mfd)
		return -ENODEV;

	if (mfd->key != MFD_KEY)
		return -EINVAL;

	vctrl = &vsync_ctrl_db[cndx];
	vctrl->mfd = mfd;
	vctrl->dev = mfd->fbi->dev;

	fbi = mfd->fbi;

	bpp = fbi->var.bits_per_pixel / 8;
	buf = (uint8 *) fbi->fix.smem_start;
	buf += fbi->var.xoffset * bpp +
		fbi->var.yoffset * fbi->fix.line_length;

	/* MDP cmd block enable */
	mdp_clk_ctrl(1);

	if (vctrl->base_pipe == NULL) {
		pipe = mdp4_overlay_pipe_alloc(OVERLAY_TYPE_BF, MDP4_MIXER2);
		if (pipe == NULL) {
			pr_info("%s: pipe_alloc failed\n", __func__);
			mdp_clk_ctrl(0);
			return -EIO;
		}
		pipe->pipe_used++;
		pipe->mixer_stage  = MDP4_MIXER_STAGE_BASE;
		pipe->mixer_num  = MDP4_MIXER2;
		pipe->src_format = MDP_ARGB_8888;
		mdp4_overlay_panel_mode(pipe->mixer_num, MDP4_PANEL_WRITEBACK);
		ret = mdp4_overlay_format2pipe(pipe);
		if (ret < 0)
			pr_info("%s: format2type failed\n", __func__);

		vctrl->base_pipe = pipe; /* keep it */

	} else {
		pipe = vctrl->base_pipe;
	}

	ret = panel_next_on(pdev);

	/* MDP_LAYERMIXER_WB_MUX_SEL to use mixer1 axi for mixer2 writeback */
	if (hdmi_prim_display)
		data = 0x01;
	else
		data = 0x02;
	outpdw(MDP_BASE + 0x100F4, data);

	MDP_OUTP(MDP_BASE + MDP4_OVERLAYPROC1_BASE + 0x5004,
		((0x0 & 0xFFF) << 16) | /* 12-bit B */
			(0x0 & 0xFFF));         /* 12-bit G */
	/* MSP_BORDER_COLOR */
	MDP_OUTP(MDP_BASE + MDP4_OVERLAYPROC1_BASE + 0x5008,
		(0x0 & 0xFFF));         /* 12-bit R */

	mdp_clk_ctrl(0);

	atomic_set(&vctrl->suspend, 0);

	return ret;
}

static void mdp4_wfd_wait4ov(int cndx);
static void mdp4_writeback_pipe_clean(struct vsync_update *vp);

int mdp4_overlay_writeback_off(struct platform_device *pdev)
{
	int cndx = 0;
	struct msm_fb_data_type *mfd;
	struct vsycn_ctrl *vctrl;
	struct mdp4_overlay_pipe *pipe;
	int ret = 0;
	int undx;
	struct vsync_update *vp;

	pr_debug("%s+:\n", __func__);

	mfd = (struct msm_fb_data_type *)platform_get_drvdata(pdev);

	vctrl = &vsync_ctrl_db[cndx];
	pipe = vctrl->base_pipe;

	atomic_set(&vctrl->suspend, 1);

	if (pipe == NULL) {
		pr_err("%s: NO base pipe\n", __func__);
		return ret;
	}

	complete(&vctrl->ov_comp);
	msleep(20);
	mdp_clk_ctrl(1);
	/* sanity check, free pipes besides base layer */
	mdp4_overlay_unset_mixer(pipe->mixer_num);
	mdp4_mixer_stage_down(pipe, 1);
	mdp4_overlay_pipe_free(pipe);
	vctrl->base_pipe = NULL;
	mdp_clk_ctrl(0);
	undx =  vctrl->update_ndx;
	vp = &vctrl->vlist[undx];
	if (vp->update_cnt) {
		/*
		 * pipe's iommu will be freed at next overlay play
		 * and iommu_drop statistic will be increased by one
		 */
		pr_warn("%s: update_cnt=%d\n", __func__, vp->update_cnt);
		mdp4_writeback_pipe_clean(vp);
	}

	ret = panel_next_off(pdev);

	mdp_clk_ctrl(1);
	/* MDP_LAYERMIXER_WB_MUX_SEL to restore to default cfg*/
	outpdw(MDP_BASE + 0x100F4, 0x0);
	mdp_clk_ctrl(0);

	pr_debug("%s-:\n", __func__);
	return ret;
}

static int mdp4_overlay_writeback_update(struct msm_fb_data_type *mfd)
{
	struct fb_info *fbi;
	uint8 *buf;
	unsigned int buf_offset;
	struct mdp4_overlay_pipe *pipe;
	int bpp;
	int cndx = 0;
	struct vsycn_ctrl *vctrl;

	if (mfd->key != MFD_KEY)
		return -ENODEV;


	fbi = mfd->fbi;

	vctrl = &vsync_ctrl_db[cndx];

	pipe = vctrl->base_pipe;
	if (!pipe) {
		pr_err("%s: no base layer pipe\n", __func__);
		return -EINVAL;
	}

	bpp = fbi->var.bits_per_pixel / 8;
	buf = (uint8 *) fbi->fix.smem_start;
	buf_offset = fbi->var.xoffset * bpp +
		fbi->var.yoffset * fbi->fix.line_length;

	/* MDP cmd block enable */
	mdp_clk_ctrl(1);

	pipe->src_height = fbi->var.yres;
	pipe->src_width = fbi->var.xres;
	pipe->src_h = fbi->var.yres;
	pipe->src_w = fbi->var.xres;
	pipe->dst_h = fbi->var.yres;
	pipe->dst_w = fbi->var.xres;
	pipe->srcp0_ystride = fbi->fix.line_length;
	pipe->src_y = 0;
	pipe->src_x = 0;
	pipe->dst_y = 0;
	pipe->dst_x = 0;

	mdp4_overlay_mdp_pipe_req(pipe, mfd);
	mdp4_calc_blt_mdp_bw(mfd, pipe);

	if (mfd->display_iova)
		pipe->srcp0_addr = mfd->display_iova + buf_offset;
	else
		pipe->srcp0_addr = (uint32)(buf + buf_offset);

	mdp4_mixer_stage_up(pipe, 0);

	mdp4_overlayproc_cfg(pipe);

	if (hdmi_prim_display)
		outpdw(MDP_BASE + 0x100F4, 0x01);
	else
		outpdw(MDP_BASE + 0x100F4, 0x02);

	/* MDP cmd block disable */
	mdp_clk_ctrl(0);

	wmb();
	return 0;
}

/*
 * mdp4_wfd_piep_queue:
 * called from thread context
 */
void mdp4_wfd_pipe_queue(int cndx, struct mdp4_overlay_pipe *pipe)
{
	struct vsycn_ctrl *vctrl;
	struct vsync_update *vp;
	struct mdp4_overlay_pipe *pp;
	int undx;

	if (cndx >= MAX_CONTROLLER) {
		pr_err("%s: out or range: cndx=%d\n", __func__, cndx);
		return;
	}

	vctrl = &vsync_ctrl_db[cndx];

	if (atomic_read(&vctrl->suspend) > 0)
		return;

	mutex_lock(&vctrl->update_lock);
	undx =  vctrl->update_ndx;
	vp = &vctrl->vlist[undx];

	pp = &vp->plist[pipe->pipe_ndx - 1];	/* ndx start form 1 */

	pr_debug("%s: vndx=%d pipe_ndx=%d pid=%d\n", __func__,
		undx, pipe->pipe_ndx, current->pid);

	*pp = *pipe;	/* clone it */
	vp->update_cnt++;

	mutex_unlock(&vctrl->update_lock);
	mdp4_stat.overlay_play[pipe->mixer_num]++;
}

static void mdp4_writeback_pipe_clean(struct vsync_update *vp)
{
	struct mdp4_overlay_pipe *pipe;
	int i;

	pipe = vp->plist;
	for (i = 0; i < OVERLAY_PIPE_MAX; i++, pipe++) {
		if (pipe->pipe_used) {
			mdp4_overlay_iommu_pipe_free(pipe->pipe_ndx, 0);
			pipe->pipe_used = 0; /* clear */
		}
	}
	vp->update_cnt = 0;     /* empty queue */
}

int mdp4_wfd_pipe_commit(struct msm_fb_data_type *mfd,
			int cndx, int wait)
{
	int  i, undx;
	int mixer = 0;
	struct vsycn_ctrl *vctrl;
	struct vsync_update *vp;
	struct mdp4_overlay_pipe *pipe;
	struct mdp4_overlay_pipe *real_pipe;
	unsigned long flags;
	int cnt = 0;
	struct msmfb_writeback_data_list *node = NULL;
	int rc = 0;

	vctrl = &vsync_ctrl_db[cndx];

	mutex_lock(&vctrl->update_lock);
	undx =  vctrl->update_ndx;
	vp = &vctrl->vlist[undx];
	pipe = vctrl->base_pipe;
	mixer = pipe->mixer_num;

	/*
	 * allow stage_commit without pipes queued
	 * (vp->update_cnt == 0) to unstage pipes after
	 * overlay_unset
	 */

	vctrl->update_ndx++;
	vctrl->update_ndx &= 0x01;
	vp->update_cnt = 0;     /* reset */
	mutex_unlock(&vctrl->update_lock);

	rc = mdp4_wfd_dequeue_update(mfd, &node);
	if (rc != 0) {
		pr_err("%s: mdp4_wfd_dequeue_update failed !! mfd=%x\n",
			__func__, (int)mfd);
		pipe = vp->plist;
		for (i = 0; i < OVERLAY_PIPE_MAX; i++, pipe++) {
			pipe->pipe_used = 0;
			pr_info("%s: dequeue update failed, unsetting pipes\n",
				__func__);
		}
		return cnt;
	}
	/* free previous committed iommu back to pool */
	mdp4_overlay_iommu_unmap_freelist(mixer);

	mdp_clk_ctrl(1);
	pipe = vp->plist;
	for (i = 0; i < OVERLAY_PIPE_MAX; i++, pipe++) {
		if (pipe->pipe_used) {
			cnt++;
			real_pipe = mdp4_overlay_ndx2pipe(pipe->pipe_ndx);
			if (real_pipe && real_pipe->pipe_used) {
				/* pipe not unset */
				mdp4_overlay_vsync_commit(pipe);
			}
			/* free previous iommu to freelist
			* which will be freed at next
			* pipe_commit
			*/
			mdp4_overlay_iommu_pipe_free(pipe->pipe_ndx, 0);
			pipe->pipe_used = 0; /* clear */
		}
	}

	mdp_clk_ctrl(1);

	mdp4_mixer_stage_commit(mixer);

	pipe = vctrl->base_pipe;
	spin_lock_irqsave(&vctrl->spin_lock, flags);
	vctrl->ov_koff++;
	INIT_COMPLETION(vctrl->ov_comp);
	vsync_irq_enable(INTR_OVERLAY2_DONE, MDP_OVERLAY2_TERM);
	pr_debug("%s: kickoff\n", __func__);
	/* kickoff overlay engine */
	mdp4_stat.kickoff_ov2++;
	outpdw(MDP_BASE + 0x00D0, 0);
	mb(); /* make sure kickoff executed */
	spin_unlock_irqrestore(&vctrl->spin_lock, flags);

	mdp4_stat.overlay_commit[pipe->mixer_num]++;

	if (wait)
		mdp4_wfd_wait4ov(cndx);

	mdp4_wfd_queue_wakeup(mfd, node);

	return cnt;
}

static void clk_ctrl_work(struct work_struct *work)
{
	struct vsycn_ctrl *vctrl =
		container_of(work, typeof(*vctrl), clk_work);
	mdp_clk_ctrl(0);
}

void mdp4_wfd_init(int cndx)
{
	struct vsycn_ctrl *vctrl;

	if (cndx >= MAX_CONTROLLER) {
		pr_err("%s: out or range: cndx=%d\n", __func__, cndx);
		return;
	}

	vctrl = &vsync_ctrl_db[cndx];
	if (vctrl->inited)
		return;

	vctrl->inited = 1;
	vctrl->update_ndx = 0;
	mutex_init(&vctrl->update_lock);
	init_completion(&vctrl->ov_comp);
	atomic_set(&vctrl->suspend, 1);
	spin_lock_init(&vctrl->spin_lock);
	INIT_WORK(&vctrl->clk_work, clk_ctrl_work);
}

static void mdp4_wfd_wait4ov(int cndx)
{
	struct vsycn_ctrl *vctrl;

	if (cndx >= MAX_CONTROLLER) {
		pr_err("%s: out or range: cndx=%d\n", __func__, cndx);
		return;
	}

	vctrl = &vsync_ctrl_db[cndx];

	if (atomic_read(&vctrl->suspend) > 0)
		return;

	wait_for_completion(&vctrl->ov_comp);
}


void mdp4_overlay2_done_wfd(struct mdp_dma_data *dma)
{
	struct vsycn_ctrl *vctrl;
	struct mdp4_overlay_pipe *pipe;
	int cndx = 0;

	vctrl = &vsync_ctrl_db[cndx];
	pipe = vctrl->base_pipe;

	spin_lock(&vctrl->spin_lock);
	vsync_irq_disable(INTR_OVERLAY2_DONE, MDP_OVERLAY2_TERM);
	vctrl->ov_done++;
	complete(&vctrl->ov_comp);
	schedule_work(&vctrl->clk_work);
	pr_debug("%s ovdone interrupt\n", __func__);
	spin_unlock(&vctrl->spin_lock);
}

void mdp4_writeback_overlay(struct msm_fb_data_type *mfd)
{
	struct vsycn_ctrl *vctrl;
	struct mdp4_overlay_pipe *pipe;

	if (mfd && !mfd->panel_power_on)
		return;

	pr_debug("%s:+ mfd=%x\n", __func__, (int)mfd);

	vctrl = &vsync_ctrl_db[0];
	pipe = vctrl->base_pipe;

	mutex_lock(&mfd->dma->ov_mutex);

	if (pipe->pipe_type == OVERLAY_TYPE_RGB)
		mdp4_wfd_pipe_queue(0, pipe);

	mdp4_overlay_mdp_perf_upd(mfd, 1);

	mdp4_wfd_pipe_commit(mfd, 0, 1);

	mdp4_overlay_mdp_perf_upd(mfd, 0);

	mutex_unlock(&mfd->dma->ov_mutex);
}

static int mdp4_overlay_writeback_register_buffer(
	struct msm_fb_data_type *mfd, struct msmfb_writeback_data_list *node)
{
	if (!node) {
		pr_err("Cannot register a NULL node\n");
		return -EINVAL;
	}
	node->state = REGISTERED;
	list_add_tail(&node->registered_entry, &mfd->writeback_register_queue);
	return 0;
}
static struct msmfb_writeback_data_list *get_if_registered(
			struct msm_fb_data_type *mfd, struct msmfb_data *data)
{
	struct msmfb_writeback_data_list *temp;
	bool found = false;
	int domain;

	if (!list_empty(&mfd->writeback_register_queue)) {
		list_for_each_entry(temp,
				&mfd->writeback_register_queue,
				registered_entry) {
			if (temp && temp->buf_info.iova == data->iova) {
				found = true;
				break;
			}
		}
	}
	if (!found) {
		temp = kzalloc(sizeof(struct msmfb_writeback_data_list),
				GFP_KERNEL);
		if (temp == NULL) {
			pr_err("%s: out of memory\n", __func__);
			goto register_alloc_fail;
		}
		temp->ihdl = NULL;
		if (data->iova)
			temp->addr = (void *)(data->iova + data->offset);
		else if (mfd->iclient) {
			struct ion_handle *srcp_ihdl;
			ulong len;
			srcp_ihdl = ion_import_dma_buf(mfd->iclient,
						  data->memory_id);
			if (IS_ERR_OR_NULL(srcp_ihdl)) {
				pr_err("%s: ion import fd failed\n", __func__);
				goto register_ion_fail;
			}

			if (mdp_iommu_split_domain)
				domain = DISPLAY_WRITE_DOMAIN;
			else
				domain = DISPLAY_READ_DOMAIN;

			if (ion_map_iommu(mfd->iclient,
					  srcp_ihdl,
					  domain,
					  GEN_POOL,
					  SZ_4K,
					  0,
					  (ulong *)&temp->addr,
					  (ulong *)&len,
					  0,
					  ION_IOMMU_UNMAP_DELAYED)) {
				ion_free(mfd->iclient, srcp_ihdl);
				pr_err("%s: unable to get ion mapping addr\n",
				       __func__);
				goto register_ion_fail;
			}
			temp->addr += data->offset;
			temp->ihdl = srcp_ihdl;
		}
		else {
			pr_err("%s: only support ion memory\n", __func__);
			goto register_ion_fail;
		}

		memcpy(&temp->buf_info, data, sizeof(struct msmfb_data));
		if (mdp4_overlay_writeback_register_buffer(mfd, temp)) {
			pr_err("%s: error registering node\n", __func__);
			goto register_ion_fail;
		}
	}
	return temp;
 register_ion_fail:
	kfree(temp);
 register_alloc_fail:
	return NULL;
}
int mdp4_writeback_start(
		struct fb_info *info)
{
	struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
	mutex_lock(&mfd->writeback_mutex);
	mfd->writeback_state = WB_START;
	mutex_unlock(&mfd->writeback_mutex);
	wake_up(&mfd->wait_q);
	return 0;
}

int mdp4_writeback_queue_buffer(struct fb_info *info, struct msmfb_data *data)
{
	struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
	struct msmfb_writeback_data_list *node = NULL;
	int rv = 0;

	mutex_lock(&mfd->writeback_mutex);
	node = get_if_registered(mfd, data);
	if (!node || node->state == IN_BUSY_QUEUE ||
		node->state == IN_FREE_QUEUE) {
		pr_err("memory not registered or Buffer already with us\n");
		rv = -EINVAL;
		goto exit;
	}

	list_add_tail(&node->active_entry, &mfd->writeback_free_queue);
	node->state = IN_FREE_QUEUE;

exit:
	mutex_unlock(&mfd->writeback_mutex);
	return rv;
}
static int is_buffer_ready(struct msm_fb_data_type *mfd)
{
	int rc;
	mutex_lock(&mfd->writeback_mutex);
	rc = !list_empty(&mfd->writeback_busy_queue) ||
			(mfd->writeback_state == WB_STOPING);
	mutex_unlock(&mfd->writeback_mutex);
	return rc;
}
int mdp4_writeback_dequeue_buffer(struct fb_info *info, struct msmfb_data *data)
{
	struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
	struct msmfb_writeback_data_list *node = NULL;
	int rc = 0, domain;

	rc = wait_event_interruptible(mfd->wait_q, is_buffer_ready(mfd));
	if (rc) {
		pr_err("failed to get dequeued buffer\n");
		return -ENOBUFS;
	}
	mutex_lock(&mfd->writeback_mutex);
	if (mfd->writeback_state == WB_STOPING) {
		mfd->writeback_state = WB_STOP;
		mutex_unlock(&mfd->writeback_mutex);
		return -ENOBUFS;
	} else	if (!list_empty(&mfd->writeback_busy_queue)) {
		node = list_first_entry(&mfd->writeback_busy_queue,
				struct msmfb_writeback_data_list, active_entry);
	}
	if (node) {
		list_del(&node->active_entry);
		node->state = WITH_CLIENT;
		memcpy(data, &node->buf_info, sizeof(struct msmfb_data));
		if (!data->iova)
			if (mfd->iclient && node->ihdl) {
				if (mdp_iommu_split_domain)
					domain = DISPLAY_WRITE_DOMAIN;
				else
					domain = DISPLAY_READ_DOMAIN;

				ion_unmap_iommu(mfd->iclient,
						node->ihdl,
						domain,
						GEN_POOL);
				ion_free(mfd->iclient,
					 node->ihdl);
			}
	} else {
		pr_err("node is NULL. Somebody else dequeued?\n");
		rc = -ENOBUFS;
	}
	mutex_unlock(&mfd->writeback_mutex);
	return rc;
}

static bool is_writeback_inactive(struct msm_fb_data_type *mfd)
{
	bool active;
	mutex_lock(&mfd->writeback_mutex);
	active = !mfd->writeback_active_cnt;
	mutex_unlock(&mfd->writeback_mutex);
	return active;
}
int mdp4_writeback_stop(struct fb_info *info)
{
	struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
	mutex_lock(&mfd->writeback_mutex);
	mfd->writeback_state = WB_STOPING;
	mutex_unlock(&mfd->writeback_mutex);
	/* Wait for all pending writebacks to finish */
	wait_event_interruptible(mfd->wait_q, is_writeback_inactive(mfd));

	/* Wake up dequeue thread in case of no UI update*/
	wake_up(&mfd->wait_q);

	return 0;
}
int mdp4_writeback_init(struct fb_info *info)
{
	struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
	mutex_init(&mfd->writeback_mutex);
	mutex_init(&mfd->unregister_mutex);
	INIT_LIST_HEAD(&mfd->writeback_free_queue);
	INIT_LIST_HEAD(&mfd->writeback_busy_queue);
	INIT_LIST_HEAD(&mfd->writeback_register_queue);
	mfd->writeback_state = WB_OPEN;
	init_waitqueue_head(&mfd->wait_q);
	return 0;
}
int mdp4_writeback_terminate(struct fb_info *info)
{
	struct list_head *ptr, *next;
	struct msmfb_writeback_data_list *temp;
	struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
	int rc = 0;

	mutex_lock(&mfd->unregister_mutex);
	mutex_lock(&mfd->writeback_mutex);

	if (mfd->writeback_state != WB_STOPING &&
		mfd->writeback_state != WB_STOP) {
		pr_err("%s called without stopping\n", __func__);
		rc = -EPERM;
		goto terminate_err;

	}

	if (!list_empty(&mfd->writeback_register_queue)) {
		list_for_each_safe(ptr, next,
				&mfd->writeback_register_queue) {
			temp = list_entry(ptr,
					struct msmfb_writeback_data_list,
					registered_entry);
			list_del(&temp->registered_entry);
			kfree(temp);
		}
	}
	INIT_LIST_HEAD(&mfd->writeback_register_queue);
	INIT_LIST_HEAD(&mfd->writeback_busy_queue);
	INIT_LIST_HEAD(&mfd->writeback_free_queue);


terminate_err:
	mutex_unlock(&mfd->writeback_mutex);
	mutex_unlock(&mfd->unregister_mutex);
	return rc;
}

static int mdp4_wfd_dequeue_update(struct msm_fb_data_type *mfd,
			struct msmfb_writeback_data_list **wfdnode)
{
	struct vsycn_ctrl *vctrl;
	struct mdp4_overlay_pipe *pipe;
	struct msmfb_writeback_data_list *node = NULL;

	if (mfd && !mfd->panel_power_on)
		return -EPERM;

	pr_debug("%s:+ mfd=%x\n", __func__, (int)mfd);

	vctrl = &vsync_ctrl_db[0];
	pipe = vctrl->base_pipe;

	mutex_lock(&mfd->unregister_mutex);
	mutex_lock(&mfd->writeback_mutex);
	if (!list_empty(&mfd->writeback_free_queue)
		&& mfd->writeback_state != WB_STOPING
		&& mfd->writeback_state != WB_STOP) {
		node = list_first_entry(&mfd->writeback_free_queue,
				struct msmfb_writeback_data_list, active_entry);
	}
	if (node) {
		list_del(&(node->active_entry));
		node->state = IN_BUSY_QUEUE;
		mfd->writeback_active_cnt++;
	}
	mutex_unlock(&mfd->writeback_mutex);

	pipe->ov_blt_addr = (ulong) (node ? node->addr : NULL);

	if (!pipe->ov_blt_addr) {
		pr_err("%s: no writeback buffer 0x%x, %p\n", __func__,
			(unsigned int)pipe->ov_blt_addr, node);

		if (node) {
			mutex_lock(&mfd->writeback_mutex);
			list_add_tail(&node->active_entry,
				&mfd->writeback_free_queue);
			node->state = IN_FREE_QUEUE;
			mfd->writeback_active_cnt--;
			mutex_unlock(&mfd->writeback_mutex);
		}

		mutex_unlock(&mfd->unregister_mutex);
		return -EINVAL;
	}

	mdp4_overlay_writeback_update(mfd);

	*wfdnode = node;

	mutex_unlock(&mfd->unregister_mutex);
	return 0;
}

static void mdp4_wfd_queue_wakeup(struct msm_fb_data_type *mfd,
			struct msmfb_writeback_data_list *node)
{

	if (mfd && !mfd->panel_power_on)
		return;

	if (node == NULL)
		return;

	pr_debug("%s: mfd=%x node: %p", __func__, (int)mfd, node);

	mutex_lock(&mfd->writeback_mutex);
	list_add_tail(&node->active_entry, &mfd->writeback_busy_queue);
	mfd->writeback_active_cnt--;
	mutex_unlock(&mfd->writeback_mutex);
	wake_up(&mfd->wait_q);
}

int mdp4_writeback_set_mirroring_hint(struct fb_info *info, int hint)
{
	struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;

	if (mfd->panel.type != WRITEBACK_PANEL)
		return -ENOTSUPP;

	switch (hint) {
	case MDP_WRITEBACK_MIRROR_ON:
	case MDP_WRITEBACK_MIRROR_PAUSE:
	case MDP_WRITEBACK_MIRROR_RESUME:
	case MDP_WRITEBACK_MIRROR_OFF:
		pr_info("wfd state switched to %d\n", hint);
		switch_set_state(&mfd->writeback_sdev, hint);
		return 0;
	default:
		return -EINVAL;
	}
}
