Patchwork [1/1] drm/edid: allow to load edid firmware

login
register
mail settings
Submitter Andy Whitcroft
Date March 21, 2012, 3:58 p.m.
Message ID <1332345504-9321-2-git-send-email-apw@canonical.com>
Download mbox | patch
Permalink /patch/148023/
State New
Headers show

Comments

Andy Whitcroft - March 21, 2012, 3:58 p.m.
From: Carsten Emde <C.Emde@osadl.org>

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 <C.Emde@osadl.org>
Signed-off-by: Andy Whitcroft <apw@canonical.com>
---
 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(-)

Patch

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 <linux/gfp.h>
 #include <linux/err.h>
 #include <linux/export.h>
+#include <linux/firmware.h>
 
 #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];