video: tegra: Implement EDID query
Robert Morell [Wed, 24 Aug 2011 23:52:56 +0000 (16:52 -0700)]
This change implements the TEGRA_DC_EXT_CONTROL_GET_OUTPUT_EDID ioctl in
the dc_ext interface.

It first adds a way for the tegra dc EDID module to export EDID data
safely, without the risk of reading an incomplete or corrupted EDID in
the presence of hotplug, by moving the actual data to a substructure
with a lifetime maintained by a kref.  Then, that support is plumbed
through the hdmi block (which is currently the only way to get at the
EDID) and out to userspace.

Signed-off-by: Robert Morell <rmorell@nvidia.com>

Bug 817119

Original-Change-Id: I78cd170e15322011b428cb71ffad2c0c3ea058ac
Reviewed-on: http://git-master/r/49127
Reviewed-by: Rohan Somvanshi <rsomvanshi@nvidia.com>
Tested-by: Rohan Somvanshi <rsomvanshi@nvidia.com>

Rebase-Id: Rafafc0a6fbacda5494b12162ad99a8c70ceeb2e0

arch/arm/mach-tegra/include/mach/dc.h
drivers/video/tegra/dc/edid.c
drivers/video/tegra/dc/edid.h
drivers/video/tegra/dc/ext/control.c
drivers/video/tegra/dc/hdmi.c
include/video/tegra_dc_ext.h

index 5aac575..4bce3c3 100644 (file)
@@ -499,4 +499,18 @@ void tegra_dc_config_pwm(struct tegra_dc *dc, struct tegra_dc_pwm_params *cfg);
 
 int tegra_dc_update_csc(struct tegra_dc *dc, int win_index);
 
+/*
+ * In order to get a dc's current EDID, first call tegra_dc_get_edid() from an
+ * interruptible context.  The returned value (if non-NULL) points to a
+ * snapshot of the current state; after copying data from it, call
+ * tegra_dc_put_edid() on that pointer.  Do not dereference anything through
+ * that pointer after calling tegra_dc_put_edid().
+ */
+struct tegra_dc_edid {
+       size_t          len;
+       u8              buf[0];
+};
+struct tegra_dc_edid *tegra_dc_get_edid(struct tegra_dc *dc);
+void tegra_dc_put_edid(struct tegra_dc_edid *edid);
+
 #endif
index 055cea3..7b23c60 100644 (file)
 
 #include "edid.h"
 
+struct tegra_edid_pvt {
+       struct kref                     refcnt;
+       struct tegra_edid_hdmi_eld      eld;
+       bool                            support_stereo;
+       /* Note: dc_edid must remain the last member */
+       struct tegra_dc_edid            dc_edid;
+};
+
 struct tegra_edid {
        struct i2c_client       *client;
        struct i2c_board_info   info;
        int                     bus;
 
-       u8                      *data;
-       unsigned                len;
-       u8                      support_stereo;
-       struct tegra_edid_hdmi_eld              eld;
+       struct tegra_edid_pvt   *data;
+
+       struct mutex            lock;
 };
 
 #if defined(DEBUG) || defined(CONFIG_DEBUG_FS)
 static int tegra_edid_show(struct seq_file *s, void *unused)
 {
        struct tegra_edid *edid = s->private;
+       struct tegra_dc_edid *data;
+       u8 *buf;
        int i;
 
-       for (i = 0; i < edid->len; i++) {
+       data = tegra_edid_get_data(edid);
+       if (!data) {
+               seq_printf(s, "No EDID\n");
+               return 0;
+       }
+
+       buf = data->buf;
+
+       for (i = 0; i < data->len; i++) {
                if (i % 16 == 0)
                        seq_printf(s, "edid[%03x] =", i);
 
-               seq_printf(s, " %02x", edid->data[i]);
+               seq_printf(s, " %02x", buf[i]);
 
                if (i % 16 == 15)
                        seq_printf(s, "\n");
        }
 
+       tegra_edid_put_data(data);
+
        return 0;
 }
 #endif
@@ -165,9 +184,10 @@ int tegra_edid_read_block(struct tegra_edid *edid, int block, u8 *data)
        return 0;
 }
 
-int tegra_edid_parse_ext_block(u8 *raw, int idx, struct tegra_edid *edid)
+int tegra_edid_parse_ext_block(const u8 *raw, int idx,
+                              struct tegra_edid_pvt *edid)
 {
-       u8 *ptr;
+       const u8 *ptr;
        u8 tmp;
        u8 code;
        int len;
@@ -300,45 +320,64 @@ int tegra_edid_mode_support_stereo(struct fb_videomode *mode)
        return 0;
 }
 
+static void data_release(struct kref *ref)
+{
+       struct tegra_edid_pvt *data =
+               container_of(ref, struct tegra_edid_pvt, refcnt);
+       vfree(data);
+}
+
 int tegra_edid_get_monspecs(struct tegra_edid *edid, struct fb_monspecs *specs)
 {
        int i;
        int j;
        int ret;
        int extension_blocks;
+       struct tegra_edid_pvt *new_data, *old_data;
+       u8 *data;
+
+       new_data = vmalloc(SZ_32K + sizeof(struct tegra_edid_pvt));
+       if (!new_data)
+               return -ENOMEM;
 
-       edid->support_stereo = 0;
+       kref_init(&new_data->refcnt);
 
-       ret = tegra_edid_read_block(edid, 0, edid->data);
+       new_data->support_stereo = 0;
+
+       data = new_data->dc_edid.buf;
+
+       ret = tegra_edid_read_block(edid, 0, data);
        if (ret)
-               return ret;
+               goto fail;
 
        memset(specs, 0x0, sizeof(struct fb_monspecs));
-       memset(&edid->eld, 0x0, sizeof(struct tegra_edid_hdmi_eld));
-       fb_edid_to_monspecs(edid->data, specs);
-       if (specs->modedb == NULL)
-               return -EINVAL;
-       memcpy(edid->eld.monitor_name, specs->monitor, sizeof(specs->monitor));
-       edid->eld.mnl = strlen(edid->eld.monitor_name) + 1;
-       edid->eld.product_id[0] = edid->data[0x8];
-       edid->eld.product_id[1] = edid->data[0x9];
-       edid->eld.manufacture_id[0] = edid->data[0xA];
-       edid->eld.manufacture_id[1] = edid->data[0xB];
-
-       extension_blocks = edid->data[0x7e];
+       memset(&new_data->eld, 0x0, sizeof(new_data->eld));
+       fb_edid_to_monspecs(data, specs);
+       if (specs->modedb == NULL) {
+               ret = -EINVAL;
+               goto fail;
+       }
+       memcpy(new_data->eld.monitor_name, specs->monitor, sizeof(specs->monitor));
+       new_data->eld.mnl = strlen(new_data->eld.monitor_name) + 1;
+       new_data->eld.product_id[0] = data[0x8];
+       new_data->eld.product_id[1] = data[0x9];
+       new_data->eld.manufacture_id[0] = data[0xA];
+       new_data->eld.manufacture_id[1] = data[0xB];
+
+       extension_blocks = data[0x7e];
 
        for (i = 1; i <= extension_blocks; i++) {
-               ret = tegra_edid_read_block(edid, i, edid->data + i * 128);
+               ret = tegra_edid_read_block(edid, i, data + i * 128);
                if (ret < 0)
                        break;
 
-               if (edid->data[i * 128] == 0x2) {
-                       fb_edid_add_monspecs(edid->data + i * 128, specs);
+               if (data[i * 128] == 0x2) {
+                       fb_edid_add_monspecs(data + i * 128, specs);
 
-                       tegra_edid_parse_ext_block(edid->data + i * 128,
-                                       edid->data[i * 128 + 2], edid);
+                       tegra_edid_parse_ext_block(data + i * 128,
+                                       data[i * 128 + 2], new_data);
 
-                       if (edid->support_stereo) {
+                       if (new_data->support_stereo) {
                                for (j = 0; j < specs->modedb_len; j++) {
                                        if (tegra_edid_mode_support_stereo(
                                                &specs->modedb[j]))
@@ -349,18 +388,30 @@ int tegra_edid_get_monspecs(struct tegra_edid *edid, struct fb_monspecs *specs)
                }
        }
 
-       edid->len = i * 128;
+       new_data->dc_edid.len = i * 128;
+
+       mutex_lock(&edid->lock);
+       old_data = edid->data;
+       edid->data = new_data;
+       mutex_unlock(&edid->lock);
+
+       if (old_data)
+               kref_put(&old_data->refcnt, data_release);
 
        tegra_edid_dump(edid);
        return 0;
+
+fail:
+       vfree(new_data);
+       return ret;
 }
 
 int tegra_edid_get_eld(struct tegra_edid *edid, struct tegra_edid_hdmi_eld *elddata)
 {
-       if (!elddata)
+       if (!elddata || !edid->data)
                return -EFAULT;
 
-       memcpy(elddata,&edid->eld,sizeof(struct tegra_edid_hdmi_eld));
+       memcpy(elddata,&edid->data->eld,sizeof(struct tegra_edid_hdmi_eld));
 
        return 0;
 }
@@ -375,11 +426,7 @@ struct tegra_edid *tegra_edid_create(int bus)
        if (!edid)
                return ERR_PTR(-ENOMEM);
 
-       edid->data = vmalloc(SZ_32K);
-       if (!edid->data) {
-               err = -ENOMEM;
-               goto free_edid;
-       }
+       mutex_init(&edid->lock);
        strlcpy(edid->info.type, "tegra_edid", sizeof(edid->info.type));
        edid->bus = bus;
        edid->info.addr = 0x50;
@@ -406,7 +453,6 @@ struct tegra_edid *tegra_edid_create(int bus)
        return edid;
 
 free_edid:
-       vfree(edid->data);
        kfree(edid);
 
        return ERR_PTR(err);
@@ -415,10 +461,36 @@ free_edid:
 void tegra_edid_destroy(struct tegra_edid *edid)
 {
        i2c_release_client(edid->client);
-       vfree(edid->data);
+       if (edid->data)
+               kref_put(&edid->data->refcnt, data_release);
        kfree(edid);
 }
 
+struct tegra_dc_edid *tegra_edid_get_data(struct tegra_edid *edid)
+{
+       struct tegra_edid_pvt *data;
+
+       mutex_lock(&edid->lock);
+       data = edid->data;
+       if (data)
+               kref_get(&data->refcnt);
+       mutex_unlock(&edid->lock);
+
+       return data ? &data->dc_edid : NULL;
+}
+
+void tegra_edid_put_data(struct tegra_dc_edid *data)
+{
+       struct tegra_edid_pvt *pvt;
+
+       if (!data)
+               return;
+
+       pvt = container_of(data, struct tegra_edid_pvt, dc_edid);
+
+       kref_put(&pvt->refcnt, data_release);
+}
+
 static const struct i2c_device_id tegra_edid_id[] = {
         { "tegra_edid", 0 },
         { }
index 9952db7..773fa0a 100644 (file)
@@ -20,6 +20,7 @@
 
 #include <linux/i2c.h>
 #include <linux/wait.h>
+#include <mach/dc.h>
 
 #define ELD_MAX_MNL    16
 #define ELD_MAX_SAD    16
@@ -52,4 +53,7 @@ void tegra_edid_destroy(struct tegra_edid *edid);
 int tegra_edid_get_monspecs(struct tegra_edid *edid, struct fb_monspecs *specs);
 int tegra_edid_get_eld(struct tegra_edid *edid, struct tegra_edid_hdmi_eld *elddata);
 
+struct tegra_dc_edid *tegra_edid_get_data(struct tegra_edid *edid);
+void tegra_edid_put_data(struct tegra_dc_edid *data);
+
 #endif
index 1bf0b2d..f6fb3c0 100644 (file)
@@ -64,8 +64,43 @@ get_output_properties(struct tegra_dc_ext_control_output_properties *properties)
 
 static int get_output_edid(struct tegra_dc_ext_control_output_edid *edid)
 {
-       /* XXX implement me */
-       return -ENOTSUPP;
+       struct tegra_dc *dc;
+       size_t user_size = edid->size;
+       struct tegra_dc_edid *dc_edid = NULL;
+       int ret;
+
+       /* TODO: this should be more dynamic */
+       if (edid->handle > 2)
+               return -EINVAL;
+
+       dc = tegra_dc_get_dc(edid->handle);
+
+       dc_edid = tegra_dc_get_edid(dc);
+       if (IS_ERR(dc_edid))
+               return PTR_ERR(dc_edid);
+
+       if (!dc_edid) {
+               edid->size = 0;
+       } else {
+               edid->size = dc_edid->len;
+
+               if (user_size < edid->size) {
+                       ret = -EFBIG;
+                       goto done;
+               }
+
+               if (copy_to_user(edid->data, dc_edid->buf, edid->size)) {
+                       ret = -EFAULT;
+                       goto done;
+               }
+
+       }
+
+done:
+       if (dc_edid)
+               tegra_dc_put_edid(dc_edid);
+
+       return ret;
 }
 
 static int set_event_mask(struct tegra_dc_ext_control_user *user, u32 mask)
index 1516051..506d59a 100644 (file)
@@ -1639,3 +1639,22 @@ struct tegra_dc_out_ops tegra_dc_hdmi_ops = {
        .resume = tegra_dc_hdmi_resume,
 };
 
+struct tegra_dc_edid *tegra_dc_get_edid(struct tegra_dc *dc)
+{
+       struct tegra_dc_hdmi_data *hdmi;
+
+       /* TODO: Support EDID on non-HDMI devices */
+       if (dc->out->type != TEGRA_DC_OUT_HDMI)
+               return ERR_PTR(-ENODEV);
+
+       hdmi = tegra_dc_get_outdata(dc);
+
+       return tegra_edid_get_data(hdmi->edid);
+}
+EXPORT_SYMBOL(tegra_dc_get_edid);
+
+void tegra_dc_put_edid(struct tegra_dc_edid *edid)
+{
+       tegra_edid_put_data(edid);
+}
+EXPORT_SYMBOL(tegra_dc_put_edid);
index 824afea..bdfebae 100644 (file)
@@ -233,6 +233,16 @@ struct tegra_dc_ext_control_output_properties {
        __u32 head_mask;
 };
 
+/*
+ * This allows userspace to query the raw EDID data for the specified output
+ * handle.
+ *
+ * Here, the size parameter is both an input and an output:
+ * 1. Userspace passes in the size of the buffer allocated for data.
+ * 2. If size is too small, the call fails with the error EFBIG; otherwise, the
+ *    raw EDID data is written to the buffer pointed to by data.  In both
+ *    cases, size will be filled in with the size of the data.
+ */
 struct tegra_dc_ext_control_output_edid {
        __u32 handle;
        __u32 size;