drm/msm/dsi-staging: add support for configuring display topology

Display topology is influenced by the target capabilities
and the panel features. This change allows the sde display
device tree to list down the possible topologies valid on
the target and default topology selected for the display.
The change also provides options to override the default
topology through kernel boot parameters.

Change-Id: Ie7b3c6ba5d79965c3247a629e992df9baded6ecd
Signed-off-by: Jeykumar Sankaran <jsanka@codeaurora.org>
diff --git a/Documentation/devicetree/bindings/drm/msm/mdss-dsi-panel.txt b/Documentation/devicetree/bindings/drm/msm/mdss-dsi-panel.txt
index 3e7fcb7..fe0e0e4 100644
--- a/Documentation/devicetree/bindings/drm/msm/mdss-dsi-panel.txt
+++ b/Documentation/devicetree/bindings/drm/msm/mdss-dsi-panel.txt
@@ -456,28 +456,6 @@
 					with the supply entry index. For a detailed description of
 					fields in the supply entry, refer to the qcom,ctrl-supply-entries
 					binding above.
-- qcom,config-select:			Optional property to select default configuration.
-
-[[Optional config sub-nodes]]		These subnodes provide different configurations for a given same panel.
-					Default configuration can be chosen by specifying phandle of the
-					selected subnode in the qcom,config-select.
-Required properties for sub-nodes:	None
-Optional properites:
-- qcom,lm-split:			An array of two values indicating MDP should use two layer
-					mixers to reduce power.
-					Ex: Normally 1080x1920 display uses single DSI and thus one layer
-					    mixer. But if we use two layer mixers then mux the output of
-					    those two mixers into single stream and route it to single DSI
-					    then we can lower the clock requirements of MDP. To use this
-					    configuration we need two fill this array with <540 540>.
-					Both values doesn't have to be same, but recommended, however sum of
-					both values has to be equal to the panel-width.
-					By default two mixer streams are merged using 2D mux, however if
-					2 DSC encoders are used then merge is performed within compression
-					engine.
-- qcom,split-mode:			String property indicating which split mode MDP should use. Valid
-					entries are "pingpong-split" and "dualctl-split".
-					This property is mutually exclusive with qcom,lm-split.
 - qcom,mdss-dsc-version:		An 8 bit value indicates the DSC version supported by panel. Bits[0.3]
 					provides information about minor version while Bits[4.7] provides
 					major version information. It supports only DSC rev 1(Major).1(Minor)
@@ -500,6 +478,21 @@
 - qcom,mdss-dsc-block-prediction-enable: A boolean value to enable/disable the block prediction at decoder.
 - qcom,mdss-dsc-config-by-manufacture-cmd: A boolean to indicates panel use manufacture command to setup pps
 					instead of standard dcs type 0x0A.
+- qcom,display-topology:  		Array of u32 values which specifies the	list of topologies available
+					for the display. A display topology is defined by a
+					set of 3 values in the order:
+					- number of mixers
+					- number of compression encoders
+					- number of interfaces
+					Therefore, the array should always contain a tuple of 3 elements.
+- qcom,default-topology-index:          An u32 value which indexes the topology set
+					specified by the node "qcom,display-topology"
+					to identify the default topology for the
+					display. The first set is indexed by the
+					value 0.
+
+Required properties for sub-nodes:	None
+Optional properties:
 - qcom,dba-panel:	Indicates whether the current panel is used as a display bridge
 					to a non-DSI interface.
 - qcom,bridge-name:			A string to indicate the name of the bridge chip connected to DSI. qcom,bridge-name
@@ -692,18 +685,15 @@
 					29 00 00 00 00 00 02 F1 00];
 				qcom,mdss-dsi-timing-switch-command-state = "dsi_lp_mode";
 
-				qcom,config-select = <&dsi_sim_vid_config0>;
-				dsi_sim_vid_config0: config0 {
-					qcom,lm-split = <360 360>;
-					qcom,mdss-dsc-encoders = <2>;
-					qcom,mdss-dsc-slice-height = <16>;
-					qcom,mdss-dsc-slice-width = <360>;
-					qcom,mdss-dsc-slice-per-pkt = <2>;
-					qcom,mdss-dsc-bit-per-component = <8>;
-					qcom,mdss-dsc-bit-per-pixel = <8>;
-					qcom,mdss-dsc-block-prediction-enable;
-					qcom,mdss-dsc-config-by-manufacture-cmd;
-				};
+				qcom,mdss-dsc-slice-height = <16>;
+				qcom,mdss-dsc-slice-width = <360>;
+				qcom,mdss-dsc-slice-per-pkt = <2>;
+				qcom,mdss-dsc-bit-per-component = <8>;
+				qcom,mdss-dsc-bit-per-pixel = <8>;
+				qcom,mdss-dsc-block-prediction-enable;
+				qcom,mdss-dsc-config-by-manufacture-cmd;
+				qcom,display-topology = <1 1 1>;
+				qcom,default-topology-index = <0>;
 			};
 		};
 		qcom,panel-supply-entries {
@@ -737,41 +727,19 @@
 			};
 		};
 
-		qcom,config-select = <&dsi_sim_vid_config0>;
 		qcom,dba-panel;
 		qcom,bridge-name = "adv7533";
 		qcom,mdss-dsc-version = <0x11>;
 		qcom,mdss-dsc-scr-version = <0x1>;
-
-		dsi_sim_vid_config0: config0 {
-			qcom,lm-split = <360 360>;
-			qcom,mdss-dsc-encoders = <2>;
-			qcom,mdss-dsc-slice-height = <16>;
-			qcom,mdss-dsc-slice-width = <360>;
-			qcom,mdss-dsc-slice-per-pkt = <2>;
-			qcom,mdss-dsc-bit-per-component = <8>;
-			qcom,mdss-dsc-bit-per-pixel = <8>;
-			qcom,mdss-dsc-block-prediction-enable;
-			qcom,mdss-dsc-config-by-manufacture-cmd;
-		};
-
-		dsi_sim_vid_config1: config1 {
-			qcom,mdss-dsc-encoders = <1>;
-			qcom,mdss-dsc-slice-height = <16>;
-			qcom,mdss-dsc-slice-width = <360>;
-			qcom,mdss-dsc-slice-per-pkt = <2>;
-			qcom,mdss-dsc-bit-per-component = <8>;
-			qcom,mdss-dsc-bit-per-pixel = <8>;
-			qcom,mdss-dsc-block-prediction-enable;
-			qcom,mdss-dsc-config-by-manufacture-cmd;
-		};
-
-		dsi_sim_vid_config2: config2 {
-			qcom,split-mode = "dualctl-split";
-		};
-
-		dsi_sim_vid_config3: config3 {
-			qcom,split-mode = "pingpong-split";
-		};
+		qcom,mdss-dsc-slice-height = <16>;
+		qcom,mdss-dsc-slice-width = <360>;
+		qcom,mdss-dsc-slice-per-pkt = <2>;
+		qcom,mdss-dsc-bit-per-component = <8>;
+		qcom,mdss-dsc-bit-per-pixel = <8>;
+		qcom,mdss-dsc-block-prediction-enable;
+		qcom,mdss-dsc-config-by-manufacture-cmd;
+		qcom,display-topology = <1 1 1>,
+			                <2 2 1>;
+		qcom,default-topology-index = <0>;
 	};
 };
diff --git a/drivers/gpu/drm/msm/dsi-staging/dsi_defs.h b/drivers/gpu/drm/msm/dsi-staging/dsi_defs.h
index ee39ec7..563285d 100644
--- a/drivers/gpu/drm/msm/dsi-staging/dsi_defs.h
+++ b/drivers/gpu/drm/msm/dsi-staging/dsi_defs.h
@@ -408,6 +408,7 @@
 	u32 pixel_clk_khz;
 	enum dsi_op_mode panel_mode;
 	u32 dsi_mode_flags;
+	struct msm_mode_info *mode_info;
 };
 
 #endif /* _DSI_DEFS_H_ */
diff --git a/drivers/gpu/drm/msm/dsi-staging/dsi_drm.c b/drivers/gpu/drm/msm/dsi-staging/dsi_drm.c
index 556c0d8..3f4bb5a5 100644
--- a/drivers/gpu/drm/msm/dsi-staging/dsi_drm.c
+++ b/drivers/gpu/drm/msm/dsi-staging/dsi_drm.c
@@ -50,6 +50,8 @@
 	dsi_mode->pixel_clk_khz = drm_mode->clock;
 	dsi_mode->panel_mode = 0; /* TODO: Panel Mode */
 
+	dsi_mode->mode_info = (struct msm_mode_info *)drm_mode->private;
+
 	if (msm_is_mode_seamless(drm_mode))
 		dsi_mode->dsi_mode_flags |= DSI_MODE_FLAG_SEAMLESS;
 	if (msm_is_mode_dynamic_fps(drm_mode))
@@ -81,6 +83,8 @@
 	drm_mode->vrefresh = dsi_mode->timing.refresh_rate;
 	drm_mode->clock = dsi_mode->pixel_clk_khz;
 
+	drm_mode->private = (int *)dsi_mode->mode_info;
+
 	if (dsi_mode->dsi_mode_flags & DSI_MODE_FLAG_SEAMLESS)
 		drm_mode->flags |= DRM_MODE_FLAG_SEAMLESS;
 	if (dsi_mode->dsi_mode_flags & DSI_MODE_FLAG_DFPS)
@@ -255,6 +259,26 @@
 	return ret;
 }
 
+int dsi_conn_get_topology(const struct drm_display_mode *drm_mode,
+	struct msm_display_topology *topology,
+	u32 max_mixer_width)
+{
+	struct dsi_display_mode dsi_mode;
+
+	if (!drm_mode || !topology)
+		return -EINVAL;
+
+	convert_to_dsi_mode(drm_mode, &dsi_mode);
+
+	if (!dsi_mode.mode_info)
+		return -EINVAL;
+
+	memcpy(topology, &dsi_mode.mode_info->topology,
+			sizeof(struct msm_display_topology));
+
+	return 0;
+}
+
 static const struct drm_bridge_funcs dsi_bridge_ops = {
 	.attach       = dsi_bridge_attach,
 	.mode_fixup   = dsi_bridge_mode_fixup,
diff --git a/drivers/gpu/drm/msm/dsi-staging/dsi_drm.h b/drivers/gpu/drm/msm/dsi-staging/dsi_drm.h
index 4339a11..68520a8 100644
--- a/drivers/gpu/drm/msm/dsi-staging/dsi_drm.h
+++ b/drivers/gpu/drm/msm/dsi-staging/dsi_drm.h
@@ -64,6 +64,17 @@
 		void *display);
 
 /**
+ * dsi_conn_get_topology - retrieve current topology for the mode selected
+ * @drm_mode: Display mode set for the display
+ * @topology: Out parameter. Topology for the mode.
+ * @max_mixer_width: max width supported by HW layer mixer
+ * Returns: Zero on success
+ */
+int dsi_conn_get_topology(const struct drm_display_mode *drm_mode,
+	struct msm_display_topology *topology,
+	u32 max_mixer_width);
+
+/**
  * dsi_conn_mode_valid - callback to determine if specified mode is valid
  * @connector: Pointer to drm connector structure
  * @mode: Pointer to drm mode structure
diff --git a/drivers/gpu/drm/msm/dsi-staging/dsi_panel.c b/drivers/gpu/drm/msm/dsi-staging/dsi_panel.c
index b814eb8..cb4afe4 100644
--- a/drivers/gpu/drm/msm/dsi-staging/dsi_panel.c
+++ b/drivers/gpu/drm/msm/dsi-staging/dsi_panel.c
@@ -20,6 +20,19 @@
 #include "dsi_panel.h"
 #include "dsi_ctrl_hw.h"
 
+#define MAX_CMDLINE_PARAM_LEN 256
+static char display_config[MAX_CMDLINE_PARAM_LEN];
+
+/**
+ * topology is currently defined by a set of following 3 values:
+ * 1. num of layer mixers
+ * 2. num of compression encoders
+ * 3. num of interfaces
+ */
+#define TOPOLOGY_SET_LEN 3
+#define INT_BASE_10 10
+#define MAX_TOPOLOGY 5
+
 #define DSI_PANEL_DEFAULT_LABEL  "Default dsi panel"
 
 #define DEFAULT_MDP_TRANSFER_TIME 14000
@@ -1912,25 +1925,18 @@
 	u32 data;
 	int rc = -EINVAL;
 	int intf_width;
-	struct device_node *dsc_np = NULL;
 
 	if (!panel->dsc_enabled)
 		return 0;
 
-	dsc_np = of_parse_phandle(of_node, "qcom,config-select", 0);
-	if (!dsc_np) {
-		pr_err("no dsc config found\n");
-		goto error;
-	}
-
-	rc = of_property_read_u32(dsc_np, "qcom,mdss-dsc-slice-height", &data);
+	rc = of_property_read_u32(of_node, "qcom,mdss-dsc-slice-height", &data);
 	if (rc) {
 		pr_err("failed to parse qcom,mdss-dsc-slice-height\n");
 		goto error;
 	}
 	panel->dsc.slice_height = data;
 
-	rc = of_property_read_u32(dsc_np, "qcom,mdss-dsc-slice-width", &data);
+	rc = of_property_read_u32(of_node, "qcom,mdss-dsc-slice-width", &data);
 	if (rc) {
 		pr_err("failed to parse qcom,mdss-dsc-slice-width\n");
 		goto error;
@@ -1946,14 +1952,15 @@
 	panel->dsc.pic_width = panel->mode.timing.h_active;
 	panel->dsc.pic_height = panel->mode.timing.v_active;
 
-	rc = of_property_read_u32(dsc_np, "qcom,mdss-dsc-slice-per-pkt", &data);
+	rc = of_property_read_u32(of_node, "qcom,mdss-dsc-slice-per-pkt",
+			&data);
 	if (rc) {
 		pr_err("failed to parse qcom,mdss-dsc-slice-per-pkt\n");
 		goto error;
 	}
 	panel->dsc.slice_per_pkt = data;
 
-	rc = of_property_read_u32(dsc_np, "qcom,mdss-dsc-bit-per-component",
+	rc = of_property_read_u32(of_node, "qcom,mdss-dsc-bit-per-component",
 		&data);
 	if (rc) {
 		pr_err("failed to parse qcom,mdss-dsc-bit-per-component\n");
@@ -1961,14 +1968,15 @@
 	}
 	panel->dsc.bpc = data;
 
-	rc = of_property_read_u32(dsc_np, "qcom,mdss-dsc-bit-per-pixel", &data);
+	rc = of_property_read_u32(of_node, "qcom,mdss-dsc-bit-per-pixel",
+			&data);
 	if (rc) {
 		pr_err("failed to parse qcom,mdss-dsc-bit-per-pixel\n");
 		goto error;
 	}
 	panel->dsc.bpp = data;
 
-	panel->dsc.block_pred_enable = of_property_read_bool(dsc_np,
+	panel->dsc.block_pred_enable = of_property_read_bool(of_node,
 		"qcom,mdss-dsc-block-prediction-enable");
 
 	panel->dsc.full_frame_slices = DIV_ROUND_UP(intf_width,
@@ -2027,6 +2035,112 @@
 	return 0;
 }
 
+static int dsi_get_cmdline_top_override(void)
+{
+	char *str = display_config;
+	int top_index = -1;
+
+	/*
+	 * This module need to be updated with needed cmd line argument parsing
+	 * for other dsi parameters.
+	 */
+	if (strlcat(str, "\0", sizeof(str)) > sizeof(str))
+		return -EINVAL;
+
+	str = strnstr(display_config, "config", strlen(display_config));
+	if (!str)
+		return -EINVAL;
+
+	if (kstrtol(str + strlen("config"), INT_BASE_10,
+				(unsigned long *)&top_index))
+		return -EINVAL;
+
+	return top_index;
+}
+
+static int dsi_panel_parse_topology(struct dsi_panel *panel,
+		struct device_node *of_node)
+{
+	struct msm_display_topology *topology;
+	u32 top_count, top_sel, *array = NULL;
+	int i, len = 0;
+	int rc = -EINVAL;
+
+	len = of_property_count_u32_elems(of_node, "qcom,display-topology");
+	if (len <= 0 || len % TOPOLOGY_SET_LEN ||
+			len > (TOPOLOGY_SET_LEN * MAX_TOPOLOGY)) {
+		pr_err("invalid topology list for the panel, rc = %d\n", rc);
+		return rc;
+	}
+
+	top_count = len / TOPOLOGY_SET_LEN;
+
+	array = kcalloc(len, sizeof(u32), GFP_KERNEL);
+	if (!array)
+		return -ENOMEM;
+
+	rc = of_property_read_u32_array(of_node,
+			"qcom,display-topology", array, len);
+	if (rc) {
+		pr_err("unable to read the display topologies, rc = %d\n", rc);
+		goto read_fail;
+	}
+
+	topology = kcalloc(top_count, sizeof(*topology), GFP_KERNEL);
+	if (!topology) {
+		rc = -ENOMEM;
+		goto read_fail;
+	}
+
+	for (i = 0; i < top_count; i++) {
+		struct msm_display_topology *top = &topology[i];
+
+		top->num_lm = array[i * TOPOLOGY_SET_LEN];
+		top->num_enc = array[i * TOPOLOGY_SET_LEN + 1];
+		top->num_intf = array[i * TOPOLOGY_SET_LEN + 2];
+	};
+
+	top_sel = dsi_get_cmdline_top_override();
+	if (top_sel >= 0 && top_sel < top_count) {
+		pr_info("overidden topology: lm: %d comp_enc:%d intf: %d\n",
+			topology[top_sel].num_lm,
+			topology[top_sel].num_enc,
+			topology[top_sel].num_intf);
+		goto parse_done;
+	}
+
+	rc = of_property_read_u32(of_node,
+			"qcom,default-topology-index", &top_sel);
+	if (rc) {
+		pr_err("no default topology selected, rc = %d\n", rc);
+		goto parse_fail;
+	}
+
+	if (top_sel >= top_count) {
+		rc = -EINVAL;
+		pr_err("default topology is specified is not valid, rc = %d\n",
+			rc);
+		goto parse_fail;
+	}
+
+	pr_info("default topology: lm: %d comp_enc:%d intf: %d\n",
+		topology[top_sel].num_lm,
+		topology[top_sel].num_enc,
+		topology[top_sel].num_intf);
+
+parse_done:
+	panel->mode.mode_info = kzalloc(sizeof(struct msm_mode_info),
+			GFP_KERNEL);
+	memcpy(&panel->mode.mode_info->topology, &topology[top_sel],
+		sizeof(struct msm_display_topology));
+parse_fail:
+	kfree(topology);
+read_fail:
+	kfree(array);
+
+	return rc;
+}
+
 struct dsi_panel *dsi_panel_get(struct device *parent,
 				struct device_node *of_node)
 {
@@ -2084,6 +2198,13 @@
 	panel->mode.pixel_clk_khz = (DSI_H_TOTAL(&panel->mode.timing) *
 				    DSI_V_TOTAL(&panel->mode.timing) *
 				    panel->mode.timing.refresh_rate) / 1000;
+
+	rc = dsi_panel_parse_topology(panel, of_node);
+	if (rc) {
+		pr_err("failed to parse panel topology, rc=%d\n", rc);
+		goto error;
+	}
+
 	rc = dsi_panel_parse_host_config(panel, of_node);
 	if (rc) {
 		pr_err("failed to parse host configuration, rc=%d\n", rc);
@@ -2153,6 +2274,8 @@
 	for (i = 0; i < DSI_CMD_SET_MAX; i++)
 		dsi_panel_destroy_cmd_packets(&panel->cmd_sets[i]);
 
+	kfree(panel->mode.mode_info);
+
 	/* TODO:  more free */
 	kfree(panel);
 }
@@ -2611,3 +2734,6 @@
 	mutex_unlock(&panel->panel_lock);
 	return rc;
 }
+
+module_param_string(display_param, display_config, MAX_CMDLINE_PARAM_LEN, 0600);
+MODULE_PARM_DESC(display_param, "format: configx - x indexes the selected topology from the display topology list. Index 0 corresponds to the first topology in the list");
diff --git a/drivers/gpu/drm/msm/msm_drv.h b/drivers/gpu/drm/msm/msm_drv.h
index 4471d0b..2aa966b 100644
--- a/drivers/gpu/drm/msm/msm_drv.h
+++ b/drivers/gpu/drm/msm/msm_drv.h
@@ -353,6 +353,26 @@
 };
 
 /**
+ * struct msm_display_topology - defines a display topology pipeline
+ * @num_lm:       number of layer mixers used
+ * @num_enc:      number of compression encoder blocks used
+ * @num_intf:     number of interfaces the panel is mounted on
+ */
+struct msm_display_topology {
+	u32 num_lm;
+	u32 num_enc;
+	u32 num_intf;
+};
+
+/**
+ * struct msm_mode_info - defines all msm custom mode info
+ * @topology - supported topology for the mode
+ */
+struct msm_mode_info {
+	struct msm_display_topology topology;
+};
+
+/**
  * struct msm_display_info - defines display properties
  * @intf_type:          DRM_MODE_CONNECTOR_ display type
  * @capabilities:       Bitmask of display flags
diff --git a/drivers/gpu/drm/msm/sde/sde_connector.h b/drivers/gpu/drm/msm/sde/sde_connector.h
index 601299e..c8c0eed 100644
--- a/drivers/gpu/drm/msm/sde/sde_connector.h
+++ b/drivers/gpu/drm/msm/sde/sde_connector.h
@@ -121,6 +121,17 @@
 	int (*get_info)(struct msm_display_info *info, void *display);
 
 	/**
+	 * get_topology - retrieve current topology for the mode selected
+	 * @drm_mode: Display mode set for the display
+	 * @topology: Out parameter. Topology for the mode.
+	 * @max_mixer_width: max width supported by HW layer mixer
+	 * Returns: Zero on success
+	 */
+	int (*get_topology)(const struct drm_display_mode *drm_mode,
+			struct msm_display_topology *topology,
+			u32 max_mixer_width);
+
+	/**
 	 * enable_event - notify display of event registration/unregistration
 	 * @connector: Pointer to drm connector structure
 	 * @event_idx: SDE connector event index