Merge branch 'LA.UM.9.12.C10.11.00.00.840.478' via branch 'qcom-msm-4.19-7250' into android-msm-pixel-4.19

Bug: 261541074
Change-Id: I92c4233b786e0681ee6e236334481486e5563637
Signed-off-by: JohnnLee <johnnlee@google.com>
diff --git a/Makefile b/Makefile
index b282962..0f921da 100644
--- a/Makefile
+++ b/Makefile
@@ -1,20 +1,26 @@
 # SPDX-License-Identifier: GPL-2.0-only
 
 # auto-detect subdirs
-ifeq ($(CONFIG_ARCH_KONA), y)
-include $(srctree)/techpack/display/config/konadisp.conf
+ifeq ($(CONFIG_DRM_MSM:M=m), m)
+SUFFIX_DRM_MSM:=-gki
+else
+SUFFIX_DRM_MSM:=
 endif
 
 ifeq ($(CONFIG_ARCH_KONA), y)
-LINUXINCLUDE    += -include $(srctree)/techpack/display/config/konadispconf.h
+include $(srctree)/techpack/display/config/konadisp$(SUFFIX_DRM_MSM).conf
+endif
+
+ifeq ($(CONFIG_ARCH_KONA), y)
+LINUXINCLUDE    += -include $(srctree)/techpack/display/config/konadispconf$(SUFFIX_DRM_MSM).h
 endif
 
 ifeq ($(CONFIG_ARCH_LITO), y)
-include $(srctree)/techpack/display/config/saipdisp.conf
+include $(srctree)/techpack/display/config/saipdisp$(SUFFIX_DRM_MSM).conf
 endif
 
 ifeq ($(CONFIG_ARCH_LITO), y)
-LINUXINCLUDE    += -include $(srctree)/techpack/display/config/saipdispconf.h
+LINUXINCLUDE    += -include $(srctree)/techpack/display/config/saipdispconf$(SUFFIX_DRM_MSM).h
 endif
 
 ifeq ($(CONFIG_ARCH_BENGAL), y)
@@ -27,4 +33,3 @@
 
 obj-$(CONFIG_DRM_MSM) += msm/
 obj-$(CONFIG_MSM_SDE_ROTATOR) += rotator/
-obj-$(CONFIG_QCOM_MDSS_PLL) += pll/
diff --git a/config/konadisp-gki.conf b/config/konadisp-gki.conf
new file mode 100644
index 0000000..7e69785
--- /dev/null
+++ b/config/konadisp-gki.conf
@@ -0,0 +1,11 @@
+export CONFIG_DRM_MSM=m
+export CONFIG_DRM_MSM_SDE=y
+export CONFIG_SYNC_FILE=y
+export CONFIG_DRM_MSM_DSI=y
+export CONFIG_DRM_MSM_DP=y
+export CONFIG_QCOM_MDSS_DP_PLL=y
+export CONFIG_DSI_PARSER=y
+export CONFIG_DRM_SDE_WB=y
+export CONFIG_DRM_MSM_REGISTER_LOGGING=y
+export CONFIG_QCOM_MDSS_PLL=y
+export CONFIG_DRM_SDE_RSC=y
diff --git a/config/konadisp.conf b/config/konadisp.conf
index dbbf3c8..48bdc0b 100644
--- a/config/konadisp.conf
+++ b/config/konadisp.conf
@@ -1,4 +1,4 @@
-export CONFIG_DRM_MSM=y
+export CONFIG_DRM_MSM=m
 export CONFIG_DRM_MSM_SDE=y
 export CONFIG_SYNC_FILE=y
 export CONFIG_DRM_MSM_DSI=y
@@ -8,6 +8,5 @@
 export CONFIG_DRM_SDE_WB=y
 export CONFIG_DRM_MSM_REGISTER_LOGGING=y
 export CONFIG_QCOM_MDSS_PLL=y
-export CONFIG_MSM_SDE_ROTATOR=y
 export CONFIG_MSM_SDE_ROTATOR_EVTLOG_DEBUG=y
 export CONFIG_DRM_SDE_RSC=y
diff --git a/config/konadispconf-gki.h b/config/konadispconf-gki.h
new file mode 100644
index 0000000..e8f68a2
--- /dev/null
+++ b/config/konadispconf-gki.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2019, The Linux Foundation. All rights reserved.
+ */
+
+#define CONFIG_DRM_MSM_MODULE 1
+#define CONFIG_DRM_MSM_SDE 1
+#define CONFIG_SYNC_FILE 1
+#define CONFIG_DRM_MSM_DSI 1
+#define CONFIG_DRM_MSM_DP 1
+#define CONFIG_QCOM_MDSS_DP_PLL 1
+#define CONFIG_DSI_PARSER 1
+#define CONFIG_DRM_SDE_WB 1
+#define CONFIG_DRM_MSM_REGISTER_LOGGING 1
+#define CONFIG_DRM_SDE_EVTLOG_DEBUG 1
+#define CONFIG_QCOM_MDSS_PLL 1
+#define CONFIG_DRM_SDE_RSC 1
+
diff --git a/config/konadispconf.h b/config/konadispconf.h
index 690d4ec..a3ccfc2 100644
--- a/config/konadispconf.h
+++ b/config/konadispconf.h
@@ -3,7 +3,7 @@
  * Copyright (c) 2019, The Linux Foundation. All rights reserved.
  */
 
-#define CONFIG_DRM_MSM 1
+#define CONFIG_DRM_MSM_MODULE 1
 #define CONFIG_DRM_MSM_SDE 1
 #define CONFIG_SYNC_FILE 1
 #define CONFIG_DRM_MSM_DSI 1
diff --git a/config/saipdisp-gki.conf b/config/saipdisp-gki.conf
new file mode 100644
index 0000000..7e69785
--- /dev/null
+++ b/config/saipdisp-gki.conf
@@ -0,0 +1,11 @@
+export CONFIG_DRM_MSM=m
+export CONFIG_DRM_MSM_SDE=y
+export CONFIG_SYNC_FILE=y
+export CONFIG_DRM_MSM_DSI=y
+export CONFIG_DRM_MSM_DP=y
+export CONFIG_QCOM_MDSS_DP_PLL=y
+export CONFIG_DSI_PARSER=y
+export CONFIG_DRM_SDE_WB=y
+export CONFIG_DRM_MSM_REGISTER_LOGGING=y
+export CONFIG_QCOM_MDSS_PLL=y
+export CONFIG_DRM_SDE_RSC=y
diff --git a/config/saipdispconf-gki.h b/config/saipdispconf-gki.h
new file mode 100644
index 0000000..d91b4fb
--- /dev/null
+++ b/config/saipdispconf-gki.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2019, The Linux Foundation. All rights reserved.
+ */
+
+#define CONFIG_DRM_MSM_MODULE 1
+#define CONFIG_DRM_MSM_SDE 1
+#define CONFIG_SYNC_FILE 1
+#define CONFIG_DRM_MSM_DSI 1
+#define CONFIG_DRM_MSM_DP 1
+#define CONFIG_QCOM_MDSS_DP_PLL 1
+#define CONFIG_DSI_PARSER 1
+#define CONFIG_DRM_SDE_WB 1
+#define CONFIG_DRM_MSM_REGISTER_LOGGING 1
+#define CONFIG_DRM_SDE_EVTLOG_DEBUG 1
+#define CONFIG_QCOM_MDSS_PLL 1
+#define CONFIG_DRM_SDE_RSC 1
diff --git a/msm/Makefile b/msm/Makefile
index 18eb12c..5791224 100644
--- a/msm/Makefile
+++ b/msm/Makefile
@@ -3,6 +3,16 @@
 ccflags-y += -I$(srctree)/techpack/display/msm/sde
 ccflags-y += -I$(srctree)/techpack/display/rotator
 
+ccflags-y += -I$(srctree)/techpack/display/pll
+ccflags-y += -I$(srctree)/drivers/clk/qcom/
+
+msm_drm-$(CONFIG_QCOM_MDSS_PLL) += $(patsubst %,../pll/%,pll_util.o pll_drv.o dsi_pll_10nm.o \
+	dsi_pll_7nm.o dsi_pll_28lpm.o dsi_pll_28nm_util.o dsi_pll_14nm.o dsi_pll_14nm_util.o \
+	hdmi_pll_28lpm.o)
+
+msm_drm-$(CONFIG_QCOM_MDSS_DP_PLL) += $(patsubst %,../pll/%,dp_pll_7nm.o dp_pll_7nm_util.o \
+	dp_pll_10nm.o dp_pll_10nm_util.o dp_pll_14nm.o)
+
 msm_drm-$(CONFIG_DRM_MSM_DP) += dp/dp_usbpd.o \
 	dp/dp_parser.o \
 	dp/dp_power.o \
@@ -98,9 +108,12 @@
 	dsi/dsi_drm.o \
 	dsi/dsi_display.o \
 	dsi/dsi_panel.o \
+	dsi/dsi_backlight.o \
 	dsi/dsi_clk_manager.o \
 	dsi/dsi_display_test.o \
 
+msm_drm-$(CONFIG_DRM_MSM_DSI) += dsi/dsi_panel_switch.o
+
 msm_drm-$(CONFIG_DSI_PARSER) += dsi/dsi_parser.o \
 
 msm_drm-$(CONFIG_DRM_MSM) += \
diff --git a/msm/dp/dp_audio.c b/msm/dp/dp_audio.c
index 90c3ad1..778b89d 100644
--- a/msm/dp/dp_audio.c
+++ b/msm/dp/dp_audio.c
@@ -602,7 +602,7 @@
 		rc = -ENODEV;
 		goto end;
 	}
-#if defined(CONFIG_MSM_EXT_DISPLAY)
+#if IS_ENABLED(CONFIG_MSM_EXT_DISPLAY)
 	rc = msm_ext_disp_register_intf(audio->ext_pdev, ext);
 	if (rc)
 		DP_ERR("failed to register disp\n");
@@ -643,7 +643,7 @@
 		goto end;
 	}
 
-#if defined(CONFIG_MSM_EXT_DISPLAY)
+#if IS_ENABLED(CONFIG_MSM_EXT_DISPLAY)
 	rc = msm_ext_disp_deregister_intf(audio->ext_pdev, ext);
 	if (rc)
 		DP_ERR("failed to deregister disp\n");
diff --git a/msm/dp/dp_aux.c b/msm/dp/dp_aux.c
index 4a1f54e..b9ae738 100644
--- a/msm/dp/dp_aux.c
+++ b/msm/dp/dp_aux.c
@@ -1,6 +1,5 @@
 // SPDX-License-Identifier: GPL-2.0-only
 /*
- * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
  * Copyright (c) 2012-2019, The Linux Foundation. All rights reserved.
  */
 
@@ -772,16 +771,11 @@
 	aux = container_of(dp_aux, struct dp_aux_private, dp_aux);
 
 	if (!aux->aux_switch_node) {
-		DP_DEBUG("undefined aux switch handle\n");
+		DP_DEBUG("undefined fsa4480 handle\n");
 		rc = -EINVAL;
 		goto end;
 	}
 
-	if (strcmp(aux->aux_switch_node->name, "fsa4480")) {
-		DP_DEBUG("Not an fsa4480 aux switch\n");
-		goto end;
-	}
-
 	if (enable) {
 		switch (orientation) {
 		case ORIENTATION_CC1:
diff --git a/msm/dp/dp_debug.c b/msm/dp/dp_debug.c
index 0bccc7b..6303c1c 100644
--- a/msm/dp/dp_debug.c
+++ b/msm/dp/dp_debug.c
@@ -1,6 +1,5 @@
 // SPDX-License-Identifier: GPL-2.0-only
 /*
- * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
  * Copyright (c) 2017-2020, The Linux Foundation. All rights reserved.
  */
 
@@ -2285,7 +2284,6 @@
 	dp_debug->dp_mst_connector_list.con_id = -1;
 	dp_debug->dp_mst_connector_list.conn = NULL;
 	dp_debug->dp_mst_connector_list.debug_en = false;
-	mutex_init(&dp_debug->dp_mst_connector_list.lock);
 
 	dp_debug->max_pclk_khz = debug->parser->max_pclk_khz;
 
diff --git a/msm/dp/dp_display.c b/msm/dp/dp_display.c
index 26554a4..97c5c18 100644
--- a/msm/dp/dp_display.c
+++ b/msm/dp/dp_display.c
@@ -1,6 +1,5 @@
 // SPDX-License-Identifier: GPL-2.0-only
 /*
- * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
  * Copyright (c) 2017-2021, The Linux Foundation. All rights reserved.
  */
 
@@ -722,11 +721,6 @@
 	kobject_uevent_env(&dev->primary->kdev->kobj, KOBJ_CHANGE,
 			envp);
 
-	if (dev->mode_config.funcs->output_poll_changed)
-		dev->mode_config.funcs->output_poll_changed(dev);
-
-	drm_client_dev_hotplug(dev);
-
 	if (connector->status == connector_status_connected) {
 		dp_display_state_add(DP_STATE_CONNECT_NOTIFIED);
 		dp_display_state_remove(DP_STATE_DISCONNECT_NOTIFIED);
@@ -982,8 +976,6 @@
 
 	dp->dp_display.max_pclk_khz = min(dp->parser->max_pclk_khz,
 					dp->debug->max_pclk_khz);
-	dp->dp_display.max_hdisplay = dp->parser->max_hdisplay;
-	dp->dp_display.max_vdisplay = dp->parser->max_vdisplay;
 
 	/*
 	 * If dp video session is not restored from a previous session teardown
@@ -2324,162 +2316,23 @@
 	return 0;
 }
 
-static int dp_display_validate_resources(
-		struct dp_display *dp_display,
-		void *panel, struct drm_display_mode *mode,
-		const struct msm_resource_caps_info *avail_res)
-{
-	struct dp_display_private *dp;
-	struct dp_panel *dp_panel;
-	struct dp_debug *debug;
-	struct dp_display_mode dp_mode;
-	u32 mode_rate_khz, supported_rate_khz, mode_bpp, num_lm;
-	int rc, tmds_max_clock, rate;
-	bool dsc_en;
-
-	dp = container_of(dp_display, struct dp_display_private, dp_display);
-	dp_panel = panel;
-	debug = dp->debug;
-
-	dp_display->convert_to_dp_mode(dp_display, panel, mode, &dp_mode);
-
-	dsc_en = dp_mode.timing.comp_info.comp_ratio ? true : false;
-	mode_bpp = dsc_en ? dp_mode.timing.comp_info.dsc_info.bpp :
-			dp_mode.timing.bpp;
-
-	mode_rate_khz = mode->clock * mode_bpp;
-	rate = drm_dp_bw_code_to_link_rate(dp->link->link_params.bw_code);
-	supported_rate_khz = dp->link->link_params.lane_count * rate * 8;
-	tmds_max_clock = dp_panel->connector->display_info.max_tmds_clock;
-
-	if (mode_rate_khz > supported_rate_khz) {
-		DP_DEBUG("pclk:%d, supported_rate:%d\n",
-				mode->clock, supported_rate_khz);
-		return -EINVAL;
-	}
-
-	if (mode->clock > dp_display->max_pclk_khz) {
-		DP_DEBUG("clk:%d, max:%d\n", mode->clock,
-				dp_display->max_pclk_khz);
-		return -EINVAL;
-	}
-
-	if ((dp_display->max_hdisplay > 0) && (dp_display->max_vdisplay > 0) &&
-			((mode->hdisplay > dp_display->max_hdisplay) ||
-			(mode->vdisplay > dp_display->max_vdisplay))) {
-		DP_DEBUG("hdisplay:%d, max-hdisplay:%d",
-			mode->hdisplay, dp_display->max_hdisplay);
-		DP_DEBUG("vdisplay:%d, max-vdisplay:%d\n",
-			mode->vdisplay, dp_display->max_vdisplay);
-		return -EINVAL;
-	}
-
-	if (tmds_max_clock > 0 && mode->clock > tmds_max_clock) {
-		DP_DEBUG("clk:%d, max tmds:%d\n", mode->clock,
-				tmds_max_clock);
-		return -EINVAL;
-	}
-
-	rc = msm_get_mixer_count(dp->priv, mode, avail_res, &num_lm);
-	if (rc) {
-		DP_ERR("error getting mixer count. rc:%d\n", rc);
-		return -EINVAL;
-	}
-
-	if (num_lm > avail_res->num_lm ||
-			(num_lm == 2 && !avail_res->num_3dmux)) {
-		DP_DEBUG("num_lm:%d, req lm:%d 3dmux:%d\n", num_lm,
-				avail_res->num_lm, avail_res->num_3dmux);
-		return -EINVAL;
-	}
-
-	return 0;
-}
-
-static int dp_display_check_overrides(
-		struct dp_display *dp_display,
-		void *panel, struct drm_display_mode *mode,
-		const struct msm_resource_caps_info *avail_res)
-{
-	struct dp_mst_connector *mst_connector;
-	struct dp_display_private *dp;
-	struct dp_panel *dp_panel;
-	struct dp_debug *debug;
-	bool in_list = false;
-	int hdis, vdis, vref, ar, _hdis, _vdis, _vref, _ar;
-
-	dp = container_of(dp_display, struct dp_display_private, dp_display);
-	dp_panel = panel;
-	debug = dp->debug;
-
-	/*
-	 * If the connector exists in the mst connector list and if debug is
-	 * enabled for that connector, use the mst connector settings from the
-	 * list for validation. Otherwise, use non-mst default settings.
-	 */
-	mutex_lock(&debug->dp_mst_connector_list.lock);
-
-	if (list_empty(&debug->dp_mst_connector_list.list)) {
-		DP_MST_DEBUG("MST connect list is empty\n");
-		mutex_unlock(&debug->dp_mst_connector_list.lock);
-		goto verify_default;
-	}
-
-	list_for_each_entry(mst_connector, &debug->dp_mst_connector_list.list,
-			list) {
-		if (mst_connector->con_id == dp_panel->connector->base.id) {
-			in_list = true;
-
-			if (!mst_connector->debug_en) {
-				mutex_unlock(
-				&debug->dp_mst_connector_list.lock);
-				return 0;
-			}
-
-			hdis = mst_connector->hdisplay;
-			vdis = mst_connector->vdisplay;
-			vref = mst_connector->vrefresh;
-			ar = mst_connector->aspect_ratio;
-
-			_hdis = mode->hdisplay;
-			_vdis = mode->vdisplay;
-			_vref = mode->vrefresh;
-			_ar = mode->picture_aspect_ratio;
-
-			if (hdis == _hdis && vdis == _vdis && vref == _vref &&
-					ar == _ar) {
-				mutex_unlock(
-				&debug->dp_mst_connector_list.lock);
-				return 0;
-			}
-			break;
-		}
-	}
-
-	mutex_unlock(&debug->dp_mst_connector_list.lock);
-	if (in_list)
-		return -EINVAL;
-
-verify_default:
-	if (debug->debug_en && (mode->hdisplay != debug->hdisplay ||
-			mode->vdisplay != debug->vdisplay ||
-			mode->vrefresh != debug->vrefresh ||
-			mode->picture_aspect_ratio != debug->aspect_ratio))
-		return -EINVAL;
-
-	return 0;
-}
-
 static enum drm_mode_status dp_display_validate_mode(
 		struct dp_display *dp_display,
 		void *panel, struct drm_display_mode *mode,
 		const struct msm_resource_caps_info *avail_res)
 {
 	struct dp_display_private *dp;
-
+	u32 mode_rate_khz = 0, supported_rate_khz = 0, mode_bpp = 0;
 	struct dp_panel *dp_panel;
 	struct dp_debug *debug;
 	enum drm_mode_status mode_status = MODE_BAD;
+	bool in_list = false;
+	struct dp_mst_connector *mst_connector;
+	int hdis, vdis, vref, ar, _hdis, _vdis, _vref, _ar, rate;
+	struct dp_display_mode dp_mode;
+	bool dsc_en;
+	u32 num_lm = 0;
+	int rc = 0, tmds_max_clock = 0;
 
 	if (!dp_display || !mode || !panel ||
 			!avail_res || !avail_res->max_mixer_width) {
@@ -2498,26 +2351,109 @@
 	}
 
 	debug = dp->debug;
-	if (!debug) {
-		DP_ERR("invalid debug node\n");
+	if (!debug)
+		goto end;
+
+	dp_display->convert_to_dp_mode(dp_display, panel, mode, &dp_mode);
+
+	dsc_en = dp_mode.timing.comp_info.comp_ratio ? true : false;
+	mode_bpp = dsc_en ? dp_mode.timing.comp_info.dsc_info.bpp :
+			dp_mode.timing.bpp;
+
+	mode_rate_khz = mode->clock * mode_bpp;
+	rate = drm_dp_bw_code_to_link_rate(dp->link->link_params.bw_code);
+	supported_rate_khz = dp->link->link_params.lane_count * rate * 8;
+	tmds_max_clock = dp_panel->connector->display_info.max_tmds_clock;
+
+	if (mode_rate_khz > supported_rate_khz) {
+		DP_MST_DEBUG("pclk:%d, supported_rate:%d\n",
+				mode->clock, supported_rate_khz);
 		goto end;
 	}
 
-	if (dp_display_validate_resources(dp_display, panel, mode, avail_res)) {
-		DP_DEBUG("DP bad mode %dx%d@%d\n",
-			mode->hdisplay, mode->vdisplay, mode->clock);
+	if (mode->clock > dp_display->max_pclk_khz) {
+		DP_MST_DEBUG("clk:%d, max:%d\n", mode->clock,
+				dp_display->max_pclk_khz);
 		goto end;
 	}
 
-	if (dp_display_check_overrides(dp_display, panel,
-				mode, avail_res)) {
-		DP_MST_DEBUG("DP overrides ignore mode %dx%d@%d\n",
-			mode->hdisplay, mode->vdisplay, mode->clock);
+	if (tmds_max_clock > 0 && mode->clock > tmds_max_clock) {
+		DP_MST_DEBUG("clk:%d, max tmds:%d\n", mode->clock,
+				tmds_max_clock);
 		goto end;
 	}
 
-	DP_DEBUG("DP ok mode %dx%d@%d\n",
-			mode->hdisplay, mode->vdisplay, mode->clock);
+	rc = msm_get_mixer_count(dp->priv, mode, avail_res, &num_lm);
+	if (rc) {
+		DP_ERR("error getting mixer count. rc:%d\n", rc);
+		goto end;
+	}
+
+	if (num_lm > avail_res->num_lm ||
+			(num_lm == 2 && !avail_res->num_3dmux)) {
+		DP_MST_DEBUG("num_lm:%d, req lm:%d 3dmux:%d\n", num_lm,
+				avail_res->num_lm, avail_res->num_3dmux);
+		goto end;
+	}
+
+	/*
+	 * If the connector exists in the mst connector list and if debug is
+	 * enabled for that connector, use the mst connector settings from the
+	 * list for validation. Otherwise, use non-mst default settings.
+	 */
+	mutex_lock(&debug->dp_mst_connector_list.lock);
+
+	if (list_empty(&debug->dp_mst_connector_list.list)) {
+		mutex_unlock(&debug->dp_mst_connector_list.lock);
+		goto verify_default;
+	}
+
+	list_for_each_entry(mst_connector, &debug->dp_mst_connector_list.list,
+			list) {
+		if (mst_connector->con_id == dp_panel->connector->base.id) {
+			in_list = true;
+
+			if (!mst_connector->debug_en) {
+				mode_status = MODE_OK;
+				mutex_unlock(
+				&debug->dp_mst_connector_list.lock);
+				goto end;
+			}
+
+			hdis = mst_connector->hdisplay;
+			vdis = mst_connector->vdisplay;
+			vref = mst_connector->vrefresh;
+			ar = mst_connector->aspect_ratio;
+
+			_hdis = mode->hdisplay;
+			_vdis = mode->vdisplay;
+			_vref = mode->vrefresh;
+			_ar = mode->picture_aspect_ratio;
+
+			if (hdis == _hdis && vdis == _vdis && vref == _vref &&
+					ar == _ar) {
+				mode_status = MODE_OK;
+				mutex_unlock(
+				&debug->dp_mst_connector_list.lock);
+				goto end;
+			}
+
+			break;
+		}
+	}
+
+	mutex_unlock(&debug->dp_mst_connector_list.lock);
+
+	if (in_list)
+		goto end;
+
+verify_default:
+	if (debug->debug_en && (mode->hdisplay != debug->hdisplay ||
+			mode->vdisplay != debug->vdisplay ||
+			mode->vrefresh != debug->vrefresh ||
+			mode->picture_aspect_ratio != debug->aspect_ratio))
+		goto end;
+
 	mode_status = MODE_OK;
 end:
 	mutex_unlock(&dp->session_lock);
@@ -2720,11 +2656,6 @@
 		goto end;
 	}
 
-	if (strcmp(dp->aux_switch_node->name, "fsa4480")) {
-		DP_DEBUG("Not an fsa4480 aux switch\n");
-		goto end;
-	}
-
 	nb.notifier_call = dp_display_fsa4480_callback;
 	nb.priority = 0;
 
@@ -3362,7 +3293,7 @@
 	},
 };
 
-static int __init dp_display_init(void)
+int __init dp_display_init(void)
 {
 	int ret;
 
@@ -3374,10 +3305,14 @@
 
 	return ret;
 }
-late_initcall(dp_display_init);
 
-static void __exit dp_display_cleanup(void)
+
+void __exit dp_display_cleanup(void)
 {
 	platform_driver_unregister(&dp_display_driver);
 }
+
+#ifndef CONFIG_DRM_MSM_MODULE
+late_initcall(dp_display_init);
 module_exit(dp_display_cleanup);
+#endif
diff --git a/msm/dp/dp_display.h b/msm/dp/dp_display.h
index 0172889..3f4b8d9 100644
--- a/msm/dp/dp_display.h
+++ b/msm/dp/dp_display.h
@@ -1,6 +1,5 @@
 /* SPDX-License-Identifier: GPL-2.0-only */
 /*
- * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
  * Copyright (c) 2017-2021, The Linux Foundation. All rights reserved.
  */
 
@@ -71,8 +70,6 @@
 	bool is_sst_connected;
 	bool is_mst_supported;
 	u32 max_pclk_khz;
-	u32 max_hdisplay;
-	u32 max_vdisplay;
 	u32 no_mst_encoder;
 	void *dp_mst_prv_info;
 	bool is_primary;
diff --git a/msm/dp/dp_parser.c b/msm/dp/dp_parser.c
index e33f781..4c37aa2 100644
--- a/msm/dp/dp_parser.c
+++ b/msm/dp/dp_parser.c
@@ -1,6 +1,5 @@
 // SPDX-License-Identifier: GPL-2.0-only
 /*
- * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
  * Copyright (c) 2012-2021, The Linux Foundation. All rights reserved.
  */
 
@@ -171,16 +170,6 @@
 	if (!parser->display_type)
 		parser->display_type = "unknown";
 
-	rc = of_property_read_u32(of_node,
-		"qcom,max-hdisplay", &parser->max_hdisplay);
-	if (rc)
-		parser->max_hdisplay = 0;
-
-	rc = of_property_read_u32(of_node,
-		"qcom,max-vdisplay", &parser->max_vdisplay);
-	if (rc)
-		parser->max_vdisplay = 0;
-
 	return 0;
 }
 
diff --git a/msm/dp/dp_parser.h b/msm/dp/dp_parser.h
index 61cbe38..5d937ca 100644
--- a/msm/dp/dp_parser.h
+++ b/msm/dp/dp_parser.h
@@ -1,6 +1,5 @@
 /* SPDX-License-Identifier: GPL-2.0-only */
 /*
- * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
  * Copyright (c) 2012-2021, The Linux Foundation. All rights reserved.
  */
 
@@ -187,8 +186,6 @@
  * @l_pnswap: P/N swap status on each lane
  * @max_pclk_khz: maximum pixel clock supported for the platform
  * @max_lclk_khz: maximum link clock supported for the platform
- * @max_hdisplay: maximum supported horizontal display by the platform for dp
- * @max_vdisplay: maximum supported vertical display by the platform for dp
  * @hw_cfg: DP HW specific settings
  * @has_mst: MST feature enable status
  * @has_mst_sideband: MST sideband feature enable status
@@ -220,8 +217,6 @@
 	struct dp_aux_cfg aux_cfg[AUX_CFG_LEN];
 	u32 max_pclk_khz;
 	u32 max_lclk_khz;
-	u32 max_hdisplay;
-	u32 max_vdisplay;
 	struct dp_hw_cfg hw_cfg;
 	bool has_mst;
 	bool has_mst_sideband;
diff --git a/msm/dsi/dsi_backlight.c b/msm/dsi/dsi_backlight.c
new file mode 100644
index 0000000..e4cb20c
--- /dev/null
+++ b/msm/dsi/dsi_backlight.c
@@ -0,0 +1,2217 @@
+/*
+ * Copyright (c) 2017, 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:%d: " fmt, __func__, __LINE__
+#include <linux/backlight.h>
+#include <linux/debugfs.h>
+#include <linux/of_gpio.h>
+#include <linux/pwm.h>
+#include <linux/sysfs.h>
+#include <linux/list.h>
+#include <linux/list_sort.h>
+#include <video/mipi_display.h>
+
+#include <dsi_drm.h>
+#include <sde_crtc.h>
+#include <sde_encoder.h>
+
+#include "dsi_display.h"
+#include "dsi_panel.h"
+#include "sde_connector.h"
+
+#define BL_NODE_NAME_SIZE 32
+#define BL_BRIGHTNESS_BUF_SIZE 2
+
+struct dsi_backlight_pwm_config {
+	struct pwm_device *pwm_bl;
+	bool pwm_enabled;
+	u32 pwm_period_usecs;
+};
+
+static void dsi_panel_bl_hbm_free(struct device *dev,
+	struct dsi_backlight_config *bl);
+
+static void dsi_panel_bl_notifier_free(struct device *dev,
+	struct dsi_backlight_config *bl);
+
+static int dsi_panel_bl_find_range(struct dsi_backlight_config *bl,
+		int brightness, u32 *range);
+
+static inline bool is_lp_mode(unsigned long state)
+{
+	return (state & (BL_STATE_LP | BL_STATE_LP2)) != 0;
+}
+
+static inline bool is_on_mode(unsigned long state)
+{
+	return (!is_lp_mode(state) && !is_standby_mode(state));
+}
+
+static inline unsigned int regulator_mode_from_state(unsigned long state)
+{
+	if (is_standby_mode(state))
+		return REGULATOR_MODE_STANDBY;
+	else if (is_lp_mode(state))
+		return REGULATOR_MODE_IDLE;
+	else
+		return REGULATOR_MODE_NORMAL;
+}
+
+static int dsi_panel_pwm_bl_register(struct dsi_backlight_config *bl);
+
+static void dsi_panel_bl_free_unregister(struct dsi_backlight_config *bl)
+{
+	kfree(bl->priv);
+}
+
+static int dsi_backlight_update_dcs(struct dsi_backlight_config *bl, u32 bl_lvl)
+{
+	int rc = 0;
+	struct dsi_panel *panel;
+	struct mipi_dsi_device *dsi;
+	size_t num_params;
+	const u32 hbyte = bl->high_byte_offset;
+
+	if (!bl || (bl_lvl > 0xffff)) {
+		pr_err("invalid params\n");
+		return -EINVAL;
+	}
+
+	panel = container_of(bl, struct dsi_panel, bl_config);
+	/* if no change in backlight, abort */
+	if (bl_lvl == bl->bl_actual)
+		return 0;
+
+	dsi = &panel->mipi_device;
+
+	num_params = bl->bl_max_level >= BIT(hbyte) ? 2 : 1;
+	if (num_params == 2) {
+		u8 payload[2] = { bl_lvl >> hbyte, (BIT(hbyte) - 1) & bl_lvl };
+
+		rc = mipi_dsi_dcs_write(dsi, MIPI_DCS_SET_DISPLAY_BRIGHTNESS,
+					&payload, sizeof(payload));
+	} else {
+		u8 payload = bl_lvl;
+
+		rc = mipi_dsi_dcs_write(dsi, MIPI_DCS_SET_DISPLAY_BRIGHTNESS,
+					&payload, sizeof(payload));
+	}
+
+	if (rc < 0)
+		pr_err("failed to update dcs backlight:%d\n", bl_lvl);
+
+	return rc;
+}
+
+/* Linearly interpolate value x from range [x1, x2] to determine the
+ * corresponding value in range [y1, y2].
+ */
+static int dsi_backlight_lerp(u16 x1, u16 x2, u16 y1, u16 y2, u16 x, u32 *y)
+{
+	if ((x2 < x1) || (y2 < y1))
+		return -EINVAL;
+
+	if (((x2 - x1) == 0) || (x <= x1))
+		*y = y1;
+	else if (x >= x2)
+		*y = y2;
+	else
+		*y = DIV_ROUND_CLOSEST((x - x1) * (y2 - y1), x2 - x1) + y1;
+
+	return 0;
+}
+
+static u32 dsi_backlight_calculate_normal(struct dsi_backlight_config *bl,
+		int brightness)
+{
+	u32 bl_lvl = 0;
+	int rc = 0;
+
+	if (bl->lut) {
+		/*
+		 * look up panel brightness; the first entry in the LUT
+		 corresponds to userspace brightness level 1
+		 */
+		if (WARN_ON(brightness > bl->brightness_max_level))
+			bl_lvl = bl->lut[bl->brightness_max_level];
+		else
+			bl_lvl = bl->lut[brightness];
+	} else {
+		/* map UI brightness into driver backlight level rounding it */
+		rc = dsi_backlight_lerp(
+			1, bl->brightness_max_level,
+			bl->bl_min_level ? : 1, bl->bl_max_level,
+			brightness, &bl_lvl);
+		if (unlikely(rc))
+			pr_err("failed to linearly interpolate, brightness unmodified\n");
+	}
+
+	pr_debug("normal bl: bl_lut %sused\n", bl->lut ? "" : "un");
+
+	return bl_lvl;
+}
+int dsi_panel_switch_update_hbm(struct dsi_panel *panel)
+{
+	if (!panel || !panel->funcs || !panel->funcs->update_hbm)
+		return -EOPNOTSUPP;
+
+	return panel->funcs->update_hbm(panel);
+}
+
+int dsi_backlight_hbm_dimming_start(struct dsi_backlight_config *bl,
+	u32 num_frames, struct dsi_panel_cmd_set *stop_cmd)
+{
+	struct hbm_data *hbm = bl->hbm;
+
+	if (!hbm || !num_frames)
+		return 0;
+
+	if (unlikely(!hbm->dimming_workq)) {
+		pr_err("hbm: tried to start dimming, but missing worker thread\n");
+		return -EINVAL;
+	}
+
+	if (!hbm->dimming_active) {
+		struct dsi_display *display =
+			dev_get_drvdata(hbm->panel->parent);
+		int rc;
+
+		if (likely(display->bridge &&
+			display->bridge->base.encoder &&
+			display->bridge->base.encoder->crtc)) {
+			rc = drm_crtc_vblank_get(
+				display->bridge->base.encoder->crtc);
+		} else {
+			pr_err("hbm: missing crtc during dimming start.\n");
+			return -EINVAL;
+		}
+
+		if (rc) {
+			pr_err("hbm: failed drm request to get vblank\n: %d",
+				rc);
+			return rc;
+		}
+	}
+
+	hbm->dimming_frames_total = num_frames;
+	hbm->dimming_frames_left = num_frames;
+	hbm->dimming_stop_cmd = stop_cmd;
+	hbm->dimming_active = true;
+
+	pr_debug("hbm dimming starting\n");
+	queue_work(hbm->dimming_workq, &hbm->dimming_work);
+
+	return 0;
+}
+
+void dsi_backlight_hbm_dimming_stop(struct dsi_backlight_config *bl)
+{
+	struct dsi_display *display;
+	struct hbm_data *hbm = bl->hbm;
+	struct dsi_panel *panel = container_of(bl, struct dsi_panel, bl_config);
+	int rc = 0;
+
+	if (!hbm || !hbm->dimming_active)
+		return;
+
+	display = dev_get_drvdata(hbm->panel->parent);
+	if (likely(display->bridge &&
+		display->bridge->base.encoder &&
+		display->bridge->base.encoder->crtc)) {
+		drm_crtc_vblank_put(display->bridge->base.encoder->crtc);
+	} else {
+		pr_err("hbm: missing crtc during dimming end.\n");
+	}
+
+	hbm->dimming_frames_total = 0;
+	hbm->dimming_frames_left = 0;
+	hbm->dimming_active = false;
+
+	if (hbm->dimming_stop_cmd) {
+		rc = dsi_panel_switch_update_hbm(panel);
+		if (rc == -EOPNOTSUPP)
+			rc = dsi_panel_cmd_set_transfer(hbm->panel,
+				hbm->dimming_stop_cmd);
+		if (rc)
+			pr_err("hbm: failed to disable brightness dimming.\n");
+	}
+
+	hbm->dimming_stop_cmd = NULL;
+
+	if (panel->hbm_pending_irc_on) {
+		rc = dsi_panel_bl_update_irc(bl, true);
+
+		if (rc)
+			pr_err("hmb sv: failed to enable IRC.\n");
+		panel->hbm_pending_irc_on = false;
+	}
+
+	pr_debug("hbm dimming stopped\n");
+}
+
+static void dsi_backlight_hbm_dimming_restart(struct dsi_backlight_config *bl)
+{
+	struct hbm_data *hbm = bl->hbm;
+
+	if (!hbm || !hbm->dimming_active)
+		return;
+
+	hbm->dimming_frames_left = hbm->dimming_frames_total;
+	pr_debug("hbm: dimming restarted\n");
+}
+
+static int dsi_backlight_hbm_wait_frame(struct hbm_data *hbm)
+{
+	struct dsi_display *display = dev_get_drvdata(hbm->panel->parent);
+
+	if (likely(display->bridge && display->bridge->base.encoder)) {
+		int rc = sde_encoder_wait_for_event(
+			display->bridge->base.encoder, MSM_ENC_VBLANK);
+		if (rc)
+			return rc;
+	} else {
+		pr_err("hbm: missing sde encoder, can't wait for vblank\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static void dsi_backlight_hbm_dimming_work(struct work_struct *work)
+{
+	struct dsi_panel *panel;
+	struct hbm_data *hbm =
+		container_of(work, struct hbm_data, dimming_work);
+
+	if (!hbm)
+		return;
+
+	panel = hbm->panel;
+	while (hbm->dimming_active) {
+		int rc = dsi_backlight_hbm_wait_frame(hbm);
+
+		/*
+		 * it's possible that this thread is running while the driver is
+		 * attempting to shut down. if this is the case, the driver
+		 * will signal for dimming to stop while holding panel_lock.
+		 * so if we fail to acquire the lock, wait a bit, then check the
+		 * state of dimming_active again.
+		 */
+		if (!mutex_trylock(&panel->panel_lock)) {
+			usleep_range(1000, 2000);
+			continue;
+		}
+
+		pr_debug("hbm: dimming waited on frame %d of %d\n",
+			hbm->dimming_frames_left, hbm->dimming_frames_total);
+		if (!hbm->dimming_active) {
+			mutex_unlock(&panel->panel_lock);
+			break;
+		}
+
+		if (rc) {
+			pr_err("hbm: failed to wait for vblank, disabling dimming now\n");
+			hbm->dimming_frames_left = 0;
+		} else if (hbm->dimming_frames_left > 0) {
+			hbm->dimming_frames_left--;
+		}
+
+		if (!hbm->dimming_frames_left)
+			dsi_backlight_hbm_dimming_stop(&panel->bl_config);
+
+		mutex_unlock(&panel->panel_lock);
+	}
+}
+
+int dsi_backlight_hbm_find_range(struct dsi_backlight_config *bl,
+		int brightness, u32 *range)
+{
+	u32 i;
+
+	if (!bl || !bl->hbm || !range)
+		return -EINVAL;
+
+	for (i = 0; i < bl->hbm->num_ranges; i++) {
+		if (brightness <= bl->hbm->ranges[i].user_bri_end) {
+			*range = i;
+			return 0;
+		}
+	}
+
+	return -EINVAL;
+}
+
+static u32 dsi_backlight_calculate_hbm(struct dsi_backlight_config *bl,
+		int brightness)
+{
+	struct dsi_panel *panel = container_of(bl, struct dsi_panel, bl_config);
+	struct hbm_data *hbm = bl->hbm;
+	struct hbm_range *range = NULL;
+	u32 bl_lvl = 0;
+	int rc = 0;
+	/* It's unlikely that a brightness value of 0 will make it to this
+	 * function, but if it does use the dimmest HBM range.
+	 */
+	u32 target_range = 0;
+
+	if (likely(brightness)) {
+		rc = dsi_backlight_hbm_find_range(bl, brightness,
+			&target_range);
+		if (rc) {
+			pr_err("Did not find a matching HBM range for brightness %d\n",
+				brightness);
+			return bl->bl_actual;
+		}
+	}
+
+	range = hbm->ranges + target_range;
+	if (hbm->cur_range != target_range) {
+		dsi_backlight_hbm_dimming_start(bl, range->num_dimming_frames,
+			&range->dimming_stop_cmd);
+		pr_info("hbm: range %d -> %d\n", hbm->cur_range, target_range);
+		hbm->cur_range = target_range;
+
+		rc = dsi_panel_switch_update_hbm(panel);
+		if (rc == -EOPNOTSUPP)
+			rc = dsi_panel_cmd_set_transfer(panel,
+				&range->entry_cmd);
+		if (rc) {
+			pr_err("Failed to send command for range %d\n",
+				target_range);
+			return bl->bl_actual;
+		}
+	}
+
+	rc = dsi_backlight_lerp(
+		range->user_bri_start, range->user_bri_end,
+		range->panel_bri_start, range->panel_bri_end,
+		brightness, &bl_lvl);
+	if (unlikely(rc))
+		pr_err("hbm: failed to linearly interpolate, brightness unmodified\n");
+
+	pr_debug("hbm: user %d-%d, panel %d-%d\n",
+		range->user_bri_start, range->user_bri_end,
+		range->panel_bri_start, range->panel_bri_end);
+
+	return bl_lvl;
+}
+
+static u32 dsi_backlight_calculate(struct dsi_backlight_config *bl,
+				   int brightness)
+{
+	struct dsi_panel *panel = container_of(bl, struct dsi_panel, bl_config);
+	u32 bl_lvl = 0;
+	u32 bl_temp;
+
+	if (brightness <= 0)
+		return 0;
+
+	/* scale backlight */
+	bl_temp = mult_frac(brightness, bl->bl_scale,
+			MAX_BL_SCALE_LEVEL);
+
+	bl_temp = mult_frac(bl_temp, bl->bl_scale_sv,
+			MAX_SV_BL_SCALE_LEVEL);
+
+	if (panel->hbm_mode != HBM_MODE_OFF)
+		bl_lvl = dsi_backlight_calculate_hbm(bl, bl_temp);
+	else
+		bl_lvl = dsi_backlight_calculate_normal(bl, bl_temp);
+
+	pr_debug("brightness=%d, bl_scale=%d, sv=%d, bl_lvl=%d, hbm = %d\n",
+			brightness, bl->bl_scale, bl->bl_scale_sv, bl_lvl,
+			panel->hbm_mode);
+
+	return bl_lvl;
+}
+
+static void dsi_panel_bl_elvss_clean_flag(struct dsi_backlight_config *bl)
+{
+	struct dynamic_elvss_data *elvss = bl->elvss;
+
+	if (!bl || !elvss)
+		return;
+
+	elvss->cur_elvss_range = DYNAMIC_ELVSS_RANGE_MAX;
+	elvss->cur_mode = ELVSS_MODE_INIT;
+	pr_debug("ELVSS: Set variables to default value\n");
+}
+
+static enum elvss_mode dsi_panel_get_elvss_mode(struct backlight_device *bd)
+{
+	struct dsi_backlight_config *bl = bl_get_data(bd);
+	struct dsi_panel *panel = container_of(bl, struct dsi_panel, bl_config);
+
+	if ((is_on_mode(bd->props.state) && dsi_panel_get_hbm(panel)) ||
+		(is_lp_mode(bd->props.state)))
+		return ELVSS_MODE_DISABLE;
+	else
+		return ELVSS_MODE_ENABLE;
+}
+
+static void dsi_panel_bl_elvss_update(struct backlight_device *bd,
+				      enum ctrl_elvss elvss_update)
+{
+	struct dsi_backlight_config *bl = bl_get_data(bd);
+	struct dsi_panel *panel = container_of(bl, struct dsi_panel, bl_config);
+	struct dynamic_elvss_data *elvss = bl->elvss;
+	int rc, i;
+	enum elvss_mode mode;
+
+	if (!elvss)
+		return;
+
+	if (elvss->enable_dynamic_elvss == false)
+		mode = ELVSS_MODE_DISABLE;
+	else
+		mode = dsi_panel_get_elvss_mode(bd);
+
+	if ((mode == elvss->cur_mode) && (mode != ELVSS_MODE_ENABLE))
+		return;
+
+	switch (mode) {
+	case ELVSS_MODE_DISABLE:
+		pr_debug("ELVSS: Disable Dynamic ELVSS\n");
+
+		rc = dsi_panel_cmd_set_transfer(panel,
+				&elvss->disable_dynamic_elvss_cmd);
+		if (rc)
+			pr_err("ELVSS: [%s] failed to send disable dynamic ELVSS cmd, rc=%d\n",
+				panel->name, rc);
+
+		elvss->cur_elvss_range = DYNAMIC_ELVSS_RANGE_MAX;
+		elvss->cur_mode = ELVSS_MODE_DISABLE;
+
+		break;
+	case ELVSS_MODE_ENABLE:
+		for (i = 0; i < elvss->num_ranges; i++) {
+			if (bd->props.brightness <=
+				elvss->nodes[i].brightness_threshold)
+				break;
+		}
+
+		if (i == elvss->num_ranges) {
+			pr_warn("ELVSS: brightness is larger than brightness_threshold\n");
+			return;
+		}
+
+		if ((elvss_update == ELVSS_PRE_UPDATE &&
+			 elvss->cur_elvss_range < i) ||
+			(elvss_update == ELVSS_POST_UPDATE &&
+			 elvss->cur_elvss_range > i)) {
+			pr_debug("ELVSS: Update: ori_range=%d, new_range=%d, ctrl_elvss_update:%d\n",
+				elvss->cur_elvss_range, i, elvss_update);
+
+			rc = dsi_panel_cmd_set_transfer(panel,
+				&elvss->nodes[i].elvss_cmd);
+			if (rc)
+				pr_err("ELVSS: [%s] failed to send elvss_cmd, rc=%d\n",
+					   panel->name, rc);
+
+			elvss->cur_elvss_range = i;
+			elvss->cur_mode = ELVSS_MODE_ENABLE;
+		}
+		break;
+	default:
+		pr_warn("ELVSS: Invalid Mode\n");
+		break;
+	}
+}
+
+static int dsi_panel_bl_parse_elvss_node(struct device *parent,
+	struct dsi_backlight_config *bl, struct device_node *np,
+	struct elvss_range *node)
+{
+	int rc;
+	u32 val;
+
+	rc = of_property_read_u32(np,
+		"google,dsi-elvss-range-brightness-threshold", &val);
+	if (rc) {
+		pr_err("Unable to parse dsi-elvss-range-brightness-threshold\n");
+		return rc;
+	}
+	if (val > bl->brightness_max_level) {
+		pr_err("elvss-range-brightness-threshold exceeds max userspace brightness\n");
+		return -EINVAL;
+	}
+	node->brightness_threshold = val;
+
+	rc = dsi_panel_parse_dt_cmd_set(np,
+		"google,dsi-elvss-range-update-command",
+		"google,dsi-elvss-range-commands-state", &node->elvss_cmd);
+	if (rc) {
+		pr_warn("Unable to parse google,dsi-elvss-range-update-command\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static void dsi_panel_bl_elvss_free(struct device *dev,
+	struct dsi_backlight_config *bl)
+{
+	u32 i;
+	struct dynamic_elvss_data *elvss = bl->elvss;
+
+	if (!elvss)
+		return;
+
+	dsi_panel_destroy_cmd_packets(&elvss->disable_dynamic_elvss_cmd);
+	dsi_panel_dealloc_cmd_packets(&elvss->disable_dynamic_elvss_cmd);
+
+	for (i = 0; i < elvss->num_ranges; i++) {
+		dsi_panel_destroy_cmd_packets(&elvss->nodes[i].elvss_cmd);
+		dsi_panel_dealloc_cmd_packets(&elvss->nodes[i].elvss_cmd);
+	}
+
+	devm_kfree(dev, elvss);
+	bl->elvss = NULL;
+}
+
+void dsi_panel_debugfs_create_dynamic_elvss(struct dentry *parent,
+					struct dynamic_elvss_data *elvss)
+{
+	if (!parent || !elvss)
+		return;
+
+	debugfs_create_bool("enable_dynamic_elvss", 0600,
+			parent, &elvss->enable_dynamic_elvss);
+
+}
+
+static int dsi_panel_bl_parse_dynamic_elvss(struct device *parent,
+		struct dsi_backlight_config *bl, struct device_node *of_node)
+{
+	struct device_node *elvss_ranges_np;
+	struct device_node *child_np;
+	u32 rc, i = 0, num_ranges;
+
+	elvss_ranges_np = of_get_child_by_name(of_node, "google,elvss-ranges");
+	if (!elvss_ranges_np) {
+		pr_info("ELVSS modes list not found\n");
+		return 0;
+	}
+
+	num_ranges = of_get_child_count(elvss_ranges_np);
+	if (!num_ranges || (num_ranges > DYNAMIC_ELVSS_RANGE_MAX)) {
+		pr_err("Invalid number of ELVSS modes: %d\n", num_ranges);
+		return -EINVAL;
+	}
+
+	bl->elvss = devm_kzalloc(parent, sizeof(struct dynamic_elvss_data),
+							 GFP_KERNEL);
+	if (bl->elvss == NULL) {
+		pr_err("Failed to allocate memory for dynamic elvss data\n");
+		return -ENOMEM;
+	}
+
+	rc = dsi_panel_parse_dt_cmd_set(elvss_ranges_np,
+		"google,dsi-elvss-range-off-command",
+		"google,dsi-elvss-range-commands-state",
+		&bl->elvss->disable_dynamic_elvss_cmd);
+	if (rc) {
+		devm_kfree(parent, bl->elvss);
+		bl->elvss = NULL;
+		pr_err("Unable to parse dsi-elvss-range-off-command\n");
+		return -EINVAL;
+	}
+
+	bl->elvss->num_ranges = num_ranges;
+
+	for_each_child_of_node(elvss_ranges_np, child_np) {
+		rc = dsi_panel_bl_parse_elvss_node(parent, bl,
+			child_np, bl->elvss->nodes + i);
+		if (rc) {
+			bl->elvss->num_ranges = i;
+			pr_err("Failed to parse ELVSS range %d\n", i);
+			goto exit_free;
+		}
+		i++;
+	}
+
+	for (i = 0; i < num_ranges - 1; i++) {
+		/* Make sure ranges are sorted and not overlapping */
+		if (bl->elvss->nodes[i].brightness_threshold >=
+				bl->elvss->nodes[i + 1].brightness_threshold) {
+			pr_err("ELVSS nodes must be sorted by elvss-brightness-threshold\n");
+			rc = -EINVAL;
+			goto exit_free;
+		}
+	}
+
+	dsi_panel_bl_elvss_clean_flag(bl);
+
+	bl->elvss->enable_dynamic_elvss = true;
+
+	return 0;
+
+exit_free:
+	dsi_panel_bl_elvss_free(parent, bl);
+	return rc;
+}
+
+static int dsi_backlight_update_status(struct backlight_device *bd)
+{
+	struct dsi_backlight_config *bl = bl_get_data(bd);
+	struct dsi_panel *panel = container_of(bl, struct dsi_panel, bl_config);
+	struct dsi_display *display;
+	int brightness = bd->props.brightness;
+	int bl_lvl;
+	int rc = 0;
+	bool need_notify = false;
+
+	mutex_lock(&panel->panel_lock);
+	mutex_lock(&bl->state_lock);
+	if ((bd->props.state & (BL_CORE_FBBLANK | BL_CORE_SUSPENDED)) ||
+			(bd->props.power != FB_BLANK_UNBLANK))
+		brightness = 0;
+
+	bl_lvl = dsi_backlight_calculate(bl, brightness);
+	if (bl_lvl == bl->bl_actual && bl->last_state == bd->props.state)
+		goto done;
+
+	if (!bl->allow_bl_update) {
+		bl->bl_update_pending = true;
+		goto done;
+	}
+
+	dsi_backlight_hbm_dimming_restart(bl);
+
+	if (dsi_panel_initialized(panel) && bl->update_bl) {
+		pr_info("req:%d bl:%d state:0x%x\n",
+			bd->props.brightness, bl_lvl, bd->props.state);
+
+		dsi_panel_bl_elvss_update(bd, ELVSS_PRE_UPDATE);
+
+		rc = bl->update_bl(bl, bl_lvl);
+		if (rc) {
+			pr_err("unable to set backlight (%d)\n", rc);
+			goto done;
+		}
+
+		dsi_panel_bl_elvss_update(bd, ELVSS_POST_UPDATE);
+
+		bl->bl_update_pending = false;
+		need_notify = true;
+		if (bl->bl_notifier && is_on_mode(bd->props.state)
+				&& !(dsi_panel_get_hbm(panel))) {
+			u32 target_range = 0;
+
+			rc = dsi_panel_bl_find_range(bl, brightness, &target_range);
+			if (rc) {
+				pr_err("unable to find range from the backlight table (%d)\n", rc);
+			} else if (bl->bl_notifier->cur_range != target_range) {
+				bl->bl_notifier->cur_range = target_range;
+				sysfs_notify(&bd->dev.kobj, NULL, "brightness");
+				pr_debug("cur_range = %d, brightness = %d\n",
+						bl->bl_notifier->cur_range, brightness);
+			}
+		}
+	}
+	bl->bl_actual = bl_lvl;
+	bl->last_state = bd->props.state;
+
+done:
+	mutex_unlock(&bl->state_lock);
+	mutex_unlock(&panel->panel_lock);
+
+	/* skip notifying user space if bl is 0 */
+	if (likely(need_notify && brightness)) {
+		display = dev_get_drvdata(panel->parent);
+		if (unlikely(!display))
+			return rc;
+
+		sde_connector_event_notify(display->drm_conn,
+			DRM_EVENT_SYS_BACKLIGHT, sizeof(u32), brightness);
+	}
+
+	return rc;
+}
+
+static int dsi_backlight_get_brightness(struct backlight_device *bd)
+{
+	struct dsi_backlight_config *bl = bl_get_data(bd);
+
+	return bl->bl_actual;
+}
+
+static const struct backlight_ops dsi_backlight_ops = {
+	.update_status = dsi_backlight_update_status,
+	.get_brightness = dsi_backlight_get_brightness,
+};
+
+static ssize_t alpm_mode_store(struct device *dev,
+			       struct device_attribute *attr,
+			       const char *buf, size_t count)
+{
+	struct backlight_device *bd = to_backlight_device(dev);
+	struct dsi_backlight_config *bl = bl_get_data(bd);
+	struct dsi_panel *panel = container_of(bl, struct dsi_panel, bl_config);
+	int rc, alpm_mode;
+	const unsigned int lp_state = bl->bl_device->props.state &
+			(BL_STATE_LP | BL_STATE_LP2);
+
+	rc = kstrtoint(buf, 0, &alpm_mode);
+	if (rc)
+		return rc;
+
+	if (bl->bl_device->props.state & BL_CORE_FBBLANK) {
+		return -EINVAL;
+	} else if ((alpm_mode == 1) && (lp_state != BL_STATE_LP)) {
+		pr_info("activating lp1 mode\n");
+		dsi_panel_set_lp1(panel);
+	} else if ((alpm_mode > 1) && !(lp_state & BL_STATE_LP2)) {
+		pr_info("activating lp2 mode\n");
+		dsi_panel_set_lp2(panel);
+	} else if (!alpm_mode && lp_state) {
+		pr_info("activating normal mode\n");
+		dsi_panel_set_nolp(panel);
+	}
+
+	return count;
+}
+
+static ssize_t alpm_mode_show(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct backlight_device *bd = to_backlight_device(dev);
+	int alpm_mode;
+
+	if (bd->props.state & BL_STATE_LP2)
+		alpm_mode = 2;
+	else
+		alpm_mode = (bd->props.state & BL_STATE_LP) != 0;
+
+	return sprintf(buf, "%d\n", alpm_mode);
+}
+static DEVICE_ATTR_RW(alpm_mode);
+
+static ssize_t hbm_mode_store(struct device *dev,
+			       struct device_attribute *attr,
+			       const char *buf, size_t count)
+{
+	struct backlight_device *bd = NULL;
+	struct dsi_backlight_config *bl = NULL;
+	struct dsi_panel *panel = NULL;
+	int rc = 0;
+	int hbm_mode = 0;
+
+	/* dev is non-NULL, enforced by sysfs_create_file_ns */
+	bd = to_backlight_device(dev);
+	bl = bl_get_data(bd);
+
+	if (!bl->hbm)
+		return -ENOTSUPP;
+
+	rc = kstrtoint(buf, 10, &hbm_mode);
+	if (rc)
+		return rc;
+
+	panel = container_of(bl, struct dsi_panel, bl_config);
+	rc = dsi_panel_update_hbm(panel, hbm_mode);
+	if (rc) {
+		pr_err("hbm_mode store failed: %d\n", rc);
+		return rc;
+	}
+	pr_debug("hbm_mode set to %d\n", panel->hbm_mode);
+
+	return count;
+}
+
+static ssize_t hbm_mode_show(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct backlight_device *bd = NULL;
+	struct dsi_backlight_config *bl = NULL;
+	struct dsi_panel *panel = NULL;
+	int hbm_mode = false;
+
+	/* dev is non-NULL, enforced by sysfs_create_file_ns */
+	bd = to_backlight_device(dev);
+	bl = bl_get_data(bd);
+
+	if (!bl->hbm)
+		return snprintf(buf, PAGE_SIZE, "unsupported\n");
+
+	panel = container_of(bl, struct dsi_panel, bl_config);
+	hbm_mode = dsi_panel_get_hbm(panel);
+
+	return snprintf(buf, PAGE_SIZE, "%d\n", hbm_mode);
+}
+
+static DEVICE_ATTR_RW(hbm_mode);
+
+static ssize_t hbm_sv_enabled_store(struct device *dev,
+			       struct device_attribute *attr,
+			       const char *buf, size_t count)
+{
+	struct backlight_device *bd;
+	struct dsi_backlight_config *bl;
+	struct dsi_panel *panel;
+	int rc = 0;
+	bool hbm_sv_enabled = false;
+
+	/* dev is non-NULL, enforced by sysfs_create_file_ns */
+	bd = to_backlight_device(dev);
+	bl = bl_get_data(bd);
+
+	if (!bl->hbm)
+		return -ENOTSUPP;
+
+	rc = kstrtobool(buf, &hbm_sv_enabled);
+	if (rc)
+		return rc;
+
+	panel = container_of(bl, struct dsi_panel, bl_config);
+	if (!hbm_sv_enabled && panel->hbm_mode == HBM_MODE_SV)
+		return -EBUSY;
+
+	panel->hbm_sv_enabled = hbm_sv_enabled;
+
+	return count;
+}
+
+static ssize_t hbm_sv_enabled_show(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct backlight_device *bd;
+	struct dsi_backlight_config *bl;
+	struct dsi_panel *panel;
+
+	/* dev is non-NULL, enforced by sysfs_create_file_ns */
+	bd = to_backlight_device(dev);
+	bl = bl_get_data(bd);
+
+	if (!bl->hbm)
+		return snprintf(buf, PAGE_SIZE, "unsupported\n");
+
+	panel = container_of(bl, struct dsi_panel, bl_config);
+	return snprintf(buf, PAGE_SIZE, "%s\n",
+			panel->hbm_sv_enabled ? "true" : "false");
+}
+
+static DEVICE_ATTR_RW(hbm_sv_enabled);
+
+static ssize_t state_show(struct device *dev, struct device_attribute *attr,
+			  char *buf)
+{
+	struct backlight_device *bd = to_backlight_device(dev);
+	struct dsi_backlight_config *bl = bl_get_data(bd);
+	struct dsi_panel *panel = container_of(bl,
+					struct dsi_panel, bl_config);
+	bool show_mode = false;
+	char *statestr;
+	int rc;
+
+	mutex_lock(&bl->state_lock);
+	if (is_standby_mode(bd->props.state)) {
+		statestr = "Off";
+	} else if (is_lp_mode(bd->props.state)) {
+		statestr = "LP";
+	} else {
+		show_mode = true;
+		if (dsi_panel_get_hbm(panel))
+			statestr = "HBM";
+		else
+			statestr = "On";
+	}
+	mutex_unlock(&bl->state_lock);
+
+	if (show_mode) {
+		const struct dsi_display_mode *mode =
+				get_panel_display_mode(panel);
+
+		if (unlikely(!mode))
+			return -ENODEV;
+
+		rc = snprintf(buf, PAGE_SIZE, "%s: %dx%d@%d\n", statestr,
+			 mode->timing.h_active, mode->timing.v_active,
+			 mode->timing.refresh_rate);
+	} else {
+		rc = snprintf(buf, PAGE_SIZE, "%s\n", statestr);
+	}
+
+	return rc;
+}
+
+static DEVICE_ATTR_RO(state);
+
+int parse_u32_buf(char *src, size_t src_len, u32 *out, size_t out_len)
+{
+	int rc = 0, cnt = 0;
+	char *str;
+	const char *delim = " ";
+
+	if (unlikely(!src || !src_len || !out || !out_len))
+		return -EINVAL;
+
+	/* src_len is the length of src including null character '\0' */
+	if (strnlen(src, src_len) == src_len)
+		return -EINVAL;
+
+	for (str = strsep(&src, delim); str != NULL; str = strsep(&src, delim)) {
+		rc = kstrtou32(str, 0, out + cnt);
+		if (rc)
+			return -EINVAL;
+
+		cnt++;
+
+		if (out_len == cnt)
+			break;
+	}
+
+	return cnt;
+}
+
+static ssize_t als_table_store(struct device *dev,
+			       struct device_attribute *attr,
+			       const char *buf, size_t count)
+{
+
+	struct backlight_device *bd = to_backlight_device(dev);
+	struct dsi_backlight_config *bl = bl_get_data(bd);
+	ssize_t als_count, buf_dup_len;
+	u8 i;
+	u32 ranges[BL_RANGE_MAX] = {0};
+	char *buf_dup;
+
+	if (unlikely(!bl || !bl->bl_notifier))
+		return -EINVAL;
+
+	if (count == 0)
+		return -EINVAL;
+
+	buf_dup = kstrndup(buf, count, GFP_KERNEL);
+	if (!buf_dup)
+		return -ENOMEM;
+
+	buf_dup_len = strlen(buf_dup) + 1;
+
+	als_count = parse_u32_buf(buf_dup, buf_dup_len, ranges, BL_RANGE_MAX);
+	if ((als_count < 0) || (als_count > BL_RANGE_MAX)) {
+		kfree(buf_dup);
+		pr_warn("als: incorrect parameters from als table node\n");
+		return -EINVAL;
+	}
+
+	mutex_lock(&bl->state_lock);
+
+	bl->bl_notifier->num_ranges = als_count;
+	for (i = 0; i < bl->bl_notifier->num_ranges; i++)
+		bl->bl_notifier->ranges[i] = ranges[i];
+
+	mutex_unlock(&bl->state_lock);
+
+	kfree(buf_dup);
+
+	return count;
+}
+
+static ssize_t als_table_show(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct backlight_device *bd = to_backlight_device(dev);
+	struct dsi_backlight_config *bl = bl_get_data(bd);
+	ssize_t rc = 0;
+	size_t len = 0;
+	u32 i = 0;
+
+	if (unlikely(!bl || !bl->bl_notifier))
+		return -EINVAL;
+
+	mutex_lock(&bl->state_lock);
+
+	for (i = 0; i < bl->bl_notifier->num_ranges; i++) {
+		rc = scnprintf((buf + len), PAGE_SIZE - len,
+				"%u ", bl->bl_notifier->ranges[i]);
+		if (rc < 0) {
+			pr_warn("als: incorrect bl_notifier ranges\n");
+			mutex_unlock(&bl->state_lock);
+			return -EINVAL;
+		}
+		len = len + rc;
+	}
+
+	mutex_unlock(&bl->state_lock);
+
+	len += scnprintf(buf + len, PAGE_SIZE - len, "\n");
+
+	return len;
+}
+static DEVICE_ATTR_RW(als_table);
+
+static struct attribute *bl_device_attrs[] = {
+	&dev_attr_alpm_mode.attr,
+	&dev_attr_hbm_mode.attr,
+	&dev_attr_hbm_sv_enabled.attr,
+	&dev_attr_state.attr,
+	&dev_attr_als_table.attr,
+	NULL,
+};
+ATTRIBUTE_GROUPS(bl_device);
+
+static int dsi_backlight_register(struct dsi_backlight_config *bl)
+{
+	static int display_count;
+	char bl_node_name[BL_NODE_NAME_SIZE];
+	struct backlight_properties props = {
+		.type = BACKLIGHT_RAW,
+		.power = FB_BLANK_UNBLANK,
+	};
+	struct dsi_panel *panel = container_of(bl, struct dsi_panel, bl_config);
+	struct regulator *reg;
+
+	props.max_brightness = bl->brightness_max_level;
+	props.brightness = bl->brightness_max_level / 2;
+
+	snprintf(bl_node_name, BL_NODE_NAME_SIZE, "panel%u-backlight",
+		 display_count);
+	bl->bl_device = devm_backlight_device_register(panel->parent,
+				bl_node_name, panel->parent, bl,
+				&dsi_backlight_ops, &props);
+	if (IS_ERR_OR_NULL(bl->bl_device)) {
+		bl->bl_device = NULL;
+		return -ENODEV;
+	}
+
+	if (sysfs_create_groups(&bl->bl_device->dev.kobj, bl_device_groups))
+		pr_warn("unable to create device groups\n");
+
+	reg = regulator_get_optional(panel->parent, "lab");
+	if (!PTR_ERR_OR_ZERO(reg)) {
+		pr_info("LAB regulator found\n");
+		panel->bl_config.lab_vreg = reg;
+	}
+
+	display_count++;
+	return 0;
+}
+
+static unsigned long get_state_after_dpms(struct dsi_backlight_config *bl,
+				   int power_mode)
+{
+	struct backlight_device *bd = bl->bl_device;
+	unsigned long state = bd->props.state;
+
+	switch (power_mode) {
+	case SDE_MODE_DPMS_ON:
+		state &= ~(BL_CORE_FBBLANK | BL_STATE_LP | BL_STATE_LP2);
+		break;
+	case SDE_MODE_DPMS_OFF:
+		state &= ~(BL_STATE_LP | BL_STATE_LP2);
+		state |= BL_CORE_FBBLANK;
+		break;
+	case SDE_MODE_DPMS_LP1:
+		state |= BL_STATE_LP;
+		state &= ~BL_STATE_LP2;
+		break;
+	case SDE_MODE_DPMS_LP2:
+		state |= BL_STATE_LP | BL_STATE_LP2;
+		break;
+	}
+
+	return state;
+}
+
+static int dsi_backlight_update_regulator(struct dsi_backlight_config *bl,
+					  unsigned int state)
+{
+	int rc = 0;
+
+	if (bl->lab_vreg) {
+		const unsigned int mode = regulator_mode_from_state(state);
+		const unsigned int last_mode =
+				regulator_mode_from_state(bl->last_state);
+
+		if (last_mode != mode) {
+			pr_debug("set lab vreg mode: 0x%0x\n", mode);
+			rc = regulator_set_mode(bl->lab_vreg, mode);
+		}
+	}
+
+	return rc;
+}
+
+int dsi_backlight_early_dpms(struct dsi_backlight_config *bl, int power_mode)
+{
+	struct backlight_device *bd = bl->bl_device;
+	unsigned long state;
+	int rc = 0;
+
+	if (!bd)
+		return 0;
+
+	pr_info("power_mode:%d state:0x%0x\n", power_mode, bd->props.state);
+
+	mutex_lock(&bl->state_lock);
+	state = get_state_after_dpms(bl, power_mode);
+
+	if (is_lp_mode(state)) {
+		rc = dsi_backlight_update_regulator(bl, state);
+		if (rc)
+			pr_warn("Error updating regulator state: 0x%x (%d)\n",
+				state, rc);
+	}
+	mutex_unlock(&bl->state_lock);
+
+	return rc;
+}
+
+int dsi_backlight_late_dpms(struct dsi_backlight_config *bl, int power_mode)
+{
+	struct backlight_device *bd = bl->bl_device;
+	unsigned long state;
+
+	if (!bd)
+		return 0;
+
+	pr_debug("power_mode:%d state:0x%0x\n", power_mode, bd->props.state);
+
+	mutex_lock(&bl->state_lock);
+	state = get_state_after_dpms(bl, power_mode);
+
+	if (!is_lp_mode(state)) {
+		const int rc = dsi_backlight_update_regulator(bl, state);
+
+		if (rc)
+			pr_warn("Error updating regulator state: 0x%x (%d)\n",
+				state, rc);
+	}
+
+	bd->props.power = state & BL_CORE_FBBLANK ? FB_BLANK_POWERDOWN :
+			FB_BLANK_UNBLANK;
+	bd->props.state = state;
+
+	/* The dynamic elvss register will be restored to
+	 * the default OTP's value automatically when the
+	 * panel is power off(HW behavior). We need to set
+	 * the variables to default value for this kind of
+	 * case. When the device comes back from panel off
+	 * to other modes, the dynamic elvss will be updated.
+	 */
+	if (bd->props.power == FB_BLANK_POWERDOWN)
+		dsi_panel_bl_elvss_clean_flag(bl);
+
+	mutex_unlock(&bl->state_lock);
+	backlight_update_status(bd);
+	sysfs_notify(&bd->dev.kobj, NULL, "state");
+
+	pr_info("sysfs_notify state:0x%0x\n", bd->props.state);
+
+	return 0;
+}
+
+#define MAX_BINNED_BL_MODES 10
+
+struct binned_lp_node {
+	struct list_head head;
+	const char *name;
+	u32 bl_threshold;
+	struct dsi_panel_cmd_set dsi_cmd;
+	struct dentry *mode_dir;
+};
+
+struct binned_lp_data {
+	struct list_head mode_list;
+	struct binned_lp_node *last_lp_mode;
+	struct binned_lp_node priv_pool[MAX_BINNED_BL_MODES];
+};
+
+static int dsi_panel_te2_lp_mode_update(struct dsi_panel *panel,
+				struct binned_lp_node *node)
+{
+	int rc;
+
+	if (unlikely(!panel->funcs || !panel->funcs->update_te2))
+		return -EINVAL;
+
+	if (unlikely(!panel->te2_config.te2_ready))
+		return -EINVAL;
+
+	if (node->bl_threshold > panel->te2_config.lp_threshold)
+		panel->te2_config.current_type = TE2_EDGE_LP_HIGH;
+	else
+		panel->te2_config.current_type = TE2_EDGE_LP_LOW;
+
+	rc = panel->funcs->update_te2(panel);
+	if (rc < 0)
+		pr_warn("TE2: LP '%s' mode failed, rc=%d\n", node->name, rc);
+
+	return rc;
+}
+
+static int dsi_panel_binned_bl_update(struct dsi_backlight_config *bl,
+				      u32 bl_lvl)
+{
+	struct dsi_panel *panel = container_of(bl, struct dsi_panel, bl_config);
+	struct binned_lp_data *lp_data = bl->priv;
+	struct binned_lp_node *node = NULL;
+	struct backlight_properties *props = &bl->bl_device->props;
+
+	if (is_lp_mode(props->state)) {
+		struct binned_lp_node *tmp;
+
+		list_for_each_entry(tmp, &lp_data->mode_list, head) {
+			if (props->brightness <= tmp->bl_threshold) {
+				node = tmp;
+				break;
+			}
+		}
+		WARN(!node, "unable to find lp node for bl_lvl: %d\n",
+		     props->brightness);
+	}
+
+	if (node != lp_data->last_lp_mode) {
+		lp_data->last_lp_mode = node;
+		if (node) {
+			pr_debug("switching display lp mode: %s (%d)\n",
+				node->name, props->brightness);
+			dsi_panel_cmd_set_transfer(panel, &node->dsi_cmd);
+
+			dsi_panel_te2_lp_mode_update(panel, node);
+		} else {
+			/* ensure update after lpm */
+			bl->bl_actual = -1;
+		}
+	}
+
+	/* no need to send backlight command if HLPM active */
+	if (node)
+		return 0;
+
+	return dsi_backlight_update_dcs(bl, bl_lvl);
+}
+
+static int _dsi_panel_binned_lp_parse(struct device_node *np,
+				      struct binned_lp_node *node)
+{
+	int rc;
+	u32 val = 0;
+
+	rc = of_property_read_u32(np, "google,dsi-lp-brightness-threshold",
+				  &val);
+	/* treat lack of property as max threshold */
+	node->bl_threshold = !rc ? val : UINT_MAX;
+
+	rc = dsi_panel_parse_dt_cmd_set(np, "google,dsi-lp-command",
+					"google,dsi-lp-command-state",
+					&node->dsi_cmd);
+	if (rc) {
+		pr_err("Unable to parse dsi-lp-command\n");
+		return rc;
+	}
+
+	of_property_read_string(np, "label", &node->name);
+
+	pr_debug("Successfully parsed lp mode: %s threshold: %d\n",
+		node->name, node->bl_threshold);
+
+	return 0;
+}
+
+static int _dsi_panel_binned_bl_cmp(void *priv, struct list_head *lha,
+				    struct list_head *lhb)
+{
+	struct binned_lp_node *a = list_entry(lha, struct binned_lp_node, head);
+	struct binned_lp_node *b = list_entry(lhb, struct binned_lp_node, head);
+
+	return a->bl_threshold - b->bl_threshold;
+}
+
+void dsi_panel_debugfs_create_binned_bl(struct dentry *parent,
+					struct dsi_backlight_config *bl)
+{
+	struct dentry *r, *file;
+	struct binned_lp_data *lp_data;
+	struct binned_lp_node *tmp;
+	struct dsi_panel *panel = container_of(bl, struct dsi_panel, bl_config);
+
+	r = debugfs_create_dir("lp_modes", parent);
+	if (IS_ERR(r)) {
+		pr_err("create debugfs lp_modes failed\n");
+		return;
+	}
+
+	lp_data = bl->priv;
+
+	list_for_each_entry(tmp, &lp_data->mode_list, head) {
+		tmp->mode_dir = debugfs_create_dir(tmp->name, r);
+		if (IS_ERR(tmp->mode_dir)) {
+			pr_err("create debugfs binned_bl failed\n");
+			goto error;
+		}
+
+		file = debugfs_create_u32("threshold", 0600,
+					  tmp->mode_dir,
+					  &tmp->bl_threshold);
+		if (IS_ERR_OR_NULL(file)) {
+			pr_err("debugfs create threshold file failed\n");
+			goto error;
+		}
+
+		if (dsi_panel_debugfs_create_cmdset(tmp->mode_dir, "cmd",
+						    panel, &tmp->dsi_cmd)) {
+			pr_err("debugfs create cmd file failed\n");
+			goto error;
+		}
+	}
+
+	return;
+
+error:
+	debugfs_remove_recursive(r);
+}
+
+void dsi_panel_bl_elvss_debugfs_init(struct dentry *parent,
+				      struct dsi_panel *panel)
+{
+	struct dsi_backlight_config *bl = &panel->bl_config;
+	struct dynamic_elvss_data *elvss;
+
+	if (!parent || !bl->elvss)
+		return;
+
+	elvss = bl->elvss;
+
+	dsi_panel_debugfs_create_dynamic_elvss(parent, elvss);
+}
+
+static int dsi_panel_binned_lp_register(struct dsi_backlight_config *bl)
+{
+	struct dsi_panel *panel = container_of(bl, struct dsi_panel, bl_config);
+	struct binned_lp_data *lp_data;
+	struct device_node *lp_modes_np, *child_np;
+	struct binned_lp_node *lp_node;
+	int num_modes;
+	int rc = -ENOTSUPP;
+
+	lp_data = kzalloc(sizeof(*lp_data), GFP_KERNEL);
+	if (!lp_data)
+		return -ENOMEM;
+
+	lp_modes_np = of_get_child_by_name(panel->panel_of_node,
+					   "google,lp-modes");
+
+	if (!lp_modes_np) {
+		kfree(lp_data);
+		return rc;
+	}
+
+	num_modes = of_get_child_count(lp_modes_np);
+	if (!num_modes || (num_modes > MAX_BINNED_BL_MODES)) {
+		pr_err("Invalid binned brightness modes: %d\n", num_modes);
+		goto exit;
+	}
+
+	INIT_LIST_HEAD(&lp_data->mode_list);
+	lp_node = lp_data->priv_pool;
+
+	for_each_child_of_node(lp_modes_np, child_np) {
+		rc = _dsi_panel_binned_lp_parse(child_np, lp_node);
+		if (rc)
+			goto exit;
+
+		list_add_tail(&lp_node->head, &lp_data->mode_list);
+		lp_node++;
+		if (lp_node > &lp_data->priv_pool[MAX_BINNED_BL_MODES - 1]) {
+			pr_err("Too many LP modes\n");
+			rc = -ENOTSUPP;
+			goto exit;
+		}
+	}
+	list_sort(NULL, &lp_data->mode_list, _dsi_panel_binned_bl_cmp);
+
+	bl->update_bl = dsi_panel_binned_bl_update;
+	bl->unregister = dsi_panel_bl_free_unregister;
+	bl->debugfs_init = dsi_panel_debugfs_create_binned_bl;
+	bl->priv = lp_data;
+
+exit:
+	of_node_put(lp_modes_np);
+	if (rc)
+		kfree(lp_data);
+
+	return rc;
+}
+
+static const struct of_device_id dsi_backlight_dt_match[] = {
+	{
+		.compatible = "google,dsi_binned_lp",
+		.data = dsi_panel_binned_lp_register,
+	},
+	{}
+};
+
+void dsi_panel_bl_debugfs_init(struct dentry *parent, struct dsi_panel *panel)
+{
+	struct dsi_backlight_config *bl = &panel->bl_config;
+
+	if (bl->debugfs_init)
+		bl->debugfs_init(parent, bl);
+}
+
+int dsi_backlight_get_dpms(struct dsi_backlight_config *bl)
+{
+	struct backlight_device *bd = bl->bl_device;
+	int power = 0;
+	int state = 0;
+
+	mutex_lock(&bl->state_lock);
+	power = bd->props.power;
+	state = bd->props.state;
+	mutex_unlock(&bl->state_lock);
+
+	if (power == FB_BLANK_POWERDOWN)
+		return SDE_MODE_DPMS_OFF;
+	else if (state & BL_STATE_LP2)
+		return SDE_MODE_DPMS_LP2;
+	else if (state & BL_STATE_LP)
+		return SDE_MODE_DPMS_LP1;
+	else
+		return SDE_MODE_DPMS_ON;
+}
+
+static int dsi_panel_bl_parse_hbm_node(struct device *parent,
+	struct dsi_backlight_config *bl, struct device_node *np,
+	struct hbm_range *range)
+{
+	int rc;
+	u32 val = 0;
+
+	rc = of_property_read_u32(np,
+		"google,dsi-hbm-range-brightness-threshold", &val);
+	if (rc) {
+		pr_err("Unable to parse dsi-hbm-range-brightness-threshold");
+		return rc;
+	}
+	if (val > bl->brightness_max_level) {
+		pr_err("hbm-brightness-threshold exceeds max userspace brightness\n");
+		return rc;
+	}
+	range->user_bri_start = val;
+
+	rc = of_property_read_u32(np, "google,dsi-hbm-range-bl-min-level",
+		&val);
+	if (rc) {
+		pr_err("dsi-hbm-range-bl-min-level unspecified\n");
+		return rc;
+	}
+	range->panel_bri_start = val;
+
+	rc = of_property_read_u32(np, "google,dsi-hbm-range-bl-max-level",
+		&val);
+	if (rc) {
+		pr_err("dsi-hbm-range-bl-max-level unspecified\n");
+		return rc;
+	}
+	if (val < range->panel_bri_start) {
+		pr_err("Invalid HBM panel brightness range: bl-hbm-max-level < bl-hbm-min-level\n");
+		return rc;
+	}
+	range->panel_bri_end = val;
+
+	rc = dsi_panel_parse_dt_cmd_set(np,
+		"google,dsi-hbm-range-entry-command",
+		"google,dsi-hbm-range-commands-state", &range->entry_cmd);
+	if (rc)
+		pr_info("Unable to parse optional dsi-hbm-range-entry-command\n");
+
+	rc = of_property_read_u32(np,
+		"google,dsi-hbm-range-num-dimming-frames", &val);
+	if (rc) {
+		pr_debug("Unable to parse optional hbm-range-entry-num-dimming-frames\n");
+		range->num_dimming_frames = 0;
+	} else {
+		range->num_dimming_frames = val;
+	}
+
+	rc = dsi_panel_parse_dt_cmd_set(np,
+		"google,dsi-hbm-range-dimming-stop-command",
+		"google,dsi-hbm-range-commands-state",
+		&range->dimming_stop_cmd);
+	if (rc)
+		pr_debug("Unable to parse optional dsi-hbm-range-dimming-stop-command\n");
+
+	if ((range->dimming_stop_cmd.count && !range->num_dimming_frames) ||
+		(!range->dimming_stop_cmd.count && range->num_dimming_frames)) {
+		pr_err("HBM dimming requires both stop command and number of frames.\n");
+		return -EINVAL;
+	}
+	return 0;
+}
+
+int dsi_panel_bl_register(struct dsi_panel *panel)
+{
+	int rc = 0;
+	struct dsi_backlight_config *bl = &panel->bl_config;
+	const struct of_device_id *match;
+	int (*register_func)(struct dsi_backlight_config *) = NULL;
+
+	mutex_init(&bl->state_lock);
+	match = of_match_node(dsi_backlight_dt_match, panel->panel_of_node);
+	if (match && match->data) {
+		register_func = match->data;
+		rc = register_func(bl);
+	}
+
+	if (!register_func || (rc == -ENOTSUPP)) {
+		switch (bl->type) {
+		case DSI_BACKLIGHT_WLED:
+			break;
+		case DSI_BACKLIGHT_DCS:
+			bl->update_bl = dsi_backlight_update_dcs;
+			break;
+		case DSI_BACKLIGHT_PWM:
+			register_func = dsi_panel_pwm_bl_register;
+			break;
+		default:
+			pr_err("Backlight type(%d) not supported\n", bl->type);
+			rc = -ENOTSUPP;
+			break;
+		}
+
+		if (register_func)
+			rc = register_func(bl);
+	}
+
+	if (!rc)
+		rc = dsi_backlight_register(bl);
+
+	return rc;
+}
+
+
+int dsi_panel_bl_unregister(struct dsi_panel *panel)
+{
+	struct dsi_backlight_config *bl = &panel->bl_config;
+
+	mutex_destroy(&bl->state_lock);
+	if (bl->unregister)
+		bl->unregister(bl);
+
+	if (bl->bl_device)
+		sysfs_remove_groups(&bl->bl_device->dev.kobj, bl_device_groups);
+
+	dsi_panel_bl_hbm_free(panel->parent, bl);
+	dsi_panel_bl_elvss_free(panel->parent, bl);
+	dsi_panel_bl_notifier_free(panel->parent, bl);
+
+	return 0;
+}
+
+static int dsi_panel_bl_parse_pwm_config(struct dsi_panel *panel,
+				struct dsi_backlight_pwm_config *config)
+{
+	int rc = 0;
+	u32 val;
+	struct dsi_parser_utils *utils = &panel->utils;
+
+	rc = utils->read_u32(utils->data, "qcom,bl-pmic-pwm-period-usecs",
+				  &val);
+	if (rc) {
+		pr_err("bl-pmic-pwm-period-usecs is not defined, rc=%d\n", rc);
+		goto error;
+	}
+	config->pwm_period_usecs = val;
+
+error:
+	return rc;
+}
+
+static void dsi_panel_pwm_bl_unregister(struct dsi_backlight_config *bl)
+{
+	struct dsi_panel *panel = container_of(bl, struct dsi_panel, bl_config);
+	struct dsi_backlight_pwm_config *pwm_cfg = bl->priv;
+
+	devm_pwm_put(panel->parent, pwm_cfg->pwm_bl);
+	kfree(pwm_cfg);
+}
+
+static int dsi_panel_pwm_bl_register(struct dsi_backlight_config *bl)
+{
+	struct dsi_panel *panel = container_of(bl, struct dsi_panel, bl_config);
+	struct dsi_backlight_pwm_config *pwm_cfg;
+	int rc = 0;
+
+	pwm_cfg = kzalloc(sizeof(*pwm_cfg), GFP_KERNEL);
+	if (!pwm_cfg)
+		return -ENOMEM;
+
+	pwm_cfg->pwm_bl = devm_of_pwm_get(panel->parent, panel->panel_of_node, NULL);
+	if (IS_ERR_OR_NULL(pwm_cfg->pwm_bl)) {
+		rc = PTR_ERR(pwm_cfg->pwm_bl);
+		pr_err("[%s] failed to request pwm, rc=%d\n", panel->name,
+			rc);
+		kfree(pwm_cfg);
+		return rc;
+	}
+
+	rc = dsi_panel_bl_parse_pwm_config(panel, pwm_cfg);
+	if (rc) {
+		pr_err("[%s] failed to parse pwm config, rc=%d\n",
+		       panel->name, rc);
+		dsi_panel_pwm_bl_unregister(bl);
+		return rc;
+	}
+
+	bl->priv = pwm_cfg;
+	bl->unregister = dsi_panel_pwm_bl_unregister;
+
+	return 0;
+}
+
+static int dsi_panel_bl_parse_lut(struct device *parent,
+	struct device_node *of_node, const char *bl_lut_prop_name,
+	u32 brightness_max_level, u16 **lut)
+{
+	u32 len = 0;
+	u32 i = 0;
+	u32 rc = 0;
+	const __be32 *val = 0;
+	struct property *prop = NULL;
+	u32 lut_length = brightness_max_level + 1;
+	u16 *bl_lut_tmp = NULL;
+
+	if (!of_node || !bl_lut_prop_name || !lut)
+		return -EINVAL;
+
+	if (*lut) {
+		pr_warn("LUT for %s already exists, freeing before reparsing\n",
+			bl_lut_prop_name);
+		devm_kfree(parent, *lut);
+		*lut = NULL;
+	}
+
+	prop = of_find_property(of_node, bl_lut_prop_name, &len);
+	if (!prop)
+		goto done; /* LUT is unspecified */
+
+	len /= sizeof(u32);
+	if (len != lut_length) {
+		pr_warn("%s length %d doesn't match brightness_max_level + 1 %d\n",
+			bl_lut_prop_name, len, lut_length);
+		goto done;
+	}
+
+	pr_debug("%s length %d\n", bl_lut_prop_name, lut_length);
+	bl_lut_tmp = devm_kmalloc(parent, sizeof(u16) * lut_length, GFP_KERNEL);
+	if (bl_lut_tmp == NULL) {
+		rc = -ENOMEM;
+		goto done;
+	}
+
+	val = prop->value;
+	for (i = 0; i < len; i++)
+		bl_lut_tmp[i] = (u16)(be32_to_cpup(val++) & 0xffff);
+
+	*lut = bl_lut_tmp;
+
+done:
+	return rc;
+}
+
+static void dsi_panel_bl_hbm_free(struct device *dev,
+	struct dsi_backlight_config *bl)
+{
+	u32 i = 0;
+	struct hbm_data *hbm = bl->hbm;
+
+	if (!hbm)
+		return;
+
+	if (hbm->dimming_workq) {
+		dsi_backlight_hbm_dimming_stop(bl);
+		flush_workqueue(hbm->dimming_workq);
+		destroy_workqueue(hbm->dimming_workq);
+	}
+
+	dsi_panel_destroy_cmd_packets(&hbm->exit_cmd);
+	dsi_panel_dealloc_cmd_packets(&hbm->exit_cmd);
+	dsi_panel_destroy_cmd_packets(&hbm->exit_dimming_stop_cmd);
+	dsi_panel_dealloc_cmd_packets(&hbm->exit_dimming_stop_cmd);
+
+	dsi_panel_destroy_cmd_packets(&hbm->irc_unlock_cmd);
+	dsi_panel_destroy_cmd_packets(&hbm->irc_lock_cmd);
+	kfree(hbm->irc_data);
+
+	for (i = 0; i < hbm->num_ranges; i++) {
+		dsi_panel_destroy_cmd_packets(&hbm->ranges[i].entry_cmd);
+		dsi_panel_dealloc_cmd_packets(&hbm->ranges[i].entry_cmd);
+
+		dsi_panel_destroy_cmd_packets(&hbm->ranges[i].dimming_stop_cmd);
+		dsi_panel_dealloc_cmd_packets(&hbm->ranges[i].dimming_stop_cmd);
+	}
+
+	devm_kfree(dev, hbm);
+	bl->hbm = NULL;
+}
+
+static int dsi_panel_bl_parse_hbm(struct device *parent,
+		struct dsi_backlight_config *bl, struct device_node *of_node)
+{
+	struct device_node *hbm_ranges_np;
+	struct device_node *child_np;
+	struct dsi_panel *panel = container_of(bl, struct dsi_panel, bl_config);
+	u32 rc = 0;
+	u32 i = 0;
+	u32 num_ranges = 0;
+	u32 val = 0;
+	bool dimming_used = false;
+
+	panel->hbm_mode = HBM_MODE_OFF;
+
+	if (bl->hbm) {
+		pr_warn("HBM data already parsed, freeing before reparsing\n");
+		dsi_panel_bl_hbm_free(parent, bl);
+	}
+
+	hbm_ranges_np = of_get_child_by_name(of_node, "google,hbm-ranges");
+	if (!hbm_ranges_np) {
+		pr_info("HBM modes list not found\n");
+		return 0;
+	}
+
+	num_ranges = of_get_child_count(hbm_ranges_np);
+	if (!num_ranges || (num_ranges > HBM_RANGE_MAX)) {
+		pr_err("Invalid number of HBM modes: %d\n", num_ranges);
+		return -EINVAL;
+	}
+
+	bl->hbm = devm_kzalloc(parent, sizeof(struct hbm_data), GFP_KERNEL);
+	if (bl->hbm == NULL) {
+		pr_err("Failed to allocate memory for HBM data\n");
+		return -ENOMEM;
+	}
+
+	rc = dsi_panel_parse_dt_cmd_set(hbm_ranges_np,
+		"google,dsi-hbm-exit-command",
+		"google,dsi-hbm-commands-state", &bl->hbm->exit_cmd);
+	if (rc)
+		pr_info("Unable to parse optional dsi-hbm-exit-command\n");
+
+	bl->hbm->num_ranges = num_ranges;
+
+	rc = of_property_read_u32(hbm_ranges_np,
+		"google,dsi-hbm-exit-num-dimming-frames", &val);
+	if (rc) {
+		pr_debug("Unable to parse optional num-dimming-frames\n");
+		bl->hbm->exit_num_dimming_frames = 0;
+	} else {
+		bl->hbm->exit_num_dimming_frames = val;
+	}
+
+	rc = dsi_panel_parse_dt_cmd_set(hbm_ranges_np,
+		"google,dsi-hbm-exit-dimming-stop-command",
+		"google,dsi-hbm-commands-state",
+		&bl->hbm->exit_dimming_stop_cmd);
+	if (rc)
+		pr_debug("Unable to parse optional dsi-hbm-exit-dimming-stop-command\n");
+
+	if ((bl->hbm->exit_dimming_stop_cmd.count &&
+		 !bl->hbm->exit_num_dimming_frames) ||
+		(!bl->hbm->exit_dimming_stop_cmd.count &&
+		 bl->hbm->exit_num_dimming_frames)) {
+		pr_err("HBM dimming requires both stop command and number of frames.\n");
+		goto exit_free;
+	}
+
+	rc = of_property_read_u32(hbm_ranges_np,
+		"google,dsi-irc-addr", &val);
+	if (rc) {
+		pr_debug("Unable to parse dsi-irc-addr\n");
+		bl->hbm->irc_addr = 0;
+	} else {
+		bl->hbm->irc_addr = val;
+		rc = of_property_read_u32(hbm_ranges_np,
+			"google,dsi-irc-bit-offset", &val);
+		if (rc) {
+			bl->hbm->irc_bit_offset = 0;
+			bl->hbm->irc_addr = 0;
+			pr_warn("Unable to parse dsi-irc-bit-offset\n");
+		} else {
+			bl->hbm->irc_bit_offset = val;
+		}
+
+		rc = dsi_panel_parse_dt_cmd_set(hbm_ranges_np,
+			"google,dsi-irc-unlock-command",
+			"google,dsi-irc-unlock-commands-state",
+			&bl->hbm->irc_unlock_cmd);
+		if (rc)
+			pr_debug("Unable to parse optional dsi-irc-unlock-command\n");
+
+		rc = dsi_panel_parse_dt_cmd_set(hbm_ranges_np,
+			"google,dsi-irc-lock-command",
+			"google,dsi-irc-lock-commands-state",
+			&bl->hbm->irc_lock_cmd);
+		if (rc)
+			pr_debug("Unable to parse optional dsi-irc-lock-command\n");
+
+		if (!bl->hbm->irc_unlock_cmd.count != !bl->hbm->irc_lock_cmd.count) {
+			dsi_panel_destroy_cmd_packets(&bl->hbm->irc_unlock_cmd);
+			dsi_panel_destroy_cmd_packets(&bl->hbm->irc_lock_cmd);
+			bl->hbm->irc_addr = 0;
+			pr_warn("Unable to get a pair of dsi-irc-unlock/lock command\n");
+		}
+	}
+
+	for_each_child_of_node(hbm_ranges_np, child_np) {
+		rc = dsi_panel_bl_parse_hbm_node(parent, bl,
+			child_np, bl->hbm->ranges + i);
+		if (rc) {
+			pr_err("Failed to parse HBM range %d of %d\n",
+				i + 1, num_ranges);
+			goto exit_free;
+		}
+		i++;
+	}
+
+	for (i = 0; i < num_ranges - 1; i++) {
+		/* Make sure ranges are sorted and not overlapping */
+		if (bl->hbm->ranges[i].user_bri_start >=
+				bl->hbm->ranges[i + 1].user_bri_start) {
+			pr_err("HBM ranges must be sorted by hbm-brightness-threshold\n");
+			rc = -EINVAL;
+			goto exit_free;
+		}
+
+		if (bl->hbm->ranges[i].num_dimming_frames)
+			dimming_used = true;
+
+		/* Fill in user_bri_end for each range */
+		bl->hbm->ranges[i].user_bri_end =
+			bl->hbm->ranges[i + 1].user_bri_start - 1;
+	}
+
+	if (bl->hbm->ranges[num_ranges - 1].num_dimming_frames ||
+		bl->hbm->exit_num_dimming_frames)
+		dimming_used = true;
+
+
+	if (dimming_used) {
+		bl->hbm->dimming_workq =
+			create_singlethread_workqueue("dsi_dimming_workq");
+		if (!bl->hbm->dimming_workq)
+			pr_err("failed to create hbm dimming workq!\n");
+		else
+			INIT_WORK(&bl->hbm->dimming_work,
+				dsi_backlight_hbm_dimming_work);
+	}
+
+	bl->hbm->ranges[i].user_bri_end = bl->brightness_max_level;
+	bl->hbm->cur_range = HBM_RANGE_MAX;
+	bl->hbm->dimming_active = false;
+	bl->hbm->dimming_frames_total = 0;
+	bl->hbm->dimming_frames_left = 0;
+	bl->hbm->panel = panel;
+
+	return 0;
+
+exit_free:
+	dsi_panel_bl_hbm_free(parent, bl);
+	return rc;
+}
+
+static int dsi_panel_bl_find_range(struct dsi_backlight_config *bl,
+		int brightness, u32 *range)
+{
+	u32 i;
+
+	if (!bl || !bl->bl_notifier || !range)
+		return -EINVAL;
+
+	for (i = 0; i < bl->bl_notifier->num_ranges; i++) {
+		if (brightness <= bl->bl_notifier->ranges[i]) {
+			*range = i;
+			return 0;
+		}
+	}
+
+	return -EINVAL;
+}
+
+static void dsi_panel_bl_notifier_free(struct device *dev,
+	struct dsi_backlight_config *bl)
+{
+	struct bl_notifier_data *bl_notifier = bl->bl_notifier;
+
+	if (!bl_notifier)
+		return;
+
+	devm_kfree(dev, bl_notifier);
+	bl->bl_notifier = NULL;
+}
+
+static int dsi_panel_bl_parse_ranges(struct device *parent,
+		struct dsi_backlight_config *bl, struct device_node *of_node)
+{
+	int num_ranges = 0;
+
+	bl->bl_notifier = devm_kzalloc(parent, sizeof(struct bl_notifier_data), GFP_KERNEL);
+	if (bl->bl_notifier == NULL) {
+		pr_err("Failed to allocate memory for  bl_notifier_data\n");
+		return -ENOMEM;
+	}
+
+	num_ranges = of_property_read_variable_u32_array(of_node,
+						"qcom,mdss-dsi-bl-notifier-ranges",
+						(u32 *)&bl->bl_notifier->ranges,
+						0,
+						BL_RANGE_MAX);
+	if (num_ranges >= 0) {
+		bl->bl_notifier->num_ranges = num_ranges;
+	} else {
+		dsi_panel_bl_notifier_free(parent, bl);
+		pr_debug("Unable to parse optional backlight ranges (%d)\n", num_ranges);
+		return num_ranges;
+	}
+
+	return 0;
+}
+
+int dsi_panel_bl_parse_config(struct device *parent, struct dsi_backlight_config *bl)
+{
+	struct dsi_panel *panel = container_of(bl, struct dsi_panel, bl_config);
+	int rc = 0;
+	u32 val = 0;
+	const char *bl_type;
+	const char *data;
+	struct dsi_parser_utils *utils = &panel->utils;
+	char *bl_name;
+
+	if (!strcmp(panel->type, "primary"))
+		bl_name = "qcom,mdss-dsi-bl-pmic-control-type";
+	else
+		bl_name = "qcom,mdss-dsi-sec-bl-pmic-control-type";
+
+	bl_type = utils->get_property(utils->data, bl_name, NULL);
+	if (!bl_type) {
+		bl->type = DSI_BACKLIGHT_UNKNOWN;
+	} else if (!strcmp(bl_type, "bl_ctrl_pwm")) {
+		bl->type = DSI_BACKLIGHT_PWM;
+	} else if (!strcmp(bl_type, "bl_ctrl_wled")) {
+		bl->type = DSI_BACKLIGHT_WLED;
+	} else if (!strcmp(bl_type, "bl_ctrl_dcs")) {
+		bl->type = DSI_BACKLIGHT_DCS;
+	} else if (!strcmp(bl_type, "bl_ctrl_external")) {
+		bl->type = DSI_BACKLIGHT_EXTERNAL;
+	} else {
+		pr_debug("[%s] bl-pmic-control-type unknown-%s\n",
+			 panel->name, bl_type);
+		bl->type = DSI_BACKLIGHT_UNKNOWN;
+	}
+	data = utils->get_property(utils->data, "qcom,bl-update-flag", NULL);
+	if (!data) {
+		panel->bl_config.bl_update = BL_UPDATE_NONE;
+	} else if (!strcmp(data, "delay_until_first_frame")) {
+		panel->bl_config.bl_update = BL_UPDATE_DELAY_UNTIL_FIRST_FRAME;
+	} else {
+		pr_debug("[%s] No valid bl-update-flag: %s\n",
+						panel->name, data);
+		panel->bl_config.bl_update = BL_UPDATE_NONE;
+	}
+	panel->bl_config.bl_scale = MAX_BL_SCALE_LEVEL;
+	panel->bl_config.bl_scale_sv = MAX_SV_BL_SCALE_LEVEL;
+	rc = utils->read_u32(utils->data, "qcom,mdss-dsi-bl-min-level", &val);
+	if (rc) {
+		pr_debug("[%s] bl-min-level unspecified, defaulting to zero\n",
+			 panel->name);
+		bl->bl_min_level = 0;
+	} else {
+		bl->bl_min_level = val;
+	}
+
+	rc = utils->read_u32(utils->data, "qcom,mdss-dsi-bl-max-level", &val);
+	if (rc) {
+		pr_debug("[%s] bl-max-level unspecified, defaulting to max level\n",
+			 panel->name);
+		bl->bl_max_level = MAX_BL_LEVEL;
+	} else {
+		bl->bl_max_level = val;
+	}
+
+	rc = utils->read_u32(utils->data, "qcom,mdss-brightness-max-level",
+		&val);
+	if (rc) {
+		pr_debug("[%s] brigheness-max-level unspecified, defaulting to 255\n",
+			 panel->name);
+		bl->brightness_max_level = 255;
+	} else {
+		bl->brightness_max_level = val;
+	}
+
+	rc = dsi_panel_bl_parse_lut(parent, utils->data, "qcom,mdss-dsi-bl-lut",
+		bl->brightness_max_level, &bl->lut);
+	if (rc) {
+		pr_err("[%s] failed to create backlight LUT, rc=%d\n",
+			panel->name, rc);
+		goto error;
+	}
+	pr_debug("[%s] bl-lut %sused\n", panel->name, bl->lut ? "" : "un");
+
+	bl->dimming_mode = utils->read_bool(utils->data,
+				"google,dsi-panel-dimming-enable");
+
+	rc = dsi_panel_bl_parse_hbm(parent, bl, utils->data);
+	if (rc)
+		pr_err("[%s] error while parsing high brightness mode (hbm) details, rc=%d\n",
+			panel->name, rc);
+
+	rc = dsi_panel_bl_parse_dynamic_elvss(parent, bl, utils->data);
+	if (rc)
+		pr_err("[%s] error while parsing dynamic elvss details, rc=%d\n",
+			panel->name, rc);
+
+	rc = dsi_panel_bl_parse_ranges(parent, bl, utils->data);
+	if (rc)
+		pr_debug("[%s] error while parsing backlight ranges, rc=%d\n",
+			panel->name, rc);
+
+	rc = utils->read_u32(utils->data, "google,dsi-bl-cmd-high-byte-offset",
+		&val);
+	if (rc) {
+		pr_debug("[%s] dsi-bl-cmd-high-byte-offset unspecified, defaulting to 8\n",
+			 panel->name);
+		bl->high_byte_offset = 8;
+	} else {
+		bl->high_byte_offset = val;
+	}
+
+	bl->en_gpio = utils->get_named_gpio(utils->data,
+					      "qcom,platform-bklight-en-gpio",
+					      0);
+	if (!gpio_is_valid(bl->en_gpio)) {
+		if (bl->en_gpio == -EPROBE_DEFER) {
+			pr_debug("[%s] failed to get bklt gpio, rc=%d\n",
+					panel->name, rc);
+			rc = -EPROBE_DEFER;
+			goto error;
+		} else {
+			pr_debug("[%s] failed to get bklt gpio, rc=%d\n",
+					 panel->name, rc);
+			rc = 0;
+			goto error;
+		}
+	}
+
+error:
+	return rc;
+}
+
+static int dsi_panel_bl_read_brightness(struct dsi_panel *panel,
+		struct dsi_backlight_config *bl_cfg, int *lvl)
+{
+	u32 rc;
+	u8 buf[BL_BRIGHTNESS_BUF_SIZE];
+
+	rc = mipi_dsi_dcs_read(&panel->mipi_device,
+		MIPI_DCS_GET_DISPLAY_BRIGHTNESS, buf, BL_BRIGHTNESS_BUF_SIZE);
+
+	if (rc <= 0 || rc > BL_BRIGHTNESS_BUF_SIZE) {
+		pr_err("mipi_dsi_dcs_read error: %d\n", rc);
+		return -EIO;
+	}
+
+	if (rc == 1)
+		*lvl = buf[0];
+	else if (rc == 2)
+		*lvl = be16_to_cpu(*(const __be16 *)buf);
+	else {
+		pr_err("unexpected buffer size: %d\n", rc);
+		return -EIO;
+	}
+
+	/* Some panels may not clear non-functional bits. */
+	*lvl &= (1 << fls(bl_cfg->bl_max_level)) - 1;
+
+	return 0;
+}
+int dsi_panel_bl_brightness_handoff(struct dsi_panel *panel)
+{
+	struct dsi_backlight_config *bl_cfg;
+	struct backlight_device *bl_device;
+	int bl_lvl = 0, brightness, rc;
+
+	if (!panel || !panel->bl_config.bl_device)
+		return -EINVAL;
+
+	bl_cfg = &panel->bl_config;
+	bl_device = bl_cfg->bl_device;
+
+	rc = dsi_panel_bl_read_brightness(panel, bl_cfg, &bl_lvl);
+	if (rc) {
+		pr_err("Failed to read brightness from panel.\n");
+		return rc;
+	}
+
+	rc = dsi_backlight_lerp(bl_cfg->bl_min_level, bl_cfg->bl_max_level, 1,
+		bl_cfg->brightness_max_level, bl_lvl, &brightness);
+	if (rc) {
+		pr_err("Failed to map brightness to user space.\n");
+		return rc;
+	}
+
+	pr_debug("brightness 0x%x to user space %d\n", bl_lvl, brightness);
+	bl_device->props.brightness = brightness;
+
+	return rc;
+}
+
+int dsi_panel_bl_update_irc(struct dsi_backlight_config *bl, bool enable)
+{
+	struct hbm_data *hbm = bl->hbm;
+	int rc = 0;
+	u32 byte_offset;
+	u32 bit_mask;
+	u32 irc_data_size;
+
+	if (!hbm || hbm->irc_addr == 0)
+		return -EOPNOTSUPP;
+
+	byte_offset = hbm->irc_bit_offset / BITS_PER_BYTE;
+	bit_mask = BIT(hbm->irc_bit_offset % BITS_PER_BYTE);
+	irc_data_size = byte_offset + 1;
+
+	pr_info("irc update: %d\n", enable);
+	dsi_panel_cmd_set_transfer(hbm->panel, &hbm->irc_unlock_cmd);
+	if (hbm->irc_data == NULL) {
+		hbm->irc_data = kzalloc(irc_data_size, GFP_KERNEL);
+		if (hbm->irc_data == NULL) {
+			pr_err("failed to alloc irc_data.\n");
+			goto done;
+		}
+
+		rc = mipi_dsi_dcs_read(&hbm->panel->mipi_device, hbm->irc_addr,
+				hbm->irc_data, irc_data_size);
+		if (rc != irc_data_size) {
+			pr_err("failed to read irc.\n");
+			goto done;
+		}
+		pr_info("Read back irc initial configuration\n");
+	}
+
+	if (enable)
+		hbm->irc_data[byte_offset] |= bit_mask;
+	else
+		hbm->irc_data[byte_offset] &= ~bit_mask;
+
+	rc = mipi_dsi_dcs_write(&hbm->panel->mipi_device,
+			hbm->irc_addr, hbm->irc_data, irc_data_size);
+
+	if (rc)
+		pr_err("failed to send irc cmd.\n");
+done:
+	dsi_panel_cmd_set_transfer(hbm->panel, &hbm->irc_lock_cmd);
+	return rc;
+}
diff --git a/msm/dsi/dsi_clk.h b/msm/dsi/dsi_clk.h
index fcd352d..ccc1ba7 100644
--- a/msm/dsi/dsi_clk.h
+++ b/msm/dsi/dsi_clk.h
@@ -268,8 +268,7 @@
  *
  * return: error code in case of failure or 0 for success.
  */
-int dsi_display_clk_ctrl(void *handle,
-	enum dsi_clk_type clk_type, enum dsi_clk_state clk_state);
+int dsi_display_clk_ctrl(void *handle, u32 clk_type, u32 clk_state);
 
 /**
  * dsi_clk_set_link_frequencies() - set frequencies for link clks
diff --git a/msm/dsi/dsi_clk_manager.c b/msm/dsi/dsi_clk_manager.c
index ec2df96..7983ff0 100644
--- a/msm/dsi/dsi_clk_manager.c
+++ b/msm/dsi/dsi_clk_manager.c
@@ -1291,8 +1291,7 @@
 	return rc;
 }
 
-int dsi_display_clk_ctrl(void *handle,
-	enum dsi_clk_type clk_type, enum dsi_clk_state clk_state)
+int dsi_display_clk_ctrl(void *handle, u32 clk_type, u32 clk_state)
 {
 	int rc = 0;
 
diff --git a/msm/dsi/dsi_ctrl.c b/msm/dsi/dsi_ctrl.c
index c92bfbc..54d5dbf 100644
--- a/msm/dsi/dsi_ctrl.c
+++ b/msm/dsi/dsi_ctrl.c
@@ -1053,36 +1053,25 @@
 	return rc;
 }
 
-static int dsi_ctrl_copy_and_pad_cmd(struct dsi_ctrl *dsi_ctrl,
-				     const struct mipi_dsi_packet *packet,
-				     u8 **buffer,
-				     u32 *size)
+static int dsi_ctrl_copy_and_pad_cmd(const struct mipi_dsi_packet *packet,
+				     u8 *buf, size_t len)
 {
 	int rc = 0;
-	u8 *buf = NULL;
-	u32 len, i;
 	u8 cmd_type = 0;
 
-	len = packet->size;
-	len += 0x3; len &= ~0x03; /* Align to 32 bits */
+	if (unlikely(len < packet->size))
+		return -EINVAL;
 
-	buf = devm_kzalloc(&dsi_ctrl->pdev->dev, len * sizeof(u8), GFP_KERNEL);
-	if (!buf)
-		return -ENOMEM;
-
-	for (i = 0; i < len; i++) {
-		if (i >= packet->size)
-			buf[i] = 0xFF;
-		else if (i < sizeof(packet->header))
-			buf[i] = packet->header[i];
-		else
-			buf[i] = packet->payload[i - sizeof(packet->header)];
-	}
+	memcpy(buf, packet->header, sizeof(packet->header));
+	if (packet->payload_length)
+		memcpy(buf + sizeof(packet->header), packet->payload,
+		       packet->payload_length);
+	if (packet->size < len)
+		memset(buf + packet->size, 0xFF, len - packet->size);
 
 	if (packet->payload_length > 0)
 		buf[3] |= BIT(6);
 
-
 	/* send embedded BTA for read commands */
 	cmd_type = buf[2] & 0x3f;
 	if ((cmd_type == MIPI_DSI_DCS_READ) ||
@@ -1091,9 +1080,6 @@
 			(cmd_type == MIPI_DSI_GENERIC_READ_REQUEST_2_PARAM))
 		buf[3] |= BIT(5);
 
-	*buffer = buf;
-	*size = len;
-
 	return rc;
 }
 
@@ -1215,11 +1201,13 @@
 			DSI_CTRL_ERR(dsi_ctrl, "Cannot transfer,size is greater than 4096\n");
 			return -ENOTSUPP;
 		}
-	}
+	} else if (*flags & DSI_CTRL_CMD_FETCH_MEMORY) {
+		const size_t transfer_size = dsi_ctrl->cmd_len + cmd_len + 4;
 
-	if (*flags & DSI_CTRL_CMD_FETCH_MEMORY) {
-		if ((dsi_ctrl->cmd_len + cmd_len + 4) > SZ_4K) {
-			DSI_CTRL_ERR(dsi_ctrl, "Cannot transfer,size is greater than 4096\n");
+		if (transfer_size > DSI_EMBEDDED_MODE_DMA_MAX_SIZE_BYTES) {
+			DSI_CTRL_ERR(dsi_ctrl,"Cannot transfer, size: %zu is greater than %d\n",
+			       transfer_size,
+			       DSI_EMBEDDED_MODE_DMA_MAX_SIZE_BYTES);
 			return -ENOTSUPP;
 		}
 	}
@@ -1370,6 +1358,28 @@
 		*flags &= ~DSI_CTRL_CMD_ASYNC_WAIT;
 }
 
+static int dsi_msm_create_packet(struct mipi_dsi_packet *packet,
+			const struct mipi_dsi_msg *msg)
+{
+	int rc;
+	u8 tmp;
+	if (!packet)
+		return -EINVAL;
+
+	rc = mipi_dsi_create_packet(packet, msg);
+	if (rc)
+		return rc;
+
+	/* MSM chipsets swap the Data ID with the Word Count. So we need to shift
+	 * the header over. */
+	tmp = packet->header[0];
+	packet->header[0] = packet->header[1];
+	packet->header[1] = packet->header[2];
+	packet->header[2] = tmp;
+
+	return 0;
+}
+
 static int dsi_message_tx(struct dsi_ctrl *dsi_ctrl,
 			  const struct mipi_dsi_msg *msg,
 			  u32 *flags)
@@ -1378,9 +1388,8 @@
 	struct mipi_dsi_packet packet;
 	struct dsi_ctrl_cmd_dma_fifo_info cmd;
 	struct dsi_ctrl_cmd_dma_info cmd_mem;
-	u32 length = 0;
+	u32 length;
 	u8 *buffer = NULL;
-	u32 cnt = 0;
 	u8 *cmdbuf;
 
 	/* Select the tx mode to transfer the command */
@@ -1400,6 +1409,10 @@
 	if (dsi_ctrl->dma_wait_queued)
 		dsi_ctrl_flush_cmd_dma_queue(dsi_ctrl);
 
+	DSI_CTRL_DEBUG(dsi_ctrl, "cmd tx type=%02x cmd=%02x len=%d last=%d\n",
+		 msg->type, msg->tx_len ? *((u8 *)msg->tx_buf) : 0, msg->tx_len,
+		 (msg->flags & MIPI_DSI_MSG_LASTCOMMAND) != 0);
+
 	if (*flags & DSI_CTRL_CMD_NON_EMBEDDED_MODE) {
 		cmd_mem.offset = dsi_ctrl->cmd_buffer_iova;
 		cmd_mem.en_broadcast = (*flags & DSI_CTRL_CMD_BROADCAST) ?
@@ -1420,26 +1433,29 @@
 		goto kickoff;
 	}
 
-	rc = mipi_dsi_create_packet(&packet, msg);
+	rc = dsi_msm_create_packet(&packet, msg);
 	if (rc) {
 		DSI_CTRL_ERR(dsi_ctrl, "Failed to create message packet, rc=%d\n",
 				rc);
 		goto error;
 	}
 
-	rc = dsi_ctrl_copy_and_pad_cmd(dsi_ctrl,
-			&packet,
-			&buffer,
-			&length);
-	if (rc) {
-		DSI_CTRL_ERR(dsi_ctrl, "failed to copy message, rc=%d\n", rc);
-		goto error;
-	}
+	length = ALIGN(packet.size, 4);
 
 	if ((msg->flags & MIPI_DSI_MSG_LASTCOMMAND))
-		buffer[3] |= BIT(7);//set the last cmd bit in header.
+		packet.header[3] |= BIT(7);//set the last cmd bit in header.
 
 	if (*flags & DSI_CTRL_CMD_FETCH_MEMORY) {
+		msm_gem_sync(dsi_ctrl->tx_cmd_buf);
+		cmdbuf = dsi_ctrl->vaddr + dsi_ctrl->cmd_len;
+
+		rc = dsi_ctrl_copy_and_pad_cmd(&packet, cmdbuf, length);
+		if (rc) {
+			DSI_CTRL_ERR(dsi_ctrl, "failed to copy message, rc=%d\n",
+					rc);
+			goto error;
+		}
+
 		/* Embedded mode config is selected */
 		cmd_mem.offset = dsi_ctrl->cmd_buffer_iova;
 		cmd_mem.en_broadcast = (*flags & DSI_CTRL_CMD_BROADCAST) ?
@@ -1449,12 +1465,6 @@
 		cmd_mem.use_lpm = (msg->flags & MIPI_DSI_MSG_USE_LPM) ?
 			true : false;
 
-		cmdbuf = (u8 *)(dsi_ctrl->vaddr);
-
-		msm_gem_sync(dsi_ctrl->tx_cmd_buf);
-		for (cnt = 0; cnt < length; cnt++)
-			cmdbuf[dsi_ctrl->cmd_len + cnt] = buffer[cnt];
-
 		dsi_ctrl->cmd_len += length;
 
 		if (!(msg->flags & MIPI_DSI_MSG_LASTCOMMAND)) {
@@ -1465,6 +1475,20 @@
 		}
 
 	} else if (*flags & DSI_CTRL_CMD_FIFO_STORE) {
+		buffer = devm_kzalloc(&dsi_ctrl->pdev->dev, length,
+					   GFP_KERNEL);
+		if (!buffer) {
+			rc = -ENOMEM;
+			goto error;
+		}
+
+		rc = dsi_ctrl_copy_and_pad_cmd(&packet, buffer, length);
+		if (rc) {
+			DSI_CTRL_ERR(dsi_ctrl, "failed to copy message, rc=%d\n",
+					rc);
+			goto error;
+		}
+
 		cmd.command =  (u32 *)buffer;
 		cmd.size = length;
 		cmd.en_broadcast = (*flags & DSI_CTRL_CMD_BROADCAST) ?
@@ -1568,14 +1592,16 @@
 			  const struct mipi_dsi_msg *msg,
 			  u32 *flags)
 {
-	int rc = 0;
+	int rc = 0, i = 0;
 	u32 rd_pkt_size, total_read_len, hw_read_cnt;
 	u32 current_read_len = 0, total_bytes_read = 0;
 	bool short_resp = false;
 	bool read_done = false;
 	u32 dlen, diff, rlen;
-	unsigned char *buff;
+	unsigned char *buff = NULL;
 	char cmd;
+	u32 buffer_sz = 0, header_offset = 0;
+	u8 *head = NULL;
 
 	if (!msg) {
 		DSI_CTRL_ERR(dsi_ctrl, "Invalid msg\n");
@@ -1588,6 +1614,13 @@
 		short_resp = true;
 		rd_pkt_size = msg->rx_len;
 		total_read_len = 4;
+
+		/*
+		 * buffer size: header + data
+		 * No 32 bits alignment issue, thus offset is 0
+		 */
+		buffer_sz = 4;
+
 	} else {
 		short_resp = false;
 		current_read_len = 10;
@@ -1597,8 +1630,30 @@
 			rd_pkt_size = current_read_len;
 
 		total_read_len = current_read_len + 6;
+		/*
+		 * buffer size: header + data + footer, rounded up to 4 bytes
+		 * Out of bound can occurs is rx_len is not aligned to size 4.
+		 * We are reading 32 bits registers, and converting
+		 * the data to CPU endianness, thus inserting garbage data
+		 * at the beginning of buffer.
+		 */
+		buffer_sz = ALIGN(4 + msg->rx_len + 2, 4);
+		if (buffer_sz < 16)
+			buffer_sz = 16;
 	}
-	buff = msg->rx_buf;
+
+	DSI_CTRL_DEBUG(dsi_ctrl, "short_resp %d, msg->rx_len %zd, rd_pkt_size %u\n",
+			short_resp, msg->rx_len, rd_pkt_size);
+
+	DSI_CTRL_DEBUG(dsi_ctrl, "total_read_len %u, buffer_sz %u\n",
+			total_read_len, buffer_sz);
+
+	buff = kzalloc(buffer_sz, GFP_KERNEL);
+	if (!buff) {
+		rc = -ENOMEM;
+		goto error;
+	}
+	head = buff;
 
 	while (!read_done) {
 		rc = dsi_set_max_return_size(dsi_ctrl, msg, rd_pkt_size);
@@ -1656,13 +1711,22 @@
 		}
 	}
 
+	buff = head;
+	for (i = 0; i < buffer_sz; i += 4)
+		DSI_CTRL_DEBUG(dsi_ctrl, "buffer[%d-%d] = %08x\n",
+			 i, i + 3, *((u32 *)&buff[i]));
+
 	if (hw_read_cnt < 16 && !short_resp)
-		buff = msg->rx_buf + (16 - hw_read_cnt);
+		header_offset = (16 - hw_read_cnt);
 	else
-		buff = msg->rx_buf;
+		header_offset = 0;
+
+	DSI_CTRL_DEBUG(dsi_ctrl, "hw_read_cnt %d, header_offset %d\n",
+			hw_read_cnt, header_offset);
 
 	/* parse the data read from panel */
-	cmd = buff[0];
+	cmd = buff[header_offset];
+	DSI_CTRL_DEBUG(dsi_ctrl, "response type %d\n", cmd);
 	switch (cmd) {
 	case MIPI_DSI_RX_ACKNOWLEDGE_AND_ERROR_REPORT:
 		DSI_CTRL_ERR(dsi_ctrl, "Rx ACK_ERROR 0x%x\n", cmd);
@@ -1670,15 +1734,15 @@
 		break;
 	case MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_1BYTE:
 	case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_1BYTE:
-		rc = dsi_parse_short_read1_resp(msg, buff);
+		rc = dsi_parse_short_read1_resp(msg, &buff[header_offset]);
 		break;
 	case MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_2BYTE:
 	case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_2BYTE:
-		rc = dsi_parse_short_read2_resp(msg, buff);
+		rc = dsi_parse_short_read2_resp(msg, &buff[header_offset]);
 		break;
 	case MIPI_DSI_RX_GENERIC_LONG_READ_RESPONSE:
 	case MIPI_DSI_RX_DCS_LONG_READ_RESPONSE:
-		rc = dsi_parse_long_read_resp(msg, buff);
+		rc = dsi_parse_long_read_resp(msg, &buff[header_offset]);
 		break;
 	default:
 		DSI_CTRL_WARN(dsi_ctrl, "Invalid response: 0x%x\n", cmd);
@@ -1686,6 +1750,7 @@
 	}
 
 error:
+	kfree(buff);
 	return rc;
 }
 
@@ -3219,9 +3284,11 @@
 
 	if (*flags & DSI_CTRL_CMD_READ) {
 		rc = dsi_message_rx(dsi_ctrl, msg, flags);
-		if (rc <= 0)
-			DSI_CTRL_ERR(dsi_ctrl, "read message failed read length, rc=%d\n",
+		if (rc < 0)
+			DSI_CTRL_ERR(dsi_ctrl, "read message failed read, rc=%d\n",
 					rc);
+		else if (rc == 0)
+			DSI_CTRL_WARN(dsi_ctrl, "empty message back\n");
 	} else {
 		rc = dsi_message_tx(dsi_ctrl, msg, flags);
 		if (rc)
diff --git a/msm/dsi/dsi_defs.h b/msm/dsi/dsi_defs.h
index 5179edf..476a84e 100644
--- a/msm/dsi/dsi_defs.h
+++ b/msm/dsi/dsi_defs.h
@@ -106,7 +106,7 @@
 	DSI_MODE_FLAG_VRR			= BIT(4),
 	DSI_MODE_FLAG_POMS			= BIT(5),
 	DSI_MODE_FLAG_DYN_CLK			= BIT(6),
-	DSI_MODE_FLAG_DMS_FPS                   = BIT(7),
+	DSI_MODE_FLAG_DMS_FPS			= BIT(7),
 };
 
 /**
@@ -301,6 +301,7 @@
 	DSI_CMD_SET_LP1,
 	DSI_CMD_SET_LP2,
 	DSI_CMD_SET_NOLP,
+	DSI_CMD_SET_POST_NOLP,
 	DSI_CMD_SET_PPS,
 	DSI_CMD_SET_ROI,
 	DSI_CMD_SET_TIMING_SWITCH,
@@ -625,7 +626,10 @@
 	struct msm_display_topology topology;
 	struct msm_display_dsc_info dsc;
 	bool dsc_enabled;
+	bool pps_created;
 	struct msm_roi_caps roi_caps;
+
+	void *switch_data;
 };
 
 /**
diff --git a/msm/dsi/dsi_display.c b/msm/dsi/dsi_display.c
index acb1f7d..2cf726b 100644
--- a/msm/dsi/dsi_display.c
+++ b/msm/dsi/dsi_display.c
@@ -20,6 +20,7 @@
 #include "dsi_pwr.h"
 #include "sde_dbg.h"
 #include "dsi_parser.h"
+#include "sde_trace.h"
 
 #define to_dsi_display(x) container_of(x, struct dsi_display, host)
 #define INT_BASE_10 10
@@ -45,6 +46,12 @@
 	{}
 };
 
+static inline bool is_lp_mode(int power_mode)
+{
+	return power_mode == SDE_MODE_DPMS_LP1 ||
+			power_mode == SDE_MODE_DPMS_LP2;
+}
+
 static void dsi_display_mask_ctrl_error_interrupts(struct dsi_display *display,
 			u32 mask, bool enable)
 {
@@ -190,62 +197,6 @@
 	}
 }
 
-int dsi_display_set_backlight(struct drm_connector *connector,
-		void *display, u32 bl_lvl)
-{
-	struct dsi_display *dsi_display = display;
-	struct dsi_panel *panel;
-	u32 bl_scale, bl_scale_sv;
-	u64 bl_temp;
-	int rc = 0;
-
-	if (dsi_display == NULL || dsi_display->panel == NULL)
-		return -EINVAL;
-
-	panel = dsi_display->panel;
-
-	mutex_lock(&panel->panel_lock);
-	if (!dsi_panel_initialized(panel)) {
-		rc = -EINVAL;
-		goto error;
-	}
-
-	panel->bl_config.bl_level = bl_lvl;
-
-	/* scale backlight */
-	bl_scale = panel->bl_config.bl_scale;
-	bl_temp = bl_lvl * bl_scale / MAX_BL_SCALE_LEVEL;
-
-	bl_scale_sv = panel->bl_config.bl_scale_sv;
-	bl_temp = (u32)bl_temp * bl_scale_sv / MAX_SV_BL_SCALE_LEVEL;
-
-	DSI_DEBUG("bl_scale = %u, bl_scale_sv = %u, bl_lvl = %u\n",
-		bl_scale, bl_scale_sv, (u32)bl_temp);
-	rc = dsi_display_clk_ctrl(dsi_display->dsi_clk_handle,
-			DSI_CORE_CLK, DSI_CLK_ON);
-	if (rc) {
-		DSI_ERR("[%s] failed to enable DSI core clocks, rc=%d\n",
-		       dsi_display->name, rc);
-		goto error;
-	}
-
-	rc = dsi_panel_set_backlight(panel, (u32)bl_temp);
-	if (rc)
-		DSI_ERR("unable to set backlight\n");
-
-	rc = dsi_display_clk_ctrl(dsi_display->dsi_clk_handle,
-			DSI_CORE_CLK, DSI_CLK_OFF);
-	if (rc) {
-		DSI_ERR("[%s] failed to disable DSI core clocks, rc=%d\n",
-		       dsi_display->name, rc);
-		goto error;
-	}
-
-error:
-	mutex_unlock(&panel->panel_lock);
-	return rc;
-}
-
 static int dsi_display_cmd_engine_enable(struct dsi_display *display)
 {
 	int rc = 0;
@@ -392,36 +343,59 @@
 static irqreturn_t dsi_display_panel_te_irq_handler(int irq, void *data)
 {
 	struct dsi_display *display = (struct dsi_display *)data;
+	struct dsi_display_te_listener *tl;
 
-	/*
-	 * This irq handler is used for sole purpose of identifying
-	 * ESD attacks on panel and we can safely assume IRQ_HANDLED
-	 * in case of display not being initialized yet
-	 */
-	if (!display)
+	if (unlikely(!display))
 		return IRQ_HANDLED;
 
 	SDE_EVT32(SDE_EVTLOG_FUNC_CASE1);
-	complete_all(&display->esd_te_gate);
+
+	spin_lock(&display->te_lock);
+	list_for_each_entry(tl, &display->te_listeners, head)
+		tl->handler(tl);
+	spin_unlock(&display->te_lock);
+
 	return IRQ_HANDLED;
 }
 
-static void dsi_display_change_te_irq_status(struct dsi_display *display,
-					bool enable)
+int dsi_display_add_te_listener(struct dsi_display *display,
+				struct dsi_display_te_listener *tl)
 {
-	if (!display) {
-		DSI_ERR("Invalid params\n");
-		return;
-	}
+	unsigned long flags;
+	bool needs_enable;
 
-	/* Handle unbalanced irq enable/disable calls */
-	if (enable && !display->is_te_irq_enabled) {
+	if (!display || !tl)
+		return -ENOENT;
+
+	spin_lock_irqsave(&display->te_lock, flags);
+	needs_enable = list_empty(&display->te_listeners);
+	list_add(&tl->head, &display->te_listeners);
+	spin_unlock_irqrestore(&display->te_lock, flags);
+
+	if (needs_enable)
 		enable_irq(gpio_to_irq(display->disp_te_gpio));
-		display->is_te_irq_enabled = true;
-	} else if (!enable && display->is_te_irq_enabled) {
-		disable_irq(gpio_to_irq(display->disp_te_gpio));
-		display->is_te_irq_enabled = false;
-	}
+
+	return 0;
+}
+
+int dsi_display_remove_te_listener(struct dsi_display *display,
+				   struct dsi_display_te_listener *tl)
+{
+	unsigned long flags;
+	unsigned int te_irq;
+
+	if (!display || !tl)
+		return -ENOENT;
+
+	te_irq = gpio_to_irq(display->disp_te_gpio);
+
+	spin_lock_irqsave(&display->te_lock, flags);
+	list_del(&tl->head);
+	if (list_empty(&display->te_listeners))
+		disable_irq_nosync(te_irq);
+	spin_unlock_irqrestore(&display->te_lock, flags);
+
+	return 0;
 }
 
 static void dsi_display_register_te_irq(struct dsi_display *display)
@@ -448,15 +422,16 @@
 		goto error;
 	}
 
-	init_completion(&display->esd_te_gate);
 	te_irq = gpio_to_irq(display->disp_te_gpio);
 
+	spin_lock_init(&display->te_lock);
+	INIT_LIST_HEAD(&display->te_listeners);
+
 	/* Avoid deferred spurious irqs with disable_irq() */
 	irq_set_status_flags(te_irq, IRQ_DISABLE_UNLAZY);
 
 	rc = devm_request_irq(dev, te_irq, dsi_display_panel_te_irq_handler,
-			      IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
-			      "TE_GPIO", display);
+			IRQF_TRIGGER_RISING, "TE_GPIO", display);
 	if (rc) {
 		DSI_ERR("TE request_irq failed for ESD rc:%d\n", rc);
 		irq_clear_status_flags(te_irq, IRQ_DISABLE_UNLAZY);
@@ -464,7 +439,6 @@
 	}
 
 	disable_irq(te_irq);
-	display->is_te_irq_enabled = false;
 
 	return;
 
@@ -758,21 +732,31 @@
 	return rc;
 }
 
+static void _handle_esd_te(struct dsi_display_te_listener *tl)
+{
+	struct completion *esd_te_gate = tl->data;
+
+	complete(esd_te_gate);
+}
+
 static int dsi_display_status_check_te(struct dsi_display *display)
 {
 	int rc = 1;
 	int const esd_te_timeout = msecs_to_jiffies(3*20);
+	DECLARE_COMPLETION(esd_te_gate);
+	struct dsi_display_te_listener tl = {
+		.handler = _handle_esd_te,
+		.data = &esd_te_gate,
+	};
 
-	dsi_display_change_te_irq_status(display, true);
+	dsi_display_add_te_listener(display, &tl);
 
-	reinit_completion(&display->esd_te_gate);
-	if (!wait_for_completion_timeout(&display->esd_te_gate,
-				esd_te_timeout)) {
+	if (!wait_for_completion_timeout(&esd_te_gate, esd_te_timeout)) {
 		DSI_ERR("TE check failed\n");
 		rc = -EINVAL;
 	}
 
-	dsi_display_change_te_irq_status(display, false);
+	dsi_display_remove_te_listener(display, &tl);
 
 	return rc;
 }
@@ -1060,8 +1044,7 @@
 		rc = dsi_panel_set_lp2(display->panel);
 		break;
 	case SDE_MODE_DPMS_ON:
-		if ((display->panel->power_mode == SDE_MODE_DPMS_LP1) ||
-			(display->panel->power_mode == SDE_MODE_DPMS_LP2))
+		if (is_lp_mode(display->panel->power_mode))
 			rc = dsi_panel_set_nolp(display->panel);
 		break;
 	case SDE_MODE_DPMS_OFF:
@@ -1080,75 +1063,53 @@
 }
 
 #ifdef CONFIG_DEBUG_FS
-static bool dsi_display_is_te_based_esd(struct dsi_display *display)
+static int debugfs_dump_info_read(struct seq_file *seq, void *data)
 {
-	u32 status_mode = 0;
-
-	if (!display->panel) {
-		DSI_ERR("Invalid panel data\n");
-		return false;
-	}
-
-	status_mode = display->panel->esd_config.status_mode;
-
-	if (status_mode == ESD_MODE_PANEL_TE &&
-			gpio_is_valid(display->disp_te_gpio))
-		return true;
-	return false;
-}
-
-static ssize_t debugfs_dump_info_read(struct file *file,
-				      char __user *user_buf,
-				      size_t user_len,
-				      loff_t *ppos)
-{
-	struct dsi_display *display = file->private_data;
-	char *buf;
-	u32 len = 0;
+	struct dsi_display *display = seq->private;
+	struct drm_connector *conn;
+	struct drm_display_mode *mode;
 	int i;
 
 	if (!display)
 		return -ENODEV;
 
-	if (*ppos)
-		return 0;
+	conn = display->drm_conn;
 
-	buf = kzalloc(SZ_4K, GFP_KERNEL);
-	if (!buf)
-		return -ENOMEM;
-
-	len += snprintf(buf + len, (SZ_4K - len), "name = %s\n", display->name);
-	len += snprintf(buf + len, (SZ_4K - len),
-			"\tResolution = %dx%d\n",
+	seq_printf(seq, "name = %s\n", display->name);
+	seq_printf(seq, "\tResolution = %dx%d Fps = %d\n",
 			display->config.video_timing.h_active,
-			display->config.video_timing.v_active);
+			display->config.video_timing.v_active,
+			display->config.video_timing.refresh_rate);
+	seq_printf(seq, "\tclk rate = %d\n",
+		   display->config.video_timing.clk_rate_hz);
 
 	display_for_each_ctrl(i, display) {
-		len += snprintf(buf + len, (SZ_4K - len),
-				"\tCTRL_%d:\n\t\tctrl = %s\n\t\tphy = %s\n",
-				i, display->ctrl[i].ctrl->name,
-				display->ctrl[i].phy->name);
+		seq_printf(seq, "\tCTRL_%d:\n\t\tctrl = %s\n\t\tphy = %s\n",
+			   i, display->ctrl[i].ctrl->name,
+			   display->ctrl[i].phy->name);
 	}
 
-	len += snprintf(buf + len, (SZ_4K - len),
-			"\tPanel = %s\n", display->panel->name);
-
-	len += snprintf(buf + len, (SZ_4K - len),
-			"\tClock master = %s\n",
+	seq_printf(seq, "\tPanel = %s\n", display->panel->name);
+	seq_printf(seq, "\tClock master = %s\n",
 			display->ctrl[display->clk_master_idx].ctrl->name);
 
-	if (len > user_len)
-		len = user_len;
+	seq_puts(seq, "\tList of modes:\n");
+	list_for_each_entry(mode, &conn->modes, head) {
+		struct dsi_display_mode_priv_info *priv_info;
 
-	if (copy_to_user(user_buf, buf, len)) {
-		kfree(buf);
-		return -EFAULT;
+		priv_info = (typeof(priv_info)) mode->private;
+
+		seq_printf(seq, "\t\t%dx%d@%d drm_clk:%u dsi_clk:%llu\n",
+			   mode->htotal, mode->vtotal, mode->vrefresh,
+			   mode->clock, priv_info->clk_rate_hz);
 	}
 
-	*ppos += len;
+	return 0;
+}
 
-	kfree(buf);
-	return len;
+static int debugfs_dump_info_open(struct inode *inode, struct file *f)
+{
+	return single_open(f, debugfs_dump_info_read, inode->i_private);
 }
 
 static ssize_t debugfs_misr_setup(struct file *file,
@@ -1403,7 +1364,6 @@
 		}
 		DSI_INFO("ESD check is switched to TE mode by user\n");
 		esd_config->status_mode = ESD_MODE_PANEL_TE;
-		dsi_display_change_te_irq_status(display, true);
 	}
 
 	if (!strcmp(buf, "reg_read\n")) {
@@ -1416,8 +1376,6 @@
 			goto error;
 		}
 		esd_config->status_mode = ESD_MODE_REG_READ;
-		if (dsi_display_is_te_based_esd(display))
-			dsi_display_change_te_irq_status(display, false);
 	}
 
 	if (!strcmp(buf, "esd_sw_sim_success\n"))
@@ -1508,8 +1466,10 @@
 }
 
 static const struct file_operations dump_info_fops = {
-	.open = simple_open,
-	.read = debugfs_dump_info_read,
+	.open =		debugfs_dump_info_open,
+	.read =		seq_read,
+	.llseek =	seq_lseek,
+	.release =	single_release,
 };
 
 static const struct file_operations misr_data_fops = {
@@ -2934,15 +2894,18 @@
 	} else {
 		int ctrl_idx = (msg->flags & MIPI_DSI_MSG_UNICAST) ?
 				msg->ctrl : 0;
-		u32 cmd_flags = DSI_CTRL_CMD_FETCH_MEMORY;
+		u32 flags = DSI_CTRL_CMD_FETCH_MEMORY;
+
+		if (msg->rx_buf)
+			flags |= DSI_CTRL_CMD_READ;
 
 		if (display->queue_cmd_waits ||
 				msg->flags & MIPI_DSI_MSG_ASYNC_OVERRIDE)
-			cmd_flags |= DSI_CTRL_CMD_ASYNC_WAIT;
+			flags |= DSI_CTRL_CMD_ASYNC_WAIT;
 
 		rc = dsi_ctrl_cmd_transfer(display->ctrl[ctrl_idx].ctrl, msg,
-				&cmd_flags);
-		if (rc) {
+					   &flags);
+		if (rc < 0) {
 			DSI_ERR("[%s] cmd transfer failed, rc=%d\n",
 			       display->name, rc);
 			goto error_disable_cmd_engine;
@@ -2956,12 +2919,10 @@
 				display->name, ret);
 	}
 error_disable_clks:
-	ret = dsi_display_clk_ctrl(display->dsi_clk_handle,
-			DSI_ALL_CLKS, DSI_CLK_OFF);
-	if (ret) {
-		DSI_ERR("[%s] failed to disable all DSI clocks, rc=%d\n",
-		       display->name, ret);
-	}
+	if (dsi_display_clk_ctrl(display->dsi_clk_handle,
+				 DSI_ALL_CLKS, DSI_CLK_OFF))
+		DSI_ERR("[%s] failed to disable all DSI clocks\n",
+		       display->name);
 error:
 	return rc;
 }
@@ -4619,30 +4580,6 @@
 	return rc;
 }
 
-static void dsi_display_validate_dms_fps(struct dsi_display_mode *cur_mode,
-		struct dsi_display_mode *to_mode)
-{
-	u32 cur_fps, to_fps;
-	u32 cur_h_active, to_h_active;
-	u32 cur_v_active, to_v_active;
-
-	cur_fps = cur_mode->timing.refresh_rate;
-	to_fps = to_mode->timing.refresh_rate;
-	cur_h_active = cur_mode->timing.h_active;
-	cur_v_active = cur_mode->timing.v_active;
-	to_h_active = to_mode->timing.h_active;
-	to_v_active = to_mode->timing.v_active;
-
-	if ((cur_h_active == to_h_active) && (cur_v_active == to_v_active) &&
-			(cur_fps != to_fps)) {
-		to_mode->dsi_mode_flags |= DSI_MODE_FLAG_DMS_FPS;
-		DSI_DEBUG("DMS Modeset with FPS change\n");
-	} else {
-		to_mode->dsi_mode_flags &= ~DSI_MODE_FLAG_DMS_FPS;
-	}
-}
-
-
 static int dsi_display_set_mode_sub(struct dsi_display *display,
 				    struct dsi_display_mode *mode,
 				    u32 flags)
@@ -4753,8 +4690,6 @@
 		/* No need to set clkrate pending flag if clocks are same */
 		if ((!cur_bitclk && !to_bitclk) || (cur_bitclk != to_bitclk))
 			atomic_set(&display->clkrate_change_pending, 1);
-
-		dsi_display_validate_dms_fps(display->panel->cur_mode, mode);
 	}
 
 	if (priv_info->phy_timing_len) {
@@ -4900,9 +4835,6 @@
 	dsi_config_host_engine_state_for_cont_splash(display);
 	mutex_unlock(&display->display_lock);
 
-	/* Set the current brightness level */
-	dsi_panel_bl_handoff(display->panel);
-
 	return rc;
 
 clks_disabled:
@@ -5187,6 +5119,8 @@
 		goto error_host_deinit;
 	}
 
+	dsi_panel_debugfs_init(display->panel, display->root);
+
 	DSI_INFO("Successfully bind display panel '%s'\n", display->name);
 	display->drm_dev = drm;
 
@@ -6512,6 +6446,23 @@
 	return rc;
 }
 
+void dsi_display_set_idle_hint(void *dsi_display, bool is_idle)
+{
+	const struct dsi_display *display = dsi_display;
+
+	if (unlikely(!display || !display->panel))
+		return;
+
+	if (!dsi_panel_initialized(display->panel) ||
+	    is_lp_mode(display->panel->power_mode))
+		return;
+
+	if (is_idle)
+		dsi_panel_idle(display->panel);
+	else
+		dsi_panel_wakeup(display->panel);
+}
+
 int dsi_display_get_qsync_min_fps(void *display_dsi, u32 mode_fps)
 {
 	struct dsi_display *display = (struct dsi_display *)display_dsi;
@@ -6645,6 +6596,11 @@
 					adj_mode->timing.h_front_porch,
 					cur_mode->timing.v_front_porch,
 					adj_mode->timing.v_front_porch);
+			} else {
+				DSI_DEBUG("Switching to %d FPS with mode switch\n",
+					adj_mode->timing.refresh_rate);
+				adj_mode->dsi_mode_flags |= DSI_MODE_FLAG_DMS_FPS;
+				goto error;
 			}
 		}
 
@@ -7281,6 +7237,14 @@
 				goto error_ctrl_link_off;
 			}
 		}
+	} else {
+		/* Keep the screen brightness for continuous splash. */
+		rc = dsi_panel_bl_brightness_handoff(display->panel);
+		if (rc) {
+			pr_warn("[%s] failed to handoff brightness, rc = %d\n",
+					display->panel->name, rc);
+			/* Ignore error and use default brightness */
+		}
 	}
 	goto error;
 
@@ -7500,6 +7464,9 @@
 		mutex_unlock(&display->display_lock);
 	}
 
+	if (display->panel->funcs && display->panel->funcs->pre_kickoff)
+		display->panel->funcs->pre_kickoff(display->panel);
+
 	return rc;
 }
 
@@ -7597,6 +7564,7 @@
 		}
 
 		display->panel->panel_initialized = true;
+		display->panel->power_mode = SDE_MODE_DPMS_ON;
 		DSI_DEBUG("cont splash enabled, display enable not required\n");
 		return 0;
 	}
@@ -7623,7 +7591,7 @@
 	}
 
 	/* Block sending pps command if modeset is due to fps difference */
-	if ((mode->priv_info->dsc_enabled) &&
+	if (mode->priv_info->dsc_enabled &&
 			!(mode->dsi_mode_flags & DSI_MODE_FLAG_DMS_FPS)) {
 		mode->priv_info->dsc.pic_width *= display->ctrl_count;
 		rc = dsi_panel_update_pps(display->panel);
@@ -7677,15 +7645,30 @@
 
 int dsi_display_post_enable(struct dsi_display *display)
 {
-	int rc = 0;
+	int rc = 0, err;
 
-	if (!display) {
+	if (!display || !display->panel) {
 		DSI_ERR("Invalid params\n");
 		return -EINVAL;
 	}
 
 	mutex_lock(&display->display_lock);
 
+	/* Some features may need panel extinfo in post_enable */
+	if (!display->panel->vendor_info.extinfo_read) {
+		err = dsi_panel_get_vendor_extinfo(display->panel);
+		if (err)
+			DSI_ERR("[%s] failed to get extinfo, err=%d\n",
+				display->name, err);
+	}
+
+	if (!display->panel->vendor_info.is_sn) {
+		err = dsi_panel_get_sn(display->panel);
+		if (err)
+			DSI_ERR("[%s] failed to get SN, err=%d\n",
+						display->name, err);
+	}
+
 	if (display->panel->cur_mode->dsi_mode_flags & DSI_MODE_FLAG_POMS) {
 		if (display->config.panel_mode == DSI_OP_CMD_MODE)
 			dsi_panel_mode_switch_to_cmd(display->panel);
@@ -7918,7 +7901,7 @@
 	return rc;
 }
 
-static int __init dsi_display_register(void)
+int __init dsi_display_register(void)
 {
 	dsi_phy_drv_register();
 	dsi_ctrl_drv_register();
@@ -7928,7 +7911,7 @@
 	return platform_driver_register(&dsi_display_driver);
 }
 
-static void __exit dsi_display_unregister(void)
+void __exit dsi_display_unregister(void)
 {
 	platform_driver_unregister(&dsi_display_driver);
 	dsi_ctrl_drv_unregister();
@@ -7942,5 +7925,7 @@
 								0600);
 MODULE_PARM_DESC(dsi_display1,
 	"msm_drm.dsi_display1=<display node>:<configX> where <display node> is 'secondary dsi display node name' and <configX> where x represents index in the topology list");
+#ifndef CONFIG_DRM_MSM_MODULE
 module_init(dsi_display_register);
 module_exit(dsi_display_unregister);
+#endif
diff --git a/msm/dsi/dsi_display.h b/msm/dsi/dsi_display.h
index d254dfe..f7d6448 100644
--- a/msm/dsi/dsi_display.h
+++ b/msm/dsi/dsi_display.h
@@ -147,8 +147,8 @@
  * @sw_te_using_wd:   Is software te enabled
  * @display_lock:     Mutex for dsi_display interface.
  * @disp_te_gpio:     GPIO for panel TE interrupt.
- * @is_te_irq_enabled:bool to specify whether TE interrupt is enabled.
- * @esd_te_gate:      completion gate to signal TE interrupt.
+ * @te_listeners:     List of listeners registered for TE callbacks.
+ * @te_lock:          Lock protecting te_listeners list.
  * @ctrl_count:       Number of DSI interfaces required by panel.
  * @ctrl:             Controller information for DSI display.
  * @panel:            Handle to DSI panel.
@@ -202,8 +202,8 @@
 	bool sw_te_using_wd;
 	struct mutex display_lock;
 	int disp_te_gpio;
-	bool is_te_irq_enabled;
-	struct completion esd_te_gate;
+	struct list_head te_listeners;
+	spinlock_t te_lock;
 
 	u32 ctrl_count;
 	struct dsi_display_ctrl ctrl[MAX_DSI_CTRLS_PER_DISPLAY];
@@ -212,6 +212,7 @@
 	struct dsi_panel *panel;
 	struct device_node *panel_node;
 	struct device_node *parser_node;
+	struct device *panel_info_dev;
 
 	/* external bridge */
 	struct dsi_display_ext_bridge ext_bridge[MAX_DSI_CTRLS_PER_DISPLAY];
@@ -276,6 +277,48 @@
 	struct workqueue_struct *dma_cmd_workq;
 };
 
+/**
+ * struct dsi_display_te_listener - data for TE listener
+ * @head:    List node pointer.
+ * @handler: TE callback function, called in atomic context.
+ * @data:    Private data that is not modified by add/remove API
+ */
+struct dsi_display_te_listener {
+	struct list_head head;
+	void (*handler)(struct dsi_display_te_listener *);
+	void *data;
+};
+
+/**
+ * dsi_display_add_te_listener - adds a new listener for TE events
+ * @display: Handle to display
+ * @tl:      TE listener struct
+ *
+ * Adds a new TE listener and enables TE irq if there are no other listeners.
+ * Upon TE interrupt, the handler passed in will be called back in atomic
+ * context.
+ *
+ * Note: caller is responsible for lifetime of @tl which should be available
+ * until dsi_display_remove_te_listener() is called.
+ *
+ * Returns: 0 on success, otherwise errno on failure
+ */
+int dsi_display_add_te_listener(struct dsi_display *display,
+				struct dsi_display_te_listener *tl);
+
+/**
+ * dsi_display_add_te_listener - removes listener for TE events
+ * @display: Handle to display
+ * @tl:      TE listener struct
+ *
+ * Removes TE listener and disables TE irq if there are no other listeners.
+ *
+ * Returns: 0 on success, otherwise errno on failure
+ */
+int dsi_display_remove_te_listener(struct dsi_display *display,
+				   struct dsi_display_te_listener *tl);
+
+
 int dsi_display_dev_probe(struct platform_device *pdev);
 int dsi_display_dev_remove(struct platform_device *pdev);
 
@@ -735,4 +778,11 @@
 int dsi_display_get_panel_vfp(void *display,
 	int h_active, int v_active);
 
+/**
+ * dsi_display_set_idle_hint - gives hint to display whether display is idle
+ * @display: Pointer to private display handle
+ * @is_idle: true if display is idle, false otherwise
+ */
+void dsi_display_set_idle_hint(void *display, bool is_idle);
+
 #endif /* _DSI_DISPLAY_H_ */
diff --git a/msm/dsi/dsi_drm.c b/msm/dsi/dsi_drm.c
index 0457c57..f1f14a5 100644
--- a/msm/dsi/dsi_drm.c
+++ b/msm/dsi/dsi_drm.c
@@ -69,6 +69,8 @@
 		dsi_mode->dsi_mode_flags |= DSI_MODE_FLAG_VBLANK_PRE_MODESET;
 	if (msm_is_mode_seamless_dms(drm_mode))
 		dsi_mode->dsi_mode_flags |= DSI_MODE_FLAG_DMS;
+	if (msm_is_mode_seamless_dms_fps(drm_mode))
+		dsi_mode->dsi_mode_flags |= DSI_MODE_FLAG_DMS_FPS;
 	if (msm_is_mode_seamless_vrr(drm_mode))
 		dsi_mode->dsi_mode_flags |= DSI_MODE_FLAG_VRR;
 	if (msm_is_mode_seamless_poms(drm_mode))
@@ -122,6 +124,8 @@
 		drm_mode->private_flags |= MSM_MODE_FLAG_VBLANK_PRE_MODESET;
 	if (dsi_mode->dsi_mode_flags & DSI_MODE_FLAG_DMS)
 		drm_mode->private_flags |= MSM_MODE_FLAG_SEAMLESS_DMS;
+	if (dsi_mode->dsi_mode_flags & DSI_MODE_FLAG_DMS_FPS)
+		drm_mode->private_flags |= MSM_MODE_FLAG_SEAMLESS_DMS_FPS;
 	if (dsi_mode->dsi_mode_flags & DSI_MODE_FLAG_VRR)
 		drm_mode->private_flags |= MSM_MODE_FLAG_SEAMLESS_VRR;
 	if (dsi_mode->dsi_mode_flags & DSI_MODE_FLAG_POMS)
@@ -218,6 +222,9 @@
 	if (rc)
 		DSI_ERR("Continuous splash pipeline cleanup failed, rc=%d\n",
 									rc);
+
+	if (c_bridge->display && c_bridge->display->drm_conn)
+		sde_connector_helper_bridge_pre_enable(c_bridge->display->drm_conn);
 }
 
 static void dsi_bridge_enable(struct drm_bridge *bridge)
@@ -244,8 +251,8 @@
 		DSI_ERR("[%d] DSI display post enabled failed, rc=%d\n",
 		       c_bridge->id, rc);
 
-	if (display && display->drm_conn) {
-		sde_connector_helper_bridge_enable(display->drm_conn);
+	if (c_bridge->display && c_bridge->display->drm_conn) {
+		sde_connector_helper_bridge_post_enable(c_bridge->display->drm_conn);
 		if (c_bridge->dsi_mode.dsi_mode_flags & DSI_MODE_FLAG_POMS)
 			sde_connector_schedule_status_work(display->drm_conn,
 				true);
diff --git a/msm/dsi/dsi_panel.c b/msm/dsi/dsi_panel.c
index e1f231f..f7f40b0 100644
--- a/msm/dsi/dsi_panel.c
+++ b/msm/dsi/dsi_panel.c
@@ -4,11 +4,11 @@
  * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
  */
 
+#include <linux/debugfs.h>
 #include <linux/delay.h>
 #include <linux/slab.h>
 #include <linux/gpio.h>
 #include <linux/of_gpio.h>
-#include <linux/pwm.h>
 #include <video/mipi_display.h>
 
 #include "dsi_panel.h"
@@ -26,7 +26,9 @@
 #define MAX_TOPOLOGY 5
 
 #define DSI_PANEL_DEFAULT_LABEL  "Default dsi panel"
+#define DSI_PANEL_VENDOR_DEFAULT_LABEL "Undefined vendor"
 
+#define HBM_SV_MAX_MS (10 * 60 * 1000) /* 10 min */
 #define DEFAULT_PANEL_JITTER_NUMERATOR		2
 #define DEFAULT_PANEL_JITTER_DENOMINATOR	1
 #define DEFAULT_PANEL_JITTER_ARRAY_SIZE		2
@@ -96,6 +98,9 @@
 static char dsi_dsc_rc_range_bpg_offset[] = {2, 0, 0, -2, -4, -6, -8, -8,
 		-8, -10, -10, -12, -12, -12, -12};
 
+static int dsi_panel_update_hbm_locked(struct dsi_panel *panel,
+					enum hbm_mode_type hbm_mode);
+
 int dsi_dsc_create_pps_buf_cmd(struct msm_display_dsc_info *dsc, char *buf,
 				int pps_id)
 {
@@ -452,13 +457,6 @@
 {
 	int rc = 0;
 
-	rc = dsi_pwr_enable_regulator(&panel->power_info, true);
-	if (rc) {
-		DSI_ERR("[%s] failed to enable vregs, rc=%d\n",
-				panel->name, rc);
-		goto exit;
-	}
-
 	rc = dsi_panel_set_pinctrl_state(panel, true);
 	if (rc) {
 		DSI_ERR("[%s] failed to set pinctrl, rc=%d\n", panel->name, rc);
@@ -523,47 +521,33 @@
 
 	return rc;
 }
-static int dsi_panel_tx_cmd_set(struct dsi_panel *panel,
-				enum dsi_cmd_set_type type)
+
+int dsi_panel_cmd_set_transfer(struct dsi_panel *panel,
+			       struct dsi_panel_cmd_set *cmd)
 {
 	int rc = 0, i = 0;
 	ssize_t len;
 	struct dsi_cmd_desc *cmds;
-	u32 count;
-	enum dsi_cmd_set_state state;
-	struct dsi_display_mode *mode;
 	const struct mipi_dsi_host_ops *ops = panel->host->ops;
 
-	if (!panel || !panel->cur_mode)
-		return -EINVAL;
+	cmds = cmd->cmds;
 
-	mode = panel->cur_mode;
-
-	cmds = mode->priv_info->cmd_sets[type].cmds;
-	count = mode->priv_info->cmd_sets[type].count;
-	state = mode->priv_info->cmd_sets[type].state;
-	SDE_EVT32(type, state, count);
-
-	if (count == 0) {
-		DSI_DEBUG("[%s] No commands to be sent for state(%d)\n",
-			 panel->name, type);
+	if (cmd->count == 0) {
+		DSI_DEBUG("[%s] No commands to be sent\n", panel->name);
 		goto error;
 	}
 
-	for (i = 0; i < count; i++) {
-		if (state == DSI_CMD_SET_STATE_LP)
+	for (i = 0; i < cmd->count; i++) {
+		if (cmd->state == DSI_CMD_SET_STATE_LP)
 			cmds->msg.flags |= MIPI_DSI_MSG_USE_LPM;
 
 		if (cmds->last_command)
 			cmds->msg.flags |= MIPI_DSI_MSG_LASTCOMMAND;
 
-		if (type == DSI_CMD_SET_VID_TO_CMD_SWITCH)
-			cmds->msg.flags |= MIPI_DSI_MSG_ASYNC_OVERRIDE;
-
 		len = ops->transfer(panel->host, &cmds->msg);
 		if (len < 0) {
 			rc = len;
-			DSI_ERR("failed to set cmds(%d), rc=%d\n", type, rc);
+			DSI_ERR("failed to set cmds, rc=%d\n", rc);
 			goto error;
 		}
 		if (cmds->post_wait_ms)
@@ -623,262 +607,6 @@
 	return rc;
 }
 
-static int dsi_panel_wled_register(struct dsi_panel *panel,
-		struct dsi_backlight_config *bl)
-{
-	struct backlight_device *bd;
-
-	bd = backlight_device_get_by_type(BACKLIGHT_RAW);
-	if (!bd) {
-		DSI_ERR("[%s] fail raw backlight register\n", panel->name);
-		return -EPROBE_DEFER;
-	}
-
-	bl->raw_bd = bd;
-	return 0;
-}
-
-static int dsi_panel_dcs_set_display_brightness_c2(struct mipi_dsi_device *dsi,
-			u32 bl_lvl)
-{
-	u16 brightness = (u16)bl_lvl;
-	u8 first_byte = brightness & 0xff;
-	u8 second_byte = brightness >> 8;
-	u8 payload[8] = {second_byte, first_byte,
-		second_byte, first_byte,
-		second_byte, first_byte,
-		second_byte, first_byte};
-
-	return mipi_dsi_dcs_write(dsi, 0xC2, payload, sizeof(payload));
-}
-
-
-
-static int dsi_panel_update_backlight(struct dsi_panel *panel,
-	u32 bl_lvl)
-{
-	int rc = 0;
-	struct mipi_dsi_device *dsi;
-	struct dsi_backlight_config *bl;
-
-	if (!panel || (bl_lvl > 0xffff)) {
-		DSI_ERR("invalid params\n");
-		return -EINVAL;
-	}
-
-	dsi = &panel->mipi_device;
-	bl = &panel->bl_config;
-
-	if (panel->bl_config.bl_inverted_dbv)
-		bl_lvl = (((bl_lvl & 0xff) << 8) | (bl_lvl >> 8));
-
-	if (panel->bl_config.bl_dcs_subtype == 0xc2)
-		rc = dsi_panel_dcs_set_display_brightness_c2(dsi, bl_lvl);
-	else
-		rc = mipi_dsi_dcs_set_display_brightness(dsi, bl_lvl);
-
-	if (rc < 0)
-		DSI_ERR("failed to update dcs backlight:%d\n", bl_lvl);
-
-	return rc;
-}
-
-static int dsi_panel_update_pwm_backlight(struct dsi_panel *panel,
-	u32 bl_lvl)
-{
-	int rc = 0;
-	u32 duty = 0;
-	u32 period_ns = 0;
-	struct dsi_backlight_config *bl;
-
-	if (!panel) {
-		DSI_ERR("Invalid Params\n");
-		return -EINVAL;
-	}
-
-	bl = &panel->bl_config;
-	if (!bl->pwm_bl) {
-		DSI_ERR("pwm device not found\n");
-		return -EINVAL;
-	}
-
-	period_ns = bl->pwm_period_usecs * NSEC_PER_USEC;
-	duty = bl_lvl * period_ns;
-	duty /= bl->bl_max_level;
-
-	rc = pwm_config(bl->pwm_bl, duty, period_ns);
-	if (rc) {
-		DSI_ERR("[%s] failed to change pwm config, rc=\n", panel->name,
-			rc);
-		goto error;
-	}
-
-	if (bl_lvl == 0 && bl->pwm_enabled) {
-		pwm_disable(bl->pwm_bl);
-		bl->pwm_enabled = false;
-		return 0;
-	}
-
-	if (!bl->pwm_enabled) {
-		rc = pwm_enable(bl->pwm_bl);
-		if (rc) {
-			DSI_ERR("[%s] failed to enable pwm, rc=\n", panel->name,
-				rc);
-			goto error;
-		}
-
-		bl->pwm_enabled = true;
-	}
-
-error:
-	return rc;
-}
-
-int dsi_panel_set_backlight(struct dsi_panel *panel, u32 bl_lvl)
-{
-	int rc = 0;
-	struct dsi_backlight_config *bl = &panel->bl_config;
-
-	if (panel->host_config.ext_bridge_mode)
-		return 0;
-
-	DSI_DEBUG("backlight type:%d lvl:%d\n", bl->type, bl_lvl);
-	switch (bl->type) {
-	case DSI_BACKLIGHT_WLED:
-		rc = backlight_device_set_brightness(bl->raw_bd, bl_lvl);
-		break;
-	case DSI_BACKLIGHT_DCS:
-		rc = dsi_panel_update_backlight(panel, bl_lvl);
-		break;
-	case DSI_BACKLIGHT_EXTERNAL:
-		break;
-	case DSI_BACKLIGHT_PWM:
-		rc = dsi_panel_update_pwm_backlight(panel, bl_lvl);
-		break;
-	default:
-		DSI_ERR("Backlight type(%d) not supported\n", bl->type);
-		rc = -ENOTSUPP;
-	}
-
-	return rc;
-}
-
-static u32 dsi_panel_get_brightness(struct dsi_backlight_config *bl)
-{
-	u32 cur_bl_level;
-	struct backlight_device *bd = bl->raw_bd;
-
-	/* default the brightness level to 50% */
-	cur_bl_level = bl->bl_max_level >> 1;
-
-	switch (bl->type) {
-	case DSI_BACKLIGHT_WLED:
-		/* Try to query the backlight level from the backlight device */
-		if (bd->ops && bd->ops->get_brightness)
-			cur_bl_level = bd->ops->get_brightness(bd);
-		break;
-	case DSI_BACKLIGHT_DCS:
-	case DSI_BACKLIGHT_EXTERNAL:
-	case DSI_BACKLIGHT_PWM:
-	default:
-		/*
-		 * Ideally, we should read the backlight level from the
-		 * panel. For now, just set it default value.
-		 */
-		break;
-	}
-
-	DSI_DEBUG("cur_bl_level=%d\n", cur_bl_level);
-	return cur_bl_level;
-}
-
-void dsi_panel_bl_handoff(struct dsi_panel *panel)
-{
-	struct dsi_backlight_config *bl = &panel->bl_config;
-
-	bl->bl_level = dsi_panel_get_brightness(bl);
-}
-
-static int dsi_panel_pwm_register(struct dsi_panel *panel)
-{
-	int rc = 0;
-	struct dsi_backlight_config *bl = &panel->bl_config;
-
-	bl->pwm_bl = devm_of_pwm_get(panel->parent, panel->panel_of_node, NULL);
-	if (IS_ERR_OR_NULL(bl->pwm_bl)) {
-		rc = PTR_ERR(bl->pwm_bl);
-		DSI_ERR("[%s] failed to request pwm, rc=%d\n", panel->name,
-			rc);
-		return rc;
-	}
-
-	return 0;
-}
-
-static int dsi_panel_bl_register(struct dsi_panel *panel)
-{
-	int rc = 0;
-	struct dsi_backlight_config *bl = &panel->bl_config;
-
-	if (panel->host_config.ext_bridge_mode)
-		return 0;
-
-	switch (bl->type) {
-	case DSI_BACKLIGHT_WLED:
-		rc = dsi_panel_wled_register(panel, bl);
-		break;
-	case DSI_BACKLIGHT_DCS:
-		break;
-	case DSI_BACKLIGHT_EXTERNAL:
-		break;
-	case DSI_BACKLIGHT_PWM:
-		rc = dsi_panel_pwm_register(panel);
-		break;
-	default:
-		DSI_ERR("Backlight type(%d) not supported\n", bl->type);
-		rc = -ENOTSUPP;
-		goto error;
-	}
-
-error:
-	return rc;
-}
-
-static void dsi_panel_pwm_unregister(struct dsi_panel *panel)
-{
-	struct dsi_backlight_config *bl = &panel->bl_config;
-
-	devm_pwm_put(panel->parent, bl->pwm_bl);
-}
-
-static int dsi_panel_bl_unregister(struct dsi_panel *panel)
-{
-	int rc = 0;
-	struct dsi_backlight_config *bl = &panel->bl_config;
-
-	if (panel->host_config.ext_bridge_mode)
-		return 0;
-
-	switch (bl->type) {
-	case DSI_BACKLIGHT_WLED:
-		break;
-	case DSI_BACKLIGHT_DCS:
-		break;
-	case DSI_BACKLIGHT_EXTERNAL:
-		break;
-	case DSI_BACKLIGHT_PWM:
-		dsi_panel_pwm_unregister(panel);
-		break;
-	default:
-		DSI_ERR("Backlight type(%d) not supported\n", bl->type);
-		rc = -ENOTSUPP;
-		goto error;
-	}
-
-error:
-	return rc;
-}
-
 static int dsi_panel_parse_timing(struct dsi_mode_info *mode,
 				  struct dsi_parser_utils *utils)
 {
@@ -1281,6 +1009,377 @@
 	split_link->split_link_enabled = true;
 }
 
+static int dsi_panel_create_sn_buf(struct dsi_panel *panel)
+{
+	struct dsi_panel_vendor_info *const vendor_info = &panel->vendor_info;
+	struct dsi_panel_sn_location *const location = &vendor_info->location;
+	ssize_t rc = 0;
+	u32 sn_str_size;
+	u8 *tmp_sn_buf;
+
+	if (!panel)
+		return -EINVAL;
+
+	if  (!location->addr || !location->sn_length) {
+		DSI_ERR("[%s] invalid location\n", __func__);
+		return -EINVAL;
+	}
+
+	// prepare buffer for bin2hex()
+	// e.g. buf[0] = 0x01 -> sn[0] = '0' and sn[1] = '1'
+	sn_str_size = location->sn_length * 2;
+	tmp_sn_buf = kmalloc(sn_str_size + 1, GFP_KERNEL);
+	if (!tmp_sn_buf)
+		return -ENOMEM;
+
+	mutex_lock(&panel->panel_lock);
+	if (!vendor_info->sn) {
+		vendor_info->is_sn = false;
+		vendor_info->sn = tmp_sn_buf;
+	} else
+		kfree(tmp_sn_buf);
+
+	mutex_unlock(&panel->panel_lock);
+	return rc;
+}
+
+static int dsi_panel_release_sn_buf(struct dsi_panel *panel)
+{
+	struct dsi_panel_vendor_info *const vendor_info = &panel->vendor_info;
+	ssize_t rc = 0;
+
+	if (!panel)
+		return -EINVAL;
+
+	mutex_lock(&panel->panel_lock);
+	kfree(vendor_info->sn);
+	vendor_info->sn = NULL;
+	vendor_info->is_sn = false;
+	mutex_unlock(&panel->panel_lock);
+	return rc;
+}
+
+int dsi_panel_get_sn(struct dsi_panel *panel)
+{
+	struct dsi_panel_vendor_info *const vendor_info = &panel->vendor_info;
+	struct dsi_panel_sn_location *const location = &vendor_info->location;
+	ssize_t rc = 0;
+	u32 read_size, read_back_size, sn_str_size;
+	u8 *buf;
+
+	if (!panel || !panel->panel_initialized) {
+		DSI_ERR("panel is not ready\n");
+		return -EINVAL;
+	}
+
+	read_size = location->start_byte + location->sn_length;
+	buf = kmalloc(read_size, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	mutex_lock(&panel->panel_lock);
+
+	if (!panel->vendor_info.sn) {
+		rc = -EINVAL;
+		goto out_mutex;
+	}
+
+	read_back_size = mipi_dsi_dcs_read(&panel->mipi_device, location->addr,
+					   buf, read_size);
+	if (read_back_size == read_size) {
+		bin2hex(vendor_info->sn, &buf[location->start_byte],
+			location->sn_length);
+		// e.g. buf[0] = 0x01 -> sn[0] = '0' and sn[1] = '1'
+		sn_str_size = location->sn_length * 2;
+		vendor_info->sn[sn_str_size] = '\0';
+		vendor_info->is_sn = true;
+	} else {
+		DSI_ERR("failed to read: addr=0x%X, read_back_size=%d, read_size=%d\n",
+		      location->addr, read_back_size, read_size);
+		rc = -EINVAL;
+	}
+
+	kfree(buf);
+out_mutex:
+	mutex_unlock(&panel->panel_lock);
+	return rc;
+}
+
+static int dsi_panel_parse_sn_location(struct dsi_panel *panel,
+				       struct device_node *of_node)
+{
+	struct dsi_panel_vendor_info *const vendor_info = &panel->vendor_info;
+	int rc = 0, len = 0;
+	u32 array[3] = {0};
+
+	if (!panel)
+		return -EINVAL;
+
+	len = of_property_count_u32_elems(of_node,
+			"google,mdss-dsi-panel-sn-location");
+	if (len != 3) {
+		DSI_ERR("[%s] invalid format\n", __func__);
+		rc = -EINVAL;
+		goto error;
+	}
+
+	rc = of_property_read_u32_array(of_node,
+			"google,mdss-dsi-panel-sn-location", array, len);
+	if (rc || !array[0] || !array[2]) {
+		DSI_ERR("[%s] invalid format\n", __func__);
+		rc = -EINVAL;
+		goto error;
+	}
+
+	vendor_info->location.addr = array[0];
+	vendor_info->location.start_byte = array[1];
+	vendor_info->location.sn_length = array[2];
+
+	DSI_DEBUG("addr=0x%x, start=%d, length=%d",
+		vendor_info->location.addr, vendor_info->location.start_byte,
+		vendor_info->location.sn_length);
+error:
+	return rc;
+}
+
+int dsi_panel_get_vendor_extinfo(struct dsi_panel *panel)
+{
+	struct dsi_panel_vendor_info *const vendor_info = &panel->vendor_info;
+	char buffer[128];
+	size_t bytes = 0;
+	u32 read_addr, read_start, read_len, dsi_read_len;
+	int i, rc = 0;
+
+	mutex_lock(&panel->panel_lock);
+
+	/* Transmit the commands as specified. */
+	for (i = 0; i < vendor_info->extinfo_loc_length; i += 3) {
+		read_addr  = vendor_info->extinfo_loc[i];
+		read_start = vendor_info->extinfo_loc[i + 1];
+		read_len   = vendor_info->extinfo_loc[i + 2];
+
+		/* Compute size of entire DSI read, starting from byte zero. */
+		dsi_read_len = read_start + read_len;
+
+		if (dsi_read_len > sizeof(buffer)) {
+			DSI_ERR("Read is too large for buffer.\n");
+			rc = -EINVAL;
+			goto error;
+		} else if (read_len > vendor_info->extinfo_length - bytes) {
+			DSI_ERR("Ran out of space reading extinfo data.\n");
+			rc = -EINVAL;
+			goto error;
+		}
+
+		rc = mipi_dsi_dcs_read(&panel->mipi_device,
+				       read_addr,
+				       buffer,
+				       dsi_read_len);
+		if (rc < 0) {
+			DSI_ERR("mipi_dsi_dcs_read failed with rc=%d.\n", rc);
+			goto error;
+		}
+
+		/* Copy only the specified portion of the DSI read, which is
+		 * read_len bytes starting at read_start.
+		 */
+		memcpy(&vendor_info->extinfo[bytes], &buffer[read_start],
+		       read_len);
+		bytes += read_len;
+	}
+
+	vendor_info->extinfo_read = bytes;
+	rc = 0;
+
+error:
+
+	if (rc < 0) {
+		DSI_ERR("Failed to read vendor extinfo. Freeing extinfo memory.\n");
+
+		vendor_info->extinfo_loc_length = 0;
+		vendor_info->extinfo_length = 0;
+		vendor_info->extinfo_read = 0;
+
+		kfree(vendor_info->extinfo_loc);
+		vendor_info->extinfo_loc = NULL;
+
+		kfree(vendor_info->extinfo);
+		vendor_info->extinfo = NULL;
+	}
+
+	mutex_unlock(&panel->panel_lock);
+	return rc;
+}
+
+static int dsi_panel_release_vendor_extinfo(struct dsi_panel *panel)
+{
+	struct dsi_panel_vendor_info *const vendor_info = &panel->vendor_info;
+
+	mutex_lock(&panel->panel_lock);
+
+	vendor_info->extinfo_loc_length = 0;
+	vendor_info->extinfo_length = 0;
+	vendor_info->extinfo_read = 0;
+
+	kfree(vendor_info->extinfo_loc);
+	vendor_info->extinfo_loc = NULL;
+
+	kfree(vendor_info->extinfo);
+	vendor_info->extinfo = NULL;
+
+	mutex_unlock(&panel->panel_lock);
+	return 0;
+}
+
+static int dsi_panel_parse_vendor_extinfo_location(struct dsi_panel *panel,
+						   struct device_node *of_node)
+{
+	u32 extinfo_location[24];
+	int size_bytes, loc_length;
+	int extinfo_length = 0;
+	int i, rc = 0;
+
+	if (!of_property_read_bool(
+			of_node, "google,mdss-dsi-panel-vendor-extinfo-loc")) {
+		pr_info("Could not find optional vendor extinfo.\n");
+		rc = 0;
+		goto error;
+	}
+
+	if (IS_ERR(of_get_property(of_node,
+				   "google,mdss-dsi-panel-vendor-extinfo-loc",
+				   &size_bytes))) {
+		DSI_ERR("Failed to read google,mdss-dsi-panel-vendor-extinfo-loc.\n");
+		goto error;
+	}
+	loc_length = size_bytes / 4;
+
+	if (size_bytes > sizeof(extinfo_location)) {
+		DSI_ERR("Length (%d) is larger than allocated (%d).\n",
+		       size_bytes, sizeof(extinfo_location));
+		rc = -EINVAL;
+		goto error;
+	} else if (size_bytes % 4 != 0 || loc_length % 3 != 0) {
+		DSI_ERR("Size (%d) is not 32-bit aligned and/or the length (%d) is not divisible by 3.\n",
+		       size_bytes, loc_length);
+		rc = -EINVAL;
+		goto error;
+	}
+
+	rc = of_property_read_u32_array(
+			of_node, "google,mdss-dsi-panel-vendor-extinfo-loc",
+			extinfo_location, loc_length);
+	if (rc) {
+		DSI_ERR("Error (%d) reading extinfo location.\n", rc);
+		goto error;
+	}
+
+	/* Compute expected length of extinfo */
+	for (i = 0; i < loc_length; i += 3) {
+		/* Each register read consists of three u32 values specifying
+		 * the register address, start byte of the target range, and the
+		 * length of the target range. Length is the 3rd u32.
+		 */
+		extinfo_length += extinfo_location[i + 2];
+	}
+
+	panel->vendor_info.extinfo_loc = kzalloc(size_bytes, GFP_KERNEL);
+	panel->vendor_info.extinfo = kzalloc(extinfo_length, GFP_KERNEL);
+	if (!panel->vendor_info.extinfo_loc || !panel->vendor_info.extinfo) {
+		DSI_ERR("Failed to allocate extinfo buffer memory.\n");
+
+		kfree(panel->vendor_info.extinfo_loc);
+		panel->vendor_info.extinfo_loc = NULL;
+
+		kfree(panel->vendor_info.extinfo);
+		panel->vendor_info.extinfo = NULL;
+
+		rc = -ENOMEM;
+		goto error;
+	}
+	memcpy(panel->vendor_info.extinfo_loc, extinfo_location, size_bytes);
+	panel->vendor_info.extinfo_loc_length = loc_length;
+	panel->vendor_info.extinfo_length = extinfo_length;
+	rc = 0;
+
+error:
+	return rc;
+}
+
+/*
+ * Copy the display panel extended info to a buffer provided by the caller.
+ *
+ * panel  - pointer to a drm_panel
+ * buffer - destination for extinfo data
+ * len    - size of destination buffer
+ *
+ * If there is no extinfo to read, returns 0
+ * If provided buffer is NULL, returns the size of the extinfo in bytes
+ * If len is less than the extinfo length, returns -ENOSPC
+ * If there is extinfo pending read, returns -EBUSY
+ * Otherwise, copies extinfo to buffer and returns the number of bytes copied
+ */
+int dsi_panel_read_vendor_extinfo(struct drm_panel *panel, char *buffer,
+				  size_t len)
+{
+	struct dsi_panel *p = container_of(panel, struct dsi_panel, drm_panel);
+	struct dsi_panel_vendor_info *const vendor_info = &p->vendor_info;
+
+	if (!panel)
+		return -EINVAL;
+
+	/* Return zero if there is no extinfo to read */
+	if (vendor_info->extinfo_loc_length == 0)
+		return 0;
+
+	/* If buffer is null, this is a request for the length of extinfo */
+	if (!buffer)
+		return vendor_info->extinfo_length;
+
+	/* Verify adequate buffer space */
+	if (len < vendor_info->extinfo_length)
+		return -ENOSPC;
+
+	/* Return -EBUSY if there is extinfo to read, but read is pending */
+	if (vendor_info->extinfo_read == 0)
+		return -EBUSY;
+
+	memcpy(buffer, vendor_info->extinfo, vendor_info->extinfo_length);
+	return vendor_info->extinfo_length;
+}
+EXPORT_SYMBOL(dsi_panel_read_vendor_extinfo);
+
+static int dsi_panel_parse_vendor_info(struct dsi_panel *panel,
+				       struct device_node *of_node)
+{
+	int rc = 0;
+	struct dsi_panel_vendor_info *const vendor_info = &panel->vendor_info;
+
+	if (!panel)
+		return -EINVAL;
+
+	vendor_info->name = of_get_property(of_node,
+				"google,mdss-dsi-panel-vendor", NULL);
+
+	if (!vendor_info->name) {
+		vendor_info->name = DSI_PANEL_VENDOR_DEFAULT_LABEL;
+		rc = -EINVAL;
+		goto error;
+	}
+	rc = dsi_panel_parse_sn_location(panel, of_node);
+	if (rc) {
+		DSI_ERR("[%s] failed to parse the parameter for SN, rc=%d\n",
+			panel->name, rc);
+	}
+	rc = dsi_panel_parse_vendor_extinfo_location(panel, of_node);
+	if (rc) {
+		DSI_ERR("[%s] failed to parse the extended panel info, rc=%d\n",
+			panel->name, rc);
+	}
+error:
+	return rc;
+}
+
 static int dsi_panel_parse_host_config(struct dsi_panel *panel)
 {
 	int rc = 0;
@@ -1824,6 +1923,7 @@
 	"qcom,mdss-dsi-lp1-command",
 	"qcom,mdss-dsi-lp2-command",
 	"qcom,mdss-dsi-nolp-command",
+	"qcom,mdss-dsi-post-nolp-command",
 	"PPS not parsed from DTSI, generated dynamically",
 	"ROI not parsed from DTSI, generated dynamically",
 	"qcom,mdss-dsi-timing-switch-command",
@@ -1850,6 +1950,7 @@
 	"qcom,mdss-dsi-lp1-command-state",
 	"qcom,mdss-dsi-lp2-command-state",
 	"qcom,mdss-dsi-nolp-command-state",
+	"qcom,mdss-dsi-post-nolp-command-state",
 	"PPS not parsed from DTSI, generated dynamically",
 	"ROI not parsed from DTSI, generated dynamically",
 	"qcom,mdss-dsi-timing-switch-command-state",
@@ -1858,6 +1959,28 @@
 	"qcom,mdss-dsi-qsync-off-commands-state",
 };
 
+static int dsi_panel_tx_cmd_set(struct dsi_panel *panel,
+				enum dsi_cmd_set_type type)
+{
+	struct dsi_display_mode *mode;
+	struct dsi_panel_cmd_set *cmd;
+
+	if (!panel || !panel->cur_mode)
+		return -EINVAL;
+
+	mode = panel->cur_mode;
+	cmd = &mode->priv_info->cmd_sets[type];
+
+	if ((cmd->count) &&
+			(type >= 0) && (type < DSI_CMD_SET_MAX))
+		pr_debug("send cmdset %s\n", cmd_set_prop_map[type]);
+
+	if (type == DSI_CMD_SET_VID_TO_CMD_SWITCH)
+		cmd->cmds->msg.flags |= MIPI_DSI_MSG_ASYNC_OVERRIDE;
+
+	return dsi_panel_cmd_set_transfer(panel, cmd);
+}
+
 static int dsi_panel_get_cmd_pkt_count(const char *data, u32 length, u32 *cnt)
 {
 	const u32 cmd_set_min_size = 7;
@@ -1927,7 +2050,7 @@
 	return rc;
 }
 
-static void dsi_panel_destroy_cmd_packets(struct dsi_panel_cmd_set *set)
+void dsi_panel_destroy_cmd_packets(struct dsi_panel_cmd_set *set)
 {
 	u32 i = 0;
 	struct dsi_cmd_desc *cmd;
@@ -1938,7 +2061,7 @@
 	}
 }
 
-static void dsi_panel_dealloc_cmd_packets(struct dsi_panel_cmd_set *set)
+void dsi_panel_dealloc_cmd_packets(struct dsi_panel_cmd_set *set)
 {
 	kfree(set->cmds);
 }
@@ -1958,36 +2081,18 @@
 }
 
 static int dsi_panel_parse_cmd_sets_sub(struct dsi_panel_cmd_set *cmd,
-					enum dsi_cmd_set_type type,
-					struct dsi_parser_utils *utils)
+					const char *data,
+					size_t length)
 {
 	int rc = 0;
-	u32 length = 0;
-	const char *data;
-	const char *state;
 	u32 packet_count = 0;
 
-	data = utils->get_property(utils->data, cmd_set_prop_map[type],
-			&length);
-	if (!data) {
-		DSI_DEBUG("%s commands not defined\n", cmd_set_prop_map[type]);
-		rc = -ENOTSUPP;
-		goto error;
-	}
-
-	DSI_DEBUG("type=%d, name=%s, length=%d\n", type,
-		cmd_set_prop_map[type], length);
-
-	print_hex_dump_debug("", DUMP_PREFIX_NONE,
-		       8, 1, data, length, false);
-
 	rc = dsi_panel_get_cmd_pkt_count(data, length, &packet_count);
 	if (rc) {
 		DSI_ERR("commands failed, rc=%d\n", rc);
 		goto error;
 	}
-	DSI_DEBUG("[%s] packet-count=%d, %d\n", cmd_set_prop_map[type],
-		packet_count, length);
+	DSI_DEBUG("packet-count=%d, %d\n", packet_count, length);
 
 	rc = dsi_panel_alloc_cmd_packets(cmd, packet_count);
 	if (rc) {
@@ -2002,24 +2107,62 @@
 		goto error_free_mem;
 	}
 
-	state = utils->get_property(utils->data, cmd_set_state_map[type], NULL);
-	if (!state || !strcmp(state, "dsi_lp_mode")) {
-		cmd->state = DSI_CMD_SET_STATE_LP;
-	} else if (!strcmp(state, "dsi_hs_mode")) {
-		cmd->state = DSI_CMD_SET_STATE_HS;
-	} else {
-		DSI_ERR("[%s] command state unrecognized-%s\n",
-		       cmd_set_state_map[type], state);
-		goto error_free_mem;
-	}
-
 	return rc;
 error_free_mem:
 	kfree(cmd->cmds);
 	cmd->cmds = NULL;
 error:
 	return rc;
+}
 
+int dsi_panel_parse_dt_cmd_set(struct device_node *of_node,
+			       const char *cmd_str,
+			       const char *cmd_state_str,
+			       struct dsi_panel_cmd_set *cmd)
+{
+	const char *data;
+	const char *state;
+	enum dsi_cmd_set_state st;
+	u32 length = 0;
+	int rc;
+
+	data = of_get_property(of_node, cmd_str, &length);
+	if (!data) {
+		pr_debug("%s commands not defined\n", cmd_str);
+		return -ENOTSUPP;
+	}
+
+	pr_debug("name=%s, length=%d\n", cmd_str, length);
+
+	print_hex_dump_debug("", DUMP_PREFIX_NONE,
+		8, 1, data, length, false);
+
+	state = of_get_property(of_node, cmd_state_str, NULL);
+	if (!state || !strcmp(state, "dsi_lp_mode")) {
+		st = DSI_CMD_SET_STATE_LP;
+	} else if (!strcmp(state, "dsi_hs_mode")) {
+		st = DSI_CMD_SET_STATE_HS;
+	} else {
+		DSI_ERR("[%s] command state unrecognized-%s\n",
+		       cmd_state_str, state);
+		return -ENOTSUPP;
+	}
+
+	rc = dsi_panel_parse_cmd_sets_sub(cmd, data, length);
+	if (rc)
+		return rc;
+
+	cmd->state = st;
+
+	return 0;
+}
+
+static int dsi_panel_parse_cmd_sets_dt(struct dsi_panel_cmd_set *cmd,
+				       enum dsi_cmd_set_type type,
+				       struct dsi_parser_utils *utils)
+{
+	return dsi_panel_parse_dt_cmd_set(utils->data, cmd_set_prop_map[type],
+					  cmd_set_state_map[type], cmd);
 }
 
 static int dsi_panel_parse_cmd_sets(
@@ -2047,7 +2190,7 @@
 					i, rc);
 			set->state = DSI_CMD_SET_STATE_LP;
 		} else {
-			rc = dsi_panel_parse_cmd_sets_sub(set, i, utils);
+			rc = dsi_panel_parse_cmd_sets_dt(set, i, utils);
 			if (rc)
 				DSI_DEBUG("failed to parse set %d\n", i);
 		}
@@ -2100,7 +2243,8 @@
 	rc = utils->read_u32_array(utils->data, "qcom,mdss-dsi-reset-sequence",
 					arr_32, length);
 	if (rc) {
-		DSI_ERR("[%s] cannot read dso-reset-seqience\n", panel->name);
+		DSI_ERR("[%s] cannot read qcom,mdss-dsi-reset-sequence\n",
+		       panel->name);
 		goto error_free_arr_32;
 	}
 
@@ -2131,6 +2275,7 @@
 static int dsi_panel_parse_misc_features(struct dsi_panel *panel)
 {
 	struct dsi_parser_utils *utils = &panel->utils;
+	u32 val;
 
 	panel->ulps_feature_enabled =
 		utils->read_bool(utils->data, "qcom,ulps-enabled");
@@ -2156,6 +2301,15 @@
 	panel->reset_gpio_always_on = utils->read_bool(utils->data,
 			"qcom,platform-reset-gpio-always-on");
 
+	if (!utils->read_u32(utils->data,
+				  "qcom,mdss-dsi-init-delay-us",
+				  &val)) {
+		pr_debug("Panel init delay specified: %d\n", val);
+		panel->init_delay_us = val;
+	} else {
+		panel->init_delay_us = 0;
+	}
+
 	return 0;
 }
 
@@ -2314,141 +2468,6 @@
 	return rc;
 }
 
-static int dsi_panel_parse_bl_pwm_config(struct dsi_panel *panel)
-{
-	int rc = 0;
-	u32 val;
-	struct dsi_backlight_config *config = &panel->bl_config;
-	struct dsi_parser_utils *utils = &panel->utils;
-
-	rc = utils->read_u32(utils->data, "qcom,bl-pmic-pwm-period-usecs",
-				  &val);
-	if (rc) {
-		DSI_ERR("bl-pmic-pwm-period-usecs is not defined, rc=%d\n", rc);
-		goto error;
-	}
-	config->pwm_period_usecs = val;
-
-error:
-	return rc;
-}
-
-static int dsi_panel_parse_bl_config(struct dsi_panel *panel)
-{
-	int rc = 0;
-	u32 val = 0;
-	const char *bl_type;
-	const char *data;
-	struct dsi_parser_utils *utils = &panel->utils;
-	char *bl_name;
-
-	if (!strcmp(panel->type, "primary"))
-		bl_name = "qcom,mdss-dsi-bl-pmic-control-type";
-	else
-		bl_name = "qcom,mdss-dsi-sec-bl-pmic-control-type";
-
-	bl_type = utils->get_property(utils->data, bl_name, NULL);
-	if (!bl_type) {
-		panel->bl_config.type = DSI_BACKLIGHT_UNKNOWN;
-	} else if (!strcmp(bl_type, "bl_ctrl_pwm")) {
-		panel->bl_config.type = DSI_BACKLIGHT_PWM;
-	} else if (!strcmp(bl_type, "bl_ctrl_wled")) {
-		panel->bl_config.type = DSI_BACKLIGHT_WLED;
-	} else if (!strcmp(bl_type, "bl_ctrl_dcs")) {
-		panel->bl_config.type = DSI_BACKLIGHT_DCS;
-	} else if (!strcmp(bl_type, "bl_ctrl_external")) {
-		panel->bl_config.type = DSI_BACKLIGHT_EXTERNAL;
-	} else {
-		DSI_DEBUG("[%s] bl-pmic-control-type unknown-%s\n",
-			 panel->name, bl_type);
-		panel->bl_config.type = DSI_BACKLIGHT_UNKNOWN;
-	}
-
-	data = utils->get_property(utils->data, "qcom,bl-update-flag", NULL);
-	if (!data) {
-		panel->bl_config.bl_update = BL_UPDATE_NONE;
-	} else if (!strcmp(data, "delay_until_first_frame")) {
-		panel->bl_config.bl_update = BL_UPDATE_DELAY_UNTIL_FIRST_FRAME;
-	} else {
-		DSI_DEBUG("[%s] No valid bl-update-flag: %s\n",
-						panel->name, data);
-		panel->bl_config.bl_update = BL_UPDATE_NONE;
-	}
-
-	panel->bl_config.bl_scale = MAX_BL_SCALE_LEVEL;
-	panel->bl_config.bl_scale_sv = MAX_SV_BL_SCALE_LEVEL;
-
-	rc = utils->read_u32(utils->data, "qcom,mdss-dsi-bl-min-level", &val);
-	if (rc) {
-		DSI_DEBUG("[%s] bl-min-level unspecified, defaulting to zero\n",
-			 panel->name);
-		panel->bl_config.bl_min_level = 0;
-	} else {
-		panel->bl_config.bl_min_level = val;
-	}
-
-	rc = utils->read_u32(utils->data, "qcom,mdss-dsi-bl-max-level", &val);
-	if (rc) {
-		DSI_DEBUG("[%s] bl-max-level unspecified, defaulting to max level\n",
-			 panel->name);
-		panel->bl_config.bl_max_level = MAX_BL_LEVEL;
-	} else {
-		panel->bl_config.bl_max_level = val;
-	}
-
-	rc = utils->read_u32(utils->data, "qcom,mdss-brightness-max-level",
-		&val);
-	if (rc) {
-		DSI_DEBUG("[%s] brigheness-max-level unspecified, defaulting to 255\n",
-			 panel->name);
-		panel->bl_config.brightness_max_level = 255;
-	} else {
-		panel->bl_config.brightness_max_level = val;
-	}
-
-	rc = utils->read_u32(utils->data, "qcom,mdss-dsi-bl-ctrl-dcs-subtype",
-		&val);
-	if (rc) {
-		DSI_DEBUG("[%s] bl-ctrl-dcs-subtype, defautling to zero\n",
-			panel->name);
-		panel->bl_config.bl_dcs_subtype = 0;
-	} else {
-		panel->bl_config.bl_dcs_subtype = val;
-	}
-
-	panel->bl_config.bl_inverted_dbv = utils->read_bool(utils->data,
-		"qcom,mdss-dsi-bl-inverted-dbv");
-
-	if (panel->bl_config.type == DSI_BACKLIGHT_PWM) {
-		rc = dsi_panel_parse_bl_pwm_config(panel);
-		if (rc) {
-			DSI_ERR("[%s] failed to parse pwm config, rc=%d\n",
-			       panel->name, rc);
-			goto error;
-		}
-	}
-
-	panel->bl_config.en_gpio = utils->get_named_gpio(utils->data,
-					      "qcom,platform-bklight-en-gpio",
-					      0);
-	if (!gpio_is_valid(panel->bl_config.en_gpio)) {
-		if (panel->bl_config.en_gpio == -EPROBE_DEFER) {
-			DSI_DEBUG("[%s] failed to get bklt gpio, rc=%d\n",
-					panel->name, rc);
-			rc = -EPROBE_DEFER;
-			goto error;
-		} else {
-			DSI_DEBUG("[%s] failed to get bklt gpio, rc=%d\n",
-					 panel->name, rc);
-			rc = 0;
-			goto error;
-		}
-	}
-
-error:
-	return rc;
-}
-
 void dsi_dsc_pclk_param_calc(struct msm_display_dsc_info *dsc, int intf_width)
 {
 	int slice_per_pkt, slice_per_intf;
@@ -2481,7 +2500,6 @@
 	dsc->pkt_per_line = slice_per_intf / slice_per_pkt;
 }
 
-
 int dsi_dsc_populate_static_param(struct msm_display_dsc_info *dsc)
 {
 	int bpp, bpc;
@@ -2702,10 +2720,13 @@
 	priv_info = mode->priv_info;
 
 	priv_info->dsc_enabled = false;
+	mode->timing.dsc_enabled = false;
 	compression = utils->get_property(utils->data,
 			"qcom,compression-mode", NULL);
-	if (compression && !strcmp(compression, "dsc"))
+	if (compression && !strcmp(compression, "dsc")) {
 		priv_info->dsc_enabled = true;
+		mode->timing.dsc_enabled = true;
+	}
 
 	if (!priv_info->dsc_enabled) {
 		DSI_DEBUG("dsc compression is not enabled for the mode\n");
@@ -2715,7 +2736,6 @@
 	rc = utils->read_u32(utils->data, "qcom,mdss-dsc-version", &data);
 	if (rc) {
 		priv_info->dsc.version = 0x11;
-		rc = 0;
 	} else {
 		priv_info->dsc.version = data & 0xff;
 		/* only support DSC 1.1 rev */
@@ -2730,7 +2750,6 @@
 	rc = utils->read_u32(utils->data, "qcom,mdss-dsc-scr-version", &data);
 	if (rc) {
 		priv_info->dsc.scr_rev = 0x0;
-		rc = 0;
 	} else {
 		priv_info->dsc.scr_rev = data & 0xff;
 		/* only one scr rev supported */
@@ -2809,10 +2828,13 @@
 	dsi_dsc_populate_static_param(&priv_info->dsc);
 	dsi_dsc_pclk_param_calc(&priv_info->dsc, intf_width);
 
-	mode->timing.dsc_enabled = true;
 	mode->timing.dsc = &priv_info->dsc;
 
+	return 0;
+
 error:
+	priv_info->dsc_enabled = false;
+	mode->timing.dsc_enabled = false;
 	return rc;
 }
 
@@ -3176,7 +3198,7 @@
 	if (!esd_config)
 		return -EINVAL;
 
-	dsi_panel_parse_cmd_sets_sub(&esd_config->status_cmd,
+	dsi_panel_parse_cmd_sets_dt(&esd_config->status_cmd,
 				DSI_CMD_SET_PANEL_STATUS, utils);
 	if (!esd_config->status_cmd.count) {
 		DSI_ERR("panel status command parsing failed\n");
@@ -3343,6 +3365,61 @@
 	return rc;
 }
 
+static int dsi_panel_parse_te2_config(struct dsi_panel *panel)
+{
+	u8 i;
+	int rc;
+	u32 length, number_of_te2, val;
+	const char *data;
+	struct dsi_parser_utils *utils;
+	u32 arr_32[TE2_EDGE_MAX * 2] = {0};
+
+	if (unlikely(!panel))
+		return -EINVAL;
+
+	utils = &panel->utils;
+	data = utils->get_property(utils->data,
+			"google,mdss-dsi-te2-info", &length);
+	if (!data) {
+		DSI_INFO("[%s] dsi-te2-info not found\n", panel->name);
+		return -EINVAL;
+	}
+
+	number_of_te2 = length / sizeof(u32);
+
+	if (number_of_te2 != (TE2_EDGE_MAX * 2)) {
+		DSI_INFO("[%s] dsi-te2-info invalid parameters, number:%u\n",
+			panel->name, number_of_te2);
+		return -EINVAL;
+	}
+
+	rc = utils->read_u32_array(utils->data, "google,mdss-dsi-te2-info",
+					arr_32, number_of_te2);
+	if (rc) {
+		DSI_INFO("[%s] cannot read google,mdss-dsi-te2-info\n",
+			panel->name);
+		return -EINVAL;
+	}
+
+	for (i = 0; i < TE2_EDGE_MAX; i++) {
+		panel->te2_config.te2_edge[i].rising = arr_32[i * 2];
+		panel->te2_config.te2_edge[i].falling = arr_32[i * 2 + 1];
+	}
+
+	rc = utils->read_u32(utils->data, "google,mdss-dsi-te2-lp-threshold",
+		&val);
+	if (rc) {
+		DSI_INFO("[%s] google,mdss-dsi-te2-lp-threshold unspecified\n",
+			 panel->name);
+		return -EINVAL;
+	}
+
+	panel->te2_config.lp_threshold = val;
+	panel->te2_config.te2_ready = true;
+
+	return 0;
+}
+
 static void dsi_panel_update_util(struct dsi_panel *panel,
 				  struct device_node *parser_node)
 {
@@ -3363,6 +3440,16 @@
 	utils->node = panel->panel_of_node;
 }
 
+static void dsi_panel_hbmsv_hanghandler_work(struct work_struct *work)
+{
+	struct dsi_panel *panel =
+		    container_of(work, struct dsi_panel, hanghandler_work.work);
+
+	pr_warn("hbmsv hang handler\n");
+	panel->hbm_sv_enabled = false;
+	dsi_panel_update_hbm(panel, HBM_MODE_OFF);
+}
+
 struct dsi_panel *dsi_panel_get(struct device *parent,
 				struct device_node *of_node,
 				struct device_node *parser_node,
@@ -3404,6 +3491,9 @@
 				rc);
 		goto error;
 	}
+	rc = dsi_panel_parse_vendor_info(panel, of_node);
+	if (rc)
+		DSI_ERR("failed to parse vendor information, rc=%d\n", rc);
 
 	rc = dsi_panel_parse_panel_mode(panel);
 	if (rc) {
@@ -3441,7 +3531,7 @@
 	if (rc)
 		DSI_ERR("failed to parse power config, rc=%d\n", rc);
 
-	rc = dsi_panel_parse_bl_config(panel);
+	rc = dsi_panel_bl_parse_config(parent, &panel->bl_config);
 	if (rc) {
 		DSI_ERR("failed to parse backlight config, rc=%d\n", rc);
 		if (rc == -EPROBE_DEFER)
@@ -3470,6 +3560,10 @@
 	if (rc)
 		DSI_DEBUG("failed to parse esd config, rc=%d\n", rc);
 
+	rc = dsi_panel_parse_te2_config(panel);
+	if (rc)
+		DSI_DEBUG("failed to parse te2 config, rc=%d\n", rc);
+
 	panel->power_mode = SDE_MODE_DPMS_OFF;
 	drm_panel_init(&panel->drm_panel);
 	panel->drm_panel.dev = &panel->mipi_device.dev;
@@ -3481,6 +3575,14 @@
 
 	mutex_init(&panel->panel_lock);
 
+	rc = dsi_panel_create_sn_buf(panel);
+	if (rc)
+		DSI_ERR("failed to create buffer for SN, rc=%d\n", rc);
+
+	panel->hbm_sv_enabled = true;
+	INIT_DELAYED_WORK(&panel->hanghandler_work,
+			dsi_panel_hbmsv_hanghandler_work);
+
 	return panel;
 error:
 	kfree(panel);
@@ -3489,6 +3591,12 @@
 
 void dsi_panel_put(struct dsi_panel *panel)
 {
+	cancel_delayed_work_sync(&panel->hanghandler_work);
+
+	dsi_panel_release_sn_buf(panel);
+
+	dsi_panel_release_vendor_extinfo(panel);
+
 	drm_panel_remove(&panel->drm_panel);
 
 	/* free resources allocated for ESD check */
@@ -3497,6 +3605,426 @@
 	kfree(panel);
 }
 
+/* src_len should include the null termination */
+ssize_t parse_byte_buf(u8 *out, size_t out_len, char *src, size_t src_len)
+{
+	const char *skip = "\n ";
+	size_t i = 0;
+	int rc = 0;
+	char *s;
+
+	if (unlikely(!src || !src_len || !out || !out_len))
+		return -EINVAL;
+
+	if (strnlen(src, src_len) == src_len)
+		return -EINVAL;
+
+	while (src && !rc && i < out_len) {
+		s = strsep(&src, skip);
+		if (*s != '\0') {
+			rc = kstrtou8(s, 16, out + i);
+			i++;
+		}
+	}
+
+	return rc ? : i;
+}
+
+ssize_t dsi_panel_debugfs_write_reg(struct file *file,
+				    const char __user *user_buf,
+				    size_t count, loff_t *ppos)
+{
+	struct seq_file *seq = file->private_data;
+	struct dsi_panel *panel = seq->private;
+	char *buf;
+	char *payload;
+	size_t len, buf_len;
+	int rc = 0;
+
+	if (!panel || !panel->panel_initialized)
+		return -EPERM;
+
+	buf_len = count + 1;
+	/* calculate length for worst case (1 digit per byte + whitespace) */
+	len = buf_len / 2;
+
+	buf = kmalloc(buf_len, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	payload = kmalloc(len, GFP_KERNEL);
+	if (!payload) {
+		kfree(buf);
+		return -ENOMEM;
+	}
+
+	if (copy_from_user(buf, user_buf, count)) {
+		rc = -EFAULT;
+		goto done;
+	}
+
+	buf[count] = 0; /* terminate end of string */
+
+	rc = parse_byte_buf(payload, len, buf, buf_len);
+	if (rc <= 0) {
+		rc = -EINVAL;
+		goto done;
+	}
+	len = rc;
+
+	pr_debug("writing cmd=%x len=%zu\n", payload[0], len);
+
+	mutex_lock(&panel->panel_lock);
+	rc = mipi_dsi_dcs_write_buffer(&panel->mipi_device, payload, len);
+	mutex_unlock(&panel->panel_lock);
+
+done:
+	kfree(buf);
+	kfree(payload);
+
+	return rc ? : count;
+}
+
+int dsi_panel_debugfs_read_reg(struct seq_file *seq, void *data)
+{
+	struct dsi_panel *panel = seq->private;
+	char *buf;
+	ssize_t rc;
+	size_t len;
+	u8 cmd;
+
+	if (!panel || !panel->panel_initialized)
+		return -EPERM;
+
+	len = panel->debug.reg_read_len;
+	cmd = panel->debug.reg_read_cmd;
+
+	if (len == 0)
+		return -EINVAL;
+
+	buf = kmalloc(max(PAGE_SIZE, len), GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	mutex_lock(&panel->panel_lock);
+	rc = mipi_dsi_dcs_read(&panel->mipi_device, cmd, buf, len);
+	mutex_unlock(&panel->panel_lock);
+
+	if (rc > 0) {
+		int i;
+
+		pr_debug("got %zd bytes back, first: 0x%x\n", rc, buf[0]);
+		for (i = 0; i < rc; i++) {
+			if ((i % 8) > 0)
+				seq_puts(seq, " ");
+			else if (i)
+				seq_puts(seq, "\n");
+			seq_printf(seq, "%02X", buf[i]);
+		}
+		seq_puts(seq, "\n");
+		rc = 0;
+	} else if (rc == 0) {
+		pr_debug("no response back\n");
+	}
+	kfree(buf);
+
+	return rc;
+}
+
+static int dsi_panel_debugfs_open_reg(struct inode *inode, struct file *f)
+{
+	return single_open(f, dsi_panel_debugfs_read_reg, inode->i_private);
+}
+
+static const struct file_operations panel_reg_fops = {
+	.owner =	THIS_MODULE,
+	.open =		dsi_panel_debugfs_open_reg,
+	.write =	dsi_panel_debugfs_write_reg,
+	.read =		seq_read,
+	.llseek =	seq_lseek,
+	.release =	single_release,
+};
+
+struct debugfs_cmdset_entry {
+	struct dsi_panel *panel;
+	struct dsi_panel_cmd_set *set;
+	enum dsi_cmd_set_type type;
+};
+
+struct {
+	const char *label;
+	enum dsi_cmd_set_type type;
+} cmdset_list[] = {
+	{ "pre_on",		DSI_CMD_SET_PRE_ON },
+	{ "on",			DSI_CMD_SET_ON },
+	{ "post_on",		DSI_CMD_SET_POST_ON },
+	{ "pre_off",		DSI_CMD_SET_PRE_OFF },
+	{ "off",		DSI_CMD_SET_OFF },
+	{ "post_off",		DSI_CMD_SET_POST_OFF },
+	{ "panel_status",	DSI_CMD_SET_PANEL_STATUS },
+	{ "pps",		DSI_CMD_SET_PPS },
+	{ "lp1",		DSI_CMD_SET_LP1 },
+	{ "lp2",		DSI_CMD_SET_LP2 },
+	{ "no_lp",		DSI_CMD_SET_NOLP },
+	{ "post_nolp",		DSI_CMD_SET_POST_NOLP },
+	{ "switch",		DSI_CMD_SET_TIMING_SWITCH },
+	{ "post_switch",	DSI_CMD_SET_POST_TIMING_SWITCH },
+};
+
+static inline ssize_t parse_cmdset(struct dsi_panel_cmd_set *set, char *buf,
+				   size_t buf_len)
+{
+	char *tmp;
+	size_t len;
+	ssize_t rc;
+
+	/* calculate length for worst case (1 digit per byte + whitespace) */
+	len = buf_len / 2;
+	tmp = kmalloc(len, GFP_KERNEL);
+	if (!tmp)
+		return -ENOMEM;
+
+	rc = parse_byte_buf(tmp, len, buf, buf_len);
+	if (rc <= 0) {
+		rc = -EINVAL;
+		goto done;
+	}
+
+	rc = dsi_panel_parse_cmd_sets_sub(set, tmp, rc);
+done:
+	kfree(tmp);
+
+	return rc;
+}
+
+static struct dsi_panel_cmd_set *
+get_cmdset_locked(struct debugfs_cmdset_entry *entry)
+{
+	struct dsi_panel *panel = entry->panel;
+
+	if (entry->set)
+		return entry->set;
+
+	if (!panel->cur_mode || !panel->cur_mode->priv_info) {
+		pr_err("Invalid mode for panel [%s]\n", panel->name);
+		return NULL;
+	}
+
+	return &panel->cur_mode->priv_info->cmd_sets[entry->type];
+}
+
+ssize_t dsi_panel_debugfs_write_cmdset(struct file *file,
+				       const char __user *user_buf,
+				       size_t count, loff_t *ppos)
+{
+	struct seq_file *seq = file->private_data;
+	struct debugfs_cmdset_entry *entry = seq->private;
+	struct dsi_panel *panel = entry->panel;
+	struct dsi_panel_cmd_set tmp_set;
+	struct dsi_panel_cmd_set *set;
+	char *buf = NULL;
+	size_t buf_len;
+	int rc = 0;
+
+	mutex_lock(&panel->panel_lock);
+	set = get_cmdset_locked(entry);
+	if (set == NULL) {
+		mutex_unlock(&panel->panel_lock);
+		return -EINVAL;
+	}
+
+	tmp_set = *set;
+	buf_len = count + 1;
+	buf = kmalloc(buf_len, GFP_KERNEL);
+	if (!buf) {
+		rc = -ENOMEM;
+		goto done;
+	}
+
+	if (copy_from_user(buf, user_buf, count)) {
+		rc = -EFAULT;
+		goto done;
+	}
+	buf[count] = '\0';
+
+	rc = parse_cmdset(&tmp_set, buf, buf_len);
+	if (rc)
+		goto done;
+
+	dsi_panel_destroy_cmd_packets(set);
+	dsi_panel_dealloc_cmd_packets(set);
+	*set = tmp_set;
+
+done:
+	kfree(buf);
+	mutex_unlock(&panel->panel_lock);
+
+	return rc ? : count;
+}
+
+int dsi_panel_debugfs_read_cmdset(struct seq_file *seq, void *data)
+{
+	struct debugfs_cmdset_entry *entry = seq->private;
+	struct dsi_panel *panel = entry->panel;
+	struct dsi_panel_cmd_set *set;
+	int i, j;
+
+	mutex_lock(&panel->panel_lock);
+	set = get_cmdset_locked(entry);
+	if (set == NULL) {
+		mutex_unlock(&panel->panel_lock);
+		return -EINVAL;
+	}
+
+	for (i = 0; i < set->count; i++) {
+		struct dsi_cmd_desc *cmd = set->cmds + i;
+		const char *txbuf = cmd->msg.tx_buf;
+
+		seq_printf(seq, "%02X %02X %02X %02X %02X %02zX %02zX",
+			   cmd->msg.type, cmd->last_command, cmd->msg.channel,
+			   cmd->msg.flags & MIPI_DSI_MSG_REQ_ACK ? 1 : 0,
+			   cmd->post_wait_ms,
+			   cmd->msg.tx_len >> 8, cmd->msg.tx_len & 0xff);
+
+		for (j = 0; j < cmd->msg.tx_len; j++)
+			seq_printf(seq, " %02X", txbuf[j]);
+		seq_puts(seq, "\n");
+	}
+	mutex_unlock(&panel->panel_lock);
+
+	return 0;
+}
+
+static int dsi_panel_debugfs_open_cmdset(struct inode *inode, struct file *f)
+{
+	return single_open(f, dsi_panel_debugfs_read_cmdset, inode->i_private);
+}
+
+static const struct file_operations panel_cmdset_fops = {
+	.owner =	THIS_MODULE,
+	.open =		dsi_panel_debugfs_open_cmdset,
+	.write =	dsi_panel_debugfs_write_cmdset,
+	.read =		seq_read,
+	.llseek =	seq_lseek,
+	.release =	single_release,
+};
+
+int dsi_panel_debugfs_create_cmdset_files(struct dentry *parent,
+					  struct debugfs_cmdset_entry *entry,
+					  struct dsi_panel *panel,
+					  struct dsi_panel_cmd_set *set,
+					  const char *label,
+					  const size_t size)
+{
+	struct dentry *file;
+	const char *name;
+	int i;
+
+	for (i = 0; i < size; i++, entry++) {
+		if (set) {
+			name = label;
+			entry->set = set;
+		} else {
+			name = cmdset_list[i].label;
+			entry->type = cmdset_list[i].type;
+		}
+		entry->panel = panel;
+
+		file = debugfs_create_file(name, 0600, parent,
+					   entry, &panel_cmdset_fops);
+		if (IS_ERR_OR_NULL(file)) {
+			DSI_ERR("debugfs create %s file failed\n", name);
+			return PTR_ERR(file);
+		}
+	}
+
+	return 0;
+}
+
+int dsi_panel_debugfs_create_cmdset(struct dentry *parent,
+				    const char *label,
+				    struct dsi_panel *panel,
+				    struct dsi_panel_cmd_set *set)
+{
+	struct device *dev = panel->parent;
+	struct debugfs_cmdset_entry *entry;
+
+	entry = devm_kzalloc(dev, sizeof(*entry), GFP_KERNEL);
+	if (!entry)
+		return -ENOMEM;
+
+	return dsi_panel_debugfs_create_cmdset_files(parent, entry, panel,
+						     set, label, 1);
+}
+
+static void dsi_panel_debugfs_create_cmdsets_from_list(struct dentry *parent,
+						       struct dsi_panel *panel)
+{
+	struct device *dev = panel->parent;
+	struct debugfs_cmdset_entry *entry;
+	const size_t cmds_size = ARRAY_SIZE(cmdset_list);
+	struct dentry *r;
+
+	r = debugfs_create_dir("cmd_sets", parent);
+	if (IS_ERR(r)) {
+		DSI_ERR("debugfs create cmd_sets failed\n");
+		return;
+	}
+
+	entry = devm_kzalloc(dev, cmds_size * sizeof(*entry), GFP_KERNEL);
+	if (!entry)
+		goto error;
+
+	if (dsi_panel_debugfs_create_cmdset_files(r, entry, panel,
+						  NULL, NULL, cmds_size))
+		goto error;
+
+	return;
+
+error:
+	debugfs_remove_recursive(r);
+}
+
+void dsi_panel_debugfs_init(struct dsi_panel *panel, struct dentry *dir)
+{
+	struct dentry *r, *file;
+	struct dsi_panel_debug *pdbg = &panel->debug;
+
+	r = debugfs_create_dir("panel_reg", dir);
+	if (IS_ERR(r))
+		return;
+
+	/* default read of 2 bytes */
+	pdbg->reg_read_len = 2;
+
+	file = debugfs_create_u8("addr", 0600, r, &pdbg->reg_read_cmd);
+	if (IS_ERR_OR_NULL(file)) {
+		DSI_ERR("debugfs create addr file failed\n");
+		goto error;
+	}
+
+	file = debugfs_create_size_t("len", 0600, r, &pdbg->reg_read_len);
+	if (IS_ERR_OR_NULL(file)) {
+		DSI_ERR("debugfs create len file failed\n");
+		goto error;
+	}
+
+	file = debugfs_create_file("payload", 0600, r, panel, &panel_reg_fops);
+	if (IS_ERR_OR_NULL(file)) {
+		DSI_ERR("debugfs create payload file failed\n");
+		goto error;
+	}
+
+	dsi_panel_debugfs_create_cmdsets_from_list(dir, panel);
+	dsi_panel_bl_debugfs_init(dir, panel);
+	dsi_panel_bl_elvss_debugfs_init(dir, panel);
+
+	return;
+
+error:
+	debugfs_remove_recursive(r);
+}
+
 int dsi_panel_drv_init(struct dsi_panel *panel,
 		       struct mipi_dsi_host *host)
 {
@@ -3551,6 +4079,8 @@
 		goto error_gpio_release;
 	}
 
+	rc = dsi_panel_switch_init(panel);
+
 	goto exit;
 
 error_gpio_release:
@@ -3573,6 +4103,8 @@
 		return -EINVAL;
 	}
 
+	dsi_panel_switch_destroy(panel);
+
 	mutex_lock(&panel->panel_lock);
 
 	rc = dsi_panel_bl_unregister(panel);
@@ -3722,12 +4254,15 @@
 	if (!mode->priv_info)
 		return;
 
+	dsi_panel_switch_put_mode(mode);
+
 	for (i = 0; i < DSI_CMD_SET_MAX; i++) {
 		dsi_panel_destroy_cmd_packets(&mode->priv_info->cmd_sets[i]);
 		dsi_panel_dealloc_cmd_packets(&mode->priv_info->cmd_sets[i]);
 	}
-
+	kfree(mode->priv_info->phy_timing_val);
 	kfree(mode->priv_info);
+	mode->priv_info = NULL;
 }
 
 void dsi_panel_calc_dsi_transfer_time(struct dsi_host_common_cfg *config,
@@ -3776,15 +4311,14 @@
 
 	timing->min_dsi_clk_hz = min_bitclk_hz;
 
-	if (timing->clk_rate_hz) {
+	if (mode->priv_info->mdp_transfer_time_us) {
+		timing->dsi_transfer_time_us =
+			mode->priv_info->mdp_transfer_time_us;
+	} else if (timing->clk_rate_hz) {
 		/* adjust the transfer time proportionately for bit clk*/
 		dsi_transfer_time_us = frame_time_us * min_bitclk_hz;
 		do_div(dsi_transfer_time_us, timing->clk_rate_hz);
 		timing->dsi_transfer_time_us = dsi_transfer_time_us;
-
-	} else if (mode->priv_info->mdp_transfer_time_us) {
-		timing->dsi_transfer_time_us =
-			mode->priv_info->mdp_transfer_time_us;
 	} else {
 
 		min_threshold_us = mult_frac(frame_time_us,
@@ -4003,6 +4537,13 @@
 
 	mutex_lock(&panel->panel_lock);
 
+	rc = dsi_pwr_enable_regulator(&panel->power_info, true);
+	if (rc) {
+		DSI_ERR("[%s] failed to enable vregs, rc=%d\n",
+				panel->name, rc);
+		goto error;
+	}
+
 	/* If LP11_INIT is set, panel will be powered up during prepare() */
 	if (panel->lp11_init)
 		goto error;
@@ -4035,12 +4576,17 @@
 
 	set = &priv_info->cmd_sets[DSI_CMD_SET_PPS];
 
-	dsi_dsc_create_pps_buf_cmd(&priv_info->dsc, panel->dsc_pps_cmd, 0);
-	rc = dsi_panel_create_cmd_packets(panel->dsc_pps_cmd,
-					  DSI_CMD_PPS_SIZE, 1, set->cmds);
-	if (rc) {
-		DSI_ERR("failed to create cmd packets, rc=%d\n", rc);
-		goto error;
+	if (!priv_info->pps_created) {
+		dsi_dsc_create_pps_buf_cmd(&priv_info->dsc, panel->dsc_pps_cmd, 0);
+		rc = dsi_panel_create_cmd_packets(panel->dsc_pps_cmd,
+						DSI_CMD_PPS_SIZE, 1, set->cmds);
+		if (rc) {
+			DSI_ERR("failed to create cmd packets, rc=%d\n", rc);
+			goto error;
+		}
+		else {
+			priv_info->pps_created = true;
+		}
 	}
 
 	rc = dsi_panel_tx_cmd_set(panel, DSI_CMD_SET_PPS);
@@ -4049,7 +4595,6 @@
 			panel->name, rc);
 	}
 
-	dsi_panel_destroy_cmd_packets(set);
 error:
 	mutex_unlock(&panel->panel_lock);
 	return rc;
@@ -4064,27 +4609,35 @@
 		return -EINVAL;
 	}
 
+	if (panel->funcs && panel->funcs->pre_lp1)
+		panel->funcs->pre_lp1(panel);
+
+	dsi_backlight_early_dpms(&panel->bl_config, SDE_MODE_DPMS_LP1);
+
 	mutex_lock(&panel->panel_lock);
 	if (!panel->panel_initialized)
 		goto exit;
 
-	/*
-	 * Consider LP1->LP2->LP1.
-	 * If the panel is already in LP mode, do not need to
-	 * set the regulator.
-	 * IBB and AB power mode would be set at the same time
-	 * in PMIC driver, so we only call ibb setting that is enough.
-	 */
-	if (dsi_panel_is_type_oled(panel) &&
-		panel->power_mode != SDE_MODE_DPMS_LP2)
-		dsi_pwr_panel_regulator_mode_set(&panel->power_info,
-			"ibb", REGULATOR_MODE_IDLE);
+	rc = dsi_panel_update_hbm_locked(panel, HBM_MODE_OFF);
+	if (rc) {
+		DSI_ERR("[%s] couldn't disable HBM mode for LP1 transition\n",
+			panel->name);
+		mutex_unlock(&panel->panel_lock);
+		return rc;
+	}
+	dsi_backlight_hbm_dimming_stop(&panel->bl_config);
+
 	rc = dsi_panel_tx_cmd_set(panel, DSI_CMD_SET_LP1);
 	if (rc)
 		DSI_ERR("[%s] failed to send DSI_CMD_SET_LP1 cmd, rc=%d\n",
 		       panel->name, rc);
 exit:
 	mutex_unlock(&panel->panel_lock);
+
+	if (!rc)
+		rc = dsi_backlight_late_dpms(&panel->bl_config,
+					       SDE_MODE_DPMS_LP1);
+
 	return rc;
 }
 
@@ -4097,16 +4650,32 @@
 		return -EINVAL;
 	}
 
+	dsi_backlight_early_dpms(&panel->bl_config, SDE_MODE_DPMS_LP2);
+
 	mutex_lock(&panel->panel_lock);
 	if (!panel->panel_initialized)
 		goto exit;
 
+	rc = dsi_panel_update_hbm_locked(panel, HBM_MODE_OFF);
+	if (rc) {
+		DSI_ERR("[%s] couldn't disable HBM mode for LP2 transition\n",
+			panel->name);
+		mutex_unlock(&panel->panel_lock);
+		return rc;
+	}
+	dsi_backlight_hbm_dimming_stop(&panel->bl_config);
+
 	rc = dsi_panel_tx_cmd_set(panel, DSI_CMD_SET_LP2);
 	if (rc)
 		DSI_ERR("[%s] failed to send DSI_CMD_SET_LP2 cmd, rc=%d\n",
 		       panel->name, rc);
 exit:
 	mutex_unlock(&panel->panel_lock);
+
+	if (!rc)
+		rc = dsi_backlight_late_dpms(&panel->bl_config,
+					       SDE_MODE_DPMS_LP2);
+
 	return rc;
 }
 
@@ -4119,27 +4688,137 @@
 		return -EINVAL;
 	}
 
+	dsi_backlight_early_dpms(&panel->bl_config, SDE_MODE_DPMS_ON);
+
 	mutex_lock(&panel->panel_lock);
 	if (!panel->panel_initialized)
 		goto exit;
 
-	/*
-	 * Consider about LP1->LP2->NOLP.
-	 */
-	if (dsi_panel_is_type_oled(panel) &&
-	    (panel->power_mode == SDE_MODE_DPMS_LP1 ||
-	     panel->power_mode == SDE_MODE_DPMS_LP2))
-		dsi_pwr_panel_regulator_mode_set(&panel->power_info,
-			"ibb", REGULATOR_MODE_NORMAL);
-	rc = dsi_panel_tx_cmd_set(panel, DSI_CMD_SET_NOLP);
-	if (rc)
-		DSI_ERR("[%s] failed to send DSI_CMD_SET_NOLP cmd, rc=%d\n",
-		       panel->name, rc);
+	if (panel->funcs && panel->funcs->send_nolp)
+		rc = panel->funcs->send_nolp(panel);
+	else
+		rc = -EOPNOTSUPP;
+
+	if (rc == -EOPNOTSUPP) {
+		rc = dsi_panel_tx_cmd_set(panel, DSI_CMD_SET_NOLP);
+		if (rc)
+			DSI_ERR("[%s] failed to send DSI_CMD_SET_NOLP cmd, rc=%d\n",
+			       panel->name, rc);
+	}
 exit:
 	mutex_unlock(&panel->panel_lock);
+
+	if (!rc)
+		rc = dsi_backlight_late_dpms(&panel->bl_config,
+					       SDE_MODE_DPMS_ON);
 	return rc;
 }
 
+static int dsi_panel_update_hbm_locked(struct dsi_panel *panel,
+					enum hbm_mode_type hbm_mode)
+{
+	struct dsi_backlight_config *bl = &panel->bl_config;
+	struct hbm_data *hbm = bl->hbm;
+
+	if (!hbm)
+		return 0;
+
+	if (hbm_mode < 0 || hbm_mode >= HBM_MODE_MAX)
+		return -EINVAL;
+	if (hbm_mode == panel->hbm_mode)
+		return 0;
+
+	if (hbm_mode == HBM_MODE_SV && !panel->hbm_sv_enabled) {
+		pr_warn("hbmsv is disabled\n");
+		return -EINVAL;
+	}
+
+	if (dsi_backlight_get_dpms(bl) != SDE_MODE_DPMS_ON) {
+		DSI_ERR("[%s] Backlight in incompatible state, HBM changes not allowed\n",
+			panel->name);
+		return -EINVAL;
+	}
+
+	if (hbm_mode == HBM_MODE_SV)
+		mod_delayed_work(system_wq, &panel->hanghandler_work,
+				      msecs_to_jiffies(HBM_SV_MAX_MS));
+	else
+		cancel_delayed_work(&panel->hanghandler_work);
+
+	panel->hbm_pending_irc_on =
+		(panel->hbm_mode == HBM_MODE_SV && hbm_mode == HBM_MODE_OFF);
+
+	hbm->cur_range = HBM_RANGE_MAX;
+
+	if (hbm_mode == HBM_MODE_SV) {
+		int rc = dsi_panel_bl_update_irc(bl, false);
+
+		if (rc != 0 && rc != -EOPNOTSUPP)
+			pr_err("[%s] failed to disable IRC, rc=%d\n",
+				panel->name, rc);
+	} else if (hbm_mode == HBM_MODE_ON && panel->hbm_mode == HBM_MODE_SV) {
+		int rc = dsi_panel_bl_update_irc(bl, true);
+
+		if (rc != 0 && rc != -EOPNOTSUPP)
+			pr_err("[%s] failed to enable IRC, rc=%d\n",
+				panel->name, rc);
+	}
+
+	panel->hbm_mode = hbm_mode;
+
+	/* When HBM exit is requested, send HBM exit commands
+	 * immediately to avoid conflict with subsequent backlight ops.
+	 */
+	if (hbm_mode == HBM_MODE_OFF) {
+		int rc = 0;
+
+		dsi_backlight_hbm_dimming_start(bl,
+			hbm->exit_num_dimming_frames,
+			&hbm->exit_dimming_stop_cmd);
+		rc = dsi_panel_switch_update_hbm(panel);
+		if (rc == -EOPNOTSUPP)
+			rc = dsi_panel_cmd_set_transfer(panel, &hbm->exit_cmd);
+		if (rc)
+			pr_err("[%s] failed to send HBM exit cmd, rc=%d\n",
+				panel->name, rc);
+	}
+
+	if (bl->bl_device)
+		sysfs_notify(&bl->bl_device->dev.kobj, NULL,
+					"state");
+
+	return 0;
+}
+
+int dsi_panel_update_hbm(struct dsi_panel *panel, enum hbm_mode_type hbm_mode)
+{
+	int rc = 0;
+
+	if (!panel)
+		return -EINVAL;
+
+	if (!panel->bl_config.hbm)
+		return 0;
+
+	mutex_lock(&panel->panel_lock);
+	rc = dsi_panel_update_hbm_locked(panel, hbm_mode);
+	mutex_unlock(&panel->panel_lock);
+	if (rc)
+		return rc;
+
+	return backlight_update_status(panel->bl_config.bl_device);
+}
+
+enum hbm_mode_type dsi_panel_get_hbm(struct dsi_panel *panel)
+{
+	if (!panel) {
+		DSI_ERR("invalid params\n");
+		return false;
+	}
+
+	return panel->hbm_mode;
+}
+
 int dsi_panel_prepare(struct dsi_panel *panel)
 {
 	int rc = 0;
@@ -4149,8 +4828,13 @@
 		return -EINVAL;
 	}
 
+	dsi_backlight_early_dpms(&panel->bl_config, SDE_MODE_DPMS_ON);
+
 	mutex_lock(&panel->panel_lock);
 
+	if (panel->init_delay_us)
+		usleep_range(panel->init_delay_us, panel->init_delay_us);
+
 	if (panel->lp11_init) {
 		rc = dsi_panel_power_on(panel);
 		if (rc) {
@@ -4169,6 +4853,11 @@
 
 error:
 	mutex_unlock(&panel->panel_lock);
+
+	if (!rc)
+		rc = dsi_backlight_late_dpms(&panel->bl_config,
+					       SDE_MODE_DPMS_OFF);
+
 	return rc;
 }
 
@@ -4422,6 +5111,11 @@
 		return -EINVAL;
 	}
 
+	if (panel->funcs && panel->funcs->mode_switch) {
+		rc = panel->funcs->mode_switch(panel);
+		return rc;
+	}
+
 	mutex_lock(&panel->panel_lock);
 
 	rc = dsi_panel_tx_cmd_set(panel, DSI_CMD_SET_TIMING_SWITCH);
@@ -4465,12 +5159,20 @@
 	mutex_lock(&panel->panel_lock);
 
 	rc = dsi_panel_tx_cmd_set(panel, DSI_CMD_SET_ON);
-	if (rc)
+	if (rc) {
 		DSI_ERR("[%s] failed to send DSI_CMD_SET_ON cmds, rc=%d\n",
 		       panel->name, rc);
-	else
+	} else {
 		panel->panel_initialized = true;
+		panel->power_mode = SDE_MODE_DPMS_ON;
+	}
+
 	mutex_unlock(&panel->panel_lock);
+
+	if (!rc)
+		rc = dsi_backlight_late_dpms(&panel->bl_config,
+					       SDE_MODE_DPMS_ON);
+
 	return rc;
 }
 
@@ -4483,6 +5185,9 @@
 		return -EINVAL;
 	}
 
+	if (panel->funcs && panel->funcs->post_enable)
+		return panel->funcs->post_enable(panel);
+
 	mutex_lock(&panel->panel_lock);
 
 	rc = dsi_panel_tx_cmd_set(panel, DSI_CMD_SET_POST_ON);
@@ -4505,6 +5210,8 @@
 		return -EINVAL;
 	}
 
+	dsi_backlight_early_dpms(&panel->bl_config, SDE_MODE_DPMS_OFF);
+
 	mutex_lock(&panel->panel_lock);
 
 	rc = dsi_panel_tx_cmd_set(panel, DSI_CMD_SET_PRE_OFF);
@@ -4516,6 +5223,10 @@
 
 error:
 	mutex_unlock(&panel->panel_lock);
+
+	if (!rc && panel->funcs && panel->funcs->pre_disable)
+		rc = panel->funcs->pre_disable(panel);
+
 	return rc;
 }
 
@@ -4530,17 +5241,14 @@
 
 	mutex_lock(&panel->panel_lock);
 
+	rc = dsi_panel_update_hbm_locked(panel, HBM_MODE_OFF);
+	if (rc)
+		DSI_WARN("[%s] couldn't disable HBM mode to unprepare display\n",
+			panel->name);
+	dsi_backlight_hbm_dimming_stop(&panel->bl_config);
+
 	/* Avoid sending panel off commands when ESD recovery is underway */
 	if (!atomic_read(&panel->esd_recovery_pending)) {
-		/*
-		 * Need to set IBB/AB regulator mode to STANDBY,
-		 * if panel is going off from AOD mode.
-		 */
-		if (dsi_panel_is_type_oled(panel) &&
-			(panel->power_mode == SDE_MODE_DPMS_LP1 ||
-			panel->power_mode == SDE_MODE_DPMS_LP2))
-			dsi_pwr_panel_regulator_mode_set(&panel->power_info,
-				"ibb", REGULATOR_MODE_STANDBY);
 		rc = dsi_panel_tx_cmd_set(panel, DSI_CMD_SET_OFF);
 		if (rc) {
 			/*
@@ -4558,6 +5266,11 @@
 	panel->power_mode = SDE_MODE_DPMS_OFF;
 
 	mutex_unlock(&panel->panel_lock);
+
+	if (!rc)
+		rc = dsi_backlight_late_dpms(&panel->bl_config,
+						SDE_MODE_DPMS_OFF);
+
 	return rc;
 }
 
@@ -4581,6 +5294,10 @@
 
 error:
 	mutex_unlock(&panel->panel_lock);
+
+	if (panel->bl_config.bl_update == BL_UPDATE_DELAY_UNTIL_FIRST_FRAME)
+		panel->bl_config.allow_bl_update = false;
+
 	return rc;
 }
 
@@ -4605,3 +5322,25 @@
 	mutex_unlock(&panel->panel_lock);
 	return rc;
 }
+
+int dsi_panel_idle(struct dsi_panel *panel)
+{
+	if (unlikely(!panel))
+		return -EINVAL;
+
+	if (panel->funcs && panel->funcs->idle)
+		return panel->funcs->idle(panel);
+
+	return 0;
+}
+
+int dsi_panel_wakeup(struct dsi_panel *panel)
+{
+	if (unlikely(!panel))
+		return -EINVAL;
+
+	if (panel->funcs && panel->funcs->wakeup)
+		return panel->funcs->wakeup(panel);
+
+	return 0;
+}
diff --git a/msm/dsi/dsi_panel.h b/msm/dsi/dsi_panel.h
index ee30cd5..3160287 100644
--- a/msm/dsi/dsi_panel.h
+++ b/msm/dsi/dsi_panel.h
@@ -25,9 +25,15 @@
 #define MAX_BL_SCALE_LEVEL 1024
 #define MAX_SV_BL_SCALE_LEVEL 65535
 #define DSI_CMD_PPS_SIZE 135
+#define BL_RANGE_MAX 10
 
 #define DSI_MODE_MAX 32
+#define HBM_RANGE_MAX 4
+#define DYNAMIC_ELVSS_RANGE_MAX 10
 
+#define BL_STATE_STANDBY	BL_CORE_FBBLANK
+#define BL_STATE_LP		BL_CORE_LP1
+#define BL_STATE_LP2		BL_CORE_LP2
 /*
  * Defining custom dsi msg flag,
  * continued from drm_mipi_dsi.h
@@ -75,6 +81,13 @@
 	DSI_DISPLAY_PANEL_TYPE_MAX,
 };
 
+enum hbm_mode_type {
+	HBM_MODE_OFF = 0,
+	HBM_MODE_ON,
+	HBM_MODE_SV,
+	HBM_MODE_MAX,
+};
+
 struct dsi_dfps_capabilities {
 	enum dsi_dfps_type type;
 	u32 min_refresh_rate;
@@ -111,6 +124,106 @@
 	enum dsi_panel_rotation rotation;
 };
 
+struct hbm_range {
+	/* Userspace brightness range (inclusive) for this HBM range */
+	u32 user_bri_start;
+	u32 user_bri_end;
+
+	/* Panel brightness range (inclusive) for this HBM range */
+	u32 panel_bri_start;
+	u32 panel_bri_end;
+
+	/* Command to be sent to the panel when entering this HBM range */
+	struct dsi_panel_cmd_set entry_cmd;
+	/*
+	 * Command to be sent to the panel to stop brightness dimming while
+	 * in this HBM range.
+	 */
+	struct dsi_panel_cmd_set dimming_stop_cmd;
+	/* Number of frames dimming will take. */
+	u32 num_dimming_frames;
+};
+
+enum elvss_mode {
+	ELVSS_MODE_INIT = 0,
+	ELVSS_MODE_ENABLE,
+	ELVSS_MODE_DISABLE
+};
+
+enum ctrl_elvss {
+	ELVSS_PRE_UPDATE = 0,
+	ELVSS_POST_UPDATE
+};
+
+struct elvss_range {
+	/* Use brightness threshold to update the command */
+	u32 brightness_threshold;
+
+	/* Command to be sent to the panel to adjust the ELVSS power */
+	struct dsi_panel_cmd_set elvss_cmd;
+};
+
+struct dynamic_elvss_data {
+	/* Record the current elvss range */
+	u32 cur_elvss_range;
+	/* Number of elvss ranges */
+	u32 num_ranges;
+	/* Different status*/
+	enum elvss_mode cur_mode;
+
+	/* Command to disable dynamic elvss */
+	struct dsi_panel_cmd_set disable_dynamic_elvss_cmd;
+
+	/* Store the elvss data */
+	struct elvss_range nodes[DYNAMIC_ELVSS_RANGE_MAX];
+
+	/* Enable/Disable dynamic elvss */
+	bool enable_dynamic_elvss;
+};
+
+struct hbm_data {
+	/* IRC register address */
+	u8 irc_addr;
+	u8 *irc_data;
+	u32 irc_bit_offset;
+
+	/* Command to be sent to the panel to irc unlock */
+	struct dsi_panel_cmd_set irc_unlock_cmd;
+	/* Command to be sent to the panel to irc lock  */
+	struct dsi_panel_cmd_set irc_lock_cmd;
+
+	/* Command to be sent to the panel when exiting HBM */
+	struct dsi_panel_cmd_set exit_cmd;
+	/* Command to be sent to the panel to stop brightness dimming */
+	struct dsi_panel_cmd_set exit_dimming_stop_cmd;
+	/* Number of frames dimming will take */
+	u32 exit_num_dimming_frames;
+
+	struct hbm_range ranges[HBM_RANGE_MAX];
+	u32 num_ranges;
+	u32 cur_range;
+
+	/* Brightness dimming currently active */
+	bool dimming_active;
+	/* Total number of frames brightness dimming takes */
+	u32 dimming_frames_total;
+	/* Number of frames remaining until brightness settles */
+	u32 dimming_frames_left;
+	/* DSI command to send once brightness dimming settles */
+	struct dsi_panel_cmd_set *dimming_stop_cmd;
+
+	/* Work queue used to count frames during dimming */
+	struct workqueue_struct *dimming_workq;
+	struct work_struct dimming_work;
+	struct dsi_panel *panel;
+};
+
+struct bl_notifier_data {
+	u32 ranges[BL_RANGE_MAX];
+	u32 num_ranges;
+	u32 cur_range;
+};
+
 struct dsi_backlight_config {
 	enum dsi_backlight_type type;
 	enum bl_update_flag bl_update;
@@ -118,21 +231,49 @@
 	u32 bl_min_level;
 	u32 bl_max_level;
 	u32 brightness_max_level;
-	u32 bl_level;
 	u32 bl_scale;
 	u32 bl_scale_sv;
-	bool bl_inverted_dbv;
-	u32 bl_dcs_subtype;
+	u32 bl_actual;
+	u16 *lut;
+	bool bl_update_pending;
+	bool allow_bl_update;
+	bool dimming_mode;
+	u32 high_byte_offset;
+	unsigned int last_state;
+	struct mutex state_lock;
+
+	struct bl_notifier_data *bl_notifier;
+	struct dynamic_elvss_data *elvss;
+	struct hbm_data *hbm;
 
 	int en_gpio;
-	/* PWM params */
-	struct pwm_device *pwm_bl;
-	bool pwm_enabled;
-	u32 pwm_period_usecs;
+	struct backlight_device *bl_device;
+	struct regulator *lab_vreg;
 
-	/* WLED params */
-	struct led_trigger *wled;
-	struct backlight_device *raw_bd;
+	void *priv;
+
+	/**
+	 * update_bl - function used to update backlight
+	 * @bl_cfg - ptr to backlight config struct
+	 * @bl_lvl - backlight level set
+	 *
+	 * return: non-zero on success otherwise errno
+	 */
+	int (*update_bl)(struct dsi_backlight_config *bl_cfg, u32 bl_lvl);
+
+	/**
+	 * unregister - unregisters and frees any backlight data
+	 * @bl_cfg - ptr to backlight config struct
+	 */
+	void (*unregister)(struct dsi_backlight_config *bl_cfg);
+
+	/**
+	 * debugfs_init - debugfs initialization for DSI backlight
+	 * @parent - dentry to create
+	 * @bl_cfg - ptr to backlight config struct
+	 */
+	void (*debugfs_init)(struct dentry *parent,
+			     struct dsi_backlight_config *bl_cfg);
 };
 
 struct dsi_reset_seq {
@@ -150,6 +291,11 @@
 	u32 mode_sel_state;
 };
 
+struct dsi_panel_debug {
+	u8 reg_read_cmd;
+	size_t reg_read_len;
+};
+
 enum esd_check_status_mode {
 	ESD_MODE_REG_READ,
 	ESD_MODE_SW_BTA,
@@ -172,6 +318,44 @@
 	u32 groups;
 };
 
+struct dsi_panel_sn_location {
+	u32 start_byte;
+	u32 sn_length;
+	u8 addr;
+};
+
+struct dsi_panel_vendor_info {
+	struct dsi_panel_sn_location location;
+	bool is_sn;
+	u8 *sn;
+	const char *name;
+	u8 extinfo_loc_length;
+	u32 *extinfo_loc;
+	u8 extinfo_length;
+	u8 extinfo_read;
+	u8 *extinfo;
+};
+
+struct dsi_panel_te2_edge {
+	u16 rising;
+	u16 falling;
+};
+
+enum dsi_panel_te2_type {
+	TE2_EDGE_90HZ,
+	TE2_EDGE_60HZ,
+	TE2_EDGE_LP_HIGH,
+	TE2_EDGE_LP_LOW,
+	TE2_EDGE_MAX
+};
+
+struct dsi_panel_te2_config {
+	struct dsi_panel_te2_edge te2_edge[TE2_EDGE_MAX];
+	enum dsi_panel_te2_type current_type;
+	bool te2_ready;
+	u32 lp_threshold;
+};
+
 struct dsi_panel {
 	const char *name;
 	const char *type;
@@ -200,12 +384,16 @@
 	struct dsi_regulator_info power_info;
 	struct dsi_backlight_config bl_config;
 	struct dsi_panel_reset_config reset_config;
+	struct dsi_panel_te2_config te2_config;
 	struct dsi_pinctrl_info pinctrl;
 	struct drm_panel_hdr_properties hdr_props;
 	struct drm_panel_esd_config esd_config;
+	struct dsi_panel_debug debug;
 
 	struct dsi_parser_utils utils;
+	struct dsi_panel_vendor_info vendor_info;
 
+	u32 init_delay_us;
 	bool lp11_init;
 	bool ulps_feature_enabled;
 	bool ulps_suspend_enabled;
@@ -225,6 +413,49 @@
 	int panel_test_gpio;
 	int power_mode;
 	enum dsi_panel_physical_type panel_type;
+
+	const struct dsi_panel_funcs *funcs;
+	void *private_data;
+
+	/* the following set of members are guarded by panel_lock */
+	enum hbm_mode_type hbm_mode;
+	bool hbm_pending_irc_on;
+	bool hbm_sv_enabled;
+	/* Work used to handle hbmsv hang */
+	struct delayed_work hanghandler_work;
+};
+
+/**
+ * struct dsi_panel_funcs - functions that handle panel switch operations
+ *
+ * @pre_disable: called before panel is about to be disabled
+ * @post_enable: called on panel post enable
+ * @mode_switch: called when a mode switch is happening
+ * @pre_kickoff: called just before frame kickoff
+ * @idle: called when updates haven't been received for a while (idle)
+ * @wakeup: called when coming out of idle state
+ * @pre_lp1: called before power mode is going to be lp1
+ * @update_te2: called when te2 configuration needs to be updated
+ *
+ * Note: none of these functions above should be called while holding panel_lock
+ *
+ * @update_hbm: for certain projects hbm/dimming configuration may need to be
+ * kept in sync depending on current mode. This function should be called with
+ * updated hbm/dimming params
+ * @send_nolp: called when sending nolp commands
+ */
+struct dsi_panel_funcs {
+	int (*pre_disable)(struct dsi_panel *);
+	int (*post_enable)(struct dsi_panel *);
+	int (*mode_switch)(struct dsi_panel *);
+	int (*pre_kickoff)(struct dsi_panel *);
+	int (*idle)(struct dsi_panel *);
+	int (*wakeup)(struct dsi_panel *);
+	int (*pre_lp1)(struct dsi_panel *);
+	int (*update_te2)(struct dsi_panel *panel);
+	int (*update_hbm)(struct dsi_panel *);
+	int (*update_irc)(struct dsi_panel *, bool);
+	int (*send_nolp)(struct dsi_panel *);
 };
 
 static inline bool dsi_panel_ulps_feature_enabled(struct dsi_panel *panel)
@@ -252,6 +483,11 @@
 	return (panel->panel_type == DSI_DISPLAY_PANEL_TYPE_OLED);
 }
 
+static inline bool is_standby_mode(unsigned long state)
+{
+	return (state & BL_STATE_STANDBY) != 0;
+}
+
 struct dsi_panel *dsi_panel_get(struct device *parent,
 				struct device_node *of_node,
 				struct device_node *parser_node,
@@ -266,6 +502,8 @@
 
 int dsi_panel_drv_deinit(struct dsi_panel *panel);
 
+void dsi_panel_debugfs_init(struct dsi_panel *panel, struct dentry *dir);
+
 int dsi_panel_get_mode_count(struct dsi_panel *panel);
 
 void dsi_panel_put_mode(struct dsi_display_mode *mode);
@@ -309,8 +547,6 @@
 
 int dsi_panel_post_unprepare(struct dsi_panel *panel);
 
-int dsi_panel_set_backlight(struct dsi_panel *panel, u32 bl_lvl);
-
 int dsi_panel_update_pps(struct dsi_panel *panel);
 
 int dsi_panel_send_qsync_on_dcs(struct dsi_panel *panel,
@@ -332,8 +568,6 @@
 
 void dsi_dsc_pclk_param_calc(struct msm_display_dsc_info *dsc, int intf_width);
 
-void dsi_panel_bl_handoff(struct dsi_panel *panel);
-
 struct dsi_panel *dsi_panel_ext_bridge_get(struct device *parent,
 				struct device_node *of_node,
 				int topology_override);
@@ -345,4 +579,57 @@
 void dsi_panel_calc_dsi_transfer_time(struct dsi_host_common_cfg *config,
 		struct dsi_display_mode *mode, u32 frame_threshold_us);
 
+int dsi_panel_cmd_set_transfer(struct dsi_panel *panel,
+			       struct dsi_panel_cmd_set *cmd);
+int dsi_panel_parse_dt_cmd_set(struct device_node *of_node,
+			       const char *cmd_str,
+			       const char *cmd_state_str,
+			       struct dsi_panel_cmd_set *cmd);
+void dsi_panel_destroy_cmd_packets(struct dsi_panel_cmd_set *set);
+void dsi_panel_dealloc_cmd_packets(struct dsi_panel_cmd_set *set);
+
+int dsi_panel_debugfs_create_cmdset(struct dentry *parent,
+				    const char *label,
+				    struct dsi_panel *panel,
+				    struct dsi_panel_cmd_set *set);
+
+int dsi_backlight_early_dpms(struct dsi_backlight_config *bl, int power_state);
+int dsi_backlight_late_dpms(struct dsi_backlight_config *bl, int power_state);
+
+int dsi_backlight_get_dpms(struct dsi_backlight_config *bl);
+
+int dsi_backlight_hbm_dimming_start(struct dsi_backlight_config *bl,
+	u32 num_frames, struct dsi_panel_cmd_set *stop_cmd);
+void dsi_backlight_hbm_dimming_stop(struct dsi_backlight_config *bl);
+
+int dsi_panel_bl_register(struct dsi_panel *panel);
+int dsi_panel_bl_unregister(struct dsi_panel *panel);
+int dsi_panel_bl_parse_config(struct device *parent,
+			      struct dsi_backlight_config *bl);
+int dsi_panel_bl_brightness_handoff(struct dsi_panel *panel);
+void dsi_panel_bl_debugfs_init(struct dentry *parent, struct dsi_panel *panel);
+void dsi_panel_bl_elvss_debugfs_init(struct dentry *parent,
+				     struct dsi_panel *panel);
+
+/* Set/get high brightness mode */
+int dsi_panel_update_hbm(struct dsi_panel *panel, enum hbm_mode_type);
+enum hbm_mode_type dsi_panel_get_hbm(struct dsi_panel *panel);
+
+int dsi_panel_bl_update_irc(struct dsi_backlight_config *bl, bool enable);
+
+int dsi_panel_switch_init(struct dsi_panel *panel);
+void dsi_panel_switch_destroy(struct dsi_panel *panel);
+void dsi_panel_switch_put_mode(struct dsi_display_mode *mode);
+int dsi_panel_switch_update_hbm(struct dsi_panel *panel);
+
+int dsi_panel_idle(struct dsi_panel *panel);
+int dsi_panel_wakeup(struct dsi_panel *panel);
+
+int dsi_panel_get_sn(struct dsi_panel *panel);
+int dsi_panel_get_vendor_extinfo(struct dsi_panel *panel);
+
+ssize_t parse_byte_buf(u8 *out, size_t out_len, char *src, size_t src_len);
+int parse_u32_buf(char *src, size_t src_len, u32 *out, size_t out_len);
+const struct dsi_display_mode *get_panel_display_mode(struct dsi_panel *panel);
+
 #endif /* _DSI_PANEL_H_ */
diff --git a/msm/dsi/dsi_panel_switch.c b/msm/dsi/dsi_panel_switch.c
new file mode 100644
index 0000000..17496b1
--- /dev/null
+++ b/msm/dsi/dsi_panel_switch.c
@@ -0,0 +1,2317 @@
+/*
+ * Copyright (c) 2019, 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/atomic.h>
+#include <linux/completion.h>
+#include <linux/wait.h>
+#include <linux/kthread.h>
+#include <uapi/linux/sched/types.h>
+#include <video/mipi_display.h>
+
+#include "dsi_display.h"
+#include "dsi_panel.h"
+#include "sde_trace.h"
+#include "sde_connector.h"
+
+#define DSI_PANEL_GAMMA_NAME "google,dsi_s6e3hc2_gamma"
+#define DSI_PANEL_SWITCH_NAME "google,dsi_panel_switch"
+
+#define TE_TIMEOUT_MS	50
+
+#define for_each_display_mode(i, mode, panel) \
+	for (i = 0, mode = dsi_panel_to_display(panel)->modes; \
+		i < panel->num_timing_nodes; i++, mode++)
+
+
+#define DSI_WRITE_CMD_BUF(dsi, cmd) \
+	(IS_ERR_VALUE(mipi_dsi_dcs_write_buffer(dsi, cmd, ARRAY_SIZE(cmd))))
+
+#define DEFAULT_GAMMA_STR "default"
+#define CALI_GAMMA_HEADER_SIZE	3
+
+#define S6E3HC2_DEFAULT_FPS 60
+
+static const u8 unlock_cmd[] = { 0xF0, 0x5A, 0x5A };
+static const u8 lock_cmd[]   = { 0xF0, 0xA5, 0xA5 };
+
+struct panel_switch_funcs {
+	struct panel_switch_data *(*create)(struct dsi_panel *panel);
+	void (*destroy)(struct panel_switch_data *pdata);
+	void (*put_mode)(struct dsi_display_mode *mode);
+	void (*perform_switch)(struct panel_switch_data *pdata,
+			       const struct dsi_display_mode *mode);
+	int (*post_enable)(struct panel_switch_data *pdata);
+	int (*support_update_te2)(struct dsi_panel *panel);
+	int (*support_update_hbm)(struct dsi_panel *);
+	int (*send_nolp_cmds)(struct dsi_panel *panel);
+	ssize_t (*support_cali_gamma_store)(struct dsi_panel *panel,
+				const char *buf, size_t count);
+};
+
+struct panel_switch_data {
+	struct dsi_panel *panel;
+	struct dentry *debug_root;
+
+	struct kthread_work switch_work;
+	struct kthread_worker worker;
+	struct task_struct *thread;
+
+	const struct dsi_display_mode *display_mode;
+	const struct dsi_display_mode *idle_mode;
+	wait_queue_head_t switch_wq;
+	bool switch_pending;
+	int switch_te_listen_count;
+
+	atomic_t te_counter;
+	struct dsi_display_te_listener te_listener;
+	struct completion te_completion;
+
+	const struct panel_switch_funcs *funcs;
+};
+
+static inline
+struct dsi_display *dsi_panel_to_display(const struct dsi_panel *panel)
+{
+	return dev_get_drvdata(panel->parent);
+}
+
+static inline bool is_display_mode_same(const struct dsi_display_mode *m1,
+					const struct dsi_display_mode *m2)
+{
+	return m1 && m2 && m1->timing.refresh_rate == m2->timing.refresh_rate;
+}
+
+static inline void sde_atrace_mode_fps(const struct panel_switch_data *pdata,
+				       const struct dsi_display_mode *mode)
+{
+	sde_atrace('C', pdata->thread, "FPS", mode->timing.refresh_rate);
+}
+
+ssize_t panel_dsi_write_buf(struct dsi_panel *panel,
+			    const void *data, size_t len, bool send_last)
+{
+	const struct mipi_dsi_device *dsi = &panel->mipi_device;
+	const struct mipi_dsi_host_ops *ops = panel->host->ops;
+	struct mipi_dsi_msg msg = {
+		.channel = dsi->channel,
+		.tx_buf = data,
+		.tx_len = len,
+		.flags = 0,
+	};
+
+	switch (len) {
+	case 0:
+		return -EINVAL;
+
+	case 1:
+		msg.type = MIPI_DSI_DCS_SHORT_WRITE;
+		break;
+
+	case 2:
+		msg.type = MIPI_DSI_DCS_SHORT_WRITE_PARAM;
+		break;
+
+	default:
+		msg.type = MIPI_DSI_DCS_LONG_WRITE;
+		break;
+	}
+
+	if (send_last)
+		msg.flags |= MIPI_DSI_MSG_LASTCOMMAND;
+
+	return ops->transfer(panel->host, &msg);
+}
+
+static void panel_handle_te(struct dsi_display_te_listener *tl)
+{
+	struct panel_switch_data *pdata;
+
+	if (unlikely(!tl))
+		return;
+
+	pdata = container_of(tl, struct panel_switch_data, te_listener);
+
+	complete(&pdata->te_completion);
+
+	if (likely(pdata->thread)) {
+		/* 1-bit counter that shows up in panel thread timeline */
+		sde_atrace('C', pdata->thread, "TE_VSYNC",
+			   atomic_inc_return(&pdata->te_counter) & 1);
+	}
+}
+
+enum s6e3hc2_wrctrld_flags {
+	S6E3HC2_WRCTRLD_DIMMING_BIT	= BIT(3),
+	S6E3HC2_WRCTRLD_FRAME_RATE_BIT	= BIT(4),
+	S6E3HC2_WRCTRLD_BCTRL_BIT	= BIT(5),
+	S6E3HC2_WRCTRLD_HBM_BIT		= BIT(7) | BIT(6),
+};
+
+struct s6e3hc2_wrctrl_data {
+	bool hbm_enable;
+	bool dimming_active;
+	u32 refresh_rate;
+};
+
+static int s6e3hc2_write_ctrld_reg(struct dsi_panel *panel,
+				   const struct s6e3hc2_wrctrl_data *data,
+				   bool send_last)
+{
+	u8 wrctrl_reg = S6E3HC2_WRCTRLD_BCTRL_BIT;
+	u8 payload[2] = { MIPI_DCS_WRITE_CONTROL_DISPLAY, 0 };
+
+	if (data->hbm_enable)
+		wrctrl_reg |= S6E3HC2_WRCTRLD_HBM_BIT;
+
+	if (data->dimming_active)
+		wrctrl_reg |= S6E3HC2_WRCTRLD_DIMMING_BIT;
+
+	if (data->refresh_rate == 90)
+		wrctrl_reg |= S6E3HC2_WRCTRLD_FRAME_RATE_BIT;
+
+	pr_debug("hbm_enable: %d dimming_active: %d refresh_rate: %d hz\n",
+		data->hbm_enable, data->dimming_active, data->refresh_rate);
+
+	payload[1] = wrctrl_reg;
+
+	return panel_dsi_write_buf(panel, &payload, sizeof(payload), send_last);
+}
+
+static int s6e3hc2_switch_mode_update(struct dsi_panel *panel,
+				      const struct dsi_display_mode *mode,
+				      bool send_last)
+{
+	const struct hbm_data *hbm = panel->bl_config.hbm;
+	struct s6e3hc2_wrctrl_data data = {0};
+
+	if (unlikely(!mode || !hbm))
+		return -EINVAL;
+
+	/* display is expected not to operate in HBM mode for the first bl range
+	 * (cur_range = 0) when panel->hbm_mode is not off
+	 */
+	data.hbm_enable = (panel->hbm_mode != HBM_MODE_OFF) &&
+			hbm->cur_range != 0;
+	data.dimming_active = panel->bl_config.dimming_mode ||
+				panel->bl_config.hbm->dimming_active;
+	data.refresh_rate = mode->timing.refresh_rate;
+
+	return s6e3hc2_write_ctrld_reg(panel, &data, send_last);
+}
+
+static void panel_switch_cmd_set_transfer(struct panel_switch_data *pdata,
+					  const struct dsi_display_mode *mode)
+{
+	struct dsi_panel *panel = pdata->panel;
+	struct dsi_panel_cmd_set *cmd;
+	int rc;
+
+	cmd = &mode->priv_info->cmd_sets[DSI_CMD_SET_TIMING_SWITCH];
+
+	rc = dsi_panel_cmd_set_transfer(panel, cmd);
+	if (rc)
+		pr_warn("failed to send TIMING switch cmd, rc=%d\n", rc);
+}
+
+static void panel_switch_to_mode(struct panel_switch_data *pdata,
+				 const struct dsi_display_mode *mode)
+{
+	struct dsi_panel *panel = pdata->panel;
+
+	SDE_ATRACE_BEGIN(__func__);
+	if (pdata->funcs && pdata->funcs->perform_switch)
+		pdata->funcs->perform_switch(pdata, mode);
+
+	if (pdata->switch_pending) {
+		pdata->switch_pending = false;
+		wake_up_all(&pdata->switch_wq);
+	}
+
+	if (panel->bl_config.bl_device)
+		sysfs_notify(&panel->bl_config.bl_device->dev.kobj, NULL,
+					"state");
+
+	SDE_ATRACE_END(__func__);
+}
+
+static void panel_switch_worker(struct kthread_work *work)
+{
+	struct panel_switch_data *pdata;
+	struct dsi_panel *panel;
+	struct dsi_display *display;
+	const struct dsi_display_mode *mode;
+	const unsigned long timeout = msecs_to_jiffies(TE_TIMEOUT_MS);
+	int rc;
+	u32 te_listen_cnt;
+
+	if (unlikely(!work))
+		return;
+
+	pdata = container_of(work, struct panel_switch_data, switch_work);
+	panel = pdata->panel;
+	if (unlikely(!panel))
+		return;
+
+	display = dsi_panel_to_display(panel);
+	if (unlikely(!display))
+		return;
+
+	mutex_lock(&panel->panel_lock);
+	mode = pdata->display_mode;
+	if (unlikely(!mode)) {
+		mutex_unlock(&panel->panel_lock);
+		return;
+	}
+
+	SDE_ATRACE_BEGIN(__func__);
+
+	pr_debug("switching mode to %dhz\n", mode->timing.refresh_rate);
+
+	te_listen_cnt = pdata->switch_te_listen_count;
+	if (te_listen_cnt) {
+		reinit_completion(&pdata->te_completion);
+		dsi_display_add_te_listener(display, &pdata->te_listener);
+	}
+
+	/* switch is shadowed by vsync so this can be done ahead of TE */
+	panel_switch_to_mode(pdata, mode);
+	mutex_unlock(&panel->panel_lock);
+
+	if (te_listen_cnt) {
+		rc = wait_for_completion_timeout(&pdata->te_completion,
+						 timeout);
+		if (!rc)
+			pr_warn("Timed out waiting for TE while switching!\n");
+		else
+			pr_debug("TE received after %dus\n",
+				jiffies_to_usecs(timeout - rc));
+	}
+
+	sde_atrace_mode_fps(pdata, mode);
+	SDE_ATRACE_END(__func__);
+
+	/*
+	 * this is meant only for debugging purposes, keep TE enabled for a few
+	 * extra frames to see how they align after switch
+	 */
+	if (te_listen_cnt) {
+		te_listen_cnt--;
+		pr_debug("waiting for %d extra te\n", te_listen_cnt);
+		while (rc && te_listen_cnt) {
+			rc = wait_for_completion_timeout(&pdata->te_completion,
+							timeout);
+			te_listen_cnt--;
+		}
+		dsi_display_remove_te_listener(display, &pdata->te_listener);
+	}
+}
+
+static bool dsi_mode_matches_cmdline(const struct dsi_display_mode *dm,
+				     const struct drm_cmdline_mode *cm)
+{
+
+	if (!cm->refresh_specified && !cm->specified)
+		return false;
+	if (cm->refresh_specified && cm->refresh != dm->timing.refresh_rate)
+		return false;
+	if (cm->specified && (cm->xres != dm->timing.h_active ||
+			      cm->yres != dm->timing.v_active))
+		return false;
+	return true;
+}
+
+static const struct dsi_display_mode *
+display_mode_from_cmdline(const struct dsi_panel *panel,
+			  const char *modestr)
+{
+	const struct dsi_display *display;
+	const struct dsi_display_mode *mode;
+	struct drm_cmdline_mode cm = {0};
+	int i;
+
+	display = dsi_panel_to_display(panel);
+	if (!display)
+		return ERR_PTR(-ENODEV);
+
+	if (!drm_mode_parse_command_line_for_connector(modestr,
+						       display->drm_conn,
+						       &cm))
+		return ERR_PTR(-EINVAL);
+
+	for_each_display_mode(i, mode, panel) {
+		if (dsi_mode_matches_cmdline(mode, &cm))
+			return mode;
+	}
+
+	return NULL;
+}
+
+static const struct dsi_display_mode *
+display_mode_from_user(const struct dsi_panel *panel,
+		       const char __user *user_buf, size_t user_len)
+{
+	char modestr[40];
+	size_t len = min(user_len, sizeof(modestr) - 1);
+	int rc;
+
+	rc = copy_from_user(modestr, user_buf, len);
+	if (rc)
+		return ERR_PTR(-EFAULT);
+
+	modestr[len] = '\0';
+
+	return display_mode_from_cmdline(panel, strim(modestr));
+}
+
+static void panel_queue_switch(struct panel_switch_data *pdata,
+			       const struct dsi_display_mode *new_mode)
+{
+	if (unlikely(!pdata || !pdata->panel || !new_mode))
+		return;
+
+	kthread_flush_work(&pdata->switch_work);
+
+	mutex_lock(&pdata->panel->panel_lock);
+	pdata->display_mode = new_mode;
+	pdata->switch_pending = true;
+	mutex_unlock(&pdata->panel->panel_lock);
+
+	kthread_queue_work(&pdata->worker, &pdata->switch_work);
+}
+
+static int panel_switch(struct dsi_panel *panel)
+{
+	struct panel_switch_data *pdata = panel->private_data;
+
+	if (!panel->cur_mode)
+		return -EINVAL;
+
+	SDE_ATRACE_BEGIN(__func__);
+	panel_queue_switch(pdata, panel->cur_mode);
+	SDE_ATRACE_END(__func__);
+
+	return 0;
+}
+
+static int panel_pre_kickoff(struct dsi_panel *panel)
+{
+	struct panel_switch_data *pdata = panel->private_data;
+	const unsigned long timeout = msecs_to_jiffies(TE_TIMEOUT_MS);
+
+	if (!wait_event_timeout(pdata->switch_wq,
+				!pdata->switch_pending, timeout))
+		pr_warn("Timed out waiting for panel switch\n");
+
+	return 0;
+}
+
+static int panel_flush_switch_queue(struct dsi_panel *panel)
+{
+	struct panel_switch_data *pdata = panel->private_data;
+
+	kthread_flush_worker(&pdata->worker);
+
+	return 0;
+}
+
+static int panel_post_enable(struct dsi_panel *panel)
+{
+	struct panel_switch_data *pdata = panel->private_data;
+	int rc = 0;
+
+	if (!pdata)
+		return -EINVAL;
+
+	if (pdata->funcs && pdata->funcs->post_enable)
+		rc = pdata->funcs->post_enable(pdata);
+
+	return rc;
+}
+
+const struct dsi_display_mode *get_panel_display_mode(struct dsi_panel *panel)
+{
+	struct panel_switch_data *pdata = panel->private_data;
+
+	return !pdata ? panel->cur_mode : pdata->display_mode;
+}
+
+static int panel_idle(struct dsi_panel *panel)
+{
+	struct panel_switch_data *pdata = panel->private_data;
+	const struct dsi_display_mode *idle_mode;
+
+	if (unlikely(!pdata))
+		return -EINVAL;
+
+	kthread_flush_work(&pdata->switch_work);
+
+	mutex_lock(&panel->panel_lock);
+	idle_mode = pdata->idle_mode;
+	if (idle_mode && !is_display_mode_same(idle_mode, panel->cur_mode)) {
+		/*
+		 * clocks are going to be turned off right after this call, so
+		 * switch needs to happen synchronously
+		 */
+		pdata->display_mode = idle_mode;
+		panel_switch_to_mode(pdata, idle_mode);
+
+		sde_atrace_mode_fps(pdata, idle_mode);
+	}
+	mutex_unlock(&panel->panel_lock);
+
+	SDE_ATRACE_INT("display_idle", 1);
+
+	return 0;
+}
+
+static int panel_wakeup(struct dsi_panel *panel)
+{
+	struct panel_switch_data *pdata = panel->private_data;
+	const struct dsi_display_mode *mode = NULL;
+
+	if (unlikely(!pdata))
+		return -EINVAL;
+
+	mutex_lock(&panel->panel_lock);
+	if (!is_display_mode_same(pdata->display_mode, panel->cur_mode))
+		mode = panel->cur_mode;
+	mutex_unlock(&panel->panel_lock);
+
+	if (mode)
+		panel_queue_switch(pdata, mode);
+
+	SDE_ATRACE_INT("display_idle", 0);
+
+	return 0;
+}
+
+static int panel_update_hbm(struct dsi_panel *panel)
+{
+	struct panel_switch_data *pdata = panel->private_data;
+
+	if (unlikely(!pdata || !pdata->funcs))
+		return -EINVAL;
+
+	if (!pdata->funcs->support_update_hbm)
+		return -EOPNOTSUPP;
+
+	return pdata->funcs->support_update_hbm(panel);
+}
+
+static int s6e3hc2_te2_set_edge_info(struct dsi_panel *panel)
+{
+	struct dsi_panel_te2_config *te2;
+	enum dsi_panel_te2_type current_type;
+	u8 payload[5] = { 0xB9, 0x00, 0x77, 0xBA, 0xBC };
+	u8 rising_low_byte, rising_high_byte;
+	u8 falling_low_byte, falling_high_byte;
+
+	if (unlikely(!panel))
+		return -EINVAL;
+
+	te2 = &panel->te2_config;
+	current_type = te2->current_type;
+
+	rising_low_byte = te2->te2_edge[current_type].rising & 0xff;
+	rising_high_byte = (te2->te2_edge[current_type].rising >> 8) & 0xff;
+	falling_low_byte = te2->te2_edge[current_type].falling & 0xff;
+	falling_high_byte = (te2->te2_edge[current_type].falling >> 8) & 0xff;
+
+	payload[2] = (rising_high_byte << 4) | falling_high_byte;
+	payload[3] = rising_low_byte;
+	payload[4] = falling_low_byte;
+
+	return panel_dsi_write_buf(panel,
+				&payload, sizeof(payload), true);
+}
+
+static int s6e3hc2_te2_update(struct dsi_panel *panel)
+{
+	struct mipi_dsi_device *dsi = &panel->mipi_device;
+	const u8 global_para[] = { 0xB0, 0x2C, 0xF2 };
+	const u8 tout_enable[] = { 0xF2, 0x01 };
+
+	if (s6e3hc2_te2_set_edge_info(panel))
+		goto error;
+
+	if (DSI_WRITE_CMD_BUF(dsi, global_para))
+		goto error;
+
+	if (DSI_WRITE_CMD_BUF(dsi, tout_enable))
+		goto error;
+
+	return 0;
+
+error:
+	pr_err("TE2: Failed to te2 setting update\n");
+	return -EFAULT;
+}
+
+/*
+ * s6e3hc2_te2_update_reg_locked() write the TE2 register data
+ * into panel. Control TE2 signal timing.
+ */
+static int s6e3hc2_te2_update_reg_locked(struct dsi_panel *panel)
+{
+	struct mipi_dsi_device *dsi;
+	int rc;
+
+	if (unlikely(!panel || !panel->private_data))
+		return -EINVAL;
+
+	dsi = &panel->mipi_device;
+
+	if (DSI_WRITE_CMD_BUF(dsi, unlock_cmd))
+		return -EFAULT;
+
+	rc = s6e3hc2_te2_update(panel);
+	if (rc < 0)
+		pr_warn("TE2: setting update fail\n");
+
+	if (DSI_WRITE_CMD_BUF(dsi, lock_cmd))
+		return -EFAULT;
+
+	return rc;
+}
+
+static int panel_update_te2(struct dsi_panel *panel)
+{
+	struct panel_switch_data *pdata = panel->private_data;
+
+	if (unlikely(!panel || !pdata || !pdata->funcs))
+		return -EINVAL;
+
+	if (!pdata->funcs->support_update_te2)
+		return -EOPNOTSUPP;
+
+	return pdata->funcs->support_update_te2(panel);
+}
+
+static int s6e3hc2_te2_normal_mode_update(struct dsi_panel *panel,
+					bool reg_locked)
+{
+	const struct dsi_display_mode *mode;
+
+	if (unlikely(!panel || !panel->te2_config.te2_ready))
+		return -EINVAL;
+
+	mode = panel->cur_mode;
+	panel->te2_config.current_type =
+		((mode->timing.refresh_rate == S6E3HC2_DEFAULT_FPS)
+			? TE2_EDGE_60HZ : TE2_EDGE_90HZ);
+
+	if (reg_locked == true)
+		s6e3hc2_te2_update_reg_locked(panel);
+	else
+		s6e3hc2_te2_update(panel);
+
+	return 0;
+}
+
+static int panel_send_nolp(struct dsi_panel *panel)
+{
+	struct panel_switch_data *pdata = panel->private_data;
+
+	if (unlikely(!pdata || !pdata->funcs))
+		return -EINVAL;
+
+	if (!pdata->funcs->send_nolp_cmds)
+		return -EOPNOTSUPP;
+
+	return pdata->funcs->send_nolp_cmds(panel);
+}
+
+static ssize_t debugfs_panel_switch_mode_write(struct file *file,
+					       const char __user *user_buf,
+					       size_t user_len,
+					       loff_t *ppos)
+{
+	struct seq_file *seq = file->private_data;
+	struct panel_switch_data *pdata = seq->private;
+	const struct dsi_display_mode *mode;
+
+	if (!pdata->panel || !dsi_panel_initialized(pdata->panel))
+		return -ENOENT;
+
+	mode = display_mode_from_user(pdata->panel, user_buf, user_len);
+	if (IS_ERR(mode))
+		return PTR_ERR(mode);
+	else if (!mode)
+		return -ENOENT;
+
+	panel_queue_switch(pdata, mode);
+
+	return user_len;
+}
+
+static int debugfs_panel_switch_mode_read(struct seq_file *seq, void *data)
+{
+	struct panel_switch_data *pdata = seq->private;
+	const struct dsi_display_mode *mode;
+
+	if (unlikely(!pdata->panel))
+		return -ENOENT;
+
+	mutex_lock(&pdata->panel->panel_lock);
+	mode = pdata->display_mode;
+	mutex_unlock(&pdata->panel->panel_lock);
+
+	if (mode)
+		seq_printf(seq, "%dx%d@%d\n", mode->timing.h_active,
+			   mode->timing.v_active, mode->timing.refresh_rate);
+	else
+		seq_puts(seq, "unknown");
+
+	return 0;
+}
+
+static int debugfs_panel_switch_mode_open(struct inode *inode, struct file *f)
+{
+	return single_open(f, debugfs_panel_switch_mode_read, inode->i_private);
+}
+
+static const struct file_operations panel_switch_fops = {
+	.owner = THIS_MODULE,
+	.open = debugfs_panel_switch_mode_open,
+	.write = debugfs_panel_switch_mode_write,
+	.read = seq_read,
+	.release = single_release,
+};
+
+static ssize_t te2_table_store(struct device *dev,
+			       struct device_attribute *attr,
+			       const char *buf, size_t count)
+{
+	const struct dsi_display *display;
+	struct dsi_panel *panel;
+	struct dsi_backlight_config *bl;
+	struct backlight_device *bd;
+	u32 table[TE2_EDGE_MAX * 2] = {0};
+	char *buf_dup;
+	ssize_t rc, buf_dup_len;
+	u8 i, table_len;
+
+	display = dev_get_drvdata(dev);
+	if (unlikely(!display || !display->panel || !count))
+		return -EINVAL;
+
+	panel = display->panel;
+	bl = &panel->bl_config;
+	bd = bl->bl_device;
+	if (unlikely(!bd))
+		return -EINVAL;
+
+	if (!panel->te2_config.te2_ready)
+		return -EOPNOTSUPP;
+
+	buf_dup = kstrndup(buf, count, GFP_KERNEL);
+	if (!buf_dup)
+		return -ENOMEM;
+
+	buf_dup_len = strlen(buf_dup) + 1;
+
+	rc = parse_u32_buf(buf_dup, buf_dup_len, table, TE2_EDGE_MAX * 2);
+	if (rc < 0) {
+		kfree(buf_dup);
+		pr_warn("incorrect parameters from te2 table node, rc=%d\n",
+			rc);
+		return rc;
+	}
+
+	table_len = rc / 2;
+	if (table_len != ARRAY_SIZE(panel->te2_config.te2_edge)) {
+		kfree(buf_dup);
+		pr_warn("incorrect array size of te2 table.\n");
+		return -EINVAL;
+	}
+
+	mutex_lock(&panel->panel_lock);
+	for (i = 0; i < table_len; i++) {
+		panel->te2_config.te2_edge[i].rising = table[i * 2];
+		panel->te2_config.te2_edge[i].falling = table[i * 2 + 1];
+	}
+
+	if (!is_standby_mode(bd->props.state)) {
+		pr_debug("te2_config.current_type =%d\n",
+			 panel->te2_config.current_type);
+		s6e3hc2_te2_update_reg_locked(panel);
+	}
+
+	mutex_unlock(&panel->panel_lock);
+
+	kfree(buf_dup);
+
+	return count;
+}
+
+static ssize_t te2_table_show(struct device *dev,
+			      struct device_attribute *attr, char *buf)
+{
+	const struct dsi_display *display;
+	struct dsi_panel *panel;
+	int i;
+	ssize_t len = 0;
+
+	display = dev_get_drvdata(dev);
+	if (unlikely(!display || !display->panel))
+		return -EINVAL;
+
+	panel = display->panel;
+	if (!panel->te2_config.te2_ready)
+		return -EOPNOTSUPP;
+
+	mutex_lock(&panel->panel_lock);
+
+	for (i = 0; i < TE2_EDGE_MAX; i++) {
+		len += scnprintf((buf + len), PAGE_SIZE - len, "%u %u ",
+				 panel->te2_config.te2_edge[i].rising,
+				 panel->te2_config.te2_edge[i].falling);
+	}
+
+	mutex_unlock(&panel->panel_lock);
+
+	len += scnprintf(buf + len, PAGE_SIZE - len, "\n");
+
+	return len;
+}
+
+static DEVICE_ATTR_RW(te2_table);
+
+static struct attribute *panel_te2_sysfs_attrs[] = {
+	&dev_attr_te2_table.attr,
+	NULL,
+};
+
+static struct attribute_group panel_te2_attrs_group = {
+	.attrs = panel_te2_sysfs_attrs,
+};
+
+
+static ssize_t sysfs_idle_mode_show(struct device *dev,
+				    struct device_attribute *attr, char *buf)
+{
+	const struct dsi_display *display;
+	struct panel_switch_data *pdata;
+	const struct dsi_display_mode *mode = NULL;
+	ssize_t rc;
+
+	display = dev_get_drvdata(dev);
+	if (unlikely(!display || !display->panel ||
+		     !display->panel->private_data))
+		return -EINVAL;
+
+	pdata = display->panel->private_data;
+
+	mutex_lock(&pdata->panel->panel_lock);
+	mode = pdata->idle_mode;
+	mutex_unlock(&pdata->panel->panel_lock);
+
+	if (mode)
+		rc = snprintf(buf, PAGE_SIZE, "%dx%d@%d\n",
+			      mode->timing.h_active, mode->timing.v_active,
+			      mode->timing.refresh_rate);
+	else
+		rc = snprintf(buf, PAGE_SIZE, "none\n");
+
+	return rc;
+}
+static ssize_t sysfs_idle_mode_store(struct device *dev,
+				     struct device_attribute *attr,
+				     const char *buf, size_t count)
+{
+	const struct dsi_display *display;
+	struct panel_switch_data *pdata;
+	const struct dsi_display_mode *mode = NULL;
+
+	display = dev_get_drvdata(dev);
+	if (unlikely(!display || !display->panel ||
+		     !display->panel->private_data))
+		return -EINVAL;
+
+	pdata = display->panel->private_data;
+	if (count > 1 && strncmp(buf, "none", 4)) {
+		char *modestr = kstrndup(buf, count, GFP_KERNEL);
+
+		/* remove any trailing lf at end of sysfs input */
+		mode = display_mode_from_cmdline(display->panel,
+						 strim(modestr));
+		kfree(modestr);
+
+		if (IS_ERR(mode))
+			return PTR_ERR(mode);
+	}
+	mutex_lock(&display->panel->panel_lock);
+	pdata->idle_mode = mode;
+	mutex_unlock(&display->panel->panel_lock);
+
+	return count;
+}
+
+static DEVICE_ATTR(idle_mode, 0644,
+		   sysfs_idle_mode_show,
+		   sysfs_idle_mode_store);
+
+static struct attribute *panel_switch_sysfs_attrs[] = {
+	&dev_attr_idle_mode.attr,
+	NULL,
+};
+
+static struct attribute_group panel_switch_sysfs_attrs_group = {
+	.attrs = panel_switch_sysfs_attrs,
+};
+
+static const struct dsi_panel_funcs panel_funcs = {
+	.mode_switch = panel_switch,
+	.pre_disable = panel_flush_switch_queue,
+	.pre_kickoff = panel_pre_kickoff,
+	.idle        = panel_idle,
+	.wakeup      = panel_wakeup,
+	.post_enable = panel_post_enable,
+	.pre_lp1     = panel_flush_switch_queue,
+	.update_te2  = panel_update_te2,
+	.update_hbm  = panel_update_hbm,
+	.send_nolp   = panel_send_nolp,
+};
+
+static int panel_switch_data_init(struct dsi_panel *panel,
+				  struct panel_switch_data *pdata)
+{
+	struct sched_param param = {
+		.sched_priority = 16,
+	};
+	const struct dsi_display *display;
+
+	display = dsi_panel_to_display(panel);
+	if (unlikely(!display))
+		return -ENOENT;
+
+	kthread_init_work(&pdata->switch_work, panel_switch_worker);
+	kthread_init_worker(&pdata->worker);
+	pdata->thread = kthread_run(kthread_worker_fn, &pdata->worker, "panel");
+	if (IS_ERR_OR_NULL(pdata->thread))
+		return -EFAULT;
+
+	pdata->panel = panel;
+	pdata->te_listener.handler = panel_handle_te;
+	pdata->display_mode = panel->cur_mode;
+
+	sched_setscheduler(pdata->thread, SCHED_FIFO, &param);
+	init_completion(&pdata->te_completion);
+	init_waitqueue_head(&pdata->switch_wq);
+	atomic_set(&pdata->te_counter, 0);
+
+	panel->private_data = pdata;
+	panel->funcs = &panel_funcs;
+
+	pdata->debug_root = debugfs_create_dir("switch", display->root);
+	debugfs_create_file("mode", 0600, pdata->debug_root, pdata,
+			    &panel_switch_fops);
+	debugfs_create_u32("te_listen_count", 0600, pdata->debug_root,
+			    &pdata->switch_te_listen_count);
+	debugfs_create_atomic_t("te_counter", 0600, pdata->debug_root,
+				&pdata->te_counter);
+
+	sysfs_create_group(&panel->parent->kobj,
+			   &panel_switch_sysfs_attrs_group);
+	sysfs_create_group(&panel->parent->kobj,
+			   &panel_te2_attrs_group);
+
+	return 0;
+}
+
+static void panel_switch_data_deinit(struct panel_switch_data *pdata)
+{
+	kthread_flush_worker(&pdata->worker);
+	kthread_stop(pdata->thread);
+	sysfs_remove_group(&pdata->panel->parent->kobj,
+			   &panel_switch_sysfs_attrs_group);
+	sysfs_remove_group(&pdata->panel->parent->kobj,
+			   &panel_te2_attrs_group);
+}
+
+static void panel_switch_data_destroy(struct panel_switch_data *pdata)
+{
+	panel_switch_data_deinit(pdata);
+	if (pdata->panel && pdata->panel->parent)
+		devm_kfree(pdata->panel->parent, pdata);
+}
+
+static struct panel_switch_data *panel_switch_create(struct dsi_panel *panel)
+{
+	struct panel_switch_data *pdata;
+	int rc;
+
+	pdata = devm_kzalloc(panel->parent, sizeof(*pdata), GFP_KERNEL);
+	if (!pdata)
+		return ERR_PTR(-ENOMEM);
+
+	rc = panel_switch_data_init(panel, pdata);
+	if (rc)
+		return ERR_PTR(rc);
+
+	return pdata;
+}
+
+const struct panel_switch_funcs panel_switch_default_funcs = {
+	.create = panel_switch_create,
+	.destroy = panel_switch_data_destroy,
+	.perform_switch = panel_switch_cmd_set_transfer,
+};
+
+struct gamma_calibration_info {
+	u8 *data;
+	size_t len;
+};
+
+struct gamma_fixup_location {
+	u32 src_idx;
+	u32 dst_idx;
+	u8 cmd;
+	u8 mask;
+};
+
+struct gamma_fixup_info {
+	struct gamma_fixup_location *fixup_loc;
+	u32 fixup_loc_count;
+	u32 panel_id_len;
+	u8 *panel_id;
+};
+
+struct gamma_switch_data {
+	struct panel_switch_data base;
+	struct kthread_work gamma_work;
+	struct gamma_fixup_info fixup_gamma;
+	struct gamma_calibration_info cali_gamma;
+	u8 num_of_cali_gamma;
+	bool native_gamma_ready;
+};
+
+#define S6E3HC2_GAMMA_BAND_LEN 45
+
+/**
+ * s6e3hc2_gamma_info - Information used to access gamma data on s6e3hc2.
+ * @cmd: Command to use when writing/reading gamma from the DDIC.
+ * @len: Total number of bytes to write/read from DDIC, including prefix_len.
+ * @prefix_len: Number of bytes that precede gamma data when writing/reading
+ *     from the DDIC. This is a subset of len.
+ * @flash_offset: Address offset to use when reading from flash.
+ */
+static const struct s6e3hc2_gamma_info {
+	u8 cmd;
+	u32 len;
+	u32 prefix_len;
+	u32 flash_offset;
+} s6e3hc2_gamma_tables[] = {
+	/* order of commands matter due to use of cmds grouping */
+	{ 0xC8, S6E3HC2_GAMMA_BAND_LEN * 3, 0, 0x0000 },
+	{ 0xC9, S6E3HC2_GAMMA_BAND_LEN * 4, 0, 0x0087 },
+	{ 0xB3, 2 + S6E3HC2_GAMMA_BAND_LEN, 2, 0x013B },
+};
+
+#define S6E3HC2_NUM_GAMMA_TABLES ARRAY_SIZE(s6e3hc2_gamma_tables)
+#define MAX_GAMMA_PACKETS S6E3HC2_NUM_GAMMA_TABLES
+
+struct s6e3hc2_gamma_packet_group {
+	size_t num_packets;
+	struct {
+		u32 index;
+		size_t max_size;
+	} packets[MAX_GAMMA_PACKETS];
+};
+
+struct s6e3hc2_gamma_packet {
+	u32 dbv_threshold;
+	size_t num_groups;
+	struct s6e3hc2_gamma_packet_group groups[MAX_GAMMA_PACKETS];
+};
+
+static const struct s6e3hc2_gamma_packet s6e3hc2_gamma_packets[] = {
+	{ .dbv_threshold = 0x39, .num_groups = 2, .groups = {
+		{ .num_packets = 1, .packets = {
+			{ 1 }, /* 0xC9 */
+		}}, { .num_packets = 2, .packets = {
+			{ 0 }, /* 0xC8 */
+			{ 2 }, /* 0xB3 */
+		}},
+	}}, { .dbv_threshold = 0xB6, .num_groups = 2, .groups = {
+		{ .num_packets = 2, .packets = {
+			{ 0 }, /* 0xC8 */
+			{ 1, S6E3HC2_GAMMA_BAND_LEN }, /* 0xC9: 1st gamma */
+		}}, { .num_packets = 2, .packets = {
+			{ 1 }, /* 0xC9: full gamma */
+			{ 2 }, /* 0xB3 */
+		}},
+	}}, { .dbv_threshold = UINT_MAX, .num_groups = 2, .groups = {
+		{ .num_packets = 2, .packets = {
+			{ 0 }, /* 0xC8 */
+			{ 2 }, /* 0xB3 */
+		}}, { .num_packets = 1, .packets = {
+			{ 1 }, /* 0xC9 */
+		}},
+	}},
+};
+
+struct s6e3hc2_panel_data {
+	u8 *gamma_data[S6E3HC2_NUM_GAMMA_TABLES];
+	u8 *native_gamma_data[S6E3HC2_NUM_GAMMA_TABLES];
+};
+
+static void s6e3hc2_gamma_group_write(struct panel_switch_data *pdata,
+				struct s6e3hc2_panel_data *priv_data,
+				const struct s6e3hc2_gamma_packet_group *group)
+{
+	const struct s6e3hc2_gamma_info *info;
+	const void *data;
+	size_t len;
+	int i;
+
+	for (i = 0; i < group->num_packets; i++) {
+		const u32 ndx = group->packets[i].index;
+		const u32 max_size = group->packets[i].max_size;
+		const bool last_packet = (i + 1) == group->num_packets;
+
+		if (WARN(ndx >= S6E3HC2_NUM_GAMMA_TABLES, "invalid ndx=%d\n"))
+			continue;
+
+		data = priv_data->gamma_data[ndx];
+
+		if (WARN(!data, "Gamma table #%d not read\n", i))
+			continue;
+
+		info = &s6e3hc2_gamma_tables[ndx];
+		/* extra byte for the dsi command */
+		len = (max_size ?: info->len) + 1;
+
+		pr_debug("Writing %d bytes to 0x%02X gamma last: %d\n",
+			 len, info->cmd, last_packet);
+
+
+		if (IS_ERR_VALUE(panel_dsi_write_buf(pdata->panel, data, len,
+					last_packet)))
+			pr_warn("failed sending gamma cmd 0x%02x\n", info->cmd);
+	}
+}
+
+/*
+ * s6e3hc2_gamma_update() expects DD-IC to be in unlocked state, so
+ * to make sure there are unlock/lock commands when calling this func.
+ */
+static void s6e3hc2_gamma_update(struct panel_switch_data *pdata,
+				 const struct dsi_display_mode *mode)
+{
+	struct s6e3hc2_panel_data *priv_data;
+	const struct s6e3hc2_gamma_packet *packet = NULL;
+	int i, dbv;
+
+	if (unlikely(!mode || !mode->priv_info))
+		return;
+
+	priv_data = mode->priv_info->switch_data;
+	if (unlikely(!priv_data))
+		return;
+
+	dbv = pdata->panel->bl_config.bl_actual;
+
+	for (i = 0; i < ARRAY_SIZE(s6e3hc2_gamma_packets); i++) {
+		if (dbv < s6e3hc2_gamma_packets[i].dbv_threshold) {
+			packet = &s6e3hc2_gamma_packets[i];
+			break;
+		}
+	}
+
+	if (!packet) {
+		pr_err("Unable to find packet for dbv=%02X\n", dbv);
+		return;
+	}
+
+	pr_debug("Found packet ndx=%d for dbv=%02X\n", i, dbv);
+
+	for (i = 0; i < packet->num_groups; i++)
+		s6e3hc2_gamma_group_write(pdata, priv_data, &packet->groups[i]);
+}
+
+static void s6e3hc2_gamma_update_reg_locked(struct panel_switch_data *pdata,
+				 const struct dsi_display_mode *mode)
+{
+	struct dsi_panel *panel = pdata->panel;
+	struct mipi_dsi_device *dsi = &panel->mipi_device;
+
+	if (unlikely(!mode))
+		return;
+
+	DSI_WRITE_CMD_BUF(dsi, unlock_cmd);
+	s6e3hc2_gamma_update(pdata, mode);
+	DSI_WRITE_CMD_BUF(dsi, lock_cmd);
+}
+
+static int s6e3hc2_gamma_read_otp(struct panel_switch_data *pdata,
+				  const struct s6e3hc2_panel_data *priv_data)
+{
+	struct mipi_dsi_device *dsi;
+	ssize_t rc;
+	int i;
+
+	SDE_ATRACE_BEGIN(__func__);
+
+	dsi = &pdata->panel->mipi_device;
+
+	for (i = 0; i < S6E3HC2_NUM_GAMMA_TABLES; i++) {
+		const struct s6e3hc2_gamma_info *info =
+			&s6e3hc2_gamma_tables[i];
+		u8 *buf = priv_data->gamma_data[i];
+
+		/* store cmd on first byte to send payload as is */
+		*buf = info->cmd;
+		buf++;
+
+		rc = mipi_dsi_dcs_read(dsi, info->cmd, buf, info->len);
+		if (rc != info->len)
+			pr_warn("Only got %zd / %d bytes\n", rc, info->len);
+	}
+
+	SDE_ATRACE_END(__func__);
+
+	return 0;
+}
+
+static int s6e3hc2_gamma_read_flash(struct panel_switch_data *pdata,
+				    const struct s6e3hc2_panel_data *priv_data)
+{
+	struct mipi_dsi_device *dsi;
+	const u8 flash_mode_en[]  = { 0xF1, 0xF1, 0xA2 };
+	const u8 flash_mode_dis[] = { 0xF1, 0xA5, 0xA5 };
+	const u8 pgm_dis[]        = { 0xC0, 0x00 };
+	const u8 pgm_en[]         = { 0xC0, 0x02 };
+	const u8 exe_inst[]	  = { 0xC0, 0x03 };
+	const u8 write_en[]       = { 0xC1,
+		0x00, 0x00, 0x00, 0x06,
+		0x00, 0x00, 0x00, 0x00,
+		0x00, 0x00, 0x00, 0x05 };
+	const u8 quad_en[]        = { 0xC1,
+		0x00, 0x00, 0x00, 0x01,
+		0x40, 0x02, 0x00, 0x00,
+		0x00, 0x00, 0x10 };
+	ssize_t rc;
+	int i, j;
+
+	SDE_ATRACE_BEGIN(__func__);
+
+	dsi = &pdata->panel->mipi_device;
+
+	if (DSI_WRITE_CMD_BUF(dsi, flash_mode_en) ||
+	    DSI_WRITE_CMD_BUF(dsi, pgm_en) ||
+	    DSI_WRITE_CMD_BUF(dsi, write_en) ||
+	    DSI_WRITE_CMD_BUF(dsi, exe_inst))
+		goto error;
+	usleep_range(950, 1000);
+
+	if (DSI_WRITE_CMD_BUF(dsi, quad_en) ||
+	    DSI_WRITE_CMD_BUF(dsi, exe_inst))
+		goto error;
+	msleep(30);
+
+	for (i = 0; i < S6E3HC2_NUM_GAMMA_TABLES; i++) {
+		const struct s6e3hc2_gamma_info *info;
+		const u8 gpar_cmd[] = { 0xB0, 0x0B };
+		u8 flash_rd[] = { 0xC1,
+			0x00, 0x00, 0x00, 0x6B, 0x00, 0x00, 0x00, /*Read Inst*/
+			0x0A, 0x00, 0x00,    /* Flash data Address : 0A0000h */
+			0x00, 0x05,          /* Bit rate setting */
+			0x01 };
+		u32 offset;
+		u8 *buf;
+
+		info = &s6e3hc2_gamma_tables[i];
+		offset = info->flash_offset;
+		buf = priv_data->gamma_data[i];
+		/* store cmd on first byte to send payload as is */
+		*buf = info->cmd;
+		buf++;
+
+		for (j = info->prefix_len; j < info->len; j++, offset++) {
+			u8 tmp[2];
+
+			flash_rd[9] = (offset >> 8) & 0xFF;
+			flash_rd[10] = offset & 0xFF;
+
+			if (DSI_WRITE_CMD_BUF(dsi, flash_rd) ||
+			    DSI_WRITE_CMD_BUF(dsi, exe_inst))
+				goto error;
+			usleep_range(200, 250);
+
+			if (DSI_WRITE_CMD_BUF(dsi, gpar_cmd))
+				goto error;
+
+			rc = mipi_dsi_dcs_read(dsi, 0xFB, tmp, sizeof(tmp));
+			if (rc != 2)
+				pr_warn("Only got %zd / 2 bytes\n", rc);
+
+			pr_debug("read flash offset %04x: %02X %02X\n",
+				 offset, tmp[0], tmp[1]);
+			buf[j] = tmp[1];
+		}
+	}
+
+	if (DSI_WRITE_CMD_BUF(dsi, pgm_dis) ||
+	    DSI_WRITE_CMD_BUF(dsi, flash_mode_dis))
+		goto error;
+
+	SDE_ATRACE_END(__func__);
+
+	return 0;
+
+error:
+	SDE_ATRACE_END(__func__);
+
+	pr_err("Failed to read gamma from flash\n");
+	return -EFAULT;
+}
+
+static int s6e3hc2_gamma_alloc_mode_memory(const struct dsi_display_mode *mode)
+{
+	struct s6e3hc2_panel_data *priv_data;
+	size_t offset, native_offset, total_size;
+	int i;
+	u8 *buf, *native_buf;
+
+	if (unlikely(!mode || !mode->priv_info))
+		return -EINVAL;
+
+	if (mode->priv_info->switch_data)
+		return 0;
+
+	total_size = sizeof(*priv_data);
+
+	for (i = 0; i < S6E3HC2_NUM_GAMMA_TABLES; i++)
+		total_size += s6e3hc2_gamma_tables[i].len;
+	/* add an extra byte for cmd */
+	total_size += S6E3HC2_NUM_GAMMA_TABLES;
+
+	/* hold native gamma */
+	native_offset = total_size;
+	total_size *= 2;
+
+	priv_data = kmalloc(total_size, GFP_KERNEL);
+	if (!priv_data)
+		return -ENOMEM;
+
+	/* use remaining data at the end of buffer */
+	buf = (u8 *)(priv_data);
+	offset = sizeof(*priv_data);
+	native_buf = buf + native_offset;
+
+	for (i = 0; i < S6E3HC2_NUM_GAMMA_TABLES; i++) {
+		const size_t len = s6e3hc2_gamma_tables[i].len;
+
+		priv_data->gamma_data[i] = buf + offset;
+		priv_data->native_gamma_data[i] = native_buf + offset;
+		/* reserve extra byte to hold cmd */
+		offset += len + 1;
+	}
+
+	mode->priv_info->switch_data = priv_data;
+
+	return 0;
+}
+
+static int s6e3hc2_gamma_read_mode(struct panel_switch_data *pdata,
+				   const struct dsi_display_mode *mode)
+{
+	const struct s6e3hc2_panel_data *priv_data;
+	int rc;
+
+	rc = s6e3hc2_gamma_alloc_mode_memory(mode);
+	if (rc)
+		return rc;
+
+	priv_data = mode->priv_info->switch_data;
+
+	switch (mode->timing.refresh_rate) {
+	case 60:
+		rc = s6e3hc2_gamma_read_otp(pdata, priv_data);
+		break;
+	case 90:
+		rc = s6e3hc2_gamma_read_flash(pdata, priv_data);
+		break;
+	default:
+		pr_warn("Unknown refresh rate!\n");
+		rc = -EINVAL;
+		break;
+	}
+
+	return rc;
+}
+
+static int find_gamma_data_for_refresh_rate(struct dsi_panel *panel,
+	u32 refresh_rate, u8 ***gamma_data)
+{
+	struct dsi_display_mode *mode;
+	int i;
+
+	if (!gamma_data)
+		return -EINVAL;
+
+	for_each_display_mode(i, mode, panel)
+		if (mode->timing.refresh_rate == refresh_rate) {
+			struct s6e3hc2_panel_data *priv_data =
+				mode->priv_info->switch_data;
+
+			if (unlikely(!priv_data))
+				return -ENODATA;
+
+			*gamma_data = priv_data->gamma_data;
+			return 0;
+		}
+
+	return -ENODATA;
+}
+
+/*
+ * For some modes, gamma curves are located in registers addresses that require
+ * an offset to read/write. Because we cannot access a register offset directly,
+ * we must read the portion of the data that precedes the gamma curve data
+ * itself ("prefix") as well. In such cases, we read the prefix + gamma curve
+ * data from DDIC registers, and only gamma curve data from flash.
+ *
+ * This function looks for such gamma curves, and adjusts gamma data read from
+ * flash to include the prefix read from registers. The result is that, for all
+ * modes, wherever the gamma curves were read from (registers or flash), when
+ * that gamma data is written back to registers the write includes the original
+ * prefix.
+ * In other words, when we write gamma data to registers, we do not modify
+ * prefix data; we only modify gamma data.
+ */
+static int s6e3hc2_gamma_set_prefixes(struct panel_switch_data *pdata)
+{
+	int i;
+	int rc = 0;
+	u8 **gamma_data_otp;
+	u8 **gamma_data_flash;
+
+	/*
+	 * For s6e3hc2, 60Hz gamma curves are read from OTP and 90Hz
+	 * gamma curves are read from flash.
+	 */
+	rc = find_gamma_data_for_refresh_rate(pdata->panel, 60,
+		&gamma_data_otp);
+	if (rc) {
+		pr_err("Error setting gamma prefix: no matching OTP mode, err %d\n",
+			rc);
+		return rc;
+	}
+
+	rc = find_gamma_data_for_refresh_rate(pdata->panel, 90,
+		&gamma_data_flash);
+	if (rc) {
+		pr_err("Error setting gamma prefix: no matching flash mode, err %d\n",
+			rc);
+		return rc;
+	}
+
+	for (i = 0; i < S6E3HC2_NUM_GAMMA_TABLES; i++) {
+		const struct s6e3hc2_gamma_info *gamma_info =
+			&s6e3hc2_gamma_tables[i];
+		u8 *gamma_curve_otp = gamma_data_otp[i];
+		u8 *gamma_curve_flash = gamma_data_flash[i];
+
+		if (!gamma_info->prefix_len)
+			continue;
+
+		/* skip command byte */
+		gamma_curve_otp++;
+		gamma_curve_flash++;
+
+		memcpy(gamma_curve_flash, gamma_curve_otp,
+			gamma_info->prefix_len);
+	}
+
+	return rc;
+}
+
+static int s6e3hc2_copy_gamma_table(u8 **dest_gamma, u8 **src_gamma)
+{
+	int i;
+
+	for (i = 0; i < S6E3HC2_NUM_GAMMA_TABLES; i++) {
+		const size_t len = s6e3hc2_gamma_tables[i].len;
+
+		memcpy(dest_gamma[i], src_gamma[i], len * sizeof(**src_gamma));
+	}
+
+	return 0;
+}
+
+static int s6e3hc2_gamma_read_tables(struct panel_switch_data *pdata)
+{
+	struct gamma_switch_data *sdata;
+	const struct dsi_display_mode *mode;
+	struct mipi_dsi_device *dsi;
+	int i, rc = 0;
+
+	if (unlikely(!pdata || !pdata->panel))
+		return -ENOENT;
+
+	sdata = container_of(pdata, struct gamma_switch_data, base);
+	if (sdata->native_gamma_ready)
+		return 0;
+
+	dsi = &pdata->panel->mipi_device;
+	if (DSI_WRITE_CMD_BUF(dsi, unlock_cmd))
+		return -EFAULT;
+
+	for_each_display_mode(i, mode, pdata->panel) {
+		rc = s6e3hc2_gamma_read_mode(pdata, mode);
+		if (rc) {
+			pr_err("Unable to read gamma for mode #%d\n", i);
+			goto abort;
+		}
+	}
+
+	rc = s6e3hc2_gamma_set_prefixes(pdata);
+	if (rc) {
+		pr_err("Unable to set gamma prefix\n");
+		goto abort;
+	}
+
+	for_each_display_mode(i, mode, pdata->panel) {
+		struct s6e3hc2_panel_data *priv_data =
+			mode->priv_info->switch_data;
+		u8 **gamma_data = priv_data->gamma_data;
+		u8 **native_gamma_data = priv_data->native_gamma_data;
+
+		s6e3hc2_copy_gamma_table(native_gamma_data, gamma_data);
+	}
+
+	sdata->native_gamma_ready = true;
+
+abort:
+	if (DSI_WRITE_CMD_BUF(dsi, lock_cmd))
+		return -EFAULT;
+
+	return rc;
+}
+
+static void s6e3hc2_gamma_print(struct seq_file *seq,
+				const struct dsi_display_mode *mode)
+{
+	const struct s6e3hc2_panel_data *priv_data;
+	int i, j;
+
+	if (!mode || !mode->priv_info)
+		return;
+
+	seq_printf(seq, "\n=== %dhz Mode Gamma ===\n",
+		   mode->timing.refresh_rate);
+
+	priv_data = mode->priv_info->switch_data;
+
+	if (!priv_data) {
+		seq_puts(seq, "No data available!\n");
+		return;
+	}
+
+	for (i = 0; i < S6E3HC2_NUM_GAMMA_TABLES; i++) {
+		const size_t len = s6e3hc2_gamma_tables[i].len;
+		const u8 cmd = s6e3hc2_gamma_tables[i].cmd;
+		const u8 *buf = priv_data->gamma_data[i] + 1;
+
+		seq_printf(seq, "0x%02X:", cmd);
+		for (j = 0; j < len; j++) {
+			if (j && (j % 8) == 0)
+				seq_puts(seq, "\n     ");
+			seq_printf(seq, " %02X", buf[j]);
+		}
+		seq_puts(seq, "\n");
+	}
+}
+
+static int debugfs_s6e3hc2_gamma_read(struct seq_file *seq, void *data)
+{
+	struct panel_switch_data *pdata = seq->private;
+	int i, rc;
+
+	if (unlikely(!pdata || !pdata->panel))
+		return -EINVAL;
+
+	if (!dsi_panel_initialized(pdata->panel))
+		return -EPIPE;
+
+	mutex_lock(&pdata->panel->panel_lock);
+	rc = s6e3hc2_gamma_read_tables(pdata);
+	if (!rc) {
+		const struct dsi_display_mode *mode;
+
+		for_each_display_mode(i, mode, pdata->panel)
+			s6e3hc2_gamma_print(seq, mode);
+	}
+	mutex_unlock(&pdata->panel->panel_lock);
+
+	return rc;
+}
+
+ssize_t debugfs_s6e3hc2_gamma_write(struct file *file,
+				    const char __user *user_buf,
+				    size_t count, loff_t *ppos)
+{
+	struct seq_file *seq = file->private_data;
+	struct panel_switch_data *pdata = seq->private;
+	struct gamma_switch_data *sdata;
+
+	if (!pdata || !pdata->panel)
+		return -EINVAL;
+
+	sdata = container_of(pdata, struct gamma_switch_data, base);
+
+	mutex_lock(&pdata->panel->panel_lock);
+	sdata->native_gamma_ready = false;
+	mutex_unlock(&pdata->panel->panel_lock);
+
+	return count;
+}
+
+static int debugfs_s6e3hc2_gamma_open(struct inode *inode, struct file *f)
+{
+	return single_open(f, debugfs_s6e3hc2_gamma_read, inode->i_private);
+}
+
+static const struct file_operations s6e3hc2_read_gamma_fops = {
+	.owner   = THIS_MODULE,
+	.open    = debugfs_s6e3hc2_gamma_open,
+	.write   = debugfs_s6e3hc2_gamma_write,
+	.read    = seq_read,
+	.release = single_release,
+};
+
+static int s6e3hc2_check_gamma_infos(const struct s6e3hc2_gamma_info *infos,
+		size_t num_infos)
+{
+	int i;
+
+	if (!infos) {
+		pr_err("Null gamma infos\n");
+		return -EINVAL;
+	}
+
+	for (i = 0; i < num_infos; i++) {
+		const struct s6e3hc2_gamma_info *info = &infos[i];
+
+		if (unlikely(info->prefix_len >= info->len)) {
+			pr_err("Gamma prefix length (%u) >= total length length (%u)\n",
+				info->prefix_len, info->len);
+			return -EINVAL;
+		}
+	}
+
+	return 0;
+}
+
+static int s6e3hc2_restore_gamma_data(struct gamma_switch_data *sdata)
+{
+	struct panel_switch_data *pdata;
+	struct dsi_display_mode *mode;
+	int i;
+
+	if (unlikely(!sdata))
+		return -EINVAL;
+
+	pdata = &sdata->base;
+	if (unlikely(!pdata->panel))
+		return -EINVAL;
+
+	for_each_display_mode(i, mode, pdata->panel) {
+		struct s6e3hc2_panel_data *priv_data =
+			mode->priv_info->switch_data;
+		u8 **gamma_data = priv_data->gamma_data;
+		u8 **native_gamma_data = priv_data->native_gamma_data;
+
+		s6e3hc2_copy_gamma_table(gamma_data, native_gamma_data);
+	}
+
+	sdata->num_of_cali_gamma = 0;
+
+	return 0;
+}
+
+static int parse_payload_len(const u8 *payload, size_t len, size_t *out_size)
+{
+	size_t payload_len;
+
+	if (!out_size || len <= CALI_GAMMA_HEADER_SIZE)
+		return -EINVAL;
+
+	*out_size = payload_len = (payload[1] << 8) + payload[2];
+
+	return payload_len &&
+		(payload_len + CALI_GAMMA_HEADER_SIZE <= len) ? 0 : -EINVAL;
+}
+
+/* ID (1 byte) | payload size (2 bytes) | payload */
+static int s6e3hc2_check_gamma_payload(const u8 *src, size_t len)
+{
+	size_t total_len = 0;
+	int num_payload = 0;
+
+	if (!src)
+		return -EINVAL;
+
+	while (len > total_len) {
+		const u8 *payload = src + total_len;
+		size_t payload_len;
+		int rc;
+
+		rc = parse_payload_len(payload, len - total_len, &payload_len);
+		if (rc)
+			return -EINVAL;
+
+		total_len += CALI_GAMMA_HEADER_SIZE + payload_len;
+		num_payload++;
+	}
+
+	return num_payload;
+}
+
+static void s6e3hc2_gamma_fixup_after_overwrite(
+			const struct gamma_fixup_location *fixup_loc,
+			u8 *gamma_buf, const size_t len)
+{
+	const u32 src_idx = fixup_loc->src_idx;
+	const u32 dst_idx = fixup_loc->dst_idx;
+	const u8 mask = fixup_loc->mask;
+
+	if (len <= src_idx || len <= dst_idx) {
+		pr_warn("gamma length %zu of cmd 0x%x not enough\n", len,
+							fixup_loc->cmd);
+		return;
+	}
+
+	if (mask) {
+		gamma_buf[dst_idx] &= ~mask;
+		gamma_buf[dst_idx] |= (gamma_buf[src_idx] & mask);
+	}
+}
+
+static int s6e3hc2_overwrite_gamma_bands(
+			const struct dsi_panel_vendor_info *panel_info,
+			const struct gamma_fixup_info *fixup_gamma,
+			u8 **gamma_data, const u8 *src, size_t len)
+{
+	int i, num_of_reg;
+	size_t payload_len, read_len = 0;
+
+	if (!gamma_data || !src)
+		return -EINVAL;
+
+	num_of_reg = s6e3hc2_check_gamma_payload(src, len);
+	if (num_of_reg != S6E3HC2_NUM_GAMMA_TABLES - 1) {
+		pr_err("Invalid gamma bands\n");
+		return -EINVAL;
+	}
+
+	if (fixup_gamma) {
+		if (!panel_info || !panel_info->extinfo_length ||
+		    panel_info->extinfo_length != fixup_gamma->panel_id_len ||
+		    memcmp(panel_info->extinfo, fixup_gamma->panel_id,
+			    panel_info->extinfo_length))
+			fixup_gamma = NULL;
+	}
+
+	/* reg (1 byte) | band size (2 bytes) | band */
+	for (i = 0; i < num_of_reg; i++) {
+		const struct s6e3hc2_gamma_info *gamma_info =
+			&s6e3hc2_gamma_tables[i];
+		const u8 *payload = src + read_len;
+		const u8 cmd = *payload;
+		u8 *tmp_buf = gamma_data[i] + sizeof(cmd)
+				+ gamma_info->prefix_len;
+
+		parse_payload_len(payload, len - read_len, &payload_len);
+		if (gamma_info->cmd != cmd ||
+		    payload_len != (gamma_info->len - gamma_info->prefix_len)) {
+			pr_err("Failed to overwrite 0x%02X reg\n", cmd);
+			return -EINVAL;
+		}
+
+		memcpy(tmp_buf, payload + CALI_GAMMA_HEADER_SIZE, payload_len);
+		if (fixup_gamma && fixup_gamma->fixup_loc) {
+			int cnt;
+
+			for (cnt = 0; cnt < fixup_gamma->fixup_loc_count; cnt++)
+				if (cmd == fixup_gamma->fixup_loc[cnt].cmd)
+					s6e3hc2_gamma_fixup_after_overwrite(
+						&fixup_gamma->fixup_loc[cnt],
+						tmp_buf, payload_len);
+		}
+		read_len = CALI_GAMMA_HEADER_SIZE + payload_len;
+	}
+
+	return 0;
+}
+
+static int s6e3hc2_overwrite_gamma_data(struct gamma_switch_data *sdata)
+{
+	struct panel_switch_data *pdata;
+	struct dsi_panel *panel;
+	u8 *payload, **old_gamma_data;
+	size_t payload_len, total_len;
+	int rc = 0, num_of_fps;
+	u8 count = 0;
+
+	if (unlikely(!sdata))
+		return -EINVAL;
+
+	pdata = &sdata->base;
+	payload = sdata->cali_gamma.data;
+
+	if (unlikely(!pdata->panel || !payload))
+		return -EINVAL;
+
+	total_len = sdata->cali_gamma.len;
+	num_of_fps = s6e3hc2_check_gamma_payload(payload, total_len);
+	if (num_of_fps <= 0) {
+		pr_err("Invalid gamma data\n");
+		return -EINVAL;
+	}
+
+	/*
+	 * 60Hz's gamma table and 90Hz's gamma table are the same size,
+	 * so we won't recalculate payload_len.
+	 */
+	parse_payload_len(payload, total_len, &payload_len);
+
+	panel = pdata->panel;
+
+	/* FPS (1 byte) | gamma size (2 bytes) | gamma */
+	while (count < num_of_fps) {
+		const u8 cali_fps = *payload;
+
+		rc = find_gamma_data_for_refresh_rate(panel,
+					cali_fps, &old_gamma_data);
+		if (rc) {
+			pr_err("Not support %ufps, err %d\n", cali_fps, rc);
+			break;
+		}
+		payload += CALI_GAMMA_HEADER_SIZE;
+		rc = s6e3hc2_overwrite_gamma_bands(&panel->vendor_info,
+						   &sdata->fixup_gamma,
+						   old_gamma_data, payload,
+						   payload_len);
+		if (rc) {
+			pr_err("Failed to overwrite gamma\n");
+			break;
+		}
+		payload += payload_len;
+		count++;
+	}
+
+	if (rc) {
+		s6e3hc2_restore_gamma_data(sdata);
+	} else {
+		s6e3hc2_gamma_update_reg_locked(pdata, panel->cur_mode);
+		sdata->num_of_cali_gamma = count;
+		pr_info("Finished overwriting gamma\n");
+	}
+	return rc;
+}
+
+static void s6e3hc2_release_cali_gamma(struct gamma_switch_data *sdata)
+{
+	if (!sdata)
+		return;
+
+	kfree(sdata->cali_gamma.data);
+	sdata->cali_gamma.data = NULL;
+	sdata->cali_gamma.len = 0;
+}
+
+static int s6e3hc2_create_cali_gamma(struct gamma_switch_data *sdata,
+					size_t len, char *buf, size_t buf_len)
+{
+	int rc = 0;
+
+	if (unlikely(!sdata || !buf))
+		return -EINVAL;
+
+	if (sdata->cali_gamma.data)
+		s6e3hc2_release_cali_gamma(sdata);
+
+	sdata->cali_gamma.data = kzalloc(len, GFP_KERNEL);
+	if (!sdata->cali_gamma.data)
+		return -ENOMEM;
+
+	rc = parse_byte_buf(sdata->cali_gamma.data, len, buf, buf_len);
+	if (rc <= 0) {
+		pr_err("Invalid gamma data\n");
+		s6e3hc2_release_cali_gamma(sdata);
+		return -EINVAL;
+	}
+	sdata->cali_gamma.len = rc;
+
+	return 0;
+}
+
+static void s6e3hc2_gamma_work(struct kthread_work *work)
+{
+	struct gamma_switch_data *sdata;
+	struct dsi_panel *panel;
+
+	sdata = container_of(work, struct gamma_switch_data, gamma_work);
+	panel = sdata->base.panel;
+
+	if (!panel)
+		return;
+
+	mutex_lock(&panel->panel_lock);
+
+	s6e3hc2_gamma_read_tables(&sdata->base);
+	if (sdata->cali_gamma.data) {
+		s6e3hc2_overwrite_gamma_data(sdata);
+		s6e3hc2_release_cali_gamma(sdata);
+	}
+
+	mutex_unlock(&panel->panel_lock);
+}
+
+static ssize_t
+s6e3hc2_gamma_store(struct dsi_panel *panel, const char *buf, size_t count)
+{
+	struct panel_switch_data *pdata;
+	struct gamma_switch_data *sdata;
+	size_t len, buf_dup_len;
+	int rc = 0;
+	char *buf_dup;
+
+	if (unlikely(!panel || !panel->private_data))
+		return -EINVAL;
+
+	pdata = panel->private_data;
+	sdata = container_of(pdata, struct gamma_switch_data, base);
+
+	/* calculate length for worst case (1 digit per byte + whitespace) */
+	len = (count + 1) / 2;
+	buf_dup = kstrndup(buf, count, GFP_KERNEL);
+	if (!buf_dup)
+		return -ENOMEM;
+
+	buf_dup_len = strlen(buf_dup) + 1;
+
+	mutex_lock(&panel->panel_lock);
+
+	if (!strncmp(buf_dup, DEFAULT_GAMMA_STR, strlen(DEFAULT_GAMMA_STR))) {
+		if (!sdata->num_of_cali_gamma)
+			goto done;
+
+		s6e3hc2_restore_gamma_data(sdata);
+		if (dsi_panel_initialized(panel))
+			s6e3hc2_gamma_update_reg_locked(pdata,
+						panel->cur_mode);
+		pr_info("Finished restore gamma\n");
+		goto done;
+	}
+
+	rc = s6e3hc2_create_cali_gamma(sdata, len, buf_dup, buf_dup_len);
+	if (rc)
+		goto error;
+
+	if (!sdata->native_gamma_ready) {
+		pr_info("Gamma table is not ready!\n");
+		goto done;
+	}
+
+	if (!dsi_panel_initialized(panel)) {
+		pr_warn("Panel not yet initialized.\n");
+		rc = -EINVAL;
+		goto error;
+	}
+
+	rc = s6e3hc2_overwrite_gamma_data(sdata);
+	if (rc)
+		pr_warn("Failed to update calibrated gamma.\n");
+
+	s6e3hc2_release_cali_gamma(sdata);
+
+error:
+done:
+	mutex_unlock(&panel->panel_lock);
+	kfree(buf_dup);
+
+	return rc ? rc : count;
+}
+
+static ssize_t
+gamma_store(struct device *dev, struct device_attribute *attr,
+			const char *buf, size_t count)
+{
+	const struct dsi_display *display;
+	struct dsi_panel *panel;
+	struct panel_switch_data *pdata;
+
+	display = dev_get_drvdata(dev);
+	if (unlikely(!display || !display->panel ||
+		     !display->panel->private_data))
+		return -EINVAL;
+
+	panel = display->panel;
+	pdata = panel->private_data;
+
+	if (unlikely(!pdata || !pdata->funcs))
+		return -EINVAL;
+
+	if (!pdata->funcs->support_cali_gamma_store)
+		return -EOPNOTSUPP;
+
+	return pdata->funcs->support_cali_gamma_store(panel, buf, count);
+}
+
+struct device_attribute dev_attr_gamma = __ATTR_WO(gamma);
+
+static struct attribute *gamma_attrs[] = {
+	&dev_attr_gamma.attr,
+	NULL
+};
+
+static const struct attribute_group gamma_group = {
+	.attrs = gamma_attrs,
+};
+
+static void
+s6e3hc2_gamma_release_fixup_info(struct gamma_fixup_info *fixup_gamma)
+{
+	if (!fixup_gamma)
+		return;
+
+	kfree(fixup_gamma->fixup_loc);
+	fixup_gamma->fixup_loc = NULL;
+	fixup_gamma->fixup_loc_count = 0;
+
+	kfree(fixup_gamma->panel_id);
+	fixup_gamma->panel_id = NULL;
+	fixup_gamma->panel_id_len = 0;
+}
+
+#define GAMMA_FIXUP_LOCATION_LEN 4
+static void
+s6e3hc2_gamma_create_fixup_info(struct dsi_panel *panel,
+				struct gamma_fixup_info *fixup_gamma)
+{
+	struct dsi_display *display;
+	struct device_node *of_node;
+	int i, rc, id_len, loc_len, offset = 0;
+	u32 loc_count, *fixup_loc;
+
+	if (unlikely(!panel || !fixup_gamma))
+		return;
+
+	display = dsi_panel_to_display(panel);
+	if (unlikely(!display || !display->pdev)) {
+		pr_warn("Invalid device\n");
+		return;
+	}
+
+	of_node = display->pdev->dev.of_node;
+	if (unlikely(!of_node))
+		return;
+
+	id_len = of_property_count_u8_elems(of_node,
+			"google,gamma_fixup_panel_id");
+	if (id_len <= 0) {
+		pr_debug("Skip gamma fixup\n");
+		return;
+	}
+
+	fixup_gamma->panel_id = kzalloc(id_len *
+			sizeof(*fixup_gamma->panel_id), GFP_KERNEL);
+	if (!fixup_gamma->panel_id)
+		return;
+
+	rc = of_property_read_u8_array(of_node,
+			"google,gamma_fixup_panel_id",
+			fixup_gamma->panel_id, id_len);
+	if (rc) {
+		pr_err("Failed to parse panel id\n");
+		goto error;
+	}
+
+	loc_len = of_property_count_u32_elems(of_node,
+			"google,gamma_fixup_location");
+	if (loc_len <= 0 || loc_len % GAMMA_FIXUP_LOCATION_LEN != 0) {
+		pr_err("invalid format\n");
+		goto error;
+	}
+
+	loc_count = loc_len / GAMMA_FIXUP_LOCATION_LEN;
+	fixup_loc = kcalloc(loc_len, sizeof(*fixup_loc), GFP_KERNEL);
+	if (!fixup_loc)
+		goto error;
+
+	rc = of_property_read_u32_array(of_node,
+			"google,gamma_fixup_location", fixup_loc, loc_len);
+	if (rc) {
+		pr_err("Failed to parse location\n");
+		goto error_fixup_loc;
+	}
+
+	fixup_gamma->fixup_loc = kcalloc(loc_count,
+				sizeof(*fixup_gamma->fixup_loc), GFP_KERNEL);
+	if (!fixup_gamma->fixup_loc)
+		goto error_fixup_loc;
+
+	for (i = 0; i < loc_count; i++) {
+		fixup_gamma->fixup_loc[i].cmd = fixup_loc[offset];
+		fixup_gamma->fixup_loc[i].src_idx = fixup_loc[offset + 1];
+		fixup_gamma->fixup_loc[i].dst_idx = fixup_loc[offset + 2];
+		fixup_gamma->fixup_loc[i].mask = fixup_loc[offset + 3];
+		offset += GAMMA_FIXUP_LOCATION_LEN;
+	}
+
+	fixup_gamma->fixup_loc_count = loc_count;
+	fixup_gamma->panel_id_len = id_len;
+
+	kfree(fixup_loc);
+
+	return;
+
+error_fixup_loc:
+	kfree(fixup_loc);
+error:
+	kfree(fixup_gamma->panel_id);
+	fixup_gamma->panel_id = NULL;
+}
+
+static struct panel_switch_data *s6e3hc2_switch_create(struct dsi_panel *panel)
+{
+	struct gamma_switch_data *sdata;
+	int rc;
+
+	rc = s6e3hc2_check_gamma_infos(s6e3hc2_gamma_tables,
+		S6E3HC2_NUM_GAMMA_TABLES);
+	if (rc)
+		return ERR_PTR(rc);
+
+	sdata = devm_kzalloc(panel->parent, sizeof(*sdata), GFP_KERNEL);
+	if (!sdata)
+		return ERR_PTR(-ENOMEM);
+
+	rc = panel_switch_data_init(panel, &sdata->base);
+	if (rc)
+		return ERR_PTR(rc);
+
+	kthread_init_work(&sdata->gamma_work, s6e3hc2_gamma_work);
+	debugfs_create_file("gamma", 0600, sdata->base.debug_root,
+			    &sdata->base, &s6e3hc2_read_gamma_fops);
+	s6e3hc2_gamma_create_fixup_info(panel, &sdata->fixup_gamma);
+
+	return &sdata->base;
+}
+
+static void s6e3hc2_switch_data_destroy(struct panel_switch_data *pdata)
+{
+	struct gamma_switch_data *sdata;
+
+	sdata = container_of(pdata, struct gamma_switch_data, base);
+
+	s6e3hc2_gamma_release_fixup_info(&sdata->fixup_gamma);
+	panel_switch_data_deinit(pdata);
+	if (pdata->panel && pdata->panel->parent)
+		devm_kfree(pdata->panel->parent, sdata);
+}
+
+static int s6e3hc2_update_hbm(struct dsi_panel *panel)
+{
+	const struct panel_switch_data *pdata = panel->private_data;
+
+	return s6e3hc2_switch_mode_update(panel, pdata->display_mode, true);
+}
+
+static void s6e3hc2_perform_switch(struct panel_switch_data *pdata,
+				   const struct dsi_display_mode *mode)
+{
+	struct dsi_panel *panel = pdata->panel;
+	struct mipi_dsi_device *dsi = &panel->mipi_device;
+
+	if (!mode)
+		return;
+
+	if (DSI_WRITE_CMD_BUF(dsi, unlock_cmd))
+		return;
+
+	s6e3hc2_switch_mode_update(panel, mode, false);
+	s6e3hc2_gamma_update(pdata, mode);
+
+	s6e3hc2_te2_normal_mode_update(panel, false);
+
+	DSI_WRITE_CMD_BUF(dsi, lock_cmd);
+}
+
+int s6e3hc2_send_nolp_cmds(struct dsi_panel *panel)
+{
+	struct panel_switch_data *pdata;
+	struct dsi_display_mode *cur_mode;
+	struct dsi_panel_cmd_set *cmd;
+	int rc = 0;
+
+	if (unlikely(!panel || !panel->cur_mode))
+		return -EINVAL;
+
+	pdata = panel->private_data;
+	cur_mode = panel->cur_mode;
+
+	cmd = &cur_mode->priv_info->cmd_sets[DSI_CMD_SET_NOLP];
+	rc = dsi_panel_cmd_set_transfer(panel, cmd);
+	if (rc) {
+		pr_debug("[%s] failed to send DSI_CMD_SET_NOLP cmd, rc=%d\n",
+		       panel->name, rc);
+		return rc;
+	}
+
+	s6e3hc2_gamma_update(pdata, cur_mode);
+
+	s6e3hc2_te2_normal_mode_update(panel, false);
+
+	cmd = &cur_mode->priv_info->cmd_sets[DSI_CMD_SET_POST_NOLP];
+	rc = dsi_panel_cmd_set_transfer(panel, cmd);
+	if (rc)
+		pr_debug("[%s] failed to send DSI_CMD_SET_POST_NOLP cmd, rc=%d\n",
+		       panel->name, rc);
+	return rc;
+}
+
+static bool s6e3hc2_need_update_gamma(
+			const struct gamma_switch_data *sdata,
+			const struct dsi_display_mode *mode)
+{
+	const struct panel_switch_data *pdata = &sdata->base;
+
+	return mode && pdata && pdata->panel &&
+		!(mode->dsi_mode_flags & DSI_MODE_FLAG_DMS) &&
+		(pdata->panel->power_mode == SDE_MODE_DPMS_ON) &&
+		!((mode->timing.refresh_rate == S6E3HC2_DEFAULT_FPS) &&
+		!sdata->num_of_cali_gamma);
+}
+
+static int s6e3hc2_post_enable(struct panel_switch_data *pdata)
+{
+	struct gamma_switch_data *sdata;
+	const struct dsi_display_mode *mode;
+	struct dsi_panel *panel = pdata->panel;
+
+	if (unlikely(!pdata || !pdata->panel))
+		return -ENOENT;
+
+	sdata = container_of(pdata, struct gamma_switch_data, base);
+	mode = pdata->panel->cur_mode;
+
+	kthread_flush_work(&sdata->gamma_work);
+	if (!sdata->native_gamma_ready) {
+		kthread_queue_work(&pdata->worker, &sdata->gamma_work);
+	} else if (s6e3hc2_need_update_gamma(sdata, mode)) {
+		mutex_lock(&pdata->panel->panel_lock);
+		s6e3hc2_gamma_update_reg_locked(pdata, mode);
+		mutex_unlock(&pdata->panel->panel_lock);
+		pr_debug("Updated gamma for %dhz\n", mode->timing.refresh_rate);
+	}
+
+	if (!(mode->dsi_mode_flags & DSI_MODE_FLAG_DMS)) {
+		mutex_lock(&panel->panel_lock);
+		s6e3hc2_te2_normal_mode_update(panel, true);
+		mutex_unlock(&panel->panel_lock);
+	}
+
+	return 0;
+}
+
+const struct panel_switch_funcs s6e3hc2_switch_funcs = {
+	.create                   = s6e3hc2_switch_create,
+	.destroy                  = s6e3hc2_switch_data_destroy,
+	.perform_switch           = s6e3hc2_perform_switch,
+	.post_enable              = s6e3hc2_post_enable,
+	.support_update_te2       = s6e3hc2_te2_update_reg_locked,
+	.support_update_hbm       = s6e3hc2_update_hbm,
+	.send_nolp_cmds           = s6e3hc2_send_nolp_cmds,
+	.support_cali_gamma_store = s6e3hc2_gamma_store,
+};
+
+static const struct of_device_id panel_switch_dt_match[] = {
+	{
+		.compatible = DSI_PANEL_GAMMA_NAME,
+		.data = &s6e3hc2_switch_funcs,
+	},
+	{
+		.compatible = DSI_PANEL_SWITCH_NAME,
+		.data = &panel_switch_default_funcs,
+	},
+	{},
+};
+
+int dsi_panel_switch_init(struct dsi_panel *panel)
+{
+	struct panel_switch_data *pdata = NULL;
+	const struct panel_switch_funcs *funcs = NULL;
+	const struct of_device_id *match;
+
+	match = of_match_node(panel_switch_dt_match, panel->panel_of_node);
+	if (match && match->data)
+		funcs = match->data;
+
+	if (!funcs) {
+		pr_info("Panel switch is not supported\n");
+		return 0;
+	}
+
+	if (funcs->create)
+		pdata = funcs->create(panel);
+
+	if (IS_ERR_OR_NULL(pdata))
+		return -ENOENT;
+
+	pdata->funcs = funcs;
+
+	if (pdata->funcs->support_cali_gamma_store) {
+		if (sysfs_create_group(&panel->parent->kobj,
+					&gamma_group))
+			pr_warn("unable to create gamma_group\n");
+	}
+
+	return 0;
+}
+
+void dsi_panel_switch_put_mode(struct dsi_display_mode *mode)
+{
+	if (likely(mode->priv_info)) {
+		kfree(mode->priv_info->switch_data);
+		mode->priv_info->switch_data = NULL;
+	}
+}
+
+/*
+ * This should be called without panel_lock as flush/wait on worker can
+ * deadlock while holding it
+ */
+void dsi_panel_switch_destroy(struct dsi_panel *panel)
+{
+	struct panel_switch_data *pdata = panel->private_data;
+	struct dsi_display_mode *mode;
+	int i;
+
+	if (pdata->funcs->support_cali_gamma_store)
+		sysfs_remove_group(&pdata->panel->parent->kobj,
+					&gamma_group);
+
+	for_each_display_mode(i, mode, pdata->panel)
+		dsi_panel_switch_put_mode(mode);
+
+	if (pdata && pdata->funcs && pdata->funcs->destroy)
+		pdata->funcs->destroy(pdata);
+}
diff --git a/msm/dsi/dsi_pwr.c b/msm/dsi/dsi_pwr.c
index bcaefaf..1448d92 100644
--- a/msm/dsi/dsi_pwr.c
+++ b/msm/dsi/dsi_pwr.c
@@ -398,50 +398,3 @@
 	return rc;
 }
 
-/*
- * dsi_pwr_panel_regulator_mode_set()
- * set the AB/IBB regulator mode for OLED panel
- * AOD mode entry and exit
- * @regs:	Pointer to set of regulators to enable or disable.
- * @reg_name:	Name of panel power we want to set.
- * @retulator_mode:	Regulator mode values, like:
- *	REGULATOR_MODE_INVALID
- *	REGULATOR_MODE_FAST
- *	REGULATOR_MODE_NORMAL
- *	REGULATOR_MODE_IDLE
- *	REGULATOR_MODE_STANDBY
- *
- * return: error code in case of failure or 0 for success.
- */
-int dsi_pwr_panel_regulator_mode_set(struct dsi_regulator_info *regs,
-						const char *reg_name,
-						int regulator_mode)
-{
-	int i = 0, rc = 0;
-	struct dsi_vreg *vreg;
-
-	if (regs->count == 0)
-		return -EINVAL;
-
-	if (!regs->vregs)
-		return -EINVAL;
-
-	for (i = 0; i < regs->count; i++) {
-		vreg = &regs->vregs[i];
-		if (!strcmp(vreg->vreg_name, reg_name)) {
-			rc = regulator_set_mode(vreg->vreg,
-							regulator_mode);
-			if (rc)
-				DSI_ERR("Regulator %s set mode %d failed\n",
-					vreg->vreg_name, rc);
-			break;
-		}
-	}
-
-	if (i >= regs->count) {
-		DSI_ERR("Regulator %s was not found\n", reg_name);
-		return -EINVAL;
-	}
-
-	return rc;
-}
diff --git a/msm/dsi/dsi_pwr.h b/msm/dsi/dsi_pwr.h
index fd9ef2c..3c5518d 100644
--- a/msm/dsi/dsi_pwr.h
+++ b/msm/dsi/dsi_pwr.h
@@ -86,21 +86,4 @@
  */
 int dsi_pwr_enable_regulator(struct dsi_regulator_info *regs, bool enable);
 
-/**
- * dsi_pwr_panel_regulator_mode_set()
- * set regulator mode for OLED panel
- * @regs:       Pointer to set of regulators to enable or disable.
- * @reg_name:		Panel regulator name
- * @regulator_mode:	Regulator mode values, like:
- *                  REGULATOR_MODE_INVALID
- *                  REGULATOR_MODE_FAST
- *                  REGULATOR_MODE_NORMAL
- *                  REGULATOR_MODE_IDLE
- *                  REGULATOR_MODE_STANDBY
- *
- * return: error code in case of failure or 0 for success.
- */
-int dsi_pwr_panel_regulator_mode_set(struct dsi_regulator_info *regs,
-						const char *reg_name,
-						int regulator_mode);
 #endif /* _DSI_PWR_H_ */
diff --git a/msm/msm_atomic.c b/msm/msm_atomic.c
index ddd966d..cd5bca1 100644
--- a/msm/msm_atomic.c
+++ b/msm/msm_atomic.c
@@ -33,6 +33,49 @@
 	struct kthread_work commit_work;
 };
 
+static BLOCKING_NOTIFIER_HEAD(msm_drm_notifier_list);
+
+/**
+ * msm_drm_register_client - register a client notifier
+ * @nb: notifier block to callback on events
+ *
+ * This function registers a notifier callback function
+ * to msm_drm_notifier_list, which would be called when
+ * received unblank/power down event.
+ */
+int msm_drm_register_client(struct notifier_block *nb)
+{
+	return blocking_notifier_chain_register(&msm_drm_notifier_list,
+						nb);
+}
+EXPORT_SYMBOL(msm_drm_register_client);
+
+/**
+ * msm_drm_unregister_client - unregister a client notifier
+ * @nb: notifier block to callback on events
+ *
+ * This function unregisters the callback function from
+ * msm_drm_notifier_list.
+ */
+int msm_drm_unregister_client(struct notifier_block *nb)
+{
+	return blocking_notifier_chain_unregister(&msm_drm_notifier_list,
+						  nb);
+}
+EXPORT_SYMBOL(msm_drm_unregister_client);
+
+/**
+ * msm_drm_notifier_call_chain - notify clients of drm_events
+ * @val: event MSM_DRM_EARLY_EVENT_BLANK or MSM_DRM_EVENT_BLANK
+ * @v: notifier data, inculde display id and display blank
+ *     event(unblank or power down).
+ */
+int msm_drm_notifier_call_chain(unsigned long val, void *v)
+{
+	return blocking_notifier_call_chain(&msm_drm_notifier_list, val,
+					    v);
+}
+
 static inline bool _msm_seamless_for_crtc(struct drm_device *dev,
 					struct drm_atomic_state *state,
 			struct drm_crtc_state *crtc_state, bool enable)
diff --git a/msm/msm_drv.c b/msm/msm_drv.c
index 7025d9d..c3f58be 100644
--- a/msm/msm_drv.c
+++ b/msm/msm_drv.c
@@ -39,6 +39,7 @@
 
 #include <linux/of_address.h>
 #include <linux/kthread.h>
+#include <linux/workqueue.h>
 #include <uapi/linux/sched/types.h>
 #include <drm/drm_of.h>
 
@@ -61,6 +62,9 @@
 #define MSM_VERSION_MINOR	3
 #define MSM_VERSION_PATCHLEVEL	0
 
+#define IDLE_ENCODER_MASK_DEFAULT	1
+#define IDLE_TIMEOUT_MS_DEFAULT		100
+
 static DEFINE_MUTEX(msm_release_lock);
 
 static void msm_fb_output_poll_changed(struct drm_device *dev)
@@ -697,6 +701,160 @@
 	return kms;
 }
 
+static ssize_t idle_encoder_mask_store(struct device *device,
+			       struct device_attribute *attr,
+			       const char *buf, size_t count)
+{
+	struct drm_device *ddev = dev_get_drvdata(device);
+	struct msm_drm_private *priv = ddev->dev_private;
+	struct msm_idle *idle = &priv->idle;
+	u32 encoder_mask = 0;
+	int rc;
+	unsigned long flags;
+
+	rc = kstrtouint(buf, 0, &encoder_mask);
+	if (rc)
+		return rc;
+
+	spin_lock_irqsave(&idle->lock, flags);
+	idle->encoder_mask = encoder_mask;
+	idle->active_mask &= encoder_mask;
+	spin_unlock_irqrestore(&idle->lock, flags);
+
+	return count;
+}
+
+static ssize_t idle_encoder_mask_show(struct device *device,
+			      struct device_attribute *attr,
+			      char *buf)
+{
+	struct drm_device *ddev = dev_get_drvdata(device);
+	struct msm_drm_private *priv = ddev->dev_private;
+	struct msm_idle *idle = &priv->idle;
+
+	return snprintf(buf, PAGE_SIZE, "0x%x\n", idle->encoder_mask);
+}
+
+static ssize_t idle_timeout_ms_store(struct device *device,
+			       struct device_attribute *attr,
+			       const char *buf, size_t count)
+{
+	struct drm_device *ddev = dev_get_drvdata(device);
+	struct msm_drm_private *priv = ddev->dev_private;
+	struct msm_idle *idle = &priv->idle;
+	u32 timeout_ms = 0;
+	int rc;
+	unsigned long flags;
+
+	rc = kstrtouint(buf, 10, &timeout_ms);
+	if (rc)
+		return rc;
+
+	spin_lock_irqsave(&idle->lock, flags);
+	idle->timeout_ms = timeout_ms;
+	spin_unlock_irqrestore(&idle->lock, flags);
+
+	return count;
+}
+
+static ssize_t idle_timeout_ms_show(struct device *device,
+			      struct device_attribute *attr,
+			      char *buf)
+{
+	struct drm_device *ddev = dev_get_drvdata(device);
+	struct msm_drm_private *priv = ddev->dev_private;
+	struct msm_idle *idle = &priv->idle;
+
+	return scnprintf(buf, PAGE_SIZE, "%d\n", idle->timeout_ms);
+}
+
+static ssize_t idle_state_show(struct device *device,
+			      struct device_attribute *attr,
+			      char *buf)
+{
+	struct drm_device *ddev = dev_get_drvdata(device);
+	struct msm_drm_private *priv = ddev->dev_private;
+	struct msm_idle *idle = &priv->idle;
+	const char *state;
+	unsigned long flags;
+
+	spin_lock_irqsave(&idle->lock, flags);
+	if (idle->active_mask) {
+		state = "active";
+		spin_unlock_irqrestore(&idle->lock, flags);
+		return scnprintf(buf, PAGE_SIZE, "%s (0x%x)\n",
+				 state, idle->active_mask);
+	} else if (delayed_work_pending(&idle->work))
+		state = "pending";
+	else
+		state = "idle";
+	spin_unlock_irqrestore(&idle->lock, flags);
+
+	return scnprintf(buf, PAGE_SIZE, "%s\n", state);
+}
+
+static DEVICE_ATTR_RW(idle_encoder_mask);
+static DEVICE_ATTR_RW(idle_timeout_ms);
+static DEVICE_ATTR_RO(idle_state);
+
+static const struct attribute *msm_idle_attrs[] = {
+	&dev_attr_idle_encoder_mask.attr,
+	&dev_attr_idle_timeout_ms.attr,
+	&dev_attr_idle_state.attr,
+	NULL
+};
+
+static void msm_idle_work(struct work_struct *work)
+{
+	struct delayed_work *dw = to_delayed_work(work);
+	struct msm_idle *idle = container_of(dw, struct msm_idle, work);
+	struct msm_drm_private *priv = container_of(idle,
+					struct msm_drm_private, idle);
+
+	if (!idle->active_mask)
+		sysfs_notify(&priv->dev->dev->kobj, NULL, "idle_state");
+}
+
+void msm_idle_set_state(struct drm_encoder *encoder, bool active)
+{
+	struct drm_device *ddev = encoder->dev;
+	struct msm_drm_private *priv = ddev->dev_private;
+	struct msm_idle *idle = &priv->idle;
+	unsigned int mask = 1 << drm_encoder_index(encoder);
+	unsigned long flags;
+
+	spin_lock_irqsave(&idle->lock, flags);
+	if (mask & idle->encoder_mask) {
+		if (active)
+			idle->active_mask |= mask;
+		else
+			idle->active_mask &= ~mask;
+
+		if (idle->timeout_ms && !idle->active_mask)
+			mod_delayed_work(system_wq, &idle->work,
+					 msecs_to_jiffies(idle->timeout_ms));
+		else
+			cancel_delayed_work(&idle->work);
+	}
+	spin_unlock_irqrestore(&idle->lock, flags);
+}
+
+static void msm_idle_init(struct drm_device *ddev)
+{
+	struct msm_drm_private *priv = ddev->dev_private;
+	struct msm_idle *idle = &priv->idle;
+
+	if (sysfs_create_files(&ddev->dev->kobj, msm_idle_attrs) < 0)
+		pr_warn("failed to create idle state file");
+
+	idle->active_mask = 0;
+	idle->encoder_mask = IDLE_ENCODER_MASK_DEFAULT;
+	idle->timeout_ms = IDLE_TIMEOUT_MS_DEFAULT;
+
+	INIT_DELAYED_WORK(&idle->work, msm_idle_work);
+	spin_lock_init(&idle->lock);
+}
+
 static int msm_drm_init(struct device *dev, struct drm_driver *drv)
 {
 	struct platform_device *pdev = to_platform_device(dev);
@@ -747,6 +905,8 @@
 		goto dbg_init_fail;
 	}
 
+	msm_idle_init(ddev);
+
 	/* Bind all our sub-components: */
 	ret = msm_component_bind_all(dev, ddev);
 	if (ret)
@@ -2066,13 +2226,13 @@
 	priv->shutdown_in_progress = true;
 }
 
-static const struct of_device_id dt_match[] = {
+static const struct of_device_id dt_match_msm_drv[] = {
 	{ .compatible = "qcom,mdp4", .data = (void *)KMS_MDP4 },
 	{ .compatible = "qcom,mdss", .data = (void *)KMS_MDP5 },
 	{ .compatible = "qcom,sde-kms", .data = (void *)KMS_SDE },
 	{},
 };
-MODULE_DEVICE_TABLE(of, dt_match);
+MODULE_DEVICE_TABLE(of, dt_match_msm_drv);
 
 static struct platform_driver msm_platform_driver = {
 	.probe      = msm_pdev_probe,
@@ -2080,7 +2240,7 @@
 	.shutdown   = msm_pdev_shutdown,
 	.driver     = {
 		.name   = "msm_drm",
-		.of_match_table = dt_match,
+		.of_match_table = dt_match_msm_drv,
 		.pm     = &msm_pm_ops,
 		.suppress_bind_attrs = true,
 	},
@@ -2109,8 +2269,76 @@
 	msm_smmu_driver_cleanup();
 }
 
+#ifdef CONFIG_DRM_MSM_MODULE
+static int __init msm_drm_module_init(void)
+{
+	int ret;
+
+	ret = sde_rsc_rpmh_register();
+	if (ret) {
+		pr_err("sde_rsc_rpmh register fails, error:%d\n", ret);
+		return ret;
+	}
+
+	ret = mdss_pll_driver_init();
+	if (ret) {
+		pr_err("mdss_pll_driver_init failed, error %d\n", ret);
+		return ret;
+	}
+
+	ret = sde_wb_register();
+	if (ret) {
+		pr_err("sde_wb register fails, error:%d\n", ret);
+		return ret;
+	}
+
+	ret = sde_rsc_register();
+	if (ret) {
+		pr_err("sde_rsc register fails, error: %d\n", ret);
+		return ret;
+	}
+
+	ret = dsi_display_register();
+	if (ret) {
+		pr_err("dsi_display register fails, error: %d\n", ret);
+		return ret;
+	}
+
+	ret = msm_drm_register();
+	if (ret) {
+		pr_err("msm_drm register fails, error: %d\n", ret);
+		return ret;
+	}
+
+	ret = msm_notifier_register();
+	if (ret) {
+		pr_err("msm_notifier register fails, error: %d\n", ret);
+		return ret;
+	}
+
+	ret = dp_display_init();
+	if (ret)
+		pr_err("dp_display init fails, error: %d\n", ret);
+
+	return ret;
+}
+
+static void __exit msm_drm_module_cleanup(void)
+{
+	dp_display_cleanup();
+	msm_notifier_unregister();
+	msm_drm_unregister();
+	dsi_display_unregister();
+	sde_rsc_unregister();
+	sde_wb_unregister();
+	mdss_pll_driver_deinit();
+}
+module_init(msm_drm_module_init);
+module_exit(msm_drm_module_cleanup);
+#else
 module_init(msm_drm_register);
 module_exit(msm_drm_unregister);
+#endif
 
 MODULE_AUTHOR("Rob Clark <robdclark@gmail.com");
 MODULE_DESCRIPTION("MSM DRM Driver");
diff --git a/msm/msm_drv.h b/msm/msm_drv.h
index 4ba732d..57bf491 100644
--- a/msm/msm_drv.h
+++ b/msm/msm_drv.h
@@ -591,6 +591,15 @@
 	struct kthread_worker worker;
 };
 
+struct msm_idle {
+	u32 timeout_ms;
+	u32 encoder_mask;
+	u32 active_mask;
+
+	spinlock_t lock;
+	struct delayed_work work;
+};
+
 struct msm_drm_private {
 
 	struct drm_device *dev;
@@ -701,6 +710,8 @@
 
 	/* update the flag when msm driver receives shutdown notification */
 	bool shutdown_in_progress;
+
+	struct msm_idle idle;
 };
 
 /* get struct msm_kms * from drm_device * */
@@ -716,6 +727,7 @@
 void msm_atomic_commit_tail(struct drm_atomic_state *state);
 int msm_atomic_commit(struct drm_device *dev,
 	struct drm_atomic_state *state, bool nonblock);
+int msm_drm_notifier_call_chain(unsigned long val, void *v);
 
 /* callback from wq once fence has passed: */
 struct msm_fence_cb {
@@ -967,6 +979,7 @@
 }
 #endif
 
+void msm_idle_set_state(struct drm_encoder *encoder, bool active);
 #ifdef CONFIG_DEBUG_FS
 void msm_gem_describe(struct drm_gem_object *obj, struct seq_file *m);
 void msm_gem_describe_objects(struct list_head *list, struct seq_file *m);
@@ -1039,4 +1052,20 @@
 		const struct drm_display_mode *mode,
 		const struct msm_resource_caps_info *res, u32 *num_lm);
 
+#ifdef CONFIG_DRM_MSM_MODULE
+int dsi_display_register(void);
+void dsi_display_unregister(void);
+int dp_display_init(void);
+void dp_display_cleanup(void);
+int sde_rsc_rpmh_register(void);
+int sde_rsc_register(void);
+void sde_rsc_unregister(void);
+int sde_wb_register(void);
+void sde_wb_unregister(void);
+int mdss_pll_driver_init(void);
+void mdss_pll_driver_deinit(void);
+int msm_notifier_register(void);
+void msm_notifier_unregister(void);
+#endif
+
 #endif /* __MSM_DRV_H__ */
diff --git a/msm/msm_kms.h b/msm/msm_kms.h
index d6ff517..de34cb6 100644
--- a/msm/msm_kms.h
+++ b/msm/msm_kms.h
@@ -42,6 +42,8 @@
 #define MSM_MODE_FLAG_SEAMLESS_POMS			(1<<4)
 /* Request to switch the bit clk */
 #define MSM_MODE_FLAG_SEAMLESS_DYN_CLK			(1<<5)
+/* Request to switch the connector mode, fps only */
+#define MSM_MODE_FLAG_SEAMLESS_DMS_FPS			(1<<6)
 
 /* As there are different display controller blocks depending on the
  * snapdragon version, the kms support is split out and the appropriate
@@ -208,6 +210,13 @@
 		: false;
 }
 
+static inline
+bool msm_is_mode_seamless_dms_fps(const struct drm_display_mode *mode)
+{
+	return mode &&
+		(mode->private_flags & MSM_MODE_FLAG_SEAMLESS_DMS_FPS) != 0;
+}
+
 static inline bool msm_is_mode_dynamic_fps(const struct drm_display_mode *mode)
 {
 	return ((mode->flags & DRM_MODE_FLAG_SEAMLESS) &&
diff --git a/msm/msm_notifier.c b/msm/msm_notifier.c
index 20be9ba..ed528b0 100644
--- a/msm/msm_notifier.c
+++ b/msm/msm_notifier.c
@@ -11,6 +11,7 @@
 #include <linux/sched.h>
 
 #include <drm/drm_panel.h>
+#include "msm_drv.h"
 #include "sde_dbg.h"
 
 struct msm_display_fps_info {
@@ -183,32 +184,34 @@
 	return ret;
 }
 
-static const struct of_device_id dt_match[] = {
+static const struct of_device_id dt_match_msm_notifier[] = {
 	{ .compatible = "qcom,msm-notifier"},
 	{},
 };
 
-MODULE_DEVICE_TABLE(of, dt_match);
+MODULE_DEVICE_TABLE(of, dt_match_msm_notifier);
 
 static struct platform_driver msm_notifier_platform_driver = {
 	.probe     = msm_notifier_probe,
 	.remove    = msm_notifier_remove,
 	.driver     = {
 		.name   = "msm_notifier",
-		.of_match_table = dt_match,
+		.of_match_table = dt_match_msm_notifier,
 		.suppress_bind_attrs = true,
 	},
 };
 
-static int __init msm_notifier_register(void)
+int __init msm_notifier_register(void)
 {
 	return platform_driver_register(&msm_notifier_platform_driver);
 }
 
-static void __exit msm_notifier_unregister(void)
+void __exit msm_notifier_unregister(void)
 {
 	platform_driver_unregister(&msm_notifier_platform_driver);
 }
 
+#ifndef CONFIG_DRM_MSM_MODULE
 late_initcall(msm_notifier_register);
 module_exit(msm_notifier_unregister);
+#endif
diff --git a/msm/sde/sde_connector.c b/msm/sde/sde_connector.c
index 35f6783..166a52c 100644
--- a/msm/sde/sde_connector.c
+++ b/msm/sde/sde_connector.c
@@ -1,5 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0-only
 /*
+ * Copyright (c) 2021-2022 Qualcomm Innovation Center, Inc. All rights reserved.
  * Copyright (c) 2016-2020, The Linux Foundation. All rights reserved.
  * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
  */
@@ -18,7 +19,6 @@
 #include "sde_crtc.h"
 #include "sde_rm.h"
 
-#define BL_NODE_NAME_SIZE 32
 #define HDR10_PLUS_VSIF_TYPE_CODE      0x81
 
 /* Autorefresh will occur after FRAME_CNT frames. Large values are unlikely */
@@ -29,9 +29,6 @@
 
 #define SDE_ERROR_CONN(c, fmt, ...) SDE_ERROR("conn%d " fmt,\
 		(c) ? (c)->base.base.id : -1, ##__VA_ARGS__)
-static u32 dither_matrix[DITHER_MATRIX_SZ] = {
-	15, 7, 13, 5, 3, 11, 1, 9, 12, 4, 14, 6, 0, 8, 2, 10
-};
 
 static const struct drm_prop_enum_list e_topology_name[] = {
 	{SDE_RM_TOPOLOGY_NONE,	"sde_none"},
@@ -67,104 +64,6 @@
 	{FRAME_DONE_WAIT_POSTED_START, "posted_start"},
 };
 
-static int sde_backlight_device_update_status(struct backlight_device *bd)
-{
-	int brightness;
-	struct dsi_display *display;
-	struct sde_connector *c_conn;
-	int bl_lvl;
-	struct drm_event event;
-	int rc = 0;
-
-	brightness = bd->props.brightness;
-
-	if ((bd->props.power != FB_BLANK_UNBLANK) ||
-			(bd->props.state & BL_CORE_FBBLANK) ||
-			(bd->props.state & BL_CORE_SUSPENDED))
-		brightness = 0;
-
-	c_conn = bl_get_data(bd);
-	display = (struct dsi_display *) c_conn->display;
-	if (brightness > display->panel->bl_config.bl_max_level)
-		brightness = display->panel->bl_config.bl_max_level;
-
-	/* map UI brightness into driver backlight level with rounding */
-	bl_lvl = mult_frac(brightness, display->panel->bl_config.bl_max_level,
-			display->panel->bl_config.brightness_max_level);
-
-	if (!bl_lvl && brightness)
-		bl_lvl = 1;
-
-	if (!c_conn->allow_bl_update) {
-		c_conn->unset_bl_level = bl_lvl;
-		return 0;
-	}
-
-	if (c_conn->ops.set_backlight) {
-		/* skip notifying user space if bl is 0 */
-		if (brightness != 0) {
-			event.type = DRM_EVENT_SYS_BACKLIGHT;
-			event.length = sizeof(u32);
-			msm_mode_object_event_notify(&c_conn->base.base,
-				c_conn->base.dev, &event, (u8 *)&brightness);
-		}
-		rc = c_conn->ops.set_backlight(&c_conn->base,
-				c_conn->display, bl_lvl);
-		c_conn->unset_bl_level = 0;
-	}
-
-	return rc;
-}
-
-static int sde_backlight_device_get_brightness(struct backlight_device *bd)
-{
-	return 0;
-}
-
-static const struct backlight_ops sde_backlight_device_ops = {
-	.update_status = sde_backlight_device_update_status,
-	.get_brightness = sde_backlight_device_get_brightness,
-};
-
-static int sde_backlight_setup(struct sde_connector *c_conn,
-					struct drm_device *dev)
-{
-	struct backlight_properties props;
-	struct dsi_display *display;
-	struct dsi_backlight_config *bl_config;
-	static int display_count;
-	char bl_node_name[BL_NODE_NAME_SIZE];
-
-	if (!c_conn || !dev || !dev->dev) {
-		SDE_ERROR("invalid param\n");
-		return -EINVAL;
-	} else if (c_conn->connector_type != DRM_MODE_CONNECTOR_DSI) {
-		return 0;
-	}
-
-	memset(&props, 0, sizeof(props));
-	props.type = BACKLIGHT_RAW;
-	props.power = FB_BLANK_UNBLANK;
-
-	display = (struct dsi_display *) c_conn->display;
-	bl_config = &display->panel->bl_config;
-	props.max_brightness = bl_config->brightness_max_level;
-	props.brightness = bl_config->brightness_max_level;
-	snprintf(bl_node_name, BL_NODE_NAME_SIZE, "panel%u-backlight",
-							display_count);
-	c_conn->bl_device = backlight_device_register(bl_node_name, dev->dev,
-			c_conn, &sde_backlight_device_ops, &props);
-	if (IS_ERR_OR_NULL(c_conn->bl_device)) {
-		SDE_ERROR("Failed to register backlight: %ld\n",
-				    PTR_ERR(c_conn->bl_device));
-		c_conn->bl_device = NULL;
-		return -ENODEV;
-	}
-	display_count++;
-
-	return 0;
-}
-
 int sde_connector_trigger_event(void *drm_connector,
 		uint32_t event_idx, uint32_t instance_idx,
 		uint32_t data0, uint32_t data1,
@@ -242,60 +141,12 @@
 	(void)sde_connector_register_event(connector, event_idx, 0, 0);
 }
 
-static int _sde_connector_get_default_dither_cfg_v1(
-		struct sde_connector *c_conn, void *cfg)
-{
-	struct drm_msm_dither *dither_cfg = (struct drm_msm_dither *)cfg;
-	enum dsi_pixel_format dst_format = DSI_PIXEL_FORMAT_MAX;
-
-	if (!c_conn || !cfg) {
-		SDE_ERROR("invalid argument(s), c_conn %pK, cfg %pK\n",
-				c_conn, cfg);
-		return -EINVAL;
-	}
-
-	if (!c_conn->ops.get_dst_format) {
-		SDE_DEBUG("get_dst_format is unavailable\n");
-		return 0;
-	}
-
-	dst_format = c_conn->ops.get_dst_format(&c_conn->base, c_conn->display);
-	switch (dst_format) {
-	case DSI_PIXEL_FORMAT_RGB888:
-		dither_cfg->c0_bitdepth = 8;
-		dither_cfg->c1_bitdepth = 8;
-		dither_cfg->c2_bitdepth = 8;
-		dither_cfg->c3_bitdepth = 8;
-		break;
-	case DSI_PIXEL_FORMAT_RGB666:
-	case DSI_PIXEL_FORMAT_RGB666_LOOSE:
-		dither_cfg->c0_bitdepth = 6;
-		dither_cfg->c1_bitdepth = 6;
-		dither_cfg->c2_bitdepth = 6;
-		dither_cfg->c3_bitdepth = 6;
-		break;
-	default:
-		SDE_DEBUG("no default dither config for dst_format %d\n",
-			dst_format);
-		return -ENODATA;
-	}
-
-	memcpy(&dither_cfg->matrix, dither_matrix,
-			sizeof(u32) * DITHER_MATRIX_SZ);
-	dither_cfg->temporal_en = 0;
-	return 0;
-}
-
 static void _sde_connector_install_dither_property(struct drm_device *dev,
 		struct sde_kms *sde_kms, struct sde_connector *c_conn)
 {
 	char prop_name[DRM_PROP_NAME_LEN];
 	struct sde_mdss_cfg *catalog = NULL;
-	struct drm_property_blob *blob_ptr;
-	void *cfg;
-	int ret = 0;
-	u32 version = 0, len = 0;
-	bool defalut_dither_needed = false;
+	u32 version = 0;
 
 	if (!dev || !sde_kms || !c_conn) {
 		SDE_ERROR("invld args (s), dev %pK, sde_kms %pK, c_conn %pK\n",
@@ -313,54 +164,48 @@
 		msm_property_install_blob(&c_conn->property_info, prop_name,
 			DRM_MODE_PROP_BLOB,
 			CONNECTOR_PROP_PP_DITHER);
-		len = sizeof(struct drm_msm_dither);
-		cfg = kzalloc(len, GFP_KERNEL);
-		if (!cfg)
-			return;
-
-		ret = _sde_connector_get_default_dither_cfg_v1(c_conn, cfg);
-		if (!ret)
-			defalut_dither_needed = true;
 		break;
 	default:
 		SDE_ERROR("unsupported dither version %d\n", version);
 		return;
 	}
-
-	if (defalut_dither_needed) {
-		blob_ptr = drm_property_create_blob(dev, len, cfg);
-		if (IS_ERR_OR_NULL(blob_ptr))
-			goto exit;
-		c_conn->blob_dither = blob_ptr;
-	}
-exit:
-	kfree(cfg);
 }
 
 int sde_connector_get_dither_cfg(struct drm_connector *conn,
 			struct drm_connector_state *state, void **cfg,
-			size_t *len)
+			size_t *len, bool idle_pc)
 {
 	struct sde_connector *c_conn = NULL;
 	struct sde_connector_state *c_state = NULL;
 	size_t dither_sz = 0;
+	bool is_dirty;
 	u32 *p = (u32 *)cfg;
 
-	if (!conn || !state || !p)
+	if (!conn || !state || !p) {
+		SDE_ERROR("invalid arguments\n");
 		return -EINVAL;
+	}
 
 	c_conn = to_sde_connector(conn);
 	c_state = to_sde_connector_state(state);
 
-	/* try to get user config data first */
-	*cfg = msm_property_get_blob(&c_conn->property_info,
-					&c_state->property_state,
-					&dither_sz,
-					CONNECTOR_PROP_PP_DITHER);
-	/* if user config data doesn't exist, use default dither blob */
-	if (*cfg == NULL && c_conn->blob_dither) {
-		*cfg = c_conn->blob_dither->data;
-		dither_sz = c_conn->blob_dither->length;
+	is_dirty = msm_property_is_dirty(&c_conn->property_info,
+			&c_state->property_state,
+			CONNECTOR_PROP_PP_DITHER);
+
+	if (!is_dirty && !idle_pc) {
+		return -ENODATA;
+	} else if (is_dirty || idle_pc) {
+		*cfg = msm_property_get_blob(&c_conn->property_info,
+				&c_state->property_state,
+				&dither_sz,
+				CONNECTOR_PROP_PP_DITHER);
+		/* in idle_pc use case return early, when dither is already disabled  */
+		if (idle_pc && *cfg == NULL)
+			return -ENODATA;
+		/* disable dither based on user config data */
+		else if (*cfg == NULL)
+			return 0;
 	}
 	*len = dither_sz;
 	return 0;
@@ -588,25 +433,16 @@
 
 	bl_config = &dsi_display->panel->bl_config;
 
-	if (!c_conn->allow_bl_update) {
-		c_conn->unset_bl_level = bl_config->bl_level;
-		return 0;
-	}
-
-	if (c_conn->unset_bl_level)
-		bl_config->bl_level = c_conn->unset_bl_level;
-
 	bl_config->bl_scale = c_conn->bl_scale > MAX_BL_SCALE_LEVEL ?
 			MAX_BL_SCALE_LEVEL : c_conn->bl_scale;
 	bl_config->bl_scale_sv = c_conn->bl_scale_sv > MAX_SV_BL_SCALE_LEVEL ?
 			MAX_SV_BL_SCALE_LEVEL : c_conn->bl_scale_sv;
 
-	SDE_DEBUG("bl_scale = %u, bl_scale_sv = %u, bl_level = %u\n",
+	SDE_DEBUG("bl_scale = %u, bl_scale_sv = %u, bl_actual = %u\n",
 		bl_config->bl_scale, bl_config->bl_scale_sv,
-		bl_config->bl_level);
-	rc = c_conn->ops.set_backlight(&c_conn->base,
-			dsi_display, bl_config->bl_level);
-	c_conn->unset_bl_level = 0;
+		bl_config->bl_actual);
+	if (bl_config->bl_device)
+		backlight_update_status(bl_config->bl_device);
 
 	return rc;
 }
@@ -695,6 +531,8 @@
 {
 	struct sde_connector *c_conn;
 	struct sde_connector_state *c_state;
+	struct dsi_display *dsi_display = NULL;
+	struct dsi_backlight_config *bl_config = NULL;
 	int idx;
 
 	if (!connector) {
@@ -705,6 +543,13 @@
 	c_conn = to_sde_connector(connector);
 	c_state = to_sde_connector_state(connector->state);
 
+	if (c_conn->connector_type == DRM_MODE_CONNECTOR_DSI) {
+		dsi_display = c_conn->display;
+
+		if (dsi_display && dsi_display->panel)
+			bl_config = &dsi_display->panel->bl_config;
+	}
+
 	mutex_lock(&c_conn->property_info.property_lock);
 	while ((idx = msm_property_pop_dirty(&c_conn->property_info,
 					&c_state->property_state)) >= 0) {
@@ -740,7 +585,8 @@
 	 * Special handling for postproc properties and
 	 * for updating backlight if any unset backlight level is present
 	 */
-	if (c_conn->bl_scale_dirty || c_conn->unset_bl_level) {
+	if (c_conn->bl_scale_dirty || (bl_config &&
+			bl_config->bl_update_pending == true)) {
 		_sde_connector_update_bl_scale(c_conn);
 		c_conn->bl_scale_dirty = false;
 	}
@@ -877,25 +723,47 @@
 	/* Disable ESD thread */
 	sde_connector_schedule_status_work(connector, false);
 
-	if (c_conn->bl_device) {
-		c_conn->bl_device->props.power = FB_BLANK_POWERDOWN;
-		c_conn->bl_device->props.state |= BL_CORE_FBBLANK;
-		backlight_update_status(c_conn->bl_device);
-	}
+	c_conn = to_sde_connector(connector);
 
-	c_conn->allow_bl_update = false;
+	c_conn->last_panel_power_mode = SDE_MODE_DPMS_OFF;
 }
 
-void sde_connector_helper_bridge_enable(struct drm_connector *connector)
+void sde_connector_helper_bridge_pre_enable(struct drm_connector *connector)
 {
 	struct sde_connector *c_conn = NULL;
-	struct dsi_display *display;
 
 	if (!connector)
 		return;
 
 	c_conn = to_sde_connector(connector);
-	display = (struct dsi_display *) c_conn->display;
+
+	c_conn->last_panel_power_mode = SDE_MODE_DPMS_ON;
+}
+
+void sde_connector_helper_bridge_post_enable(struct drm_connector *connector)
+{
+	struct sde_connector *c_conn = NULL;
+	struct dsi_display *dsi_display = NULL;
+	struct dsi_backlight_config *bl_config = NULL;
+
+	if (!connector)
+		return;
+
+	c_conn = to_sde_connector(connector);
+
+	if (c_conn->connector_type == DRM_MODE_CONNECTOR_DSI) {
+		dsi_display = c_conn->display;
+
+		if (dsi_display && dsi_display->panel)
+			bl_config = &dsi_display->panel->bl_config;
+	}
+
+	c_conn->panel_dead = false;
+
+	if (!bl_config) {
+		SDE_ERROR("Invalid params bl_config null\n");
+		return;
+	}
 
 	/*
 	 * Special handling for some panels which need atleast
@@ -903,18 +771,15 @@
 	 * So delay backlight update to these panels until the
 	 * first frame commit is received from the HW.
 	 */
-	if (display->panel->bl_config.bl_update ==
-				BL_UPDATE_DELAY_UNTIL_FIRST_FRAME)
-		sde_encoder_wait_for_event(c_conn->encoder,
-				MSM_ENC_TX_COMPLETE);
-	c_conn->allow_bl_update = true;
-
-	if (c_conn->bl_device) {
-		c_conn->bl_device->props.power = FB_BLANK_UNBLANK;
-		c_conn->bl_device->props.state &= ~BL_CORE_FBBLANK;
-		backlight_update_status(c_conn->bl_device);
+	if (!bl_config->allow_bl_update) {
+		if (bl_config->bl_update == BL_UPDATE_DELAY_UNTIL_FIRST_FRAME)
+			sde_encoder_wait_for_event(c_conn->encoder,
+						   MSM_ENC_TX_COMPLETE);
+		bl_config->allow_bl_update = true;
 	}
-	c_conn->panel_dead = false;
+
+	if (bl_config->bl_device)
+		backlight_update_status(bl_config->bl_device);
 }
 
 int sde_connector_clk_ctrl(struct drm_connector *connector, bool enable)
@@ -967,8 +832,6 @@
 	if (c_conn->blob_ext_hdr)
 		drm_property_blob_put(c_conn->blob_ext_hdr);
 
-	if (c_conn->bl_device)
-		backlight_device_unregister(c_conn->bl_device);
 	drm_connector_unregister(connector);
 	mutex_destroy(&c_conn->lock);
 	sde_fence_deinit(c_conn->retire_fence);
@@ -1787,6 +1650,18 @@
 	return vfp;
 }
 
+void sde_connector_set_idle_hint(struct drm_connector *connector, bool is_idle)
+{
+	struct sde_connector *c_conn;
+
+	if (unlikely(!connector))
+		return;
+
+	c_conn = to_sde_connector(connector);
+	if (c_conn->ops.set_idle_hint)
+		c_conn->ops.set_idle_hint(c_conn->display, is_idle);
+}
+
 static int _sde_debugfs_conn_cmd_tx_open(struct inode *inode, struct file *file)
 {
 	/* non-seekable */
@@ -2661,12 +2536,6 @@
 		goto error_cleanup_fence;
 	}
 
-	rc = sde_backlight_setup(c_conn, dev);
-	if (rc) {
-		SDE_ERROR("failed to setup backlight, rc=%d\n", rc);
-		goto error_cleanup_fence;
-	}
-
 	/* create properties */
 	msm_property_init(&c_conn->property_info, &c_conn->base.base, dev,
 			priv->conn_property, c_conn->property_data,
diff --git a/msm/sde/sde_connector.h b/msm/sde/sde_connector.h
index cbc6bfa..dba3439 100644
--- a/msm/sde/sde_connector.h
+++ b/msm/sde/sde_connector.h
@@ -160,15 +160,6 @@
 			uint32_t event_idx, bool enable, void *display);
 
 	/**
-	 * set_backlight - set backlight level
-	 * @connector: Pointer to drm connector structure
-	 * @display: Pointer to private display structure
-	 * @bl_lvel: Backlight level
-	 */
-	int (*set_backlight)(struct drm_connector *connector,
-			void *display, u32 bl_lvl);
-
-	/**
 	 * set_colorspace - set colorspace for connector
 	 * @connector: Pointer to drm connector structure
 	 * @display: Pointer to private display structure
@@ -340,6 +331,13 @@
 		struct msm_display_conn_params *params);
 
 	/**
+	 * set_idle_hint - gives hint to display whether display is idle
+	 * @display: Pointer to private display handle
+	 * @is_idle: true if display is idle, false otherwise
+	 */
+	void (*set_idle_hint)(void *display, bool is_idle);
+
+	/**
 	 * get_qsync_min_fps - Get qsync min fps from qsync-min-fps-list
 	 * @display: Pointer to private display structure
 	 * @mode_fps: Fps value in dfps list
@@ -476,8 +474,6 @@
 	bool bl_scale_dirty;
 	u32 bl_scale;
 	u32 bl_scale_sv;
-	u32 unset_bl_level;
-	bool allow_bl_update;
 
 	u32 qsync_mode;
 	bool qsync_updated;
@@ -862,10 +858,12 @@
  * @state: Pointer to drm_connector_state struct
  * @cfg: Pointer to pointer to dither cfg
  * @len: length of the dither data
+ * @idle_pc: flag to indicate idle_pc_restore happened
  * Returns: Zero on success
  */
 int sde_connector_get_dither_cfg(struct drm_connector *conn,
-		struct drm_connector_state *state, void **cfg, size_t *len);
+		struct drm_connector_state *state, void **cfg,
+		size_t *len, bool idle_pc);
 
 /**
  * sde_connector_set_blob_data - set connector blob property data
@@ -951,10 +949,18 @@
 int sde_connector_event_notify(struct drm_connector *connector, uint32_t type,
 		uint32_t len, uint32_t val);
 /**
- * sde_connector_helper_bridge_enable - helper function for drm bridge enable
+ * sde_connector_helper_bridge_pre_enable - helper function for drm bridge
+ *                                          pre enable
  * @connector: Pointer to DRM connector object
  */
-void sde_connector_helper_bridge_enable(struct drm_connector *connector);
+void sde_connector_helper_bridge_pre_enable(struct drm_connector *connector);
+
+/**
+ * sde_connector_helper_bridge_post_enable - helper function for drm bridge
+ *                                           post enable
+ * @connector: Pointer to DRM connector object
+ */
+void sde_connector_helper_bridge_post_enable(struct drm_connector *connector);
 
 /**
  * sde_connector_get_panel_vfp - helper to get panel vfp
@@ -965,6 +971,14 @@
  */
 int sde_connector_get_panel_vfp(struct drm_connector *connector,
 	struct drm_display_mode *mode);
+
+/**
+ * sde_connector_set_idle_hint - helper to give idle hint to connector
+ * @connector: pointer to drm connector
+ * @is_idle: true on idle, false on wake up from idle
+ */
+void sde_connector_set_idle_hint(struct drm_connector *connector, bool is_idle);
+
 /**
  * sde_connector_esd_status - helper function to check te status
  * @connector: Pointer to DRM connector object
diff --git a/msm/sde/sde_crtc.c b/msm/sde/sde_crtc.c
index 602476b..889980f 100644
--- a/msm/sde/sde_crtc.c
+++ b/msm/sde/sde_crtc.c
@@ -1,4 +1,5 @@
 /*
+ * Copyright (c) 2021-2022 Qualcomm Innovation Center, Inc. All rights reserved.
  * Copyright (c) 2014-2021 The Linux Foundation. All rights reserved.
  * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
  * Copyright (C) 2013 Red Hat
@@ -215,6 +216,54 @@
 }
 #endif
 
+static ssize_t early_wakeup_store(struct device *device,
+		struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct drm_crtc *crtc;
+	struct sde_crtc *sde_crtc;
+	struct msm_drm_private *priv;
+	u32 crtc_id;
+	bool trigger;
+
+	if (!device || !buf || !count) {
+		SDE_ERROR("invalid input param(s)\n");
+		return -EINVAL;
+	}
+
+	if (kstrtobool(buf, &trigger) < 0)
+		return -EINVAL;
+
+	if (!trigger)
+		return count;
+
+	crtc = dev_get_drvdata(device);
+	if (!crtc || !crtc->dev || !crtc->dev->dev_private) {
+		SDE_ERROR("invalid crtc\n");
+		return -EINVAL;
+	}
+
+	sde_crtc = to_sde_crtc(crtc);
+	priv = crtc->dev->dev_private;
+
+	crtc_id = drm_crtc_index(crtc);
+	if (crtc_id >= ARRAY_SIZE(priv->disp_thread)) {
+		SDE_ERROR("invalid crtc index[%d]\n", crtc_id);
+		return -EINVAL;
+	}
+
+	kthread_queue_work(&priv->disp_thread[crtc_id].worker,
+			&sde_crtc->early_wakeup_work);
+
+	return count;
+}
+
+static ssize_t early_wakeup_show(struct device *device,
+		struct device_attribute *attr, char *buf)
+{
+
+    return 0;
+}
+
 static ssize_t fps_periodicity_ms_store(struct device *device,
 		struct device_attribute *attr, const char *buf, size_t count)
 {
@@ -401,12 +450,14 @@
 static DEVICE_ATTR_RO(vsync_event);
 static DEVICE_ATTR_RO(measured_fps);
 static DEVICE_ATTR_RW(fps_periodicity_ms);
+static DEVICE_ATTR_RW(early_wakeup);
 static DEVICE_ATTR_RO(retire_frame_event);
 
 static struct attribute *sde_crtc_dev_attrs[] = {
 	&dev_attr_vsync_event.attr,
 	&dev_attr_measured_fps.attr,
 	&dev_attr_fps_periodicity_ms.attr,
+	&dev_attr_early_wakeup.attr,
 	&dev_attr_retire_frame_event.attr,
 	NULL
 };
@@ -445,6 +496,7 @@
 	_sde_crtc_deinit_events(sde_crtc);
 
 	drm_crtc_cleanup(crtc);
+	mutex_destroy(&sde_crtc->vblank_modeset_ctrl_lock);
 	mutex_destroy(&sde_crtc->crtc_lock);
 	kfree(sde_crtc);
 }
@@ -5770,14 +5822,15 @@
 				sde_crtc->vblank_cb_count * 1000, diff_ms) : 0;
 
 		seq_printf(s,
-			"vblank fps:%lld count:%u total:%llums total_framecount:%llu\n",
+			"vblank fps:%lld count:%u total:%llums\n",
 				fps, sde_crtc->vblank_cb_count,
-				ktime_to_ms(diff), sde_crtc->play_count);
+				ktime_to_ms(diff));
 
 		/* reset time & count for next measurement */
 		sde_crtc->vblank_cb_count = 0;
 		sde_crtc->vblank_cb_time = ktime_set(0, 0);
 	}
+	seq_printf(s, "total_framecount:%llu\n", sde_crtc->play_count);
 
 	mutex_unlock(&sde_crtc->crtc_lock);
 
@@ -6262,6 +6315,40 @@
 	}
 }
 
+/*
+ * __sde_crtc_early_wakeup_work - trigger early wakeup from user space
+ */
+static void __sde_crtc_early_wakeup_work(struct kthread_work *work)
+{
+	struct sde_crtc *sde_crtc = container_of(work, struct sde_crtc,
+				early_wakeup_work);
+	struct drm_crtc *crtc;
+	struct drm_device *dev;
+	struct msm_drm_private *priv;
+	struct sde_kms *sde_kms;
+
+	if (!sde_crtc) {
+		SDE_ERROR("invalid sde crtc\n");
+		return;
+	}
+
+	if (!sde_crtc->enabled) {
+		SDE_INFO("sde crtc is not enabled\n");
+		return;
+	}
+
+	crtc = &sde_crtc->base;
+	dev = crtc->dev;
+	if (!dev) {
+		SDE_ERROR("invalid drm device\n");
+		return;
+	}
+
+	priv = dev->dev_private;
+	sde_kms = to_sde_kms(priv->kms);
+	sde_kms_trigger_early_wakeup(sde_kms, crtc);
+}
+
 /* initialize crtc */
 struct drm_crtc *sde_crtc_init(struct drm_device *dev, struct drm_plane *plane)
 {
@@ -6353,6 +6440,8 @@
 
 	kthread_init_delayed_work(&sde_crtc->idle_notify_work,
 					__sde_crtc_idle_notify_work);
+	kthread_init_work(&sde_crtc->early_wakeup_work,
+					__sde_crtc_early_wakeup_work);
 
 	SDE_DEBUG("crtc=%d new_llcc=%d, old_llcc=%d\n",
 		crtc->base.id,
diff --git a/msm/sde/sde_crtc.h b/msm/sde/sde_crtc.h
index bd16cd3..9d05268 100644
--- a/msm/sde/sde_crtc.h
+++ b/msm/sde/sde_crtc.h
@@ -272,6 +272,7 @@
  * @misr_frame_count  : misr frame count provided by client
  * @misr_data     : store misr data before turning off the clocks.
  * @idle_notify_work: delayed worker to notify idle timeout to user space
+ * @early_wakeup_work: work to trigger early wakeup
  * @power_event   : registered power event handle
  * @cur_perf      : current performance committed to clock/bandwidth driver
  * @plane_mask_old: keeps track of the planes used in the previous commit
@@ -349,6 +350,7 @@
 	bool misr_enable_debugfs;
 	u32 misr_frame_count;
 	struct kthread_delayed_work idle_notify_work;
+	struct kthread_work early_wakeup_work;
 
 	struct sde_power_event *power_event;
 
diff --git a/msm/sde/sde_encoder.c b/msm/sde/sde_encoder.c
index 71bbeaf..036ac50 100644
--- a/msm/sde/sde_encoder.c
+++ b/msm/sde/sde_encoder.c
@@ -1100,6 +1100,11 @@
 	return ret;
 }
 
+static inline bool is_lp_mode(int mode)
+{
+	return mode == SDE_MODE_DPMS_LP1 || mode == SDE_MODE_DPMS_LP2;
+}
+
 static int sde_encoder_virt_atomic_check(
 	struct drm_encoder *drm_enc, struct drm_crtc_state *crtc_state,
 	struct drm_connector_state *conn_state)
@@ -1141,6 +1146,18 @@
 	if (ret)
 		return ret;
 
+	if (msm_is_mode_seamless_dms(adj_mode)) {
+		const int prop_lp_mode = sde_connector_get_property(conn_state,
+					CONNECTOR_PROP_LP);
+
+		if (is_lp_mode(sde_conn->lp_mode) || is_lp_mode(prop_lp_mode)) {
+			SDE_ERROR_ENC(sde_enc,
+				"no DMS under LP mode: connector %d, prop %d\n",
+				sde_conn->lp_mode, prop_lp_mode);
+			return -EINVAL;
+		}
+	}
+
 	ret = _sde_encoder_atomic_check_pu_roi(sde_enc, crtc_state,
 			conn_state, sde_conn_state, sde_crtc_state);
 	if (ret)
@@ -2370,6 +2387,20 @@
 				sw_event);
 }
 
+static void sde_encoder_idle_hint(const struct drm_encoder *drm_enc,
+				  bool is_idle)
+{
+	const struct sde_encoder_virt *sde_enc = to_sde_encoder_virt(drm_enc);
+
+	SDE_ATRACE_BEGIN(__func__);
+	if (sde_enc->cur_master) {
+		struct drm_connector *conn = sde_enc->cur_master->connector;
+
+		sde_connector_set_idle_hint(conn, is_idle);
+	}
+	SDE_ATRACE_END(__func__);
+}
+
 static int _sde_encoder_rc_kickoff(struct drm_encoder *drm_enc,
 	u32 sw_event, struct sde_encoder_virt *sde_enc, bool is_vid_mode)
 {
@@ -2383,6 +2414,8 @@
 	/* cancel delayed off work, if any */
 	_sde_encoder_rc_cancel_delayed(sde_enc, sw_event);
 
+	msm_idle_set_state(drm_enc, true);
+
 	mutex_lock(&sde_enc->rc_lock);
 
 	/* return if the resource control is already in ON state */
@@ -2417,6 +2450,9 @@
 					SDE_EVTLOG_ERROR);
 			goto end;
 		}
+
+		sde_encoder_idle_hint(drm_enc, false);
+
 		_sde_encoder_update_rsc_client(drm_enc, true);
 	}
 	SDE_EVT32(DRMID(drm_enc), sw_event, sde_enc->rc_state,
@@ -2492,6 +2528,8 @@
 	else
 		idle_pc_duration = IDLE_POWERCOLLAPSE_DURATION;
 
+	msm_idle_set_state(drm_enc, false);
+
 	if (!autorefresh_enabled)
 		kthread_mod_delayed_work(
 			&disp_thread->worker,
@@ -2719,6 +2757,8 @@
 		_sde_encoder_irq_control(drm_enc, false);
 		sde_kms_update_pm_qos_irq_request(sde_kms, false, false);
 	} else {
+		sde_encoder_idle_hint(drm_enc, true);
+
 		/* disable all the clks and resources */
 		_sde_encoder_update_rsc_client(drm_enc, false);
 		_sde_encoder_resource_control_helper(drm_enc, false);
@@ -2790,6 +2830,8 @@
 			goto end;
 		}
 
+		sde_encoder_idle_hint(drm_enc, false);
+
 		_sde_encoder_update_rsc_client(drm_enc, true);
 
 		/*
@@ -2988,9 +3030,11 @@
 	if (msm_is_mode_seamless_dms(adj_mode) ||
 			(msm_is_mode_seamless_dyn_clk(adj_mode) &&
 			 is_cmd_mode)) {
+		SDE_ATRACE_BEGIN("pre_modeset");
 		/* restore resource state before releasing them */
 		ret = sde_encoder_resource_control(drm_enc,
 				SDE_ENC_RC_EVENT_PRE_MODESET);
+		SDE_ATRACE_END("pre_modeset");
 		if (ret) {
 			SDE_ERROR_ENC(sde_enc,
 					"sde resource control failed: %d\n",
@@ -3000,9 +3044,11 @@
 
 		/*
 		 * Disable dsc before switch the mode and after pre_modeset,
-		 * to guarantee that previous kickoff finished.
+		 * to guarantee that previous kickoff finished. No need to
+		 * disable if there's only change in refresh rate
 		 */
-		_sde_encoder_dsc_disable(sde_enc);
+		if (!msm_is_mode_seamless_dms_fps(adj_mode))
+			_sde_encoder_dsc_disable(sde_enc);
 	} else if (msm_is_mode_seamless_poms(adj_mode)) {
 		_sde_encoder_modeset_helper_locked(drm_enc,
 					SDE_ENC_RC_EVENT_PRE_MODESET);
@@ -3083,9 +3129,12 @@
 	/* update resources after seamless mode change */
 	if (msm_is_mode_seamless_dms(adj_mode) ||
 			(msm_is_mode_seamless_dyn_clk(adj_mode) &&
-			is_cmd_mode))
+			is_cmd_mode)) {
+		SDE_ATRACE_BEGIN("post_modeset");
 		sde_encoder_resource_control(&sde_enc->base,
 						SDE_ENC_RC_EVENT_POST_MODESET);
+		SDE_ATRACE_END("post_modeset");
+	}
 	else if (msm_is_mode_seamless_poms(adj_mode))
 		_sde_encoder_modeset_helper_locked(drm_enc,
 						SDE_ENC_RC_EVENT_POST_MODESET);
@@ -3292,6 +3341,57 @@
 	memset(&sde_enc->cur_conn_roi, 0, sizeof(sde_enc->cur_conn_roi));
 }
 
+static void _sde_encoder_setup_dither(struct sde_encoder_phys *phys)
+{
+	void *dither_cfg = NULL;
+	int ret = 0, i = 0;
+	size_t len = 0;
+	enum sde_rm_topology_name topology;
+	struct drm_encoder *drm_enc;
+	struct msm_display_dsc_info *dsc = NULL;
+	struct sde_encoder_virt *sde_enc;
+	struct sde_hw_pingpong *hw_pp;
+
+	if (!phys || !phys->connector || !phys->hw_pp ||
+			!phys->hw_pp->ops.setup_dither || !phys->parent)
+		return;
+
+	topology = sde_connector_get_topology_name(phys->connector);
+	if ((topology == SDE_RM_TOPOLOGY_PPSPLIT) &&
+			(phys->split_role == ENC_ROLE_SLAVE))
+		return;
+
+	drm_enc = phys->parent;
+	sde_enc = to_sde_encoder_virt(drm_enc);
+	dsc = &sde_enc->mode_info.comp_info.dsc_info;
+
+	/* disable dither for 10 bpp or 10bpc dsc config */
+	if (dsc->bpp == 10 || dsc->bpc == 10) {
+		phys->hw_pp->ops.setup_dither(phys->hw_pp, NULL, 0);
+		return;
+	}
+
+	ret = sde_connector_get_dither_cfg(phys->connector,
+			phys->connector->state, &dither_cfg,
+			&len, sde_enc->idle_pc_restore);
+
+        /* skip reg writes when return values are invalid or no data */
+	if (ret && ret == -ENODATA)
+		return;
+
+	if (TOPOLOGY_DUALPIPE_MERGE_MODE(topology)) {
+		for (i = 0; i < MAX_CHANNELS_PER_ENC; i++) {
+			hw_pp = sde_enc->hw_pp[i];
+			phys->hw_pp->ops.setup_dither(hw_pp,
+					dither_cfg,len);
+		}
+
+	} else {
+		phys->hw_pp->ops.setup_dither(phys->hw_pp,
+				dither_cfg, len);
+	}
+}
+
 void sde_encoder_virt_restore(struct drm_encoder *drm_enc)
 {
 	struct sde_encoder_virt *sde_enc = NULL;
@@ -3324,6 +3424,8 @@
 
 		if ((phys != sde_enc->cur_master) && phys->ops.restore)
 			phys->ops.restore(phys);
+
+		_sde_encoder_setup_dither(phys);
 	}
 
 	if (sde_enc->cur_master->ops.restore)
@@ -4467,53 +4569,6 @@
 	sde_enc->idle_pc_restore = false;
 }
 
-static void _sde_encoder_setup_dither(struct sde_encoder_phys *phys)
-{
-	void *dither_cfg;
-	int ret = 0, i = 0;
-	size_t len = 0;
-	enum sde_rm_topology_name topology;
-	struct drm_encoder *drm_enc;
-	struct msm_display_dsc_info *dsc = NULL;
-	struct sde_encoder_virt *sde_enc;
-	struct sde_hw_pingpong *hw_pp;
-
-	if (!phys || !phys->connector || !phys->hw_pp ||
-			!phys->hw_pp->ops.setup_dither || !phys->parent)
-		return;
-
-	topology = sde_connector_get_topology_name(phys->connector);
-	if ((topology == SDE_RM_TOPOLOGY_PPSPLIT) &&
-			(phys->split_role == ENC_ROLE_SLAVE))
-		return;
-
-	drm_enc = phys->parent;
-	sde_enc = to_sde_encoder_virt(drm_enc);
-	dsc = &sde_enc->mode_info.comp_info.dsc_info;
-	/* disable dither for 10 bpp or 10bpc dsc config */
-	if (dsc->bpp == 10 || dsc->bpc == 10) {
-		phys->hw_pp->ops.setup_dither(phys->hw_pp, NULL, 0);
-		return;
-	}
-
-	ret = sde_connector_get_dither_cfg(phys->connector,
-			phys->connector->state, &dither_cfg, &len);
-	if (ret)
-		return;
-
-	if (TOPOLOGY_DUALPIPE_MERGE_MODE(topology)) {
-		for (i = 0; i < MAX_CHANNELS_PER_ENC; i++) {
-			hw_pp = sde_enc->hw_pp[i];
-			if (hw_pp) {
-				phys->hw_pp->ops.setup_dither(hw_pp, dither_cfg,
-								len);
-			}
-		}
-	} else {
-		phys->hw_pp->ops.setup_dither(phys->hw_pp, dither_cfg, len);
-	}
-}
-
 static int _sde_encoder_wakeup_time(struct drm_encoder *drm_enc,
 		ktime_t *wakeup_time)
 {
@@ -6313,3 +6368,28 @@
 	sde_enc = to_sde_encoder_virt(encoder);
 	sde_enc->recovery_events_enabled = enabled;
 }
+
+void sde_encoder_trigger_early_wakeup(struct drm_encoder *drm_enc)
+{
+	struct sde_encoder_virt *sde_enc = NULL;
+	struct msm_drm_private *priv = NULL;
+
+	priv = drm_enc->dev->dev_private;
+	sde_enc = to_sde_encoder_virt(drm_enc);
+	if (!sde_enc->crtc || (sde_enc->crtc->index
+			>= ARRAY_SIZE(priv->disp_thread))) {
+		SDE_DEBUG_ENC(sde_enc,
+			"invalid cached CRTC: %d or crtc index: %d\n",
+			sde_enc->crtc == NULL,
+			sde_enc->crtc ? sde_enc->crtc->index : -EINVAL);
+		return;
+	}
+
+	if (sde_enc->rc_state == SDE_ENC_RC_STATE_IDLE ||
+		sde_enc->rc_state == SDE_ENC_RC_STATE_ON) {
+		SDE_ATRACE_BEGIN("sde_encoder_resource_control");
+		sde_encoder_resource_control(drm_enc,
+					SDE_ENC_RC_EVENT_EARLY_WAKEUP);
+		SDE_ATRACE_END("sde_encoder_resource_control");
+	}
+}
diff --git a/msm/sde/sde_encoder.h b/msm/sde/sde_encoder.h
index ebee140..3204a15 100644
--- a/msm/sde/sde_encoder.h
+++ b/msm/sde/sde_encoder.h
@@ -373,6 +373,12 @@
 void sde_encoder_uidle_enable(struct drm_encoder *drm_enc, bool enable);
 
 /**
+ * sde_encoder_trigger_early_wakeup - trigger early wake up
+ * @drm_enc:    Pointer to drm encoder structure
+ */
+void sde_encoder_trigger_early_wakeup(struct drm_encoder *drm_enc);
+
+/**
  * sde_encoder_virt_reset - delay encoder virt reset
  * @drm_enc:	Pointer to drm encoder structure
  */
diff --git a/msm/sde/sde_kms.c b/msm/sde/sde_kms.c
index 2b8e9e6..ce6c6a4 100644
--- a/msm/sde/sde_kms.c
+++ b/msm/sde/sde_kms.c
@@ -28,6 +28,7 @@
 #include <linux/dma-buf.h>
 #include <linux/memblock.h>
 #include <linux/bootmem.h>
+#include <linux/msm_drm_notify.h>
 #include <soc/qcom/scm.h>
 
 #include "msm_drv.h"
@@ -62,6 +63,7 @@
 #define MEM_PROTECT_SD_CTRL_SWITCH 0x18
 #define MDP_DEVICE_ID            0x1A
 
+#define PANEL_INFO_CLASS_NAME "panel_info"
 #define TCSR_DISP_HF_SF_ARES_GLITCH_MASK        0x01FCA084
 
 static const char * const iommu_ports[] = {
@@ -110,6 +112,11 @@
 static int _sde_kms_mmu_init(struct sde_kms *sde_kms);
 static int _sde_kms_register_events(struct msm_kms *kms,
 		struct drm_mode_object *obj, u32 event, bool en);
+
+static int panel_info_dev_create(struct sde_kms *sde_kms);
+static void panel_info_dev_release(struct sde_kms *sde_kms);
+static struct class *panel_info_class;
+
 bool sde_is_custom_client(void)
 {
 	return sdecustom;
@@ -1396,11 +1403,164 @@
 	sde_kms->wb_displays = NULL;
 	sde_kms->wb_display_count = 0;
 
+	panel_info_dev_release(sde_kms);
 	kfree(sde_kms->dsi_displays);
 	sde_kms->dsi_displays = NULL;
 	sde_kms->dsi_display_count = 0;
 }
 
+static ssize_t panel_vendor_name_show(struct device *dev,
+				      struct device_attribute *attr,
+				      char *buf)
+{
+	struct dsi_display *display;
+	struct dsi_panel *panel;
+
+	display = dev_get_drvdata(dev);
+	panel = display->panel;
+
+	if (!display || !panel || !panel->vendor_info.name || !buf) {
+		pr_err("Failed to show vendor name\n");
+		return 0;
+	}
+
+	return scnprintf(buf, PAGE_SIZE, "%s\n", panel->vendor_info.name);
+}
+
+static ssize_t serial_number_show(struct device *dev,
+				  struct device_attribute *attr,
+				  char *buf)
+{
+	struct dsi_display *display;
+	struct dsi_panel *panel;
+
+	display = dev_get_drvdata(dev);
+	panel = display->panel;
+
+	if (!display || !panel || !panel->vendor_info.is_sn || !buf) {
+		pr_err("Failed to show SN\n");
+		return 0;
+	}
+
+	return scnprintf(buf, PAGE_SIZE, "%s\n", panel->vendor_info.sn);
+}
+
+static ssize_t panel_extinfo_show(struct device *dev,
+				   struct device_attribute *attr,
+				   char *buf)
+{
+	struct dsi_display *display;
+	struct dsi_panel *panel;
+	const struct dsi_panel_vendor_info *vendor_info;
+	int i, written;
+
+	display = dev_get_drvdata(dev);
+	if (unlikely(!display || !display->panel || !buf))
+		return -EINVAL;
+
+	panel = display->panel;
+	vendor_info = &panel->vendor_info;
+	if (!vendor_info->extinfo_read) {
+		pr_err("Failed to show Ext info\n");
+		return 0;
+	}
+
+	mutex_lock(&panel->panel_lock);
+	written = 0;
+	for (i = 0; i < vendor_info->extinfo_read && written < PAGE_SIZE; i++) {
+		written += scnprintf(buf + written, PAGE_SIZE - written,
+				     "0x%02X ", vendor_info->extinfo[i]);
+	}
+	mutex_unlock(&panel->panel_lock);
+	if (!written)
+		return -EFAULT;
+	/* replace last space with a line return */
+	buf[written - 1] = '\n';
+	buf[written] = '\0';
+
+	return written;
+}
+
+struct device_attribute dev_attr_panel_vendor_name =
+			__ATTR_RO_MODE(panel_vendor_name, 0400);
+struct device_attribute dev_attr_serial_number =
+			__ATTR_RO_MODE(serial_number, 0400);
+struct device_attribute dev_attr_panel_ext_info =
+			__ATTR_RO_MODE(panel_extinfo, 0400);
+
+static struct attribute *panel_info_dev_attrs[] = {
+	&dev_attr_panel_vendor_name.attr,
+	&dev_attr_serial_number.attr,
+	&dev_attr_panel_ext_info.attr,
+	NULL
+};
+
+
+static const struct attribute_group panel_info_dev_group = {
+	.attrs = panel_info_dev_attrs,
+};
+
+static const struct attribute_group *panel_info_dev_groups[] = {
+	&panel_info_dev_group,
+	NULL
+};
+
+static int panel_info_dev_create(struct sde_kms *sde_kms)
+{
+	struct dsi_display *display;
+	int i;
+
+	if (panel_info_class && !IS_ERR(panel_info_class))
+		return 0;
+
+	panel_info_class = class_create(THIS_MODULE, PANEL_INFO_CLASS_NAME);
+	if (!panel_info_class || IS_ERR(panel_info_class))
+		return -EINVAL;
+
+	for (i = 0; i < sde_kms->dsi_display_count; ++i) {
+		display = (struct dsi_display *)sde_kms->dsi_displays[i];
+		display->panel_info_dev =
+			device_create_with_groups(panel_info_class,
+						  &display->pdev->dev,
+						  0,
+						  display,
+						  panel_info_dev_groups,
+						  "panel%d",
+						  i);
+		if (!display->panel_info_dev || IS_ERR(display->panel_info_dev))
+			goto error;
+	}
+	return 0;
+error:
+	while (--i >= 0) {
+		display = (struct dsi_display *)sde_kms->dsi_displays[i];
+		device_unregister(display->panel_info_dev);
+		display->panel_info_dev = NULL;
+	}
+
+	class_destroy(panel_info_class);
+	panel_info_class = NULL;
+
+	return -EINVAL;
+}
+
+static void panel_info_dev_release(struct sde_kms *sde_kms)
+{
+	struct dsi_display *display;
+	int i;
+
+	if (!panel_info_class || IS_ERR(panel_info_class))
+		return;
+
+	for (i = 0; i < sde_kms->dsi_display_count; ++i) {
+		display = (struct dsi_display *)sde_kms->dsi_displays[i];
+		device_unregister(display->panel_info_dev);
+		display->panel_info_dev = NULL;
+	}
+	class_destroy(panel_info_class);
+	panel_info_class = NULL;
+}
+
 /**
  * _sde_kms_setup_displays - create encoders, bridges and connectors
  *                           for underlying displays
@@ -1420,7 +1580,6 @@
 		.pre_destroy =  dsi_connector_put_modes,
 		.mode_valid = dsi_conn_mode_valid,
 		.get_info =   dsi_display_get_info,
-		.set_backlight = dsi_display_set_backlight,
 		.soft_reset   = dsi_display_soft_reset,
 		.pre_kickoff  = dsi_conn_pre_kickoff,
 		.clk_ctrl = dsi_display_clk_ctrl,
@@ -1434,6 +1593,7 @@
 		.cont_splash_config = dsi_display_cont_splash_config,
 		.get_panel_vfp = dsi_display_get_panel_vfp,
 		.get_default_lms = dsi_display_get_default_lms,
+		.set_idle_hint = dsi_display_set_idle_hint,
 		.get_qsync_min_fps = dsi_display_get_qsync_min_fps,
 	};
 	static const struct sde_connector_ops wb_ops = {
@@ -1489,6 +1649,7 @@
 	}
 
 	/* dsi */
+	panel_info_dev_create(sde_kms);
 	for (i = 0; i < sde_kms->dsi_display_count &&
 		priv->num_encoders < max_encoders; ++i) {
 		display = sde_kms->dsi_displays[i];
@@ -3933,3 +4094,26 @@
 	SDE_EVT32(DRMID(encoder), MSM_ENC_ACTIVE_REGION);
 	return sde_encoder_wait_for_event(encoder, MSM_ENC_ACTIVE_REGION);
 }
+
+void sde_kms_trigger_early_wakeup(struct sde_kms *sde_kms,
+		struct drm_crtc *crtc)
+{
+	struct msm_drm_private *priv;
+	struct drm_encoder *drm_enc;
+
+	if (!sde_kms || !crtc) {
+		SDE_ERROR("invalid argument sde_kms %pK crtc %pK\n",
+			sde_kms, crtc);
+		return;
+	}
+
+	priv = sde_kms->dev->dev_private;
+
+	SDE_ATRACE_BEGIN("sde_kms_trigger_early_wakeup");
+	drm_for_each_encoder_mask(drm_enc, crtc->dev, crtc->state->encoder_mask)
+		sde_encoder_trigger_early_wakeup(drm_enc);
+
+	if (sde_kms->first_kickoff)
+		sde_power_scale_reg_bus(&priv->phandle, VOTE_INDEX_HIGH, false);
+	SDE_ATRACE_END("sde_kms_trigger_early_wakeup");
+}
diff --git a/msm/sde/sde_kms.h b/msm/sde/sde_kms.h
index 6882cdf..2c6f19a 100644
--- a/msm/sde/sde_kms.h
+++ b/msm/sde/sde_kms.h
@@ -663,6 +663,14 @@
 int sde_kms_handle_recovery(struct drm_encoder *encoder);
 
 /**
+ * sde_kms_trigger_early_wakeup - trigger early wake up
+ * @sde_kms: pointer to sde_kms structure
+ * @crtc: pointer to drm_crtc structure
+ */
+void sde_kms_trigger_early_wakeup(struct sde_kms *sde_kms,
+		struct drm_crtc *crtc);
+
+/**
  * sde_kms_update_pm_qos_irq_request - Update Qos vote for CPU receiving
  *					display IRQ
  * @sde_kms : pointer to sde_kms structure
diff --git a/msm/sde/sde_plane.c b/msm/sde/sde_plane.c
index 0264cef..5ef4889 100644
--- a/msm/sde/sde_plane.c
+++ b/msm/sde/sde_plane.c
@@ -1,4 +1,5 @@
 /*
+ * Copyright (c) 2021-2022 Qualcomm Innovation Center, Inc. All rights reserved.
  * Copyright (C) 2014-2020 The Linux Foundation. All rights reserved.
  * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
  * Copyright (C) 2013 Red Hat
diff --git a/msm/sde/sde_wb.c b/msm/sde/sde_wb.c
index 4fcc2c3..4656373 100644
--- a/msm/sde/sde_wb.c
+++ b/msm/sde/sde_wb.c
@@ -823,15 +823,17 @@
 	},
 };
 
-static int __init sde_wb_register(void)
+int __init sde_wb_register(void)
 {
 	return platform_driver_register(&sde_wb_driver);
 }
 
-static void __exit sde_wb_unregister(void)
+void __exit sde_wb_unregister(void)
 {
 	platform_driver_unregister(&sde_wb_driver);
 }
 
+#ifndef CONFIG_DRM_MSM_MODULE
 module_init(sde_wb_register);
 module_exit(sde_wb_unregister);
+#endif
diff --git a/msm/sde_dbg.c b/msm/sde_dbg.c
index 04037d5..8fc73fb 100644
--- a/msm/sde_dbg.c
+++ b/msm/sde_dbg.c
@@ -1,5 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0-only
 /*
+ * Copyright (c) 2021-2022 Qualcomm Innovation Center, Inc. All rights reserved.
  * Copyright (c) 2009-2020, The Linux Foundation. All rights reserved.
  * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
  */
@@ -3291,7 +3292,7 @@
 
 	if (in_mem) {
 		if (!(*dump_mem))
-			*dump_mem =  vzalloc(list_size);
+			*dump_mem = vzalloc(list_size);
 
 		if (*dump_mem) {
 			dump_addr = *dump_mem;
@@ -3450,7 +3451,7 @@
 
 	if (in_mem) {
 		if (!(*dump_mem))
-			*dump_mem =  vzalloc(list_size);
+			*dump_mem = vzalloc(list_size);
 
 		if (*dump_mem) {
 			dump_addr = *dump_mem;
diff --git a/msm/sde_power_handle.c b/msm/sde_power_handle.c
index 9f66039..ebe3eb9 100644
--- a/msm/sde_power_handle.c
+++ b/msm/sde_power_handle.c
@@ -279,7 +279,7 @@
 	return rc;
 }
 
-#ifdef CONFIG_QCOM_BUS_SCALING
+#if IS_ENABLED(CONFIG_QCOM_BUS_SCALING)
 
 #define MAX_AXI_PORT_COUNT 3
 
diff --git a/msm/sde_rsc.c b/msm/sde_rsc.c
index b6397a9..fed56a7 100644
--- a/msm/sde_rsc.c
+++ b/msm/sde_rsc.c
@@ -1647,19 +1647,19 @@
 	return 0;
 }
 
-static const struct of_device_id dt_match[] = {
+static const struct of_device_id dt_match_sde_rsc[] = {
 	{ .compatible = "qcom,sde-rsc"},
 	{},
 };
 
-MODULE_DEVICE_TABLE(of, dt_match);
+MODULE_DEVICE_TABLE(of, dt_match_sde_rsc);
 
 static struct platform_driver sde_rsc_platform_driver = {
 	.probe      = sde_rsc_probe,
 	.remove     = sde_rsc_remove,
 	.driver     = {
 		.name   = "sde_rsc",
-		.of_match_table = dt_match,
+		.of_match_table = dt_match_sde_rsc,
 		.suppress_bind_attrs = true,
 	},
 };
@@ -1678,21 +1678,23 @@
 	},
 };
 
-static int __init sde_rsc_register(void)
+int __init sde_rsc_register(void)
 {
 	return platform_driver_register(&sde_rsc_platform_driver);
 }
 
-static void __exit sde_rsc_unregister(void)
+void __exit sde_rsc_unregister(void)
 {
 	platform_driver_unregister(&sde_rsc_platform_driver);
 }
 
-static int __init sde_rsc_rpmh_register(void)
+int __init sde_rsc_rpmh_register(void)
 {
 	return platform_driver_register(&sde_rsc_rpmh_driver);
 }
 
+#ifndef CONFIG_DRM_MSM_MODULE
 subsys_initcall(sde_rsc_rpmh_register);
 module_init(sde_rsc_register);
 module_exit(sde_rsc_unregister);
+#endif
diff --git a/pll/Makefile b/pll/Makefile
deleted file mode 100644
index 0b29a90..0000000
--- a/pll/Makefile
+++ /dev/null
@@ -1,18 +0,0 @@
-# SPDX-License-Identifier: GPL-2.0-only
-
-ccflags-y := -I$(srctree)/drivers/clk/qcom/
-
-obj-$(CONFIG_QCOM_MDSS_PLL) += pll_util.o
-obj-$(CONFIG_QCOM_MDSS_PLL) += pll_drv.o
-obj-$(CONFIG_QCOM_MDSS_PLL) += dsi_pll_10nm.o
-obj-$(CONFIG_QCOM_MDSS_PLL) += dsi_pll_7nm.o
-obj-$(CONFIG_QCOM_MDSS_PLL) += dsi_pll_28lpm.o
-obj-$(CONFIG_QCOM_MDSS_PLL) += dsi_pll_28nm_util.o
-obj-$(CONFIG_QCOM_MDSS_PLL) += dsi_pll_14nm.o
-obj-$(CONFIG_QCOM_MDSS_PLL) += dsi_pll_14nm_util.o
-obj-$(CONFIG_QCOM_MDSS_PLL) += hdmi_pll_28lpm.o
-obj-$(CONFIG_QCOM_MDSS_DP_PLL) += dp_pll_7nm.o \
-	dp_pll_7nm_util.o \
-	dp_pll_10nm.o \
-	dp_pll_10nm_util.o \
-	dp_pll_14nm.o \
diff --git a/pll/pll_drv.c b/pll/pll_drv.c
index 5e0cd4c..454baa0 100644
--- a/pll/pll_drv.c
+++ b/pll/pll_drv.c
@@ -391,7 +391,7 @@
 	{},
 };
 
-MODULE_DEVICE_TABLE(of, mdss_clock_dt_match);
+MODULE_DEVICE_TABLE(of, mdss_pll_dt_match);
 
 static struct platform_driver mdss_pll_driver = {
 	.probe = mdss_pll_probe,
@@ -399,10 +399,11 @@
 	.driver = {
 		.name = "mdss_pll",
 		.of_match_table = mdss_pll_dt_match,
+		.sync_state = clk_sync_state,
 	},
 };
 
-static int __init mdss_pll_driver_init(void)
+int __init mdss_pll_driver_init(void)
 {
 	int rc;
 
@@ -412,13 +413,15 @@
 
 	return rc;
 }
-fs_initcall(mdss_pll_driver_init);
 
-static void __exit mdss_pll_driver_deinit(void)
+void __exit mdss_pll_driver_deinit(void)
 {
 	platform_driver_unregister(&mdss_pll_driver);
 }
-module_exit(mdss_pll_driver_deinit);
 
+#ifndef CONFIG_DRM_MSM_MODULE
+fs_initcall(mdss_pll_driver_init);
+module_exit(mdss_pll_driver_deinit);
+#endif
 MODULE_LICENSE("GPL v2");
 MODULE_DESCRIPTION("mdss pll driver");
diff --git a/rotator/sde_rotator_base.c b/rotator/sde_rotator_base.c
index 3cf8507..4ef5151 100644
--- a/rotator/sde_rotator_base.c
+++ b/rotator/sde_rotator_base.c
@@ -827,7 +827,7 @@
 #define BUS_VOTE_40_MHZ 320000000
 #define BUS_VOTE_80_MHZ 640000000
 
-#ifdef CONFIG_QCOM_BUS_SCALING
+#if IS_ENABLED(CONFIG_QCOM_BUS_SCALING)
 
 static struct msm_bus_vectors mdp_reg_bus_vectors[] = {
 	MDP_REG_BUS_VECTOR_ENTRY(0, 0),
diff --git a/rotator/sde_rotator_core.c b/rotator/sde_rotator_core.c
index 438699e..e7f72fd 100644
--- a/rotator/sde_rotator_core.c
+++ b/rotator/sde_rotator_core.c
@@ -86,7 +86,7 @@
 /* forward prototype */
 static int sde_rotator_update_perf(struct sde_rot_mgr *mgr);
 
-#ifdef CONFIG_QCOM_BUS_SCALING
+#if IS_ENABLED(CONFIG_QCOM_BUS_SCALING)
 static struct msm_bus_vectors rot_reg_bus_vectors[] = {
 	SDE_REG_BUS_VECTOR_ENTRY(0, 0),
 	SDE_REG_BUS_VECTOR_ENTRY(0, BUS_VOTE_19_MHZ),
@@ -2781,7 +2781,7 @@
 	.attrs = sde_rotator_fs_attrs
 };
 
-#ifdef CONFIG_QCOM_BUS_SCALING
+#if IS_ENABLED(CONFIG_QCOM_BUS_SCALING)
 static int sde_rotator_parse_dt_bus(struct sde_rot_mgr *mgr,
 	struct platform_device *dev)
 {
@@ -2935,7 +2935,7 @@
 	return rc;
 }
 
-#ifdef CONFIG_QCOM_BUS_SCALING
+#if IS_ENABLED(CONFIG_QCOM_BUS_SCALING)
 static void sde_rotator_bus_scale_unregister(struct sde_rot_mgr *mgr)
 {
 	SDEROT_DBG("unregister bus_hdl=%x, reg_bus_hdl=%x\n",