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 confgure 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 | 11 +++ drivers/gpu/drm/tidss/tidss_dispc.h | 8 ++ drivers/gpu/drm/tidss/tidss_drv.h | 3 + drivers/gpu/drm/tidss/tidss_kms.c | 146 +++++++++++++++++++++++----- 4 files changed, 145 insertions(+), 23 deletions(-) diff --git a/drivers/gpu/drm/tidss/tidss_dispc.c b/drivers/gpu/drm/tidss/tidss_dispc.c index 34f0da4bb3e3..88008ad39b55 100644 --- a/drivers/gpu/drm/tidss/tidss_dispc.c +++ b/drivers/gpu/drm/tidss/tidss_dispc.c @@ -354,6 +354,8 @@ struct dispc_device { bool is_enabled; + enum dispc_oldi_modes oldi_mode; + struct dss_vp_data vp_data[TIDSS_MAX_PORTS]; u32 *fourccs; @@ -1958,6 +1960,15 @@ const u32 *dispc_plane_formats(struct dispc_device *dispc, unsigned int *len) return dispc->fourccs; } +int dispc_configure_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 b66418e583ee..45cce1054832 100644 --- a/drivers/gpu/drm/tidss/tidss_dispc.h +++ b/drivers/gpu/drm/tidss/tidss_dispc.h @@ -64,6 +64,13 @@ 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. */ +}; + struct dispc_features { int min_pclk_khz; int max_pclk_khz[DISPC_VP_MAX_BUS_TYPE]; @@ -131,6 +138,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_configure_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 d7f27b0b0315..2252ba0222ca 100644 --- a/drivers/gpu/drm/tidss/tidss_drv.h +++ b/drivers/gpu/drm/tidss/tidss_drv.h @@ -12,6 +12,9 @@ #define TIDSS_MAX_PORTS 4 #define TIDSS_MAX_PLANES 4 +/* For AM625-DSS with 2 OLDI TXes */ +#define TIDSS_MAX_BRIDGE_PER_PIPE 2 + typedef u32 dispc_irq_t; struct tidss_device { diff --git a/drivers/gpu/drm/tidss/tidss_kms.c b/drivers/gpu/drm/tidss/tidss_kms.c index 666e527a0acf..73afe390f36d 100644 --- a/drivers/gpu/drm/tidss/tidss_kms.c +++ b/drivers/gpu/drm/tidss/tidss_kms.c @@ -107,32 +107,84 @@ static const struct drm_mode_config_funcs mode_config_funcs = { .atomic_commit = drm_atomic_helper_commit, }; +static int tidss_get_oldi_mode(struct tidss_device *tidss) +{ + int pixel_order; + struct device_node *dss_ports, *oldi0_port, *oldi1_port; + + dss_ports = of_get_next_child(tidss->dev->of_node, NULL); + oldi0_port = of_graph_get_port_by_id(dss_ports, 0); + oldi1_port = of_graph_get_port_by_id(dss_ports, 2); + + if (!(oldi0_port && oldi1_port)) + return OLDI_SINGLE_LINK_SINGLE_MODE; + + /* + * 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: + case DRM_LVDS_DUAL_LINK_ODD_EVEN_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. + */ + 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_BRIDGE_PER_PIPE]; u32 enc_type; + u32 num_bridges; }; const struct dispc_features *feat = tidss->feat; - u32 max_vps = feat->num_vps; + u32 max_ports = feat->num_max_ports; u32 max_planes = feat->num_planes; struct pipe pipes[TIDSS_MAX_PORTS]; u32 num_pipes = 0; + u32 pipe_number = 0; u32 crtc_mask; + u32 num_oldi = 0; + u32 oldi0_port = 0; + u32 hw_vp = 0; + enum dispc_oldi_modes oldi_mode; /* first find all the connected panels & bridges */ - for (i = 0; i < max_vps; i++) { + for (i = 0; i < max_ports; i++) { struct drm_panel *panel; struct drm_bridge *bridge; + bool bridge_req = true; u32 enc_type = DRM_MODE_ENCODER_NONE; int ret; @@ -146,6 +198,11 @@ static int tidss_dispc_modeset_init(struct tidss_device *tidss) return ret; } + /* default number of bridges required for a panel/bridge*/ + pipe_number = num_pipes; + pipes[pipe_number].num_bridges = 1; + hw_vp = i; + if (panel) { u32 conn_type; @@ -155,7 +212,43 @@ static int tidss_dispc_modeset_init(struct tidss_device *tidss) case DISPC_VP_OLDI: enc_type = DRM_MODE_ENCODER_LVDS; conn_type = DRM_MODE_CONNECTOR_LVDS; + + /* + * A single DSS controller cannot support 2 + * independent displays. If 2nd node is detected, + * it is for Dual Link Mode or Clone Mode. + * + * A new pipe instance is not required. + */ + if (++num_oldi == 2) { + pipe_number = oldi0_port; + hw_vp = i; + + /* 2nd OLDI DT node detected. Get its mode */ + oldi_mode = tidss_get_oldi_mode(tidss); + bridge_req = false; + + /* + * A separate panel bridge will only be + * required if 2 panels are connected for + * the OLDI Clone Mode. + */ + if (oldi_mode == OLDI_SINGLE_LINK_CLONE_MODE) { + bridge_req = true; + (pipes[pipe_number].num_bridges)++; + } + } else { + /* + * First OLDI DT node detected. Save it + * in case there is another node for Dual + * Link Mode or Clone Mode. + */ + oldi0_port = i; + oldi_mode = OLDI_SINGLE_LINK_SINGLE_MODE; + } + dispc_configure_oldi_mode(tidss->dispc, oldi_mode); break; + case DISPC_VP_DPI: enc_type = DRM_MODE_ENCODER_DPI; conn_type = DRM_MODE_CONNECTOR_DPI; @@ -173,19 +266,23 @@ static int tidss_dispc_modeset_init(struct tidss_device *tidss) return -EINVAL; } - bridge = devm_drm_panel_bridge_add(dev, panel); - if (IS_ERR(bridge)) { - dev_err(dev, - "failed to set up panel bridge for port %d\n", - i); - return PTR_ERR(bridge); + if (bridge_req) { + bridge = devm_drm_panel_bridge_add(dev, panel); + if (IS_ERR(bridge)) { + dev_err(dev, + "failed to set up panel bridge for port %d\n", + i); + return PTR_ERR(bridge); + } } } - pipes[num_pipes].hw_videoport = i; - pipes[num_pipes].bridge = bridge; - pipes[num_pipes].enc_type = enc_type; - num_pipes++; + if (bridge_req) { + pipes[pipe_number].hw_videoport = hw_vp; + pipes[pipe_number].bridge[pipes[pipe_number].num_bridges - 1] = bridge; + pipes[pipe_number].enc_type = enc_type; + num_pipes++; + } } /* all planes can be on any crtc */ @@ -200,6 +297,7 @@ static int tidss_dispc_modeset_init(struct tidss_device *tidss) u32 hw_plane_id = feat->vid_order[tidss->num_planes]; int ret; + /* Creating planes and CRTCs only for real pipes */ tplane = tidss_plane_create(tidss, hw_plane_id, DRM_PLANE_TYPE_PRIMARY, crtc_mask, fourccs, fourccs_len); @@ -219,16 +317,18 @@ 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++) { + 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); + } - 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; + } } /* create overlay planes of the leftover planes */ -- 2.37.0