I just attached this patch to
https://bugzilla.redhat.com/show_bug.cgi?id=806091, along with the
following comments:
This is the "first draft" of an EDID quirk-based approach to solving
this problem. I intend to perform a bit of cleanup and break it up
into a series of separate patches, but I'm posting it here for any
comments about the approach.
The patch does the following:
* Changes the vendor field of struct edid_quirk to an array, rather
than a pointer. (This has already been sent to the dri-devel list.)
* Adds two new quirks EDID_QUIRK_DISABLE_INFOFRAMES and
EDID_QUIRK_NO_AUDIO. This first quirk causes drm_detect_hdmi_monitor
to return false; the second causes drm_detect_monitor_audio to
return false.
* Logs the EDID vendor and model of connected monitors.
* Adds an edid_quirks parameter to the drm module, for user-defined
quirks.
With this patch, my display works when I add
drm.edid_quirks=GSM:0x563f:0x180 to my kernel command line. (0x80,
EDID_QUIRK_DISABLE_INFOFRAMES, makes it work with the nouveau driver;
0x100, EDID_QUIRK_NO_AUDIO, makes it work with the i915 driver. I.e.,
the i915 driver is sending audio InfoFrames even when
drm_detect_hdmi_monitor returns false; bug?)
Thoughts?
--
========================================================================
Ian Pilcher [email protected]
"If you're going to shift my paradigm ... at least buy me dinner first."
========================================================================
diff -ur linux-3.3/drivers/gpu/drm/drm_drv.c linux-3.3-working/drivers/gpu/drm/drm_drv.c
--- linux-3.3/drivers/gpu/drm/drm_drv.c 2012-03-18 18:15:34.000000000 -0500
+++ linux-3.3-working/drivers/gpu/drm/drm_drv.c 2012-04-27 21:30:07.535452184 -0500
@@ -280,6 +280,8 @@
ret = -1;
goto err_p3;
}
+
+ drm_edid_xtra_quirks_parse();
DRM_INFO("Initialized %s %d.%d.%d %s\n",
CORE_NAME, CORE_MAJOR, CORE_MINOR, CORE_PATCHLEVEL, CORE_DATE);
@@ -304,6 +306,8 @@
idr_remove_all(&drm_minors_idr);
idr_destroy(&drm_minors_idr);
+
+ drm_edid_xtra_quirks_free();
}
module_init(drm_core_init);
diff -ur linux-3.3/drivers/gpu/drm/drm_edid.c linux-3.3-working/drivers/gpu/drm/drm_edid.c
--- linux-3.3/drivers/gpu/drm/drm_edid.c 2012-03-18 18:15:34.000000000 -0500
+++ linux-3.3-working/drivers/gpu/drm/drm_edid.c 2012-04-28 01:11:51.316839743 -0500
@@ -66,6 +66,10 @@
#define EDID_QUIRK_FIRST_DETAILED_PREFERRED (1 << 5)
/* use +hsync +vsync for detailed mode */
#define EDID_QUIRK_DETAILED_SYNC_PP (1 << 6)
+/* Display is confused by InfoFrames; don't send any. */
+#define EDID_QUIRK_DISABLE_INFOFRAMES (1 << 7)
+/* Display doesn't have any audio output */
+#define EDID_QUIRK_NO_AUDIO (1 << 8)
struct detailed_mode_closure {
struct drm_connector *connector;
@@ -81,7 +85,7 @@
#define LEVEL_CVT 3
static struct edid_quirk {
- char *vendor;
+ char vendor[4];
int product_id;
u32 quirks;
} edid_quirk_list[] = {
@@ -122,13 +126,100 @@
{ "SAM", 638, EDID_QUIRK_PREFER_LARGE_60 },
};
+/* Parsed extra quirks from the edid_quirks module parameter */
+static struct edid_quirk *edid_xtra_quirk_list;
+static int edid_num_xtra_quirks;
+
+/*
+ * Free the extra EDID quirks list.
+ */
+void drm_edid_xtra_quirks_free(void)
+{
+ kfree(edid_xtra_quirk_list);
+ edid_xtra_quirk_list = NULL;
+ edid_num_xtra_quirks = 0;
+}
+
+/*
+ * Parse the EDID quirk at s and store it a q. Returns 1 on success, 0 on error.
+ */
+static int drm_edid_parse_quirk(char *s, struct edid_quirk *q)
+{
+ char *c;
+
+ if (sscanf(s, "%3s:%i:%i",
+ q->vendor, &q->product_id, &q->quirks) == 3) {
+ DRM_DEBUG("Parsed EDID quirk: "
+ "mfr: %s, product: %d, quirks: %u\n",
+ q->vendor, q->product_id, q->quirks);
+ return 1;
+ } else {
+ if ((c = strchr(s, ',')) != NULL) {
+ *c = 0;
+ }
+ printk(KERN_WARNING "Invalid EDID quirk: '%s'\n", s);
+ if (c != NULL) {
+ *c = ',';
+ }
+ return 0;
+ }
+}
+
+/*
+ * Parse drm_edid_xtra_quirks and create the extra EDID quirks list.
+ */
+void drm_edid_xtra_quirks_parse(void)
+{
+ int num_quirks;
+ char *c;
+ if (drm_edid_xtra_quirks == NULL) {
+ edid_xtra_quirk_list = NULL;
+ edid_num_xtra_quirks = 0;
+ return;
+ }
+
+ /* Number of (potential) quirks = number of commas in param + 1 */
+ num_quirks = 1;
+ c = drm_edid_xtra_quirks;
+ while ((c = strchr(c, ',')) != NULL) {
+ ++num_quirks;
+ ++c;
+ }
+
+ if (num_quirks > DRM_EDID_XTRA_QUIRKS_MAX) {
+ printk(KERN_WARNING "Number of additional EDID quirks limited "
+ "to " __stringify(DRM_EDID_XTRA_QUIRKS_MAX) "\n");
+ num_quirks = DRM_EDID_XTRA_QUIRKS_MAX;
+ }
+
+ edid_xtra_quirk_list = kmalloc(sizeof(struct edid_quirk) * num_quirks,
+ GFP_KERNEL);
+ if (edid_xtra_quirk_list == NULL) {
+ printk(KERN_WARNING "Failed to allocate memory for additional "
+ "EDID quirks\n");
+ return;
+ }
+
+ edid_num_xtra_quirks = 0;
+ c = drm_edid_xtra_quirks;
+ while (1) {
+ edid_num_xtra_quirks += drm_edid_parse_quirk(c,
+ &edid_xtra_quirk_list[edid_num_xtra_quirks]);
+ if (edid_num_xtra_quirks == num_quirks ||
+ (c = strchr(c, ',')) == NULL) {
+ break;
+ }
+ ++c;
+ }
+}
+
/*** DDC fetch and block validation ***/
static const u8 edid_header[] = {
0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00
};
- /*
+/*
* Sanity check the header of the base EDID block. Return 8 if the header
* is perfect, down to 0 if it's totally wrong.
*/
@@ -366,6 +457,24 @@
}
/**
+ * edid_vendor_string - decodes EDID vendor field
+ * @edid: EDID from which to extract the vendor field
+ * @edid_vendor: buffer in which to store the vendor string
+ *
+ * Returns a pointer to @edid_vendor
+ */
+static char *edid_vendor_string(const struct edid *edid, char edid_vendor[4])
+{
+ 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) + '@';
+ edid_vendor[3] = 0;
+
+ return edid_vendor;
+}
+
+/**
* drm_get_edid - get EDID data, if available
* @connector: connector we're probing
* @adapter: i2c adapter to use for DDC
@@ -378,15 +487,31 @@
struct edid *drm_get_edid(struct drm_connector *connector,
struct i2c_adapter *adapter)
{
+ const struct edid *old_edid;
+ char edid_vendor[4];
struct edid *edid = NULL;
-
+
if (drm_probe_ddc(adapter))
edid = (struct edid *)drm_do_get_edid(connector, adapter);
+
+ /* Log if something has changed */
+ if (edid != NULL) {
+ old_edid = (struct edid *)connector->display_info.raw_edid;
+ /* memcmp call compares the mfg_id, prod_code, and serial
+ * fields of the two (packed) EDID structures for equality */
+ if (!old_edid || memcmp(&edid->mfg_id, &old_edid->mfg_id, 8)) {
+ DRM_INFO("Detected display on connector %s: "
+ "mfr: %s, product: %04hx, serial: %d\n",
+ drm_get_connector_name(connector),
+ edid_vendor_string(edid, edid_vendor),
+ le16_to_cpu(EDID_PRODUCT_ID(edid)),
+ le32_to_cpu(edid->serial));
+ }
+ }
connector->display_info.raw_edid = (char *)edid;
return edid;
-
}
EXPORT_SYMBOL(drm_get_edid);
@@ -401,13 +526,9 @@
*/
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) + '@';
+ char edid_vendor[4];
+ edid_vendor_string(edid, edid_vendor);
return !strncmp(edid_vendor, vendor, 3);
}
@@ -420,17 +541,34 @@
static u32 edid_get_quirks(struct edid *edid)
{
struct edid_quirk *quirk;
+ u32 quirks;
int i;
+
+ quirks = 0;
+ /* Check the built-in quirks first */
for (i = 0; i < ARRAY_SIZE(edid_quirk_list); i++) {
quirk = &edid_quirk_list[i];
if (edid_vendor(edid, quirk->vendor) &&
- (EDID_PRODUCT_ID(edid) == quirk->product_id))
- return quirk->quirks;
+ (EDID_PRODUCT_ID(edid) == quirk->product_id)) {
+ quirks = quirk->quirks;
+ break;
+ }
+ }
+
+ /* Extra quirks can override built-in ones */
+ for (i = 0; i < edid_num_xtra_quirks; ++i) {
+ quirk = &edid_xtra_quirk_list[i];
+
+ if (edid_vendor(edid, quirk->vendor) &&
+ EDID_PRODUCT_ID(edid) == quirk->product_id) {
+ quirks = quirk->quirks;
+ break;
+ }
}
- return 0;
+ return quirks;
}
#define MODE_SIZE(m) ((m)->hdisplay * (m)->vdisplay)
@@ -1562,6 +1700,11 @@
int i, hdmi_id;
int start_offset, end_offset;
bool is_hdmi = false;
+
+ if (edid_get_quirks(edid) & EDID_QUIRK_DISABLE_INFOFRAMES) {
+ DRM_DEBUG_KMS("Disabling HDMI InfoFrames due to EDID quirk\n");
+ goto end;
+ }
edid_ext = drm_find_cea_extension(edid);
if (!edid_ext)
@@ -1610,6 +1753,11 @@
int i, j;
bool has_audio = false;
int start_offset, end_offset;
+
+ if (edid_get_quirks(edid) & EDID_QUIRK_NO_AUDIO) {
+ DRM_DEBUG_KMS("Disabling HDMI audio due to EDID quirk\n");
+ goto end;
+ }
edid_ext = drm_find_cea_extension(edid);
if (!edid_ext)
diff -ur linux-3.3/drivers/gpu/drm/drm_stub.c linux-3.3-working/drivers/gpu/drm/drm_stub.c
--- linux-3.3/drivers/gpu/drm/drm_stub.c 2012-03-18 18:15:34.000000000 -0500
+++ linux-3.3-working/drivers/gpu/drm/drm_stub.c 2012-04-24 13:32:35.394132568 -0500
@@ -46,16 +46,22 @@
unsigned int drm_timestamp_precision = 20; /* Default to 20 usecs. */
EXPORT_SYMBOL(drm_timestamp_precision);
+char *drm_edid_xtra_quirks = NULL;
+EXPORT_SYMBOL(drm_edid_xtra_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, "Additional EDID quirks [up to "
+ __stringify(DRM_EDID_XTRA_QUIRKS_MAX) "]");
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_xtra_quirks, charp, 0600);
struct idr drm_minors_idr;
diff -ur linux-3.3/include/drm/drmP.h linux-3.3-working/include/drm/drmP.h
--- linux-3.3/include/drm/drmP.h 2012-03-18 18:15:34.000000000 -0500
+++ linux-3.3-working/include/drm/drmP.h 2012-04-27 16:09:12.043494619 -0500
@@ -1468,6 +1468,7 @@
extern unsigned int drm_vblank_offdelay;
extern unsigned int drm_timestamp_precision;
+extern char *drm_edid_xtra_quirks;
extern struct class *drm_class;
extern struct proc_dir_entry *drm_proc_root;
@@ -1552,6 +1553,12 @@
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) */
+/* This gets stringified in drm_stub.c and drm_edid.c; don't add parentheses */
+#define DRM_EDID_XTRA_QUIRKS_MAX 10
+void drm_edid_xtra_quirks_parse(void);
+void drm_edid_xtra_quirks_free(void);
+
#include "drm_global.h"
static inline void
_______________________________________________
dri-devel mailing list
[email protected]
http://lists.freedesktop.org/mailman/listinfo/dri-devel