* [PATCH RESEND v5 01/25] drm/msm/dp: introduce stream_id for each DP panel
2026-06-29 14:14 [PATCH RESEND v5 00/25] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
@ 2026-06-29 14:14 ` Yongxing Mou
2026-07-12 11:11 ` Dmitry Baryshkov
2026-06-29 14:14 ` [PATCH RESEND v5 02/25] drm/msm/dp: introduce max_streams for DP controller MST support Yongxing Mou
` (24 subsequent siblings)
25 siblings, 1 reply; 47+ messages in thread
From: Yongxing Mou @ 2026-06-29 14:14 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, 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 dynamically assigned to actual stream IDs by the MST
path. For SST, default this to stream 0.
In the MST path, panels are dynamically assigned to actual stream IDs at
stream enable time by the MST layer.
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 | 67 +++++++++++++++++++++++--------------
drivers/gpu/drm/msm/dp/dp_ctrl.h | 2 +-
drivers/gpu/drm/msm/dp/dp_display.c | 2 +-
drivers/gpu/drm/msm/dp/dp_panel.c | 1 +
drivers/gpu/drm/msm/dp/dp_panel.h | 11 ++++++
5 files changed, 55 insertions(+), 28 deletions(-)
diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c
index 90fba03de7f0..a475e787656e 100644
--- a/drivers/gpu/drm/msm/dp/dp_ctrl.c
+++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c
@@ -126,7 +126,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;
@@ -138,7 +138,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)
@@ -1746,7 +1746,7 @@ int msm_dp_ctrl_core_clk_enable(struct msm_dp_ctrl *msm_dp_ctrl)
drm_dbg_dp(ctrl->drm_dev, "enable core clocks \n");
drm_dbg_dp(ctrl->drm_dev, "stream_clks:%s link_clks:%s core_clks:%s\n",
- str_on_off(ctrl->stream_clks_on),
+ str_on_off(ctrl->stream_clks_on[DP_STREAM_0]),
str_on_off(ctrl->link_clks_on),
str_on_off(ctrl->core_clks_on));
@@ -1765,7 +1765,7 @@ void msm_dp_ctrl_core_clk_disable(struct msm_dp_ctrl *msm_dp_ctrl)
drm_dbg_dp(ctrl->drm_dev, "disable core clocks \n");
drm_dbg_dp(ctrl->drm_dev, "stream_clks:%s link_clks:%s core_clks:%s\n",
- str_on_off(ctrl->stream_clks_on),
+ str_on_off(ctrl->stream_clks_on[DP_STREAM_0]),
str_on_off(ctrl->link_clks_on),
str_on_off(ctrl->core_clks_on));
}
@@ -1796,7 +1796,7 @@ static int msm_dp_ctrl_link_clk_enable(struct msm_dp_ctrl *msm_dp_ctrl)
drm_dbg_dp(ctrl->drm_dev, "enable link clocks\n");
drm_dbg_dp(ctrl->drm_dev, "stream_clks:%s link_clks:%s core_clks:%s\n",
- str_on_off(ctrl->stream_clks_on),
+ str_on_off(ctrl->stream_clks_on[DP_STREAM_0]),
str_on_off(ctrl->link_clks_on),
str_on_off(ctrl->core_clks_on));
@@ -1815,7 +1815,7 @@ static void msm_dp_ctrl_link_clk_disable(struct msm_dp_ctrl *msm_dp_ctrl)
drm_dbg_dp(ctrl->drm_dev, "disabled link clocks\n");
drm_dbg_dp(ctrl->drm_dev, "stream_clks:%s link_clks:%s core_clks:%s\n",
- str_on_off(ctrl->stream_clks_on),
+ str_on_off(ctrl->stream_clks_on[DP_STREAM_0]),
str_on_off(ctrl->link_clks_on),
str_on_off(ctrl->core_clks_on));
}
@@ -2188,38 +2188,39 @@ 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 (WARN_ON_ONCE(ctrl->stream_clks_on))
+ if (WARN_ON_ONCE(ctrl->stream_clks_on[stream_id]))
return 0;
- 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;
}
}
@@ -2240,7 +2241,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, panel->stream_id);
msm_dp_ctrl_off_link(&ctrl->msm_dp_ctrl, panel);
ret = msm_dp_ctrl_on_link(&ctrl->msm_dp_ctrl, panel);
@@ -2250,7 +2251,7 @@ static int msm_dp_ctrl_process_phy_test_request(struct msm_dp_ctrl_private *ctrl
}
pixel_rate = 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, panel->stream_id);
msm_dp_ctrl_send_phy_test_pattern(ctrl);
@@ -2542,9 +2543,8 @@ int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl,
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, panel);
@@ -2584,7 +2584,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, panel->stream_id);
if (ret)
return ret;
@@ -2644,8 +2644,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(panel);
-
msm_dp_ctrl_mainlink_disable(ctrl);
msm_dp_ctrl_reset(&ctrl->msm_dp_ctrl, panel);
@@ -2716,6 +2714,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;
@@ -2749,9 +2754,19 @@ 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])) {
+ if (PTR_ERR(ctrl->pixel_clk[i]) != -ENOENT)
+ return PTR_ERR(ctrl->pixel_clk[i]);
+ DRM_DEBUG_DP("stream %d pixel clock not found", 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 5902cf7e746a..be0d89d60914 100644
--- a/drivers/gpu/drm/msm/dp/dp_ctrl.h
+++ b/drivers/gpu/drm/msm/dp/dp_ctrl.h
@@ -24,7 +24,7 @@ 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,
struct msm_dp_panel *panel);
-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,
struct msm_dp_panel *panel);
diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
index bea5bfb22967..bb243ab09e66 100644
--- a/drivers/gpu/drm/msm/dp/dp_display.c
+++ b/drivers/gpu/drm/msm/dp/dp_display.c
@@ -718,7 +718,7 @@ static int msm_dp_display_disable(struct msm_dp_display_private *dp,
msm_dp_panel_disable_vsc_sdp(msm_dp_panel);
- msm_dp_ctrl_off_pixel_clk(dp->ctrl);
+ 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)
diff --git a/drivers/gpu/drm/msm/dp/dp_panel.c b/drivers/gpu/drm/msm/dp/dp_panel.c
index e76dad0f6663..745ee6976897 100644
--- a/drivers/gpu/drm/msm/dp/dp_panel.c
+++ b/drivers/gpu/drm/msm/dp/dp_panel.c
@@ -723,6 +723,7 @@ struct msm_dp_panel *msm_dp_panel_get(struct device *dev, struct drm_dp_aux *aux
msm_dp_panel = &panel->msm_dp_panel;
msm_dp_panel->max_bw_code = DP_LINK_BW_8_1;
+ msm_dp_panel->stream_id = DP_STREAM_0;
return msm_dp_panel;
}
diff --git a/drivers/gpu/drm/msm/dp/dp_panel.h b/drivers/gpu/drm/msm/dp/dp_panel.h
index 4519ac374220..50a721401751 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] 47+ messages in thread* Re: [PATCH RESEND v5 01/25] drm/msm/dp: introduce stream_id for each DP panel
2026-06-29 14:14 ` [PATCH RESEND v5 01/25] drm/msm/dp: introduce stream_id for each DP panel Yongxing Mou
@ 2026-07-12 11:11 ` Dmitry Baryshkov
0 siblings, 0 replies; 47+ messages in thread
From: Dmitry Baryshkov @ 2026-07-12 11: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 Mon, Jun 29, 2026 at 10:14:22PM +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
You are not creating IDs.
> panel which shall be dynamically assigned to actual stream IDs by the MST
> path. For SST, default this to stream 0.
>
> In the MST path, panels are dynamically assigned to actual stream IDs at
> stream enable time by the MST layer.
>
> 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 | 67 +++++++++++++++++++++++--------------
> drivers/gpu/drm/msm/dp/dp_ctrl.h | 2 +-
> drivers/gpu/drm/msm/dp/dp_display.c | 2 +-
> drivers/gpu/drm/msm/dp/dp_panel.c | 1 +
> drivers/gpu/drm/msm/dp/dp_panel.h | 11 ++++++
> 5 files changed, 55 insertions(+), 28 deletions(-)
>
> @@ -2188,38 +2188,39 @@ 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 (WARN_ON_ONCE(ctrl->stream_clks_on))
> + if (WARN_ON_ONCE(ctrl->stream_clks_on[stream_id]))
> return 0;
Is it an error, defensive coding, DT mismatch or something else? Why are
we warning the users _and_ returning success here?
>
> - 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;
> }
>
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 47+ messages in thread
* [PATCH RESEND v5 02/25] drm/msm/dp: introduce max_streams for DP controller MST support
2026-06-29 14:14 [PATCH RESEND v5 00/25] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
2026-06-29 14:14 ` [PATCH RESEND v5 01/25] drm/msm/dp: introduce stream_id for each DP panel Yongxing Mou
@ 2026-06-29 14:14 ` Yongxing Mou
2026-07-12 11:17 ` Dmitry Baryshkov
2026-06-29 14:14 ` [PATCH RESEND v5 03/25] drm/msm/dp: Add support for programming p1/p2/p3 register blocks Yongxing Mou
` (23 subsequent siblings)
25 siblings, 1 reply; 47+ messages in thread
From: Yongxing Mou @ 2026-06-29 14:14 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, Abhinav Kumar
From: Abhinav Kumar <quic_abhinavk@quicinc.com>
DP controllers across different SoCs vary in the number of concurrent
MST streams they can support. Rather than hardcoding per-platform
values, the number of available pixel clocks in DT serves as a natural
indicator since each stream requires a dedicated pixel clock.
Introduce max_stream to capture this at initialization time and expose
it for the MST module to use during setup.
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 | 13 +++++++++++++
drivers/gpu/drm/msm/dp/dp_ctrl.h | 1 +
drivers/gpu/drm/msm/dp/dp_display.c | 12 ++++++++++++
drivers/gpu/drm/msm/dp/dp_display.h | 1 +
4 files changed, 27 insertions(+)
diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c
index a475e787656e..68fb4facb056 100644
--- a/drivers/gpu/drm/msm/dp/dp_ctrl.c
+++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c
@@ -127,6 +127,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;
@@ -2754,6 +2755,7 @@ static int msm_dp_ctrl_clk_init(struct msm_dp_ctrl *msm_dp_ctrl)
if (rc)
return rc;
+ ctrl->num_pixel_clks = 0;
for (i = DP_STREAM_0; i < DP_STREAM_MAX; i++) {
ctrl->pixel_clk[i] = devm_clk_get(dev, pixel_clks[i]);
@@ -2766,11 +2768,22 @@ static int msm_dp_ctrl_clk_init(struct msm_dp_ctrl *msm_dp_ctrl)
DRM_DEBUG_DP("stream %d pixel clock not found", 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 drm_dp_aux *aux,
struct phy *phy,
diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.h b/drivers/gpu/drm/msm/dp/dp_ctrl.h
index be0d89d60914..305add3dcd93 100644
--- a/drivers/gpu/drm/msm/dp/dp_ctrl.h
+++ b/drivers/gpu/drm/msm/dp/dp_ctrl.h
@@ -55,4 +55,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 bb243ab09e66..9cd243411e44 100644
--- a/drivers/gpu/drm/msm/dp/dp_display.c
+++ b/drivers/gpu/drm/msm/dp/dp_display.c
@@ -87,6 +87,8 @@ struct msm_dp_display_private {
void __iomem *p0_base;
size_t p0_len;
+
+ int max_stream;
};
struct msm_dp_desc {
@@ -578,6 +580,7 @@ static int msm_dp_init_sub_modules(struct msm_dp_display_private *dp)
dp->ctrl = NULL;
goto error_link;
}
+ dp->max_stream = msm_dp_ctrl_get_stream_cnt(dp->ctrl);
dp->audio = msm_dp_audio_get(dp->msm_dp_display.pdev, dp->link_base);
if (IS_ERR(dp->audio)) {
@@ -1178,6 +1181,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;
diff --git a/drivers/gpu/drm/msm/dp/dp_display.h b/drivers/gpu/drm/msm/dp/dp_display.h
index 43ed79093e24..d3d4ab98089d 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] 47+ messages in thread* Re: [PATCH RESEND v5 02/25] drm/msm/dp: introduce max_streams for DP controller MST support
2026-06-29 14:14 ` [PATCH RESEND v5 02/25] drm/msm/dp: introduce max_streams for DP controller MST support Yongxing Mou
@ 2026-07-12 11:17 ` Dmitry Baryshkov
0 siblings, 0 replies; 47+ messages in thread
From: Dmitry Baryshkov @ 2026-07-12 11: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 Mon, Jun 29, 2026 at 10:14:23PM +0800, Yongxing Mou wrote:
> From: Abhinav Kumar <quic_abhinavk@quicinc.com>
>
> DP controllers across different SoCs vary in the number of concurrent
> MST streams they can support. Rather than hardcoding per-platform
> values, the number of available pixel clocks in DT serves as a natural
> indicator since each stream requires a dedicated pixel clock.
>
> Introduce max_stream to capture this at initialization time and expose
> it for the MST module to use during setup.
>
> 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 | 13 +++++++++++++
> drivers/gpu/drm/msm/dp/dp_ctrl.h | 1 +
> drivers/gpu/drm/msm/dp/dp_display.c | 12 ++++++++++++
> drivers/gpu/drm/msm/dp/dp_display.h | 1 +
> 4 files changed, 27 insertions(+)
>
> diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c
> index a475e787656e..68fb4facb056 100644
> --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c
> +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c
> @@ -127,6 +127,7 @@ struct msm_dp_ctrl_private {
> struct clk_bulk_data *link_clks;
>
> struct clk *pixel_clk[DP_STREAM_MAX];
> + unsigned int num_pixel_clks;
Squash num_pixel_clks to the previous patch.
>
> union phy_configure_opts phy_opts;
>
> @@ -2754,6 +2755,7 @@ static int msm_dp_ctrl_clk_init(struct msm_dp_ctrl *msm_dp_ctrl)
> if (rc)
> return rc;
>
> + ctrl->num_pixel_clks = 0;
> for (i = DP_STREAM_0; i < DP_STREAM_MAX; i++) {
> ctrl->pixel_clk[i] = devm_clk_get(dev, pixel_clks[i]);
>
> @@ -2766,11 +2768,22 @@ static int msm_dp_ctrl_clk_init(struct msm_dp_ctrl *msm_dp_ctrl)
> DRM_DEBUG_DP("stream %d pixel clock not found", 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 drm_dp_aux *aux,
> struct phy *phy,
> diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.h b/drivers/gpu/drm/msm/dp/dp_ctrl.h
> index be0d89d60914..305add3dcd93 100644
> --- a/drivers/gpu/drm/msm/dp/dp_ctrl.h
> +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.h
> @@ -55,4 +55,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 bb243ab09e66..9cd243411e44 100644
> --- a/drivers/gpu/drm/msm/dp/dp_display.c
> +++ b/drivers/gpu/drm/msm/dp/dp_display.c
> @@ -87,6 +87,8 @@ struct msm_dp_display_private {
>
> void __iomem *p0_base;
> size_t p0_len;
> +
> + int max_stream;
> };
>
> struct msm_dp_desc {
> @@ -578,6 +580,7 @@ static int msm_dp_init_sub_modules(struct msm_dp_display_private *dp)
> dp->ctrl = NULL;
> goto error_link;
> }
> + dp->max_stream = msm_dp_ctrl_get_stream_cnt(dp->ctrl);
>
> dp->audio = msm_dp_audio_get(dp->msm_dp_display.pdev, dp->link_base);
> if (IS_ERR(dp->audio)) {
> @@ -1178,6 +1181,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;
Anything stopping the driver from returning
msm_dp_ctrl_get_stream_cnt(dp->ctrl) here? Why do we need to cache it?
> +}
> +
> static int msm_dp_display_probe(struct platform_device *pdev)
> {
> int rc = 0;
> diff --git a/drivers/gpu/drm/msm/dp/dp_display.h b/drivers/gpu/drm/msm/dp/dp_display.h
> index 43ed79093e24..d3d4ab98089d 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] 47+ messages in thread
* [PATCH RESEND v5 03/25] drm/msm/dp: Add support for programming p1/p2/p3 register blocks
2026-06-29 14:14 [PATCH RESEND v5 00/25] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
2026-06-29 14:14 ` [PATCH RESEND v5 01/25] drm/msm/dp: introduce stream_id for each DP panel Yongxing Mou
2026-06-29 14:14 ` [PATCH RESEND v5 02/25] drm/msm/dp: introduce max_streams for DP controller MST support Yongxing Mou
@ 2026-06-29 14:14 ` Yongxing Mou
2026-07-12 11:23 ` Dmitry Baryshkov
2026-06-29 14:14 ` [PATCH RESEND v5 04/25] drm/msm/dp: use stream_id to change offsets in dp_catalog Yongxing Mou
` (22 subsequent siblings)
25 siblings, 1 reply; 47+ messages in thread
From: Yongxing Mou @ 2026-06-29 14:14 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, 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 | 82 ++++++++++++++++++-------------------
drivers/gpu/drm/msm/dp/dp_panel.h | 2 +-
3 files changed, 71 insertions(+), 53 deletions(-)
diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
index 9cd243411e44..74f481a18164 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;
};
@@ -564,7 +564,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);
@@ -850,8 +850,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)
@@ -1131,6 +1137,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))
@@ -1160,8 +1167,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;
}
@@ -1172,10 +1179,21 @@ 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 < DP_STREAM_MAX; 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]);
+ display->pixel_base[i] = NULL;
+ break;
+ }
}
return 0;
diff --git a/drivers/gpu/drm/msm/dp/dp_panel.c b/drivers/gpu/drm/msm/dp/dp_panel.c
index 745ee6976897..238920c45261 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);
@@ -701,7 +701,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;
@@ -719,7 +719,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 50a721401751..218a09a2fa65 100644
--- a/drivers/gpu/drm/msm/dp/dp_panel.h
+++ b/drivers/gpu/drm/msm/dp/dp_panel.h
@@ -102,5 +102,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] 47+ messages in thread* Re: [PATCH RESEND v5 03/25] drm/msm/dp: Add support for programming p1/p2/p3 register blocks
2026-06-29 14:14 ` [PATCH RESEND v5 03/25] drm/msm/dp: Add support for programming p1/p2/p3 register blocks Yongxing Mou
@ 2026-07-12 11:23 ` Dmitry Baryshkov
0 siblings, 0 replies; 47+ messages in thread
From: Dmitry Baryshkov @ 2026-07-12 11: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 Mon, Jun 29, 2026 at 10:14:24PM +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 | 82 ++++++++++++++++++-------------------
> drivers/gpu/drm/msm/dp/dp_panel.h | 2 +-
> 3 files changed, 71 insertions(+), 53 deletions(-)
>
> diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
> index 9cd243411e44..74f481a18164 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;
> };
> @@ -564,7 +564,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);
> @@ -850,8 +850,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");
It should be:
for int i = 0; i < DP_STREAM_MAX; i++)
Also, you've just added a NULL pointer exception in the crash handler.
Check for the address being non-zero before adding it to the snapshots.
> }
>
> void msm_dp_display_set_psr(struct msm_dp *msm_dp_display, bool enter)
> @@ -1131,6 +1137,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))
> @@ -1160,8 +1167,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;
> }
> @@ -1172,10 +1179,21 @@ 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 < DP_STREAM_MAX; 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]);
> + display->pixel_base[i] = NULL;
> + break;
Here we should differentiate between the address being not present in
DT (which should be ignored) and any other errors.
> + }
> }
>
> return 0;
> diff --git a/drivers/gpu/drm/msm/dp/dp_panel.c b/drivers/gpu/drm/msm/dp/dp_panel.c
> index 745ee6976897..238920c45261 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()
Hmm, so the comment talks about writel(_relaxed), but the code is readl.
Is the comment wrong? Or is it not applcable and we should be using
readl() here?
> */
> - return readl_relaxed(panel->p0_base + offset);
> + return readl_relaxed(panel->pixel_base + offset);
> }
>
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 47+ messages in thread
* [PATCH RESEND v5 04/25] drm/msm/dp: use stream_id to change offsets in dp_catalog
2026-06-29 14:14 [PATCH RESEND v5 00/25] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
` (2 preceding siblings ...)
2026-06-29 14:14 ` [PATCH RESEND v5 03/25] drm/msm/dp: Add support for programming p1/p2/p3 register blocks Yongxing Mou
@ 2026-06-29 14:14 ` Yongxing Mou
2026-07-12 11:29 ` Dmitry Baryshkov
2026-06-29 14:14 ` [PATCH RESEND v5 05/25] drm/msm/dp: add support to send ACT packets for MST Yongxing Mou
` (21 subsequent siblings)
25 siblings, 1 reply; 47+ messages in thread
From: Yongxing Mou @ 2026-06-29 14:14 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, Abhinav Kumar
From: Abhinav Kumar <quic_abhinavk@quicinc.com>
In the DP MST architecture, stream 1 shares the same link clock as
stream 0 but uses different register offsets within the same link
register space. Use the dp_panel's stream_id to select the correct
register offsets for stream 1 in dp_catalog. Also add stream 1
register defines.
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 | 78 ++++++++++++++++++++++++------
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 | 94 ++++++++++++++++++++++++++++++++++++-
drivers/gpu/drm/msm/dp/dp_panel.h | 4 ++
drivers/gpu/drm/msm/dp/dp_reg.h | 44 +++++++++++++++++
6 files changed, 229 insertions(+), 19 deletions(-)
diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c
index 68fb4facb056..5c491a925b4b 100644
--- a/drivers/gpu/drm/msm/dp/dp_ctrl.c
+++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c
@@ -117,6 +117,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;
@@ -172,6 +174,49 @@ static inline void msm_dp_write_link(struct msm_dp_ctrl_private *ctrl,
writel(data, ctrl->link_base + offset);
}
+static inline u32 msm_dp_read_stream_link(struct msm_dp_ctrl_private *ctrl,
+ enum msm_dp_stream_id stream_id, u32 offset)
+{
+ offset = msm_dp_stream_reg(stream_id, 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_stream_link(struct msm_dp_ctrl_private *ctrl,
+ 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()
+ */
+ offset = msm_dp_stream_reg(stream_id, 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,
struct msm_dp_link_info *link)
{
@@ -397,7 +442,8 @@ static void msm_dp_ctrl_config_ctrl_streams(struct msm_dp_ctrl_private *ctrl,
/*
* RMW: Called from atomic_enable(). Serialized by the DRM atomic framework.
*/
- 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, REG_DP_CONFIGURATION_CTRL);
if (msm_dp_panel->msm_dp_mode.out_fmt_is_yuv_420)
config |= DP_CONFIGURATION_CTRL_RGB_YUV; /* YUV420 */
@@ -412,7 +458,7 @@ 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);
+ msm_dp_write_stream_link(ctrl, msm_dp_panel->stream_id, REG_DP_CONFIGURATION_CTRL, config);
}
static void msm_dp_ctrl_config_ctrl_link(struct msm_dp_ctrl_private *ctrl,
@@ -469,7 +515,7 @@ static void msm_dp_ctrl_config_misc1_misc0(struct msm_dp_ctrl_private *ctrl,
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);
+ misc_val = msm_dp_read_stream_link(ctrl, msm_dp_panel->stream_id, REG_DP_MISC1_MISC0);
/* clear bpp bits */
misc_val &= ~(0x07 << DP_MISC0_TEST_BITS_DEPTH_SHIFT);
@@ -479,7 +525,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_stream_link(ctrl, msm_dp_panel->stream_id, REG_DP_MISC1_MISC0, misc_val);
}
static void msm_dp_ctrl_configure_source_params(struct msm_dp_ctrl_private *ctrl,
@@ -2461,8 +2507,8 @@ 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,
- u32 rate, u32 stream_rate_khz,
- bool is_ycbcr_420)
+ struct msm_dp_panel *panel,
+ u32 rate, u32 stream_rate_khz)
{
u32 pixel_m, pixel_n;
u32 mvid, nvid, pixel_div, dispcc_input_rate;
@@ -2514,7 +2560,7 @@ static void msm_dp_ctrl_config_msa(struct msm_dp_ctrl_private *ctrl,
nvid = temp;
}
- if (is_ycbcr_420)
+ if (panel->msm_dp_mode.out_fmt_is_yuv_420)
mvid /= 2;
if (link_rate_hbr2 == rate)
@@ -2524,8 +2570,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_stream_link(ctrl, panel->stream_id, REG_DP_SOFTWARE_MVID, mvid);
+ msm_dp_write_stream_link(ctrl, panel->stream_id, REG_DP_SOFTWARE_NVID, nvid);
}
int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl,
@@ -2597,14 +2643,14 @@ 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, panel);
+ if (panel->stream_id == DP_STREAM_0)
+ msm_dp_ctrl_config_ctrl_link(ctrl, panel);
msm_dp_ctrl_configure_source_params(ctrl, panel);
msm_dp_ctrl_config_msa(ctrl,
- ctrl->link->link_params.rate,
- pixel_rate_orig,
- panel->msm_dp_mode.out_fmt_is_yuv_420);
+ panel, ctrl->link->link_params.rate,
+ pixel_rate_orig);
msm_dp_panel_clear_dsc_dto(panel);
@@ -2788,7 +2834,9 @@ struct msm_dp_ctrl *msm_dp_ctrl_get(struct device *dev, struct msm_dp_link *link
struct drm_dp_aux *aux,
struct phy *phy,
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 +2875,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);
if (ret) {
diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.h b/drivers/gpu/drm/msm/dp/dp_ctrl.h
index 305add3dcd93..49d16911ae8b 100644
--- a/drivers/gpu/drm/msm/dp/dp_ctrl.h
+++ b/drivers/gpu/drm/msm/dp/dp_ctrl.h
@@ -35,7 +35,9 @@ struct msm_dp_ctrl *msm_dp_ctrl_get(struct device *dev,
struct drm_dp_aux *aux,
struct phy *phy,
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,
struct msm_dp_panel *panel);
diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
index 74f481a18164..c58896b351b3 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;
@@ -564,7 +570,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);
@@ -573,7 +580,8 @@ static int msm_dp_init_sub_modules(struct msm_dp_display_private *dp)
}
dp->ctrl = msm_dp_ctrl_get(dev, dp->link, dp->aux,
- phy, dp->ahb_base, dp->link_base);
+ phy, 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);
@@ -850,6 +858,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,
@@ -1196,6 +1208,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 238920c45261..e0c0e8c9178c 100644
--- a/drivers/gpu/drm/msm/dp/dp_panel.c
+++ b/drivers/gpu/drm/msm/dp/dp_panel.c
@@ -25,13 +25,84 @@ 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;
};
+u32 msm_dp_stream_reg(enum msm_dp_stream_id id, u32 reg)
+{
+ bool is_s1 = (id == DP_STREAM_1);
+
+ if (id == DP_STREAM_0)
+ return reg;
+
+ switch (reg) {
+ case REG_DP_CONFIGURATION_CTRL:
+ return is_s1 ? REG_DP1_CONFIGURATION_CTRL : REG_DP_MSTLINK_CONFIGURATION_CTRL;
+ case REG_DP_SOFTWARE_MVID:
+ return is_s1 ? REG_DP1_SOFTWARE_MVID : REG_MSTLINK_SOFTWARE_MVID;
+ case REG_DP_SOFTWARE_NVID:
+ return is_s1 ? REG_DP1_SOFTWARE_NVID : REG_MSTLINK_SOFTWARE_NVID;
+ case REG_DP_TOTAL_HOR_VER:
+ return is_s1 ? REG_DP1_TOTAL_HOR_VER : REG_DP_MSTLINK_TOTAL_HOR_VER;
+ case REG_DP_START_HOR_VER_FROM_SYNC:
+ return is_s1 ? REG_DP1_START_HOR_VER_FROM_SYNC
+ : REG_DP_MSTLINK_START_HOR_VER_FROM_SYNC;
+ case REG_DP_HSYNC_VSYNC_WIDTH_POLARITY:
+ return is_s1 ? REG_DP1_HSYNC_VSYNC_WIDTH_POLARITY
+ : REG_DP_MSTLINK_HSYNC_VSYNC_WIDTH_POLARITY;
+ case REG_DP_ACTIVE_HOR_VER:
+ return is_s1 ? REG_DP1_ACTIVE_HOR_VER : REG_DP_MSTLINK_ACTIVE_HOR_VER;
+ case REG_DP_MISC1_MISC0:
+ return is_s1 ? REG_DP1_MISC1_MISC0 : REG_DP_MSTLINK_MISC1_MISC0;
+ case MMSS_DP_SDP_CFG:
+ return is_s1 ? MMSS_DP1_SDP_CFG : MMSS_DP_MSTLINK_SDP_CFG;
+ case MMSS_DP_SDP_CFG2:
+ return is_s1 ? MMSS_DP1_SDP_CFG2 : MMSS_DP_MSTLINK_SDP_CFG2;
+ case MMSS_DP_SDP_CFG3:
+ return is_s1 ? MMSS_DP1_SDP_CFG3 : MMSS_DP_MSTLINK_SDP_CFG3;
+ case MMSS_DP_GENERIC0_0:
+ return is_s1 ? MMSS_DP1_GENERIC0_0 : MMSS_DP_MSTLINK_GENERIC0_0;
+ case MMSS_DP_GENERIC0_1:
+ return is_s1 ? MMSS_DP1_GENERIC0_1 : MMSS_DP_MSTLINK_GENERIC0_1;
+ case MMSS_DP_GENERIC0_2:
+ return is_s1 ? MMSS_DP1_GENERIC0_2 : MMSS_DP_MSTLINK_GENERIC0_2;
+ case MMSS_DP_GENERIC0_3:
+ return is_s1 ? MMSS_DP1_GENERIC0_3 : MMSS_DP_MSTLINK_GENERIC0_3;
+ case MMSS_DP_GENERIC0_4:
+ return is_s1 ? MMSS_DP1_GENERIC0_4 : MMSS_DP_MSTLINK_GENERIC0_4;
+ case MMSS_DP_GENERIC0_5:
+ return is_s1 ? MMSS_DP1_GENERIC0_5 : MMSS_DP_MSTLINK_GENERIC0_5;
+ case MMSS_DP_GENERIC0_6:
+ return is_s1 ? MMSS_DP1_GENERIC0_6 : MMSS_DP_MSTLINK_GENERIC0_6;
+ case MMSS_DP_GENERIC0_7:
+ return is_s1 ? MMSS_DP1_GENERIC0_7 : MMSS_DP_MSTLINK_GENERIC0_7;
+ case MMSS_DP_GENERIC0_8:
+ return is_s1 ? MMSS_DP1_GENERIC0_8 : MMSS_DP_MSTLINK_GENERIC0_8;
+ case MMSS_DP_GENERIC0_9:
+ return is_s1 ? MMSS_DP1_GENERIC0_9 : MMSS_DP_MSTLINK_GENERIC0_9;
+ default:
+ return reg;
+ }
+}
+
static inline u32 msm_dp_read_link(struct msm_dp_panel_private *panel, u32 offset)
{
- return readl_relaxed(panel->link_base + offset);
+ offset = msm_dp_stream_reg(panel->msm_dp_panel.stream_id, 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,
@@ -41,7 +112,22 @@ static inline void msm_dp_write_link(struct msm_dp_panel_private *panel,
* 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);
+ offset = msm_dp_stream_reg(panel->msm_dp_panel.stream_id, 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,
@@ -701,6 +787,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;
@@ -720,6 +808,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 218a09a2fa65..dc046fec24fc 100644
--- a/drivers/gpu/drm/msm/dp/dp_panel.h
+++ b/drivers/gpu/drm/msm/dp/dp_panel.h
@@ -99,8 +99,12 @@ static inline bool is_lane_count_valid(u32 lane_count)
lane_count == 4);
}
+u32 msm_dp_stream_reg(enum msm_dp_stream_id id, u32 reg);
+
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 3689642b7fc0..310e5a1cc934 100644
--- a/drivers/gpu/drm/msm/dp/dp_reg.h
+++ b/drivers/gpu/drm/msm/dp/dp_reg.h
@@ -332,6 +332,50 @@
#define DP_TPG_VIDEO_CONFIG_BPP_8BIT (0x00000001)
#define DP_TPG_VIDEO_CONFIG_RGB (0x00000004)
+#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_START_HOR_VER_FROM_SYNC (0x00000420)
+#define REG_DP1_HSYNC_VSYNC_WIDTH_POLARITY (0x00000424)
+#define REG_DP1_ACTIVE_HOR_VER (0x00000428)
+#define REG_DP1_MISC1_MISC0 (0x0000042C)
+#define MMSS_DP1_GENERIC0_0 (0x00000490)
+#define MMSS_DP1_GENERIC0_1 (0x00000494)
+#define MMSS_DP1_GENERIC0_2 (0x00000498)
+#define MMSS_DP1_GENERIC0_3 (0x0000049C)
+#define MMSS_DP1_GENERIC0_4 (0x000004A0)
+#define MMSS_DP1_GENERIC0_5 (0x000004A4)
+#define MMSS_DP1_GENERIC0_6 (0x000004A8)
+#define MMSS_DP1_GENERIC0_7 (0x000004AC)
+#define MMSS_DP1_GENERIC0_8 (0x000004B0)
+#define MMSS_DP1_GENERIC0_9 (0x000004B4)
+#define MMSS_DP1_SDP_CFG (0x000004E0)
+#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_GENERIC0_3 (0x000000C8)
+#define MMSS_DP_MSTLINK_GENERIC0_4 (0x000000CC)
+#define MMSS_DP_MSTLINK_GENERIC0_5 (0x000000D0)
+#define MMSS_DP_MSTLINK_GENERIC0_6 (0x000000D4)
+#define MMSS_DP_MSTLINK_GENERIC0_7 (0x000000D8)
+#define MMSS_DP_MSTLINK_GENERIC0_8 (0x000000DC)
+#define MMSS_DP_MSTLINK_GENERIC0_9 (0x000000E0)
+#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] 47+ messages in thread* Re: [PATCH RESEND v5 04/25] drm/msm/dp: use stream_id to change offsets in dp_catalog
2026-06-29 14:14 ` [PATCH RESEND v5 04/25] drm/msm/dp: use stream_id to change offsets in dp_catalog Yongxing Mou
@ 2026-07-12 11:29 ` Dmitry Baryshkov
0 siblings, 0 replies; 47+ messages in thread
From: Dmitry Baryshkov @ 2026-07-12 11:29 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 29, 2026 at 10:14:25PM +0800, Yongxing Mou wrote:
> From: Abhinav Kumar <quic_abhinavk@quicinc.com>
>
> In the DP MST architecture, stream 1 shares the same link clock as
> stream 0 but uses different register offsets within the same link
> register space. Use the dp_panel's stream_id to select the correct
> register offsets for stream 1 in dp_catalog. Also add stream 1
> register defines.
>
> Streams 2 and 3 are not covered here, as they use separate link clocks
> and require separate handling.
I think, this is no longer true. I see them being handled here.
>
> 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 | 78 ++++++++++++++++++++++++------
> 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 | 94 ++++++++++++++++++++++++++++++++++++-
> drivers/gpu/drm/msm/dp/dp_panel.h | 4 ++
> drivers/gpu/drm/msm/dp/dp_reg.h | 44 +++++++++++++++++
> 6 files changed, 229 insertions(+), 19 deletions(-)
>
> @@ -397,7 +442,8 @@ static void msm_dp_ctrl_config_ctrl_streams(struct msm_dp_ctrl_private *ctrl,
> /*
> * RMW: Called from atomic_enable(). Serialized by the DRM atomic framework.
> */
> - 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, REG_DP_CONFIGURATION_CTRL);
Why is it being done only for stream 0?
>
> if (msm_dp_panel->msm_dp_mode.out_fmt_is_yuv_420)
> config |= DP_CONFIGURATION_CTRL_RGB_YUV; /* YUV420 */
> @@ -412,7 +458,7 @@ 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);
> + msm_dp_write_stream_link(ctrl, msm_dp_panel->stream_id, REG_DP_CONFIGURATION_CTRL, config);
> }
>
> static void msm_dp_ctrl_config_ctrl_link(struct msm_dp_ctrl_private *ctrl,
> @@ -2514,7 +2560,7 @@ static void msm_dp_ctrl_config_msa(struct msm_dp_ctrl_private *ctrl,
> nvid = temp;
> }
>
> - if (is_ycbcr_420)
> + if (panel->msm_dp_mode.out_fmt_is_yuv_420)
Unrelated change.
> mvid /= 2;
>
> if (link_rate_hbr2 == rate)
> @@ -2524,8 +2570,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_stream_link(ctrl, panel->stream_id, REG_DP_SOFTWARE_MVID, mvid);
> + msm_dp_write_stream_link(ctrl, panel->stream_id, REG_DP_SOFTWARE_NVID, nvid);
> }
>
> int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl,
> @@ -2597,14 +2643,14 @@ 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, panel);
> + if (panel->stream_id == DP_STREAM_0)
> + msm_dp_ctrl_config_ctrl_link(ctrl, panel);
Why is it only done for stream 0? Split all unrelated changes.
>
> msm_dp_ctrl_configure_source_params(ctrl, panel);
>
> msm_dp_ctrl_config_msa(ctrl,
> - ctrl->link->link_params.rate,
> - pixel_rate_orig,
> - panel->msm_dp_mode.out_fmt_is_yuv_420);
> + panel, ctrl->link->link_params.rate,
> + pixel_rate_orig);
>
> msm_dp_panel_clear_dsc_dto(panel);
>
> diff --git a/drivers/gpu/drm/msm/dp/dp_reg.h b/drivers/gpu/drm/msm/dp/dp_reg.h
> index 3689642b7fc0..310e5a1cc934 100644
> --- a/drivers/gpu/drm/msm/dp/dp_reg.h
> +++ b/drivers/gpu/drm/msm/dp/dp_reg.h
> @@ -332,6 +332,50 @@
> #define DP_TPG_VIDEO_CONFIG_BPP_8BIT (0x00000001)
> #define DP_TPG_VIDEO_CONFIG_RGB (0x00000004)
>
> +#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)
lowercase hex. Also are there no registers from the same register space?
> +#define REG_DP1_START_HOR_VER_FROM_SYNC (0x00000420)
> +#define REG_DP1_HSYNC_VSYNC_WIDTH_POLARITY (0x00000424)
> +#define REG_DP1_ACTIVE_HOR_VER (0x00000428)
> +#define REG_DP1_MISC1_MISC0 (0x0000042C)
> +#define MMSS_DP1_GENERIC0_0 (0x00000490)
> +#define MMSS_DP1_GENERIC0_1 (0x00000494)
> +#define MMSS_DP1_GENERIC0_2 (0x00000498)
> +#define MMSS_DP1_GENERIC0_3 (0x0000049C)
> +#define MMSS_DP1_GENERIC0_4 (0x000004A0)
> +#define MMSS_DP1_GENERIC0_5 (0x000004A4)
> +#define MMSS_DP1_GENERIC0_6 (0x000004A8)
> +#define MMSS_DP1_GENERIC0_7 (0x000004AC)
> +#define MMSS_DP1_GENERIC0_8 (0x000004B0)
> +#define MMSS_DP1_GENERIC0_9 (0x000004B4)
> +#define MMSS_DP1_SDP_CFG (0x000004E0)
> +#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)
Why are they not REG_DP_MSTLINK?
> +#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_GENERIC0_3 (0x000000C8)
> +#define MMSS_DP_MSTLINK_GENERIC0_4 (0x000000CC)
> +#define MMSS_DP_MSTLINK_GENERIC0_5 (0x000000D0)
> +#define MMSS_DP_MSTLINK_GENERIC0_6 (0x000000D4)
> +#define MMSS_DP_MSTLINK_GENERIC0_7 (0x000000D8)
> +#define MMSS_DP_MSTLINK_GENERIC0_8 (0x000000DC)
> +#define MMSS_DP_MSTLINK_GENERIC0_9 (0x000000E0)
> +#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] 47+ messages in thread
* [PATCH RESEND v5 05/25] drm/msm/dp: add support to send ACT packets for MST
2026-06-29 14:14 [PATCH RESEND v5 00/25] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
` (3 preceding siblings ...)
2026-06-29 14:14 ` [PATCH RESEND v5 04/25] drm/msm/dp: use stream_id to change offsets in dp_catalog Yongxing Mou
@ 2026-06-29 14:14 ` Yongxing Mou
2026-07-12 13:56 ` Dmitry Baryshkov
2026-06-29 14:14 ` [PATCH RESEND v5 06/25] drm/msm/dp: Add support to enable MST in mainlink control Yongxing Mou
` (20 subsequent siblings)
25 siblings, 1 reply; 47+ messages in thread
From: Yongxing Mou @ 2026-06-29 14:14 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, 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 | 44 ++++++++++++++++++++++++++++++++++++-
drivers/gpu/drm/msm/dp/dp_ctrl.h | 5 ++++-
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, 52 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c
index 5c491a925b4b..6754b10d418c 100644
--- a/drivers/gpu/drm/msm/dp/dp_ctrl.c
+++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c
@@ -142,6 +142,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)
@@ -244,6 +245,37 @@ 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_panel *panel)
+{
+ struct msm_dp_ctrl_private *ctrl;
+ const struct drm_display_mode *mode;
+ u32 frame_time_ms;
+
+ ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl);
+
+ if (!ctrl->mst_active)
+ return 0;
+
+ mode = &panel->msm_dp_mode.drm_mode;
+ frame_time_ms = DIV_ROUND_UP((u64)mode->htotal * mode->vtotal,
+ mode->clock) + 1;
+
+ msm_dp_write_link(ctrl, REG_DP_MST_ACT, 0x1);
+ /* make sure ACT signal is performed */
+ wmb();
+
+ msleep(frame_time_ms);
+
+ /* Hardware clears this bit after sending 4 ACT headers */
+ if (msm_dp_read_link(ctrl, REG_DP_MST_ACT)) {
+ 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
*/
@@ -2610,7 +2642,8 @@ int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl,
return ret;
}
-int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *panel)
+int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *panel,
+ bool mst_active)
{
int ret = 0;
bool mainlink_ready = false;
@@ -2623,6 +2656,8 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *
ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl);
+ ctrl->mst_active = mst_active;
+
pixel_rate_orig = panel->msm_dp_mode.drm_mode.clock;
pixel_rate = pixel_rate_orig;
@@ -2658,6 +2693,10 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *
msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, DP_STATE_CTRL_SEND_VIDEO);
+ ret = msm_dp_ctrl_mst_send_act(msm_dp_ctrl, panel);
+ if (ret)
+ return ret;
+
ret = msm_dp_ctrl_wait4video_ready(ctrl);
if (ret)
return ret;
@@ -2695,6 +2734,8 @@ void msm_dp_ctrl_off_link(struct msm_dp_ctrl *msm_dp_ctrl,
msm_dp_ctrl_reset(&ctrl->msm_dp_ctrl, panel);
+ ctrl->mst_active = false;
+
dev_pm_opp_set_rate(ctrl->dev, 0);
msm_dp_ctrl_link_clk_disable(&ctrl->msm_dp_ctrl);
@@ -2877,6 +2918,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);
if (ret) {
diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.h b/drivers/gpu/drm/msm/dp/dp_ctrl.h
index 49d16911ae8b..6de028da85fb 100644
--- a/drivers/gpu/drm/msm/dp/dp_ctrl.h
+++ b/drivers/gpu/drm/msm/dp/dp_ctrl.h
@@ -18,7 +18,8 @@ struct phy;
int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl,
struct msm_dp_panel *panel);
-int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *panel);
+int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *panel,
+ bool mst_active);
int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl,
struct msm_dp_panel *panel,
bool force_link_train);
@@ -58,4 +59,6 @@ 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,
+ struct msm_dp_panel *panel);
#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 c58896b351b3..acb581a8a541 100644
--- a/drivers/gpu/drm/msm/dp/dp_display.c
+++ b/drivers/gpu/drm/msm/dp/dp_display.c
@@ -673,7 +673,7 @@ static int msm_dp_display_enable(struct msm_dp_display_private *dp,
return 0;
}
- rc = msm_dp_ctrl_on_stream(dp->ctrl, msm_dp_panel);
+ rc = msm_dp_ctrl_on_stream(dp->ctrl, msm_dp_panel, msm_dp_display->mst_active);
if (!rc)
msm_dp_display->power_on = true;
@@ -1509,6 +1509,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, msm_dp_display->panel);
}
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 d3d4ab98089d..e987de80522c 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 310e5a1cc934..6808965878d4 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] 47+ messages in thread* Re: [PATCH RESEND v5 05/25] drm/msm/dp: add support to send ACT packets for MST
2026-06-29 14:14 ` [PATCH RESEND v5 05/25] drm/msm/dp: add support to send ACT packets for MST Yongxing Mou
@ 2026-07-12 13:56 ` Dmitry Baryshkov
0 siblings, 0 replies; 47+ messages in thread
From: Dmitry Baryshkov @ 2026-07-12 13:56 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 29, 2026 at 10:14:26PM +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 | 44 ++++++++++++++++++++++++++++++++++++-
> drivers/gpu/drm/msm/dp/dp_ctrl.h | 5 ++++-
> 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, 52 insertions(+), 3 deletions(-)
>
> @@ -2623,6 +2656,8 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *
>
> ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl);
>
> + ctrl->mst_active = mst_active;
> +
> pixel_rate_orig = panel->msm_dp_mode.drm_mode.clock;
> pixel_rate = pixel_rate_orig;
>
> @@ -2658,6 +2693,10 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *
>
> msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, DP_STATE_CTRL_SEND_VIDEO);
>
> + ret = msm_dp_ctrl_mst_send_act(msm_dp_ctrl, panel);
> + if (ret)
> + return ret;
Should not we mark mst_active only here?
> +
> ret = msm_dp_ctrl_wait4video_ready(ctrl);
> if (ret)
> return ret;
> diff --git a/drivers/gpu/drm/msm/dp/dp_reg.h b/drivers/gpu/drm/msm/dp/dp_reg.h
> index 310e5a1cc934..6808965878d4 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)
It's not that it is a register in its own register space.
> +
> #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] 47+ messages in thread
* [PATCH RESEND v5 06/25] drm/msm/dp: Add support to enable MST in mainlink control
2026-06-29 14:14 [PATCH RESEND v5 00/25] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
` (4 preceding siblings ...)
2026-06-29 14:14 ` [PATCH RESEND v5 05/25] drm/msm/dp: add support to send ACT packets for MST Yongxing Mou
@ 2026-06-29 14:14 ` Yongxing Mou
2026-06-29 14:14 ` [PATCH RESEND v5 07/25] drm/msm/dp: no need to update tu calculation for mst Yongxing Mou
` (19 subsequent siblings)
25 siblings, 0 replies; 47+ messages in thread
From: Yongxing Mou @ 2026-06-29 14:14 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, Abhinav Kumar,
Dmitry Baryshkov
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>
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@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 6754b10d418c..e9aa0e254234 100644
--- a/drivers/gpu/drm/msm/dp/dp_ctrl.c
+++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c
@@ -276,6 +276,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, 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, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
+}
+
/*
* NOTE: resetting DP controller will also clear any pending HPD related interrupts
*/
@@ -2678,6 +2691,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 (panel->stream_id == DP_STREAM_0)
msm_dp_ctrl_config_ctrl_link(ctrl, panel);
@@ -2731,6 +2747,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, panel);
diff --git a/drivers/gpu/drm/msm/dp/dp_reg.h b/drivers/gpu/drm/msm/dp/dp_reg.h
index 6808965878d4..deb40ed24654 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] 47+ messages in thread* [PATCH RESEND v5 07/25] drm/msm/dp: no need to update tu calculation for mst
2026-06-29 14:14 [PATCH RESEND v5 00/25] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
` (5 preceding siblings ...)
2026-06-29 14:14 ` [PATCH RESEND v5 06/25] drm/msm/dp: Add support to enable MST in mainlink control Yongxing Mou
@ 2026-06-29 14:14 ` Yongxing Mou
2026-06-29 14:14 ` [PATCH RESEND v5 08/25] drm/msm/dp: Add support for MST channel slot allocation Yongxing Mou
` (18 subsequent siblings)
25 siblings, 0 replies; 47+ messages in thread
From: Yongxing Mou @ 2026-06-29 14:14 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, Abhinav Kumar,
Dmitry Baryshkov
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>
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@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 e9aa0e254234..5b5149b160df 100644
--- a/drivers/gpu/drm/msm/dp/dp_ctrl.c
+++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c
@@ -2705,7 +2705,8 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *
msm_dp_panel_clear_dsc_dto(panel);
- msm_dp_ctrl_setup_tr_unit(ctrl, panel);
+ if (!ctrl->mst_active)
+ msm_dp_ctrl_setup_tr_unit(ctrl, panel);
msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, DP_STATE_CTRL_SEND_VIDEO);
--
2.43.0
^ permalink raw reply [flat|nested] 47+ messages in thread* [PATCH RESEND v5 08/25] drm/msm/dp: Add support for MST channel slot allocation
2026-06-29 14:14 [PATCH RESEND v5 00/25] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
` (6 preceding siblings ...)
2026-06-29 14:14 ` [PATCH RESEND v5 07/25] drm/msm/dp: no need to update tu calculation for mst Yongxing Mou
@ 2026-06-29 14:14 ` Yongxing Mou
2026-07-12 18:57 ` Dmitry Baryshkov
2026-06-29 14:14 ` [PATCH RESEND v5 09/25] drm/msm/dp: Add support for sending VCPF packets in DP controller Yongxing Mou
` (17 subsequent siblings)
25 siblings, 1 reply; 47+ messages in thread
From: Yongxing Mou @ 2026-06-29 14:14 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, Abhinav Kumar
From: Abhinav Kumar <quic_abhinavk@quicinc.com>
DP MST streams share 64 MTP slots in a time-multiplexed manner. Add
support for calculating the rate governor, slot allocation, and slot
reservation in the DP controller.
Each MST stream can reserve its slots by calling
msm_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 | 192 ++++++++++++++++++++++++++++++++++++
drivers/gpu/drm/msm/dp/dp_ctrl.h | 4 +
drivers/gpu/drm/msm/dp/dp_display.c | 17 ++++
drivers/gpu/drm/msm/dp/dp_display.h | 2 +
drivers/gpu/drm/msm/dp/dp_panel.c | 6 ++
drivers/gpu/drm/msm/dp/dp_panel.h | 1 +
drivers/gpu/drm/msm/dp/dp_reg.h | 10 ++
7 files changed, 232 insertions(+)
diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c
index 5b5149b160df..15df82a0caca 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;
@@ -143,6 +149,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)
@@ -289,6 +297,44 @@ static void msm_dp_ctrl_mst_config(struct msm_dp_ctrl_private *ctrl, bool enable
msm_dp_write_link(ctrl, 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 slot_reg_1 = 0, slot_reg_2 = 0;
+
+ 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 (ch_start_slot && tot_slot_cnt) {
+ u64 mask = GENMASK_ULL(ch_start_slot + tot_slot_cnt - 2, ch_start_slot - 1);
+
+ slot_reg_1 = mask & 0xFFFFFFFF;
+ slot_reg_2 = (mask >> 32) & 0xFFFFFFFF;
+ }
+
+ msm_dp_write_stream_link(ctrl, stream_id, REG_DP_DP0_TIMESLOT_1_32, slot_reg_1);
+ msm_dp_write_stream_link(ctrl, stream_id, REG_DP_DP0_TIMESLOT_33_63, 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 = y_frac_enum | (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);
+
+ msm_dp_write_stream_link(ctrl, stream_id, REG_DP_DP0_RG, rg);
+}
+
/*
* NOTE: resetting DP controller will also clear any pending HPD related interrupts
*/
@@ -2619,6 +2665,117 @@ static void msm_dp_ctrl_config_msa(struct msm_dp_ctrl_private *ctrl,
msm_dp_write_stream_link(ctrl, panel->stream_id, REG_DP_SOFTWARE_NVID, nvid);
}
+/*
+ * Calculate MST Rate Governor parameters x_int and y_frac_enum (HPG 3.8.1.2).
+ *
+ * The RG paces symbol delivery per MTP via: M = x_int + y_frac_enum/256
+ * where M is the target symbol count per MTP across all lanes.
+ *
+ * min_slot_cnt = (pclk * bpp/8) / (lclk * lanes) * 64 -- slots at 1.0x BW
+ * max_slot_cnt = pbn * 54 / (lclk * lanes) -- slots at PBN limit
+ * raw_target_sc = (min + max) / 2 -- midpoint (~1.003x)
+ *
+ * Quantize raw_target_sc to 1/(256*lanes) steps, then:
+ * M = Chosen_TARGET_Slot_Count * lanes
+ * x_int = INT(M)
+ * y_frac_enum = CEIL(256 * MOD(M, 1))
+ */
+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,
struct msm_dp_panel *panel,
bool force_link_train)
@@ -2708,6 +2865,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, panel);
+ msm_dp_ctrl_mst_stream_setup(ctrl, panel);
+
msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, DP_STATE_CTRL_SEND_VIDEO);
ret = msm_dp_ctrl_mst_send_act(msm_dp_ctrl, panel);
@@ -2760,6 +2919,39 @@ void msm_dp_ctrl_off_link(struct msm_dp_ctrl *msm_dp_ctrl,
phy_power_off(phy);
}
+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_panel *panel)
{
diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.h b/drivers/gpu/drm/msm/dp/dp_ctrl.h
index 6de028da85fb..e1d10ae20f70 100644
--- a/drivers/gpu/drm/msm/dp/dp_ctrl.h
+++ b/drivers/gpu/drm/msm/dp/dp_ctrl.h
@@ -61,4 +61,8 @@ 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,
struct msm_dp_panel *panel);
+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 acb581a8a541..36857d6ed313 100644
--- a/drivers/gpu/drm/msm/dp/dp_display.c
+++ b/drivers/gpu/drm/msm/dp/dp_display.c
@@ -749,6 +749,20 @@ 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,
+ u32 start_slot, u32 num_slots, u32 pbn)
+{
+ struct msm_dp_display_private *dp;
+
+ dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display);
+
+ msm_dp_ctrl_set_mst_channel_info(dp->ctrl, panel->stream_id, start_slot, num_slots);
+
+ panel->pbn = pbn;
+
+ return 0;
+}
+
/**
* msm_dp_bridge_mode_valid - callback to determine if specified mode is valid
* @dp: Pointer to dp display structure
@@ -1489,6 +1503,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, 0, 0);
+
rc = msm_dp_display_enable(dp, dp->panel);
if (rc)
DRM_ERROR("DP display enable failed, rc=%d\n", rc);
@@ -1509,6 +1525,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_stream_channel_slot_setup(msm_dp_display->ctrl);
msm_dp_ctrl_mst_send_act(msm_dp_display->ctrl, msm_dp_display->panel);
}
diff --git a/drivers/gpu/drm/msm/dp/dp_display.h b/drivers/gpu/drm/msm/dp/dp_display.h
index e987de80522c..45e2cc2d6add 100644
--- a/drivers/gpu/drm/msm/dp/dp_display.h
+++ b/drivers/gpu/drm/msm/dp/dp_display.h
@@ -43,5 +43,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,
+ u32 start_slot, u32 num_slots, u32 pbn);
#endif /* _DP_DISPLAY_H_ */
diff --git a/drivers/gpu/drm/msm/dp/dp_panel.c b/drivers/gpu/drm/msm/dp/dp_panel.c
index e0c0e8c9178c..ef2ded8ec4ea 100644
--- a/drivers/gpu/drm/msm/dp/dp_panel.c
+++ b/drivers/gpu/drm/msm/dp/dp_panel.c
@@ -57,6 +57,12 @@ u32 msm_dp_stream_reg(enum msm_dp_stream_id id, u32 reg)
return is_s1 ? REG_DP1_ACTIVE_HOR_VER : REG_DP_MSTLINK_ACTIVE_HOR_VER;
case REG_DP_MISC1_MISC0:
return is_s1 ? REG_DP1_MISC1_MISC0 : REG_DP_MSTLINK_MISC1_MISC0;
+ case REG_DP_DP0_TIMESLOT_1_32:
+ return is_s1 ? REG_DP_DP1_TIMESLOT_1_32 : REG_DP_MSTLINK_TIMESLOT_1_32;
+ case REG_DP_DP0_TIMESLOT_33_63:
+ return is_s1 ? REG_DP_DP1_TIMESLOT_33_63 : REG_DP_MSTLINK_TIMESLOT_33_63;
+ case REG_DP_DP0_RG:
+ return is_s1 ? REG_DP_DP1_RG : REG_DP_MSTLINK_DP_RG;
case MMSS_DP_SDP_CFG:
return is_s1 ? MMSS_DP1_SDP_CFG : MMSS_DP_MSTLINK_SDP_CFG;
case MMSS_DP_SDP_CFG2:
diff --git a/drivers/gpu/drm/msm/dp/dp_panel.h b/drivers/gpu/drm/msm/dp/dp_panel.h
index dc046fec24fc..3e78af9e430d 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 deb40ed24654..f2bd96f3bbd0 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_BPP_8BIT (0x00000001)
#define DP_TPG_VIDEO_CONFIG_RGB (0x00000004)
+
+#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)
@@ -359,8 +365,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] 47+ messages in thread* Re: [PATCH RESEND v5 08/25] drm/msm/dp: Add support for MST channel slot allocation
2026-06-29 14:14 ` [PATCH RESEND v5 08/25] drm/msm/dp: Add support for MST channel slot allocation Yongxing Mou
@ 2026-07-12 18:57 ` Dmitry Baryshkov
0 siblings, 0 replies; 47+ messages in thread
From: Dmitry Baryshkov @ 2026-07-12 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 Mon, Jun 29, 2026 at 10:14:29PM +0800, Yongxing Mou wrote:
> From: Abhinav Kumar <quic_abhinavk@quicinc.com>
>
> DP MST streams share 64 MTP slots in a time-multiplexed manner. Add
> support for calculating the rate governor, slot allocation, and slot
> reservation in the DP controller.
>
> Each MST stream can reserve its slots by calling
> msm_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 | 192 ++++++++++++++++++++++++++++++++++++
> drivers/gpu/drm/msm/dp/dp_ctrl.h | 4 +
> drivers/gpu/drm/msm/dp/dp_display.c | 17 ++++
> drivers/gpu/drm/msm/dp/dp_display.h | 2 +
> drivers/gpu/drm/msm/dp/dp_panel.c | 6 ++
> drivers/gpu/drm/msm/dp/dp_panel.h | 1 +
> drivers/gpu/drm/msm/dp/dp_reg.h | 10 ++
> 7 files changed, 232 insertions(+)
>
> diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c
> index 5b5149b160df..15df82a0caca 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;
> @@ -143,6 +149,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)
> @@ -289,6 +297,44 @@ static void msm_dp_ctrl_mst_config(struct msm_dp_ctrl_private *ctrl, bool enable
> msm_dp_write_link(ctrl, 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 slot_reg_1 = 0, slot_reg_2 = 0;
> +
> + 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);
Is this an actually possible error or is it defensive coding?
> + 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 (ch_start_slot && tot_slot_cnt) {
> + u64 mask = GENMASK_ULL(ch_start_slot + tot_slot_cnt - 2, ch_start_slot - 1);
> +
> + slot_reg_1 = mask & 0xFFFFFFFF;
> + slot_reg_2 = (mask >> 32) & 0xFFFFFFFF;
> + }
> +
> + msm_dp_write_stream_link(ctrl, stream_id, REG_DP_DP0_TIMESLOT_1_32, slot_reg_1);
> + msm_dp_write_stream_link(ctrl, stream_id, REG_DP_DP0_TIMESLOT_33_63, 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 = y_frac_enum | (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);
> +
> + msm_dp_write_stream_link(ctrl, stream_id, REG_DP_DP0_RG, rg);
> +}
> +
> /*
> * NOTE: resetting DP controller will also clear any pending HPD related interrupts
> */
> @@ -2619,6 +2665,117 @@ static void msm_dp_ctrl_config_msa(struct msm_dp_ctrl_private *ctrl,
> msm_dp_write_stream_link(ctrl, panel->stream_id, REG_DP_SOFTWARE_NVID, nvid);
> }
>
> +/*
> + * Calculate MST Rate Governor parameters x_int and y_frac_enum (HPG 3.8.1.2).
HPG links are pretty useless.
> + *
> + * The RG paces symbol delivery per MTP via: M = x_int + y_frac_enum/256
> + * where M is the target symbol count per MTP across all lanes.
> + *
> + * min_slot_cnt = (pclk * bpp/8) / (lclk * lanes) * 64 -- slots at 1.0x BW
> + * max_slot_cnt = pbn * 54 / (lclk * lanes) -- slots at PBN limit
> + * raw_target_sc = (min + max) / 2 -- midpoint (~1.003x)
> + *
> + * Quantize raw_target_sc to 1/(256*lanes) steps, then:
> + * M = Chosen_TARGET_Slot_Count * lanes
> + * x_int = INT(M)
> + * y_frac_enum = CEIL(256 * MOD(M, 1))
> + */
> +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,
> struct msm_dp_panel *panel,
> bool force_link_train)
> @@ -2708,6 +2865,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, panel);
>
> + msm_dp_ctrl_mst_stream_setup(ctrl, panel);
> +
> msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, DP_STATE_CTRL_SEND_VIDEO);
>
> ret = msm_dp_ctrl_mst_send_act(msm_dp_ctrl, panel);
> @@ -2760,6 +2919,39 @@ void msm_dp_ctrl_off_link(struct msm_dp_ctrl *msm_dp_ctrl,
> phy_power_off(phy);
> }
>
> +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_panel *panel)
> {
> diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.h b/drivers/gpu/drm/msm/dp/dp_ctrl.h
> index 6de028da85fb..e1d10ae20f70 100644
> --- a/drivers/gpu/drm/msm/dp/dp_ctrl.h
> +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.h
> @@ -61,4 +61,8 @@ 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,
> struct msm_dp_panel *panel);
> +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 acb581a8a541..36857d6ed313 100644
> --- a/drivers/gpu/drm/msm/dp/dp_display.c
> +++ b/drivers/gpu/drm/msm/dp/dp_display.c
> @@ -749,6 +749,20 @@ 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,
> + u32 start_slot, u32 num_slots, u32 pbn)
Why is it a display function rather than a panel or a control one?
> +{
> + struct msm_dp_display_private *dp;
> +
> + dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display);
> +
> + msm_dp_ctrl_set_mst_channel_info(dp->ctrl, panel->stream_id, start_slot, num_slots);
> +
> + panel->pbn = pbn;
> +
> + return 0;
> +}
> +
> /**
> * msm_dp_bridge_mode_valid - callback to determine if specified mode is valid
> * @dp: Pointer to dp display structure
> @@ -1489,6 +1503,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, 0, 0);
Why is it being called in the SST case?
> +
> rc = msm_dp_display_enable(dp, dp->panel);
> if (rc)
> DRM_ERROR("DP display enable failed, rc=%d\n", rc);
> @@ -1509,6 +1525,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_stream_channel_slot_setup(msm_dp_display->ctrl);
> msm_dp_ctrl_mst_send_act(msm_dp_display->ctrl, msm_dp_display->panel);
> }
>
> diff --git a/drivers/gpu/drm/msm/dp/dp_display.h b/drivers/gpu/drm/msm/dp/dp_display.h
> index e987de80522c..45e2cc2d6add 100644
> --- a/drivers/gpu/drm/msm/dp/dp_display.h
> +++ b/drivers/gpu/drm/msm/dp/dp_display.h
> @@ -43,5 +43,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,
> + u32 start_slot, u32 num_slots, u32 pbn);
>
> #endif /* _DP_DISPLAY_H_ */
> diff --git a/drivers/gpu/drm/msm/dp/dp_panel.c b/drivers/gpu/drm/msm/dp/dp_panel.c
> index e0c0e8c9178c..ef2ded8ec4ea 100644
> --- a/drivers/gpu/drm/msm/dp/dp_panel.c
> +++ b/drivers/gpu/drm/msm/dp/dp_panel.c
> @@ -57,6 +57,12 @@ u32 msm_dp_stream_reg(enum msm_dp_stream_id id, u32 reg)
> return is_s1 ? REG_DP1_ACTIVE_HOR_VER : REG_DP_MSTLINK_ACTIVE_HOR_VER;
> case REG_DP_MISC1_MISC0:
> return is_s1 ? REG_DP1_MISC1_MISC0 : REG_DP_MSTLINK_MISC1_MISC0;
> + case REG_DP_DP0_TIMESLOT_1_32:
> + return is_s1 ? REG_DP_DP1_TIMESLOT_1_32 : REG_DP_MSTLINK_TIMESLOT_1_32;
> + case REG_DP_DP0_TIMESLOT_33_63:
> + return is_s1 ? REG_DP_DP1_TIMESLOT_33_63 : REG_DP_MSTLINK_TIMESLOT_33_63;
> + case REG_DP_DP0_RG:
> + return is_s1 ? REG_DP_DP1_RG : REG_DP_MSTLINK_DP_RG;
Get all registers handled by these functions at once. There is no need
to keep it being patched over and over again.
> case MMSS_DP_SDP_CFG:
> return is_s1 ? MMSS_DP1_SDP_CFG : MMSS_DP_MSTLINK_SDP_CFG;
> case MMSS_DP_SDP_CFG2:
> diff --git a/drivers/gpu/drm/msm/dp/dp_panel.h b/drivers/gpu/drm/msm/dp/dp_panel.h
> index dc046fec24fc..3e78af9e430d 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 deb40ed24654..f2bd96f3bbd0 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_BPP_8BIT (0x00000001)
> #define DP_TPG_VIDEO_CONFIG_RGB (0x00000004)
>
> +
> +#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)
> @@ -359,8 +365,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] 47+ messages in thread
* [PATCH RESEND v5 09/25] drm/msm/dp: Add support for sending VCPF packets in DP controller
2026-06-29 14:14 [PATCH RESEND v5 00/25] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
` (7 preceding siblings ...)
2026-06-29 14:14 ` [PATCH RESEND v5 08/25] drm/msm/dp: Add support for MST channel slot allocation Yongxing Mou
@ 2026-06-29 14:14 ` Yongxing Mou
2026-06-29 14:14 ` [PATCH RESEND v5 10/25] drm/msm/dp: Always program MST_FIFO_CONSTANT_FILL for MST use cases Yongxing Mou
` (16 subsequent siblings)
25 siblings, 0 replies; 47+ messages in thread
From: Yongxing Mou @ 2026-06-29 14:14 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, 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.
Add 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 | 57 +++++++++++++++++++++++++++++++++++++
drivers/gpu/drm/msm/dp/dp_ctrl.h | 1 +
drivers/gpu/drm/msm/dp/dp_display.c | 2 +-
drivers/gpu/drm/msm/dp/dp_panel.c | 2 ++
drivers/gpu/drm/msm/dp/dp_reg.h | 8 ++++++
5 files changed, 69 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c
index 15df82a0caca..c4f1a68b1210 100644
--- a/drivers/gpu/drm/msm/dp/dp_ctrl.c
+++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c
@@ -65,6 +65,11 @@
(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)
@@ -398,6 +403,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)
@@ -407,6 +414,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)
@@ -426,6 +434,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;
@@ -525,6 +547,34 @@ void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl)
drm_dbg_dp(ctrl->drm_dev, "mainlink off\n");
}
+/* Must be called with msm_dp_mst::mst_lock held */
+void msm_dp_ctrl_push_vcpf(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_stream_link(ctrl, msm_dp_panel->stream_id, REG_DP_STATE_CTRL, state);
+
+ if (!wait_for_completion_timeout(&ctrl->idle_comp,
+ IDLE_PATTERN_COMPLETION_TIMEOUT_JIFFIES))
+ pr_warn("PUSH_VCPF pattern timedout\n");
+
+ drm_dbg_dp(ctrl->drm_dev, "vcpf sent\n");
+}
+
static void msm_dp_ctrl_config_ctrl_streams(struct msm_dp_ctrl_private *ctrl,
struct msm_dp_panel *msm_dp_panel)
{
@@ -2994,6 +3044,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 e1d10ae20f70..88a02d52f61c 100644
--- a/drivers/gpu/drm/msm/dp/dp_ctrl.h
+++ b/drivers/gpu/drm/msm/dp/dp_ctrl.h
@@ -27,6 +27,7 @@ void msm_dp_ctrl_off_link(struct msm_dp_ctrl *msm_dp_ctrl,
struct msm_dp_panel *panel);
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_vcpf(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *panel);
irqreturn_t msm_dp_ctrl_isr(struct msm_dp_ctrl *msm_dp_ctrl,
struct msm_dp_panel *panel);
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 36857d6ed313..1af56c84b82e 100644
--- a/drivers/gpu/drm/msm/dp/dp_display.c
+++ b/drivers/gpu/drm/msm/dp/dp_display.c
@@ -1524,7 +1524,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_push_vcpf(msm_dp_display->ctrl, msm_dp_display->panel);
msm_dp_ctrl_mst_stream_channel_slot_setup(msm_dp_display->ctrl);
msm_dp_ctrl_mst_send_act(msm_dp_display->ctrl, msm_dp_display->panel);
}
diff --git a/drivers/gpu/drm/msm/dp/dp_panel.c b/drivers/gpu/drm/msm/dp/dp_panel.c
index ef2ded8ec4ea..cbbcc0dbf652 100644
--- a/drivers/gpu/drm/msm/dp/dp_panel.c
+++ b/drivers/gpu/drm/msm/dp/dp_panel.c
@@ -39,6 +39,8 @@ u32 msm_dp_stream_reg(enum msm_dp_stream_id id, u32 reg)
return reg;
switch (reg) {
+ case REG_DP_STATE_CTRL:
+ return is_s1 ? REG_DP_STATE_CTRL : REG_DP_MSTLINK_STATE_CTRL;
case REG_DP_CONFIGURATION_CTRL:
return is_s1 ? REG_DP1_CONFIGURATION_CTRL : REG_DP_MSTLINK_CONFIGURATION_CTRL;
case REG_DP_SOFTWARE_MVID:
diff --git a/drivers/gpu/drm/msm/dp/dp_reg.h b/drivers/gpu/drm/msm/dp/dp_reg.h
index f2bd96f3bbd0..ade7b362d650 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)
@@ -143,6 +147,8 @@
#define DP_STATE_CTRL_LINK_TEST_CUSTOM_PATTERN (0x00000040)
#define DP_STATE_CTRL_SEND_VIDEO (0x00000080)
#define DP_STATE_CTRL_PUSH_IDLE (0x00000100)
+#define DP_DP0_PUSH_VCPF BIT(12)
+#define DP_DP1_PUSH_VCPF BIT(14)
#define REG_DP_CONFIGURATION_CTRL (0x00000008)
#define DP_CONFIGURATION_CTRL_SYNC_ASYNC_CLK (0x00000001)
@@ -368,6 +374,8 @@
#define REG_DP_DP0_RG (0x000004F8)
#define REG_DP_DP1_RG (0x000004FC)
+#define REG_DP_MSTLINK_STATE_CTRL (0x00000000)
+#define DP_MSTLINK_PUSH_VCPF BIT(12)
#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] 47+ messages in thread* [PATCH RESEND v5 10/25] drm/msm/dp: Always program MST_FIFO_CONSTANT_FILL for MST use cases
2026-06-29 14:14 [PATCH RESEND v5 00/25] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
` (8 preceding siblings ...)
2026-06-29 14:14 ` [PATCH RESEND v5 09/25] drm/msm/dp: Add support for sending VCPF packets in DP controller Yongxing Mou
@ 2026-06-29 14:14 ` Yongxing Mou
2026-06-29 14:14 ` [PATCH RESEND v5 11/25] drm/msm/dp: move link-level teardown from display_disable to display_unprepare Yongxing Mou
` (15 subsequent siblings)
25 siblings, 0 replies; 47+ messages in thread
From: Yongxing Mou @ 2026-06-29 14:14 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, 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. Ensure 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 c4f1a68b1210..80116e19fbbf 100644
--- a/drivers/gpu/drm/msm/dp/dp_ctrl.c
+++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c
@@ -677,6 +677,8 @@ static void msm_dp_ctrl_configure_source_params(struct msm_dp_ctrl_private *ctrl
msm_dp_ctrl_config_misc1_misc0(ctrl, panel);
msm_dp_panel_timing_cfg(panel, ctrl->msm_dp_ctrl.wide_bus_en);
+
+ msm_dp_panel_mst_async_fifo(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 cbbcc0dbf652..ba5ee2ad8924 100644
--- a/drivers/gpu/drm/msm/dp/dp_panel.c
+++ b/drivers/gpu/drm/msm/dp/dp_panel.c
@@ -741,6 +741,18 @@ int msm_dp_panel_timing_cfg(struct msm_dp_panel *msm_dp_panel, bool wide_bus_en)
return 0;
}
+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,
const struct drm_display_mode *adjusted_mode,
u32 bpp)
diff --git a/drivers/gpu/drm/msm/dp/dp_panel.h b/drivers/gpu/drm/msm/dp/dp_panel.h
index 3e78af9e430d..edc39ee5268e 100644
--- a/drivers/gpu/drm/msm/dp/dp_panel.h
+++ b/drivers/gpu/drm/msm/dp/dp_panel.h
@@ -73,6 +73,8 @@ void msm_dp_panel_clear_dsc_dto(struct msm_dp_panel *msm_dp_panel);
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] 47+ messages in thread* [PATCH RESEND v5 11/25] drm/msm/dp: move link-level teardown from display_disable to display_unprepare
2026-06-29 14:14 [PATCH RESEND v5 00/25] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
` (9 preceding siblings ...)
2026-06-29 14:14 ` [PATCH RESEND v5 10/25] drm/msm/dp: Always program MST_FIFO_CONSTANT_FILL for MST use cases Yongxing Mou
@ 2026-06-29 14:14 ` Yongxing Mou
2026-07-12 18:20 ` Dmitry Baryshkov
2026-06-29 14:14 ` [PATCH RESEND v5 12/25] drm/msm/dp: factor out _helper variants of bridge ops accepting a panel Yongxing Mou
` (14 subsequent siblings)
25 siblings, 1 reply; 47+ messages in thread
From: Yongxing Mou @ 2026-06-29 14:14 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, Abhinav Kumar
From: Abhinav Kumar <quic_abhinavk@quicinc.com>
msm_dp_display_disable() currently mixes stream-level shutdown
(disable VSC SDP, off pixel clk, clear power_on) with link-level
teardown (PSM config when sink_count==0, off_link, PHY re-init or
host PHY exit).
For DP MST the same link is shared across multiple streams, so
disabling one stream must not tear down the link. Move the
link-level steps into msm_dp_display_unprepare() so that
display_disable() handles only the per-stream sequence, mirroring
the split already present on the prepare path
(display_prepare_link vs display_set_mode / display_enable).
SST behaviour is unchanged: atomic_post_disable() still calls
display_disable() followed by display_unprepare() in the same
order, and the cached dp->panel used inside unprepare is the same
panel that was previously passed in.
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 ++++++++++++------------
1 file changed, 12 insertions(+), 12 deletions(-)
diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
index 1af56c84b82e..1680a67284a7 100644
--- a/drivers/gpu/drm/msm/dp/dp_display.c
+++ b/drivers/gpu/drm/msm/dp/dp_display.c
@@ -731,18 +731,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, dp->panel);
-
- 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);
@@ -1533,6 +1521,18 @@ static void msm_dp_display_unprepare(struct msm_dp_display_private *dp)
{
struct msm_dp *msm_dp_display = &dp->msm_dp_display;
+ /* 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, dp->panel);
+
+ 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);
+
pm_runtime_put_sync(&msm_dp_display->pdev->dev);
drm_dbg_dp(dp->drm_dev, "type=%d Done\n", msm_dp_display->connector_type);
--
2.43.0
^ permalink raw reply [flat|nested] 47+ messages in thread* Re: [PATCH RESEND v5 11/25] drm/msm/dp: move link-level teardown from display_disable to display_unprepare
2026-06-29 14:14 ` [PATCH RESEND v5 11/25] drm/msm/dp: move link-level teardown from display_disable to display_unprepare Yongxing Mou
@ 2026-07-12 18:20 ` Dmitry Baryshkov
0 siblings, 0 replies; 47+ messages in thread
From: Dmitry Baryshkov @ 2026-07-12 18: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 Mon, Jun 29, 2026 at 10:14:32PM +0800, Yongxing Mou wrote:
> From: Abhinav Kumar <quic_abhinavk@quicinc.com>
>
> msm_dp_display_disable() currently mixes stream-level shutdown
> (disable VSC SDP, off pixel clk, clear power_on) with link-level
> teardown (PSM config when sink_count==0, off_link, PHY re-init or
> host PHY exit).
>
> For DP MST the same link is shared across multiple streams, so
> disabling one stream must not tear down the link. Move the
> link-level steps into msm_dp_display_unprepare() so that
> display_disable() handles only the per-stream sequence, mirroring
> the split already present on the prepare path
> (display_prepare_link vs display_set_mode / display_enable).
>
> SST behaviour is unchanged: atomic_post_disable() still calls
> display_disable() followed by display_unprepare() in the same
> order, and the cached dp->panel used inside unprepare is the same
> panel that was previously passed in.
>
> 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 ++++++++++++------------
> 1 file changed, 12 insertions(+), 12 deletions(-)
>
> diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
> index 1af56c84b82e..1680a67284a7 100644
> --- a/drivers/gpu/drm/msm/dp/dp_display.c
> +++ b/drivers/gpu/drm/msm/dp/dp_display.c
> @@ -731,18 +731,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, dp->panel);
> -
> - 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;
The list is no longer powred on at this point. So, this is wrong.
>
> drm_dbg_dp(dp->drm_dev, "sink count: %d\n", dp->link->sink_count);
> @@ -1533,6 +1521,18 @@ static void msm_dp_display_unprepare(struct msm_dp_display_private *dp)
> {
> struct msm_dp *msm_dp_display = &dp->msm_dp_display;
>
> + /* 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, dp->panel);
> +
> + 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);
> +
> pm_runtime_put_sync(&msm_dp_display->pdev->dev);
>
> drm_dbg_dp(dp->drm_dev, "type=%d Done\n", msm_dp_display->connector_type);
>
> --
> 2.43.0
>
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 47+ messages in thread
* [PATCH RESEND v5 12/25] drm/msm/dp: factor out _helper variants of bridge ops accepting a panel
2026-06-29 14:14 [PATCH RESEND v5 00/25] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
` (10 preceding siblings ...)
2026-06-29 14:14 ` [PATCH RESEND v5 11/25] drm/msm/dp: move link-level teardown from display_disable to display_unprepare Yongxing Mou
@ 2026-06-29 14:14 ` Yongxing Mou
2026-07-12 18:43 ` Dmitry Baryshkov
2026-06-29 14:14 ` [PATCH RESEND v5 13/25] drm/msm/dp: replace power_on with active_stream_cnt for dp_display Yongxing Mou
` (13 subsequent siblings)
25 siblings, 1 reply; 47+ messages in thread
From: Yongxing Mou @ 2026-06-29 14:14 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, Abhinav Kumar
From: Abhinav Kumar <quic_abhinavk@quicinc.com>
The atomic bridge callbacks (set_mode / enable / disable /
post_disable) on dp_display currently hard-code dp->panel. For
DP MST every stream has its own msm_dp_panel that the MST
encoder owns, so the same enable/disable sequence needs to be
invokable against an arbitrary panel.
Introduce *_helper variants that take struct msm_dp_panel * and
reduce the existing atomic_* callbacks to thin wrappers that
pass dp->panel. No SST-path behaviour change.
Also drop the static qualifier from msm_dp_display_prepare_link()
and msm_dp_display_unprepare() and change them to take
struct msm_dp * so the upcoming MST encoder code can drive
link-level prepare/unprepare uniformly through the public API.
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 | 93 +++++++++++++++++++++++++++----------
drivers/gpu/drm/msm/dp/dp_display.h | 12 +++++
2 files changed, 80 insertions(+), 25 deletions(-)
diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
index 1680a67284a7..0e432f35cc51 100644
--- a/drivers/gpu/drm/msm/dp/dp_display.c
+++ b/drivers/gpu/drm/msm/dp/dp_display.c
@@ -628,12 +628,14 @@ static int msm_dp_display_set_mode(struct msm_dp *msm_dp_display,
return 0;
}
-static int msm_dp_display_prepare_link(struct msm_dp_display_private *dp)
+int msm_dp_display_prepare_link(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)
@@ -1457,69 +1459,101 @@ 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_commit *state)
+int msm_dp_display_set_mode_helper(struct msm_dp *msm_dp_display,
+ struct drm_atomic_commit *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_commit *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_link(dp);
+ rc = msm_dp_display_prepare_link(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);
+ msm_dp_display_set_stream_info(msm_dp_display, msm_dp_panel, 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_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_display_set_stream_info(msm_dp_display, dp->panel, 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_vcpf(dp->ctrl, msm_dp_panel);
+ msm_dp_ctrl_mst_stream_channel_slot_setup(dp->ctrl);
+ msm_dp_ctrl_mst_send_act(dp->ctrl, msm_dp_panel);
+}
+
void msm_dp_display_atomic_disable(struct msm_dp *dp)
{
struct msm_dp_display_private *msm_dp_display;
msm_dp_display = container_of(dp, struct msm_dp_display_private, msm_dp_display);
- msm_dp_ctrl_push_vcpf(msm_dp_display->ctrl, msm_dp_display->panel);
- msm_dp_ctrl_mst_stream_channel_slot_setup(msm_dp_display->ctrl);
- msm_dp_ctrl_mst_send_act(msm_dp_display->ctrl, msm_dp_display->panel);
+ msm_dp_display_disable_helper(dp, msm_dp_display->panel);
}
-static void msm_dp_display_unprepare(struct msm_dp_display_private *dp)
+void msm_dp_display_unprepare(struct msm_dp *msm_dp_display)
{
- struct msm_dp *msm_dp_display = &dp->msm_dp_display;
+ struct msm_dp_display_private *dp;
+
+ dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display);
/* dongle is still connected but sinks are disconnected */
if (dp->link->sink_count == 0)
@@ -1534,11 +1568,9 @@ static void msm_dp_display_unprepare(struct msm_dp_display_private *dp)
msm_dp_display_host_phy_exit(dp);
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_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;
@@ -1549,7 +1581,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 45e2cc2d6add..55ffa22bb233 100644
--- a/drivers/gpu/drm/msm/dp/dp_display.h
+++ b/drivers/gpu/drm/msm/dp/dp_display.h
@@ -45,5 +45,17 @@ enum drm_mode_status msm_dp_display_mode_valid(struct msm_dp *dp,
const struct drm_display_mode *mode);
int msm_dp_display_set_stream_info(struct msm_dp *msm_dp_display, struct msm_dp_panel *panel,
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_commit *state,
+ struct drm_encoder *drm_encoder,
+ struct msm_dp_panel *msm_dp_panel);
+int msm_dp_display_prepare_link(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] 47+ messages in thread* Re: [PATCH RESEND v5 12/25] drm/msm/dp: factor out _helper variants of bridge ops accepting a panel
2026-06-29 14:14 ` [PATCH RESEND v5 12/25] drm/msm/dp: factor out _helper variants of bridge ops accepting a panel Yongxing Mou
@ 2026-07-12 18:43 ` Dmitry Baryshkov
0 siblings, 0 replies; 47+ messages in thread
From: Dmitry Baryshkov @ 2026-07-12 18:43 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 29, 2026 at 10:14:33PM +0800, Yongxing Mou wrote:
> From: Abhinav Kumar <quic_abhinavk@quicinc.com>
>
> The atomic bridge callbacks (set_mode / enable / disable /
> post_disable) on dp_display currently hard-code dp->panel. For
> DP MST every stream has its own msm_dp_panel that the MST
> encoder owns, so the same enable/disable sequence needs to be
> invokable against an arbitrary panel.
>
> Introduce *_helper variants that take struct msm_dp_panel * and
> reduce the existing atomic_* callbacks to thin wrappers that
> pass dp->panel. No SST-path behaviour change.
>
> Also drop the static qualifier from msm_dp_display_prepare_link()
> and msm_dp_display_unprepare() and change them to take
> struct msm_dp * so the upcoming MST encoder code can drive
> link-level prepare/unprepare uniformly through the public API.
>
> 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 | 93 +++++++++++++++++++++++++++----------
> drivers/gpu/drm/msm/dp/dp_display.h | 12 +++++
> 2 files changed, 80 insertions(+), 25 deletions(-)
>
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 47+ messages in thread
* [PATCH RESEND v5 13/25] drm/msm/dp: replace power_on with active_stream_cnt for dp_display
2026-06-29 14:14 [PATCH RESEND v5 00/25] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
` (11 preceding siblings ...)
2026-06-29 14:14 ` [PATCH RESEND v5 12/25] drm/msm/dp: factor out _helper variants of bridge ops accepting a panel Yongxing Mou
@ 2026-06-29 14:14 ` Yongxing Mou
2026-07-12 18:51 ` Dmitry Baryshkov
2026-06-29 14:14 ` [PATCH RESEND v5 14/25] drm/msm/dp: Mark the SST bridge disconnected when mst is active Yongxing Mou
` (12 subsequent siblings)
25 siblings, 1 reply; 47+ messages in thread
From: Yongxing Mou @ 2026-06-29 14:14 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, 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 | 28 +++++++++++++++++-----------
drivers/gpu/drm/msm/dp/dp_display.h | 2 +-
3 files changed, 19 insertions(+), 13 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 0e432f35cc51..d0081ea9f5cd 100644
--- a/drivers/gpu/drm/msm/dp/dp_display.c
+++ b/drivers/gpu/drm/msm/dp/dp_display.c
@@ -650,7 +650,7 @@ int msm_dp_display_prepare_link(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;
}
@@ -670,14 +670,10 @@ static int msm_dp_display_enable(struct msm_dp_display_private *dp,
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, msm_dp_display->mst_active);
- if (!rc)
- msm_dp_display->power_on = true;
+
+ msm_dp_display->active_stream_cnt++;
return rc;
}
@@ -726,14 +722,14 @@ 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)
+ if (!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;
+ msm_dp_display->active_stream_cnt--;
drm_dbg_dp(dp->drm_dev, "sink count: %d\n", dp->link->sink_count);
return 0;
@@ -850,10 +846,10 @@ void msm_dp_snapshot(struct msm_disp_state *disp_state, struct msm_dp *dp)
* if we are reading registers we need the link clocks to be on
* however till DP cable is connected this will not happen as we
* do not know the resolution to power up with. Hence check the
- * power_on status before dumping DP registers to avoid crash due
+ * active_stream_cnt 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,
@@ -1535,6 +1531,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_vcpf(dp->ctrl, msm_dp_panel);
msm_dp_ctrl_mst_stream_channel_slot_setup(dp->ctrl);
msm_dp_ctrl_mst_send_act(dp->ctrl, msm_dp_panel);
@@ -1555,6 +1556,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 55ffa22bb233..75dc40261723 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] 47+ messages in thread* Re: [PATCH RESEND v5 13/25] drm/msm/dp: replace power_on with active_stream_cnt for dp_display
2026-06-29 14:14 ` [PATCH RESEND v5 13/25] drm/msm/dp: replace power_on with active_stream_cnt for dp_display Yongxing Mou
@ 2026-07-12 18:51 ` Dmitry Baryshkov
0 siblings, 0 replies; 47+ messages in thread
From: Dmitry Baryshkov @ 2026-07-12 18: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 Mon, Jun 29, 2026 at 10:14:34PM +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.
Last two phrases are redundat. Drop the "Introduce..." one.
>
> 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 | 28 +++++++++++++++++-----------
> drivers/gpu/drm/msm/dp/dp_display.h | 2 +-
> 3 files changed, 19 insertions(+), 13 deletions(-)
>
> @@ -670,14 +670,10 @@ static int msm_dp_display_enable(struct msm_dp_display_private *dp,
> 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, msm_dp_display->mst_active);
> - if (!rc)
> - msm_dp_display->power_on = true;
> +
> + msm_dp_display->active_stream_cnt++;
Current power_on prevents from calling msm_dp_ctrl_on_stream() several
times. How is it prevented after the change?
>
> return rc;
> }
> @@ -726,14 +722,14 @@ 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)
> + if (!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;
> + msm_dp_display->active_stream_cnt--;
>
> drm_dbg_dp(dp->drm_dev, "sink count: %d\n", dp->link->sink_count);
> return 0;
> @@ -850,10 +846,10 @@ void msm_dp_snapshot(struct msm_disp_state *disp_state, struct msm_dp *dp)
> * if we are reading registers we need the link clocks to be on
> * however till DP cable is connected this will not happen as we
> * do not know the resolution to power up with. Hence check the
> - * power_on status before dumping DP registers to avoid crash due
> + * active_stream_cnt 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,
> @@ -1535,6 +1531,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;
> + }
If you get here, there was at least one active stream, wasn't it?
> +
> msm_dp_ctrl_push_vcpf(dp->ctrl, msm_dp_panel);
> msm_dp_ctrl_mst_stream_channel_slot_setup(dp->ctrl);
> msm_dp_ctrl_mst_send_act(dp->ctrl, msm_dp_panel);
> @@ -1555,6 +1556,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;
Why?
General comment. You are not just replacing power_on with
active_stream_cnt. You are also introducing sequence changes, but it is
not covered in the cover letter. It's not clear, why those changes are
legitimage or why they are required.
> + }
> +
> /* 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 55ffa22bb233..75dc40261723 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] 47+ messages in thread
* [PATCH RESEND v5 14/25] drm/msm/dp: Mark the SST bridge disconnected when mst is active
2026-06-29 14:14 [PATCH RESEND v5 00/25] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
` (12 preceding siblings ...)
2026-06-29 14:14 ` [PATCH RESEND v5 13/25] drm/msm/dp: replace power_on with active_stream_cnt for dp_display Yongxing Mou
@ 2026-06-29 14:14 ` Yongxing Mou
2026-07-12 21:00 ` Dmitry Baryshkov
2026-07-12 21:03 ` Dmitry Baryshkov
2026-06-29 14:14 ` [PATCH RESEND v5 15/25] drm/msm/dp: add an API to initialize MST on sink side Yongxing Mou
` (11 subsequent siblings)
25 siblings, 2 replies; 47+ messages in thread
From: Yongxing Mou @ 2026-06-29 14:14 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, 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. 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 | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
index d0081ea9f5cd..5786e598a406 100644
--- a/drivers/gpu/drm/msm/dp/dp_display.c
+++ b/drivers/gpu/drm/msm/dp/dp_display.c
@@ -910,6 +910,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;
+
guard(mutex)(&priv->plugged_lock);
ret = pm_runtime_resume_and_get(&dp->pdev->dev);
if (ret) {
@@ -955,6 +958,10 @@ enum drm_connector_status msm_dp_bridge_detect(struct drm_bridge *bridge,
status = connector_status_disconnected;
}
+ /* skip for MST */
+ if (priv->max_stream > 1 && drm_dp_read_mst_cap(priv->aux, dpcd))
+ status = connector_status_disconnected;
+
end:
/*
* If we detected the DPRX, leave the controller on so that it doesn't
--
2.43.0
^ permalink raw reply [flat|nested] 47+ messages in thread* Re: [PATCH RESEND v5 14/25] drm/msm/dp: Mark the SST bridge disconnected when mst is active
2026-06-29 14:14 ` [PATCH RESEND v5 14/25] drm/msm/dp: Mark the SST bridge disconnected when mst is active Yongxing Mou
@ 2026-07-12 21:00 ` Dmitry Baryshkov
2026-07-12 21:03 ` Dmitry Baryshkov
1 sibling, 0 replies; 47+ messages in thread
From: Dmitry Baryshkov @ 2026-07-12 21: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 Mon, Jun 29, 2026 at 10:14:35PM +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. Skips detection for the
> SST bridge when MST is active.
Please mention that it matches the state of other platforms.
>
> 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 | 7 +++++++
> 1 file changed, 7 insertions(+)
>
> diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
> index d0081ea9f5cd..5786e598a406 100644
> --- a/drivers/gpu/drm/msm/dp/dp_display.c
> +++ b/drivers/gpu/drm/msm/dp/dp_display.c
> @@ -910,6 +910,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;
> +
> guard(mutex)(&priv->plugged_lock);
> ret = pm_runtime_resume_and_get(&dp->pdev->dev);
> if (ret) {
> @@ -955,6 +958,10 @@ enum drm_connector_status msm_dp_bridge_detect(struct drm_bridge *bridge,
> status = connector_status_disconnected;
> }
>
> + /* skip for MST */
> + if (priv->max_stream > 1 && drm_dp_read_mst_cap(priv->aux, dpcd))
> + status = connector_status_disconnected;
Hmm, should it only be marked as disconnected after we switch to the MST
mode? When is the switch happening?
> +
> end:
> /*
> * If we detected the DPRX, leave the controller on so that it doesn't
>
> --
> 2.43.0
>
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 47+ messages in thread* Re: [PATCH RESEND v5 14/25] drm/msm/dp: Mark the SST bridge disconnected when mst is active
2026-06-29 14:14 ` [PATCH RESEND v5 14/25] drm/msm/dp: Mark the SST bridge disconnected when mst is active Yongxing Mou
2026-07-12 21:00 ` Dmitry Baryshkov
@ 2026-07-12 21:03 ` Dmitry Baryshkov
1 sibling, 0 replies; 47+ messages in thread
From: Dmitry Baryshkov @ 2026-07-12 21:03 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 29, 2026 at 10:14:35PM +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. 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 | 7 +++++++
> 1 file changed, 7 insertions(+)
>
> diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
> index d0081ea9f5cd..5786e598a406 100644
> --- a/drivers/gpu/drm/msm/dp/dp_display.c
> +++ b/drivers/gpu/drm/msm/dp/dp_display.c
> @@ -910,6 +910,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;
> +
> guard(mutex)(&priv->plugged_lock);
> ret = pm_runtime_resume_and_get(&dp->pdev->dev);
> if (ret) {
> @@ -955,6 +958,10 @@ enum drm_connector_status msm_dp_bridge_detect(struct drm_bridge *bridge,
> status = connector_status_disconnected;
> }
>
> + /* skip for MST */
> + if (priv->max_stream > 1 && drm_dp_read_mst_cap(priv->aux, dpcd))
> + status = connector_status_disconnected;
Also, if this patch gets applied at this moment, it breaks DP support,
since the SST connector will report disconnected, but MST connectors are
not there yet.
> +
> end:
> /*
> * If we detected the DPRX, leave the controller on so that it doesn't
>
> --
> 2.43.0
>
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 47+ messages in thread
* [PATCH RESEND v5 15/25] drm/msm/dp: add an API to initialize MST on sink side
2026-06-29 14:14 [PATCH RESEND v5 00/25] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
` (13 preceding siblings ...)
2026-06-29 14:14 ` [PATCH RESEND v5 14/25] drm/msm/dp: Mark the SST bridge disconnected when mst is active Yongxing Mou
@ 2026-06-29 14:14 ` Yongxing Mou
2026-07-12 21:35 ` Dmitry Baryshkov
2026-06-29 14:14 ` [PATCH RESEND v5 16/25] drm/msm/dp: add msm_dp_display_get_panel() to initialize DP panel Yongxing Mou
` (10 subsequent siblings)
25 siblings, 1 reply; 47+ messages in thread
From: Yongxing Mou @ 2026-06-29 14:14 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, 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 | 48 +++++++++++++++++++++++++++++++------
1 file changed, 41 insertions(+), 7 deletions(-)
diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
index 5786e598a406..c3be656f10ee 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,31 @@ 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)
+{
+ 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;
+ }
+
+ 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 +314,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 +348,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] 47+ messages in thread* Re: [PATCH RESEND v5 15/25] drm/msm/dp: add an API to initialize MST on sink side
2026-06-29 14:14 ` [PATCH RESEND v5 15/25] drm/msm/dp: add an API to initialize MST on sink side Yongxing Mou
@ 2026-07-12 21:35 ` Dmitry Baryshkov
0 siblings, 0 replies; 47+ messages in thread
From: Dmitry Baryshkov @ 2026-07-12 21:35 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 29, 2026 at 10:14:36PM +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 | 48 +++++++++++++++++++++++++++++++------
> 1 file changed, 41 insertions(+), 7 deletions(-)
>
> diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
> index 5786e598a406..c3be656f10ee 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,31 @@ 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)
> +{
> + 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;
> + }
Is it not enough to write the new value? Please add a small comment,
why (reference DP standard in the commit message, please).
> +
> + 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 +314,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;
This also is going to break DP. If you are using dp->max_streams as a
determining factor, rearrange commits so that max_streams are parseed
and set only after the rest of the commits are in place (and the driver
is ready for DP MST).
> }
>
> msm_dp_link_process_request(dp->link);
> @@ -317,6 +348,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] 47+ messages in thread
* [PATCH RESEND v5 16/25] drm/msm/dp: add msm_dp_display_get_panel() to initialize DP panel
2026-06-29 14:14 [PATCH RESEND v5 00/25] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
` (14 preceding siblings ...)
2026-06-29 14:14 ` [PATCH RESEND v5 15/25] drm/msm/dp: add an API to initialize MST on sink side Yongxing Mou
@ 2026-06-29 14:14 ` Yongxing Mou
2026-07-12 21:56 ` Dmitry Baryshkov
2026-06-29 14:14 ` [PATCH RESEND v5 17/25] drm/msm/dp: add link_ready to manage link-level operations Yongxing Mou
` (9 subsequent siblings)
25 siblings, 1 reply; 47+ messages in thread
From: Yongxing Mou @ 2026-06-29 14:14 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, Abhinav Kumar
From: Abhinav Kumar <quic_abhinavk@quicinc.com>
Add an API msm_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 | 27 +++++++++++++++++++++++++++
drivers/gpu/drm/msm/dp/dp_display.h | 2 ++
2 files changed, 29 insertions(+)
diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
index c3be656f10ee..2a74302bcb7c 100644
--- a/drivers/gpu/drm/msm/dp/dp_display.c
+++ b/drivers/gpu/drm/msm/dp/dp_display.c
@@ -565,6 +565,33 @@ 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,
+ enum msm_dp_stream_id stream_id)
+{
+ 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);
+
+ if (stream_id >= DP_STREAM_MAX || stream_id >= dp->max_stream) {
+ DRM_ERROR("invalid stream_id %d\n", stream_id);
+ return NULL;
+ }
+
+ 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[stream_id]);
+
+ if (IS_ERR(dp_panel)) {
+ DRM_ERROR("failed to initialize panel\n");
+ return NULL;
+ }
+
+ dp_panel->stream_id = stream_id;
+
+ 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 75dc40261723..676213a48089 100644
--- a/drivers/gpu/drm/msm/dp/dp_display.h
+++ b/drivers/gpu/drm/msm/dp/dp_display.h
@@ -58,4 +58,6 @@ int msm_dp_display_set_mode_helper(struct msm_dp *msm_dp_display,
int msm_dp_display_prepare_link(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,
+ enum msm_dp_stream_id stream_id);
#endif /* _DP_DISPLAY_H_ */
--
2.43.0
^ permalink raw reply [flat|nested] 47+ messages in thread* Re: [PATCH RESEND v5 16/25] drm/msm/dp: add msm_dp_display_get_panel() to initialize DP panel
2026-06-29 14:14 ` [PATCH RESEND v5 16/25] drm/msm/dp: add msm_dp_display_get_panel() to initialize DP panel Yongxing Mou
@ 2026-07-12 21:56 ` Dmitry Baryshkov
0 siblings, 0 replies; 47+ messages in thread
From: Dmitry Baryshkov @ 2026-07-12 21:56 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 29, 2026 at 10:14:37PM +0800, Yongxing Mou wrote:
> From: Abhinav Kumar <quic_abhinavk@quicinc.com>
>
> Add an API msm_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 | 27 +++++++++++++++++++++++++++
> drivers/gpu/drm/msm/dp/dp_display.h | 2 ++
> 2 files changed, 29 insertions(+)
>
> diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
> index c3be656f10ee..2a74302bcb7c 100644
> --- a/drivers/gpu/drm/msm/dp/dp_display.c
> +++ b/drivers/gpu/drm/msm/dp/dp_display.c
> @@ -565,6 +565,33 @@ 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,
> + enum msm_dp_stream_id stream_id)
> +{
> + 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);
> +
> + if (stream_id >= DP_STREAM_MAX || stream_id >= dp->max_stream) {
We know that max_stream is <= DP_STREAM_MAX
> + DRM_ERROR("invalid stream_id %d\n", stream_id);
> + return NULL;
> + }
> +
> + 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[stream_id]);
> +
> + if (IS_ERR(dp_panel)) {
> + DRM_ERROR("failed to initialize panel\n");
> + return NULL;
> + }
> +
> + dp_panel->stream_id = stream_id;
> +
> + 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 75dc40261723..676213a48089 100644
> --- a/drivers/gpu/drm/msm/dp/dp_display.h
> +++ b/drivers/gpu/drm/msm/dp/dp_display.h
> @@ -58,4 +58,6 @@ int msm_dp_display_set_mode_helper(struct msm_dp *msm_dp_display,
> int msm_dp_display_prepare_link(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,
> + enum msm_dp_stream_id stream_id);
> #endif /* _DP_DISPLAY_H_ */
>
> --
> 2.43.0
>
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 47+ messages in thread
* [PATCH RESEND v5 17/25] drm/msm/dp: add link_ready to manage link-level operations
2026-06-29 14:14 [PATCH RESEND v5 00/25] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
` (15 preceding siblings ...)
2026-06-29 14:14 ` [PATCH RESEND v5 16/25] drm/msm/dp: add msm_dp_display_get_panel() to initialize DP panel Yongxing Mou
@ 2026-06-29 14:14 ` Yongxing Mou
2026-07-12 23:46 ` Dmitry Baryshkov
2026-06-29 14:14 ` [PATCH RESEND v5 18/25] drm/msm/dpu: initialize encoders per stream for DP MST Yongxing Mou
` (8 subsequent siblings)
25 siblings, 1 reply; 47+ messages in thread
From: Yongxing Mou @ 2026-06-29 14:14 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, Abhinav Kumar
In MST mode, multiple streams share the same DP link. Track a link_ready
state so msm_dp_display_prepare_link() 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 | 42 ++++++++++++++++++++++++-------------
drivers/gpu/drm/msm/dp/dp_display.h | 1 +
2 files changed, 29 insertions(+), 14 deletions(-)
diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
index 2a74302bcb7c..d56ee10ee065 100644
--- a/drivers/gpu/drm/msm/dp/dp_display.c
+++ b/drivers/gpu/drm/msm/dp/dp_display.c
@@ -702,6 +702,9 @@ int msm_dp_display_prepare_link(struct msm_dp *msm_dp_display)
if (msm_dp_display->is_edp)
msm_dp_hpd_plug_handle(dp);
+ if (msm_dp_display->link_ready)
+ return 0;
+
rc = pm_runtime_resume_and_get(&msm_dp_display->pdev->dev);
if (rc) {
DRM_ERROR("failed to pm_runtime_resume\n");
@@ -714,14 +717,18 @@ int msm_dp_display_prepare_link(struct msm_dp *msm_dp_display)
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, dp->panel);
+ 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, dp->panel);
- if (rc)
- DRM_ERROR("Failed link training (rc=%d)\n", rc);
- // TODO: schedule drm_connector_set_link_status_property()
+ rc = msm_dp_ctrl_prepare_stream_on(dp->ctrl, dp->panel, force_link_train);
+ if (!rc)
+ msm_dp_display->link_ready = true;
- return msm_dp_ctrl_prepare_stream_on(dp->ctrl, dp->panel, force_link_train);
+ return rc;
}
static int msm_dp_display_enable(struct msm_dp_display_private *dp,
@@ -1566,16 +1573,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);
- msm_dp_display_set_stream_info(msm_dp_display, msm_dp_panel, 0, 0, 0);
-
- 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->link_ready) {
+ 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);
@@ -1624,6 +1631,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->link_ready) {
+ 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;
@@ -1642,6 +1654,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->link_ready = 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 676213a48089..0464f8941e8d 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 link_ready;
struct msm_dp_audio *msm_dp_audio;
bool psr_supported;
--
2.43.0
^ permalink raw reply [flat|nested] 47+ messages in thread* Re: [PATCH RESEND v5 17/25] drm/msm/dp: add link_ready to manage link-level operations
2026-06-29 14:14 ` [PATCH RESEND v5 17/25] drm/msm/dp: add link_ready to manage link-level operations Yongxing Mou
@ 2026-07-12 23:46 ` Dmitry Baryshkov
0 siblings, 0 replies; 47+ messages in thread
From: Dmitry Baryshkov @ 2026-07-12 23:46 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 29, 2026 at 10:14:38PM +0800, Yongxing Mou wrote:
> In MST mode, multiple streams share the same DP link. Track a link_ready
> state so msm_dp_display_prepare_link() 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 | 42 ++++++++++++++++++++++++-------------
> drivers/gpu/drm/msm/dp/dp_display.h | 1 +
> 2 files changed, 29 insertions(+), 14 deletions(-)
>
> diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
> index 2a74302bcb7c..d56ee10ee065 100644
> --- a/drivers/gpu/drm/msm/dp/dp_display.c
> +++ b/drivers/gpu/drm/msm/dp/dp_display.c
> @@ -702,6 +702,9 @@ int msm_dp_display_prepare_link(struct msm_dp *msm_dp_display)
> if (msm_dp_display->is_edp)
> msm_dp_hpd_plug_handle(dp);
>
> + if (msm_dp_display->link_ready)
> + return 0;
> +
What kind of locking is protecting this variable? What prevents it from
being accessed concurrently?
> rc = pm_runtime_resume_and_get(&msm_dp_display->pdev->dev);
> if (rc) {
> DRM_ERROR("failed to pm_runtime_resume\n");
> @@ -714,14 +717,18 @@ int msm_dp_display_prepare_link(struct msm_dp *msm_dp_display)
> 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, dp->panel);
> + 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, dp->panel);
> - if (rc)
> - DRM_ERROR("Failed link training (rc=%d)\n", rc);
> - // TODO: schedule drm_connector_set_link_status_property()
Why is it being moved now rather than in the patch where
active_stream_cnt was introduced?
> + rc = msm_dp_ctrl_prepare_stream_on(dp->ctrl, dp->panel, force_link_train);
> + if (!rc)
> + msm_dp_display->link_ready = true;
>
> - return msm_dp_ctrl_prepare_stream_on(dp->ctrl, dp->panel, force_link_train);
> + return rc;
> }
>
> static int msm_dp_display_enable(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 676213a48089..0464f8941e8d 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 link_ready;
Please group the status variables together.
>
> struct msm_dp_audio *msm_dp_audio;
> bool psr_supported;
>
> --
> 2.43.0
>
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 47+ messages in thread
* [PATCH RESEND v5 18/25] drm/msm/dpu: initialize encoders per stream for DP MST
2026-06-29 14:14 [PATCH RESEND v5 00/25] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
` (16 preceding siblings ...)
2026-06-29 14:14 ` [PATCH RESEND v5 17/25] drm/msm/dp: add link_ready to manage link-level operations Yongxing Mou
@ 2026-06-29 14:14 ` Yongxing Mou
2026-07-12 23:55 ` Dmitry Baryshkov
2026-06-29 14:14 ` [PATCH RESEND v5 19/25] drm/msm/dp: initialize dp_mst module for each DP MST controller Yongxing Mou
` (7 subsequent siblings)
25 siblings, 1 reply; 47+ messages in thread
From: Yongxing Mou @ 2026-06-29 14:14 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, Dmitry Baryshkov
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>
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@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 da3556eb6ecc..7a00c4094d5c 100644
--- a/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c
+++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c
@@ -655,7 +655,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])
@@ -678,6 +678,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 c3fb3205f683..5fee0b291059 100644
--- a/drivers/gpu/drm/msm/msm_drv.h
+++ b/drivers/gpu/drm/msm/msm_drv.h
@@ -355,7 +355,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)
{
@@ -372,6 +372,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] 47+ messages in thread* Re: [PATCH RESEND v5 18/25] drm/msm/dpu: initialize encoders per stream for DP MST
2026-06-29 14:14 ` [PATCH RESEND v5 18/25] drm/msm/dpu: initialize encoders per stream for DP MST Yongxing Mou
@ 2026-07-12 23:55 ` Dmitry Baryshkov
0 siblings, 0 replies; 47+ messages in thread
From: Dmitry Baryshkov @ 2026-07-12 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
On Mon, Jun 29, 2026 at 10:14:39PM +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>
> Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@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 da3556eb6ecc..7a00c4094d5c 100644
> --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c
> +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c
> @@ -655,7 +655,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])
> @@ -678,6 +678,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++) {
for (int stream_id = 0;
stream_cnt > 1 && stream_id < stream_cnt;
stream_id ++) {
// foo
}
> + 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 c3fb3205f683..5fee0b291059 100644
> --- a/drivers/gpu/drm/msm/msm_drv.h
> +++ b/drivers/gpu/drm/msm/msm_drv.h
> @@ -355,7 +355,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);
This should be a part of the patch, introducing the function.
> #else
> static inline int __init msm_dp_register(void)
> {
> @@ -372,6 +372,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)
And this one too.
> +{
> + 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] 47+ messages in thread
* [PATCH RESEND v5 19/25] drm/msm/dp: initialize dp_mst module for each DP MST controller
2026-06-29 14:14 [PATCH RESEND v5 00/25] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
` (17 preceding siblings ...)
2026-06-29 14:14 ` [PATCH RESEND v5 18/25] drm/msm/dpu: initialize encoders per stream for DP MST Yongxing Mou
@ 2026-06-29 14:14 ` Yongxing Mou
2026-06-29 14:14 ` [PATCH RESEND v5 20/25] drm/msm/dpu: expose dpu_encoder ops for DP MST reuse Yongxing Mou
` (6 subsequent siblings)
25 siblings, 0 replies; 47+ messages in thread
From: Yongxing Mou @ 2026-06-29 14:14 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, 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 | 60 +++++++++++++++++++++++++++++++++
drivers/gpu/drm/msm/dp/dp_mst_drm.h | 13 +++++++
drivers/gpu/drm/msm/msm_drv.h | 6 ++++
7 files changed, 107 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
index ba45e99be05b..d510be1c173f 100644
--- a/drivers/gpu/drm/msm/Makefile
+++ b/drivers/gpu/drm/msm/Makefile
@@ -145,7 +145,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 7a00c4094d5c..91d33b432427 100644
--- a/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c
+++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c
@@ -682,6 +682,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 d56ee10ee065..fc9c1e3e57ab 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);
@@ -351,6 +352,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:
@@ -522,6 +526,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);
@@ -1530,6 +1539,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_commit *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 0464f8941e8d..a185819ec57e 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 link_ready;
+ 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..78b8dffe111b
--- /dev/null
+++ b/drivers/gpu/drm/msm/dp/dp_mst_drm.c
@@ -0,0 +1,60 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2025 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#include <drm/drm_edid.h>
+#include <drm/display/drm_dp_mst_helper.h>
+
+#include "dp_mst_drm.h"
+#include "dp_panel.h"
+
+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;
+};
+
+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_err(dp_display->drm_dev,
+ "[MST] failed to set topology mgr state to %d rc:%d\n", state, rc);
+ }
+
+ drm_dbg_kms(dp_display->drm_dev, "[MST] 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;
+ struct msm_dp_mst *mst;
+ int ret;
+
+ mst = devm_kzalloc(dev->dev, sizeof(*mst), GFP_KERNEL);
+ if (!mst)
+ return -ENOMEM;
+
+ mst->msm_dp = dp_display;
+ mst->max_streams = max_streams;
+ mst->dp_aux = drm_aux;
+
+ ret = drm_dp_mst_topology_mgr_init(&mst->mst_mgr, dev,
+ drm_aux,
+ 16,
+ max_streams,
+ dp_display->connector->base.id);
+ if (ret) {
+ drm_err(dev, "[MST] topology manager init failed\n");
+ return ret;
+ }
+
+ dp_display->msm_dp_mst = mst;
+ return 0;
+}
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 5fee0b291059..963303079220 100644
--- a/drivers/gpu/drm/msm/msm_drv.h
+++ b/drivers/gpu/drm/msm/msm_drv.h
@@ -356,6 +356,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)
{
@@ -377,6 +378,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] 47+ messages in thread* [PATCH RESEND v5 20/25] drm/msm/dpu: expose dpu_encoder ops for DP MST reuse
2026-06-29 14:14 [PATCH RESEND v5 00/25] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
` (18 preceding siblings ...)
2026-06-29 14:14 ` [PATCH RESEND v5 19/25] drm/msm/dp: initialize dp_mst module for each DP MST controller Yongxing Mou
@ 2026-06-29 14:14 ` Yongxing Mou
2026-07-13 0:01 ` Dmitry Baryshkov
2026-06-29 14:14 ` [PATCH RESEND v5 21/25] drm/msm/dpu: use msm_dp_get_mst_intf_id() to get the intf id Yongxing Mou
` (5 subsequent siblings)
25 siblings, 1 reply; 47+ messages in thread
From: Yongxing Mou @ 2026-06-29 14:14 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, Abhinav Kumar
Export dpu_encoder_atomic_mode_set, dpu_encoder_phys_enable and
dpu_encoder_phys_disable so MST encoder helper funcs can reuse them.
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 | 6 ++++++
2 files changed, 16 insertions(+), 10 deletions(-)
diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c
index 778e231d4967..1c74ff6f0dbd 100644
--- a/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c
+++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c
@@ -1149,9 +1149,9 @@ void dpu_encoder_cleanup_wb_job(struct drm_encoder *drm_enc,
}
}
-static void dpu_encoder_virt_atomic_mode_set(struct drm_encoder *drm_enc,
- struct drm_crtc_state *crtc_state,
- struct drm_connector_state *conn_state)
+void dpu_encoder_atomic_mode_set(struct drm_encoder *drm_enc,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state)
{
struct dpu_encoder_virt *dpu_enc;
struct msm_drm_private *priv;
@@ -1334,8 +1334,8 @@ void dpu_encoder_virt_runtime_resume(struct drm_encoder *drm_enc)
mutex_unlock(&dpu_enc->enc_lock);
}
-static void dpu_encoder_virt_atomic_enable(struct drm_encoder *drm_enc,
- struct drm_atomic_commit *state)
+void dpu_encoder_phys_enable(struct drm_encoder *drm_enc,
+ struct drm_atomic_commit *state)
{
struct dpu_encoder_virt *dpu_enc = NULL;
int ret = 0;
@@ -1381,8 +1381,8 @@ static void dpu_encoder_virt_atomic_enable(struct drm_encoder *drm_enc,
mutex_unlock(&dpu_enc->enc_lock);
}
-static void dpu_encoder_virt_atomic_disable(struct drm_encoder *drm_enc,
- struct drm_atomic_commit *state)
+void dpu_encoder_phys_disable(struct drm_encoder *drm_enc,
+ struct drm_atomic_commit *state)
{
struct dpu_encoder_virt *dpu_enc = NULL;
struct drm_crtc *crtc;
@@ -2739,9 +2739,9 @@ static void dpu_encoder_frame_done_timeout(struct timer_list *t)
}
static const struct drm_encoder_helper_funcs dpu_encoder_helper_funcs = {
- .atomic_mode_set = dpu_encoder_virt_atomic_mode_set,
- .atomic_disable = dpu_encoder_virt_atomic_disable,
- .atomic_enable = dpu_encoder_virt_atomic_enable,
+ .atomic_mode_set = dpu_encoder_atomic_mode_set,
+ .atomic_disable = dpu_encoder_phys_disable,
+ .atomic_enable = dpu_encoder_phys_enable,
};
static const struct drm_encoder_funcs dpu_encoder_funcs = {
diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.h b/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.h
index 4942097e7613..25ade3dbbeda 100644
--- a/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.h
+++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.h
@@ -98,4 +98,10 @@ void dpu_encoder_cleanup_wb_job(struct drm_encoder *drm_enc,
bool dpu_encoder_is_valid_for_commit(struct drm_encoder *drm_enc);
void dpu_encoder_start_frame_done_timer(struct drm_encoder *drm_enc);
+
+void dpu_encoder_phys_enable(struct drm_encoder *enc, struct drm_atomic_commit *state);
+void dpu_encoder_phys_disable(struct drm_encoder *enc, struct drm_atomic_commit *state);
+void dpu_encoder_atomic_mode_set(struct drm_encoder *enc,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state);
#endif /* __DPU_ENCODER_H__ */
--
2.43.0
^ permalink raw reply [flat|nested] 47+ messages in thread* Re: [PATCH RESEND v5 20/25] drm/msm/dpu: expose dpu_encoder ops for DP MST reuse
2026-06-29 14:14 ` [PATCH RESEND v5 20/25] drm/msm/dpu: expose dpu_encoder ops for DP MST reuse Yongxing Mou
@ 2026-07-13 0:01 ` Dmitry Baryshkov
0 siblings, 0 replies; 47+ messages in thread
From: Dmitry Baryshkov @ 2026-07-13 0:01 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 29, 2026 at 10:14:41PM +0800, Yongxing Mou wrote:
> Export dpu_encoder_atomic_mode_set, dpu_encoder_phys_enable and
> dpu_encoder_phys_disable so MST encoder helper funcs can reuse them.
>
> 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 | 6 ++++++
> 2 files changed, 16 insertions(+), 10 deletions(-)
>
> diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c
> index 778e231d4967..1c74ff6f0dbd 100644
> --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c
> +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c
> @@ -1149,9 +1149,9 @@ void dpu_encoder_cleanup_wb_job(struct drm_encoder *drm_enc,
> }
> }
>
> -static void dpu_encoder_virt_atomic_mode_set(struct drm_encoder *drm_enc,
> - struct drm_crtc_state *crtc_state,
> - struct drm_connector_state *conn_state)
> +void dpu_encoder_atomic_mode_set(struct drm_encoder *drm_enc,
> + struct drm_crtc_state *crtc_state,
> + struct drm_connector_state *conn_state)
The commit message says nothing about renaming the functions. Why are
they being renamed?
> {
> struct dpu_encoder_virt *dpu_enc;
> struct msm_drm_private *priv;
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 47+ messages in thread
* [PATCH RESEND v5 21/25] drm/msm/dpu: use msm_dp_get_mst_intf_id() to get the intf id
2026-06-29 14:14 [PATCH RESEND v5 00/25] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
` (19 preceding siblings ...)
2026-06-29 14:14 ` [PATCH RESEND v5 20/25] drm/msm/dpu: expose dpu_encoder ops for DP MST reuse Yongxing Mou
@ 2026-06-29 14:14 ` Yongxing Mou
2026-07-13 0:09 ` Dmitry Baryshkov
2026-06-29 14:14 ` [PATCH RESEND v5 22/25] drm/msm/dp: wire MST helpers into atomic check and commit paths Yongxing Mou
` (4 subsequent siblings)
25 siblings, 1 reply; 47+ messages in thread
From: Yongxing Mou @ 2026-06-29 14:14 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, Abhinav Kumar
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 1c74ff6f0dbd..3adfaeaab71d 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 @@ void dpu_encoder_phys_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 25ade3dbbeda..861d69afbd76 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 91d33b432427..b32ecd5b0777 100644
--- a/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c
+++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c
@@ -614,6 +614,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) {
@@ -689,6 +690,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");
@@ -716,6 +718,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)) {
@@ -748,6 +751,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] 47+ messages in thread* Re: [PATCH RESEND v5 21/25] drm/msm/dpu: use msm_dp_get_mst_intf_id() to get the intf id
2026-06-29 14:14 ` [PATCH RESEND v5 21/25] drm/msm/dpu: use msm_dp_get_mst_intf_id() to get the intf id Yongxing Mou
@ 2026-07-13 0:09 ` Dmitry Baryshkov
0 siblings, 0 replies; 47+ messages in thread
From: Dmitry Baryshkov @ 2026-07-13 0:09 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 29, 2026 at 10:14:42PM +0800, Yongxing Mou wrote:
> 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(-)
This really should have been a part (or done before) the patch adding
DPMST encoders.
>
> diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c
> index 1c74ff6f0dbd..3adfaeaab71d 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 @@ void dpu_encoder_phys_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)
As you are now passing whole msm_display_info, controller_id is
available as disp_info->h_tile_instance[i]. Pass index to this function
instead of passing controller_id.
Ideally, split this patch into two: one changing the function to get
disp_info, another one adding stream_id to the struct.
> {
> - 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)
Why did you change the order of the args?
> + if (cnt++ == stream_id)
Squash into the condition.
> + 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 25ade3dbbeda..861d69afbd76 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 91d33b432427..b32ecd5b0777 100644
> --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c
> +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c
> @@ -614,6 +614,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;
It is memset to 0. Drop this (here and below).
>
> rc = dpu_kms_dsi_set_te_source(&info, priv->kms->dsi[i]);
> if (rc) {
> @@ -689,6 +690,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");
> @@ -716,6 +718,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)) {
> @@ -748,6 +751,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
>
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 47+ messages in thread
* [PATCH RESEND v5 22/25] drm/msm/dp: wire MST helpers into atomic check and commit paths
2026-06-29 14:14 [PATCH RESEND v5 00/25] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
` (20 preceding siblings ...)
2026-06-29 14:14 ` [PATCH RESEND v5 21/25] drm/msm/dpu: use msm_dp_get_mst_intf_id() to get the intf id Yongxing Mou
@ 2026-06-29 14:14 ` Yongxing Mou
2026-06-29 14:14 ` [PATCH RESEND v5 23/25] drm/msm/dp: add dp_mst_drm to manage DP MST encoder operations Yongxing Mou
` (3 subsequent siblings)
25 siblings, 0 replies; 47+ messages in thread
From: Yongxing Mou @ 2026-06-29 14:14 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, 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 | 14 +++++++++++++-
drivers/gpu/drm/msm/msm_drv.h | 1 +
drivers/gpu/drm/msm/msm_kms.c | 1 +
3 files changed, 15 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/msm/msm_atomic.c b/drivers/gpu/drm/msm/msm_atomic.c
index a8babf1dbe0d..e70e5088cfe5 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_commit *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_commit *state)
@@ -221,6 +226,8 @@ void msm_atomic_commit_tail(struct drm_atomic_commit *state)
trace_msm_atomic_commit_tail_start(async, crtc_mask);
+ drm_dp_mst_atomic_wait_for_dependencies(state);
+
kms->funcs->enable_commit(kms);
/*
@@ -322,3 +329,8 @@ void msm_atomic_commit_tail(struct drm_atomic_commit *state)
trace_msm_atomic_commit_tail_finish(async, crtc_mask);
}
+
+int msm_atomic_commit_setup(struct drm_atomic_commit *state)
+{
+ return drm_dp_mst_atomic_setup_commit(state);
+}
diff --git a/drivers/gpu/drm/msm/msm_drv.h b/drivers/gpu/drm/msm/msm_drv.h
index 963303079220..f71200a790f3 100644
--- a/drivers/gpu/drm/msm/msm_drv.h
+++ b/drivers/gpu/drm/msm/msm_drv.h
@@ -213,6 +213,7 @@ int msm_atomic_init_pending_timer(struct msm_pending_timer *timer,
struct msm_kms *kms, int crtc_idx);
void msm_atomic_destroy_pending_timer(struct msm_pending_timer *timer);
void msm_atomic_commit_tail(struct drm_atomic_commit *state);
+int msm_atomic_commit_setup(struct drm_atomic_commit *state);
int msm_atomic_check(struct drm_device *dev, struct drm_atomic_commit *state);
struct drm_atomic_commit *msm_atomic_state_alloc(struct drm_device *dev);
diff --git a/drivers/gpu/drm/msm/msm_kms.c b/drivers/gpu/drm/msm/msm_kms.c
index e5d0ea629448..33ab30300e16 100644
--- a/drivers/gpu/drm/msm/msm_kms.c
+++ b/drivers/gpu/drm/msm/msm_kms.c
@@ -29,6 +29,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 = msm_atomic_commit_setup,
};
static irqreturn_t msm_irq(int irq, void *arg)
--
2.43.0
^ permalink raw reply [flat|nested] 47+ messages in thread* [PATCH RESEND v5 23/25] drm/msm/dp: add dp_mst_drm to manage DP MST encoder operations
2026-06-29 14:14 [PATCH RESEND v5 00/25] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
` (21 preceding siblings ...)
2026-06-29 14:14 ` [PATCH RESEND v5 22/25] drm/msm/dp: wire MST helpers into atomic check and commit paths Yongxing Mou
@ 2026-06-29 14:14 ` Yongxing Mou
2026-07-13 0:40 ` Dmitry Baryshkov
2026-06-29 14:14 ` [PATCH RESEND v5 24/25] drm/msm/dp: add connector abstraction for DP MST Yongxing Mou
` (2 subsequent siblings)
25 siblings, 1 reply; 47+ messages in thread
From: Yongxing Mou @ 2026-06-29 14:14 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, Abhinav Kumar
drm/msm/dp: introduce dp_mst_drm for MST stream management
Add a dp_mst_drm layer to manage DP MST streams with a clear ownership
model between encoder, panel and connector.
Each MST stream is represented by a dedicated drm_encoder. At modeset
initialization time, one (encoder, dp_panel) pair is created per
stream_id and remains fixed for the lifetime of the driver. The
dp_panel thus carries a stable stream context, including stream_id
and pixel mapping.
MST connectors are created and destroyed dynamically on hotplug and
are attached to a dp_panel through atomic routing. During an atomic
commit, connectors are associated with encoders via
atomic_best_encoder(), forming a temporary binding for the duration
of the commit.
Encoder helper callbacks drive the MST stream lifecycle, including
timeslot allocation, link enable/disable and payload programming.
A per-MST-instance lock serializes operations on shared link state
across multiple streams.
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_display.c | 9 ++
drivers/gpu/drm/msm/dp/dp_display.h | 2 +
drivers/gpu/drm/msm/dp/dp_mst_drm.c | 245 ++++++++++++++++++++++++++++++++
drivers/gpu/drm/msm/msm_drv.h | 7 +
5 files changed, 269 insertions(+)
diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c
index b32ecd5b0777..ac5dc844fead 100644
--- a/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c
+++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c
@@ -696,6 +696,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 attach_encoder failed, rc = %d\n", rc);
+ return rc;
+ }
}
}
}
diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
index fc9c1e3e57ab..6eac390af2e0 100644
--- a/drivers/gpu/drm/msm/dp/dp_display.c
+++ b/drivers/gpu/drm/msm/dp/dp_display.c
@@ -601,6 +601,15 @@ struct msm_dp_panel *msm_dp_display_get_panel(struct msm_dp *msm_dp_display,
return dp_panel;
}
+void msm_dp_display_set_link_info(struct msm_dp *msm_dp_display,
+ struct msm_dp_link_info *dst)
+{
+ struct msm_dp_display_private *dp =
+ container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display);
+
+ memcpy(dst, &dp->panel->link_info, sizeof(*dst));
+}
+
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 a185819ec57e..fb6bdd372b52 100644
--- a/drivers/gpu/drm/msm/dp/dp_display.h
+++ b/drivers/gpu/drm/msm/dp/dp_display.h
@@ -63,4 +63,6 @@ void msm_dp_display_unprepare(struct msm_dp *dp);
struct msm_dp_panel *msm_dp_display_get_panel(struct msm_dp *msm_dp_display,
enum msm_dp_stream_id stream_id);
+void msm_dp_display_set_link_info(struct msm_dp *msm_dp_display,
+ struct msm_dp_link_info *dst);
#endif /* _DP_DISPLAY_H_ */
diff --git a/drivers/gpu/drm/msm/dp/dp_mst_drm.c b/drivers/gpu/drm/msm/dp/dp_mst_drm.c
index 78b8dffe111b..6a77fdef85e9 100644
--- a/drivers/gpu/drm/msm/dp/dp_mst_drm.c
+++ b/drivers/gpu/drm/msm/dp/dp_mst_drm.c
@@ -4,18 +4,259 @@
*/
#include <drm/drm_edid.h>
+#include <drm/drm_fixed.h>
+#include <drm/drm_atomic_helper.h>
#include <drm/display/drm_dp_mst_helper.h>
#include "dp_mst_drm.h"
#include "dp_panel.h"
+#include "dpu_encoder.h"
+
+#define to_dp_mst_connector(x) \
+ container_of((x), struct msm_dp_mst_connector, connector)
+
+struct msm_dp_mst_encoder {
+ struct drm_encoder *enc;
+ int stream_id;
+ struct msm_dp_panel *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_mst {
struct drm_dp_mst_topology_mgr mst_mgr;
+ struct msm_dp_mst_encoder mst_encoders[DP_STREAM_MAX];
struct msm_dp *msm_dp;
struct drm_dp_aux *dp_aux;
u32 max_streams;
+ struct mutex mst_lock;
+ struct msm_dp_link_info link_info;
};
+static struct msm_dp_panel *msm_dp_mst_panel_from_encoder(struct msm_dp_mst *mst,
+ struct drm_encoder *enc)
+{
+ int i;
+
+ for (i = 0; i < mst->max_streams; i++) {
+ if (mst->mst_encoders[i].enc == enc)
+ return mst->mst_encoders[i].dp_panel;
+ }
+ return NULL;
+}
+
+static void msm_dp_mst_update_timeslots(struct msm_dp_mst *mst,
+ struct msm_dp_panel *panel,
+ struct drm_dp_mst_atomic_payload *payload)
+{
+ if (payload->vc_start_slot < 0)
+ msm_dp_display_set_stream_info(mst->msm_dp, panel, 1, 0, 0);
+ else
+ msm_dp_display_set_stream_info(mst->msm_dp, panel,
+ payload->vc_start_slot,
+ payload->time_slots, payload->pbn);
+
+ drm_dbg_kms(mst->msm_dp->drm_dev,
+ "[MST] stream:%u timeslots vc_start:%d slots:%d pbn:%d\n",
+ panel->stream_id, payload->vc_start_slot,
+ payload->time_slots, payload->pbn);
+}
+
+static void msm_dp_mst_stream_enable(struct drm_encoder *encoder,
+ struct drm_atomic_commit *state)
+{
+ struct drm_connector *connector =
+ drm_atomic_get_new_connector_for_encoder(state, encoder);
+ 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 msm_dp_panel *panel = msm_dp_mst_panel_from_encoder(mst, encoder);
+ struct drm_dp_mst_port *port = mst_conn->mst_port;
+ struct drm_dp_mst_topology_state *mst_state =
+ drm_atomic_get_new_mst_topology_state(state, &mst->mst_mgr);
+ struct drm_dp_mst_atomic_payload *payload =
+ drm_atomic_get_mst_payload_state(mst_state, port);
+ int rc;
+
+ panel->connector = connector;
+
+ guard(mutex)(&mst->mst_lock);
+
+ rc = msm_dp_display_set_mode_helper(dp_display, state, encoder, panel);
+ if (rc) {
+ drm_err(dp_display->drm_dev,
+ "[MST] stream:%u set_mode failed rc=%d\n", panel->stream_id, rc);
+ goto out;
+ }
+
+ rc = msm_dp_display_prepare_link(dp_display);
+ if (rc) {
+ drm_err(dp_display->drm_dev,
+ "[MST] stream:%u prepare_link failed rc=%d\n", panel->stream_id, rc);
+ msm_dp_display_unprepare(dp_display);
+ goto out;
+ }
+
+ 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_err(dp_display->drm_dev,
+ "[MST] payload allocation failure for conn:%d\n", connector->base.id);
+ msm_dp_display_unprepare(dp_display);
+ goto out;
+ }
+
+ msm_dp_mst_update_timeslots(mst, panel, payload);
+
+ msm_dp_display_enable_helper(dp_display, panel);
+
+ drm_dp_check_act_status(&mst->mst_mgr);
+
+ drm_dp_add_payload_part2(&mst->mst_mgr, payload);
+
+out:
+ drm_connector_get(connector);
+}
+
+static void msm_dp_mst_stream_disable(struct drm_encoder *encoder,
+ struct drm_atomic_commit *state)
+{
+ struct drm_connector *connector = drm_atomic_get_old_connector_for_encoder(state, encoder);
+ struct msm_dp_mst_connector *mst_conn = to_dp_mst_connector(connector);
+ struct msm_dp_mst *mst = mst_conn->dp_mst;
+ struct msm_dp_panel *panel = msm_dp_mst_panel_from_encoder(mst, encoder);
+ struct drm_dp_mst_topology_state *old_mst_state =
+ drm_atomic_get_old_mst_topology_state(state, &mst->mst_mgr);
+ struct drm_dp_mst_topology_state *new_mst_state =
+ drm_atomic_get_new_mst_topology_state(state, &mst->mst_mgr);
+ struct drm_dp_mst_atomic_payload *old_payload =
+ drm_atomic_get_mst_payload_state(old_mst_state, mst_conn->mst_port);
+ struct drm_dp_mst_atomic_payload *new_payload =
+ drm_atomic_get_mst_payload_state(new_mst_state, mst_conn->mst_port);
+
+ guard(mutex)(&mst->mst_lock);
+
+ 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, panel, new_payload);
+
+ msm_dp_display_disable_helper(mst->msm_dp, panel);
+
+ drm_dp_check_act_status(&mst->mst_mgr);
+}
+
+static void msm_dp_mst_stream_post_disable(struct drm_encoder *encoder,
+ struct drm_atomic_commit *state)
+{
+ struct drm_connector *connector = drm_atomic_get_old_connector_for_encoder(state, encoder);
+ struct msm_dp_mst_connector *mst_conn = to_dp_mst_connector(connector);
+ struct msm_dp_mst *mst = mst_conn->dp_mst;
+ struct msm_dp_panel *panel = msm_dp_mst_panel_from_encoder(mst, encoder);
+
+ guard(mutex)(&mst->mst_lock);
+
+ msm_dp_display_atomic_post_disable_helper(mst->msm_dp, panel);
+
+ if (!mst->msm_dp->mst_active)
+ msm_dp_display_unprepare(mst->msm_dp);
+
+ panel->connector = NULL;
+
+ drm_connector_put(connector);
+}
+
+static int msm_dp_mst_enc_atomic_check(struct drm_encoder *enc,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state)
+{
+ struct msm_dp_mst_connector *mst_conn = to_dp_mst_connector(conn_state->connector);
+ struct msm_dp_mst *mst = mst_conn->dp_mst;
+ struct drm_dp_mst_topology_state *mst_state;
+ int bpp, pbn, slots;
+
+ if (!conn_state->crtc)
+ return 0;
+
+ if (!drm_atomic_crtc_needs_modeset(crtc_state) || !crtc_state->active)
+ return 0;
+
+ bpp = (conn_state->connector->display_info.bpc * 3) ?: 24; /* fallback: assume 8bpc */
+ pbn = drm_dp_calc_pbn_mode(crtc_state->mode.clock, bpp << 4);
+
+ mst_state = drm_atomic_get_mst_topology_state(crtc_state->state, &mst->mst_mgr);
+ 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->link_info.rate,
+ mst->link_info.num_lanes);
+ }
+
+ slots = drm_dp_atomic_find_time_slots(crtc_state->state, &mst->mst_mgr,
+ mst_conn->mst_port, pbn);
+ if (slots < 0)
+ return slots;
+
+ return 0;
+}
+
+static void msm_dp_mst_enc_atomic_enable(struct drm_encoder *enc,
+ struct drm_atomic_commit *state)
+{
+ msm_dp_mst_stream_enable(enc, state);
+ dpu_encoder_phys_enable(enc, state);
+}
+
+static void msm_dp_mst_enc_atomic_disable(struct drm_encoder *enc,
+ struct drm_atomic_commit *state)
+{
+ msm_dp_mst_stream_disable(enc, state);
+ dpu_encoder_phys_disable(enc, state);
+ msm_dp_mst_stream_post_disable(enc, state);
+}
+
+static const struct drm_encoder_helper_funcs msm_dp_mst_encoder_helper_funcs = {
+ .atomic_check = msm_dp_mst_enc_atomic_check,
+ .atomic_mode_set = dpu_encoder_atomic_mode_set,
+ .atomic_enable = msm_dp_mst_enc_atomic_enable,
+ .atomic_disable = msm_dp_mst_enc_atomic_disable,
+};
+
+int msm_dp_mst_attach_encoder(struct msm_dp *dp_display, struct drm_encoder *encoder)
+{
+ struct msm_dp_mst *mst = dp_display->msm_dp_mst;
+ struct msm_dp_panel *dp_panel;
+ int i;
+
+ for (i = 0; i < mst->max_streams; i++) {
+ if (!mst->mst_encoders[i].enc)
+ break;
+ }
+
+ dp_panel = msm_dp_display_get_panel(dp_display, i);
+ if (!dp_panel) {
+ drm_err(dp_display->drm_dev,
+ "[MST] failed to allocate panel for stream %d\n", i);
+ return -ENOMEM;
+ }
+
+ mst->mst_encoders[i].enc = encoder;
+ mst->mst_encoders[i].stream_id = i;
+ mst->mst_encoders[i].dp_panel = dp_panel;
+ drm_encoder_helper_add(encoder, &msm_dp_mst_encoder_helper_funcs);
+
+ return 0;
+}
+
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;
@@ -27,6 +268,9 @@ int msm_dp_mst_display_set_mgr_state(struct msm_dp *dp_display, bool state)
"[MST] failed to set topology mgr state to %d rc:%d\n", state, rc);
}
+ if (state)
+ msm_dp_display_set_link_info(dp_display, &mst->link_info);
+
drm_dbg_kms(dp_display->drm_dev, "[MST] set_mgr_state state:%d\n", state);
return rc;
}
@@ -55,6 +299,7 @@ int msm_dp_mst_init(struct msm_dp *dp_display, u32 max_streams, struct drm_dp_au
return ret;
}
+ mutex_init(&mst->mst_lock);
dp_display->msm_dp_mst = mst;
return 0;
}
diff --git a/drivers/gpu/drm/msm/msm_drv.h b/drivers/gpu/drm/msm/msm_drv.h
index f71200a790f3..854dd08eede2 100644
--- a/drivers/gpu/drm/msm/msm_drv.h
+++ b/drivers/gpu/drm/msm/msm_drv.h
@@ -358,6 +358,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)
{
@@ -384,6 +386,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] 47+ messages in thread* Re: [PATCH RESEND v5 23/25] drm/msm/dp: add dp_mst_drm to manage DP MST encoder operations
2026-06-29 14:14 ` [PATCH RESEND v5 23/25] drm/msm/dp: add dp_mst_drm to manage DP MST encoder operations Yongxing Mou
@ 2026-07-13 0:40 ` Dmitry Baryshkov
0 siblings, 0 replies; 47+ messages in thread
From: Dmitry Baryshkov @ 2026-07-13 0:40 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 29, 2026 at 10:14:44PM +0800, Yongxing Mou wrote:
> drm/msm/dp: introduce dp_mst_drm for MST stream management
duplicate subject?
>
> Add a dp_mst_drm layer to manage DP MST streams with a clear ownership
> model between encoder, panel and connector.
>
> Each MST stream is represented by a dedicated drm_encoder. At modeset
> initialization time, one (encoder, dp_panel) pair is created per
> stream_id and remains fixed for the lifetime of the driver. The
> dp_panel thus carries a stable stream context, including stream_id
> and pixel mapping.
>
> MST connectors are created and destroyed dynamically on hotplug and
> are attached to a dp_panel through atomic routing. During an atomic
> commit, connectors are associated with encoders via
> atomic_best_encoder(), forming a temporary binding for the duration
> of the commit.
This doesnt't seem to be true any longer.
>
> Encoder helper callbacks drive the MST stream lifecycle, including
> timeslot allocation, link enable/disable and payload programming.
>
> A per-MST-instance lock serializes operations on shared link state
> across multiple streams.
>
> 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_display.c | 9 ++
> drivers/gpu/drm/msm/dp/dp_display.h | 2 +
> drivers/gpu/drm/msm/dp/dp_mst_drm.c | 245 ++++++++++++++++++++++++++++++++
> drivers/gpu/drm/msm/msm_drv.h | 7 +
> 5 files changed, 269 insertions(+)
>
> diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c
> index b32ecd5b0777..ac5dc844fead 100644
> --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c
> +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c
> @@ -696,6 +696,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 attach_encoder failed, rc = %d\n", rc);
> + return rc;
> + }
Reoder, this should be a part of the patch, adding the encoder.
> }
> }
> }
> diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
> index fc9c1e3e57ab..6eac390af2e0 100644
> --- a/drivers/gpu/drm/msm/dp/dp_display.c
> +++ b/drivers/gpu/drm/msm/dp/dp_display.c
> @@ -601,6 +601,15 @@ struct msm_dp_panel *msm_dp_display_get_panel(struct msm_dp *msm_dp_display,
> return dp_panel;
> }
>
> +void msm_dp_display_set_link_info(struct msm_dp *msm_dp_display,
> + struct msm_dp_link_info *dst)
> +{
> + struct msm_dp_display_private *dp =
> + container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display);
> +
> + memcpy(dst, &dp->panel->link_info, sizeof(*dst));
> +}
> +
> 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 a185819ec57e..fb6bdd372b52 100644
> --- a/drivers/gpu/drm/msm/dp/dp_display.h
> +++ b/drivers/gpu/drm/msm/dp/dp_display.h
> @@ -63,4 +63,6 @@ void msm_dp_display_unprepare(struct msm_dp *dp);
>
> struct msm_dp_panel *msm_dp_display_get_panel(struct msm_dp *msm_dp_display,
> enum msm_dp_stream_id stream_id);
> +void msm_dp_display_set_link_info(struct msm_dp *msm_dp_display,
> + struct msm_dp_link_info *dst);
> #endif /* _DP_DISPLAY_H_ */
> diff --git a/drivers/gpu/drm/msm/dp/dp_mst_drm.c b/drivers/gpu/drm/msm/dp/dp_mst_drm.c
> index 78b8dffe111b..6a77fdef85e9 100644
> --- a/drivers/gpu/drm/msm/dp/dp_mst_drm.c
> +++ b/drivers/gpu/drm/msm/dp/dp_mst_drm.c
> @@ -4,18 +4,259 @@
> */
>
> #include <drm/drm_edid.h>
> +#include <drm/drm_fixed.h>
> +#include <drm/drm_atomic_helper.h>
> #include <drm/display/drm_dp_mst_helper.h>
>
> #include "dp_mst_drm.h"
> #include "dp_panel.h"
> +#include "dpu_encoder.h"
> +
> +#define to_dp_mst_connector(x) \
> + container_of((x), struct msm_dp_mst_connector, connector)
> +
> +struct msm_dp_mst_encoder {
> + struct drm_encoder *enc;
> + int stream_id;
> + struct msm_dp_panel *dp_panel;
> +};
> +
> +struct msm_dp_mst_connector {
> + struct drm_connector connector;
> + struct drm_dp_mst_port *mst_port;
> + struct msm_dp_mst *dp_mst;
> +};
> +
Extra empty line.
>
> struct msm_dp_mst {
> struct drm_dp_mst_topology_mgr mst_mgr;
> + struct msm_dp_mst_encoder mst_encoders[DP_STREAM_MAX];
> struct msm_dp *msm_dp;
> struct drm_dp_aux *dp_aux;
> u32 max_streams;
> + struct mutex mst_lock;
> + struct msm_dp_link_info link_info;
> };
>
> +static struct msm_dp_panel *msm_dp_mst_panel_from_encoder(struct msm_dp_mst *mst,
> + struct drm_encoder *enc)
> +{
> + int i;
> +
> + for (i = 0; i < mst->max_streams; i++) {
> + if (mst->mst_encoders[i].enc == enc)
> + return mst->mst_encoders[i].dp_panel;
> + }
> + return NULL;
> +}
> +
> +static void msm_dp_mst_update_timeslots(struct msm_dp_mst *mst,
> + struct msm_dp_panel *panel,
> + struct drm_dp_mst_atomic_payload *payload)
> +{
> + if (payload->vc_start_slot < 0)
> + msm_dp_display_set_stream_info(mst->msm_dp, panel, 1, 0, 0);
> + else
> + msm_dp_display_set_stream_info(mst->msm_dp, panel,
> + payload->vc_start_slot,
> + payload->time_slots, payload->pbn);
Can you pass payload as is? Then you don't need a wrapping function.
> +
> + drm_dbg_kms(mst->msm_dp->drm_dev,
> + "[MST] stream:%u timeslots vc_start:%d slots:%d pbn:%d\n",
> + panel->stream_id, payload->vc_start_slot,
> + payload->time_slots, payload->pbn);
> +}
> +
> +static void msm_dp_mst_stream_enable(struct drm_encoder *encoder,
> + struct drm_atomic_commit *state)
> +{
> + struct drm_connector *connector =
> + drm_atomic_get_new_connector_for_encoder(state, encoder);
> + 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 msm_dp_panel *panel = msm_dp_mst_panel_from_encoder(mst, encoder);
> + struct drm_dp_mst_port *port = mst_conn->mst_port;
> + struct drm_dp_mst_topology_state *mst_state =
> + drm_atomic_get_new_mst_topology_state(state, &mst->mst_mgr);
> + struct drm_dp_mst_atomic_payload *payload =
> + drm_atomic_get_mst_payload_state(mst_state, port);
> + int rc;
> +
> + panel->connector = connector;
The connector should be _get before assigning.
> +
> + guard(mutex)(&mst->mst_lock);
> +
> + rc = msm_dp_display_set_mode_helper(dp_display, state, encoder, panel);
> + if (rc) {
> + drm_err(dp_display->drm_dev,
> + "[MST] stream:%u set_mode failed rc=%d\n", panel->stream_id, rc);
> + goto out;
> + }
> +
> + rc = msm_dp_display_prepare_link(dp_display);
> + if (rc) {
> + drm_err(dp_display->drm_dev,
> + "[MST] stream:%u prepare_link failed rc=%d\n", panel->stream_id, rc);
> + msm_dp_display_unprepare(dp_display);
> + goto out;
> + }
> +
> + 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_err(dp_display->drm_dev,
> + "[MST] payload allocation failure for conn:%d\n", connector->base.id);
> + msm_dp_display_unprepare(dp_display);
> + goto out;
> + }
> +
> + msm_dp_mst_update_timeslots(mst, panel, payload);
> +
> + msm_dp_display_enable_helper(dp_display, panel);
> +
> + drm_dp_check_act_status(&mst->mst_mgr);
> +
> + drm_dp_add_payload_part2(&mst->mst_mgr, payload);
> +
> +out:
> + drm_connector_get(connector);
> +}
> +
> +static void msm_dp_mst_stream_disable(struct drm_encoder *encoder,
> + struct drm_atomic_commit *state)
> +{
> + struct drm_connector *connector = drm_atomic_get_old_connector_for_encoder(state, encoder);
> + struct msm_dp_mst_connector *mst_conn = to_dp_mst_connector(connector);
> + struct msm_dp_mst *mst = mst_conn->dp_mst;
> + struct msm_dp_panel *panel = msm_dp_mst_panel_from_encoder(mst, encoder);
> + struct drm_dp_mst_topology_state *old_mst_state =
> + drm_atomic_get_old_mst_topology_state(state, &mst->mst_mgr);
> + struct drm_dp_mst_topology_state *new_mst_state =
> + drm_atomic_get_new_mst_topology_state(state, &mst->mst_mgr);
> + struct drm_dp_mst_atomic_payload *old_payload =
> + drm_atomic_get_mst_payload_state(old_mst_state, mst_conn->mst_port);
> + struct drm_dp_mst_atomic_payload *new_payload =
> + drm_atomic_get_mst_payload_state(new_mst_state, mst_conn->mst_port);
> +
> + guard(mutex)(&mst->mst_lock);
> +
> + 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, panel, new_payload);
> +
> + msm_dp_display_disable_helper(mst->msm_dp, panel);
> +
> + drm_dp_check_act_status(&mst->mst_mgr);
> +}
> +
> +static void msm_dp_mst_stream_post_disable(struct drm_encoder *encoder,
> + struct drm_atomic_commit *state)
> +{
> + struct drm_connector *connector = drm_atomic_get_old_connector_for_encoder(state, encoder);
> + struct msm_dp_mst_connector *mst_conn = to_dp_mst_connector(connector);
> + struct msm_dp_mst *mst = mst_conn->dp_mst;
> + struct msm_dp_panel *panel = msm_dp_mst_panel_from_encoder(mst, encoder);
> +
> + guard(mutex)(&mst->mst_lock);
> +
> + msm_dp_display_atomic_post_disable_helper(mst->msm_dp, panel);
> +
> + if (!mst->msm_dp->mst_active)
> + msm_dp_display_unprepare(mst->msm_dp);
> +
> + panel->connector = NULL;
> +
> + drm_connector_put(connector);
> +}
> +
> +static int msm_dp_mst_enc_atomic_check(struct drm_encoder *enc,
> + struct drm_crtc_state *crtc_state,
> + struct drm_connector_state *conn_state)
> +{
> + struct msm_dp_mst_connector *mst_conn = to_dp_mst_connector(conn_state->connector);
> + struct msm_dp_mst *mst = mst_conn->dp_mst;
> + struct drm_dp_mst_topology_state *mst_state;
> + int bpp, pbn, slots;
> +
> + if (!conn_state->crtc)
> + return 0;
> +
> + if (!drm_atomic_crtc_needs_modeset(crtc_state) || !crtc_state->active)
> + return 0;
> +
> + bpp = (conn_state->connector->display_info.bpc * 3) ?: 24; /* fallback: assume 8bpc */
> + pbn = drm_dp_calc_pbn_mode(crtc_state->mode.clock, bpp << 4);
> +
> + mst_state = drm_atomic_get_mst_topology_state(crtc_state->state, &mst->mst_mgr);
> + 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->link_info.rate,
> + mst->link_info.num_lanes);
> + }
> +
> + slots = drm_dp_atomic_find_time_slots(crtc_state->state, &mst->mst_mgr,
> + mst_conn->mst_port, pbn);
> + if (slots < 0)
> + return slots;
> +
> + return 0;
> +}
> +
> +static void msm_dp_mst_enc_atomic_enable(struct drm_encoder *enc,
> + struct drm_atomic_commit *state)
> +{
> + msm_dp_mst_stream_enable(enc, state);
> + dpu_encoder_phys_enable(enc, state);
Looking at this, no, squashing the bridge and the encoder was not a good
idea, excuse me. The encoder is an internal implement of the DPU layer
and we should leave it there as is. DPU can use DP functions, but not
other way around.
> +}
> +
> +static void msm_dp_mst_enc_atomic_disable(struct drm_encoder *enc,
> + struct drm_atomic_commit *state)
> +{
> + msm_dp_mst_stream_disable(enc, state);
> + dpu_encoder_phys_disable(enc, state);
> + msm_dp_mst_stream_post_disable(enc, state);
> +}
> +
> +static const struct drm_encoder_helper_funcs msm_dp_mst_encoder_helper_funcs = {
> + .atomic_check = msm_dp_mst_enc_atomic_check,
> + .atomic_mode_set = dpu_encoder_atomic_mode_set,
> + .atomic_enable = msm_dp_mst_enc_atomic_enable,
> + .atomic_disable = msm_dp_mst_enc_atomic_disable,
> +};
> +
> +int msm_dp_mst_attach_encoder(struct msm_dp *dp_display, struct drm_encoder *encoder)
> +{
> + struct msm_dp_mst *mst = dp_display->msm_dp_mst;
> + struct msm_dp_panel *dp_panel;
> + int i;
> +
> + for (i = 0; i < mst->max_streams; i++) {
> + if (!mst->mst_encoders[i].enc)
> + break;
> + }
> +
> + dp_panel = msm_dp_display_get_panel(dp_display, i);
> + if (!dp_panel) {
> + drm_err(dp_display->drm_dev,
> + "[MST] failed to allocate panel for stream %d\n", i);
> + return -ENOMEM;
> + }
> +
> + mst->mst_encoders[i].enc = encoder;
> + mst->mst_encoders[i].stream_id = i;
> + mst->mst_encoders[i].dp_panel = dp_panel;
> + drm_encoder_helper_add(encoder, &msm_dp_mst_encoder_helper_funcs);
> +
> + return 0;
> +}
> +
> 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;
> @@ -27,6 +268,9 @@ int msm_dp_mst_display_set_mgr_state(struct msm_dp *dp_display, bool state)
> "[MST] failed to set topology mgr state to %d rc:%d\n", state, rc);
> }
>
> + if (state)
Why?
> + msm_dp_display_set_link_info(dp_display, &mst->link_info);
> +
> drm_dbg_kms(dp_display->drm_dev, "[MST] set_mgr_state state:%d\n", state);
> return rc;
> }
> @@ -55,6 +299,7 @@ int msm_dp_mst_init(struct msm_dp *dp_display, u32 max_streams, struct drm_dp_au
> return ret;
> }
>
> + mutex_init(&mst->mst_lock);
> dp_display->msm_dp_mst = mst;
> return 0;
> }
> diff --git a/drivers/gpu/drm/msm/msm_drv.h b/drivers/gpu/drm/msm/msm_drv.h
> index f71200a790f3..854dd08eede2 100644
> --- a/drivers/gpu/drm/msm/msm_drv.h
> +++ b/drivers/gpu/drm/msm/msm_drv.h
> @@ -358,6 +358,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)
> {
> @@ -384,6 +386,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] 47+ messages in thread
* [PATCH RESEND v5 24/25] drm/msm/dp: add connector abstraction for DP MST
2026-06-29 14:14 [PATCH RESEND v5 00/25] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
` (22 preceding siblings ...)
2026-06-29 14:14 ` [PATCH RESEND v5 23/25] drm/msm/dp: add dp_mst_drm to manage DP MST encoder operations Yongxing Mou
@ 2026-06-29 14:14 ` Yongxing Mou
2026-07-13 0:52 ` Dmitry Baryshkov
2026-06-29 14:14 ` [PATCH RESEND v5 25/25] drm/msm/dp: add HPD callback for dp MST Yongxing Mou
2026-07-12 11:04 ` [PATCH RESEND v5 00/25] drm/msm/dp: Add MST support for MSM chipsets Dmitry Baryshkov
25 siblings, 1 reply; 47+ messages in thread
From: Yongxing Mou @ 2026-06-29 14:14 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, 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 | 232 ++++++++++++++++++++++++++++++++++++
1 file changed, 232 insertions(+)
diff --git a/drivers/gpu/drm/msm/dp/dp_mst_drm.c b/drivers/gpu/drm/msm/dp/dp_mst_drm.c
index 6a77fdef85e9..12b47a413793 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_fixed.h>
#include <drm/drm_atomic_helper.h>
#include <drm/display/drm_dp_mst_helper.h>
+#include <linux/pm_runtime.h>
#include "dp_mst_drm.h"
#include "dp_panel.h"
@@ -50,6 +51,18 @@ static struct msm_dp_panel *msm_dp_mst_panel_from_encoder(struct msm_dp_mst *mst
return NULL;
}
+static int msm_dp_mst_encoder_stream_id(struct msm_dp_mst *mst,
+ struct drm_encoder *enc)
+{
+ int i;
+
+ for (i = 0; i < mst->max_streams; i++) {
+ if (mst->mst_encoders[i].enc == enc)
+ return mst->mst_encoders[i].stream_id;
+ }
+ return -1;
+}
+
static void msm_dp_mst_update_timeslots(struct msm_dp_mst *mst,
struct msm_dp_panel *panel,
struct drm_dp_mst_atomic_payload *payload)
@@ -275,6 +288,224 @@ 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;
+ int rc;
+
+ drm_edid = drm_dp_mst_edid_read(connector, &mst->mst_mgr, mst_conn->mst_port);
+ drm_edid_connector_update(connector, drm_edid);
+
+ rc = drm_edid_connector_add_modes(connector);
+
+ drm_edid_free(drm_edid);
+
+ return rc;
+}
+
+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;
+
+ /* FIXME: use negotiated bpp (DSC, YUV 4:2:0, etc.); for now use
+ * 18bpp (6bpc) as a conservative lower bound like i915/nouveau.
+ */
+ 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 const struct drm_connector_funcs msm_dp_drm_mst_connector_funcs;
+
+static struct drm_encoder *
+msm_dp_mst_atomic_best_encoder(struct drm_connector *connector, struct drm_atomic_commit *state)
+{
+ struct msm_dp_mst_connector *mst_conn = to_dp_mst_connector(connector);
+ struct msm_dp_mst *mst = mst_conn->dp_mst;
+ struct drm_connector_state *conn_state;
+ struct drm_connector *iter;
+ struct drm_connector_list_iter conn_iter;
+ u32 stream_mask = 0;
+ u32 i;
+
+ conn_state = drm_atomic_get_new_connector_state(state, connector);
+ if (!conn_state)
+ return NULL;
+
+ if (conn_state->best_encoder)
+ return conn_state->best_encoder;
+
+ drm_connector_list_iter_begin(connector->dev, &conn_iter);
+ drm_for_each_connector_iter(iter, &conn_iter) {
+ struct drm_connector_state *peer_state;
+ int stream_id;
+
+ if (iter == connector ||
+ iter->funcs != &msm_dp_drm_mst_connector_funcs ||
+ to_dp_mst_connector(iter)->dp_mst != mst)
+ continue;
+
+ peer_state = drm_atomic_get_new_connector_state(state, iter) ?: iter->state;
+ if (!peer_state || !peer_state->crtc || !peer_state->best_encoder)
+ continue;
+
+ stream_id = msm_dp_mst_encoder_stream_id(mst, peer_state->best_encoder);
+ if (stream_id >= 0 && stream_id < mst->max_streams)
+ stream_mask |= BIT(stream_id);
+ }
+ drm_connector_list_iter_end(&conn_iter);
+
+ for (i = 0; i < mst->max_streams; i++) {
+ if (!(stream_mask & BIT(i))) {
+ conn_state->best_encoder = mst->mst_encoders[i].enc;
+ return mst->mst_encoders[i].enc;
+ }
+ }
+
+ return NULL;
+}
+
+static int msm_dp_mst_connector_atomic_check(struct drm_connector *connector,
+ struct drm_atomic_commit *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 *mst = container_of(mgr, struct msm_dp_mst, mst_mgr);
+ struct drm_device *dev = mst->msm_dp->drm_dev;
+ struct msm_dp_mst_connector *mst_conn;
+ struct drm_connector *connector;
+ int rc, i;
+
+ mst_conn = kzalloc_obj(*mst_conn);
+ if (!mst_conn)
+ return NULL;
+
+ connector = &mst_conn->connector;
+ rc = drm_connector_dynamic_init(dev, connector,
+ &msm_dp_drm_mst_connector_funcs,
+ DRM_MODE_CONNECTOR_DisplayPort, NULL);
+ if (rc)
+ goto err_free;
+
+ mst_conn->dp_mst = mst;
+
+ drm_connector_helper_add(connector, &msm_dp_drm_mst_connector_helper_funcs);
+ connector->funcs->reset(connector);
+
+ /* add all encoders as possible encoders */
+ for (i = 0; i < mst->max_streams; i++) {
+ rc = drm_connector_attach_encoder(connector, mst->mst_encoders[i].enc);
+ if (rc) {
+ drm_err(dev, "[MST] failed to attach encoder:%u to conn:%d rc:%d\n",
+ mst->mst_encoders[i].enc->base.id,
+ connector->base.id, rc);
+ goto err_connector;
+ }
+ }
+
+ mst_conn->mst_port = port;
+ drm_dp_mst_get_port_malloc(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_dbg_kms(dev, "[MST] add_connector done conn:%d max_streams:%u\n",
+ connector->base.id, mst->max_streams);
+
+ return connector;
+
+err_connector:
+ drm_connector_cleanup(connector);
+err_free:
+ kfree(mst_conn);
+ return NULL;
+}
+
+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;
@@ -285,6 +516,7 @@ int msm_dp_mst_init(struct msm_dp *dp_display, u32 max_streams, struct drm_dp_au
if (!mst)
return -ENOMEM;
+ mst->mst_mgr.cbs = &msm_dp_mst_drm_cbs;
mst->msm_dp = dp_display;
mst->max_streams = max_streams;
mst->dp_aux = drm_aux;
--
2.43.0
^ permalink raw reply [flat|nested] 47+ messages in thread* Re: [PATCH RESEND v5 24/25] drm/msm/dp: add connector abstraction for DP MST
2026-06-29 14:14 ` [PATCH RESEND v5 24/25] drm/msm/dp: add connector abstraction for DP MST Yongxing Mou
@ 2026-07-13 0:52 ` Dmitry Baryshkov
0 siblings, 0 replies; 47+ messages in thread
From: Dmitry Baryshkov @ 2026-07-13 0: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, Abhinav Kumar
On Mon, Jun 29, 2026 at 10:14:45PM +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.
If it is so generic, can it be completely generic? Can we move it to the
drm/display/* helper code? Or, at least, leverage significant parts of
it there, providing necessary callbacks from the MSM code? It feels that
only the connector -> encoder part is not generic enough.
>
> 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 | 232 ++++++++++++++++++++++++++++++++++++
> 1 file changed, 232 insertions(+)
>
> diff --git a/drivers/gpu/drm/msm/dp/dp_mst_drm.c b/drivers/gpu/drm/msm/dp/dp_mst_drm.c
> index 6a77fdef85e9..12b47a413793 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_fixed.h>
> #include <drm/drm_atomic_helper.h>
> #include <drm/display/drm_dp_mst_helper.h>
> +#include <linux/pm_runtime.h>
>
> #include "dp_mst_drm.h"
> #include "dp_panel.h"
> @@ -50,6 +51,18 @@ static struct msm_dp_panel *msm_dp_mst_panel_from_encoder(struct msm_dp_mst *mst
> return NULL;
> }
>
> +static int msm_dp_mst_encoder_stream_id(struct msm_dp_mst *mst,
> + struct drm_encoder *enc)
> +{
> + int i;
> +
Ca> + for (i = 0; i < mst->max_streams; i++) {
> + if (mst->mst_encoders[i].enc == enc)
> + return mst->mst_encoders[i].stream_id;
> + }
> + return -1;
> +}
> +
> static void msm_dp_mst_update_timeslots(struct msm_dp_mst *mst,
> struct msm_dp_panel *panel,
> struct drm_dp_mst_atomic_payload *payload)
> @@ -275,6 +288,224 @@ 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)
Can it be inactive here?
> + 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;
> + int rc;
> +
> + drm_edid = drm_dp_mst_edid_read(connector, &mst->mst_mgr, mst_conn->mst_port);
> + drm_edid_connector_update(connector, drm_edid);
> +
> + rc = drm_edid_connector_add_modes(connector);
> +
> + drm_edid_free(drm_edid);
> +
> + return rc;
> +}
> +
> +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;
> +
> + /* FIXME: use negotiated bpp (DSC, YUV 4:2:0, etc.); for now use
You can't use negotiated bpp, because it well might be that the
negoation didn't happen. Anyway, mode_valid should be returning the
modes which are even theoretically not possible to be set.
> + * 18bpp (6bpc) as a conservative lower bound like i915/nouveau.
> + */
> + 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 const struct drm_connector_funcs msm_dp_drm_mst_connector_funcs;
> +
> +static struct drm_encoder *
> +msm_dp_mst_atomic_best_encoder(struct drm_connector *connector, struct drm_atomic_commit *state)
> +{
> + struct msm_dp_mst_connector *mst_conn = to_dp_mst_connector(connector);
> + struct msm_dp_mst *mst = mst_conn->dp_mst;
> + struct drm_connector_state *conn_state;
> + struct drm_connector *iter;
> + struct drm_connector_list_iter conn_iter;
> + u32 stream_mask = 0;
> + u32 i;
> +
> + conn_state = drm_atomic_get_new_connector_state(state, connector);
> + if (!conn_state)
> + return NULL;
> +
> + if (conn_state->best_encoder)
> + return conn_state->best_encoder;
> +
> + drm_connector_list_iter_begin(connector->dev, &conn_iter);
> + drm_for_each_connector_iter(iter, &conn_iter) {
> + struct drm_connector_state *peer_state;
> + int stream_id;
> +
> + if (iter == connector ||
> + iter->funcs != &msm_dp_drm_mst_connector_funcs ||
> + to_dp_mst_connector(iter)->dp_mst != mst)
> + continue;
> +
> + peer_state = drm_atomic_get_new_connector_state(state, iter) ?: iter->state;
> + if (!peer_state || !peer_state->crtc || !peer_state->best_encoder)
> + continue;
> +
> + stream_id = msm_dp_mst_encoder_stream_id(mst, peer_state->best_encoder);
> + if (stream_id >= 0 && stream_id < mst->max_streams)
> + stream_mask |= BIT(stream_id);
> + }
> + drm_connector_list_iter_end(&conn_iter);
> +
> + for (i = 0; i < mst->max_streams; i++) {
> + if (!(stream_mask & BIT(i))) {
> + conn_state->best_encoder = mst->mst_encoders[i].enc;
> + return mst->mst_encoders[i].enc;
> + }
> + }
> +
> + return NULL;
> +}
> +
> +static int msm_dp_mst_connector_atomic_check(struct drm_connector *connector,
> + struct drm_atomic_commit *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);
Why is it being called here? Don't we need to perform slot allocation
too?
> +}
> +
> +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 *mst = container_of(mgr, struct msm_dp_mst, mst_mgr);
> + struct drm_device *dev = mst->msm_dp->drm_dev;
> + struct msm_dp_mst_connector *mst_conn;
> + struct drm_connector *connector;
> + int rc, i;
> +
> + mst_conn = kzalloc_obj(*mst_conn);
> + if (!mst_conn)
> + return NULL;
> +
> + connector = &mst_conn->connector;
> + rc = drm_connector_dynamic_init(dev, connector,
> + &msm_dp_drm_mst_connector_funcs,
> + DRM_MODE_CONNECTOR_DisplayPort, NULL);
> + if (rc)
> + goto err_free;
> +
> + mst_conn->dp_mst = mst;
> +
> + drm_connector_helper_add(connector, &msm_dp_drm_mst_connector_helper_funcs);
> + connector->funcs->reset(connector);
> +
> + /* add all encoders as possible encoders */
> + for (i = 0; i < mst->max_streams; i++) {
> + rc = drm_connector_attach_encoder(connector, mst->mst_encoders[i].enc);
> + if (rc) {
> + drm_err(dev, "[MST] failed to attach encoder:%u to conn:%d rc:%d\n",
> + mst->mst_encoders[i].enc->base.id,
> + connector->base.id, rc);
> + goto err_connector;
> + }
> + }
> +
> + mst_conn->mst_port = port;
> + drm_dp_mst_get_port_malloc(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_dbg_kms(dev, "[MST] add_connector done conn:%d max_streams:%u\n",
> + connector->base.id, mst->max_streams);
> +
> + return connector;
> +
> +err_connector:
> + drm_connector_cleanup(connector);
> +err_free:
> + kfree(mst_conn);
> + return NULL;
> +}
> +
> +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;
> @@ -285,6 +516,7 @@ int msm_dp_mst_init(struct msm_dp *dp_display, u32 max_streams, struct drm_dp_au
> if (!mst)
> return -ENOMEM;
>
> + mst->mst_mgr.cbs = &msm_dp_mst_drm_cbs;
> mst->msm_dp = dp_display;
> mst->max_streams = max_streams;
> mst->dp_aux = drm_aux;
>
> --
> 2.43.0
>
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 47+ messages in thread
* [PATCH RESEND v5 25/25] drm/msm/dp: add HPD callback for dp MST
2026-06-29 14:14 [PATCH RESEND v5 00/25] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
` (23 preceding siblings ...)
2026-06-29 14:14 ` [PATCH RESEND v5 24/25] drm/msm/dp: add connector abstraction for DP MST Yongxing Mou
@ 2026-06-29 14:14 ` Yongxing Mou
2026-07-13 0:54 ` Dmitry Baryshkov
2026-07-12 11:04 ` [PATCH RESEND v5 00/25] drm/msm/dp: Add MST support for MSM chipsets Dmitry Baryshkov
25 siblings, 1 reply; 47+ messages in thread
From: Yongxing Mou @ 2026-06-29 14:14 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, 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 | 22 ++++++++++++++----
drivers/gpu/drm/msm/dp/dp_mst_drm.c | 46 +++++++++++++++++++++++++++++++++++++
drivers/gpu/drm/msm/dp/dp_mst_drm.h | 1 +
3 files changed, 65 insertions(+), 4 deletions(-)
diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
index 6eac390af2e0..49a7bc5e031e 100644
--- a/drivers/gpu/drm/msm/dp/dp_display.c
+++ b/drivers/gpu/drm/msm/dp/dp_display.c
@@ -455,6 +455,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 && dp->msm_dp_display.mst_active)
+ return 0;
+
guard(mutex)(&dp->plugged_lock);
ret = pm_runtime_resume_and_get(&pdev->dev);
@@ -550,12 +553,18 @@ 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) {
+ 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) {
@@ -1111,9 +1120,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;
@@ -1776,7 +1789,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 12b47a413793..56f7a84e77d1 100644
--- a/drivers/gpu/drm/msm/dp/dp_mst_drm.c
+++ b/drivers/gpu/drm/msm/dp/dp_mst_drm.c
@@ -36,6 +36,8 @@ struct msm_dp_mst {
struct drm_dp_aux *dp_aux;
u32 max_streams;
struct mutex mst_lock;
+ /* Serializes HPD IRQ handling between IRQ handler and poll_hpd_irq. */
+ struct mutex hpd_irq_lock;
struct msm_dp_link_info link_info;
};
@@ -288,6 +290,41 @@ int msm_dp_mst_display_set_mgr_state(struct msm_dp *dp_display, bool state)
return rc;
}
+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;
+
+ guard(mutex)(&mst->hpd_irq_lock);
+
+ 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,
@@ -502,8 +539,16 @@ msm_dp_mst_add_connector(struct drm_dp_mst_topology_mgr *mgr,
return NULL;
}
+static void msm_dp_mst_poll_hpd_irq(struct drm_dp_mst_topology_mgr *mgr)
+{
+ struct msm_dp_mst *mst = container_of(mgr, struct msm_dp_mst, mst_mgr);
+
+ msm_dp_mst_display_hpd_irq(mst->msm_dp);
+}
+
static const struct drm_dp_mst_topology_cbs msm_dp_mst_drm_cbs = {
.add_connector = msm_dp_mst_add_connector,
+ .poll_hpd_irq = msm_dp_mst_poll_hpd_irq,
};
int msm_dp_mst_init(struct msm_dp *dp_display, u32 max_streams, struct drm_dp_aux *drm_aux)
@@ -532,6 +577,7 @@ int msm_dp_mst_init(struct msm_dp *dp_display, u32 max_streams, struct drm_dp_au
}
mutex_init(&mst->mst_lock);
+ mutex_init(&mst->hpd_irq_lock);
dp_display->msm_dp_mst = mst;
return 0;
}
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] 47+ messages in thread* Re: [PATCH RESEND v5 25/25] drm/msm/dp: add HPD callback for dp MST
2026-06-29 14:14 ` [PATCH RESEND v5 25/25] drm/msm/dp: add HPD callback for dp MST Yongxing Mou
@ 2026-07-13 0:54 ` Dmitry Baryshkov
0 siblings, 0 replies; 47+ messages in thread
From: Dmitry Baryshkov @ 2026-07-13 0:54 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 29, 2026 at 10:14:46PM +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 | 22 ++++++++++++++----
> drivers/gpu/drm/msm/dp/dp_mst_drm.c | 46 +++++++++++++++++++++++++++++++++++++
> drivers/gpu/drm/msm/dp/dp_mst_drm.h | 1 +
> 3 files changed, 65 insertions(+), 4 deletions(-)
>
> diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
> index 6eac390af2e0..49a7bc5e031e 100644
> --- a/drivers/gpu/drm/msm/dp/dp_display.c
> +++ b/drivers/gpu/drm/msm/dp/dp_display.c
> @@ -455,6 +455,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 && dp->msm_dp_display.mst_active)
> + return 0;
Why?
> +
> guard(mutex)(&dp->plugged_lock);
>
> ret = pm_runtime_resume_and_get(&pdev->dev);
> @@ -550,12 +553,18 @@ 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) {
> + msm_dp_mst_display_hpd_irq(&dp->msm_dp_display);
This should be routed through the HPD / notifications API so that there
is always a single path for handling the IRQ_HPD interrupts.
> + return 0;
> + }
> +
> /* check for any test request issued by sink */
> rc = msm_dp_link_process_request(dp->link);
> if (!rc) {
> @@ -1111,9 +1120,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)
Locking? What if MST is being enabled or disabled on the other core?
> + msm_dp_irq_hpd_handle(dp);
> + else
> + drm_bridge_hpd_notify(dp->msm_dp_display.bridge,
> + connector_status_connected);
> + }
>
> ret = IRQ_HANDLED;
>
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 47+ messages in thread
* Re: [PATCH RESEND v5 00/25] drm/msm/dp: Add MST support for MSM chipsets
2026-06-29 14:14 [PATCH RESEND v5 00/25] drm/msm/dp: Add MST support for MSM chipsets Yongxing Mou
` (24 preceding siblings ...)
2026-06-29 14:14 ` [PATCH RESEND v5 25/25] drm/msm/dp: add HPD callback for dp MST Yongxing Mou
@ 2026-07-12 11:04 ` Dmitry Baryshkov
25 siblings, 0 replies; 47+ messages in thread
From: Dmitry Baryshkov @ 2026-07-12 11: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 Mon, Jun 29, 2026 at 10:14:21PM +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.
Type-C?
>
> 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/167458/
>
> 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.
>
> Type-C MST support will be submitted shortly after this series.
You end up introducing issues which are fixed in those patches. The API
changes might need to be submitted separately, but the rest should go
through this patchset.
> Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
> ---
> Changes in v5(fix comments from V4):
> - Dropped early refactoring patches (v4 01-12); restructured as
> "move link-level teardown", "factor out _helper variants", etc.
> - Dropped v4 39/39 (platform MST stream list); use DT pixel-clock
> count instead per Dmitry's request.
> - Patch 02: Remove artificial max-stream limitation check.
> unify register naming (REG_DP1 vs MMSS_DP1).
> - Patch 05: Calculate ACT wait time dynamically from mode parameters
> instead of hardcoded 20 ms.
> - Patch 08: Replace start-slot loop with direct math; fix commit message.
> - Patch 09: Add locking annotation "Must be called with
> msm_dp_mst::mst_lock held" to msm_dp_ctrl_push_vcpf().
> - Patch 17: Rename prepared flag to link_ready.
> - Patch 22: Move drm_dp_mst_atomic_check() call into msm_atomic.c.
> - Patch 23: Replace bridge-based MST DRM model with encoder-based
> approach; remove redundant bridge layer between encoder and connector.
> - Patch 24: Add drm_edid_free(); add FIXME for bpp negotiation.
> - Patch 25: Use dp->plugged flag instead of link status for MST plug path routing.
> - Link to v4: https://lore.kernel.org/r/20260410-msm-dp-mst-v4-0-b20518dea8de@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 (19):
> 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: move link-level teardown from display_disable to display_unprepare
> drm/msm/dp: factor out _helper variants of bridge ops accepting 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 msm_dp_display_get_panel() to initialize DP panel
> drm/msm/dp: initialize dp_mst module for each DP MST controller
> drm/msm/dp: add connector abstraction for DP MST
> drm/msm/dp: add HPD callback for dp MST
>
> Yongxing Mou (6):
> drm/msm/dp: add link_ready to manage link-level operations
> drm/msm/dpu: initialize encoders per stream for DP MST
> drm/msm/dpu: expose dpu_encoder ops for DP MST reuse
> drm/msm/dpu: use msm_dp_get_mst_intf_id() to get the intf id
> drm/msm/dp: wire MST helpers into atomic check and commit paths
> drm/msm/dp: add dp_mst_drm to manage DP MST encoder operations
>
> drivers/gpu/drm/msm/Makefile | 3 +-
> drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c | 40 +-
> drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.h | 8 +
> 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 | 473 ++++++++++++++++++++--
> drivers/gpu/drm/msm/dp/dp_ctrl.h | 17 +-
> drivers/gpu/drm/msm/dp/dp_display.c | 394 +++++++++++++++----
> drivers/gpu/drm/msm/dp/dp_display.h | 25 +-
> drivers/gpu/drm/msm/dp/dp_mst_drm.c | 583 ++++++++++++++++++++++++++++
> drivers/gpu/drm/msm/dp/dp_mst_drm.h | 14 +
> drivers/gpu/drm/msm/dp/dp_panel.c | 197 ++++++++--
> drivers/gpu/drm/msm/dp/dp_panel.h | 20 +-
> drivers/gpu/drm/msm/dp/dp_reg.h | 68 ++++
> drivers/gpu/drm/msm/msm_atomic.c | 14 +-
> drivers/gpu/drm/msm/msm_drv.h | 19 +
> drivers/gpu/drm/msm/msm_kms.c | 1 +
> 17 files changed, 1718 insertions(+), 190 deletions(-)
> ---
> base-commit: e7d700e14934e68f86338c5610cf2ae76798b663
> change-id: 20260410-msm-dp-mst-35130b6e8b84
> prerequisite-message-id: <20260609-dp_mstclean-v7-0-ea04113e8233@oss.qualcomm.com>
> prerequisite-patch-id: 1d440cb9fed2bdd66d8de0e1e20475f0fe166973
> prerequisite-patch-id: be0f4b80697df7224c80362b161b8a9f0a542184
> prerequisite-patch-id: eefa6e6353df301420feae1da704a9db2c2155f2
> prerequisite-patch-id: 9e9095f82dd6c131c9f3c1de4fdb8a62bd65ca24
> prerequisite-patch-id: 3e635f008f9b56823101abd9253905f078fcb3b5
> prerequisite-patch-id: e39e0dc124ed043c7a419610ebe03ad105da27db
> prerequisite-patch-id: 945af39213cd4241e1a5929fada04a9286aeb5db
> prerequisite-patch-id: 898ae7e4582a6b31492c223e7dd167fb9ce78096
> prerequisite-patch-id: 3887553893357c1ffbda99eb010801bc2166cbad
> prerequisite-patch-id: 7ccd961fa3c6f925659dee7d7a5bd167c8e7331b
> prerequisite-patch-id: be2bf918e0e87ec2ea999927f36bd172c498748e
> prerequisite-patch-id: 6aacdabb2dd0536dc04da04f8419ae39e35f8b19
> prerequisite-patch-id: a9f27eff8f643ff445810b17d670891928f5b416
> prerequisite-patch-id: efd300a2b52715153b8c1c7407db696eb331594b
> prerequisite-patch-id: 950abefc4862050ef606404977fd27c5dd2cbb2b
>
> Best regards,
> --
> Yongxing Mou <yongxing.mou@oss.qualcomm.com>
>
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 47+ messages in thread