/* Copyright 2016 Castle Technology Ltd * * 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. */ /* edidsupport.c */ /* * EDID support. */ #include #include #include #include #include #include #include "kernel.h" #include "swis.h" #include "Global/RISCOS.h" #include "Global/GraphicsV.h" #include "Global/Services.h" #include "Global/VIDCList.h" #include "Interface/HighFSI.h" #include "edidmemory.h" #include "errors.h" #include "monitors.h" #include "modex.h" #include "edidsupport.h" #include "tables.h" /* By default we only use DMT timings; we will check for additional support later */ static int timing_support = EDID_USE_DMT; /* Check if a new mode is present already. * If it has, we won't be adding it to the chain. * Return 1 if successful, and 0 if we have rejected it for any reason */ static int add_proposed_mode(MonitorDescriptionRef monitor_definition, ModeDescriptionRef proposed_mode) { proposed_mode->next = monitor_definition->modelist; monitor_definition->modelist = proposed_mode; return 1; } #if DODEBUG /* Produce debug output showing the * mode descriptor being passed for a particular mode */ static void display_mode_parameters(ModeDescriptionRef mode_desc) { printf("XRes: %i YRes: %i ", mode_desc->definition.xres, mode_desc->definition.yres); if (mode_desc->definition.interlaced == 1) { printf("(interlaced)"); } else { printf("(non-interlaced)"); } printf(" PixRate: %i\nHSync:%i Hbpch:%i Hlbdr:%i Hdisp:%i Hrbdr:%i Hfpch:%i\nVSync:%i Vbpch:%i Vtbdr:%i Vdispl:%i Vbbdr:%i Vfpch:%i\n\n", mode_desc->definition.pixel_khz, mode_desc->definition.hpar[FR_SYNC], mode_desc->definition.hpar[FR_BPCH], mode_desc->definition.hpar[FR_BDR1], mode_desc->definition.hpar[FR_DISP], mode_desc->definition.hpar[FR_BDR2], mode_desc->definition.hpar[FR_FPCH], mode_desc->definition.vpar[FR_SYNC], mode_desc->definition.vpar[FR_BPCH], mode_desc->definition.vpar[FR_BDR1], mode_desc->definition.vpar[FR_DISP], mode_desc->definition.vpar[FR_BDR2], mode_desc->definition.vpar[FR_FPCH]); } #endif /* Copy a builtin mode and add it to the monitor description */ static _kernel_oserror *add_builtin_mode(MonitorDescriptionRef monitor, const ModeDefinition *mode, uint8_t priority) { ModeDescriptionRef mode_desc = (ModeDescriptionRef) malloc(sizeof(ModeDescription)); if (mode_desc == NULL) { return error(ERR_NOSPACE, 0, 0, 0); } mode_desc->definition = *mode; compute_modedescription(mode_desc); mode_desc->frame_hz = mode->freq; mode_desc->priority = priority; if (add_proposed_mode(monitor, mode_desc) == 0) { free(mode_desc); return NULL; } #if DODEBUG display_mode_parameters(mode_desc); #endif return NULL; } /* generate_dmt_mode creates a mode using standardised timing parameters. * These come from the Display Monitor Timing Document (VESA). * They are part of EDID 1.0 to 1.3 and should be used where a DMT * standard exists (refer Appendix B of EDID 1.4 spec and the DMT document). */ static _kernel_oserror *generate_dmt_mode(char dmt, MonitorDescriptionRef monitor, uint8_t priority) { /* If there is no established timing, ignore (for now). * Later we will generate a mode here and add */ if (dmt > 0) { dmt--; /* Convert DMT back to array number */ IFDEBUG printf("Established Modes Display Monitor Timing :\n"); return add_builtin_mode(monitor, &display_monitor_timings[dmt], priority); } #if DODEBUG else { printf("Ignoring established timing(not a standard mode).\n\n"); } #endif return 0; } static _kernel_oserror *Check_EDID_Checksum(EDIDBlockRef edidblock, bool checkall) { _kernel_oserror *res = NULL; size_t byte, block, blocks_to_check = 0; for (block = 0; block <= blocks_to_check; block++) { uint8_t count = 0; uint8_t *ediddata = (uint8_t *)&edidblock[block]; for (byte = 0; byte < sizeof(EDIDBlock); byte++) { count = count + ediddata[byte]; } if (count != 0) { char linebuff1[4]; char linebuff2[4]; sprintf(linebuff1, "%u", block); sprintf(linebuff2, "%u", blocks_to_check); res = error(ERR_BADCHECKSUM, linebuff1, linebuff2, 0); } else { if ((block == 0) && checkall) { blocks_to_check = edidblock->extension_block_count; } IFDEBUG printf("Checksum: Block %i of %i OK\n", block, blocks_to_check); } } return res; } /* Takes a block of text from an 18-byte EDID data block * And fills a supplied character buffer with a usable 0-terminated string * Buffer must be at least 14 bytes in length. */ static void get_dtd_ascii(EDIDBlockRef edidblockref, int block_number, char *data) { int i; for (i = 0; i < 13; ++i) { data[i] = edidblockref->data_block[block_number][i+5]; if (data[i] == 0x0A) { break; } else if ((data[i] < 32) || (data[i] == 127)) { /* Strip out any control characters for safety */ data[i] = ' '; } } data[i] = 0; } static void generate_mode_using_gtf(double h_pixels, double v_lines, double ip_freq_rqd, ModeDescriptionRef mode_desc, MonitorDescriptionRef monitor) { const int margins_rqd = 0; /* Set to 1 if margins are wanted. */ const double margin_per = 1.8; /* Percentage size of margin (0 to 100) if reqd */ double v_lines_rnd = 0; /* Number of desired visible lines rounded down to */ /* The nearest character cell */ double v_sync_rqd = 3; /* The width of the v sync in lines */ double h_sync_percent = 8; /* The width of the H sync as percentage of the total line period */ double cell_gran_rnd = 8; /* Character cell width in pixels. May be able to confirm this value in hardware but at present hardcode this to 8 (usual value), */ const int int_rqd = 0; /* int_rqd specifies whether the mode should be interlaced. Most modes used are not. */ double v_field_rate_reqd = 0; /* The actual vertical field rate after interlacing is taking into consideration */ double interlace = 0; /* If interlacing is used, this will be set to 0.5 */ const double min_vsync_and_bp = 550; /* The minimum time of vertical sync and back porch interval(us) */ const double min_porch = 1; /* Minimum front porch in lines (vertical) and character cells (horizontal) */ const double c = 40; /* The blanking formula offset */ const double m = 600; /* The blanking formula gradient */ const double k = 128; /* The blanking formula scaling factor */ const double j = 20; /* The blanking formula scaling factor weighting */ /* coefficient calculations. These should be constants in standard GTF */ double C = ((c-j) * k / 256) + j; double M = k / 256 * m; /* Find the refresh rate required (Hz) */ /* If interlaced, the number of vertical lines assumed by the * calculation must be halved, as the computation calculates the * number of vertical lines per field. In either case, the number * of lines is rounded down to the nearest integer. */ if (int_rqd == 1) { v_lines_rnd = round(v_lines / 2); v_field_rate_reqd = ip_freq_rqd * 2; interlace = 0.5; } else { v_lines_rnd = round(v_lines); v_field_rate_reqd = ip_freq_rqd; /* Interlace is automatically set to 0 */ } /* Round the number of pixels to the nearest character */ /* cell boundary */ double h_pixels_rnd = (round(h_pixels/cell_gran_rnd)) * cell_gran_rnd; /* Determine the width of the left and right borders */ double left_margin = 0; /* Number of pixels in the left hand margin, rounded */ /* down to the nearest character cell. If no margins */ /* required this is set to 0. */ double right_margin = 0; /* see left_margin */ double top_margin = 0; /* No of lines rounded down to nearest line. */ double bot_margin = 0; /* See top, left, right */ if (margins_rqd == 1) { left_margin = round(h_pixels_rnd * margin_per / 100 / cell_gran_rnd) *cell_gran_rnd; right_margin = left_margin; top_margin = round(margin_per / 100 * v_lines_rnd); bot_margin = top_margin; } /* For the calculations below, refer to the VESA GTF v1.1 spec */ /* Estimate the horizontal period */ double h_period_est = ((1 / v_field_rate_reqd) - min_vsync_and_bp/1000000) / (v_lines_rnd + (2 * top_margin) + min_porch + interlace) * 1000000; /* Find the number of lines in V sync + B Porch */ double vsync_and_bp = round(min_vsync_and_bp / h_period_est); /* Find number of lines in back porch alone */ double v_back_porch = vsync_and_bp - v_sync_rqd; /* Find the total number of lines in the vertical field period */ double total_v_lines = v_lines_rnd + top_margin + bot_margin + vsync_and_bp + interlace + min_porch; /* Estimate the vertical field frequency */ double v_field_rate_est = 1/h_period_est/total_v_lines * 1000000; /* Find the actual horizontal period */ double h_period = h_period_est / (v_field_rate_reqd / v_field_rate_est); /* Find the actual vertical field frequency */ double v_field_rate = 1/h_period/total_v_lines * 1000000; /* Find the vertical frame frequency */ if (int_rqd == 1) { v_field_rate = v_field_rate / 2; } /* GTF calculates margins using a percentage here. We use actual pixels */ /* (and 0 always at present) */ double total_active_pixels = h_pixels_rnd + left_margin + right_margin; /* Find the ideal blanking duty cycle from the blanking duty cycle equation */ double ideal_duty_cycle = C - (M * h_period / 1000); if (ideal_duty_cycle < 0) { IFDEBUG printf("Error - negative duty cycle\n"); /* If this happens we should just ignore the mode */ free(mode_desc); return; } /* Find the number of pixels in the blanking time to the nearest double */ /* character cell */ double h_blank_pixels = (round(total_active_pixels * ideal_duty_cycle / (100 - ideal_duty_cycle) / (2 * cell_gran_rnd))) * (2 * cell_gran_rnd); /* Find the total number of pixels */ double total_pixels = total_active_pixels + h_blank_pixels; /* Find pixel clock frequency */ double pixel_freq = total_pixels / h_period; /* Find horizontal frequency */ /* double h_freq = 1000 / h_period; */ /* From GTF spec - Using Stage 1 parameters to derive stage 2 parameters */ /* Find addressable lines per frame */ double addr_lines_per_frame; if (int_rqd == 1) { addr_lines_per_frame = v_lines_rnd * 2; } else { addr_lines_per_frame = v_lines_rnd; } /* Find the total number of lines in a frame: */ double total_lines_per_frame = v_lines_rnd + top_margin + bot_margin + vsync_and_bp + interlace + min_porch; if (int_rqd == 1) { total_lines_per_frame = total_lines_per_frame * 2; } /* Find the number of pixels in the horizontal sync period */ double h_sync_pixels = (round(h_sync_percent/100 * total_pixels / cell_gran_rnd)) * cell_gran_rnd; /* Find the number of pixels in the horizontal front porch period */ double h_front_porch = (h_blank_pixels / 2) - h_sync_pixels; double h_back_porch = h_front_porch + h_sync_pixels; /* Find the odd front porch period (lines) (36) */ double v_fporch = min_porch + interlace; mode_desc->definition.xres = (int)h_pixels; mode_desc->definition.yres = (int)v_lines; mode_desc->definition.hpar[FR_SYNC] = (int) h_sync_pixels; mode_desc->definition.hpar[FR_BPCH] = (int) h_back_porch; mode_desc->definition.hpar[FR_BDR1] = (int) left_margin; mode_desc->definition.hpar[FR_DISP] = (int) h_pixels; mode_desc->definition.hpar[FR_BDR2] = (int) right_margin; mode_desc->definition.hpar[FR_FPCH] = (int) h_front_porch; mode_desc->definition.vpar[FR_SYNC] = (int) v_sync_rqd; mode_desc->definition.vpar[FR_BPCH] = (int) v_back_porch; mode_desc->definition.vpar[FR_BDR1] = (int) top_margin; if (int_rqd == 1) { mode_desc->definition.vpar[FR_DISP] = (int) v_lines / 2; } else { mode_desc->definition.vpar[FR_DISP] = (int) v_lines; } mode_desc->definition.vpar[FR_BDR2] = (int) bot_margin; mode_desc->definition.vpar[FR_FPCH] = (int) v_fporch; mode_desc->definition.pixel_khz = (int) round(pixel_freq * 1000); mode_desc->definition.external_clock = -1; if (timing_support == EDID_USE_GTF) { mode_desc->definition.syncpol = HSync_Negative+VSync_Positive; /* Default GTF */ } else { mode_desc->definition.syncpol = HSync_Positive+VSync_Negative; /* Secondary GTF */ } mode_desc->definition.interlaced = int_rqd; sprintf(mode_desc->definition.name, "%d x %d", (int)h_pixels, (int)v_lines); compute_modedescription(mode_desc); if (add_proposed_mode(monitor, mode_desc) == 0) { free(mode_desc); } #if DODEBUG display_mode_parameters(mode_desc); #endif } static void generate_mode_using_cvt_rb(double h_pixels, double v_lines, double ip_freq_rqd, ModeDescriptionRef mode_desc, MonitorDescriptionRef monitor) { const int int_rqd = 0; /* int_rqd specifies whether the mode should be interlaced. Most modes used are not. */ const int margins_rqd = 0; /* Set to 1 if margins are wanted. */ const double margin_per = 1.8; /* Percentage size of margin(0 to 100) if reqd */ double cell_gran_rnd = 8; /* Character cell width in pixels. May be able to confirm this value in hardware but at present hardcode this to 8 (usual value), */ double v_field_rate_reqd = 0; /* The actual vertical field rate after interlacing is taking into consideration */ double v_lines_rnd = 0; /* Number of desired visible lines rounded down to the nearest character cell */ double interlace = 0; /* If interlacing is used, this will be set to 0.5 */ int v_sync_rnd = 0; /* Determine the aspect ratio and set the v_sync_rnd variable * This is a slightly messy lookup table - VESA recommends the lookup * approach. We match on X, but in some cases we need to also check Y if * there are other modes with different heights. */ if ((h_pixels == 640) || (h_pixels == 800) || (h_pixels == 1024) || (h_pixels == 1400) || (h_pixels == 1600) || (h_pixels == 1920) || (h_pixels == 2048) || (h_pixels == 2560) || (h_pixels == 3200) || (h_pixels == 3840)) { v_sync_rnd = 4; } if ((h_pixels == 1280) && ((v_lines == 1024) || (v_lines == 768))) { v_sync_rnd = 7; } if ((h_pixels == 848) || (h_pixels == 1064) || ((h_pixels == 1280) && (v_lines == 720)) || (h_pixels == 1360) || (h_pixels == 1704) || (h_pixels == 1864) || ((h_pixels == 1920) && (v_lines == 1080)) || (h_pixels == 2128) || (h_pixels == 2560) || (h_pixels == 2728) || (h_pixels == 3408) || (h_pixels == 4264)) { v_sync_rnd = 5; } if ((int) (h_pixels / v_lines * 10) == 16) { v_sync_rnd = 6; } if (v_sync_rnd == 0) { IFDEBUG printf("Error - Cannot handle this aspect ratio\n"); /* If this happens we should just ignore the mode */ free(mode_desc); return; } /* The variable names used below are those in the VESA Coordinated Timings * Standard. I've kept this code as close to the original as possible for * clarity at the cost of some speed. When this works 110%, this can be * optimised out. */ /* Find the refresh rate required (Hz) */ /* If interlaced, the number of vertical lines assumed by the * calculation must be halved, as the computation calculates the * number of vertical lines per field. In either case, the number * of lines is rounded down to the nearest integer. */ if (int_rqd == 1) { v_lines_rnd = floor(v_lines / 2); v_field_rate_reqd = ip_freq_rqd * 2; interlace = 0.5; } else { v_lines_rnd = floor(v_lines); v_field_rate_reqd = ip_freq_rqd; /* Interlace is automatically set to 0 */ } /* Round the number of pixels to the nearest character cell boundary */ double h_pixels_rnd = (floor(h_pixels/cell_gran_rnd)) * cell_gran_rnd; /* Determine the width of the left and right borders */ double left_margin = 0; /* Number of pixels in the left hand margin, rounded down to the nearest character cell. If no margins required this is set to 0. */ double right_margin = 0; /* see left_margin */ double top_margin = 0; /* No of lines rounded down to nearest line. */ double bot_margin = 0; /* See top, left, right */ if (margins_rqd == 1) { left_margin = floor(h_pixels_rnd * margin_per / 100 / cell_gran_rnd) *cell_gran_rnd; right_margin = left_margin; top_margin = floor(margin_per / 100 * v_lines_rnd); bot_margin = top_margin; } /* Minimum vertical back porch (used in both sets of calcs). */ const double min_v_bporch = 6; /* The total number of active pixels is equal to the rounded * horizontal pixels and the margins: */ double total_active_pixels = h_pixels_rnd + left_margin + right_margin; /* Pixel clock resolution - see CVT spec S3.2 */ const double clock_step = 0.25; /* Define back porch (used in our final block) */ int v_back_porch; /* Define actual pixel frequency (for final block) */ double act_pixel_freq; /* H blanking period (for final block) */ int h_blank; /* H sync (for final block) */ int h_sync; /* V front porch (NB in CVT this is min_v_porch_rnd and in CVT-RB this is * rb_v_fporch but as they are both constants declare them. */ double v_front_porch = 3; if (timing_support == EDID_USE_CVT) { /* Computation of "CRT" (ie non-RB) CVT timings */ /* Minimum timing for vertical blanking for 'CRT' timings */ const double min_vsync_bp = 550; /* Standard 'CRT' timing vertical front porch */ /* Estimate the horizontal period (kHz) */ double h_period_est = ((1 / v_field_rate_reqd) - min_vsync_bp / 1000000) / (v_lines_rnd + (2 * top_margin) + v_front_porch + interlace) * 1000000; /* Find the number of lines in V sync + back porch */ double v_sync_bp = floor(min_vsync_bp / h_period_est) + 1; if (v_sync_bp < v_sync_rnd + min_v_bporch) { v_sync_bp = v_sync_rnd + min_v_bporch; } /* Find the number of lines in V back porch */ v_back_porch = (int) (v_sync_bp - v_sync_rnd); /* Find total number of lines in vertical field period */ double total_v_lines = v_lines_rnd + top_margin + bot_margin + v_sync_bp + interlace + v_front_porch; /* C_PRIME = ((C-J) * K / 256 + J (from CVT spec) */ const double c_prime = 30; /* M_PRIME = K / 256 * M (from CVT spec) */ const double m_prime = 300; /* Find the ideal blanking duty cycle from the blanking duty cycle equation (%): */ double ideal_duty_cycle = c_prime - (m_prime * h_period_est / 1000); /* Find the number of pixels in the horizontal blanking time to the nearest double character cell * (limit horizontal blanking so that it is >= 20% of the horizontal total). */ if (ideal_duty_cycle < 20) { h_blank = (int) (floor(total_active_pixels * 20 / (100-20) / (2 * cell_gran_rnd)) * (2 * cell_gran_rnd)); } else { h_blank = (int) (floor(total_active_pixels * ideal_duty_cycle / (100 - ideal_duty_cycle) / (2 * cell_gran_rnd)) * (2 * cell_gran_rnd)); } /* Find the total number of pixels in a line */ double total_pixels = total_active_pixels + h_blank; /* Find pixel clock frequency (MHz): */ act_pixel_freq = clock_step * floor((total_pixels / h_period_est) / clock_step); /* Find actual horizontal frequency (kHz) */ double act_h_freq = 1000 * act_pixel_freq / total_pixels; /* Find actual field rate (Hz) */ double act_field_rate = 1000 * act_h_freq / total_v_lines; /* Find actual refresh rate (Hz) */ double act_frame_rate = act_field_rate; if (int_rqd == 1) { act_frame_rate = act_field_rate / 2; } /* H sync per is the percentage of horizontal total period that * defines horizontal sync width */ const double h_sync_per = 8; /* Calculate H sync */ h_sync = (int) (floor(h_sync_per / 100 * total_pixels / cell_gran_rnd) * cell_gran_rnd); } else { /* Computation of Reduced Blanking timing parameters. */ /* Estimate the horizontal period (kHz) */ const double rb_min_v_blank = 460; /* Min V blank for reduced timings */ double h_period_est = ((1000000/v_field_rate_reqd) - rb_min_v_blank) / (v_lines_rnd + top_margin + bot_margin); double vbi_lines = floor(rb_min_v_blank / h_period_est) + 1; /* Vertical front porch is fixed in reduced blanking */ v_front_porch = 3; double rb_min_vbi = v_front_porch + v_sync_rnd + min_v_bporch; double act_vbi_lines; /* Actual number of vertical blanking lines */ if (vbi_lines < rb_min_vbi) { act_vbi_lines = rb_min_vbi; } else { act_vbi_lines = vbi_lines; } double total_v_lines = act_vbi_lines + v_lines_rnd + top_margin + bot_margin + interlace; h_sync = 32; /* H sync is fixed in reduced blanking */ h_blank = 160; /* H Blank is fixed in reduced blanking */ /* Find total number of pixel clocks per line */ double total_pixels = h_blank + total_active_pixels; const double clock_step = 0.25; /* Calculate the pixel clock frequency to nearest 0.25MHz */ act_pixel_freq = clock_step * floor((v_field_rate_reqd * total_v_lines * total_pixels / 1000000) / clock_step); /* Find the number of lines in V_sync + back porch: */ v_back_porch = (int) (act_vbi_lines - v_front_porch - v_sync_rnd); } /* Calculate front and back porch */ int h_back_porch = h_blank / 2; int h_front_porch = h_blank - h_back_porch - h_sync; /* Now populate the mode definition block */ mode_desc->definition.xres = (int)h_pixels; mode_desc->definition.yres = (int)v_lines; mode_desc->definition.hpar[FR_SYNC] = h_sync; mode_desc->definition.hpar[FR_BPCH] = h_back_porch; mode_desc->definition.hpar[FR_BDR1] = (int) left_margin; mode_desc->definition.hpar[FR_DISP] = (int) h_pixels; mode_desc->definition.hpar[FR_BDR2] = (int) right_margin; mode_desc->definition.hpar[FR_FPCH] = h_front_porch; mode_desc->definition.vpar[FR_SYNC] = (int) v_sync_rnd; mode_desc->definition.vpar[FR_BPCH] = v_back_porch; mode_desc->definition.vpar[FR_BDR1] = (int) top_margin; if (int_rqd == 1) { mode_desc->definition.vpar[FR_DISP] = (int) v_lines / 2; } else { mode_desc->definition.vpar[FR_DISP] = (int) v_lines; } mode_desc->definition.vpar[FR_BDR2] = (int) bot_margin; mode_desc->definition.vpar[FR_FPCH] = (int) v_front_porch; mode_desc->definition.pixel_khz = (int) (act_pixel_freq * 1000); mode_desc->definition.external_clock = -1; if (timing_support == EDID_USE_CVT) { mode_desc->definition.syncpol = HSync_Negative+VSync_Positive; } else { /* EDID_USE_CVTRB */ mode_desc->definition.syncpol = HSync_Positive+VSync_Negative; } mode_desc->definition.interlaced = int_rqd; sprintf(mode_desc->definition.name, "%d x %d", (int)h_pixels, (int)v_lines); compute_modedescription(mode_desc); if (add_proposed_mode(monitor, mode_desc) == 0) { free(mode_desc); } #if DODEBUG display_mode_parameters(mode_desc); #endif } static _kernel_oserror *generate_standard_timing(char std1, char std2, MonitorDescriptionRef monitor) { ModeDescriptionRef mode_desc = NULL; /* If this mode is defined as a DMT standard, use this by checking the * bytes directly with the lookup table */ int i = 0; IFDEBUG printf("Standard Timing(%x %x) ", std1, std2); while (std_timings[i].stdcode[0] || std_timings[i].stdcode[1]) { if ((std1 == std_timings[i].stdcode[0]) && (std2 == std_timings[i].stdcode[1])) { mode_desc = (ModeDescriptionRef) malloc(sizeof(ModeDescription)); if (mode_desc == NULL) { return error(ERR_NOSPACE, 0, 0, 0); } mode_desc->definition = display_monitor_timings[std_timings[i].dmt-1]; compute_modedescription(mode_desc); mode_desc->frame_hz = display_monitor_timings[std_timings[i].dmt-1].freq; mode_desc->priority = 5; #if DODEBUG printf("(derived from DMT standard):\n"); display_mode_parameters(mode_desc); #endif if (add_proposed_mode(monitor, mode_desc) == 0) { free(mode_desc); } return NULL; } i++; } /* If it's not in the DMT lookups and we can generate a timing, do so */ if (timing_support != EDID_USE_DMT) { int yres = 0; int xres = (std1 + 31) * 8; char pixel_ratio_flags = (std2 & 0xC0); if (pixel_ratio_flags == 0) { /* 00 = 16:10 */ yres = (int) ((xres * 10) / 16); } if (pixel_ratio_flags == (1<<6)) { /* 01 = 4:3 */ yres = (int) ((xres * 3) / 4); } if (pixel_ratio_flags == (1<<7)) { /* 10 = 5:4 */ yres = (int) ((xres * 4) / 5); } if (pixel_ratio_flags == ((1<<7) + (1<<6))) { /* 11 = 16:9 */ yres = (int) ((xres * 9) / 16); } int freq = (std2 & 0x3f) + 60; mode_desc = (ModeDescriptionRef) malloc(sizeof(ModeDescription)); if (mode_desc == NULL) { return error(ERR_NOSPACE, 0, 0, 0); } IFDEBUG printf("(calculated):\n"); if ((timing_support == EDID_USE_GTF) || (timing_support == EDID_USE_GTF2)) { generate_mode_using_gtf(xres, yres, freq, mode_desc, monitor); } if ((timing_support == EDID_USE_CVT) || (timing_support == EDID_USE_CVTRB)) { generate_mode_using_cvt_rb(xres, yres, freq, mode_desc, monitor); } } return NULL; } /* Generates a mode from a 3-byte CVT code * NB this is UNTESTED as I haven't got an appropriate EDID to test from */ static _kernel_oserror *generate_cvt3_timing(char cvt1, char cvt2, char cvt3, MonitorDescriptionRef monitor) { ModeDescriptionRef mode_desc = NULL; /* If this mode is defined as a DMT standard, use this by checking the * bytes directly with the lookup table */ IFDEBUG printf("CVT 3-byte Timing(%x %x %x) ", cvt1, cvt2, cvt3); /* Check the reserved bits are 00. If not, let this mode fail silently */ if ((cvt2 & 0x03) != 0) { IFDEBUG printf("has unknown values in the reserved bits of byte 2 - skipped.\n"); return NULL; } #ifdef DERIVE_CVT3_FROM_DMT int i = 0; while (cvt_timings[i].cvtcode[0] || cvt_timings[i].cvtcode[1] || cvt_timings[i].cvtcode[2]) { if ((cvt1 == cvt_timings[i].cvtcode[0]) && (cvt2 == cvt_timings[i].cvtcode[1]) && (cvt3 == cvt_timings[i].cvtcode[2])) { mode_desc = (ModeDescriptionRef) malloc(sizeof(ModeDescription)); if (mode_desc == NULL) { return error(ERR_NOSPACE, 0, 0, 0); } mode_desc->definition = display_monitor_timings[cvt_timings[i].dmt-1]; compute_modedescription(mode_desc); mode_desc->frame_hz = display_monitor_timings[cvt_timings[i].dmt-1].freq; mode_desc->priority = 4; /* CVT timing */ #if DODEBUG printf("(derived from DMT standard):\n"); display_mode_parameters(mode_desc); #endif if (add_proposed_mode(monitor, mode_desc) == 0) { free(mode_desc); } return NULL; } i++; } #endif /* If it's not in the DMT lookups and we can generate a timing, do so */ if ((mode_desc == NULL) && (timing_support != EDID_USE_DMT)) { int vsize = cvt1 + ((cvt2 & 0xf0) << 4); int yres = (vsize + 1) * 2; int xres = 0; int freq = 0; /* Use the vertical line count to generate the horizontal addressable * Resolution (HAdd). Use 8 x {RoundDown[(VAdd x Aspect ratio) / 8]} * Per the EDID guidance (p48). */ char pixel_ratio_flags = (cvt2 & 0x0C) >> 2; switch (pixel_ratio_flags) { case 0: xres = (int) (8 * floor((((double) yres * 4) / 3) / 8)); break; case 1: xres = (int) (8 * floor((((double) yres * 16) / 9) / 8)); break; case 2: xres = (int) (8 * floor((((double) yres * 16) / 10) / 8)); break; case 3: xres = (int) (8 * floor((((double) yres * 15) / 9) / 8)); break; } /* First generate a descriptor using the display-preferred frequency * (bits 5 and 6 of cvt3) */ switch ((cvt3 & 0x60) >> 5) { case 0: freq = 50; break; case 1: freq = 60; break; case 2: freq = 75; break; case 3: freq = 85; break; } if (freq != 0) { mode_desc = (ModeDescriptionRef) malloc(sizeof(ModeDescription)); if (mode_desc == NULL) { return error(ERR_NOSPACE, 0, 0, 0); } IFDEBUG printf("\nAt %x Hz(calculated):\n", freq); generate_mode_using_cvt_rb(xres, yres, freq, mode_desc, monitor); if (add_proposed_mode(monitor, mode_desc) == 0) { free(mode_desc); } } int preferred_freq = freq; /* Now we add any other frequencies it supports */ /* Use timing_support to switch the timing method we use in the * CVT calculations and restore it at the end. */ int timing_support_preferred = timing_support; for (int supported_freq=0; supported_freq<5; supported_freq++) { if ((cvt3 & (1<definition.xres = dtd_data[2] + ((dtd_data[4] & 0xf0) << 4); mode_desc->definition.yres = dtd_data[5] + ((dtd_data[7] & 0xf0) << 4); mode_desc->definition.interlaced = ((dtd_data[17]>>7) & 1); mode_desc->definition.hpar[FR_SYNC] = dtd_data[9] + ((dtd_data[11] & 0x30) << 4); mode_desc->definition.hpar[FR_BDR1] = dtd_data[15]; mode_desc->definition.hpar[FR_DISP] = mode_desc->definition.xres; mode_desc->definition.hpar[FR_BDR2] = dtd_data[15]; mode_desc->definition.hpar[FR_FPCH] = dtd_data[8] + ((dtd_data[11] & 0xc0) << 2); mode_desc->definition.hpar[FR_BPCH] = dtd_data[3] + ((dtd_data[4] & 0x0f) << 8) - mode_desc->definition.hpar[FR_SYNC] - mode_desc->definition.hpar[FR_FPCH] - mode_desc->definition.hpar[FR_BDR1] - mode_desc->definition.hpar[FR_BDR2]; mode_desc->definition.vpar[FR_SYNC] = (dtd_data[10] & 0xf) + ((dtd_data[11] & 0x3) << 4); mode_desc->definition.vpar[FR_BDR1] = dtd_data[16]; mode_desc->definition.vpar[FR_DISP] = mode_desc->definition.yres; mode_desc->definition.vpar[FR_BDR2] = dtd_data[16]; mode_desc->definition.vpar[FR_FPCH] = (dtd_data[10] >> 4) + ((dtd_data[11] & 0xc) << 2); mode_desc->definition.vpar[FR_BPCH] = dtd_data[6] + ((dtd_data[7] & 0x0f) << 8) - mode_desc->definition.vpar[FR_SYNC] - mode_desc->definition.vpar[FR_FPCH] - mode_desc->definition.vpar[FR_BDR1] - mode_desc->definition.vpar[FR_BDR2]; mode_desc->definition.pixel_khz = (dtd_data[0] + (dtd_data[1]<<8)) * 10; mode_desc->definition.external_clock = -1; /* Only accept non-stereoscopic modes * Technically we should check for sync here (since we only really support * digital separate syncs), but for now we'll assume that misrepresenting * the sync isn't going to cause major problems, as it will allow more * modes to work (e.g. BenQ FP241W HDMI port advertises 1080p as being * serrated sync-on-RGB, even though the monitor is perfectly happy with * digital syncs) */ if ((dtd_data[17] & 0x60) != 0x00) { IFDEBUG printf("Rejecting DTD due to unsupported frame format\n"); return false; } /* -----00- is negative-negative, start with that and then invert if necessary */ mode_desc->definition.syncpol = HSync_Negative+VSync_Negative; if (dtd_data[17] & 4) { mode_desc->definition.syncpol ^= VSync_Negative^VSync_Positive; } if (dtd_data[17] & 2) { mode_desc->definition.syncpol ^= HSync_Negative^HSync_Positive; } /* If we are interlaced, we need to double the number of vertical pixels */ if (mode_desc->definition.interlaced == 1) { mode_desc->definition.yres = mode_desc->definition.yres * 2; } sprintf(mode_desc->definition.name, "%d x %d", mode_desc->definition.xres, mode_desc->definition.yres); compute_modedescription(mode_desc); #if DODEBUG display_mode_parameters(mode_desc); #endif return true; } /* Checks the 18-byte data blocks for their type. * The type returned is then either 10 (empty), 0 (detailed mode descriptor) * or other type numbers per the EDID specification */ static int get_extd_type(EDIDBlockRef edidblockref, int block_no) { if (edidblockref->data_block[block_no][0] + edidblockref->data_block[block_no][1] + edidblockref->data_block[block_no][2] == 0) { return edidblockref->data_block[block_no][3]; } return -1; /* -1 = display descriptor */ } /* Add to our list of audio formats */ static _kernel_oserror *add_audio_format(uint8_t byte1, uint8_t byte2, uint8_t byte3, MonitorDescriptionRef new_monitor) { AudioFormat newformat; newformat.format_code = (byte1 >> 3) & 0x7f; newformat.max_channels = (byte1 & 7) + 1; newformat.sample_rates = byte2; newformat.format_specific = byte3; /* Discard if bogus */ if ((!newformat.sample_rates) || ((newformat.format_code == 1) && !(newformat.format_specific))) { return NULL; } /* Work out where to merge it into our list * CEA 861-D doesn't state what we should do if multiple descriptors are * found for the same format code, so we should be conservative with how * we merge blocks together */ int i; for (i=0;iaudio_format_count;i++) { AudioFormat *candidate = &new_monitor->audio_formats[i]; if (candidate->format_code > newformat.format_code) { break; } else if (candidate->format_code < newformat.format_code) { continue; } /* Try and merge with this entry * note - currently not doing any fancy merging of the format-specific * byte (could be a bit tricky for LPCM depending on what future meaning * is given to the reserved bits) */ if (newformat.format_specific == candidate->format_specific) { if (newformat.max_channels == candidate->max_channels) { /* Merge our list of sample rates into the candidate and call * it a day. */ candidate->sample_rates |= newformat.sample_rates; return NULL; } else if (newformat.max_channels < candidate->max_channels) { /* Candidate supports more channels than us with same * format-specific settings, so ignore any sample rates which * the candidate supports. */ newformat.sample_rates &= ~candidate->sample_rates; if (!newformat.sample_rates) { return NULL; } } else if (newformat.max_channels > candidate->max_channels) { /* We support more channels than the candidate, so remove * sample rates from the candidate. */ candidate->sample_rates &= ~newformat.sample_rates; if (!candidate->sample_rates) { /* Candidate can be removed completely */ new_monitor->audio_format_count--; memmove(candidate, candidate+1, sizeof(AudioFormat)*(new_monitor->audio_format_count-i)); void *new = realloc(new_monitor->audio_formats, new_monitor->audio_format_count*sizeof(AudioFormat)); if (new || !new_monitor->audio_format_count) { new_monitor->audio_formats = (AudioFormat*) new; } /* Everything's been shuffled down, so process this entry * again. */ i--; continue; } } } } /* Need to insert a new entry */ void *new = realloc(new_monitor->audio_formats, (new_monitor->audio_format_count+1)*sizeof(AudioFormat)); if (!new) { return error(ERR_NOSPACE, 0, 0, 0); } new_monitor->audio_formats = (AudioFormat*) new; /* Shuffle following entries up */ memmove(new_monitor->audio_formats+i+1, new_monitor->audio_formats+i, sizeof(AudioFormat)*(new_monitor->audio_format_count-i)); new_monitor->audio_format_count++; new_monitor->audio_formats[i] = newformat; return NULL; } /* Process an audio data block from a CEA extension block */ static _kernel_oserror *process_cea_audio_data_block(EDIDExtensionBlockRef ext_block, int length, const uint8_t *block, MonitorDescriptionRef new_monitor) { while (length >= 3) { IFDEBUG printf("CEA Short Audio Descriptor %02x %02x %02x\n",block[0],block[1],block[2]); /* Ignore if any reserved bits in the first couple of bytes are set */ if (!(block[0] & 128) && !(block[1] & 128)) { _kernel_oserror *err = add_audio_format(block[0], block[1], block[2], new_monitor); if (err) { return err; } } block += 3; length -= 3; } return NULL; } /* Process a video data block from a CEA extension block */ static _kernel_oserror *process_cea_video_data_block(EDIDExtensionBlockRef ext_block, int length, const uint8_t *block, MonitorDescriptionRef new_monitor) { /* Parse SVDs and add to mode list */ while (length--) { uint8_t num = block[0] & 0x7f; IFDEBUG printf("CEA SVD %02x -> CEA mode %d%s\n", block[0], num, (block[0] & 128) ? " (native)" : ""); block++; if ((num == 0) || (num > MAXCEAMODENUM)) { continue; } /* Ignore modes that require pixel repetition (currently we have no way of indicating that requirement to the driver) */ const ModeDefinition *mode = &cea_modes[num-1]; if ((mode->xres != mode->hpar[FR_DISP]) || (mode->xres == 2880)) { continue; } _kernel_oserror *err = add_builtin_mode(new_monitor, mode, 3); /* SVDs should be considered the same priority as DTDs */ if (err) { return err; } } return NULL; } /* Process a speaker allocation block from a CEA extension block */ static _kernel_oserror *process_cea_speaker_allocation_data_block(EDIDExtensionBlockRef ext_block, int length, const uint8_t *block, MonitorDescriptionRef new_monitor) { if (length == 3) { IFDEBUG printf("CEA Speaker Allocation %02x %02x %02x\n",block[0],block[1],block[2]); new_monitor->speaker_mask = block[0] & 0x7f; new_monitor->speaker_mask_provided = true; } return NULL; } /* Process a data block from a CEA extension block */ static _kernel_oserror *process_cea_data_block(EDIDExtensionBlockRef ext_block, int tag_code, int length, const uint8_t *block, MonitorDescriptionRef new_monitor) { if (tag_code == 7) { /* Extended tag */ tag_code = *block++; length--; IFDEBUG printf("CEA extended data block code %d data length %d\n",tag_code,length); /* TODO: Process any interesting ones */ return NULL; } IFDEBUG printf("CEA data block code %d data length %d\n",tag_code,length); switch (tag_code) { case 1: return process_cea_audio_data_block(ext_block, length, block, new_monitor); case 2: return process_cea_video_data_block(ext_block, length, block, new_monitor); case 4: return process_cea_speaker_allocation_data_block(ext_block, length, block, new_monitor); default: return NULL; } } /* Process a CEA extension block */ static _kernel_oserror *process_cea_extension(EDIDExtensionBlockRef ext_block, MonitorDescriptionRef new_monitor) { /* Offset from the start of the extension block to the * first dtd in the extension block. Uses the offset from block+2. */ const uint8_t *extdata = (uint8_t *) ext_block; int dtd_offset = extdata[2]; if (dtd_offset > (126 - 18)) { /* Bogus DTD offset, ignore block */ return NULL; } bool basic_audio = (ext_block->revision >= 2) && (extdata[3] & 0x40); if (basic_audio) { IFDEBUG printf("Basic audio supported\n"); /* 2-ch LPCM at 32kHz, 44.1kHz, 48kHz * Assume 16/20/24bit supported (spec is a bit vague, but in reality it * shouldn't matter that much because they all get packaged the same) */ _kernel_oserror *err = add_audio_format(0x9, 0x7, 0x7, new_monitor); if (err) { return err; } } if ((ext_block->revision >= 3) && (dtd_offset > 4)) { /* CEA Data Block Collection present */ int block_offset = 4; while (block_offset < dtd_offset) { int tag_code = extdata[block_offset] >> 5; int length = extdata[block_offset] & 0x1f; block_offset++; if (!tag_code || !length || (length > (dtd_offset-block_offset))) { /* Bad block length or invalid tag code. TODO - Should probably throw away everything we've learnt so far from this extension block. */ return NULL; } _kernel_oserror *err = process_cea_data_block(ext_block, tag_code, length, extdata+block_offset, new_monitor); if (err) { return err; } block_offset += length; } } if (dtd_offset < 4) { /* No DTD's provided in this block */ return NULL; } while ((dtd_offset <= (126 - 18)) && (extdata[dtd_offset] != 0) && (extdata[dtd_offset+1] != 0)) { ModeDescriptionRef mp; mp = (ModeDescriptionRef) malloc(sizeof(ModeDescription)); if (mp == NULL) { return error(ERR_NOSPACE, 0, 0, 0); } if (!dtd_block_to_modedesc((char *) &(extdata[dtd_offset]), mp)) { free(mp); } else { mp->priority = 3; if (add_proposed_mode(new_monitor, mp) == 0) { free(mp); } } dtd_offset += 18; /* 18 is the size of a DTD block */ } return NULL; } /* Process a VTB extension block */ static _kernel_oserror *process_vtb_extension_block(EDIDExtensionBlockRef ext_block, MonitorDescriptionRef new_monitor) { _kernel_oserror *res = NULL; const uint8_t *extdata = (uint8_t *) ext_block; /* 'w', 'y' and 'z' originate from the VTB specification */ int w = extdata[2]; /* 'w' is the number of detailed timing blocks (DTB) */ int y = extdata[3]; /* 'y' is the number of CVT descriptions */ int z = extdata[4]; /* 'z' is the number of standard timing descriptions */ /* There's only space for 122 bytes of timing data. If the block looks like it needs more than that then it's a bad block. */ if (w*0x12 + y*0x3 + z*0x2 > 122) { return NULL; } /* Unused bytes should be zero */ for (int byte = 0x5 + w*0x12 + y*0x3 + z*0x2; byte < 127; byte++) { if (extdata[byte]) { return NULL; } } for (int dtb_blockno = 0; dtb_blockno < w; dtb_blockno++) { ModeDescriptionRef mp; mp = (ModeDescriptionRef) malloc(sizeof(ModeDescription)); if (mp == NULL) { return error(ERR_NOSPACE, 0, 0, 0); } if (!dtd_block_to_modedesc((char *) &(extdata[0x5 + dtb_blockno*0x12]), mp)) { free(mp); } else { mp->priority = 3; if (add_proposed_mode(new_monitor, mp) == 0) { free(mp); } } } for (int cvt_blockno = 0; cvt_blockno < y; cvt_blockno++) { ModeDescriptionRef mp; mp = (ModeDescriptionRef) malloc(sizeof(ModeDescription)); if (mp == NULL) { return error(ERR_NOSPACE, 0, 0, 0); } int cvt_byte = 0x5 + w*0x12 + cvt_blockno*0x3; if (!((extdata[cvt_byte] == 0) && (extdata[cvt_byte+1] == 0) && (extdata[cvt_byte+2] == 0))) { res = generate_cvt3_timing(extdata[cvt_byte], extdata[cvt_byte+1], extdata[cvt_byte+2], new_monitor); if (res) { return res; } } } for (int std_blockno = 0; std_blockno < z; std_blockno++) { ModeDescriptionRef mp; mp = (ModeDescriptionRef) malloc(sizeof(ModeDescription)); if (mp == NULL) { return error(ERR_NOSPACE, 0, 0, 0); } int std_byte = 0x5 + w*0x12 + y*0x3 + std_blockno*0x2; if (!((extdata[std_byte] == 0x1) && (extdata[std_byte+1] == 0x1))) { res = generate_standard_timing(extdata[std_byte], extdata[std_byte+1], new_monitor); if (res) { return res; } } } return NULL; } static _kernel_oserror *parseedid(char *ediddata, const char *file) { _kernel_oserror *res = NULL; FILE *f = NULL; MonitorDescriptionRef new_monitor; ModeDescriptionRef new_preferred_mode = NULL; /* No preferred mode */ EDIDBlockRef edidblockref = (EDIDBlockRef) ediddata; int RangeBlock = -1; /* edid block containing timimg limits */ /* OK, commit to reading a monitor description - go allocate space */ new_monitor = (MonitorDescriptionRef) malloc(sizeof(MonitorDescription)); if (new_monitor == NULL) { IFDEBUG printf("Can't allocate space for monitor list\n"); return error(ERR_NOSPACE, 0, 0, 0); } memset(new_monitor, 0, sizeof(MonitorDescription)); strcpy(new_monitor->name,"Display"); new_monitor->dpms_state = -1; /* DPMS -1 = Indicates not available */ new_monitor->lcd_support = 0; /* 'CRT by default' - what does this do???*/ new_monitor->output_format = -1; /* Output format */ new_monitor->external_clock = -1; /* External clock not present */ new_monitor->modelist = NULL; /* Make sure the mode list is initially nil. */ /* If we are EDID 1.4 compliant or above GTF and CVT support should be * present. Use CVT in preference, fallback to DMT only. */ timing_support = EDID_USE_DMT; /* Default at DMT */ if ((edidblockref->edid_version >= 1) && (edidblockref->edid_revision >= 4)) { IFDEBUG printf("Use CVT; fallback to DMT if unavailable\n"); timing_support = EDID_USE_CVT; } /* If we are EDID 1.4 compliant or above GTF support should be * present. Use GTF in preference, fallback to DMT only. */ if ((edidblockref->edid_version == 1) && ((edidblockref->edid_revision == 2) || (edidblockref->edid_revision == 3))) { IFDEBUG printf("Use GTF; fallback to DMT if unavailable\n"); timing_support = EDID_USE_GTF; } #if DODEBUG if (timing_support == EDID_USE_DMT) { printf("Use DMT only\n"); } else{ if ((edidblockref->feature_support & 1) == 1) { printf("This display supports continuous frequency modes(but we don't use this yet\n"); } else { printf("This display supports only non-continuous frequency modes - use only the resolutions defined\n"); } } #endif #ifdef FORCE_TIMINGS /* Now if we have set force_timings as a debug option, override the * timing_support setting for testing calculations */ timing_support = FORCE_TIMINGS; #endif /* Next do each of the 2-byte 'standard timings' (Priority 5). */ /* NB GTF support must be functional for these */ for (int std_timing_code=0; std_timing_code<8;std_timing_code++) { if (!((edidblockref->standard_timings[std_timing_code*2] == 0x01) && (edidblockref->standard_timings[std_timing_code*2+1] == 0x01))) { res = generate_standard_timing(edidblockref->standard_timings[std_timing_code*2], edidblockref->standard_timings[std_timing_code*2+1], new_monitor); if (res) { return res; } } } /* Sort the data blocks - store information strings and detailed * timing descriptors. Defer CVT codes and Established timings III * The first block is the 'preferred timing mode' and has highest * priority to go in the MDF. Any other 'detailed timing modes * have second priority so they will go in next. */ for (int blockno = 0; blockno < 4; blockno++) { switch (get_extd_type(edidblockref, blockno)) { case -1: /* Normal descriptor block */ { IFDEBUG printf("Data block\n"); ModeDescriptionRef mp; mp = (ModeDescriptionRef) malloc(sizeof(ModeDescription)); if (mp == NULL) { return error(ERR_NOSPACE, 0, 0, 0); } if (!dtd_block_to_modedesc((char *) &(edidblockref->data_block[blockno][0]), mp)) { free(mp); } else { IFDEBUG printf("Detailed Mode: %s, X: %i Y: %i\n", mp->definition.name,mp->definition.xres, mp->definition.yres); if (add_proposed_mode(new_monitor, mp) == 0) { free(mp); } else { /* The first block should define the preferred mode */ if (blockno == 0) { new_preferred_mode = mp; mp->priority = 0x01; } else { mp->priority = 0x02; } } } break; } case DATA_TYPE_RANGELIMITS: { IFDEBUG printf("Display range limits\n"); RangeBlock = blockno; /* remember */ /* Not 100% sure about this but think this is how we pick up CVT-RB support */ if (timing_support == EDID_USE_CVT) { char cvt_blanking = edidblockref->data_block[blockno][15]; if ((cvt_blanking & 0x10) == 0x10) { timing_support = EDID_USE_CVTRB; } } break; } case DATA_TYPE_MONITORNAME: { get_dtd_ascii(edidblockref, blockno, new_monitor->name); IFDEBUG printf("Model name: %s\n", new_monitor->name); break; } case DATA_TYPE_ESTTIMINGS3: { IFDEBUG printf("Established timings III"); for (int timings_byte=0; timings_byte<6; timings_byte++) { for (int timings_bit=0; timings_bit<8; timings_bit++) { if ((edidblockref->data_block[blockno][6+timings_byte] & (1<<(timings_bit%8))) == (1<<(timings_bit%8))) { generate_dmt_mode(established_timings3[(timings_byte * 8) + (7 - timings_bit)], new_monitor, 6); } } } break; } case DATA_TYPE_CVT3BYTE: { IFDEBUG printf("CVT 3 Byte timing codes, priority 4\n"); for (int cvt_byte = 6;cvt_byte < 18; cvt_byte+=3) { if (!((edidblockref->data_block[blockno][cvt_byte] == 0) && (edidblockref->data_block[blockno][cvt_byte+1] == 0) && (edidblockref->data_block[blockno][cvt_byte+2] == 0))) { res = generate_cvt3_timing( edidblockref->data_block[blockno][cvt_byte], edidblockref->data_block[blockno][cvt_byte+1], edidblockref->data_block[blockno][cvt_byte+2], new_monitor); if (res) { return res; } } } break; } #if DODEBUG case DATA_TYPE_MONITORSERIALNUM: { char serial_number[15]; get_dtd_ascii(edidblockref, blockno, serial_number); printf("Product serial number %s\n", serial_number); break; } case DATA_TYPE_COLOURPOINT: printf("Colour point data\n"); break; case DATA_TYPE_STDTIMING: printf("Standard timing identifications\n"); break; case DATA_TYPE_DCM: printf("Display colour management (DCM) data\n"); break; case DATA_TYPE_DUMMY: printf("Empty\n"); break; #endif } } #if DODEBUG printf("Timings bitfield 0 = %02x\n",edidblockref->established_timings[0]); printf("Timings bitfield 1 = %02x\n",edidblockref->established_timings[1]); printf("Timings bitfield 2 = %02x\n",edidblockref->established_timings[2]); #endif /* Add Established timings I and II (priority 6) */ for (int timings_byte=0; timings_byte<2; timings_byte++) { for (int timings_bit=0; timings_bit<8; timings_bit++) { if ((edidblockref->established_timings[timings_byte] & (1<<(timings_bit%8))) == (1<<(timings_bit%8))) { IFDEBUG printf("Timings bitfield byte %d bit %d:\n", timings_byte, timings_bit); int dmt_mode = established_timings[(timings_byte * 8)+(7 - timings_bit)]; if (dmt_mode > 0) { generate_dmt_mode(dmt_mode, new_monitor, 6); } else { /* Established timings for 720x400 @ 70Hz, derived from: * http://www.javiervalcarce.eu/html/vga-signal-format-timming-specs-en.html * http://www.epanorama.net/documents/pc/vga_timing.html */ static const ModeDefinition established_timing_7 = { /* 720 x 400 @ 70Hz */ "720 x 400", 720, 400, 108, 54, 0, 720, 0, 18, 2, 34, 0, 400, 0, 13, 28322, -1, NP, 0, 70, 0 }; /* There are 3 cases which do not have defined DMT codes. * These are byte 0 bits 7, 6 and 4 which we will calculate. */ int xres = 0; int yres = 0; int freq = 0; switch (timings_bit | (timings_byte << 3)) { case 7: /* 720 X 400 @ 70Hz */ /* Use some fixed timings for better compatibility */ add_builtin_mode(new_monitor, &established_timing_7, 6); break; case 6: /* 720 X 400 @ 88Hz */ xres = 720; yres = 400; freq = 88; break; case 4: /* 640 X 480 @ 67Hz */ xres = 640; yres = 480; freq = 67; break; } if (xres > 0) { ModeDescriptionRef mp; mp = (ModeDescriptionRef) malloc(sizeof(ModeDescription)); if (mp == NULL) { return error(ERR_NOSPACE, 0, 0, 0); } if ((timing_support == EDID_USE_GTF) || (timing_support == EDID_USE_GTF2)) { generate_mode_using_gtf(xres, yres, freq, mp, new_monitor); } if ((timing_support == EDID_USE_CVT) || (timing_support == EDID_USE_CVTRB)) { generate_mode_using_cvt_rb(xres, yres, freq, mp, new_monitor); } } } } } } /* Now check extension blocks for modes. Detailed timing modes found here have 3rd priority */ if (edidblockref->extension_block_count > 0) { IFDEBUG printf("%i Extension block(s) found\n", edidblockref->extension_block_count); for (int ext_block_id = 1; ext_block_id <= edidblockref->extension_block_count; ext_block_id++) { EDIDExtensionBlockRef ext_block = (EDIDExtensionBlockRef)&ediddata[sizeof(EDIDBlock) * ext_block_id]; /* Different block types to be handled here */ /* CEA Extension - uses Tag 0x02 */ if (ext_block->tag == 0x02) { IFDEBUG printf("Block %i: CEA Extension block found\n", ext_block_id); IFDEBUG printf("Version %i\n", ext_block->revision); if (ext_block->revision != 0) /* Revision 0 doesn't exist. All othe revisions(including future ones we don't know about) should be at least partially parseable. */ { res = process_cea_extension(ext_block, new_monitor); if (res) { return res; } } } /* VTB Extension block - Uses Tag 0x10, revision 0x01 */ if ((ext_block->tag == 0x10) && (ext_block->revision == 0x01)) { res = process_vtb_extension_block(ext_block, new_monitor); if (res) { return res; } } } } /* Then sort the modes */ if (new_monitor->modelist == NULL) { return error(ERR_NOMODES, "(EDID)", 0, 0); } else { sort_modelist(&new_monitor->modelist); } IFDEBUG printf("Modes sorted\n"); if (file) { /* open file for new mode file if required */ /* file is a string terminated in 0x0D */ char *ptr,*fbuf; int length = strcspn(file,"\r"); fbuf = malloc(length+MAXMONITORNAME+2); /* get around the CR terminated file name */ if (fbuf) { memcpy(fbuf,file,length); fbuf[length] = '.'; strcpy(fbuf+length+1,new_monitor->name); while (ptr=strpbrk(fbuf+length+1," \"#$%&*.:@\\^|\x7f<>"),ptr) { *ptr='_'; } f = fopen(fbuf,"w"); free(fbuf); } if (!f) { return _kernel_last_oserror(); } } if (f) { fprintf(f,"# Monitor description file for %s\n",new_monitor->name); fprintf(f,"# (EDID specified modes only, no calculated modes)\n\n"); fprintf(f,"# Max Viewable H %d cm \n",edidblockref->horizontal_screen_size); fprintf(f,"# Max Viewable V %d cm\n",edidblockref->vertical_screen_size); if (RangeBlock != -1) { uint8_t flags = edidblockref->data_block[RangeBlock][4]; int vmin = 0, vmax = 0, hmin = 0, hmax = 0, pixmax = 0; if ((flags & 0x3) == 0x2) { vmax = 255;} if ((flags & 0x3) == 0x2) { vmax = 255; vmin = 255;} if ((flags & 0xc) == 0x8) { hmax = 255;} if ((flags & 0xc) == 0xc) { hmax = 255; hmin = 255;} vmin += edidblockref->data_block[RangeBlock][5]; vmax += edidblockref->data_block[RangeBlock][6]; hmin += edidblockref->data_block[RangeBlock][7]; hmax += edidblockref->data_block[RangeBlock][8]; pixmax = edidblockref->data_block[RangeBlock][9]; fprintf(f,"# Line rate: %2d - %2dkHz\n",hmin,hmax); fprintf(f,"# Frame rate: %2d - %2dHz\n",vmin,vmax); fprintf(f,"# Max Dot rate: %3dMHz(rounded down)\n",pixmax*10); } fprintf(f,"# Uses %s frequency pixel clocks\n", ((edidblockref->feature_support & 1) == 1)?"Continuous":"Specific"); char *rules="unknown"; switch (timing_support) { case EDID_USE_CVT:rules="CVT";break; case EDID_USE_DMT:rules="DMT";break; case EDID_USE_CVTRB:rules="CVTRB";break; case EDID_USE_GTF:rules="GTF";break; case EDID_USE_GTF2:rules="GTF2";break; } fprintf(f,"# Use %s timing rules\n#\n",rules); fprintf(f,"file_format:1\nmonitor_title:%s\n",new_monitor->name); if (new_monitor->dpms_state!=-1) { fprintf(f,"DPMS_state:%d\n",new_monitor->dpms_state); } ModeDescriptionRef this = new_monitor->modelist; do { fprintf(f,"\n# Mode: %d x %d @ %dHz",this->definition.xres,this->definition.yres,this->frame_hz); fprintf(f,"\n# Bounds: H %3.2fkHz, V %3.2f, DClock %3.2fMHz",((float)(this->line_hz))/1000.0f,((float)(this->frame_mhz))/1000.0f,((float)(this->definition.pixel_khz))/1000.0f); fprintf(f,"\nstartmode"); fprintf(f,"\n mode_name:%d x %d",this->definition.xres,this->definition.yres); fprintf(f,"\n x_res:%d",this->definition.xres); fprintf(f,"\n y_res:%d",this->definition.yres); fprintf(f,"\n pixel_rate:%d",this->definition.pixel_khz); fprintf(f,"\n h_timings:%d,%d,%d,%d,%d,%d",this->definition.hpar[0],this->definition.hpar[1],this->definition.hpar[2],this->definition.hpar[3],this->definition.hpar[4],this->definition.hpar[5]); fprintf(f,"\n v_timings:%d,%d,%d,%d,%d,%d",this->definition.vpar[0],this->definition.vpar[1],this->definition.vpar[2],this->definition.vpar[3],this->definition.vpar[4],this->definition.vpar[5]); fprintf(f,"\n sync_pol:%d",this->definition.syncpol); if (this->definition.interlaced == 1) { fprintf(f,"\n interlaced"); } fprintf(f,"\nEndMode\n"); this = this->next; /* will be NULL at list end*/ } while (this); /* now fo hex dump of EDID block */ fprintf(f,"# EDID block dump\n#\n"); for (int i=0; i<(edidblockref->extension_block_count+1)*0x80;i+=16) { fprintf(f,"# %02x %02x %02x %02x %02x %02x %02x %02x", ediddata[i],ediddata[i+1],ediddata[i+2],ediddata[i+3], ediddata[i+4],ediddata[i+5],ediddata[i+6],ediddata[i+7]); fprintf(f," %02x %02x %02x %02x %02x %02x %02x %02x\n", ediddata[i+8],ediddata[i+9],ediddata[i+10],ediddata[i+11], ediddata[i+12],ediddata[i+13],ediddata[i+14],ediddata[i+15]); } fprintf(f,"#\n#End\n"); fclose(f); } else { /* Below taken from the loadmodefile code * If we haven't got a file loaded at present, then * read current monitortype, to restore on module shutdown */ if (old_monitortype == -1) { old_monitortype = read_monitortype(); } res = set_monitortype(MONITOR_FILE); if (res != NULL) { _kernel_oserror *res2; IFDEBUG printf("setting of monitor type to type `FILE' failed\n"); res2 = restore_monitortype(); /* restore old value */ if (res2 != NULL) { IFDEBUG printf("couldn't reset monitor type to CMOS default!\n"); } } else { _kernel_swi_regs regs; release_currentmonitor(); current_monitor = new_monitor; /* Set up the mode specifier */ if (preferred_mode && new_preferred_mode) { preferred_mode->bit0 = 1; preferred_mode->format = 0; preferred_mode->xresol = new_preferred_mode->definition.xres; preferred_mode->yresol = new_preferred_mode->definition.yres; preferred_mode->depth = 5; preferred_mode->framerate = new_preferred_mode->frame_hz; preferred_mode->param[0].index = -1; } else if (preferred_mode) { preferred_mode->bit0 = 0; } /* Set the preferred sync type from video input definition bit 3. */ /* (ref EDID spec table 3.11) */ if ((edidblockref->video_input_definition & 0x8) == 0x8) { preferred_sync_type = 0; } else { preferred_sync_type = 1; } IFDEBUG printf("Monitor type changed\n"); /* Newly defined monitor, announce it */ regs.r[1] = Service_ModeFileChanged; _kernel_swi(OS_ServiceCall, ®s, ®s); IFDEBUG printf("Service_ModeFileChanged issued\n"); } } return res; } static _kernel_oserror *readedidblock(int displaynum, EDIDBlockRef edidblock, int offset, int count) { _kernel_oserror *res; int iic_code = (0xa1 << 16) | (0x80*offset << 0); int op_code = (displaynum << 24) | (0 << 16) | (GraphicsV_IICOp << 0); res = _swix(OS_CallAVector, _INR(0,2) | _IN(4) | _IN(9) | _OUT(0) | _OUT(4), iic_code, edidblock+offset, 0x80*count, op_code, GraphicsV, &iic_code, &op_code); /* If GraphicsV 14 was not claimed, R4 (op_code) should return * unchanged in which case we need to alert the user that the * hardware doesn't like EDID :-( */ if (iic_code != 0) { res = error(ERR_IICOPFAIL, 0, 0, 0); } /* An 'EDID read not supported' error will trump an IIC failure. */ if (op_code != 0) { res = error(ERR_CANTREADEDID, 0, 0, 0); } return res; } _kernel_oserror *loadedid(const char *file) { _kernel_oserror *res; /* Load the EDID data into a data block. */ /* Disable automatic EDID reading when an EDID file is in use */ EDIDEnabled = 0; int file_length, object_found; res = _swix(OS_File, _INR(0,1) | _OUT(0) | _OUT(4), OSFile_ReadWithTypeNoPath, file, &object_found, &file_length); if (!res && (object_found == object_file)) { char *edidblock = (char *) malloc(file_length + 4); if (edidblock == NULL) { return error(ERR_NOSPACE, 0, 0, 0); } res = _swix(OS_File, _INR(0,3), OSFile_Load, file, edidblock, 0); /* Check the block is valid */ if (!res) { res = Check_EDID_Checksum((EDIDBlockRef)edidblock, true); } if (!res) { res = parseedid(edidblock,NULL); } free(edidblock); } return res; } _kernel_oserror *readedid(int displaynum, const char *file) { _kernel_oserror *res; EDIDBlockRef edidblock = (EDIDBlockRef)malloc(sizeof(EDIDBlock)); if (edidblock == NULL) { return error(ERR_NOSPACE, 0, 0, 0); } IFDEBUG printf("ReadEDID called\n"); res = readedidblock(displaynum, edidblock, 0, 1); /* Check the block is valid */ if (!res) { res = Check_EDID_Checksum(edidblock, false); } /* Now use a separate pointer to the block for extending it and * setting up extensions (because if it fails we use the original * pointer to free up the memory block and exit). */ char *ediddata = (char *)edidblock; /* If it is, resize to accomodate extension blocks */ if (!res) { IFDEBUG printf("%i EDID extension blocks found\n", edidblock->extension_block_count); if (edidblock->extension_block_count > 0) { ediddata = realloc(edidblock, sizeof(EDIDBlock) * (edidblock->extension_block_count + 1)); if (ediddata == NULL) { res = error(ERR_NOSPACE, 0, 0, 0); } else { edidblock = (EDIDBlockRef) ediddata; res = readedidblock(displaynum, edidblock, 1, edidblock->extension_block_count); /* Check the block is valid */ if (!res) { res = Check_EDID_Checksum(edidblock, true); } } } } if (!res) { IFDEBUG printf("Parsing EDID block\n"); EDIDEnabled = 1; res = parseedid(ediddata,file); } free(edidblock); return res; } /* EOF edidsupport.c */