diff options
author | Adam Jackson <ajax@redhat.com> | 2010-03-29 21:43:18 +0000 |
---|---|---|
committer | Dave Airlie <airlied@redhat.com> | 2010-04-06 10:40:16 +1000 |
commit | 61e57a8d72f2336faf39b5d940215cf085e01e6e (patch) | |
tree | 8c9c6713b4f1ff4c610771860c6cca411cb28cfc | |
parent | 95beb690170e6ce918fe53c73a0fcc7cf64d704a (diff) | |
download | linux-61e57a8d72f2336faf39b5d940215cf085e01e6e.tar.gz linux-61e57a8d72f2336faf39b5d940215cf085e01e6e.tar.bz2 linux-61e57a8d72f2336faf39b5d940215cf085e01e6e.zip |
drm/edid: Fix secondary block fetch.
This makes fetching the second EDID block on HDMI monitors actually
work. DDC can't transfer more than 128 bytes at a time. Also,
rearrange the code so the pure DDC bits are separate from block parse.
Signed-off-by: Adam Jackson <ajax@redhat.com>
Signed-off-by: Dave Airlie <airlied@redhat.com>
-rw-r--r-- | drivers/gpu/drm/drm_edid.c | 330 | ||||
-rw-r--r-- | include/drm/drm_crtc.h | 2 |
2 files changed, 187 insertions, 145 deletions
diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index 7e608f4a0df9..5e60a6129641 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -2,6 +2,7 @@ * Copyright (c) 2006 Luc Verhaegen (quirks list) * Copyright (c) 2007-2008 Intel Corporation * Jesse Barnes <jesse.barnes@intel.com> + * Copyright 2010 Red Hat, Inc. * * DDC probing routines (drm_ddc_read & drm_do_probe_ddc_edid) originally from * FB layer. @@ -106,36 +107,38 @@ static struct edid_quirk { { "SAM", 638, EDID_QUIRK_PREFER_LARGE_60 }, }; +/*** DDC fetch and block validation ***/ -/* Valid EDID header has these bytes */ static const u8 edid_header[] = { 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00 }; -/** - * drm_edid_is_valid - sanity check EDID data - * @edid: EDID data - * - * Sanity check the EDID block by looking at the header, the version number - * and the checksum. Return 0 if the EDID doesn't check out, or 1 if it's - * valid. +/* + * Sanity check the EDID block (base or extension). Return 0 if the block + * doesn't check out, or 1 if it's valid. */ -bool drm_edid_is_valid(struct edid *edid) +static bool +drm_edid_block_valid(u8 *raw_edid) { - int i, score = 0; + int i; u8 csum = 0; - u8 *raw_edid = (u8 *)edid; + struct edid *edid = (struct edid *)raw_edid; - for (i = 0; i < sizeof(edid_header); i++) - if (raw_edid[i] == edid_header[i]) - score++; + if (raw_edid[0] == 0x00) { + int score = 0; - if (score == 8) ; - else if (score >= 6) { - DRM_DEBUG("Fixing EDID header, your hardware may be failing\n"); - memcpy(raw_edid, edid_header, sizeof(edid_header)); - } else - goto bad; + for (i = 0; i < sizeof(edid_header); i++) + if (raw_edid[i] == edid_header[i]) + score++; + + if (score == 8) ; + else if (score >= 6) { + DRM_DEBUG("Fixing EDID header, your hardware may be failing\n"); + memcpy(raw_edid, edid_header, sizeof(edid_header)); + } else { + goto bad; + } + } for (i = 0; i < EDID_LENGTH; i++) csum += raw_edid[i]; @@ -144,13 +147,21 @@ bool drm_edid_is_valid(struct edid *edid) goto bad; } - if (edid->version != 1) { - DRM_ERROR("EDID has major version %d, instead of 1\n", edid->version); - goto bad; - } + /* per-block-type checks */ + switch (raw_edid[0]) { + case 0: /* base */ + if (edid->version != 1) { + DRM_ERROR("EDID has major version %d, instead of 1\n", edid->version); + goto bad; + } - if (edid->revision > 4) - DRM_DEBUG("EDID minor > 4, assuming backward compatibility\n"); + if (edid->revision > 4) + DRM_DEBUG("EDID minor > 4, assuming backward compatibility\n"); + break; + + default: + break; + } return 1; @@ -162,8 +173,158 @@ bad: } return 0; } + +/** + * drm_edid_is_valid - sanity check EDID data + * @edid: EDID data + * + * Sanity-check an entire EDID record (including extensions) + */ +bool drm_edid_is_valid(struct edid *edid) +{ + int i; + u8 *raw = (u8 *)edid; + + if (!edid) + return false; + + for (i = 0; i <= edid->extensions; i++) + if (!drm_edid_block_valid(raw + i * EDID_LENGTH)) + return false; + + return true; +} EXPORT_SYMBOL(drm_edid_is_valid); +#define DDC_ADDR 0x50 +#define DDC_SEGMENT_ADDR 0x30 +/** + * 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. + */ +static int +drm_do_probe_ddc_edid(struct i2c_adapter *adapter, unsigned char *buf, + int block, int len) +{ + unsigned char start = block * EDID_LENGTH; + struct i2c_msg msgs[] = { + { + .addr = DDC_ADDR, + .flags = 0, + .len = 1, + .buf = &start, + }, { + .addr = DDC_ADDR, + .flags = I2C_M_RD, + .len = len, + .buf = buf + start, + } + }; + + if (i2c_transfer(adapter, msgs, 2) == 2) + return 0; + + return -1; +} + +static u8 * +drm_do_get_edid(struct drm_connector *connector, struct i2c_adapter *adapter) +{ + int i, j = 0; + u8 *block, *new; + + if ((block = kmalloc(EDID_LENGTH, GFP_KERNEL)) == NULL) + return NULL; + + /* base block fetch */ + for (i = 0; i < 4; i++) { + if (drm_do_probe_ddc_edid(adapter, block, 0, EDID_LENGTH)) + goto out; + if (drm_edid_block_valid(block)) + break; + } + if (i == 4) + goto carp; + + /* if there's no extensions, we're done */ + if (block[0x7e] == 0) + return block; + + new = krealloc(block, (block[0x7e] + 1) * EDID_LENGTH, GFP_KERNEL); + if (!new) + goto out; + block = new; + + for (j = 1; j <= block[0x7e]; j++) { + for (i = 0; i < 4; i++) { + if (drm_do_probe_ddc_edid(adapter, block, j, + EDID_LENGTH)) + goto out; + if (drm_edid_block_valid(block + j * EDID_LENGTH)) + break; + } + if (i == 4) + goto carp; + } + + return block; + +carp: + dev_warn(&connector->dev->pdev->dev, "%s: EDID block %d invalid.\n", + drm_get_connector_name(connector), j); + +out: + kfree(block); + return NULL; +} + +/** + * Probe DDC presence. + * + * \param adapter : i2c device adaptor + * \return 1 on success + */ +static bool +drm_probe_ddc(struct i2c_adapter *adapter) +{ + unsigned char out; + + return (drm_do_probe_ddc_edid(adapter, &out, 0, 1) == 0); +} + +/** + * drm_get_edid - get EDID data, if available + * @connector: connector we're probing + * @adapter: i2c adapter to use for DDC + * + * Poke the given i2c channel to grab EDID data if possible. If found, + * attach it to the connector. + * + * Return edid data or NULL if we couldn't find any. + */ +struct edid *drm_get_edid(struct drm_connector *connector, + struct i2c_adapter *adapter) +{ + struct edid *edid = NULL; + + if (drm_probe_ddc(adapter)) + edid = (struct edid *)drm_do_get_edid(connector, adapter); + + connector->display_info.raw_edid = (char *)edid; + + return edid; + +} +EXPORT_SYMBOL(drm_get_edid); + +/*** EDID parsing ***/ + /** * edid_vendor - match a string against EDID's obfuscated vendor field * @edid: EDID to match @@ -1141,123 +1302,6 @@ static int add_detailed_info_eedid(struct drm_connector *connector, return modes; } -#define DDC_ADDR 0x50 -/** - * 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; - struct i2c_msg msgs[] = { - { - .addr = DDC_ADDR, - .flags = 0, - .len = 1, - .buf = &start, - }, { - .addr = DDC_ADDR, - .flags = I2C_M_RD, - .len = len, - .buf = buf, - } - }; - - if (i2c_transfer(adapter, msgs, 2) == 2) - return 0; - - return -1; -} -EXPORT_SYMBOL(drm_do_probe_ddc_edid); - -static int drm_ddc_read_edid(struct drm_connector *connector, - struct i2c_adapter *adapter, - char *buf, int len) -{ - int i; - - for (i = 0; i < 4; i++) { - if (drm_do_probe_ddc_edid(adapter, buf, len)) - return -1; - if (drm_edid_is_valid((struct edid *)buf)) - return 0; - } - - /* repeated checksum failures; warn, but carry on */ - dev_warn(&connector->dev->pdev->dev, "%s: EDID invalid.\n", - drm_get_connector_name(connector)); - return -1; -} - -/** - * drm_get_edid - get EDID data, if available - * @connector: connector we're probing - * @adapter: i2c adapter to use for DDC - * - * Poke the given connector's i2c channel to grab EDID data if possible. - * - * Return edid data or NULL if we couldn't find any. - */ -struct edid *drm_get_edid(struct drm_connector *connector, - struct i2c_adapter *adapter) -{ - int ret; - struct edid *edid; - - edid = kmalloc(EDID_LENGTH * (DRM_MAX_EDID_EXT_NUM + 1), - GFP_KERNEL); - if (edid == NULL) { - dev_warn(&connector->dev->pdev->dev, - "Failed to allocate EDID\n"); - goto end; - } - - /* 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 > DRM_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, DRM_MAX_EDID_EXT_NUM, - DRM_MAX_EDID_EXT_NUM); - /* Reset EDID extension number to be read */ - edid_ext_num = DRM_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); - #define HDMI_IDENTIFIER 0x000C03 #define VENDOR_BLOCK 0x03 /** diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h index 1347524a8e30..f74523a299c9 100644 --- a/include/drm/drm_crtc.h +++ b/include/drm/drm_crtc.h @@ -666,8 +666,6 @@ extern void drm_fb_release(struct drm_file *file_priv); extern int drm_mode_group_init_legacy_group(struct drm_device *dev, struct drm_mode_group *group); extern struct edid *drm_get_edid(struct drm_connector *connector, struct i2c_adapter *adapter); -extern int drm_do_probe_ddc_edid(struct i2c_adapter *adapter, - unsigned char *buf, int len); extern int drm_add_edid_modes(struct drm_connector *connector, struct edid *edid); extern void drm_mode_probed_add(struct drm_connector *connector, struct drm_display_mode *mode); extern void drm_mode_remove(struct drm_connector *connector, struct drm_display_mode *mode); |