/* * Copyright (C) Texas Instruments - http://www.ti.com/ * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ #include #include #include #include #include "edid_parser_priv.h" const struct svd_info_t svd_table[] = { {0, 0, 0, HDMI_SCAN_PROGRESSIVE, "reserved"}, {640, 480, 60, HDMI_SCAN_PROGRESSIVE, "DMT0659"}, {720, 480, 60, HDMI_SCAN_PROGRESSIVE, "480p"}, {720, 480, 60, HDMI_SCAN_PROGRESSIVE, "480pH"}, {1280, 720, 60, HDMI_SCAN_PROGRESSIVE, "720p"}, {1920, 1080, 60, HDMI_SCAN_INTERLACED, "1080i"}, {720, 480, 60, HDMI_SCAN_INTERLACED, "480i"}, {720, 480, 60, HDMI_SCAN_INTERLACED, "480iH"}, {720, 240, 60, HDMI_SCAN_PROGRESSIVE, "240p"}, {1280, 720, 60, HDMI_SCAN_PROGRESSIVE, "240pH"}, {720, 480, 60, HDMI_SCAN_INTERLACED, "480i4x"}, {720, 480, 60, HDMI_SCAN_INTERLACED, "480i4xH"}, {720, 240, 60, HDMI_SCAN_PROGRESSIVE, "240p4x"}, {720, 240, 60, HDMI_SCAN_PROGRESSIVE, "240p4xH"}, {720, 480, 60, HDMI_SCAN_PROGRESSIVE, "480p2x"}, {720, 480, 60, HDMI_SCAN_PROGRESSIVE, "480p2xH"}, {1920, 1080, 60, HDMI_SCAN_PROGRESSIVE, "1080p"}, {720, 576, 50, HDMI_SCAN_PROGRESSIVE, "576p"}, {720, 576, 50, HDMI_SCAN_PROGRESSIVE, "576pH"}, {1280, 720, 50, HDMI_SCAN_PROGRESSIVE, "720p50"}, {1920, 1080, 50, HDMI_SCAN_INTERLACED, "1080i25"}, {720, 576, 50, HDMI_SCAN_INTERLACED, "576i"}, {720, 576, 50, HDMI_SCAN_INTERLACED, "576iH"}, {720, 288, 50, HDMI_SCAN_PROGRESSIVE, "288p"}, {720, 288, 50, HDMI_SCAN_PROGRESSIVE, "288pH"}, {720, 576, 50, HDMI_SCAN_INTERLACED, "576i4x"}, {720, 576, 50, HDMI_SCAN_INTERLACED, "576i4xH"}, {720, 288, 50, HDMI_SCAN_PROGRESSIVE, "288p4x"}, {720, 288, 50, HDMI_SCAN_PROGRESSIVE, "288p4xH"}, {720, 576, 50, HDMI_SCAN_PROGRESSIVE, "576p2x"}, {720, 576, 50, HDMI_SCAN_PROGRESSIVE, "576p2xH"}, {1920, 1080, 50, HDMI_SCAN_PROGRESSIVE, "1080p50"}, {1920, 1080, 24, HDMI_SCAN_PROGRESSIVE, "1080p24"}, {1920, 1080, 25, HDMI_SCAN_PROGRESSIVE, "1080p25"}, {1920, 1080, 30, HDMI_SCAN_PROGRESSIVE, "1080p30"}, {720, 480, 60, HDMI_SCAN_PROGRESSIVE, "480p4x"}, {720, 480, 60, HDMI_SCAN_PROGRESSIVE, "480p4xH"}, {720, 576, 50, HDMI_SCAN_PROGRESSIVE, "576p4x"}, {720, 576, 50, HDMI_SCAN_PROGRESSIVE, "576p4xH"}, {1920, 1080, 50, HDMI_SCAN_INTERLACED, "1080i25"}, {1920, 1080, 100, HDMI_SCAN_INTERLACED, "1080i50"}, {1280, 720, 100, HDMI_SCAN_PROGRESSIVE, "720p100"}, {720, 576, 100, HDMI_SCAN_PROGRESSIVE, "576p100"}, {720, 576, 100, HDMI_SCAN_PROGRESSIVE, "576p100H"}, {720, 576, 100, HDMI_SCAN_INTERLACED, "576i50"}, {720, 576, 100, HDMI_SCAN_INTERLACED, "576i50H"}, {1920, 1080, 120, HDMI_SCAN_INTERLACED, "1080i60"}, {1280, 720, 120, HDMI_SCAN_PROGRESSIVE, "720p120"}, {720, 480, 120, HDMI_SCAN_PROGRESSIVE, "480p120"}, {720, 480, 120, HDMI_SCAN_PROGRESSIVE, "480p120H"}, {720, 480, 120, HDMI_SCAN_INTERLACED, "480i60"}, {720, 480, 120, HDMI_SCAN_INTERLACED, "480i60H"}, {720, 576, 200, HDMI_SCAN_PROGRESSIVE, "576p200"}, {720, 576, 200, HDMI_SCAN_PROGRESSIVE, "576p200H"}, {720, 576, 200, HDMI_SCAN_INTERLACED, "576i100"}, {720, 576, 200, HDMI_SCAN_INTERLACED, "576i100H"}, {720, 480, 240, HDMI_SCAN_PROGRESSIVE, "480p240"}, {720, 480, 240, HDMI_SCAN_PROGRESSIVE, "480p240H"}, {720, 480, 240, HDMI_SCAN_INTERLACED, "480i120"}, {720, 480, 240, HDMI_SCAN_INTERLACED, "480i120H"}, {1280, 720, 24, HDMI_SCAN_PROGRESSIVE, "720p24"}, {1280, 720, 25, HDMI_SCAN_PROGRESSIVE, "720p25"}, {1280, 720, 30, HDMI_SCAN_PROGRESSIVE, "720p30"}, {1920, 1080, 120, HDMI_SCAN_PROGRESSIVE, "1080p120"} }; const int NUM_SVD_ENTRIES = sizeof(svd_table)/sizeof(svd_table[0]); static unsigned int count_set_bits(uint16_t value) { unsigned int count; for (count = 0; value; count++) value &= value - 1; return count; } static void set_s3d_format_bits(const uint8_t *edid_data, int off, int hdmi_3d_len, uint16_t *s3d_struct_bits) { //These are 3D formats signaled through 2D VIC order and 3D_structure-3D_Detail while (hdmi_3d_len > 0) { unsigned int val = edid_data[off++] & 0x0F; *s3d_struct_bits |= 1 << val; //3D Detail_x is included for 3D_Structure_x range 0x8-0xF if ( val >= HDMI_SIDE_BY_SIDE_HALF) { hdmi_3d_len--; off++; } hdmi_3d_len--; } } static void update_s3d_format(struct edid_t *edid, enum hdmi_3d_format format, uint8_t vic_pos, enum hdmi_3d_subsampling subsamp) { unsigned int format_ix, vic_pos_ix; if (vic_pos > edid->num_svds) { return; } for (format_ix = 0; format_ix < edid->num_s3d_formats; format_ix++) { if (edid->s3d_format_list[format_ix].format == format) { break; } } if (format_ix >= edid->num_s3d_formats) { return; } //In case this has already been signaled, we'll update the subsampling mode for (vic_pos_ix = 0; vic_pos_ix < edid->s3d_format_list[format_ix].num_valid_vic; vic_pos_ix++) { if (edid->s3d_format_list[format_ix].vic_info[vic_pos_ix].vic_pos == vic_pos) { break; } } if (vic_pos_ix >= edid->s3d_format_list[format_ix].num_valid_vic) { vic_pos_ix = edid->s3d_format_list[format_ix].num_valid_vic; edid->s3d_format_list[format_ix].num_valid_vic += 1; } edid->s3d_format_list[format_ix].vic_info[vic_pos_ix].vic_pos = vic_pos; edid->s3d_format_list[format_ix].vic_info[vic_pos_ix].subsampling = subsamp; } /* This function was originally written by Mythri pk */ static int edid_get_datablock_offset(const uint8_t *edid_data, enum datablock_id type, unsigned int *off) { uint8_t val; uint8_t ext_length; uint8_t offset; //CEA extension signaled? If not, then no datablocks are contained if (edid_data[0x7e] == 0x00) { return 1; } //18-byte descriptors only? Otherwise, there are datablocks present ext_length = edid_data[0x82]; if (ext_length == 0x4) { return 1; } //Start of first extended data block offset = 0x84; while (offset < (0x80 + ext_length)) { val = edid_data[offset]; //Upper 3 bits indicate block type if ((val >> 5) == type) { *off = offset; return 0; } else { //lower 5 bits indicate block length offset += (val & 0x1F) + 1; } } return 1; } static void edid_parse_s3d_support(struct edid_t *edid, const uint8_t *edid_data) { unsigned int off; unsigned int i, count; uint8_t val; uint8_t s3d_multi_present; uint8_t hdmi_3d_len; uint16_t s3d_struct_all = 0; uint16_t s3d_struct_bits = 0; uint16_t hdmi_vic_pos_bits = 0; //memset(edid->s3d_formats, 0, sizeof(edid->s3d_formats)); //S3D HDMI information is signaled in the Vendor Specific datablock if(edid_get_datablock_offset(edid_data, DATABLOCK_VENDOR, &off)) return; //Skip header and other non-S3D related fields off += 8; val = edid_data[off++]; //HDMI_Video_present? if (!(val & 0x20)) { return; } //Latency_Fields_Present? Skip if (val & 0x80) { off += 2; } //I_Latency_Fields_Present? Skip if (val & 0x40) { off += 2; } val = edid_data[off++]; //3D_Present? if (!(val & 0x80)) { return; } edid->s3d_capable = true; s3d_multi_present = (val & 0x60) >> 5; //Skip HDMI_XX_LEN val = edid_data[off++]; off += (val & 0xE0) >> 5; hdmi_3d_len = (val & 0x1F); //3D capabilities signaled through bitmasks //s3d_struct_all has all the 3D formats supported (per bit) //hdmi_vic_mask has which of the corresponding VIC codes the 3D formats apply to //if s3d_multi_present = 1, then the 3D formats apply to all the first 16 VIC codes if (s3d_multi_present == 1 || s3d_multi_present == 2) { s3d_struct_all = (edid_data[off] << 8) | edid_data[off+1]; hdmi_vic_pos_bits = 0xFFFF; hdmi_3d_len -= 2; off += 2; } if (s3d_multi_present == 2) { hdmi_vic_pos_bits = (edid_data[off] << 8) | edid_data[off+1]; hdmi_3d_len -= 2; off += 2; } //Bit 15 signals same format as Bit 8 - HDMI_SIDE_BY_SIDE_HALF, they only differ in subsampling options s3d_struct_bits = s3d_struct_all & 0x7FFF; set_s3d_format_bits(edid_data, off, hdmi_3d_len, &s3d_struct_bits); edid->num_s3d_formats = count_set_bits(s3d_struct_bits); edid->s3d_format_list = (struct hdmi_s3d_format_info_t *) malloc(edid->num_s3d_formats * sizeof(struct hdmi_s3d_format_info_t)); count = 0; for (i = 0; i <= HDMI_SIDE_BY_SIDE_HALF; i++) { if (s3d_struct_bits & (1 << i)) { edid->s3d_format_list[count++].format = (enum hdmi_3d_format)i; } } for (i = 0; i < edid->num_s3d_formats; i++) { unsigned int j; enum hdmi_3d_subsampling subsampling; if (edid->s3d_format_list[i].format == HDMI_SIDE_BY_SIDE_HALF) { uint16_t bitmask = HDMI_SIDE_BY_SIDE_HALF_QUINCUNX_BIT | HDMI_SIDE_BY_SIDE_HALF_BIT; if ( (s3d_struct_all & bitmask) == bitmask) { subsampling = HDMI_SS_HORZANDQUINCUNX; } else if ((s3d_struct_all & bitmask) == HDMI_SIDE_BY_SIDE_HALF_QUINCUNX_BIT) { subsampling = HDMI_SS_QUINCUNX_ALL; } else if ((s3d_struct_all & bitmask) == HDMI_SIDE_BY_SIDE_HALF_BIT) { subsampling = HDMI_SS_HORIZONTAL; } } else if (edid->s3d_format_list[i].format == HDMI_TOPBOTTOM) { subsampling = HDMI_SS_VERTICAL; } else { subsampling = HDMI_SS_NONE; } count = 0; for (j = 0; j < 16; j++) { if ((s3d_struct_all & (1 << edid->s3d_format_list[i].format)) && (hdmi_vic_pos_bits & (1 << j))) { edid->s3d_format_list[i].vic_info[count].subsampling = subsampling; edid->s3d_format_list[i].vic_info[count++].vic_pos = j; } } edid->s3d_format_list[i].num_valid_vic = count; } //In this case, the 3D formats signaled only apply to the VIC codes signaled per bit //i.e. bit0 = VIC code 0 from the Short video descriptors list while (hdmi_3d_len > 0) { //Upper 4 bits indicate vic position, lower 4 bits are the 3D structure value enum hdmi_3d_subsampling subsampling = HDMI_SS_NONE; uint8_t vic_pos = (edid_data[off] & 0xF0) >> 4; enum hdmi_3d_format format = (enum hdmi_3d_format) (edid_data[off++] & 0x0F); if (format >= HDMI_SIDE_BY_SIDE_HALF) { subsampling = (enum hdmi_3d_subsampling)((edid_data[off++] & 0xF0) >> 4); hdmi_3d_len--; } if (format == HDMI_TOPBOTTOM) { subsampling = HDMI_SS_VERTICAL; } update_s3d_format(edid, format, vic_pos, subsampling); hdmi_3d_len--; } } static void edid_fill_svd_info(uint8_t code, struct svd_info_t *info) { if(code > NUM_SVD_ENTRIES) code = 0; memcpy(info, &svd_table[code], sizeof(struct svd_info_t)); } static void edid_parse_svds(struct edid_t *edid, const uint8_t *raw_edid_data) { unsigned int offset; unsigned int i; if (edid_get_datablock_offset(raw_edid_data, DATABLOCK_VIDEO, &offset)) { edid->num_svds = 0; edid->svd_list = NULL; return ; } edid->num_svds = raw_edid_data[offset] & 0x1F; edid->svd_list = (struct svd_t *) malloc(edid->num_svds * sizeof(struct svd_t)); for (i = 0; i < edid->num_svds; i++) { struct svd_t *svd = &edid->svd_list[i]; svd->code = raw_edid_data[offset + i] & 0x7F; svd->native = (raw_edid_data[offset + i] & 0x80) == 0x80; edid_fill_svd_info(svd->code, &svd->info); } } /*=======================================================*/ int edid_parser_init(struct edid_t **edid_handle, const uint8_t *raw_edid_data) { if(edid_handle == NULL) { return -1; } struct edid_t *edid = (struct edid_t *) malloc(sizeof(struct edid_t)); if (edid == NULL) { return -1; } memset(edid, 0, sizeof(struct edid_t)); edid_parse_svds(edid, raw_edid_data); edid_parse_s3d_support(edid, raw_edid_data); *edid_handle = edid; return 0; } void edid_parser_deinit(struct edid_t *edid) { free(edid->s3d_format_list); free(edid->svd_list); free(edid); } bool edid_s3d_capable(struct edid_t *edid) { return edid->s3d_capable; } bool edid_supports_s3d_format(struct edid_t *edid, enum hdmi_3d_format format) { unsigned int i; for (i = 0; i < edid->num_s3d_formats; i++) { if (edid->s3d_format_list[i].format == format) { return true; } } return false; } const struct hdmi_s3d_format_info_t * edid_get_s3d_format_info(struct edid_t *edid, enum hdmi_3d_format format) { unsigned int i; for (i = 0; i < edid->num_s3d_formats; i++) { if (edid->s3d_format_list[i].format == format) { return &edid->s3d_format_list[i]; } } return NULL; } void edid_get_svd_list(struct edid_t *edid, struct svd_t **list, unsigned int *num_elements) { if(list == NULL || num_elements == NULL) return; *list = edid->svd_list; *num_elements = edid->num_svds; } const struct svd_t *edid_get_svd_descriptor(struct edid_t *edid, uint8_t vic_pos) { if(vic_pos > edid->num_svds) return NULL; return &edid->svd_list[vic_pos]; }