#include "libedid.h"
#include "libddc.h"
#include "video.h"
#include "audio.h"

//@{
/**
 * @name EDID Addresses
 */
#define EDID_ADDR                                       (0xa0)
#define EDID_SEGMENT_POINTER                            (0x60)
//@}

//@{
/**
 * @name EDID offset and bit values
 */
#define SIZEOFBYTE                                      (8)
#define SIZEOFEDIDBLOCK                                 (0x80)
#define EDID_EXTENSION_NUMBER_POS                       (0x7E)

#define EDID_TIMING_EXT_TAG_ADDR_POS                    (0)
#define EDID_TIMING_EXT_REV_NUMBER_POS                  (1)
#define EDID_DETAILED_TIMING_OFFSET_POS                 (2)
#define EDID_DATA_BLOCK_START_POS                       (4)

// for Extension Data Block
#define EDID_TIMING_EXT_TAG_VAL                         (0x02)
#define EDID_BLOCK_MAP_EXT_TAG_VAL                      (0xF0)

#define EDID_SHORT_AUD_DEC_TAG_VAL                      (1<<5)
#define EDID_SHORT_VID_DEC_TAG_VAL                      (2<<5)
#define EDID_VSDB_TAG_VAL                               (3<<5)
#define EDID_SPEAKER_ALLOCATION_TAG_VAL                 (4<<5)
#define EDID_VESA_DTC_TAG_VAL                           (5<<5)
#define EDID_RESERVED_TAG_VAL                           (6<<5)

#define EDID_EXTENDED_TAG_VAL                           (7<<5)
#define EDID_EXTENDED_COLORIMETRY_VAL                   (5)
#define EDID_EXTENDED_COLORIMETRY_BLOCK_LEN             (3)

#define EDID_TAG_CODE_MASK                              (1<<7 | 1<<6 | 1<<5)
#define EDID_DATA_BLOCK_SIZE_MASK                       (1<<4 | 1<<3 | 1<<2 | 1<<1 | 1<<0)

#define EDID_VSDB_MIN_LENGTH_VAL                        (5)

// for Established Timings
#define EDID_ET_POS                                     (0x23)
#define EDID_ET_640x480p_VAL                            (0x20)

// for DTD
#define EDID_DTD_START_ADDR                             (0x36)
#define EDID_DTD_BYTE_LENGTH                            (18)
#define EDID_DTD_TOTAL_LENGTH                           (EDID_DTD_BYTE_LENGTH*4)

#define EDID_DTD_PIXELCLOCK_POS1                        (0)
#define EDID_DTD_PIXELCLOCK_POS2                        (1)

#define EDID_DTD_HBLANK_POS1                            (3)
#define EDID_DTD_HBLANK_POS2                            (4)
#define EDID_DTD_HBLANK_POS2_MASK                       (0xF)

#define EDID_DTD_HACTIVE_POS1                           (2)
#define EDID_DTD_HACTIVE_POS2                           (4)
#define EDID_DTD_HACTIVE_POS2_MASK                      (0xF0)

#define EDID_DTD_VBLANK_POS1                            (6)
#define EDID_DTD_VBLANK_POS2                            (7)
#define EDID_DTD_VBLANK_POS2_MASK                       (0x0F)

#define EDID_DTD_VACTIVE_POS1                           (5)
#define EDID_DTD_VACTIVE_POS2                           (7)
#define EDID_DTD_VACTIVE_POS2_MASK                      (0xF0)

#define EDID_DTD_INTERLACE_POS                          (17)
#define EDID_DTD_INTERLACE_MASK                         (1<<7)

// for SVD
#define EDID_SVD_VIC_MASK                               (0x7F)


// for CS
#define EDID_COLOR_SPACE_POS                            (3)
#define EDID_YCBCR444_CS_MASK                           (1<<5)
#define EDID_YCBCR422_CS_MASK                           (1<<4)

// for Color Depth
#define EDID_DC_48_VAL                                  (1<<6)
#define EDID_DC_36_VAL                                  (1<<5)
#define EDID_DC_30_VAL                                  (1<<4)
#define EDID_DC_YCBCR_VAL                               (1<<3)

#define EDID_DC_POS                                     (6)
#define EDID_DC_MASK                                    (EDID_DC_48_VAL | EDID_DC_36_VAL| EDID_DC_30_VAL | EDID_DC_YCBCR_VAL)

// for colorimetry
#define EDID_XVYCC601_MASK                              (1<<0)
#define EDID_XVYCC709_MASK                              (1<<1)
#define EDID_EXTENDED_MASK                              (1<<0|1<<1|1<<2)


// for SAD
#define SHORT_AUD_DESCRIPTOR_LPCM                       (1<<0)
#define SHORT_AUD_DESCRIPTOR_AC3                        (1<<1)
#define SHORT_AUD_DESCRIPTOR_MPEG1                      (1<<2)
#define SHORT_AUD_DESCRIPTOR_MP3                        (1<<3)
#define SHORT_AUD_DESCRIPTOR_MPEG2                      (1<<4)
#define SHORT_AUD_DESCRIPTOR_AAC                        (1<<5)
#define SHORT_AUD_DESCRIPTOR_DTS                        (1<<6)
#define SHORT_AUD_DESCRIPTOR_ATRAC                      (1<<7)

#define EDID_SAD_CODE_MASK                              (1<<6 | 1<<5 | 1<<4 | 1<<3)
#define EDID_SAD_CHANNEL_MASK                           (1<<2 | 1<<1 | 1<<0)
#define EDID_SAD_192KHZ_MASK                            (1<<6)
#define EDID_SAD_176KHZ_MASK                            (1<<5)
#define EDID_SAD_96KHZ_MASK                             (1<<4)
#define EDID_SAD_88KHZ_MASK                             (1<<3)
#define EDID_SAD_48KHZ_MASK                             (1<<2)
#define EDID_SAD_44KHZ_MASK                             (1<<1)
#define EDID_SAD_32KHZ_MASK                             (1<<0)

#define EDID_SAD_WORD_24_MASK                           (1<<2)
#define EDID_SAD_WORD_20_MASK                           (1<<1)
#define EDID_SAD_WORD_16_MASK                           (1<<0)

// for CEC
#define EDID_CEC_PHYICAL_ADDR                           (4)
//@}

//TODO: check release msg zone
#define EDID_ZONE   1

#define _DYNAMIC_MEMORY_FLAG	1

#if _DYNAMIC_MEMORY_FLAG
static PBYTE gpEdidData = NULL;
#else
static BYTE gpEdidData[SIZEOFEDIDBLOCK*4] = {0x00,0x00,0x00,};
#endif

/**
 * @var gExtensions
 * Number of EDID extensions
 */
static DWORD gExtensions;

//! Structure for parsing video timing parameter in EDID
static const struct edid_params
{
    /** H Blank */
    DWORD vHBlank;
    /** V Blank */
    DWORD vVBlank;
    /**
     * H Total + V Total @n
     * For more information, refer HDMI register map
     */
    DWORD vHVLine;
    /** CEA VIC */
    BYTE  VIC;
    /** CEA VIC for 16:9 aspect ratio */
    BYTE  VIC16_9;
    /** 0 if progressive, 1 if interlaced */
    BYTE  interlaced;
    /** Pixel frequency */
    enum PixelFreq PixelClock;
} aVideoParams[] =
{
    { 0xA0 , 0x16A0D, 0x32020D,  1 , 1 , 0, PIXEL_FREQ_25_200,  },  // v640x480p_60Hz
    { 0x8A , 0x16A0D, 0x35A20D,  2 , 3 , 0, PIXEL_FREQ_27_027,  },  // v720x480p_60Hz
    { 0x172, 0xF2EE , 0x6722EE,  4 , 4 , 0, PIXEL_FREQ_74_250,  },  // v1280x720p_60Hz
    { 0x118, 0xB232 , 0x898465,  5 , 5 , 1, PIXEL_FREQ_74_250,  },  // v1920x1080i_60Hz
    { 0x114, 0xB106 , 0x6B420D,  6 , 7 , 1, PIXEL_FREQ_27_027,  },  // v720x480i_60Hz
    { 0x114, 0xB106 , 0x6B4106,  8 , 9 , 0, PIXEL_FREQ_27_027,  },  // v720x240p_60Hz
    { 0x228, 0xB106 , 0xD6820D,  10, 11, 1, PIXEL_FREQ_54_054,  },  // v2880x480i_60Hz
    { 0x228, 0xB106 , 0x6B4106,  12, 13, 0, PIXEL_FREQ_54_054,  },  // v2880x240p_60Hz
    { 0x114, 0x16A0D, 0x6B420D,  14, 15, 0, PIXEL_FREQ_54_054,  },  // v1440x480p_60Hz
    { 0x118, 0x16C65, 0x898465,  16, 16, 0, PIXEL_FREQ_148_500, },  // v1920x1080p_60Hz
    { 0x90 , 0x18A71, 0x360271,  17, 18, 0, PIXEL_FREQ_27,      },  // v720x576p_50Hz
    { 0x2BC, 0xF2EE , 0x7BC2EE,  19, 19, 0, PIXEL_FREQ_74_250,  },  // v1280x720p_50Hz
    { 0x2D0, 0xB232 , 0xA50465,  20, 20, 1, PIXEL_FREQ_74_250,  },  // v1920x1080i_50Hz
    { 0x120, 0xC138 , 0x6C0271,  21, 22, 1, PIXEL_FREQ_27,      },  // v720x576i_50Hz
    { 0x120, 0xC138 , 0x6C0138,  23, 24, 0, PIXEL_FREQ_27,      },  // v720x288p_50Hz
    { 0x240, 0xC138 , 0xD80271,  25, 26, 1, PIXEL_FREQ_54,      },  // v2880x576i_50Hz
    { 0x240, 0xC138 , 0xD80138,  27, 28, 0, PIXEL_FREQ_54,      },  // v2880x288p_50Hz
    { 0x120, 0x18A71, 0x6C0271,  29, 30, 0, PIXEL_FREQ_54,      },  // v1440x576p_50Hz
    { 0x2D0, 0x16C65, 0xA50465,  31, 31, 0, PIXEL_FREQ_148_500, },  // v1920x1080p_50Hz
    { 0x33E, 0x16C65, 0xABE465,  32, 32, 0, PIXEL_FREQ_74_250,  },  // v1920x1080p_24Hz
    { 0x2D0, 0x16C65, 0xA50465,  33, 33, 0, PIXEL_FREQ_74_250,  },  // v1920x1080p_25Hz
    { 0x2D0, 0x16C65, 0xA50465,  34, 34, 0, PIXEL_FREQ_74_250,  },  // v1920x1080p_30Hz
    { 0x228, 0x16A0D, 0xD6820D,  35, 36, 0, PIXEL_FREQ_108_108, },  // v2880x480p_60Hz
    { 0x240, 0x18A71, 0xD80271,  37, 38, 0, PIXEL_FREQ_108,     },  // v2880x576p_50Hz
    { 0x180, 0x2AA71, 0x9004E2,  39, 39, 0, PIXEL_FREQ_72,      },  // v1920x1080i_50Hz(1250)
    { 0x2D0, 0xB232 , 0xA50465,  40, 40, 1, PIXEL_FREQ_148_500, },  // v1920x1080i_100Hz
    { 0x2BC, 0xF2EE , 0x7BC2EE,  41, 41, 0, PIXEL_FREQ_148_500, },  // v1280x720p_100Hz
    { 0x90 , 0x18A71, 0x360271,  42, 43, 0, PIXEL_FREQ_54,      },  // v720x576p_100Hz
    { 0x120, 0xC138 , 0x6C0271,  44, 45, 1, PIXEL_FREQ_54,      },  // v720x576i_100Hz
    { 0x118, 0xB232 , 0x898465,  46, 46, 1, PIXEL_FREQ_148_500, },  // v1920x1080i_120Hz
    { 0x172, 0xF2EE , 0x6722EE,  47, 47, 0, PIXEL_FREQ_148_500, },  // v1280x720p_120Hz
    { 0x8A , 0x16A0D, 0x35A20D,  48, 49, 0, PIXEL_FREQ_54_054,  },  // v720x480p_120Hz
    { 0x114, 0xB106 , 0x6B420D,  50, 51, 1, PIXEL_FREQ_54_054,  },  // v720x480i_120Hz
    { 0x90 , 0x18A71, 0x360271,  52, 53, 0, PIXEL_FREQ_108,     },  // v720x576p_200Hz
    { 0x120, 0xC138 , 0x6C0271,  54, 55, 1, PIXEL_FREQ_108,     },  // v720x576i_200Hz
    { 0x8A , 0x16A0D, 0x35A20D,  56, 57, 0, PIXEL_FREQ_108_108, },  // v720x480p_240Hz
    { 0x114, 0xB106 , 0x6B420D,  58, 59, 1, PIXEL_FREQ_108_108, },  // v720x480i_240Hz
};


static BOOL ReadEDIDBlock(DWORD blockNum, PBYTE outBuffer);
static BOOL EDIDValid(VOID);
static BOOL IsTimingExtension(DWORD extension);
static DWORD GetVSDBOffset(DWORD extension);
static BOOL IsContainVideoDTD(DWORD extension,VIDEO_FORMAT videoFormat);
static BOOL IsContainVIC(DWORD extension, DWORD VIC);
static BOOL CalcChecksum(PBYTE pBuffer, DWORD size);

// Initialize EDID library. Initialize DDC library
BOOL EDIDInit(VOID)
{
    DWORD dwErr = 0;

    RETAILMSG(EDID_ZONE,(_T("[EDID: %s] \n\r"),TEXT(__FUNCTION__)));
    
    if(EDIDValid())
    {
        RETAILMSG(EDID_ZONE,(_T("[EDID: %s] EDID is already opened!!!\n\r"),TEXT(__FUNCTION__)));
        return FALSE;
    }

    // init DDC library
    if ( !DDCInit() )
    {
        dwErr = GetLastError();
        RETAILMSG(EDID_ZONE,(_T("[EDID: %s] Fail to initialize DDC library : 0x%x\n\r"), TEXT(__FUNCTION__), dwErr));
        return FALSE;
    }

    return TRUE;
}

// Deinitialize EDID library. Deinitialize DDC library
BOOL EDIDDeInit(VOID)
{
	RETAILMSG(EDID_ZONE,(_T("[EDID: %s] \n\r"),TEXT(__FUNCTION__)));
	
    // reset EDID
    EDIDReset();

    // finialize DDC lib
    if( !DDCDeInit() )
    {
        RETAILMSG(EDID_ZONE,(_T("[EDID: %s] Fail to finalize DDC library\n\r"), TEXT(__FUNCTION__)));
        return FALSE;
    }

    return TRUE;    
}

// Read EDID data
BOOL EDIDRead(VOID)
{
    DWORD block,dataPtr;

#if _DYNAMIC_MEMORY_FLAG    
    BYTE temp[SIZEOFEDIDBLOCK];
#endif    

    // if already read??
    if (EDIDValid())
        return TRUE;

#if _DYNAMIC_MEMORY_FLAG
    // read EDID Extension Number
    // read EDID
    if (!ReadEDIDBlock(0,temp))
    {
        return FALSE;
    }
    
    // get extension
    gExtensions = (DWORD)temp[EDID_EXTENSION_NUMBER_POS];

    // prepare buffer
    gpEdidData = LocalAlloc(0,(gExtensions+1)*SIZEOFEDIDBLOCK);
    if (gpEdidData == NULL)
    {
        RETAILMSG(EDID_ZONE,(_T("[EDID: %s] fail to allocate memory for EDID!!! \n\r"),TEXT(__FUNCTION__)));
        return FALSE;
    }

    // copy EDID Block 0
    CopyMemory(gpEdidData,temp,SIZEOFEDIDBLOCK);
#else
// read EDID Extension Number
    // read EDID
    if (!ReadEDIDBlock(0,(PBYTE)gpEdidData))
    {
        return FALSE;
    }
    
    // get extension
    gExtensions = (DWORD)gpEdidData[EDID_EXTENSION_NUMBER_POS];

#endif 

#if 0    
    // read EDID Extension
    for ( block = 1,dataPtr = SIZEOFEDIDBLOCK; block <= gExtensions; block++,dataPtr+=SIZEOFEDIDBLOCK )
    {
        // read extension 1~gExtensions
        if (!ReadEDIDBlock(block, (PBYTE)(gpEdidData+dataPtr)))
        {
            // reset buffer
            EDIDReset();
            return FALSE;
        }
    }
#else
	// read EDID Extension
    for ( block = 1,dataPtr = SIZEOFEDIDBLOCK; block <= gExtensions; block++,dataPtr+=SIZEOFEDIDBLOCK )
    {
        // read extension 1~gExtensions
        if (!ReadEDIDBlock(block, temp))
        {
            // reset buffer
            EDIDReset();
            return FALSE;
        }
        else
        {
        	// copy memory
        	CopyMemory(gpEdidData+dataPtr,temp,SIZEOFEDIDBLOCK);
		}
    }
#endif
    // check if extension is more than 1, and first extension block is not block map.
    if (gExtensions > 1 && gpEdidData[SIZEOFEDIDBLOCK] != EDID_BLOCK_MAP_EXT_TAG_VAL)
    {
        // reset buffer
        RETAILMSG(EDID_ZONE,(_T("[EDID: %s]EDID has more than 1 extension but, first extension block is not block map\n"),TEXT(__FUNCTION__)));
        EDIDReset();
        return FALSE;
    }

    return TRUE;
}

// Reset EDID data
VOID EDIDReset(VOID)
{
#if _DYNAMIC_MEMORY_FLAG	
    if (gpEdidData)
    {
        LocalFree(gpEdidData);
        gpEdidData = NULL;
    }
#else
	gpEdidData[1] = 0x00;
	gpEdidData[2] = 0x00;
#endif    
}

/**
 * Check if EDID supports the HDMI mode.
 * @return  If EDID supports HDMI mode, return TRUE;Otherwise, return FALSE.
 */
BOOL EDIDHDMIModeSupport(VOID)
{
    DWORD index;

    // read EDID
    if(!EDIDRead())
    {
        return FALSE;
    }

    // find VSDB
    for ( index = 1 ; index <= gExtensions ; index++ )
    {
        if ( IsTimingExtension(index) ) // if it's timing extension block
        {
            if (GetVSDBOffset(index) > 0) // if there is a VSDB, it means RX support HDMI mode
                return TRUE;
        }
    }
    return FALSE;
}

/**
 * Check if EDID contains the video format.
 * @param   videoMode [in]    Video Mode to check
 * @param   pixelRatio  [in]    Pixel aspect ratio of video mode to check
 * @return  if EDID contains the video format, return TRUE;Otherwise, return FALSE.
 */
BOOL EDIDVideoResolutionSupport(VIDEO_FORMAT videoMode, 
                                    PIXEL_ASPECT_RATIO pixelRatio)
{
    DWORD index,vic;

    // read EDID
    if(!EDIDRead())
    {
        return 0;
    }

    // check ET(Established Timings) for 640x480p@60Hz
    if ( videoMode == v640x480p_60Hz // if it's 640x480p@60Hz
        && (gpEdidData[EDID_ET_POS] & EDID_ET_640x480p_VAL) ) // it support
    {
         return TRUE;
    }

    // check STI(Standard Timing Identification)
    // do not need

    // check DTD(Detailed Timing Description) of EDID block(0th)
    if (IsContainVideoDTD(0,videoMode))
    {
        return TRUE;
    }

    // check EDID Extension
    vic = (pixelRatio == HDMI_PIXEL_RATIO_16_9) ? aVideoParams[videoMode].VIC16_9 : aVideoParams[videoMode].VIC;

    // find VSDB
    for ( index = 1 ; index <= gExtensions ; index++ )
    {
        if ( IsTimingExtension(index) ) // if it's timing block
        {
            if (IsContainVIC(index,vic) || IsContainVideoDTD(index,videoMode))
                return TRUE;
        }
    }

    return FALSE;
}

/**
 * Check if EDID supports the color depth.
 * @param   colorDepth [in]    Color depth
 * @param   ColorSpace [in]    Color space
 * @return  If EDID supports the color depth, return TRUE;Otherwise, return FALSE.
 */
BOOL EDIDColorDepthSupport(COLOR_DEPTH colorDepth, COLOR_SPACE colorSpace)
{
    DWORD index;
    DWORD StartAddr;

	// mandatory
	if (colorDepth == HDMI_CD_24)
		return TRUE;
	
	
    // check EDID data is valid or not
    // read EDID
    if(!EDIDRead())
    {
        return FALSE;
    }

    // find VSDB
    for ( index = 1 ; index <= gExtensions ; index++ )
    {
        if ( IsTimingExtension(index) // if it's timing block
            && ((StartAddr = GetVSDBOffset(index)) > 0) )   // check block
        {
            // get supported DC value
            DWORD deepColor = gpEdidData[StartAddr + EDID_DC_POS] & EDID_DC_MASK;
            RETAILMSG(EDID_ZONE,(_T("[EDID]EDID deepColor = %x\n"),deepColor));
            // check supported DeepColor

            // if YCBCR444
            if (colorSpace == HDMI_CS_YCBCR444)
            {
                if ( !(deepColor & EDID_DC_YCBCR_VAL))
                    return FALSE;
            }

            // check colorDepth
            switch (colorDepth)
            {
                case HDMI_CD_36:
                {
                    deepColor &= EDID_DC_36_VAL;
                    break;
                }
                case HDMI_CD_30:
                {
                    deepColor &= EDID_DC_30_VAL;
                    break;
                }
//Remove DEAD_CODE
/*				
                case HDMI_CD_24:
                {
                    deepColor = 1;
                    break;
                }
*/                
                default :
                    deepColor = 0;
            } // switch

            if (deepColor)
                return TRUE;
            else
                return FALSE;
        } // if
    } // for

    return FALSE;
}

/**
 * Check if EDID supports the color space.
 * @param   colorSpace [in]    Color space
 * @return  If EDID supports the color space, return TRUE;Otherwise, return FALSE.
 */
BOOL EDIDColorSpaceSupport(COLOR_SPACE colorSpace)
{
    DWORD index;

    // RGB is default
    if (colorSpace == HDMI_CS_RGB)
        return TRUE;

    // check EDID data is valid or not
    // read EDID
    if(!EDIDRead())
    {
        return FALSE;
    }

    // find VSDB
    for ( index = 1 ; index <= gExtensions ; index++ )
    {
        if (IsTimingExtension(index))  // if it's timing block
        {
            // read Color Space
            DWORD CS = gpEdidData[index*SIZEOFEDIDBLOCK + EDID_COLOR_SPACE_POS];

            if ( (colorSpace == HDMI_CS_YCBCR444 && (CS & EDID_YCBCR444_CS_MASK)) || // YCBCR444
                    (colorSpace == HDMI_CS_YCBCR422 && (CS & EDID_YCBCR422_CS_MASK)) ) // YCBCR422
            {
                return TRUE;
            }
        } // if
    } // for
    return FALSE;
}

/**
 * Check if EDID supports the colorimetry.
 * @param   colorimetry [in]    Colorimetry
 * @return  If EDID supports the colorimetry, return TRUE;Otherwise, return FALSE.
 */
BOOL EDIDColorimetrySupport(HDMI_COLORMETRY colorimetry)
{
    DWORD index;

    // do not need to parse if not extended colorimetry
    if (colorimetry == HDMI_COLORIMETRY_NO_DATA || 
            colorimetry == HDMI_COLORIMETRY_ITU601 || 
            colorimetry == HDMI_COLORIMETRY_ITU709)
        return TRUE;

    // read EDID
    if(!EDIDRead())
    {
       return FALSE;
    }

    // find VSDB
    for ( index = 1 ; index <= gExtensions ; index++ )
    {
        if ( IsTimingExtension(index) ) // if it's timing block
        {
            // check address
            DWORD ExtAddr = index*SIZEOFEDIDBLOCK + EDID_DATA_BLOCK_START_POS;
            DWORD EndAddr = index*SIZEOFEDIDBLOCK + gpEdidData[index*SIZEOFEDIDBLOCK + EDID_DETAILED_TIMING_OFFSET_POS];
            DWORD tag,blockLen;

            // while
            while ( ExtAddr < EndAddr )
            {
                // find the block tag and length
                // tag
                tag = gpEdidData[ExtAddr] & EDID_TAG_CODE_MASK;
                // block len
                blockLen = (gpEdidData[ExtAddr] & EDID_DATA_BLOCK_SIZE_MASK) + 1;

                // check if it is colorimetry block
                if (tag == EDID_EXTENDED_TAG_VAL && // extended tag
                    gpEdidData[ExtAddr+1] == EDID_EXTENDED_COLORIMETRY_VAL && // colorimetry block
                    (blockLen-1) == EDID_EXTENDED_COLORIMETRY_BLOCK_LEN )// check length
                {
                    // get supported DC value
                    DWORD colorimetry = (gpEdidData[ExtAddr + 2]);
                    DWORD metadata = (gpEdidData[ExtAddr + 3]);

                    RETAILMSG(EDID_ZONE,(_T("[EDID]EDID extened colorimetry = %x\n"),colorimetry));
                    RETAILMSG(EDID_ZONE,(_T("[EDID]EDID gamut metadata profile = %x\n"),metadata));

                    // check colorDepth
                    switch (colorimetry)
                    {
                        case HDMI_COLORIMETRY_EXTENDED_xvYCC601:
                            if ((colorimetry & EDID_XVYCC601_MASK) && metadata)
                                return TRUE;
                            break;
                        case HDMI_COLORIMETRY_EXTENDED_xvYCC709:
                            if ((colorimetry & EDID_XVYCC709_MASK) && metadata)
                                return TRUE;
                            break;
                        default:
                            break;
                    }
                    return FALSE;
                } // if VSDB block
                // else find next block
                ExtAddr += blockLen;
            } // while()
        } // if
    } // for

    return FALSE;
}

/**
 * Check if Rx supports requested audio parameters or not.
 * @param   audio [in]   Audio parameters to check
 * @return  If Rx supports audio parameters, return TRUE;Otherwise, return FALSE.
 */
BOOL EDIDAudioModeSupport(HDMI_AUDIO_PARAMETER* pAudio)
{
    DWORD index;

    // read EDID
    if(!EDIDRead())
    {
        return FALSE;
    }

    // check EDID Extension

    // find timing block
    for ( index = 1 ; index <= gExtensions ; index++ )
    {
        if ( IsTimingExtension(index) ) // if it's timing block
        {
            // find Short Audio Description
            DWORD StartAddr = index*SIZEOFEDIDBLOCK;
            DWORD ExtAddr = StartAddr + EDID_DATA_BLOCK_START_POS;
            DWORD tag,blockLen;
            DWORD DTDStartAddr = gpEdidData[StartAddr + EDID_DETAILED_TIMING_OFFSET_POS];

            // while
            while ( ExtAddr < StartAddr + DTDStartAddr )
            {
                // find the block tag and length
                // tag
                tag = gpEdidData[ExtAddr] & EDID_TAG_CODE_MASK;
                // block len
                blockLen = (gpEdidData[ExtAddr] & EDID_DATA_BLOCK_SIZE_MASK) + 1;

                RETAILMSG(EDID_ZONE,(_T("[EDID]tag = %d\n"),tag));
                RETAILMSG(EDID_ZONE,(_T("[EDID]blockLen = %d\n"),blockLen-1));

                // check if it is short video description
                if (tag == EDID_SHORT_AUD_DEC_TAG_VAL)
                {
                    // if so, check SAD
                    DWORD i;
                    DWORD audioFormat,channelNum,sampleFreq,wordLen;
                    for (i=1; i < blockLen; i+=3)
                    {
                        audioFormat = gpEdidData[ExtAddr+i] & EDID_SAD_CODE_MASK;
                        channelNum = gpEdidData[ExtAddr+i] & EDID_SAD_CHANNEL_MASK;
                        sampleFreq = gpEdidData[ExtAddr+i+1];
                        wordLen = gpEdidData[ExtAddr+i+2];

                        RETAILMSG(EDID_ZONE,(_T("[EDID]EDIDAudioFormatCode = %d\n"),audioFormat));
                        RETAILMSG(EDID_ZONE,(_T("[EDID]EDIDChannelNumber= %d\n"),channelNum));
                        RETAILMSG(EDID_ZONE,(_T("[EDID]EDIDSampleFreq= %d\n"),sampleFreq));
                        RETAILMSG(EDID_ZONE,(_T("[EDID]EDIDWordLeng= %d\n"),wordLen));

                        // check parameter
                        // check audioFormat
                        if ( audioFormat == ((pAudio->formatCode) << 3)     &&  // format code
                                channelNum >= (DWORD)((pAudio->channelNum)-1)         &&  // channel number
                                (sampleFreq & (1<<(pAudio->sampleFreq)))      ) // sample frequency
                        {
                            if (audioFormat == LPCM_FORMAT) // check wordLen
                            {
                                if ( wordLen & (1<<pAudio->wordLength) )
                                    return TRUE;
                                else
                                    return FALSE;
                            }
                            return TRUE; // if not LPCM
                        }
                    } // for
                } // if tag
                // else find next block
                ExtAddr += blockLen;
            } // while()
        } // if
    } // for

    return FALSE;
}

/**
 * Get CEC physical address.
 * @param   outAddr [out]   CEC physical address. LSB 2 bytes is available. [0:0:AB:CD]
 * @return  If success, return 1;Otherwise, return 0.
 */
BOOL EDIDGetCECPhysicalAddress(PDWORD outAddr)
{
    DWORD index;
    DWORD StartAddr;

    // check EDID data is valid or not
    // read EDID
    if(!EDIDRead())
    {
        return FALSE;
    }

    // find VSDB
    for ( index = 1 ; index <= gExtensions ; index++ )
    {
        if ( IsTimingExtension(index) // if it's timing block
            && (StartAddr = GetVSDBOffset(index)) > 0 )   // check block
        {
            // get supported DC value
            // int tempDC1 = (int)(gpEdidData[tempAddr+EDID_DC_POS]);
            DWORD phyAddr = gpEdidData[StartAddr + EDID_CEC_PHYICAL_ADDR] << 8;
            phyAddr |= gpEdidData[StartAddr + EDID_CEC_PHYICAL_ADDR+1];

            RETAILMSG(EDID_ZONE,(_T("[EDID]phyAddr = %x\n"),phyAddr));

            *outAddr = phyAddr;

            return TRUE;
        } // if
    } // for

    return FALSE;    
}

/**
 * Read EDID Block(128 bytes)
 * @param   blockNum    [in]    Number of block to read
 * @param   outBuffer   [out]   Pointer to buffer to store EDID data
 * @return  If fail to read, return FALSE;Otherwise, return TRUE
 */
BOOL ReadEDIDBlock(DWORD blockNum, PBYTE outBuffer)
{
    DWORD segNum, offset, dataPtr;
    DWORD i = 0;
    DWORD dwErr = 0;
    DWORD dwbytes = 0;
    DWORD dwOffsetAddr = 0;
    
    if (outBuffer == NULL)
    {
        RETAILMSG(EDID_ZONE,(_T("[EDID: %s] Invalid arguement\n\r"), TEXT(__FUNCTION__)));
        return FALSE;
    }

    // calculate
    segNum = blockNum / 2;
    offset = (blockNum % 2) * SIZEOFEDIDBLOCK;
    dataPtr = (blockNum) * SIZEOFEDIDBLOCK;

    RETAILMSG(EDID_ZONE,(_T("[EDID: %s] segnum : %d, addr: %d, offset: %d, size: %d, buffer = 0x%04X \n\r"), TEXT(__FUNCTION__), segNum, EDID_ADDR, offset, SIZEOFEDIDBLOCK, outBuffer));

    // read EDID
    if (!EDDCReadEDID((BYTE)segNum,EDID_ADDR,offset,SIZEOFEDIDBLOCK,outBuffer))
    {
        RETAILMSG(EDID_ZONE,(_T("[EDID: %s] Fail to read  : %dth EDID Block\n\r"), TEXT(__FUNCTION__), blockNum));
        return FALSE;
    }
    
    
    if (!CalcChecksum(outBuffer, SIZEOFEDIDBLOCK))
    {
        RETAILMSG(EDID_ZONE,(_T("[EDID: %s] CheckSum fail : %dth EDID Block\n"), TEXT(__FUNCTION__), blockNum));
        return FALSE;
    }

// print data
#if 0
    offset = 0;

	RETAILMSG(1,(_T("==============================================================================\r\n")));

    do
    {
        RETAILMSG(EDID_ZONE,(_T("0x%02X"), outBuffer[offset++]));
        if (offset%16)
            RETAILMSG(EDID_ZONE,(_T(" ")));
        else
            RETAILMSG(EDID_ZONE,(_T("\n\r")));
    }
    while (SIZEOFEDIDBLOCK > offset);

	RETAILMSG(1,(_T("==============================================================================\r\n")));

#endif
    return TRUE;
}


/**
 * Check if EDID data is valid or not.
 * @return if EDID data is valid, return FALSE;otherwise, return TRUE
 */
BOOL EDIDValid(void)
{
#if _DYNAMIC_MEMORY_FLAG
    return (gpEdidData == NULL) ?  FALSE : TRUE;
#else
	if (gpEdidData[0] == 0x00 && gpEdidData[1] == 0xFF && gpEdidData[2] == 0xFF )
		return TRUE;
	else
		return FALSE;
#endif    
}

/**
 * Check if EDID extension block is timing extension block or not.
 * @param   extension   [in] The number of EDID extension block to check
 * @return  If the block is timing extension, return TRUE;Otherwise, return FALSE
 */
BOOL IsTimingExtension(DWORD extension)
{
    if (gpEdidData[extension*SIZEOFEDIDBLOCK] == EDID_TIMING_EXT_TAG_VAL
      && gpEdidData[extension*SIZEOFEDIDBLOCK + EDID_TIMING_EXT_REV_NUMBER_POS] >= 3)
        return TRUE;
    return FALSE;
}

/**
 * Search Vender Specific Data Block(VSDB) in EDID extension block.
 * @param   extension   [in] the number of EDID extension block to check
 * @return  if there is a VSDB, return the its offset from start of @n
 *          EDID extension block. if there is no VSDB, return 0.
 */
DWORD GetVSDBOffset(DWORD extension)
{
    DWORD BlockOffset = extension*SIZEOFEDIDBLOCK;
    DWORD offset = BlockOffset + EDID_DATA_BLOCK_START_POS;
    DWORD tag,blockLen;
    DWORD DTDOffset = (DWORD)gpEdidData[BlockOffset + EDID_DETAILED_TIMING_OFFSET_POS];

    // check if there is HDMI VSDB
    while ( offset < BlockOffset + DTDOffset )
    {
        // find the block tag and length
        // tag
        tag = (DWORD)(gpEdidData[offset] & EDID_TAG_CODE_MASK);
        // block len
        blockLen = (DWORD)(gpEdidData[offset] & EDID_DATA_BLOCK_SIZE_MASK) + 1;

        // check if it is HDMI VSDB
        // if so, check identifier value, if it's hdmi vsbd - return offset
        if (tag == EDID_VSDB_TAG_VAL &&
            gpEdidData[offset+1] == 0x03 &&
            gpEdidData[offset+2] == 0x0C &&
            gpEdidData[offset+3] == 0x0 &&
            blockLen > EDID_VSDB_MIN_LENGTH_VAL )
        {
            return offset;
        }
        // else find next block
        offset += blockLen;
    } // while()

    // return error
    return 0;
}

/**
 * Check if the video format is contained in - @n
 * Detailed Timing Descriptor(DTD) of EDID extension block.
 * @param   extension   [in]    Number of EDID extension block to check
 * @param   videoFormat [in]    Video format to check
 * @return  If the video format is contained in DTD of EDID extension block, -@n
 *          return TRUE;Otherwise, return FALSE.
 */
BOOL IsContainVideoDTD(DWORD extension,VIDEO_FORMAT videoFormat)
{
    DWORD i,StartAddr,EndAddr;

    // if edid block
    if (extension == 0)
    {
        StartAddr = EDID_DTD_START_ADDR;
        EndAddr = StartAddr + EDID_DTD_TOTAL_LENGTH;
    }
    else // if edid extension block
    {
        StartAddr = (DWORD)gpEdidData[extension*SIZEOFEDIDBLOCK + EDID_DETAILED_TIMING_OFFSET_POS];
        EndAddr = (DWORD)gpEdidData[(extension+1)*SIZEOFEDIDBLOCK];
    }

    // check DTD(Detailed Timing Description)
    for (i = StartAddr; i < EndAddr; i+= EDID_DTD_BYTE_LENGTH)
    {
        DWORD hblank = 0, hactive = 0, vblank = 0, vactive = 0, interlaced = 0, pixelclock = 0;
        DWORD vHActive = 0, vVActive = 0;

        // get pixel clock
        pixelclock = (DWORD)(gpEdidData[i+EDID_DTD_PIXELCLOCK_POS2] << SIZEOFBYTE);
        pixelclock |= (DWORD)gpEdidData[i+EDID_DTD_PIXELCLOCK_POS1];

        if (!pixelclock)
        {
            continue;
        }

        // get HBLANK value in pixels
        hblank = (DWORD)gpEdidData[i+EDID_DTD_HBLANK_POS2] & EDID_DTD_HBLANK_POS2_MASK;
        hblank <<= SIZEOFBYTE; // lower 4 bits
        hblank |= (DWORD)gpEdidData[i+EDID_DTD_HBLANK_POS1];

        // get HACTIVE value in pixels
        hactive = (DWORD)gpEdidData[i+EDID_DTD_HACTIVE_POS2] & EDID_DTD_HACTIVE_POS2_MASK;
        hactive <<= (SIZEOFBYTE/2); // upper 4 bits
        hactive |= (DWORD)gpEdidData[i+EDID_DTD_HACTIVE_POS1];

        // get VBLANK value in pixels
        vblank = (DWORD)gpEdidData[i+EDID_DTD_VBLANK_POS2] & EDID_DTD_VBLANK_POS2_MASK;
        vblank <<= SIZEOFBYTE; // lower 4 bits
        vblank |= (DWORD)gpEdidData[i+EDID_DTD_VBLANK_POS1];

        // get VACTIVE value in pixels
        vactive = (DWORD)gpEdidData[i+EDID_DTD_VACTIVE_POS2] & EDID_DTD_VACTIVE_POS2_MASK;
        vactive <<= (SIZEOFBYTE/2); // upper 4 bits
        vactive |= (DWORD)gpEdidData[i+EDID_DTD_VACTIVE_POS1];

        vHActive = (aVideoParams[videoFormat].vHVLine & 0xFFF) - aVideoParams[videoFormat].vHBlank;
        vVActive = (aVideoParams[videoFormat].vHVLine & 0xFFF000) >> 12;
        vVActive -= aVideoParams[videoFormat].vVBlank;


        // get Interlaced Mode Value
        interlaced = (DWORD)(gpEdidData[i+EDID_DTD_INTERLACE_POS] & EDID_DTD_INTERLACE_MASK);
        if (interlaced) interlaced = 1;

        if (hblank == aVideoParams[videoFormat].vHBlank && vblank == aVideoParams[videoFormat].vHBlank // blank
            && hactive == vHActive && vactive == vVActive ) //line
        {
            DWORD EDIDpixelclock = aVideoParams[videoFormat].PixelClock;
            EDIDpixelclock /= 100; pixelclock /= 100;

            if (pixelclock == EDIDpixelclock)
            {
                RETAILMSG(EDID_ZONE,(_T("[EDID: %s]Sink Support the Video mode\n"), TEXT(__FUNCTION__)));
                return TRUE;
            }
        }
    } // for
    return FALSE;
}

/**
 * Check if a VIC(Video Identification Code) is contained in -@n
 * EDID extension block.
 * @param   extension   [in]    Number of EDID extension block to check
 * @param   VIC         [in]    VIC to check
 * @return  If the VIC is contained in contained in EDID extension block, -@n
 *          return TRUE;otherwise, Return FALSE.
 */
BOOL IsContainVIC(DWORD extension, DWORD VIC)
{
    DWORD StartAddr = extension*SIZEOFEDIDBLOCK;
    DWORD ExtAddr = StartAddr + EDID_DATA_BLOCK_START_POS;
    DWORD tag,blockLen;
    DWORD DTDStartAddr = gpEdidData[StartAddr + EDID_DETAILED_TIMING_OFFSET_POS];

    // while
    while ( ExtAddr < StartAddr + DTDStartAddr )
    {
        // find the block tag and length
        // tag
        tag = (DWORD)(gpEdidData[ExtAddr] & EDID_TAG_CODE_MASK);
        // block len
        blockLen = (DWORD)(gpEdidData[ExtAddr] & EDID_DATA_BLOCK_SIZE_MASK) + 1;
        RETAILMSG(EDID_ZONE,(_T("[EDID]tag = %d\n"),tag));
        RETAILMSG(EDID_ZONE,(_T("[EDID]blockLen = %d\n"),blockLen-1));

        // check if it is short video description
        if (tag == EDID_SHORT_VID_DEC_TAG_VAL)
        {
            // if so, check SVD
            DWORD index;
            for (index=1; index < blockLen;index++)
            {
                RETAILMSG(EDID_ZONE,(_T("[EDID]EDIDVIC = %d\n"),gpEdidData[ExtAddr+index] & EDID_SVD_VIC_MASK));
                RETAILMSG(EDID_ZONE,(_T("[EDID]VIC = %d\n"),VIC));

                // check VIC with SVDB
                if (VIC == (gpEdidData[ExtAddr+index] & EDID_SVD_VIC_MASK) )
                {
                    RETAILMSG(EDID_ZONE,(_T("[EDID: %s]Sink Device supports requested video mode\n"), TEXT(__FUNCTION__)));
                    return TRUE;
                }
            } // for
        } // if tag
        // else find next block
        ExtAddr += blockLen;
    } // while()

    return FALSE;
}

/**
 * Calculate a checksum.
 * @param   buffer  [in]    Pointer to data to calculate a checksum
 * @param   size    [in]    Sizes of data
 * @return  If checksum result is 0, return TRUE;Otherwise, return FALSE.
 */
BOOL CalcChecksum(PBYTE pBuffer, DWORD size)
{
    BYTE i,sum;

    for (sum = 0, i = 0 ; i < size; i++)
    {
        sum += pBuffer[i];
    }

    // check checksum
    if (sum != 0)
    {
        return FALSE;
    }

    return TRUE;
}