| // SPDX-License-Identifier: MIT |
| /* |
| * Copyright 2006-2012 Red Hat, Inc. |
| * Copyright 2018-2020 Cisco Systems, Inc. and/or its affiliates. All rights reserved. |
| * |
| * Author: Adam Jackson <[email protected]> |
| * Maintainer: Hans Verkuil <[email protected]> |
| */ |
| |
| #ifndef __EDID_DECODE_H_ |
| #define __EDID_DECODE_H_ |
| |
| #include <string> |
| #include <vector> |
| #include <set> |
| #include <string.h> |
| |
| #define ARRAY_SIZE(x) (sizeof(x) / sizeof(*(x))) |
| #define min(a, b) ((a) < (b) ? (a) : (b)) |
| #define max(a, b) ((a) > (b) ? (a) : (b)) |
| |
| #define EDID_PAGE_SIZE 128U |
| #define EDID_MAX_BLOCKS 256U |
| |
| #define RB_ALT (1U << 7) |
| |
| #define RB_NONE (0U) |
| #define RB_CVT_V1 (1U) |
| #define RB_CVT_V2 (2U) |
| #define RB_CVT_V3 (3U) |
| #define RB_GTF (4U) |
| |
| // 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. |
| // |
| // The sequence of the various video parameters is as follows: |
| // |
| // border - front porch - sync - back porch - border - active video |
| // |
| // Note: this is slightly different from EDID 1.4 which calls |
| // 'active video' as 'addressable video' and the EDID 1.4 term |
| // 'active video' includes the borders. |
| // |
| // But since borders are rarely used, the term 'active video' will |
| // typically be the same as 'addressable video', and that's how I |
| // use it. |
| struct timings { |
| // 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: CVT reduced blanking version 1 |
| // 2: CVT reduced blanking version 2 |
| // 2 | RB_ALT: CVT reduced blanking version 2 video-optimized (1000/1001 fps) |
| // 3: CVT reduced blanking version 3 |
| // 3 | RB_ALT: v3 with a horizontal blank of 160 |
| // 4: GTF Secondary Curve |
| unsigned rb; |
| bool interlaced; |
| // The horizontal frontporch may be negative in GTF calculations, |
| // so use int instead of unsigned for hfp. Example: 292x176@76. |
| int hfp; |
| unsigned hsync; |
| // The backporch may be negative in buggy detailed timings. |
| // So use int instead of unsigned for hbp and vbp. |
| int hbp; |
| bool pos_pol_hsync; |
| // For interlaced formats the vertical front porch of the Even Field |
| // is actually a half-line longer. |
| unsigned vfp, vsync; |
| // For interlaced formats the vertical back porch of the Odd Field |
| // is actually a half-line longer. |
| int vbp; |
| bool pos_pol_vsync; |
| unsigned hborder, vborder; |
| bool even_vtotal; // special for VIC 39 |
| bool no_pol_vsync; // digital composite signals have no vsync polarity |
| unsigned hsize_mm, vsize_mm; |
| bool ycbcr420; // YCbCr 4:2:0 encoding |
| }; |
| |
| struct timings_ext { |
| timings_ext() |
| { |
| memset(&t, 0, sizeof(t)); |
| } |
| timings_ext(unsigned svr, const std::string &_type) |
| { |
| memset(&t, 0, sizeof(t)); |
| t.hact = svr; |
| type = _type; |
| } |
| timings_ext(const timings &_t, const std::string &_type, const std::string &_flags) |
| { |
| t = _t; |
| type = _type; |
| flags = _flags; |
| } |
| |
| bool is_valid() const { return t.hact; } |
| bool has_svr() const { return t.hact && !t.vact; } |
| unsigned svr() const { return t.hact; } |
| timings t; |
| std::string type; |
| 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 { |
| edid_state() |
| { |
| // Global state |
| edid_size = num_blocks = block_nr = 0; |
| max_hor_freq_hz = max_vert_freq_hz = max_pixclk_khz = 0; |
| min_hor_freq_hz = 0xffffff; |
| min_vert_freq_hz = 0xffffffff; |
| dtd_max_vsize_mm = dtd_max_hsize_mm = 0; |
| warnings = failures = 0; |
| has_cta = has_dispid = false; |
| hide_serial_numbers = false; |
| |
| // Base block state |
| base.edid_minor = 0; |
| base.has_name_descriptor = base.has_display_range_descriptor = |
| base.has_serial_number = base.has_serial_string = |
| base.supports_continuous_freq = base.supports_gtf = |
| 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 = |
| base.min_display_vert_freq_hz = base.max_display_vert_freq_hz = |
| base.max_display_pixclk_khz = base.max_display_width_mm = |
| base.max_display_height_mm = 0; |
| |
| // CTA-861 block state |
| cta.has_vic_1 = cta.first_svd_might_be_preferred = cta.has_sldb = |
| cta.has_hdmi = cta.has_vcdb = cta.has_vfpdb = false; |
| cta.last_block_was_hdmi_vsdb = cta.have_hf_vsdb = cta.have_hf_scdb = false; |
| cta.first_block = cta.first_svd = true; |
| cta.supported_hdmi_vic_codes = cta.supported_hdmi_vic_vsb_codes = 0; |
| memset(cta.vics, 0, sizeof(cta.vics)); |
| memset(cta.preparsed_has_vic, 0, sizeof(cta.preparsed_has_vic)); |
| cta.preparsed_phys_addr = 0xffff; |
| cta.preparsed_speaker_count = 0; |
| cta.preparsed_sld = false; |
| cta.preparsed_sld_has_coord = false; |
| cta.preparsed_total_dtds = 0; |
| cta.preparsed_total_vtdbs = 0; |
| cta.preparsed_has_t8vtdb = false; |
| |
| // DisplayID block state |
| dispid.version = 0; |
| dispid.native_width = dispid.native_height = 0; |
| dispid.preparsed_color_ids = dispid.preparsed_xfer_ids = 0; |
| dispid.preparsed_displayid_blocks = 0; |
| dispid.is_base_block = true; |
| dispid.is_display = dispid.has_product_identification = |
| dispid.has_display_parameters = dispid.has_type_1_7 = |
| dispid.has_display_interface_features = false; |
| |
| // Block Map block state |
| block_map.saw_block_1 = false; |
| block_map.saw_block_128 = false; |
| } |
| |
| // Global state |
| unsigned edid_size; |
| unsigned num_blocks; |
| unsigned block_nr; |
| std::string block; |
| std::string data_block; |
| bool has_cta; |
| bool has_dispid; |
| bool hide_serial_numbers; |
| |
| unsigned min_hor_freq_hz; |
| unsigned max_hor_freq_hz; |
| double min_vert_freq_hz; |
| double max_vert_freq_hz; |
| unsigned max_pixclk_khz; |
| unsigned dtd_max_hsize_mm; |
| unsigned dtd_max_vsize_mm; |
| |
| unsigned warnings; |
| unsigned failures; |
| |
| // Base block state |
| struct { |
| unsigned edid_minor; |
| bool has_name_descriptor; |
| bool has_display_range_descriptor; |
| bool has_serial_number; |
| 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; |
| unsigned dtd_cnt; |
| bool seen_non_detailed_descriptor; |
| bool has_640x480p60_est_timing; |
| bool preferred_is_also_native; |
| timings_ext preferred_timing; |
| |
| unsigned min_display_hor_freq_hz; |
| unsigned max_display_hor_freq_hz; |
| unsigned min_display_vert_freq_hz; |
| unsigned max_display_vert_freq_hz; |
| 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 |
| struct { |
| unsigned preparsed_total_dtds; |
| vec_timings_ext vec_dtds; |
| unsigned preparsed_total_vtdbs; |
| vec_timings_ext vec_vtdbs; |
| vec_timings_ext preferred_timings; |
| bool preparsed_has_t8vtdb; |
| // Keep track of the found Tag/Extended Tag pairs. |
| // The unsigned value is equal to: (tag << 8) | ext_tag |
| std::set<unsigned> found_tags; |
| timings_ext t8vtdb; |
| vec_timings_ext native_timings; |
| bool has_vic_1; |
| bool first_svd_might_be_preferred; |
| unsigned char byte3; |
| bool has_hdmi; |
| bool has_vcdb; |
| bool has_vfpdb; |
| unsigned preparsed_speaker_count; |
| bool preparsed_sld_has_coord; |
| bool preparsed_sld; |
| bool has_sldb; |
| unsigned short preparsed_phys_addr; |
| bool last_block_was_hdmi_vsdb; |
| bool have_hf_vsdb, have_hf_scdb; |
| bool first_block; |
| bool first_svd; |
| unsigned supported_hdmi_vic_codes; |
| unsigned supported_hdmi_vic_vsb_codes; |
| unsigned short vics[256][2]; |
| bool preparsed_has_vic[2][256]; |
| std::vector<unsigned char> preparsed_svds[2]; |
| } cta; |
| |
| // DisplayID block state |
| struct { |
| unsigned char version; |
| unsigned short preparsed_color_ids; |
| unsigned short preparsed_xfer_ids; |
| unsigned preparsed_displayid_blocks; |
| bool is_base_block; |
| bool is_display; |
| bool has_product_identification; |
| bool has_display_parameters; |
| bool has_type_1_7; |
| bool has_display_interface_features; |
| vec_timings_ext preferred_timings; |
| unsigned native_width, native_height; |
| // Keep track of the found CTA-861 Tag/Extended Tag pairs. |
| // The unsigned value is equal to: (tag << 8) | ext_tag |
| std::set<unsigned> found_tags; |
| } dispid; |
| |
| // Block Map block state |
| struct { |
| bool saw_block_1; |
| bool saw_block_128; |
| } block_map; |
| |
| std::string dtd_type(unsigned dtd); |
| std::string dtd_type() { return dtd_type(base.dtd_cnt); } |
| bool print_timings(const char *prefix, const struct timings *t, |
| const char *type, const char *flags = "", |
| bool detailed = false, bool do_checks = true); |
| bool print_timings(const char *prefix, const struct timings_ext &t, |
| bool detailed = false, bool do_checks = true) |
| { |
| return print_timings(prefix, &t.t, t.type.c_str(), t.flags.c_str(), |
| 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, |
| unsigned rb_h_blank = 0); |
| void edid_cvt_mode(unsigned refresh, struct timings &t); |
| 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, 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, |
| bool base_or_cta = true); |
| void preparse_detailed_block(const unsigned char *x); |
| 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 hdmi_latency(unsigned char vid_lat, unsigned char aud_lat, bool is_ilaced); |
| void cta_vcdb(const unsigned char *x, unsigned length); |
| void cta_svd(const unsigned char *x, unsigned n, bool for_ycbcr420); |
| void cta_y420cmdb(const unsigned char *x, unsigned length); |
| void cta_vfpdb(const unsigned char *x, unsigned length); |
| void cta_rcdb(const unsigned char *x, unsigned length); |
| void cta_sldb(const unsigned char *x, unsigned length); |
| void cta_preparse_sldb(const unsigned char *x, unsigned length); |
| void cta_hdmi_block(const unsigned char *x, unsigned length); |
| void cta_displayid_type_7(const unsigned char *x, unsigned length); |
| void cta_displayid_type_8(const unsigned char *x, unsigned length); |
| void cta_displayid_type_10(const unsigned char *x, unsigned length); |
| void cta_ext_block(const unsigned char *x, unsigned length, bool duplicate); |
| void cta_block(const unsigned char *x, bool duplicate); |
| void preparse_cta_block(const unsigned char *x); |
| void parse_cta_block(const unsigned char *x); |
| 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); |
| void parse_display_caps(const unsigned char *x); |
| void parse_display_xfer(const unsigned char *x); |
| void parse_di_ext_block(const unsigned char *x); |
| |
| void check_displayid_datablock_revision(unsigned char hdr, |
| unsigned char valid_flags = 0, |
| unsigned char rev = 0); |
| void parse_displayid_product_id(const unsigned char *x); |
| std::string product_type(unsigned char x, bool heading); |
| void parse_displayid_interface_features(const unsigned char *x); |
| void parse_displayid_parameters(const unsigned char *x); |
| void parse_displayid_parameters_v2(const unsigned char *x, unsigned block_rev); |
| void parse_displayid_display_intf(const unsigned char *x); |
| void parse_displayid_color_characteristics(const unsigned char *x); |
| void parse_displayid_transfer_characteristics(const unsigned char *x); |
| void parse_displayid_stereo_display_intf(const unsigned char *x); |
| void parse_displayid_type_1_7_timing(const unsigned char *x, |
| bool type7, unsigned block_rev, bool is_cta = false); |
| void parse_displayid_type_2_timing(const unsigned char *x); |
| void parse_displayid_type_3_timing(const unsigned char *x); |
| void parse_displayid_type_4_8_timing(unsigned char type, unsigned short id, bool is_cta = false); |
| void parse_displayid_video_timing_range_limits(const unsigned char *x); |
| void parse_displayid_string(const unsigned char *x); |
| void parse_displayid_display_device(const unsigned char *x); |
| void parse_displayid_intf_power_sequencing(const unsigned char *x); |
| void parse_displayid_type_5_timing(const unsigned char *x); |
| void parse_displayid_tiled_display_topology(const unsigned char *x, bool is_v2); |
| void parse_displayid_type_6_timing(const unsigned char *x); |
| void parse_displayid_type_9_timing(const unsigned char *x); |
| void parse_displayid_dynamic_video_timings_range_limits(const unsigned char *x); |
| void parse_displayid_ContainerID(const unsigned char *x); |
| void parse_displayid_type_10_timing(const unsigned char *x, bool is_cta = false); |
| void preparse_displayid_block(const unsigned char *x); |
| void parse_displayid_block(const unsigned char *x); |
| void parse_displayid_vesa(const unsigned char *x); |
| void parse_displayid_cta_data_block(const unsigned char *x); |
| void check_displayid_blocks(); |
| |
| void parse_vtb_ext_block(const unsigned char *x); |
| |
| void parse_string_table(const unsigned char *x); |
| void parse_ls_ext_block(const unsigned char *x); |
| |
| void parse_block_map(const unsigned char *x); |
| |
| void preparse_extension(const unsigned char *x); |
| void parse_extension(const unsigned char *x); |
| int parse_edid(); |
| }; |
| |
| static inline void add_str(std::string &s, const std::string &add) |
| { |
| if (s.empty()) |
| s = add; |
| else if (!add.empty()) |
| s = s + ", " + add; |
| } |
| |
| void msg(bool is_warn, const char *fmt, ...); |
| |
| #ifdef _WIN32 |
| |
| #define warn(fmt, ...) msg(true, fmt, __VA_ARGS__) |
| #define warn_once(fmt, ...) \ |
| do { \ |
| static bool shown_warn; \ |
| if (!shown_warn) { \ |
| shown_warn = true; \ |
| msg(true, fmt, __VA_ARGS__); \ |
| } \ |
| } while (0) |
| #define fail(fmt, ...) msg(false, fmt, __VA_ARGS__) |
| |
| #else |
| |
| #define warn(fmt, args...) msg(true, fmt, ##args) |
| #define warn_once(fmt, args...) \ |
| do { \ |
| static bool shown_warn; \ |
| if (!shown_warn) { \ |
| shown_warn = true; \ |
| msg(true, fmt, ##args); \ |
| } \ |
| } while (0) |
| #define fail(fmt, args...) msg(false, fmt, ##args) |
| |
| #endif |
| |
| void do_checksum(const char *prefix, const unsigned char *x, size_t len); |
| std::string utohex(unsigned char x); |
| std::string ouitohex(unsigned oui); |
| std::string containerid2s(const unsigned char *x); |
| bool memchk(const unsigned char *x, unsigned len, unsigned char v = 0); |
| void hex_block(const char *prefix, const unsigned char *x, unsigned length, |
| bool show_ascii = true, unsigned step = 16); |
| std::string block_name(unsigned char block); |
| void calc_ratio(struct timings *t); |
| const char *oui_name(unsigned oui, bool reverse = false); |
| |
| bool timings_close_match(const timings &t1, const timings &t2); |
| const struct timings *find_dmt_id(unsigned char dmt_id); |
| const struct timings *close_match_to_dmt(const timings &t, unsigned &dmt); |
| const struct timings *find_vic_id(unsigned char vic); |
| const struct timings *find_hdmi_vic_id(unsigned char hdmi_vic); |
| const struct timings *cta_close_match_to_vic(const timings &t, unsigned &vic); |
| unsigned char hdmi_vic_to_vic(unsigned char hdmi_vic); |
| char *extract_string(const unsigned char *x, unsigned len); |
| |
| #endif |