[PATCH v6 3/5] 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   |  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




[Index of Archives]     [Linux DRI Users]     [Linux Intel Graphics]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]
  Powered by Linux