From 06165d372782dfbc14a5ff48c936e6b6b7a67eae Mon Sep 17 00:00:00 2001 From: Jason Chen Date: Tue, 30 Aug 2011 11:52:11 +0800 Subject: ipuv3: add EDID utility functions Simplify the EDID utility functions in mxc_edid.c from linux-imx-2.6.38. Signed-off-by: Jason Chen --- drivers/video/mxc/Makefile | 1 + drivers/video/mxc/mxc_edid.c | 474 +++++++++++++++++++++++++++++++++++++++++++ drivers/video/mxc/mxc_edid.h | 48 +++++ 3 files changed, 523 insertions(+) create mode 100644 drivers/video/mxc/mxc_edid.c create mode 100644 drivers/video/mxc/mxc_edid.h diff --git a/drivers/video/mxc/Makefile b/drivers/video/mxc/Makefile index 32365062cc5..610aee44f4d 100644 --- a/drivers/video/mxc/Makefile +++ b/drivers/video/mxc/Makefile @@ -1 +1,2 @@ +obj-$(CONFIG_FB_MODE_HELPERS) += mxc_edid.o obj-$(CONFIG_FB_MXC_SYNC_PANEL) += mxc_dispdrv.o mxc_ipuv3_fb.o diff --git a/drivers/video/mxc/mxc_edid.c b/drivers/video/mxc/mxc_edid.c new file mode 100644 index 00000000000..9a6add26444 --- /dev/null +++ b/drivers/video/mxc/mxc_edid.c @@ -0,0 +1,474 @@ +/* + * Copyright 2009-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup Framebuffer Framebuffer Driver for SDC and ADC. + */ + +/*! + * @file mxc_edid.c + * + * @brief MXC EDID driver + * + * @ingroup Framebuffer + */ + +/*! + * Include files + */ +#include +#include +#include "mxc_edid.h" +#include "../edid.h" + +#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 mxc_cea_mode[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, + }, + /* #4: 1280x720p@59.94/60Hz */ + [4] = { + NULL, 60, 1280, 720, 13468, 220, 110, 20, 5, 40, 5, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + 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, + }, + /* #16: 1920x1080p@60Hz */ + [16] = { + NULL, 60, 1920, 1080, 6734, 148, 88, 36, 4, 44, 5, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + 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, + }, + /* #31: 1920x1080p@50Hz */ + [31] = { + NULL, 50, 1920, 1080, 6734, 148, 528, 36, 4, 44, 5, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, 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_timing_desc_offset; + struct fb_videomode *mode, *m; + unsigned char index = 0x0; + unsigned char *block; + int i, num = 0, revision; + + if (edid[index++] != 0x2) /* only support cea ext block now */ + return -1; + revision = edid[index++]; + DPRINTK("cea extent revision %d\n", revision); + mode = kzalloc(50 * sizeof(struct fb_videomode), GFP_KERNEL); + if (mode == NULL) + return -1; + + detail_timing_desc_offset = edid[index++]; + + if (revision >= 2) { + 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; + + DPRINTK("CEA underscan %d\n", cfg->cea_underscan); + DPRINTK("CEA basicaudio %d\n", cfg->cea_basicaudio); + DPRINTK("CEA ycbcr444 %d\n", cfg->cea_ycbcr444); + DPRINTK("CEA ycbcr422 %d\n", cfg->cea_ycbcr422); + } + + if (revision >= 3) { + /* short desc */ + DPRINTK("CEA Short desc timmings\n"); + index++; + while (index < detail_timing_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(mxc_cea_mode) && + (mxc_cea_mode[cea_idx].xres)) { + DPRINTK("Support CEA Format #%d\n", cea_idx); + mode[num] = mxc_cea_mode[cea_idx]; + mode[num].flag |= FB_MODE_IS_STANDARD; + num++; + } + i++; + } + break; + } + case 0x3: /*Vendor specific data*/ + { + unsigned char IEEE_reg_iden[3]; + unsigned char deep_color; + IEEE_reg_iden[0] = edid[index+1]; + IEEE_reg_iden[1] = edid[index+2]; + IEEE_reg_iden[2] = edid[index+3]; + deep_color = edid[index+6]; + + if ((IEEE_reg_iden[0] == 0x03) && + (IEEE_reg_iden[1] == 0x0c) && + (IEEE_reg_iden[2] == 0x00)) + cfg->hdmi_cap = 1; + + if (deep_color & 0x40) + cfg->vsd_dc_48bit = true; + if (deep_color & 0x20) + cfg->vsd_dc_36bit = true; + if (deep_color & 0x10) + cfg->vsd_dc_30bit = true; + if (deep_color & 0x08) + cfg->vsd_dc_y444 = true; + if (deep_color & 0x01) + cfg->vsd_dvi_dual = true; + + DPRINTK("VSD hdmi capability %d\n", cfg->hdmi_cap); + DPRINTK("VSD support deep color 48bit %d\n", cfg->vsd_dc_48bit); + DPRINTK("VSD support deep color 36bit %d\n", cfg->vsd_dc_36bit); + DPRINTK("VSD support deep color 30bit %d\n", cfg->vsd_dc_30bit); + DPRINTK("VSD support deep color y444 %d\n", cfg->vsd_dc_y444); + DPRINTK("VSD support dvi dual %d\n", cfg->vsd_dvi_dual); + + 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_timing_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; +} + +static char compal_edid[256] = { +0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x38, 0xA3, 0x75, 0x67, 0x01, 0x01, 0x01, 0x01, +0x02, 0x13, 0x01, 0x03, 0x80, 0x33, 0x1D, 0x78, 0xEA, 0xEE, 0x95, 0xA3, 0x54, 0x4C, 0x99, 0x26, +0x0F, 0x50, 0x54, 0xBD, 0xEF, 0x80, 0x71, 0x4F, 0x81, 0x4F, 0x90, 0x40, 0x81, 0xC0, 0x81, 0x80, +0x95, 0x00, 0xB3, 0x00, 0x01, 0x01, 0x02, 0x3A, 0x80, 0x18, 0x71, 0x38, 0x2D, 0x40, 0x58, 0x2C, +0x45, 0x00, 0xFD, 0x1E, 0x11, 0x00, 0x00, 0x1E, 0x66, 0x21, 0x56, 0xAA, 0x51, 0x00, 0x1E, 0x30, +0x46, 0x8F, 0x33, 0x00, 0xFE, 0x1F, 0x11, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0xFD, 0x00, 0x37, +0x4C, 0x1E, 0x53, 0x11, 0x00, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0xFC, +0x00, 0x46, 0x32, 0x33, 0x57, 0x31, 0x41, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x01, 0x32, +0x02, 0x03, 0x24, 0x71, 0x4A, 0x90, 0x05, 0x04, 0x03, 0x07, 0x1F, 0x14, 0x13, 0x12, 0x16, 0x23, +0x09, 0x07, 0x07, 0x83, 0x01, 0x00, 0x00, 0x6C, 0x03, 0x0C, 0x00, 0x10, 0x00, 0x00, 0x00, 0xC0, +0x15, 0x15, 0x1F, 0x1F, 0x01, 0x1D, 0x80, 0x18, 0x71, 0x1C, 0x16, 0x20, 0x58, 0x2C, 0x25, 0x00, +0xFE, 0x1F, 0x11, 0x00, 0x00, 0x9E, 0x01, 0x1D, 0x00, 0x72, 0x51, 0xD0, 0x1E, 0x20, 0x6E, 0x28, +0x55, 0x00, 0xFE, 0x1F, 0x11, 0x00, 0x00, 0x1E, 0x8C, 0x0A, 0xD0, 0x8A, 0x20, 0xE0, 0x2D, 0x10, +0x10, 0x3E, 0x96, 0x00, 0xFE, 0x1F, 0x11, 0x00, 0x00, 0x18, 0x8C, 0x0A, 0xA0, 0x14, 0x51, 0xF0, +0x16, 0x00, 0x26, 0x7C, 0x43, 0x00, 0xFE, 0x1F, 0x11, 0x00, 0x00, 0x98, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE3, +}; + +static int mxc_edid_readblk(struct i2c_adapter *adp, + unsigned short addr, unsigned char *edid) +{ +#if 0 + int ret = 0, extblknum = 0; + unsigned char regaddr = 0x0; + struct i2c_msg msg[2] = { + { + .addr = addr, + .flags = 0, + .len = 1, + .buf = ®addr, + }, { + .addr = addr, + .flags = I2C_M_RD, + .len = EDID_LENGTH, + .buf = edid, + }, + }; + + ret = i2c_transfer(adp, msg, ARRAY_SIZE(msg)); + if (ret != ARRAY_SIZE(msg)) { + DPRINTK("unable to read EDID block\n"); + return -EIO; + } + + if (edid[1] == 0x00) + return -ENOENT; + + extblknum = edid[0x7E]; + + if (extblknum) { + regaddr = 128; + msg[1].buf = edid + EDID_LENGTH; + + ret = i2c_transfer(adp, msg, ARRAY_SIZE(msg)); + if (ret != ARRAY_SIZE(msg)) { + DPRINTK("unable to read EDID ext block\n"); + return -EIO; + } + } + + return extblknum; +#else + memcpy(edid, compal_edid, 256); + return 1; +#endif +} + +static int mxc_edid_readsegblk(struct i2c_adapter *adp, unsigned short addr, + unsigned char *edid, int seg_num) +{ + int ret = 0; + unsigned char segment = 0x1, regaddr = 0; + struct i2c_msg msg[3] = { + { + .addr = 0x30, + .flags = 0, + .len = 1, + .buf = &segment, + }, { + .addr = addr, + .flags = 0, + .len = 1, + .buf = ®addr, + }, { + .addr = addr, + .flags = I2C_M_RD, + .len = EDID_LENGTH, + .buf = edid, + }, + }; + + ret = i2c_transfer(adp, msg, ARRAY_SIZE(msg)); + if (ret != ARRAY_SIZE(msg)) { + DPRINTK("unable to read EDID block\n"); + return -EIO; + } + + if (seg_num == 2) { + regaddr = 128; + msg[2].buf = edid + EDID_LENGTH; + + ret = i2c_transfer(adp, msg, ARRAY_SIZE(msg)); + if (ret != ARRAY_SIZE(msg)) { + DPRINTK("unable to read EDID block\n"); + return -EIO; + } + } + + return ret; +} + +int mxc_edid_var_to_vic(struct fb_var_screeninfo *var) +{ + int i; + struct fb_videomode m; + + for (i = 0; i < ARRAY_SIZE(mxc_cea_mode); i++) { + fb_var_to_videomode(&m, var); + if (fb_mode_is_equal(&m, &mxc_cea_mode[i])) + break; + } + + if (i == ARRAY_SIZE(mxc_cea_mode)) + return 0; + + return i; +} +EXPORT_SYMBOL(mxc_edid_var_to_vic); + +/* make sure edid has 512 bytes*/ +int mxc_edid_read(struct i2c_adapter *adp, unsigned short addr, + unsigned char *edid, struct mxc_edid_cfg *cfg, struct fb_info *fbi) +{ + int ret = 0, extblknum; + if (!adp || !edid || !cfg || !fbi) + return -EINVAL; + + memset(edid, 0, EDID_LENGTH*4); + memset(cfg, 0, sizeof(struct mxc_edid_cfg)); + + extblknum = mxc_edid_readblk(adp, addr, edid); + if (extblknum < 0) + return extblknum; + + /* edid first block parsing */ + memset(&fbi->monspecs, 0, sizeof(fbi->monspecs)); + fb_edid_to_monspecs(edid, &fbi->monspecs); + + if (extblknum) { + int i; + + /* need read segment block? */ + if (extblknum > 1) { + ret = mxc_edid_readsegblk(adp, addr, + edid + EDID_LENGTH*2, extblknum - 1); + if (ret < 0) + return ret; + } + + for (i = 1; i <= extblknum; i++) + /* edid ext block parsing */ + mxc_edid_parse_ext_blk(edid + i*EDID_LENGTH, + cfg, &fbi->monspecs); + } + + return 0; +} +EXPORT_SYMBOL(mxc_edid_read); diff --git a/drivers/video/mxc/mxc_edid.h b/drivers/video/mxc/mxc_edid.h new file mode 100644 index 00000000000..ec65a5fa9bd --- /dev/null +++ b/drivers/video/mxc/mxc_edid.h @@ -0,0 +1,48 @@ +/* + * Copyright 2009-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup Framebuffer Framebuffer Driver for SDC and ADC. + */ + +/*! + * @file mxc_edid.h + * + * @brief MXC EDID tools + * + * @ingroup Framebuffer + */ + +#ifndef MXC_EDID_H +#define MXC_EDID_H + +struct mxc_edid_cfg { + bool cea_underscan; + bool cea_basicaudio; + bool cea_ycbcr444; + bool cea_ycbcr422; + bool hdmi_cap; + + /*VSD*/ + bool vsd_dc_48bit; + bool vsd_dc_36bit; + bool vsd_dc_30bit; + bool vsd_dc_y444; + bool vsd_dvi_dual; +}; + +int mxc_edid_var_to_vic(struct fb_var_screeninfo *var); +int mxc_edid_read(struct i2c_adapter *adp, unsigned short addr, + unsigned char *edid, struct mxc_edid_cfg *cfg, struct fb_info *fbi); + +#endif -- cgit v1.2.3