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:
parent
95beb69017
commit
61e57a8d72
@ -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
|
||||||
/**
|
/**
|
||||||
|
@ -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);
|
||||||
|
Loading…
Reference in New Issue
Block a user