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   |  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

Reply via email to