summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--arch/arm/mach-tegra/include/mach/dc.h14
-rw-r--r--drivers/video/tegra/dc/edid.c150
-rw-r--r--drivers/video/tegra/dc/edid.h4
-rw-r--r--drivers/video/tegra/dc/ext/control.c39
-rw-r--r--drivers/video/tegra/dc/hdmi.c19
-rw-r--r--include/video/tegra_dc_ext.h10
6 files changed, 195 insertions, 41 deletions
diff --git a/arch/arm/mach-tegra/include/mach/dc.h b/arch/arm/mach-tegra/include/mach/dc.h
index d291f29268d7..e3cc94138d32 100644
--- a/arch/arm/mach-tegra/include/mach/dc.h
+++ b/arch/arm/mach-tegra/include/mach/dc.h
@@ -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
diff --git a/drivers/video/tegra/dc/edid.c b/drivers/video/tegra/dc/edid.c
index 055cea3a11fe..7b23c60bc57e 100644
--- a/drivers/video/tegra/dc/edid.c
+++ b/drivers/video/tegra/dc/edid.c
@@ -26,33 +26,52 @@
#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 },
{ }
diff --git a/drivers/video/tegra/dc/edid.h b/drivers/video/tegra/dc/edid.h
index 9952db7c21df..773fa0afd18c 100644
--- a/drivers/video/tegra/dc/edid.h
+++ b/drivers/video/tegra/dc/edid.h
@@ -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
diff --git a/drivers/video/tegra/dc/ext/control.c b/drivers/video/tegra/dc/ext/control.c
index 1bf0b2de0711..f6fb3c0d9006 100644
--- a/drivers/video/tegra/dc/ext/control.c
+++ b/drivers/video/tegra/dc/ext/control.c
@@ -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)
diff --git a/drivers/video/tegra/dc/hdmi.c b/drivers/video/tegra/dc/hdmi.c
index 003228bba5df..5679ce8134e1 100644
--- a/drivers/video/tegra/dc/hdmi.c
+++ b/drivers/video/tegra/dc/hdmi.c
@@ -1700,3 +1700,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);
diff --git a/include/video/tegra_dc_ext.h b/include/video/tegra_dc_ext.h
index 824afeaf147b..bdfebaeb3c9a 100644
--- a/include/video/tegra_dc_ext.h
+++ b/include/video/tegra_dc_ext.h
@@ -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;