/****************************************************************************** * * Copyright (C) 2015-2016 Cadence Design Systems, Inc. * All rights reserved worldwide. * * Copyright 2017-2018 NXP * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. Neither the name of the copyright holder nor the names of its contributors * may be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. THE SOFTWARE IS PROVIDED "AS IS", * WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED * TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * ****************************************************************************** * * edid_parser.c * ****************************************************************************** */ #include "edid_parser.h" static EDID_PARSER_RESULT edid_parse_dtd(S_DTD_DATA *descriptor, unsigned char *raw_data) { unsigned int raw_data_index = 0; descriptor->header.type = DESCRIPTOR_TYPE_DTD; descriptor->header.tag = 0; descriptor->pixel_clock = raw_data[raw_data_index]; descriptor->pixel_clock += (unsigned short)raw_data[raw_data_index + 1] << 8; descriptor->horizontal_addressable_video = raw_data[raw_data_index + 2]; descriptor->horizontal_addressable_video += ((unsigned short)raw_data[raw_data_index + 4] & 0xF0) << 4; descriptor->horizontal_blanking = raw_data[raw_data_index + 3]; descriptor->horizontal_blanking += ((unsigned short)raw_data[raw_data_index + 4] & 0x0F) << 8; descriptor->vertical_addressable_video = raw_data[raw_data_index + 5]; descriptor->vertical_addressable_video += ((unsigned short)raw_data[raw_data_index + 7] & 0xF0) << 4; descriptor->vertical_blanking = raw_data[raw_data_index + 6]; descriptor->vertical_blanking += ((unsigned short)raw_data[raw_data_index + 7] & 0x0F) << 8; descriptor->horizontal_front_porch = raw_data[raw_data_index + 8]; descriptor->horizontal_front_porch += ((unsigned short)raw_data[raw_data_index + 11] & 0xC0) << 2; descriptor->horizontal_sync_pulse_width = raw_data[raw_data_index + 9]; descriptor->horizontal_sync_pulse_width += ((unsigned short)raw_data[raw_data_index + 11] & 0x30) << 4; descriptor->vertical_front_porch = (raw_data[raw_data_index + 10] & 0xF0) >> 4; descriptor->vertical_front_porch += (raw_data[raw_data_index + 11] & 0x0C) << 2; descriptor->vertical_sync_pulse_width = raw_data[raw_data_index + 10] & 0x0F; descriptor->vertical_sync_pulse_width += (raw_data[raw_data_index + 11] & 0x03) << 4; descriptor->horizontal_addressable_video_image_size = raw_data[raw_data_index + 12]; descriptor->horizontal_addressable_video_image_size += ((unsigned short)raw_data[raw_data_index + 14] & 0xF0) << 4; descriptor->vertical_addressable_video_image_size = raw_data[raw_data_index + 13]; descriptor->vertical_addressable_video_image_size += ((unsigned short)raw_data[raw_data_index + 14] & 0x0F) << 8; descriptor->horizontal_border = raw_data[raw_data_index + 15]; descriptor->vertical_border = raw_data[raw_data_index + 16]; descriptor->signal_features = raw_data[raw_data_index + 17]; return EDID_PARSER_SUCCESS; } static EDID_PARSER_RESULT edid_parse_serial_number(S_SERIAL_NUMBER_DATA * descriptor, unsigned char *raw_data) { unsigned int raw_data_index = 0; descriptor->header.type = DESCRIPTOR_TYPE_SERIAL_NUMBER; descriptor->header.tag = 0xFF; int idx; for (idx = 0; idx < 13; idx++) descriptor->serial_number[idx] = raw_data[raw_data_index + 5 + idx]; return EDID_PARSER_SUCCESS; } static EDID_PARSER_RESULT edid_parse_data_string(S_DATA_STRING_DATA * descriptor, unsigned char *raw_data) { unsigned int raw_data_index = 0; descriptor->header.type = DESCRIPTOR_TYPE_DATA_STRING; descriptor->header.tag = 0xFE; int idx; for (idx = 0; idx < 13; idx++) descriptor->data_string[idx] = raw_data[raw_data_index + 5 + idx]; return EDID_PARSER_SUCCESS; } static EDID_PARSER_RESULT edid_parse_range_limits(S_RANGE_LIMITS_DATA * descriptor, unsigned char *raw_data) { unsigned int raw_data_index = 0; descriptor->header.type = DESCRIPTOR_TYPE_RANGE_LIMITS; descriptor->header.tag = 0xFD; descriptor->offset_flags = raw_data[raw_data_index + 4]; descriptor->min_vertical_rate = raw_data[raw_data_index + 5]; descriptor->max_vertical_rate = raw_data[raw_data_index + 6]; descriptor->min_horizontal_rate = raw_data[raw_data_index + 7]; descriptor->max_horizontal_rate = raw_data[raw_data_index + 8]; descriptor->max_pixel_clock = raw_data[raw_data_index + 9]; switch (raw_data[raw_data_index + 10]) { case 0x00: descriptor->type = VIDEO_TIMING_DEFAULT_GTF; break; case 0x01: descriptor->type = VIDEO_TIMING_RANGE_LIMITS_ONLY; break; case 0x02: descriptor->type = VIDEO_TIMING_SECONDARY_GTF; S_RANGE_LIMITS_VIDEO_TIMING_SECONDARY_GTF *timing_type_gtf = (S_RANGE_LIMITS_VIDEO_TIMING_SECONDARY_GTF *) descriptor->suport_flags; timing_type_gtf->start_break_frequency = raw_data[raw_data_index + 12]; timing_type_gtf->c = raw_data[raw_data_index + 13]; timing_type_gtf->m = raw_data[raw_data_index + 14]; timing_type_gtf->m += (unsigned short)raw_data[raw_data_index + 15] << 8; timing_type_gtf->k = raw_data[raw_data_index + 16]; timing_type_gtf->j = raw_data[raw_data_index + 17]; break; case 0x04: descriptor->type = VIDEO_TIMING_CVT; S_RANGE_LIMITS_VIDEO_TIMING_CVT *timing_type_cvt = (S_RANGE_LIMITS_VIDEO_TIMING_CVT *)descriptor-> suport_flags; timing_type_cvt->cvt_version = raw_data[raw_data_index + 11]; timing_type_cvt->additional_pixel_clock_precision = raw_data[raw_data_index + 12] >> 2; timing_type_cvt->max_active_pixels = raw_data[raw_data_index + 13]; timing_type_cvt->max_active_pixels += (unsigned short)(raw_data[raw_data_index + 12] & 0x03) << 8; timing_type_cvt->supported_ar = raw_data[raw_data_index + 14] >> 3; timing_type_cvt->preferred_ar = raw_data[raw_data_index + 15] >> 5; timing_type_cvt->blanking_support = (raw_data[raw_data_index + 15] & 0x18) >> 3; timing_type_cvt->supported_scalling = raw_data[raw_data_index + 16] >> 4; timing_type_cvt->preferred_vertical_refresh_rate = raw_data[raw_data_index + 17]; break; } return EDID_PARSER_SUCCESS; } static EDID_PARSER_RESULT edid_parse_product_name(S_PRODUCT_NAME_DATA * descriptor, unsigned char *raw_data) { unsigned int raw_data_index = 0; descriptor->header.type = DESCRIPTOR_TYPE_PRODUCT_NAME; descriptor->header.tag = 0xFC; int idx; for (idx = 0; idx < 13; idx++) descriptor->product_name[idx] = raw_data[raw_data_index + 5 + idx]; return EDID_PARSER_SUCCESS; } static EDID_PARSER_RESULT edid_parse_color_point(S_COLOR_POINT_DATA * descriptor, unsigned char *raw_data) { unsigned int raw_data_index = 0; descriptor->header.type = DESCRIPTOR_TYPE_COLOR_POINT; descriptor->header.tag = 0xFB; descriptor->white_point_index_1 = raw_data[raw_data_index + 5]; descriptor->white_x_1 = (raw_data[raw_data_index + 6] & 0x0C) >> 2; descriptor->white_x_1 += (unsigned short)raw_data[raw_data_index + 7] << 2; descriptor->white_y_1 = raw_data[raw_data_index + 6] & 0x03; descriptor->white_y_1 += (unsigned short)raw_data[raw_data_index + 8] << 2; descriptor->gamma_1 = raw_data[raw_data_index + 9]; descriptor->white_point_index_2 = raw_data[raw_data_index + 10]; descriptor->white_x_2 = (raw_data[raw_data_index + 11] & 0x0C) >> 2; descriptor->white_x_2 += (unsigned short)raw_data[raw_data_index + 12] << 2; descriptor->white_y_2 = raw_data[raw_data_index + 11] & 0x03; descriptor->white_y_2 += (unsigned short)raw_data[raw_data_index + 13] << 2; descriptor->gamma_2 = raw_data[raw_data_index + 14]; return EDID_PARSER_SUCCESS; } static EDID_PARSER_RESULT edid_parse_standard_timing(S_STANDARD_TIMING_DATA * descriptor, unsigned char *raw_data) { unsigned int raw_data_index = 0; descriptor->header.type = DESCRIPTOR_TYPE_STANDARD_TIMING; descriptor->header.tag = 0xFA; int idx; for (idx = 0; idx < 6; idx++) { descriptor->standard_timings[idx] = raw_data[raw_data_index + 5 + 2 * idx]; descriptor->standard_timings[idx] += (unsigned short)raw_data[raw_data_index + 5 + 2 * idx + 1]; } return EDID_PARSER_SUCCESS; } static EDID_PARSER_RESULT edid_parse_color_management(S_COLOR_MANAGEMENT_DATA * descriptor, unsigned char *raw_data) { unsigned int raw_data_index = 0; descriptor->header.type = DESCRIPTOR_TYPE_COLOR_MANAGEMENT; descriptor->header.tag = 0xF9; descriptor->version = raw_data[raw_data_index + 5]; descriptor->red_a3 = raw_data[raw_data_index + 6]; descriptor->red_a3 += (unsigned short)raw_data[raw_data_index + 7] << 8; descriptor->red_a2 = raw_data[raw_data_index + 8]; descriptor->red_a2 += (unsigned short)raw_data[raw_data_index + 9] << 8; descriptor->green_a3 = raw_data[raw_data_index + 10]; descriptor->green_a3 += (unsigned short)raw_data[raw_data_index + 11] << 8; descriptor->green_a2 = raw_data[raw_data_index + 12]; descriptor->green_a2 += (unsigned short)raw_data[raw_data_index + 13] << 8; descriptor->blue_a3 = raw_data[raw_data_index + 14]; descriptor->blue_a3 += (unsigned short)raw_data[raw_data_index + 15] << 8; descriptor->blue_a2 = raw_data[raw_data_index + 16]; descriptor->blue_a2 += (unsigned short)raw_data[raw_data_index + 17] << 8; return EDID_PARSER_SUCCESS; } static EDID_PARSER_RESULT edid_parse_cvt_timing_codes(S_CVT_TIMING_CODES_DATA * descriptor, unsigned char *raw_data) { unsigned int raw_data_index = 0; descriptor->header.type = DESCRIPTOR_TYPE_CVT_TIMING_CODES; descriptor->header.tag = 0xF8; descriptor->version = raw_data[raw_data_index + 5]; int idx; for (idx = 0; idx < 4; idx++) { descriptor->addressable_lines[idx] = raw_data[raw_data_index + 6 + idx * 3]; descriptor->addressable_lines[idx] += (unsigned short)(raw_data[raw_data_index + 7 + idx * 3] & 0xF0) << 4; descriptor->aspect_ratio[idx] = (raw_data[raw_data_index + 7 + idx * 3] & 0x0C) >> 2; descriptor->preferred_vertical_rate[idx] = (raw_data[raw_data_index + 8 + idx * 3] & 0x60) >> 5; descriptor->supported_vertical_rate_and_blanking[idx] = raw_data[raw_data_index + 8 + idx * 3] & 0x1F; } return EDID_PARSER_SUCCESS; } static EDID_PARSER_RESULT edid_parse_established_timings_3(S_ESTABLISHED_TIMINGS_3_DATA *descriptor, unsigned char *raw_data) { unsigned int raw_data_index = 0; descriptor->header.type = DESCRIPTOR_TYPE_ESTABLISHED_TIMINGS_3; descriptor->header.tag = 0xF7; descriptor->version = raw_data[raw_data_index + 5]; int idx; for (idx = 0; idx < 6; idx++) { descriptor->established_timings[idx] = raw_data[raw_data_index + 6 + idx]; } return EDID_PARSER_SUCCESS; } static EDID_PARSER_RESULT edid_parse_dummy(S_DUMMY_DATA *descriptor, unsigned char *raw_data) { descriptor->header.type = DESCRIPTOR_TYPE_DUMMY; descriptor->header.tag = 0x10; return EDID_PARSER_SUCCESS; } static EDID_PARSER_RESULT edid_parse_manufacturer_specific(S_MANUFACTURER_SPECIFIC_DATA *descriptor, unsigned char *raw_data, unsigned char tag) { descriptor->header.type = DESCRIPTOR_TYPE_MANUFACTURER_SPECIFIC; descriptor->header.tag = tag; return EDID_PARSER_SUCCESS; } EDID_PARSER_RESULT edid_parse(S_EDID_DATA *edid, unsigned char *raw_data, unsigned int len) { unsigned int raw_data_index = 0; unsigned char sum = 0; /*CHECK SUM OF BYTES IN BLOCK0 */ for (raw_data_index = 0; raw_data_index < EDID_LENGTH; raw_data_index++) sum += raw_data[raw_data_index]; if (sum != 0) return EDID_PARSER_ERROR; /*READ HEADER */ for (raw_data_index = 0; raw_data_index < EDID_HEADER_LENGTH; raw_data_index++) edid->header[raw_data_index] = raw_data[raw_data_index]; /*READ VENDOR & PRODUCT IDENTIFICATION */ /*manufacturer name */ edid->manufacturer_name[0] = ((raw_data[8] & 0x7C) >> 2) + 0x40; edid->manufacturer_name[1] = ((raw_data[8] & 0x03) << 3) + ((raw_data[9] & 0xE0) >> 5) + 0x40; edid->manufacturer_name[2] = ((raw_data[9] & 0x1F)) + 0x40; edid->manufacturer_name[3] = 0; /*product code */ edid->product_code = (raw_data[10]); edid->product_code += ((unsigned short)raw_data[11]) << 8; /*serial number */ edid->serial_number = raw_data[12]; edid->serial_number += (unsigned int)raw_data[13] << 8; edid->serial_number += (unsigned int)raw_data[14] << 16; edid->serial_number += (unsigned int)raw_data[15] << 24; /*week of manufacture */ edid->week = raw_data[16]; /*year of manufacture */ edid->year = raw_data[17]; /*EDID STRUCTURE VERSION & REVISION */ edid->edid_version = ((unsigned short)raw_data[18] << 8) + raw_data[19]; /*BASIC DISPLAY PARAMETERS AND FEATURES */ /*video input definition */ edid->video_input_definition = raw_data[20]; /*horizontal screen size */ edid->horizontal_size = raw_data[21]; /*vertical screen size */ edid->vertical_size = raw_data[22]; /*display transfer characteristic */ edid->gamma = raw_data[23]; /*feature support */ edid->feature_support = raw_data[24]; /*COLOR CHARACTERISTIC */ /*red */ edid->chromacity_coorditates_red_x = (raw_data[25] & 0xC0) >> 6; edid->chromacity_coorditates_red_x += (unsigned short)raw_data[27] << 2; edid->chromacity_coorditates_red_y = (raw_data[25] & 0x30) >> 4; edid->chromacity_coorditates_red_y += (unsigned short)raw_data[28] << 2; /*green */ edid->chromacity_coorditates_green_x = (raw_data[25] & 0x0C) >> 2; edid->chromacity_coorditates_green_x += (unsigned short)raw_data[29] << 2; edid->chromacity_coorditates_green_y = (raw_data[25] & 0x03); edid->chromacity_coorditates_green_y += (unsigned short)raw_data[30] << 2; /*blue */ edid->chromacity_coorditates_blue_x = (raw_data[26] & 0xC0) >> 6; edid->chromacity_coorditates_blue_x += (unsigned short)raw_data[31] << 2; edid->chromacity_coorditates_blue_y = (raw_data[26] & 0x30) >> 4; edid->chromacity_coorditates_blue_y += (unsigned short)raw_data[32] << 2; /*blue */ edid->chromacity_coorditates_white_x = (raw_data[26] & 0x0C) >> 2; edid->chromacity_coorditates_white_x += (unsigned short)raw_data[33] << 2; edid->chromacity_coorditates_white_y = (raw_data[26] & 0x03); edid->chromacity_coorditates_white_y += (unsigned short)raw_data[34] << 2; /*ESTABLISHED TIMINGS */ edid->established_timing_1 = raw_data[35]; edid->established_timing_2 = raw_data[36]; edid->manufacturer_timing = raw_data[37]; /*STANDARD TIMINGS */ for (raw_data_index = 0; raw_data_index < 8; raw_data_index++) { edid->standard_timings[raw_data_index] = raw_data[38 + (2 * raw_data_index)]; edid->standard_timings[raw_data_index] += (unsigned short)raw_data[38 + (2 * raw_data_index + 1)]; } /*extensions */ edid->extensions = raw_data[126]; /*DESCRIPTORS */ unsigned int descriptor_index; raw_data_index = 54; for (descriptor_index = 0; descriptor_index < 4; descriptor_index++) { if (raw_data[raw_data_index] == 0 && raw_data[raw_data_index] == 0) { /*display descriptor found */ unsigned char tag = raw_data[raw_data_index + 3]; if (tag == 0xFF) { /*display product serial number */ S_SERIAL_NUMBER_DATA *descriptor = (S_SERIAL_NUMBER_DATA *)edid-> descriptors[descriptor_index]; if (edid_parse_serial_number (descriptor, raw_data + raw_data_index) != EDID_PARSER_SUCCESS) return EDID_PARSER_ERROR; } else if (tag == 0xFE) { /*alphanumeric data string */ S_DATA_STRING_DATA *descriptor = (S_DATA_STRING_DATA *)edid-> descriptors[descriptor_index]; if (edid_parse_data_string (descriptor, raw_data + raw_data_index) != EDID_PARSER_SUCCESS) return EDID_PARSER_ERROR; } else if (tag == 0xFD) { /*display range limits */ S_RANGE_LIMITS_DATA *descriptor = (S_RANGE_LIMITS_DATA *)edid-> descriptors[descriptor_index]; if (edid_parse_range_limits (descriptor, raw_data + raw_data_index) != EDID_PARSER_SUCCESS) return EDID_PARSER_ERROR; } else if (tag == 0xFC) { /*display product name */ S_PRODUCT_NAME_DATA *descriptor = (S_PRODUCT_NAME_DATA *)edid-> descriptors[descriptor_index]; if (edid_parse_product_name (descriptor, raw_data + raw_data_index) != EDID_PARSER_SUCCESS) return EDID_PARSER_ERROR; } else if (tag == 0xFB) { /*color point data */ S_COLOR_POINT_DATA *descriptor = (S_COLOR_POINT_DATA *)edid-> descriptors[descriptor_index]; if (edid_parse_color_point (descriptor, raw_data + raw_data_index) != EDID_PARSER_SUCCESS) return EDID_PARSER_ERROR; } else if (tag == 0xFA) { /*standard timing identifications */ S_STANDARD_TIMING_DATA *descriptor = (S_STANDARD_TIMING_DATA *)edid-> descriptors[descriptor_index]; if (edid_parse_standard_timing (descriptor, raw_data + raw_data_index) != EDID_PARSER_SUCCESS) return EDID_PARSER_ERROR; } else if (tag == 0xF9) { /*display color management (DCM) */ S_COLOR_MANAGEMENT_DATA *descriptor = (S_COLOR_MANAGEMENT_DATA *)edid-> descriptors[descriptor_index]; if (edid_parse_color_management (descriptor, raw_data + raw_data_index) != EDID_PARSER_SUCCESS) return EDID_PARSER_ERROR; } else if (tag == 0xF8) { /*CVT 3 byte timing codes */ S_CVT_TIMING_CODES_DATA *descriptor = (S_CVT_TIMING_CODES_DATA *)edid-> descriptors[descriptor_index]; if (edid_parse_cvt_timing_codes (descriptor, raw_data + raw_data_index) != EDID_PARSER_SUCCESS) return EDID_PARSER_ERROR; } else if (tag == 0xF7) { /*established timings III */ S_ESTABLISHED_TIMINGS_3_DATA *descriptor = (S_ESTABLISHED_TIMINGS_3_DATA *)edid-> descriptors[descriptor_index]; if (edid_parse_established_timings_3 (descriptor, raw_data + raw_data_index) != EDID_PARSER_SUCCESS) return EDID_PARSER_ERROR; } else if (tag == 0x10) { /*dummy */ S_DUMMY_DATA *descriptor = (S_DUMMY_DATA *)edid-> descriptors[descriptor_index]; if (edid_parse_dummy (descriptor, raw_data + raw_data_index) != EDID_PARSER_SUCCESS) return EDID_PARSER_ERROR; } else if (tag <= 0x0F) { /*manufacturer specific data */ S_MANUFACTURER_SPECIFIC_DATA *descriptor = (S_MANUFACTURER_SPECIFIC_DATA *)edid-> descriptors[descriptor_index]; if (edid_parse_manufacturer_specific (descriptor, raw_data + raw_data_index, tag) != EDID_PARSER_SUCCESS) return EDID_PARSER_ERROR; } } else { /*detailed timing definition */ S_DTD_DATA *descriptor = (S_DTD_DATA *)edid-> descriptors[descriptor_index]; if (edid_parse_dtd (descriptor, raw_data + raw_data_index) != EDID_PARSER_SUCCESS) return EDID_PARSER_ERROR; } raw_data_index += 18; } return EDID_PARSER_SUCCESS; }