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

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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




[Index of Archives]     [Device Tree Compilter]     [Device Tree Spec]     [Linux Driver Backports]     [Video for Linux]     [Linux USB Devel]     [Linux PCI Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Yosemite Backpacking]


  Powered by Linux