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 | 24 ++- drivers/gpu/drm/tidss/tidss_dispc.h | 12 ++ 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 | 221 ++++++++++++++++++++++++-- 6 files changed, 245 insertions(+), 22 deletions(-) diff --git a/drivers/gpu/drm/tidss/tidss_dispc.c b/drivers/gpu/drm/tidss/tidss_dispc.c index b55ccbcaa67f..37a73e309330 100644 --- a/drivers/gpu/drm/tidss/tidss_dispc.c +++ b/drivers/gpu/drm/tidss/tidss_dispc.c @@ -88,6 +88,8 @@ const struct dispc_features dispc_k2g_feats = { .subrev = DISPC_K2G, + .has_oldi = false, + .common = "common", .common_regs = tidss_k2g_common_regs, @@ -166,6 +168,8 @@ const struct dispc_features dispc_am625_feats = { .subrev = DISPC_AM625, + .has_oldi = true, + .common = "common", .common_regs = tidss_am65x_common_regs, @@ -218,6 +222,8 @@ const struct dispc_features dispc_am65x_feats = { .subrev = DISPC_AM65X, + .has_oldi = true, + .common = "common", .common_regs = tidss_am65x_common_regs, @@ -309,6 +315,8 @@ const struct dispc_features dispc_j721e_feats = { .subrev = DISPC_J721E, + .has_oldi = false, + .common = "common_m", .common_regs = tidss_j721e_common_regs, @@ -361,6 +369,8 @@ struct dispc_device { struct dss_vp_data vp_data[TIDSS_MAX_VPS]; + enum dispc_oldi_modes oldi_mode; + u32 *fourccs; u32 num_fourccs; @@ -1963,6 +1973,12 @@ const u32 *dispc_plane_formats(struct dispc_device *dispc, unsigned int *len) return dispc->fourccs; } +void dispc_set_oldi_mode(struct dispc_device *dispc, + enum dispc_oldi_modes oldi_mode) +{ + dispc->oldi_mode = oldi_mode; +} + static s32 pixinc(int pixels, u8 ps) { if (pixels == 1) @@ -2647,7 +2663,7 @@ int dispc_runtime_resume(struct dispc_device *dispc) REG_GET(dispc, DSS_SYSSTATUS, 2, 2), REG_GET(dispc, DSS_SYSSTATUS, 3, 3)); - if (dispc->feat->subrev == DISPC_AM65X) + if (dispc->feat->has_oldi) dev_dbg(dispc->dev, "OLDI RESETDONE %d,%d,%d\n", REG_GET(dispc, DSS_SYSSTATUS, 5, 5), REG_GET(dispc, DSS_SYSSTATUS, 6, 6), @@ -2688,7 +2704,7 @@ static int dispc_iomap_resource(struct platform_device *pdev, const char *name, return 0; } -static int dispc_init_am65x_oldi_io_ctrl(struct device *dev, +static int dispc_init_am6xx_oldi_io_ctrl(struct device *dev, struct dispc_device *dispc) { dispc->oldi_io_ctrl = @@ -2827,8 +2843,8 @@ int dispc_init(struct tidss_device *tidss) dispc->vp_data[i].gamma_table = gamma_table; } - if (feat->subrev == DISPC_AM65X) { - r = dispc_init_am65x_oldi_io_ctrl(dev, dispc); + if (feat->has_oldi) { + r = dispc_init_am6xx_oldi_io_ctrl(dev, dispc); if (r) return r; } diff --git a/drivers/gpu/drm/tidss/tidss_dispc.h b/drivers/gpu/drm/tidss/tidss_dispc.h index 971f2856f015..880bc7de68b3 100644 --- a/drivers/gpu/drm/tidss/tidss_dispc.h +++ b/drivers/gpu/drm/tidss/tidss_dispc.h @@ -64,6 +64,15 @@ enum dispc_dss_subrevision { DISPC_J721E, }; +enum dispc_oldi_modes { + OLDI_MODE_SINGLE_LINK, /* Single output over OLDI 0. */ + OLDI_MODE_CLONE_SINGLE_LINK, /* Cloned output over OLDI 0 and 1. */ + OLDI_MODE_DUAL_LINK, /* Combined output over OLDI 0 and 1. */ + OLDI_MODE_OFF, /* OLDI TXes not connected in OF. */ + OLDI_MODE_UNSUPPORTED, /* Unsupported OLDI configuration in OF. */ + OLDI_MODE_UNAVAILABLE, /* OLDI TXes not available in SoC. */ +}; + struct dispc_features { int min_pclk_khz; int max_pclk_khz[DISPC_PORT_MAX_BUS_TYPE]; @@ -72,6 +81,8 @@ struct dispc_features { enum dispc_dss_subrevision subrev; + bool has_oldi; + const char *common; const u16 *common_regs; u32 num_vps; @@ -131,6 +142,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); +void 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 0d4865e9c03d..bd2a7358d7b0 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 d449131935d2..8322ee6310bf 100644 --- a/drivers/gpu/drm/tidss/tidss_kms.c +++ b/drivers/gpu/drm/tidss/tidss_kms.c @@ -13,6 +13,7 @@ #include <drm/drm_of.h> #include <drm/drm_panel.h> #include <drm/drm_vblank.h> +#include <linux/of.h> #include "tidss_crtc.h" #include "tidss_dispc.h" @@ -104,26 +105,129 @@ 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 tidss_device *tidss) +{ + int pixel_order; + enum dispc_oldi_modes oldi_mode; + struct device_node *oldi0_port, *oldi1_port; + + /* + * For am625-dss, the OLDI ports are expected at port reg = 0 and 2, + * and for am65x-dss, the OLDI port is expected only at port reg = 0. + */ + const u32 portnum_oldi0 = 0, portnum_oldi1 = 2; + + oldi0_port = of_graph_get_port_by_id(tidss->dev->of_node, portnum_oldi0); + oldi1_port = of_graph_get_port_by_id(tidss->dev->of_node, portnum_oldi1); + + if (!(oldi0_port || oldi1_port)) { + /* Keep OLDI TXes OFF if neither OLDI port is present. */ + oldi_mode = OLDI_MODE_OFF; + } else if (oldi0_port && !oldi1_port) { + /* + * OLDI0 port found, but not OLDI1 port. Setting single + * link output mode. + */ + oldi_mode = OLDI_MODE_SINGLE_LINK; + } 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. + */ + dev_warn(tidss->dev, + "Single Mode over OLDI 1 is not supported in HW.\n"); + oldi_mode = OLDI_MODE_UNSUPPORTED; + } else { + /* + * 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. + */ + oldi_mode = OLDI_MODE_CLONE_SINGLE_LINK; + break; + + 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 HW and an cannot + * be change via SW. + */ + dev_warn(tidss->dev, + "EVEN-ODD Dual-Link Mode is not supported in HW.\n"); + oldi_mode = OLDI_MODE_UNSUPPORTED; + break; + + case DRM_LVDS_DUAL_LINK_ODD_EVEN_PIXELS: + oldi_mode = OLDI_MODE_DUAL_LINK; + break; + + default: + oldi_mode = OLDI_MODE_UNSUPPORTED; + break; + } + } + + of_node_put(oldi0_port); + of_node_put(oldi1_port); + + return oldi_mode; +} + 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 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; + enum dispc_oldi_modes oldi_mode = OLDI_MODE_UNAVAILABLE; + u32 num_oldi = 0; + u32 num_encoders = 0; + u32 oldi_pipe_index = 0; + + if (feat->has_oldi) { + oldi_mode = tidss_get_oldi_mode(tidss); + + if ((oldi_mode == OLDI_MODE_DUAL_LINK || + oldi_mode == OLDI_MODE_CLONE_SINGLE_LINK) && + feat->subrev == DISPC_AM65X) { + dev_warn(tidss->dev, + "am65x-dss does not support this OLDI mode.\n"); + + oldi_mode = OLDI_MODE_UNSUPPORTED; + } + + dispc_set_oldi_mode(tidss->dispc, oldi_mode); + } /* first find all the connected panels & bridges */ @@ -179,10 +283,87 @@ 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_PORT_OLDI) { + switch (oldi_mode) { + case OLDI_MODE_UNSUPPORTED: + case OLDI_MODE_OFF: + /* + * Either the OLDI ports are not connected in + * OF, or their configuration mode is not + * supported. + * In both the cases, the OLDI sink ports shall + * not be logically connected to DSS ports. + * + * However, since other dss ports might still + * be in use (eg, for DPI), the driver shall + * continue to find the next connected sink in + * OF. + */ + dev_dbg(dev, "OLDI disconnected on port %d\n", i); + continue; + + case OLDI_MODE_DUAL_LINK: + /* + * The 2nd OLDI port of a dual-link sink does + * not require a separate bridge entity. + */ + if (num_oldi) { + drm_panel_bridge_remove(bridge); + continue; + } + + fallthrough; + + case OLDI_MODE_CLONE_SINGLE_LINK: + case OLDI_MODE_SINGLE_LINK: + /* + * Setting up pipe parameters when 1st OLDI + * port is detected. + */ + if (!num_oldi) { + 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; + + /* + * Incrememnt num_pipe when 1st oldi + * port is discovered. For the 2nd OLDI + * port, num_pipe need not be + * incremented because the 2nd + * Encoder-to-Bridge connection will + * still be the part of the first OLDI + * Port pipe. + */ + num_pipes++; + } + + /* + * Bridge is required to be added only if the + * detected port is the first OLDI port (of any + * mode) or a subsequent port in Clone Mode. + */ + pipes[oldi_pipe_index].bridge[num_oldi] = bridge; + pipes[oldi_pipe_index].num_bridges++; + num_oldi++; + break; + + case OLDI_MODE_UNAVAILABLE: + default: + dev_dbg(dev, "OLDI unavailable on this device.\n"); + break; + } + } 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 */ @@ -194,6 +375,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; @@ -216,16 +398,23 @@ 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); - } + possible_clones = (((1 << pipes[i].num_bridges) - 1) + << num_encoders); - ret = drm_bridge_attach(enc, pipes[i].bridge, NULL, 0); - if (ret) - return ret; + for (j = 0; j < pipes[i].num_bridges; j++) { + 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[j], NULL, 0); + if (ret) + return ret; + } + num_encoders += pipes[i].num_bridges; } /* create overlay planes of the leftover planes */ -- 2.39.0