From patchwork Fri Aug 10 04:23:11 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ian Pilcher X-Patchwork-Id: 1303941 Return-Path: X-Original-To: patchwork-dri-devel@patchwork.kernel.org Delivered-To: patchwork-process-083081@patchwork2.kernel.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) by patchwork2.kernel.org (Postfix) with ESMTP id 64E55DFFEB for ; Fri, 10 Aug 2012 04:24:32 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id 48302A0F90 for ; Thu, 9 Aug 2012 21:24:32 -0700 (PDT) X-Original-To: dri-devel@lists.freedesktop.org Delivered-To: dri-devel@lists.freedesktop.org Received: from mail-ob0-f177.google.com (mail-ob0-f177.google.com [209.85.214.177]) by gabe.freedesktop.org (Postfix) with ESMTP id 9B4BE9EF79 for ; Thu, 9 Aug 2012 21:23:32 -0700 (PDT) Received: by obbta17 with SMTP id ta17so1669262obb.36 for ; Thu, 09 Aug 2012 21:23:32 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=from:to:cc:subject:date:message-id:x-mailer:in-reply-to:references; bh=XbED5+U5/xsX0f6ID7bcEQO0TRiJBy/EVYmslFbHY5s=; b=NQ0WAi7/XnSrae5LCRu/m/9YKqv8t18ig/WXOdwnOjdtczHKKDde7EOGe4VdJdBtBo j8jUN47Ka+RG0O6Bvzs40mw3ff+mow/aybU7o56hPxLYPNLZpEGF/X86rRf7BBUJPbRs 52eH3PctNsZLnv9h4ZX55MbvNLeV1mjWnjnZX8IlKE5ux3J4YO0eYcDdQqwlcTAAUWVB q09l5qF2vWF18LGYzgU31ffzO2KEYfe+oYKLErMy3d7yMq7CPbjNOyF/61elL4oSvesv HLzBPr/TNX7ZSBSpevMBeSRhUF2pToOlchyaHEoQQi/IRdYaaFAtdJHCmQW3U1GFT19h f4/g== Received: by 10.60.169.134 with SMTP id ae6mr2311941oec.55.1344572612062; Thu, 09 Aug 2012 21:23:32 -0700 (PDT) Received: from ian.icp.selfip.net (pool-71-170-109-80.dllstx.fios.verizon.net. [71.170.109.80]) by mx.google.com with ESMTPS id x10sm1945141oeb.8.2012.08.09.21.23.31 (version=TLSv1/SSLv3 cipher=OTHER); Thu, 09 Aug 2012 21:23:31 -0700 (PDT) From: Ian Pilcher To: ajax@redhat.com Subject: [PATCH] drm: EDID quirk improvements Date: Thu, 9 Aug 2012 23:23:11 -0500 Message-Id: <1344572591-14480-2-git-send-email-arequipeno@gmail.com> X-Mailer: git-send-email 1.7.11.2 In-Reply-To: <1344572591-14480-1-git-send-email-arequipeno@gmail.com> References: <1336426728.20580.80.camel@atropine> <1344572591-14480-1-git-send-email-arequipeno@gmail.com> Cc: Ian Pilcher , dri-devel@lists.freedesktop.org X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.13 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: dri-devel-bounces+patchwork-dri-devel=patchwork.kernel.org@lists.freedesktop.org Errors-To: dri-devel-bounces+patchwork-dri-devel=patchwork.kernel.org@lists.freedesktop.org Add ability for user to add or remove EDID quirks, via module parameter or sysfs. Also add two new quirk flags -- EDID_QUIRK_DISABLE_INFOFRAMES and EDID_QUIRK_NO_AUDIO -- and adds a quirk for the LG L246WP display. Document module parameter and sysfs interface. --- Documentation/EDID/edid_quirks.txt | 161 ++++++++++++ drivers/gpu/drm/drm_drv.c | 2 + drivers/gpu/drm/drm_edid.c | 524 +++++++++++++++++++++++++++++++++---- drivers/gpu/drm/drm_stub.c | 5 + drivers/gpu/drm/drm_sysfs.c | 19 ++ include/drm/drmP.h | 10 + include/drm/drm_edid.h | 13 +- 7 files changed, 673 insertions(+), 61 deletions(-) create mode 100644 Documentation/EDID/edid_quirks.txt diff --git a/Documentation/EDID/edid_quirks.txt b/Documentation/EDID/edid_quirks.txt new file mode 100644 index 0000000..07a0087 --- /dev/null +++ b/Documentation/EDID/edid_quirks.txt @@ -0,0 +1,161 @@ + EDID Quirks + ============= + Ian Pilcher + August 8, 2012 + + + "EDID blocks out in the wild have a variety of bugs" + -- from drivers/gpu/drm/drm_edid.c + + +Overview +======== + +EDID quirks provide a mechanism for working around display hardware with buggy +EDID data. + +An individual EDID quirk maps a display type (identified by its EDID +manufacturer ID and product code[1]) to a set of flags. For example, the current +list of quirks built into the kernel is: + + ACR:0xad46:0x00000001 + API:0x7602:0x00000001 + ACR:0x0977:0x00000020 + MAX:0x05ec:0x00000001 + MAX:0x077e:0x00000001 + EPI:0xe780:0x00000002 + EPI:0x2028:0x00000001 + FCM:0x3520:0x0000000c + LPL:0x0000:0x00000010 + LPL:0x2a00:0x00000010 + PHL:0xe014:0x00000020 + PTS:0x02fd:0x00000020 + SAM:0x021d:0x00000040 + SAM:0x0254:0x00000001 + SAM:0x027e:0x00000001 + VSC:0x139c:0x00000080 + GSM:0x563f:0x00000300 + +The first field of each quirk is the manufacturer ID, the second field is the +product code, and the third field is the quirk flags. + +NOTE: All of the manufacturer IDs above are displayed as 3-character strings, + because they are conformant IDs that have been properly encoded: + + - The most-significant bit of the encoded ID is 0 + - They only contain ASCII characters in the range A-Z + + IDs that do not conform to these rules are displayed as "raw" hexadecimal + values. + +The current quirk flags are: + + /* First detailed mode wrong, use largest 60Hz mode */ + #define EDID_QUIRK_PREFER_LARGE_60 0x00000001 + + /* Reported 135MHz pixel clock is too high, needs adjustment */ + #define EDID_QUIRK_135_CLOCK_TOO_HIGH 0x00000002 + + /* Prefer the largest mode at 75 Hz */ + #define EDID_QUIRK_PREFER_LARGE_75 0x00000004 + + /* Detail timing is in cm not mm */ + #define EDID_QUIRK_DETAILED_IN_CM 0x00000008 + + /* Detailed timing descriptors have bogus size values, so just take the + * maximum size and use that. + */ + #define EDID_QUIRK_DETAILED_USE_MAXIMUM_SIZE 0x00000010 + + /* Monitor forgot to set the first detailed is preferred bit. */ + #define EDID_QUIRK_FIRST_DETAILED_PREFERRED 0x00000020 + + /* use +hsync +vsync for detailed mode */ + #define EDID_QUIRK_DETAILED_SYNC_PP 0x00000040 + + /* Force reduced-blanking timings for detailed modes */ + #define EDID_QUIRK_FORCE_REDUCED_BLANKING 0x00000080 + + /* Display is confused by InfoFrames; don't sent any */ + #define EDID_QUIRK_DISABLE_INFOFRAMES 0x00000100 + + /* Display doesn't have any audio output */ + #define EDID_QUIRK_NO_AUDIO 0x00000200 + + +sysfs interface +=============== + +The current EDID quirk list can be read from /sys/class/drm/edid_quirks. + +The number of total "slots" in the list can be read from +/sys/class/drm/edid_quirks_size. This total includes both occupied slots (i.e. +the current list) and any slots available for additional quirks. The number of +available slots can be calculated by subtracting the number of quirks in the +current list from the total number of slots. + +If a slot is available, an additional quirk can be added to the list by writing +it to edid_quirks: + + # echo FOO:0xffff:0x100 > /sys/class/drm/edid_quirks + +Manufacturer IDs can also be specified numerically. (This is the only way to +specify a nonconformant ID.) This command is equivalent to the previous one: + + # echo 0x19ef:0xffff:0x100 > /sys/class/drm/edid_quirks + +Numeric values can also be specified in decimal or octal formats; a number that +begins with a 0 is assumed to be octal: + + # echo FOO:65535:0400 > /sys/class/drm/edid_quirks + +An existing quirk can be replaced by writing a new set of flags: + + # echo FOO:0xffff:0x200 > /sys/class/drm/edid_quirks + +A quirk can be deleted from the list by writing an empty flag set (0). This +makes the list slot occupied by that quirk available. + + # echo FOO:0xffff:0 > /sys/class/drm/edid_quirks + +Writing the a single period (.) clears the entire quirk list: + + # echo . > /sys/class/drm/edid_quirks + +Multiple changes to the list can be specified in a comma (or newline) separated +list. For example, the following command clears all of the existing quirks in +the list and adds 3 new quirks: + + # echo .,FOO:0xffff:0x100,BAR:0x1111:0x001,BAZ:0x2222:0x002 > \ + /sys/class/drm/edid_quirks + +Note however, that any error (an incorrectly formatted quirk or an attempt to +add a quirk when no slot is available) will abort processing of any further +changes, potentially making it difficult to determine exactly which change +caused the error and what changes were made. For this reason, making changes +one at a time is recommended, particularly if the changes are being made by a +script or program. + + +Module parameter +================ + +The EDID quirk list can also be modified via the edid_quirks module parameter +(drm.edid_quirks on the kernel command line). The effect of setting this +parameter is identical to the effect of writing its value to +/sys/class/drm/edid_quirks, with one important difference. When an error is +encountered during module parameter parsing or processing, any remaining quirks +in the parameter string will still be processed. (It is hoped that this approach +maximizes the probability of producing a working display.) + + +Follow-up +========= + +If you encounter a display that requires an additional EDID quirk in order to +function properly, please report it to the direct rendering development mailing +list . + + +[1] See http://en.wikipedia.org/wiki/Extended_display_identification_data for a + description of the manufacturer ID and product code fields. diff --git a/drivers/gpu/drm/drm_drv.c b/drivers/gpu/drm/drm_drv.c index 9238de4..7fe39e0 100644 --- a/drivers/gpu/drm/drm_drv.c +++ b/drivers/gpu/drm/drm_drv.c @@ -276,6 +276,8 @@ static int __init drm_core_init(void) goto err_p3; } + drm_edid_quirks_param_process(); + DRM_INFO("Initialized %s %d.%d.%d %s\n", CORE_NAME, CORE_MAJOR, CORE_MINOR, CORE_PATCHLEVEL, CORE_DATE); return 0; diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index a8743c3..6a5280a 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -31,6 +31,7 @@ #include #include #include +#include #include "drmP.h" #include "drm_edid.h" #include "drm_edid_modes.h" @@ -68,6 +69,15 @@ #define EDID_QUIRK_DETAILED_SYNC_PP (1 << 6) /* Force reduced-blanking timings for detailed modes */ #define EDID_QUIRK_FORCE_REDUCED_BLANKING (1 << 7) +/* Display is confused by InfoFrames; don't send any */ +#define EDID_QUIRK_DISABLE_INFOFRAMES (1 << 8) +/* Display doesn't have any audio output */ +#define EDID_QUIRK_NO_AUDIO (1 << 9) + +/* + * When adding additional quirk flags, please update + * Documentation/EDID/edid_quirks.txt. + */ struct detailed_mode_closure { struct drm_connector *connector; @@ -82,51 +92,457 @@ struct detailed_mode_closure { #define LEVEL_GTF2 2 #define LEVEL_CVT 3 -static struct edid_quirk { - char vendor[4]; - int product_id; - u32 quirks; -} edid_quirk_list[] = { +union edid_quirk { + struct { + union edid_display_id display_id; + u32 quirks; + } __attribute__((packed)) s; + u64 u; +}; + +#define EDID_MFG_ID(c1, c2, c3) cpu_to_be16( \ + (c1 & 0x1f) << 10 | \ + (c2 & 0x1f) << 5 | \ + (c3 & 0x1f) \ + ) + +#define EDID_QUIRK_LIST_SIZE 24 + +union edid_quirk edid_quirk_list[EDID_QUIRK_LIST_SIZE] = { + /* Acer AL1706 */ - { "ACR", 44358, EDID_QUIRK_PREFER_LARGE_60 }, + { { { { EDID_MFG_ID('A', 'C', 'R'), cpu_to_le16(44358) } }, + EDID_QUIRK_PREFER_LARGE_60 } }, /* Acer F51 */ - { "API", 0x7602, EDID_QUIRK_PREFER_LARGE_60 }, + { { { { EDID_MFG_ID('A', 'P', 'I'), cpu_to_le16(0x7602) } }, + EDID_QUIRK_PREFER_LARGE_60 } }, /* Unknown Acer */ - { "ACR", 2423, EDID_QUIRK_FIRST_DETAILED_PREFERRED }, + { { { { EDID_MFG_ID('A', 'C', 'R'), cpu_to_le16(2423) } }, + EDID_QUIRK_FIRST_DETAILED_PREFERRED } }, /* Belinea 10 15 55 */ - { "MAX", 1516, EDID_QUIRK_PREFER_LARGE_60 }, - { "MAX", 0x77e, EDID_QUIRK_PREFER_LARGE_60 }, + { { { { EDID_MFG_ID('M', 'A', 'X'), cpu_to_le16(1516) } }, + EDID_QUIRK_PREFER_LARGE_60 } }, + { { { { EDID_MFG_ID('M', 'A', 'X'), cpu_to_le16(0x77e) } }, + EDID_QUIRK_PREFER_LARGE_60 } }, /* Envision Peripherals, Inc. EN-7100e */ - { "EPI", 59264, EDID_QUIRK_135_CLOCK_TOO_HIGH }, + { { { { EDID_MFG_ID('E', 'P', 'I'), cpu_to_le16(59264) } }, + EDID_QUIRK_135_CLOCK_TOO_HIGH } }, /* Envision EN2028 */ - { "EPI", 8232, EDID_QUIRK_PREFER_LARGE_60 }, + { { { { EDID_MFG_ID('E', 'P', 'I'), cpu_to_le16(8232) } }, + EDID_QUIRK_PREFER_LARGE_60 } }, /* Funai Electronics PM36B */ - { "FCM", 13600, EDID_QUIRK_PREFER_LARGE_75 | - EDID_QUIRK_DETAILED_IN_CM }, + { { { { EDID_MFG_ID('F', 'C', 'M'), cpu_to_le16(13600) } }, + EDID_QUIRK_PREFER_LARGE_75 | EDID_QUIRK_DETAILED_IN_CM } }, /* LG Philips LCD LP154W01-A5 */ - { "LPL", 0, EDID_QUIRK_DETAILED_USE_MAXIMUM_SIZE }, - { "LPL", 0x2a00, EDID_QUIRK_DETAILED_USE_MAXIMUM_SIZE }, + { { { { EDID_MFG_ID('L', 'P', 'L'), cpu_to_le16(0) } }, + EDID_QUIRK_DETAILED_USE_MAXIMUM_SIZE } }, + { { { { EDID_MFG_ID('L', 'P', 'L'), cpu_to_le16(0x2a00) } }, + EDID_QUIRK_DETAILED_USE_MAXIMUM_SIZE } }, /* Philips 107p5 CRT */ - { "PHL", 57364, EDID_QUIRK_FIRST_DETAILED_PREFERRED }, + { { { { EDID_MFG_ID('P', 'H', 'L'), cpu_to_le16(57364) } }, + EDID_QUIRK_FIRST_DETAILED_PREFERRED } }, /* Proview AY765C */ - { "PTS", 765, EDID_QUIRK_FIRST_DETAILED_PREFERRED }, + { { { { EDID_MFG_ID('P', 'T', 'S'), cpu_to_le16(765) } }, + EDID_QUIRK_FIRST_DETAILED_PREFERRED } }, /* Samsung SyncMaster 205BW. Note: irony */ - { "SAM", 541, EDID_QUIRK_DETAILED_SYNC_PP }, + { { { { EDID_MFG_ID('S', 'A', 'M'), cpu_to_le16(541) } }, + EDID_QUIRK_DETAILED_SYNC_PP } }, /* Samsung SyncMaster 22[5-6]BW */ - { "SAM", 596, EDID_QUIRK_PREFER_LARGE_60 }, - { "SAM", 638, EDID_QUIRK_PREFER_LARGE_60 }, + { { { { EDID_MFG_ID('S', 'A', 'M'), cpu_to_le16(596) } }, + EDID_QUIRK_PREFER_LARGE_60 } }, + { { { { EDID_MFG_ID('S', 'A', 'M'), cpu_to_le16(638) } }, + EDID_QUIRK_PREFER_LARGE_60 } }, /* ViewSonic VA2026w */ - { "VSC", 5020, EDID_QUIRK_FORCE_REDUCED_BLANKING }, + { { { { EDID_MFG_ID('V', 'S', 'C'), cpu_to_le16(5020) } }, + EDID_QUIRK_FORCE_REDUCED_BLANKING } }, + + /* LG L246WP */ + { { { { EDID_MFG_ID('G', 'S', 'M'), cpu_to_le16(0x563f) } }, + EDID_QUIRK_DISABLE_INFOFRAMES | EDID_QUIRK_NO_AUDIO } }, + + /* + * When adding built-in quirks, please adjust EDID_QUIRK_LIST_SIZE to + * provide some room for user-supplied quirks. + */ }; +DEFINE_MUTEX(edid_quirk_list_mutex); + +/** + * drm_edid_mfg_format - format an "encoded" EDID manufacturer ID for printing + * @mfg_id: the encoded manufacturer ID + * @buf: destination buffer for the formated manufacturer ID (minimum 7 bytes) + * @strip: if non-zero, the returned pointer will skip any leading spaces + * + * An EDID manufacturer ID is supposed to consist of 3 capital letters (A-Z). + * Each letter is stored as a 5-bit value between 1 and 26, taking up 15 bits of + * the 16-bit ID. The remaining bit should always be 0. If display manufacturers + * always did things correctly, however, EDID quirks wouldn't be required in + * the first place. This function does the following: + * + * - Broken IDs are printed in hexadecimal (0xffff). + * - "Correct" IDs are formatted as a 3-letter ID string, preceded by 3 spaces; + * the spaces ensure that both output formats are the same length. + * + * Thus, a formatted manufacturer ID is always 6 characters long (not including + * the terminating 0). + * + * If @strip is 0, or the manufacturer ID has been formatted as a hexadecimal + * number, @buf is returned. If @strip is non-zero, and the manufacturer ID has + * been formatted as a 3-letter string, a pointer to the first non-space + * character (@buf + 3) is returned. + */ +static const char *drm_edid_mfg_format(__be16 mfg_id, char *buf, int strip) +{ + u16 id = be16_to_cpu(mfg_id); + + if (id & 0x8000) + goto bad_id; + + buf[3] = ((id & 0x7c00) >> 10) + '@'; + if (!isupper(buf[3])) + goto bad_id; + + buf[4] = ((id & 0x03e0) >> 5) + '@'; + if (!isupper(buf[4])) + goto bad_id; + + buf[5] = (id & 0x001f) + '@'; + if (!isupper(buf[5])) + goto bad_id; + + memset(buf, ' ', 3); + buf[6] = 0; + + return strip ? (buf + 3) : buf; + +bad_id: + sprintf(buf, "0x%04hx", id); + return buf; +} + +#define EDID_MFG_BUF_SIZE 7 + +/** + * drm_edid_display_id_format - format an EDID "display ID" (manufacturer ID + * and product code) for printing + * @display_id: the display ID + * @buf: destination buffer for the formatted display ID (minimum 14 bytes) + * @strip: if non-zero, the returned pointer will skip any leading spaces + * + * A formatted display ID is always 13 characters long (not including the + * terminating 0). + * + * If @strip is 0, or the manufacturer ID has been formatted as a hexadecimal + * number, @buf is returned. If @strip is non-zero, and the manufacturer ID has + * been formatted as a 3-letter string, a pointer to the first non-space + * character (@buf + 3) is returned. + */ +static const char *drm_edid_display_id_format(union edid_display_id display_id, + char *buf, int strip) +{ + const char *s; + + s = drm_edid_mfg_format(display_id.s.mfg_id, buf, strip); + sprintf(buf + EDID_MFG_BUF_SIZE - 1, ":0x%04hx", + le16_to_cpu(display_id.s.prod_code)); + + return s; +} + +#define EDID_DISPLAY_ID_BUF_SIZE (EDID_MFG_BUF_SIZE + 7) + +/** + * drm_edid_quirk_format - format an EDID quirk for printing + * @quirk: the quirk + * @buf: destination buffer for the formatted quirk (minimum 25 bytes) + * @strip: if non-zero, the returned pointer will skip any leading spaces + * + * A formatted EDID quirk is always 24 characters long (not including the + * terminating 0). + * + * If @strip is 0, or the manufacturer ID has been formatted as a hexadecimal + * number, @buf is returned. If @strip is non-zero, and the manufacturer ID has + * been formatted as a 3-letter string, a pointer to the first non-space + * character (@buf + 3) is returned. + */ +static const char *drm_edid_quirk_format(const union edid_quirk *quirk, + char *buf, int strip) +{ + const char *s; + + s = drm_edid_display_id_format(quirk->s.display_id, buf, strip); + sprintf(buf + EDID_DISPLAY_ID_BUF_SIZE - 1, ":0x%08x", quirk->s.quirks); + + return s; +} + +#define EDID_QUIRK_BUF_SIZE (EDID_DISPLAY_ID_BUF_SIZE + 11) + +/** + * drm_edid_quirk_parse - parse an EDID quirk + * @s: string containing the quirk to be parsed + * @quirk: destination for parsed quirk + * + * Returns 0 on success, < 0 (currently -EINVAL) on error. + */ +static int drm_edid_quirk_parse(const char *s, union edid_quirk *quirk) +{ + char buf[EDID_QUIRK_BUF_SIZE]; + s32 mfg; + s32 product; + s64 quirks; + char *c; + + if (sscanf(s, "%i:%i:%lli", &mfg, &product, &quirks) == 3) { + if (mfg < 0 || mfg > 0xffff) + goto error; + quirk->s.display_id.s.mfg_id = cpu_to_be16((u16)mfg); + } else { + if (sscanf(s, "%3s:%i:%lli", buf, &product, &quirks) != 3 || + !isupper(buf[0]) || + !isupper(buf[1]) || + !isupper(buf[2])) + goto error; + quirk->s.display_id.s.mfg_id = + EDID_MFG_ID(buf[0], buf[1], buf[2]); + } + + if (product < 0 || product > 0xffff || + quirks < 0 || quirks > 0xffffffffLL) + goto error; + + quirk->s.display_id.s.prod_code = cpu_to_le16((u16)product); + quirk->s.quirks = (u32)quirks; + + DRM_DEBUG("Successfully parsed EDID quirk: %s\n", + drm_edid_quirk_format(quirk, buf, 1)); + + return 0; + +error: + c = strpbrk(s, ",\n"); + if (c == NULL) { + printk(KERN_WARNING "Invalid EDID quirk: '%s'\n", s); + } else { + printk(KERN_WARNING "Invalid EDID quirk: '%.*s'\n", + (int)(c - s), s); + } + + return -EINVAL; +} + +/** + * drm_edid_quirk_find_by_id - find the EDID quirk matching a display ID + * @display_id: the display ID to match + * + * Caller MUST hold edid_quirk_list_mutex. + * + * Returns a pointer to the matching quirk list entry, NULL if no such entry + * exists. + */ +static union edid_quirk *drm_edid_quirk_find_by_id(union edid_display_id id) +{ + union edid_quirk *q = edid_quirk_list; + + do { + if (q->s.display_id.u == id.u && q->s.quirks != 0) + return q; + } while (++q < edid_quirk_list + ARRAY_SIZE(edid_quirk_list)); + + return NULL; +} + +/** + * drm_edid_quirk_find_slot - find an empty slot in the EDID quirk list + * + * Caller MUST hold edid_quirk_list_mutex. + * + * Returns a pointer to the first empty slot, NULL if no empty slots exist. + */ +static union edid_quirk *drm_edid_quirk_find_empty(void) +{ + union edid_quirk *q = edid_quirk_list; + + do { + if (q->s.quirks == 0) + return q; + } while (++q < edid_quirk_list + ARRAY_SIZE(edid_quirk_list)); + + return NULL; +} + +/** + * drm_edid_quirk_process - process a newly parsed EDID quirk + * @quirk: the quirk to be processed + * + * Depending on the newly parsed quirk and the contents of the quirks list, this + * function will clear the quirks list, remove a single quirk from the list, add + * a new quirk to the list, or replace an existing quirk. + * + * Returns 0 on success, < 0 on error (-ENOSPC if there is no free slot for a + * new quirk). Note that trying to remove a quirk that isn't present is not + * considered an error. + */ +static int drm_edid_quirk_process(const union edid_quirk *quirk) +{ + char buf[EDID_QUIRK_BUF_SIZE]; + union edid_quirk *q; + int res = 0; + + mutex_lock(&edid_quirk_list_mutex); + + if (quirk->s.quirks == 0) { + DRM_INFO("Removing EDID quirk for display %s\n", + drm_edid_display_id_format(quirk->s.display_id, + buf, 1)); + q = drm_edid_quirk_find_by_id(quirk->s.display_id); + if (q == NULL) { + printk(KERN_WARNING "No quirk found for display %s\n", + drm_edid_display_id_format(quirk->s.display_id, + buf, 1)); + } else { + q->u = 0; + } + } else { + DRM_INFO("Adding EDID quirk: %s\n", + drm_edid_quirk_format(quirk, buf, 1)); + q = drm_edid_quirk_find_by_id(quirk->s.display_id); + if (q == NULL) { + q = drm_edid_quirk_find_empty(); + if (q == NULL) { + printk(KERN_WARNING + "No free slot in EDID quirk list\n"); + res = -ENOSPC; + } else { + q->u = quirk->u; + } + } else { + DRM_INFO("Replacing existing quirk: %s\n", + drm_edid_quirk_format(q, buf, 1)); + q->s.quirks = quirk->s.quirks; + } + } + + mutex_unlock(&edid_quirk_list_mutex); + + return res; +} + +/** + * drm_edid_quirks_process - parse and process a comma separated list of EDID + * quirks + * @s: string containing the quirks to be processed + * @strict: if non-zero, any parsing or processing error aborts further + * processing + * + * Returns 0 on success, < 0 if any error is encountered. (If multiple errors + * occur when strict is set to 0, the last error encountered is returned.) + */ +static int drm_edid_quirks_process(const char *s, int strict) +{ + union edid_quirk quirk; + int res = 0; + + do { + + if (*s == '.') { + DRM_INFO("Clearing EDID quirk list\n"); + mutex_lock(&edid_quirk_list_mutex); + memset(edid_quirk_list, 0, sizeof edid_quirk_list); + mutex_unlock(&edid_quirk_list_mutex); + } else { + res = drm_edid_quirk_parse(s, &quirk); + if (res != 0) { + if (strict) + goto error; + continue; + } + + res = drm_edid_quirk_process(&quirk); + if (res != 0) { + if (strict) + goto error; + } + } + + s = strpbrk(s, ",\n"); + + } while (s != NULL && *(++s) != 0); + + return res; + +error: + printk(KERN_WARNING "Aborting EDID quirk parsing\n"); + return res; +} + +/** + * drm_edid_quirks_param_process - process the edid_quirks module parameter + */ +void drm_edid_quirks_param_process(void) +{ + if (drm_edid_quirks != NULL) + drm_edid_quirks_process(drm_edid_quirks, 0); +} + +/** + * drm_edid_quirks_size_show - show the size of the EDID quirk list in sysfs + * @buf: destination buffer (PAGE_SIZE bytes) + */ +ssize_t drm_edid_quirks_size_show(struct class *class, + struct class_attribute *attr, char *buf) +{ + return sprintf(buf, "%zu\n", ARRAY_SIZE(edid_quirk_list)); +} + +/** + * drm_edid_quirks_show - show the contents of the EDID quirk list in sysfs + * @buf: destination buffer (PAGE_SIZE bytes) + */ +ssize_t drm_edid_quirks_show(struct class *class, struct class_attribute *attr, + char *buf) +{ + const union edid_quirk *q = edid_quirk_list; + ssize_t count = 0; + + BUILD_BUG_ON(ARRAY_SIZE(edid_quirk_list) > + PAGE_SIZE / EDID_QUIRK_BUF_SIZE); + + mutex_lock(&edid_quirk_list_mutex); + + do { + if (q->s.quirks != 0) { + drm_edid_quirk_format(q, buf + count, 0); + (buf + count)[EDID_QUIRK_BUF_SIZE - 1] = '\n'; + count += EDID_QUIRK_BUF_SIZE; + } + } while (++q < edid_quirk_list + ARRAY_SIZE(edid_quirk_list)); + + mutex_unlock(&edid_quirk_list_mutex); + + return count; +} + +ssize_t drm_edid_quirks_store(struct class *class, struct class_attribute *attr, + const char *buf, size_t count) +{ + int res; + + res = drm_edid_quirks_process(buf, 1); + if (res != 0) + return res; + + return count; +} /*** DDC fetch and block validation ***/ static const u8 edid_header[] = { @@ -409,25 +825,6 @@ EXPORT_SYMBOL(drm_get_edid); /*** EDID parsing ***/ /** - * edid_vendor - match a string against EDID's obfuscated vendor field - * @edid: EDID to match - * @vendor: vendor string - * - * Returns true if @vendor is in @edid, false otherwise - */ -static bool edid_vendor(struct edid *edid, char *vendor) -{ - char edid_vendor[3]; - - edid_vendor[0] = ((edid->mfg_id[0] & 0x7c) >> 2) + '@'; - edid_vendor[1] = (((edid->mfg_id[0] & 0x3) << 3) | - ((edid->mfg_id[1] & 0xe0) >> 5)) + '@'; - edid_vendor[2] = (edid->mfg_id[1] & 0x1f) + '@'; - - return !strncmp(edid_vendor, vendor, 3); -} - -/** * edid_get_quirks - return quirk flags for a given EDID * @edid: EDID to process * @@ -435,18 +832,18 @@ static bool edid_vendor(struct edid *edid, char *vendor) */ static u32 edid_get_quirks(struct edid *edid) { - struct edid_quirk *quirk; - int i; + union edid_quirk *q; + u32 quirks = 0; - for (i = 0; i < ARRAY_SIZE(edid_quirk_list); i++) { - quirk = &edid_quirk_list[i]; + mutex_lock(&edid_quirk_list_mutex); - if (edid_vendor(edid, quirk->vendor) && - (EDID_PRODUCT_ID(edid) == quirk->product_id)) - return quirk->quirks; - } + q = drm_edid_quirk_find_by_id(edid->display_id); + if (q != NULL) + quirks = q->s.quirks; - return 0; + mutex_unlock(&edid_quirk_list_mutex); + + return quirks; } #define MODE_SIZE(m) ((m)->hdisplay * (m)->vdisplay) @@ -1162,7 +1559,7 @@ do_inferred_modes(struct detailed_timing *timing, void *c) closure->modes += drm_dmt_modes_for_range(closure->connector, closure->edid, timing); - + if (!version_greater(closure->edid, 1, 1)) return; /* GTF not defined yet */ @@ -1399,7 +1796,7 @@ do_cvt_mode(struct detailed_timing *timing, void *c) static int add_cvt_modes(struct drm_connector *connector, struct edid *edid) -{ +{ struct detailed_mode_closure closure = { connector, edid, 0, 0, 0 }; @@ -1615,15 +2012,12 @@ void drm_edid_to_eld(struct drm_connector *connector, struct edid *edid) eld[0] = 2 << 3; /* ELD version: 2 */ - eld[16] = edid->mfg_id[0]; - eld[17] = edid->mfg_id[1]; - eld[18] = edid->prod_code[0]; - eld[19] = edid->prod_code[1]; + *(u32 *)(&eld[16]) = edid->display_id.u; if (cea[1] >= 3) for (db = cea + 4; db < cea + cea[2]; db += dbl + 1) { dbl = db[0] & 0x1f; - + switch ((db[0] & 0xe0) >> 5) { case AUDIO_BLOCK: /* Audio Data Block, contains SADs */ @@ -1723,6 +2117,14 @@ bool drm_detect_hdmi_monitor(struct edid *edid) int i, hdmi_id; int start_offset, end_offset; bool is_hdmi = false; + char buf[EDID_DISPLAY_ID_BUF_SIZE]; + + if (edid_get_quirks(edid) & EDID_QUIRK_DISABLE_INFOFRAMES) { + DRM_INFO("Disabling HDMI InfoFrames on display %s " + "due to EDID quirk\n", + drm_edid_display_id_format(edid->display_id, buf, 1)); + goto end; + } edid_ext = drm_find_cea_extension(edid); if (!edid_ext) @@ -1771,6 +2173,14 @@ bool drm_detect_monitor_audio(struct edid *edid) int i, j; bool has_audio = false; int start_offset, end_offset; + char buf[EDID_DISPLAY_ID_BUF_SIZE]; + + if (edid_get_quirks(edid) & EDID_QUIRK_NO_AUDIO) { + DRM_INFO("Disabling HDMI audio on display %s " + "due to EDID quirk\n", + drm_edid_display_id_format(edid->display_id, buf, 1)); + goto end; + } edid_ext = drm_find_cea_extension(edid); if (!edid_ext) diff --git a/drivers/gpu/drm/drm_stub.c b/drivers/gpu/drm/drm_stub.c index 21bcd4a..1885fc9 100644 --- a/drivers/gpu/drm/drm_stub.c +++ b/drivers/gpu/drm/drm_stub.c @@ -46,16 +46,21 @@ EXPORT_SYMBOL(drm_vblank_offdelay); unsigned int drm_timestamp_precision = 20; /* Default to 20 usecs. */ EXPORT_SYMBOL(drm_timestamp_precision); +char *drm_edid_quirks = NULL; +EXPORT_SYMBOL(drm_edid_quirks); + MODULE_AUTHOR(CORE_AUTHOR); MODULE_DESCRIPTION(CORE_DESC); MODULE_LICENSE("GPL and additional rights"); MODULE_PARM_DESC(debug, "Enable debug output"); MODULE_PARM_DESC(vblankoffdelay, "Delay until vblank irq auto-disable [msecs]"); MODULE_PARM_DESC(timestamp_precision_usec, "Max. error on timestamps [usecs]"); +MODULE_PARM_DESC(edid_quirks, "See Documentation/EDID/edid_quirks.txt"); module_param_named(debug, drm_debug, int, 0600); module_param_named(vblankoffdelay, drm_vblank_offdelay, int, 0600); module_param_named(timestamp_precision_usec, drm_timestamp_precision, int, 0600); +module_param_named(edid_quirks, drm_edid_quirks, charp, 0400); struct idr drm_minors_idr; diff --git a/drivers/gpu/drm/drm_sysfs.c b/drivers/gpu/drm/drm_sysfs.c index 45ac8d6..84dc365 100644 --- a/drivers/gpu/drm/drm_sysfs.c +++ b/drivers/gpu/drm/drm_sysfs.c @@ -84,6 +84,11 @@ static CLASS_ATTR_STRING(version, S_IRUGO, __stringify(CORE_PATCHLEVEL) " " CORE_DATE); +static CLASS_ATTR(edid_quirks_size, 0400, drm_edid_quirks_size_show, 0); + +static CLASS_ATTR(edid_quirks, 0600, drm_edid_quirks_show, + drm_edid_quirks_store); + /** * drm_sysfs_create - create a struct drm_sysfs_class structure * @owner: pointer to the module that is to "own" this struct drm_sysfs_class @@ -113,10 +118,22 @@ struct class *drm_sysfs_create(struct module *owner, char *name) if (err) goto err_out_class; + err = class_create_file(class, &class_attr_edid_quirks_size); + if (err) + goto err_out_version; + + err = class_create_file(class, &class_attr_edid_quirks); + if (err) + goto err_out_quirks_size; + class->devnode = drm_devnode; return class; +err_out_quirks_size: + class_remove_file(class, &class_attr_edid_quirks_size); +err_out_version: + class_remove_file(class, &class_attr_version.attr); err_out_class: class_destroy(class); err_out: @@ -132,6 +149,8 @@ void drm_sysfs_destroy(void) { if ((drm_class == NULL) || (IS_ERR(drm_class))) return; + class_remove_file(drm_class, &class_attr_edid_quirks); + class_remove_file(drm_class, &class_attr_edid_quirks_size); class_remove_file(drm_class, &class_attr_version.attr); class_destroy(drm_class); drm_class = NULL; diff --git a/include/drm/drmP.h b/include/drm/drmP.h index d6b67bb..c947f3e 100644 --- a/include/drm/drmP.h +++ b/include/drm/drmP.h @@ -1501,6 +1501,7 @@ extern unsigned int drm_debug; extern unsigned int drm_vblank_offdelay; extern unsigned int drm_timestamp_precision; +extern char *drm_edid_quirks; extern struct class *drm_class; extern struct proc_dir_entry *drm_proc_root; @@ -1612,6 +1613,15 @@ void drm_gem_vm_open(struct vm_area_struct *vma); void drm_gem_vm_close(struct vm_area_struct *vma); int drm_gem_mmap(struct file *filp, struct vm_area_struct *vma); + /* EDID support (drm_edid.c) */ +void drm_edid_quirks_param_process(void); +ssize_t drm_edid_quirks_size_show(struct class *class, + struct class_attribute *attr, char *buf); +ssize_t drm_edid_quirks_show(struct class *class, struct class_attribute *attr, + char *buf); +ssize_t drm_edid_quirks_store(struct class *class, struct class_attribute *attr, + const char *buf, size_t count); + #include "drm_global.h" static inline void diff --git a/include/drm/drm_edid.h b/include/drm/drm_edid.h index 0cac551..713229b 100644 --- a/include/drm/drm_edid.h +++ b/include/drm/drm_edid.h @@ -202,11 +202,18 @@ struct detailed_timing { #define DRM_EDID_FEATURE_PM_SUSPEND (1 << 6) #define DRM_EDID_FEATURE_PM_STANDBY (1 << 7) +union edid_display_id { + struct { + __be16 mfg_id; + __le16 prod_code; + } __attribute__((packed)) s; + u32 u; +}; + struct edid { u8 header[8]; /* Vendor & product info */ - u8 mfg_id[2]; - u8 prod_code[2]; + union edid_display_id display_id; u32 serial; /* FIXME: byte order */ u8 mfg_week; u8 mfg_year; @@ -242,8 +249,6 @@ struct edid { u8 checksum; } __attribute__((packed)); -#define EDID_PRODUCT_ID(e) ((e)->prod_code[0] | ((e)->prod_code[1] << 8)) - struct drm_encoder; struct drm_connector; struct drm_display_mode;