display: ct3a: support new panel framework

Bug: 327138338
Test: boot, suspend/resume
Change-Id: I10174d4c5a141bc2a5ea8ad5694029f98800c790
Signed-off-by: Weizhung Ding <[email protected]>
diff --git a/display/BUILD.bazel b/display/BUILD.bazel
index ee3ac17..7995659 100644
--- a/display/BUILD.bazel
+++ b/display/BUILD.bazel
@@ -17,9 +17,9 @@
     ],
     outs = [
         # keep sorted
-        "panel-google-ct3a.ko",
         "panel-google-ct3c.ko",
         "panel-google-ct3e.ko",
+        "panel-gs-ct3a.ko",
         "panel-gs-ct3b.ko",
         "panel-gs-ct3d.ko",
     ],
diff --git a/display/Kbuild b/display/Kbuild
index 3e6920d..962c70d 100644
--- a/display/Kbuild
+++ b/display/Kbuild
@@ -3,5 +3,6 @@
 obj-$(CONFIG_DRM_PANEL_GOOGLE_CT3A)		+= panel-google-ct3a.o
 obj-$(CONFIG_DRM_PANEL_GOOGLE_CT3C)		+= panel-google-ct3c.o
 obj-$(CONFIG_DRM_PANEL_GOOGLE_CT3E)		+= panel-google-ct3e.o
+obj-$(CONFIG_DRM_PANEL_GS_CT3A)		+= panel-gs-ct3a.o
 obj-$(CONFIG_DRM_PANEL_GS_CT3B)		+= panel-gs-ct3b.o
 obj-$(CONFIG_DRM_PANEL_GS_CT3D)		+= panel-gs-ct3d.o
diff --git a/display/Makefile b/display/Makefile
index d9ff9ba..77bc7fd 100644
--- a/display/Makefile
+++ b/display/Makefile
@@ -2,9 +2,9 @@
 
 KBASE_PATH_RELATIVE = $(M)
 
-KBUILD_OPTIONS += CONFIG_DRM_PANEL_GOOGLE_CT3A=m
 KBUILD_OPTIONS += CONFIG_DRM_PANEL_GOOGLE_CT3C=m
 KBUILD_OPTIONS += CONFIG_DRM_PANEL_GOOGLE_CT3E=m
+KBUILD_OPTIONS += CONFIG_DRM_PANEL_GS_CT3A=m
 KBUILD_OPTIONS += CONFIG_DRM_PANEL_GS_CT3B=m
 KBUILD_OPTIONS += CONFIG_DRM_PANEL_GS_CT3D=m
 
diff --git a/display/panel-gs-ct3a.c b/display/panel-gs-ct3a.c
new file mode 100644
index 0000000..a854e7b
--- /dev/null
+++ b/display/panel-gs-ct3a.c
@@ -0,0 +1,1710 @@
+/* SPDX-License-Identifier: MIT */
+
+#include <drm/drm_vblank.h>
+#include <drm/display/drm_dsc_helper.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/thermal.h>
+#include <video/mipi_display.h>
+
+#include "trace/panel_trace.h"
+
+#include "gs_panel/drm_panel_funcs_defaults.h"
+#include "gs_panel/gs_panel.h"
+#include "gs_panel/gs_panel_funcs_defaults.h"
+
+#define VLIN_CMD_SIZE 3
+
+/**
+ * one frame durtion(us) at 30Hz
+ */
+#define DELAY_30HZ_ONE_FRAME 34000
+
+static const u8 vlin_7v7[] = { 0x46, 0x23, 0x06 };
+static const u8 vlin_7v9[] = { 0x46, 0x23, 0x02 };
+static const u8 vgh_7v1[]  = { 0xF4, 0x15, 0x15, 0x15, 0x15 };
+static const u8 vgh_7v4[]  = { 0xF4, 0x18, 0x18, 0x18, 0x18 };
+static const u8 vreg_6v9[] = { 0xF4, 0x18 };
+
+/**
+ * struct ct3a_panel - panel specific runtime info
+ *
+ * This struct maintains ct3a panel specific runtime info, any fixed details about panel
+ * should most likely go into struct gs_panel_desc
+ */
+struct ct3a_panel {
+	/** @base: base panel struct */
+	struct gs_panel base;
+
+	/**
+	 * @auto_mode_vrefresh: indicates current minimum refresh rate while in auto mode,
+	 *			if 0 it means that auto mode is not enabled
+	 */
+	u32 auto_mode_vrefresh;
+	/** @force_changeable_te: force changeable TE (instead of fixed) during early exit */
+	bool force_changeable_te;
+	/** @force_changeable_te2: force changeable TE (instead of fixed) for monitoring refresh rate */
+	bool force_changeable_te2;
+	/**
+	 * @is_pixel_off: pixel-off command is sent to panel. Only sending normal-on or resetting
+	 *		  panel can recover to normal mode after entering pixel-off state.
+	 */
+	bool is_pixel_off;
+	/** @panel_voltage: panel default voltage
+	 *			1st byte: the fixed address 0x46.
+	 *			2nd byte: the fixed data 0x23.
+	 *			3th byte: read from panel.
+	 */
+	struct panel_voltage {
+		u8 vlin_default[VLIN_CMD_SIZE];
+	} panel_voltage;
+};
+
+#define to_spanel(ctx) container_of(ctx, struct ct3a_panel, base)
+
+static const struct drm_dsc_config pps_config = {
+		.line_buf_depth = 9,
+		.bits_per_component = 8,
+		.convert_rgb = true,
+		.slice_width = 1076,
+		.slice_height = 173,
+		.slice_count = 2,
+		.simple_422 = false,
+		.pic_width = 2152,
+		.pic_height = 2076,
+		.rc_tgt_offset_high = 3,
+		.rc_tgt_offset_low = 3,
+		.bits_per_pixel = 128,
+		.rc_edge_factor = 6,
+		.rc_quant_incr_limit1 = 11,
+		.rc_quant_incr_limit0 = 11,
+		.initial_xmit_delay = 512,
+		.initial_dec_delay = 930,
+		.block_pred_enable = true,
+		.first_line_bpg_offset = 15,
+		.initial_offset = 6144,
+		.rc_buf_thresh = {
+				14, 28, 42, 56,
+				70, 84, 98, 105,
+				112, 119, 121, 123,
+				125, 126
+		},
+		.rc_range_params = {
+				{.range_min_qp = 0, .range_max_qp = 4, .range_bpg_offset = 2},
+				{.range_min_qp = 0, .range_max_qp = 4, .range_bpg_offset = 0},
+				{.range_min_qp = 1, .range_max_qp = 5, .range_bpg_offset = 0},
+				{.range_min_qp = 1, .range_max_qp = 6, .range_bpg_offset = 62},
+				{.range_min_qp = 3, .range_max_qp = 7, .range_bpg_offset = 60},
+				{.range_min_qp = 3, .range_max_qp = 7, .range_bpg_offset = 58},
+				{.range_min_qp = 3, .range_max_qp = 7, .range_bpg_offset = 56},
+				{.range_min_qp = 3, .range_max_qp = 8, .range_bpg_offset = 56},
+				{.range_min_qp = 3, .range_max_qp = 9, .range_bpg_offset = 56},
+				{.range_min_qp = 3, .range_max_qp = 10, .range_bpg_offset = 54},
+				{.range_min_qp = 5, .range_max_qp = 10, .range_bpg_offset = 54},
+				{.range_min_qp = 5, .range_max_qp = 11, .range_bpg_offset = 52},
+				{.range_min_qp = 5, .range_max_qp = 11, .range_bpg_offset = 52},
+				{.range_min_qp = 9, .range_max_qp = 12, .range_bpg_offset = 52},
+				{.range_min_qp = 12, .range_max_qp = 13, .range_bpg_offset = 52}
+		},
+		.rc_model_size = 8192,
+		.flatness_min_qp = 3,
+		.flatness_max_qp = 12,
+		.initial_scale_value = 32,
+		.scale_decrement_interval = 14,
+		.scale_increment_interval = 4976,
+		.nfl_bpg_offset = 179,
+		.slice_bpg_offset = 75,
+		.final_offset = 4320,
+		.vbr_enable = false,
+		.slice_chunk_size = 1076,
+		.dsc_version_minor = 2,
+		.dsc_version_major = 1,
+		.native_422 = false,
+		.native_420 = false,
+		.second_line_bpg_offset = 0,
+		.nsl_bpg_offset = 0,
+		.second_line_offset_adj = 0,
+};
+
+#define CT3A_WRCTRLD_DIMMING_BIT    0x08
+#define CT3A_WRCTRLD_BCTRL_BIT      0x20
+
+#define CT3A_TE_USEC_120HZ_HS 320
+#define CT3A_TE_USEC_VRR_HS 320
+#define CT3A_TE_USEC_VRR_NS 640
+
+static const u8 unlock_cmd_f0[] = { 0xF0, 0x5A, 0x5A };
+static const u8 lock_cmd_f0[] = { 0xF0, 0xA5, 0xA5 };
+static const u8 ltps_update[] = { 0xF7, 0x0F };
+static const u8 pixel_off[] = { 0x22 };
+
+static const struct gs_dsi_cmd ct3a_lp_low_cmds[] = {
+	/* AOD Low Mode, 10nit */
+	GS_DSI_CMD(0x51, 0x03, 0x8A),
+};
+
+static const struct gs_dsi_cmd ct3a_lp_high_cmds[] = {
+	/* AOD Low Mode, 10nit */
+	GS_DSI_CMD(0x51, 0x07, 0xFF),
+};
+
+static const struct gs_binned_lp ct3a_binned_lp[] = {
+	/* low threshold 40 nits */
+	BINNED_LP_MODE("low", 689, ct3a_lp_low_cmds),
+	BINNED_LP_MODE("high", 2988, ct3a_lp_high_cmds)
+};
+
+static const struct gs_dsi_cmd ct3a_off_cmds[] = {
+	GS_DSI_CMD(MIPI_DCS_SET_DISPLAY_OFF),
+	GS_DSI_DELAY_CMD(120, MIPI_DCS_ENTER_SLEEP_MODE),
+};
+static DEFINE_GS_CMDSET(ct3a_off);
+
+static const struct gs_dsi_cmd ct3a_init_cmds[] = {
+	GS_DSI_CMD(MIPI_DCS_SET_TEAR_ON),
+
+	/* CASET: 2151 */
+	GS_DSI_CMD(MIPI_DCS_SET_COLUMN_ADDRESS, 0x00, 0x00, 0x08, 0x67),
+	/* PASET: 2075 */
+	GS_DSI_CMD(MIPI_DCS_SET_PAGE_ADDRESS, 0x00, 0x00, 0x08, 0x1B),
+
+	GS_DSI_CMDLIST(unlock_cmd_f0),
+	/* manual TE, fixed TE2 setting */
+	GS_DSI_REV_CMD(PANEL_REV_PROTO1, 0xB9, 0x00, 0x51, 0x00, 0x00),
+	GS_DSI_REV_CMD(PANEL_REV_PROTO1_1|PANEL_REV_PROTO1_2, 0xB9, 0x04, 0x51, 0x00, 0x00),
+	GS_DSI_REV_CMD(PANEL_REV_GE(PANEL_REV_EVT1), 0xB9, 0x04),
+	GS_DSI_CMD(0xB0, 0x00, 0x08, 0xB9),
+	GS_DSI_CMD(0xB9, 0x08, 0x1C, 0x00, 0x00, 0x08, 0x1C, 0x00, 0x00),
+	GS_DSI_REV_CMD(PANEL_REV_GE(PANEL_REV_EVT1), 0xB0, 0x00, 0x01, 0xB9),
+	GS_DSI_REV_CMD(PANEL_REV_GE(PANEL_REV_EVT1), 0xB9, 0x51),
+	GS_DSI_CMD(0xB0, 0x00, 0x22, 0xB9),
+	GS_DSI_CMD(0xB9, 0x00, 0x2F, 0x00, 0x82, 0x00, 0x2F, 0x00, 0x82),
+
+	/* early exit off */
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_PROTO1_2), 0xB0, 0x00, 0x01, 0xBD),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_PROTO1_2), 0xBD, 0x81),
+	GS_DSI_CMDLIST(ltps_update),
+
+	/* gamma improvement setting */
+	GS_DSI_CMD(0xB0, 0x00, 0x24, 0xF8),
+	GS_DSI_CMD(0xF8, 0x05),
+
+	/* HLPM transition preset*/
+	GS_DSI_REV_CMD(PANEL_REV_GE(PANEL_REV_EVT1), 0xB0, 0x00, 0x03, 0xBB),
+	GS_DSI_REV_CMD(PANEL_REV_GE(PANEL_REV_EVT1), 0xBB, 0x45, 0x0E),
+
+	GS_DSI_CMDLIST(lock_cmd_f0),
+};
+static DEFINE_GS_CMDSET(ct3a_init);
+
+static u8 ct3a_get_te2_option(struct gs_panel *ctx)
+{
+	struct ct3a_panel *spanel = to_spanel(ctx);
+
+	if (!ctx || !ctx->current_mode || spanel->force_changeable_te2)
+		return TEX_OPT_CHANGEABLE;
+
+	if (ctx->current_mode->gs_mode.is_lp_mode ||
+	    (test_bit(FEAT_EARLY_EXIT, ctx->sw_status.feat) &&
+		spanel->auto_mode_vrefresh < 30))
+		return TEX_OPT_FIXED;
+
+	return TEX_OPT_CHANGEABLE;
+}
+
+static void ct3a_update_te2(struct gs_panel *ctx)
+{
+	ctx->te2.option = ct3a_get_te2_option(ctx);
+
+	dev_dbg(ctx->dev,
+		"TE2 updated: op=%d, is_changeable=%d, idle=%d\n",
+		test_bit(FEAT_OP_NS, ctx->sw_status.feat), (ctx->te2.option == TEX_OPT_CHANGEABLE),
+		ctx->idle_data.panel_idle_vrefresh);
+}
+
+static inline bool is_auto_mode_allowed(struct gs_panel *ctx)
+{
+	/* don't want to enable auto mode/early exit during dimming on */
+	if (ctx->dimming_on)
+		return false;
+
+	if (ctx->idle_data.idle_delay_ms) {
+		const unsigned int delta_ms = gs_panel_get_idle_time_delta(ctx);
+
+		if (delta_ms < ctx->idle_data.idle_delay_ms)
+			return false;
+	}
+
+	return ctx->idle_data.panel_idle_enabled;
+}
+
+static u32 ct3a_get_min_idle_vrefresh(struct gs_panel *ctx,
+				     const struct gs_panel_mode *pmode)
+{
+	const int vrefresh = drm_mode_vrefresh(&pmode->mode);
+	int min_idle_vrefresh = ctx->min_vrefresh;
+
+	if ((min_idle_vrefresh < 0) || !is_auto_mode_allowed(ctx))
+		return 0;
+
+	if (min_idle_vrefresh <= 1)
+		min_idle_vrefresh = 1;
+	else if (min_idle_vrefresh <= 10)
+		min_idle_vrefresh = 10;
+	else if (min_idle_vrefresh <= 30)
+		min_idle_vrefresh = 30;
+	else
+		return 0;
+
+	if (min_idle_vrefresh >= vrefresh) {
+		dev_dbg(ctx->dev, "min idle vrefresh (%d) higher than target (%d)\n",
+				min_idle_vrefresh, vrefresh);
+		return 0;
+	}
+
+	dev_dbg(ctx->dev, "%s: min_idle_vrefresh %d\n", __func__, min_idle_vrefresh);
+
+	return min_idle_vrefresh;
+}
+
+/**
+ * ct3a_set_panel_feat - configure panel features
+ * @ctx: gs_panel struct
+ * @pmode: gs_panel_mode struct, target panel mode
+ * @idle_vrefresh: target vrefresh rate in auto mode, 0 if disabling auto mode
+ * @enforce: force to write all of registers even if no feature state changes
+ *
+ * Configure panel features based on the context.
+ */
+static void ct3a_set_panel_feat(struct gs_panel *ctx,
+	const struct gs_panel_mode *pmode, u32 idle_vrefresh, bool enforce)
+{
+	struct ct3a_panel *spanel = to_spanel(ctx);
+	struct device *dev = ctx->dev;
+	unsigned long *feat = ctx->sw_status.feat;
+	u32 vrefresh = drm_mode_vrefresh(&pmode->mode);
+	u32 te_freq = gs_drm_mode_te_freq(&pmode->mode);
+	bool is_vrr = gs_is_vrr_mode(pmode);
+	u8 val;
+	DECLARE_BITMAP(changed_feat, FEAT_MAX);
+
+#ifndef PANEL_FACTORY_BUILD
+	vrefresh = 1;
+	idle_vrefresh = 0;
+	set_bit(FEAT_EARLY_EXIT, feat);
+	clear_bit(FEAT_FRAME_AUTO, feat);
+	if (is_vrr) {
+		if (pmode->mode.flags & DRM_MODE_FLAG_NS)
+			set_bit(FEAT_OP_NS, feat);
+		else
+			clear_bit(FEAT_OP_NS, feat);
+	}
+#endif
+
+	if (enforce) {
+		bitmap_fill(changed_feat, FEAT_MAX);
+	} else {
+		bitmap_xor(changed_feat, feat, ctx->hw_status.feat, FEAT_MAX);
+		if (bitmap_empty(changed_feat, FEAT_MAX) &&
+			vrefresh == ctx->hw_status.vrefresh &&
+			idle_vrefresh == ctx->hw_status.idle_vrefresh &&
+			te_freq == ctx->hw_status.te_freq) {
+			dev_dbg(ctx->dev, "%s: no changes, skip update\n", __func__);
+			return;
+		}
+	}
+
+	dev_dbg(ctx->dev,
+		"op=%d ee=%d fi=%d fps=%u idle_fps=%u te=%u vrr=%d\n",
+		test_bit(FEAT_OP_NS, feat), test_bit(FEAT_EARLY_EXIT, feat),
+		test_bit(FEAT_FRAME_AUTO, feat),
+		vrefresh, idle_vrefresh, te_freq, is_vrr);
+
+	GS_DCS_BUF_ADD_CMDLIST(dev, unlock_cmd_f0);
+	/* TE setting */
+	if (test_bit(FEAT_EARLY_EXIT, changed_feat) ||
+		test_bit(FEAT_OP_NS, changed_feat) || ctx->hw_status.te_freq != te_freq) {
+		if (test_bit(FEAT_EARLY_EXIT, feat) && !spanel->force_changeable_te) {
+			if (is_vrr && te_freq == 240) {
+				/* 240Hz multi TE */
+				GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x08, 0xB9);
+				if (test_bit(FEAT_OP_NS, feat))
+					GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x08, 0x1C, 0x00, 0x00, 0x01,
+							0xC6, 0x00, 0x01);
+				else
+					GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x08, 0x1C, 0x00, 0x00, 0x03,
+							0xE0, 0x00, 0x01);
+				GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x61);
+			} else {
+				if (ctx->panel_rev < PANEL_REV_EVT1)
+					GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x51, 0x51, 0x00, 0x00);
+				else
+					GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x51);
+
+				GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x02, 0xB9);
+				/* Fixed TE */
+				if (test_bit(FEAT_OP_NS, feat) || te_freq != 60) {
+					GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x00);
+				} else {
+					GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x01);
+				}
+			}
+		} else {
+			/* Changeable TE */
+			if (ctx->panel_rev == PANEL_REV_PROTO1)
+				GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x00, 0x51, 0x00, 0x00);
+			else if (ctx->panel_rev == PANEL_REV_PROTO1_1 ||
+					    ctx->panel_rev == PANEL_REV_PROTO1_2)
+				GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x04, 0x51, 0x00, 0x00);
+			else
+				GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x04);
+		}
+	}
+
+	/*
+	 * Early-exit: enable or disable
+	 */
+	if (is_vrr) {
+		GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x01, 0xBD);
+		GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x41);
+	} else {
+		GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x01, 0xBD);
+		val = test_bit(FEAT_EARLY_EXIT, feat) ? 0x01 : 0x81;
+		GS_DCS_BUF_ADD_CMD(dev, 0xBD, val);
+	}
+
+	/*
+	 * Frequency setting: FI, frequency, idle frequency
+	 *
+	 * Description: this sequence possibly overrides some configs early-exit
+	 * and operation set, depending on FI mode.
+	 */
+	if (test_bit(FEAT_FRAME_AUTO, feat)) {
+		if (test_bit(FEAT_OP_NS, feat))
+			GS_DCS_BUF_ADD_CMD(dev, 0x60, 0x18);
+		else
+			GS_DCS_BUF_ADD_CMD(dev, 0x60, 0x00);
+		/* frame insertion on */
+		GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0xE3);
+		/* target frequency */
+		GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x13, 0xBD);
+		if (test_bit(FEAT_OP_NS, feat)) {
+			if (idle_vrefresh == 30) {
+				val = 0x04;
+			} else if (idle_vrefresh == 10) {
+				val = 0x14;
+			} else {
+				if (idle_vrefresh != 1)
+					dev_warn(ctx->dev, "%s: unsupported target freq %d (ns)\n",
+						 __func__, idle_vrefresh);
+				/* 1Hz */
+				val = 0xEC;
+			}
+			GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x00, val);
+		} else {
+			if (idle_vrefresh == 60) {
+				val = 0x02;
+			} else if (idle_vrefresh == 30) {
+				val = 0x06;
+			} else if (idle_vrefresh == 10) {
+				val = 0x16;
+			} else {
+				if (idle_vrefresh != 1)
+					dev_warn(ctx->dev, "%s: unsupported target freq %d (hs)\n",
+						 __func__, idle_vrefresh);
+				/* 1Hz */
+				val = 0xEC;
+			}
+			GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x00, val);
+		}
+		/* step setting */
+		GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x9E, 0xBD);
+		if (test_bit(FEAT_OP_NS, feat))
+			GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x00, 0x00, 0x00, 0x04, 0x00, 0x14, 0x00, 0x00);
+		else
+			GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x00, 0x00, 0x00, 0x02, 0x00, 0x06, 0x00, 0x16);
+
+		GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0xAE, 0xBD);
+		if (test_bit(FEAT_OP_NS, feat))
+			GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x00, 0x02, 0x00, 0x00);
+		else
+			GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x00, 0x00, 0x02, 0x00);
+
+		if (ctx->panel_rev >= PANEL_REV_PROTO1_2) {
+			if (test_bit(FEAT_OP_NS, feat)) {
+				if (ctx->panel_rev == PANEL_REV_PROTO1_2)
+					GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x85);
+				else
+					GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x85, 0xBD);
+			} else {
+				if (ctx->panel_rev == PANEL_REV_PROTO1_2)
+					GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x83);
+				else
+					GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x83, 0xBD);
+			}
+			GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x00);
+		}
+	} else { /* manual */
+		if (!is_vrr) {
+			GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0xE1);
+		}
+		if (test_bit(FEAT_OP_NS, feat)) {
+			if(is_vrr) {
+				GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x83, 0xBD);
+				GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x00);
+				GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x61);
+			}
+
+			GS_DCS_BUF_ADD_CMD(dev, 0xF2, 0x01);
+
+			if (vrefresh == 1) {
+				if (ctx->panel_rev >= PANEL_REV_PROTO1_2)
+					val = 0x1E;
+				else
+					val = 0x1D;
+			} else if (vrefresh == 10) {
+				val = 0x1C;
+			} else if (vrefresh == 30) {
+				val = 0x19;
+			} else {
+				if (vrefresh != 60)
+					dev_warn(ctx->dev,
+						 "%s: unsupported manual freq %d (ns)\n",
+						 __func__, vrefresh);
+				/* 60Hz */
+				val = 0x18;
+			}
+		} else {
+			GS_DCS_BUF_ADD_CMD(dev, 0xF2, 0x01);
+
+			if (vrefresh == 1) {
+				val = 0x06;
+			} else if (vrefresh == 10) {
+				val = 0x05;
+			} else if (vrefresh == 30) {
+				if (ctx->panel_rev < PANEL_REV_EVT1)
+					val = 0x02;
+				else
+					val = 0x03;
+			} else if (vrefresh == 60) {
+				if (ctx->panel_rev < PANEL_REV_EVT1)
+					val = 0x01;
+				else
+					val = 0x02;
+			} else {
+				if (vrefresh != 120)
+					dev_warn(ctx->dev,
+						 "%s: unsupported manual freq %d (hs)\n",
+						 __func__, vrefresh);
+				/* 120Hz */
+				val = 0x00;
+			}
+		}
+		/* TODO: b/321871919 - send in the same batch */
+		if(is_vrr)
+			GS_DCS_BUF_ADD_CMD_AND_FLUSH(dev, 0x60, val);
+		else
+			GS_DCS_BUF_ADD_CMD(dev, 0x60, val);
+	}
+	/* TODO: b/321871919 - send in the same batch */
+	if(is_vrr)
+		GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, ltps_update);
+	else
+		GS_DCS_BUF_ADD_CMDLIST(dev, ltps_update);
+	GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, lock_cmd_f0);
+
+	ctx->hw_status.vrefresh = vrefresh;
+	ctx->hw_status.idle_vrefresh = idle_vrefresh;
+	ctx->hw_status.te_freq = te_freq;
+	bitmap_copy(ctx->hw_status.feat, feat, FEAT_MAX);
+}
+
+/**
+ * ct3a_update_panel_feat - configure panel features with current refresh rate
+ * @ctx: gs_panel struct
+ * @enforce: force to write all of registers even if no feature state changes
+ *
+ * Configure panel features based on the context without changing current refresh rate
+ * and idle setting.
+ */
+static void ct3a_update_panel_feat(struct gs_panel *ctx, bool enforce)
+{
+	const struct gs_panel_mode *pmode = ctx->current_mode;
+	struct ct3a_panel *spanel = to_spanel(ctx);
+	u32 idle_vrefresh = spanel->auto_mode_vrefresh;
+
+	ct3a_set_panel_feat(ctx, pmode, idle_vrefresh, enforce);
+}
+
+static void ct3a_update_refresh_mode(struct gs_panel *ctx,
+				const struct gs_panel_mode *pmode,
+					const u32 idle_vrefresh)
+{
+	struct ct3a_panel *spanel = to_spanel(ctx);
+
+	dev_info(ctx->dev, "%s: mode: %s set idle_vrefresh: %u\n", __func__,
+		pmode->mode.name, idle_vrefresh);
+
+	spanel->auto_mode_vrefresh = idle_vrefresh;
+	/*
+	 * Note: when mode is explicitly set, panel performs early exit to get out
+	 * of idle at next vsync, and will not back to idle until not seeing new
+	 * frame traffic for a while. If idle_vrefresh != 0, try best to guess what
+	 * panel_idle_vrefresh will be soon, and ct3a_update_idle_state() in
+	 * new frame commit will correct it if the guess is wrong.
+	 */
+	ctx->idle_data.panel_idle_vrefresh = idle_vrefresh;
+	ct3a_set_panel_feat(ctx, pmode, idle_vrefresh, false);
+	notify_panel_mode_changed(ctx);
+
+	dev_dbg(ctx->dev, "%s: display state is notified\n", __func__);
+}
+
+static void ct3a_change_frequency(struct gs_panel *ctx,
+				const struct gs_panel_mode *pmode)
+{
+	u32 vrefresh = drm_mode_vrefresh(&pmode->mode);
+	u32 idle_vrefresh = 0;
+
+	if (!ctx)
+		return;
+
+	if (vrefresh > ctx->op_hz) {
+		dev_err(ctx->dev, "invalid freq setting: op_hz=%u, vrefresh=%u\n",
+				ctx->op_hz, vrefresh);
+		return;
+	}
+
+	if (pmode->idle_mode == GIDLE_MODE_ON_INACTIVITY)
+		idle_vrefresh = ct3a_get_min_idle_vrefresh(ctx, pmode);
+
+	ct3a_update_refresh_mode(ctx, pmode, idle_vrefresh);
+
+	dev_dbg(ctx->dev, "%s: change to %uHz\n", __func__, vrefresh);
+}
+
+static void ct3a_panel_idle_notification(struct gs_panel *ctx,
+				u32 display_id, u32 vrefresh, u32 idle_te_vrefresh)
+{
+	char event_string[64];
+	char *envp[] = { event_string, NULL };
+	struct drm_device *dev = ctx->bridge.dev;
+
+	if (!dev) {
+		dev_warn(ctx->dev, "%s: drm_device is null\n", __func__);
+	} else {
+		snprintf(event_string, sizeof(event_string),
+			"PANEL_IDLE_ENTER=%u,%u,%u", display_id, vrefresh, idle_te_vrefresh);
+		kobject_uevent_env(&dev->primary->kdev->kobj, KOBJ_CHANGE, envp);
+	}
+}
+
+static void ct3a_wait_one_vblank(struct gs_panel *ctx)
+{
+	struct drm_crtc *crtc = NULL;
+
+	if (ctx->gs_connector->base.state)
+		crtc = ctx->gs_connector->base.state->crtc;
+
+	PANEL_ATRACE_BEGIN(__func__);
+	if (crtc) {
+		int ret = drm_crtc_vblank_get(crtc);
+
+		if (!ret) {
+			drm_crtc_wait_one_vblank(crtc);
+			drm_crtc_vblank_put(crtc);
+		} else {
+			usleep_range(8350, 8500);
+		}
+	} else {
+		usleep_range(8350, 8500);
+	}
+	PANEL_ATRACE_END(__func__);
+}
+
+static bool ct3a_set_self_refresh(struct gs_panel *ctx, bool enable)
+{
+	const struct gs_panel_mode *pmode = ctx->current_mode;
+	struct ct3a_panel *spanel = to_spanel(ctx);
+	u32 idle_vrefresh;
+
+	dev_dbg(ctx->dev, "%s: %d\n", __func__, enable);
+
+	if (unlikely(!pmode))
+		return false;
+
+	/* self refresh is not supported in lp mode since that always makes use of early exit */
+	if (pmode->gs_mode.is_lp_mode) {
+		/* set 1Hz while self refresh is active, otherwise clear it */
+		ctx->idle_data.panel_idle_vrefresh = enable ? 1 : 0;
+		notify_panel_mode_changed(ctx);
+		return false;
+	}
+
+	idle_vrefresh = ct3a_get_min_idle_vrefresh(ctx, pmode);
+
+	if (pmode->idle_mode != GIDLE_MODE_ON_SELF_REFRESH) {
+		/*
+		 * if idle mode is on inactivity, may need to update the target fps for auto mode,
+		 * or switch to manual mode if idle should be disabled (idle_vrefresh=0)
+		 */
+		if ((pmode->idle_mode == GIDLE_MODE_ON_INACTIVITY) &&
+			(spanel->auto_mode_vrefresh != idle_vrefresh)) {
+			ct3a_update_refresh_mode(ctx, pmode, idle_vrefresh);
+			return true;
+		}
+		return false;
+	}
+
+	if (!enable)
+		idle_vrefresh = 0;
+
+	/* if there's no change in idle state then skip cmds */
+	if (ctx->idle_data.panel_idle_vrefresh == idle_vrefresh)
+		return false;
+
+	PANEL_ATRACE_BEGIN(__func__);
+	ct3a_update_refresh_mode(ctx, pmode, idle_vrefresh);
+
+	if (idle_vrefresh) {
+		const int vrefresh = drm_mode_vrefresh(&pmode->mode);
+
+		ct3a_panel_idle_notification(ctx, 0, vrefresh, 120);
+	} else if (ctx->idle_data.panel_need_handle_idle_exit) {
+		/*
+		 * after exit idle mode with fixed TE at non-120hz, TE may still keep at 120hz.
+		 * If any layer that already be assigned to DPU that can't be handled at 120hz,
+		 * panel_need_handle_idle_exit will be set then we need to wait one vblank to
+		 * avoid underrun issue.
+		 */
+		dev_dbg(ctx->dev, "wait one vblank after exit idle\n");
+		ct3a_wait_one_vblank(ctx);
+	}
+
+	PANEL_ATRACE_END(__func__);
+
+	return true;
+}
+
+static int ct3a_atomic_check(struct gs_panel *ctx, struct drm_atomic_state *state)
+{
+	struct drm_connector *conn = &ctx->gs_connector->base;
+	struct drm_connector_state *new_conn_state = drm_atomic_get_new_connector_state(state, conn);
+	struct drm_crtc_state *old_crtc_state, *new_crtc_state;
+	struct ct3a_panel *spanel = to_spanel(ctx);
+
+	if (!ctx->current_mode || drm_mode_vrefresh(&ctx->current_mode->mode) == 120 ||
+	    !new_conn_state || !new_conn_state->crtc)
+		return 0;
+
+	new_crtc_state = drm_atomic_get_new_crtc_state(state, new_conn_state->crtc);
+	old_crtc_state = drm_atomic_get_old_crtc_state(state, new_conn_state->crtc);
+	if (!old_crtc_state || !new_crtc_state || !new_crtc_state->active)
+		return 0;
+
+	if ((spanel->auto_mode_vrefresh && old_crtc_state->self_refresh_active) ||
+	    !drm_atomic_crtc_effectively_active(old_crtc_state)) {
+		struct drm_display_mode *mode = &new_crtc_state->adjusted_mode;
+
+		/* set clock to max refresh rate on self refresh exit or resume due to early exit */
+		mode->clock = mode->htotal * mode->vtotal * 120 / 1000;
+
+		if (mode->clock != new_crtc_state->mode.clock) {
+			new_crtc_state->mode_changed = true;
+			dev_dbg(ctx->dev, "raise mode (%s) clock to 120hz on %s\n",
+				mode->name,
+				old_crtc_state->self_refresh_active ? "self refresh exit" : "resume");
+		}
+	} else if (old_crtc_state->active_changed &&
+		   (old_crtc_state->adjusted_mode.clock != old_crtc_state->mode.clock)) {
+		/* clock hacked in last commit due to self refresh exit or resume, undo that */
+		new_crtc_state->mode_changed = true;
+		new_crtc_state->adjusted_mode.clock = new_crtc_state->mode.clock;
+		dev_dbg(ctx->dev, "restore mode (%s) clock after self refresh exit or resume\n",
+			new_crtc_state->mode.name);
+	}
+
+	return 0;
+}
+
+static void ct3a_update_wrctrld(struct gs_panel *ctx)
+{
+	struct device *dev = ctx->dev;
+	u8 val = CT3A_WRCTRLD_BCTRL_BIT;
+
+	if (ctx->dimming_on)
+		val |= CT3A_WRCTRLD_DIMMING_BIT;
+
+	dev_dbg(dev,
+		"%s(wrctrld:0x%x, hbm=%d, dimming=%d)\n",
+		__func__, val, GS_IS_HBM_ON(ctx->hbm_mode), ctx->dimming_on);
+
+	GS_DCS_BUF_ADD_CMD_AND_FLUSH(dev, MIPI_DCS_WRITE_CONTROL_DISPLAY, val);
+}
+
+static void ct3a_set_lp_mode(struct gs_panel *ctx, const struct gs_panel_mode *pmode)
+ {
+	struct device *dev = ctx->dev;
+
+	dev_dbg(ctx->dev, "%s\n", __func__);
+
+	PANEL_ATRACE_BEGIN(__func__);
+
+	GS_DCS_BUF_ADD_CMDLIST(dev, unlock_cmd_f0);
+
+	if (ctx->panel_rev == PANEL_REV_PROTO1_1) {
+		GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x02, 0xAE, 0xCB);
+		GS_DCS_BUF_ADD_CMD(dev, 0xCB, 0x11, 0x70);
+		GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x05, 0xBD);
+		GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x03);
+		GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x52, 0x64);
+		GS_DCS_BUF_ADD_CMD(dev, 0x64, 0x01, 0x03, 0x0A, 0x03);
+		GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x5E, 0xBD);
+		GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x00, 0x00, 0x00, 0x08, 0x00, 0x74);
+	}
+
+	GS_DCS_BUF_ADD_CMD(dev, 0x53, 0x24);
+	GS_DCS_BUF_ADD_CMD(dev, 0x60, 0x00, 0x00);
+
+	/* Fixed TE */
+	if (ctx->panel_rev < PANEL_REV_EVT1)
+		GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x51, 0x51, 0x00, 0x00);
+	else
+		GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x51);
+
+	/* Enable early exit */
+	GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x01, 0xBD);
+	GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x01);
+
+	/* Auto frame insertion: 1Hz */
+	GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0xE5);
+	GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x19, 0xBD);
+	GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x00, 0x74);
+	GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0xB8, 0xBD);
+	GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x00, 0x00, 0x00, 0x08, 0x00, 0x74);
+	GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0xC8, 0xBD);
+	GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x02, 0x01);
+	GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x85, 0xBD);
+	GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x01);
+	GS_DCS_BUF_ADD_CMDLIST(dev, ltps_update);
+	GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, lock_cmd_f0);
+
+	ctx->hw_status.vrefresh = 30;
+	ctx->hw_status.te_freq = 30;
+
+	PANEL_ATRACE_END(__func__);
+
+	dev_info(ctx->dev, "enter %dhz LP mode\n", drm_mode_vrefresh(&pmode->mode));
+}
+
+static void ct3a_set_nolp_mode(struct gs_panel *ctx,
+			      const struct gs_panel_mode *pmode)
+{
+	struct ct3a_panel *spanel = to_spanel(ctx);
+	struct device *dev = ctx->dev;
+	u32 idle_vrefresh = spanel->auto_mode_vrefresh;
+
+	if (!gs_is_panel_active(ctx))
+		return;
+
+	PANEL_ATRACE_BEGIN(__func__);
+
+	GS_DCS_BUF_ADD_CMDLIST(dev, unlock_cmd_f0);
+	/* manual mode */
+	GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0xE1);
+
+	/* Disable early exit */
+	GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x01, 0xBD);
+	GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x81);
+
+	/* changeable TE*/
+	if (ctx->panel_rev == PANEL_REV_PROTO1)
+		GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x00, 0x51, 0x00, 0x00);
+	else if (ctx->panel_rev == PANEL_REV_PROTO1_1 || ctx->panel_rev == PANEL_REV_PROTO1_2)
+		GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x04, 0x51, 0x00, 0x00);
+	else
+		GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x04);
+
+	/* AoD off */
+	if (ctx->panel_rev == PANEL_REV_PROTO1_1) {
+		GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x52, 0x64);
+		GS_DCS_BUF_ADD_CMD(dev, 0x64, 0x00);
+	}
+	ct3a_update_wrctrld(ctx);
+	GS_DCS_BUF_ADD_CMDLIST(dev, ltps_update);
+	GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, lock_cmd_f0);
+	usleep_range(DELAY_30HZ_ONE_FRAME, DELAY_30HZ_ONE_FRAME + 10);
+	ct3a_set_panel_feat(ctx, pmode, idle_vrefresh, true);
+	ct3a_change_frequency(ctx, pmode);
+
+	PANEL_ATRACE_END(__func__);
+
+	dev_info(ctx->dev, "exit LP mode\n");
+}
+
+static int ct3a_set_op_hz(struct gs_panel *ctx, unsigned int hz)
+{
+	const unsigned int vrefresh = drm_mode_vrefresh(&ctx->current_mode->mode);
+
+	if (gs_is_vrr_mode(ctx->current_mode)) {
+		dev_warn(ctx->dev, "set_op_hz: should be set by mode switch when in vrr mode\n");
+		return -EINVAL;
+	}
+
+	if ((vrefresh > hz) || ((hz != 60) && (hz != 120))) {
+		dev_err(ctx->dev, "invalid op_hz=%u for vrefresh=%u\n",
+			hz, vrefresh);
+		return -EINVAL;
+	}
+
+	PANEL_ATRACE_BEGIN(__func__);
+
+	ctx->op_hz = hz;
+	if (hz == 60)
+		set_bit(FEAT_OP_NS, ctx->sw_status.feat);
+	else
+		clear_bit(FEAT_OP_NS, ctx->sw_status.feat);
+
+	if (gs_is_panel_active(ctx))
+		ct3a_update_panel_feat(ctx, false);
+
+	dev_info(ctx->dev, "%s op_hz at %d\n",
+		gs_is_panel_active(ctx) ? "set" : "cache", hz);
+
+	if (hz == 120) {
+		/*
+		 * We may transfer the frame for the first TE after switching from
+		 * NS to HS mode. The DDIC read speed will change from 60Hz to 120Hz,
+		 * but the DPU write speed will remain the same. In this case,
+		 * underruns would happen. Waiting for an extra vblank here so that
+		 * the frame can be postponed to the next TE to avoid the noises.
+		 */
+		dev_dbg(ctx->dev, "wait one vblank after NS to HS\n");
+		ct3a_wait_one_vblank(ctx);
+	}
+
+	PANEL_ATRACE_END(__func__);
+
+	return 0;
+}
+
+static int ct3a_set_brightness(struct gs_panel *ctx, u16 br)
+{
+	struct device *dev = ctx->dev;
+	u16 brightness;
+	struct ct3a_panel *spanel = to_spanel(ctx);
+
+	if (ctx->current_mode->gs_mode.is_lp_mode) {
+
+		/* don't stay at pixel-off state in AOD, or black screen is possibly seen */
+		if (spanel->is_pixel_off) {
+			GS_DCS_BUF_ADD_CMD_AND_FLUSH(dev, MIPI_DCS_ENTER_NORMAL_MODE);
+			spanel->is_pixel_off = false;
+		}
+
+		if (gs_panel_has_func(ctx, set_binned_lp))
+			ctx->desc->gs_panel_func->set_binned_lp(ctx, br);
+		return 0;
+	}
+
+	/* Use pixel off command instead of setting DBV 0 */
+	if (!br) {
+		if (!spanel->is_pixel_off) {
+			GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, pixel_off);
+			spanel->is_pixel_off = true;
+			dev_dbg(ctx->dev, "%s: pixel off instead of dbv 0\n", __func__);
+		}
+		return 0;
+	} else if (br && spanel->is_pixel_off) {
+		GS_DCS_BUF_ADD_CMD_AND_FLUSH(dev, MIPI_DCS_ENTER_NORMAL_MODE);
+		spanel->is_pixel_off = false;
+	}
+
+	brightness = swab16(br);
+
+	return gs_dcs_set_brightness(ctx, brightness);
+}
+
+static void ct3a_set_default_voltage(struct gs_panel *ctx, bool enable)
+{
+	struct ct3a_panel *spanel = to_spanel(ctx);
+	struct device *dev = ctx->dev;
+	const u8 *vlin_default = spanel->panel_voltage.vlin_default;
+
+	if(ctx->panel_rev < PANEL_REV_EVT1)
+		return;
+	dev_dbg(dev, "%s enable = %d\n", __func__, enable);
+
+	GS_DCS_BUF_ADD_CMDLIST(dev, unlock_cmd_f0);
+
+	if (enable) {
+		/* 3.4 VLIN / VGH / VREG Return Setting */
+		GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x03, 0x48);
+		GS_DCS_BUF_ADD_CMD(dev, 0x48, 0x08);
+		GS_DCS_BUF_ADD_CMD(dev, 0x48, 0xF1);
+		GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x01, 0x46);
+		if(ctx->panel_rev < PANEL_REV_EVT1_1)
+			GS_DCS_BUF_ADD_CMDLIST(dev, vlin_7v7);
+		else
+			GS_DCS_BUF_ADD_CMD(dev, 0x46, 0x23, vlin_default[2]);
+		GS_DCS_BUF_ADD_CMD(dev, 0x46, 0x00);
+		GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x0F, 0xF4);
+		GS_DCS_BUF_ADD_CMDLIST(dev, vgh_7v1);
+		GS_DCS_BUF_ADD_CMD(dev, 0x48, 0x80);
+		GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x32, 0xF4);
+		GS_DCS_BUF_ADD_CMDLIST(dev, vreg_6v9);
+	} else {
+		/* 3.3 VGH / VLIN / VREG Setting */
+		GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x03, 0x48);
+		GS_DCS_BUF_ADD_CMD(dev, 0x48, 0x08);
+		GS_DCS_BUF_ADD_CMD(dev, 0x48, 0xF1);
+		GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x01, 0x46);
+		GS_DCS_BUF_ADD_CMDLIST(dev, vlin_7v9);
+		GS_DCS_BUF_ADD_CMD(dev, 0x46, 0x00);
+		GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x0F, 0xF4);
+		GS_DCS_BUF_ADD_CMDLIST(dev, vgh_7v4);
+		GS_DCS_BUF_ADD_CMD(dev, 0x48, 0x80);
+		GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x32, 0xF4);
+		GS_DCS_BUF_ADD_CMDLIST(dev, vreg_6v9);
+	}
+
+	GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, lock_cmd_f0);
+}
+
+static int ct3a_disable(struct drm_panel *panel)
+{
+	struct gs_panel *ctx = container_of(panel, struct gs_panel, base);
+	int ret;
+
+	dev_info(ctx->dev, "%s\n", __func__);
+
+	/* skip disable sequence if going through RR */
+	if (ctx->mode_in_progress == MODE_RR_IN_PROGRESS) {
+		dev_dbg(ctx->dev, "%s: RRS in progress, skip\n", __func__);
+		return 0;
+	}
+
+	ct3a_set_default_voltage(ctx, false);
+	ret = gs_panel_disable(panel);
+	if (ret)
+		return ret;
+
+	/* panel register state gets reset after disabling hardware */
+	bitmap_clear(ctx->hw_status.feat, 0, FEAT_MAX);
+	ctx->hw_status.vrefresh = 60;
+	ctx->hw_status.te_freq = 60;
+	ctx->hw_status.idle_vrefresh = 0;
+
+	return 0;
+}
+
+/*
+ * 120hz auto mode takes at least 2 frames to start lowering refresh rate in addition to
+ * time to next vblank. Use just over 2 frames time to consider worst case scenario
+ */
+#define EARLY_EXIT_THRESHOLD_US 17000
+
+/**
+ * ct3a_update_idle_state - update panel auto frame insertion state
+ * @ctx: panel struct
+ *
+ * - update timestamp of switching to manual mode in case its been a while since the
+ *   last frame update and auto mode may have started to lower refresh rate.
+ * - trigger early exit by command if it's changeable TE and no switching delay, which
+ *   could result in fast 120 Hz boost and seeing 120 Hz TE earlier, otherwise disable
+ *   auto refresh mode to avoid lowering frequency too fast.
+ */
+static void ct3a_update_idle_state(struct gs_panel *ctx)
+{
+	struct device *dev = ctx->dev;
+	s64 delta_us;
+	struct ct3a_panel *spanel = to_spanel(ctx);
+
+	ctx->idle_data.panel_idle_vrefresh = 0;
+	if (!test_bit(FEAT_FRAME_AUTO, ctx->sw_status.feat))
+		return;
+
+	delta_us = ktime_us_delta(ktime_get(), ctx->timestamps.last_commit_ts);
+	if (delta_us < EARLY_EXIT_THRESHOLD_US) {
+		dev_dbg(dev, "skip early exit. %lldus since last commit\n",
+			delta_us);
+		return;
+	}
+
+	/* triggering early exit causes a switch to 120hz */
+	ctx->timestamps.last_mode_set_ts = ktime_get();
+
+	PANEL_ATRACE_BEGIN(__func__);
+
+	if (!ctx->idle_data.idle_delay_ms && spanel->force_changeable_te) {
+		dev_dbg(dev, "sending early exit out cmd\n");
+		GS_DCS_BUF_ADD_CMDLIST(dev, unlock_cmd_f0);
+		GS_DCS_BUF_ADD_CMDLIST(dev, ltps_update);
+		GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, lock_cmd_f0);
+	} else {
+		/* turn off auto mode to prevent panel from lowering frequency too fast */
+		ct3a_update_refresh_mode(ctx, ctx->current_mode, 0);
+	}
+
+	PANEL_ATRACE_END(__func__);
+}
+
+static void ct3a_commit_done(struct gs_panel *ctx)
+{
+	if (ctx->current_mode->gs_mode.is_lp_mode)
+		return;
+
+	ct3a_update_idle_state(ctx);
+}
+
+static void ct3a_set_hbm_mode(struct gs_panel *ctx, enum gs_hbm_mode mode)
+{
+	struct device *dev = ctx->dev;
+	const bool irc_update = (GS_IS_HBM_ON_IRC_OFF(ctx->hbm_mode) != GS_IS_HBM_ON_IRC_OFF(mode));
+
+	if (mode == ctx->hbm_mode)
+		return;
+
+	ctx->hbm_mode = mode;
+
+	if ((ctx->panel_rev >= PANEL_REV_PROTO1_2) && irc_update) {
+		GS_DCS_BUF_ADD_CMDLIST(dev, unlock_cmd_f0);
+		GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0xAF, 0x93);
+		GS_DCS_BUF_ADD_CMD(dev, 0x93, GS_IS_HBM_ON_IRC_OFF(mode) ? 0x0B : 0x2B);
+		GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, unlock_cmd_f0);
+	}
+
+	dev_info(dev, "hbm_on=%d hbm_ircoff=%d\n", GS_IS_HBM_ON(ctx->hbm_mode),
+		 GS_IS_HBM_ON_IRC_OFF(ctx->hbm_mode));
+}
+
+static void ct3a_set_dimming_on(struct gs_panel *gs_panel,
+				bool dimming_on)
+{
+	const struct gs_panel_mode *pmode = gs_panel->current_mode;
+	gs_panel->dimming_on = dimming_on;
+
+	if (pmode->gs_mode.is_lp_mode) {
+		dev_warn(gs_panel->dev, "in lp mode; skip updating dimming_on\n");
+		return;
+	}
+
+	ct3a_update_wrctrld(gs_panel);
+}
+
+static void ct3a_mode_set(struct gs_panel *ctx, const struct gs_panel_mode *pmode)
+{
+	ct3a_change_frequency(ctx, pmode);
+}
+
+static bool ct3a_is_mode_seamless(const struct gs_panel *ctx,
+			const struct gs_panel_mode *pmode)
+{
+	const struct drm_display_mode *c = &ctx->current_mode->mode;
+	const struct drm_display_mode *n = &pmode->mode;
+
+	/* seamless mode set can happen if active region resolution is same */
+	return (c->vdisplay == n->vdisplay) && (c->hdisplay == n->hdisplay);
+}
+
+static void ct3a_debugfs_init(struct drm_panel *panel, struct dentry *root)
+{
+#ifdef CONFIG_DEBUG_FS
+	struct gs_panel *ctx = container_of(panel, struct gs_panel, base);
+	struct dentry *panel_root, *csroot;
+
+	if (!ctx)
+		return;
+
+	panel_root = debugfs_lookup("panel", root);
+	if (!panel_root)
+		return;
+
+	csroot = debugfs_lookup("cmdsets", panel_root);
+	if (!csroot) {
+		goto panel_out;
+	}
+
+	gs_panel_debugfs_create_cmdset(csroot, &ct3a_init_cmdset, "init");
+	dput(csroot);
+panel_out:
+	dput(panel_root);
+#endif
+}
+
+static void ct3a_get_panel_rev(struct gs_panel *ctx, u32 id)
+{
+	/* extract command 0xDB */
+	u8 build_code = (id & 0xFF00) >> 8;
+	u8 rev = ((build_code & 0xE0) >> 3) | ((build_code & 0x0C) >> 2);
+
+	switch (rev) {
+	case 0x00:
+		ctx->panel_rev = PANEL_REV_PROTO1;
+		break;
+	case 0x01:
+		ctx->panel_rev = PANEL_REV_PROTO1_1;
+		break;
+	case 0x02:
+		ctx->panel_rev = PANEL_REV_PROTO1_2;
+		break;
+	case 0x0C:
+		ctx->panel_rev = PANEL_REV_EVT1;
+		break;
+	case 0x0E:
+		ctx->panel_rev = PANEL_REV_EVT1_1;
+		break;
+	case 0x0F:
+		ctx->panel_rev = PANEL_REV_EVT1_2;
+		break;
+	case 0x11:
+		ctx->panel_rev = PANEL_REV_DVT1;
+		break;
+	case 0x12:
+		ctx->panel_rev = PANEL_REV_DVT1_1;
+		break;
+	case 0x14:
+		ctx->panel_rev = PANEL_REV_PVT;
+		break;
+	default:
+		dev_warn(ctx->dev,
+			 "unknown rev from panel (0x%x), default to latest\n",
+			 rev);
+		ctx->panel_rev = PANEL_REV_LATEST;
+		return;
+	}
+
+	dev_info(ctx->dev, "panel_rev: 0x%x\n", ctx->panel_rev);
+}
+
+static int ct3a_read_default_voltage(struct gs_panel *ctx)
+{
+	struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
+	struct ct3a_panel *spanel = to_spanel(ctx);
+	struct device *dev = ctx->dev;
+	u8 *vlin = spanel->panel_voltage.vlin_default;
+	int ret;
+
+	GS_DCS_BUF_ADD_CMDLIST(dev, unlock_cmd_f0);
+	GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x46, 0x48);
+	vlin[0] = 0x46;
+	vlin[1] = 0x23;
+	ret = mipi_dsi_dcs_read(dsi, 0x48, vlin + 2, VLIN_CMD_SIZE - 2);
+	if (ret == (VLIN_CMD_SIZE-2)) {
+		dev_info(dev, "%s: vlin: 0x%x\n", __func__, vlin[2]);
+	} else {
+		vlin[2] = 0x06; /* use vlin 7.7v as default */
+		dev_err(dev, "unable to read vlin\n");
+	}
+	GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, lock_cmd_f0);
+
+	return 0;
+}
+
+static int ct3a_read_id(struct gs_panel *ctx)
+{
+	int ret = gs_panel_read_slsi_ddic_id(ctx);
+	if(ret)
+		return ret;
+
+	dev_dbg(ctx->dev, "%s: 0x%x\n", __func__, ctx->panel_rev);
+	if(ctx->panel_rev < PANEL_REV_EVT1_1)
+		return 0;
+
+	ct3a_read_default_voltage(ctx);
+	return 0;
+}
+
+static int ct3a_enable(struct drm_panel *panel)
+{
+	struct gs_panel *ctx = container_of(panel, struct gs_panel, base);
+	const struct gs_panel_mode *pmode = ctx->current_mode;
+	struct device *dev = ctx->dev;
+
+	if (!pmode) {
+		dev_err(ctx->dev, "no current mode set\n");
+		return -EINVAL;
+	}
+
+	dev_info(ctx->dev, "%s +\n", __func__);
+
+	PANEL_ATRACE_BEGIN(__func__);
+
+	gs_panel_reset_helper(ctx);
+
+	/* TODO: b/277158216, Use 0x9E for PPS setting */
+	/* DSC related configuration */
+	gs_dcs_write_dsc_config(dev, &pps_config);
+	GS_DCS_WRITE_CMD(dev, 0x9D, 0x01); /* DSC Enable */
+
+	if(ctx->panel_rev < PANEL_REV_EVT1) {
+		GS_DCS_WRITE_DELAY_CMD(dev, 120, MIPI_DCS_EXIT_SLEEP_MODE);
+	}
+	else {
+		GS_DCS_WRITE_DELAY_CMD(dev,  10, MIPI_DCS_EXIT_SLEEP_MODE);
+		ct3a_set_default_voltage(ctx, false);
+		usleep_range(110000, 110010);
+	}
+
+	gs_panel_send_cmdset(ctx, &ct3a_init_cmdset);
+
+	ct3a_update_panel_feat(ctx, true);
+
+	/* dimming and HBM */
+	ct3a_update_wrctrld(ctx);
+
+	/* frequency */
+	ct3a_change_frequency(ctx, pmode);
+
+	if (pmode->gs_mode.is_lp_mode)
+		ct3a_set_lp_mode(ctx, pmode);
+
+	GS_DCS_WRITE_CMD(dev, MIPI_DCS_SET_DISPLAY_ON);
+
+	ct3a_set_default_voltage(ctx, true);
+	dev_info(ctx->dev, "%s -\n", __func__);
+
+	PANEL_ATRACE_END(__func__);
+
+	return 0;
+}
+
+static int spanel_get_brightness(struct thermal_zone_device *tzd, int *temp)
+{
+	struct ct3a_panel *spanel;
+
+	if (tzd == NULL)
+		return -EINVAL;
+
+	spanel = tzd->devdata;
+
+	if (spanel && spanel->base.bl) {
+		mutex_lock(&spanel->base.bl_state_lock);
+		*temp = (spanel->base.bl->props.state & BL_STATE_STANDBY) ?
+					0 : spanel->base.bl->props.brightness;
+		mutex_unlock(&spanel->base.bl_state_lock);
+	} else {
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static struct thermal_zone_device_ops spanel_tzd_ops = {
+	.get_temp = spanel_get_brightness,
+};
+
+static void ct3a_panel_init(struct gs_panel *ctx)
+{
+	const struct gs_panel_mode *pmode = ctx->current_mode;
+
+#ifdef PANEL_FACTORY_BUILD
+	ctx->idle_data.panel_idle_enabled = false;
+#endif
+
+	/* re-init panel to decouple bootloader settings */
+	if (pmode)
+		ct3a_set_panel_feat(ctx, pmode, 0, true);
+}
+
+static int ct3a_panel_probe(struct mipi_dsi_device *dsi)
+{
+	struct ct3a_panel *spanel;
+	struct gs_panel *ctx;
+	int ret;
+
+	spanel = devm_kzalloc(&dsi->dev, sizeof(*spanel), GFP_KERNEL);
+	if (!spanel)
+		return -ENOMEM;
+
+	ctx = &spanel->base;
+
+	ctx->thermal = devm_kzalloc(&dsi->dev, sizeof(*ctx->thermal), GFP_KERNEL);
+	if (!ctx->thermal) {
+		devm_kfree(&dsi->dev, spanel);
+		return -ENOMEM;
+	}
+
+	spanel->base.op_hz = 120;
+	spanel->is_pixel_off = false;
+	ctx->hw_status.vrefresh = 60;
+	ctx->hw_status.te_freq = 60;
+	clear_bit(FEAT_ZA, ctx->hw_status.feat);
+
+	ctx->thermal->tz = thermal_zone_device_register("inner_brightness",
+				0, 0, spanel, &spanel_tzd_ops, NULL, 0, 0);
+	if (IS_ERR(ctx->thermal->tz))
+		dev_err(ctx->dev, "failed to register inner"
+			" display thermal zone: %ld", PTR_ERR(ctx->thermal->tz));
+
+	ret = thermal_zone_device_enable(ctx->thermal->tz);
+	if (ret) {
+		dev_err(ctx->dev, "failed to enable inner"
+					" display thermal zone ret=%d", ret);
+		thermal_zone_device_unregister(ctx->thermal->tz);
+	}
+
+	return gs_dsi_panel_common_init(dsi, ctx);
+}
+
+static const struct gs_display_underrun_param underrun_param = {
+	.te_idle_us = 350,
+	.te_var = 1,
+};
+
+static const u32 ct3a_bl_range[] = {
+	94, 180, 270, 360, 3307
+};
+
+static const u16 WIDTH_MM = 147, HEIGHT_MM = 141;
+
+#define CT3A_DSC {\
+	.enabled = true,\
+	.dsc_count = 1,\
+	.cfg = &pps_config,\
+}
+
+static const struct gs_panel_mode_array ct3a_modes = {
+	.num_modes = 5,
+	.modes = {
+/* MRR modes */
+#ifdef PANEL_FACTORY_BUILD
+		{
+			.mode = {
+				.name = "2152x2076@1:1",
+				DRM_MODE_TIMING(1, 2152, 80, 30, 38, 2076, 6, 4, 14),
+				.width_mm = WIDTH_MM,
+				.height_mm = HEIGHT_MM,
+			},
+			.gs_mode = {
+				.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
+				.vblank_usec = 120,
+				.bpc = 8,
+				.dsc = CT3A_DSC,
+				.underrun_param = &underrun_param,
+			},
+			.idle_mode = GIDLE_MODE_UNSUPPORTED,
+		},
+		{
+			.mode = {
+				.name = "2152x2076@10:10",
+				DRM_MODE_TIMING(10, 2152, 80, 30, 38, 2076, 6, 4, 14),
+				.width_mm = WIDTH_MM,
+				.height_mm = HEIGHT_MM,
+			},
+			.gs_mode = {
+				.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
+				.vblank_usec = 120,
+				.bpc = 8,
+				.dsc = CT3A_DSC,
+				.underrun_param = &underrun_param,
+			},
+			.idle_mode = GIDLE_MODE_UNSUPPORTED,
+		},
+		{
+			.mode = {
+				.name = "2152x2076@30:30",
+				DRM_MODE_TIMING(30, 2152, 80, 30, 38, 2076, 6, 4, 14),
+				.width_mm = WIDTH_MM,
+				.height_mm = HEIGHT_MM,
+			},
+			.gs_mode = {
+				.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
+				.vblank_usec = 120,
+				.bpc = 8,
+				.dsc = CT3A_DSC,
+				.underrun_param = &underrun_param,
+			},
+			.idle_mode = GIDLE_MODE_UNSUPPORTED,
+		},
+#endif
+		{
+			.mode = {
+				.name = "2152x2076@60:60",
+				DRM_MODE_TIMING(60, 2152, 80, 30, 38, 2076, 6, 4, 14),
+				.width_mm = WIDTH_MM,
+				.height_mm = HEIGHT_MM,
+				.flags = DRM_MODE_FLAG_BTS_OP_RATE,
+				.type = DRM_MODE_TYPE_PREFERRED,
+			},
+			.gs_mode = {
+				.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
+				.vblank_usec = 120,
+				.bpc = 8,
+				.dsc = CT3A_DSC,
+				.underrun_param = &underrun_param,
+			},
+			.idle_mode = GIDLE_MODE_UNSUPPORTED,
+		},
+		{
+			.mode = {
+				.name = "2152x2076@120:120",
+				DRM_MODE_TIMING(120, 2152, 80, 30, 38, 2076, 6, 4, 14),
+				.flags = DRM_MODE_FLAG_BTS_OP_RATE,
+				.width_mm = WIDTH_MM,
+				.height_mm = HEIGHT_MM,
+			},
+			.gs_mode = {
+				.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
+				.vblank_usec = 120,
+				.te_usec = CT3A_TE_USEC_120HZ_HS,
+				.bpc = 8,
+				.dsc = CT3A_DSC,
+				.underrun_param = &underrun_param,
+			},
+			.idle_mode = GIDLE_MODE_UNSUPPORTED,
+		},
+#ifndef PANEL_FACTORY_BUILD
+		/* VRR modes */
+		{
+			.mode = {
+				.name = "2152x2076@120:240",
+				DRM_MODE_TIMING(120, 2152, 80, 30, 38, 2076, 6, 4, 14),
+				.flags = DRM_MODE_FLAG_TE_FREQ_X2,
+				.type = DRM_MODE_TYPE_VRR | DRM_MODE_TYPE_PREFERRED,
+				.width_mm = WIDTH_MM,
+				.height_mm = HEIGHT_MM,
+			},
+			.gs_mode = {
+				.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
+				.vblank_usec = 120,
+				.te_usec = CT3A_TE_USEC_VRR_HS,
+				.bpc = 8,
+				.dsc = CT3A_DSC,
+				.underrun_param = &underrun_param,
+			},
+			.idle_mode = GIDLE_MODE_UNSUPPORTED,
+		},
+		{
+			.mode = {
+				.name = "2152x2076@120:120",
+				DRM_MODE_TIMING(120, 2152, 80, 30, 38, 2076, 6, 4, 14),
+				.flags = DRM_MODE_FLAG_TE_FREQ_X1,
+				.type = DRM_MODE_TYPE_VRR,
+				.width_mm = WIDTH_MM,
+				.height_mm = HEIGHT_MM,
+			},
+			.gs_mode = {
+				.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
+				.vblank_usec = 120,
+				.te_usec = CT3A_TE_USEC_VRR_HS,
+				.bpc = 8,
+				.dsc = CT3A_DSC,
+				.underrun_param = &underrun_param,
+			},
+			.idle_mode = GIDLE_MODE_UNSUPPORTED,
+		},
+		{
+			.mode = {
+				.name = "2152x2076@60:240",
+				DRM_MODE_TIMING(60, 2152, 80, 30, 38, 2076, 6, 4, 14),
+				.flags = DRM_MODE_FLAG_TE_FREQ_X4 | DRM_MODE_FLAG_NS,
+				.type = DRM_MODE_TYPE_VRR,
+				.width_mm = WIDTH_MM,
+				.height_mm = HEIGHT_MM,
+			},
+			.gs_mode = {
+				.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
+				.vblank_usec = 120,
+				.te_usec = CT3A_TE_USEC_VRR_NS,
+				.bpc = 8,
+				.dsc = CT3A_DSC,
+				.underrun_param = &underrun_param,
+			},
+			.idle_mode = GIDLE_MODE_UNSUPPORTED,
+		},
+#endif
+	},/* modes */
+};
+
+static const struct gs_panel_mode_array ct3a_lp_mode = {
+	.num_modes = 1,
+	.modes = {
+		{
+			.mode = {
+				.name = "2152x2076@30:30",
+				DRM_MODE_TIMING(30, 2152, 80, 32, 36, 2076, 6, 4, 14),
+				.width_mm = WIDTH_MM,
+				.height_mm = HEIGHT_MM,
+			},
+			.gs_mode = {
+				.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
+				.vblank_usec = 120,
+				.bpc = 8,
+				.dsc = CT3A_DSC,
+				.underrun_param = &underrun_param,
+				.is_lp_mode = true,
+			}
+		},
+	},
+};
+
+static const struct drm_panel_funcs ct3a_drm_funcs = {
+	.disable = ct3a_disable,
+	.unprepare = gs_panel_unprepare,
+	.prepare = gs_panel_prepare,
+	.enable = ct3a_enable,
+	.get_modes = gs_panel_get_modes,
+	.debugfs_init = ct3a_debugfs_init,
+};
+
+
+static const struct gs_brightness_configuration ct3a_btr_configs[] = {
+	{
+		.panel_rev = PANEL_REV_EVT1_1 | PANEL_REV_LATEST,
+		.default_brightness = 1223,    /* 140 nits brightness */
+		.brt_capability = {
+			.normal = {
+				.nits = {
+					.min = 2,
+					.max = 1000,
+				},
+				.level = {
+					.min = 157,
+					.max = 2988,
+				},
+				.percentage = {
+					.min = 0,
+					.max = 63,
+				},
+			},
+			.hbm = {
+				.nits = {
+					.min = 1000,
+					.max = 1600,
+				},
+				.level = {
+					.min = 2989,
+					.max = 3701,
+				},
+				.percentage = {
+					.min = 63,
+					.max = 100,
+				},
+			},
+		},
+	},
+	{
+		.panel_rev = PANEL_REV_PROTO1 | PANEL_REV_PROTO1_1 | PANEL_REV_PROTO1_2 | PANEL_REV_EVT1,
+		.default_brightness = 1353,    /* 140 nits brightness */
+		.brt_capability = {
+			.normal = {
+				.nits = {
+					.min = 2,
+					.max = 1000,
+				},
+				.level = {
+					.min = 174,
+					.max = 3307,
+				},
+				.percentage = {
+					.min = 0,
+					.max = 63,
+				},
+			},
+			.hbm = {
+				.nits = {
+					.min = 1000,
+					.max = 1600,
+				},
+				.level = {
+					.min = 3308,
+					.max = 4095,
+				},
+				.percentage = {
+					.min = 63,
+					.max = 100,
+				},
+			},
+		},
+	},
+};
+
+static struct gs_panel_brightness_desc ct3a_brightness_desc = {
+	.max_luminance = 10000000,
+	.max_avg_luminance = 1200000,
+	.min_luminance = 5,
+};
+
+static int ct3a_panel_config(struct gs_panel *ctx)
+{
+	/* b/300383405 Currently, we can't support multiple
+	 *  displays in `display_layout_configuration.xml`.
+	 */
+	/* gs_panel_model_init(ctx, PROJECT, 0); */
+
+	return gs_panel_update_brightness_desc(&ct3a_brightness_desc, ct3a_btr_configs,
+						ARRAY_SIZE(ct3a_btr_configs), ctx->panel_rev);
+}
+
+
+static const struct gs_panel_funcs ct3a_gs_funcs = {
+	.set_brightness = ct3a_set_brightness,
+	.set_lp_mode = ct3a_set_lp_mode,
+	.set_nolp_mode = ct3a_set_nolp_mode,
+	.set_binned_lp = gs_panel_set_binned_lp_helper,
+	.set_dimming = ct3a_set_dimming_on,
+	.set_hbm_mode = ct3a_set_hbm_mode,
+	.update_te2 = ct3a_update_te2,
+	.commit_done = ct3a_commit_done,
+	.atomic_check = ct3a_atomic_check,
+	.set_self_refresh = ct3a_set_self_refresh,
+	.set_op_hz = ct3a_set_op_hz,
+	.is_mode_seamless = ct3a_is_mode_seamless,
+	.mode_set = ct3a_mode_set,
+	.get_panel_rev = ct3a_get_panel_rev,
+	.read_id = ct3a_read_id,
+	.panel_init = ct3a_panel_init,
+	.panel_config = ct3a_panel_config,
+};
+
+static struct gs_panel_reg_ctrl_desc ct3a_reg_ctrl_desc = {
+	.reg_ctrl_enable = {
+		{PANEL_REG_ID_VDDI, 1},
+		{PANEL_REG_ID_VCI,  0},
+		{PANEL_REG_ID_VDDR, 10},
+	},
+	.reg_ctrl_disable = {
+		{PANEL_REG_ID_VDDR, 0},
+		{PANEL_REG_ID_VDDI, 1},
+		{PANEL_REG_ID_VCI, 15},
+	},
+};
+
+struct gs_panel_desc gs_ct3a = {
+	.data_lane_cnt = 4,
+	.brightness_desc = &ct3a_brightness_desc,
+	.reg_ctrl_desc = &ct3a_reg_ctrl_desc,
+	/* supported HDR format bitmask : 1(DOLBY_VISION), 2(HDR10), 3(HLG) */
+	.hdr_formats = BIT(2) | BIT(3),
+	.bl_range = ct3a_bl_range,
+	.modes = &ct3a_modes,
+	.lp_modes = &ct3a_lp_mode,
+	.binned_lp = ct3a_binned_lp,
+	.num_binned_lp = ARRAY_SIZE(ct3a_binned_lp),
+	.is_idle_supported = true,
+	.off_cmdset = &ct3a_off_cmdset,
+	.panel_func = &ct3a_drm_funcs,
+	.gs_panel_func = &ct3a_gs_funcs,
+	.reset_timing_ms = { -1, 1, 10 },
+};
+
+static const struct of_device_id gs_panel_of_match[] = {
+	{ .compatible = "google,gs-ct3a", .data = &gs_ct3a },
+	{ },
+};
+
+static struct mipi_dsi_driver gs_panel_driver = {
+	.probe = ct3a_panel_probe,
+	.remove = gs_dsi_panel_common_remove,
+	.driver = {
+		.name = "panel-gs-ct3a",
+		.of_match_table = gs_panel_of_match,
+	},
+};
+module_mipi_dsi_driver(gs_panel_driver);
+
+MODULE_AUTHOR("Weizhung Ding <[email protected]>");
+MODULE_DESCRIPTION("MIPI-DSI based Google ct3a panel driver");
+MODULE_LICENSE("Dual MIT/GPL");
diff --git a/vendor_kernel_boot_modules.comet b/vendor_kernel_boot_modules.comet
index 89578c3..34c3fb4 100644
--- a/vendor_kernel_boot_modules.comet
+++ b/vendor_kernel_boot_modules.comet
@@ -2,8 +2,8 @@
 # comet specific modules loaded during first stage init from vendor_kernel_boot
 # (platform common modules are from vendor_kernel_boot_modules.zuma)
 #
-panel-google-ct3a.ko
 panel-google-ct3c.ko
 panel-google-ct3e.ko
+panel-gs-ct3a.ko
 panel-gs-ct3b.ko
 panel-gs-ct3d.ko