drm: read EDID extensions from monitor

Usually drm read basic EDID, that is enough for us, but since igital display
were introduced i.e. HDMI monitor, sometime we need to interact with monitor by
EDID extension information,

EDID extensions include audio/video data block, speaker allocation and vendor specific data blocks.

This patch intends to read EDID extensions from digital monitor for users.

Signed-off-by: Ma Ling <ling.ma@intel.com>
Signed-off-by: Dave Airlie <airlied@redhat.com>
diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c
index a839a28..fab2bdf 100644
--- a/drivers/gpu/drm/drm_edid.c
+++ b/drivers/gpu/drm/drm_edid.c
@@ -550,11 +550,20 @@
 }
 
 #define DDC_ADDR 0x50
-
-unsigned char *drm_do_probe_ddc_edid(struct i2c_adapter *adapter)
+/**
+ * Get EDID information via I2C.
+ *
+ * \param adapter : i2c device adaptor
+ * \param buf     : EDID data buffer to be filled
+ * \param len     : EDID data buffer length
+ * \return 0 on success or -1 on failure.
+ *
+ * Try to fetch EDID information by calling i2c driver function.
+ */
+int drm_do_probe_ddc_edid(struct i2c_adapter *adapter,
+			  unsigned char *buf, int len)
 {
 	unsigned char start = 0x0;
-	unsigned char *buf = kmalloc(EDID_LENGTH, GFP_KERNEL);
 	struct i2c_msg msgs[] = {
 		{
 			.addr	= DDC_ADDR,
@@ -564,31 +573,36 @@
 		}, {
 			.addr	= DDC_ADDR,
 			.flags	= I2C_M_RD,
-			.len	= EDID_LENGTH,
+			.len	= len,
 			.buf	= buf,
 		}
 	};
 
-	if (!buf) {
-		dev_warn(&adapter->dev, "unable to allocate memory for EDID "
-			 "block.\n");
-		return NULL;
-	}
-
 	if (i2c_transfer(adapter, msgs, 2) == 2)
-		return buf;
+		return 0;
 
 	dev_info(&adapter->dev, "unable to read EDID block.\n");
-	kfree(buf);
-	return NULL;
+	return -1;
 }
 EXPORT_SYMBOL(drm_do_probe_ddc_edid);
 
-static unsigned char *drm_ddc_read(struct i2c_adapter *adapter)
+/**
+ * Get EDID information.
+ *
+ * \param adapter : i2c device adaptor.
+ * \param buf     : EDID data buffer to be filled
+ * \param len     : EDID data buffer length
+ * \return 0 on success or -1 on failure.
+ *
+ * Initialize DDC, then fetch EDID information
+ * by calling drm_do_probe_ddc_edid function.
+ */
+static int drm_ddc_read(struct i2c_adapter *adapter,
+			unsigned char *buf, int len)
 {
 	struct i2c_algo_bit_data *algo_data = adapter->algo_data;
-	unsigned char *edid = NULL;
 	int i, j;
+	int ret = -1;
 
 	algo_data->setscl(algo_data->data, 1);
 
@@ -616,7 +630,7 @@
 		msleep(15);
 
 		/* Do the real work */
-		edid = drm_do_probe_ddc_edid(adapter);
+		ret = drm_do_probe_ddc_edid(adapter, buf, len);
 		algo_data->setsda(algo_data->data, 0);
 		algo_data->setscl(algo_data->data, 0);
 		msleep(15);
@@ -632,7 +646,7 @@
 		msleep(15);
 		algo_data->setscl(algo_data->data, 0);
 		algo_data->setsda(algo_data->data, 0);
-		if (edid)
+		if (ret == 0)
 			break;
 	}
 	/* Release the DDC lines when done or the Apple Cinema HD display
@@ -641,9 +655,31 @@
 	algo_data->setsda(algo_data->data, 1);
 	algo_data->setscl(algo_data->data, 1);
 
-	return edid;
+	return ret;
 }
 
+static int drm_ddc_read_edid(struct drm_connector *connector,
+			     struct i2c_adapter *adapter,
+			     char *buf, int len)
+{
+	int ret;
+
+	ret = drm_ddc_read(adapter, buf, len);
+	if (ret != 0) {
+		dev_info(&connector->dev->pdev->dev, "%s: no EDID data\n",
+			 drm_get_connector_name(connector));
+		goto end;
+	}
+	if (!edid_is_valid((struct edid *)buf)) {
+		dev_warn(&connector->dev->pdev->dev, "%s: EDID invalid.\n",
+			 drm_get_connector_name(connector));
+		ret = -1;
+	}
+end:
+	return ret;
+}
+
+#define MAX_EDID_EXT_NUM 4
 /**
  * drm_get_edid - get EDID data, if available
  * @connector: connector we're probing
@@ -656,24 +692,53 @@
 struct edid *drm_get_edid(struct drm_connector *connector,
 			  struct i2c_adapter *adapter)
 {
+	int ret;
 	struct edid *edid;
 
-	edid = (struct edid *)drm_ddc_read(adapter);
-	if (!edid) {
-		dev_info(&connector->dev->pdev->dev, "%s: no EDID data\n",
-			 drm_get_connector_name(connector));
-		return NULL;
+	edid = kmalloc(EDID_LENGTH * (MAX_EDID_EXT_NUM + 1),
+		       GFP_KERNEL);
+	if (edid == NULL) {
+		dev_warn(&connector->dev->pdev->dev,
+			 "Failed to allocate EDID\n");
+		goto end;
 	}
-	if (!edid_is_valid(edid)) {
-		dev_warn(&connector->dev->pdev->dev, "%s: EDID invalid.\n",
-			 drm_get_connector_name(connector));
-		kfree(edid);
-		return NULL;
+
+	/* Read first EDID block */
+	ret = drm_ddc_read_edid(connector, adapter,
+				(unsigned char *)edid, EDID_LENGTH);
+	if (ret != 0)
+		goto clean_up;
+
+	/* There are EDID extensions to be read */
+	if (edid->extensions != 0) {
+		int edid_ext_num = edid->extensions;
+
+		if (edid_ext_num > MAX_EDID_EXT_NUM) {
+			dev_warn(&connector->dev->pdev->dev,
+				 "The number of extension(%d) is "
+				 "over max (%d), actually read number (%d)\n",
+				 edid_ext_num, MAX_EDID_EXT_NUM,
+				 MAX_EDID_EXT_NUM);
+			/* Reset EDID extension number to be read */
+			edid_ext_num = MAX_EDID_EXT_NUM;
+		}
+		/* Read EDID including extensions too */
+		ret = drm_ddc_read_edid(connector, adapter, (char *)edid,
+					EDID_LENGTH * (edid_ext_num + 1));
+		if (ret != 0)
+			goto clean_up;
+
 	}
 
 	connector->display_info.raw_edid = (char *)edid;
+	goto end;
 
+clean_up:
+	kfree(edid);
+	edid = NULL;
+end:
 	return edid;
+
 }
 EXPORT_SYMBOL(drm_get_edid);