From 61e57a8d72f2336faf39b5d940215cf085e01e6e Mon Sep 17 00:00:00 2001 From: Adam Jackson Date: Mon, 29 Mar 2010 21:43:18 +0000 Subject: [PATCH 01/16] 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 Signed-off-by: Dave Airlie --- drivers/gpu/drm/drm_edid.c | 330 +++++++++++++++++++++---------------- 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 + * 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 @@ bool drm_edid_is_valid(struct edid *edid) } 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); From fbcc06b6439024b967cf6927b95d303f4c3c1a4f Mon Sep 17 00:00:00 2001 From: Adam Jackson Date: Mon, 29 Mar 2010 21:43:19 +0000 Subject: [PATCH 02/16] drm/edid: Remove some misleading comments Signed-off-by: Adam Jackson Signed-off-by: Dave Airlie --- drivers/gpu/drm/drm_edid.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index 5e60a6129641..30095d912d63 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -33,11 +33,6 @@ #include "drmP.h" #include "drm_edid.h" -/* - * TODO: - * - support EDID 1.4 (incl. CE blocks) - */ - /* * EDID blocks out in the wild have a variety of bugs, try to collect * them here (note that userspace may work around broken monitors first, @@ -694,9 +689,6 @@ bad_std_timing(u8 a, u8 b) * * Take the standard timing params (in this case width, aspect, and refresh) * and convert them into a real mode using CVT/GTF/DMT. - * - * Punts for now, but should eventually use the FB layer's CVT based mode - * generation code. */ struct drm_display_mode *drm_mode_std(struct drm_device *dev, struct std_timing *t, From 2b470ab075b30aaeeab29d67b8f1f111096a5fbe Mon Sep 17 00:00:00 2001 From: Adam Jackson Date: Mon, 29 Mar 2010 21:43:20 +0000 Subject: [PATCH 03/16] drm/edid: Remove a redundant check Signed-off-by: Adam Jackson Signed-off-by: Dave Airlie --- drivers/gpu/drm/drm_edid.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index 30095d912d63..9c4717ff5f71 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -1016,10 +1016,6 @@ static int add_standard_modes(struct drm_connector *connector, struct edid *edid struct std_timing *t = &edid->standard_timings[i]; struct drm_display_mode *newmode; - /* If std timings bytes are 1, 1 it's empty */ - if (t->hsize == 1 && t->vfreq_aspect == 1) - continue; - newmode = drm_mode_std(dev, &edid->standard_timings[i], edid->revision, timing_level); if (newmode) { From c867df7043b738da4f4d358d7039c243a29b4272 Mon Sep 17 00:00:00 2001 From: Adam Jackson Date: Mon, 29 Mar 2010 21:43:21 +0000 Subject: [PATCH 04/16] drm/edid: Reshuffle mode list construction to closer match the spec Also, document what the spec says to do. Signed-off-by: Adam Jackson Signed-off-by: Dave Airlie --- drivers/gpu/drm/drm_edid.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index 9c4717ff5f71..858fedc33d9b 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -1377,10 +1377,24 @@ int drm_add_edid_modes(struct drm_connector *connector, struct edid *edid) quirks = edid_get_quirks(edid); - num_modes += add_established_modes(connector, edid); - num_modes += add_standard_modes(connector, edid); + /* + * EDID spec says modes should be preferred in this order: + * - preferred detailed mode + * - other detailed modes from base block + * - detailed modes from extension blocks + * - CVT 3-byte code modes + * - standard timing codes + * - established timing codes + * - modes inferred from GTF or CVT range information + * + * We don't quite implement this yet, but we're close. + * + * XXX order for additional mode types in extension blocks? + */ num_modes += add_detailed_info(connector, edid, quirks); num_modes += add_detailed_info_eedid(connector, edid, quirks); + num_modes += add_standard_modes(connector, edid); + num_modes += add_established_modes(connector, edid); if (quirks & (EDID_QUIRK_PREFER_LARGE_60 | EDID_QUIRK_PREFER_LARGE_75)) edid_fixup_preferred(connector, quirks); From 2255be14cb82370a6af4054edb3b4cd170d80752 Mon Sep 17 00:00:00 2001 From: Adam Jackson Date: Mon, 29 Mar 2010 21:43:22 +0000 Subject: [PATCH 05/16] drm/edid: Add modes for Established Timings III section Signed-off-by: Adam Jackson Signed-off-by: Dave Airlie --- drivers/gpu/drm/drm_edid.c | 91 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index 858fedc33d9b..58b67932f04a 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -1134,6 +1134,94 @@ static int drm_cvt_modes(struct drm_connector *connector, return modes; } +static const struct { + short w; + short h; + short r; + short rb; +} est3_modes[] = { + /* byte 6 */ + { 640, 350, 85, 0 }, + { 640, 400, 85, 0 }, + { 720, 400, 85, 0 }, + { 640, 480, 85, 0 }, + { 848, 480, 60, 0 }, + { 800, 600, 85, 0 }, + { 1024, 768, 85, 0 }, + { 1152, 864, 75, 0 }, + /* byte 7 */ + { 1280, 768, 60, 1 }, + { 1280, 768, 60, 0 }, + { 1280, 768, 75, 0 }, + { 1280, 768, 85, 0 }, + { 1280, 960, 60, 0 }, + { 1280, 960, 85, 0 }, + { 1280, 1024, 60, 0 }, + { 1280, 1024, 85, 0 }, + /* byte 8 */ + { 1360, 768, 60, 0 }, + { 1440, 900, 60, 1 }, + { 1440, 900, 60, 0 }, + { 1440, 900, 75, 0 }, + { 1440, 900, 85, 0 }, + { 1400, 1050, 60, 1 }, + { 1400, 1050, 60, 0 }, + { 1400, 1050, 75, 0 }, + /* byte 9 */ + { 1400, 1050, 85, 0 }, + { 1680, 1050, 60, 1 }, + { 1680, 1050, 60, 0 }, + { 1680, 1050, 75, 0 }, + { 1680, 1050, 85, 0 }, + { 1600, 1200, 60, 0 }, + { 1600, 1200, 65, 0 }, + { 1600, 1200, 70, 0 }, + /* byte 10 */ + { 1600, 1200, 75, 0 }, + { 1600, 1200, 85, 0 }, + { 1792, 1344, 60, 0 }, + { 1792, 1344, 85, 0 }, + { 1856, 1392, 60, 0 }, + { 1856, 1392, 75, 0 }, + { 1920, 1200, 60, 1 }, + { 1920, 1200, 60, 0 }, + /* byte 11 */ + { 1920, 1200, 75, 0 }, + { 1920, 1200, 85, 0 }, + { 1920, 1440, 60, 0 }, + { 1920, 1440, 75, 0 }, +}; +static const int num_est3_modes = sizeof(est3_modes) / sizeof(est3_modes[0]); + +static int +drm_est3_modes(struct drm_connector *connector, struct detailed_timing *timing) +{ + int i, j, m, modes = 0; + struct drm_display_mode *mode; + u8 *est = ((u8 *)timing) + 5; + + for (i = 0; i < 6; i++) { + for (j = 7; j > 0; j--) { + m = (i * 8) + (7 - j); + if (m > num_est3_modes) + break; + if (est[i] & (1 << j)) { + mode = drm_find_dmt(connector->dev, + est3_modes[m].w, + est3_modes[m].h, + est3_modes[m].r + /*, est3_modes[m].rb */); + if (mode) { + drm_mode_probed_add(connector, mode); + modes++; + } + } + } + } + + return modes; +} + static int add_detailed_modes(struct drm_connector *connector, struct detailed_timing *timing, struct edid *edid, u32 quirks, int preferred) @@ -1181,6 +1269,9 @@ static int add_detailed_modes(struct drm_connector *connector, case EDID_DETAIL_CVT_3BYTE: modes += drm_cvt_modes(connector, timing); break; + case EDID_DETAIL_EST_TIMINGS: + modes += drm_est3_modes(connector, timing); + break; default: break; } From 7466f4cc508878a8328dff1c328a2b4108888d2e Mon Sep 17 00:00:00 2001 From: Adam Jackson Date: Mon, 29 Mar 2010 21:43:23 +0000 Subject: [PATCH 06/16] drm/edid: Remove arbitrary EDID extension limit Signed-off-by: Adam Jackson Signed-off-by: Dave Airlie --- drivers/gpu/drm/drm_crtc.c | 7 +++++-- drivers/gpu/drm/drm_edid.c | 19 +++++-------------- drivers/gpu/drm/drm_sysfs.c | 2 +- drivers/gpu/drm/radeon/radeon_combios.c | 8 ++++---- include/drm/drm_edid.h | 3 --- 5 files changed, 15 insertions(+), 24 deletions(-) diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c index d91fb8c0b7b3..aa24f2f9dc0a 100644 --- a/drivers/gpu/drm/drm_crtc.c +++ b/drivers/gpu/drm/drm_crtc.c @@ -33,6 +33,7 @@ #include "drm.h" #include "drmP.h" #include "drm_crtc.h" +#include "drm_edid.h" struct drm_prop_enum_list { int type; @@ -2349,7 +2350,7 @@ int drm_mode_connector_update_edid_property(struct drm_connector *connector, struct edid *edid) { struct drm_device *dev = connector->dev; - int ret = 0; + int ret = 0, size; if (connector->edid_blob_ptr) drm_property_destroy_blob(dev, connector->edid_blob_ptr); @@ -2361,7 +2362,9 @@ int drm_mode_connector_update_edid_property(struct drm_connector *connector, return ret; } - connector->edid_blob_ptr = drm_property_create_blob(connector->dev, 128, edid); + size = EDID_LENGTH * (1 + edid->extensions); + connector->edid_blob_ptr = drm_property_create_blob(connector->dev, + size, edid); ret = drm_connector_property_set_value(connector, dev->mode_config.edid_property, diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index 58b67932f04a..cf24ecab6a4a 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -1325,7 +1325,6 @@ static int add_detailed_info_eedid(struct drm_connector *connector, int i, modes = 0; char *edid_ext = NULL; struct detailed_timing *timing; - int edid_ext_num; int start_offset, end_offset; int timing_level; @@ -1342,19 +1341,15 @@ static int add_detailed_info_eedid(struct drm_connector *connector, return 0; } - /* Chose real EDID extension number */ - edid_ext_num = edid->extensions > DRM_MAX_EDID_EXT_NUM ? - DRM_MAX_EDID_EXT_NUM : edid->extensions; - /* Find CEA extension */ - for (i = 0; i < edid_ext_num; i++) { + for (i = 0; i < edid->extensions; 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) { + if (i == edid->extensions) { /* if there is no additional timing EDID block, return */ return 0; } @@ -1393,7 +1388,7 @@ static int add_detailed_info_eedid(struct drm_connector *connector, bool drm_detect_hdmi_monitor(struct edid *edid) { char *edid_ext = NULL; - int i, hdmi_id, edid_ext_num; + int i, hdmi_id; int start_offset, end_offset; bool is_hdmi = false; @@ -1401,19 +1396,15 @@ bool drm_detect_hdmi_monitor(struct edid *edid) if (edid == NULL || edid->extensions == 0) goto end; - /* Chose real EDID extension number */ - edid_ext_num = edid->extensions > DRM_MAX_EDID_EXT_NUM ? - DRM_MAX_EDID_EXT_NUM : edid->extensions; - /* Find CEA extension */ - for (i = 0; i < edid_ext_num; i++) { + for (i = 0; i < edid->extensions; 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) + if (i == edid->extensions) goto end; /* Data block offset in CEA extension block */ diff --git a/drivers/gpu/drm/drm_sysfs.c b/drivers/gpu/drm/drm_sysfs.c index 014ce24761b9..7b7c83f6041a 100644 --- a/drivers/gpu/drm/drm_sysfs.c +++ b/drivers/gpu/drm/drm_sysfs.c @@ -332,7 +332,7 @@ static struct device_attribute connector_attrs_opt1[] = { static struct bin_attribute edid_attr = { .attr.name = "edid", .attr.mode = 0444, - .size = 128, + .size = 0, .read = edid_show, }; diff --git a/drivers/gpu/drm/radeon/radeon_combios.c b/drivers/gpu/drm/radeon/radeon_combios.c index 2becdeda68a3..20f38b85c70a 100644 --- a/drivers/gpu/drm/radeon/radeon_combios.c +++ b/drivers/gpu/drm/radeon/radeon_combios.c @@ -450,17 +450,17 @@ bool radeon_combios_check_hardcoded_edid(struct radeon_device *rdev) { int edid_info; struct edid *edid; + unsigned char *raw; edid_info = combios_get_table_offset(rdev->ddev, COMBIOS_HARDCODED_EDID_TABLE); if (!edid_info) return false; - edid = kmalloc(EDID_LENGTH * (DRM_MAX_EDID_EXT_NUM + 1), - GFP_KERNEL); + raw = rdev->bios + edid_info; + edid = kmalloc(EDID_LENGTH * (raw[0x7e] + 1), GFP_KERNEL); if (edid == NULL) return false; - memcpy((unsigned char *)edid, - (unsigned char *)(rdev->bios + edid_info), EDID_LENGTH); + memcpy((unsigned char *)edid, raw, EDID_LENGTH * (raw[0x7e] + 1)); if (!drm_edid_is_valid(edid)) { kfree(edid); diff --git a/include/drm/drm_edid.h b/include/drm/drm_edid.h index b4209898f115..d33c3e038606 100644 --- a/include/drm/drm_edid.h +++ b/include/drm/drm_edid.h @@ -201,7 +201,4 @@ struct edid { #define EDID_PRODUCT_ID(e) ((e)->prod_code[0] | ((e)->prod_code[1] << 8)) -/* define the number of Extension EDID block */ -#define DRM_MAX_EDID_EXT_NUM 4 - #endif /* __DRM_EDID_H__ */ From 59d8aff6e4fc2705053e7ce2948b51f7fe507536 Mon Sep 17 00:00:00 2001 From: Adam Jackson Date: Mon, 29 Mar 2010 21:43:24 +0000 Subject: [PATCH 07/16] drm/edid: Remove some silly comments Signed-off-by: Adam Jackson Signed-off-by: Dave Airlie --- drivers/gpu/drm/drm_edid.c | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index cf24ecab6a4a..9aa65b289d01 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -1328,31 +1328,20 @@ static int add_detailed_info_eedid(struct drm_connector *connector, int start_offset, end_offset; int timing_level; - if (edid->version == 1 && edid->revision < 3) { - /* If the EDID version is less than 1.3, there is no - * extension EDID. - */ + if (edid->version == 1 && edid->revision < 3) return 0; - } - if (!edid->extensions) { - /* if there is no extension EDID, it is unnecessary to - * parse the E-EDID to get detailed info - */ + if (!edid->extensions) return 0; - } /* Find CEA extension */ for (i = 0; i < edid->extensions; i++) { edid_ext = (char *)edid + EDID_LENGTH * (i + 1); - /* This block is CEA extension */ if (edid_ext[0] == 0x02) break; } - if (i == edid->extensions) { - /* if there is no additional timing EDID block, return */ + if (i == edid->extensions) return 0; - } /* Get the start offset of detailed timing block */ start_offset = edid_ext[2]; From a327f6b806103ee177aba20bb1e42ba7ec7d0f4b Mon Sep 17 00:00:00 2001 From: Adam Jackson Date: Mon, 29 Mar 2010 21:43:25 +0000 Subject: [PATCH 08/16] drm/edid: Fix preferred mode parse for EDID 1.4 In 1.4, the first detailed mode is always the preferred mode. The bit that used to mean that, now means "this mode is the physical size in pixels". Signed-off-by: Adam Jackson Signed-off-by: Dave Airlie --- drivers/gpu/drm/drm_edid.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index 9aa65b289d01..2e1298cf576f 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -1295,7 +1295,10 @@ static int add_detailed_info(struct drm_connector *connector, for (i = 0; i < EDID_DETAILED_TIMINGS; i++) { struct detailed_timing *timing = &edid->detailed_timings[i]; - int preferred = (i == 0) && (edid->features & DRM_EDID_FEATURE_PREFERRED_TIMING); + int preferred = (i == 0); + + if (preferred && edid->version == 1 && edid->revision < 4) + preferred = (edid->features & DRM_EDID_FEATURE_PREFERRED_TIMING); /* In 1.0, only timings are allowed */ if (!timing->pixel_clock && edid->version == 1 && From d1ff6409b1cd2cd2a65df415648fa38b9fdf4cd8 Mon Sep 17 00:00:00 2001 From: Adam Jackson Date: Mon, 29 Mar 2010 21:43:26 +0000 Subject: [PATCH 09/16] drm/edid: Add test for monitor reduced blanking support. The generic block walk callback looks like overkill, but we'll need it for other detailed block walks in the future. Signed-off-by: Adam Jackson Signed-off-by: Dave Airlie --- drivers/gpu/drm/drm_edid.c | 47 ++++++++++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index 2e1298cf576f..a1b6b433e5bc 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -33,6 +33,10 @@ #include "drmP.h" #include "drm_edid.h" +#define EDID_EST_TIMINGS 16 +#define EDID_STD_TIMINGS 8 +#define EDID_DETAILED_TIMINGS 4 + /* * EDID blocks out in the wild have a variety of bugs, try to collect * them here (note that userspace may work around broken monitors first, @@ -670,6 +674,45 @@ static struct drm_display_mode *drm_find_dmt(struct drm_device *dev, return mode; } +typedef void detailed_cb(struct detailed_timing *timing, void *closure); + +static void +drm_for_each_detailed_block(u8 *raw_edid, detailed_cb *cb, void *closure) +{ + int i; + struct edid *edid = (struct edid *)raw_edid; + + if (edid == NULL) + return; + + for (i = 0; i < EDID_DETAILED_TIMINGS; i++) + cb(&(edid->detailed_timings[i]), closure); + + /* XXX extension block walk */ +} + +static void +is_rb(struct detailed_timing *t, void *data) +{ + u8 *r = (u8 *)t; + if (r[3] == EDID_DETAIL_MONITOR_RANGE) + if (r[15] & 0x10) + *(bool *)data = true; +} + +/* EDID 1.4 defines this explicitly. For EDID 1.3, we guess, badly. */ +static bool +drm_monitor_supports_rb(struct edid *edid) +{ + if (edid->revision >= 4) { + bool ret; + drm_for_each_detailed_block((u8 *)edid, is_rb, &ret); + return ret; + } + + return ((edid->input & DRM_EDID_INPUT_DIGITAL) != 0); +} + /* * 0 is reserved. The spec says 0x01 fill for unused timings. Some old * monitors fill with ascii space (0x20) instead. @@ -952,10 +995,6 @@ static struct drm_display_mode edid_est_modes[] = { DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, /* 1152x864@75Hz */ }; -#define EDID_EST_TIMINGS 16 -#define EDID_STD_TIMINGS 8 -#define EDID_DETAILED_TIMINGS 4 - /** * add_established_modes - get est. modes from EDID and add them * @edid: EDID block to scan From b17e52ef7e7961f02baec98872781386218558bc Mon Sep 17 00:00:00 2001 From: Adam Jackson Date: Mon, 29 Mar 2010 21:43:27 +0000 Subject: [PATCH 10/16] drm/edid: Extend range-based mode addition for EDID 1.4 1.4 adds better pixel clock precision, explicit reduced blanking awareness, and extended sync ranges. It's almost like a real spec. Signed-off-by: Adam Jackson Signed-off-by: Dave Airlie --- drivers/gpu/drm/drm_edid.c | 101 ++++++++++++++++++++++++++++--------- 1 file changed, 78 insertions(+), 23 deletions(-) diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index a1b6b433e5bc..e746dd56658a 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -1066,36 +1066,89 @@ static int add_standard_modes(struct drm_connector *connector, struct edid *edid return modes; } +static bool +mode_is_rb(struct drm_display_mode *mode) +{ + return (mode->htotal - mode->hdisplay == 160) && + (mode->hsync_end - mode->hdisplay == 80) && + (mode->hsync_end - mode->hsync_start == 32) && + (mode->vsync_start - mode->vdisplay == 3); +} + +static bool +mode_in_hsync_range(struct drm_display_mode *mode, struct edid *edid, u8 *t) +{ + int hsync, hmin, hmax; + + hmin = t[7]; + if (edid->revision >= 4) + hmin += ((t[4] & 0x04) ? 255 : 0); + hmax = t[8]; + if (edid->revision >= 4) + hmax += ((t[4] & 0x08) ? 255 : 0); + hsync = drm_mode_hsync(mode); + + return (hsync <= hmax && hsync >= hmin); +} + +static bool +mode_in_vsync_range(struct drm_display_mode *mode, struct edid *edid, u8 *t) +{ + int vsync, vmin, vmax; + + vmin = t[5]; + if (edid->revision >= 4) + vmin += ((t[4] & 0x01) ? 255 : 0); + vmax = t[6]; + if (edid->revision >= 4) + vmax += ((t[4] & 0x02) ? 255 : 0); + vsync = drm_mode_vrefresh(mode); + + return (vsync <= vmax && vsync >= vmin); +} + +static u32 +range_pixel_clock(struct edid *edid, u8 *t) +{ + /* unspecified */ + if (t[9] == 0 || t[9] == 255) + return 0; + + /* 1.4 with CVT support gives us real precision, yay */ + if (edid->revision >= 4 && t[10] == 0x04) + return (t[9] * 10000) - ((t[12] >> 2) * 250); + + /* 1.3 is pathetic, so fuzz up a bit */ + return t[9] * 10000 + 5001; +} + /* - * XXX fix this for: - * - GTF secondary curve formula - * - EDID 1.4 range offsets - * - CVT extended bits + * XXX fix this for GTF secondary curve formula */ static bool -mode_in_range(struct drm_display_mode *mode, struct detailed_timing *timing) +mode_in_range(struct drm_display_mode *mode, struct edid *edid, + struct detailed_timing *timing) { - struct detailed_data_monitor_range *range; - int hsync, vrefresh; + u32 max_clock; + u8 *t = (u8 *)timing; - range = &timing->data.other_data.data.range; - - hsync = drm_mode_hsync(mode); - vrefresh = drm_mode_vrefresh(mode); - - if (hsync < range->min_hfreq_khz || hsync > range->max_hfreq_khz) + if (!mode_in_hsync_range(mode, edid, t)) return false; - if (vrefresh < range->min_vfreq || vrefresh > range->max_vfreq) + if (!mode_in_vsync_range(mode, edid, t)) return false; - if (range->pixel_clock_mhz && range->pixel_clock_mhz != 0xff) { - /* be forgiving since it's in units of 10MHz */ - int max_clock = range->pixel_clock_mhz * 10 + 9; - max_clock *= 1000; + if ((max_clock = range_pixel_clock(edid, t))) if (mode->clock > max_clock) return false; - } + + /* 1.4 max horizontal check */ + if (edid->revision >= 4 && t[10] == 0x04) + if (t[13] && mode->hdisplay > 8 * (t[13] + (256 * (t[12]&0x3)))) + return false; + + if (mode_is_rb(mode) && !drm_monitor_supports_rb(edid)) + return false; return true; } @@ -1104,15 +1157,16 @@ mode_in_range(struct drm_display_mode *mode, struct detailed_timing *timing) * XXX If drm_dmt_modes ever regrows the CVT-R modes (and it will) this will * need to account for them. */ -static int drm_gtf_modes_for_range(struct drm_connector *connector, - struct detailed_timing *timing) +static int +drm_gtf_modes_for_range(struct drm_connector *connector, struct edid *edid, + struct detailed_timing *timing) { int i, modes = 0; struct drm_display_mode *newmode; struct drm_device *dev = connector->dev; for (i = 0; i < drm_num_dmt_modes; i++) { - if (mode_in_range(drm_dmt_modes + i, timing)) { + if (mode_in_range(drm_dmt_modes + i, edid, timing)) { newmode = drm_mode_duplicate(dev, &drm_dmt_modes[i]); if (newmode) { drm_mode_probed_add(connector, newmode); @@ -1288,7 +1342,8 @@ static int add_detailed_modes(struct drm_connector *connector, switch (data->type) { case EDID_DETAIL_MONITOR_RANGE: if (gtf) - modes += drm_gtf_modes_for_range(connector, timing); + modes += drm_gtf_modes_for_range(connector, edid, + timing); break; case EDID_DETAIL_STD_MODES: /* Six modes per detailed section */ From a0910c8e37c3bede089e9f9dee63a7eaf9601a43 Mon Sep 17 00:00:00 2001 From: Adam Jackson Date: Mon, 29 Mar 2010 21:43:28 +0000 Subject: [PATCH 11/16] drm/edid: Fix the HDTV hack. Standard timings don't let you say 1366. Both 1360 and 1368 have been seen in the wild. So invent a CVT timing for it. CVT will round 1366 up to 1368; we'll then manually underscan it. Split this into two parts, since we need to do something sneaky between them in the future. Signed-off-by: Adam Jackson Signed-off-by: Dave Airlie --- drivers/gpu/drm/drm_edid.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index e746dd56658a..d7214389c2d6 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -765,15 +765,25 @@ struct drm_display_mode *drm_mode_std(struct drm_device *dev, vsize = (hsize * 4) / 5; else vsize = (hsize * 9) / 16; - /* HDTV hack */ - if (hsize == 1360 && vsize == 765 && vrefresh_rate == 60) { - mode = drm_cvt_mode(dev, hsize, vsize, vrefresh_rate, 0, 0, + + /* HDTV hack, part 1 */ + if (vrefresh_rate == 60 && + ((hsize == 1360 && vsize == 765) || + (hsize == 1368 && vsize == 769))) { + hsize = 1366; + vsize = 768; + } + + /* HDTV hack, part 2 */ + if (hsize == 1366 && vsize == 768 && vrefresh_rate == 60) { + mode = drm_cvt_mode(dev, 1366, 768, vrefresh_rate, 0, 0, false); mode->hdisplay = 1366; mode->vsync_start = mode->vsync_start - 1; mode->vsync_end = mode->vsync_end - 1; return mode; } + mode = NULL; /* check whether it can be found in default mode table */ mode = drm_find_dmt(dev, hsize, vsize, vrefresh_rate); From 7ca6adb37918db21d076a489c6c39490fb34264e Mon Sep 17 00:00:00 2001 From: Adam Jackson Date: Mon, 29 Mar 2010 21:43:29 +0000 Subject: [PATCH 12/16] drm/edid: Strengthen the algorithm for standard mode codes If you have 1920x1200 in both detailed (probably RB) and standard variants, you probably only want the RB version. But we have no way of guessing that from standard mode parse. So, if a mode already exists for a given w/h/r, skip adding it. Signed-off-by: Adam Jackson Signed-off-by: Dave Airlie --- drivers/gpu/drm/drm_edid.c | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index d7214389c2d6..6e999bd03935 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -733,12 +733,12 @@ bad_std_timing(u8 a, u8 b) * Take the standard timing params (in this case width, aspect, and refresh) * and convert them into a real mode using CVT/GTF/DMT. */ -struct drm_display_mode *drm_mode_std(struct drm_device *dev, - struct std_timing *t, - int revision, - int timing_level) +static struct drm_display_mode * +drm_mode_std(struct drm_connector *connector, struct std_timing *t, + int revision, int timing_level) { - struct drm_display_mode *mode; + struct drm_device *dev = connector->dev; + struct drm_display_mode *m, *mode = NULL; int hsize, vsize; int vrefresh_rate; unsigned aspect_ratio = (t->vfreq_aspect & EDID_TIMING_ASPECT_MASK) @@ -774,6 +774,17 @@ struct drm_display_mode *drm_mode_std(struct drm_device *dev, vsize = 768; } + /* + * If this connector already has a mode for this size and refresh + * rate (because it came from detailed or CVT info), use that + * instead. This way we don't have to guess at interlace or + * reduced blanking. + */ + list_for_each_entry(m, &connector->modes, head) + if (m->hdisplay == hsize && m->vdisplay == vsize && + drm_mode_vrefresh(m) == vrefresh_rate) + return NULL; + /* HDTV hack, part 2 */ if (hsize == 1366 && vsize == 768 && vrefresh_rate == 60) { mode = drm_cvt_mode(dev, 1366, 768, vrefresh_rate, 0, 0, @@ -784,7 +795,6 @@ struct drm_display_mode *drm_mode_std(struct drm_device *dev, return mode; } - mode = NULL; /* check whether it can be found in default mode table */ mode = drm_find_dmt(dev, hsize, vsize, vrefresh_rate); if (mode) @@ -1055,17 +1065,15 @@ static int standard_timing_level(struct edid *edid) */ static int add_standard_modes(struct drm_connector *connector, struct edid *edid) { - struct drm_device *dev = connector->dev; int i, modes = 0; int timing_level; timing_level = standard_timing_level(edid); for (i = 0; i < EDID_STD_TIMINGS; i++) { - struct std_timing *t = &edid->standard_timings[i]; struct drm_display_mode *newmode; - newmode = drm_mode_std(dev, &edid->standard_timings[i], + newmode = drm_mode_std(connector, &edid->standard_timings[i], edid->revision, timing_level); if (newmode) { drm_mode_probed_add(connector, newmode); @@ -1362,7 +1370,7 @@ static int add_detailed_modes(struct drm_connector *connector, struct drm_display_mode *newmode; std = &data->data.timings[i]; - newmode = drm_mode_std(dev, std, edid->revision, + newmode = drm_mode_std(connector, std, edid->revision, timing_level); if (newmode) { drm_mode_probed_add(connector, newmode); From 7a37435008b0ffea2442eb1134ddd4adeea81e19 Mon Sep 17 00:00:00 2001 From: Adam Jackson Date: Mon, 29 Mar 2010 21:43:30 +0000 Subject: [PATCH 13/16] drm/edid: Add secondary GTF curve support Before CVT-R, some monitors would advertise support for an alternative GTF formula with lower blanking intervals. Correctly identify such monitors, and use the alternative formula when generating modes for them. Note that we only do this for "standard" timing descriptors (tuples of hsize in characters / aspect ratio / vertical refresh). Range-based mode lists still only refer to the primary GTF curve. It would be possible to do better for the latter case, but monitors are required to support the primary curve over the entire advertised range, so all it would win you is a lower pixel clock and therefore possibly better image quality on analog links. Signed-off-by: Adam Jackson Signed-off-by: Dave Airlie --- drivers/gpu/drm/drm_edid.c | 119 ++++++++++++++++++++++++++++-------- drivers/gpu/drm/drm_modes.c | 81 ++++++++++++++++-------- include/drm/drm_crtc.h | 4 ++ 3 files changed, 150 insertions(+), 54 deletions(-) diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index 6e999bd03935..3924a7bf271a 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -64,7 +64,8 @@ #define LEVEL_DMT 0 #define LEVEL_GTF 1 -#define LEVEL_CVT 2 +#define LEVEL_GTF2 2 +#define LEVEL_CVT 3 static struct edid_quirk { char *vendor; @@ -713,6 +714,71 @@ drm_monitor_supports_rb(struct edid *edid) return ((edid->input & DRM_EDID_INPUT_DIGITAL) != 0); } +static void +find_gtf2(struct detailed_timing *t, void *data) +{ + u8 *r = (u8 *)t; + if (r[3] == EDID_DETAIL_MONITOR_RANGE && r[10] == 0x02) + *(u8 **)data = r; +} + +/* Secondary GTF curve kicks in above some break frequency */ +static int +drm_gtf2_hbreak(struct edid *edid) +{ + u8 *r = NULL; + drm_for_each_detailed_block((u8 *)edid, find_gtf2, &r); + return r ? (r[12] * 2) : 0; +} + +static int +drm_gtf2_2c(struct edid *edid) +{ + u8 *r = NULL; + drm_for_each_detailed_block((u8 *)edid, find_gtf2, &r); + return r ? r[13] : 0; +} + +static int +drm_gtf2_m(struct edid *edid) +{ + u8 *r = NULL; + drm_for_each_detailed_block((u8 *)edid, find_gtf2, &r); + return r ? (r[15] << 8) + r[14] : 0; +} + +static int +drm_gtf2_k(struct edid *edid) +{ + u8 *r = NULL; + drm_for_each_detailed_block((u8 *)edid, find_gtf2, &r); + return r ? r[16] : 0; +} + +static int +drm_gtf2_2j(struct edid *edid) +{ + u8 *r = NULL; + drm_for_each_detailed_block((u8 *)edid, find_gtf2, &r); + return r ? r[17] : 0; +} + +/** + * standard_timing_level - get std. timing level(CVT/GTF/DMT) + * @edid: EDID block to scan + */ +static int standard_timing_level(struct edid *edid) +{ + if (edid->revision >= 2) { + if (edid->revision >= 4 && (edid->features & DRM_EDID_FEATURE_DEFAULT_GTF)) + return LEVEL_CVT; + if (drm_gtf2_hbreak(edid)) + return LEVEL_GTF2; + return LEVEL_GTF; + } + return LEVEL_DMT; +} + /* * 0 is reserved. The spec says 0x01 fill for unused timings. Some old * monitors fill with ascii space (0x20) instead. @@ -734,8 +800,8 @@ bad_std_timing(u8 a, u8 b) * and convert them into a real mode using CVT/GTF/DMT. */ static struct drm_display_mode * -drm_mode_std(struct drm_connector *connector, struct std_timing *t, - int revision, int timing_level) +drm_mode_std(struct drm_connector *connector, struct edid *edid, + struct std_timing *t, int revision) { struct drm_device *dev = connector->dev; struct drm_display_mode *m, *mode = NULL; @@ -745,6 +811,7 @@ drm_mode_std(struct drm_connector *connector, struct std_timing *t, >> EDID_TIMING_ASPECT_SHIFT; unsigned vfreq = (t->vfreq_aspect & EDID_TIMING_VFREQ_MASK) >> EDID_TIMING_VFREQ_SHIFT; + int timing_level = standard_timing_level(edid); if (bad_std_timing(t->hsize, t->vfreq_aspect)) return NULL; @@ -806,6 +873,23 @@ drm_mode_std(struct drm_connector *connector, struct std_timing *t, case LEVEL_GTF: mode = drm_gtf_mode(dev, hsize, vsize, vrefresh_rate, 0, 0); break; + case LEVEL_GTF2: + /* + * This is potentially wrong if there's ever a monitor with + * more than one ranges section, each claiming a different + * secondary GTF curve. Please don't do that. + */ + mode = drm_gtf_mode(dev, hsize, vsize, vrefresh_rate, 0, 0); + if (drm_mode_hsync(mode) > drm_gtf2_hbreak(edid)) { + kfree(mode); + mode = drm_gtf_mode_complex(dev, hsize, vsize, + vrefresh_rate, 0, 0, + drm_gtf2_m(edid), + drm_gtf2_2c(edid), + drm_gtf2_k(edid), + drm_gtf2_2j(edid)); + } + break; case LEVEL_CVT: mode = drm_cvt_mode(dev, hsize, vsize, vrefresh_rate, 0, 0, false); @@ -1042,19 +1126,6 @@ static int add_established_modes(struct drm_connector *connector, struct edid *e return modes; } -/** - * stanard_timing_level - get std. timing level(CVT/GTF/DMT) - * @edid: EDID block to scan - */ -static int standard_timing_level(struct edid *edid) -{ - if (edid->revision >= 2) { - if (edid->revision >= 4 && (edid->features & DRM_EDID_FEATURE_DEFAULT_GTF)) - return LEVEL_CVT; - return LEVEL_GTF; - } - return LEVEL_DMT; -} /** * add_standard_modes - get std. modes from EDID and add them @@ -1066,15 +1137,13 @@ static int standard_timing_level(struct edid *edid) static int add_standard_modes(struct drm_connector *connector, struct edid *edid) { int i, modes = 0; - int timing_level; - - timing_level = standard_timing_level(edid); for (i = 0; i < EDID_STD_TIMINGS; i++) { struct drm_display_mode *newmode; - newmode = drm_mode_std(connector, &edid->standard_timings[i], - edid->revision, timing_level); + newmode = drm_mode_std(connector, edid, + &edid->standard_timings[i], + edid->revision); if (newmode) { drm_mode_probed_add(connector, newmode); modes++; @@ -1140,9 +1209,6 @@ range_pixel_clock(struct edid *edid, u8 *t) return t[9] * 10000 + 5001; } -/* - * XXX fix this for GTF secondary curve formula - */ static bool mode_in_range(struct drm_display_mode *mode, struct edid *edid, struct detailed_timing *timing) @@ -1339,7 +1405,6 @@ static int add_detailed_modes(struct drm_connector *connector, { int i, modes = 0; struct detailed_non_pixel *data = &timing->data.other_data; - int timing_level = standard_timing_level(edid); int gtf = (edid->features & DRM_EDID_FEATURE_DEFAULT_GTF); struct drm_display_mode *newmode; struct drm_device *dev = connector->dev; @@ -1370,8 +1435,8 @@ static int add_detailed_modes(struct drm_connector *connector, struct drm_display_mode *newmode; std = &data->data.timings[i]; - newmode = drm_mode_std(connector, std, edid->revision, - timing_level); + newmode = drm_mode_std(connector, edid, std, + edid->revision); if (newmode) { drm_mode_probed_add(connector, newmode); modes++; diff --git a/drivers/gpu/drm/drm_modes.c b/drivers/gpu/drm/drm_modes.c index 76d63394c776..d460b6c472d4 100644 --- a/drivers/gpu/drm/drm_modes.c +++ b/drivers/gpu/drm/drm_modes.c @@ -276,35 +276,29 @@ struct drm_display_mode *drm_cvt_mode(struct drm_device *dev, int hdisplay, EXPORT_SYMBOL(drm_cvt_mode); /** - * drm_gtf_mode - create the modeline based on GTF algorithm + * drm_gtf_mode_complex - create the modeline based on full GTF algorithm * * @dev :drm device * @hdisplay :hdisplay size * @vdisplay :vdisplay size * @vrefresh :vrefresh rate. * @interlaced :whether the interlace is supported - * @margins :whether the margin is supported + * @margins :desired margin size + * @GTF_[MCKJ] :extended GTF formula parameters * * LOCKING. * none. * - * return the modeline based on GTF algorithm + * return the modeline based on full GTF algorithm. * - * This function is to create the modeline based on the GTF algorithm. - * Generalized Timing Formula is derived from: - * GTF Spreadsheet by Andy Morrish (1/5/97) - * available at http://www.vesa.org - * - * And it is copied from the file of xserver/hw/xfree86/modes/xf86gtf.c. - * What I have done is to translate it by using integer calculation. - * I also refer to the function of fb_get_mode in the file of - * drivers/video/fbmon.c + * GTF feature blocks specify C and J in multiples of 0.5, so we pass them + * in here multiplied by two. For a C of 40, pass in 80. */ -struct drm_display_mode *drm_gtf_mode(struct drm_device *dev, int hdisplay, - int vdisplay, int vrefresh, - bool interlaced, int margins) -{ - /* 1) top/bottom margin size (% of height) - default: 1.8, */ +struct drm_display_mode * +drm_gtf_mode_complex(struct drm_device *dev, int hdisplay, int vdisplay, + int vrefresh, bool interlaced, int margins, + int GTF_M, int GTF_2C, int GTF_K, int GTF_2J) +{ /* 1) top/bottom margin size (% of height) - default: 1.8, */ #define GTF_MARGIN_PERCENTAGE 18 /* 2) character cell horizontal granularity (pixels) - default 8 */ #define GTF_CELL_GRAN 8 @@ -316,17 +310,9 @@ struct drm_display_mode *drm_gtf_mode(struct drm_device *dev, int hdisplay, #define H_SYNC_PERCENT 8 /* min time of vsync + back porch (microsec) */ #define MIN_VSYNC_PLUS_BP 550 - /* blanking formula gradient */ -#define GTF_M 600 - /* blanking formula offset */ -#define GTF_C 40 - /* blanking formula scaling factor */ -#define GTF_K 128 - /* blanking formula scaling factor */ -#define GTF_J 20 /* C' and M' are part of the Blanking Duty Cycle computation */ -#define GTF_C_PRIME (((GTF_C - GTF_J) * GTF_K / 256) + GTF_J) -#define GTF_M_PRIME (GTF_K * GTF_M / 256) +#define GTF_C_PRIME ((((GTF_2C - GTF_2J) * GTF_K / 256) + GTF_2J) / 2) +#define GTF_M_PRIME (GTF_K * GTF_M / 256) struct drm_display_mode *drm_mode; unsigned int hdisplay_rnd, vdisplay_rnd, vfieldrate_rqd; int top_margin, bottom_margin; @@ -470,7 +456,48 @@ struct drm_display_mode *drm_gtf_mode(struct drm_device *dev, int hdisplay, return drm_mode; } +EXPORT_SYMBOL(drm_gtf_mode_complex); + +/** + * drm_gtf_mode - create the modeline based on GTF algorithm + * + * @dev :drm device + * @hdisplay :hdisplay size + * @vdisplay :vdisplay size + * @vrefresh :vrefresh rate. + * @interlaced :whether the interlace is supported + * @margins :whether the margin is supported + * + * LOCKING. + * none. + * + * return the modeline based on GTF algorithm + * + * This function is to create the modeline based on the GTF algorithm. + * Generalized Timing Formula is derived from: + * GTF Spreadsheet by Andy Morrish (1/5/97) + * available at http://www.vesa.org + * + * And it is copied from the file of xserver/hw/xfree86/modes/xf86gtf.c. + * What I have done is to translate it by using integer calculation. + * I also refer to the function of fb_get_mode in the file of + * drivers/video/fbmon.c + * + * Standard GTF parameters: + * M = 600 + * C = 40 + * K = 128 + * J = 20 + */ +struct drm_display_mode * +drm_gtf_mode(struct drm_device *dev, int hdisplay, int vdisplay, int vrefresh, + bool lace, int margins) +{ + return drm_gtf_mode_complex(dev, hdisplay, vdisplay, vrefresh, lace, + margins, 600, 40 * 2, 128, 20 * 2); +} EXPORT_SYMBOL(drm_gtf_mode); + /** * drm_mode_set_name - set the name on a mode * @mode: name will be set in this mode diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h index f74523a299c9..8eb3630ee67d 100644 --- a/include/drm/drm_crtc.h +++ b/include/drm/drm_crtc.h @@ -797,6 +797,10 @@ extern struct drm_display_mode *drm_cvt_mode(struct drm_device *dev, extern struct drm_display_mode *drm_gtf_mode(struct drm_device *dev, int hdisplay, int vdisplay, int vrefresh, bool interlaced, int margins); +extern struct drm_display_mode *drm_gtf_mode_complex(struct drm_device *dev, + int hdisplay, int vdisplay, int vrefresh, + bool interlaced, int margins, int GTF_M, + int GTF_2C, int GTF_K, int GTF_2J); extern int drm_add_modes_noedid(struct drm_connector *connector, int hdisplay, int vdisplay); From 171fdd892979081e8a9b1a67ce86c7008b7abbdf Mon Sep 17 00:00:00 2001 From: Adam Jackson Date: Mon, 29 Mar 2010 21:43:31 +0000 Subject: [PATCH 14/16] drm/modes: Fix interlaced mode names Height in frame size, not field size, and trailed with an 'i'. Matches the X server behaviour. Signed-off-by: Adam Jackson Signed-off-by: Dave Airlie --- drivers/gpu/drm/drm_edid.c | 4 ++-- drivers/gpu/drm/drm_modes.c | 21 ++++++++++++--------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index 3924a7bf271a..0f8c06311cf6 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -1013,10 +1013,10 @@ static struct drm_display_mode *drm_mode_detailed(struct drm_device *dev, if (mode->vsync_end > mode->vtotal) mode->vtotal = mode->vsync_end + 1; - drm_mode_set_name(mode); - drm_mode_do_interlace_quirk(mode, pt); + drm_mode_set_name(mode); + if (quirks & EDID_QUIRK_DETAILED_SYNC_PP) { pt->misc |= DRM_EDID_PT_HSYNC_POSITIVE | DRM_EDID_PT_VSYNC_POSITIVE; } diff --git a/drivers/gpu/drm/drm_modes.c b/drivers/gpu/drm/drm_modes.c index d460b6c472d4..8840066a5775 100644 --- a/drivers/gpu/drm/drm_modes.c +++ b/drivers/gpu/drm/drm_modes.c @@ -258,8 +258,10 @@ struct drm_display_mode *drm_cvt_mode(struct drm_device *dev, int hdisplay, drm_mode->clock -= drm_mode->clock % CVT_CLOCK_STEP; /* 18/16. Find actual vertical frame frequency */ /* ignore - just set the mode flag for interlaced */ - if (interlaced) + if (interlaced) { drm_mode->vtotal *= 2; + drm_mode->flags |= DRM_MODE_FLAG_INTERLACE; + } /* Fill the mode line name */ drm_mode_set_name(drm_mode); if (reduced) @@ -268,10 +270,8 @@ struct drm_display_mode *drm_cvt_mode(struct drm_device *dev, int hdisplay, else drm_mode->flags |= (DRM_MODE_FLAG_PVSYNC | DRM_MODE_FLAG_NHSYNC); - if (interlaced) - drm_mode->flags |= DRM_MODE_FLAG_INTERLACE; - return drm_mode; + return drm_mode; } EXPORT_SYMBOL(drm_cvt_mode); @@ -446,14 +446,14 @@ drm_gtf_mode_complex(struct drm_device *dev, int hdisplay, int vdisplay, drm_mode->clock = pixel_freq; - drm_mode_set_name(drm_mode); - drm_mode->flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC; - if (interlaced) { drm_mode->vtotal *= 2; drm_mode->flags |= DRM_MODE_FLAG_INTERLACE; } + drm_mode_set_name(drm_mode); + drm_mode->flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC; + return drm_mode; } EXPORT_SYMBOL(drm_gtf_mode_complex); @@ -509,8 +509,11 @@ EXPORT_SYMBOL(drm_gtf_mode); */ void drm_mode_set_name(struct drm_display_mode *mode) { - snprintf(mode->name, DRM_DISPLAY_MODE_LEN, "%dx%d", mode->hdisplay, - mode->vdisplay); + bool interlaced = !!(mode->flags & DRM_MODE_FLAG_INTERLACE); + + snprintf(mode->name, DRM_DISPLAY_MODE_LEN, "%dx%d%s", + mode->hdisplay, mode->vdisplay, + interlaced ? "i" : ""); } EXPORT_SYMBOL(drm_mode_set_name); From c385e50cb51ace73ebe12d57df76882e9dcf0e53 Mon Sep 17 00:00:00 2001 From: Adam Jackson Date: Thu, 8 Apr 2010 19:00:30 +0000 Subject: [PATCH 15/16] drm/edid: Fix sync polarity for secondary GTF curve Signed-off-by: Adam Jackson Signed-off-by: Dave Airlie --- drivers/gpu/drm/drm_modes.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/drm_modes.c b/drivers/gpu/drm/drm_modes.c index 8840066a5775..f1f473ea97d3 100644 --- a/drivers/gpu/drm/drm_modes.c +++ b/drivers/gpu/drm/drm_modes.c @@ -452,7 +452,10 @@ drm_gtf_mode_complex(struct drm_device *dev, int hdisplay, int vdisplay, } drm_mode_set_name(drm_mode); - drm_mode->flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC; + if (GTF_M == 600 && GTF_2C == 80 && GTF_K == 128 && GTF_2J == 40) + drm_mode->flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC; + else + drm_mode->flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC; return drm_mode; } From 522032da7ed3068cf79f733fb836118d908b7719 Mon Sep 17 00:00:00 2001 From: Adam Jackson Date: Fri, 9 Apr 2010 16:52:49 +0000 Subject: [PATCH 16/16] drm/edid: When checking duplicate standard modes, walked the probed list ... and not the global list. Signed-off-by: Adam Jackson Signed-off-by: Dave Airlie --- drivers/gpu/drm/drm_edid.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index 0f8c06311cf6..df7a1abfe7e8 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -847,7 +847,7 @@ drm_mode_std(struct drm_connector *connector, struct edid *edid, * instead. This way we don't have to guess at interlace or * reduced blanking. */ - list_for_each_entry(m, &connector->modes, head) + list_for_each_entry(m, &connector->probed_modes, head) if (m->hdisplay == hsize && m->vdisplay == vsize && drm_mode_vrefresh(m) == vrefresh_rate) return NULL;