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); --- _______________________________________________ dri-devel mailing list dri-devel@xxxxxxxxxxxxxxxxxxxxx http://lists.freedesktop.org/mailman/listinfo/dri-devel