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>
This commit is contained in:
Adam Jackson 2010-03-29 21:43:18 +00:00 committed by Dave Airlie
parent 95beb69017
commit 61e57a8d72
2 changed files with 187 additions and 145 deletions

View File

@ -2,6 +2,7 @@
* Copyright (c) 2006 Luc Verhaegen (quirks list) * Copyright (c) 2006 Luc Verhaegen (quirks list)
* Copyright (c) 2007-2008 Intel Corporation * Copyright (c) 2007-2008 Intel Corporation
* Jesse Barnes <jesse.barnes@intel.com> * Jesse Barnes <jesse.barnes@intel.com>
* Copyright 2010 Red Hat, Inc.
* *
* DDC probing routines (drm_ddc_read & drm_do_probe_ddc_edid) originally from * DDC probing routines (drm_ddc_read & drm_do_probe_ddc_edid) originally from
* FB layer. * FB layer.
@ -106,36 +107,38 @@ static struct edid_quirk {
{ "SAM", 638, EDID_QUIRK_PREFER_LARGE_60 }, { "SAM", 638, EDID_QUIRK_PREFER_LARGE_60 },
}; };
/*** DDC fetch and block validation ***/
/* Valid EDID header has these bytes */
static const u8 edid_header[] = { static const u8 edid_header[] = {
0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00
}; };
/** /*
* drm_edid_is_valid - sanity check EDID data * Sanity check the EDID block (base or extension). Return 0 if the block
* @edid: EDID data * doesn't check out, or 1 if it's valid.
*
* 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.
*/ */
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 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[0] == 0x00) {
if (raw_edid[i] == edid_header[i]) int score = 0;
score++;
if (score == 8) ; for (i = 0; i < sizeof(edid_header); i++)
else if (score >= 6) { if (raw_edid[i] == edid_header[i])
DRM_DEBUG("Fixing EDID header, your hardware may be failing\n"); score++;
memcpy(raw_edid, edid_header, sizeof(edid_header));
} else if (score == 8) ;
goto bad; 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++) for (i = 0; i < EDID_LENGTH; i++)
csum += raw_edid[i]; csum += raw_edid[i];
@ -144,13 +147,21 @@ bool drm_edid_is_valid(struct edid *edid)
goto bad; goto bad;
} }
if (edid->version != 1) { /* per-block-type checks */
DRM_ERROR("EDID has major version %d, instead of 1\n", edid->version); switch (raw_edid[0]) {
goto bad; 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) if (edid->revision > 4)
DRM_DEBUG("EDID minor > 4, assuming backward compatibility\n"); DRM_DEBUG("EDID minor > 4, assuming backward compatibility\n");
break;
default:
break;
}
return 1; return 1;
@ -162,8 +173,158 @@ bool drm_edid_is_valid(struct edid *edid)
} }
return 0; 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); 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_vendor - match a string against EDID's obfuscated vendor field
* @edid: EDID to match * @edid: EDID to match
@ -1141,123 +1302,6 @@ static int add_detailed_info_eedid(struct drm_connector *connector,
return modes; 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 HDMI_IDENTIFIER 0x000C03
#define VENDOR_BLOCK 0x03 #define VENDOR_BLOCK 0x03
/** /**

View File

@ -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 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, extern struct edid *drm_get_edid(struct drm_connector *connector,
struct i2c_adapter *adapter); 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 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_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); extern void drm_mode_remove(struct drm_connector *connector, struct drm_display_mode *mode);