mdss: display: add dynamic mdp clock control for smart panel

For smart panel, the clock can be turned off during static screen
update to save power. This patch adds support to control mdp related
clocks dynamically.

Change-Id: I0328e156d5c0b42822f0f81aa0a8195159a1eeb7
Signed-off-by: Kuogee Hsieh <khsieh@codeaurora.org>
diff --git a/drivers/video/msm/mdss/mdss.h b/drivers/video/msm/mdss/mdss.h
index 04217f5..5be0173 100644
--- a/drivers/video/msm/mdss/mdss.h
+++ b/drivers/video/msm/mdss/mdss.h
@@ -62,8 +62,6 @@
 	struct regulator *fs;
 	u32 max_mdp_clk_rate;
 
-	struct workqueue_struct *clk_ctrl_wq;
-	struct work_struct clk_ctrl_worker;
 	struct platform_device *pdev;
 	char __iomem *mdp_base;
 	size_t mdp_reg_size;
@@ -80,7 +78,6 @@
 	u32 mdp_hist_irq_mask;
 
 	int suspend_fs_ena;
-	atomic_t clk_ref;
 	u8 clk_ena;
 	u8 fs_ena;
 	u8 vsync_ena;
diff --git a/drivers/video/msm/mdss/mdss_mdp.c b/drivers/video/msm/mdss/mdss_mdp.c
index a3be773..2f09fee 100644
--- a/drivers/video/msm/mdss/mdss_mdp.c
+++ b/drivers/video/msm/mdss/mdss_mdp.c
@@ -621,71 +621,43 @@
 	return clk_rate;
 }
 
-static void mdss_mdp_clk_ctrl_update(struct mdss_data_type *mdata)
-{
-	int enable;
-
-	mutex_lock(&mdp_clk_lock);
-	enable = atomic_read(&mdata->clk_ref) > 0;
-	if (mdata->clk_ena == enable) {
-		mutex_unlock(&mdp_clk_lock);
-		return;
-	}
-	mdata->clk_ena = enable;
-
-	if (enable)
-		pm_runtime_get_sync(&mdata->pdev->dev);
-
-	pr_debug("MDP CLKS %s\n", (enable ? "Enable" : "Disable"));
-	mb();
-
-	mdss_mdp_clk_update(MDSS_CLK_AHB, enable);
-	mdss_mdp_clk_update(MDSS_CLK_AXI, enable);
-
-	mdss_mdp_clk_update(MDSS_CLK_MDP_CORE, enable);
-	mdss_mdp_clk_update(MDSS_CLK_MDP_LUT, enable);
-	if (mdata->vsync_ena)
-		mdss_mdp_clk_update(MDSS_CLK_MDP_VSYNC, enable);
-
-	if (!enable)
-		pm_runtime_put(&mdata->pdev->dev);
-
-	mutex_unlock(&mdp_clk_lock);
-}
-
-static void mdss_mdp_clk_ctrl_workqueue_handler(struct work_struct *work)
-{
-	struct mdss_data_type *mdata;
-
-	mdata = container_of(work, struct mdss_data_type, clk_ctrl_worker);
-	mdss_mdp_clk_ctrl_update(mdata);
-}
-
 void mdss_mdp_clk_ctrl(int enable, int isr)
 {
 	struct mdss_data_type *mdata = mdss_res;
+	static int mdp_clk_cnt;
+	int changed = 0;
 
-	pr_debug("clk enable=%d isr=%d ref= %d\n", enable, isr,
-			atomic_read(&mdata->clk_ref));
-
-	if (enable == MDP_BLOCK_POWER_ON) {
-		BUG_ON(isr);
-
-		if (atomic_inc_return(&mdata->clk_ref) == 1)
-			mdss_mdp_clk_ctrl_update(mdata);
+	mutex_lock(&mdp_clk_lock);
+	if (enable) {
+		if (mdp_clk_cnt == 0)
+			changed++;
+		mdp_clk_cnt++;
 	} else {
-		BUG_ON(atomic_read(&mdata->clk_ref) == 0);
-
-		if (atomic_dec_and_test(&mdata->clk_ref)) {
-			if (isr)
-				queue_work(mdata->clk_ctrl_wq,
-						&mdata->clk_ctrl_worker);
-			else
-				mdss_mdp_clk_ctrl_update(mdata);
-		}
+		mdp_clk_cnt--;
+		if (mdp_clk_cnt == 0)
+			changed++;
 	}
 
+	pr_debug("%s: clk_cnt=%d changed=%d enable=%d\n",
+			__func__, mdp_clk_cnt, changed, enable);
 
+	if (changed) {
+		mdata->clk_ena = enable;
+		if (enable)
+			pm_runtime_get_sync(&mdata->pdev->dev);
+
+		mdss_mdp_clk_update(MDSS_CLK_AHB, enable);
+		mdss_mdp_clk_update(MDSS_CLK_AXI, enable);
+		mdss_mdp_clk_update(MDSS_CLK_MDP_CORE, enable);
+		mdss_mdp_clk_update(MDSS_CLK_MDP_LUT, enable);
+		if (mdata->vsync_ena)
+			mdss_mdp_clk_update(MDSS_CLK_MDP_VSYNC, enable);
+
+		if (!enable)
+			pm_runtime_put(&mdata->pdev->dev);
+	}
+
+	mutex_unlock(&mdp_clk_lock);
 }
 
 static inline int mdss_mdp_irq_clk_register(struct mdss_data_type *mdata,
@@ -934,9 +906,6 @@
 	if (rc)
 		return rc;
 
-	mdata->clk_ctrl_wq = create_singlethread_workqueue("mdp_clk_wq");
-	INIT_WORK(&mdata->clk_ctrl_worker, mdss_mdp_clk_ctrl_workqueue_handler);
-
 	mdata->iclient = msm_ion_client_create(-1, mdata->pdev->name);
 	if (IS_ERR_OR_NULL(mdata->iclient)) {
 		pr_err("msm_ion_client_create() return error (%p)\n",
@@ -1572,8 +1541,6 @@
 
 static inline int mdss_mdp_suspend_sub(struct mdss_data_type *mdata)
 {
-	flush_workqueue(mdata->clk_ctrl_wq);
-
 	mdata->suspend_fs_ena = mdata->fs_ena;
 	mdss_mdp_footswitch_ctrl(mdata, false);
 
@@ -1671,8 +1638,6 @@
 
 	dev_dbg(dev, "pm_runtime: idling...\n");
 
-	flush_workqueue(mdata->clk_ctrl_wq);
-
 	return 0;
 }
 
diff --git a/drivers/video/msm/mdss/mdss_mdp_intf_cmd.c b/drivers/video/msm/mdss/mdss_mdp_intf_cmd.c
index 006a8dd..fff8a6e 100644
--- a/drivers/video/msm/mdss/mdss_mdp_intf_cmd.c
+++ b/drivers/video/msm/mdss/mdss_mdp_intf_cmd.c
@@ -15,20 +15,32 @@
 #include "mdss_panel.h"
 #include "mdss_mdp.h"
 
+#define VSYNC_EXPIRE_TICK 4
+
 #define START_THRESHOLD 4
 #define CONTINUE_TRESHOLD 4
 
 #define MAX_SESSIONS 2
 
+/* wait for at most 2 vsync for lowest refresh rate (24hz) */
+#define KOFF_TIMEOUT msecs_to_jiffies(84)
+
 struct mdss_mdp_cmd_ctx {
 	u32 pp_num;
 	u8 ref_cnt;
-
 	struct completion pp_comp;
+	struct completion stop_comp;
 	atomic_t vsync_ref;
-	spinlock_t vsync_lock;
-	mdp_vsync_handler_t vsync_handler;
+	mdp_vsync_handler_t send_vsync;
 	int panel_on;
+	int koff_cnt;
+	int clk_enabled;
+	int clk_control;
+	int vsync_enabled;
+	int expire;
+	struct mutex clk_mtx;
+	spinlock_t clk_lock;
+	struct work_struct clk_work;
 
 	/* te config */
 	u8 tear_check;
@@ -118,54 +130,6 @@
 	return 0;
 }
 
-static inline void cmd_readptr_irq_enable(struct mdss_mdp_ctl *ctl)
-{
-	struct mdss_mdp_cmd_ctx *ctx = ctl->priv_data;
-
-	if (atomic_inc_return(&ctx->vsync_ref) == 1) {
-		pr_debug("%s:\n", __func__);
-		mdss_mdp_irq_enable(MDSS_MDP_IRQ_PING_PONG_RD_PTR, ctx->pp_num);
-	}
-}
-
-static inline void cmd_readptr_irq_disable(struct mdss_mdp_ctl *ctl)
-{
-	struct mdss_mdp_cmd_ctx *ctx = ctl->priv_data;
-
-	if (atomic_dec_return(&ctx->vsync_ref) == 0) {
-		pr_debug("%s:\n", __func__);
-		mdss_mdp_irq_disable(MDSS_MDP_IRQ_PING_PONG_RD_PTR,
-							ctx->pp_num);
-	}
-}
-
-int mdss_mdp_cmd_set_vsync_handler(struct mdss_mdp_ctl *ctl,
-		mdp_vsync_handler_t vsync_handler)
-{
-	struct mdss_mdp_cmd_ctx *ctx;
-	unsigned long flags;
-
-	ctx = (struct mdss_mdp_cmd_ctx *) ctl->priv_data;
-	if (!ctx) {
-		pr_err("invalid ctx for ctl=%d\n", ctl->num);
-		return -ENODEV;
-	}
-
-	spin_lock_irqsave(&ctx->vsync_lock, flags);
-
-	if (!ctx->vsync_handler && vsync_handler) {
-		ctx->vsync_handler = vsync_handler;
-		cmd_readptr_irq_enable(ctl);
-	} else if (ctx->vsync_handler && !vsync_handler) {
-		cmd_readptr_irq_disable(ctl);
-		ctx->vsync_handler = vsync_handler;
-	}
-
-	spin_unlock_irqrestore(&ctx->vsync_lock, flags);
-
-	return 0;
-}
-
 static void mdss_mdp_cmd_readptr_done(void *arg)
 {
 	struct mdss_mdp_ctl *ctl = arg;
@@ -177,15 +141,29 @@
 		return;
 	}
 
-	pr_debug("%s: ctl=%d intf_num=%d\n", __func__, ctl->num, ctl->intf_num);
+	pr_debug("%s: num=%d ctx=%d expire=%d koff=%d\n", __func__, ctl->num,
+			ctx->pp_num, ctx->expire, ctx->koff_cnt);
 
 	vsync_time = ktime_get();
 	ctl->vsync_cnt++;
 
-	spin_lock(&ctx->vsync_lock);
-	if (ctx->vsync_handler)
-		ctx->vsync_handler(ctl, vsync_time);
-	spin_unlock(&ctx->vsync_lock);
+	spin_lock(&ctx->clk_lock);
+	if (ctx->send_vsync)
+		ctx->send_vsync(ctl, vsync_time);
+
+	if (ctx->expire) {
+		ctx->expire--;
+		if (ctx->expire == 0) {
+			if (ctx->koff_cnt <= 0) {
+				ctx->clk_control = 1;
+				schedule_work(&ctx->clk_work);
+			} else {
+				/* put off one vsync */
+				ctx->expire += 1;
+			}
+		}
+	}
+	spin_unlock(&ctx->clk_lock);
 }
 
 static void mdss_mdp_cmd_pingpong_done(void *arg)
@@ -193,12 +171,161 @@
 	struct mdss_mdp_ctl *ctl = arg;
 	struct mdss_mdp_cmd_ctx *ctx = ctl->priv_data;
 
-	pr_debug("%s: intf_num=%d ctx=%p\n", __func__, ctl->intf_num, ctx);
+	if (!ctx) {
+		pr_err("%s: invalid ctx\n", __func__);
+		return;
+	}
 
+	spin_lock(&ctx->clk_lock);
 	mdss_mdp_irq_disable_nosync(MDSS_MDP_IRQ_PING_PONG_COMP, ctx->pp_num);
 
-	if (ctx)
-		complete(&ctx->pp_comp);
+	complete_all(&ctx->pp_comp);
+
+	if (ctx->koff_cnt)
+		ctx->koff_cnt--;
+
+	pr_debug("%s: ctl_num=%d intf_num=%d ctx=%d kcnt=%d\n", __func__,
+		ctl->num, ctl->intf_num, ctx->pp_num, ctx->koff_cnt);
+
+	spin_unlock(&ctx->clk_lock);
+}
+
+static void clk_ctrl_work(struct work_struct *work)
+{
+	unsigned long flags;
+	struct mdss_mdp_cmd_ctx *ctx =
+		container_of(work, typeof(*ctx), clk_work);
+
+	if (!ctx) {
+		pr_err("%s: invalid ctx\n", __func__);
+		return;
+	}
+
+	pr_debug("%s:ctx=%p num=%d\n", __func__, ctx, ctx->pp_num);
+
+	mutex_lock(&ctx->clk_mtx);
+	spin_lock_irqsave(&ctx->clk_lock, flags);
+	if (ctx->clk_control && ctx->clk_enabled) {
+		ctx->clk_enabled = 0;
+		ctx->clk_control = 0;
+		spin_unlock_irqrestore(&ctx->clk_lock, flags);
+		/*
+		 * make sure dsi link is idle  here
+		 */
+		ctx->vsync_enabled = 0;
+		mdss_mdp_irq_disable(MDSS_MDP_IRQ_PING_PONG_RD_PTR,
+						ctx->pp_num);
+		mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF, false);
+		complete(&ctx->stop_comp);
+		pr_debug("%s: SET_CLK_OFF, pid=%d\n", __func__, current->pid);
+	} else {
+		spin_unlock_irqrestore(&ctx->clk_lock, flags);
+	}
+	mutex_unlock(&ctx->clk_mtx);
+}
+
+static int mdss_mdp_cmd_vsync_ctrl(struct mdss_mdp_ctl *ctl,
+		mdp_vsync_handler_t send_vsync)
+{
+	struct mdss_mdp_cmd_ctx *ctx;
+	unsigned long flags;
+	int enable;
+
+	ctx = (struct mdss_mdp_cmd_ctx *) ctl->priv_data;
+	if (!ctx) {
+		pr_err("%s: invalid ctx\n", __func__);
+		return -ENODEV;
+	}
+
+	enable = (send_vsync != NULL);
+
+	pr_debug("%s: ctx=%p ctx=%d enabled=%d %d clk_enabled=%d clk_ctrl=%d\n",
+			__func__, ctx, ctx->pp_num, ctx->vsync_enabled, enable,
+					ctx->clk_enabled, ctx->clk_control);
+
+	mutex_lock(&ctx->clk_mtx);
+	if (ctx->vsync_enabled == enable) {
+		mutex_unlock(&ctx->clk_mtx);
+		return 0;
+	}
+
+	if (enable) {
+		spin_lock_irqsave(&ctx->clk_lock, flags);
+		ctx->clk_control = 0;
+		ctx->expire = 0;
+		ctx->send_vsync = send_vsync;
+		spin_unlock_irqrestore(&ctx->clk_lock, flags);
+		if (ctx->clk_enabled == 0) {
+			mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON, false);
+			mdss_mdp_irq_enable(MDSS_MDP_IRQ_PING_PONG_RD_PTR,
+							ctx->pp_num);
+			ctx->vsync_enabled = 1;
+			ctx->clk_enabled = 1;
+			pr_debug("%s: SET_CLK_ON, pid=%d\n", __func__,
+						current->pid);
+		}
+	} else {
+		spin_lock_irqsave(&ctx->clk_lock, flags);
+		ctx->expire = VSYNC_EXPIRE_TICK;
+		spin_unlock_irqrestore(&ctx->clk_lock, flags);
+	}
+	mutex_unlock(&ctx->clk_mtx);
+
+	return 0;
+}
+
+static void mdss_mdp_cmd_chk_clock(struct mdss_mdp_cmd_ctx *ctx)
+{
+	unsigned long flags;
+	int set_clk_on = 0;
+
+	if (!ctx) {
+		pr_err("invalid ctx\n");
+		return;
+	}
+
+	pr_debug("%s: ctx=%p num=%d clk_enabled=%d\n", __func__,
+				ctx, ctx->pp_num, ctx->clk_enabled);
+
+	mutex_lock(&ctx->clk_mtx);
+	spin_lock_irqsave(&ctx->clk_lock, flags);
+	ctx->koff_cnt++;
+	ctx->clk_control = 0;
+	ctx->expire = VSYNC_EXPIRE_TICK;
+	if (ctx->clk_enabled == 0) {
+		set_clk_on++;
+		ctx->clk_enabled = 1;
+	}
+	spin_unlock_irqrestore(&ctx->clk_lock, flags);
+
+	if (set_clk_on) {
+		mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON, false);
+		ctx->vsync_enabled = 1;
+		mdss_mdp_irq_enable(MDSS_MDP_IRQ_PING_PONG_RD_PTR, ctx->pp_num);
+		pr_debug("%s: ctx=%p num=%d SET_CLK_ON\n", __func__,
+						ctx, ctx->pp_num);
+	}
+	mutex_unlock(&ctx->clk_mtx);
+}
+
+static int mdss_mdp_cmd_wait4comp(struct mdss_mdp_ctl *ctl, void *arg)
+{
+	struct mdss_mdp_cmd_ctx *ctx;
+	int rc;
+
+	ctx = (struct mdss_mdp_cmd_ctx *) ctl->priv_data;
+	if (!ctx) {
+		pr_err("invalid ctx\n");
+		return -ENODEV;
+	}
+
+	pr_debug("%s: intf_num=%d ctx=%p\n", __func__, ctl->intf_num, ctx);
+
+	rc = wait_for_completion_interruptible_timeout(&ctx->pp_comp,
+			KOFF_TIMEOUT);
+	WARN(rc <= 0, "cmd kickoff timed out (%d) ctl=%d\n", rc, ctl->num);
+
+	return rc;
 }
 
 int mdss_mdp_cmd_kickoff(struct mdss_mdp_ctl *ctl, void *arg)
@@ -207,14 +334,16 @@
 	int rc;
 
 	ctx = (struct mdss_mdp_cmd_ctx *) ctl->priv_data;
-	pr_debug("%s: kickoff intf_num=%d ctx=%p\n", __func__,
-					ctl->intf_num, ctx);
-
 	if (!ctx) {
 		pr_err("invalid ctx\n");
 		return -ENODEV;
 	}
 
+	pr_debug("%s: kickoff intf_num=%d ctx=%p\n", __func__,
+					ctl->intf_num, ctx);
+
+	mdss_mdp_cmd_chk_clock(ctx);
+
 	if (ctx->panel_on == 0) {
 		rc = mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_UNBLANK, NULL);
 		WARN(rc, "intf %d unblank error (%d)\n", ctl->intf_num, rc);
@@ -230,27 +359,37 @@
 
 	mdss_mdp_ctl_write(ctl, MDSS_MDP_REG_CTL_START, 1);
 
-	wait_for_completion_interruptible(&ctx->pp_comp);
-
 	return 0;
 }
 
 int mdss_mdp_cmd_stop(struct mdss_mdp_ctl *ctl)
 {
 	struct mdss_mdp_cmd_ctx *ctx;
+	int need_wait = 0;
 	int ret;
 
-	pr_debug("%s: +\n", __func__);
-
 	ctx = (struct mdss_mdp_cmd_ctx *) ctl->priv_data;
 	if (!ctx) {
 		pr_err("invalid ctx\n");
 		return -ENODEV;
 	}
 
+	pr_debug("%s:+ vaync_enable=%d expire=%d\n", __func__,
+		ctx->vsync_enabled, ctx->expire);
+
+	mutex_lock(&ctx->clk_mtx);
+	if (ctx->vsync_enabled) {
+		INIT_COMPLETION(ctx->stop_comp);
+		need_wait = 1;
+	}
+	mutex_unlock(&ctx->clk_mtx);
+
+	if (need_wait)
+		wait_for_completion_interruptible(&ctx->stop_comp);
+
 	ctx->panel_on = 0;
 
-	mdss_mdp_cmd_set_vsync_handler(ctl, NULL);
+	mdss_mdp_cmd_vsync_ctrl(ctl, NULL);
 
 	mdss_mdp_set_intr_callback(MDSS_MDP_IRQ_PING_PONG_RD_PTR, ctl->intf_num,
 				   NULL, NULL);
@@ -265,7 +404,6 @@
 
 	ret = mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_PANEL_OFF, NULL);
 	WARN(ret, "intf %d unblank error (%d)\n", ctl->intf_num, ret);
-	mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF, false);
 
 	pr_debug("%s:-\n", __func__);
 
@@ -280,8 +418,6 @@
 
 	pr_debug("%s:+\n", __func__);
 
-	mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON, false);
-
 	mixer = mdss_mdp_mixer_get(ctl, MDSS_MDP_MIXER_MUX_LEFT);
 	if (!mixer) {
 		pr_err("mixer not setup correctly\n");
@@ -308,8 +444,14 @@
 
 	ctx->pp_num = mixer->num;
 	init_completion(&ctx->pp_comp);
-	spin_lock_init(&ctx->vsync_lock);
+	init_completion(&ctx->stop_comp);
 	atomic_set(&ctx->vsync_ref, 0);
+	spin_lock_init(&ctx->clk_lock);
+	mutex_init(&ctx->clk_mtx);
+	INIT_WORK(&ctx->clk_work, clk_ctrl_work);
+
+	pr_debug("%s: ctx=%p num=%d mixer=%d\n", __func__,
+				ctx, ctx->pp_num, mixer->num);
 
 	mdss_mdp_set_intr_callback(MDSS_MDP_IRQ_PING_PONG_RD_PTR, ctx->pp_num,
 				   mdss_mdp_cmd_readptr_done, ctl);
@@ -325,7 +467,8 @@
 
 	ctl->stop_fnc = mdss_mdp_cmd_stop;
 	ctl->display_fnc = mdss_mdp_cmd_kickoff;
-	ctl->set_vsync_handler = mdss_mdp_cmd_set_vsync_handler;
+	ctl->wait_fnc = mdss_mdp_cmd_wait4comp;
+	ctl->set_vsync_handler = mdss_mdp_cmd_vsync_ctrl;
 
 	pr_debug("%s:-\n", __func__);
 
diff --git a/drivers/video/msm/mdss/mdss_mdp_intf_writeback.c b/drivers/video/msm/mdss/mdss_mdp_intf_writeback.c
index d86527b..b4d5014 100644
--- a/drivers/video/msm/mdss/mdss_mdp_intf_writeback.c
+++ b/drivers/video/msm/mdss/mdss_mdp_intf_writeback.c
@@ -17,6 +17,9 @@
 #include "mdss_mdp.h"
 #include "mdss_mdp_rotator.h"
 
+/* wait for at most 2 vsync for lowest refresh rate (24hz) */
+#define KOFF_TIMEOUT msecs_to_jiffies(84)
+
 enum mdss_mdp_writeback_type {
 	MDSS_MDP_WRITEBACK_TYPE_ROTATOR,
 	MDSS_MDP_WRITEBACK_TYPE_LINE,
@@ -28,6 +31,8 @@
 	char __iomem *base;
 	u8 ref_cnt;
 	u8 type;
+	struct completion wb_comp;
+	int comp_cnt;
 
 	u32 intr_type;
 	u32 intf_num;
@@ -321,10 +326,37 @@
 	pr_debug("intr wb_num=%d\n", ctx->wb_num);
 
 	mdss_mdp_irq_disable_nosync(ctx->intr_type, ctx->intf_num);
-	mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF, true);
 
 	if (ctx->callback_fnc)
 		ctx->callback_fnc(ctx->callback_arg);
+
+	complete_all(&ctx->wb_comp);
+}
+
+static int mdss_mdp_wb_wait4comp(struct mdss_mdp_ctl *ctl, void *arg)
+{
+	struct mdss_mdp_writeback_ctx *ctx;
+	int rc = 0;
+
+	ctx = (struct mdss_mdp_writeback_ctx *) ctl->priv_data;
+	if (!ctx) {
+		pr_err("invalid ctx\n");
+		return -ENODEV;
+	}
+
+	if (ctx->comp_cnt == 0)
+		return rc;
+
+	rc = wait_for_completion_interruptible_timeout(&ctx->wb_comp,
+			KOFF_TIMEOUT);
+	WARN(rc <= 0, "writeback kickoff timed out (%d) ctl=%d\n",
+							rc, ctl->num);
+
+	mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF, false); /* clock off */
+
+	ctx->comp_cnt--;
+
+	return rc;
 }
 
 static int mdss_mdp_writeback_display(struct mdss_mdp_ctl *ctl, void *arg)
@@ -338,6 +370,12 @@
 	if (!ctx)
 		return -ENODEV;
 
+	if (ctx->comp_cnt) {
+		pr_err("previous kickoff not completed yet, ctl=%d\n",
+					ctl->num);
+		return -EPERM;
+	}
+
 	wb_args = (struct mdss_mdp_writeback_arg *) arg;
 	if (!wb_args)
 		return -ENOENT;
@@ -354,12 +392,15 @@
 	flush_bits = BIT(16); /* WB */
 	mdss_mdp_ctl_write(ctl, MDSS_MDP_REG_CTL_FLUSH, flush_bits);
 
+	INIT_COMPLETION(ctx->wb_comp);
 	mdss_mdp_irq_enable(ctx->intr_type, ctx->intf_num);
 
 	mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON, false);
 	mdss_mdp_ctl_write(ctl, MDSS_MDP_REG_CTL_START, 1);
 	wmb();
 
+	ctx->comp_cnt++;
+
 	return 0;
 }
 
@@ -387,6 +428,7 @@
 	ctx->wb_num = ctl->num;	/* wb num should match ctl num */
 	ctx->base = ctl->wb_base;
 	ctx->initialized = false;
+	init_completion(&ctx->wb_comp);
 
 	mdss_mdp_set_intr_callback(ctx->intr_type, ctx->intf_num,
 				   mdss_mdp_writeback_intr_done, ctx);
@@ -397,6 +439,7 @@
 		ctl->prepare_fnc = mdss_mdp_writeback_prepare_wfd;
 	ctl->stop_fnc = mdss_mdp_writeback_stop;
 	ctl->display_fnc = mdss_mdp_writeback_display;
+	ctl->wait_fnc = mdss_mdp_wb_wait4comp;
 
 	return ret;
 }
diff --git a/drivers/video/msm/mdss/mdss_mdp_rotator.c b/drivers/video/msm/mdss/mdss_mdp_rotator.c
index ce4c28f..a027dd9 100644
--- a/drivers/video/msm/mdss/mdss_mdp_rotator.c
+++ b/drivers/video/msm/mdss/mdss_mdp_rotator.c
@@ -41,7 +41,6 @@
 			rot->ref_cnt++;
 			rot->session_id = i | MDSS_MDP_ROT_SESSION_MASK;
 			mutex_init(&rot->lock);
-			init_completion(&rot->comp);
 			break;
 		}
 	}
@@ -87,10 +86,18 @@
 
 static int mdss_mdp_rotator_busy_wait(struct mdss_mdp_rotator_session *rot)
 {
+	struct mdss_mdp_pipe *rot_pipe = NULL;
+	struct mdss_mdp_ctl *ctl = NULL;
+
+	rot_pipe = rot->pipe;
+	if (!rot_pipe)
+		return -ENODEV;
+
+	ctl = rot_pipe->mixer->ctl;
 	mutex_lock(&rot->lock);
 	if (rot->busy) {
 		pr_debug("waiting for rot=%d to complete\n", rot->pipe->num);
-		wait_for_completion_interruptible(&rot->comp);
+		mdss_mdp_display_wait4comp(ctl);
 		rot->busy = false;
 
 	}
@@ -99,28 +106,18 @@
 	return 0;
 }
 
-static void mdss_mdp_rotator_callback(void *arg)
-{
-	struct mdss_mdp_rotator_session *rot;
-
-	rot = (struct mdss_mdp_rotator_session *) arg;
-	if (rot)
-		complete(&rot->comp);
-}
-
 static int mdss_mdp_rotator_kickoff(struct mdss_mdp_ctl *ctl,
 				    struct mdss_mdp_rotator_session *rot,
 				    struct mdss_mdp_data *dst_data)
 {
 	int ret;
 	struct mdss_mdp_writeback_arg wb_args = {
-		.callback_fnc = mdss_mdp_rotator_callback,
+		.callback_fnc = NULL,
 		.data = dst_data,
 		.priv_data = rot,
 	};
 
 	mutex_lock(&rot->lock);
-	INIT_COMPLETION(rot->comp);
 	rot->busy = true;
 	ret = mdss_mdp_display_commit(ctl, &wb_args);
 	if (ret) {
diff --git a/drivers/video/msm/mdss/mdss_mdp_rotator.h b/drivers/video/msm/mdss/mdss_mdp_rotator.h
index c50d710..3401fe8 100644
--- a/drivers/video/msm/mdss/mdss_mdp_rotator.h
+++ b/drivers/video/msm/mdss/mdss_mdp_rotator.h
@@ -36,7 +36,6 @@
 	struct mdss_mdp_pipe *pipe;
 
 	struct mutex lock;
-	struct completion comp;
 	u8 busy;
 	u8 no_wait;