From a390baa8bf6c9cc795c32214c352977667e8e5df Mon Sep 17 00:00:00 2001 From: Arpit Saini Date: Tue, 24 Mar 2026 12:33:22 +0530 Subject: [PATCH 1/9] disp: msm: dsi: Move MIPI host init/deinit from display bind/unbind Move the dsi_display_mipi_host_init() function call from dsi_display_bind() to dsi_display_dev_init() and dsi_display_mipi_host_deinit() function call from dsi_display_unbind() to dsi_display_dev_deinit(). Change-Id: Ib8b9ed9d5ca9cca6dd3911eea98bd095f8b19fea Signed-off-by: Arpit Saini --- msm/dsi/dsi_display.c | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/msm/dsi/dsi_display.c b/msm/dsi/dsi_display.c index ffe8d26..fedd431 100644 --- a/msm/dsi/dsi_display.c +++ b/msm/dsi/dsi_display.c @@ -5612,6 +5612,13 @@ static int _dsi_display_dev_init(struct dsi_display *display) display->name, rc); goto error; } + + rc = dsi_display_mipi_host_init(display); + if (rc) { + DSI_ERR("[%s] failed to initialize mipi host, rc=%d\n", + display->name, rc); + goto error; + } error: mutex_unlock(&display->display_lock); return rc; @@ -5634,6 +5641,11 @@ static int _dsi_display_dev_deinit(struct dsi_display *display) mutex_lock(&display->display_lock); + rc = dsi_display_mipi_host_deinit(display); + if (rc) + DSI_ERR("[%s] failed to deinit mipi hosts, rc=%d\n", + display->name, rc); + rc = dsi_display_res_deinit(display); if (rc) DSI_ERR("[%s] failed to deinitialize resource, rc=%d\n", @@ -6165,12 +6177,6 @@ static int dsi_display_bind(struct device *dev, } dsi_display_update_byte_intf_div(display); - rc = dsi_display_mipi_host_init(display); - if (rc) { - DSI_ERR("[%s] failed to initialize mipi host, rc=%d\n", - display->name, rc); - goto error_ctrl_deinit; - } rc = dsi_panel_drv_init(display->panel, &display->host); if (rc) { @@ -6267,11 +6273,12 @@ static void dsi_display_unbind(struct device *dev, /* remove the display from the manager list */ dsi_display_manager_unregister(display); - rc = dsi_display_mipi_host_deinit(display); - if (rc) - DSI_ERR("[%s] failed to deinit mipi hosts, rc=%d\n", - display->name, - rc); + if (display->panel) { + rc = display->panel->panel_ops.gpio_release(display->panel); + if (rc) + DSI_ERR("[%s] failed to release gpios, rc=%d\n", display->panel->name, + rc); + } display_for_each_ctrl(i, display) { display_ctrl = &display->ctrl[i]; From c5f1fb2087f2e59ce589266821c07e1074e005ed Mon Sep 17 00:00:00 2001 From: Arpit Saini Date: Tue, 5 May 2026 13:11:24 +0530 Subject: [PATCH 2/9] disp: msm: dsi: Add DRM panel detection and stub creation in probe For DRM panels, the panel driver registers itself via the MIPI DSI host framework. This change adds the probe-time infrastructure to detect and initialize DRM panels and create a minimal stub panel, enabling integration with the MIPI DSI host framework without requiring a downstream DT panel node. Change-Id: Iaf6d18b6620f75e26acfb6d5201d4acf79f911b7 Signed-off-by: Arpit Saini --- msm/dsi/dsi_display.c | 135 ++++++++++++++++++++++++++++++++++++------ msm/dsi/dsi_display.h | 2 + msm/dsi/dsi_panel.c | 48 ++++++++++++++- msm/dsi/dsi_panel.h | 6 +- msm/dsi/dsi_pwr.c | 2 +- 5 files changed, 171 insertions(+), 22 deletions(-) diff --git a/msm/dsi/dsi_display.c b/msm/dsi/dsi_display.c index fedd431..5616f94 100644 --- a/msm/dsi/dsi_display.c +++ b/msm/dsi/dsi_display.c @@ -3661,6 +3661,7 @@ static int dsi_display_clocks_init(struct dsi_display *display) struct clk *esync_clk_rcg; struct clk *dsi_clk; struct dsi_clk_link_set *pll = &display->clock_info.pll_clks; + const char *pll_clk_names[2]; char *dsi_clock_name; if (!strcmp(display->display_type, "primary")) @@ -3673,9 +3674,24 @@ static int dsi_display_clocks_init(struct dsi_display *display) num_clk = dsi_display_get_clocks_count(display, dsi_clock_name); + if (num_clk <= 0) { + if (!strcmp(display->display_type, "primary")) { + pll_clk_names[0] = "pll_byte_clk0"; + pll_clk_names[1] = "pll_dsi_clk0"; + } else { + pll_clk_names[0] = "pll_byte_clk1"; + pll_clk_names[1] = "pll_dsi_clk1"; + } + num_clk = ARRAY_SIZE(pll_clk_names); + DSI_DEBUG("DT clocks not found ,using fallback parent PLL\n"); + } else { + for (i = 0; i < num_clk; i++) + dsi_display_get_clock_name(display, dsi_clock_name, i, + &pll_clk_names[i]); + } + for (i = 0; i < num_clk; i++) { - dsi_display_get_clock_name(display, dsi_clock_name, i, - &clk_name); + clk_name = pll_clk_names[i]; DSI_DEBUG("clock name:%s\n", clk_name); @@ -4303,6 +4319,21 @@ static int dsi_display_parse_dt(struct dsi_display *display) dsi_phy_name = "qcom,dsi-sec-phy-num"; } + /*TODO : support split display with drm panel/bridge */ + if (!display->panel_node && display->has_drm_panel_or_bridge) { + struct dsi_display_ctrl *ctrl = &display->ctrl[0]; + + display->ctrl_count = 1; + + ctrl->ctrl_of_node = of_parse_phandle(of_node, "qcom,dsi-ctrl", 0); + of_node_put(ctrl->ctrl_of_node); + + ctrl->phy_of_node = of_parse_phandle(of_node, "qcom,dsi-phy", 0); + of_node_put(ctrl->phy_of_node); + + goto skip_phandle_count; + } + display->ctrl_count = dsi_display_get_phandle_count(display, dsi_ctrl_name); phy_count = dsi_display_get_phandle_count(display, dsi_phy_name); @@ -4338,6 +4369,8 @@ static int dsi_display_parse_dt(struct dsi_display *display) of_node_put(ctrl->phy_of_node); } +skip_phandle_count: + /* Parse TE data */ dsi_display_parse_te_data(display); @@ -4366,7 +4399,8 @@ static bool dsi_display_validate_panel_resources(struct dsi_display *display) if (!is_sim_panel(display)) { if (!display->panel->host_config.ext_bridge_mode && - !gpio_is_valid(display->panel->reset_config.reset_gpio)) { + !display->has_drm_panel_or_bridge && + !gpio_is_valid(display->panel->reset_config.reset_gpio)) { DSI_ERR("invalid reset gpio for the panel\n"); return false; } @@ -4443,11 +4477,13 @@ static int dsi_display_res_init(struct dsi_display *display) } display->panel = dsi_panel_get(&display->pdev->dev, - display->panel_node, - display->parser_node, - display->display_type, - display->cmdline_topology, - display->trusted_vm_env); + display->panel_node, + display->parser_node, + display->display_type, + display->cmdline_topology, + display->trusted_vm_env, + display->has_drm_panel_or_bridge); + if (IS_ERR_OR_NULL(display->panel)) { rc = PTR_ERR(display->panel); DSI_ERR("failed to get panel, rc=%d\n", rc); @@ -4468,11 +4504,28 @@ static int dsi_display_res_init(struct dsi_display *display) struct msm_dsi_phy *phy = display->ctrl[i].phy; struct dsi_host_common_cfg *host = &display->panel->host_config; - phy->cfg.force_clk_lane_hs = - display->panel->host_config.force_hs_clk_lane; - phy->cfg.phy_type = - display->panel->host_config.phy_type; + /* + * dsi_host_attach() derives force_hs_clk_lane from mode_flags and + * stores it in panel->host_config.force_hs_clk_lane so it is + * available here when the PHY is configured. + */ + if (!display->has_drm_panel_or_bridge) { + phy->cfg.force_clk_lane_hs = + display->panel->host_config.force_hs_clk_lane; + } + /* + * TODO: Add a new downstream DSI PHY DT property (e.g. + * "qcom,phy-type") and read it here so that platform overlays can + * specify CPHY/DPHY independently of the panel node. For now, + * hardcode to DPHY since nt37801 uses DPHY. + */ + if (display->has_drm_panel_or_bridge) { + phy->cfg.phy_type = DSI_PHY_TYPE_DPHY; + } else { + phy->cfg.phy_type = + display->panel->host_config.phy_type; + } /* * Parse the dynamic clock trim codes for PLL, for video mode panels that have * dynamic clock property set. @@ -5589,7 +5642,7 @@ static int _dsi_display_dev_init(struct dsi_display *display) return -EINVAL; } - if (!display->panel_node && !display->fw) + if (!display->panel_node && !display->fw && !display->has_drm_panel_or_bridge) return 0; mutex_lock(&display->display_lock); @@ -6316,6 +6369,28 @@ static struct platform_driver dsi_display_driver = { }, }; +/** + * dsi_display_has_drm_panel_or_bridge - detect upstream DRM panel/bridge + * @display: dsi_display instance + * @node: OF node of the DSI display controller + * + * Checks whether a remote device is connected to port@0 of the DSI + * controller node via the OF graph. + * + * Returns true if a remote endpoint is found, false otherwise. + */ +static bool dsi_display_has_drm_panel_or_bridge(struct dsi_display *display, + struct device_node *node) +{ + struct device_node *remote = of_graph_get_remote_node(node, 0, 0); + + if (!remote) + return false; + + of_node_put(remote); + return true; +} + static int dsi_display_prepare_dt_parser(struct dsi_display *display) { struct dsi_display_boot_param *boot_disp = display->boot_disp; @@ -6328,6 +6403,14 @@ static int dsi_display_prepare_dt_parser(struct dsi_display *display) bool prim = !strcmp(display->display_type, "primary"); int rc = 0; + if (dsi_display_has_drm_panel_or_bridge(display, node)) { + display->panel_node = NULL; + display->name = node->name; + display->has_drm_panel_or_bridge = true; + DSI_INFO("skipping DT panel lookup\n"); + return 0; + } + if (boot_disp->boot_disp_en) { if (!strcmp(boot_disp->name, "qcom,dsi_prop")) { /* use DSI firmware instead of device tree */ @@ -6406,7 +6489,7 @@ static int dsi_display_init(struct dsi_display *display) if (rc) { DSI_ERR("[%s] failed to enable vregs, rc=%d\n", display->panel->name, rc); - return rc; + goto vreg_fail; } } @@ -6427,11 +6510,27 @@ static int dsi_display_init(struct dsi_display *display) display->panel->post_power_enable_status = true; } - rc = component_add(&pdev->dev, &dsi_display_comp_ops); - if (rc) - DSI_ERR("component add failed, rc=%d\n", rc); + /* + * In case of external drm panel/bridge, the component_add() + * will be called from dsi_host_attach() as part of devm_mipi_dsi_attach() + * from the external drm panel/bridge driver. + */ + if (!display->has_drm_panel_or_bridge) { + rc = component_add(&pdev->dev, &dsi_display_comp_ops); + if (rc){ + DSI_ERR("component add failed, rc=%d\n", rc); + goto comp_add_fail; + } + DSI_DEBUG("component add success: %s\n", display->name); + } + + return rc; +comp_add_fail: + if (display->panel) + dsi_pwr_enable_regulator(&display->panel->power_info, false); +vreg_fail: + _dsi_display_dev_deinit(display); - DSI_DEBUG("component add success: %s\n", display->name); end: return rc; } diff --git a/msm/dsi/dsi_display.h b/msm/dsi/dsi_display.h index 3e60d8a..b7ad616 100644 --- a/msm/dsi/dsi_display.h +++ b/msm/dsi/dsi_display.h @@ -276,6 +276,8 @@ struct dsi_display { struct dsi_display_ext_bridge ext_bridge[MAX_DSI_CTRLS_PER_DISPLAY]; u32 ext_bridge_cnt; + bool has_drm_panel_or_bridge; + struct dsi_display_mode *modes; enum dsi_display_type type; diff --git a/msm/dsi/dsi_panel.c b/msm/dsi/dsi_panel.c index 7e77ad1..9676af5 100644 --- a/msm/dsi/dsi_panel.c +++ b/msm/dsi/dsi_panel.c @@ -4494,12 +4494,55 @@ static void dsi_panel_setup_vm_ops(struct dsi_panel *panel, bool trusted_vm_env) } } +/** + * dsi_panel_initialize_stub - initialize a minimal DSI panel stub + * @panel: pre-allocated, kzalloc-zeroed dsi_panel + * @parent: parent device (DSI host) + * @type: display type string (e.g. "primary") + * @trusted_vm_env: whether we are running in a trusted VM environment + * + * Returns the initialized panel pointer. + */ +static struct dsi_panel *dsi_panel_initialize_stub(struct dsi_panel *panel, + struct device *parent, + const char *type, + bool trusted_vm_env) +{ + dsi_panel_setup_vm_ops(panel, trusted_vm_env); + + panel->parent = parent; + panel->type = type; + panel->name = "panel-stub"; + panel->power_mode = SDE_MODE_DPMS_OFF; + panel->panel_type = DSI_DISPLAY_PANEL_TYPE_OLED; + + panel->bl_config.bl_scale = MAX_BL_SCALE_LEVEL; + panel->bl_config.bl_scale_sv = MAX_SV_BL_SCALE_LEVEL; + + panel->host_config.mdp_cmd_trigger = DSI_TRIGGER_SW; + panel->host_config.dma_cmd_trigger = DSI_TRIGGER_SW; + panel->host_config.te_mode = 1; + + panel->cmd_config.wr_mem_start = 0x2C; + panel->cmd_config.wr_mem_continue = 0x3C; + panel->cmd_config.insert_dcs_command = true; + + panel->esync_caps.emsync_switch_enabled = false; + + dsi_panel_update_util(panel, NULL); + + mutex_init(&panel->panel_lock); + + return panel; +} + struct dsi_panel *dsi_panel_get(struct device *parent, struct device_node *of_node, struct device_node *parser_node, const char *type, int topology_override, - bool trusted_vm_env) + bool trusted_vm_env, + bool has_drm_panel_or_bridge) { struct dsi_panel *panel; struct dsi_parser_utils *utils; @@ -4510,6 +4553,9 @@ struct dsi_panel *dsi_panel_get(struct device *parent, if (!panel) return ERR_PTR(-ENOMEM); + if (has_drm_panel_or_bridge) + return dsi_panel_initialize_stub(panel, parent, type, trusted_vm_env); + dsi_panel_setup_vm_ops(panel, trusted_vm_env); panel->panel_of_node = of_node; diff --git a/msm/dsi/dsi_panel.h b/msm/dsi/dsi_panel.h index b022c0a..6575971 100644 --- a/msm/dsi/dsi_panel.h +++ b/msm/dsi/dsi_panel.h @@ -253,7 +253,8 @@ struct dsi_panel { struct device_node *rgb_right_led_node; struct mutex panel_lock; - struct drm_panel drm_panel; + struct drm_panel *drm_panel; + bool has_drm_panel; struct mipi_dsi_host *host; struct device *parent; @@ -356,7 +357,8 @@ struct dsi_panel *dsi_panel_get(struct device *parent, struct device_node *parser_node, const char *type, int topology_override, - bool trusted_vm_env); + bool trusted_vm_env, + bool has_drm_panel_or_bridge); void dsi_panel_put(struct dsi_panel *panel); diff --git a/msm/dsi/dsi_pwr.c b/msm/dsi/dsi_pwr.c index 340fe3f..66e9bfa 100644 --- a/msm/dsi/dsi_pwr.c +++ b/msm/dsi/dsi_pwr.c @@ -408,7 +408,7 @@ int dsi_pwr_enable_regulator(struct dsi_regulator_info *regs, bool enable) { int rc = 0; - if (regs->count == 0) { + if (!regs || regs->count == 0) { DSI_DEBUG("No valid regulators to enable\n"); return 0; } From a1e4bffe6529f5ea5827f91e393636a1359e85f1 Mon Sep 17 00:00:00 2001 From: Arpit Saini Date: Tue, 24 Mar 2026 12:53:13 +0530 Subject: [PATCH 3/9] disp: msm: dsi: Move component_add/del to dsi_host_ops Move component_add() from dsi_display_bind() to dsi_host_attach() and component_del() to dsi_host_detach(). This enables support for DRM panel/bridge by properly managing component lifecycle during DSI host attachment/detachment Change-Id: Id8d6b69a696201a8dcf776eaed7dbe9c621dff95 Signed-off-by: Arpit Saini --- msm/dsi/dsi_display.c | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/msm/dsi/dsi_display.c b/msm/dsi/dsi_display.c index 5616f94..c83a24c 100644 --- a/msm/dsi/dsi_display.c +++ b/msm/dsi/dsi_display.c @@ -3463,15 +3463,41 @@ int dsi_display_phy_sw_reset(struct dsi_display *display) return rc; } +static int dsi_display_bind(struct device *dev, struct device *master, void *data); +static void dsi_display_unbind(struct device *dev, struct device *master, void *data); + +static const struct component_ops dsi_display_comp_ops = { + .bind = dsi_display_bind, + .unbind = dsi_display_unbind, +}; + static int dsi_host_attach(struct mipi_dsi_host *host, struct mipi_dsi_device *dsi) { + int rc = 0; + struct dsi_display *display; + display = to_dsi_display(host); + + struct platform_device *pdev = display->pdev; + + rc = component_add(&pdev->dev, &dsi_display_comp_ops); + if (rc) + DSI_ERR("component add failed, rc=%d\n", rc); + + DSI_DEBUG("component add success: %s\n", display->name); return 0; } static int dsi_host_detach(struct mipi_dsi_host *host, struct mipi_dsi_device *dsi) { + struct dsi_display *display; + display = to_dsi_display(host); + + struct platform_device *pdev = display->pdev; + + component_del(&pdev->dev, &dsi_display_comp_ops); + return 0; } @@ -6354,11 +6380,6 @@ static void dsi_display_unbind(struct device *dev, mutex_unlock(&display->display_lock); } -static const struct component_ops dsi_display_comp_ops = { - .bind = dsi_display_bind, - .unbind = dsi_display_unbind, -}; - static struct platform_driver dsi_display_driver = { .probe = dsi_display_dev_probe, .remove = dsi_display_dev_remove, From a26b859a6a88b3d8c8dc3f5988ca45d6c26366df Mon Sep 17 00:00:00 2001 From: Arpit Saini Date: Wed, 6 May 2026 12:47:30 +0530 Subject: [PATCH 4/9] disp: msm: dsi: Remove panel DT dependency to support drm panel Add DT-based discovery of drm_panel and allow bind without legacy panel data when a DRM panel/bridge is present. Skip legacy GPIO/backlight handling when using DRM panel/bridge. Change-Id: Iaf052584b2732cb13612d0924068850bb9d9925c Signed-off-by: Arpit Saini --- msm/dsi/dsi_display.c | 72 +++++++++++++++++++++++++++++++++++++------ msm/dsi/dsi_display.h | 8 +++++ msm/dsi/dsi_panel.c | 26 +++++++++++++--- 3 files changed, 92 insertions(+), 14 deletions(-) diff --git a/msm/dsi/dsi_display.c b/msm/dsi/dsi_display.c index c83a24c..67ba82f 100644 --- a/msm/dsi/dsi_display.c +++ b/msm/dsi/dsi_display.c @@ -6093,6 +6093,34 @@ static int dsi_display_get_display_node_count(struct dsi_display *display) return dsi_display_node_count; } +struct drm_panel* dsi_display_get_drm_panel_from_dt(struct dsi_display *display) +{ + struct device_node *np; + struct device_node *panel_np; + struct drm_panel *panel; + + if (!display || !display->pdev) + return ERR_PTR(-EINVAL); + + np = display->pdev->dev.of_node; + if (!np) + return ERR_PTR(-ENODEV); + + panel_np = of_graph_get_remote_node(np, 0 , 0); + if (!panel_np) + return ERR_PTR(-ENODEV); + + panel = of_drm_find_panel(panel_np); + + of_node_put(panel_np); + + if (IS_ERR(panel)){ + DSI_DEBUG("DRM panel not found\n"); + } + + return panel; +} + /** * dsi_display_bind - bind dsi device with controlling device * @dev: Pointer to base of platform device @@ -6133,8 +6161,10 @@ static int dsi_display_bind(struct device *dev, drm, display); return -EINVAL; } - if (!display->panel_node && !display->fw) + + if (!display->panel_node && !display->fw && !display->has_drm_panel_or_bridge) { return 0; + } /* defer bind if ext bridge driver is not loaded */ if (display->panel && display->panel->host_config.ext_bridge_mode) { @@ -6257,6 +6287,23 @@ static int dsi_display_bind(struct device *dev, dsi_display_update_byte_intf_div(display); + if (display->has_drm_panel_or_bridge && + display->panel && !display->panel->host_config.ext_bridge_mode) { + struct drm_panel *panel; + + panel = dsi_display_get_drm_panel_from_dt(display); + if (!IS_ERR_OR_NULL(panel)) { + display->panel->drm_panel = panel; + display->panel->has_drm_panel = true; + + /* Update stub name to the real upstream driver name */ + if (panel->dev && panel->dev->driver){ + display->name = panel->dev->driver->name; + } + DSI_INFO("[%s] bind with DRM panel\n", display->name); + } + } + rc = dsi_panel_drv_init(display->panel, &display->host); if (rc) { if (rc != -EPROBE_DEFER) @@ -6342,7 +6389,7 @@ static void dsi_display_unbind(struct device *dev, } display = platform_get_drvdata(pdev); - if (!display || !display->panel_node) { + if (!display || (!display->panel_node && !display->has_drm_panel_or_bridge)) { DSI_ERR("invalid display\n"); return; } @@ -6664,15 +6711,18 @@ int dsi_display_dev_remove(struct platform_device *pdev) } display = platform_get_drvdata(pdev); - if (!display || !display->panel_node) { - DSI_ERR("invalid param, display %pK, display panel node %pK\n", - display, display ? display->panel_node : NULL); + if (!display || (!display->panel_node && !display->has_drm_panel_or_bridge)) { + DSI_ERR("invalid param, display %pK, panel_node %pK has_drm_panel_or_bridge=%d\n", + display, + display ? display->panel_node : NULL, + display ? display->has_drm_panel_or_bridge : 0); rc = -EINVAL; goto end; } - /* decrement ref count */ - of_node_put(display->panel_node); + /* decrement ref count only when a DT panel node was used */ + if (display->panel_node) + of_node_put(display->panel_node); if (display->post_cmd_tx_workq) { flush_workqueue(display->post_cmd_tx_workq); @@ -6718,10 +6768,12 @@ int dsi_display_get_num_of_displays(struct drm_device *dev) continue; if ((display && display->panel_node) || - (display && display->fw)) + (display && display->fw) || + (display && display->has_drm_panel_or_bridge)) count++; } + DSI_DEBUG("display count is %d\n", count); return count; } @@ -6745,10 +6797,12 @@ int dsi_display_get_active_displays(struct drm_device *dev, continue; if ((display && display->panel_node) || - (display && display->fw)) + (display && display->fw) || + (display && display->has_drm_panel_or_bridge)) display_array[count++] = display; } + DSI_DEBUG("active display count is %d\n", count); return count; } diff --git a/msm/dsi/dsi_display.h b/msm/dsi/dsi_display.h index b7ad616..27ffc24 100644 --- a/msm/dsi/dsi_display.h +++ b/msm/dsi/dsi_display.h @@ -1100,4 +1100,12 @@ int dsi_display_ctl_post_transition(void *display); int dsi_display_get_phandle_count(struct dsi_display *display, const char *propname); +/** + * dsi_display_get_drm_panel_from_dt() - get upstream drm_panel from DT + * @display: Handle to display + * + * return: pointer to drm_panel or ERR_PTR on failure. + */ +struct drm_panel *dsi_display_get_drm_panel_from_dt(struct dsi_display *display); + #endif /* _DSI_DISPLAY_H_ */ diff --git a/msm/dsi/dsi_panel.c b/msm/dsi/dsi_panel.c index 9676af5..79acf75 100644 --- a/msm/dsi/dsi_panel.c +++ b/msm/dsi/dsi_panel.c @@ -4673,23 +4673,35 @@ struct dsi_panel *dsi_panel_get(struct device *parent, } panel->power_mode = SDE_MODE_DPMS_OFF; - drm_panel_init(&panel->drm_panel, &panel->mipi_device.dev, - NULL, DRM_MODE_CONNECTOR_DSI); - panel->mipi_device.dev.of_node = of_node; - drm_panel_add(&panel->drm_panel); + panel->drm_panel = kzalloc(sizeof(*panel->drm_panel), GFP_KERNEL); + if (!panel->drm_panel) { + rc = -ENOMEM; + goto error; + } + drm_panel_init(panel->drm_panel, parent, NULL, DRM_MODE_CONNECTOR_DSI); + drm_panel_add(panel->drm_panel); mutex_init(&panel->panel_lock); return panel; error: + if (panel->drm_panel) { + drm_panel_remove(panel->drm_panel); + kfree(panel->drm_panel); + } kfree(panel); return ERR_PTR(rc); } void dsi_panel_put(struct dsi_panel *panel) { - drm_panel_remove(&panel->drm_panel); + if (!panel->has_drm_panel) { + drm_panel_remove(panel->drm_panel); + kfree(panel->drm_panel); + } + + panel->drm_panel = NULL; /* free resources allocated for ESD check */ dsi_panel_esd_config_deinit(&panel->esd_config); @@ -4709,6 +4721,10 @@ int dsi_panel_drv_init(struct dsi_panel *panel, return -EINVAL; } + if (panel->has_drm_panel) { + return rc; + } + mutex_lock(&panel->panel_lock); dev = &panel->mipi_device; From 5560a515e7dfa23085ea4a1495f64b10ce54af80 Mon Sep 17 00:00:00 2001 From: Arpit Saini Date: Tue, 5 May 2026 17:57:22 +0530 Subject: [PATCH 5/9] disp: msm: dsi: Store drm panel DSI config and mipi device Store the drm panel's DSI config (lanes, format, mode_flags,dsc) into panel->mipi_device so dsi_display_bind() can use it to populate DSC params and panel config. Also store the host pointer so panel->mipi_device is a fully functional mipi_dsi_device for DCS command sending. Change-Id: I11ec3e09898040fd773e645892e98e1d79625821 Signed-off-by: Arpit Saini --- msm/dsi/dsi_display.c | 85 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 80 insertions(+), 5 deletions(-) diff --git a/msm/dsi/dsi_display.c b/msm/dsi/dsi_display.c index 67ba82f..7b4e34b 100644 --- a/msm/dsi/dsi_display.c +++ b/msm/dsi/dsi_display.c @@ -3471,20 +3471,95 @@ static const struct component_ops dsi_display_comp_ops = { .unbind = dsi_display_unbind, }; +/** + * dsi_panel_update_from_mipi_dsi_device - Populate dsi_panel stub from the + * mipi_dsi_device config. + * + * @panel: stub panel created by dsi_panel_get() with has_drm_panel_or_bridge + * @dsi: mipi_dsi_device populated by the panel driver in probe() + */ +static int dsi_panel_update_from_mipi_dsi_device(struct dsi_panel *panel, + const struct mipi_dsi_device *dsi) +{ + if (!panel || !dsi) { + DSI_ERR("Invalid params\n"); + return -EINVAL; + } + + struct dsi_host_common_cfg *host = &panel->host_config; + + /* store upstream config fields into panel->mipi_device */ + panel->mipi_device.lanes = dsi->lanes; + panel->mipi_device.format = dsi->format; + panel->mipi_device.mode_flags = dsi->mode_flags; + panel->mipi_device.dsc = dsi->dsc; + panel->mipi_device.host = dsi->host; + + DSI_INFO("[%s] applying mipi_dsi_device config " + "lanes=%d format=%d mode_flags=0x%lx\n", + panel->name, dsi->lanes, dsi->format, dsi->mode_flags); + + /* data lanes */ + host->data_lanes = 0; + if (dsi->lanes > 0) + host->data_lanes |= DSI_DATA_LANE_0; + if (dsi->lanes > 1) + host->data_lanes |= DSI_DATA_LANE_1; + if (dsi->lanes > 2) + host->data_lanes |= DSI_DATA_LANE_2; + if (dsi->lanes > 3) + host->data_lanes |= DSI_DATA_LANE_3; + host->num_data_lanes = dsi->lanes; + + /* pixel format */ + switch (dsi->format) { + case MIPI_DSI_FMT_RGB888: + host->dst_format = DSI_PIXEL_FORMAT_RGB888; + break; + case MIPI_DSI_FMT_RGB666: + host->dst_format = DSI_PIXEL_FORMAT_RGB666_LOOSE; + break; + case MIPI_DSI_FMT_RGB666_PACKED: + host->dst_format = DSI_PIXEL_FORMAT_RGB666; + break; + case MIPI_DSI_FMT_RGB565: + default: + host->dst_format = DSI_PIXEL_FORMAT_RGB565; + break; + } + + /* + * Panel mode: video if MIPI_DSI_MODE_VIDEO is set, otherwise cmd mode. + */ + if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO) + panel->panel_mode = DSI_OP_VIDEO_MODE; + else + panel->panel_mode = DSI_OP_CMD_MODE; + + /* + * Clock lane HS: stay continuous unless MIPI_DSI_CLOCK_NON_CONTINUOUS + * is set by the upstream driver. + */ + host->force_hs_clk_lane = !(dsi->mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS); + return 0; +} + static int dsi_host_attach(struct mipi_dsi_host *host, struct mipi_dsi_device *dsi) { + struct dsi_display *display = to_dsi_display(host); + struct platform_device *pdev = display->pdev; int rc = 0; - struct dsi_display *display; - display = to_dsi_display(host); - struct platform_device *pdev = display->pdev; + rc = dsi_panel_update_from_mipi_dsi_device(display->panel, dsi); + if (rc) + DSI_ERR("Invalid Params rc=%d\n", rc); rc = component_add(&pdev->dev, &dsi_display_comp_ops); if (rc) - DSI_ERR("component add failed, rc=%d\n", rc); + DSI_ERR("component add failed rc=%d\n", rc); - DSI_DEBUG("component add success: %s\n", display->name); + DSI_DEBUG("component add success\n"); return 0; } From 968d09f67463b29696e55b99262799e6479faaae Mon Sep 17 00:00:00 2001 From: Arpit Saini Date: Sat, 6 Jun 2026 17:27:49 +0530 Subject: [PATCH 6/9] disp: msm: dsi: Support mode enumeration support from drm panel For DRM panel, use drm_panel_get_modes() to discover modes. Populate dsi_display_mode priv_info (DSC config, topology,transfer time) via dsi_display_populate_modes_from_drm_panel(). Change-Id: Ifb37b45343754739075b3fd89b5fe8b69e5bc099 Signed-off-by: Arpit Saini --- msm/dsi/dsi_display.c | 178 +++++++++++++++++++++++++++++++++++++++++- msm/dsi/dsi_display.h | 14 ++++ msm/dsi/dsi_drm.c | 74 ++++++++++++++++++ 3 files changed, 265 insertions(+), 1 deletion(-) diff --git a/msm/dsi/dsi_display.c b/msm/dsi/dsi_display.c index 7b4e34b..984e14b 100644 --- a/msm/dsi/dsi_display.c +++ b/msm/dsi/dsi_display.c @@ -18,6 +18,8 @@ #include "msm_drv.h" #include "hfi_msm_drv.h" #include "sde_connector.h" +#include "sde_dsc_helper.h" +#include "hfi_connector.h" #include "msm_mmu.h" #include "dsi_display.h" #include "dsi_panel.h" @@ -7298,7 +7300,7 @@ struct drm_panel *dsi_display_get_drm_panel(struct dsi_display *display) return NULL; } - return &display->panel->drm_panel; + return display->panel->drm_panel; } bool dsi_display_has_dsc_switch_support(struct dsi_display *display) @@ -8220,6 +8222,175 @@ int dsi_display_get_modes(struct dsi_display *display, return rc; } +/** + * dsi_display_populate_modes_from_drm_panel - Allocate and populate + * display->modes[] from an DRM panel. Converts drm_display_mode + * timing, derives DSC config from mipi_dsi_device::dsc, and computes + * transfer time. Called once from dsi_display_get_drm_modes(). + * + * @display: dsi_display handle + * @drm_mode: mode reported by the drm panel's get_modes() callback + * @dsi: mipi_dsi_device carrying DSC config from panel probe + * + * Return: 0 on success, negative errno on failure. + */ +int dsi_display_populate_modes_from_drm_panel(struct dsi_display *display, + const struct drm_display_mode *drm_mode, + const struct mipi_dsi_device *dsi) +{ + struct dsi_display_mode *mode; + struct dsi_display_mode_priv_info *priv_info; + int rc = 0; + + if (!display || !display->panel || !drm_mode || !dsi) { + DSI_ERR("%s: invalid display or panel\n", __func__); + return -EINVAL; + } + + if (display->modes) { + DSI_DEBUG("%s: modes already populated, skipping\n", __func__); + return 0; + } + + if (!display->ctrl[0].ctrl) { + DSI_ERR("%s: ctrl not initialized\n", __func__); + return -EINVAL; + } + + mode = kzalloc(sizeof(*mode), GFP_KERNEL); + if (!mode) + return -ENOMEM; + + priv_info = kzalloc(sizeof(*priv_info), GFP_KERNEL); + if (!priv_info) { + kfree(mode); + return -ENOMEM; + } + + mode->priv_info = priv_info; + + /* Timing: convert drm_display_mode to dsi_mode_info (same as convert_to_dsi_mode). */ + mode->timing.h_active = drm_mode->hdisplay; + mode->timing.h_back_porch = drm_mode->htotal - drm_mode->hsync_end; + mode->timing.h_sync_width = drm_mode->htotal - + (drm_mode->hsync_start + mode->timing.h_back_porch); + mode->timing.h_front_porch = drm_mode->hsync_start - drm_mode->hdisplay; + mode->timing.h_skew = drm_mode->hskew; + + mode->timing.v_active = drm_mode->vdisplay; + mode->timing.v_back_porch = drm_mode->vtotal - drm_mode->vsync_end; + mode->timing.v_sync_width = drm_mode->vtotal - + (drm_mode->vsync_start + mode->timing.v_back_porch); + mode->timing.v_front_porch = drm_mode->vsync_start - drm_mode->vdisplay; + + mode->timing.h_sync_polarity = !!(drm_mode->flags & DRM_MODE_FLAG_PHSYNC); + mode->timing.v_sync_polarity = !!(drm_mode->flags & DRM_MODE_FLAG_PVSYNC); + mode->timing.refresh_rate = drm_mode_vrefresh(drm_mode); + + /* Read widebus support from HW before DSC block which uses it. */ + priv_info->widebus_support = display->ctrl[0].ctrl->hw.widebus_support; + + /* DSC: copy geometry from the drm panel's mipi_dsi_device::dsc. */ + if (dsi->dsc) { + mode->timing.dsc_enabled = true; + priv_info->dsc_enabled = true; + + priv_info->dsc.config = *dsi->dsc; + priv_info->dsc.config.pic_width = mode->timing.h_active; + priv_info->dsc.config.pic_height = mode->timing.v_active; + + /* slice_per_pkt: upstream also hardcoded this. */ + priv_info->dsc.slice_per_pkt = 1; + + /* scr_rev=0 selects the DSC_V11_8BPC_8BPP RC table + * in sde_dsc_populate_dsc_config. + */ + priv_info->dsc.scr_rev = 0; + + priv_info->dsc.rc_override_v1 = true; + + priv_info->dsc.chroma_format = MSM_CHROMA_444; + priv_info->dsc.source_color_space = MSM_RGB; + + /* Compute RC parameters (same call as dsi_panel_parse_dsc_params). */ + rc = sde_dsc_populate_dsc_config(&priv_info->dsc.config, + priv_info->dsc.scr_rev); + if (rc) { + DSI_ERR("[%s] sde_dsc_populate_dsc_config failed rc=%d\n", + display->name, rc); + kfree(priv_info); + kfree(mode); + return rc; + } + + /* Compute pkt_per_line, bytes_per_pkt, eol_byte_num (same as DT path). */ + rc = sde_dsc_populate_dsc_private_params(&priv_info->dsc, + mode->timing.h_active, + priv_info->widebus_support); + if (rc) { + DSI_ERR("[%s] sde_dsc_populate_dsc_private_params failed rc=%d\n", + display->name, rc); + kfree(priv_info); + kfree(mode); + return rc; + } + + mode->timing.dsc = &priv_info->dsc; + + /* TODO:remove the hardcoded topology, derive dynamically based on DPU catalog.*/ + priv_info->topology.num_lm = 2; + priv_info->topology.num_enc = 2; + priv_info->topology.num_intf = 1; + priv_info->topology.comp_type = MSM_DISPLAY_COMPRESSION_DSC; + + /* pclk_scale: DSC compression ratio + * (same formula as dsi_panel_parse_dsc_params). + */ + priv_info->pclk_scale.numer = dsi->dsc->bits_per_pixel >> 4; + priv_info->pclk_scale.denom = 3 * dsi->dsc->bits_per_component; + mode->timing.pclk_scale = priv_info->pclk_scale; + } else { + /* Non-DSC: single LM, single INTF, no compression. */ + /* TODO:remove the hardcoded topology, derive dynamically based on DPU catalog.*/ + priv_info->topology.num_lm = 1; + priv_info->topology.num_enc = 0; + priv_info->topology.num_intf = 1; + } + + /* + * pixel_clk_khz is overwritten by dsi_panel_calc_dsi_transfer_time below. + */ + mode->panel_mode_caps = display->panel->panel_mode; + mode->pixel_format_caps = display->panel->host_config.dst_format; + mode->bpp = dsi_pixel_format_to_bpp(mode->pixel_format_caps); + mode->mode_idx = 0; + mode->is_preferred = true; + + dsi_panel_calc_dsi_transfer_time(&display->panel->host_config, + mode, + display->ctrl[0].ctrl->frame_threshold_time_us); + + priv_info->dsi_transfer_time_us = mode->timing.dsi_transfer_time_us; + priv_info->min_dsi_clk_hz = mode->timing.min_dsi_clk_hz; + priv_info->mdp_transfer_time_us = mode->timing.mdp_transfer_time_us; + + mutex_lock(&display->display_lock); + display->modes = mode; + mutex_unlock(&display->display_lock); + + DSI_DEBUG("[%s] drm panel mode: %ux%u@%uHz" + "pixel_clk=%u kHz DSC=%d topology=<%u %u %u>\n", + display->name, + mode->timing.h_active, mode->timing.v_active, + mode->timing.refresh_rate, mode->pixel_clk_khz, + priv_info->dsc_enabled, + priv_info->topology.num_lm, + priv_info->topology.num_enc, + priv_info->topology.num_intf); + + return 0; +} + int dsi_display_get_panel_vfp(void *dsi_display, int h_active, int v_active) { @@ -8284,6 +8455,11 @@ int dsi_display_get_default_lms(void *dsi_display, u32 *num_lm) *num_lm = 0; + if (display->panel && display->panel->has_drm_panel) { + *num_lm = display->panel->lm_count; + return 0; + } + mutex_lock(&display->display_lock); count = display->panel->num_display_modes; mutex_unlock(&display->display_lock); diff --git a/msm/dsi/dsi_display.h b/msm/dsi/dsi_display.h index 27ffc24..eb7dd81 100644 --- a/msm/dsi/dsi_display.h +++ b/msm/dsi/dsi_display.h @@ -468,6 +468,20 @@ int dsi_display_get_mode_count(struct dsi_display *display, u32 *count); int dsi_display_get_modes(struct dsi_display *display, struct dsi_display_mode **modes); +/** + * dsi_display_populate_modes_from_drm_panel - Populate display->modes[] for the + * DRM panel using the drm_display_mode + * and mipi_dsi_device DSC config from the panel driver. + * + * @display: Handle to display. + * @drm_mode: drm_display_mode from the panel's get_modes() callback. + * @dsi: mipi_dsi_device whose dsc pointer carries the DSC config. + * Return: 0 on success, negative error code on failure. + */ +int dsi_display_populate_modes_from_drm_panel(struct dsi_display *display, + const struct drm_display_mode *drm_mode, + const struct mipi_dsi_device *dsi); + /** * dsi_display_put_mode() - free up mode created for the display * @display: Handle to display. diff --git a/msm/dsi/dsi_drm.c b/msm/dsi/dsi_drm.c index bab9bbf..bddad36 100644 --- a/msm/dsi/dsi_drm.c +++ b/msm/dsi/dsi_drm.c @@ -10,6 +10,8 @@ #include #include +#include +#include #include "msm_kms.h" #include "sde_connector.h" #include "dsi_drm.h" @@ -17,6 +19,7 @@ #include "sde_dbg.h" #include "msm_drv.h" #include "sde_encoder.h" +#include "sde_dsc_helper.h" #define to_dsi_bridge(x) container_of((x), struct dsi_bridge, base) #define to_dsi_state(x) container_of((x), struct dsi_connector_state, base) @@ -1276,6 +1279,72 @@ static void dsi_drm_update_checksum(struct edid *edid) edid->checksum = 0x100 - (sum & 0xFF); } +/** + * dsi_display_get_drm_modes - get modes from an DRM panel and + * populate display->modes[] with priv_info. + * @display: dsi_display whose panel has has_drm_panel=true + * @connector: DRM connector to add the probed modes to + * + * Called from dsi_connector_get_modes() when the panel is + * DRM panel. Enumerates modes via drm_panel_get_modes(). + * + * Return: number of modes added, or 0 on failure. + */ +static int dsi_display_get_drm_modes(struct dsi_display *display, + struct drm_connector *connector) +{ + struct drm_panel *panel; + struct drm_display_mode *drm_mode; + struct mipi_dsi_device *dsi; + int count = 0; + int rc; + + if (!display || !display->panel || !connector) { + DSI_ERR("invalid params\n"); + return 0; + } + + panel = display->panel->drm_panel; + if (!panel) { + DSI_ERR("[%s] has_drm_panel set but drm_panel pointer is NULL\n", + display->name); + return 0; + } + + count = drm_panel_get_modes(panel, connector); + if (count <= 0) { + DSI_ERR("[%s] drm_panel_get_modes returned %d\n", + display->name, count); + return 0; + } + + display->panel->num_display_modes = count; + display->panel->num_timing_nodes = count; + + dsi = &display->panel->mipi_device; + drm_mode = list_first_entry_or_null(&connector->probed_modes, + struct drm_display_mode, head); + + display->panel->phy_props.panel_width_mm = connector->display_info.width_mm; + display->panel->phy_props.panel_height_mm = connector->display_info.height_mm; + + /* record pic dimensions for DSC; RC tables populated in dsi_bridge_pre_enable */ + if (drm_mode && dsi->dsc) { + dsi->dsc->pic_width = drm_mode->hdisplay; + dsi->dsc->pic_height = drm_mode->vdisplay; + } + + if (drm_mode) { + rc = dsi_display_populate_modes_from_drm_panel(display, drm_mode, dsi); + if (rc) { + DSI_ERR("[%s] failed to populate display modes rc=%d\n", + display->name, rc); + return 0; + } + } + return count; +} + int dsi_connector_get_modes(struct drm_connector *connector, void *data, const struct msm_resource_caps_info *avail_res) { @@ -1300,6 +1369,11 @@ int dsi_connector_get_modes(struct drm_connector *connector, void *data, memcpy(&edid, edid_buf, edid_size); + if (display->panel->has_drm_panel) { + count = dsi_display_get_drm_modes(display, connector); + goto end; + } + rc = dsi_display_get_mode_count(display, &count); if (rc) { DSI_ERR("failed to get num of modes, rc=%d\n", rc); From 8a33643fc56dba4f9d0482c59f16c91f0cf103f8 Mon Sep 17 00:00:00 2001 From: Arpit Saini Date: Sat, 6 Jun 2026 17:53:51 +0530 Subject: [PATCH 7/9] disp: msm: dsi: Add drm_panel support in bridge and connector ops DRM panels rely on the panel driver and DRM core for mode validation, instead of DT-based timing nodes. Guard DSI bridge and connector ops for drm_panel. Change-Id: Id0571e1173363e4560c4d794e6037d611864be0d Signed-off-by: Arpit Saini --- msm/dsi/dsi_display.c | 4 +++ msm/dsi/dsi_drm.c | 58 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/msm/dsi/dsi_display.c b/msm/dsi/dsi_display.c index 984e14b..b8dfd5f 100644 --- a/msm/dsi/dsi_display.c +++ b/msm/dsi/dsi_display.c @@ -5934,6 +5934,10 @@ int dsi_display_splash_res_cleanup(struct dsi_display *display) { int rc = 0; + if (display->panel && display->panel->has_drm_panel) { + return 0; + } + if (!display->is_cont_splash_enabled) return 0; diff --git a/msm/dsi/dsi_drm.c b/msm/dsi/dsi_drm.c index bddad36..4c4f340 100644 --- a/msm/dsi/dsi_drm.c +++ b/msm/dsi/dsi_drm.c @@ -229,6 +229,24 @@ static void dsi_bridge_pre_enable(struct drm_bridge *bridge) if (bridge->encoder->crtc->state->active_changed) atomic_set(&c_bridge->display->panel->esd_recovery_pending, 0); + /* + * For DRM panels with DSC, populate RC tables now that + * pic_width/pic_height were set during mode enumeration. + */ + if (display->panel && display->panel->has_drm_panel) { + struct mipi_dsi_device *dsi = &display->panel->mipi_device; + + if (dsi->dsc) { + dsi->dsc->convert_rgb = 1; + rc = sde_dsc_populate_dsc_config(dsi->dsc, 0); + if (rc) { + DSI_ERR("[%s] sde_dsc_populate_dsc_config failed rc=%d\n", + display->name, rc); + return; + } + } + } + /* By this point mode should have been validated through mode_fixup */ rc = dsi_display_set_mode(c_bridge->display, &(c_bridge->dsi_mode), 0x0); @@ -551,6 +569,20 @@ static bool dsi_bridge_mode_fixup(struct drm_bridge *bridge, return false; } + if (display->panel && display->panel->has_drm_panel) { + *adjusted_mode = *mode; + convert_to_dsi_mode(mode, &dsi_mode); + rc = dsi_display_find_mode(display, &dsi_mode, NULL, &panel_dsi_mode); + if (rc) { + DSI_ERR("[%s] drm_panel: requested mode not found, rc=%d\n", + display->name, rc); + return false; + } + dsi_convert_to_msm_mode(panel_dsi_mode, &conn_state->msm_mode); + conn_state->msm_mode.base = adjusted_mode; + return true; + } + /* * if no timing defined in panel, it must be external mode * and we'll use empty priv info to populate the mode @@ -666,6 +698,11 @@ int dsi_conn_get_lm_from_mode(void *display, const struct drm_display_mode *drm_ return rc; } + /*TODO: remove the hardcoded topology, derive dynamically based on DPU catalog.*/ + if (dsi_display->panel && dsi_display->panel->has_drm_panel){ + return dsi_display->panel->lm_count; + } + convert_to_dsi_mode(drm_mode, &dsi_mode); drm_to_dsi_update_overlap(dsi_display, &dsi_mode); @@ -693,6 +730,13 @@ int dsi_conn_get_mode_info(struct drm_connector *connector, if (!drm_mode || !mode_info) return -EINVAL; + if (dsi_display && dsi_display->panel && + dsi_display->panel->has_drm_panel && !dsi_display->modes) { + DSI_ERR("[%s] drm_panel modes not yet populated\n", + dsi_display->name); + return -EINVAL; + } + convert_to_dsi_mode(drm_mode, &partial_dsi_mode); drm_to_dsi_update_overlap(dsi_display, &partial_dsi_mode); rc = dsi_display_find_mode(dsi_display, &partial_dsi_mode, sub_mode, &dsi_mode); @@ -1055,6 +1099,11 @@ void dsi_conn_set_submode_blob_info(struct drm_connector *conn, return; } + if (dsi_display && dsi_display->panel && + dsi_display->panel->has_drm_panel) { + return; + } + convert_to_dsi_mode(drm_mode, &partial_dsi_mode); drm_to_dsi_update_overlap(dsi_display, &partial_dsi_mode); @@ -1445,6 +1494,7 @@ enum drm_mode_status dsi_conn_mode_valid(struct drm_connector *connector, struct drm_display_mode *mode, void *display, const struct msm_resource_caps_info *avail_res) { + struct dsi_display *dsi_display = display; struct dsi_display_mode dsi_mode; struct dsi_display_mode *full_dsi_mode = NULL; struct sde_connector_state *conn_state; @@ -1455,6 +1505,10 @@ enum drm_mode_status dsi_conn_mode_valid(struct drm_connector *connector, return MODE_ERROR; } + if (dsi_display && dsi_display->panel && dsi_display->panel->has_drm_panel){ + return MODE_OK; + } + convert_to_dsi_mode(mode, &dsi_mode); drm_to_dsi_update_overlap(display, &dsi_mode); @@ -1711,6 +1765,10 @@ void dsi_conn_set_allowed_mode_switch(struct drm_connector *connector, return; } + if(disp->panel->has_drm_panel){ + return; + } + panel = disp->panel; list_for_each_entry(drm_mode, &connector->modes, head) mode_count++; From d1e57d15cbefd1e77bc075eeaca76a55884b2ae6 Mon Sep 17 00:00:00 2001 From: Arpit Saini Date: Tue, 9 Jun 2026 17:53:09 +0530 Subject: [PATCH 8/9] disp: msm: dsi: Add drm_panel lifecycle ops support For DRM panels, delegate panel enable/disable lifecycle to drm_panel prepare/enable/disable/unprepare() ops. Legacy DT-based panels are unaffected. Change-Id: I4d791ecf433c4319fb6aab664a525caad5ba04fd Signed-off-by: Arpit Saini --- msm/dsi/dsi_display.c | 4 +++ msm/dsi/dsi_panel.c | 73 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/msm/dsi/dsi_display.c b/msm/dsi/dsi_display.c index b8dfd5f..e3d9cb1 100644 --- a/msm/dsi/dsi_display.c +++ b/msm/dsi/dsi_display.c @@ -9821,6 +9821,10 @@ static int dsi_display_set_roi(struct dsi_display *display, if (!display || !rois || !display->panel) return -EINVAL; + if (display->panel->has_drm_panel){ + return 0; + } + cur_mode = display->panel->cur_mode; if (!cur_mode) return 0; diff --git a/msm/dsi/dsi_panel.c b/msm/dsi/dsi_panel.c index 79acf75..b7c411c 100644 --- a/msm/dsi/dsi_panel.c +++ b/msm/dsi/dsi_panel.c @@ -5400,6 +5400,10 @@ int dsi_panel_pre_prepare(struct dsi_panel *panel) return -EINVAL; } + if (panel->has_drm_panel) { + return 0; + } + mutex_lock(&panel->panel_lock); /* If LP11_INIT is set, panel will be powered up during prepare() */ @@ -5428,6 +5432,9 @@ int dsi_panel_update_pps(struct dsi_panel *panel) return -EINVAL; } + if (panel->has_drm_panel) + return 0; + mutex_lock(&panel->panel_lock); priv_info = panel->cur_mode->priv_info; @@ -5588,6 +5595,9 @@ int dsi_panel_prepare(struct dsi_panel *panel) return -EINVAL; } + if (panel->has_drm_panel) + return 0; + mutex_lock(&panel->panel_lock); if (panel->lp11_init) { @@ -6224,6 +6234,23 @@ int dsi_panel_enable(struct dsi_panel *panel) return -EINVAL; } + if (panel->has_drm_panel) { +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 14, 0)) + drm_panel_prepare(panel->drm_panel); +#else + rc = drm_panel_prepare(panel->drm_panel); + if (rc) { + DSI_ERR("[%s] drm_panel_prepare failed rc=%d\n", + panel->name, rc); + return rc; + } +#endif + mutex_lock(&panel->panel_lock); + panel->panel_initialized = true; + mutex_unlock(&panel->panel_lock); + return 0; + } + mutex_lock(&panel->panel_lock); rc = dsi_panel_tx_cmd_set(panel, DSI_CMD_SET_ON, false); @@ -6264,6 +6291,17 @@ int dsi_panel_post_enable(struct dsi_panel *panel) return -EINVAL; } + if (panel->has_drm_panel) { +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 14, 0)) + drm_panel_enable(panel->drm_panel); +#else + rc = drm_panel_enable(panel->drm_panel); + if (rc) + DSI_ERR("[%s] drm_panel_enable failed rc=%d\n", panel->name, rc); +#endif + return 0; + } + mutex_lock(&panel->panel_lock); if (panel->need_post_on_supply) { @@ -6295,6 +6333,17 @@ int dsi_panel_pre_disable(struct dsi_panel *panel) return -EINVAL; } + if (panel->has_drm_panel) { +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 14, 0)) + drm_panel_disable(panel->drm_panel); +#else + rc = drm_panel_disable(panel->drm_panel); + if (rc) + DSI_ERR("[%s] drm_panel_disable failed rc=%d\n", panel->name, rc); +#endif + return 0; + } + mutex_lock(&panel->panel_lock); if (gpio_is_valid(panel->bl_config.en_gpio)) @@ -6321,6 +6370,22 @@ int dsi_panel_disable(struct dsi_panel *panel) return -EINVAL; } + if (panel->has_drm_panel) { + mutex_lock(&panel->panel_lock); +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 14, 0)) + drm_panel_unprepare(panel->drm_panel); +#else + rc = drm_panel_unprepare(panel->drm_panel); + if (rc) + DSI_ERR("[%s] drm_panel_unprepare failed rc=%d\n", + panel->name, rc); +#endif + panel->panel_initialized = false; + panel->power_mode = SDE_MODE_DPMS_OFF; + mutex_unlock(&panel->panel_lock); + return 0; + } + mutex_lock(&panel->panel_lock); /* Avoid sending panel off commands when ESD recovery is underway */ @@ -6363,6 +6428,10 @@ int dsi_panel_unprepare(struct dsi_panel *panel) return -EINVAL; } + if (panel->has_drm_panel) { + return 0; + } + mutex_lock(&panel->panel_lock); rc = dsi_panel_tx_cmd_set(panel, DSI_CMD_SET_POST_OFF, false); @@ -6394,6 +6463,10 @@ int dsi_panel_post_unprepare(struct dsi_panel *panel) return -EINVAL; } + if (panel->has_drm_panel) { + return 0; + } + mutex_lock(&panel->panel_lock); rc = dsi_panel_power_off(panel); From 044bf8df10419c8d5dbd2cf2923c7e242d1726d7 Mon Sep 17 00:00:00 2001 From: Arpit Saini Date: Sat, 6 Jun 2026 21:07:20 +0530 Subject: [PATCH 9/9] disp: msm: dsi: Skip downstream backlight setup for drm_panel The drm panels manage their own backlight through the panel driver. Skip backlight registration and status updates in sde_connector ops for drm_panel. Change-Id: Ib9f45f2ab1a44bf4e62d638fbc5d67207dcf6fb1 Signed-off-by: Arpit Saini --- msm/dsi/dsi_display.c | 4 ++++ msm/sde/sde_connector.c | 7 +++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/msm/dsi/dsi_display.c b/msm/dsi/dsi_display.c index e3d9cb1..4bc63eb 100644 --- a/msm/dsi/dsi_display.c +++ b/msm/dsi/dsi_display.c @@ -256,6 +256,10 @@ int dsi_display_set_backlight(struct drm_connector *connector, return -EINVAL; panel = dsi_display->panel; + + if (panel->has_drm_panel) + return 0; + sde_conn = to_sde_connector(connector); mutex_lock(&panel->panel_lock); diff --git a/msm/sde/sde_connector.c b/msm/sde/sde_connector.c index 5ee4b99..0484ccb 100644 --- a/msm/sde/sde_connector.c +++ b/msm/sde/sde_connector.c @@ -496,6 +496,9 @@ static int sde_backlight_setup(struct sde_connector *c_conn, if (!display) return 0; + if (display->has_drm_panel_or_bridge) + return 0; + bl_config = &display->panel->bl_config; if (bl_config->type != DSI_BACKLIGHT_DCS && @@ -1880,7 +1883,7 @@ void sde_connector_helper_bridge_disable(struct drm_connector *connector) if (!sde_in_trusted_vm(sde_kms) && c_conn->bl_device && !poms_pending) { c_conn->bl_device->props.power = FB_BLANK_POWERDOWN; c_conn->bl_device->props.state |= BL_CORE_FBBLANK; - backlight_update_status(c_conn->bl_device); + if(!display->panel->has_drm_panel) backlight_update_status(c_conn->bl_device); } c_conn->allow_bl_update = false; @@ -1927,7 +1930,7 @@ void sde_connector_helper_bridge_enable(struct drm_connector *connector) if (!sde_in_trusted_vm(sde_kms) && c_conn->bl_device && !display->poms_pending) { c_conn->bl_device->props.power = FB_BLANK_UNBLANK; c_conn->bl_device->props.state &= ~BL_CORE_FBBLANK; - backlight_update_status(c_conn->bl_device); + if(!display->panel->has_drm_panel) backlight_update_status(c_conn->bl_device); } }