drm/msm/sde: add dynamic clock and bandwidth commit support

Current crtc supports static clock and bandwidth defined in
device tree.  Add support for dynamic clock and bandwidth
control using new atomic crtc properties. User mode
can adjust core clock rate, instantaneous and arbitrated
bandwidth per crtc commit.

CRs-Fixed: 2005348
Change-Id: I753a141bbc023d89f1532943f48252875883d684
Signed-off-by: Alan Kwong <akwong@codeaurora.org>
diff --git a/drivers/gpu/drm/msm/dsi-staging/dsi_defs.h b/drivers/gpu/drm/msm/dsi-staging/dsi_defs.h
index 50a0733..2caa32e 100644
--- a/drivers/gpu/drm/msm/dsi-staging/dsi_defs.h
+++ b/drivers/gpu/drm/msm/dsi-staging/dsi_defs.h
@@ -318,12 +318,15 @@
  * @wr_mem_continue:               DCS command for write_memory_continue.
  * @insert_dcs_command:            Insert DCS command as first byte of payload
  *                                 of the pixel data.
+ * @mdp_transfer_time_us   Specifies the mdp transfer time for command mode
+ *                         panels in microseconds
  */
 struct dsi_cmd_engine_cfg {
 	u32 max_cmd_packets_interleave;
 	u32 wr_mem_start;
 	u32 wr_mem_continue;
 	bool insert_dcs_command;
+	u32 mdp_transfer_time_us;
 };
 
 /**
diff --git a/drivers/gpu/drm/msm/dsi-staging/dsi_drm.c b/drivers/gpu/drm/msm/dsi-staging/dsi_drm.c
index bf50ebb..a1adecf 100644
--- a/drivers/gpu/drm/msm/dsi-staging/dsi_drm.c
+++ b/drivers/gpu/drm/msm/dsi-staging/dsi_drm.c
@@ -313,6 +313,8 @@
 		break;
 	case DSI_OP_CMD_MODE:
 		sde_kms_info_add_keystr(info, "panel mode", "command");
+		sde_kms_info_add_keyint(info, "mdp_transfer_time_us",
+				panel->cmd_config.mdp_transfer_time_us);
 		break;
 	default:
 		pr_debug("invalid panel type:%d\n", panel->mode.panel_mode);
diff --git a/drivers/gpu/drm/msm/dsi-staging/dsi_panel.c b/drivers/gpu/drm/msm/dsi-staging/dsi_panel.c
index 28cfa1f..a7a39e6 100644
--- a/drivers/gpu/drm/msm/dsi-staging/dsi_panel.c
+++ b/drivers/gpu/drm/msm/dsi-staging/dsi_panel.c
@@ -22,6 +22,8 @@
 
 #define DSI_PANEL_DEFAULT_LABEL  "Default dsi panel"
 
+#define DEFAULT_MDP_TRANSFER_TIME 14000
+
 static int dsi_panel_vreg_get(struct dsi_panel *panel)
 {
 	int rc = 0;
@@ -954,6 +956,14 @@
 		goto error;
 	}
 
+	if (of_property_read_u32(of_node, "qcom,mdss-mdp-transfer-time-us",
+				&val)) {
+		pr_debug("[%s] Fallback to default transfer-time-us\n", name);
+		cfg->mdp_transfer_time_us = DEFAULT_MDP_TRANSFER_TIME;
+	} else {
+		cfg->mdp_transfer_time_us = val;
+	}
+
 error:
 	return rc;
 }
diff --git a/drivers/gpu/drm/msm/msm_drv.h b/drivers/gpu/drm/msm/msm_drv.h
index 499dfcc..647698e 100644
--- a/drivers/gpu/drm/msm/msm_drv.h
+++ b/drivers/gpu/drm/msm/msm_drv.h
@@ -126,6 +126,9 @@
 	CRTC_PROP_OUTPUT_FENCE,
 	CRTC_PROP_OUTPUT_FENCE_OFFSET,
 	CRTC_PROP_DIM_LAYER_V1,
+	CRTC_PROP_CORE_CLK,
+	CRTC_PROP_CORE_AB,
+	CRTC_PROP_CORE_IB,
 
 	/* total # of properties */
 	CRTC_PROP_COUNT
diff --git a/drivers/gpu/drm/msm/sde/sde_core_perf.c b/drivers/gpu/drm/msm/sde/sde_core_perf.c
index 3479cf8..0ba644d 100644
--- a/drivers/gpu/drm/msm/sde/sde_core_perf.c
+++ b/drivers/gpu/drm/msm/sde/sde_core_perf.c
@@ -33,8 +33,469 @@
 #include "sde_core_perf.h"
 #include "sde_trace.h"
 
+static struct sde_kms *_sde_crtc_get_kms(struct drm_crtc *crtc)
+{
+	struct msm_drm_private *priv;
+
+	if (!crtc->dev || !crtc->dev->dev_private) {
+		SDE_ERROR("invalid device\n");
+		return NULL;
+	}
+
+	priv = crtc->dev->dev_private;
+	if (!priv || !priv->kms) {
+		SDE_ERROR("invalid kms\n");
+		return NULL;
+	}
+
+	return to_sde_kms(priv->kms);
+}
+
+static bool _sde_core_perf_crtc_is_power_on(struct drm_crtc *crtc)
+{
+	return sde_crtc_is_enabled(crtc);
+}
+
+static bool _sde_core_video_mode_intf_connected(struct drm_crtc *crtc)
+{
+	struct drm_crtc *tmp_crtc;
+
+	if (!crtc)
+		return 0;
+
+	drm_for_each_crtc(tmp_crtc, crtc->dev) {
+		if ((sde_crtc_get_intf_mode(tmp_crtc) == INTF_MODE_VIDEO) &&
+				_sde_core_perf_crtc_is_power_on(tmp_crtc)) {
+			SDE_DEBUG("video interface connected crtc:%d\n",
+				tmp_crtc->base.id);
+			return true;
+		}
+	}
+
+	return false;
+}
+
+int sde_core_perf_crtc_check(struct drm_crtc *crtc,
+		struct drm_crtc_state *state)
+{
+	u32 bw, threshold;
+	u64 bw_sum_of_intfs = 0;
+	bool is_video_mode;
+	struct sde_crtc_state *sde_cstate;
+	struct drm_crtc *tmp_crtc;
+	struct sde_kms *kms;
+
+	if (!crtc || !state) {
+		SDE_ERROR("invalid crtc\n");
+		return -EINVAL;
+	}
+
+	kms = _sde_crtc_get_kms(crtc);
+	if (!kms || !kms->catalog) {
+		SDE_ERROR("invalid parameters\n");
+		return 0;
+	}
+
+	/* we only need bandwidth check on real-time clients (interfaces) */
+	if (sde_crtc_is_wb(crtc))
+		return 0;
+
+	sde_cstate = to_sde_crtc_state(state);
+
+	bw_sum_of_intfs = sde_crtc_get_property(sde_cstate, CRTC_PROP_CORE_AB);
+
+	drm_for_each_crtc(tmp_crtc, crtc->dev) {
+		if (_sde_core_perf_crtc_is_power_on(tmp_crtc) &&
+				sde_crtc_is_rt(tmp_crtc) && tmp_crtc != crtc) {
+			struct sde_crtc_state *tmp_cstate =
+					to_sde_crtc_state(tmp_crtc->state);
+
+			bw_sum_of_intfs += tmp_cstate->cur_perf.bw_ctl;
+		}
+	}
+
+	/* convert bandwidth to kb */
+	bw = DIV_ROUND_UP_ULL(bw_sum_of_intfs, 1000);
+	SDE_DEBUG("calculated bandwidth=%uk\n", bw);
+
+	is_video_mode = sde_crtc_get_intf_mode(crtc) == INTF_MODE_VIDEO;
+	threshold = (is_video_mode ||
+		_sde_core_video_mode_intf_connected(crtc)) ?
+		kms->catalog->perf.max_bw_low : kms->catalog->perf.max_bw_high;
+
+	SDE_DEBUG("final threshold bw limit = %d\n", threshold);
+
+	if (!threshold) {
+		sde_cstate->cur_perf.bw_ctl = 0;
+		SDE_ERROR("no bandwidth limits specified\n");
+		return -E2BIG;
+	} else if (bw > threshold) {
+		sde_cstate->cur_perf.bw_ctl = 0;
+		SDE_DEBUG("exceeds bandwidth: %ukb > %ukb\n", bw, threshold);
+		return -E2BIG;
+	}
+
+	return 0;
+}
+
+static void _sde_core_perf_calc_crtc(struct sde_kms *kms,
+		struct drm_crtc *crtc,
+		struct sde_core_perf_params *perf)
+{
+	struct sde_crtc_state *sde_cstate;
+
+	sde_cstate = to_sde_crtc_state(crtc->state);
+	memset(perf, 0, sizeof(struct sde_core_perf_params));
+
+	perf->bw_ctl = sde_crtc_get_property(sde_cstate, CRTC_PROP_CORE_AB);
+	perf->max_per_pipe_ib =
+			sde_crtc_get_property(sde_cstate, CRTC_PROP_CORE_IB);
+	perf->core_clk_rate =
+			sde_crtc_get_property(sde_cstate, CRTC_PROP_CORE_CLK);
+
+	SDE_DEBUG("crtc=%d clk_rate=%u ib=%llu ab=%llu\n",
+			crtc->base.id, perf->core_clk_rate,
+			perf->max_per_pipe_ib, perf->bw_ctl);
+}
+
+static u64 _sde_core_perf_crtc_calc_client_vote(struct sde_kms *kms,
+		struct drm_crtc *crtc, struct sde_core_perf_params *perf,
+		bool nrt_client, u32 core_clk)
+{
+	u64 bw_sum_of_intfs = 0;
+	struct drm_crtc *tmp_crtc;
+
+	drm_for_each_crtc(tmp_crtc, crtc->dev) {
+		if (_sde_core_perf_crtc_is_power_on(crtc) &&
+		    /* RealTime clients */
+		    ((!nrt_client) ||
+		    /* Non-RealTime clients */
+		    (nrt_client && sde_crtc_is_nrt(tmp_crtc)))) {
+			struct sde_crtc_state *sde_cstate =
+					to_sde_crtc_state(tmp_crtc->state);
+
+			perf->max_per_pipe_ib = max(perf->max_per_pipe_ib,
+				sde_cstate->cur_perf.max_per_pipe_ib);
+
+			bw_sum_of_intfs += sde_cstate->cur_perf.bw_ctl;
+
+			SDE_DEBUG("crtc=%d bw=%llu\n",
+				tmp_crtc->base.id,
+				sde_cstate->cur_perf.bw_ctl);
+		}
+	}
+
+	return bw_sum_of_intfs;
+}
+
+static void _sde_core_perf_crtc_update_client_vote(struct sde_kms *kms,
+	struct sde_core_perf_params *params, bool nrt_client, u64 bw_vote)
+{
+	struct msm_drm_private *priv = kms->dev->dev_private;
+	u64 bus_ab_quota, bus_ib_quota;
+
+	bus_ab_quota = max(bw_vote, kms->perf.perf_tune.min_bus_vote);
+	bus_ib_quota = params->max_per_pipe_ib;
+
+	SDE_ATRACE_INT("bus_quota", bus_ib_quota);
+	sde_power_data_bus_set_quota(&priv->phandle, kms->core_client,
+		nrt_client ? SDE_POWER_HANDLE_DATA_BUS_CLIENT_NRT :
+				SDE_POWER_HANDLE_DATA_BUS_CLIENT_RT,
+		bus_ab_quota, bus_ib_quota);
+	SDE_DEBUG("client:%s ab=%llu ib=%llu\n", nrt_client ? "nrt" : "rt",
+		bus_ab_quota, bus_ib_quota);
+}
+
+static void _sde_core_perf_crtc_update_bus(struct sde_kms *kms,
+		struct drm_crtc *crtc, u32 core_clk)
+{
+	u64 bw_sum_of_rt_intfs = 0, bw_sum_of_nrt_intfs = 0;
+	struct sde_core_perf_params params = {0};
+
+	SDE_ATRACE_BEGIN(__func__);
+
+	/*
+	 * non-real time client
+	 */
+	if (sde_crtc_is_nrt(crtc)) {
+		bw_sum_of_nrt_intfs = _sde_core_perf_crtc_calc_client_vote(
+				kms, crtc, &params, true, core_clk);
+		_sde_core_perf_crtc_update_client_vote(kms, &params, true,
+			bw_sum_of_nrt_intfs);
+	}
+
+	/*
+	 * real time client
+	 */
+	if (!sde_crtc_is_nrt(crtc) ||
+		sde_crtc_is_wb(crtc)) {
+		bw_sum_of_rt_intfs = _sde_core_perf_crtc_calc_client_vote(kms,
+				crtc, &params, false, core_clk);
+		_sde_core_perf_crtc_update_client_vote(kms, &params, false,
+			bw_sum_of_rt_intfs);
+	}
+
+	SDE_ATRACE_END(__func__);
+}
+
+/**
+ * @sde_core_perf_crtc_release_bw() - request zero bandwidth
+ * @crtc - pointer to a crtc
+ *
+ * Function checks a state variable for the crtc, if all pending commit
+ * requests are done, meaning no more bandwidth is needed, release
+ * bandwidth request.
+ */
+void sde_core_perf_crtc_release_bw(struct drm_crtc *crtc)
+{
+	struct drm_crtc *tmp_crtc;
+	struct sde_crtc_state *sde_cstate;
+	struct sde_kms *kms;
+
+	if (!crtc) {
+		SDE_ERROR("invalid crtc\n");
+		return;
+	}
+
+	kms = _sde_crtc_get_kms(crtc);
+	if (!kms || !kms->catalog) {
+		SDE_ERROR("invalid kms\n");
+		return;
+	}
+
+	sde_cstate = to_sde_crtc_state(crtc->state);
+
+	/* only do this for command panel or writeback */
+	if ((sde_crtc_get_intf_mode(crtc) != INTF_MODE_CMD) &&
+			(sde_crtc_get_intf_mode(crtc) != INTF_MODE_WB_LINE))
+		return;
+
+	/*
+	 * If video interface present, cmd panel bandwidth cannot be
+	 * released.
+	 */
+	if (sde_crtc_get_intf_mode(crtc) == INTF_MODE_CMD)
+		drm_for_each_crtc(tmp_crtc, crtc->dev) {
+			if (_sde_core_perf_crtc_is_power_on(tmp_crtc) &&
+				sde_crtc_get_intf_mode(tmp_crtc) ==
+						INTF_MODE_VIDEO)
+				return;
+		}
+
+	/* Release the bandwidth */
+	if (kms->perf.enable_bw_release) {
+		trace_sde_cmd_release_bw(crtc->base.id);
+		sde_cstate->cur_perf.bw_ctl = 0;
+		sde_cstate->new_perf.bw_ctl = 0;
+		SDE_DEBUG("Release BW crtc=%d\n", crtc->base.id);
+		_sde_core_perf_crtc_update_bus(kms, crtc, 0);
+	}
+}
+
+static int _sde_core_select_clk_lvl(struct sde_kms *kms,
+			u32 clk_rate)
+{
+	return clk_round_rate(kms->perf.core_clk, clk_rate);
+}
+
+static u32 _sde_core_perf_get_core_clk_rate(struct sde_kms *kms)
+{
+	u32 clk_rate = 0;
+	struct drm_crtc *crtc;
+	struct sde_crtc_state *sde_cstate;
+	int ncrtc = 0;
+
+	drm_for_each_crtc(crtc, kms->dev) {
+		if (_sde_core_perf_crtc_is_power_on(crtc)) {
+			sde_cstate = to_sde_crtc_state(crtc->state);
+			clk_rate = max(sde_cstate->cur_perf.core_clk_rate,
+							clk_rate);
+			clk_rate = clk_round_rate(kms->perf.core_clk, clk_rate);
+		}
+		ncrtc++;
+	}
+	clk_rate = _sde_core_select_clk_lvl(kms, clk_rate);
+
+	SDE_DEBUG("clk:%u ncrtc:%d\n", clk_rate, ncrtc);
+
+	return clk_rate;
+}
+
+void sde_core_perf_crtc_update(struct drm_crtc *crtc,
+		int params_changed, bool stop_req)
+{
+	struct sde_core_perf_params *new, *old;
+	int update_bus = 0, update_clk = 0;
+	u32 clk_rate = 0;
+	struct sde_crtc *sde_crtc;
+	struct sde_crtc_state *sde_cstate;
+	int ret;
+	struct msm_drm_private *priv;
+	struct sde_kms *kms;
+
+	if (!crtc) {
+		SDE_ERROR("invalid crtc\n");
+		return;
+	}
+
+	kms = _sde_crtc_get_kms(crtc);
+	if (!kms || !kms->catalog) {
+		SDE_ERROR("invalid kms\n");
+		return;
+	}
+	priv = kms->dev->dev_private;
+
+	sde_crtc = to_sde_crtc(crtc);
+	sde_cstate = to_sde_crtc_state(crtc->state);
+
+	SDE_DEBUG("crtc:%d stop_req:%d core_clk:%u\n",
+			crtc->base.id, stop_req, kms->perf.core_clk_rate);
+
+	SDE_ATRACE_BEGIN(__func__);
+
+	old = &sde_cstate->cur_perf;
+	new = &sde_cstate->new_perf;
+
+	if (_sde_core_perf_crtc_is_power_on(crtc) && !stop_req) {
+		if (params_changed)
+			_sde_core_perf_calc_crtc(kms, crtc, new);
+
+		/*
+		 * cases for bus bandwidth update.
+		 * 1. new bandwidth vote or writeback output vote
+		 *    are higher than current vote for update request.
+		 * 2. new bandwidth vote or writeback output vote are
+		 *    lower than current vote at end of commit or stop.
+		 */
+		if ((params_changed && ((new->bw_ctl > old->bw_ctl))) ||
+		    (!params_changed && ((new->bw_ctl < old->bw_ctl)))) {
+			SDE_DEBUG("crtc=%d p=%d new_bw=%llu,old_bw=%llu\n",
+				crtc->base.id, params_changed, new->bw_ctl,
+				old->bw_ctl);
+			old->bw_ctl = new->bw_ctl;
+			old->max_per_pipe_ib = new->max_per_pipe_ib;
+			update_bus = 1;
+		}
+
+		if ((params_changed &&
+				(new->core_clk_rate > old->core_clk_rate)) ||
+				(!params_changed &&
+				(new->core_clk_rate < old->core_clk_rate))) {
+			old->core_clk_rate = new->core_clk_rate;
+			update_clk = 1;
+		}
+	} else {
+		SDE_DEBUG("crtc=%d disable\n", crtc->base.id);
+		memset(old, 0, sizeof(*old));
+		memset(new, 0, sizeof(*new));
+		update_bus = 1;
+		update_clk = 1;
+	}
+
+	/*
+	 * Calculate mdp clock before bandwidth calculation. If traffic shaper
+	 * is enabled and clock increased, the bandwidth calculation can
+	 * use the new clock for the rotator bw calculation.
+	 */
+	if (update_clk)
+		clk_rate = _sde_core_perf_get_core_clk_rate(kms);
+
+	if (update_bus)
+		_sde_core_perf_crtc_update_bus(kms, crtc, clk_rate);
+
+	/*
+	 * Update the clock after bandwidth vote to ensure
+	 * bandwidth is available before clock rate is increased.
+	 */
+	if (update_clk) {
+		SDE_ATRACE_INT(kms->perf.clk_name, clk_rate);
+		SDE_EVT32(kms->dev, stop_req, clk_rate);
+		ret = sde_power_clk_set_rate(&priv->phandle,
+				kms->perf.clk_name, clk_rate);
+		if (ret) {
+			SDE_ERROR("failed to set %s clock rate %u\n",
+					kms->perf.clk_name, clk_rate);
+			goto end;
+		}
+
+		kms->perf.core_clk_rate = clk_rate;
+		SDE_DEBUG("update clk rate = %d HZ\n", clk_rate);
+	}
+
+end:
+	SDE_ATRACE_END(__func__);
+}
+
 #ifdef CONFIG_DEBUG_FS
 
+static ssize_t _sde_core_perf_mode_write(struct file *file,
+		    const char __user *user_buf, size_t count, loff_t *ppos)
+{
+	struct sde_core_perf *perf = file->private_data;
+	struct sde_perf_cfg *cfg = &perf->catalog->perf;
+	int perf_mode = 0;
+	char buf[10];
+
+	if (!perf)
+		return -ENODEV;
+
+	if (count >= sizeof(buf))
+		return -EFAULT;
+
+	if (copy_from_user(buf, user_buf, count))
+		return -EFAULT;
+
+	buf[count] = 0;	/* end of string */
+
+	if (kstrtoint(buf, 0, &perf_mode))
+		return -EFAULT;
+
+	if (perf_mode) {
+		/* run the driver with max clk and BW vote */
+		perf->perf_tune.min_core_clk = perf->max_core_clk_rate;
+		perf->perf_tune.min_bus_vote =
+				(u64) cfg->max_bw_high * 1000;
+	} else {
+		/* reset the perf tune params to 0 */
+		perf->perf_tune.min_core_clk = 0;
+		perf->perf_tune.min_bus_vote = 0;
+	}
+	return count;
+}
+
+static ssize_t _sde_core_perf_mode_read(struct file *file,
+			char __user *buff, size_t count, loff_t *ppos)
+{
+	struct sde_core_perf *perf = file->private_data;
+	int len = 0;
+	char buf[40] = {'\0'};
+
+	if (!perf)
+		return -ENODEV;
+
+	if (*ppos)
+		return 0;	/* the end */
+
+	len = snprintf(buf, sizeof(buf), "min_mdp_clk %lu min_bus_vote %llu\n",
+			perf->perf_tune.min_core_clk,
+			perf->perf_tune.min_bus_vote);
+	if (len < 0 || len >= sizeof(buf))
+		return 0;
+
+	if ((count < sizeof(buf)) || copy_to_user(buff, buf, len))
+		return -EFAULT;
+
+	*ppos += len;   /* increase offset */
+
+	return len;
+}
+
+static const struct file_operations sde_core_perf_mode_fops = {
+	.open = simple_open,
+	.read = _sde_core_perf_mode_read,
+	.write = _sde_core_perf_mode_write,
+};
+
 static void sde_debugfs_core_perf_destroy(struct sde_core_perf *perf)
 {
 	debugfs_remove_recursive(perf->debugfs_root);
@@ -44,6 +505,7 @@
 static int sde_debugfs_core_perf_init(struct sde_core_perf *perf,
 		struct dentry *parent)
 {
+	struct sde_mdss_cfg *catalog = perf->catalog;
 	struct msm_drm_private *priv;
 	struct sde_kms *sde_kms;
 
@@ -65,6 +527,14 @@
 			&perf->max_core_clk_rate);
 	debugfs_create_u32("core_clk_rate", 0644, perf->debugfs_root,
 			&perf->core_clk_rate);
+	debugfs_create_u32("enable_bw_release", 0644, perf->debugfs_root,
+			(u32 *)&perf->enable_bw_release);
+	debugfs_create_u32("threshold_low", 0644, perf->debugfs_root,
+			(u32 *)&catalog->perf.max_bw_low);
+	debugfs_create_u32("threshold_high", 0644, perf->debugfs_root,
+			(u32 *)&catalog->perf.max_bw_high);
+	debugfs_create_file("perf_mode", 0644, perf->debugfs_root,
+			(u32 *)perf, &sde_core_perf_mode_fops);
 
 	return 0;
 }
diff --git a/drivers/gpu/drm/msm/sde/sde_core_perf.h b/drivers/gpu/drm/msm/sde/sde_core_perf.h
index 172deb5..e5dd9b6 100644
--- a/drivers/gpu/drm/msm/sde/sde_core_perf.h
+++ b/drivers/gpu/drm/msm/sde/sde_core_perf.h
@@ -16,11 +16,34 @@
 #include <linux/types.h>
 #include <linux/dcache.h>
 #include <linux/mutex.h>
+#include <drm/drm_crtc.h>
 
 #include "sde_hw_catalog.h"
 #include "sde_power_handle.h"
 
 /**
+ * struct sde_core_perf_params - definition of performance parameters
+ * @max_per_pipe_ib: maximum instantaneous bandwidth request
+ * @bw_ctl: arbitrated bandwidth request
+ * @core_clk_rate: core clock rate request
+ */
+struct sde_core_perf_params {
+	u64 max_per_pipe_ib;
+	u64 bw_ctl;
+	u32 core_clk_rate;
+};
+
+/**
+ * struct sde_core_perf_tune - definition of performance tuning control
+ * @min_core_clk: minimum core clock
+ * @min_bus_vote: minimum bus vote
+ */
+struct sde_core_perf_tune {
+	unsigned long min_core_clk;
+	u64 min_bus_vote;
+};
+
+/**
  * struct sde_core_perf - definition of core performance context
  * @dev: Pointer to drm device
  * @debugfs_root: top level debug folder
@@ -32,6 +55,8 @@
  * @core_clk: Pointer to core clock structure
  * @core_clk_rate: current core clock rate
  * @max_core_clk_rate: maximum allowable core clock rate
+ * @perf_tune: debug control for performance tuning
+ * @enable_bw_release: debug control for bandwidth release
  */
 struct sde_core_perf {
 	struct drm_device *dev;
@@ -44,9 +69,35 @@
 	struct clk *core_clk;
 	u32 core_clk_rate;
 	u64 max_core_clk_rate;
+	struct sde_core_perf_tune perf_tune;
+	u32 enable_bw_release;
 };
 
 /**
+ * sde_core_perf_crtc_check - validate performance of the given crtc state
+ * @crtc: Pointer to crtc
+ * @state: Pointer to new crtc state
+ * return: zero if success, or error code otherwise
+ */
+int sde_core_perf_crtc_check(struct drm_crtc *crtc,
+		struct drm_crtc_state *state);
+
+/**
+ * sde_core_perf_crtc_update - update performance of the given crtc
+ * @crtc: Pointer to crtc
+ * @params_changed: true if crtc parameters are modified
+ * @stop_req: true if this is a stop request
+ */
+void sde_core_perf_crtc_update(struct drm_crtc *crtc,
+		int params_changed, bool stop_req);
+
+/**
+ * sde_core_perf_crtc_release_bw - release bandwidth of the given crtc
+ * @crtc: Pointer to crtc
+ */
+void sde_core_perf_crtc_release_bw(struct drm_crtc *crtc);
+
+/**
  * sde_core_perf_destroy - destroy the given core performance context
  * @perf: Pointer to core performance context
  */
diff --git a/drivers/gpu/drm/msm/sde/sde_crtc.c b/drivers/gpu/drm/msm/sde/sde_crtc.c
index aabee22..b85f200 100644
--- a/drivers/gpu/drm/msm/sde/sde_crtc.c
+++ b/drivers/gpu/drm/msm/sde/sde_crtc.c
@@ -35,6 +35,7 @@
 #include "sde_encoder.h"
 #include "sde_connector.h"
 #include "sde_power_handle.h"
+#include "sde_core_perf.h"
 
 /* default input fence timeout, in ms */
 #define SDE_CRTC_INPUT_FENCE_TIMEOUT    2000
@@ -542,6 +543,7 @@
 			SDE_EVT32(DRMID(crtc), fevent->event, 1);
 			sde_power_data_bus_bandwidth_ctrl(&priv->phandle,
 					sde_kms->core_client, false);
+			sde_core_perf_crtc_release_bw(crtc);
 		} else {
 			SDE_EVT32(DRMID(crtc), fevent->event, 2);
 		}
@@ -918,6 +920,9 @@
 	/* wait for acquire fences before anything else is done */
 	_sde_crtc_wait_for_fences(crtc);
 
+	/* update performance setting before crtc kickoff */
+	sde_core_perf_crtc_update(crtc, 1, false);
+
 	/*
 	 * Final plane updates: Give each plane a chance to complete all
 	 *                      required writes/flushing before crtc's "flush
@@ -1123,9 +1128,12 @@
 		SDE_EVT32(DRMID(crtc));
 		sde_power_data_bus_bandwidth_ctrl(&priv->phandle,
 				sde_kms->core_client, false);
+		sde_core_perf_crtc_release_bw(crtc);
 		atomic_set(&sde_crtc->frame_pending, 0);
 	}
 
+	sde_core_perf_crtc_update(crtc, 0, true);
+
 	drm_for_each_encoder(encoder, crtc->dev) {
 		if (encoder->crtc != crtc)
 			continue;
@@ -1382,10 +1390,17 @@
 					multirect_plane[i].r0->plane->base.id,
 					multirect_plane[i].r1->plane->base.id);
 			rc = -EINVAL;
-			break;
+			goto end;
 		}
 	}
 
+	rc = sde_core_perf_crtc_check(crtc, state);
+	if (rc) {
+		SDE_ERROR("crtc%d failed performance check %d\n",
+				crtc->base.id, rc);
+		goto end;
+	}
+
 end:
 	return rc;
 }
@@ -1446,6 +1461,7 @@
 	struct sde_crtc *sde_crtc;
 	struct drm_device *dev;
 	struct sde_kms_info *info;
+	struct sde_kms *sde_kms;
 
 	SDE_DEBUG("\n");
 
@@ -1456,6 +1472,7 @@
 
 	sde_crtc = to_sde_crtc(crtc);
 	dev = crtc->dev;
+	sde_kms = _sde_crtc_get_kms(crtc);
 
 	info = kzalloc(sizeof(struct sde_kms_info), GFP_KERNEL);
 	if (!info) {
@@ -1475,6 +1492,19 @@
 			"output_fence_offset", 0x0, 0, 1, 0,
 			CRTC_PROP_OUTPUT_FENCE_OFFSET);
 
+	msm_property_install_range(&sde_crtc->property_info,
+			"core_clk", 0x0, 0, U64_MAX,
+			sde_kms->perf.max_core_clk_rate,
+			CRTC_PROP_CORE_CLK);
+	msm_property_install_range(&sde_crtc->property_info,
+			"core_ab", 0x0, 0, U64_MAX,
+			SDE_POWER_HANDLE_DATA_BUS_AB_QUOTA,
+			CRTC_PROP_CORE_AB);
+	msm_property_install_range(&sde_crtc->property_info,
+			"core_ib", 0x0, 0, U64_MAX,
+			SDE_POWER_HANDLE_DATA_BUS_IB_QUOTA,
+			CRTC_PROP_CORE_IB);
+
 	msm_property_install_blob(&sde_crtc->property_info, "capabilities",
 		DRM_MODE_PROP_IMMUTABLE, CRTC_PROP_INFO);
 
@@ -1784,6 +1814,10 @@
 	seq_printf(s, "num_connectors: %d\n", cstate->num_connectors);
 	seq_printf(s, "is_rt: %d\n", cstate->is_rt);
 	seq_printf(s, "intf_mode: %d\n", cstate->intf_mode);
+	seq_printf(s, "bw_ctl: %llu\n", cstate->cur_perf.bw_ctl);
+	seq_printf(s, "core_clk_rate: %u\n", cstate->cur_perf.core_clk_rate);
+	seq_printf(s, "max_per_pipe_ib: %llu\n",
+			cstate->cur_perf.max_per_pipe_ib);
 
 	return 0;
 }
@@ -1807,6 +1841,10 @@
 			debugfs_create_file("status", 0444,
 					sde_crtc->debugfs_root,
 					sde_crtc, &debugfs_status_fops);
+			debugfs_create_file("state", 0644,
+					sde_crtc->debugfs_root,
+					&sde_crtc->base,
+					&sde_crtc_debugfs_state_fops);
 		}
 	}
 }
diff --git a/drivers/gpu/drm/msm/sde/sde_crtc.h b/drivers/gpu/drm/msm/sde/sde_crtc.h
index 47ecbf5..91fdaed 100644
--- a/drivers/gpu/drm/msm/sde/sde_crtc.h
+++ b/drivers/gpu/drm/msm/sde/sde_crtc.h
@@ -23,6 +23,7 @@
 #include "msm_prop.h"
 #include "sde_fence.h"
 #include "sde_kms.h"
+#include "sde_core_perf.h"
 
 #define SDE_CRTC_NAME_SIZE	12
 
@@ -142,6 +143,8 @@
  * @property_blobs: Reference pointers for blob properties
  * @num_dim_layers: Number of dim layers
  * @dim_layer: Dim layer configs
+ * @cur_perf: current performance state
+ * @new_perf: new performance state
  */
 struct sde_crtc_state {
 	struct drm_crtc_state base;
@@ -156,6 +159,9 @@
 	struct drm_property_blob *property_blobs[CRTC_PROP_COUNT];
 	uint32_t num_dim_layers;
 	struct sde_hw_dim_layer dim_layer[SDE_MAX_DIM_LAYERS];
+
+	struct sde_core_perf_params cur_perf;
+	struct sde_core_perf_params new_perf;
 };
 
 #define to_sde_crtc_state(x) \
@@ -255,4 +261,46 @@
  */
 bool sde_crtc_is_rt(struct drm_crtc *crtc);
 
+/**
+ * sde_crtc_get_intf_mode - get interface mode of the given crtc
+ * @crtc: Pointert to crtc
+ */
+static inline enum sde_intf_mode sde_crtc_get_intf_mode(struct drm_crtc *crtc)
+{
+	struct sde_crtc_state *cstate =
+			crtc ? to_sde_crtc_state(crtc->state) : NULL;
+
+	return cstate ? cstate->intf_mode : INTF_MODE_NONE;
+}
+
+/**
+ * sde_core_perf_crtc_is_wb - check if writeback is primary output of this crtc
+ * @crtc: Pointer to crtc
+ */
+static inline bool sde_crtc_is_wb(struct drm_crtc *crtc)
+{
+	struct sde_crtc_state *cstate =
+			crtc ? to_sde_crtc_state(crtc->state) : NULL;
+
+	return cstate ? (cstate->intf_mode == INTF_MODE_WB_LINE) : false;
+}
+
+/**
+ * sde_crtc_is_nrt - check if primary output of this crtc is non-realtime client
+ * @crtc: Pointer to crtc
+ */
+static inline bool sde_crtc_is_nrt(struct drm_crtc *crtc)
+{
+	return sde_crtc_is_wb(crtc);
+}
+
+/**
+ * sde_crtc_is_enabled - check if sde crtc is enabled or not
+ * @crtc: Pointer to crtc
+ */
+static inline bool sde_crtc_is_enabled(struct drm_crtc *crtc)
+{
+	return crtc ? crtc->enabled : false;
+}
+
 #endif /* _SDE_CRTC_H_ */
diff --git a/drivers/gpu/drm/msm/sde/sde_hw_catalog.c b/drivers/gpu/drm/msm/sde/sde_hw_catalog.c
index c53a373..9a1994c 100644
--- a/drivers/gpu/drm/msm/sde/sde_hw_catalog.c
+++ b/drivers/gpu/drm/msm/sde/sde_hw_catalog.c
@@ -108,6 +108,12 @@
 };
 
 enum {
+	PERF_MAX_BW_LOW,
+	PERF_MAX_BW_HIGH,
+	PERF_PROP_MAX,
+};
+
+enum {
 	SSPP_OFF,
 	SSPP_SIZE,
 	SSPP_TYPE,
@@ -284,6 +290,11 @@
 	{SMART_DMA_REV, "qcom,sde-smart-dma-rev", false, PROP_TYPE_STRING},
 };
 
+static struct sde_prop_type sde_perf_prop[] = {
+	{PERF_MAX_BW_LOW, "qcom,sde-max-bw-low-kbps", false, PROP_TYPE_U32},
+	{PERF_MAX_BW_HIGH, "qcom,sde-max-bw-high-kbps", false, PROP_TYPE_U32},
+};
+
 static struct sde_prop_type sspp_prop[] = {
 	{SSPP_OFF, "qcom,sde-sspp-off", true, PROP_TYPE_U32_ARRAY},
 	{SSPP_SIZE, "qcom,sde-sspp-src-size", false, PROP_TYPE_U32},
@@ -1959,6 +1970,46 @@
 	return 0;
 }
 
+static int sde_perf_parse_dt(struct device_node *np, struct sde_mdss_cfg *cfg)
+{
+	int rc, len, prop_count[PERF_PROP_MAX];
+	struct sde_prop_value *prop_value = NULL;
+	bool prop_exists[PERF_PROP_MAX];
+
+	if (!cfg) {
+		SDE_ERROR("invalid argument\n");
+		rc = -EINVAL;
+		goto end;
+	}
+
+	prop_value = kzalloc(SDE_PROP_MAX *
+			sizeof(struct sde_prop_value), GFP_KERNEL);
+	if (!prop_value) {
+		rc = -ENOMEM;
+		goto end;
+	}
+
+	rc = _validate_dt_entry(np, sde_perf_prop, ARRAY_SIZE(sde_perf_prop),
+			prop_count, &len);
+	if (rc)
+		goto freeprop;
+
+	rc = _read_dt_entry(np, sde_perf_prop, ARRAY_SIZE(sde_perf_prop),
+			prop_count, prop_exists, prop_value);
+	if (rc)
+		goto freeprop;
+
+	cfg->perf.max_bw_low =
+			PROP_VALUE_ACCESS(prop_value, PERF_MAX_BW_LOW, 0);
+	cfg->perf.max_bw_high =
+			PROP_VALUE_ACCESS(prop_value, PERF_MAX_BW_HIGH, 0);
+
+freeprop:
+	kfree(prop_value);
+end:
+	return rc;
+}
+
 static void sde_hardware_caps(struct sde_mdss_cfg *sde_cfg, uint32_t hw_rev)
 {
 	switch (hw_rev) {
@@ -2064,6 +2115,10 @@
 	if (rc)
 		goto end;
 
+	rc = sde_perf_parse_dt(np, sde_cfg);
+	if (rc)
+		goto end;
+
 	sde_hardware_caps(sde_cfg, hw_rev);
 
 	return sde_cfg;
diff --git a/drivers/gpu/drm/msm/sde/sde_hw_catalog.h b/drivers/gpu/drm/msm/sde/sde_hw_catalog.h
index 6bd42b8..7c073ba 100644
--- a/drivers/gpu/drm/msm/sde/sde_hw_catalog.h
+++ b/drivers/gpu/drm/msm/sde/sde_hw_catalog.h
@@ -621,6 +621,16 @@
 };
 
 /**
+ * struct sde_perf_cfg - performance control settings
+ * @max_bw_low         low threshold of maximum bandwidth (kbps)
+ * @max_bw_high        high threshold of maximum bandwidth (kbps)
+ */
+struct sde_perf_cfg {
+	u32 max_bw_low;
+	u32 max_bw_high;
+};
+
+/**
  * struct sde_mdss_cfg - information of MDSS HW
  * This is the main catalog data structure representing
  * this HW version. Contains number of instances,
@@ -689,6 +699,8 @@
 	u32 reg_dma_count;
 	struct sde_reg_dma_cfg dma_cfg;
 	/* Add additional block data structures here */
+
+	struct sde_perf_cfg perf;
 };
 
 struct sde_mdss_hw_cfg_handler {
diff --git a/drivers/gpu/drm/msm/sde/sde_trace.h b/drivers/gpu/drm/msm/sde/sde_trace.h
index 26e3afa..2a4e6b5 100644
--- a/drivers/gpu/drm/msm/sde/sde_trace.h
+++ b/drivers/gpu/drm/msm/sde/sde_trace.h
@@ -113,6 +113,18 @@
 )
 
 
+TRACE_EVENT(sde_cmd_release_bw,
+	TP_PROTO(u32 crtc_id),
+	TP_ARGS(crtc_id),
+	TP_STRUCT__entry(
+			__field(u32, crtc_id)
+	),
+	TP_fast_assign(
+			__entry->crtc_id = crtc_id;
+	),
+	TP_printk("crtc:%d", __entry->crtc_id)
+);
+
 TRACE_EVENT(sde_mark_write,
 	TP_PROTO(int pid, const char *name, bool trace_begin),
 	TP_ARGS(pid, name, trace_begin),