text: /* Copyright (c) 2012-2016, The Linux Foundation. All rights reserved.
   *
   * This program is free software; you can redistribute it and/or modify
   * it under the terms of the GNU General Public License version 2 and
   * only version 2 as published by the Free Software Foundation.
   *
   * This program is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   * GNU General Public License for more details.
   *
   */
  
  #define pr_fmt(fmt)	"%s: " fmt, __func__
  
  #include <linux/iopoll.h>
  #include <linux/delay.h>
  #include <linux/dma-mapping.h>
  #include <linux/memblock.h>
  
  #include "mdss_fb.h"
  #include "mdss_mdp.h"
  #include "mdss_panel.h"
  #include "mdss_debug.h"
  #include "mdss_mdp_trace.h"
  #if defined(CONFIG_FB_MSM_MDSS_SAMSUNG)
  #include "samsung/ss_dsi_panel_common.h"
  #endif
  
  /* wait for at least 2 vsyncs for lowest refresh rate (24hz) */
  #define VSYNC_TIMEOUT_US 100000
  
  /* Poll time to do recovery during active region */
  #define POLL_TIME_USEC_FOR_LN_CNT 500
  
  #define MDP_INTR_MASK_INTF_VSYNC(intf_num) \
  	(1 << (2 * (intf_num - MDSS_MDP_INTF0) + MDSS_MDP_IRQ_INTF_VSYNC))
  
  /* intf timing settings */
  struct intf_timing_params {
  	u32 width;
  	u32 height;
  	u32 xres;
  	u32 yres;
  
  	u32 h_back_porch;
  	u32 h_front_porch;
  	u32 v_back_porch;
  	u32 v_front_porch;
  	u32 hsync_pulse_width;
  	u32 vsync_pulse_width;
  
  	u32 border_clr;
  	u32 underflow_clr;
  	u32 hsync_skew;
  };
  
  struct mdss_mdp_video_ctx {
  	u32 intf_num;
  	char __iomem *base;
  	u32 intf_type;
  	u8 ref_cnt;
  
  	u8 timegen_en;
  	bool polling_en;
  	u32 poll_cnt;
  	struct completion vsync_comp;
  	struct completion pp_comp;
  	int wait_pending;
  
  	u32 default_fps;
  	u32 saved_vtotal;
  	u32 saved_vfporch;
  
  	atomic_t vsync_ref;
  	spinlock_t vsync_lock;
  	spinlock_t dfps_lock;
  	struct mutex vsync_mtx;
  	struct list_head vsync_handlers;
  	struct mdss_intf_recovery intf_recovery;
  };
  
  static void mdss_mdp_fetch_start_config(struct mdss_mdp_video_ctx *ctx,
  		struct mdss_mdp_ctl *ctl);
  
  static inline void mdp_video_write(struct mdss_mdp_video_ctx *ctx,
  				   u32 reg, u32 val)
  {
  	writel_relaxed(val, ctx->base + reg);
  }
  
  static inline u32 mdp_video_read(struct mdss_mdp_video_ctx *ctx,
  				   u32 reg)
  {
  	return readl_relaxed(ctx->base + reg);
  }
  
  static inline u32 mdss_mdp_video_line_count(struct mdss_mdp_ctl *ctl)
  {
  	struct mdss_mdp_video_ctx *ctx;
  	u32 line_cnt = 0;
  	if (!ctl || !ctl->priv_data)
  		goto line_count_exit;
  	ctx = ctl->priv_data;
  	line_cnt = mdp_video_read(ctx, MDSS_MDP_REG_INTF_LINE_COUNT);
  line_count_exit:
  	return line_cnt;
  }
  
  int mdss_mdp_video_addr_setup(struct mdss_data_type *mdata,
  				u32 *offsets,  u32 count)
  {
  	struct mdss_mdp_video_ctx *head;
  	u32 i;
  
  	head = devm_kzalloc(&mdata->pdev->dev,
  			sizeof(struct mdss_mdp_video_ctx) * count, GFP_KERNEL);
  	if (!head)
  		return -ENOMEM;
  
  	for (i = 0; i < count; i++) {
  		head[i].base = mdata->mdss_io.base + offsets[i];
  		pr_debug("adding Video Intf #%d offset=0x%x virt=%pK\n", i,
  				offsets[i], head[i].base);
  		head[i].ref_cnt = 0;
  		head[i].intf_num = i + MDSS_MDP_INTF0;
  		INIT_LIST_HEAD(&head[i].vsync_handlers);
  	}
  
  	mdata->video_intf = head;
  	mdata->nintf = count;
  	return 0;
  }
  
  static void mdss_mdp_video_intf_recovery(void *data, int event)
  {
  	struct mdss_mdp_video_ctx *ctx;
  	struct mdss_mdp_ctl *ctl = data;
  	struct mdss_panel_info *pinfo;
  	u32 line_cnt, min_ln_cnt, active_lns_cnt;
  	u32 clk_rate, clk_period, time_of_line;
  	u32 delay;
  
  	if (!data) {
  		pr_err("%s: invalid ctl\n", __func__);
  		return;
  	}
  
  	/*
  	 * Currently, only intf_fifo_overflow is
  	 * supported for recovery sequence for video
  	 * mode DSI interface
  	 */
  	if (event != MDP_INTF_DSI_VIDEO_FIFO_OVERFLOW) {
  		pr_warn("%s: unsupported recovery event:%d\n",
  					__func__, event);
  		return;
  	}
  
  	ctx = ctl->priv_data;
  	pr_debug("%s: ctl num = %d, event = %d\n",
  				__func__, ctl->num, event);
  
  	pinfo = &ctl->panel_data->panel_info;
  	clk_rate = ((ctl->intf_type == MDSS_INTF_DSI) ?
  			pinfo->mipi.dsi_pclk_rate :
  			pinfo->clk_rate);
  
  	clk_rate /= 1000;	/* in kHz */
  	if (!clk_rate) {
  		pr_err("Unable to get proper clk_rate\n");
  		return;
  	}
  	/*
  	 * calculate clk_period as pico second to maintain good
  	 * accuracy with high pclk rate and this number is in 17 bit
  	 * range.
  	 */
  	clk_period = 1000000000 / clk_rate;
  	if (!clk_period) {
  		pr_err("Unable to calculate clock period\n");
  		return;
  	}
  	min_ln_cnt = pinfo->lcdc.v_back_porch + pinfo->lcdc.v_pulse_width;
  	active_lns_cnt = pinfo->yres;
  	time_of_line = (pinfo->lcdc.h_back_porch +
  		 pinfo->lcdc.h_front_porch +
  		 pinfo->lcdc.h_pulse_width +
  		 pinfo->xres) * clk_period;
  
  	/* delay in micro seconds */
  	delay = (time_of_line * (min_ln_cnt +
  			pinfo->lcdc.v_front_porch)) / 1000000;
  
  	/*
  	 * Wait for max delay before
  	 * polling to check active region
  	 */
  	if (delay > POLL_TIME_USEC_FOR_LN_CNT)
  		delay = POLL_TIME_USEC_FOR_LN_CNT;
  
  	mutex_lock(&ctl->offlock);
  	while (1) {
  		if (!ctl || ctl->mfd->shutdown_pending || !ctx ||
  				!ctx->timegen_en) {
  			pr_warn("Target is in suspend or shutdown pending\n");
  			mutex_unlock(&ctl->offlock);
  			return;
  		}
  
  		line_cnt = mdss_mdp_video_line_count(ctl);
  
  		if ((line_cnt >= min_ln_cnt) && (line_cnt <
  			(active_lns_cnt + min_ln_cnt))) {
  			pr_debug("%s, Needed lines left line_cnt=%d\n",
  						__func__, line_cnt);
  			mutex_unlock(&ctl->offlock);
  			return;
  		} else {
  			pr_warn_once("line count is less. line_cnt = %d\n",
  								line_cnt);
  			/* Add delay so that line count is in active region */
  			udelay(delay);
  		}
  	}
  }
  
  static int mdss_mdp_video_timegen_setup(struct mdss_mdp_ctl *ctl,
  					struct intf_timing_params *p)
  {
  	u32 hsync_period, vsync_period;
  	u32 hsync_start_x, hsync_end_x, display_v_start, display_v_end;
  	u32 active_h_start, active_h_end, active_v_start, active_v_end;
  	u32 den_polarity, hsync_polarity, vsync_polarity;
  	u32 display_hctl, active_hctl, hsync_ctl, polarity_ctl;
  	struct mdss_mdp_video_ctx *ctx;
  	struct mdss_data_type *mdata;
  
  	ctx = ctl->priv_data;
  	mdata = ctl->mdata;
  	hsync_period = p->hsync_pulse_width + p->h_back_porch +
  			p->width + p->h_front_porch;
  	vsync_period = p->vsync_pulse_width + p->v_back_porch +
  			p->height + p->v_front_porch;
  
  	display_v_start = ((p->vsync_pulse_width + p->v_back_porch) *
  			hsync_period) + p->hsync_skew;
  	display_v_end = ((vsync_period - p->v_front_porch) * hsync_period) +
  			p->hsync_skew - 1;
  
  	if (ctx->intf_type == MDSS_INTF_EDP) {
  		display_v_start += p->hsync_pulse_width + p->h_back_porch;
  		display_v_end -= p->h_front_porch;
  	}
  
  	/* TIMING_2 flush bit on 8939 is BIT 31 */
  	if (mdata->mdp_rev == MDSS_MDP_HW_REV_108 &&
  				ctx->intf_num == MDSS_MDP_INTF2)
  		ctl->flush_bits |= BIT(31);
  	else
  		ctl->flush_bits |= BIT(31) >>
  			(ctx->intf_num - MDSS_MDP_INTF0);
  
  	hsync_start_x = p->h_back_porch + p->hsync_pulse_width;
  	hsync_end_x = hsync_period - p->h_front_porch - 1;
  
  	if (p->width != p->xres) {
  		active_h_start = hsync_start_x;
  		active_h_end = active_h_start + p->xres - 1;
  	} else {
  		active_h_start = 0;
  		active_h_end = 0;
  	}
  
  	if (p->height != p->yres) {
  		active_v_start = display_v_start;
  		active_v_end = active_v_start + (p->yres * hsync_period) - 1;
  	} else {
  		active_v_start = 0;
  		active_v_end = 0;
  	}
  
  
  	if (active_h_end) {
  		active_hctl = (active_h_end << 16) | active_h_start;
  		active_hctl |= BIT(31);	/* ACTIVE_H_ENABLE */
  	} else {
  		active_hctl = 0;
  	}
  
  	if (active_v_end)
  		active_v_start |= BIT(31); /* ACTIVE_V_ENABLE */
  
  	hsync_ctl = (hsync_period << 16) | p->hsync_pulse_width;
  	display_hctl = (hsync_end_x << 16) | hsync_start_x;
  
  	den_polarity = 0;
  	if (MDSS_INTF_HDMI == ctx->intf_type) {
  		hsync_polarity = p->yres >= 720 ? 0 : 1;
  		vsync_polarity = p->yres >= 720 ? 0 : 1;
  	} else {
  		hsync_polarity = 0;
  		vsync_polarity = 0;
  	}
  	polarity_ctl = (den_polarity << 2)   | /*  DEN Polarity  */
  		       (vsync_polarity << 1) | /* VSYNC Polarity */
  		       (hsync_polarity << 0);  /* HSYNC Polarity */
  
  	mdp_video_write(ctx, MDSS_MDP_REG_INTF_HSYNC_CTL, hsync_ctl);
  	mdp_video_write(ctx, MDSS_MDP_REG_INTF_VSYNC_PERIOD_F0,
  			vsync_period * hsync_period);
  	mdp_video_write(ctx, MDSS_MDP_REG_INTF_VSYNC_PULSE_WIDTH_F0,
  			   p->vsync_pulse_width * hsync_period);
  	mdp_video_write(ctx, MDSS_MDP_REG_INTF_DISPLAY_HCTL, display_hctl);
  	mdp_video_write(ctx, MDSS_MDP_REG_INTF_DISPLAY_V_START_F0,
  			   display_v_start);
  	mdp_video_write(ctx, MDSS_MDP_REG_INTF_DISPLAY_V_END_F0, display_v_end);
  	mdp_video_write(ctx, MDSS_MDP_REG_INTF_ACTIVE_HCTL, active_hctl);
  	mdp_video_write(ctx, MDSS_MDP_REG_INTF_ACTIVE_V_START_F0,
  			   active_v_start);
  	mdp_video_write(ctx, MDSS_MDP_REG_INTF_ACTIVE_V_END_F0, active_v_end);
  
  	mdp_video_write(ctx, MDSS_MDP_REG_INTF_BORDER_COLOR, p->border_clr);
  	mdp_video_write(ctx, MDSS_MDP_REG_INTF_UNDERFLOW_COLOR,
  			   p->underflow_clr);
  	mdp_video_write(ctx, MDSS_MDP_REG_INTF_HSYNC_SKEW, p->hsync_skew);
  	mdp_video_write(ctx, MDSS_MDP_REG_INTF_POLARITY_CTL, polarity_ctl);
  	mdp_video_write(ctx, MDSS_MDP_REG_INTF_FRAME_LINE_COUNT_EN, 0x3);
  
  	return 0;
  }
  
  
  static inline void video_vsync_irq_enable(struct mdss_mdp_ctl *ctl, bool clear)
  {
  	struct mdss_mdp_video_ctx *ctx = ctl->priv_data;
  
  	mutex_lock(&ctx->vsync_mtx);
  	if (atomic_inc_return(&ctx->vsync_ref) == 1)
  		mdss_mdp_irq_enable(MDSS_MDP_IRQ_INTF_VSYNC, ctl->intf_num);
  	else if (clear)
  		mdss_mdp_irq_clear(ctl->mdata, MDSS_MDP_IRQ_INTF_VSYNC,
  				ctl->intf_num);
  	mutex_unlock(&ctx->vsync_mtx);
  }
  
  static inline void video_vsync_irq_disable(struct mdss_mdp_ctl *ctl)
  {
  	struct mdss_mdp_video_ctx *ctx = ctl->priv_data;
  
  	mutex_lock(&ctx->vsync_mtx);
  	if (atomic_dec_return(&ctx->vsync_ref) == 0)
  		mdss_mdp_irq_disable(MDSS_MDP_IRQ_INTF_VSYNC, ctl->intf_num);
  	mutex_unlock(&ctx->vsync_mtx);
  }
  
  static int mdss_mdp_video_add_vsync_handler(struct mdss_mdp_ctl *ctl,
  		struct mdss_mdp_vsync_handler *handle)
  {
  	struct mdss_mdp_video_ctx *ctx;
  	unsigned long flags;
  	int ret = 0;
  	bool irq_en = false;
  
  	if (!handle || !(handle->vsync_handler)) {
  		ret = -EINVAL;
  		goto exit;
  	}
  
  	ctx = (struct mdss_mdp_video_ctx *) ctl->priv_data;
  	if (!ctx) {
  		pr_err("invalid ctx for ctl=%d\n", ctl->num);
  		ret = -ENODEV;
  		goto exit;
  	}
  
  	MDSS_XLOG(ctl->num, ctl->vsync_cnt, handle->enabled);
  
  	spin_lock_irqsave(&ctx->vsync_lock, flags);
  	if (!handle->enabled) {
  		handle->enabled = true;
  		list_add(&handle->list, &ctx->vsync_handlers);
  		irq_en = true;
  	}
  	spin_unlock_irqrestore(&ctx->vsync_lock, flags);
  	if (irq_en)
  		video_vsync_irq_enable(ctl, false);
  exit:
  	return ret;
  }
  
  static int mdss_mdp_video_remove_vsync_handler(struct mdss_mdp_ctl *ctl,
  		struct mdss_mdp_vsync_handler *handle)
  {
  	struct mdss_mdp_video_ctx *ctx;
  	unsigned long flags;
  	bool irq_dis = false;
  
  	ctx = (struct mdss_mdp_video_ctx *) ctl->priv_data;
  	if (!ctx) {
  		pr_err("invalid ctx for ctl=%d\n", ctl->num);
  		return -ENODEV;
  	}
  
  	MDSS_XLOG(ctl->num, ctl->vsync_cnt, handle->enabled);
  
  	spin_lock_irqsave(&ctx->vsync_lock, flags);
  	if (handle->enabled) {
  		handle->enabled = false;
  		list_del_init(&handle->list);
  		irq_dis = true;
  	}
  	spin_unlock_irqrestore(&ctx->vsync_lock, flags);
  	if (irq_dis)
  		video_vsync_irq_disable(ctl);
  	return 0;
  }
  
  static int mdss_mdp_video_intfs_stop(struct mdss_mdp_ctl *ctl,
  	struct mdss_panel_data *pdata, int inum)
  {
  	struct mdss_data_type *mdata;
  	struct mdss_panel_info *pinfo;
  	struct mdss_mdp_video_ctx *ctx;
  	struct mdss_mdp_vsync_handler *tmp, *handle;
  	struct mdss_mdp_ctl *sctl;
  	int rc = 0;
  	u32 frame_rate = 0;
  	int ret = 0;
  
  	if (pdata == NULL)
  		return 0;
  
  	if (is_split_dst(ctl->mfd)) {
  		ret = mdss_mdp_video_intfs_stop(ctl, pdata->next, (inum + 1));
  		if (IS_ERR_VALUE(ret))
  			return ret;
  	}
  
  	mdata = ctl->mdata;
  	pinfo = &pdata->panel_info;
  
  	if (inum < mdata->nintf) {
  		ctx = ((struct mdss_mdp_video_ctx *) mdata->video_intf) + inum;
  		if (!ctx->ref_cnt) {
  			pr_err("Intf %d not in use\n", (inum + MDSS_MDP_INTF0));
  			return -ENODEV;
  		}
  		pr_debug("stop ctl=%d video Intf #%d base=%pK", ctl->num,
  			ctx->intf_num, ctx->base);
  	} else {
  		pr_err("Invalid intf number: %d\n", (inum + MDSS_MDP_INTF0));
  		return -EINVAL;
  	}
  
  	mutex_lock(&ctl->offlock);
  	if (ctx->timegen_en) {
  		rc = mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_BLANK, NULL);
  		if (rc == -EBUSY) {
  			pr_debug("intf #%d busy don't turn off\n",
  				 ctl->intf_num);
  			goto end;
  		}
  		WARN(rc, "intf %d blank error (%d)\n", ctl->intf_num, rc);
  
  		mdp_video_write(ctx, MDSS_MDP_REG_INTF_TIMING_ENGINE_EN, 0);
  		/* wait for at least one VSYNC for proper TG OFF */
  		frame_rate = mdss_panel_get_framerate
  				(&(ctl->panel_data->panel_info));
  		if (!(frame_rate >= 24 && frame_rate <= 240))
  			frame_rate = 24;
  		msleep((1000/frame_rate) + 1);
  
  		mdss_iommu_ctrl(0);
  		mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF);
  		ctx->timegen_en = false;
  
  		rc = mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_PANEL_OFF, NULL);
  		WARN(rc, "intf %d timegen off error (%d)\n", ctl->intf_num, rc);
  
  		mdss_mdp_irq_disable(MDSS_MDP_IRQ_INTF_UNDER_RUN,
  			ctl->intf_num);
  		sctl = mdss_mdp_get_split_ctl(ctl);
  		if (sctl)
  			mdss_mdp_irq_disable(MDSS_MDP_IRQ_INTF_UNDER_RUN,
  				sctl->intf_num);
  
  		mdss_bus_bandwidth_ctrl(false);
  	}
  
  	list_for_each_entry_safe(handle, tmp, &ctx->vsync_handlers, list)
  		mdss_mdp_video_remove_vsync_handler(ctl, handle);
  
  	mdss_mdp_set_intr_callback(MDSS_MDP_IRQ_INTF_VSYNC,
  		(inum + MDSS_MDP_INTF0), NULL, NULL);
  	mdss_mdp_set_intr_callback(MDSS_MDP_IRQ_INTF_UNDER_RUN,
  		(inum + MDSS_MDP_INTF0), NULL, NULL);
  	if(ctl->intf_type == MDSS_INTF_DSI)
  		mdss_mdp_set_intr_callback(MDSS_MDP_IRQ_PING_PONG_COMP,
  			ctl->mixer_left->num, NULL, NULL);
  
  	ctx->ref_cnt--;
  end:
  	mutex_unlock(&ctl->offlock);
  	return rc;
  }
  
  
  static int mdss_mdp_video_stop(struct mdss_mdp_ctl *ctl, int panel_power_state)
  {
  	int intfs_num, ret = 0;
  
  	intfs_num = ctl->intf_num - MDSS_MDP_INTF0;
  	ret = mdss_mdp_video_intfs_stop(ctl, ctl->panel_data, intfs_num);
  	if (IS_ERR_VALUE(ret)) {
  		pr_err("unable to stop video interface: %d\n", ret);
  		return ret;
  	}
  
  	MDSS_XLOG(ctl->num, ctl->vsync_cnt);
  
  	mdss_mdp_ctl_reset(ctl);
  	ctl->priv_data = NULL;
  
  	return 0;
  }
  
  static void mdss_mdp_video_vsync_intr_done(void *arg)
  {
  	struct mdss_mdp_ctl *ctl = arg;
  	struct mdss_mdp_video_ctx *ctx = ctl->priv_data;
  	struct mdss_mdp_vsync_handler *tmp;
  	ktime_t vsync_time;
  
  	if (!ctx) {
  		pr_err("invalid ctx\n");
  		return;
  	}
  
  	vsync_time = ktime_get();
  	ctl->vsync_cnt++;
  
  	MDSS_XLOG(ctl->num, ctl->vsync_cnt, ctl->vsync_cnt);
  
  	pr_debug("intr ctl=%d vsync cnt=%u vsync_time=%d\n",
  		 ctl->num, ctl->vsync_cnt, (int)ktime_to_ms(vsync_time));
  
  	ctx->polling_en = false;
  	complete_all(&ctx->vsync_comp);
  	spin_lock(&ctx->vsync_lock);
  	list_for_each_entry(tmp, &ctx->vsync_handlers, list) {
  		tmp->vsync_handler(ctl, vsync_time);
  	}
  	spin_unlock(&ctx->vsync_lock);
  }
  
  static int mdss_mdp_video_pollwait(struct mdss_mdp_ctl *ctl)
  {
  	struct mdss_mdp_video_ctx *ctx = ctl->priv_data;
  	u32 mask, status;
  	int rc;
  
  	mask = MDP_INTR_MASK_INTF_VSYNC(ctl->intf_num);
  
  	mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON);
  	rc = readl_poll_timeout(ctl->mdata->mdp_base + MDSS_MDP_REG_INTR_STATUS,
  		status,
  		(status & mask) || try_wait_for_completion(&ctx->vsync_comp),
  		1000,
  		VSYNC_TIMEOUT_US);
  	mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF);
  
  	if (rc == 0) {
  		MDSS_XLOG(ctl->num, ctl->vsync_cnt);
  		pr_debug("vsync poll successful! rc=%d status=0x%x\n",
  				rc, status);
  		ctx->poll_cnt++;
  		if (status) {
  			struct mdss_mdp_vsync_handler *tmp;
  			unsigned long flags;
  			ktime_t vsync_time = ktime_get();
  
  			spin_lock_irqsave(&ctx->vsync_lock, flags);
  			list_for_each_entry(tmp, &ctx->vsync_handlers, list)
  				tmp->vsync_handler(ctl, vsync_time);
  			spin_unlock_irqrestore(&ctx->vsync_lock, flags);
  		}
  	} else {
  		pr_warn("vsync poll timed out! rc=%d status=0x%x mask=0x%x\n",
  				rc, status, mask);
  	}
  
  	return rc;
  }
  
  static int mdss_mdp_video_wait4comp(struct mdss_mdp_ctl *ctl, void *arg)
  {
  	struct mdss_mdp_video_ctx *ctx;
  	int rc;
  
  	ctx = (struct mdss_mdp_video_ctx *) ctl->priv_data;
  	if (!ctx) {
  		pr_err("invalid ctx\n");
  		return -ENODEV;
  	}
  
  	WARN(!ctx->wait_pending, "waiting without commit! ctl=%d", ctl->num);
  
  	if (ctx->polling_en) {
  		rc = mdss_mdp_video_pollwait(ctl);
  	} else {
  		mutex_unlock(&ctl->lock);
  		rc = wait_for_completion_timeout(&ctx->vsync_comp,
  				usecs_to_jiffies(VSYNC_TIMEOUT_US));
  		mutex_lock(&ctl->lock);
  		if (rc == 0) {
  			pr_warn("vsync wait timeout %d, fallback to poll mode\n",
  					ctl->num);
  			ctx->polling_en++;
  			rc = mdss_mdp_video_pollwait(ctl);
  		} else {
  			rc = 0;
  		}
  	}
  	mdss_mdp_ctl_notify(ctl,
  			rc ? MDP_NOTIFY_FRAME_TIMEOUT : MDP_NOTIFY_FRAME_DONE);
  
  	if (ctx->wait_pending) {
  		ctx->wait_pending = 0;
  		video_vsync_irq_disable(ctl);
  	}
  
  	return rc;
  }
  
  static void recover_underrun_work(struct work_struct *work)
  {
  	struct mdss_mdp_ctl *ctl =
  		container_of(work, typeof(*ctl), recover_work);
  
  	if (!ctl || !ctl->add_vsync_handler) {
  		pr_err("ctl or vsync handler is NULL\n");
  		return;
  	}
  
  	mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON);
  	ctl->add_vsync_handler(ctl, &ctl->recover_underrun_handler);
  	mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF);
  }
  
  static void mdss_mdp_video_underrun_intr_done(void *arg)
  {
  	struct mdss_mdp_ctl *ctl = arg;
  	if (unlikely(!ctl))
  		return;
  
  	ctl->underrun_cnt++;
  	MDSS_XLOG(ctl->num, ctl->underrun_cnt);
  	MDSS_XLOG_TOUT_HANDLER("mdp", "dsi0", "dsi1", "edp", "hdmi", "panic");
  	trace_mdp_video_underrun_done(ctl->num, ctl->underrun_cnt);
  	pr_info("display underrun detected for ctl=%d count=%d\n", ctl->num,
  			ctl->underrun_cnt);
  
  	if (ctl->opmode & MDSS_MDP_CTL_OP_PACK_3D_ENABLE)
  		schedule_work(&ctl->recover_work);
  }
  
  static int mdss_mdp_video_vfp_fps_update(struct mdss_mdp_video_ctx *ctx,
  				 struct mdss_panel_data *pdata, int new_fps)
  {
  	u32 add_v_lines = 0;
  	u32 current_vsync_period_f0, new_vsync_period_f0;
  	u32 vsync_period, hsync_period;
  	int diff;
  
  	vsync_period = mdss_panel_get_vtotal(&pdata->panel_info);
  	hsync_period = mdss_panel_get_htotal(&pdata->panel_info, true);
  
  	if (!ctx->default_fps) {
  		ctx->default_fps = mdss_panel_get_framerate(&pdata->panel_info);
  		ctx->saved_vtotal = vsync_period;
  		ctx->saved_vfporch = pdata->panel_info.lcdc.v_front_porch;
  	}
  
  	diff = ctx->default_fps - new_fps;
  	add_v_lines = mult_frac(ctx->saved_vtotal, diff, new_fps);
  	pdata->panel_info.lcdc.v_front_porch = ctx->saved_vfporch +
  			add_v_lines;
  
  	vsync_period = mdss_panel_get_vtotal(&pdata->panel_info);
  	current_vsync_period_f0 = mdp_video_read(ctx,
  		MDSS_MDP_REG_INTF_VSYNC_PERIOD_F0);
  	new_vsync_period_f0 = (vsync_period * hsync_period);
  
  	mdp_video_write(ctx, MDSS_MDP_REG_INTF_VSYNC_PERIOD_F0,
  			current_vsync_period_f0 | 0x800000);
  	if (new_vsync_period_f0 & 0x800000) {
  		mdp_video_write(ctx, MDSS_MDP_REG_INTF_VSYNC_PERIOD_F0,
  			new_vsync_period_f0);
  	} else {
  		mdp_video_write(ctx, MDSS_MDP_REG_INTF_VSYNC_PERIOD_F0,
  			new_vsync_period_f0 | 0x800000);
  		mdp_video_write(ctx, MDSS_MDP_REG_INTF_VSYNC_PERIOD_F0,
  			new_vsync_period_f0 & 0x7fffff);
  	}
  
  	return 0;
  }
  
  static int mdss_mdp_video_dfps_wait4vsync(struct mdss_mdp_ctl *ctl)
  {
  	int rc = 0;
  	struct mdss_mdp_video_ctx *ctx;
  
  	ctx = (struct mdss_mdp_video_ctx *) ctl->priv_data;
  	if (!ctx) {
  		pr_err("invalid ctx\n");
  		return -ENODEV;
  	}
  
  	video_vsync_irq_enable(ctl, true);
  	INIT_COMPLETION(ctx->vsync_comp);
  	rc = wait_for_completion_timeout(&ctx->vsync_comp,
  		usecs_to_jiffies(VSYNC_TIMEOUT_US));
  	WARN(rc <= 0, "timeout (%d) vsync interrupt on ctl=%d\n",
  		rc, ctl->num);
  
  	video_vsync_irq_disable(ctl);
  	if (rc <= 0)
  		return -EPERM;
  
  	return 0;
  }
  
  static int mdss_mdp_video_dfps_check_line_cnt(struct mdss_mdp_ctl *ctl)
  {
  	struct mdss_panel_data *pdata;
  	u32 line_cnt;
  	pdata = ctl->panel_data;
  	if (pdata == NULL) {
  		pr_err("%s: Invalid panel data\n", __func__);
  		return -EINVAL;
  	}
  
  	line_cnt = mdss_mdp_video_line_count(ctl);
  	if (line_cnt >=	pdata->panel_info.yres/2) {
  		pr_err("Too few lines left line_cnt=%d yres/2=%d\n",
  			line_cnt,
  			pdata->panel_info.yres/2);
  		return -EPERM;
  	}
  	return 0;
  }
  
  static void mdss_mdp_video_timegen_flush(struct mdss_mdp_ctl *ctl,
  					struct mdss_mdp_video_ctx *sctx)
  {
  	u32 ctl_flush;
  	struct mdss_data_type *mdata;
  	mdata = ctl->mdata;
  	ctl_flush = (BIT(31) >> (ctl->intf_num - MDSS_MDP_INTF0));
  	if (sctx) {
  		/* For 8939, sctx is always INTF2 and the flush bit is BIT 31 */
  		if (mdata->mdp_rev == MDSS_MDP_HW_REV_108)
  			ctl_flush |= BIT(31);
  		else
  			ctl_flush |= (BIT(31) >>
  					(sctx->intf_num - MDSS_MDP_INTF0));
  	}
  	mdss_mdp_ctl_write(ctl, MDSS_MDP_REG_CTL_FLUSH, ctl_flush);
  }
  
  static int mdss_mdp_video_config_fps(struct mdss_mdp_ctl *ctl,
  					struct mdss_mdp_ctl *sctl, int new_fps)
  {
  	struct mdss_mdp_video_ctx *ctx, *sctx = NULL;
  	struct mdss_panel_data *pdata;
  	int rc = 0;
  	u32 hsync_period, vsync_period;
  	struct mdss_data_type *mdata;
  	u32 inum = ctl->intf_num - MDSS_MDP_INTF0;
  
  	ctx = (struct mdss_mdp_video_ctx *) ctl->priv_data;
  	if (!ctx) {
  		pr_err("invalid ctx\n");
  		return -ENODEV;
  	}
  
  	mdata = ctl->mdata;
  	if (sctl) {
  		sctx = (struct mdss_mdp_video_ctx *) sctl->priv_data;
  		if (!sctx) {
  			pr_err("invalid ctx\n");
  			return -ENODEV;
  		}
  	} else if (is_split_dst(ctl->mfd)) {
  		/*
  		 * On targets when destination split is enabled, mixer swap
  		 * is not valid. So we can safely assume that ctl->intf_num
  		 * is INTF1. For this case, increment inum to retrieve sctx.
  		 */
  		inum += 1;
  		if (inum < mdata->nintf) {
  			sctx = ((struct mdss_mdp_video_ctx *)
  						mdata->video_intf) + inum;
  			if (!sctx) {
  				pr_err("invalid sctx\n");
  				return -ENODEV;
  			}
  		} else {
  			pr_err("Invalid intf number: %d\n",
  						(inum + MDSS_MDP_INTF0));
  			return -ENODEV;
  		}
  	}
  
  	pdata = ctl->panel_data;
  	if (pdata == NULL) {
  		pr_err("%s: Invalid panel data\n", __func__);
  		return -EINVAL;
  	}
  
  	if (!pdata->panel_info.dynamic_fps) {
  		pr_err("%s: Dynamic fps not enabled for this panel\n",
  						__func__);
  		return -EINVAL;
  	}
  
  	vsync_period = mdss_panel_get_vtotal(&pdata->panel_info);
  	hsync_period = mdss_panel_get_htotal(&pdata->panel_info, true);
  
  	if (pdata->panel_info.dfps_update
  			!= DFPS_SUSPEND_RESUME_MODE) {
  		if (pdata->panel_info.dfps_update
  				== DFPS_IMMEDIATE_CLK_UPDATE_MODE) {
  			if (!ctx->timegen_en) {
  				pr_err("TG is OFF. DFPS mode invalid\n");
  				return -EINVAL;
  			}
  			rc = mdss_mdp_ctl_intf_event(ctl,
  					MDSS_EVENT_PANEL_UPDATE_FPS,
  					(void *) (unsigned long) new_fps);
  			WARN(rc, "intf %d panel fps update error (%d)\n",
  							ctl->intf_num, rc);
  		} else if (pdata->panel_info.dfps_update
  				== DFPS_IMMEDIATE_PORCH_UPDATE_MODE) {
  			bool wait4vsync;
  			unsigned long flags;
  			if (!ctx->timegen_en) {
  				pr_err("TG is OFF. DFPS mode invalid\n");
  				return -EINVAL;
  			}
  
  			/*
  			 * MDP INTF registers are double buffered starting from
  			 * MDP v1.5. No need to wait for vsync on these targets.
  			 */
  			wait4vsync = (mdata->mdp_rev < MDSS_MDP_HW_REV_105);
  
  			if (wait4vsync) {
  				rc = mdss_mdp_video_dfps_wait4vsync(ctl);
  				if (rc < 0) {
  					pr_err("Error during wait4vsync\n");
  					return rc;
  				}
  			}
  
  			mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON);
  			spin_lock_irqsave(&ctx->dfps_lock, flags);
  
  			if (wait4vsync) {
  				rc = mdss_mdp_video_dfps_check_line_cnt(ctl);
  				if (rc < 0)
  					goto exit_dfps;
  			}
  
  			rc = mdss_mdp_video_vfp_fps_update(ctx, pdata, new_fps);
  			if (rc < 0) {
  				pr_err("%s: Error during DFPS\n", __func__);
  				goto exit_dfps;
  			}
  			if (sctx) {
  				rc = mdss_mdp_video_vfp_fps_update(sctx,
  							pdata->next, new_fps);
  				if (rc < 0) {
  					pr_err("%s: DFPS error\n", __func__);
  					goto exit_dfps;
  				}
  			}
  			rc = mdss_mdp_ctl_intf_event(ctl,
  					MDSS_EVENT_PANEL_UPDATE_FPS,
  					(void *) (unsigned long) new_fps);
  			WARN(rc, "intf %d panel fps update error (%d)\n",
  							ctl->intf_num, rc);
  
  			mdss_mdp_fetch_start_config(ctx, ctl);
  			if (sctx)
  				mdss_mdp_fetch_start_config(sctx, ctl);
  
  			/*
  			 * MDP INTF registers support DB on targets
  			 * starting from MDP v1.5.
  			 */
  			if (!wait4vsync)
  				mdss_mdp_video_timegen_flush(ctl, sctx);
  
  exit_dfps:
  			spin_unlock_irqrestore(&ctx->dfps_lock, flags);
  			mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF);
  		} else {
  			pr_err("intf %d panel, unknown FPS mode\n",
  							ctl->intf_num);
  			return -EINVAL;
  		}
  	} else {
  		rc = mdss_mdp_ctl_intf_event(ctl,
  				MDSS_EVENT_PANEL_UPDATE_FPS,
  				(void *) (unsigned long) new_fps);
  		WARN(rc, "intf %d panel fps update error (%d)\n",
  						ctl->intf_num, rc);
  	}
  
  	return rc;
  }
  
  static int mdss_mdp_video_display(struct mdss_mdp_ctl *ctl, void *arg)
  {
  	struct mdss_mdp_video_ctx *ctx;
  	struct mdss_mdp_ctl *sctl;
  	struct mdss_panel_data *pdata = ctl->panel_data;
  	int rc;
  
  	pr_debug("kickoff ctl=%d\n", ctl->num);
  
  	ctx = (struct mdss_mdp_video_ctx *) ctl->priv_data;
  	if (!ctx) {
  		pr_err("invalid ctx\n");
  		return -ENODEV;
  	}
  
  	if (!ctx->wait_pending) {
  		ctx->wait_pending++;
  		video_vsync_irq_enable(ctl, true);
  		INIT_COMPLETION(ctx->vsync_comp);
  	} else {
  		WARN(1, "commit without wait! ctl=%d", ctl->num);
  	}
  
  	MDSS_XLOG(ctl->num, ctl->underrun_cnt);
  
  	if (!ctx->timegen_en) {
  		rc = mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_LINK_READY, NULL);
  		if (rc) {
  			pr_warn("intf #%d link ready error (%d)\n",
  					ctl->intf_num, rc);
  			video_vsync_irq_disable(ctl);
  			ctx->wait_pending = 0;
  			return rc;
  		}
  
  		rc = mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_UNBLANK, NULL);
  		WARN(rc, "intf %d unblank error (%d)\n", ctl->intf_num, rc);
  
  		pr_debug("enabling timing gen for intf=%d\n", ctl->intf_num);
  
  		if (pdata->panel_info.cont_splash_enabled &&
  			!ctl->mfd->splash_info.splash_logo_enabled) {
  			rc = wait_for_completion_timeout(&ctx->vsync_comp,
  					usecs_to_jiffies(VSYNC_TIMEOUT_US));
  		}
  
  		rc = mdss_iommu_ctrl(1);
  		if (IS_ERR_VALUE(rc)) {
  			pr_err("IOMMU attach failed\n");
  			return rc;
  		}
  
  		mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON);
  
  		mdss_mdp_irq_enable(MDSS_MDP_IRQ_INTF_UNDER_RUN, ctl->intf_num);
  		sctl = mdss_mdp_get_split_ctl(ctl);
  		if (sctl)
  			mdss_mdp_irq_enable(MDSS_MDP_IRQ_INTF_UNDER_RUN,
  				sctl->intf_num);
  
  		mdss_bus_bandwidth_ctrl(true);
  
  		mdp_video_write(ctx, MDSS_MDP_REG_INTF_TIMING_ENGINE_EN, 1);
  		wmb();
  
  		rc = wait_for_completion_timeout(&ctx->vsync_comp,
  				usecs_to_jiffies(VSYNC_TIMEOUT_US));
  		WARN(rc == 0, "timeout (%d) enabling timegen on ctl=%d\n",
  				rc, ctl->num);
  
  		ctx->timegen_en = true;
  		rc = mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_PANEL_ON, NULL);
  		WARN(rc, "intf %d panel on error (%d)\n", ctl->intf_num, rc);
  		mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_POST_PANEL_ON, NULL);
  	}
  
  	return 0;
  }
  
  int mdss_mdp_video_reconfigure_splash_done(struct mdss_mdp_ctl *ctl,
  	bool handoff)
  {
  	struct mdss_panel_data *pdata;
  	int i, ret = 0, off;
  	u32 data, flush;
  	struct mdss_mdp_video_ctx *ctx;
  	struct mdss_mdp_ctl *sctl = mdss_mdp_get_split_ctl(ctl);
  #if defined(CONFIG_FB_MSM_MDSS_SAMSUNG)
  	struct mdss_dsi_ctrl_pdata *ctrl_pdata = NULL;
  	struct samsung_display_driver_data *vdd = NULL;
  #endif
  
  	off = 0;
  	ctx = (struct mdss_mdp_video_ctx *) ctl->priv_data;
  	if (!ctx) {
  		pr_err("invalid ctx for ctl=%d\n", ctl->num);
  		return -ENODEV;
  	}
  
  	pdata = ctl->panel_data;
  
  	pdata->panel_info.cont_splash_enabled = 0;
  	if (sctl)
  		sctl->panel_data->panel_info.cont_splash_enabled = 0;
  	else if (ctl->panel_data->next && is_split_dst(ctl->mfd))
  		ctl->panel_data->next->panel_info.cont_splash_enabled = 0;
  
  #if defined(CONFIG_FB_MSM_MDSS_SAMSUNG)
  	ctrl_pdata = container_of(pdata, struct mdss_dsi_ctrl_pdata,
  				panel_data);
  	vdd = check_valid_ctrl(ctrl_pdata);
  	if (vdd->panel_func.samsung_tft_blic_init && \
  		 vdd->dtsi_data[ctrl_pdata->ndx].tft_common_support)
  		vdd->panel_func.samsung_tft_blic_init(ctrl_pdata);
  	else if (handoff) {
  #else
  	if (handoff) {
  #endif
  		ctl->force_screen_state = MDSS_SCREEN_FORCE_BLANK;
  		mdss_mdp_display_commit(ctl, NULL, NULL);
  		mdss_mdp_display_wait4comp(ctl);
  		mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_BLANK, NULL);
  
  		mdp_video_write(ctx,	MDSS_MDP_REG_INTF_TIMING_ENGINE_EN, 0);
  		/*
  		 * Need to wait for atleast one vsync time for proper
  		 * TG OFF before doing changes on interfaces
  		 */
  		msleep(20);
  		mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_PANEL_OFF, NULL);
  
  		mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_LINK_READY, NULL);
  		mdss_mdp_ctl_intf_event(ctl,MDSS_EVENT_UNBLANK, NULL);
  		mdp_video_write(ctx,	MDSS_MDP_REG_INTF_TIMING_ENGINE_EN, 1);
  		mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_PANEL_ON, NULL);
  		/*
  		 * Add memory barrier to make sure the MDP Video
  		 * mode engine is enabled before next frame is sent
  		 */
  		mb();
  		ctl->force_screen_state = MDSS_SCREEN_DEFAULT;
  		mdss_mdp_display_commit(ctl, NULL, NULL);
  		mdss_mdp_display_wait4comp(ctl);
  	}
  
  	if (!handoff) {
  		ret = mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_CONT_SPLASH_BEGIN,
  					      NULL);
  		if (ret) {
  			pr_err("%s: Failed to handle 'CONT_SPLASH_BEGIN' event\n"
  				, __func__);
  			return ret;
  		}
  
  		/* clear up mixer0 and mixer1 */
  		flush = 0;
  		for (i = 0; i < 2; i++) {
  			data = mdss_mdp_ctl_read(ctl,
  				MDSS_MDP_REG_CTL_LAYER(i));
  			if (data) {
  				mdss_mdp_ctl_write(ctl,
  					MDSS_MDP_REG_CTL_LAYER(i),
  					MDSS_MDP_LM_BORDER_COLOR);
  				flush |= (0x40 << i);
  			}
  		}
  		mdss_mdp_ctl_write(ctl, MDSS_MDP_REG_CTL_FLUSH, flush);
  
  		mdp_video_write(ctx, MDSS_MDP_REG_INTF_TIMING_ENGINE_EN, 0);
  		/* wait for 1 VSYNC for the pipe to be unstaged */
  		msleep(20);
  
  		ret = mdss_mdp_ctl_intf_event(ctl,
  			MDSS_EVENT_CONT_SPLASH_FINISH, NULL);
  	}
  
  	return ret;
  }
  
  static bool mdss_mdp_fetch_programable(struct mdss_mdp_ctl *ctl)
  {
  	struct mdss_panel_info *pinfo = &ctl->panel_data->panel_info;
  	struct mdss_data_type *mdata;
  	bool ret;
  
  	mdata = ctl->mdata;
  
  	if (mdata->mdp_rev >= MDSS_MDP_HW_REV_105) {
  		if ((pinfo->lcdc.v_back_porch + pinfo->lcdc.v_front_porch) <
  				MDP_MIN_FETCH) {
  			pr_warn_once("low vbp+vfp may lead to perf issues in some cases\n");
  		}
  		ret = true;
  
  		if (pinfo->lcdc.v_back_porch > MDP_MIN_FETCH)
  			ret = false;
  	} else {
  		if (pinfo->lcdc.v_back_porch < MDP_MIN_FETCH)
  			pr_warn_once("low vbp may lead to display performance issues");
  		ret = false;
  	}
  
  	return ret;
  }
  
  static void mdss_mdp_fetch_start_config(struct mdss_mdp_video_ctx *ctx,
  		struct mdss_mdp_ctl *ctl)
  {
  	int fetch_start, fetch_enable, v_total, h_total;
  	struct mdss_data_type *mdata;
  	struct mdss_panel_info *pinfo = &ctl->panel_data->panel_info;
  
  	mdata = ctl->mdata;
  
  	if (!mdss_mdp_fetch_programable(ctl)) {
  		pr_debug("programmable fetch is not needed/supported\n");
  		ctl->prg_fet = 0;
  		return;
  	}
  
  	/*
  	 * Fetch should always be outside the active lines. If the fetching
  	 * is programmed within active region, hardware behavior is unknown.
  	 */
  	v_total = mdss_panel_get_vtotal(pinfo);
  	h_total = mdss_panel_get_htotal(pinfo, true);
  	ctl->prg_fet = pinfo->lcdc.v_front_porch;
  	if (ctl->prg_fet > MDSS_MDP_MAX_FETCH)
  		ctl->prg_fet = MDSS_MDP_MAX_FETCH;
  	fetch_start = (v_total - ctl->prg_fet) * h_total + 1;
  	fetch_enable = BIT(31);
  
  	pr_debug("ctl:%d, fetch start=%d\n", ctl->num, fetch_start);
  	mdp_video_write(ctx, MDSS_MDP_REG_INTF_PROG_FETCH_START, fetch_start);
  	mdp_video_write(ctx, MDSS_MDP_REG_INTF_CONFIG, fetch_enable);
  }
  static void mdss_mdp_video_pingpong_done(void *arg)
  {
  	struct mdss_mdp_ctl *ctl = arg;
  	struct mdss_mdp_video_ctx *ctx;
  	ctx = (struct mdss_mdp_video_ctx *) ctl->priv_data;
  	pr_info("%s:mdss_mdp_isr ctl->mixer_left->num = %d\n", __func__, ctl->mixer_left->num);
  
  	if (!ctx) {
  		pr_err("invalid ctx\n");
  		return;
  	}
  	if(ctl->intf_type == MDSS_INTF_DSI)
  		mdss_mdp_irq_disable_nosync(MDSS_MDP_IRQ_PING_PONG_COMP, ctl->mixer_left->num);
  	complete_all(&ctx->pp_comp);
  
  }
  static int mdss_mdp_video_wait4pingpong(struct mdss_mdp_ctl *ctl, void *arg)
  {
  	struct mdss_mdp_video_ctx *ctx;
  	int rc = 0;
  	ctx = (struct mdss_mdp_video_ctx *) ctl->priv_data;
  	pr_info("%s: mdss_mdp_isr ctl->mixer_left->num = %d\n", __func__, ctl->mixer_left->num);
  
  	if (!ctx) {
  		pr_err("invalid ctx\n");
  		return -ENODEV;
  	}
  	INIT_COMPLETION(ctx->pp_comp);
  
  	rc = wait_for_completion_timeout(
  		&ctx->pp_comp, msecs_to_jiffies(20));
  
  	return rc;
  }
  
  static void mdss_mdp_handoff_programmable_fetch(struct mdss_mdp_ctl *ctl,
  	struct mdss_mdp_video_ctx *ctx)
  {
  	u32 fetch_start_handoff, v_total_handoff, h_total_handoff;
  	ctl->prg_fet = 0;
  	if (mdp_video_read(ctx, MDSS_MDP_REG_INTF_CONFIG) & BIT(31)) {
  		fetch_start_handoff = mdp_video_read(ctx,
  			MDSS_MDP_REG_INTF_PROG_FETCH_START);
  		h_total_handoff = mdp_video_read(ctx,
  			MDSS_MDP_REG_INTF_HSYNC_CTL) >> 16;
  		v_total_handoff = mdp_video_read(ctx,
  			MDSS_MDP_REG_INTF_VSYNC_PERIOD_F0)/h_total_handoff;
  		ctl->prg_fet = v_total_handoff -
  			((fetch_start_handoff - 1)/h_total_handoff);
  		pr_debug("programmable fetch lines %d\n", ctl->prg_fet);
  	}
  }
  
  static int mdss_mdp_video_intfs_setup(struct mdss_mdp_ctl *ctl,
  	struct mdss_panel_data *pdata, int inum)
  {
  	struct mdss_data_type *mdata;
  	struct mdss_panel_info *pinfo;
  	struct mdss_mdp_video_ctx *ctx;
  	struct intf_timing_params itp = {0};
  	u32 dst_bpp;
  	int ret = 0;
  
  	if (pdata == NULL)
  		return 0;
  
  	if (is_split_dst(ctl->mfd)) {
  		ret = mdss_mdp_video_intfs_setup(ctl, pdata->next, (inum + 1));
  		if (IS_ERR_VALUE(ret))
  			return ret;
  	}
  
  	mdata = ctl->mdata;
  	pinfo = &pdata->panel_info;
  
  	if (inum < mdata->nintf) {
  		ctx = ((struct mdss_mdp_video_ctx *) mdata->video_intf) + inum;
  		if (ctx->ref_cnt) {
  			pr_err("Intf %d already in use\n",
  				(inum + MDSS_MDP_INTF0));
  			return -EBUSY;
  		}
  		pr_debug("video Intf #%d base=%pK", ctx->intf_num, ctx->base);
  		ctx->ref_cnt++;
  	} else {
  		pr_err("Invalid intf number: %d\n", (inum + MDSS_MDP_INTF0));
  		return -EINVAL;
  	}
  
  	ctl->priv_data = ctx;
  	ctx->intf_type = ctl->intf_type;
  	init_completion(&ctx->vsync_comp);
  	init_completion(&ctx->pp_comp);
  	spin_lock_init(&ctx->vsync_lock);
  	spin_lock_init(&ctx->dfps_lock);
  	mutex_init(&ctx->vsync_mtx);
  	atomic_set(&ctx->vsync_ref, 0);
  	INIT_WORK(&ctl->recover_work, recover_underrun_work);
  
  	if (ctl->intf_type == MDSS_INTF_DSI) {
  		ctx->intf_recovery.fxn = mdss_mdp_video_intf_recovery;
  		ctx->intf_recovery.data = ctl;
  		if (mdss_mdp_ctl_intf_event(ctl,
  					MDSS_EVENT_REGISTER_RECOVERY_HANDLER,
  					(void *)&ctx->intf_recovery)) {
  			pr_err("Failed to register intf recovery handler\n");
  			return -EINVAL;
  		}
  	} else {
  		ctx->intf_recovery.fxn = NULL;
  		ctx->intf_recovery.data = NULL;
  	}
  
  	mdss_mdp_set_intr_callback(MDSS_MDP_IRQ_INTF_VSYNC,
  				(inum + MDSS_MDP_INTF0),
  				mdss_mdp_video_vsync_intr_done, ctl);
  	mdss_mdp_set_intr_callback(MDSS_MDP_IRQ_INTF_UNDER_RUN,
  				(inum + MDSS_MDP_INTF0),
  				mdss_mdp_video_underrun_intr_done, ctl);
  	if(ctl->intf_type == MDSS_INTF_DSI)
  		mdss_mdp_set_intr_callback(MDSS_MDP_IRQ_PING_PONG_COMP,
  					ctl->mixer_left->num,  mdss_mdp_video_pingpong_done, ctl);
  	dst_bpp = pinfo->fbc.enabled ? (pinfo->fbc.target_bpp) : (pinfo->bpp);
  
  	itp.width = mult_frac((pinfo->xres + pinfo->lcdc.xres_pad),
  				dst_bpp, pinfo->bpp);
  	itp.height = pinfo->yres + pinfo->lcdc.yres_pad;
  	itp.border_clr = pinfo->lcdc.border_clr;
  	itp.underflow_clr = pinfo->lcdc.underflow_clr;
  	itp.hsync_skew = pinfo->lcdc.hsync_skew;
  
  	itp.xres = mult_frac(pinfo->xres, dst_bpp, pinfo->bpp);
  	itp.yres = pinfo->yres;
  	itp.h_back_porch = pinfo->lcdc.h_back_porch;
  	itp.h_front_porch = pinfo->lcdc.h_front_porch;
  	itp.v_back_porch = pinfo->lcdc.v_back_porch;
  	itp.v_front_porch = pinfo->lcdc.v_front_porch;
  	itp.hsync_pulse_width = pinfo->lcdc.h_pulse_width;
  	itp.vsync_pulse_width = pinfo->lcdc.v_pulse_width;
  
  	if (!ctl->panel_data->panel_info.cont_splash_enabled) {
  		if (mdss_mdp_video_timegen_setup(ctl, &itp)) {
  			pr_err("unable to set timing parameters intfs: %d\n",
  				(inum + MDSS_MDP_INTF0));
  			return -EINVAL;
  		}
  		mdss_mdp_fetch_start_config(ctx, ctl);
  	} else {
  		mdss_mdp_handoff_programmable_fetch(ctl, ctx);
  	}
  
  	mdp_video_write(ctx, MDSS_MDP_REG_INTF_PANEL_FORMAT, ctl->dst_format);
  
  	return 0;
  }
  
  int mdss_mdp_video_start(struct mdss_mdp_ctl *ctl)
  {
  	int intfs_num, ret = 0;
  
  	intfs_num = ctl->intf_num - MDSS_MDP_INTF0;
  	ret = mdss_mdp_video_intfs_setup(ctl, ctl->panel_data, intfs_num);
  	if (IS_ERR_VALUE(ret)) {
  		pr_err("unable to set video interface: %d\n", ret);
  		return ret;
  	}
  
  	ctl->stop_fnc = mdss_mdp_video_stop;
  	ctl->display_fnc = mdss_mdp_video_display;
  	ctl->wait_fnc = mdss_mdp_video_wait4comp;
  	ctl->read_line_cnt_fnc = mdss_mdp_video_line_count;
  	ctl->add_vsync_handler = mdss_mdp_video_add_vsync_handler;
  	ctl->remove_vsync_handler = mdss_mdp_video_remove_vsync_handler;
  	ctl->config_fps_fnc = mdss_mdp_video_config_fps;
  	if(ctl->intf_type == MDSS_INTF_DSI)
  		ctl->wait_video_pingpong = mdss_mdp_video_wait4pingpong;
  	else
  		ctl->wait_video_pingpong = NULL;
  	return 0;
  }
  
  void *mdss_mdp_get_intf_base_addr(struct mdss_data_type *mdata,
  		u32 interface_id)
  {
  	struct mdss_mdp_video_ctx *ctx;
  	ctx = ((struct mdss_mdp_video_ctx *) mdata->video_intf) + interface_id;
  	return (void *)(ctx->base);
  }
  