Re: [PATCH v7 4/6] drm/tidss: Add support to configure OLDI mode for am625-dss

From: Tomi Valkeinen
Date: Mon Feb 06 2023 - 08:42:34 EST


On 05/02/2023 15:42, Aradhya Bhatia wrote:
Hi Tomi,

On 03-Feb-23 20:42, Tomi Valkeinen wrote:
On 25/01/2023 13:35, Aradhya Bhatia wrote:
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;
+        }

Shouldn't OLDI_MODE_UNSUPPORTED be handled as an error? It means the DT
is faulty, doesn't it? Maybe it could even be renamed to
OLDI_MODE_ERROR. Or tidss_get_oldi_mode() could return a negative error
code.


The idea was to let the framework continue configuring the 2nd videoport
for DPI, even if the OLDI DT is wrong. But I have come across more
examples recently where that is not the case. DT error for one pipe has
resulted in returning of an error code.

Will make the change.

My opinion is that the DT has to be correct. If it isn't, just fail and exit as soon as possible. There shouldn't be any reasons for the drivers to be trying to cope with a broken DT.

+
+        dispc_set_oldi_mode(tidss->dispc, oldi_mode);
+    }

Would it be better to move the above dispc_set_oldi_mode() to be outside
the if block? Then oldi mode would be set to OLDI_MODE_UNAVAILABLE on
SoCs that don't have OLDI.

Ahh, yes! Will make the change.


tidss_get_oldi_mode and dispc_set_oldi_mode sound like opposites, but
they're totally different things. Maybe tidss_get_oldi_mode should
rather be something about parsing oldi dt properties or such.

Okay! Is 'tidss_parse_oldi_properties' acceptable? This is just
something I came up with now. I can think of more if this is not good.

Sounds fine.

Tomi