Merge "msm: mdss: implement vsync ctrl and events" into msm-3.4
diff --git a/drivers/video/msm/mdss/mdss_fb.c b/drivers/video/msm/mdss/mdss_fb.c
index 1a1bb07..1f83b2b 100644
--- a/drivers/video/msm/mdss/mdss_fb.c
+++ b/drivers/video/msm/mdss/mdss_fb.c
@@ -186,7 +186,7 @@
 	/*
 	 * alloc framebuffer info + par data
 	 */
-	fbi = framebuffer_alloc(sizeof(struct msm_fb_data_type), &pdev->dev);
+	fbi = framebuffer_alloc(sizeof(struct msm_fb_data_type), NULL);
 	if (fbi == NULL) {
 		pr_err("can't allocate framebuffer info data!\n");
 		return -ENOMEM;
diff --git a/drivers/video/msm/mdss/mdss_mdp.h b/drivers/video/msm/mdss/mdss_mdp.h
index 17281d5..a282c3a 100644
--- a/drivers/video/msm/mdss/mdss_mdp.h
+++ b/drivers/video/msm/mdss/mdss_mdp.h
@@ -107,6 +107,9 @@
 	MDSS_MDP_MAX_CSC
 };
 
+struct mdss_mdp_ctl;
+typedef void (*mdp_vsync_handler_t)(struct mdss_mdp_ctl *, ktime_t);
+
 struct mdss_mdp_ctl {
 	u32 num;
 	u32 ref_cnt;
@@ -138,6 +141,7 @@
 	int (*stop_fnc) (struct mdss_mdp_ctl *ctl);
 	int (*prepare_fnc) (struct mdss_mdp_ctl *ctl, void *arg);
 	int (*display_fnc) (struct mdss_mdp_ctl *ctl, void *arg);
+	int (*set_vsync_handler) (struct mdss_mdp_ctl *, mdp_vsync_handler_t);
 
 	void *priv_data;
 };
diff --git a/drivers/video/msm/mdss/mdss_mdp_intf_video.c b/drivers/video/msm/mdss/mdss_mdp_intf_video.c
index bc64d2e..6f92474 100644
--- a/drivers/video/msm/mdss/mdss_mdp_intf_video.c
+++ b/drivers/video/msm/mdss/mdss_mdp_intf_video.c
@@ -13,6 +13,8 @@
 
 #define pr_fmt(fmt)	"%s: " fmt, __func__
 
+#include <linux/workqueue.h>
+
 #include "mdss_fb.h"
 #include "mdss_mdp.h"
 
@@ -44,6 +46,12 @@
 	u8 timegen_en;
 	struct completion pp_comp;
 	struct completion vsync_comp;
+
+	struct mutex vsync_lock;
+	struct work_struct vsync_work;
+	mdp_vsync_handler_t vsync_handler;
+	void *vsync_ptr;
+	ktime_t vsync_time;
 };
 
 struct mdss_mdp_video_ctx mdss_mdp_video_ctx_list[MAX_SESSIONS];
@@ -141,6 +149,47 @@
 	return 0;
 }
 
+static void send_vsync_work(struct work_struct *work)
+{
+	struct mdss_mdp_video_ctx *ctx;
+
+	ctx = container_of(work, typeof(*ctx), vsync_work);
+	mutex_lock(&ctx->vsync_lock);
+	if (ctx->vsync_handler)
+		ctx->vsync_handler(ctx->vsync_ptr, ctx->vsync_time);
+	mutex_unlock(&ctx->vsync_lock);
+}
+
+static int mdss_mdp_video_set_vsync_handler(struct mdss_mdp_ctl *ctl,
+		mdp_vsync_handler_t vsync_handler)
+{
+	struct mdss_mdp_video_ctx *ctx;
+
+	ctx = (struct mdss_mdp_video_ctx *) ctl->priv_data;
+	if (!ctx) {
+		pr_err("invalid ctx for ctl=%d\n", ctl->num);
+		return -ENODEV;
+	}
+	if (mutex_lock_interruptible(&ctx->vsync_lock))
+		return -EINTR;
+
+	if (!ctx->timegen_en) {
+		ctx->vsync_time = ktime_get();
+		schedule_work(&ctx->vsync_work);
+	}
+
+	if (!ctx->vsync_handler && vsync_handler)
+		mdss_mdp_irq_enable(MDSS_MDP_IRQ_INTF_VSYNC, ctl->intf_num);
+	else if (ctx->vsync_handler && !vsync_handler)
+		mdss_mdp_irq_disable(MDSS_MDP_IRQ_INTF_VSYNC, ctl->intf_num);
+
+	ctx->vsync_handler = vsync_handler;
+	ctx->vsync_ptr = ctl;
+	mutex_unlock(&ctx->vsync_lock);
+
+	return 0;
+}
+
 static int mdss_mdp_video_stop(struct mdss_mdp_ctl *ctl)
 {
 	struct mdss_mdp_video_ctx *ctx;
@@ -161,6 +210,11 @@
 		ctx->timegen_en = false;
 	}
 
+	mdss_mdp_set_intr_callback(MDSS_MDP_IRQ_INTF_VSYNC, ctl->intf_num,
+				   NULL, NULL);
+	mdss_mdp_set_intr_callback(MDSS_MDP_IRQ_PING_PONG_COMP, ctx->pp_num,
+				   NULL, NULL);
+
 	memset(ctx, 0, sizeof(*ctx));
 
 	return 0;
@@ -190,10 +244,13 @@
 		pr_err("invalid ctx\n");
 		return;
 	}
+	ctx->vsync_time = ktime_get();
 
 	pr_debug("intr ctl=%d\n", ctx->ctl_num);
 
 	complete(&ctx->vsync_comp);
+	if (ctx->vsync_handler)
+		schedule_work(&ctx->vsync_work);
 }
 
 static int mdss_mdp_video_prepare(struct mdss_mdp_ctl *ctl, void *arg)
@@ -207,15 +264,10 @@
 	}
 
 	if (ctx->timegen_en) {
-		u32 intr_type = MDSS_MDP_IRQ_PING_PONG_COMP;
-
 		pr_debug("waiting for ping pong %d done\n", ctx->pp_num);
-		mdss_mdp_set_intr_callback(intr_type, ctx->pp_num,
-					   mdss_mdp_video_pp_intr_done, ctx);
-		mdss_mdp_irq_enable(intr_type, ctx->pp_num);
-
+		mdss_mdp_irq_enable(MDSS_MDP_IRQ_PING_PONG_COMP, ctx->pp_num);
 		wait_for_completion_interruptible(&ctx->pp_comp);
-		mdss_mdp_irq_disable(intr_type, ctx->pp_num);
+		mdss_mdp_irq_disable(MDSS_MDP_IRQ_PING_PONG_COMP, ctx->pp_num);
 	}
 
 	return 0;
@@ -224,7 +276,6 @@
 static int mdss_mdp_video_display(struct mdss_mdp_ctl *ctl, void *arg)
 {
 	struct mdss_mdp_video_ctx *ctx;
-	u32 intr_type = MDSS_MDP_IRQ_INTF_VSYNC;
 
 	pr_debug("kickoff ctl=%d\n", ctl->num);
 
@@ -233,9 +284,12 @@
 		pr_err("invalid ctx\n");
 		return -ENODEV;
 	}
-	mdss_mdp_set_intr_callback(intr_type, ctl->intf_num,
-				   mdss_mdp_video_vsync_intr_done, ctx);
-	mdss_mdp_irq_enable(intr_type, ctl->intf_num);
+	INIT_COMPLETION(ctx->vsync_comp);
+
+	if (mutex_lock_interruptible(&ctx->vsync_lock))
+		return -EINTR;
+	if (!ctx->vsync_handler)
+		mdss_mdp_irq_enable(MDSS_MDP_IRQ_INTF_VSYNC, ctl->intf_num);
 
 	if (!ctx->timegen_en) {
 		int off = MDSS_MDP_REG_INTF_OFFSET(ctl->intf_num);
@@ -249,7 +303,9 @@
 	}
 
 	wait_for_completion_interruptible(&ctx->vsync_comp);
-	mdss_mdp_irq_disable(intr_type, ctl->intf_num);
+	if (!ctx->vsync_handler)
+		mdss_mdp_irq_disable(MDSS_MDP_IRQ_INTF_VSYNC, ctl->intf_num);
+	mutex_unlock(&ctx->vsync_lock);
 
 	return 0;
 }
@@ -293,6 +349,13 @@
 	init_completion(&ctx->pp_comp);
 	init_completion(&ctx->vsync_comp);
 
+	INIT_WORK(&ctx->vsync_work, send_vsync_work);
+	mutex_init(&ctx->vsync_lock);
+	mdss_mdp_set_intr_callback(MDSS_MDP_IRQ_INTF_VSYNC, ctl->intf_num,
+				   mdss_mdp_video_vsync_intr_done, ctx);
+	mdss_mdp_set_intr_callback(MDSS_MDP_IRQ_PING_PONG_COMP, ctx->pp_num,
+				   mdss_mdp_video_pp_intr_done, ctx);
+
 	itp.width = pinfo->xres + pinfo->lcdc.xres_pad;
 	itp.height = pinfo->yres + pinfo->lcdc.yres_pad;
 	itp.border_clr = pinfo->lcdc.border_clr;
@@ -316,6 +379,7 @@
 	ctl->stop_fnc = mdss_mdp_video_stop;
 	ctl->prepare_fnc = mdss_mdp_video_prepare;
 	ctl->display_fnc = mdss_mdp_video_display;
+	ctl->set_vsync_handler = mdss_mdp_video_set_vsync_handler;
 
 	return 0;
 }
diff --git a/drivers/video/msm/mdss/mdss_mdp_overlay.c b/drivers/video/msm/mdss/mdss_mdp_overlay.c
index d4d889b..1cb474d 100644
--- a/drivers/video/msm/mdss/mdss_mdp_overlay.c
+++ b/drivers/video/msm/mdss/mdss_mdp_overlay.c
@@ -615,6 +615,49 @@
 		mfd->kickoff_fnc(mfd->ctl);
 }
 
+static void mdss_mdp_overlay_handle_vsync(struct mdss_mdp_ctl *ctl, ktime_t t)
+{
+	struct device *dev;
+	char buf[64];
+	char *envp[2];
+
+	if (!ctl || !ctl->mfd || !ctl->mfd->fbi) {
+		pr_warn("Invalid handle for vsync\n");
+		return;
+	}
+
+	dev = ctl->mfd->fbi->dev;
+
+	snprintf(buf, sizeof(buf), "VSYNC=%llu", ktime_to_ns(t));
+	envp[0] = buf;
+	envp[1] = NULL;
+	kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp);
+
+	pr_debug("sent vsync on ctl=%d ts=%llu\n", ctl->num, ktime_to_ns(t));
+}
+
+static int mdss_mdp_overlay_vsync_ctrl(struct msm_fb_data_type *mfd, int en)
+{
+	struct mdss_mdp_ctl *ctl = mfd->ctl;
+	int rc;
+
+	if (!ctl)
+		return -ENODEV;
+	if (!ctl->set_vsync_handler)
+		return -ENOTSUPP;
+
+	pr_debug("vsync en=%d\n", en);
+
+	mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON, false);
+	if (en)
+		rc = ctl->set_vsync_handler(ctl, mdss_mdp_overlay_handle_vsync);
+	else
+		rc = ctl->set_vsync_handler(ctl, NULL);
+	mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF, false);
+
+	return rc;
+}
+
 static int mdss_mdp_hw_cursor_update(struct msm_fb_data_type *mfd,
 				     struct fb_cursor *cursor)
 {
@@ -822,6 +865,16 @@
 		}
 		break;
 
+	case MSMFB_VSYNC_CTRL:
+	case MSMFB_OVERLAY_VSYNC_CTRL:
+		if (!copy_from_user(&val, argp, sizeof(val))) {
+			ret = mdss_mdp_overlay_vsync_ctrl(mfd, val);
+		} else {
+			pr_err("MSMFB_OVERLAY_VSYNC_CTRL failed (%d)\n", ret);
+			ret = -EFAULT;
+		}
+		break;
+
 	default:
 		if (mfd->panel_info.type == WRITEBACK_PANEL)
 			ret = mdss_mdp_wb_ioctl_handler(mfd, cmd, argp);
diff --git a/drivers/video/msm/mdss/mdss_mdp_util.c b/drivers/video/msm/mdss/mdss_mdp_util.c
index 7f61a14..7567db1 100644
--- a/drivers/video/msm/mdss/mdss_mdp_util.c
+++ b/drivers/video/msm/mdss/mdss_mdp_util.c
@@ -102,8 +102,6 @@
 	spin_lock(&mdss_mdp_intr_lock);
 	fnc = mdp_intr_cb[index].func;
 	arg = mdp_intr_cb[index].arg;
-	if (fnc != NULL)
-		mdp_intr_cb[index].func = NULL;
 	spin_unlock(&mdss_mdp_intr_lock);
 	if (fnc)
 		fnc(arg);