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-bhat...@ti.com>
---
 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

Reply via email to