diff options
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | calc-gtf-cvt.cpp | 238 | ||||
-rw-r--r-- | edid-decode.1 | 86 | ||||
-rw-r--r-- | edid-decode.cpp | 471 | ||||
-rw-r--r-- | edid-decode.h | 47 | ||||
-rw-r--r-- | parse-base-block.cpp | 438 | ||||
-rw-r--r-- | parse-cta-block.cpp | 26 | ||||
-rw-r--r-- | parse-displayid-block.cpp | 26 | ||||
-rw-r--r-- | parse-vtb-ext-block.cpp | 14 |
9 files changed, 916 insertions, 432 deletions
@@ -5,7 +5,7 @@ EMXX ?= em++ SOURCES = edid-decode.cpp parse-base-block.cpp parse-cta-block.cpp \ parse-displayid-block.cpp parse-ls-ext-block.cpp \ - parse-di-ext-block.cpp parse-vtb-ext-block.cpp + parse-di-ext-block.cpp parse-vtb-ext-block.cpp calc-gtf-cvt.cpp WARN_FLAGS = -Wall -Wextra -Wno-missing-field-initializers -Wno-unused-parameter all: edid-decode diff --git a/calc-gtf-cvt.cpp b/calc-gtf-cvt.cpp new file mode 100644 index 0000000..b9d62b2 --- /dev/null +++ b/calc-gtf-cvt.cpp @@ -0,0 +1,238 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright 2006-2012 Red Hat, Inc. + * Copyright 2018-2021 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + * + * Author: Adam Jackson <ajax@nwnk.net> + * Maintainer: Hans Verkuil <hverkuil-cisco@xs4all.nl> + */ + +#include <stdio.h> +#include <stdlib.h> +#include <math.h> +#include <time.h> + +#include "edid-decode.h" + +#define CELL_GRAN 8.0 +#define MARGIN_PERC 1.8 +#define GTF_MIN_PORCH 1.0 +#define GTF_V_SYNC_RQD 3.0 +#define GTF_H_SYNC_PERC 8.0 +#define GTF_MIN_VSYNC_BP 550.0 + +timings edid_state::calc_gtf_mode(unsigned h_pixels, unsigned v_lines, + double ip_freq_rqd, bool int_rqd, + enum gtf_ip_parm ip_parm, bool margins_rqd, + bool secondary, double C, double M, double K, double J) +{ + timings t = {}; + /* C' and M' are part of the Blanking Duty Cycle computation */ + double C_PRIME = ((C - J) * K / 256.0) + J; + double M_PRIME = K / 256.0 * M; + + double h_pixels_rnd = round(h_pixels / CELL_GRAN) * CELL_GRAN; + double v_lines_rnd = int_rqd ? round(v_lines / 2.0) : v_lines; + unsigned hor_margin = margins_rqd ? + round(h_pixels_rnd * MARGIN_PERC / 100.0 / CELL_GRAN) * CELL_GRAN : 0; + unsigned vert_margin = margins_rqd ? round(MARGIN_PERC / 100.0 * v_lines_rnd) : 0; + double interlace = int_rqd ? 0.5 : 0; + double total_active_pixels = h_pixels_rnd + hor_margin * 2; + + t.hact = h_pixels_rnd; + t.vact = v_lines; + t.interlaced = int_rqd; + + double pixel_freq; + double h_blank_pixels; + double total_pixels; + double v_sync_bp; + + if (ip_parm == gtf_ip_vert_freq) { + // vertical frame frequency (Hz) + double v_field_rate_rqd = int_rqd ? ip_freq_rqd * 2 : ip_freq_rqd; + double h_period_est = ((1.0 / v_field_rate_rqd) - GTF_MIN_VSYNC_BP / 1000000.0) / + (v_lines_rnd + vert_margin * 2 + GTF_MIN_PORCH + interlace) * 1000000.0; + v_sync_bp = round(GTF_MIN_VSYNC_BP / h_period_est); + double total_v_lines = v_lines_rnd + vert_margin * 2 + + v_sync_bp + interlace + GTF_MIN_PORCH; + double v_field_rate_est = 1.0 / h_period_est / total_v_lines * 1000000.0; + double h_period = h_period_est / (v_field_rate_rqd / v_field_rate_est); + double ideal_duty_cycle = C_PRIME - (M_PRIME * h_period / 1000.0); + h_blank_pixels = round(total_active_pixels * ideal_duty_cycle / + (100.0 - ideal_duty_cycle) / + (2 * CELL_GRAN)) * 2 * CELL_GRAN; + total_pixels = total_active_pixels + h_blank_pixels; + pixel_freq = total_pixels / h_period; + } else if (ip_parm == gtf_ip_hor_freq) { + // horizontal frequency (kHz) + double h_freq = ip_freq_rqd; + v_sync_bp = round(GTF_MIN_VSYNC_BP * h_freq / 1000.0); + double ideal_duty_cycle = C_PRIME - (M_PRIME / h_freq); + h_blank_pixels = round(total_active_pixels * ideal_duty_cycle / + (100.0 - ideal_duty_cycle) / + (2 * CELL_GRAN)) * 2 * CELL_GRAN; + total_pixels = total_active_pixels + h_blank_pixels; + pixel_freq = total_pixels * h_freq / 1000.0; + } else { + // pixel clock rate (MHz) + pixel_freq = ip_freq_rqd; + double ideal_h_period = + ((C_PRIME - 100.0) + + sqrt(((100.0 - C_PRIME) * (100.0 - C_PRIME) + + (0.4 * M_PRIME * (total_active_pixels + hor_margin * 2) / + pixel_freq)))) / 2.0 / M_PRIME * 1000.0; + double ideal_duty_cycle = C_PRIME - (M_PRIME * ideal_h_period) / 1000.0; + h_blank_pixels = round(total_active_pixels * ideal_duty_cycle / + (100.0 - ideal_duty_cycle) / + (2 * CELL_GRAN)) * 2 * CELL_GRAN; + total_pixels = total_active_pixels + h_blank_pixels; + double h_freq = pixel_freq / total_pixels * 1000.0; + v_sync_bp = round(GTF_MIN_VSYNC_BP * h_freq / 1000.0); + } + + double v_back_porch = v_sync_bp - GTF_V_SYNC_RQD; + + t.vbp = v_back_porch; + t.vsync = GTF_V_SYNC_RQD; + t.vfp = GTF_MIN_PORCH; + t.pixclk_khz = round(1000.0 * pixel_freq); + t.hsync = round(GTF_H_SYNC_PERC / 100.0 * total_pixels / CELL_GRAN) * CELL_GRAN; + t.hfp = (h_blank_pixels / 2.0) - t.hsync; + t.hbp = t.hfp + t.hsync; + t.hborder = hor_margin; + t.vborder = vert_margin; + t.pos_pol_hsync = secondary; + t.pos_pol_vsync = !secondary; + t.rb = secondary ? RB_GTF : 0; + return t; +} + +void edid_state::edid_gtf_mode(unsigned refresh, struct timings &t) +{ + unsigned hratio = t.hratio; + unsigned vratio = t.vratio; + t = calc_gtf_mode(t.hact, t.vact, refresh, t.interlaced); + t.hratio = hratio; + t.vratio = vratio; +} + +#define CVT_MIN_VSYNC_BP 550.0 +#define CVT_MIN_V_PORCH 3 +#define CVT_MIN_V_BPORCH 6 +#define CVT_C_PRIME 30.0 +#define CVT_M_PRIME 300.0 +#define CVT_RB_MIN_VBLANK 460.0 + +// If rb == RB_CVT_V2, then alt means video-optimized (i.e. 59.94 instead of 60 Hz, etc.). +// If rb == RB_CVT_V3, then alt means that rb_h_blank is 160 instead of 80. +// Note: for RB_CVT_V3 this calculation is slightly different, but +// since CVT 1.3 is not yet public, I cannot update the calculation yet. For now +// it will follow V2. So RBv3 timings will be off for now. +timings edid_state::calc_cvt_mode(unsigned h_pixels, unsigned v_lines, + double ip_freq_rqd, unsigned rb, bool int_rqd, + bool margins_rqd, bool alt) +{ + timings t = {}; + + t.hact = h_pixels; + t.vact = v_lines; + t.interlaced = int_rqd; + + double cell_gran = rb == RB_CVT_V2 ? 1 : CELL_GRAN; + double h_pixels_rnd = floor(h_pixels / cell_gran) * cell_gran; + double v_lines_rnd = int_rqd ? floor(v_lines / 2.0) : v_lines; + unsigned hor_margin = margins_rqd ? + floor((h_pixels_rnd * MARGIN_PERC / 100.0) / cell_gran) * cell_gran : 0; + unsigned vert_margin = margins_rqd ? floor(MARGIN_PERC / 100.0 * v_lines_rnd) : 0; + double interlace = int_rqd ? 0.5 : 0; + double total_active_pixels = h_pixels_rnd + hor_margin * 2; + double v_field_rate_rqd = int_rqd ? ip_freq_rqd * 2 : ip_freq_rqd; + double clock_step = rb == RB_CVT_V2 ? 0.001 : 0.25; + double h_blank = (rb == RB_CVT_V1 || (rb == RB_CVT_V3 && alt)) ? 160 : 80; + double rb_v_fporch = rb == RB_CVT_V1 ? 3 : 1; + double refresh_multiplier = (rb == RB_CVT_V2 && alt) ? 1000.0 / 1001.0 : 1; + double h_sync = 32; + + double v_sync; + double pixel_freq; + double v_blank; + double v_sync_bp; + + /* Determine VSync Width from aspect ratio */ + if ((t.vact * 4 / 3) == t.hact) + v_sync = 4; + else if ((t.vact * 16 / 9) == t.hact) + v_sync = 5; + else if ((t.vact * 16 / 10) == t.hact) + v_sync = 6; + else if (!(t.vact % 4) && ((t.vact * 5 / 4) == t.hact)) + v_sync = 7; + else if ((t.vact * 15 / 9) == t.hact) + v_sync = 7; + else /* Custom */ + v_sync = 10; + + if (rb >= RB_CVT_V2) + v_sync = 8; + + if (rb == 0) { + double h_period_est = ((1.0 / v_field_rate_rqd) - CVT_MIN_VSYNC_BP / 1000000.0) / + (v_lines_rnd + vert_margin * 2 + CVT_MIN_V_PORCH + interlace) * 1000000.0; + v_sync_bp = floor(CVT_MIN_VSYNC_BP / h_period_est) + 1; + if (v_sync_bp < v_sync + CVT_MIN_V_BPORCH) + v_sync_bp = v_sync + CVT_MIN_V_BPORCH; + v_blank = v_sync_bp + CVT_MIN_V_PORCH; + double ideal_duty_cycle = CVT_C_PRIME - (CVT_M_PRIME * h_period_est / 1000.0); + if (ideal_duty_cycle < 20) + ideal_duty_cycle = 20; + h_blank = floor(total_active_pixels * ideal_duty_cycle / + (100.0 - ideal_duty_cycle) / + (2 * CELL_GRAN)) * 2 * CELL_GRAN; + double total_pixels = total_active_pixels + h_blank; + h_sync = floor(total_pixels * 0.08 / CELL_GRAN) * CELL_GRAN; + pixel_freq = floor((total_pixels / h_period_est) / clock_step) * clock_step; + } else { + double h_period_est = ((1000000.0 / v_field_rate_rqd) - CVT_RB_MIN_VBLANK) / + (v_lines_rnd + vert_margin * 2); + double vbi_lines = floor(CVT_RB_MIN_VBLANK / h_period_est) + 1; + double rb_min_vbi = rb_v_fporch + v_sync + CVT_MIN_V_BPORCH; + v_blank = vbi_lines < rb_min_vbi ? rb_min_vbi : vbi_lines; + double total_v_lines = v_blank + v_lines_rnd + vert_margin * 2 + interlace; + if (rb == RB_CVT_V1) + v_sync_bp = v_blank - rb_v_fporch; + else + v_sync_bp = v_sync + CVT_MIN_V_BPORCH; + double total_pixels = h_blank + total_active_pixels; + pixel_freq = floor((v_field_rate_rqd * total_v_lines * total_pixels / 1000000.0 * + refresh_multiplier) / clock_step) * clock_step; + } + + t.vbp = v_sync_bp - v_sync; + t.vsync = v_sync; + t.vfp = v_blank - t.vbp - t.vsync; + t.pixclk_khz = round(1000.0 * pixel_freq); + t.hsync = h_sync; + t.hfp = (h_blank / 2.0) - t.hsync; + t.hbp = t.hfp + t.hsync; + t.hborder = hor_margin; + t.vborder = vert_margin; + t.rb = rb; + if (alt && (rb == RB_CVT_V2 || rb == RB_CVT_V3)) + t.rb |= RB_FLAG; + t.pos_pol_hsync = t.rb; + t.pos_pol_vsync = !t.rb; + calc_ratio(&t); + return t; +} + +void edid_state::edid_cvt_mode(unsigned refresh, struct timings &t) +{ + unsigned hratio = t.hratio; + unsigned vratio = t.vratio; + + t = calc_cvt_mode(t.hact, t.vact, refresh, t.rb & ~RB_FLAG, t.interlaced, + false, t.rb & RB_FLAG); + t.hratio = hratio; + t.vratio = vratio; +} diff --git a/edid-decode.1 b/edid-decode.1 index 58e81bf..e7c86bf 100644 --- a/edid-decode.1 +++ b/edid-decode.1 @@ -154,9 +154,9 @@ GTF 1.1: VESA Generalized Timing Formula Standard, Version: 1.1 \fB\-h\fR, \fB\-\-help\fR Prints the help message. .TP -\fB\-o\fR, \fB\-\-output\-format\fR=\fI<fmt>\fR +\fB\-o\fR, \fB\-\-output\-format\fR \fI<fmt>\fR If [out] is specified, then write the EDID in format \fI<fmt>\fR. -.br + The output format can be one of: .br hex: hex numbers in ascii text (default for stdout) @@ -230,11 +230,87 @@ the start. \fB\-\-version\fR Show the SHA hash and the last commit date. +.SH TIMING OPTIONS +The following options report the timings for DMT, VIC and HDMI VIC codes and +calculate the timings for CVT or GTF timings, based on the given parameters. +The EDID will not be shown, although it can be used with the \fB\-\-gtf\fR +option in order to read the secondary curve parameters. +.TP +\fB\-\-std\fR \fI<byte1>\fR,\fI<byte2>\fR +Show the standard timing represented by these two bytes. +.TP +\fB\-\-dmt\fR \fI<dmt>\fR +Show the timings for the DMT with the given DMT ID. +.TP +\fB\-\-vic\fR \fI<vic>\fR +Show the timings for this VIC. +.TP +\fB\-\-hdmi\-vic\fR \fI<hdmivic>\fR +Show the timings for this HDMI VIC. +.TP +\fB\-\-cvt\fR \fBw\fR=\fI<width>\fR,\fBh\fR=\fI<height>\fR,\fBfps\fR=\fI<fps>\fR[,\fBrb\fR=\fI<rb>\fR][,\fBinterlaced\fR][,\fBoverscan\fR][,\fBalt\fR] +.br +Calculate the CVT timings for the given format. + +\fI<width>\fR is the width in pixels, \fI<height>\fR is the frame (not field!) height in lines. +.br +\fI<fps>\fR is frames per second for progressive timings and fields per second for interlaced timings. +.br +\fI<rb>\fR can be 0 (no reduced blanking, default), or 1-3 for the reduced blanking version. +.br +If \fBinterlaced\fR is given, then this is an interlaced format. +.br +If \fBoverscan\fR is given, then this is an overscanned format. I.e., margins are required. +.br +If \fBalt\fR is given and \fI<rb>\fR=2, then report the timings +optimized for video: 1000 / 1001 * \fI<fps>\fR. +.br +If \fBalt\fR is given and \fI<rb>\fR=3, then the horizontal blanking +is 160 instead of 80 pixels. +.TP +\fB\-\-gtf\fR \fBw\fR=\fI<width>\fR,\fBh\fR=\fI<height>\fR[,\fBfps\fR=\fI<fps>\fR][,\fBhorfreq\fR=\fI<horfreq>\fR][,\fBpixclk\fR=\fI<pixclk>\fR] +[,\fBinterlaced\fR][,\fBoverscan\fR][,\fBsecondary\fR][,\fBC\fR=\fI<c>\fR][,\fBM\fR=\fI<m>\fR][,\fBK\fR=\fI<k>\fR][,\fBJ\fR=\fI<j>\fR] +.br +Calculate the GTF timings for the given format. + +\fI<width>\fR is the width in pixels, \fI<height>\fR is the frame (not field!) height in lines. +.br +\fI<fps>\fR is frames per second for progressive timings and fields per second for interlaced timings. +.br +\fI<horfreq>\fR is the horizontal frequency in kHz. +.br +\fI<pixclk>\fR is the pixel clock frequency in MHz. +Only one of \fBfps\fR, \fBhorfreq\fR or \fBpixclk\fR must be given. +.br +If \fBinterlaced\fR is given, then this is an interlaced format. +.br +If \fBoverscan\fR is given, then this is an overscanned format. I.e., margins are required. +.br +If \fBsecondary\fR is given, then the secondary GTF is used for +reduced blanking, where \fI<c>\fR, \fI<m>\fR, \fI<k>\fR and \fI<j>\fR are parameters +for the secondary curve. If none of the secondary curve parameters +were set, and an EDID file is passed as command line option, then the +secondary curve parameters are read from that EDID. +.br +The default secondary curve parameters are 40 for \fI<c>\fR, 600 for \fI<m>\fR, +128 for \fI<k>\fR and 20 for \fI<j>\fR. +These values correspond to the normal curve that GTF uses. +.TP +\fB\-\-list\-established\-timings\fR +List all known Established Timings. +.TP +\fB\-\-list\-dmts\fR +List all known DMTs. +.TP +\fB\-\-list\-vics\fR +List all known VICs. +.TP +\fB\-\-list\-hdmi\-vics\fR +List all known HDMI VICs. + .PP .SH NOTES -Not all fields are decoded, or decoded completely. Some fields' decoding -may appear to corrupt the output (for example, detailed string sections -have their contents printed literally). +Not all fields are decoded, or decoded completely. .B edid-decode does attempt to validate its input against the relevant standards, but its opinions have not been double-checked with the relevant standards bodies, diff --git a/edid-decode.cpp b/edid-decode.cpp index 2b5aafc..1470406 100644 --- a/edid-decode.cpp +++ b/edid-decode.cpp @@ -56,6 +56,16 @@ enum Option { OptSkipSHA = 128, OptHideSerialNumbers, OptVersion, + OptSTD, + OptDMT, + OptVIC, + OptHDMIVIC, + OptCVT, + OptGTF, + OptListEstTimings, + OptListDMTs, + OptListVICs, + OptListHDMIVICs, OptLast = 256 }; @@ -78,6 +88,16 @@ static struct option long_options[] = { { "xmodeline", no_argument, 0, OptXModeLineTimings }, { "fbmode", no_argument, 0, OptFBModeTimings }, { "v4l2-timings", no_argument, 0, OptV4L2Timings }, + { "std", required_argument, 0, OptSTD }, + { "dmt", required_argument, 0, OptDMT }, + { "vic", required_argument, 0, OptVIC }, + { "hdmi-vic", required_argument, 0, OptHDMIVIC }, + { "cvt", required_argument, 0, OptCVT }, + { "gtf", required_argument, 0, OptGTF }, + { "list-established-timings", no_argument, 0, OptListEstTimings }, + { "list-dmts", no_argument, 0, OptListDMTs }, + { "list-vics", no_argument, 0, OptListVICs }, + { "list-hdmi-vics", no_argument, 0, OptListHDMIVICs }, { 0, 0, 0, 0 } }; @@ -90,29 +110,62 @@ static void usage(void) " if the output filename is '-'.\n" "\nOptions:\n" " -o, --output-format <fmt>\n" - " if [out] is specified, then write the EDID in this format\n" + " If [out] is specified, then write the EDID in this format\n" " <fmt> is one of:\n" " hex: hex numbers in ascii text (default for stdout)\n" " raw: binary data (default unless writing to stdout)\n" " carray: c-program struct\n" " xml: XML data\n" - " -c, --check check if the EDID conforms to the standards, failures and\n" + " -c, --check Check if the EDID conforms to the standards, failures and\n" " warnings are reported at the end.\n" - " -C, --check-inline check if the EDID conforms to the standards, failures and\n" + " -C, --check-inline Check if the EDID conforms to the standards, failures and\n" " warnings are reported inline.\n" - " -n, --native-timings report the native timings\n" - " -p, --preferred-timings report the preferred timings\n" - " -P, --physical-address only report the CEC physical address\n" - " -S, --short-timings report all video timings in a short format\n" - " -L, --long-timings report all video timings in a long format\n" - " -X, --xmodeline report all long video timings in Xorg.conf format\n" - " -F, --fbmode report all long video timings in fb.modes format\n" - " -V, --v4l2-timings report all long video timings in v4l2-dv-timings.h format\n" - " -s, --skip-hex-dump skip the initial hex dump of the EDID\n" - " --skip-sha skip the SHA report\n" - " --hide-serial-numbers replace serial numbers with '...'\n" + " -n, --native-timings Report the native timings.\n" + " -p, --preferred-timings Report the preferred timings.\n" + " -P, --physical-address Only report the CEC physical address.\n" + " -S, --short-timings Report all video timings in a short format.\n" + " -L, --long-timings Report all video timings in a long format.\n" + " -X, --xmodeline Report all long video timings in Xorg.conf format.\n" + " -F, --fbmode Report all long video timings in fb.modes format.\n" + " -V, --v4l2-timings Report all long video timings in v4l2-dv-timings.h format.\n" + " -s, --skip-hex-dump Skip the initial hex dump of the EDID.\n" + " --skip-sha Skip the SHA report.\n" + " --hide-serial-numbers Replace serial numbers with '...'\n" " --version show the edid-decode version (SHA)\n" - " -h, --help display this help message\n"); + " --std <byte1>,<byte2> Show the standard timing represented by these two bytes.\n" + " --dmt <dmt> Show the timings for the DMT with the given DMT ID.\n" + " --vic <vic> Show the timings for this VIC.\n" + " --hdmi-vic <hdmivic> Show the timings for this HDMI VIC.\n" + " --cvt w=<width>,h=<height>,fps=<fps>[,rb=<rb>][,interlaced][,overscan][,alt]\n" + " Calculate the CVT timings for the given format.\n" + " <fps> is frames per second for progressive timings,\n" + " or fields per second for interlaced timings.\n" + " <rb> can be 0 (no reduced blanking, default), or\n" + " 1-3 for the reduced blanking version.\n" + " If 'interlaced' is given, then this is an interlaced format.\n" + " If 'overscan' is given, then this is an overscanned format.\n" + " If 'alt' is given and <rb>=2, then report the timings\n" + " optimized for video: 1000 / 1001 * <fps>.\n" + " If 'alt' is given and <rb>=3, then the horizontal blanking\n" + " is 160 instead of 80 pixels.\n" + " --gtf w=<width>,h=<height>[,fps=<fps>][,horfreq=<horfreq>][,pixclk=<pixclk>][,interlaced]\n" + " [,overscan][,secondary][,C=<c>][,M=<m>][,K=<k>][,J=<j>]\n" + " Calculate the GTF timings for the given format.\n" + " <fps> is frames per second for progressive timings,\n" + " or fields per second for interlaced timings.\n" + " <horfreq> is the horizontal frequency in kHz.\n" + " <pixclk> is the pixel clock frequency in MHz.\n" + " Only one of fps, horfreq or pixclk must be given.\n" + " If 'interlaced' is given, then this is an interlaced format.\n" + " If 'overscan' is given, then this is an overscanned format.\n" + " If 'secondary' is given, then the secondary GTF is used for\n" + " reduced blanking, where <c>, <m>, <k> and <j> are parameters\n" + " for the secondary curve.\n" + " --list-established-timings List all known Established Timings.\n" + " --list-dmts List all known DMTs.\n" + " --list-vics List all known VICs.\n" + " --list-hdmi-vics List all known HDMI VICs.\n" + " -h, --help Display this help message.\n"); } static std::string s_msgs[EDID_MAX_BLOCKS + 1][2]; @@ -449,12 +502,16 @@ bool edid_state::print_timings(const char *prefix, const struct timings *t, double refresh = (double)t->pixclk_khz * 1000.0 / (htotal * vtotal); std::string s; - if (t->rb) { + unsigned rb = t->rb & ~RB_FLAG; + if (rb) { + bool alt = t->rb & RB_FLAG; s = "RB"; - if (t->rb == 2) - s += "v2"; - else if (t->rb == 3) - s += "v3"; + // Mark RB_CVT_V3 as preliminary since CVT 1.3 has not been + // released yet. + if (rb == RB_CVT_V2) + s += std::string("v2") + (alt ? ",video-optimized" : ""); + else if (rb == RB_CVT_V3) + s += std::string("v3-is-preliminary") + (alt ? ",h-blank-160" : ""); } add_str(s, flags); if (t->hsize_mm || t->vsize_mm) @@ -470,7 +527,7 @@ bool edid_state::print_timings(const char *prefix, const struct timings *t, char buf[10]; sprintf(buf, "%u%s", t->vact, t->interlaced ? "i" : ""); - printf("%s%s: %5ux%-5s %7.3f Hz %3u:%-3u %7.3f kHz %7.3f MHz%s\n", + printf("%s%s: %5ux%-5s %7.3f Hz %3u:%-3u %7.3f kHz %8.3f MHz%s\n", prefix, type, t->hact, buf, refresh, @@ -497,10 +554,12 @@ bool edid_state::print_timings(const char *prefix, const struct timings *t, min_vert_freq_hz = min(min_vert_freq_hz, refresh); max_vert_freq_hz = max(max_vert_freq_hz, refresh); } - if (pixclk_khz && (t->hact + hbl)) { - min_hor_freq_hz = min(min_hor_freq_hz, (pixclk_khz * 1000ULL) / (t->hact + hbl)); - max_hor_freq_hz = max(max_hor_freq_hz, (pixclk_khz * 1000ULL) / (t->hact + hbl)); + if (hor_freq_khz) { + min_hor_freq_hz = min(min_hor_freq_hz, hor_freq_khz * 1000.0); + max_hor_freq_hz = max(max_hor_freq_hz, hor_freq_khz * 1000.0); max_pixclk_khz = max(max_pixclk_khz, pixclk_khz); + if (t->pos_pol_hsync && !t->pos_pol_vsync && t->vsync == 3) + base.max_pos_neg_hor_freq_khz = hor_freq_khz; } if (t->ycbcr420 && t->pixclk_khz < 590000) @@ -1200,16 +1259,278 @@ int edid_state::parse_edid() return failures ? -2 : 0; } +enum cvt_opts { + CVT_WIDTH = 0, + CVT_HEIGHT, + CVT_FPS, + CVT_INTERLACED, + CVT_OVERSCAN, + CVT_RB, + CVT_ALT, +}; + +static int parse_cvt_subopt(char **subopt_str, double *value) +{ + int opt; + char *opt_str; + + static const char * const subopt_list[] = { + "w", + "h", + "fps", + "interlaced", + "overscan", + "rb", + "alt", + nullptr + }; + + opt = getsubopt(subopt_str, (char* const*) subopt_list, &opt_str); + + if (opt == -1) { + fprintf(stderr, "Invalid suboptions specified.\n"); + usage(); + std::exit(EXIT_FAILURE); + } + if (opt_str == nullptr && opt != CVT_INTERLACED && opt != CVT_ALT && + opt != CVT_OVERSCAN) { + fprintf(stderr, "No value given to suboption <%s>.\n", + subopt_list[opt]); + usage(); + std::exit(EXIT_FAILURE); + } + + if (opt_str) + *value = strtod(opt_str, nullptr); + return opt; +} + +static void parse_cvt(char *optarg) +{ + unsigned w = 0, h = 0; + double fps = 0; + unsigned rb = 0; + bool interlaced = false; + bool alt = false; + bool overscan = false; + + while (*optarg != '\0') { + int opt; + double opt_val; + + opt = parse_cvt_subopt(&optarg, &opt_val); + + switch (opt) { + case CVT_WIDTH: + w = round(opt_val); + break; + case CVT_HEIGHT: + h = round(opt_val); + break; + case CVT_FPS: + fps = opt_val; + break; + case CVT_RB: + rb = opt_val; + break; + case CVT_OVERSCAN: + overscan = true; + break; + case CVT_INTERLACED: + interlaced = opt_val; + break; + case CVT_ALT: + alt = opt_val; + break; + default: + break; + } + } + + if (!w || !h || !fps) { + fprintf(stderr, "Missing width, height and/or fps.\n"); + usage(); + std::exit(EXIT_FAILURE); + } + if (interlaced) + fps /= 2; + timings t = state.calc_cvt_mode(w, h, fps, rb, interlaced, overscan, alt); + state.print_timings("", &t, "CVT", "", true, false); +} + +struct gtf_parsed_data { + unsigned w, h; + double freq; + double C, M, K, J; + bool overscan; + bool interlaced; + bool secondary; + bool params_from_edid; + enum gtf_ip_parm ip_parm; +}; + +enum gtf_opts { + GTF_WIDTH = 0, + GTF_HEIGHT, + GTF_FPS, + GTF_HORFREQ, + GTF_PIXCLK, + GTF_INTERLACED, + GTF_OVERSCAN, + GTF_SECONDARY, + GTF_C2, + GTF_M, + GTF_K, + GTF_J2, +}; + +static int parse_gtf_subopt(char **subopt_str, double *value) +{ + int opt; + char *opt_str; + + static const char * const subopt_list[] = { + "w", + "h", + "fps", + "horfreq", + "pixclk", + "interlaced", + "overscan", + "secondary", + "C", + "M", + "K", + "J", + nullptr + }; + + opt = getsubopt(subopt_str, (char * const *)subopt_list, &opt_str); + + if (opt == -1) { + fprintf(stderr, "Invalid suboptions specified.\n"); + usage(); + std::exit(EXIT_FAILURE); + } + if (opt_str == nullptr && opt != GTF_INTERLACED && opt != GTF_OVERSCAN && + opt != GTF_SECONDARY) { + fprintf(stderr, "No value given to suboption <%s>.\n", + subopt_list[opt]); + usage(); + std::exit(EXIT_FAILURE); + } + + if (opt == GTF_C2 || opt == GTF_J2) + *value = round(2.0 * strtod(opt_str, nullptr)); + else if (opt_str) + *value = strtod(opt_str, nullptr); + return opt; +} + +static void parse_gtf(char *optarg, gtf_parsed_data &data) +{ + memset(&data, 0, sizeof(data)); + data.params_from_edid = true; + data.C = 40; + data.M = 600; + data.K = 128; + data.J = 20; + + while (*optarg != '\0') { + int opt; + double opt_val; + + opt = parse_gtf_subopt(&optarg, &opt_val); + + switch (opt) { + case GTF_WIDTH: + data.w = round(opt_val); + break; + case GTF_HEIGHT: + data.h = round(opt_val); + break; + case GTF_FPS: + data.freq = opt_val; + data.ip_parm = gtf_ip_vert_freq; + break; + case GTF_HORFREQ: + data.freq = opt_val; + data.ip_parm = gtf_ip_hor_freq; + break; + case GTF_PIXCLK: + data.freq = opt_val; + data.ip_parm = gtf_ip_clk_freq; + break; + case GTF_INTERLACED: + data.interlaced = true; + break; + case GTF_OVERSCAN: + data.overscan = true; + break; + case GTF_SECONDARY: + data.secondary = true; + break; + case GTF_C2: + data.C = opt_val / 2.0; + data.params_from_edid = false; + break; + case GTF_M: + data.M = round(opt_val); + data.params_from_edid = false; + break; + case GTF_K: + data.K = round(opt_val); + data.params_from_edid = false; + break; + case GTF_J2: + data.J = opt_val / 2.0; + data.params_from_edid = false; + break; + default: + break; + } + } + + if (!data.w || !data.h) { + fprintf(stderr, "Missing width and/or height.\n"); + usage(); + std::exit(EXIT_FAILURE); + } + if (!data.freq) { + fprintf(stderr, "One of fps, horfreq or pixclk must be given.\n"); + usage(); + std::exit(EXIT_FAILURE); + } + if (!data.secondary) + data.params_from_edid = false; + if (data.interlaced && data.ip_parm == gtf_ip_vert_freq) + data.freq /= 2; +} + +static void show_gtf(gtf_parsed_data &data) +{ + timings t; + + t = state.calc_gtf_mode(data.w, data.h, data.freq, data.interlaced, + data.ip_parm, data.overscan, data.secondary, + data.C, data.M, data.K, data.J); + calc_ratio(&t); + state.print_timings("", &t, "GTF", "", true, false); +} + int main(int argc, char **argv) { char short_options[26 * 2 * 2 + 1]; enum output_format out_fmt = OUT_FMT_DEFAULT; + gtf_parsed_data gtf_data; int ret; while (1) { int option_index = 0; unsigned idx = 0; - unsigned i; + unsigned i, val; + const timings *t; + char buf[16]; for (i = 0; long_options[i].name; i++) { if (!isalpha(long_options[i].val)) @@ -1243,6 +1564,52 @@ int main(int argc, char **argv) exit(1); } break; + case OptSTD: { + unsigned char byte1, byte2 = 0; + char *endptr; + + byte1 = strtoul(optarg, &endptr, 0); + if (*endptr == ',') + byte2 = strtoul(endptr + 1, NULL, 0); + state.print_standard_timing("", byte1, byte2, false, true); + break; + } + case OptDMT: + val = strtoul(optarg, NULL, 0); + t = find_dmt_id(val); + if (t) { + sprintf(buf, "DMT 0x%02x", val); + state.print_timings("", t, buf, "", true, false); + } else { + fprintf(stderr, "Unknown DMT code 0x%02x.\n", val); + } + break; + case OptVIC: + val = strtoul(optarg, NULL, 0); + t = find_vic_id(val); + if (t) { + sprintf(buf, "VIC %3u", val); + state.print_timings("", t, buf, "", true, false); + } else { + fprintf(stderr, "Unknown VIC code %u.\n", val); + } + break; + case OptHDMIVIC: + val = strtoul(optarg, NULL, 0); + t = find_hdmi_vic_id(val); + if (t) { + sprintf(buf, "HDMI VIC %u", val); + state.print_timings("", t, buf, "", true, false); + } else { + fprintf(stderr, "Unknown HDMI VIC code %u.\n", val); + } + break; + case OptCVT: + parse_cvt(optarg); + break; + case OptGTF: + parse_gtf(optarg, gtf_data); + break; case ':': fprintf(stderr, "Option '%s' requires a value.\n", argv[optind]); @@ -1263,6 +1630,28 @@ int main(int argc, char **argv) return 0; } + if (options[OptListEstTimings]) + state.list_established_timings(); + if (options[OptListDMTs]) + state.list_dmts(); + if (options[OptListVICs]) + state.cta_list_vics(); + if (options[OptListHDMIVICs]) + state.cta_list_hdmi_vics(); + + if (options[OptListEstTimings] || options[OptListDMTs] || + options[OptListVICs] || options[OptListHDMIVICs]) + return 0; + + if (options[OptCVT] || options[OptDMT] || options[OptVIC] || + options[OptHDMIVIC] || options[OptSTD]) + return 0; + + if (options[OptGTF] && (!gtf_data.params_from_edid || optind == argc)) { + show_gtf(gtf_data); + return 0; + } + if (optind == argc) ret = edid_from_file("-", stdout); else @@ -1275,6 +1664,38 @@ int main(int argc, char **argv) if (optind < argc - 1) return ret ? ret : edid_to_file(argv[optind + 1], out_fmt); + if (options[OptGTF]) { + timings t; + + // Find the Secondary Curve + state.preparse_detailed_block(edid + 0x36); + state.preparse_detailed_block(edid + 0x48); + state.preparse_detailed_block(edid + 0x5a); + state.preparse_detailed_block(edid + 0x6c); + + t = state.calc_gtf_mode(gtf_data.w, gtf_data.h, gtf_data.freq, + gtf_data.interlaced, gtf_data.ip_parm, + gtf_data.overscan); + unsigned hbl = t.hfp + t.hsync + t.hbp; + unsigned htotal = t.hact + hbl; + double hor_freq_khz = htotal ? (double)t.pixclk_khz / htotal : 0; + + if (state.base.supports_sec_gtf && + hor_freq_khz >= state.base.sec_gtf_start_freq) { + t = state.calc_gtf_mode(gtf_data.w, gtf_data.h, gtf_data.freq, + gtf_data.interlaced, gtf_data.ip_parm, + gtf_data.overscan, true, + state.base.C, state.base.M, + state.base.K, state.base.J); + } + calc_ratio(&t); + if (t.hfp <= 0) + state.print_timings("", &t, "GTF", "INVALID: Hfront <= 0", true, false); + else + state.print_timings("", &t, "GTF", "", true, false); + return 0; + } + return ret ? ret : state.parse_edid(); } diff --git a/edid-decode.h b/edid-decode.h index af7af0b..cadcff4 100644 --- a/edid-decode.h +++ b/edid-decode.h @@ -22,24 +22,30 @@ #define EDID_PAGE_SIZE 128U #define EDID_MAX_BLOCKS 256U -#define RB_FLAG (1U << 7) +#define RB_FLAG (1U << 7) + +#define RB_CVT_V1 (1) +#define RB_CVT_V2 (2) +#define RB_CVT_V3 (3) +#define RB_GTF (4) // Video Timings // If interlaced is true, then the vertical blanking // for each field is (vfp + vsync + vbp + 0.5), except for // the VIC 39 timings that doesn't have the 0.5 constant. struct timings { - // Active horizontal and vertical frame height, including any + // Active horizontal and vertical frame height, excluding any // borders, if present. // Note: for interlaced formats the active field height is vact / 2 unsigned hact, vact; unsigned hratio, vratio; unsigned pixclk_khz; // 0: no reduced blanking - // 1: reduced blanking version 1 - // 2: reduced blanking version 2 - // 3: reduced blanking version 3 with a horizontal blank of 80 - // 3 | RB_FLAG: reduced blanking version 3 with a horizontal blank of 160 + // 1: CVT reduced blanking version 1 + // 2: CVT reduced blanking version 2 + // 3: CVT reduced blanking version 3 with a horizontal blank of 80 + // 3 | RB_FLAG: CVT reduced blanking version 3 with a horizontal blank of 160 + // 4: GTF Secondary Curve unsigned rb; bool interlaced; // The horizontal frontporch may be negative in GTF calculations, @@ -90,6 +96,12 @@ struct timings_ext { std::string flags; }; +enum gtf_ip_parm { + gtf_ip_vert_freq = 1, + gtf_ip_hor_freq, + gtf_ip_clk_freq, +}; + typedef std::vector<timings_ext> vec_timings_ext; struct edid_state { @@ -113,6 +125,10 @@ struct edid_state { base.supports_cvt = base.seen_non_detailed_descriptor = base.has_640x480p60_est_timing = base.has_spwg = base.preferred_is_also_native = false; + base.supports_sec_gtf = false; + base.sec_gtf_start_freq = 0; + base.C = base.M = base.K = base.J = 0; + base.max_pos_neg_hor_freq_khz = 0; base.detailed_block_cnt = base.dtd_cnt = 0; base.min_display_hor_freq_hz = base.max_display_hor_freq_hz = @@ -181,6 +197,9 @@ struct edid_state { bool has_serial_string; bool supports_continuous_freq; bool supports_gtf; + bool supports_sec_gtf; + unsigned sec_gtf_start_freq; + double C, M, K, J; bool supports_cvt; bool has_spwg; unsigned detailed_block_cnt; @@ -197,6 +216,7 @@ struct edid_state { unsigned max_display_pixclk_khz; unsigned max_display_width_mm; unsigned max_display_height_mm; + unsigned max_pos_neg_hor_freq_khz; } base; // CTA-861 block state @@ -271,12 +291,19 @@ struct edid_state { detailed, do_checks); }; bool match_timings(const timings &t1, const timings &t2); + timings calc_gtf_mode(unsigned h_pixels, unsigned v_lines, + double ip_freq_rqd, bool int_rqd = false, + enum gtf_ip_parm ip_parm = gtf_ip_vert_freq, + bool margins_rqd = false, bool secondary = false, + double C = 40, double M = 600, double K = 128, double J = 20); void edid_gtf_mode(unsigned refresh, struct timings &t); + timings calc_cvt_mode(unsigned h_pixels, unsigned v_lines, + double ip_freq_rqd, unsigned rb, bool int_rqd = false, + bool margins_rqd = false, bool alt = false); void edid_cvt_mode(unsigned refresh, struct timings &t); - timings calc_cvt_mode(unsigned refresh, unsigned hact, unsigned vact, unsigned rb); void detailed_cvt_descriptor(const char *prefix, const unsigned char *x, bool first); void print_standard_timing(const char *prefix, unsigned char b1, unsigned char b2, - bool gtf_only = false, unsigned vrefresh_offset = 60); + bool gtf_only = false, bool show_both = false); void detailed_display_range_limits(const unsigned char *x); void detailed_epi(const unsigned char *x); void detailed_timings(const char *prefix, const unsigned char *x, @@ -285,6 +312,8 @@ struct edid_state { void detailed_block(const unsigned char *x); void parse_base_block(const unsigned char *x); void check_base_block(); + void list_dmts(); + void list_established_timings(); void print_vic_index(const char *prefix, unsigned idx, const char *suffix, bool ycbcr420 = false); void cta_vcdb(const unsigned char *x, unsigned length); @@ -305,6 +334,8 @@ struct edid_state { void cta_resolve_svr(vec_timings_ext::iterator iter); void cta_resolve_svrs(); void check_cta_blocks(); + void cta_list_vics(); + void cta_list_hdmi_vics(); void parse_digital_interface(const unsigned char *x); void parse_display_device(const unsigned char *x); diff --git a/parse-base-block.cpp b/parse-base-block.cpp index 550b622..7aa27bd 100644 --- a/parse-base-block.cpp +++ b/parse-base-block.cpp @@ -354,376 +354,51 @@ static const struct timings *find_std_id(unsigned short std_id, unsigned char &d return NULL; } -/* - * Copied from xserver/hw/xfree86/modes/xf86gtf.c - */ -void edid_state::edid_gtf_mode(unsigned refresh, struct timings &t) -{ -#define CELL_GRAN 8.0 /* assumed character cell granularity */ -#define MIN_PORCH 1 /* minimum front porch */ -#define V_SYNC_RQD 3 /* width of vsync in lines */ -#define H_SYNC_PERCENT 8.0 /* width of hsync as % of total line */ -#define MIN_VSYNC_PLUS_BP 550.0 /* min time of vsync + back porch (microsec) */ -#define M 600.0 /* blanking formula gradient */ -#define C 40.0 /* blanking formula offset */ -#define K 128.0 /* blanking formula scaling factor */ -#define J 20.0 /* blanking formula scaling factor */ - - /* C' and M' are part of the Blanking Duty Cycle computation */ - -#define C_PRIME (((C - J) * K/256.0) + J) -#define M_PRIME (K/256.0 * M) - double h_pixels_rnd; - double v_lines_rnd; - double v_field_rate_rqd; - double h_period_est; - double vsync_plus_bp; - double total_v_lines; - double v_field_rate_est; - double h_period; - double total_active_pixels; - double ideal_duty_cycle; - double h_blank; - double total_pixels; - - /* 1. In order to give correct results, the number of horizontal - * pixels requested is first processed to ensure that it is divisible - * by the character size, by rounding it to the nearest character - * cell boundary: - * - * [H PIXELS RND] = ((ROUND([H PIXELS]/[CELL GRAN RND],0))*[CELLGRAN RND]) - */ - - h_pixels_rnd = rint((double)t.hact / CELL_GRAN) * CELL_GRAN; - - /* 2. If interlace is requested, 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 to the nearest integer. - * - * [V LINES RND] = IF([INT RQD?]="y", ROUND([V LINES]/2,0), - * ROUND([V LINES],0)) - */ - - v_lines_rnd = t.vact; - - /* 3. Find the frame rate required: - * - * [V FIELD RATE RQD] = IF([INT RQD?]="y", [I/P FREQ RQD]*2, - * [I/P FREQ RQD]) - */ - - v_field_rate_rqd = refresh; - - /* 7. Estimate the Horizontal period - * - * [H PERIOD EST] = ((1/[V FIELD RATE RQD]) - [MIN VSYNC+BP]/1000000) / - * ([V LINES RND] + - * [MIN PORCH RND]+[INTERLACE]) * 1000000 - */ - - h_period_est = (((1.0/v_field_rate_rqd) - (MIN_VSYNC_PLUS_BP/1000000.0)) - / (v_lines_rnd + MIN_PORCH) - * 1000000.0); - - /* 8. Find the number of lines in V sync + back porch: - * - * [V SYNC+BP] = ROUND(([MIN VSYNC+BP]/[H PERIOD EST]),0) - */ - - vsync_plus_bp = rint(MIN_VSYNC_PLUS_BP/h_period_est); - - /* 10. Find the total number of lines in Vertical field period: - * - * [TOTAL V LINES] = [V LINES RND] + - * [V SYNC+BP] + [INTERLACE] + - * [MIN PORCH RND] - */ - - total_v_lines = v_lines_rnd + vsync_plus_bp + MIN_PORCH; - t.vbp = vsync_plus_bp - V_SYNC_RQD; - t.vsync = V_SYNC_RQD; - t.vfp = MIN_PORCH; - - /* 11. Estimate the Vertical field frequency: - * - * [V FIELD RATE EST] = 1 / [H PERIOD EST] / [TOTAL V LINES] * 1000000 - */ - - v_field_rate_est = 1.0 / h_period_est / total_v_lines * 1000000.0; - - /* 12. Find the actual horizontal period: - * - * [H PERIOD] = [H PERIOD EST] / ([V FIELD RATE RQD] / [V FIELD RATE EST]) - */ - - h_period = h_period_est / (v_field_rate_rqd / v_field_rate_est); - - /* 17. Find total number of active pixels in image - * - * [TOTAL ACTIVE PIXELS] = [H PIXELS RND] - */ - - total_active_pixels = h_pixels_rnd; - - /* 18. Find the ideal blanking duty cycle from the blanking duty cycle - * equation: - * - * [IDEAL DUTY CYCLE] = [C'] - ([M']*[H PERIOD]/1000) - */ - - ideal_duty_cycle = C_PRIME - (M_PRIME * h_period / 1000.0); - - /* 19. Find the number of pixels in the blanking time to the nearest - * double character cell: - * - * [H BLANK (PIXELS)] = (ROUND(([TOTAL ACTIVE PIXELS] * - * [IDEAL DUTY CYCLE] / - * (100-[IDEAL DUTY CYCLE]) / - * (2*[CELL GRAN RND])), 0)) - * * (2*[CELL GRAN RND]) - */ - - h_blank = rint(total_active_pixels * - ideal_duty_cycle / - (100.0 - ideal_duty_cycle) / - (2.0 * CELL_GRAN)) * (2.0 * CELL_GRAN); - - /* 20. Find total number of pixels: - * - * [TOTAL PIXELS] = [TOTAL ACTIVE PIXELS] + [H BLANK (PIXELS)] - */ - - total_pixels = total_active_pixels + h_blank; - - /* 21. Find pixel clock frequency: - * - * [PIXEL FREQ] = [TOTAL PIXELS] / [H PERIOD] - */ - - t.pixclk_khz = (int)(1000.0 * total_pixels / h_period + 0.5); - - /* Stage 1 computations are now complete; I should really pass - the results to another function and do the Stage 2 - computations, but I only need a few more values so I'll just - append the computations here for now */ - /* 17. Find the number of pixels in the horizontal sync period: - * - * [H SYNC (PIXELS)] =(ROUND(([H SYNC%] / 100 * [TOTAL PIXELS] / - * [CELL GRAN RND]),0))*[CELL GRAN RND] - */ - t.hsync = rint(H_SYNC_PERCENT / 100.0 * total_pixels / CELL_GRAN) * CELL_GRAN; - /* 18. Find the number of pixels in the horizontal front porch period: - * - * [H FRONT PORCH (PIXELS)] = ([H BLANK (PIXELS)]/2)-[H SYNC (PIXELS)] - */ - t.hfp = (h_blank / 2.0) - t.hsync; - /* 19. Find the number of pixels in the horizontal back porch period: - * - * [H BACK PORCH (PIXELS)] = [H FRONT PORCH (PIXELS)]+[H SYNC (PIXELS)] - */ - t.hbp = t.hfp + t.hsync; - t.pos_pol_hsync = false; - t.pos_pol_vsync = true; - t.interlaced = false; - t.rb = 0; -} - -/* - * Copied from xserver/hw/xfree86/modes/xf86cvt.c - */ -void edid_state::edid_cvt_mode(unsigned refresh, struct timings &t) +void edid_state::list_established_timings() { - int HDisplay = t.hact; - int VDisplay = t.vact; - - /* 2) character cell horizontal granularity (pixels) - default 8 */ -#define CVT_H_GRANULARITY 8 - - /* 4) Minimum vertical porch (lines) - default 3 */ -#define CVT_MIN_V_PORCH 3 - - /* 4) Minimum number of vertical back porch lines - default 6 */ -#define CVT_MIN_V_BPORCH 6 - - /* Pixel Clock step (kHz) */ -#define CVT_CLOCK_STEP 250 - - double HPeriod; - int VDisplayRnd, VSync; - double VFieldRate = refresh; - int HTotal, VTotal, Clock, HSyncStart, HSyncEnd, VSyncStart, VSyncEnd; - - /* 2. Horizontal pixels */ - HDisplay = HDisplay - (HDisplay % CVT_H_GRANULARITY); - - /* 5. Find number of lines per field */ - VDisplayRnd = VDisplay; - - /* Determine VSync Width from aspect ratio */ - if ((VDisplay * 4 / 3) == HDisplay) - VSync = 4; - else if ((VDisplay * 16 / 9) == HDisplay) - VSync = 5; - else if ((VDisplay * 16 / 10) == HDisplay) - VSync = 6; - else if (!(VDisplay % 4) && ((VDisplay * 5 / 4) == HDisplay)) - VSync = 7; - else if ((VDisplay * 15 / 9) == HDisplay) - VSync = 7; - else /* Custom */ - VSync = 10; - - if (!t.rb) { /* simplified GTF calculation */ - /* 4) Minimum time of vertical sync + back porch interval (µs) - * default 550.0 */ -#define CVT_MIN_VSYNC_BP 550.0 - - /* 3) Nominal HSync width (% of line period) - default 8 */ -#define CVT_HSYNC_PERCENTAGE 8 - - double HBlankPercentage; - int VSyncAndBackPorch; - int HBlank; - - /* 8. Estimated Horizontal period */ - HPeriod = ((double) (1000000.0 / VFieldRate - CVT_MIN_VSYNC_BP)) / - (VDisplayRnd + CVT_MIN_V_PORCH); - - /* 9. Find number of lines in sync + backporch */ - if (((int) (CVT_MIN_VSYNC_BP / HPeriod) + 1) < - (VSync + CVT_MIN_V_BPORCH)) - VSyncAndBackPorch = VSync + CVT_MIN_V_BPORCH; - else - VSyncAndBackPorch = (int) (CVT_MIN_VSYNC_BP / HPeriod) + 1; - - VTotal = VDisplayRnd + VSyncAndBackPorch + CVT_MIN_V_PORCH; - - /* 5) Definition of Horizontal blanking time limitation */ - /* Gradient (%/kHz) - default 600 */ -#define CVT_M_FACTOR 600.0 - - /* Offset (%) - default 40 */ -#define CVT_C_FACTOR 40.0 - - /* Blanking time scaling factor - default 128 */ -#define CVT_K_FACTOR 128.0 - - /* Scaling factor weighting - default 20 */ -#define CVT_J_FACTOR 20.0 - -#define CVT_M_PRIME (CVT_M_FACTOR * CVT_K_FACTOR / 256.0) -#define CVT_C_PRIME ((CVT_C_FACTOR - CVT_J_FACTOR) * CVT_K_FACTOR / 256.0 + \ - CVT_J_FACTOR) - - /* 12. Find ideal blanking duty cycle from formula */ - HBlankPercentage = CVT_C_PRIME - CVT_M_PRIME * HPeriod / 1000.0; - - /* 13. Blanking time */ - if (HBlankPercentage < 20) - HBlankPercentage = 20; - - HBlank = (double)HDisplay * HBlankPercentage / (100.0 - HBlankPercentage) / (2.0 * CVT_H_GRANULARITY); - HBlank *= 2 * CVT_H_GRANULARITY; - - /* 14. Find total number of pixels in a line. */ - HTotal = HDisplay + HBlank; - - int HSync = (HTotal * CVT_HSYNC_PERCENTAGE) / 100.0 + 0.0; - //printf("%d %d %d\n", HTotal, HBlank, HSync); - HSync -= HSync % CVT_H_GRANULARITY; - - /* Fill in HSync values */ - HSyncEnd = HTotal - HBlank / 2; - - HSyncStart = HSyncEnd - HSync; - VSyncStart = VDisplayRnd + CVT_MIN_V_PORCH; - VSyncEnd = VSyncStart + VSync; + printf("Established Timings I & II:\n\n"); + for (unsigned i = 0; i < ARRAY_SIZE(established_timings12); i++) { + unsigned char dmt_id = established_timings12[i].dmt_id; + const struct timings *t; + char type[16]; - /* 15/13. Find pixel clock frequency (kHz) */ - Clock = ((double)HTotal / HPeriod) * 1000.0; - Clock -= Clock % CVT_CLOCK_STEP; + if (dmt_id) { + sprintf(type, "DMT 0x%02x", dmt_id); + t = find_dmt_id(dmt_id); + } else { + t = &established_timings12[i].t; + sprintf(type, "%-8s", established_timings12[i].type); + } + print_timings("", t, type, "", false, false); } - else { /* Reduced blanking */ - /* Minimum vertical blanking interval time (µs) - default 460 */ -#define CVT_RB_MIN_VBLANK 460.0 - - /* Fixed number of clocks for horizontal sync */ -#define CVT_RB_H_SYNC 32.0 - - /* Fixed number of clocks for horizontal blanking */ -#define CVT_RB1_H_BLANK 160.0 // RB1 & RB3 with RB_FLAG set -#define CVT_RB2_H_BLANK 80.0 // RB2 & RB3 with RB_FLAG cleared - - /* Fixed number of lines for vertical front porch - default 3 */ -#define CVT_RB1_V_FPORCH 3 -#define CVT_RB2_V_FPORCH 1 -#define CVT_RB3_V_FIELD_RATE_PPM_ADJ 350.0 -#define CVT_RB2_CLOCK_STEP 1 - - int VBILines; - double h_blank = (t.rb & ~RB_FLAG) == 1 ? CVT_RB1_H_BLANK : CVT_RB2_H_BLANK; - int v_fporch = t.rb == 1 ? CVT_RB1_V_FPORCH : CVT_RB2_V_FPORCH; - unsigned clock_step = t.rb == 1 ? CVT_CLOCK_STEP : CVT_RB2_CLOCK_STEP; - - if (t.rb == 3) - VFieldRate += VFieldRate * (CVT_RB3_V_FIELD_RATE_PPM_ADJ / 1000000.0); - - /* 8. Estimate Horizontal period. */ - HPeriod = ((double) (1000000.0 / VFieldRate - CVT_RB_MIN_VBLANK)) / VDisplayRnd; - - /* 9. Find number of lines in vertical blanking */ - VBILines = ((double) CVT_RB_MIN_VBLANK) / HPeriod; - VBILines++; - - /* 10. Check if vertical blanking is sufficient */ - if (VBILines < (v_fporch + VSync + CVT_MIN_V_BPORCH)) - VBILines = v_fporch + VSync + CVT_MIN_V_BPORCH; - - /* 11. Find total number of lines in vertical field */ - VTotal = VDisplayRnd + VBILines; - - /* 12. Find total number of pixels in a line */ - HTotal = HDisplay + h_blank; - - /* Fill in HSync values */ - HSyncEnd = HDisplay + h_blank / 2; - HSyncStart = HSyncEnd - CVT_RB_H_SYNC; - - /* Fill in VSync values */ - VSyncStart = VDisplay + v_fporch; - VSyncEnd = VSyncStart + VSync; + printf("\nEstablished timings III:\n\n"); + for (unsigned i = 0; i < ARRAY_SIZE(established_timings3_dmt_ids); i++) { + unsigned char dmt_id = established_timings3_dmt_ids[i]; + char type[16]; - /* 15/13. Find pixel clock frequency (kHz) */ - double clk_khz = ((double)VFieldRate * VTotal * HTotal) / 1000.0; - if (t.rb < 3) - Clock = clock_step * floor(clk_khz / clock_step); - else - Clock = clock_step * ceil(clk_khz / clock_step); + sprintf(type, "DMT 0x%02x", dmt_id); + print_timings("", find_dmt_id(dmt_id), type, "", false, false); } - t.pixclk_khz = Clock; - - t.pos_pol_hsync = t.rb; - t.pos_pol_vsync = !t.rb; - t.vfp = VSyncStart - VDisplay; - t.vsync = VSyncEnd - VSyncStart; - t.vbp = VTotal - VSyncEnd; - t.hfp = HSyncStart - HDisplay; - t.hsync = HSyncEnd - HSyncStart; - t.hbp = HTotal - HSyncEnd; - t.interlaced = false; } -timings edid_state::calc_cvt_mode(unsigned refresh, unsigned hact, unsigned vact, unsigned rb) +void edid_state::list_dmts() { - timings t = {}; - - t.hact = hact; - t.vact = vact; - t.rb = rb; - calc_ratio(&t); - edid_cvt_mode(refresh, t); - return t; + char type[16]; + + for (unsigned i = 0; i < ARRAY_SIZE(dmt_timings); i++) { + sprintf(type, "DMT 0x%02x", dmt_timings[i].dmt_id); + std::string flags; + if (dmt_timings[i].std_id) + flags += std::string("STD: ") + + utohex(dmt_timings[i].std_id >> 8) + " " + + utohex(dmt_timings[i].std_id & 0xff); + if (dmt_timings[i].cvt_id) + add_str(flags, std::string("CVT: ") + + utohex(dmt_timings[i].cvt_id >> 16) + " " + + utohex((dmt_timings[i].cvt_id >> 8) & 0xff) + " " + + utohex(dmt_timings[i].cvt_id & 0xff)); + print_timings("", &dmt_timings[i].t, type, flags.c_str(), false, false); + } } void edid_state::detailed_cvt_descriptor(const char *prefix, const unsigned char *x, bool first) @@ -794,7 +469,7 @@ void edid_state::detailed_cvt_descriptor(const char *prefix, const unsigned char print_timings(prefix, &cvt_t, "CVT", preferred == 3 ? s_pref : ""); } if (x[2] & 0x01) { - cvt_t.rb = 1; + cvt_t.rb = RB_CVT_V1; edid_cvt_mode(60, cvt_t); print_timings(prefix, &cvt_t, "CVT", preferred == 4 ? s_pref : ""); } @@ -838,7 +513,7 @@ char *extract_string(const unsigned char *x, unsigned len) } void edid_state::print_standard_timing(const char *prefix, unsigned char b1, unsigned char b2, - bool gtf_only, unsigned vrefresh_offset) + bool gtf_only, bool show_both) { const struct timings *t; struct timings formula = {}; @@ -862,7 +537,7 @@ void edid_state::print_standard_timing(const char *prefix, unsigned char b1, uns hact = (b1 + 31) * 8; switch ((b2 >> 6) & 0x3) { case 0x00: - if (gtf_only || base.edid_minor >= 3) { + if (gtf_only || show_both || base.edid_minor >= 3) { hratio = 16; vratio = 10; } else { @@ -885,17 +560,18 @@ void edid_state::print_standard_timing(const char *prefix, unsigned char b1, uns } vact = (double)hact * vratio / hratio; vact = 8 * ((vact + 7) / 8); - refresh = vrefresh_offset + (b2 & 0x3f); + refresh = (b2 & 0x3f) + 60; formula.hact = hact; formula.vact = vact; formula.hratio = hratio; formula.vratio = vratio; - if (!gtf_only && base.edid_minor >= 4) { - if (base.supports_cvt) { + if (!gtf_only && (show_both || base.edid_minor >= 4)) { + if (show_both || base.supports_cvt) { edid_cvt_mode(refresh, formula); - print_timings(prefix, &formula, "CVT ", "EDID 1.4 source"); + print_timings(prefix, &formula, "CVT ", + show_both ? "" : "EDID 1.4 source"); } /* * An EDID 1.3 source will assume GTF, so both GTF and CVT @@ -1380,8 +1056,16 @@ void edid_state::preparse_detailed_block(const unsigned char *x) switch (x[10]) { case 0x00: /* default gtf */ + base.supports_gtf = true; + break; case 0x02: /* secondary gtf curve */ base.supports_gtf = true; + base.supports_sec_gtf = !memchk(x + 12, 6); + base.sec_gtf_start_freq = x[12] * 2; + base.C = x[13] / 2.0; + base.M = (x[15] << 8) | x[14]; + base.K = x[16]; + base.J = x[17] / 2.0; break; case 0x04: /* cvt */ if (base.edid_minor >= 4) { @@ -1446,7 +1130,7 @@ void edid_state::detailed_block(const unsigned char *x) case 0xf7: data_block = "Established timings III"; printf(" %s:\n", data_block.c_str()); - for (i = 0; i < 44; i++) + for (i = 0; i < ARRAY_SIZE(established_timings3_dmt_ids); i++) if (x[6 + i / 8] & (1 << (7 - i % 8))) { unsigned char dmt_id = established_timings3_dmt_ids[i]; char type[16]; @@ -1579,7 +1263,7 @@ void edid_state::parse_base_block(const unsigned char *x) { time_t the_time; struct tm *ptm; - int analog, i; + int analog; unsigned col_x, col_y; bool has_preferred_timing = false; @@ -1813,7 +1497,7 @@ void edid_state::parse_base_block(const unsigned char *x) data_block = "Established Timings I & II"; if (x[0x23] || x[0x24] || x[0x25]) { printf(" %s:\n", data_block.c_str()); - for (i = 0; i < 17; i++) { + for (unsigned i = 0; i < ARRAY_SIZE(established_timings12); i++) { if (x[0x23 + i / 8] & (1 << (7 - i % 8))) { unsigned char dmt_id = established_timings12[i].dmt_id; const struct timings *t; @@ -1845,7 +1529,7 @@ void edid_state::parse_base_block(const unsigned char *x) data_block = "Standard Timings"; bool found = false; - for (i = 0; i < 8; i++) { + for (unsigned i = 0; i < 8; i++) { if (x[0x26 + i * 2] != 0x01 || x[0x26 + i * 2 + 1] != 0x01) { found = true; break; @@ -1853,7 +1537,7 @@ void edid_state::parse_base_block(const unsigned char *x) } if (found) { printf(" %s:\n", data_block.c_str()); - for (i = 0; i < 8; i++) + for (unsigned i = 0; i < 8; i++) print_standard_timing(" ", x[0x26 + i * 2], x[0x26 + i * 2 + 1]); } else { printf(" %s: none\n", data_block.c_str()); @@ -1973,6 +1657,12 @@ void edid_state::check_base_block() dtd_max_hsize_mm <= 2559 && dtd_max_vsize_mm <= 2559) { fail("The DTD image sizes all fit inside 255x255cm, but no display size was set.\n"); } + // Secondary GTF curves start at a specific frequency. Any legacy timings + // that have a positive hsync and negative vsync must be less than that + // frequency to avoid confusion. + if (base.supports_sec_gtf && base.max_pos_neg_hor_freq_khz >= base.sec_gtf_start_freq) + fail("Second GTF start frequency %u is less than the highest P/N frequency %u.\n", + base.sec_gtf_start_freq, base.max_pos_neg_hor_freq_khz); if (base.edid_minor == 3 && num_blocks > 2 && !block_map.saw_block_1) fail("EDID 1.3 requires a Block Map Extension in Block 1 if there are more than 2 blocks in the EDID.\n"); if (base.edid_minor == 3 && num_blocks > 128 && !block_map.saw_block_128) diff --git a/parse-cta-block.cpp b/parse-cta-block.cpp index e3f0dfd..f881a2b 100644 --- a/parse-cta-block.cpp +++ b/parse-cta-block.cpp @@ -201,7 +201,7 @@ const struct timings *find_vic_id(unsigned char vic) { if (vic > 0 && vic <= ARRAY_SIZE(edid_cta_modes1)) return edid_cta_modes1 + vic - 1; - if (vic >= 193 && vic <= ARRAY_SIZE(edid_cta_modes2) + 193) + if (vic >= 193 && vic < ARRAY_SIZE(edid_cta_modes2) + 193) return edid_cta_modes2 + vic - 193; return NULL; } @@ -213,6 +213,30 @@ const struct timings *find_hdmi_vic_id(unsigned char hdmi_vic) return NULL; } +void edid_state::cta_list_vics() +{ + char type[16]; + for (unsigned vic = 1; vic <= ARRAY_SIZE(edid_cta_modes1); vic++) { + sprintf(type, "VIC %3u", vic); + print_timings("", &edid_cta_modes1[vic - 1], type, "", false, false); + } + for (unsigned vic = 193; vic < ARRAY_SIZE(edid_cta_modes2) + 193; vic++) { + sprintf(type, "VIC %3u", vic); + print_timings("", &edid_cta_modes2[vic - 193], type, "", false, false); + } +} + +void edid_state::cta_list_hdmi_vics() +{ + for (unsigned i = 0; i < ARRAY_SIZE(edid_hdmi_mode_map); i++) { + unsigned vic = edid_hdmi_mode_map[i]; + char type[16]; + + sprintf(type, "HDMI VIC %u", i + 1); + print_timings("", find_vic_id(vic), type, "", false, false); + } +} + static std::string audio_ext_format(unsigned char x) { if (x >= 1 && x <= 3) diff --git a/parse-displayid-block.cpp b/parse-displayid-block.cpp index ca3e73d..a1c0eee 100644 --- a/parse-displayid-block.cpp +++ b/parse-displayid-block.cpp @@ -336,14 +336,15 @@ void edid_state::parse_displayid_type_1_7_timing(const unsigned char *x, unsigned vtot = t.vact + t.vfp + t.vsync + t.vbp; unsigned refresh = (t.pixclk_khz * 1000ULL) / (htot * vtot); - for (unsigned rb = 0; rb <= 3; rb++) { - timings cvt_t = calc_cvt_mode(refresh, t.hact, t.vact, rb); + for (unsigned rb = 0; rb <= RB_CVT_V3; rb++) { + timings cvt_t = calc_cvt_mode(t.hact, t.vact, refresh, rb); if (match_timings(t, cvt_t)) { fail("This T7VTDB can be represented as a T10VTDB.\n"); return; } } - timings cvt_t = calc_cvt_mode(refresh, t.hact, t.vact, 3 | RB_FLAG); + timings cvt_t = calc_cvt_mode(t.hact, t.vact, refresh, RB_CVT_V3, + false, false, true); if (match_timings(t, cvt_t)) fail("This T7VTDB can be represented as a T10VTDB.\n"); } @@ -461,7 +462,7 @@ void edid_state::parse_displayid_type_3_timing(const unsigned char *x) break; } - t.rb = ((x[0] & 0x70) >> 4) == 1; + t.rb = ((x[0] & 0x70) >> 4) == 1 ? RB_CVT_V1 : 0; t.hact = 8 + 8 * x[1]; t.vact = t.hact * t.vratio / t.hratio; @@ -939,7 +940,7 @@ void edid_state::parse_displayid_type_5_timing(const unsigned char *x) if (x[0] & 0x10) s += ", refresh rate * (1000/1001) supported"; - t.rb = 2; + t.rb = RB_CVT_V2; if ((x[0] & 0x03) == 1) warn("Unexpected use of 'custom reduced blanking'.\n"); else if ((x[0] & 0x03) > 1) @@ -1223,8 +1224,8 @@ void edid_state::parse_displayid_type_9_timing(const unsigned char *x) s += ", refresh rate * (1000/1001) supported"; switch (x[0] & 0x07) { - case 1: t.rb = 1; break; - case 2: t.rb = 2; break; + case 1: t.rb = RB_CVT_V1; break; + case 2: t.rb = RB_CVT_V2; break; default: break; } @@ -1397,16 +1398,17 @@ void edid_state::parse_displayid_type_10_timing(const unsigned char *x, bool is_ } switch (x[0] & 0x07) { - case 1: t.rb = 1; break; - case 2: t.rb = 2; break; - case 3: t.rb = 3; break; + case 1: t.rb = RB_CVT_V1; break; + case 2: t.rb = RB_CVT_V2; break; + case 3: t.rb = RB_CVT_V3; break; default: break; } if (x[0] & 0x10) { - if (t.rb == 2) { + if (t.rb == RB_CVT_V2) { s += ", refresh rate * (1000/1001) supported"; - } else if (t.rb == 3) { + t.rb |= RB_FLAG; + } else if (t.rb == RB_CVT_V3) { s += ", hblank is 160 pixels"; t.rb |= RB_FLAG; } else { diff --git a/parse-vtb-ext-block.cpp b/parse-vtb-ext-block.cpp index 4936f1e..05d54f4 100644 --- a/parse-vtb-ext-block.cpp +++ b/parse-vtb-ext-block.cpp @@ -29,12 +29,14 @@ void edid_state::parse_vtb_ext_block(const unsigned char *x) detailed_cvt_descriptor(" ", x, false); } if (num_st) { + // Note: the VTB-EXT standard has a mistake in the example EDID + // that it provides: there the refresh rate (bits 5-0 of the + // second byte) is set to 60 for 60 Hz, but this should be 0 + // since the actual refresh rate is the value + 60. + // + // The documentation itself is correct, though. printf(" Standard Timings:\n"); - for (unsigned i = 0; i < num_st; i++, x += 2) { - if ((x[1] & 0x3f) >= 60) - print_standard_timing(" ", x[0], x[1] - 60, true); - else - print_standard_timing(" ", x[0], x[1], true, 0); - } + for (unsigned i = 0; i < num_st; i++, x += 2) + print_standard_timing(" ", x[0], x[1], true); } } |