diff options
-rw-r--r-- | arch/arm/mach-tegra/include/mach/dc.h | 14 | ||||
-rw-r--r-- | drivers/video/tegra/dc/edid.c | 150 | ||||
-rw-r--r-- | drivers/video/tegra/dc/edid.h | 4 | ||||
-rw-r--r-- | drivers/video/tegra/dc/ext/control.c | 39 | ||||
-rw-r--r-- | drivers/video/tegra/dc/hdmi.c | 19 | ||||
-rw-r--r-- | include/video/tegra_dc_ext.h | 10 |
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; |