drm/msm: allow rsc clients to wait on crtc id on transition

Add an API to the RSC state update function to give
responsibility of waiting on a VBLANK after RSC state transition
to the caller rather than RSC itself. This is due to RSC not
having the correct ability to wait for VBLANK on the correct
thread, instead the SDE encoder has that ability. SDE encoder is
also the only client type that needs to execute the wait.

This necessarily breaks up the encoder disable callflow to be
able to correctly transition the RSC to the IDLE state before
the physical encoders are transitioned to the disabled state.

Change-Id: I3e44c5d0e56dfc4c60eb7f53ca29898576efd399
Signed-off-by: Lloyd Atkinson <latkinso@codeaurora.org>
diff --git a/drivers/gpu/drm/msm/msm_drv.h b/drivers/gpu/drm/msm/msm_drv.h
index 2f665a4..c6943de 100644
--- a/drivers/gpu/drm/msm/msm_drv.h
+++ b/drivers/gpu/drm/msm/msm_drv.h
@@ -216,10 +216,12 @@
  * enum msm_event_wait - type of HW events to wait for
  * @MSM_ENC_COMMIT_DONE - wait for the driver to flush the registers to HW
  * @MSM_ENC_TX_COMPLETE - wait for the HW to transfer the frame to panel
+ * @MSM_ENC_VBLANK - wait for the HW VBLANK event (for driver-internal waiters)
  */
 enum msm_event_wait {
 	MSM_ENC_COMMIT_DONE = 0,
 	MSM_ENC_TX_COMPLETE,
+	MSM_ENC_VBLANK,
 };
 
 /**
diff --git a/drivers/gpu/drm/msm/sde/sde_encoder.c b/drivers/gpu/drm/msm/sde/sde_encoder.c
index 4ba2b75..0a2518c 100644
--- a/drivers/gpu/drm/msm/sde/sde_encoder.c
+++ b/drivers/gpu/drm/msm/sde/sde_encoder.c
@@ -71,9 +71,12 @@
 
 #define MISR_BUFF_SIZE			256
 
-#define IDLE_TIMEOUT	64
+#define IDLE_TIMEOUT	(66 - 16/2)
 #define IDLE_SHORT_TIMEOUT	1
 
+/* Maximum number of VSYNC wait attempts for RSC state transition */
+#define MAX_RSC_WAIT	5
+
 /**
  * enum sde_enc_rc_events - events for resource control state machine
  * @SDE_ENC_RC_EVENT_KICKOFF:
@@ -87,11 +90,19 @@
  *	Event signals the end of the data transfer after the PP FRAME_DONE
  *	event. At the end of this event, a delayed work is scheduled to go to
  *	IDLE_PC state after IDLE_TIMEOUT time.
+ * @SDE_ENC_RC_EVENT_PRE_STOP:
+ *	This event happens at NORMAL priority.
+ *	This event, when received during the ON state, set RSC to IDLE, and
+ *	and leave the RC STATE in the PRE_OFF state.
+ *	It should be followed by the STOP event as part of encoder disable.
+ *	If received during IDLE or OFF states, it will do nothing.
  * @SDE_ENC_RC_EVENT_STOP:
  *	This event happens at NORMAL priority.
- *	When this event is received, disable all the MDP/DSI core clocks
- *	and request RSC with IDLE state. Resource state should be in OFF
- *	at the end of the event.
+ *	When this event is received, disable all the MDP/DSI core clocks, and
+ *	disable IRQs. It should be called from the PRE_OFF or IDLE states.
+ *	IDLE is expected when IDLE_PC has run, and PRE_OFF did nothing.
+ *	PRE_OFF is expected when PRE_STOP was executed during the ON state.
+ *	Resource state should be in OFF at the end of the event.
  * @SDE_ENC_RC_EARLY_WAKEUP
  *	This event happens at NORMAL priority from a work item.
  *	Event signals that there will be frame update soon and the driver should
@@ -105,6 +116,7 @@
 enum sde_enc_rc_events {
 	SDE_ENC_RC_EVENT_KICKOFF = 1,
 	SDE_ENC_RC_EVENT_FRAME_DONE,
+	SDE_ENC_RC_EVENT_PRE_STOP,
 	SDE_ENC_RC_EVENT_STOP,
 	SDE_ENC_RC_EVENT_EARLY_WAKE_UP,
 	SDE_ENC_RC_EVENT_ENTER_IDLE
@@ -113,11 +125,13 @@
 /*
  * enum sde_enc_rc_states - states that the resource control maintains
  * @SDE_ENC_RC_STATE_OFF: Resource is in OFF state
+ * @SDE_ENC_RC_STATE_PRE_OFF: Resource is transitioning to OFF state
  * @SDE_ENC_RC_STATE_ON: Resource is in ON state
  * @SDE_ENC_RC_STATE_IDLE: Resource is in IDLE state
  */
 enum sde_enc_rc_states {
 	SDE_ENC_RC_STATE_OFF,
+	SDE_ENC_RC_STATE_PRE_OFF,
 	SDE_ENC_RC_STATE_ON,
 	SDE_ENC_RC_STATE_IDLE
 };
@@ -324,6 +338,8 @@
 	/* return EWOULDBLOCK since we know the wait isn't necessary */
 	if (phys_enc->enable_state == SDE_ENC_DISABLED) {
 		SDE_ERROR_PHYS(phys_enc, "encoder is disabled\n");
+		SDE_EVT32(DRMID(phys_enc->parent), intr_idx, irq->hw_idx,
+				irq->irq_idx, intr_idx, SDE_EVTLOG_ERROR);
 		return -EWOULDBLOCK;
 	}
 
@@ -1197,28 +1213,34 @@
 	}
 }
 
-static int sde_encoder_update_rsc_client(
+static int _sde_encoder_update_rsc_client(
 		struct drm_encoder *drm_enc,
 		struct sde_encoder_rsc_config *config, bool enable)
 {
 	struct sde_encoder_virt *sde_enc;
+	struct drm_crtc *crtc;
 	enum sde_rsc_state rsc_state;
 	struct sde_rsc_cmd_config rsc_config;
 	int ret;
 	struct msm_display_info *disp_info;
 	struct msm_mode_info *mode_info;
+	int wait_vblank_crtc_id = SDE_RSC_INVALID_CRTC_ID;
+	int wait_count = 0;
+	struct drm_crtc *primary_crtc;
+	int pipe = -1;
 
-	if (!drm_enc) {
-		SDE_ERROR("invalid encoder\n");
+	if (!drm_enc || !drm_enc->crtc || !drm_enc->dev) {
+		SDE_ERROR("invalid arguments\n");
 		return -EINVAL;
 	}
 
 	sde_enc = to_sde_encoder_virt(drm_enc);
+	crtc = drm_enc->crtc;
 	disp_info = &sde_enc->disp_info;
 	mode_info = &sde_enc->mode_info;
 
 	if (!sde_enc->rsc_client) {
-		SDE_DEBUG("rsc client not created\n");
+		SDE_DEBUG_ENC(sde_enc, "rsc client not created\n");
 		return 0;
 	}
 
@@ -1251,16 +1273,70 @@
 			sde_enc->rsc_cfg = *config;
 
 		ret = sde_rsc_client_state_update(sde_enc->rsc_client,
-			rsc_state, &rsc_config,
-			drm_enc->crtc ? drm_enc->crtc->index : -1);
+			rsc_state, &rsc_config, crtc->base.id,
+			&wait_vblank_crtc_id);
 	} else {
 		ret = sde_rsc_client_state_update(sde_enc->rsc_client,
-			rsc_state, NULL,
-			drm_enc->crtc ? drm_enc->crtc->index : -1);
+			rsc_state, NULL, crtc->base.id,
+			&wait_vblank_crtc_id);
 	}
 
-	if (ret)
-		SDE_ERROR("sde rsc client update failed ret:%d\n", ret);
+	/**
+	 * if RSC performed a state change that requires a VBLANK wait, it will
+	 * set wait_vblank_crtc_id to the CRTC whose VBLANK we must wait on.
+	 *
+	 * if we are the primary display, we will need to enable and wait
+	 * locally since we hold the commit thread
+	 *
+	 * if we are an external display, we must send a signal to the primary
+	 * to enable its VBLANK and wait one, since the RSC hardware is driven
+	 * by the primary panel's VBLANK signals
+	 */
+	SDE_EVT32_VERBOSE(DRMID(drm_enc), wait_vblank_crtc_id);
+	if (ret) {
+		SDE_ERROR_ENC(sde_enc,
+				"sde rsc client update failed ret:%d\n", ret);
+		return ret;
+	} else if (wait_vblank_crtc_id == SDE_RSC_INVALID_CRTC_ID) {
+		return ret;
+	}
+
+	if (crtc->base.id != wait_vblank_crtc_id) {
+		primary_crtc = drm_crtc_find(drm_enc->dev, wait_vblank_crtc_id);
+		if (!primary_crtc) {
+			SDE_ERROR_ENC(sde_enc,
+					"failed to find primary crtc id %d\n",
+					wait_vblank_crtc_id);
+			return -EINVAL;
+		}
+		pipe = drm_crtc_index(primary_crtc);
+	}
+
+	/**
+	 * note: VBLANK is expected to be enabled at this point in
+	 * resource control state machine if on primary CRTC
+	 */
+	for (wait_count = 0; wait_count < MAX_RSC_WAIT; wait_count++) {
+		if (sde_rsc_client_is_state_update_complete(
+				sde_enc->rsc_client))
+			break;
+
+		if (crtc->base.id == wait_vblank_crtc_id)
+			ret = sde_encoder_wait_for_event(drm_enc,
+					MSM_ENC_VBLANK);
+		else
+			drm_wait_one_vblank(drm_enc->dev, pipe);
+
+		if (ret) {
+			SDE_ERROR_ENC(sde_enc,
+					"wait for vblank failed ret:%d\n", ret);
+			break;
+		}
+	}
+
+	if (wait_count >= MAX_RSC_WAIT)
+		SDE_EVT32(DRMID(drm_enc), wait_vblank_crtc_id, wait_count,
+				SDE_EVTLOG_ERROR);
 
 	return ret;
 }
@@ -1275,13 +1351,43 @@
 	return sde_enc->rsc_client;
 }
 
+static void _sde_encoder_resource_control_rsc_update(
+		struct drm_encoder *drm_enc, bool enable)
+{
+	struct sde_encoder_virt *sde_enc = to_sde_encoder_virt(drm_enc);
+	struct sde_encoder_rsc_config rsc_cfg = { 0 };
+
+	if (enable) {
+		rsc_cfg.inline_rotate_prefill =
+				sde_crtc_get_inline_prefill(drm_enc->crtc);
+
+		/* connect the TE source to actual TE GPIO to drive RSC */
+		_sde_encoder_update_vsync_source(sde_enc, &sde_enc->disp_info,
+				false);
+
+		_sde_encoder_update_rsc_client(drm_enc, &rsc_cfg, true);
+	} else {
+		_sde_encoder_update_rsc_client(drm_enc, NULL, false);
+
+		/**
+		 * disconnect the TE source from the actual TE GPIO for RSC
+		 *
+		 * this call is for hardware workaround on sdm845 and should
+		 * not be removed without considering the design changes for
+		 * sde rsc + command mode concurrency. It may lead to pp
+		 * timeout due to vsync from panel for command mode panel.
+		 */
+		_sde_encoder_update_vsync_source(sde_enc, &sde_enc->disp_info,
+				true);
+	}
+}
+
 static void _sde_encoder_resource_control_helper(struct drm_encoder *drm_enc,
 		bool enable)
 {
 	struct msm_drm_private *priv;
 	struct sde_kms *sde_kms;
 	struct sde_encoder_virt *sde_enc;
-	struct sde_encoder_rsc_config rsc_cfg = { 0 };
 	int i;
 
 	sde_enc = to_sde_encoder_virt(drm_enc);
@@ -1311,29 +1417,7 @@
 			if (phys && phys->ops.irq_control)
 				phys->ops.irq_control(phys, true);
 		}
-
-		rsc_cfg.inline_rotate_prefill =
-				sde_crtc_get_inline_prefill(drm_enc->crtc);
-
-		_sde_encoder_update_vsync_source(sde_enc, &sde_enc->disp_info,
-									false);
-
-		/* enable RSC */
-		sde_encoder_update_rsc_client(drm_enc, &rsc_cfg, true);
-
 	} else {
-
-		/* disable RSC */
-		sde_encoder_update_rsc_client(drm_enc, NULL, false);
-
-		/**
-		 * this call is for hardware workaround on sdm845 and should
-		 * not be removed without considering the design changes for
-		 * sde rsc + command mode concurrency. It may lead to pp
-		 * timeout due to vsync from panel for command mode panel.
-		 */
-		_sde_encoder_update_vsync_source(sde_enc, &sde_enc->disp_info,
-									true);
 		/* disable all the irq */
 		for (i = 0; i < sde_enc->num_phys_encs; i++) {
 			struct sde_encoder_phys *phys =
@@ -1383,7 +1467,8 @@
 	 */
 	if (!sde_enc->idle_pc_supported &&
 			(sw_event != SDE_ENC_RC_EVENT_KICKOFF &&
-				sw_event != SDE_ENC_RC_EVENT_STOP))
+				sw_event != SDE_ENC_RC_EVENT_STOP &&
+				sw_event != SDE_ENC_RC_EVENT_PRE_STOP))
 		return 0;
 
 	SDE_DEBUG_ENC(sde_enc, "sw_event:%d, idle_pc_supported:%d\n", sw_event,
@@ -1407,10 +1492,19 @@
 					sw_event);
 			mutex_unlock(&sde_enc->rc_lock);
 			return 0;
+		} else if (sde_enc->rc_state != SDE_ENC_RC_STATE_OFF &&
+				sde_enc->rc_state != SDE_ENC_RC_STATE_IDLE) {
+			SDE_ERROR_ENC(sde_enc, "sw_event:%d, rc in state %d\n",
+					sw_event, sde_enc->rc_state);
+			SDE_EVT32(DRMID(drm_enc), sw_event, sde_enc->rc_state,
+					SDE_EVTLOG_ERROR);
+			mutex_unlock(&sde_enc->rc_lock);
+			return -EINVAL;
 		}
 
 		/* enable all the clks and resources */
 		_sde_encoder_resource_control_helper(drm_enc, true);
+		_sde_encoder_resource_control_rsc_update(drm_enc, true);
 
 		SDE_EVT32(DRMID(drm_enc), sw_event, sde_enc->rc_state,
 				SDE_ENC_RC_STATE_ON, SDE_EVTLOG_FUNC_CASE1);
@@ -1429,6 +1523,8 @@
 		if (sde_enc->rc_state != SDE_ENC_RC_STATE_ON) {
 			SDE_ERROR_ENC(sde_enc, "sw_event:%d,rc:%d-unexpected\n",
 					sw_event, sde_enc->rc_state);
+			SDE_EVT32(DRMID(drm_enc), sw_event, sde_enc->rc_state,
+					SDE_EVTLOG_ERROR);
 			return -EINVAL;
 		}
 
@@ -1472,7 +1568,7 @@
 				sw_event);
 		break;
 
-	case SDE_ENC_RC_EVENT_STOP:
+	case SDE_ENC_RC_EVENT_PRE_STOP:
 		/* cancel delayed off work, if any */
 		if (kthread_cancel_delayed_work_sync(
 				&sde_enc->delayed_off_work))
@@ -1481,20 +1577,53 @@
 
 		mutex_lock(&sde_enc->rc_lock);
 
+		/* skip if is already OFF or IDLE, resources are off already */
+		if (sde_enc->rc_state == SDE_ENC_RC_STATE_OFF ||
+				sde_enc->rc_state == SDE_ENC_RC_STATE_IDLE) {
+			SDE_DEBUG_ENC(sde_enc, "sw_event:%d, rc in %d state\n",
+					sw_event, sde_enc->rc_state);
+			mutex_unlock(&sde_enc->rc_lock);
+			return 0;
+		}
+
+		/**
+		 * IRQs are still enabled currently, which allows wait for
+		 * VBLANK which RSC may require to correctly transition to OFF
+		 */
+		_sde_encoder_resource_control_rsc_update(drm_enc, false);
+
+		SDE_EVT32(DRMID(drm_enc), sw_event, sde_enc->rc_state,
+				SDE_ENC_RC_STATE_PRE_OFF,
+				SDE_EVTLOG_FUNC_CASE6);
+
+		sde_enc->rc_state = SDE_ENC_RC_STATE_PRE_OFF;
+
+		mutex_unlock(&sde_enc->rc_lock);
+		break;
+
+	case SDE_ENC_RC_EVENT_STOP:
+		mutex_lock(&sde_enc->rc_lock);
+
 		/* return if the resource control is already in OFF state */
 		if (sde_enc->rc_state == SDE_ENC_RC_STATE_OFF) {
 			SDE_DEBUG_ENC(sde_enc, "sw_event:%d, rc in OFF state\n",
 					sw_event);
 			mutex_unlock(&sde_enc->rc_lock);
 			return 0;
+		} else if (sde_enc->rc_state == SDE_ENC_RC_STATE_ON) {
+			SDE_ERROR_ENC(sde_enc, "sw_event:%d, rc in state %d\n",
+					sw_event, sde_enc->rc_state);
+			SDE_EVT32(DRMID(drm_enc), sw_event, sde_enc->rc_state,
+					SDE_EVTLOG_ERROR);
+			mutex_unlock(&sde_enc->rc_lock);
+			return -EINVAL;
 		}
 
-		/*
-		 * disable the clks and resources only if the resource control
-		 * is in ON state, otherwise the clks and resources would have
-		 * been disabled while going into IDLE state
+		/**
+		 * expect to arrive here only if in either idle state or pre-off
+		 * and in IDLE state the resources are already disabled
 		 */
-		if (sde_enc->rc_state == SDE_ENC_RC_STATE_ON)
+		if (sde_enc->rc_state == SDE_ENC_RC_STATE_PRE_OFF)
 			_sde_encoder_resource_control_helper(drm_enc, false);
 
 		SDE_EVT32(DRMID(drm_enc), sw_event, sde_enc->rc_state,
@@ -1533,6 +1662,7 @@
 		 */
 		if (sde_enc->rc_state == SDE_ENC_RC_STATE_IDLE) {
 			_sde_encoder_resource_control_helper(drm_enc, true);
+			_sde_encoder_resource_control_rsc_update(drm_enc, true);
 			sde_enc->rc_state = SDE_ENC_RC_STATE_ON;
 			schedule_off = true;
 		}
@@ -1561,10 +1691,10 @@
 		mutex_lock(&sde_enc->rc_lock);
 
 		if (sde_enc->rc_state != SDE_ENC_RC_STATE_ON) {
-			SDE_DEBUG_ENC(sde_enc, "sw_event:%d, rc:%d !ON state\n",
+			SDE_ERROR_ENC(sde_enc, "sw_event:%d, rc:%d !ON state\n",
 					sw_event, sde_enc->rc_state);
-			SDE_EVT32_VERBOSE(DRMID(drm_enc), sw_event,
-					sde_enc->rc_state);
+			SDE_EVT32(DRMID(drm_enc), sw_event, sde_enc->rc_state,
+					SDE_EVTLOG_ERROR);
 			mutex_unlock(&sde_enc->rc_lock);
 			return 0;
 		}
@@ -1574,16 +1704,17 @@
 		 * ignore the IDLE event, it's probably a stale timer event
 		 */
 		if (sde_enc->frame_busy_mask[0]) {
-			SDE_DEBUG_ENC(sde_enc,
+			SDE_ERROR_ENC(sde_enc,
 					"sw_event:%d, rc:%d frame pending\n",
 					sw_event, sde_enc->rc_state);
-			SDE_EVT32_VERBOSE(DRMID(drm_enc), sw_event,
-					sde_enc->rc_state);
+			SDE_EVT32(DRMID(drm_enc), sw_event, sde_enc->rc_state,
+					SDE_EVTLOG_ERROR);
 			mutex_unlock(&sde_enc->rc_lock);
 			return 0;
 		}
 
 		/* disable all the clks and resources */
+		_sde_encoder_resource_control_rsc_update(drm_enc, false);
 		_sde_encoder_resource_control_helper(drm_enc, false);
 		SDE_EVT32(DRMID(drm_enc), sw_event, sde_enc->rc_state,
 				SDE_ENC_RC_STATE_IDLE, SDE_EVTLOG_FUNC_CASE5);
@@ -1869,18 +2000,18 @@
 
 	SDE_EVT32(DRMID(drm_enc));
 
+	/* wait for idle */
+	sde_encoder_wait_for_event(drm_enc, MSM_ENC_TX_COMPLETE);
+
+	sde_encoder_resource_control(drm_enc, SDE_ENC_RC_EVENT_PRE_STOP);
+
 	for (i = 0; i < sde_enc->num_phys_encs; i++) {
 		struct sde_encoder_phys *phys = sde_enc->phys_encs[i];
 
-		if (phys && phys->ops.disable && !phys->ops.is_master(phys)) {
+		if (phys && phys->ops.disable)
 			phys->ops.disable(phys);
-			phys->connector = NULL;
-		}
 	}
 
-	if (sde_enc->cur_master && sde_enc->cur_master->ops.disable)
-		sde_enc->cur_master->ops.disable(sde_enc->cur_master);
-
 	/* after phys waits for frame-done, should be no more frames pending */
 	if (atomic_xchg(&sde_enc->frame_done_timeout, 0)) {
 		SDE_ERROR("enc%d timeout pending\n", drm_enc->base.id);
@@ -3237,6 +3368,13 @@
 		case MSM_ENC_TX_COMPLETE:
 			fn_wait = phys->ops.wait_for_tx_complete;
 			break;
+		case MSM_ENC_VBLANK:
+			fn_wait = phys->ops.wait_for_vblank;
+			break;
+		default:
+			SDE_ERROR_ENC(sde_enc, "unknown wait event %d\n",
+					event);
+			return -EINVAL;
 		};
 
 		if (phys && fn_wait) {
diff --git a/drivers/gpu/drm/msm/sde/sde_encoder_phys.h b/drivers/gpu/drm/msm/sde/sde_encoder_phys.h
index 7170d55..2098b9f 100644
--- a/drivers/gpu/drm/msm/sde/sde_encoder_phys.h
+++ b/drivers/gpu/drm/msm/sde/sde_encoder_phys.h
@@ -113,6 +113,7 @@
  *				current pending frames to hardware
  * @wait_for_tx_complete:	Wait for hardware to transfer the pixels
  *				to the panel
+ * @wait_for_vblank:		Wait for VBLANK, for sub-driver internal use
  * @prepare_for_kickoff:	Do any work necessary prior to a kickoff
  *				For CMD encoder, may wait for previous tx done
  * @handle_post_kickoff:	Do any work necessary post-kickoff work
@@ -152,6 +153,7 @@
 	int (*control_vblank_irq)(struct sde_encoder_phys *enc, bool enable);
 	int (*wait_for_commit_done)(struct sde_encoder_phys *phys_enc);
 	int (*wait_for_tx_complete)(struct sde_encoder_phys *phys_enc);
+	int (*wait_for_vblank)(struct sde_encoder_phys *phys_enc);
 	void (*prepare_for_kickoff)(struct sde_encoder_phys *phys_enc,
 			struct sde_encoder_kickoff_params *params);
 	void (*handle_post_kickoff)(struct sde_encoder_phys *phys_enc);
@@ -313,10 +315,12 @@
  * @serialize_wait4pp:	serialize wait4pp feature waits for pp_done interrupt
  *			after ctl_start instead of before next frame kickoff
  * @pp_timeout_report_cnt: number of pingpong done irq timeout errors
+ * @autorefresh: autorefresh feature state
  * @pending_rd_ptr_cnt: atomic counter to indicate if retire fence can be
  *                      signaled at the next rd_ptr_irq
  * @rd_ptr_timestamp: last rd_ptr_irq timestamp
- * @autorefresh: autorefresh feature state
+ * @pending_vblank_cnt: Atomic counter tracking pending wait for VBLANK
+ * @pending_vblank_wq: Wait queue for blocking until VBLANK received
  */
 struct sde_encoder_phys_cmd {
 	struct sde_encoder_phys base;
@@ -326,6 +330,8 @@
 	struct sde_encoder_phys_cmd_autorefresh autorefresh;
 	atomic_t pending_rd_ptr_cnt;
 	ktime_t rd_ptr_timestamp;
+	atomic_t pending_vblank_cnt;
+	wait_queue_head_t pending_vblank_wq;
 };
 
 /**
diff --git a/drivers/gpu/drm/msm/sde/sde_encoder_phys_cmd.c b/drivers/gpu/drm/msm/sde/sde_encoder_phys_cmd.c
index ad00a7f..aacf70b 100644
--- a/drivers/gpu/drm/msm/sde/sde_encoder_phys_cmd.c
+++ b/drivers/gpu/drm/msm/sde/sde_encoder_phys_cmd.c
@@ -250,6 +250,8 @@
 
 	cmd_enc->rd_ptr_timestamp = ktime_get();
 
+	atomic_add_unless(&cmd_enc->pending_vblank_cnt, -1, 0);
+	wake_up_all(&cmd_enc->pending_vblank_wq);
 	SDE_ATRACE_END("rd_ptr_irq");
 }
 
@@ -599,22 +601,29 @@
 	struct sde_encoder_phys_cmd *cmd_enc =
 		to_sde_encoder_phys_cmd(phys_enc);
 	int ret = 0;
+	int refcount;
 
 	if (!phys_enc) {
 		SDE_ERROR("invalid encoder\n");
 		return -EINVAL;
 	}
 
+	refcount = atomic_read(&phys_enc->vblank_refcount);
+
 	/* Slave encoders don't report vblank */
 	if (!sde_encoder_phys_cmd_is_master(phys_enc))
 		goto end;
 
-	SDE_DEBUG_CMDENC(cmd_enc, "[%pS] enable=%d/%d\n",
-			__builtin_return_address(0),
-			enable, atomic_read(&phys_enc->vblank_refcount));
+	/* protect against negative */
+	if (!enable && refcount == 0) {
+		ret = -EINVAL;
+		goto end;
+	}
 
+	SDE_DEBUG_CMDENC(cmd_enc, "[%pS] enable=%d/%d\n",
+			__builtin_return_address(0), enable, refcount);
 	SDE_EVT32(DRMID(phys_enc->parent), phys_enc->hw_pp->idx - PINGPONG_0,
-			enable, atomic_read(&phys_enc->vblank_refcount));
+			enable, refcount);
 
 	if (enable && atomic_inc_return(&phys_enc->vblank_refcount) == 1)
 		ret = sde_encoder_helper_register_irq(phys_enc, INTR_IDX_RDPTR);
@@ -623,10 +632,14 @@
 				INTR_IDX_RDPTR);
 
 end:
-	if (ret)
+	if (ret) {
 		SDE_ERROR_CMDENC(cmd_enc,
-				"control vblank irq error %d, enable %d\n",
-				ret, enable);
+				"control vblank irq error %d, enable %d, refcount %d\n",
+				ret, enable, refcount);
+		SDE_EVT32(DRMID(phys_enc->parent),
+				phys_enc->hw_pp->idx - PINGPONG_0,
+				enable, refcount, SDE_EVTLOG_ERROR);
+	}
 
 	return ret;
 }
@@ -866,33 +879,22 @@
 {
 	struct sde_encoder_phys_cmd *cmd_enc =
 		to_sde_encoder_phys_cmd(phys_enc);
-	int ret;
 
 	if (!phys_enc || !phys_enc->hw_pp) {
 		SDE_ERROR("invalid encoder\n");
 		return;
 	}
-	SDE_DEBUG_CMDENC(cmd_enc, "pp %d\n", phys_enc->hw_pp->idx - PINGPONG_0);
+	SDE_DEBUG_CMDENC(cmd_enc, "pp %d state %d\n",
+			phys_enc->hw_pp->idx - PINGPONG_0,
+			phys_enc->enable_state);
+	SDE_EVT32(DRMID(phys_enc->parent), phys_enc->hw_pp->idx - PINGPONG_0,
+			phys_enc->enable_state);
 
 	if (phys_enc->enable_state == SDE_ENC_DISABLED) {
 		SDE_ERROR_CMDENC(cmd_enc, "already disabled\n");
 		return;
 	}
 
-	SDE_EVT32(DRMID(phys_enc->parent), phys_enc->hw_pp->idx - PINGPONG_0);
-
-	if (!_sde_encoder_phys_is_ppsplit_slave(phys_enc)) {
-		ret = _sde_encoder_phys_cmd_wait_for_idle(phys_enc);
-		if (ret) {
-			atomic_set(&phys_enc->pending_kickoff_cnt, 0);
-			SDE_ERROR_CMDENC(cmd_enc,
-					"pp %d failed wait for idle, %d\n",
-					phys_enc->hw_pp->idx - PINGPONG_0, ret);
-			SDE_EVT32(DRMID(phys_enc->parent),
-					phys_enc->hw_pp->idx - PINGPONG_0, ret);
-		}
-	}
-
 	if (phys_enc->hw_pp->ops.enable_tearcheck)
 		phys_enc->hw_pp->ops.enable_tearcheck(phys_enc->hw_pp, false);
 	phys_enc->enable_state = SDE_ENC_DISABLED;
@@ -1047,6 +1049,34 @@
 	return rc;
 }
 
+static int sde_encoder_phys_cmd_wait_for_vblank(
+		struct sde_encoder_phys *phys_enc)
+{
+	int rc = 0;
+	struct sde_encoder_phys_cmd *cmd_enc;
+	struct sde_encoder_wait_info wait_info;
+
+	if (!phys_enc)
+		return -EINVAL;
+
+	cmd_enc = to_sde_encoder_phys_cmd(phys_enc);
+
+	/* only required for master controller */
+	if (!sde_encoder_phys_cmd_is_master(phys_enc))
+		return rc;
+
+	wait_info.wq = &cmd_enc->pending_vblank_wq;
+	wait_info.atomic_cnt = &cmd_enc->pending_vblank_cnt;
+	wait_info.timeout_ms = _sde_encoder_phys_cmd_get_idle_timeout(cmd_enc);
+
+	atomic_inc(&cmd_enc->pending_vblank_cnt);
+
+	rc = sde_encoder_helper_wait_for_irq(phys_enc, INTR_IDX_RDPTR,
+			&wait_info);
+
+	return rc;
+}
+
 static void sde_encoder_phys_cmd_update_split_role(
 		struct sde_encoder_phys *phys_enc,
 		enum sde_enc_split_role role)
@@ -1167,6 +1197,7 @@
 	ops->wait_for_commit_done = sde_encoder_phys_cmd_wait_for_commit_done;
 	ops->prepare_for_kickoff = sde_encoder_phys_cmd_prepare_for_kickoff;
 	ops->wait_for_tx_complete = sde_encoder_phys_cmd_wait_for_tx_complete;
+	ops->wait_for_vblank = sde_encoder_phys_cmd_wait_for_vblank;
 	ops->trigger_start = sde_encoder_phys_cmd_trigger_start;
 	ops->needs_single_flush = sde_encoder_phys_cmd_needs_single_flush;
 	ops->hw_reset = sde_encoder_helper_hw_reset;
@@ -1258,7 +1289,9 @@
 	atomic_set(&phys_enc->pending_ctlstart_cnt, 0);
 	atomic_set(&phys_enc->pending_retire_fence_cnt, 0);
 	atomic_set(&cmd_enc->pending_rd_ptr_cnt, 0);
+	atomic_set(&cmd_enc->pending_vblank_cnt, 0);
 	init_waitqueue_head(&phys_enc->pending_kickoff_wq);
+	init_waitqueue_head(&cmd_enc->pending_vblank_wq);
 	atomic_set(&cmd_enc->autorefresh.kickoff_cnt, 0);
 	init_waitqueue_head(&cmd_enc->autorefresh.kickoff_wq);
 
diff --git a/drivers/gpu/drm/msm/sde/sde_encoder_phys_vid.c b/drivers/gpu/drm/msm/sde/sde_encoder_phys_vid.c
index 933e4812..7850cb7 100644
--- a/drivers/gpu/drm/msm/sde/sde_encoder_phys_vid.c
+++ b/drivers/gpu/drm/msm/sde/sde_encoder_phys_vid.c
@@ -657,7 +657,7 @@
 	hw_res->intfs[vid_enc->hw_intf->idx - INTF_0] = INTF_MODE_VIDEO;
 }
 
-static int sde_encoder_phys_vid_wait_for_vblank(
+static int _sde_encoder_phys_vid_wait_for_vblank(
 		struct sde_encoder_phys *phys_enc, bool notify)
 {
 	struct sde_encoder_wait_info wait_info;
@@ -696,10 +696,10 @@
 	return ret;
 }
 
-static int sde_encoder_phys_vid_wait_for_commit_done(
+static int sde_encoder_phys_vid_wait_for_vblank(
 		struct sde_encoder_phys *phys_enc)
 {
-	return sde_encoder_phys_vid_wait_for_vblank(phys_enc, true);
+	return _sde_encoder_phys_vid_wait_for_vblank(phys_enc, true);
 }
 
 static void sde_encoder_phys_vid_prepare_for_kickoff(
@@ -781,7 +781,7 @@
 	 * scanout buffer) don't latch properly..
 	 */
 	if (sde_encoder_phys_vid_is_master(phys_enc)) {
-		ret = sde_encoder_phys_vid_wait_for_vblank(phys_enc, false);
+		ret = _sde_encoder_phys_vid_wait_for_vblank(phys_enc, false);
 		if (ret) {
 			atomic_set(&phys_enc->pending_kickoff_cnt, 0);
 			SDE_ERROR_VIDENC(vid_enc,
@@ -866,7 +866,9 @@
 	ops->destroy = sde_encoder_phys_vid_destroy;
 	ops->get_hw_resources = sde_encoder_phys_vid_get_hw_resources;
 	ops->control_vblank_irq = sde_encoder_phys_vid_control_vblank_irq;
-	ops->wait_for_commit_done = sde_encoder_phys_vid_wait_for_commit_done;
+	ops->wait_for_commit_done = sde_encoder_phys_vid_wait_for_vblank;
+	ops->wait_for_vblank = sde_encoder_phys_vid_wait_for_vblank;
+	ops->wait_for_tx_complete = sde_encoder_phys_vid_wait_for_vblank;
 	ops->prepare_for_kickoff = sde_encoder_phys_vid_prepare_for_kickoff;
 	ops->handle_post_kickoff = sde_encoder_phys_vid_handle_post_kickoff;
 	ops->needs_single_flush = sde_encoder_phys_vid_needs_single_flush;
diff --git a/drivers/gpu/drm/msm/sde_dbg.h b/drivers/gpu/drm/msm/sde_dbg.h
index e14d60e..843dbc9 100644
--- a/drivers/gpu/drm/msm/sde_dbg.h
+++ b/drivers/gpu/drm/msm/sde_dbg.h
@@ -25,6 +25,7 @@
 #define SDE_EVTLOG_FUNC_CASE3	0x5555
 #define SDE_EVTLOG_FUNC_CASE4	0x6666
 #define SDE_EVTLOG_FUNC_CASE5	0x7777
+#define SDE_EVTLOG_FUNC_CASE6	0x8888
 #define SDE_EVTLOG_PANIC	0xdead
 #define SDE_EVTLOG_FATAL	0xbad
 #define SDE_EVTLOG_ERROR	0xebad
diff --git a/drivers/gpu/drm/msm/sde_power_handle.c b/drivers/gpu/drm/msm/sde_power_handle.c
index 28a2d4d..e5c83cb 100644
--- a/drivers/gpu/drm/msm/sde_power_handle.c
+++ b/drivers/gpu/drm/msm/sde_power_handle.c
@@ -76,7 +76,7 @@
 
 	if (phandle->rsc_client)
 		ret = sde_rsc_client_state_update(phandle->rsc_client,
-			rsc_state, NULL, -1);
+			rsc_state, NULL, SDE_RSC_INVALID_CRTC_ID, NULL);
 
 	return ret;
 }
diff --git a/drivers/gpu/drm/msm/sde_rsc.c b/drivers/gpu/drm/msm/sde_rsc.c
index 4fc40d9..0418644 100644
--- a/drivers/gpu/drm/msm/sde_rsc.c
+++ b/drivers/gpu/drm/msm/sde_rsc.c
@@ -58,12 +58,8 @@
 #define TRY_CLK_MODE_SWITCH		0xFFFE
 #define STATE_UPDATE_NOT_ALLOWED	0xFFFD
 
-/**
- * Expected primary command mode panel vsync ranges
- * Note: update if a primary panel is expected to run lower than 60fps
- */
-#define PRIMARY_VBLANK_MIN_US (18 * 1000)
-#define PRIMARY_VBLANK_MAX_US (20 * 1000)
+/* Primary panel worst case VSYNC expected to be no less than 30fps */
+#define PRIMARY_VBLANK_WORST_CASE_MS 34
 
 static struct sde_rsc_priv *rsc_prv_list[MAX_RSC_COUNT];
 
@@ -153,9 +149,22 @@
 	state = client->current_state;
 	mutex_unlock(&rsc->client_lock);
 
-	if (state != SDE_RSC_IDLE_STATE)
-		sde_rsc_client_state_update(client, SDE_RSC_IDLE_STATE,
-								NULL, -1);
+	if (state != SDE_RSC_IDLE_STATE) {
+		int wait_vblank_crtc_id;
+
+		sde_rsc_client_state_update(client, SDE_RSC_IDLE_STATE, NULL,
+				SDE_RSC_INVALID_CRTC_ID, &wait_vblank_crtc_id);
+
+		/* if vblank wait required at shutdown, use a simple sleep */
+		if (wait_vblank_crtc_id != SDE_RSC_INVALID_CRTC_ID) {
+			pr_err("unexpected sleep required on crtc %d at rsc client destroy\n",
+					wait_vblank_crtc_id);
+			SDE_EVT32(client->id, state, rsc->current_state,
+					client->crtc_id, wait_vblank_crtc_id,
+					SDE_EVTLOG_ERROR);
+			msleep(PRIMARY_VBLANK_WORST_CASE_MS);
+		}
+	}
 	mutex_lock(&rsc->client_lock);
 	list_del_init(&client->list);
 	mutex_unlock(&rsc->client_lock);
@@ -449,7 +458,8 @@
 
 static int sde_rsc_switch_to_cmd(struct sde_rsc_priv *rsc,
 	struct sde_rsc_cmd_config *config,
-	struct sde_rsc_client *caller_client)
+	struct sde_rsc_client *caller_client,
+	int *wait_vblank_crtc_id)
 {
 	struct sde_rsc_client *client;
 	int rc = STATE_UPDATE_NOT_ALLOWED;
@@ -469,9 +479,19 @@
 	if (config)
 		sde_rsc_timer_calculate(rsc, config);
 
+	/**
+	 * rsc clients can still send config at any time. If a config is
+	 * received during cmd_state then vsync_wait will execute with the logic
+	 * below. If a config is received when rsc is in AMC mode; A mode
+	 * switch will do the vsync wait. updated checks still support all cases
+	 * for dynamic mode switch and inline rotation.
+	 */
 	if (rsc->current_state == SDE_RSC_CMD_STATE) {
 		rc = 0;
-		goto vsync_wait;
+		if (config)
+			goto vsync_wait;
+		else
+			goto end;
 	}
 
 	/* any one client in video state blocks the cmd state switch */
@@ -486,15 +506,29 @@
 	}
 
 vsync_wait:
-	/* wait for vsync for vid to cmd state switch and config update */
+	/* indicate wait for vsync for vid to cmd state switch & cfg update */
 	if (!rc && (rsc->current_state == SDE_RSC_VID_STATE ||
-			rsc->current_state == SDE_RSC_CMD_STATE))
-		usleep_range(PRIMARY_VBLANK_MIN_US, PRIMARY_VBLANK_MAX_US);
+			rsc->current_state == SDE_RSC_CMD_STATE)) {
+		/* clear VSYNC timestamp for indication when update completes */
+		if (rsc->hw_ops.hw_vsync)
+			rsc->hw_ops.hw_vsync(rsc, VSYNC_ENABLE, NULL, 0, 0);
+		if (!wait_vblank_crtc_id) {
+			pr_err("invalid crtc id wait pointer, client %d\n",
+					caller_client->id);
+			SDE_EVT32(caller_client->id, rsc->current_state,
+					caller_client->crtc_id,
+					wait_vblank_crtc_id, SDE_EVTLOG_ERROR);
+			msleep(PRIMARY_VBLANK_WORST_CASE_MS);
+		} else {
+			*wait_vblank_crtc_id = rsc->primary_client->crtc_id;
+		}
+	}
 end:
 	return rc;
 }
 
-static int sde_rsc_switch_to_clk(struct sde_rsc_priv *rsc)
+static int sde_rsc_switch_to_clk(struct sde_rsc_priv *rsc,
+		int *wait_vblank_crtc_id)
 {
 	struct sde_rsc_client *client;
 	int rc = STATE_UPDATE_NOT_ALLOWED;
@@ -510,17 +544,27 @@
 			rpmh_mode_solver_set(rsc->disp_rsc, false);
 	}
 
-	/* wait for vsync for cmd to clk state switch */
+	/* indicate wait for vsync for cmd to clk state switch */
 	if (!rc && rsc->primary_client &&
-				(rsc->current_state == SDE_RSC_CMD_STATE))
-		usleep_range(PRIMARY_VBLANK_MIN_US, PRIMARY_VBLANK_MAX_US);
+			(rsc->current_state == SDE_RSC_CMD_STATE)) {
+		/* clear VSYNC timestamp for indication when update completes */
+		if (rsc->hw_ops.hw_vsync)
+			rsc->hw_ops.hw_vsync(rsc, VSYNC_ENABLE, NULL, 0, 0);
+		if (!wait_vblank_crtc_id) {
+			pr_err("invalid crtc id wait pointer provided\n");
+			msleep(PRIMARY_VBLANK_WORST_CASE_MS);
+		} else {
+			*wait_vblank_crtc_id = rsc->primary_client->crtc_id;
+		}
+	}
 end:
 	return rc;
 }
 
 static int sde_rsc_switch_to_vid(struct sde_rsc_priv *rsc,
 	struct sde_rsc_cmd_config *config,
-	struct sde_rsc_client *caller_client)
+	struct sde_rsc_client *caller_client,
+	int *wait_vblank_crtc_id)
 {
 	int rc = 0;
 
@@ -539,16 +583,62 @@
 			rpmh_mode_solver_set(rsc->disp_rsc, false);
 	}
 
-	/* wait for vsync for cmd to vid state switch */
+	/* indicate wait for vsync for cmd to vid state switch */
 	if (!rc && rsc->primary_client &&
-			(rsc->current_state == SDE_RSC_CMD_STATE))
-		usleep_range(PRIMARY_VBLANK_MIN_US, PRIMARY_VBLANK_MAX_US);
+			(rsc->current_state == SDE_RSC_CMD_STATE)) {
+		/* clear VSYNC timestamp for indication when update completes */
+		if (rsc->hw_ops.hw_vsync)
+			rsc->hw_ops.hw_vsync(rsc, VSYNC_ENABLE, NULL, 0, 0);
+		if (!wait_vblank_crtc_id) {
+			pr_err("invalid crtc id wait pointer provided\n");
+			msleep(PRIMARY_VBLANK_WORST_CASE_MS);
+		} else {
+			*wait_vblank_crtc_id = rsc->primary_client->crtc_id;
+		}
+	}
 
 end:
 	return rc;
 }
 
 /**
+ * sde_rsc_client_is_state_update_complete() - check if state update is complete
+ * RSC state transition is not complete until HW receives VBLANK signal. This
+ * function checks RSC HW to determine whether that signal has been received.
+ * @client:	 Client pointer provided by sde_rsc_client_create().
+ *
+ * Return: true if the state update has completed.
+ */
+bool sde_rsc_client_is_state_update_complete(
+		struct sde_rsc_client *caller_client)
+{
+	struct sde_rsc_priv *rsc;
+	u32 vsync_timestamp0 = 0;
+
+	if (!caller_client) {
+		pr_err("invalid client for rsc state update\n");
+		return false;
+	} else if (caller_client->rsc_index >= MAX_RSC_COUNT) {
+		pr_err("invalid rsc index\n");
+		return false;
+	}
+
+	rsc = rsc_prv_list[caller_client->rsc_index];
+	if (!rsc)
+		return false;
+
+	/**
+	 * state updates clear VSYNC timestamp, check if a new one arrived.
+	 * use VSYNC mode 0 (CMD TE) always for this, per HW recommendation.
+	 */
+	if (rsc->hw_ops.hw_vsync)
+		vsync_timestamp0 = rsc->hw_ops.hw_vsync(rsc, VSYNC_READ_VSYNC0,
+				NULL, 0, 0);
+
+	return vsync_timestamp0 != 0;
+}
+
+/**
  * sde_rsc_client_state_update() - rsc client state update
  * Video mode, cmd mode and clk state are suppoed as modes. A client need to
  * set this property during panel config time. A switching client can set the
@@ -559,12 +649,18 @@
  * @config:	 fps, vtotal, porches, etc configuration for command mode
  *               panel
  * @crtc_id:	 current client's crtc id
+ * @wait_vblank_crtc_id:	Output parameter. If set to non-zero, rsc hw
+ *				state update requires a wait for one vblank on
+ *				the primary crtc. In that case, this output
+ *				param will be set to the crtc on which to wait.
+ *				If SDE_RSC_INVALID_CRTC_ID, no wait necessary
  *
  * Return: error code.
  */
 int sde_rsc_client_state_update(struct sde_rsc_client *caller_client,
 	enum sde_rsc_state state,
-	struct sde_rsc_cmd_config *config, int crtc_id)
+	struct sde_rsc_cmd_config *config, int crtc_id,
+	int *wait_vblank_crtc_id)
 {
 	int rc = 0;
 	struct sde_rsc_priv *rsc;
@@ -581,6 +677,9 @@
 	if (!rsc)
 		return -EINVAL;
 
+	if (wait_vblank_crtc_id)
+		*wait_vblank_crtc_id = SDE_RSC_INVALID_CRTC_ID;
+
 	mutex_lock(&rsc->client_lock);
 	SDE_EVT32_VERBOSE(caller_client->id, caller_client->current_state,
 			state, rsc->current_state, SDE_EVTLOG_FUNC_ENTRY);
@@ -610,28 +709,31 @@
 		/* video state client might be exiting; try cmd state switch */
 		if (rc == TRY_CMD_MODE_SWITCH) {
 			rc = sde_rsc_switch_to_cmd(rsc, NULL,
-							rsc->primary_client);
+					rsc->primary_client,
+					wait_vblank_crtc_id);
 			if (!rc)
 				state = SDE_RSC_CMD_STATE;
 
 		/* cmd state client might be exiting; try clk state switch */
 		} else if (rc == TRY_CLK_MODE_SWITCH) {
-			rc = sde_rsc_switch_to_clk(rsc);
+			rc = sde_rsc_switch_to_clk(rsc, wait_vblank_crtc_id);
 			if (!rc)
 				state = SDE_RSC_CLK_STATE;
 		}
 		break;
 
 	case SDE_RSC_CMD_STATE:
-		rc = sde_rsc_switch_to_cmd(rsc, config, caller_client);
+		rc = sde_rsc_switch_to_cmd(rsc, config, caller_client,
+				wait_vblank_crtc_id);
 		break;
 
 	case SDE_RSC_VID_STATE:
-		rc = sde_rsc_switch_to_vid(rsc, config, caller_client);
+		rc = sde_rsc_switch_to_vid(rsc, config, caller_client,
+				wait_vblank_crtc_id);
 		break;
 
 	case SDE_RSC_CLK_STATE:
-		rc = sde_rsc_switch_to_clk(rsc);
+		rc = sde_rsc_switch_to_clk(rsc, wait_vblank_crtc_id);
 		break;
 
 	default:
diff --git a/drivers/gpu/drm/msm/sde_rsc_hw.c b/drivers/gpu/drm/msm/sde_rsc_hw.c
index aa8fa01..4b61def 100644
--- a/drivers/gpu/drm/msm/sde_rsc_hw.c
+++ b/drivers/gpu/drm/msm/sde_rsc_hw.c
@@ -428,6 +428,7 @@
 
 	if (rc) {
 		pr_err("mdss gdsc power down failed rc:%d\n", rc);
+		SDE_EVT32(rc, SDE_EVTLOG_ERROR);
 		goto end;
 	}
 
@@ -675,10 +676,24 @@
 				rsc->debug_mode));
 		break;
 
+	case VSYNC_READ_VSYNC0:
+		return dss_reg_r(&rsc->wrapper_io,
+				SDE_RSCC_WRAPPER_VSYNC_TIMESTAMP0,
+				rsc->debug_mode);
+
 	case VSYNC_ENABLE:
-		reg = BIT(8) | ((mode & 0x7) << 10);
+		/* clear the current VSYNC value */
+		reg = BIT(9) | ((mode & 0x7) << 10);
 		dss_reg_w(&rsc->wrapper_io, SDE_RSCC_WRAPPER_DEBUG_BUS,
 					reg, rsc->debug_mode);
+
+		/* enable the VSYNC logging */
+		reg = BIT(8) | ((mode & 0x7) << 10);
+		dss_reg_w(&rsc->wrapper_io, SDE_RSCC_WRAPPER_DEBUG_BUS,
+				reg, rsc->debug_mode);
+
+		/* ensure vsync config has been written before waiting on it */
+		wmb();
 		break;
 
 	case VSYNC_DISABLE:
diff --git a/drivers/gpu/drm/msm/sde_rsc_priv.h b/drivers/gpu/drm/msm/sde_rsc_priv.h
index b90b0ac..fe338d37 100644
--- a/drivers/gpu/drm/msm/sde_rsc_priv.h
+++ b/drivers/gpu/drm/msm/sde_rsc_priv.h
@@ -51,11 +51,13 @@
 /**
  * rsc_vsync_req: sde rsc vsync request information
  * VSYNC_READ: read vsync status
+ * VSYNC_READ_VSYNC0: read value vsync0 timestamp (cast to int from u32)
  * VSYNC_ENABLE: enable rsc wrapper vsync status
  * VSYNC_DISABLE: disable rsc wrapper vsync status
  */
 enum rsc_vsync_req {
 	VSYNC_READ,
+	VSYNC_READ_VSYNC0,
 	VSYNC_ENABLE,
 	VSYNC_DISABLE,
 };