diff mbox

[v3,3/4] drm/msm/mdp5: Add START signal to kick off certain pipelines

Message ID 1426276174-17010-4-git-send-email-sviau@codeaurora.org (mailing list archive)
State Not Applicable, archived
Headers show

Commit Message

Stephane Viau March 13, 2015, 7:49 p.m. UTC
Some interfaces (WB, DSI Command Mode) need to be kicked off
through a START Signal. This signal needs to be sent at the right
time and requests in some cases to keep track of the pipeline
status (eg: whether pipeline registers are flushed AND output WB
buffers are ready, in case of WB interface).

Signed-off-by: Stephane Viau <sviau@codeaurora.org>
---
 drivers/gpu/drm/msm/mdp/mdp5/mdp5_cfg.c     |   2 +
 drivers/gpu/drm/msm/mdp/mdp5/mdp5_cfg.h     |   7 +-
 drivers/gpu/drm/msm/mdp/mdp5/mdp5_crtc.c    |  31 ++--
 drivers/gpu/drm/msm/mdp/mdp5/mdp5_ctl.c     | 247 ++++++++++++++++++++++++----
 drivers/gpu/drm/msm/mdp/mdp5/mdp5_ctl.h     |  72 +++-----
 drivers/gpu/drm/msm/mdp/mdp5/mdp5_encoder.c |  13 +-
 drivers/gpu/drm/msm/mdp/mdp5/mdp5_kms.h     |   1 +
 7 files changed, 276 insertions(+), 97 deletions(-)

Comments

Archit Taneja March 23, 2015, 10:50 a.m. UTC | #1
Hi Stephane,

On 03/14/2015 01:19 AM, Stephane Viau wrote:
> Some interfaces (WB, DSI Command Mode) need to be kicked off
> through a START Signal. This signal needs to be sent at the right
> time and requests in some cases to keep track of the pipeline
> status (eg: whether pipeline registers are flushed AND output WB
> buffers are ready, in case of WB interface).
>
> Signed-off-by: Stephane Viau <sviau@codeaurora.org>
> ---
>   drivers/gpu/drm/msm/mdp/mdp5/mdp5_cfg.c     |   2 +
>   drivers/gpu/drm/msm/mdp/mdp5/mdp5_cfg.h     |   7 +-
>   drivers/gpu/drm/msm/mdp/mdp5/mdp5_crtc.c    |  31 ++--
>   drivers/gpu/drm/msm/mdp/mdp5/mdp5_ctl.c     | 247 ++++++++++++++++++++++++----
>   drivers/gpu/drm/msm/mdp/mdp5/mdp5_ctl.h     |  72 +++-----
>   drivers/gpu/drm/msm/mdp/mdp5/mdp5_encoder.c |  13 +-
>   drivers/gpu/drm/msm/mdp/mdp5/mdp5_kms.h     |   1 +
>   7 files changed, 276 insertions(+), 97 deletions(-)
>
> diff --git a/drivers/gpu/drm/msm/mdp/mdp5/mdp5_cfg.c b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_cfg.c
> index c078f30..72c075a 100644
> --- a/drivers/gpu/drm/msm/mdp/mdp5/mdp5_cfg.c
> +++ b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_cfg.c
> @@ -31,6 +31,7 @@ const struct mdp5_cfg_hw msm8x74_config = {
>   	.ctl = {
>   		.count = 5,
>   		.base = { 0x00600, 0x00700, 0x00800, 0x00900, 0x00a00 },
> +		.flush_hw_mask = 0x0003ffff,
>   	},
>   	.pipe_vig = {
>   		.count = 3,
> @@ -78,6 +79,7 @@ const struct mdp5_cfg_hw apq8084_config = {
>   	.ctl = {
>   		.count = 5,
>   		.base = { 0x00600, 0x00700, 0x00800, 0x00900, 0x00a00 },
> +		.flush_hw_mask = 0x003fffff,

msm8x16 would require a flush_hw_mask too, it should be 0x32a59 if I'm 
not wrong. Could you please add it for the next revision, or as a part 
of the 8x16 hw cfg patch?

Thanks,
Archit
Stephane Viau March 23, 2015, 10:10 p.m. UTC | #2
Hi Archit,

> Hi Stephane,
>
> On 03/14/2015 01:19 AM, Stephane Viau wrote:
>> Some interfaces (WB, DSI Command Mode) need to be kicked off
>> through a START Signal. This signal needs to be sent at the right
>> time and requests in some cases to keep track of the pipeline
>> status (eg: whether pipeline registers are flushed AND output WB
>> buffers are ready, in case of WB interface).
>>
>> Signed-off-by: Stephane Viau <sviau@codeaurora.org>
>> ---
>>   drivers/gpu/drm/msm/mdp/mdp5/mdp5_cfg.c     |   2 +
>>   drivers/gpu/drm/msm/mdp/mdp5/mdp5_cfg.h     |   7 +-
>>   drivers/gpu/drm/msm/mdp/mdp5/mdp5_crtc.c    |  31 ++--
>>   drivers/gpu/drm/msm/mdp/mdp5/mdp5_ctl.c     | 247
>> ++++++++++++++++++++++++----
>>   drivers/gpu/drm/msm/mdp/mdp5/mdp5_ctl.h     |  72 +++-----
>>   drivers/gpu/drm/msm/mdp/mdp5/mdp5_encoder.c |  13 +-
>>   drivers/gpu/drm/msm/mdp/mdp5/mdp5_kms.h     |   1 +
>>   7 files changed, 276 insertions(+), 97 deletions(-)
>>
>> diff --git a/drivers/gpu/drm/msm/mdp/mdp5/mdp5_cfg.c
>> b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_cfg.c
>> index c078f30..72c075a 100644
>> --- a/drivers/gpu/drm/msm/mdp/mdp5/mdp5_cfg.c
>> +++ b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_cfg.c
>> @@ -31,6 +31,7 @@ const struct mdp5_cfg_hw msm8x74_config = {
>>   	.ctl = {
>>   		.count = 5,
>>   		.base = { 0x00600, 0x00700, 0x00800, 0x00900, 0x00a00 },
>> +		.flush_hw_mask = 0x0003ffff,
>>   	},
>>   	.pipe_vig = {
>>   		.count = 3,
>> @@ -78,6 +79,7 @@ const struct mdp5_cfg_hw apq8084_config = {
>>   	.ctl = {
>>   		.count = 5,
>>   		.base = { 0x00600, 0x00700, 0x00800, 0x00900, 0x00a00 },
>> +		.flush_hw_mask = 0x003fffff,
>
> msm8x16 would require a flush_hw_mask too, it should be 0x32a59 if I'm
> not wrong. Could you please add it for the next revision, or as a part
> of the 8x16 hw cfg patch?

Correct; thanks for pointing this out.

IMO, this value should be 0x4003ffff because the fields are actually
present in the register (even though the interfaces/pipes.etc. are not).
Anyway, these bits won't be accessed because the driver won't even allow
the usage of the corresponding resources.

I will update in the v2 of "drm/msm/mdp5: Add hardware configuration for
msm8x16".

Thanks,
Stephane.

>
> Thanks,
> Archit
>
> --
> Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum,
> a Linux Foundation Collaborative Project
> --
> To unsubscribe from this list: send the line "unsubscribe linux-arm-msm"
> in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
>
Archit Taneja March 24, 2015, 4:47 a.m. UTC | #3
On 03/24/2015 03:40 AM, "Stéphane Viau" wrote:
> Hi Archit,
>
>> Hi Stephane,
>>
>> On 03/14/2015 01:19 AM, Stephane Viau wrote:
>>> Some interfaces (WB, DSI Command Mode) need to be kicked off
>>> through a START Signal. This signal needs to be sent at the right
>>> time and requests in some cases to keep track of the pipeline
>>> status (eg: whether pipeline registers are flushed AND output WB
>>> buffers are ready, in case of WB interface).
>>>
>>> Signed-off-by: Stephane Viau <sviau@codeaurora.org>
>>> ---
>>>    drivers/gpu/drm/msm/mdp/mdp5/mdp5_cfg.c     |   2 +
>>>    drivers/gpu/drm/msm/mdp/mdp5/mdp5_cfg.h     |   7 +-
>>>    drivers/gpu/drm/msm/mdp/mdp5/mdp5_crtc.c    |  31 ++--
>>>    drivers/gpu/drm/msm/mdp/mdp5/mdp5_ctl.c     | 247
>>> ++++++++++++++++++++++++----
>>>    drivers/gpu/drm/msm/mdp/mdp5/mdp5_ctl.h     |  72 +++-----
>>>    drivers/gpu/drm/msm/mdp/mdp5/mdp5_encoder.c |  13 +-
>>>    drivers/gpu/drm/msm/mdp/mdp5/mdp5_kms.h     |   1 +
>>>    7 files changed, 276 insertions(+), 97 deletions(-)
>>>
>>> diff --git a/drivers/gpu/drm/msm/mdp/mdp5/mdp5_cfg.c
>>> b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_cfg.c
>>> index c078f30..72c075a 100644
>>> --- a/drivers/gpu/drm/msm/mdp/mdp5/mdp5_cfg.c
>>> +++ b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_cfg.c
>>> @@ -31,6 +31,7 @@ const struct mdp5_cfg_hw msm8x74_config = {
>>>    	.ctl = {
>>>    		.count = 5,
>>>    		.base = { 0x00600, 0x00700, 0x00800, 0x00900, 0x00a00 },
>>> +		.flush_hw_mask = 0x0003ffff,
>>>    	},
>>>    	.pipe_vig = {
>>>    		.count = 3,
>>> @@ -78,6 +79,7 @@ const struct mdp5_cfg_hw apq8084_config = {
>>>    	.ctl = {
>>>    		.count = 5,
>>>    		.base = { 0x00600, 0x00700, 0x00800, 0x00900, 0x00a00 },
>>> +		.flush_hw_mask = 0x003fffff,
>>
>> msm8x16 would require a flush_hw_mask too, it should be 0x32a59 if I'm
>> not wrong. Could you please add it for the next revision, or as a part
>> of the 8x16 hw cfg patch?
>
> Correct; thanks for pointing this out.
>
> IMO, this value should be 0x4003ffff because the fields are actually
> present in the register (even though the interfaces/pipes.etc. are not).
> Anyway, these bits won't be accessed because the driver won't even allow
> the usage of the corresponding resources.
>

Okay, that makes sense.


> I will update in the v2 of "drm/msm/mdp5: Add hardware configuration for
> msm8x16".
>
> Thanks,
> Stephane.
>

Thanks!
Archit
diff mbox

Patch

diff --git a/drivers/gpu/drm/msm/mdp/mdp5/mdp5_cfg.c b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_cfg.c
index c078f30..72c075a 100644
--- a/drivers/gpu/drm/msm/mdp/mdp5/mdp5_cfg.c
+++ b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_cfg.c
@@ -31,6 +31,7 @@  const struct mdp5_cfg_hw msm8x74_config = {
 	.ctl = {
 		.count = 5,
 		.base = { 0x00600, 0x00700, 0x00800, 0x00900, 0x00a00 },
+		.flush_hw_mask = 0x0003ffff,
 	},
 	.pipe_vig = {
 		.count = 3,
@@ -78,6 +79,7 @@  const struct mdp5_cfg_hw apq8084_config = {
 	.ctl = {
 		.count = 5,
 		.base = { 0x00600, 0x00700, 0x00800, 0x00900, 0x00a00 },
+		.flush_hw_mask = 0x003fffff,
 	},
 	.pipe_vig = {
 		.count = 4,
diff --git a/drivers/gpu/drm/msm/mdp/mdp5/mdp5_cfg.h b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_cfg.h
index 4e90740..69e35ac 100644
--- a/drivers/gpu/drm/msm/mdp/mdp5/mdp5_cfg.h
+++ b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_cfg.h
@@ -44,6 +44,11 @@  struct mdp5_lm_block {
 	uint32_t nb_stages;		/* number of stages per blender */
 };
 
+struct mdp5_ctl_block {
+	MDP5_SUB_BLOCK_DEFINITION;
+	uint32_t flush_hw_mask;		/* FLUSH register's hardware mask */
+};
+
 struct mdp5_smp_block {
 	int mmb_count;			/* number of SMP MMBs */
 	int mmb_size;			/* MMB: size in bytes */
@@ -55,7 +60,7 @@  struct mdp5_cfg_hw {
 	char  *name;
 
 	struct mdp5_smp_block smp;
-	struct mdp5_sub_block ctl;
+	struct mdp5_ctl_block ctl;
 	struct mdp5_sub_block pipe_vig;
 	struct mdp5_sub_block pipe_rgb;
 	struct mdp5_sub_block pipe_dma;
diff --git a/drivers/gpu/drm/msm/mdp/mdp5/mdp5_crtc.c b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_crtc.c
index 15136f1..9527ad1 100644
--- a/drivers/gpu/drm/msm/mdp/mdp5/mdp5_crtc.c
+++ b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_crtc.c
@@ -82,8 +82,6 @@  static void request_pending(struct drm_crtc *crtc, uint32_t pending)
 	mdp_irq_register(&get_kms(crtc)->base, &mdp5_crtc->vblank);
 }
 
-#define mdp5_lm_get_flush(lm)	mdp_ctl_flush_mask_lm(lm)
-
 static void crtc_flush(struct drm_crtc *crtc, u32 flush_mask)
 {
 	struct mdp5_crtc *mdp5_crtc = to_mdp5_crtc(crtc);
@@ -110,8 +108,8 @@  static void crtc_flush_all(struct drm_crtc *crtc)
 	drm_atomic_crtc_for_each_plane(plane, crtc) {
 		flush_mask |= mdp5_plane_get_flush(plane);
 	}
-	flush_mask |= mdp5_ctl_get_flush(mdp5_crtc->ctl);
-	flush_mask |= mdp5_lm_get_flush(mdp5_crtc->lm);
+
+	flush_mask |= mdp_ctl_flush_mask_lm(mdp5_crtc->lm);
 
 	crtc_flush(crtc, flush_mask);
 }
@@ -442,13 +440,14 @@  static int mdp5_crtc_cursor_set(struct drm_crtc *crtc,
 	struct mdp5_crtc *mdp5_crtc = to_mdp5_crtc(crtc);
 	struct drm_device *dev = crtc->dev;
 	struct mdp5_kms *mdp5_kms = get_kms(crtc);
-	struct drm_gem_object *cursor_bo, *old_bo;
+	struct drm_gem_object *cursor_bo, *old_bo = NULL;
 	uint32_t blendcfg, cursor_addr, stride;
 	int ret, bpp, lm;
 	unsigned int depth;
 	enum mdp5_cursor_alpha cur_alpha = CURSOR_ALPHA_PER_PIXEL;
 	uint32_t flush_mask = mdp_ctl_flush_mask_cursor(0);
 	uint32_t roi_w, roi_h;
+	bool cursor_enable = true;
 	unsigned long flags;
 
 	if ((width > CURSOR_WIDTH) || (height > CURSOR_HEIGHT)) {
@@ -461,7 +460,8 @@  static int mdp5_crtc_cursor_set(struct drm_crtc *crtc,
 
 	if (!handle) {
 		DBG("Cursor off");
-		return mdp5_ctl_set_cursor(mdp5_crtc->ctl, false);
+		cursor_enable = false;
+		goto set_cursor;
 	}
 
 	cursor_bo = drm_gem_object_lookup(dev, file, handle);
@@ -502,11 +502,14 @@  static int mdp5_crtc_cursor_set(struct drm_crtc *crtc,
 
 	spin_unlock_irqrestore(&mdp5_crtc->cursor.lock, flags);
 
-	ret = mdp5_ctl_set_cursor(mdp5_crtc->ctl, true);
-	if (ret)
+set_cursor:
+	ret = mdp5_ctl_set_cursor(mdp5_crtc->ctl, 0, cursor_enable);
+	if (ret) {
+		dev_err(dev->dev, "failed to %sable cursor: %d\n",
+				cursor_enable ? "en" : "dis", ret);
 		goto end;
+	}
 
-	flush_mask |= mdp5_ctl_get_flush(mdp5_crtc->ctl);
 	crtc_flush(crtc, flush_mask);
 
 end:
@@ -628,11 +631,13 @@  void mdp5_crtc_set_intf(struct drm_crtc *crtc, struct mdp5_interface *intf)
 int mdp5_crtc_get_lm(struct drm_crtc *crtc)
 {
 	struct mdp5_crtc *mdp5_crtc = to_mdp5_crtc(crtc);
+	return WARN_ON(!crtc) ? -EINVAL : mdp5_crtc->lm;
+}
 
-	if (WARN_ON(!crtc))
-		return -EINVAL;
-
-	return mdp5_crtc->lm;
+struct mdp5_ctl *mdp5_crtc_get_ctl(struct drm_crtc *crtc)
+{
+	struct mdp5_crtc *mdp5_crtc = to_mdp5_crtc(crtc);
+	return WARN_ON(!crtc) ? NULL : mdp5_crtc->ctl;
 }
 
 /* initialize crtc */
diff --git a/drivers/gpu/drm/msm/mdp/mdp5/mdp5_ctl.c b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_ctl.c
index a548113..7c0adf5 100644
--- a/drivers/gpu/drm/msm/mdp/mdp5/mdp5_ctl.c
+++ b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_ctl.c
@@ -1,5 +1,5 @@ 
 /*
- * Copyright (c) 2014 The Linux Foundation. All rights reserved.
+ * Copyright (c) 2014-2015 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
@@ -35,18 +35,16 @@ 
 
 struct op_mode {
 	struct mdp5_interface intf;
-	/*
-	 * TODO: add a state variable to control the pipeline
-	 *
-	 * eg: WB interface needs both buffer addresses to be committed +
-	 * output buffers ready to be written into, before we can START.
-	 */
+
+	bool encoder_enabled;
+	uint32_t start_mask;
 };
 
 struct mdp5_ctl {
 	struct mdp5_ctl_manager *ctlm;
 
 	u32 id;
+	int lm;
 
 	/* whether this CTL has been allocated or not: */
 	bool busy;
@@ -58,8 +56,8 @@  struct mdp5_ctl {
 	spinlock_t hw_lock;
 	u32 reg_offset;
 
-	/* flush mask used to commit CTL registers */
-	u32 flush_mask;
+	/* when do CTL registers need to be flushed? (mask of trigger bits) */
+	u32 pending_ctl_trigger;
 
 	bool cursor_on;
 
@@ -73,6 +71,9 @@  struct mdp5_ctl_manager {
 	u32 nlm;
 	u32 nctl;
 
+	/* to filter out non-present bits in the current hardware config */
+	u32 flush_hw_mask;
+
 	/* pool of CTLs + lock to protect resource allocation (ctls[i].busy) */
 	spinlock_t pool_lock;
 	struct mdp5_ctl ctls[MAX_CTL];
@@ -174,6 +175,9 @@  int mdp5_ctl_set_intf(struct mdp5_ctl *ctl, struct mdp5_interface *intf)
 
 	memcpy(&ctl->pipeline.intf, intf, sizeof(*intf));
 
+	ctl->pipeline.start_mask = mdp_ctl_flush_mask_lm(ctl->lm) |
+				   mdp_ctl_flush_mask_encoder(intf);
+
 	/* Virtual interfaces need not set a display intf (e.g.: Writeback) */
 	if (!mdp5_cfg_intf_is_virtual(intf->type))
 		set_display_intf(mdp5_kms, intf);
@@ -183,14 +187,90 @@  int mdp5_ctl_set_intf(struct mdp5_ctl *ctl, struct mdp5_interface *intf)
 	return 0;
 }
 
-int mdp5_ctl_set_cursor(struct mdp5_ctl *ctl, bool enable)
+static bool start_signal_needed(struct mdp5_ctl *ctl)
+{
+	struct op_mode *pipeline = &ctl->pipeline;
+
+	if (!pipeline->encoder_enabled || pipeline->start_mask != 0)
+		return false;
+
+	switch (pipeline->intf.type) {
+	case INTF_WB:
+		return true;
+	case INTF_DSI:
+		return pipeline->intf.mode == MDP5_INTF_DSI_MODE_COMMAND;
+	default:
+		return false;
+	}
+}
+
+/*
+ * send_start_signal() - Overlay Processor Start Signal
+ *
+ * For a given control operation (display pipeline), a START signal needs to be
+ * executed in order to kick off operation and activate all layers.
+ * e.g.: DSI command mode, Writeback
+ */
+static void send_start_signal(struct mdp5_ctl *ctl)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&ctl->hw_lock, flags);
+	ctl_write(ctl, REG_MDP5_CTL_START(ctl->id), 1);
+	spin_unlock_irqrestore(&ctl->hw_lock, flags);
+}
+
+static void refill_start_mask(struct mdp5_ctl *ctl)
+{
+	struct op_mode *pipeline = &ctl->pipeline;
+	struct mdp5_interface *intf = &ctl->pipeline.intf;
+
+	pipeline->start_mask = mdp_ctl_flush_mask_lm(ctl->lm);
+
+	/*
+	 * Writeback encoder needs to program & flush
+	 * address registers for each page flip..
+	 */
+	if (intf->type == INTF_WB)
+		pipeline->start_mask |= mdp_ctl_flush_mask_encoder(intf);
+}
+
+/**
+ * mdp5_ctl_set_encoder_state() - set the encoder state
+ *
+ * @enable: true, when encoder is ready for data streaming; false, otherwise.
+ *
+ * Note:
+ * This encoder state is needed to trigger START signal (data path kickoff).
+ */
+int mdp5_ctl_set_encoder_state(struct mdp5_ctl *ctl, bool enabled)
+{
+	if (WARN_ON(!ctl))
+		return -EINVAL;
+
+	ctl->pipeline.encoder_enabled = enabled;
+	DBG("intf_%d: %s", ctl->pipeline.intf.num, enabled ? "on" : "off");
+
+	if (start_signal_needed(ctl)) {
+		send_start_signal(ctl);
+		refill_start_mask(ctl);
+	}
+
+	return 0;
+}
+
+/*
+ * Note:
+ * CTL registers need to be flushed after calling this function
+ * (call mdp5_ctl_commit() with mdp_ctl_flush_mask_ctl() mask)
+ */
+int mdp5_ctl_set_cursor(struct mdp5_ctl *ctl, int cursor_id, bool enable)
 {
 	struct mdp5_ctl_manager *ctl_mgr = ctl->ctlm;
 	unsigned long flags;
 	u32 blend_cfg;
-	int lm;
+	int lm = ctl->lm;
 
-	lm = mdp5_crtc_get_lm(ctl->crtc);
 	if (unlikely(WARN_ON(lm < 0))) {
 		dev_err(ctl_mgr->dev->dev, "CTL %d cannot find LM: %d",
 				ctl->id, lm);
@@ -210,12 +290,12 @@  int mdp5_ctl_set_cursor(struct mdp5_ctl *ctl, bool enable)
 
 	spin_unlock_irqrestore(&ctl->hw_lock, flags);
 
+	ctl->pending_ctl_trigger = mdp_ctl_flush_mask_cursor(cursor_id);
 	ctl->cursor_on = enable;
 
 	return 0;
 }
 
-
 int mdp5_ctl_blend(struct mdp5_ctl *ctl, u32 lm, u32 blend_cfg)
 {
 	unsigned long flags;
@@ -229,37 +309,133 @@  int mdp5_ctl_blend(struct mdp5_ctl *ctl, u32 lm, u32 blend_cfg)
 	ctl_write(ctl, REG_MDP5_CTL_LAYER_REG(ctl->id, lm), blend_cfg);
 	spin_unlock_irqrestore(&ctl->hw_lock, flags);
 
+	ctl->pending_ctl_trigger = mdp_ctl_flush_mask_lm(lm);
+
 	return 0;
 }
 
+u32 mdp_ctl_flush_mask_encoder(struct mdp5_interface *intf)
+{
+	/* these are dummy bits for now, but will appear in next chipsets: */
+#define MDP5_CTL_FLUSH_TIMING_0		0x80000000
+#define MDP5_CTL_FLUSH_TIMING_1		0x40000000
+#define MDP5_CTL_FLUSH_TIMING_2		0x20000000
+#define MDP5_CTL_FLUSH_TIMING_3		0x10000000
+#define MDP5_CTL_FLUSH_WB		0x00010000
+
+	if (intf->type == INTF_WB)
+		return MDP5_CTL_FLUSH_WB;
+
+	switch (intf->num) {
+	case 0: return MDP5_CTL_FLUSH_TIMING_0;
+	case 1: return MDP5_CTL_FLUSH_TIMING_1;
+	case 2: return MDP5_CTL_FLUSH_TIMING_2;
+	case 3: return MDP5_CTL_FLUSH_TIMING_3;
+	default: return 0;
+	}
+}
+
+u32 mdp_ctl_flush_mask_cursor(int cursor_id)
+{
+	/* these are dummy bits for now, but will appear in next chipsets: */
+#define MDP5_CTL_FLUSH_CURSOR_0		0x00400000
+#define MDP5_CTL_FLUSH_CURSOR_1		0x00800000
+
+	switch (cursor_id) {
+	case 0: return MDP5_CTL_FLUSH_CURSOR_0;
+	case 1: return MDP5_CTL_FLUSH_CURSOR_1;
+	default: return 0;
+	}
+}
+
+u32 mdp_ctl_flush_mask_pipe(enum mdp5_pipe pipe)
+{
+	switch (pipe) {
+	case SSPP_VIG0: return MDP5_CTL_FLUSH_VIG0;
+	case SSPP_VIG1: return MDP5_CTL_FLUSH_VIG1;
+	case SSPP_VIG2: return MDP5_CTL_FLUSH_VIG2;
+	case SSPP_RGB0: return MDP5_CTL_FLUSH_RGB0;
+	case SSPP_RGB1: return MDP5_CTL_FLUSH_RGB1;
+	case SSPP_RGB2: return MDP5_CTL_FLUSH_RGB2;
+	case SSPP_DMA0: return MDP5_CTL_FLUSH_DMA0;
+	case SSPP_DMA1: return MDP5_CTL_FLUSH_DMA1;
+	case SSPP_VIG3: return MDP5_CTL_FLUSH_VIG3;
+	case SSPP_RGB3: return MDP5_CTL_FLUSH_RGB3;
+	default:        return 0;
+	}
+}
+
+u32 mdp_ctl_flush_mask_lm(int lm)
+{
+	switch (lm) {
+	case 0:  return MDP5_CTL_FLUSH_LM0;
+	case 1:  return MDP5_CTL_FLUSH_LM1;
+	case 2:  return MDP5_CTL_FLUSH_LM2;
+	case 5:  return MDP5_CTL_FLUSH_LM5;
+	default: return 0;
+	}
+}
+
+static u32 fix_sw_flush(struct mdp5_ctl *ctl, u32 flush_mask)
+{
+	struct mdp5_ctl_manager *ctl_mgr = ctl->ctlm;
+	u32 sw_mask = 0;
+#define BIT_NEEDS_SW_FIX(bit) \
+	(!(ctl_mgr->flush_hw_mask & bit) && (flush_mask & bit))
+
+	/* for some targets, cursor bit is the same as LM bit */
+	if (BIT_NEEDS_SW_FIX(MDP5_CTL_FLUSH_CURSOR_0))
+		sw_mask |= mdp_ctl_flush_mask_lm(ctl->lm);
+
+	return sw_mask;
+}
+
+/**
+ * mdp5_ctl_commit() - Register Flush
+ *
+ * The flush register is used to indicate several registers are all
+ * programmed, and are safe to update to the back copy of the double
+ * buffered registers.
+ *
+ * Some registers FLUSH bits are shared when the hardware does not have
+ * dedicated bits for them; handling these is the job of fix_sw_flush().
+ *
+ * CTL registers need to be flushed in some circumstances; if that is the
+ * case, some trigger bits will be present in both flush mask and
+ * ctl->pending_ctl_trigger.
+ */
 int mdp5_ctl_commit(struct mdp5_ctl *ctl, u32 flush_mask)
 {
 	struct mdp5_ctl_manager *ctl_mgr = ctl->ctlm;
+	struct op_mode *pipeline = &ctl->pipeline;
 	unsigned long flags;
 
-	if (flush_mask & MDP5_CTL_FLUSH_CURSOR_DUMMY) {
-		int lm = mdp5_crtc_get_lm(ctl->crtc);
+	pipeline->start_mask &= ~flush_mask;
 
-		if (unlikely(WARN_ON(lm < 0))) {
-			dev_err(ctl_mgr->dev->dev, "CTL %d cannot find LM: %d",
-					ctl->id, lm);
-			return -EINVAL;
-		}
+	VERB("flush_mask=%x, start_mask=%x, trigger=%x", flush_mask,
+			pipeline->start_mask, ctl->pending_ctl_trigger);
 
-		/* for current targets, cursor bit is the same as LM bit */
-		flush_mask |= mdp_ctl_flush_mask_lm(lm);
+	if (ctl->pending_ctl_trigger & flush_mask) {
+		flush_mask |= MDP5_CTL_FLUSH_CTL;
+		ctl->pending_ctl_trigger = 0;
 	}
 
-	spin_lock_irqsave(&ctl->hw_lock, flags);
-	ctl_write(ctl, REG_MDP5_CTL_FLUSH(ctl->id), flush_mask);
-	spin_unlock_irqrestore(&ctl->hw_lock, flags);
+	flush_mask |= fix_sw_flush(ctl, flush_mask);
 
-	return 0;
-}
+	flush_mask &= ctl_mgr->flush_hw_mask;
 
-u32 mdp5_ctl_get_flush(struct mdp5_ctl *ctl)
-{
-	return ctl->flush_mask;
+	if (flush_mask) {
+		spin_lock_irqsave(&ctl->hw_lock, flags);
+		ctl_write(ctl, REG_MDP5_CTL_FLUSH(ctl->id), flush_mask);
+		spin_unlock_irqrestore(&ctl->hw_lock, flags);
+	}
+
+	if (start_signal_needed(ctl)) {
+		send_start_signal(ctl);
+		refill_start_mask(ctl);
+	}
+
+	return 0;
 }
 
 void mdp5_ctl_release(struct mdp5_ctl *ctl)
@@ -280,6 +456,11 @@  void mdp5_ctl_release(struct mdp5_ctl *ctl)
 	DBG("CTL %d released", ctl->id);
 }
 
+int mdp5_ctl_get_ctl_id(struct mdp5_ctl *ctl)
+{
+	return WARN_ON(!ctl) ? -EINVAL : ctl->id;
+}
+
 /*
  * mdp5_ctl_request() - CTL dynamic allocation
  *
@@ -307,8 +488,10 @@  struct mdp5_ctl *mdp5_ctlm_request(struct mdp5_ctl_manager *ctl_mgr,
 
 	ctl = &ctl_mgr->ctls[c];
 
+	ctl->lm = mdp5_crtc_get_lm(crtc);
 	ctl->crtc = crtc;
 	ctl->busy = true;
+	ctl->pending_ctl_trigger = 0;
 	DBG("CTL %d allocated", ctl->id);
 
 unlock:
@@ -339,7 +522,7 @@  struct mdp5_ctl_manager *mdp5_ctlm_init(struct drm_device *dev,
 		void __iomem *mmio_base, const struct mdp5_cfg_hw *hw_cfg)
 {
 	struct mdp5_ctl_manager *ctl_mgr;
-	const struct mdp5_sub_block *ctl_cfg = &hw_cfg->ctl;
+	const struct mdp5_ctl_block *ctl_cfg = &hw_cfg->ctl;
 	unsigned long flags;
 	int c, ret;
 
@@ -361,6 +544,7 @@  struct mdp5_ctl_manager *mdp5_ctlm_init(struct drm_device *dev,
 	ctl_mgr->dev = dev;
 	ctl_mgr->nlm = hw_cfg->lm.count;
 	ctl_mgr->nctl = ctl_cfg->count;
+	ctl_mgr->flush_hw_mask = ctl_cfg->flush_hw_mask;
 	spin_lock_init(&ctl_mgr->pool_lock);
 
 	/* initialize each CTL of the pool: */
@@ -376,7 +560,6 @@  struct mdp5_ctl_manager *mdp5_ctlm_init(struct drm_device *dev,
 		ctl->ctlm = ctl_mgr;
 		ctl->id = c;
 		ctl->reg_offset = ctl_cfg->base[c];
-		ctl->flush_mask = MDP5_CTL_FLUSH_CTL;
 		ctl->busy = false;
 		spin_lock_init(&ctl->hw_lock);
 	}
diff --git a/drivers/gpu/drm/msm/mdp/mdp5/mdp5_ctl.h b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_ctl.h
index 271d5ac..7a62000 100644
--- a/drivers/gpu/drm/msm/mdp/mdp5/mdp5_ctl.h
+++ b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_ctl.h
@@ -33,20 +33,13 @@  void mdp5_ctlm_destroy(struct mdp5_ctl_manager *ctlm);
  * which is then used to call the other mdp5_ctl_*(ctl, ...) functions.
  */
 struct mdp5_ctl *mdp5_ctlm_request(struct mdp5_ctl_manager *ctlm, struct drm_crtc *crtc);
+int mdp5_ctl_get_ctl_id(struct mdp5_ctl *ctl);
 
 struct mdp5_interface;
 int mdp5_ctl_set_intf(struct mdp5_ctl *ctl, struct mdp5_interface *intf);
+int mdp5_ctl_set_encoder_state(struct mdp5_ctl *ctl, bool enabled);
 
-int mdp5_ctl_set_cursor(struct mdp5_ctl *ctl, bool enable);
-
-/* @blend_cfg: see LM blender config definition below */
-int mdp5_ctl_blend(struct mdp5_ctl *ctl, u32 lm, u32 blend_cfg);
-
-/* @flush_mask: see CTL flush masks definitions below */
-int mdp5_ctl_commit(struct mdp5_ctl *ctl, u32 flush_mask);
-u32 mdp5_ctl_get_flush(struct mdp5_ctl *ctl);
-
-void mdp5_ctl_release(struct mdp5_ctl *ctl);
+int mdp5_ctl_set_cursor(struct mdp5_ctl *ctl, int cursor_id, bool enable);
 
 /*
  * blend_cfg (LM blender config):
@@ -73,51 +66,32 @@  static inline u32 mdp_ctl_blend_mask(enum mdp5_pipe pipe,
 }
 
 /*
- * flush_mask (CTL flush masks):
+ * mdp5_ctl_blend() - Blend multiple layers on a Layer Mixer (LM)
+ *
+ * @blend_cfg: see LM blender config definition below
  *
- * The following functions allow each DRM entity to get and store
- * their own flush mask.
- * Once stored, these masks will then be accessed through each DRM's
- * interface and used by the caller of mdp5_ctl_commit() to specify
- * which block(s) need to be flushed through @flush_mask parameter.
+ * Note:
+ * CTL registers need to be flushed after calling this function
+ * (call mdp5_ctl_commit() with mdp_ctl_flush_mask_ctl() mask)
  */
+int mdp5_ctl_blend(struct mdp5_ctl *ctl, u32 lm, u32 blend_cfg);
 
-#define MDP5_CTL_FLUSH_CURSOR_DUMMY	0x80000000
+/**
+ * mdp_ctl_flush_mask...() - Register FLUSH masks
+ *
+ * These masks are used to specify which block(s) need to be flushed
+ * through @flush_mask parameter in mdp5_ctl_commit(.., flush_mask).
+ */
+u32 mdp_ctl_flush_mask_lm(int lm);
+u32 mdp_ctl_flush_mask_pipe(enum mdp5_pipe pipe);
+u32 mdp_ctl_flush_mask_cursor(int cursor_id);
+u32 mdp_ctl_flush_mask_encoder(struct mdp5_interface *intf);
 
-static inline u32 mdp_ctl_flush_mask_cursor(int cursor_id)
-{
-	/* TODO: use id once multiple cursor support is present */
-	(void)cursor_id;
+/* @flush_mask: see CTL flush masks definitions below */
+int mdp5_ctl_commit(struct mdp5_ctl *ctl, u32 flush_mask);
 
-	return MDP5_CTL_FLUSH_CURSOR_DUMMY;
-}
+void mdp5_ctl_release(struct mdp5_ctl *ctl);
 
-static inline u32 mdp_ctl_flush_mask_lm(int lm)
-{
-	switch (lm) {
-	case 0:  return MDP5_CTL_FLUSH_LM0;
-	case 1:  return MDP5_CTL_FLUSH_LM1;
-	case 2:  return MDP5_CTL_FLUSH_LM2;
-	case 5:  return MDP5_CTL_FLUSH_LM5;
-	default: return 0;
-	}
-}
 
-static inline u32 mdp_ctl_flush_mask_pipe(enum mdp5_pipe pipe)
-{
-	switch (pipe) {
-	case SSPP_VIG0: return MDP5_CTL_FLUSH_VIG0;
-	case SSPP_VIG1: return MDP5_CTL_FLUSH_VIG1;
-	case SSPP_VIG2: return MDP5_CTL_FLUSH_VIG2;
-	case SSPP_RGB0: return MDP5_CTL_FLUSH_RGB0;
-	case SSPP_RGB1: return MDP5_CTL_FLUSH_RGB1;
-	case SSPP_RGB2: return MDP5_CTL_FLUSH_RGB2;
-	case SSPP_DMA0: return MDP5_CTL_FLUSH_DMA0;
-	case SSPP_DMA1: return MDP5_CTL_FLUSH_DMA1;
-	case SSPP_VIG3: return MDP5_CTL_FLUSH_VIG3;
-	case SSPP_RGB3: return MDP5_CTL_FLUSH_RGB3;
-	default:        return 0;
-	}
-}
 
 #endif /* __MDP5_CTL_H__ */
diff --git a/drivers/gpu/drm/msm/mdp/mdp5/mdp5_encoder.c b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_encoder.c
index f3b6fe8..a17eb9c 100644
--- a/drivers/gpu/drm/msm/mdp/mdp5/mdp5_encoder.c
+++ b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_encoder.c
@@ -217,12 +217,15 @@  static void mdp5_encoder_mode_set(struct drm_encoder *encoder,
 	mdp5_write(mdp5_kms, REG_MDP5_INTF_FRAME_LINE_COUNT_EN(intf), 0x3);  /* frame+line? */
 
 	spin_unlock_irqrestore(&mdp5_encoder->intf_lock, flags);
+
+	mdp5_crtc_set_intf(encoder->crtc, &mdp5_encoder->intf);
 }
 
 static void mdp5_encoder_disable(struct drm_encoder *encoder)
 {
 	struct mdp5_encoder *mdp5_encoder = to_mdp5_encoder(encoder);
 	struct mdp5_kms *mdp5_kms = get_kms(encoder);
+	struct mdp5_ctl *ctl = mdp5_crtc_get_ctl(encoder->crtc);
 	int lm = mdp5_crtc_get_lm(encoder->crtc);
 	struct mdp5_interface *intf = &mdp5_encoder->intf;
 	int intfn = mdp5_encoder->intf.num;
@@ -231,9 +234,12 @@  static void mdp5_encoder_disable(struct drm_encoder *encoder)
 	if (WARN_ON(!mdp5_encoder->enabled))
 		return;
 
+	mdp5_ctl_set_encoder_state(ctl, false);
+
 	spin_lock_irqsave(&mdp5_encoder->intf_lock, flags);
 	mdp5_write(mdp5_kms, REG_MDP5_INTF_TIMING_ENGINE_EN(intfn), 0);
 	spin_unlock_irqrestore(&mdp5_encoder->intf_lock, flags);
+	mdp5_ctl_commit(ctl, mdp_ctl_flush_mask_encoder(intf));
 
 	/*
 	 * Wait for a vsync so we know the ENABLE=0 latched before
@@ -254,18 +260,21 @@  static void mdp5_encoder_enable(struct drm_encoder *encoder)
 {
 	struct mdp5_encoder *mdp5_encoder = to_mdp5_encoder(encoder);
 	struct mdp5_kms *mdp5_kms = get_kms(encoder);
+	struct mdp5_ctl *ctl = mdp5_crtc_get_ctl(encoder->crtc);
+	struct mdp5_interface *intf = &mdp5_encoder->intf;
 	int intfn = mdp5_encoder->intf.num;
 	unsigned long flags;
 
 	if (WARN_ON(mdp5_encoder->enabled))
 		return;
 
-	mdp5_crtc_set_intf(encoder->crtc, &mdp5_encoder->intf);
-
 	bs_set(mdp5_encoder, 1);
 	spin_lock_irqsave(&mdp5_encoder->intf_lock, flags);
 	mdp5_write(mdp5_kms, REG_MDP5_INTF_TIMING_ENGINE_EN(intfn), 1);
 	spin_unlock_irqrestore(&mdp5_encoder->intf_lock, flags);
+	mdp5_ctl_commit(ctl, mdp_ctl_flush_mask_encoder(intf));
+
+	mdp5_ctl_set_encoder_state(ctl, true);
 
 	mdp5_encoder->enabled = true;
 }
diff --git a/drivers/gpu/drm/msm/mdp/mdp5/mdp5_kms.h b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_kms.h
index 1a6aa49..690edfd 100644
--- a/drivers/gpu/drm/msm/mdp/mdp5/mdp5_kms.h
+++ b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_kms.h
@@ -230,6 +230,7 @@  struct drm_plane *mdp5_plane_init(struct drm_device *dev,
 uint32_t mdp5_crtc_vblank(struct drm_crtc *crtc);
 
 int mdp5_crtc_get_lm(struct drm_crtc *crtc);
+struct mdp5_ctl *mdp5_crtc_get_ctl(struct drm_crtc *crtc);
 void mdp5_crtc_cancel_pending_flip(struct drm_crtc *crtc, struct drm_file *file);
 void mdp5_crtc_set_intf(struct drm_crtc *crtc, struct mdp5_interface *intf);
 struct drm_crtc *mdp5_crtc_init(struct drm_device *dev,