From patchwork Wed Mar 21 15:58:24 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: [1/1] drm/edid: allow to load edid firmware Date: Wed, 21 Mar 2012 05:58:24 -0000 From: Andy Whitcroft X-Patchwork-Id: 148023 Message-Id: <1332345504-9321-2-git-send-email-apw@canonical.com> To: kernel-team@lists.ubuntu.com Cc: Andy Whitcroft From: Carsten Emde Use the firmware interface to load binary EDID data from a file and use them to assign monitor data to a video connector. EDID data is located in the edid/ directory within the existing firmware directories. [apw@canonical.com: follow the changes to the sysfs interfaces] [apw@canonical.com: fix the memory allocation so we use the real buffer] [apw@canonical.com: add a firware prefix edid/ to limit what may be loaded] Signed-off-by: Carsten Emde Signed-off-by: Andy Whitcroft --- drivers/gpu/drm/drm_crtc_helper.c | 7 ++++ drivers/gpu/drm/drm_edid.c | 5 +++ drivers/gpu/drm/drm_sysfs.c | 66 ++++++++++++++++++++++++++++++++++++- include/drm/drm_crtc.h | 1 + 4 files changed, 78 insertions(+), 1 deletions(-) diff --git a/drivers/gpu/drm/drm_crtc_helper.c b/drivers/gpu/drm/drm_crtc_helper.c index d2619d7..8dd1680 100644 --- a/drivers/gpu/drm/drm_crtc_helper.c +++ b/drivers/gpu/drm/drm_crtc_helper.c @@ -94,6 +94,13 @@ int drm_helper_probe_single_connector_modes(struct drm_connector *connector, DRM_DEBUG_KMS("[CONNECTOR:%d:%s]\n", connector->base.id, drm_get_connector_name(connector)); + + if (connector->edid_pinned) { + list_for_each_entry(mode, &connector->modes, head) + count++; + return count; + } + /* set all modes to the unverified state */ list_for_each_entry_safe(mode, t, &connector->modes, head) mode->status = MODE_UNVERIFIED; diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index fb6c26c..1944fd5 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -385,6 +385,11 @@ struct edid *drm_get_edid(struct drm_connector *connector, { struct edid *edid = NULL; + if (connector->edid_pinned) { + edid = (struct edid *) connector->display_info.raw_edid; + return edid; + } + if (drm_probe_ddc(adapter)) edid = (struct edid *)drm_do_get_edid(connector, adapter); diff --git a/drivers/gpu/drm/drm_sysfs.c b/drivers/gpu/drm/drm_sysfs.c index 0f9ef9b..c5c875d 100644 --- a/drivers/gpu/drm/drm_sysfs.c +++ b/drivers/gpu/drm/drm_sysfs.c @@ -17,9 +17,11 @@ #include #include #include +#include #include "drm_sysfs.h" #include "drm_core.h" +#include "drm_edid.h" #include "drmP.h" #define to_drm_minor(d) container_of(d, struct drm_minor, kdev) @@ -228,6 +230,67 @@ static ssize_t edid_show(struct file *filp, struct kobject *kobj, return count; } +#define EDID_FIRMWARE_PREFIX "edid/" + +static ssize_t edid_store(struct file *file, struct kobject *kobj, + struct bin_attribute *attr, char *buf, loff_t off, size_t count) +{ + struct device *connector_dev = container_of(kobj, struct device, kobj); + struct drm_connector *connector = to_drm_connector(connector_dev); + const struct firmware *fw; + unsigned char *edid; + size_t size = EDID_LENGTH; + int status; + char *filename, *cr; + + if (count == 0 || *buf == '\n' || *buf == '\0') { + connector->edid_pinned = 0; + return count; + } + + filename = kmalloc(sizeof(EDID_FIRMWARE_PREFIX) + count, GFP_KERNEL); + if (!filename) + return -ENOMEM; + strcpy(filename, EDID_FIRMWARE_PREFIX); + memcpy(filename + sizeof(EDID_FIRMWARE_PREFIX) - 1, buf, count); + filename[sizeof(EDID_FIRMWARE_PREFIX) + count] = '\0'; + + cr = strchr(filename, '\n'); + if (cr) + *cr = '\0'; + + status = request_firmware(&fw, filename, connector_dev); + kfree(filename); + if (status) + return status; + + if (fw->size != size) { + release_firmware(fw); + return -EINVAL; + } + + edid = kmalloc(size, GFP_KERNEL); + if (edid == NULL) { + release_firmware(fw); + return -ENOMEM; + } + + memcpy(edid, fw->data, size); + + drm_mode_connector_update_edid_property(connector, + (struct edid *) edid); + drm_add_edid_modes(connector, (struct edid *) edid); + drm_mode_connector_list_update(connector); + drm_mode_sort(&connector->modes); + + connector->display_info.raw_edid = edid; + connector->edid_pinned = 1; + + release_firmware(fw); + + return count; +} + static ssize_t modes_show(struct device *device, struct device_attribute *attr, char *buf) @@ -341,9 +404,10 @@ static struct device_attribute connector_attrs_opt1[] = { static struct bin_attribute edid_attr = { .attr.name = "edid", - .attr.mode = 0444, + .attr.mode = 0644, .size = 0, .read = edid_show, + .write = edid_store, }; /** diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h index 8020798..96d6e18 100644 --- a/include/drm/drm_crtc.h +++ b/include/drm/drm_crtc.h @@ -509,6 +509,7 @@ struct drm_connector { struct list_head user_modes; struct drm_property_blob *edid_blob_ptr; + int edid_pinned; u32 property_ids[DRM_CONNECTOR_MAX_PROPERTY]; uint64_t property_values[DRM_CONNECTOR_MAX_PROPERTY];