mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v4 00/39] drm/msm/dp: Add MST support for MSM chipsets
@ 2026-04-10  9:33 Yongxing Mou
  2026-04-10  9:33 ` [PATCH v4 01/39] drm/msm/dp: remove cached drm_edid from panel Yongxing Mou
                   ` (40 more replies)
  0 siblings, 41 replies; 128+ messages in thread
From: Yongxing Mou @ 2026-04-10  9:33 UTC (permalink / raw)
  To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang
  Cc: linux-arm-msm, dri-devel, freedreno, linux-kernel, Yongxing Mou,
	Abhinav Kumar, Dmitry Baryshkov

Add support for Multi-stream transport for MSM chipsets that allow
a single instance of DP controller to send multiple streams.

This series has been validated on sa8775p ride platform using multiple
MST dongles and also daisy chain method on both DP0 and DP1 upto 1080P.

With 4x4K monitors, due to lack of layer mixers that combination will not
work but this can be supported as well after some rework on the DPU side.

In addition, SST was re-validated with all these changes to ensure there
were no regressions.

This patch series was made on top of:

[1] : https://patchwork.freedesktop.org/series/151522/ (v5 to fix up HPD)

Overall, the patch series has been organized in the following way:

1) First set are a couple of fixes made while debugging MST but applicable
to SST as well so go ahead of everything else
2) Prepare the DP driver to get ready to handle multiple streams. This is the bulk
of the work as current DP driver design had to be adjusted to make this happen.
3) Finally, new files to handle MST related operations

Note:
Validation for this series has so far been done on the latest linux-next
on LeMans, covering both FB console and Weston.

Broader validation, including additional Type-C DP use cases, is still
in progress and may lead to some follow-up adjustments in the next
revision. I wanted to post the current version first to collect early
feedback on the overall approach.

Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
---
Changes in v4:
- Fixed most comments from V3.
- Rebase onto HPD refactor V5.
- Fixed casing/formatting issues, for example: “mst”.
- Drop .mode_set() and set_mode in .atomic_enable().
- Rewrite commit messages that are unclear.
- Use the same API for MST link and SST link writes.
- Use the new drm_dp_dpcd_read_byte() / drm_dp_dpcd_write_byte() interfaces.
- Remove some unnecessary payload fields from the MST bridge.
- Remove some defensive NULL pointer checks.
- Reworked the patch order to make the series easier to follow.
- Add support for more platforms.
- Link to v3: https://lore.kernel.org/r/20250825-msm-dp-mst-v3-0-01faacfcdedd@oss.qualcomm.com

Changes in v3: Fixed review comments from Dmitry
- Fixed lots of comments from series V1/V2.
- Rebased onto next-20250808.
- Rebased onto Jessica's HPD-refactor branch.
- Fixed formatting issues in commit messages under changes.
- Removed unnecessary one-line wrappers.
- Relocated MST-related .atomic_check() calls to their appropriate positions.
- Removed the logic related to slot checking in .mode_valid().
- Link to v2: https://lore.kernel.org/r/20250609-msm-dp-mst-v2-0-a54d8902a23d@quicinc.com

Changes in v2: Fixed review comments from Dmitry
- Rebase on top of next-20250606
- Add all 4 streams pixel clks support and MST2/MST3 Link clk support
- Address the formatting issues mentioned in the review comments
- Drop the cache of msm_dp_panel->drm_edid cached
- Remove the one-line wrapper funtion and redundant conditional check
- Fixed the commit messgae descriptions of some patches
- Reordered the patches and renamed some functions and variables
- Link to v1: https://lore.kernel.org/all/20241205-dp_mst-v1-0-f
8618d42a99a@quicinc.com/

Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>

---
Abhinav Kumar (26):
      drm/msm/dp: break up dp_display_enable into two parts
      drm/msm/dp: re-arrange dp_display_disable() into functional parts
      drm/msm/dp: allow dp_ctrl stream APIs to use any panel passed to it
      drm/msm/dp: split dp_ctrl_off() into stream and link parts
      drm/msm/dp: make bridge helpers use dp_display to allow re-use
      drm/msm/dp: separate dp_display_prepare() into its own API
      drm/msm/dp: introduce stream_id for each DP panel
      drm/msm/dp: introduce max_streams for DP controller MST support
      drm/msm/dp: Add support for programming p1/p2/p3 register blocks
      drm/msm/dp: use stream_id to change offsets in dp_catalog
      drm/msm/dp: add support to send ACT packets for MST
      drm/msm/dp: Add support to enable MST in mainlink control
      drm/msm/dp: no need to update tu calculation for mst
      drm/msm/dp: Add support for MST channel slot allocation
      drm/msm/dp: Add support for sending VCPF packets in DP controller
      drm/msm/dp: Always program MST_FIFO_CONSTANT_FILL for MST use cases
      drm/msm/dp: abstract out the dp_display stream helpers to accept a panel
      drm/msm/dp: replace power_on with active_stream_cnt for dp_display
      drm/msm/dp: Mark the SST bridge disconnected when mst is active
      drm/msm/dp: add an API to initialize MST on sink side
      drm/msm/dp: add dp_display_get_panel() to initialize DP panel
      drm/msm/dp: initialize dp_mst module for each DP MST controller
      drm/msm/dp: add dp_mst_drm to manage DP MST bridge operations
      drm/msm/dp: add connector abstraction for DP MST
      drm/msm/dp: add HPD callback for dp MST
      drm/msm/dpu: use msm_dp_get_mst_intf_id() to get the intf id

Yongxing Mou (13):
      drm/msm/dp: remove cached drm_edid from panel
      drm/msm/dp: drop deprecated .mode_set() and use .atomic_enable
      drm/msm/dp: splite msm_dp_ctrl_config_ctrl() into link parts and stream parts
      drm/msm/dp: extract MISC1_MISC0 configuration into a separate function
      drm/msm/dp: split link setup from source params
      drm/msm/dp: move the pixel clock control to its own API
      drm/msm/dp: Add catalog support for 3rd/4th stream MST
      drm/msm/dp: simplify link and clock disable sequence
      drm/msm/dp: pass panel to display enable/disable helpers
      drm/msm/dp: add prepared to manage link-level operations
      drm/msm/dpu: initialize encoders per stream for DP MST
      drm/msm/dp: wire MST helpers into atomic check and commit paths
      drm/msm/dp: Add MST stream support for supported DP controllers

 drivers/gpu/drm/msm/Makefile                |   3 +-
 drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c |  20 +-
 drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.h |   2 +
 drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c     |  30 +-
 drivers/gpu/drm/msm/dp/dp_audio.c           |   2 +-
 drivers/gpu/drm/msm/dp/dp_ctrl.c            | 822 +++++++++++++++++++++-------
 drivers/gpu/drm/msm/dp/dp_ctrl.h            |  24 +-
 drivers/gpu/drm/msm/dp/dp_display.c         | 598 ++++++++++++++------
 drivers/gpu/drm/msm/dp/dp_display.h         |  31 +-
 drivers/gpu/drm/msm/dp/dp_drm.c             |  43 +-
 drivers/gpu/drm/msm/dp/dp_drm.h             |  12 -
 drivers/gpu/drm/msm/dp/dp_mst_drm.c         | 795 +++++++++++++++++++++++++++
 drivers/gpu/drm/msm/dp/dp_mst_drm.h         |  14 +
 drivers/gpu/drm/msm/dp/dp_panel.c           | 315 +++++++----
 drivers/gpu/drm/msm/dp/dp_panel.h           |  32 +-
 drivers/gpu/drm/msm/dp/dp_reg.h             |  48 +-
 drivers/gpu/drm/msm/msm_atomic.c            |   9 +-
 drivers/gpu/drm/msm/msm_drv.h               |  18 +
 drivers/gpu/drm/msm/msm_kms.c               |   2 +
 19 files changed, 2300 insertions(+), 520 deletions(-)
---
base-commit: 3ef088b0c5772a6f75634e54aa34f5fc0a2c041c
change-id: 20260410-msm-dp-mst-35130b6e8b84
prerequisite-message-id: <20260305-mdss_catalog-v5-0-06678ac39ac7@oss.qualcomm.com>
prerequisite-patch-id: 8b3f7f40025e7a10c4646435e9cfec4f9e275871
prerequisite-patch-id: 547db98962f20218380e966b976aede824ff8433
prerequisite-message-id: <20260314-hpd-refactor-v5-0-0c8450737d64@oss.qualcomm.com>
prerequisite-patch-id: 1867c344ef6ead1034523ba65d2640f9ad0606cd
prerequisite-patch-id: a58ebaf429385c622869c83e83ce7ffdfe9ea27e
prerequisite-patch-id: d0f570e57559248ba6ca733f96fdb02af29f3055
prerequisite-patch-id: 44d390f5319068ad534c4be698dd8dba99c0bfd1
prerequisite-patch-id: 85438690573583cf62065f43531dbb4e221e9dd0
prerequisite-patch-id: b37db0f0b10f16ed45ab056aa87f2275bb02df94
prerequisite-patch-id: dcccd204ee8a979328719d41334453474bcfdf98
prerequisite-patch-id: db07c3aa80a597c91ae2329bc677bfd72b63716a
prerequisite-patch-id: 9778cc1ab60a0870a74d58ff220bb01011fdf1c0
prerequisite-patch-id: 95692a605647e51bdaf8d5b2e61635a6978121f0

Best regards,
-- 
Yongxing Mou <yongxing.mou@oss.qualcomm.com>


^ permalink raw reply	[flat|nested] 128+ messages in thread

* [PATCH v4 01/39] drm/msm/dp: remove cached drm_edid from panel
  2026-04-10  9:33 [PATCH v4 00/39] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
@ 2026-04-10  9:33 ` Yongxing Mou
  2026-04-10 13:52   ` Dmitry Baryshkov
  2026-04-10  9:33 ` [PATCH v4 02/39] drm/msm/dp: drop deprecated .mode_set() and use .atomic_enable Yongxing Mou
                   ` (39 subsequent siblings)
  40 siblings, 1 reply; 128+ messages in thread
From: Yongxing Mou @ 2026-04-10  9:33 UTC (permalink / raw)
  To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang
  Cc: linux-arm-msm, dri-devel, freedreno, linux-kernel, Yongxing Mou

The cached drm_edid seems unnecessary here. Use the drm_edid pointer
directly in the plug stage instead of caching it. Remove the cached
drm_edid and the corresponding oneliner to simplify the code.

Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
---
 drivers/gpu/drm/msm/dp/dp_display.c | 27 +++++++++++-------
 drivers/gpu/drm/msm/dp/dp_panel.c   | 57 ++++---------------------------------
 drivers/gpu/drm/msm/dp/dp_panel.h   | 13 +++------
 3 files changed, 26 insertions(+), 71 deletions(-)

diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
index 5c6a24ec140d..e28cc1bbb5b1 100644
--- a/drivers/gpu/drm/msm/dp/dp_display.c
+++ b/drivers/gpu/drm/msm/dp/dp_display.c
@@ -268,6 +268,7 @@ static int msm_dp_display_process_hpd_high(struct msm_dp_display_private *dp)
 	const struct drm_display_info *info = &connector->display_info;
 	int rc = 0;
 	u8 dpcd[DP_RECEIVER_CAP_SIZE];
+	const struct drm_edid *drm_edid;
 
 	rc = drm_dp_read_dpcd_caps(dp->aux, dpcd);
 	if (rc)
@@ -275,10 +276,20 @@ static int msm_dp_display_process_hpd_high(struct msm_dp_display_private *dp)
 
 	dp->link->lttpr_count = msm_dp_display_lttpr_init(dp, dpcd);
 
-	rc = msm_dp_panel_read_sink_caps(dp->panel, connector);
+	rc = msm_dp_panel_read_link_caps(dp->panel, connector);
 	if (rc)
 		goto end;
 
+	drm_edid = drm_edid_read_ddc(connector, &dp->aux->ddc);
+	drm_edid_connector_update(connector, drm_edid);
+
+	if (!drm_edid) {
+		DRM_ERROR("panel edid read failed\n");
+		/* check edid read fail is due to unplug */
+		if (!msm_dp_aux_is_link_connected(dp->aux))
+			return -ETIMEDOUT;
+	}
+
 	msm_dp_link_process_request(dp->link);
 
 	if (!dp->msm_dp_display.is_edp)
@@ -290,7 +301,7 @@ static int msm_dp_display_process_hpd_high(struct msm_dp_display_private *dp)
 	dp->msm_dp_display.psr_supported = dp->panel->psr_cap.version && psr_enabled;
 
 	dp->audio_supported = info->has_audio;
-	msm_dp_panel_handle_sink_request(dp->panel);
+	msm_dp_panel_handle_sink_request(dp->panel, drm_edid);
 
 	/*
 	 * set sink to normal operation mode -- D0
@@ -449,7 +460,7 @@ static int msm_dp_hpd_unplug_handle(struct msm_dp_display_private *dp)
 
 	/* Don't forget modes for eDP */
 	if (!dp->msm_dp_display.is_edp)
-		msm_dp_panel_unplugged(dp->panel, dp->msm_dp_display.connector);
+		drm_edid_connector_update(dp->msm_dp_display.connector, NULL);
 
 	/* triggered by irq_hdp with sink_count = 0 */
 	if (dp->link->sink_count == 0)
@@ -512,7 +523,6 @@ static int msm_dp_irq_hpd_handle(struct msm_dp_display_private *dp)
 static void msm_dp_display_deinit_sub_modules(struct msm_dp_display_private *dp)
 {
 	msm_dp_audio_put(dp->audio);
-	msm_dp_panel_put(dp->panel);
 	msm_dp_aux_put(dp->aux);
 }
 
@@ -563,7 +573,7 @@ static int msm_dp_init_sub_modules(struct msm_dp_display_private *dp)
 		rc = PTR_ERR(dp->ctrl);
 		DRM_ERROR("failed to initialize ctrl, rc = %d\n", rc);
 		dp->ctrl = NULL;
-		goto error_ctrl;
+		goto error_link;
 	}
 
 	dp->audio = msm_dp_audio_get(dp->msm_dp_display.pdev, dp->link_base);
@@ -571,13 +581,11 @@ static int msm_dp_init_sub_modules(struct msm_dp_display_private *dp)
 		rc = PTR_ERR(dp->audio);
 		pr_err("failed to initialize audio, rc = %d\n", rc);
 		dp->audio = NULL;
-		goto error_ctrl;
+		goto error_link;
 	}
 
 	return rc;
 
-error_ctrl:
-	msm_dp_panel_put(dp->panel);
 error_link:
 	msm_dp_aux_put(dp->aux);
 error:
@@ -741,8 +749,7 @@ int msm_dp_display_get_modes(struct msm_dp *dp)
 
 	msm_dp_display = container_of(dp, struct msm_dp_display_private, msm_dp_display);
 
-	return msm_dp_panel_get_modes(msm_dp_display->panel,
-		dp->connector);
+	return drm_edid_connector_add_modes(msm_dp_display->panel->connector);
 }
 
 bool msm_dp_display_check_video_test(struct msm_dp *dp)
diff --git a/drivers/gpu/drm/msm/dp/dp_panel.c b/drivers/gpu/drm/msm/dp/dp_panel.c
index 6bb021820d7c..bde4a772d22c 100644
--- a/drivers/gpu/drm/msm/dp/dp_panel.c
+++ b/drivers/gpu/drm/msm/dp/dp_panel.c
@@ -232,8 +232,8 @@ static u32 msm_dp_panel_get_supported_bpp(struct msm_dp_panel *msm_dp_panel,
 	return min_supported_bpp;
 }
 
-int msm_dp_panel_read_sink_caps(struct msm_dp_panel *msm_dp_panel,
-	struct drm_connector *connector)
+int msm_dp_panel_read_link_caps(struct msm_dp_panel *msm_dp_panel,
+				struct drm_connector *connector)
 {
 	int rc, bw_code;
 	int count;
@@ -271,36 +271,9 @@ int msm_dp_panel_read_sink_caps(struct msm_dp_panel *msm_dp_panel,
 
 	rc = drm_dp_read_downstream_info(panel->aux, msm_dp_panel->dpcd,
 					 msm_dp_panel->downstream_ports);
-	if (rc)
-		return rc;
-
-	drm_edid_free(msm_dp_panel->drm_edid);
-
-	msm_dp_panel->drm_edid = drm_edid_read_ddc(connector, &panel->aux->ddc);
-
-	drm_edid_connector_update(connector, msm_dp_panel->drm_edid);
-
-	if (!msm_dp_panel->drm_edid) {
-		DRM_ERROR("panel edid read failed\n");
-		/* check edid read fail is due to unplug */
-		if (!msm_dp_aux_is_link_connected(panel->aux)) {
-			rc = -ETIMEDOUT;
-			goto end;
-		}
-	}
-
-end:
 	return rc;
 }
 
-void msm_dp_panel_unplugged(struct msm_dp_panel *msm_dp_panel,
-			    struct drm_connector *connector)
-{
-	drm_edid_connector_update(connector, NULL);
-	drm_edid_free(msm_dp_panel->drm_edid);
-	msm_dp_panel->drm_edid = NULL;
-}
-
 u32 msm_dp_panel_get_mode_bpp(struct msm_dp_panel *msm_dp_panel,
 		u32 mode_edid_bpp, u32 mode_pclk_khz)
 {
@@ -324,20 +297,6 @@ u32 msm_dp_panel_get_mode_bpp(struct msm_dp_panel *msm_dp_panel,
 	return bpp;
 }
 
-int msm_dp_panel_get_modes(struct msm_dp_panel *msm_dp_panel,
-	struct drm_connector *connector)
-{
-	if (!msm_dp_panel) {
-		DRM_ERROR("invalid input\n");
-		return -EINVAL;
-	}
-
-	if (msm_dp_panel->drm_edid)
-		return drm_edid_connector_add_modes(connector);
-
-	return 0;
-}
-
 static u8 msm_dp_panel_get_edid_checksum(const struct edid *edid)
 {
 	edid += edid->extensions;
@@ -345,7 +304,8 @@ static u8 msm_dp_panel_get_edid_checksum(const struct edid *edid)
 	return edid->checksum;
 }
 
-void msm_dp_panel_handle_sink_request(struct msm_dp_panel *msm_dp_panel)
+void msm_dp_panel_handle_sink_request(struct msm_dp_panel *msm_dp_panel,
+				      const struct drm_edid *drm_edid)
 {
 	struct msm_dp_panel_private *panel;
 
@@ -358,7 +318,7 @@ void msm_dp_panel_handle_sink_request(struct msm_dp_panel *msm_dp_panel)
 
 	if (panel->link->sink_request & DP_TEST_LINK_EDID_READ) {
 		/* FIXME: get rid of drm_edid_raw() */
-		const struct edid *edid = drm_edid_raw(msm_dp_panel->drm_edid);
+		const struct edid *edid = drm_edid_raw(drm_edid);
 		u8 checksum;
 
 		if (edid)
@@ -755,10 +715,3 @@ struct msm_dp_panel *msm_dp_panel_get(struct device *dev, struct drm_dp_aux *aux
 	return msm_dp_panel;
 }
 
-void msm_dp_panel_put(struct msm_dp_panel *msm_dp_panel)
-{
-	if (!msm_dp_panel)
-		return;
-
-	drm_edid_free(msm_dp_panel->drm_edid);
-}
diff --git a/drivers/gpu/drm/msm/dp/dp_panel.h b/drivers/gpu/drm/msm/dp/dp_panel.h
index 9173e90a5053..53b7b4463551 100644
--- a/drivers/gpu/drm/msm/dp/dp_panel.h
+++ b/drivers/gpu/drm/msm/dp/dp_panel.h
@@ -33,7 +33,6 @@ struct msm_dp_panel {
 	u8 downstream_ports[DP_MAX_DOWNSTREAM_PORTS];
 
 	struct msm_dp_link_info link_info;
-	const struct drm_edid *drm_edid;
 	struct drm_connector *connector;
 	struct msm_dp_display_mode msm_dp_mode;
 	struct msm_dp_panel_psr psr_cap;
@@ -47,15 +46,12 @@ struct msm_dp_panel {
 int msm_dp_panel_init_panel_info(struct msm_dp_panel *msm_dp_panel);
 int msm_dp_panel_deinit(struct msm_dp_panel *msm_dp_panel);
 int msm_dp_panel_timing_cfg(struct msm_dp_panel *msm_dp_panel, bool wide_bus_en);
-int msm_dp_panel_read_sink_caps(struct msm_dp_panel *msm_dp_panel,
-		struct drm_connector *connector);
-void msm_dp_panel_unplugged(struct msm_dp_panel *msm_dp_panel,
-			    struct drm_connector *connector);
+int msm_dp_panel_read_link_caps(struct msm_dp_panel *msm_dp_panel,
+				struct drm_connector *connector);
 u32 msm_dp_panel_get_mode_bpp(struct msm_dp_panel *msm_dp_panel, u32 mode_max_bpp,
 			u32 mode_pclk_khz);
-int msm_dp_panel_get_modes(struct msm_dp_panel *msm_dp_panel,
-		struct drm_connector *connector);
-void msm_dp_panel_handle_sink_request(struct msm_dp_panel *msm_dp_panel);
+void msm_dp_panel_handle_sink_request(struct msm_dp_panel *msm_dp_panel,
+				      const struct drm_edid *drm_edid);
 void msm_dp_panel_tpg_config(struct msm_dp_panel *msm_dp_panel, bool enable);
 
 void msm_dp_panel_clear_dsc_dto(struct msm_dp_panel *msm_dp_panel);
@@ -94,5 +90,4 @@ struct msm_dp_panel *msm_dp_panel_get(struct device *dev, struct drm_dp_aux *aux
 			      struct msm_dp_link *link,
 			      void __iomem *link_base,
 			      void __iomem *p0_base);
-void msm_dp_panel_put(struct msm_dp_panel *msm_dp_panel);
 #endif /* _DP_PANEL_H_ */

-- 
2.43.0


^ permalink raw reply	[flat|nested] 128+ messages in thread

* [PATCH v4 02/39] drm/msm/dp: drop deprecated .mode_set() and use .atomic_enable
  2026-04-10  9:33 [PATCH v4 00/39] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
  2026-04-10  9:33 ` [PATCH v4 01/39] drm/msm/dp: remove cached drm_edid from panel Yongxing Mou
@ 2026-04-10  9:33 ` Yongxing Mou
  2026-04-11 17:23   ` Dmitry Baryshkov
  2026-04-10  9:33 ` [PATCH v4 03/39] drm/msm/dp: break up dp_display_enable into two parts Yongxing Mou
                   ` (38 subsequent siblings)
  40 siblings, 1 reply; 128+ messages in thread
From: Yongxing Mou @ 2026-04-10  9:33 UTC (permalink / raw)
  To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang
  Cc: linux-arm-msm, dri-devel, freedreno, linux-kernel, Yongxing Mou,
	Abhinav Kumar

The bridge .mode_set() callback is deprecated. Remove it and move all
mode setup logic to .atomic_enable(), where the adjusted_mode is
available from the atomic CRTC state.

Drop msm_dp_mode from msm_dp_display_private and store the mode directly
in the panel, as it was only used as a temporary cache. Both changes are
limited to msm_dp_display_set_mode and are kept in a single patch.

Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
---
 drivers/gpu/drm/msm/dp/dp_display.c | 81 ++++++++++++++-----------------------
 drivers/gpu/drm/msm/dp/dp_drm.c     |  2 -
 drivers/gpu/drm/msm/dp/dp_drm.h     |  3 --
 3 files changed, 31 insertions(+), 55 deletions(-)

diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
index e28cc1bbb5b1..e9f0b96c3ebd 100644
--- a/drivers/gpu/drm/msm/dp/dp_display.c
+++ b/drivers/gpu/drm/msm/dp/dp_display.c
@@ -63,7 +63,6 @@ struct msm_dp_display_private {
 	struct msm_dp_panel   *panel;
 	struct msm_dp_ctrl    *ctrl;
 
-	struct msm_dp_display_mode msm_dp_mode;
 	struct msm_dp msm_dp_display;
 
 	/* wait for audio signaling */
@@ -593,16 +592,33 @@ static int msm_dp_init_sub_modules(struct msm_dp_display_private *dp)
 }
 
 static int msm_dp_display_set_mode(struct msm_dp *msm_dp_display,
-			       struct msm_dp_display_mode *mode)
+				   const struct drm_display_mode *adjusted_mode,
+				   struct msm_dp_panel *msm_dp_panel)
 {
 	struct msm_dp_display_private *dp;
+	u32 bpp;
 
 	dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display);
 
-	drm_mode_copy(&dp->panel->msm_dp_mode.drm_mode, &mode->drm_mode);
-	dp->panel->msm_dp_mode.bpp = mode->bpp;
-	dp->panel->msm_dp_mode.out_fmt_is_yuv_420 = mode->out_fmt_is_yuv_420;
-	msm_dp_panel_init_panel_info(dp->panel);
+	drm_mode_copy(&msm_dp_panel->msm_dp_mode.drm_mode, adjusted_mode);
+	if (msm_dp_display_check_video_test(msm_dp_display))
+		bpp = msm_dp_display_get_test_bpp(msm_dp_display);
+	else
+		bpp = msm_dp_panel->connector->display_info.bpc * 3;
+
+	msm_dp_panel->msm_dp_mode.bpp = bpp ? bpp : 24; /* Default bpp */
+	msm_dp_panel->msm_dp_mode.v_active_low =
+		!!(adjusted_mode->flags & DRM_MODE_FLAG_NVSYNC);
+	msm_dp_panel->msm_dp_mode.h_active_low =
+		!!(adjusted_mode->flags & DRM_MODE_FLAG_NHSYNC);
+	msm_dp_panel->msm_dp_mode.out_fmt_is_yuv_420 =
+		drm_mode_is_420_only(&msm_dp_panel->connector->display_info, adjusted_mode) &&
+		msm_dp_panel->vsc_sdp_supported;
+	msm_dp_panel_init_panel_info(msm_dp_panel);
+
+	/* populate wide_bus_support to different layers */
+	dp->ctrl->wide_bus_en =
+		msm_dp_panel->msm_dp_mode.out_fmt_is_yuv_420 ? false : dp->wide_bus_supported;
 	return 0;
 }
 
@@ -1305,7 +1321,7 @@ bool msm_dp_wide_bus_available(const struct msm_dp *msm_dp_display)
 
 	dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display);
 
-	if (dp->msm_dp_mode.out_fmt_is_yuv_420)
+	if (dp->panel->msm_dp_mode.out_fmt_is_yuv_420)
 		return false;
 
 	return dp->wide_bus_supported;
@@ -1361,15 +1377,19 @@ void msm_dp_bridge_atomic_enable(struct drm_bridge *drm_bridge,
 {
 	struct msm_dp_bridge *msm_dp_bridge = to_dp_bridge(drm_bridge);
 	struct msm_dp *dp = msm_dp_bridge->msm_dp_display;
+	struct drm_crtc *crtc;
+	struct drm_crtc_state *crtc_state;
 	int rc = 0;
 	struct msm_dp_display_private *msm_dp_display;
 	bool force_link_train = false;
 
 	msm_dp_display = container_of(dp, struct msm_dp_display_private, msm_dp_display);
-	if (!msm_dp_display->msm_dp_mode.drm_mode.clock) {
-		DRM_ERROR("invalid params\n");
+
+	crtc = drm_atomic_get_new_crtc_for_encoder(state,
+						   drm_bridge->encoder);
+	if (!crtc)
 		return;
-	}
+	crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
 
 	if (dp->is_edp)
 		msm_dp_hpd_plug_handle(msm_dp_display);
@@ -1382,7 +1402,7 @@ void msm_dp_bridge_atomic_enable(struct drm_bridge *drm_bridge,
 	if (msm_dp_display->link->sink_count == 0)
 		return;
 
-	rc = msm_dp_display_set_mode(dp, &msm_dp_display->msm_dp_mode);
+	rc = msm_dp_display_set_mode(dp, &crtc_state->adjusted_mode, msm_dp_display->panel);
 	if (rc) {
 		DRM_ERROR("Failed to perform a mode set, rc=%d\n", rc);
 		return;
@@ -1440,45 +1460,6 @@ void msm_dp_bridge_atomic_post_disable(struct drm_bridge *drm_bridge,
 	pm_runtime_put_sync(&dp->pdev->dev);
 }
 
-void msm_dp_bridge_mode_set(struct drm_bridge *drm_bridge,
-			const struct drm_display_mode *mode,
-			const struct drm_display_mode *adjusted_mode)
-{
-	struct msm_dp_bridge *msm_dp_bridge = to_dp_bridge(drm_bridge);
-	struct msm_dp *dp = msm_dp_bridge->msm_dp_display;
-	struct msm_dp_display_private *msm_dp_display;
-	struct msm_dp_panel *msm_dp_panel;
-
-	msm_dp_display = container_of(dp, struct msm_dp_display_private, msm_dp_display);
-	msm_dp_panel = msm_dp_display->panel;
-
-	memset(&msm_dp_display->msm_dp_mode, 0x0, sizeof(struct msm_dp_display_mode));
-
-	if (msm_dp_display_check_video_test(dp))
-		msm_dp_display->msm_dp_mode.bpp = msm_dp_display_get_test_bpp(dp);
-	else /* Default num_components per pixel = 3 */
-		msm_dp_display->msm_dp_mode.bpp = dp->connector->display_info.bpc * 3;
-
-	if (!msm_dp_display->msm_dp_mode.bpp)
-		msm_dp_display->msm_dp_mode.bpp = 24; /* Default bpp */
-
-	drm_mode_copy(&msm_dp_display->msm_dp_mode.drm_mode, adjusted_mode);
-
-	msm_dp_display->msm_dp_mode.v_active_low =
-		!!(msm_dp_display->msm_dp_mode.drm_mode.flags & DRM_MODE_FLAG_NVSYNC);
-
-	msm_dp_display->msm_dp_mode.h_active_low =
-		!!(msm_dp_display->msm_dp_mode.drm_mode.flags & DRM_MODE_FLAG_NHSYNC);
-
-	msm_dp_display->msm_dp_mode.out_fmt_is_yuv_420 =
-		drm_mode_is_420_only(&dp->connector->display_info, adjusted_mode) &&
-		msm_dp_panel->vsc_sdp_supported;
-
-	/* populate wide_bus_support to different layers */
-	msm_dp_display->ctrl->wide_bus_en =
-		msm_dp_display->msm_dp_mode.out_fmt_is_yuv_420 ? false : msm_dp_display->wide_bus_supported;
-}
-
 void msm_dp_bridge_hpd_enable(struct drm_bridge *bridge)
 {
 	struct msm_dp_bridge *msm_dp_bridge = to_dp_bridge(bridge);
diff --git a/drivers/gpu/drm/msm/dp/dp_drm.c b/drivers/gpu/drm/msm/dp/dp_drm.c
index 8dc0dabd275c..af3d3e3a2d84 100644
--- a/drivers/gpu/drm/msm/dp/dp_drm.c
+++ b/drivers/gpu/drm/msm/dp/dp_drm.c
@@ -56,7 +56,6 @@ static const struct drm_bridge_funcs msm_dp_bridge_ops = {
 	.atomic_enable          = msm_dp_bridge_atomic_enable,
 	.atomic_disable         = msm_dp_bridge_atomic_disable,
 	.atomic_post_disable    = msm_dp_bridge_atomic_post_disable,
-	.mode_set     = msm_dp_bridge_mode_set,
 	.mode_valid   = msm_dp_bridge_mode_valid,
 	.get_modes    = msm_dp_bridge_get_modes,
 	.detect       = msm_dp_bridge_detect,
@@ -233,7 +232,6 @@ static const struct drm_bridge_funcs msm_edp_bridge_ops = {
 	.atomic_enable = msm_edp_bridge_atomic_enable,
 	.atomic_disable = msm_edp_bridge_atomic_disable,
 	.atomic_post_disable = msm_edp_bridge_atomic_post_disable,
-	.mode_set = msm_dp_bridge_mode_set,
 	.mode_valid = msm_edp_bridge_mode_valid,
 	.atomic_reset = drm_atomic_helper_bridge_reset,
 	.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
diff --git a/drivers/gpu/drm/msm/dp/dp_drm.h b/drivers/gpu/drm/msm/dp/dp_drm.h
index 6c0426803d78..6d4cbb9f3918 100644
--- a/drivers/gpu/drm/msm/dp/dp_drm.h
+++ b/drivers/gpu/drm/msm/dp/dp_drm.h
@@ -36,9 +36,6 @@ void msm_dp_bridge_atomic_post_disable(struct drm_bridge *drm_bridge,
 enum drm_mode_status msm_dp_bridge_mode_valid(struct drm_bridge *bridge,
 					  const struct drm_display_info *info,
 					  const struct drm_display_mode *mode);
-void msm_dp_bridge_mode_set(struct drm_bridge *drm_bridge,
-			const struct drm_display_mode *mode,
-			const struct drm_display_mode *adjusted_mode);
 void msm_dp_bridge_hpd_enable(struct drm_bridge *bridge);
 void msm_dp_bridge_hpd_disable(struct drm_bridge *bridge);
 void msm_dp_bridge_hpd_notify(struct drm_bridge *bridge,

-- 
2.43.0


^ permalink raw reply	[flat|nested] 128+ messages in thread

* [PATCH v4 03/39] drm/msm/dp: break up dp_display_enable into two parts
  2026-04-10  9:33 [PATCH v4 00/39] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
  2026-04-10  9:33 ` [PATCH v4 01/39] drm/msm/dp: remove cached drm_edid from panel Yongxing Mou
  2026-04-10  9:33 ` [PATCH v4 02/39] drm/msm/dp: drop deprecated .mode_set() and use .atomic_enable Yongxing Mou
@ 2026-04-10  9:33 ` Yongxing Mou
  2026-04-11 17:33   ` Dmitry Baryshkov
  2026-04-12  0:20   ` Dmitry Baryshkov
  2026-04-10  9:33 ` [PATCH v4 04/39] drm/msm/dp: re-arrange dp_display_disable() into functional parts Yongxing Mou
                   ` (37 subsequent siblings)
  40 siblings, 2 replies; 128+ messages in thread
From: Yongxing Mou @ 2026-04-10  9:33 UTC (permalink / raw)
  To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang
  Cc: linux-arm-msm, dri-devel, freedreno, linux-kernel, Yongxing Mou,
	Abhinav Kumar

From: Abhinav Kumar <quic_abhinavk@quicinc.com>

dp_display_enable() currently re-trains the link if needed and then
enables the pixel clock, programs the controller to start sending the
pixel stream. Split these two parts into prepare/enable APIs, to support
MST bridges_enable insert the MST payloads funcs between enable
stream_clks and program register.

Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
---
 drivers/gpu/drm/msm/dp/dp_ctrl.c    |  54 ++++++++++++-------
 drivers/gpu/drm/msm/dp/dp_ctrl.h    |   3 +-
 drivers/gpu/drm/msm/dp/dp_display.c | 105 +++++++++++++++++++++++-------------
 3 files changed, 106 insertions(+), 56 deletions(-)

diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c
index 3bb08c9a020e..0fd4a7b6d931 100644
--- a/drivers/gpu/drm/msm/dp/dp_ctrl.c
+++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c
@@ -2472,27 +2472,19 @@ static void msm_dp_ctrl_config_msa(struct msm_dp_ctrl_private *ctrl,
 	msm_dp_write_link(ctrl, REG_DP_SOFTWARE_NVID, nvid);
 }
 
-int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train)
+int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train)
 {
 	int ret = 0;
-	bool mainlink_ready = false;
 	struct msm_dp_ctrl_private *ctrl;
-	unsigned long pixel_rate;
-	unsigned long pixel_rate_orig;
 
 	if (!msm_dp_ctrl)
 		return -EINVAL;
 
 	ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl);
 
-	pixel_rate = pixel_rate_orig = ctrl->panel->msm_dp_mode.drm_mode.clock;
-
-	if (msm_dp_ctrl->wide_bus_en || ctrl->panel->msm_dp_mode.out_fmt_is_yuv_420)
-		pixel_rate >>= 1;
-
-	drm_dbg_dp(ctrl->drm_dev, "rate=%d, num_lanes=%d, pixel_rate=%lu\n",
-		ctrl->link->link_params.rate,
-		ctrl->link->link_params.num_lanes, pixel_rate);
+	drm_dbg_dp(ctrl->drm_dev, "rate=%d, num_lanes=%d\n",
+		   ctrl->link->link_params.rate,
+		   ctrl->link->link_params.num_lanes);
 
 	drm_dbg_dp(ctrl->drm_dev,
 		"core_clk_on=%d link_clk_on=%d stream_clk_on=%d\n",
@@ -2502,10 +2494,40 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train
 		ret = msm_dp_ctrl_enable_mainlink_clocks(ctrl);
 		if (ret) {
 			DRM_ERROR("Failed to start link clocks. ret=%d\n", ret);
-			goto end;
+			return ret;
 		}
 	}
 
+	if (force_link_train || !msm_dp_ctrl_channel_eq_ok(ctrl))
+		msm_dp_ctrl_link_retrain(ctrl);
+
+	/* stop txing train pattern to end link training */
+	msm_dp_ctrl_clear_training_pattern(ctrl, DP_PHY_DPRX);
+
+	return ret;
+}
+
+int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl)
+{
+	int ret = 0;
+	bool mainlink_ready = false;
+	struct msm_dp_ctrl_private *ctrl;
+	unsigned long pixel_rate;
+	unsigned long pixel_rate_orig;
+
+	if (!msm_dp_ctrl)
+		return -EINVAL;
+
+	ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl);
+
+	pixel_rate_orig = ctrl->panel->msm_dp_mode.drm_mode.clock;
+	pixel_rate = pixel_rate_orig;
+
+	if (msm_dp_ctrl->wide_bus_en || ctrl->panel->msm_dp_mode.out_fmt_is_yuv_420)
+		pixel_rate >>= 1;
+
+	drm_dbg_dp(ctrl->drm_dev, "pixel_rate=%lu\n", pixel_rate);
+
 	ret = clk_set_rate(ctrl->pixel_clk, pixel_rate * 1000);
 	if (ret) {
 		DRM_ERROR("Failed to set pixel clock rate. ret=%d\n", ret);
@@ -2523,12 +2545,6 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train
 		ctrl->stream_clks_on = true;
 	}
 
-	if (force_link_train || !msm_dp_ctrl_channel_eq_ok(ctrl))
-		msm_dp_ctrl_link_retrain(ctrl);
-
-	/* stop txing train pattern to end link training */
-	msm_dp_ctrl_clear_training_pattern(ctrl, DP_PHY_DPRX);
-
 	/*
 	 * Set up transfer unit values and set controller state to send
 	 * video.
diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.h b/drivers/gpu/drm/msm/dp/dp_ctrl.h
index f68bee62713f..1497f1a8fc2f 100644
--- a/drivers/gpu/drm/msm/dp/dp_ctrl.h
+++ b/drivers/gpu/drm/msm/dp/dp_ctrl.h
@@ -17,7 +17,8 @@ struct msm_dp_ctrl {
 struct phy;
 
 int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl);
-int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train);
+int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl);
+int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train);
 void msm_dp_ctrl_off_link_stream(struct msm_dp_ctrl *msm_dp_ctrl);
 void msm_dp_ctrl_off(struct msm_dp_ctrl *msm_dp_ctrl);
 void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl);
diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
index e9f0b96c3ebd..5ecbc83c3838 100644
--- a/drivers/gpu/drm/msm/dp/dp_display.c
+++ b/drivers/gpu/drm/msm/dp/dp_display.c
@@ -622,7 +622,40 @@ static int msm_dp_display_set_mode(struct msm_dp *msm_dp_display,
 	return 0;
 }
 
-static int msm_dp_display_enable(struct msm_dp_display_private *dp, bool force_link_train)
+static int msm_dp_display_prepare(struct msm_dp_display_private *dp)
+{
+	struct msm_dp *msm_dp_display = &dp->msm_dp_display;
+	int rc = 0;
+	bool force_link_train = false;
+
+	drm_dbg_dp(dp->drm_dev, "sink_count=%d\n", dp->link->sink_count);
+
+	if (msm_dp_display->is_edp)
+		msm_dp_hpd_plug_handle(dp);
+
+	rc = pm_runtime_resume_and_get(&msm_dp_display->pdev->dev);
+	if (rc) {
+		DRM_ERROR("failed to pm_runtime_resume\n");
+		return rc;
+	}
+
+	if (dp->link->sink_count == 0)
+		return rc;
+
+	if (!msm_dp_display->power_on) {
+		msm_dp_display_host_phy_init(dp);
+		force_link_train = true;
+	}
+
+	rc = msm_dp_ctrl_on_link(dp->ctrl);
+	if (rc)
+		DRM_ERROR("Failed link training (rc=%d)\n", rc);
+	// TODO: schedule drm_connector_set_link_status_property()
+
+	return msm_dp_ctrl_prepare_stream_on(dp->ctrl, force_link_train);
+}
+
+static int msm_dp_display_enable(struct msm_dp_display_private *dp)
 {
 	int rc = 0;
 	struct msm_dp *msm_dp_display = &dp->msm_dp_display;
@@ -633,7 +666,7 @@ static int msm_dp_display_enable(struct msm_dp_display_private *dp, bool force_l
 		return 0;
 	}
 
-	rc = msm_dp_ctrl_on_stream(dp->ctrl, force_link_train);
+	rc = msm_dp_ctrl_on_stream(dp->ctrl);
 	if (!rc)
 		msm_dp_display->power_on = true;
 
@@ -663,13 +696,10 @@ static int msm_dp_display_post_enable(struct msm_dp *msm_dp_display)
 	return 0;
 }
 
-static int msm_dp_display_disable(struct msm_dp_display_private *dp)
+static void msm_dp_display_audio_notify_disable(struct msm_dp_display_private *dp)
 {
 	struct msm_dp *msm_dp_display = &dp->msm_dp_display;
 
-	if (!msm_dp_display->power_on)
-		return 0;
-
 	/* wait only if audio was enabled */
 	if (msm_dp_display->audio_enabled) {
 		/* signal the disconnect event */
@@ -680,6 +710,14 @@ static int msm_dp_display_disable(struct msm_dp_display_private *dp)
 	}
 
 	msm_dp_display->audio_enabled = false;
+}
+
+static int msm_dp_display_disable(struct msm_dp_display_private *dp)
+{
+	struct msm_dp *msm_dp_display = &dp->msm_dp_display;
+
+	if (!msm_dp_display->power_on)
+		return 0;
 
 	if (dp->link->sink_count == 0) {
 		/*
@@ -1376,14 +1414,13 @@ void msm_dp_bridge_atomic_enable(struct drm_bridge *drm_bridge,
 				 struct drm_atomic_state *state)
 {
 	struct msm_dp_bridge *msm_dp_bridge = to_dp_bridge(drm_bridge);
-	struct msm_dp *dp = msm_dp_bridge->msm_dp_display;
+	struct msm_dp *msm_dp_display = msm_dp_bridge->msm_dp_display;
 	struct drm_crtc *crtc;
 	struct drm_crtc_state *crtc_state;
 	int rc = 0;
-	struct msm_dp_display_private *msm_dp_display;
-	bool force_link_train = false;
+	struct msm_dp_display_private *dp;
 
-	msm_dp_display = container_of(dp, struct msm_dp_display_private, msm_dp_display);
+	dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display);
 
 	crtc = drm_atomic_get_new_crtc_for_encoder(state,
 						   drm_bridge->encoder);
@@ -1391,42 +1428,29 @@ void msm_dp_bridge_atomic_enable(struct drm_bridge *drm_bridge,
 		return;
 	crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
 
-	if (dp->is_edp)
-		msm_dp_hpd_plug_handle(msm_dp_display);
-
-	if (pm_runtime_resume_and_get(&dp->pdev->dev)) {
-		DRM_ERROR("failed to pm_runtime_resume\n");
-		return;
-	}
-
-	if (msm_dp_display->link->sink_count == 0)
-		return;
-
-	rc = msm_dp_display_set_mode(dp, &crtc_state->adjusted_mode, msm_dp_display->panel);
+	rc = msm_dp_display_set_mode(msm_dp_display, &crtc_state->adjusted_mode, dp->panel);
 	if (rc) {
 		DRM_ERROR("Failed to perform a mode set, rc=%d\n", rc);
 		return;
 	}
 
-	if (!dp->power_on) {
-		msm_dp_display_host_phy_init(msm_dp_display);
-		force_link_train = true;
+	rc = msm_dp_display_prepare(dp);
+	if (rc) {
+		DRM_ERROR("DP display prepare failed, rc=%d\n", rc);
+		return;
 	}
 
-	rc = msm_dp_ctrl_on_link(msm_dp_display->ctrl);
+	rc = msm_dp_display_enable(dp);
 	if (rc)
-		DRM_ERROR("Failed link training (rc=%d)\n", rc);
-	// TODO: schedule drm_connector_set_link_status_property()
+		DRM_ERROR("DP display enable failed, rc=%d\n", rc);
 
-	msm_dp_display_enable(msm_dp_display, force_link_train);
-
-	rc = msm_dp_display_post_enable(dp);
+	rc = msm_dp_display_post_enable(msm_dp_display);
 	if (rc) {
 		DRM_ERROR("DP display post enable failed, rc=%d\n", rc);
-		msm_dp_display_disable(msm_dp_display);
+		msm_dp_display_disable(dp);
 	}
 
-	drm_dbg_dp(dp->drm_dev, "type=%d Done\n", dp->connector_type);
+	drm_dbg_dp(msm_dp_display->drm_dev, "type=%d Done\n", msm_dp_display->connector_type);
 }
 
 void msm_dp_bridge_atomic_disable(struct drm_bridge *drm_bridge,
@@ -1441,6 +1465,15 @@ void msm_dp_bridge_atomic_disable(struct drm_bridge *drm_bridge,
 	msm_dp_ctrl_push_idle(msm_dp_display->ctrl);
 }
 
+static void msm_dp_display_unprepare(struct msm_dp_display_private *dp)
+{
+	struct msm_dp *msm_dp_display = &dp->msm_dp_display;
+
+	pm_runtime_put_sync(&msm_dp_display->pdev->dev);
+
+	drm_dbg_dp(dp->drm_dev, "type=%d Done\n", msm_dp_display->connector_type);
+}
+
 void msm_dp_bridge_atomic_post_disable(struct drm_bridge *drm_bridge,
 				       struct drm_atomic_state *state)
 {
@@ -1453,11 +1486,11 @@ void msm_dp_bridge_atomic_post_disable(struct drm_bridge *drm_bridge,
 	if (dp->is_edp)
 		msm_dp_hpd_unplug_handle(msm_dp_display);
 
-	msm_dp_display_disable(msm_dp_display);
+	msm_dp_display_audio_notify_disable(msm_dp_display);
 
-	drm_dbg_dp(dp->drm_dev, "type=%d Done\n", dp->connector_type);
+	msm_dp_display_disable(msm_dp_display);
 
-	pm_runtime_put_sync(&dp->pdev->dev);
+	msm_dp_display_unprepare(msm_dp_display);
 }
 
 void msm_dp_bridge_hpd_enable(struct drm_bridge *bridge)

-- 
2.43.0


^ permalink raw reply	[flat|nested] 128+ messages in thread

* [PATCH v4 04/39] drm/msm/dp: re-arrange dp_display_disable() into functional parts
  2026-04-10  9:33 [PATCH v4 00/39] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
                   ` (2 preceding siblings ...)
  2026-04-10  9:33 ` [PATCH v4 03/39] drm/msm/dp: break up dp_display_enable into two parts Yongxing Mou
@ 2026-04-10  9:33 ` Yongxing Mou
  2026-04-10  9:33 ` [PATCH v4 05/39] drm/msm/dp: splite msm_dp_ctrl_config_ctrl() into link parts and stream parts Yongxing Mou
                   ` (36 subsequent siblings)
  40 siblings, 0 replies; 128+ messages in thread
From: Yongxing Mou @ 2026-04-10  9:33 UTC (permalink / raw)
  To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang
  Cc: linux-arm-msm, dri-devel, freedreno, linux-kernel, Yongxing Mou,
	Abhinav Kumar, Dmitry Baryshkov

From: Abhinav Kumar <quic_abhinavk@quicinc.com>

dp_display_disable() handles special case of when monitor is
disconnected from the dongle while the dongle stays connected
thereby needing a separate function dp_ctrl_off_link_stream()
for this. However with a slight rework this can still be handled
by keeping common paths same for regular and special case.

Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
---
 drivers/gpu/drm/msm/dp/dp_ctrl.c    | 19 +------------------
 drivers/gpu/drm/msm/dp/dp_ctrl.h    |  2 +-
 drivers/gpu/drm/msm/dp/dp_display.c | 10 +++++++++-
 3 files changed, 11 insertions(+), 20 deletions(-)

diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c
index 0fd4a7b6d931..476346e3ac19 100644
--- a/drivers/gpu/drm/msm/dp/dp_ctrl.c
+++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c
@@ -2576,7 +2576,7 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl)
 	return ret;
 }
 
-void msm_dp_ctrl_off_link_stream(struct msm_dp_ctrl *msm_dp_ctrl)
+void msm_dp_ctrl_reinit_phy(struct msm_dp_ctrl *msm_dp_ctrl)
 {
 	struct msm_dp_ctrl_private *ctrl;
 	struct phy *phy;
@@ -2584,23 +2584,6 @@ void msm_dp_ctrl_off_link_stream(struct msm_dp_ctrl *msm_dp_ctrl)
 	ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl);
 	phy = ctrl->phy;
 
-	msm_dp_panel_disable_vsc_sdp(ctrl->panel);
-
-	/* set dongle to D3 (power off) mode */
-	msm_dp_link_psm_config(ctrl->link, &ctrl->panel->link_info, true);
-
-	msm_dp_ctrl_mainlink_disable(ctrl);
-
-	if (ctrl->stream_clks_on) {
-		clk_disable_unprepare(ctrl->pixel_clk);
-		ctrl->stream_clks_on = false;
-	}
-
-	dev_pm_opp_set_rate(ctrl->dev, 0);
-	msm_dp_ctrl_link_clk_disable(&ctrl->msm_dp_ctrl);
-
-	phy_power_off(phy);
-
 	/* aux channel down, reinit phy */
 	phy_exit(phy);
 	phy_init(phy);
diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.h b/drivers/gpu/drm/msm/dp/dp_ctrl.h
index 1497f1a8fc2f..5d615f50d13b 100644
--- a/drivers/gpu/drm/msm/dp/dp_ctrl.h
+++ b/drivers/gpu/drm/msm/dp/dp_ctrl.h
@@ -19,7 +19,6 @@ struct phy;
 int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl);
 int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl);
 int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train);
-void msm_dp_ctrl_off_link_stream(struct msm_dp_ctrl *msm_dp_ctrl);
 void msm_dp_ctrl_off(struct msm_dp_ctrl *msm_dp_ctrl);
 void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl);
 irqreturn_t msm_dp_ctrl_isr(struct msm_dp_ctrl *msm_dp_ctrl);
@@ -46,4 +45,5 @@ void msm_dp_ctrl_core_clk_disable(struct msm_dp_ctrl *msm_dp_ctrl);
 void msm_dp_ctrl_enable_irq(struct msm_dp_ctrl *msm_dp_ctrl);
 void msm_dp_ctrl_disable_irq(struct msm_dp_ctrl *msm_dp_ctrl);
 
+void msm_dp_ctrl_reinit_phy(struct msm_dp_ctrl *msm_dp_ctrl);
 #endif /* _DP_CTRL_H_ */
diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
index 5ecbc83c3838..58c46d5ab4d8 100644
--- a/drivers/gpu/drm/msm/dp/dp_display.c
+++ b/drivers/gpu/drm/msm/dp/dp_display.c
@@ -719,12 +719,20 @@ static int msm_dp_display_disable(struct msm_dp_display_private *dp)
 	if (!msm_dp_display->power_on)
 		return 0;
 
+	msm_dp_panel_disable_vsc_sdp(dp->panel);
+
+	/* dongle is still connected but sinks are disconnected */
 	if (dp->link->sink_count == 0) {
 		/*
 		 * irq_hpd with sink_count = 0
 		 * hdmi unplugged out of dongle
 		 */
-		msm_dp_ctrl_off_link_stream(dp->ctrl);
+
+		/* set dongle to D3 (power off) mode */
+		msm_dp_link_psm_config(dp->link, &dp->panel->link_info, true);
+		msm_dp_ctrl_off(dp->ctrl);
+		/* re-init the PHY so that we can listen to Dongle disconnect */
+		msm_dp_ctrl_reinit_phy(dp->ctrl);
 	} else {
 		/*
 		 * unplugged interrupt

-- 
2.43.0


^ permalink raw reply	[flat|nested] 128+ messages in thread

* [PATCH v4 05/39] drm/msm/dp: splite msm_dp_ctrl_config_ctrl() into link parts and stream parts
  2026-04-10  9:33 [PATCH v4 00/39] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
                   ` (3 preceding siblings ...)
  2026-04-10  9:33 ` [PATCH v4 04/39] drm/msm/dp: re-arrange dp_display_disable() into functional parts Yongxing Mou
@ 2026-04-10  9:33 ` Yongxing Mou
  2026-04-11 17:34   ` Dmitry Baryshkov
  2026-04-10  9:33 ` [PATCH v4 06/39] drm/msm/dp: extract MISC1_MISC0 configuration into a separate function Yongxing Mou
                   ` (35 subsequent siblings)
  40 siblings, 1 reply; 128+ messages in thread
From: Yongxing Mou @ 2026-04-10  9:33 UTC (permalink / raw)
  To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang
  Cc: linux-arm-msm, dri-devel, freedreno, linux-kernel, Yongxing Mou

The DP_CONFIGURATION_CTRL register contains both link-level and
stream-specific fields. Currently, msm_dp_ctrl_config_ctrl() configures
all of them together. Separates the configuration into link parts and
streams part for support MST.

Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
---
 drivers/gpu/drm/msm/dp/dp_ctrl.c | 43 ++++++++++++++++++++++++++--------------
 1 file changed, 28 insertions(+), 15 deletions(-)

diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c
index 476346e3ac19..85315467b5d0 100644
--- a/drivers/gpu/drm/msm/dp/dp_ctrl.c
+++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c
@@ -388,26 +388,41 @@ void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl)
 	drm_dbg_dp(ctrl->drm_dev, "mainlink off\n");
 }
 
-static void msm_dp_ctrl_config_ctrl(struct msm_dp_ctrl_private *ctrl)
+static void msm_dp_ctrl_config_ctrl_streams(struct msm_dp_ctrl_private *ctrl,
+					    struct msm_dp_panel *msm_dp_panel)
 {
 	u32 config = 0, tbd;
+
+	config = msm_dp_read_link(ctrl, REG_DP_CONFIGURATION_CTRL);
+
+	if (msm_dp_panel->msm_dp_mode.out_fmt_is_yuv_420)
+		config |= DP_CONFIGURATION_CTRL_RGB_YUV; /* YUV420 */
+
+	tbd = msm_dp_link_get_test_bits_depth(ctrl->link,
+					      msm_dp_panel->msm_dp_mode.bpp);
+
+	config |= tbd << DP_CONFIGURATION_CTRL_BPC_SHIFT;
+
+	if (msm_dp_panel->psr_cap.version)
+		config |= DP_CONFIGURATION_CTRL_SEND_VSC;
+
+	drm_dbg_dp(ctrl->drm_dev, "stream DP_CONFIGURATION_CTRL=0x%x\n", config);
+
+	msm_dp_write_link(ctrl, REG_DP_CONFIGURATION_CTRL, config);
+}
+
+static void msm_dp_ctrl_config_ctrl_link(struct msm_dp_ctrl_private *ctrl)
+{
+	u32 config = 0;
 	const u8 *dpcd = ctrl->panel->dpcd;
 
 	/* Default-> LSCLK DIV: 1/4 LCLK  */
 	config |= (2 << DP_CONFIGURATION_CTRL_LSCLK_DIV_SHIFT);
 
-	if (ctrl->panel->msm_dp_mode.out_fmt_is_yuv_420)
-		config |= DP_CONFIGURATION_CTRL_RGB_YUV; /* YUV420 */
-
 	/* Scrambler reset enable */
 	if (drm_dp_alternate_scrambler_reset_cap(dpcd))
 		config |= DP_CONFIGURATION_CTRL_ASSR;
 
-	tbd = msm_dp_link_get_test_bits_depth(ctrl->link,
-			ctrl->panel->msm_dp_mode.bpp);
-
-	config |= tbd << DP_CONFIGURATION_CTRL_BPC_SHIFT;
-
 	/* Num of Lanes */
 	config |= ((ctrl->link->link_params.num_lanes - 1)
 			<< DP_CONFIGURATION_CTRL_NUM_OF_LANES_SHIFT);
@@ -421,10 +436,7 @@ static void msm_dp_ctrl_config_ctrl(struct msm_dp_ctrl_private *ctrl)
 	config |= DP_CONFIGURATION_CTRL_STATIC_DYNAMIC_CN;
 	config |= DP_CONFIGURATION_CTRL_SYNC_ASYNC_CLK;
 
-	if (ctrl->panel->psr_cap.version)
-		config |= DP_CONFIGURATION_CTRL_SEND_VSC;
-
-	drm_dbg_dp(ctrl->drm_dev, "DP_CONFIGURATION_CTRL=0x%x\n", config);
+	drm_dbg_dp(ctrl->drm_dev, "link DP_CONFIGURATION_CTRL=0x%x\n", config);
 
 	msm_dp_write_link(ctrl, REG_DP_CONFIGURATION_CTRL, config);
 }
@@ -450,7 +462,8 @@ static void msm_dp_ctrl_configure_source_params(struct msm_dp_ctrl_private *ctrl
 	msm_dp_ctrl_lane_mapping(ctrl);
 	msm_dp_setup_peripheral_flush(ctrl);
 
-	msm_dp_ctrl_config_ctrl(ctrl);
+	msm_dp_ctrl_config_ctrl_link(ctrl);
+	msm_dp_ctrl_config_ctrl_streams(ctrl, ctrl->panel);
 
 	test_bits_depth = msm_dp_link_get_test_bits_depth(ctrl->link, ctrl->panel->msm_dp_mode.bpp);
 	colorimetry_cfg = msm_dp_link_get_colorimetry_config(ctrl->link);
@@ -1628,7 +1641,7 @@ static int msm_dp_ctrl_link_train(struct msm_dp_ctrl_private *ctrl,
 	u8 assr;
 	struct msm_dp_link_info link_info = {0};
 
-	msm_dp_ctrl_config_ctrl(ctrl);
+	msm_dp_ctrl_config_ctrl_link(ctrl);
 
 	link_info.num_lanes = ctrl->link->link_params.num_lanes;
 	link_info.rate = ctrl->link->link_params.rate;

-- 
2.43.0


^ permalink raw reply	[flat|nested] 128+ messages in thread

* [PATCH v4 06/39] drm/msm/dp: extract MISC1_MISC0 configuration into a separate function
  2026-04-10  9:33 [PATCH v4 00/39] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
                   ` (4 preceding siblings ...)
  2026-04-10  9:33 ` [PATCH v4 05/39] drm/msm/dp: splite msm_dp_ctrl_config_ctrl() into link parts and stream parts Yongxing Mou
@ 2026-04-10  9:33 ` Yongxing Mou
  2026-04-10  9:33 ` [PATCH v4 07/39] drm/msm/dp: split link setup from source params Yongxing Mou
                   ` (34 subsequent siblings)
  40 siblings, 0 replies; 128+ messages in thread
From: Yongxing Mou @ 2026-04-10  9:33 UTC (permalink / raw)
  To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang
  Cc: linux-arm-msm, dri-devel, freedreno, linux-kernel, Yongxing Mou,
	Dmitry Baryshkov

Refactor the MISC1_MISC0 register configuration into a standalone helper
function to support MST.

Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
---
 drivers/gpu/drm/msm/dp/dp_ctrl.c | 23 +++++++++++++++--------
 1 file changed, 15 insertions(+), 8 deletions(-)

diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c
index 85315467b5d0..fd6caebae148 100644
--- a/drivers/gpu/drm/msm/dp/dp_ctrl.c
+++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c
@@ -455,17 +455,13 @@ static void msm_dp_ctrl_lane_mapping(struct msm_dp_ctrl_private *ctrl)
 			ln_mapping);
 }
 
-static void msm_dp_ctrl_configure_source_params(struct msm_dp_ctrl_private *ctrl)
+static void msm_dp_ctrl_config_misc1_misc0(struct msm_dp_ctrl_private *ctrl,
+					   struct msm_dp_panel *msm_dp_panel)
 {
 	u32 colorimetry_cfg, test_bits_depth, misc_val;
 
-	msm_dp_ctrl_lane_mapping(ctrl);
-	msm_dp_setup_peripheral_flush(ctrl);
-
-	msm_dp_ctrl_config_ctrl_link(ctrl);
-	msm_dp_ctrl_config_ctrl_streams(ctrl, ctrl->panel);
-
-	test_bits_depth = msm_dp_link_get_test_bits_depth(ctrl->link, ctrl->panel->msm_dp_mode.bpp);
+	test_bits_depth = msm_dp_link_get_test_bits_depth(ctrl->link,
+							  msm_dp_panel->msm_dp_mode.bpp);
 	colorimetry_cfg = msm_dp_link_get_colorimetry_config(ctrl->link);
 
 	misc_val = msm_dp_read_link(ctrl, REG_DP_MISC1_MISC0);
@@ -479,6 +475,17 @@ static void msm_dp_ctrl_configure_source_params(struct msm_dp_ctrl_private *ctrl
 
 	drm_dbg_dp(ctrl->drm_dev, "misc settings = 0x%x\n", misc_val);
 	msm_dp_write_link(ctrl, REG_DP_MISC1_MISC0, misc_val);
+}
+
+static void msm_dp_ctrl_configure_source_params(struct msm_dp_ctrl_private *ctrl)
+{
+	msm_dp_ctrl_lane_mapping(ctrl);
+	msm_dp_setup_peripheral_flush(ctrl);
+
+	msm_dp_ctrl_config_ctrl_link(ctrl);
+	msm_dp_ctrl_config_ctrl_streams(ctrl, ctrl->panel);
+
+	msm_dp_ctrl_config_misc1_misc0(ctrl, ctrl->panel);
 
 	msm_dp_panel_timing_cfg(ctrl->panel, ctrl->msm_dp_ctrl.wide_bus_en);
 }

-- 
2.43.0


^ permalink raw reply	[flat|nested] 128+ messages in thread

* [PATCH v4 07/39] drm/msm/dp: split link setup from source params
  2026-04-10  9:33 [PATCH v4 00/39] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
                   ` (5 preceding siblings ...)
  2026-04-10  9:33 ` [PATCH v4 06/39] drm/msm/dp: extract MISC1_MISC0 configuration into a separate function Yongxing Mou
@ 2026-04-10  9:33 ` Yongxing Mou
  2026-04-11 17:36   ` Dmitry Baryshkov
  2026-04-10  9:33 ` [PATCH v4 08/39] drm/msm/dp: allow dp_ctrl stream APIs to use any panel passed to it Yongxing Mou
                   ` (33 subsequent siblings)
  40 siblings, 1 reply; 128+ messages in thread
From: Yongxing Mou @ 2026-04-10  9:33 UTC (permalink / raw)
  To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang
  Cc: linux-arm-msm, dri-devel, freedreno, linux-kernel, Yongxing Mou

msm_dp_ctrl_configure_source_params() should only handle stream-related
configuration. Move the link setup out of it so MST can program link and
stream settings separately.

Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
---
 drivers/gpu/drm/msm/dp/dp_ctrl.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c
index fd6caebae148..cd58968d4e14 100644
--- a/drivers/gpu/drm/msm/dp/dp_ctrl.c
+++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c
@@ -479,10 +479,6 @@ static void msm_dp_ctrl_config_misc1_misc0(struct msm_dp_ctrl_private *ctrl,
 
 static void msm_dp_ctrl_configure_source_params(struct msm_dp_ctrl_private *ctrl)
 {
-	msm_dp_ctrl_lane_mapping(ctrl);
-	msm_dp_setup_peripheral_flush(ctrl);
-
-	msm_dp_ctrl_config_ctrl_link(ctrl);
 	msm_dp_ctrl_config_ctrl_streams(ctrl, ctrl->panel);
 
 	msm_dp_ctrl_config_misc1_misc0(ctrl, ctrl->panel);
@@ -2571,6 +2567,10 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl)
 	 */
 	reinit_completion(&ctrl->video_comp);
 
+	msm_dp_ctrl_lane_mapping(ctrl);
+	msm_dp_setup_peripheral_flush(ctrl);
+	msm_dp_ctrl_config_ctrl_link(ctrl);
+
 	msm_dp_ctrl_configure_source_params(ctrl);
 
 	msm_dp_ctrl_config_msa(ctrl,

-- 
2.43.0


^ permalink raw reply	[flat|nested] 128+ messages in thread

* [PATCH v4 08/39] drm/msm/dp: allow dp_ctrl stream APIs to use any panel passed to it
  2026-04-10  9:33 [PATCH v4 00/39] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
                   ` (6 preceding siblings ...)
  2026-04-10  9:33 ` [PATCH v4 07/39] drm/msm/dp: split link setup from source params Yongxing Mou
@ 2026-04-10  9:33 ` Yongxing Mou
  2026-04-11 17:38   ` Dmitry Baryshkov
  2026-04-10  9:33 ` [PATCH v4 09/39] drm/msm/dp: move the pixel clock control to its own API Yongxing Mou
                   ` (32 subsequent siblings)
  40 siblings, 1 reply; 128+ messages in thread
From: Yongxing Mou @ 2026-04-10  9:33 UTC (permalink / raw)
  To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang
  Cc: linux-arm-msm, dri-devel, freedreno, linux-kernel, Yongxing Mou,
	Abhinav Kumar

From: Abhinav Kumar <quic_abhinavk@quicinc.com>

Currently, the dp_ctrl stream APIs operate on their own dp_panel
which is stored inside the dp_ctrl's private struct. However with MST,
the stored panel represents the fixed link and not the sinks which
are hotplugged. Allow the stream related APIs to work on the panel
which is passed to them rather than the stored one. For SST cases,
this shall continue to use the stored dp_panel.

Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
---
 drivers/gpu/drm/msm/dp/dp_ctrl.c    | 21 +++++++++++----------
 drivers/gpu/drm/msm/dp/dp_ctrl.h    |  2 +-
 drivers/gpu/drm/msm/dp/dp_display.c |  2 +-
 3 files changed, 13 insertions(+), 12 deletions(-)

diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c
index cd58968d4e14..d0eed8c7df45 100644
--- a/drivers/gpu/drm/msm/dp/dp_ctrl.c
+++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c
@@ -477,13 +477,14 @@ static void msm_dp_ctrl_config_misc1_misc0(struct msm_dp_ctrl_private *ctrl,
 	msm_dp_write_link(ctrl, REG_DP_MISC1_MISC0, misc_val);
 }
 
-static void msm_dp_ctrl_configure_source_params(struct msm_dp_ctrl_private *ctrl)
+static void msm_dp_ctrl_configure_source_params(struct msm_dp_ctrl_private *ctrl,
+						struct msm_dp_panel *msm_dp_panel)
 {
-	msm_dp_ctrl_config_ctrl_streams(ctrl, ctrl->panel);
+	msm_dp_ctrl_config_ctrl_streams(ctrl, msm_dp_panel);
 
-	msm_dp_ctrl_config_misc1_misc0(ctrl, ctrl->panel);
+	msm_dp_ctrl_config_misc1_misc0(ctrl, msm_dp_panel);
 
-	msm_dp_panel_timing_cfg(ctrl->panel, ctrl->msm_dp_ctrl.wide_bus_en);
+	msm_dp_panel_timing_cfg(msm_dp_panel, ctrl->msm_dp_ctrl.wide_bus_en);
 }
 
 /*
@@ -2523,7 +2524,7 @@ int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, bool force_li
 	return ret;
 }
 
-int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl)
+int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *msm_dp_panel)
 {
 	int ret = 0;
 	bool mainlink_ready = false;
@@ -2536,10 +2537,10 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl)
 
 	ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl);
 
-	pixel_rate_orig = ctrl->panel->msm_dp_mode.drm_mode.clock;
+	pixel_rate_orig = msm_dp_panel->msm_dp_mode.drm_mode.clock;
 	pixel_rate = pixel_rate_orig;
 
-	if (msm_dp_ctrl->wide_bus_en || ctrl->panel->msm_dp_mode.out_fmt_is_yuv_420)
+	if (msm_dp_ctrl->wide_bus_en || msm_dp_panel->msm_dp_mode.out_fmt_is_yuv_420)
 		pixel_rate >>= 1;
 
 	drm_dbg_dp(ctrl->drm_dev, "pixel_rate=%lu\n", pixel_rate);
@@ -2571,14 +2572,14 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl)
 	msm_dp_setup_peripheral_flush(ctrl);
 	msm_dp_ctrl_config_ctrl_link(ctrl);
 
-	msm_dp_ctrl_configure_source_params(ctrl);
+	msm_dp_ctrl_configure_source_params(ctrl, msm_dp_panel);
 
 	msm_dp_ctrl_config_msa(ctrl,
 		ctrl->link->link_params.rate,
 		pixel_rate_orig,
-		ctrl->panel->msm_dp_mode.out_fmt_is_yuv_420);
+		msm_dp_panel->msm_dp_mode.out_fmt_is_yuv_420);
 
-	msm_dp_panel_clear_dsc_dto(ctrl->panel);
+	msm_dp_panel_clear_dsc_dto(msm_dp_panel);
 
 	msm_dp_ctrl_setup_tr_unit(ctrl);
 
diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.h b/drivers/gpu/drm/msm/dp/dp_ctrl.h
index 5d615f50d13b..32196e97cbe9 100644
--- a/drivers/gpu/drm/msm/dp/dp_ctrl.h
+++ b/drivers/gpu/drm/msm/dp/dp_ctrl.h
@@ -17,7 +17,7 @@ struct msm_dp_ctrl {
 struct phy;
 
 int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl);
-int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl);
+int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *msm_dp_panel);
 int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train);
 void msm_dp_ctrl_off(struct msm_dp_ctrl *msm_dp_ctrl);
 void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl);
diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
index 58c46d5ab4d8..1bf1335712bc 100644
--- a/drivers/gpu/drm/msm/dp/dp_display.c
+++ b/drivers/gpu/drm/msm/dp/dp_display.c
@@ -666,7 +666,7 @@ static int msm_dp_display_enable(struct msm_dp_display_private *dp)
 		return 0;
 	}
 
-	rc = msm_dp_ctrl_on_stream(dp->ctrl);
+	rc = msm_dp_ctrl_on_stream(dp->ctrl, dp->panel);
 	if (!rc)
 		msm_dp_display->power_on = true;
 

-- 
2.43.0


^ permalink raw reply	[flat|nested] 128+ messages in thread

* [PATCH v4 09/39] drm/msm/dp: move the pixel clock control to its own API
  2026-04-10  9:33 [PATCH v4 00/39] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
                   ` (7 preceding siblings ...)
  2026-04-10  9:33 ` [PATCH v4 08/39] drm/msm/dp: allow dp_ctrl stream APIs to use any panel passed to it Yongxing Mou
@ 2026-04-10  9:33 ` Yongxing Mou
  2026-04-10  9:33 ` [PATCH v4 10/39] drm/msm/dp: split dp_ctrl_off() into stream and link parts Yongxing Mou
                   ` (31 subsequent siblings)
  40 siblings, 0 replies; 128+ messages in thread
From: Yongxing Mou @ 2026-04-10  9:33 UTC (permalink / raw)
  To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang
  Cc: linux-arm-msm, dri-devel, freedreno, linux-kernel, Yongxing Mou,
	Abhinav Kumar, Dmitry Baryshkov

Enable/Disable of DP pixel clock happens in multiple code paths
leading to code duplication. Move it into individual helpers so that
the helpers can be called wherever necessary.

Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
---
 drivers/gpu/drm/msm/dp/dp_ctrl.c | 79 +++++++++++++++++++++-------------------
 1 file changed, 41 insertions(+), 38 deletions(-)

diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c
index d0eed8c7df45..fa62f8f91189 100644
--- a/drivers/gpu/drm/msm/dp/dp_ctrl.c
+++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c
@@ -2176,6 +2176,42 @@ static bool msm_dp_ctrl_send_phy_test_pattern(struct msm_dp_ctrl_private *ctrl)
 	return success;
 }
 
+static int msm_dp_ctrl_on_pixel_clk(struct msm_dp_ctrl_private *ctrl, unsigned long pixel_rate)
+{
+	int ret;
+
+	ret = clk_set_rate(ctrl->pixel_clk, pixel_rate * 1000);
+	if (ret) {
+		DRM_ERROR("Failed to set pixel clock rate. ret=%d\n", ret);
+		return ret;
+	}
+
+	if (ctrl->stream_clks_on) {
+		drm_dbg_dp(ctrl->drm_dev, "pixel clks already enabled\n");
+	} else {
+		ret = clk_prepare_enable(ctrl->pixel_clk);
+		if (ret) {
+			DRM_ERROR("Failed to start pixel clocks. ret=%d\n", ret);
+			return ret;
+		}
+		ctrl->stream_clks_on = true;
+	}
+
+	return ret;
+}
+
+static void msm_dp_ctrl_off_pixel_clk(struct msm_dp_ctrl *msm_dp_ctrl)
+{
+	struct msm_dp_ctrl_private *ctrl;
+
+	ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl);
+
+	if (ctrl->stream_clks_on) {
+		clk_disable_unprepare(ctrl->pixel_clk);
+		ctrl->stream_clks_on = false;
+	}
+}
+
 static int msm_dp_ctrl_process_phy_test_request(struct msm_dp_ctrl_private *ctrl)
 {
 	int ret;
@@ -2201,22 +2237,7 @@ static int msm_dp_ctrl_process_phy_test_request(struct msm_dp_ctrl_private *ctrl
 	}
 
 	pixel_rate = ctrl->panel->msm_dp_mode.drm_mode.clock;
-	ret = clk_set_rate(ctrl->pixel_clk, pixel_rate * 1000);
-	if (ret) {
-		DRM_ERROR("Failed to set pixel clock rate. ret=%d\n", ret);
-		return ret;
-	}
-
-	if (ctrl->stream_clks_on) {
-		drm_dbg_dp(ctrl->drm_dev, "pixel clks already enabled\n");
-	} else {
-		ret = clk_prepare_enable(ctrl->pixel_clk);
-		if (ret) {
-			DRM_ERROR("Failed to start pixel clocks. ret=%d\n", ret);
-			return ret;
-		}
-		ctrl->stream_clks_on = true;
-	}
+	ret = msm_dp_ctrl_on_pixel_clk(ctrl, pixel_rate);
 
 	msm_dp_ctrl_send_phy_test_pattern(ctrl);
 
@@ -2545,22 +2566,9 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *
 
 	drm_dbg_dp(ctrl->drm_dev, "pixel_rate=%lu\n", pixel_rate);
 
-	ret = clk_set_rate(ctrl->pixel_clk, pixel_rate * 1000);
-	if (ret) {
-		DRM_ERROR("Failed to set pixel clock rate. ret=%d\n", ret);
-		goto end;
-	}
-
-	if (ctrl->stream_clks_on) {
-		drm_dbg_dp(ctrl->drm_dev, "pixel clks already enabled\n");
-	} else {
-		ret = clk_prepare_enable(ctrl->pixel_clk);
-		if (ret) {
-			DRM_ERROR("Failed to start pixel clocks. ret=%d\n", ret);
-			goto end;
-		}
-		ctrl->stream_clks_on = true;
-	}
+	ret = msm_dp_ctrl_on_pixel_clk(ctrl, pixel_rate);
+	if (ret)
+		return ret;
 
 	/*
 	 * Set up transfer unit values and set controller state to send
@@ -2593,7 +2601,6 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *
 	drm_dbg_dp(ctrl->drm_dev,
 		"mainlink %s\n", mainlink_ready ? "READY" : "NOT READY");
 
-end:
 	return ret;
 }
 
@@ -2627,11 +2634,7 @@ void msm_dp_ctrl_off(struct msm_dp_ctrl *msm_dp_ctrl)
 
 	msm_dp_ctrl_reset(&ctrl->msm_dp_ctrl);
 
-	if (ctrl->stream_clks_on) {
-		clk_disable_unprepare(ctrl->pixel_clk);
-		ctrl->stream_clks_on = false;
-	}
-
+	msm_dp_ctrl_off_pixel_clk(msm_dp_ctrl);
 	dev_pm_opp_set_rate(ctrl->dev, 0);
 	msm_dp_ctrl_link_clk_disable(&ctrl->msm_dp_ctrl);
 

-- 
2.43.0


^ permalink raw reply	[flat|nested] 128+ messages in thread

* [PATCH v4 10/39] drm/msm/dp: split dp_ctrl_off() into stream and link parts
  2026-04-10  9:33 [PATCH v4 00/39] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
                   ` (8 preceding siblings ...)
  2026-04-10  9:33 ` [PATCH v4 09/39] drm/msm/dp: move the pixel clock control to its own API Yongxing Mou
@ 2026-04-10  9:33 ` Yongxing Mou
  2026-04-10  9:33 ` [PATCH v4 11/39] drm/msm/dp: make bridge helpers use dp_display to allow re-use Yongxing Mou
                   ` (30 subsequent siblings)
  40 siblings, 0 replies; 128+ messages in thread
From: Yongxing Mou @ 2026-04-10  9:33 UTC (permalink / raw)
  To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang
  Cc: linux-arm-msm, dri-devel, freedreno, linux-kernel, Yongxing Mou,
	Abhinav Kumar, Dmitry Baryshkov

From: Abhinav Kumar <quic_abhinavk@quicinc.com>

Split dp_ctrl_off() into stream and link parts so that for MST
cases we can control the link and pixel parts separately.

Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
---
 drivers/gpu/drm/msm/dp/dp_ctrl.c    | 8 ++++----
 drivers/gpu/drm/msm/dp/dp_ctrl.h    | 3 ++-
 drivers/gpu/drm/msm/dp/dp_display.c | 6 ++++--
 3 files changed, 10 insertions(+), 7 deletions(-)

diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c
index fa62f8f91189..120ec00884e5 100644
--- a/drivers/gpu/drm/msm/dp/dp_ctrl.c
+++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c
@@ -2200,7 +2200,7 @@ static int msm_dp_ctrl_on_pixel_clk(struct msm_dp_ctrl_private *ctrl, unsigned l
 	return ret;
 }
 
-static void msm_dp_ctrl_off_pixel_clk(struct msm_dp_ctrl *msm_dp_ctrl)
+void msm_dp_ctrl_off_pixel_clk(struct msm_dp_ctrl *msm_dp_ctrl)
 {
 	struct msm_dp_ctrl_private *ctrl;
 
@@ -2228,7 +2228,8 @@ static int msm_dp_ctrl_process_phy_test_request(struct msm_dp_ctrl_private *ctrl
 	 * running. Add the global reset just before disabling the
 	 * link clocks and core clocks.
 	 */
-	msm_dp_ctrl_off(&ctrl->msm_dp_ctrl);
+	msm_dp_ctrl_off_pixel_clk(&ctrl->msm_dp_ctrl);
+	msm_dp_ctrl_off_link(&ctrl->msm_dp_ctrl);
 
 	ret = msm_dp_ctrl_on_link(&ctrl->msm_dp_ctrl);
 	if (ret) {
@@ -2620,7 +2621,7 @@ void msm_dp_ctrl_reinit_phy(struct msm_dp_ctrl *msm_dp_ctrl)
 			phy, phy->init_count, phy->power_count);
 }
 
-void msm_dp_ctrl_off(struct msm_dp_ctrl *msm_dp_ctrl)
+void msm_dp_ctrl_off_link(struct msm_dp_ctrl *msm_dp_ctrl)
 {
 	struct msm_dp_ctrl_private *ctrl;
 	struct phy *phy;
@@ -2634,7 +2635,6 @@ void msm_dp_ctrl_off(struct msm_dp_ctrl *msm_dp_ctrl)
 
 	msm_dp_ctrl_reset(&ctrl->msm_dp_ctrl);
 
-	msm_dp_ctrl_off_pixel_clk(msm_dp_ctrl);
 	dev_pm_opp_set_rate(ctrl->dev, 0);
 	msm_dp_ctrl_link_clk_disable(&ctrl->msm_dp_ctrl);
 
diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.h b/drivers/gpu/drm/msm/dp/dp_ctrl.h
index 32196e97cbe9..b83be2252a9b 100644
--- a/drivers/gpu/drm/msm/dp/dp_ctrl.h
+++ b/drivers/gpu/drm/msm/dp/dp_ctrl.h
@@ -19,7 +19,8 @@ struct phy;
 int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl);
 int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *msm_dp_panel);
 int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train);
-void msm_dp_ctrl_off(struct msm_dp_ctrl *msm_dp_ctrl);
+void msm_dp_ctrl_off_link(struct msm_dp_ctrl *msm_dp_ctrl);
+void msm_dp_ctrl_off_pixel_clk(struct msm_dp_ctrl *msm_dp_ctrl);
 void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl);
 irqreturn_t msm_dp_ctrl_isr(struct msm_dp_ctrl *msm_dp_ctrl);
 void msm_dp_ctrl_handle_sink_request(struct msm_dp_ctrl *msm_dp_ctrl);
diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
index 1bf1335712bc..31e229ac2393 100644
--- a/drivers/gpu/drm/msm/dp/dp_display.c
+++ b/drivers/gpu/drm/msm/dp/dp_display.c
@@ -730,7 +730,8 @@ static int msm_dp_display_disable(struct msm_dp_display_private *dp)
 
 		/* set dongle to D3 (power off) mode */
 		msm_dp_link_psm_config(dp->link, &dp->panel->link_info, true);
-		msm_dp_ctrl_off(dp->ctrl);
+		msm_dp_ctrl_off_pixel_clk(dp->ctrl);
+		msm_dp_ctrl_off_link(dp->ctrl);
 		/* re-init the PHY so that we can listen to Dongle disconnect */
 		msm_dp_ctrl_reinit_phy(dp->ctrl);
 	} else {
@@ -738,7 +739,8 @@ static int msm_dp_display_disable(struct msm_dp_display_private *dp)
 		 * unplugged interrupt
 		 * dongle unplugged out of DUT
 		 */
-		msm_dp_ctrl_off(dp->ctrl);
+		msm_dp_ctrl_off_pixel_clk(dp->ctrl);
+		msm_dp_ctrl_off_link(dp->ctrl);
 		msm_dp_display_host_phy_exit(dp);
 	}
 

-- 
2.43.0


^ permalink raw reply	[flat|nested] 128+ messages in thread

* [PATCH v4 11/39] drm/msm/dp: make bridge helpers use dp_display to allow re-use
  2026-04-10  9:33 [PATCH v4 00/39] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
                   ` (9 preceding siblings ...)
  2026-04-10  9:33 ` [PATCH v4 10/39] drm/msm/dp: split dp_ctrl_off() into stream and link parts Yongxing Mou
@ 2026-04-10  9:33 ` Yongxing Mou
  2026-04-11 17:41   ` Dmitry Baryshkov
  2026-04-10  9:33 ` [PATCH v4 12/39] drm/msm/dp: separate dp_display_prepare() into its own API Yongxing Mou
                   ` (29 subsequent siblings)
  40 siblings, 1 reply; 128+ messages in thread
From: Yongxing Mou @ 2026-04-10  9:33 UTC (permalink / raw)
  To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang
  Cc: linux-arm-msm, dri-devel, freedreno, linux-kernel, Yongxing Mou,
	Abhinav Kumar

From: Abhinav Kumar <quic_abhinavk@quicinc.com>

dp_bridge helpers take drm_bridge as an input and extract the
dp_display object to be used in the dp_display module. Rather than
doing it in a roundabout way, directly pass the dp_display object
to these helpers so that the MST bridge can also re-use the same
helpers.

Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
---
 drivers/gpu/drm/msm/dp/dp_display.c | 29 +++++++++------------------
 drivers/gpu/drm/msm/dp/dp_display.h |  7 +++++++
 drivers/gpu/drm/msm/dp/dp_drm.c     | 39 ++++++++++++++++++++++++++++++++++++-
 drivers/gpu/drm/msm/dp/dp_drm.h     |  9 ---------
 4 files changed, 54 insertions(+), 30 deletions(-)

diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
index 31e229ac2393..c7dc861301de 100644
--- a/drivers/gpu/drm/msm/dp/dp_display.c
+++ b/drivers/gpu/drm/msm/dp/dp_display.c
@@ -752,24 +752,21 @@ static int msm_dp_display_disable(struct msm_dp_display_private *dp)
 
 /**
  * msm_dp_bridge_mode_valid - callback to determine if specified mode is valid
- * @bridge: Pointer to drm bridge structure
+ * @dp: Pointer to dp display structure
  * @info: display info
  * @mode: Pointer to drm mode structure
  * Returns: Validity status for specified mode
  */
-enum drm_mode_status msm_dp_bridge_mode_valid(struct drm_bridge *bridge,
-					  const struct drm_display_info *info,
-					  const struct drm_display_mode *mode)
+enum drm_mode_status msm_dp_display_mode_valid(struct msm_dp *dp,
+					       const struct drm_display_info *info,
+					       const struct drm_display_mode *mode)
 {
 	const u32 num_components = 3, default_bpp = 24;
 	struct msm_dp_display_private *msm_dp_display;
 	struct msm_dp_link_info *link_info;
 	u32 mode_rate_khz = 0, supported_rate_khz = 0, mode_bpp = 0;
-	struct msm_dp *dp;
 	int mode_pclk_khz = mode->clock;
 
-	dp = to_dp_bridge(bridge)->msm_dp_display;
-
 	if (!dp || !mode_pclk_khz || !dp->connector) {
 		DRM_ERROR("invalid params\n");
 		return -EINVAL;
@@ -1420,11 +1417,9 @@ int msm_dp_modeset_init(struct msm_dp *msm_dp_display, struct drm_device *dev,
 	return 0;
 }
 
-void msm_dp_bridge_atomic_enable(struct drm_bridge *drm_bridge,
-				 struct drm_atomic_state *state)
+void msm_dp_display_atomic_enable(struct msm_dp *msm_dp_display,
+				  struct drm_atomic_state *state)
 {
-	struct msm_dp_bridge *msm_dp_bridge = to_dp_bridge(drm_bridge);
-	struct msm_dp *msm_dp_display = msm_dp_bridge->msm_dp_display;
 	struct drm_crtc *crtc;
 	struct drm_crtc_state *crtc_state;
 	int rc = 0;
@@ -1433,7 +1428,7 @@ void msm_dp_bridge_atomic_enable(struct drm_bridge *drm_bridge,
 	dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display);
 
 	crtc = drm_atomic_get_new_crtc_for_encoder(state,
-						   drm_bridge->encoder);
+						   msm_dp_display->bridge->encoder);
 	if (!crtc)
 		return;
 	crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
@@ -1463,11 +1458,8 @@ void msm_dp_bridge_atomic_enable(struct drm_bridge *drm_bridge,
 	drm_dbg_dp(msm_dp_display->drm_dev, "type=%d Done\n", msm_dp_display->connector_type);
 }
 
-void msm_dp_bridge_atomic_disable(struct drm_bridge *drm_bridge,
-				  struct drm_atomic_state *state)
+void msm_dp_display_atomic_disable(struct msm_dp *dp)
 {
-	struct msm_dp_bridge *msm_dp_bridge = to_dp_bridge(drm_bridge);
-	struct msm_dp *dp = msm_dp_bridge->msm_dp_display;
 	struct msm_dp_display_private *msm_dp_display;
 
 	msm_dp_display = container_of(dp, struct msm_dp_display_private, msm_dp_display);
@@ -1484,11 +1476,8 @@ static void msm_dp_display_unprepare(struct msm_dp_display_private *dp)
 	drm_dbg_dp(dp->drm_dev, "type=%d Done\n", msm_dp_display->connector_type);
 }
 
-void msm_dp_bridge_atomic_post_disable(struct drm_bridge *drm_bridge,
-				       struct drm_atomic_state *state)
+void msm_dp_display_atomic_post_disable(struct msm_dp *dp)
 {
-	struct msm_dp_bridge *msm_dp_bridge = to_dp_bridge(drm_bridge);
-	struct msm_dp *dp = msm_dp_bridge->msm_dp_display;
 	struct msm_dp_display_private *msm_dp_display;
 
 	msm_dp_display = container_of(dp, struct msm_dp_display_private, msm_dp_display);
diff --git a/drivers/gpu/drm/msm/dp/dp_display.h b/drivers/gpu/drm/msm/dp/dp_display.h
index 0b65e16c790d..1a697fb305a7 100644
--- a/drivers/gpu/drm/msm/dp/dp_display.h
+++ b/drivers/gpu/drm/msm/dp/dp_display.h
@@ -33,5 +33,12 @@ void msm_dp_display_signal_audio_start(struct msm_dp *msm_dp_display);
 void msm_dp_display_signal_audio_complete(struct msm_dp *msm_dp_display);
 void msm_dp_display_set_psr(struct msm_dp *dp, bool enter);
 void msm_dp_display_debugfs_init(struct msm_dp *msm_dp_display, struct dentry *dentry, bool is_edp);
+void msm_dp_display_atomic_post_disable(struct msm_dp *dp_display);
+void msm_dp_display_atomic_disable(struct msm_dp *dp_display);
+void msm_dp_display_atomic_enable(struct msm_dp *dp_display,
+				  struct drm_atomic_state *state);
+enum drm_mode_status msm_dp_display_mode_valid(struct msm_dp *dp,
+					       const struct drm_display_info *info,
+					       const struct drm_display_mode *mode);
 
 #endif /* _DP_DISPLAY_H_ */
diff --git a/drivers/gpu/drm/msm/dp/dp_drm.c b/drivers/gpu/drm/msm/dp/dp_drm.c
index af3d3e3a2d84..cb54d7e71f8e 100644
--- a/drivers/gpu/drm/msm/dp/dp_drm.c
+++ b/drivers/gpu/drm/msm/dp/dp_drm.c
@@ -49,6 +49,43 @@ static void msm_dp_bridge_debugfs_init(struct drm_bridge *bridge, struct dentry
 	msm_dp_display_debugfs_init(dp, root, false);
 }
 
+static void msm_dp_bridge_atomic_enable(struct drm_bridge *drm_bridge,
+					struct drm_atomic_state *state)
+{
+	struct msm_dp_bridge *dp_bridge = to_dp_bridge(drm_bridge);
+	struct msm_dp *dp = dp_bridge->msm_dp_display;
+
+	msm_dp_display_atomic_enable(dp, state);
+}
+
+static void msm_dp_bridge_atomic_disable(struct drm_bridge *drm_bridge,
+					 struct drm_atomic_state *state)
+{
+	struct msm_dp_bridge *dp_bridge = to_dp_bridge(drm_bridge);
+	struct msm_dp *dp = dp_bridge->msm_dp_display;
+
+	msm_dp_display_atomic_disable(dp);
+}
+
+static void msm_dp_bridge_atomic_post_disable(struct drm_bridge *drm_bridge,
+					      struct drm_atomic_state *state)
+{
+	struct msm_dp_bridge *dp_bridge = to_dp_bridge(drm_bridge);
+	struct msm_dp *dp = dp_bridge->msm_dp_display;
+
+	msm_dp_display_atomic_post_disable(dp);
+}
+
+static enum drm_mode_status msm_dp_bridge_mode_valid(struct drm_bridge *drm_bridge,
+						     const struct drm_display_info *info,
+						     const struct drm_display_mode *mode)
+{
+	struct msm_dp_bridge *dp_bridge = to_dp_bridge(drm_bridge);
+	struct msm_dp *dp = dp_bridge->msm_dp_display;
+
+	return msm_dp_display_mode_valid(dp, info, mode);
+}
+
 static const struct drm_bridge_funcs msm_dp_bridge_ops = {
 	.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
 	.atomic_destroy_state   = drm_atomic_helper_bridge_destroy_state,
@@ -115,7 +152,7 @@ static void msm_edp_bridge_atomic_enable(struct drm_bridge *drm_bridge,
 		return;
 	}
 
-	msm_dp_bridge_atomic_enable(drm_bridge, state);
+	msm_dp_display_atomic_enable(dp, state);
 }
 
 static void msm_edp_bridge_atomic_disable(struct drm_bridge *drm_bridge,
diff --git a/drivers/gpu/drm/msm/dp/dp_drm.h b/drivers/gpu/drm/msm/dp/dp_drm.h
index 6d4cbb9f3918..da412c788503 100644
--- a/drivers/gpu/drm/msm/dp/dp_drm.h
+++ b/drivers/gpu/drm/msm/dp/dp_drm.h
@@ -27,15 +27,6 @@ int msm_dp_bridge_init(struct msm_dp *msm_dp_display, struct drm_device *dev,
 
 enum drm_connector_status msm_dp_bridge_detect(struct drm_bridge *bridge,
 					       struct drm_connector *connector);
-void msm_dp_bridge_atomic_enable(struct drm_bridge *drm_bridge,
-				 struct drm_atomic_state *state);
-void msm_dp_bridge_atomic_disable(struct drm_bridge *drm_bridge,
-				  struct drm_atomic_state *state);
-void msm_dp_bridge_atomic_post_disable(struct drm_bridge *drm_bridge,
-				       struct drm_atomic_state *state);
-enum drm_mode_status msm_dp_bridge_mode_valid(struct drm_bridge *bridge,
-					  const struct drm_display_info *info,
-					  const struct drm_display_mode *mode);
 void msm_dp_bridge_hpd_enable(struct drm_bridge *bridge);
 void msm_dp_bridge_hpd_disable(struct drm_bridge *bridge);
 void msm_dp_bridge_hpd_notify(struct drm_bridge *bridge,

-- 
2.43.0


^ permalink raw reply	[flat|nested] 128+ messages in thread

* [PATCH v4 12/39] drm/msm/dp: separate dp_display_prepare() into its own API
  2026-04-10  9:33 [PATCH v4 00/39] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
                   ` (10 preceding siblings ...)
  2026-04-10  9:33 ` [PATCH v4 11/39] drm/msm/dp: make bridge helpers use dp_display to allow re-use Yongxing Mou
@ 2026-04-10  9:33 ` Yongxing Mou
  2026-04-11 17:42   ` Dmitry Baryshkov
  2026-04-10  9:33 ` [PATCH v4 13/39] drm/msm/dp: introduce stream_id for each DP panel Yongxing Mou
                   ` (28 subsequent siblings)
  40 siblings, 1 reply; 128+ messages in thread
From: Yongxing Mou @ 2026-04-10  9:33 UTC (permalink / raw)
  To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang
  Cc: linux-arm-msm, dri-devel, freedreno, linux-kernel, Yongxing Mou,
	Abhinav Kumar

From: Abhinav Kumar <quic_abhinavk@quicinc.com>

For MST, the link setup should only be done once when multiple sinks are
enabled, while stream setup may run multiple times for each sink. Split
the link-related preparation out of msm_dp_display_atomic_enable() so it
can be called separately before the per-stream enable path.

Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
---
 drivers/gpu/drm/msm/dp/dp_display.c | 16 +++++++++++-----
 drivers/gpu/drm/msm/dp/dp_display.h |  5 +++--
 drivers/gpu/drm/msm/dp/dp_drm.c     |  6 ++++--
 3 files changed, 18 insertions(+), 9 deletions(-)

diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
index c7dc861301de..32ad00e326ba 100644
--- a/drivers/gpu/drm/msm/dp/dp_display.c
+++ b/drivers/gpu/drm/msm/dp/dp_display.c
@@ -1417,8 +1417,8 @@ int msm_dp_modeset_init(struct msm_dp *msm_dp_display, struct drm_device *dev,
 	return 0;
 }
 
-void msm_dp_display_atomic_enable(struct msm_dp *msm_dp_display,
-				  struct drm_atomic_state *state)
+void msm_dp_display_atomic_prepare(struct msm_dp *msm_dp_display,
+				   struct drm_atomic_state *state)
 {
 	struct drm_crtc *crtc;
 	struct drm_crtc_state *crtc_state;
@@ -1440,10 +1440,16 @@ void msm_dp_display_atomic_enable(struct msm_dp *msm_dp_display,
 	}
 
 	rc = msm_dp_display_prepare(dp);
-	if (rc) {
+	if (rc)
 		DRM_ERROR("DP display prepare failed, rc=%d\n", rc);
-		return;
-	}
+}
+
+void msm_dp_display_atomic_enable(struct msm_dp *msm_dp_display)
+{
+	struct msm_dp_display_private *dp;
+	int rc = 0;
+
+	dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display);
 
 	rc = msm_dp_display_enable(dp);
 	if (rc)
diff --git a/drivers/gpu/drm/msm/dp/dp_display.h b/drivers/gpu/drm/msm/dp/dp_display.h
index 1a697fb305a7..295da7ae0047 100644
--- a/drivers/gpu/drm/msm/dp/dp_display.h
+++ b/drivers/gpu/drm/msm/dp/dp_display.h
@@ -35,8 +35,9 @@ void msm_dp_display_set_psr(struct msm_dp *dp, bool enter);
 void msm_dp_display_debugfs_init(struct msm_dp *msm_dp_display, struct dentry *dentry, bool is_edp);
 void msm_dp_display_atomic_post_disable(struct msm_dp *dp_display);
 void msm_dp_display_atomic_disable(struct msm_dp *dp_display);
-void msm_dp_display_atomic_enable(struct msm_dp *dp_display,
-				  struct drm_atomic_state *state);
+void msm_dp_display_atomic_prepare(struct msm_dp *dp_display,
+				   struct drm_atomic_state *state);
+void msm_dp_display_atomic_enable(struct msm_dp *dp_display);
 enum drm_mode_status msm_dp_display_mode_valid(struct msm_dp *dp,
 					       const struct drm_display_info *info,
 					       const struct drm_display_mode *mode);
diff --git a/drivers/gpu/drm/msm/dp/dp_drm.c b/drivers/gpu/drm/msm/dp/dp_drm.c
index cb54d7e71f8e..0feb757e2db9 100644
--- a/drivers/gpu/drm/msm/dp/dp_drm.c
+++ b/drivers/gpu/drm/msm/dp/dp_drm.c
@@ -55,7 +55,8 @@ static void msm_dp_bridge_atomic_enable(struct drm_bridge *drm_bridge,
 	struct msm_dp_bridge *dp_bridge = to_dp_bridge(drm_bridge);
 	struct msm_dp *dp = dp_bridge->msm_dp_display;
 
-	msm_dp_display_atomic_enable(dp, state);
+	msm_dp_display_atomic_prepare(dp, state);
+	msm_dp_display_atomic_enable(dp);
 }
 
 static void msm_dp_bridge_atomic_disable(struct drm_bridge *drm_bridge,
@@ -152,7 +153,8 @@ static void msm_edp_bridge_atomic_enable(struct drm_bridge *drm_bridge,
 		return;
 	}
 
-	msm_dp_display_atomic_enable(dp, state);
+	msm_dp_display_atomic_prepare(dp, state);
+	msm_dp_display_atomic_enable(dp);
 }
 
 static void msm_edp_bridge_atomic_disable(struct drm_bridge *drm_bridge,

-- 
2.43.0


^ permalink raw reply	[flat|nested] 128+ messages in thread

* [PATCH v4 13/39] drm/msm/dp: introduce stream_id for each DP panel
  2026-04-10  9:33 [PATCH v4 00/39] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
                   ` (11 preceding siblings ...)
  2026-04-10  9:33 ` [PATCH v4 12/39] drm/msm/dp: separate dp_display_prepare() into its own API Yongxing Mou
@ 2026-04-10  9:33 ` Yongxing Mou
  2026-04-11 17:55   ` Dmitry Baryshkov
                     ` (2 more replies)
  2026-04-10  9:33 ` [PATCH v4 14/39] drm/msm/dp: introduce max_streams for DP controller MST support Yongxing Mou
                   ` (27 subsequent siblings)
  40 siblings, 3 replies; 128+ messages in thread
From: Yongxing Mou @ 2026-04-10  9:33 UTC (permalink / raw)
  To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang
  Cc: linux-arm-msm, dri-devel, freedreno, linux-kernel, Yongxing Mou,
	Abhinav Kumar

From: Abhinav Kumar <quic_abhinavk@quicinc.com>

With MST, each DP controller can handle multiple streams.
There shall be one dp_panel for each stream but the dp_display
object shall be shared among them. To represent this abstraction,
create a stream_id for each DP panel which shall be set by the
MST stream. For SST, default this to stream 0.

Use the stream ID to control the pixel clock of that respective
stream by extending the clock handles and state tracking of the
DP pixel clock to an array of max supported streams. The maximum
streams currently is 4.

Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
---
 drivers/gpu/drm/msm/dp/dp_ctrl.c    | 57 +++++++++++++++++++++++--------------
 drivers/gpu/drm/msm/dp/dp_ctrl.h    |  2 +-
 drivers/gpu/drm/msm/dp/dp_display.c | 24 ++++++++++++++--
 drivers/gpu/drm/msm/dp/dp_display.h |  2 ++
 drivers/gpu/drm/msm/dp/dp_panel.h   | 11 +++++++
 5 files changed, 71 insertions(+), 25 deletions(-)

diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c
index 120ec00884e5..fb6396727628 100644
--- a/drivers/gpu/drm/msm/dp/dp_ctrl.c
+++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c
@@ -127,7 +127,7 @@ struct msm_dp_ctrl_private {
 	unsigned int num_link_clks;
 	struct clk_bulk_data *link_clks;
 
-	struct clk *pixel_clk;
+	struct clk *pixel_clk[DP_STREAM_MAX];
 
 	union phy_configure_opts phy_opts;
 
@@ -139,7 +139,7 @@ struct msm_dp_ctrl_private {
 
 	bool core_clks_on;
 	bool link_clks_on;
-	bool stream_clks_on;
+	bool stream_clks_on[DP_STREAM_MAX];
 };
 
 static inline u32 msm_dp_read_ahb(const struct msm_dp_ctrl_private *ctrl, u32 offset)
@@ -2176,39 +2176,40 @@ static bool msm_dp_ctrl_send_phy_test_pattern(struct msm_dp_ctrl_private *ctrl)
 	return success;
 }
 
-static int msm_dp_ctrl_on_pixel_clk(struct msm_dp_ctrl_private *ctrl, unsigned long pixel_rate)
+static int msm_dp_ctrl_on_pixel_clk(struct msm_dp_ctrl_private *ctrl, unsigned long pixel_rate,
+				    enum msm_dp_stream_id stream_id)
 {
 	int ret;
 
-	ret = clk_set_rate(ctrl->pixel_clk, pixel_rate * 1000);
+	ret = clk_set_rate(ctrl->pixel_clk[stream_id], pixel_rate * 1000);
 	if (ret) {
 		DRM_ERROR("Failed to set pixel clock rate. ret=%d\n", ret);
 		return ret;
 	}
 
-	if (ctrl->stream_clks_on) {
+	if (ctrl->stream_clks_on[stream_id]) {
 		drm_dbg_dp(ctrl->drm_dev, "pixel clks already enabled\n");
 	} else {
-		ret = clk_prepare_enable(ctrl->pixel_clk);
+		ret = clk_prepare_enable(ctrl->pixel_clk[stream_id]);
 		if (ret) {
 			DRM_ERROR("Failed to start pixel clocks. ret=%d\n", ret);
 			return ret;
 		}
-		ctrl->stream_clks_on = true;
+		ctrl->stream_clks_on[stream_id] = true;
 	}
 
 	return ret;
 }
 
-void msm_dp_ctrl_off_pixel_clk(struct msm_dp_ctrl *msm_dp_ctrl)
+void msm_dp_ctrl_off_pixel_clk(struct msm_dp_ctrl *msm_dp_ctrl, enum msm_dp_stream_id stream_id)
 {
 	struct msm_dp_ctrl_private *ctrl;
 
 	ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl);
 
-	if (ctrl->stream_clks_on) {
-		clk_disable_unprepare(ctrl->pixel_clk);
-		ctrl->stream_clks_on = false;
+	if (ctrl->stream_clks_on[stream_id]) {
+		clk_disable_unprepare(ctrl->pixel_clk[stream_id]);
+		ctrl->stream_clks_on[stream_id] = false;
 	}
 }
 
@@ -2228,7 +2229,7 @@ static int msm_dp_ctrl_process_phy_test_request(struct msm_dp_ctrl_private *ctrl
 	 * running. Add the global reset just before disabling the
 	 * link clocks and core clocks.
 	 */
-	msm_dp_ctrl_off_pixel_clk(&ctrl->msm_dp_ctrl);
+	msm_dp_ctrl_off_pixel_clk(&ctrl->msm_dp_ctrl, ctrl->panel->stream_id);
 	msm_dp_ctrl_off_link(&ctrl->msm_dp_ctrl);
 
 	ret = msm_dp_ctrl_on_link(&ctrl->msm_dp_ctrl);
@@ -2238,7 +2239,7 @@ static int msm_dp_ctrl_process_phy_test_request(struct msm_dp_ctrl_private *ctrl
 	}
 
 	pixel_rate = ctrl->panel->msm_dp_mode.drm_mode.clock;
-	ret = msm_dp_ctrl_on_pixel_clk(ctrl, pixel_rate);
+	ret = msm_dp_ctrl_on_pixel_clk(ctrl, pixel_rate, ctrl->panel->stream_id);
 
 	msm_dp_ctrl_send_phy_test_pattern(ctrl);
 
@@ -2525,9 +2526,8 @@ int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, bool force_li
 		   ctrl->link->link_params.rate,
 		   ctrl->link->link_params.num_lanes);
 
-	drm_dbg_dp(ctrl->drm_dev,
-		"core_clk_on=%d link_clk_on=%d stream_clk_on=%d\n",
-		ctrl->core_clks_on, ctrl->link_clks_on, ctrl->stream_clks_on);
+	drm_dbg_dp(ctrl->drm_dev, "core_clk_on=%d link_clk_on=%d\n",
+		   ctrl->core_clks_on, ctrl->link_clks_on);
 
 	if (!ctrl->link_clks_on) { /* link clk is off */
 		ret = msm_dp_ctrl_enable_mainlink_clocks(ctrl);
@@ -2567,7 +2567,7 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *
 
 	drm_dbg_dp(ctrl->drm_dev, "pixel_rate=%lu\n", pixel_rate);
 
-	ret = msm_dp_ctrl_on_pixel_clk(ctrl, pixel_rate);
+	ret = msm_dp_ctrl_on_pixel_clk(ctrl, pixel_rate, msm_dp_panel->stream_id);
 	if (ret)
 		return ret;
 
@@ -2629,8 +2629,6 @@ void msm_dp_ctrl_off_link(struct msm_dp_ctrl *msm_dp_ctrl)
 	ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl);
 	phy = ctrl->phy;
 
-	msm_dp_panel_disable_vsc_sdp(ctrl->panel);
-
 	msm_dp_ctrl_mainlink_disable(ctrl);
 
 	msm_dp_ctrl_reset(&ctrl->msm_dp_ctrl);
@@ -2702,6 +2700,13 @@ static const char *ctrl_clks[] = {
 	"ctrl_link_iface",
 };
 
+static const char * const pixel_clks[] = {
+	"stream_pixel",
+	"stream_1_pixel",
+	"stream_2_pixel",
+	"stream_3_pixel",
+};
+
 static int msm_dp_ctrl_clk_init(struct msm_dp_ctrl *msm_dp_ctrl)
 {
 	struct msm_dp_ctrl_private *ctrl;
@@ -2735,9 +2740,17 @@ static int msm_dp_ctrl_clk_init(struct msm_dp_ctrl *msm_dp_ctrl)
 	if (rc)
 		return rc;
 
-	ctrl->pixel_clk = devm_clk_get(dev, "stream_pixel");
-	if (IS_ERR(ctrl->pixel_clk))
-		return PTR_ERR(ctrl->pixel_clk);
+	for (i = DP_STREAM_0; i < DP_STREAM_MAX; i++) {
+		ctrl->pixel_clk[i] = devm_clk_get(dev, pixel_clks[i]);
+
+		if (i == 0 && IS_ERR(ctrl->pixel_clk[i]))
+			return PTR_ERR(ctrl->pixel_clk[i]);
+
+		if (IS_ERR(ctrl->pixel_clk[i])) {
+			DRM_DEBUG_DP("stream %d pixel clock not exist", i);
+			break;
+		}
+	}
 
 	return 0;
 }
diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.h b/drivers/gpu/drm/msm/dp/dp_ctrl.h
index b83be2252a9b..b9f0705b03ba 100644
--- a/drivers/gpu/drm/msm/dp/dp_ctrl.h
+++ b/drivers/gpu/drm/msm/dp/dp_ctrl.h
@@ -20,7 +20,7 @@ int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl);
 int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *msm_dp_panel);
 int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train);
 void msm_dp_ctrl_off_link(struct msm_dp_ctrl *msm_dp_ctrl);
-void msm_dp_ctrl_off_pixel_clk(struct msm_dp_ctrl *msm_dp_ctrl);
+void msm_dp_ctrl_off_pixel_clk(struct msm_dp_ctrl *msm_dp_ctrl, enum msm_dp_stream_id stream_id);
 void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl);
 irqreturn_t msm_dp_ctrl_isr(struct msm_dp_ctrl *msm_dp_ctrl);
 void msm_dp_ctrl_handle_sink_request(struct msm_dp_ctrl *msm_dp_ctrl);
diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
index 32ad00e326ba..736b621c0531 100644
--- a/drivers/gpu/drm/msm/dp/dp_display.c
+++ b/drivers/gpu/drm/msm/dp/dp_display.c
@@ -730,7 +730,7 @@ static int msm_dp_display_disable(struct msm_dp_display_private *dp)
 
 		/* set dongle to D3 (power off) mode */
 		msm_dp_link_psm_config(dp->link, &dp->panel->link_info, true);
-		msm_dp_ctrl_off_pixel_clk(dp->ctrl);
+		msm_dp_ctrl_off_pixel_clk(dp->ctrl, dp->panel->stream_id);
 		msm_dp_ctrl_off_link(dp->ctrl);
 		/* re-init the PHY so that we can listen to Dongle disconnect */
 		msm_dp_ctrl_reinit_phy(dp->ctrl);
@@ -739,7 +739,7 @@ static int msm_dp_display_disable(struct msm_dp_display_private *dp)
 		 * unplugged interrupt
 		 * dongle unplugged out of DUT
 		 */
-		msm_dp_ctrl_off_pixel_clk(dp->ctrl);
+		msm_dp_ctrl_off_pixel_clk(dp->ctrl, dp->panel->stream_id);
 		msm_dp_ctrl_off_link(dp->ctrl);
 		msm_dp_display_host_phy_exit(dp);
 	}
@@ -750,6 +750,24 @@ static int msm_dp_display_disable(struct msm_dp_display_private *dp)
 	return 0;
 }
 
+int msm_dp_display_set_stream_info(struct msm_dp *msm_dp_display,
+				   struct msm_dp_panel *panel, enum msm_dp_stream_id stream_id)
+{
+	int rc = 0;
+	struct msm_dp_display_private *dp;
+
+	dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display);
+
+	if (!dp) {
+		DRM_ERROR("invalid input\n");
+		return -EINVAL;
+	}
+
+	panel->stream_id = stream_id;
+
+	return rc;
+}
+
 /**
  * msm_dp_bridge_mode_valid - callback to determine if specified mode is valid
  * @dp: Pointer to dp display structure
@@ -1451,6 +1469,8 @@ void msm_dp_display_atomic_enable(struct msm_dp *msm_dp_display)
 
 	dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display);
 
+	msm_dp_display_set_stream_info(msm_dp_display, dp->panel, 0);
+
 	rc = msm_dp_display_enable(dp);
 	if (rc)
 		DRM_ERROR("DP display enable failed, rc=%d\n", rc);
diff --git a/drivers/gpu/drm/msm/dp/dp_display.h b/drivers/gpu/drm/msm/dp/dp_display.h
index 295da7ae0047..a5c6ed5b18e4 100644
--- a/drivers/gpu/drm/msm/dp/dp_display.h
+++ b/drivers/gpu/drm/msm/dp/dp_display.h
@@ -41,5 +41,7 @@ void msm_dp_display_atomic_enable(struct msm_dp *dp_display);
 enum drm_mode_status msm_dp_display_mode_valid(struct msm_dp *dp,
 					       const struct drm_display_info *info,
 					       const struct drm_display_mode *mode);
+int msm_dp_display_set_stream_info(struct msm_dp *msm_dp_display,
+				   struct msm_dp_panel *panel, enum msm_dp_stream_id stream_id);
 
 #endif /* _DP_DISPLAY_H_ */
diff --git a/drivers/gpu/drm/msm/dp/dp_panel.h b/drivers/gpu/drm/msm/dp/dp_panel.h
index 53b7b4463551..21f7f30e6dfd 100644
--- a/drivers/gpu/drm/msm/dp/dp_panel.h
+++ b/drivers/gpu/drm/msm/dp/dp_panel.h
@@ -27,6 +27,15 @@ struct msm_dp_panel_psr {
 	u8 capabilities;
 };
 
+/* stream id */
+enum msm_dp_stream_id {
+	DP_STREAM_0,
+	DP_STREAM_1,
+	DP_STREAM_2,
+	DP_STREAM_3,
+	DP_STREAM_MAX,
+};
+
 struct msm_dp_panel {
 	/* dpcd raw data */
 	u8 dpcd[DP_RECEIVER_CAP_SIZE];
@@ -40,6 +49,8 @@ struct msm_dp_panel {
 	bool vsc_sdp_supported;
 	u32 hw_revision;
 
+	enum msm_dp_stream_id stream_id;
+
 	u32 max_bw_code;
 };
 

-- 
2.43.0


^ permalink raw reply	[flat|nested] 128+ messages in thread

* [PATCH v4 14/39] drm/msm/dp: introduce max_streams for DP controller MST support
  2026-04-10  9:33 [PATCH v4 00/39] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
                   ` (12 preceding siblings ...)
  2026-04-10  9:33 ` [PATCH v4 13/39] drm/msm/dp: introduce stream_id for each DP panel Yongxing Mou
@ 2026-04-10  9:33 ` Yongxing Mou
  2026-04-11 17:59   ` Dmitry Baryshkov
  2026-04-10  9:33 ` [PATCH v4 15/39] drm/msm/dp: Add support for programming p1/p2/p3 register blocks Yongxing Mou
                   ` (26 subsequent siblings)
  40 siblings, 1 reply; 128+ messages in thread
From: Yongxing Mou @ 2026-04-10  9:33 UTC (permalink / raw)
  To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang
  Cc: linux-arm-msm, dri-devel, freedreno, linux-kernel, Yongxing Mou,
	Abhinav Kumar

From: Abhinav Kumar <quic_abhinavk@quicinc.com>

Introduce the `mst_streams` field in each DP controller descriptor to
specify the number of supported MST streams. Most platforms support 2 or
4 MST streams, while platforms without MST support default to a single
stream (`DEFAULT_STREAM_COUNT = 1`).

Also accounts for platforms with asymmetric stream support, e.g., DP0
supporting 4 streams and DP1 supporting 2.

Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
---
 drivers/gpu/drm/msm/dp/dp_ctrl.c    | 20 +++++++++++++++++---
 drivers/gpu/drm/msm/dp/dp_ctrl.h    |  2 ++
 drivers/gpu/drm/msm/dp/dp_display.c | 20 +++++++++++++++++++-
 drivers/gpu/drm/msm/dp/dp_display.h |  1 +
 4 files changed, 39 insertions(+), 4 deletions(-)

diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c
index fb6396727628..1e80d6fc7bda 100644
--- a/drivers/gpu/drm/msm/dp/dp_ctrl.c
+++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c
@@ -128,6 +128,7 @@ struct msm_dp_ctrl_private {
 	struct clk_bulk_data *link_clks;
 
 	struct clk *pixel_clk[DP_STREAM_MAX];
+	unsigned int num_pixel_clks;
 
 	union phy_configure_opts phy_opts;
 
@@ -2707,7 +2708,7 @@ static const char * const pixel_clks[] = {
 	"stream_3_pixel",
 };
 
-static int msm_dp_ctrl_clk_init(struct msm_dp_ctrl *msm_dp_ctrl)
+static int msm_dp_ctrl_clk_init(struct msm_dp_ctrl *msm_dp_ctrl, int max_stream)
 {
 	struct msm_dp_ctrl_private *ctrl;
 	struct device *dev;
@@ -2740,7 +2741,8 @@ static int msm_dp_ctrl_clk_init(struct msm_dp_ctrl *msm_dp_ctrl)
 	if (rc)
 		return rc;
 
-	for (i = DP_STREAM_0; i < DP_STREAM_MAX; i++) {
+	ctrl->num_pixel_clks = 0;
+	for (i = DP_STREAM_0; i < max_stream; i++) {
 		ctrl->pixel_clk[i] = devm_clk_get(dev, pixel_clks[i]);
 
 		if (i == 0 && IS_ERR(ctrl->pixel_clk[i]))
@@ -2750,14 +2752,26 @@ static int msm_dp_ctrl_clk_init(struct msm_dp_ctrl *msm_dp_ctrl)
 			DRM_DEBUG_DP("stream %d pixel clock not exist", i);
 			break;
 		}
+
+		ctrl->num_pixel_clks++;
 	}
 
 	return 0;
 }
 
+int msm_dp_ctrl_get_stream_cnt(struct msm_dp_ctrl *msm_dp_ctrl)
+{
+	struct msm_dp_ctrl_private *ctrl;
+
+	ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl);
+
+	return ctrl->num_pixel_clks;
+}
+
 struct msm_dp_ctrl *msm_dp_ctrl_get(struct device *dev, struct msm_dp_link *link,
 			struct msm_dp_panel *panel,	struct drm_dp_aux *aux,
 			struct phy *phy,
+			int max_stream,
 			void __iomem *ahb_base,
 			void __iomem *link_base)
 {
@@ -2800,7 +2814,7 @@ struct msm_dp_ctrl *msm_dp_ctrl_get(struct device *dev, struct msm_dp_link *link
 	ctrl->ahb_base = ahb_base;
 	ctrl->link_base = link_base;
 
-	ret = msm_dp_ctrl_clk_init(&ctrl->msm_dp_ctrl);
+	ret = msm_dp_ctrl_clk_init(&ctrl->msm_dp_ctrl, max_stream);
 	if (ret) {
 		dev_err(dev, "failed to init clocks\n");
 		return ERR_PTR(ret);
diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.h b/drivers/gpu/drm/msm/dp/dp_ctrl.h
index b9f0705b03ba..6fed3ff3a72d 100644
--- a/drivers/gpu/drm/msm/dp/dp_ctrl.h
+++ b/drivers/gpu/drm/msm/dp/dp_ctrl.h
@@ -29,6 +29,7 @@ struct msm_dp_ctrl *msm_dp_ctrl_get(struct device *dev,
 				    struct msm_dp_panel *panel,
 				    struct drm_dp_aux *aux,
 				    struct phy *phy,
+				    int max_stream,
 				    void __iomem *ahb_base,
 				    void __iomem *link_base);
 
@@ -47,4 +48,5 @@ void msm_dp_ctrl_enable_irq(struct msm_dp_ctrl *msm_dp_ctrl);
 void msm_dp_ctrl_disable_irq(struct msm_dp_ctrl *msm_dp_ctrl);
 
 void msm_dp_ctrl_reinit_phy(struct msm_dp_ctrl *msm_dp_ctrl);
+int msm_dp_ctrl_get_stream_cnt(struct msm_dp_ctrl *dp_ctrl);
 #endif /* _DP_CTRL_H_ */
diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
index 736b621c0531..7984a0f9e938 100644
--- a/drivers/gpu/drm/msm/dp/dp_display.c
+++ b/drivers/gpu/drm/msm/dp/dp_display.c
@@ -87,12 +87,15 @@ struct msm_dp_display_private {
 
 	void __iomem *p0_base;
 	size_t p0_len;
+
+	int max_stream;
 };
 
 struct msm_dp_desc {
 	phys_addr_t io_start;
 	unsigned int id;
 	bool wide_bus_supported;
+	int mst_streams;
 };
 
 static const struct msm_dp_desc msm_dp_desc_glymur[] = {
@@ -567,13 +570,15 @@ static int msm_dp_init_sub_modules(struct msm_dp_display_private *dp)
 	}
 
 	dp->ctrl = msm_dp_ctrl_get(dev, dp->link, dp->panel, dp->aux,
-			       phy, dp->ahb_base, dp->link_base);
+			       phy, dp->max_stream, dp->ahb_base, dp->link_base);
 	if (IS_ERR(dp->ctrl)) {
 		rc = PTR_ERR(dp->ctrl);
 		DRM_ERROR("failed to initialize ctrl, rc = %d\n", rc);
 		dp->ctrl = NULL;
 		goto error_link;
 	}
+	if (dp->max_stream != msm_dp_ctrl_get_stream_cnt(dp->ctrl))
+		dp->max_stream = 1;
 
 	dp->audio = msm_dp_audio_get(dp->msm_dp_display.pdev, dp->link_base);
 	if (IS_ERR(dp->audio)) {
@@ -1208,6 +1213,15 @@ static int msm_dp_display_get_io(struct msm_dp_display_private *display)
 	return 0;
 }
 
+int msm_dp_get_mst_max_stream(struct msm_dp *msm_dp_display)
+{
+	struct msm_dp_display_private *dp;
+
+	dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display);
+
+	return dp->max_stream;
+}
+
 static int msm_dp_display_probe(struct platform_device *pdev)
 {
 	int rc = 0;
@@ -1234,6 +1248,10 @@ static int msm_dp_display_probe(struct platform_device *pdev)
 	dp->msm_dp_display.is_edp =
 		(dp->msm_dp_display.connector_type == DRM_MODE_CONNECTOR_eDP);
 	dp->hpd_isr_status = 0;
+	dp->max_stream = 1;
+
+	if (desc->mst_streams > 1)
+		dp->max_stream = desc->mst_streams;
 
 	mutex_init(&dp->plugged_lock);
 
diff --git a/drivers/gpu/drm/msm/dp/dp_display.h b/drivers/gpu/drm/msm/dp/dp_display.h
index a5c6ed5b18e4..b0cfdf215970 100644
--- a/drivers/gpu/drm/msm/dp/dp_display.h
+++ b/drivers/gpu/drm/msm/dp/dp_display.h
@@ -26,6 +26,7 @@ struct msm_dp {
 	bool psr_supported;
 };
 
+int msm_dp_get_mst_max_stream(struct msm_dp *msm_dp_display);
 int msm_dp_display_get_modes(struct msm_dp *msm_dp_display);
 bool msm_dp_display_check_video_test(struct msm_dp *msm_dp_display);
 int msm_dp_display_get_test_bpp(struct msm_dp *msm_dp_display);

-- 
2.43.0


^ permalink raw reply	[flat|nested] 128+ messages in thread

* [PATCH v4 15/39] drm/msm/dp: Add support for programming p1/p2/p3 register blocks
  2026-04-10  9:33 [PATCH v4 00/39] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
                   ` (13 preceding siblings ...)
  2026-04-10  9:33 ` [PATCH v4 14/39] drm/msm/dp: introduce max_streams for DP controller MST support Yongxing Mou
@ 2026-04-10  9:33 ` Yongxing Mou
  2026-04-11 18:07   ` Dmitry Baryshkov
  2026-04-10  9:33 ` [PATCH v4 16/39] drm/msm/dp: use stream_id to change offsets in dp_catalog Yongxing Mou
                   ` (25 subsequent siblings)
  40 siblings, 1 reply; 128+ messages in thread
From: Yongxing Mou @ 2026-04-10  9:33 UTC (permalink / raw)
  To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang
  Cc: linux-arm-msm, dri-devel, freedreno, linux-kernel, Yongxing Mou,
	Abhinav Kumar

From: Abhinav Kumar <quic_abhinavk@quicinc.com>

Add support for additional pixel register blocks (p1, p2, p3) to enable
4‑stream MST pixel clocks. Introduce the helper functions msm_dp_read_pn
and msm_dp_write_pn for pixel register programming. All pixel clocks
share the same register layout but use different base addresses.

Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
---
 drivers/gpu/drm/msm/dp/dp_display.c | 40 ++++++++++++-----
 drivers/gpu/drm/msm/dp/dp_panel.c   | 89 ++++++++++++++++++++-----------------
 drivers/gpu/drm/msm/dp/dp_panel.h   |  3 +-
 3 files changed, 79 insertions(+), 53 deletions(-)

diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
index 7984a0f9e938..ff506064a3fa 100644
--- a/drivers/gpu/drm/msm/dp/dp_display.c
+++ b/drivers/gpu/drm/msm/dp/dp_display.c
@@ -85,8 +85,8 @@ struct msm_dp_display_private {
 	void __iomem *link_base;
 	size_t link_len;
 
-	void __iomem *p0_base;
-	size_t p0_len;
+	void __iomem *pixel_base[DP_STREAM_MAX];
+	size_t pixel_len;
 
 	int max_stream;
 };
@@ -561,7 +561,7 @@ static int msm_dp_init_sub_modules(struct msm_dp_display_private *dp)
 		goto error_link;
 	}
 
-	dp->panel = msm_dp_panel_get(dev, dp->aux, dp->link, dp->link_base, dp->p0_base);
+	dp->panel = msm_dp_panel_get(dev, dp->aux, dp->link, dp->link_base, dp->pixel_base[0]);
 	if (IS_ERR(dp->panel)) {
 		rc = PTR_ERR(dp->panel);
 		DRM_ERROR("failed to initialize panel, rc = %d\n", rc);
@@ -769,6 +769,7 @@ int msm_dp_display_set_stream_info(struct msm_dp *msm_dp_display,
 	}
 
 	panel->stream_id = stream_id;
+	msm_dp_panel_set_pixel_base(panel, dp->pixel_base[stream_id]);
 
 	return rc;
 }
@@ -882,8 +883,14 @@ void msm_dp_snapshot(struct msm_disp_state *disp_state, struct msm_dp *dp)
 				    msm_dp_display->aux_base, "dp_aux");
 	msm_disp_snapshot_add_block(disp_state, msm_dp_display->link_len,
 				    msm_dp_display->link_base, "dp_link");
-	msm_disp_snapshot_add_block(disp_state, msm_dp_display->p0_len,
-				    msm_dp_display->p0_base, "dp_p0");
+	msm_disp_snapshot_add_block(disp_state, msm_dp_display->pixel_len,
+				    msm_dp_display->pixel_base[0], "dp_p0");
+	msm_disp_snapshot_add_block(disp_state, msm_dp_display->pixel_len,
+				    msm_dp_display->pixel_base[1], "dp_p1");
+	msm_disp_snapshot_add_block(disp_state, msm_dp_display->pixel_len,
+				    msm_dp_display->pixel_base[2], "dp_p2");
+	msm_disp_snapshot_add_block(disp_state, msm_dp_display->pixel_len,
+				    msm_dp_display->pixel_base[3], "dp_p3");
 }
 
 void msm_dp_display_set_psr(struct msm_dp *msm_dp_display, bool enter)
@@ -1163,6 +1170,7 @@ static void __iomem *msm_dp_ioremap(struct platform_device *pdev, int idx, size_
 static int msm_dp_display_get_io(struct msm_dp_display_private *display)
 {
 	struct platform_device *pdev = display->msm_dp_display.pdev;
+	int i;
 
 	display->ahb_base = msm_dp_ioremap(pdev, 0, &display->ahb_len);
 	if (IS_ERR(display->ahb_base))
@@ -1192,8 +1200,8 @@ static int msm_dp_display_get_io(struct msm_dp_display_private *display)
 		display->aux_len = DP_DEFAULT_AUX_SIZE;
 		display->link_base = display->ahb_base + DP_DEFAULT_LINK_OFFSET;
 		display->link_len = DP_DEFAULT_LINK_SIZE;
-		display->p0_base = display->ahb_base + DP_DEFAULT_P0_OFFSET;
-		display->p0_len = DP_DEFAULT_P0_SIZE;
+		display->pixel_base[0] = display->ahb_base + DP_DEFAULT_P0_OFFSET;
+		display->pixel_len = DP_DEFAULT_P0_SIZE;
 
 		return 0;
 	}
@@ -1204,10 +1212,20 @@ static int msm_dp_display_get_io(struct msm_dp_display_private *display)
 		return PTR_ERR(display->link_base);
 	}
 
-	display->p0_base = msm_dp_ioremap(pdev, 3, &display->p0_len);
-	if (IS_ERR(display->p0_base)) {
-		DRM_ERROR("unable to remap p0 region: %pe\n", display->p0_base);
-		return PTR_ERR(display->p0_base);
+	display->pixel_base[0] = msm_dp_ioremap(pdev, 3, &display->pixel_len);
+	if (IS_ERR(display->pixel_base[0])) {
+		DRM_ERROR("unable to remap p0 region: %pe\n", display->pixel_base[0]);
+		return PTR_ERR(display->pixel_base[0]);
+	}
+
+	for (i = DP_STREAM_1; i < display->max_stream; i++) {
+		/* pixels clk reg index start from 3*/
+		display->pixel_base[i] = msm_dp_ioremap(pdev, i + 3, &display->pixel_len);
+		if (IS_ERR(display->pixel_base[i])) {
+			DRM_DEBUG_DP("unable to remap p%d region: %pe\n", i,
+				     display->pixel_base[i]);
+			break;
+		}
 	}
 
 	return 0;
diff --git a/drivers/gpu/drm/msm/dp/dp_panel.c b/drivers/gpu/drm/msm/dp/dp_panel.c
index bde4a772d22c..c17b87353d1a 100644
--- a/drivers/gpu/drm/msm/dp/dp_panel.c
+++ b/drivers/gpu/drm/msm/dp/dp_panel.c
@@ -25,7 +25,7 @@ struct msm_dp_panel_private {
 	struct drm_dp_aux *aux;
 	struct msm_dp_link *link;
 	void __iomem *link_base;
-	void __iomem *p0_base;
+	void __iomem *pixel_base;
 	bool panel_on;
 };
 
@@ -44,24 +44,24 @@ static inline void msm_dp_write_link(struct msm_dp_panel_private *panel,
 	writel(data, panel->link_base + offset);
 }
 
-static inline void msm_dp_write_p0(struct msm_dp_panel_private *panel,
-			       u32 offset, u32 data)
+static inline void msm_dp_write_pn(struct msm_dp_panel_private *panel,
+				   u32 offset, u32 data)
 {
 	/*
 	 * To make sure interface reg writes happens before any other operation,
 	 * this function uses writel() instread of writel_relaxed()
 	 */
-	writel(data, panel->p0_base + offset);
+	writel(data, panel->pixel_base + offset);
 }
 
-static inline u32 msm_dp_read_p0(struct msm_dp_panel_private *panel,
-			       u32 offset)
+static inline u32 msm_dp_read_pn(struct msm_dp_panel_private *panel,
+				 u32 offset)
 {
 	/*
 	 * To make sure interface reg writes happens before any other operation,
 	 * this function uses writel() instread of writel_relaxed()
 	 */
-	return readl_relaxed(panel->p0_base + offset);
+	return readl_relaxed(panel->pixel_base + offset);
 }
 
 static void msm_dp_panel_read_psr_cap(struct msm_dp_panel_private *panel)
@@ -367,34 +367,34 @@ static void msm_dp_panel_tpg_enable(struct msm_dp_panel *msm_dp_panel,
 	display_hctl = (hsync_end_x << 16) | hsync_start_x;
 
 
-	msm_dp_write_p0(panel, MMSS_DP_INTF_HSYNC_CTL, hsync_ctl);
-	msm_dp_write_p0(panel, MMSS_DP_INTF_VSYNC_PERIOD_F0, vsync_period *
+	msm_dp_write_pn(panel, MMSS_DP_INTF_HSYNC_CTL, hsync_ctl);
+	msm_dp_write_pn(panel, MMSS_DP_INTF_VSYNC_PERIOD_F0, vsync_period *
 			hsync_period);
-	msm_dp_write_p0(panel, MMSS_DP_INTF_VSYNC_PULSE_WIDTH_F0, v_sync_width *
+	msm_dp_write_pn(panel, MMSS_DP_INTF_VSYNC_PULSE_WIDTH_F0, v_sync_width *
 			hsync_period);
-	msm_dp_write_p0(panel, MMSS_DP_INTF_VSYNC_PERIOD_F1, 0);
-	msm_dp_write_p0(panel, MMSS_DP_INTF_VSYNC_PULSE_WIDTH_F1, 0);
-	msm_dp_write_p0(panel, MMSS_DP_INTF_DISPLAY_HCTL, display_hctl);
-	msm_dp_write_p0(panel, MMSS_DP_INTF_ACTIVE_HCTL, 0);
-	msm_dp_write_p0(panel, MMSS_INTF_DISPLAY_V_START_F0, display_v_start);
-	msm_dp_write_p0(panel, MMSS_DP_INTF_DISPLAY_V_END_F0, display_v_end);
-	msm_dp_write_p0(panel, MMSS_INTF_DISPLAY_V_START_F1, 0);
-	msm_dp_write_p0(panel, MMSS_DP_INTF_DISPLAY_V_END_F1, 0);
-	msm_dp_write_p0(panel, MMSS_DP_INTF_ACTIVE_V_START_F0, 0);
-	msm_dp_write_p0(panel, MMSS_DP_INTF_ACTIVE_V_END_F0, 0);
-	msm_dp_write_p0(panel, MMSS_DP_INTF_ACTIVE_V_START_F1, 0);
-	msm_dp_write_p0(panel, MMSS_DP_INTF_ACTIVE_V_END_F1, 0);
-	msm_dp_write_p0(panel, MMSS_DP_INTF_POLARITY_CTL, 0);
-
-	msm_dp_write_p0(panel, MMSS_DP_TPG_MAIN_CONTROL,
-				DP_TPG_CHECKERED_RECT_PATTERN);
-	msm_dp_write_p0(panel, MMSS_DP_TPG_VIDEO_CONFIG,
-				DP_TPG_VIDEO_CONFIG_BPP_8BIT |
-				DP_TPG_VIDEO_CONFIG_RGB);
-	msm_dp_write_p0(panel, MMSS_DP_BIST_ENABLE,
-				DP_BIST_ENABLE_DPBIST_EN);
-	msm_dp_write_p0(panel, MMSS_DP_TIMING_ENGINE_EN,
-				DP_TIMING_ENGINE_EN_EN);
+	msm_dp_write_pn(panel, MMSS_DP_INTF_VSYNC_PERIOD_F1, 0);
+	msm_dp_write_pn(panel, MMSS_DP_INTF_VSYNC_PULSE_WIDTH_F1, 0);
+	msm_dp_write_pn(panel, MMSS_DP_INTF_DISPLAY_HCTL, display_hctl);
+	msm_dp_write_pn(panel, MMSS_DP_INTF_ACTIVE_HCTL, 0);
+	msm_dp_write_pn(panel, MMSS_INTF_DISPLAY_V_START_F0, display_v_start);
+	msm_dp_write_pn(panel, MMSS_DP_INTF_DISPLAY_V_END_F0, display_v_end);
+	msm_dp_write_pn(panel, MMSS_INTF_DISPLAY_V_START_F1, 0);
+	msm_dp_write_pn(panel, MMSS_DP_INTF_DISPLAY_V_END_F1, 0);
+	msm_dp_write_pn(panel, MMSS_DP_INTF_ACTIVE_V_START_F0, 0);
+	msm_dp_write_pn(panel, MMSS_DP_INTF_ACTIVE_V_END_F0, 0);
+	msm_dp_write_pn(panel, MMSS_DP_INTF_ACTIVE_V_START_F1, 0);
+	msm_dp_write_pn(panel, MMSS_DP_INTF_ACTIVE_V_END_F1, 0);
+	msm_dp_write_pn(panel, MMSS_DP_INTF_POLARITY_CTL, 0);
+
+	msm_dp_write_pn(panel, MMSS_DP_TPG_MAIN_CONTROL,
+			DP_TPG_CHECKERED_RECT_PATTERN);
+	msm_dp_write_pn(panel, MMSS_DP_TPG_VIDEO_CONFIG,
+			DP_TPG_VIDEO_CONFIG_BPP_8BIT |
+			DP_TPG_VIDEO_CONFIG_RGB);
+	msm_dp_write_pn(panel, MMSS_DP_BIST_ENABLE,
+			DP_BIST_ENABLE_DPBIST_EN);
+	msm_dp_write_pn(panel, MMSS_DP_TIMING_ENGINE_EN,
+			DP_TIMING_ENGINE_EN_EN);
 	drm_dbg_dp(panel->drm_dev, "%s: enabled tpg\n", __func__);
 }
 
@@ -403,9 +403,9 @@ static void msm_dp_panel_tpg_disable(struct msm_dp_panel *msm_dp_panel)
 	struct msm_dp_panel_private *panel =
 		container_of(msm_dp_panel, struct msm_dp_panel_private, msm_dp_panel);
 
-	msm_dp_write_p0(panel, MMSS_DP_TPG_MAIN_CONTROL, 0x0);
-	msm_dp_write_p0(panel, MMSS_DP_BIST_ENABLE, 0x0);
-	msm_dp_write_p0(panel, MMSS_DP_TIMING_ENGINE_EN, 0x0);
+	msm_dp_write_pn(panel, MMSS_DP_TPG_MAIN_CONTROL, 0x0);
+	msm_dp_write_pn(panel, MMSS_DP_BIST_ENABLE, 0x0);
+	msm_dp_write_pn(panel, MMSS_DP_TIMING_ENGINE_EN, 0x0);
 }
 
 void msm_dp_panel_tpg_config(struct msm_dp_panel *msm_dp_panel, bool enable)
@@ -439,7 +439,7 @@ void msm_dp_panel_clear_dsc_dto(struct msm_dp_panel *msm_dp_panel)
 	struct msm_dp_panel_private *panel =
 		container_of(msm_dp_panel, struct msm_dp_panel_private, msm_dp_panel);
 
-	msm_dp_write_p0(panel, MMSS_DP_DSC_DTO, 0x0);
+	msm_dp_write_pn(panel, MMSS_DP_DSC_DTO, 0x0);
 }
 
 static void msm_dp_panel_send_vsc_sdp(struct msm_dp_panel_private *panel, struct dp_sdp *vsc_sdp)
@@ -629,7 +629,7 @@ int msm_dp_panel_timing_cfg(struct msm_dp_panel *msm_dp_panel, bool wide_bus_en)
 	msm_dp_write_link(panel, REG_DP_HSYNC_VSYNC_WIDTH_POLARITY, width_blanking);
 	msm_dp_write_link(panel, REG_DP_ACTIVE_HOR_VER, msm_dp_active);
 
-	reg = msm_dp_read_p0(panel, MMSS_DP_INTF_CONFIG);
+	reg = msm_dp_read_pn(panel, MMSS_DP_INTF_CONFIG);
 	if (wide_bus_en)
 		reg |= DP_INTF_CONFIG_DATABUS_WIDEN;
 	else
@@ -637,7 +637,7 @@ int msm_dp_panel_timing_cfg(struct msm_dp_panel *msm_dp_panel, bool wide_bus_en)
 
 	drm_dbg_dp(panel->drm_dev, "wide_bus_en=%d reg=%#x\n", wide_bus_en, reg);
 
-	msm_dp_write_p0(panel, MMSS_DP_INTF_CONFIG, reg);
+	msm_dp_write_pn(panel, MMSS_DP_INTF_CONFIG, reg);
 
 	if (msm_dp_panel->msm_dp_mode.out_fmt_is_yuv_420)
 		msm_dp_panel_setup_vsc_sdp_yuv_420(msm_dp_panel);
@@ -647,6 +647,13 @@ int msm_dp_panel_timing_cfg(struct msm_dp_panel *msm_dp_panel, bool wide_bus_en)
 	return 0;
 }
 
+void msm_dp_panel_set_pixel_base(struct msm_dp_panel *msm_dp_panel, void __iomem *pixel_base)
+{
+	struct msm_dp_panel_private *panel =
+		container_of(msm_dp_panel, struct msm_dp_panel_private, msm_dp_panel);
+	panel->pixel_base = pixel_base;
+}
+
 int msm_dp_panel_init_panel_info(struct msm_dp_panel *msm_dp_panel)
 {
 	struct drm_display_mode *drm_mode;
@@ -689,7 +696,7 @@ int msm_dp_panel_init_panel_info(struct msm_dp_panel *msm_dp_panel)
 struct msm_dp_panel *msm_dp_panel_get(struct device *dev, struct drm_dp_aux *aux,
 			      struct msm_dp_link *link,
 			      void __iomem *link_base,
-			      void __iomem *p0_base)
+			      void __iomem *pixel_base)
 {
 	struct msm_dp_panel_private *panel;
 	struct msm_dp_panel *msm_dp_panel;
@@ -707,7 +714,7 @@ struct msm_dp_panel *msm_dp_panel_get(struct device *dev, struct drm_dp_aux *aux
 	panel->aux = aux;
 	panel->link = link;
 	panel->link_base = link_base;
-	panel->p0_base = p0_base;
+	panel->pixel_base = pixel_base;
 
 	msm_dp_panel = &panel->msm_dp_panel;
 	msm_dp_panel->max_bw_code = DP_LINK_BW_8_1;
diff --git a/drivers/gpu/drm/msm/dp/dp_panel.h b/drivers/gpu/drm/msm/dp/dp_panel.h
index 21f7f30e6dfd..fe4ac3e47e17 100644
--- a/drivers/gpu/drm/msm/dp/dp_panel.h
+++ b/drivers/gpu/drm/msm/dp/dp_panel.h
@@ -66,6 +66,7 @@ void msm_dp_panel_handle_sink_request(struct msm_dp_panel *msm_dp_panel,
 void msm_dp_panel_tpg_config(struct msm_dp_panel *msm_dp_panel, bool enable);
 
 void msm_dp_panel_clear_dsc_dto(struct msm_dp_panel *msm_dp_panel);
+void msm_dp_panel_set_pixel_base(struct msm_dp_panel *msm_dp_panel, void __iomem *pixel_base);
 
 void msm_dp_panel_enable_vsc_sdp(struct msm_dp_panel *msm_dp_panel, struct dp_sdp *vsc_sdp);
 void msm_dp_panel_disable_vsc_sdp(struct msm_dp_panel *msm_dp_panel);
@@ -100,5 +101,5 @@ static inline bool is_lane_count_valid(u32 lane_count)
 struct msm_dp_panel *msm_dp_panel_get(struct device *dev, struct drm_dp_aux *aux,
 			      struct msm_dp_link *link,
 			      void __iomem *link_base,
-			      void __iomem *p0_base);
+			      void __iomem *pixel_base);
 #endif /* _DP_PANEL_H_ */

-- 
2.43.0


^ permalink raw reply	[flat|nested] 128+ messages in thread

* [PATCH v4 16/39] drm/msm/dp: use stream_id to change offsets in dp_catalog
  2026-04-10  9:33 [PATCH v4 00/39] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
                   ` (14 preceding siblings ...)
  2026-04-10  9:33 ` [PATCH v4 15/39] drm/msm/dp: Add support for programming p1/p2/p3 register blocks Yongxing Mou
@ 2026-04-10  9:33 ` Yongxing Mou
  2026-04-11 18:12   ` Dmitry Baryshkov
  2026-04-10  9:33 ` [PATCH v4 17/39] drm/msm/dp: Add catalog support for 3rd/4th stream MST Yongxing Mou
                   ` (24 subsequent siblings)
  40 siblings, 1 reply; 128+ messages in thread
From: Yongxing Mou @ 2026-04-10  9:33 UTC (permalink / raw)
  To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang
  Cc: linux-arm-msm, dri-devel, freedreno, linux-kernel, Yongxing Mou,
	Abhinav Kumar

From: Abhinav Kumar <quic_abhinavk@quicinc.com>

Use the dp_panel's stream_id to adjust the offsets for stream 1 which will
be used for MST in the dp_catalog. Stream 1 share the same link clk with
stream 0 with different reg offset. Also add additional register defines
for stream 1.

Streams 2 and 3 are not covered here, as they use separate link clocks and
require separate handling.

Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
---
 drivers/gpu/drm/msm/dp/dp_ctrl.c  | 24 ++++++++++---
 drivers/gpu/drm/msm/dp/dp_panel.c | 72 +++++++++++++++++++++++++++------------
 drivers/gpu/drm/msm/dp/dp_reg.h   | 11 ++++++
 3 files changed, 81 insertions(+), 26 deletions(-)

diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c
index 1e80d6fc7bda..a52bcd9ea2a3 100644
--- a/drivers/gpu/drm/msm/dp/dp_ctrl.c
+++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c
@@ -393,6 +393,7 @@ static void msm_dp_ctrl_config_ctrl_streams(struct msm_dp_ctrl_private *ctrl,
 					    struct msm_dp_panel *msm_dp_panel)
 {
 	u32 config = 0, tbd;
+	u32 reg_offset = 0;
 
 	config = msm_dp_read_link(ctrl, REG_DP_CONFIGURATION_CTRL);
 
@@ -409,7 +410,8 @@ static void msm_dp_ctrl_config_ctrl_streams(struct msm_dp_ctrl_private *ctrl,
 
 	drm_dbg_dp(ctrl->drm_dev, "stream DP_CONFIGURATION_CTRL=0x%x\n", config);
 
-	msm_dp_write_link(ctrl, REG_DP_CONFIGURATION_CTRL, config);
+	if (msm_dp_panel->stream_id == DP_STREAM_1)
+		reg_offset = REG_DP1_CONFIGURATION_CTRL - REG_DP_CONFIGURATION_CTRL;
 }
 
 static void msm_dp_ctrl_config_ctrl_link(struct msm_dp_ctrl_private *ctrl)
@@ -460,12 +462,16 @@ static void msm_dp_ctrl_config_misc1_misc0(struct msm_dp_ctrl_private *ctrl,
 					   struct msm_dp_panel *msm_dp_panel)
 {
 	u32 colorimetry_cfg, test_bits_depth, misc_val;
+	u32 reg_offset = 0;
 
 	test_bits_depth = msm_dp_link_get_test_bits_depth(ctrl->link,
 							  msm_dp_panel->msm_dp_mode.bpp);
 	colorimetry_cfg = msm_dp_link_get_colorimetry_config(ctrl->link);
 
-	misc_val = msm_dp_read_link(ctrl, REG_DP_MISC1_MISC0);
+	if (msm_dp_panel->stream_id == DP_STREAM_1)
+		reg_offset = REG_DP1_MISC1_MISC0 - REG_DP_MISC1_MISC0;
+
+	misc_val = msm_dp_read_link(ctrl, REG_DP_MISC1_MISC0 + reg_offset);
 
 	/* clear bpp bits */
 	misc_val &= ~(0x07 << DP_MISC0_TEST_BITS_DEPTH_SHIFT);
@@ -475,7 +481,7 @@ static void msm_dp_ctrl_config_misc1_misc0(struct msm_dp_ctrl_private *ctrl,
 	misc_val |= DP_MISC0_SYNCHRONOUS_CLK;
 
 	drm_dbg_dp(ctrl->drm_dev, "misc settings = 0x%x\n", misc_val);
-	msm_dp_write_link(ctrl, REG_DP_MISC1_MISC0, misc_val);
+	msm_dp_write_link(ctrl, REG_DP_MISC1_MISC0 + reg_offset, misc_val);
 }
 
 static void msm_dp_ctrl_configure_source_params(struct msm_dp_ctrl_private *ctrl,
@@ -2446,6 +2452,7 @@ static int msm_dp_ctrl_link_retrain(struct msm_dp_ctrl_private *ctrl)
 }
 
 static void msm_dp_ctrl_config_msa(struct msm_dp_ctrl_private *ctrl,
+			       struct msm_dp_panel *msm_dp_panel,
 			       u32 rate, u32 stream_rate_khz,
 			       bool is_ycbcr_420)
 {
@@ -2455,6 +2462,12 @@ static void msm_dp_ctrl_config_msa(struct msm_dp_ctrl_private *ctrl,
 	u32 const link_rate_hbr2 = 540000;
 	u32 const link_rate_hbr3 = 810000;
 	unsigned long den, num;
+	u32 mvid_reg_off = 0, nvid_reg_off = 0;
+
+	if (msm_dp_panel->stream_id == DP_STREAM_1) {
+		mvid_reg_off = REG_DP1_SOFTWARE_MVID - REG_DP_SOFTWARE_MVID;
+		nvid_reg_off = REG_DP1_SOFTWARE_NVID - REG_DP_SOFTWARE_NVID;
+	}
 
 	switch (rate) {
 	case link_rate_hbr3:
@@ -2509,8 +2522,8 @@ static void msm_dp_ctrl_config_msa(struct msm_dp_ctrl_private *ctrl,
 		nvid *= 3;
 
 	drm_dbg_dp(ctrl->drm_dev, "mvid=0x%x, nvid=0x%x\n", mvid, nvid);
-	msm_dp_write_link(ctrl, REG_DP_SOFTWARE_MVID, mvid);
-	msm_dp_write_link(ctrl, REG_DP_SOFTWARE_NVID, nvid);
+	msm_dp_write_link(ctrl, REG_DP_SOFTWARE_MVID + mvid_reg_off, mvid);
+	msm_dp_write_link(ctrl, REG_DP_SOFTWARE_NVID + nvid_reg_off, nvid);
 }
 
 int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train)
@@ -2585,6 +2598,7 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *
 	msm_dp_ctrl_configure_source_params(ctrl, msm_dp_panel);
 
 	msm_dp_ctrl_config_msa(ctrl,
+		msm_dp_panel,
 		ctrl->link->link_params.rate,
 		pixel_rate_orig,
 		msm_dp_panel->msm_dp_mode.out_fmt_is_yuv_420);
diff --git a/drivers/gpu/drm/msm/dp/dp_panel.c b/drivers/gpu/drm/msm/dp/dp_panel.c
index c17b87353d1a..6c88cc7e3037 100644
--- a/drivers/gpu/drm/msm/dp/dp_panel.c
+++ b/drivers/gpu/drm/msm/dp/dp_panel.c
@@ -447,27 +447,35 @@ static void msm_dp_panel_send_vsc_sdp(struct msm_dp_panel_private *panel, struct
 	u32 header[2];
 	u32 val;
 	int i;
+	u32 offset = 0;
+
+	if (panel->msm_dp_panel.stream_id == DP_STREAM_1)
+		offset = MMSS_DP1_GENERIC0_0 - MMSS_DP_GENERIC0_0;
 
 	msm_dp_utils_pack_sdp_header(&vsc_sdp->sdp_header, header);
 
-	msm_dp_write_link(panel, MMSS_DP_GENERIC0_0, header[0]);
-	msm_dp_write_link(panel, MMSS_DP_GENERIC0_1, header[1]);
+	msm_dp_write_link(panel, MMSS_DP_GENERIC0_0 + offset, header[0]);
+	msm_dp_write_link(panel, MMSS_DP_GENERIC0_1 + offset, header[1]);
 
 	for (i = 0; i < sizeof(vsc_sdp->db); i += 4) {
 		val = ((vsc_sdp->db[i]) | (vsc_sdp->db[i + 1] << 8) | (vsc_sdp->db[i + 2] << 16) |
 		       (vsc_sdp->db[i + 3] << 24));
-		msm_dp_write_link(panel, MMSS_DP_GENERIC0_2 + i, val);
+		msm_dp_write_link(panel, MMSS_DP_GENERIC0_2 + i + offset, val);
 	}
 }
 
 static void msm_dp_panel_update_sdp(struct msm_dp_panel_private *panel)
 {
 	u32 hw_revision = panel->msm_dp_panel.hw_revision;
+	u32 offset = 0;
+
+	if (panel->msm_dp_panel.stream_id == DP_STREAM_1)
+		offset = MMSS_DP1_SDP_CFG3 - MMSS_DP_SDP_CFG3;
 
 	if (hw_revision >= DP_HW_VERSION_1_0 &&
 	    hw_revision < DP_HW_VERSION_1_2) {
-		msm_dp_write_link(panel, MMSS_DP_SDP_CFG3, UPDATE_SDP);
-		msm_dp_write_link(panel, MMSS_DP_SDP_CFG3, 0x0);
+		msm_dp_write_link(panel, MMSS_DP_SDP_CFG3 + offset, UPDATE_SDP);
+		msm_dp_write_link(panel, MMSS_DP_SDP_CFG3 + offset, 0x0);
 	}
 }
 
@@ -476,16 +484,25 @@ void msm_dp_panel_enable_vsc_sdp(struct msm_dp_panel *msm_dp_panel, struct dp_sd
 	struct msm_dp_panel_private *panel =
 		container_of(msm_dp_panel, struct msm_dp_panel_private, msm_dp_panel);
 	u32 cfg, cfg2, misc;
+	u32 misc_reg_offset = 0;
+	u32 sdp_cfg_offset = 0;
+	u32 sdp_cfg2_offset = 0;
+
+	if (msm_dp_panel->stream_id == DP_STREAM_1) {
+		misc_reg_offset = REG_DP1_MISC1_MISC0 - REG_DP_MISC1_MISC0;
+		sdp_cfg_offset = MMSS_DP1_SDP_CFG - MMSS_DP_SDP_CFG;
+		sdp_cfg2_offset = MMSS_DP1_SDP_CFG2 - MMSS_DP_SDP_CFG2;
+	}
 
-	cfg = msm_dp_read_link(panel, MMSS_DP_SDP_CFG);
-	cfg2 = msm_dp_read_link(panel, MMSS_DP_SDP_CFG2);
-	misc = msm_dp_read_link(panel, REG_DP_MISC1_MISC0);
+	cfg = msm_dp_read_link(panel, MMSS_DP_SDP_CFG + sdp_cfg_offset);
+	cfg2 = msm_dp_read_link(panel, MMSS_DP_SDP_CFG2 + sdp_cfg2_offset);
+	misc = msm_dp_read_link(panel, REG_DP_MISC1_MISC0 + misc_reg_offset);
 
 	cfg |= GEN0_SDP_EN;
-	msm_dp_write_link(panel, MMSS_DP_SDP_CFG, cfg);
+	msm_dp_write_link(panel, MMSS_DP_SDP_CFG + sdp_cfg_offset, cfg);
 
 	cfg2 |= GENERIC0_SDPSIZE_VALID;
-	msm_dp_write_link(panel, MMSS_DP_SDP_CFG2, cfg2);
+	msm_dp_write_link(panel, MMSS_DP_SDP_CFG2 + sdp_cfg2_offset, cfg2);
 
 	msm_dp_panel_send_vsc_sdp(panel, vsc_sdp);
 
@@ -495,7 +512,7 @@ void msm_dp_panel_enable_vsc_sdp(struct msm_dp_panel *msm_dp_panel, struct dp_sd
 	drm_dbg_dp(panel->drm_dev, "vsc sdp enable=1\n");
 
 	pr_debug("misc settings = 0x%x\n", misc);
-	msm_dp_write_link(panel, REG_DP_MISC1_MISC0, misc);
+	msm_dp_write_link(panel, REG_DP_MISC1_MISC0 + misc_reg_offset, misc);
 
 	msm_dp_panel_update_sdp(panel);
 }
@@ -505,16 +522,25 @@ void msm_dp_panel_disable_vsc_sdp(struct msm_dp_panel *msm_dp_panel)
 	struct msm_dp_panel_private *panel =
 		container_of(msm_dp_panel, struct msm_dp_panel_private, msm_dp_panel);
 	u32 cfg, cfg2, misc;
+	u32 misc_reg_offset = 0;
+	u32 sdp_cfg_offset = 0;
+	u32 sdp_cfg2_offset = 0;
+
+	if (msm_dp_panel->stream_id == DP_STREAM_1) {
+		misc_reg_offset = REG_DP1_MISC1_MISC0 - REG_DP_MISC1_MISC0;
+		sdp_cfg_offset = MMSS_DP1_SDP_CFG - MMSS_DP_SDP_CFG;
+		sdp_cfg2_offset = MMSS_DP1_SDP_CFG2 - MMSS_DP_SDP_CFG2;
+	}
 
-	cfg = msm_dp_read_link(panel, MMSS_DP_SDP_CFG);
-	cfg2 = msm_dp_read_link(panel, MMSS_DP_SDP_CFG2);
-	misc = msm_dp_read_link(panel, REG_DP_MISC1_MISC0);
+	cfg = msm_dp_read_link(panel, MMSS_DP_SDP_CFG + sdp_cfg_offset);
+	cfg2 = msm_dp_read_link(panel, MMSS_DP_SDP_CFG2 + sdp_cfg2_offset);
+	misc = msm_dp_read_link(panel, REG_DP_MISC1_MISC0 + misc_reg_offset);
 
 	cfg &= ~GEN0_SDP_EN;
-	msm_dp_write_link(panel, MMSS_DP_SDP_CFG, cfg);
+	msm_dp_write_link(panel, MMSS_DP_SDP_CFG + sdp_cfg_offset, cfg);
 
 	cfg2 &= ~GENERIC0_SDPSIZE_VALID;
-	msm_dp_write_link(panel, MMSS_DP_SDP_CFG2, cfg2);
+	msm_dp_write_link(panel, MMSS_DP_SDP_CFG2 + sdp_cfg2_offset, cfg2);
 
 	/* switch back to MSA */
 	misc &= ~DP_MISC1_VSC_SDP;
@@ -522,7 +548,7 @@ void msm_dp_panel_disable_vsc_sdp(struct msm_dp_panel *msm_dp_panel)
 	drm_dbg_dp(panel->drm_dev, "vsc sdp enable=0\n");
 
 	pr_debug("misc settings = 0x%x\n", misc);
-	msm_dp_write_link(panel, REG_DP_MISC1_MISC0, misc);
+	msm_dp_write_link(panel, REG_DP_MISC1_MISC0 + misc_reg_offset, misc);
 
 	msm_dp_panel_update_sdp(panel);
 }
@@ -580,6 +606,7 @@ int msm_dp_panel_timing_cfg(struct msm_dp_panel *msm_dp_panel, bool wide_bus_en)
 	u32 msm_dp_active;
 	u32 total;
 	u32 reg;
+	u32 offset = 0;
 
 	panel = container_of(msm_dp_panel, struct msm_dp_panel_private, msm_dp_panel);
 	drm_mode = &panel->msm_dp_panel.msm_dp_mode.drm_mode;
@@ -594,6 +621,9 @@ int msm_dp_panel_timing_cfg(struct msm_dp_panel *msm_dp_panel, bool wide_bus_en)
 		drm_mode->vsync_start - drm_mode->vdisplay,
 		drm_mode->vsync_end - drm_mode->vsync_start);
 
+	if (msm_dp_panel->stream_id == DP_STREAM_1)
+		offset = REG_DP1_TOTAL_HOR_VER - REG_DP_TOTAL_HOR_VER;
+
 	total_hor = drm_mode->htotal;
 
 	total_ver = drm_mode->vtotal;
@@ -624,10 +654,10 @@ int msm_dp_panel_timing_cfg(struct msm_dp_panel *msm_dp_panel, bool wide_bus_en)
 
 	msm_dp_active = data;
 
-	msm_dp_write_link(panel, REG_DP_TOTAL_HOR_VER, total);
-	msm_dp_write_link(panel, REG_DP_START_HOR_VER_FROM_SYNC, sync_start);
-	msm_dp_write_link(panel, REG_DP_HSYNC_VSYNC_WIDTH_POLARITY, width_blanking);
-	msm_dp_write_link(panel, REG_DP_ACTIVE_HOR_VER, msm_dp_active);
+	msm_dp_write_link(panel, REG_DP_TOTAL_HOR_VER + offset, total);
+	msm_dp_write_link(panel, REG_DP_START_HOR_VER_FROM_SYNC  + offset, sync_start);
+	msm_dp_write_link(panel, REG_DP_HSYNC_VSYNC_WIDTH_POLARITY + offset, width_blanking);
+	msm_dp_write_link(panel, REG_DP_ACTIVE_HOR_VER  + offset, msm_dp_active);
 
 	reg = msm_dp_read_pn(panel, MMSS_DP_INTF_CONFIG);
 	if (wide_bus_en)
diff --git a/drivers/gpu/drm/msm/dp/dp_reg.h b/drivers/gpu/drm/msm/dp/dp_reg.h
index 3689642b7fc0..295c1161e6b7 100644
--- a/drivers/gpu/drm/msm/dp/dp_reg.h
+++ b/drivers/gpu/drm/msm/dp/dp_reg.h
@@ -332,6 +332,17 @@
 #define DP_TPG_VIDEO_CONFIG_BPP_8BIT		(0x00000001)
 #define DP_TPG_VIDEO_CONFIG_RGB			(0x00000004)
 
+/* DP MST registers */
+#define REG_DP1_CONFIGURATION_CTRL		(0x00000400)
+#define REG_DP1_SOFTWARE_MVID			(0x00000414)
+#define REG_DP1_SOFTWARE_NVID			(0x00000418)
+#define REG_DP1_TOTAL_HOR_VER			(0x0000041C)
+#define REG_DP1_MISC1_MISC0			(0x0000042C)
+#define MMSS_DP1_GENERIC0_0			(0x00000490)
+#define MMSS_DP1_SDP_CFG			(0x000004E0)
+#define MMSS_DP1_SDP_CFG2			(0x000004E4)
+#define MMSS_DP1_SDP_CFG3			(0x000004E8)
+
 #define MMSS_DP_ASYNC_FIFO_CONFIG		(0x00000088)
 
 #define REG_DP_PHY_AUX_INTERRUPT_CLEAR          (0x0000004C)

-- 
2.43.0


^ permalink raw reply	[flat|nested] 128+ messages in thread

* [PATCH v4 17/39] drm/msm/dp: Add catalog support for 3rd/4th stream MST
  2026-04-10  9:33 [PATCH v4 00/39] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
                   ` (15 preceding siblings ...)
  2026-04-10  9:33 ` [PATCH v4 16/39] drm/msm/dp: use stream_id to change offsets in dp_catalog Yongxing Mou
@ 2026-04-10  9:33 ` Yongxing Mou
  2026-04-11 18:24   ` Dmitry Baryshkov
  2026-04-10  9:33 ` [PATCH v4 18/39] drm/msm/dp: add support to send ACT packets for MST Yongxing Mou
                   ` (23 subsequent siblings)
  40 siblings, 1 reply; 128+ messages in thread
From: Yongxing Mou @ 2026-04-10  9:33 UTC (permalink / raw)
  To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang
  Cc: linux-arm-msm, dri-devel, freedreno, linux-kernel, Yongxing Mou

To support 4-stream MST, the link clocks for stream 3 and stream 4 are
controlled by MST_2_LCLK and MST_3_LCLK which share the same register
definitions but use different base addresses.

Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
---
 drivers/gpu/drm/msm/dp/dp_ctrl.c    | 225 ++++++++++++++++++++++--------------
 drivers/gpu/drm/msm/dp/dp_ctrl.h    |   4 +-
 drivers/gpu/drm/msm/dp/dp_display.c |  24 +++-
 drivers/gpu/drm/msm/dp/dp_panel.c   | 135 +++++++++++++++++-----
 drivers/gpu/drm/msm/dp/dp_panel.h   |   2 +
 drivers/gpu/drm/msm/dp/dp_reg.h     |  16 ++-
 6 files changed, 283 insertions(+), 123 deletions(-)

diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c
index a52bcd9ea2a3..1109b2df21be 100644
--- a/drivers/gpu/drm/msm/dp/dp_ctrl.c
+++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c
@@ -118,6 +118,8 @@ struct msm_dp_ctrl_private {
 	struct msm_dp_link *link;
 	void __iomem *ahb_base;
 	void __iomem *link_base;
+	void __iomem *mst2link_base;
+	void __iomem *mst3link_base;
 
 	struct phy *phy;
 
@@ -158,19 +160,45 @@ static inline void msm_dp_write_ahb(struct msm_dp_ctrl_private *ctrl,
 	writel(data, ctrl->ahb_base + offset);
 }
 
-static inline u32 msm_dp_read_link(struct msm_dp_ctrl_private *ctrl, u32 offset)
+static inline u32 msm_dp_read_link(struct msm_dp_ctrl_private *ctrl,
+				   enum msm_dp_stream_id stream_id, u32 offset)
 {
-	return readl_relaxed(ctrl->link_base + offset);
+	switch (stream_id) {
+	case DP_STREAM_0:
+	case DP_STREAM_1:
+		return readl_relaxed(ctrl->link_base + offset);
+	case DP_STREAM_2:
+		return readl_relaxed(ctrl->mst2link_base + offset);
+	case DP_STREAM_3:
+		return readl_relaxed(ctrl->mst3link_base + offset);
+	default:
+		DRM_ERROR("error stream_id\n");
+		return 0;
+	}
 }
 
 static inline void msm_dp_write_link(struct msm_dp_ctrl_private *ctrl,
-			       u32 offset, u32 data)
+				     enum msm_dp_stream_id stream_id, u32 offset, u32 data)
 {
 	/*
 	 * To make sure link reg writes happens before any other operation,
 	 * this function uses writel() instread of writel_relaxed()
 	 */
-	writel(data, ctrl->link_base + offset);
+	switch (stream_id) {
+	case DP_STREAM_0:
+	case DP_STREAM_1:
+		writel(data, ctrl->link_base + offset);
+		break;
+	case DP_STREAM_2:
+		writel(data, ctrl->mst2link_base + offset);
+		break;
+	case DP_STREAM_3:
+		writel(data, ctrl->mst3link_base + offset);
+		break;
+	default:
+		DRM_ERROR("error stream_id\n");
+		break;
+	}
 }
 
 static int msm_dp_aux_link_configure(struct drm_dp_aux *aux,
@@ -294,18 +322,18 @@ static void msm_dp_ctrl_psr_mainlink_enable(struct msm_dp_ctrl_private *ctrl)
 {
 	u32 val;
 
-	val = msm_dp_read_link(ctrl, REG_DP_MAINLINK_CTRL);
+	val = msm_dp_read_link(ctrl, 0, REG_DP_MAINLINK_CTRL);
 	val |= DP_MAINLINK_CTRL_ENABLE;
-	msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, val);
+	msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_CTRL, val);
 }
 
 static void msm_dp_ctrl_psr_mainlink_disable(struct msm_dp_ctrl_private *ctrl)
 {
 	u32 val;
 
-	val = msm_dp_read_link(ctrl, REG_DP_MAINLINK_CTRL);
+	val = msm_dp_read_link(ctrl, 0, REG_DP_MAINLINK_CTRL);
 	val &= ~DP_MAINLINK_CTRL_ENABLE;
-	msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, val);
+	msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_CTRL, val);
 }
 
 static void msm_dp_ctrl_mainlink_enable(struct msm_dp_ctrl_private *ctrl)
@@ -314,21 +342,21 @@ static void msm_dp_ctrl_mainlink_enable(struct msm_dp_ctrl_private *ctrl)
 
 	drm_dbg_dp(ctrl->drm_dev, "enable\n");
 
-	mainlink_ctrl = msm_dp_read_link(ctrl, REG_DP_MAINLINK_CTRL);
+	mainlink_ctrl = msm_dp_read_link(ctrl, 0, REG_DP_MAINLINK_CTRL);
 
 	mainlink_ctrl &= ~(DP_MAINLINK_CTRL_RESET |
 					DP_MAINLINK_CTRL_ENABLE);
-	msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
+	msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
 
 	mainlink_ctrl |= DP_MAINLINK_CTRL_RESET;
-	msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
+	msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
 
 	mainlink_ctrl &= ~DP_MAINLINK_CTRL_RESET;
-	msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
+	msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
 
 	mainlink_ctrl |= (DP_MAINLINK_CTRL_ENABLE |
 				DP_MAINLINK_FB_BOUNDARY_SEL);
-	msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
+	msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
 }
 
 static void msm_dp_ctrl_mainlink_disable(struct msm_dp_ctrl_private *ctrl)
@@ -337,23 +365,23 @@ static void msm_dp_ctrl_mainlink_disable(struct msm_dp_ctrl_private *ctrl)
 
 	drm_dbg_dp(ctrl->drm_dev, "disable\n");
 
-	mainlink_ctrl = msm_dp_read_link(ctrl, REG_DP_MAINLINK_CTRL);
+	mainlink_ctrl = msm_dp_read_link(ctrl, 0, REG_DP_MAINLINK_CTRL);
 	mainlink_ctrl &= ~DP_MAINLINK_CTRL_ENABLE;
-	msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
+	msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
 }
 
 static void msm_dp_setup_peripheral_flush(struct msm_dp_ctrl_private *ctrl)
 {
 	u32 mainlink_ctrl;
 
-	mainlink_ctrl = msm_dp_read_link(ctrl, REG_DP_MAINLINK_CTRL);
+	mainlink_ctrl = msm_dp_read_link(ctrl, 0, REG_DP_MAINLINK_CTRL);
 
 	if (ctrl->hw_revision >= DP_HW_VERSION_1_2)
 		mainlink_ctrl |= DP_MAINLINK_FLUSH_MODE_SDE_PERIPH_UPDATE;
 	else
 		mainlink_ctrl |= DP_MAINLINK_FLUSH_MODE_UPDATE_SDP;
 
-	msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
+	msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
 }
 
 static bool msm_dp_ctrl_mainlink_ready(struct msm_dp_ctrl_private *ctrl)
@@ -380,7 +408,7 @@ void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl)
 	ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl);
 
 	reinit_completion(&ctrl->idle_comp);
-	msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, DP_STATE_CTRL_PUSH_IDLE);
+	msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, DP_STATE_CTRL_PUSH_IDLE);
 
 	if (!wait_for_completion_timeout(&ctrl->idle_comp,
 			IDLE_PATTERN_COMPLETION_TIMEOUT_JIFFIES))
@@ -395,7 +423,11 @@ static void msm_dp_ctrl_config_ctrl_streams(struct msm_dp_ctrl_private *ctrl,
 	u32 config = 0, tbd;
 	u32 reg_offset = 0;
 
-	config = msm_dp_read_link(ctrl, REG_DP_CONFIGURATION_CTRL);
+	if (msm_dp_panel->stream_id == DP_STREAM_0)
+		config = msm_dp_read_link(ctrl, 0, REG_DP_CONFIGURATION_CTRL);
+
+	if (msm_dp_panel->stream_id == DP_STREAM_1)
+		reg_offset = REG_DP1_CONFIGURATION_CTRL - REG_DP_CONFIGURATION_CTRL;
 
 	if (msm_dp_panel->msm_dp_mode.out_fmt_is_yuv_420)
 		config |= DP_CONFIGURATION_CTRL_RGB_YUV; /* YUV420 */
@@ -410,8 +442,10 @@ static void msm_dp_ctrl_config_ctrl_streams(struct msm_dp_ctrl_private *ctrl,
 
 	drm_dbg_dp(ctrl->drm_dev, "stream DP_CONFIGURATION_CTRL=0x%x\n", config);
 
-	if (msm_dp_panel->stream_id == DP_STREAM_1)
-		reg_offset = REG_DP1_CONFIGURATION_CTRL - REG_DP_CONFIGURATION_CTRL;
+	msm_dp_write_link(ctrl, msm_dp_panel->stream_id, msm_dp_panel->stream_id > 1 ?
+			  REG_DP_MSTLINK_CONFIGURATION_CTRL :
+			  REG_DP_CONFIGURATION_CTRL + reg_offset, config);
+
 }
 
 static void msm_dp_ctrl_config_ctrl_link(struct msm_dp_ctrl_private *ctrl)
@@ -441,7 +475,7 @@ static void msm_dp_ctrl_config_ctrl_link(struct msm_dp_ctrl_private *ctrl)
 
 	drm_dbg_dp(ctrl->drm_dev, "link DP_CONFIGURATION_CTRL=0x%x\n", config);
 
-	msm_dp_write_link(ctrl, REG_DP_CONFIGURATION_CTRL, config);
+	msm_dp_write_link(ctrl, 0, REG_DP_CONFIGURATION_CTRL, config);
 }
 
 static void msm_dp_ctrl_lane_mapping(struct msm_dp_ctrl_private *ctrl)
@@ -454,8 +488,8 @@ static void msm_dp_ctrl_lane_mapping(struct msm_dp_ctrl_private *ctrl)
 	ln_mapping |= lane_map[2] << LANE2_MAPPING_SHIFT;
 	ln_mapping |= lane_map[3] << LANE3_MAPPING_SHIFT;
 
-	msm_dp_write_link(ctrl, REG_DP_LOGICAL2PHYSICAL_LANE_MAPPING,
-			ln_mapping);
+	msm_dp_write_link(ctrl, 0, REG_DP_LOGICAL2PHYSICAL_LANE_MAPPING,
+			  ln_mapping);
 }
 
 static void msm_dp_ctrl_config_misc1_misc0(struct msm_dp_ctrl_private *ctrl,
@@ -471,7 +505,8 @@ static void msm_dp_ctrl_config_misc1_misc0(struct msm_dp_ctrl_private *ctrl,
 	if (msm_dp_panel->stream_id == DP_STREAM_1)
 		reg_offset = REG_DP1_MISC1_MISC0 - REG_DP_MISC1_MISC0;
 
-	misc_val = msm_dp_read_link(ctrl, REG_DP_MISC1_MISC0 + reg_offset);
+	misc_val = msm_dp_read_link(ctrl, msm_dp_panel->stream_id, msm_dp_panel->stream_id > 1 ?
+				    REG_DP_MSTLINK_MISC1_MISC0 : REG_DP_MISC1_MISC0 + reg_offset);
 
 	/* clear bpp bits */
 	misc_val &= ~(0x07 << DP_MISC0_TEST_BITS_DEPTH_SHIFT);
@@ -481,7 +516,10 @@ static void msm_dp_ctrl_config_misc1_misc0(struct msm_dp_ctrl_private *ctrl,
 	misc_val |= DP_MISC0_SYNCHRONOUS_CLK;
 
 	drm_dbg_dp(ctrl->drm_dev, "misc settings = 0x%x\n", misc_val);
-	msm_dp_write_link(ctrl, REG_DP_MISC1_MISC0 + reg_offset, misc_val);
+	msm_dp_write_link(ctrl, msm_dp_panel->stream_id,
+			  msm_dp_panel->stream_id > 1 ?
+			  REG_DP_MSTLINK_MISC1_MISC0 : REG_DP_MISC1_MISC0 + reg_offset,
+			  misc_val);
 }
 
 static void msm_dp_ctrl_configure_source_params(struct msm_dp_ctrl_private *ctrl,
@@ -1307,9 +1345,9 @@ static void msm_dp_ctrl_setup_tr_unit(struct msm_dp_ctrl_private *ctrl)
 	pr_debug("dp_tu=0x%x, valid_boundary=0x%x, valid_boundary2=0x%x\n",
 			msm_dp_tu, valid_boundary, valid_boundary2);
 
-	msm_dp_write_link(ctrl, REG_DP_VALID_BOUNDARY, valid_boundary);
-	msm_dp_write_link(ctrl, REG_DP_TU, msm_dp_tu);
-	msm_dp_write_link(ctrl, REG_DP_VALID_BOUNDARY_2, valid_boundary2);
+	msm_dp_write_link(ctrl, 0, REG_DP_VALID_BOUNDARY, valid_boundary);
+	msm_dp_write_link(ctrl, 0, REG_DP_TU, msm_dp_tu);
+	msm_dp_write_link(ctrl, 0, REG_DP_VALID_BOUNDARY_2, valid_boundary2);
 }
 
 static int msm_dp_ctrl_wait4video_ready(struct msm_dp_ctrl_private *ctrl)
@@ -1426,7 +1464,7 @@ static int msm_dp_ctrl_set_pattern_state_bit(struct msm_dp_ctrl_private *ctrl,
 
 	bit = BIT(state_bit - 1);
 	drm_dbg_dp(ctrl->drm_dev, "hw: bit=%d train=%d\n", bit, state_bit);
-	msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, bit);
+	msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, bit);
 
 	bit = BIT(state_bit - 1) << DP_MAINLINK_READY_LINK_TRAINING_SHIFT;
 
@@ -1453,7 +1491,7 @@ static int msm_dp_ctrl_link_train_1(struct msm_dp_ctrl_private *ctrl,
 	delay_us = drm_dp_read_clock_recovery_delay(ctrl->aux,
 						    ctrl->panel->dpcd, dp_phy, false);
 
-	msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, 0);
+	msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, 0);
 
 	*training_step = DP_TRAINING_1;
 
@@ -1577,7 +1615,7 @@ static int msm_dp_ctrl_link_train_2(struct msm_dp_ctrl_private *ctrl,
 	delay_us = drm_dp_read_channel_eq_delay(ctrl->aux,
 						ctrl->panel->dpcd, dp_phy, false);
 
-	msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, 0);
+	msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, 0);
 
 	*training_step = DP_TRAINING_2;
 
@@ -1694,7 +1732,7 @@ static int msm_dp_ctrl_link_train(struct msm_dp_ctrl_private *ctrl,
 	}
 
 end:
-	msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, 0);
+	msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, 0);
 
 	return ret;
 }
@@ -1840,34 +1878,34 @@ static int msm_dp_ctrl_enable_mainlink_clocks(struct msm_dp_ctrl_private *ctrl)
 static void msm_dp_ctrl_enable_sdp(struct msm_dp_ctrl_private *ctrl)
 {
 	/* trigger sdp */
-	msm_dp_write_link(ctrl, MMSS_DP_SDP_CFG3, UPDATE_SDP);
-	msm_dp_write_link(ctrl, MMSS_DP_SDP_CFG3, 0x0);
+	msm_dp_write_link(ctrl, 0, MMSS_DP_SDP_CFG3, UPDATE_SDP);
+	msm_dp_write_link(ctrl, 0, MMSS_DP_SDP_CFG3, 0x0);
 }
 
 static void msm_dp_ctrl_psr_enter(struct msm_dp_ctrl_private *ctrl)
 {
 	u32 cmd;
 
-	cmd = msm_dp_read_link(ctrl, REG_PSR_CMD);
+	cmd = msm_dp_read_link(ctrl, 0, REG_PSR_CMD);
 
 	cmd &= ~(PSR_ENTER | PSR_EXIT);
 	cmd |= PSR_ENTER;
 
 	msm_dp_ctrl_enable_sdp(ctrl);
-	msm_dp_write_link(ctrl, REG_PSR_CMD, cmd);
+	msm_dp_write_link(ctrl, 0, REG_PSR_CMD, cmd);
 }
 
 static void msm_dp_ctrl_psr_exit(struct msm_dp_ctrl_private *ctrl)
 {
 	u32 cmd;
 
-	cmd = msm_dp_read_link(ctrl, REG_PSR_CMD);
+	cmd = msm_dp_read_link(ctrl, 0, REG_PSR_CMD);
 
 	cmd &= ~(PSR_ENTER | PSR_EXIT);
 	cmd |= PSR_EXIT;
 
 	msm_dp_ctrl_enable_sdp(ctrl);
-	msm_dp_write_link(ctrl, REG_PSR_CMD, cmd);
+	msm_dp_write_link(ctrl, 0, REG_PSR_CMD, cmd);
 }
 
 void msm_dp_ctrl_config_psr(struct msm_dp_ctrl *msm_dp_ctrl)
@@ -1880,9 +1918,9 @@ void msm_dp_ctrl_config_psr(struct msm_dp_ctrl *msm_dp_ctrl)
 		return;
 
 	/* enable PSR1 function */
-	cfg = msm_dp_read_link(ctrl, REG_PSR_CONFIG);
+	cfg = msm_dp_read_link(ctrl, 0, REG_PSR_CONFIG);
 	cfg |= PSR1_SUPPORTED;
-	msm_dp_write_link(ctrl, REG_PSR_CONFIG, cfg);
+	msm_dp_write_link(ctrl, 0, REG_PSR_CONFIG, cfg);
 
 	msm_dp_ctrl_config_psr_interrupt(ctrl);
 	msm_dp_ctrl_enable_sdp(ctrl);
@@ -1921,16 +1959,16 @@ void msm_dp_ctrl_set_psr(struct msm_dp_ctrl *msm_dp_ctrl, bool enter)
 		}
 
 		msm_dp_ctrl_push_idle(msm_dp_ctrl);
-		msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, 0);
+		msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, 0);
 
 		msm_dp_ctrl_psr_mainlink_disable(ctrl);
 	} else {
 		msm_dp_ctrl_psr_mainlink_enable(ctrl);
 
 		msm_dp_ctrl_psr_exit(ctrl);
-		msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, DP_STATE_CTRL_SEND_VIDEO);
+		msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, DP_STATE_CTRL_SEND_VIDEO);
 		msm_dp_ctrl_wait4video_ready(ctrl);
-		msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, 0);
+		msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, 0);
 	}
 }
 
@@ -2041,7 +2079,7 @@ static int msm_dp_ctrl_link_maintenance(struct msm_dp_ctrl_private *ctrl)
 
 	msm_dp_ctrl_clear_training_pattern(ctrl, DP_PHY_DPRX);
 
-	msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, DP_STATE_CTRL_SEND_VIDEO);
+	msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, DP_STATE_CTRL_SEND_VIDEO);
 
 	ret = msm_dp_ctrl_wait4video_ready(ctrl);
 end:
@@ -2056,72 +2094,72 @@ static void msm_dp_ctrl_send_phy_pattern(struct msm_dp_ctrl_private *ctrl,
 	u32 value = 0x0;
 
 	/* Make sure to clear the current pattern before starting a new one */
-	msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, 0x0);
+	msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, 0x0);
 
 	drm_dbg_dp(ctrl->drm_dev, "pattern: %#x\n", pattern);
 	switch (pattern) {
 	case DP_PHY_TEST_PATTERN_D10_2:
-		msm_dp_write_link(ctrl, REG_DP_STATE_CTRL,
-			      DP_STATE_CTRL_LINK_TRAINING_PATTERN1);
+		msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL,
+				  DP_STATE_CTRL_LINK_TRAINING_PATTERN1);
 		break;
 
 	case DP_PHY_TEST_PATTERN_ERROR_COUNT:
 		value &= ~(1 << 16);
-		msm_dp_write_link(ctrl, REG_DP_HBR2_COMPLIANCE_SCRAMBLER_RESET,
-			      value);
+		msm_dp_write_link(ctrl, 0, REG_DP_HBR2_COMPLIANCE_SCRAMBLER_RESET,
+				  value);
 		value |= SCRAMBLER_RESET_COUNT_VALUE;
-		msm_dp_write_link(ctrl, REG_DP_HBR2_COMPLIANCE_SCRAMBLER_RESET,
-			      value);
-		msm_dp_write_link(ctrl, REG_DP_MAINLINK_LEVELS,
-			      DP_MAINLINK_SAFE_TO_EXIT_LEVEL_2);
-		msm_dp_write_link(ctrl, REG_DP_STATE_CTRL,
-			      DP_STATE_CTRL_LINK_SYMBOL_ERR_MEASURE);
+		msm_dp_write_link(ctrl, 0, REG_DP_HBR2_COMPLIANCE_SCRAMBLER_RESET,
+				  value);
+		msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_LEVELS,
+				  DP_MAINLINK_SAFE_TO_EXIT_LEVEL_2);
+		msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL,
+				  DP_STATE_CTRL_LINK_SYMBOL_ERR_MEASURE);
 		break;
 
 	case DP_PHY_TEST_PATTERN_PRBS7:
-		msm_dp_write_link(ctrl, REG_DP_STATE_CTRL,
-			      DP_STATE_CTRL_LINK_PRBS7);
+		msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL,
+				  DP_STATE_CTRL_LINK_PRBS7);
 		break;
 
 	case DP_PHY_TEST_PATTERN_80BIT_CUSTOM:
-		msm_dp_write_link(ctrl, REG_DP_STATE_CTRL,
-			      DP_STATE_CTRL_LINK_TEST_CUSTOM_PATTERN);
+		msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL,
+				  DP_STATE_CTRL_LINK_TEST_CUSTOM_PATTERN);
 		/* 00111110000011111000001111100000 */
-		msm_dp_write_link(ctrl, REG_DP_TEST_80BIT_CUSTOM_PATTERN_REG0,
-			      0x3E0F83E0);
+		msm_dp_write_link(ctrl, 0, REG_DP_TEST_80BIT_CUSTOM_PATTERN_REG0,
+				  0x3E0F83E0);
 		/* 00001111100000111110000011111000 */
-		msm_dp_write_link(ctrl, REG_DP_TEST_80BIT_CUSTOM_PATTERN_REG1,
-			      0x0F83E0F8);
+		msm_dp_write_link(ctrl, 0, REG_DP_TEST_80BIT_CUSTOM_PATTERN_REG1,
+				  0x0F83E0F8);
 		/* 1111100000111110 */
-		msm_dp_write_link(ctrl, REG_DP_TEST_80BIT_CUSTOM_PATTERN_REG2,
-			      0x0000F83E);
+		msm_dp_write_link(ctrl, 0, REG_DP_TEST_80BIT_CUSTOM_PATTERN_REG2,
+				  0x0000F83E);
 		break;
 
 	case DP_PHY_TEST_PATTERN_CP2520:
-		value = msm_dp_read_link(ctrl, REG_DP_MAINLINK_CTRL);
+		value = msm_dp_read_link(ctrl, 0, REG_DP_MAINLINK_CTRL);
 		value &= ~DP_MAINLINK_CTRL_SW_BYPASS_SCRAMBLER;
-		msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, value);
+		msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_CTRL, value);
 
 		value = DP_HBR2_ERM_PATTERN;
-		msm_dp_write_link(ctrl, REG_DP_HBR2_COMPLIANCE_SCRAMBLER_RESET,
-			      value);
+		msm_dp_write_link(ctrl, 0, REG_DP_HBR2_COMPLIANCE_SCRAMBLER_RESET,
+				  value);
 		value |= SCRAMBLER_RESET_COUNT_VALUE;
-		msm_dp_write_link(ctrl, REG_DP_HBR2_COMPLIANCE_SCRAMBLER_RESET,
-			      value);
-		msm_dp_write_link(ctrl, REG_DP_MAINLINK_LEVELS,
-			      DP_MAINLINK_SAFE_TO_EXIT_LEVEL_2);
-		msm_dp_write_link(ctrl, REG_DP_STATE_CTRL,
-			      DP_STATE_CTRL_LINK_SYMBOL_ERR_MEASURE);
-		value = msm_dp_read_link(ctrl, REG_DP_MAINLINK_CTRL);
+		msm_dp_write_link(ctrl, 0, REG_DP_HBR2_COMPLIANCE_SCRAMBLER_RESET,
+				  value);
+		msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_LEVELS,
+				  DP_MAINLINK_SAFE_TO_EXIT_LEVEL_2);
+		msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL,
+				  DP_STATE_CTRL_LINK_SYMBOL_ERR_MEASURE);
+		value = msm_dp_read_link(ctrl, 0, REG_DP_MAINLINK_CTRL);
 		value |= DP_MAINLINK_CTRL_ENABLE;
-		msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, value);
+		msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_CTRL, value);
 		break;
 
 	case DP_PHY_TEST_PATTERN_SEL_MASK:
-		msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL,
-			      DP_MAINLINK_CTRL_ENABLE);
-		msm_dp_write_link(ctrl, REG_DP_STATE_CTRL,
-			      DP_STATE_CTRL_LINK_TRAINING_PATTERN4);
+		msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_CTRL,
+				  DP_MAINLINK_CTRL_ENABLE);
+		msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL,
+				  DP_STATE_CTRL_LINK_TRAINING_PATTERN4);
 		break;
 
 	default:
@@ -2149,7 +2187,7 @@ static bool msm_dp_ctrl_send_phy_test_pattern(struct msm_dp_ctrl_private *ctrl)
 	msm_dp_ctrl_update_phy_vx_px(ctrl, DP_PHY_DPRX);
 	msm_dp_link_send_test_response(ctrl->link);
 
-	pattern_sent = msm_dp_read_link(ctrl, REG_DP_MAINLINK_READY);
+	pattern_sent = msm_dp_read_link(ctrl, 0, REG_DP_MAINLINK_READY);
 
 	switch (pattern_sent) {
 	case MR_LINK_TRAINING1:
@@ -2522,8 +2560,14 @@ static void msm_dp_ctrl_config_msa(struct msm_dp_ctrl_private *ctrl,
 		nvid *= 3;
 
 	drm_dbg_dp(ctrl->drm_dev, "mvid=0x%x, nvid=0x%x\n", mvid, nvid);
-	msm_dp_write_link(ctrl, REG_DP_SOFTWARE_MVID + mvid_reg_off, mvid);
-	msm_dp_write_link(ctrl, REG_DP_SOFTWARE_NVID + nvid_reg_off, nvid);
+	msm_dp_write_link(ctrl, msm_dp_panel->stream_id,
+			  msm_dp_panel->stream_id > 1 ?
+			  REG_MSTLINK_SOFTWARE_MVID : REG_DP_SOFTWARE_MVID + mvid_reg_off,
+			  mvid);
+	msm_dp_write_link(ctrl, msm_dp_panel->stream_id,
+			  msm_dp_panel->stream_id > 1 ?
+			  REG_MSTLINK_SOFTWARE_NVID : REG_DP_SOFTWARE_NVID + nvid_reg_off,
+			  nvid);
 }
 
 int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train)
@@ -2593,7 +2637,8 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *
 
 	msm_dp_ctrl_lane_mapping(ctrl);
 	msm_dp_setup_peripheral_flush(ctrl);
-	msm_dp_ctrl_config_ctrl_link(ctrl);
+	if (msm_dp_panel->stream_id == DP_STREAM_0)
+		msm_dp_ctrl_config_ctrl_link(ctrl);
 
 	msm_dp_ctrl_configure_source_params(ctrl, msm_dp_panel);
 
@@ -2607,7 +2652,7 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *
 
 	msm_dp_ctrl_setup_tr_unit(ctrl);
 
-	msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, DP_STATE_CTRL_SEND_VIDEO);
+	msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, DP_STATE_CTRL_SEND_VIDEO);
 
 	ret = msm_dp_ctrl_wait4video_ready(ctrl);
 	if (ret)
@@ -2787,7 +2832,9 @@ struct msm_dp_ctrl *msm_dp_ctrl_get(struct device *dev, struct msm_dp_link *link
 			struct phy *phy,
 			int max_stream,
 			void __iomem *ahb_base,
-			void __iomem *link_base)
+			void __iomem *link_base,
+			void __iomem *mst2link_base,
+			void __iomem *mst3link_base)
 {
 	struct msm_dp_ctrl_private *ctrl;
 	int ret;
@@ -2827,6 +2874,8 @@ struct msm_dp_ctrl *msm_dp_ctrl_get(struct device *dev, struct msm_dp_link *link
 	ctrl->phy      = phy;
 	ctrl->ahb_base = ahb_base;
 	ctrl->link_base = link_base;
+	ctrl->mst2link_base = mst2link_base;
+	ctrl->mst3link_base = mst3link_base;
 
 	ret = msm_dp_ctrl_clk_init(&ctrl->msm_dp_ctrl, max_stream);
 	if (ret) {
diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.h b/drivers/gpu/drm/msm/dp/dp_ctrl.h
index 6fed3ff3a72d..e72d501ac1ce 100644
--- a/drivers/gpu/drm/msm/dp/dp_ctrl.h
+++ b/drivers/gpu/drm/msm/dp/dp_ctrl.h
@@ -31,7 +31,9 @@ struct msm_dp_ctrl *msm_dp_ctrl_get(struct device *dev,
 				    struct phy *phy,
 				    int max_stream,
 				    void __iomem *ahb_base,
-				    void __iomem *link_base);
+				    void __iomem *link_base,
+				    void __iomem *mst2link_base,
+				    void __iomem *mst3link_base);
 
 void msm_dp_ctrl_reset(struct msm_dp_ctrl *msm_dp_ctrl);
 void msm_dp_ctrl_phy_init(struct msm_dp_ctrl *msm_dp_ctrl);
diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
index ff506064a3fa..a924fbd825f7 100644
--- a/drivers/gpu/drm/msm/dp/dp_display.c
+++ b/drivers/gpu/drm/msm/dp/dp_display.c
@@ -85,6 +85,12 @@ struct msm_dp_display_private {
 	void __iomem *link_base;
 	size_t link_len;
 
+	void __iomem *mst2link_base;
+	size_t mst2link_len;
+
+	void __iomem *mst3link_base;
+	size_t mst3link_len;
+
 	void __iomem *pixel_base[DP_STREAM_MAX];
 	size_t pixel_len;
 
@@ -561,7 +567,8 @@ static int msm_dp_init_sub_modules(struct msm_dp_display_private *dp)
 		goto error_link;
 	}
 
-	dp->panel = msm_dp_panel_get(dev, dp->aux, dp->link, dp->link_base, dp->pixel_base[0]);
+	dp->panel = msm_dp_panel_get(dev, dp->aux, dp->link, dp->link_base,
+				     dp->mst2link_base, dp->mst3link_base, dp->pixel_base[0]);
 	if (IS_ERR(dp->panel)) {
 		rc = PTR_ERR(dp->panel);
 		DRM_ERROR("failed to initialize panel, rc = %d\n", rc);
@@ -570,7 +577,8 @@ static int msm_dp_init_sub_modules(struct msm_dp_display_private *dp)
 	}
 
 	dp->ctrl = msm_dp_ctrl_get(dev, dp->link, dp->panel, dp->aux,
-			       phy, dp->max_stream, dp->ahb_base, dp->link_base);
+			       phy, dp->max_stream, dp->ahb_base,
+			       dp->link_base, dp->mst2link_base, dp->mst3link_base);
 	if (IS_ERR(dp->ctrl)) {
 		rc = PTR_ERR(dp->ctrl);
 		DRM_ERROR("failed to initialize ctrl, rc = %d\n", rc);
@@ -883,6 +891,10 @@ void msm_dp_snapshot(struct msm_disp_state *disp_state, struct msm_dp *dp)
 				    msm_dp_display->aux_base, "dp_aux");
 	msm_disp_snapshot_add_block(disp_state, msm_dp_display->link_len,
 				    msm_dp_display->link_base, "dp_link");
+	msm_disp_snapshot_add_block(disp_state, msm_dp_display->mst2link_len,
+				    msm_dp_display->mst2link_base, "dp_mst2link");
+	msm_disp_snapshot_add_block(disp_state, msm_dp_display->mst3link_len,
+				    msm_dp_display->mst3link_base, "dp_mst3link");
 	msm_disp_snapshot_add_block(disp_state, msm_dp_display->pixel_len,
 				    msm_dp_display->pixel_base[0], "dp_p0");
 	msm_disp_snapshot_add_block(disp_state, msm_dp_display->pixel_len,
@@ -1228,6 +1240,14 @@ static int msm_dp_display_get_io(struct msm_dp_display_private *display)
 		}
 	}
 
+	display->mst2link_base = msm_dp_ioremap(pdev, 7, &display->mst2link_len);
+	if (IS_ERR(display->mst2link_base))
+		DRM_DEBUG_DP("unable to remap link region: %pe\n", display->mst2link_base);
+
+	display->mst3link_base = msm_dp_ioremap(pdev, 8, &display->mst3link_len);
+	if (IS_ERR(display->mst3link_base))
+		DRM_DEBUG_DP("unable to remap link region: %pe\n", display->mst3link_base);
+
 	return 0;
 }
 
diff --git a/drivers/gpu/drm/msm/dp/dp_panel.c b/drivers/gpu/drm/msm/dp/dp_panel.c
index 6c88cc7e3037..a8a6297b37e3 100644
--- a/drivers/gpu/drm/msm/dp/dp_panel.c
+++ b/drivers/gpu/drm/msm/dp/dp_panel.c
@@ -25,23 +25,50 @@ struct msm_dp_panel_private {
 	struct drm_dp_aux *aux;
 	struct msm_dp_link *link;
 	void __iomem *link_base;
+	void __iomem *mst2link_base;
+	void __iomem *mst3link_base;
 	void __iomem *pixel_base;
 	bool panel_on;
 };
 
 static inline u32 msm_dp_read_link(struct msm_dp_panel_private *panel, u32 offset)
 {
-	return readl_relaxed(panel->link_base + offset);
+	switch (panel->msm_dp_panel.stream_id) {
+	case DP_STREAM_0:
+	case DP_STREAM_1:
+		return readl_relaxed(panel->link_base + offset);
+	case DP_STREAM_2:
+		return readl_relaxed(panel->mst2link_base + offset);
+	case DP_STREAM_3:
+		return readl_relaxed(panel->mst3link_base + offset);
+	default:
+		DRM_ERROR("error stream_id\n");
+		return 0;
+	}
 }
 
 static inline void msm_dp_write_link(struct msm_dp_panel_private *panel,
-			       u32 offset, u32 data)
+					u32 offset, u32 data)
 {
 	/*
 	 * To make sure link reg writes happens before any other operation,
 	 * this function uses writel() instread of writel_relaxed()
 	 */
-	writel(data, panel->link_base + offset);
+	switch (panel->msm_dp_panel.stream_id) {
+	case DP_STREAM_0:
+	case DP_STREAM_1:
+		writel(data, panel->link_base + offset);
+		break;
+	case DP_STREAM_2:
+		writel(data, panel->mst2link_base + offset);
+		break;
+	case DP_STREAM_3:
+		writel(data, panel->mst3link_base + offset);
+		break;
+	default:
+		DRM_ERROR("error stream_id\n");
+		break;
+	}
 }
 
 static inline void msm_dp_write_pn(struct msm_dp_panel_private *panel,
@@ -444,38 +471,51 @@ void msm_dp_panel_clear_dsc_dto(struct msm_dp_panel *msm_dp_panel)
 
 static void msm_dp_panel_send_vsc_sdp(struct msm_dp_panel_private *panel, struct dp_sdp *vsc_sdp)
 {
+	u32 id = panel->msm_dp_panel.stream_id;
 	u32 header[2];
 	u32 val;
 	int i;
 	u32 offset = 0;
 
-	if (panel->msm_dp_panel.stream_id == DP_STREAM_1)
+	if (id == DP_STREAM_1)
 		offset = MMSS_DP1_GENERIC0_0 - MMSS_DP_GENERIC0_0;
 
 	msm_dp_utils_pack_sdp_header(&vsc_sdp->sdp_header, header);
 
-	msm_dp_write_link(panel, MMSS_DP_GENERIC0_0 + offset, header[0]);
-	msm_dp_write_link(panel, MMSS_DP_GENERIC0_1 + offset, header[1]);
+	msm_dp_write_link(panel, id > 1 ?
+			  MMSS_DP_MSTLINK_GENERIC0_0 : MMSS_DP_GENERIC0_0 + offset,
+			  header[0]);
+	msm_dp_write_link(panel, id > 1 ?
+			  MMSS_DP_MSTLINK_GENERIC0_1 : MMSS_DP_GENERIC0_1 + offset,
+			  header[1]);
 
 	for (i = 0; i < sizeof(vsc_sdp->db); i += 4) {
 		val = ((vsc_sdp->db[i]) | (vsc_sdp->db[i + 1] << 8) | (vsc_sdp->db[i + 2] << 16) |
 		       (vsc_sdp->db[i + 3] << 24));
-		msm_dp_write_link(panel, MMSS_DP_GENERIC0_2 + i + offset, val);
+
+		msm_dp_write_link(panel, id > 1 ?
+				  MMSS_DP_MSTLINK_GENERIC0_2 + i : MMSS_DP_GENERIC0_2 + i + offset,
+				  val);
 	}
 }
 
 static void msm_dp_panel_update_sdp(struct msm_dp_panel_private *panel)
 {
+	u32 id = panel->msm_dp_panel.stream_id;
 	u32 hw_revision = panel->msm_dp_panel.hw_revision;
 	u32 offset = 0;
 
-	if (panel->msm_dp_panel.stream_id == DP_STREAM_1)
+	if (id == DP_STREAM_1)
 		offset = MMSS_DP1_SDP_CFG3 - MMSS_DP_SDP_CFG3;
 
 	if (hw_revision >= DP_HW_VERSION_1_0 &&
 	    hw_revision < DP_HW_VERSION_1_2) {
-		msm_dp_write_link(panel, MMSS_DP_SDP_CFG3 + offset, UPDATE_SDP);
-		msm_dp_write_link(panel, MMSS_DP_SDP_CFG3 + offset, 0x0);
+		msm_dp_write_link(panel, id > 1 ?
+				  MMSS_DP_MSTLINK_SDP_CFG3 : MMSS_DP_SDP_CFG3 + offset,
+				  UPDATE_SDP);
+		msm_dp_write_link(panel, id > 1 ?
+				  MMSS_DP_MSTLINK_SDP_CFG3 : MMSS_DP_SDP_CFG3 + offset,
+				  0x0);
 	}
 }
 
@@ -483,26 +523,34 @@ void msm_dp_panel_enable_vsc_sdp(struct msm_dp_panel *msm_dp_panel, struct dp_sd
 {
 	struct msm_dp_panel_private *panel =
 		container_of(msm_dp_panel, struct msm_dp_panel_private, msm_dp_panel);
+	u32 id = msm_dp_panel->stream_id;
 	u32 cfg, cfg2, misc;
 	u32 misc_reg_offset = 0;
 	u32 sdp_cfg_offset = 0;
 	u32 sdp_cfg2_offset = 0;
 
-	if (msm_dp_panel->stream_id == DP_STREAM_1) {
+	if (id == DP_STREAM_1) {
 		misc_reg_offset = REG_DP1_MISC1_MISC0 - REG_DP_MISC1_MISC0;
 		sdp_cfg_offset = MMSS_DP1_SDP_CFG - MMSS_DP_SDP_CFG;
 		sdp_cfg2_offset = MMSS_DP1_SDP_CFG2 - MMSS_DP_SDP_CFG2;
 	}
 
-	cfg = msm_dp_read_link(panel, MMSS_DP_SDP_CFG + sdp_cfg_offset);
-	cfg2 = msm_dp_read_link(panel, MMSS_DP_SDP_CFG2 + sdp_cfg2_offset);
-	misc = msm_dp_read_link(panel, REG_DP_MISC1_MISC0 + misc_reg_offset);
+	cfg = msm_dp_read_link(panel, id > 1 ?
+			       MMSS_DP_MSTLINK_SDP_CFG : MMSS_DP_SDP_CFG + sdp_cfg_offset);
+	cfg2 = msm_dp_read_link(panel, id > 1 ?
+				MMSS_DP_MSTLINK_SDP_CFG2 : MMSS_DP_SDP_CFG2 + sdp_cfg2_offset);
+	misc = msm_dp_read_link(panel, id > 1 ?
+				REG_DP_MSTLINK_MISC1_MISC0 : REG_DP_MISC1_MISC0 + misc_reg_offset);
 
 	cfg |= GEN0_SDP_EN;
-	msm_dp_write_link(panel, MMSS_DP_SDP_CFG + sdp_cfg_offset, cfg);
-
 	cfg2 |= GENERIC0_SDPSIZE_VALID;
-	msm_dp_write_link(panel, MMSS_DP_SDP_CFG2 + sdp_cfg2_offset, cfg2);
+
+	msm_dp_write_link(panel, id > 1 ?
+			  MMSS_DP_MSTLINK_SDP_CFG : MMSS_DP_SDP_CFG + sdp_cfg_offset,
+			  cfg);
+	msm_dp_write_link(panel, id > 1 ?
+			  MMSS_DP_MSTLINK_SDP_CFG2 : MMSS_DP_SDP_CFG2 + sdp_cfg2_offset,
+			  cfg2);
 
 	msm_dp_panel_send_vsc_sdp(panel, vsc_sdp);
 
@@ -512,7 +560,9 @@ void msm_dp_panel_enable_vsc_sdp(struct msm_dp_panel *msm_dp_panel, struct dp_sd
 	drm_dbg_dp(panel->drm_dev, "vsc sdp enable=1\n");
 
 	pr_debug("misc settings = 0x%x\n", misc);
-	msm_dp_write_link(panel, REG_DP_MISC1_MISC0 + misc_reg_offset, misc);
+	msm_dp_write_link(panel, id > 1 ?
+			  REG_DP_MSTLINK_MISC1_MISC0 : REG_DP_MISC1_MISC0 + misc_reg_offset,
+			  misc);
 
 	msm_dp_panel_update_sdp(panel);
 }
@@ -521,26 +571,34 @@ void msm_dp_panel_disable_vsc_sdp(struct msm_dp_panel *msm_dp_panel)
 {
 	struct msm_dp_panel_private *panel =
 		container_of(msm_dp_panel, struct msm_dp_panel_private, msm_dp_panel);
+	u32 id = msm_dp_panel->stream_id;
 	u32 cfg, cfg2, misc;
 	u32 misc_reg_offset = 0;
 	u32 sdp_cfg_offset = 0;
 	u32 sdp_cfg2_offset = 0;
 
-	if (msm_dp_panel->stream_id == DP_STREAM_1) {
+	if (id == DP_STREAM_1) {
 		misc_reg_offset = REG_DP1_MISC1_MISC0 - REG_DP_MISC1_MISC0;
 		sdp_cfg_offset = MMSS_DP1_SDP_CFG - MMSS_DP_SDP_CFG;
 		sdp_cfg2_offset = MMSS_DP1_SDP_CFG2 - MMSS_DP_SDP_CFG2;
 	}
 
-	cfg = msm_dp_read_link(panel, MMSS_DP_SDP_CFG + sdp_cfg_offset);
-	cfg2 = msm_dp_read_link(panel, MMSS_DP_SDP_CFG2 + sdp_cfg2_offset);
-	misc = msm_dp_read_link(panel, REG_DP_MISC1_MISC0 + misc_reg_offset);
+	cfg = msm_dp_read_link(panel, id > 1 ?
+			       MMSS_DP_MSTLINK_SDP_CFG : MMSS_DP_SDP_CFG + sdp_cfg_offset);
+	cfg2 = msm_dp_read_link(panel, id > 1 ?
+				MMSS_DP_MSTLINK_SDP_CFG2 : MMSS_DP_SDP_CFG2 + sdp_cfg2_offset);
+	misc = msm_dp_read_link(panel, id > 1 ?
+				REG_DP_MSTLINK_MISC1_MISC0 : REG_DP_MISC1_MISC0 + misc_reg_offset);
 
 	cfg &= ~GEN0_SDP_EN;
-	msm_dp_write_link(panel, MMSS_DP_SDP_CFG + sdp_cfg_offset, cfg);
-
 	cfg2 &= ~GENERIC0_SDPSIZE_VALID;
-	msm_dp_write_link(panel, MMSS_DP_SDP_CFG2 + sdp_cfg2_offset, cfg2);
+
+	msm_dp_write_link(panel, id > 1 ?
+			  MMSS_DP_MSTLINK_SDP_CFG : MMSS_DP_SDP_CFG + sdp_cfg_offset,
+			  cfg);
+	msm_dp_write_link(panel, id > 1 ?
+			  MMSS_DP_MSTLINK_SDP_CFG2 : MMSS_DP_SDP_CFG2 + sdp_cfg2_offset,
+			  cfg2);
 
 	/* switch back to MSA */
 	misc &= ~DP_MISC1_VSC_SDP;
@@ -548,7 +606,9 @@ void msm_dp_panel_disable_vsc_sdp(struct msm_dp_panel *msm_dp_panel)
 	drm_dbg_dp(panel->drm_dev, "vsc sdp enable=0\n");
 
 	pr_debug("misc settings = 0x%x\n", misc);
-	msm_dp_write_link(panel, REG_DP_MISC1_MISC0 + misc_reg_offset, misc);
+	msm_dp_write_link(panel, id > 1 ?
+			  REG_DP_MSTLINK_MISC1_MISC0 : REG_DP_MISC1_MISC0 + misc_reg_offset,
+			  misc);
 
 	msm_dp_panel_update_sdp(panel);
 }
@@ -598,6 +658,7 @@ static int msm_dp_panel_setup_vsc_sdp_yuv_420(struct msm_dp_panel *msm_dp_panel)
 
 int msm_dp_panel_timing_cfg(struct msm_dp_panel *msm_dp_panel, bool wide_bus_en)
 {
+	u32 id = msm_dp_panel->stream_id;
 	u32 data, total_ver, total_hor;
 	struct msm_dp_panel_private *panel;
 	struct drm_display_mode *drm_mode;
@@ -621,7 +682,7 @@ int msm_dp_panel_timing_cfg(struct msm_dp_panel *msm_dp_panel, bool wide_bus_en)
 		drm_mode->vsync_start - drm_mode->vdisplay,
 		drm_mode->vsync_end - drm_mode->vsync_start);
 
-	if (msm_dp_panel->stream_id == DP_STREAM_1)
+	if (id == DP_STREAM_1)
 		offset = REG_DP1_TOTAL_HOR_VER - REG_DP_TOTAL_HOR_VER;
 
 	total_hor = drm_mode->htotal;
@@ -654,10 +715,18 @@ int msm_dp_panel_timing_cfg(struct msm_dp_panel *msm_dp_panel, bool wide_bus_en)
 
 	msm_dp_active = data;
 
-	msm_dp_write_link(panel, REG_DP_TOTAL_HOR_VER + offset, total);
-	msm_dp_write_link(panel, REG_DP_START_HOR_VER_FROM_SYNC  + offset, sync_start);
-	msm_dp_write_link(panel, REG_DP_HSYNC_VSYNC_WIDTH_POLARITY + offset, width_blanking);
-	msm_dp_write_link(panel, REG_DP_ACTIVE_HOR_VER  + offset, msm_dp_active);
+	msm_dp_write_link(panel,
+			  id > 1 ? REG_DP_MSTLINK_TOTAL_HOR_VER :
+			  REG_DP_TOTAL_HOR_VER + offset, total);
+	msm_dp_write_link(panel,
+			  id > 1 ? REG_DP_MSTLINK_START_HOR_VER_FROM_SYNC :
+			  REG_DP_START_HOR_VER_FROM_SYNC + offset, sync_start);
+	msm_dp_write_link(panel,
+			  id > 1 ? REG_DP_MSTLINK_HSYNC_VSYNC_WIDTH_POLARITY :
+			  REG_DP_HSYNC_VSYNC_WIDTH_POLARITY + offset, width_blanking);
+	msm_dp_write_link(panel,
+			  id > 1 ? REG_DP_MSTLINK_ACTIVE_HOR_VER :
+			  REG_DP_ACTIVE_HOR_VER + offset, msm_dp_active);
 
 	reg = msm_dp_read_pn(panel, MMSS_DP_INTF_CONFIG);
 	if (wide_bus_en)
@@ -726,6 +795,8 @@ int msm_dp_panel_init_panel_info(struct msm_dp_panel *msm_dp_panel)
 struct msm_dp_panel *msm_dp_panel_get(struct device *dev, struct drm_dp_aux *aux,
 			      struct msm_dp_link *link,
 			      void __iomem *link_base,
+			      void __iomem *mst2link_base,
+			      void __iomem *mst3link_base,
 			      void __iomem *pixel_base)
 {
 	struct msm_dp_panel_private *panel;
@@ -745,6 +816,8 @@ struct msm_dp_panel *msm_dp_panel_get(struct device *dev, struct drm_dp_aux *aux
 	panel->link = link;
 	panel->link_base = link_base;
 	panel->pixel_base = pixel_base;
+	panel->mst2link_base = mst2link_base;
+	panel->mst3link_base = mst3link_base;
 
 	msm_dp_panel = &panel->msm_dp_panel;
 	msm_dp_panel->max_bw_code = DP_LINK_BW_8_1;
diff --git a/drivers/gpu/drm/msm/dp/dp_panel.h b/drivers/gpu/drm/msm/dp/dp_panel.h
index fe4ac3e47e17..4873c55bd693 100644
--- a/drivers/gpu/drm/msm/dp/dp_panel.h
+++ b/drivers/gpu/drm/msm/dp/dp_panel.h
@@ -101,5 +101,7 @@ static inline bool is_lane_count_valid(u32 lane_count)
 struct msm_dp_panel *msm_dp_panel_get(struct device *dev, struct drm_dp_aux *aux,
 			      struct msm_dp_link *link,
 			      void __iomem *link_base,
+			      void __iomem *mst2link_base,
+			      void __iomem *mst3link_base,
 			      void __iomem *pixel_base);
 #endif /* _DP_PANEL_H_ */
diff --git a/drivers/gpu/drm/msm/dp/dp_reg.h b/drivers/gpu/drm/msm/dp/dp_reg.h
index 295c1161e6b7..1c2d3d8d029d 100644
--- a/drivers/gpu/drm/msm/dp/dp_reg.h
+++ b/drivers/gpu/drm/msm/dp/dp_reg.h
@@ -162,7 +162,6 @@
 #define REG_DP_START_HOR_VER_FROM_SYNC		(0x00000020)
 #define REG_DP_HSYNC_VSYNC_WIDTH_POLARITY	(0x00000024)
 #define REG_DP_ACTIVE_HOR_VER			(0x00000028)
-
 #define REG_DP_MISC1_MISC0			(0x0000002C)
 #define DP_MISC0_SYNCHRONOUS_CLK		(0x00000001)
 #define DP_MISC0_COLORIMETRY_CFG_SHIFT		(0x00000001)
@@ -343,6 +342,21 @@
 #define MMSS_DP1_SDP_CFG2			(0x000004E4)
 #define MMSS_DP1_SDP_CFG3			(0x000004E8)
 
+#define REG_DP_MSTLINK_CONFIGURATION_CTRL	(0x00000034)
+#define REG_MSTLINK_SOFTWARE_MVID		(0x00000040)
+#define REG_MSTLINK_SOFTWARE_NVID		(0x00000044)
+#define REG_DP_MSTLINK_TOTAL_HOR_VER		(0x00000048)
+#define REG_DP_MSTLINK_START_HOR_VER_FROM_SYNC	(0x0000004C)
+#define REG_DP_MSTLINK_HSYNC_VSYNC_WIDTH_POLARITY	(0x00000050)
+#define REG_DP_MSTLINK_ACTIVE_HOR_VER		(0x00000054)
+#define REG_DP_MSTLINK_MISC1_MISC0		(0x00000058)
+#define MMSS_DP_MSTLINK_GENERIC0_0		(0x000000BC)
+#define MMSS_DP_MSTLINK_GENERIC0_1		(0x000000C0)
+#define MMSS_DP_MSTLINK_GENERIC0_2		(0x000000C4)
+#define MMSS_DP_MSTLINK_SDP_CFG			(0x0000010c)
+#define MMSS_DP_MSTLINK_SDP_CFG2		(0x0000011c)
+#define MMSS_DP_MSTLINK_SDP_CFG3		(0x00000114)
+
 #define MMSS_DP_ASYNC_FIFO_CONFIG		(0x00000088)
 
 #define REG_DP_PHY_AUX_INTERRUPT_CLEAR          (0x0000004C)

-- 
2.43.0


^ permalink raw reply	[flat|nested] 128+ messages in thread

* [PATCH v4 18/39] drm/msm/dp: add support to send ACT packets for MST
  2026-04-10  9:33 [PATCH v4 00/39] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
                   ` (16 preceding siblings ...)
  2026-04-10  9:33 ` [PATCH v4 17/39] drm/msm/dp: Add catalog support for 3rd/4th stream MST Yongxing Mou
@ 2026-04-10  9:33 ` Yongxing Mou
  2026-04-11 18:57   ` Dmitry Baryshkov
  2026-04-10  9:33 ` [PATCH v4 19/39] drm/msm/dp: Add support to enable MST in mainlink control Yongxing Mou
                   ` (22 subsequent siblings)
  40 siblings, 1 reply; 128+ messages in thread
From: Yongxing Mou @ 2026-04-10  9:33 UTC (permalink / raw)
  To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang
  Cc: linux-arm-msm, dri-devel, freedreno, linux-kernel, Yongxing Mou,
	Abhinav Kumar

From: Abhinav Kumar <quic_abhinavk@quicinc.com>

Whenever virtual channel slot allocation changes, the DP
source must send the action control trigger sequence to notify
the sink about the same. This would be applicable during the
start and stop of the pixel stream. Add the infrastructure
to be able to send ACT packets for the DP controller when
operating in MST mode.

Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
---
 drivers/gpu/drm/msm/dp/dp_ctrl.c    | 43 +++++++++++++++++++++++++++++++++++--
 drivers/gpu/drm/msm/dp/dp_ctrl.h    |  3 ++-
 drivers/gpu/drm/msm/dp/dp_display.c |  3 ++-
 drivers/gpu/drm/msm/dp/dp_display.h |  1 +
 drivers/gpu/drm/msm/dp/dp_reg.h     |  2 ++
 5 files changed, 48 insertions(+), 4 deletions(-)

diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c
index 1109b2df21be..6f25145ef214 100644
--- a/drivers/gpu/drm/msm/dp/dp_ctrl.c
+++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c
@@ -143,6 +143,7 @@ struct msm_dp_ctrl_private {
 	bool core_clks_on;
 	bool link_clks_on;
 	bool stream_clks_on[DP_STREAM_MAX];
+	bool mst_active;
 };
 
 static inline u32 msm_dp_read_ahb(const struct msm_dp_ctrl_private *ctrl, u32 offset)
@@ -228,6 +229,32 @@ static int msm_dp_aux_link_configure(struct drm_dp_aux *aux,
 	return err;
 }
 
+int msm_dp_ctrl_mst_send_act(struct msm_dp_ctrl *msm_dp_ctrl)
+{
+	struct msm_dp_ctrl_private *ctrl;
+	bool act_complete;
+
+	ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl);
+
+	if (!ctrl->mst_active)
+		return 0;
+
+	msm_dp_write_link(ctrl, 0, REG_DP_MST_ACT, 0x1);
+	/* make sure ACT signal is performed */
+	wmb();
+
+	msleep(20); /* needs 1 frame time */
+
+	act_complete = msm_dp_read_link(ctrl, 0, REG_DP_MST_ACT);
+
+	if (!act_complete) {
+		drm_dbg_dp(ctrl->drm_dev, "MST ACT trigger complete failed\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
 /*
  * NOTE: resetting DP controller will also clear any pending HPD related interrupts
  */
@@ -2081,6 +2108,10 @@ static int msm_dp_ctrl_link_maintenance(struct msm_dp_ctrl_private *ctrl)
 
 	msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, DP_STATE_CTRL_SEND_VIDEO);
 
+	ret = msm_dp_ctrl_mst_send_act(&ctrl->msm_dp_ctrl);
+	if (ret)
+		return ret;
+
 	ret = msm_dp_ctrl_wait4video_ready(ctrl);
 end:
 	return ret;
@@ -2277,7 +2308,7 @@ static int msm_dp_ctrl_process_phy_test_request(struct msm_dp_ctrl_private *ctrl
 	msm_dp_ctrl_off_pixel_clk(&ctrl->msm_dp_ctrl, ctrl->panel->stream_id);
 	msm_dp_ctrl_off_link(&ctrl->msm_dp_ctrl);
 
-	ret = msm_dp_ctrl_on_link(&ctrl->msm_dp_ctrl);
+	ret = msm_dp_ctrl_on_link(&ctrl->msm_dp_ctrl, false);
 	if (ret) {
 		DRM_ERROR("failed to enable DP link controller\n");
 		return ret;
@@ -2357,7 +2388,7 @@ static bool msm_dp_ctrl_channel_eq_ok(struct msm_dp_ctrl_private *ctrl)
 	return drm_dp_channel_eq_ok(link_status, num_lanes);
 }
 
-int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl)
+int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl, bool mst_active)
 {
 	int rc = 0;
 	struct msm_dp_ctrl_private *ctrl;
@@ -2375,6 +2406,7 @@ int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl)
 
 	rate = ctrl->panel->link_info.rate;
 	pixel_rate = ctrl->panel->msm_dp_mode.drm_mode.clock;
+	ctrl->mst_active = mst_active;
 
 	msm_dp_ctrl_core_clk_enable(&ctrl->msm_dp_ctrl);
 
@@ -2654,6 +2686,10 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *
 
 	msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, DP_STATE_CTRL_SEND_VIDEO);
 
+	ret = msm_dp_ctrl_mst_send_act(msm_dp_ctrl);
+	if (ret)
+		return ret;
+
 	ret = msm_dp_ctrl_wait4video_ready(ctrl);
 	if (ret)
 		return ret;
@@ -2693,6 +2729,8 @@ void msm_dp_ctrl_off_link(struct msm_dp_ctrl *msm_dp_ctrl)
 
 	msm_dp_ctrl_reset(&ctrl->msm_dp_ctrl);
 
+	ctrl->mst_active = false;
+
 	dev_pm_opp_set_rate(ctrl->dev, 0);
 	msm_dp_ctrl_link_clk_disable(&ctrl->msm_dp_ctrl);
 
@@ -2876,6 +2914,7 @@ struct msm_dp_ctrl *msm_dp_ctrl_get(struct device *dev, struct msm_dp_link *link
 	ctrl->link_base = link_base;
 	ctrl->mst2link_base = mst2link_base;
 	ctrl->mst3link_base = mst3link_base;
+	ctrl->mst_active = false;
 
 	ret = msm_dp_ctrl_clk_init(&ctrl->msm_dp_ctrl, max_stream);
 	if (ret) {
diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.h b/drivers/gpu/drm/msm/dp/dp_ctrl.h
index e72d501ac1ce..f82fd96e412a 100644
--- a/drivers/gpu/drm/msm/dp/dp_ctrl.h
+++ b/drivers/gpu/drm/msm/dp/dp_ctrl.h
@@ -16,7 +16,7 @@ struct msm_dp_ctrl {
 
 struct phy;
 
-int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl);
+int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl, bool mst_active);
 int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *msm_dp_panel);
 int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train);
 void msm_dp_ctrl_off_link(struct msm_dp_ctrl *msm_dp_ctrl);
@@ -51,4 +51,5 @@ void msm_dp_ctrl_disable_irq(struct msm_dp_ctrl *msm_dp_ctrl);
 
 void msm_dp_ctrl_reinit_phy(struct msm_dp_ctrl *msm_dp_ctrl);
 int msm_dp_ctrl_get_stream_cnt(struct msm_dp_ctrl *dp_ctrl);
+int msm_dp_ctrl_mst_send_act(struct msm_dp_ctrl *msm_dp_ctrl);
 #endif /* _DP_CTRL_H_ */
diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
index a924fbd825f7..80bb5fc4003f 100644
--- a/drivers/gpu/drm/msm/dp/dp_display.c
+++ b/drivers/gpu/drm/msm/dp/dp_display.c
@@ -660,7 +660,7 @@ static int msm_dp_display_prepare(struct msm_dp_display_private *dp)
 		force_link_train = true;
 	}
 
-	rc = msm_dp_ctrl_on_link(dp->ctrl);
+	rc = msm_dp_ctrl_on_link(dp->ctrl, msm_dp_display->mst_active);
 	if (rc)
 		DRM_ERROR("Failed link training (rc=%d)\n", rc);
 	// TODO: schedule drm_connector_set_link_status_property()
@@ -1547,6 +1547,7 @@ void msm_dp_display_atomic_disable(struct msm_dp *dp)
 	msm_dp_display = container_of(dp, struct msm_dp_display_private, msm_dp_display);
 
 	msm_dp_ctrl_push_idle(msm_dp_display->ctrl);
+	msm_dp_ctrl_mst_send_act(msm_dp_display->ctrl);
 }
 
 static void msm_dp_display_unprepare(struct msm_dp_display_private *dp)
diff --git a/drivers/gpu/drm/msm/dp/dp_display.h b/drivers/gpu/drm/msm/dp/dp_display.h
index b0cfdf215970..fdbe6e4871d9 100644
--- a/drivers/gpu/drm/msm/dp/dp_display.h
+++ b/drivers/gpu/drm/msm/dp/dp_display.h
@@ -19,6 +19,7 @@ struct msm_dp {
 	struct drm_bridge *bridge;
 	bool audio_enabled;
 	bool power_on;
+	bool mst_active;
 	unsigned int connector_type;
 	bool is_edp;
 
diff --git a/drivers/gpu/drm/msm/dp/dp_reg.h b/drivers/gpu/drm/msm/dp/dp_reg.h
index 1c2d3d8d029d..237325d52dbd 100644
--- a/drivers/gpu/drm/msm/dp/dp_reg.h
+++ b/drivers/gpu/drm/msm/dp/dp_reg.h
@@ -156,6 +156,8 @@
 #define DP_CONFIGURATION_CTRL_BPC_SHIFT		(0x08)
 #define DP_CONFIGURATION_CTRL_LSCLK_DIV_SHIFT	(0x0D)
 
+#define REG_DP_MST_ACT				(0x00000500)
+
 #define REG_DP_SOFTWARE_MVID			(0x00000010)
 #define REG_DP_SOFTWARE_NVID			(0x00000018)
 #define REG_DP_TOTAL_HOR_VER			(0x0000001C)

-- 
2.43.0


^ permalink raw reply	[flat|nested] 128+ messages in thread

* [PATCH v4 19/39] drm/msm/dp: Add support to enable MST in mainlink control
  2026-04-10  9:33 [PATCH v4 00/39] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
                   ` (17 preceding siblings ...)
  2026-04-10  9:33 ` [PATCH v4 18/39] drm/msm/dp: add support to send ACT packets for MST Yongxing Mou
@ 2026-04-10  9:33 ` Yongxing Mou
  2026-04-11 18:59   ` Dmitry Baryshkov
  2026-04-10  9:33 ` [PATCH v4 20/39] drm/msm/dp: no need to update tu calculation for mst Yongxing Mou
                   ` (21 subsequent siblings)
  40 siblings, 1 reply; 128+ messages in thread
From: Yongxing Mou @ 2026-04-10  9:33 UTC (permalink / raw)
  To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang
  Cc: linux-arm-msm, dri-devel, freedreno, linux-kernel, Yongxing Mou,
	Abhinav Kumar

From: Abhinav Kumar <quic_abhinavk@quicinc.com>

Add support to program the MST enable bit in the mainlink control
register when an MST session is active or being disabled.

Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
---
 drivers/gpu/drm/msm/dp/dp_ctrl.c | 17 +++++++++++++++++
 drivers/gpu/drm/msm/dp/dp_reg.h  |  4 ++++
 2 files changed, 21 insertions(+)

diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c
index 6f25145ef214..9513de81abc4 100644
--- a/drivers/gpu/drm/msm/dp/dp_ctrl.c
+++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c
@@ -255,6 +255,19 @@ int msm_dp_ctrl_mst_send_act(struct msm_dp_ctrl *msm_dp_ctrl)
 	return 0;
 }
 
+static void msm_dp_ctrl_mst_config(struct msm_dp_ctrl_private *ctrl, bool enable)
+{
+	u32 mainlink_ctrl;
+
+	mainlink_ctrl = msm_dp_read_link(ctrl, 0, REG_DP_MAINLINK_CTRL);
+	if (enable)
+		mainlink_ctrl |= DP_MAINLINK_CTRL_MST_EN;
+	else
+		mainlink_ctrl &= ~DP_MAINLINK_CTRL_MST_EN;
+
+	msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
+}
+
 /*
  * NOTE: resetting DP controller will also clear any pending HPD related interrupts
  */
@@ -2669,6 +2682,9 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *
 
 	msm_dp_ctrl_lane_mapping(ctrl);
 	msm_dp_setup_peripheral_flush(ctrl);
+	if (ctrl->mst_active)
+		msm_dp_ctrl_mst_config(ctrl, true);
+
 	if (msm_dp_panel->stream_id == DP_STREAM_0)
 		msm_dp_ctrl_config_ctrl_link(ctrl);
 
@@ -2726,6 +2742,7 @@ void msm_dp_ctrl_off_link(struct msm_dp_ctrl *msm_dp_ctrl)
 	phy = ctrl->phy;
 
 	msm_dp_ctrl_mainlink_disable(ctrl);
+	msm_dp_ctrl_mst_config(ctrl, false);
 
 	msm_dp_ctrl_reset(&ctrl->msm_dp_ctrl);
 
diff --git a/drivers/gpu/drm/msm/dp/dp_reg.h b/drivers/gpu/drm/msm/dp/dp_reg.h
index 237325d52dbd..87eaaefa014d 100644
--- a/drivers/gpu/drm/msm/dp/dp_reg.h
+++ b/drivers/gpu/drm/msm/dp/dp_reg.h
@@ -128,6 +128,10 @@
 #define DP_MAINLINK_FLUSH_MODE_UPDATE_SDP	FIELD_PREP(DP_MAINLINK_CTRL_FLUSH_MODE_MASK, 1)
 #define DP_MAINLINK_FLUSH_MODE_SDE_PERIPH_UPDATE	FIELD_PREP(DP_MAINLINK_CTRL_FLUSH_MODE_MASK, 3)
 #define DP_MAINLINK_FB_BOUNDARY_SEL		(0x02000000)
+#define DP_MAINLINK_CTRL_ECF_MODE		BIT(26)
+#define DP_MAINLINK_CTRL_MST_ACTIVE		BIT(8)
+#define DP_MAINLINK_CTRL_MST_EN			(DP_MAINLINK_CTRL_ECF_MODE | \
+						DP_MAINLINK_CTRL_MST_ACTIVE)
 
 #define REG_DP_STATE_CTRL			(0x00000004)
 #define DP_STATE_CTRL_LINK_TRAINING_PATTERN1	(0x00000001)

-- 
2.43.0


^ permalink raw reply	[flat|nested] 128+ messages in thread

* [PATCH v4 20/39] drm/msm/dp: no need to update tu calculation for mst
  2026-04-10  9:33 [PATCH v4 00/39] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
                   ` (18 preceding siblings ...)
  2026-04-10  9:33 ` [PATCH v4 19/39] drm/msm/dp: Add support to enable MST in mainlink control Yongxing Mou
@ 2026-04-10  9:33 ` Yongxing Mou
  2026-04-11 19:00   ` Dmitry Baryshkov
  2026-04-10  9:33 ` [PATCH v4 21/39] drm/msm/dp: Add support for MST channel slot allocation Yongxing Mou
                   ` (20 subsequent siblings)
  40 siblings, 1 reply; 128+ messages in thread
From: Yongxing Mou @ 2026-04-10  9:33 UTC (permalink / raw)
  To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang
  Cc: linux-arm-msm, dri-devel, freedreno, linux-kernel, Yongxing Mou,
	Abhinav Kumar

From: Abhinav Kumar <quic_abhinavk@quicinc.com>

DP stream is transmitted in transfer units only for SST
case, there is no need to calculate and program TU parameters
for MST case. Skip the TU programming for MST cases.

Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
---
 drivers/gpu/drm/msm/dp/dp_ctrl.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c
index 9513de81abc4..98316892eccd 100644
--- a/drivers/gpu/drm/msm/dp/dp_ctrl.c
+++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c
@@ -2698,7 +2698,8 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *
 
 	msm_dp_panel_clear_dsc_dto(msm_dp_panel);
 
-	msm_dp_ctrl_setup_tr_unit(ctrl);
+	if (!ctrl->mst_active)
+		msm_dp_ctrl_setup_tr_unit(ctrl);
 
 	msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, DP_STATE_CTRL_SEND_VIDEO);
 

-- 
2.43.0


^ permalink raw reply	[flat|nested] 128+ messages in thread

* [PATCH v4 21/39] drm/msm/dp: Add support for MST channel slot allocation
  2026-04-10  9:33 [PATCH v4 00/39] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
                   ` (19 preceding siblings ...)
  2026-04-10  9:33 ` [PATCH v4 20/39] drm/msm/dp: no need to update tu calculation for mst Yongxing Mou
@ 2026-04-10  9:33 ` Yongxing Mou
  2026-04-11 23:58   ` Dmitry Baryshkov
  2026-04-10  9:33 ` [PATCH v4 22/39] drm/msm/dp: Add support for sending VCPF packets in DP controller Yongxing Mou
                   ` (19 subsequent siblings)
  40 siblings, 1 reply; 128+ messages in thread
From: Yongxing Mou @ 2026-04-10  9:33 UTC (permalink / raw)
  To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang
  Cc: linux-arm-msm, dri-devel, freedreno, linux-kernel, Yongxing Mou,
	Abhinav Kumar

From: Abhinav Kumar <quic_abhinavk@quicinc.com>

DP MST streams share 64 MTP slots in a time-multiplexed manner. This patch
adds support for calculating the rate governor, slot allocation, and slot
reservation in the DP controller.

Each MST stream can reserve its slots by calling
dp_display_set_stream_info() from its bridge callbacks.

Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
---
 drivers/gpu/drm/msm/dp/dp_ctrl.c    | 207 ++++++++++++++++++++++++++++++++++++
 drivers/gpu/drm/msm/dp/dp_ctrl.h    |   7 +-
 drivers/gpu/drm/msm/dp/dp_display.c |  28 +++--
 drivers/gpu/drm/msm/dp/dp_display.h |   5 +-
 drivers/gpu/drm/msm/dp/dp_panel.h   |   1 +
 drivers/gpu/drm/msm/dp/dp_reg.h     |  10 ++
 6 files changed, 247 insertions(+), 11 deletions(-)

diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c
index 98316892eccd..e64f81bc8c36 100644
--- a/drivers/gpu/drm/msm/dp/dp_ctrl.c
+++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c
@@ -73,6 +73,7 @@
 #define MR_LINK_PRBS7 0x100
 #define MR_LINK_CUSTOM80 0x200
 #define MR_LINK_TRAINING4  0x40
+#define DP_MAX_TIME_SLOTS 64
 
 enum {
 	DP_TRAINING_NONE,
@@ -109,6 +110,11 @@ struct msm_dp_vc_tu_mapping_table {
 	u8 tu_size_minus1;
 };
 
+struct msm_dp_mst_ch_slot_info {
+	u32 start_slot;
+	u32 tot_slots;
+};
+
 struct msm_dp_ctrl_private {
 	struct msm_dp_ctrl msm_dp_ctrl;
 	struct drm_device *drm_dev;
@@ -144,6 +150,8 @@ struct msm_dp_ctrl_private {
 	bool link_clks_on;
 	bool stream_clks_on[DP_STREAM_MAX];
 	bool mst_active;
+
+	struct msm_dp_mst_ch_slot_info mst_ch_info[DP_STREAM_MAX];
 };
 
 static inline u32 msm_dp_read_ahb(const struct msm_dp_ctrl_private *ctrl, u32 offset)
@@ -268,6 +276,73 @@ static void msm_dp_ctrl_mst_config(struct msm_dp_ctrl_private *ctrl, bool enable
 	msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
 }
 
+static void msm_dp_ctrl_mst_channel_alloc(struct msm_dp_ctrl_private *ctrl,
+					  enum msm_dp_stream_id stream_id, u32 ch_start_slot,
+					  u32 tot_slot_cnt)
+{
+	u32 i, slot;
+	u32  slot_reg_1, slot_reg_2;
+	u32 reg_off = 0;
+	int const num_slots_per_reg = 32;
+
+	if (ch_start_slot > DP_MAX_TIME_SLOTS ||
+	    (ch_start_slot + tot_slot_cnt > DP_MAX_TIME_SLOTS)) {
+		DRM_ERROR("invalid slots start %d, tot %d\n",
+			  ch_start_slot, tot_slot_cnt);
+		return;
+	}
+
+	drm_dbg_dp(ctrl->drm_dev, "stream_id %d, start_slot %d, tot_slot %d\n",
+		   stream_id, ch_start_slot, tot_slot_cnt);
+
+	if (stream_id == DP_STREAM_1)
+		reg_off = REG_DP_DP1_TIMESLOT_1_32 - REG_DP_DP0_TIMESLOT_1_32;
+
+	slot_reg_1 = 0;
+	slot_reg_2 = 0;
+
+	if (ch_start_slot && tot_slot_cnt) {
+		ch_start_slot--;
+		for (i = 0; i < tot_slot_cnt; i++) {
+			if (ch_start_slot < num_slots_per_reg) {
+				slot_reg_1 |= BIT(ch_start_slot);
+			} else {
+				slot = ch_start_slot - num_slots_per_reg;
+				slot_reg_2 |= BIT(slot);
+			}
+			ch_start_slot++;
+		}
+	}
+
+	drm_dbg_dp(ctrl->drm_dev, "stream_id:%d slot_reg_1:%d, slot_reg_2:%d\n", stream_id,
+		   slot_reg_1, slot_reg_2);
+
+	msm_dp_write_link(ctrl, stream_id, stream_id > DP_STREAM_1 ?
+			  REG_DP_MSTLINK_TIMESLOT_1_32 : REG_DP_DP0_TIMESLOT_1_32 + reg_off,
+			  slot_reg_1);
+	msm_dp_write_link(ctrl, stream_id, stream_id > DP_STREAM_1 ?
+			  REG_DP_MSTLINK_TIMESLOT_33_63 : REG_DP_DP0_TIMESLOT_33_63 + reg_off,
+			  slot_reg_2);
+}
+
+static void msm_dp_ctrl_update_rg(struct msm_dp_ctrl_private *ctrl,
+				  enum msm_dp_stream_id stream_id, u32 x_int, u32 y_frac_enum)
+{
+	u32 rg, reg_off = 0;
+
+	rg = y_frac_enum;
+	rg |= (x_int << 16);
+
+	drm_dbg_dp(ctrl->drm_dev, "stream_id: %d x_int:%d y_frac_enum:%d rg:%d\n",
+		   stream_id, x_int, y_frac_enum, rg);
+
+	if (stream_id == DP_STREAM_1)
+		reg_off = REG_DP_DP1_RG - REG_DP_DP0_RG;
+
+	msm_dp_write_link(ctrl, stream_id, stream_id > 1 ?
+			     REG_DP_MSTLINK_DP_RG : REG_DP_DP0_RG + reg_off, rg);
+}
+
 /*
  * NOTE: resetting DP controller will also clear any pending HPD related interrupts
  */
@@ -2615,6 +2690,103 @@ static void msm_dp_ctrl_config_msa(struct msm_dp_ctrl_private *ctrl,
 			  nvid);
 }
 
+/* TODO: comments here. */
+static void msm_dp_ctrl_mst_calculate_rg(struct msm_dp_ctrl_private *ctrl,
+					 struct msm_dp_panel *panel,
+					 u32 *p_x_int, u32 *p_y_frac_enum)
+{
+	u64 min_slot_cnt, max_slot_cnt;
+	u64 raw_target_sc, target_sc_fixp;
+	u64 ts_denom, ts_enum, ts_int;
+	u64 pclk = panel->msm_dp_mode.drm_mode.clock;
+	u64 lclk = 0;
+	u64 lanes = ctrl->link->link_params.num_lanes;
+	u64 bpp = panel->msm_dp_mode.bpp;
+	u64 pbn = panel->pbn;
+	u64 numerator, denominator, temp, temp1, temp2;
+	u32 x_int = 0, y_frac_enum = 0;
+	u64 target_strm_sym, ts_int_fixp, ts_frac_fixp, y_frac_enum_fixp;
+
+	lclk = ctrl->link->link_params.rate;
+
+	/* min_slot_cnt */
+	numerator = pclk * bpp * 64 * 1000;
+	denominator = lclk * lanes * 8 * 1000;
+	min_slot_cnt = drm_fixp_from_fraction(numerator, denominator);
+
+	/* max_slot_cnt */
+	numerator = pbn * 54 * 1000;
+	denominator = lclk * lanes;
+	max_slot_cnt = drm_fixp_from_fraction(numerator, denominator);
+
+	/* raw_target_sc */
+	numerator = max_slot_cnt + min_slot_cnt;
+	denominator = drm_fixp_from_fraction(2, 1);
+	raw_target_sc = drm_fixp_div(numerator, denominator);
+
+	/* target_sc */
+	temp = drm_fixp_from_fraction(256 * lanes, 1);
+	numerator = drm_fixp_mul(raw_target_sc, temp);
+	denominator = drm_fixp_from_fraction(256 * lanes, 1);
+	target_sc_fixp = drm_fixp_div(numerator, denominator);
+
+	ts_enum = 256 * lanes;
+	ts_denom = drm_fixp_from_fraction(256 * lanes, 1);
+	ts_int = drm_fixp2int(target_sc_fixp);
+
+	temp = drm_fixp2int_ceil(raw_target_sc);
+	if (temp != ts_int) {
+		temp = drm_fixp_from_fraction(ts_int, 1);
+		temp1 = raw_target_sc - temp;
+		temp2 = drm_fixp_mul(temp1, ts_denom);
+		ts_enum = drm_fixp2int(temp2);
+	}
+
+	/* target_strm_sym */
+	ts_int_fixp = drm_fixp_from_fraction(ts_int, 1);
+	ts_frac_fixp = drm_fixp_from_fraction(ts_enum, drm_fixp2int(ts_denom));
+	temp = ts_int_fixp + ts_frac_fixp;
+	temp1 = drm_fixp_from_fraction(lanes, 1);
+	target_strm_sym = drm_fixp_mul(temp, temp1);
+
+	/* x_int */
+	x_int = drm_fixp2int(target_strm_sym);
+
+	/* y_enum_frac */
+	temp = drm_fixp_from_fraction(x_int, 1);
+	temp1 = target_strm_sym - temp;
+	temp2 = drm_fixp_from_fraction(256, 1);
+	y_frac_enum_fixp = drm_fixp_mul(temp1, temp2);
+
+	temp1 = drm_fixp2int(y_frac_enum_fixp);
+	temp2 = drm_fixp2int_ceil(y_frac_enum_fixp);
+
+	y_frac_enum = (u32)((temp1 == temp2) ? temp1 : temp1 + 1);
+
+	*p_x_int = x_int;
+	*p_y_frac_enum = y_frac_enum;
+
+	drm_dbg_dp(ctrl->drm_dev, "MST lane_cnt:%llu, rate:%llu x_int:%d, y_frac:%d\n",
+		   lanes, lclk, x_int, y_frac_enum);
+}
+
+static void msm_dp_ctrl_mst_stream_setup(struct msm_dp_ctrl_private *ctrl,
+					 struct msm_dp_panel *panel)
+{
+	u32 x_int, y_frac_enum;
+
+	if (!ctrl->mst_active)
+		return;
+
+	drm_dbg_dp(ctrl->drm_dev, "MST stream channel allocation\n");
+
+	msm_dp_ctrl_mst_stream_channel_slot_setup(&ctrl->msm_dp_ctrl);
+
+	msm_dp_ctrl_mst_calculate_rg(ctrl, panel, &x_int, &y_frac_enum);
+
+	msm_dp_ctrl_update_rg(ctrl, panel->stream_id, x_int, y_frac_enum);
+}
+
 int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train)
 {
 	int ret = 0;
@@ -2701,6 +2873,8 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *
 	if (!ctrl->mst_active)
 		msm_dp_ctrl_setup_tr_unit(ctrl);
 
+	msm_dp_ctrl_mst_stream_setup(ctrl, msm_dp_panel);
+
 	msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, DP_STATE_CTRL_SEND_VIDEO);
 
 	ret = msm_dp_ctrl_mst_send_act(msm_dp_ctrl);
@@ -2757,6 +2931,39 @@ void msm_dp_ctrl_off_link(struct msm_dp_ctrl *msm_dp_ctrl)
 			phy, phy->init_count, phy->power_count);
 }
 
+void msm_dp_ctrl_set_mst_channel_info(struct msm_dp_ctrl *msm_dp_ctrl,
+				      enum msm_dp_stream_id stream_id,
+				      u32 start_slot, u32 tot_slots)
+{
+	struct msm_dp_ctrl_private *ctrl;
+
+	if (!msm_dp_ctrl || stream_id >= DP_STREAM_MAX) {
+		DRM_ERROR("invalid input\n");
+		return;
+	}
+
+	ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl);
+
+	ctrl->mst_ch_info[stream_id].start_slot = start_slot;
+	ctrl->mst_ch_info[stream_id].tot_slots = tot_slots;
+}
+
+void msm_dp_ctrl_mst_stream_channel_slot_setup(struct msm_dp_ctrl *msm_dp_ctrl)
+{
+	struct msm_dp_ctrl_private *ctrl;
+	int i;
+
+	ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl);
+
+	if (!ctrl->mst_active)
+		return;
+
+	for (i = DP_STREAM_0; i < ctrl->num_pixel_clks; i++) {
+		msm_dp_ctrl_mst_channel_alloc(ctrl, i, ctrl->mst_ch_info[i].start_slot,
+					      ctrl->mst_ch_info[i].tot_slots);
+	}
+}
+
 irqreturn_t msm_dp_ctrl_isr(struct msm_dp_ctrl *msm_dp_ctrl)
 {
 	struct msm_dp_ctrl_private *ctrl;
diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.h b/drivers/gpu/drm/msm/dp/dp_ctrl.h
index f82fd96e412a..c59338199399 100644
--- a/drivers/gpu/drm/msm/dp/dp_ctrl.h
+++ b/drivers/gpu/drm/msm/dp/dp_ctrl.h
@@ -17,7 +17,8 @@ struct msm_dp_ctrl {
 struct phy;
 
 int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl, bool mst_active);
-int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *msm_dp_panel);
+int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl,
+			  struct msm_dp_panel *msm_dp_panel);
 int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train);
 void msm_dp_ctrl_off_link(struct msm_dp_ctrl *msm_dp_ctrl);
 void msm_dp_ctrl_off_pixel_clk(struct msm_dp_ctrl *msm_dp_ctrl, enum msm_dp_stream_id stream_id);
@@ -52,4 +53,8 @@ void msm_dp_ctrl_disable_irq(struct msm_dp_ctrl *msm_dp_ctrl);
 void msm_dp_ctrl_reinit_phy(struct msm_dp_ctrl *msm_dp_ctrl);
 int msm_dp_ctrl_get_stream_cnt(struct msm_dp_ctrl *dp_ctrl);
 int msm_dp_ctrl_mst_send_act(struct msm_dp_ctrl *msm_dp_ctrl);
+void msm_dp_ctrl_mst_stream_channel_slot_setup(struct msm_dp_ctrl *msm_dp_ctrl);
+void msm_dp_ctrl_set_mst_channel_info(struct msm_dp_ctrl *msm_dp_ctrl,
+				      enum msm_dp_stream_id stream_id,
+				      u32 start_slot, u32 tot_slots);
 #endif /* _DP_CTRL_H_ */
diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
index 80bb5fc4003f..e0bf4dffa6af 100644
--- a/drivers/gpu/drm/msm/dp/dp_display.c
+++ b/drivers/gpu/drm/msm/dp/dp_display.c
@@ -763,11 +763,13 @@ static int msm_dp_display_disable(struct msm_dp_display_private *dp)
 	return 0;
 }
 
-int msm_dp_display_set_stream_info(struct msm_dp *msm_dp_display,
-				   struct msm_dp_panel *panel, enum msm_dp_stream_id stream_id)
+int msm_dp_display_set_stream_info(struct msm_dp *msm_dp_display, struct msm_dp_panel *panel,
+				   enum msm_dp_stream_id stream_id, u32 start_slot,
+				   u32 num_slots, u32 pbn)
 {
 	int rc = 0;
 	struct msm_dp_display_private *dp;
+	const int max_slots = 64;
 
 	dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display);
 
@@ -776,7 +778,16 @@ int msm_dp_display_set_stream_info(struct msm_dp *msm_dp_display,
 		return -EINVAL;
 	}
 
+	if (start_slot + num_slots > max_slots) {
+		DRM_ERROR("invalid channel info received. start:%d, slots:%d\n",
+			  start_slot, num_slots);
+		return -EINVAL;
+	}
+
+	msm_dp_ctrl_set_mst_channel_info(dp->ctrl, stream_id, start_slot, num_slots);
+
 	panel->stream_id = stream_id;
+	panel->pbn = pbn;
 	msm_dp_panel_set_pixel_base(panel, dp->pixel_base[stream_id]);
 
 	return rc;
@@ -1525,7 +1536,7 @@ void msm_dp_display_atomic_enable(struct msm_dp *msm_dp_display)
 
 	dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display);
 
-	msm_dp_display_set_stream_info(msm_dp_display, dp->panel, 0);
+	msm_dp_display_set_stream_info(msm_dp_display, dp->panel, 0, 0, 0, 0);
 
 	rc = msm_dp_display_enable(dp);
 	if (rc)
@@ -1540,14 +1551,15 @@ void msm_dp_display_atomic_enable(struct msm_dp *msm_dp_display)
 	drm_dbg_dp(msm_dp_display->drm_dev, "type=%d Done\n", msm_dp_display->connector_type);
 }
 
-void msm_dp_display_atomic_disable(struct msm_dp *dp)
+void msm_dp_display_atomic_disable(struct msm_dp *msm_dp_display)
 {
-	struct msm_dp_display_private *msm_dp_display;
+	struct msm_dp_display_private *dp;
 
-	msm_dp_display = container_of(dp, struct msm_dp_display_private, msm_dp_display);
+	dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display);
 
-	msm_dp_ctrl_push_idle(msm_dp_display->ctrl);
-	msm_dp_ctrl_mst_send_act(msm_dp_display->ctrl);
+	msm_dp_ctrl_push_idle(dp->ctrl);
+	msm_dp_ctrl_mst_stream_channel_slot_setup(dp->ctrl);
+	msm_dp_ctrl_mst_send_act(dp->ctrl);
 }
 
 static void msm_dp_display_unprepare(struct msm_dp_display_private *dp)
diff --git a/drivers/gpu/drm/msm/dp/dp_display.h b/drivers/gpu/drm/msm/dp/dp_display.h
index fdbe6e4871d9..0ccdddb223c8 100644
--- a/drivers/gpu/drm/msm/dp/dp_display.h
+++ b/drivers/gpu/drm/msm/dp/dp_display.h
@@ -43,7 +43,8 @@ void msm_dp_display_atomic_enable(struct msm_dp *dp_display);
 enum drm_mode_status msm_dp_display_mode_valid(struct msm_dp *dp,
 					       const struct drm_display_info *info,
 					       const struct drm_display_mode *mode);
-int msm_dp_display_set_stream_info(struct msm_dp *msm_dp_display,
-				   struct msm_dp_panel *panel, enum msm_dp_stream_id stream_id);
+int msm_dp_display_set_stream_info(struct msm_dp *msm_dp_display, struct msm_dp_panel *panel,
+				   enum msm_dp_stream_id stream_id,
+				   u32 start_slot, u32 num_slots, u32 pbn);
 
 #endif /* _DP_DISPLAY_H_ */
diff --git a/drivers/gpu/drm/msm/dp/dp_panel.h b/drivers/gpu/drm/msm/dp/dp_panel.h
index 4873c55bd693..8e7374de90eb 100644
--- a/drivers/gpu/drm/msm/dp/dp_panel.h
+++ b/drivers/gpu/drm/msm/dp/dp_panel.h
@@ -50,6 +50,7 @@ struct msm_dp_panel {
 	u32 hw_revision;
 
 	enum msm_dp_stream_id stream_id;
+	u32 pbn;
 
 	u32 max_bw_code;
 };
diff --git a/drivers/gpu/drm/msm/dp/dp_reg.h b/drivers/gpu/drm/msm/dp/dp_reg.h
index 87eaaefa014d..835a55446868 100644
--- a/drivers/gpu/drm/msm/dp/dp_reg.h
+++ b/drivers/gpu/drm/msm/dp/dp_reg.h
@@ -338,7 +338,13 @@
 #define DP_TPG_VIDEO_CONFIG_RGB			(0x00000004)
 
 /* DP MST registers */
+
+#define REG_DP_MSTLINK_DP_RG			(0X0000011C)
 #define REG_DP1_CONFIGURATION_CTRL		(0x00000400)
+#define REG_DP_DP0_TIMESLOT_1_32		(0x00000404)
+#define REG_DP_DP0_TIMESLOT_33_63		(0x00000408)
+#define REG_DP_DP1_TIMESLOT_1_32		(0x0000040C)
+#define REG_DP_DP1_TIMESLOT_33_63		(0x00000410)
 #define REG_DP1_SOFTWARE_MVID			(0x00000414)
 #define REG_DP1_SOFTWARE_NVID			(0x00000418)
 #define REG_DP1_TOTAL_HOR_VER			(0x0000041C)
@@ -347,8 +353,12 @@
 #define MMSS_DP1_SDP_CFG			(0x000004E0)
 #define MMSS_DP1_SDP_CFG2			(0x000004E4)
 #define MMSS_DP1_SDP_CFG3			(0x000004E8)
+#define REG_DP_DP0_RG				(0x000004F8)
+#define REG_DP_DP1_RG				(0x000004FC)
 
 #define REG_DP_MSTLINK_CONFIGURATION_CTRL	(0x00000034)
+#define REG_DP_MSTLINK_TIMESLOT_1_32		(0x00000038)
+#define REG_DP_MSTLINK_TIMESLOT_33_63		(0x0000003C)
 #define REG_MSTLINK_SOFTWARE_MVID		(0x00000040)
 #define REG_MSTLINK_SOFTWARE_NVID		(0x00000044)
 #define REG_DP_MSTLINK_TOTAL_HOR_VER		(0x00000048)

-- 
2.43.0


^ permalink raw reply	[flat|nested] 128+ messages in thread

* [PATCH v4 22/39] drm/msm/dp: Add support for sending VCPF packets in DP controller
  2026-04-10  9:33 [PATCH v4 00/39] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
                   ` (20 preceding siblings ...)
  2026-04-10  9:33 ` [PATCH v4 21/39] drm/msm/dp: Add support for MST channel slot allocation Yongxing Mou
@ 2026-04-10  9:33 ` Yongxing Mou
  2026-04-11 19:24   ` Dmitry Baryshkov
  2026-04-10  9:33 ` [PATCH v4 23/39] drm/msm/dp: Always program MST_FIFO_CONSTANT_FILL for MST use cases Yongxing Mou
                   ` (18 subsequent siblings)
  40 siblings, 1 reply; 128+ messages in thread
From: Yongxing Mou @ 2026-04-10  9:33 UTC (permalink / raw)
  To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang
  Cc: linux-arm-msm, dri-devel, freedreno, linux-kernel, Yongxing Mou,
	Abhinav Kumar, Dmitry Baryshkov

From: Abhinav Kumar <quic_abhinavk@quicinc.com>

The VC Payload Fill (VCPF) sequence is inserted by the DP controller
when stream symbols are absent, typically before a stream is disabled.
This patch adds support for triggering the VCPF sequence in the MSM DP
controller.

Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
---
 drivers/gpu/drm/msm/dp/dp_ctrl.c    | 55 ++++++++++++++++++++++++++++++++++---
 drivers/gpu/drm/msm/dp/dp_ctrl.h    |  2 +-
 drivers/gpu/drm/msm/dp/dp_display.c |  2 +-
 drivers/gpu/drm/msm/dp/dp_reg.h     |  5 ++++
 4 files changed, 58 insertions(+), 6 deletions(-)

diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c
index e64f81bc8c36..9907f2e56e65 100644
--- a/drivers/gpu/drm/msm/dp/dp_ctrl.c
+++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c
@@ -65,9 +65,18 @@
 	(PSR_UPDATE_MASK | PSR_CAPTURE_MASK | PSR_EXIT_MASK | \
 	PSR_UPDATE_ERROR_MASK | PSR_WAKE_ERROR_MASK)
 
+#define DP_INTERRUPT_STATUS5 \
+	(DP_INTR_DP0_VCPF_SENT | DP_INTR_DP1_VCPF_SENT)
+#define DP_INTERRUPT_STATUS5_MASK \
+	(DP_INTERRUPT_STATUS5 << DP_INTERRUPT_STATUS_MASK_SHIFT)
+
 #define DP_CTRL_INTR_READY_FOR_VIDEO     BIT(0)
 #define DP_CTRL_INTR_IDLE_PATTERN_SENT  BIT(3)
 
+#define DP_DP0_PUSH_VCPF		BIT(12)
+#define DP_DP1_PUSH_VCPF		BIT(14)
+#define DP_MSTLINK_PUSH_VCPF		BIT(12)
+
 #define MR_LINK_TRAINING1  0x8
 #define MR_LINK_SYMBOL_ERM 0x80
 #define MR_LINK_PRBS7 0x100
@@ -405,6 +414,8 @@ void msm_dp_ctrl_enable_irq(struct msm_dp_ctrl *msm_dp_ctrl)
 			DP_INTERRUPT_STATUS1_MASK);
 	msm_dp_write_ahb(ctrl, REG_DP_INTR_STATUS2,
 			DP_INTERRUPT_STATUS2_MASK);
+	msm_dp_write_ahb(ctrl, REG_DP_INTR_STATUS5,
+			 DP_INTERRUPT_STATUS5_MASK);
 }
 
 void msm_dp_ctrl_disable_irq(struct msm_dp_ctrl *msm_dp_ctrl)
@@ -414,6 +425,7 @@ void msm_dp_ctrl_disable_irq(struct msm_dp_ctrl *msm_dp_ctrl)
 
 	msm_dp_write_ahb(ctrl, REG_DP_INTR_STATUS, 0x00);
 	msm_dp_write_ahb(ctrl, REG_DP_INTR_STATUS2, 0x00);
+	msm_dp_write_ahb(ctrl, REG_DP_INTR_STATUS5, 0x00);
 }
 
 static u32 msm_dp_ctrl_get_psr_interrupt(struct msm_dp_ctrl_private *ctrl)
@@ -433,6 +445,20 @@ static void msm_dp_ctrl_config_psr_interrupt(struct msm_dp_ctrl_private *ctrl)
 	msm_dp_write_ahb(ctrl, REG_DP_INTR_MASK4, DP_INTERRUPT_MASK4);
 }
 
+static u32 msm_dp_ctrl_get_mst_interrupt(struct msm_dp_ctrl_private *ctrl)
+{
+	u32 intr, intr_ack;
+
+	intr = msm_dp_read_ahb(ctrl, REG_DP_INTR_STATUS5);
+	intr &= ~DP_INTERRUPT_STATUS5_MASK;
+	intr_ack = (intr & DP_INTERRUPT_STATUS5)
+			<< DP_INTERRUPT_STATUS_ACK_SHIFT;
+	msm_dp_write_ahb(ctrl, REG_DP_INTR_STATUS5,
+			 intr_ack | DP_INTERRUPT_STATUS5_MASK);
+
+	return intr;
+}
+
 static void msm_dp_ctrl_psr_mainlink_enable(struct msm_dp_ctrl_private *ctrl)
 {
 	u32 val;
@@ -516,14 +542,28 @@ static bool msm_dp_ctrl_mainlink_ready(struct msm_dp_ctrl_private *ctrl)
 	return true;
 }
 
-void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl)
+void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *msm_dp_panel)
 {
 	struct msm_dp_ctrl_private *ctrl;
+	u32 state = 0x0;
 
 	ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl);
 
+	if (!ctrl->mst_active)
+		state |= DP_STATE_CTRL_PUSH_IDLE;
+	else if (msm_dp_panel->stream_id == DP_STREAM_0)
+		state |= DP_DP0_PUSH_VCPF;
+	else if (msm_dp_panel->stream_id == DP_STREAM_1)
+		state |= DP_DP1_PUSH_VCPF;
+	else
+		state |= DP_MSTLINK_PUSH_VCPF;
+
 	reinit_completion(&ctrl->idle_comp);
-	msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, DP_STATE_CTRL_PUSH_IDLE);
+
+	msm_dp_write_link(ctrl, msm_dp_panel->stream_id,
+			  msm_dp_panel->stream_id > 1 ?
+			  REG_DP_MSTLINK_STATE_CTRL : REG_DP_STATE_CTRL,
+			  state);
 
 	if (!wait_for_completion_timeout(&ctrl->idle_comp,
 			IDLE_PATTERN_COMPLETION_TIMEOUT_JIFFIES))
@@ -2073,7 +2113,7 @@ void msm_dp_ctrl_set_psr(struct msm_dp_ctrl *msm_dp_ctrl, bool enter)
 			return;
 		}
 
-		msm_dp_ctrl_push_idle(msm_dp_ctrl);
+		msm_dp_ctrl_push_idle(msm_dp_ctrl, ctrl->panel);
 		msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, 0);
 
 		msm_dp_ctrl_psr_mainlink_disable(ctrl);
@@ -2183,7 +2223,7 @@ static int msm_dp_ctrl_link_maintenance(struct msm_dp_ctrl_private *ctrl)
 	int ret = 0;
 	int training_step = DP_TRAINING_NONE;
 
-	msm_dp_ctrl_push_idle(&ctrl->msm_dp_ctrl);
+	msm_dp_ctrl_push_idle(&ctrl->msm_dp_ctrl, ctrl->panel);
 
 	ctrl->link->phy_params.p_level = 0;
 	ctrl->link->phy_params.v_level = 0;
@@ -3005,6 +3045,13 @@ irqreturn_t msm_dp_ctrl_isr(struct msm_dp_ctrl *msm_dp_ctrl)
 		ret = IRQ_HANDLED;
 	}
 
+	isr = msm_dp_ctrl_get_mst_interrupt(ctrl);
+	if (isr & (DP_INTR_DP0_VCPF_SENT | DP_INTR_DP1_VCPF_SENT)) {
+		drm_dbg_dp(ctrl->drm_dev, "vcpf sent\n");
+		complete(&ctrl->idle_comp);
+		ret = IRQ_HANDLED;
+	}
+
 	/* DP aux isr */
 	isr = msm_dp_ctrl_get_aux_interrupt(ctrl);
 	if (isr)
diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.h b/drivers/gpu/drm/msm/dp/dp_ctrl.h
index c59338199399..cfe7e4496943 100644
--- a/drivers/gpu/drm/msm/dp/dp_ctrl.h
+++ b/drivers/gpu/drm/msm/dp/dp_ctrl.h
@@ -22,7 +22,7 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl,
 int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train);
 void msm_dp_ctrl_off_link(struct msm_dp_ctrl *msm_dp_ctrl);
 void msm_dp_ctrl_off_pixel_clk(struct msm_dp_ctrl *msm_dp_ctrl, enum msm_dp_stream_id stream_id);
-void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl);
+void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *msm_dp_panel);
 irqreturn_t msm_dp_ctrl_isr(struct msm_dp_ctrl *msm_dp_ctrl);
 void msm_dp_ctrl_handle_sink_request(struct msm_dp_ctrl *msm_dp_ctrl);
 struct msm_dp_ctrl *msm_dp_ctrl_get(struct device *dev,
diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
index e0bf4dffa6af..e8028402f748 100644
--- a/drivers/gpu/drm/msm/dp/dp_display.c
+++ b/drivers/gpu/drm/msm/dp/dp_display.c
@@ -1557,7 +1557,7 @@ void msm_dp_display_atomic_disable(struct msm_dp *msm_dp_display)
 
 	dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display);
 
-	msm_dp_ctrl_push_idle(dp->ctrl);
+	msm_dp_ctrl_push_idle(dp->ctrl, dp->panel);
 	msm_dp_ctrl_mst_stream_channel_slot_setup(dp->ctrl);
 	msm_dp_ctrl_mst_send_act(dp->ctrl);
 }
diff --git a/drivers/gpu/drm/msm/dp/dp_reg.h b/drivers/gpu/drm/msm/dp/dp_reg.h
index 835a55446868..65695fcb48d0 100644
--- a/drivers/gpu/drm/msm/dp/dp_reg.h
+++ b/drivers/gpu/drm/msm/dp/dp_reg.h
@@ -42,9 +42,13 @@
 #define DP_INTR_FRAME_END		BIT(6)
 #define DP_INTR_CRC_UPDATED		BIT(9)
 
+#define DP_INTR_DP0_VCPF_SENT		BIT(0)
+#define DP_INTR_DP1_VCPF_SENT		BIT(3)
+
 #define REG_DP_INTR_STATUS3			(0x00000028)
 
 #define REG_DP_INTR_STATUS4			(0x0000002C)
+#define REG_DP_INTR_STATUS5			(0x00000034)
 #define PSR_UPDATE_INT				(0x00000001)
 #define PSR_CAPTURE_INT				(0x00000004)
 #define PSR_EXIT_INT				(0x00000010)
@@ -356,6 +360,7 @@
 #define REG_DP_DP0_RG				(0x000004F8)
 #define REG_DP_DP1_RG				(0x000004FC)
 
+#define REG_DP_MSTLINK_STATE_CTRL		(0x00000000)
 #define REG_DP_MSTLINK_CONFIGURATION_CTRL	(0x00000034)
 #define REG_DP_MSTLINK_TIMESLOT_1_32		(0x00000038)
 #define REG_DP_MSTLINK_TIMESLOT_33_63		(0x0000003C)

-- 
2.43.0


^ permalink raw reply	[flat|nested] 128+ messages in thread

* [PATCH v4 23/39] drm/msm/dp: Always program MST_FIFO_CONSTANT_FILL for MST use cases
  2026-04-10  9:33 [PATCH v4 00/39] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
                   ` (21 preceding siblings ...)
  2026-04-10  9:33 ` [PATCH v4 22/39] drm/msm/dp: Add support for sending VCPF packets in DP controller Yongxing Mou
@ 2026-04-10  9:33 ` Yongxing Mou
  2026-04-10  9:33 ` [PATCH v4 24/39] drm/msm/dp: simplify link and clock disable sequence Yongxing Mou
                   ` (17 subsequent siblings)
  40 siblings, 0 replies; 128+ messages in thread
From: Yongxing Mou @ 2026-04-10  9:33 UTC (permalink / raw)
  To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang
  Cc: linux-arm-msm, dri-devel, freedreno, linux-kernel, Yongxing Mou,
	Abhinav Kumar, Dmitry Baryshkov

From: Abhinav Kumar <quic_abhinavk@quicinc.com>

As per the hardware programming guide, MST_FIFO_CONSTANT_FILL must
always be programmed when operating in MST mode. This patch ensures
the register is configured accordingly.

Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
---
 drivers/gpu/drm/msm/dp/dp_ctrl.c  |  2 ++
 drivers/gpu/drm/msm/dp/dp_panel.c | 12 ++++++++++++
 drivers/gpu/drm/msm/dp/dp_panel.h |  2 ++
 3 files changed, 16 insertions(+)

diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c
index 9907f2e56e65..199c2806aaa2 100644
--- a/drivers/gpu/drm/msm/dp/dp_ctrl.c
+++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c
@@ -685,6 +685,8 @@ static void msm_dp_ctrl_configure_source_params(struct msm_dp_ctrl_private *ctrl
 	msm_dp_ctrl_config_misc1_misc0(ctrl, msm_dp_panel);
 
 	msm_dp_panel_timing_cfg(msm_dp_panel, ctrl->msm_dp_ctrl.wide_bus_en);
+
+	msm_dp_panel_mst_async_fifo(msm_dp_panel, ctrl->mst_active);
 }
 
 /*
diff --git a/drivers/gpu/drm/msm/dp/dp_panel.c b/drivers/gpu/drm/msm/dp/dp_panel.c
index a8a6297b37e3..e05d96f33c43 100644
--- a/drivers/gpu/drm/msm/dp/dp_panel.c
+++ b/drivers/gpu/drm/msm/dp/dp_panel.c
@@ -753,6 +753,18 @@ void msm_dp_panel_set_pixel_base(struct msm_dp_panel *msm_dp_panel, void __iomem
 	panel->pixel_base = pixel_base;
 }
 
+void msm_dp_panel_mst_async_fifo(struct msm_dp_panel *msm_dp_panel, bool mst_en)
+{
+	struct msm_dp_panel_private *panel;
+
+	panel = container_of(msm_dp_panel, struct msm_dp_panel_private, msm_dp_panel);
+
+	if (mst_en)
+		msm_dp_write_pn(panel, MMSS_DP_ASYNC_FIFO_CONFIG, 0x01);
+	else
+		msm_dp_write_pn(panel, MMSS_DP_ASYNC_FIFO_CONFIG, 0x00);
+}
+
 int msm_dp_panel_init_panel_info(struct msm_dp_panel *msm_dp_panel)
 {
 	struct drm_display_mode *drm_mode;
diff --git a/drivers/gpu/drm/msm/dp/dp_panel.h b/drivers/gpu/drm/msm/dp/dp_panel.h
index 8e7374de90eb..8bab27520439 100644
--- a/drivers/gpu/drm/msm/dp/dp_panel.h
+++ b/drivers/gpu/drm/msm/dp/dp_panel.h
@@ -72,6 +72,8 @@ void msm_dp_panel_set_pixel_base(struct msm_dp_panel *msm_dp_panel, void __iomem
 void msm_dp_panel_enable_vsc_sdp(struct msm_dp_panel *msm_dp_panel, struct dp_sdp *vsc_sdp);
 void msm_dp_panel_disable_vsc_sdp(struct msm_dp_panel *msm_dp_panel);
 
+void msm_dp_panel_mst_async_fifo(struct msm_dp_panel *msm_dp_panel, bool mst_en);
+
 /**
  * is_link_rate_valid() - validates the link rate
  * @bw_code: link rate requested by the sink

-- 
2.43.0


^ permalink raw reply	[flat|nested] 128+ messages in thread

* [PATCH v4 24/39] drm/msm/dp: simplify link and clock disable sequence
  2026-04-10  9:33 [PATCH v4 00/39] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
                   ` (22 preceding siblings ...)
  2026-04-10  9:33 ` [PATCH v4 23/39] drm/msm/dp: Always program MST_FIFO_CONSTANT_FILL for MST use cases Yongxing Mou
@ 2026-04-10  9:33 ` Yongxing Mou
  2026-04-12  0:00   ` Dmitry Baryshkov
  2026-04-10  9:34 ` [PATCH v4 25/39] drm/msm/dp: pass panel to display enable/disable helpers Yongxing Mou
                   ` (16 subsequent siblings)
  40 siblings, 1 reply; 128+ messages in thread
From: Yongxing Mou @ 2026-04-10  9:33 UTC (permalink / raw)
  To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang
  Cc: linux-arm-msm, dri-devel, freedreno, linux-kernel, Yongxing Mou

Move the common disable steps out of the sink_count check to make the
flow easier to follow.

No functional change intended.

Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
---
 drivers/gpu/drm/msm/dp/dp_display.c | 25 ++++++++-----------------
 1 file changed, 8 insertions(+), 17 deletions(-)

diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
index e8028402f748..7b3b9160e005 100644
--- a/drivers/gpu/drm/msm/dp/dp_display.c
+++ b/drivers/gpu/drm/msm/dp/dp_display.c
@@ -734,28 +734,19 @@ static int msm_dp_display_disable(struct msm_dp_display_private *dp)
 
 	msm_dp_panel_disable_vsc_sdp(dp->panel);
 
-	/* dongle is still connected but sinks are disconnected */
-	if (dp->link->sink_count == 0) {
-		/*
-		 * irq_hpd with sink_count = 0
-		 * hdmi unplugged out of dongle
-		 */
+	msm_dp_ctrl_off_pixel_clk(dp->ctrl, dp->panel->stream_id);
 
-		/* set dongle to D3 (power off) mode */
+	/* dongle is still connected but sinks are disconnected */
+	if (dp->link->sink_count == 0)
 		msm_dp_link_psm_config(dp->link, &dp->panel->link_info, true);
-		msm_dp_ctrl_off_pixel_clk(dp->ctrl, dp->panel->stream_id);
-		msm_dp_ctrl_off_link(dp->ctrl);
+
+	msm_dp_ctrl_off_link(dp->ctrl);
+
+	if (dp->link->sink_count == 0)
 		/* re-init the PHY so that we can listen to Dongle disconnect */
 		msm_dp_ctrl_reinit_phy(dp->ctrl);
-	} else {
-		/*
-		 * unplugged interrupt
-		 * dongle unplugged out of DUT
-		 */
-		msm_dp_ctrl_off_pixel_clk(dp->ctrl, dp->panel->stream_id);
-		msm_dp_ctrl_off_link(dp->ctrl);
+	else
 		msm_dp_display_host_phy_exit(dp);
-	}
 
 	msm_dp_display->power_on = false;
 

-- 
2.43.0


^ permalink raw reply	[flat|nested] 128+ messages in thread

* [PATCH v4 25/39] drm/msm/dp: pass panel to display enable/disable helpers
  2026-04-10  9:33 [PATCH v4 00/39] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
                   ` (23 preceding siblings ...)
  2026-04-10  9:33 ` [PATCH v4 24/39] drm/msm/dp: simplify link and clock disable sequence Yongxing Mou
@ 2026-04-10  9:34 ` Yongxing Mou
  2026-04-12  0:05   ` Dmitry Baryshkov
  2026-04-10  9:34 ` [PATCH v4 26/39] drm/msm/dp: abstract out the dp_display stream helpers to accept a panel Yongxing Mou
                   ` (15 subsequent siblings)
  40 siblings, 1 reply; 128+ messages in thread
From: Yongxing Mou @ 2026-04-10  9:34 UTC (permalink / raw)
  To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang
  Cc: linux-arm-msm, dri-devel, freedreno, linux-kernel, Yongxing Mou

Pass struct msm_dp_panel to the display enable/disable helpers to make
them easier to reuse for MST stream handling.

Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
---
 drivers/gpu/drm/msm/dp/dp_display.c | 18 ++++++++++--------
 1 file changed, 10 insertions(+), 8 deletions(-)

diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
index 7b3b9160e005..88a078e53dc1 100644
--- a/drivers/gpu/drm/msm/dp/dp_display.c
+++ b/drivers/gpu/drm/msm/dp/dp_display.c
@@ -668,7 +668,8 @@ static int msm_dp_display_prepare(struct msm_dp_display_private *dp)
 	return msm_dp_ctrl_prepare_stream_on(dp->ctrl, force_link_train);
 }
 
-static int msm_dp_display_enable(struct msm_dp_display_private *dp)
+static int msm_dp_display_enable(struct msm_dp_display_private *dp,
+				 struct msm_dp_panel *msm_dp_panel)
 {
 	int rc = 0;
 	struct msm_dp *msm_dp_display = &dp->msm_dp_display;
@@ -725,20 +726,21 @@ static void msm_dp_display_audio_notify_disable(struct msm_dp_display_private *d
 	msm_dp_display->audio_enabled = false;
 }
 
-static int msm_dp_display_disable(struct msm_dp_display_private *dp)
+static int msm_dp_display_disable(struct msm_dp_display_private *dp,
+				  struct msm_dp_panel *msm_dp_panel)
 {
 	struct msm_dp *msm_dp_display = &dp->msm_dp_display;
 
 	if (!msm_dp_display->power_on)
 		return 0;
 
-	msm_dp_panel_disable_vsc_sdp(dp->panel);
+	msm_dp_panel_disable_vsc_sdp(msm_dp_panel);
 
-	msm_dp_ctrl_off_pixel_clk(dp->ctrl, dp->panel->stream_id);
+	msm_dp_ctrl_off_pixel_clk(dp->ctrl, msm_dp_panel->stream_id);
 
 	/* dongle is still connected but sinks are disconnected */
 	if (dp->link->sink_count == 0)
-		msm_dp_link_psm_config(dp->link, &dp->panel->link_info, true);
+		msm_dp_link_psm_config(dp->link, &msm_dp_panel->link_info, true);
 
 	msm_dp_ctrl_off_link(dp->ctrl);
 
@@ -1529,14 +1531,14 @@ void msm_dp_display_atomic_enable(struct msm_dp *msm_dp_display)
 
 	msm_dp_display_set_stream_info(msm_dp_display, dp->panel, 0, 0, 0, 0);
 
-	rc = msm_dp_display_enable(dp);
+	rc = msm_dp_display_enable(dp, dp->panel);
 	if (rc)
 		DRM_ERROR("DP display enable failed, rc=%d\n", rc);
 
 	rc = msm_dp_display_post_enable(msm_dp_display);
 	if (rc) {
 		DRM_ERROR("DP display post enable failed, rc=%d\n", rc);
-		msm_dp_display_disable(dp);
+		msm_dp_display_disable(dp, dp->panel);
 	}
 
 	drm_dbg_dp(msm_dp_display->drm_dev, "type=%d Done\n", msm_dp_display->connector_type);
@@ -1573,7 +1575,7 @@ void msm_dp_display_atomic_post_disable(struct msm_dp *dp)
 
 	msm_dp_display_audio_notify_disable(msm_dp_display);
 
-	msm_dp_display_disable(msm_dp_display);
+	msm_dp_display_disable(msm_dp_display, msm_dp_display->panel);
 
 	msm_dp_display_unprepare(msm_dp_display);
 }

-- 
2.43.0


^ permalink raw reply	[flat|nested] 128+ messages in thread

* [PATCH v4 26/39] drm/msm/dp: abstract out the dp_display stream helpers to accept a panel
  2026-04-10  9:33 [PATCH v4 00/39] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
                   ` (24 preceding siblings ...)
  2026-04-10  9:34 ` [PATCH v4 25/39] drm/msm/dp: pass panel to display enable/disable helpers Yongxing Mou
@ 2026-04-10  9:34 ` Yongxing Mou
  2026-04-12  0:07   ` Dmitry Baryshkov
  2026-04-10  9:34 ` [PATCH v4 27/39] drm/msm/dp: replace power_on with active_stream_cnt for dp_display Yongxing Mou
                   ` (14 subsequent siblings)
  40 siblings, 1 reply; 128+ messages in thread
From: Yongxing Mou @ 2026-04-10  9:34 UTC (permalink / raw)
  To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang
  Cc: linux-arm-msm, dri-devel, freedreno, linux-kernel, Yongxing Mou,
	Abhinav Kumar

From: Abhinav Kumar <quic_abhinavk@quicinc.com>

Currently the dp_display bridge helpers, in particular the
dp_display_enable()/dp_display_disable() use the cached panel.
To be able to re-use these helpers for MST use-case abstract the
helpers to use the panel which is passed in to them.

Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
---
 drivers/gpu/drm/msm/dp/dp_display.c | 117 ++++++++++++++++++++++++------------
 drivers/gpu/drm/msm/dp/dp_display.h |  12 ++++
 2 files changed, 91 insertions(+), 38 deletions(-)

diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
index 88a078e53dc1..33d8539afee7 100644
--- a/drivers/gpu/drm/msm/dp/dp_display.c
+++ b/drivers/gpu/drm/msm/dp/dp_display.c
@@ -635,12 +635,14 @@ static int msm_dp_display_set_mode(struct msm_dp *msm_dp_display,
 	return 0;
 }
 
-static int msm_dp_display_prepare(struct msm_dp_display_private *dp)
+int msm_dp_display_prepare(struct msm_dp *msm_dp_display)
 {
-	struct msm_dp *msm_dp_display = &dp->msm_dp_display;
+	struct msm_dp_display_private *dp;
 	int rc = 0;
 	bool force_link_train = false;
 
+	dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display);
+
 	drm_dbg_dp(dp->drm_dev, "sink_count=%d\n", dp->link->sink_count);
 
 	if (msm_dp_display->is_edp)
@@ -680,7 +682,7 @@ static int msm_dp_display_enable(struct msm_dp_display_private *dp,
 		return 0;
 	}
 
-	rc = msm_dp_ctrl_on_stream(dp->ctrl, dp->panel);
+	rc = msm_dp_ctrl_on_stream(dp->ctrl, msm_dp_panel);
 	if (!rc)
 		msm_dp_display->power_on = true;
 
@@ -738,18 +740,6 @@ static int msm_dp_display_disable(struct msm_dp_display_private *dp,
 
 	msm_dp_ctrl_off_pixel_clk(dp->ctrl, msm_dp_panel->stream_id);
 
-	/* dongle is still connected but sinks are disconnected */
-	if (dp->link->sink_count == 0)
-		msm_dp_link_psm_config(dp->link, &msm_dp_panel->link_info, true);
-
-	msm_dp_ctrl_off_link(dp->ctrl);
-
-	if (dp->link->sink_count == 0)
-		/* re-init the PHY so that we can listen to Dongle disconnect */
-		msm_dp_ctrl_reinit_phy(dp->ctrl);
-	else
-		msm_dp_display_host_phy_exit(dp);
-
 	msm_dp_display->power_on = false;
 
 	drm_dbg_dp(dp->drm_dev, "sink count: %d\n", dp->link->sink_count);
@@ -1495,76 +1485,116 @@ int msm_dp_modeset_init(struct msm_dp *msm_dp_display, struct drm_device *dev,
 	return 0;
 }
 
-void msm_dp_display_atomic_prepare(struct msm_dp *msm_dp_display,
-				   struct drm_atomic_state *state)
+int msm_dp_display_set_mode_helper(struct msm_dp *msm_dp_display,
+				   struct drm_atomic_state *state,
+				   struct drm_encoder *drm_encoder,
+				   struct msm_dp_panel *msm_dp_panel)
 {
 	struct drm_crtc *crtc;
 	struct drm_crtc_state *crtc_state;
+
+	crtc = drm_atomic_get_new_crtc_for_encoder(state, drm_encoder);
+	if (!crtc)
+		return 0;
+	crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
+
+	return msm_dp_display_set_mode(msm_dp_display, &crtc_state->adjusted_mode, msm_dp_panel);
+}
+
+void msm_dp_display_atomic_prepare(struct msm_dp *msm_dp_display,
+				   struct drm_atomic_state *state)
+{
 	int rc = 0;
 	struct msm_dp_display_private *dp;
 
 	dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display);
 
-	crtc = drm_atomic_get_new_crtc_for_encoder(state,
-						   msm_dp_display->bridge->encoder);
-	if (!crtc)
-		return;
-	crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
-
-	rc = msm_dp_display_set_mode(msm_dp_display, &crtc_state->adjusted_mode, dp->panel);
+	rc = msm_dp_display_set_mode_helper(msm_dp_display, state,
+					    msm_dp_display->bridge->encoder, dp->panel);
 	if (rc) {
 		DRM_ERROR("Failed to perform a mode set, rc=%d\n", rc);
 		return;
 	}
 
-	rc = msm_dp_display_prepare(dp);
+	rc = msm_dp_display_prepare(msm_dp_display);
 	if (rc)
 		DRM_ERROR("DP display prepare failed, rc=%d\n", rc);
 }
 
-void msm_dp_display_atomic_enable(struct msm_dp *msm_dp_display)
+void msm_dp_display_enable_helper(struct msm_dp *msm_dp_display, struct msm_dp_panel *msm_dp_panel)
 {
 	struct msm_dp_display_private *dp;
 	int rc = 0;
 
 	dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display);
 
-	msm_dp_display_set_stream_info(msm_dp_display, dp->panel, 0, 0, 0, 0);
-
-	rc = msm_dp_display_enable(dp, dp->panel);
+	rc = msm_dp_display_enable(dp, msm_dp_panel);
 	if (rc)
 		DRM_ERROR("DP display enable failed, rc=%d\n", rc);
 
 	rc = msm_dp_display_post_enable(msm_dp_display);
 	if (rc) {
 		DRM_ERROR("DP display post enable failed, rc=%d\n", rc);
-		msm_dp_display_disable(dp, dp->panel);
+		msm_dp_display_disable(dp, msm_dp_panel);
 	}
 
 	drm_dbg_dp(msm_dp_display->drm_dev, "type=%d Done\n", msm_dp_display->connector_type);
 }
 
-void msm_dp_display_atomic_disable(struct msm_dp *msm_dp_display)
+void msm_dp_display_atomic_enable(struct msm_dp *msm_dp_display)
 {
 	struct msm_dp_display_private *dp;
 
 	dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display);
 
-	msm_dp_ctrl_push_idle(dp->ctrl, dp->panel);
+	msm_dp_display_set_stream_info(msm_dp_display, dp->panel, 0, 0, 0, 0);
+
+	msm_dp_display_enable_helper(msm_dp_display, dp->panel);
+}
+
+void msm_dp_display_disable_helper(struct msm_dp *msm_dp_display,
+				   struct msm_dp_panel *msm_dp_panel)
+{
+	struct msm_dp_display_private *dp;
+
+	dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display);
+
+	msm_dp_ctrl_push_idle(dp->ctrl, msm_dp_panel);
 	msm_dp_ctrl_mst_stream_channel_slot_setup(dp->ctrl);
 	msm_dp_ctrl_mst_send_act(dp->ctrl);
 }
 
-static void msm_dp_display_unprepare(struct msm_dp_display_private *dp)
+void msm_dp_display_atomic_disable(struct msm_dp *msm_dp_display)
 {
-	struct msm_dp *msm_dp_display = &dp->msm_dp_display;
+	struct msm_dp_display_private *dp;
 
-	pm_runtime_put_sync(&msm_dp_display->pdev->dev);
+	dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display);
+
+	msm_dp_display_disable_helper(msm_dp_display, dp->panel);
+}
+
+void msm_dp_display_unprepare(struct msm_dp *msm_dp_display)
+{
+	struct msm_dp_display_private *dp;
+
+	dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display);
 
-	drm_dbg_dp(dp->drm_dev, "type=%d Done\n", msm_dp_display->connector_type);
+	/* dongle is still connected but sinks are disconnected */
+	if (dp->link->sink_count == 0)
+		msm_dp_link_psm_config(dp->link, &dp->panel->link_info, true);
+
+	msm_dp_ctrl_off_link(dp->ctrl);
+
+	/* re-init the PHY so that we can listen to Dongle disconnect */
+	if (dp->link->sink_count == 0)
+		msm_dp_ctrl_reinit_phy(dp->ctrl);
+	else
+		msm_dp_display_host_phy_exit(dp);
+
+	pm_runtime_put_sync(&msm_dp_display->pdev->dev);
 }
 
-void msm_dp_display_atomic_post_disable(struct msm_dp *dp)
+void msm_dp_display_atomic_post_disable_helper(struct msm_dp *dp, struct msm_dp_panel *msm_dp_panel)
 {
 	struct msm_dp_display_private *msm_dp_display;
 
@@ -1575,7 +1605,18 @@ void msm_dp_display_atomic_post_disable(struct msm_dp *dp)
 
 	msm_dp_display_audio_notify_disable(msm_dp_display);
 
-	msm_dp_display_disable(msm_dp_display, msm_dp_display->panel);
+	msm_dp_display_disable(msm_dp_display, msm_dp_panel);
+
+	drm_dbg_dp(dp->drm_dev, "type=%d Done\n", dp->connector_type);
+}
+
+void msm_dp_display_atomic_post_disable(struct msm_dp *msm_dp_display)
+{
+	struct msm_dp_display_private *dp;
+
+	dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display);
+
+	msm_dp_display_atomic_post_disable_helper(msm_dp_display, dp->panel);
 
 	msm_dp_display_unprepare(msm_dp_display);
 }
diff --git a/drivers/gpu/drm/msm/dp/dp_display.h b/drivers/gpu/drm/msm/dp/dp_display.h
index 0ccdddb223c8..0ede5505be58 100644
--- a/drivers/gpu/drm/msm/dp/dp_display.h
+++ b/drivers/gpu/drm/msm/dp/dp_display.h
@@ -46,5 +46,17 @@ enum drm_mode_status msm_dp_display_mode_valid(struct msm_dp *dp,
 int msm_dp_display_set_stream_info(struct msm_dp *msm_dp_display, struct msm_dp_panel *panel,
 				   enum msm_dp_stream_id stream_id,
 				   u32 start_slot, u32 num_slots, u32 pbn);
+void msm_dp_display_enable_helper(struct msm_dp *msm_dp_display,
+				  struct msm_dp_panel *msm_dp_panel);
+void msm_dp_display_disable_helper(struct msm_dp *msm_dp_display,
+				   struct msm_dp_panel *msm_dp_panel);
+void msm_dp_display_atomic_post_disable_helper(struct msm_dp *msm_dp_display,
+					       struct msm_dp_panel *msm_dp_panel);
+int msm_dp_display_set_mode_helper(struct msm_dp *msm_dp_display,
+				   struct drm_atomic_state *state,
+				   struct drm_encoder *drm_encoder,
+				   struct msm_dp_panel *msm_dp_panel);
+int msm_dp_display_prepare(struct msm_dp *msm_dp_display);
+void msm_dp_display_unprepare(struct msm_dp *dp);
 
 #endif /* _DP_DISPLAY_H_ */

-- 
2.43.0


^ permalink raw reply	[flat|nested] 128+ messages in thread

* [PATCH v4 27/39] drm/msm/dp: replace power_on with active_stream_cnt for dp_display
  2026-04-10  9:33 [PATCH v4 00/39] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
                   ` (25 preceding siblings ...)
  2026-04-10  9:34 ` [PATCH v4 26/39] drm/msm/dp: abstract out the dp_display stream helpers to accept a panel Yongxing Mou
@ 2026-04-10  9:34 ` Yongxing Mou
  2026-04-12  0:11   ` Dmitry Baryshkov
  2026-04-10  9:34 ` [PATCH v4 28/39] drm/msm/dp: Mark the SST bridge disconnected when mst is active Yongxing Mou
                   ` (13 subsequent siblings)
  40 siblings, 1 reply; 128+ messages in thread
From: Yongxing Mou @ 2026-04-10  9:34 UTC (permalink / raw)
  To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang
  Cc: linux-arm-msm, dri-devel, freedreno, linux-kernel, Yongxing Mou,
	Abhinav Kumar

From: Abhinav Kumar <quic_abhinavk@quicinc.com>

For DP MST, the link clock and power domain resources stay on until
both streams have been disabled OR we receive hotplug. Introduce an
active_stream_cnt to track the number of active streams and necessary
state handling. Replace the power_on variable with active_stream_cnt
as power_on boolean works only for a single stream.

Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
---
 drivers/gpu/drm/msm/dp/dp_audio.c   |  2 +-
 drivers/gpu/drm/msm/dp/dp_display.c | 38 +++++++++++++++++++------------------
 drivers/gpu/drm/msm/dp/dp_display.h |  2 +-
 3 files changed, 22 insertions(+), 20 deletions(-)

diff --git a/drivers/gpu/drm/msm/dp/dp_audio.c b/drivers/gpu/drm/msm/dp/dp_audio.c
index 41018e82efa1..035e230201fd 100644
--- a/drivers/gpu/drm/msm/dp/dp_audio.c
+++ b/drivers/gpu/drm/msm/dp/dp_audio.c
@@ -284,7 +284,7 @@ int msm_dp_audio_prepare(struct drm_bridge *bridge,
 	 * such cases check for connection status and bail out if not
 	 * connected.
 	 */
-	if (!msm_dp_display->power_on) {
+	if (!msm_dp_display->active_stream_cnt) {
 		rc = -EINVAL;
 		goto end;
 	}
diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
index 33d8539afee7..e6ecbb3a688e 100644
--- a/drivers/gpu/drm/msm/dp/dp_display.c
+++ b/drivers/gpu/drm/msm/dp/dp_display.c
@@ -657,15 +657,15 @@ int msm_dp_display_prepare(struct msm_dp *msm_dp_display)
 	if (dp->link->sink_count == 0)
 		return rc;
 
-	if (!msm_dp_display->power_on) {
+	if (!msm_dp_display->active_stream_cnt) {
 		msm_dp_display_host_phy_init(dp);
 		force_link_train = true;
-	}
 
-	rc = msm_dp_ctrl_on_link(dp->ctrl, msm_dp_display->mst_active);
-	if (rc)
-		DRM_ERROR("Failed link training (rc=%d)\n", rc);
-	// TODO: schedule drm_connector_set_link_status_property()
+		rc = msm_dp_ctrl_on_link(dp->ctrl, msm_dp_display->mst_active);
+		if (rc)
+			DRM_ERROR("Failed link training (rc=%d)\n", rc);
+		// TODO: schedule drm_connector_set_link_status_property()
+	}
 
 	return msm_dp_ctrl_prepare_stream_on(dp->ctrl, force_link_train);
 }
@@ -674,18 +674,12 @@ static int msm_dp_display_enable(struct msm_dp_display_private *dp,
 				 struct msm_dp_panel *msm_dp_panel)
 {
 	int rc = 0;
-	struct msm_dp *msm_dp_display = &dp->msm_dp_display;
 
 	drm_dbg_dp(dp->drm_dev, "sink_count=%d\n", dp->link->sink_count);
-	if (msm_dp_display->power_on) {
-		drm_dbg_dp(dp->drm_dev, "Link already setup, return\n");
-		return 0;
-	}
 
 	rc = msm_dp_ctrl_on_stream(dp->ctrl, msm_dp_panel);
-	if (!rc)
-		msm_dp_display->power_on = true;
 
+	dp->msm_dp_display.active_stream_cnt++;
 	return rc;
 }
 
@@ -731,16 +725,14 @@ static void msm_dp_display_audio_notify_disable(struct msm_dp_display_private *d
 static int msm_dp_display_disable(struct msm_dp_display_private *dp,
 				  struct msm_dp_panel *msm_dp_panel)
 {
-	struct msm_dp *msm_dp_display = &dp->msm_dp_display;
-
-	if (!msm_dp_display->power_on)
+	if (!dp->msm_dp_display.active_stream_cnt)
 		return 0;
 
 	msm_dp_panel_disable_vsc_sdp(msm_dp_panel);
 
 	msm_dp_ctrl_off_pixel_clk(dp->ctrl, msm_dp_panel->stream_id);
 
-	msm_dp_display->power_on = false;
+	dp->msm_dp_display.active_stream_cnt--;
 
 	drm_dbg_dp(dp->drm_dev, "sink count: %d\n", dp->link->sink_count);
 	return 0;
@@ -876,7 +868,7 @@ void msm_dp_snapshot(struct msm_disp_state *disp_state, struct msm_dp *dp)
 	 * power_on status before dumping DP registers to avoid crash due
 	 * to unclocked access
 	 */
-	if (!dp->power_on)
+	if (!dp->active_stream_cnt)
 		return;
 
 	msm_disp_snapshot_add_block(disp_state, msm_dp_display->ahb_len,
@@ -1559,6 +1551,11 @@ void msm_dp_display_disable_helper(struct msm_dp *msm_dp_display,
 
 	dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display);
 
+	if (!msm_dp_display->active_stream_cnt) {
+		drm_dbg_dp(dp->drm_dev, "no active streams\n");
+		return;
+	}
+
 	msm_dp_ctrl_push_idle(dp->ctrl, msm_dp_panel);
 	msm_dp_ctrl_mst_stream_channel_slot_setup(dp->ctrl);
 	msm_dp_ctrl_mst_send_act(dp->ctrl);
@@ -1579,6 +1576,11 @@ void msm_dp_display_unprepare(struct msm_dp *msm_dp_display)
 
 	dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display);
 
+	if (msm_dp_display->active_stream_cnt) {
+		drm_dbg_dp(dp->drm_dev, "stream still active, return\n");
+		return;
+	}
+
 	/* dongle is still connected but sinks are disconnected */
 	if (dp->link->sink_count == 0)
 		msm_dp_link_psm_config(dp->link, &dp->panel->link_info, true);
diff --git a/drivers/gpu/drm/msm/dp/dp_display.h b/drivers/gpu/drm/msm/dp/dp_display.h
index 0ede5505be58..2548f67cd441 100644
--- a/drivers/gpu/drm/msm/dp/dp_display.h
+++ b/drivers/gpu/drm/msm/dp/dp_display.h
@@ -18,7 +18,7 @@ struct msm_dp {
 	struct drm_bridge *next_bridge;
 	struct drm_bridge *bridge;
 	bool audio_enabled;
-	bool power_on;
+	u32 active_stream_cnt;
 	bool mst_active;
 	unsigned int connector_type;
 	bool is_edp;

-- 
2.43.0


^ permalink raw reply	[flat|nested] 128+ messages in thread

* [PATCH v4 28/39] drm/msm/dp: Mark the SST bridge disconnected when mst is active
  2026-04-10  9:33 [PATCH v4 00/39] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
                   ` (26 preceding siblings ...)
  2026-04-10  9:34 ` [PATCH v4 27/39] drm/msm/dp: replace power_on with active_stream_cnt for dp_display Yongxing Mou
@ 2026-04-10  9:34 ` Yongxing Mou
  2026-04-12  0:11   ` Dmitry Baryshkov
  2026-04-10  9:34 ` [PATCH v4 29/39] drm/msm/dp: add an API to initialize MST on sink side Yongxing Mou
                   ` (12 subsequent siblings)
  40 siblings, 1 reply; 128+ messages in thread
From: Yongxing Mou @ 2026-04-10  9:34 UTC (permalink / raw)
  To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang
  Cc: linux-arm-msm, dri-devel, freedreno, linux-kernel, Yongxing Mou,
	Abhinav Kumar, Dmitry Baryshkov

From: Abhinav Kumar <quic_abhinavk@quicinc.com>

The bridge detect function is only applicable for SST. In MST mode,
connector detection is handled by MST bridges. This patch skips
detection for the SST bridge when MST is active.

Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
---
 drivers/gpu/drm/msm/dp/dp_display.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
index e6ecbb3a688e..8ae690ce2b9f 100644
--- a/drivers/gpu/drm/msm/dp/dp_display.c
+++ b/drivers/gpu/drm/msm/dp/dp_display.c
@@ -926,6 +926,9 @@ enum drm_connector_status msm_dp_bridge_detect(struct drm_bridge *bridge,
 
 	priv = container_of(dp, struct msm_dp_display_private, msm_dp_display);
 
+	if (dp->mst_active)
+		return status;
+
 	mutex_lock(&priv->plugged_lock);
 	ret = pm_runtime_resume_and_get(&dp->pdev->dev);
 	if (ret) {

-- 
2.43.0


^ permalink raw reply	[flat|nested] 128+ messages in thread

* [PATCH v4 29/39] drm/msm/dp: add an API to initialize MST on sink side
  2026-04-10  9:33 [PATCH v4 00/39] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
                   ` (27 preceding siblings ...)
  2026-04-10  9:34 ` [PATCH v4 28/39] drm/msm/dp: Mark the SST bridge disconnected when mst is active Yongxing Mou
@ 2026-04-10  9:34 ` Yongxing Mou
  2026-04-12  0:15   ` Dmitry Baryshkov
  2026-04-10  9:34 ` [PATCH v4 30/39] drm/msm/dp: add dp_display_get_panel() to initialize DP panel Yongxing Mou
                   ` (11 subsequent siblings)
  40 siblings, 1 reply; 128+ messages in thread
From: Yongxing Mou @ 2026-04-10  9:34 UTC (permalink / raw)
  To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang
  Cc: linux-arm-msm, dri-devel, freedreno, linux-kernel, Yongxing Mou,
	Abhinav Kumar

From: Abhinav Kumar <quic_abhinavk@quicinc.com>

If the DP controller is capable of supporting multiple streams
then initialize the DP sink in MST mode by programming the DP_MSTM_CTRL
DPCD register to enable MST mode.

Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
---
 drivers/gpu/drm/msm/dp/dp_display.c | 57 ++++++++++++++++++++++++++++++++-----
 1 file changed, 50 insertions(+), 7 deletions(-)

diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
index 8ae690ce2b9f..abf26951819a 100644
--- a/drivers/gpu/drm/msm/dp/dp_display.c
+++ b/drivers/gpu/drm/msm/dp/dp_display.c
@@ -14,6 +14,7 @@
 #include <linux/string_choices.h>
 #include <drm/display/drm_dp_aux_bus.h>
 #include <drm/display/drm_hdmi_audio_helper.h>
+#include <drm/display/drm_dp_mst_helper.h>
 #include <drm/drm_edid.h>
 
 #include "msm_drv.h"
@@ -270,6 +271,40 @@ static int msm_dp_display_lttpr_init(struct msm_dp_display_private *dp, u8 *dpcd
 	return lttpr_count;
 }
 
+static void msm_dp_display_mst_init(struct msm_dp_display_private *dp)
+{
+	const unsigned long clear_mstm_ctrl_timeout_us = 100000;
+	u8 old_mstm_ctrl;
+	struct msm_dp *msm_dp = &dp->msm_dp_display;
+	int ret;
+
+	/* clear sink MST state */
+	drm_dp_dpcd_read_byte(dp->aux, DP_MSTM_CTRL, &old_mstm_ctrl);
+
+	ret = drm_dp_dpcd_write_byte(dp->aux, DP_MSTM_CTRL, 0);
+	if (ret < 0) {
+		DRM_ERROR("failed to clear DP_MSTM_CTRL, ret=%d\n", ret);
+		return;
+	}
+
+	/* add extra delay if MST old state is on*/
+	if (old_mstm_ctrl) {
+		drm_dbg_dp(dp->drm_dev, "wait %luus to set DP_MSTM_CTRL set 0\n",
+			   clear_mstm_ctrl_timeout_us);
+		usleep_range(clear_mstm_ctrl_timeout_us,
+			     clear_mstm_ctrl_timeout_us + 1000);
+	}
+
+	ret = drm_dp_dpcd_write_byte(dp->aux, DP_MSTM_CTRL,
+				     DP_MST_EN | DP_UP_REQ_EN | DP_UPSTREAM_IS_SRC);
+	if (ret < 0) {
+		DRM_ERROR("sink MST enablement failed\n");
+		return;
+	}
+
+	msm_dp->mst_active = true;
+}
+
 static int msm_dp_display_process_hpd_high(struct msm_dp_display_private *dp)
 {
 	struct drm_connector *connector = dp->msm_dp_display.connector;
@@ -288,14 +323,19 @@ static int msm_dp_display_process_hpd_high(struct msm_dp_display_private *dp)
 	if (rc)
 		goto end;
 
-	drm_edid = drm_edid_read_ddc(connector, &dp->aux->ddc);
-	drm_edid_connector_update(connector, drm_edid);
+	if (!(dp->max_stream > 1) || !drm_dp_read_mst_cap(dp->aux, dp->panel->dpcd)) {
+		drm_edid = drm_edid_read_ddc(connector, &dp->aux->ddc);
+		drm_edid_connector_update(connector, drm_edid);
 
-	if (!drm_edid) {
-		DRM_ERROR("panel edid read failed\n");
-		/* check edid read fail is due to unplug */
-		if (!msm_dp_aux_is_link_connected(dp->aux))
-			return -ETIMEDOUT;
+		if (!drm_edid) {
+			DRM_ERROR("panel edid read failed\n");
+			/* check edid read fail is due to unplug */
+			if (!msm_dp_aux_is_link_connected(dp->aux))
+				return -ETIMEDOUT;
+		}
+
+		if (rc)
+			goto end;
 	}
 
 	msm_dp_link_process_request(dp->link);
@@ -317,6 +357,9 @@ static int msm_dp_display_process_hpd_high(struct msm_dp_display_private *dp)
 	 */
 	msm_dp_link_psm_config(dp->link, &dp->panel->link_info, false);
 
+	if (dp->max_stream > 1 && drm_dp_read_mst_cap(dp->aux, dp->panel->dpcd))
+		msm_dp_display_mst_init(dp);
+
 	msm_dp_link_reset_phy_params_vx_px(dp->link);
 
 end:

-- 
2.43.0


^ permalink raw reply	[flat|nested] 128+ messages in thread

* [PATCH v4 30/39] drm/msm/dp: add dp_display_get_panel() to initialize DP panel
  2026-04-10  9:33 [PATCH v4 00/39] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
                   ` (28 preceding siblings ...)
  2026-04-10  9:34 ` [PATCH v4 29/39] drm/msm/dp: add an API to initialize MST on sink side Yongxing Mou
@ 2026-04-10  9:34 ` Yongxing Mou
  2026-04-12  0:17   ` Dmitry Baryshkov
  2026-04-10  9:34 ` [PATCH v4 31/39] drm/msm/dp: add prepared to manage link-level operations Yongxing Mou
                   ` (10 subsequent siblings)
  40 siblings, 1 reply; 128+ messages in thread
From: Yongxing Mou @ 2026-04-10  9:34 UTC (permalink / raw)
  To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang
  Cc: linux-arm-msm, dri-devel, freedreno, linux-kernel, Yongxing Mou,
	Abhinav Kumar

From: Abhinav Kumar <quic_abhinavk@quicinc.com>

Add an API dp_display_get_panel() to initialize and return a DP
panel to be used by DP MST module. Since some of the fields of
DP panel are private, dp_display module needs to initialize these
parts and return the panel back.

Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
---
 drivers/gpu/drm/msm/dp/dp_display.c | 24 ++++++++++++++++++++++++
 drivers/gpu/drm/msm/dp/dp_display.h |  1 +
 2 files changed, 25 insertions(+)

diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
index abf26951819a..1f26283b2dee 100644
--- a/drivers/gpu/drm/msm/dp/dp_display.c
+++ b/drivers/gpu/drm/msm/dp/dp_display.c
@@ -571,6 +571,30 @@ static int msm_dp_irq_hpd_handle(struct msm_dp_display_private *dp)
 	return rc;
 }
 
+struct msm_dp_panel *msm_dp_display_get_panel(struct msm_dp *msm_dp_display)
+{
+	struct msm_dp_display_private *dp;
+	struct msm_dp_panel *dp_panel;
+
+	dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display);
+
+	dp_panel = msm_dp_panel_get(&dp->msm_dp_display.pdev->dev, dp->aux, dp->link,
+				    dp->link_base, dp->mst2link_base, dp->mst3link_base,
+				    dp->pixel_base[0]);
+
+	if (IS_ERR(dp->panel)) {
+		DRM_ERROR("failed to initialize panel\n");
+		return NULL;
+	}
+
+	/* FIXME: move out of panel */
+	memcpy(dp_panel->dpcd, dp->panel->dpcd, DP_RECEIVER_CAP_SIZE);
+	memcpy(&dp_panel->link_info, &dp->panel->link_info,
+	       sizeof(dp->panel->link_info));
+
+	return dp_panel;
+}
+
 static void msm_dp_display_deinit_sub_modules(struct msm_dp_display_private *dp)
 {
 	msm_dp_audio_put(dp->audio);
diff --git a/drivers/gpu/drm/msm/dp/dp_display.h b/drivers/gpu/drm/msm/dp/dp_display.h
index 2548f67cd441..5f3ef295d710 100644
--- a/drivers/gpu/drm/msm/dp/dp_display.h
+++ b/drivers/gpu/drm/msm/dp/dp_display.h
@@ -59,4 +59,5 @@ int msm_dp_display_set_mode_helper(struct msm_dp *msm_dp_display,
 int msm_dp_display_prepare(struct msm_dp *msm_dp_display);
 void msm_dp_display_unprepare(struct msm_dp *dp);
 
+struct msm_dp_panel *msm_dp_display_get_panel(struct msm_dp *msm_dp_display);
 #endif /* _DP_DISPLAY_H_ */

-- 
2.43.0


^ permalink raw reply	[flat|nested] 128+ messages in thread

* [PATCH v4 31/39] drm/msm/dp: add prepared to manage link-level operations
  2026-04-10  9:33 [PATCH v4 00/39] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
                   ` (29 preceding siblings ...)
  2026-04-10  9:34 ` [PATCH v4 30/39] drm/msm/dp: add dp_display_get_panel() to initialize DP panel Yongxing Mou
@ 2026-04-10  9:34 ` Yongxing Mou
  2026-04-12  0:19   ` Dmitry Baryshkov
  2026-04-10  9:34 ` [PATCH v4 32/39] drm/msm/dpu: initialize encoders per stream for DP MST Yongxing Mou
                   ` (9 subsequent siblings)
  40 siblings, 1 reply; 128+ messages in thread
From: Yongxing Mou @ 2026-04-10  9:34 UTC (permalink / raw)
  To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang
  Cc: linux-arm-msm, dri-devel, freedreno, linux-kernel, Yongxing Mou,
	Abhinav Kumar

In MST mode, multiple streams share the same DP link. Track a prepared
state so msm_dp_display_prepare() runs only once per link and repeated
calls are skipped.

Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
---
 drivers/gpu/drm/msm/dp/dp_display.c | 34 ++++++++++++++++++++++++++--------
 drivers/gpu/drm/msm/dp/dp_display.h |  1 +
 2 files changed, 27 insertions(+), 8 deletions(-)

diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
index 1f26283b2dee..9eaf6994a350 100644
--- a/drivers/gpu/drm/msm/dp/dp_display.c
+++ b/drivers/gpu/drm/msm/dp/dp_display.c
@@ -715,6 +715,11 @@ int msm_dp_display_prepare(struct msm_dp *msm_dp_display)
 	if (msm_dp_display->is_edp)
 		msm_dp_hpd_plug_handle(dp);
 
+	if (msm_dp_display->prepared) {
+		drm_dbg_dp(dp->drm_dev, "Link already setup, return\n");
+		return 0;
+	}
+
 	rc = pm_runtime_resume_and_get(&msm_dp_display->pdev->dev);
 	if (rc) {
 		DRM_ERROR("failed to pm_runtime_resume\n");
@@ -734,7 +739,11 @@ int msm_dp_display_prepare(struct msm_dp *msm_dp_display)
 		// TODO: schedule drm_connector_set_link_status_property()
 	}
 
-	return msm_dp_ctrl_prepare_stream_on(dp->ctrl, force_link_train);
+	rc = msm_dp_ctrl_prepare_stream_on(dp->ctrl, force_link_train);
+	if (!rc)
+		msm_dp_display->prepared = true;
+
+	return rc;
 }
 
 static int msm_dp_display_enable(struct msm_dp_display_private *dp,
@@ -1590,14 +1599,16 @@ void msm_dp_display_enable_helper(struct msm_dp *msm_dp_display, struct msm_dp_p
 
 	dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display);
 
-	rc = msm_dp_display_enable(dp, msm_dp_panel);
-	if (rc)
-		DRM_ERROR("DP display enable failed, rc=%d\n", rc);
+	if (msm_dp_display->prepared) {
+		rc = msm_dp_display_enable(dp, msm_dp_panel);
+		if (rc)
+			DRM_ERROR("DP display enable failed, rc=%d\n", rc);
 
-	rc = msm_dp_display_post_enable(msm_dp_display);
-	if (rc) {
-		DRM_ERROR("DP display post enable failed, rc=%d\n", rc);
-		msm_dp_display_disable(dp, msm_dp_panel);
+		rc = msm_dp_display_post_enable(msm_dp_display);
+		if (rc) {
+			DRM_ERROR("DP display post enable failed, rc=%d\n", rc);
+			msm_dp_display_disable(dp, msm_dp_panel);
+		}
 	}
 
 	drm_dbg_dp(msm_dp_display->drm_dev, "type=%d Done\n", msm_dp_display->connector_type);
@@ -1646,6 +1657,11 @@ void msm_dp_display_unprepare(struct msm_dp *msm_dp_display)
 
 	dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display);
 
+	if (!msm_dp_display->prepared) {
+		drm_dbg_dp(dp->drm_dev, "Link already setup, return\n");
+		return;
+	}
+
 	if (msm_dp_display->active_stream_cnt) {
 		drm_dbg_dp(dp->drm_dev, "stream still active, return\n");
 		return;
@@ -1664,6 +1680,8 @@ void msm_dp_display_unprepare(struct msm_dp *msm_dp_display)
 		msm_dp_display_host_phy_exit(dp);
 
 	pm_runtime_put_sync(&msm_dp_display->pdev->dev);
+
+	msm_dp_display->prepared = false;
 }
 
 void msm_dp_display_atomic_post_disable_helper(struct msm_dp *dp, struct msm_dp_panel *msm_dp_panel)
diff --git a/drivers/gpu/drm/msm/dp/dp_display.h b/drivers/gpu/drm/msm/dp/dp_display.h
index 5f3ef295d710..bda76319c459 100644
--- a/drivers/gpu/drm/msm/dp/dp_display.h
+++ b/drivers/gpu/drm/msm/dp/dp_display.h
@@ -22,6 +22,7 @@ struct msm_dp {
 	bool mst_active;
 	unsigned int connector_type;
 	bool is_edp;
+	bool prepared;
 
 	struct msm_dp_audio *msm_dp_audio;
 	bool psr_supported;

-- 
2.43.0


^ permalink raw reply	[flat|nested] 128+ messages in thread

* [PATCH v4 32/39] drm/msm/dpu: initialize encoders per stream for DP MST
  2026-04-10  9:33 [PATCH v4 00/39] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
                   ` (30 preceding siblings ...)
  2026-04-10  9:34 ` [PATCH v4 31/39] drm/msm/dp: add prepared to manage link-level operations Yongxing Mou
@ 2026-04-10  9:34 ` Yongxing Mou
  2026-04-11 19:31   ` Dmitry Baryshkov
  2026-04-10  9:34 ` [PATCH v4 33/39] drm/msm/dp: initialize dp_mst module for each DP MST controller Yongxing Mou
                   ` (8 subsequent siblings)
  40 siblings, 1 reply; 128+ messages in thread
From: Yongxing Mou @ 2026-04-10  9:34 UTC (permalink / raw)
  To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang
  Cc: linux-arm-msm, dri-devel, freedreno, linux-kernel, Yongxing Mou

Simply initialize MST encoders for MST-capable DP controllers, and
introduce msm_dp_get_mst_max_stream to query MST streams.

Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
---
 drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c | 14 +++++++++++++-
 drivers/gpu/drm/msm/msm_drv.h           |  7 ++++++-
 2 files changed, 19 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c
index 61d7e65469b3..090e7d790593 100644
--- a/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c
+++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c
@@ -653,7 +653,7 @@ static int _dpu_kms_initialize_displayport(struct drm_device *dev,
 	struct msm_display_info info;
 	bool yuv_supported;
 	int rc;
-	int i;
+	int i, stream_id, stream_cnt;
 
 	for (i = 0; i < ARRAY_SIZE(priv->kms->dp); i++) {
 		if (!priv->kms->dp[i])
@@ -676,6 +676,18 @@ static int _dpu_kms_initialize_displayport(struct drm_device *dev,
 			DPU_ERROR("modeset_init failed for DP, rc = %d\n", rc);
 			return rc;
 		}
+
+		stream_cnt = msm_dp_get_mst_max_stream(priv->kms->dp[i]);
+
+		if (stream_cnt > 1) {
+			for (stream_id = 0; stream_id < stream_cnt; stream_id++) {
+				encoder = dpu_encoder_init(dev, DRM_MODE_ENCODER_DPMST, &info);
+				if (IS_ERR(encoder)) {
+					DPU_ERROR("encoder init failed for dp mst display\n");
+					return PTR_ERR(encoder);
+				}
+			}
+		}
 	}
 
 	return 0;
diff --git a/drivers/gpu/drm/msm/msm_drv.h b/drivers/gpu/drm/msm/msm_drv.h
index 6d847d593f1a..3061eca49cb2 100644
--- a/drivers/gpu/drm/msm/msm_drv.h
+++ b/drivers/gpu/drm/msm/msm_drv.h
@@ -362,7 +362,7 @@ bool msm_dp_is_yuv_420_enabled(const struct msm_dp *dp_display,
 bool msm_dp_needs_periph_flush(const struct msm_dp *dp_display,
 			       const struct drm_display_mode *mode);
 bool msm_dp_wide_bus_available(const struct msm_dp *dp_display);
-
+int msm_dp_get_mst_max_stream(struct msm_dp *dp_display);
 #else
 static inline int __init msm_dp_register(void)
 {
@@ -379,6 +379,11 @@ static inline int msm_dp_modeset_init(struct msm_dp *dp_display,
 	return -EINVAL;
 }
 
+static inline int msm_dp_get_mst_max_stream(struct msm_dp *dp_display)
+{
+	return -EINVAL;
+}
+
 static inline void msm_dp_snapshot(struct msm_disp_state *disp_state, struct msm_dp *dp_display)
 {
 }

-- 
2.43.0


^ permalink raw reply	[flat|nested] 128+ messages in thread

* [PATCH v4 33/39] drm/msm/dp: initialize dp_mst module for each DP MST controller
  2026-04-10  9:33 [PATCH v4 00/39] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
                   ` (31 preceding siblings ...)
  2026-04-10  9:34 ` [PATCH v4 32/39] drm/msm/dpu: initialize encoders per stream for DP MST Yongxing Mou
@ 2026-04-10  9:34 ` Yongxing Mou
  2026-04-11 23:36   ` Dmitry Baryshkov
  2026-04-10  9:34 ` [PATCH v4 34/39] drm/msm/dp: add dp_mst_drm to manage DP MST bridge operations Yongxing Mou
                   ` (7 subsequent siblings)
  40 siblings, 1 reply; 128+ messages in thread
From: Yongxing Mou @ 2026-04-10  9:34 UTC (permalink / raw)
  To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang
  Cc: linux-arm-msm, dri-devel, freedreno, linux-kernel, Yongxing Mou,
	Abhinav Kumar

From: Abhinav Kumar <quic_abhinavk@quicinc.com>

For each MST capable DP controller, initialize a dp_mst module to
manage its DP MST operations. The DP MST module for each controller
is the central entity to manage its topology related operations as
well as interfacing with the rest of the DP driver.

Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
---
 drivers/gpu/drm/msm/Makefile            |  3 +-
 drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c |  6 +++
 drivers/gpu/drm/msm/dp/dp_display.c     | 18 ++++++++
 drivers/gpu/drm/msm/dp/dp_display.h     |  2 +
 drivers/gpu/drm/msm/dp/dp_mst_drm.c     | 73 +++++++++++++++++++++++++++++++++
 drivers/gpu/drm/msm/dp/dp_mst_drm.h     | 13 ++++++
 drivers/gpu/drm/msm/msm_drv.h           |  6 +++
 7 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
index 8b94c5f1cb68..1d8426876aa1 100644
--- a/drivers/gpu/drm/msm/Makefile
+++ b/drivers/gpu/drm/msm/Makefile
@@ -144,7 +144,8 @@ msm-display-$(CONFIG_DRM_MSM_DP)+= dp/dp_aux.o \
 	dp/dp_link.o \
 	dp/dp_panel.o \
 	dp/dp_audio.o \
-	dp/dp_utils.o
+	dp/dp_utils.o \
+	dp/dp_mst_drm.o
 
 msm-display-$(CONFIG_DRM_MSM_HDMI_HDCP) += hdmi/hdmi_hdcp.o
 
diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c
index 090e7d790593..d7ce13a4586d 100644
--- a/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c
+++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c
@@ -680,6 +680,12 @@ static int _dpu_kms_initialize_displayport(struct drm_device *dev,
 		stream_cnt = msm_dp_get_mst_max_stream(priv->kms->dp[i]);
 
 		if (stream_cnt > 1) {
+			rc = msm_dp_mst_register(priv->kms->dp[i]);
+			if (rc) {
+				DPU_ERROR("dp_mst_init failed for DP, rc = %d\n", rc);
+				return rc;
+			}
+
 			for (stream_id = 0; stream_id < stream_cnt; stream_id++) {
 				encoder = dpu_encoder_init(dev, DRM_MODE_ENCODER_DPMST, &info);
 				if (IS_ERR(encoder)) {
diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
index 9eaf6994a350..919767945ba5 100644
--- a/drivers/gpu/drm/msm/dp/dp_display.c
+++ b/drivers/gpu/drm/msm/dp/dp_display.c
@@ -28,6 +28,7 @@
 #include "dp_drm.h"
 #include "dp_audio.h"
 #include "dp_debug.h"
+#include "dp_mst_drm.h"
 
 static bool psr_enabled = false;
 module_param(psr_enabled, bool, 0);
@@ -360,6 +361,9 @@ static int msm_dp_display_process_hpd_high(struct msm_dp_display_private *dp)
 	if (dp->max_stream > 1 && drm_dp_read_mst_cap(dp->aux, dp->panel->dpcd))
 		msm_dp_display_mst_init(dp);
 
+	if (dp->msm_dp_display.mst_active)
+		msm_dp_mst_display_set_mgr_state(&dp->msm_dp_display, true);
+
 	msm_dp_link_reset_phy_params_vx_px(dp->link);
 
 end:
@@ -527,6 +531,11 @@ static int msm_dp_hpd_unplug_handle(struct msm_dp_display_private *dp)
 						 dp->panel->dpcd,
 						 dp->panel->downstream_ports);
 
+	if (dp->msm_dp_display.mst_active) {
+		msm_dp_mst_display_set_mgr_state(&dp->msm_dp_display, false);
+		dp->msm_dp_display.mst_active = false;
+	}
+
 	/* signal the disconnect event early to ensure proper teardown */
 	msm_dp_display_handle_plugged_change(&dp->msm_dp_display, false);
 
@@ -1556,6 +1565,15 @@ int msm_dp_modeset_init(struct msm_dp *msm_dp_display, struct drm_device *dev,
 	return 0;
 }
 
+int msm_dp_mst_register(struct msm_dp *msm_dp_display)
+{
+	struct msm_dp_display_private *dp;
+
+	dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display);
+
+	return msm_dp_mst_init(msm_dp_display, dp->max_stream, dp->aux);
+}
+
 int msm_dp_display_set_mode_helper(struct msm_dp *msm_dp_display,
 				   struct drm_atomic_state *state,
 				   struct drm_encoder *drm_encoder,
diff --git a/drivers/gpu/drm/msm/dp/dp_display.h b/drivers/gpu/drm/msm/dp/dp_display.h
index bda76319c459..55874daf41c4 100644
--- a/drivers/gpu/drm/msm/dp/dp_display.h
+++ b/drivers/gpu/drm/msm/dp/dp_display.h
@@ -24,6 +24,8 @@ struct msm_dp {
 	bool is_edp;
 	bool prepared;
 
+	void *msm_dp_mst;
+
 	struct msm_dp_audio *msm_dp_audio;
 	bool psr_supported;
 };
diff --git a/drivers/gpu/drm/msm/dp/dp_mst_drm.c b/drivers/gpu/drm/msm/dp/dp_mst_drm.c
new file mode 100644
index 000000000000..b6c7b8211025
--- /dev/null
+++ b/drivers/gpu/drm/msm/dp/dp_mst_drm.c
@@ -0,0 +1,73 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2025 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#include <drm/drm_edid.h>
+#include <drm/drm_managed.h>
+#include <drm/drm_bridge.h>
+#include <drm/display/drm_dp_mst_helper.h>
+
+#include "dp_mst_drm.h"
+#include "dp_panel.h"
+
+#define MAX_DPCD_TRANSACTION_BYTES 16
+
+struct msm_dp_mst {
+	struct drm_dp_mst_topology_mgr mst_mgr;
+	struct msm_dp *msm_dp;
+	struct drm_dp_aux *dp_aux;
+	u32 max_streams;
+	/* Protects MST bridge enable/disable handling. */
+	struct mutex mst_lock;
+};
+
+int msm_dp_mst_display_set_mgr_state(struct msm_dp *dp_display, bool state)
+{
+	struct msm_dp_mst *mst = dp_display->msm_dp_mst;
+	int rc;
+
+	rc = drm_dp_mst_topology_mgr_set_mst(&mst->mst_mgr, state);
+	if (rc < 0) {
+		DRM_ERROR("failed to set topology mgr state to %d. rc %d\n",
+			  state, rc);
+	}
+
+	drm_dbg_dp(dp_display->drm_dev, "dp_mst_display_set_mgr_state state:%d\n", state);
+	return rc;
+}
+
+int msm_dp_mst_init(struct msm_dp *dp_display, u32 max_streams, struct drm_dp_aux *drm_aux)
+{
+	struct drm_device *dev = dp_display->drm_dev;
+	int conn_base_id = 0;
+	int ret;
+	struct msm_dp_mst *msm_dp_mst;
+
+	msm_dp_mst = devm_kzalloc(dev->dev, sizeof(*msm_dp_mst), GFP_KERNEL);
+	if (!msm_dp_mst)
+		return -ENOMEM;
+
+	memset(&msm_dp_mst->mst_mgr, 0, sizeof(msm_dp_mst->mst_mgr));
+
+	conn_base_id = dp_display->connector->base.id;
+	msm_dp_mst->msm_dp = dp_display;
+	msm_dp_mst->max_streams = max_streams;
+
+	msm_dp_mst->dp_aux = drm_aux;
+
+	ret = drm_dp_mst_topology_mgr_init(&msm_dp_mst->mst_mgr, dev,
+					   drm_aux,
+					   MAX_DPCD_TRANSACTION_BYTES,
+					   max_streams,
+					   conn_base_id);
+	if (ret) {
+		DRM_ERROR("DP DRM MST topology manager init failed\n");
+		return ret;
+	}
+
+	dp_display->msm_dp_mst = msm_dp_mst;
+
+	mutex_init(&msm_dp_mst->mst_lock);
+	return ret;
+}
diff --git a/drivers/gpu/drm/msm/dp/dp_mst_drm.h b/drivers/gpu/drm/msm/dp/dp_mst_drm.h
new file mode 100644
index 000000000000..5d411529f681
--- /dev/null
+++ b/drivers/gpu/drm/msm/dp/dp_mst_drm.h
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: GPL-2.0-only
+ * Copyright (c) 2025 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#ifndef _DP_MST_DRM_H_
+#define _DP_MST_DRM_H_
+
+#include "dp_display.h"
+
+int msm_dp_mst_init(struct msm_dp *dp_display, u32 max_streams, struct drm_dp_aux *drm_aux);
+int msm_dp_mst_display_set_mgr_state(struct msm_dp *dp_display, bool state);
+
+#endif /* _DP_MST_DRM_H_ */
diff --git a/drivers/gpu/drm/msm/msm_drv.h b/drivers/gpu/drm/msm/msm_drv.h
index 3061eca49cb2..5f73e0aa1c2f 100644
--- a/drivers/gpu/drm/msm/msm_drv.h
+++ b/drivers/gpu/drm/msm/msm_drv.h
@@ -363,6 +363,7 @@ bool msm_dp_needs_periph_flush(const struct msm_dp *dp_display,
 			       const struct drm_display_mode *mode);
 bool msm_dp_wide_bus_available(const struct msm_dp *dp_display);
 int msm_dp_get_mst_max_stream(struct msm_dp *dp_display);
+int msm_dp_mst_register(struct msm_dp *dp_display);
 #else
 static inline int __init msm_dp_register(void)
 {
@@ -384,6 +385,11 @@ static inline int msm_dp_get_mst_max_stream(struct msm_dp *dp_display)
 	return -EINVAL;
 }
 
+static inline int msm_dp_mst_register(struct msm_dp *dp_display)
+{
+	return -EINVAL;
+}
+
 static inline void msm_dp_snapshot(struct msm_disp_state *disp_state, struct msm_dp *dp_display)
 {
 }

-- 
2.43.0


^ permalink raw reply	[flat|nested] 128+ messages in thread

* [PATCH v4 34/39] drm/msm/dp: add dp_mst_drm to manage DP MST bridge operations
  2026-04-10  9:33 [PATCH v4 00/39] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
                   ` (32 preceding siblings ...)
  2026-04-10  9:34 ` [PATCH v4 33/39] drm/msm/dp: initialize dp_mst module for each DP MST controller Yongxing Mou
@ 2026-04-10  9:34 ` Yongxing Mou
  2026-04-11 23:51   ` Dmitry Baryshkov
  2026-04-10  9:34 ` [PATCH v4 35/39] drm/msm/dp: wire MST helpers into atomic check and commit paths Yongxing Mou
                   ` (6 subsequent siblings)
  40 siblings, 1 reply; 128+ messages in thread
From: Yongxing Mou @ 2026-04-10  9:34 UTC (permalink / raw)
  To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang
  Cc: linux-arm-msm, dri-devel, freedreno, linux-kernel, Yongxing Mou,
	Abhinav Kumar

From: Abhinav Kumar <quic_abhinavk@quicinc.com>

Add dp_mst_drm to manage the DP MST bridge operations similar to the
dp_drm file which manages the SST bridge operations. Each MST encoder
creates one bridge and each bridge is bound to its own dp_panel
abstraction to manage the operations of its pipeline.

Keep the connector/panel association in bridge private state for atomic
assignment and release, and mirror it in the bridge object for runtime
bridge callbacks.

Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
---
 drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c |   6 +
 drivers/gpu/drm/msm/dp/dp_mst_drm.c     | 459 +++++++++++++++++++++++++++++++-
 drivers/gpu/drm/msm/msm_drv.h           |   7 +
 3 files changed, 471 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c
index d7ce13a4586d..89868443c0fe 100644
--- a/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c
+++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c
@@ -692,6 +692,12 @@ static int _dpu_kms_initialize_displayport(struct drm_device *dev,
 					DPU_ERROR("encoder init failed for dp mst display\n");
 					return PTR_ERR(encoder);
 				}
+
+				rc = msm_dp_mst_attach_encoder(priv->kms->dp[i], encoder);
+				if (rc) {
+					DPU_ERROR("DP MST init failed, %d\n", rc);
+					continue;
+				}
 			}
 		}
 	}
diff --git a/drivers/gpu/drm/msm/dp/dp_mst_drm.c b/drivers/gpu/drm/msm/dp/dp_mst_drm.c
index b6c7b8211025..4df3ea5e36d0 100644
--- a/drivers/gpu/drm/msm/dp/dp_mst_drm.c
+++ b/drivers/gpu/drm/msm/dp/dp_mst_drm.c
@@ -13,8 +13,47 @@
 
 #define MAX_DPCD_TRANSACTION_BYTES 16
 
+#define to_dp_mst_bridge(x)     container_of((x), struct msm_dp_mst_bridge, base)
+#define to_dp_mst_bridge_state_priv(x) \
+		container_of((x), struct msm_dp_mst_bridge_state, base)
+#define to_dp_mst_bridge_state(x) \
+		to_dp_mst_bridge_state_priv((x)->obj.state)
+#define to_dp_mst_connector(x) \
+		container_of((x), struct msm_dp_mst_connector, connector)
+
+#define DP_MST_CONN_ID(x) ((x)->connector ? \
+		(x)->connector->base.id : 0)
+
+struct msm_dp_mst_bridge {
+	struct drm_bridge base;
+	struct drm_private_obj obj;
+	u32 id;
+
+	bool initialized;
+
+	struct msm_dp *display;
+	struct drm_encoder *encoder;
+
+	struct drm_connector *connector;
+	struct msm_dp_panel *msm_dp_panel;
+};
+
+struct msm_dp_mst_bridge_state {
+	struct drm_private_state base;
+	struct drm_connector *connector;
+	struct msm_dp_panel *msm_dp_panel;
+};
+
+struct msm_dp_mst_connector {
+	struct drm_connector connector;
+	struct drm_dp_mst_port *mst_port;
+	struct msm_dp_mst *dp_mst;
+	struct msm_dp_panel *dp_panel;
+};
+
 struct msm_dp_mst {
 	struct drm_dp_mst_topology_mgr mst_mgr;
+	struct msm_dp_mst_bridge *mst_bridge[DP_STREAM_MAX];
 	struct msm_dp *msm_dp;
 	struct drm_dp_aux *dp_aux;
 	u32 max_streams;
@@ -22,6 +61,419 @@ struct msm_dp_mst {
 	struct mutex mst_lock;
 };
 
+static struct drm_private_state *msm_dp_mst_duplicate_bridge_state(struct drm_private_obj *obj)
+{
+	struct msm_dp_mst_bridge_state *mst_bridge_state;
+
+	mst_bridge_state = kmemdup(obj->state, sizeof(*mst_bridge_state), GFP_KERNEL);
+	if (!mst_bridge_state)
+		return NULL;
+
+	__drm_atomic_helper_private_obj_duplicate_state(obj, &mst_bridge_state->base);
+
+	return &mst_bridge_state->base;
+}
+
+static void msm_dp_mst_destroy_bridge_state(struct drm_private_obj *obj,
+					    struct drm_private_state *state)
+{
+	struct msm_dp_mst_bridge_state *mst_bridge_state =
+		to_dp_mst_bridge_state_priv(state);
+
+	kfree(mst_bridge_state);
+}
+
+static const struct drm_private_state_funcs msm_dp_mst_bridge_state_funcs = {
+	.atomic_duplicate_state = msm_dp_mst_duplicate_bridge_state,
+	.atomic_destroy_state = msm_dp_mst_destroy_bridge_state,
+};
+
+static struct msm_dp_mst_bridge_state *msm_dp_mst_br_priv_state(struct drm_atomic_state *st,
+								struct msm_dp_mst_bridge *bridge)
+{
+	struct drm_device *dev = bridge->base.dev;
+	struct drm_private_state *obj_state = drm_atomic_get_private_obj_state(st, &bridge->obj);
+
+	WARN_ON(!drm_modeset_is_locked(&dev->mode_config.connection_mutex));
+
+	return to_dp_mst_bridge_state_priv(obj_state);
+}
+
+static void msm_dp_mst_update_timeslots(struct msm_dp_mst *mst,
+					struct msm_dp_mst_bridge *mst_bridge,
+					struct drm_atomic_state *state,
+					struct drm_dp_mst_port *port)
+{
+	struct drm_dp_mst_topology_state *mst_state;
+	struct drm_dp_mst_atomic_payload *payload;
+
+	mst_state = to_drm_dp_mst_topology_state(mst->mst_mgr.base.state);
+	payload = drm_atomic_get_mst_payload_state(mst_state, port);
+
+	if (!payload) {
+		DRM_ERROR("MST bridge [%d] update_timeslots failed, null payload\n",
+			  mst_bridge->id);
+		return;
+	}
+
+	if (payload->vc_start_slot < 0)
+		msm_dp_display_set_stream_info(mst->msm_dp, mst_bridge->msm_dp_panel,
+					       mst_bridge->id, 1, 0, 0);
+	else
+		msm_dp_display_set_stream_info(mst->msm_dp, mst_bridge->msm_dp_panel,
+					       mst_bridge->id, payload->vc_start_slot,
+					       payload->time_slots, payload->pbn);
+}
+
+static int msm_dp_mst_bridge_pre_enable_part1(struct msm_dp_mst_bridge *dp_bridge,
+					      struct drm_atomic_state *state)
+{
+	struct msm_dp *dp_display = dp_bridge->display;
+	struct msm_dp_mst *mst = dp_display->msm_dp_mst;
+	struct msm_dp_mst_connector *mst_conn = to_dp_mst_connector(dp_bridge->connector);
+	struct drm_dp_mst_port *port = mst_conn->mst_port;
+	struct drm_dp_mst_topology_state *mst_state;
+	struct drm_dp_mst_atomic_payload *payload;
+	struct msm_dp_panel *dp_panel = mst_conn->dp_panel;
+	int pbn;
+	int rc = 0;
+
+	mst_state = drm_atomic_get_new_mst_topology_state(state, &mst->mst_mgr);
+
+	pbn = drm_dp_calc_pbn_mode(dp_panel->msm_dp_mode.drm_mode.clock,
+				   (mst_conn->connector.display_info.bpc * 3) << 4);
+
+	payload = drm_atomic_get_mst_payload_state(mst_state, port);
+	if (!payload || payload->time_slots <= 0) {
+		DRM_ERROR("time slots not allocated for conn:%d\n", DP_MST_CONN_ID(dp_bridge));
+		rc = -EINVAL;
+		return rc;
+	}
+
+	drm_dbg_dp(dp_display->drm_dev, "conn:%d pbn:%d, slots:%d\n", DP_MST_CONN_ID(dp_bridge),
+		   pbn, payload->time_slots);
+
+	drm_dp_mst_update_slots(mst_state, DP_CAP_ANSI_8B10B);
+
+	rc = drm_dp_add_payload_part1(&mst->mst_mgr, mst_state, payload);
+	if (rc) {
+		DRM_ERROR("payload allocation failure for conn:%d\n", DP_MST_CONN_ID(dp_bridge));
+		return rc;
+	}
+
+	msm_dp_mst_update_timeslots(mst, dp_bridge, state, port);
+
+	return rc;
+}
+
+static void _msm_dp_mst_bridge_pre_enable_part2(struct msm_dp_mst_bridge *dp_bridge,
+						struct drm_atomic_state *state)
+{
+	struct msm_dp *dp_display = dp_bridge->display;
+	struct msm_dp_mst *mst = dp_display->msm_dp_mst;
+	struct msm_dp_mst_connector *mst_conn = to_dp_mst_connector(dp_bridge->connector);
+	struct drm_dp_mst_port *port = mst_conn->mst_port;
+	struct drm_dp_mst_topology_state *mst_state;
+	struct drm_dp_mst_atomic_payload *payload;
+
+	drm_dp_check_act_status(&mst->mst_mgr);
+
+	mst_state = to_drm_dp_mst_topology_state(mst->mst_mgr.base.state);
+	payload = drm_atomic_get_mst_payload_state(mst_state, port);
+
+	drm_dp_add_payload_part2(&mst->mst_mgr, payload);
+
+	drm_dbg_dp(dp_display->drm_dev, "MST bridge [%d] _pre enable part-2 complete\n",
+		   dp_bridge->id);
+}
+
+static void msm_dp_mst_bridge_pre_disable_part1(struct msm_dp_mst_bridge *dp_bridge,
+						struct drm_atomic_state *state)
+{
+	struct msm_dp *dp_display = dp_bridge->display;
+	struct msm_dp_mst *mst = dp_display->msm_dp_mst;
+	struct msm_dp_mst_connector *mst_conn = to_dp_mst_connector(dp_bridge->connector);
+	struct drm_dp_mst_port *port = mst_conn->mst_port;
+	struct drm_dp_mst_topology_state *old_mst_state;
+	struct drm_dp_mst_topology_state *new_mst_state;
+	const struct drm_dp_mst_atomic_payload *old_payload;
+	struct drm_dp_mst_atomic_payload *new_payload;
+
+	old_mst_state = drm_atomic_get_old_mst_topology_state(state, &mst->mst_mgr);
+	new_mst_state = drm_atomic_get_new_mst_topology_state(state, &mst->mst_mgr);
+
+	old_payload = drm_atomic_get_mst_payload_state(old_mst_state, port);
+	new_payload = drm_atomic_get_mst_payload_state(new_mst_state, port);
+
+	if (!old_payload || !new_payload) {
+		DRM_ERROR("MST bridge [%d] _pre disable part-1 failed, null payload\n",
+			  dp_bridge->id);
+		return;
+	}
+
+	drm_dp_remove_payload_part1(&mst->mst_mgr, new_mst_state, new_payload);
+	drm_dp_remove_payload_part2(&mst->mst_mgr, new_mst_state, old_payload, new_payload);
+
+	msm_dp_mst_update_timeslots(mst, dp_bridge, state, port);
+
+	drm_dbg_dp(dp_display->drm_dev, "MST bridge [%d] _pre disable part-1 complete\n",
+		   dp_bridge->id);
+}
+
+static void msm_dp_mst_bridge_atomic_pre_enable(struct drm_bridge *drm_bridge,
+						struct drm_atomic_state *state)
+{
+	int rc = 0;
+	struct msm_dp_mst_bridge *bridge;
+	struct msm_dp *dp_display;
+	struct msm_dp_mst_bridge_state *mst_bridge_state;
+	struct msm_dp_mst *dp_mst;
+	struct msm_dp_panel   *msm_dp_panel;
+
+	if (!drm_bridge) {
+		DRM_ERROR("Invalid params\n");
+		return;
+	}
+
+	bridge = to_dp_mst_bridge(drm_bridge);
+	mst_bridge_state = to_dp_mst_bridge_state(bridge);
+	dp_display = bridge->display;
+	dp_mst = dp_display->msm_dp_mst;
+
+	/* to cover cases of bridge_disable/bridge_enable without modeset */
+	bridge->connector = mst_bridge_state->connector;
+	bridge->msm_dp_panel = mst_bridge_state->msm_dp_panel;
+
+	if (!bridge->connector) {
+		DRM_ERROR("Invalid connector\n");
+		return;
+	}
+
+	msm_dp_panel = bridge->msm_dp_panel;
+	mutex_lock(&dp_mst->mst_lock);
+
+	rc = msm_dp_display_set_mode_helper(dp_display, state, drm_bridge->encoder, msm_dp_panel);
+	if (rc) {
+		DRM_ERROR("Failed to perform a mode set, rc=%d\n", rc);
+		mutex_unlock(&dp_mst->mst_lock);
+		return;
+	}
+	msm_dp_panel->pbn = drm_dp_calc_pbn_mode(msm_dp_panel->msm_dp_mode.drm_mode.clock,
+						 msm_dp_panel->msm_dp_mode.bpp << 4);
+	rc = msm_dp_display_prepare(dp_display);
+	if (rc) {
+		DRM_ERROR("[%d] DP display pre-enable failed, rc=%d\n", bridge->id, rc);
+		msm_dp_display_unprepare(dp_display);
+		mutex_unlock(&dp_mst->mst_lock);
+		return;
+	}
+
+	rc = msm_dp_mst_bridge_pre_enable_part1(bridge, state);
+	if (rc) {
+		DRM_ERROR("[%d] DP display pre-enable failed, rc=%d\n", bridge->id, rc);
+		mutex_unlock(&dp_mst->mst_lock);
+		return;
+	}
+
+	msm_dp_display_enable_helper(dp_display, bridge->msm_dp_panel);
+
+	_msm_dp_mst_bridge_pre_enable_part2(bridge, state);
+
+	mutex_unlock(&dp_mst->mst_lock);
+
+	drm_dbg_dp(dp_display->drm_dev, "conn:%d mode:%s pre enable done\n",
+		   DP_MST_CONN_ID(bridge), bridge->msm_dp_panel->msm_dp_mode.drm_mode.name);
+}
+
+static void msm_dp_mst_bridge_atomic_disable(struct drm_bridge *drm_bridge,
+					     struct drm_atomic_state *state)
+{
+	struct msm_dp_mst_bridge *bridge;
+	struct msm_dp *dp_display;
+	struct msm_dp_mst *mst;
+
+	if (!drm_bridge) {
+		DRM_ERROR("Invalid params\n");
+		return;
+	}
+
+	bridge = to_dp_mst_bridge(drm_bridge);
+	if (!bridge->connector) {
+		DRM_ERROR("Invalid connector\n");
+		return;
+	}
+
+	dp_display = bridge->display;
+	mst = dp_display->msm_dp_mst;
+
+	mutex_lock(&mst->mst_lock);
+
+	msm_dp_mst_bridge_pre_disable_part1(bridge, state);
+
+	msm_dp_display_disable_helper(dp_display, bridge->msm_dp_panel);
+
+	drm_dp_check_act_status(&mst->mst_mgr);
+
+	mutex_unlock(&mst->mst_lock);
+
+	drm_dbg_dp(dp_display->drm_dev, "MST bridge:%d disable complete\n", bridge->id);
+}
+
+static void msm_dp_mst_bridge_atomic_post_disable(struct drm_bridge *drm_bridge,
+						  struct drm_atomic_state *state)
+{
+	struct msm_dp_mst_bridge *bridge;
+	struct msm_dp *dp_display;
+	struct msm_dp_mst *mst;
+
+	if (!drm_bridge) {
+		DRM_ERROR("Invalid params\n");
+		return;
+	}
+
+	bridge = to_dp_mst_bridge(drm_bridge);
+	if (!bridge->connector) {
+		DRM_ERROR("Invalid connector\n");
+		return;
+	}
+
+	dp_display = bridge->display;
+	mst = dp_display->msm_dp_mst;
+
+	mutex_lock(&mst->mst_lock);
+
+	msm_dp_display_atomic_post_disable_helper(dp_display, bridge->msm_dp_panel);
+
+	if (!dp_display->mst_active)
+		msm_dp_display_unprepare(dp_display);
+
+	bridge->connector = NULL;
+	bridge->msm_dp_panel =  NULL;
+
+	mutex_unlock(&mst->mst_lock);
+
+	drm_dbg_dp(dp_display->drm_dev, "MST bridge:%d conn:%d post disable complete\n",
+		   bridge->id, DP_MST_CONN_ID(bridge));
+}
+
+static int msm_dp_mst_bridge_atomic_check(struct drm_bridge *drm_bridge,
+					  struct drm_bridge_state *bridge_state,
+					  struct drm_crtc_state *crtc_state,
+					  struct drm_connector_state *conn_state)
+{
+	struct drm_atomic_state *state = crtc_state->state;
+	struct drm_connector *connector = conn_state->connector;
+	struct drm_dp_mst_topology_state *mst_state;
+	struct msm_dp_mst_connector *mst_conn;
+	struct msm_dp_mst *mst;
+	int rc = 0, pbn, slots;
+	struct msm_dp_mst_bridge_state *mst_bridge_state;
+	u32 bpp;
+
+	if (!drm_atomic_crtc_needs_modeset(crtc_state) || !crtc_state->enable)
+		return 0;
+
+	mst_conn = to_dp_mst_connector(connector);
+	mst = mst_conn->dp_mst;
+
+	bpp = connector->display_info.bpc * 3;
+
+	if (!bpp)
+		bpp = 24;
+
+	pbn = drm_dp_calc_pbn_mode(crtc_state->mode.clock, bpp << 4);
+
+	mst_state = to_drm_dp_mst_topology_state(mst->mst_mgr.base.state);
+	if (IS_ERR(mst_state))
+		return PTR_ERR(mst_state);
+
+	if (!dfixed_trunc(mst_state->pbn_div)) {
+		mst_state->pbn_div =
+			drm_dp_get_vc_payload_bw(mst_conn->dp_panel->link_info.rate,
+						 mst_conn->dp_panel->link_info.num_lanes);
+	}
+
+	slots = drm_dp_atomic_find_time_slots(state, &mst->mst_mgr, mst_conn->mst_port, pbn);
+
+	drm_dbg_dp(drm_bridge->dev, "add slots, conn:%d pbn:%d slots:%d rc:%d\n",
+		   connector->base.id, pbn, slots, rc);
+
+	if (!conn_state->crtc)  {
+		mst_bridge_state = msm_dp_mst_br_priv_state(state, to_dp_mst_bridge(drm_bridge));
+		mst_bridge_state->connector = NULL;
+		mst_bridge_state->msm_dp_panel = NULL;
+	}
+
+	return 0;
+}
+
+/* DP MST Bridge APIs */
+static const struct drm_bridge_funcs msm_dp_mst_bridge_ops = {
+	.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
+	.atomic_destroy_state   = drm_atomic_helper_bridge_destroy_state,
+	.atomic_reset           = drm_atomic_helper_bridge_reset,
+	.atomic_pre_enable   = msm_dp_mst_bridge_atomic_pre_enable,
+	.atomic_disable      = msm_dp_mst_bridge_atomic_disable,
+	.atomic_post_disable = msm_dp_mst_bridge_atomic_post_disable,
+	.atomic_check        = msm_dp_mst_bridge_atomic_check,
+};
+
+int msm_dp_mst_attach_encoder(struct msm_dp *dp_display, struct drm_encoder *encoder)
+{
+	int rc = 0;
+	struct msm_dp_mst_bridge *bridge = NULL;
+	struct msm_dp_mst_bridge_state *mst_bridge_state;
+	struct drm_device *dev;
+	struct msm_dp_mst *mst = dp_display->msm_dp_mst;
+	int i;
+
+	for (i = 0; i < mst->max_streams; i++) {
+		if (!mst->mst_bridge[i]->initialized) {
+			bridge = mst->mst_bridge[i];
+			bridge->encoder = encoder;
+			bridge->initialized = true;
+			bridge->id = i;
+			break;
+		}
+	}
+
+	if (i == mst->max_streams) {
+		DRM_ERROR("MST supports only %d bridges\n", mst->max_streams);
+		rc = -EACCES;
+		goto end;
+	}
+
+	dev = dp_display->drm_dev;
+	bridge->display = dp_display;
+	bridge->base.encoder = encoder;
+	bridge->base.type = dp_display->connector_type;
+	bridge->base.ops = DRM_BRIDGE_OP_MODES;
+	drm_bridge_add(&bridge->base);
+
+	rc = drm_bridge_attach(encoder, &bridge->base, NULL, 0);
+	if (rc) {
+		DRM_ERROR("failed to attach bridge, rc=%d\n", rc);
+		goto end;
+	}
+
+	mst_bridge_state = kzalloc(sizeof(*mst_bridge_state), GFP_KERNEL);
+	if (!mst_bridge_state) {
+		rc = -ENOMEM;
+		goto end;
+	}
+
+	drm_atomic_private_obj_init(dev, &bridge->obj,
+				    &mst_bridge_state->base,
+				    &msm_dp_mst_bridge_state_funcs);
+
+	drm_dbg_dp(dp_display->drm_dev, "MST drm bridge init. bridge id:%d\n", i);
+
+	return 0;
+
+end:
+	return rc;
+}
+
 int msm_dp_mst_display_set_mgr_state(struct msm_dp *dp_display, bool state)
 {
 	struct msm_dp_mst *mst = dp_display->msm_dp_mst;
@@ -49,11 +501,16 @@ int msm_dp_mst_init(struct msm_dp *dp_display, u32 max_streams, struct drm_dp_au
 		return -ENOMEM;
 
 	memset(&msm_dp_mst->mst_mgr, 0, sizeof(msm_dp_mst->mst_mgr));
-
 	conn_base_id = dp_display->connector->base.id;
 	msm_dp_mst->msm_dp = dp_display;
 	msm_dp_mst->max_streams = max_streams;
 
+	for (int i = 0; i < DP_STREAM_MAX; i++) {
+		msm_dp_mst->mst_bridge[i] =
+			devm_drm_bridge_alloc(dev->dev, struct msm_dp_mst_bridge, base,
+					      &msm_dp_mst_bridge_ops);
+	}
+
 	msm_dp_mst->dp_aux = drm_aux;
 
 	ret = drm_dp_mst_topology_mgr_init(&msm_dp_mst->mst_mgr, dev,
diff --git a/drivers/gpu/drm/msm/msm_drv.h b/drivers/gpu/drm/msm/msm_drv.h
index 5f73e0aa1c2f..03bedd15fe02 100644
--- a/drivers/gpu/drm/msm/msm_drv.h
+++ b/drivers/gpu/drm/msm/msm_drv.h
@@ -364,6 +364,8 @@ bool msm_dp_needs_periph_flush(const struct msm_dp *dp_display,
 bool msm_dp_wide_bus_available(const struct msm_dp *dp_display);
 int msm_dp_get_mst_max_stream(struct msm_dp *dp_display);
 int msm_dp_mst_register(struct msm_dp *dp_display);
+int msm_dp_mst_attach_encoder(struct msm_dp *dp_display, struct drm_encoder *encoder);
+
 #else
 static inline int __init msm_dp_register(void)
 {
@@ -390,6 +392,11 @@ static inline int msm_dp_mst_register(struct msm_dp *dp_display)
 	return -EINVAL;
 }
 
+static inline int msm_dp_mst_attach_encoder(struct msm_dp *dp_display, struct drm_encoder *encoder)
+{
+	return -EINVAL;
+}
+
 static inline void msm_dp_snapshot(struct msm_disp_state *disp_state, struct msm_dp *dp_display)
 {
 }

-- 
2.43.0


^ permalink raw reply	[flat|nested] 128+ messages in thread

* [PATCH v4 35/39] drm/msm/dp: wire MST helpers into atomic check and commit paths
  2026-04-10  9:33 [PATCH v4 00/39] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
                   ` (33 preceding siblings ...)
  2026-04-10  9:34 ` [PATCH v4 34/39] drm/msm/dp: add dp_mst_drm to manage DP MST bridge operations Yongxing Mou
@ 2026-04-10  9:34 ` Yongxing Mou
  2026-04-11 23:55   ` Dmitry Baryshkov
  2026-04-10  9:34 ` [PATCH v4 36/39] drm/msm/dp: add connector abstraction for DP MST Yongxing Mou
                   ` (5 subsequent siblings)
  40 siblings, 1 reply; 128+ messages in thread
From: Yongxing Mou @ 2026-04-10  9:34 UTC (permalink / raw)
  To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang
  Cc: linux-arm-msm, dri-devel, freedreno, linux-kernel, Yongxing Mou,
	Abhinav Kumar, Dmitry Baryshkov

Call drm_dp_mst_atomic_check() from msm_atomic_check() so MST-specific
state, such as connector and topology changes, is validated as part of
the atomic check.

Hook the MST helpers into atomic_commit_setup() and
atomic_commit_tail() to support non-blocking atomic commits for
DisplayPort MST, and ensure MST commits properly wait for dependencies.

For SST, non-blocking commits are already handled via commit_tail(),
which waits for dependencies in the DRM core.

Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
---
 drivers/gpu/drm/msm/msm_atomic.c | 9 ++++++++-
 drivers/gpu/drm/msm/msm_kms.c    | 2 ++
 2 files changed, 10 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/msm/msm_atomic.c b/drivers/gpu/drm/msm/msm_atomic.c
index 87a91148a731..ea064aa6d8fc 100644
--- a/drivers/gpu/drm/msm/msm_atomic.c
+++ b/drivers/gpu/drm/msm/msm_atomic.c
@@ -4,6 +4,7 @@
  * Author: Rob Clark <robdclark@gmail.com>
  */
 
+#include <drm/display/drm_dp_mst_helper.h>
 #include <drm/drm_atomic_uapi.h>
 #include <drm/drm_vblank.h>
 
@@ -207,7 +208,11 @@ int msm_atomic_check(struct drm_device *dev, struct drm_atomic_state *state)
 	if (ret)
 		return ret;
 
-	return drm_atomic_helper_check(dev, state);
+	ret = drm_atomic_helper_check(dev, state);
+	if (ret)
+		return ret;
+
+	return drm_dp_mst_atomic_check(state);
 }
 
 void msm_atomic_commit_tail(struct drm_atomic_state *state)
@@ -221,6 +226,8 @@ void msm_atomic_commit_tail(struct drm_atomic_state *state)
 
 	trace_msm_atomic_commit_tail_start(async, crtc_mask);
 
+	drm_dp_mst_atomic_wait_for_dependencies(state);
+
 	kms->funcs->enable_commit(kms);
 
 	/*
diff --git a/drivers/gpu/drm/msm/msm_kms.c b/drivers/gpu/drm/msm/msm_kms.c
index e5d0ea629448..a8f5fbb3239d 100644
--- a/drivers/gpu/drm/msm/msm_kms.c
+++ b/drivers/gpu/drm/msm/msm_kms.c
@@ -10,6 +10,7 @@
 #include <linux/sched/mm.h>
 #include <uapi/linux/sched/types.h>
 
+#include <drm/display/drm_dp_mst_helper.h>
 #include <drm/drm_drv.h>
 #include <drm/drm_mode_config.h>
 #include <drm/drm_vblank.h>
@@ -29,6 +30,7 @@ static const struct drm_mode_config_funcs mode_config_funcs = {
 
 static const struct drm_mode_config_helper_funcs mode_config_helper_funcs = {
 	.atomic_commit_tail = msm_atomic_commit_tail,
+	.atomic_commit_setup = drm_dp_mst_atomic_setup_commit,
 };
 
 static irqreturn_t msm_irq(int irq, void *arg)

-- 
2.43.0


^ permalink raw reply	[flat|nested] 128+ messages in thread

* [PATCH v4 36/39] drm/msm/dp: add connector abstraction for DP MST
  2026-04-10  9:33 [PATCH v4 00/39] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
                   ` (34 preceding siblings ...)
  2026-04-10  9:34 ` [PATCH v4 35/39] drm/msm/dp: wire MST helpers into atomic check and commit paths Yongxing Mou
@ 2026-04-10  9:34 ` Yongxing Mou
  2026-04-11 23:30   ` Dmitry Baryshkov
  2026-04-10  9:34 ` [PATCH v4 37/39] drm/msm/dp: add HPD callback for dp MST Yongxing Mou
                   ` (4 subsequent siblings)
  40 siblings, 1 reply; 128+ messages in thread
From: Yongxing Mou @ 2026-04-10  9:34 UTC (permalink / raw)
  To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang
  Cc: linux-arm-msm, dri-devel, freedreno, linux-kernel, Yongxing Mou,
	Abhinav Kumar

From: Abhinav Kumar <quic_abhinavk@quicinc.com>

Introduce an MST connector abstraction for DP MST, with each MST
connector associated with a DP panel and connected through a DRM bridge
to an MST encoder.

The connector is only used for MST helper callbacks, such as detect,
get_modes, and get_encoder. Display enable/disable, hotplug handling,
and modeset sequencing continue to be handled by the bridge path.

Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
---
 drivers/gpu/drm/msm/dp/dp_mst_drm.c | 231 ++++++++++++++++++++++++++++++++++++
 1 file changed, 231 insertions(+)

diff --git a/drivers/gpu/drm/msm/dp/dp_mst_drm.c b/drivers/gpu/drm/msm/dp/dp_mst_drm.c
index 4df3ea5e36d0..bb3898b1f6b1 100644
--- a/drivers/gpu/drm/msm/dp/dp_mst_drm.c
+++ b/drivers/gpu/drm/msm/dp/dp_mst_drm.c
@@ -7,6 +7,7 @@
 #include <drm/drm_managed.h>
 #include <drm/drm_bridge.h>
 #include <drm/display/drm_dp_mst_helper.h>
+#include <linux/pm_runtime.h>
 
 #include "dp_mst_drm.h"
 #include "dp_panel.h"
@@ -489,6 +490,235 @@ int msm_dp_mst_display_set_mgr_state(struct msm_dp *dp_display, bool state)
 	return rc;
 }
 
+/* DP MST Connector OPs */
+static int
+msm_dp_mst_connector_detect(struct drm_connector *connector,
+			    struct drm_modeset_acquire_ctx *ctx,
+			    bool force)
+{
+	struct msm_dp_mst_connector *mst_conn = to_dp_mst_connector(connector);
+	struct msm_dp_mst *mst = mst_conn->dp_mst;
+	struct msm_dp *dp_display = mst->msm_dp;
+	struct device *dev = dp_display->drm_dev->dev;
+	enum drm_connector_status status = connector_status_disconnected;
+	int ret;
+
+	ret = pm_runtime_resume_and_get(dev);
+	if (ret < 0)
+		return status;
+
+	if (dp_display->mst_active)
+		status = drm_dp_mst_detect_port(connector,
+						ctx, &mst->mst_mgr, mst_conn->mst_port);
+
+	pm_runtime_put_autosuspend(dev);
+
+	return status;
+}
+
+static int msm_dp_mst_connector_get_modes(struct drm_connector *connector)
+{
+	struct msm_dp_mst_connector *mst_conn = to_dp_mst_connector(connector);
+	struct msm_dp_mst *mst = mst_conn->dp_mst;
+	const struct drm_edid *drm_edid;
+
+	drm_edid = drm_dp_mst_edid_read(connector, &mst->mst_mgr, mst_conn->mst_port);
+	drm_edid_connector_update(connector, drm_edid);
+
+	return drm_edid_connector_add_modes(connector);
+}
+
+static enum drm_mode_status msm_dp_mst_connector_mode_valid(struct drm_connector *connector,
+							    const struct drm_display_mode *mode)
+{
+	struct msm_dp_mst_connector *mst_conn;
+	struct drm_dp_mst_port *mst_port;
+	struct msm_dp *dp_display;
+	int required_pbn;
+
+	if (drm_connector_is_unregistered(connector))
+		return 0;
+
+	mst_conn = to_dp_mst_connector(connector);
+	mst_port = mst_conn->mst_port;
+	dp_display = mst_conn->dp_mst->msm_dp;
+
+	if (!mst_port)
+		return MODE_ERROR;
+
+	required_pbn = drm_dp_calc_pbn_mode(mode->clock, (6 * 3) << 4);
+
+	if (required_pbn > mst_port->full_pbn) {
+		drm_dbg_dp(dp_display->drm_dev, "mode:%s not supported.\n", mode->name);
+		return MODE_CLOCK_HIGH;
+	}
+
+	return msm_dp_display_mode_valid(dp_display, &connector->display_info, mode);
+}
+
+static struct drm_encoder *
+msm_dp_mst_atomic_best_encoder(struct drm_connector *connector, struct drm_atomic_state *state)
+{
+	struct msm_dp_mst_connector *mst_conn = to_dp_mst_connector(connector);
+	struct msm_dp_mst *mst = mst_conn->dp_mst;
+	struct msm_dp *dp_display = mst->msm_dp;
+	struct drm_encoder *enc = NULL;
+	struct msm_dp_mst_bridge_state *mst_bridge_state;
+	u32 i;
+	struct drm_connector_state *conn_state = drm_atomic_get_new_connector_state(state,
+										    connector);
+
+	if (conn_state && conn_state->best_encoder)
+		return conn_state->best_encoder;
+
+	for (i = 0; i < mst->max_streams; i++) {
+		mst_bridge_state = msm_dp_mst_br_priv_state(state, mst->mst_bridge[i]);
+		if (IS_ERR(mst_bridge_state))
+			goto end;
+
+		if (mst_bridge_state->connector == connector) {
+			enc = mst->mst_bridge[i]->encoder;
+			goto end;
+		}
+	}
+
+	for (i = 0; i < mst->max_streams; i++) {
+		mst_bridge_state = msm_dp_mst_br_priv_state(state, mst->mst_bridge[i]);
+
+		if (!mst_bridge_state->connector) {
+			mst_bridge_state->connector = connector;
+			mst_bridge_state->msm_dp_panel = mst_conn->dp_panel;
+			enc = mst->mst_bridge[i]->encoder;
+			break;
+		}
+	}
+
+end:
+	if (enc)
+		drm_dbg_dp(dp_display->drm_dev, "MST connector:%d atomic best encoder:%d\n",
+			   connector->base.id, i);
+	else
+		drm_dbg_dp(dp_display->drm_dev, "MST connector:%d atomic best encoder failed\n",
+			   connector->base.id);
+
+	return enc;
+}
+
+static int msm_dp_mst_connector_atomic_check(struct drm_connector *connector,
+					     struct drm_atomic_state *state)
+{
+	struct msm_dp_mst_connector *mst_conn = to_dp_mst_connector(connector);
+	struct msm_dp_mst *mst = mst_conn->dp_mst;
+
+	return drm_dp_atomic_release_time_slots(state, &mst->mst_mgr, mst_conn->mst_port);
+}
+
+static void dp_mst_connector_destroy(struct drm_connector *connector)
+{
+	struct msm_dp_mst_connector *mst_conn = to_dp_mst_connector(connector);
+
+	drm_connector_cleanup(connector);
+	drm_dp_mst_put_port_malloc(mst_conn->mst_port);
+	kfree(mst_conn);
+}
+
+/* DRM MST callbacks */
+static const struct drm_connector_helper_funcs msm_dp_drm_mst_connector_helper_funcs = {
+	.get_modes =    msm_dp_mst_connector_get_modes,
+	.detect_ctx =   msm_dp_mst_connector_detect,
+	.mode_valid =   msm_dp_mst_connector_mode_valid,
+	.atomic_best_encoder = msm_dp_mst_atomic_best_encoder,
+	.atomic_check = msm_dp_mst_connector_atomic_check,
+};
+
+static const struct drm_connector_funcs msm_dp_drm_mst_connector_funcs = {
+	.reset = drm_atomic_helper_connector_reset,
+	.destroy = dp_mst_connector_destroy,
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static struct drm_connector *
+msm_dp_mst_add_connector(struct drm_dp_mst_topology_mgr *mgr,
+			 struct drm_dp_mst_port *port, const char *pathprop)
+{
+	struct msm_dp_mst *dp_mst;
+	struct drm_device *dev;
+	struct msm_dp *dp_display;
+	struct msm_dp_mst_connector *mst_conn;
+	struct drm_connector *connector;
+	int rc, i;
+
+	dp_mst = container_of(mgr, struct msm_dp_mst, mst_mgr);
+
+	dp_display = dp_mst->msm_dp;
+	dev = dp_display->drm_dev;
+
+	mst_conn = kzalloc_obj(*mst_conn);
+
+	if (!mst_conn)
+		return NULL;
+
+	drm_modeset_lock_all(dev);
+
+	connector = &mst_conn->connector;
+	rc = drm_connector_dynamic_init(dev, connector,
+					&msm_dp_drm_mst_connector_funcs,
+					DRM_MODE_CONNECTOR_DisplayPort, NULL);
+	if (rc) {
+		kfree(mst_conn);
+		drm_modeset_unlock_all(dev);
+		return NULL;
+	}
+
+	mst_conn->dp_panel = msm_dp_display_get_panel(dp_display);
+	if (!mst_conn->dp_panel) {
+		DRM_ERROR("failed to get dp_panel for connector\n");
+		kfree(mst_conn);
+		drm_modeset_unlock_all(dev);
+		return NULL;
+	}
+
+	mst_conn->dp_panel->connector = connector;
+	mst_conn->dp_mst = dp_mst;
+
+	drm_connector_helper_add(connector, &msm_dp_drm_mst_connector_helper_funcs);
+
+	if (connector->funcs->reset)
+		connector->funcs->reset(connector);
+
+	/* add all encoders as possible encoders */
+	for (i = 0; i < dp_mst->max_streams; i++) {
+		rc = drm_connector_attach_encoder(connector, dp_mst->mst_bridge[i]->encoder);
+
+		if (rc) {
+			DRM_ERROR("failed to attach encoder to connector, %d\n", rc);
+			kfree(mst_conn);
+			drm_modeset_unlock_all(dev);
+			return NULL;
+		}
+	}
+
+	mst_conn->mst_port = port;
+	drm_dp_mst_get_port_malloc(mst_conn->mst_port);
+
+	drm_object_attach_property(&connector->base,
+				   dev->mode_config.path_property, 0);
+	drm_object_attach_property(&connector->base,
+				   dev->mode_config.tile_property, 0);
+	drm_connector_set_path_property(connector, pathprop);
+	drm_modeset_unlock_all(dev);
+
+	drm_dbg_dp(dp_display->drm_dev, "add MST connector id:%d\n", connector->base.id);
+
+	return connector;
+}
+
+static const struct drm_dp_mst_topology_cbs msm_dp_mst_drm_cbs = {
+	.add_connector = msm_dp_mst_add_connector,
+};
+
 int msm_dp_mst_init(struct msm_dp *dp_display, u32 max_streams, struct drm_dp_aux *drm_aux)
 {
 	struct drm_device *dev = dp_display->drm_dev;
@@ -501,6 +731,7 @@ int msm_dp_mst_init(struct msm_dp *dp_display, u32 max_streams, struct drm_dp_au
 		return -ENOMEM;
 
 	memset(&msm_dp_mst->mst_mgr, 0, sizeof(msm_dp_mst->mst_mgr));
+	msm_dp_mst->mst_mgr.cbs = &msm_dp_mst_drm_cbs;
 	conn_base_id = dp_display->connector->base.id;
 	msm_dp_mst->msm_dp = dp_display;
 	msm_dp_mst->max_streams = max_streams;

-- 
2.43.0


^ permalink raw reply	[flat|nested] 128+ messages in thread

* [PATCH v4 37/39] drm/msm/dp: add HPD callback for dp MST
  2026-04-10  9:33 [PATCH v4 00/39] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
                   ` (35 preceding siblings ...)
  2026-04-10  9:34 ` [PATCH v4 36/39] drm/msm/dp: add connector abstraction for DP MST Yongxing Mou
@ 2026-04-10  9:34 ` Yongxing Mou
  2026-04-11 22:00   ` Dmitry Baryshkov
  2026-04-10  9:34 ` [PATCH v4 38/39] drm/msm/dpu: use msm_dp_get_mst_intf_id() to get the intf id Yongxing Mou
                   ` (3 subsequent siblings)
  40 siblings, 1 reply; 128+ messages in thread
From: Yongxing Mou @ 2026-04-10  9:34 UTC (permalink / raw)
  To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang
  Cc: linux-arm-msm, dri-devel, freedreno, linux-kernel, Yongxing Mou,
	Abhinav Kumar

From: Abhinav Kumar <quic_abhinavk@quicinc.com>

Add HPD callback for the MST module which shall be invoked from the
dp_display's HPD handler to perform MST specific operations in case
of HPD. In MST case, route the HPD messages to MST module.

Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
---
 drivers/gpu/drm/msm/dp/dp_display.c | 23 +++++++++++++++++++----
 drivers/gpu/drm/msm/dp/dp_mst_drm.c | 34 ++++++++++++++++++++++++++++++++++
 drivers/gpu/drm/msm/dp/dp_mst_drm.h |  1 +
 3 files changed, 54 insertions(+), 4 deletions(-)

diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
index 919767945ba5..ca89e20b7563 100644
--- a/drivers/gpu/drm/msm/dp/dp_display.c
+++ b/drivers/gpu/drm/msm/dp/dp_display.c
@@ -454,6 +454,9 @@ static int msm_dp_hpd_plug_handle(struct msm_dp_display_private *dp)
 			dp->msm_dp_display.connector_type,
 			dp->link->sink_count);
 
+	if (dp->plugged)
+		return 0;
+
 	mutex_lock(&dp->plugged_lock);
 
 	ret = pm_runtime_resume_and_get(&pdev->dev);
@@ -556,12 +559,19 @@ static int msm_dp_irq_hpd_handle(struct msm_dp_display_private *dp)
 {
 	u32 sink_request;
 	int rc = 0;
+	struct msm_dp *msm_dp_display = &dp->msm_dp_display;
 
 	/* irq_hpd can happen at either connected or disconnected state */
 	drm_dbg_dp(dp->drm_dev, "Before, type=%d, sink_count=%d\n",
 			dp->msm_dp_display.connector_type,
 			dp->link->sink_count);
 
+	if (msm_dp_display->mst_active) {
+		if (msm_dp_aux_is_link_connected(dp->aux) != ISR_DISCONNECTED)
+			msm_dp_mst_display_hpd_irq(&dp->msm_dp_display);
+		return 0;
+	}
+
 	/* check for any test request issued by sink */
 	rc = msm_dp_link_process_request(dp->link);
 	if (!rc) {
@@ -1125,9 +1135,13 @@ static irqreturn_t msm_dp_display_irq_thread(int irq, void *dev_id)
 				      connector_status_connected);
 
 	/* Send HPD as connected and distinguish it in the notifier */
-	if (hpd_isr_status & DP_DP_IRQ_HPD_INT_MASK)
-		drm_bridge_hpd_notify(dp->msm_dp_display.bridge,
-				      connector_status_connected);
+	if (hpd_isr_status & DP_DP_IRQ_HPD_INT_MASK) {
+		if (dp->msm_dp_display.mst_active)
+			msm_dp_irq_hpd_handle(dp);
+		else
+			drm_bridge_hpd_notify(dp->msm_dp_display.bridge,
+					      connector_status_connected);
+	}
 
 	ret = IRQ_HANDLED;
 
@@ -1793,7 +1807,8 @@ void msm_dp_bridge_hpd_notify(struct drm_bridge *bridge,
 			msm_dp_hpd_plug_handle(dp);
 		}
 	} else {
-		msm_dp_hpd_unplug_handle(dp);
+		if (hpd_link_status == ISR_DISCONNECTED)
+			msm_dp_hpd_unplug_handle(dp);
 	}
 
 	pm_runtime_put_sync(&msm_dp_display->pdev->dev);
diff --git a/drivers/gpu/drm/msm/dp/dp_mst_drm.c b/drivers/gpu/drm/msm/dp/dp_mst_drm.c
index bb3898b1f6b1..71d3f63973e6 100644
--- a/drivers/gpu/drm/msm/dp/dp_mst_drm.c
+++ b/drivers/gpu/drm/msm/dp/dp_mst_drm.c
@@ -490,6 +490,40 @@ int msm_dp_mst_display_set_mgr_state(struct msm_dp *dp_display, bool state)
 	return rc;
 }
 
+/* DP MST HPD IRQ callback */
+void msm_dp_mst_display_hpd_irq(struct msm_dp *dp_display)
+{
+	int rc;
+	struct msm_dp_mst *mst = dp_display->msm_dp_mst;
+	u8 ack[8] = {};
+	u8 esi[4];
+	unsigned int esi_res = DP_SINK_COUNT_ESI + 1;
+	bool handled;
+
+	rc = drm_dp_dpcd_read_data(mst->dp_aux, DP_SINK_COUNT_ESI, esi, 4);
+	if (rc < 0) {
+		DRM_ERROR("DPCD sink status read failed, rlen=%d\n", rc);
+		return;
+	}
+
+	drm_dbg_dp(dp_display->drm_dev, "MST irq: esi1[0x%x] esi2[0x%x] esi3[%x]\n",
+		   esi[1], esi[2], esi[3]);
+
+	rc = drm_dp_mst_hpd_irq_handle_event(&mst->mst_mgr, esi, ack, &handled);
+
+	/* ack the request */
+	if (handled) {
+		rc = drm_dp_dpcd_write_byte(mst->dp_aux, esi_res, ack[1]);
+		if (rc < 0) {
+			DRM_ERROR("DPCD esi_res failed. rc=%d\n", rc);
+			return;
+		}
+
+		drm_dp_mst_hpd_irq_send_new_request(&mst->mst_mgr);
+	}
+	drm_dbg_dp(dp_display->drm_dev, "MST display hpd_irq handled:%d rc:%d\n", handled, rc);
+}
+
 /* DP MST Connector OPs */
 static int
 msm_dp_mst_connector_detect(struct drm_connector *connector,
diff --git a/drivers/gpu/drm/msm/dp/dp_mst_drm.h b/drivers/gpu/drm/msm/dp/dp_mst_drm.h
index 5d411529f681..08e145399cfc 100644
--- a/drivers/gpu/drm/msm/dp/dp_mst_drm.h
+++ b/drivers/gpu/drm/msm/dp/dp_mst_drm.h
@@ -9,5 +9,6 @@
 
 int msm_dp_mst_init(struct msm_dp *dp_display, u32 max_streams, struct drm_dp_aux *drm_aux);
 int msm_dp_mst_display_set_mgr_state(struct msm_dp *dp_display, bool state);
+void msm_dp_mst_display_hpd_irq(struct msm_dp *dp_display);
 
 #endif /* _DP_MST_DRM_H_ */

-- 
2.43.0


^ permalink raw reply	[flat|nested] 128+ messages in thread

* [PATCH v4 38/39] drm/msm/dpu: use msm_dp_get_mst_intf_id() to get the intf id
  2026-04-10  9:33 [PATCH v4 00/39] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
                   ` (36 preceding siblings ...)
  2026-04-10  9:34 ` [PATCH v4 37/39] drm/msm/dp: add HPD callback for dp MST Yongxing Mou
@ 2026-04-10  9:34 ` Yongxing Mou
  2026-04-10  9:34 ` [PATCH v4 39/39] drm/msm/dp: Add MST stream support for supported DP controllers Yongxing Mou
                   ` (2 subsequent siblings)
  40 siblings, 0 replies; 128+ messages in thread
From: Yongxing Mou @ 2026-04-10  9:34 UTC (permalink / raw)
  To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang
  Cc: linux-arm-msm, dri-devel, freedreno, linux-kernel, Yongxing Mou,
	Abhinav Kumar

From: Abhinav Kumar <quic_abhinavk@quicinc.com>

Use msm_dp_get_mst_intf_id() to get the interface ID for the DP MST
controller as the intf_id is unique for each MST stream of each DP
controller.

For DSI/eDP/DP SST, the stream_id is always 0, so existing behavior
remains unchanged.

Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
---
 drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c | 20 +++++++++++---------
 drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.h |  2 ++
 drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c     |  4 ++++
 3 files changed, 17 insertions(+), 9 deletions(-)

diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c
index eba1d52211f6..d6813107a27d 100644
--- a/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c
+++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c
@@ -1438,18 +1438,21 @@ static void dpu_encoder_virt_atomic_disable(struct drm_encoder *drm_enc,
 
 static struct dpu_hw_intf *dpu_encoder_get_intf(const struct dpu_mdss_cfg *catalog,
 		struct dpu_rm *dpu_rm,
-		enum dpu_intf_type type, u32 controller_id)
+		struct msm_display_info *disp_info, u32 controller_id)
 {
-	int i = 0;
+	int i = 0, cnt = 0;
+	int stream_id = disp_info->stream_id;
 
-	if (type == INTF_WB)
+	if (disp_info->intf_type == INTF_WB)
 		return NULL;
 
+	DPU_DEBUG("intf_type 0x%x controller_id %d stream_id %d\n",
+		  disp_info->intf_type, controller_id, stream_id);
 	for (i = 0; i < catalog->intf_count; i++) {
-		if (catalog->intf[i].type == type
-		    && catalog->intf[i].controller_id == controller_id) {
-			return dpu_rm_get_intf(dpu_rm, catalog->intf[i].id);
-		}
+		if (catalog->intf[i].type == disp_info->intf_type &&
+		    controller_id == catalog->intf[i].controller_id)
+			if (cnt++ == stream_id)
+				return dpu_rm_get_intf(dpu_rm, catalog->intf[i].id);
 	}
 
 	return NULL;
@@ -2675,8 +2678,7 @@ static int dpu_encoder_setup_display(struct dpu_encoder_virt *dpu_enc,
 				i, controller_id, phys_params.split_role);
 
 		phys_params.hw_intf = dpu_encoder_get_intf(dpu_kms->catalog, &dpu_kms->rm,
-							   disp_info->intf_type,
-							   controller_id);
+							   disp_info, controller_id);
 
 		if (disp_info->intf_type == INTF_WB && controller_id < WB_MAX)
 			phys_params.hw_wb = dpu_rm_get_wb(&dpu_kms->rm, controller_id);
diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.h b/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.h
index ca1ca2e51d7e..2eb4c39b111c 100644
--- a/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.h
+++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.h
@@ -28,6 +28,7 @@
  * @h_tile_instance:    Controller instance used per tile. Number of elements is
  *                      based on num_of_h_tiles
  * @is_cmd_mode		Boolean to indicate if the CMD mode is requested
+ * @stream_id		stream id for which the interface needs to be acquired
  * @vsync_source:	Source of the TE signal for DSI CMD devices
  */
 struct msm_display_info {
@@ -35,6 +36,7 @@ struct msm_display_info {
 	uint32_t num_of_h_tiles;
 	uint32_t h_tile_instance[MAX_H_TILES_PER_DISPLAY];
 	bool is_cmd_mode;
+	int stream_id;
 	enum dpu_vsync_source vsync_source;
 };
 
diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c
index 89868443c0fe..305d4c76098d 100644
--- a/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c
+++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c
@@ -612,6 +612,7 @@ static int _dpu_kms_initialize_dsi(struct drm_device *dev,
 			info.h_tile_instance[info.num_of_h_tiles++] = other;
 
 		info.is_cmd_mode = msm_dsi_is_cmd_mode(priv->kms->dsi[i]);
+		info.stream_id = 0;
 
 		rc = dpu_kms_dsi_set_te_source(&info, priv->kms->dsi[i]);
 		if (rc) {
@@ -687,6 +688,7 @@ static int _dpu_kms_initialize_displayport(struct drm_device *dev,
 			}
 
 			for (stream_id = 0; stream_id < stream_cnt; stream_id++) {
+				info.stream_id = stream_id;
 				encoder = dpu_encoder_init(dev, DRM_MODE_ENCODER_DPMST, &info);
 				if (IS_ERR(encoder)) {
 					DPU_ERROR("encoder init failed for dp mst display\n");
@@ -720,6 +722,7 @@ static int _dpu_kms_initialize_hdmi(struct drm_device *dev,
 	info.num_of_h_tiles = 1;
 	info.h_tile_instance[0] = 0;
 	info.intf_type = INTF_HDMI;
+	info.stream_id = 0;
 
 	encoder = dpu_encoder_init(dev, DRM_MODE_ENCODER_TMDS, &info);
 	if (IS_ERR(encoder)) {
@@ -752,6 +755,7 @@ static int _dpu_kms_initialize_writeback(struct drm_device *dev,
 	/* use only WB idx 2 instance for DPU */
 	info.h_tile_instance[0] = wb_idx;
 	info.intf_type = INTF_WB;
+	info.stream_id = 0;
 
 	maxlinewidth = dpu_rm_get_wb(&dpu_kms->rm, info.h_tile_instance[0])->caps->maxlinewidth;
 

-- 
2.43.0


^ permalink raw reply	[flat|nested] 128+ messages in thread

* [PATCH v4 39/39] drm/msm/dp: Add MST stream support for supported DP controllers
  2026-04-10  9:33 [PATCH v4 00/39] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
                   ` (37 preceding siblings ...)
  2026-04-10  9:34 ` [PATCH v4 38/39] drm/msm/dpu: use msm_dp_get_mst_intf_id() to get the intf id Yongxing Mou
@ 2026-04-10  9:34 ` Yongxing Mou
  2026-04-10 10:23   ` Konrad Dybcio
  2026-04-11 19:26   ` Dmitry Baryshkov
  2026-04-10 13:53 ` [PATCH v4 00/39] drm/msm/dp: Add MST support for MSM chipsets Dmitry Baryshkov
  2026-04-12  0:34 ` Dmitry Baryshkov
  40 siblings, 2 replies; 128+ messages in thread
From: Yongxing Mou @ 2026-04-10  9:34 UTC (permalink / raw)
  To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang
  Cc: linux-arm-msm, dri-devel, freedreno, linux-kernel, Yongxing Mou

Enables MST support for MSM DP controllers that support it, allowing each
controller to handle up to two or four DisplayPort streams. All necessary
MST support code was already implemented in the previous series of patches.

Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
---
 drivers/gpu/drm/msm/dp/dp_display.c | 42 ++++++++++++++++++++++++-------------
 1 file changed, 28 insertions(+), 14 deletions(-)

diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
index ca89e20b7563..f632b4f64ccc 100644
--- a/drivers/gpu/drm/msm/dp/dp_display.c
+++ b/drivers/gpu/drm/msm/dp/dp_display.c
@@ -107,16 +107,21 @@ struct msm_dp_desc {
 };
 
 static const struct msm_dp_desc msm_dp_desc_glymur[] = {
-	{ .io_start = 0x0af54000, .id = MSM_DP_CONTROLLER_0, .wide_bus_supported = true },
-	{ .io_start = 0x0af5c000, .id = MSM_DP_CONTROLLER_1, .wide_bus_supported = true },
-	{ .io_start = 0x0af64000, .id = MSM_DP_CONTROLLER_2, .wide_bus_supported = true },
+	{ .io_start = 0x0af54000, .id = MSM_DP_CONTROLLER_0, .wide_bus_supported = true,
+	  .mst_streams = 2 },
+	{ .io_start = 0x0af5c000, .id = MSM_DP_CONTROLLER_1, .wide_bus_supported = true,
+	  .mst_streams = 2 },
+	{ .io_start = 0x0af64000, .id = MSM_DP_CONTROLLER_2, .wide_bus_supported = true,
+	  .mst_streams = 2 },
 	{ .io_start = 0x0af6c000, .id = MSM_DP_CONTROLLER_3, .wide_bus_supported = true },
 	{}
 };
 
 static const struct msm_dp_desc msm_dp_desc_sa8775p[] = {
-	{ .io_start = 0x0af54000, .id = MSM_DP_CONTROLLER_0, .wide_bus_supported = true },
-	{ .io_start = 0x0af5c000, .id = MSM_DP_CONTROLLER_1, .wide_bus_supported = true },
+	{ .io_start = 0x0af54000, .id = MSM_DP_CONTROLLER_0, .wide_bus_supported = true,
+	  .mst_streams = 4},
+	{ .io_start = 0x0af5c000, .id = MSM_DP_CONTROLLER_1, .wide_bus_supported = true,
+	  .mst_streams = 2},
 	{ .io_start = 0x22154000, .id = MSM_DP_CONTROLLER_0, .wide_bus_supported = true },
 	{ .io_start = 0x2215c000, .id = MSM_DP_CONTROLLER_1, .wide_bus_supported = true },
 	{}
@@ -133,38 +138,47 @@ static const struct msm_dp_desc msm_dp_desc_sc7180[] = {
 };
 
 static const struct msm_dp_desc msm_dp_desc_sc7280[] = {
-	{ .io_start = 0x0ae90000, .id = MSM_DP_CONTROLLER_0, .wide_bus_supported = true },
+	{ .io_start = 0x0ae90000, .id = MSM_DP_CONTROLLER_0, .wide_bus_supported = true,
+	  .mst_streams = 2 },
 	{ .io_start = 0x0aea0000, .id = MSM_DP_CONTROLLER_1, .wide_bus_supported = true },
 	{}
 };
 
 static const struct msm_dp_desc msm_dp_desc_sc8180x[] = {
-	{ .io_start = 0x0ae90000, .id = MSM_DP_CONTROLLER_0, .wide_bus_supported = true },
+	{ .io_start = 0x0ae90000, .id = MSM_DP_CONTROLLER_0, .wide_bus_supported = true,
+	  .mst_streams = 2 },
 	{ .io_start = 0x0ae98000, .id = MSM_DP_CONTROLLER_1, .wide_bus_supported = true },
 	{ .io_start = 0x0ae9a000, .id = MSM_DP_CONTROLLER_2, .wide_bus_supported = true },
 	{}
 };
 
 static const struct msm_dp_desc msm_dp_desc_sc8280xp[] = {
-	{ .io_start = 0x0ae90000, .id = MSM_DP_CONTROLLER_0, .wide_bus_supported = true },
-	{ .io_start = 0x0ae98000, .id = MSM_DP_CONTROLLER_1, .wide_bus_supported = true },
+	{ .io_start = 0x0ae90000, .id = MSM_DP_CONTROLLER_0, .wide_bus_supported = true,
+	  .mst_streams = 2},
+	{ .io_start = 0x0ae98000, .id = MSM_DP_CONTROLLER_1, .wide_bus_supported = true,
+	  .mst_streams = 2},
 	{ .io_start = 0x0ae9a000, .id = MSM_DP_CONTROLLER_2, .wide_bus_supported = true },
 	{ .io_start = 0x0aea0000, .id = MSM_DP_CONTROLLER_3, .wide_bus_supported = true },
-	{ .io_start = 0x22090000, .id = MSM_DP_CONTROLLER_0, .wide_bus_supported = true },
-	{ .io_start = 0x22098000, .id = MSM_DP_CONTROLLER_1, .wide_bus_supported = true },
+	{ .io_start = 0x22090000, .id = MSM_DP_CONTROLLER_0, .wide_bus_supported = true,
+	  .mst_streams = 2},
+	{ .io_start = 0x22098000, .id = MSM_DP_CONTROLLER_1, .wide_bus_supported = true,
+	  .mst_streams = 2},
 	{ .io_start = 0x2209a000, .id = MSM_DP_CONTROLLER_2, .wide_bus_supported = true },
 	{ .io_start = 0x220a0000, .id = MSM_DP_CONTROLLER_3, .wide_bus_supported = true },
 	{}
 };
 
 static const struct msm_dp_desc msm_dp_desc_sm8650[] = {
-	{ .io_start = 0x0af54000, .id = MSM_DP_CONTROLLER_0, .wide_bus_supported = true },
+	{ .io_start = 0x0af54000, .id = MSM_DP_CONTROLLER_0, .wide_bus_supported = true,
+	  .mst_streams = 2 },
 	{}
 };
 
 static const struct msm_dp_desc msm_dp_desc_x1e80100[] = {
-	{ .io_start = 0x0ae90000, .id = MSM_DP_CONTROLLER_0, .wide_bus_supported = true },
-	{ .io_start = 0x0ae98000, .id = MSM_DP_CONTROLLER_1, .wide_bus_supported = true },
+	{ .io_start = 0x0ae90000, .id = MSM_DP_CONTROLLER_0, .wide_bus_supported = true,
+	  .mst_streams = 2},
+	{ .io_start = 0x0ae98000, .id = MSM_DP_CONTROLLER_1, .wide_bus_supported = true,
+	  .mst_streams = 2},
 	{ .io_start = 0x0ae9a000, .id = MSM_DP_CONTROLLER_2, .wide_bus_supported = true },
 	{ .io_start = 0x0aea0000, .id = MSM_DP_CONTROLLER_3, .wide_bus_supported = true },
 	{}

-- 
2.43.0


^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 39/39] drm/msm/dp: Add MST stream support for supported DP controllers
  2026-04-10  9:34 ` [PATCH v4 39/39] drm/msm/dp: Add MST stream support for supported DP controllers Yongxing Mou
@ 2026-04-10 10:23   ` Konrad Dybcio
  2026-04-11 19:26   ` Dmitry Baryshkov
  1 sibling, 0 replies; 128+ messages in thread
From: Konrad Dybcio @ 2026-04-10 10:23 UTC (permalink / raw)
  To: Yongxing Mou, Rob Clark, Dmitry Baryshkov, Abhinav Kumar,
	Sean Paul, Marijn Suijten, David Airlie, Simona Vetter,
	Jessica Zhang
  Cc: linux-arm-msm, dri-devel, freedreno, linux-kernel

On 4/10/26 11:34 AM, Yongxing Mou wrote:
> Enables MST support for MSM DP controllers that support it, allowing each
> controller to handle up to two or four DisplayPort streams. All necessary
> MST support code was already implemented in the previous series of patches.
> 
> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
> ---
>  drivers/gpu/drm/msm/dp/dp_display.c | 42 ++++++++++++++++++++++++-------------
>  1 file changed, 28 insertions(+), 14 deletions(-)
> 
> diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
> index ca89e20b7563..f632b4f64ccc 100644
> --- a/drivers/gpu/drm/msm/dp/dp_display.c
> +++ b/drivers/gpu/drm/msm/dp/dp_display.c
> @@ -107,16 +107,21 @@ struct msm_dp_desc {
>  };
>  
>  static const struct msm_dp_desc msm_dp_desc_glymur[] = {
> -	{ .io_start = 0x0af54000, .id = MSM_DP_CONTROLLER_0, .wide_bus_supported = true },

Please reformat all the changes so that the entries have one assignment a line

> -	{ .io_start = 0x0af5c000, .id = MSM_DP_CONTROLLER_1, .wide_bus_supported = true },
> -	{ .io_start = 0x0af64000, .id = MSM_DP_CONTROLLER_2, .wide_bus_supported = true },
> +	{ .io_start = 0x0af54000, .id = MSM_DP_CONTROLLER_0, .wide_bus_supported = true,
> +	  .mst_streams = 2 },
> +	{ .io_start = 0x0af5c000, .id = MSM_DP_CONTROLLER_1, .wide_bus_supported = true,
> +	  .mst_streams = 2 },
> +	{ .io_start = 0x0af64000, .id = MSM_DP_CONTROLLER_2, .wide_bus_supported = true,
> +	  .mst_streams = 2 },
>  	{ .io_start = 0x0af6c000, .id = MSM_DP_CONTROLLER_3, .wide_bus_supported = true },
>  	{}

All 4 DPTXes on Glymur have the address spaces that suggest 4-way MST


>  };
>  
>  static const struct msm_dp_desc msm_dp_desc_sa8775p[] = {
> -	{ .io_start = 0x0af54000, .id = MSM_DP_CONTROLLER_0, .wide_bus_supported = true },
> -	{ .io_start = 0x0af5c000, .id = MSM_DP_CONTROLLER_1, .wide_bus_supported = true },
> +	{ .io_start = 0x0af54000, .id = MSM_DP_CONTROLLER_0, .wide_bus_supported = true,
> +	  .mst_streams = 4},
> +	{ .io_start = 0x0af5c000, .id = MSM_DP_CONTROLLER_1, .wide_bus_supported = true,
> +	  .mst_streams = 2},
>  	{ .io_start = 0x22154000, .id = MSM_DP_CONTROLLER_0, .wide_bus_supported = true },
>  	{ .io_start = 0x2215c000, .id = MSM_DP_CONTROLLER_1, .wide_bus_supported = true },
>  	{}

Same thing here

> @@ -133,38 +138,47 @@ static const struct msm_dp_desc msm_dp_desc_sc7180[] = {
>  };
>  
>  static const struct msm_dp_desc msm_dp_desc_sc7280[] = {
> -	{ .io_start = 0x0ae90000, .id = MSM_DP_CONTROLLER_0, .wide_bus_supported = true },
> +	{ .io_start = 0x0ae90000, .id = MSM_DP_CONTROLLER_0, .wide_bus_supported = true,
> +	  .mst_streams = 2 },
>  	{ .io_start = 0x0aea0000, .id = MSM_DP_CONTROLLER_1, .wide_bus_supported = true },
>  	{}

Interestingly the eDP host on 7280 also has the registers for MST..

Please recheck all the entries

Konrad

^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 01/39] drm/msm/dp: remove cached drm_edid from panel
  2026-04-10  9:33 ` [PATCH v4 01/39] drm/msm/dp: remove cached drm_edid from panel Yongxing Mou
@ 2026-04-10 13:52   ` Dmitry Baryshkov
  2026-05-19  7:27     ` Yongxing Mou
  0 siblings, 1 reply; 128+ messages in thread
From: Dmitry Baryshkov @ 2026-04-10 13:52 UTC (permalink / raw)
  To: Yongxing Mou
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel

On Fri, Apr 10, 2026 at 05:33:36PM +0800, Yongxing Mou wrote:
> The cached drm_edid seems unnecessary here. Use the drm_edid pointer
> directly in the plug stage instead of caching it. Remove the cached
> drm_edid and the corresponding oneliner to simplify the code.
> 
> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
> ---
>  drivers/gpu/drm/msm/dp/dp_display.c | 27 +++++++++++-------
>  drivers/gpu/drm/msm/dp/dp_panel.c   | 57 ++++---------------------------------
>  drivers/gpu/drm/msm/dp/dp_panel.h   | 13 +++------
>  3 files changed, 26 insertions(+), 71 deletions(-)
> 
> diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
> index 5c6a24ec140d..e28cc1bbb5b1 100644
> --- a/drivers/gpu/drm/msm/dp/dp_display.c
> +++ b/drivers/gpu/drm/msm/dp/dp_display.c
> @@ -268,6 +268,7 @@ static int msm_dp_display_process_hpd_high(struct msm_dp_display_private *dp)
>  	const struct drm_display_info *info = &connector->display_info;
>  	int rc = 0;
>  	u8 dpcd[DP_RECEIVER_CAP_SIZE];
> +	const struct drm_edid *drm_edid;
>  
>  	rc = drm_dp_read_dpcd_caps(dp->aux, dpcd);
>  	if (rc)
> @@ -275,10 +276,20 @@ static int msm_dp_display_process_hpd_high(struct msm_dp_display_private *dp)
>  
>  	dp->link->lttpr_count = msm_dp_display_lttpr_init(dp, dpcd);
>  
> -	rc = msm_dp_panel_read_sink_caps(dp->panel, connector);
> +	rc = msm_dp_panel_read_link_caps(dp->panel, connector);
>  	if (rc)
>  		goto end;
>  
> +	drm_edid = drm_edid_read_ddc(connector, &dp->aux->ddc);

Where is it free?

> +	drm_edid_connector_update(connector, drm_edid);
> +
> +	if (!drm_edid) {
> +		DRM_ERROR("panel edid read failed\n");
> +		/* check edid read fail is due to unplug */
> +		if (!msm_dp_aux_is_link_connected(dp->aux))

Does this work for USB-C AltMode usecase?

> +			return -ETIMEDOUT;
> +	}
> +
>  	msm_dp_link_process_request(dp->link);
>  
>  	if (!dp->msm_dp_display.is_edp)

-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 00/39] drm/msm/dp: Add MST support for MSM chipsets
  2026-04-10  9:33 [PATCH v4 00/39] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
                   ` (38 preceding siblings ...)
  2026-04-10  9:34 ` [PATCH v4 39/39] drm/msm/dp: Add MST stream support for supported DP controllers Yongxing Mou
@ 2026-04-10 13:53 ` Dmitry Baryshkov
  2026-04-12  0:34 ` Dmitry Baryshkov
  40 siblings, 0 replies; 128+ messages in thread
From: Dmitry Baryshkov @ 2026-04-10 13:53 UTC (permalink / raw)
  To: Yongxing Mou
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar

On Fri, Apr 10, 2026 at 05:33:35PM +0800, Yongxing Mou wrote:
> Add support for Multi-stream transport for MSM chipsets that allow
> a single instance of DP controller to send multiple streams.
> 
> This series has been validated on sa8775p ride platform using multiple
> MST dongles and also daisy chain method on both DP0 and DP1 upto 1080P.
> 
> With 4x4K monitors, due to lack of layer mixers that combination will not
> work but this can be supported as well after some rework on the DPU side.
> 
> In addition, SST was re-validated with all these changes to ensure there
> were no regressions.
> 
> This patch series was made on top of:
> 
> [1] : https://patchwork.freedesktop.org/series/151522/ (v5 to fix up HPD)
> 
> Overall, the patch series has been organized in the following way:
> 
> 1) First set are a couple of fixes made while debugging MST but applicable
> to SST as well so go ahead of everything else
> 2) Prepare the DP driver to get ready to handle multiple streams. This is the bulk
> of the work as current DP driver design had to be adjusted to make this happen.
> 3) Finally, new files to handle MST related operations
> 
> Note:
> Validation for this series has so far been done on the latest linux-next
> on LeMans, covering both FB console and Weston.
> 
> Broader validation, including additional Type-C DP use cases, is still
> in progress and may lead to some follow-up adjustments in the next
> revision. I wanted to post the current version first to collect early
> feedback on the overall approach.

It's fine to post the current state, thank you. But I'd definitely want
it to be regularly tested with the USB-C attachment.

> 
> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
> ---
> Changes in v4:
> - Fixed most comments from V3.
> - Rebase onto HPD refactor V5.
> - Fixed casing/formatting issues, for example: “mst”.
> - Drop .mode_set() and set_mode in .atomic_enable().
> - Rewrite commit messages that are unclear.
> - Use the same API for MST link and SST link writes.
> - Use the new drm_dp_dpcd_read_byte() / drm_dp_dpcd_write_byte() interfaces.
> - Remove some unnecessary payload fields from the MST bridge.
> - Remove some defensive NULL pointer checks.
> - Reworked the patch order to make the series easier to follow.
> - Add support for more platforms.
> - Link to v3: https://lore.kernel.org/r/20250825-msm-dp-mst-v3-0-01faacfcdedd@oss.qualcomm.com
> 
> Changes in v3: Fixed review comments from Dmitry
> - Fixed lots of comments from series V1/V2.
> - Rebased onto next-20250808.
> - Rebased onto Jessica's HPD-refactor branch.
> - Fixed formatting issues in commit messages under changes.
> - Removed unnecessary one-line wrappers.
> - Relocated MST-related .atomic_check() calls to their appropriate positions.
> - Removed the logic related to slot checking in .mode_valid().
> - Link to v2: https://lore.kernel.org/r/20250609-msm-dp-mst-v2-0-a54d8902a23d@quicinc.com
> 
> Changes in v2: Fixed review comments from Dmitry
> - Rebase on top of next-20250606
> - Add all 4 streams pixel clks support and MST2/MST3 Link clk support
> - Address the formatting issues mentioned in the review comments
> - Drop the cache of msm_dp_panel->drm_edid cached
> - Remove the one-line wrapper funtion and redundant conditional check
> - Fixed the commit messgae descriptions of some patches
> - Reordered the patches and renamed some functions and variables
> - Link to v1: https://lore.kernel.org/all/20241205-dp_mst-v1-0-f
> 8618d42a99a@quicinc.com/
> 
> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
> 
> ---
> Abhinav Kumar (26):
>       drm/msm/dp: break up dp_display_enable into two parts
>       drm/msm/dp: re-arrange dp_display_disable() into functional parts
>       drm/msm/dp: allow dp_ctrl stream APIs to use any panel passed to it
>       drm/msm/dp: split dp_ctrl_off() into stream and link parts
>       drm/msm/dp: make bridge helpers use dp_display to allow re-use
>       drm/msm/dp: separate dp_display_prepare() into its own API
>       drm/msm/dp: introduce stream_id for each DP panel
>       drm/msm/dp: introduce max_streams for DP controller MST support
>       drm/msm/dp: Add support for programming p1/p2/p3 register blocks
>       drm/msm/dp: use stream_id to change offsets in dp_catalog
>       drm/msm/dp: add support to send ACT packets for MST
>       drm/msm/dp: Add support to enable MST in mainlink control
>       drm/msm/dp: no need to update tu calculation for mst
>       drm/msm/dp: Add support for MST channel slot allocation
>       drm/msm/dp: Add support for sending VCPF packets in DP controller
>       drm/msm/dp: Always program MST_FIFO_CONSTANT_FILL for MST use cases
>       drm/msm/dp: abstract out the dp_display stream helpers to accept a panel
>       drm/msm/dp: replace power_on with active_stream_cnt for dp_display
>       drm/msm/dp: Mark the SST bridge disconnected when mst is active
>       drm/msm/dp: add an API to initialize MST on sink side
>       drm/msm/dp: add dp_display_get_panel() to initialize DP panel
>       drm/msm/dp: initialize dp_mst module for each DP MST controller
>       drm/msm/dp: add dp_mst_drm to manage DP MST bridge operations
>       drm/msm/dp: add connector abstraction for DP MST
>       drm/msm/dp: add HPD callback for dp MST
>       drm/msm/dpu: use msm_dp_get_mst_intf_id() to get the intf id
> 
> Yongxing Mou (13):
>       drm/msm/dp: remove cached drm_edid from panel
>       drm/msm/dp: drop deprecated .mode_set() and use .atomic_enable
>       drm/msm/dp: splite msm_dp_ctrl_config_ctrl() into link parts and stream parts
>       drm/msm/dp: extract MISC1_MISC0 configuration into a separate function
>       drm/msm/dp: split link setup from source params
>       drm/msm/dp: move the pixel clock control to its own API
>       drm/msm/dp: Add catalog support for 3rd/4th stream MST
>       drm/msm/dp: simplify link and clock disable sequence
>       drm/msm/dp: pass panel to display enable/disable helpers
>       drm/msm/dp: add prepared to manage link-level operations
>       drm/msm/dpu: initialize encoders per stream for DP MST
>       drm/msm/dp: wire MST helpers into atomic check and commit paths
>       drm/msm/dp: Add MST stream support for supported DP controllers
> 
>  drivers/gpu/drm/msm/Makefile                |   3 +-
>  drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c |  20 +-
>  drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.h |   2 +
>  drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c     |  30 +-
>  drivers/gpu/drm/msm/dp/dp_audio.c           |   2 +-
>  drivers/gpu/drm/msm/dp/dp_ctrl.c            | 822 +++++++++++++++++++++-------
>  drivers/gpu/drm/msm/dp/dp_ctrl.h            |  24 +-
>  drivers/gpu/drm/msm/dp/dp_display.c         | 598 ++++++++++++++------
>  drivers/gpu/drm/msm/dp/dp_display.h         |  31 +-
>  drivers/gpu/drm/msm/dp/dp_drm.c             |  43 +-
>  drivers/gpu/drm/msm/dp/dp_drm.h             |  12 -
>  drivers/gpu/drm/msm/dp/dp_mst_drm.c         | 795 +++++++++++++++++++++++++++
>  drivers/gpu/drm/msm/dp/dp_mst_drm.h         |  14 +
>  drivers/gpu/drm/msm/dp/dp_panel.c           | 315 +++++++----
>  drivers/gpu/drm/msm/dp/dp_panel.h           |  32 +-
>  drivers/gpu/drm/msm/dp/dp_reg.h             |  48 +-
>  drivers/gpu/drm/msm/msm_atomic.c            |   9 +-
>  drivers/gpu/drm/msm/msm_drv.h               |  18 +
>  drivers/gpu/drm/msm/msm_kms.c               |   2 +
>  19 files changed, 2300 insertions(+), 520 deletions(-)
> ---
> base-commit: 3ef088b0c5772a6f75634e54aa34f5fc0a2c041c
> change-id: 20260410-msm-dp-mst-35130b6e8b84
> prerequisite-message-id: <20260305-mdss_catalog-v5-0-06678ac39ac7@oss.qualcomm.com>
> prerequisite-patch-id: 8b3f7f40025e7a10c4646435e9cfec4f9e275871
> prerequisite-patch-id: 547db98962f20218380e966b976aede824ff8433
> prerequisite-message-id: <20260314-hpd-refactor-v5-0-0c8450737d64@oss.qualcomm.com>
> prerequisite-patch-id: 1867c344ef6ead1034523ba65d2640f9ad0606cd
> prerequisite-patch-id: a58ebaf429385c622869c83e83ce7ffdfe9ea27e
> prerequisite-patch-id: d0f570e57559248ba6ca733f96fdb02af29f3055
> prerequisite-patch-id: 44d390f5319068ad534c4be698dd8dba99c0bfd1
> prerequisite-patch-id: 85438690573583cf62065f43531dbb4e221e9dd0
> prerequisite-patch-id: b37db0f0b10f16ed45ab056aa87f2275bb02df94
> prerequisite-patch-id: dcccd204ee8a979328719d41334453474bcfdf98
> prerequisite-patch-id: db07c3aa80a597c91ae2329bc677bfd72b63716a
> prerequisite-patch-id: 9778cc1ab60a0870a74d58ff220bb01011fdf1c0
> prerequisite-patch-id: 95692a605647e51bdaf8d5b2e61635a6978121f0
> 
> Best regards,
> -- 
> Yongxing Mou <yongxing.mou@oss.qualcomm.com>
> 

-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 02/39] drm/msm/dp: drop deprecated .mode_set() and use .atomic_enable
  2026-04-10  9:33 ` [PATCH v4 02/39] drm/msm/dp: drop deprecated .mode_set() and use .atomic_enable Yongxing Mou
@ 2026-04-11 17:23   ` Dmitry Baryshkov
  0 siblings, 0 replies; 128+ messages in thread
From: Dmitry Baryshkov @ 2026-04-11 17:23 UTC (permalink / raw)
  To: Yongxing Mou
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar

On Fri, Apr 10, 2026 at 05:33:37PM +0800, Yongxing Mou wrote:
> The bridge .mode_set() callback is deprecated. Remove it and move all
> mode setup logic to .atomic_enable(), where the adjusted_mode is
> available from the atomic CRTC state.
> 
> Drop msm_dp_mode from msm_dp_display_private and store the mode directly
> in the panel, as it was only used as a temporary cache. Both changes are
> limited to msm_dp_display_set_mode and are kept in a single patch.
> 
> Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
> ---
>  drivers/gpu/drm/msm/dp/dp_display.c | 81 ++++++++++++++-----------------------
>  drivers/gpu/drm/msm/dp/dp_drm.c     |  2 -
>  drivers/gpu/drm/msm/dp/dp_drm.h     |  3 --
>  3 files changed, 31 insertions(+), 55 deletions(-)
> 
> diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
> index e28cc1bbb5b1..e9f0b96c3ebd 100644
> --- a/drivers/gpu/drm/msm/dp/dp_display.c
> +++ b/drivers/gpu/drm/msm/dp/dp_display.c
> @@ -63,7 +63,6 @@ struct msm_dp_display_private {
>  	struct msm_dp_panel   *panel;
>  	struct msm_dp_ctrl    *ctrl;
>  
> -	struct msm_dp_display_mode msm_dp_mode;
>  	struct msm_dp msm_dp_display;
>  
>  	/* wait for audio signaling */
> @@ -593,16 +592,33 @@ static int msm_dp_init_sub_modules(struct msm_dp_display_private *dp)
>  }
>  
>  static int msm_dp_display_set_mode(struct msm_dp *msm_dp_display,
> -			       struct msm_dp_display_mode *mode)
> +				   const struct drm_display_mode *adjusted_mode,
> +				   struct msm_dp_panel *msm_dp_panel)
>  {
>  	struct msm_dp_display_private *dp;
> +	u32 bpp;
>  
>  	dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display);
>  
> -	drm_mode_copy(&dp->panel->msm_dp_mode.drm_mode, &mode->drm_mode);
> -	dp->panel->msm_dp_mode.bpp = mode->bpp;
> -	dp->panel->msm_dp_mode.out_fmt_is_yuv_420 = mode->out_fmt_is_yuv_420;
> -	msm_dp_panel_init_panel_info(dp->panel);
> +	drm_mode_copy(&msm_dp_panel->msm_dp_mode.drm_mode, adjusted_mode);
> +	if (msm_dp_display_check_video_test(msm_dp_display))
> +		bpp = msm_dp_display_get_test_bpp(msm_dp_display);
> +	else
> +		bpp = msm_dp_panel->connector->display_info.bpc * 3;
> +
> +	msm_dp_panel->msm_dp_mode.bpp = bpp ? bpp : 24; /* Default bpp */
> +	msm_dp_panel->msm_dp_mode.v_active_low =
> +		!!(adjusted_mode->flags & DRM_MODE_FLAG_NVSYNC);
> +	msm_dp_panel->msm_dp_mode.h_active_low =
> +		!!(adjusted_mode->flags & DRM_MODE_FLAG_NHSYNC);
> +	msm_dp_panel->msm_dp_mode.out_fmt_is_yuv_420 =
> +		drm_mode_is_420_only(&msm_dp_panel->connector->display_info, adjusted_mode) &&
> +		msm_dp_panel->vsc_sdp_supported;

We need to stop poking into the msm_dp_panel internals. would you mind
changing the code to pass the mode to msm_dp_panel_init_panel_info()? It
can be the next patch in the series.

Nevertheless,

Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>


> +	msm_dp_panel_init_panel_info(msm_dp_panel);
> +
> +	/* populate wide_bus_support to different layers */
> +	dp->ctrl->wide_bus_en =
> +		msm_dp_panel->msm_dp_mode.out_fmt_is_yuv_420 ? false : dp->wide_bus_supported;
>  	return 0;
>  }
>  
> @@ -1305,7 +1321,7 @@ bool msm_dp_wide_bus_available(const struct msm_dp *msm_dp_display)
>  
>  	dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display);
>  
> -	if (dp->msm_dp_mode.out_fmt_is_yuv_420)
> +	if (dp->panel->msm_dp_mode.out_fmt_is_yuv_420)
>  		return false;
>  
>  	return dp->wide_bus_supported;
> @@ -1361,15 +1377,19 @@ void msm_dp_bridge_atomic_enable(struct drm_bridge *drm_bridge,
>  {
>  	struct msm_dp_bridge *msm_dp_bridge = to_dp_bridge(drm_bridge);
>  	struct msm_dp *dp = msm_dp_bridge->msm_dp_display;
> +	struct drm_crtc *crtc;
> +	struct drm_crtc_state *crtc_state;
>  	int rc = 0;
>  	struct msm_dp_display_private *msm_dp_display;
>  	bool force_link_train = false;
>  
>  	msm_dp_display = container_of(dp, struct msm_dp_display_private, msm_dp_display);
> -	if (!msm_dp_display->msm_dp_mode.drm_mode.clock) {
> -		DRM_ERROR("invalid params\n");
> +
> +	crtc = drm_atomic_get_new_crtc_for_encoder(state,
> +						   drm_bridge->encoder);
> +	if (!crtc)
>  		return;
> -	}
> +	crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
>  
>  	if (dp->is_edp)
>  		msm_dp_hpd_plug_handle(msm_dp_display);
> @@ -1382,7 +1402,7 @@ void msm_dp_bridge_atomic_enable(struct drm_bridge *drm_bridge,
>  	if (msm_dp_display->link->sink_count == 0)
>  		return;
>  
> -	rc = msm_dp_display_set_mode(dp, &msm_dp_display->msm_dp_mode);
> +	rc = msm_dp_display_set_mode(dp, &crtc_state->adjusted_mode, msm_dp_display->panel);
>  	if (rc) {
>  		DRM_ERROR("Failed to perform a mode set, rc=%d\n", rc);
>  		return;
> @@ -1440,45 +1460,6 @@ void msm_dp_bridge_atomic_post_disable(struct drm_bridge *drm_bridge,
>  	pm_runtime_put_sync(&dp->pdev->dev);
>  }
>  
> -void msm_dp_bridge_mode_set(struct drm_bridge *drm_bridge,
> -			const struct drm_display_mode *mode,
> -			const struct drm_display_mode *adjusted_mode)
> -{
> -	struct msm_dp_bridge *msm_dp_bridge = to_dp_bridge(drm_bridge);
> -	struct msm_dp *dp = msm_dp_bridge->msm_dp_display;
> -	struct msm_dp_display_private *msm_dp_display;
> -	struct msm_dp_panel *msm_dp_panel;
> -
> -	msm_dp_display = container_of(dp, struct msm_dp_display_private, msm_dp_display);
> -	msm_dp_panel = msm_dp_display->panel;
> -
> -	memset(&msm_dp_display->msm_dp_mode, 0x0, sizeof(struct msm_dp_display_mode));
> -
> -	if (msm_dp_display_check_video_test(dp))
> -		msm_dp_display->msm_dp_mode.bpp = msm_dp_display_get_test_bpp(dp);
> -	else /* Default num_components per pixel = 3 */
> -		msm_dp_display->msm_dp_mode.bpp = dp->connector->display_info.bpc * 3;
> -
> -	if (!msm_dp_display->msm_dp_mode.bpp)
> -		msm_dp_display->msm_dp_mode.bpp = 24; /* Default bpp */
> -
> -	drm_mode_copy(&msm_dp_display->msm_dp_mode.drm_mode, adjusted_mode);
> -
> -	msm_dp_display->msm_dp_mode.v_active_low =
> -		!!(msm_dp_display->msm_dp_mode.drm_mode.flags & DRM_MODE_FLAG_NVSYNC);
> -
> -	msm_dp_display->msm_dp_mode.h_active_low =
> -		!!(msm_dp_display->msm_dp_mode.drm_mode.flags & DRM_MODE_FLAG_NHSYNC);
> -
> -	msm_dp_display->msm_dp_mode.out_fmt_is_yuv_420 =
> -		drm_mode_is_420_only(&dp->connector->display_info, adjusted_mode) &&
> -		msm_dp_panel->vsc_sdp_supported;
> -
> -	/* populate wide_bus_support to different layers */
> -	msm_dp_display->ctrl->wide_bus_en =
> -		msm_dp_display->msm_dp_mode.out_fmt_is_yuv_420 ? false : msm_dp_display->wide_bus_supported;
> -}
> -
>  void msm_dp_bridge_hpd_enable(struct drm_bridge *bridge)
>  {
>  	struct msm_dp_bridge *msm_dp_bridge = to_dp_bridge(bridge);
> diff --git a/drivers/gpu/drm/msm/dp/dp_drm.c b/drivers/gpu/drm/msm/dp/dp_drm.c
> index 8dc0dabd275c..af3d3e3a2d84 100644
> --- a/drivers/gpu/drm/msm/dp/dp_drm.c
> +++ b/drivers/gpu/drm/msm/dp/dp_drm.c
> @@ -56,7 +56,6 @@ static const struct drm_bridge_funcs msm_dp_bridge_ops = {
>  	.atomic_enable          = msm_dp_bridge_atomic_enable,
>  	.atomic_disable         = msm_dp_bridge_atomic_disable,
>  	.atomic_post_disable    = msm_dp_bridge_atomic_post_disable,
> -	.mode_set     = msm_dp_bridge_mode_set,
>  	.mode_valid   = msm_dp_bridge_mode_valid,
>  	.get_modes    = msm_dp_bridge_get_modes,
>  	.detect       = msm_dp_bridge_detect,
> @@ -233,7 +232,6 @@ static const struct drm_bridge_funcs msm_edp_bridge_ops = {
>  	.atomic_enable = msm_edp_bridge_atomic_enable,
>  	.atomic_disable = msm_edp_bridge_atomic_disable,
>  	.atomic_post_disable = msm_edp_bridge_atomic_post_disable,
> -	.mode_set = msm_dp_bridge_mode_set,
>  	.mode_valid = msm_edp_bridge_mode_valid,
>  	.atomic_reset = drm_atomic_helper_bridge_reset,
>  	.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
> diff --git a/drivers/gpu/drm/msm/dp/dp_drm.h b/drivers/gpu/drm/msm/dp/dp_drm.h
> index 6c0426803d78..6d4cbb9f3918 100644
> --- a/drivers/gpu/drm/msm/dp/dp_drm.h
> +++ b/drivers/gpu/drm/msm/dp/dp_drm.h
> @@ -36,9 +36,6 @@ void msm_dp_bridge_atomic_post_disable(struct drm_bridge *drm_bridge,
>  enum drm_mode_status msm_dp_bridge_mode_valid(struct drm_bridge *bridge,
>  					  const struct drm_display_info *info,
>  					  const struct drm_display_mode *mode);
> -void msm_dp_bridge_mode_set(struct drm_bridge *drm_bridge,
> -			const struct drm_display_mode *mode,
> -			const struct drm_display_mode *adjusted_mode);
>  void msm_dp_bridge_hpd_enable(struct drm_bridge *bridge);
>  void msm_dp_bridge_hpd_disable(struct drm_bridge *bridge);
>  void msm_dp_bridge_hpd_notify(struct drm_bridge *bridge,
> 
> -- 
> 2.43.0
> 

-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 03/39] drm/msm/dp: break up dp_display_enable into two parts
  2026-04-10  9:33 ` [PATCH v4 03/39] drm/msm/dp: break up dp_display_enable into two parts Yongxing Mou
@ 2026-04-11 17:33   ` Dmitry Baryshkov
  2026-04-12  0:20   ` Dmitry Baryshkov
  1 sibling, 0 replies; 128+ messages in thread
From: Dmitry Baryshkov @ 2026-04-11 17:33 UTC (permalink / raw)
  To: Yongxing Mou
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar

On Fri, Apr 10, 2026 at 05:33:38PM +0800, Yongxing Mou wrote:
> From: Abhinav Kumar <quic_abhinavk@quicinc.com>
> 
> dp_display_enable() currently re-trains the link if needed and then
> enables the pixel clock, programs the controller to start sending the
> pixel stream. Split these two parts into prepare/enable APIs, to support
> MST bridges_enable insert the MST payloads funcs between enable
> stream_clks and program register.
> 
> Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
> ---
>  drivers/gpu/drm/msm/dp/dp_ctrl.c    |  54 ++++++++++++-------
>  drivers/gpu/drm/msm/dp/dp_ctrl.h    |   3 +-
>  drivers/gpu/drm/msm/dp/dp_display.c | 105 +++++++++++++++++++++++-------------
>  3 files changed, 106 insertions(+), 56 deletions(-)
> 

Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>


-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 05/39] drm/msm/dp: splite msm_dp_ctrl_config_ctrl() into link parts and stream parts
  2026-04-10  9:33 ` [PATCH v4 05/39] drm/msm/dp: splite msm_dp_ctrl_config_ctrl() into link parts and stream parts Yongxing Mou
@ 2026-04-11 17:34   ` Dmitry Baryshkov
  2026-05-19  7:46     ` Yongxing Mou
  0 siblings, 1 reply; 128+ messages in thread
From: Dmitry Baryshkov @ 2026-04-11 17:34 UTC (permalink / raw)
  To: Yongxing Mou
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel

On Fri, Apr 10, 2026 at 05:33:40PM +0800, Yongxing Mou wrote:
> The DP_CONFIGURATION_CTRL register contains both link-level and
> stream-specific fields. Currently, msm_dp_ctrl_config_ctrl() configures
> all of them together. Separates the configuration into link parts and
> streams part for support MST.
> 
> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
> ---
>  drivers/gpu/drm/msm/dp/dp_ctrl.c | 43 ++++++++++++++++++++++++++--------------
>  1 file changed, 28 insertions(+), 15 deletions(-)
> 
> diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c
> index 476346e3ac19..85315467b5d0 100644
> --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c
> +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c
> @@ -388,26 +388,41 @@ void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl)
>  	drm_dbg_dp(ctrl->drm_dev, "mainlink off\n");
>  }
>  
> -static void msm_dp_ctrl_config_ctrl(struct msm_dp_ctrl_private *ctrl)
> +static void msm_dp_ctrl_config_ctrl_streams(struct msm_dp_ctrl_private *ctrl,
> +					    struct msm_dp_panel *msm_dp_panel)
>  {
>  	u32 config = 0, tbd;
> +
> +	config = msm_dp_read_link(ctrl, REG_DP_CONFIGURATION_CTRL);
> +
> +	if (msm_dp_panel->msm_dp_mode.out_fmt_is_yuv_420)
> +		config |= DP_CONFIGURATION_CTRL_RGB_YUV; /* YUV420 */
> +
> +	tbd = msm_dp_link_get_test_bits_depth(ctrl->link,
> +					      msm_dp_panel->msm_dp_mode.bpp);
> +
> +	config |= tbd << DP_CONFIGURATION_CTRL_BPC_SHIFT;
> +
> +	if (msm_dp_panel->psr_cap.version)
> +		config |= DP_CONFIGURATION_CTRL_SEND_VSC;
> +
> +	drm_dbg_dp(ctrl->drm_dev, "stream DP_CONFIGURATION_CTRL=0x%x\n", config);
> +
> +	msm_dp_write_link(ctrl, REG_DP_CONFIGURATION_CTRL, config);

You have an RMW cycle here. Please document what prevents it from racing
with the concurrent msm_dp_ctrl_config_ctrl_link().

> +}
> +
> +static void msm_dp_ctrl_config_ctrl_link(struct msm_dp_ctrl_private *ctrl)
> +{
> +	u32 config = 0;
>  	const u8 *dpcd = ctrl->panel->dpcd;
>  
>  	/* Default-> LSCLK DIV: 1/4 LCLK  */
>  	config |= (2 << DP_CONFIGURATION_CTRL_LSCLK_DIV_SHIFT);
>  
> -	if (ctrl->panel->msm_dp_mode.out_fmt_is_yuv_420)
> -		config |= DP_CONFIGURATION_CTRL_RGB_YUV; /* YUV420 */
> -
>  	/* Scrambler reset enable */
>  	if (drm_dp_alternate_scrambler_reset_cap(dpcd))
>  		config |= DP_CONFIGURATION_CTRL_ASSR;
>  
> -	tbd = msm_dp_link_get_test_bits_depth(ctrl->link,
> -			ctrl->panel->msm_dp_mode.bpp);
> -
> -	config |= tbd << DP_CONFIGURATION_CTRL_BPC_SHIFT;
> -
>  	/* Num of Lanes */
>  	config |= ((ctrl->link->link_params.num_lanes - 1)
>  			<< DP_CONFIGURATION_CTRL_NUM_OF_LANES_SHIFT);
> @@ -421,10 +436,7 @@ static void msm_dp_ctrl_config_ctrl(struct msm_dp_ctrl_private *ctrl)
>  	config |= DP_CONFIGURATION_CTRL_STATIC_DYNAMIC_CN;
>  	config |= DP_CONFIGURATION_CTRL_SYNC_ASYNC_CLK;
>  
> -	if (ctrl->panel->psr_cap.version)
> -		config |= DP_CONFIGURATION_CTRL_SEND_VSC;
> -
> -	drm_dbg_dp(ctrl->drm_dev, "DP_CONFIGURATION_CTRL=0x%x\n", config);
> +	drm_dbg_dp(ctrl->drm_dev, "link DP_CONFIGURATION_CTRL=0x%x\n", config);
>  
>  	msm_dp_write_link(ctrl, REG_DP_CONFIGURATION_CTRL, config);
>  }
> @@ -450,7 +462,8 @@ static void msm_dp_ctrl_configure_source_params(struct msm_dp_ctrl_private *ctrl
>  	msm_dp_ctrl_lane_mapping(ctrl);
>  	msm_dp_setup_peripheral_flush(ctrl);
>  
> -	msm_dp_ctrl_config_ctrl(ctrl);
> +	msm_dp_ctrl_config_ctrl_link(ctrl);
> +	msm_dp_ctrl_config_ctrl_streams(ctrl, ctrl->panel);
>  
>  	test_bits_depth = msm_dp_link_get_test_bits_depth(ctrl->link, ctrl->panel->msm_dp_mode.bpp);
>  	colorimetry_cfg = msm_dp_link_get_colorimetry_config(ctrl->link);
> @@ -1628,7 +1641,7 @@ static int msm_dp_ctrl_link_train(struct msm_dp_ctrl_private *ctrl,
>  	u8 assr;
>  	struct msm_dp_link_info link_info = {0};
>  
> -	msm_dp_ctrl_config_ctrl(ctrl);
> +	msm_dp_ctrl_config_ctrl_link(ctrl);
>  
>  	link_info.num_lanes = ctrl->link->link_params.num_lanes;
>  	link_info.rate = ctrl->link->link_params.rate;
> 
> -- 
> 2.43.0
> 

-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 07/39] drm/msm/dp: split link setup from source params
  2026-04-10  9:33 ` [PATCH v4 07/39] drm/msm/dp: split link setup from source params Yongxing Mou
@ 2026-04-11 17:36   ` Dmitry Baryshkov
  0 siblings, 0 replies; 128+ messages in thread
From: Dmitry Baryshkov @ 2026-04-11 17:36 UTC (permalink / raw)
  To: Yongxing Mou
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel

On Fri, Apr 10, 2026 at 05:33:42PM +0800, Yongxing Mou wrote:
> msm_dp_ctrl_configure_source_params() should only handle stream-related
> configuration. Move the link setup out of it so MST can program link and
> stream settings separately.
> 
> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
> ---
>  drivers/gpu/drm/msm/dp/dp_ctrl.c | 8 ++++----
>  1 file changed, 4 insertions(+), 4 deletions(-)
> 

Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>


-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 08/39] drm/msm/dp: allow dp_ctrl stream APIs to use any panel passed to it
  2026-04-10  9:33 ` [PATCH v4 08/39] drm/msm/dp: allow dp_ctrl stream APIs to use any panel passed to it Yongxing Mou
@ 2026-04-11 17:38   ` Dmitry Baryshkov
  2026-05-19  7:52     ` Yongxing Mou
  0 siblings, 1 reply; 128+ messages in thread
From: Dmitry Baryshkov @ 2026-04-11 17:38 UTC (permalink / raw)
  To: Yongxing Mou
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar

On Fri, Apr 10, 2026 at 05:33:43PM +0800, Yongxing Mou wrote:
> From: Abhinav Kumar <quic_abhinavk@quicinc.com>
> 
> Currently, the dp_ctrl stream APIs operate on their own dp_panel
> which is stored inside the dp_ctrl's private struct. However with MST,
> the stored panel represents the fixed link and not the sinks which
> are hotplugged. Allow the stream related APIs to work on the panel
> which is passed to them rather than the stored one. For SST cases,
> this shall continue to use the stored dp_panel.

Hmm. Why? Can't we get rid of it (in the followup patch)?

> 
> Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
> ---
>  drivers/gpu/drm/msm/dp/dp_ctrl.c    | 21 +++++++++++----------
>  drivers/gpu/drm/msm/dp/dp_ctrl.h    |  2 +-
>  drivers/gpu/drm/msm/dp/dp_display.c |  2 +-
>  3 files changed, 13 insertions(+), 12 deletions(-)
> 
> diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c
> index cd58968d4e14..d0eed8c7df45 100644
> --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c
> +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c
> @@ -477,13 +477,14 @@ static void msm_dp_ctrl_config_misc1_misc0(struct msm_dp_ctrl_private *ctrl,
>  	msm_dp_write_link(ctrl, REG_DP_MISC1_MISC0, misc_val);
>  }
>  
> -static void msm_dp_ctrl_configure_source_params(struct msm_dp_ctrl_private *ctrl)
> +static void msm_dp_ctrl_configure_source_params(struct msm_dp_ctrl_private *ctrl,
> +						struct msm_dp_panel *msm_dp_panel)
>  {
> -	msm_dp_ctrl_config_ctrl_streams(ctrl, ctrl->panel);
> +	msm_dp_ctrl_config_ctrl_streams(ctrl, msm_dp_panel);
>  
> -	msm_dp_ctrl_config_misc1_misc0(ctrl, ctrl->panel);
> +	msm_dp_ctrl_config_misc1_misc0(ctrl, msm_dp_panel);
>  
> -	msm_dp_panel_timing_cfg(ctrl->panel, ctrl->msm_dp_ctrl.wide_bus_en);
> +	msm_dp_panel_timing_cfg(msm_dp_panel, ctrl->msm_dp_ctrl.wide_bus_en);
>  }
>  
>  /*
> @@ -2523,7 +2524,7 @@ int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, bool force_li
>  	return ret;
>  }
>  
> -int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl)
> +int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *msm_dp_panel)
>  {
>  	int ret = 0;
>  	bool mainlink_ready = false;
> @@ -2536,10 +2537,10 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl)
>  
>  	ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl);
>  
> -	pixel_rate_orig = ctrl->panel->msm_dp_mode.drm_mode.clock;
> +	pixel_rate_orig = msm_dp_panel->msm_dp_mode.drm_mode.clock;
>  	pixel_rate = pixel_rate_orig;
>  
> -	if (msm_dp_ctrl->wide_bus_en || ctrl->panel->msm_dp_mode.out_fmt_is_yuv_420)
> +	if (msm_dp_ctrl->wide_bus_en || msm_dp_panel->msm_dp_mode.out_fmt_is_yuv_420)
>  		pixel_rate >>= 1;
>  
>  	drm_dbg_dp(ctrl->drm_dev, "pixel_rate=%lu\n", pixel_rate);
> @@ -2571,14 +2572,14 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl)
>  	msm_dp_setup_peripheral_flush(ctrl);
>  	msm_dp_ctrl_config_ctrl_link(ctrl);
>  
> -	msm_dp_ctrl_configure_source_params(ctrl);
> +	msm_dp_ctrl_configure_source_params(ctrl, msm_dp_panel);
>  
>  	msm_dp_ctrl_config_msa(ctrl,
>  		ctrl->link->link_params.rate,
>  		pixel_rate_orig,
> -		ctrl->panel->msm_dp_mode.out_fmt_is_yuv_420);
> +		msm_dp_panel->msm_dp_mode.out_fmt_is_yuv_420);
>  
> -	msm_dp_panel_clear_dsc_dto(ctrl->panel);
> +	msm_dp_panel_clear_dsc_dto(msm_dp_panel);
>  
>  	msm_dp_ctrl_setup_tr_unit(ctrl);
>  
> diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.h b/drivers/gpu/drm/msm/dp/dp_ctrl.h
> index 5d615f50d13b..32196e97cbe9 100644
> --- a/drivers/gpu/drm/msm/dp/dp_ctrl.h
> +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.h
> @@ -17,7 +17,7 @@ struct msm_dp_ctrl {
>  struct phy;
>  
>  int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl);
> -int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl);
> +int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *msm_dp_panel);
>  int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train);
>  void msm_dp_ctrl_off(struct msm_dp_ctrl *msm_dp_ctrl);
>  void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl);
> diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
> index 58c46d5ab4d8..1bf1335712bc 100644
> --- a/drivers/gpu/drm/msm/dp/dp_display.c
> +++ b/drivers/gpu/drm/msm/dp/dp_display.c
> @@ -666,7 +666,7 @@ static int msm_dp_display_enable(struct msm_dp_display_private *dp)
>  		return 0;
>  	}
>  
> -	rc = msm_dp_ctrl_on_stream(dp->ctrl);
> +	rc = msm_dp_ctrl_on_stream(dp->ctrl, dp->panel);
>  	if (!rc)
>  		msm_dp_display->power_on = true;
>  
> 
> -- 
> 2.43.0
> 

-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 11/39] drm/msm/dp: make bridge helpers use dp_display to allow re-use
  2026-04-10  9:33 ` [PATCH v4 11/39] drm/msm/dp: make bridge helpers use dp_display to allow re-use Yongxing Mou
@ 2026-04-11 17:41   ` Dmitry Baryshkov
  0 siblings, 0 replies; 128+ messages in thread
From: Dmitry Baryshkov @ 2026-04-11 17:41 UTC (permalink / raw)
  To: Yongxing Mou
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar

On Fri, Apr 10, 2026 at 05:33:46PM +0800, Yongxing Mou wrote:
> From: Abhinav Kumar <quic_abhinavk@quicinc.com>
> 
> dp_bridge helpers take drm_bridge as an input and extract the
> dp_display object to be used in the dp_display module. Rather than
> doing it in a roundabout way, directly pass the dp_display object
> to these helpers so that the MST bridge can also re-use the same
> helpers.
> 
> Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
> ---
>  drivers/gpu/drm/msm/dp/dp_display.c | 29 +++++++++------------------
>  drivers/gpu/drm/msm/dp/dp_display.h |  7 +++++++
>  drivers/gpu/drm/msm/dp/dp_drm.c     | 39 ++++++++++++++++++++++++++++++++++++-
>  drivers/gpu/drm/msm/dp/dp_drm.h     |  9 ---------
>  4 files changed, 54 insertions(+), 30 deletions(-)
> 

Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>


-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 12/39] drm/msm/dp: separate dp_display_prepare() into its own API
  2026-04-10  9:33 ` [PATCH v4 12/39] drm/msm/dp: separate dp_display_prepare() into its own API Yongxing Mou
@ 2026-04-11 17:42   ` Dmitry Baryshkov
  0 siblings, 0 replies; 128+ messages in thread
From: Dmitry Baryshkov @ 2026-04-11 17:42 UTC (permalink / raw)
  To: Yongxing Mou
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar

On Fri, Apr 10, 2026 at 05:33:47PM +0800, Yongxing Mou wrote:
> From: Abhinav Kumar <quic_abhinavk@quicinc.com>
> 
> For MST, the link setup should only be done once when multiple sinks are
> enabled, while stream setup may run multiple times for each sink. Split
> the link-related preparation out of msm_dp_display_atomic_enable() so it
> can be called separately before the per-stream enable path.
> 
> Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
> ---
>  drivers/gpu/drm/msm/dp/dp_display.c | 16 +++++++++++-----
>  drivers/gpu/drm/msm/dp/dp_display.h |  5 +++--
>  drivers/gpu/drm/msm/dp/dp_drm.c     |  6 ++++--
>  3 files changed, 18 insertions(+), 9 deletions(-)
> 

In the hope that the atomic_prepare() function ends up being used as the
.atomic_prepare callback...

Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>


-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 13/39] drm/msm/dp: introduce stream_id for each DP panel
  2026-04-10  9:33 ` [PATCH v4 13/39] drm/msm/dp: introduce stream_id for each DP panel Yongxing Mou
@ 2026-04-11 17:55   ` Dmitry Baryshkov
  2026-05-19  8:26     ` Yongxing Mou
  2026-04-11 18:04   ` Dmitry Baryshkov
  2026-04-12  0:04   ` Dmitry Baryshkov
  2 siblings, 1 reply; 128+ messages in thread
From: Dmitry Baryshkov @ 2026-04-11 17:55 UTC (permalink / raw)
  To: Yongxing Mou
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar

On Fri, Apr 10, 2026 at 05:33:48PM +0800, Yongxing Mou wrote:
> From: Abhinav Kumar <quic_abhinavk@quicinc.com>
> 
> With MST, each DP controller can handle multiple streams.
> There shall be one dp_panel for each stream but the dp_display
> object shall be shared among them. To represent this abstraction,
> create a stream_id for each DP panel which shall be set by the
> MST stream. For SST, default this to stream 0.
> 
> Use the stream ID to control the pixel clock of that respective
> stream by extending the clock handles and state tracking of the
> DP pixel clock to an array of max supported streams. The maximum
> streams currently is 4.

Please mention that panels are going to be dynamically assigned to
actual stream IDs.

> 
> Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
> ---
>  drivers/gpu/drm/msm/dp/dp_ctrl.c    | 57 +++++++++++++++++++++++--------------
>  drivers/gpu/drm/msm/dp/dp_ctrl.h    |  2 +-
>  drivers/gpu/drm/msm/dp/dp_display.c | 24 ++++++++++++++--
>  drivers/gpu/drm/msm/dp/dp_display.h |  2 ++
>  drivers/gpu/drm/msm/dp/dp_panel.h   | 11 +++++++
>  5 files changed, 71 insertions(+), 25 deletions(-)
> 
> diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c
> index 120ec00884e5..fb6396727628 100644
> --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c
> +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c
> @@ -127,7 +127,7 @@ struct msm_dp_ctrl_private {
>  	unsigned int num_link_clks;
>  	struct clk_bulk_data *link_clks;
>  
> -	struct clk *pixel_clk;
> +	struct clk *pixel_clk[DP_STREAM_MAX];
>  
>  	union phy_configure_opts phy_opts;
>  
> @@ -139,7 +139,7 @@ struct msm_dp_ctrl_private {
>  
>  	bool core_clks_on;
>  	bool link_clks_on;
> -	bool stream_clks_on;
> +	bool stream_clks_on[DP_STREAM_MAX];
>  };
>  
>  static inline u32 msm_dp_read_ahb(const struct msm_dp_ctrl_private *ctrl, u32 offset)
> @@ -2176,39 +2176,40 @@ static bool msm_dp_ctrl_send_phy_test_pattern(struct msm_dp_ctrl_private *ctrl)
>  	return success;
>  }
>  
> -static int msm_dp_ctrl_on_pixel_clk(struct msm_dp_ctrl_private *ctrl, unsigned long pixel_rate)
> +static int msm_dp_ctrl_on_pixel_clk(struct msm_dp_ctrl_private *ctrl, unsigned long pixel_rate,
> +				    enum msm_dp_stream_id stream_id)
>  {
>  	int ret;
>  
> -	ret = clk_set_rate(ctrl->pixel_clk, pixel_rate * 1000);
> +	ret = clk_set_rate(ctrl->pixel_clk[stream_id], pixel_rate * 1000);
>  	if (ret) {
>  		DRM_ERROR("Failed to set pixel clock rate. ret=%d\n", ret);
>  		return ret;
>  	}
>  
> -	if (ctrl->stream_clks_on) {
> +	if (ctrl->stream_clks_on[stream_id]) {
>  		drm_dbg_dp(ctrl->drm_dev, "pixel clks already enabled\n");
>  	} else {
> -		ret = clk_prepare_enable(ctrl->pixel_clk);
> +		ret = clk_prepare_enable(ctrl->pixel_clk[stream_id]);
>  		if (ret) {
>  			DRM_ERROR("Failed to start pixel clocks. ret=%d\n", ret);
>  			return ret;
>  		}
> -		ctrl->stream_clks_on = true;
> +		ctrl->stream_clks_on[stream_id] = true;
>  	}
>  
>  	return ret;
>  }
>  
> -void msm_dp_ctrl_off_pixel_clk(struct msm_dp_ctrl *msm_dp_ctrl)
> +void msm_dp_ctrl_off_pixel_clk(struct msm_dp_ctrl *msm_dp_ctrl, enum msm_dp_stream_id stream_id)
>  {
>  	struct msm_dp_ctrl_private *ctrl;
>  
>  	ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl);
>  
> -	if (ctrl->stream_clks_on) {
> -		clk_disable_unprepare(ctrl->pixel_clk);
> -		ctrl->stream_clks_on = false;
> +	if (ctrl->stream_clks_on[stream_id]) {
> +		clk_disable_unprepare(ctrl->pixel_clk[stream_id]);
> +		ctrl->stream_clks_on[stream_id] = false;
>  	}
>  }
>  
> @@ -2228,7 +2229,7 @@ static int msm_dp_ctrl_process_phy_test_request(struct msm_dp_ctrl_private *ctrl
>  	 * running. Add the global reset just before disabling the
>  	 * link clocks and core clocks.
>  	 */
> -	msm_dp_ctrl_off_pixel_clk(&ctrl->msm_dp_ctrl);
> +	msm_dp_ctrl_off_pixel_clk(&ctrl->msm_dp_ctrl, ctrl->panel->stream_id);

Why are we using ctrl->panel again here for the stream-related
functions? Didn't you got rid of its usage few patches ago?

>  	msm_dp_ctrl_off_link(&ctrl->msm_dp_ctrl);
>  
>  	ret = msm_dp_ctrl_on_link(&ctrl->msm_dp_ctrl);
> @@ -2238,7 +2239,7 @@ static int msm_dp_ctrl_process_phy_test_request(struct msm_dp_ctrl_private *ctrl
>  	}
>  
>  	pixel_rate = ctrl->panel->msm_dp_mode.drm_mode.clock;
> -	ret = msm_dp_ctrl_on_pixel_clk(ctrl, pixel_rate);
> +	ret = msm_dp_ctrl_on_pixel_clk(ctrl, pixel_rate, ctrl->panel->stream_id);

And here...

>  
>  	msm_dp_ctrl_send_phy_test_pattern(ctrl);
>  
> @@ -1451,6 +1469,8 @@ void msm_dp_display_atomic_enable(struct msm_dp *msm_dp_display)
>  
>  	dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display);
>  
> +	msm_dp_display_set_stream_info(msm_dp_display, dp->panel, 0);

DP_STREAM_0

> +
>  	rc = msm_dp_display_enable(dp);
>  	if (rc)
>  		DRM_ERROR("DP display enable failed, rc=%d\n", rc);

-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 14/39] drm/msm/dp: introduce max_streams for DP controller MST support
  2026-04-10  9:33 ` [PATCH v4 14/39] drm/msm/dp: introduce max_streams for DP controller MST support Yongxing Mou
@ 2026-04-11 17:59   ` Dmitry Baryshkov
  2026-05-21 11:34     ` Yongxing Mou
  0 siblings, 1 reply; 128+ messages in thread
From: Dmitry Baryshkov @ 2026-04-11 17:59 UTC (permalink / raw)
  To: Yongxing Mou
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar

On Fri, Apr 10, 2026 at 05:33:49PM +0800, Yongxing Mou wrote:
> From: Abhinav Kumar <quic_abhinavk@quicinc.com>
> 
> Introduce the `mst_streams` field in each DP controller descriptor to
> specify the number of supported MST streams. Most platforms support 2 or
> 4 MST streams, while platforms without MST support default to a single
> stream (`DEFAULT_STREAM_COUNT = 1`).

There is no such const anymore.

> 
> Also accounts for platforms with asymmetric stream support, e.g., DP0
> supporting 4 streams and DP1 supporting 2.

There is no need to mention it.

> 
> Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
> ---
>  drivers/gpu/drm/msm/dp/dp_ctrl.c    | 20 +++++++++++++++++---
>  drivers/gpu/drm/msm/dp/dp_ctrl.h    |  2 ++
>  drivers/gpu/drm/msm/dp/dp_display.c | 20 +++++++++++++++++++-
>  drivers/gpu/drm/msm/dp/dp_display.h |  1 +
>  4 files changed, 39 insertions(+), 4 deletions(-)
> 
> @@ -2740,7 +2741,8 @@ static int msm_dp_ctrl_clk_init(struct msm_dp_ctrl *msm_dp_ctrl)
>  	if (rc)
>  		return rc;
>  
> -	for (i = DP_STREAM_0; i < DP_STREAM_MAX; i++) {
> +	ctrl->num_pixel_clks = 0;
> +	for (i = DP_STREAM_0; i < max_stream; i++) {

Why? I think the code was fine with getting up to DP_STREAM_MAX clocks.
In fact, I'd rather use that instead of the hardcoded value for
determining how many streams can be there. Think of the old DTs which
didn't have extra stream clocks. We still need to support those.

>  		ctrl->pixel_clk[i] = devm_clk_get(dev, pixel_clks[i]);
>  
>  		if (i == 0 && IS_ERR(ctrl->pixel_clk[i]))
> @@ -2750,14 +2752,26 @@ static int msm_dp_ctrl_clk_init(struct msm_dp_ctrl *msm_dp_ctrl)
>  			DRM_DEBUG_DP("stream %d pixel clock not exist", i);
>  			break;
>  		}
> +
> +		ctrl->num_pixel_clks++;
>  	}
>  
>  	return 0;
>  }
>  
> @@ -1234,6 +1248,10 @@ static int msm_dp_display_probe(struct platform_device *pdev)
>  	dp->msm_dp_display.is_edp =
>  		(dp->msm_dp_display.connector_type == DRM_MODE_CONNECTOR_eDP);
>  	dp->hpd_isr_status = 0;
> +	dp->max_stream = 1;
> +
> +	if (desc->mst_streams > 1)
> +		dp->max_stream = desc->mst_streams;

Here you can assign it from the DT resources.

>  
>  	mutex_init(&dp->plugged_lock);
>  
> diff --git a/drivers/gpu/drm/msm/dp/dp_display.h b/drivers/gpu/drm/msm/dp/dp_display.h
> index a5c6ed5b18e4..b0cfdf215970 100644
> --- a/drivers/gpu/drm/msm/dp/dp_display.h
> +++ b/drivers/gpu/drm/msm/dp/dp_display.h
> @@ -26,6 +26,7 @@ struct msm_dp {
>  	bool psr_supported;
>  };
>  
> +int msm_dp_get_mst_max_stream(struct msm_dp *msm_dp_display);
>  int msm_dp_display_get_modes(struct msm_dp *msm_dp_display);
>  bool msm_dp_display_check_video_test(struct msm_dp *msm_dp_display);
>  int msm_dp_display_get_test_bpp(struct msm_dp *msm_dp_display);
> 
> -- 
> 2.43.0
> 

-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 13/39] drm/msm/dp: introduce stream_id for each DP panel
  2026-04-10  9:33 ` [PATCH v4 13/39] drm/msm/dp: introduce stream_id for each DP panel Yongxing Mou
  2026-04-11 17:55   ` Dmitry Baryshkov
@ 2026-04-11 18:04   ` Dmitry Baryshkov
  2026-05-20  8:54     ` Yongxing Mou
  2026-04-12  0:04   ` Dmitry Baryshkov
  2 siblings, 1 reply; 128+ messages in thread
From: Dmitry Baryshkov @ 2026-04-11 18:04 UTC (permalink / raw)
  To: Yongxing Mou
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar

On Fri, Apr 10, 2026 at 05:33:48PM +0800, Yongxing Mou wrote:
> From: Abhinav Kumar <quic_abhinavk@quicinc.com>
> 
> With MST, each DP controller can handle multiple streams.
> There shall be one dp_panel for each stream but the dp_display
> object shall be shared among them. To represent this abstraction,
> create a stream_id for each DP panel which shall be set by the
> MST stream. For SST, default this to stream 0.
> 
> Use the stream ID to control the pixel clock of that respective
> stream by extending the clock handles and state tracking of the
> DP pixel clock to an array of max supported streams. The maximum
> streams currently is 4.
> 
> Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
> ---
>  drivers/gpu/drm/msm/dp/dp_ctrl.c    | 57 +++++++++++++++++++++++--------------
>  drivers/gpu/drm/msm/dp/dp_ctrl.h    |  2 +-
>  drivers/gpu/drm/msm/dp/dp_display.c | 24 ++++++++++++++--
>  drivers/gpu/drm/msm/dp/dp_display.h |  2 ++
>  drivers/gpu/drm/msm/dp/dp_panel.h   | 11 +++++++
>  5 files changed, 71 insertions(+), 25 deletions(-)
> @@ -2735,9 +2740,17 @@ static int msm_dp_ctrl_clk_init(struct msm_dp_ctrl *msm_dp_ctrl)
>  	if (rc)
>  		return rc;
>  
> -	ctrl->pixel_clk = devm_clk_get(dev, "stream_pixel");
> -	if (IS_ERR(ctrl->pixel_clk))
> -		return PTR_ERR(ctrl->pixel_clk);
> +	for (i = DP_STREAM_0; i < DP_STREAM_MAX; i++) {
> +		ctrl->pixel_clk[i] = devm_clk_get(dev, pixel_clks[i]);
> +
> +		if (i == 0 && IS_ERR(ctrl->pixel_clk[i]))
> +			return PTR_ERR(ctrl->pixel_clk[i]);
> +
> +		if (IS_ERR(ctrl->pixel_clk[i])) {
> +			DRM_DEBUG_DP("stream %d pixel clock not exist", i);
> +			break;
> +		}

Almost missed it. If it is -EPROBE_DEFER, we must return an error. In
fact, I'd rather check for -ENOENT (or is it -ENODEV?) and pass all
other errors to the caller.

> +	}
>  
>  	return 0;
>  }

-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 15/39] drm/msm/dp: Add support for programming p1/p2/p3 register blocks
  2026-04-10  9:33 ` [PATCH v4 15/39] drm/msm/dp: Add support for programming p1/p2/p3 register blocks Yongxing Mou
@ 2026-04-11 18:07   ` Dmitry Baryshkov
  2026-05-21 11:50     ` Yongxing Mou
  0 siblings, 1 reply; 128+ messages in thread
From: Dmitry Baryshkov @ 2026-04-11 18:07 UTC (permalink / raw)
  To: Yongxing Mou
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar

On Fri, Apr 10, 2026 at 05:33:50PM +0800, Yongxing Mou wrote:
> From: Abhinav Kumar <quic_abhinavk@quicinc.com>
> 
> Add support for additional pixel register blocks (p1, p2, p3) to enable
> 4‑stream MST pixel clocks. Introduce the helper functions msm_dp_read_pn
> and msm_dp_write_pn for pixel register programming. All pixel clocks
> share the same register layout but use different base addresses.
> 
> Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
> ---
>  drivers/gpu/drm/msm/dp/dp_display.c | 40 ++++++++++++-----
>  drivers/gpu/drm/msm/dp/dp_panel.c   | 89 ++++++++++++++++++++-----------------
>  drivers/gpu/drm/msm/dp/dp_panel.h   |  3 +-
>  3 files changed, 79 insertions(+), 53 deletions(-)
> 
> diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
> index 7984a0f9e938..ff506064a3fa 100644
> --- a/drivers/gpu/drm/msm/dp/dp_display.c
> +++ b/drivers/gpu/drm/msm/dp/dp_display.c
> @@ -85,8 +85,8 @@ struct msm_dp_display_private {
>  	void __iomem *link_base;
>  	size_t link_len;
>  
> -	void __iomem *p0_base;
> -	size_t p0_len;
> +	void __iomem *pixel_base[DP_STREAM_MAX];
> +	size_t pixel_len;
>  
>  	int max_stream;
>  };
> @@ -561,7 +561,7 @@ static int msm_dp_init_sub_modules(struct msm_dp_display_private *dp)
>  		goto error_link;
>  	}
>  
> -	dp->panel = msm_dp_panel_get(dev, dp->aux, dp->link, dp->link_base, dp->p0_base);
> +	dp->panel = msm_dp_panel_get(dev, dp->aux, dp->link, dp->link_base, dp->pixel_base[0]);
>  	if (IS_ERR(dp->panel)) {
>  		rc = PTR_ERR(dp->panel);
>  		DRM_ERROR("failed to initialize panel, rc = %d\n", rc);
> @@ -769,6 +769,7 @@ int msm_dp_display_set_stream_info(struct msm_dp *msm_dp_display,
>  	}
>  
>  	panel->stream_id = stream_id;
> +	msm_dp_panel_set_pixel_base(panel, dp->pixel_base[stream_id]);

Hmmm.... Would it be better to set it up differently? Allocate one panel
per the stream from the beginning and then simply get the first
available panel when required? This would require some minimal resource
manager, but then we won't have to pass dummy register base to the panel
code. Or actually allocate a panel when it is required? Do we need a
panel before atomic_enable()?

>  
>  	return rc;
>  }
> @@ -882,8 +883,14 @@ void msm_dp_snapshot(struct msm_disp_state *disp_state, struct msm_dp *dp)
>  				    msm_dp_display->aux_base, "dp_aux");
>  	msm_disp_snapshot_add_block(disp_state, msm_dp_display->link_len,
>  				    msm_dp_display->link_base, "dp_link");
> -	msm_disp_snapshot_add_block(disp_state, msm_dp_display->p0_len,
> -				    msm_dp_display->p0_base, "dp_p0");
> +	msm_disp_snapshot_add_block(disp_state, msm_dp_display->pixel_len,
> +				    msm_dp_display->pixel_base[0], "dp_p0");
> +	msm_disp_snapshot_add_block(disp_state, msm_dp_display->pixel_len,
> +				    msm_dp_display->pixel_base[1], "dp_p1");
> +	msm_disp_snapshot_add_block(disp_state, msm_dp_display->pixel_len,
> +				    msm_dp_display->pixel_base[2], "dp_p2");
> +	msm_disp_snapshot_add_block(disp_state, msm_dp_display->pixel_len,
> +				    msm_dp_display->pixel_base[3], "dp_p3");
>  }
>  
>  void msm_dp_display_set_psr(struct msm_dp *msm_dp_display, bool enter)
> @@ -1163,6 +1170,7 @@ static void __iomem *msm_dp_ioremap(struct platform_device *pdev, int idx, size_
>  static int msm_dp_display_get_io(struct msm_dp_display_private *display)
>  {
>  	struct platform_device *pdev = display->msm_dp_display.pdev;
> +	int i;
>  
>  	display->ahb_base = msm_dp_ioremap(pdev, 0, &display->ahb_len);
>  	if (IS_ERR(display->ahb_base))
> @@ -1192,8 +1200,8 @@ static int msm_dp_display_get_io(struct msm_dp_display_private *display)
>  		display->aux_len = DP_DEFAULT_AUX_SIZE;
>  		display->link_base = display->ahb_base + DP_DEFAULT_LINK_OFFSET;
>  		display->link_len = DP_DEFAULT_LINK_SIZE;
> -		display->p0_base = display->ahb_base + DP_DEFAULT_P0_OFFSET;
> -		display->p0_len = DP_DEFAULT_P0_SIZE;
> +		display->pixel_base[0] = display->ahb_base + DP_DEFAULT_P0_OFFSET;
> +		display->pixel_len = DP_DEFAULT_P0_SIZE;
>  
>  		return 0;
>  	}
> @@ -1204,10 +1212,20 @@ static int msm_dp_display_get_io(struct msm_dp_display_private *display)
>  		return PTR_ERR(display->link_base);
>  	}
>  
> -	display->p0_base = msm_dp_ioremap(pdev, 3, &display->p0_len);
> -	if (IS_ERR(display->p0_base)) {
> -		DRM_ERROR("unable to remap p0 region: %pe\n", display->p0_base);
> -		return PTR_ERR(display->p0_base);
> +	display->pixel_base[0] = msm_dp_ioremap(pdev, 3, &display->pixel_len);
> +	if (IS_ERR(display->pixel_base[0])) {
> +		DRM_ERROR("unable to remap p0 region: %pe\n", display->pixel_base[0]);
> +		return PTR_ERR(display->pixel_base[0]);
> +	}
> +
> +	for (i = DP_STREAM_1; i < display->max_stream; i++) {
> +		/* pixels clk reg index start from 3*/
> +		display->pixel_base[i] = msm_dp_ioremap(pdev, i + 3, &display->pixel_len);
> +		if (IS_ERR(display->pixel_base[i])) {
> +			DRM_DEBUG_DP("unable to remap p%d region: %pe\n", i,
> +				     display->pixel_base[i]);
> +			break;

No, return an error.

> +		}
>  	}
>  
>  	return 0;

-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 16/39] drm/msm/dp: use stream_id to change offsets in dp_catalog
  2026-04-10  9:33 ` [PATCH v4 16/39] drm/msm/dp: use stream_id to change offsets in dp_catalog Yongxing Mou
@ 2026-04-11 18:12   ` Dmitry Baryshkov
  2026-05-25  8:06     ` Yongxing Mou
  0 siblings, 1 reply; 128+ messages in thread
From: Dmitry Baryshkov @ 2026-04-11 18:12 UTC (permalink / raw)
  To: Yongxing Mou
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar

On Fri, Apr 10, 2026 at 05:33:51PM +0800, Yongxing Mou wrote:
> From: Abhinav Kumar <quic_abhinavk@quicinc.com>
> 
> Use the dp_panel's stream_id to adjust the offsets for stream 1 which will
> be used for MST in the dp_catalog.

Please start by describing the problem.

> Stream 1 share the same link clk with
> stream 0 with different reg offset. Also add additional register defines
> for stream 1.
> 
> Streams 2 and 3 are not covered here, as they use separate link clocks and
> require separate handling.
> 
> Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
> ---
>  drivers/gpu/drm/msm/dp/dp_ctrl.c  | 24 ++++++++++---
>  drivers/gpu/drm/msm/dp/dp_panel.c | 72 +++++++++++++++++++++++++++------------
>  drivers/gpu/drm/msm/dp/dp_reg.h   | 11 ++++++
>  3 files changed, 81 insertions(+), 26 deletions(-)
> 
> diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c
> index 1e80d6fc7bda..a52bcd9ea2a3 100644
> --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c
> +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c
> @@ -393,6 +393,7 @@ static void msm_dp_ctrl_config_ctrl_streams(struct msm_dp_ctrl_private *ctrl,
>  					    struct msm_dp_panel *msm_dp_panel)
>  {
>  	u32 config = 0, tbd;
> +	u32 reg_offset = 0;
>  
>  	config = msm_dp_read_link(ctrl, REG_DP_CONFIGURATION_CTRL);
>  
> @@ -409,7 +410,8 @@ static void msm_dp_ctrl_config_ctrl_streams(struct msm_dp_ctrl_private *ctrl,
>  
>  	drm_dbg_dp(ctrl->drm_dev, "stream DP_CONFIGURATION_CTRL=0x%x\n", config);
>  
> -	msm_dp_write_link(ctrl, REG_DP_CONFIGURATION_CTRL, config);
> +	if (msm_dp_panel->stream_id == DP_STREAM_1)
> +		reg_offset = REG_DP1_CONFIGURATION_CTRL - REG_DP_CONFIGURATION_CTRL;

Where is the actual write?

>  }
>  
>  static void msm_dp_ctrl_config_ctrl_link(struct msm_dp_ctrl_private *ctrl)
> @@ -460,12 +462,16 @@ static void msm_dp_ctrl_config_misc1_misc0(struct msm_dp_ctrl_private *ctrl,
>  					   struct msm_dp_panel *msm_dp_panel)
>  {
>  	u32 colorimetry_cfg, test_bits_depth, misc_val;
> +	u32 reg_offset = 0;
>  
>  	test_bits_depth = msm_dp_link_get_test_bits_depth(ctrl->link,
>  							  msm_dp_panel->msm_dp_mode.bpp);
>  	colorimetry_cfg = msm_dp_link_get_colorimetry_config(ctrl->link);
>  
> -	misc_val = msm_dp_read_link(ctrl, REG_DP_MISC1_MISC0);
> +	if (msm_dp_panel->stream_id == DP_STREAM_1)
> +		reg_offset = REG_DP1_MISC1_MISC0 - REG_DP_MISC1_MISC0;
> +
> +	misc_val = msm_dp_read_link(ctrl, REG_DP_MISC1_MISC0 + reg_offset);

This quickly becomes unreadable. I'd rather have something like:

reg_addr = (stream == DP_STREAM_1) ? REG_DP1_MISC1_MISC0 : REG_DP_MISC1_MISC0;
misc_val = msm_dp_read_link(ctrl, reg_addr);


>  
>  	/* clear bpp bits */
>  	misc_val &= ~(0x07 << DP_MISC0_TEST_BITS_DEPTH_SHIFT);
> @@ -475,7 +481,7 @@ static void msm_dp_ctrl_config_misc1_misc0(struct msm_dp_ctrl_private *ctrl,
>  	misc_val |= DP_MISC0_SYNCHRONOUS_CLK;
>  
>  	drm_dbg_dp(ctrl->drm_dev, "misc settings = 0x%x\n", misc_val);
> -	msm_dp_write_link(ctrl, REG_DP_MISC1_MISC0, misc_val);
> +	msm_dp_write_link(ctrl, REG_DP_MISC1_MISC0 + reg_offset, misc_val);
>  }
>  
>  static void msm_dp_ctrl_configure_source_params(struct msm_dp_ctrl_private *ctrl,
> @@ -2446,6 +2452,7 @@ static int msm_dp_ctrl_link_retrain(struct msm_dp_ctrl_private *ctrl)
>  }
>  
>  static void msm_dp_ctrl_config_msa(struct msm_dp_ctrl_private *ctrl,
> +			       struct msm_dp_panel *msm_dp_panel,
>  			       u32 rate, u32 stream_rate_khz,
>  			       bool is_ycbcr_420)
>  {
> @@ -2455,6 +2462,12 @@ static void msm_dp_ctrl_config_msa(struct msm_dp_ctrl_private *ctrl,
>  	u32 const link_rate_hbr2 = 540000;
>  	u32 const link_rate_hbr3 = 810000;
>  	unsigned long den, num;
> +	u32 mvid_reg_off = 0, nvid_reg_off = 0;
> +
> +	if (msm_dp_panel->stream_id == DP_STREAM_1) {
> +		mvid_reg_off = REG_DP1_SOFTWARE_MVID - REG_DP_SOFTWARE_MVID;
> +		nvid_reg_off = REG_DP1_SOFTWARE_NVID - REG_DP_SOFTWARE_NVID;
> +	}
>  
>  	switch (rate) {
>  	case link_rate_hbr3:
> @@ -2509,8 +2522,8 @@ static void msm_dp_ctrl_config_msa(struct msm_dp_ctrl_private *ctrl,
>  		nvid *= 3;
>  
>  	drm_dbg_dp(ctrl->drm_dev, "mvid=0x%x, nvid=0x%x\n", mvid, nvid);
> -	msm_dp_write_link(ctrl, REG_DP_SOFTWARE_MVID, mvid);
> -	msm_dp_write_link(ctrl, REG_DP_SOFTWARE_NVID, nvid);
> +	msm_dp_write_link(ctrl, REG_DP_SOFTWARE_MVID + mvid_reg_off, mvid);
> +	msm_dp_write_link(ctrl, REG_DP_SOFTWARE_NVID + nvid_reg_off, nvid);
>  }
>  
>  int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train)
> @@ -2585,6 +2598,7 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *
>  	msm_dp_ctrl_configure_source_params(ctrl, msm_dp_panel);
>  
>  	msm_dp_ctrl_config_msa(ctrl,
> +		msm_dp_panel,
>  		ctrl->link->link_params.rate,
>  		pixel_rate_orig,
>  		msm_dp_panel->msm_dp_mode.out_fmt_is_yuv_420);
> diff --git a/drivers/gpu/drm/msm/dp/dp_panel.c b/drivers/gpu/drm/msm/dp/dp_panel.c
> index c17b87353d1a..6c88cc7e3037 100644
> --- a/drivers/gpu/drm/msm/dp/dp_panel.c
> +++ b/drivers/gpu/drm/msm/dp/dp_panel.c
> @@ -447,27 +447,35 @@ static void msm_dp_panel_send_vsc_sdp(struct msm_dp_panel_private *panel, struct
>  	u32 header[2];
>  	u32 val;
>  	int i;
> +	u32 offset = 0;
> +
> +	if (panel->msm_dp_panel.stream_id == DP_STREAM_1)
> +		offset = MMSS_DP1_GENERIC0_0 - MMSS_DP_GENERIC0_0;
>  
>  	msm_dp_utils_pack_sdp_header(&vsc_sdp->sdp_header, header);
>  
> -	msm_dp_write_link(panel, MMSS_DP_GENERIC0_0, header[0]);
> -	msm_dp_write_link(panel, MMSS_DP_GENERIC0_1, header[1]);
> +	msm_dp_write_link(panel, MMSS_DP_GENERIC0_0 + offset, header[0]);
> +	msm_dp_write_link(panel, MMSS_DP_GENERIC0_1 + offset, header[1]);
>  
>  	for (i = 0; i < sizeof(vsc_sdp->db); i += 4) {
>  		val = ((vsc_sdp->db[i]) | (vsc_sdp->db[i + 1] << 8) | (vsc_sdp->db[i + 2] << 16) |
>  		       (vsc_sdp->db[i + 3] << 24));
> -		msm_dp_write_link(panel, MMSS_DP_GENERIC0_2 + i, val);
> +		msm_dp_write_link(panel, MMSS_DP_GENERIC0_2 + i + offset, val);
>  	}
>  }
>  
>  static void msm_dp_panel_update_sdp(struct msm_dp_panel_private *panel)
>  {
>  	u32 hw_revision = panel->msm_dp_panel.hw_revision;
> +	u32 offset = 0;
> +
> +	if (panel->msm_dp_panel.stream_id == DP_STREAM_1)
> +		offset = MMSS_DP1_SDP_CFG3 - MMSS_DP_SDP_CFG3;
>  
>  	if (hw_revision >= DP_HW_VERSION_1_0 &&
>  	    hw_revision < DP_HW_VERSION_1_2) {
> -		msm_dp_write_link(panel, MMSS_DP_SDP_CFG3, UPDATE_SDP);
> -		msm_dp_write_link(panel, MMSS_DP_SDP_CFG3, 0x0);
> +		msm_dp_write_link(panel, MMSS_DP_SDP_CFG3 + offset, UPDATE_SDP);
> +		msm_dp_write_link(panel, MMSS_DP_SDP_CFG3 + offset, 0x0);
>  	}
>  }
>  
> @@ -476,16 +484,25 @@ void msm_dp_panel_enable_vsc_sdp(struct msm_dp_panel *msm_dp_panel, struct dp_sd
>  	struct msm_dp_panel_private *panel =
>  		container_of(msm_dp_panel, struct msm_dp_panel_private, msm_dp_panel);
>  	u32 cfg, cfg2, misc;
> +	u32 misc_reg_offset = 0;
> +	u32 sdp_cfg_offset = 0;
> +	u32 sdp_cfg2_offset = 0;
> +
> +	if (msm_dp_panel->stream_id == DP_STREAM_1) {
> +		misc_reg_offset = REG_DP1_MISC1_MISC0 - REG_DP_MISC1_MISC0;
> +		sdp_cfg_offset = MMSS_DP1_SDP_CFG - MMSS_DP_SDP_CFG;
> +		sdp_cfg2_offset = MMSS_DP1_SDP_CFG2 - MMSS_DP_SDP_CFG2;
> +	}
>  
> -	cfg = msm_dp_read_link(panel, MMSS_DP_SDP_CFG);
> -	cfg2 = msm_dp_read_link(panel, MMSS_DP_SDP_CFG2);
> -	misc = msm_dp_read_link(panel, REG_DP_MISC1_MISC0);
> +	cfg = msm_dp_read_link(panel, MMSS_DP_SDP_CFG + sdp_cfg_offset);
> +	cfg2 = msm_dp_read_link(panel, MMSS_DP_SDP_CFG2 + sdp_cfg2_offset);
> +	misc = msm_dp_read_link(panel, REG_DP_MISC1_MISC0 + misc_reg_offset);
>  
>  	cfg |= GEN0_SDP_EN;
> -	msm_dp_write_link(panel, MMSS_DP_SDP_CFG, cfg);
> +	msm_dp_write_link(panel, MMSS_DP_SDP_CFG + sdp_cfg_offset, cfg);
>  
>  	cfg2 |= GENERIC0_SDPSIZE_VALID;
> -	msm_dp_write_link(panel, MMSS_DP_SDP_CFG2, cfg2);
> +	msm_dp_write_link(panel, MMSS_DP_SDP_CFG2 + sdp_cfg2_offset, cfg2);
>  
>  	msm_dp_panel_send_vsc_sdp(panel, vsc_sdp);
>  
> @@ -495,7 +512,7 @@ void msm_dp_panel_enable_vsc_sdp(struct msm_dp_panel *msm_dp_panel, struct dp_sd
>  	drm_dbg_dp(panel->drm_dev, "vsc sdp enable=1\n");
>  
>  	pr_debug("misc settings = 0x%x\n", misc);
> -	msm_dp_write_link(panel, REG_DP_MISC1_MISC0, misc);
> +	msm_dp_write_link(panel, REG_DP_MISC1_MISC0 + misc_reg_offset, misc);
>  
>  	msm_dp_panel_update_sdp(panel);
>  }
> @@ -505,16 +522,25 @@ void msm_dp_panel_disable_vsc_sdp(struct msm_dp_panel *msm_dp_panel)
>  	struct msm_dp_panel_private *panel =
>  		container_of(msm_dp_panel, struct msm_dp_panel_private, msm_dp_panel);
>  	u32 cfg, cfg2, misc;
> +	u32 misc_reg_offset = 0;
> +	u32 sdp_cfg_offset = 0;
> +	u32 sdp_cfg2_offset = 0;
> +
> +	if (msm_dp_panel->stream_id == DP_STREAM_1) {
> +		misc_reg_offset = REG_DP1_MISC1_MISC0 - REG_DP_MISC1_MISC0;
> +		sdp_cfg_offset = MMSS_DP1_SDP_CFG - MMSS_DP_SDP_CFG;
> +		sdp_cfg2_offset = MMSS_DP1_SDP_CFG2 - MMSS_DP_SDP_CFG2;
> +	}
>  
> -	cfg = msm_dp_read_link(panel, MMSS_DP_SDP_CFG);
> -	cfg2 = msm_dp_read_link(panel, MMSS_DP_SDP_CFG2);
> -	misc = msm_dp_read_link(panel, REG_DP_MISC1_MISC0);
> +	cfg = msm_dp_read_link(panel, MMSS_DP_SDP_CFG + sdp_cfg_offset);
> +	cfg2 = msm_dp_read_link(panel, MMSS_DP_SDP_CFG2 + sdp_cfg2_offset);
> +	misc = msm_dp_read_link(panel, REG_DP_MISC1_MISC0 + misc_reg_offset);
>  
>  	cfg &= ~GEN0_SDP_EN;
> -	msm_dp_write_link(panel, MMSS_DP_SDP_CFG, cfg);
> +	msm_dp_write_link(panel, MMSS_DP_SDP_CFG + sdp_cfg_offset, cfg);
>  
>  	cfg2 &= ~GENERIC0_SDPSIZE_VALID;
> -	msm_dp_write_link(panel, MMSS_DP_SDP_CFG2, cfg2);
> +	msm_dp_write_link(panel, MMSS_DP_SDP_CFG2 + sdp_cfg2_offset, cfg2);
>  
>  	/* switch back to MSA */
>  	misc &= ~DP_MISC1_VSC_SDP;
> @@ -522,7 +548,7 @@ void msm_dp_panel_disable_vsc_sdp(struct msm_dp_panel *msm_dp_panel)
>  	drm_dbg_dp(panel->drm_dev, "vsc sdp enable=0\n");
>  
>  	pr_debug("misc settings = 0x%x\n", misc);
> -	msm_dp_write_link(panel, REG_DP_MISC1_MISC0, misc);
> +	msm_dp_write_link(panel, REG_DP_MISC1_MISC0 + misc_reg_offset, misc);
>  
>  	msm_dp_panel_update_sdp(panel);
>  }
> @@ -580,6 +606,7 @@ int msm_dp_panel_timing_cfg(struct msm_dp_panel *msm_dp_panel, bool wide_bus_en)
>  	u32 msm_dp_active;
>  	u32 total;
>  	u32 reg;
> +	u32 offset = 0;
>  
>  	panel = container_of(msm_dp_panel, struct msm_dp_panel_private, msm_dp_panel);
>  	drm_mode = &panel->msm_dp_panel.msm_dp_mode.drm_mode;
> @@ -594,6 +621,9 @@ int msm_dp_panel_timing_cfg(struct msm_dp_panel *msm_dp_panel, bool wide_bus_en)
>  		drm_mode->vsync_start - drm_mode->vdisplay,
>  		drm_mode->vsync_end - drm_mode->vsync_start);
>  
> +	if (msm_dp_panel->stream_id == DP_STREAM_1)
> +		offset = REG_DP1_TOTAL_HOR_VER - REG_DP_TOTAL_HOR_VER;
> +
>  	total_hor = drm_mode->htotal;
>  
>  	total_ver = drm_mode->vtotal;
> @@ -624,10 +654,10 @@ int msm_dp_panel_timing_cfg(struct msm_dp_panel *msm_dp_panel, bool wide_bus_en)
>  
>  	msm_dp_active = data;
>  
> -	msm_dp_write_link(panel, REG_DP_TOTAL_HOR_VER, total);
> -	msm_dp_write_link(panel, REG_DP_START_HOR_VER_FROM_SYNC, sync_start);
> -	msm_dp_write_link(panel, REG_DP_HSYNC_VSYNC_WIDTH_POLARITY, width_blanking);
> -	msm_dp_write_link(panel, REG_DP_ACTIVE_HOR_VER, msm_dp_active);
> +	msm_dp_write_link(panel, REG_DP_TOTAL_HOR_VER + offset, total);
> +	msm_dp_write_link(panel, REG_DP_START_HOR_VER_FROM_SYNC  + offset, sync_start);
> +	msm_dp_write_link(panel, REG_DP_HSYNC_VSYNC_WIDTH_POLARITY + offset, width_blanking);
> +	msm_dp_write_link(panel, REG_DP_ACTIVE_HOR_VER  + offset, msm_dp_active);
>  
>  	reg = msm_dp_read_pn(panel, MMSS_DP_INTF_CONFIG);
>  	if (wide_bus_en)
> diff --git a/drivers/gpu/drm/msm/dp/dp_reg.h b/drivers/gpu/drm/msm/dp/dp_reg.h
> index 3689642b7fc0..295c1161e6b7 100644
> --- a/drivers/gpu/drm/msm/dp/dp_reg.h
> +++ b/drivers/gpu/drm/msm/dp/dp_reg.h
> @@ -332,6 +332,17 @@
>  #define DP_TPG_VIDEO_CONFIG_BPP_8BIT		(0x00000001)
>  #define DP_TPG_VIDEO_CONFIG_RGB			(0x00000004)
>  
> +/* DP MST registers */

Which register spaces are they from? 

> +#define REG_DP1_CONFIGURATION_CTRL		(0x00000400)
> +#define REG_DP1_SOFTWARE_MVID			(0x00000414)
> +#define REG_DP1_SOFTWARE_NVID			(0x00000418)
> +#define REG_DP1_TOTAL_HOR_VER			(0x0000041C)
> +#define REG_DP1_MISC1_MISC0			(0x0000042C)
> +#define MMSS_DP1_GENERIC0_0			(0x00000490)
> +#define MMSS_DP1_SDP_CFG			(0x000004E0)
> +#define MMSS_DP1_SDP_CFG2			(0x000004E4)
> +#define MMSS_DP1_SDP_CFG3			(0x000004E8)
> +
>  #define MMSS_DP_ASYNC_FIFO_CONFIG		(0x00000088)
>  
>  #define REG_DP_PHY_AUX_INTERRUPT_CLEAR          (0x0000004C)
> 
> -- 
> 2.43.0
> 

-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 17/39] drm/msm/dp: Add catalog support for 3rd/4th stream MST
  2026-04-10  9:33 ` [PATCH v4 17/39] drm/msm/dp: Add catalog support for 3rd/4th stream MST Yongxing Mou
@ 2026-04-11 18:24   ` Dmitry Baryshkov
  2026-05-25  8:06     ` Yongxing Mou
  0 siblings, 1 reply; 128+ messages in thread
From: Dmitry Baryshkov @ 2026-04-11 18:24 UTC (permalink / raw)
  To: Yongxing Mou
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel

Nit, there is no catalog anymore, please fix the subject line.

On Fri, Apr 10, 2026 at 05:33:52PM +0800, Yongxing Mou wrote:
> To support 4-stream MST, the link clocks for stream 3 and stream 4 are
> controlled by MST_2_LCLK and MST_3_LCLK which share the same register
> definitions but use different base addresses.
> 
> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
> ---
>  drivers/gpu/drm/msm/dp/dp_ctrl.c    | 225 ++++++++++++++++++++++--------------
>  drivers/gpu/drm/msm/dp/dp_ctrl.h    |   4 +-
>  drivers/gpu/drm/msm/dp/dp_display.c |  24 +++-
>  drivers/gpu/drm/msm/dp/dp_panel.c   | 135 +++++++++++++++++-----
>  drivers/gpu/drm/msm/dp/dp_panel.h   |   2 +
>  drivers/gpu/drm/msm/dp/dp_reg.h     |  16 ++-
>  6 files changed, 283 insertions(+), 123 deletions(-)
> 
> diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c
> index a52bcd9ea2a3..1109b2df21be 100644
> --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c
> +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c
> @@ -118,6 +118,8 @@ struct msm_dp_ctrl_private {
>  	struct msm_dp_link *link;
>  	void __iomem *ahb_base;
>  	void __iomem *link_base;
> +	void __iomem *mst2link_base;
> +	void __iomem *mst3link_base;
>  
>  	struct phy *phy;
>  
> @@ -158,19 +160,45 @@ static inline void msm_dp_write_ahb(struct msm_dp_ctrl_private *ctrl,
>  	writel(data, ctrl->ahb_base + offset);
>  }
>  
> -static inline u32 msm_dp_read_link(struct msm_dp_ctrl_private *ctrl, u32 offset)
> +static inline u32 msm_dp_read_link(struct msm_dp_ctrl_private *ctrl,
> +				   enum msm_dp_stream_id stream_id, u32 offset)
>  {
> -	return readl_relaxed(ctrl->link_base + offset);
> +	switch (stream_id) {
> +	case DP_STREAM_0:
> +	case DP_STREAM_1:

Continuing the comment from the previous patch:

Or, can we remap all offsets here? It would be much, much easier to
always pass REG_DP_foo and then here, in the link reading code, remap
the offsets for DP_STREAM_1.

> +		return readl_relaxed(ctrl->link_base + offset);
> +	case DP_STREAM_2:
> +		return readl_relaxed(ctrl->mst2link_base + offset);
> +	case DP_STREAM_3:
> +		return readl_relaxed(ctrl->mst3link_base + offset);
> +	default:
> +		DRM_ERROR("error stream_id\n");
> +		return 0;
> +	}
>  }
>  
>  static inline void msm_dp_write_link(struct msm_dp_ctrl_private *ctrl,
> -			       u32 offset, u32 data)
> +				     enum msm_dp_stream_id stream_id, u32 offset, u32 data)
>  {
>  	/*
>  	 * To make sure link reg writes happens before any other operation,
>  	 * this function uses writel() instread of writel_relaxed()
>  	 */
> -	writel(data, ctrl->link_base + offset);
> +	switch (stream_id) {
> +	case DP_STREAM_0:
> +	case DP_STREAM_1:
> +		writel(data, ctrl->link_base + offset);
> +		break;
> +	case DP_STREAM_2:
> +		writel(data, ctrl->mst2link_base + offset);
> +		break;
> +	case DP_STREAM_3:
> +		writel(data, ctrl->mst3link_base + offset);
> +		break;
> +	default:
> +		DRM_ERROR("error stream_id\n");
> +		break;
> +	}
>  }
>  
>  static int msm_dp_aux_link_configure(struct drm_dp_aux *aux,
> @@ -294,18 +322,18 @@ static void msm_dp_ctrl_psr_mainlink_enable(struct msm_dp_ctrl_private *ctrl)
>  {
>  	u32 val;
>  
> -	val = msm_dp_read_link(ctrl, REG_DP_MAINLINK_CTRL);
> +	val = msm_dp_read_link(ctrl, 0, REG_DP_MAINLINK_CTRL);
>  	val |= DP_MAINLINK_CTRL_ENABLE;
> -	msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, val);
> +	msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_CTRL, val);

Are those always using the link0 base? If so, can we keep the existing
API for the stream-independent registers and introduce a separate API
call for the stream-dependent calls? Generally, if you keep the existing
function, I think, it becomes easier to squash this and the previous
patches.

>  }
>  
>  static void msm_dp_ctrl_psr_mainlink_disable(struct msm_dp_ctrl_private *ctrl)
>  {
>  	u32 val;
>  
> -	val = msm_dp_read_link(ctrl, REG_DP_MAINLINK_CTRL);
> +	val = msm_dp_read_link(ctrl, 0, REG_DP_MAINLINK_CTRL);
>  	val &= ~DP_MAINLINK_CTRL_ENABLE;
> -	msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, val);
> +	msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_CTRL, val);
>  }
>  
>  static void msm_dp_ctrl_mainlink_enable(struct msm_dp_ctrl_private *ctrl)
> @@ -314,21 +342,21 @@ static void msm_dp_ctrl_mainlink_enable(struct msm_dp_ctrl_private *ctrl)
>  
>  	drm_dbg_dp(ctrl->drm_dev, "enable\n");
>  
> -	mainlink_ctrl = msm_dp_read_link(ctrl, REG_DP_MAINLINK_CTRL);
> +	mainlink_ctrl = msm_dp_read_link(ctrl, 0, REG_DP_MAINLINK_CTRL);
>  
>  	mainlink_ctrl &= ~(DP_MAINLINK_CTRL_RESET |
>  					DP_MAINLINK_CTRL_ENABLE);
> -	msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
> +	msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
>  
>  	mainlink_ctrl |= DP_MAINLINK_CTRL_RESET;
> -	msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
> +	msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
>  
>  	mainlink_ctrl &= ~DP_MAINLINK_CTRL_RESET;
> -	msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
> +	msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
>  
>  	mainlink_ctrl |= (DP_MAINLINK_CTRL_ENABLE |
>  				DP_MAINLINK_FB_BOUNDARY_SEL);
> -	msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
> +	msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
>  }
>  
>  static void msm_dp_ctrl_mainlink_disable(struct msm_dp_ctrl_private *ctrl)
> @@ -337,23 +365,23 @@ static void msm_dp_ctrl_mainlink_disable(struct msm_dp_ctrl_private *ctrl)
>  
>  	drm_dbg_dp(ctrl->drm_dev, "disable\n");
>  
> -	mainlink_ctrl = msm_dp_read_link(ctrl, REG_DP_MAINLINK_CTRL);
> +	mainlink_ctrl = msm_dp_read_link(ctrl, 0, REG_DP_MAINLINK_CTRL);
>  	mainlink_ctrl &= ~DP_MAINLINK_CTRL_ENABLE;
> -	msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
> +	msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
>  }
>  
>  static void msm_dp_setup_peripheral_flush(struct msm_dp_ctrl_private *ctrl)
>  {
>  	u32 mainlink_ctrl;
>  
> -	mainlink_ctrl = msm_dp_read_link(ctrl, REG_DP_MAINLINK_CTRL);
> +	mainlink_ctrl = msm_dp_read_link(ctrl, 0, REG_DP_MAINLINK_CTRL);
>  
>  	if (ctrl->hw_revision >= DP_HW_VERSION_1_2)
>  		mainlink_ctrl |= DP_MAINLINK_FLUSH_MODE_SDE_PERIPH_UPDATE;
>  	else
>  		mainlink_ctrl |= DP_MAINLINK_FLUSH_MODE_UPDATE_SDP;
>  
> -	msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
> +	msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
>  }
>  
>  static bool msm_dp_ctrl_mainlink_ready(struct msm_dp_ctrl_private *ctrl)
> @@ -380,7 +408,7 @@ void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl)
>  	ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl);
>  
>  	reinit_completion(&ctrl->idle_comp);
> -	msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, DP_STATE_CTRL_PUSH_IDLE);
> +	msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, DP_STATE_CTRL_PUSH_IDLE);
>  
>  	if (!wait_for_completion_timeout(&ctrl->idle_comp,
>  			IDLE_PATTERN_COMPLETION_TIMEOUT_JIFFIES))
> @@ -395,7 +423,11 @@ static void msm_dp_ctrl_config_ctrl_streams(struct msm_dp_ctrl_private *ctrl,
>  	u32 config = 0, tbd;
>  	u32 reg_offset = 0;
>  
> -	config = msm_dp_read_link(ctrl, REG_DP_CONFIGURATION_CTRL);
> +	if (msm_dp_panel->stream_id == DP_STREAM_0)
> +		config = msm_dp_read_link(ctrl, 0, REG_DP_CONFIGURATION_CTRL);
> +
> +	if (msm_dp_panel->stream_id == DP_STREAM_1)
> +		reg_offset = REG_DP1_CONFIGURATION_CTRL - REG_DP_CONFIGURATION_CTRL;
>  
>  	if (msm_dp_panel->msm_dp_mode.out_fmt_is_yuv_420)
>  		config |= DP_CONFIGURATION_CTRL_RGB_YUV; /* YUV420 */
> @@ -410,8 +442,10 @@ static void msm_dp_ctrl_config_ctrl_streams(struct msm_dp_ctrl_private *ctrl,
>  
>  	drm_dbg_dp(ctrl->drm_dev, "stream DP_CONFIGURATION_CTRL=0x%x\n", config);
>  
> -	if (msm_dp_panel->stream_id == DP_STREAM_1)
> -		reg_offset = REG_DP1_CONFIGURATION_CTRL - REG_DP_CONFIGURATION_CTRL;
> +	msm_dp_write_link(ctrl, msm_dp_panel->stream_id, msm_dp_panel->stream_id > 1 ?
> +			  REG_DP_MSTLINK_CONFIGURATION_CTRL :
> +			  REG_DP_CONFIGURATION_CTRL + reg_offset, config);
> +

Yep, it would definitely help to handle these differences in the link
writing code.

>  }
>  
>  static void msm_dp_ctrl_config_ctrl_link(struct msm_dp_ctrl_private *ctrl)
> @@ -441,7 +475,7 @@ static void msm_dp_ctrl_config_ctrl_link(struct msm_dp_ctrl_private *ctrl)
>  
>  	drm_dbg_dp(ctrl->drm_dev, "link DP_CONFIGURATION_CTRL=0x%x\n", config);
>  
> -	msm_dp_write_link(ctrl, REG_DP_CONFIGURATION_CTRL, config);
> +	msm_dp_write_link(ctrl, 0, REG_DP_CONFIGURATION_CTRL, config);
>  }
>  
>  static void msm_dp_ctrl_lane_mapping(struct msm_dp_ctrl_private *ctrl)
> @@ -454,8 +488,8 @@ static void msm_dp_ctrl_lane_mapping(struct msm_dp_ctrl_private *ctrl)
>  	ln_mapping |= lane_map[2] << LANE2_MAPPING_SHIFT;
>  	ln_mapping |= lane_map[3] << LANE3_MAPPING_SHIFT;
>  
> -	msm_dp_write_link(ctrl, REG_DP_LOGICAL2PHYSICAL_LANE_MAPPING,
> -			ln_mapping);
> +	msm_dp_write_link(ctrl, 0, REG_DP_LOGICAL2PHYSICAL_LANE_MAPPING,
> +			  ln_mapping);
>  }
>  
>  static void msm_dp_ctrl_config_misc1_misc0(struct msm_dp_ctrl_private *ctrl,
> @@ -471,7 +505,8 @@ static void msm_dp_ctrl_config_misc1_misc0(struct msm_dp_ctrl_private *ctrl,
>  	if (msm_dp_panel->stream_id == DP_STREAM_1)
>  		reg_offset = REG_DP1_MISC1_MISC0 - REG_DP_MISC1_MISC0;
>  
> -	misc_val = msm_dp_read_link(ctrl, REG_DP_MISC1_MISC0 + reg_offset);
> +	misc_val = msm_dp_read_link(ctrl, msm_dp_panel->stream_id, msm_dp_panel->stream_id > 1 ?
> +				    REG_DP_MSTLINK_MISC1_MISC0 : REG_DP_MISC1_MISC0 + reg_offset);
>  
>  	/* clear bpp bits */
>  	misc_val &= ~(0x07 << DP_MISC0_TEST_BITS_DEPTH_SHIFT);
> @@ -481,7 +516,10 @@ static void msm_dp_ctrl_config_misc1_misc0(struct msm_dp_ctrl_private *ctrl,
>  	misc_val |= DP_MISC0_SYNCHRONOUS_CLK;
>  
>  	drm_dbg_dp(ctrl->drm_dev, "misc settings = 0x%x\n", misc_val);
> -	msm_dp_write_link(ctrl, REG_DP_MISC1_MISC0 + reg_offset, misc_val);
> +	msm_dp_write_link(ctrl, msm_dp_panel->stream_id,
> +			  msm_dp_panel->stream_id > 1 ?
> +			  REG_DP_MSTLINK_MISC1_MISC0 : REG_DP_MISC1_MISC0 + reg_offset,
> +			  misc_val);
>  }
>  
>  static void msm_dp_ctrl_configure_source_params(struct msm_dp_ctrl_private *ctrl,
> @@ -1307,9 +1345,9 @@ static void msm_dp_ctrl_setup_tr_unit(struct msm_dp_ctrl_private *ctrl)
>  	pr_debug("dp_tu=0x%x, valid_boundary=0x%x, valid_boundary2=0x%x\n",
>  			msm_dp_tu, valid_boundary, valid_boundary2);
>  
> -	msm_dp_write_link(ctrl, REG_DP_VALID_BOUNDARY, valid_boundary);
> -	msm_dp_write_link(ctrl, REG_DP_TU, msm_dp_tu);
> -	msm_dp_write_link(ctrl, REG_DP_VALID_BOUNDARY_2, valid_boundary2);
> +	msm_dp_write_link(ctrl, 0, REG_DP_VALID_BOUNDARY, valid_boundary);
> +	msm_dp_write_link(ctrl, 0, REG_DP_TU, msm_dp_tu);
> +	msm_dp_write_link(ctrl, 0, REG_DP_VALID_BOUNDARY_2, valid_boundary2);
>  }
>  
>  static int msm_dp_ctrl_wait4video_ready(struct msm_dp_ctrl_private *ctrl)
> @@ -1426,7 +1464,7 @@ static int msm_dp_ctrl_set_pattern_state_bit(struct msm_dp_ctrl_private *ctrl,
>  
>  	bit = BIT(state_bit - 1);
>  	drm_dbg_dp(ctrl->drm_dev, "hw: bit=%d train=%d\n", bit, state_bit);
> -	msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, bit);
> +	msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, bit);
>  
>  	bit = BIT(state_bit - 1) << DP_MAINLINK_READY_LINK_TRAINING_SHIFT;
>  
> @@ -1453,7 +1491,7 @@ static int msm_dp_ctrl_link_train_1(struct msm_dp_ctrl_private *ctrl,
>  	delay_us = drm_dp_read_clock_recovery_delay(ctrl->aux,
>  						    ctrl->panel->dpcd, dp_phy, false);
>  
> -	msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, 0);
> +	msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, 0);
>  
>  	*training_step = DP_TRAINING_1;
>  
> @@ -1577,7 +1615,7 @@ static int msm_dp_ctrl_link_train_2(struct msm_dp_ctrl_private *ctrl,
>  	delay_us = drm_dp_read_channel_eq_delay(ctrl->aux,
>  						ctrl->panel->dpcd, dp_phy, false);
>  
> -	msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, 0);
> +	msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, 0);
>  
>  	*training_step = DP_TRAINING_2;
>  
> @@ -1694,7 +1732,7 @@ static int msm_dp_ctrl_link_train(struct msm_dp_ctrl_private *ctrl,
>  	}
>  
>  end:
> -	msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, 0);
> +	msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, 0);
>  
>  	return ret;
>  }
> @@ -1840,34 +1878,34 @@ static int msm_dp_ctrl_enable_mainlink_clocks(struct msm_dp_ctrl_private *ctrl)
>  static void msm_dp_ctrl_enable_sdp(struct msm_dp_ctrl_private *ctrl)
>  {
>  	/* trigger sdp */
> -	msm_dp_write_link(ctrl, MMSS_DP_SDP_CFG3, UPDATE_SDP);
> -	msm_dp_write_link(ctrl, MMSS_DP_SDP_CFG3, 0x0);
> +	msm_dp_write_link(ctrl, 0, MMSS_DP_SDP_CFG3, UPDATE_SDP);
> +	msm_dp_write_link(ctrl, 0, MMSS_DP_SDP_CFG3, 0x0);
>  }
>  
>  static void msm_dp_ctrl_psr_enter(struct msm_dp_ctrl_private *ctrl)
>  {
>  	u32 cmd;
>  
> -	cmd = msm_dp_read_link(ctrl, REG_PSR_CMD);
> +	cmd = msm_dp_read_link(ctrl, 0, REG_PSR_CMD);
>  
>  	cmd &= ~(PSR_ENTER | PSR_EXIT);
>  	cmd |= PSR_ENTER;
>  
>  	msm_dp_ctrl_enable_sdp(ctrl);
> -	msm_dp_write_link(ctrl, REG_PSR_CMD, cmd);
> +	msm_dp_write_link(ctrl, 0, REG_PSR_CMD, cmd);
>  }
>  
>  static void msm_dp_ctrl_psr_exit(struct msm_dp_ctrl_private *ctrl)
>  {
>  	u32 cmd;
>  
> -	cmd = msm_dp_read_link(ctrl, REG_PSR_CMD);
> +	cmd = msm_dp_read_link(ctrl, 0, REG_PSR_CMD);
>  
>  	cmd &= ~(PSR_ENTER | PSR_EXIT);
>  	cmd |= PSR_EXIT;
>  
>  	msm_dp_ctrl_enable_sdp(ctrl);
> -	msm_dp_write_link(ctrl, REG_PSR_CMD, cmd);
> +	msm_dp_write_link(ctrl, 0, REG_PSR_CMD, cmd);
>  }
>  
>  void msm_dp_ctrl_config_psr(struct msm_dp_ctrl *msm_dp_ctrl)
> @@ -1880,9 +1918,9 @@ void msm_dp_ctrl_config_psr(struct msm_dp_ctrl *msm_dp_ctrl)
>  		return;
>  
>  	/* enable PSR1 function */
> -	cfg = msm_dp_read_link(ctrl, REG_PSR_CONFIG);
> +	cfg = msm_dp_read_link(ctrl, 0, REG_PSR_CONFIG);
>  	cfg |= PSR1_SUPPORTED;
> -	msm_dp_write_link(ctrl, REG_PSR_CONFIG, cfg);
> +	msm_dp_write_link(ctrl, 0, REG_PSR_CONFIG, cfg);
>  
>  	msm_dp_ctrl_config_psr_interrupt(ctrl);
>  	msm_dp_ctrl_enable_sdp(ctrl);
> @@ -1921,16 +1959,16 @@ void msm_dp_ctrl_set_psr(struct msm_dp_ctrl *msm_dp_ctrl, bool enter)
>  		}
>  
>  		msm_dp_ctrl_push_idle(msm_dp_ctrl);
> -		msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, 0);
> +		msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, 0);
>  
>  		msm_dp_ctrl_psr_mainlink_disable(ctrl);
>  	} else {
>  		msm_dp_ctrl_psr_mainlink_enable(ctrl);
>  
>  		msm_dp_ctrl_psr_exit(ctrl);
> -		msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, DP_STATE_CTRL_SEND_VIDEO);
> +		msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, DP_STATE_CTRL_SEND_VIDEO);
>  		msm_dp_ctrl_wait4video_ready(ctrl);
> -		msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, 0);
> +		msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, 0);
>  	}
>  }
>  
> @@ -2041,7 +2079,7 @@ static int msm_dp_ctrl_link_maintenance(struct msm_dp_ctrl_private *ctrl)
>  
>  	msm_dp_ctrl_clear_training_pattern(ctrl, DP_PHY_DPRX);
>  
> -	msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, DP_STATE_CTRL_SEND_VIDEO);
> +	msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, DP_STATE_CTRL_SEND_VIDEO);
>  
>  	ret = msm_dp_ctrl_wait4video_ready(ctrl);
>  end:
> @@ -2056,72 +2094,72 @@ static void msm_dp_ctrl_send_phy_pattern(struct msm_dp_ctrl_private *ctrl,
>  	u32 value = 0x0;
>  
>  	/* Make sure to clear the current pattern before starting a new one */
> -	msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, 0x0);
> +	msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, 0x0);
>  
>  	drm_dbg_dp(ctrl->drm_dev, "pattern: %#x\n", pattern);
>  	switch (pattern) {
>  	case DP_PHY_TEST_PATTERN_D10_2:
> -		msm_dp_write_link(ctrl, REG_DP_STATE_CTRL,
> -			      DP_STATE_CTRL_LINK_TRAINING_PATTERN1);
> +		msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL,
> +				  DP_STATE_CTRL_LINK_TRAINING_PATTERN1);
>  		break;
>  
>  	case DP_PHY_TEST_PATTERN_ERROR_COUNT:
>  		value &= ~(1 << 16);
> -		msm_dp_write_link(ctrl, REG_DP_HBR2_COMPLIANCE_SCRAMBLER_RESET,
> -			      value);
> +		msm_dp_write_link(ctrl, 0, REG_DP_HBR2_COMPLIANCE_SCRAMBLER_RESET,
> +				  value);
>  		value |= SCRAMBLER_RESET_COUNT_VALUE;
> -		msm_dp_write_link(ctrl, REG_DP_HBR2_COMPLIANCE_SCRAMBLER_RESET,
> -			      value);
> -		msm_dp_write_link(ctrl, REG_DP_MAINLINK_LEVELS,
> -			      DP_MAINLINK_SAFE_TO_EXIT_LEVEL_2);
> -		msm_dp_write_link(ctrl, REG_DP_STATE_CTRL,
> -			      DP_STATE_CTRL_LINK_SYMBOL_ERR_MEASURE);
> +		msm_dp_write_link(ctrl, 0, REG_DP_HBR2_COMPLIANCE_SCRAMBLER_RESET,
> +				  value);
> +		msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_LEVELS,
> +				  DP_MAINLINK_SAFE_TO_EXIT_LEVEL_2);
> +		msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL,
> +				  DP_STATE_CTRL_LINK_SYMBOL_ERR_MEASURE);
>  		break;
>  
>  	case DP_PHY_TEST_PATTERN_PRBS7:
> -		msm_dp_write_link(ctrl, REG_DP_STATE_CTRL,
> -			      DP_STATE_CTRL_LINK_PRBS7);
> +		msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL,
> +				  DP_STATE_CTRL_LINK_PRBS7);
>  		break;
>  
>  	case DP_PHY_TEST_PATTERN_80BIT_CUSTOM:
> -		msm_dp_write_link(ctrl, REG_DP_STATE_CTRL,
> -			      DP_STATE_CTRL_LINK_TEST_CUSTOM_PATTERN);
> +		msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL,
> +				  DP_STATE_CTRL_LINK_TEST_CUSTOM_PATTERN);
>  		/* 00111110000011111000001111100000 */
> -		msm_dp_write_link(ctrl, REG_DP_TEST_80BIT_CUSTOM_PATTERN_REG0,
> -			      0x3E0F83E0);
> +		msm_dp_write_link(ctrl, 0, REG_DP_TEST_80BIT_CUSTOM_PATTERN_REG0,
> +				  0x3E0F83E0);
>  		/* 00001111100000111110000011111000 */
> -		msm_dp_write_link(ctrl, REG_DP_TEST_80BIT_CUSTOM_PATTERN_REG1,
> -			      0x0F83E0F8);
> +		msm_dp_write_link(ctrl, 0, REG_DP_TEST_80BIT_CUSTOM_PATTERN_REG1,
> +				  0x0F83E0F8);
>  		/* 1111100000111110 */
> -		msm_dp_write_link(ctrl, REG_DP_TEST_80BIT_CUSTOM_PATTERN_REG2,
> -			      0x0000F83E);
> +		msm_dp_write_link(ctrl, 0, REG_DP_TEST_80BIT_CUSTOM_PATTERN_REG2,
> +				  0x0000F83E);
>  		break;
>  
>  	case DP_PHY_TEST_PATTERN_CP2520:
> -		value = msm_dp_read_link(ctrl, REG_DP_MAINLINK_CTRL);
> +		value = msm_dp_read_link(ctrl, 0, REG_DP_MAINLINK_CTRL);
>  		value &= ~DP_MAINLINK_CTRL_SW_BYPASS_SCRAMBLER;
> -		msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, value);
> +		msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_CTRL, value);
>  
>  		value = DP_HBR2_ERM_PATTERN;
> -		msm_dp_write_link(ctrl, REG_DP_HBR2_COMPLIANCE_SCRAMBLER_RESET,
> -			      value);
> +		msm_dp_write_link(ctrl, 0, REG_DP_HBR2_COMPLIANCE_SCRAMBLER_RESET,
> +				  value);
>  		value |= SCRAMBLER_RESET_COUNT_VALUE;
> -		msm_dp_write_link(ctrl, REG_DP_HBR2_COMPLIANCE_SCRAMBLER_RESET,
> -			      value);
> -		msm_dp_write_link(ctrl, REG_DP_MAINLINK_LEVELS,
> -			      DP_MAINLINK_SAFE_TO_EXIT_LEVEL_2);
> -		msm_dp_write_link(ctrl, REG_DP_STATE_CTRL,
> -			      DP_STATE_CTRL_LINK_SYMBOL_ERR_MEASURE);
> -		value = msm_dp_read_link(ctrl, REG_DP_MAINLINK_CTRL);
> +		msm_dp_write_link(ctrl, 0, REG_DP_HBR2_COMPLIANCE_SCRAMBLER_RESET,
> +				  value);
> +		msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_LEVELS,
> +				  DP_MAINLINK_SAFE_TO_EXIT_LEVEL_2);
> +		msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL,
> +				  DP_STATE_CTRL_LINK_SYMBOL_ERR_MEASURE);
> +		value = msm_dp_read_link(ctrl, 0, REG_DP_MAINLINK_CTRL);
>  		value |= DP_MAINLINK_CTRL_ENABLE;
> -		msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, value);
> +		msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_CTRL, value);
>  		break;
>  
>  	case DP_PHY_TEST_PATTERN_SEL_MASK:
> -		msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL,
> -			      DP_MAINLINK_CTRL_ENABLE);
> -		msm_dp_write_link(ctrl, REG_DP_STATE_CTRL,
> -			      DP_STATE_CTRL_LINK_TRAINING_PATTERN4);
> +		msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_CTRL,
> +				  DP_MAINLINK_CTRL_ENABLE);
> +		msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL,
> +				  DP_STATE_CTRL_LINK_TRAINING_PATTERN4);
>  		break;
>  
>  	default:
> @@ -2149,7 +2187,7 @@ static bool msm_dp_ctrl_send_phy_test_pattern(struct msm_dp_ctrl_private *ctrl)
>  	msm_dp_ctrl_update_phy_vx_px(ctrl, DP_PHY_DPRX);
>  	msm_dp_link_send_test_response(ctrl->link);
>  
> -	pattern_sent = msm_dp_read_link(ctrl, REG_DP_MAINLINK_READY);
> +	pattern_sent = msm_dp_read_link(ctrl, 0, REG_DP_MAINLINK_READY);
>  
>  	switch (pattern_sent) {
>  	case MR_LINK_TRAINING1:
> @@ -2522,8 +2560,14 @@ static void msm_dp_ctrl_config_msa(struct msm_dp_ctrl_private *ctrl,
>  		nvid *= 3;
>  
>  	drm_dbg_dp(ctrl->drm_dev, "mvid=0x%x, nvid=0x%x\n", mvid, nvid);
> -	msm_dp_write_link(ctrl, REG_DP_SOFTWARE_MVID + mvid_reg_off, mvid);
> -	msm_dp_write_link(ctrl, REG_DP_SOFTWARE_NVID + nvid_reg_off, nvid);
> +	msm_dp_write_link(ctrl, msm_dp_panel->stream_id,
> +			  msm_dp_panel->stream_id > 1 ?
> +			  REG_MSTLINK_SOFTWARE_MVID : REG_DP_SOFTWARE_MVID + mvid_reg_off,
> +			  mvid);
> +	msm_dp_write_link(ctrl, msm_dp_panel->stream_id,
> +			  msm_dp_panel->stream_id > 1 ?
> +			  REG_MSTLINK_SOFTWARE_NVID : REG_DP_SOFTWARE_NVID + nvid_reg_off,
> +			  nvid);
>  }
>  
>  int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train)
> @@ -2593,7 +2637,8 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *
>  
>  	msm_dp_ctrl_lane_mapping(ctrl);
>  	msm_dp_setup_peripheral_flush(ctrl);
> -	msm_dp_ctrl_config_ctrl_link(ctrl);
> +	if (msm_dp_panel->stream_id == DP_STREAM_0)
> +		msm_dp_ctrl_config_ctrl_link(ctrl);
>  
>  	msm_dp_ctrl_configure_source_params(ctrl, msm_dp_panel);
>  
> @@ -2607,7 +2652,7 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *
>  
>  	msm_dp_ctrl_setup_tr_unit(ctrl);
>  
> -	msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, DP_STATE_CTRL_SEND_VIDEO);
> +	msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, DP_STATE_CTRL_SEND_VIDEO);
>  
>  	ret = msm_dp_ctrl_wait4video_ready(ctrl);
>  	if (ret)
> @@ -2787,7 +2832,9 @@ struct msm_dp_ctrl *msm_dp_ctrl_get(struct device *dev, struct msm_dp_link *link
>  			struct phy *phy,
>  			int max_stream,
>  			void __iomem *ahb_base,
> -			void __iomem *link_base)
> +			void __iomem *link_base,
> +			void __iomem *mst2link_base,
> +			void __iomem *mst3link_base)
>  {
>  	struct msm_dp_ctrl_private *ctrl;
>  	int ret;
> @@ -2827,6 +2874,8 @@ struct msm_dp_ctrl *msm_dp_ctrl_get(struct device *dev, struct msm_dp_link *link
>  	ctrl->phy      = phy;
>  	ctrl->ahb_base = ahb_base;
>  	ctrl->link_base = link_base;
> +	ctrl->mst2link_base = mst2link_base;
> +	ctrl->mst3link_base = mst3link_base;
>  
>  	ret = msm_dp_ctrl_clk_init(&ctrl->msm_dp_ctrl, max_stream);
>  	if (ret) {
> diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.h b/drivers/gpu/drm/msm/dp/dp_ctrl.h
> index 6fed3ff3a72d..e72d501ac1ce 100644
> --- a/drivers/gpu/drm/msm/dp/dp_ctrl.h
> +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.h
> @@ -31,7 +31,9 @@ struct msm_dp_ctrl *msm_dp_ctrl_get(struct device *dev,
>  				    struct phy *phy,
>  				    int max_stream,
>  				    void __iomem *ahb_base,
> -				    void __iomem *link_base);
> +				    void __iomem *link_base,
> +				    void __iomem *mst2link_base,
> +				    void __iomem *mst3link_base);
>  
>  void msm_dp_ctrl_reset(struct msm_dp_ctrl *msm_dp_ctrl);
>  void msm_dp_ctrl_phy_init(struct msm_dp_ctrl *msm_dp_ctrl);
> diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
> index ff506064a3fa..a924fbd825f7 100644
> --- a/drivers/gpu/drm/msm/dp/dp_display.c
> +++ b/drivers/gpu/drm/msm/dp/dp_display.c
> @@ -85,6 +85,12 @@ struct msm_dp_display_private {
>  	void __iomem *link_base;
>  	size_t link_len;
>  
> +	void __iomem *mst2link_base;
> +	size_t mst2link_len;
> +
> +	void __iomem *mst3link_base;
> +	size_t mst3link_len;
> +
>  	void __iomem *pixel_base[DP_STREAM_MAX];
>  	size_t pixel_len;
>  
> @@ -561,7 +567,8 @@ static int msm_dp_init_sub_modules(struct msm_dp_display_private *dp)
>  		goto error_link;
>  	}
>  
> -	dp->panel = msm_dp_panel_get(dev, dp->aux, dp->link, dp->link_base, dp->pixel_base[0]);
> +	dp->panel = msm_dp_panel_get(dev, dp->aux, dp->link, dp->link_base,
> +				     dp->mst2link_base, dp->mst3link_base, dp->pixel_base[0]);
>  	if (IS_ERR(dp->panel)) {
>  		rc = PTR_ERR(dp->panel);
>  		DRM_ERROR("failed to initialize panel, rc = %d\n", rc);
> @@ -570,7 +577,8 @@ static int msm_dp_init_sub_modules(struct msm_dp_display_private *dp)
>  	}
>  
>  	dp->ctrl = msm_dp_ctrl_get(dev, dp->link, dp->panel, dp->aux,
> -			       phy, dp->max_stream, dp->ahb_base, dp->link_base);
> +			       phy, dp->max_stream, dp->ahb_base,
> +			       dp->link_base, dp->mst2link_base, dp->mst3link_base);
>  	if (IS_ERR(dp->ctrl)) {
>  		rc = PTR_ERR(dp->ctrl);
>  		DRM_ERROR("failed to initialize ctrl, rc = %d\n", rc);
> @@ -883,6 +891,10 @@ void msm_dp_snapshot(struct msm_disp_state *disp_state, struct msm_dp *dp)
>  				    msm_dp_display->aux_base, "dp_aux");
>  	msm_disp_snapshot_add_block(disp_state, msm_dp_display->link_len,
>  				    msm_dp_display->link_base, "dp_link");
> +	msm_disp_snapshot_add_block(disp_state, msm_dp_display->mst2link_len,
> +				    msm_dp_display->mst2link_base, "dp_mst2link");
> +	msm_disp_snapshot_add_block(disp_state, msm_dp_display->mst3link_len,
> +				    msm_dp_display->mst3link_base, "dp_mst3link");
>  	msm_disp_snapshot_add_block(disp_state, msm_dp_display->pixel_len,
>  				    msm_dp_display->pixel_base[0], "dp_p0");
>  	msm_disp_snapshot_add_block(disp_state, msm_dp_display->pixel_len,
> @@ -1228,6 +1240,14 @@ static int msm_dp_display_get_io(struct msm_dp_display_private *display)
>  		}
>  	}
>  
> +	display->mst2link_base = msm_dp_ioremap(pdev, 7, &display->mst2link_len);
> +	if (IS_ERR(display->mst2link_base))
> +		DRM_DEBUG_DP("unable to remap link region: %pe\n", display->mst2link_base);
> +
> +	display->mst3link_base = msm_dp_ioremap(pdev, 8, &display->mst3link_len);
> +	if (IS_ERR(display->mst3link_base))
> +		DRM_DEBUG_DP("unable to remap link region: %pe\n", display->mst3link_base);
> +
>  	return 0;
>  }
>  
> diff --git a/drivers/gpu/drm/msm/dp/dp_panel.c b/drivers/gpu/drm/msm/dp/dp_panel.c
> index 6c88cc7e3037..a8a6297b37e3 100644
> --- a/drivers/gpu/drm/msm/dp/dp_panel.c
> +++ b/drivers/gpu/drm/msm/dp/dp_panel.c
> @@ -25,23 +25,50 @@ struct msm_dp_panel_private {
>  	struct drm_dp_aux *aux;
>  	struct msm_dp_link *link;
>  	void __iomem *link_base;
> +	void __iomem *mst2link_base;
> +	void __iomem *mst3link_base;
>  	void __iomem *pixel_base;
>  	bool panel_on;
>  };
>  
>  static inline u32 msm_dp_read_link(struct msm_dp_panel_private *panel, u32 offset)
>  {
> -	return readl_relaxed(panel->link_base + offset);
> +	switch (panel->msm_dp_panel.stream_id) {
> +	case DP_STREAM_0:
> +	case DP_STREAM_1:
> +		return readl_relaxed(panel->link_base + offset);
> +	case DP_STREAM_2:
> +		return readl_relaxed(panel->mst2link_base + offset);
> +	case DP_STREAM_3:
> +		return readl_relaxed(panel->mst3link_base + offset);
> +	default:
> +		DRM_ERROR("error stream_id\n");
> +		return 0;
> +	}
>  }
>  
>  static inline void msm_dp_write_link(struct msm_dp_panel_private *panel,
> -			       u32 offset, u32 data)
> +					u32 offset, u32 data)
>  {
>  	/*
>  	 * To make sure link reg writes happens before any other operation,
>  	 * this function uses writel() instread of writel_relaxed()
>  	 */
> -	writel(data, panel->link_base + offset);
> +	switch (panel->msm_dp_panel.stream_id) {
> +	case DP_STREAM_0:
> +	case DP_STREAM_1:
> +		writel(data, panel->link_base + offset);
> +		break;
> +	case DP_STREAM_2:
> +		writel(data, panel->mst2link_base + offset);
> +		break;
> +	case DP_STREAM_3:
> +		writel(data, panel->mst3link_base + offset);
> +		break;
> +	default:
> +		DRM_ERROR("error stream_id\n");
> +		break;
> +	}
>  }
>  
>  static inline void msm_dp_write_pn(struct msm_dp_panel_private *panel,
> @@ -444,38 +471,51 @@ void msm_dp_panel_clear_dsc_dto(struct msm_dp_panel *msm_dp_panel)
>  
>  static void msm_dp_panel_send_vsc_sdp(struct msm_dp_panel_private *panel, struct dp_sdp *vsc_sdp)
>  {
> +	u32 id = panel->msm_dp_panel.stream_id;
>  	u32 header[2];
>  	u32 val;
>  	int i;
>  	u32 offset = 0;
>  
> -	if (panel->msm_dp_panel.stream_id == DP_STREAM_1)
> +	if (id == DP_STREAM_1)
>  		offset = MMSS_DP1_GENERIC0_0 - MMSS_DP_GENERIC0_0;
>  
>  	msm_dp_utils_pack_sdp_header(&vsc_sdp->sdp_header, header);
>  
> -	msm_dp_write_link(panel, MMSS_DP_GENERIC0_0 + offset, header[0]);
> -	msm_dp_write_link(panel, MMSS_DP_GENERIC0_1 + offset, header[1]);
> +	msm_dp_write_link(panel, id > 1 ?
> +			  MMSS_DP_MSTLINK_GENERIC0_0 : MMSS_DP_GENERIC0_0 + offset,
> +			  header[0]);
> +	msm_dp_write_link(panel, id > 1 ?
> +			  MMSS_DP_MSTLINK_GENERIC0_1 : MMSS_DP_GENERIC0_1 + offset,
> +			  header[1]);
>  
>  	for (i = 0; i < sizeof(vsc_sdp->db); i += 4) {
>  		val = ((vsc_sdp->db[i]) | (vsc_sdp->db[i + 1] << 8) | (vsc_sdp->db[i + 2] << 16) |
>  		       (vsc_sdp->db[i + 3] << 24));
> -		msm_dp_write_link(panel, MMSS_DP_GENERIC0_2 + i + offset, val);
> +
> +		msm_dp_write_link(panel, id > 1 ?
> +				  MMSS_DP_MSTLINK_GENERIC0_2 + i : MMSS_DP_GENERIC0_2 + i + offset,
> +				  val);
>  	}
>  }
>  
>  static void msm_dp_panel_update_sdp(struct msm_dp_panel_private *panel)
>  {
> +	u32 id = panel->msm_dp_panel.stream_id;
>  	u32 hw_revision = panel->msm_dp_panel.hw_revision;
>  	u32 offset = 0;
>  
> -	if (panel->msm_dp_panel.stream_id == DP_STREAM_1)
> +	if (id == DP_STREAM_1)
>  		offset = MMSS_DP1_SDP_CFG3 - MMSS_DP_SDP_CFG3;
>  
>  	if (hw_revision >= DP_HW_VERSION_1_0 &&
>  	    hw_revision < DP_HW_VERSION_1_2) {
> -		msm_dp_write_link(panel, MMSS_DP_SDP_CFG3 + offset, UPDATE_SDP);
> -		msm_dp_write_link(panel, MMSS_DP_SDP_CFG3 + offset, 0x0);
> +		msm_dp_write_link(panel, id > 1 ?
> +				  MMSS_DP_MSTLINK_SDP_CFG3 : MMSS_DP_SDP_CFG3 + offset,
> +				  UPDATE_SDP);
> +		msm_dp_write_link(panel, id > 1 ?
> +				  MMSS_DP_MSTLINK_SDP_CFG3 : MMSS_DP_SDP_CFG3 + offset,
> +				  0x0);
>  	}
>  }
>  
> @@ -483,26 +523,34 @@ void msm_dp_panel_enable_vsc_sdp(struct msm_dp_panel *msm_dp_panel, struct dp_sd
>  {
>  	struct msm_dp_panel_private *panel =
>  		container_of(msm_dp_panel, struct msm_dp_panel_private, msm_dp_panel);
> +	u32 id = msm_dp_panel->stream_id;
>  	u32 cfg, cfg2, misc;
>  	u32 misc_reg_offset = 0;
>  	u32 sdp_cfg_offset = 0;
>  	u32 sdp_cfg2_offset = 0;
>  
> -	if (msm_dp_panel->stream_id == DP_STREAM_1) {
> +	if (id == DP_STREAM_1) {
>  		misc_reg_offset = REG_DP1_MISC1_MISC0 - REG_DP_MISC1_MISC0;
>  		sdp_cfg_offset = MMSS_DP1_SDP_CFG - MMSS_DP_SDP_CFG;
>  		sdp_cfg2_offset = MMSS_DP1_SDP_CFG2 - MMSS_DP_SDP_CFG2;
>  	}
>  
> -	cfg = msm_dp_read_link(panel, MMSS_DP_SDP_CFG + sdp_cfg_offset);
> -	cfg2 = msm_dp_read_link(panel, MMSS_DP_SDP_CFG2 + sdp_cfg2_offset);
> -	misc = msm_dp_read_link(panel, REG_DP_MISC1_MISC0 + misc_reg_offset);
> +	cfg = msm_dp_read_link(panel, id > 1 ?
> +			       MMSS_DP_MSTLINK_SDP_CFG : MMSS_DP_SDP_CFG + sdp_cfg_offset);
> +	cfg2 = msm_dp_read_link(panel, id > 1 ?
> +				MMSS_DP_MSTLINK_SDP_CFG2 : MMSS_DP_SDP_CFG2 + sdp_cfg2_offset);
> +	misc = msm_dp_read_link(panel, id > 1 ?
> +				REG_DP_MSTLINK_MISC1_MISC0 : REG_DP_MISC1_MISC0 + misc_reg_offset);
>  
>  	cfg |= GEN0_SDP_EN;
> -	msm_dp_write_link(panel, MMSS_DP_SDP_CFG + sdp_cfg_offset, cfg);
> -
>  	cfg2 |= GENERIC0_SDPSIZE_VALID;
> -	msm_dp_write_link(panel, MMSS_DP_SDP_CFG2 + sdp_cfg2_offset, cfg2);
> +
> +	msm_dp_write_link(panel, id > 1 ?
> +			  MMSS_DP_MSTLINK_SDP_CFG : MMSS_DP_SDP_CFG + sdp_cfg_offset,
> +			  cfg);
> +	msm_dp_write_link(panel, id > 1 ?
> +			  MMSS_DP_MSTLINK_SDP_CFG2 : MMSS_DP_SDP_CFG2 + sdp_cfg2_offset,
> +			  cfg2);
>  
>  	msm_dp_panel_send_vsc_sdp(panel, vsc_sdp);
>  
> @@ -512,7 +560,9 @@ void msm_dp_panel_enable_vsc_sdp(struct msm_dp_panel *msm_dp_panel, struct dp_sd
>  	drm_dbg_dp(panel->drm_dev, "vsc sdp enable=1\n");
>  
>  	pr_debug("misc settings = 0x%x\n", misc);
> -	msm_dp_write_link(panel, REG_DP_MISC1_MISC0 + misc_reg_offset, misc);
> +	msm_dp_write_link(panel, id > 1 ?
> +			  REG_DP_MSTLINK_MISC1_MISC0 : REG_DP_MISC1_MISC0 + misc_reg_offset,
> +			  misc);
>  
>  	msm_dp_panel_update_sdp(panel);
>  }
> @@ -521,26 +571,34 @@ void msm_dp_panel_disable_vsc_sdp(struct msm_dp_panel *msm_dp_panel)
>  {
>  	struct msm_dp_panel_private *panel =
>  		container_of(msm_dp_panel, struct msm_dp_panel_private, msm_dp_panel);
> +	u32 id = msm_dp_panel->stream_id;
>  	u32 cfg, cfg2, misc;
>  	u32 misc_reg_offset = 0;
>  	u32 sdp_cfg_offset = 0;
>  	u32 sdp_cfg2_offset = 0;
>  
> -	if (msm_dp_panel->stream_id == DP_STREAM_1) {
> +	if (id == DP_STREAM_1) {
>  		misc_reg_offset = REG_DP1_MISC1_MISC0 - REG_DP_MISC1_MISC0;
>  		sdp_cfg_offset = MMSS_DP1_SDP_CFG - MMSS_DP_SDP_CFG;
>  		sdp_cfg2_offset = MMSS_DP1_SDP_CFG2 - MMSS_DP_SDP_CFG2;
>  	}
>  
> -	cfg = msm_dp_read_link(panel, MMSS_DP_SDP_CFG + sdp_cfg_offset);
> -	cfg2 = msm_dp_read_link(panel, MMSS_DP_SDP_CFG2 + sdp_cfg2_offset);
> -	misc = msm_dp_read_link(panel, REG_DP_MISC1_MISC0 + misc_reg_offset);
> +	cfg = msm_dp_read_link(panel, id > 1 ?
> +			       MMSS_DP_MSTLINK_SDP_CFG : MMSS_DP_SDP_CFG + sdp_cfg_offset);
> +	cfg2 = msm_dp_read_link(panel, id > 1 ?
> +				MMSS_DP_MSTLINK_SDP_CFG2 : MMSS_DP_SDP_CFG2 + sdp_cfg2_offset);
> +	misc = msm_dp_read_link(panel, id > 1 ?
> +				REG_DP_MSTLINK_MISC1_MISC0 : REG_DP_MISC1_MISC0 + misc_reg_offset);
>  
>  	cfg &= ~GEN0_SDP_EN;
> -	msm_dp_write_link(panel, MMSS_DP_SDP_CFG + sdp_cfg_offset, cfg);
> -
>  	cfg2 &= ~GENERIC0_SDPSIZE_VALID;
> -	msm_dp_write_link(panel, MMSS_DP_SDP_CFG2 + sdp_cfg2_offset, cfg2);
> +
> +	msm_dp_write_link(panel, id > 1 ?
> +			  MMSS_DP_MSTLINK_SDP_CFG : MMSS_DP_SDP_CFG + sdp_cfg_offset,
> +			  cfg);
> +	msm_dp_write_link(panel, id > 1 ?
> +			  MMSS_DP_MSTLINK_SDP_CFG2 : MMSS_DP_SDP_CFG2 + sdp_cfg2_offset,
> +			  cfg2);
>  
>  	/* switch back to MSA */
>  	misc &= ~DP_MISC1_VSC_SDP;
> @@ -548,7 +606,9 @@ void msm_dp_panel_disable_vsc_sdp(struct msm_dp_panel *msm_dp_panel)
>  	drm_dbg_dp(panel->drm_dev, "vsc sdp enable=0\n");
>  
>  	pr_debug("misc settings = 0x%x\n", misc);
> -	msm_dp_write_link(panel, REG_DP_MISC1_MISC0 + misc_reg_offset, misc);
> +	msm_dp_write_link(panel, id > 1 ?
> +			  REG_DP_MSTLINK_MISC1_MISC0 : REG_DP_MISC1_MISC0 + misc_reg_offset,
> +			  misc);
>  
>  	msm_dp_panel_update_sdp(panel);
>  }
> @@ -598,6 +658,7 @@ static int msm_dp_panel_setup_vsc_sdp_yuv_420(struct msm_dp_panel *msm_dp_panel)
>  
>  int msm_dp_panel_timing_cfg(struct msm_dp_panel *msm_dp_panel, bool wide_bus_en)
>  {
> +	u32 id = msm_dp_panel->stream_id;
>  	u32 data, total_ver, total_hor;
>  	struct msm_dp_panel_private *panel;
>  	struct drm_display_mode *drm_mode;
> @@ -621,7 +682,7 @@ int msm_dp_panel_timing_cfg(struct msm_dp_panel *msm_dp_panel, bool wide_bus_en)
>  		drm_mode->vsync_start - drm_mode->vdisplay,
>  		drm_mode->vsync_end - drm_mode->vsync_start);
>  
> -	if (msm_dp_panel->stream_id == DP_STREAM_1)
> +	if (id == DP_STREAM_1)
>  		offset = REG_DP1_TOTAL_HOR_VER - REG_DP_TOTAL_HOR_VER;
>  
>  	total_hor = drm_mode->htotal;
> @@ -654,10 +715,18 @@ int msm_dp_panel_timing_cfg(struct msm_dp_panel *msm_dp_panel, bool wide_bus_en)
>  
>  	msm_dp_active = data;
>  
> -	msm_dp_write_link(panel, REG_DP_TOTAL_HOR_VER + offset, total);
> -	msm_dp_write_link(panel, REG_DP_START_HOR_VER_FROM_SYNC  + offset, sync_start);
> -	msm_dp_write_link(panel, REG_DP_HSYNC_VSYNC_WIDTH_POLARITY + offset, width_blanking);
> -	msm_dp_write_link(panel, REG_DP_ACTIVE_HOR_VER  + offset, msm_dp_active);
> +	msm_dp_write_link(panel,
> +			  id > 1 ? REG_DP_MSTLINK_TOTAL_HOR_VER :
> +			  REG_DP_TOTAL_HOR_VER + offset, total);
> +	msm_dp_write_link(panel,
> +			  id > 1 ? REG_DP_MSTLINK_START_HOR_VER_FROM_SYNC :
> +			  REG_DP_START_HOR_VER_FROM_SYNC + offset, sync_start);
> +	msm_dp_write_link(panel,
> +			  id > 1 ? REG_DP_MSTLINK_HSYNC_VSYNC_WIDTH_POLARITY :
> +			  REG_DP_HSYNC_VSYNC_WIDTH_POLARITY + offset, width_blanking);
> +	msm_dp_write_link(panel,
> +			  id > 1 ? REG_DP_MSTLINK_ACTIVE_HOR_VER :
> +			  REG_DP_ACTIVE_HOR_VER + offset, msm_dp_active);
>  
>  	reg = msm_dp_read_pn(panel, MMSS_DP_INTF_CONFIG);
>  	if (wide_bus_en)
> @@ -726,6 +795,8 @@ int msm_dp_panel_init_panel_info(struct msm_dp_panel *msm_dp_panel)
>  struct msm_dp_panel *msm_dp_panel_get(struct device *dev, struct drm_dp_aux *aux,
>  			      struct msm_dp_link *link,
>  			      void __iomem *link_base,
> +			      void __iomem *mst2link_base,
> +			      void __iomem *mst3link_base,
>  			      void __iomem *pixel_base)
>  {
>  	struct msm_dp_panel_private *panel;
> @@ -745,6 +816,8 @@ struct msm_dp_panel *msm_dp_panel_get(struct device *dev, struct drm_dp_aux *aux
>  	panel->link = link;
>  	panel->link_base = link_base;
>  	panel->pixel_base = pixel_base;
> +	panel->mst2link_base = mst2link_base;
> +	panel->mst3link_base = mst3link_base;
>  
>  	msm_dp_panel = &panel->msm_dp_panel;
>  	msm_dp_panel->max_bw_code = DP_LINK_BW_8_1;
> diff --git a/drivers/gpu/drm/msm/dp/dp_panel.h b/drivers/gpu/drm/msm/dp/dp_panel.h
> index fe4ac3e47e17..4873c55bd693 100644
> --- a/drivers/gpu/drm/msm/dp/dp_panel.h
> +++ b/drivers/gpu/drm/msm/dp/dp_panel.h
> @@ -101,5 +101,7 @@ static inline bool is_lane_count_valid(u32 lane_count)
>  struct msm_dp_panel *msm_dp_panel_get(struct device *dev, struct drm_dp_aux *aux,
>  			      struct msm_dp_link *link,
>  			      void __iomem *link_base,
> +			      void __iomem *mst2link_base,
> +			      void __iomem *mst3link_base,
>  			      void __iomem *pixel_base);
>  #endif /* _DP_PANEL_H_ */
> diff --git a/drivers/gpu/drm/msm/dp/dp_reg.h b/drivers/gpu/drm/msm/dp/dp_reg.h
> index 295c1161e6b7..1c2d3d8d029d 100644
> --- a/drivers/gpu/drm/msm/dp/dp_reg.h
> +++ b/drivers/gpu/drm/msm/dp/dp_reg.h
> @@ -162,7 +162,6 @@
>  #define REG_DP_START_HOR_VER_FROM_SYNC		(0x00000020)
>  #define REG_DP_HSYNC_VSYNC_WIDTH_POLARITY	(0x00000024)
>  #define REG_DP_ACTIVE_HOR_VER			(0x00000028)
> -
>  #define REG_DP_MISC1_MISC0			(0x0000002C)
>  #define DP_MISC0_SYNCHRONOUS_CLK		(0x00000001)
>  #define DP_MISC0_COLORIMETRY_CFG_SHIFT		(0x00000001)
> @@ -343,6 +342,21 @@
>  #define MMSS_DP1_SDP_CFG2			(0x000004E4)
>  #define MMSS_DP1_SDP_CFG3			(0x000004E8)
>  
> +#define REG_DP_MSTLINK_CONFIGURATION_CTRL	(0x00000034)
> +#define REG_MSTLINK_SOFTWARE_MVID		(0x00000040)
> +#define REG_MSTLINK_SOFTWARE_NVID		(0x00000044)
> +#define REG_DP_MSTLINK_TOTAL_HOR_VER		(0x00000048)
> +#define REG_DP_MSTLINK_START_HOR_VER_FROM_SYNC	(0x0000004C)
> +#define REG_DP_MSTLINK_HSYNC_VSYNC_WIDTH_POLARITY	(0x00000050)
> +#define REG_DP_MSTLINK_ACTIVE_HOR_VER		(0x00000054)
> +#define REG_DP_MSTLINK_MISC1_MISC0		(0x00000058)
> +#define MMSS_DP_MSTLINK_GENERIC0_0		(0x000000BC)
> +#define MMSS_DP_MSTLINK_GENERIC0_1		(0x000000C0)
> +#define MMSS_DP_MSTLINK_GENERIC0_2		(0x000000C4)
> +#define MMSS_DP_MSTLINK_SDP_CFG			(0x0000010c)
> +#define MMSS_DP_MSTLINK_SDP_CFG2		(0x0000011c)
> +#define MMSS_DP_MSTLINK_SDP_CFG3		(0x00000114)
> +
>  #define MMSS_DP_ASYNC_FIFO_CONFIG		(0x00000088)
>  
>  #define REG_DP_PHY_AUX_INTERRUPT_CLEAR          (0x0000004C)
> 
> -- 
> 2.43.0
> 

-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 18/39] drm/msm/dp: add support to send ACT packets for MST
  2026-04-10  9:33 ` [PATCH v4 18/39] drm/msm/dp: add support to send ACT packets for MST Yongxing Mou
@ 2026-04-11 18:57   ` Dmitry Baryshkov
  2026-06-15  8:21     ` Yongxing Mou
  0 siblings, 1 reply; 128+ messages in thread
From: Dmitry Baryshkov @ 2026-04-11 18:57 UTC (permalink / raw)
  To: Yongxing Mou
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar

On Fri, Apr 10, 2026 at 05:33:53PM +0800, Yongxing Mou wrote:
> From: Abhinav Kumar <quic_abhinavk@quicinc.com>
> 
> Whenever virtual channel slot allocation changes, the DP
> source must send the action control trigger sequence to notify
> the sink about the same. This would be applicable during the
> start and stop of the pixel stream. Add the infrastructure
> to be able to send ACT packets for the DP controller when
> operating in MST mode.
> 
> Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
> ---
>  drivers/gpu/drm/msm/dp/dp_ctrl.c    | 43 +++++++++++++++++++++++++++++++++++--
>  drivers/gpu/drm/msm/dp/dp_ctrl.h    |  3 ++-
>  drivers/gpu/drm/msm/dp/dp_display.c |  3 ++-
>  drivers/gpu/drm/msm/dp/dp_display.h |  1 +
>  drivers/gpu/drm/msm/dp/dp_reg.h     |  2 ++
>  5 files changed, 48 insertions(+), 4 deletions(-)
> 
> diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c
> index 1109b2df21be..6f25145ef214 100644
> --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c
> +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c
> @@ -143,6 +143,7 @@ struct msm_dp_ctrl_private {
>  	bool core_clks_on;
>  	bool link_clks_on;
>  	bool stream_clks_on[DP_STREAM_MAX];
> +	bool mst_active;
>  };
>  
>  static inline u32 msm_dp_read_ahb(const struct msm_dp_ctrl_private *ctrl, u32 offset)
> @@ -228,6 +229,32 @@ static int msm_dp_aux_link_configure(struct drm_dp_aux *aux,
>  	return err;
>  }
>  
> +int msm_dp_ctrl_mst_send_act(struct msm_dp_ctrl *msm_dp_ctrl)
> +{
> +	struct msm_dp_ctrl_private *ctrl;
> +	bool act_complete;
> +
> +	ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl);
> +
> +	if (!ctrl->mst_active)
> +		return 0;
> +
> +	msm_dp_write_link(ctrl, 0, REG_DP_MST_ACT, 0x1);
> +	/* make sure ACT signal is performed */
> +	wmb();
> +
> +	msleep(20); /* needs 1 frame time */

20 ms is 50fps. What if we have 30 or 25 fps?

> +
> +	act_complete = msm_dp_read_link(ctrl, 0, REG_DP_MST_ACT);
> +

Nit: drop empty line.

> +	if (!act_complete) {
> +		drm_dbg_dp(ctrl->drm_dev, "MST ACT trigger complete failed\n");
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +
>  /*
>   * NOTE: resetting DP controller will also clear any pending HPD related interrupts
>   */
> @@ -2081,6 +2108,10 @@ static int msm_dp_ctrl_link_maintenance(struct msm_dp_ctrl_private *ctrl)
>  
>  	msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, DP_STATE_CTRL_SEND_VIDEO);
>  
> +	ret = msm_dp_ctrl_mst_send_act(&ctrl->msm_dp_ctrl);
> +	if (ret)
> +		return ret;
> +
>  	ret = msm_dp_ctrl_wait4video_ready(ctrl);
>  end:
>  	return ret;
> @@ -2277,7 +2308,7 @@ static int msm_dp_ctrl_process_phy_test_request(struct msm_dp_ctrl_private *ctrl
>  	msm_dp_ctrl_off_pixel_clk(&ctrl->msm_dp_ctrl, ctrl->panel->stream_id);
>  	msm_dp_ctrl_off_link(&ctrl->msm_dp_ctrl);
>  
> -	ret = msm_dp_ctrl_on_link(&ctrl->msm_dp_ctrl);
> +	ret = msm_dp_ctrl_on_link(&ctrl->msm_dp_ctrl, false);
>  	if (ret) {
>  		DRM_ERROR("failed to enable DP link controller\n");
>  		return ret;
> @@ -2357,7 +2388,7 @@ static bool msm_dp_ctrl_channel_eq_ok(struct msm_dp_ctrl_private *ctrl)
>  	return drm_dp_channel_eq_ok(link_status, num_lanes);
>  }
>  
> -int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl)
> +int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl, bool mst_active)
>  {
>  	int rc = 0;
>  	struct msm_dp_ctrl_private *ctrl;
> @@ -2375,6 +2406,7 @@ int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl)
>  
>  	rate = ctrl->panel->link_info.rate;
>  	pixel_rate = ctrl->panel->msm_dp_mode.drm_mode.clock;
> +	ctrl->mst_active = mst_active;

Should it be set to active only when you actually activate the MST?

>  
>  	msm_dp_ctrl_core_clk_enable(&ctrl->msm_dp_ctrl);
>  
> @@ -2654,6 +2686,10 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *
>  
>  	msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, DP_STATE_CTRL_SEND_VIDEO);
>  

I'd rather have the `if mst_active` here rather than in the function.

> +	ret = msm_dp_ctrl_mst_send_act(msm_dp_ctrl);
> +	if (ret)
> +		return ret;
> +
>  	ret = msm_dp_ctrl_wait4video_ready(ctrl);
>  	if (ret)
>  		return ret;
> @@ -2693,6 +2729,8 @@ void msm_dp_ctrl_off_link(struct msm_dp_ctrl *msm_dp_ctrl)
>  
>  	msm_dp_ctrl_reset(&ctrl->msm_dp_ctrl);
>  
> +	ctrl->mst_active = false;
> +
>  	dev_pm_opp_set_rate(ctrl->dev, 0);
>  	msm_dp_ctrl_link_clk_disable(&ctrl->msm_dp_ctrl);
>  
> @@ -2876,6 +2914,7 @@ struct msm_dp_ctrl *msm_dp_ctrl_get(struct device *dev, struct msm_dp_link *link
>  	ctrl->link_base = link_base;
>  	ctrl->mst2link_base = mst2link_base;
>  	ctrl->mst3link_base = mst3link_base;
> +	ctrl->mst_active = false;
>  
>  	ret = msm_dp_ctrl_clk_init(&ctrl->msm_dp_ctrl, max_stream);
>  	if (ret) {
> diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.h b/drivers/gpu/drm/msm/dp/dp_ctrl.h
> index e72d501ac1ce..f82fd96e412a 100644
> --- a/drivers/gpu/drm/msm/dp/dp_ctrl.h
> +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.h
> @@ -16,7 +16,7 @@ struct msm_dp_ctrl {
>  
>  struct phy;
>  
> -int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl);
> +int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl, bool mst_active);
>  int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *msm_dp_panel);
>  int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train);
>  void msm_dp_ctrl_off_link(struct msm_dp_ctrl *msm_dp_ctrl);
> @@ -51,4 +51,5 @@ void msm_dp_ctrl_disable_irq(struct msm_dp_ctrl *msm_dp_ctrl);
>  
>  void msm_dp_ctrl_reinit_phy(struct msm_dp_ctrl *msm_dp_ctrl);
>  int msm_dp_ctrl_get_stream_cnt(struct msm_dp_ctrl *dp_ctrl);
> +int msm_dp_ctrl_mst_send_act(struct msm_dp_ctrl *msm_dp_ctrl);
>  #endif /* _DP_CTRL_H_ */
> diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
> index a924fbd825f7..80bb5fc4003f 100644
> --- a/drivers/gpu/drm/msm/dp/dp_display.c
> +++ b/drivers/gpu/drm/msm/dp/dp_display.c
> @@ -660,7 +660,7 @@ static int msm_dp_display_prepare(struct msm_dp_display_private *dp)
>  		force_link_train = true;
>  	}
>  
> -	rc = msm_dp_ctrl_on_link(dp->ctrl);
> +	rc = msm_dp_ctrl_on_link(dp->ctrl, msm_dp_display->mst_active);
>  	if (rc)
>  		DRM_ERROR("Failed link training (rc=%d)\n", rc);
>  	// TODO: schedule drm_connector_set_link_status_property()
> @@ -1547,6 +1547,7 @@ void msm_dp_display_atomic_disable(struct msm_dp *dp)
>  	msm_dp_display = container_of(dp, struct msm_dp_display_private, msm_dp_display);
>  
>  	msm_dp_ctrl_push_idle(msm_dp_display->ctrl);
> +	msm_dp_ctrl_mst_send_act(msm_dp_display->ctrl);
>  }
>  
>  static void msm_dp_display_unprepare(struct msm_dp_display_private *dp)
> diff --git a/drivers/gpu/drm/msm/dp/dp_display.h b/drivers/gpu/drm/msm/dp/dp_display.h
> index b0cfdf215970..fdbe6e4871d9 100644
> --- a/drivers/gpu/drm/msm/dp/dp_display.h
> +++ b/drivers/gpu/drm/msm/dp/dp_display.h
> @@ -19,6 +19,7 @@ struct msm_dp {
>  	struct drm_bridge *bridge;
>  	bool audio_enabled;
>  	bool power_on;
> +	bool mst_active;
>  	unsigned int connector_type;
>  	bool is_edp;
>  
> diff --git a/drivers/gpu/drm/msm/dp/dp_reg.h b/drivers/gpu/drm/msm/dp/dp_reg.h
> index 1c2d3d8d029d..237325d52dbd 100644
> --- a/drivers/gpu/drm/msm/dp/dp_reg.h
> +++ b/drivers/gpu/drm/msm/dp/dp_reg.h
> @@ -156,6 +156,8 @@
>  #define DP_CONFIGURATION_CTRL_BPC_SHIFT		(0x08)
>  #define DP_CONFIGURATION_CTRL_LSCLK_DIV_SHIFT	(0x0D)
>  
> +#define REG_DP_MST_ACT				(0x00000500)
> +
>  #define REG_DP_SOFTWARE_MVID			(0x00000010)
>  #define REG_DP_SOFTWARE_NVID			(0x00000018)
>  #define REG_DP_TOTAL_HOR_VER			(0x0000001C)
> 
> -- 
> 2.43.0
> 

-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 19/39] drm/msm/dp: Add support to enable MST in mainlink control
  2026-04-10  9:33 ` [PATCH v4 19/39] drm/msm/dp: Add support to enable MST in mainlink control Yongxing Mou
@ 2026-04-11 18:59   ` Dmitry Baryshkov
  0 siblings, 0 replies; 128+ messages in thread
From: Dmitry Baryshkov @ 2026-04-11 18:59 UTC (permalink / raw)
  To: Yongxing Mou
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar

On Fri, Apr 10, 2026 at 05:33:54PM +0800, Yongxing Mou wrote:
> From: Abhinav Kumar <quic_abhinavk@quicinc.com>
> 
> Add support to program the MST enable bit in the mainlink control
> register when an MST session is active or being disabled.
> 
> Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
> ---
>  drivers/gpu/drm/msm/dp/dp_ctrl.c | 17 +++++++++++++++++
>  drivers/gpu/drm/msm/dp/dp_reg.h  |  4 ++++
>  2 files changed, 21 insertions(+)
> 

Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>


-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 20/39] drm/msm/dp: no need to update tu calculation for mst
  2026-04-10  9:33 ` [PATCH v4 20/39] drm/msm/dp: no need to update tu calculation for mst Yongxing Mou
@ 2026-04-11 19:00   ` Dmitry Baryshkov
  0 siblings, 0 replies; 128+ messages in thread
From: Dmitry Baryshkov @ 2026-04-11 19:00 UTC (permalink / raw)
  To: Yongxing Mou
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar

On Fri, Apr 10, 2026 at 05:33:55PM +0800, Yongxing Mou wrote:
> From: Abhinav Kumar <quic_abhinavk@quicinc.com>
> 
> DP stream is transmitted in transfer units only for SST
> case, there is no need to calculate and program TU parameters
> for MST case. Skip the TU programming for MST cases.
> 
> Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
> ---
>  drivers/gpu/drm/msm/dp/dp_ctrl.c | 3 ++-
>  1 file changed, 2 insertions(+), 1 deletion(-)
> 

Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>


-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 22/39] drm/msm/dp: Add support for sending VCPF packets in DP controller
  2026-04-10  9:33 ` [PATCH v4 22/39] drm/msm/dp: Add support for sending VCPF packets in DP controller Yongxing Mou
@ 2026-04-11 19:24   ` Dmitry Baryshkov
  2026-06-15  8:35     ` Yongxing Mou
  0 siblings, 1 reply; 128+ messages in thread
From: Dmitry Baryshkov @ 2026-04-11 19:24 UTC (permalink / raw)
  To: Yongxing Mou
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar

On Fri, Apr 10, 2026 at 05:33:57PM +0800, Yongxing Mou wrote:
> From: Abhinav Kumar <quic_abhinavk@quicinc.com>
> 
> The VC Payload Fill (VCPF) sequence is inserted by the DP controller
> when stream symbols are absent, typically before a stream is disabled.
> This patch adds support for triggering the VCPF sequence in the MSM DP
> controller.
> 
> Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
> Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
> ---
>  drivers/gpu/drm/msm/dp/dp_ctrl.c    | 55 ++++++++++++++++++++++++++++++++++---
>  drivers/gpu/drm/msm/dp/dp_ctrl.h    |  2 +-
>  drivers/gpu/drm/msm/dp/dp_display.c |  2 +-
>  drivers/gpu/drm/msm/dp/dp_reg.h     |  5 ++++
>  4 files changed, 58 insertions(+), 6 deletions(-)
> 
> diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c
> index e64f81bc8c36..9907f2e56e65 100644
> --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c
> +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c
> @@ -65,9 +65,18 @@
>  	(PSR_UPDATE_MASK | PSR_CAPTURE_MASK | PSR_EXIT_MASK | \
>  	PSR_UPDATE_ERROR_MASK | PSR_WAKE_ERROR_MASK)
>  
> +#define DP_INTERRUPT_STATUS5 \
> +	(DP_INTR_DP0_VCPF_SENT | DP_INTR_DP1_VCPF_SENT)
> +#define DP_INTERRUPT_STATUS5_MASK \
> +	(DP_INTERRUPT_STATUS5 << DP_INTERRUPT_STATUS_MASK_SHIFT)
> +
>  #define DP_CTRL_INTR_READY_FOR_VIDEO     BIT(0)
>  #define DP_CTRL_INTR_IDLE_PATTERN_SENT  BIT(3)
>  
> +#define DP_DP0_PUSH_VCPF		BIT(12)
> +#define DP_DP1_PUSH_VCPF		BIT(14)
> +#define DP_MSTLINK_PUSH_VCPF		BIT(12)

dp_reg.h, under corresponding registers.

> +
>  #define MR_LINK_TRAINING1  0x8
>  #define MR_LINK_SYMBOL_ERM 0x80
>  #define MR_LINK_PRBS7 0x100
> @@ -405,6 +414,8 @@ void msm_dp_ctrl_enable_irq(struct msm_dp_ctrl *msm_dp_ctrl)
>  			DP_INTERRUPT_STATUS1_MASK);
>  	msm_dp_write_ahb(ctrl, REG_DP_INTR_STATUS2,
>  			DP_INTERRUPT_STATUS2_MASK);
> +	msm_dp_write_ahb(ctrl, REG_DP_INTR_STATUS5,
> +			 DP_INTERRUPT_STATUS5_MASK);
>  }
>  
>  void msm_dp_ctrl_disable_irq(struct msm_dp_ctrl *msm_dp_ctrl)
> @@ -414,6 +425,7 @@ void msm_dp_ctrl_disable_irq(struct msm_dp_ctrl *msm_dp_ctrl)
>  
>  	msm_dp_write_ahb(ctrl, REG_DP_INTR_STATUS, 0x00);
>  	msm_dp_write_ahb(ctrl, REG_DP_INTR_STATUS2, 0x00);
> +	msm_dp_write_ahb(ctrl, REG_DP_INTR_STATUS5, 0x00);
>  }
>  
>  static u32 msm_dp_ctrl_get_psr_interrupt(struct msm_dp_ctrl_private *ctrl)
> @@ -433,6 +445,20 @@ static void msm_dp_ctrl_config_psr_interrupt(struct msm_dp_ctrl_private *ctrl)
>  	msm_dp_write_ahb(ctrl, REG_DP_INTR_MASK4, DP_INTERRUPT_MASK4);
>  }
>  
> +static u32 msm_dp_ctrl_get_mst_interrupt(struct msm_dp_ctrl_private *ctrl)
> +{
> +	u32 intr, intr_ack;
> +
> +	intr = msm_dp_read_ahb(ctrl, REG_DP_INTR_STATUS5);
> +	intr &= ~DP_INTERRUPT_STATUS5_MASK;
> +	intr_ack = (intr & DP_INTERRUPT_STATUS5)
> +			<< DP_INTERRUPT_STATUS_ACK_SHIFT;
> +	msm_dp_write_ahb(ctrl, REG_DP_INTR_STATUS5,
> +			 intr_ack | DP_INTERRUPT_STATUS5_MASK);
> +
> +	return intr;
> +}
> +
>  static void msm_dp_ctrl_psr_mainlink_enable(struct msm_dp_ctrl_private *ctrl)
>  {
>  	u32 val;
> @@ -516,14 +542,28 @@ static bool msm_dp_ctrl_mainlink_ready(struct msm_dp_ctrl_private *ctrl)
>  	return true;
>  }
>  
> -void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl)
> +void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *msm_dp_panel)
>  {
>  	struct msm_dp_ctrl_private *ctrl;
> +	u32 state = 0x0;
>  
>  	ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl);
>  
> +	if (!ctrl->mst_active)
> +		state |= DP_STATE_CTRL_PUSH_IDLE;
> +	else if (msm_dp_panel->stream_id == DP_STREAM_0)
> +		state |= DP_DP0_PUSH_VCPF;
> +	else if (msm_dp_panel->stream_id == DP_STREAM_1)
> +		state |= DP_DP1_PUSH_VCPF;
> +	else
> +		state |= DP_MSTLINK_PUSH_VCPF;
> +
>  	reinit_completion(&ctrl->idle_comp);

And there can't be two streams wanting to push idle at the same time? 

> -	msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, DP_STATE_CTRL_PUSH_IDLE);
> +
> +	msm_dp_write_link(ctrl, msm_dp_panel->stream_id,
> +			  msm_dp_panel->stream_id > 1 ?
> +			  REG_DP_MSTLINK_STATE_CTRL : REG_DP_STATE_CTRL,
> +			  state);
>  
>  	if (!wait_for_completion_timeout(&ctrl->idle_comp,
>  			IDLE_PATTERN_COMPLETION_TIMEOUT_JIFFIES))
> @@ -2073,7 +2113,7 @@ void msm_dp_ctrl_set_psr(struct msm_dp_ctrl *msm_dp_ctrl, bool enter)
>  			return;
>  		}
>  
> -		msm_dp_ctrl_push_idle(msm_dp_ctrl);
> +		msm_dp_ctrl_push_idle(msm_dp_ctrl, ctrl->panel);
>  		msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, 0);
>  
>  		msm_dp_ctrl_psr_mainlink_disable(ctrl);
> @@ -2183,7 +2223,7 @@ static int msm_dp_ctrl_link_maintenance(struct msm_dp_ctrl_private *ctrl)
>  	int ret = 0;
>  	int training_step = DP_TRAINING_NONE;
>  
> -	msm_dp_ctrl_push_idle(&ctrl->msm_dp_ctrl);
> +	msm_dp_ctrl_push_idle(&ctrl->msm_dp_ctrl, ctrl->panel);

Which panel are we passing and why? It feels to me that we have two
different cases, one for the MST stream and another one for the SST
link. Can we handle them separately? (note: I might be wrong here,
please correct me if I'm wrong).

>  
>  	ctrl->link->phy_params.p_level = 0;
>  	ctrl->link->phy_params.v_level = 0;
> @@ -3005,6 +3045,13 @@ irqreturn_t msm_dp_ctrl_isr(struct msm_dp_ctrl *msm_dp_ctrl)
>  		ret = IRQ_HANDLED;
>  	}
>  
> +	isr = msm_dp_ctrl_get_mst_interrupt(ctrl);
> +	if (isr & (DP_INTR_DP0_VCPF_SENT | DP_INTR_DP1_VCPF_SENT)) {
> +		drm_dbg_dp(ctrl->drm_dev, "vcpf sent\n");
> +		complete(&ctrl->idle_comp);
> +		ret = IRQ_HANDLED;
> +	}
> +
>  	/* DP aux isr */
>  	isr = msm_dp_ctrl_get_aux_interrupt(ctrl);
>  	if (isr)
> diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.h b/drivers/gpu/drm/msm/dp/dp_ctrl.h
> index c59338199399..cfe7e4496943 100644
> --- a/drivers/gpu/drm/msm/dp/dp_ctrl.h
> +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.h
> @@ -22,7 +22,7 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl,
>  int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train);
>  void msm_dp_ctrl_off_link(struct msm_dp_ctrl *msm_dp_ctrl);
>  void msm_dp_ctrl_off_pixel_clk(struct msm_dp_ctrl *msm_dp_ctrl, enum msm_dp_stream_id stream_id);
> -void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl);
> +void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *msm_dp_panel);
>  irqreturn_t msm_dp_ctrl_isr(struct msm_dp_ctrl *msm_dp_ctrl);
>  void msm_dp_ctrl_handle_sink_request(struct msm_dp_ctrl *msm_dp_ctrl);
>  struct msm_dp_ctrl *msm_dp_ctrl_get(struct device *dev,
> diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
> index e0bf4dffa6af..e8028402f748 100644
> --- a/drivers/gpu/drm/msm/dp/dp_display.c
> +++ b/drivers/gpu/drm/msm/dp/dp_display.c
> @@ -1557,7 +1557,7 @@ void msm_dp_display_atomic_disable(struct msm_dp *msm_dp_display)
>  
>  	dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display);
>  
> -	msm_dp_ctrl_push_idle(dp->ctrl);
> +	msm_dp_ctrl_push_idle(dp->ctrl, dp->panel);
>  	msm_dp_ctrl_mst_stream_channel_slot_setup(dp->ctrl);
>  	msm_dp_ctrl_mst_send_act(dp->ctrl);
>  }
> diff --git a/drivers/gpu/drm/msm/dp/dp_reg.h b/drivers/gpu/drm/msm/dp/dp_reg.h
> index 835a55446868..65695fcb48d0 100644
> --- a/drivers/gpu/drm/msm/dp/dp_reg.h
> +++ b/drivers/gpu/drm/msm/dp/dp_reg.h
> @@ -42,9 +42,13 @@
>  #define DP_INTR_FRAME_END		BIT(6)
>  #define DP_INTR_CRC_UPDATED		BIT(9)
>  
> +#define DP_INTR_DP0_VCPF_SENT		BIT(0)
> +#define DP_INTR_DP1_VCPF_SENT		BIT(3)
> +
>  #define REG_DP_INTR_STATUS3			(0x00000028)
>  
>  #define REG_DP_INTR_STATUS4			(0x0000002C)
> +#define REG_DP_INTR_STATUS5			(0x00000034)
>  #define PSR_UPDATE_INT				(0x00000001)
>  #define PSR_CAPTURE_INT				(0x00000004)
>  #define PSR_EXIT_INT				(0x00000010)
> @@ -356,6 +360,7 @@
>  #define REG_DP_DP0_RG				(0x000004F8)
>  #define REG_DP_DP1_RG				(0x000004FC)
>  
> +#define REG_DP_MSTLINK_STATE_CTRL		(0x00000000)
>  #define REG_DP_MSTLINK_CONFIGURATION_CTRL	(0x00000034)
>  #define REG_DP_MSTLINK_TIMESLOT_1_32		(0x00000038)
>  #define REG_DP_MSTLINK_TIMESLOT_33_63		(0x0000003C)
> 
> -- 
> 2.43.0
> 

-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 39/39] drm/msm/dp: Add MST stream support for supported DP controllers
  2026-04-10  9:34 ` [PATCH v4 39/39] drm/msm/dp: Add MST stream support for supported DP controllers Yongxing Mou
  2026-04-10 10:23   ` Konrad Dybcio
@ 2026-04-11 19:26   ` Dmitry Baryshkov
  1 sibling, 0 replies; 128+ messages in thread
From: Dmitry Baryshkov @ 2026-04-11 19:26 UTC (permalink / raw)
  To: Yongxing Mou
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel

On Fri, Apr 10, 2026 at 05:34:14PM +0800, Yongxing Mou wrote:
> Enables MST support for MSM DP controllers that support it, allowing each
> controller to handle up to two or four DisplayPort streams. All necessary
> MST support code was already implemented in the previous series of patches.

As I wrote earlier, we have this information in the DT. Please drop.

> 
> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
> ---
>  drivers/gpu/drm/msm/dp/dp_display.c | 42 ++++++++++++++++++++++++-------------
>  1 file changed, 28 insertions(+), 14 deletions(-)
> 
> diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
> index ca89e20b7563..f632b4f64ccc 100644
> --- a/drivers/gpu/drm/msm/dp/dp_display.c
> +++ b/drivers/gpu/drm/msm/dp/dp_display.c
> @@ -107,16 +107,21 @@ struct msm_dp_desc {
>  };
>  
>  static const struct msm_dp_desc msm_dp_desc_glymur[] = {
> -	{ .io_start = 0x0af54000, .id = MSM_DP_CONTROLLER_0, .wide_bus_supported = true },
> -	{ .io_start = 0x0af5c000, .id = MSM_DP_CONTROLLER_1, .wide_bus_supported = true },
> -	{ .io_start = 0x0af64000, .id = MSM_DP_CONTROLLER_2, .wide_bus_supported = true },
> +	{ .io_start = 0x0af54000, .id = MSM_DP_CONTROLLER_0, .wide_bus_supported = true,
> +	  .mst_streams = 2 },
> +	{ .io_start = 0x0af5c000, .id = MSM_DP_CONTROLLER_1, .wide_bus_supported = true,
> +	  .mst_streams = 2 },
> +	{ .io_start = 0x0af64000, .id = MSM_DP_CONTROLLER_2, .wide_bus_supported = true,
> +	  .mst_streams = 2 },
>  	{ .io_start = 0x0af6c000, .id = MSM_DP_CONTROLLER_3, .wide_bus_supported = true },
>  	{}
>  };
>  
>  static const struct msm_dp_desc msm_dp_desc_sa8775p[] = {
> -	{ .io_start = 0x0af54000, .id = MSM_DP_CONTROLLER_0, .wide_bus_supported = true },
> -	{ .io_start = 0x0af5c000, .id = MSM_DP_CONTROLLER_1, .wide_bus_supported = true },
> +	{ .io_start = 0x0af54000, .id = MSM_DP_CONTROLLER_0, .wide_bus_supported = true,
> +	  .mst_streams = 4},
> +	{ .io_start = 0x0af5c000, .id = MSM_DP_CONTROLLER_1, .wide_bus_supported = true,
> +	  .mst_streams = 2},
>  	{ .io_start = 0x22154000, .id = MSM_DP_CONTROLLER_0, .wide_bus_supported = true },
>  	{ .io_start = 0x2215c000, .id = MSM_DP_CONTROLLER_1, .wide_bus_supported = true },
>  	{}
> @@ -133,38 +138,47 @@ static const struct msm_dp_desc msm_dp_desc_sc7180[] = {
>  };
>  
>  static const struct msm_dp_desc msm_dp_desc_sc7280[] = {
> -	{ .io_start = 0x0ae90000, .id = MSM_DP_CONTROLLER_0, .wide_bus_supported = true },
> +	{ .io_start = 0x0ae90000, .id = MSM_DP_CONTROLLER_0, .wide_bus_supported = true,
> +	  .mst_streams = 2 },
>  	{ .io_start = 0x0aea0000, .id = MSM_DP_CONTROLLER_1, .wide_bus_supported = true },
>  	{}
>  };
>  
>  static const struct msm_dp_desc msm_dp_desc_sc8180x[] = {
> -	{ .io_start = 0x0ae90000, .id = MSM_DP_CONTROLLER_0, .wide_bus_supported = true },
> +	{ .io_start = 0x0ae90000, .id = MSM_DP_CONTROLLER_0, .wide_bus_supported = true,
> +	  .mst_streams = 2 },
>  	{ .io_start = 0x0ae98000, .id = MSM_DP_CONTROLLER_1, .wide_bus_supported = true },
>  	{ .io_start = 0x0ae9a000, .id = MSM_DP_CONTROLLER_2, .wide_bus_supported = true },
>  	{}
>  };
>  
>  static const struct msm_dp_desc msm_dp_desc_sc8280xp[] = {
> -	{ .io_start = 0x0ae90000, .id = MSM_DP_CONTROLLER_0, .wide_bus_supported = true },
> -	{ .io_start = 0x0ae98000, .id = MSM_DP_CONTROLLER_1, .wide_bus_supported = true },
> +	{ .io_start = 0x0ae90000, .id = MSM_DP_CONTROLLER_0, .wide_bus_supported = true,
> +	  .mst_streams = 2},
> +	{ .io_start = 0x0ae98000, .id = MSM_DP_CONTROLLER_1, .wide_bus_supported = true,
> +	  .mst_streams = 2},
>  	{ .io_start = 0x0ae9a000, .id = MSM_DP_CONTROLLER_2, .wide_bus_supported = true },
>  	{ .io_start = 0x0aea0000, .id = MSM_DP_CONTROLLER_3, .wide_bus_supported = true },
> -	{ .io_start = 0x22090000, .id = MSM_DP_CONTROLLER_0, .wide_bus_supported = true },
> -	{ .io_start = 0x22098000, .id = MSM_DP_CONTROLLER_1, .wide_bus_supported = true },
> +	{ .io_start = 0x22090000, .id = MSM_DP_CONTROLLER_0, .wide_bus_supported = true,
> +	  .mst_streams = 2},
> +	{ .io_start = 0x22098000, .id = MSM_DP_CONTROLLER_1, .wide_bus_supported = true,
> +	  .mst_streams = 2},
>  	{ .io_start = 0x2209a000, .id = MSM_DP_CONTROLLER_2, .wide_bus_supported = true },
>  	{ .io_start = 0x220a0000, .id = MSM_DP_CONTROLLER_3, .wide_bus_supported = true },
>  	{}
>  };
>  
>  static const struct msm_dp_desc msm_dp_desc_sm8650[] = {
> -	{ .io_start = 0x0af54000, .id = MSM_DP_CONTROLLER_0, .wide_bus_supported = true },
> +	{ .io_start = 0x0af54000, .id = MSM_DP_CONTROLLER_0, .wide_bus_supported = true,
> +	  .mst_streams = 2 },
>  	{}
>  };
>  
>  static const struct msm_dp_desc msm_dp_desc_x1e80100[] = {
> -	{ .io_start = 0x0ae90000, .id = MSM_DP_CONTROLLER_0, .wide_bus_supported = true },
> -	{ .io_start = 0x0ae98000, .id = MSM_DP_CONTROLLER_1, .wide_bus_supported = true },
> +	{ .io_start = 0x0ae90000, .id = MSM_DP_CONTROLLER_0, .wide_bus_supported = true,
> +	  .mst_streams = 2},
> +	{ .io_start = 0x0ae98000, .id = MSM_DP_CONTROLLER_1, .wide_bus_supported = true,
> +	  .mst_streams = 2},
>  	{ .io_start = 0x0ae9a000, .id = MSM_DP_CONTROLLER_2, .wide_bus_supported = true },
>  	{ .io_start = 0x0aea0000, .id = MSM_DP_CONTROLLER_3, .wide_bus_supported = true },
>  	{}
> 
> -- 
> 2.43.0
> 

-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 32/39] drm/msm/dpu: initialize encoders per stream for DP MST
  2026-04-10  9:34 ` [PATCH v4 32/39] drm/msm/dpu: initialize encoders per stream for DP MST Yongxing Mou
@ 2026-04-11 19:31   ` Dmitry Baryshkov
  0 siblings, 0 replies; 128+ messages in thread
From: Dmitry Baryshkov @ 2026-04-11 19:31 UTC (permalink / raw)
  To: Yongxing Mou
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel

On Fri, Apr 10, 2026 at 05:34:07PM +0800, Yongxing Mou wrote:
> Simply initialize MST encoders for MST-capable DP controllers, and
> introduce msm_dp_get_mst_max_stream to query MST streams.
> 
> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
> ---
>  drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c | 14 +++++++++++++-
>  drivers/gpu/drm/msm/msm_drv.h           |  7 ++++++-
>  2 files changed, 19 insertions(+), 2 deletions(-)
> 

Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>


-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 37/39] drm/msm/dp: add HPD callback for dp MST
  2026-04-10  9:34 ` [PATCH v4 37/39] drm/msm/dp: add HPD callback for dp MST Yongxing Mou
@ 2026-04-11 22:00   ` Dmitry Baryshkov
  2026-06-15 10:05     ` Yongxing Mou
  0 siblings, 1 reply; 128+ messages in thread
From: Dmitry Baryshkov @ 2026-04-11 22:00 UTC (permalink / raw)
  To: Yongxing Mou
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar

On Fri, Apr 10, 2026 at 05:34:12PM +0800, Yongxing Mou wrote:
> From: Abhinav Kumar <quic_abhinavk@quicinc.com>
> 
> Add HPD callback for the MST module which shall be invoked from the
> dp_display's HPD handler to perform MST specific operations in case
> of HPD. In MST case, route the HPD messages to MST module.
> 
> Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
> ---
>  drivers/gpu/drm/msm/dp/dp_display.c | 23 +++++++++++++++++++----
>  drivers/gpu/drm/msm/dp/dp_mst_drm.c | 34 ++++++++++++++++++++++++++++++++++
>  drivers/gpu/drm/msm/dp/dp_mst_drm.h |  1 +
>  3 files changed, 54 insertions(+), 4 deletions(-)
> 
> diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
> index 919767945ba5..ca89e20b7563 100644
> --- a/drivers/gpu/drm/msm/dp/dp_display.c
> +++ b/drivers/gpu/drm/msm/dp/dp_display.c
> @@ -454,6 +454,9 @@ static int msm_dp_hpd_plug_handle(struct msm_dp_display_private *dp)
>  			dp->msm_dp_display.connector_type,
>  			dp->link->sink_count);
>  
> +	if (dp->plugged)
> +		return 0;
> +
>  	mutex_lock(&dp->plugged_lock);
>  
>  	ret = pm_runtime_resume_and_get(&pdev->dev);
> @@ -556,12 +559,19 @@ static int msm_dp_irq_hpd_handle(struct msm_dp_display_private *dp)
>  {
>  	u32 sink_request;
>  	int rc = 0;
> +	struct msm_dp *msm_dp_display = &dp->msm_dp_display;
>  
>  	/* irq_hpd can happen at either connected or disconnected state */
>  	drm_dbg_dp(dp->drm_dev, "Before, type=%d, sink_count=%d\n",
>  			dp->msm_dp_display.connector_type,
>  			dp->link->sink_count);
>  
> +	if (msm_dp_display->mst_active) {
> +		if (msm_dp_aux_is_link_connected(dp->aux) != ISR_DISCONNECTED)

Will this work for USB-C?

> +			msm_dp_mst_display_hpd_irq(&dp->msm_dp_display);
> +		return 0;
> +	}
> +
>  	/* check for any test request issued by sink */
>  	rc = msm_dp_link_process_request(dp->link);
>  	if (!rc) {
> @@ -1125,9 +1135,13 @@ static irqreturn_t msm_dp_display_irq_thread(int irq, void *dev_id)
>  				      connector_status_connected);
>  
>  	/* Send HPD as connected and distinguish it in the notifier */
> -	if (hpd_isr_status & DP_DP_IRQ_HPD_INT_MASK)
> -		drm_bridge_hpd_notify(dp->msm_dp_display.bridge,
> -				      connector_status_connected);
> +	if (hpd_isr_status & DP_DP_IRQ_HPD_INT_MASK) {
> +		if (dp->msm_dp_display.mst_active)
> +			msm_dp_irq_hpd_handle(dp);

No, don't touch this code. HPD notifications might be coming from the
other entities. This IRQ thread can only send the HPD notification.
There rest should be handled in the notifier.

> +		else
> +			drm_bridge_hpd_notify(dp->msm_dp_display.bridge,
> +					      connector_status_connected);
> +	}
>  
>  	ret = IRQ_HANDLED;
>  
> @@ -1793,7 +1807,8 @@ void msm_dp_bridge_hpd_notify(struct drm_bridge *bridge,
>  			msm_dp_hpd_plug_handle(dp);
>  		}
>  	} else {
> -		msm_dp_hpd_unplug_handle(dp);
> +		if (hpd_link_status == ISR_DISCONNECTED)

Why?

> +			msm_dp_hpd_unplug_handle(dp);
>  	}
>  
>  	pm_runtime_put_sync(&msm_dp_display->pdev->dev);
> diff --git a/drivers/gpu/drm/msm/dp/dp_mst_drm.c b/drivers/gpu/drm/msm/dp/dp_mst_drm.c
> index bb3898b1f6b1..71d3f63973e6 100644
> --- a/drivers/gpu/drm/msm/dp/dp_mst_drm.c
> +++ b/drivers/gpu/drm/msm/dp/dp_mst_drm.c
> @@ -490,6 +490,40 @@ int msm_dp_mst_display_set_mgr_state(struct msm_dp *dp_display, bool state)
>  	return rc;
>  }
>  
> +/* DP MST HPD IRQ callback */

Useless comment.

> +void msm_dp_mst_display_hpd_irq(struct msm_dp *dp_display)
> +{
> +	int rc;
> +	struct msm_dp_mst *mst = dp_display->msm_dp_mst;
> +	u8 ack[8] = {};
> +	u8 esi[4];
> +	unsigned int esi_res = DP_SINK_COUNT_ESI + 1;
> +	bool handled;
> +
> +	rc = drm_dp_dpcd_read_data(mst->dp_aux, DP_SINK_COUNT_ESI, esi, 4);
> +	if (rc < 0) {
> +		DRM_ERROR("DPCD sink status read failed, rlen=%d\n", rc);
> +		return;
> +	}
> +
> +	drm_dbg_dp(dp_display->drm_dev, "MST irq: esi1[0x%x] esi2[0x%x] esi3[%x]\n",
> +		   esi[1], esi[2], esi[3]);
> +
> +	rc = drm_dp_mst_hpd_irq_handle_event(&mst->mst_mgr, esi, ack, &handled);
> +
> +	/* ack the request */
> +	if (handled) {
> +		rc = drm_dp_dpcd_write_byte(mst->dp_aux, esi_res, ack[1]);
> +		if (rc < 0) {
> +			DRM_ERROR("DPCD esi_res failed. rc=%d\n", rc);
> +			return;
> +		}
> +
> +		drm_dp_mst_hpd_irq_send_new_request(&mst->mst_mgr);
> +	}
> +	drm_dbg_dp(dp_display->drm_dev, "MST display hpd_irq handled:%d rc:%d\n", handled, rc);
> +}
> +
>  /* DP MST Connector OPs */
>  static int
>  msm_dp_mst_connector_detect(struct drm_connector *connector,
> diff --git a/drivers/gpu/drm/msm/dp/dp_mst_drm.h b/drivers/gpu/drm/msm/dp/dp_mst_drm.h
> index 5d411529f681..08e145399cfc 100644
> --- a/drivers/gpu/drm/msm/dp/dp_mst_drm.h
> +++ b/drivers/gpu/drm/msm/dp/dp_mst_drm.h
> @@ -9,5 +9,6 @@
>  
>  int msm_dp_mst_init(struct msm_dp *dp_display, u32 max_streams, struct drm_dp_aux *drm_aux);
>  int msm_dp_mst_display_set_mgr_state(struct msm_dp *dp_display, bool state);
> +void msm_dp_mst_display_hpd_irq(struct msm_dp *dp_display);
>  
>  #endif /* _DP_MST_DRM_H_ */
> 
> -- 
> 2.43.0
> 

-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 36/39] drm/msm/dp: add connector abstraction for DP MST
  2026-04-10  9:34 ` [PATCH v4 36/39] drm/msm/dp: add connector abstraction for DP MST Yongxing Mou
@ 2026-04-11 23:30   ` Dmitry Baryshkov
  0 siblings, 0 replies; 128+ messages in thread
From: Dmitry Baryshkov @ 2026-04-11 23:30 UTC (permalink / raw)
  To: Yongxing Mou
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar

On Fri, Apr 10, 2026 at 05:34:11PM +0800, Yongxing Mou wrote:
> From: Abhinav Kumar <quic_abhinavk@quicinc.com>
> 
> Introduce an MST connector abstraction for DP MST, with each MST
> connector associated with a DP panel and connected through a DRM bridge
> to an MST encoder.
> 
> The connector is only used for MST helper callbacks, such as detect,
> get_modes, and get_encoder. Display enable/disable, hotplug handling,
> and modeset sequencing continue to be handled by the bridge path.
> 
> Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
> ---
>  drivers/gpu/drm/msm/dp/dp_mst_drm.c | 231 ++++++++++++++++++++++++++++++++++++
>  1 file changed, 231 insertions(+)
> 
> diff --git a/drivers/gpu/drm/msm/dp/dp_mst_drm.c b/drivers/gpu/drm/msm/dp/dp_mst_drm.c
> index 4df3ea5e36d0..bb3898b1f6b1 100644
> --- a/drivers/gpu/drm/msm/dp/dp_mst_drm.c
> +++ b/drivers/gpu/drm/msm/dp/dp_mst_drm.c
> @@ -7,6 +7,7 @@
>  #include <drm/drm_managed.h>
>  #include <drm/drm_bridge.h>
>  #include <drm/display/drm_dp_mst_helper.h>
> +#include <linux/pm_runtime.h>
>  
>  #include "dp_mst_drm.h"
>  #include "dp_panel.h"
> @@ -489,6 +490,235 @@ int msm_dp_mst_display_set_mgr_state(struct msm_dp *dp_display, bool state)
>  	return rc;
>  }
>  
> +/* DP MST Connector OPs */
> +static int
> +msm_dp_mst_connector_detect(struct drm_connector *connector,
> +			    struct drm_modeset_acquire_ctx *ctx,
> +			    bool force)
> +{
> +	struct msm_dp_mst_connector *mst_conn = to_dp_mst_connector(connector);
> +	struct msm_dp_mst *mst = mst_conn->dp_mst;
> +	struct msm_dp *dp_display = mst->msm_dp;
> +	struct device *dev = dp_display->drm_dev->dev;
> +	enum drm_connector_status status = connector_status_disconnected;
> +	int ret;
> +
> +	ret = pm_runtime_resume_and_get(dev);
> +	if (ret < 0)
> +		return status;
> +
> +	if (dp_display->mst_active)
> +		status = drm_dp_mst_detect_port(connector,
> +						ctx, &mst->mst_mgr, mst_conn->mst_port);
> +
> +	pm_runtime_put_autosuspend(dev);
> +
> +	return status;
> +}
> +
> +static int msm_dp_mst_connector_get_modes(struct drm_connector *connector)
> +{
> +	struct msm_dp_mst_connector *mst_conn = to_dp_mst_connector(connector);
> +	struct msm_dp_mst *mst = mst_conn->dp_mst;
> +	const struct drm_edid *drm_edid;
> +
> +	drm_edid = drm_dp_mst_edid_read(connector, &mst->mst_mgr, mst_conn->mst_port);
> +	drm_edid_connector_update(connector, drm_edid);

Missing drm_edid_free()

> +
> +	return drm_edid_connector_add_modes(connector);
> +}
> +
> +static enum drm_mode_status msm_dp_mst_connector_mode_valid(struct drm_connector *connector,
> +							    const struct drm_display_mode *mode)
> +{
> +	struct msm_dp_mst_connector *mst_conn;
> +	struct drm_dp_mst_port *mst_port;
> +	struct msm_dp *dp_display;
> +	int required_pbn;
> +
> +	if (drm_connector_is_unregistered(connector))
> +		return 0;
> +
> +	mst_conn = to_dp_mst_connector(connector);
> +	mst_port = mst_conn->mst_port;
> +	dp_display = mst_conn->dp_mst->msm_dp;
> +
> +	if (!mst_port)
> +		return MODE_ERROR;

Protective coding?

> +
> +	required_pbn = drm_dp_calc_pbn_mode(mode->clock, (6 * 3) << 4);

Can we actually support 18bpp? At least it deserves a FIXME for bpp
negotiation (for the DSC, etc.). More practically, we have YUV,
espcially YUV 4:2:0, which uses less than 18bpp, if I'm not mistaken.

> +
> +	if (required_pbn > mst_port->full_pbn) {
> +		drm_dbg_dp(dp_display->drm_dev, "mode:%s not supported.\n", mode->name);
> +		return MODE_CLOCK_HIGH;
> +	}
> +
> +	return msm_dp_display_mode_valid(dp_display, &connector->display_info, mode);
> +}
> +
> +static struct drm_encoder *
> +msm_dp_mst_atomic_best_encoder(struct drm_connector *connector, struct drm_atomic_state *state)
> +{
> +	struct msm_dp_mst_connector *mst_conn = to_dp_mst_connector(connector);
> +	struct msm_dp_mst *mst = mst_conn->dp_mst;
> +	struct msm_dp *dp_display = mst->msm_dp;
> +	struct drm_encoder *enc = NULL;
> +	struct msm_dp_mst_bridge_state *mst_bridge_state;
> +	u32 i;
> +	struct drm_connector_state *conn_state = drm_atomic_get_new_connector_state(state,
> +										    connector);
> +
> +	if (conn_state && conn_state->best_encoder)
> +		return conn_state->best_encoder;
> +
> +	for (i = 0; i < mst->max_streams; i++) {
> +		mst_bridge_state = msm_dp_mst_br_priv_state(state, mst->mst_bridge[i]);
> +		if (IS_ERR(mst_bridge_state))
> +			goto end;
> +
> +		if (mst_bridge_state->connector == connector) {
> +			enc = mst->mst_bridge[i]->encoder;
> +			goto end;
> +		}
> +	}

I thought you wanted to get rid of the msm_dp_mst_bridge_state. Let me
see...

> +
> +	for (i = 0; i < mst->max_streams; i++) {
> +		mst_bridge_state = msm_dp_mst_br_priv_state(state, mst->mst_bridge[i]);
> +
> +		if (!mst_bridge_state->connector) {
> +			mst_bridge_state->connector = connector;
> +			mst_bridge_state->msm_dp_panel = mst_conn->dp_panel;
> +			enc = mst->mst_bridge[i]->encoder;
> +			break;
> +		}
> +	}
> +
> +end:
> +	if (enc)
> +		drm_dbg_dp(dp_display->drm_dev, "MST connector:%d atomic best encoder:%d\n",
> +			   connector->base.id, i);
> +	else
> +		drm_dbg_dp(dp_display->drm_dev, "MST connector:%d atomic best encoder failed\n",
> +			   connector->base.id);
> +
> +	return enc;
> +}
> +
> +static int msm_dp_mst_connector_atomic_check(struct drm_connector *connector,
> +					     struct drm_atomic_state *state)
> +{
> +	struct msm_dp_mst_connector *mst_conn = to_dp_mst_connector(connector);
> +	struct msm_dp_mst *mst = mst_conn->dp_mst;
> +
> +	return drm_dp_atomic_release_time_slots(state, &mst->mst_mgr, mst_conn->mst_port);
> +}
> +
> +static void dp_mst_connector_destroy(struct drm_connector *connector)
> +{
> +	struct msm_dp_mst_connector *mst_conn = to_dp_mst_connector(connector);
> +
> +	drm_connector_cleanup(connector);
> +	drm_dp_mst_put_port_malloc(mst_conn->mst_port);
> +	kfree(mst_conn);
> +}
> +
> +/* DRM MST callbacks */
> +static const struct drm_connector_helper_funcs msm_dp_drm_mst_connector_helper_funcs = {
> +	.get_modes =    msm_dp_mst_connector_get_modes,
> +	.detect_ctx =   msm_dp_mst_connector_detect,
> +	.mode_valid =   msm_dp_mst_connector_mode_valid,
> +	.atomic_best_encoder = msm_dp_mst_atomic_best_encoder,
> +	.atomic_check = msm_dp_mst_connector_atomic_check,
> +};
> +
> +static const struct drm_connector_funcs msm_dp_drm_mst_connector_funcs = {
> +	.reset = drm_atomic_helper_connector_reset,
> +	.destroy = dp_mst_connector_destroy,
> +	.fill_modes = drm_helper_probe_single_connector_modes,
> +	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
> +	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
> +};
> +
> +static struct drm_connector *
> +msm_dp_mst_add_connector(struct drm_dp_mst_topology_mgr *mgr,
> +			 struct drm_dp_mst_port *port, const char *pathprop)
> +{
> +	struct msm_dp_mst *dp_mst;
> +	struct drm_device *dev;
> +	struct msm_dp *dp_display;
> +	struct msm_dp_mst_connector *mst_conn;
> +	struct drm_connector *connector;
> +	int rc, i;
> +
> +	dp_mst = container_of(mgr, struct msm_dp_mst, mst_mgr);
> +
> +	dp_display = dp_mst->msm_dp;
> +	dev = dp_display->drm_dev;
> +
> +	mst_conn = kzalloc_obj(*mst_conn);
> +
> +	if (!mst_conn)
> +		return NULL;
> +
> +	drm_modeset_lock_all(dev);

What for?

> +
> +	connector = &mst_conn->connector;
> +	rc = drm_connector_dynamic_init(dev, connector,
> +					&msm_dp_drm_mst_connector_funcs,
> +					DRM_MODE_CONNECTOR_DisplayPort, NULL);
> +	if (rc) {
> +		kfree(mst_conn);
> +		drm_modeset_unlock_all(dev);
> +		return NULL;
> +	}
> +
> +	mst_conn->dp_panel = msm_dp_display_get_panel(dp_display);
> +	if (!mst_conn->dp_panel) {
> +		DRM_ERROR("failed to get dp_panel for connector\n");
> +		kfree(mst_conn);
> +		drm_modeset_unlock_all(dev);
> +		return NULL;
> +	}
> +
> +	mst_conn->dp_panel->connector = connector;
> +	mst_conn->dp_mst = dp_mst;
> +
> +	drm_connector_helper_add(connector, &msm_dp_drm_mst_connector_helper_funcs);
> +
> +	if (connector->funcs->reset)
> +		connector->funcs->reset(connector);
> +
> +	/* add all encoders as possible encoders */
> +	for (i = 0; i < dp_mst->max_streams; i++) {
> +		rc = drm_connector_attach_encoder(connector, dp_mst->mst_bridge[i]->encoder);
> +
> +		if (rc) {
> +			DRM_ERROR("failed to attach encoder to connector, %d\n", rc);
> +			kfree(mst_conn);
> +			drm_modeset_unlock_all(dev);
> +			return NULL;
> +		}
> +	}
> +
> +	mst_conn->mst_port = port;
> +	drm_dp_mst_get_port_malloc(mst_conn->mst_port);
> +
> +	drm_object_attach_property(&connector->base,
> +				   dev->mode_config.path_property, 0);
> +	drm_object_attach_property(&connector->base,
> +				   dev->mode_config.tile_property, 0);
> +	drm_connector_set_path_property(connector, pathprop);
> +	drm_modeset_unlock_all(dev);
> +
> +	drm_dbg_dp(dp_display->drm_dev, "add MST connector id:%d\n", connector->base.id);
> +
> +	return connector;
> +}
> +
> +static const struct drm_dp_mst_topology_cbs msm_dp_mst_drm_cbs = {
> +	.add_connector = msm_dp_mst_add_connector,
> +};
> +
>  int msm_dp_mst_init(struct msm_dp *dp_display, u32 max_streams, struct drm_dp_aux *drm_aux)
>  {
>  	struct drm_device *dev = dp_display->drm_dev;
> @@ -501,6 +731,7 @@ int msm_dp_mst_init(struct msm_dp *dp_display, u32 max_streams, struct drm_dp_au
>  		return -ENOMEM;
>  
>  	memset(&msm_dp_mst->mst_mgr, 0, sizeof(msm_dp_mst->mst_mgr));
> +	msm_dp_mst->mst_mgr.cbs = &msm_dp_mst_drm_cbs;
>  	conn_base_id = dp_display->connector->base.id;
>  	msm_dp_mst->msm_dp = dp_display;
>  	msm_dp_mst->max_streams = max_streams;
> 
> -- 
> 2.43.0
> 

-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 33/39] drm/msm/dp: initialize dp_mst module for each DP MST controller
  2026-04-10  9:34 ` [PATCH v4 33/39] drm/msm/dp: initialize dp_mst module for each DP MST controller Yongxing Mou
@ 2026-04-11 23:36   ` Dmitry Baryshkov
  0 siblings, 0 replies; 128+ messages in thread
From: Dmitry Baryshkov @ 2026-04-11 23:36 UTC (permalink / raw)
  To: Yongxing Mou
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar

On Fri, Apr 10, 2026 at 05:34:08PM +0800, Yongxing Mou wrote:
> From: Abhinav Kumar <quic_abhinavk@quicinc.com>
> 
> For each MST capable DP controller, initialize a dp_mst module to
> manage its DP MST operations. The DP MST module for each controller
> is the central entity to manage its topology related operations as
> well as interfacing with the rest of the DP driver.
> 
> Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
> ---
>  drivers/gpu/drm/msm/Makefile            |  3 +-
>  drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c |  6 +++
>  drivers/gpu/drm/msm/dp/dp_display.c     | 18 ++++++++
>  drivers/gpu/drm/msm/dp/dp_display.h     |  2 +
>  drivers/gpu/drm/msm/dp/dp_mst_drm.c     | 73 +++++++++++++++++++++++++++++++++
>  drivers/gpu/drm/msm/dp/dp_mst_drm.h     | 13 ++++++
>  drivers/gpu/drm/msm/msm_drv.h           |  6 +++
>  7 files changed, 120 insertions(+), 1 deletion(-)
> 
> diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
> index 8b94c5f1cb68..1d8426876aa1 100644
> --- a/drivers/gpu/drm/msm/Makefile
> +++ b/drivers/gpu/drm/msm/Makefile
> @@ -144,7 +144,8 @@ msm-display-$(CONFIG_DRM_MSM_DP)+= dp/dp_aux.o \
>  	dp/dp_link.o \
>  	dp/dp_panel.o \
>  	dp/dp_audio.o \
> -	dp/dp_utils.o
> +	dp/dp_utils.o \
> +	dp/dp_mst_drm.o
>  
>  msm-display-$(CONFIG_DRM_MSM_HDMI_HDCP) += hdmi/hdmi_hdcp.o
>  
> diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c
> index 090e7d790593..d7ce13a4586d 100644
> --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c
> +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c
> @@ -680,6 +680,12 @@ static int _dpu_kms_initialize_displayport(struct drm_device *dev,
>  		stream_cnt = msm_dp_get_mst_max_stream(priv->kms->dp[i]);
>  
>  		if (stream_cnt > 1) {
> +			rc = msm_dp_mst_register(priv->kms->dp[i]);
> +			if (rc) {
> +				DPU_ERROR("dp_mst_init failed for DP, rc = %d\n", rc);
> +				return rc;
> +			}
> +
>  			for (stream_id = 0; stream_id < stream_cnt; stream_id++) {
>  				encoder = dpu_encoder_init(dev, DRM_MODE_ENCODER_DPMST, &info);
>  				if (IS_ERR(encoder)) {
> diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
> index 9eaf6994a350..919767945ba5 100644
> --- a/drivers/gpu/drm/msm/dp/dp_display.c
> +++ b/drivers/gpu/drm/msm/dp/dp_display.c
> @@ -28,6 +28,7 @@
>  #include "dp_drm.h"
>  #include "dp_audio.h"
>  #include "dp_debug.h"
> +#include "dp_mst_drm.h"
>  
>  static bool psr_enabled = false;
>  module_param(psr_enabled, bool, 0);
> @@ -360,6 +361,9 @@ static int msm_dp_display_process_hpd_high(struct msm_dp_display_private *dp)
>  	if (dp->max_stream > 1 && drm_dp_read_mst_cap(dp->aux, dp->panel->dpcd))
>  		msm_dp_display_mst_init(dp);
>  
> +	if (dp->msm_dp_display.mst_active)
> +		msm_dp_mst_display_set_mgr_state(&dp->msm_dp_display, true);
> +
>  	msm_dp_link_reset_phy_params_vx_px(dp->link);
>  
>  end:
> @@ -527,6 +531,11 @@ static int msm_dp_hpd_unplug_handle(struct msm_dp_display_private *dp)
>  						 dp->panel->dpcd,
>  						 dp->panel->downstream_ports);
>  
> +	if (dp->msm_dp_display.mst_active) {
> +		msm_dp_mst_display_set_mgr_state(&dp->msm_dp_display, false);
> +		dp->msm_dp_display.mst_active = false;
> +	}
> +
>  	/* signal the disconnect event early to ensure proper teardown */
>  	msm_dp_display_handle_plugged_change(&dp->msm_dp_display, false);
>  
> @@ -1556,6 +1565,15 @@ int msm_dp_modeset_init(struct msm_dp *msm_dp_display, struct drm_device *dev,
>  	return 0;
>  }
>  
> +int msm_dp_mst_register(struct msm_dp *msm_dp_display)
> +{
> +	struct msm_dp_display_private *dp;
> +
> +	dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display);
> +
> +	return msm_dp_mst_init(msm_dp_display, dp->max_stream, dp->aux);
> +}
> +
>  int msm_dp_display_set_mode_helper(struct msm_dp *msm_dp_display,
>  				   struct drm_atomic_state *state,
>  				   struct drm_encoder *drm_encoder,
> diff --git a/drivers/gpu/drm/msm/dp/dp_display.h b/drivers/gpu/drm/msm/dp/dp_display.h
> index bda76319c459..55874daf41c4 100644
> --- a/drivers/gpu/drm/msm/dp/dp_display.h
> +++ b/drivers/gpu/drm/msm/dp/dp_display.h
> @@ -24,6 +24,8 @@ struct msm_dp {
>  	bool is_edp;
>  	bool prepared;
>  
> +	void *msm_dp_mst;
> +
>  	struct msm_dp_audio *msm_dp_audio;
>  	bool psr_supported;
>  };
> diff --git a/drivers/gpu/drm/msm/dp/dp_mst_drm.c b/drivers/gpu/drm/msm/dp/dp_mst_drm.c
> new file mode 100644
> index 000000000000..b6c7b8211025
> --- /dev/null
> +++ b/drivers/gpu/drm/msm/dp/dp_mst_drm.c
> @@ -0,0 +1,73 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (c) 2025 Qualcomm Innovation Center, Inc. All rights reserved.
> + */
> +
> +#include <drm/drm_edid.h>
> +#include <drm/drm_managed.h>
> +#include <drm/drm_bridge.h>
> +#include <drm/display/drm_dp_mst_helper.h>
> +
> +#include "dp_mst_drm.h"
> +#include "dp_panel.h"
> +
> +#define MAX_DPCD_TRANSACTION_BYTES 16
> +
> +struct msm_dp_mst {
> +	struct drm_dp_mst_topology_mgr mst_mgr;
> +	struct msm_dp *msm_dp;
> +	struct drm_dp_aux *dp_aux;
> +	u32 max_streams;
> +	/* Protects MST bridge enable/disable handling. */
> +	struct mutex mst_lock;

I don't see it being used here. Please drop. Isn't mst_mgr->lock enough?

> +};
> +
> +int msm_dp_mst_display_set_mgr_state(struct msm_dp *dp_display, bool state)
> +{
> +	struct msm_dp_mst *mst = dp_display->msm_dp_mst;
> +	int rc;
> +
> +	rc = drm_dp_mst_topology_mgr_set_mst(&mst->mst_mgr, state);
> +	if (rc < 0) {
> +		DRM_ERROR("failed to set topology mgr state to %d. rc %d\n",
> +			  state, rc);
> +	}
> +
> +	drm_dbg_dp(dp_display->drm_dev, "dp_mst_display_set_mgr_state state:%d\n", state);
> +	return rc;
> +}
> +
> +int msm_dp_mst_init(struct msm_dp *dp_display, u32 max_streams, struct drm_dp_aux *drm_aux)
> +{
> +	struct drm_device *dev = dp_display->drm_dev;
> +	int conn_base_id = 0;
> +	int ret;
> +	struct msm_dp_mst *msm_dp_mst;
> +
> +	msm_dp_mst = devm_kzalloc(dev->dev, sizeof(*msm_dp_mst), GFP_KERNEL);
> +	if (!msm_dp_mst)
> +		return -ENOMEM;
> +
> +	memset(&msm_dp_mst->mst_mgr, 0, sizeof(msm_dp_mst->mst_mgr));
> +
> +	conn_base_id = dp_display->connector->base.id;
> +	msm_dp_mst->msm_dp = dp_display;
> +	msm_dp_mst->max_streams = max_streams;
> +
> +	msm_dp_mst->dp_aux = drm_aux;
> +
> +	ret = drm_dp_mst_topology_mgr_init(&msm_dp_mst->mst_mgr, dev,
> +					   drm_aux,
> +					   MAX_DPCD_TRANSACTION_BYTES,

Inline 16 here.

> +					   max_streams,
> +					   conn_base_id);
> +	if (ret) {
> +		DRM_ERROR("DP DRM MST topology manager init failed\n");
> +		return ret;
> +	}
> +
> +	dp_display->msm_dp_mst = msm_dp_mst;
> +
> +	mutex_init(&msm_dp_mst->mst_lock);
> +	return ret;
> +}
> diff --git a/drivers/gpu/drm/msm/dp/dp_mst_drm.h b/drivers/gpu/drm/msm/dp/dp_mst_drm.h
> new file mode 100644
> index 000000000000..5d411529f681
> --- /dev/null
> +++ b/drivers/gpu/drm/msm/dp/dp_mst_drm.h
> @@ -0,0 +1,13 @@
> +/* SPDX-License-Identifier: GPL-2.0-only
> + * Copyright (c) 2025 Qualcomm Innovation Center, Inc. All rights reserved.
> + */
> +
> +#ifndef _DP_MST_DRM_H_
> +#define _DP_MST_DRM_H_
> +
> +#include "dp_display.h"
> +
> +int msm_dp_mst_init(struct msm_dp *dp_display, u32 max_streams, struct drm_dp_aux *drm_aux);
> +int msm_dp_mst_display_set_mgr_state(struct msm_dp *dp_display, bool state);
> +
> +#endif /* _DP_MST_DRM_H_ */
> diff --git a/drivers/gpu/drm/msm/msm_drv.h b/drivers/gpu/drm/msm/msm_drv.h
> index 3061eca49cb2..5f73e0aa1c2f 100644
> --- a/drivers/gpu/drm/msm/msm_drv.h
> +++ b/drivers/gpu/drm/msm/msm_drv.h
> @@ -363,6 +363,7 @@ bool msm_dp_needs_periph_flush(const struct msm_dp *dp_display,
>  			       const struct drm_display_mode *mode);
>  bool msm_dp_wide_bus_available(const struct msm_dp *dp_display);
>  int msm_dp_get_mst_max_stream(struct msm_dp *dp_display);
> +int msm_dp_mst_register(struct msm_dp *dp_display);
>  #else
>  static inline int __init msm_dp_register(void)
>  {
> @@ -384,6 +385,11 @@ static inline int msm_dp_get_mst_max_stream(struct msm_dp *dp_display)
>  	return -EINVAL;
>  }
>  
> +static inline int msm_dp_mst_register(struct msm_dp *dp_display)
> +{
> +	return -EINVAL;
> +}
> +
>  static inline void msm_dp_snapshot(struct msm_disp_state *disp_state, struct msm_dp *dp_display)
>  {
>  }
> 
> -- 
> 2.43.0
> 

-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 34/39] drm/msm/dp: add dp_mst_drm to manage DP MST bridge operations
  2026-04-10  9:34 ` [PATCH v4 34/39] drm/msm/dp: add dp_mst_drm to manage DP MST bridge operations Yongxing Mou
@ 2026-04-11 23:51   ` Dmitry Baryshkov
  2026-05-18 12:00     ` Yongxing Mou
  0 siblings, 1 reply; 128+ messages in thread
From: Dmitry Baryshkov @ 2026-04-11 23:51 UTC (permalink / raw)
  To: Yongxing Mou
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar

On Fri, Apr 10, 2026 at 05:34:09PM +0800, Yongxing Mou wrote:
> From: Abhinav Kumar <quic_abhinavk@quicinc.com>
> 
> Add dp_mst_drm to manage the DP MST bridge operations similar to the
> dp_drm file which manages the SST bridge operations. Each MST encoder
> creates one bridge and each bridge is bound to its own dp_panel
> abstraction to manage the operations of its pipeline.

This doesn't explain the most important part: why do we need a bridge?
There is nothing else between the encoder and the connector. The bridge
state is almost empty. Can we squash it into the MST connector?

> 
> Keep the connector/panel association in bridge private state for atomic
> assignment and release, and mirror it in the bridge object for runtime
> bridge callbacks.
> 
> Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
> ---
>  drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c |   6 +
>  drivers/gpu/drm/msm/dp/dp_mst_drm.c     | 459 +++++++++++++++++++++++++++++++-
>  drivers/gpu/drm/msm/msm_drv.h           |   7 +
>  3 files changed, 471 insertions(+), 1 deletion(-)
> 
> diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c
> index d7ce13a4586d..89868443c0fe 100644
> --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c
> +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c
> @@ -692,6 +692,12 @@ static int _dpu_kms_initialize_displayport(struct drm_device *dev,
>  					DPU_ERROR("encoder init failed for dp mst display\n");
>  					return PTR_ERR(encoder);
>  				}
> +
> +				rc = msm_dp_mst_attach_encoder(priv->kms->dp[i], encoder);
> +				if (rc) {
> +					DPU_ERROR("DP MST init failed, %d\n", rc);
> +					continue;
> +				}
>  			}
>  		}
>  	}
> diff --git a/drivers/gpu/drm/msm/dp/dp_mst_drm.c b/drivers/gpu/drm/msm/dp/dp_mst_drm.c
> index b6c7b8211025..4df3ea5e36d0 100644
> --- a/drivers/gpu/drm/msm/dp/dp_mst_drm.c
> +++ b/drivers/gpu/drm/msm/dp/dp_mst_drm.c
> @@ -13,8 +13,47 @@
>  
>  #define MAX_DPCD_TRANSACTION_BYTES 16
>  
> +#define to_dp_mst_bridge(x)     container_of((x), struct msm_dp_mst_bridge, base)
> +#define to_dp_mst_bridge_state_priv(x) \
> +		container_of((x), struct msm_dp_mst_bridge_state, base)
> +#define to_dp_mst_bridge_state(x) \
> +		to_dp_mst_bridge_state_priv((x)->obj.state)
> +#define to_dp_mst_connector(x) \
> +		container_of((x), struct msm_dp_mst_connector, connector)
> +
> +#define DP_MST_CONN_ID(x) ((x)->connector ? \
> +		(x)->connector->base.id : 0)
> +
> +struct msm_dp_mst_bridge {
> +	struct drm_bridge base;
> +	struct drm_private_obj obj;
> +	u32 id;
> +
> +	bool initialized;
> +
> +	struct msm_dp *display;
> +	struct drm_encoder *encoder;
> +
> +	struct drm_connector *connector;
> +	struct msm_dp_panel *msm_dp_panel;
> +};
> +
> +struct msm_dp_mst_bridge_state {
> +	struct drm_private_state base;
> +	struct drm_connector *connector;
> +	struct msm_dp_panel *msm_dp_panel;
> +};
> +
> +struct msm_dp_mst_connector {
> +	struct drm_connector connector;
> +	struct drm_dp_mst_port *mst_port;
> +	struct msm_dp_mst *dp_mst;
> +	struct msm_dp_panel *dp_panel;

After squashing the bridge to the connector, you might need to separate
state from the non-state variables.

> +};
> +
>  struct msm_dp_mst {
>  	struct drm_dp_mst_topology_mgr mst_mgr;
> +	struct msm_dp_mst_bridge *mst_bridge[DP_STREAM_MAX];
>  	struct msm_dp *msm_dp;
>  	struct drm_dp_aux *dp_aux;
>  	u32 max_streams;
> @@ -22,6 +61,419 @@ struct msm_dp_mst {
>  	struct mutex mst_lock;
>  };
>  
> +static struct drm_private_state *msm_dp_mst_duplicate_bridge_state(struct drm_private_obj *obj)
> +{
> +	struct msm_dp_mst_bridge_state *mst_bridge_state;
> +
> +	mst_bridge_state = kmemdup(obj->state, sizeof(*mst_bridge_state), GFP_KERNEL);
> +	if (!mst_bridge_state)
> +		return NULL;
> +
> +	__drm_atomic_helper_private_obj_duplicate_state(obj, &mst_bridge_state->base);
> +
> +	return &mst_bridge_state->base;
> +}
> +
> +static void msm_dp_mst_destroy_bridge_state(struct drm_private_obj *obj,
> +					    struct drm_private_state *state)
> +{
> +	struct msm_dp_mst_bridge_state *mst_bridge_state =
> +		to_dp_mst_bridge_state_priv(state);
> +
> +	kfree(mst_bridge_state);
> +}
> +
> +static const struct drm_private_state_funcs msm_dp_mst_bridge_state_funcs = {
> +	.atomic_duplicate_state = msm_dp_mst_duplicate_bridge_state,
> +	.atomic_destroy_state = msm_dp_mst_destroy_bridge_state,
> +};
> +
> +static struct msm_dp_mst_bridge_state *msm_dp_mst_br_priv_state(struct drm_atomic_state *st,
> +								struct msm_dp_mst_bridge *bridge)
> +{
> +	struct drm_device *dev = bridge->base.dev;
> +	struct drm_private_state *obj_state = drm_atomic_get_private_obj_state(st, &bridge->obj);
> +
> +	WARN_ON(!drm_modeset_is_locked(&dev->mode_config.connection_mutex));
> +
> +	return to_dp_mst_bridge_state_priv(obj_state);
> +}
> +
> +static void msm_dp_mst_update_timeslots(struct msm_dp_mst *mst,
> +					struct msm_dp_mst_bridge *mst_bridge,
> +					struct drm_atomic_state *state,
> +					struct drm_dp_mst_port *port)
> +{
> +	struct drm_dp_mst_topology_state *mst_state;
> +	struct drm_dp_mst_atomic_payload *payload;
> +
> +	mst_state = to_drm_dp_mst_topology_state(mst->mst_mgr.base.state);
> +	payload = drm_atomic_get_mst_payload_state(mst_state, port);
> +
> +	if (!payload) {
> +		DRM_ERROR("MST bridge [%d] update_timeslots failed, null payload\n",
> +			  mst_bridge->id);
> +		return;
> +	}
> +
> +	if (payload->vc_start_slot < 0)
> +		msm_dp_display_set_stream_info(mst->msm_dp, mst_bridge->msm_dp_panel,
> +					       mst_bridge->id, 1, 0, 0);
> +	else
> +		msm_dp_display_set_stream_info(mst->msm_dp, mst_bridge->msm_dp_panel,
> +					       mst_bridge->id, payload->vc_start_slot,
> +					       payload->time_slots, payload->pbn);
> +}
> +
> +static int msm_dp_mst_bridge_pre_enable_part1(struct msm_dp_mst_bridge *dp_bridge,
> +					      struct drm_atomic_state *state)
> +{
> +	struct msm_dp *dp_display = dp_bridge->display;
> +	struct msm_dp_mst *mst = dp_display->msm_dp_mst;
> +	struct msm_dp_mst_connector *mst_conn = to_dp_mst_connector(dp_bridge->connector);
> +	struct drm_dp_mst_port *port = mst_conn->mst_port;
> +	struct drm_dp_mst_topology_state *mst_state;
> +	struct drm_dp_mst_atomic_payload *payload;
> +	struct msm_dp_panel *dp_panel = mst_conn->dp_panel;
> +	int pbn;
> +	int rc = 0;
> +
> +	mst_state = drm_atomic_get_new_mst_topology_state(state, &mst->mst_mgr);
> +
> +	pbn = drm_dp_calc_pbn_mode(dp_panel->msm_dp_mode.drm_mode.clock,
> +				   (mst_conn->connector.display_info.bpc * 3) << 4);
> +
> +	payload = drm_atomic_get_mst_payload_state(mst_state, port);
> +	if (!payload || payload->time_slots <= 0) {
> +		DRM_ERROR("time slots not allocated for conn:%d\n", DP_MST_CONN_ID(dp_bridge));
> +		rc = -EINVAL;
> +		return rc;
> +	}
> +
> +	drm_dbg_dp(dp_display->drm_dev, "conn:%d pbn:%d, slots:%d\n", DP_MST_CONN_ID(dp_bridge),
> +		   pbn, payload->time_slots);
> +
> +	drm_dp_mst_update_slots(mst_state, DP_CAP_ANSI_8B10B);
> +
> +	rc = drm_dp_add_payload_part1(&mst->mst_mgr, mst_state, payload);
> +	if (rc) {
> +		DRM_ERROR("payload allocation failure for conn:%d\n", DP_MST_CONN_ID(dp_bridge));
> +		return rc;
> +	}
> +
> +	msm_dp_mst_update_timeslots(mst, dp_bridge, state, port);
> +
> +	return rc;
> +}
> +
> +static void _msm_dp_mst_bridge_pre_enable_part2(struct msm_dp_mst_bridge *dp_bridge,
> +						struct drm_atomic_state *state)
> +{
> +	struct msm_dp *dp_display = dp_bridge->display;
> +	struct msm_dp_mst *mst = dp_display->msm_dp_mst;
> +	struct msm_dp_mst_connector *mst_conn = to_dp_mst_connector(dp_bridge->connector);
> +	struct drm_dp_mst_port *port = mst_conn->mst_port;
> +	struct drm_dp_mst_topology_state *mst_state;
> +	struct drm_dp_mst_atomic_payload *payload;
> +
> +	drm_dp_check_act_status(&mst->mst_mgr);
> +
> +	mst_state = to_drm_dp_mst_topology_state(mst->mst_mgr.base.state);
> +	payload = drm_atomic_get_mst_payload_state(mst_state, port);
> +
> +	drm_dp_add_payload_part2(&mst->mst_mgr, payload);
> +
> +	drm_dbg_dp(dp_display->drm_dev, "MST bridge [%d] _pre enable part-2 complete\n",
> +		   dp_bridge->id);
> +}
> +
> +static void msm_dp_mst_bridge_pre_disable_part1(struct msm_dp_mst_bridge *dp_bridge,
> +						struct drm_atomic_state *state)
> +{
> +	struct msm_dp *dp_display = dp_bridge->display;
> +	struct msm_dp_mst *mst = dp_display->msm_dp_mst;
> +	struct msm_dp_mst_connector *mst_conn = to_dp_mst_connector(dp_bridge->connector);
> +	struct drm_dp_mst_port *port = mst_conn->mst_port;
> +	struct drm_dp_mst_topology_state *old_mst_state;
> +	struct drm_dp_mst_topology_state *new_mst_state;
> +	const struct drm_dp_mst_atomic_payload *old_payload;
> +	struct drm_dp_mst_atomic_payload *new_payload;
> +
> +	old_mst_state = drm_atomic_get_old_mst_topology_state(state, &mst->mst_mgr);
> +	new_mst_state = drm_atomic_get_new_mst_topology_state(state, &mst->mst_mgr);
> +
> +	old_payload = drm_atomic_get_mst_payload_state(old_mst_state, port);
> +	new_payload = drm_atomic_get_mst_payload_state(new_mst_state, port);
> +
> +	if (!old_payload || !new_payload) {
> +		DRM_ERROR("MST bridge [%d] _pre disable part-1 failed, null payload\n",
> +			  dp_bridge->id);
> +		return;
> +	}
> +
> +	drm_dp_remove_payload_part1(&mst->mst_mgr, new_mst_state, new_payload);
> +	drm_dp_remove_payload_part2(&mst->mst_mgr, new_mst_state, old_payload, new_payload);
> +
> +	msm_dp_mst_update_timeslots(mst, dp_bridge, state, port);
> +
> +	drm_dbg_dp(dp_display->drm_dev, "MST bridge [%d] _pre disable part-1 complete\n",
> +		   dp_bridge->id);
> +}
> +
> +static void msm_dp_mst_bridge_atomic_pre_enable(struct drm_bridge *drm_bridge,
> +						struct drm_atomic_state *state)
> +{
> +	int rc = 0;
> +	struct msm_dp_mst_bridge *bridge;
> +	struct msm_dp *dp_display;
> +	struct msm_dp_mst_bridge_state *mst_bridge_state;
> +	struct msm_dp_mst *dp_mst;
> +	struct msm_dp_panel   *msm_dp_panel;
> +
> +	if (!drm_bridge) {
> +		DRM_ERROR("Invalid params\n");
> +		return;
> +	}
> +
> +	bridge = to_dp_mst_bridge(drm_bridge);
> +	mst_bridge_state = to_dp_mst_bridge_state(bridge);
> +	dp_display = bridge->display;
> +	dp_mst = dp_display->msm_dp_mst;
> +
> +	/* to cover cases of bridge_disable/bridge_enable without modeset */
> +	bridge->connector = mst_bridge_state->connector;
> +	bridge->msm_dp_panel = mst_bridge_state->msm_dp_panel;
> +
> +	if (!bridge->connector) {
> +		DRM_ERROR("Invalid connector\n");
> +		return;
> +	}
> +
> +	msm_dp_panel = bridge->msm_dp_panel;
> +	mutex_lock(&dp_mst->mst_lock);
> +
> +	rc = msm_dp_display_set_mode_helper(dp_display, state, drm_bridge->encoder, msm_dp_panel);
> +	if (rc) {
> +		DRM_ERROR("Failed to perform a mode set, rc=%d\n", rc);
> +		mutex_unlock(&dp_mst->mst_lock);
> +		return;
> +	}
> +	msm_dp_panel->pbn = drm_dp_calc_pbn_mode(msm_dp_panel->msm_dp_mode.drm_mode.clock,
> +						 msm_dp_panel->msm_dp_mode.bpp << 4);
> +	rc = msm_dp_display_prepare(dp_display);
> +	if (rc) {
> +		DRM_ERROR("[%d] DP display pre-enable failed, rc=%d\n", bridge->id, rc);
> +		msm_dp_display_unprepare(dp_display);
> +		mutex_unlock(&dp_mst->mst_lock);
> +		return;
> +	}
> +
> +	rc = msm_dp_mst_bridge_pre_enable_part1(bridge, state);
> +	if (rc) {
> +		DRM_ERROR("[%d] DP display pre-enable failed, rc=%d\n", bridge->id, rc);
> +		mutex_unlock(&dp_mst->mst_lock);
> +		return;
> +	}
> +
> +	msm_dp_display_enable_helper(dp_display, bridge->msm_dp_panel);
> +
> +	_msm_dp_mst_bridge_pre_enable_part2(bridge, state);
> +
> +	mutex_unlock(&dp_mst->mst_lock);
> +
> +	drm_dbg_dp(dp_display->drm_dev, "conn:%d mode:%s pre enable done\n",
> +		   DP_MST_CONN_ID(bridge), bridge->msm_dp_panel->msm_dp_mode.drm_mode.name);
> +}
> +
> +static void msm_dp_mst_bridge_atomic_disable(struct drm_bridge *drm_bridge,
> +					     struct drm_atomic_state *state)
> +{
> +	struct msm_dp_mst_bridge *bridge;
> +	struct msm_dp *dp_display;
> +	struct msm_dp_mst *mst;
> +
> +	if (!drm_bridge) {
> +		DRM_ERROR("Invalid params\n");
> +		return;
> +	}
> +
> +	bridge = to_dp_mst_bridge(drm_bridge);
> +	if (!bridge->connector) {
> +		DRM_ERROR("Invalid connector\n");
> +		return;
> +	}
> +
> +	dp_display = bridge->display;
> +	mst = dp_display->msm_dp_mst;
> +
> +	mutex_lock(&mst->mst_lock);
> +
> +	msm_dp_mst_bridge_pre_disable_part1(bridge, state);
> +
> +	msm_dp_display_disable_helper(dp_display, bridge->msm_dp_panel);
> +
> +	drm_dp_check_act_status(&mst->mst_mgr);
> +
> +	mutex_unlock(&mst->mst_lock);
> +
> +	drm_dbg_dp(dp_display->drm_dev, "MST bridge:%d disable complete\n", bridge->id);
> +}
> +
> +static void msm_dp_mst_bridge_atomic_post_disable(struct drm_bridge *drm_bridge,
> +						  struct drm_atomic_state *state)
> +{
> +	struct msm_dp_mst_bridge *bridge;
> +	struct msm_dp *dp_display;
> +	struct msm_dp_mst *mst;
> +
> +	if (!drm_bridge) {
> +		DRM_ERROR("Invalid params\n");
> +		return;
> +	}
> +
> +	bridge = to_dp_mst_bridge(drm_bridge);
> +	if (!bridge->connector) {
> +		DRM_ERROR("Invalid connector\n");
> +		return;
> +	}
> +
> +	dp_display = bridge->display;
> +	mst = dp_display->msm_dp_mst;
> +
> +	mutex_lock(&mst->mst_lock);
> +
> +	msm_dp_display_atomic_post_disable_helper(dp_display, bridge->msm_dp_panel);
> +
> +	if (!dp_display->mst_active)
> +		msm_dp_display_unprepare(dp_display);
> +
> +	bridge->connector = NULL;
> +	bridge->msm_dp_panel =  NULL;
> +
> +	mutex_unlock(&mst->mst_lock);
> +
> +	drm_dbg_dp(dp_display->drm_dev, "MST bridge:%d conn:%d post disable complete\n",
> +		   bridge->id, DP_MST_CONN_ID(bridge));
> +}
> +
> +static int msm_dp_mst_bridge_atomic_check(struct drm_bridge *drm_bridge,
> +					  struct drm_bridge_state *bridge_state,
> +					  struct drm_crtc_state *crtc_state,
> +					  struct drm_connector_state *conn_state)
> +{
> +	struct drm_atomic_state *state = crtc_state->state;
> +	struct drm_connector *connector = conn_state->connector;
> +	struct drm_dp_mst_topology_state *mst_state;
> +	struct msm_dp_mst_connector *mst_conn;
> +	struct msm_dp_mst *mst;
> +	int rc = 0, pbn, slots;
> +	struct msm_dp_mst_bridge_state *mst_bridge_state;
> +	u32 bpp;
> +
> +	if (!drm_atomic_crtc_needs_modeset(crtc_state) || !crtc_state->enable)
> +		return 0;
> +
> +	mst_conn = to_dp_mst_connector(connector);
> +	mst = mst_conn->dp_mst;
> +
> +	bpp = connector->display_info.bpc * 3;
> +
> +	if (!bpp)
> +		bpp = 24;
> +
> +	pbn = drm_dp_calc_pbn_mode(crtc_state->mode.clock, bpp << 4);
> +
> +	mst_state = to_drm_dp_mst_topology_state(mst->mst_mgr.base.state);
> +	if (IS_ERR(mst_state))
> +		return PTR_ERR(mst_state);
> +
> +	if (!dfixed_trunc(mst_state->pbn_div)) {
> +		mst_state->pbn_div =
> +			drm_dp_get_vc_payload_bw(mst_conn->dp_panel->link_info.rate,
> +						 mst_conn->dp_panel->link_info.num_lanes);
> +	}
> +
> +	slots = drm_dp_atomic_find_time_slots(state, &mst->mst_mgr, mst_conn->mst_port, pbn);
> +
> +	drm_dbg_dp(drm_bridge->dev, "add slots, conn:%d pbn:%d slots:%d rc:%d\n",
> +		   connector->base.id, pbn, slots, rc);
> +
> +	if (!conn_state->crtc)  {
> +		mst_bridge_state = msm_dp_mst_br_priv_state(state, to_dp_mst_bridge(drm_bridge));
> +		mst_bridge_state->connector = NULL;
> +		mst_bridge_state->msm_dp_panel = NULL;
> +	}
> +
> +	return 0;
> +}
> +
> +/* DP MST Bridge APIs */
> +static const struct drm_bridge_funcs msm_dp_mst_bridge_ops = {
> +	.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
> +	.atomic_destroy_state   = drm_atomic_helper_bridge_destroy_state,
> +	.atomic_reset           = drm_atomic_helper_bridge_reset,
> +	.atomic_pre_enable   = msm_dp_mst_bridge_atomic_pre_enable,
> +	.atomic_disable      = msm_dp_mst_bridge_atomic_disable,
> +	.atomic_post_disable = msm_dp_mst_bridge_atomic_post_disable,
> +	.atomic_check        = msm_dp_mst_bridge_atomic_check,
> +};
> +
> +int msm_dp_mst_attach_encoder(struct msm_dp *dp_display, struct drm_encoder *encoder)
> +{
> +	int rc = 0;
> +	struct msm_dp_mst_bridge *bridge = NULL;
> +	struct msm_dp_mst_bridge_state *mst_bridge_state;
> +	struct drm_device *dev;
> +	struct msm_dp_mst *mst = dp_display->msm_dp_mst;
> +	int i;
> +
> +	for (i = 0; i < mst->max_streams; i++) {
> +		if (!mst->mst_bridge[i]->initialized) {

We _know_ that it is not initialized now. And anyway, it's an error if
we call this twice.

> +			bridge = mst->mst_bridge[i];
> +			bridge->encoder = encoder;

This looks wrong. It's already stored inside drm_bridge

> +			bridge->initialized = true;
> +			bridge->id = i;

Definitely not an id.

> +			break;
> +		}
> +	}
> +
> +	if (i == mst->max_streams) {
> +		DRM_ERROR("MST supports only %d bridges\n", mst->max_streams);
> +		rc = -EACCES;
> +		goto end;
> +	}
> +
> +	dev = dp_display->drm_dev;
> +	bridge->display = dp_display;
> +	bridge->base.encoder = encoder;

why?

> +	bridge->base.type = dp_display->connector_type;
> +	bridge->base.ops = DRM_BRIDGE_OP_MODES;
> +	drm_bridge_add(&bridge->base);
> +
> +	rc = drm_bridge_attach(encoder, &bridge->base, NULL, 0);
> +	if (rc) {
> +		DRM_ERROR("failed to attach bridge, rc=%d\n", rc);
> +		goto end;
> +	}
> +
> +	mst_bridge_state = kzalloc(sizeof(*mst_bridge_state), GFP_KERNEL);
> +	if (!mst_bridge_state) {
> +		rc = -ENOMEM;
> +		goto end;
> +	}
> +
> +	drm_atomic_private_obj_init(dev, &bridge->obj,
> +				    &mst_bridge_state->base,
> +				    &msm_dp_mst_bridge_state_funcs);

It's already done for you in the drm_bridge_attach().

> +
> +	drm_dbg_dp(dp_display->drm_dev, "MST drm bridge init. bridge id:%d\n", i);
> +
> +	return 0;
> +
> +end:
> +	return rc;
> +}
> +
>  int msm_dp_mst_display_set_mgr_state(struct msm_dp *dp_display, bool state)
>  {
>  	struct msm_dp_mst *mst = dp_display->msm_dp_mst;
> @@ -49,11 +501,16 @@ int msm_dp_mst_init(struct msm_dp *dp_display, u32 max_streams, struct drm_dp_au
>  		return -ENOMEM;
>  
>  	memset(&msm_dp_mst->mst_mgr, 0, sizeof(msm_dp_mst->mst_mgr));
> -
>  	conn_base_id = dp_display->connector->base.id;
>  	msm_dp_mst->msm_dp = dp_display;
>  	msm_dp_mst->max_streams = max_streams;
>  
> +	for (int i = 0; i < DP_STREAM_MAX; i++) {
> +		msm_dp_mst->mst_bridge[i] =
> +			devm_drm_bridge_alloc(dev->dev, struct msm_dp_mst_bridge, base,
> +					      &msm_dp_mst_bridge_ops);
> +	}
> +
>  	msm_dp_mst->dp_aux = drm_aux;
>  
>  	ret = drm_dp_mst_topology_mgr_init(&msm_dp_mst->mst_mgr, dev,
> diff --git a/drivers/gpu/drm/msm/msm_drv.h b/drivers/gpu/drm/msm/msm_drv.h
> index 5f73e0aa1c2f..03bedd15fe02 100644
> --- a/drivers/gpu/drm/msm/msm_drv.h
> +++ b/drivers/gpu/drm/msm/msm_drv.h
> @@ -364,6 +364,8 @@ bool msm_dp_needs_periph_flush(const struct msm_dp *dp_display,
>  bool msm_dp_wide_bus_available(const struct msm_dp *dp_display);
>  int msm_dp_get_mst_max_stream(struct msm_dp *dp_display);
>  int msm_dp_mst_register(struct msm_dp *dp_display);
> +int msm_dp_mst_attach_encoder(struct msm_dp *dp_display, struct drm_encoder *encoder);
> +
>  #else
>  static inline int __init msm_dp_register(void)
>  {
> @@ -390,6 +392,11 @@ static inline int msm_dp_mst_register(struct msm_dp *dp_display)
>  	return -EINVAL;
>  }
>  
> +static inline int msm_dp_mst_attach_encoder(struct msm_dp *dp_display, struct drm_encoder *encoder)
> +{
> +	return -EINVAL;
> +}
> +
>  static inline void msm_dp_snapshot(struct msm_disp_state *disp_state, struct msm_dp *dp_display)
>  {
>  }
> 
> -- 
> 2.43.0
> 

-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 35/39] drm/msm/dp: wire MST helpers into atomic check and commit paths
  2026-04-10  9:34 ` [PATCH v4 35/39] drm/msm/dp: wire MST helpers into atomic check and commit paths Yongxing Mou
@ 2026-04-11 23:55   ` Dmitry Baryshkov
  0 siblings, 0 replies; 128+ messages in thread
From: Dmitry Baryshkov @ 2026-04-11 23:55 UTC (permalink / raw)
  To: Yongxing Mou
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar

On Fri, Apr 10, 2026 at 05:34:10PM +0800, Yongxing Mou wrote:
> Call drm_dp_mst_atomic_check() from msm_atomic_check() so MST-specific
> state, such as connector and topology changes, is validated as part of
> the atomic check.
> 
> Hook the MST helpers into atomic_commit_setup() and
> atomic_commit_tail() to support non-blocking atomic commits for
> DisplayPort MST, and ensure MST commits properly wait for dependencies.
> 
> For SST, non-blocking commits are already handled via commit_tail(),
> which waits for dependencies in the DRM core.
> 
> Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
> Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
> ---
>  drivers/gpu/drm/msm/msm_atomic.c | 9 ++++++++-
>  drivers/gpu/drm/msm/msm_kms.c    | 2 ++
>  2 files changed, 10 insertions(+), 1 deletion(-)
> 
> diff --git a/drivers/gpu/drm/msm/msm_atomic.c b/drivers/gpu/drm/msm/msm_atomic.c
> index 87a91148a731..ea064aa6d8fc 100644
> --- a/drivers/gpu/drm/msm/msm_atomic.c
> +++ b/drivers/gpu/drm/msm/msm_atomic.c
> @@ -4,6 +4,7 @@
>   * Author: Rob Clark <robdclark@gmail.com>
>   */
>  
> +#include <drm/display/drm_dp_mst_helper.h>
>  #include <drm/drm_atomic_uapi.h>
>  #include <drm/drm_vblank.h>
>  
> @@ -207,7 +208,11 @@ int msm_atomic_check(struct drm_device *dev, struct drm_atomic_state *state)
>  	if (ret)
>  		return ret;
>  
> -	return drm_atomic_helper_check(dev, state);
> +	ret = drm_atomic_helper_check(dev, state);
> +	if (ret)
> +		return ret;
> +
> +	return drm_dp_mst_atomic_check(state);
>  }
>  
>  void msm_atomic_commit_tail(struct drm_atomic_state *state)
> @@ -221,6 +226,8 @@ void msm_atomic_commit_tail(struct drm_atomic_state *state)
>  
>  	trace_msm_atomic_commit_tail_start(async, crtc_mask);
>  
> +	drm_dp_mst_atomic_wait_for_dependencies(state);
> +
>  	kms->funcs->enable_commit(kms);
>  
>  	/*
> diff --git a/drivers/gpu/drm/msm/msm_kms.c b/drivers/gpu/drm/msm/msm_kms.c
> index e5d0ea629448..a8f5fbb3239d 100644
> --- a/drivers/gpu/drm/msm/msm_kms.c
> +++ b/drivers/gpu/drm/msm/msm_kms.c
> @@ -10,6 +10,7 @@
>  #include <linux/sched/mm.h>
>  #include <uapi/linux/sched/types.h>
>  
> +#include <drm/display/drm_dp_mst_helper.h>
>  #include <drm/drm_drv.h>
>  #include <drm/drm_mode_config.h>
>  #include <drm/drm_vblank.h>
> @@ -29,6 +30,7 @@ static const struct drm_mode_config_funcs mode_config_funcs = {
>  
>  static const struct drm_mode_config_helper_funcs mode_config_helper_funcs = {
>  	.atomic_commit_tail = msm_atomic_commit_tail,
> +	.atomic_commit_setup = drm_dp_mst_atomic_setup_commit,

I usually hate one-liners, but here I'd prefer to see this call as a
part of the msm_atomic.c

>  };
>  
>  static irqreturn_t msm_irq(int irq, void *arg)
> 
> -- 
> 2.43.0
> 

-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 21/39] drm/msm/dp: Add support for MST channel slot allocation
  2026-04-10  9:33 ` [PATCH v4 21/39] drm/msm/dp: Add support for MST channel slot allocation Yongxing Mou
@ 2026-04-11 23:58   ` Dmitry Baryshkov
  0 siblings, 0 replies; 128+ messages in thread
From: Dmitry Baryshkov @ 2026-04-11 23:58 UTC (permalink / raw)
  To: Yongxing Mou
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar

On Fri, Apr 10, 2026 at 05:33:56PM +0800, Yongxing Mou wrote:
> From: Abhinav Kumar <quic_abhinavk@quicinc.com>
> 
> DP MST streams share 64 MTP slots in a time-multiplexed manner. This patch

Documentation/process/submitting-patches.rst, "This patch".

> adds support for calculating the rate governor, slot allocation, and slot
> reservation in the DP controller.
> 
> Each MST stream can reserve its slots by calling
> dp_display_set_stream_info() from its bridge callbacks.

-ENOSUCHFUNCTION

> 
> Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
> ---
>  drivers/gpu/drm/msm/dp/dp_ctrl.c    | 207 ++++++++++++++++++++++++++++++++++++
>  drivers/gpu/drm/msm/dp/dp_ctrl.h    |   7 +-
>  drivers/gpu/drm/msm/dp/dp_display.c |  28 +++--
>  drivers/gpu/drm/msm/dp/dp_display.h |   5 +-
>  drivers/gpu/drm/msm/dp/dp_panel.h   |   1 +
>  drivers/gpu/drm/msm/dp/dp_reg.h     |  10 ++
>  6 files changed, 247 insertions(+), 11 deletions(-)
> 
> diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c
> index 98316892eccd..e64f81bc8c36 100644
> --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c
> +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c
> @@ -73,6 +73,7 @@
>  #define MR_LINK_PRBS7 0x100
>  #define MR_LINK_CUSTOM80 0x200
>  #define MR_LINK_TRAINING4  0x40
> +#define DP_MAX_TIME_SLOTS 64
>  
>  enum {
>  	DP_TRAINING_NONE,
> @@ -109,6 +110,11 @@ struct msm_dp_vc_tu_mapping_table {
>  	u8 tu_size_minus1;
>  };
>  
> +struct msm_dp_mst_ch_slot_info {
> +	u32 start_slot;
> +	u32 tot_slots;
> +};
> +
>  struct msm_dp_ctrl_private {
>  	struct msm_dp_ctrl msm_dp_ctrl;
>  	struct drm_device *drm_dev;
> @@ -144,6 +150,8 @@ struct msm_dp_ctrl_private {
>  	bool link_clks_on;
>  	bool stream_clks_on[DP_STREAM_MAX];
>  	bool mst_active;
> +
> +	struct msm_dp_mst_ch_slot_info mst_ch_info[DP_STREAM_MAX];
>  };
>  
>  static inline u32 msm_dp_read_ahb(const struct msm_dp_ctrl_private *ctrl, u32 offset)
> @@ -268,6 +276,73 @@ static void msm_dp_ctrl_mst_config(struct msm_dp_ctrl_private *ctrl, bool enable
>  	msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
>  }
>  
> +static void msm_dp_ctrl_mst_channel_alloc(struct msm_dp_ctrl_private *ctrl,
> +					  enum msm_dp_stream_id stream_id, u32 ch_start_slot,
> +					  u32 tot_slot_cnt)
> +{
> +	u32 i, slot;
> +	u32  slot_reg_1, slot_reg_2;

Fold these two lines.

> +	u32 reg_off = 0;
> +	int const num_slots_per_reg = 32;
> +
> +	if (ch_start_slot > DP_MAX_TIME_SLOTS ||
> +	    (ch_start_slot + tot_slot_cnt > DP_MAX_TIME_SLOTS)) {
> +		DRM_ERROR("invalid slots start %d, tot %d\n",
> +			  ch_start_slot, tot_slot_cnt);
> +		return;
> +	}
> +
> +	drm_dbg_dp(ctrl->drm_dev, "stream_id %d, start_slot %d, tot_slot %d\n",
> +		   stream_id, ch_start_slot, tot_slot_cnt);
> +
> +	if (stream_id == DP_STREAM_1)
> +		reg_off = REG_DP_DP1_TIMESLOT_1_32 - REG_DP_DP0_TIMESLOT_1_32;
> +
> +	slot_reg_1 = 0;
> +	slot_reg_2 = 0;
> +
> +	if (ch_start_slot && tot_slot_cnt) {
> +		ch_start_slot--;
> +		for (i = 0; i < tot_slot_cnt; i++) {
> +			if (ch_start_slot < num_slots_per_reg) {
> +				slot_reg_1 |= BIT(ch_start_slot);
> +			} else {
> +				slot = ch_start_slot - num_slots_per_reg;
> +				slot_reg_2 |= BIT(slot);
> +			}
> +			ch_start_slot++;

If it is a counter, it's no longer a starting slot. And I think I wrote
this before. Please replace the loop with the math.

> +		}
> +	}
> +
> +	drm_dbg_dp(ctrl->drm_dev, "stream_id:%d slot_reg_1:%d, slot_reg_2:%d\n", stream_id,
> +		   slot_reg_1, slot_reg_2);
> +
> +	msm_dp_write_link(ctrl, stream_id, stream_id > DP_STREAM_1 ?
> +			  REG_DP_MSTLINK_TIMESLOT_1_32 : REG_DP_DP0_TIMESLOT_1_32 + reg_off,
> +			  slot_reg_1);
> +	msm_dp_write_link(ctrl, stream_id, stream_id > DP_STREAM_1 ?
> +			  REG_DP_MSTLINK_TIMESLOT_33_63 : REG_DP_DP0_TIMESLOT_33_63 + reg_off,
> +			  slot_reg_2);
> +}
> +
> +static void msm_dp_ctrl_update_rg(struct msm_dp_ctrl_private *ctrl,
> +				  enum msm_dp_stream_id stream_id, u32 x_int, u32 y_frac_enum)
> +{
> +	u32 rg, reg_off = 0;
> +
> +	rg = y_frac_enum;
> +	rg |= (x_int << 16);
> +
> +	drm_dbg_dp(ctrl->drm_dev, "stream_id: %d x_int:%d y_frac_enum:%d rg:%d\n",
> +		   stream_id, x_int, y_frac_enum, rg);
> +
> +	if (stream_id == DP_STREAM_1)
> +		reg_off = REG_DP_DP1_RG - REG_DP_DP0_RG;
> +
> +	msm_dp_write_link(ctrl, stream_id, stream_id > 1 ?
> +			     REG_DP_MSTLINK_DP_RG : REG_DP_DP0_RG + reg_off, rg);
> +}
> +
>  /*
>   * NOTE: resetting DP controller will also clear any pending HPD related interrupts
>   */
> @@ -2615,6 +2690,103 @@ static void msm_dp_ctrl_config_msa(struct msm_dp_ctrl_private *ctrl,
>  			  nvid);
>  }
>  
> +/* TODO: comments here. */

Well, TODO, please.

> +static void msm_dp_ctrl_mst_calculate_rg(struct msm_dp_ctrl_private *ctrl,
> +					 struct msm_dp_panel *panel,
> +					 u32 *p_x_int, u32 *p_y_frac_enum)
> +{
> +	u64 min_slot_cnt, max_slot_cnt;
> +	u64 raw_target_sc, target_sc_fixp;
> +	u64 ts_denom, ts_enum, ts_int;
> +	u64 pclk = panel->msm_dp_mode.drm_mode.clock;
> +	u64 lclk = 0;
> +	u64 lanes = ctrl->link->link_params.num_lanes;
> +	u64 bpp = panel->msm_dp_mode.bpp;
> +	u64 pbn = panel->pbn;
> +	u64 numerator, denominator, temp, temp1, temp2;
> +	u32 x_int = 0, y_frac_enum = 0;
> +	u64 target_strm_sym, ts_int_fixp, ts_frac_fixp, y_frac_enum_fixp;
> +
> +	lclk = ctrl->link->link_params.rate;
> +
> +	/* min_slot_cnt */
> +	numerator = pclk * bpp * 64 * 1000;
> +	denominator = lclk * lanes * 8 * 1000;
> +	min_slot_cnt = drm_fixp_from_fraction(numerator, denominator);
> +
> +	/* max_slot_cnt */
> +	numerator = pbn * 54 * 1000;
> +	denominator = lclk * lanes;
> +	max_slot_cnt = drm_fixp_from_fraction(numerator, denominator);
> +
> +	/* raw_target_sc */
> +	numerator = max_slot_cnt + min_slot_cnt;
> +	denominator = drm_fixp_from_fraction(2, 1);
> +	raw_target_sc = drm_fixp_div(numerator, denominator);
> +
> +	/* target_sc */
> +	temp = drm_fixp_from_fraction(256 * lanes, 1);
> +	numerator = drm_fixp_mul(raw_target_sc, temp);
> +	denominator = drm_fixp_from_fraction(256 * lanes, 1);
> +	target_sc_fixp = drm_fixp_div(numerator, denominator);
> +
> +	ts_enum = 256 * lanes;
> +	ts_denom = drm_fixp_from_fraction(256 * lanes, 1);
> +	ts_int = drm_fixp2int(target_sc_fixp);
> +
> +	temp = drm_fixp2int_ceil(raw_target_sc);
> +	if (temp != ts_int) {
> +		temp = drm_fixp_from_fraction(ts_int, 1);
> +		temp1 = raw_target_sc - temp;
> +		temp2 = drm_fixp_mul(temp1, ts_denom);
> +		ts_enum = drm_fixp2int(temp2);
> +	}
> +
> +	/* target_strm_sym */
> +	ts_int_fixp = drm_fixp_from_fraction(ts_int, 1);
> +	ts_frac_fixp = drm_fixp_from_fraction(ts_enum, drm_fixp2int(ts_denom));
> +	temp = ts_int_fixp + ts_frac_fixp;
> +	temp1 = drm_fixp_from_fraction(lanes, 1);
> +	target_strm_sym = drm_fixp_mul(temp, temp1);
> +
> +	/* x_int */
> +	x_int = drm_fixp2int(target_strm_sym);
> +
> +	/* y_enum_frac */
> +	temp = drm_fixp_from_fraction(x_int, 1);
> +	temp1 = target_strm_sym - temp;
> +	temp2 = drm_fixp_from_fraction(256, 1);
> +	y_frac_enum_fixp = drm_fixp_mul(temp1, temp2);
> +
> +	temp1 = drm_fixp2int(y_frac_enum_fixp);
> +	temp2 = drm_fixp2int_ceil(y_frac_enum_fixp);
> +
> +	y_frac_enum = (u32)((temp1 == temp2) ? temp1 : temp1 + 1);
> +
> +	*p_x_int = x_int;
> +	*p_y_frac_enum = y_frac_enum;
> +
> +	drm_dbg_dp(ctrl->drm_dev, "MST lane_cnt:%llu, rate:%llu x_int:%d, y_frac:%d\n",
> +		   lanes, lclk, x_int, y_frac_enum);
> +}
> +
> +static void msm_dp_ctrl_mst_stream_setup(struct msm_dp_ctrl_private *ctrl,
> +					 struct msm_dp_panel *panel)
> +{
> +	u32 x_int, y_frac_enum;
> +
> +	if (!ctrl->mst_active)
> +		return;
> +
> +	drm_dbg_dp(ctrl->drm_dev, "MST stream channel allocation\n");
> +
> +	msm_dp_ctrl_mst_stream_channel_slot_setup(&ctrl->msm_dp_ctrl);
> +
> +	msm_dp_ctrl_mst_calculate_rg(ctrl, panel, &x_int, &y_frac_enum);
> +
> +	msm_dp_ctrl_update_rg(ctrl, panel->stream_id, x_int, y_frac_enum);
> +}
> +
>  int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train)
>  {
>  	int ret = 0;
> @@ -2701,6 +2873,8 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *
>  	if (!ctrl->mst_active)
>  		msm_dp_ctrl_setup_tr_unit(ctrl);
>  
> +	msm_dp_ctrl_mst_stream_setup(ctrl, msm_dp_panel);
> +
>  	msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, DP_STATE_CTRL_SEND_VIDEO);
>  
>  	ret = msm_dp_ctrl_mst_send_act(msm_dp_ctrl);
> @@ -2757,6 +2931,39 @@ void msm_dp_ctrl_off_link(struct msm_dp_ctrl *msm_dp_ctrl)
>  			phy, phy->init_count, phy->power_count);
>  }
>  
> +void msm_dp_ctrl_set_mst_channel_info(struct msm_dp_ctrl *msm_dp_ctrl,
> +				      enum msm_dp_stream_id stream_id,
> +				      u32 start_slot, u32 tot_slots)
> +{
> +	struct msm_dp_ctrl_private *ctrl;
> +
> +	if (!msm_dp_ctrl || stream_id >= DP_STREAM_MAX) {
> +		DRM_ERROR("invalid input\n");
> +		return;
> +	}
> +
> +	ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl);
> +
> +	ctrl->mst_ch_info[stream_id].start_slot = start_slot;
> +	ctrl->mst_ch_info[stream_id].tot_slots = tot_slots;
> +}
> +
> +void msm_dp_ctrl_mst_stream_channel_slot_setup(struct msm_dp_ctrl *msm_dp_ctrl)
> +{
> +	struct msm_dp_ctrl_private *ctrl;
> +	int i;
> +
> +	ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl);
> +
> +	if (!ctrl->mst_active)
> +		return;
> +
> +	for (i = DP_STREAM_0; i < ctrl->num_pixel_clks; i++) {
> +		msm_dp_ctrl_mst_channel_alloc(ctrl, i, ctrl->mst_ch_info[i].start_slot,
> +					      ctrl->mst_ch_info[i].tot_slots);
> +	}
> +}
> +
>  irqreturn_t msm_dp_ctrl_isr(struct msm_dp_ctrl *msm_dp_ctrl)
>  {
>  	struct msm_dp_ctrl_private *ctrl;
> diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.h b/drivers/gpu/drm/msm/dp/dp_ctrl.h
> index f82fd96e412a..c59338199399 100644
> --- a/drivers/gpu/drm/msm/dp/dp_ctrl.h
> +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.h
> @@ -17,7 +17,8 @@ struct msm_dp_ctrl {
>  struct phy;
>  
>  int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl, bool mst_active);
> -int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *msm_dp_panel);
> +int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl,
> +			  struct msm_dp_panel *msm_dp_panel);
>  int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train);
>  void msm_dp_ctrl_off_link(struct msm_dp_ctrl *msm_dp_ctrl);
>  void msm_dp_ctrl_off_pixel_clk(struct msm_dp_ctrl *msm_dp_ctrl, enum msm_dp_stream_id stream_id);
> @@ -52,4 +53,8 @@ void msm_dp_ctrl_disable_irq(struct msm_dp_ctrl *msm_dp_ctrl);
>  void msm_dp_ctrl_reinit_phy(struct msm_dp_ctrl *msm_dp_ctrl);
>  int msm_dp_ctrl_get_stream_cnt(struct msm_dp_ctrl *dp_ctrl);
>  int msm_dp_ctrl_mst_send_act(struct msm_dp_ctrl *msm_dp_ctrl);
> +void msm_dp_ctrl_mst_stream_channel_slot_setup(struct msm_dp_ctrl *msm_dp_ctrl);
> +void msm_dp_ctrl_set_mst_channel_info(struct msm_dp_ctrl *msm_dp_ctrl,
> +				      enum msm_dp_stream_id stream_id,
> +				      u32 start_slot, u32 tot_slots);
>  #endif /* _DP_CTRL_H_ */
> diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
> index 80bb5fc4003f..e0bf4dffa6af 100644
> --- a/drivers/gpu/drm/msm/dp/dp_display.c
> +++ b/drivers/gpu/drm/msm/dp/dp_display.c
> @@ -763,11 +763,13 @@ static int msm_dp_display_disable(struct msm_dp_display_private *dp)
>  	return 0;
>  }
>  
> -int msm_dp_display_set_stream_info(struct msm_dp *msm_dp_display,
> -				   struct msm_dp_panel *panel, enum msm_dp_stream_id stream_id)
> +int msm_dp_display_set_stream_info(struct msm_dp *msm_dp_display, struct msm_dp_panel *panel,
> +				   enum msm_dp_stream_id stream_id, u32 start_slot,
> +				   u32 num_slots, u32 pbn)
>  {
>  	int rc = 0;
>  	struct msm_dp_display_private *dp;
> +	const int max_slots = 64;
>  
>  	dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display);
>  
> @@ -776,7 +778,16 @@ int msm_dp_display_set_stream_info(struct msm_dp *msm_dp_display,
>  		return -EINVAL;
>  	}
>  
> +	if (start_slot + num_slots > max_slots) {
> +		DRM_ERROR("invalid channel info received. start:%d, slots:%d\n",
> +			  start_slot, num_slots);
> +		return -EINVAL;
> +	}
> +
> +	msm_dp_ctrl_set_mst_channel_info(dp->ctrl, stream_id, start_slot, num_slots);
> +
>  	panel->stream_id = stream_id;
> +	panel->pbn = pbn;
>  	msm_dp_panel_set_pixel_base(panel, dp->pixel_base[stream_id]);
>  
>  	return rc;
> @@ -1525,7 +1536,7 @@ void msm_dp_display_atomic_enable(struct msm_dp *msm_dp_display)
>  
>  	dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display);
>  
> -	msm_dp_display_set_stream_info(msm_dp_display, dp->panel, 0);
> +	msm_dp_display_set_stream_info(msm_dp_display, dp->panel, 0, 0, 0, 0);

Hmm.

>  
>  	rc = msm_dp_display_enable(dp);
>  	if (rc)
> @@ -1540,14 +1551,15 @@ void msm_dp_display_atomic_enable(struct msm_dp *msm_dp_display)
>  	drm_dbg_dp(msm_dp_display->drm_dev, "type=%d Done\n", msm_dp_display->connector_type);
>  }
>  
> -void msm_dp_display_atomic_disable(struct msm_dp *dp)
> +void msm_dp_display_atomic_disable(struct msm_dp *msm_dp_display)
>  {
> -	struct msm_dp_display_private *msm_dp_display;
> +	struct msm_dp_display_private *dp;
>  
> -	msm_dp_display = container_of(dp, struct msm_dp_display_private, msm_dp_display);
> +	dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display);
>  
> -	msm_dp_ctrl_push_idle(msm_dp_display->ctrl);
> -	msm_dp_ctrl_mst_send_act(msm_dp_display->ctrl);
> +	msm_dp_ctrl_push_idle(dp->ctrl);
> +	msm_dp_ctrl_mst_stream_channel_slot_setup(dp->ctrl);
> +	msm_dp_ctrl_mst_send_act(dp->ctrl);
>  }
>  
>  static void msm_dp_display_unprepare(struct msm_dp_display_private *dp)
> diff --git a/drivers/gpu/drm/msm/dp/dp_display.h b/drivers/gpu/drm/msm/dp/dp_display.h
> index fdbe6e4871d9..0ccdddb223c8 100644
> --- a/drivers/gpu/drm/msm/dp/dp_display.h
> +++ b/drivers/gpu/drm/msm/dp/dp_display.h
> @@ -43,7 +43,8 @@ void msm_dp_display_atomic_enable(struct msm_dp *dp_display);
>  enum drm_mode_status msm_dp_display_mode_valid(struct msm_dp *dp,
>  					       const struct drm_display_info *info,
>  					       const struct drm_display_mode *mode);
> -int msm_dp_display_set_stream_info(struct msm_dp *msm_dp_display,
> -				   struct msm_dp_panel *panel, enum msm_dp_stream_id stream_id);
> +int msm_dp_display_set_stream_info(struct msm_dp *msm_dp_display, struct msm_dp_panel *panel,
> +				   enum msm_dp_stream_id stream_id,
> +				   u32 start_slot, u32 num_slots, u32 pbn);
>  
>  #endif /* _DP_DISPLAY_H_ */
> diff --git a/drivers/gpu/drm/msm/dp/dp_panel.h b/drivers/gpu/drm/msm/dp/dp_panel.h
> index 4873c55bd693..8e7374de90eb 100644
> --- a/drivers/gpu/drm/msm/dp/dp_panel.h
> +++ b/drivers/gpu/drm/msm/dp/dp_panel.h
> @@ -50,6 +50,7 @@ struct msm_dp_panel {
>  	u32 hw_revision;
>  
>  	enum msm_dp_stream_id stream_id;
> +	u32 pbn;
>  
>  	u32 max_bw_code;
>  };
> diff --git a/drivers/gpu/drm/msm/dp/dp_reg.h b/drivers/gpu/drm/msm/dp/dp_reg.h
> index 87eaaefa014d..835a55446868 100644
> --- a/drivers/gpu/drm/msm/dp/dp_reg.h
> +++ b/drivers/gpu/drm/msm/dp/dp_reg.h
> @@ -338,7 +338,13 @@
>  #define DP_TPG_VIDEO_CONFIG_RGB			(0x00000004)
>  
>  /* DP MST registers */
> +
> +#define REG_DP_MSTLINK_DP_RG			(0X0000011C)
>  #define REG_DP1_CONFIGURATION_CTRL		(0x00000400)
> +#define REG_DP_DP0_TIMESLOT_1_32		(0x00000404)
> +#define REG_DP_DP0_TIMESLOT_33_63		(0x00000408)
> +#define REG_DP_DP1_TIMESLOT_1_32		(0x0000040C)
> +#define REG_DP_DP1_TIMESLOT_33_63		(0x00000410)
>  #define REG_DP1_SOFTWARE_MVID			(0x00000414)
>  #define REG_DP1_SOFTWARE_NVID			(0x00000418)
>  #define REG_DP1_TOTAL_HOR_VER			(0x0000041C)
> @@ -347,8 +353,12 @@
>  #define MMSS_DP1_SDP_CFG			(0x000004E0)
>  #define MMSS_DP1_SDP_CFG2			(0x000004E4)
>  #define MMSS_DP1_SDP_CFG3			(0x000004E8)
> +#define REG_DP_DP0_RG				(0x000004F8)
> +#define REG_DP_DP1_RG				(0x000004FC)
>  
>  #define REG_DP_MSTLINK_CONFIGURATION_CTRL	(0x00000034)
> +#define REG_DP_MSTLINK_TIMESLOT_1_32		(0x00000038)
> +#define REG_DP_MSTLINK_TIMESLOT_33_63		(0x0000003C)
>  #define REG_MSTLINK_SOFTWARE_MVID		(0x00000040)
>  #define REG_MSTLINK_SOFTWARE_NVID		(0x00000044)
>  #define REG_DP_MSTLINK_TOTAL_HOR_VER		(0x00000048)
> 
> -- 
> 2.43.0
> 

-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 24/39] drm/msm/dp: simplify link and clock disable sequence
  2026-04-10  9:33 ` [PATCH v4 24/39] drm/msm/dp: simplify link and clock disable sequence Yongxing Mou
@ 2026-04-12  0:00   ` Dmitry Baryshkov
  0 siblings, 0 replies; 128+ messages in thread
From: Dmitry Baryshkov @ 2026-04-12  0:00 UTC (permalink / raw)
  To: Yongxing Mou
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel

On Fri, Apr 10, 2026 at 05:33:59PM +0800, Yongxing Mou wrote:
> Move the common disable steps out of the sink_count check to make the
> flow easier to follow.
> 
> No functional change intended.
> 
> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
> ---
>  drivers/gpu/drm/msm/dp/dp_display.c | 25 ++++++++-----------------
>  1 file changed, 8 insertions(+), 17 deletions(-)
> 

Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>


-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 13/39] drm/msm/dp: introduce stream_id for each DP panel
  2026-04-10  9:33 ` [PATCH v4 13/39] drm/msm/dp: introduce stream_id for each DP panel Yongxing Mou
  2026-04-11 17:55   ` Dmitry Baryshkov
  2026-04-11 18:04   ` Dmitry Baryshkov
@ 2026-04-12  0:04   ` Dmitry Baryshkov
  2026-05-20  8:54     ` Yongxing Mou
  2 siblings, 1 reply; 128+ messages in thread
From: Dmitry Baryshkov @ 2026-04-12  0:04 UTC (permalink / raw)
  To: Yongxing Mou
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar

On Fri, Apr 10, 2026 at 05:33:48PM +0800, Yongxing Mou wrote:
> From: Abhinav Kumar <quic_abhinavk@quicinc.com>
> 
> With MST, each DP controller can handle multiple streams.
> There shall be one dp_panel for each stream but the dp_display
> object shall be shared among them. To represent this abstraction,
> create a stream_id for each DP panel which shall be set by the
> MST stream. For SST, default this to stream 0.
> 
> Use the stream ID to control the pixel clock of that respective
> stream by extending the clock handles and state tracking of the
> DP pixel clock to an array of max supported streams. The maximum
> streams currently is 4.
> 
> Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
> ---
>  drivers/gpu/drm/msm/dp/dp_ctrl.c    | 57 +++++++++++++++++++++++--------------
>  drivers/gpu/drm/msm/dp/dp_ctrl.h    |  2 +-
>  drivers/gpu/drm/msm/dp/dp_display.c | 24 ++++++++++++++--
>  drivers/gpu/drm/msm/dp/dp_display.h |  2 ++
>  drivers/gpu/drm/msm/dp/dp_panel.h   | 11 +++++++
>  5 files changed, 71 insertions(+), 25 deletions(-)
> 
> diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c
> index 120ec00884e5..fb6396727628 100644
> --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c
> +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c
> @@ -127,7 +127,7 @@ struct msm_dp_ctrl_private {
>  	unsigned int num_link_clks;
>  	struct clk_bulk_data *link_clks;
>  
> -	struct clk *pixel_clk;
> +	struct clk *pixel_clk[DP_STREAM_MAX];
>  
>  	union phy_configure_opts phy_opts;
>  
> @@ -139,7 +139,7 @@ struct msm_dp_ctrl_private {
>  
>  	bool core_clks_on;
>  	bool link_clks_on;
> -	bool stream_clks_on;
> +	bool stream_clks_on[DP_STREAM_MAX];

drivers/gpu/drm/msm/dp/dp_ctrl.c: In function ‘msm_dp_ctrl_core_clk_enable’:
drivers/gpu/drm/msm/dp/dp_ctrl.c:1937:9: warning: the comparison will always evaluate as ‘true’ for the address of ‘stream_clks_on’ will never be NULL [-Waddress]
 1937 |         drm_dbg_dp(ctrl->drm_dev, "stream_clks:%s link_clks:%s core_clks:%s\n",
      |         ^~~~~~~~~~


-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 25/39] drm/msm/dp: pass panel to display enable/disable helpers
  2026-04-10  9:34 ` [PATCH v4 25/39] drm/msm/dp: pass panel to display enable/disable helpers Yongxing Mou
@ 2026-04-12  0:05   ` Dmitry Baryshkov
  2026-05-28 10:13     ` Yongxing Mou
  0 siblings, 1 reply; 128+ messages in thread
From: Dmitry Baryshkov @ 2026-04-12  0:05 UTC (permalink / raw)
  To: Yongxing Mou
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel

On Fri, Apr 10, 2026 at 05:34:00PM +0800, Yongxing Mou wrote:
> Pass struct msm_dp_panel to the display enable/disable helpers to make
> them easier to reuse for MST stream handling.
> 
> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
> ---
>  drivers/gpu/drm/msm/dp/dp_display.c | 18 ++++++++++--------
>  1 file changed, 10 insertions(+), 8 deletions(-)
> 
> diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
> index 7b3b9160e005..88a078e53dc1 100644
> --- a/drivers/gpu/drm/msm/dp/dp_display.c
> +++ b/drivers/gpu/drm/msm/dp/dp_display.c
> @@ -668,7 +668,8 @@ static int msm_dp_display_prepare(struct msm_dp_display_private *dp)
>  	return msm_dp_ctrl_prepare_stream_on(dp->ctrl, force_link_train);
>  }
>  
> -static int msm_dp_display_enable(struct msm_dp_display_private *dp)
> +static int msm_dp_display_enable(struct msm_dp_display_private *dp,
> +				 struct msm_dp_panel *msm_dp_panel)

Where is it being used?

>  {
>  	int rc = 0;
>  	struct msm_dp *msm_dp_display = &dp->msm_dp_display;
> @@ -725,20 +726,21 @@ static void msm_dp_display_audio_notify_disable(struct msm_dp_display_private *d
>  	msm_dp_display->audio_enabled = false;
>  }
>  
> -static int msm_dp_display_disable(struct msm_dp_display_private *dp)
> +static int msm_dp_display_disable(struct msm_dp_display_private *dp,
> +				  struct msm_dp_panel *msm_dp_panel)
>  {
>  	struct msm_dp *msm_dp_display = &dp->msm_dp_display;
>  
>  	if (!msm_dp_display->power_on)
>  		return 0;
>  
> -	msm_dp_panel_disable_vsc_sdp(dp->panel);
> +	msm_dp_panel_disable_vsc_sdp(msm_dp_panel);
>  
> -	msm_dp_ctrl_off_pixel_clk(dp->ctrl, dp->panel->stream_id);
> +	msm_dp_ctrl_off_pixel_clk(dp->ctrl, msm_dp_panel->stream_id);
>  
>  	/* dongle is still connected but sinks are disconnected */
>  	if (dp->link->sink_count == 0)
> -		msm_dp_link_psm_config(dp->link, &dp->panel->link_info, true);
> +		msm_dp_link_psm_config(dp->link, &msm_dp_panel->link_info, true);
>  
>  	msm_dp_ctrl_off_link(dp->ctrl);
>  
> @@ -1529,14 +1531,14 @@ void msm_dp_display_atomic_enable(struct msm_dp *msm_dp_display)
>  
>  	msm_dp_display_set_stream_info(msm_dp_display, dp->panel, 0, 0, 0, 0);
>  
> -	rc = msm_dp_display_enable(dp);
> +	rc = msm_dp_display_enable(dp, dp->panel);
>  	if (rc)
>  		DRM_ERROR("DP display enable failed, rc=%d\n", rc);
>  
>  	rc = msm_dp_display_post_enable(msm_dp_display);
>  	if (rc) {
>  		DRM_ERROR("DP display post enable failed, rc=%d\n", rc);
> -		msm_dp_display_disable(dp);
> +		msm_dp_display_disable(dp, dp->panel);
>  	}
>  
>  	drm_dbg_dp(msm_dp_display->drm_dev, "type=%d Done\n", msm_dp_display->connector_type);
> @@ -1573,7 +1575,7 @@ void msm_dp_display_atomic_post_disable(struct msm_dp *dp)
>  
>  	msm_dp_display_audio_notify_disable(msm_dp_display);
>  
> -	msm_dp_display_disable(msm_dp_display);
> +	msm_dp_display_disable(msm_dp_display, msm_dp_display->panel);
>  
>  	msm_dp_display_unprepare(msm_dp_display);
>  }
> 
> -- 
> 2.43.0
> 

-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 26/39] drm/msm/dp: abstract out the dp_display stream helpers to accept a panel
  2026-04-10  9:34 ` [PATCH v4 26/39] drm/msm/dp: abstract out the dp_display stream helpers to accept a panel Yongxing Mou
@ 2026-04-12  0:07   ` Dmitry Baryshkov
  0 siblings, 0 replies; 128+ messages in thread
From: Dmitry Baryshkov @ 2026-04-12  0:07 UTC (permalink / raw)
  To: Yongxing Mou
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar

On Fri, Apr 10, 2026 at 05:34:01PM +0800, Yongxing Mou wrote:
> From: Abhinav Kumar <quic_abhinavk@quicinc.com>
> 
> Currently the dp_display bridge helpers, in particular the
> dp_display_enable()/dp_display_disable() use the cached panel.
> To be able to re-use these helpers for MST use-case abstract the
> helpers to use the panel which is passed in to them.

This commit is doing more than just passing the panel. Please move
relevant parts to the previos one and write a proper commit message for
the rest of the changes.

> 
> Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
> ---
>  drivers/gpu/drm/msm/dp/dp_display.c | 117 ++++++++++++++++++++++++------------
>  drivers/gpu/drm/msm/dp/dp_display.h |  12 ++++
>  2 files changed, 91 insertions(+), 38 deletions(-)
> 
> diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
> index 88a078e53dc1..33d8539afee7 100644
> --- a/drivers/gpu/drm/msm/dp/dp_display.c
> +++ b/drivers/gpu/drm/msm/dp/dp_display.c
> @@ -635,12 +635,14 @@ static int msm_dp_display_set_mode(struct msm_dp *msm_dp_display,
>  	return 0;
>  }
>  
> -static int msm_dp_display_prepare(struct msm_dp_display_private *dp)
> +int msm_dp_display_prepare(struct msm_dp *msm_dp_display)
>  {
> -	struct msm_dp *msm_dp_display = &dp->msm_dp_display;
> +	struct msm_dp_display_private *dp;
>  	int rc = 0;
>  	bool force_link_train = false;
>  
> +	dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display);
> +
>  	drm_dbg_dp(dp->drm_dev, "sink_count=%d\n", dp->link->sink_count);
>  
>  	if (msm_dp_display->is_edp)
> @@ -680,7 +682,7 @@ static int msm_dp_display_enable(struct msm_dp_display_private *dp,
>  		return 0;
>  	}
>  
> -	rc = msm_dp_ctrl_on_stream(dp->ctrl, dp->panel);
> +	rc = msm_dp_ctrl_on_stream(dp->ctrl, msm_dp_panel);
>  	if (!rc)
>  		msm_dp_display->power_on = true;
>  
> @@ -738,18 +740,6 @@ static int msm_dp_display_disable(struct msm_dp_display_private *dp,
>  
>  	msm_dp_ctrl_off_pixel_clk(dp->ctrl, msm_dp_panel->stream_id);
>  
> -	/* dongle is still connected but sinks are disconnected */
> -	if (dp->link->sink_count == 0)
> -		msm_dp_link_psm_config(dp->link, &msm_dp_panel->link_info, true);
> -
> -	msm_dp_ctrl_off_link(dp->ctrl);
> -
> -	if (dp->link->sink_count == 0)
> -		/* re-init the PHY so that we can listen to Dongle disconnect */
> -		msm_dp_ctrl_reinit_phy(dp->ctrl);
> -	else
> -		msm_dp_display_host_phy_exit(dp);
> -
>  	msm_dp_display->power_on = false;
>  
>  	drm_dbg_dp(dp->drm_dev, "sink count: %d\n", dp->link->sink_count);
> @@ -1495,76 +1485,116 @@ int msm_dp_modeset_init(struct msm_dp *msm_dp_display, struct drm_device *dev,
>  	return 0;
>  }
>  
> -void msm_dp_display_atomic_prepare(struct msm_dp *msm_dp_display,
> -				   struct drm_atomic_state *state)
> +int msm_dp_display_set_mode_helper(struct msm_dp *msm_dp_display,
> +				   struct drm_atomic_state *state,
> +				   struct drm_encoder *drm_encoder,
> +				   struct msm_dp_panel *msm_dp_panel)
>  {
>  	struct drm_crtc *crtc;
>  	struct drm_crtc_state *crtc_state;
> +
> +	crtc = drm_atomic_get_new_crtc_for_encoder(state, drm_encoder);
> +	if (!crtc)
> +		return 0;
> +	crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
> +
> +	return msm_dp_display_set_mode(msm_dp_display, &crtc_state->adjusted_mode, msm_dp_panel);
> +}
> +
> +void msm_dp_display_atomic_prepare(struct msm_dp *msm_dp_display,
> +				   struct drm_atomic_state *state)
> +{
>  	int rc = 0;
>  	struct msm_dp_display_private *dp;
>  
>  	dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display);
>  
> -	crtc = drm_atomic_get_new_crtc_for_encoder(state,
> -						   msm_dp_display->bridge->encoder);
> -	if (!crtc)
> -		return;
> -	crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
> -
> -	rc = msm_dp_display_set_mode(msm_dp_display, &crtc_state->adjusted_mode, dp->panel);
> +	rc = msm_dp_display_set_mode_helper(msm_dp_display, state,
> +					    msm_dp_display->bridge->encoder, dp->panel);
>  	if (rc) {
>  		DRM_ERROR("Failed to perform a mode set, rc=%d\n", rc);
>  		return;
>  	}
>  
> -	rc = msm_dp_display_prepare(dp);
> +	rc = msm_dp_display_prepare(msm_dp_display);
>  	if (rc)
>  		DRM_ERROR("DP display prepare failed, rc=%d\n", rc);
>  }
>  
> -void msm_dp_display_atomic_enable(struct msm_dp *msm_dp_display)
> +void msm_dp_display_enable_helper(struct msm_dp *msm_dp_display, struct msm_dp_panel *msm_dp_panel)
>  {
>  	struct msm_dp_display_private *dp;
>  	int rc = 0;
>  
>  	dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display);
>  
> -	msm_dp_display_set_stream_info(msm_dp_display, dp->panel, 0, 0, 0, 0);
> -
> -	rc = msm_dp_display_enable(dp, dp->panel);
> +	rc = msm_dp_display_enable(dp, msm_dp_panel);
>  	if (rc)
>  		DRM_ERROR("DP display enable failed, rc=%d\n", rc);
>  
>  	rc = msm_dp_display_post_enable(msm_dp_display);
>  	if (rc) {
>  		DRM_ERROR("DP display post enable failed, rc=%d\n", rc);
> -		msm_dp_display_disable(dp, dp->panel);
> +		msm_dp_display_disable(dp, msm_dp_panel);
>  	}
>  
>  	drm_dbg_dp(msm_dp_display->drm_dev, "type=%d Done\n", msm_dp_display->connector_type);
>  }
>  
> -void msm_dp_display_atomic_disable(struct msm_dp *msm_dp_display)
> +void msm_dp_display_atomic_enable(struct msm_dp *msm_dp_display)
>  {
>  	struct msm_dp_display_private *dp;
>  
>  	dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display);
>  
> -	msm_dp_ctrl_push_idle(dp->ctrl, dp->panel);
> +	msm_dp_display_set_stream_info(msm_dp_display, dp->panel, 0, 0, 0, 0);
> +
> +	msm_dp_display_enable_helper(msm_dp_display, dp->panel);
> +}
> +
> +void msm_dp_display_disable_helper(struct msm_dp *msm_dp_display,
> +				   struct msm_dp_panel *msm_dp_panel)
> +{
> +	struct msm_dp_display_private *dp;
> +
> +	dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display);
> +
> +	msm_dp_ctrl_push_idle(dp->ctrl, msm_dp_panel);
>  	msm_dp_ctrl_mst_stream_channel_slot_setup(dp->ctrl);
>  	msm_dp_ctrl_mst_send_act(dp->ctrl);
>  }
>  
> -static void msm_dp_display_unprepare(struct msm_dp_display_private *dp)
> +void msm_dp_display_atomic_disable(struct msm_dp *msm_dp_display)
>  {
> -	struct msm_dp *msm_dp_display = &dp->msm_dp_display;
> +	struct msm_dp_display_private *dp;
>  
> -	pm_runtime_put_sync(&msm_dp_display->pdev->dev);
> +	dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display);
> +
> +	msm_dp_display_disable_helper(msm_dp_display, dp->panel);
> +}
> +
> +void msm_dp_display_unprepare(struct msm_dp *msm_dp_display)
> +{
> +	struct msm_dp_display_private *dp;
> +
> +	dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display);
>  
> -	drm_dbg_dp(dp->drm_dev, "type=%d Done\n", msm_dp_display->connector_type);
> +	/* dongle is still connected but sinks are disconnected */
> +	if (dp->link->sink_count == 0)
> +		msm_dp_link_psm_config(dp->link, &dp->panel->link_info, true);
> +
> +	msm_dp_ctrl_off_link(dp->ctrl);
> +
> +	/* re-init the PHY so that we can listen to Dongle disconnect */
> +	if (dp->link->sink_count == 0)
> +		msm_dp_ctrl_reinit_phy(dp->ctrl);
> +	else
> +		msm_dp_display_host_phy_exit(dp);
> +
> +	pm_runtime_put_sync(&msm_dp_display->pdev->dev);
>  }
>  
> -void msm_dp_display_atomic_post_disable(struct msm_dp *dp)
> +void msm_dp_display_atomic_post_disable_helper(struct msm_dp *dp, struct msm_dp_panel *msm_dp_panel)
>  {
>  	struct msm_dp_display_private *msm_dp_display;
>  
> @@ -1575,7 +1605,18 @@ void msm_dp_display_atomic_post_disable(struct msm_dp *dp)
>  
>  	msm_dp_display_audio_notify_disable(msm_dp_display);
>  
> -	msm_dp_display_disable(msm_dp_display, msm_dp_display->panel);
> +	msm_dp_display_disable(msm_dp_display, msm_dp_panel);
> +
> +	drm_dbg_dp(dp->drm_dev, "type=%d Done\n", dp->connector_type);
> +}
> +
> +void msm_dp_display_atomic_post_disable(struct msm_dp *msm_dp_display)
> +{
> +	struct msm_dp_display_private *dp;
> +
> +	dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display);
> +
> +	msm_dp_display_atomic_post_disable_helper(msm_dp_display, dp->panel);
>  
>  	msm_dp_display_unprepare(msm_dp_display);
>  }
> diff --git a/drivers/gpu/drm/msm/dp/dp_display.h b/drivers/gpu/drm/msm/dp/dp_display.h
> index 0ccdddb223c8..0ede5505be58 100644
> --- a/drivers/gpu/drm/msm/dp/dp_display.h
> +++ b/drivers/gpu/drm/msm/dp/dp_display.h
> @@ -46,5 +46,17 @@ enum drm_mode_status msm_dp_display_mode_valid(struct msm_dp *dp,
>  int msm_dp_display_set_stream_info(struct msm_dp *msm_dp_display, struct msm_dp_panel *panel,
>  				   enum msm_dp_stream_id stream_id,
>  				   u32 start_slot, u32 num_slots, u32 pbn);
> +void msm_dp_display_enable_helper(struct msm_dp *msm_dp_display,
> +				  struct msm_dp_panel *msm_dp_panel);
> +void msm_dp_display_disable_helper(struct msm_dp *msm_dp_display,
> +				   struct msm_dp_panel *msm_dp_panel);
> +void msm_dp_display_atomic_post_disable_helper(struct msm_dp *msm_dp_display,
> +					       struct msm_dp_panel *msm_dp_panel);
> +int msm_dp_display_set_mode_helper(struct msm_dp *msm_dp_display,
> +				   struct drm_atomic_state *state,
> +				   struct drm_encoder *drm_encoder,
> +				   struct msm_dp_panel *msm_dp_panel);
> +int msm_dp_display_prepare(struct msm_dp *msm_dp_display);
> +void msm_dp_display_unprepare(struct msm_dp *dp);
>  
>  #endif /* _DP_DISPLAY_H_ */
> 
> -- 
> 2.43.0
> 

-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 27/39] drm/msm/dp: replace power_on with active_stream_cnt for dp_display
  2026-04-10  9:34 ` [PATCH v4 27/39] drm/msm/dp: replace power_on with active_stream_cnt for dp_display Yongxing Mou
@ 2026-04-12  0:11   ` Dmitry Baryshkov
  2026-06-15  8:45     ` Yongxing Mou
  0 siblings, 1 reply; 128+ messages in thread
From: Dmitry Baryshkov @ 2026-04-12  0:11 UTC (permalink / raw)
  To: Yongxing Mou
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar

On Fri, Apr 10, 2026 at 05:34:02PM +0800, Yongxing Mou wrote:
> From: Abhinav Kumar <quic_abhinavk@quicinc.com>
> 
> For DP MST, the link clock and power domain resources stay on until
> both streams have been disabled OR we receive hotplug. Introduce an
> active_stream_cnt to track the number of active streams and necessary
> state handling. Replace the power_on variable with active_stream_cnt
> as power_on boolean works only for a single stream.
> 
> Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
> ---
>  drivers/gpu/drm/msm/dp/dp_audio.c   |  2 +-
>  drivers/gpu/drm/msm/dp/dp_display.c | 38 +++++++++++++++++++------------------
>  drivers/gpu/drm/msm/dp/dp_display.h |  2 +-
>  3 files changed, 22 insertions(+), 20 deletions(-)
> 
> diff --git a/drivers/gpu/drm/msm/dp/dp_audio.c b/drivers/gpu/drm/msm/dp/dp_audio.c
> index 41018e82efa1..035e230201fd 100644
> --- a/drivers/gpu/drm/msm/dp/dp_audio.c
> +++ b/drivers/gpu/drm/msm/dp/dp_audio.c
> @@ -284,7 +284,7 @@ int msm_dp_audio_prepare(struct drm_bridge *bridge,
>  	 * such cases check for connection status and bail out if not
>  	 * connected.
>  	 */
> -	if (!msm_dp_display->power_on) {
> +	if (!msm_dp_display->active_stream_cnt) {
>  		rc = -EINVAL;
>  		goto end;
>  	}
> diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
> index 33d8539afee7..e6ecbb3a688e 100644
> --- a/drivers/gpu/drm/msm/dp/dp_display.c
> +++ b/drivers/gpu/drm/msm/dp/dp_display.c
> @@ -657,15 +657,15 @@ int msm_dp_display_prepare(struct msm_dp *msm_dp_display)
>  	if (dp->link->sink_count == 0)
>  		return rc;
>  
> -	if (!msm_dp_display->power_on) {
> +	if (!msm_dp_display->active_stream_cnt) {

Okay, I'm now being lost between the names. When is prepare being
called? Is it still part of the atomic_enable()? Or atomic_pre_enable()?

>  		msm_dp_display_host_phy_init(dp);
>  		force_link_train = true;
> -	}
>  
> -	rc = msm_dp_ctrl_on_link(dp->ctrl, msm_dp_display->mst_active);
> -	if (rc)
> -		DRM_ERROR("Failed link training (rc=%d)\n", rc);
> -	// TODO: schedule drm_connector_set_link_status_property()
> +		rc = msm_dp_ctrl_on_link(dp->ctrl, msm_dp_display->mst_active);
> +		if (rc)
> +			DRM_ERROR("Failed link training (rc=%d)\n", rc);
> +		// TODO: schedule drm_connector_set_link_status_property()
> +	}

Again, this more than the scope of the commit message.

>  
>  	return msm_dp_ctrl_prepare_stream_on(dp->ctrl, force_link_train);
>  }
> @@ -674,18 +674,12 @@ static int msm_dp_display_enable(struct msm_dp_display_private *dp,
>  				 struct msm_dp_panel *msm_dp_panel)
>  {
>  	int rc = 0;
> -	struct msm_dp *msm_dp_display = &dp->msm_dp_display;
>  
>  	drm_dbg_dp(dp->drm_dev, "sink_count=%d\n", dp->link->sink_count);
> -	if (msm_dp_display->power_on) {
> -		drm_dbg_dp(dp->drm_dev, "Link already setup, return\n");
> -		return 0;
> -	}
>  
>  	rc = msm_dp_ctrl_on_stream(dp->ctrl, msm_dp_panel);
> -	if (!rc)
> -		msm_dp_display->power_on = true;
>  
> +	dp->msm_dp_display.active_stream_cnt++;
>  	return rc;
>  }
>  
> @@ -731,16 +725,14 @@ static void msm_dp_display_audio_notify_disable(struct msm_dp_display_private *d
>  static int msm_dp_display_disable(struct msm_dp_display_private *dp,
>  				  struct msm_dp_panel *msm_dp_panel)
>  {
> -	struct msm_dp *msm_dp_display = &dp->msm_dp_display;
> -
> -	if (!msm_dp_display->power_on)
> +	if (!dp->msm_dp_display.active_stream_cnt)
>  		return 0;
>  
>  	msm_dp_panel_disable_vsc_sdp(msm_dp_panel);
>  
>  	msm_dp_ctrl_off_pixel_clk(dp->ctrl, msm_dp_panel->stream_id);
>  
> -	msm_dp_display->power_on = false;
> +	dp->msm_dp_display.active_stream_cnt--;
>  
>  	drm_dbg_dp(dp->drm_dev, "sink count: %d\n", dp->link->sink_count);
>  	return 0;
> @@ -876,7 +868,7 @@ void msm_dp_snapshot(struct msm_disp_state *disp_state, struct msm_dp *dp)
>  	 * power_on status before dumping DP registers to avoid crash due
>  	 * to unclocked access
>  	 */
> -	if (!dp->power_on)
> +	if (!dp->active_stream_cnt)
>  		return;
>  
>  	msm_disp_snapshot_add_block(disp_state, msm_dp_display->ahb_len,
> @@ -1559,6 +1551,11 @@ void msm_dp_display_disable_helper(struct msm_dp *msm_dp_display,
>  
>  	dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display);
>  
> +	if (!msm_dp_display->active_stream_cnt) {
> +		drm_dbg_dp(dp->drm_dev, "no active streams\n");
> +		return;
> +	}
> +
>  	msm_dp_ctrl_push_idle(dp->ctrl, msm_dp_panel);
>  	msm_dp_ctrl_mst_stream_channel_slot_setup(dp->ctrl);
>  	msm_dp_ctrl_mst_send_act(dp->ctrl);
> @@ -1579,6 +1576,11 @@ void msm_dp_display_unprepare(struct msm_dp *msm_dp_display)
>  
>  	dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display);
>  
> +	if (msm_dp_display->active_stream_cnt) {
> +		drm_dbg_dp(dp->drm_dev, "stream still active, return\n");
> +		return;
> +	}
> +
>  	/* dongle is still connected but sinks are disconnected */
>  	if (dp->link->sink_count == 0)
>  		msm_dp_link_psm_config(dp->link, &dp->panel->link_info, true);
> diff --git a/drivers/gpu/drm/msm/dp/dp_display.h b/drivers/gpu/drm/msm/dp/dp_display.h
> index 0ede5505be58..2548f67cd441 100644
> --- a/drivers/gpu/drm/msm/dp/dp_display.h
> +++ b/drivers/gpu/drm/msm/dp/dp_display.h
> @@ -18,7 +18,7 @@ struct msm_dp {
>  	struct drm_bridge *next_bridge;
>  	struct drm_bridge *bridge;
>  	bool audio_enabled;
> -	bool power_on;
> +	u32 active_stream_cnt;
>  	bool mst_active;
>  	unsigned int connector_type;
>  	bool is_edp;
> 
> -- 
> 2.43.0
> 

-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 28/39] drm/msm/dp: Mark the SST bridge disconnected when mst is active
  2026-04-10  9:34 ` [PATCH v4 28/39] drm/msm/dp: Mark the SST bridge disconnected when mst is active Yongxing Mou
@ 2026-04-12  0:11   ` Dmitry Baryshkov
  2026-06-15  8:47     ` Yongxing Mou
  0 siblings, 1 reply; 128+ messages in thread
From: Dmitry Baryshkov @ 2026-04-12  0:11 UTC (permalink / raw)
  To: Yongxing Mou
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar

On Fri, Apr 10, 2026 at 05:34:03PM +0800, Yongxing Mou wrote:
> From: Abhinav Kumar <quic_abhinavk@quicinc.com>
> 
> The bridge detect function is only applicable for SST. In MST mode,
> connector detection is handled by MST bridges. This patch skips
> detection for the SST bridge when MST is active.

"This patch"

> 
> Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
> Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
> ---
>  drivers/gpu/drm/msm/dp/dp_display.c | 3 +++
>  1 file changed, 3 insertions(+)
> 

-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 29/39] drm/msm/dp: add an API to initialize MST on sink side
  2026-04-10  9:34 ` [PATCH v4 29/39] drm/msm/dp: add an API to initialize MST on sink side Yongxing Mou
@ 2026-04-12  0:15   ` Dmitry Baryshkov
  2026-06-15  9:02     ` Yongxing Mou
  0 siblings, 1 reply; 128+ messages in thread
From: Dmitry Baryshkov @ 2026-04-12  0:15 UTC (permalink / raw)
  To: Yongxing Mou
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar

On Fri, Apr 10, 2026 at 05:34:04PM +0800, Yongxing Mou wrote:
> From: Abhinav Kumar <quic_abhinavk@quicinc.com>
> 
> If the DP controller is capable of supporting multiple streams
> then initialize the DP sink in MST mode by programming the DP_MSTM_CTRL
> DPCD register to enable MST mode.
> 
> Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
> ---
>  drivers/gpu/drm/msm/dp/dp_display.c | 57 ++++++++++++++++++++++++++++++++-----
>  1 file changed, 50 insertions(+), 7 deletions(-)
> 
> diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
> index 8ae690ce2b9f..abf26951819a 100644
> --- a/drivers/gpu/drm/msm/dp/dp_display.c
> +++ b/drivers/gpu/drm/msm/dp/dp_display.c
> @@ -14,6 +14,7 @@
>  #include <linux/string_choices.h>
>  #include <drm/display/drm_dp_aux_bus.h>
>  #include <drm/display/drm_hdmi_audio_helper.h>
> +#include <drm/display/drm_dp_mst_helper.h>
>  #include <drm/drm_edid.h>
>  
>  #include "msm_drv.h"
> @@ -270,6 +271,40 @@ static int msm_dp_display_lttpr_init(struct msm_dp_display_private *dp, u8 *dpcd
>  	return lttpr_count;
>  }
>  
> +static void msm_dp_display_mst_init(struct msm_dp_display_private *dp)
> +{
> +	const unsigned long clear_mstm_ctrl_timeout_us = 100000;
> +	u8 old_mstm_ctrl;
> +	struct msm_dp *msm_dp = &dp->msm_dp_display;
> +	int ret;
> +
> +	/* clear sink MST state */
> +	drm_dp_dpcd_read_byte(dp->aux, DP_MSTM_CTRL, &old_mstm_ctrl);
> +
> +	ret = drm_dp_dpcd_write_byte(dp->aux, DP_MSTM_CTRL, 0);
> +	if (ret < 0) {
> +		DRM_ERROR("failed to clear DP_MSTM_CTRL, ret=%d\n", ret);
> +		return;
> +	}
> +
> +	/* add extra delay if MST old state is on*/
> +	if (old_mstm_ctrl) {
> +		drm_dbg_dp(dp->drm_dev, "wait %luus to set DP_MSTM_CTRL set 0\n",
> +			   clear_mstm_ctrl_timeout_us);
> +		usleep_range(clear_mstm_ctrl_timeout_us,
> +			     clear_mstm_ctrl_timeout_us + 1000);

For 100 ms you should be using msleep() instead. But where is that
timeout coming from?

> +	}
> +
> +	ret = drm_dp_dpcd_write_byte(dp->aux, DP_MSTM_CTRL,
> +				     DP_MST_EN | DP_UP_REQ_EN | DP_UPSTREAM_IS_SRC);

Isn't it too early to enable MST? (I don't remember this part of the
standard).

> +	if (ret < 0) {
> +		DRM_ERROR("sink MST enablement failed\n");
> +		return;
> +	}
> +
> +	msm_dp->mst_active = true;
> +}
> +
>  static int msm_dp_display_process_hpd_high(struct msm_dp_display_private *dp)
>  {
>  	struct drm_connector *connector = dp->msm_dp_display.connector;
> @@ -288,14 +323,19 @@ static int msm_dp_display_process_hpd_high(struct msm_dp_display_private *dp)
>  	if (rc)
>  		goto end;
>  
> -	drm_edid = drm_edid_read_ddc(connector, &dp->aux->ddc);
> -	drm_edid_connector_update(connector, drm_edid);
> +	if (!(dp->max_stream > 1) || !drm_dp_read_mst_cap(dp->aux, dp->panel->dpcd)) {
> +		drm_edid = drm_edid_read_ddc(connector, &dp->aux->ddc);
> +		drm_edid_connector_update(connector, drm_edid);
>  
> -	if (!drm_edid) {
> -		DRM_ERROR("panel edid read failed\n");
> -		/* check edid read fail is due to unplug */
> -		if (!msm_dp_aux_is_link_connected(dp->aux))
> -			return -ETIMEDOUT;
> +		if (!drm_edid) {
> +			DRM_ERROR("panel edid read failed\n");
> +			/* check edid read fail is due to unplug */
> +			if (!msm_dp_aux_is_link_connected(dp->aux))
> +				return -ETIMEDOUT;
> +		}
> +
> +		if (rc)
> +			goto end;
>  	}
>  
>  	msm_dp_link_process_request(dp->link);
> @@ -317,6 +357,9 @@ static int msm_dp_display_process_hpd_high(struct msm_dp_display_private *dp)
>  	 */
>  	msm_dp_link_psm_config(dp->link, &dp->panel->link_info, false);
>  
> +	if (dp->max_stream > 1 && drm_dp_read_mst_cap(dp->aux, dp->panel->dpcd))
> +		msm_dp_display_mst_init(dp);
> +
>  	msm_dp_link_reset_phy_params_vx_px(dp->link);
>  
>  end:
> 
> -- 
> 2.43.0
> 

-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 30/39] drm/msm/dp: add dp_display_get_panel() to initialize DP panel
  2026-04-10  9:34 ` [PATCH v4 30/39] drm/msm/dp: add dp_display_get_panel() to initialize DP panel Yongxing Mou
@ 2026-04-12  0:17   ` Dmitry Baryshkov
  0 siblings, 0 replies; 128+ messages in thread
From: Dmitry Baryshkov @ 2026-04-12  0:17 UTC (permalink / raw)
  To: Yongxing Mou
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar

On Fri, Apr 10, 2026 at 05:34:05PM +0800, Yongxing Mou wrote:
> From: Abhinav Kumar <quic_abhinavk@quicinc.com>
> 
> Add an API dp_display_get_panel() to initialize and return a DP

The commit message wasn't updated.

> panel to be used by DP MST module. Since some of the fields of
> DP panel are private, dp_display module needs to initialize these
> parts and return the panel back.
> 
> Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
> ---
>  drivers/gpu/drm/msm/dp/dp_display.c | 24 ++++++++++++++++++++++++
>  drivers/gpu/drm/msm/dp/dp_display.h |  1 +
>  2 files changed, 25 insertions(+)
> 

-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 31/39] drm/msm/dp: add prepared to manage link-level operations
  2026-04-10  9:34 ` [PATCH v4 31/39] drm/msm/dp: add prepared to manage link-level operations Yongxing Mou
@ 2026-04-12  0:19   ` Dmitry Baryshkov
  0 siblings, 0 replies; 128+ messages in thread
From: Dmitry Baryshkov @ 2026-04-12  0:19 UTC (permalink / raw)
  To: Yongxing Mou
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar

On Fri, Apr 10, 2026 at 05:34:06PM +0800, Yongxing Mou wrote:
> In MST mode, multiple streams share the same DP link. Track a prepared
> state so msm_dp_display_prepare() runs only once per link and repeated
> calls are skipped.
> 
> Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
> ---
>  drivers/gpu/drm/msm/dp/dp_display.c | 34 ++++++++++++++++++++++++++--------
>  drivers/gpu/drm/msm/dp/dp_display.h |  1 +
>  2 files changed, 27 insertions(+), 8 deletions(-)
> 
> diff --git a/drivers/gpu/drm/msm/dp/dp_display.h b/drivers/gpu/drm/msm/dp/dp_display.h
> index 5f3ef295d710..bda76319c459 100644
> --- a/drivers/gpu/drm/msm/dp/dp_display.h
> +++ b/drivers/gpu/drm/msm/dp/dp_display.h
> @@ -22,6 +22,7 @@ struct msm_dp {
>  	bool mst_active;
>  	unsigned int connector_type;
>  	bool is_edp;
> +	bool prepared;

link_ready

>  
>  	struct msm_dp_audio *msm_dp_audio;
>  	bool psr_supported;
> 
> -- 
> 2.43.0
> 

-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 03/39] drm/msm/dp: break up dp_display_enable into two parts
  2026-04-10  9:33 ` [PATCH v4 03/39] drm/msm/dp: break up dp_display_enable into two parts Yongxing Mou
  2026-04-11 17:33   ` Dmitry Baryshkov
@ 2026-04-12  0:20   ` Dmitry Baryshkov
  1 sibling, 0 replies; 128+ messages in thread
From: Dmitry Baryshkov @ 2026-04-12  0:20 UTC (permalink / raw)
  To: Yongxing Mou
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar

On Fri, Apr 10, 2026 at 05:33:38PM +0800, Yongxing Mou wrote:
> From: Abhinav Kumar <quic_abhinavk@quicinc.com>
> 
> dp_display_enable() currently re-trains the link if needed and then
> enables the pixel clock, programs the controller to start sending the
> pixel stream. Split these two parts into prepare/enable APIs, to support
> MST bridges_enable insert the MST payloads funcs between enable
> stream_clks and program register.
> 
> Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
> ---
>  drivers/gpu/drm/msm/dp/dp_ctrl.c    |  54 ++++++++++++-------
>  drivers/gpu/drm/msm/dp/dp_ctrl.h    |   3 +-
>  drivers/gpu/drm/msm/dp/dp_display.c | 105 +++++++++++++++++++++++-------------
>  3 files changed, 106 insertions(+), 56 deletions(-)
> 
> diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
> index e9f0b96c3ebd..5ecbc83c3838 100644
> --- a/drivers/gpu/drm/msm/dp/dp_display.c
> +++ b/drivers/gpu/drm/msm/dp/dp_display.c
> @@ -622,7 +622,40 @@ static int msm_dp_display_set_mode(struct msm_dp *msm_dp_display,
>  	return 0;
>  }
>  
> -static int msm_dp_display_enable(struct msm_dp_display_private *dp, bool force_link_train)
> +static int msm_dp_display_prepare(struct msm_dp_display_private *dp)

After going through the patches, msm_dp_display_prepare() ->
prepare_link.

> +{
> +	struct msm_dp *msm_dp_display = &dp->msm_dp_display;
> +	int rc = 0;
> +	bool force_link_train = false;
> +

-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 00/39] drm/msm/dp: Add MST support for MSM chipsets
  2026-04-10  9:33 [PATCH v4 00/39] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
                   ` (39 preceding siblings ...)
  2026-04-10 13:53 ` [PATCH v4 00/39] drm/msm/dp: Add MST support for MSM chipsets Dmitry Baryshkov
@ 2026-04-12  0:34 ` Dmitry Baryshkov
  2026-05-22  8:07   ` Yongxing Mou
  40 siblings, 1 reply; 128+ messages in thread
From: Dmitry Baryshkov @ 2026-04-12  0:34 UTC (permalink / raw)
  To: Yongxing Mou
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar

On Fri, Apr 10, 2026 at 05:33:35PM +0800, Yongxing Mou wrote:
> Add support for Multi-stream transport for MSM chipsets that allow
> a single instance of DP controller to send multiple streams.
> 
> This series has been validated on sa8775p ride platform using multiple
> MST dongles and also daisy chain method on both DP0 and DP1 upto 1080P.
> 
> With 4x4K monitors, due to lack of layer mixers that combination will not
> work but this can be supported as well after some rework on the DPU side.
> 
> In addition, SST was re-validated with all these changes to ensure there
> were no regressions.
> 
> This patch series was made on top of:
> 
> [1] : https://patchwork.freedesktop.org/series/151522/ (v5 to fix up HPD)
> 
> Overall, the patch series has been organized in the following way:
> 
> 1) First set are a couple of fixes made while debugging MST but applicable
> to SST as well so go ahead of everything else
> 2) Prepare the DP driver to get ready to handle multiple streams. This is the bulk
> of the work as current DP driver design had to be adjusted to make this happen.
> 3) Finally, new files to handle MST related operations

General suggestion. Please reorg the series into the more logical
chunks. If you are touching enable / disable path, then continue doing
that until its done (more or less). I'd really like to be able to merge
at least a part of the series in the next cycle, but there needs to be a
logical "halfway done" moment. Let's define it at the "all paths are
refactored, all booleans are in place, we are ready to get MST parts".

I've not found a use for separate bridges in the MST path. Please split
the functionality between the MST connector and, maybe, another private
object for generic state (like connector -> encoder mapping). Other
drivers don't have this issue because they have fixed CRTC -> encoder
mapping. MSM doesn't so we need a separate way to store that. It's sad
that we can't subclass MST topology manager (or its state). Maybe it
would be worth changing that and letting our topology manager handle the
assignment in it.

Also, I found a set of warnings while trying to build the code. Please
make sure that there are no warnings.

> Note:
> Validation for this series has so far been done on the latest linux-next
> on LeMans, covering both FB console and Weston.

It wasn't. I couldn't apply it to the latest linux-next. There were
fuzz-based rejections because of one of the patches merged some time
ago.

> 
> Broader validation, including additional Type-C DP use cases, is still
> in progress and may lead to some follow-up adjustments in the next
> revision. I wanted to post the current version first to collect early
> feedback on the overall approach.

It's nice, but please keep in mind that majority of users use Type-C
rather than native DP connectors. In some cases your lack of Type-C
testing affects the design (e.g. for the HPD handling).

> 
> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>

-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 34/39] drm/msm/dp: add dp_mst_drm to manage DP MST bridge operations
  2026-04-11 23:51   ` Dmitry Baryshkov
@ 2026-05-18 12:00     ` Yongxing Mou
  0 siblings, 0 replies; 128+ messages in thread
From: Yongxing Mou @ 2026-05-18 12:00 UTC (permalink / raw)
  To: Dmitry Baryshkov
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar



On 4/12/2026 7:51 AM, Dmitry Baryshkov wrote:
> On Fri, Apr 10, 2026 at 05:34:09PM +0800, Yongxing Mou wrote:
>> From: Abhinav Kumar <quic_abhinavk@quicinc.com>
>>
>> Add dp_mst_drm to manage the DP MST bridge operations similar to the
>> dp_drm file which manages the SST bridge operations. Each MST encoder
>> creates one bridge and each bridge is bound to its own dp_panel
>> abstraction to manage the operations of its pipeline.
> 
> This doesn't explain the most important part: why do we need a bridge?
> There is nothing else between the encoder and the connector. The bridge
> state is almost empty. Can we squash it into the MST connector?
> 
Emm ... I'll remove it in the next version and merge the logic directly 
into the MST connector. Here seems need lots of changes..
>>
>> Keep the connector/panel association in bridge private state for atomic
>> assignment and release, and mirror it in the bridge object for runtime
>> bridge callbacks.
>>
>> Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
>> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
>> ---
>>   drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c |   6 +
>>   drivers/gpu/drm/msm/dp/dp_mst_drm.c     | 459 +++++++++++++++++++++++++++++++-
>>   drivers/gpu/drm/msm/msm_drv.h           |   7 +
>>   3 files changed, 471 insertions(+), 1 deletion(-)
>>
>> diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c
>> index d7ce13a4586d..89868443c0fe 100644
>> --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c
>> +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c
>> @@ -692,6 +692,12 @@ static int _dpu_kms_initialize_displayport(struct drm_device *dev,
>>   					DPU_ERROR("encoder init failed for dp mst display\n");
>>   					return PTR_ERR(encoder);
>>   				}
>> +
>> +				rc = msm_dp_mst_attach_encoder(priv->kms->dp[i], encoder);
>> +				if (rc) {
>> +					DPU_ERROR("DP MST init failed, %d\n", rc);
>> +					continue;
>> +				}
>>   			}
>>   		}
>>   	}
>> diff --git a/drivers/gpu/drm/msm/dp/dp_mst_drm.c b/drivers/gpu/drm/msm/dp/dp_mst_drm.c
>> index b6c7b8211025..4df3ea5e36d0 100644
>> --- a/drivers/gpu/drm/msm/dp/dp_mst_drm.c
>> +++ b/drivers/gpu/drm/msm/dp/dp_mst_drm.c
>> @@ -13,8 +13,47 @@
>>   
>>   #define MAX_DPCD_TRANSACTION_BYTES 16
>>   
>> +#define to_dp_mst_bridge(x)     container_of((x), struct msm_dp_mst_bridge, base)
>> +#define to_dp_mst_bridge_state_priv(x) \
>> +		container_of((x), struct msm_dp_mst_bridge_state, base)
>> +#define to_dp_mst_bridge_state(x) \
>> +		to_dp_mst_bridge_state_priv((x)->obj.state)
>> +#define to_dp_mst_connector(x) \
>> +		container_of((x), struct msm_dp_mst_connector, connector)
>> +
>> +#define DP_MST_CONN_ID(x) ((x)->connector ? \
>> +		(x)->connector->base.id : 0)
>> +
>> +struct msm_dp_mst_bridge {
>> +	struct drm_bridge base;
>> +	struct drm_private_obj obj;
>> +	u32 id;
>> +
>> +	bool initialized;
>> +
>> +	struct msm_dp *display;
>> +	struct drm_encoder *encoder;
>> +
>> +	struct drm_connector *connector;
>> +	struct msm_dp_panel *msm_dp_panel;
>> +};
>> +
>> +struct msm_dp_mst_bridge_state {
>> +	struct drm_private_state base;
>> +	struct drm_connector *connector;
>> +	struct msm_dp_panel *msm_dp_panel;
>> +};
>> +
>> +struct msm_dp_mst_connector {
>> +	struct drm_connector connector;
>> +	struct drm_dp_mst_port *mst_port;
>> +	struct msm_dp_mst *dp_mst;
>> +	struct msm_dp_panel *dp_panel;
> 
> After squashing the bridge to the connector, you might need to separate
> state from the non-state variables.
> 
>> +};
>> +
>>   struct msm_dp_mst {
>>   	struct drm_dp_mst_topology_mgr mst_mgr;
>> +	struct msm_dp_mst_bridge *mst_bridge[DP_STREAM_MAX];
>>   	struct msm_dp *msm_dp;
>>   	struct drm_dp_aux *dp_aux;
>>   	u32 max_streams;
>> @@ -22,6 +61,419 @@ struct msm_dp_mst {
>>   	struct mutex mst_lock;
>>   };
>>   
>> +static struct drm_private_state *msm_dp_mst_duplicate_bridge_state(struct drm_private_obj *obj)
>> +{
>> +	struct msm_dp_mst_bridge_state *mst_bridge_state;
>> +
>> +	mst_bridge_state = kmemdup(obj->state, sizeof(*mst_bridge_state), GFP_KERNEL);
>> +	if (!mst_bridge_state)
>> +		return NULL;
>> +
>> +	__drm_atomic_helper_private_obj_duplicate_state(obj, &mst_bridge_state->base);
>> +
>> +	return &mst_bridge_state->base;
>> +}
>> +
>> +static void msm_dp_mst_destroy_bridge_state(struct drm_private_obj *obj,
>> +					    struct drm_private_state *state)
>> +{
>> +	struct msm_dp_mst_bridge_state *mst_bridge_state =
>> +		to_dp_mst_bridge_state_priv(state);
>> +
>> +	kfree(mst_bridge_state);
>> +}
>> +
>> +static const struct drm_private_state_funcs msm_dp_mst_bridge_state_funcs = {
>> +	.atomic_duplicate_state = msm_dp_mst_duplicate_bridge_state,
>> +	.atomic_destroy_state = msm_dp_mst_destroy_bridge_state,
>> +};
>> +
>> +static struct msm_dp_mst_bridge_state *msm_dp_mst_br_priv_state(struct drm_atomic_state *st,
>> +								struct msm_dp_mst_bridge *bridge)
>> +{
>> +	struct drm_device *dev = bridge->base.dev;
>> +	struct drm_private_state *obj_state = drm_atomic_get_private_obj_state(st, &bridge->obj);
>> +
>> +	WARN_ON(!drm_modeset_is_locked(&dev->mode_config.connection_mutex));
>> +
>> +	return to_dp_mst_bridge_state_priv(obj_state);
>> +}
>> +
>> +static void msm_dp_mst_update_timeslots(struct msm_dp_mst *mst,
>> +					struct msm_dp_mst_bridge *mst_bridge,
>> +					struct drm_atomic_state *state,
>> +					struct drm_dp_mst_port *port)
>> +{
>> +	struct drm_dp_mst_topology_state *mst_state;
>> +	struct drm_dp_mst_atomic_payload *payload;
>> +
>> +	mst_state = to_drm_dp_mst_topology_state(mst->mst_mgr.base.state);
>> +	payload = drm_atomic_get_mst_payload_state(mst_state, port);
>> +
>> +	if (!payload) {
>> +		DRM_ERROR("MST bridge [%d] update_timeslots failed, null payload\n",
>> +			  mst_bridge->id);
>> +		return;
>> +	}
>> +
>> +	if (payload->vc_start_slot < 0)
>> +		msm_dp_display_set_stream_info(mst->msm_dp, mst_bridge->msm_dp_panel,
>> +					       mst_bridge->id, 1, 0, 0);
>> +	else
>> +		msm_dp_display_set_stream_info(mst->msm_dp, mst_bridge->msm_dp_panel,
>> +					       mst_bridge->id, payload->vc_start_slot,
>> +					       payload->time_slots, payload->pbn);
>> +}
>> +
>> +static int msm_dp_mst_bridge_pre_enable_part1(struct msm_dp_mst_bridge *dp_bridge,
>> +					      struct drm_atomic_state *state)
>> +{
>> +	struct msm_dp *dp_display = dp_bridge->display;
>> +	struct msm_dp_mst *mst = dp_display->msm_dp_mst;
>> +	struct msm_dp_mst_connector *mst_conn = to_dp_mst_connector(dp_bridge->connector);
>> +	struct drm_dp_mst_port *port = mst_conn->mst_port;
>> +	struct drm_dp_mst_topology_state *mst_state;
>> +	struct drm_dp_mst_atomic_payload *payload;
>> +	struct msm_dp_panel *dp_panel = mst_conn->dp_panel;
>> +	int pbn;
>> +	int rc = 0;
>> +
>> +	mst_state = drm_atomic_get_new_mst_topology_state(state, &mst->mst_mgr);
>> +
>> +	pbn = drm_dp_calc_pbn_mode(dp_panel->msm_dp_mode.drm_mode.clock,
>> +				   (mst_conn->connector.display_info.bpc * 3) << 4);
>> +
>> +	payload = drm_atomic_get_mst_payload_state(mst_state, port);
>> +	if (!payload || payload->time_slots <= 0) {
>> +		DRM_ERROR("time slots not allocated for conn:%d\n", DP_MST_CONN_ID(dp_bridge));
>> +		rc = -EINVAL;
>> +		return rc;
>> +	}
>> +
>> +	drm_dbg_dp(dp_display->drm_dev, "conn:%d pbn:%d, slots:%d\n", DP_MST_CONN_ID(dp_bridge),
>> +		   pbn, payload->time_slots);
>> +
>> +	drm_dp_mst_update_slots(mst_state, DP_CAP_ANSI_8B10B);
>> +
>> +	rc = drm_dp_add_payload_part1(&mst->mst_mgr, mst_state, payload);
>> +	if (rc) {
>> +		DRM_ERROR("payload allocation failure for conn:%d\n", DP_MST_CONN_ID(dp_bridge));
>> +		return rc;
>> +	}
>> +
>> +	msm_dp_mst_update_timeslots(mst, dp_bridge, state, port);
>> +
>> +	return rc;
>> +}
>> +
>> +static void _msm_dp_mst_bridge_pre_enable_part2(struct msm_dp_mst_bridge *dp_bridge,
>> +						struct drm_atomic_state *state)
>> +{
>> +	struct msm_dp *dp_display = dp_bridge->display;
>> +	struct msm_dp_mst *mst = dp_display->msm_dp_mst;
>> +	struct msm_dp_mst_connector *mst_conn = to_dp_mst_connector(dp_bridge->connector);
>> +	struct drm_dp_mst_port *port = mst_conn->mst_port;
>> +	struct drm_dp_mst_topology_state *mst_state;
>> +	struct drm_dp_mst_atomic_payload *payload;
>> +
>> +	drm_dp_check_act_status(&mst->mst_mgr);
>> +
>> +	mst_state = to_drm_dp_mst_topology_state(mst->mst_mgr.base.state);
>> +	payload = drm_atomic_get_mst_payload_state(mst_state, port);
>> +
>> +	drm_dp_add_payload_part2(&mst->mst_mgr, payload);
>> +
>> +	drm_dbg_dp(dp_display->drm_dev, "MST bridge [%d] _pre enable part-2 complete\n",
>> +		   dp_bridge->id);
>> +}
>> +
>> +static void msm_dp_mst_bridge_pre_disable_part1(struct msm_dp_mst_bridge *dp_bridge,
>> +						struct drm_atomic_state *state)
>> +{
>> +	struct msm_dp *dp_display = dp_bridge->display;
>> +	struct msm_dp_mst *mst = dp_display->msm_dp_mst;
>> +	struct msm_dp_mst_connector *mst_conn = to_dp_mst_connector(dp_bridge->connector);
>> +	struct drm_dp_mst_port *port = mst_conn->mst_port;
>> +	struct drm_dp_mst_topology_state *old_mst_state;
>> +	struct drm_dp_mst_topology_state *new_mst_state;
>> +	const struct drm_dp_mst_atomic_payload *old_payload;
>> +	struct drm_dp_mst_atomic_payload *new_payload;
>> +
>> +	old_mst_state = drm_atomic_get_old_mst_topology_state(state, &mst->mst_mgr);
>> +	new_mst_state = drm_atomic_get_new_mst_topology_state(state, &mst->mst_mgr);
>> +
>> +	old_payload = drm_atomic_get_mst_payload_state(old_mst_state, port);
>> +	new_payload = drm_atomic_get_mst_payload_state(new_mst_state, port);
>> +
>> +	if (!old_payload || !new_payload) {
>> +		DRM_ERROR("MST bridge [%d] _pre disable part-1 failed, null payload\n",
>> +			  dp_bridge->id);
>> +		return;
>> +	}
>> +
>> +	drm_dp_remove_payload_part1(&mst->mst_mgr, new_mst_state, new_payload);
>> +	drm_dp_remove_payload_part2(&mst->mst_mgr, new_mst_state, old_payload, new_payload);
>> +
>> +	msm_dp_mst_update_timeslots(mst, dp_bridge, state, port);
>> +
>> +	drm_dbg_dp(dp_display->drm_dev, "MST bridge [%d] _pre disable part-1 complete\n",
>> +		   dp_bridge->id);
>> +}
>> +
>> +static void msm_dp_mst_bridge_atomic_pre_enable(struct drm_bridge *drm_bridge,
>> +						struct drm_atomic_state *state)
>> +{
>> +	int rc = 0;
>> +	struct msm_dp_mst_bridge *bridge;
>> +	struct msm_dp *dp_display;
>> +	struct msm_dp_mst_bridge_state *mst_bridge_state;
>> +	struct msm_dp_mst *dp_mst;
>> +	struct msm_dp_panel   *msm_dp_panel;
>> +
>> +	if (!drm_bridge) {
>> +		DRM_ERROR("Invalid params\n");
>> +		return;
>> +	}
>> +
>> +	bridge = to_dp_mst_bridge(drm_bridge);
>> +	mst_bridge_state = to_dp_mst_bridge_state(bridge);
>> +	dp_display = bridge->display;
>> +	dp_mst = dp_display->msm_dp_mst;
>> +
>> +	/* to cover cases of bridge_disable/bridge_enable without modeset */
>> +	bridge->connector = mst_bridge_state->connector;
>> +	bridge->msm_dp_panel = mst_bridge_state->msm_dp_panel;
>> +
>> +	if (!bridge->connector) {
>> +		DRM_ERROR("Invalid connector\n");
>> +		return;
>> +	}
>> +
>> +	msm_dp_panel = bridge->msm_dp_panel;
>> +	mutex_lock(&dp_mst->mst_lock);
>> +
>> +	rc = msm_dp_display_set_mode_helper(dp_display, state, drm_bridge->encoder, msm_dp_panel);
>> +	if (rc) {
>> +		DRM_ERROR("Failed to perform a mode set, rc=%d\n", rc);
>> +		mutex_unlock(&dp_mst->mst_lock);
>> +		return;
>> +	}
>> +	msm_dp_panel->pbn = drm_dp_calc_pbn_mode(msm_dp_panel->msm_dp_mode.drm_mode.clock,
>> +						 msm_dp_panel->msm_dp_mode.bpp << 4);
>> +	rc = msm_dp_display_prepare(dp_display);
>> +	if (rc) {
>> +		DRM_ERROR("[%d] DP display pre-enable failed, rc=%d\n", bridge->id, rc);
>> +		msm_dp_display_unprepare(dp_display);
>> +		mutex_unlock(&dp_mst->mst_lock);
>> +		return;
>> +	}
>> +
>> +	rc = msm_dp_mst_bridge_pre_enable_part1(bridge, state);
>> +	if (rc) {
>> +		DRM_ERROR("[%d] DP display pre-enable failed, rc=%d\n", bridge->id, rc);
>> +		mutex_unlock(&dp_mst->mst_lock);
>> +		return;
>> +	}
>> +
>> +	msm_dp_display_enable_helper(dp_display, bridge->msm_dp_panel);
>> +
>> +	_msm_dp_mst_bridge_pre_enable_part2(bridge, state);
>> +
>> +	mutex_unlock(&dp_mst->mst_lock);
>> +
>> +	drm_dbg_dp(dp_display->drm_dev, "conn:%d mode:%s pre enable done\n",
>> +		   DP_MST_CONN_ID(bridge), bridge->msm_dp_panel->msm_dp_mode.drm_mode.name);
>> +}
>> +
>> +static void msm_dp_mst_bridge_atomic_disable(struct drm_bridge *drm_bridge,
>> +					     struct drm_atomic_state *state)
>> +{
>> +	struct msm_dp_mst_bridge *bridge;
>> +	struct msm_dp *dp_display;
>> +	struct msm_dp_mst *mst;
>> +
>> +	if (!drm_bridge) {
>> +		DRM_ERROR("Invalid params\n");
>> +		return;
>> +	}
>> +
>> +	bridge = to_dp_mst_bridge(drm_bridge);
>> +	if (!bridge->connector) {
>> +		DRM_ERROR("Invalid connector\n");
>> +		return;
>> +	}
>> +
>> +	dp_display = bridge->display;
>> +	mst = dp_display->msm_dp_mst;
>> +
>> +	mutex_lock(&mst->mst_lock);
>> +
>> +	msm_dp_mst_bridge_pre_disable_part1(bridge, state);
>> +
>> +	msm_dp_display_disable_helper(dp_display, bridge->msm_dp_panel);
>> +
>> +	drm_dp_check_act_status(&mst->mst_mgr);
>> +
>> +	mutex_unlock(&mst->mst_lock);
>> +
>> +	drm_dbg_dp(dp_display->drm_dev, "MST bridge:%d disable complete\n", bridge->id);
>> +}
>> +
>> +static void msm_dp_mst_bridge_atomic_post_disable(struct drm_bridge *drm_bridge,
>> +						  struct drm_atomic_state *state)
>> +{
>> +	struct msm_dp_mst_bridge *bridge;
>> +	struct msm_dp *dp_display;
>> +	struct msm_dp_mst *mst;
>> +
>> +	if (!drm_bridge) {
>> +		DRM_ERROR("Invalid params\n");
>> +		return;
>> +	}
>> +
>> +	bridge = to_dp_mst_bridge(drm_bridge);
>> +	if (!bridge->connector) {
>> +		DRM_ERROR("Invalid connector\n");
>> +		return;
>> +	}
>> +
>> +	dp_display = bridge->display;
>> +	mst = dp_display->msm_dp_mst;
>> +
>> +	mutex_lock(&mst->mst_lock);
>> +
>> +	msm_dp_display_atomic_post_disable_helper(dp_display, bridge->msm_dp_panel);
>> +
>> +	if (!dp_display->mst_active)
>> +		msm_dp_display_unprepare(dp_display);
>> +
>> +	bridge->connector = NULL;
>> +	bridge->msm_dp_panel =  NULL;
>> +
>> +	mutex_unlock(&mst->mst_lock);
>> +
>> +	drm_dbg_dp(dp_display->drm_dev, "MST bridge:%d conn:%d post disable complete\n",
>> +		   bridge->id, DP_MST_CONN_ID(bridge));
>> +}
>> +
>> +static int msm_dp_mst_bridge_atomic_check(struct drm_bridge *drm_bridge,
>> +					  struct drm_bridge_state *bridge_state,
>> +					  struct drm_crtc_state *crtc_state,
>> +					  struct drm_connector_state *conn_state)
>> +{
>> +	struct drm_atomic_state *state = crtc_state->state;
>> +	struct drm_connector *connector = conn_state->connector;
>> +	struct drm_dp_mst_topology_state *mst_state;
>> +	struct msm_dp_mst_connector *mst_conn;
>> +	struct msm_dp_mst *mst;
>> +	int rc = 0, pbn, slots;
>> +	struct msm_dp_mst_bridge_state *mst_bridge_state;
>> +	u32 bpp;
>> +
>> +	if (!drm_atomic_crtc_needs_modeset(crtc_state) || !crtc_state->enable)
>> +		return 0;
>> +
>> +	mst_conn = to_dp_mst_connector(connector);
>> +	mst = mst_conn->dp_mst;
>> +
>> +	bpp = connector->display_info.bpc * 3;
>> +
>> +	if (!bpp)
>> +		bpp = 24;
>> +
>> +	pbn = drm_dp_calc_pbn_mode(crtc_state->mode.clock, bpp << 4);
>> +
>> +	mst_state = to_drm_dp_mst_topology_state(mst->mst_mgr.base.state);
>> +	if (IS_ERR(mst_state))
>> +		return PTR_ERR(mst_state);
>> +
>> +	if (!dfixed_trunc(mst_state->pbn_div)) {
>> +		mst_state->pbn_div =
>> +			drm_dp_get_vc_payload_bw(mst_conn->dp_panel->link_info.rate,
>> +						 mst_conn->dp_panel->link_info.num_lanes);
>> +	}
>> +
>> +	slots = drm_dp_atomic_find_time_slots(state, &mst->mst_mgr, mst_conn->mst_port, pbn);
>> +
>> +	drm_dbg_dp(drm_bridge->dev, "add slots, conn:%d pbn:%d slots:%d rc:%d\n",
>> +		   connector->base.id, pbn, slots, rc);
>> +
>> +	if (!conn_state->crtc)  {
>> +		mst_bridge_state = msm_dp_mst_br_priv_state(state, to_dp_mst_bridge(drm_bridge));
>> +		mst_bridge_state->connector = NULL;
>> +		mst_bridge_state->msm_dp_panel = NULL;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +/* DP MST Bridge APIs */
>> +static const struct drm_bridge_funcs msm_dp_mst_bridge_ops = {
>> +	.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
>> +	.atomic_destroy_state   = drm_atomic_helper_bridge_destroy_state,
>> +	.atomic_reset           = drm_atomic_helper_bridge_reset,
>> +	.atomic_pre_enable   = msm_dp_mst_bridge_atomic_pre_enable,
>> +	.atomic_disable      = msm_dp_mst_bridge_atomic_disable,
>> +	.atomic_post_disable = msm_dp_mst_bridge_atomic_post_disable,
>> +	.atomic_check        = msm_dp_mst_bridge_atomic_check,
>> +};
>> +
>> +int msm_dp_mst_attach_encoder(struct msm_dp *dp_display, struct drm_encoder *encoder)
>> +{
>> +	int rc = 0;
>> +	struct msm_dp_mst_bridge *bridge = NULL;
>> +	struct msm_dp_mst_bridge_state *mst_bridge_state;
>> +	struct drm_device *dev;
>> +	struct msm_dp_mst *mst = dp_display->msm_dp_mst;
>> +	int i;
>> +
>> +	for (i = 0; i < mst->max_streams; i++) {
>> +		if (!mst->mst_bridge[i]->initialized) {
> 
> We _know_ that it is not initialized now. And anyway, it's an error if
> we call this twice.
> 
>> +			bridge = mst->mst_bridge[i];
>> +			bridge->encoder = encoder;
> 
> This looks wrong. It's already stored inside drm_bridge
> 
>> +			bridge->initialized = true;
>> +			bridge->id = i;
> 
> Definitely not an id.
> 
>> +			break;
>> +		}
>> +	}
>> +
>> +	if (i == mst->max_streams) {
>> +		DRM_ERROR("MST supports only %d bridges\n", mst->max_streams);
>> +		rc = -EACCES;
>> +		goto end;
>> +	}
>> +
>> +	dev = dp_display->drm_dev;
>> +	bridge->display = dp_display;
>> +	bridge->base.encoder = encoder;
> 
> why?
> 
>> +	bridge->base.type = dp_display->connector_type;
>> +	bridge->base.ops = DRM_BRIDGE_OP_MODES;
>> +	drm_bridge_add(&bridge->base);
>> +
>> +	rc = drm_bridge_attach(encoder, &bridge->base, NULL, 0);
>> +	if (rc) {
>> +		DRM_ERROR("failed to attach bridge, rc=%d\n", rc);
>> +		goto end;
>> +	}
>> +
>> +	mst_bridge_state = kzalloc(sizeof(*mst_bridge_state), GFP_KERNEL);
>> +	if (!mst_bridge_state) {
>> +		rc = -ENOMEM;
>> +		goto end;
>> +	}
>> +
>> +	drm_atomic_private_obj_init(dev, &bridge->obj,
>> +				    &mst_bridge_state->base,
>> +				    &msm_dp_mst_bridge_state_funcs);
> 
> It's already done for you in the drm_bridge_attach().
> 
>> +
>> +	drm_dbg_dp(dp_display->drm_dev, "MST drm bridge init. bridge id:%d\n", i);
>> +
>> +	return 0;
>> +
>> +end:
>> +	return rc;
>> +}
>> +
>>   int msm_dp_mst_display_set_mgr_state(struct msm_dp *dp_display, bool state)
>>   {
>>   	struct msm_dp_mst *mst = dp_display->msm_dp_mst;
>> @@ -49,11 +501,16 @@ int msm_dp_mst_init(struct msm_dp *dp_display, u32 max_streams, struct drm_dp_au
>>   		return -ENOMEM;
>>   
>>   	memset(&msm_dp_mst->mst_mgr, 0, sizeof(msm_dp_mst->mst_mgr));
>> -
>>   	conn_base_id = dp_display->connector->base.id;
>>   	msm_dp_mst->msm_dp = dp_display;
>>   	msm_dp_mst->max_streams = max_streams;
>>   
>> +	for (int i = 0; i < DP_STREAM_MAX; i++) {
>> +		msm_dp_mst->mst_bridge[i] =
>> +			devm_drm_bridge_alloc(dev->dev, struct msm_dp_mst_bridge, base,
>> +					      &msm_dp_mst_bridge_ops);
>> +	}
>> +
>>   	msm_dp_mst->dp_aux = drm_aux;
>>   
>>   	ret = drm_dp_mst_topology_mgr_init(&msm_dp_mst->mst_mgr, dev,
>> diff --git a/drivers/gpu/drm/msm/msm_drv.h b/drivers/gpu/drm/msm/msm_drv.h
>> index 5f73e0aa1c2f..03bedd15fe02 100644
>> --- a/drivers/gpu/drm/msm/msm_drv.h
>> +++ b/drivers/gpu/drm/msm/msm_drv.h
>> @@ -364,6 +364,8 @@ bool msm_dp_needs_periph_flush(const struct msm_dp *dp_display,
>>   bool msm_dp_wide_bus_available(const struct msm_dp *dp_display);
>>   int msm_dp_get_mst_max_stream(struct msm_dp *dp_display);
>>   int msm_dp_mst_register(struct msm_dp *dp_display);
>> +int msm_dp_mst_attach_encoder(struct msm_dp *dp_display, struct drm_encoder *encoder);
>> +
>>   #else
>>   static inline int __init msm_dp_register(void)
>>   {
>> @@ -390,6 +392,11 @@ static inline int msm_dp_mst_register(struct msm_dp *dp_display)
>>   	return -EINVAL;
>>   }
>>   
>> +static inline int msm_dp_mst_attach_encoder(struct msm_dp *dp_display, struct drm_encoder *encoder)
>> +{
>> +	return -EINVAL;
>> +}
>> +
>>   static inline void msm_dp_snapshot(struct msm_disp_state *disp_state, struct msm_dp *dp_display)
>>   {
>>   }
>>
>> -- 
>> 2.43.0
>>
> 


^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 01/39] drm/msm/dp: remove cached drm_edid from panel
  2026-04-10 13:52   ` Dmitry Baryshkov
@ 2026-05-19  7:27     ` Yongxing Mou
  2026-05-20  9:22       ` Dmitry Baryshkov
  0 siblings, 1 reply; 128+ messages in thread
From: Yongxing Mou @ 2026-05-19  7:27 UTC (permalink / raw)
  To: Dmitry Baryshkov
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel



On 4/10/2026 9:52 PM, Dmitry Baryshkov wrote:
> On Fri, Apr 10, 2026 at 05:33:36PM +0800, Yongxing Mou wrote:
>> The cached drm_edid seems unnecessary here. Use the drm_edid pointer
>> directly in the plug stage instead of caching it. Remove the cached
>> drm_edid and the corresponding oneliner to simplify the code.
>>
>> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
>> ---
>>   drivers/gpu/drm/msm/dp/dp_display.c | 27 +++++++++++-------
>>   drivers/gpu/drm/msm/dp/dp_panel.c   | 57 ++++---------------------------------
>>   drivers/gpu/drm/msm/dp/dp_panel.h   | 13 +++------
>>   3 files changed, 26 insertions(+), 71 deletions(-)
>>
>> diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
>> index 5c6a24ec140d..e28cc1bbb5b1 100644
>> --- a/drivers/gpu/drm/msm/dp/dp_display.c
>> +++ b/drivers/gpu/drm/msm/dp/dp_display.c
>> @@ -268,6 +268,7 @@ static int msm_dp_display_process_hpd_high(struct msm_dp_display_private *dp)
>>   	const struct drm_display_info *info = &connector->display_info;
>>   	int rc = 0;
>>   	u8 dpcd[DP_RECEIVER_CAP_SIZE];
>> +	const struct drm_edid *drm_edid;
>>   
>>   	rc = drm_dp_read_dpcd_caps(dp->aux, dpcd);
>>   	if (rc)
>> @@ -275,10 +276,20 @@ static int msm_dp_display_process_hpd_high(struct msm_dp_display_private *dp)
>>   
>>   	dp->link->lttpr_count = msm_dp_display_lttpr_init(dp, dpcd);
>>   
>> -	rc = msm_dp_panel_read_sink_caps(dp->panel, connector);
>> +	rc = msm_dp_panel_read_link_caps(dp->panel, connector);
>>   	if (rc)
>>   		goto end;
>>   
>> +	drm_edid = drm_edid_read_ddc(connector, &dp->aux->ddc);
> 
> Where is it free?
> 
Ohh, i miss it, will update next version
>> +	drm_edid_connector_update(connector, drm_edid);
>> +
>> +	if (!drm_edid) {
>> +		DRM_ERROR("panel edid read failed\n");
>> +		/* check edid read fail is due to unplug */
>> +		if (!msm_dp_aux_is_link_connected(dp->aux))
> 
> Does this work for USB-C AltMode usecase?
> 
For USB-C DP, i think it always TRUE, here should already existed before 
this patch..
>> +			return -ETIMEDOUT;
>> +	}
>> +
>>   	msm_dp_link_process_request(dp->link);
>>   
>>   	if (!dp->msm_dp_display.is_edp)
> 


^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 05/39] drm/msm/dp: splite msm_dp_ctrl_config_ctrl() into link parts and stream parts
  2026-04-11 17:34   ` Dmitry Baryshkov
@ 2026-05-19  7:46     ` Yongxing Mou
  2026-05-20  9:23       ` Dmitry Baryshkov
  0 siblings, 1 reply; 128+ messages in thread
From: Yongxing Mou @ 2026-05-19  7:46 UTC (permalink / raw)
  To: Dmitry Baryshkov
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel



On 4/12/2026 1:34 AM, Dmitry Baryshkov wrote:
> On Fri, Apr 10, 2026 at 05:33:40PM +0800, Yongxing Mou wrote:
>> The DP_CONFIGURATION_CTRL register contains both link-level and
>> stream-specific fields. Currently, msm_dp_ctrl_config_ctrl() configures
>> all of them together. Separates the configuration into link parts and
>> streams part for support MST.
>>
>> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
>> ---
>>   drivers/gpu/drm/msm/dp/dp_ctrl.c | 43 ++++++++++++++++++++++++++--------------
>>   1 file changed, 28 insertions(+), 15 deletions(-)
>>
>> diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c
>> index 476346e3ac19..85315467b5d0 100644
>> --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c
>> +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c
>> @@ -388,26 +388,41 @@ void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl)
>>   	drm_dbg_dp(ctrl->drm_dev, "mainlink off\n");
>>   }
>>   
>> -static void msm_dp_ctrl_config_ctrl(struct msm_dp_ctrl_private *ctrl)
>> +static void msm_dp_ctrl_config_ctrl_streams(struct msm_dp_ctrl_private *ctrl,
>> +					    struct msm_dp_panel *msm_dp_panel)
>>   {
>>   	u32 config = 0, tbd;
>> +
>> +	config = msm_dp_read_link(ctrl, REG_DP_CONFIGURATION_CTRL);
>> +
>> +	if (msm_dp_panel->msm_dp_mode.out_fmt_is_yuv_420)
>> +		config |= DP_CONFIGURATION_CTRL_RGB_YUV; /* YUV420 */
>> +
>> +	tbd = msm_dp_link_get_test_bits_depth(ctrl->link,
>> +					      msm_dp_panel->msm_dp_mode.bpp);
>> +
>> +	config |= tbd << DP_CONFIGURATION_CTRL_BPC_SHIFT;
>> +
>> +	if (msm_dp_panel->psr_cap.version)
>> +		config |= DP_CONFIGURATION_CTRL_SEND_VSC;
>> +
>> +	drm_dbg_dp(ctrl->drm_dev, "stream DP_CONFIGURATION_CTRL=0x%x\n", config);
>> +
>> +	msm_dp_write_link(ctrl, REG_DP_CONFIGURATION_CTRL, config);
> 
> You have an RMW cycle here. Please document what prevents it from racing
> with the concurrent msm_dp_ctrl_config_ctrl_link().
> 
Here protected by mst_lock in MST case. Will add a comment.
>> +}
>> +
>> +static void msm_dp_ctrl_config_ctrl_link(struct msm_dp_ctrl_private *ctrl)
>> +{
>> +	u32 config = 0;
>>   	const u8 *dpcd = ctrl->panel->dpcd;
>>   
>>   	/* Default-> LSCLK DIV: 1/4 LCLK  */
>>   	config |= (2 << DP_CONFIGURATION_CTRL_LSCLK_DIV_SHIFT);
>>   
>> -	if (ctrl->panel->msm_dp_mode.out_fmt_is_yuv_420)
>> -		config |= DP_CONFIGURATION_CTRL_RGB_YUV; /* YUV420 */
>> -
>>   	/* Scrambler reset enable */
>>   	if (drm_dp_alternate_scrambler_reset_cap(dpcd))
>>   		config |= DP_CONFIGURATION_CTRL_ASSR;
>>   
>> -	tbd = msm_dp_link_get_test_bits_depth(ctrl->link,
>> -			ctrl->panel->msm_dp_mode.bpp);
>> -
>> -	config |= tbd << DP_CONFIGURATION_CTRL_BPC_SHIFT;
>> -
>>   	/* Num of Lanes */
>>   	config |= ((ctrl->link->link_params.num_lanes - 1)
>>   			<< DP_CONFIGURATION_CTRL_NUM_OF_LANES_SHIFT);
>> @@ -421,10 +436,7 @@ static void msm_dp_ctrl_config_ctrl(struct msm_dp_ctrl_private *ctrl)
>>   	config |= DP_CONFIGURATION_CTRL_STATIC_DYNAMIC_CN;
>>   	config |= DP_CONFIGURATION_CTRL_SYNC_ASYNC_CLK;
>>   
>> -	if (ctrl->panel->psr_cap.version)
>> -		config |= DP_CONFIGURATION_CTRL_SEND_VSC;
>> -
>> -	drm_dbg_dp(ctrl->drm_dev, "DP_CONFIGURATION_CTRL=0x%x\n", config);
>> +	drm_dbg_dp(ctrl->drm_dev, "link DP_CONFIGURATION_CTRL=0x%x\n", config);
>>   
>>   	msm_dp_write_link(ctrl, REG_DP_CONFIGURATION_CTRL, config);
>>   }
>> @@ -450,7 +462,8 @@ static void msm_dp_ctrl_configure_source_params(struct msm_dp_ctrl_private *ctrl
>>   	msm_dp_ctrl_lane_mapping(ctrl);
>>   	msm_dp_setup_peripheral_flush(ctrl);
>>   
>> -	msm_dp_ctrl_config_ctrl(ctrl);
>> +	msm_dp_ctrl_config_ctrl_link(ctrl);
>> +	msm_dp_ctrl_config_ctrl_streams(ctrl, ctrl->panel);
>>   
>>   	test_bits_depth = msm_dp_link_get_test_bits_depth(ctrl->link, ctrl->panel->msm_dp_mode.bpp);
>>   	colorimetry_cfg = msm_dp_link_get_colorimetry_config(ctrl->link);
>> @@ -1628,7 +1641,7 @@ static int msm_dp_ctrl_link_train(struct msm_dp_ctrl_private *ctrl,
>>   	u8 assr;
>>   	struct msm_dp_link_info link_info = {0};
>>   
>> -	msm_dp_ctrl_config_ctrl(ctrl);
>> +	msm_dp_ctrl_config_ctrl_link(ctrl);
>>   
>>   	link_info.num_lanes = ctrl->link->link_params.num_lanes;
>>   	link_info.rate = ctrl->link->link_params.rate;
>>
>> -- 
>> 2.43.0
>>
> 


^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 08/39] drm/msm/dp: allow dp_ctrl stream APIs to use any panel passed to it
  2026-04-11 17:38   ` Dmitry Baryshkov
@ 2026-05-19  7:52     ` Yongxing Mou
  2026-05-20  9:24       ` Dmitry Baryshkov
  0 siblings, 1 reply; 128+ messages in thread
From: Yongxing Mou @ 2026-05-19  7:52 UTC (permalink / raw)
  To: Dmitry Baryshkov
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar



On 4/12/2026 1:38 AM, Dmitry Baryshkov wrote:
> On Fri, Apr 10, 2026 at 05:33:43PM +0800, Yongxing Mou wrote:
>> From: Abhinav Kumar <quic_abhinavk@quicinc.com>
>>
>> Currently, the dp_ctrl stream APIs operate on their own dp_panel
>> which is stored inside the dp_ctrl's private struct. However with MST,
>> the stored panel represents the fixed link and not the sinks which
>> are hotplugged. Allow the stream related APIs to work on the panel
>> which is passed to them rather than the stored one. For SST cases,
>> this shall continue to use the stored dp_panel.
> 
> Hmm. Why? Can't we get rid of it (in the followup patch)?
> 
In MST, the stream panel is bound to the connector, but in SST the panel 
is still stored in msm_dp_display_private and msm_dp_ctrl_private, so
   dropping it does not seem necessary at this point. The purpose of 
these changes is to allow the functions to operate on the MST panel data 
passed in per stream.
>>
>> Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
>> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
>> ---
>>   drivers/gpu/drm/msm/dp/dp_ctrl.c    | 21 +++++++++++----------
>>   drivers/gpu/drm/msm/dp/dp_ctrl.h    |  2 +-
>>   drivers/gpu/drm/msm/dp/dp_display.c |  2 +-
>>   3 files changed, 13 insertions(+), 12 deletions(-)
>>
>> diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c
>> index cd58968d4e14..d0eed8c7df45 100644
>> --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c
>> +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c
>> @@ -477,13 +477,14 @@ static void msm_dp_ctrl_config_misc1_misc0(struct msm_dp_ctrl_private *ctrl,
>>   	msm_dp_write_link(ctrl, REG_DP_MISC1_MISC0, misc_val);
>>   }
>>   
>> -static void msm_dp_ctrl_configure_source_params(struct msm_dp_ctrl_private *ctrl)
>> +static void msm_dp_ctrl_configure_source_params(struct msm_dp_ctrl_private *ctrl,
>> +						struct msm_dp_panel *msm_dp_panel)
>>   {
>> -	msm_dp_ctrl_config_ctrl_streams(ctrl, ctrl->panel);
>> +	msm_dp_ctrl_config_ctrl_streams(ctrl, msm_dp_panel);
>>   
>> -	msm_dp_ctrl_config_misc1_misc0(ctrl, ctrl->panel);
>> +	msm_dp_ctrl_config_misc1_misc0(ctrl, msm_dp_panel);
>>   
>> -	msm_dp_panel_timing_cfg(ctrl->panel, ctrl->msm_dp_ctrl.wide_bus_en);
>> +	msm_dp_panel_timing_cfg(msm_dp_panel, ctrl->msm_dp_ctrl.wide_bus_en);
>>   }
>>   
>>   /*
>> @@ -2523,7 +2524,7 @@ int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, bool force_li
>>   	return ret;
>>   }
>>   
>> -int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl)
>> +int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *msm_dp_panel)
>>   {
>>   	int ret = 0;
>>   	bool mainlink_ready = false;
>> @@ -2536,10 +2537,10 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl)
>>   
>>   	ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl);
>>   
>> -	pixel_rate_orig = ctrl->panel->msm_dp_mode.drm_mode.clock;
>> +	pixel_rate_orig = msm_dp_panel->msm_dp_mode.drm_mode.clock;
>>   	pixel_rate = pixel_rate_orig;
>>   
>> -	if (msm_dp_ctrl->wide_bus_en || ctrl->panel->msm_dp_mode.out_fmt_is_yuv_420)
>> +	if (msm_dp_ctrl->wide_bus_en || msm_dp_panel->msm_dp_mode.out_fmt_is_yuv_420)
>>   		pixel_rate >>= 1;
>>   
>>   	drm_dbg_dp(ctrl->drm_dev, "pixel_rate=%lu\n", pixel_rate);
>> @@ -2571,14 +2572,14 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl)
>>   	msm_dp_setup_peripheral_flush(ctrl);
>>   	msm_dp_ctrl_config_ctrl_link(ctrl);
>>   
>> -	msm_dp_ctrl_configure_source_params(ctrl);
>> +	msm_dp_ctrl_configure_source_params(ctrl, msm_dp_panel);
>>   
>>   	msm_dp_ctrl_config_msa(ctrl,
>>   		ctrl->link->link_params.rate,
>>   		pixel_rate_orig,
>> -		ctrl->panel->msm_dp_mode.out_fmt_is_yuv_420);
>> +		msm_dp_panel->msm_dp_mode.out_fmt_is_yuv_420);
>>   
>> -	msm_dp_panel_clear_dsc_dto(ctrl->panel);
>> +	msm_dp_panel_clear_dsc_dto(msm_dp_panel);
>>   
>>   	msm_dp_ctrl_setup_tr_unit(ctrl);
>>   
>> diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.h b/drivers/gpu/drm/msm/dp/dp_ctrl.h
>> index 5d615f50d13b..32196e97cbe9 100644
>> --- a/drivers/gpu/drm/msm/dp/dp_ctrl.h
>> +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.h
>> @@ -17,7 +17,7 @@ struct msm_dp_ctrl {
>>   struct phy;
>>   
>>   int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl);
>> -int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl);
>> +int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *msm_dp_panel);
>>   int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train);
>>   void msm_dp_ctrl_off(struct msm_dp_ctrl *msm_dp_ctrl);
>>   void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl);
>> diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
>> index 58c46d5ab4d8..1bf1335712bc 100644
>> --- a/drivers/gpu/drm/msm/dp/dp_display.c
>> +++ b/drivers/gpu/drm/msm/dp/dp_display.c
>> @@ -666,7 +666,7 @@ static int msm_dp_display_enable(struct msm_dp_display_private *dp)
>>   		return 0;
>>   	}
>>   
>> -	rc = msm_dp_ctrl_on_stream(dp->ctrl);
>> +	rc = msm_dp_ctrl_on_stream(dp->ctrl, dp->panel);
>>   	if (!rc)
>>   		msm_dp_display->power_on = true;
>>   
>>
>> -- 
>> 2.43.0
>>
> 


^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 13/39] drm/msm/dp: introduce stream_id for each DP panel
  2026-04-11 17:55   ` Dmitry Baryshkov
@ 2026-05-19  8:26     ` Yongxing Mou
  2026-05-20  9:27       ` Dmitry Baryshkov
  0 siblings, 1 reply; 128+ messages in thread
From: Yongxing Mou @ 2026-05-19  8:26 UTC (permalink / raw)
  To: Dmitry Baryshkov
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar



On 4/12/2026 1:55 AM, Dmitry Baryshkov wrote:
> On Fri, Apr 10, 2026 at 05:33:48PM +0800, Yongxing Mou wrote:
>> From: Abhinav Kumar <quic_abhinavk@quicinc.com>
>>
>> With MST, each DP controller can handle multiple streams.
>> There shall be one dp_panel for each stream but the dp_display
>> object shall be shared among them. To represent this abstraction,
>> create a stream_id for each DP panel which shall be set by the
>> MST stream. For SST, default this to stream 0.
>>
>> Use the stream ID to control the pixel clock of that respective
>> stream by extending the clock handles and state tracking of the
>> DP pixel clock to an array of max supported streams. The maximum
>> streams currently is 4.
> 
> Please mention that panels are going to be dynamically assigned to
> actual stream IDs.
> 
Got it. will update next patch.
>>
>> Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
>> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
>> ---
>>   drivers/gpu/drm/msm/dp/dp_ctrl.c    | 57 +++++++++++++++++++++++--------------
>>   drivers/gpu/drm/msm/dp/dp_ctrl.h    |  2 +-
>>   drivers/gpu/drm/msm/dp/dp_display.c | 24 ++++++++++++++--
>>   drivers/gpu/drm/msm/dp/dp_display.h |  2 ++
>>   drivers/gpu/drm/msm/dp/dp_panel.h   | 11 +++++++
>>   5 files changed, 71 insertions(+), 25 deletions(-)
>>
>> diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c
>> index 120ec00884e5..fb6396727628 100644
>> --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c
>> +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c
>> @@ -127,7 +127,7 @@ struct msm_dp_ctrl_private {
>>   	unsigned int num_link_clks;
>>   	struct clk_bulk_data *link_clks;
>>   
>> -	struct clk *pixel_clk;
>> +	struct clk *pixel_clk[DP_STREAM_MAX];
>>   
>>   	union phy_configure_opts phy_opts;
>>   
>> @@ -139,7 +139,7 @@ struct msm_dp_ctrl_private {
>>   
>>   	bool core_clks_on;
>>   	bool link_clks_on;
>> -	bool stream_clks_on;
>> +	bool stream_clks_on[DP_STREAM_MAX];
>>   };
>>   
>>   static inline u32 msm_dp_read_ahb(const struct msm_dp_ctrl_private *ctrl, u32 offset)
>> @@ -2176,39 +2176,40 @@ static bool msm_dp_ctrl_send_phy_test_pattern(struct msm_dp_ctrl_private *ctrl)
>>   	return success;
>>   }
>>   
>> -static int msm_dp_ctrl_on_pixel_clk(struct msm_dp_ctrl_private *ctrl, unsigned long pixel_rate)
>> +static int msm_dp_ctrl_on_pixel_clk(struct msm_dp_ctrl_private *ctrl, unsigned long pixel_rate,
>> +				    enum msm_dp_stream_id stream_id)
>>   {
>>   	int ret;
>>   
>> -	ret = clk_set_rate(ctrl->pixel_clk, pixel_rate * 1000);
>> +	ret = clk_set_rate(ctrl->pixel_clk[stream_id], pixel_rate * 1000);
>>   	if (ret) {
>>   		DRM_ERROR("Failed to set pixel clock rate. ret=%d\n", ret);
>>   		return ret;
>>   	}
>>   
>> -	if (ctrl->stream_clks_on) {
>> +	if (ctrl->stream_clks_on[stream_id]) {
>>   		drm_dbg_dp(ctrl->drm_dev, "pixel clks already enabled\n");
>>   	} else {
>> -		ret = clk_prepare_enable(ctrl->pixel_clk);
>> +		ret = clk_prepare_enable(ctrl->pixel_clk[stream_id]);
>>   		if (ret) {
>>   			DRM_ERROR("Failed to start pixel clocks. ret=%d\n", ret);
>>   			return ret;
>>   		}
>> -		ctrl->stream_clks_on = true;
>> +		ctrl->stream_clks_on[stream_id] = true;
>>   	}
>>   
>>   	return ret;
>>   }
>>   
>> -void msm_dp_ctrl_off_pixel_clk(struct msm_dp_ctrl *msm_dp_ctrl)
>> +void msm_dp_ctrl_off_pixel_clk(struct msm_dp_ctrl *msm_dp_ctrl, enum msm_dp_stream_id stream_id)
>>   {
>>   	struct msm_dp_ctrl_private *ctrl;
>>   
>>   	ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl);
>>   
>> -	if (ctrl->stream_clks_on) {
>> -		clk_disable_unprepare(ctrl->pixel_clk);
>> -		ctrl->stream_clks_on = false;
>> +	if (ctrl->stream_clks_on[stream_id]) {
>> +		clk_disable_unprepare(ctrl->pixel_clk[stream_id]);
>> +		ctrl->stream_clks_on[stream_id] = false;
>>   	}
>>   }
>>   
>> @@ -2228,7 +2229,7 @@ static int msm_dp_ctrl_process_phy_test_request(struct msm_dp_ctrl_private *ctrl
>>   	 * running. Add the global reset just before disabling the
>>   	 * link clocks and core clocks.
>>   	 */
>> -	msm_dp_ctrl_off_pixel_clk(&ctrl->msm_dp_ctrl);
>> +	msm_dp_ctrl_off_pixel_clk(&ctrl->msm_dp_ctrl, ctrl->panel->stream_id);
> 
> Why are we using ctrl->panel again here for the stream-related
> functions? Didn't you got rid of its usage few patches ago?
> 
For MST path, we pass panel to the func. For SST case, we still need to 
pass the sst panel to func, in here it is ctrl->panel.. in dp_display, 
it is dp_display->panel.
>>   	msm_dp_ctrl_off_link(&ctrl->msm_dp_ctrl);
>>   
>>   	ret = msm_dp_ctrl_on_link(&ctrl->msm_dp_ctrl);
>> @@ -2238,7 +2239,7 @@ static int msm_dp_ctrl_process_phy_test_request(struct msm_dp_ctrl_private *ctrl
>>   	}
>>   
>>   	pixel_rate = ctrl->panel->msm_dp_mode.drm_mode.clock;
>> -	ret = msm_dp_ctrl_on_pixel_clk(ctrl, pixel_rate);
>> +	ret = msm_dp_ctrl_on_pixel_clk(ctrl, pixel_rate, ctrl->panel->stream_id);
> 
> And here...
> 
>>   
>>   	msm_dp_ctrl_send_phy_test_pattern(ctrl);
>>   
>> @@ -1451,6 +1469,8 @@ void msm_dp_display_atomic_enable(struct msm_dp *msm_dp_display)
>>   
>>   	dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display);
>>   
>> +	msm_dp_display_set_stream_info(msm_dp_display, dp->panel, 0);
> 
> DP_STREAM_0
> 
>> +
>>   	rc = msm_dp_display_enable(dp);
>>   	if (rc)
>>   		DRM_ERROR("DP display enable failed, rc=%d\n", rc);
> 


^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 13/39] drm/msm/dp: introduce stream_id for each DP panel
  2026-04-11 18:04   ` Dmitry Baryshkov
@ 2026-05-20  8:54     ` Yongxing Mou
  0 siblings, 0 replies; 128+ messages in thread
From: Yongxing Mou @ 2026-05-20  8:54 UTC (permalink / raw)
  To: Dmitry Baryshkov
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar



On 4/12/2026 2:04 AM, Dmitry Baryshkov wrote:
> On Fri, Apr 10, 2026 at 05:33:48PM +0800, Yongxing Mou wrote:
>> From: Abhinav Kumar <quic_abhinavk@quicinc.com>
>>
>> With MST, each DP controller can handle multiple streams.
>> There shall be one dp_panel for each stream but the dp_display
>> object shall be shared among them. To represent this abstraction,
>> create a stream_id for each DP panel which shall be set by the
>> MST stream. For SST, default this to stream 0.
>>
>> Use the stream ID to control the pixel clock of that respective
>> stream by extending the clock handles and state tracking of the
>> DP pixel clock to an array of max supported streams. The maximum
>> streams currently is 4.
>>
>> Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
>> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
>> ---
>>   drivers/gpu/drm/msm/dp/dp_ctrl.c    | 57 +++++++++++++++++++++++--------------
>>   drivers/gpu/drm/msm/dp/dp_ctrl.h    |  2 +-
>>   drivers/gpu/drm/msm/dp/dp_display.c | 24 ++++++++++++++--
>>   drivers/gpu/drm/msm/dp/dp_display.h |  2 ++
>>   drivers/gpu/drm/msm/dp/dp_panel.h   | 11 +++++++
>>   5 files changed, 71 insertions(+), 25 deletions(-)
>> @@ -2735,9 +2740,17 @@ static int msm_dp_ctrl_clk_init(struct msm_dp_ctrl *msm_dp_ctrl)
>>   	if (rc)
>>   		return rc;
>>   
>> -	ctrl->pixel_clk = devm_clk_get(dev, "stream_pixel");
>> -	if (IS_ERR(ctrl->pixel_clk))
>> -		return PTR_ERR(ctrl->pixel_clk);
>> +	for (i = DP_STREAM_0; i < DP_STREAM_MAX; i++) {
>> +		ctrl->pixel_clk[i] = devm_clk_get(dev, pixel_clks[i]);
>> +
>> +		if (i == 0 && IS_ERR(ctrl->pixel_clk[i]))
>> +			return PTR_ERR(ctrl->pixel_clk[i]);
>> +
>> +		if (IS_ERR(ctrl->pixel_clk[i])) {
>> +			DRM_DEBUG_DP("stream %d pixel clock not exist", i);
>> +			break;
>> +		}
> 
> Almost missed it. If it is -EPROBE_DEFER, we must return an error. In
> fact, I'd rather check for -ENOENT (or is it -ENODEV?) and pass all
> other errors to the caller.
> 
Thanks, will fix next version. i add logs change check this, if no this 
stream clk in dts, will return  -ENOENT..
>> +	}
>>   
>>   	return 0;
>>   }
> 


^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 13/39] drm/msm/dp: introduce stream_id for each DP panel
  2026-04-12  0:04   ` Dmitry Baryshkov
@ 2026-05-20  8:54     ` Yongxing Mou
  0 siblings, 0 replies; 128+ messages in thread
From: Yongxing Mou @ 2026-05-20  8:54 UTC (permalink / raw)
  To: Dmitry Baryshkov
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar



On 4/12/2026 8:04 AM, Dmitry Baryshkov wrote:
> On Fri, Apr 10, 2026 at 05:33:48PM +0800, Yongxing Mou wrote:
>> From: Abhinav Kumar <quic_abhinavk@quicinc.com>
>>
>> With MST, each DP controller can handle multiple streams.
>> There shall be one dp_panel for each stream but the dp_display
>> object shall be shared among them. To represent this abstraction,
>> create a stream_id for each DP panel which shall be set by the
>> MST stream. For SST, default this to stream 0.
>>
>> Use the stream ID to control the pixel clock of that respective
>> stream by extending the clock handles and state tracking of the
>> DP pixel clock to an array of max supported streams. The maximum
>> streams currently is 4.
>>
>> Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
>> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
>> ---
>>   drivers/gpu/drm/msm/dp/dp_ctrl.c    | 57 +++++++++++++++++++++++--------------
>>   drivers/gpu/drm/msm/dp/dp_ctrl.h    |  2 +-
>>   drivers/gpu/drm/msm/dp/dp_display.c | 24 ++++++++++++++--
>>   drivers/gpu/drm/msm/dp/dp_display.h |  2 ++
>>   drivers/gpu/drm/msm/dp/dp_panel.h   | 11 +++++++
>>   5 files changed, 71 insertions(+), 25 deletions(-)
>>
>> diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c
>> index 120ec00884e5..fb6396727628 100644
>> --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c
>> +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c
>> @@ -127,7 +127,7 @@ struct msm_dp_ctrl_private {
>>   	unsigned int num_link_clks;
>>   	struct clk_bulk_data *link_clks;
>>   
>> -	struct clk *pixel_clk;
>> +	struct clk *pixel_clk[DP_STREAM_MAX];
>>   
>>   	union phy_configure_opts phy_opts;
>>   
>> @@ -139,7 +139,7 @@ struct msm_dp_ctrl_private {
>>   
>>   	bool core_clks_on;
>>   	bool link_clks_on;
>> -	bool stream_clks_on;
>> +	bool stream_clks_on[DP_STREAM_MAX];
> 
> drivers/gpu/drm/msm/dp/dp_ctrl.c: In function ‘msm_dp_ctrl_core_clk_enable’:
> drivers/gpu/drm/msm/dp/dp_ctrl.c:1937:9: warning: the comparison will always evaluate as ‘true’ for the address of ‘stream_clks_on’ will never be NULL [-Waddress]
>   1937 |         drm_dbg_dp(ctrl->drm_dev, "stream_clks:%s link_clks:%s core_clks:%s\n",
>        |         ^~~~~~~~~~
> 
> 
Emm. thanks will fix this next vesion.


^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 01/39] drm/msm/dp: remove cached drm_edid from panel
  2026-05-19  7:27     ` Yongxing Mou
@ 2026-05-20  9:22       ` Dmitry Baryshkov
  2026-05-20 10:09         ` Yongxing Mou
  0 siblings, 1 reply; 128+ messages in thread
From: Dmitry Baryshkov @ 2026-05-20  9:22 UTC (permalink / raw)
  To: Yongxing Mou
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel

On Tue, May 19, 2026 at 03:27:00PM +0800, Yongxing Mou wrote:
> 
> 
> On 4/10/2026 9:52 PM, Dmitry Baryshkov wrote:
> > On Fri, Apr 10, 2026 at 05:33:36PM +0800, Yongxing Mou wrote:
> > > The cached drm_edid seems unnecessary here. Use the drm_edid pointer
> > > directly in the plug stage instead of caching it. Remove the cached
> > > drm_edid and the corresponding oneliner to simplify the code.
> > > 
> > > Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
> > > ---
> > >   drivers/gpu/drm/msm/dp/dp_display.c | 27 +++++++++++-------
> > >   drivers/gpu/drm/msm/dp/dp_panel.c   | 57 ++++---------------------------------
> > >   drivers/gpu/drm/msm/dp/dp_panel.h   | 13 +++------
> > >   3 files changed, 26 insertions(+), 71 deletions(-)
> > > 
> > > diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
> > > index 5c6a24ec140d..e28cc1bbb5b1 100644
> > > --- a/drivers/gpu/drm/msm/dp/dp_display.c
> > > +++ b/drivers/gpu/drm/msm/dp/dp_display.c
> > > @@ -268,6 +268,7 @@ static int msm_dp_display_process_hpd_high(struct msm_dp_display_private *dp)
> > >   	const struct drm_display_info *info = &connector->display_info;
> > >   	int rc = 0;
> > >   	u8 dpcd[DP_RECEIVER_CAP_SIZE];
> > > +	const struct drm_edid *drm_edid;
> > >   	rc = drm_dp_read_dpcd_caps(dp->aux, dpcd);
> > >   	if (rc)
> > > @@ -275,10 +276,20 @@ static int msm_dp_display_process_hpd_high(struct msm_dp_display_private *dp)
> > >   	dp->link->lttpr_count = msm_dp_display_lttpr_init(dp, dpcd);
> > > -	rc = msm_dp_panel_read_sink_caps(dp->panel, connector);
> > > +	rc = msm_dp_panel_read_link_caps(dp->panel, connector);
> > >   	if (rc)
> > >   		goto end;
> > > +	drm_edid = drm_edid_read_ddc(connector, &dp->aux->ddc);
> > 
> > Where is it free?
> > 
> Ohh, i miss it, will update next version
> > > +	drm_edid_connector_update(connector, drm_edid);
> > > +
> > > +	if (!drm_edid) {
> > > +		DRM_ERROR("panel edid read failed\n");
> > > +		/* check edid read fail is due to unplug */
> > > +		if (!msm_dp_aux_is_link_connected(dp->aux))
> > 
> > Does this work for USB-C AltMode usecase?
> > 
> For USB-C DP, i think it always TRUE, here should already existed before
> this patch..

I'd expect the answer without the 'i think' part.

> > > +			return -ETIMEDOUT;
> > > +	}
> > > +
> > >   	msm_dp_link_process_request(dp->link);
> > >   	if (!dp->msm_dp_display.is_edp)
> > 
> 

-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 05/39] drm/msm/dp: splite msm_dp_ctrl_config_ctrl() into link parts and stream parts
  2026-05-19  7:46     ` Yongxing Mou
@ 2026-05-20  9:23       ` Dmitry Baryshkov
  2026-05-20 10:16         ` Yongxing Mou
  0 siblings, 1 reply; 128+ messages in thread
From: Dmitry Baryshkov @ 2026-05-20  9:23 UTC (permalink / raw)
  To: Yongxing Mou
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel

On Tue, May 19, 2026 at 03:46:36PM +0800, Yongxing Mou wrote:
> 
> 
> On 4/12/2026 1:34 AM, Dmitry Baryshkov wrote:
> > On Fri, Apr 10, 2026 at 05:33:40PM +0800, Yongxing Mou wrote:
> > > The DP_CONFIGURATION_CTRL register contains both link-level and
> > > stream-specific fields. Currently, msm_dp_ctrl_config_ctrl() configures
> > > all of them together. Separates the configuration into link parts and
> > > streams part for support MST.
> > > 
> > > Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
> > > ---
> > >   drivers/gpu/drm/msm/dp/dp_ctrl.c | 43 ++++++++++++++++++++++++++--------------
> > >   1 file changed, 28 insertions(+), 15 deletions(-)
> > > 
> > > diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c
> > > index 476346e3ac19..85315467b5d0 100644
> > > --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c
> > > +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c
> > > @@ -388,26 +388,41 @@ void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl)
> > >   	drm_dbg_dp(ctrl->drm_dev, "mainlink off\n");
> > >   }
> > > -static void msm_dp_ctrl_config_ctrl(struct msm_dp_ctrl_private *ctrl)
> > > +static void msm_dp_ctrl_config_ctrl_streams(struct msm_dp_ctrl_private *ctrl,
> > > +					    struct msm_dp_panel *msm_dp_panel)
> > >   {
> > >   	u32 config = 0, tbd;
> > > +
> > > +	config = msm_dp_read_link(ctrl, REG_DP_CONFIGURATION_CTRL);
> > > +
> > > +	if (msm_dp_panel->msm_dp_mode.out_fmt_is_yuv_420)
> > > +		config |= DP_CONFIGURATION_CTRL_RGB_YUV; /* YUV420 */
> > > +
> > > +	tbd = msm_dp_link_get_test_bits_depth(ctrl->link,
> > > +					      msm_dp_panel->msm_dp_mode.bpp);
> > > +
> > > +	config |= tbd << DP_CONFIGURATION_CTRL_BPC_SHIFT;
> > > +
> > > +	if (msm_dp_panel->psr_cap.version)
> > > +		config |= DP_CONFIGURATION_CTRL_SEND_VSC;
> > > +
> > > +	drm_dbg_dp(ctrl->drm_dev, "stream DP_CONFIGURATION_CTRL=0x%x\n", config);
> > > +
> > > +	msm_dp_write_link(ctrl, REG_DP_CONFIGURATION_CTRL, config);
> > 
> > You have an RMW cycle here. Please document what prevents it from racing
> > with the concurrent msm_dp_ctrl_config_ctrl_link().
> > 
> Here protected by mst_lock in MST case. Will add a comment.

And in SST case?

> > > +}
> > > +
> > > +static void msm_dp_ctrl_config_ctrl_link(struct msm_dp_ctrl_private *ctrl)
> > > +{
> > > +	u32 config = 0;
> > >   	const u8 *dpcd = ctrl->panel->dpcd;
> > >   	/* Default-> LSCLK DIV: 1/4 LCLK  */
> > >   	config |= (2 << DP_CONFIGURATION_CTRL_LSCLK_DIV_SHIFT);
> > > -	if (ctrl->panel->msm_dp_mode.out_fmt_is_yuv_420)
> > > -		config |= DP_CONFIGURATION_CTRL_RGB_YUV; /* YUV420 */
> > > -
> > >   	/* Scrambler reset enable */
> > >   	if (drm_dp_alternate_scrambler_reset_cap(dpcd))
> > >   		config |= DP_CONFIGURATION_CTRL_ASSR;
> > > -	tbd = msm_dp_link_get_test_bits_depth(ctrl->link,
> > > -			ctrl->panel->msm_dp_mode.bpp);
> > > -
> > > -	config |= tbd << DP_CONFIGURATION_CTRL_BPC_SHIFT;
> > > -
> > >   	/* Num of Lanes */
> > >   	config |= ((ctrl->link->link_params.num_lanes - 1)
> > >   			<< DP_CONFIGURATION_CTRL_NUM_OF_LANES_SHIFT);
> > > @@ -421,10 +436,7 @@ static void msm_dp_ctrl_config_ctrl(struct msm_dp_ctrl_private *ctrl)
> > >   	config |= DP_CONFIGURATION_CTRL_STATIC_DYNAMIC_CN;
> > >   	config |= DP_CONFIGURATION_CTRL_SYNC_ASYNC_CLK;
> > > -	if (ctrl->panel->psr_cap.version)
> > > -		config |= DP_CONFIGURATION_CTRL_SEND_VSC;
> > > -
> > > -	drm_dbg_dp(ctrl->drm_dev, "DP_CONFIGURATION_CTRL=0x%x\n", config);
> > > +	drm_dbg_dp(ctrl->drm_dev, "link DP_CONFIGURATION_CTRL=0x%x\n", config);
> > >   	msm_dp_write_link(ctrl, REG_DP_CONFIGURATION_CTRL, config);
> > >   }
> > > @@ -450,7 +462,8 @@ static void msm_dp_ctrl_configure_source_params(struct msm_dp_ctrl_private *ctrl
> > >   	msm_dp_ctrl_lane_mapping(ctrl);
> > >   	msm_dp_setup_peripheral_flush(ctrl);
> > > -	msm_dp_ctrl_config_ctrl(ctrl);
> > > +	msm_dp_ctrl_config_ctrl_link(ctrl);
> > > +	msm_dp_ctrl_config_ctrl_streams(ctrl, ctrl->panel);
> > >   	test_bits_depth = msm_dp_link_get_test_bits_depth(ctrl->link, ctrl->panel->msm_dp_mode.bpp);
> > >   	colorimetry_cfg = msm_dp_link_get_colorimetry_config(ctrl->link);
> > > @@ -1628,7 +1641,7 @@ static int msm_dp_ctrl_link_train(struct msm_dp_ctrl_private *ctrl,
> > >   	u8 assr;
> > >   	struct msm_dp_link_info link_info = {0};
> > > -	msm_dp_ctrl_config_ctrl(ctrl);
> > > +	msm_dp_ctrl_config_ctrl_link(ctrl);
> > >   	link_info.num_lanes = ctrl->link->link_params.num_lanes;
> > >   	link_info.rate = ctrl->link->link_params.rate;
> > > 
> > > -- 
> > > 2.43.0
> > > 
> > 
> 

-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 08/39] drm/msm/dp: allow dp_ctrl stream APIs to use any panel passed to it
  2026-05-19  7:52     ` Yongxing Mou
@ 2026-05-20  9:24       ` Dmitry Baryshkov
  2026-05-20 10:31         ` Yongxing Mou
  0 siblings, 1 reply; 128+ messages in thread
From: Dmitry Baryshkov @ 2026-05-20  9:24 UTC (permalink / raw)
  To: Yongxing Mou
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar

On Tue, May 19, 2026 at 03:52:35PM +0800, Yongxing Mou wrote:
> 
> 
> On 4/12/2026 1:38 AM, Dmitry Baryshkov wrote:
> > On Fri, Apr 10, 2026 at 05:33:43PM +0800, Yongxing Mou wrote:
> > > From: Abhinav Kumar <quic_abhinavk@quicinc.com>
> > > 
> > > Currently, the dp_ctrl stream APIs operate on their own dp_panel
> > > which is stored inside the dp_ctrl's private struct. However with MST,
> > > the stored panel represents the fixed link and not the sinks which
> > > are hotplugged. Allow the stream related APIs to work on the panel
> > > which is passed to them rather than the stored one. For SST cases,
> > > this shall continue to use the stored dp_panel.
> > 
> > Hmm. Why? Can't we get rid of it (in the followup patch)?
> > 
> In MST, the stream panel is bound to the connector, but in SST the panel is
> still stored in msm_dp_display_private and msm_dp_ctrl_private, so
>   dropping it does not seem necessary at this point. The purpose of these
> changes is to allow the functions to operate on the MST panel data passed in
> per stream.

I understand the panel being stored in msm_dp_display_private. Can we
drop it from msm_dp_ctrl_private?

> > > 
> > > Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
> > > Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
> > > ---
> > >   drivers/gpu/drm/msm/dp/dp_ctrl.c    | 21 +++++++++++----------
> > >   drivers/gpu/drm/msm/dp/dp_ctrl.h    |  2 +-
> > >   drivers/gpu/drm/msm/dp/dp_display.c |  2 +-
> > >   3 files changed, 13 insertions(+), 12 deletions(-)
> > > 

-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 13/39] drm/msm/dp: introduce stream_id for each DP panel
  2026-05-19  8:26     ` Yongxing Mou
@ 2026-05-20  9:27       ` Dmitry Baryshkov
  0 siblings, 0 replies; 128+ messages in thread
From: Dmitry Baryshkov @ 2026-05-20  9:27 UTC (permalink / raw)
  To: Yongxing Mou
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar

On Tue, May 19, 2026 at 04:26:21PM +0800, Yongxing Mou wrote:
> 
> 
> On 4/12/2026 1:55 AM, Dmitry Baryshkov wrote:
> > On Fri, Apr 10, 2026 at 05:33:48PM +0800, Yongxing Mou wrote:
> > > From: Abhinav Kumar <quic_abhinavk@quicinc.com>
> > > 
> > > With MST, each DP controller can handle multiple streams.
> > > There shall be one dp_panel for each stream but the dp_display
> > > object shall be shared among them. To represent this abstraction,
> > > create a stream_id for each DP panel which shall be set by the
> > > MST stream. For SST, default this to stream 0.
> > > 
> > > Use the stream ID to control the pixel clock of that respective
> > > stream by extending the clock handles and state tracking of the
> > > DP pixel clock to an array of max supported streams. The maximum
> > > streams currently is 4.
> > 
> > Please mention that panels are going to be dynamically assigned to
> > actual stream IDs.
> > 
> Got it. will update next patch.
> > > 
> > > Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
> > > Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
> > > ---
> > >   drivers/gpu/drm/msm/dp/dp_ctrl.c    | 57 +++++++++++++++++++++++--------------
> > >   drivers/gpu/drm/msm/dp/dp_ctrl.h    |  2 +-
> > >   drivers/gpu/drm/msm/dp/dp_display.c | 24 ++++++++++++++--
> > >   drivers/gpu/drm/msm/dp/dp_display.h |  2 ++
> > >   drivers/gpu/drm/msm/dp/dp_panel.h   | 11 +++++++
> > >   5 files changed, 71 insertions(+), 25 deletions(-)
> > > 
> > > diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c
> > > index 120ec00884e5..fb6396727628 100644
> > > --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c
> > > +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c
> > > @@ -127,7 +127,7 @@ struct msm_dp_ctrl_private {
> > >   	unsigned int num_link_clks;
> > >   	struct clk_bulk_data *link_clks;
> > > -	struct clk *pixel_clk;
> > > +	struct clk *pixel_clk[DP_STREAM_MAX];
> > >   	union phy_configure_opts phy_opts;
> > > @@ -139,7 +139,7 @@ struct msm_dp_ctrl_private {
> > >   	bool core_clks_on;
> > >   	bool link_clks_on;
> > > -	bool stream_clks_on;
> > > +	bool stream_clks_on[DP_STREAM_MAX];
> > >   };
> > >   static inline u32 msm_dp_read_ahb(const struct msm_dp_ctrl_private *ctrl, u32 offset)
> > > @@ -2176,39 +2176,40 @@ static bool msm_dp_ctrl_send_phy_test_pattern(struct msm_dp_ctrl_private *ctrl)
> > >   	return success;
> > >   }
> > > -static int msm_dp_ctrl_on_pixel_clk(struct msm_dp_ctrl_private *ctrl, unsigned long pixel_rate)
> > > +static int msm_dp_ctrl_on_pixel_clk(struct msm_dp_ctrl_private *ctrl, unsigned long pixel_rate,
> > > +				    enum msm_dp_stream_id stream_id)
> > >   {
> > >   	int ret;
> > > -	ret = clk_set_rate(ctrl->pixel_clk, pixel_rate * 1000);
> > > +	ret = clk_set_rate(ctrl->pixel_clk[stream_id], pixel_rate * 1000);
> > >   	if (ret) {
> > >   		DRM_ERROR("Failed to set pixel clock rate. ret=%d\n", ret);
> > >   		return ret;
> > >   	}
> > > -	if (ctrl->stream_clks_on) {
> > > +	if (ctrl->stream_clks_on[stream_id]) {
> > >   		drm_dbg_dp(ctrl->drm_dev, "pixel clks already enabled\n");
> > >   	} else {
> > > -		ret = clk_prepare_enable(ctrl->pixel_clk);
> > > +		ret = clk_prepare_enable(ctrl->pixel_clk[stream_id]);
> > >   		if (ret) {
> > >   			DRM_ERROR("Failed to start pixel clocks. ret=%d\n", ret);
> > >   			return ret;
> > >   		}
> > > -		ctrl->stream_clks_on = true;
> > > +		ctrl->stream_clks_on[stream_id] = true;
> > >   	}
> > >   	return ret;
> > >   }
> > > -void msm_dp_ctrl_off_pixel_clk(struct msm_dp_ctrl *msm_dp_ctrl)
> > > +void msm_dp_ctrl_off_pixel_clk(struct msm_dp_ctrl *msm_dp_ctrl, enum msm_dp_stream_id stream_id)
> > >   {
> > >   	struct msm_dp_ctrl_private *ctrl;
> > >   	ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl);
> > > -	if (ctrl->stream_clks_on) {
> > > -		clk_disable_unprepare(ctrl->pixel_clk);
> > > -		ctrl->stream_clks_on = false;
> > > +	if (ctrl->stream_clks_on[stream_id]) {
> > > +		clk_disable_unprepare(ctrl->pixel_clk[stream_id]);
> > > +		ctrl->stream_clks_on[stream_id] = false;
> > >   	}
> > >   }
> > > @@ -2228,7 +2229,7 @@ static int msm_dp_ctrl_process_phy_test_request(struct msm_dp_ctrl_private *ctrl
> > >   	 * running. Add the global reset just before disabling the
> > >   	 * link clocks and core clocks.
> > >   	 */
> > > -	msm_dp_ctrl_off_pixel_clk(&ctrl->msm_dp_ctrl);
> > > +	msm_dp_ctrl_off_pixel_clk(&ctrl->msm_dp_ctrl, ctrl->panel->stream_id);
> > 
> > Why are we using ctrl->panel again here for the stream-related
> > functions? Didn't you got rid of its usage few patches ago?
> > 
> For MST path, we pass panel to the func. For SST case, we still need to pass
> the sst panel to func, in here it is ctrl->panel.. in dp_display, it is
> dp_display->panel.

This doesn't sound correct. How do we differ between MST and SST in
dp_ctrl functions? I'd really store the panel only in
dp_display_private, passing it as required.

> > >   	msm_dp_ctrl_off_link(&ctrl->msm_dp_ctrl);
> > >   	ret = msm_dp_ctrl_on_link(&ctrl->msm_dp_ctrl);
> > > @@ -2238,7 +2239,7 @@ static int msm_dp_ctrl_process_phy_test_request(struct msm_dp_ctrl_private *ctrl
> > >   	}
> > >   	pixel_rate = ctrl->panel->msm_dp_mode.drm_mode.clock;
> > > -	ret = msm_dp_ctrl_on_pixel_clk(ctrl, pixel_rate);
> > > +	ret = msm_dp_ctrl_on_pixel_clk(ctrl, pixel_rate, ctrl->panel->stream_id);
> > 
> > And here...
> > 
> > >   	msm_dp_ctrl_send_phy_test_pattern(ctrl);
> > > @@ -1451,6 +1469,8 @@ void msm_dp_display_atomic_enable(struct msm_dp *msm_dp_display)
> > >   	dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display);
> > > +	msm_dp_display_set_stream_info(msm_dp_display, dp->panel, 0);
> > 
> > DP_STREAM_0
> > 
> > > +
> > >   	rc = msm_dp_display_enable(dp);
> > >   	if (rc)
> > >   		DRM_ERROR("DP display enable failed, rc=%d\n", rc);
> > 
> 

-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 01/39] drm/msm/dp: remove cached drm_edid from panel
  2026-05-20  9:22       ` Dmitry Baryshkov
@ 2026-05-20 10:09         ` Yongxing Mou
  0 siblings, 0 replies; 128+ messages in thread
From: Yongxing Mou @ 2026-05-20 10:09 UTC (permalink / raw)
  To: Dmitry Baryshkov
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel



On 5/20/2026 5:22 PM, Dmitry Baryshkov wrote:
> On Tue, May 19, 2026 at 03:27:00PM +0800, Yongxing Mou wrote:
>>
>>
>> On 4/10/2026 9:52 PM, Dmitry Baryshkov wrote:
>>> On Fri, Apr 10, 2026 at 05:33:36PM +0800, Yongxing Mou wrote:
>>>> The cached drm_edid seems unnecessary here. Use the drm_edid pointer
>>>> directly in the plug stage instead of caching it. Remove the cached
>>>> drm_edid and the corresponding oneliner to simplify the code.
>>>>
>>>> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
>>>> ---
>>>>    drivers/gpu/drm/msm/dp/dp_display.c | 27 +++++++++++-------
>>>>    drivers/gpu/drm/msm/dp/dp_panel.c   | 57 ++++---------------------------------
>>>>    drivers/gpu/drm/msm/dp/dp_panel.h   | 13 +++------
>>>>    3 files changed, 26 insertions(+), 71 deletions(-)
>>>>
>>>> diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
>>>> index 5c6a24ec140d..e28cc1bbb5b1 100644
>>>> --- a/drivers/gpu/drm/msm/dp/dp_display.c
>>>> +++ b/drivers/gpu/drm/msm/dp/dp_display.c
>>>> @@ -268,6 +268,7 @@ static int msm_dp_display_process_hpd_high(struct msm_dp_display_private *dp)
>>>>    	const struct drm_display_info *info = &connector->display_info;
>>>>    	int rc = 0;
>>>>    	u8 dpcd[DP_RECEIVER_CAP_SIZE];
>>>> +	const struct drm_edid *drm_edid;
>>>>    	rc = drm_dp_read_dpcd_caps(dp->aux, dpcd);
>>>>    	if (rc)
>>>> @@ -275,10 +276,20 @@ static int msm_dp_display_process_hpd_high(struct msm_dp_display_private *dp)
>>>>    	dp->link->lttpr_count = msm_dp_display_lttpr_init(dp, dpcd);
>>>> -	rc = msm_dp_panel_read_sink_caps(dp->panel, connector);
>>>> +	rc = msm_dp_panel_read_link_caps(dp->panel, connector);
>>>>    	if (rc)
>>>>    		goto end;
>>>> +	drm_edid = drm_edid_read_ddc(connector, &dp->aux->ddc);
>>>
>>> Where is it free?
>>>
>> Ohh, i miss it, will update next version
>>>> +	drm_edid_connector_update(connector, drm_edid);
>>>> +
>>>> +	if (!drm_edid) {
>>>> +		DRM_ERROR("panel edid read failed\n");
>>>> +		/* check edid read fail is due to unplug */
>>>> +		if (!msm_dp_aux_is_link_connected(dp->aux))
>>>
>>> Does this work for USB-C AltMode usecase?
>>>
>> For USB-C DP, i think it always TRUE, here should already existed before
>> this patch..
> 
> I'd expect the answer without the 'i think' part.
> 
Sorry.Add log and tested.  For  USB-C AltMode usecase 
msm_dp_aux_is_link_connected() always return 0.
>>>> +			return -ETIMEDOUT;
>>>> +	}
>>>> +
>>>>    	msm_dp_link_process_request(dp->link);
>>>>    	if (!dp->msm_dp_display.is_edp)
>>>
>>
> 


^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 05/39] drm/msm/dp: splite msm_dp_ctrl_config_ctrl() into link parts and stream parts
  2026-05-20  9:23       ` Dmitry Baryshkov
@ 2026-05-20 10:16         ` Yongxing Mou
  2026-05-20 10:20           ` Dmitry Baryshkov
  0 siblings, 1 reply; 128+ messages in thread
From: Yongxing Mou @ 2026-05-20 10:16 UTC (permalink / raw)
  To: Dmitry Baryshkov
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel



On 5/20/2026 5:23 PM, Dmitry Baryshkov wrote:
> On Tue, May 19, 2026 at 03:46:36PM +0800, Yongxing Mou wrote:
>>
>>
>> On 4/12/2026 1:34 AM, Dmitry Baryshkov wrote:
>>> On Fri, Apr 10, 2026 at 05:33:40PM +0800, Yongxing Mou wrote:
>>>> The DP_CONFIGURATION_CTRL register contains both link-level and
>>>> stream-specific fields. Currently, msm_dp_ctrl_config_ctrl() configures
>>>> all of them together. Separates the configuration into link parts and
>>>> streams part for support MST.
>>>>
>>>> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
>>>> ---
>>>>    drivers/gpu/drm/msm/dp/dp_ctrl.c | 43 ++++++++++++++++++++++++++--------------
>>>>    1 file changed, 28 insertions(+), 15 deletions(-)
>>>>
>>>> diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c
>>>> index 476346e3ac19..85315467b5d0 100644
>>>> --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c
>>>> +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c
>>>> @@ -388,26 +388,41 @@ void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl)
>>>>    	drm_dbg_dp(ctrl->drm_dev, "mainlink off\n");
>>>>    }
>>>> -static void msm_dp_ctrl_config_ctrl(struct msm_dp_ctrl_private *ctrl)
>>>> +static void msm_dp_ctrl_config_ctrl_streams(struct msm_dp_ctrl_private *ctrl,
>>>> +					    struct msm_dp_panel *msm_dp_panel)
>>>>    {
>>>>    	u32 config = 0, tbd;
>>>> +
>>>> +	config = msm_dp_read_link(ctrl, REG_DP_CONFIGURATION_CTRL);
>>>> +
>>>> +	if (msm_dp_panel->msm_dp_mode.out_fmt_is_yuv_420)
>>>> +		config |= DP_CONFIGURATION_CTRL_RGB_YUV; /* YUV420 */
>>>> +
>>>> +	tbd = msm_dp_link_get_test_bits_depth(ctrl->link,
>>>> +					      msm_dp_panel->msm_dp_mode.bpp);
>>>> +
>>>> +	config |= tbd << DP_CONFIGURATION_CTRL_BPC_SHIFT;
>>>> +
>>>> +	if (msm_dp_panel->psr_cap.version)
>>>> +		config |= DP_CONFIGURATION_CTRL_SEND_VSC;
>>>> +
>>>> +	drm_dbg_dp(ctrl->drm_dev, "stream DP_CONFIGURATION_CTRL=0x%x\n", config);
>>>> +
>>>> +	msm_dp_write_link(ctrl, REG_DP_CONFIGURATION_CTRL, config);
>>>
>>> You have an RMW cycle here. Please document what prevents it from racing
>>> with the concurrent msm_dp_ctrl_config_ctrl_link().
>>>
>> Here protected by mst_lock in MST case. Will add a comment.
> 
> And in SST case?
>
Emm, do you think this comments fine?

   /*
    * RMW: in SST, config_ctrl_link and config_ctrl_streams are called
    * sequentially on the same thread. In MST, caller holds mst_lock.
    */

>>>> +}
>>>> +
>>>> +static void msm_dp_ctrl_config_ctrl_link(struct msm_dp_ctrl_private *ctrl)
>>>> +{
>>>> +	u32 config = 0;
>>>>    	const u8 *dpcd = ctrl->panel->dpcd;
>>>>    	/* Default-> LSCLK DIV: 1/4 LCLK  */
>>>>    	config |= (2 << DP_CONFIGURATION_CTRL_LSCLK_DIV_SHIFT);
>>>> -	if (ctrl->panel->msm_dp_mode.out_fmt_is_yuv_420)
>>>> -		config |= DP_CONFIGURATION_CTRL_RGB_YUV; /* YUV420 */
>>>> -
>>>>    	/* Scrambler reset enable */
>>>>    	if (drm_dp_alternate_scrambler_reset_cap(dpcd))
>>>>    		config |= DP_CONFIGURATION_CTRL_ASSR;
>>>> -	tbd = msm_dp_link_get_test_bits_depth(ctrl->link,
>>>> -			ctrl->panel->msm_dp_mode.bpp);
>>>> -
>>>> -	config |= tbd << DP_CONFIGURATION_CTRL_BPC_SHIFT;
>>>> -
>>>>    	/* Num of Lanes */
>>>>    	config |= ((ctrl->link->link_params.num_lanes - 1)
>>>>    			<< DP_CONFIGURATION_CTRL_NUM_OF_LANES_SHIFT);
>>>> @@ -421,10 +436,7 @@ static void msm_dp_ctrl_config_ctrl(struct msm_dp_ctrl_private *ctrl)
>>>>    	config |= DP_CONFIGURATION_CTRL_STATIC_DYNAMIC_CN;
>>>>    	config |= DP_CONFIGURATION_CTRL_SYNC_ASYNC_CLK;
>>>> -	if (ctrl->panel->psr_cap.version)
>>>> -		config |= DP_CONFIGURATION_CTRL_SEND_VSC;
>>>> -
>>>> -	drm_dbg_dp(ctrl->drm_dev, "DP_CONFIGURATION_CTRL=0x%x\n", config);
>>>> +	drm_dbg_dp(ctrl->drm_dev, "link DP_CONFIGURATION_CTRL=0x%x\n", config);
>>>>    	msm_dp_write_link(ctrl, REG_DP_CONFIGURATION_CTRL, config);
>>>>    }
>>>> @@ -450,7 +462,8 @@ static void msm_dp_ctrl_configure_source_params(struct msm_dp_ctrl_private *ctrl
>>>>    	msm_dp_ctrl_lane_mapping(ctrl);
>>>>    	msm_dp_setup_peripheral_flush(ctrl);
>>>> -	msm_dp_ctrl_config_ctrl(ctrl);
>>>> +	msm_dp_ctrl_config_ctrl_link(ctrl);
>>>> +	msm_dp_ctrl_config_ctrl_streams(ctrl, ctrl->panel);
>>>>    	test_bits_depth = msm_dp_link_get_test_bits_depth(ctrl->link, ctrl->panel->msm_dp_mode.bpp);
>>>>    	colorimetry_cfg = msm_dp_link_get_colorimetry_config(ctrl->link);
>>>> @@ -1628,7 +1641,7 @@ static int msm_dp_ctrl_link_train(struct msm_dp_ctrl_private *ctrl,
>>>>    	u8 assr;
>>>>    	struct msm_dp_link_info link_info = {0};
>>>> -	msm_dp_ctrl_config_ctrl(ctrl);
>>>> +	msm_dp_ctrl_config_ctrl_link(ctrl);
>>>>    	link_info.num_lanes = ctrl->link->link_params.num_lanes;
>>>>    	link_info.rate = ctrl->link->link_params.rate;
>>>>
>>>> -- 
>>>> 2.43.0
>>>>
>>>
>>
> 


^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 05/39] drm/msm/dp: splite msm_dp_ctrl_config_ctrl() into link parts and stream parts
  2026-05-20 10:16         ` Yongxing Mou
@ 2026-05-20 10:20           ` Dmitry Baryshkov
  0 siblings, 0 replies; 128+ messages in thread
From: Dmitry Baryshkov @ 2026-05-20 10:20 UTC (permalink / raw)
  To: Yongxing Mou
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel

On Wed, May 20, 2026 at 06:16:05PM +0800, Yongxing Mou wrote:
> 
> 
> On 5/20/2026 5:23 PM, Dmitry Baryshkov wrote:
> > On Tue, May 19, 2026 at 03:46:36PM +0800, Yongxing Mou wrote:
> > > 
> > > 
> > > On 4/12/2026 1:34 AM, Dmitry Baryshkov wrote:
> > > > On Fri, Apr 10, 2026 at 05:33:40PM +0800, Yongxing Mou wrote:
> > > > > The DP_CONFIGURATION_CTRL register contains both link-level and
> > > > > stream-specific fields. Currently, msm_dp_ctrl_config_ctrl() configures
> > > > > all of them together. Separates the configuration into link parts and
> > > > > streams part for support MST.
> > > > > 
> > > > > Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
> > > > > ---
> > > > >    drivers/gpu/drm/msm/dp/dp_ctrl.c | 43 ++++++++++++++++++++++++++--------------
> > > > >    1 file changed, 28 insertions(+), 15 deletions(-)
> > > > > 
> > > > > diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c
> > > > > index 476346e3ac19..85315467b5d0 100644
> > > > > --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c
> > > > > +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c
> > > > > @@ -388,26 +388,41 @@ void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl)
> > > > >    	drm_dbg_dp(ctrl->drm_dev, "mainlink off\n");
> > > > >    }
> > > > > -static void msm_dp_ctrl_config_ctrl(struct msm_dp_ctrl_private *ctrl)
> > > > > +static void msm_dp_ctrl_config_ctrl_streams(struct msm_dp_ctrl_private *ctrl,
> > > > > +					    struct msm_dp_panel *msm_dp_panel)
> > > > >    {
> > > > >    	u32 config = 0, tbd;
> > > > > +
> > > > > +	config = msm_dp_read_link(ctrl, REG_DP_CONFIGURATION_CTRL);
> > > > > +
> > > > > +	if (msm_dp_panel->msm_dp_mode.out_fmt_is_yuv_420)
> > > > > +		config |= DP_CONFIGURATION_CTRL_RGB_YUV; /* YUV420 */
> > > > > +
> > > > > +	tbd = msm_dp_link_get_test_bits_depth(ctrl->link,
> > > > > +					      msm_dp_panel->msm_dp_mode.bpp);
> > > > > +
> > > > > +	config |= tbd << DP_CONFIGURATION_CTRL_BPC_SHIFT;
> > > > > +
> > > > > +	if (msm_dp_panel->psr_cap.version)
> > > > > +		config |= DP_CONFIGURATION_CTRL_SEND_VSC;
> > > > > +
> > > > > +	drm_dbg_dp(ctrl->drm_dev, "stream DP_CONFIGURATION_CTRL=0x%x\n", config);
> > > > > +
> > > > > +	msm_dp_write_link(ctrl, REG_DP_CONFIGURATION_CTRL, config);
> > > > 
> > > > You have an RMW cycle here. Please document what prevents it from racing
> > > > with the concurrent msm_dp_ctrl_config_ctrl_link().
> > > > 
> > > Here protected by mst_lock in MST case. Will add a comment.
> > 
> > And in SST case?
> > 
> Emm, do you think this comments fine?
> 
>   /*
>    * RMW: in SST, config_ctrl_link and config_ctrl_streams are called
>    * sequentially on the same thread. In MST, caller holds mst_lock.
>    */

LGTM

-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 08/39] drm/msm/dp: allow dp_ctrl stream APIs to use any panel passed to it
  2026-05-20  9:24       ` Dmitry Baryshkov
@ 2026-05-20 10:31         ` Yongxing Mou
  2026-05-20 12:36           ` Dmitry Baryshkov
  0 siblings, 1 reply; 128+ messages in thread
From: Yongxing Mou @ 2026-05-20 10:31 UTC (permalink / raw)
  To: Dmitry Baryshkov
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar



On 5/20/2026 5:24 PM, Dmitry Baryshkov wrote:
> On Tue, May 19, 2026 at 03:52:35PM +0800, Yongxing Mou wrote:
>>
>>
>> On 4/12/2026 1:38 AM, Dmitry Baryshkov wrote:
>>> On Fri, Apr 10, 2026 at 05:33:43PM +0800, Yongxing Mou wrote:
>>>> From: Abhinav Kumar <quic_abhinavk@quicinc.com>
>>>>
>>>> Currently, the dp_ctrl stream APIs operate on their own dp_panel
>>>> which is stored inside the dp_ctrl's private struct. However with MST,
>>>> the stored panel represents the fixed link and not the sinks which
>>>> are hotplugged. Allow the stream related APIs to work on the panel
>>>> which is passed to them rather than the stored one. For SST cases,
>>>> this shall continue to use the stored dp_panel.
>>>
>>> Hmm. Why? Can't we get rid of it (in the followup patch)?
>>>
>> In MST, the stream panel is bound to the connector, but in SST the panel is
>> still stored in msm_dp_display_private and msm_dp_ctrl_private, so
>>    dropping it does not seem necessary at this point. The purpose of these
>> changes is to allow the functions to operate on the MST panel data passed in
>> per stream.
> 
> I understand the panel being stored in msm_dp_display_private. Can we
> drop it from msm_dp_ctrl_private?
> 
Dropping panel from msm_dp_ctrl_private would require adding a panel 
parameter to approximately 15 functions in dp_ctrl.c.  Would you like us 
to do that in this patch?
>>>>
>>>> Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
>>>> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
>>>> ---
>>>>    drivers/gpu/drm/msm/dp/dp_ctrl.c    | 21 +++++++++++----------
>>>>    drivers/gpu/drm/msm/dp/dp_ctrl.h    |  2 +-
>>>>    drivers/gpu/drm/msm/dp/dp_display.c |  2 +-
>>>>    3 files changed, 13 insertions(+), 12 deletions(-)
>>>>
> 


^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 08/39] drm/msm/dp: allow dp_ctrl stream APIs to use any panel passed to it
  2026-05-20 10:31         ` Yongxing Mou
@ 2026-05-20 12:36           ` Dmitry Baryshkov
  0 siblings, 0 replies; 128+ messages in thread
From: Dmitry Baryshkov @ 2026-05-20 12:36 UTC (permalink / raw)
  To: Yongxing Mou
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar

On Wed, May 20, 2026 at 06:31:24PM +0800, Yongxing Mou wrote:
> 
> 
> On 5/20/2026 5:24 PM, Dmitry Baryshkov wrote:
> > On Tue, May 19, 2026 at 03:52:35PM +0800, Yongxing Mou wrote:
> > > 
> > > 
> > > On 4/12/2026 1:38 AM, Dmitry Baryshkov wrote:
> > > > On Fri, Apr 10, 2026 at 05:33:43PM +0800, Yongxing Mou wrote:
> > > > > From: Abhinav Kumar <quic_abhinavk@quicinc.com>
> > > > > 
> > > > > Currently, the dp_ctrl stream APIs operate on their own dp_panel
> > > > > which is stored inside the dp_ctrl's private struct. However with MST,
> > > > > the stored panel represents the fixed link and not the sinks which
> > > > > are hotplugged. Allow the stream related APIs to work on the panel
> > > > > which is passed to them rather than the stored one. For SST cases,
> > > > > this shall continue to use the stored dp_panel.
> > > > 
> > > > Hmm. Why? Can't we get rid of it (in the followup patch)?
> > > > 
> > > In MST, the stream panel is bound to the connector, but in SST the panel is
> > > still stored in msm_dp_display_private and msm_dp_ctrl_private, so
> > >    dropping it does not seem necessary at this point. The purpose of these
> > > changes is to allow the functions to operate on the MST panel data passed in
> > > per stream.
> > 
> > I understand the panel being stored in msm_dp_display_private. Can we
> > drop it from msm_dp_ctrl_private?
> > 
> Dropping panel from msm_dp_ctrl_private would require adding a panel
> parameter to approximately 15 functions in dp_ctrl.c.  Would you like us to
> do that in this patch?

Either this or a separate patch. But it should be clear which panel gets
used. Otherwise we risk using a wrong panel in the MST case.

> > > > > 
> > > > > Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
> > > > > Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
> > > > > ---
> > > > >    drivers/gpu/drm/msm/dp/dp_ctrl.c    | 21 +++++++++++----------
> > > > >    drivers/gpu/drm/msm/dp/dp_ctrl.h    |  2 +-
> > > > >    drivers/gpu/drm/msm/dp/dp_display.c |  2 +-
> > > > >    3 files changed, 13 insertions(+), 12 deletions(-)
> > > > > 
> > 
> 

-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 14/39] drm/msm/dp: introduce max_streams for DP controller MST support
  2026-04-11 17:59   ` Dmitry Baryshkov
@ 2026-05-21 11:34     ` Yongxing Mou
  2026-05-21 12:07       ` Dmitry Baryshkov
  0 siblings, 1 reply; 128+ messages in thread
From: Yongxing Mou @ 2026-05-21 11:34 UTC (permalink / raw)
  To: Dmitry Baryshkov
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar



On 4/12/2026 1:59 AM, Dmitry Baryshkov wrote:
> On Fri, Apr 10, 2026 at 05:33:49PM +0800, Yongxing Mou wrote:
>> From: Abhinav Kumar <quic_abhinavk@quicinc.com>
>>
>> Introduce the `mst_streams` field in each DP controller descriptor to
>> specify the number of supported MST streams. Most platforms support 2 or
>> 4 MST streams, while platforms without MST support default to a single
>> stream (`DEFAULT_STREAM_COUNT = 1`).
> 
> There is no such const anymore.
> 
>>
>> Also accounts for platforms with asymmetric stream support, e.g., DP0
>> supporting 4 streams and DP1 supporting 2.
> 
> There is no need to mention it.
> 
>>
>> Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
>> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
>> ---
>>   drivers/gpu/drm/msm/dp/dp_ctrl.c    | 20 +++++++++++++++++---
>>   drivers/gpu/drm/msm/dp/dp_ctrl.h    |  2 ++
>>   drivers/gpu/drm/msm/dp/dp_display.c | 20 +++++++++++++++++++-
>>   drivers/gpu/drm/msm/dp/dp_display.h |  1 +
>>   4 files changed, 39 insertions(+), 4 deletions(-)
>>
>> @@ -2740,7 +2741,8 @@ static int msm_dp_ctrl_clk_init(struct msm_dp_ctrl *msm_dp_ctrl)
>>   	if (rc)
>>   		return rc;
>>   
>> -	for (i = DP_STREAM_0; i < DP_STREAM_MAX; i++) {
>> +	ctrl->num_pixel_clks = 0;
>> +	for (i = DP_STREAM_0; i < max_stream; i++) {
> 
> Why? I think the code was fine with getting up to DP_STREAM_MAX clocks.
Sure.. will change max_stream -> DP_STREAM_MAX next version.
> In fact, I'd rather use that instead of the hardcoded value for
> determining how many streams can be there. Think of the old DTs which
> didn't have extra stream clocks. We still need to support those.
> 
The current logic only assigns num streams to dp->max_stream when 
desc->mst_streams == num_pixel_clks, otherwise set to 1 which mean sst.
I would like to introduce a switch in the driver to control the number 
of streams, rather than having it fully determined by the DTs.
Do you think it would be acceptable to use:
dp->max_stream = min(num_pixel_clks, desc->mst_streams)?

>>   		ctrl->pixel_clk[i] = devm_clk_get(dev, pixel_clks[i]);
>>   
>>   		if (i == 0 && IS_ERR(ctrl->pixel_clk[i]))
>> @@ -2750,14 +2752,26 @@ static int msm_dp_ctrl_clk_init(struct msm_dp_ctrl *msm_dp_ctrl)
>>   			DRM_DEBUG_DP("stream %d pixel clock not exist", i);
>>   			break;
>>   		}
>> +
>> +		ctrl->num_pixel_clks++;
>>   	}
>>   
>>   	return 0;
>>   }
>>   
>> @@ -1234,6 +1248,10 @@ static int msm_dp_display_probe(struct platform_device *pdev)
>>   	dp->msm_dp_display.is_edp =
>>   		(dp->msm_dp_display.connector_type == DRM_MODE_CONNECTOR_eDP);
>>   	dp->hpd_isr_status = 0;
>> +	dp->max_stream = 1;
>> +
>> +	if (desc->mst_streams > 1)
>> +		dp->max_stream = desc->mst_streams;
> 
> Here you can assign it from the DT resources.
> 
>>   
>>   	mutex_init(&dp->plugged_lock);
>>   
>> diff --git a/drivers/gpu/drm/msm/dp/dp_display.h b/drivers/gpu/drm/msm/dp/dp_display.h
>> index a5c6ed5b18e4..b0cfdf215970 100644
>> --- a/drivers/gpu/drm/msm/dp/dp_display.h
>> +++ b/drivers/gpu/drm/msm/dp/dp_display.h
>> @@ -26,6 +26,7 @@ struct msm_dp {
>>   	bool psr_supported;
>>   };
>>   
>> +int msm_dp_get_mst_max_stream(struct msm_dp *msm_dp_display);
>>   int msm_dp_display_get_modes(struct msm_dp *msm_dp_display);
>>   bool msm_dp_display_check_video_test(struct msm_dp *msm_dp_display);
>>   int msm_dp_display_get_test_bpp(struct msm_dp *msm_dp_display);
>>
>> -- 
>> 2.43.0
>>
> 


^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 15/39] drm/msm/dp: Add support for programming p1/p2/p3 register blocks
  2026-04-11 18:07   ` Dmitry Baryshkov
@ 2026-05-21 11:50     ` Yongxing Mou
  2026-05-21 12:20       ` Dmitry Baryshkov
  0 siblings, 1 reply; 128+ messages in thread
From: Yongxing Mou @ 2026-05-21 11:50 UTC (permalink / raw)
  To: Dmitry Baryshkov
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar



On 4/12/2026 2:07 AM, Dmitry Baryshkov wrote:
> On Fri, Apr 10, 2026 at 05:33:50PM +0800, Yongxing Mou wrote:
>> From: Abhinav Kumar <quic_abhinavk@quicinc.com>
>>
>> Add support for additional pixel register blocks (p1, p2, p3) to enable
>> 4‑stream MST pixel clocks. Introduce the helper functions msm_dp_read_pn
>> and msm_dp_write_pn for pixel register programming. All pixel clocks
>> share the same register layout but use different base addresses.
>>
>> Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
>> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
>> ---
>>   drivers/gpu/drm/msm/dp/dp_display.c | 40 ++++++++++++-----
>>   drivers/gpu/drm/msm/dp/dp_panel.c   | 89 ++++++++++++++++++++-----------------
>>   drivers/gpu/drm/msm/dp/dp_panel.h   |  3 +-
>>   3 files changed, 79 insertions(+), 53 deletions(-)
>>
>> diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
>> index 7984a0f9e938..ff506064a3fa 100644
>> --- a/drivers/gpu/drm/msm/dp/dp_display.c
>> +++ b/drivers/gpu/drm/msm/dp/dp_display.c
>> @@ -85,8 +85,8 @@ struct msm_dp_display_private {
>>   	void __iomem *link_base;
>>   	size_t link_len;
>>   
>> -	void __iomem *p0_base;
>> -	size_t p0_len;
>> +	void __iomem *pixel_base[DP_STREAM_MAX];
>> +	size_t pixel_len;
>>   
>>   	int max_stream;
>>   };
>> @@ -561,7 +561,7 @@ static int msm_dp_init_sub_modules(struct msm_dp_display_private *dp)
>>   		goto error_link;
>>   	}
>>   
>> -	dp->panel = msm_dp_panel_get(dev, dp->aux, dp->link, dp->link_base, dp->p0_base);
>> +	dp->panel = msm_dp_panel_get(dev, dp->aux, dp->link, dp->link_base, dp->pixel_base[0]);
>>   	if (IS_ERR(dp->panel)) {
>>   		rc = PTR_ERR(dp->panel);
>>   		DRM_ERROR("failed to initialize panel, rc = %d\n", rc);
>> @@ -769,6 +769,7 @@ int msm_dp_display_set_stream_info(struct msm_dp *msm_dp_display,
>>   	}
>>   
>>   	panel->stream_id = stream_id;
>> +	msm_dp_panel_set_pixel_base(panel, dp->pixel_base[stream_id]);
> 
> Hmmm.... Would it be better to set it up differently? Allocate one panel
> per the stream from the beginning and then simply get the first
> available panel when required? This would require some minimal resource
> manager, but then we won't have to pass dummy register base to the panel
> code. Or actually allocate a panel when it is required? Do we need a
> panel before atomic_enable()?
> 
In this series, panel come with MST connectors, Because the connectors 
are dynamically assigned, we don’t know which connector corresponds to 
which stream, so there stream_id and pixel base address are dynamic.
we have 2 optionals here:
1.panel come with encoder (after bridge remvoed), like that:
struct msm_dp_mst_encoder {
	struct drm_encoder *enc;
	int stream_id;
	struct msm_dp_panel *dp_panel;
};
2. During enable and disable, allocate and release dynamically.
would you prefer which one?
>>   
>>   	return rc;
>>   }
>> @@ -882,8 +883,14 @@ void msm_dp_snapshot(struct msm_disp_state *disp_state, struct msm_dp *dp)
>>   				    msm_dp_display->aux_base, "dp_aux");
>>   	msm_disp_snapshot_add_block(disp_state, msm_dp_display->link_len,
>>   				    msm_dp_display->link_base, "dp_link");
>> -	msm_disp_snapshot_add_block(disp_state, msm_dp_display->p0_len,
>> -				    msm_dp_display->p0_base, "dp_p0");
>> +	msm_disp_snapshot_add_block(disp_state, msm_dp_display->pixel_len,
>> +				    msm_dp_display->pixel_base[0], "dp_p0");
>> +	msm_disp_snapshot_add_block(disp_state, msm_dp_display->pixel_len,
>> +				    msm_dp_display->pixel_base[1], "dp_p1");
>> +	msm_disp_snapshot_add_block(disp_state, msm_dp_display->pixel_len,
>> +				    msm_dp_display->pixel_base[2], "dp_p2");
>> +	msm_disp_snapshot_add_block(disp_state, msm_dp_display->pixel_len,
>> +				    msm_dp_display->pixel_base[3], "dp_p3");
>>   }
>>   
>>   void msm_dp_display_set_psr(struct msm_dp *msm_dp_display, bool enter)
>> @@ -1163,6 +1170,7 @@ static void __iomem *msm_dp_ioremap(struct platform_device *pdev, int idx, size_
>>   static int msm_dp_display_get_io(struct msm_dp_display_private *display)
>>   {
>>   	struct platform_device *pdev = display->msm_dp_display.pdev;
>> +	int i;
>>   
>>   	display->ahb_base = msm_dp_ioremap(pdev, 0, &display->ahb_len);
>>   	if (IS_ERR(display->ahb_base))
>> @@ -1192,8 +1200,8 @@ static int msm_dp_display_get_io(struct msm_dp_display_private *display)
>>   		display->aux_len = DP_DEFAULT_AUX_SIZE;
>>   		display->link_base = display->ahb_base + DP_DEFAULT_LINK_OFFSET;
>>   		display->link_len = DP_DEFAULT_LINK_SIZE;
>> -		display->p0_base = display->ahb_base + DP_DEFAULT_P0_OFFSET;
>> -		display->p0_len = DP_DEFAULT_P0_SIZE;
>> +		display->pixel_base[0] = display->ahb_base + DP_DEFAULT_P0_OFFSET;
>> +		display->pixel_len = DP_DEFAULT_P0_SIZE;
>>   
>>   		return 0;
>>   	}
>> @@ -1204,10 +1212,20 @@ static int msm_dp_display_get_io(struct msm_dp_display_private *display)
>>   		return PTR_ERR(display->link_base);
>>   	}
>>   
>> -	display->p0_base = msm_dp_ioremap(pdev, 3, &display->p0_len);
>> -	if (IS_ERR(display->p0_base)) {
>> -		DRM_ERROR("unable to remap p0 region: %pe\n", display->p0_base);
>> -		return PTR_ERR(display->p0_base);
>> +	display->pixel_base[0] = msm_dp_ioremap(pdev, 3, &display->pixel_len);
>> +	if (IS_ERR(display->pixel_base[0])) {
>> +		DRM_ERROR("unable to remap p0 region: %pe\n", display->pixel_base[0]);
>> +		return PTR_ERR(display->pixel_base[0]);
>> +	}
>> +
>> +	for (i = DP_STREAM_1; i < display->max_stream; i++) {
>> +		/* pixels clk reg index start from 3*/
>> +		display->pixel_base[i] = msm_dp_ioremap(pdev, i + 3, &display->pixel_len);
>> +		if (IS_ERR(display->pixel_base[i])) {
>> +			DRM_DEBUG_DP("unable to remap p%d region: %pe\n", i,
>> +				     display->pixel_base[i]);
>> +			break;
> 
> No, return an error.
> 
Got it.
>> +		}
>>   	}
>>   
>>   	return 0;
> 


^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 14/39] drm/msm/dp: introduce max_streams for DP controller MST support
  2026-05-21 11:34     ` Yongxing Mou
@ 2026-05-21 12:07       ` Dmitry Baryshkov
  2026-05-22  7:58         ` Yongxing Mou
  0 siblings, 1 reply; 128+ messages in thread
From: Dmitry Baryshkov @ 2026-05-21 12:07 UTC (permalink / raw)
  To: Yongxing Mou
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar

On Thu, May 21, 2026 at 07:34:32PM +0800, Yongxing Mou wrote:
> 
> 
> On 4/12/2026 1:59 AM, Dmitry Baryshkov wrote:
> > On Fri, Apr 10, 2026 at 05:33:49PM +0800, Yongxing Mou wrote:
> > > From: Abhinav Kumar <quic_abhinavk@quicinc.com>
> > > @@ -2740,7 +2741,8 @@ static int msm_dp_ctrl_clk_init(struct msm_dp_ctrl *msm_dp_ctrl)
> > >   	if (rc)
> > >   		return rc;
> > > -	for (i = DP_STREAM_0; i < DP_STREAM_MAX; i++) {
> > > +	ctrl->num_pixel_clks = 0;
> > > +	for (i = DP_STREAM_0; i < max_stream; i++) {
> > 
> > Why? I think the code was fine with getting up to DP_STREAM_MAX clocks.
> Sure.. will change max_stream -> DP_STREAM_MAX next version.
> > In fact, I'd rather use that instead of the hardcoded value for
> > determining how many streams can be there. Think of the old DTs which
> > didn't have extra stream clocks. We still need to support those.
> > 
> The current logic only assigns num streams to dp->max_stream when
> desc->mst_streams == num_pixel_clks, otherwise set to 1 which mean sst.
> I would like to introduce a switch in the driver to control the number of
> streams, rather than having it fully determined by the DTs.

Why?

> Do you think it would be acceptable to use:
> dp->max_stream = min(num_pixel_clks, desc->mst_streams)?

We know that num_pixel_clks <= desc->mst_streams. Otherwise DT is wrong
and should not have been comitted. So, the min(a,b) has a known return
value of num_pixel_clks.

> 
> > >   		ctrl->pixel_clk[i] = devm_clk_get(dev, pixel_clks[i]);
> > >   		if (i == 0 && IS_ERR(ctrl->pixel_clk[i]))

-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 15/39] drm/msm/dp: Add support for programming p1/p2/p3 register blocks
  2026-05-21 11:50     ` Yongxing Mou
@ 2026-05-21 12:20       ` Dmitry Baryshkov
  2026-05-22  7:51         ` Yongxing Mou
  0 siblings, 1 reply; 128+ messages in thread
From: Dmitry Baryshkov @ 2026-05-21 12:20 UTC (permalink / raw)
  To: Yongxing Mou
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar

On Thu, May 21, 2026 at 07:50:30PM +0800, Yongxing Mou wrote:
> 
> 
> On 4/12/2026 2:07 AM, Dmitry Baryshkov wrote:
> > On Fri, Apr 10, 2026 at 05:33:50PM +0800, Yongxing Mou wrote:
> > > From: Abhinav Kumar <quic_abhinavk@quicinc.com>
> > > 
> > > Add support for additional pixel register blocks (p1, p2, p3) to enable
> > > 4‑stream MST pixel clocks. Introduce the helper functions msm_dp_read_pn
> > > and msm_dp_write_pn for pixel register programming. All pixel clocks
> > > share the same register layout but use different base addresses.
> > > 
> > > Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
> > > Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
> > > ---
> > >   drivers/gpu/drm/msm/dp/dp_display.c | 40 ++++++++++++-----
> > >   drivers/gpu/drm/msm/dp/dp_panel.c   | 89 ++++++++++++++++++++-----------------
> > >   drivers/gpu/drm/msm/dp/dp_panel.h   |  3 +-
> > >   3 files changed, 79 insertions(+), 53 deletions(-)
> > > 
> > > diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
> > > index 7984a0f9e938..ff506064a3fa 100644
> > > --- a/drivers/gpu/drm/msm/dp/dp_display.c
> > > +++ b/drivers/gpu/drm/msm/dp/dp_display.c
> > > @@ -85,8 +85,8 @@ struct msm_dp_display_private {
> > >   	void __iomem *link_base;
> > >   	size_t link_len;
> > > -	void __iomem *p0_base;
> > > -	size_t p0_len;
> > > +	void __iomem *pixel_base[DP_STREAM_MAX];
> > > +	size_t pixel_len;
> > >   	int max_stream;
> > >   };
> > > @@ -561,7 +561,7 @@ static int msm_dp_init_sub_modules(struct msm_dp_display_private *dp)
> > >   		goto error_link;
> > >   	}
> > > -	dp->panel = msm_dp_panel_get(dev, dp->aux, dp->link, dp->link_base, dp->p0_base);
> > > +	dp->panel = msm_dp_panel_get(dev, dp->aux, dp->link, dp->link_base, dp->pixel_base[0]);
> > >   	if (IS_ERR(dp->panel)) {
> > >   		rc = PTR_ERR(dp->panel);
> > >   		DRM_ERROR("failed to initialize panel, rc = %d\n", rc);
> > > @@ -769,6 +769,7 @@ int msm_dp_display_set_stream_info(struct msm_dp *msm_dp_display,
> > >   	}
> > >   	panel->stream_id = stream_id;
> > > +	msm_dp_panel_set_pixel_base(panel, dp->pixel_base[stream_id]);
> > 
> > Hmmm.... Would it be better to set it up differently? Allocate one panel
> > per the stream from the beginning and then simply get the first
> > available panel when required? This would require some minimal resource
> > manager, but then we won't have to pass dummy register base to the panel
> > code. Or actually allocate a panel when it is required? Do we need a
> > panel before atomic_enable()?
> > 
> In this series, panel come with MST connectors, Because the connectors are
> dynamically assigned, we don’t know which connector corresponds to which
> stream, so there stream_id and pixel base address are dynamic.

I read this as 'streams are dynamically assigned'. Connectors are fixed
and created for each branch point / real physical connector. Streams are
assigned on the first-serve bases.

> we have 2 optionals here:

What is the runtime requirement for the panels? Are they required for
parsing of the resources or only for the setup of the actual screen?

If we have 5 monitors connected to a single DP controller (via the
complicated topology), how many msm_dp_panel instances do we need to
handle the case, present it to the user and still let it select only 4
of them for the video output?

> 1.panel come with encoder (after bridge remvoed), like that:
> struct msm_dp_mst_encoder {
> 	struct drm_encoder *enc;
> 	int stream_id;
> 	struct msm_dp_panel *dp_panel;
> };

This sounds like a good idea. This way there is no additional resource
management, etc.

> 2. During enable and disable, allocate and release dynamically.
> would you prefer which one?
> > >   	return rc;
> > >   }

-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 15/39] drm/msm/dp: Add support for programming p1/p2/p3 register blocks
  2026-05-21 12:20       ` Dmitry Baryshkov
@ 2026-05-22  7:51         ` Yongxing Mou
  2026-05-25  8:19           ` Dmitry Baryshkov
  0 siblings, 1 reply; 128+ messages in thread
From: Yongxing Mou @ 2026-05-22  7:51 UTC (permalink / raw)
  To: Dmitry Baryshkov
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar



On 5/21/2026 8:20 PM, Dmitry Baryshkov wrote:
> On Thu, May 21, 2026 at 07:50:30PM +0800, Yongxing Mou wrote:
>>
>>
>> On 4/12/2026 2:07 AM, Dmitry Baryshkov wrote:
>>> On Fri, Apr 10, 2026 at 05:33:50PM +0800, Yongxing Mou wrote:
>>>> From: Abhinav Kumar <quic_abhinavk@quicinc.com>
>>>>
>>>> Add support for additional pixel register blocks (p1, p2, p3) to enable
>>>> 4‑stream MST pixel clocks. Introduce the helper functions msm_dp_read_pn
>>>> and msm_dp_write_pn for pixel register programming. All pixel clocks
>>>> share the same register layout but use different base addresses.
>>>>
>>>> Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
>>>> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
>>>> ---
>>>>    drivers/gpu/drm/msm/dp/dp_display.c | 40 ++++++++++++-----
>>>>    drivers/gpu/drm/msm/dp/dp_panel.c   | 89 ++++++++++++++++++++-----------------
>>>>    drivers/gpu/drm/msm/dp/dp_panel.h   |  3 +-
>>>>    3 files changed, 79 insertions(+), 53 deletions(-)
>>>>
>>>> diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
>>>> index 7984a0f9e938..ff506064a3fa 100644
>>>> --- a/drivers/gpu/drm/msm/dp/dp_display.c
>>>> +++ b/drivers/gpu/drm/msm/dp/dp_display.c
>>>> @@ -85,8 +85,8 @@ struct msm_dp_display_private {
>>>>    	void __iomem *link_base;
>>>>    	size_t link_len;
>>>> -	void __iomem *p0_base;
>>>> -	size_t p0_len;
>>>> +	void __iomem *pixel_base[DP_STREAM_MAX];
>>>> +	size_t pixel_len;
>>>>    	int max_stream;
>>>>    };
>>>> @@ -561,7 +561,7 @@ static int msm_dp_init_sub_modules(struct msm_dp_display_private *dp)
>>>>    		goto error_link;
>>>>    	}
>>>> -	dp->panel = msm_dp_panel_get(dev, dp->aux, dp->link, dp->link_base, dp->p0_base);
>>>> +	dp->panel = msm_dp_panel_get(dev, dp->aux, dp->link, dp->link_base, dp->pixel_base[0]);
>>>>    	if (IS_ERR(dp->panel)) {
>>>>    		rc = PTR_ERR(dp->panel);
>>>>    		DRM_ERROR("failed to initialize panel, rc = %d\n", rc);
>>>> @@ -769,6 +769,7 @@ int msm_dp_display_set_stream_info(struct msm_dp *msm_dp_display,
>>>>    	}
>>>>    	panel->stream_id = stream_id;
>>>> +	msm_dp_panel_set_pixel_base(panel, dp->pixel_base[stream_id]);
>>>
>>> Hmmm.... Would it be better to set it up differently? Allocate one panel
>>> per the stream from the beginning and then simply get the first
>>> available panel when required? This would require some minimal resource
>>> manager, but then we won't have to pass dummy register base to the panel
>>> code. Or actually allocate a panel when it is required? Do we need a
>>> panel before atomic_enable()?
>>>
>> In this series, panel come with MST connectors, Because the connectors are
>> dynamically assigned, we don’t know which connector corresponds to which
>> stream, so there stream_id and pixel base address are dynamic.
> 
> I read this as 'streams are dynamically assigned'. Connectors are fixed
> and created for each branch point / real physical connector. Streams are
> assigned on the first-serve bases.
> 
Thanks for pointing that out — your statement here is accurate.
>> we have 2 optionals here:
> 
> What is the runtime requirement for the panels? Are they required for
> parsing of the resources or only for the setup of the actual screen?
> 
> If we have 5 monitors connected to a single DP controller (via the
> complicated topology), how many msm_dp_panel instances do we need to
> handle the case, present it to the user and still let it select only 4
> of them for the video output?
> 
It seems that if we move the link-related resources from the panel into 
dp_link,
struct msm_dp_panel {
	/* dpcd raw data */
	u8 dpcd[DP_RECEIVER_CAP_SIZE];
	u8 downstream_ports[DP_MAX_DOWNSTREAM_PORTS];
	struct msm_dp_link_info link_info;
...
}
  we would only need to assign the panel during atomic_enable / 
atomic_disable.
This way, in the scenario you described, only the final four panels 
would be exposed to the user.
In the current implementation, the panel contains link-related 
information that is required during the link training phase.
>> 1.panel come with encoder (after bridge remvoed), like that:
>> struct msm_dp_mst_encoder {
>> 	struct drm_encoder *enc;
>> 	int stream_id;
>> 	struct msm_dp_panel *dp_panel;
>> };
> 
> This sounds like a good idea. This way there is no additional resource
> management, etc.
> 
>> 2. During enable and disable, allocate and release dynamically.
>> would you prefer which one?
>>>>    	return rc;
>>>>    }
> 


^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 14/39] drm/msm/dp: introduce max_streams for DP controller MST support
  2026-05-21 12:07       ` Dmitry Baryshkov
@ 2026-05-22  7:58         ` Yongxing Mou
  2026-05-25  8:16           ` Dmitry Baryshkov
  0 siblings, 1 reply; 128+ messages in thread
From: Yongxing Mou @ 2026-05-22  7:58 UTC (permalink / raw)
  To: Dmitry Baryshkov
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar



On 5/21/2026 8:07 PM, Dmitry Baryshkov wrote:
> On Thu, May 21, 2026 at 07:34:32PM +0800, Yongxing Mou wrote:
>>
>>
>> On 4/12/2026 1:59 AM, Dmitry Baryshkov wrote:
>>> On Fri, Apr 10, 2026 at 05:33:49PM +0800, Yongxing Mou wrote:
>>>> From: Abhinav Kumar <quic_abhinavk@quicinc.com>
>>>> @@ -2740,7 +2741,8 @@ static int msm_dp_ctrl_clk_init(struct msm_dp_ctrl *msm_dp_ctrl)
>>>>    	if (rc)
>>>>    		return rc;
>>>> -	for (i = DP_STREAM_0; i < DP_STREAM_MAX; i++) {
>>>> +	ctrl->num_pixel_clks = 0;
>>>> +	for (i = DP_STREAM_0; i < max_stream; i++) {
>>>
>>> Why? I think the code was fine with getting up to DP_STREAM_MAX clocks.
>> Sure.. will change max_stream -> DP_STREAM_MAX next version.
>>> In fact, I'd rather use that instead of the hardcoded value for
>>> determining how many streams can be there. Think of the old DTs which
>>> didn't have extra stream clocks. We still need to support those.
>>>
>> The current logic only assigns num streams to dp->max_stream when
>> desc->mst_streams == num_pixel_clks, otherwise set to 1 which mean sst.
>> I would like to introduce a switch in the driver to control the number of
>> streams, rather than having it fully determined by the DTs.
> 
> Why?
> 
1. In some scenarios, even if the hardware supports up to 4-stream MST, 
we may not want to enable all streams (e.g., LeMans: 2xDSI + 4xMST + 
2xMST, but it only has a 6-layer mixer).
2. Currently, the DTs for all platforms have already been merged, and 
for some legacy platforms, not sure if all platforms MST will work.

If my understanding is incorrect, please feel free to correct me. Thanks.
>> Do you think it would be acceptable to use:
>> dp->max_stream = min(num_pixel_clks, desc->mst_streams)?
> 
> We know that num_pixel_clks <= desc->mst_streams. Otherwise DT is wrong
> and should not have been comitted. So, the min(a,b) has a known return
> value of num_pixel_clks.
> 
>>
>>>>    		ctrl->pixel_clk[i] = devm_clk_get(dev, pixel_clks[i]);
>>>>    		if (i == 0 && IS_ERR(ctrl->pixel_clk[i]))
> 


^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 00/39] drm/msm/dp: Add MST support for MSM chipsets
  2026-04-12  0:34 ` Dmitry Baryshkov
@ 2026-05-22  8:07   ` Yongxing Mou
  2026-05-25  8:26     ` Dmitry Baryshkov
  0 siblings, 1 reply; 128+ messages in thread
From: Yongxing Mou @ 2026-05-22  8:07 UTC (permalink / raw)
  To: Dmitry Baryshkov
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar



On 4/12/2026 8:34 AM, Dmitry Baryshkov wrote:
> On Fri, Apr 10, 2026 at 05:33:35PM +0800, Yongxing Mou wrote:
>> Add support for Multi-stream transport for MSM chipsets that allow
>> a single instance of DP controller to send multiple streams.
>>
>> This series has been validated on sa8775p ride platform using multiple
>> MST dongles and also daisy chain method on both DP0 and DP1 upto 1080P.
>>
>> With 4x4K monitors, due to lack of layer mixers that combination will not
>> work but this can be supported as well after some rework on the DPU side.
>>
>> In addition, SST was re-validated with all these changes to ensure there
>> were no regressions.
>>
>> This patch series was made on top of:
>>
>> [1] : https://patchwork.freedesktop.org/series/151522/ (v5 to fix up HPD)
>>
>> Overall, the patch series has been organized in the following way:
>>
>> 1) First set are a couple of fixes made while debugging MST but applicable
>> to SST as well so go ahead of everything else
>> 2) Prepare the DP driver to get ready to handle multiple streams. This is the bulk
>> of the work as current DP driver design had to be adjusted to make this happen.
>> 3) Finally, new files to handle MST related operations
> 
> General suggestion. Please reorg the series into the more logical
> chunks. If you are touching enable / disable path, then continue doing
> that until its done (more or less). I'd really like to be able to merge
> at least a part of the series in the next cycle, but there needs to be a
> logical "halfway done" moment. Let's define it at the "all paths are
> refactored, all booleans are in place, we are ready to get MST parts".
> 
> I've not found a use for separate bridges in the MST path. Please split
> the functionality between the MST connector and, maybe, another private
> object for generic state (like connector -> encoder mapping). Other
> drivers don't have this issue because they have fixed CRTC -> encoder
> mapping. MSM doesn't so we need a separate way to store that. It's sad
> that we can't subclass MST topology manager (or its state). Maybe it
> would be worth changing that and letting our topology manager handle the
> assignment in it.
> 
> Also, I found a set of warnings while trying to build the code. Please
> make sure that there are no warnings.
> 
Got it, thanks...
Does this mean I can send a subset first (rebase on latest linux-next 
and ), for example the first twelve patches? They are mainly clean-up 
changes and do not touch the MST part yet.
>> Note:
>> Validation for this series has so far been done on the latest linux-next
>> on LeMans, covering both FB console and Weston.
> 
> It wasn't. I couldn't apply it to the latest linux-next. There were
> fuzz-based rejections because of one of the patches merged some time
> ago.
> 
Will rebase it.>>
>> Broader validation, including additional Type-C DP use cases, is still
>> in progress and may lead to some follow-up adjustments in the next
>> revision. I wanted to post the current version first to collect early
>> feedback on the overall approach.
> 
> It's nice, but please keep in mind that majority of users use Type-C
> rather than native DP connectors. In some cases your lack of Type-C
> testing affects the design (e.g. for the HPD handling).
> 
>>
>> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
> 


^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 16/39] drm/msm/dp: use stream_id to change offsets in dp_catalog
  2026-04-11 18:12   ` Dmitry Baryshkov
@ 2026-05-25  8:06     ` Yongxing Mou
  2026-05-25  8:21       ` Dmitry Baryshkov
  0 siblings, 1 reply; 128+ messages in thread
From: Yongxing Mou @ 2026-05-25  8:06 UTC (permalink / raw)
  To: Dmitry Baryshkov
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar



On 4/12/2026 2:12 AM, Dmitry Baryshkov wrote:
> On Fri, Apr 10, 2026 at 05:33:51PM +0800, Yongxing Mou wrote:
>> From: Abhinav Kumar <quic_abhinavk@quicinc.com>
>>
>> Use the dp_panel's stream_id to adjust the offsets for stream 1 which will
>> be used for MST in the dp_catalog.
> 
> Please start by describing the problem.
>
Got it, thanks.
>> Stream 1 share the same link clk with
>> stream 0 with different reg offset. Also add additional register defines
>> for stream 1.
>>
>> Streams 2 and 3 are not covered here, as they use separate link clocks and
>> require separate handling.
>>
>> Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
>> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
>> ---
>>   drivers/gpu/drm/msm/dp/dp_ctrl.c  | 24 ++++++++++---
>>   drivers/gpu/drm/msm/dp/dp_panel.c | 72 +++++++++++++++++++++++++++------------
>>   drivers/gpu/drm/msm/dp/dp_reg.h   | 11 ++++++
>>   3 files changed, 81 insertions(+), 26 deletions(-)
>>
>> diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c
>> index 1e80d6fc7bda..a52bcd9ea2a3 100644
>> --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c
>> +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c
>> @@ -393,6 +393,7 @@ static void msm_dp_ctrl_config_ctrl_streams(struct msm_dp_ctrl_private *ctrl,
>>   					    struct msm_dp_panel *msm_dp_panel)
>>   {
>>   	u32 config = 0, tbd;
>> +	u32 reg_offset = 0;
>>   
>>   	config = msm_dp_read_link(ctrl, REG_DP_CONFIGURATION_CTRL);
>>   
>> @@ -409,7 +410,8 @@ static void msm_dp_ctrl_config_ctrl_streams(struct msm_dp_ctrl_private *ctrl,
>>   
>>   	drm_dbg_dp(ctrl->drm_dev, "stream DP_CONFIGURATION_CTRL=0x%x\n", config);
>>   
>> -	msm_dp_write_link(ctrl, REG_DP_CONFIGURATION_CTRL, config);
>> +	if (msm_dp_panel->stream_id == DP_STREAM_1)
>> +		reg_offset = REG_DP1_CONFIGURATION_CTRL - REG_DP_CONFIGURATION_CTRL;
> 
> Where is the actual write?
> 
Sorry, here is a mistake. will fix it next version.
>>   }
>>   
>>   static void msm_dp_ctrl_config_ctrl_link(struct msm_dp_ctrl_private *ctrl)
>> @@ -460,12 +462,16 @@ static void msm_dp_ctrl_config_misc1_misc0(struct msm_dp_ctrl_private *ctrl,
>>   					   struct msm_dp_panel *msm_dp_panel)
>>   {
>>   	u32 colorimetry_cfg, test_bits_depth, misc_val;
>> +	u32 reg_offset = 0;
>>   
>>   	test_bits_depth = msm_dp_link_get_test_bits_depth(ctrl->link,
>>   							  msm_dp_panel->msm_dp_mode.bpp);
>>   	colorimetry_cfg = msm_dp_link_get_colorimetry_config(ctrl->link);
>>   
>> -	misc_val = msm_dp_read_link(ctrl, REG_DP_MISC1_MISC0);
>> +	if (msm_dp_panel->stream_id == DP_STREAM_1)
>> +		reg_offset = REG_DP1_MISC1_MISC0 - REG_DP_MISC1_MISC0;
>> +
>> +	misc_val = msm_dp_read_link(ctrl, REG_DP_MISC1_MISC0 + reg_offset);
> 
> This quickly becomes unreadable. I'd rather have something like:
> 
> reg_addr = (stream == DP_STREAM_1) ? REG_DP1_MISC1_MISC0 : REG_DP_MISC1_MISC0;
> misc_val = msm_dp_read_link(ctrl, reg_addr);
> 
Will discuss next patch.
> 
>>   
>>   	/* clear bpp bits */
>>   	misc_val &= ~(0x07 << DP_MISC0_TEST_BITS_DEPTH_SHIFT);
>> @@ -475,7 +481,7 @@ static void msm_dp_ctrl_config_misc1_misc0(struct msm_dp_ctrl_private *ctrl,
>>   	misc_val |= DP_MISC0_SYNCHRONOUS_CLK;
>>   
>>   	drm_dbg_dp(ctrl->drm_dev, "misc settings = 0x%x\n", misc_val);
>> -	msm_dp_write_link(ctrl, REG_DP_MISC1_MISC0, misc_val);
>> +	msm_dp_write_link(ctrl, REG_DP_MISC1_MISC0 + reg_offset, misc_val);
>>   }
>>   
>>   static void msm_dp_ctrl_configure_source_params(struct msm_dp_ctrl_private *ctrl,
>> @@ -2446,6 +2452,7 @@ static int msm_dp_ctrl_link_retrain(struct msm_dp_ctrl_private *ctrl)
>>   }
>>   
>>   static void msm_dp_ctrl_config_msa(struct msm_dp_ctrl_private *ctrl,
>> +			       struct msm_dp_panel *msm_dp_panel,
>>   			       u32 rate, u32 stream_rate_khz,
>>   			       bool is_ycbcr_420)
>>   {
>> @@ -2455,6 +2462,12 @@ static void msm_dp_ctrl_config_msa(struct msm_dp_ctrl_private *ctrl,
>>   	u32 const link_rate_hbr2 = 540000;
>>   	u32 const link_rate_hbr3 = 810000;
>>   	unsigned long den, num;
>> +	u32 mvid_reg_off = 0, nvid_reg_off = 0;
>> +
>> +	if (msm_dp_panel->stream_id == DP_STREAM_1) {
>> +		mvid_reg_off = REG_DP1_SOFTWARE_MVID - REG_DP_SOFTWARE_MVID;
>> +		nvid_reg_off = REG_DP1_SOFTWARE_NVID - REG_DP_SOFTWARE_NVID;
>> +	}
>>   
>>   	switch (rate) {
>>   	case link_rate_hbr3:
>> @@ -2509,8 +2522,8 @@ static void msm_dp_ctrl_config_msa(struct msm_dp_ctrl_private *ctrl,
>>   		nvid *= 3;
>>   
>>   	drm_dbg_dp(ctrl->drm_dev, "mvid=0x%x, nvid=0x%x\n", mvid, nvid);
>> -	msm_dp_write_link(ctrl, REG_DP_SOFTWARE_MVID, mvid);
>> -	msm_dp_write_link(ctrl, REG_DP_SOFTWARE_NVID, nvid);
>> +	msm_dp_write_link(ctrl, REG_DP_SOFTWARE_MVID + mvid_reg_off, mvid);
>> +	msm_dp_write_link(ctrl, REG_DP_SOFTWARE_NVID + nvid_reg_off, nvid);
>>   }
>>   
>>   int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train)
>> @@ -2585,6 +2598,7 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *
>>   	msm_dp_ctrl_configure_source_params(ctrl, msm_dp_panel);
>>   
>>   	msm_dp_ctrl_config_msa(ctrl,
>> +		msm_dp_panel,
>>   		ctrl->link->link_params.rate,
>>   		pixel_rate_orig,
>>   		msm_dp_panel->msm_dp_mode.out_fmt_is_yuv_420);
>> diff --git a/drivers/gpu/drm/msm/dp/dp_panel.c b/drivers/gpu/drm/msm/dp/dp_panel.c
>> index c17b87353d1a..6c88cc7e3037 100644
>> --- a/drivers/gpu/drm/msm/dp/dp_panel.c
>> +++ b/drivers/gpu/drm/msm/dp/dp_panel.c
>> @@ -447,27 +447,35 @@ static void msm_dp_panel_send_vsc_sdp(struct msm_dp_panel_private *panel, struct
>>   	u32 header[2];
>>   	u32 val;
>>   	int i;
>> +	u32 offset = 0;
>> +
>> +	if (panel->msm_dp_panel.stream_id == DP_STREAM_1)
>> +		offset = MMSS_DP1_GENERIC0_0 - MMSS_DP_GENERIC0_0;
>>   
>>   	msm_dp_utils_pack_sdp_header(&vsc_sdp->sdp_header, header);
>>   
>> -	msm_dp_write_link(panel, MMSS_DP_GENERIC0_0, header[0]);
>> -	msm_dp_write_link(panel, MMSS_DP_GENERIC0_1, header[1]);
>> +	msm_dp_write_link(panel, MMSS_DP_GENERIC0_0 + offset, header[0]);
>> +	msm_dp_write_link(panel, MMSS_DP_GENERIC0_1 + offset, header[1]);
>>   
>>   	for (i = 0; i < sizeof(vsc_sdp->db); i += 4) {
>>   		val = ((vsc_sdp->db[i]) | (vsc_sdp->db[i + 1] << 8) | (vsc_sdp->db[i + 2] << 16) |
>>   		       (vsc_sdp->db[i + 3] << 24));
>> -		msm_dp_write_link(panel, MMSS_DP_GENERIC0_2 + i, val);
>> +		msm_dp_write_link(panel, MMSS_DP_GENERIC0_2 + i + offset, val);
>>   	}
>>   }
>>   
>>   static void msm_dp_panel_update_sdp(struct msm_dp_panel_private *panel)
>>   {
>>   	u32 hw_revision = panel->msm_dp_panel.hw_revision;
>> +	u32 offset = 0;
>> +
>> +	if (panel->msm_dp_panel.stream_id == DP_STREAM_1)
>> +		offset = MMSS_DP1_SDP_CFG3 - MMSS_DP_SDP_CFG3;
>>   
>>   	if (hw_revision >= DP_HW_VERSION_1_0 &&
>>   	    hw_revision < DP_HW_VERSION_1_2) {
>> -		msm_dp_write_link(panel, MMSS_DP_SDP_CFG3, UPDATE_SDP);
>> -		msm_dp_write_link(panel, MMSS_DP_SDP_CFG3, 0x0);
>> +		msm_dp_write_link(panel, MMSS_DP_SDP_CFG3 + offset, UPDATE_SDP);
>> +		msm_dp_write_link(panel, MMSS_DP_SDP_CFG3 + offset, 0x0);
>>   	}
>>   }
>>   
>> @@ -476,16 +484,25 @@ void msm_dp_panel_enable_vsc_sdp(struct msm_dp_panel *msm_dp_panel, struct dp_sd
>>   	struct msm_dp_panel_private *panel =
>>   		container_of(msm_dp_panel, struct msm_dp_panel_private, msm_dp_panel);
>>   	u32 cfg, cfg2, misc;
>> +	u32 misc_reg_offset = 0;
>> +	u32 sdp_cfg_offset = 0;
>> +	u32 sdp_cfg2_offset = 0;
>> +
>> +	if (msm_dp_panel->stream_id == DP_STREAM_1) {
>> +		misc_reg_offset = REG_DP1_MISC1_MISC0 - REG_DP_MISC1_MISC0;
>> +		sdp_cfg_offset = MMSS_DP1_SDP_CFG - MMSS_DP_SDP_CFG;
>> +		sdp_cfg2_offset = MMSS_DP1_SDP_CFG2 - MMSS_DP_SDP_CFG2;
>> +	}
>>   
>> -	cfg = msm_dp_read_link(panel, MMSS_DP_SDP_CFG);
>> -	cfg2 = msm_dp_read_link(panel, MMSS_DP_SDP_CFG2);
>> -	misc = msm_dp_read_link(panel, REG_DP_MISC1_MISC0);
>> +	cfg = msm_dp_read_link(panel, MMSS_DP_SDP_CFG + sdp_cfg_offset);
>> +	cfg2 = msm_dp_read_link(panel, MMSS_DP_SDP_CFG2 + sdp_cfg2_offset);
>> +	misc = msm_dp_read_link(panel, REG_DP_MISC1_MISC0 + misc_reg_offset);
>>   
>>   	cfg |= GEN0_SDP_EN;
>> -	msm_dp_write_link(panel, MMSS_DP_SDP_CFG, cfg);
>> +	msm_dp_write_link(panel, MMSS_DP_SDP_CFG + sdp_cfg_offset, cfg);
>>   
>>   	cfg2 |= GENERIC0_SDPSIZE_VALID;
>> -	msm_dp_write_link(panel, MMSS_DP_SDP_CFG2, cfg2);
>> +	msm_dp_write_link(panel, MMSS_DP_SDP_CFG2 + sdp_cfg2_offset, cfg2);
>>   
>>   	msm_dp_panel_send_vsc_sdp(panel, vsc_sdp);
>>   
>> @@ -495,7 +512,7 @@ void msm_dp_panel_enable_vsc_sdp(struct msm_dp_panel *msm_dp_panel, struct dp_sd
>>   	drm_dbg_dp(panel->drm_dev, "vsc sdp enable=1\n");
>>   
>>   	pr_debug("misc settings = 0x%x\n", misc);
>> -	msm_dp_write_link(panel, REG_DP_MISC1_MISC0, misc);
>> +	msm_dp_write_link(panel, REG_DP_MISC1_MISC0 + misc_reg_offset, misc);
>>   
>>   	msm_dp_panel_update_sdp(panel);
>>   }
>> @@ -505,16 +522,25 @@ void msm_dp_panel_disable_vsc_sdp(struct msm_dp_panel *msm_dp_panel)
>>   	struct msm_dp_panel_private *panel =
>>   		container_of(msm_dp_panel, struct msm_dp_panel_private, msm_dp_panel);
>>   	u32 cfg, cfg2, misc;
>> +	u32 misc_reg_offset = 0;
>> +	u32 sdp_cfg_offset = 0;
>> +	u32 sdp_cfg2_offset = 0;
>> +
>> +	if (msm_dp_panel->stream_id == DP_STREAM_1) {
>> +		misc_reg_offset = REG_DP1_MISC1_MISC0 - REG_DP_MISC1_MISC0;
>> +		sdp_cfg_offset = MMSS_DP1_SDP_CFG - MMSS_DP_SDP_CFG;
>> +		sdp_cfg2_offset = MMSS_DP1_SDP_CFG2 - MMSS_DP_SDP_CFG2;
>> +	}
>>   
>> -	cfg = msm_dp_read_link(panel, MMSS_DP_SDP_CFG);
>> -	cfg2 = msm_dp_read_link(panel, MMSS_DP_SDP_CFG2);
>> -	misc = msm_dp_read_link(panel, REG_DP_MISC1_MISC0);
>> +	cfg = msm_dp_read_link(panel, MMSS_DP_SDP_CFG + sdp_cfg_offset);
>> +	cfg2 = msm_dp_read_link(panel, MMSS_DP_SDP_CFG2 + sdp_cfg2_offset);
>> +	misc = msm_dp_read_link(panel, REG_DP_MISC1_MISC0 + misc_reg_offset);
>>   
>>   	cfg &= ~GEN0_SDP_EN;
>> -	msm_dp_write_link(panel, MMSS_DP_SDP_CFG, cfg);
>> +	msm_dp_write_link(panel, MMSS_DP_SDP_CFG + sdp_cfg_offset, cfg);
>>   
>>   	cfg2 &= ~GENERIC0_SDPSIZE_VALID;
>> -	msm_dp_write_link(panel, MMSS_DP_SDP_CFG2, cfg2);
>> +	msm_dp_write_link(panel, MMSS_DP_SDP_CFG2 + sdp_cfg2_offset, cfg2);
>>   
>>   	/* switch back to MSA */
>>   	misc &= ~DP_MISC1_VSC_SDP;
>> @@ -522,7 +548,7 @@ void msm_dp_panel_disable_vsc_sdp(struct msm_dp_panel *msm_dp_panel)
>>   	drm_dbg_dp(panel->drm_dev, "vsc sdp enable=0\n");
>>   
>>   	pr_debug("misc settings = 0x%x\n", misc);
>> -	msm_dp_write_link(panel, REG_DP_MISC1_MISC0, misc);
>> +	msm_dp_write_link(panel, REG_DP_MISC1_MISC0 + misc_reg_offset, misc);
>>   
>>   	msm_dp_panel_update_sdp(panel);
>>   }
>> @@ -580,6 +606,7 @@ int msm_dp_panel_timing_cfg(struct msm_dp_panel *msm_dp_panel, bool wide_bus_en)
>>   	u32 msm_dp_active;
>>   	u32 total;
>>   	u32 reg;
>> +	u32 offset = 0;
>>   
>>   	panel = container_of(msm_dp_panel, struct msm_dp_panel_private, msm_dp_panel);
>>   	drm_mode = &panel->msm_dp_panel.msm_dp_mode.drm_mode;
>> @@ -594,6 +621,9 @@ int msm_dp_panel_timing_cfg(struct msm_dp_panel *msm_dp_panel, bool wide_bus_en)
>>   		drm_mode->vsync_start - drm_mode->vdisplay,
>>   		drm_mode->vsync_end - drm_mode->vsync_start);
>>   
>> +	if (msm_dp_panel->stream_id == DP_STREAM_1)
>> +		offset = REG_DP1_TOTAL_HOR_VER - REG_DP_TOTAL_HOR_VER;
>> +
>>   	total_hor = drm_mode->htotal;
>>   
>>   	total_ver = drm_mode->vtotal;
>> @@ -624,10 +654,10 @@ int msm_dp_panel_timing_cfg(struct msm_dp_panel *msm_dp_panel, bool wide_bus_en)
>>   
>>   	msm_dp_active = data;
>>   
>> -	msm_dp_write_link(panel, REG_DP_TOTAL_HOR_VER, total);
>> -	msm_dp_write_link(panel, REG_DP_START_HOR_VER_FROM_SYNC, sync_start);
>> -	msm_dp_write_link(panel, REG_DP_HSYNC_VSYNC_WIDTH_POLARITY, width_blanking);
>> -	msm_dp_write_link(panel, REG_DP_ACTIVE_HOR_VER, msm_dp_active);
>> +	msm_dp_write_link(panel, REG_DP_TOTAL_HOR_VER + offset, total);
>> +	msm_dp_write_link(panel, REG_DP_START_HOR_VER_FROM_SYNC  + offset, sync_start);
>> +	msm_dp_write_link(panel, REG_DP_HSYNC_VSYNC_WIDTH_POLARITY + offset, width_blanking);
>> +	msm_dp_write_link(panel, REG_DP_ACTIVE_HOR_VER  + offset, msm_dp_active);
>>   
>>   	reg = msm_dp_read_pn(panel, MMSS_DP_INTF_CONFIG);
>>   	if (wide_bus_en)
>> diff --git a/drivers/gpu/drm/msm/dp/dp_reg.h b/drivers/gpu/drm/msm/dp/dp_reg.h
>> index 3689642b7fc0..295c1161e6b7 100644
>> --- a/drivers/gpu/drm/msm/dp/dp_reg.h
>> +++ b/drivers/gpu/drm/msm/dp/dp_reg.h
>> @@ -332,6 +332,17 @@
>>   #define DP_TPG_VIDEO_CONFIG_BPP_8BIT		(0x00000001)
>>   #define DP_TPG_VIDEO_CONFIG_RGB			(0x00000004)
>>   
>> +/* DP MST registers */
> 
> Which register spaces are they from?
> 
Here also from DPTX_*, what about this? /* DP_TX MST registers */
>> +#define REG_DP1_CONFIGURATION_CTRL		(0x00000400)
>> +#define REG_DP1_SOFTWARE_MVID			(0x00000414)
>> +#define REG_DP1_SOFTWARE_NVID			(0x00000418)
>> +#define REG_DP1_TOTAL_HOR_VER			(0x0000041C)
>> +#define REG_DP1_MISC1_MISC0			(0x0000042C)
>> +#define MMSS_DP1_GENERIC0_0			(0x00000490)
>> +#define MMSS_DP1_SDP_CFG			(0x000004E0)
>> +#define MMSS_DP1_SDP_CFG2			(0x000004E4)
>> +#define MMSS_DP1_SDP_CFG3			(0x000004E8)
>> +
>>   #define MMSS_DP_ASYNC_FIFO_CONFIG		(0x00000088)
>>   
>>   #define REG_DP_PHY_AUX_INTERRUPT_CLEAR          (0x0000004C)
>>
>> -- 
>> 2.43.0
>>
> 


^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 17/39] drm/msm/dp: Add catalog support for 3rd/4th stream MST
  2026-04-11 18:24   ` Dmitry Baryshkov
@ 2026-05-25  8:06     ` Yongxing Mou
  2026-05-25  8:25       ` Dmitry Baryshkov
  0 siblings, 1 reply; 128+ messages in thread
From: Yongxing Mou @ 2026-05-25  8:06 UTC (permalink / raw)
  To: Dmitry Baryshkov
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel



On 4/12/2026 2:24 AM, Dmitry Baryshkov wrote:
> Nit, there is no catalog anymore, please fix the subject line.
> 
> On Fri, Apr 10, 2026 at 05:33:52PM +0800, Yongxing Mou wrote:
>> To support 4-stream MST, the link clocks for stream 3 and stream 4 are
>> controlled by MST_2_LCLK and MST_3_LCLK which share the same register
>> definitions but use different base addresses.
>>
>> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
>> ---
>>   drivers/gpu/drm/msm/dp/dp_ctrl.c    | 225 ++++++++++++++++++++++--------------
>>   drivers/gpu/drm/msm/dp/dp_ctrl.h    |   4 +-
>>   drivers/gpu/drm/msm/dp/dp_display.c |  24 +++-
>>   drivers/gpu/drm/msm/dp/dp_panel.c   | 135 +++++++++++++++++-----
>>   drivers/gpu/drm/msm/dp/dp_panel.h   |   2 +
>>   drivers/gpu/drm/msm/dp/dp_reg.h     |  16 ++-
>>   6 files changed, 283 insertions(+), 123 deletions(-)
>>
>> diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c
>> index a52bcd9ea2a3..1109b2df21be 100644
>> --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c
>> +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c
>> @@ -118,6 +118,8 @@ struct msm_dp_ctrl_private {
>>   	struct msm_dp_link *link;
>>   	void __iomem *ahb_base;
>>   	void __iomem *link_base;
>> +	void __iomem *mst2link_base;
>> +	void __iomem *mst3link_base;
>>   
>>   	struct phy *phy;
>>   
>> @@ -158,19 +160,45 @@ static inline void msm_dp_write_ahb(struct msm_dp_ctrl_private *ctrl,
>>   	writel(data, ctrl->ahb_base + offset);
>>   }
>>   
>> -static inline u32 msm_dp_read_link(struct msm_dp_ctrl_private *ctrl, u32 offset)
>> +static inline u32 msm_dp_read_link(struct msm_dp_ctrl_private *ctrl,
>> +				   enum msm_dp_stream_id stream_id, u32 offset)
>>   {
>> -	return readl_relaxed(ctrl->link_base + offset);
>> +	switch (stream_id) {
>> +	case DP_STREAM_0:
>> +	case DP_STREAM_1:
> 
> Continuing the comment from the previous patch:
> 
> Or, can we remap all offsets here? It would be much, much easier to
> always pass REG_DP_foo and then here, in the link reading code, remap
> the offsets for DP_STREAM_1.
> 
The stream 1 offsets relative to stream 0 are not uniform:
     REG_DP_CONFIGURATION_CTRL  0x008 -> 0x400  (diff 0x3F8)
     REG_DP_MISC1_MISC0         0x02C -> 0x42C  (diff 0x400)
  So remapping inside msm_dp_read/write_link would require a full lookup
  table, which seems not help for current status.

Can we introduce a small helper msm_dp_stream_reg() that selects the 
correct register address at the call site:
//dp_panel.h
static inline u32 msm_dp_stream_reg(enum msm_dp_stream_id id,
				     u32 reg_s0, u32 reg_s1, u32 reg_mst)
{
	if (id > DP_STREAM_1)
		return reg_mst;
	return id == DP_STREAM_1 ? reg_s1 : reg_s0;
}

//dp_panel.c and dp_ctrl.c
       u32 reg = msm_dp_stream_reg(stream_id,
                                   REG_DP_MISC1_MISC0,
                                   REG_DP1_MISC1_MISC0,
                                   REG_DP_MSTLINK_MISC1_MISC0);
       msm_dp_read_link(ctrl, stream_id, reg);
>> +		return readl_relaxed(ctrl->link_base + offset);
>> +	case DP_STREAM_2:
>> +		return readl_relaxed(ctrl->mst2link_base + offset);
>> +	case DP_STREAM_3:
>> +		return readl_relaxed(ctrl->mst3link_base + offset);
>> +	default:
>> +		DRM_ERROR("error stream_id\n");
>> +		return 0;
>> +	}
>>   }
>>   
>>   static inline void msm_dp_write_link(struct msm_dp_ctrl_private *ctrl,
>> -			       u32 offset, u32 data)
>> +				     enum msm_dp_stream_id stream_id, u32 offset, u32 data)
>>   {
>>   	/*
>>   	 * To make sure link reg writes happens before any other operation,
>>   	 * this function uses writel() instread of writel_relaxed()
>>   	 */
>> -	writel(data, ctrl->link_base + offset);
>> +	switch (stream_id) {
>> +	case DP_STREAM_0:
>> +	case DP_STREAM_1:
>> +		writel(data, ctrl->link_base + offset);
>> +		break;
>> +	case DP_STREAM_2:
>> +		writel(data, ctrl->mst2link_base + offset);
>> +		break;
>> +	case DP_STREAM_3:
>> +		writel(data, ctrl->mst3link_base + offset);
>> +		break;
>> +	default:
>> +		DRM_ERROR("error stream_id\n");
>> +		break;
>> +	}
>>   }
>>   
>>   static int msm_dp_aux_link_configure(struct drm_dp_aux *aux,
>> @@ -294,18 +322,18 @@ static void msm_dp_ctrl_psr_mainlink_enable(struct msm_dp_ctrl_private *ctrl)
>>   {
>>   	u32 val;
>>   
>> -	val = msm_dp_read_link(ctrl, REG_DP_MAINLINK_CTRL);
>> +	val = msm_dp_read_link(ctrl, 0, REG_DP_MAINLINK_CTRL);
>>   	val |= DP_MAINLINK_CTRL_ENABLE;
>> -	msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, val);
>> +	msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_CTRL, val);
> 
> Are those always using the link0 base? If so, can we keep the existing
> API for the stream-independent registers and introduce a separate API
> call for the stream-dependent calls? Generally, if you keep the existing
> function, I think, it becomes easier to squash this and the previous
> patches.
> 
Here will always using the link0 base. After introducing the helper 
func, will squash this patch into the previous one.
>>   }
>>   
>>   static void msm_dp_ctrl_psr_mainlink_disable(struct msm_dp_ctrl_private *ctrl)
>>   {
>>   	u32 val;
>>   
>> -	val = msm_dp_read_link(ctrl, REG_DP_MAINLINK_CTRL);
>> +	val = msm_dp_read_link(ctrl, 0, REG_DP_MAINLINK_CTRL);
>>   	val &= ~DP_MAINLINK_CTRL_ENABLE;
>> -	msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, val);
>> +	msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_CTRL, val);
>>   }
>>   
>>   static void msm_dp_ctrl_mainlink_enable(struct msm_dp_ctrl_private *ctrl)
>> @@ -314,21 +342,21 @@ static void msm_dp_ctrl_mainlink_enable(struct msm_dp_ctrl_private *ctrl)
>>   
>>   	drm_dbg_dp(ctrl->drm_dev, "enable\n");
>>   
>> -	mainlink_ctrl = msm_dp_read_link(ctrl, REG_DP_MAINLINK_CTRL);
>> +	mainlink_ctrl = msm_dp_read_link(ctrl, 0, REG_DP_MAINLINK_CTRL);
>>   
>>   	mainlink_ctrl &= ~(DP_MAINLINK_CTRL_RESET |
>>   					DP_MAINLINK_CTRL_ENABLE);
>> -	msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
>> +	msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
>>   
>>   	mainlink_ctrl |= DP_MAINLINK_CTRL_RESET;
>> -	msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
>> +	msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
>>   
>>   	mainlink_ctrl &= ~DP_MAINLINK_CTRL_RESET;
>> -	msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
>> +	msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
>>   
>>   	mainlink_ctrl |= (DP_MAINLINK_CTRL_ENABLE |
>>   				DP_MAINLINK_FB_BOUNDARY_SEL);
>> -	msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
>> +	msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
>>   }
>>   
>>   static void msm_dp_ctrl_mainlink_disable(struct msm_dp_ctrl_private *ctrl)
>> @@ -337,23 +365,23 @@ static void msm_dp_ctrl_mainlink_disable(struct msm_dp_ctrl_private *ctrl)
>>   
>>   	drm_dbg_dp(ctrl->drm_dev, "disable\n");
>>   
>> -	mainlink_ctrl = msm_dp_read_link(ctrl, REG_DP_MAINLINK_CTRL);
>> +	mainlink_ctrl = msm_dp_read_link(ctrl, 0, REG_DP_MAINLINK_CTRL);
>>   	mainlink_ctrl &= ~DP_MAINLINK_CTRL_ENABLE;
>> -	msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
>> +	msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
>>   }
>>   
>>   static void msm_dp_setup_peripheral_flush(struct msm_dp_ctrl_private *ctrl)
>>   {
>>   	u32 mainlink_ctrl;
>>   
>> -	mainlink_ctrl = msm_dp_read_link(ctrl, REG_DP_MAINLINK_CTRL);
>> +	mainlink_ctrl = msm_dp_read_link(ctrl, 0, REG_DP_MAINLINK_CTRL);
>>   
>>   	if (ctrl->hw_revision >= DP_HW_VERSION_1_2)
>>   		mainlink_ctrl |= DP_MAINLINK_FLUSH_MODE_SDE_PERIPH_UPDATE;
>>   	else
>>   		mainlink_ctrl |= DP_MAINLINK_FLUSH_MODE_UPDATE_SDP;
>>   
>> -	msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
>> +	msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
>>   }
>>   
>>   static bool msm_dp_ctrl_mainlink_ready(struct msm_dp_ctrl_private *ctrl)
>> @@ -380,7 +408,7 @@ void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl)
>>   	ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl);
>>   
>>   	reinit_completion(&ctrl->idle_comp);
>> -	msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, DP_STATE_CTRL_PUSH_IDLE);
>> +	msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, DP_STATE_CTRL_PUSH_IDLE);
>>   
>>   	if (!wait_for_completion_timeout(&ctrl->idle_comp,
>>   			IDLE_PATTERN_COMPLETION_TIMEOUT_JIFFIES))
>> @@ -395,7 +423,11 @@ static void msm_dp_ctrl_config_ctrl_streams(struct msm_dp_ctrl_private *ctrl,
>>   	u32 config = 0, tbd;
>>   	u32 reg_offset = 0;
>>   
>> -	config = msm_dp_read_link(ctrl, REG_DP_CONFIGURATION_CTRL);
>> +	if (msm_dp_panel->stream_id == DP_STREAM_0)
>> +		config = msm_dp_read_link(ctrl, 0, REG_DP_CONFIGURATION_CTRL);
>> +
>> +	if (msm_dp_panel->stream_id == DP_STREAM_1)
>> +		reg_offset = REG_DP1_CONFIGURATION_CTRL - REG_DP_CONFIGURATION_CTRL;
>>   
>>   	if (msm_dp_panel->msm_dp_mode.out_fmt_is_yuv_420)
>>   		config |= DP_CONFIGURATION_CTRL_RGB_YUV; /* YUV420 */
>> @@ -410,8 +442,10 @@ static void msm_dp_ctrl_config_ctrl_streams(struct msm_dp_ctrl_private *ctrl,
>>   
>>   	drm_dbg_dp(ctrl->drm_dev, "stream DP_CONFIGURATION_CTRL=0x%x\n", config);
>>   
>> -	if (msm_dp_panel->stream_id == DP_STREAM_1)
>> -		reg_offset = REG_DP1_CONFIGURATION_CTRL - REG_DP_CONFIGURATION_CTRL;
>> +	msm_dp_write_link(ctrl, msm_dp_panel->stream_id, msm_dp_panel->stream_id > 1 ?
>> +			  REG_DP_MSTLINK_CONFIGURATION_CTRL :
>> +			  REG_DP_CONFIGURATION_CTRL + reg_offset, config);
>> +
> 
> Yep, it would definitely help to handle these differences in the link
> writing code.
> 
>>   }
>>   
>>   static void msm_dp_ctrl_config_ctrl_link(struct msm_dp_ctrl_private *ctrl)
>> @@ -441,7 +475,7 @@ static void msm_dp_ctrl_config_ctrl_link(struct msm_dp_ctrl_private *ctrl)
>>   
>>   	drm_dbg_dp(ctrl->drm_dev, "link DP_CONFIGURATION_CTRL=0x%x\n", config);
>>   
>> -	msm_dp_write_link(ctrl, REG_DP_CONFIGURATION_CTRL, config);
>> +	msm_dp_write_link(ctrl, 0, REG_DP_CONFIGURATION_CTRL, config);
>>   }
>>   
>>   static void msm_dp_ctrl_lane_mapping(struct msm_dp_ctrl_private *ctrl)
>> @@ -454,8 +488,8 @@ static void msm_dp_ctrl_lane_mapping(struct msm_dp_ctrl_private *ctrl)
>>   	ln_mapping |= lane_map[2] << LANE2_MAPPING_SHIFT;
>>   	ln_mapping |= lane_map[3] << LANE3_MAPPING_SHIFT;
>>   
>> -	msm_dp_write_link(ctrl, REG_DP_LOGICAL2PHYSICAL_LANE_MAPPING,
>> -			ln_mapping);
>> +	msm_dp_write_link(ctrl, 0, REG_DP_LOGICAL2PHYSICAL_LANE_MAPPING,
>> +			  ln_mapping);
>>   }
>>   
>>   static void msm_dp_ctrl_config_misc1_misc0(struct msm_dp_ctrl_private *ctrl,
>> @@ -471,7 +505,8 @@ static void msm_dp_ctrl_config_misc1_misc0(struct msm_dp_ctrl_private *ctrl,
>>   	if (msm_dp_panel->stream_id == DP_STREAM_1)
>>   		reg_offset = REG_DP1_MISC1_MISC0 - REG_DP_MISC1_MISC0;
>>   
>> -	misc_val = msm_dp_read_link(ctrl, REG_DP_MISC1_MISC0 + reg_offset);
>> +	misc_val = msm_dp_read_link(ctrl, msm_dp_panel->stream_id, msm_dp_panel->stream_id > 1 ?
>> +				    REG_DP_MSTLINK_MISC1_MISC0 : REG_DP_MISC1_MISC0 + reg_offset);
>>   
>>   	/* clear bpp bits */
>>   	misc_val &= ~(0x07 << DP_MISC0_TEST_BITS_DEPTH_SHIFT);
>> @@ -481,7 +516,10 @@ static void msm_dp_ctrl_config_misc1_misc0(struct msm_dp_ctrl_private *ctrl,
>>   	misc_val |= DP_MISC0_SYNCHRONOUS_CLK;
>>   
>>   	drm_dbg_dp(ctrl->drm_dev, "misc settings = 0x%x\n", misc_val);
>> -	msm_dp_write_link(ctrl, REG_DP_MISC1_MISC0 + reg_offset, misc_val);
>> +	msm_dp_write_link(ctrl, msm_dp_panel->stream_id,
>> +			  msm_dp_panel->stream_id > 1 ?
>> +			  REG_DP_MSTLINK_MISC1_MISC0 : REG_DP_MISC1_MISC0 + reg_offset,
>> +			  misc_val);
>>   }
>>   
>>   static void msm_dp_ctrl_configure_source_params(struct msm_dp_ctrl_private *ctrl,
>> @@ -1307,9 +1345,9 @@ static void msm_dp_ctrl_setup_tr_unit(struct msm_dp_ctrl_private *ctrl)
>>   	pr_debug("dp_tu=0x%x, valid_boundary=0x%x, valid_boundary2=0x%x\n",
>>   			msm_dp_tu, valid_boundary, valid_boundary2);
>>   
>> -	msm_dp_write_link(ctrl, REG_DP_VALID_BOUNDARY, valid_boundary);
>> -	msm_dp_write_link(ctrl, REG_DP_TU, msm_dp_tu);
>> -	msm_dp_write_link(ctrl, REG_DP_VALID_BOUNDARY_2, valid_boundary2);
>> +	msm_dp_write_link(ctrl, 0, REG_DP_VALID_BOUNDARY, valid_boundary);
>> +	msm_dp_write_link(ctrl, 0, REG_DP_TU, msm_dp_tu);
>> +	msm_dp_write_link(ctrl, 0, REG_DP_VALID_BOUNDARY_2, valid_boundary2);
>>   }
>>   
>>   static int msm_dp_ctrl_wait4video_ready(struct msm_dp_ctrl_private *ctrl)
>> @@ -1426,7 +1464,7 @@ static int msm_dp_ctrl_set_pattern_state_bit(struct msm_dp_ctrl_private *ctrl,
>>   
>>   	bit = BIT(state_bit - 1);
>>   	drm_dbg_dp(ctrl->drm_dev, "hw: bit=%d train=%d\n", bit, state_bit);
>> -	msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, bit);
>> +	msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, bit);
>>   
>>   	bit = BIT(state_bit - 1) << DP_MAINLINK_READY_LINK_TRAINING_SHIFT;
>>   
>> @@ -1453,7 +1491,7 @@ static int msm_dp_ctrl_link_train_1(struct msm_dp_ctrl_private *ctrl,
>>   	delay_us = drm_dp_read_clock_recovery_delay(ctrl->aux,
>>   						    ctrl->panel->dpcd, dp_phy, false);
>>   
>> -	msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, 0);
>> +	msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, 0);
>>   
>>   	*training_step = DP_TRAINING_1;
>>   
>> @@ -1577,7 +1615,7 @@ static int msm_dp_ctrl_link_train_2(struct msm_dp_ctrl_private *ctrl,
>>   	delay_us = drm_dp_read_channel_eq_delay(ctrl->aux,
>>   						ctrl->panel->dpcd, dp_phy, false);
>>   
>> -	msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, 0);
>> +	msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, 0);
>>   
>>   	*training_step = DP_TRAINING_2;
>>   
>> @@ -1694,7 +1732,7 @@ static int msm_dp_ctrl_link_train(struct msm_dp_ctrl_private *ctrl,
>>   	}
>>   
>>   end:
>> -	msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, 0);
>> +	msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, 0);
>>   
>>   	return ret;
>>   }
>> @@ -1840,34 +1878,34 @@ static int msm_dp_ctrl_enable_mainlink_clocks(struct msm_dp_ctrl_private *ctrl)
>>   static void msm_dp_ctrl_enable_sdp(struct msm_dp_ctrl_private *ctrl)
>>   {
>>   	/* trigger sdp */
>> -	msm_dp_write_link(ctrl, MMSS_DP_SDP_CFG3, UPDATE_SDP);
>> -	msm_dp_write_link(ctrl, MMSS_DP_SDP_CFG3, 0x0);
>> +	msm_dp_write_link(ctrl, 0, MMSS_DP_SDP_CFG3, UPDATE_SDP);
>> +	msm_dp_write_link(ctrl, 0, MMSS_DP_SDP_CFG3, 0x0);
>>   }
>>   
>>   static void msm_dp_ctrl_psr_enter(struct msm_dp_ctrl_private *ctrl)
>>   {
>>   	u32 cmd;
>>   
>> -	cmd = msm_dp_read_link(ctrl, REG_PSR_CMD);
>> +	cmd = msm_dp_read_link(ctrl, 0, REG_PSR_CMD);
>>   
>>   	cmd &= ~(PSR_ENTER | PSR_EXIT);
>>   	cmd |= PSR_ENTER;
>>   
>>   	msm_dp_ctrl_enable_sdp(ctrl);
>> -	msm_dp_write_link(ctrl, REG_PSR_CMD, cmd);
>> +	msm_dp_write_link(ctrl, 0, REG_PSR_CMD, cmd);
>>   }
>>   
>>   static void msm_dp_ctrl_psr_exit(struct msm_dp_ctrl_private *ctrl)
>>   {
>>   	u32 cmd;
>>   
>> -	cmd = msm_dp_read_link(ctrl, REG_PSR_CMD);
>> +	cmd = msm_dp_read_link(ctrl, 0, REG_PSR_CMD);
>>   
>>   	cmd &= ~(PSR_ENTER | PSR_EXIT);
>>   	cmd |= PSR_EXIT;
>>   
>>   	msm_dp_ctrl_enable_sdp(ctrl);
>> -	msm_dp_write_link(ctrl, REG_PSR_CMD, cmd);
>> +	msm_dp_write_link(ctrl, 0, REG_PSR_CMD, cmd);
>>   }
>>   
>>   void msm_dp_ctrl_config_psr(struct msm_dp_ctrl *msm_dp_ctrl)
>> @@ -1880,9 +1918,9 @@ void msm_dp_ctrl_config_psr(struct msm_dp_ctrl *msm_dp_ctrl)
>>   		return;
>>   
>>   	/* enable PSR1 function */
>> -	cfg = msm_dp_read_link(ctrl, REG_PSR_CONFIG);
>> +	cfg = msm_dp_read_link(ctrl, 0, REG_PSR_CONFIG);
>>   	cfg |= PSR1_SUPPORTED;
>> -	msm_dp_write_link(ctrl, REG_PSR_CONFIG, cfg);
>> +	msm_dp_write_link(ctrl, 0, REG_PSR_CONFIG, cfg);
>>   
>>   	msm_dp_ctrl_config_psr_interrupt(ctrl);
>>   	msm_dp_ctrl_enable_sdp(ctrl);
>> @@ -1921,16 +1959,16 @@ void msm_dp_ctrl_set_psr(struct msm_dp_ctrl *msm_dp_ctrl, bool enter)
>>   		}
>>   
>>   		msm_dp_ctrl_push_idle(msm_dp_ctrl);
>> -		msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, 0);
>> +		msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, 0);
>>   
>>   		msm_dp_ctrl_psr_mainlink_disable(ctrl);
>>   	} else {
>>   		msm_dp_ctrl_psr_mainlink_enable(ctrl);
>>   
>>   		msm_dp_ctrl_psr_exit(ctrl);
>> -		msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, DP_STATE_CTRL_SEND_VIDEO);
>> +		msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, DP_STATE_CTRL_SEND_VIDEO);
>>   		msm_dp_ctrl_wait4video_ready(ctrl);
>> -		msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, 0);
>> +		msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, 0);
>>   	}
>>   }
>>   
>> @@ -2041,7 +2079,7 @@ static int msm_dp_ctrl_link_maintenance(struct msm_dp_ctrl_private *ctrl)
>>   
>>   	msm_dp_ctrl_clear_training_pattern(ctrl, DP_PHY_DPRX);
>>   
>> -	msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, DP_STATE_CTRL_SEND_VIDEO);
>> +	msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, DP_STATE_CTRL_SEND_VIDEO);
>>   
>>   	ret = msm_dp_ctrl_wait4video_ready(ctrl);
>>   end:
>> @@ -2056,72 +2094,72 @@ static void msm_dp_ctrl_send_phy_pattern(struct msm_dp_ctrl_private *ctrl,
>>   	u32 value = 0x0;
>>   
>>   	/* Make sure to clear the current pattern before starting a new one */
>> -	msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, 0x0);
>> +	msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, 0x0);
>>   
>>   	drm_dbg_dp(ctrl->drm_dev, "pattern: %#x\n", pattern);
>>   	switch (pattern) {
>>   	case DP_PHY_TEST_PATTERN_D10_2:
>> -		msm_dp_write_link(ctrl, REG_DP_STATE_CTRL,
>> -			      DP_STATE_CTRL_LINK_TRAINING_PATTERN1);
>> +		msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL,
>> +				  DP_STATE_CTRL_LINK_TRAINING_PATTERN1);
>>   		break;
>>   
>>   	case DP_PHY_TEST_PATTERN_ERROR_COUNT:
>>   		value &= ~(1 << 16);
>> -		msm_dp_write_link(ctrl, REG_DP_HBR2_COMPLIANCE_SCRAMBLER_RESET,
>> -			      value);
>> +		msm_dp_write_link(ctrl, 0, REG_DP_HBR2_COMPLIANCE_SCRAMBLER_RESET,
>> +				  value);
>>   		value |= SCRAMBLER_RESET_COUNT_VALUE;
>> -		msm_dp_write_link(ctrl, REG_DP_HBR2_COMPLIANCE_SCRAMBLER_RESET,
>> -			      value);
>> -		msm_dp_write_link(ctrl, REG_DP_MAINLINK_LEVELS,
>> -			      DP_MAINLINK_SAFE_TO_EXIT_LEVEL_2);
>> -		msm_dp_write_link(ctrl, REG_DP_STATE_CTRL,
>> -			      DP_STATE_CTRL_LINK_SYMBOL_ERR_MEASURE);
>> +		msm_dp_write_link(ctrl, 0, REG_DP_HBR2_COMPLIANCE_SCRAMBLER_RESET,
>> +				  value);
>> +		msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_LEVELS,
>> +				  DP_MAINLINK_SAFE_TO_EXIT_LEVEL_2);
>> +		msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL,
>> +				  DP_STATE_CTRL_LINK_SYMBOL_ERR_MEASURE);
>>   		break;
>>   
>>   	case DP_PHY_TEST_PATTERN_PRBS7:
>> -		msm_dp_write_link(ctrl, REG_DP_STATE_CTRL,
>> -			      DP_STATE_CTRL_LINK_PRBS7);
>> +		msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL,
>> +				  DP_STATE_CTRL_LINK_PRBS7);
>>   		break;
>>   
>>   	case DP_PHY_TEST_PATTERN_80BIT_CUSTOM:
>> -		msm_dp_write_link(ctrl, REG_DP_STATE_CTRL,
>> -			      DP_STATE_CTRL_LINK_TEST_CUSTOM_PATTERN);
>> +		msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL,
>> +				  DP_STATE_CTRL_LINK_TEST_CUSTOM_PATTERN);
>>   		/* 00111110000011111000001111100000 */
>> -		msm_dp_write_link(ctrl, REG_DP_TEST_80BIT_CUSTOM_PATTERN_REG0,
>> -			      0x3E0F83E0);
>> +		msm_dp_write_link(ctrl, 0, REG_DP_TEST_80BIT_CUSTOM_PATTERN_REG0,
>> +				  0x3E0F83E0);
>>   		/* 00001111100000111110000011111000 */
>> -		msm_dp_write_link(ctrl, REG_DP_TEST_80BIT_CUSTOM_PATTERN_REG1,
>> -			      0x0F83E0F8);
>> +		msm_dp_write_link(ctrl, 0, REG_DP_TEST_80BIT_CUSTOM_PATTERN_REG1,
>> +				  0x0F83E0F8);
>>   		/* 1111100000111110 */
>> -		msm_dp_write_link(ctrl, REG_DP_TEST_80BIT_CUSTOM_PATTERN_REG2,
>> -			      0x0000F83E);
>> +		msm_dp_write_link(ctrl, 0, REG_DP_TEST_80BIT_CUSTOM_PATTERN_REG2,
>> +				  0x0000F83E);
>>   		break;
>>   
>>   	case DP_PHY_TEST_PATTERN_CP2520:
>> -		value = msm_dp_read_link(ctrl, REG_DP_MAINLINK_CTRL);
>> +		value = msm_dp_read_link(ctrl, 0, REG_DP_MAINLINK_CTRL);
>>   		value &= ~DP_MAINLINK_CTRL_SW_BYPASS_SCRAMBLER;
>> -		msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, value);
>> +		msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_CTRL, value);
>>   
>>   		value = DP_HBR2_ERM_PATTERN;
>> -		msm_dp_write_link(ctrl, REG_DP_HBR2_COMPLIANCE_SCRAMBLER_RESET,
>> -			      value);
>> +		msm_dp_write_link(ctrl, 0, REG_DP_HBR2_COMPLIANCE_SCRAMBLER_RESET,
>> +				  value);
>>   		value |= SCRAMBLER_RESET_COUNT_VALUE;
>> -		msm_dp_write_link(ctrl, REG_DP_HBR2_COMPLIANCE_SCRAMBLER_RESET,
>> -			      value);
>> -		msm_dp_write_link(ctrl, REG_DP_MAINLINK_LEVELS,
>> -			      DP_MAINLINK_SAFE_TO_EXIT_LEVEL_2);
>> -		msm_dp_write_link(ctrl, REG_DP_STATE_CTRL,
>> -			      DP_STATE_CTRL_LINK_SYMBOL_ERR_MEASURE);
>> -		value = msm_dp_read_link(ctrl, REG_DP_MAINLINK_CTRL);
>> +		msm_dp_write_link(ctrl, 0, REG_DP_HBR2_COMPLIANCE_SCRAMBLER_RESET,
>> +				  value);
>> +		msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_LEVELS,
>> +				  DP_MAINLINK_SAFE_TO_EXIT_LEVEL_2);
>> +		msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL,
>> +				  DP_STATE_CTRL_LINK_SYMBOL_ERR_MEASURE);
>> +		value = msm_dp_read_link(ctrl, 0, REG_DP_MAINLINK_CTRL);
>>   		value |= DP_MAINLINK_CTRL_ENABLE;
>> -		msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, value);
>> +		msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_CTRL, value);
>>   		break;
>>   
>>   	case DP_PHY_TEST_PATTERN_SEL_MASK:
>> -		msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL,
>> -			      DP_MAINLINK_CTRL_ENABLE);
>> -		msm_dp_write_link(ctrl, REG_DP_STATE_CTRL,
>> -			      DP_STATE_CTRL_LINK_TRAINING_PATTERN4);
>> +		msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_CTRL,
>> +				  DP_MAINLINK_CTRL_ENABLE);
>> +		msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL,
>> +				  DP_STATE_CTRL_LINK_TRAINING_PATTERN4);
>>   		break;
>>   
>>   	default:
>> @@ -2149,7 +2187,7 @@ static bool msm_dp_ctrl_send_phy_test_pattern(struct msm_dp_ctrl_private *ctrl)
>>   	msm_dp_ctrl_update_phy_vx_px(ctrl, DP_PHY_DPRX);
>>   	msm_dp_link_send_test_response(ctrl->link);
>>   
>> -	pattern_sent = msm_dp_read_link(ctrl, REG_DP_MAINLINK_READY);
>> +	pattern_sent = msm_dp_read_link(ctrl, 0, REG_DP_MAINLINK_READY);
>>   
>>   	switch (pattern_sent) {
>>   	case MR_LINK_TRAINING1:
>> @@ -2522,8 +2560,14 @@ static void msm_dp_ctrl_config_msa(struct msm_dp_ctrl_private *ctrl,
>>   		nvid *= 3;
>>   
>>   	drm_dbg_dp(ctrl->drm_dev, "mvid=0x%x, nvid=0x%x\n", mvid, nvid);
>> -	msm_dp_write_link(ctrl, REG_DP_SOFTWARE_MVID + mvid_reg_off, mvid);
>> -	msm_dp_write_link(ctrl, REG_DP_SOFTWARE_NVID + nvid_reg_off, nvid);
>> +	msm_dp_write_link(ctrl, msm_dp_panel->stream_id,
>> +			  msm_dp_panel->stream_id > 1 ?
>> +			  REG_MSTLINK_SOFTWARE_MVID : REG_DP_SOFTWARE_MVID + mvid_reg_off,
>> +			  mvid);
>> +	msm_dp_write_link(ctrl, msm_dp_panel->stream_id,
>> +			  msm_dp_panel->stream_id > 1 ?
>> +			  REG_MSTLINK_SOFTWARE_NVID : REG_DP_SOFTWARE_NVID + nvid_reg_off,
>> +			  nvid);
>>   }
>>   
>>   int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train)
>> @@ -2593,7 +2637,8 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *
>>   
>>   	msm_dp_ctrl_lane_mapping(ctrl);
>>   	msm_dp_setup_peripheral_flush(ctrl);
>> -	msm_dp_ctrl_config_ctrl_link(ctrl);
>> +	if (msm_dp_panel->stream_id == DP_STREAM_0)
>> +		msm_dp_ctrl_config_ctrl_link(ctrl);
>>   
>>   	msm_dp_ctrl_configure_source_params(ctrl, msm_dp_panel);
>>   
>> @@ -2607,7 +2652,7 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *
>>   
>>   	msm_dp_ctrl_setup_tr_unit(ctrl);
>>   
>> -	msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, DP_STATE_CTRL_SEND_VIDEO);
>> +	msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, DP_STATE_CTRL_SEND_VIDEO);
>>   
>>   	ret = msm_dp_ctrl_wait4video_ready(ctrl);
>>   	if (ret)
>> @@ -2787,7 +2832,9 @@ struct msm_dp_ctrl *msm_dp_ctrl_get(struct device *dev, struct msm_dp_link *link
>>   			struct phy *phy,
>>   			int max_stream,
>>   			void __iomem *ahb_base,
>> -			void __iomem *link_base)
>> +			void __iomem *link_base,
>> +			void __iomem *mst2link_base,
>> +			void __iomem *mst3link_base)
>>   {
>>   	struct msm_dp_ctrl_private *ctrl;
>>   	int ret;
>> @@ -2827,6 +2874,8 @@ struct msm_dp_ctrl *msm_dp_ctrl_get(struct device *dev, struct msm_dp_link *link
>>   	ctrl->phy      = phy;
>>   	ctrl->ahb_base = ahb_base;
>>   	ctrl->link_base = link_base;
>> +	ctrl->mst2link_base = mst2link_base;
>> +	ctrl->mst3link_base = mst3link_base;
>>   
>>   	ret = msm_dp_ctrl_clk_init(&ctrl->msm_dp_ctrl, max_stream);
>>   	if (ret) {
>> diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.h b/drivers/gpu/drm/msm/dp/dp_ctrl.h
>> index 6fed3ff3a72d..e72d501ac1ce 100644
>> --- a/drivers/gpu/drm/msm/dp/dp_ctrl.h
>> +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.h
>> @@ -31,7 +31,9 @@ struct msm_dp_ctrl *msm_dp_ctrl_get(struct device *dev,
>>   				    struct phy *phy,
>>   				    int max_stream,
>>   				    void __iomem *ahb_base,
>> -				    void __iomem *link_base);
>> +				    void __iomem *link_base,
>> +				    void __iomem *mst2link_base,
>> +				    void __iomem *mst3link_base);
>>   
>>   void msm_dp_ctrl_reset(struct msm_dp_ctrl *msm_dp_ctrl);
>>   void msm_dp_ctrl_phy_init(struct msm_dp_ctrl *msm_dp_ctrl);
>> diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
>> index ff506064a3fa..a924fbd825f7 100644
>> --- a/drivers/gpu/drm/msm/dp/dp_display.c
>> +++ b/drivers/gpu/drm/msm/dp/dp_display.c
>> @@ -85,6 +85,12 @@ struct msm_dp_display_private {
>>   	void __iomem *link_base;
>>   	size_t link_len;
>>   
>> +	void __iomem *mst2link_base;
>> +	size_t mst2link_len;
>> +
>> +	void __iomem *mst3link_base;
>> +	size_t mst3link_len;
>> +
>>   	void __iomem *pixel_base[DP_STREAM_MAX];
>>   	size_t pixel_len;
>>   
>> @@ -561,7 +567,8 @@ static int msm_dp_init_sub_modules(struct msm_dp_display_private *dp)
>>   		goto error_link;
>>   	}
>>   
>> -	dp->panel = msm_dp_panel_get(dev, dp->aux, dp->link, dp->link_base, dp->pixel_base[0]);
>> +	dp->panel = msm_dp_panel_get(dev, dp->aux, dp->link, dp->link_base,
>> +				     dp->mst2link_base, dp->mst3link_base, dp->pixel_base[0]);
>>   	if (IS_ERR(dp->panel)) {
>>   		rc = PTR_ERR(dp->panel);
>>   		DRM_ERROR("failed to initialize panel, rc = %d\n", rc);
>> @@ -570,7 +577,8 @@ static int msm_dp_init_sub_modules(struct msm_dp_display_private *dp)
>>   	}
>>   
>>   	dp->ctrl = msm_dp_ctrl_get(dev, dp->link, dp->panel, dp->aux,
>> -			       phy, dp->max_stream, dp->ahb_base, dp->link_base);
>> +			       phy, dp->max_stream, dp->ahb_base,
>> +			       dp->link_base, dp->mst2link_base, dp->mst3link_base);
>>   	if (IS_ERR(dp->ctrl)) {
>>   		rc = PTR_ERR(dp->ctrl);
>>   		DRM_ERROR("failed to initialize ctrl, rc = %d\n", rc);
>> @@ -883,6 +891,10 @@ void msm_dp_snapshot(struct msm_disp_state *disp_state, struct msm_dp *dp)
>>   				    msm_dp_display->aux_base, "dp_aux");
>>   	msm_disp_snapshot_add_block(disp_state, msm_dp_display->link_len,
>>   				    msm_dp_display->link_base, "dp_link");
>> +	msm_disp_snapshot_add_block(disp_state, msm_dp_display->mst2link_len,
>> +				    msm_dp_display->mst2link_base, "dp_mst2link");
>> +	msm_disp_snapshot_add_block(disp_state, msm_dp_display->mst3link_len,
>> +				    msm_dp_display->mst3link_base, "dp_mst3link");
>>   	msm_disp_snapshot_add_block(disp_state, msm_dp_display->pixel_len,
>>   				    msm_dp_display->pixel_base[0], "dp_p0");
>>   	msm_disp_snapshot_add_block(disp_state, msm_dp_display->pixel_len,
>> @@ -1228,6 +1240,14 @@ static int msm_dp_display_get_io(struct msm_dp_display_private *display)
>>   		}
>>   	}
>>   
>> +	display->mst2link_base = msm_dp_ioremap(pdev, 7, &display->mst2link_len);
>> +	if (IS_ERR(display->mst2link_base))
>> +		DRM_DEBUG_DP("unable to remap link region: %pe\n", display->mst2link_base);
>> +
>> +	display->mst3link_base = msm_dp_ioremap(pdev, 8, &display->mst3link_len);
>> +	if (IS_ERR(display->mst3link_base))
>> +		DRM_DEBUG_DP("unable to remap link region: %pe\n", display->mst3link_base);
>> +
>>   	return 0;
>>   }
>>   
>> diff --git a/drivers/gpu/drm/msm/dp/dp_panel.c b/drivers/gpu/drm/msm/dp/dp_panel.c
>> index 6c88cc7e3037..a8a6297b37e3 100644
>> --- a/drivers/gpu/drm/msm/dp/dp_panel.c
>> +++ b/drivers/gpu/drm/msm/dp/dp_panel.c
>> @@ -25,23 +25,50 @@ struct msm_dp_panel_private {
>>   	struct drm_dp_aux *aux;
>>   	struct msm_dp_link *link;
>>   	void __iomem *link_base;
>> +	void __iomem *mst2link_base;
>> +	void __iomem *mst3link_base;
>>   	void __iomem *pixel_base;
>>   	bool panel_on;
>>   };
>>   
>>   static inline u32 msm_dp_read_link(struct msm_dp_panel_private *panel, u32 offset)
>>   {
>> -	return readl_relaxed(panel->link_base + offset);
>> +	switch (panel->msm_dp_panel.stream_id) {
>> +	case DP_STREAM_0:
>> +	case DP_STREAM_1:
>> +		return readl_relaxed(panel->link_base + offset);
>> +	case DP_STREAM_2:
>> +		return readl_relaxed(panel->mst2link_base + offset);
>> +	case DP_STREAM_3:
>> +		return readl_relaxed(panel->mst3link_base + offset);
>> +	default:
>> +		DRM_ERROR("error stream_id\n");
>> +		return 0;
>> +	}
>>   }
>>   
>>   static inline void msm_dp_write_link(struct msm_dp_panel_private *panel,
>> -			       u32 offset, u32 data)
>> +					u32 offset, u32 data)
>>   {
>>   	/*
>>   	 * To make sure link reg writes happens before any other operation,
>>   	 * this function uses writel() instread of writel_relaxed()
>>   	 */
>> -	writel(data, panel->link_base + offset);
>> +	switch (panel->msm_dp_panel.stream_id) {
>> +	case DP_STREAM_0:
>> +	case DP_STREAM_1:
>> +		writel(data, panel->link_base + offset);
>> +		break;
>> +	case DP_STREAM_2:
>> +		writel(data, panel->mst2link_base + offset);
>> +		break;
>> +	case DP_STREAM_3:
>> +		writel(data, panel->mst3link_base + offset);
>> +		break;
>> +	default:
>> +		DRM_ERROR("error stream_id\n");
>> +		break;
>> +	}
>>   }
>>   
>>   static inline void msm_dp_write_pn(struct msm_dp_panel_private *panel,
>> @@ -444,38 +471,51 @@ void msm_dp_panel_clear_dsc_dto(struct msm_dp_panel *msm_dp_panel)
>>   
>>   static void msm_dp_panel_send_vsc_sdp(struct msm_dp_panel_private *panel, struct dp_sdp *vsc_sdp)
>>   {
>> +	u32 id = panel->msm_dp_panel.stream_id;
>>   	u32 header[2];
>>   	u32 val;
>>   	int i;
>>   	u32 offset = 0;
>>   
>> -	if (panel->msm_dp_panel.stream_id == DP_STREAM_1)
>> +	if (id == DP_STREAM_1)
>>   		offset = MMSS_DP1_GENERIC0_0 - MMSS_DP_GENERIC0_0;
>>   
>>   	msm_dp_utils_pack_sdp_header(&vsc_sdp->sdp_header, header);
>>   
>> -	msm_dp_write_link(panel, MMSS_DP_GENERIC0_0 + offset, header[0]);
>> -	msm_dp_write_link(panel, MMSS_DP_GENERIC0_1 + offset, header[1]);
>> +	msm_dp_write_link(panel, id > 1 ?
>> +			  MMSS_DP_MSTLINK_GENERIC0_0 : MMSS_DP_GENERIC0_0 + offset,
>> +			  header[0]);
>> +	msm_dp_write_link(panel, id > 1 ?
>> +			  MMSS_DP_MSTLINK_GENERIC0_1 : MMSS_DP_GENERIC0_1 + offset,
>> +			  header[1]);
>>   
>>   	for (i = 0; i < sizeof(vsc_sdp->db); i += 4) {
>>   		val = ((vsc_sdp->db[i]) | (vsc_sdp->db[i + 1] << 8) | (vsc_sdp->db[i + 2] << 16) |
>>   		       (vsc_sdp->db[i + 3] << 24));
>> -		msm_dp_write_link(panel, MMSS_DP_GENERIC0_2 + i + offset, val);
>> +
>> +		msm_dp_write_link(panel, id > 1 ?
>> +				  MMSS_DP_MSTLINK_GENERIC0_2 + i : MMSS_DP_GENERIC0_2 + i + offset,
>> +				  val);
>>   	}
>>   }
>>   
>>   static void msm_dp_panel_update_sdp(struct msm_dp_panel_private *panel)
>>   {
>> +	u32 id = panel->msm_dp_panel.stream_id;
>>   	u32 hw_revision = panel->msm_dp_panel.hw_revision;
>>   	u32 offset = 0;
>>   
>> -	if (panel->msm_dp_panel.stream_id == DP_STREAM_1)
>> +	if (id == DP_STREAM_1)
>>   		offset = MMSS_DP1_SDP_CFG3 - MMSS_DP_SDP_CFG3;
>>   
>>   	if (hw_revision >= DP_HW_VERSION_1_0 &&
>>   	    hw_revision < DP_HW_VERSION_1_2) {
>> -		msm_dp_write_link(panel, MMSS_DP_SDP_CFG3 + offset, UPDATE_SDP);
>> -		msm_dp_write_link(panel, MMSS_DP_SDP_CFG3 + offset, 0x0);
>> +		msm_dp_write_link(panel, id > 1 ?
>> +				  MMSS_DP_MSTLINK_SDP_CFG3 : MMSS_DP_SDP_CFG3 + offset,
>> +				  UPDATE_SDP);
>> +		msm_dp_write_link(panel, id > 1 ?
>> +				  MMSS_DP_MSTLINK_SDP_CFG3 : MMSS_DP_SDP_CFG3 + offset,
>> +				  0x0);
>>   	}
>>   }
>>   
>> @@ -483,26 +523,34 @@ void msm_dp_panel_enable_vsc_sdp(struct msm_dp_panel *msm_dp_panel, struct dp_sd
>>   {
>>   	struct msm_dp_panel_private *panel =
>>   		container_of(msm_dp_panel, struct msm_dp_panel_private, msm_dp_panel);
>> +	u32 id = msm_dp_panel->stream_id;
>>   	u32 cfg, cfg2, misc;
>>   	u32 misc_reg_offset = 0;
>>   	u32 sdp_cfg_offset = 0;
>>   	u32 sdp_cfg2_offset = 0;
>>   
>> -	if (msm_dp_panel->stream_id == DP_STREAM_1) {
>> +	if (id == DP_STREAM_1) {
>>   		misc_reg_offset = REG_DP1_MISC1_MISC0 - REG_DP_MISC1_MISC0;
>>   		sdp_cfg_offset = MMSS_DP1_SDP_CFG - MMSS_DP_SDP_CFG;
>>   		sdp_cfg2_offset = MMSS_DP1_SDP_CFG2 - MMSS_DP_SDP_CFG2;
>>   	}
>>   
>> -	cfg = msm_dp_read_link(panel, MMSS_DP_SDP_CFG + sdp_cfg_offset);
>> -	cfg2 = msm_dp_read_link(panel, MMSS_DP_SDP_CFG2 + sdp_cfg2_offset);
>> -	misc = msm_dp_read_link(panel, REG_DP_MISC1_MISC0 + misc_reg_offset);
>> +	cfg = msm_dp_read_link(panel, id > 1 ?
>> +			       MMSS_DP_MSTLINK_SDP_CFG : MMSS_DP_SDP_CFG + sdp_cfg_offset);
>> +	cfg2 = msm_dp_read_link(panel, id > 1 ?
>> +				MMSS_DP_MSTLINK_SDP_CFG2 : MMSS_DP_SDP_CFG2 + sdp_cfg2_offset);
>> +	misc = msm_dp_read_link(panel, id > 1 ?
>> +				REG_DP_MSTLINK_MISC1_MISC0 : REG_DP_MISC1_MISC0 + misc_reg_offset);
>>   
>>   	cfg |= GEN0_SDP_EN;
>> -	msm_dp_write_link(panel, MMSS_DP_SDP_CFG + sdp_cfg_offset, cfg);
>> -
>>   	cfg2 |= GENERIC0_SDPSIZE_VALID;
>> -	msm_dp_write_link(panel, MMSS_DP_SDP_CFG2 + sdp_cfg2_offset, cfg2);
>> +
>> +	msm_dp_write_link(panel, id > 1 ?
>> +			  MMSS_DP_MSTLINK_SDP_CFG : MMSS_DP_SDP_CFG + sdp_cfg_offset,
>> +			  cfg);
>> +	msm_dp_write_link(panel, id > 1 ?
>> +			  MMSS_DP_MSTLINK_SDP_CFG2 : MMSS_DP_SDP_CFG2 + sdp_cfg2_offset,
>> +			  cfg2);
>>   
>>   	msm_dp_panel_send_vsc_sdp(panel, vsc_sdp);
>>   
>> @@ -512,7 +560,9 @@ void msm_dp_panel_enable_vsc_sdp(struct msm_dp_panel *msm_dp_panel, struct dp_sd
>>   	drm_dbg_dp(panel->drm_dev, "vsc sdp enable=1\n");
>>   
>>   	pr_debug("misc settings = 0x%x\n", misc);
>> -	msm_dp_write_link(panel, REG_DP_MISC1_MISC0 + misc_reg_offset, misc);
>> +	msm_dp_write_link(panel, id > 1 ?
>> +			  REG_DP_MSTLINK_MISC1_MISC0 : REG_DP_MISC1_MISC0 + misc_reg_offset,
>> +			  misc);
>>   
>>   	msm_dp_panel_update_sdp(panel);
>>   }
>> @@ -521,26 +571,34 @@ void msm_dp_panel_disable_vsc_sdp(struct msm_dp_panel *msm_dp_panel)
>>   {
>>   	struct msm_dp_panel_private *panel =
>>   		container_of(msm_dp_panel, struct msm_dp_panel_private, msm_dp_panel);
>> +	u32 id = msm_dp_panel->stream_id;
>>   	u32 cfg, cfg2, misc;
>>   	u32 misc_reg_offset = 0;
>>   	u32 sdp_cfg_offset = 0;
>>   	u32 sdp_cfg2_offset = 0;
>>   
>> -	if (msm_dp_panel->stream_id == DP_STREAM_1) {
>> +	if (id == DP_STREAM_1) {
>>   		misc_reg_offset = REG_DP1_MISC1_MISC0 - REG_DP_MISC1_MISC0;
>>   		sdp_cfg_offset = MMSS_DP1_SDP_CFG - MMSS_DP_SDP_CFG;
>>   		sdp_cfg2_offset = MMSS_DP1_SDP_CFG2 - MMSS_DP_SDP_CFG2;
>>   	}
>>   
>> -	cfg = msm_dp_read_link(panel, MMSS_DP_SDP_CFG + sdp_cfg_offset);
>> -	cfg2 = msm_dp_read_link(panel, MMSS_DP_SDP_CFG2 + sdp_cfg2_offset);
>> -	misc = msm_dp_read_link(panel, REG_DP_MISC1_MISC0 + misc_reg_offset);
>> +	cfg = msm_dp_read_link(panel, id > 1 ?
>> +			       MMSS_DP_MSTLINK_SDP_CFG : MMSS_DP_SDP_CFG + sdp_cfg_offset);
>> +	cfg2 = msm_dp_read_link(panel, id > 1 ?
>> +				MMSS_DP_MSTLINK_SDP_CFG2 : MMSS_DP_SDP_CFG2 + sdp_cfg2_offset);
>> +	misc = msm_dp_read_link(panel, id > 1 ?
>> +				REG_DP_MSTLINK_MISC1_MISC0 : REG_DP_MISC1_MISC0 + misc_reg_offset);
>>   
>>   	cfg &= ~GEN0_SDP_EN;
>> -	msm_dp_write_link(panel, MMSS_DP_SDP_CFG + sdp_cfg_offset, cfg);
>> -
>>   	cfg2 &= ~GENERIC0_SDPSIZE_VALID;
>> -	msm_dp_write_link(panel, MMSS_DP_SDP_CFG2 + sdp_cfg2_offset, cfg2);
>> +
>> +	msm_dp_write_link(panel, id > 1 ?
>> +			  MMSS_DP_MSTLINK_SDP_CFG : MMSS_DP_SDP_CFG + sdp_cfg_offset,
>> +			  cfg);
>> +	msm_dp_write_link(panel, id > 1 ?
>> +			  MMSS_DP_MSTLINK_SDP_CFG2 : MMSS_DP_SDP_CFG2 + sdp_cfg2_offset,
>> +			  cfg2);
>>   
>>   	/* switch back to MSA */
>>   	misc &= ~DP_MISC1_VSC_SDP;
>> @@ -548,7 +606,9 @@ void msm_dp_panel_disable_vsc_sdp(struct msm_dp_panel *msm_dp_panel)
>>   	drm_dbg_dp(panel->drm_dev, "vsc sdp enable=0\n");
>>   
>>   	pr_debug("misc settings = 0x%x\n", misc);
>> -	msm_dp_write_link(panel, REG_DP_MISC1_MISC0 + misc_reg_offset, misc);
>> +	msm_dp_write_link(panel, id > 1 ?
>> +			  REG_DP_MSTLINK_MISC1_MISC0 : REG_DP_MISC1_MISC0 + misc_reg_offset,
>> +			  misc);
>>   
>>   	msm_dp_panel_update_sdp(panel);
>>   }
>> @@ -598,6 +658,7 @@ static int msm_dp_panel_setup_vsc_sdp_yuv_420(struct msm_dp_panel *msm_dp_panel)
>>   
>>   int msm_dp_panel_timing_cfg(struct msm_dp_panel *msm_dp_panel, bool wide_bus_en)
>>   {
>> +	u32 id = msm_dp_panel->stream_id;
>>   	u32 data, total_ver, total_hor;
>>   	struct msm_dp_panel_private *panel;
>>   	struct drm_display_mode *drm_mode;
>> @@ -621,7 +682,7 @@ int msm_dp_panel_timing_cfg(struct msm_dp_panel *msm_dp_panel, bool wide_bus_en)
>>   		drm_mode->vsync_start - drm_mode->vdisplay,
>>   		drm_mode->vsync_end - drm_mode->vsync_start);
>>   
>> -	if (msm_dp_panel->stream_id == DP_STREAM_1)
>> +	if (id == DP_STREAM_1)
>>   		offset = REG_DP1_TOTAL_HOR_VER - REG_DP_TOTAL_HOR_VER;
>>   
>>   	total_hor = drm_mode->htotal;
>> @@ -654,10 +715,18 @@ int msm_dp_panel_timing_cfg(struct msm_dp_panel *msm_dp_panel, bool wide_bus_en)
>>   
>>   	msm_dp_active = data;
>>   
>> -	msm_dp_write_link(panel, REG_DP_TOTAL_HOR_VER + offset, total);
>> -	msm_dp_write_link(panel, REG_DP_START_HOR_VER_FROM_SYNC  + offset, sync_start);
>> -	msm_dp_write_link(panel, REG_DP_HSYNC_VSYNC_WIDTH_POLARITY + offset, width_blanking);
>> -	msm_dp_write_link(panel, REG_DP_ACTIVE_HOR_VER  + offset, msm_dp_active);
>> +	msm_dp_write_link(panel,
>> +			  id > 1 ? REG_DP_MSTLINK_TOTAL_HOR_VER :
>> +			  REG_DP_TOTAL_HOR_VER + offset, total);
>> +	msm_dp_write_link(panel,
>> +			  id > 1 ? REG_DP_MSTLINK_START_HOR_VER_FROM_SYNC :
>> +			  REG_DP_START_HOR_VER_FROM_SYNC + offset, sync_start);
>> +	msm_dp_write_link(panel,
>> +			  id > 1 ? REG_DP_MSTLINK_HSYNC_VSYNC_WIDTH_POLARITY :
>> +			  REG_DP_HSYNC_VSYNC_WIDTH_POLARITY + offset, width_blanking);
>> +	msm_dp_write_link(panel,
>> +			  id > 1 ? REG_DP_MSTLINK_ACTIVE_HOR_VER :
>> +			  REG_DP_ACTIVE_HOR_VER + offset, msm_dp_active);
>>   
>>   	reg = msm_dp_read_pn(panel, MMSS_DP_INTF_CONFIG);
>>   	if (wide_bus_en)
>> @@ -726,6 +795,8 @@ int msm_dp_panel_init_panel_info(struct msm_dp_panel *msm_dp_panel)
>>   struct msm_dp_panel *msm_dp_panel_get(struct device *dev, struct drm_dp_aux *aux,
>>   			      struct msm_dp_link *link,
>>   			      void __iomem *link_base,
>> +			      void __iomem *mst2link_base,
>> +			      void __iomem *mst3link_base,
>>   			      void __iomem *pixel_base)
>>   {
>>   	struct msm_dp_panel_private *panel;
>> @@ -745,6 +816,8 @@ struct msm_dp_panel *msm_dp_panel_get(struct device *dev, struct drm_dp_aux *aux
>>   	panel->link = link;
>>   	panel->link_base = link_base;
>>   	panel->pixel_base = pixel_base;
>> +	panel->mst2link_base = mst2link_base;
>> +	panel->mst3link_base = mst3link_base;
>>   
>>   	msm_dp_panel = &panel->msm_dp_panel;
>>   	msm_dp_panel->max_bw_code = DP_LINK_BW_8_1;
>> diff --git a/drivers/gpu/drm/msm/dp/dp_panel.h b/drivers/gpu/drm/msm/dp/dp_panel.h
>> index fe4ac3e47e17..4873c55bd693 100644
>> --- a/drivers/gpu/drm/msm/dp/dp_panel.h
>> +++ b/drivers/gpu/drm/msm/dp/dp_panel.h
>> @@ -101,5 +101,7 @@ static inline bool is_lane_count_valid(u32 lane_count)
>>   struct msm_dp_panel *msm_dp_panel_get(struct device *dev, struct drm_dp_aux *aux,
>>   			      struct msm_dp_link *link,
>>   			      void __iomem *link_base,
>> +			      void __iomem *mst2link_base,
>> +			      void __iomem *mst3link_base,
>>   			      void __iomem *pixel_base);
>>   #endif /* _DP_PANEL_H_ */
>> diff --git a/drivers/gpu/drm/msm/dp/dp_reg.h b/drivers/gpu/drm/msm/dp/dp_reg.h
>> index 295c1161e6b7..1c2d3d8d029d 100644
>> --- a/drivers/gpu/drm/msm/dp/dp_reg.h
>> +++ b/drivers/gpu/drm/msm/dp/dp_reg.h
>> @@ -162,7 +162,6 @@
>>   #define REG_DP_START_HOR_VER_FROM_SYNC		(0x00000020)
>>   #define REG_DP_HSYNC_VSYNC_WIDTH_POLARITY	(0x00000024)
>>   #define REG_DP_ACTIVE_HOR_VER			(0x00000028)
>> -
>>   #define REG_DP_MISC1_MISC0			(0x0000002C)
>>   #define DP_MISC0_SYNCHRONOUS_CLK		(0x00000001)
>>   #define DP_MISC0_COLORIMETRY_CFG_SHIFT		(0x00000001)
>> @@ -343,6 +342,21 @@
>>   #define MMSS_DP1_SDP_CFG2			(0x000004E4)
>>   #define MMSS_DP1_SDP_CFG3			(0x000004E8)
>>   
>> +#define REG_DP_MSTLINK_CONFIGURATION_CTRL	(0x00000034)
>> +#define REG_MSTLINK_SOFTWARE_MVID		(0x00000040)
>> +#define REG_MSTLINK_SOFTWARE_NVID		(0x00000044)
>> +#define REG_DP_MSTLINK_TOTAL_HOR_VER		(0x00000048)
>> +#define REG_DP_MSTLINK_START_HOR_VER_FROM_SYNC	(0x0000004C)
>> +#define REG_DP_MSTLINK_HSYNC_VSYNC_WIDTH_POLARITY	(0x00000050)
>> +#define REG_DP_MSTLINK_ACTIVE_HOR_VER		(0x00000054)
>> +#define REG_DP_MSTLINK_MISC1_MISC0		(0x00000058)
>> +#define MMSS_DP_MSTLINK_GENERIC0_0		(0x000000BC)
>> +#define MMSS_DP_MSTLINK_GENERIC0_1		(0x000000C0)
>> +#define MMSS_DP_MSTLINK_GENERIC0_2		(0x000000C4)
>> +#define MMSS_DP_MSTLINK_SDP_CFG			(0x0000010c)
>> +#define MMSS_DP_MSTLINK_SDP_CFG2		(0x0000011c)
>> +#define MMSS_DP_MSTLINK_SDP_CFG3		(0x00000114)
>> +
>>   #define MMSS_DP_ASYNC_FIFO_CONFIG		(0x00000088)
>>   
>>   #define REG_DP_PHY_AUX_INTERRUPT_CLEAR          (0x0000004C)
>>
>> -- 
>> 2.43.0
>>
> 


^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 14/39] drm/msm/dp: introduce max_streams for DP controller MST support
  2026-05-22  7:58         ` Yongxing Mou
@ 2026-05-25  8:16           ` Dmitry Baryshkov
  0 siblings, 0 replies; 128+ messages in thread
From: Dmitry Baryshkov @ 2026-05-25  8:16 UTC (permalink / raw)
  To: Yongxing Mou
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar

On Fri, May 22, 2026 at 03:58:06PM +0800, Yongxing Mou wrote:
> 
> 
> On 5/21/2026 8:07 PM, Dmitry Baryshkov wrote:
> > On Thu, May 21, 2026 at 07:34:32PM +0800, Yongxing Mou wrote:
> > > 
> > > 
> > > On 4/12/2026 1:59 AM, Dmitry Baryshkov wrote:
> > > > On Fri, Apr 10, 2026 at 05:33:49PM +0800, Yongxing Mou wrote:
> > > > > From: Abhinav Kumar <quic_abhinavk@quicinc.com>
> > > > > @@ -2740,7 +2741,8 @@ static int msm_dp_ctrl_clk_init(struct msm_dp_ctrl *msm_dp_ctrl)
> > > > >    	if (rc)
> > > > >    		return rc;
> > > > > -	for (i = DP_STREAM_0; i < DP_STREAM_MAX; i++) {
> > > > > +	ctrl->num_pixel_clks = 0;
> > > > > +	for (i = DP_STREAM_0; i < max_stream; i++) {
> > > > 
> > > > Why? I think the code was fine with getting up to DP_STREAM_MAX clocks.
> > > Sure.. will change max_stream -> DP_STREAM_MAX next version.
> > > > In fact, I'd rather use that instead of the hardcoded value for
> > > > determining how many streams can be there. Think of the old DTs which
> > > > didn't have extra stream clocks. We still need to support those.
> > > > 
> > > The current logic only assigns num streams to dp->max_stream when
> > > desc->mst_streams == num_pixel_clks, otherwise set to 1 which mean sst.
> > > I would like to introduce a switch in the driver to control the number of
> > > streams, rather than having it fully determined by the DTs.
> > 
> > Why?
> > 
> 1. In some scenarios, even if the hardware supports up to 4-stream MST, we
> may not want to enable all streams (e.g., LeMans: 2xDSI + 4xMST + 2xMST, but
> it only has a 6-layer mixer).

6 layer mixers, not 6-layer mixer (the latter means the mixer which
supports 6 blending layers).

Even with 6 LMs, there is no reason to prevent users from using the
hardware in the way they want. Any artifical limitations are NAKed.

> 2. Currently, the DTs for all platforms have already been merged, and for
> some legacy platforms, not sure if all platforms MST will work.

You are a part of the display team of the SoC manufacturer. You can test
the platforms and make sure that MST will work. I should be able to help
you with some of those, maybe except the MSM8998 / SDM660 generation.

> If my understanding is incorrect, please feel free to correct me. Thanks.
> > > Do you think it would be acceptable to use:
> > > dp->max_stream = min(num_pixel_clks, desc->mst_streams)?
> > 
> > We know that num_pixel_clks <= desc->mst_streams. Otherwise DT is wrong
> > and should not have been comitted. So, the min(a,b) has a known return
> > value of num_pixel_clks.
> > 
> > > 
> > > > >    		ctrl->pixel_clk[i] = devm_clk_get(dev, pixel_clks[i]);
> > > > >    		if (i == 0 && IS_ERR(ctrl->pixel_clk[i]))
> > 
> 

-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 15/39] drm/msm/dp: Add support for programming p1/p2/p3 register blocks
  2026-05-22  7:51         ` Yongxing Mou
@ 2026-05-25  8:19           ` Dmitry Baryshkov
  0 siblings, 0 replies; 128+ messages in thread
From: Dmitry Baryshkov @ 2026-05-25  8:19 UTC (permalink / raw)
  To: Yongxing Mou
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar

On Fri, May 22, 2026 at 03:51:57PM +0800, Yongxing Mou wrote:
> 
> 
> On 5/21/2026 8:20 PM, Dmitry Baryshkov wrote:
> > On Thu, May 21, 2026 at 07:50:30PM +0800, Yongxing Mou wrote:
> > > 
> > > 
> > > On 4/12/2026 2:07 AM, Dmitry Baryshkov wrote:
> > > > On Fri, Apr 10, 2026 at 05:33:50PM +0800, Yongxing Mou wrote:
> > > > > From: Abhinav Kumar <quic_abhinavk@quicinc.com>
> > > > > 
> > > > > Add support for additional pixel register blocks (p1, p2, p3) to enable
> > > > > 4‑stream MST pixel clocks. Introduce the helper functions msm_dp_read_pn
> > > > > and msm_dp_write_pn for pixel register programming. All pixel clocks
> > > > > share the same register layout but use different base addresses.
> > > > > 
> > > > > Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
> > > > > Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
> > > > > ---
> > > > >    drivers/gpu/drm/msm/dp/dp_display.c | 40 ++++++++++++-----
> > > > >    drivers/gpu/drm/msm/dp/dp_panel.c   | 89 ++++++++++++++++++++-----------------
> > > > >    drivers/gpu/drm/msm/dp/dp_panel.h   |  3 +-
> > > > >    3 files changed, 79 insertions(+), 53 deletions(-)
> > > > > 
> > > > > diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
> > > > > index 7984a0f9e938..ff506064a3fa 100644
> > > > > --- a/drivers/gpu/drm/msm/dp/dp_display.c
> > > > > +++ b/drivers/gpu/drm/msm/dp/dp_display.c
> > > > > @@ -85,8 +85,8 @@ struct msm_dp_display_private {
> > > > >    	void __iomem *link_base;
> > > > >    	size_t link_len;
> > > > > -	void __iomem *p0_base;
> > > > > -	size_t p0_len;
> > > > > +	void __iomem *pixel_base[DP_STREAM_MAX];
> > > > > +	size_t pixel_len;
> > > > >    	int max_stream;
> > > > >    };
> > > > > @@ -561,7 +561,7 @@ static int msm_dp_init_sub_modules(struct msm_dp_display_private *dp)
> > > > >    		goto error_link;
> > > > >    	}
> > > > > -	dp->panel = msm_dp_panel_get(dev, dp->aux, dp->link, dp->link_base, dp->p0_base);
> > > > > +	dp->panel = msm_dp_panel_get(dev, dp->aux, dp->link, dp->link_base, dp->pixel_base[0]);
> > > > >    	if (IS_ERR(dp->panel)) {
> > > > >    		rc = PTR_ERR(dp->panel);
> > > > >    		DRM_ERROR("failed to initialize panel, rc = %d\n", rc);
> > > > > @@ -769,6 +769,7 @@ int msm_dp_display_set_stream_info(struct msm_dp *msm_dp_display,
> > > > >    	}
> > > > >    	panel->stream_id = stream_id;
> > > > > +	msm_dp_panel_set_pixel_base(panel, dp->pixel_base[stream_id]);
> > > > 
> > > > Hmmm.... Would it be better to set it up differently? Allocate one panel
> > > > per the stream from the beginning and then simply get the first
> > > > available panel when required? This would require some minimal resource
> > > > manager, but then we won't have to pass dummy register base to the panel
> > > > code. Or actually allocate a panel when it is required? Do we need a
> > > > panel before atomic_enable()?
> > > > 
> > > In this series, panel come with MST connectors, Because the connectors are
> > > dynamically assigned, we don’t know which connector corresponds to which
> > > stream, so there stream_id and pixel base address are dynamic.
> > 
> > I read this as 'streams are dynamically assigned'. Connectors are fixed
> > and created for each branch point / real physical connector. Streams are
> > assigned on the first-serve bases.
> > 
> Thanks for pointing that out — your statement here is accurate.
> > > we have 2 optionals here:
> > 
> > What is the runtime requirement for the panels? Are they required for
> > parsing of the resources or only for the setup of the actual screen?
> > 
> > If we have 5 monitors connected to a single DP controller (via the
> > complicated topology), how many msm_dp_panel instances do we need to
> > handle the case, present it to the user and still let it select only 4
> > of them for the video output?
> > 
> It seems that if we move the link-related resources from the panel into
> dp_link,
> struct msm_dp_panel {
> 	/* dpcd raw data */
> 	u8 dpcd[DP_RECEIVER_CAP_SIZE];
> 	u8 downstream_ports[DP_MAX_DOWNSTREAM_PORTS];
> 	struct msm_dp_link_info link_info;
> ...
> }
>  we would only need to assign the panel during atomic_enable /
> atomic_disable.
> This way, in the scenario you described, only the final four panels would be
> exposed to the user.
> In the current implementation, the panel contains link-related information
> that is required during the link training phase.

link training is a part of atomic_enable. So, what does your answer
mean?


-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 16/39] drm/msm/dp: use stream_id to change offsets in dp_catalog
  2026-05-25  8:06     ` Yongxing Mou
@ 2026-05-25  8:21       ` Dmitry Baryshkov
  2026-05-25 12:13         ` Yongxing Mou
  0 siblings, 1 reply; 128+ messages in thread
From: Dmitry Baryshkov @ 2026-05-25  8:21 UTC (permalink / raw)
  To: Yongxing Mou
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar

On Mon, May 25, 2026 at 04:06:08PM +0800, Yongxing Mou wrote:
> 
> 
> On 4/12/2026 2:12 AM, Dmitry Baryshkov wrote:
> > On Fri, Apr 10, 2026 at 05:33:51PM +0800, Yongxing Mou wrote:
> > > From: Abhinav Kumar <quic_abhinavk@quicinc.com>
> > > 
> > > Use the dp_panel's stream_id to adjust the offsets for stream 1 which will
> > > be used for MST in the dp_catalog.
> > 
> > Please start by describing the problem.
> > 
> Got it, thanks.
> > > Stream 1 share the same link clk with
> > > stream 0 with different reg offset. Also add additional register defines
> > > for stream 1.
> > > 
> > > Streams 2 and 3 are not covered here, as they use separate link clocks and
> > > require separate handling.
> > > 
> > > Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
> > > Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
> > > ---
> > >   drivers/gpu/drm/msm/dp/dp_ctrl.c  | 24 ++++++++++---
> > >   drivers/gpu/drm/msm/dp/dp_panel.c | 72 +++++++++++++++++++++++++++------------
> > >   drivers/gpu/drm/msm/dp/dp_reg.h   | 11 ++++++
> > >   3 files changed, 81 insertions(+), 26 deletions(-)
> > > 
> > > diff --git a/drivers/gpu/drm/msm/dp/dp_reg.h b/drivers/gpu/drm/msm/dp/dp_reg.h
> > > index 3689642b7fc0..295c1161e6b7 100644
> > > --- a/drivers/gpu/drm/msm/dp/dp_reg.h
> > > +++ b/drivers/gpu/drm/msm/dp/dp_reg.h
> > > @@ -332,6 +332,17 @@
> > >   #define DP_TPG_VIDEO_CONFIG_BPP_8BIT		(0x00000001)
> > >   #define DP_TPG_VIDEO_CONFIG_RGB			(0x00000004)
> > > +/* DP MST registers */
> > 
> > Which register spaces are they from?
> > 
> Here also from DPTX_*, what about this? /* DP_TX MST registers */

Why? don't separate them from the registers from the same space. Also,
please, name them uniformly. Why some of those are REG_DP1 and others
are MMSS_DP1?

> > > +#define REG_DP1_CONFIGURATION_CTRL		(0x00000400)
> > > +#define REG_DP1_SOFTWARE_MVID			(0x00000414)
> > > +#define REG_DP1_SOFTWARE_NVID			(0x00000418)
> > > +#define REG_DP1_TOTAL_HOR_VER			(0x0000041C)
> > > +#define REG_DP1_MISC1_MISC0			(0x0000042C)
> > > +#define MMSS_DP1_GENERIC0_0			(0x00000490)
> > > +#define MMSS_DP1_SDP_CFG			(0x000004E0)
> > > +#define MMSS_DP1_SDP_CFG2			(0x000004E4)
> > > +#define MMSS_DP1_SDP_CFG3			(0x000004E8)
> > > +
> > >   #define MMSS_DP_ASYNC_FIFO_CONFIG		(0x00000088)
> > >   #define REG_DP_PHY_AUX_INTERRUPT_CLEAR          (0x0000004C)
> > > 

-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 17/39] drm/msm/dp: Add catalog support for 3rd/4th stream MST
  2026-05-25  8:06     ` Yongxing Mou
@ 2026-05-25  8:25       ` Dmitry Baryshkov
  2026-05-25 12:01         ` Yongxing Mou
  0 siblings, 1 reply; 128+ messages in thread
From: Dmitry Baryshkov @ 2026-05-25  8:25 UTC (permalink / raw)
  To: Yongxing Mou
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel

On Mon, May 25, 2026 at 04:06:17PM +0800, Yongxing Mou wrote:
> 
> 
> On 4/12/2026 2:24 AM, Dmitry Baryshkov wrote:
> > Nit, there is no catalog anymore, please fix the subject line.
> > 
> > On Fri, Apr 10, 2026 at 05:33:52PM +0800, Yongxing Mou wrote:
> > > To support 4-stream MST, the link clocks for stream 3 and stream 4 are
> > > controlled by MST_2_LCLK and MST_3_LCLK which share the same register
> > > definitions but use different base addresses.
> > > 
> > > Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
> > > ---
> > >   drivers/gpu/drm/msm/dp/dp_ctrl.c    | 225 ++++++++++++++++++++++--------------
> > >   drivers/gpu/drm/msm/dp/dp_ctrl.h    |   4 +-
> > >   drivers/gpu/drm/msm/dp/dp_display.c |  24 +++-
> > >   drivers/gpu/drm/msm/dp/dp_panel.c   | 135 +++++++++++++++++-----
> > >   drivers/gpu/drm/msm/dp/dp_panel.h   |   2 +
> > >   drivers/gpu/drm/msm/dp/dp_reg.h     |  16 ++-
> > >   6 files changed, 283 insertions(+), 123 deletions(-)
> > > 
> > > diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c
> > > index a52bcd9ea2a3..1109b2df21be 100644
> > > --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c
> > > +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c
> > > @@ -118,6 +118,8 @@ struct msm_dp_ctrl_private {
> > >   	struct msm_dp_link *link;
> > >   	void __iomem *ahb_base;
> > >   	void __iomem *link_base;
> > > +	void __iomem *mst2link_base;
> > > +	void __iomem *mst3link_base;
> > >   	struct phy *phy;
> > > @@ -158,19 +160,45 @@ static inline void msm_dp_write_ahb(struct msm_dp_ctrl_private *ctrl,
> > >   	writel(data, ctrl->ahb_base + offset);
> > >   }
> > > -static inline u32 msm_dp_read_link(struct msm_dp_ctrl_private *ctrl, u32 offset)
> > > +static inline u32 msm_dp_read_link(struct msm_dp_ctrl_private *ctrl,
> > > +				   enum msm_dp_stream_id stream_id, u32 offset)
> > >   {
> > > -	return readl_relaxed(ctrl->link_base + offset);
> > > +	switch (stream_id) {
> > > +	case DP_STREAM_0:
> > > +	case DP_STREAM_1:
> > 
> > Continuing the comment from the previous patch:
> > 
> > Or, can we remap all offsets here? It would be much, much easier to
> > always pass REG_DP_foo and then here, in the link reading code, remap
> > the offsets for DP_STREAM_1.
> > 
> The stream 1 offsets relative to stream 0 are not uniform:
>     REG_DP_CONFIGURATION_CTRL  0x008 -> 0x400  (diff 0x3F8)
>     REG_DP_MISC1_MISC0         0x02C -> 0x42C  (diff 0x400)
>  So remapping inside msm_dp_read/write_link would require a full lookup
>  table, which seems not help for current status.
> 
> Can we introduce a small helper msm_dp_stream_reg() that selects the correct
> register address at the call site:
> //dp_panel.h
> static inline u32 msm_dp_stream_reg(enum msm_dp_stream_id id,
> 				     u32 reg_s0, u32 reg_s1, u32 reg_mst)
> {
> 	if (id > DP_STREAM_1)
> 		return reg_mst;
> 	return id == DP_STREAM_1 ? reg_s1 : reg_s0;
> }

No, that's what I want to get rid of. You are spreading these lookups
all over the code. Push them to a single function (or a single set of
functions, one per the stream, but having a single frontend to be called
by the rest of the driver).

> 
> //dp_panel.c and dp_ctrl.c
>       u32 reg = msm_dp_stream_reg(stream_id,
>                                   REG_DP_MISC1_MISC0,
>                                   REG_DP1_MISC1_MISC0,
>                                   REG_DP_MSTLINK_MISC1_MISC0);

And then, if we need to support more streams or if the map for the
registers change, we'd need to go through all caller sites. No, that's a
bad idea.

>       msm_dp_read_link(ctrl, stream_id, reg);
> > > +		return readl_relaxed(ctrl->link_base + offset);
> > > +	case DP_STREAM_2:
> > > +		return readl_relaxed(ctrl->mst2link_base + offset);
> > > +	case DP_STREAM_3:
> > > +		return readl_relaxed(ctrl->mst3link_base + offset);
> > > +	default:
> > > +		DRM_ERROR("error stream_id\n");
> > > +		return 0;
> > > +	}
> > >   }
> > >   static inline void msm_dp_write_link(struct msm_dp_ctrl_private *ctrl,
> > > -			       u32 offset, u32 data)
> > > +				     enum msm_dp_stream_id stream_id, u32 offset, u32 data)
> > >   {
> > >   	/*
> > >   	 * To make sure link reg writes happens before any other operation,
> > >   	 * this function uses writel() instread of writel_relaxed()
> > >   	 */
> > > -	writel(data, ctrl->link_base + offset);
> > > +	switch (stream_id) {
> > > +	case DP_STREAM_0:
> > > +	case DP_STREAM_1:
> > > +		writel(data, ctrl->link_base + offset);
> > > +		break;
> > > +	case DP_STREAM_2:
> > > +		writel(data, ctrl->mst2link_base + offset);
> > > +		break;
> > > +	case DP_STREAM_3:
> > > +		writel(data, ctrl->mst3link_base + offset);
> > > +		break;
> > > +	default:
> > > +		DRM_ERROR("error stream_id\n");
> > > +		break;
> > > +	}
> > >   }
> > >   static int msm_dp_aux_link_configure(struct drm_dp_aux *aux,
> > > @@ -294,18 +322,18 @@ static void msm_dp_ctrl_psr_mainlink_enable(struct msm_dp_ctrl_private *ctrl)
> > >   {
> > >   	u32 val;
> > > -	val = msm_dp_read_link(ctrl, REG_DP_MAINLINK_CTRL);
> > > +	val = msm_dp_read_link(ctrl, 0, REG_DP_MAINLINK_CTRL);
> > >   	val |= DP_MAINLINK_CTRL_ENABLE;
> > > -	msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, val);
> > > +	msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_CTRL, val);
> > 
> > Are those always using the link0 base? If so, can we keep the existing
> > API for the stream-independent registers and introduce a separate API
> > call for the stream-dependent calls? Generally, if you keep the existing
> > function, I think, it becomes easier to squash this and the previous
> > patches.
> > 
> Here will always using the link0 base. After introducing the helper func,
> will squash this patch into the previous one.
> > >   }
> > >   static void msm_dp_ctrl_psr_mainlink_disable(struct msm_dp_ctrl_private *ctrl)
> > >   {
> > >   	u32 val;
> > > -	val = msm_dp_read_link(ctrl, REG_DP_MAINLINK_CTRL);
> > > +	val = msm_dp_read_link(ctrl, 0, REG_DP_MAINLINK_CTRL);
> > >   	val &= ~DP_MAINLINK_CTRL_ENABLE;
> > > -	msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, val);
> > > +	msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_CTRL, val);
> > >   }
> > >   static void msm_dp_ctrl_mainlink_enable(struct msm_dp_ctrl_private *ctrl)
> > > @@ -314,21 +342,21 @@ static void msm_dp_ctrl_mainlink_enable(struct msm_dp_ctrl_private *ctrl)
> > >   	drm_dbg_dp(ctrl->drm_dev, "enable\n");
> > > -	mainlink_ctrl = msm_dp_read_link(ctrl, REG_DP_MAINLINK_CTRL);
> > > +	mainlink_ctrl = msm_dp_read_link(ctrl, 0, REG_DP_MAINLINK_CTRL);
> > >   	mainlink_ctrl &= ~(DP_MAINLINK_CTRL_RESET |
> > >   					DP_MAINLINK_CTRL_ENABLE);
> > > -	msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
> > > +	msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
> > >   	mainlink_ctrl |= DP_MAINLINK_CTRL_RESET;
> > > -	msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
> > > +	msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
> > >   	mainlink_ctrl &= ~DP_MAINLINK_CTRL_RESET;
> > > -	msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
> > > +	msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
> > >   	mainlink_ctrl |= (DP_MAINLINK_CTRL_ENABLE |
> > >   				DP_MAINLINK_FB_BOUNDARY_SEL);
> > > -	msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
> > > +	msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
> > >   }
> > >   static void msm_dp_ctrl_mainlink_disable(struct msm_dp_ctrl_private *ctrl)
> > > @@ -337,23 +365,23 @@ static void msm_dp_ctrl_mainlink_disable(struct msm_dp_ctrl_private *ctrl)
> > >   	drm_dbg_dp(ctrl->drm_dev, "disable\n");
> > > -	mainlink_ctrl = msm_dp_read_link(ctrl, REG_DP_MAINLINK_CTRL);
> > > +	mainlink_ctrl = msm_dp_read_link(ctrl, 0, REG_DP_MAINLINK_CTRL);
> > >   	mainlink_ctrl &= ~DP_MAINLINK_CTRL_ENABLE;
> > > -	msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
> > > +	msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
> > >   }
> > >   static void msm_dp_setup_peripheral_flush(struct msm_dp_ctrl_private *ctrl)
> > >   {
> > >   	u32 mainlink_ctrl;
> > > -	mainlink_ctrl = msm_dp_read_link(ctrl, REG_DP_MAINLINK_CTRL);
> > > +	mainlink_ctrl = msm_dp_read_link(ctrl, 0, REG_DP_MAINLINK_CTRL);
> > >   	if (ctrl->hw_revision >= DP_HW_VERSION_1_2)
> > >   		mainlink_ctrl |= DP_MAINLINK_FLUSH_MODE_SDE_PERIPH_UPDATE;
> > >   	else
> > >   		mainlink_ctrl |= DP_MAINLINK_FLUSH_MODE_UPDATE_SDP;
> > > -	msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
> > > +	msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
> > >   }
> > >   static bool msm_dp_ctrl_mainlink_ready(struct msm_dp_ctrl_private *ctrl)
> > > @@ -380,7 +408,7 @@ void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl)
> > >   	ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl);
> > >   	reinit_completion(&ctrl->idle_comp);
> > > -	msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, DP_STATE_CTRL_PUSH_IDLE);
> > > +	msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, DP_STATE_CTRL_PUSH_IDLE);
> > >   	if (!wait_for_completion_timeout(&ctrl->idle_comp,
> > >   			IDLE_PATTERN_COMPLETION_TIMEOUT_JIFFIES))
> > > @@ -395,7 +423,11 @@ static void msm_dp_ctrl_config_ctrl_streams(struct msm_dp_ctrl_private *ctrl,
> > >   	u32 config = 0, tbd;
> > >   	u32 reg_offset = 0;
> > > -	config = msm_dp_read_link(ctrl, REG_DP_CONFIGURATION_CTRL);
> > > +	if (msm_dp_panel->stream_id == DP_STREAM_0)
> > > +		config = msm_dp_read_link(ctrl, 0, REG_DP_CONFIGURATION_CTRL);
> > > +
> > > +	if (msm_dp_panel->stream_id == DP_STREAM_1)
> > > +		reg_offset = REG_DP1_CONFIGURATION_CTRL - REG_DP_CONFIGURATION_CTRL;
> > >   	if (msm_dp_panel->msm_dp_mode.out_fmt_is_yuv_420)
> > >   		config |= DP_CONFIGURATION_CTRL_RGB_YUV; /* YUV420 */
> > > @@ -410,8 +442,10 @@ static void msm_dp_ctrl_config_ctrl_streams(struct msm_dp_ctrl_private *ctrl,
> > >   	drm_dbg_dp(ctrl->drm_dev, "stream DP_CONFIGURATION_CTRL=0x%x\n", config);
> > > -	if (msm_dp_panel->stream_id == DP_STREAM_1)
> > > -		reg_offset = REG_DP1_CONFIGURATION_CTRL - REG_DP_CONFIGURATION_CTRL;
> > > +	msm_dp_write_link(ctrl, msm_dp_panel->stream_id, msm_dp_panel->stream_id > 1 ?
> > > +			  REG_DP_MSTLINK_CONFIGURATION_CTRL :
> > > +			  REG_DP_CONFIGURATION_CTRL + reg_offset, config);
> > > +
> > 
> > Yep, it would definitely help to handle these differences in the link
> > writing code.
> > 
> > >   }
> > >   static void msm_dp_ctrl_config_ctrl_link(struct msm_dp_ctrl_private *ctrl)
> > > @@ -441,7 +475,7 @@ static void msm_dp_ctrl_config_ctrl_link(struct msm_dp_ctrl_private *ctrl)
> > >   	drm_dbg_dp(ctrl->drm_dev, "link DP_CONFIGURATION_CTRL=0x%x\n", config);
> > > -	msm_dp_write_link(ctrl, REG_DP_CONFIGURATION_CTRL, config);
> > > +	msm_dp_write_link(ctrl, 0, REG_DP_CONFIGURATION_CTRL, config);
> > >   }
> > >   static void msm_dp_ctrl_lane_mapping(struct msm_dp_ctrl_private *ctrl)
> > > @@ -454,8 +488,8 @@ static void msm_dp_ctrl_lane_mapping(struct msm_dp_ctrl_private *ctrl)
> > >   	ln_mapping |= lane_map[2] << LANE2_MAPPING_SHIFT;
> > >   	ln_mapping |= lane_map[3] << LANE3_MAPPING_SHIFT;
> > > -	msm_dp_write_link(ctrl, REG_DP_LOGICAL2PHYSICAL_LANE_MAPPING,
> > > -			ln_mapping);
> > > +	msm_dp_write_link(ctrl, 0, REG_DP_LOGICAL2PHYSICAL_LANE_MAPPING,
> > > +			  ln_mapping);
> > >   }
> > >   static void msm_dp_ctrl_config_misc1_misc0(struct msm_dp_ctrl_private *ctrl,
> > > @@ -471,7 +505,8 @@ static void msm_dp_ctrl_config_misc1_misc0(struct msm_dp_ctrl_private *ctrl,
> > >   	if (msm_dp_panel->stream_id == DP_STREAM_1)
> > >   		reg_offset = REG_DP1_MISC1_MISC0 - REG_DP_MISC1_MISC0;
> > > -	misc_val = msm_dp_read_link(ctrl, REG_DP_MISC1_MISC0 + reg_offset);
> > > +	misc_val = msm_dp_read_link(ctrl, msm_dp_panel->stream_id, msm_dp_panel->stream_id > 1 ?
> > > +				    REG_DP_MSTLINK_MISC1_MISC0 : REG_DP_MISC1_MISC0 + reg_offset);
> > >   	/* clear bpp bits */
> > >   	misc_val &= ~(0x07 << DP_MISC0_TEST_BITS_DEPTH_SHIFT);
> > > @@ -481,7 +516,10 @@ static void msm_dp_ctrl_config_misc1_misc0(struct msm_dp_ctrl_private *ctrl,
> > >   	misc_val |= DP_MISC0_SYNCHRONOUS_CLK;
> > >   	drm_dbg_dp(ctrl->drm_dev, "misc settings = 0x%x\n", misc_val);
> > > -	msm_dp_write_link(ctrl, REG_DP_MISC1_MISC0 + reg_offset, misc_val);
> > > +	msm_dp_write_link(ctrl, msm_dp_panel->stream_id,
> > > +			  msm_dp_panel->stream_id > 1 ?
> > > +			  REG_DP_MSTLINK_MISC1_MISC0 : REG_DP_MISC1_MISC0 + reg_offset,
> > > +			  misc_val);
> > >   }
> > >   static void msm_dp_ctrl_configure_source_params(struct msm_dp_ctrl_private *ctrl,
> > > @@ -1307,9 +1345,9 @@ static void msm_dp_ctrl_setup_tr_unit(struct msm_dp_ctrl_private *ctrl)
> > >   	pr_debug("dp_tu=0x%x, valid_boundary=0x%x, valid_boundary2=0x%x\n",
> > >   			msm_dp_tu, valid_boundary, valid_boundary2);
> > > -	msm_dp_write_link(ctrl, REG_DP_VALID_BOUNDARY, valid_boundary);
> > > -	msm_dp_write_link(ctrl, REG_DP_TU, msm_dp_tu);
> > > -	msm_dp_write_link(ctrl, REG_DP_VALID_BOUNDARY_2, valid_boundary2);
> > > +	msm_dp_write_link(ctrl, 0, REG_DP_VALID_BOUNDARY, valid_boundary);
> > > +	msm_dp_write_link(ctrl, 0, REG_DP_TU, msm_dp_tu);
> > > +	msm_dp_write_link(ctrl, 0, REG_DP_VALID_BOUNDARY_2, valid_boundary2);
> > >   }
> > >   static int msm_dp_ctrl_wait4video_ready(struct msm_dp_ctrl_private *ctrl)
> > > @@ -1426,7 +1464,7 @@ static int msm_dp_ctrl_set_pattern_state_bit(struct msm_dp_ctrl_private *ctrl,
> > >   	bit = BIT(state_bit - 1);
> > >   	drm_dbg_dp(ctrl->drm_dev, "hw: bit=%d train=%d\n", bit, state_bit);
> > > -	msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, bit);
> > > +	msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, bit);
> > >   	bit = BIT(state_bit - 1) << DP_MAINLINK_READY_LINK_TRAINING_SHIFT;
> > > @@ -1453,7 +1491,7 @@ static int msm_dp_ctrl_link_train_1(struct msm_dp_ctrl_private *ctrl,
> > >   	delay_us = drm_dp_read_clock_recovery_delay(ctrl->aux,
> > >   						    ctrl->panel->dpcd, dp_phy, false);
> > > -	msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, 0);
> > > +	msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, 0);
> > >   	*training_step = DP_TRAINING_1;
> > > @@ -1577,7 +1615,7 @@ static int msm_dp_ctrl_link_train_2(struct msm_dp_ctrl_private *ctrl,
> > >   	delay_us = drm_dp_read_channel_eq_delay(ctrl->aux,
> > >   						ctrl->panel->dpcd, dp_phy, false);
> > > -	msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, 0);
> > > +	msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, 0);
> > >   	*training_step = DP_TRAINING_2;
> > > @@ -1694,7 +1732,7 @@ static int msm_dp_ctrl_link_train(struct msm_dp_ctrl_private *ctrl,
> > >   	}
> > >   end:
> > > -	msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, 0);
> > > +	msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, 0);
> > >   	return ret;
> > >   }
> > > @@ -1840,34 +1878,34 @@ static int msm_dp_ctrl_enable_mainlink_clocks(struct msm_dp_ctrl_private *ctrl)
> > >   static void msm_dp_ctrl_enable_sdp(struct msm_dp_ctrl_private *ctrl)
> > >   {
> > >   	/* trigger sdp */
> > > -	msm_dp_write_link(ctrl, MMSS_DP_SDP_CFG3, UPDATE_SDP);
> > > -	msm_dp_write_link(ctrl, MMSS_DP_SDP_CFG3, 0x0);
> > > +	msm_dp_write_link(ctrl, 0, MMSS_DP_SDP_CFG3, UPDATE_SDP);
> > > +	msm_dp_write_link(ctrl, 0, MMSS_DP_SDP_CFG3, 0x0);
> > >   }
> > >   static void msm_dp_ctrl_psr_enter(struct msm_dp_ctrl_private *ctrl)
> > >   {
> > >   	u32 cmd;
> > > -	cmd = msm_dp_read_link(ctrl, REG_PSR_CMD);
> > > +	cmd = msm_dp_read_link(ctrl, 0, REG_PSR_CMD);
> > >   	cmd &= ~(PSR_ENTER | PSR_EXIT);
> > >   	cmd |= PSR_ENTER;
> > >   	msm_dp_ctrl_enable_sdp(ctrl);
> > > -	msm_dp_write_link(ctrl, REG_PSR_CMD, cmd);
> > > +	msm_dp_write_link(ctrl, 0, REG_PSR_CMD, cmd);
> > >   }
> > >   static void msm_dp_ctrl_psr_exit(struct msm_dp_ctrl_private *ctrl)
> > >   {
> > >   	u32 cmd;
> > > -	cmd = msm_dp_read_link(ctrl, REG_PSR_CMD);
> > > +	cmd = msm_dp_read_link(ctrl, 0, REG_PSR_CMD);
> > >   	cmd &= ~(PSR_ENTER | PSR_EXIT);
> > >   	cmd |= PSR_EXIT;
> > >   	msm_dp_ctrl_enable_sdp(ctrl);
> > > -	msm_dp_write_link(ctrl, REG_PSR_CMD, cmd);
> > > +	msm_dp_write_link(ctrl, 0, REG_PSR_CMD, cmd);
> > >   }
> > >   void msm_dp_ctrl_config_psr(struct msm_dp_ctrl *msm_dp_ctrl)
> > > @@ -1880,9 +1918,9 @@ void msm_dp_ctrl_config_psr(struct msm_dp_ctrl *msm_dp_ctrl)
> > >   		return;
> > >   	/* enable PSR1 function */
> > > -	cfg = msm_dp_read_link(ctrl, REG_PSR_CONFIG);
> > > +	cfg = msm_dp_read_link(ctrl, 0, REG_PSR_CONFIG);
> > >   	cfg |= PSR1_SUPPORTED;
> > > -	msm_dp_write_link(ctrl, REG_PSR_CONFIG, cfg);
> > > +	msm_dp_write_link(ctrl, 0, REG_PSR_CONFIG, cfg);
> > >   	msm_dp_ctrl_config_psr_interrupt(ctrl);
> > >   	msm_dp_ctrl_enable_sdp(ctrl);
> > > @@ -1921,16 +1959,16 @@ void msm_dp_ctrl_set_psr(struct msm_dp_ctrl *msm_dp_ctrl, bool enter)
> > >   		}
> > >   		msm_dp_ctrl_push_idle(msm_dp_ctrl);
> > > -		msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, 0);
> > > +		msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, 0);
> > >   		msm_dp_ctrl_psr_mainlink_disable(ctrl);
> > >   	} else {
> > >   		msm_dp_ctrl_psr_mainlink_enable(ctrl);
> > >   		msm_dp_ctrl_psr_exit(ctrl);
> > > -		msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, DP_STATE_CTRL_SEND_VIDEO);
> > > +		msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, DP_STATE_CTRL_SEND_VIDEO);
> > >   		msm_dp_ctrl_wait4video_ready(ctrl);
> > > -		msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, 0);
> > > +		msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, 0);
> > >   	}
> > >   }
> > > @@ -2041,7 +2079,7 @@ static int msm_dp_ctrl_link_maintenance(struct msm_dp_ctrl_private *ctrl)
> > >   	msm_dp_ctrl_clear_training_pattern(ctrl, DP_PHY_DPRX);
> > > -	msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, DP_STATE_CTRL_SEND_VIDEO);
> > > +	msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, DP_STATE_CTRL_SEND_VIDEO);
> > >   	ret = msm_dp_ctrl_wait4video_ready(ctrl);
> > >   end:
> > > @@ -2056,72 +2094,72 @@ static void msm_dp_ctrl_send_phy_pattern(struct msm_dp_ctrl_private *ctrl,
> > >   	u32 value = 0x0;
> > >   	/* Make sure to clear the current pattern before starting a new one */
> > > -	msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, 0x0);
> > > +	msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, 0x0);
> > >   	drm_dbg_dp(ctrl->drm_dev, "pattern: %#x\n", pattern);
> > >   	switch (pattern) {
> > >   	case DP_PHY_TEST_PATTERN_D10_2:
> > > -		msm_dp_write_link(ctrl, REG_DP_STATE_CTRL,
> > > -			      DP_STATE_CTRL_LINK_TRAINING_PATTERN1);
> > > +		msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL,
> > > +				  DP_STATE_CTRL_LINK_TRAINING_PATTERN1);
> > >   		break;
> > >   	case DP_PHY_TEST_PATTERN_ERROR_COUNT:
> > >   		value &= ~(1 << 16);
> > > -		msm_dp_write_link(ctrl, REG_DP_HBR2_COMPLIANCE_SCRAMBLER_RESET,
> > > -			      value);
> > > +		msm_dp_write_link(ctrl, 0, REG_DP_HBR2_COMPLIANCE_SCRAMBLER_RESET,
> > > +				  value);
> > >   		value |= SCRAMBLER_RESET_COUNT_VALUE;
> > > -		msm_dp_write_link(ctrl, REG_DP_HBR2_COMPLIANCE_SCRAMBLER_RESET,
> > > -			      value);
> > > -		msm_dp_write_link(ctrl, REG_DP_MAINLINK_LEVELS,
> > > -			      DP_MAINLINK_SAFE_TO_EXIT_LEVEL_2);
> > > -		msm_dp_write_link(ctrl, REG_DP_STATE_CTRL,
> > > -			      DP_STATE_CTRL_LINK_SYMBOL_ERR_MEASURE);
> > > +		msm_dp_write_link(ctrl, 0, REG_DP_HBR2_COMPLIANCE_SCRAMBLER_RESET,
> > > +				  value);
> > > +		msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_LEVELS,
> > > +				  DP_MAINLINK_SAFE_TO_EXIT_LEVEL_2);
> > > +		msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL,
> > > +				  DP_STATE_CTRL_LINK_SYMBOL_ERR_MEASURE);
> > >   		break;
> > >   	case DP_PHY_TEST_PATTERN_PRBS7:
> > > -		msm_dp_write_link(ctrl, REG_DP_STATE_CTRL,
> > > -			      DP_STATE_CTRL_LINK_PRBS7);
> > > +		msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL,
> > > +				  DP_STATE_CTRL_LINK_PRBS7);
> > >   		break;
> > >   	case DP_PHY_TEST_PATTERN_80BIT_CUSTOM:
> > > -		msm_dp_write_link(ctrl, REG_DP_STATE_CTRL,
> > > -			      DP_STATE_CTRL_LINK_TEST_CUSTOM_PATTERN);
> > > +		msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL,
> > > +				  DP_STATE_CTRL_LINK_TEST_CUSTOM_PATTERN);
> > >   		/* 00111110000011111000001111100000 */
> > > -		msm_dp_write_link(ctrl, REG_DP_TEST_80BIT_CUSTOM_PATTERN_REG0,
> > > -			      0x3E0F83E0);
> > > +		msm_dp_write_link(ctrl, 0, REG_DP_TEST_80BIT_CUSTOM_PATTERN_REG0,
> > > +				  0x3E0F83E0);
> > >   		/* 00001111100000111110000011111000 */
> > > -		msm_dp_write_link(ctrl, REG_DP_TEST_80BIT_CUSTOM_PATTERN_REG1,
> > > -			      0x0F83E0F8);
> > > +		msm_dp_write_link(ctrl, 0, REG_DP_TEST_80BIT_CUSTOM_PATTERN_REG1,
> > > +				  0x0F83E0F8);
> > >   		/* 1111100000111110 */
> > > -		msm_dp_write_link(ctrl, REG_DP_TEST_80BIT_CUSTOM_PATTERN_REG2,
> > > -			      0x0000F83E);
> > > +		msm_dp_write_link(ctrl, 0, REG_DP_TEST_80BIT_CUSTOM_PATTERN_REG2,
> > > +				  0x0000F83E);
> > >   		break;
> > >   	case DP_PHY_TEST_PATTERN_CP2520:
> > > -		value = msm_dp_read_link(ctrl, REG_DP_MAINLINK_CTRL);
> > > +		value = msm_dp_read_link(ctrl, 0, REG_DP_MAINLINK_CTRL);
> > >   		value &= ~DP_MAINLINK_CTRL_SW_BYPASS_SCRAMBLER;
> > > -		msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, value);
> > > +		msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_CTRL, value);
> > >   		value = DP_HBR2_ERM_PATTERN;
> > > -		msm_dp_write_link(ctrl, REG_DP_HBR2_COMPLIANCE_SCRAMBLER_RESET,
> > > -			      value);
> > > +		msm_dp_write_link(ctrl, 0, REG_DP_HBR2_COMPLIANCE_SCRAMBLER_RESET,
> > > +				  value);
> > >   		value |= SCRAMBLER_RESET_COUNT_VALUE;
> > > -		msm_dp_write_link(ctrl, REG_DP_HBR2_COMPLIANCE_SCRAMBLER_RESET,
> > > -			      value);
> > > -		msm_dp_write_link(ctrl, REG_DP_MAINLINK_LEVELS,
> > > -			      DP_MAINLINK_SAFE_TO_EXIT_LEVEL_2);
> > > -		msm_dp_write_link(ctrl, REG_DP_STATE_CTRL,
> > > -			      DP_STATE_CTRL_LINK_SYMBOL_ERR_MEASURE);
> > > -		value = msm_dp_read_link(ctrl, REG_DP_MAINLINK_CTRL);
> > > +		msm_dp_write_link(ctrl, 0, REG_DP_HBR2_COMPLIANCE_SCRAMBLER_RESET,
> > > +				  value);
> > > +		msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_LEVELS,
> > > +				  DP_MAINLINK_SAFE_TO_EXIT_LEVEL_2);
> > > +		msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL,
> > > +				  DP_STATE_CTRL_LINK_SYMBOL_ERR_MEASURE);
> > > +		value = msm_dp_read_link(ctrl, 0, REG_DP_MAINLINK_CTRL);
> > >   		value |= DP_MAINLINK_CTRL_ENABLE;
> > > -		msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, value);
> > > +		msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_CTRL, value);
> > >   		break;
> > >   	case DP_PHY_TEST_PATTERN_SEL_MASK:
> > > -		msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL,
> > > -			      DP_MAINLINK_CTRL_ENABLE);
> > > -		msm_dp_write_link(ctrl, REG_DP_STATE_CTRL,
> > > -			      DP_STATE_CTRL_LINK_TRAINING_PATTERN4);
> > > +		msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_CTRL,
> > > +				  DP_MAINLINK_CTRL_ENABLE);
> > > +		msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL,
> > > +				  DP_STATE_CTRL_LINK_TRAINING_PATTERN4);
> > >   		break;
> > >   	default:
> > > @@ -2149,7 +2187,7 @@ static bool msm_dp_ctrl_send_phy_test_pattern(struct msm_dp_ctrl_private *ctrl)
> > >   	msm_dp_ctrl_update_phy_vx_px(ctrl, DP_PHY_DPRX);
> > >   	msm_dp_link_send_test_response(ctrl->link);
> > > -	pattern_sent = msm_dp_read_link(ctrl, REG_DP_MAINLINK_READY);
> > > +	pattern_sent = msm_dp_read_link(ctrl, 0, REG_DP_MAINLINK_READY);
> > >   	switch (pattern_sent) {
> > >   	case MR_LINK_TRAINING1:
> > > @@ -2522,8 +2560,14 @@ static void msm_dp_ctrl_config_msa(struct msm_dp_ctrl_private *ctrl,
> > >   		nvid *= 3;
> > >   	drm_dbg_dp(ctrl->drm_dev, "mvid=0x%x, nvid=0x%x\n", mvid, nvid);
> > > -	msm_dp_write_link(ctrl, REG_DP_SOFTWARE_MVID + mvid_reg_off, mvid);
> > > -	msm_dp_write_link(ctrl, REG_DP_SOFTWARE_NVID + nvid_reg_off, nvid);
> > > +	msm_dp_write_link(ctrl, msm_dp_panel->stream_id,
> > > +			  msm_dp_panel->stream_id > 1 ?
> > > +			  REG_MSTLINK_SOFTWARE_MVID : REG_DP_SOFTWARE_MVID + mvid_reg_off,
> > > +			  mvid);
> > > +	msm_dp_write_link(ctrl, msm_dp_panel->stream_id,
> > > +			  msm_dp_panel->stream_id > 1 ?
> > > +			  REG_MSTLINK_SOFTWARE_NVID : REG_DP_SOFTWARE_NVID + nvid_reg_off,
> > > +			  nvid);
> > >   }
> > >   int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train)
> > > @@ -2593,7 +2637,8 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *
> > >   	msm_dp_ctrl_lane_mapping(ctrl);
> > >   	msm_dp_setup_peripheral_flush(ctrl);
> > > -	msm_dp_ctrl_config_ctrl_link(ctrl);
> > > +	if (msm_dp_panel->stream_id == DP_STREAM_0)
> > > +		msm_dp_ctrl_config_ctrl_link(ctrl);
> > >   	msm_dp_ctrl_configure_source_params(ctrl, msm_dp_panel);
> > > @@ -2607,7 +2652,7 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *
> > >   	msm_dp_ctrl_setup_tr_unit(ctrl);
> > > -	msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, DP_STATE_CTRL_SEND_VIDEO);
> > > +	msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, DP_STATE_CTRL_SEND_VIDEO);
> > >   	ret = msm_dp_ctrl_wait4video_ready(ctrl);
> > >   	if (ret)
> > > @@ -2787,7 +2832,9 @@ struct msm_dp_ctrl *msm_dp_ctrl_get(struct device *dev, struct msm_dp_link *link
> > >   			struct phy *phy,
> > >   			int max_stream,
> > >   			void __iomem *ahb_base,
> > > -			void __iomem *link_base)
> > > +			void __iomem *link_base,
> > > +			void __iomem *mst2link_base,
> > > +			void __iomem *mst3link_base)
> > >   {
> > >   	struct msm_dp_ctrl_private *ctrl;
> > >   	int ret;
> > > @@ -2827,6 +2874,8 @@ struct msm_dp_ctrl *msm_dp_ctrl_get(struct device *dev, struct msm_dp_link *link
> > >   	ctrl->phy      = phy;
> > >   	ctrl->ahb_base = ahb_base;
> > >   	ctrl->link_base = link_base;
> > > +	ctrl->mst2link_base = mst2link_base;
> > > +	ctrl->mst3link_base = mst3link_base;
> > >   	ret = msm_dp_ctrl_clk_init(&ctrl->msm_dp_ctrl, max_stream);
> > >   	if (ret) {
> > > diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.h b/drivers/gpu/drm/msm/dp/dp_ctrl.h
> > > index 6fed3ff3a72d..e72d501ac1ce 100644
> > > --- a/drivers/gpu/drm/msm/dp/dp_ctrl.h
> > > +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.h
> > > @@ -31,7 +31,9 @@ struct msm_dp_ctrl *msm_dp_ctrl_get(struct device *dev,
> > >   				    struct phy *phy,
> > >   				    int max_stream,
> > >   				    void __iomem *ahb_base,
> > > -				    void __iomem *link_base);
> > > +				    void __iomem *link_base,
> > > +				    void __iomem *mst2link_base,
> > > +				    void __iomem *mst3link_base);
> > >   void msm_dp_ctrl_reset(struct msm_dp_ctrl *msm_dp_ctrl);
> > >   void msm_dp_ctrl_phy_init(struct msm_dp_ctrl *msm_dp_ctrl);
> > > diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
> > > index ff506064a3fa..a924fbd825f7 100644
> > > --- a/drivers/gpu/drm/msm/dp/dp_display.c
> > > +++ b/drivers/gpu/drm/msm/dp/dp_display.c
> > > @@ -85,6 +85,12 @@ struct msm_dp_display_private {
> > >   	void __iomem *link_base;
> > >   	size_t link_len;
> > > +	void __iomem *mst2link_base;
> > > +	size_t mst2link_len;
> > > +
> > > +	void __iomem *mst3link_base;
> > > +	size_t mst3link_len;
> > > +
> > >   	void __iomem *pixel_base[DP_STREAM_MAX];
> > >   	size_t pixel_len;
> > > @@ -561,7 +567,8 @@ static int msm_dp_init_sub_modules(struct msm_dp_display_private *dp)
> > >   		goto error_link;
> > >   	}
> > > -	dp->panel = msm_dp_panel_get(dev, dp->aux, dp->link, dp->link_base, dp->pixel_base[0]);
> > > +	dp->panel = msm_dp_panel_get(dev, dp->aux, dp->link, dp->link_base,
> > > +				     dp->mst2link_base, dp->mst3link_base, dp->pixel_base[0]);
> > >   	if (IS_ERR(dp->panel)) {
> > >   		rc = PTR_ERR(dp->panel);
> > >   		DRM_ERROR("failed to initialize panel, rc = %d\n", rc);
> > > @@ -570,7 +577,8 @@ static int msm_dp_init_sub_modules(struct msm_dp_display_private *dp)
> > >   	}
> > >   	dp->ctrl = msm_dp_ctrl_get(dev, dp->link, dp->panel, dp->aux,
> > > -			       phy, dp->max_stream, dp->ahb_base, dp->link_base);
> > > +			       phy, dp->max_stream, dp->ahb_base,
> > > +			       dp->link_base, dp->mst2link_base, dp->mst3link_base);
> > >   	if (IS_ERR(dp->ctrl)) {
> > >   		rc = PTR_ERR(dp->ctrl);
> > >   		DRM_ERROR("failed to initialize ctrl, rc = %d\n", rc);
> > > @@ -883,6 +891,10 @@ void msm_dp_snapshot(struct msm_disp_state *disp_state, struct msm_dp *dp)
> > >   				    msm_dp_display->aux_base, "dp_aux");
> > >   	msm_disp_snapshot_add_block(disp_state, msm_dp_display->link_len,
> > >   				    msm_dp_display->link_base, "dp_link");
> > > +	msm_disp_snapshot_add_block(disp_state, msm_dp_display->mst2link_len,
> > > +				    msm_dp_display->mst2link_base, "dp_mst2link");
> > > +	msm_disp_snapshot_add_block(disp_state, msm_dp_display->mst3link_len,
> > > +				    msm_dp_display->mst3link_base, "dp_mst3link");
> > >   	msm_disp_snapshot_add_block(disp_state, msm_dp_display->pixel_len,
> > >   				    msm_dp_display->pixel_base[0], "dp_p0");
> > >   	msm_disp_snapshot_add_block(disp_state, msm_dp_display->pixel_len,
> > > @@ -1228,6 +1240,14 @@ static int msm_dp_display_get_io(struct msm_dp_display_private *display)
> > >   		}
> > >   	}
> > > +	display->mst2link_base = msm_dp_ioremap(pdev, 7, &display->mst2link_len);
> > > +	if (IS_ERR(display->mst2link_base))
> > > +		DRM_DEBUG_DP("unable to remap link region: %pe\n", display->mst2link_base);
> > > +
> > > +	display->mst3link_base = msm_dp_ioremap(pdev, 8, &display->mst3link_len);
> > > +	if (IS_ERR(display->mst3link_base))
> > > +		DRM_DEBUG_DP("unable to remap link region: %pe\n", display->mst3link_base);
> > > +
> > >   	return 0;
> > >   }
> > > diff --git a/drivers/gpu/drm/msm/dp/dp_panel.c b/drivers/gpu/drm/msm/dp/dp_panel.c
> > > index 6c88cc7e3037..a8a6297b37e3 100644
> > > --- a/drivers/gpu/drm/msm/dp/dp_panel.c
> > > +++ b/drivers/gpu/drm/msm/dp/dp_panel.c
> > > @@ -25,23 +25,50 @@ struct msm_dp_panel_private {
> > >   	struct drm_dp_aux *aux;
> > >   	struct msm_dp_link *link;
> > >   	void __iomem *link_base;
> > > +	void __iomem *mst2link_base;
> > > +	void __iomem *mst3link_base;
> > >   	void __iomem *pixel_base;
> > >   	bool panel_on;
> > >   };
> > >   static inline u32 msm_dp_read_link(struct msm_dp_panel_private *panel, u32 offset)
> > >   {
> > > -	return readl_relaxed(panel->link_base + offset);
> > > +	switch (panel->msm_dp_panel.stream_id) {
> > > +	case DP_STREAM_0:
> > > +	case DP_STREAM_1:
> > > +		return readl_relaxed(panel->link_base + offset);
> > > +	case DP_STREAM_2:
> > > +		return readl_relaxed(panel->mst2link_base + offset);
> > > +	case DP_STREAM_3:
> > > +		return readl_relaxed(panel->mst3link_base + offset);
> > > +	default:
> > > +		DRM_ERROR("error stream_id\n");
> > > +		return 0;
> > > +	}
> > >   }
> > >   static inline void msm_dp_write_link(struct msm_dp_panel_private *panel,
> > > -			       u32 offset, u32 data)
> > > +					u32 offset, u32 data)
> > >   {
> > >   	/*
> > >   	 * To make sure link reg writes happens before any other operation,
> > >   	 * this function uses writel() instread of writel_relaxed()
> > >   	 */
> > > -	writel(data, panel->link_base + offset);
> > > +	switch (panel->msm_dp_panel.stream_id) {
> > > +	case DP_STREAM_0:
> > > +	case DP_STREAM_1:
> > > +		writel(data, panel->link_base + offset);
> > > +		break;
> > > +	case DP_STREAM_2:
> > > +		writel(data, panel->mst2link_base + offset);
> > > +		break;
> > > +	case DP_STREAM_3:
> > > +		writel(data, panel->mst3link_base + offset);
> > > +		break;
> > > +	default:
> > > +		DRM_ERROR("error stream_id\n");
> > > +		break;
> > > +	}
> > >   }
> > >   static inline void msm_dp_write_pn(struct msm_dp_panel_private *panel,
> > > @@ -444,38 +471,51 @@ void msm_dp_panel_clear_dsc_dto(struct msm_dp_panel *msm_dp_panel)
> > >   static void msm_dp_panel_send_vsc_sdp(struct msm_dp_panel_private *panel, struct dp_sdp *vsc_sdp)
> > >   {
> > > +	u32 id = panel->msm_dp_panel.stream_id;
> > >   	u32 header[2];
> > >   	u32 val;
> > >   	int i;
> > >   	u32 offset = 0;
> > > -	if (panel->msm_dp_panel.stream_id == DP_STREAM_1)
> > > +	if (id == DP_STREAM_1)
> > >   		offset = MMSS_DP1_GENERIC0_0 - MMSS_DP_GENERIC0_0;
> > >   	msm_dp_utils_pack_sdp_header(&vsc_sdp->sdp_header, header);
> > > -	msm_dp_write_link(panel, MMSS_DP_GENERIC0_0 + offset, header[0]);
> > > -	msm_dp_write_link(panel, MMSS_DP_GENERIC0_1 + offset, header[1]);
> > > +	msm_dp_write_link(panel, id > 1 ?
> > > +			  MMSS_DP_MSTLINK_GENERIC0_0 : MMSS_DP_GENERIC0_0 + offset,
> > > +			  header[0]);
> > > +	msm_dp_write_link(panel, id > 1 ?
> > > +			  MMSS_DP_MSTLINK_GENERIC0_1 : MMSS_DP_GENERIC0_1 + offset,
> > > +			  header[1]);
> > >   	for (i = 0; i < sizeof(vsc_sdp->db); i += 4) {
> > >   		val = ((vsc_sdp->db[i]) | (vsc_sdp->db[i + 1] << 8) | (vsc_sdp->db[i + 2] << 16) |
> > >   		       (vsc_sdp->db[i + 3] << 24));
> > > -		msm_dp_write_link(panel, MMSS_DP_GENERIC0_2 + i + offset, val);
> > > +
> > > +		msm_dp_write_link(panel, id > 1 ?
> > > +				  MMSS_DP_MSTLINK_GENERIC0_2 + i : MMSS_DP_GENERIC0_2 + i + offset,
> > > +				  val);
> > >   	}
> > >   }
> > >   static void msm_dp_panel_update_sdp(struct msm_dp_panel_private *panel)
> > >   {
> > > +	u32 id = panel->msm_dp_panel.stream_id;
> > >   	u32 hw_revision = panel->msm_dp_panel.hw_revision;
> > >   	u32 offset = 0;
> > > -	if (panel->msm_dp_panel.stream_id == DP_STREAM_1)
> > > +	if (id == DP_STREAM_1)
> > >   		offset = MMSS_DP1_SDP_CFG3 - MMSS_DP_SDP_CFG3;
> > >   	if (hw_revision >= DP_HW_VERSION_1_0 &&
> > >   	    hw_revision < DP_HW_VERSION_1_2) {
> > > -		msm_dp_write_link(panel, MMSS_DP_SDP_CFG3 + offset, UPDATE_SDP);
> > > -		msm_dp_write_link(panel, MMSS_DP_SDP_CFG3 + offset, 0x0);
> > > +		msm_dp_write_link(panel, id > 1 ?
> > > +				  MMSS_DP_MSTLINK_SDP_CFG3 : MMSS_DP_SDP_CFG3 + offset,
> > > +				  UPDATE_SDP);
> > > +		msm_dp_write_link(panel, id > 1 ?
> > > +				  MMSS_DP_MSTLINK_SDP_CFG3 : MMSS_DP_SDP_CFG3 + offset,
> > > +				  0x0);
> > >   	}
> > >   }
> > > @@ -483,26 +523,34 @@ void msm_dp_panel_enable_vsc_sdp(struct msm_dp_panel *msm_dp_panel, struct dp_sd
> > >   {
> > >   	struct msm_dp_panel_private *panel =
> > >   		container_of(msm_dp_panel, struct msm_dp_panel_private, msm_dp_panel);
> > > +	u32 id = msm_dp_panel->stream_id;
> > >   	u32 cfg, cfg2, misc;
> > >   	u32 misc_reg_offset = 0;
> > >   	u32 sdp_cfg_offset = 0;
> > >   	u32 sdp_cfg2_offset = 0;
> > > -	if (msm_dp_panel->stream_id == DP_STREAM_1) {
> > > +	if (id == DP_STREAM_1) {
> > >   		misc_reg_offset = REG_DP1_MISC1_MISC0 - REG_DP_MISC1_MISC0;
> > >   		sdp_cfg_offset = MMSS_DP1_SDP_CFG - MMSS_DP_SDP_CFG;
> > >   		sdp_cfg2_offset = MMSS_DP1_SDP_CFG2 - MMSS_DP_SDP_CFG2;
> > >   	}
> > > -	cfg = msm_dp_read_link(panel, MMSS_DP_SDP_CFG + sdp_cfg_offset);
> > > -	cfg2 = msm_dp_read_link(panel, MMSS_DP_SDP_CFG2 + sdp_cfg2_offset);
> > > -	misc = msm_dp_read_link(panel, REG_DP_MISC1_MISC0 + misc_reg_offset);
> > > +	cfg = msm_dp_read_link(panel, id > 1 ?
> > > +			       MMSS_DP_MSTLINK_SDP_CFG : MMSS_DP_SDP_CFG + sdp_cfg_offset);
> > > +	cfg2 = msm_dp_read_link(panel, id > 1 ?
> > > +				MMSS_DP_MSTLINK_SDP_CFG2 : MMSS_DP_SDP_CFG2 + sdp_cfg2_offset);
> > > +	misc = msm_dp_read_link(panel, id > 1 ?
> > > +				REG_DP_MSTLINK_MISC1_MISC0 : REG_DP_MISC1_MISC0 + misc_reg_offset);
> > >   	cfg |= GEN0_SDP_EN;
> > > -	msm_dp_write_link(panel, MMSS_DP_SDP_CFG + sdp_cfg_offset, cfg);
> > > -
> > >   	cfg2 |= GENERIC0_SDPSIZE_VALID;
> > > -	msm_dp_write_link(panel, MMSS_DP_SDP_CFG2 + sdp_cfg2_offset, cfg2);
> > > +
> > > +	msm_dp_write_link(panel, id > 1 ?
> > > +			  MMSS_DP_MSTLINK_SDP_CFG : MMSS_DP_SDP_CFG + sdp_cfg_offset,
> > > +			  cfg);
> > > +	msm_dp_write_link(panel, id > 1 ?
> > > +			  MMSS_DP_MSTLINK_SDP_CFG2 : MMSS_DP_SDP_CFG2 + sdp_cfg2_offset,
> > > +			  cfg2);
> > >   	msm_dp_panel_send_vsc_sdp(panel, vsc_sdp);
> > > @@ -512,7 +560,9 @@ void msm_dp_panel_enable_vsc_sdp(struct msm_dp_panel *msm_dp_panel, struct dp_sd
> > >   	drm_dbg_dp(panel->drm_dev, "vsc sdp enable=1\n");
> > >   	pr_debug("misc settings = 0x%x\n", misc);
> > > -	msm_dp_write_link(panel, REG_DP_MISC1_MISC0 + misc_reg_offset, misc);
> > > +	msm_dp_write_link(panel, id > 1 ?
> > > +			  REG_DP_MSTLINK_MISC1_MISC0 : REG_DP_MISC1_MISC0 + misc_reg_offset,
> > > +			  misc);
> > >   	msm_dp_panel_update_sdp(panel);
> > >   }
> > > @@ -521,26 +571,34 @@ void msm_dp_panel_disable_vsc_sdp(struct msm_dp_panel *msm_dp_panel)
> > >   {
> > >   	struct msm_dp_panel_private *panel =
> > >   		container_of(msm_dp_panel, struct msm_dp_panel_private, msm_dp_panel);
> > > +	u32 id = msm_dp_panel->stream_id;
> > >   	u32 cfg, cfg2, misc;
> > >   	u32 misc_reg_offset = 0;
> > >   	u32 sdp_cfg_offset = 0;
> > >   	u32 sdp_cfg2_offset = 0;
> > > -	if (msm_dp_panel->stream_id == DP_STREAM_1) {
> > > +	if (id == DP_STREAM_1) {
> > >   		misc_reg_offset = REG_DP1_MISC1_MISC0 - REG_DP_MISC1_MISC0;
> > >   		sdp_cfg_offset = MMSS_DP1_SDP_CFG - MMSS_DP_SDP_CFG;
> > >   		sdp_cfg2_offset = MMSS_DP1_SDP_CFG2 - MMSS_DP_SDP_CFG2;
> > >   	}
> > > -	cfg = msm_dp_read_link(panel, MMSS_DP_SDP_CFG + sdp_cfg_offset);
> > > -	cfg2 = msm_dp_read_link(panel, MMSS_DP_SDP_CFG2 + sdp_cfg2_offset);
> > > -	misc = msm_dp_read_link(panel, REG_DP_MISC1_MISC0 + misc_reg_offset);
> > > +	cfg = msm_dp_read_link(panel, id > 1 ?
> > > +			       MMSS_DP_MSTLINK_SDP_CFG : MMSS_DP_SDP_CFG + sdp_cfg_offset);
> > > +	cfg2 = msm_dp_read_link(panel, id > 1 ?
> > > +				MMSS_DP_MSTLINK_SDP_CFG2 : MMSS_DP_SDP_CFG2 + sdp_cfg2_offset);
> > > +	misc = msm_dp_read_link(panel, id > 1 ?
> > > +				REG_DP_MSTLINK_MISC1_MISC0 : REG_DP_MISC1_MISC0 + misc_reg_offset);
> > >   	cfg &= ~GEN0_SDP_EN;
> > > -	msm_dp_write_link(panel, MMSS_DP_SDP_CFG + sdp_cfg_offset, cfg);
> > > -
> > >   	cfg2 &= ~GENERIC0_SDPSIZE_VALID;
> > > -	msm_dp_write_link(panel, MMSS_DP_SDP_CFG2 + sdp_cfg2_offset, cfg2);
> > > +
> > > +	msm_dp_write_link(panel, id > 1 ?
> > > +			  MMSS_DP_MSTLINK_SDP_CFG : MMSS_DP_SDP_CFG + sdp_cfg_offset,
> > > +			  cfg);
> > > +	msm_dp_write_link(panel, id > 1 ?
> > > +			  MMSS_DP_MSTLINK_SDP_CFG2 : MMSS_DP_SDP_CFG2 + sdp_cfg2_offset,
> > > +			  cfg2);
> > >   	/* switch back to MSA */
> > >   	misc &= ~DP_MISC1_VSC_SDP;
> > > @@ -548,7 +606,9 @@ void msm_dp_panel_disable_vsc_sdp(struct msm_dp_panel *msm_dp_panel)
> > >   	drm_dbg_dp(panel->drm_dev, "vsc sdp enable=0\n");
> > >   	pr_debug("misc settings = 0x%x\n", misc);
> > > -	msm_dp_write_link(panel, REG_DP_MISC1_MISC0 + misc_reg_offset, misc);
> > > +	msm_dp_write_link(panel, id > 1 ?
> > > +			  REG_DP_MSTLINK_MISC1_MISC0 : REG_DP_MISC1_MISC0 + misc_reg_offset,
> > > +			  misc);
> > >   	msm_dp_panel_update_sdp(panel);
> > >   }
> > > @@ -598,6 +658,7 @@ static int msm_dp_panel_setup_vsc_sdp_yuv_420(struct msm_dp_panel *msm_dp_panel)
> > >   int msm_dp_panel_timing_cfg(struct msm_dp_panel *msm_dp_panel, bool wide_bus_en)
> > >   {
> > > +	u32 id = msm_dp_panel->stream_id;
> > >   	u32 data, total_ver, total_hor;
> > >   	struct msm_dp_panel_private *panel;
> > >   	struct drm_display_mode *drm_mode;
> > > @@ -621,7 +682,7 @@ int msm_dp_panel_timing_cfg(struct msm_dp_panel *msm_dp_panel, bool wide_bus_en)
> > >   		drm_mode->vsync_start - drm_mode->vdisplay,
> > >   		drm_mode->vsync_end - drm_mode->vsync_start);
> > > -	if (msm_dp_panel->stream_id == DP_STREAM_1)
> > > +	if (id == DP_STREAM_1)
> > >   		offset = REG_DP1_TOTAL_HOR_VER - REG_DP_TOTAL_HOR_VER;
> > >   	total_hor = drm_mode->htotal;
> > > @@ -654,10 +715,18 @@ int msm_dp_panel_timing_cfg(struct msm_dp_panel *msm_dp_panel, bool wide_bus_en)
> > >   	msm_dp_active = data;
> > > -	msm_dp_write_link(panel, REG_DP_TOTAL_HOR_VER + offset, total);
> > > -	msm_dp_write_link(panel, REG_DP_START_HOR_VER_FROM_SYNC  + offset, sync_start);
> > > -	msm_dp_write_link(panel, REG_DP_HSYNC_VSYNC_WIDTH_POLARITY + offset, width_blanking);
> > > -	msm_dp_write_link(panel, REG_DP_ACTIVE_HOR_VER  + offset, msm_dp_active);
> > > +	msm_dp_write_link(panel,
> > > +			  id > 1 ? REG_DP_MSTLINK_TOTAL_HOR_VER :
> > > +			  REG_DP_TOTAL_HOR_VER + offset, total);
> > > +	msm_dp_write_link(panel,
> > > +			  id > 1 ? REG_DP_MSTLINK_START_HOR_VER_FROM_SYNC :
> > > +			  REG_DP_START_HOR_VER_FROM_SYNC + offset, sync_start);
> > > +	msm_dp_write_link(panel,
> > > +			  id > 1 ? REG_DP_MSTLINK_HSYNC_VSYNC_WIDTH_POLARITY :
> > > +			  REG_DP_HSYNC_VSYNC_WIDTH_POLARITY + offset, width_blanking);
> > > +	msm_dp_write_link(panel,
> > > +			  id > 1 ? REG_DP_MSTLINK_ACTIVE_HOR_VER :
> > > +			  REG_DP_ACTIVE_HOR_VER + offset, msm_dp_active);
> > >   	reg = msm_dp_read_pn(panel, MMSS_DP_INTF_CONFIG);
> > >   	if (wide_bus_en)
> > > @@ -726,6 +795,8 @@ int msm_dp_panel_init_panel_info(struct msm_dp_panel *msm_dp_panel)
> > >   struct msm_dp_panel *msm_dp_panel_get(struct device *dev, struct drm_dp_aux *aux,
> > >   			      struct msm_dp_link *link,
> > >   			      void __iomem *link_base,
> > > +			      void __iomem *mst2link_base,
> > > +			      void __iomem *mst3link_base,
> > >   			      void __iomem *pixel_base)
> > >   {
> > >   	struct msm_dp_panel_private *panel;
> > > @@ -745,6 +816,8 @@ struct msm_dp_panel *msm_dp_panel_get(struct device *dev, struct drm_dp_aux *aux
> > >   	panel->link = link;
> > >   	panel->link_base = link_base;
> > >   	panel->pixel_base = pixel_base;
> > > +	panel->mst2link_base = mst2link_base;
> > > +	panel->mst3link_base = mst3link_base;
> > >   	msm_dp_panel = &panel->msm_dp_panel;
> > >   	msm_dp_panel->max_bw_code = DP_LINK_BW_8_1;
> > > diff --git a/drivers/gpu/drm/msm/dp/dp_panel.h b/drivers/gpu/drm/msm/dp/dp_panel.h
> > > index fe4ac3e47e17..4873c55bd693 100644
> > > --- a/drivers/gpu/drm/msm/dp/dp_panel.h
> > > +++ b/drivers/gpu/drm/msm/dp/dp_panel.h
> > > @@ -101,5 +101,7 @@ static inline bool is_lane_count_valid(u32 lane_count)
> > >   struct msm_dp_panel *msm_dp_panel_get(struct device *dev, struct drm_dp_aux *aux,
> > >   			      struct msm_dp_link *link,
> > >   			      void __iomem *link_base,
> > > +			      void __iomem *mst2link_base,
> > > +			      void __iomem *mst3link_base,
> > >   			      void __iomem *pixel_base);
> > >   #endif /* _DP_PANEL_H_ */
> > > diff --git a/drivers/gpu/drm/msm/dp/dp_reg.h b/drivers/gpu/drm/msm/dp/dp_reg.h
> > > index 295c1161e6b7..1c2d3d8d029d 100644
> > > --- a/drivers/gpu/drm/msm/dp/dp_reg.h
> > > +++ b/drivers/gpu/drm/msm/dp/dp_reg.h
> > > @@ -162,7 +162,6 @@
> > >   #define REG_DP_START_HOR_VER_FROM_SYNC		(0x00000020)
> > >   #define REG_DP_HSYNC_VSYNC_WIDTH_POLARITY	(0x00000024)
> > >   #define REG_DP_ACTIVE_HOR_VER			(0x00000028)
> > > -
> > >   #define REG_DP_MISC1_MISC0			(0x0000002C)
> > >   #define DP_MISC0_SYNCHRONOUS_CLK		(0x00000001)
> > >   #define DP_MISC0_COLORIMETRY_CFG_SHIFT		(0x00000001)
> > > @@ -343,6 +342,21 @@
> > >   #define MMSS_DP1_SDP_CFG2			(0x000004E4)
> > >   #define MMSS_DP1_SDP_CFG3			(0x000004E8)
> > > +#define REG_DP_MSTLINK_CONFIGURATION_CTRL	(0x00000034)
> > > +#define REG_MSTLINK_SOFTWARE_MVID		(0x00000040)
> > > +#define REG_MSTLINK_SOFTWARE_NVID		(0x00000044)
> > > +#define REG_DP_MSTLINK_TOTAL_HOR_VER		(0x00000048)
> > > +#define REG_DP_MSTLINK_START_HOR_VER_FROM_SYNC	(0x0000004C)
> > > +#define REG_DP_MSTLINK_HSYNC_VSYNC_WIDTH_POLARITY	(0x00000050)
> > > +#define REG_DP_MSTLINK_ACTIVE_HOR_VER		(0x00000054)
> > > +#define REG_DP_MSTLINK_MISC1_MISC0		(0x00000058)
> > > +#define MMSS_DP_MSTLINK_GENERIC0_0		(0x000000BC)
> > > +#define MMSS_DP_MSTLINK_GENERIC0_1		(0x000000C0)
> > > +#define MMSS_DP_MSTLINK_GENERIC0_2		(0x000000C4)
> > > +#define MMSS_DP_MSTLINK_SDP_CFG			(0x0000010c)
> > > +#define MMSS_DP_MSTLINK_SDP_CFG2		(0x0000011c)
> > > +#define MMSS_DP_MSTLINK_SDP_CFG3		(0x00000114)
> > > +
> > >   #define MMSS_DP_ASYNC_FIFO_CONFIG		(0x00000088)
> > >   #define REG_DP_PHY_AUX_INTERRUPT_CLEAR          (0x0000004C)
> > > 
> > > -- 
> > > 2.43.0
> > > 
> > 
> 

-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 00/39] drm/msm/dp: Add MST support for MSM chipsets
  2026-05-22  8:07   ` Yongxing Mou
@ 2026-05-25  8:26     ` Dmitry Baryshkov
  0 siblings, 0 replies; 128+ messages in thread
From: Dmitry Baryshkov @ 2026-05-25  8:26 UTC (permalink / raw)
  To: Yongxing Mou
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar

On Fri, May 22, 2026 at 04:07:52PM +0800, Yongxing Mou wrote:
> 
> 
> On 4/12/2026 8:34 AM, Dmitry Baryshkov wrote:
> > On Fri, Apr 10, 2026 at 05:33:35PM +0800, Yongxing Mou wrote:
> > > Add support for Multi-stream transport for MSM chipsets that allow
> > > a single instance of DP controller to send multiple streams.
> > > 
> > > This series has been validated on sa8775p ride platform using multiple
> > > MST dongles and also daisy chain method on both DP0 and DP1 upto 1080P.
> > > 
> > > With 4x4K monitors, due to lack of layer mixers that combination will not
> > > work but this can be supported as well after some rework on the DPU side.
> > > 
> > > In addition, SST was re-validated with all these changes to ensure there
> > > were no regressions.
> > > 
> > > This patch series was made on top of:
> > > 
> > > [1] : https://patchwork.freedesktop.org/series/151522/ (v5 to fix up HPD)
> > > 
> > > Overall, the patch series has been organized in the following way:
> > > 
> > > 1) First set are a couple of fixes made while debugging MST but applicable
> > > to SST as well so go ahead of everything else
> > > 2) Prepare the DP driver to get ready to handle multiple streams. This is the bulk
> > > of the work as current DP driver design had to be adjusted to make this happen.
> > > 3) Finally, new files to handle MST related operations
> > 
> > General suggestion. Please reorg the series into the more logical
> > chunks. If you are touching enable / disable path, then continue doing
> > that until its done (more or less). I'd really like to be able to merge
> > at least a part of the series in the next cycle, but there needs to be a
> > logical "halfway done" moment. Let's define it at the "all paths are
> > refactored, all booleans are in place, we are ready to get MST parts".
> > 
> > I've not found a use for separate bridges in the MST path. Please split
> > the functionality between the MST connector and, maybe, another private
> > object for generic state (like connector -> encoder mapping). Other
> > drivers don't have this issue because they have fixed CRTC -> encoder
> > mapping. MSM doesn't so we need a separate way to store that. It's sad
> > that we can't subclass MST topology manager (or its state). Maybe it
> > would be worth changing that and letting our topology manager handle the
> > assignment in it.
> > 
> > Also, I found a set of warnings while trying to build the code. Please
> > make sure that there are no warnings.
> > 
> Got it, thanks...
> Does this mean I can send a subset first (rebase on latest linux-next and ),
> for example the first twelve patches? They are mainly clean-up changes and
> do not touch the MST part yet.

Yes. Keep the version numbers increasing and document in the cover letter
that it's a spin-off of the MST series.

> > > Note:
> > > Validation for this series has so far been done on the latest linux-next
> > > on LeMans, covering both FB console and Weston.
> > 
> > It wasn't. I couldn't apply it to the latest linux-next. There were
> > fuzz-based rejections because of one of the patches merged some time
> > ago.
> > 
> Will rebase it.>>
> > > Broader validation, including additional Type-C DP use cases, is still
> > > in progress and may lead to some follow-up adjustments in the next
> > > revision. I wanted to post the current version first to collect early
> > > feedback on the overall approach.
> > 
> > It's nice, but please keep in mind that majority of users use Type-C
> > rather than native DP connectors. In some cases your lack of Type-C
> > testing affects the design (e.g. for the HPD handling).
> > 
> > > 
> > > Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
> > 
> 

-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 17/39] drm/msm/dp: Add catalog support for 3rd/4th stream MST
  2026-05-25  8:25       ` Dmitry Baryshkov
@ 2026-05-25 12:01         ` Yongxing Mou
  2026-05-25 13:32           ` Dmitry Baryshkov
  0 siblings, 1 reply; 128+ messages in thread
From: Yongxing Mou @ 2026-05-25 12:01 UTC (permalink / raw)
  To: Dmitry Baryshkov
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel



On 5/25/2026 4:25 PM, Dmitry Baryshkov wrote:
> On Mon, May 25, 2026 at 04:06:17PM +0800, Yongxing Mou wrote:
>>
>>
>> On 4/12/2026 2:24 AM, Dmitry Baryshkov wrote:
>>> Nit, there is no catalog anymore, please fix the subject line.
>>>
>>> On Fri, Apr 10, 2026 at 05:33:52PM +0800, Yongxing Mou wrote:
>>>> To support 4-stream MST, the link clocks for stream 3 and stream 4 are
>>>> controlled by MST_2_LCLK and MST_3_LCLK which share the same register
>>>> definitions but use different base addresses.
>>>>
>>>> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
>>>> ---
>>>>    drivers/gpu/drm/msm/dp/dp_ctrl.c    | 225 ++++++++++++++++++++++--------------
>>>>    drivers/gpu/drm/msm/dp/dp_ctrl.h    |   4 +-
>>>>    drivers/gpu/drm/msm/dp/dp_display.c |  24 +++-
>>>>    drivers/gpu/drm/msm/dp/dp_panel.c   | 135 +++++++++++++++++-----
>>>>    drivers/gpu/drm/msm/dp/dp_panel.h   |   2 +
>>>>    drivers/gpu/drm/msm/dp/dp_reg.h     |  16 ++-
>>>>    6 files changed, 283 insertions(+), 123 deletions(-)
>>>>
>>>> diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c
>>>> index a52bcd9ea2a3..1109b2df21be 100644
>>>> --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c
>>>> +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c
>>>> @@ -118,6 +118,8 @@ struct msm_dp_ctrl_private {
>>>>    	struct msm_dp_link *link;
>>>>    	void __iomem *ahb_base;
>>>>    	void __iomem *link_base;
>>>> +	void __iomem *mst2link_base;
>>>> +	void __iomem *mst3link_base;
>>>>    	struct phy *phy;
>>>> @@ -158,19 +160,45 @@ static inline void msm_dp_write_ahb(struct msm_dp_ctrl_private *ctrl,
>>>>    	writel(data, ctrl->ahb_base + offset);
>>>>    }
>>>> -static inline u32 msm_dp_read_link(struct msm_dp_ctrl_private *ctrl, u32 offset)
>>>> +static inline u32 msm_dp_read_link(struct msm_dp_ctrl_private *ctrl,
>>>> +				   enum msm_dp_stream_id stream_id, u32 offset)
>>>>    {
>>>> -	return readl_relaxed(ctrl->link_base + offset);
>>>> +	switch (stream_id) {
>>>> +	case DP_STREAM_0:
>>>> +	case DP_STREAM_1:
>>>
>>> Continuing the comment from the previous patch:
>>>
>>> Or, can we remap all offsets here? It would be much, much easier to
>>> always pass REG_DP_foo and then here, in the link reading code, remap
>>> the offsets for DP_STREAM_1.
>>>
>> The stream 1 offsets relative to stream 0 are not uniform:
>>      REG_DP_CONFIGURATION_CTRL  0x008 -> 0x400  (diff 0x3F8)
>>      REG_DP_MISC1_MISC0         0x02C -> 0x42C  (diff 0x400)
>>   So remapping inside msm_dp_read/write_link would require a full lookup
>>   table, which seems not help for current status.
>>
>> Can we introduce a small helper msm_dp_stream_reg() that selects the correct
>> register address at the call site:
>> //dp_panel.h
>> static inline u32 msm_dp_stream_reg(enum msm_dp_stream_id id,
>> 				     u32 reg_s0, u32 reg_s1, u32 reg_mst)
>> {
>> 	if (id > DP_STREAM_1)
>> 		return reg_mst;
>> 	return id == DP_STREAM_1 ? reg_s1 : reg_s0;
>> }
> 
> No, that's what I want to get rid of. You are spreading these lookups
> all over the code. Push them to a single function (or a single set of
> functions, one per the stream, but having a single frontend to be called
> by the rest of the driver).
> 
Okay. I should got how to do..
void msm_dp_write_link ()
{
	offset = msm_dp_stream_reg(stream_id, offset);
	switch (stream_id) {
	....
}

msm_dp_stream_reg() will handle all the offset. then callers then simply do:
   msm_dp_write_link(ctrl, panel->stream_id, REG_DP_MISC1_MISC0, val);
Does this match what you had in mind?
>>
>> //dp_panel.c and dp_ctrl.c
>>        u32 reg = msm_dp_stream_reg(stream_id,
>>                                    REG_DP_MISC1_MISC0,
>>                                    REG_DP1_MISC1_MISC0,
>>                                    REG_DP_MSTLINK_MISC1_MISC0);
> 
> And then, if we need to support more streams or if the map for the
> registers change, we'd need to go through all caller sites. No, that's a
> bad idea.
> 
>>        msm_dp_read_link(ctrl, stream_id, reg);
>>>> +		return readl_relaxed(ctrl->link_base + offset);
>>>> +	case DP_STREAM_2:
>>>> +		return readl_relaxed(ctrl->mst2link_base + offset);
>>>> +	case DP_STREAM_3:
>>>> +		return readl_relaxed(ctrl->mst3link_base + offset);
>>>> +	default:
>>>> +		DRM_ERROR("error stream_id\n");
>>>> +		return 0;
>>>> +	}
>>>>    }
>>>>    static inline void msm_dp_write_link(struct msm_dp_ctrl_private *ctrl,
>>>> -			       u32 offset, u32 data)
>>>> +				     enum msm_dp_stream_id stream_id, u32 offset, u32 data)
>>>>    {
>>>>    	/*
>>>>    	 * To make sure link reg writes happens before any other operation,
>>>>    	 * this function uses writel() instread of writel_relaxed()
>>>>    	 */
>>>> -	writel(data, ctrl->link_base + offset);
>>>> +	switch (stream_id) {
>>>> +	case DP_STREAM_0:
>>>> +	case DP_STREAM_1:
>>>> +		writel(data, ctrl->link_base + offset);
>>>> +		break;
>>>> +	case DP_STREAM_2:
>>>> +		writel(data, ctrl->mst2link_base + offset);
>>>> +		break;
>>>> +	case DP_STREAM_3:
>>>> +		writel(data, ctrl->mst3link_base + offset);
>>>> +		break;
>>>> +	default:
>>>> +		DRM_ERROR("error stream_id\n");
>>>> +		break;
>>>> +	}
>>>>    }
>>>>    static int msm_dp_aux_link_configure(struct drm_dp_aux *aux,
>>>> @@ -294,18 +322,18 @@ static void msm_dp_ctrl_psr_mainlink_enable(struct msm_dp_ctrl_private *ctrl)
>>>>    {
>>>>    	u32 val;
>>>> -	val = msm_dp_read_link(ctrl, REG_DP_MAINLINK_CTRL);
>>>> +	val = msm_dp_read_link(ctrl, 0, REG_DP_MAINLINK_CTRL);
>>>>    	val |= DP_MAINLINK_CTRL_ENABLE;
>>>> -	msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, val);
>>>> +	msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_CTRL, val);
>>>
>>> Are those always using the link0 base? If so, can we keep the existing
>>> API for the stream-independent registers and introduce a separate API
>>> call for the stream-dependent calls? Generally, if you keep the existing
>>> function, I think, it becomes easier to squash this and the previous
>>> patches.
>>>
>> Here will always using the link0 base. After introducing the helper func,
>> will squash this patch into the previous one.
>>>>    }
>>>>    static void msm_dp_ctrl_psr_mainlink_disable(struct msm_dp_ctrl_private *ctrl)
>>>>    {
>>>>    	u32 val;
>>>> -	val = msm_dp_read_link(ctrl, REG_DP_MAINLINK_CTRL);
>>>> +	val = msm_dp_read_link(ctrl, 0, REG_DP_MAINLINK_CTRL);
>>>>    	val &= ~DP_MAINLINK_CTRL_ENABLE;
>>>> -	msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, val);
>>>> +	msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_CTRL, val);
>>>>    }
>>>>    static void msm_dp_ctrl_mainlink_enable(struct msm_dp_ctrl_private *ctrl)
>>>> @@ -314,21 +342,21 @@ static void msm_dp_ctrl_mainlink_enable(struct msm_dp_ctrl_private *ctrl)
>>>>    	drm_dbg_dp(ctrl->drm_dev, "enable\n");
>>>> -	mainlink_ctrl = msm_dp_read_link(ctrl, REG_DP_MAINLINK_CTRL);
>>>> +	mainlink_ctrl = msm_dp_read_link(ctrl, 0, REG_DP_MAINLINK_CTRL);
>>>>    	mainlink_ctrl &= ~(DP_MAINLINK_CTRL_RESET |
>>>>    					DP_MAINLINK_CTRL_ENABLE);
>>>> -	msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
>>>> +	msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
>>>>    	mainlink_ctrl |= DP_MAINLINK_CTRL_RESET;
>>>> -	msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
>>>> +	msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
>>>>    	mainlink_ctrl &= ~DP_MAINLINK_CTRL_RESET;
>>>> -	msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
>>>> +	msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
>>>>    	mainlink_ctrl |= (DP_MAINLINK_CTRL_ENABLE |
>>>>    				DP_MAINLINK_FB_BOUNDARY_SEL);
>>>> -	msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
>>>> +	msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
>>>>    }
>>>>    static void msm_dp_ctrl_mainlink_disable(struct msm_dp_ctrl_private *ctrl)
>>>> @@ -337,23 +365,23 @@ static void msm_dp_ctrl_mainlink_disable(struct msm_dp_ctrl_private *ctrl)
>>>>    	drm_dbg_dp(ctrl->drm_dev, "disable\n");
>>>> -	mainlink_ctrl = msm_dp_read_link(ctrl, REG_DP_MAINLINK_CTRL);
>>>> +	mainlink_ctrl = msm_dp_read_link(ctrl, 0, REG_DP_MAINLINK_CTRL);
>>>>    	mainlink_ctrl &= ~DP_MAINLINK_CTRL_ENABLE;
>>>> -	msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
>>>> +	msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
>>>>    }
>>>>    static void msm_dp_setup_peripheral_flush(struct msm_dp_ctrl_private *ctrl)
>>>>    {
>>>>    	u32 mainlink_ctrl;
>>>> -	mainlink_ctrl = msm_dp_read_link(ctrl, REG_DP_MAINLINK_CTRL);
>>>> +	mainlink_ctrl = msm_dp_read_link(ctrl, 0, REG_DP_MAINLINK_CTRL);
>>>>    	if (ctrl->hw_revision >= DP_HW_VERSION_1_2)
>>>>    		mainlink_ctrl |= DP_MAINLINK_FLUSH_MODE_SDE_PERIPH_UPDATE;
>>>>    	else
>>>>    		mainlink_ctrl |= DP_MAINLINK_FLUSH_MODE_UPDATE_SDP;
>>>> -	msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
>>>> +	msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
>>>>    }
>>>>    static bool msm_dp_ctrl_mainlink_ready(struct msm_dp_ctrl_private *ctrl)
>>>> @@ -380,7 +408,7 @@ void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl)
>>>>    	ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl);
>>>>    	reinit_completion(&ctrl->idle_comp);
>>>> -	msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, DP_STATE_CTRL_PUSH_IDLE);
>>>> +	msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, DP_STATE_CTRL_PUSH_IDLE);
>>>>    	if (!wait_for_completion_timeout(&ctrl->idle_comp,
>>>>    			IDLE_PATTERN_COMPLETION_TIMEOUT_JIFFIES))
>>>> @@ -395,7 +423,11 @@ static void msm_dp_ctrl_config_ctrl_streams(struct msm_dp_ctrl_private *ctrl,
>>>>    	u32 config = 0, tbd;
>>>>    	u32 reg_offset = 0;
>>>> -	config = msm_dp_read_link(ctrl, REG_DP_CONFIGURATION_CTRL);
>>>> +	if (msm_dp_panel->stream_id == DP_STREAM_0)
>>>> +		config = msm_dp_read_link(ctrl, 0, REG_DP_CONFIGURATION_CTRL);
>>>> +
>>>> +	if (msm_dp_panel->stream_id == DP_STREAM_1)
>>>> +		reg_offset = REG_DP1_CONFIGURATION_CTRL - REG_DP_CONFIGURATION_CTRL;
>>>>    	if (msm_dp_panel->msm_dp_mode.out_fmt_is_yuv_420)
>>>>    		config |= DP_CONFIGURATION_CTRL_RGB_YUV; /* YUV420 */
>>>> @@ -410,8 +442,10 @@ static void msm_dp_ctrl_config_ctrl_streams(struct msm_dp_ctrl_private *ctrl,
>>>>    	drm_dbg_dp(ctrl->drm_dev, "stream DP_CONFIGURATION_CTRL=0x%x\n", config);
>>>> -	if (msm_dp_panel->stream_id == DP_STREAM_1)
>>>> -		reg_offset = REG_DP1_CONFIGURATION_CTRL - REG_DP_CONFIGURATION_CTRL;
>>>> +	msm_dp_write_link(ctrl, msm_dp_panel->stream_id, msm_dp_panel->stream_id > 1 ?
>>>> +			  REG_DP_MSTLINK_CONFIGURATION_CTRL :
>>>> +			  REG_DP_CONFIGURATION_CTRL + reg_offset, config);
>>>> +
>>>
>>> Yep, it would definitely help to handle these differences in the link
>>> writing code.
>>>
>>>>    }
>>>>    static void msm_dp_ctrl_config_ctrl_link(struct msm_dp_ctrl_private *ctrl)
>>>> @@ -441,7 +475,7 @@ static void msm_dp_ctrl_config_ctrl_link(struct msm_dp_ctrl_private *ctrl)
>>>>    	drm_dbg_dp(ctrl->drm_dev, "link DP_CONFIGURATION_CTRL=0x%x\n", config);
>>>> -	msm_dp_write_link(ctrl, REG_DP_CONFIGURATION_CTRL, config);
>>>> +	msm_dp_write_link(ctrl, 0, REG_DP_CONFIGURATION_CTRL, config);
>>>>    }
>>>>    static void msm_dp_ctrl_lane_mapping(struct msm_dp_ctrl_private *ctrl)
>>>> @@ -454,8 +488,8 @@ static void msm_dp_ctrl_lane_mapping(struct msm_dp_ctrl_private *ctrl)
>>>>    	ln_mapping |= lane_map[2] << LANE2_MAPPING_SHIFT;
>>>>    	ln_mapping |= lane_map[3] << LANE3_MAPPING_SHIFT;
>>>> -	msm_dp_write_link(ctrl, REG_DP_LOGICAL2PHYSICAL_LANE_MAPPING,
>>>> -			ln_mapping);
>>>> +	msm_dp_write_link(ctrl, 0, REG_DP_LOGICAL2PHYSICAL_LANE_MAPPING,
>>>> +			  ln_mapping);
>>>>    }
>>>>    static void msm_dp_ctrl_config_misc1_misc0(struct msm_dp_ctrl_private *ctrl,
>>>> @@ -471,7 +505,8 @@ static void msm_dp_ctrl_config_misc1_misc0(struct msm_dp_ctrl_private *ctrl,
>>>>    	if (msm_dp_panel->stream_id == DP_STREAM_1)
>>>>    		reg_offset = REG_DP1_MISC1_MISC0 - REG_DP_MISC1_MISC0;
>>>> -	misc_val = msm_dp_read_link(ctrl, REG_DP_MISC1_MISC0 + reg_offset);
>>>> +	misc_val = msm_dp_read_link(ctrl, msm_dp_panel->stream_id, msm_dp_panel->stream_id > 1 ?
>>>> +				    REG_DP_MSTLINK_MISC1_MISC0 : REG_DP_MISC1_MISC0 + reg_offset);
>>>>    	/* clear bpp bits */
>>>>    	misc_val &= ~(0x07 << DP_MISC0_TEST_BITS_DEPTH_SHIFT);
>>>> @@ -481,7 +516,10 @@ static void msm_dp_ctrl_config_misc1_misc0(struct msm_dp_ctrl_private *ctrl,
>>>>    	misc_val |= DP_MISC0_SYNCHRONOUS_CLK;
>>>>    	drm_dbg_dp(ctrl->drm_dev, "misc settings = 0x%x\n", misc_val);
>>>> -	msm_dp_write_link(ctrl, REG_DP_MISC1_MISC0 + reg_offset, misc_val);
>>>> +	msm_dp_write_link(ctrl, msm_dp_panel->stream_id,
>>>> +			  msm_dp_panel->stream_id > 1 ?
>>>> +			  REG_DP_MSTLINK_MISC1_MISC0 : REG_DP_MISC1_MISC0 + reg_offset,
>>>> +			  misc_val);
>>>>    }
>>>>    static void msm_dp_ctrl_configure_source_params(struct msm_dp_ctrl_private *ctrl,
>>>> @@ -1307,9 +1345,9 @@ static void msm_dp_ctrl_setup_tr_unit(struct msm_dp_ctrl_private *ctrl)
>>>>    	pr_debug("dp_tu=0x%x, valid_boundary=0x%x, valid_boundary2=0x%x\n",
>>>>    			msm_dp_tu, valid_boundary, valid_boundary2);
>>>> -	msm_dp_write_link(ctrl, REG_DP_VALID_BOUNDARY, valid_boundary);
>>>> -	msm_dp_write_link(ctrl, REG_DP_TU, msm_dp_tu);
>>>> -	msm_dp_write_link(ctrl, REG_DP_VALID_BOUNDARY_2, valid_boundary2);
>>>> +	msm_dp_write_link(ctrl, 0, REG_DP_VALID_BOUNDARY, valid_boundary);
>>>> +	msm_dp_write_link(ctrl, 0, REG_DP_TU, msm_dp_tu);
>>>> +	msm_dp_write_link(ctrl, 0, REG_DP_VALID_BOUNDARY_2, valid_boundary2);
>>>>    }
>>>>    static int msm_dp_ctrl_wait4video_ready(struct msm_dp_ctrl_private *ctrl)
>>>> @@ -1426,7 +1464,7 @@ static int msm_dp_ctrl_set_pattern_state_bit(struct msm_dp_ctrl_private *ctrl,
>>>>    	bit = BIT(state_bit - 1);
>>>>    	drm_dbg_dp(ctrl->drm_dev, "hw: bit=%d train=%d\n", bit, state_bit);
>>>> -	msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, bit);
>>>> +	msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, bit);
>>>>    	bit = BIT(state_bit - 1) << DP_MAINLINK_READY_LINK_TRAINING_SHIFT;
>>>> @@ -1453,7 +1491,7 @@ static int msm_dp_ctrl_link_train_1(struct msm_dp_ctrl_private *ctrl,
>>>>    	delay_us = drm_dp_read_clock_recovery_delay(ctrl->aux,
>>>>    						    ctrl->panel->dpcd, dp_phy, false);
>>>> -	msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, 0);
>>>> +	msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, 0);
>>>>    	*training_step = DP_TRAINING_1;
>>>> @@ -1577,7 +1615,7 @@ static int msm_dp_ctrl_link_train_2(struct msm_dp_ctrl_private *ctrl,
>>>>    	delay_us = drm_dp_read_channel_eq_delay(ctrl->aux,
>>>>    						ctrl->panel->dpcd, dp_phy, false);
>>>> -	msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, 0);
>>>> +	msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, 0);
>>>>    	*training_step = DP_TRAINING_2;
>>>> @@ -1694,7 +1732,7 @@ static int msm_dp_ctrl_link_train(struct msm_dp_ctrl_private *ctrl,
>>>>    	}
>>>>    end:
>>>> -	msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, 0);
>>>> +	msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, 0);
>>>>    	return ret;
>>>>    }
>>>> @@ -1840,34 +1878,34 @@ static int msm_dp_ctrl_enable_mainlink_clocks(struct msm_dp_ctrl_private *ctrl)
>>>>    static void msm_dp_ctrl_enable_sdp(struct msm_dp_ctrl_private *ctrl)
>>>>    {
>>>>    	/* trigger sdp */
>>>> -	msm_dp_write_link(ctrl, MMSS_DP_SDP_CFG3, UPDATE_SDP);
>>>> -	msm_dp_write_link(ctrl, MMSS_DP_SDP_CFG3, 0x0);
>>>> +	msm_dp_write_link(ctrl, 0, MMSS_DP_SDP_CFG3, UPDATE_SDP);
>>>> +	msm_dp_write_link(ctrl, 0, MMSS_DP_SDP_CFG3, 0x0);
>>>>    }
>>>>    static void msm_dp_ctrl_psr_enter(struct msm_dp_ctrl_private *ctrl)
>>>>    {
>>>>    	u32 cmd;
>>>> -	cmd = msm_dp_read_link(ctrl, REG_PSR_CMD);
>>>> +	cmd = msm_dp_read_link(ctrl, 0, REG_PSR_CMD);
>>>>    	cmd &= ~(PSR_ENTER | PSR_EXIT);
>>>>    	cmd |= PSR_ENTER;
>>>>    	msm_dp_ctrl_enable_sdp(ctrl);
>>>> -	msm_dp_write_link(ctrl, REG_PSR_CMD, cmd);
>>>> +	msm_dp_write_link(ctrl, 0, REG_PSR_CMD, cmd);
>>>>    }
>>>>    static void msm_dp_ctrl_psr_exit(struct msm_dp_ctrl_private *ctrl)
>>>>    {
>>>>    	u32 cmd;
>>>> -	cmd = msm_dp_read_link(ctrl, REG_PSR_CMD);
>>>> +	cmd = msm_dp_read_link(ctrl, 0, REG_PSR_CMD);
>>>>    	cmd &= ~(PSR_ENTER | PSR_EXIT);
>>>>    	cmd |= PSR_EXIT;
>>>>    	msm_dp_ctrl_enable_sdp(ctrl);
>>>> -	msm_dp_write_link(ctrl, REG_PSR_CMD, cmd);
>>>> +	msm_dp_write_link(ctrl, 0, REG_PSR_CMD, cmd);
>>>>    }
>>>>    void msm_dp_ctrl_config_psr(struct msm_dp_ctrl *msm_dp_ctrl)
>>>> @@ -1880,9 +1918,9 @@ void msm_dp_ctrl_config_psr(struct msm_dp_ctrl *msm_dp_ctrl)
>>>>    		return;
>>>>    	/* enable PSR1 function */
>>>> -	cfg = msm_dp_read_link(ctrl, REG_PSR_CONFIG);
>>>> +	cfg = msm_dp_read_link(ctrl, 0, REG_PSR_CONFIG);
>>>>    	cfg |= PSR1_SUPPORTED;
>>>> -	msm_dp_write_link(ctrl, REG_PSR_CONFIG, cfg);
>>>> +	msm_dp_write_link(ctrl, 0, REG_PSR_CONFIG, cfg);
>>>>    	msm_dp_ctrl_config_psr_interrupt(ctrl);
>>>>    	msm_dp_ctrl_enable_sdp(ctrl);
>>>> @@ -1921,16 +1959,16 @@ void msm_dp_ctrl_set_psr(struct msm_dp_ctrl *msm_dp_ctrl, bool enter)
>>>>    		}
>>>>    		msm_dp_ctrl_push_idle(msm_dp_ctrl);
>>>> -		msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, 0);
>>>> +		msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, 0);
>>>>    		msm_dp_ctrl_psr_mainlink_disable(ctrl);
>>>>    	} else {
>>>>    		msm_dp_ctrl_psr_mainlink_enable(ctrl);
>>>>    		msm_dp_ctrl_psr_exit(ctrl);
>>>> -		msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, DP_STATE_CTRL_SEND_VIDEO);
>>>> +		msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, DP_STATE_CTRL_SEND_VIDEO);
>>>>    		msm_dp_ctrl_wait4video_ready(ctrl);
>>>> -		msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, 0);
>>>> +		msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, 0);
>>>>    	}
>>>>    }
>>>> @@ -2041,7 +2079,7 @@ static int msm_dp_ctrl_link_maintenance(struct msm_dp_ctrl_private *ctrl)
>>>>    	msm_dp_ctrl_clear_training_pattern(ctrl, DP_PHY_DPRX);
>>>> -	msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, DP_STATE_CTRL_SEND_VIDEO);
>>>> +	msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, DP_STATE_CTRL_SEND_VIDEO);
>>>>    	ret = msm_dp_ctrl_wait4video_ready(ctrl);
>>>>    end:
>>>> @@ -2056,72 +2094,72 @@ static void msm_dp_ctrl_send_phy_pattern(struct msm_dp_ctrl_private *ctrl,
>>>>    	u32 value = 0x0;
>>>>    	/* Make sure to clear the current pattern before starting a new one */
>>>> -	msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, 0x0);
>>>> +	msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, 0x0);
>>>>    	drm_dbg_dp(ctrl->drm_dev, "pattern: %#x\n", pattern);
>>>>    	switch (pattern) {
>>>>    	case DP_PHY_TEST_PATTERN_D10_2:
>>>> -		msm_dp_write_link(ctrl, REG_DP_STATE_CTRL,
>>>> -			      DP_STATE_CTRL_LINK_TRAINING_PATTERN1);
>>>> +		msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL,
>>>> +				  DP_STATE_CTRL_LINK_TRAINING_PATTERN1);
>>>>    		break;
>>>>    	case DP_PHY_TEST_PATTERN_ERROR_COUNT:
>>>>    		value &= ~(1 << 16);
>>>> -		msm_dp_write_link(ctrl, REG_DP_HBR2_COMPLIANCE_SCRAMBLER_RESET,
>>>> -			      value);
>>>> +		msm_dp_write_link(ctrl, 0, REG_DP_HBR2_COMPLIANCE_SCRAMBLER_RESET,
>>>> +				  value);
>>>>    		value |= SCRAMBLER_RESET_COUNT_VALUE;
>>>> -		msm_dp_write_link(ctrl, REG_DP_HBR2_COMPLIANCE_SCRAMBLER_RESET,
>>>> -			      value);
>>>> -		msm_dp_write_link(ctrl, REG_DP_MAINLINK_LEVELS,
>>>> -			      DP_MAINLINK_SAFE_TO_EXIT_LEVEL_2);
>>>> -		msm_dp_write_link(ctrl, REG_DP_STATE_CTRL,
>>>> -			      DP_STATE_CTRL_LINK_SYMBOL_ERR_MEASURE);
>>>> +		msm_dp_write_link(ctrl, 0, REG_DP_HBR2_COMPLIANCE_SCRAMBLER_RESET,
>>>> +				  value);
>>>> +		msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_LEVELS,
>>>> +				  DP_MAINLINK_SAFE_TO_EXIT_LEVEL_2);
>>>> +		msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL,
>>>> +				  DP_STATE_CTRL_LINK_SYMBOL_ERR_MEASURE);
>>>>    		break;
>>>>    	case DP_PHY_TEST_PATTERN_PRBS7:
>>>> -		msm_dp_write_link(ctrl, REG_DP_STATE_CTRL,
>>>> -			      DP_STATE_CTRL_LINK_PRBS7);
>>>> +		msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL,
>>>> +				  DP_STATE_CTRL_LINK_PRBS7);
>>>>    		break;
>>>>    	case DP_PHY_TEST_PATTERN_80BIT_CUSTOM:
>>>> -		msm_dp_write_link(ctrl, REG_DP_STATE_CTRL,
>>>> -			      DP_STATE_CTRL_LINK_TEST_CUSTOM_PATTERN);
>>>> +		msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL,
>>>> +				  DP_STATE_CTRL_LINK_TEST_CUSTOM_PATTERN);
>>>>    		/* 00111110000011111000001111100000 */
>>>> -		msm_dp_write_link(ctrl, REG_DP_TEST_80BIT_CUSTOM_PATTERN_REG0,
>>>> -			      0x3E0F83E0);
>>>> +		msm_dp_write_link(ctrl, 0, REG_DP_TEST_80BIT_CUSTOM_PATTERN_REG0,
>>>> +				  0x3E0F83E0);
>>>>    		/* 00001111100000111110000011111000 */
>>>> -		msm_dp_write_link(ctrl, REG_DP_TEST_80BIT_CUSTOM_PATTERN_REG1,
>>>> -			      0x0F83E0F8);
>>>> +		msm_dp_write_link(ctrl, 0, REG_DP_TEST_80BIT_CUSTOM_PATTERN_REG1,
>>>> +				  0x0F83E0F8);
>>>>    		/* 1111100000111110 */
>>>> -		msm_dp_write_link(ctrl, REG_DP_TEST_80BIT_CUSTOM_PATTERN_REG2,
>>>> -			      0x0000F83E);
>>>> +		msm_dp_write_link(ctrl, 0, REG_DP_TEST_80BIT_CUSTOM_PATTERN_REG2,
>>>> +				  0x0000F83E);
>>>>    		break;
>>>>    	case DP_PHY_TEST_PATTERN_CP2520:
>>>> -		value = msm_dp_read_link(ctrl, REG_DP_MAINLINK_CTRL);
>>>> +		value = msm_dp_read_link(ctrl, 0, REG_DP_MAINLINK_CTRL);
>>>>    		value &= ~DP_MAINLINK_CTRL_SW_BYPASS_SCRAMBLER;
>>>> -		msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, value);
>>>> +		msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_CTRL, value);
>>>>    		value = DP_HBR2_ERM_PATTERN;
>>>> -		msm_dp_write_link(ctrl, REG_DP_HBR2_COMPLIANCE_SCRAMBLER_RESET,
>>>> -			      value);
>>>> +		msm_dp_write_link(ctrl, 0, REG_DP_HBR2_COMPLIANCE_SCRAMBLER_RESET,
>>>> +				  value);
>>>>    		value |= SCRAMBLER_RESET_COUNT_VALUE;
>>>> -		msm_dp_write_link(ctrl, REG_DP_HBR2_COMPLIANCE_SCRAMBLER_RESET,
>>>> -			      value);
>>>> -		msm_dp_write_link(ctrl, REG_DP_MAINLINK_LEVELS,
>>>> -			      DP_MAINLINK_SAFE_TO_EXIT_LEVEL_2);
>>>> -		msm_dp_write_link(ctrl, REG_DP_STATE_CTRL,
>>>> -			      DP_STATE_CTRL_LINK_SYMBOL_ERR_MEASURE);
>>>> -		value = msm_dp_read_link(ctrl, REG_DP_MAINLINK_CTRL);
>>>> +		msm_dp_write_link(ctrl, 0, REG_DP_HBR2_COMPLIANCE_SCRAMBLER_RESET,
>>>> +				  value);
>>>> +		msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_LEVELS,
>>>> +				  DP_MAINLINK_SAFE_TO_EXIT_LEVEL_2);
>>>> +		msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL,
>>>> +				  DP_STATE_CTRL_LINK_SYMBOL_ERR_MEASURE);
>>>> +		value = msm_dp_read_link(ctrl, 0, REG_DP_MAINLINK_CTRL);
>>>>    		value |= DP_MAINLINK_CTRL_ENABLE;
>>>> -		msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, value);
>>>> +		msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_CTRL, value);
>>>>    		break;
>>>>    	case DP_PHY_TEST_PATTERN_SEL_MASK:
>>>> -		msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL,
>>>> -			      DP_MAINLINK_CTRL_ENABLE);
>>>> -		msm_dp_write_link(ctrl, REG_DP_STATE_CTRL,
>>>> -			      DP_STATE_CTRL_LINK_TRAINING_PATTERN4);
>>>> +		msm_dp_write_link(ctrl, 0, REG_DP_MAINLINK_CTRL,
>>>> +				  DP_MAINLINK_CTRL_ENABLE);
>>>> +		msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL,
>>>> +				  DP_STATE_CTRL_LINK_TRAINING_PATTERN4);
>>>>    		break;
>>>>    	default:
>>>> @@ -2149,7 +2187,7 @@ static bool msm_dp_ctrl_send_phy_test_pattern(struct msm_dp_ctrl_private *ctrl)
>>>>    	msm_dp_ctrl_update_phy_vx_px(ctrl, DP_PHY_DPRX);
>>>>    	msm_dp_link_send_test_response(ctrl->link);
>>>> -	pattern_sent = msm_dp_read_link(ctrl, REG_DP_MAINLINK_READY);
>>>> +	pattern_sent = msm_dp_read_link(ctrl, 0, REG_DP_MAINLINK_READY);
>>>>    	switch (pattern_sent) {
>>>>    	case MR_LINK_TRAINING1:
>>>> @@ -2522,8 +2560,14 @@ static void msm_dp_ctrl_config_msa(struct msm_dp_ctrl_private *ctrl,
>>>>    		nvid *= 3;
>>>>    	drm_dbg_dp(ctrl->drm_dev, "mvid=0x%x, nvid=0x%x\n", mvid, nvid);
>>>> -	msm_dp_write_link(ctrl, REG_DP_SOFTWARE_MVID + mvid_reg_off, mvid);
>>>> -	msm_dp_write_link(ctrl, REG_DP_SOFTWARE_NVID + nvid_reg_off, nvid);
>>>> +	msm_dp_write_link(ctrl, msm_dp_panel->stream_id,
>>>> +			  msm_dp_panel->stream_id > 1 ?
>>>> +			  REG_MSTLINK_SOFTWARE_MVID : REG_DP_SOFTWARE_MVID + mvid_reg_off,
>>>> +			  mvid);
>>>> +	msm_dp_write_link(ctrl, msm_dp_panel->stream_id,
>>>> +			  msm_dp_panel->stream_id > 1 ?
>>>> +			  REG_MSTLINK_SOFTWARE_NVID : REG_DP_SOFTWARE_NVID + nvid_reg_off,
>>>> +			  nvid);
>>>>    }
>>>>    int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train)
>>>> @@ -2593,7 +2637,8 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *
>>>>    	msm_dp_ctrl_lane_mapping(ctrl);
>>>>    	msm_dp_setup_peripheral_flush(ctrl);
>>>> -	msm_dp_ctrl_config_ctrl_link(ctrl);
>>>> +	if (msm_dp_panel->stream_id == DP_STREAM_0)
>>>> +		msm_dp_ctrl_config_ctrl_link(ctrl);
>>>>    	msm_dp_ctrl_configure_source_params(ctrl, msm_dp_panel);
>>>> @@ -2607,7 +2652,7 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *
>>>>    	msm_dp_ctrl_setup_tr_unit(ctrl);
>>>> -	msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, DP_STATE_CTRL_SEND_VIDEO);
>>>> +	msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, DP_STATE_CTRL_SEND_VIDEO);
>>>>    	ret = msm_dp_ctrl_wait4video_ready(ctrl);
>>>>    	if (ret)
>>>> @@ -2787,7 +2832,9 @@ struct msm_dp_ctrl *msm_dp_ctrl_get(struct device *dev, struct msm_dp_link *link
>>>>    			struct phy *phy,
>>>>    			int max_stream,
>>>>    			void __iomem *ahb_base,
>>>> -			void __iomem *link_base)
>>>> +			void __iomem *link_base,
>>>> +			void __iomem *mst2link_base,
>>>> +			void __iomem *mst3link_base)
>>>>    {
>>>>    	struct msm_dp_ctrl_private *ctrl;
>>>>    	int ret;
>>>> @@ -2827,6 +2874,8 @@ struct msm_dp_ctrl *msm_dp_ctrl_get(struct device *dev, struct msm_dp_link *link
>>>>    	ctrl->phy      = phy;
>>>>    	ctrl->ahb_base = ahb_base;
>>>>    	ctrl->link_base = link_base;
>>>> +	ctrl->mst2link_base = mst2link_base;
>>>> +	ctrl->mst3link_base = mst3link_base;
>>>>    	ret = msm_dp_ctrl_clk_init(&ctrl->msm_dp_ctrl, max_stream);
>>>>    	if (ret) {
>>>> diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.h b/drivers/gpu/drm/msm/dp/dp_ctrl.h
>>>> index 6fed3ff3a72d..e72d501ac1ce 100644
>>>> --- a/drivers/gpu/drm/msm/dp/dp_ctrl.h
>>>> +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.h
>>>> @@ -31,7 +31,9 @@ struct msm_dp_ctrl *msm_dp_ctrl_get(struct device *dev,
>>>>    				    struct phy *phy,
>>>>    				    int max_stream,
>>>>    				    void __iomem *ahb_base,
>>>> -				    void __iomem *link_base);
>>>> +				    void __iomem *link_base,
>>>> +				    void __iomem *mst2link_base,
>>>> +				    void __iomem *mst3link_base);
>>>>    void msm_dp_ctrl_reset(struct msm_dp_ctrl *msm_dp_ctrl);
>>>>    void msm_dp_ctrl_phy_init(struct msm_dp_ctrl *msm_dp_ctrl);
>>>> diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
>>>> index ff506064a3fa..a924fbd825f7 100644
>>>> --- a/drivers/gpu/drm/msm/dp/dp_display.c
>>>> +++ b/drivers/gpu/drm/msm/dp/dp_display.c
>>>> @@ -85,6 +85,12 @@ struct msm_dp_display_private {
>>>>    	void __iomem *link_base;
>>>>    	size_t link_len;
>>>> +	void __iomem *mst2link_base;
>>>> +	size_t mst2link_len;
>>>> +
>>>> +	void __iomem *mst3link_base;
>>>> +	size_t mst3link_len;
>>>> +
>>>>    	void __iomem *pixel_base[DP_STREAM_MAX];
>>>>    	size_t pixel_len;
>>>> @@ -561,7 +567,8 @@ static int msm_dp_init_sub_modules(struct msm_dp_display_private *dp)
>>>>    		goto error_link;
>>>>    	}
>>>> -	dp->panel = msm_dp_panel_get(dev, dp->aux, dp->link, dp->link_base, dp->pixel_base[0]);
>>>> +	dp->panel = msm_dp_panel_get(dev, dp->aux, dp->link, dp->link_base,
>>>> +				     dp->mst2link_base, dp->mst3link_base, dp->pixel_base[0]);
>>>>    	if (IS_ERR(dp->panel)) {
>>>>    		rc = PTR_ERR(dp->panel);
>>>>    		DRM_ERROR("failed to initialize panel, rc = %d\n", rc);
>>>> @@ -570,7 +577,8 @@ static int msm_dp_init_sub_modules(struct msm_dp_display_private *dp)
>>>>    	}
>>>>    	dp->ctrl = msm_dp_ctrl_get(dev, dp->link, dp->panel, dp->aux,
>>>> -			       phy, dp->max_stream, dp->ahb_base, dp->link_base);
>>>> +			       phy, dp->max_stream, dp->ahb_base,
>>>> +			       dp->link_base, dp->mst2link_base, dp->mst3link_base);
>>>>    	if (IS_ERR(dp->ctrl)) {
>>>>    		rc = PTR_ERR(dp->ctrl);
>>>>    		DRM_ERROR("failed to initialize ctrl, rc = %d\n", rc);
>>>> @@ -883,6 +891,10 @@ void msm_dp_snapshot(struct msm_disp_state *disp_state, struct msm_dp *dp)
>>>>    				    msm_dp_display->aux_base, "dp_aux");
>>>>    	msm_disp_snapshot_add_block(disp_state, msm_dp_display->link_len,
>>>>    				    msm_dp_display->link_base, "dp_link");
>>>> +	msm_disp_snapshot_add_block(disp_state, msm_dp_display->mst2link_len,
>>>> +				    msm_dp_display->mst2link_base, "dp_mst2link");
>>>> +	msm_disp_snapshot_add_block(disp_state, msm_dp_display->mst3link_len,
>>>> +				    msm_dp_display->mst3link_base, "dp_mst3link");
>>>>    	msm_disp_snapshot_add_block(disp_state, msm_dp_display->pixel_len,
>>>>    				    msm_dp_display->pixel_base[0], "dp_p0");
>>>>    	msm_disp_snapshot_add_block(disp_state, msm_dp_display->pixel_len,
>>>> @@ -1228,6 +1240,14 @@ static int msm_dp_display_get_io(struct msm_dp_display_private *display)
>>>>    		}
>>>>    	}
>>>> +	display->mst2link_base = msm_dp_ioremap(pdev, 7, &display->mst2link_len);
>>>> +	if (IS_ERR(display->mst2link_base))
>>>> +		DRM_DEBUG_DP("unable to remap link region: %pe\n", display->mst2link_base);
>>>> +
>>>> +	display->mst3link_base = msm_dp_ioremap(pdev, 8, &display->mst3link_len);
>>>> +	if (IS_ERR(display->mst3link_base))
>>>> +		DRM_DEBUG_DP("unable to remap link region: %pe\n", display->mst3link_base);
>>>> +
>>>>    	return 0;
>>>>    }
>>>> diff --git a/drivers/gpu/drm/msm/dp/dp_panel.c b/drivers/gpu/drm/msm/dp/dp_panel.c
>>>> index 6c88cc7e3037..a8a6297b37e3 100644
>>>> --- a/drivers/gpu/drm/msm/dp/dp_panel.c
>>>> +++ b/drivers/gpu/drm/msm/dp/dp_panel.c
>>>> @@ -25,23 +25,50 @@ struct msm_dp_panel_private {
>>>>    	struct drm_dp_aux *aux;
>>>>    	struct msm_dp_link *link;
>>>>    	void __iomem *link_base;
>>>> +	void __iomem *mst2link_base;
>>>> +	void __iomem *mst3link_base;
>>>>    	void __iomem *pixel_base;
>>>>    	bool panel_on;
>>>>    };
>>>>    static inline u32 msm_dp_read_link(struct msm_dp_panel_private *panel, u32 offset)
>>>>    {
>>>> -	return readl_relaxed(panel->link_base + offset);
>>>> +	switch (panel->msm_dp_panel.stream_id) {
>>>> +	case DP_STREAM_0:
>>>> +	case DP_STREAM_1:
>>>> +		return readl_relaxed(panel->link_base + offset);
>>>> +	case DP_STREAM_2:
>>>> +		return readl_relaxed(panel->mst2link_base + offset);
>>>> +	case DP_STREAM_3:
>>>> +		return readl_relaxed(panel->mst3link_base + offset);
>>>> +	default:
>>>> +		DRM_ERROR("error stream_id\n");
>>>> +		return 0;
>>>> +	}
>>>>    }
>>>>    static inline void msm_dp_write_link(struct msm_dp_panel_private *panel,
>>>> -			       u32 offset, u32 data)
>>>> +					u32 offset, u32 data)
>>>>    {
>>>>    	/*
>>>>    	 * To make sure link reg writes happens before any other operation,
>>>>    	 * this function uses writel() instread of writel_relaxed()
>>>>    	 */
>>>> -	writel(data, panel->link_base + offset);
>>>> +	switch (panel->msm_dp_panel.stream_id) {
>>>> +	case DP_STREAM_0:
>>>> +	case DP_STREAM_1:
>>>> +		writel(data, panel->link_base + offset);
>>>> +		break;
>>>> +	case DP_STREAM_2:
>>>> +		writel(data, panel->mst2link_base + offset);
>>>> +		break;
>>>> +	case DP_STREAM_3:
>>>> +		writel(data, panel->mst3link_base + offset);
>>>> +		break;
>>>> +	default:
>>>> +		DRM_ERROR("error stream_id\n");
>>>> +		break;
>>>> +	}
>>>>    }
>>>>    static inline void msm_dp_write_pn(struct msm_dp_panel_private *panel,
>>>> @@ -444,38 +471,51 @@ void msm_dp_panel_clear_dsc_dto(struct msm_dp_panel *msm_dp_panel)
>>>>    static void msm_dp_panel_send_vsc_sdp(struct msm_dp_panel_private *panel, struct dp_sdp *vsc_sdp)
>>>>    {
>>>> +	u32 id = panel->msm_dp_panel.stream_id;
>>>>    	u32 header[2];
>>>>    	u32 val;
>>>>    	int i;
>>>>    	u32 offset = 0;
>>>> -	if (panel->msm_dp_panel.stream_id == DP_STREAM_1)
>>>> +	if (id == DP_STREAM_1)
>>>>    		offset = MMSS_DP1_GENERIC0_0 - MMSS_DP_GENERIC0_0;
>>>>    	msm_dp_utils_pack_sdp_header(&vsc_sdp->sdp_header, header);
>>>> -	msm_dp_write_link(panel, MMSS_DP_GENERIC0_0 + offset, header[0]);
>>>> -	msm_dp_write_link(panel, MMSS_DP_GENERIC0_1 + offset, header[1]);
>>>> +	msm_dp_write_link(panel, id > 1 ?
>>>> +			  MMSS_DP_MSTLINK_GENERIC0_0 : MMSS_DP_GENERIC0_0 + offset,
>>>> +			  header[0]);
>>>> +	msm_dp_write_link(panel, id > 1 ?
>>>> +			  MMSS_DP_MSTLINK_GENERIC0_1 : MMSS_DP_GENERIC0_1 + offset,
>>>> +			  header[1]);
>>>>    	for (i = 0; i < sizeof(vsc_sdp->db); i += 4) {
>>>>    		val = ((vsc_sdp->db[i]) | (vsc_sdp->db[i + 1] << 8) | (vsc_sdp->db[i + 2] << 16) |
>>>>    		       (vsc_sdp->db[i + 3] << 24));
>>>> -		msm_dp_write_link(panel, MMSS_DP_GENERIC0_2 + i + offset, val);
>>>> +
>>>> +		msm_dp_write_link(panel, id > 1 ?
>>>> +				  MMSS_DP_MSTLINK_GENERIC0_2 + i : MMSS_DP_GENERIC0_2 + i + offset,
>>>> +				  val);
>>>>    	}
>>>>    }
>>>>    static void msm_dp_panel_update_sdp(struct msm_dp_panel_private *panel)
>>>>    {
>>>> +	u32 id = panel->msm_dp_panel.stream_id;
>>>>    	u32 hw_revision = panel->msm_dp_panel.hw_revision;
>>>>    	u32 offset = 0;
>>>> -	if (panel->msm_dp_panel.stream_id == DP_STREAM_1)
>>>> +	if (id == DP_STREAM_1)
>>>>    		offset = MMSS_DP1_SDP_CFG3 - MMSS_DP_SDP_CFG3;
>>>>    	if (hw_revision >= DP_HW_VERSION_1_0 &&
>>>>    	    hw_revision < DP_HW_VERSION_1_2) {
>>>> -		msm_dp_write_link(panel, MMSS_DP_SDP_CFG3 + offset, UPDATE_SDP);
>>>> -		msm_dp_write_link(panel, MMSS_DP_SDP_CFG3 + offset, 0x0);
>>>> +		msm_dp_write_link(panel, id > 1 ?
>>>> +				  MMSS_DP_MSTLINK_SDP_CFG3 : MMSS_DP_SDP_CFG3 + offset,
>>>> +				  UPDATE_SDP);
>>>> +		msm_dp_write_link(panel, id > 1 ?
>>>> +				  MMSS_DP_MSTLINK_SDP_CFG3 : MMSS_DP_SDP_CFG3 + offset,
>>>> +				  0x0);
>>>>    	}
>>>>    }
>>>> @@ -483,26 +523,34 @@ void msm_dp_panel_enable_vsc_sdp(struct msm_dp_panel *msm_dp_panel, struct dp_sd
>>>>    {
>>>>    	struct msm_dp_panel_private *panel =
>>>>    		container_of(msm_dp_panel, struct msm_dp_panel_private, msm_dp_panel);
>>>> +	u32 id = msm_dp_panel->stream_id;
>>>>    	u32 cfg, cfg2, misc;
>>>>    	u32 misc_reg_offset = 0;
>>>>    	u32 sdp_cfg_offset = 0;
>>>>    	u32 sdp_cfg2_offset = 0;
>>>> -	if (msm_dp_panel->stream_id == DP_STREAM_1) {
>>>> +	if (id == DP_STREAM_1) {
>>>>    		misc_reg_offset = REG_DP1_MISC1_MISC0 - REG_DP_MISC1_MISC0;
>>>>    		sdp_cfg_offset = MMSS_DP1_SDP_CFG - MMSS_DP_SDP_CFG;
>>>>    		sdp_cfg2_offset = MMSS_DP1_SDP_CFG2 - MMSS_DP_SDP_CFG2;
>>>>    	}
>>>> -	cfg = msm_dp_read_link(panel, MMSS_DP_SDP_CFG + sdp_cfg_offset);
>>>> -	cfg2 = msm_dp_read_link(panel, MMSS_DP_SDP_CFG2 + sdp_cfg2_offset);
>>>> -	misc = msm_dp_read_link(panel, REG_DP_MISC1_MISC0 + misc_reg_offset);
>>>> +	cfg = msm_dp_read_link(panel, id > 1 ?
>>>> +			       MMSS_DP_MSTLINK_SDP_CFG : MMSS_DP_SDP_CFG + sdp_cfg_offset);
>>>> +	cfg2 = msm_dp_read_link(panel, id > 1 ?
>>>> +				MMSS_DP_MSTLINK_SDP_CFG2 : MMSS_DP_SDP_CFG2 + sdp_cfg2_offset);
>>>> +	misc = msm_dp_read_link(panel, id > 1 ?
>>>> +				REG_DP_MSTLINK_MISC1_MISC0 : REG_DP_MISC1_MISC0 + misc_reg_offset);
>>>>    	cfg |= GEN0_SDP_EN;
>>>> -	msm_dp_write_link(panel, MMSS_DP_SDP_CFG + sdp_cfg_offset, cfg);
>>>> -
>>>>    	cfg2 |= GENERIC0_SDPSIZE_VALID;
>>>> -	msm_dp_write_link(panel, MMSS_DP_SDP_CFG2 + sdp_cfg2_offset, cfg2);
>>>> +
>>>> +	msm_dp_write_link(panel, id > 1 ?
>>>> +			  MMSS_DP_MSTLINK_SDP_CFG : MMSS_DP_SDP_CFG + sdp_cfg_offset,
>>>> +			  cfg);
>>>> +	msm_dp_write_link(panel, id > 1 ?
>>>> +			  MMSS_DP_MSTLINK_SDP_CFG2 : MMSS_DP_SDP_CFG2 + sdp_cfg2_offset,
>>>> +			  cfg2);
>>>>    	msm_dp_panel_send_vsc_sdp(panel, vsc_sdp);
>>>> @@ -512,7 +560,9 @@ void msm_dp_panel_enable_vsc_sdp(struct msm_dp_panel *msm_dp_panel, struct dp_sd
>>>>    	drm_dbg_dp(panel->drm_dev, "vsc sdp enable=1\n");
>>>>    	pr_debug("misc settings = 0x%x\n", misc);
>>>> -	msm_dp_write_link(panel, REG_DP_MISC1_MISC0 + misc_reg_offset, misc);
>>>> +	msm_dp_write_link(panel, id > 1 ?
>>>> +			  REG_DP_MSTLINK_MISC1_MISC0 : REG_DP_MISC1_MISC0 + misc_reg_offset,
>>>> +			  misc);
>>>>    	msm_dp_panel_update_sdp(panel);
>>>>    }
>>>> @@ -521,26 +571,34 @@ void msm_dp_panel_disable_vsc_sdp(struct msm_dp_panel *msm_dp_panel)
>>>>    {
>>>>    	struct msm_dp_panel_private *panel =
>>>>    		container_of(msm_dp_panel, struct msm_dp_panel_private, msm_dp_panel);
>>>> +	u32 id = msm_dp_panel->stream_id;
>>>>    	u32 cfg, cfg2, misc;
>>>>    	u32 misc_reg_offset = 0;
>>>>    	u32 sdp_cfg_offset = 0;
>>>>    	u32 sdp_cfg2_offset = 0;
>>>> -	if (msm_dp_panel->stream_id == DP_STREAM_1) {
>>>> +	if (id == DP_STREAM_1) {
>>>>    		misc_reg_offset = REG_DP1_MISC1_MISC0 - REG_DP_MISC1_MISC0;
>>>>    		sdp_cfg_offset = MMSS_DP1_SDP_CFG - MMSS_DP_SDP_CFG;
>>>>    		sdp_cfg2_offset = MMSS_DP1_SDP_CFG2 - MMSS_DP_SDP_CFG2;
>>>>    	}
>>>> -	cfg = msm_dp_read_link(panel, MMSS_DP_SDP_CFG + sdp_cfg_offset);
>>>> -	cfg2 = msm_dp_read_link(panel, MMSS_DP_SDP_CFG2 + sdp_cfg2_offset);
>>>> -	misc = msm_dp_read_link(panel, REG_DP_MISC1_MISC0 + misc_reg_offset);
>>>> +	cfg = msm_dp_read_link(panel, id > 1 ?
>>>> +			       MMSS_DP_MSTLINK_SDP_CFG : MMSS_DP_SDP_CFG + sdp_cfg_offset);
>>>> +	cfg2 = msm_dp_read_link(panel, id > 1 ?
>>>> +				MMSS_DP_MSTLINK_SDP_CFG2 : MMSS_DP_SDP_CFG2 + sdp_cfg2_offset);
>>>> +	misc = msm_dp_read_link(panel, id > 1 ?
>>>> +				REG_DP_MSTLINK_MISC1_MISC0 : REG_DP_MISC1_MISC0 + misc_reg_offset);
>>>>    	cfg &= ~GEN0_SDP_EN;
>>>> -	msm_dp_write_link(panel, MMSS_DP_SDP_CFG + sdp_cfg_offset, cfg);
>>>> -
>>>>    	cfg2 &= ~GENERIC0_SDPSIZE_VALID;
>>>> -	msm_dp_write_link(panel, MMSS_DP_SDP_CFG2 + sdp_cfg2_offset, cfg2);
>>>> +
>>>> +	msm_dp_write_link(panel, id > 1 ?
>>>> +			  MMSS_DP_MSTLINK_SDP_CFG : MMSS_DP_SDP_CFG + sdp_cfg_offset,
>>>> +			  cfg);
>>>> +	msm_dp_write_link(panel, id > 1 ?
>>>> +			  MMSS_DP_MSTLINK_SDP_CFG2 : MMSS_DP_SDP_CFG2 + sdp_cfg2_offset,
>>>> +			  cfg2);
>>>>    	/* switch back to MSA */
>>>>    	misc &= ~DP_MISC1_VSC_SDP;
>>>> @@ -548,7 +606,9 @@ void msm_dp_panel_disable_vsc_sdp(struct msm_dp_panel *msm_dp_panel)
>>>>    	drm_dbg_dp(panel->drm_dev, "vsc sdp enable=0\n");
>>>>    	pr_debug("misc settings = 0x%x\n", misc);
>>>> -	msm_dp_write_link(panel, REG_DP_MISC1_MISC0 + misc_reg_offset, misc);
>>>> +	msm_dp_write_link(panel, id > 1 ?
>>>> +			  REG_DP_MSTLINK_MISC1_MISC0 : REG_DP_MISC1_MISC0 + misc_reg_offset,
>>>> +			  misc);
>>>>    	msm_dp_panel_update_sdp(panel);
>>>>    }
>>>> @@ -598,6 +658,7 @@ static int msm_dp_panel_setup_vsc_sdp_yuv_420(struct msm_dp_panel *msm_dp_panel)
>>>>    int msm_dp_panel_timing_cfg(struct msm_dp_panel *msm_dp_panel, bool wide_bus_en)
>>>>    {
>>>> +	u32 id = msm_dp_panel->stream_id;
>>>>    	u32 data, total_ver, total_hor;
>>>>    	struct msm_dp_panel_private *panel;
>>>>    	struct drm_display_mode *drm_mode;
>>>> @@ -621,7 +682,7 @@ int msm_dp_panel_timing_cfg(struct msm_dp_panel *msm_dp_panel, bool wide_bus_en)
>>>>    		drm_mode->vsync_start - drm_mode->vdisplay,
>>>>    		drm_mode->vsync_end - drm_mode->vsync_start);
>>>> -	if (msm_dp_panel->stream_id == DP_STREAM_1)
>>>> +	if (id == DP_STREAM_1)
>>>>    		offset = REG_DP1_TOTAL_HOR_VER - REG_DP_TOTAL_HOR_VER;
>>>>    	total_hor = drm_mode->htotal;
>>>> @@ -654,10 +715,18 @@ int msm_dp_panel_timing_cfg(struct msm_dp_panel *msm_dp_panel, bool wide_bus_en)
>>>>    	msm_dp_active = data;
>>>> -	msm_dp_write_link(panel, REG_DP_TOTAL_HOR_VER + offset, total);
>>>> -	msm_dp_write_link(panel, REG_DP_START_HOR_VER_FROM_SYNC  + offset, sync_start);
>>>> -	msm_dp_write_link(panel, REG_DP_HSYNC_VSYNC_WIDTH_POLARITY + offset, width_blanking);
>>>> -	msm_dp_write_link(panel, REG_DP_ACTIVE_HOR_VER  + offset, msm_dp_active);
>>>> +	msm_dp_write_link(panel,
>>>> +			  id > 1 ? REG_DP_MSTLINK_TOTAL_HOR_VER :
>>>> +			  REG_DP_TOTAL_HOR_VER + offset, total);
>>>> +	msm_dp_write_link(panel,
>>>> +			  id > 1 ? REG_DP_MSTLINK_START_HOR_VER_FROM_SYNC :
>>>> +			  REG_DP_START_HOR_VER_FROM_SYNC + offset, sync_start);
>>>> +	msm_dp_write_link(panel,
>>>> +			  id > 1 ? REG_DP_MSTLINK_HSYNC_VSYNC_WIDTH_POLARITY :
>>>> +			  REG_DP_HSYNC_VSYNC_WIDTH_POLARITY + offset, width_blanking);
>>>> +	msm_dp_write_link(panel,
>>>> +			  id > 1 ? REG_DP_MSTLINK_ACTIVE_HOR_VER :
>>>> +			  REG_DP_ACTIVE_HOR_VER + offset, msm_dp_active);
>>>>    	reg = msm_dp_read_pn(panel, MMSS_DP_INTF_CONFIG);
>>>>    	if (wide_bus_en)
>>>> @@ -726,6 +795,8 @@ int msm_dp_panel_init_panel_info(struct msm_dp_panel *msm_dp_panel)
>>>>    struct msm_dp_panel *msm_dp_panel_get(struct device *dev, struct drm_dp_aux *aux,
>>>>    			      struct msm_dp_link *link,
>>>>    			      void __iomem *link_base,
>>>> +			      void __iomem *mst2link_base,
>>>> +			      void __iomem *mst3link_base,
>>>>    			      void __iomem *pixel_base)
>>>>    {
>>>>    	struct msm_dp_panel_private *panel;
>>>> @@ -745,6 +816,8 @@ struct msm_dp_panel *msm_dp_panel_get(struct device *dev, struct drm_dp_aux *aux
>>>>    	panel->link = link;
>>>>    	panel->link_base = link_base;
>>>>    	panel->pixel_base = pixel_base;
>>>> +	panel->mst2link_base = mst2link_base;
>>>> +	panel->mst3link_base = mst3link_base;
>>>>    	msm_dp_panel = &panel->msm_dp_panel;
>>>>    	msm_dp_panel->max_bw_code = DP_LINK_BW_8_1;
>>>> diff --git a/drivers/gpu/drm/msm/dp/dp_panel.h b/drivers/gpu/drm/msm/dp/dp_panel.h
>>>> index fe4ac3e47e17..4873c55bd693 100644
>>>> --- a/drivers/gpu/drm/msm/dp/dp_panel.h
>>>> +++ b/drivers/gpu/drm/msm/dp/dp_panel.h
>>>> @@ -101,5 +101,7 @@ static inline bool is_lane_count_valid(u32 lane_count)
>>>>    struct msm_dp_panel *msm_dp_panel_get(struct device *dev, struct drm_dp_aux *aux,
>>>>    			      struct msm_dp_link *link,
>>>>    			      void __iomem *link_base,
>>>> +			      void __iomem *mst2link_base,
>>>> +			      void __iomem *mst3link_base,
>>>>    			      void __iomem *pixel_base);
>>>>    #endif /* _DP_PANEL_H_ */
>>>> diff --git a/drivers/gpu/drm/msm/dp/dp_reg.h b/drivers/gpu/drm/msm/dp/dp_reg.h
>>>> index 295c1161e6b7..1c2d3d8d029d 100644
>>>> --- a/drivers/gpu/drm/msm/dp/dp_reg.h
>>>> +++ b/drivers/gpu/drm/msm/dp/dp_reg.h
>>>> @@ -162,7 +162,6 @@
>>>>    #define REG_DP_START_HOR_VER_FROM_SYNC		(0x00000020)
>>>>    #define REG_DP_HSYNC_VSYNC_WIDTH_POLARITY	(0x00000024)
>>>>    #define REG_DP_ACTIVE_HOR_VER			(0x00000028)
>>>> -
>>>>    #define REG_DP_MISC1_MISC0			(0x0000002C)
>>>>    #define DP_MISC0_SYNCHRONOUS_CLK		(0x00000001)
>>>>    #define DP_MISC0_COLORIMETRY_CFG_SHIFT		(0x00000001)
>>>> @@ -343,6 +342,21 @@
>>>>    #define MMSS_DP1_SDP_CFG2			(0x000004E4)
>>>>    #define MMSS_DP1_SDP_CFG3			(0x000004E8)
>>>> +#define REG_DP_MSTLINK_CONFIGURATION_CTRL	(0x00000034)
>>>> +#define REG_MSTLINK_SOFTWARE_MVID		(0x00000040)
>>>> +#define REG_MSTLINK_SOFTWARE_NVID		(0x00000044)
>>>> +#define REG_DP_MSTLINK_TOTAL_HOR_VER		(0x00000048)
>>>> +#define REG_DP_MSTLINK_START_HOR_VER_FROM_SYNC	(0x0000004C)
>>>> +#define REG_DP_MSTLINK_HSYNC_VSYNC_WIDTH_POLARITY	(0x00000050)
>>>> +#define REG_DP_MSTLINK_ACTIVE_HOR_VER		(0x00000054)
>>>> +#define REG_DP_MSTLINK_MISC1_MISC0		(0x00000058)
>>>> +#define MMSS_DP_MSTLINK_GENERIC0_0		(0x000000BC)
>>>> +#define MMSS_DP_MSTLINK_GENERIC0_1		(0x000000C0)
>>>> +#define MMSS_DP_MSTLINK_GENERIC0_2		(0x000000C4)
>>>> +#define MMSS_DP_MSTLINK_SDP_CFG			(0x0000010c)
>>>> +#define MMSS_DP_MSTLINK_SDP_CFG2		(0x0000011c)
>>>> +#define MMSS_DP_MSTLINK_SDP_CFG3		(0x00000114)
>>>> +
>>>>    #define MMSS_DP_ASYNC_FIFO_CONFIG		(0x00000088)
>>>>    #define REG_DP_PHY_AUX_INTERRUPT_CLEAR          (0x0000004C)
>>>>
>>>> -- 
>>>> 2.43.0
>>>>
>>>
>>
> 


^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 16/39] drm/msm/dp: use stream_id to change offsets in dp_catalog
  2026-05-25  8:21       ` Dmitry Baryshkov
@ 2026-05-25 12:13         ` Yongxing Mou
  2026-05-25 13:31           ` Dmitry Baryshkov
  0 siblings, 1 reply; 128+ messages in thread
From: Yongxing Mou @ 2026-05-25 12:13 UTC (permalink / raw)
  To: Dmitry Baryshkov
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar



On 5/25/2026 4:21 PM, Dmitry Baryshkov wrote:
> On Mon, May 25, 2026 at 04:06:08PM +0800, Yongxing Mou wrote:
>>
>>
>> On 4/12/2026 2:12 AM, Dmitry Baryshkov wrote:
>>> On Fri, Apr 10, 2026 at 05:33:51PM +0800, Yongxing Mou wrote:
>>>> From: Abhinav Kumar <quic_abhinavk@quicinc.com>
>>>>
>>>> Use the dp_panel's stream_id to adjust the offsets for stream 1 which will
>>>> be used for MST in the dp_catalog.
>>>
>>> Please start by describing the problem.
>>>
>> Got it, thanks.
>>>> Stream 1 share the same link clk with
>>>> stream 0 with different reg offset. Also add additional register defines
>>>> for stream 1.
>>>>
>>>> Streams 2 and 3 are not covered here, as they use separate link clocks and
>>>> require separate handling.
>>>>
>>>> Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
>>>> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
>>>> ---
>>>>    drivers/gpu/drm/msm/dp/dp_ctrl.c  | 24 ++++++++++---
>>>>    drivers/gpu/drm/msm/dp/dp_panel.c | 72 +++++++++++++++++++++++++++------------
>>>>    drivers/gpu/drm/msm/dp/dp_reg.h   | 11 ++++++
>>>>    3 files changed, 81 insertions(+), 26 deletions(-)
>>>>
>>>> diff --git a/drivers/gpu/drm/msm/dp/dp_reg.h b/drivers/gpu/drm/msm/dp/dp_reg.h
>>>> index 3689642b7fc0..295c1161e6b7 100644
>>>> --- a/drivers/gpu/drm/msm/dp/dp_reg.h
>>>> +++ b/drivers/gpu/drm/msm/dp/dp_reg.h
>>>> @@ -332,6 +332,17 @@
>>>>    #define DP_TPG_VIDEO_CONFIG_BPP_8BIT		(0x00000001)
>>>>    #define DP_TPG_VIDEO_CONFIG_RGB			(0x00000004)
>>>> +/* DP MST registers */
>>>
>>> Which register spaces are they from?
>>>
>> Here also from DPTX_*, what about this? /* DP_TX MST registers */
> 
> Why? don't separate them from the registers from the same space. Also,
> please, name them uniformly. Why some of those are REG_DP1 and others
> are MMSS_DP1?
> 
Hmm. So should I just delete this comments and sort them in ascending 
address order? Regarding REG_DP1_ and MMSS_DP1_, this is a historical 
naming convention inherited from the existing code.
>>>> +#define REG_DP1_CONFIGURATION_CTRL		(0x00000400)
>>>> +#define REG_DP1_SOFTWARE_MVID			(0x00000414)
>>>> +#define REG_DP1_SOFTWARE_NVID			(0x00000418)
>>>> +#define REG_DP1_TOTAL_HOR_VER			(0x0000041C)
>>>> +#define REG_DP1_MISC1_MISC0			(0x0000042C)
>>>> +#define MMSS_DP1_GENERIC0_0			(0x00000490)
>>>> +#define MMSS_DP1_SDP_CFG			(0x000004E0)
>>>> +#define MMSS_DP1_SDP_CFG2			(0x000004E4)
>>>> +#define MMSS_DP1_SDP_CFG3			(0x000004E8)
>>>> +
>>>>    #define MMSS_DP_ASYNC_FIFO_CONFIG		(0x00000088)
>>>>    #define REG_DP_PHY_AUX_INTERRUPT_CLEAR          (0x0000004C)
>>>>
> 


^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 16/39] drm/msm/dp: use stream_id to change offsets in dp_catalog
  2026-05-25 12:13         ` Yongxing Mou
@ 2026-05-25 13:31           ` Dmitry Baryshkov
  0 siblings, 0 replies; 128+ messages in thread
From: Dmitry Baryshkov @ 2026-05-25 13:31 UTC (permalink / raw)
  To: Yongxing Mou
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar

On Mon, May 25, 2026 at 08:13:35PM +0800, Yongxing Mou wrote:
> 
> 
> On 5/25/2026 4:21 PM, Dmitry Baryshkov wrote:
> > On Mon, May 25, 2026 at 04:06:08PM +0800, Yongxing Mou wrote:
> > > 
> > > 
> > > On 4/12/2026 2:12 AM, Dmitry Baryshkov wrote:
> > > > On Fri, Apr 10, 2026 at 05:33:51PM +0800, Yongxing Mou wrote:
> > > > > From: Abhinav Kumar <quic_abhinavk@quicinc.com>
> > > > > 
> > > > > Use the dp_panel's stream_id to adjust the offsets for stream 1 which will
> > > > > be used for MST in the dp_catalog.
> > > > 
> > > > Please start by describing the problem.
> > > > 
> > > Got it, thanks.
> > > > > Stream 1 share the same link clk with
> > > > > stream 0 with different reg offset. Also add additional register defines
> > > > > for stream 1.
> > > > > 
> > > > > Streams 2 and 3 are not covered here, as they use separate link clocks and
> > > > > require separate handling.
> > > > > 
> > > > > Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
> > > > > Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
> > > > > ---
> > > > >    drivers/gpu/drm/msm/dp/dp_ctrl.c  | 24 ++++++++++---
> > > > >    drivers/gpu/drm/msm/dp/dp_panel.c | 72 +++++++++++++++++++++++++++------------
> > > > >    drivers/gpu/drm/msm/dp/dp_reg.h   | 11 ++++++
> > > > >    3 files changed, 81 insertions(+), 26 deletions(-)
> > > > > 
> > > > > diff --git a/drivers/gpu/drm/msm/dp/dp_reg.h b/drivers/gpu/drm/msm/dp/dp_reg.h
> > > > > index 3689642b7fc0..295c1161e6b7 100644
> > > > > --- a/drivers/gpu/drm/msm/dp/dp_reg.h
> > > > > +++ b/drivers/gpu/drm/msm/dp/dp_reg.h
> > > > > @@ -332,6 +332,17 @@
> > > > >    #define DP_TPG_VIDEO_CONFIG_BPP_8BIT		(0x00000001)
> > > > >    #define DP_TPG_VIDEO_CONFIG_RGB			(0x00000004)
> > > > > +/* DP MST registers */
> > > > 
> > > > Which register spaces are they from?
> > > > 
> > > Here also from DPTX_*, what about this? /* DP_TX MST registers */
> > 
> > Why? don't separate them from the registers from the same space. Also,
> > please, name them uniformly. Why some of those are REG_DP1 and others
> > are MMSS_DP1?
> > 
> Hmm. So should I just delete this comments and sort them in ascending
> address order?

Yes, please

> Regarding REG_DP1_ and MMSS_DP1_, this is a historical naming
> convention inherited from the existing code.

I see. Maybe it would be more obvious with the followup patches.

> > > > > +#define REG_DP1_CONFIGURATION_CTRL		(0x00000400)
> > > > > +#define REG_DP1_SOFTWARE_MVID			(0x00000414)
> > > > > +#define REG_DP1_SOFTWARE_NVID			(0x00000418)
> > > > > +#define REG_DP1_TOTAL_HOR_VER			(0x0000041C)
> > > > > +#define REG_DP1_MISC1_MISC0			(0x0000042C)
> > > > > +#define MMSS_DP1_GENERIC0_0			(0x00000490)
> > > > > +#define MMSS_DP1_SDP_CFG			(0x000004E0)
> > > > > +#define MMSS_DP1_SDP_CFG2			(0x000004E4)
> > > > > +#define MMSS_DP1_SDP_CFG3			(0x000004E8)
> > > > > +
> > > > >    #define MMSS_DP_ASYNC_FIFO_CONFIG		(0x00000088)
> > > > >    #define REG_DP_PHY_AUX_INTERRUPT_CLEAR          (0x0000004C)
> > > > > 
> > 
> 

-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 17/39] drm/msm/dp: Add catalog support for 3rd/4th stream MST
  2026-05-25 12:01         ` Yongxing Mou
@ 2026-05-25 13:32           ` Dmitry Baryshkov
  0 siblings, 0 replies; 128+ messages in thread
From: Dmitry Baryshkov @ 2026-05-25 13:32 UTC (permalink / raw)
  To: Yongxing Mou
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel

On Mon, May 25, 2026 at 08:01:09PM +0800, Yongxing Mou wrote:
> 
> 
> On 5/25/2026 4:25 PM, Dmitry Baryshkov wrote:
> > On Mon, May 25, 2026 at 04:06:17PM +0800, Yongxing Mou wrote:
> > > 
> > > 
> > > On 4/12/2026 2:24 AM, Dmitry Baryshkov wrote:
> > > > Nit, there is no catalog anymore, please fix the subject line.
> > > > 
> > > > On Fri, Apr 10, 2026 at 05:33:52PM +0800, Yongxing Mou wrote:
> > > > > To support 4-stream MST, the link clocks for stream 3 and stream 4 are
> > > > > controlled by MST_2_LCLK and MST_3_LCLK which share the same register
> > > > > definitions but use different base addresses.
> > > > > 
> > > > > Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
> > > > > ---
> > > > >    drivers/gpu/drm/msm/dp/dp_ctrl.c    | 225 ++++++++++++++++++++++--------------
> > > > >    drivers/gpu/drm/msm/dp/dp_ctrl.h    |   4 +-
> > > > >    drivers/gpu/drm/msm/dp/dp_display.c |  24 +++-
> > > > >    drivers/gpu/drm/msm/dp/dp_panel.c   | 135 +++++++++++++++++-----
> > > > >    drivers/gpu/drm/msm/dp/dp_panel.h   |   2 +
> > > > >    drivers/gpu/drm/msm/dp/dp_reg.h     |  16 ++-
> > > > >    6 files changed, 283 insertions(+), 123 deletions(-)
> > > > > 
> > > > > diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c
> > > > > index a52bcd9ea2a3..1109b2df21be 100644
> > > > > --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c
> > > > > +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c
> > > > > @@ -118,6 +118,8 @@ struct msm_dp_ctrl_private {
> > > > >    	struct msm_dp_link *link;
> > > > >    	void __iomem *ahb_base;
> > > > >    	void __iomem *link_base;
> > > > > +	void __iomem *mst2link_base;
> > > > > +	void __iomem *mst3link_base;
> > > > >    	struct phy *phy;
> > > > > @@ -158,19 +160,45 @@ static inline void msm_dp_write_ahb(struct msm_dp_ctrl_private *ctrl,
> > > > >    	writel(data, ctrl->ahb_base + offset);
> > > > >    }
> > > > > -static inline u32 msm_dp_read_link(struct msm_dp_ctrl_private *ctrl, u32 offset)
> > > > > +static inline u32 msm_dp_read_link(struct msm_dp_ctrl_private *ctrl,
> > > > > +				   enum msm_dp_stream_id stream_id, u32 offset)
> > > > >    {
> > > > > -	return readl_relaxed(ctrl->link_base + offset);
> > > > > +	switch (stream_id) {
> > > > > +	case DP_STREAM_0:
> > > > > +	case DP_STREAM_1:
> > > > 
> > > > Continuing the comment from the previous patch:
> > > > 
> > > > Or, can we remap all offsets here? It would be much, much easier to
> > > > always pass REG_DP_foo and then here, in the link reading code, remap
> > > > the offsets for DP_STREAM_1.
> > > > 
> > > The stream 1 offsets relative to stream 0 are not uniform:
> > >      REG_DP_CONFIGURATION_CTRL  0x008 -> 0x400  (diff 0x3F8)
> > >      REG_DP_MISC1_MISC0         0x02C -> 0x42C  (diff 0x400)
> > >   So remapping inside msm_dp_read/write_link would require a full lookup
> > >   table, which seems not help for current status.
> > > 
> > > Can we introduce a small helper msm_dp_stream_reg() that selects the correct
> > > register address at the call site:
> > > //dp_panel.h
> > > static inline u32 msm_dp_stream_reg(enum msm_dp_stream_id id,
> > > 				     u32 reg_s0, u32 reg_s1, u32 reg_mst)
> > > {
> > > 	if (id > DP_STREAM_1)
> > > 		return reg_mst;
> > > 	return id == DP_STREAM_1 ? reg_s1 : reg_s0;
> > > }
> > 
> > No, that's what I want to get rid of. You are spreading these lookups
> > all over the code. Push them to a single function (or a single set of
> > functions, one per the stream, but having a single frontend to be called
> > by the rest of the driver).
> > 
> Okay. I should got how to do..
> void msm_dp_write_link ()
> {
> 	offset = msm_dp_stream_reg(stream_id, offset);
> 	switch (stream_id) {
> 	....
> }
> 
> msm_dp_stream_reg() will handle all the offset. then callers then simply do:
>   msm_dp_write_link(ctrl, panel->stream_id, REG_DP_MISC1_MISC0, val);
> Does this match what you had in mind?

Yes, exactly!

> > > 
> > > //dp_panel.c and dp_ctrl.c
> > >        u32 reg = msm_dp_stream_reg(stream_id,
> > >                                    REG_DP_MISC1_MISC0,
> > >                                    REG_DP1_MISC1_MISC0,
> > >                                    REG_DP_MSTLINK_MISC1_MISC0);
> > 
> > And then, if we need to support more streams or if the map for the
> > registers change, we'd need to go through all caller sites. No, that's a
> > bad idea.
> > 
> > >        msm_dp_read_link(ctrl, stream_id, reg);
> > > > > +		return readl_relaxed(ctrl->link_base + offset);
> > > > > +	case DP_STREAM_2:
> > > > > +		return readl_relaxed(ctrl->mst2link_base + offset);
> > > > > +	case DP_STREAM_3:
> > > > > +		return readl_relaxed(ctrl->mst3link_base + offset);
> > > > > +	default:
> > > > > +		DRM_ERROR("error stream_id\n");
> > > > > +		return 0;
> > > > > +	}
> > > > >    }

-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 25/39] drm/msm/dp: pass panel to display enable/disable helpers
  2026-04-12  0:05   ` Dmitry Baryshkov
@ 2026-05-28 10:13     ` Yongxing Mou
  0 siblings, 0 replies; 128+ messages in thread
From: Yongxing Mou @ 2026-05-28 10:13 UTC (permalink / raw)
  To: Dmitry Baryshkov
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel



On 4/12/2026 8:05 AM, Dmitry Baryshkov wrote:
> On Fri, Apr 10, 2026 at 05:34:00PM +0800, Yongxing Mou wrote:
>> Pass struct msm_dp_panel to the display enable/disable helpers to make
>> them easier to reuse for MST stream handling.
>>
>> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
>> ---
>>   drivers/gpu/drm/msm/dp/dp_display.c | 18 ++++++++++--------
>>   1 file changed, 10 insertions(+), 8 deletions(-)
>>
>> diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
>> index 7b3b9160e005..88a078e53dc1 100644
>> --- a/drivers/gpu/drm/msm/dp/dp_display.c
>> +++ b/drivers/gpu/drm/msm/dp/dp_display.c
>> @@ -668,7 +668,8 @@ static int msm_dp_display_prepare(struct msm_dp_display_private *dp)
>>   	return msm_dp_ctrl_prepare_stream_on(dp->ctrl, force_link_train);
>>   }
>>   
>> -static int msm_dp_display_enable(struct msm_dp_display_private *dp)
>> +static int msm_dp_display_enable(struct msm_dp_display_private *dp,
>> +				 struct msm_dp_panel *msm_dp_panel)
> 
> Where is it being used?
> 
Got it..That was my mistake — I’ll pass it inside the function.
>>   {
>>   	int rc = 0;
>>   	struct msm_dp *msm_dp_display = &dp->msm_dp_display;
>> @@ -725,20 +726,21 @@ static void msm_dp_display_audio_notify_disable(struct msm_dp_display_private *d
>>   	msm_dp_display->audio_enabled = false;
>>   }
>>   
>> -static int msm_dp_display_disable(struct msm_dp_display_private *dp)
>> +static int msm_dp_display_disable(struct msm_dp_display_private *dp,
>> +				  struct msm_dp_panel *msm_dp_panel)
>>   {
>>   	struct msm_dp *msm_dp_display = &dp->msm_dp_display;
>>   
>>   	if (!msm_dp_display->power_on)
>>   		return 0;
>>   
>> -	msm_dp_panel_disable_vsc_sdp(dp->panel);
>> +	msm_dp_panel_disable_vsc_sdp(msm_dp_panel);
>>   
>> -	msm_dp_ctrl_off_pixel_clk(dp->ctrl, dp->panel->stream_id);
>> +	msm_dp_ctrl_off_pixel_clk(dp->ctrl, msm_dp_panel->stream_id);
>>   
>>   	/* dongle is still connected but sinks are disconnected */
>>   	if (dp->link->sink_count == 0)
>> -		msm_dp_link_psm_config(dp->link, &dp->panel->link_info, true);
>> +		msm_dp_link_psm_config(dp->link, &msm_dp_panel->link_info, true);
>>   
>>   	msm_dp_ctrl_off_link(dp->ctrl);
>>   
>> @@ -1529,14 +1531,14 @@ void msm_dp_display_atomic_enable(struct msm_dp *msm_dp_display)
>>   
>>   	msm_dp_display_set_stream_info(msm_dp_display, dp->panel, 0, 0, 0, 0);
>>   
>> -	rc = msm_dp_display_enable(dp);
>> +	rc = msm_dp_display_enable(dp, dp->panel);
>>   	if (rc)
>>   		DRM_ERROR("DP display enable failed, rc=%d\n", rc);
>>   
>>   	rc = msm_dp_display_post_enable(msm_dp_display);
>>   	if (rc) {
>>   		DRM_ERROR("DP display post enable failed, rc=%d\n", rc);
>> -		msm_dp_display_disable(dp);
>> +		msm_dp_display_disable(dp, dp->panel);
>>   	}
>>   
>>   	drm_dbg_dp(msm_dp_display->drm_dev, "type=%d Done\n", msm_dp_display->connector_type);
>> @@ -1573,7 +1575,7 @@ void msm_dp_display_atomic_post_disable(struct msm_dp *dp)
>>   
>>   	msm_dp_display_audio_notify_disable(msm_dp_display);
>>   
>> -	msm_dp_display_disable(msm_dp_display);
>> +	msm_dp_display_disable(msm_dp_display, msm_dp_display->panel);
>>   
>>   	msm_dp_display_unprepare(msm_dp_display);
>>   }
>>
>> -- 
>> 2.43.0
>>
> 


^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 18/39] drm/msm/dp: add support to send ACT packets for MST
  2026-04-11 18:57   ` Dmitry Baryshkov
@ 2026-06-15  8:21     ` Yongxing Mou
  2026-06-16  0:41       ` Dmitry Baryshkov
  0 siblings, 1 reply; 128+ messages in thread
From: Yongxing Mou @ 2026-06-15  8:21 UTC (permalink / raw)
  To: Dmitry Baryshkov
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar



On 4/12/2026 2:57 AM, Dmitry Baryshkov wrote:
> On Fri, Apr 10, 2026 at 05:33:53PM +0800, Yongxing Mou wrote:
>> From: Abhinav Kumar <quic_abhinavk@quicinc.com>
>>
>> Whenever virtual channel slot allocation changes, the DP
>> source must send the action control trigger sequence to notify
>> the sink about the same. This would be applicable during the
>> start and stop of the pixel stream. Add the infrastructure
>> to be able to send ACT packets for the DP controller when
>> operating in MST mode.
>>
>> Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
>> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
>> ---
>>   drivers/gpu/drm/msm/dp/dp_ctrl.c    | 43 +++++++++++++++++++++++++++++++++++--
>>   drivers/gpu/drm/msm/dp/dp_ctrl.h    |  3 ++-
>>   drivers/gpu/drm/msm/dp/dp_display.c |  3 ++-
>>   drivers/gpu/drm/msm/dp/dp_display.h |  1 +
>>   drivers/gpu/drm/msm/dp/dp_reg.h     |  2 ++
>>   5 files changed, 48 insertions(+), 4 deletions(-)
>>
>> diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c
>> index 1109b2df21be..6f25145ef214 100644
>> --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c
>> +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c
>> @@ -143,6 +143,7 @@ struct msm_dp_ctrl_private {
>>   	bool core_clks_on;
>>   	bool link_clks_on;
>>   	bool stream_clks_on[DP_STREAM_MAX];
>> +	bool mst_active;
>>   };
>>   
>>   static inline u32 msm_dp_read_ahb(const struct msm_dp_ctrl_private *ctrl, u32 offset)
>> @@ -228,6 +229,32 @@ static int msm_dp_aux_link_configure(struct drm_dp_aux *aux,
>>   	return err;
>>   }
>>   
>> +int msm_dp_ctrl_mst_send_act(struct msm_dp_ctrl *msm_dp_ctrl)
>> +{
>> +	struct msm_dp_ctrl_private *ctrl;
>> +	bool act_complete;
>> +
>> +	ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl);
>> +
>> +	if (!ctrl->mst_active)
>> +		return 0;
>> +
>> +	msm_dp_write_link(ctrl, 0, REG_DP_MST_ACT, 0x1);
>> +	/* make sure ACT signal is performed */
>> +	wmb();
>> +
>> +	msleep(20); /* needs 1 frame time */
> 
> 20 ms is 50fps. What if we have 30 or 25 fps?
> 
This hasn’t been considered yet. Do we need to change this to wait based 
on the per-frame duration of the mode?
>> +
>> +	act_complete = msm_dp_read_link(ctrl, 0, REG_DP_MST_ACT);
>> +
> 
> Nit: drop empty line.
> 
Got it.
>> +	if (!act_complete) {
>> +		drm_dbg_dp(ctrl->drm_dev, "MST ACT trigger complete failed\n");
>> +		return -EINVAL;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>>   /*
>>    * NOTE: resetting DP controller will also clear any pending HPD related interrupts
>>    */
>> @@ -2081,6 +2108,10 @@ static int msm_dp_ctrl_link_maintenance(struct msm_dp_ctrl_private *ctrl)
>>   
>>   	msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, DP_STATE_CTRL_SEND_VIDEO);
>>   
>> +	ret = msm_dp_ctrl_mst_send_act(&ctrl->msm_dp_ctrl);
>> +	if (ret)
>> +		return ret;
>> +
>>   	ret = msm_dp_ctrl_wait4video_ready(ctrl);
>>   end:
>>   	return ret;
>> @@ -2277,7 +2308,7 @@ static int msm_dp_ctrl_process_phy_test_request(struct msm_dp_ctrl_private *ctrl
>>   	msm_dp_ctrl_off_pixel_clk(&ctrl->msm_dp_ctrl, ctrl->panel->stream_id);
>>   	msm_dp_ctrl_off_link(&ctrl->msm_dp_ctrl);
>>   
>> -	ret = msm_dp_ctrl_on_link(&ctrl->msm_dp_ctrl);
>> +	ret = msm_dp_ctrl_on_link(&ctrl->msm_dp_ctrl, false);
>>   	if (ret) {
>>   		DRM_ERROR("failed to enable DP link controller\n");
>>   		return ret;
>> @@ -2357,7 +2388,7 @@ static bool msm_dp_ctrl_channel_eq_ok(struct msm_dp_ctrl_private *ctrl)
>>   	return drm_dp_channel_eq_ok(link_status, num_lanes);
>>   }
>>   
>> -int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl)
>> +int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl, bool mst_active)
>>   {
>>   	int rc = 0;
>>   	struct msm_dp_ctrl_private *ctrl;
>> @@ -2375,6 +2406,7 @@ int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl)
>>   
>>   	rate = ctrl->panel->link_info.rate;
>>   	pixel_rate = ctrl->panel->msm_dp_mode.drm_mode.clock;
>> +	ctrl->mst_active = mst_active;
> 
> Should it be set to active only when you actually activate the MST?
> 
This can be moved into on_stream.
>>   
>>   	msm_dp_ctrl_core_clk_enable(&ctrl->msm_dp_ctrl);
>>   
>> @@ -2654,6 +2686,10 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *
>>   
>>   	msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, DP_STATE_CTRL_SEND_VIDEO);
>>   
> 
> I'd rather have the `if mst_active` here rather than in the function.
> 
Got it.
>> +	ret = msm_dp_ctrl_mst_send_act(msm_dp_ctrl);
>> +	if (ret)
>> +		return ret;
>> +
>>   	ret = msm_dp_ctrl_wait4video_ready(ctrl);
>>   	if (ret)
>>   		return ret;
>> @@ -2693,6 +2729,8 @@ void msm_dp_ctrl_off_link(struct msm_dp_ctrl *msm_dp_ctrl)
>>   
>>   	msm_dp_ctrl_reset(&ctrl->msm_dp_ctrl);
>>   
>> +	ctrl->mst_active = false;
>> +
>>   	dev_pm_opp_set_rate(ctrl->dev, 0);
>>   	msm_dp_ctrl_link_clk_disable(&ctrl->msm_dp_ctrl);
>>   
>> @@ -2876,6 +2914,7 @@ struct msm_dp_ctrl *msm_dp_ctrl_get(struct device *dev, struct msm_dp_link *link
>>   	ctrl->link_base = link_base;
>>   	ctrl->mst2link_base = mst2link_base;
>>   	ctrl->mst3link_base = mst3link_base;
>> +	ctrl->mst_active = false;
>>   
>>   	ret = msm_dp_ctrl_clk_init(&ctrl->msm_dp_ctrl, max_stream);
>>   	if (ret) {
>> diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.h b/drivers/gpu/drm/msm/dp/dp_ctrl.h
>> index e72d501ac1ce..f82fd96e412a 100644
>> --- a/drivers/gpu/drm/msm/dp/dp_ctrl.h
>> +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.h
>> @@ -16,7 +16,7 @@ struct msm_dp_ctrl {
>>   
>>   struct phy;
>>   
>> -int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl);
>> +int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl, bool mst_active);
>>   int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *msm_dp_panel);
>>   int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train);
>>   void msm_dp_ctrl_off_link(struct msm_dp_ctrl *msm_dp_ctrl);
>> @@ -51,4 +51,5 @@ void msm_dp_ctrl_disable_irq(struct msm_dp_ctrl *msm_dp_ctrl);
>>   
>>   void msm_dp_ctrl_reinit_phy(struct msm_dp_ctrl *msm_dp_ctrl);
>>   int msm_dp_ctrl_get_stream_cnt(struct msm_dp_ctrl *dp_ctrl);
>> +int msm_dp_ctrl_mst_send_act(struct msm_dp_ctrl *msm_dp_ctrl);
>>   #endif /* _DP_CTRL_H_ */
>> diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
>> index a924fbd825f7..80bb5fc4003f 100644
>> --- a/drivers/gpu/drm/msm/dp/dp_display.c
>> +++ b/drivers/gpu/drm/msm/dp/dp_display.c
>> @@ -660,7 +660,7 @@ static int msm_dp_display_prepare(struct msm_dp_display_private *dp)
>>   		force_link_train = true;
>>   	}
>>   
>> -	rc = msm_dp_ctrl_on_link(dp->ctrl);
>> +	rc = msm_dp_ctrl_on_link(dp->ctrl, msm_dp_display->mst_active);
>>   	if (rc)
>>   		DRM_ERROR("Failed link training (rc=%d)\n", rc);
>>   	// TODO: schedule drm_connector_set_link_status_property()
>> @@ -1547,6 +1547,7 @@ void msm_dp_display_atomic_disable(struct msm_dp *dp)
>>   	msm_dp_display = container_of(dp, struct msm_dp_display_private, msm_dp_display);
>>   
>>   	msm_dp_ctrl_push_idle(msm_dp_display->ctrl);
>> +	msm_dp_ctrl_mst_send_act(msm_dp_display->ctrl);
>>   }
>>   
>>   static void msm_dp_display_unprepare(struct msm_dp_display_private *dp)
>> diff --git a/drivers/gpu/drm/msm/dp/dp_display.h b/drivers/gpu/drm/msm/dp/dp_display.h
>> index b0cfdf215970..fdbe6e4871d9 100644
>> --- a/drivers/gpu/drm/msm/dp/dp_display.h
>> +++ b/drivers/gpu/drm/msm/dp/dp_display.h
>> @@ -19,6 +19,7 @@ struct msm_dp {
>>   	struct drm_bridge *bridge;
>>   	bool audio_enabled;
>>   	bool power_on;
>> +	bool mst_active;
>>   	unsigned int connector_type;
>>   	bool is_edp;
>>   
>> diff --git a/drivers/gpu/drm/msm/dp/dp_reg.h b/drivers/gpu/drm/msm/dp/dp_reg.h
>> index 1c2d3d8d029d..237325d52dbd 100644
>> --- a/drivers/gpu/drm/msm/dp/dp_reg.h
>> +++ b/drivers/gpu/drm/msm/dp/dp_reg.h
>> @@ -156,6 +156,8 @@
>>   #define DP_CONFIGURATION_CTRL_BPC_SHIFT		(0x08)
>>   #define DP_CONFIGURATION_CTRL_LSCLK_DIV_SHIFT	(0x0D)
>>   
>> +#define REG_DP_MST_ACT				(0x00000500)
>> +
>>   #define REG_DP_SOFTWARE_MVID			(0x00000010)
>>   #define REG_DP_SOFTWARE_NVID			(0x00000018)
>>   #define REG_DP_TOTAL_HOR_VER			(0x0000001C)
>>
>> -- 
>> 2.43.0
>>
> 


^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 22/39] drm/msm/dp: Add support for sending VCPF packets in DP controller
  2026-04-11 19:24   ` Dmitry Baryshkov
@ 2026-06-15  8:35     ` Yongxing Mou
  2026-06-16  0:44       ` Dmitry Baryshkov
  0 siblings, 1 reply; 128+ messages in thread
From: Yongxing Mou @ 2026-06-15  8:35 UTC (permalink / raw)
  To: Dmitry Baryshkov
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar



On 4/12/2026 3:24 AM, Dmitry Baryshkov wrote:
> On Fri, Apr 10, 2026 at 05:33:57PM +0800, Yongxing Mou wrote:
>> From: Abhinav Kumar <quic_abhinavk@quicinc.com>
>>
>> The VC Payload Fill (VCPF) sequence is inserted by the DP controller
>> when stream symbols are absent, typically before a stream is disabled.
>> This patch adds support for triggering the VCPF sequence in the MSM DP
>> controller.
>>
>> Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
>> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
>> Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
>> ---
>>   drivers/gpu/drm/msm/dp/dp_ctrl.c    | 55 ++++++++++++++++++++++++++++++++++---
>>   drivers/gpu/drm/msm/dp/dp_ctrl.h    |  2 +-
>>   drivers/gpu/drm/msm/dp/dp_display.c |  2 +-
>>   drivers/gpu/drm/msm/dp/dp_reg.h     |  5 ++++
>>   4 files changed, 58 insertions(+), 6 deletions(-)
>>
>> diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c
>> index e64f81bc8c36..9907f2e56e65 100644
>> --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c
>> +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c
>> @@ -65,9 +65,18 @@
>>   	(PSR_UPDATE_MASK | PSR_CAPTURE_MASK | PSR_EXIT_MASK | \
>>   	PSR_UPDATE_ERROR_MASK | PSR_WAKE_ERROR_MASK)
>>   
>> +#define DP_INTERRUPT_STATUS5 \
>> +	(DP_INTR_DP0_VCPF_SENT | DP_INTR_DP1_VCPF_SENT)
>> +#define DP_INTERRUPT_STATUS5_MASK \
>> +	(DP_INTERRUPT_STATUS5 << DP_INTERRUPT_STATUS_MASK_SHIFT)
>> +
>>   #define DP_CTRL_INTR_READY_FOR_VIDEO     BIT(0)
>>   #define DP_CTRL_INTR_IDLE_PATTERN_SENT  BIT(3)
>>   
>> +#define DP_DP0_PUSH_VCPF		BIT(12)
>> +#define DP_DP1_PUSH_VCPF		BIT(14)
>> +#define DP_MSTLINK_PUSH_VCPF		BIT(12)
> 
> dp_reg.h, under corresponding registers.
> 
Got it.
>> +
>>   #define MR_LINK_TRAINING1  0x8
>>   #define MR_LINK_SYMBOL_ERM 0x80
>>   #define MR_LINK_PRBS7 0x100
>> @@ -405,6 +414,8 @@ void msm_dp_ctrl_enable_irq(struct msm_dp_ctrl *msm_dp_ctrl)
>>   			DP_INTERRUPT_STATUS1_MASK);
>>   	msm_dp_write_ahb(ctrl, REG_DP_INTR_STATUS2,
>>   			DP_INTERRUPT_STATUS2_MASK);
>> +	msm_dp_write_ahb(ctrl, REG_DP_INTR_STATUS5,
>> +			 DP_INTERRUPT_STATUS5_MASK);
>>   }
>>   
>>   void msm_dp_ctrl_disable_irq(struct msm_dp_ctrl *msm_dp_ctrl)
>> @@ -414,6 +425,7 @@ void msm_dp_ctrl_disable_irq(struct msm_dp_ctrl *msm_dp_ctrl)
>>   
>>   	msm_dp_write_ahb(ctrl, REG_DP_INTR_STATUS, 0x00);
>>   	msm_dp_write_ahb(ctrl, REG_DP_INTR_STATUS2, 0x00);
>> +	msm_dp_write_ahb(ctrl, REG_DP_INTR_STATUS5, 0x00);
>>   }
>>   
>>   static u32 msm_dp_ctrl_get_psr_interrupt(struct msm_dp_ctrl_private *ctrl)
>> @@ -433,6 +445,20 @@ static void msm_dp_ctrl_config_psr_interrupt(struct msm_dp_ctrl_private *ctrl)
>>   	msm_dp_write_ahb(ctrl, REG_DP_INTR_MASK4, DP_INTERRUPT_MASK4);
>>   }
>>   
>> +static u32 msm_dp_ctrl_get_mst_interrupt(struct msm_dp_ctrl_private *ctrl)
>> +{
>> +	u32 intr, intr_ack;
>> +
>> +	intr = msm_dp_read_ahb(ctrl, REG_DP_INTR_STATUS5);
>> +	intr &= ~DP_INTERRUPT_STATUS5_MASK;
>> +	intr_ack = (intr & DP_INTERRUPT_STATUS5)
>> +			<< DP_INTERRUPT_STATUS_ACK_SHIFT;
>> +	msm_dp_write_ahb(ctrl, REG_DP_INTR_STATUS5,
>> +			 intr_ack | DP_INTERRUPT_STATUS5_MASK);
>> +
>> +	return intr;
>> +}
>> +
>>   static void msm_dp_ctrl_psr_mainlink_enable(struct msm_dp_ctrl_private *ctrl)
>>   {
>>   	u32 val;
>> @@ -516,14 +542,28 @@ static bool msm_dp_ctrl_mainlink_ready(struct msm_dp_ctrl_private *ctrl)
>>   	return true;
>>   }
>>   
>> -void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl)
>> +void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *msm_dp_panel)
>>   {
>>   	struct msm_dp_ctrl_private *ctrl;
>> +	u32 state = 0x0;
>>   
>>   	ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl);
>>   
>> +	if (!ctrl->mst_active)
>> +		state |= DP_STATE_CTRL_PUSH_IDLE;
>> +	else if (msm_dp_panel->stream_id == DP_STREAM_0)
>> +		state |= DP_DP0_PUSH_VCPF;
>> +	else if (msm_dp_panel->stream_id == DP_STREAM_1)
>> +		state |= DP_DP1_PUSH_VCPF;
>> +	else
>> +		state |= DP_MSTLINK_PUSH_VCPF;
>> +
>>   	reinit_completion(&ctrl->idle_comp);
> 
> And there can't be two streams wanting to push idle at the same time?
> 
In MST, msm_dp_ctrl_push_idle() is only reached from 
msm_dp_display_disable_helper(), which is called from 
msm_dp_mst_stream_disable() / msm_dp_mst_stream_post_disable() in 
dp_mst_drm.c. Both of those
    callers hold mst->mst_lock for the duration of the disable sequence, 
which serializes push_idle (and the wait on idle_comp) across streams. 
So sharing a single idle_comp on the ctrl is safe.
>> -	msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, DP_STATE_CTRL_PUSH_IDLE);
>> +
>> +	msm_dp_write_link(ctrl, msm_dp_panel->stream_id,
>> +			  msm_dp_panel->stream_id > 1 ?
>> +			  REG_DP_MSTLINK_STATE_CTRL : REG_DP_STATE_CTRL,
>> +			  state);
>>   
>>   	if (!wait_for_completion_timeout(&ctrl->idle_comp,
>>   			IDLE_PATTERN_COMPLETION_TIMEOUT_JIFFIES))
>> @@ -2073,7 +2113,7 @@ void msm_dp_ctrl_set_psr(struct msm_dp_ctrl *msm_dp_ctrl, bool enter)
>>   			return;
>>   		}
>>   
>> -		msm_dp_ctrl_push_idle(msm_dp_ctrl);
>> +		msm_dp_ctrl_push_idle(msm_dp_ctrl, ctrl->panel);
>>   		msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, 0);
>>   
>>   		msm_dp_ctrl_psr_mainlink_disable(ctrl);
>> @@ -2183,7 +2223,7 @@ static int msm_dp_ctrl_link_maintenance(struct msm_dp_ctrl_private *ctrl)
>>   	int ret = 0;
>>   	int training_step = DP_TRAINING_NONE;
>>   
>> -	msm_dp_ctrl_push_idle(&ctrl->msm_dp_ctrl);
>> +	msm_dp_ctrl_push_idle(&ctrl->msm_dp_ctrl, ctrl->panel);
> 
> Which panel are we passing and why? It feels to me that we have two
> different cases, one for the MST stream and another one for the SST
> link. Can we handle them separately? (note: I might be wrong here,
> please correct me if I'm wrong).
> 
For SST, we push to bit 8 of MDSS_0_DPTX_0_STATE_CTRL.
For MST, stream0 and stream1 use bit 12 and bit 14 respectively.
For MST stream2 and stream3 use REG_DP_MSTLINK_STATE_CTRL.
Do we need to handle MST and SST separately here?
>>   
>>   	ctrl->link->phy_params.p_level = 0;
>>   	ctrl->link->phy_params.v_level = 0;
>> @@ -3005,6 +3045,13 @@ irqreturn_t msm_dp_ctrl_isr(struct msm_dp_ctrl *msm_dp_ctrl)
>>   		ret = IRQ_HANDLED;
>>   	}
>>   
>> +	isr = msm_dp_ctrl_get_mst_interrupt(ctrl);
>> +	if (isr & (DP_INTR_DP0_VCPF_SENT | DP_INTR_DP1_VCPF_SENT)) {
>> +		drm_dbg_dp(ctrl->drm_dev, "vcpf sent\n");
>> +		complete(&ctrl->idle_comp);
>> +		ret = IRQ_HANDLED;
>> +	}
>> +
>>   	/* DP aux isr */
>>   	isr = msm_dp_ctrl_get_aux_interrupt(ctrl);
>>   	if (isr)
>> diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.h b/drivers/gpu/drm/msm/dp/dp_ctrl.h
>> index c59338199399..cfe7e4496943 100644
>> --- a/drivers/gpu/drm/msm/dp/dp_ctrl.h
>> +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.h
>> @@ -22,7 +22,7 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl,
>>   int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train);
>>   void msm_dp_ctrl_off_link(struct msm_dp_ctrl *msm_dp_ctrl);
>>   void msm_dp_ctrl_off_pixel_clk(struct msm_dp_ctrl *msm_dp_ctrl, enum msm_dp_stream_id stream_id);
>> -void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl);
>> +void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *msm_dp_panel);
>>   irqreturn_t msm_dp_ctrl_isr(struct msm_dp_ctrl *msm_dp_ctrl);
>>   void msm_dp_ctrl_handle_sink_request(struct msm_dp_ctrl *msm_dp_ctrl);
>>   struct msm_dp_ctrl *msm_dp_ctrl_get(struct device *dev,
>> diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
>> index e0bf4dffa6af..e8028402f748 100644
>> --- a/drivers/gpu/drm/msm/dp/dp_display.c
>> +++ b/drivers/gpu/drm/msm/dp/dp_display.c
>> @@ -1557,7 +1557,7 @@ void msm_dp_display_atomic_disable(struct msm_dp *msm_dp_display)
>>   
>>   	dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display);
>>   
>> -	msm_dp_ctrl_push_idle(dp->ctrl);
>> +	msm_dp_ctrl_push_idle(dp->ctrl, dp->panel);
>>   	msm_dp_ctrl_mst_stream_channel_slot_setup(dp->ctrl);
>>   	msm_dp_ctrl_mst_send_act(dp->ctrl);
>>   }
>> diff --git a/drivers/gpu/drm/msm/dp/dp_reg.h b/drivers/gpu/drm/msm/dp/dp_reg.h
>> index 835a55446868..65695fcb48d0 100644
>> --- a/drivers/gpu/drm/msm/dp/dp_reg.h
>> +++ b/drivers/gpu/drm/msm/dp/dp_reg.h
>> @@ -42,9 +42,13 @@
>>   #define DP_INTR_FRAME_END		BIT(6)
>>   #define DP_INTR_CRC_UPDATED		BIT(9)
>>   
>> +#define DP_INTR_DP0_VCPF_SENT		BIT(0)
>> +#define DP_INTR_DP1_VCPF_SENT		BIT(3)
>> +
>>   #define REG_DP_INTR_STATUS3			(0x00000028)
>>   
>>   #define REG_DP_INTR_STATUS4			(0x0000002C)
>> +#define REG_DP_INTR_STATUS5			(0x00000034)
>>   #define PSR_UPDATE_INT				(0x00000001)
>>   #define PSR_CAPTURE_INT				(0x00000004)
>>   #define PSR_EXIT_INT				(0x00000010)
>> @@ -356,6 +360,7 @@
>>   #define REG_DP_DP0_RG				(0x000004F8)
>>   #define REG_DP_DP1_RG				(0x000004FC)
>>   
>> +#define REG_DP_MSTLINK_STATE_CTRL		(0x00000000)
>>   #define REG_DP_MSTLINK_CONFIGURATION_CTRL	(0x00000034)
>>   #define REG_DP_MSTLINK_TIMESLOT_1_32		(0x00000038)
>>   #define REG_DP_MSTLINK_TIMESLOT_33_63		(0x0000003C)
>>
>> -- 
>> 2.43.0
>>
> 


^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 27/39] drm/msm/dp: replace power_on with active_stream_cnt for dp_display
  2026-04-12  0:11   ` Dmitry Baryshkov
@ 2026-06-15  8:45     ` Yongxing Mou
  0 siblings, 0 replies; 128+ messages in thread
From: Yongxing Mou @ 2026-06-15  8:45 UTC (permalink / raw)
  To: Dmitry Baryshkov
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar



On 4/12/2026 8:11 AM, Dmitry Baryshkov wrote:
> On Fri, Apr 10, 2026 at 05:34:02PM +0800, Yongxing Mou wrote:
>> From: Abhinav Kumar <quic_abhinavk@quicinc.com>
>>
>> For DP MST, the link clock and power domain resources stay on until
>> both streams have been disabled OR we receive hotplug. Introduce an
>> active_stream_cnt to track the number of active streams and necessary
>> state handling. Replace the power_on variable with active_stream_cnt
>> as power_on boolean works only for a single stream.
>>
>> Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
>> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
>> ---
>>   drivers/gpu/drm/msm/dp/dp_audio.c   |  2 +-
>>   drivers/gpu/drm/msm/dp/dp_display.c | 38 +++++++++++++++++++------------------
>>   drivers/gpu/drm/msm/dp/dp_display.h |  2 +-
>>   3 files changed, 22 insertions(+), 20 deletions(-)
>>
>> diff --git a/drivers/gpu/drm/msm/dp/dp_audio.c b/drivers/gpu/drm/msm/dp/dp_audio.c
>> index 41018e82efa1..035e230201fd 100644
>> --- a/drivers/gpu/drm/msm/dp/dp_audio.c
>> +++ b/drivers/gpu/drm/msm/dp/dp_audio.c
>> @@ -284,7 +284,7 @@ int msm_dp_audio_prepare(struct drm_bridge *bridge,
>>   	 * such cases check for connection status and bail out if not
>>   	 * connected.
>>   	 */
>> -	if (!msm_dp_display->power_on) {
>> +	if (!msm_dp_display->active_stream_cnt) {
>>   		rc = -EINVAL;
>>   		goto end;
>>   	}
>> diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
>> index 33d8539afee7..e6ecbb3a688e 100644
>> --- a/drivers/gpu/drm/msm/dp/dp_display.c
>> +++ b/drivers/gpu/drm/msm/dp/dp_display.c
>> @@ -657,15 +657,15 @@ int msm_dp_display_prepare(struct msm_dp *msm_dp_display)
>>   	if (dp->link->sink_count == 0)
>>   		return rc;
>>   
>> -	if (!msm_dp_display->power_on) {
>> +	if (!msm_dp_display->active_stream_cnt) {
> 
> Okay, I'm now being lost between the names. When is prepare being
> called? Is it still part of the atomic_enable()? Or atomic_pre_enable()?
>
I’ve renamed the function to msm_dp_display_prepare_link, which looks 
better. We’re still in atomic_enable().
>>   		msm_dp_display_host_phy_init(dp);
>>   		force_link_train = true;
>> -	}
>>   
>> -	rc = msm_dp_ctrl_on_link(dp->ctrl, msm_dp_display->mst_active);
>> -	if (rc)
>> -		DRM_ERROR("Failed link training (rc=%d)\n", rc);
>> -	// TODO: schedule drm_connector_set_link_status_property()
>> +		rc = msm_dp_ctrl_on_link(dp->ctrl, msm_dp_display->mst_active);
>> +		if (rc)
>> +			DRM_ERROR("Failed link training (rc=%d)\n", rc);
>> +		// TODO: schedule drm_connector_set_link_status_property()
>> +	}
> 
> Again, this more than the scope of the commit message.
> 
Got it..I will update the commit message to explain why this change is 
needed. Under MST, preparing the link only needs to be done once by the 
first stream.
>>   
>>   	return msm_dp_ctrl_prepare_stream_on(dp->ctrl, force_link_train);
>>   }
>> @@ -674,18 +674,12 @@ static int msm_dp_display_enable(struct msm_dp_display_private *dp,
>>   				 struct msm_dp_panel *msm_dp_panel)
>>   {
>>   	int rc = 0;
>> -	struct msm_dp *msm_dp_display = &dp->msm_dp_display;
>>   
>>   	drm_dbg_dp(dp->drm_dev, "sink_count=%d\n", dp->link->sink_count);
>> -	if (msm_dp_display->power_on) {
>> -		drm_dbg_dp(dp->drm_dev, "Link already setup, return\n");
>> -		return 0;
>> -	}
>>   
>>   	rc = msm_dp_ctrl_on_stream(dp->ctrl, msm_dp_panel);
>> -	if (!rc)
>> -		msm_dp_display->power_on = true;
>>   
>> +	dp->msm_dp_display.active_stream_cnt++;
>>   	return rc;
>>   }
>>   
>> @@ -731,16 +725,14 @@ static void msm_dp_display_audio_notify_disable(struct msm_dp_display_private *d
>>   static int msm_dp_display_disable(struct msm_dp_display_private *dp,
>>   				  struct msm_dp_panel *msm_dp_panel)
>>   {
>> -	struct msm_dp *msm_dp_display = &dp->msm_dp_display;
>> -
>> -	if (!msm_dp_display->power_on)
>> +	if (!dp->msm_dp_display.active_stream_cnt)
>>   		return 0;
>>   
>>   	msm_dp_panel_disable_vsc_sdp(msm_dp_panel);
>>   
>>   	msm_dp_ctrl_off_pixel_clk(dp->ctrl, msm_dp_panel->stream_id);
>>   
>> -	msm_dp_display->power_on = false;
>> +	dp->msm_dp_display.active_stream_cnt--;
>>   
>>   	drm_dbg_dp(dp->drm_dev, "sink count: %d\n", dp->link->sink_count);
>>   	return 0;
>> @@ -876,7 +868,7 @@ void msm_dp_snapshot(struct msm_disp_state *disp_state, struct msm_dp *dp)
>>   	 * power_on status before dumping DP registers to avoid crash due
>>   	 * to unclocked access
>>   	 */
>> -	if (!dp->power_on)
>> +	if (!dp->active_stream_cnt)
>>   		return;
>>   
>>   	msm_disp_snapshot_add_block(disp_state, msm_dp_display->ahb_len,
>> @@ -1559,6 +1551,11 @@ void msm_dp_display_disable_helper(struct msm_dp *msm_dp_display,
>>   
>>   	dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display);
>>   
>> +	if (!msm_dp_display->active_stream_cnt) {
>> +		drm_dbg_dp(dp->drm_dev, "no active streams\n");
>> +		return;
>> +	}
>> +
>>   	msm_dp_ctrl_push_idle(dp->ctrl, msm_dp_panel);
>>   	msm_dp_ctrl_mst_stream_channel_slot_setup(dp->ctrl);
>>   	msm_dp_ctrl_mst_send_act(dp->ctrl);
>> @@ -1579,6 +1576,11 @@ void msm_dp_display_unprepare(struct msm_dp *msm_dp_display)
>>   
>>   	dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display);
>>   
>> +	if (msm_dp_display->active_stream_cnt) {
>> +		drm_dbg_dp(dp->drm_dev, "stream still active, return\n");
>> +		return;
>> +	}
>> +
>>   	/* dongle is still connected but sinks are disconnected */
>>   	if (dp->link->sink_count == 0)
>>   		msm_dp_link_psm_config(dp->link, &dp->panel->link_info, true);
>> diff --git a/drivers/gpu/drm/msm/dp/dp_display.h b/drivers/gpu/drm/msm/dp/dp_display.h
>> index 0ede5505be58..2548f67cd441 100644
>> --- a/drivers/gpu/drm/msm/dp/dp_display.h
>> +++ b/drivers/gpu/drm/msm/dp/dp_display.h
>> @@ -18,7 +18,7 @@ struct msm_dp {
>>   	struct drm_bridge *next_bridge;
>>   	struct drm_bridge *bridge;
>>   	bool audio_enabled;
>> -	bool power_on;
>> +	u32 active_stream_cnt;
>>   	bool mst_active;
>>   	unsigned int connector_type;
>>   	bool is_edp;
>>
>> -- 
>> 2.43.0
>>
> 


^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 28/39] drm/msm/dp: Mark the SST bridge disconnected when mst is active
  2026-04-12  0:11   ` Dmitry Baryshkov
@ 2026-06-15  8:47     ` Yongxing Mou
  0 siblings, 0 replies; 128+ messages in thread
From: Yongxing Mou @ 2026-06-15  8:47 UTC (permalink / raw)
  To: Dmitry Baryshkov
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar



On 4/12/2026 8:11 AM, Dmitry Baryshkov wrote:
> On Fri, Apr 10, 2026 at 05:34:03PM +0800, Yongxing Mou wrote:
>> From: Abhinav Kumar <quic_abhinavk@quicinc.com>
>>
>> The bridge detect function is only applicable for SST. In MST mode,
>> connector detection is handled by MST bridges. This patch skips
>> detection for the SST bridge when MST is active.
> 
> "This patch"
> 
Sorry, I missed that earlier. I’ve fixed it in this version.
>>
>> Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
>> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
>> Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
>> ---
>>   drivers/gpu/drm/msm/dp/dp_display.c | 3 +++
>>   1 file changed, 3 insertions(+)
>>
> 


^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 29/39] drm/msm/dp: add an API to initialize MST on sink side
  2026-04-12  0:15   ` Dmitry Baryshkov
@ 2026-06-15  9:02     ` Yongxing Mou
  2026-06-16  0:45       ` Dmitry Baryshkov
  0 siblings, 1 reply; 128+ messages in thread
From: Yongxing Mou @ 2026-06-15  9:02 UTC (permalink / raw)
  To: Dmitry Baryshkov
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar



On 4/12/2026 8:15 AM, Dmitry Baryshkov wrote:
> On Fri, Apr 10, 2026 at 05:34:04PM +0800, Yongxing Mou wrote:
>> From: Abhinav Kumar <quic_abhinavk@quicinc.com>
>>
>> If the DP controller is capable of supporting multiple streams
>> then initialize the DP sink in MST mode by programming the DP_MSTM_CTRL
>> DPCD register to enable MST mode.
>>
>> Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
>> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
>> ---
>>   drivers/gpu/drm/msm/dp/dp_display.c | 57 ++++++++++++++++++++++++++++++++-----
>>   1 file changed, 50 insertions(+), 7 deletions(-)
>>
>> diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
>> index 8ae690ce2b9f..abf26951819a 100644
>> --- a/drivers/gpu/drm/msm/dp/dp_display.c
>> +++ b/drivers/gpu/drm/msm/dp/dp_display.c
>> @@ -14,6 +14,7 @@
>>   #include <linux/string_choices.h>
>>   #include <drm/display/drm_dp_aux_bus.h>
>>   #include <drm/display/drm_hdmi_audio_helper.h>
>> +#include <drm/display/drm_dp_mst_helper.h>
>>   #include <drm/drm_edid.h>
>>   
>>   #include "msm_drv.h"
>> @@ -270,6 +271,40 @@ static int msm_dp_display_lttpr_init(struct msm_dp_display_private *dp, u8 *dpcd
>>   	return lttpr_count;
>>   }
>>   
>> +static void msm_dp_display_mst_init(struct msm_dp_display_private *dp)
>> +{
>> +	const unsigned long clear_mstm_ctrl_timeout_us = 100000;
>> +	u8 old_mstm_ctrl;
>> +	struct msm_dp *msm_dp = &dp->msm_dp_display;
>> +	int ret;
>> +
>> +	/* clear sink MST state */
>> +	drm_dp_dpcd_read_byte(dp->aux, DP_MSTM_CTRL, &old_mstm_ctrl);
>> +
>> +	ret = drm_dp_dpcd_write_byte(dp->aux, DP_MSTM_CTRL, 0);
>> +	if (ret < 0) {
>> +		DRM_ERROR("failed to clear DP_MSTM_CTRL, ret=%d\n", ret);
>> +		return;
>> +	}
>> +
>> +	/* add extra delay if MST old state is on*/
>> +	if (old_mstm_ctrl) {
>> +		drm_dbg_dp(dp->drm_dev, "wait %luus to set DP_MSTM_CTRL set 0\n",
>> +			   clear_mstm_ctrl_timeout_us);
>> +		usleep_range(clear_mstm_ctrl_timeout_us,
>> +			     clear_mstm_ctrl_timeout_us + 1000);
> 
> For 100 ms you should be using msleep() instead. But where is that
> timeout coming from?
> 
Will switch to msleep(100).

The 100 ms is an empirical workaround carried over from downstream — 
some sinks don't exit MST immediately after writing DP_MSTM_CTRL = 0, 
and re-enabling MST too quickly broke topology probe. It's not a DP spec 
requirement, and other drivers (drm_dp_mst core, i915, amdgpu, nouveau) 
don't have an equivalent delay.

>> +	}
>> +
>> +	ret = drm_dp_dpcd_write_byte(dp->aux, DP_MSTM_CTRL,
>> +				     DP_MST_EN | DP_UP_REQ_EN | DP_UPSTREAM_IS_SRC);
> 
> Isn't it too early to enable MST? (I don't remember this part of the
> standard).
> 
No, this follows the DP 1.4a MST spec. DP_MSTM_CTRL must be set before 
topology discovery. Topology discovery uses AUX sideband messages, which 
don’t require link training.
So we enable MST first, then discover topology, and only do link 
training and payload allocation later in atomic_enable().
>> +	if (ret < 0) {
>> +		DRM_ERROR("sink MST enablement failed\n");
>> +		return;
>> +	}
>> +
>> +	msm_dp->mst_active = true;
>> +}
>> +
>>   static int msm_dp_display_process_hpd_high(struct msm_dp_display_private *dp)
>>   {
>>   	struct drm_connector *connector = dp->msm_dp_display.connector;
>> @@ -288,14 +323,19 @@ static int msm_dp_display_process_hpd_high(struct msm_dp_display_private *dp)
>>   	if (rc)
>>   		goto end;
>>   
>> -	drm_edid = drm_edid_read_ddc(connector, &dp->aux->ddc);
>> -	drm_edid_connector_update(connector, drm_edid);
>> +	if (!(dp->max_stream > 1) || !drm_dp_read_mst_cap(dp->aux, dp->panel->dpcd)) {
>> +		drm_edid = drm_edid_read_ddc(connector, &dp->aux->ddc);
>> +		drm_edid_connector_update(connector, drm_edid);
>>   
>> -	if (!drm_edid) {
>> -		DRM_ERROR("panel edid read failed\n");
>> -		/* check edid read fail is due to unplug */
>> -		if (!msm_dp_aux_is_link_connected(dp->aux))
>> -			return -ETIMEDOUT;
>> +		if (!drm_edid) {
>> +			DRM_ERROR("panel edid read failed\n");
>> +			/* check edid read fail is due to unplug */
>> +			if (!msm_dp_aux_is_link_connected(dp->aux))
>> +				return -ETIMEDOUT;
>> +		}
>> +
>> +		if (rc)
>> +			goto end;
>>   	}
>>   
>>   	msm_dp_link_process_request(dp->link);
>> @@ -317,6 +357,9 @@ static int msm_dp_display_process_hpd_high(struct msm_dp_display_private *dp)
>>   	 */
>>   	msm_dp_link_psm_config(dp->link, &dp->panel->link_info, false);
>>   
>> +	if (dp->max_stream > 1 && drm_dp_read_mst_cap(dp->aux, dp->panel->dpcd))
>> +		msm_dp_display_mst_init(dp);
>> +
>>   	msm_dp_link_reset_phy_params_vx_px(dp->link);
>>   
>>   end:
>>
>> -- 
>> 2.43.0
>>
> 


^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 37/39] drm/msm/dp: add HPD callback for dp MST
  2026-04-11 22:00   ` Dmitry Baryshkov
@ 2026-06-15 10:05     ` Yongxing Mou
  2026-06-16  0:50       ` Dmitry Baryshkov
  0 siblings, 1 reply; 128+ messages in thread
From: Yongxing Mou @ 2026-06-15 10:05 UTC (permalink / raw)
  To: Dmitry Baryshkov
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar



On 4/12/2026 6:00 AM, Dmitry Baryshkov wrote:
> On Fri, Apr 10, 2026 at 05:34:12PM +0800, Yongxing Mou wrote:
>> From: Abhinav Kumar <quic_abhinavk@quicinc.com>
>>
>> Add HPD callback for the MST module which shall be invoked from the
>> dp_display's HPD handler to perform MST specific operations in case
>> of HPD. In MST case, route the HPD messages to MST module.
>>
>> Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
>> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
>> ---
>>   drivers/gpu/drm/msm/dp/dp_display.c | 23 +++++++++++++++++++----
>>   drivers/gpu/drm/msm/dp/dp_mst_drm.c | 34 ++++++++++++++++++++++++++++++++++
>>   drivers/gpu/drm/msm/dp/dp_mst_drm.h |  1 +
>>   3 files changed, 54 insertions(+), 4 deletions(-)
>>
>> diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
>> index 919767945ba5..ca89e20b7563 100644
>> --- a/drivers/gpu/drm/msm/dp/dp_display.c
>> +++ b/drivers/gpu/drm/msm/dp/dp_display.c
>> @@ -454,6 +454,9 @@ static int msm_dp_hpd_plug_handle(struct msm_dp_display_private *dp)
>>   			dp->msm_dp_display.connector_type,
>>   			dp->link->sink_count);
>>   
>> +	if (dp->plugged)
>> +		return 0;
>> +
>>   	mutex_lock(&dp->plugged_lock);
>>   
>>   	ret = pm_runtime_resume_and_get(&pdev->dev);
>> @@ -556,12 +559,19 @@ static int msm_dp_irq_hpd_handle(struct msm_dp_display_private *dp)
>>   {
>>   	u32 sink_request;
>>   	int rc = 0;
>> +	struct msm_dp *msm_dp_display = &dp->msm_dp_display;
>>   
>>   	/* irq_hpd can happen at either connected or disconnected state */
>>   	drm_dbg_dp(dp->drm_dev, "Before, type=%d, sink_count=%d\n",
>>   			dp->msm_dp_display.connector_type,
>>   			dp->link->sink_count);
>>   
>> +	if (msm_dp_display->mst_active) {
>> +		if (msm_dp_aux_is_link_connected(dp->aux) != ISR_DISCONNECTED)
> 
> Will this work for USB-C?
> 
Hmm not work for USB-C. We can remove this check here, as the IRQ thread 
can handle the disconnect case itself.
>> +			msm_dp_mst_display_hpd_irq(&dp->msm_dp_display);
>> +		return 0;
>> +	}
>> +
>>   	/* check for any test request issued by sink */
>>   	rc = msm_dp_link_process_request(dp->link);
>>   	if (!rc) {
>> @@ -1125,9 +1135,13 @@ static irqreturn_t msm_dp_display_irq_thread(int irq, void *dev_id)
>>   				      connector_status_connected);
>>   
>>   	/* Send HPD as connected and distinguish it in the notifier */
>> -	if (hpd_isr_status & DP_DP_IRQ_HPD_INT_MASK)
>> -		drm_bridge_hpd_notify(dp->msm_dp_display.bridge,
>> -				      connector_status_connected);
>> +	if (hpd_isr_status & DP_DP_IRQ_HPD_INT_MASK) {
>> +		if (dp->msm_dp_display.mst_active)
>> +			msm_dp_irq_hpd_handle(dp);
> 
> No, don't touch this code. HPD notifications might be coming from the
> other entities. This IRQ thread can only send the HPD notification.
> There rest should be handled in the notifier.
> 
Ok. From my understanding, after this series 
(https://patchwork.freedesktop.org/series/164954/#rev5) is rebased, we 
should use drm_aux_hpd_bridge_notify_extra() here to notify the IRQ?
>> +		else
>> +			drm_bridge_hpd_notify(dp->msm_dp_display.bridge,
>> +					      connector_status_connected);
>> +	}
>>   
>>   	ret = IRQ_HANDLED;
>>   
>> @@ -1793,7 +1807,8 @@ void msm_dp_bridge_hpd_notify(struct drm_bridge *bridge,
>>   			msm_dp_hpd_plug_handle(dp);
>>   		}
>>   	} else {
>> -		msm_dp_hpd_unplug_handle(dp);
>> +		if (hpd_link_status == ISR_DISCONNECTED)
> 
> Why?
> 
Let me explain this in more detail here.
Currently, MST hotplug and IRQ events are handled through the SST bridge.
This guards against spurious unplug handling caused by 
msm_dp_bridge_hpd_notify() being invoked from non-HPD contexts where 
status == connector_status_disconnected does not actually mean the cable 
is gone.

In addition to the real HPD IRQ path, drm_bridge_connector_detect() also 
calls drm_bridge_connector_hpd_notify() to broadcast the detect result 
to all bridges in the chain. So a single physical plug-in produces 
multiple msm_dp_bridge_hpd_notify() calls — one from the real IRQ, then 
several more from various probe/poll paths. Stack traces from a single 
insertion on QCS8300:

   1. msm_dp_display_irq_thread → real HPD plug, status=connected
   2. fbdev probe triggered by (1) → drm_bridge_connector_detect → 
status=disconnected (link not ready yet)
   3. output_poll_execute worker → same path → status=disconnected
   4. drm_dp_mst_link_probe_work → same path → status=disconnected
   5. output_poll_execute again → status=disconnected

Here not work for USB-C case yet, I’d like to switch to using 
drm_dp_read_sink_count to detect whether the sink is actually 
disconnected or no sink devices.

>> +			msm_dp_hpd_unplug_handle(dp);
>>   	}
>>   
>>   	pm_runtime_put_sync(&msm_dp_display->pdev->dev);
>> diff --git a/drivers/gpu/drm/msm/dp/dp_mst_drm.c b/drivers/gpu/drm/msm/dp/dp_mst_drm.c
>> index bb3898b1f6b1..71d3f63973e6 100644
>> --- a/drivers/gpu/drm/msm/dp/dp_mst_drm.c
>> +++ b/drivers/gpu/drm/msm/dp/dp_mst_drm.c
>> @@ -490,6 +490,40 @@ int msm_dp_mst_display_set_mgr_state(struct msm_dp *dp_display, bool state)
>>   	return rc;
>>   }
>>   
>> +/* DP MST HPD IRQ callback */
> 
> Useless comment.
> 
Got it.
>> +void msm_dp_mst_display_hpd_irq(struct msm_dp *dp_display)
>> +{
>> +	int rc;
>> +	struct msm_dp_mst *mst = dp_display->msm_dp_mst;
>> +	u8 ack[8] = {};
>> +	u8 esi[4];
>> +	unsigned int esi_res = DP_SINK_COUNT_ESI + 1;
>> +	bool handled;
>> +
>> +	rc = drm_dp_dpcd_read_data(mst->dp_aux, DP_SINK_COUNT_ESI, esi, 4);
>> +	if (rc < 0) {
>> +		DRM_ERROR("DPCD sink status read failed, rlen=%d\n", rc);
>> +		return;
>> +	}
>> +
>> +	drm_dbg_dp(dp_display->drm_dev, "MST irq: esi1[0x%x] esi2[0x%x] esi3[%x]\n",
>> +		   esi[1], esi[2], esi[3]);
>> +
>> +	rc = drm_dp_mst_hpd_irq_handle_event(&mst->mst_mgr, esi, ack, &handled);
>> +
>> +	/* ack the request */
>> +	if (handled) {
>> +		rc = drm_dp_dpcd_write_byte(mst->dp_aux, esi_res, ack[1]);
>> +		if (rc < 0) {
>> +			DRM_ERROR("DPCD esi_res failed. rc=%d\n", rc);
>> +			return;
>> +		}
>> +
>> +		drm_dp_mst_hpd_irq_send_new_request(&mst->mst_mgr);
>> +	}
>> +	drm_dbg_dp(dp_display->drm_dev, "MST display hpd_irq handled:%d rc:%d\n", handled, rc);
>> +}
>> +
>>   /* DP MST Connector OPs */
>>   static int
>>   msm_dp_mst_connector_detect(struct drm_connector *connector,
>> diff --git a/drivers/gpu/drm/msm/dp/dp_mst_drm.h b/drivers/gpu/drm/msm/dp/dp_mst_drm.h
>> index 5d411529f681..08e145399cfc 100644
>> --- a/drivers/gpu/drm/msm/dp/dp_mst_drm.h
>> +++ b/drivers/gpu/drm/msm/dp/dp_mst_drm.h
>> @@ -9,5 +9,6 @@
>>   
>>   int msm_dp_mst_init(struct msm_dp *dp_display, u32 max_streams, struct drm_dp_aux *drm_aux);
>>   int msm_dp_mst_display_set_mgr_state(struct msm_dp *dp_display, bool state);
>> +void msm_dp_mst_display_hpd_irq(struct msm_dp *dp_display);
>>   
>>   #endif /* _DP_MST_DRM_H_ */
>>
>> -- 
>> 2.43.0
>>
> 


^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 18/39] drm/msm/dp: add support to send ACT packets for MST
  2026-06-15  8:21     ` Yongxing Mou
@ 2026-06-16  0:41       ` Dmitry Baryshkov
  2026-06-16  9:36         ` Yongxing Mou
  0 siblings, 1 reply; 128+ messages in thread
From: Dmitry Baryshkov @ 2026-06-16  0:41 UTC (permalink / raw)
  To: Yongxing Mou
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar

On Mon, Jun 15, 2026 at 04:21:14PM +0800, Yongxing Mou wrote:
> 
> 
> On 4/12/2026 2:57 AM, Dmitry Baryshkov wrote:
> > On Fri, Apr 10, 2026 at 05:33:53PM +0800, Yongxing Mou wrote:
> > > From: Abhinav Kumar <quic_abhinavk@quicinc.com>
> > > 
> > > Whenever virtual channel slot allocation changes, the DP
> > > source must send the action control trigger sequence to notify
> > > the sink about the same. This would be applicable during the
> > > start and stop of the pixel stream. Add the infrastructure
> > > to be able to send ACT packets for the DP controller when
> > > operating in MST mode.
> > > 
> > > Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
> > > Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
> > > ---
> > >   drivers/gpu/drm/msm/dp/dp_ctrl.c    | 43 +++++++++++++++++++++++++++++++++++--
> > >   drivers/gpu/drm/msm/dp/dp_ctrl.h    |  3 ++-
> > >   drivers/gpu/drm/msm/dp/dp_display.c |  3 ++-
> > >   drivers/gpu/drm/msm/dp/dp_display.h |  1 +
> > >   drivers/gpu/drm/msm/dp/dp_reg.h     |  2 ++
> > >   5 files changed, 48 insertions(+), 4 deletions(-)
> > > 
> > > diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c
> > > index 1109b2df21be..6f25145ef214 100644
> > > --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c
> > > +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c
> > > @@ -143,6 +143,7 @@ struct msm_dp_ctrl_private {
> > >   	bool core_clks_on;
> > >   	bool link_clks_on;
> > >   	bool stream_clks_on[DP_STREAM_MAX];
> > > +	bool mst_active;
> > >   };
> > >   static inline u32 msm_dp_read_ahb(const struct msm_dp_ctrl_private *ctrl, u32 offset)
> > > @@ -228,6 +229,32 @@ static int msm_dp_aux_link_configure(struct drm_dp_aux *aux,
> > >   	return err;
> > >   }
> > > +int msm_dp_ctrl_mst_send_act(struct msm_dp_ctrl *msm_dp_ctrl)
> > > +{
> > > +	struct msm_dp_ctrl_private *ctrl;
> > > +	bool act_complete;
> > > +
> > > +	ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl);
> > > +
> > > +	if (!ctrl->mst_active)
> > > +		return 0;
> > > +
> > > +	msm_dp_write_link(ctrl, 0, REG_DP_MST_ACT, 0x1);
> > > +	/* make sure ACT signal is performed */
> > > +	wmb();
> > > +
> > > +	msleep(20); /* needs 1 frame time */
> > 
> > 20 ms is 50fps. What if we have 30 or 25 fps?
> > 
> This hasn’t been considered yet. Do we need to change this to wait based on
> the per-frame duration of the mode?

I don't know. You wrote that we wait for 1 frame here. Why?


-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 22/39] drm/msm/dp: Add support for sending VCPF packets in DP controller
  2026-06-15  8:35     ` Yongxing Mou
@ 2026-06-16  0:44       ` Dmitry Baryshkov
  2026-06-16  9:23         ` Yongxing Mou
  0 siblings, 1 reply; 128+ messages in thread
From: Dmitry Baryshkov @ 2026-06-16  0:44 UTC (permalink / raw)
  To: Yongxing Mou
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar

On Mon, Jun 15, 2026 at 04:35:06PM +0800, Yongxing Mou wrote:
> 
> 
> On 4/12/2026 3:24 AM, Dmitry Baryshkov wrote:
> > On Fri, Apr 10, 2026 at 05:33:57PM +0800, Yongxing Mou wrote:
> > > From: Abhinav Kumar <quic_abhinavk@quicinc.com>
> > > 
> > > The VC Payload Fill (VCPF) sequence is inserted by the DP controller
> > > when stream symbols are absent, typically before a stream is disabled.
> > > This patch adds support for triggering the VCPF sequence in the MSM DP
> > > controller.
> > > 
> > > Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
> > > Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
> > > Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
> > > ---
> > >   drivers/gpu/drm/msm/dp/dp_ctrl.c    | 55 ++++++++++++++++++++++++++++++++++---
> > >   drivers/gpu/drm/msm/dp/dp_ctrl.h    |  2 +-
> > >   drivers/gpu/drm/msm/dp/dp_display.c |  2 +-
> > >   drivers/gpu/drm/msm/dp/dp_reg.h     |  5 ++++
> > >   4 files changed, 58 insertions(+), 6 deletions(-)
> > > 
> > > @@ -516,14 +542,28 @@ static bool msm_dp_ctrl_mainlink_ready(struct msm_dp_ctrl_private *ctrl)
> > >   	return true;
> > >   }
> > > -void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl)
> > > +void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *msm_dp_panel)
> > >   {
> > >   	struct msm_dp_ctrl_private *ctrl;
> > > +	u32 state = 0x0;
> > >   	ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl);
> > > +	if (!ctrl->mst_active)
> > > +		state |= DP_STATE_CTRL_PUSH_IDLE;
> > > +	else if (msm_dp_panel->stream_id == DP_STREAM_0)
> > > +		state |= DP_DP0_PUSH_VCPF;
> > > +	else if (msm_dp_panel->stream_id == DP_STREAM_1)
> > > +		state |= DP_DP1_PUSH_VCPF;
> > > +	else
> > > +		state |= DP_MSTLINK_PUSH_VCPF;
> > > +
> > >   	reinit_completion(&ctrl->idle_comp);
> > 
> > And there can't be two streams wanting to push idle at the same time?
> > 
> In MST, msm_dp_ctrl_push_idle() is only reached from
> msm_dp_display_disable_helper(), which is called from
> msm_dp_mst_stream_disable() / msm_dp_mst_stream_post_disable() in
> dp_mst_drm.c. Both of those
>    callers hold mst->mst_lock for the duration of the disable sequence,
> which serializes push_idle (and the wait on idle_comp) across streams. So
> sharing a single idle_comp on the ctrl is safe.

Comment, protected by mst_lock in msm_dp_foo().

> > > -	msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, DP_STATE_CTRL_PUSH_IDLE);
> > > +
> > > +	msm_dp_write_link(ctrl, msm_dp_panel->stream_id,
> > > +			  msm_dp_panel->stream_id > 1 ?
> > > +			  REG_DP_MSTLINK_STATE_CTRL : REG_DP_STATE_CTRL,
> > > +			  state);
> > >   	if (!wait_for_completion_timeout(&ctrl->idle_comp,
> > >   			IDLE_PATTERN_COMPLETION_TIMEOUT_JIFFIES))

> > > @@ -2183,7 +2223,7 @@ static int msm_dp_ctrl_link_maintenance(struct msm_dp_ctrl_private *ctrl)
> > >   	int ret = 0;
> > >   	int training_step = DP_TRAINING_NONE;
> > > -	msm_dp_ctrl_push_idle(&ctrl->msm_dp_ctrl);
> > > +	msm_dp_ctrl_push_idle(&ctrl->msm_dp_ctrl, ctrl->panel);
> > 
> > Which panel are we passing and why? It feels to me that we have two
> > different cases, one for the MST stream and another one for the SST
> > link. Can we handle them separately? (note: I might be wrong here,
> > please correct me if I'm wrong).
> > 
> For SST, we push to bit 8 of MDSS_0_DPTX_0_STATE_CTRL.
> For MST, stream0 and stream1 use bit 12 and bit 14 respectively.
> For MST stream2 and stream3 use REG_DP_MSTLINK_STATE_CTRL.
> Do we need to handle MST and SST separately here?

Where is the MST panel coming from? You are pushing ctrl->panel, which
is the SST one, if I'm not mistaken.

> > >   	ctrl->link->phy_params.p_level = 0;
> > >   	ctrl->link->phy_params.v_level = 0;

-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 29/39] drm/msm/dp: add an API to initialize MST on sink side
  2026-06-15  9:02     ` Yongxing Mou
@ 2026-06-16  0:45       ` Dmitry Baryshkov
  2026-06-16  9:44         ` Yongxing Mou
  0 siblings, 1 reply; 128+ messages in thread
From: Dmitry Baryshkov @ 2026-06-16  0:45 UTC (permalink / raw)
  To: Yongxing Mou
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar

On Mon, Jun 15, 2026 at 05:02:27PM +0800, Yongxing Mou wrote:
> 
> 
> On 4/12/2026 8:15 AM, Dmitry Baryshkov wrote:
> > On Fri, Apr 10, 2026 at 05:34:04PM +0800, Yongxing Mou wrote:
> > > From: Abhinav Kumar <quic_abhinavk@quicinc.com>
> > > 
> > > If the DP controller is capable of supporting multiple streams
> > > then initialize the DP sink in MST mode by programming the DP_MSTM_CTRL
> > > DPCD register to enable MST mode.
> > > 
> > > Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
> > > Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
> > > ---
> > >   drivers/gpu/drm/msm/dp/dp_display.c | 57 ++++++++++++++++++++++++++++++++-----
> > >   1 file changed, 50 insertions(+), 7 deletions(-)
> > > 
> > > diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
> > > index 8ae690ce2b9f..abf26951819a 100644
> > > --- a/drivers/gpu/drm/msm/dp/dp_display.c
> > > +++ b/drivers/gpu/drm/msm/dp/dp_display.c
> > > @@ -14,6 +14,7 @@
> > >   #include <linux/string_choices.h>
> > >   #include <drm/display/drm_dp_aux_bus.h>
> > >   #include <drm/display/drm_hdmi_audio_helper.h>
> > > +#include <drm/display/drm_dp_mst_helper.h>
> > >   #include <drm/drm_edid.h>
> > >   #include "msm_drv.h"
> > > @@ -270,6 +271,40 @@ static int msm_dp_display_lttpr_init(struct msm_dp_display_private *dp, u8 *dpcd
> > >   	return lttpr_count;
> > >   }
> > > +static void msm_dp_display_mst_init(struct msm_dp_display_private *dp)
> > > +{
> > > +	const unsigned long clear_mstm_ctrl_timeout_us = 100000;
> > > +	u8 old_mstm_ctrl;
> > > +	struct msm_dp *msm_dp = &dp->msm_dp_display;
> > > +	int ret;
> > > +
> > > +	/* clear sink MST state */
> > > +	drm_dp_dpcd_read_byte(dp->aux, DP_MSTM_CTRL, &old_mstm_ctrl);
> > > +
> > > +	ret = drm_dp_dpcd_write_byte(dp->aux, DP_MSTM_CTRL, 0);
> > > +	if (ret < 0) {
> > > +		DRM_ERROR("failed to clear DP_MSTM_CTRL, ret=%d\n", ret);
> > > +		return;
> > > +	}
> > > +
> > > +	/* add extra delay if MST old state is on*/
> > > +	if (old_mstm_ctrl) {
> > > +		drm_dbg_dp(dp->drm_dev, "wait %luus to set DP_MSTM_CTRL set 0\n",
> > > +			   clear_mstm_ctrl_timeout_us);
> > > +		usleep_range(clear_mstm_ctrl_timeout_us,
> > > +			     clear_mstm_ctrl_timeout_us + 1000);
> > 
> > For 100 ms you should be using msleep() instead. But where is that
> > timeout coming from?
> > 
> Will switch to msleep(100).
> 
> The 100 ms is an empirical workaround carried over from downstream — some
> sinks don't exit MST immediately after writing DP_MSTM_CTRL = 0, and
> re-enabling MST too quickly broke topology probe.

Comment.

> It's not a DP spec
> requirement, and other drivers (drm_dp_mst core, i915, amdgpu, nouveau)
> don't have an equivalent delay.

Why?

> 
> > > +	}
> > > +
> > > +	ret = drm_dp_dpcd_write_byte(dp->aux, DP_MSTM_CTRL,
> > > +				     DP_MST_EN | DP_UP_REQ_EN | DP_UPSTREAM_IS_SRC);
> > 
> > Isn't it too early to enable MST? (I don't remember this part of the
> > standard).
> > 
> No, this follows the DP 1.4a MST spec. DP_MSTM_CTRL must be set before
> topology discovery. Topology discovery uses AUX sideband messages, which
> don’t require link training.
> So we enable MST first, then discover topology, and only do link training
> and payload allocation later in atomic_enable().

Ack, thanks.


-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 37/39] drm/msm/dp: add HPD callback for dp MST
  2026-06-15 10:05     ` Yongxing Mou
@ 2026-06-16  0:50       ` Dmitry Baryshkov
  2026-06-16  9:51         ` Yongxing Mou
  0 siblings, 1 reply; 128+ messages in thread
From: Dmitry Baryshkov @ 2026-06-16  0:50 UTC (permalink / raw)
  To: Yongxing Mou
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar

On Mon, Jun 15, 2026 at 06:05:07PM +0800, Yongxing Mou wrote:
> 
> 
> On 4/12/2026 6:00 AM, Dmitry Baryshkov wrote:
> > On Fri, Apr 10, 2026 at 05:34:12PM +0800, Yongxing Mou wrote:
> > > From: Abhinav Kumar <quic_abhinavk@quicinc.com>
> > > 
> > > Add HPD callback for the MST module which shall be invoked from the
> > > dp_display's HPD handler to perform MST specific operations in case
> > > of HPD. In MST case, route the HPD messages to MST module.
> > > 
> > > Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
> > > Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
> > > ---
> > >   drivers/gpu/drm/msm/dp/dp_display.c | 23 +++++++++++++++++++----
> > >   drivers/gpu/drm/msm/dp/dp_mst_drm.c | 34 ++++++++++++++++++++++++++++++++++
> > >   drivers/gpu/drm/msm/dp/dp_mst_drm.h |  1 +
> > >   3 files changed, 54 insertions(+), 4 deletions(-)
> > > 
> > > diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
> > > index 919767945ba5..ca89e20b7563 100644
> > > --- a/drivers/gpu/drm/msm/dp/dp_display.c
> > > +++ b/drivers/gpu/drm/msm/dp/dp_display.c
> > > @@ -454,6 +454,9 @@ static int msm_dp_hpd_plug_handle(struct msm_dp_display_private *dp)
> > >   			dp->msm_dp_display.connector_type,
> > >   			dp->link->sink_count);
> > > +	if (dp->plugged)
> > > +		return 0;
> > > +
> > >   	mutex_lock(&dp->plugged_lock);
> > >   	ret = pm_runtime_resume_and_get(&pdev->dev);
> > > @@ -556,12 +559,19 @@ static int msm_dp_irq_hpd_handle(struct msm_dp_display_private *dp)
> > >   {
> > >   	u32 sink_request;
> > >   	int rc = 0;
> > > +	struct msm_dp *msm_dp_display = &dp->msm_dp_display;
> > >   	/* irq_hpd can happen at either connected or disconnected state */
> > >   	drm_dbg_dp(dp->drm_dev, "Before, type=%d, sink_count=%d\n",
> > >   			dp->msm_dp_display.connector_type,
> > >   			dp->link->sink_count);
> > > +	if (msm_dp_display->mst_active) {
> > > +		if (msm_dp_aux_is_link_connected(dp->aux) != ISR_DISCONNECTED)
> > 
> > Will this work for USB-C?
> > 
> Hmm not work for USB-C. We can remove this check here, as the IRQ thread can
> handle the disconnect case itself.

Please. Start testing with USB-C too.

> > > +			msm_dp_mst_display_hpd_irq(&dp->msm_dp_display);
> > > +		return 0;
> > > +	}
> > > +
> > >   	/* check for any test request issued by sink */
> > >   	rc = msm_dp_link_process_request(dp->link);
> > >   	if (!rc) {
> > > @@ -1125,9 +1135,13 @@ static irqreturn_t msm_dp_display_irq_thread(int irq, void *dev_id)
> > >   				      connector_status_connected);
> > >   	/* Send HPD as connected and distinguish it in the notifier */
> > > -	if (hpd_isr_status & DP_DP_IRQ_HPD_INT_MASK)
> > > -		drm_bridge_hpd_notify(dp->msm_dp_display.bridge,
> > > -				      connector_status_connected);
> > > +	if (hpd_isr_status & DP_DP_IRQ_HPD_INT_MASK) {
> > > +		if (dp->msm_dp_display.mst_active)
> > > +			msm_dp_irq_hpd_handle(dp);
> > 
> > No, don't touch this code. HPD notifications might be coming from the
> > other entities. This IRQ thread can only send the HPD notification.
> > There rest should be handled in the notifier.
> > 
> Ok. From my understanding, after this series
> (https://patchwork.freedesktop.org/series/164954/#rev5) is rebased, we
> should use drm_aux_hpd_bridge_notify_extra() here to notify the IRQ?

No. There is no aux bridge here. But yes, I'd need to call a different
function in that series.

> > > +		else
> > > +			drm_bridge_hpd_notify(dp->msm_dp_display.bridge,
> > > +					      connector_status_connected);
> > > +	}
> > >   	ret = IRQ_HANDLED;
> > > @@ -1793,7 +1807,8 @@ void msm_dp_bridge_hpd_notify(struct drm_bridge *bridge,
> > >   			msm_dp_hpd_plug_handle(dp);
> > >   		}
> > >   	} else {
> > > -		msm_dp_hpd_unplug_handle(dp);
> > > +		if (hpd_link_status == ISR_DISCONNECTED)
> > 
> > Why?
> > 
> Let me explain this in more detail here.
> Currently, MST hotplug and IRQ events are handled through the SST bridge.
> This guards against spurious unplug handling caused by
> msm_dp_bridge_hpd_notify() being invoked from non-HPD contexts where status
> == connector_status_disconnected does not actually mean the cable is gone.
> 
> In addition to the real HPD IRQ path, drm_bridge_connector_detect() also
> calls drm_bridge_connector_hpd_notify() to broadcast the detect result to
> all bridges in the chain. So a single physical plug-in produces multiple
> msm_dp_bridge_hpd_notify() calls — one from the real IRQ, then several more
> from various probe/poll paths. Stack traces from a single insertion on
> QCS8300:
> 
>   1. msm_dp_display_irq_thread → real HPD plug, status=connected
>   2. fbdev probe triggered by (1) → drm_bridge_connector_detect →
> status=disconnected (link not ready yet)

This should not be happening. We don't use link status anymore to return
connected status.

>   3. output_poll_execute worker → same path → status=disconnected
>   4. drm_dp_mst_link_probe_work → same path → status=disconnected
>   5. output_poll_execute again → status=disconnected
> 
> Here not work for USB-C case yet, I’d like to switch to using
> drm_dp_read_sink_count to detect whether the sink is actually disconnected
> or no sink devices.

drm_dp_read_sink_count() isn't enough here. See the plugged flag. Maybe
we need more flags here.

> 
> > > +			msm_dp_hpd_unplug_handle(dp);
> > >   	}
> > >   	pm_runtime_put_sync(&msm_dp_display->pdev->dev);

-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 22/39] drm/msm/dp: Add support for sending VCPF packets in DP controller
  2026-06-16  0:44       ` Dmitry Baryshkov
@ 2026-06-16  9:23         ` Yongxing Mou
  0 siblings, 0 replies; 128+ messages in thread
From: Yongxing Mou @ 2026-06-16  9:23 UTC (permalink / raw)
  To: Dmitry Baryshkov
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar



On 6/16/2026 8:44 AM, Dmitry Baryshkov wrote:
> On Mon, Jun 15, 2026 at 04:35:06PM +0800, Yongxing Mou wrote:
>>
>>
>> On 4/12/2026 3:24 AM, Dmitry Baryshkov wrote:
>>> On Fri, Apr 10, 2026 at 05:33:57PM +0800, Yongxing Mou wrote:
>>>> From: Abhinav Kumar <quic_abhinavk@quicinc.com>
>>>>
>>>> The VC Payload Fill (VCPF) sequence is inserted by the DP controller
>>>> when stream symbols are absent, typically before a stream is disabled.
>>>> This patch adds support for triggering the VCPF sequence in the MSM DP
>>>> controller.
>>>>
>>>> Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
>>>> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
>>>> Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
>>>> ---
>>>>    drivers/gpu/drm/msm/dp/dp_ctrl.c    | 55 ++++++++++++++++++++++++++++++++++---
>>>>    drivers/gpu/drm/msm/dp/dp_ctrl.h    |  2 +-
>>>>    drivers/gpu/drm/msm/dp/dp_display.c |  2 +-
>>>>    drivers/gpu/drm/msm/dp/dp_reg.h     |  5 ++++
>>>>    4 files changed, 58 insertions(+), 6 deletions(-)
>>>>
>>>> @@ -516,14 +542,28 @@ static bool msm_dp_ctrl_mainlink_ready(struct msm_dp_ctrl_private *ctrl)
>>>>    	return true;
>>>>    }
>>>> -void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl)
>>>> +void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *msm_dp_panel)
>>>>    {
>>>>    	struct msm_dp_ctrl_private *ctrl;
>>>> +	u32 state = 0x0;
>>>>    	ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl);
>>>> +	if (!ctrl->mst_active)
>>>> +		state |= DP_STATE_CTRL_PUSH_IDLE;
>>>> +	else if (msm_dp_panel->stream_id == DP_STREAM_0)
>>>> +		state |= DP_DP0_PUSH_VCPF;
>>>> +	else if (msm_dp_panel->stream_id == DP_STREAM_1)
>>>> +		state |= DP_DP1_PUSH_VCPF;
>>>> +	else
>>>> +		state |= DP_MSTLINK_PUSH_VCPF;
>>>> +
>>>>    	reinit_completion(&ctrl->idle_comp);
>>>
>>> And there can't be two streams wanting to push idle at the same time?
>>>
>> In MST, msm_dp_ctrl_push_idle() is only reached from
>> msm_dp_display_disable_helper(), which is called from
>> msm_dp_mst_stream_disable() / msm_dp_mst_stream_post_disable() in
>> dp_mst_drm.c. Both of those
>>     callers hold mst->mst_lock for the duration of the disable sequence,
>> which serializes push_idle (and the wait on idle_comp) across streams. So
>> sharing a single idle_comp on the ctrl is safe.
> 
> Comment, protected by mst_lock in msm_dp_foo().
> 
Got it.
>>>> -	msm_dp_write_link(ctrl, 0, REG_DP_STATE_CTRL, DP_STATE_CTRL_PUSH_IDLE);
>>>> +
>>>> +	msm_dp_write_link(ctrl, msm_dp_panel->stream_id,
>>>> +			  msm_dp_panel->stream_id > 1 ?
>>>> +			  REG_DP_MSTLINK_STATE_CTRL : REG_DP_STATE_CTRL,
>>>> +			  state);
>>>>    	if (!wait_for_completion_timeout(&ctrl->idle_comp,
>>>>    			IDLE_PATTERN_COMPLETION_TIMEOUT_JIFFIES))
> 
>>>> @@ -2183,7 +2223,7 @@ static int msm_dp_ctrl_link_maintenance(struct msm_dp_ctrl_private *ctrl)
>>>>    	int ret = 0;
>>>>    	int training_step = DP_TRAINING_NONE;
>>>> -	msm_dp_ctrl_push_idle(&ctrl->msm_dp_ctrl);
>>>> +	msm_dp_ctrl_push_idle(&ctrl->msm_dp_ctrl, ctrl->panel);
>>>
>>> Which panel are we passing and why? It feels to me that we have two
>>> different cases, one for the MST stream and another one for the SST
>>> link. Can we handle them separately? (note: I might be wrong here,
>>> please correct me if I'm wrong).
>>>
>> For SST, we push to bit 8 of MDSS_0_DPTX_0_STATE_CTRL.
>> For MST, stream0 and stream1 use bit 12 and bit 14 respectively.
>> For MST stream2 and stream3 use REG_DP_MSTLINK_STATE_CTRL.
>> Do we need to handle MST and SST separately here?
> 
> Where is the MST panel coming from? You are pushing ctrl->panel, which
> is the SST one, if I'm not mistaken.
> 
This function is only used in the SST path, which is why ctrl->panel is 
passed in.
The only reason for the change here is the interface update of 
msm_dp_ctrl_push_idle().
If needed, we could keep msm_dp_ctrl_push_idle() for SST and introduce a 
new msm_dp_ctrl_push_vcpf() for MST. Since the SST behavior hasn’t 
changed, no modification is needed here.
>>>>    	ctrl->link->phy_params.p_level = 0;
>>>>    	ctrl->link->phy_params.v_level = 0;
> 


^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 18/39] drm/msm/dp: add support to send ACT packets for MST
  2026-06-16  0:41       ` Dmitry Baryshkov
@ 2026-06-16  9:36         ` Yongxing Mou
  0 siblings, 0 replies; 128+ messages in thread
From: Yongxing Mou @ 2026-06-16  9:36 UTC (permalink / raw)
  To: Dmitry Baryshkov
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar



On 6/16/2026 8:41 AM, Dmitry Baryshkov wrote:
> On Mon, Jun 15, 2026 at 04:21:14PM +0800, Yongxing Mou wrote:
>>
>>
>> On 4/12/2026 2:57 AM, Dmitry Baryshkov wrote:
>>> On Fri, Apr 10, 2026 at 05:33:53PM +0800, Yongxing Mou wrote:
>>>> From: Abhinav Kumar <quic_abhinavk@quicinc.com>
>>>>
>>>> Whenever virtual channel slot allocation changes, the DP
>>>> source must send the action control trigger sequence to notify
>>>> the sink about the same. This would be applicable during the
>>>> start and stop of the pixel stream. Add the infrastructure
>>>> to be able to send ACT packets for the DP controller when
>>>> operating in MST mode.
>>>>
>>>> Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
>>>> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
>>>> ---
>>>>    drivers/gpu/drm/msm/dp/dp_ctrl.c    | 43 +++++++++++++++++++++++++++++++++++--
>>>>    drivers/gpu/drm/msm/dp/dp_ctrl.h    |  3 ++-
>>>>    drivers/gpu/drm/msm/dp/dp_display.c |  3 ++-
>>>>    drivers/gpu/drm/msm/dp/dp_display.h |  1 +
>>>>    drivers/gpu/drm/msm/dp/dp_reg.h     |  2 ++
>>>>    5 files changed, 48 insertions(+), 4 deletions(-)
>>>>
>>>> diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c
>>>> index 1109b2df21be..6f25145ef214 100644
>>>> --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c
>>>> +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c
>>>> @@ -143,6 +143,7 @@ struct msm_dp_ctrl_private {
>>>>    	bool core_clks_on;
>>>>    	bool link_clks_on;
>>>>    	bool stream_clks_on[DP_STREAM_MAX];
>>>> +	bool mst_active;
>>>>    };
>>>>    static inline u32 msm_dp_read_ahb(const struct msm_dp_ctrl_private *ctrl, u32 offset)
>>>> @@ -228,6 +229,32 @@ static int msm_dp_aux_link_configure(struct drm_dp_aux *aux,
>>>>    	return err;
>>>>    }
>>>> +int msm_dp_ctrl_mst_send_act(struct msm_dp_ctrl *msm_dp_ctrl)
>>>> +{
>>>> +	struct msm_dp_ctrl_private *ctrl;
>>>> +	bool act_complete;
>>>> +
>>>> +	ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl);
>>>> +
>>>> +	if (!ctrl->mst_active)
>>>> +		return 0;
>>>> +
>>>> +	msm_dp_write_link(ctrl, 0, REG_DP_MST_ACT, 0x1);
>>>> +	/* make sure ACT signal is performed */
>>>> +	wmb();
>>>> +
>>>> +	msleep(20); /* needs 1 frame time */
>>>
>>> 20 ms is 50fps. What if we have 30 or 25 fps?
>>>
>> This hasn’t been considered yet. Do we need to change this to wait based on
>> the per-frame duration of the mode?
> 
> I don't know. You wrote that we wait for 1 frame here. Why?
> 
The one-frame delay here is inherited from the downstream code.
IPCAT describes it as: ‘When written to 1, a sequence of 4 ACT headers 
will be sent at the earliest possible time. The field will be cleared by 
HW after the 4 ACTs are sent, which then triggers the READY_FOR_VIDEO 
interrupt.’
So to cover the worst-case scenario, we chose one frame. Please correct 
me if i'm wrong.
> 


^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 29/39] drm/msm/dp: add an API to initialize MST on sink side
  2026-06-16  0:45       ` Dmitry Baryshkov
@ 2026-06-16  9:44         ` Yongxing Mou
  0 siblings, 0 replies; 128+ messages in thread
From: Yongxing Mou @ 2026-06-16  9:44 UTC (permalink / raw)
  To: Dmitry Baryshkov
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar



On 6/16/2026 8:45 AM, Dmitry Baryshkov wrote:
> On Mon, Jun 15, 2026 at 05:02:27PM +0800, Yongxing Mou wrote:
>>
>>
>> On 4/12/2026 8:15 AM, Dmitry Baryshkov wrote:
>>> On Fri, Apr 10, 2026 at 05:34:04PM +0800, Yongxing Mou wrote:
>>>> From: Abhinav Kumar <quic_abhinavk@quicinc.com>
>>>>
>>>> If the DP controller is capable of supporting multiple streams
>>>> then initialize the DP sink in MST mode by programming the DP_MSTM_CTRL
>>>> DPCD register to enable MST mode.
>>>>
>>>> Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
>>>> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
>>>> ---
>>>>    drivers/gpu/drm/msm/dp/dp_display.c | 57 ++++++++++++++++++++++++++++++++-----
>>>>    1 file changed, 50 insertions(+), 7 deletions(-)
>>>>
>>>> diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
>>>> index 8ae690ce2b9f..abf26951819a 100644
>>>> --- a/drivers/gpu/drm/msm/dp/dp_display.c
>>>> +++ b/drivers/gpu/drm/msm/dp/dp_display.c
>>>> @@ -14,6 +14,7 @@
>>>>    #include <linux/string_choices.h>
>>>>    #include <drm/display/drm_dp_aux_bus.h>
>>>>    #include <drm/display/drm_hdmi_audio_helper.h>
>>>> +#include <drm/display/drm_dp_mst_helper.h>
>>>>    #include <drm/drm_edid.h>
>>>>    #include "msm_drv.h"
>>>> @@ -270,6 +271,40 @@ static int msm_dp_display_lttpr_init(struct msm_dp_display_private *dp, u8 *dpcd
>>>>    	return lttpr_count;
>>>>    }
>>>> +static void msm_dp_display_mst_init(struct msm_dp_display_private *dp)
>>>> +{
>>>> +	const unsigned long clear_mstm_ctrl_timeout_us = 100000;
>>>> +	u8 old_mstm_ctrl;
>>>> +	struct msm_dp *msm_dp = &dp->msm_dp_display;
>>>> +	int ret;
>>>> +
>>>> +	/* clear sink MST state */
>>>> +	drm_dp_dpcd_read_byte(dp->aux, DP_MSTM_CTRL, &old_mstm_ctrl);
>>>> +
>>>> +	ret = drm_dp_dpcd_write_byte(dp->aux, DP_MSTM_CTRL, 0);
>>>> +	if (ret < 0) {
>>>> +		DRM_ERROR("failed to clear DP_MSTM_CTRL, ret=%d\n", ret);
>>>> +		return;
>>>> +	}
>>>> +
>>>> +	/* add extra delay if MST old state is on*/
>>>> +	if (old_mstm_ctrl) {
>>>> +		drm_dbg_dp(dp->drm_dev, "wait %luus to set DP_MSTM_CTRL set 0\n",
>>>> +			   clear_mstm_ctrl_timeout_us);
>>>> +		usleep_range(clear_mstm_ctrl_timeout_us,
>>>> +			     clear_mstm_ctrl_timeout_us + 1000);
>>>
>>> For 100 ms you should be using msleep() instead. But where is that
>>> timeout coming from?
>>>
>> Will switch to msleep(100).
>>
>> The 100 ms is an empirical workaround carried over from downstream — some
>> sinks don't exit MST immediately after writing DP_MSTM_CTRL = 0, and
>> re-enabling MST too quickly broke topology probe.
> 
> Comment.
> 
Got it.
>> It's not a DP spec
>> requirement, and other drivers (drm_dp_mst core, i915, amdgpu, nouveau)
>> don't have an equivalent delay.
> 
> Why?
> 
This comes from the downstream implementation and was added due to 
issues observed with some dongles, which reportedly required a ~100 ms 
delay. However, it’s unclear whether such a long delay is actually 
necessary (e.g. if a shorter delay would be sufficient).
Alternatively, we could follow nouveau and avoid adding the delay for 
now, and only introduce it if it turns out to be required.
>>
>>>> +	}
>>>> +
>>>> +	ret = drm_dp_dpcd_write_byte(dp->aux, DP_MSTM_CTRL,
>>>> +				     DP_MST_EN | DP_UP_REQ_EN | DP_UPSTREAM_IS_SRC);
>>>
>>> Isn't it too early to enable MST? (I don't remember this part of the
>>> standard).
>>>
>> No, this follows the DP 1.4a MST spec. DP_MSTM_CTRL must be set before
>> topology discovery. Topology discovery uses AUX sideband messages, which
>> don’t require link training.
>> So we enable MST first, then discover topology, and only do link training
>> and payload allocation later in atomic_enable().
> 
> Ack, thanks.
> 
> 


^ permalink raw reply	[flat|nested] 128+ messages in thread

* Re: [PATCH v4 37/39] drm/msm/dp: add HPD callback for dp MST
  2026-06-16  0:50       ` Dmitry Baryshkov
@ 2026-06-16  9:51         ` Yongxing Mou
  0 siblings, 0 replies; 128+ messages in thread
From: Yongxing Mou @ 2026-06-16  9:51 UTC (permalink / raw)
  To: Dmitry Baryshkov
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Sean Paul,
	Marijn Suijten, David Airlie, Simona Vetter, Jessica Zhang,
	linux-arm-msm, dri-devel, freedreno, linux-kernel, Abhinav Kumar



On 6/16/2026 8:50 AM, Dmitry Baryshkov wrote:
> On Mon, Jun 15, 2026 at 06:05:07PM +0800, Yongxing Mou wrote:
>>
>>
>> On 4/12/2026 6:00 AM, Dmitry Baryshkov wrote:
>>> On Fri, Apr 10, 2026 at 05:34:12PM +0800, Yongxing Mou wrote:
>>>> From: Abhinav Kumar <quic_abhinavk@quicinc.com>
>>>>
>>>> Add HPD callback for the MST module which shall be invoked from the
>>>> dp_display's HPD handler to perform MST specific operations in case
>>>> of HPD. In MST case, route the HPD messages to MST module.
>>>>
>>>> Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
>>>> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
>>>> ---
>>>>    drivers/gpu/drm/msm/dp/dp_display.c | 23 +++++++++++++++++++----
>>>>    drivers/gpu/drm/msm/dp/dp_mst_drm.c | 34 ++++++++++++++++++++++++++++++++++
>>>>    drivers/gpu/drm/msm/dp/dp_mst_drm.h |  1 +
>>>>    3 files changed, 54 insertions(+), 4 deletions(-)
>>>>
>>>> diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
>>>> index 919767945ba5..ca89e20b7563 100644
>>>> --- a/drivers/gpu/drm/msm/dp/dp_display.c
>>>> +++ b/drivers/gpu/drm/msm/dp/dp_display.c
>>>> @@ -454,6 +454,9 @@ static int msm_dp_hpd_plug_handle(struct msm_dp_display_private *dp)
>>>>    			dp->msm_dp_display.connector_type,
>>>>    			dp->link->sink_count);
>>>> +	if (dp->plugged)
>>>> +		return 0;
>>>> +
>>>>    	mutex_lock(&dp->plugged_lock);
>>>>    	ret = pm_runtime_resume_and_get(&pdev->dev);
>>>> @@ -556,12 +559,19 @@ static int msm_dp_irq_hpd_handle(struct msm_dp_display_private *dp)
>>>>    {
>>>>    	u32 sink_request;
>>>>    	int rc = 0;
>>>> +	struct msm_dp *msm_dp_display = &dp->msm_dp_display;
>>>>    	/* irq_hpd can happen at either connected or disconnected state */
>>>>    	drm_dbg_dp(dp->drm_dev, "Before, type=%d, sink_count=%d\n",
>>>>    			dp->msm_dp_display.connector_type,
>>>>    			dp->link->sink_count);
>>>> +	if (msm_dp_display->mst_active) {
>>>> +		if (msm_dp_aux_is_link_connected(dp->aux) != ISR_DISCONNECTED)
>>>
>>> Will this work for USB-C?
>>>
>> Hmm not work for USB-C. We can remove this check here, as the IRQ thread can
>> handle the disconnect case itself.
> 
> Please. Start testing with USB-C too.
> 
Yeah. will test USB-C also. Could you rebase this series on top of the 
HPD refactor series? Thanks.
>>>> +			msm_dp_mst_display_hpd_irq(&dp->msm_dp_display);
>>>> +		return 0;
>>>> +	}
>>>> +
>>>>    	/* check for any test request issued by sink */
>>>>    	rc = msm_dp_link_process_request(dp->link);
>>>>    	if (!rc) {
>>>> @@ -1125,9 +1135,13 @@ static irqreturn_t msm_dp_display_irq_thread(int irq, void *dev_id)
>>>>    				      connector_status_connected);
>>>>    	/* Send HPD as connected and distinguish it in the notifier */
>>>> -	if (hpd_isr_status & DP_DP_IRQ_HPD_INT_MASK)
>>>> -		drm_bridge_hpd_notify(dp->msm_dp_display.bridge,
>>>> -				      connector_status_connected);
>>>> +	if (hpd_isr_status & DP_DP_IRQ_HPD_INT_MASK) {
>>>> +		if (dp->msm_dp_display.mst_active)
>>>> +			msm_dp_irq_hpd_handle(dp);
>>>
>>> No, don't touch this code. HPD notifications might be coming from the
>>> other entities. This IRQ thread can only send the HPD notification.
>>> There rest should be handled in the notifier.
>>>
>> Ok. From my understanding, after this series
>> (https://patchwork.freedesktop.org/series/164954/#rev5) is rebased, we
>> should use drm_aux_hpd_bridge_notify_extra() here to notify the IRQ?
> 
> No. There is no aux bridge here. But yes, I'd need to call a different
> function in that series.
> 
There is one concern here: if we use drm_aux_hpd_bridge_notify_*() to 
notify IRQ events, does that mean every IRQ notification would also 
trigger a hotplug event?
If so, this may not be necessary, since the MST core framework already 
notifies userspace/client on its own. Additional hotplug events could 
cause userspace to query connector status again, which in turn might 
trigger the bridge notification repeatedly.
Please correct me if my understanding is wrong.
>>>> +		else
>>>> +			drm_bridge_hpd_notify(dp->msm_dp_display.bridge,
>>>> +					      connector_status_connected);
>>>> +	}
>>>>    	ret = IRQ_HANDLED;
>>>> @@ -1793,7 +1807,8 @@ void msm_dp_bridge_hpd_notify(struct drm_bridge *bridge,
>>>>    			msm_dp_hpd_plug_handle(dp);
>>>>    		}
>>>>    	} else {
>>>> -		msm_dp_hpd_unplug_handle(dp);
>>>> +		if (hpd_link_status == ISR_DISCONNECTED)
>>>
>>> Why?
>>>
>> Let me explain this in more detail here.
>> Currently, MST hotplug and IRQ events are handled through the SST bridge.
>> This guards against spurious unplug handling caused by
>> msm_dp_bridge_hpd_notify() being invoked from non-HPD contexts where status
>> == connector_status_disconnected does not actually mean the cable is gone.
>>
>> In addition to the real HPD IRQ path, drm_bridge_connector_detect() also
>> calls drm_bridge_connector_hpd_notify() to broadcast the detect result to
>> all bridges in the chain. So a single physical plug-in produces multiple
>> msm_dp_bridge_hpd_notify() calls — one from the real IRQ, then several more
>> from various probe/poll paths. Stack traces from a single insertion on
>> QCS8300:
>>
>>    1. msm_dp_display_irq_thread → real HPD plug, status=connected
>>    2. fbdev probe triggered by (1) → drm_bridge_connector_detect →
>> status=disconnected (link not ready yet)
> 
> This should not be happening. We don't use link status anymore to return
> connected status.
> 
Let me double-check this.
>>    3. output_poll_execute worker → same path → status=disconnected
>>    4. drm_dp_mst_link_probe_work → same path → status=disconnected
>>    5. output_poll_execute again → status=disconnected
>>
>> Here not work for USB-C case yet, I’d like to switch to using
>> drm_dp_read_sink_count to detect whether the sink is actually disconnected
>> or no sink devices.
> 
> drm_dp_read_sink_count() isn't enough here. See the plugged flag. Maybe
> we need more flags here.
> 
Got it. Let me see if there’s a better way to handle this.
>>
>>>> +			msm_dp_hpd_unplug_handle(dp);
>>>>    	}
>>>>    	pm_runtime_put_sync(&msm_dp_display->pdev->dev);
> 


^ permalink raw reply	[flat|nested] 128+ messages in thread

end of thread, other threads:[~2026-06-16  9:51 UTC | newest]

Thread overview: 128+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-04-10  9:33 [PATCH v4 00/39] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
2026-04-10  9:33 ` [PATCH v4 01/39] drm/msm/dp: remove cached drm_edid from panel Yongxing Mou
2026-04-10 13:52   ` Dmitry Baryshkov
2026-05-19  7:27     ` Yongxing Mou
2026-05-20  9:22       ` Dmitry Baryshkov
2026-05-20 10:09         ` Yongxing Mou
2026-04-10  9:33 ` [PATCH v4 02/39] drm/msm/dp: drop deprecated .mode_set() and use .atomic_enable Yongxing Mou
2026-04-11 17:23   ` Dmitry Baryshkov
2026-04-10  9:33 ` [PATCH v4 03/39] drm/msm/dp: break up dp_display_enable into two parts Yongxing Mou
2026-04-11 17:33   ` Dmitry Baryshkov
2026-04-12  0:20   ` Dmitry Baryshkov
2026-04-10  9:33 ` [PATCH v4 04/39] drm/msm/dp: re-arrange dp_display_disable() into functional parts Yongxing Mou
2026-04-10  9:33 ` [PATCH v4 05/39] drm/msm/dp: splite msm_dp_ctrl_config_ctrl() into link parts and stream parts Yongxing Mou
2026-04-11 17:34   ` Dmitry Baryshkov
2026-05-19  7:46     ` Yongxing Mou
2026-05-20  9:23       ` Dmitry Baryshkov
2026-05-20 10:16         ` Yongxing Mou
2026-05-20 10:20           ` Dmitry Baryshkov
2026-04-10  9:33 ` [PATCH v4 06/39] drm/msm/dp: extract MISC1_MISC0 configuration into a separate function Yongxing Mou
2026-04-10  9:33 ` [PATCH v4 07/39] drm/msm/dp: split link setup from source params Yongxing Mou
2026-04-11 17:36   ` Dmitry Baryshkov
2026-04-10  9:33 ` [PATCH v4 08/39] drm/msm/dp: allow dp_ctrl stream APIs to use any panel passed to it Yongxing Mou
2026-04-11 17:38   ` Dmitry Baryshkov
2026-05-19  7:52     ` Yongxing Mou
2026-05-20  9:24       ` Dmitry Baryshkov
2026-05-20 10:31         ` Yongxing Mou
2026-05-20 12:36           ` Dmitry Baryshkov
2026-04-10  9:33 ` [PATCH v4 09/39] drm/msm/dp: move the pixel clock control to its own API Yongxing Mou
2026-04-10  9:33 ` [PATCH v4 10/39] drm/msm/dp: split dp_ctrl_off() into stream and link parts Yongxing Mou
2026-04-10  9:33 ` [PATCH v4 11/39] drm/msm/dp: make bridge helpers use dp_display to allow re-use Yongxing Mou
2026-04-11 17:41   ` Dmitry Baryshkov
2026-04-10  9:33 ` [PATCH v4 12/39] drm/msm/dp: separate dp_display_prepare() into its own API Yongxing Mou
2026-04-11 17:42   ` Dmitry Baryshkov
2026-04-10  9:33 ` [PATCH v4 13/39] drm/msm/dp: introduce stream_id for each DP panel Yongxing Mou
2026-04-11 17:55   ` Dmitry Baryshkov
2026-05-19  8:26     ` Yongxing Mou
2026-05-20  9:27       ` Dmitry Baryshkov
2026-04-11 18:04   ` Dmitry Baryshkov
2026-05-20  8:54     ` Yongxing Mou
2026-04-12  0:04   ` Dmitry Baryshkov
2026-05-20  8:54     ` Yongxing Mou
2026-04-10  9:33 ` [PATCH v4 14/39] drm/msm/dp: introduce max_streams for DP controller MST support Yongxing Mou
2026-04-11 17:59   ` Dmitry Baryshkov
2026-05-21 11:34     ` Yongxing Mou
2026-05-21 12:07       ` Dmitry Baryshkov
2026-05-22  7:58         ` Yongxing Mou
2026-05-25  8:16           ` Dmitry Baryshkov
2026-04-10  9:33 ` [PATCH v4 15/39] drm/msm/dp: Add support for programming p1/p2/p3 register blocks Yongxing Mou
2026-04-11 18:07   ` Dmitry Baryshkov
2026-05-21 11:50     ` Yongxing Mou
2026-05-21 12:20       ` Dmitry Baryshkov
2026-05-22  7:51         ` Yongxing Mou
2026-05-25  8:19           ` Dmitry Baryshkov
2026-04-10  9:33 ` [PATCH v4 16/39] drm/msm/dp: use stream_id to change offsets in dp_catalog Yongxing Mou
2026-04-11 18:12   ` Dmitry Baryshkov
2026-05-25  8:06     ` Yongxing Mou
2026-05-25  8:21       ` Dmitry Baryshkov
2026-05-25 12:13         ` Yongxing Mou
2026-05-25 13:31           ` Dmitry Baryshkov
2026-04-10  9:33 ` [PATCH v4 17/39] drm/msm/dp: Add catalog support for 3rd/4th stream MST Yongxing Mou
2026-04-11 18:24   ` Dmitry Baryshkov
2026-05-25  8:06     ` Yongxing Mou
2026-05-25  8:25       ` Dmitry Baryshkov
2026-05-25 12:01         ` Yongxing Mou
2026-05-25 13:32           ` Dmitry Baryshkov
2026-04-10  9:33 ` [PATCH v4 18/39] drm/msm/dp: add support to send ACT packets for MST Yongxing Mou
2026-04-11 18:57   ` Dmitry Baryshkov
2026-06-15  8:21     ` Yongxing Mou
2026-06-16  0:41       ` Dmitry Baryshkov
2026-06-16  9:36         ` Yongxing Mou
2026-04-10  9:33 ` [PATCH v4 19/39] drm/msm/dp: Add support to enable MST in mainlink control Yongxing Mou
2026-04-11 18:59   ` Dmitry Baryshkov
2026-04-10  9:33 ` [PATCH v4 20/39] drm/msm/dp: no need to update tu calculation for mst Yongxing Mou
2026-04-11 19:00   ` Dmitry Baryshkov
2026-04-10  9:33 ` [PATCH v4 21/39] drm/msm/dp: Add support for MST channel slot allocation Yongxing Mou
2026-04-11 23:58   ` Dmitry Baryshkov
2026-04-10  9:33 ` [PATCH v4 22/39] drm/msm/dp: Add support for sending VCPF packets in DP controller Yongxing Mou
2026-04-11 19:24   ` Dmitry Baryshkov
2026-06-15  8:35     ` Yongxing Mou
2026-06-16  0:44       ` Dmitry Baryshkov
2026-06-16  9:23         ` Yongxing Mou
2026-04-10  9:33 ` [PATCH v4 23/39] drm/msm/dp: Always program MST_FIFO_CONSTANT_FILL for MST use cases Yongxing Mou
2026-04-10  9:33 ` [PATCH v4 24/39] drm/msm/dp: simplify link and clock disable sequence Yongxing Mou
2026-04-12  0:00   ` Dmitry Baryshkov
2026-04-10  9:34 ` [PATCH v4 25/39] drm/msm/dp: pass panel to display enable/disable helpers Yongxing Mou
2026-04-12  0:05   ` Dmitry Baryshkov
2026-05-28 10:13     ` Yongxing Mou
2026-04-10  9:34 ` [PATCH v4 26/39] drm/msm/dp: abstract out the dp_display stream helpers to accept a panel Yongxing Mou
2026-04-12  0:07   ` Dmitry Baryshkov
2026-04-10  9:34 ` [PATCH v4 27/39] drm/msm/dp: replace power_on with active_stream_cnt for dp_display Yongxing Mou
2026-04-12  0:11   ` Dmitry Baryshkov
2026-06-15  8:45     ` Yongxing Mou
2026-04-10  9:34 ` [PATCH v4 28/39] drm/msm/dp: Mark the SST bridge disconnected when mst is active Yongxing Mou
2026-04-12  0:11   ` Dmitry Baryshkov
2026-06-15  8:47     ` Yongxing Mou
2026-04-10  9:34 ` [PATCH v4 29/39] drm/msm/dp: add an API to initialize MST on sink side Yongxing Mou
2026-04-12  0:15   ` Dmitry Baryshkov
2026-06-15  9:02     ` Yongxing Mou
2026-06-16  0:45       ` Dmitry Baryshkov
2026-06-16  9:44         ` Yongxing Mou
2026-04-10  9:34 ` [PATCH v4 30/39] drm/msm/dp: add dp_display_get_panel() to initialize DP panel Yongxing Mou
2026-04-12  0:17   ` Dmitry Baryshkov
2026-04-10  9:34 ` [PATCH v4 31/39] drm/msm/dp: add prepared to manage link-level operations Yongxing Mou
2026-04-12  0:19   ` Dmitry Baryshkov
2026-04-10  9:34 ` [PATCH v4 32/39] drm/msm/dpu: initialize encoders per stream for DP MST Yongxing Mou
2026-04-11 19:31   ` Dmitry Baryshkov
2026-04-10  9:34 ` [PATCH v4 33/39] drm/msm/dp: initialize dp_mst module for each DP MST controller Yongxing Mou
2026-04-11 23:36   ` Dmitry Baryshkov
2026-04-10  9:34 ` [PATCH v4 34/39] drm/msm/dp: add dp_mst_drm to manage DP MST bridge operations Yongxing Mou
2026-04-11 23:51   ` Dmitry Baryshkov
2026-05-18 12:00     ` Yongxing Mou
2026-04-10  9:34 ` [PATCH v4 35/39] drm/msm/dp: wire MST helpers into atomic check and commit paths Yongxing Mou
2026-04-11 23:55   ` Dmitry Baryshkov
2026-04-10  9:34 ` [PATCH v4 36/39] drm/msm/dp: add connector abstraction for DP MST Yongxing Mou
2026-04-11 23:30   ` Dmitry Baryshkov
2026-04-10  9:34 ` [PATCH v4 37/39] drm/msm/dp: add HPD callback for dp MST Yongxing Mou
2026-04-11 22:00   ` Dmitry Baryshkov
2026-06-15 10:05     ` Yongxing Mou
2026-06-16  0:50       ` Dmitry Baryshkov
2026-06-16  9:51         ` Yongxing Mou
2026-04-10  9:34 ` [PATCH v4 38/39] drm/msm/dpu: use msm_dp_get_mst_intf_id() to get the intf id Yongxing Mou
2026-04-10  9:34 ` [PATCH v4 39/39] drm/msm/dp: Add MST stream support for supported DP controllers Yongxing Mou
2026-04-10 10:23   ` Konrad Dybcio
2026-04-11 19:26   ` Dmitry Baryshkov
2026-04-10 13:53 ` [PATCH v4 00/39] drm/msm/dp: Add MST support for MSM chipsets Dmitry Baryshkov
2026-04-12  0:34 ` Dmitry Baryshkov
2026-05-22  8:07   ` Yongxing Mou
2026-05-25  8:26     ` Dmitry Baryshkov

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

Powered by JetHome