summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason Chen <b02280@freescale.com>2011-01-17 13:51:24 +0800
committerJason Chen <b02280@freescale.com>2011-01-17 13:51:24 +0800
commitcf5347d7e8d18274f74bc7015a5dc237a8696751 (patch)
tree6e9ba1793509c1d5a60cd0416e4defbbbaec9996
parentc51ce7fdab4ea8a89b6a58c7fc41d3b697637412 (diff)
ENGR00137991-2 mxc edid: add cea ext block parser
add cea ext block parser. it provide more video modes and the info of device HDMI compatible. Signed-off-by: Jason Chen <b02280@freescale.com>
-rw-r--r--drivers/video/mxc/mxc_edid.c246
-rw-r--r--drivers/video/mxc/mxcfb_sii9022.c47
2 files changed, 274 insertions, 19 deletions
diff --git a/drivers/video/mxc/mxc_edid.c b/drivers/video/mxc/mxc_edid.c
index 234a5d38c111..0289c5253ad7 100644
--- a/drivers/video/mxc/mxc_edid.c
+++ b/drivers/video/mxc/mxc_edid.c
@@ -1,5 +1,5 @@
/*
- * Copyright 2009-2010 Freescale Semiconductor, Inc. All Rights Reserved.
+ * Copyright 2009-2011 Freescale Semiconductor, Inc. All Rights Reserved.
*/
/*
@@ -27,11 +27,237 @@
* Include files
*/
#include <linux/i2c.h>
+#include <linux/fb.h>
+#include <mach/mxc_edid.h>
+#include "../edid.h"
-#define EDID_LENGTH 128
+#undef DEBUG /* define this for verbose EDID parsing output */
+
+#ifdef DEBUG
+#define DPRINTK(fmt, args...) printk(fmt, ## args)
+#else
+#define DPRINTK(fmt, args...)
+#endif
+
+const struct fb_videomode cea_modes[64] = {
+ /* #1: 640x480p@59.94/60Hz */
+ [1] = {
+ NULL, 60, 640, 480, 39722, 48, 16, 33, 10, 96, 2, 0,
+ FB_VMODE_NONINTERLACED, 0,
+ },
+ /* #3: 720x480p@59.94/60Hz */
+ [3] = {
+ NULL, 60, 720, 480, 37037, 60, 16, 30, 9, 62, 6, 0,
+ FB_VMODE_NONINTERLACED, 0,
+ },
+ /* #5: 1920x1080i@59.94/60Hz */
+ [5] = {
+ NULL, 60, 1920, 1080, 13763, 148, 88, 15, 2, 44, 5,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_INTERLACED, 0,
+ },
+ /* #7: 720(1440)x480iH@59.94/60Hz */
+ [7] = {
+ NULL, 60, 1440, 480, 18554/*37108*/, 114, 38, 15, 4, 124, 3, 0,
+ FB_VMODE_INTERLACED, 0,
+ },
+ /* #9: 720(1440)x240pH@59.94/60Hz */
+ [9] = {
+ NULL, 60, 1440, 240, 18554, 114, 38, 16, 4, 124, 3, 0,
+ FB_VMODE_NONINTERLACED, 0,
+ },
+ /* #18: 720x576pH@50Hz */
+ [18] = {
+ NULL, 50, 720, 576, 37037, 68, 12, 39, 5, 64, 5, 0,
+ FB_VMODE_NONINTERLACED, 0,
+ },
+ /* #19: 1280x720p@50Hz */
+ [19] = {
+ NULL, 50, 1280, 720, 13468, 220, 440, 20, 5, 40, 5,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_NONINTERLACED, 0,
+ },
+ /* #20: 1920x1080i@50Hz */
+ [20] = {
+ NULL, 50, 1920, 1080, 13480, 148, 528, 15, 5, 528, 5,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_INTERLACED, 0,
+ },
+ /* #32: 1920x1080p@23.98/24Hz */
+ [32] = {
+ NULL, 24, 1920, 1080, 13468, 148, 638, 36, 4, 44, 5,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_NONINTERLACED, 0,
+ },
+ /* #35: (2880)x480p4x@59.94/60Hz */
+ [35] = {
+ NULL, 60, 2880, 480, 9250, 240, 64, 30, 9, 248, 6, 0,
+ FB_VMODE_NONINTERLACED, 0,
+ },
+};
+
+static void get_detailed_timing(unsigned char *block,
+ struct fb_videomode *mode)
+{
+ mode->xres = H_ACTIVE;
+ mode->yres = V_ACTIVE;
+ mode->pixclock = PIXEL_CLOCK;
+ mode->pixclock /= 1000;
+ mode->pixclock = KHZ2PICOS(mode->pixclock);
+ mode->right_margin = H_SYNC_OFFSET;
+ mode->left_margin = (H_ACTIVE + H_BLANKING) -
+ (H_ACTIVE + H_SYNC_OFFSET + H_SYNC_WIDTH);
+ mode->upper_margin = V_BLANKING - V_SYNC_OFFSET -
+ V_SYNC_WIDTH;
+ mode->lower_margin = V_SYNC_OFFSET;
+ mode->hsync_len = H_SYNC_WIDTH;
+ mode->vsync_len = V_SYNC_WIDTH;
+ if (HSYNC_POSITIVE)
+ mode->sync |= FB_SYNC_HOR_HIGH_ACT;
+ if (VSYNC_POSITIVE)
+ mode->sync |= FB_SYNC_VERT_HIGH_ACT;
+ mode->refresh = PIXEL_CLOCK/((H_ACTIVE + H_BLANKING) *
+ (V_ACTIVE + V_BLANKING));
+ if (INTERLACED) {
+ mode->yres *= 2;
+ mode->upper_margin *= 2;
+ mode->lower_margin *= 2;
+ mode->vsync_len *= 2;
+ mode->vmode |= FB_VMODE_INTERLACED;
+ }
+ mode->flag = FB_MODE_IS_DETAILED;
+
+ DPRINTK(" %d MHz ", PIXEL_CLOCK/1000000);
+ DPRINTK("%d %d %d %d ", H_ACTIVE, H_ACTIVE + H_SYNC_OFFSET,
+ H_ACTIVE + H_SYNC_OFFSET + H_SYNC_WIDTH, H_ACTIVE + H_BLANKING);
+ DPRINTK("%d %d %d %d ", V_ACTIVE, V_ACTIVE + V_SYNC_OFFSET,
+ V_ACTIVE + V_SYNC_OFFSET + V_SYNC_WIDTH, V_ACTIVE + V_BLANKING);
+ DPRINTK("%sHSync %sVSync\n\n", (HSYNC_POSITIVE) ? "+" : "-",
+ (VSYNC_POSITIVE) ? "+" : "-");
+}
+
+int mxc_edid_parse_ext_blk(unsigned char *edid,
+ struct mxc_edid_cfg *cfg,
+ struct fb_monspecs *specs)
+{
+ char detail_timming_desc_offset;
+ struct fb_videomode *mode, *m;
+ unsigned char index = 0x0;
+ unsigned char *block;
+ int i, num = 0;
+
+ if (edid[index++] != 0x2) /* only support cea ext block now */
+ return -1;
+ if (edid[index++] != 0x3) /* only support version 3*/
+ return -1;
+ mode = kzalloc(50 * sizeof(struct fb_videomode), GFP_KERNEL);
+ if (mode == NULL)
+ return -1;
+
+ detail_timming_desc_offset = edid[index++];
+
+ cfg->cea_underscan = (edid[index] >> 7) & 0x1;
+ cfg->cea_basicaudio = (edid[index] >> 6) & 0x1;
+ cfg->cea_ycbcr444 = (edid[index] >> 5) & 0x1;
+ cfg->cea_ycbcr422 = (edid[index] >> 4) & 0x1;
+
+ /* short desc */
+ DPRINTK("CEA Short desc timmings\n");
+ index++;
+ while (index < detail_timming_desc_offset) {
+ unsigned char tagcode, blklen;
+
+ tagcode = (edid[index] >> 5) & 0x7;
+ blklen = (edid[index]) & 0x1f;
+
+ DPRINTK("Tagcode %x Len %d\n", tagcode, blklen);
+
+ switch (tagcode) {
+ case 0x2: /*Video data block*/
+ {
+ int cea_idx;
+ i = 0;
+ while (i < blklen) {
+ index++;
+ cea_idx = edid[index] & 0x7f;
+ if (cea_idx < ARRAY_SIZE(cea_modes) &&
+ (cea_modes[cea_idx].xres)) {
+ DPRINTK("Support CEA Format #%d\n", cea_idx);
+ mode[num] = cea_modes[cea_idx];
+ mode[num].flag |= FB_MODE_IS_STANDARD;
+ num++;
+ }
+ i++;
+ }
+ break;
+ }
+ case 0x3: /*Vendor specific data*/
+ {
+ unsigned char IEEE_reg_iden[3];
+ IEEE_reg_iden[0] = edid[index+1];
+ IEEE_reg_iden[1] = edid[index+2];
+ IEEE_reg_iden[2] = edid[index+3];
+
+ if ((IEEE_reg_iden[0] == 0x03) &&
+ (IEEE_reg_iden[1] == 0x0c) &&
+ (IEEE_reg_iden[2] == 0x00))
+ cfg->hdmi_cap = 1;
+ index += blklen;
+ break;
+ }
+ case 0x1: /*Audio data block*/
+ case 0x4: /*Speaker allocation block*/
+ case 0x7: /*User extended block*/
+ default:
+ /* skip */
+ index += blklen;
+ break;
+ }
+
+ index++;
+ }
+
+ /* long desc */
+ DPRINTK("CEA long desc timmings\n");
+ index = detail_timming_desc_offset;
+ block = edid + index;
+ while (index < (EDID_LENGTH - DETAILED_TIMING_DESCRIPTION_SIZE)) {
+ if (!(block[0] == 0x00 && block[1] == 0x00)) {
+ get_detailed_timing(block, &mode[num]);
+ num++;
+ }
+ block += DETAILED_TIMING_DESCRIPTION_SIZE;
+ index += DETAILED_TIMING_DESCRIPTION_SIZE;
+ }
+
+ if (!num) {
+ kfree(mode);
+ return 0;
+ }
+
+ m = kmalloc((num + specs->modedb_len) *
+ sizeof(struct fb_videomode), GFP_KERNEL);
+ if (!m)
+ return 0;
+
+ if (specs->modedb_len) {
+ memmove(m, specs->modedb,
+ specs->modedb_len * sizeof(struct fb_videomode));
+ kfree(specs->modedb);
+ }
+ memmove(m+specs->modedb_len, mode,
+ num * sizeof(struct fb_videomode));
+ kfree(mode);
+
+ specs->modedb_len += num;
+ specs->modedb = m;
+
+ return 0;
+}
/* make sure edid has 256 bytes*/
-int read_edid(struct i2c_adapter *adp, unsigned char *edid)
+int mxc_edid_read(struct i2c_adapter *adp, unsigned char *edid,
+ struct mxc_edid_cfg *cfg, struct fb_info *fbi)
{
u8 buf0[2] = {0, 0};
int dat = 0;
@@ -53,6 +279,9 @@ int read_edid(struct i2c_adapter *adp, unsigned char *edid)
if (adp == NULL)
return -EINVAL;
+ memset(edid, 0, 256);
+ memset(cfg, 0, sizeof(struct mxc_edid_cfg));
+
buf0[0] = 0x00;
dat = i2c_transfer(adp, msg, 2);
@@ -67,16 +296,23 @@ int read_edid(struct i2c_adapter *adp, unsigned char *edid)
if (edid[1] == 0x00)
return -ENOENT;
+ /* edid first block parsing */
+ memset(&fbi->monspecs, 0, sizeof(fbi->monspecs));
+ fb_edid_to_monspecs(edid, &fbi->monspecs);
+
/* need read ext block? Only support one more blk now*/
if (edid[0x7E]) {
if (edid[0x7E] > 1)
- printk(KERN_WARNING "Edid has %d ext block, \
- but now only support 1 ext blk\n", edid[0x7E]);
+ DPRINTK("Edid has %d ext block, \
+ but now only support 1 ext blk\n", edid[0x7E]);
buf0[0] = 0x80;
msg[1].buf = edid + EDID_LENGTH;
dat = i2c_transfer(adp, msg, 2);
if (dat < 0)
return dat;
+
+ /* edid ext block parsing */
+ mxc_edid_parse_ext_blk(edid + 128, cfg, &fbi->monspecs);
}
return 0;
diff --git a/drivers/video/mxc/mxcfb_sii9022.c b/drivers/video/mxc/mxcfb_sii9022.c
index b7104ce43796..273d04a20a6b 100644
--- a/drivers/video/mxc/mxcfb_sii9022.c
+++ b/drivers/video/mxc/mxcfb_sii9022.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010 Freescale Semiconductor, Inc. All Rights Reserved.
+ * Copyright (C) 2010-2011 Freescale Semiconductor, Inc. All Rights Reserved.
*/
/*
@@ -56,6 +56,7 @@ struct sii9022_data {
struct i2c_client *client;
struct delayed_work det_work;
struct fb_info *fbi;
+ struct mxc_edid_cfg edid_cfg;
u8 cable_plugin;
u8 edid[256];
} sii9022;
@@ -127,27 +128,41 @@ static void sii9022_setup(struct fb_info *fbi)
static int sii9022_read_edid(void)
{
- int dat, ret;
+ int old, dat, ret, cnt = 100;
- dat = i2c_smbus_read_byte_data(sii9022.client, 0x1A);
+ old = i2c_smbus_read_byte_data(sii9022.client, 0x1A);
- i2c_smbus_write_byte_data(sii9022.client, 0x1A, dat | 0x4);
+ i2c_smbus_write_byte_data(sii9022.client, 0x1A, old | 0x4);
do {
+ cnt--;
msleep(10);
dat = i2c_smbus_read_byte_data(sii9022.client, 0x1A);
- } while (!(dat & 0x2));
+ } while ((!(dat & 0x2)) && cnt);
- i2c_smbus_write_byte_data(sii9022.client, 0x1A, 0x06);
+ if (!cnt) {
+ ret = -1;
+ goto done;
+ }
+
+ i2c_smbus_write_byte_data(sii9022.client, 0x1A, old | 0x06);
/* edid reading */
- ret = read_edid(sii9022.client->adapter, sii9022.edid);
+ ret = mxc_edid_read(sii9022.client->adapter, sii9022.edid,
+ &sii9022.edid_cfg, sii9022.fbi);
+ cnt = 100;
do {
- i2c_smbus_write_byte_data(sii9022.client, 0x1A, 0x00);
+ cnt--;
+ i2c_smbus_write_byte_data(sii9022.client, 0x1A, old & ~0x6);
msleep(10);
dat = i2c_smbus_read_byte_data(sii9022.client, 0x1A);
- } while (dat & 0x6);
+ } while ((dat & 0x6) && cnt);
+
+ if (!cnt)
+ ret = -1;
+done:
+ i2c_smbus_write_byte_data(sii9022.client, 0x1A, old);
return ret;
}
@@ -167,9 +182,6 @@ static void det_worker(struct work_struct *work)
dev_err(&sii9022.client->dev,
"SII9022: read edid fail\n");
else {
- /* change fbi modedb */
- memset(&sii9022.fbi->monspecs, 0, sizeof(sii9022.fbi->monspecs));
- fb_edid_to_monspecs(sii9022.edid, &(sii9022.fbi->monspecs));
if (sii9022.fbi->monspecs.modedb_len > 0) {
int i;
@@ -177,6 +189,7 @@ static void det_worker(struct work_struct *work)
fb_add_videomode(&sii9022.fbi->monspecs.modedb[i],
&sii9022.fbi->modelist);
}
+ sii9022_poweron();
}
} else {
sii9022.cable_plugin = 0;
@@ -331,14 +344,20 @@ static int sii9022_resume(struct i2c_client *client)
static void sii9022_poweron(void)
{
/* Turn on DVI or HDMI */
- i2c_smbus_write_byte_data(sii9022.client, 0x1A, 0x01);
+ if (sii9022.edid_cfg.hdmi_cap)
+ i2c_smbus_write_byte_data(sii9022.client, 0x1A, 0x01);
+ else
+ i2c_smbus_write_byte_data(sii9022.client, 0x1A, 0x00);
return;
}
static void sii9022_poweroff(void)
{
/* disable tmds before changing resolution */
- i2c_smbus_write_byte_data(sii9022.client, 0x1A, 0x11);
+ if (sii9022.edid_cfg.hdmi_cap)
+ i2c_smbus_write_byte_data(sii9022.client, 0x1A, 0x11);
+ else
+ i2c_smbus_write_byte_data(sii9022.client, 0x1A, 0x10);
return;
}