From 167f3a04d7366d65c7fa9a92f0d604cdcf4a11ae Mon Sep 17 00:00:00 2001 From: Ma Ling Date: Fri, 20 Mar 2009 14:09:48 +0800 Subject: 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 Signed-off-by: Dave Airlie --- drivers/gpu/drm/drm_edid.c | 121 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 93 insertions(+), 28 deletions(-) (limited to 'drivers/gpu/drm/drm_edid.c') diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index a839a28d8ee..fab2bdf9c42 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -550,11 +550,20 @@ static int add_detailed_info(struct drm_connector *connector, } #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 @@ unsigned char *drm_do_probe_ddc_edid(struct i2c_adapter *adapter) }, { .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 @@ static unsigned char *drm_ddc_read(struct i2c_adapter *adapter) 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 @@ static unsigned char *drm_ddc_read(struct i2c_adapter *adapter) 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 @@ static unsigned char *drm_ddc_read(struct i2c_adapter *adapter) 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 @@ static unsigned char *drm_ddc_read(struct i2c_adapter *adapter) 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); -- cgit v1.2.3-70-g09d2 From f23c20c83d523e5f8cda1f8f7ed52fe6afffbe29 Mon Sep 17 00:00:00 2001 From: Ma Ling Date: Thu, 26 Mar 2009 19:26:23 +0800 Subject: drm: detect hdmi monitor by hdmi identifier (v3) Sometime we need to communicate with HDMI monitor by sending audio or video info frame, so we have to know monitor type. However if user utilize HDMI-DVI adapter to connect DVI monitor, hardware detection will incorrectly show the monitor is HDMI. HDMI spec tell us that any device containing IEEE registration Identifier will be treated as HDMI device. The patch intends to detect HDMI monitor by this rule. Signed-off-by: Ma Ling Signed-off-by: Dave Airlie --- drivers/gpu/drm/drm_edid.c | 62 ++++++++++++++++++++++++++++++++++++++++++++++ include/drm/drm_crtc.h | 1 + 2 files changed, 63 insertions(+) (limited to 'drivers/gpu/drm/drm_edid.c') diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index fab2bdf9c42..c67400067b8 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -742,6 +742,68 @@ end: } EXPORT_SYMBOL(drm_get_edid); +#define HDMI_IDENTIFIER 0x000C03 +#define VENDOR_BLOCK 0x03 +/** + * drm_detect_hdmi_monitor - detect whether monitor is hdmi. + * @edid: monitor EDID information + * + * Parse the CEA extension according to CEA-861-B. + * Return true if HDMI, false if not or unknown. + */ +bool drm_detect_hdmi_monitor(struct edid *edid) +{ + char *edid_ext = NULL; + int i, hdmi_id, edid_ext_num; + int start_offset, end_offset; + bool is_hdmi = false; + + /* No EDID or EDID extensions */ + if (edid == NULL || edid->extensions == 0) + goto end; + + /* Chose real EDID extension number */ + edid_ext_num = edid->extensions > MAX_EDID_EXT_NUM ? + MAX_EDID_EXT_NUM : edid->extensions; + + /* Find CEA extension */ + for (i = 0; i < edid_ext_num; i++) { + edid_ext = (char *)edid + EDID_LENGTH * (i + 1); + /* This block is CEA extension */ + if (edid_ext[0] == 0x02) + break; + } + + if (i == edid_ext_num) + goto end; + + /* Data block offset in CEA extension block */ + start_offset = 4; + end_offset = edid_ext[2]; + + /* + * Because HDMI identifier is in Vendor Specific Block, + * search it from all data blocks of CEA extension. + */ + for (i = start_offset; i < end_offset; + /* Increased by data block len */ + i += ((edid_ext[i] & 0x1f) + 1)) { + /* Find vendor specific block */ + if ((edid_ext[i] >> 5) == VENDOR_BLOCK) { + hdmi_id = edid_ext[i + 1] | (edid_ext[i + 2] << 8) | + edid_ext[i + 3] << 16; + /* Find HDMI identifier */ + if (hdmi_id == HDMI_IDENTIFIER) + is_hdmi = true; + break; + } + } + +end: + return is_hdmi; +} +EXPORT_SYMBOL(drm_detect_hdmi_monitor); + /** * drm_add_edid_modes - add modes from EDID data, if available * @connector: connector we're probing diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h index 9022b246818..3c1924c010e 100644 --- a/include/drm/drm_crtc.h +++ b/include/drm/drm_crtc.h @@ -732,4 +732,5 @@ extern int drm_mode_gamma_get_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv); extern int drm_mode_gamma_set_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv); +extern bool drm_detect_hdmi_monitor(struct edid *edid); #endif /* __DRM_CRTC_H__ */ -- cgit v1.2.3-70-g09d2 From 16456c872e4e3d674dde73f9bdf3f69c0d95e070 Mon Sep 17 00:00:00 2001 From: Dave Airlie Date: Fri, 3 Apr 2009 09:10:33 +1000 Subject: drm: fix typo in edid vendor parsing. Should be, edid_vendor[2] = (edid->mfg_id[1] & 0x1f) + '@'; Since vendor ID has only two bytes only, I am somewhat surprised why gcc doesn't complain this. Reported-by: Guo, Chaohong Signed-off-by: Dave Airlie --- drivers/gpu/drm/drm_edid.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/gpu/drm/drm_edid.c') diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index c67400067b8..2c4a340e682 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -162,7 +162,7 @@ static bool edid_vendor(struct edid *edid, char *vendor) edid_vendor[0] = ((edid->mfg_id[0] & 0x7c) >> 2) + '@'; edid_vendor[1] = (((edid->mfg_id[0] & 0x3) << 3) | ((edid->mfg_id[1] & 0xe0) >> 5)) + '@'; - edid_vendor[2] = (edid->mfg_id[2] & 0x1f) + '@'; + edid_vendor[2] = (edid->mfg_id[1] & 0x1f) + '@'; return !strncmp(edid_vendor, vendor, 3); } -- cgit v1.2.3-70-g09d2 From b94ee6528954b6838e88aa8cf74ed75acf8dfcff Mon Sep 17 00:00:00 2001 From: Jesse Barnes Date: Thu, 2 Apr 2009 14:56:24 -0700 Subject: drm: fix EDID backward compat check EDIDs should be backward compatible, so don't bail if we see a version of 3 (which is out there now) and print a message if we see something newer, but allow it to be parsed. Signed-off-by: Jesse Barnes Signed-off-by: Dave Airlie --- drivers/gpu/drm/drm_edid.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'drivers/gpu/drm/drm_edid.c') diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index 2c4a340e682..ca9c6165671 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -125,10 +125,8 @@ static bool edid_is_valid(struct edid *edid) DRM_ERROR("EDID has major version %d, instead of 1\n", edid->version); goto bad; } - if (edid->revision > 3) { - DRM_ERROR("EDID has minor version %d, which is not between 0-3\n", edid->revision); - goto bad; - } + if (edid->revision > 4) + DRM_DEBUG("EDID minor > 4, assuming backward compatibility\n"); for (i = 0; i < EDID_LENGTH; i++) csum += raw_edid[i]; -- cgit v1.2.3-70-g09d2