The newer version of DSS (AM625-DSS) has 2 OLDI TXes at its disposal. These can be configured to support the following modes: 1. OLDI_SINGLE_LINK_SINGLE_MODE Single Output over OLDI 0. +------+ +---------+ +-------+ | | | | | | | CRTC +------->+ ENCODER +----->| PANEL | | | | | | | +------+ +---------+ +-------+ 2. OLDI_SINGLE_LINK_CLONE_MODE Duplicate Output over OLDI 0 and 1. +------+ +---------+ +-------+ | | | | | | | CRTC +---+--->| ENCODER +----->| PANEL | | | | | | | | +------+ | +---------+ +-------+ | | +---------+ +-------+ | | | | | +--->| ENCODER +----->| PANEL | | | | | +---------+ +-------+ 3. OLDI_DUAL_LINK_MODE Combined Output over OLDI 0 and 1. +------+ +---------+ +-------+ | | | +----->| | | CRTC +------->+ ENCODER | | PANEL | | | | +----->| | +------+ +---------+ +-------+ Following the above pathways for different modes, 2 encoder/panel-bridge pipes get created for clone mode, and 1 pipe in cases of single link and dual link mode. Add support for confguring the OLDI modes using OF and LVDS DRM helper functions. Signed-off-by: Aradhya Bhatia <a-bhatia1@xxxxxx> --- drivers/gpu/drm/tidss/tidss_dispc.c | 12 ++ drivers/gpu/drm/tidss/tidss_dispc.h | 9 ++ drivers/gpu/drm/tidss/tidss_drv.h | 3 + drivers/gpu/drm/tidss/tidss_encoder.c | 4 +- drivers/gpu/drm/tidss/tidss_encoder.h | 3 +- drivers/gpu/drm/tidss/tidss_kms.c | 188 +++++++++++++++++++++++--- 6 files changed, 198 insertions(+), 21 deletions(-) diff --git a/drivers/gpu/drm/tidss/tidss_dispc.c b/drivers/gpu/drm/tidss/tidss_dispc.c index dbc6a5b617ca..472226a83251 100644 --- a/drivers/gpu/drm/tidss/tidss_dispc.c +++ b/drivers/gpu/drm/tidss/tidss_dispc.c @@ -365,6 +365,8 @@ struct dispc_device { struct dss_vp_data vp_data[TIDSS_MAX_VPS]; + enum dispc_oldi_modes oldi_mode; + u32 *fourccs; u32 num_fourccs; @@ -1967,6 +1969,16 @@ const u32 *dispc_plane_formats(struct dispc_device *dispc, unsigned int *len) return dispc->fourccs; } +int dispc_set_oldi_mode(struct dispc_device *dispc, + enum dispc_oldi_modes oldi_mode) +{ + WARN_ON(!dispc); + + dispc->oldi_mode = oldi_mode; + + return 0; +} + static s32 pixinc(int pixels, u8 ps) { if (pixels == 1) diff --git a/drivers/gpu/drm/tidss/tidss_dispc.h b/drivers/gpu/drm/tidss/tidss_dispc.h index 51db500590ee..e76a7599b544 100644 --- a/drivers/gpu/drm/tidss/tidss_dispc.h +++ b/drivers/gpu/drm/tidss/tidss_dispc.h @@ -64,6 +64,14 @@ enum dispc_dss_subrevision { DISPC_AM625, }; +enum dispc_oldi_modes { + OLDI_MODE_OFF, /* OLDI turned off / tied off in IP. */ + OLDI_SINGLE_LINK_SINGLE_MODE, /* Single Output over OLDI 0. */ + OLDI_SINGLE_LINK_CLONE_MODE, /* Duplicate Output over OLDI 0 and 1. */ + OLDI_DUAL_LINK_MODE, /* Combined Output over OLDI 0 and 1. */ + OLDI_MODE_UNSUPPORTED, /* Unsupported OLDI Mode */ +}; + struct dispc_features { int min_pclk_khz; int max_pclk_khz[DISPC_VP_MAX_BUS_TYPE]; @@ -133,6 +141,7 @@ int dispc_plane_setup(struct dispc_device *dispc, u32 hw_plane, u32 hw_videoport); int dispc_plane_enable(struct dispc_device *dispc, u32 hw_plane, bool enable); const u32 *dispc_plane_formats(struct dispc_device *dispc, unsigned int *len); +int dispc_set_oldi_mode(struct dispc_device *dispc, enum dispc_oldi_modes oldi_mode); int dispc_init(struct tidss_device *tidss); void dispc_remove(struct tidss_device *tidss); diff --git a/drivers/gpu/drm/tidss/tidss_drv.h b/drivers/gpu/drm/tidss/tidss_drv.h index 0ce7ee5ccd5b..58892f065c16 100644 --- a/drivers/gpu/drm/tidss/tidss_drv.h +++ b/drivers/gpu/drm/tidss/tidss_drv.h @@ -13,6 +13,9 @@ #define TIDSS_MAX_PLANES 4 #define TIDSS_MAX_OUTPUT_PORTS 4 +/* For AM625-DSS with 2 OLDI TXes */ +#define TIDSS_MAX_BRIDGES_PER_PIPE 2 + typedef u32 dispc_irq_t; struct tidss_device { diff --git a/drivers/gpu/drm/tidss/tidss_encoder.c b/drivers/gpu/drm/tidss/tidss_encoder.c index e278a9c89476..141383ec4045 100644 --- a/drivers/gpu/drm/tidss/tidss_encoder.c +++ b/drivers/gpu/drm/tidss/tidss_encoder.c @@ -70,7 +70,8 @@ static const struct drm_encoder_funcs encoder_funcs = { }; struct drm_encoder *tidss_encoder_create(struct tidss_device *tidss, - u32 encoder_type, u32 possible_crtcs) + u32 encoder_type, u32 possible_crtcs, + u32 possible_clones) { struct drm_encoder *enc; int ret; @@ -80,6 +81,7 @@ struct drm_encoder *tidss_encoder_create(struct tidss_device *tidss, return ERR_PTR(-ENOMEM); enc->possible_crtcs = possible_crtcs; + enc->possible_clones = possible_clones; ret = drm_encoder_init(&tidss->ddev, enc, &encoder_funcs, encoder_type, NULL); diff --git a/drivers/gpu/drm/tidss/tidss_encoder.h b/drivers/gpu/drm/tidss/tidss_encoder.h index ace877c0e0fd..01c62ba3ef16 100644 --- a/drivers/gpu/drm/tidss/tidss_encoder.h +++ b/drivers/gpu/drm/tidss/tidss_encoder.h @@ -12,6 +12,7 @@ struct tidss_device; struct drm_encoder *tidss_encoder_create(struct tidss_device *tidss, - u32 encoder_type, u32 possible_crtcs); + u32 encoder_type, u32 possible_crtcs, + u32 possible_clones); #endif diff --git a/drivers/gpu/drm/tidss/tidss_kms.c b/drivers/gpu/drm/tidss/tidss_kms.c index fc9c4eefd31d..8ae321f02197 100644 --- a/drivers/gpu/drm/tidss/tidss_kms.c +++ b/drivers/gpu/drm/tidss/tidss_kms.c @@ -106,30 +106,115 @@ static const struct drm_mode_config_funcs mode_config_funcs = { .atomic_commit = drm_atomic_helper_commit, }; +static enum dispc_oldi_modes tidss_get_oldi_mode(struct device_node *oldi0_port, + struct device_node *oldi1_port) +{ + int pixel_order; + + if (!(oldi0_port || oldi1_port)) { + /* Keep OLDI TXes off if neither OLDI port is present. */ + return OLDI_MODE_OFF; + } else if (oldi0_port && !oldi1_port) { + /* + * OLDI0 port found, but not OLDI1 port. Setting single + * link, single mode output. + */ + return OLDI_SINGLE_LINK_SINGLE_MODE; + } else if (!oldi0_port && oldi1_port) { + /* + * The 2nd OLDI TX cannot be operated alone. This use case is + * not supported in the HW. Since the pins for OLDIs 0 and 1 are + * separate, one could theoretically set a clone mode over OLDIs + * 0 and 1 and just simply not use the OLDI 0. This is a hacky + * way to enable only OLDI TX 1 and hence is not officially + * supported. + */ + return OLDI_MODE_UNSUPPORTED; + } + + /* + * OLDI Ports found for both the OLDI TXes. The DSS is to be configured + * in either Dual Link or Clone Mode. + */ + pixel_order = drm_of_lvds_get_dual_link_pixel_order(oldi0_port, + oldi1_port); + switch (pixel_order) { + case -EINVAL: + /* + * The dual link properties were not found in at least one of + * the sink nodes. Since 2 OLDI ports are present in the DT, it + * can be safely assumed that the required configuration is + * Clone Mode. + */ + return OLDI_SINGLE_LINK_CLONE_MODE; + + case DRM_LVDS_DUAL_LINK_EVEN_ODD_PIXELS: + /* + * Note that the OLDI TX 0 transmits the odd set of pixels while + * the OLDI TX 1 transmits the even set. This is a fixed + * configuration in the IP and an cannot be change vis SW. These + * properties have been used to merely identify if a Dual Link + * configuration is required. Swapping this property in the panel + * port DT nodes will not make any difference. + */ + pr_warn("EVEN-ODD config for dual-link sinks is not supported in HW. Switching to ODD-EVEN.\n"); + fallthrough; + + case DRM_LVDS_DUAL_LINK_ODD_EVEN_PIXELS: + return OLDI_DUAL_LINK_MODE; + + default: + return OLDI_MODE_OFF; + } +} + static int tidss_dispc_modeset_init(struct tidss_device *tidss) { struct device *dev = tidss->dev; unsigned int fourccs_len; const u32 *fourccs = dispc_plane_formats(tidss->dispc, &fourccs_len); - unsigned int i; + unsigned int i, j; struct pipe { u32 hw_videoport; - struct drm_bridge *bridge; + struct drm_bridge *bridge[TIDSS_MAX_BRIDGES_PER_PIPE]; u32 enc_type; + u32 num_bridges; }; const struct dispc_features *feat = tidss->feat; - u32 max_vps = feat->num_vps; + u32 output_ports = feat->num_output_ports; u32 max_planes = feat->num_planes; - struct pipe pipes[TIDSS_MAX_VPS]; + struct pipe pipes[TIDSS_MAX_VPS] = {0}; + u32 num_pipes = 0; u32 crtc_mask; + u32 portnum_oldi0 = 0, portnum_oldi1 = 2; + enum dispc_oldi_modes oldi_mode = OLDI_MODE_OFF; + u32 num_oldi = 0; + u32 oldi_pipe_index = 0; + u32 num_encoders = 0; + + if (feat->oldi_supported) { + struct device_node *oldi0_port, *oldi1_port; + + oldi0_port = of_graph_get_port_by_id(dev->of_node, + portnum_oldi0); + oldi1_port = of_graph_get_port_by_id(dev->of_node, + portnum_oldi1); + + oldi_mode = tidss_get_oldi_mode(oldi0_port, oldi1_port); + + of_node_put(oldi0_port); + of_node_put(oldi1_port); + + dispc_set_oldi_mode(tidss->dispc, oldi_mode); + } /* first find all the connected panels & bridges */ - for (i = 0; i < max_vps; i++) { + for (i = 0; i < output_ports; i++) { struct drm_panel *panel; struct drm_bridge *bridge; u32 enc_type = DRM_MODE_ENCODER_NONE; @@ -145,16 +230,24 @@ static int tidss_dispc_modeset_init(struct tidss_device *tidss) return ret; } + if (feat->output_port_bus_type[i] == DISPC_VP_OLDI && + oldi_mode == OLDI_MODE_UNSUPPORTED) { + dev_err(dev, + "Single Mode over OLDI 1 is not supported in HW. Keeping OLDI off.\n"); + continue; + } + if (panel) { u32 conn_type; dev_dbg(dev, "Setting up panel for port %d\n", i); - switch (feat->vp_bus_type[i]) { + switch (feat->output_port_bus_type[i]) { case DISPC_VP_OLDI: enc_type = DRM_MODE_ENCODER_LVDS; conn_type = DRM_MODE_CONNECTOR_LVDS; break; + case DISPC_VP_DPI: enc_type = DRM_MODE_ENCODER_DPI; conn_type = DRM_MODE_CONNECTOR_DPI; @@ -172,6 +265,17 @@ static int tidss_dispc_modeset_init(struct tidss_device *tidss) return -EINVAL; } + /* + * If the 2nd OLDI port is discovered connected to a panel + * which is not to be connected in the Clone Mode then a + * bridge is not required because the detected port is the + * 2nd port for the previously connected panel. + */ + if (feat->output_port_bus_type[i] == DISPC_VP_OLDI && + oldi_mode != OLDI_SINGLE_LINK_CLONE_MODE && + num_oldi) + break; + bridge = devm_drm_panel_bridge_add(dev, panel); if (IS_ERR(bridge)) { dev_err(dev, @@ -181,10 +285,47 @@ static int tidss_dispc_modeset_init(struct tidss_device *tidss) } } - pipes[num_pipes].hw_videoport = i; - pipes[num_pipes].bridge = bridge; - pipes[num_pipes].enc_type = enc_type; - num_pipes++; + if (feat->output_port_bus_type[i] == DISPC_VP_OLDI) { + if (++num_oldi == 1) { + /* Setting up pipe parameters when 1st OLDI port is detected */ + + pipes[num_pipes].hw_videoport = i; + pipes[num_pipes].enc_type = enc_type; + + /* + * Saving the pipe index in case its required for + * 2nd OLDI Port. + */ + oldi_pipe_index = num_pipes; + + /* + * No additional pipe is required for the 2nd OLDI + * port, because the 2nd Encoder -> Bridge connection + * is the part of the first OLDI Port pipe. + * + * num_pipes will only be incremented when the first + * OLDI port is discovered. + */ + num_pipes++; + } + + /* + * Bridge is required to be added only if the detected + * port is the first OLDI port or a subsequent port in + * Clone Mode. + */ + if (oldi_mode == OLDI_SINGLE_LINK_CLONE_MODE || + num_oldi == 1) { + pipes[oldi_pipe_index].bridge[num_oldi - 1] = bridge; + pipes[oldi_pipe_index].num_bridges++; + } + } else { + pipes[num_pipes].hw_videoport = i; + pipes[num_pipes].bridge[0] = bridge; + pipes[num_pipes].num_bridges++; + pipes[num_pipes].enc_type = enc_type; + num_pipes++; + } } /* all planes can be on any crtc */ @@ -196,6 +337,7 @@ static int tidss_dispc_modeset_init(struct tidss_device *tidss) struct tidss_plane *tplane; struct tidss_crtc *tcrtc; struct drm_encoder *enc; + u32 possible_clones = 0; u32 hw_plane_id = feat->vid_order[tidss->num_planes]; int ret; @@ -218,16 +360,24 @@ static int tidss_dispc_modeset_init(struct tidss_device *tidss) tidss->crtcs[tidss->num_crtcs++] = &tcrtc->crtc; - enc = tidss_encoder_create(tidss, pipes[i].enc_type, - 1 << tcrtc->crtc.index); - if (IS_ERR(enc)) { - dev_err(tidss->dev, "encoder create failed\n"); - return PTR_ERR(enc); - } + for (j = 0; j < pipes[i].num_bridges; j++) { + if (pipes[i].num_bridges > 1) + possible_clones = (((1 << pipes[i].num_bridges) - 1) + << num_encoders); + + enc = tidss_encoder_create(tidss, pipes[i].enc_type, + 1 << tcrtc->crtc.index, + possible_clones); + if (IS_ERR(enc)) { + dev_err(tidss->dev, "encoder create failed\n"); + return PTR_ERR(enc); + } - ret = drm_bridge_attach(enc, pipes[i].bridge, NULL, 0); - if (ret) - return ret; + ret = drm_bridge_attach(enc, pipes[i].bridge[j], NULL, 0); + if (ret) + return ret; + } + num_encoders += pipes[i].num_bridges; } /* create overlay planes of the leftover planes */ -- 2.38.1