From: Thorsten Schoel <tsch...@web.de>

Provides infrastructure for overriding edid information of individual
monitors.

Signed-off-by: Thorsten Schoel <tschoel at web.de>
---
diff -Nurp vanilla/drivers/gpu/drm/drm_edid.c infra/drivers/gpu/drm/drm_edid.c
--- vanilla/drivers/gpu/drm/drm_edid.c  2012-01-25 22:29:25.000000000 +0100
+++ infra/drivers/gpu/drm/drm_edid.c    2012-01-25 22:12:59.000000000 +0100
@@ -30,6 +30,8 @@
 #include <linux/kernel.h>
 #include <linux/slab.h>
 #include <linux/i2c.h>
+#include <linux/types.h>
+#include <linux/list.h>
 #include <linux/export.h>
 #include "drmP.h"
 #include "drm_edid.h"
@@ -226,6 +228,158 @@ bool drm_edid_is_valid(struct edid *edid
 }
 EXPORT_SYMBOL(drm_edid_is_valid);

+struct drm_edid_override {
+       struct list_head list;
+       
+       char *connector;
+       struct edid *edid;
+       unsigned num_blocks;
+};
+LIST_HEAD(drm_edid_override_list);
+
+/*
+ * Free the memory associated with an EDID override.
+ */
+static void
+drm_edid_override_delete(struct drm_edid_override *entry)
+{
+       if (entry->edid)
+               kfree(entry->edid);
+       kfree(entry->connector);
+       kfree(entry);
+}
+
+/**
+ * Remove an existing override.
+ *
+ * \param connector : name of a drm_connector
+ */
+void
+drm_edid_override_remove(const char *connector)
+{
+       struct drm_edid_override *entry = NULL;
+       
+       list_for_each_entry (entry, &drm_edid_override_list, list) {
+               if (strcmp(entry->connector, connector))
+                       continue;
+               list_del(&entry->list);
+               drm_edid_override_delete(entry);
+               break;
+       }
+}
+EXPORT_SYMBOL(drm_edid_override_remove);
+
+static int
+drm_edid_override_do_set(char *connector, struct edid *edid, unsigned 
num_blocks)
+{
+       struct drm_edid_override *entry = NULL;
+       int found = 0;
+       
+       /* replace previous entry if present */
+       list_for_each_entry (entry, &drm_edid_override_list, list) {
+               if (strcmp(connector, entry->connector))
+                       continue;
+               found = 1;
+               break;
+       }
+
+       if (!found) {
+               entry = kmalloc(sizeof(struct drm_edid_override), GFP_KERNEL);
+               if (!entry) {
+                       kfree(connector);
+                       kfree(edid);
+                       return -ENOMEM;
+               }
+               INIT_LIST_HEAD(&entry->list);
+       } else {
+               kfree(entry->connector);
+               if (entry->edid)
+                       kfree(entry->edid);
+       }
+       
+       entry->connector = connector;
+       entry->edid = edid;
+       entry->num_blocks = num_blocks;
+       
+       if (!found)
+               list_add_tail(&entry->list, &drm_edid_override_list);
+       DRM_INFO("Set new EDID override for connector %s", connector);
+       
+       return 0;
+}
+
+/**
+ * Add a new override for connector if none has been set yet or replace the
+ * current one.
+ *
+ * \param connector : name of a drm_connector as retrieved through 
drm_get_connector_name
+ * \param edid      : binary EDID data
+ */
+int
+drm_edid_override_set(const char *connector, const struct edid *edid)
+{
+       char *connector_dup = NULL;
+       struct edid *edid_dup = NULL;
+       int num_blocks = 0;
+       unsigned edid_size = 0;
+       
+       connector_dup = kstrdup(connector, GFP_KERNEL);
+       if (!connector_dup)
+               return -ENOMEM;
+       
+       num_blocks = edid->extensions + 1;
+       edid_size = num_blocks * EDID_LENGTH;
+       edid_dup = kmalloc(edid_size, GFP_KERNEL);
+       if (!edid_dup) {
+               kfree(connector_dup);
+               return -ENOMEM;
+       }
+       memcpy(edid_dup, edid, edid_size);
+       
+       return drm_edid_override_do_set(connector_dup, edid_dup, num_blocks);
+}
+EXPORT_SYMBOL(drm_edid_override_set);
+
+/**
+ * Get EDID information from overrides list if available.
+ *
+ * \param connector : DRM connector to get EDID for
+ * \return a newly allocated edid structure filled with the EDID data from the
+ *        override or NULL if no override for @connector could be found.
+ */
+static struct edid *
+drm_edid_override_get (struct drm_connector *connector)
+{
+       struct list_head *pos = NULL;
+       struct drm_edid_override *entry = NULL;
+       char *connector_name = NULL;
+       struct edid *result = NULL;
+
+       connector_name = drm_get_connector_name(connector);
+    
+       list_for_each(pos, &drm_edid_override_list) {
+               entry = (struct drm_edid_override *)
+                       list_entry(pos, struct drm_edid_override, list);
+               if(!strcmp(entry->connector, connector_name))
+                       break;
+               else
+                       entry = NULL;
+       }
+       
+       if (!entry)
+               return NULL;
+       
+       if (!entry->edid) {
+               DRM_ERROR("EDID override without EDID data for connector %s", 
connector_name);
+               return NULL;
+       }
+       
+       if ((result = kmalloc(entry->num_blocks * EDID_LENGTH, GFP_KERNEL)))
+               memcpy(result, entry->edid, entry->num_blocks * EDID_LENGTH);
+       
+       return result;
+}
+
 #define DDC_SEGMENT_ADDR 0x30
 /**
  * Get EDID information via I2C.
@@ -375,7 +529,8 @@ drm_probe_ddc(struct i2c_adapter *adapte
  * @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.
+ * attach it to the connector. If there is an override for connector, use that
+ * instead.
  *
  * Return edid data or NULL if we couldn't find any.
  */
@@ -383,14 +538,20 @@ struct edid *drm_get_edid(struct drm_con
                          struct i2c_adapter *adapter)
 {
        struct edid *edid = NULL;
-
-       if (drm_probe_ddc(adapter))
+       char *connector_name = NULL;
+       
+       connector_name = drm_get_connector_name(connector);
+       
+       if ((edid = drm_edid_override_get(connector))) {
+               DRM_DEBUG("Using EDID override for %s", connector_name);
+       } else if (drm_probe_ddc(adapter)) {
+               DRM_DEBUG("Getting EDID by probing i2c-bus for %s", 
connector_name);
                edid = (struct edid *)drm_do_get_edid(connector, adapter);
+       }

        connector->display_info.raw_edid = (char *)edid;

        return edid;
-
 }
 EXPORT_SYMBOL(drm_get_edid);

diff -Nurp vanilla/include/drm/drm_crtc.h infra/include/drm/drm_crtc.h
--- vanilla/include/drm/drm_crtc.h      2012-01-12 20:42:45.000000000 +0100
+++ infra/include/drm/drm_crtc.h        2012-01-25 22:09:30.000000000 +0100
@@ -854,6 +854,8 @@ extern void drm_fb_release(struct drm_fi
 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_edid_override_set(const char *connector, const struct edid 
*edid);
+extern void drm_edid_override_remove(const char *connector);
 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);
---


Reply via email to