From d4c753a482630644148c77c076ae64e87ae9ecf6 Mon Sep 17 00:00:00 2001 From: chzhang Date: Wed, 15 Dec 2010 14:29:21 -0800 Subject: HDMI stereo support - kernel code changes Signed-off-by: Chong Zhang Change-Id: I53eea3b14852aecd589dfbd0461258e064835cc9 Reviewed-on: http://git-master/r/11405 Reviewed-by: Daniel Willemsen --- arch/arm/mach-tegra/include/mach/dc.h | 1 + drivers/video/modedb.c | 4 +- drivers/video/tegra/dc/edid.c | 90 ++++++++++++++++++++++++++++++++++- drivers/video/tegra/dc/hdmi.c | 49 +++++++++++++++++++ drivers/video/tegra/dc/hdmi.h | 31 ++++++++++++ drivers/video/tegra/fb.c | 26 ++++++++++ include/linux/fb.h | 9 ++++ 7 files changed, 207 insertions(+), 3 deletions(-) diff --git a/arch/arm/mach-tegra/include/mach/dc.h b/arch/arm/mach-tegra/include/mach/dc.h index 0bc75fb4499f..426af854eb8d 100644 --- a/arch/arm/mach-tegra/include/mach/dc.h +++ b/arch/arm/mach-tegra/include/mach/dc.h @@ -37,6 +37,7 @@ struct tegra_dc_mode { int v_active; int h_front_porch; int v_front_porch; + int stereo_mode; }; enum { diff --git a/drivers/video/modedb.c b/drivers/video/modedb.c index 209e6be1163e..930070e3ccf4 100644 --- a/drivers/video/modedb.c +++ b/drivers/video/modedb.c @@ -1163,7 +1163,7 @@ void fb_var_to_videomode(struct fb_videomode *mode, mode->upper_margin = var->upper_margin; mode->lower_margin = var->lower_margin; mode->sync = var->sync; - mode->vmode = var->vmode & FB_VMODE_MASK; + mode->vmode = var->vmode & (FB_VMODE_MASK | FB_VMODE_STEREO_MASK); mode->flag = FB_MODE_IS_FROM_VAR; mode->refresh = 0; @@ -1208,7 +1208,7 @@ void fb_videomode_to_var(struct fb_var_screeninfo *var, var->hsync_len = mode->hsync_len; var->vsync_len = mode->vsync_len; var->sync = mode->sync; - var->vmode = mode->vmode & FB_VMODE_MASK; + var->vmode = mode->vmode & (FB_VMODE_MASK | FB_VMODE_STEREO_MASK); } /** diff --git a/drivers/video/tegra/dc/edid.c b/drivers/video/tegra/dc/edid.c index 812a0087a96d..792ea54c98ff 100644 --- a/drivers/video/tegra/dc/edid.c +++ b/drivers/video/tegra/dc/edid.c @@ -32,6 +32,7 @@ struct tegra_edid { u8 *data; unsigned len; + u8 support_stereo; }; #if defined(DEBUG) || defined(CONFIG_DEBUG_FS) @@ -162,10 +163,84 @@ 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) +{ + u8 *ptr; + u8 tmp; + u8 code; + int len; + + ptr = &raw[4]; + + while (ptr < &raw[idx]) { + tmp = *ptr; + len = tmp & 0x1f; + + /* HDMI Specification v1.4a, section 8.3.2: + * see Table 8-16 for HDMI VSDB format. + * data blocks have tags in top 3 bits: + * tag code 2: video data block + * tag code 3: vendor specific data block + */ + code = (tmp >> 5) & 0x3; + switch (code) { + /* case 2 is commented out for now */ + case 3: + { + int j = 0; + + if ((len >= 8) && + (ptr[1] == 0x03) && + (ptr[2] == 0x0c) && + (ptr[3] == 0)) { + j = 8; + tmp = ptr[j++]; + /* HDMI_Video_present? */ + if (tmp & 0x20) { + /* Latency_Fields_present? */ + if (tmp & 0x80) + j += 2; + /* I_Latency_Fields_present? */ + if (tmp & 0x40) + j += 2; + /* 3D_present? */ + if (j <= len && (ptr[j] & 0x80)) + edid->support_stereo = 1; + } + } + + len++; + ptr += len; /* adding the header */ + break; + } + default: + len++; /* len does not include header */ + ptr += len; + break; + } + } + + return 0; +} + +int tegra_edid_mode_support_stereo(struct fb_videomode *mode) +{ + if (!mode) + return 0; + + if (mode->xres == 1280 && mode->yres == 720 && mode->refresh == 60) + return 1; + + if (mode->xres == 1280 && mode->yres == 720 && mode->refresh == 50) + return 1; + + return 0; +} int tegra_edid_get_monspecs(struct tegra_edid *edid, struct fb_monspecs *specs) { int i; + int j; int ret; int extension_blocks; @@ -183,8 +258,21 @@ int tegra_edid_get_monspecs(struct tegra_edid *edid, struct fb_monspecs *specs) if (ret < 0) break; - if (edid->data[i * 128] == 0x2) + if (edid->data[i * 128] == 0x2) { fb_edid_add_monspecs(edid->data + i * 128, specs); + + tegra_edid_parse_ext_block(edid->data + i * 128, + edid->data[i * 128 + 2], edid); + + if (edid->support_stereo) { + for (j = 0; j < specs->modedb_len; j++) { + if (tegra_edid_mode_support_stereo( + &specs->modedb[j])) + specs->modedb[j].vmode |= + FB_VMODE_STEREO_FRAME_PACK; + } + } + } } edid->len = i * 128; diff --git a/drivers/video/tegra/dc/hdmi.c b/drivers/video/tegra/dc/hdmi.c index 3100bd090b78..3d9d48733265 100644 --- a/drivers/video/tegra/dc/hdmi.c +++ b/drivers/video/tegra/dc/hdmi.c @@ -81,6 +81,22 @@ const struct fb_videomode tegra_dc_hdmi_supported_modes[] = { .sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, }, + /* 1280x720p 60hz: EIA/CEA-861-B Format 4 (Stereo)*/ + { + .xres = 1280, + .yres = 720, + .pixclock = KHZ2PICOS(74250), + .hsync_len = 40, /* h_sync_width */ + .vsync_len = 5, /* v_sync_width */ + .left_margin = 220, /* h_back_porch */ + .upper_margin = 20, /* v_back_porch */ + .right_margin = 110, /* h_front_porch */ + .lower_margin = 5, /* v_front_porch */ + .vmode = FB_VMODE_NONINTERLACED | + FB_VMODE_STEREO_FRAME_PACK, + .sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + }, + /* 720x480p 59.94hz: EIA/CEA-861-B Formats 2 & 3 */ { .xres = 720, @@ -851,6 +867,38 @@ static void tegra_dc_hdmi_setup_avi_infoframe(struct tegra_dc *dc, bool dvi) HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_CTRL); } +static void tegra_dc_hdmi_setup_stereo_infoframe(struct tegra_dc *dc) +{ + struct tegra_dc_hdmi_data *hdmi = tegra_dc_get_outdata(dc); + struct hdmi_stereo_infoframe stereo; + u32 val; + + if (!dc->mode.stereo_mode) { + val = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_HDMI_GENERIC_CTRL); + val &= ~GENERIC_CTRL_ENABLE; + tegra_hdmi_writel(hdmi, val, HDMI_NV_PDISP_HDMI_GENERIC_CTRL); + return; + } + + memset(&stereo, 0x0, sizeof(stereo)); + + stereo.regid0 = 0x03; + stereo.regid1 = 0x0c; + stereo.regid2 = 0x00; + stereo.hdmi_video_format = 2; /* 3D_Structure present */ + stereo._3d_structure = 0; /* frame packing */ + + tegra_dc_hdmi_write_infopack(dc, HDMI_NV_PDISP_HDMI_GENERIC_HEADER, + HDMI_INFOFRAME_TYPE_VENDOR, + HDMI_VENDOR_VERSION, + &stereo, 6); + + val = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_HDMI_GENERIC_CTRL); + val |= GENERIC_CTRL_ENABLE; + + tegra_hdmi_writel(hdmi, val, HDMI_NV_PDISP_HDMI_GENERIC_CTRL); +} + static void tegra_dc_hdmi_setup_audio_infoframe(struct tegra_dc *dc, bool dvi) { struct tegra_dc_hdmi_data *hdmi = tegra_dc_get_outdata(dc); @@ -971,6 +1019,7 @@ static void tegra_dc_hdmi_enable(struct tegra_dc *dc) tegra_dc_hdmi_setup_avi_infoframe(dc, dvi); tegra_dc_hdmi_setup_audio_infoframe(dc, dvi); + tegra_dc_hdmi_setup_stereo_infoframe(dc); /* TMDS CONFIG */ pll0 = 0x200033f; diff --git a/drivers/video/tegra/dc/hdmi.h b/drivers/video/tegra/dc/hdmi.h index 0189f08719fe..5f0fc2e3f73e 100644 --- a/drivers/video/tegra/dc/hdmi.h +++ b/drivers/video/tegra/dc/hdmi.h @@ -180,4 +180,35 @@ struct hdmi_audio_infoframe { #define HDMI_AUDIO_CXT_HE_AAC_V2 0x2 #define HDMI_AUDIO_CXT_MPEG_SURROUND 0x3 + +/* all fields little endian */ +struct hdmi_stereo_infoframe { + /* PB0 */ + u8 csum; + + /* PB1 */ + u8 regid0; + + /* PB2 */ + u8 regid1; + + /* PB3 */ + u8 regid2; + + /* PB4 */ + unsigned res1:5; + unsigned hdmi_video_format:3; + + /* PB5 */ + unsigned res2:4; + unsigned _3d_structure:4; + + /* PB6*/ + unsigned res3:4; + unsigned _3d_ext_data:4; + +} __attribute__((packed)); + +#define HDMI_VENDOR_VERSION 0x01 + #endif diff --git a/drivers/video/tegra/fb.c b/drivers/video/tegra/fb.c index eae17b5dbc28..01eaad925da6 100644 --- a/drivers/video/tegra/fb.c +++ b/drivers/video/tegra/fb.c @@ -175,6 +175,21 @@ static int tegra_fb_set_par(struct fb_info *info) mode.v_active = info->mode->yres; mode.h_front_porch = info->mode->right_margin; mode.v_front_porch = info->mode->lower_margin; + /* + * only enable stereo if the mode supports it and + * client requests it + */ + mode.stereo_mode = !!(var->vmode & info->mode->vmode & + FB_VMODE_STEREO_FRAME_PACK); + + if (mode.stereo_mode) { + mode.pclk *= 2; + /* total v_active = yres*2 + activespace */ + mode.v_active = info->mode->yres*2 + + info->mode->vsync_len + + info->mode->upper_margin + + info->mode->lower_margin; + } tegra_dc_set_mode(tegra_fb->win->dc, &mode); @@ -570,6 +585,17 @@ static int tegra_fb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long &var, sizeof(var))) return -EFAULT; i++; + + if (var.vmode & FB_VMODE_STEREO_MASK) { + if (i >= modedb.modedb_len) + break; + var.vmode &= ~FB_VMODE_STEREO_MASK; + if (copy_to_user( + (void __user *)&modedb.modedb[i], + &var, sizeof(var))) + return -EFAULT; + i++; + } } modedb.modedb_len = i; diff --git a/include/linux/fb.h b/include/linux/fb.h index 8b1f0a982bdb..3e816a73b448 100644 --- a/include/linux/fb.h +++ b/include/linux/fb.h @@ -224,6 +224,15 @@ struct fb_bitfield { #define FB_VMODE_SMOOTH_XPAN 512 /* smooth xpan possible (internally used) */ #define FB_VMODE_CONUPDATE 512 /* don't update x/yoffset */ +/* + * Stereo modes + */ +#define FB_VMODE_STEREO_NONE 0x00000000 /* not stereo */ +#define FB_VMODE_STEREO_FRAME_PACK 0x01000000 /* frame packing */ +#define FB_VMODE_STEREO_TOP_BOTTOM 0x02000000 /* top-bottom */ +#define FB_VMODE_STEREO_LEFT_RIGHT 0x04000000 /* left-right */ +#define FB_VMODE_STEREO_MASK 0xFF000000 + /* * Display rotation support */ -- cgit v1.2.3