[Patch 1/3] edid override: infrastructure
Thorsten Schoel
tschoel at web.de
Tue Apr 24 01:49:18 PDT 2012
From: Thorsten Schoel <tschoel at 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);
---
More information about the dri-devel
mailing list