Browse Source

Merge "disp: msm: restrict AVR_STEP based on panel requirement"

qctecmdr 4 năm trước cách đây
mục cha
commit
ad4c8dc646

+ 28 - 6
msm/dsi/dsi_display.c

@@ -6609,11 +6609,9 @@ int dsi_display_get_info(struct drm_connector *connector,
 	info->height_mm = phy_props.panel_height_mm;
 	info->max_width = 1920;
 	info->max_height = 1080;
-	info->qsync_min_fps =
-		display->panel->qsync_caps.qsync_min_fps;
-	info->has_qsync_min_fps_list =
-		(display->panel->qsync_caps.qsync_min_fps_list_len > 0) ?
-		true : false;
+	info->qsync_min_fps = display->panel->qsync_caps.qsync_min_fps;
+	info->has_qsync_min_fps_list = (display->panel->qsync_caps.qsync_min_fps_list_len > 0);
+	info->has_avr_step_req = (display->panel->avr_caps.avr_step_fps_list_len > 0);
 	info->poms_align_vsync = display->panel->poms_align_vsync;
 
 	switch (display->panel->panel_mode) {
@@ -7080,6 +7078,31 @@ int dsi_display_get_qsync_min_fps(void *display_dsi, u32 mode_fps)
 	return -EINVAL;
 }
 
+int dsi_display_get_avr_step_req_fps(void *display_dsi, u32 mode_fps)
+{
+	struct dsi_display *display = (struct dsi_display *)display_dsi;
+	struct dsi_panel *panel;
+	u32 i, step = 0;
+
+	if (!display || !display->panel)
+		return -EINVAL;
+
+	panel = display->panel;
+
+	/* support a single fixed rate, or rate corresponding to dfps list entry */
+	if (panel->avr_caps.avr_step_fps_list_len == 1) {
+		step = panel->avr_caps.avr_step_fps_list[0];
+	} else if (panel->avr_caps.avr_step_fps_list_len > 1) {
+		for (i = 0; i < panel->dfps_caps.dfps_list_len; i++) {
+			if (panel->dfps_caps.dfps_list[i] == mode_fps)
+				step = panel->avr_caps.avr_step_fps_list[i];
+		}
+	}
+
+	DSI_DEBUG("mode_fps %u, avr_step fps %u\n", mode_fps, step);
+	return step;
+}
+
 static bool dsi_display_match_timings(const struct dsi_display_mode *mode1,
 		struct dsi_display_mode *mode2)
 {
@@ -7095,7 +7118,6 @@ static bool dsi_display_match_timings(const struct dsi_display_mode *mode1,
 		mode1->timing.refresh_rate == mode2->timing.refresh_rate;
 }
 
-
 static bool dsi_display_mode_match(const struct dsi_display_mode *mode1,
 		struct dsi_display_mode *mode2, unsigned int match_flags)
 {

+ 9 - 1
msm/dsi/dsi_display.h

@@ -419,10 +419,18 @@ int dsi_display_get_default_lms(void *dsi_display, u32 *num_lm);
  * @display:            Handle to display.
  * @mode_fps:           Fps value of current mode
  *
- * Return: error code.
+ * Return: Qsync min fps rate or -ve error code.
  */
 int dsi_display_get_qsync_min_fps(void *dsi_display, u32 mode_fps);
 
+/**
+ * dsi_display_get_avr_step_req_fps() - get avr step rate for given fps
+ * @display:            Handle to display.
+ * @mode_fps:           Fps value of current mode
+ *
+ * Return: AVR step rate or -ve error code.
+ */
+int dsi_display_get_avr_step_req_fps(void *dsi_display, u32 mode_fps);
 
 /**
  * dsi_display_find_mode() - retrieve cached DSI mode given relevant params

+ 24 - 0
msm/dsi/dsi_drm.c

@@ -626,6 +626,28 @@ static const struct drm_bridge_funcs dsi_bridge_ops = {
 	.mode_set     = dsi_bridge_mode_set,
 };
 
+int dsi_conn_set_avr_step_info(struct dsi_panel *panel, void *info)
+{
+	u32 i;
+	int idx = 0;
+	size_t buff_sz = PAGE_SIZE;
+	char *buff;
+
+	buff = kzalloc(buff_sz, GFP_KERNEL);
+	if (!buff)
+		return -ENOMEM;
+
+	for (i = 0; i < panel->avr_caps.avr_step_fps_list_len && (idx < (buff_sz - 1)); i++)
+		idx += scnprintf(&buff[idx], buff_sz - idx, "%u@%u ",
+				 panel->avr_caps.avr_step_fps_list[i],
+				 panel->dfps_caps.dfps_list[i]);
+
+	sde_kms_info_add_keystr(info, "avr step requirement", buff);
+	kfree(buff);
+
+	return 0;
+}
+
 int dsi_conn_set_info_blob(struct drm_connector *connector,
 		void *info, void *display, struct msm_mode_info *mode_info)
 {
@@ -674,6 +696,8 @@ int dsi_conn_set_info_blob(struct drm_connector *connector,
 	switch (panel->panel_mode) {
 	case DSI_OP_VIDEO_MODE:
 		sde_kms_info_add_keystr(info, "panel mode", "video");
+		if (panel->avr_caps.avr_step_fps_list_len)
+			dsi_conn_set_avr_step_info(panel, info);
 		break;
 	case DSI_OP_CMD_MODE:
 		sde_kms_info_add_keystr(info, "panel mode", "command");

+ 37 - 0
msm/dsi/dsi_panel.c

@@ -1239,6 +1239,38 @@ error:
 	return rc;
 }
 
+static int dsi_panel_parse_avr_caps(struct dsi_panel *panel,
+				     struct device_node *of_node)
+{
+	struct dsi_avr_capabilities *avr_caps = &panel->avr_caps;
+	struct dsi_parser_utils *utils = &panel->utils;
+	int val, rc = 0;
+
+	val = utils->count_u32_elems(utils->data, "qcom,dsi-qsync-avr-step-list");
+	if (val <= 0) {
+		DSI_DEBUG("[%s] optional avr step list not defined, val:%d\n", panel->name, val);
+		return rc;
+	} else if (val > 1 && val != panel->dfps_caps.dfps_list_len) {
+		DSI_ERR("[%s] avr step list size %d not same as dfps list %d\n",
+				val, panel->dfps_caps.dfps_list_len);
+		return -EINVAL;
+	}
+
+	avr_caps->avr_step_fps_list = kcalloc(val, sizeof(u32), GFP_KERNEL);
+	if (!avr_caps->avr_step_fps_list)
+		return -ENOMEM;
+
+	rc = utils->read_u32_array(utils->data, "qcom,dsi-qsync-avr-step-list",
+			avr_caps->avr_step_fps_list, val);
+	if (rc) {
+		kfree(avr_caps->avr_step_fps_list);
+		return rc;
+	}
+
+	avr_caps->avr_step_fps_list_len = val;
+	return rc;
+}
+
 static int dsi_panel_parse_qsync_caps(struct dsi_panel *panel,
 				     struct device_node *of_node)
 {
@@ -3509,6 +3541,10 @@ struct dsi_panel *dsi_panel_get(struct device *parent,
 	if (rc)
 		DSI_DEBUG("failed to parse qsync features, rc=%d\n", rc);
 
+	rc = dsi_panel_parse_avr_caps(panel, of_node);
+	if (rc)
+		DSI_ERR("failed to parse AVR features, rc=%d\n", rc);
+
 	rc = dsi_panel_parse_dyn_clk_caps(panel);
 	if (rc)
 		DSI_ERR("failed to parse dynamic clk config, rc=%d\n", rc);
@@ -3594,6 +3630,7 @@ void dsi_panel_put(struct dsi_panel *panel)
 	/* free resources allocated for ESD check */
 	dsi_panel_esd_config_deinit(&panel->esd_config);
 
+	kfree(panel->avr_caps.avr_step_fps_list);
 	kfree(panel);
 }
 

+ 6 - 0
msm/dsi/dsi_panel.h

@@ -95,6 +95,11 @@ struct dsi_qsync_capabilities {
 	int qsync_min_fps_list_len;
 };
 
+struct dsi_avr_capabilities {
+	u32 *avr_step_fps_list;
+	u32 avr_step_fps_list_len;
+};
+
 struct dsi_dyn_clk_caps {
 	bool dyn_clk_support;
 	enum dsi_dyn_clk_feature_type type;
@@ -247,6 +252,7 @@ struct dsi_panel {
 	bool panel_initialized;
 	bool te_using_watchdog_timer;
 	struct dsi_qsync_capabilities qsync_caps;
+	struct dsi_avr_capabilities avr_caps;
 
 	char dce_pps_cmd[DSI_CMD_PPS_SIZE];
 	enum dsi_dms_mode dms_mode;

+ 3 - 0
msm/msm_drv.h

@@ -223,6 +223,7 @@ enum msm_mdp_conn_property {
 	CONNECTOR_PROP_QSYNC_MODE,
 	CONNECTOR_PROP_CMD_FRAME_TRIGGER_MODE,
 	CONNECTOR_PROP_SET_PANEL_MODE,
+	CONNECTOR_PROP_AVR_STEP,
 
 	/* total # of properties */
 	CONNECTOR_PROP_COUNT
@@ -766,6 +767,7 @@ struct msm_resource_caps_info {
  * @roi_caps:           Region of interest capability info
  * @qsync_min_fps	Minimum fps supported by Qsync feature
  * @has_qsync_min_fps_list True if dsi-supported-qsync-min-fps-list exits
+ * @has_avr_step_req    Panel has defined requirement for AVR steps
  * @te_source		vsync source pin information
  * @dsc_count:		max dsc hw blocks used by display (only available
  *			for dsi display)
@@ -795,6 +797,7 @@ struct msm_display_info {
 
 	uint32_t qsync_min_fps;
 	bool has_qsync_min_fps_list;
+	bool has_avr_step_req;
 
 	uint32_t te_source;
 

+ 18 - 2
msm/sde/sde_connector.c

@@ -697,7 +697,7 @@ void sde_connector_set_qsync_params(struct drm_connector *connector)
 {
 	struct sde_connector *c_conn;
 	struct sde_connector_state *c_state;
-	u32 qsync_propval = 0;
+	u32 qsync_propval = 0, step_val = 0;
 	bool prop_dirty;
 
 	if (!connector)
@@ -720,6 +720,17 @@ void sde_connector_set_qsync_params(struct drm_connector *connector)
 			c_conn->qsync_mode = qsync_propval;
 		}
 	}
+
+	prop_dirty = msm_property_is_dirty(&c_conn->property_info, &c_state->property_state,
+					CONNECTOR_PROP_AVR_STEP);
+	if (prop_dirty) {
+		step_val = sde_connector_get_property(c_conn->base.state, CONNECTOR_PROP_AVR_STEP);
+		if (step_val != c_conn->avr_step) {
+			SDE_DEBUG("updated avr step %d -> %d\n", c_conn->avr_step, step_val);
+			c_conn->qsync_updated = true;
+			c_conn->avr_step = step_val;
+		}
+	}
 }
 
 void sde_connector_complete_qsync_commit(struct drm_connector *conn,
@@ -2870,11 +2881,16 @@ static int _sde_connector_install_properties(struct drm_device *dev,
 			CONNECTOR_PROP_AUTOREFRESH);
 
 	if (connector_type == DRM_MODE_CONNECTOR_DSI) {
-		if (sde_kms->catalog->has_qsync && display_info->qsync_min_fps)
+		if (sde_kms->catalog->has_qsync && display_info->qsync_min_fps) {
 			msm_property_install_enum(&c_conn->property_info,
 					"qsync_mode", 0, 0, e_qsync_mode,
 					ARRAY_SIZE(e_qsync_mode), 0,
 					CONNECTOR_PROP_QSYNC_MODE);
+			if (sde_kms->catalog->has_avr_step)
+				msm_property_install_range(&c_conn->property_info,
+						"avr_step", 0x0, 0, U32_MAX, 0,
+						CONNECTOR_PROP_AVR_STEP);
+		}
 
 		if (display_info->capabilities & MSM_DISPLAY_CAP_CMD_MODE)
 			msm_property_install_enum(&c_conn->property_info,

+ 17 - 0
msm/sde/sde_connector.h

@@ -389,6 +389,14 @@ struct sde_connector_ops {
 	 * Returns: Qsync min fps value on success
 	 */
 	int (*get_qsync_min_fps)(void *display, u32 mode_fps);
+
+	/**
+	 * get_avr_step_req - Get the required avr_step for given fps rate
+	 * @display: Pointer to private display structure
+	 * @mode_fps: Fps value in dfps list
+	 * Returns: AVR step fps value on success
+	 */
+	int (*get_avr_step_req)(void *display, u32 mode_fps);
 };
 
 /**
@@ -484,6 +492,7 @@ struct sde_connector_dyn_hdr_metadata {
  * @allow_bl_update: Flag to indicate if BL update is allowed currently or not
  * @qsync_mode: Cached Qsync mode, 0=disabled, 1=continuous mode
  * @qsync_updated: Qsync settings were updated
+ * @avr_step: fps rate for fixed steps in AVR mode; 0 means step is disabled
  * @colorspace_updated: Colorspace property was updated
  * @last_cmd_tx_sts: status of the last command transfer
  * @hdr_capable: external hdr support present
@@ -553,6 +562,7 @@ struct sde_connector {
 	u8 hdr_plus_app_ver;
 	u32 qsync_mode;
 	bool qsync_updated;
+	u32 avr_step;
 
 	bool colorspace_updated;
 
@@ -604,6 +614,13 @@ struct sde_connector {
 #define sde_connector_get_qsync_mode(C) \
 	((C) ? to_sde_connector((C))->qsync_mode : 0)
 
+/**
+ * sde_connector_get_avr_step - get sde connector's avr_step
+ * @C: Pointer to drm connector structure
+ * Returns: Current cached avr_step value for given connector
+ */
+#define sde_connector_get_avr_step(C) ((C) ? to_sde_connector((C))->avr_step : 0)
+
 /**
  * sde_connector_get_propinfo - get sde connector's property info pointer
  * @C: Pointer to drm connector structure

+ 117 - 65
msm/sde/sde_encoder.c

@@ -995,6 +995,120 @@ static int _sde_encoder_atomic_check_reserve(struct drm_encoder *drm_enc,
 	return ret;
 }
 
+static void _sde_encoder_get_qsync_fps_callback(
+		struct drm_encoder *drm_enc, u32 *qsync_fps, u32 vrr_fps)
+{
+	struct msm_display_info *disp_info;
+	struct sde_encoder_virt *sde_enc;
+	int rc = 0;
+	struct sde_connector *sde_conn;
+
+	if (!qsync_fps)
+		return;
+
+	*qsync_fps = 0;
+	if (!drm_enc) {
+		SDE_ERROR("invalid drm encoder\n");
+		return;
+	}
+
+	sde_enc = to_sde_encoder_virt(drm_enc);
+	disp_info = &sde_enc->disp_info;
+	*qsync_fps = disp_info->qsync_min_fps;
+
+	if (!disp_info->has_qsync_min_fps_list) {
+		return;
+	} else if (!sde_enc->cur_master || !(disp_info->capabilities & MSM_DISPLAY_CAP_VID_MODE)) {
+		SDE_ERROR("invalid qsync settings %d\n", !sde_enc->cur_master);
+		return;
+	}
+
+	/*
+	 * If "dsi-supported-qsync-min-fps-list" is defined, get
+	 * the qsync min fps corresponding to the fps in dfps list
+	 */
+	sde_conn = to_sde_connector(sde_enc->cur_master->connector);
+	if (sde_conn->ops.get_qsync_min_fps)
+		rc = sde_conn->ops.get_qsync_min_fps(sde_conn->display, vrr_fps);
+
+	if (rc <= 0) {
+		SDE_ERROR("invalid qsync min fps %d\n", rc);
+		return;
+	}
+
+	*qsync_fps = rc;
+}
+
+static int _sde_encoder_avr_step_check(struct sde_connector *sde_conn,
+		struct sde_connector_state *sde_conn_state, u32 step)
+{
+	struct sde_encoder_virt *sde_enc = to_sde_encoder_virt(sde_conn_state->base.best_encoder);
+	u32 nom_fps = drm_mode_vrefresh(sde_conn_state->msm_mode.base);
+	u32 min_fps, req_fps = 0;
+	u32 vtotal = sde_conn_state->msm_mode.base->vtotal;
+	bool has_panel_req = sde_enc->disp_info.has_avr_step_req;
+	u32 qsync_mode = sde_connector_get_property(&sde_conn_state->base,
+			CONNECTOR_PROP_QSYNC_MODE);
+
+	if (has_panel_req) {
+		if (!sde_conn->ops.get_avr_step_req) {
+			SDE_ERROR("unable to retrieve required step rate\n");
+			return -EINVAL;
+		}
+
+		req_fps = sde_conn->ops.get_avr_step_req(sde_conn->display, nom_fps);
+		/* when qsync is enabled, the step fps *must* be set to the panel requirement */
+		if (qsync_mode && req_fps != step) {
+			SDE_ERROR("invalid avr_step %u, panel requires %u at nominal %u fps\n",
+					step, req_fps, nom_fps);
+			return -EINVAL;
+		}
+	}
+
+	if (!step)
+		return 0;
+
+	_sde_encoder_get_qsync_fps_callback(sde_conn_state->base.best_encoder, &min_fps, nom_fps);
+	if (!min_fps || !nom_fps || step % nom_fps || step % min_fps || step < nom_fps ||
+			(vtotal * nom_fps) % step) {
+		SDE_ERROR("invalid avr_step rate! nom:%u min:%u step:%u vtotal:%u\n", nom_fps,
+				min_fps, step, vtotal);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int _sde_encoder_atomic_check_qsync(struct sde_connector *sde_conn,
+		struct sde_connector_state *sde_conn_state)
+{
+	int rc = 0;
+	u32 avr_step;
+	bool qsync_dirty, has_modeset;
+	struct drm_connector_state *conn_state = &sde_conn_state->base;
+	u32 qsync_mode = sde_connector_get_property(&sde_conn_state->base,
+						CONNECTOR_PROP_QSYNC_MODE);
+
+	has_modeset = sde_crtc_atomic_check_has_modeset(conn_state->state, conn_state->crtc);
+	qsync_dirty = msm_property_is_dirty(&sde_conn->property_info,
+			&sde_conn_state->property_state, CONNECTOR_PROP_QSYNC_MODE);
+
+	if (has_modeset && qsync_dirty &&
+			(msm_is_mode_seamless_poms(&sde_conn_state->msm_mode) ||
+			 msm_is_mode_seamless_dms(&sde_conn_state->msm_mode) ||
+			 msm_is_mode_seamless_dyn_clk(&sde_conn_state->msm_mode))) {
+		SDE_ERROR("invalid qsync update during modeset priv flag:%x\n",
+				sde_conn_state->msm_mode.private_flags);
+		return -EINVAL;
+	}
+
+	avr_step = sde_connector_get_property(conn_state, CONNECTOR_PROP_AVR_STEP);
+	if (qsync_dirty || (avr_step != sde_conn->avr_step) || (qsync_mode && has_modeset))
+		rc = _sde_encoder_avr_step_check(sde_conn, sde_conn_state, avr_step);
+
+	return rc;
+}
+
 static int sde_encoder_virt_atomic_check(
 	struct drm_encoder *drm_enc, struct drm_crtc_state *crtc_state,
 	struct drm_connector_state *conn_state)
@@ -1010,7 +1124,6 @@ static int sde_encoder_virt_atomic_check(
 	enum sde_rm_topology_name top_name;
 	struct msm_display_info *disp_info;
 	int ret = 0;
-	bool qsync_dirty = false, has_modeset = false;
 
 	if (!drm_enc || !crtc_state || !conn_state) {
 		SDE_ERROR("invalid arg(s), drm_enc %d, crtc/conn state %d/%d\n",
@@ -1083,25 +1196,12 @@ static int sde_encoder_virt_atomic_check(
 
 	drm_mode_set_crtcinfo(adj_mode, 0);
 
-	has_modeset = sde_crtc_atomic_check_has_modeset(conn_state->state,
-				conn_state->crtc);
-	qsync_dirty = msm_property_is_dirty(&sde_conn->property_info,
-				&sde_conn_state->property_state,
-				CONNECTOR_PROP_QSYNC_MODE);
-
-	if (has_modeset && qsync_dirty &&
-		(msm_is_mode_seamless_poms(&sde_conn_state->msm_mode) ||
-		msm_is_mode_seamless_dms(&sde_conn_state->msm_mode) ||
-		msm_is_mode_seamless_dyn_clk(&sde_conn_state->msm_mode))) {
-		SDE_ERROR("invalid qsync update during modeset priv flag:%x\n",
-			sde_conn_state->msm_mode.private_flags);
-		return -EINVAL;
-	}
+	ret = _sde_encoder_atomic_check_qsync(sde_conn, sde_conn_state);
 
 	SDE_EVT32(DRMID(drm_enc), adj_mode->flags,
 		sde_conn_state->msm_mode.private_flags,
 		old_top, drm_mode_vrefresh(adj_mode), adj_mode->hdisplay,
-		adj_mode->vdisplay, adj_mode->htotal, adj_mode->vtotal);
+		adj_mode->vdisplay, adj_mode->htotal, adj_mode->vtotal, ret);
 
 	return ret;
 }
@@ -3342,54 +3442,6 @@ static void sde_encoder_frame_done_callback(
 	}
 }
 
-static void sde_encoder_get_qsync_fps_callback(
-	struct drm_encoder *drm_enc,
-	u32 *qsync_fps, u32 vrr_fps)
-{
-	struct msm_display_info *disp_info;
-	struct sde_encoder_virt *sde_enc;
-	int rc = 0;
-	struct sde_connector *sde_conn;
-
-	if (!qsync_fps)
-		return;
-
-	*qsync_fps = 0;
-	if (!drm_enc) {
-		SDE_ERROR("invalid drm encoder\n");
-		return;
-	}
-
-	sde_enc = to_sde_encoder_virt(drm_enc);
-	disp_info = &sde_enc->disp_info;
-	*qsync_fps = disp_info->qsync_min_fps;
-
-	/**
-	 * If "dsi-supported-qsync-min-fps-list" is defined, get
-	 * the qsync min fps corresponding to the fps in dfps list
-	 */
-	if (disp_info->has_qsync_min_fps_list) {
-
-		if (!sde_enc->cur_master ||
-			!(sde_enc->disp_info.capabilities &
-				MSM_DISPLAY_CAP_VID_MODE)) {
-			SDE_ERROR("invalid qsync settings %d\n",
-				!sde_enc->cur_master);
-			return;
-		}
-		sde_conn = to_sde_connector(sde_enc->cur_master->connector);
-
-		if (sde_conn->ops.get_qsync_min_fps)
-			rc = sde_conn->ops.get_qsync_min_fps(sde_conn->display,
-				vrr_fps);
-		if (rc <= 0) {
-			SDE_ERROR("invalid qsync min fps %d\n", rc);
-			return;
-		}
-		*qsync_fps = rc;
-	}
-}
-
 int sde_encoder_idle_request(struct drm_encoder *drm_enc)
 {
 	struct sde_encoder_virt *sde_enc;
@@ -4905,7 +4957,7 @@ static int sde_encoder_setup_display(struct sde_encoder_virt *sde_enc,
 		sde_encoder_vblank_callback,
 		sde_encoder_underrun_callback,
 		sde_encoder_frame_done_callback,
-		sde_encoder_get_qsync_fps_callback,
+		_sde_encoder_get_qsync_fps_callback,
 	};
 	struct sde_enc_phys_init_params phys_params;
 

+ 11 - 12
msm/sde/sde_encoder_phys_vid.c

@@ -367,21 +367,20 @@ static void _sde_encoder_phys_vid_setup_avr(
 static void _sde_encoder_phys_vid_avr_ctrl(struct sde_encoder_phys *phys_enc)
 {
 	struct intf_avr_params avr_params;
-	struct sde_encoder_phys_vid *vid_enc =
-			to_sde_encoder_phys_vid(phys_enc);
+	struct sde_encoder_phys_vid *vid_enc = to_sde_encoder_phys_vid(phys_enc);
+	u32 avr_step_fps = sde_connector_get_avr_step(phys_enc->connector);
 
-	avr_params.avr_mode = sde_connector_get_qsync_mode(
-			phys_enc->connector);
+	memset(&avr_params, 0, sizeof(avr_params));
+	avr_params.avr_mode = sde_connector_get_qsync_mode(phys_enc->connector);
+	if (avr_step_fps)
+		avr_params.avr_step_lines = mult_frac(phys_enc->cached_mode.vtotal,
+				vid_enc->timing_params.vrefresh, avr_step_fps);
 
-	if (vid_enc->base.hw_intf->ops.avr_ctrl) {
-		vid_enc->base.hw_intf->ops.avr_ctrl(
-				vid_enc->base.hw_intf,
-				&avr_params);
-	}
+	if (vid_enc->base.hw_intf->ops.avr_ctrl)
+		vid_enc->base.hw_intf->ops.avr_ctrl(vid_enc->base.hw_intf, &avr_params);
 
-	SDE_EVT32(DRMID(phys_enc->parent),
-		phys_enc->hw_intf->idx - INTF_0,
-		avr_params.avr_mode);
+	SDE_EVT32(DRMID(phys_enc->parent), phys_enc->hw_intf->idx - INTF_0,
+			avr_params.avr_mode, avr_params.avr_step_lines, avr_step_fps);
 }
 
 static void sde_encoder_phys_vid_setup_timing_engine(

+ 1 - 0
msm/sde/sde_hw_catalog.c

@@ -5070,6 +5070,7 @@ static int _sde_hardware_pre_caps(struct sde_mdss_cfg *sde_cfg, uint32_t hw_rev)
 		sde_cfg->has_fp16 = true;
 		set_bit(SDE_MDP_PERIPH_TOP_0_REMOVED, &sde_cfg->mdp[0].features);
 		sde_cfg->has_precise_vsync_ts = true;
+		sde_cfg->has_avr_step = true;
 		sde_cfg->has_trusted_vm_support = true;
 	} else if (IS_YUPIK_TARGET(hw_rev)) {
 		sde_cfg->has_cwb_support = true;

+ 2 - 0
msm/sde/sde_hw_catalog.h

@@ -1517,6 +1517,7 @@ struct sde_perf_cfg {
  * @has_3d_merge_reset Supports 3D merge reset
  * @has_decimation     Supports decimation
  * @has_trusted_vm_support	     Supported HW sharing with trusted VM
+ * @has_avr_step       Supports AVR with vsync alignment to a set step rate
  * @rc_lm_flush_override        Support Rounded Corner using layer mixer flush
  * @has_mixer_combined_alpha     Mixer has single register for FG & BG alpha
  * @vbif_disable_inner_outer_shareable     VBIF requires disabling shareables
@@ -1604,6 +1605,7 @@ struct sde_mdss_cfg {
 	bool has_base_layer;
 	bool has_demura;
 	bool has_trusted_vm_support;
+	bool has_avr_step;
 	bool rc_lm_flush_override;
 	u32 demura_supported[SSPP_MAX][2];
 	u32 qseed_sw_lib_rev;

+ 4 - 3
msm/sde/sde_hw_intf.c

@@ -175,9 +175,10 @@ static void sde_hw_intf_avr_ctrl(struct sde_hw_intf *ctx,
 	c = &ctx->hw;
 	if (avr_params->avr_mode) {
 		avr_ctrl = BIT(0);
-		avr_mode =
-		(avr_params->avr_mode == SDE_RM_QSYNC_ONE_SHOT_MODE) ?
-			(BIT(0) | BIT(8)) : 0x0;
+		avr_mode = (avr_params->avr_mode == SDE_RM_QSYNC_ONE_SHOT_MODE) ?
+				(BIT(0) | BIT(8)) : 0x0;
+		if (avr_params->avr_step_lines)
+			avr_mode |= avr_params->avr_step_lines << 16;
 	}
 
 	SDE_REG_WRITE(c, INTF_AVR_CONTROL, avr_ctrl);

+ 2 - 1
msm/sde/sde_hw_intf.h

@@ -63,7 +63,8 @@ struct intf_tear_status {
 struct intf_avr_params {
 	u32 default_fps;
 	u32 min_fps;
-	u32 avr_mode; /* 0 - disable, 1 - continuous, 2 - one-shot */
+	u32 avr_mode; /* one of enum @sde_rm_qsync_modes */
+	u32 avr_step_lines; /* 0 or 1 means disabled */
 };
 
 /**

+ 1 - 0
msm/sde/sde_kms.c

@@ -1748,6 +1748,7 @@ static int _sde_kms_setup_displays(struct drm_device *dev,
 		.set_allowed_mode_switch = dsi_conn_set_allowed_mode_switch,
 		.set_dyn_bit_clk = dsi_conn_set_dyn_bit_clk,
 		.get_qsync_min_fps = dsi_display_get_qsync_min_fps,
+		.get_avr_step_req = dsi_display_get_avr_step_req_fps,
 		.prepare_commit = dsi_conn_prepare_commit,
 	};
 	static const struct sde_connector_ops wb_ops = {