mirror of
https://github.com/torvalds/linux.git
synced 2024-12-02 17:11:33 +00:00
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) 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
|
||||
/**
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user