diff options
Diffstat (limited to 'src/edid.c')
-rw-r--r-- | src/edid.c | 538 |
1 files changed, 538 insertions, 0 deletions
diff --git a/src/edid.c b/src/edid.c new file mode 100644 index 0000000..1733a73 --- /dev/null +++ b/src/edid.c @@ -0,0 +1,538 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * Author: Per Persson per.xb.persson@stericsson.com for + * ST-Ericsson. + * License terms: <FOSS license>. + */ + +#include <unistd.h> /* Symbolic Constants */ +#include <sys/types.h> /* Primitive System Data Types */ +#include <errno.h> /* Errors */ +#include <stdarg.h> +#include <stdio.h> /* Input/Output */ +#include <stdlib.h> /* General Utilities */ +#include <string.h> /* String handling */ +#include <fcntl.h> +#include <time.h> +#include <ctype.h> +#include <utils/Log.h> +#include "../include/hdmi_service_api.h" +#include "../include/hdmi_service_local.h" + +struct edid_stdtim_ar { + int x; + int y; +}; + +const __u8 edidreqbl0[] = {0xA0, 0x00}; /* Request EDID block 0 */ +const __u8 edidreqbl1[] = {0xA0, 0x01}; /* Request EDID block 1 */ +const __u8 edid_block0_start[] = { + 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00}; +const __u8 edid_stdtim9_flag[] = {0x00, 0x00, 0x00, 0xFA, 0x00}; +const __u8 edid_esttim3_flag[] = {0x00, 0x00, 0x00, 0xF7, 0x00}; +const __u8 edid_esttim1_2_offset[] = {EDID_BL0_ESTTIM1_OFFSET, + EDID_BL0_ESTTIM2_OFFSET}; +const __u8 edid_esttim3_flag_offset[] = {EDID_BL1_ESTTIM3_1_FLAG_OFFSET, + EDID_BL1_ESTTIM3_2_FLAG_OFFSET, + EDID_BL1_ESTTIM3_3_FLAG_OFFSET}; +const __u8 edid_stdtim9_flag_offset[] = {EDID_BL1_STDTIM9_1_FLAG_OFFSET, + EDID_BL1_STDTIM9_2_FLAG_OFFSET, + EDID_BL1_STDTIM9_3_FLAG_OFFSET}; +/* Aspect ratios */ +const struct edid_stdtim_ar edid_stdtim_ar[] = { + {16, 10}, + {4, 3}, + {5, 4}, + {16, 9} +}; + +struct vesa_modes { + int xres; + int yres; + int freq; + int vesa_nr; +}; + +static struct vesa_modes vesa_modes[] = { + {800, 600, 60, 9}, + {848, 480, 60, 14}, + {1024, 768, 60, 16}, + {1280, 768, 60, 23}, + {1280, 800, 60, 28}, + {1360, 768, 60, 39}, + {1366, 768, 60, 81} +}; + +static int get_vesanr_from_est_timing(int timing, int byte, int bit) +{ + int vesa_nr = -1; + + LOGHDMILIB2("timing:%d bit:%d", timing, bit); + + switch (timing) { + /* Established Timing 1 */ + case 1: + switch (bit) { + case 5: + vesa_nr = 4; + break; + case 0: + vesa_nr = 9; + break; + default: + break; + } + break; + /* Established Timing 2 */ + case 2: + switch (bit) { + case 3: + vesa_nr = 16; + break; + default: + break; + } + break; + /* Established Timing 3 */ + case 3: + switch (byte) { + case 6: + switch (bit) { + case 3: + vesa_nr = 14; + break; + default: + break; + } + break; + case 7: + switch (bit) { + case 7: + vesa_nr = 23; + break; + case 6: + vesa_nr = 22; + break; + default: + break; + } + break; + case 8: + switch (bit) { + case 7: + vesa_nr = 39; + break; + default: + break; + } + break; + } + break; + default: + break; + } + + return vesa_nr; +} + +int get_vesanr_from_std_timing(int xres, int yres, int freq) +{ + int vesa_nr = -1; + int nr_of_timings; + int index; + + nr_of_timings = sizeof(vesa_modes)/sizeof(vesa_modes[0]); + for (index = 0; index < nr_of_timings; index++) { + if ((xres == vesa_modes[index].xres) && + (yres == vesa_modes[index].yres) && + (freq == vesa_modes[index].freq)) { + vesa_nr = vesa_modes[index].vesa_nr; + break; + } + } + + return vesa_nr; +} + +/* Request and read EDID message for specified block */ +int edid_read(__u8 block, __u8 *data) +{ + int edidread; + int res; + int result = 0; + __u8 buf[16]; + int size; + + LOGHDMILIB("EDID read blk %d", block); + + /* Request edid block 0 */ + edidread = open(EDIDREAD_FILE, O_RDWR); + if (edidread < 0) { + LOGHDMILIB("***** Failed to open %s *****", EDIDREAD_FILE); + result = -1; + goto edid_read_end2; + } + + if (block == 0) { + size = sizeof(edidreqbl0); + memcpy(buf, edidreqbl0, size); + } else { + size = sizeof(edidreqbl1); + memcpy(buf, edidreqbl1, size); + } + + res = write(edidread, buf, size); + if (res < 0) { + LOGHDMILIB("***** Failed to write %s *****", EDIDREAD_FILE); + result = -2; + goto edid_read_end1; + } + + /* Check edid response */ + lseek(edidread, 0, SEEK_SET); + res = read(edidread, data, EDIDREAD_SIZE); + if (res != EDIDREAD_SIZE) { + LOGHDMILIB("***** %s read error size: %d *****", EDIDREAD_FILE, + res); + result = -3; + goto edid_read_end1; + } + +edid_read_end1: + close(edidread); +edid_read_end2: + return result; +} + +/* Parse EDID block 0 */ +int edid_parse0(__u8 *data, __u8 *extension, struct video_format formats[], + int nr_formats) +{ + __u8 version; + __u8 revision; + __u8 est_timing; + int findex; + int vesa_nr; + int bit; + int cnt; + int index; + int xres; + int yres; + int byte; + int ar_index; + int freq; + __u8 edidp; + + *extension = 0; + + /* Header */ + if (memcmp(data + EDID_BL0_HEADER_OFFSET, edid_block0_start, 8) != 0) { + LOGHDMILIB("edid response:\n%02x %02x %02x %02x %02x %02x %02x " + "%02x", + *(data + 1), + *(data + 2), + *(data + 3), + *(data + 4), + *(data + 5), + *(data + 6), + *(data + 7), + *(data + 8) + ); + return EDIDREAD_FAIL; + } else { + LOGHDMILIB("%s", "--- EDID block 0 start OK ---"); + } + + /* Ver and Rev */ + version = *(data + EDID_BL0_VERSION_OFFSET); + revision = *(data + EDID_BL0_REVISION_OFFSET); + LOGHDMILIB("Ver:%d Rev:%d", version, revision); + + /* Read Established Timings 1&2 and set sink_support */ + for (index = 0; index <= 1; index++) { + est_timing = *(data + edid_esttim1_2_offset[index]); + LOGHDMILIB2("EstTim%d:%02x", index + 1, est_timing); + if (est_timing == 0) + continue; + + for (bit = 7; bit >= 0; bit--) { + if (est_timing & (1 << bit)) { + vesa_nr = get_vesanr_from_est_timing(index + 1, + 0, bit); + LOGHDMILIB2("vesa_nr:%d", vesa_nr); + if (vesa_nr < 1) + continue; + + LOGHDMILIB2("EstTim1&2 try vesa_nr:%d", + vesa_nr); + for (cnt = 0; cnt < nr_formats; cnt++) { + LOGHDMILIB3("with:%d", + formats[cnt].vesaceanr); + if ((formats[cnt].cea == 0) && + (formats[cnt].vesaceanr == + vesa_nr)) { + formats[cnt].sink_support = 1; + LOGHDMILIB("EstTim1&2 %d " + "vesa_nr:%d", + index + 1, + vesa_nr); + break; + } + } + } + } + } + + /* Read Standard Timings 1-8 and set sink_support*/ + for (index = 0; index < EDID_BL0_STDTIM1_SIZE; index++) { + edidp = EDID_BL0_STDTIM1_OFFSET + index * 2; + xres = (*(data + edidp) + 31) * 8; + byte = *(data + edidp + 1); + ar_index = (byte * EDID_STDTIM_AR_MASK) >> EDID_STDTIM_AR_SHIFT; + yres = xres * edid_stdtim_ar[ar_index].y / + edid_stdtim_ar[ar_index].x; + freq = (byte * EDID_STDTIM_FREQ_MASK) >> EDID_STDTIM_FREQ_SHIFT; + vesa_nr = get_vesanr_from_std_timing(xres, yres, freq); + if (vesa_nr < 1) + continue; + + LOGHDMILIB2("StdTim1to8 try vesa_nr:%d", vesa_nr); + for (cnt = 0; cnt < nr_formats; cnt++) { + LOGHDMILIB3("with:%d", + formats[cnt].vesaceanr); + if ((formats[cnt].cea == 0) && + (formats[cnt].vesaceanr == + vesa_nr)) { + formats[cnt].sink_support = 1; + LOGHDMILIB("StdTim1to8 %d vesa_nr:%d", + index + 1, + vesa_nr); + break; + } + } + } + + if (*(data + EDID_BL0_EXTFLAG_OFFSET) != 0) + *extension = 1; + + return RESULT_OK; +} + +/* Parse EDID block 1 */ +int edid_parse1(__u8 *data, struct video_format formats[], int nr_formats, + int *basic_audio_support, struct edid_latency *edid_latency) +{ + __u8 rev; + __u8 offset; + __u8 blockp; + __u8 code; + __u8 length = 0; + __u8 ceanr; + int index; + int index2; + int cnt; + __u8 est_timing3; + int byte; + int bit; + int vesa_nr; + int xres; + int yres; + int ar_index; + int freq; + __u8 edidp; + __u8 *p; + + rev = *(data + EDID_BL1_REVNR_OFFSET); + offset = *(data + EDID_BL1_OFFSET_OFFSET); + + LOGHDMILIB("rev:%d offset:%d", rev, offset); + + if ((rev != EDID_EXTVER_3) || (offset == 0) || + (offset == EDID_NO_DATA)) { + LOGHDMILIB("%s", "No video block"); + return EDIDREAD_NOVIDEO; + } + + /* Check Audio support */ + if (*(data + EDID_BL1_AUDIO_SUPPORT_OFFSET) & + EDID_BASIC_AUDIO_SUPPORT_MASK) { + *basic_audio_support = 1; + } + + for (edidp = EDID_BLK_START; edidp < offset; + edidp = edidp + length + 1) { + code = (*(data + edidp) & EDID_BLK_CODE_MSK) >> + EDID_BLK_CODE_SHIFT; + length = *(data + edidp) & EDID_BLK_LENGTH_MSK; + + if ((offset + length) >= EDIDREAD_SIZE) + return EDIDREAD_FAIL; + + LOGHDMILIB2("code:%d blklen:%d", code, length); + + switch (code) { + case EDID_CODE_VIDEO: + for (blockp = edidp + 1; blockp < edidp + 1 + length; + blockp++) { + ceanr = *(data + blockp) & EDID_SVD_ID_MASK; + LOGHDMILIB2("try ceanr:%d", ceanr); + for (cnt = 0; cnt < nr_formats; cnt++) { + LOGHDMILIB3("with:%d", + formats[cnt].vesaceanr); + if ((formats[cnt].cea == 1) && + (formats[cnt].vesaceanr == + ceanr)) { + formats[cnt].sink_support = 1; + LOGHDMILIB("cea:%d", ceanr); + break; + } + } + } + break; + + case EDID_CODE_VSDB: + p = data + edidp; + if (length >= (EDID_VSD_PHYS_SRC + 1)) { + LOGHDMILIB("source physaddr:%02x%02x", + *(p + EDID_VSD_PHYS_SRC), + *(p + EDID_VSD_PHYS_SRC + 1)); + + /*TODO logical addr (HDMI spec p.192)*/ + } + + /* Video and Audio latency */ + if ((length >= EDID_VSD_AUD_LAT) && + (*(p + EDID_VSD_LATENCY_IND) & + EDID_VSD_LAT_FLD_MASK )) { + edid_latency->video_latency = + 2 * (*(p + EDID_VSD_VID_LAT) - 1); + edid_latency->audio_latency = + 2 * (*(p + EDID_VSD_AUD_LAT) - 1); + } + + /* Interlaced Video and Audio latency */ + if ((length >= EDID_VSD_INTLCD_AUD_LAT) && + (*(p + EDID_VSD_LATENCY_IND) & + EDID_VSD_INTLCD_LAT_FLD_MASK )) { + edid_latency->intlcd_video_latency = + 2 * (*(p + EDID_VSD_INTLCD_VID_LAT) - 1); + edid_latency->audio_latency = + 2 * (*(p + EDID_VSD_INTLCD_AUD_LAT) - 1); + } + break; + + default: + break; + } + } + + /* Read Established Timing 3 and set sink_support */ + for (index = 0; index <= 2; index++) { + edidp = edid_esttim3_flag_offset[index]; + + /* Check for Established Timing3 flag */ + if (memcmp(data + edidp, edid_esttim3_flag, + sizeof(edid_esttim3_flag)) != 0) + /* Flag mismatch, this is not Established Timing 3 */ + continue; + + for (byte = EDID_BL1_ESTTIM3_BYTE_START; + byte <= EDID_BL1_ESTTIM3_BYTE_END; byte++) { + est_timing3 = *(data + edidp + byte); + for (bit = 7; bit >= 0; bit--) { + if ((est_timing3 & (1 << bit)) == 0) + /* Not supported in sink */ + continue; + + vesa_nr = get_vesanr_from_est_timing(3, byte, + bit); + /* Set sink_suuport */ + LOGHDMILIB2("EstTim3 try vesa_nr:%d", vesa_nr); + for (cnt = 0; cnt < nr_formats; cnt++) { + LOGHDMILIB3("with:%d", + formats[cnt].vesaceanr); + if ((formats[cnt].cea == 0) && + (formats[cnt].vesaceanr == + vesa_nr)) { + formats[cnt].sink_support = 1; + LOGHDMILIB("EstTim3 vesa_nr:%d", + vesa_nr); + break; + } + } + } + } + } + + /* Read Standard Timings 9-16 and set sink_support*/ + for (index2 = 0; index2 <= 2; index2++) { + edidp = edid_stdtim9_flag_offset[index2]; + + /* Check for Standard Timing flag */ + if (memcmp(data + edidp, edid_stdtim9_flag, + sizeof(edid_stdtim9_flag)) != 0) + /* Flag mismatch, this is not Standard Timing 9-16 */ + continue; + + for (index = 0; index < EDID_BL1_STDTIM9_SIZE; index++) { + edidp += EDID_BL1_STDTIM9_BYTE_START + index * 2; + xres = (*(data + edidp) + 31) * 8; + byte = *(data + edidp + 1); + ar_index = (byte * EDID_STDTIM_AR_MASK) >> + EDID_STDTIM_AR_SHIFT; + yres = xres * edid_stdtim_ar[ar_index].y / + edid_stdtim_ar[ar_index].x; + freq = (byte * EDID_STDTIM_FREQ_MASK) >> + EDID_STDTIM_FREQ_SHIFT; + vesa_nr = get_vesanr_from_std_timing(xres, yres, freq); + LOGHDMILIB2("StdTim9to16 try vesa_nr:%d", vesa_nr); + for (cnt = 0; cnt < nr_formats; cnt++) { + LOGHDMILIB3("with:%d", + formats[cnt].vesaceanr); + if ((formats[cnt].cea == 0) && + (formats[cnt].vesaceanr == + vesa_nr)) { + formats[cnt].sink_support = 1; + LOGHDMILIB("StdTim9to16 %d vesa_nr:%d", + index + 1, + vesa_nr); + break; + } + } + } + } + + return RESULT_OK; +} + +/* Get EDID message of specified block and send it on client socket */ +int edidreq(__u8 block, __u32 cmd_id) +{ + int res = 0; + int ret = 0; + int edidsize = 0; + int val; + __u8 buf[512]; + __u8 ediddata[EDIDREAD_SIZE]; + + LOGHDMILIB("%s begin", __func__); + + /* Request EDID */ + res = edid_read(block, ediddata); + if (res == 0) + edidsize = EDIDREAD_SIZE; + + val = HDMI_EDIDRESP; + memcpy(&buf[CMD_OFFSET], &val, 4); + memcpy(&buf[CMDID_OFFSET], &cmd_id, 4); + val = edidsize + 1; + memcpy(&buf[CMDLEN_OFFSET], &val, 4); + buf[CMDBUF_OFFSET] = res; + memcpy(&buf[CMDBUF_OFFSET + 1], ediddata, edidsize); + + /* Send on socket */ + ret = clientsocket_send(buf, CMDBUF_OFFSET + val); + + LOGHDMILIB("%s end", __func__); + return ret; +} |