diff --git a/msm/dsi/dsi_display.c b/msm/dsi/dsi_display.c index ffe8d26..4bc63eb 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" @@ -254,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); @@ -3463,15 +3469,116 @@ 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, +}; + +/** + * 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; + + 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_DEBUG("component add success\n"); 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; } @@ -3661,6 +3768,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 +3781,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 +4426,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 +4476,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 +4506,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 +4584,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 +4611,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 +5749,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); @@ -5612,6 +5772,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 +5801,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", @@ -5766,6 +5938,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; @@ -6002,6 +6178,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 @@ -6042,8 +6246,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) { @@ -6165,11 +6371,22 @@ 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; + + 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); @@ -6257,7 +6474,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; } @@ -6267,11 +6484,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]; @@ -6294,11 +6512,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, @@ -6309,6 +6522,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; @@ -6321,6 +6556,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 */ @@ -6399,7 +6642,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; } } @@ -6420,11 +6663,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; } @@ -6537,15 +6796,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); @@ -6591,10 +6853,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; } @@ -6618,10 +6882,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; } @@ -7042,7 +7308,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) @@ -7964,6 +8230,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) { @@ -8028,6 +8463,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); @@ -9385,6 +9825,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_display.h b/msm/dsi/dsi_display.h index 3e60d8a..eb7dd81 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; @@ -466,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. @@ -1098,4 +1114,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_drm.c b/msm/dsi/dsi_drm.c index bab9bbf..4c4f340 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) @@ -226,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); @@ -548,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 @@ -663,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); @@ -690,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); @@ -1052,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); @@ -1276,6 +1328,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 +1418,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); @@ -1371,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; @@ -1381,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); @@ -1637,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++; diff --git a/msm/dsi/dsi_panel.c b/msm/dsi/dsi_panel.c index 7e77ad1..b7c411c 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; @@ -4627,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); @@ -4663,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; @@ -5338,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() */ @@ -5366,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; @@ -5526,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) { @@ -6162,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); @@ -6202,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) { @@ -6233,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)) @@ -6259,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 */ @@ -6301,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); @@ -6332,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); 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; } 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); } }