From e4a44377fb53f13ca05bb2b4675989c45162042e Mon Sep 17 00:00:00 2001 From: vasilito Date: Mon, 3 Aug 2026 22:19:38 +0300 Subject: [PATCH] libdisplay-info: replace the stub with upstream 0.4.0 The recipe was not building libdisplay-info. It synthesized its own meson.build declaring `version: '0.2.3'` and compiled a hand-written stub: one di.c and four headers, 518 lines, 20 exported functions. 0.2.3 does not exist upstream -- the tags are 0.1.0, 0.1.1, 0.2.0, 0.3.0 and 0.4.0. That fabricated version satisfied kwin's pkg_check_modules(libdisplayinfo REQUIRED IMPORTED_TARGET libdisplay-info>=0.2.0) so kwin configured cleanly and then failed to compile utils/edid.cpp on di_info_get_default_color_primaries, di_info_get_hdr_static_metadata, di_info_get_supported_signal_colorimetry and di_edid_display_descriptor. A version gate satisfied by a declaration rather than an implementation. Now vendors upstream 0.4.0 (gitlab.freedesktop.org/emersion/libdisplay-info): 14 C files, 10898 lines, 10 headers, 94 exported symbols. 0.4.0 rather than 0.2.0 because the colorimetry and HDR static metadata accessors kwin needs post-date the 0.2.x series -- and they feed the real colour-primaries and HDR path of the display stack, so a stub returning NULL would have been wrong at runtime even if it had compiled. Red Bear delta is one hunk in source/meson.build: the unconditional subdir('di-edid-decode') and subdir('test') are commented out. Both build auxiliary executables and a shell test harness that are not part of the runtime and do not cross-compile for Redox. Library, headers and pkg-config are untouched. Verified: builds clean, stages libdisplay-info.so.0.4.0 with all four symbols kwin needs, pkg-config reports 0.4.0, and kwin's utils/edid.cpp now compiles. Co-Authored-By: Claude Opus 5 (1M context) --- .../recipes/libs/libdisplay-info/recipe.toml | 85 +- .../libs/libdisplay-info/source/.editorconfig | 18 + .../libdisplay-info/source/.gitlab-ci.yml | 158 + .../libs/libdisplay-info/source/LICENSE | 21 + .../libs/libdisplay-info/source/README.md | 141 + .../libdisplay-info/source/cta-vic-table.c | 2472 ++++++++++++++ .../libs/libdisplay-info/source/cta-vic.c | 39 + .../recipes/libs/libdisplay-info/source/cta.c | 2966 +++++++++++++++++ .../recipes/libs/libdisplay-info/source/cvt.c | 155 + .../recipes/libs/libdisplay-info/source/di.c | 401 --- .../libs/libdisplay-info/source/displayid.c | 920 +++++ .../libs/libdisplay-info/source/displayid2.c | 313 ++ .../libs/libdisplay-info/source/dmt-table.c | 1556 +++++++++ .../recipes/libs/libdisplay-info/source/dmt.c | 73 + .../libs/libdisplay-info/source/edid.c | 1627 +++++++++ .../recipes/libs/libdisplay-info/source/gtf.c | 128 + .../libs/libdisplay-info/source/hdmi-vic.c | 97 + .../libdisplay-info/source/include/bits.h | 40 + .../libs/libdisplay-info/source/include/cta.h | 287 ++ .../source/include/di-edid-decode.h | 63 + .../source/include/displayid.h | 108 + .../source/include/displayid2.h | 64 + .../libdisplay-info/source/include/edid.h | 160 + .../libdisplay-info/source/include/info.h | 28 + .../source/include/libdisplay-info/cta-vic.h | 78 + .../source/include/libdisplay-info/cta.h | 1560 ++++++++- .../source/include/libdisplay-info/cvt.h | 76 + .../include/libdisplay-info/displayid.h | 400 ++- .../include/libdisplay-info/displayid2.h | 121 + .../source/include/libdisplay-info/dmt.h | 77 + .../source/include/libdisplay-info/edid.h | 834 ++++- .../source/include/libdisplay-info/gtf.h | 72 + .../source/include/libdisplay-info/hdmi-vic.h | 54 + .../source/include/libdisplay-info/info.h | 321 +- .../libs/libdisplay-info/source/include/log.h | 24 + .../source/include/memory-stream.h | 32 + .../libs/libdisplay-info/source/info.c | 483 +++ .../source/libdisplay-info.map | 6 + .../recipes/libs/libdisplay-info/source/log.c | 33 + .../libdisplay-info/source/memory-stream.c | 36 + .../libs/libdisplay-info/source/meson.build | 132 +- .../libdisplay-info/source/tool/dump-i2c.sh | 36 + .../source/tool/gen-cta-vic.py | 113 + .../libdisplay-info/source/tool/gen-dmt.py | 149 + .../source/tool/gen-search-table.py | 91 + 45 files changed, 16074 insertions(+), 574 deletions(-) create mode 100644 local/recipes/libs/libdisplay-info/source/.editorconfig create mode 100644 local/recipes/libs/libdisplay-info/source/.gitlab-ci.yml create mode 100644 local/recipes/libs/libdisplay-info/source/LICENSE create mode 100644 local/recipes/libs/libdisplay-info/source/README.md create mode 100644 local/recipes/libs/libdisplay-info/source/cta-vic-table.c create mode 100644 local/recipes/libs/libdisplay-info/source/cta-vic.c create mode 100644 local/recipes/libs/libdisplay-info/source/cta.c create mode 100644 local/recipes/libs/libdisplay-info/source/cvt.c delete mode 100644 local/recipes/libs/libdisplay-info/source/di.c create mode 100644 local/recipes/libs/libdisplay-info/source/displayid.c create mode 100644 local/recipes/libs/libdisplay-info/source/displayid2.c create mode 100644 local/recipes/libs/libdisplay-info/source/dmt-table.c create mode 100644 local/recipes/libs/libdisplay-info/source/dmt.c create mode 100644 local/recipes/libs/libdisplay-info/source/edid.c create mode 100644 local/recipes/libs/libdisplay-info/source/gtf.c create mode 100644 local/recipes/libs/libdisplay-info/source/hdmi-vic.c create mode 100644 local/recipes/libs/libdisplay-info/source/include/bits.h create mode 100644 local/recipes/libs/libdisplay-info/source/include/cta.h create mode 100644 local/recipes/libs/libdisplay-info/source/include/di-edid-decode.h create mode 100644 local/recipes/libs/libdisplay-info/source/include/displayid.h create mode 100644 local/recipes/libs/libdisplay-info/source/include/displayid2.h create mode 100644 local/recipes/libs/libdisplay-info/source/include/edid.h create mode 100644 local/recipes/libs/libdisplay-info/source/include/info.h create mode 100644 local/recipes/libs/libdisplay-info/source/include/libdisplay-info/cta-vic.h create mode 100644 local/recipes/libs/libdisplay-info/source/include/libdisplay-info/cvt.h create mode 100644 local/recipes/libs/libdisplay-info/source/include/libdisplay-info/displayid2.h create mode 100644 local/recipes/libs/libdisplay-info/source/include/libdisplay-info/dmt.h create mode 100644 local/recipes/libs/libdisplay-info/source/include/libdisplay-info/gtf.h create mode 100644 local/recipes/libs/libdisplay-info/source/include/libdisplay-info/hdmi-vic.h create mode 100644 local/recipes/libs/libdisplay-info/source/include/log.h create mode 100644 local/recipes/libs/libdisplay-info/source/include/memory-stream.h create mode 100644 local/recipes/libs/libdisplay-info/source/info.c create mode 100644 local/recipes/libs/libdisplay-info/source/libdisplay-info.map create mode 100644 local/recipes/libs/libdisplay-info/source/log.c create mode 100644 local/recipes/libs/libdisplay-info/source/memory-stream.c create mode 100755 local/recipes/libs/libdisplay-info/source/tool/dump-i2c.sh create mode 100755 local/recipes/libs/libdisplay-info/source/tool/gen-cta-vic.py create mode 100755 local/recipes/libs/libdisplay-info/source/tool/gen-dmt.py create mode 100755 local/recipes/libs/libdisplay-info/source/tool/gen-search-table.py diff --git a/local/recipes/libs/libdisplay-info/recipe.toml b/local/recipes/libs/libdisplay-info/recipe.toml index 2087d2795f..0ca39916c6 100644 --- a/local/recipes/libs/libdisplay-info/recipe.toml +++ b/local/recipes/libs/libdisplay-info/recipe.toml @@ -2,55 +2,40 @@ path = "source" [build] -template = "custom" -script = ''' -DYNAMIC_INIT - -python3 - <<'PY' "${COOKBOOK_SOURCE}" -from pathlib import Path -import sys - -source_root = Path(sys.argv[1]) -meson_build = source_root / "meson.build" - -if not meson_build.exists(): - meson_build.write_text( - """project('libdisplay-info', 'c', - version: '0.2.3', - meson_version: '>= 0.54.0', - default_options: ['warning_level=1', 'buildtype=debugoptimized']) - -inc = include_directories('include') - -libdisplay_info = shared_library('display-info', - 'di.c', - include_directories: inc, - version: meson.project_version(), - install: true) - -install_headers( - 'include/libdisplay-info/cta.h', - 'include/libdisplay-info/displayid.h', - 'include/libdisplay-info/edid.h', - 'include/libdisplay-info/info.h', - subdir: 'libdisplay-info', -) - -pkg = import('pkgconfig') -pkg.generate( - libdisplay_info, - name: 'libdisplay-info', - description: 'Display identification data parsing library', - filebase: 'libdisplay-info', - version: meson.project_version(), -) -""" - ) -PY - -cookbook_meson -''' +template = "meson" [package] -version = "0.2.3" -description = "libdisplay-info — EDID and display descriptor parsing library. Real C implementation (v6.0 2026) handling base EDID vendor/product, strings, physical size, chromaticity, detailed timings, and preferred-timing metadata. CTA/DisplayID remain unsupported (bounded by Red Bear's current DRM/KMS validation surface). Replaces the prior libdisplay-info-stub that always returned NULL." +version = "0.4.0+rb0.3.2" +description = """ +libdisplay-info — EDID and DisplayID parsing library. + +Upstream libdisplay-info 0.4.0 (gitlab.freedesktop.org/emersion/libdisplay-info), +vendored under Red Bear's local-fork convention. + +This REPLACES a hand-written stub. The stub was 518 lines across one di.c and +four headers, exposing 20 functions, and it synthesized its own meson.build +declaring `version: '0.2.3'` -- a version that does not exist upstream (the tags +are 0.1.0, 0.1.1, 0.2.0, 0.3.0, 0.4.0). That fabricated version satisfied +kwin's `pkg_check_modules(libdisplay-info>=0.2.0)` while the API behind it was +absent, so kwin configured successfully and then failed to compile: + + utils/edid.cpp: 'di_info_get_default_color_primaries' was not declared + utils/edid.cpp: 'di_info_get_hdr_static_metadata' was not declared + utils/edid.cpp: 'di_info_get_supported_signal_colorimetry' was not declared + utils/edid.cpp: 'di_edid_display_descriptor' does not name a type + +Those four are why 0.4.0 rather than 0.2.0: the colorimetry and HDR static +metadata accessors kwin needs post-date the 0.2.x series. They feed the real +colour-primaries and HDR path of the display stack, so a stub returning NULL +would have been wrong at runtime even had it compiled. + +Red Bear delta (one hunk, in source/meson.build): the unconditional +`subdir('di-edid-decode')` and `subdir('test')` are commented out. Both build +auxiliary executables and a shell test harness that are not part of the runtime +and do not cross-compile cleanly for Redox. The library, its headers and its +pkg-config file are unaffected. + +The pnp-id table is generated at build time by tool/gen-search-table.py from +hwdata's pnp.ids, resolved via the host's `hwdata` pkg-config (native: true), +falling back to /usr/share/hwdata/pnp.ids. +""" diff --git a/local/recipes/libs/libdisplay-info/source/.editorconfig b/local/recipes/libs/libdisplay-info/source/.editorconfig new file mode 100644 index 0000000000..6916c80b16 --- /dev/null +++ b/local/recipes/libs/libdisplay-info/source/.editorconfig @@ -0,0 +1,18 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = tab +indent_size = 8 +max_line_length = 100 + +[*.py] +indent_style = space +indent_size = 4 + +[*.yml] +indent_style = space +indent_size = 2 diff --git a/local/recipes/libs/libdisplay-info/source/.gitlab-ci.yml b/local/recipes/libs/libdisplay-info/source/.gitlab-ci.yml new file mode 100644 index 0000000000..3b800c46b2 --- /dev/null +++ b/local/recipes/libs/libdisplay-info/source/.gitlab-ci.yml @@ -0,0 +1,158 @@ +include: + - project: 'freedesktop/ci-templates' + ref: 3f37cc0e461f5b0c815409bf6f55759f26a74e9c + file: + - '/templates/ci-fairy.yml' + - '/templates/alpine.yml' + +# When updating the prepare-container step, make sure to bump +# FDO_DISTRIBUTION_TAG, otherwise the container won't get rebuilt. +# To force a rebuild of the container, use: +# $ git push -f -o ci.variable="FDO_FORCE_REBUILD=1" +variables: + FDO_UPSTREAM_REPO: 'emersion/libdisplay-info' + FDO_DISTRIBUTION_TAG: '2025-01-20.0' + +workflow: + rules: + - if: $CI_PIPELINE_SOURCE == 'merge_request_event' + - if: $CI_PIPELINE_SOURCE == 'push' + +stages: + - "Contribution checks" + - "Prepare container" + - "Build and test" + - "Publish" + +check-mr: + extends: .fdo.ci-fairy + stage: "Contribution checks" + script: + - ci-fairy check-commits --signed-off-by + rules: + - if: '$CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "main"' + when: always + - when: never + +prepare-container: + extends: .fdo.container-build@alpine@x86_64 + stage: "Prepare container" + variables: + V4L_UTILS_COMMIT: 1316a80455ef70889bea89491f37cd69170f3ee7 + FDO_BASE_IMAGE: alpine:latest + FDO_DISTRIBUTION_PACKAGES: | + build-base clang compiler-rt meson make git gcovr py3-pygments go hwdata + perl argp-standalone gettext-dev linux-headers + FDO_DISTRIBUTION_EXEC: | + grep $V4L_UTILS_COMMIT ./subprojects/v4l-utils.wrap >/dev/null || \ + (echo "Keep v4l-utils.wrap and CI in sync!" && exit 1) + git clone https://git.linuxtv.org/v4l-utils.git + cd v4l-utils + git checkout $V4L_UTILS_COMMIT + meson setup build/ \ + -Dauto_features=disabled \ + -Dv4l-plugins=false \ + -Dv4l-wrappers=false \ + -Dv4l2-compliance-libv4l=false \ + -Dv4l2-ctl-libv4l=false \ + -Dv4l2-ctl-stream-to=false + ninja -C build/ + ninja -C build/ install + cd .. + rm -rf edid-decode + + go install git.sr.ht/~emersion/gyosu@latest + mv ~/go/bin/gyosu /usr/bin/ + rm -rf ~/go + rules: + - when: on_success + +testing-check: + extends: .fdo.distribution-image@alpine + stage: "Build and test" + script: + - | + # Check if the origin of all test EDIDs is documented + for f in ./test/data/*.edid; do + grep -q "^$(basename ${f%.edid})\s" ./test/data/README.md || + (echo "$f not in README.md" && exit 1) + done + # Check if the test references checked in the repo match what we have in CI + meson setup build/ + ninja -C build/ gen-test-data + git diff --quiet || + (echo "Checked in reference output does not match generated reference output" && exit 1) + rules: + - when: on_success + +build-gcc: + extends: .fdo.distribution-image@alpine + stage: "Build and test" + script: + - CC=gcc + CFLAGS=" -Wnull-dereference -fanalyzer -O1" + meson setup build/ + --fatal-meson-warnings + -Dwerror=true + -Db_coverage=true + - ninja -C build/ + - ninja -C build/ test + - ninja -C build/ -j1 coverage-xml coverage-html + artifacts: + when: always + paths: + - build/meson-logs/ + reports: + junit: build/meson-logs/testlog.junit.xml + coverage_report: + coverage_format: cobertura + path: build/meson-logs/coverage.xml + rules: + - when: on_success + +build-clang: + extends: .fdo.distribution-image@alpine + stage: "Build and test" + script: + - CC=clang + meson setup build/ + --fatal-meson-warnings + -Dwerror=true + -Db_sanitize=address,undefined + -Db_lundef=false + - ninja -C build/ + - UBSAN_OPTIONS=halt_on_error=1 ninja -C build/ test + artifacts: + when: always + paths: + - build/meson-logs/ + reports: + junit: build/meson-logs/testlog.junit.xml + rules: + - when: on_success + +build-docs: + extends: .fdo.distribution-image@alpine + stage: "Build and test" + script: + - gyosu -I$PWD/include/ -fexported-symbols='di_*' + -ffile-prefix-map=$PWD/include/= -fsite-name=libdisplay-info + -o public $PWD/include/libdisplay-info/ + artifacts: + paths: + - public/ + rules: + - when: on_success + +pages: + extends: .fdo.distribution-image@alpine + stage: "Publish" + script: + - "true" + artifacts: + paths: + - public/ + rules: + - if: '$CI_PROJECT_PATH == "emersion/libdisplay-info" && $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH' + when: on_success + - when: never diff --git a/local/recipes/libs/libdisplay-info/source/LICENSE b/local/recipes/libs/libdisplay-info/source/LICENSE new file mode 100644 index 0000000000..a758b1ddad --- /dev/null +++ b/local/recipes/libs/libdisplay-info/source/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 The libdisplay-info Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/local/recipes/libs/libdisplay-info/source/README.md b/local/recipes/libs/libdisplay-info/source/README.md new file mode 100644 index 0000000000..b4df38c70c --- /dev/null +++ b/local/recipes/libs/libdisplay-info/source/README.md @@ -0,0 +1,141 @@ +# libdisplay-info + +EDID and DisplayID library. + +Goals: + +- Provide a set of high-level, easy-to-use, opinionated functions as well as + low-level functions to access detailed information. +- Simplicity and correctness over performance and resource usage. +- Well-tested and fuzzed. + +Documentation is available on the [website]. + +## Using + +The public API headers are categorised as either high-level or low-level API +as per the comments in the header files. Users of libdisplay-info should prefer +high-level API over low-level API when possible. + +If high-level API lacks needed features, please propose additions to the +high-level API upstream before using low-level API to get what you need. +If the additions are rejected, you are welcome to use the low-level API. + +This policy is aimed to propagate best practises when interpreting EDID +and DisplayID information which can often be cryptic or even inconsistent. + +libdisplay-info uses [semantic versioning]. The public API is not yet stable. + +## Contributing + +Open issues and merge requests on the [GitLab project]. Discuss and ask +questions in the [#wayland] IRC channel on OFTC. + +In general, the [Wayland contribution guidelines] should be followed. In +particular, each commit must carry a Signed-off-by tag to denote that the +submitter adheres to the [Developer Certificate of Origin 1.1]. This project +follows the [freedesktop.org Contributor Covenant]. + +## Specifications + +Both EDID and DisplayID are defined by VESA and the specifications are publicly +available. There are multiple versions defined and they have multiple +mechanisms for extending the base specification. Some of the extensions also +have mechanisms for additional extensions. This sometimes makes it hard to find +where a particular data structure is defined. + +The raw data is usually read from the Display Data Channel (DDC) but other +methods of delivery exist. + +Available freely [from VESA directly] are the base EDID specifications, VESA +specified EDID extensions, as well as the base DisplayID specifications. EDID +also specifies how DisplayID can be embedded in EDID. + +* VESA Enhanced Extended Display Identification Data (E-EDID) Standard; + Release A, Revision 1; February 9, 2000; Defines EDID Structure Version 1, + Revision 3 +* VESA Enhanced Extended Display Identification Data (E-EDID) Standard; + Release A, Revision 2; September 25, 2006; Defines EDID Structure Version 1, + Revision 4 +* VESA Video Timing Block Extension Data (VTB-EXT) Standard; Release A; + November 24, 2003 +* VESA Enhanced EDID Localized String Extension Standard; Release A; + July 10, 2003 +* VESA Display Transfer Characteristics Data Block (DTCDB) Standard; + Version 1.0; August 31, 2006 +* VESA Display Information Extension Block (DI-EXT) Standard; Release A; + August 21, 2001 +* VESA Display Device Data Block (DDDB) Standard; Version 1; + September 25, 2006 +* VESA Display Identification Data (DisplayID) Standard; Version 1.3; + July 5, 2013 +* VESA Display Identification Data (DisplayID) Standard; Version 2.0; + 11 September, 2017 +* VESA Display Identification Data (DisplayID) Standard; Version 2.1; + 18 November, 2021 +* VESA Display Identification Data (DisplayID) Standard; Version 2.1a; + 18 March, 2024 + +The most common extension to both EDID and DisplayID is CTA-861 which can be +downloaded for free [from CTA]. + +* A DTV Profile for Uncompressed High Speed Digital Interfaces + (CTA-861-E, CTA-861-F, CTA-861-G, CTA-861-H, CTA-861-I) +* HDR Static Metadata Extensions (CTA-861.3-A) + +The CTA-861 specification allows for Vendor Specific Data Blocks (VSDB). Their +specifications are often proprietary and have to be reverse engineered. Some +specifications are available publicly. + +* HDMI and HDMI Forum VSDB are specified in the HDMI specification +* Microsoft EDID Extension for [HMDs and Specialized Monitors] + +The libdisplay-info structures of the low-level API always point to the +relevant specification and section therein. + +## Building + +libdisplay-info has the following dependencies: + +- [hwdata] for the PNP ID database used at build-time only. + +libdisplay-info is built using [Meson]: + + meson setup build/ + ninja -C build/ + +## Testing + +The low-level EDID library is tested against [edid-decode]. `test/data/` +contains a small collection of EDID blobs and diffs between upstream +`edid-decode` and our `di-edid-decode` clone. Our CI ensures the diffs are +up-to-date. A patch should never make the diffs grow larger. To re-generate the +test data, build `edid-decode` at the Git revision mentioned in +`.gitlab-ci.yml`, put the executable in `PATH`, and run +`ninja -C build/ gen-test-data`. + +The latest code coverage report is available on [GitLab CI][coverage]. + +## Fuzzing + +To fuzz libdisplay-info with [AFL], the library needs to be instrumented: + + CC=afl-gcc meson build/ + ninja -C build/ + afl-fuzz -i test/data/ -o afl/ build/di-edid-decode/di-edid-decode + +[website]: https://emersion.pages.freedesktop.org/libdisplay-info/ +[semantic versioning]: https://semver.org/ +[GitLab project]: https://gitlab.freedesktop.org/emersion/libdisplay-info +[#wayland]: ircs://irc.oftc.net/#wayland +[Wayland contribution guidelines]: https://gitlab.freedesktop.org/wayland/wayland/-/blob/main/CONTRIBUTING.md +[Developer Certificate of Origin 1.1]: https://developercertificate.org/ +[freedesktop.org Contributor Covenant]: https://www.freedesktop.org/wiki/CodeOfConduct/ +[from VESA directly]: https://vesa.org/vesa-standards/ +[from CTA]: https://www.cta.tech/SearchResults?search=CTA-861 +[HMDs and Specialized Monitors]: https://learn.microsoft.com/en-us/windows-hardware/drivers/display/specialized-monitors-edid-extension +[hwdata]: https://github.com/vcrhonek/hwdata +[Meson]: https://mesonbuild.com/ +[coverage]: https://gitlab.freedesktop.org/emersion/libdisplay-info/-/jobs/artifacts/main/file/build/meson-logs/coveragereport/index.html?job=build-gcc +[edid-decode]: https://git.linuxtv.org/edid-decode.git/ +[AFL]: https://lcamtuf.coredump.cx/afl/ diff --git a/local/recipes/libs/libdisplay-info/source/cta-vic-table.c b/local/recipes/libs/libdisplay-info/source/cta-vic-table.c new file mode 100644 index 0000000000..b1d0ded202 --- /dev/null +++ b/local/recipes/libs/libdisplay-info/source/cta-vic-table.c @@ -0,0 +1,2472 @@ +/* DO NOT EDIT! This file has been generated by gen-cta-vic.py from ANSI-CTA-861-I Errata FINAL.pdf. */ + +#include + +const struct di_cta_vic_video_format _di_cta_vic_video_formats[] = { + [60] = { + .vic = { .code = 60 }, + .h_active = 1280, + .v_active = 720, + .interlaced = false, + .pixel_clock_hz = 59400000, + .h_front = 1760, + .h_sync = 40, + .h_back = 220, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 5, + .v_sync = 5, + .v_back = 20, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, + }, + [65] = { + .vic = { .code = 65 }, + .h_active = 1280, + .v_active = 720, + .interlaced = false, + .pixel_clock_hz = 59400000, + .h_front = 1760, + .h_sync = 40, + .h_back = 220, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 5, + .v_sync = 5, + .v_back = 20, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [61] = { + .vic = { .code = 61 }, + .h_active = 1280, + .v_active = 720, + .interlaced = false, + .pixel_clock_hz = 74250000, + .h_front = 2420, + .h_sync = 40, + .h_back = 220, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 5, + .v_sync = 5, + .v_back = 20, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, + }, + [66] = { + .vic = { .code = 66 }, + .h_active = 1280, + .v_active = 720, + .interlaced = false, + .pixel_clock_hz = 74250000, + .h_front = 2420, + .h_sync = 40, + .h_back = 220, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 5, + .v_sync = 5, + .v_back = 20, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [62] = { + .vic = { .code = 62 }, + .h_active = 1280, + .v_active = 720, + .interlaced = false, + .pixel_clock_hz = 74250000, + .h_front = 1760, + .h_sync = 40, + .h_back = 220, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 5, + .v_sync = 5, + .v_back = 20, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, + }, + [67] = { + .vic = { .code = 67 }, + .h_active = 1280, + .v_active = 720, + .interlaced = false, + .pixel_clock_hz = 74250000, + .h_front = 1760, + .h_sync = 40, + .h_back = 220, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 5, + .v_sync = 5, + .v_back = 20, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [108] = { + .vic = { .code = 108 }, + .h_active = 1280, + .v_active = 720, + .interlaced = false, + .pixel_clock_hz = 90000000, + .h_front = 960, + .h_sync = 40, + .h_back = 220, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 5, + .v_sync = 5, + .v_back = 20, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, + }, + [109] = { + .vic = { .code = 109 }, + .h_active = 1280, + .v_active = 720, + .interlaced = false, + .pixel_clock_hz = 90000000, + .h_front = 960, + .h_sync = 40, + .h_back = 220, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 5, + .v_sync = 5, + .v_back = 20, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [79] = { + .vic = { .code = 79 }, + .h_active = 1680, + .v_active = 720, + .interlaced = false, + .pixel_clock_hz = 59400000, + .h_front = 1360, + .h_sync = 40, + .h_back = 220, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 5, + .v_sync = 5, + .v_back = 20, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [80] = { + .vic = { .code = 80 }, + .h_active = 1680, + .v_active = 720, + .interlaced = false, + .pixel_clock_hz = 59400000, + .h_front = 1228, + .h_sync = 40, + .h_back = 220, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 5, + .v_sync = 5, + .v_back = 20, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [81] = { + .vic = { .code = 81 }, + .h_active = 1680, + .v_active = 720, + .interlaced = false, + .pixel_clock_hz = 59400000, + .h_front = 700, + .h_sync = 40, + .h_back = 220, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 5, + .v_sync = 5, + .v_back = 20, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [110] = { + .vic = { .code = 110 }, + .h_active = 1680, + .v_active = 720, + .interlaced = false, + .pixel_clock_hz = 99000000, + .h_front = 810, + .h_sync = 40, + .h_back = 220, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 5, + .v_sync = 5, + .v_back = 20, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [32] = { + .vic = { .code = 32 }, + .h_active = 1920, + .v_active = 1080, + .interlaced = false, + .pixel_clock_hz = 74250000, + .h_front = 638, + .h_sync = 44, + .h_back = 148, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 4, + .v_sync = 5, + .v_back = 36, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, + }, + [72] = { + .vic = { .code = 72 }, + .h_active = 1920, + .v_active = 1080, + .interlaced = false, + .pixel_clock_hz = 74250000, + .h_front = 638, + .h_sync = 44, + .h_back = 148, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 4, + .v_sync = 5, + .v_back = 36, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [33] = { + .vic = { .code = 33 }, + .h_active = 1920, + .v_active = 1080, + .interlaced = false, + .pixel_clock_hz = 74250000, + .h_front = 528, + .h_sync = 44, + .h_back = 148, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 4, + .v_sync = 5, + .v_back = 36, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, + }, + [73] = { + .vic = { .code = 73 }, + .h_active = 1920, + .v_active = 1080, + .interlaced = false, + .pixel_clock_hz = 74250000, + .h_front = 528, + .h_sync = 44, + .h_back = 148, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 4, + .v_sync = 5, + .v_back = 36, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [34] = { + .vic = { .code = 34 }, + .h_active = 1920, + .v_active = 1080, + .interlaced = false, + .pixel_clock_hz = 74250000, + .h_front = 88, + .h_sync = 44, + .h_back = 148, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 4, + .v_sync = 5, + .v_back = 36, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, + }, + [74] = { + .vic = { .code = 74 }, + .h_active = 1920, + .v_active = 1080, + .interlaced = false, + .pixel_clock_hz = 74250000, + .h_front = 88, + .h_sync = 44, + .h_back = 148, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 4, + .v_sync = 5, + .v_back = 36, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [111] = { + .vic = { .code = 111 }, + .h_active = 1920, + .v_active = 1080, + .interlaced = false, + .pixel_clock_hz = 148500000, + .h_front = 638, + .h_sync = 44, + .h_back = 148, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 4, + .v_sync = 5, + .v_back = 36, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, + }, + [112] = { + .vic = { .code = 112 }, + .h_active = 1920, + .v_active = 1080, + .interlaced = false, + .pixel_clock_hz = 148500000, + .h_front = 638, + .h_sync = 44, + .h_back = 148, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 4, + .v_sync = 5, + .v_back = 36, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [86] = { + .vic = { .code = 86 }, + .h_active = 2560, + .v_active = 1080, + .interlaced = false, + .pixel_clock_hz = 99000000, + .h_front = 998, + .h_sync = 44, + .h_back = 148, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 4, + .v_sync = 5, + .v_back = 11, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [87] = { + .vic = { .code = 87 }, + .h_active = 2560, + .v_active = 1080, + .interlaced = false, + .pixel_clock_hz = 90000000, + .h_front = 448, + .h_sync = 44, + .h_back = 148, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 4, + .v_sync = 5, + .v_back = 36, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [88] = { + .vic = { .code = 88 }, + .h_active = 2560, + .v_active = 1080, + .interlaced = false, + .pixel_clock_hz = 118800000, + .h_front = 768, + .h_sync = 44, + .h_back = 148, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 4, + .v_sync = 5, + .v_back = 36, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [113] = { + .vic = { .code = 113 }, + .h_active = 2560, + .v_active = 1080, + .interlaced = false, + .pixel_clock_hz = 198000000, + .h_front = 998, + .h_sync = 44, + .h_back = 148, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 4, + .v_sync = 5, + .v_back = 11, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [93] = { + .vic = { .code = 93 }, + .h_active = 3840, + .v_active = 2160, + .interlaced = false, + .pixel_clock_hz = 297000000, + .h_front = 1276, + .h_sync = 88, + .h_back = 296, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 8, + .v_sync = 10, + .v_back = 72, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, + }, + [103] = { + .vic = { .code = 103 }, + .h_active = 3840, + .v_active = 2160, + .interlaced = false, + .pixel_clock_hz = 297000000, + .h_front = 1276, + .h_sync = 88, + .h_back = 296, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 8, + .v_sync = 10, + .v_back = 72, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [94] = { + .vic = { .code = 94 }, + .h_active = 3840, + .v_active = 2160, + .interlaced = false, + .pixel_clock_hz = 297000000, + .h_front = 1056, + .h_sync = 88, + .h_back = 296, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 8, + .v_sync = 10, + .v_back = 72, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, + }, + [104] = { + .vic = { .code = 104 }, + .h_active = 3840, + .v_active = 2160, + .interlaced = false, + .pixel_clock_hz = 297000000, + .h_front = 1056, + .h_sync = 88, + .h_back = 296, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 8, + .v_sync = 10, + .v_back = 72, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [95] = { + .vic = { .code = 95 }, + .h_active = 3840, + .v_active = 2160, + .interlaced = false, + .pixel_clock_hz = 297000000, + .h_front = 176, + .h_sync = 88, + .h_back = 296, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 8, + .v_sync = 10, + .v_back = 72, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, + }, + [105] = { + .vic = { .code = 105 }, + .h_active = 3840, + .v_active = 2160, + .interlaced = false, + .pixel_clock_hz = 297000000, + .h_front = 176, + .h_sync = 88, + .h_back = 296, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 8, + .v_sync = 10, + .v_back = 72, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [114] = { + .vic = { .code = 114 }, + .h_active = 3840, + .v_active = 2160, + .interlaced = false, + .pixel_clock_hz = 594000000, + .h_front = 1276, + .h_sync = 88, + .h_back = 296, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 8, + .v_sync = 10, + .v_back = 72, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, + }, + [116] = { + .vic = { .code = 116 }, + .h_active = 3840, + .v_active = 2160, + .interlaced = false, + .pixel_clock_hz = 594000000, + .h_front = 1276, + .h_sync = 88, + .h_back = 296, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 8, + .v_sync = 10, + .v_back = 72, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [98] = { + .vic = { .code = 98 }, + .h_active = 4096, + .v_active = 2160, + .interlaced = false, + .pixel_clock_hz = 297000000, + .h_front = 1020, + .h_sync = 88, + .h_back = 296, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 8, + .v_sync = 10, + .v_back = 72, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_256_135, + }, + [99] = { + .vic = { .code = 99 }, + .h_active = 4096, + .v_active = 2160, + .interlaced = false, + .pixel_clock_hz = 297000000, + .h_front = 968, + .h_sync = 88, + .h_back = 128, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 8, + .v_sync = 10, + .v_back = 72, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_256_135, + }, + [100] = { + .vic = { .code = 100 }, + .h_active = 4096, + .v_active = 2160, + .interlaced = false, + .pixel_clock_hz = 297000000, + .h_front = 88, + .h_sync = 88, + .h_back = 128, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 8, + .v_sync = 10, + .v_back = 72, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_256_135, + }, + [115] = { + .vic = { .code = 115 }, + .h_active = 4096, + .v_active = 2160, + .interlaced = false, + .pixel_clock_hz = 594000000, + .h_front = 1020, + .h_sync = 88, + .h_back = 296, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 8, + .v_sync = 10, + .v_back = 72, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_256_135, + }, + [121] = { + .vic = { .code = 121 }, + .h_active = 5120, + .v_active = 2160, + .interlaced = false, + .pixel_clock_hz = 396000000, + .h_front = 1996, + .h_sync = 88, + .h_back = 296, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 8, + .v_sync = 10, + .v_back = 22, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [122] = { + .vic = { .code = 122 }, + .h_active = 5120, + .v_active = 2160, + .interlaced = false, + .pixel_clock_hz = 396000000, + .h_front = 1696, + .h_sync = 88, + .h_back = 296, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 8, + .v_sync = 10, + .v_back = 22, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [123] = { + .vic = { .code = 123 }, + .h_active = 5120, + .v_active = 2160, + .interlaced = false, + .pixel_clock_hz = 396000000, + .h_front = 664, + .h_sync = 88, + .h_back = 128, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 8, + .v_sync = 10, + .v_back = 22, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [124] = { + .vic = { .code = 124 }, + .h_active = 5120, + .v_active = 2160, + .interlaced = false, + .pixel_clock_hz = 742500000, + .h_front = 746, + .h_sync = 88, + .h_back = 296, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 8, + .v_sync = 10, + .v_back = 297, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [194] = { + .vic = { .code = 194 }, + .h_active = 7680, + .v_active = 4320, + .interlaced = false, + .pixel_clock_hz = 1188000000, + .h_front = 2552, + .h_sync = 176, + .h_back = 592, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 16, + .v_sync = 20, + .v_back = 144, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, + }, + [202] = { + .vic = { .code = 202 }, + .h_active = 7680, + .v_active = 4320, + .interlaced = false, + .pixel_clock_hz = 1188000000, + .h_front = 2552, + .h_sync = 176, + .h_back = 592, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 16, + .v_sync = 20, + .v_back = 144, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [195] = { + .vic = { .code = 195 }, + .h_active = 7680, + .v_active = 4320, + .interlaced = false, + .pixel_clock_hz = 1188000000, + .h_front = 2352, + .h_sync = 176, + .h_back = 592, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 16, + .v_sync = 20, + .v_back = 44, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, + }, + [203] = { + .vic = { .code = 203 }, + .h_active = 7680, + .v_active = 4320, + .interlaced = false, + .pixel_clock_hz = 1188000000, + .h_front = 2352, + .h_sync = 176, + .h_back = 592, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 16, + .v_sync = 20, + .v_back = 44, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [196] = { + .vic = { .code = 196 }, + .h_active = 7680, + .v_active = 4320, + .interlaced = false, + .pixel_clock_hz = 1188000000, + .h_front = 552, + .h_sync = 176, + .h_back = 592, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 16, + .v_sync = 20, + .v_back = 44, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, + }, + [204] = { + .vic = { .code = 204 }, + .h_active = 7680, + .v_active = 4320, + .interlaced = false, + .pixel_clock_hz = 1188000000, + .h_front = 552, + .h_sync = 176, + .h_back = 592, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 16, + .v_sync = 20, + .v_back = 44, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [197] = { + .vic = { .code = 197 }, + .h_active = 7680, + .v_active = 4320, + .interlaced = false, + .pixel_clock_hz = 2376000000, + .h_front = 2552, + .h_sync = 176, + .h_back = 592, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 16, + .v_sync = 20, + .v_back = 144, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, + }, + [205] = { + .vic = { .code = 205 }, + .h_active = 7680, + .v_active = 4320, + .interlaced = false, + .pixel_clock_hz = 2376000000, + .h_front = 2552, + .h_sync = 176, + .h_back = 592, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 16, + .v_sync = 20, + .v_back = 144, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [210] = { + .vic = { .code = 210 }, + .h_active = 10240, + .v_active = 4320, + .interlaced = false, + .pixel_clock_hz = 1485000000, + .h_front = 1492, + .h_sync = 176, + .h_back = 592, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 16, + .v_sync = 20, + .v_back = 594, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [211] = { + .vic = { .code = 211 }, + .h_active = 10240, + .v_active = 4320, + .interlaced = false, + .pixel_clock_hz = 1485000000, + .h_front = 2492, + .h_sync = 176, + .h_back = 592, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 16, + .v_sync = 20, + .v_back = 44, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [212] = { + .vic = { .code = 212 }, + .h_active = 10240, + .v_active = 4320, + .interlaced = false, + .pixel_clock_hz = 1485000000, + .h_front = 288, + .h_sync = 176, + .h_back = 296, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 16, + .v_sync = 20, + .v_back = 144, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [213] = { + .vic = { .code = 213 }, + .h_active = 10240, + .v_active = 4320, + .interlaced = false, + .pixel_clock_hz = 2970000000, + .h_front = 1492, + .h_sync = 176, + .h_back = 592, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 16, + .v_sync = 20, + .v_back = 594, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [17] = { + .vic = { .code = 17 }, + .h_active = 720, + .v_active = 576, + .interlaced = false, + .pixel_clock_hz = 27000000, + .h_front = 12, + .h_sync = 64, + .h_back = 68, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .v_front = 5, + .v_sync = 5, + .v_back = 39, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_4_3, + }, + [18] = { + .vic = { .code = 18 }, + .h_active = 720, + .v_active = 576, + .interlaced = false, + .pixel_clock_hz = 27000000, + .h_front = 12, + .h_sync = 64, + .h_back = 68, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .v_front = 5, + .v_sync = 5, + .v_back = 39, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, + }, + [21] = { + .vic = { .code = 21 }, + .h_active = 1440, + .v_active = 576, + .interlaced = true, + .pixel_clock_hz = 27000000, + .h_front = 24, + .h_sync = 126, + .h_back = 138, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .v_front = 2, + .v_sync = 3, + .v_back = 19, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_4_3, + }, + [22] = { + .vic = { .code = 22 }, + .h_active = 1440, + .v_active = 576, + .interlaced = true, + .pixel_clock_hz = 27000000, + .h_front = 24, + .h_sync = 126, + .h_back = 138, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .v_front = 2, + .v_sync = 3, + .v_back = 19, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, + }, + [23] = { + .vic = { .code = 23 }, + .h_active = 1440, + .v_active = 288, + .interlaced = false, + .pixel_clock_hz = 27000000, + .h_front = 24, + .h_sync = 126, + .h_back = 138, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .v_front = 2, + .v_sync = 3, + .v_back = 19, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_4_3, + }, + [24] = { + .vic = { .code = 24 }, + .h_active = 1440, + .v_active = 288, + .interlaced = false, + .pixel_clock_hz = 27000000, + .h_front = 24, + .h_sync = 126, + .h_back = 138, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .v_front = 2, + .v_sync = 3, + .v_back = 19, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, + }, + [29] = { + .vic = { .code = 29 }, + .h_active = 1440, + .v_active = 576, + .interlaced = false, + .pixel_clock_hz = 54000000, + .h_front = 24, + .h_sync = 128, + .h_back = 136, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .v_front = 5, + .v_sync = 5, + .v_back = 39, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_4_3, + }, + [30] = { + .vic = { .code = 30 }, + .h_active = 1440, + .v_active = 576, + .interlaced = false, + .pixel_clock_hz = 54000000, + .h_front = 24, + .h_sync = 128, + .h_back = 136, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .v_front = 5, + .v_sync = 5, + .v_back = 39, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, + }, + [25] = { + .vic = { .code = 25 }, + .h_active = 2880, + .v_active = 576, + .interlaced = true, + .pixel_clock_hz = 54000000, + .h_front = 48, + .h_sync = 252, + .h_back = 276, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .v_front = 2, + .v_sync = 3, + .v_back = 19, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_4_3, + }, + [26] = { + .vic = { .code = 26 }, + .h_active = 2880, + .v_active = 576, + .interlaced = true, + .pixel_clock_hz = 54000000, + .h_front = 48, + .h_sync = 252, + .h_back = 276, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .v_front = 2, + .v_sync = 3, + .v_back = 19, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, + }, + [27] = { + .vic = { .code = 27 }, + .h_active = 2880, + .v_active = 288, + .interlaced = false, + .pixel_clock_hz = 54000000, + .h_front = 48, + .h_sync = 252, + .h_back = 276, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .v_front = 2, + .v_sync = 3, + .v_back = 19, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_4_3, + }, + [28] = { + .vic = { .code = 28 }, + .h_active = 2880, + .v_active = 288, + .interlaced = false, + .pixel_clock_hz = 54000000, + .h_front = 48, + .h_sync = 252, + .h_back = 276, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .v_front = 2, + .v_sync = 3, + .v_back = 19, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, + }, + [37] = { + .vic = { .code = 37 }, + .h_active = 2880, + .v_active = 576, + .interlaced = false, + .pixel_clock_hz = 108000000, + .h_front = 48, + .h_sync = 256, + .h_back = 272, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .v_front = 5, + .v_sync = 5, + .v_back = 39, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_4_3, + }, + [38] = { + .vic = { .code = 38 }, + .h_active = 2880, + .v_active = 576, + .interlaced = false, + .pixel_clock_hz = 108000000, + .h_front = 48, + .h_sync = 256, + .h_back = 272, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .v_front = 5, + .v_sync = 5, + .v_back = 39, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, + }, + [19] = { + .vic = { .code = 19 }, + .h_active = 1280, + .v_active = 720, + .interlaced = false, + .pixel_clock_hz = 74250000, + .h_front = 440, + .h_sync = 40, + .h_back = 220, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 5, + .v_sync = 5, + .v_back = 20, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, + }, + [68] = { + .vic = { .code = 68 }, + .h_active = 1280, + .v_active = 720, + .interlaced = false, + .pixel_clock_hz = 74250000, + .h_front = 440, + .h_sync = 40, + .h_back = 220, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 5, + .v_sync = 5, + .v_back = 20, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [82] = { + .vic = { .code = 82 }, + .h_active = 1680, + .v_active = 720, + .interlaced = false, + .pixel_clock_hz = 82500000, + .h_front = 260, + .h_sync = 40, + .h_back = 220, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 5, + .v_sync = 5, + .v_back = 20, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [39] = { + .vic = { .code = 39 }, + .h_active = 1920, + .v_active = 1080, + .interlaced = true, + .pixel_clock_hz = 72000000, + .h_front = 32, + .h_sync = 168, + .h_back = 184, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 23, + .v_sync = 5, + .v_back = 57, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, + }, + [20] = { + .vic = { .code = 20 }, + .h_active = 1920, + .v_active = 1080, + .interlaced = true, + .pixel_clock_hz = 74250000, + .h_front = 528, + .h_sync = 44, + .h_back = 148, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 2, + .v_sync = 5, + .v_back = 15, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, + }, + [31] = { + .vic = { .code = 31 }, + .h_active = 1920, + .v_active = 1080, + .interlaced = false, + .pixel_clock_hz = 148500000, + .h_front = 528, + .h_sync = 44, + .h_back = 148, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 4, + .v_sync = 5, + .v_back = 36, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, + }, + [75] = { + .vic = { .code = 75 }, + .h_active = 1920, + .v_active = 1080, + .interlaced = false, + .pixel_clock_hz = 148500000, + .h_front = 528, + .h_sync = 44, + .h_back = 148, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 4, + .v_sync = 5, + .v_back = 36, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [89] = { + .vic = { .code = 89 }, + .h_active = 2560, + .v_active = 1080, + .interlaced = false, + .pixel_clock_hz = 185625000, + .h_front = 548, + .h_sync = 44, + .h_back = 148, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 4, + .v_sync = 5, + .v_back = 36, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [96] = { + .vic = { .code = 96 }, + .h_active = 3840, + .v_active = 2160, + .interlaced = false, + .pixel_clock_hz = 594000000, + .h_front = 1056, + .h_sync = 88, + .h_back = 296, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 8, + .v_sync = 10, + .v_back = 72, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, + }, + [106] = { + .vic = { .code = 106 }, + .h_active = 3840, + .v_active = 2160, + .interlaced = false, + .pixel_clock_hz = 594000000, + .h_front = 1056, + .h_sync = 88, + .h_back = 296, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 8, + .v_sync = 10, + .v_back = 72, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [101] = { + .vic = { .code = 101 }, + .h_active = 4096, + .v_active = 2160, + .interlaced = false, + .pixel_clock_hz = 594000000, + .h_front = 968, + .h_sync = 88, + .h_back = 128, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 8, + .v_sync = 10, + .v_back = 72, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_256_135, + }, + [125] = { + .vic = { .code = 125 }, + .h_active = 5120, + .v_active = 2160, + .interlaced = false, + .pixel_clock_hz = 742500000, + .h_front = 1096, + .h_sync = 88, + .h_back = 296, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 8, + .v_sync = 10, + .v_back = 72, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [198] = { + .vic = { .code = 198 }, + .h_active = 7680, + .v_active = 4320, + .interlaced = false, + .pixel_clock_hz = 2376000000, + .h_front = 2352, + .h_sync = 176, + .h_back = 592, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 16, + .v_sync = 20, + .v_back = 44, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, + }, + [206] = { + .vic = { .code = 206 }, + .h_active = 7680, + .v_active = 4320, + .interlaced = false, + .pixel_clock_hz = 2376000000, + .h_front = 2352, + .h_sync = 176, + .h_back = 592, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 16, + .v_sync = 20, + .v_back = 44, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [214] = { + .vic = { .code = 214 }, + .h_active = 10240, + .v_active = 4320, + .interlaced = false, + .pixel_clock_hz = 2970000000, + .h_front = 2492, + .h_sync = 176, + .h_back = 592, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 16, + .v_sync = 20, + .v_back = 44, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [1] = { + .vic = { .code = 1 }, + .h_active = 640, + .v_active = 480, + .interlaced = false, + .pixel_clock_hz = 25175000, + .h_front = 16, + .h_sync = 96, + .h_back = 48, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .v_front = 10, + .v_sync = 2, + .v_back = 33, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_4_3, + }, + [2] = { + .vic = { .code = 2 }, + .h_active = 720, + .v_active = 480, + .interlaced = false, + .pixel_clock_hz = 27000000, + .h_front = 16, + .h_sync = 62, + .h_back = 60, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .v_front = 9, + .v_sync = 6, + .v_back = 30, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_4_3, + }, + [3] = { + .vic = { .code = 3 }, + .h_active = 720, + .v_active = 480, + .interlaced = false, + .pixel_clock_hz = 27000000, + .h_front = 16, + .h_sync = 62, + .h_back = 60, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .v_front = 9, + .v_sync = 6, + .v_back = 30, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, + }, + [6] = { + .vic = { .code = 6 }, + .h_active = 1440, + .v_active = 480, + .interlaced = true, + .pixel_clock_hz = 27000000, + .h_front = 38, + .h_sync = 124, + .h_back = 114, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .v_front = 4, + .v_sync = 3, + .v_back = 15, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_4_3, + }, + [7] = { + .vic = { .code = 7 }, + .h_active = 1440, + .v_active = 480, + .interlaced = true, + .pixel_clock_hz = 27000000, + .h_front = 38, + .h_sync = 124, + .h_back = 114, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .v_front = 4, + .v_sync = 3, + .v_back = 15, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, + }, + [8] = { + .vic = { .code = 8 }, + .h_active = 1440, + .v_active = 240, + .interlaced = false, + .pixel_clock_hz = 27000000, + .h_front = 38, + .h_sync = 124, + .h_back = 114, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .v_front = 4, + .v_sync = 3, + .v_back = 15, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_4_3, + }, + [9] = { + .vic = { .code = 9 }, + .h_active = 1440, + .v_active = 240, + .interlaced = false, + .pixel_clock_hz = 27000000, + .h_front = 38, + .h_sync = 124, + .h_back = 114, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .v_front = 4, + .v_sync = 3, + .v_back = 15, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, + }, + [14] = { + .vic = { .code = 14 }, + .h_active = 1440, + .v_active = 480, + .interlaced = false, + .pixel_clock_hz = 54000000, + .h_front = 32, + .h_sync = 124, + .h_back = 120, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .v_front = 9, + .v_sync = 6, + .v_back = 30, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_4_3, + }, + [15] = { + .vic = { .code = 15 }, + .h_active = 1440, + .v_active = 480, + .interlaced = false, + .pixel_clock_hz = 54000000, + .h_front = 32, + .h_sync = 124, + .h_back = 120, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .v_front = 9, + .v_sync = 6, + .v_back = 30, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, + }, + [10] = { + .vic = { .code = 10 }, + .h_active = 2880, + .v_active = 480, + .interlaced = true, + .pixel_clock_hz = 54000000, + .h_front = 76, + .h_sync = 248, + .h_back = 228, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .v_front = 4, + .v_sync = 3, + .v_back = 15, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_4_3, + }, + [11] = { + .vic = { .code = 11 }, + .h_active = 2880, + .v_active = 480, + .interlaced = true, + .pixel_clock_hz = 54000000, + .h_front = 76, + .h_sync = 248, + .h_back = 228, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .v_front = 4, + .v_sync = 3, + .v_back = 15, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, + }, + [12] = { + .vic = { .code = 12 }, + .h_active = 2880, + .v_active = 240, + .interlaced = false, + .pixel_clock_hz = 54000000, + .h_front = 76, + .h_sync = 248, + .h_back = 228, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .v_front = 4, + .v_sync = 3, + .v_back = 15, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_4_3, + }, + [13] = { + .vic = { .code = 13 }, + .h_active = 2880, + .v_active = 240, + .interlaced = false, + .pixel_clock_hz = 54000000, + .h_front = 76, + .h_sync = 248, + .h_back = 228, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .v_front = 4, + .v_sync = 3, + .v_back = 15, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, + }, + [35] = { + .vic = { .code = 35 }, + .h_active = 2880, + .v_active = 480, + .interlaced = false, + .pixel_clock_hz = 108000000, + .h_front = 64, + .h_sync = 248, + .h_back = 240, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .v_front = 9, + .v_sync = 6, + .v_back = 30, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_4_3, + }, + [36] = { + .vic = { .code = 36 }, + .h_active = 2880, + .v_active = 480, + .interlaced = false, + .pixel_clock_hz = 108000000, + .h_front = 64, + .h_sync = 248, + .h_back = 240, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .v_front = 9, + .v_sync = 6, + .v_back = 30, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, + }, + [4] = { + .vic = { .code = 4 }, + .h_active = 1280, + .v_active = 720, + .interlaced = false, + .pixel_clock_hz = 74250000, + .h_front = 110, + .h_sync = 40, + .h_back = 220, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 5, + .v_sync = 5, + .v_back = 20, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, + }, + [69] = { + .vic = { .code = 69 }, + .h_active = 1280, + .v_active = 720, + .interlaced = false, + .pixel_clock_hz = 74250000, + .h_front = 110, + .h_sync = 40, + .h_back = 220, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 5, + .v_sync = 5, + .v_back = 20, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [83] = { + .vic = { .code = 83 }, + .h_active = 1680, + .v_active = 720, + .interlaced = false, + .pixel_clock_hz = 99000000, + .h_front = 260, + .h_sync = 40, + .h_back = 220, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 5, + .v_sync = 5, + .v_back = 20, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [5] = { + .vic = { .code = 5 }, + .h_active = 1920, + .v_active = 1080, + .interlaced = true, + .pixel_clock_hz = 74250000, + .h_front = 88, + .h_sync = 44, + .h_back = 148, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 2, + .v_sync = 5, + .v_back = 15, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, + }, + [16] = { + .vic = { .code = 16 }, + .h_active = 1920, + .v_active = 1080, + .interlaced = false, + .pixel_clock_hz = 148500000, + .h_front = 88, + .h_sync = 44, + .h_back = 148, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 4, + .v_sync = 5, + .v_back = 36, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, + }, + [76] = { + .vic = { .code = 76 }, + .h_active = 1920, + .v_active = 1080, + .interlaced = false, + .pixel_clock_hz = 148500000, + .h_front = 88, + .h_sync = 44, + .h_back = 148, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 4, + .v_sync = 5, + .v_back = 36, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [90] = { + .vic = { .code = 90 }, + .h_active = 2560, + .v_active = 1080, + .interlaced = false, + .pixel_clock_hz = 198000000, + .h_front = 248, + .h_sync = 44, + .h_back = 148, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 4, + .v_sync = 5, + .v_back = 11, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [97] = { + .vic = { .code = 97 }, + .h_active = 3840, + .v_active = 2160, + .interlaced = false, + .pixel_clock_hz = 594000000, + .h_front = 176, + .h_sync = 88, + .h_back = 296, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 8, + .v_sync = 10, + .v_back = 72, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, + }, + [107] = { + .vic = { .code = 107 }, + .h_active = 3840, + .v_active = 2160, + .interlaced = false, + .pixel_clock_hz = 594000000, + .h_front = 176, + .h_sync = 88, + .h_back = 296, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 8, + .v_sync = 10, + .v_back = 72, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [102] = { + .vic = { .code = 102 }, + .h_active = 4096, + .v_active = 2160, + .interlaced = false, + .pixel_clock_hz = 594000000, + .h_front = 88, + .h_sync = 88, + .h_back = 128, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 8, + .v_sync = 10, + .v_back = 72, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_256_135, + }, + [126] = { + .vic = { .code = 126 }, + .h_active = 5120, + .v_active = 2160, + .interlaced = false, + .pixel_clock_hz = 742500000, + .h_front = 164, + .h_sync = 88, + .h_back = 128, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 8, + .v_sync = 10, + .v_back = 72, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [199] = { + .vic = { .code = 199 }, + .h_active = 7680, + .v_active = 4320, + .interlaced = false, + .pixel_clock_hz = 2376000000, + .h_front = 552, + .h_sync = 176, + .h_back = 592, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 16, + .v_sync = 20, + .v_back = 44, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, + }, + [207] = { + .vic = { .code = 207 }, + .h_active = 7680, + .v_active = 4320, + .interlaced = false, + .pixel_clock_hz = 2376000000, + .h_front = 552, + .h_sync = 176, + .h_back = 592, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 16, + .v_sync = 20, + .v_back = 44, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [215] = { + .vic = { .code = 215 }, + .h_active = 10240, + .v_active = 4320, + .interlaced = false, + .pixel_clock_hz = 2970000000, + .h_front = 288, + .h_sync = 176, + .h_back = 296, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 16, + .v_sync = 20, + .v_back = 144, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [42] = { + .vic = { .code = 42 }, + .h_active = 720, + .v_active = 576, + .interlaced = false, + .pixel_clock_hz = 54000000, + .h_front = 12, + .h_sync = 64, + .h_back = 68, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .v_front = 5, + .v_sync = 5, + .v_back = 39, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_4_3, + }, + [43] = { + .vic = { .code = 43 }, + .h_active = 720, + .v_active = 576, + .interlaced = false, + .pixel_clock_hz = 54000000, + .h_front = 12, + .h_sync = 64, + .h_back = 68, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .v_front = 5, + .v_sync = 5, + .v_back = 39, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, + }, + [44] = { + .vic = { .code = 44 }, + .h_active = 1440, + .v_active = 576, + .interlaced = true, + .pixel_clock_hz = 54000000, + .h_front = 24, + .h_sync = 126, + .h_back = 138, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .v_front = 2, + .v_sync = 3, + .v_back = 19, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_4_3, + }, + [45] = { + .vic = { .code = 45 }, + .h_active = 1440, + .v_active = 576, + .interlaced = true, + .pixel_clock_hz = 54000000, + .h_front = 24, + .h_sync = 126, + .h_back = 138, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .v_front = 2, + .v_sync = 3, + .v_back = 19, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, + }, + [41] = { + .vic = { .code = 41 }, + .h_active = 1280, + .v_active = 720, + .interlaced = false, + .pixel_clock_hz = 148500000, + .h_front = 440, + .h_sync = 40, + .h_back = 220, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 5, + .v_sync = 5, + .v_back = 20, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, + }, + [70] = { + .vic = { .code = 70 }, + .h_active = 1280, + .v_active = 720, + .interlaced = false, + .pixel_clock_hz = 148500000, + .h_front = 440, + .h_sync = 40, + .h_back = 220, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 5, + .v_sync = 5, + .v_back = 20, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [84] = { + .vic = { .code = 84 }, + .h_active = 1680, + .v_active = 720, + .interlaced = false, + .pixel_clock_hz = 165000000, + .h_front = 60, + .h_sync = 40, + .h_back = 220, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 5, + .v_sync = 5, + .v_back = 95, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [40] = { + .vic = { .code = 40 }, + .h_active = 1920, + .v_active = 1080, + .interlaced = true, + .pixel_clock_hz = 148500000, + .h_front = 528, + .h_sync = 44, + .h_back = 148, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 2, + .v_sync = 5, + .v_back = 15, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, + }, + [64] = { + .vic = { .code = 64 }, + .h_active = 1920, + .v_active = 1080, + .interlaced = false, + .pixel_clock_hz = 297000000, + .h_front = 528, + .h_sync = 44, + .h_back = 148, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 4, + .v_sync = 5, + .v_back = 36, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, + }, + [77] = { + .vic = { .code = 77 }, + .h_active = 1920, + .v_active = 1080, + .interlaced = false, + .pixel_clock_hz = 297000000, + .h_front = 528, + .h_sync = 44, + .h_back = 148, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 4, + .v_sync = 5, + .v_back = 36, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [91] = { + .vic = { .code = 91 }, + .h_active = 2560, + .v_active = 1080, + .interlaced = false, + .pixel_clock_hz = 371250000, + .h_front = 218, + .h_sync = 44, + .h_back = 148, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 4, + .v_sync = 5, + .v_back = 161, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [117] = { + .vic = { .code = 117 }, + .h_active = 3840, + .v_active = 2160, + .interlaced = false, + .pixel_clock_hz = 1188000000, + .h_front = 1056, + .h_sync = 88, + .h_back = 296, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 8, + .v_sync = 10, + .v_back = 72, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, + }, + [119] = { + .vic = { .code = 119 }, + .h_active = 3840, + .v_active = 2160, + .interlaced = false, + .pixel_clock_hz = 1188000000, + .h_front = 1056, + .h_sync = 88, + .h_back = 296, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 8, + .v_sync = 10, + .v_back = 72, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [218] = { + .vic = { .code = 218 }, + .h_active = 4096, + .v_active = 2160, + .interlaced = false, + .pixel_clock_hz = 1188000000, + .h_front = 800, + .h_sync = 88, + .h_back = 296, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 8, + .v_sync = 10, + .v_back = 72, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_256_135, + }, + [127] = { + .vic = { .code = 127 }, + .h_active = 5120, + .v_active = 2160, + .interlaced = false, + .pixel_clock_hz = 1485000000, + .h_front = 1096, + .h_sync = 88, + .h_back = 296, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 8, + .v_sync = 10, + .v_back = 72, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [200] = { + .vic = { .code = 200 }, + .h_active = 7680, + .v_active = 4320, + .interlaced = false, + .pixel_clock_hz = 4752000000, + .h_front = 2112, + .h_sync = 176, + .h_back = 592, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 16, + .v_sync = 20, + .v_back = 144, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, + }, + [208] = { + .vic = { .code = 208 }, + .h_active = 7680, + .v_active = 4320, + .interlaced = false, + .pixel_clock_hz = 4752000000, + .h_front = 2112, + .h_sync = 176, + .h_back = 592, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 16, + .v_sync = 20, + .v_back = 144, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [216] = { + .vic = { .code = 216 }, + .h_active = 10240, + .v_active = 4320, + .interlaced = false, + .pixel_clock_hz = 5940000000, + .h_front = 2192, + .h_sync = 176, + .h_back = 592, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 16, + .v_sync = 20, + .v_back = 144, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [48] = { + .vic = { .code = 48 }, + .h_active = 720, + .v_active = 480, + .interlaced = false, + .pixel_clock_hz = 54000000, + .h_front = 16, + .h_sync = 62, + .h_back = 60, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .v_front = 9, + .v_sync = 6, + .v_back = 30, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_4_3, + }, + [49] = { + .vic = { .code = 49 }, + .h_active = 720, + .v_active = 480, + .interlaced = false, + .pixel_clock_hz = 54000000, + .h_front = 16, + .h_sync = 62, + .h_back = 60, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .v_front = 9, + .v_sync = 6, + .v_back = 30, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, + }, + [50] = { + .vic = { .code = 50 }, + .h_active = 1440, + .v_active = 480, + .interlaced = true, + .pixel_clock_hz = 54000000, + .h_front = 38, + .h_sync = 124, + .h_back = 114, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .v_front = 4, + .v_sync = 3, + .v_back = 15, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_4_3, + }, + [51] = { + .vic = { .code = 51 }, + .h_active = 1440, + .v_active = 480, + .interlaced = true, + .pixel_clock_hz = 54000000, + .h_front = 38, + .h_sync = 124, + .h_back = 114, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .v_front = 4, + .v_sync = 3, + .v_back = 15, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, + }, + [47] = { + .vic = { .code = 47 }, + .h_active = 1280, + .v_active = 720, + .interlaced = false, + .pixel_clock_hz = 148500000, + .h_front = 110, + .h_sync = 40, + .h_back = 220, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 5, + .v_sync = 5, + .v_back = 20, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, + }, + [71] = { + .vic = { .code = 71 }, + .h_active = 1280, + .v_active = 720, + .interlaced = false, + .pixel_clock_hz = 148500000, + .h_front = 110, + .h_sync = 40, + .h_back = 220, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 5, + .v_sync = 5, + .v_back = 20, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [85] = { + .vic = { .code = 85 }, + .h_active = 1680, + .v_active = 720, + .interlaced = false, + .pixel_clock_hz = 198000000, + .h_front = 60, + .h_sync = 40, + .h_back = 220, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 5, + .v_sync = 5, + .v_back = 95, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [46] = { + .vic = { .code = 46 }, + .h_active = 1920, + .v_active = 1080, + .interlaced = true, + .pixel_clock_hz = 148500000, + .h_front = 88, + .h_sync = 44, + .h_back = 148, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 2, + .v_sync = 5, + .v_back = 15, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, + }, + [63] = { + .vic = { .code = 63 }, + .h_active = 1920, + .v_active = 1080, + .interlaced = false, + .pixel_clock_hz = 297000000, + .h_front = 88, + .h_sync = 44, + .h_back = 148, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 4, + .v_sync = 5, + .v_back = 36, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, + }, + [78] = { + .vic = { .code = 78 }, + .h_active = 1920, + .v_active = 1080, + .interlaced = false, + .pixel_clock_hz = 297000000, + .h_front = 88, + .h_sync = 44, + .h_back = 148, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 4, + .v_sync = 5, + .v_back = 36, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [92] = { + .vic = { .code = 92 }, + .h_active = 2560, + .v_active = 1080, + .interlaced = false, + .pixel_clock_hz = 495000000, + .h_front = 548, + .h_sync = 44, + .h_back = 148, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 4, + .v_sync = 5, + .v_back = 161, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [118] = { + .vic = { .code = 118 }, + .h_active = 3840, + .v_active = 2160, + .interlaced = false, + .pixel_clock_hz = 1188000000, + .h_front = 176, + .h_sync = 88, + .h_back = 296, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 8, + .v_sync = 10, + .v_back = 72, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, + }, + [120] = { + .vic = { .code = 120 }, + .h_active = 3840, + .v_active = 2160, + .interlaced = false, + .pixel_clock_hz = 1188000000, + .h_front = 176, + .h_sync = 88, + .h_back = 296, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 8, + .v_sync = 10, + .v_back = 72, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [219] = { + .vic = { .code = 219 }, + .h_active = 4096, + .v_active = 2160, + .interlaced = false, + .pixel_clock_hz = 1188000000, + .h_front = 88, + .h_sync = 88, + .h_back = 128, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 8, + .v_sync = 10, + .v_back = 72, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_256_135, + }, + [193] = { + .vic = { .code = 193 }, + .h_active = 5120, + .v_active = 2160, + .interlaced = false, + .pixel_clock_hz = 1485000000, + .h_front = 164, + .h_sync = 88, + .h_back = 128, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 8, + .v_sync = 10, + .v_back = 72, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [201] = { + .vic = { .code = 201 }, + .h_active = 7680, + .v_active = 4320, + .interlaced = false, + .pixel_clock_hz = 4752000000, + .h_front = 352, + .h_sync = 176, + .h_back = 592, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 16, + .v_sync = 20, + .v_back = 144, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, + }, + [209] = { + .vic = { .code = 209 }, + .h_active = 7680, + .v_active = 4320, + .interlaced = false, + .pixel_clock_hz = 4752000000, + .h_front = 352, + .h_sync = 176, + .h_back = 592, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 16, + .v_sync = 20, + .v_back = 144, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [217] = { + .vic = { .code = 217 }, + .h_active = 10240, + .v_active = 4320, + .interlaced = false, + .pixel_clock_hz = 5940000000, + .h_front = 288, + .h_sync = 176, + .h_back = 296, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .v_front = 16, + .v_sync = 20, + .v_back = 144, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, + }, + [52] = { + .vic = { .code = 52 }, + .h_active = 720, + .v_active = 576, + .interlaced = false, + .pixel_clock_hz = 108000000, + .h_front = 12, + .h_sync = 64, + .h_back = 68, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .v_front = 5, + .v_sync = 5, + .v_back = 39, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_4_3, + }, + [53] = { + .vic = { .code = 53 }, + .h_active = 720, + .v_active = 576, + .interlaced = false, + .pixel_clock_hz = 108000000, + .h_front = 12, + .h_sync = 64, + .h_back = 68, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .v_front = 5, + .v_sync = 5, + .v_back = 39, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, + }, + [54] = { + .vic = { .code = 54 }, + .h_active = 1440, + .v_active = 576, + .interlaced = true, + .pixel_clock_hz = 108000000, + .h_front = 24, + .h_sync = 126, + .h_back = 138, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .v_front = 2, + .v_sync = 3, + .v_back = 19, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_4_3, + }, + [55] = { + .vic = { .code = 55 }, + .h_active = 1440, + .v_active = 576, + .interlaced = true, + .pixel_clock_hz = 108000000, + .h_front = 24, + .h_sync = 126, + .h_back = 138, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .v_front = 2, + .v_sync = 3, + .v_back = 19, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, + }, + [56] = { + .vic = { .code = 56 }, + .h_active = 720, + .v_active = 480, + .interlaced = false, + .pixel_clock_hz = 108000000, + .h_front = 16, + .h_sync = 62, + .h_back = 60, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .v_front = 9, + .v_sync = 6, + .v_back = 30, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_4_3, + }, + [57] = { + .vic = { .code = 57 }, + .h_active = 720, + .v_active = 480, + .interlaced = false, + .pixel_clock_hz = 108000000, + .h_front = 16, + .h_sync = 62, + .h_back = 60, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .v_front = 9, + .v_sync = 6, + .v_back = 30, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, + }, + [58] = { + .vic = { .code = 58 }, + .h_active = 1440, + .v_active = 480, + .interlaced = true, + .pixel_clock_hz = 108000000, + .h_front = 38, + .h_sync = 124, + .h_back = 114, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .v_front = 4, + .v_sync = 3, + .v_back = 15, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_4_3, + }, + [59] = { + .vic = { .code = 59 }, + .h_active = 1440, + .v_active = 480, + .interlaced = true, + .pixel_clock_hz = 108000000, + .h_front = 38, + .h_sync = 124, + .h_back = 114, + .h_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .v_front = 4, + .v_sync = 3, + .v_back = 15, + .v_sync_polarity = DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, + .picture_aspect_ratio = DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, + }, +}; + +const size_t _di_cta_vic_video_formats_len = 220; diff --git a/local/recipes/libs/libdisplay-info/source/cta-vic.c b/local/recipes/libs/libdisplay-info/source/cta-vic.c new file mode 100644 index 0000000000..117a65f854 --- /dev/null +++ b/local/recipes/libs/libdisplay-info/source/cta-vic.c @@ -0,0 +1,39 @@ +#include + +extern const struct di_cta_vic_video_format _di_cta_vic_video_formats[]; +extern const size_t _di_cta_vic_video_formats_len; + +const struct di_cta_vic_video_format * +di_cta_vic_video_format_from_vic(struct di_cta_vic vic) +{ + if (vic.code >= _di_cta_vic_video_formats_len || + _di_cta_vic_video_formats[vic.code].vic.code == 0) + return NULL; + return &_di_cta_vic_video_formats[vic.code]; +} + +struct di_cta_vic +di_cta_vic_video_format_to_vic(const struct di_cta_vic_video_format *format) +{ + size_t i; + + for (i = 0; i < _di_cta_vic_video_formats_len; i++) { + const struct di_cta_vic_video_format *candidate = &_di_cta_vic_video_formats[i]; + + if (candidate->vic.code != 0 && + candidate->h_active == format->h_active && + candidate->v_active == format->v_active && + candidate->h_sync == format->h_sync && + candidate->v_sync == format->v_sync && + candidate->h_back == format->h_back && + candidate->v_back == format->v_back && + candidate->h_sync_polarity == format->h_sync_polarity && + candidate->v_sync_polarity == format->v_sync_polarity && + candidate->pixel_clock_hz == format->pixel_clock_hz && + candidate->interlaced == format->interlaced && + candidate->picture_aspect_ratio == format->picture_aspect_ratio) + return candidate->vic; + } + + return (struct di_cta_vic) { .code = 0 }; +} diff --git a/local/recipes/libs/libdisplay-info/source/cta.c b/local/recipes/libs/libdisplay-info/source/cta.c new file mode 100644 index 0000000000..46f4977c5b --- /dev/null +++ b/local/recipes/libs/libdisplay-info/source/cta.c @@ -0,0 +1,2966 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "bits.h" +#include "cta.h" +#include "log.h" +#include "edid.h" +#include "displayid.h" + +/** + * Number of bytes in the CTA header (tag + revision + DTD offset + flags). + */ +#define CTA_HEADER_SIZE 4 +/** + * Exclusive upper bound for the detailed timing definitions in the CTA block. + */ +#define CTA_DTD_END 127 +/** + * Number of bytes in a CTA short audio descriptor. + */ +#define CTA_SAD_SIZE 3 +/** + * Number of bytes in a HDMI 3D audio descriptor. + */ +#define CTA_HDMI_AUDIO_3D_DESCRIPTOR_SIZE 4 +/** + * IEEE Organizationally unique identifiers + */ +#define IEEE_OUI_DOLBY 0x00D046 +#define IEEE_OUI_HDR10PLUS 0x90848B +#define IEEE_OUI_HDMI 0x000C03 +#define IEEE_OUI_HDMI_FORUM 0xC45DD8 + +static void +add_failure(struct di_cta *cta, const char fmt[], ...) +{ + va_list args; + + va_start(args, fmt); + _di_logger_va_add_failure(cta->logger, fmt, args); + va_end(args); +} + +static void +add_failure_until(struct di_cta *cta, int revision, const char fmt[], ...) +{ + va_list args; + + if (cta->revision > revision) { + return; + } + + va_start(args, fmt); + _di_logger_va_add_failure(cta->logger, fmt, args); + va_end(args); +} + +static struct di_cta_svd * +parse_svd(struct di_cta *cta, uint8_t raw, uint8_t original_index, + const char *prefix) +{ + struct di_cta_svd svd, *svd_ptr; + + if (raw == 0 || raw == 128 || raw >= 254) { + /* Reserved */ + add_failure_until(cta, 3, + "%s: Unknown VIC %" PRIu8 ".", + prefix, + raw); + return NULL; + } else if (raw <= 127 || raw >= 193) { + svd = (struct di_cta_svd) { + .vic = { .code = raw }, + .original_index = original_index, + }; + } else { + svd = (struct di_cta_svd) { + .vic = { .code = get_bit_range(raw, 6, 0) }, + .native = true, + .original_index = original_index, + }; + } + + svd_ptr = calloc(1, sizeof(*svd_ptr)); + if (!svd_ptr) + return NULL; + *svd_ptr = svd; + return svd_ptr; +} + +static int +parse_video_block(struct di_cta *cta, struct di_cta_video_block_priv *video, + const uint8_t *data, size_t size) +{ + uint8_t i; + struct di_cta_svd *svd; + + if (size == 0) { + add_failure(cta, "Video Data Block: Empty Data Block"); + return -EINVAL; + } + + assert(size < (1 << 8)); + + for (i = 0; i < size; i++) { + svd = parse_svd(cta, data[i], i, "Video Data Block"); + if (!svd) + continue; + assert(video->svds_len < EDID_CTA_MAX_VIDEO_BLOCK_ENTRIES); + video->svds[video->svds_len++] = svd; + } + + video->base.svds = (const struct di_cta_svd *const *)video->svds; + return 0; +} + +static int +hdmi_latency_from_raw(struct di_cta *cta, const char *block_name, + const char *type, uint8_t raw) +{ + /* Unknown latency. */ + if (raw == 0) + return 0; + + /* Audio/video not supported. */ + if (raw == 255) + return 0; + + if (raw > 251) { + add_failure(cta, + "%s: %s latency byte is %u, but the ceil supported by spec is 251.", + block_name, type, raw); + return 0; + } + + return 2 * (raw - 1); +} + +static int +parse_vendor_hdmi_block(struct di_cta *cta, + struct di_cta_vendor_hdmi_block_priv *priv, + const uint8_t *data, size_t size) +{ + const ssize_t offset = -1; /* Spec gives offset relative to header */ + const char block_name[] = "Vendor-Specific Data Block (HDMI), OUI 00-0C-03"; + struct di_cta_vendor_hdmi_block *block = &priv->base; + size_t len_vic; + size_t index; + size_t i; + uint8_t val; + + if (size < 5) { + add_failure(cta, "%s: Empty Data Block", block_name); + return -EINVAL; + } + + block->source_phys_addr = (uint16_t)((uint16_t)data[4 + offset] << 8 | data[5 + offset]); + + if (size < 6) + return 0; + + block->supports_ai = has_bit(data[6 + offset], 7); + block->supports_dc_48bit = has_bit(data[6 + offset], 6); + block->supports_dc_36bit = has_bit(data[6 + offset], 5); + block->supports_dc_30bit = has_bit(data[6 + offset], 4); + block->supports_dc_y444 = has_bit(data[6 + offset], 3); + if (get_bit_range(data[6 + offset], 2, 1) != 0) + add_failure(cta, "%s: Bits 2 and 1 of byte 6 are reserved.", block_name); + block->supports_dvi_dual = has_bit(data[6 + offset], 0); + + if (size < 7) + return 0; + + block->max_tmds_clock = data[7 + offset] * 5; + + if (size < 8) + return 0; + + block->supports_content_game = has_bit(data[8 + offset], 3); + block->supports_content_cinema = has_bit(data[8 + offset], 2); + block->supports_content_photo = has_bit(data[8 + offset], 1); + block->supports_content_graphics = has_bit(data[8 + offset], 0); + + block->has_latency = has_bit(data[8 + offset], 7); + block->has_interlaced_latency = has_bit(data[8 + offset], 6); + /* Bit 5 is reserved on older HDMI spec versions but appears as the + * HDMI_Video_present flag on newer ones. This flag is a bit useless, + * because it tells us if extended video details are provided, but the + * blob size itself already gives us this info and this approach works + * for all versions. Let's ignore bit 5. */ + if (has_bit(data[8 + offset], 4)) + add_failure(cta, "%s: Bit 4 of byte 8 is reserved.", block_name); + + if (block->has_interlaced_latency && !block->has_latency) { + add_failure(cta, + "%s: Interlaced Latency support flag set, but Latency support flag is not", + block_name); + return 0; + } + + /* The next features from the block do not have fixed position, so we + * need to iterate simply incrementing the index. The next is byte 9. */ + index = (size_t) (9 + offset); + + if (block->has_latency) { + if (size <= index + 1) { /* we need 2 bytes */ + add_failure(cta, + "%s: Latency support flag set, but bytes are missing", + block_name); + return 0; + } + + val = data[index++]; + block->supports_progressive_video = (val != 255); + block->progressive_video_latency = + hdmi_latency_from_raw(cta, block_name, "Video", val); + + val = data[index++]; + block->supports_progressive_audio = (val != 255); + block->progressive_audio_latency = + hdmi_latency_from_raw(cta, block_name, "Audio", val); + } + + if (block->has_interlaced_latency) { + if (size <= index + 1) { /* we need 2 bytes */ + add_failure(cta, + "%s: Interlaced Latency support flag set, but bytes are missing", + block_name); + return 0; + } + + val = data[index++]; + block->supports_interlaced_video = (val != 255); + block->interlaced_video_latency = + hdmi_latency_from_raw(cta, block_name, "Interlaced Video", val); + + val = data[index++]; + block->supports_interlaced_audio = (val != 255); + block->interlaced_audio_latency = + hdmi_latency_from_raw(cta, block_name, "Interlaced Audio", val); + } + + if (size <= index) + return 0; + + /* Skip a byte, it should only be used when we decode HDMI 3D VIC */ + index++; + + if (size <= index) + return 0; + + len_vic = get_bit_range(data[index++], 7, 5); + if (len_vic == 0) { + add_failure(cta, + "%s: Extended Video Details flag but HDMI VIC list size 0", + block_name); + return 0; + } + + if (size <= index + len_vic - 1) { + add_failure(cta, + "%s: HDMI VIC list size %u does not fit block of size %u", + block_name, len_vic, size); + len_vic = size - index; + } + + priv->vics = calloc(len_vic, sizeof(*priv->vics)); + if (!priv->vics) + return -errno; + + for (i = 0; i < len_vic; i++) { + val = data[index++]; + if (val < 1 || val > 4) { + add_failure(cta, + "%s: HDMI VIC %d is invalid", block_name, val); + continue; + } + priv->vics[block->vics_len++] = (struct di_hdmi_vic) { .code = val, }; + } + block->vics = priv->vics; + + /* TODO: parse HDMI 3D VIC */ + + return 0; +} + +static int +parse_hdmi_scds(struct di_cta *cta, struct di_cta_hdmi_scds *scds, + struct di_cta_hdmi_dsc *dsc, const uint8_t *data, size_t size, + const char *block_name) +{ + const ssize_t offset = -1; /* Spec gives offset relative to header */ + uint8_t max_frl_rate; + uint8_t max_slices; + size_t i; + + if (size < 7) { + add_failure(cta, "%s: Empty Data Block", block_name); + return -EINVAL; + } + + scds->version = data[4 + offset]; + if (scds->version != 1) { + add_failure(cta, "%s: Unsupported version %d.", block_name, scds->version); + return -ENOTSUP; + } + + scds->max_tmds_char_rate_mhz = 5 * data[5 + offset]; + if (scds->max_tmds_char_rate_mhz != 0 && scds->max_tmds_char_rate_mhz <= 340) + add_failure(cta, "%s: Max TMDS rate is != 0 and <= 340.", block_name); + + scds->supports_3d_osd_disparity = has_bit(data[6 + offset], 0); + scds->supports_3d_dual_view = has_bit(data[6 + offset], 1); + scds->supports_3d_independent_view = has_bit(data[6 + offset], 2); + scds->supports_lte_340mcsc_scramble = has_bit(data[6 + offset], 3); + scds->supports_ccbpci = has_bit(data[6 + offset], 4); + scds->supports_cable_status = has_bit(data[6 + offset], 5); + scds->supports_scdc_read_request = has_bit(data[6 + offset], 6); + scds->supports_scdc = has_bit(data[6 + offset], 7); + scds->supports_dc_30bit_420 = has_bit(data[7 + offset], 0); + scds->supports_dc_36bit_420 = has_bit(data[7 + offset], 1); + scds->supports_dc_48bit_420 = has_bit(data[7 + offset], 2); + scds->supports_uhd_vic = has_bit(data[7 + offset], 3); + + max_frl_rate = get_bit_range(data[7 + offset], 7, 4); + switch (max_frl_rate) { + case DI_CTA_HDMI_FRL_UNSUPPORTED: + case DI_CTA_HDMI_FRL_3GBPS_3LANES: + case DI_CTA_HDMI_FRL_6GBPS_3LANES: + case DI_CTA_HDMI_FRL_6GBPS_4LANES: + case DI_CTA_HDMI_FRL_8GBPS_4LANES: + case DI_CTA_HDMI_FRL_10GBPS_4LANES: + case DI_CTA_HDMI_FRL_12GBPS_4LANES: + scds->max_frl_rate = max_frl_rate; + break; + default: + add_failure(cta, "%s: Unknown Max Fixed Rate Link (0x%02x).", + block_name, max_frl_rate); + break; + } + + if (max_frl_rate == DI_CTA_HDMI_FRL_3GBPS_3LANES && + scds->max_tmds_char_rate_mhz < 300) { + add_failure(cta, "%s: Max Fixed Rate Link is 1, but Max TMDS rate < 300.", + block_name); + } + if (max_frl_rate >= DI_CTA_HDMI_FRL_6GBPS_3LANES && + scds->max_tmds_char_rate_mhz != 600) { + add_failure(cta, "%s: Max Fixed Rate Link is >= 2, but Max TMDS rate != 600.", + block_name); + } + + if (size < 8) + return 0; + + scds->supports_fapa_start_location = has_bit(data[8 + offset], 0); + scds->supports_allm = has_bit(data[8 + offset], 1); + scds->supports_fva = has_bit(data[8 + offset], 2); + scds->supports_neg_mvrr = has_bit(data[8 + offset], 3); + scds->supports_cinema_vrr = has_bit(data[8 + offset], 4); + if (scds->supports_cinema_vrr) + add_failure(cta, "%s: CinemaVRR is deprecated and must be cleared.", block_name); + scds->m_delta = has_bit(data[8 + offset], 5); + scds->supports_qms = has_bit(data[8 + offset], 6); + scds->supports_fapa_end_extended = has_bit(data[8 + offset], 7); + + if (size < 10) + return 0; + + scds->vrr_min_hz = get_bit_range(data[9 + offset], 5, 0); + scds->vrr_max_hz = (get_bit_range(data[9 + offset], 7, 6) << 8) | data[10 + offset]; + + if (scds->vrr_min_hz > 48) + add_failure(cta, "%s: VRRmin > 48.", block_name); + if (scds->vrr_min_hz == 0 && scds->vrr_max_hz != 0) + add_failure(cta, "%s: VRRmin == 0, but VRRmax isn't.", block_name); + if (scds->vrr_max_hz < 100) + add_failure(cta, "%s: VRRmax < 100.", block_name); + + if (size < 13) + return 0; + + dsc->supports_10bpc = has_bit(data[11 + offset], 0); + dsc->supports_12bpc = has_bit(data[11 + offset], 1); + dsc->supports_all_bpc = has_bit(data[11 + offset], 3); + + scds->qms_tfr_min = has_bit(data[11 + offset], 4); + scds->qms_tfr_max = has_bit(data[11 + offset], 5); + + if (scds->qms_tfr_min && !scds->supports_qms) + add_failure(cta, "%s: QMS_TFR_min is set but QMS is not.", block_name); + if (scds->qms_tfr_max && !scds->supports_qms) + add_failure(cta, "%s: QMS_TFR_max is set but QMS is not.", block_name); + + dsc->supports_native_420 = has_bit(data[11 + offset], 6); + if (has_bit(data[11 + offset], 2)) + add_failure(cta, "%s: DSC_16bpc bit is reserved.", block_name); + if (get_bit_range(data[11 + offset], 5, 4) != 0) + add_failure(cta, "%s: Bits 4 and 5 of byte 11 are reserved.", block_name); + + max_slices = get_bit_range(data[12 + offset], 3, 0); + switch (max_slices) { + case DI_CTA_HDMI_DSC_MAX_SLICES_UNSUPPORTED: + case DI_CTA_HDMI_DSC_MAX_SLICES_1_340MHZ: + case DI_CTA_HDMI_DSC_MAX_SLICES_2_340MHZ: + case DI_CTA_HDMI_DSC_MAX_SLICES_4_340MHZ: + case DI_CTA_HDMI_DSC_MAX_SLICES_8_340MHZ: + case DI_CTA_HDMI_DSC_MAX_SLICES_8_400MHZ: + case DI_CTA_HDMI_DSC_MAX_SLICES_12_400MHZ: + case DI_CTA_HDMI_DSC_MAX_SLICES_16_400MHZ: + dsc->max_slices = max_slices; + break; + default: + add_failure(cta, "%s: Unknown DSC Max Slices (0x%02x).", + block_name, max_slices); + break; + } + + max_frl_rate = get_bit_range(data[12 + offset], 7, 4); + switch (max_frl_rate) { + case DI_CTA_HDMI_FRL_UNSUPPORTED: + case DI_CTA_HDMI_FRL_3GBPS_3LANES: + case DI_CTA_HDMI_FRL_6GBPS_3LANES: + case DI_CTA_HDMI_FRL_6GBPS_4LANES: + case DI_CTA_HDMI_FRL_8GBPS_4LANES: + case DI_CTA_HDMI_FRL_10GBPS_4LANES: + case DI_CTA_HDMI_FRL_12GBPS_4LANES: + dsc->max_frl_rate = max_frl_rate; + break; + default: + add_failure(cta, "%s: Unknown Max Fixed Rate Link (0x%02x).", + block_name, max_frl_rate); + break; + } + + dsc->max_total_chunk_bytes = 1024 * (1 + get_bit_range(data[13 + offset], 5, 0)); + if (get_bit_range(data[13 + offset], 7, 6) != 0) + add_failure(cta, "%s: Bits 6 and 7 of byte 13 are reserved.", block_name); + + if (has_bit(data[11 + offset], 7)) + scds->dsc = dsc; + else if (data[11 + offset] != 0 || data[12 + offset] != 0 || data[13 + offset] != 0) { + add_failure(cta, "%s: DSC_1p2 is unset but DSC bits are not zero.", block_name); + } + + for (i = 13; i < size; i++) { + if (data[i] != 0) + add_failure(cta, "%s: Byte %d is reserved.", block_name); + } + + return 0; +} + +static int +parse_vendor_hdmi_forum_block(struct di_cta *cta, + struct di_cta_vendor_hdmi_forum_block_priv *priv, + const uint8_t *data, size_t size) +{ + struct di_cta_vendor_hdmi_forum_block *block = &priv->base; + struct di_cta_hdmi_scds *scds = &block->scds; + struct di_cta_hdmi_dsc *dsc = &priv->dsc; + const char *block_name; + + block_name = "Vendor-Specific Data Block (HDMI Forum), OUI C4-5D-D8"; + + return parse_hdmi_scds(cta, scds, dsc, data, size, block_name); +} + +static int +parse_hdmi_forum_sink_cap(struct di_cta *cta, + struct di_cta_hdmi_forum_sink_cap_priv *priv, + const uint8_t *data, size_t size) +{ + struct di_cta_hdmi_forum_sink_cap *block = &priv->base; + struct di_cta_hdmi_scds *scds = &block->scds; + struct di_cta_hdmi_dsc *dsc = &priv->dsc; + const char *block_name; + + block_name = "HDMI Forum Sink Capability Data Block"; + + return parse_hdmi_scds(cta, scds, dsc, data, size, block_name); +} + +static int +parse_ycbcr420_block(struct di_cta *cta, + struct di_cta_ycbcr420_video_block_priv *ycbcr420, + const uint8_t *data, size_t size) +{ + uint8_t i; + struct di_cta_svd *svd; + + if (size == 0) { + add_failure(cta, "YCbCr 4:2:0 Video Data Block: Empty Data Block"); + return -EINVAL; + } + + assert(size < (1 << 8)); + + for (i = 0; i < size; i++) { + svd = parse_svd(cta, data[i], i, "YCbCr 4:2:0 Video Data Block"); + if (!svd) + continue; + assert(ycbcr420->svds_len < EDID_CTA_MAX_VIDEO_BLOCK_ENTRIES); + ycbcr420->svds[ycbcr420->svds_len++] = svd; + } + + ycbcr420->base.svds = (const struct di_cta_svd *const *)ycbcr420->svds; + return 0; +} + +static bool +parse_sad_format(struct di_cta *cta, uint8_t code, uint8_t code_ext, + enum di_cta_audio_format *format, const char *prefix) +{ + switch (code) { + case 0x0: + add_failure_until(cta, 3, "%s: Audio Format Code 0x00 is reserved.", prefix); + return false; + case 0x1: + *format = DI_CTA_AUDIO_FORMAT_LPCM; + break; + case 0x2: + *format = DI_CTA_AUDIO_FORMAT_AC3; + break; + case 0x3: + *format = DI_CTA_AUDIO_FORMAT_MPEG1; + break; + case 0x4: + *format = DI_CTA_AUDIO_FORMAT_MP3; + break; + case 0x5: + *format = DI_CTA_AUDIO_FORMAT_MPEG2; + break; + case 0x6: + *format = DI_CTA_AUDIO_FORMAT_AAC_LC; + break; + case 0x7: + *format = DI_CTA_AUDIO_FORMAT_DTS; + break; + case 0x8: + *format = DI_CTA_AUDIO_FORMAT_ATRAC; + break; + case 0x9: + *format = DI_CTA_AUDIO_FORMAT_ONE_BIT_AUDIO; + break; + case 0xA: + *format = DI_CTA_AUDIO_FORMAT_ENHANCED_AC3; + break; + case 0xB: + *format = DI_CTA_AUDIO_FORMAT_DTS_HD; + break; + case 0xC: + *format = DI_CTA_AUDIO_FORMAT_MAT; + break; + case 0xD: + *format = DI_CTA_AUDIO_FORMAT_DST; + break; + case 0xE: + *format = DI_CTA_AUDIO_FORMAT_WMA_PRO; + break; + case 0xF: + switch (code_ext) { + case 0x04: + *format = DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC; + break; + case 0x05: + *format = DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC_V2; + break; + case 0x06: + *format = DI_CTA_AUDIO_FORMAT_MPEG4_AAC_LC; + break; + case 0x07: + *format = DI_CTA_AUDIO_FORMAT_DRA; + break; + case 0x08: + *format = DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC_MPEG_SURROUND; + break; + case 0x0A: + *format = DI_CTA_AUDIO_FORMAT_MPEG4_AAC_LC_MPEG_SURROUND; + break; + case 0x0B: + *format = DI_CTA_AUDIO_FORMAT_MPEGH_3D; + break; + case 0x0C: + *format = DI_CTA_AUDIO_FORMAT_AC4; + break; + case 0x0D: + *format = DI_CTA_AUDIO_FORMAT_LPCM_3D; + break; + default: + add_failure_until(cta, 3, "%s: Unknown Audio Ext Format 0x%02x.", + prefix, code_ext); + return false; + } + break; + default: + add_failure_until(cta, 3, "%s: Unknown Audio Format 0x%02x.", prefix, code); + return false; + } + + return true; +} + +static int +parse_sad(struct di_cta *cta, struct di_cta_audio_block_priv *audio, + const uint8_t data[static CTA_SAD_SIZE]) +{ + enum di_cta_audio_format format; + struct di_cta_sad_priv *priv; + struct di_cta_sad *sad; + struct di_cta_sad_sample_rates *sample_rates; + struct di_cta_sad_lpcm *lpcm; + struct di_cta_sad_mpegh_3d *mpegh_3d; + struct di_cta_sad_mpeg_aac *mpeg_aac; + struct di_cta_sad_mpeg_surround *mpeg_surround; + struct di_cta_sad_mpeg_aac_le *mpeg_aac_le; + struct di_cta_sad_enhanced_ac3 *enhanced_ac3; + struct di_cta_sad_mat *mat; + struct di_cta_sad_wma_pro *wma_pro; + uint8_t code, code_ext; + + code = get_bit_range(data[0], 6, 3); + code_ext = get_bit_range(data[2], 7, 3); + + if (!parse_sad_format(cta, code, code_ext, &format, "Audio Data Block")) + return -EINVAL; + + priv = calloc(1, sizeof(*priv)); + if (!priv) + return -errno; + + sad = &priv->base; + sample_rates = &priv->supported_sample_rates; + lpcm = &priv->lpcm; + mpegh_3d = &priv->mpegh_3d; + mpeg_aac = &priv->mpeg_aac; + mpeg_surround = &priv->mpeg_surround; + mpeg_aac_le = &priv->mpeg_aac_le; + enhanced_ac3 = &priv->enhanced_ac3; + mat = &priv->mat; + wma_pro = &priv->wma_pro; + + sad->format = format; + + /* TODO: Find DRA documentation */ + + switch (format) { + case DI_CTA_AUDIO_FORMAT_LPCM: + case DI_CTA_AUDIO_FORMAT_AC3: + case DI_CTA_AUDIO_FORMAT_MPEG1: + case DI_CTA_AUDIO_FORMAT_MP3: + case DI_CTA_AUDIO_FORMAT_MPEG2: + case DI_CTA_AUDIO_FORMAT_AAC_LC: + case DI_CTA_AUDIO_FORMAT_DTS: + case DI_CTA_AUDIO_FORMAT_ATRAC: + case DI_CTA_AUDIO_FORMAT_ONE_BIT_AUDIO: + case DI_CTA_AUDIO_FORMAT_ENHANCED_AC3: + case DI_CTA_AUDIO_FORMAT_DTS_HD: + case DI_CTA_AUDIO_FORMAT_MAT: + case DI_CTA_AUDIO_FORMAT_DST: + case DI_CTA_AUDIO_FORMAT_WMA_PRO: + case DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC: + case DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC_V2: + case DI_CTA_AUDIO_FORMAT_MPEG4_AAC_LC: + /* DRA is not documented but this is what edid-decode does */ + case DI_CTA_AUDIO_FORMAT_DRA: + case DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC_MPEG_SURROUND: + case DI_CTA_AUDIO_FORMAT_MPEG4_AAC_LC_MPEG_SURROUND: + sad->max_channels = get_bit_range(data[0], 2, 0) + 1; + break; + case DI_CTA_AUDIO_FORMAT_LPCM_3D: + sad->max_channels = (get_bit_range(data[0], 2, 0) | + (get_bit_range(data[0], 7, 7) << 3) | + (get_bit_range(data[1], 7, 7) << 4)) + 1; + break; + case DI_CTA_AUDIO_FORMAT_MPEGH_3D: + case DI_CTA_AUDIO_FORMAT_AC4: + break; + } + + switch (format) { + case DI_CTA_AUDIO_FORMAT_LPCM: + case DI_CTA_AUDIO_FORMAT_AC3: + case DI_CTA_AUDIO_FORMAT_MPEG1: + case DI_CTA_AUDIO_FORMAT_MP3: + case DI_CTA_AUDIO_FORMAT_MPEG2: + case DI_CTA_AUDIO_FORMAT_AAC_LC: + case DI_CTA_AUDIO_FORMAT_DTS: + case DI_CTA_AUDIO_FORMAT_ATRAC: + case DI_CTA_AUDIO_FORMAT_ONE_BIT_AUDIO: + case DI_CTA_AUDIO_FORMAT_ENHANCED_AC3: + case DI_CTA_AUDIO_FORMAT_DTS_HD: + case DI_CTA_AUDIO_FORMAT_MAT: + case DI_CTA_AUDIO_FORMAT_DST: + case DI_CTA_AUDIO_FORMAT_WMA_PRO: + /* DRA is not documented but this is what edid-decode does */ + case DI_CTA_AUDIO_FORMAT_DRA: + case DI_CTA_AUDIO_FORMAT_MPEGH_3D: + case DI_CTA_AUDIO_FORMAT_LPCM_3D: + sample_rates->has_192_khz = has_bit(data[1], 6); + sample_rates->has_176_4_khz = has_bit(data[1], 5); + /* fallthrough */ + case DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC: + case DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC_V2: + case DI_CTA_AUDIO_FORMAT_MPEG4_AAC_LC: + case DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC_MPEG_SURROUND: + case DI_CTA_AUDIO_FORMAT_MPEG4_AAC_LC_MPEG_SURROUND: + sample_rates->has_96_khz = has_bit(data[1], 4); + sample_rates->has_88_2_khz = has_bit(data[1], 3); + sample_rates->has_48_khz = has_bit(data[1], 2); + sample_rates->has_44_1_khz = has_bit(data[1], 1); + sample_rates->has_32_khz = has_bit(data[1], 0); + break; + case DI_CTA_AUDIO_FORMAT_AC4: + sample_rates->has_192_khz = has_bit(data[1], 6); + sample_rates->has_96_khz = has_bit(data[1], 4); + sample_rates->has_48_khz = has_bit(data[1], 2); + sample_rates->has_44_1_khz = has_bit(data[1], 1); + break; + } + sad->supported_sample_rates = sample_rates; + + switch (format) { + case DI_CTA_AUDIO_FORMAT_AC3: + case DI_CTA_AUDIO_FORMAT_MPEG1: + case DI_CTA_AUDIO_FORMAT_MP3: + case DI_CTA_AUDIO_FORMAT_MPEG2: + case DI_CTA_AUDIO_FORMAT_AAC_LC: + case DI_CTA_AUDIO_FORMAT_DTS: + case DI_CTA_AUDIO_FORMAT_ATRAC: + sad->max_bitrate_kbs = data[2] * 8; + break; + default: + break; + } + + switch (format) { + case DI_CTA_AUDIO_FORMAT_LPCM: + case DI_CTA_AUDIO_FORMAT_LPCM_3D: + lpcm->has_sample_size_24_bits = has_bit(data[2], 2); + lpcm->has_sample_size_20_bits = has_bit(data[2], 1); + lpcm->has_sample_size_16_bits = has_bit(data[2], 0); + sad->lpcm = lpcm; + default: + break; + } + + switch (format) { + case DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC: + case DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC_V2: + case DI_CTA_AUDIO_FORMAT_MPEG4_AAC_LC: + case DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC_MPEG_SURROUND: + case DI_CTA_AUDIO_FORMAT_MPEG4_AAC_LC_MPEG_SURROUND: + mpeg_aac->has_frame_length_1024 = has_bit(data[2], 2); + mpeg_aac->has_frame_length_960 = has_bit(data[2], 1); + sad->mpeg_aac = mpeg_aac; + break; + default: + break; + } + + if (format == DI_CTA_AUDIO_FORMAT_MPEG4_AAC_LC) { + mpeg_aac_le->supports_multichannel_sound = has_bit(data[2], 0); + sad->mpeg_aac_le = mpeg_aac_le; + } + + switch (format) { + case DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC_MPEG_SURROUND: + case DI_CTA_AUDIO_FORMAT_MPEG4_AAC_LC_MPEG_SURROUND: + mpeg_surround->signaling = has_bit(data[2], 0); + sad->mpeg_surround = mpeg_surround; + break; + default: + break; + } + + if (format == DI_CTA_AUDIO_FORMAT_MPEGH_3D) { + mpegh_3d->low_complexity_profile = has_bit(data[2], 0); + mpegh_3d->baseline_profile = has_bit(data[2], 1); + mpegh_3d->level = get_bit_range(data[0], 2, 0); + if (mpegh_3d->level > DI_CTA_SAD_MPEGH_3D_LEVEL_5) { + add_failure_until(cta, 3, + "Unknown MPEG-H 3D Audio Level 0x%02x.", + mpegh_3d->level); + mpegh_3d->level = DI_CTA_SAD_MPEGH_3D_LEVEL_UNSPECIFIED; + } + sad->mpegh_3d = mpegh_3d; + } + + if (format == DI_CTA_AUDIO_FORMAT_ENHANCED_AC3) { + enhanced_ac3->supports_joint_object_coding = + has_bit(data[2], 0); + enhanced_ac3->supports_joint_object_coding_ACMOD28 = + has_bit(data[2], 1); + sad->enhanced_ac3 = enhanced_ac3; + } + + if (format == DI_CTA_AUDIO_FORMAT_MAT) { + mat->supports_object_audio_and_channel_based = + has_bit(data[2], 0); + if (mat->supports_object_audio_and_channel_based) + mat->requires_hash_calculation = !has_bit(data[2], 0); + sad->mat = mat; + } + + if (format == DI_CTA_AUDIO_FORMAT_WMA_PRO) { + wma_pro->profile = get_bit_range(data[2], 2, 0); + sad->wma_pro = wma_pro; + } + + switch (format) { + case DI_CTA_AUDIO_FORMAT_ONE_BIT_AUDIO: + case DI_CTA_AUDIO_FORMAT_DTS_HD: + case DI_CTA_AUDIO_FORMAT_DST: + /* TODO data[2] 7:0 contains unknown Audio Format Code dependent value */ + break; + default: + break; + } + + if (format == DI_CTA_AUDIO_FORMAT_AC4) { + /* TODO data[2] 2:0 contains unknown Audio Format Code dependent value */ + } + + switch (format) { + case DI_CTA_AUDIO_FORMAT_LPCM: + case DI_CTA_AUDIO_FORMAT_WMA_PRO: + if (has_bit(data[0], 7) || has_bit(data[1], 7) || + get_bit_range(data[2], 7, 3) != 0) + add_failure_until(cta, 3, + "Bits F17, F27, F37:F33 must be 0."); + break; + case DI_CTA_AUDIO_FORMAT_AC3: + case DI_CTA_AUDIO_FORMAT_MPEG1: + case DI_CTA_AUDIO_FORMAT_MP3: + case DI_CTA_AUDIO_FORMAT_MPEG2: + case DI_CTA_AUDIO_FORMAT_AAC_LC: + case DI_CTA_AUDIO_FORMAT_DTS: + case DI_CTA_AUDIO_FORMAT_ATRAC: + case DI_CTA_AUDIO_FORMAT_ONE_BIT_AUDIO: + case DI_CTA_AUDIO_FORMAT_ENHANCED_AC3: + case DI_CTA_AUDIO_FORMAT_DTS_HD: + case DI_CTA_AUDIO_FORMAT_MAT: + case DI_CTA_AUDIO_FORMAT_DST: + if (has_bit(data[0], 7) || has_bit(data[1], 7)) + add_failure_until(cta, 3, + "Bits F17, F27 must be 0."); + break; + case DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC: + case DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC_V2: + case DI_CTA_AUDIO_FORMAT_MPEG4_AAC_LC: + case DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC_MPEG_SURROUND: + case DI_CTA_AUDIO_FORMAT_MPEG4_AAC_LC_MPEG_SURROUND: + if (has_bit(data[0], 7) || get_bit_range(data[2], 7, 5) != 0) + add_failure_until(cta, 3, + "Bits F17, F27:F25 must be 0."); + break; + case DI_CTA_AUDIO_FORMAT_MPEGH_3D: + if (has_bit(data[0], 7) || has_bit(data[1], 7) || + has_bit(data[2], 2)) + add_failure_until(cta, 3, + "Bits F17, F27, F32 must be 0."); + break; + case DI_CTA_AUDIO_FORMAT_AC4: + if ((data[0] & 0x87) != 0 || (data[1] & 0xA9) != 0) + add_failure_until(cta, 3, + "Bits F17, F12:F10, F27, F25, F23, " + "F20 must be 0."); + break; + /* DRA documentation missing */ + case DI_CTA_AUDIO_FORMAT_DRA: + case DI_CTA_AUDIO_FORMAT_LPCM_3D: + break; + } + + assert(audio->sads_len < EDID_CTA_MAX_AUDIO_BLOCK_ENTRIES); + audio->sads[audio->sads_len++] = priv; + return 0; +} + +static int +parse_audio_block(struct di_cta *cta, struct di_cta_audio_block_priv *audio, + const uint8_t *data, size_t size) +{ + size_t i; + int ret; + + if (size % 3 != 0) + add_failure(cta, "Broken CTA-861 audio block length %d.", size); + + for (i = 0; i + 3 <= size; i += 3) { + ret = parse_sad(cta, audio, &data[i]); + if (ret != 0 && ret != -EINVAL) + return ret; + } + + if (audio->sads_len == 0) + return -EINVAL; + + audio->audio.sads = (const struct di_cta_sad *const *) audio->sads; + return 0; +} + +static bool +parse_speaker_alloc(struct di_cta *cta, struct di_cta_speaker_allocation *speaker_alloc, + const uint8_t data[3], const char *prefix) +{ + bool rlc_rrc; + + speaker_alloc->flw_frw = has_bit(data[0], 7); + rlc_rrc = has_bit(data[0], 6); + speaker_alloc->flc_frc = has_bit(data[0], 5); + speaker_alloc->bc = has_bit(data[0], 4); + speaker_alloc->bl_br = has_bit(data[0], 3); + speaker_alloc->fc = has_bit(data[0], 2); + speaker_alloc->lfe1 = has_bit(data[0], 1); + speaker_alloc->fl_fr = has_bit(data[0], 0); + if (rlc_rrc) { + if (cta->revision >= 3) + add_failure(cta, "%s: Deprecated bit F16 must be 0.", prefix); + else + speaker_alloc->bl_br = true; + } + + speaker_alloc->tpsil_tpsir = has_bit(data[1], 7); + speaker_alloc->sil_sir = has_bit(data[1], 6); + speaker_alloc->tpbc = has_bit(data[1], 5); + speaker_alloc->lfe2 = has_bit(data[1], 4); + speaker_alloc->ls_rs = has_bit(data[1], 3); + speaker_alloc->tpfc = has_bit(data[1], 2); + speaker_alloc->tpc = has_bit(data[1], 1); + speaker_alloc->tpfl_tpfr = has_bit(data[1], 0); + + if (get_bit_range(data[2], 7, 4) != 0) + add_failure(cta, "%s: Bits F37, F36, F34 must be 0.", prefix); + if (cta->revision >= 3 && has_bit(data[2], 3)) + add_failure(cta, "%s: Deprecated bit F33 must be 0.", prefix); + speaker_alloc->btfl_btfr = has_bit(data[2], 2); + speaker_alloc->btfc = has_bit(data[2], 1); + speaker_alloc->tpbl_tpbr = has_bit(data[2], 0); + + return true; +} + +static int +parse_speaker_alloc_block(struct di_cta *cta, + struct di_cta_speaker_alloc_block *speaker_alloc, + const uint8_t *data, size_t size) +{ + if (size < 3) { + add_failure(cta, + "Speaker Allocation Data Block: Empty Data Block with length %zu.", + size); + return -EINVAL; + } + + parse_speaker_alloc(cta, &speaker_alloc->speakers, data, + "Speaker Allocation Data Block"); + + return 0; +} + +static int +parse_video_cap_block(struct di_cta *cta, + struct di_cta_video_cap_block *video_cap, + const uint8_t *data, size_t size) +{ + if (size < 1) { + add_failure(cta, + "Video Capability Data Block: Empty Data Block with length %u.", + size); + return -EINVAL; + } + + video_cap->selectable_ycc_quantization_range = has_bit(data[0], 7); + video_cap->selectable_rgb_quantization_range = has_bit(data[0], 6); + video_cap->pt_over_underscan = get_bit_range(data[0], 5, 4); + video_cap->it_over_underscan = get_bit_range(data[0], 3, 2); + video_cap->ce_over_underscan = get_bit_range(data[0], 1, 0); + + if (!video_cap->selectable_rgb_quantization_range && cta->revision >= 3) + add_failure(cta, + "Video Capability Data Block: Set Selectable RGB Quantization to avoid interop issues."); + /* TODO: add failure if selectable_ycc_quantization_range is unset, + * the sink supports YCbCr formats and the revision is 3+ */ + + switch (video_cap->it_over_underscan) { + case DI_CTA_VIDEO_CAP_ALWAYS_OVERSCAN: + if (cta->flags && cta->flags->it_underscan) + add_failure(cta, "Video Capability Data Block: IT video formats are always overscanned, but bit 7 of Byte 3 of the CTA-861 Extension header is set to underscanned."); + break; + case DI_CTA_VIDEO_CAP_ALWAYS_UNDERSCAN: + if (cta->flags && !cta->flags->it_underscan) + add_failure(cta, "Video Capability Data Block: IT video formats are always underscanned, but bit 7 of Byte 3 of the CTA-861 Extension header is set to overscanned."); + default: + break; + } + + return 0; +} + +static bool +check_vesa_display_device_num_channels(enum di_cta_vesa_display_device_interface_type interface, + uint8_t num_channels) +{ + switch (interface) { + case DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_VGA: + case DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_NAVI_V: + case DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_NAVI_D: + return num_channels == 0; + case DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_LVDS: + case DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_RSDS: + return true; + case DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_DVI_D: + return num_channels == 1 || num_channels == 2; + case DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_DVI_I_ANALOG: + return num_channels == 0; + case DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_DVI_I_DIGITAL: + return num_channels == 1 || num_channels == 2; + case DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_HDMI_A: + return num_channels == 1; + case DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_HDMI_B: + return num_channels == 2; + case DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_MDDI: + return num_channels == 1 || num_channels == 2; + case DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_DISPLAYPORT: + return num_channels == 1 || num_channels == 2 || num_channels == 4; + case DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_IEEE_1394: + case DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_M1_ANALOG: + return num_channels == 0; + case DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_M1_DIGITAL: + return num_channels == 1 || num_channels == 2; + } + abort(); /* unreachable */ +} + +static void +parse_vesa_display_device_additional_primary_chromaticity(struct di_cta_vesa_display_device_additional_primary_chromaticity *coords, + uint8_t low, + const uint8_t high[static 2]) +{ + uint16_t raw_x, raw_y; /* only 10 bits are used */ + + raw_x = (uint16_t) ((high[0] << 2) | get_bit_range(low, 3, 2)); + raw_y = (uint16_t) ((high[1] << 2) | get_bit_range(low, 1, 0)); + + *coords = (struct di_cta_vesa_display_device_additional_primary_chromaticity) { + .x = (float) raw_x / 1024, + .y = (float) raw_y / 1024, + }; +} + +static int +parse_vesa_display_device(struct di_cta *cta, struct di_cta_vesa_display_device_block *dddb, + const uint8_t *data, size_t size) +{ + const size_t offset = 2; /* CTA block header */ + uint8_t interface_type, num_channels, content_protection, scan_direction, + subpixel_layout; + + if (size + offset != 32) { + add_failure(cta, "VESA Video Display Device Data Block: Invalid length %u.", size); + return -EINVAL; + } + + interface_type = get_bit_range(data[0x02 - offset], 7, 4); + num_channels = get_bit_range(data[0x02 - offset], 3, 0); + switch (interface_type) { + case 0x0: /* Analog */ + /* Special case: num_channels contains the detailed interface + * type. */ + switch (num_channels) { + case 0x0: + dddb->interface_type = DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_VGA; + break; + case 0x1: + dddb->interface_type = DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_NAVI_V; + break; + case 0x2: + dddb->interface_type = DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_NAVI_D; + break; + default: + add_failure(cta, + "VESA Video Display Device Data Block: Unknown analog interface type 0x%x.", + num_channels); + return -EINVAL; + } + num_channels = 0; + break; + case 0x1: + dddb->interface_type = DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_LVDS; + break; + case 0x2: + dddb->interface_type = DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_RSDS; + break; + case 0x3: + dddb->interface_type = DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_DVI_D; + break; + case 0x4: + dddb->interface_type = DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_DVI_I_ANALOG; + break; + case 0x5: + dddb->interface_type = DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_DVI_I_DIGITAL; + break; + case 0x6: + dddb->interface_type = DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_HDMI_A; + break; + case 0x7: + dddb->interface_type = DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_HDMI_B; + break; + case 0x8: + dddb->interface_type = DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_MDDI; + break; + case 0x9: + dddb->interface_type = DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_DISPLAYPORT; + break; + case 0xA: + dddb->interface_type = DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_IEEE_1394; + break; + case 0xB: + dddb->interface_type = DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_M1_ANALOG; + break; + case 0xC: + dddb->interface_type = DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_M1_DIGITAL; + break; + default: + add_failure(cta, + "VESA Video Display Device Data Block: Unknown interface type 0x%x.", + interface_type); + return -EINVAL; + } + + if (check_vesa_display_device_num_channels(dddb->interface_type, num_channels)) + dddb->num_channels = num_channels; + else + add_failure(cta, + "VESA Video Display Device Data Block: Invalid number of lanes/channels %u.", + num_channels); + + dddb->interface_version = get_bit_range(data[0x03 - offset], 7, 4); + dddb->interface_release = get_bit_range(data[0x03 - offset], 3, 0); + + content_protection = data[0x04 - offset]; + switch (content_protection) { + case DI_CTA_VESA_DISPLAY_DEVICE_CONTENT_PROTECTION_NONE: + case DI_CTA_VESA_DISPLAY_DEVICE_CONTENT_PROTECTION_HDCP: + case DI_CTA_VESA_DISPLAY_DEVICE_CONTENT_PROTECTION_DTCP: + case DI_CTA_VESA_DISPLAY_DEVICE_CONTENT_PROTECTION_DPCP: + dddb->content_protection = content_protection; + break; + default: + add_failure(cta, + "VESA Video Display Device Data Block: Invalid content protection 0x%x.", + content_protection); + } + + dddb->min_clock_freq_mhz = get_bit_range(data[0x05 - offset], 7, 2); + dddb->max_clock_freq_mhz = + (get_bit_range(data[0x05 - offset], 1, 0) << 8) | data[0x06 - offset]; + if (dddb->min_clock_freq_mhz > dddb->max_clock_freq_mhz) { + add_failure(cta, + "VESA Video Display Device Data Block: Minimum clock frequency (%d MHz) greater than maximum (%d MHz).", + dddb->min_clock_freq_mhz, dddb->max_clock_freq_mhz); + dddb->min_clock_freq_mhz = dddb->max_clock_freq_mhz = 0; + } + + dddb->native_horiz_pixels = data[0x07 - offset] | (data[0x08 - offset] << 8); + dddb->native_vert_pixels = data[0x09 - offset] | (data[0x0A - offset] << 8); + + dddb->aspect_ratio = (float)data[0x0B - offset] / 100 + 1; + dddb->default_orientation = get_bit_range(data[0x0C - offset], 7, 6); + dddb->rotation_cap = get_bit_range(data[0x0C - offset], 5, 4); + dddb->zero_pixel_location = get_bit_range(data[0x0C - offset], 3, 2); + scan_direction = get_bit_range(data[0x0C - offset], 1, 0); + if (scan_direction != 3) + dddb->scan_direction = scan_direction; + else + add_failure(cta, + "VESA Video Display Device Data Block: Invalid scan direction 0x%x.", + scan_direction); + + subpixel_layout = data[0x0D - offset]; + switch (subpixel_layout) { + case DI_CTA_VESA_DISPLAY_DEVICE_SUBPIXEL_UNDEFINED: + case DI_CTA_VESA_DISPLAY_DEVICE_SUBPIXEL_RGB_VERT: + case DI_CTA_VESA_DISPLAY_DEVICE_SUBPIXEL_RGB_HORIZ: + case DI_CTA_VESA_DISPLAY_DEVICE_SUBPIXEL_EDID_CHROM_VERT: + case DI_CTA_VESA_DISPLAY_DEVICE_SUBPIXEL_EDID_CHROM_HORIZ: + case DI_CTA_VESA_DISPLAY_DEVICE_SUBPIXEL_QUAD_RGGB: + case DI_CTA_VESA_DISPLAY_DEVICE_SUBPIXEL_QUAD_GBRG: + case DI_CTA_VESA_DISPLAY_DEVICE_SUBPIXEL_DELTA_RGB: + case DI_CTA_VESA_DISPLAY_DEVICE_SUBPIXEL_MOSAIC: + case DI_CTA_VESA_DISPLAY_DEVICE_SUBPIXEL_QUAD_ANY: + case DI_CTA_VESA_DISPLAY_DEVICE_SUBPIXEL_FIVE: + case DI_CTA_VESA_DISPLAY_DEVICE_SUBPIXEL_SIX: + case DI_CTA_VESA_DISPLAY_DEVICE_SUBPIXEL_CLAIRVOYANTE_PENTILE: + dddb->subpixel_layout = subpixel_layout; + break; + default: + add_failure(cta, + "VESA Video Display Device Data Block: Invalid subpixel layout 0x%x.", + subpixel_layout); + } + + dddb->horiz_pitch_mm = (float)data[0x0E - offset] * 0.01f; + dddb->vert_pitch_mm = (float)data[0x0F - offset] * 0.01f; + + dddb->dithering_type = get_bit_range(data[0x10 - offset], 7, 6); + dddb->direct_drive = has_bit(data[0x10 - offset], 5); + dddb->overdrive_not_recommended = has_bit(data[0x10 - offset], 4); + dddb->deinterlacing = has_bit(data[0x10 - offset], 3); + if (get_bit_range(data[0x10 - offset], 2, 0) != 0) + add_failure(cta, "VESA Video Display Device Data Block: Reserved miscellaneous display capabilities bits 2-0 must be 0."); + + dddb->audio_support = has_bit(data[0x11 - offset], 7); + dddb->separate_audio_inputs = has_bit(data[0x11 - offset], 6); + dddb->audio_input_override = has_bit(data[0x11 - offset], 5); + if (get_bit_range(data[0x11 - offset], 4, 0) != 0) + add_failure(cta, "VESA Video Display Device Data Block: Reserved audio bits 4-0 must be 0."); + + dddb->audio_delay_provided = data[0x12 - offset] != 0; + dddb->audio_delay_ms = 2 * get_bit_range(data[0x12 - offset], 6, 0); + if (!has_bit(data[0x12 - offset], 7)) + dddb->audio_delay_ms = -dddb->audio_delay_ms; + + dddb->frame_rate_conversion = get_bit_range(data[0x13 - offset], 7, 6); + dddb->frame_rate_range_hz = get_bit_range(data[0x13 - offset], 5, 0); + dddb->frame_rate_native_hz = data[0x14 - offset]; + + dddb->bit_depth_interface = get_bit_range(data[0x15 - offset], 7, 4) + 1; + dddb->bit_depth_display = get_bit_range(data[0x15 - offset], 3, 0) + 1; + + dddb->additional_primary_chromaticities_len = get_bit_range(data[0x17 - offset], 1, 0); + parse_vesa_display_device_additional_primary_chromaticity(&dddb->additional_primary_chromaticities[0], + get_bit_range(data[0x16 - offset], 7, 4), + &data[0x18 - offset]); + parse_vesa_display_device_additional_primary_chromaticity(&dddb->additional_primary_chromaticities[1], + get_bit_range(data[0x16 - offset], 3, 0), + &data[0x1A - offset]); + parse_vesa_display_device_additional_primary_chromaticity(&dddb->additional_primary_chromaticities[2], + get_bit_range(data[0x17 - offset], 7, 4), + &data[0x1C - offset]); + if (get_bit_range(data[0x17 - offset], 3, 2) != 0) + add_failure(cta, "VESA Video Display Device Data Block: Reserved additional primary chromaticities bits 3-2 of byte 0x17 must be 0."); + + dddb->resp_time_transition = has_bit(data[0x1E - offset], 7); + dddb->resp_time_ms = get_bit_range(data[0x1E - offset], 6, 0); + + dddb->overscan_horiz_pct = get_bit_range(data[0x1F - offset], 7, 4); + dddb->overscan_vert_pct = get_bit_range(data[0x1F - offset], 3, 0); + + return 0; +} + +static int +parse_colorimetry_block(struct di_cta *cta, + struct di_cta_colorimetry_block *colorimetry, + const uint8_t *data, size_t size) +{ + if (size < 2) { + add_failure(cta, "Colorimetry Data Block: Empty Data Block with length %u.", + size); + return -EINVAL; + } + + colorimetry->bt2020_rgb = has_bit(data[0], 7); + colorimetry->bt2020_ycc = has_bit(data[0], 6); + colorimetry->bt2020_cycc = has_bit(data[0], 5); + colorimetry->oprgb = has_bit(data[0], 4); + colorimetry->opycc_601 = has_bit(data[0], 3); + colorimetry->sycc_601 = has_bit(data[0], 2); + colorimetry->xvycc_709 = has_bit(data[0], 1); + colorimetry->xvycc_601 = has_bit(data[0], 0); + + colorimetry->st2113_rgb = has_bit(data[1], 7); + colorimetry->ictcp = has_bit(data[1], 6); + + if (get_bit_range(data[1], 5, 0) != 0) + add_failure_until(cta, 3, + "Colorimetry Data Block: Reserved bits MD0-MD3 must be 0."); + + return 0; +} + +static float +parse_max_luminance(uint8_t raw) +{ + if (raw == 0) + return 0; + return 50 * powf(2, (float) raw / 32); +} + +static float +parse_min_luminance(uint8_t raw, float max) +{ + if (raw == 0) + return 0; + return max * powf((float) raw / 255, 2) / 100; +} + +static int +parse_hdr_static_metadata_block(struct di_cta *cta, + struct di_cta_hdr_static_metadata_block_priv *metadata, + const uint8_t *data, size_t size) +{ + uint8_t eotfs, descriptors; + + if (size < 2) { + add_failure(cta, "HDR Static Metadata Data Block: Empty Data Block with length %u.", + size); + return -EINVAL; + } + + metadata->base.eotfs = &metadata->eotfs; + metadata->base.descriptors = &metadata->descriptors; + + eotfs = data[0]; + metadata->eotfs.traditional_sdr = has_bit(eotfs, 0); + metadata->eotfs.traditional_hdr = has_bit(eotfs, 1); + metadata->eotfs.pq = has_bit(eotfs, 2); + metadata->eotfs.hlg = has_bit(eotfs, 3); + if (get_bit_range(eotfs, 7, 4)) + add_failure_until(cta, 3, "HDR Static Metadata Data Block: Unknown EOTF."); + + descriptors = data[1]; + metadata->descriptors.type1 = has_bit(descriptors, 0); + if (get_bit_range(descriptors, 7, 1)) + add_failure_until(cta, 3, "HDR Static Metadata Data Block: Unknown descriptor type."); + + if (size > 2) + metadata->base.desired_content_max_luminance = parse_max_luminance(data[2]); + if (size > 3) + metadata->base.desired_content_max_frame_avg_luminance = parse_max_luminance(data[3]); + if (size > 4) { + if (metadata->base.desired_content_max_luminance == 0) + add_failure(cta, "HDR Static Metadata Data Block: Desired content min luminance is set, but max luminance is unset."); + else + metadata->base.desired_content_min_luminance = + parse_min_luminance(data[4], metadata->base.desired_content_max_luminance); + } + + return 0; +} + +static int +parse_hdr_dynamic_metadata_block(struct di_cta *cta, + struct di_cta_hdr_dynamic_metadata_block_priv *priv, + const uint8_t *data, size_t size) +{ + struct di_cta_hdr_dynamic_metadata_block *base; + struct di_cta_hdr_dynamic_metadata_type1 *type1; + struct di_cta_hdr_dynamic_metadata_type2 *type2; + struct di_cta_hdr_dynamic_metadata_type3 *type3; + struct di_cta_hdr_dynamic_metadata_type4 *type4; + struct di_cta_hdr_dynamic_metadata_type256 *type256; + size_t length; + int type; + + base = &priv->base; + type1 = &priv->type1; + type2 = &priv->type2; + type3 = &priv->type3; + type4 = &priv->type4; + type256 = &priv->type256; + + if (size < 3) { + add_failure(cta, "HDR Dynamic Metadata Data Block: Empty Data Block with length %u.", + size); + return -EINVAL; + } + + while (size >= 3) { + length = data[0]; + + if (size < length + 1) { + add_failure(cta, "HDR Dynamic Metadata Data Block: Length of type bigger than block size."); + return -EINVAL; + } + + if (length < 2) { + add_failure(cta, "HDR Dynamic Metadata Data Block: Type has wrong length."); + return -EINVAL; + } + + type = (data[2] << 8) | data[1]; + switch (type) { + case 0x0001: + if (length < 3) { + add_failure(cta, "HDR Dynamic Metadata Data Block: Type 1 missing Support Flags."); + break; + } + if (length != 3) + add_failure(cta, "HDR Dynamic Metadata Data Block: Type 1 length must be 3."); + type1->type_1_hdr_metadata_version = get_bit_range(data[3], 3, 0); + base->type1 = type1; + if (get_bit_range(data[3], 7, 4) != 0) + add_failure(cta, "HDR Dynamic Metadata Data Block: Type 1 support flags bits 7-4 must be 0."); + break; + case 0x0002: + if (length < 3) { + add_failure(cta, "HDR Dynamic Metadata Data Block: Type 2 missing Support Flags."); + break; + } + if (length != 3) + add_failure(cta, "HDR Dynamic Metadata Data Block: Type 2 length must be 3."); + type2->ts_103_433_spec_version = get_bit_range(data[3], 3, 0); + if (type2->ts_103_433_spec_version == 0) { + add_failure(cta, "HDR Dynamic Metadata Data Block: Type 2 spec version of 0 is not allowed."); + break; + } + type2->ts_103_433_1_capable = has_bit(data[3], 4); + type2->ts_103_433_2_capable = has_bit(data[3], 5); + type2->ts_103_433_3_capable = has_bit(data[3], 6); + base->type2 = type2; + if (has_bit(data[3], 7) != 0) + add_failure(cta, "HDR Dynamic Metadata Data Block: Type 1 support flags bit 7 must be 0."); + break; + case 0x0003: + if (length != 2) + add_failure(cta, "HDR Dynamic Metadata Data Block: Type 3 length must be 2."); + base->type3 = type3; + break; + case 0x0004: + if (length < 3) { + add_failure(cta, "HDR Dynamic Metadata Data Block: Type 4 missing Support Flags."); + break; + } + if (length != 3) + add_failure(cta, "HDR Dynamic Metadata Data Block: Type 4 length must be 3."); + type4->type_4_hdr_metadata_version = get_bit_range(data[3], 3, 0); + base->type4 = type4; + if (get_bit_range(data[3], 7, 4) != 0) + add_failure(cta, "HDR Dynamic Metadata Data Block: Type 4 support flags bits 7-4 must be 0."); + break; + case 0x0100: + if (length < 3) { + add_failure(cta, "HDR Dynamic Metadata Data Block: Type 256 missing Support Flags."); + break; + } + if (length != 3) + add_failure(cta, "HDR Dynamic Metadata Data Block: Type 256 length must be 3."); + type256->graphics_overlay_flag_version = get_bit_range(data[3], 3, 0); + base->type256 = type256; + if (get_bit_range(data[3], 7, 4) != 0) + add_failure(cta, "HDR Dynamic Metadata Data Block: Type 256 support flags bits 7-4 must be 0."); + break; + default: + add_failure(cta, "HDR Dynamic Metadata Data Block: Unknown Type 0x%04x.", type); + break; + } + + size -= length + 1; + data += length + 1; + } + + return 0; +} + +static int +parse_vesa_transfer_characteristics_block(struct di_cta *cta, + struct di_cta_vesa_transfer_characteristics_block *tf, + const uint8_t *data, size_t size) +{ + size_t i; + + if (size != 7 && size != 15 && size != 31) { + add_failure(cta, "Invalid length %u.", size); + return -EINVAL; + } + + tf->points_len = (uint8_t) size + 1; + tf->usage = get_bit_range(data[0], 7, 6); + + tf->points[0] = get_bit_range(data[0], 5, 0) / 1023.0f; + for (i = 1; i < size; i++) + tf->points[i] = tf->points[i - 1] + data[i] / 1023.0f; + tf->points[i] = 1.0f; + + return 0; +} + +static int +parse_video_format_pref_block(struct di_cta *cta, + struct di_cta_video_format_pref_priv *vfpdb, + const uint8_t *data, size_t size) +{ + struct di_cta_svr *svr; + size_t i; + uint8_t code; + + for (i = 0; i < size; i++) { + code = data[i]; + + if (code == 0 || + code == 128 || + (code >= 161 && code <= 192) || + code == 255) { + add_failure(cta, "Video Format Preference Data Block: " + "using reserved Short Video Reference value %u.", + code); + continue; + } + + svr = calloc(1, sizeof(*svr)); + if (!svr) + return -errno; + + if ((code >= 1 && code <= 127) || + (code >= 193 && code <= 253)) { + svr->type = DI_CTA_SVR_TYPE_VIC; + svr->vic.code = code; + } else if (code >= 129 && code <= 144) { + svr->type = DI_CTA_SVR_TYPE_DTD_INDEX; + svr->dtd_index = code - 129; + } else if (code >= 145 && code <= 160) { + svr->type = DI_CTA_SVR_TYPE_T7T10VTDB; + svr->dtd_index = code - 145; + } else if (code == 254) { + svr->type = DI_CTA_SVR_TYPE_FIRST_T8VTDB; + } else { + abort(); /* unreachable */ + } + + assert(vfpdb->svrs_len < EDID_CTA_MAX_VIDEO_FORMAT_PREF_BLOCK_ENTRIES); + vfpdb->svrs[vfpdb->svrs_len++] = svr; + } + + vfpdb->base.svrs = (const struct di_cta_svr *const *)vfpdb->svrs; + return 0; +} + + +static void +parse_ycbcr420_cap_map(struct di_cta *cta, + struct di_cta_ycbcr420_cap_map_block *ycbcr420_cap_map, + const uint8_t *data, size_t size) +{ + if (size == 0) { + ycbcr420_cap_map->all = true; + return; + } + + assert(size <= sizeof(ycbcr420_cap_map->svd_bitmap)); + memcpy(ycbcr420_cap_map->svd_bitmap, data, size); +} + +static bool +parse_hdmi_audio_3d_descriptor(struct di_cta *cta, + struct di_cta_sad_priv *sad, + const uint8_t *data, size_t size) +{ + /* Contains the same data as the Short Audio Descriptor, packed differently */ + struct di_cta_sad *base = &sad->base; + struct di_cta_sad_sample_rates *sample_rate = &sad->supported_sample_rates; + struct di_cta_sad_lpcm *lpcm = &sad->lpcm; + uint8_t code; + + assert(size >= CTA_HDMI_AUDIO_3D_DESCRIPTOR_SIZE); + + code = get_bit_range(data[0], 3, 0); + if (!parse_sad_format(cta, code, 0, &base->format, "HDMI Audio Data Block")) + return false; + + if (base->format != DI_CTA_AUDIO_FORMAT_LPCM && + base->format != DI_CTA_AUDIO_FORMAT_ONE_BIT_AUDIO) { + add_failure(cta, + "HDMI Audio Data Block: Unsupported 3D Audio Format 0x%04x.", + code); + return false; + } + + base->max_channels = get_bit_range(data[1], 4, 0) + 1; + sample_rate->has_192_khz = has_bit(data[2], 6); + sample_rate->has_176_4_khz = has_bit(data[2], 5); + sample_rate->has_96_khz = has_bit(data[2], 4); + sample_rate->has_88_2_khz = has_bit(data[2], 3); + sample_rate->has_48_khz = has_bit(data[2], 2); + sample_rate->has_44_1_khz = has_bit(data[2], 1); + sample_rate->has_32_khz = has_bit(data[2], 0); + base->supported_sample_rates = sample_rate; + + if (base->format == DI_CTA_AUDIO_FORMAT_LPCM) { + lpcm->has_sample_size_24_bits = has_bit(data[3], 2); + lpcm->has_sample_size_20_bits = has_bit(data[3], 1); + lpcm->has_sample_size_16_bits = has_bit(data[3], 0); + base->lpcm = lpcm; + } + + if (base->format == DI_CTA_AUDIO_FORMAT_ONE_BIT_AUDIO) { + /* TODO data[3] 7:0 contains unknown Audio Format Code dependent value */ + } + + return true; +} + +static int +parse_hdmi_audio_block(struct di_cta *cta, + struct di_cta_hdmi_audio_block_priv *priv, + const uint8_t *data, size_t size) +{ + struct di_cta_hdmi_audio_block *hdmi_audio = &priv->base; + struct di_cta_hdmi_audio_multi_stream *ms = &priv->ms; + struct di_cta_hdmi_audio_3d *a3d = &priv->a3d; + uint8_t multi_stream; + bool ms_non_mixed; + size_t num_3d_audio_descs; + size_t num_descs; + struct di_cta_sad_priv *sad_priv; + uint8_t channels; + + if (size < 1) { + add_failure(cta, "HDMI Audio Data Block: Empty Data Block with length 0."); + return -EINVAL; + } + + multi_stream = get_bit_range(data[0], 1, 0); + ms_non_mixed = has_bit(data[0], 2); + + if (multi_stream > 0) { + hdmi_audio->multi_stream = ms; + ms->max_streams = multi_stream + 1; + ms->supports_non_mixed = ms_non_mixed; + } else if (ms_non_mixed) { + add_failure(cta, "HDMI Audio Data Block: MS NonMixed support indicated but " + "Max Stream Count == 0."); + } + + if (size < 2) + return 0; + + num_3d_audio_descs = get_bit_range(data[1], 2, 0); + if (num_3d_audio_descs == 0) + return 0; + + /* If there are 3d Audio Descriptors, there is one last Speaker Allocation Descriptor */ + num_descs = num_3d_audio_descs + 1; + + /* Skip to the first descriptor */ + size -= 2; + data += 2; + + /* Make sure there is enough space for the descriptors */ + if (num_descs > size / CTA_HDMI_AUDIO_3D_DESCRIPTOR_SIZE) { + add_failure(cta, "HDMI Audio Data Block: More descriptors indicated than block size allows."); + return 0; + } + + hdmi_audio->audio_3d = a3d; + a3d->sads = (const struct di_cta_sad * const*)priv->sads; + + /* First the 3D Audio Descriptors, the last one is the 3D Speaker Allocation Descriptor */ + while (num_descs > 1) { + sad_priv = calloc(1, sizeof(*sad_priv)); + if (!sad_priv) + return -errno; + + if (!parse_hdmi_audio_3d_descriptor(cta, sad_priv, data, size)) { + free(sad_priv); + goto skip; + } + + assert(priv->sads_len < EDID_CTA_MAX_HDMI_AUDIO_BLOCK_ENTRIES); + priv->sads[priv->sads_len++] = sad_priv; + +skip: + num_descs--; + size -= CTA_HDMI_AUDIO_3D_DESCRIPTOR_SIZE; + data += CTA_HDMI_AUDIO_3D_DESCRIPTOR_SIZE; + } + + channels = get_bit_range(data[3], 7, 4); + + switch (channels) { + case DI_CTA_HDMI_AUDIO_3D_CHANNELS_UNKNOWN: + case DI_CTA_HDMI_AUDIO_3D_CHANNELS_10_2: + case DI_CTA_HDMI_AUDIO_3D_CHANNELS_22_2: + case DI_CTA_HDMI_AUDIO_3D_CHANNELS_30_2: + a3d->channels = channels; + break; + default: + a3d->channels = DI_CTA_HDMI_AUDIO_3D_CHANNELS_UNKNOWN; + break; + } + + parse_speaker_alloc(cta, &a3d->speakers, data, "Room Configuration Data Block"); + + return 0; +} + +static struct di_cta_infoframe_descriptor * +parse_infoframe(struct di_cta *cta, uint8_t type, + const uint8_t *data, size_t size) +{ + struct di_cta_infoframe_descriptor infoframe = {0}; + struct di_cta_infoframe_descriptor *ifp; + + if (type >= 8 && type <= 0x1f) { + add_failure(cta, "InfoFrame Data Block: Type code %u is reserved.", + type); + return NULL; + } + + if (type >= 0x20) { + add_failure(cta, "InfoFrame Data Block: Type code %u is forbidden.", + type); + return NULL; + } + + if (type == 1) { + /* No known vendor specific InfoFrames, yet */ + return NULL; + } else { + switch (type) { + case 0x02: + infoframe.type = DI_CTA_INFOFRAME_TYPE_AUXILIARY_VIDEO_INFORMATION; + break; + case 0x03: + infoframe.type = DI_CTA_INFOFRAME_TYPE_SOURCE_PRODUCT_DESCRIPTION; + break; + case 0x04: + infoframe.type = DI_CTA_INFOFRAME_TYPE_AUDIO; + break; + case 0x05: + infoframe.type = DI_CTA_INFOFRAME_TYPE_MPEG_SOURCE; + break; + case 0x06: + infoframe.type = DI_CTA_INFOFRAME_TYPE_NTSC_VBI; + break; + case 0x07: + infoframe.type = DI_CTA_INFOFRAME_TYPE_DYNAMIC_RANGE_AND_MASTERING; + break; + default: + abort(); /* unreachable */ + } + } + + ifp = calloc(1, sizeof(*ifp)); + if (!ifp) + return NULL; + + *ifp = infoframe; + return ifp; +} + +static int +parse_infoframe_block(struct di_cta *cta, + struct di_cta_infoframe_block_priv *ifb, + const uint8_t *data, size_t size) +{ + size_t index = 0, length; + uint8_t type; + struct di_cta_infoframe_descriptor *infoframe; + + if (size < 2) { + add_failure(cta, "InfoFrame Data Block: Empty Data Block with length %u.", + size); + return -EINVAL; + } + + ifb->base.num_simultaneous_vsifs = data[1] + 1; + ifb->base.infoframes = (const struct di_cta_infoframe_descriptor *const *)ifb->infoframes; + + index = get_bit_range(data[0], 7, 5) + 2; + if (get_bit_range(data[0], 4, 0) != 0) + add_failure(cta, "InfoFrame Data Block: InfoFrame Processing " + "Descriptor Header bits F14-F10 shall be 0."); + + while (true) { + if (index == size) + break; + if (index > size) { + add_failure(cta, "InfoFrame Data Block: Payload length exceeds block size."); + return -EINVAL; + } + + length = get_bit_range(data[index], 7, 5); + type = get_bit_range(data[index], 4, 0); + + if (type == 0) { + add_failure(cta, "InfoFrame Data Block: Short InfoFrame Descriptor with type 0 is forbidden."); + return -EINVAL; + } else if (type == 1) { + length += 4; + } else { + length += 1; + } + + if (index + length > size) { + add_failure(cta, "InfoFrame Data Block: Payload length exceeds block size."); + return -EINVAL; + } + + infoframe = parse_infoframe(cta, type, &data[index], length); + if (infoframe) { + assert(ifb->infoframes_len < EDID_CTA_INFOFRAME_BLOCK_ENTRIES); + ifb->infoframes[ifb->infoframes_len++] = infoframe; + } + + index += length; + } + + return 0; +} + +static double +decode_coord(unsigned char x) +{ + signed char s = (signed char)x; + + return s / 64.0; +} + +static int +parse_room_config_block(struct di_cta *cta, + struct di_cta_room_configuration_block *rc, + const uint8_t *data, size_t size) +{ + bool has_display_coords; + bool has_speaker_count; + + if (size < 4) { + add_failure(cta, "Room Configuration Data Block: Empty Data Block with length %u.", + size); + return -EINVAL; + } + + has_display_coords = has_bit(data[0], 7); + has_speaker_count = has_bit(data[0], 6); + rc->has_speaker_location_descriptors = has_bit(data[0], 5); + + if (has_speaker_count) { + rc->speaker_count = get_bit_range(data[0], 4, 0) + 1; + } else { + if (get_bit_range(data[0], 4, 0) != 0) { + add_failure(cta, "Room Configuration Data Block: " + "'Speaker' flag is 0, but the Speaker Count is not 0."); + } + + if (rc->has_speaker_location_descriptors) { + add_failure(cta, "Room Configuration Data Block: " + "'Speaker' flag is 0, but there are " + "Speaker Location Descriptors."); + } + } + + parse_speaker_alloc(cta, &rc->speakers, &data[1], "Room Configuration Data Block"); + + rc->max_x = 16; + rc->max_y = 16; + rc->max_z = 8; + rc->display_x = 0.0; + rc->display_y = 1.0; + rc->display_z = 0.0; + + if (size < 7) { + if (has_display_coords) + add_failure(cta, "Room Configuration Data Block: " + "'Display' flag is 1, but the Display and Maximum coordinates are not present."); + return 0; + } + + rc->max_x = data[4]; + rc->max_y = data[5]; + rc->max_z = data[6]; + + if (size < 10) { + if (has_display_coords) + add_failure(cta, "Room Configuration Data Block: " + "'Display' flag is 1, but the Display coordinates are not present."); + return 0; + } + + rc->display_x = decode_coord(data[7]); + rc->display_y = decode_coord(data[8]); + rc->display_z = decode_coord(data[9]); + + return 0; +} + +static int +parse_speaker_location_block(struct di_cta *cta, + struct di_cta_speaker_location_priv *sldb, + const uint8_t *data, size_t size) +{ + struct di_cta_speaker_location_descriptor speaker_loc, *slp; + + if (size < 2) { + add_failure(cta, "Speaker Location Data Block: Empty Data Block with length %u.", + size); + return -EINVAL; + } + + while (size >= 2) { + speaker_loc.has_coords = has_bit(data[0], 6); + speaker_loc.x = speaker_loc.y = speaker_loc.z = 0; + speaker_loc.is_active = has_bit(data[0], 5); + speaker_loc.channel_index = get_bit_range(data[0], 4, 0); + speaker_loc.speaker_id = get_bit_range(data[1], 4, 0); + + if (has_bit(data[0], 7) || get_bit_range(data[1], 7, 5) != 0) { + add_failure(cta, "Speaker Location Data Block: Bits F27-F25, F17 must be 0."); + } + + if (speaker_loc.has_coords && size >= 5) { + speaker_loc.x = decode_coord(data[2]); + speaker_loc.y = decode_coord(data[3]); + speaker_loc.z = decode_coord(data[4]); + size -= 5; + data += 5; + } else if (speaker_loc.has_coords) { + add_failure(cta, "Speaker Location Data Block: COORD bit " + "set but contains no Coordinates."); + return -EINVAL; + } else { + size -= 2; + data += 2; + } + + slp = calloc(1, sizeof(*slp)); + if (!slp) + return -errno; + + *slp = speaker_loc; + assert(sldb->locations_len < EDID_CTA_MAX_SPEAKER_LOCATION_BLOCK_ENTRIES); + sldb->locations[sldb->locations_len++] = slp; + } + + sldb->base.locations = + (const struct di_cta_speaker_location_descriptor *const *)sldb->locations; + return 0; +} + +static int +parse_did_type_vii_timing(struct di_cta *cta, + struct di_cta_type_vii_timing_priv *t, + const uint8_t *data, size_t size) +{ + uint8_t revision; + + if (size != 21) { + add_failure(cta, "DisplayID Type VII Video Timing Data Block: " + "Empty Data Block with length %u.", size); + return -EINVAL; + } + + if (get_bit_range(data[0], 6, 4) != 0) { + add_failure(cta, "DisplayID Type VII Video Timing Data Block: " + "T7_M shall be 000b."); + return -EINVAL; + } + + revision = get_bit_range(data[0], 2, 0); + if (revision != 2) { + add_failure(cta, "DisplayID Type VII Video Timing Data Block: " + "Unexpected revision (%u != %u).", + revision, 2); + return -EINVAL; + } + + if (has_bit(data[0], 3)) { + add_failure(cta, "DisplayID Type VII Video Timing Data Block: " + "DSC_PT shall be 0."); + } + if (has_bit(data[0], 7)) { + add_failure(cta, "DisplayID Type VII Video Timing Data Block: " + "Block Revision and Other Data Bit 7 must be 0."); + } + + data += 1; + size -= 1; + + _di_displayid_parse_type_1_7_timing(&t->timing, cta->logger, + "DisplayID Type VII Video Timing Data Block", + data, true); + + t->base.timing = &t->timing; + return 0; +} + +static int +peak_lum_index_to_nits(int index) +{ + switch (index) { + case 1: + return 200; + case 2: + return 300; + case 3: + return 400; + case 4: + return 500; + case 5: + return 600; + case 6: + return 800; + case 7: + return 1000; + case 8: + return 1200; + case 9: + return 1500; + case 10: + return 2000; + case 11: + return 2500; + case 12: + return 3000; + case 13: + return 4000; + case 14: + return 6000; + case 15: + return 8000; + } + + return 0; +} + +static int +ff_peak_lum_index_to_nits(int index, int peak_lum) +{ + float mult; + + /** + * Full Frame Peak Luminance index maps to a certain percentage of the + * Peak Luminance. + */ + + if (peak_lum == 0) + return 0; + + switch (index) { + case 0: + mult = 0.1f; + break; + case 1: + mult = 0.2f; + break; + case 2: + mult = 0.4f; + break; + case 3: + mult = 0.8f; + break; + default: + mult = 0.0f; + break; + } + + return (int)roundf((float)peak_lum * mult); +} + +static int +parse_hdr10plus_block(struct di_cta *cta, + struct di_cta_hdr10plus_block *block, + const uint8_t *data, size_t size) +{ + int peak_lum_index, ff_peak_lum_index; + + if (size < 1) { + add_failure(cta, "Vendor-Specific Video Data Block (HDR10+), OUI 90-84-8B: " + "Empty Data Block with length %u.", + size); + return -EINVAL; + } + + block->version = get_bit_range(data[0], 1, 0); + if (block->version != 1) { + add_failure(cta, "Vendor-Specific Video Data Block (HDR10+), OUI 90-84-8B: " + "We were expecting application version 1, but got %d.", + block->version); + return -EINVAL; + } + + /* Index 0 is reserved and > 15 invalid (but 4 bits goes up to 15). */ + peak_lum_index = get_bit_range(data[0], 7, 4); + if (peak_lum_index == 0) { + add_failure(cta, "Vendor-Specific Video Data Block (HDR10+), OUI 90-84-8B: " + "Peak luminance index 0 is reserved."); + } + block->peak_lum = peak_lum_index_to_nits(peak_lum_index); + + /* Index > 3 is invalid, but 2 bits goes up to 3 so no need to check. */ + ff_peak_lum_index = get_bit_range(data[0], 3, 2); + block->ff_peak_lum = ff_peak_lum_index_to_nits(ff_peak_lum_index, + block->peak_lum); + + return 0; +} + +static int +parse_dolby_video_block(struct di_cta *cta, + struct di_cta_dolby_video_block_priv *dv_priv, + const uint8_t *data, size_t size) +{ + struct di_cta_dolby_video_block *dv = &dv_priv->base; + struct di_cta_dolby_video_block_v0 *v0 = &dv_priv->v0; + struct di_cta_dolby_video_block_v1 *v1 = &dv_priv->v1; + struct di_cta_dolby_video_block_v2 *v2 = &dv_priv->v2; + int version; + + if (size < 1) { + add_failure(cta, "Vendor-Specific Video Data Block (Dolby), OUI 00-D0-46: " + "Empty Data Block with length %u.", + size); + return -EINVAL; + } + version = get_bit_range(data[0], 7, 5); + + if (version == 0) { + dv->version = DI_CTA_DOLBY_VIDEO_VERSION0; + dv->v0 = v0; + + if (size < 17) { + add_failure(cta, "Vendor-Specific Video Data Block (Dolby), OUI 00-D0-46: " + "Expected length of 17 for Version 0, but got length %u.", + size); + return -EINVAL; + } + + v0->global_dimming = has_bit(data[0], 2); + v0->supports_2160p60 = has_bit(data[0], 1); + v0->yuv422_12bit = has_bit(data[0], 0); + + /* TODO unused: get_bit_range(data[0], 4, 3) */ + + v0->dynamic_metadata_version_major = get_bit_range(data[16], 7, 4); + v0->dynamic_metadata_version_minor = get_bit_range(data[16], 3, 0); + + v0->target_pq_12b_level_min = data[14] << 4 | get_bit_range(data[13], 7, 4); + v0->target_pq_12b_level_max = data[15] << 4 | get_bit_range(data[13], 3, 0); + + v0->red_x = ((data[2] << 4) | get_bit_range(data[1], 7, 4)) / 4096.0; + v0->red_y = ((data[3] << 4) | get_bit_range(data[1], 3, 0)) / 4096.0; + v0->green_x = ((data[5] << 4) | get_bit_range(data[4], 7, 4)) / 4096.0; + v0->green_y = ((data[6] << 4) | get_bit_range(data[4], 3, 0)) / 4096.0; + v0->blue_x = ((data[8] << 4) | get_bit_range(data[7], 7, 4)) / 4096.0; + v0->blue_y = ((data[9] << 4) | get_bit_range(data[7], 3, 0)) / 4096.0; + v0->white_x = ((data[11] << 4) | get_bit_range(data[10], 7, 4)) / 4096.0; + v0->white_y = ((data[12] << 4) | get_bit_range(data[10], 3, 0)) / 4096.0; + } else if (version == 1) { + double lm; + double xmin, ymin; + double xstep, ystep; + int steps; + + dv->version = DI_CTA_DOLBY_VIDEO_VERSION1; + dv->v1 = v1; + + if (size < 7) { + add_failure(cta, "Vendor-Specific Video Data Block (Dolby), OUI 00-D0-46: " + "Expected length of at least 7 for Version 1, " + "but got length %u.", + size); + return -EINVAL; + } + + v1->dynamic_metadata_version = get_bit_range(data[0], 4, 2) + 2; + v1->supports_2160p60 = has_bit(data[0], 1); + v1->yuv422_12bit = has_bit(data[0], 0); + v1->global_dimming = has_bit(data[1], 0); + + v1->colorimetry = has_bit(data[2], 0) ? + DI_CTA_DOLBY_VIDEO_COLORIMETRY_P3_D65 : + DI_CTA_DOLBY_VIDEO_COLORIMETRY_BT_709; + + if (has_bit(data[3], 0)) + v1->mode_low_latency = true; + + lm = get_bit_range(data[2], 7, 1) / 127.0; + v1->target_luminance_min = lm * lm; + + lm = (get_bit_range(data[1], 7, 1) * 50.0) + 100; + v1->target_luminance_max = lm; + + if (size >= 10) { + v1->unique_primaries = false; + v1->red_x = data[4] / 256.0; + v1->red_y = data[5] / 256.0; + v1->green_x = data[6] / 256.0; + v1->green_y = data[7] / 256.0; + v1->blue_x = data[8] / 256.0; + v1->blue_y = data[9] / 256.0; + + /* TODO unused: get_bit_range(data[3], 7, 1) */ + } else { + v1->unique_primaries = true; + xmin = 0.625; + xstep = (0.74609375 - xmin) / 31.0; + v1->red_x = xmin + xstep * (data[6] >> 3); + + ymin = 0.25; + ystep = (0.37109375 - ymin) / 31.0; + steps = (get_bit_range(data[6], 2, 0) << 2) | + (get_bit_range(data[5], 0, 0) << 1) | + get_bit_range(data[4], 0, 0); + v1->red_y = ymin + ystep * steps; + + xstep = 0.49609375 / 127.0; + v1->green_x = xstep * get_bit_range(data[4], 7, 1); + + ymin = 0.5; + ystep = (0.99609375 - ymin) / 127.0; + v1->green_y = ymin + ystep * get_bit_range(data[5], 7, 1); + + xmin = 0.125; + xstep = (0.15234375 - xmin) / 7.0; + v1->blue_x = xmin + xstep * get_bit_range(data[3], 7, 5); + + ymin = 0.03125; + ystep = (0.05859375 - ymin) / 7.0; + v1->blue_y = ymin + ystep * get_bit_range(data[3], 4, 2); + + /* TODO unused: get_bit_range(data[3], 1, 1) */ + } + } else if (version == 2) { + dv->version = DI_CTA_DOLBY_VIDEO_VERSION2; + dv->v2 = v2; + + if (size < 7) { + add_failure(cta, "Vendor-Specific Video Data Block (Dolby), OUI 00-D0-46: " + "Expected length of at least 7 for Version 2, " + "but got length %u.", + size); + return -EINVAL; + } + + v2->dynamic_metadata_version = get_bit_range(data[0], 4, 2) + 2; + v2->backlight_control = has_bit(data[0], 1); + v2->yuv422_12bit = has_bit(data[0], 0); + v2->global_dimming = has_bit(data[1], 2); + + v2->backlight_luminance_min = 25 + get_bit_range(data[1], 1, 0) * 25; + + switch (get_bit_range(data[2], 1, 0)) { + case 0: + break; + case 1: + v2->mode_low_latency_hdmi = true; + break; + case 2: + v2->mode_standard = true; + break; + case 3: + v2->mode_standard = true; + v2->mode_low_latency_hdmi = true; + break; + default: + abort(); /* unreachable */ + } + + v2->yuv444 = (uint8_t)(get_bit_range(data[3], 0, 0) << 1) | + get_bit_range(data[4], 0, 0); + switch (v2->yuv444) { + case DI_CTA_DOLBY_VIDEO_YUV444_NONE: + case DI_CTA_DOLBY_VIDEO_YUV444_10_BITS: + case DI_CTA_DOLBY_VIDEO_YUV444_12_BITS: + break; + default: + add_failure(cta, "Vendor-Specific Video Data Block (Dolby), OUI 00-D0-46: " + "Reserved YUV444 mode 0x%02x.", + v2->yuv444); + return -EINVAL; + } + + v2->target_pq_12b_level_min = 20 * get_bit_range(data[1], 7, 3); + v2->target_pq_12b_level_max = 2055 + 65 * get_bit_range(data[2], 7, 3); + + v2->red_x = 0.625 + get_bit_range(data[5], 7, 3) / 256.0; + v2->red_y = 0.25 + get_bit_range(data[6], 7, 3) / 256.0; + + v2->green_x = get_bit_range(data[3], 7, 1) / 256.0; + v2->green_y = 0.5 + get_bit_range(data[4], 7, 1) / 256.0; + + v2->blue_x = 0.125 + get_bit_range(data[5], 2, 0) / 256.0; + v2->blue_y = 0.03125 + get_bit_range(data[6], 2, 0) / 256.0; + } + + return 0; +} + +void +_di_cta_data_block_destroy(struct di_cta_data_block *data_block) +{ + size_t i; + struct di_cta_video_block_priv *video; + struct di_cta_audio_block_priv *audio; + struct di_cta_infoframe_block_priv *infoframe; + struct di_cta_speaker_location_priv *speaker_location; + struct di_cta_video_format_pref_priv *vfpdb; + struct di_cta_hdmi_audio_block_priv *hdmi_audio; + struct di_cta_vendor_hdmi_block_priv *vendor_hdmi; + struct di_cta_ycbcr420_video_block_priv *ycbcr420; + + switch (data_block->tag) { + case DI_CTA_DATA_BLOCK_VIDEO: + video = &data_block->video; + for (i = 0; i < video->svds_len; i++) + free(video->svds[i]); + break; + case DI_CTA_DATA_BLOCK_VENDOR_HDMI: + vendor_hdmi = &data_block->vendor_hdmi; + free(vendor_hdmi->vics); + break; + case DI_CTA_DATA_BLOCK_YCBCR420: + ycbcr420 = &data_block->ycbcr420; + for (i = 0; i < ycbcr420->svds_len; i++) + free(ycbcr420->svds[i]); + break; + case DI_CTA_DATA_BLOCK_AUDIO: + audio = &data_block->audio; + for (i = 0; i < audio->sads_len; i++) + free(audio->sads[i]); + break; + case DI_CTA_DATA_BLOCK_INFOFRAME: + infoframe = &data_block->infoframe; + for (i = 0; i < infoframe->infoframes_len; i++) + free(infoframe->infoframes[i]); + break; + case DI_CTA_DATA_BLOCK_SPEAKER_LOCATION: + speaker_location = &data_block->speaker_location; + for (i = 0; i < speaker_location->locations_len; i++) + free(speaker_location->locations[i]); + break; + case DI_CTA_DATA_BLOCK_VIDEO_FORMAT_PREF: + vfpdb = &data_block->video_format_pref; + for (i = 0; i < vfpdb->svrs_len; i++) + free(vfpdb->svrs[i]); + break; + case DI_CTA_DATA_BLOCK_HDMI_AUDIO: + hdmi_audio = &data_block->hdmi_audio; + for (i = 0; i < hdmi_audio->sads_len; i++) + free(hdmi_audio->sads[i]); + break; + default: + /* Nothing to do. Blocks that we failed to fully initialize may + * not contain a tag (zeroed), and these should only need the + * base block destruction. And some types of data blocks simply + * don't have specific data to be released. */ + break; + } + + free(data_block); +} + +static int +parse_vendor_specific_video_block(struct di_cta *cta, + enum di_cta_data_block_tag *tag, + struct di_cta_data_block *data_block, + const uint8_t *data, size_t size) +{ + uint32_t oui; + + if (size < 3) { + add_failure(cta, + "Vendor-Specific Video Data Block: Empty Data Block with length %u.", + size); + return -EINVAL; + } + + oui = ((uint32_t)data[2] << 16) | ((uint32_t)data[1] << 8) | data[0]; + + data += 3; + size -= 3; + + switch (oui) { + case IEEE_OUI_DOLBY: + *tag = DI_CTA_DATA_BLOCK_DOLBY_VIDEO; + return parse_dolby_video_block(cta, &data_block->dolby_video, + data, size); + case IEEE_OUI_HDR10PLUS: + *tag = DI_CTA_DATA_BLOCK_HDR10PLUS; + return parse_hdr10plus_block(cta, &data_block->hdr10plus, + data, size); + default: + return -EINVAL; + } +} + +static int +parse_vendor_specific_block(struct di_cta *cta, + enum di_cta_data_block_tag *tag, + struct di_cta_data_block *data_block, + const uint8_t *data, size_t size) +{ + uint32_t oui; + + if (size < 3) { + add_failure(cta, + "Vendor-Specific Data Block: Empty Data Block with length (%u).", + size); + return -EINVAL; + } + + oui = ((uint32_t)data[2] << 16) | ((uint32_t)data[1] << 8) | data[0]; + + switch (oui) { + case IEEE_OUI_HDMI: + *tag = DI_CTA_DATA_BLOCK_VENDOR_HDMI; + return parse_vendor_hdmi_block(cta, &data_block->vendor_hdmi, + data, size); + case IEEE_OUI_HDMI_FORUM: + *tag = DI_CTA_DATA_BLOCK_VENDOR_HDMI_FORUM; + return parse_vendor_hdmi_forum_block(cta, &data_block->vendor_hdmi_forum, + data, size); + default: + return -EINVAL; + } +} + +bool +_di_cta_data_block_parse(struct di_cta *cta, uint8_t raw_tag, const uint8_t *data, size_t size, + struct di_cta_data_block **data_block_out) +{ + uint8_t extended_tag; + struct di_cta_data_block *data_block; + int ret; + + *data_block_out = NULL; + + data_block = calloc(1, sizeof(*data_block)); + if (!data_block) { + return false; + } + + ret = 0; + switch (raw_tag) { + case 1: + data_block->tag = DI_CTA_DATA_BLOCK_AUDIO; + ret = parse_audio_block(cta, &data_block->audio, data, size); + break; + case 2: + data_block->tag = DI_CTA_DATA_BLOCK_VIDEO; + ret = parse_video_block(cta, &data_block->video, data, size); + break; + case 3: /* Vendor-Specific Data Block */ + ret = parse_vendor_specific_block(cta, &data_block->tag, data_block, data, size); + break; + case 4: + data_block->tag = DI_CTA_DATA_BLOCK_SPEAKER_ALLOC; + ret = parse_speaker_alloc_block(cta, &data_block->speaker_alloc, + data, size); + break; + case 5: + data_block->tag = DI_CTA_DATA_BLOCK_VESA_DISPLAY_TRANSFER_CHARACTERISTIC; + ret = parse_vesa_transfer_characteristics_block(cta, + &data_block->vesa_transfer_characteristics, + data, size); + break; + case 6: + data_block->tag = DI_CTA_DATA_BLOCK_VIDEO_FORMAT; + break; + case 7: + /* Use Extended Tag */ + if (size < 1) { + add_failure(cta, "Empty block with extended tag."); + ret = -EINVAL; + break; + } + + extended_tag = data[0]; + data = &data[1]; + size--; + + switch (extended_tag) { + case 0: + data_block->tag = DI_CTA_DATA_BLOCK_VIDEO_CAP; + ret = parse_video_cap_block(cta, &data_block->video_cap, + data, size); + break; + case 2: + data_block->tag = DI_CTA_DATA_BLOCK_VESA_DISPLAY_DEVICE; + ret = parse_vesa_display_device(cta, &data_block->vesa_display_device, + data, size); + break; + case 5: + data_block->tag = DI_CTA_DATA_BLOCK_COLORIMETRY; + ret = parse_colorimetry_block(cta, + &data_block->colorimetry, + data, size); + break; + case 6: + data_block->tag = DI_CTA_DATA_BLOCK_HDR_STATIC_METADATA; + ret = parse_hdr_static_metadata_block(cta, + &data_block->hdr_static_metadata, + data, size); + break; + case 7: + data_block->tag = DI_CTA_DATA_BLOCK_HDR_DYNAMIC_METADATA; + ret = parse_hdr_dynamic_metadata_block(cta, + &data_block->hdr_dynamic_metadata, + data, size); + break; + case 8: + data_block->tag = DI_CTA_DATA_BLOCK_NATIVE_VIDEO_RESOLUTION; + break; + case 13: + data_block->tag = DI_CTA_DATA_BLOCK_VIDEO_FORMAT_PREF; + ret = parse_video_format_pref_block(cta, + &data_block->video_format_pref, + data, size); + break; + case 14: + data_block->tag = DI_CTA_DATA_BLOCK_YCBCR420; + ret = parse_ycbcr420_block(cta, + &data_block->ycbcr420, + data, size); + break; + case 15: + data_block->tag = DI_CTA_DATA_BLOCK_YCBCR420_CAP_MAP; + parse_ycbcr420_cap_map(cta, + &data_block->ycbcr420_cap_map, + data, size); + break; + case 18: + data_block->tag = DI_CTA_DATA_BLOCK_HDMI_AUDIO; + ret = parse_hdmi_audio_block(cta, + &data_block->hdmi_audio, + data, size); + break; + case 19: + data_block->tag = DI_CTA_DATA_BLOCK_ROOM_CONFIG; + ret = parse_room_config_block(cta, + &data_block->room_config, + data, size); + break; + case 20: + data_block->tag = DI_CTA_DATA_BLOCK_SPEAKER_LOCATION; + ret = parse_speaker_location_block(cta, + &data_block->speaker_location, + data, size); + break; + case 32: + data_block->tag = DI_CTA_DATA_BLOCK_INFOFRAME; + ret = parse_infoframe_block(cta, + &data_block->infoframe, + data, size); + break; + case 34: + data_block->tag = DI_CTA_DATA_BLOCK_DISPLAYID_VIDEO_TIMING_VII; + ret = parse_did_type_vii_timing(cta, + &data_block->did_vii_timing, + data, size); + break; + case 35: + data_block->tag = DI_CTA_DATA_BLOCK_DISPLAYID_VIDEO_TIMING_VIII; + break; + case 42: + data_block->tag = DI_CTA_DATA_BLOCK_DISPLAYID_VIDEO_TIMING_X; + break; + case 120: + data_block->tag = DI_CTA_DATA_BLOCK_HDMI_EDID_EXT_OVERRIDE; + break; + case 121: + data_block->tag = DI_CTA_DATA_BLOCK_HDMI_SINK_CAP; + /** + * This expects data to include the extended tag. Let's + * get back one byte. + */ + data -= sizeof(data[0]); + size++; + ret = parse_hdmi_forum_sink_cap(cta, &data_block->hdmi_sink_cap, + data, size); + break; + case 1: /* Vendor-Specific Video Data Block */ + ret = parse_vendor_specific_video_block(cta, &data_block->tag, + data_block, + data, size); + break; + case 17: /* Vendor-Specific Audio Data Block */ + ret = -EINVAL; + break; + default: + /* Reserved */ + add_failure_until(cta, 3, + "Unknown CTA-861 Data Block (extended tag 0x"PRIx8", length %zu).", + extended_tag, size); + ret = -EINVAL; + break; + } + break; + default: + /* Reserved */ + add_failure_until(cta, 3, "Unknown CTA-861 Data Block (tag 0x"PRIx8", length %zu).", + raw_tag, size); + ret = -EINVAL; + break; + } + if (ret != 0) { + _di_cta_data_block_destroy(data_block); + return ret == -EINVAL || ret == -ENOTSUP; + } + + *data_block_out = data_block; + return true; +} + +bool +_di_edid_cta_parse(struct di_edid_cta *edid_cta, const uint8_t *data, size_t size, + struct di_logger *logger) +{ + struct di_cta *cta; + uint8_t flags, dtd_start; + uint8_t data_block_header, data_block_tag, data_block_size; + size_t i; + struct di_edid_detailed_timing_def_priv *detailed_timing_def; + struct di_cta_data_block *data_block; + + assert(size == 128); + assert(data[0] == 0x02); + + edid_cta->revision = data[1]; + dtd_start = data[2]; + + cta = &(struct di_cta){ + .revision = edid_cta->revision, + .flags = &edid_cta->flags, + .logger = logger, + }; + + flags = data[3]; + if (cta->revision >= 2) { + edid_cta->flags.it_underscan = has_bit(flags, 7); + edid_cta->flags.basic_audio = has_bit(flags, 6); + edid_cta->flags.ycc444 = has_bit(flags, 5); + edid_cta->flags.ycc422 = has_bit(flags, 4); + edid_cta->flags.native_dtds = get_bit_range(flags, 3, 0); + } else if (flags != 0) { + /* Reserved */ + add_failure(cta, "Non-zero byte 3."); + } + + if (dtd_start == 0) { + return true; + } else if (dtd_start < CTA_HEADER_SIZE || dtd_start >= size) { + errno = EINVAL; + return false; + } + + i = CTA_HEADER_SIZE; + while (i < dtd_start) { + data_block_header = data[i]; + data_block_tag = get_bit_range(data_block_header, 7, 5); + data_block_size = get_bit_range(data_block_header, 4, 0); + + if (i + 1 + data_block_size > dtd_start) { + data_block_size = (uint8_t) (dtd_start - i - 1); + if (data_block_size == 0) { + add_failure(cta, "Data Block at offset %zu overlaps Detailed Timing " + "Definitions. No room for other blocks, skipping " + "all further Data Blocks.", i); + break; + } + add_failure(cta, "Data Block at offset %zu overlaps Detailed Timing " + "Definitions. Adjusted its size to attempt parsing.", i); + } + + if (!_di_cta_data_block_parse(cta, data_block_tag, + &data[i + 1], data_block_size, &data_block)) { + _di_edid_cta_finish(edid_cta); + return false; + } + + if (data_block != NULL) { + assert(edid_cta->data_blocks_len < EDID_CTA_MAX_DATA_BLOCKS); + edid_cta->data_blocks[edid_cta->data_blocks_len++] = data_block; + } + + i += 1 + data_block_size; + } + + if (i != dtd_start) + add_failure(cta, "Offset is %"PRIu8", but should be %zu.", + dtd_start, i); + + for (i = dtd_start; i + EDID_BYTE_DESCRIPTOR_SIZE <= CTA_DTD_END; + i += EDID_BYTE_DESCRIPTOR_SIZE) { + /* First two bytes being zeroed is reserved by spec. */ + if (data[i] == 0 && data[i + 1] == 0) + break; + + detailed_timing_def = _di_edid_parse_detailed_timing_def(&data[i]); + if (!detailed_timing_def) { + _di_edid_cta_finish(edid_cta); + return false; + } + assert(edid_cta->detailed_timing_defs_len < EDID_CTA_MAX_DETAILED_TIMING_DEFS); + edid_cta->detailed_timing_defs[edid_cta->detailed_timing_defs_len++] = detailed_timing_def; + } + + /* All padding bytes after the last DTD must be zero */ + while (i < CTA_DTD_END) { + if (data[i] != 0) { + add_failure(cta, "Padding: Contains non-zero bytes."); + break; + } + i++; + } + + return true; +} + +void +_di_edid_cta_finish(struct di_edid_cta *cta) +{ + size_t i; + + for (i = 0; i < cta->data_blocks_len; i++) { + _di_cta_data_block_destroy(cta->data_blocks[i]); + } + + for (i = 0; i < cta->detailed_timing_defs_len; i++) { + free(cta->detailed_timing_defs[i]); + } +} + +int +di_edid_cta_get_revision(const struct di_edid_cta *cta) +{ + return cta->revision; +} + +const struct di_edid_cta_flags * +di_edid_cta_get_flags(const struct di_edid_cta *cta) +{ + return &cta->flags; +} + +const struct di_cta_data_block *const * +di_edid_cta_get_data_blocks(const struct di_edid_cta *cta) +{ + return (const struct di_cta_data_block *const *) cta->data_blocks; +} + +enum di_cta_data_block_tag +di_cta_data_block_get_tag(const struct di_cta_data_block *block) +{ + return block->tag; +} + +const struct di_cta_video_block * +di_cta_data_block_get_video(const struct di_cta_data_block *block) +{ + if (block->tag != DI_CTA_DATA_BLOCK_VIDEO) { + return NULL; + } + return &block->video.base; +} + +const struct di_cta_ycbcr420_video_block * +di_cta_data_block_get_ycbcr420_video(const struct di_cta_data_block *block) +{ + if (block->tag != DI_CTA_DATA_BLOCK_YCBCR420) { + return NULL; + } + return &block->ycbcr420.base; +} + +const struct di_cta_video_format_pref_block * +di_cta_data_block_get_video_format_pref(const struct di_cta_data_block *block) +{ + if (block->tag != DI_CTA_DATA_BLOCK_VIDEO_FORMAT_PREF) { + return NULL; + } + return &block->video_format_pref.base; +} + +const struct di_cta_audio_block * +di_cta_data_block_get_audio(const struct di_cta_data_block *block) +{ + if (block->tag != DI_CTA_DATA_BLOCK_AUDIO) { + return NULL; + } + return &block->audio.audio; +} + +const struct di_cta_speaker_alloc_block * +di_cta_data_block_get_speaker_alloc(const struct di_cta_data_block *block) +{ + if (block->tag != DI_CTA_DATA_BLOCK_SPEAKER_ALLOC) { + return NULL; + } + return &block->speaker_alloc; +} + +const struct di_cta_colorimetry_block * +di_cta_data_block_get_colorimetry(const struct di_cta_data_block *block) +{ + if (block->tag != DI_CTA_DATA_BLOCK_COLORIMETRY) { + return NULL; + } + return &block->colorimetry; +} + +const struct di_cta_hdr_static_metadata_block * +di_cta_data_block_get_hdr_static_metadata(const struct di_cta_data_block *block) +{ + if (block->tag != DI_CTA_DATA_BLOCK_HDR_STATIC_METADATA) { + return NULL; + } + return &block->hdr_static_metadata.base; +} + +const struct di_cta_hdr_dynamic_metadata_block * +di_cta_data_block_get_hdr_dynamic_metadata(const struct di_cta_data_block *block) +{ + if (block->tag != DI_CTA_DATA_BLOCK_HDR_DYNAMIC_METADATA) { + return NULL; + } + return &block->hdr_dynamic_metadata.base; +} + +const struct di_cta_video_cap_block * +di_cta_data_block_get_video_cap(const struct di_cta_data_block *block) +{ + if (block->tag != DI_CTA_DATA_BLOCK_VIDEO_CAP) { + return NULL; + } + return &block->video_cap; +} + +const struct di_cta_vesa_display_device_block * +di_cta_data_block_get_vesa_display_device(const struct di_cta_data_block *block) +{ + if (block->tag != DI_CTA_DATA_BLOCK_VESA_DISPLAY_DEVICE) { + return NULL; + } + return &block->vesa_display_device; +} + +bool +di_cta_ycbcr420_cap_map_supported(const struct di_cta_ycbcr420_cap_map_block *cap_map, + size_t svd_index) +{ + size_t byte, bit; + + if (cap_map->all) + return true; + + byte = svd_index / 8; + bit = svd_index % 8; + + if (byte >= EDID_CTA_MAX_YCBCR420_CAP_MAP_BLOCK_ENTRIES) + return false; + + return cap_map->svd_bitmap[byte] & (1 << bit); +} + +const struct di_cta_ycbcr420_cap_map_block * +di_cta_data_block_get_ycbcr420_cap_map(const struct di_cta_data_block *block) +{ + if (block->tag != DI_CTA_DATA_BLOCK_YCBCR420_CAP_MAP) { + return NULL; + } + return &block->ycbcr420_cap_map; +} + +const struct di_cta_hdmi_audio_block * +di_cta_data_block_get_hdmi_audio(const struct di_cta_data_block *block) +{ + if (block->tag != DI_CTA_DATA_BLOCK_HDMI_AUDIO) { + return NULL; + } + return &block->hdmi_audio.base; +} + +const struct di_cta_infoframe_block * +di_cta_data_block_get_infoframe(const struct di_cta_data_block *block) +{ + if (block->tag != DI_CTA_DATA_BLOCK_INFOFRAME) { + return NULL; + } + return &block->infoframe.base; +} + +const struct di_cta_speaker_location_block * +di_cta_data_block_get_speaker_locations(const struct di_cta_data_block *block) +{ + if (block->tag != DI_CTA_DATA_BLOCK_SPEAKER_LOCATION) { + return NULL; + } + return &block->speaker_location.base; +} + +const struct di_cta_type_vii_timing_block * +di_cta_data_block_get_did_type_vii_timing(const struct di_cta_data_block *block) +{ + if (block->tag != DI_CTA_DATA_BLOCK_DISPLAYID_VIDEO_TIMING_VII) { + return NULL; + } + return &block->did_vii_timing.base; +} + +const struct di_cta_hdr10plus_block * +di_cta_data_block_get_hdr10plus(const struct di_cta_data_block *block) +{ + if (block->tag != DI_CTA_DATA_BLOCK_HDR10PLUS) { + return NULL; + } + return &block->hdr10plus; +} + +const struct di_cta_dolby_video_block * +di_cta_data_block_get_dolby_video(const struct di_cta_data_block *block) +{ + if (block->tag != DI_CTA_DATA_BLOCK_DOLBY_VIDEO) { + return NULL; + } + return &block->dolby_video.base; +} + +const struct di_edid_detailed_timing_def *const * +di_edid_cta_get_detailed_timing_defs(const struct di_edid_cta *cta) +{ + return (const struct di_edid_detailed_timing_def *const *) cta->detailed_timing_defs; +} + +const struct di_cta_vesa_transfer_characteristics_block * +di_cta_data_block_get_vesa_transfer_characteristics(const struct di_cta_data_block *block) +{ + if (block->tag != DI_CTA_DATA_BLOCK_VESA_DISPLAY_TRANSFER_CHARACTERISTIC) { + return NULL; + } + return &block->vesa_transfer_characteristics; +} + +const struct di_cta_room_configuration_block * +di_cta_data_block_get_room_configuration(const struct di_cta_data_block *block) +{ + if (block->tag != DI_CTA_DATA_BLOCK_ROOM_CONFIG) { + return NULL; + } + return &block->room_config; +} + +const struct di_cta_vendor_hdmi_block * +di_cta_data_block_get_vendor_hdmi(const struct di_cta_data_block *block) +{ + if (block->tag != DI_CTA_DATA_BLOCK_VENDOR_HDMI) { + return NULL; + } + return &block->vendor_hdmi.base; +} + +const struct di_cta_hdmi_forum_sink_cap * +di_cta_data_block_get_hdmi_sink_cap(const struct di_cta_data_block *block) +{ + if (block->tag != DI_CTA_DATA_BLOCK_HDMI_SINK_CAP) { + return NULL; + } + return &block->hdmi_sink_cap.base; +} + +const struct di_cta_vendor_hdmi_forum_block * +di_cta_data_block_get_vendor_hdmi_forum(const struct di_cta_data_block *block) +{ + if (block->tag != DI_CTA_DATA_BLOCK_VENDOR_HDMI_FORUM) { + return NULL; + } + return &block->vendor_hdmi_forum.base; +} diff --git a/local/recipes/libs/libdisplay-info/source/cvt.c b/local/recipes/libs/libdisplay-info/source/cvt.c new file mode 100644 index 0000000000..7566c6759b --- /dev/null +++ b/local/recipes/libs/libdisplay-info/source/cvt.c @@ -0,0 +1,155 @@ +/* + * Copyright 2006-2012 Red Hat, Inc. + * Copyright 2018-2021 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + * + * Originally imported from edid-decode. + */ + +#include + +#include + +/** + * The size of the top and bottom overscan margin as a percentage of the active + * vertical image. + */ +#define MARGIN_PERC 1.8 +#define MIN_VSYNC_BP 550.0 +#define MIN_V_PORCH 3 +/** + * Minimum vertical backporch for CVT and CVT RBv1. + */ +#define MIN_V_BPORCH 7 +/** + * Fixed vertical backporch for CVT RBv2 and RBv3. + */ +#define FIXED_V_BPORCH 6 +#define C_PRIME 30.0 +#define M_PRIME 300.0 +/** + * Minimum VBlank period (in µs) for RB timings. + */ +#define RB_MIN_VBLANK 460.0 + +void +di_cvt_compute(struct di_cvt_timing *t, const struct di_cvt_options *options) +{ + enum di_cvt_reduced_blanking_version rb = options->red_blank_ver; + double cell_gran, h_pixels_rnd, v_lines_rnd, hor_margin, vert_margin, + interlace, total_active_pixels, v_field_rate_rqd, clock_step, + h_blank, rb_v_fporch, refresh_multiplier, rb_min_vblank, h_sync, + v_sync, pixel_freq, v_blank, v_sync_bp, additional_hblank, + h_period_est, ideal_duty_cycle, total_pixels, vbi_lines, + rb_v_bporch, rb_min_vbi, total_v_lines, freq, h_front_porch, + v_back_porch, act_h_freq; + + cell_gran = rb == DI_CVT_REDUCED_BLANKING_V2 ? 1 : 8; + h_pixels_rnd = floor(options->h_pixels / cell_gran) * cell_gran; + v_lines_rnd = options->int_rqd ? floor(options->v_lines / 2.0) : options->v_lines; + hor_margin = options->margins_rqd + ? floor((h_pixels_rnd * MARGIN_PERC / 100.0) / cell_gran) * cell_gran + : 0; + vert_margin = options->margins_rqd + ? floor(MARGIN_PERC / 100.0 * v_lines_rnd) + : 0; + interlace = options->int_rqd ? 0.5 : 0; + total_active_pixels = h_pixels_rnd + hor_margin * 2; + v_field_rate_rqd = options->int_rqd + ? options->ip_freq_rqd * 2 + : options->ip_freq_rqd; + clock_step = rb >= DI_CVT_REDUCED_BLANKING_V2 ? 0.001 : 0.25; + h_blank = rb == DI_CVT_REDUCED_BLANKING_V1 ? 160 : 80; + rb_v_fporch = rb == DI_CVT_REDUCED_BLANKING_V1 ? 3 : 1; + refresh_multiplier = (rb == DI_CVT_REDUCED_BLANKING_V2 && options->video_opt) ? 1000.0 / 1001.0 : 1; + rb_min_vblank = rb == DI_CVT_REDUCED_BLANKING_V3 ? options->vblank : RB_MIN_VBLANK; + if (rb_min_vblank < RB_MIN_VBLANK) + rb_min_vblank = RB_MIN_VBLANK; + h_sync = 32; + + if (rb == DI_CVT_REDUCED_BLANKING_V3) { + additional_hblank = options->additional_hblank; + if (additional_hblank < 0) + additional_hblank = 0; + else if (additional_hblank > 120) + additional_hblank = 120; + h_blank += additional_hblank; + } + + /* Determine VSync Width from aspect ratio */ + if ((options->v_lines * 4 / 3) == options->h_pixels) + v_sync = 4; + else if ((options->v_lines * 16 / 9) == options->h_pixels) + v_sync = 5; + else if ((options->v_lines * 16 / 10) == options->h_pixels) + v_sync = 6; + else if (!(options->v_lines % 4) && ((options->v_lines * 5 / 4) == options->h_pixels)) + v_sync = 7; + else if ((options->v_lines * 15 / 9) == options->h_pixels) + v_sync = 7; + else /* Custom */ + v_sync = 10; + + if (rb >= DI_CVT_REDUCED_BLANKING_V2) + v_sync = 8; + + if (rb == DI_CVT_REDUCED_BLANKING_NONE) { + h_period_est = (1.0 / v_field_rate_rqd - MIN_VSYNC_BP / 1000000.0) / + (v_lines_rnd + vert_margin * 2 + MIN_V_PORCH + interlace) * 1000000.0; + v_sync_bp = floor(MIN_VSYNC_BP / h_period_est) + 1; + if (v_sync_bp < v_sync + MIN_V_BPORCH) + v_sync_bp = v_sync + MIN_V_BPORCH; + v_blank = v_sync_bp + MIN_V_PORCH; + total_v_lines = v_lines_rnd + vert_margin * 2 + v_sync_bp + + interlace + MIN_V_PORCH; + ideal_duty_cycle = C_PRIME - 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; + 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 { + h_period_est = (1000000.0 / v_field_rate_rqd - rb_min_vblank) / + (v_lines_rnd + vert_margin * 2); + vbi_lines = floor(rb_min_vblank / h_period_est) + 1; + rb_v_bporch = rb == DI_CVT_REDUCED_BLANKING_V1 ? MIN_V_BPORCH : FIXED_V_BPORCH; + rb_min_vbi = rb_v_fporch + v_sync + rb_v_bporch; + v_blank = vbi_lines < rb_min_vbi ? rb_min_vbi : vbi_lines; + total_v_lines = v_blank + v_lines_rnd + vert_margin * 2 + interlace; + if (rb == DI_CVT_REDUCED_BLANKING_V3 && options->early_vsync_rqd) + rb_v_bporch = floor(vbi_lines / 2.0); + if (rb == DI_CVT_REDUCED_BLANKING_V1) + v_sync_bp = v_blank - rb_v_fporch; + else + v_sync_bp = v_sync + rb_v_bporch; + total_pixels = h_blank + total_active_pixels; + freq = v_field_rate_rqd * total_v_lines * total_pixels * refresh_multiplier; + if (rb == DI_CVT_REDUCED_BLANKING_V3) + pixel_freq = ceil(freq / 1000000.0 / clock_step) * clock_step; + else + pixel_freq = floor(freq / 1000000.0 / clock_step) * clock_step; + } + + if (rb >= DI_CVT_REDUCED_BLANKING_V2) + h_front_porch = 8; + else + h_front_porch = (h_blank / 2.0) - h_sync; + + v_back_porch = v_sync_bp - v_sync; + act_h_freq = 1000 * pixel_freq / total_pixels; + + *t = (struct di_cvt_timing){ + .act_pixel_freq = pixel_freq, + .total_active_pixels = total_active_pixels, + .v_lines_rnd = v_lines_rnd, + .h_front_porch = h_front_porch, + .h_sync = h_sync, + .h_back_porch = h_blank - h_front_porch - h_sync, + .v_front_porch = v_blank - v_back_porch - v_sync, + .v_back_porch = v_back_porch, + .v_sync = v_sync, + .act_frame_rate = 1000 * act_h_freq / total_v_lines, + }; +} diff --git a/local/recipes/libs/libdisplay-info/source/di.c b/local/recipes/libs/libdisplay-info/source/di.c deleted file mode 100644 index d8f04573ae..0000000000 --- a/local/recipes/libs/libdisplay-info/source/di.c +++ /dev/null @@ -1,401 +0,0 @@ -#include -#include -#include -#include - -#include "include/libdisplay-info/info.h" - -#define EDID_BLOCK_SIZE 128 -#define EDID_DESCRIPTOR_COUNT 4 -#define EDID_DESCRIPTOR_OFFSET 54 - -struct di_edid { - struct di_edid_vendor_product vendor_product; - struct di_edid_chromaticity_coords chromaticity; - struct di_edid_screen_size screen_size; - struct di_edid_misc_features misc_features; - bool has_chromaticity; - struct di_edid_detailed_timing_def *detailed_timing_storage; - const struct di_edid_detailed_timing_def **detailed_timings; - const struct di_edid_ext **extensions; -}; - -struct di_info { - struct di_edid edid; - char *model; - char *serial; -}; - -static const uint8_t edid_header[8] = { 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00 }; -static const struct di_edid_detailed_timing_def *empty_detailed_timings[] = { NULL }; -static const struct di_edid_ext *empty_edid_exts[] = { NULL }; -static const struct di_cta_data_block *empty_cta_blocks[] = { NULL }; -static const struct di_displayid_data_block *empty_displayid_blocks[] = { NULL }; -static const struct di_displayid_type_i_ii_vii_timing *empty_timings[] = { NULL }; -static const struct di_edid_screen_size empty_screen_size = { 0, 0 }; - -static char *dup_string(const char *value) -{ - size_t len; - char *copy; - - if (!value) { - value = ""; - } - - len = strlen(value) + 1; - copy = malloc(len); - if (!copy) { - return NULL; - } - - memcpy(copy, value, len); - return copy; -} - -static bool has_nonzero_bytes(const uint8_t *data, size_t len) -{ - size_t i; - - for (i = 0; i < len; i++) { - if (data[i] != 0) { - return true; - } - } - - return false; -} - -static bool validate_edid_block(const uint8_t *block) -{ - uint8_t checksum = 0; - size_t i; - - for (i = 0; i < EDID_BLOCK_SIZE; i++) { - checksum = (uint8_t)(checksum + block[i]); - } - - return checksum == 0; -} - -static uint16_t read_le_u16(const uint8_t *data) -{ - return (uint16_t)data[0] | ((uint16_t)data[1] << 8); -} - -static uint32_t read_le_u32(const uint8_t *data) -{ - return (uint32_t)data[0] | ((uint32_t)data[1] << 8) | ((uint32_t)data[2] << 16) | ((uint32_t)data[3] << 24); -} - -static float decode_chromaticity_value(uint8_t high_bits, uint8_t low_bits) -{ - return (float)(((unsigned int)high_bits << 2) | low_bits) / 1024.0f; -} - -static void decode_manufacturer_id(const uint8_t *base, char manufacturer[4]) -{ - manufacturer[0] = (char)('A' + ((base[8] >> 2) & 0x1f) - 1); - manufacturer[1] = (char)('A' + (((base[8] & 0x03) << 3) | ((base[9] >> 5) & 0x07)) - 1); - manufacturer[2] = (char)('A' + (base[9] & 0x1f) - 1); - manufacturer[3] = '\0'; - - if (!isupper((unsigned char)manufacturer[0]) || !isupper((unsigned char)manufacturer[1]) || !isupper((unsigned char)manufacturer[2])) { - memcpy(manufacturer, "???", 4); - } -} - -static char *parse_descriptor_string(const uint8_t *descriptor) -{ - char buffer[14]; - size_t out_len = 0; - size_t i; - - for (i = 5; i < 18; i++) { - const uint8_t value = descriptor[i]; - - if (value == 0x00 || value == 0x0a || value == 0x0d) { - break; - } - if (value < 0x20 || value > 0x7e) { - continue; - } - - buffer[out_len++] = (char)value; - } - - while (out_len > 0 && isspace((unsigned char)buffer[out_len - 1])) { - out_len--; - } - buffer[out_len] = '\0'; - - return dup_string(buffer); -} - -static bool parse_detailed_timing(const uint8_t *descriptor, struct di_edid_detailed_timing_def *timing) -{ - const uint16_t pixel_clock = read_le_u16(descriptor); - - if (pixel_clock == 0) { - return false; - } - - timing->horiz_video = (int)(descriptor[2] | ((descriptor[4] & 0xf0) << 4)); - timing->vert_video = (int)(descriptor[5] | ((descriptor[7] & 0xf0) << 4)); - timing->horiz_image_mm = (int)(descriptor[12] | ((descriptor[14] & 0xf0) << 4)); - timing->vert_image_mm = (int)(descriptor[13] | ((descriptor[14] & 0x0f) << 8)); - - return timing->horiz_video > 0 && timing->vert_video > 0; -} - -static bool populate_detailed_timings(struct di_edid *edid, const uint8_t *base) -{ - struct di_edid_detailed_timing_def parsed[EDID_DESCRIPTOR_COUNT]; - size_t count = 0; - size_t i; - - edid->detailed_timings = empty_detailed_timings; - - for (i = 0; i < EDID_DESCRIPTOR_COUNT; i++) { - const uint8_t *descriptor = base + EDID_DESCRIPTOR_OFFSET + (i * 18); - - if (parse_detailed_timing(descriptor, &parsed[count])) { - count++; - } - } - - if (count == 0) { - return true; - } - - edid->detailed_timing_storage = calloc(count, sizeof(*edid->detailed_timing_storage)); - edid->detailed_timings = calloc(count + 1, sizeof(*edid->detailed_timings)); - if (!edid->detailed_timing_storage || !edid->detailed_timings) { - return false; - } - - for (i = 0; i < count; i++) { - edid->detailed_timing_storage[i] = parsed[i]; - edid->detailed_timings[i] = &edid->detailed_timing_storage[i]; - } - - edid->detailed_timings[count] = NULL; - return true; -} - -static void populate_vendor_product(struct di_edid *edid, const uint8_t *base) -{ - const int manufacture_year = (int)base[17] + 1990; - const bool has_model_year = base[16] == 0xff; - - decode_manufacturer_id(base, edid->vendor_product.manufacturer); - edid->vendor_product.product = (int)read_le_u16(base + 10); - edid->vendor_product.serial = (int)read_le_u32(base + 12); - edid->vendor_product.manufacture_week = has_model_year ? 0 : (int)base[16]; - edid->vendor_product.manufacture_year = has_model_year ? 0 : manufacture_year; - edid->vendor_product.model_year = has_model_year ? manufacture_year : 0; -} - -static void populate_chromaticity(struct di_edid *edid, const uint8_t *base) -{ - if (!has_nonzero_bytes(base + 25, 10)) { - edid->has_chromaticity = false; - return; - } - - edid->chromaticity.red_x = decode_chromaticity_value(base[27], (base[25] >> 6) & 0x03); - edid->chromaticity.red_y = decode_chromaticity_value(base[28], (base[25] >> 4) & 0x03); - edid->chromaticity.green_x = decode_chromaticity_value(base[29], (base[25] >> 2) & 0x03); - edid->chromaticity.green_y = decode_chromaticity_value(base[30], base[25] & 0x03); - edid->chromaticity.blue_x = decode_chromaticity_value(base[31], (base[26] >> 6) & 0x03); - edid->chromaticity.blue_y = decode_chromaticity_value(base[32], (base[26] >> 4) & 0x03); - edid->chromaticity.white_x = decode_chromaticity_value(base[33], (base[26] >> 2) & 0x03); - edid->chromaticity.white_y = decode_chromaticity_value(base[34], base[26] & 0x03); - edid->has_chromaticity = true; -} - -static bool populate_strings(struct di_info *info, const uint8_t *base) -{ - size_t i; - - for (i = 0; i < EDID_DESCRIPTOR_COUNT; i++) { - const uint8_t *descriptor = base + EDID_DESCRIPTOR_OFFSET + (i * 18); - - if (descriptor[0] != 0x00 || descriptor[1] != 0x00 || descriptor[2] != 0x00) { - continue; - } - - if (descriptor[3] == 0xfc && !info->model) { - info->model = parse_descriptor_string(descriptor); - } else if (descriptor[3] == 0xff && !info->serial) { - info->serial = parse_descriptor_string(descriptor); - } - } - - if (!info->model) { - info->model = dup_string(""); - } - if (!info->serial) { - if (info->edid.vendor_product.serial != 0) { - char serial_buffer[32]; - - snprintf(serial_buffer, sizeof(serial_buffer), "%u", (unsigned int)info->edid.vendor_product.serial); - info->serial = dup_string(serial_buffer); - } else { - info->serial = dup_string(""); - } - } - - return info->model && info->serial; -} - -const struct di_info *di_info_parse_edid(const void *data, size_t size) -{ - const uint8_t *base = data; - struct di_info *info; - - if (!base || size < EDID_BLOCK_SIZE) { - return NULL; - } - if (memcmp(base, edid_header, sizeof(edid_header)) != 0) { - return NULL; - } - if (!validate_edid_block(base)) { - return NULL; - } - - info = calloc(1, sizeof(*info)); - if (!info) { - return NULL; - } - - info->edid.screen_size.width_cm = (int)base[21]; - info->edid.screen_size.height_cm = (int)base[22]; - info->edid.misc_features.preferred_timing_is_native = (base[24] & 0x02) != 0; - info->edid.extensions = empty_edid_exts; - - populate_vendor_product(&info->edid, base); - populate_chromaticity(&info->edid, base); - if (!populate_detailed_timings(&info->edid, base) || !populate_strings(info, base)) { - di_info_destroy(info); - return NULL; - } - - return info; -} - -void di_info_destroy(const struct di_info *info) -{ - struct di_info *mutable_info = (struct di_info *)info; - - if (!mutable_info) { - return; - } - - free(mutable_info->model); - free(mutable_info->serial); - if (mutable_info->edid.detailed_timings != empty_detailed_timings) { - free((void *)mutable_info->edid.detailed_timings); - } - free(mutable_info->edid.detailed_timing_storage); - free(mutable_info); -} - -const struct di_edid *di_info_get_edid(const struct di_info *info) -{ - return info ? &info->edid : NULL; -} - -char *di_info_get_model(const struct di_info *info) -{ - return dup_string(info ? info->model : ""); -} - -char *di_info_get_serial(const struct di_info *info) -{ - return dup_string(info ? info->serial : ""); -} - -const struct di_edid_detailed_timing_def *const *di_edid_get_detailed_timing_defs(const struct di_edid *edid) -{ - return edid && edid->detailed_timings ? edid->detailed_timings : empty_detailed_timings; -} - -const struct di_edid_screen_size *di_edid_get_screen_size(const struct di_edid *edid) -{ - return edid ? &edid->screen_size : &empty_screen_size; -} - -const struct di_edid_vendor_product *di_edid_get_vendor_product(const struct di_edid *edid) -{ - return edid ? &edid->vendor_product : NULL; -} - -const struct di_edid_chromaticity_coords *di_edid_get_chromaticity_coords(const struct di_edid *edid) -{ - return edid && edid->has_chromaticity ? &edid->chromaticity : NULL; -} - -const struct di_edid_ext *const *di_edid_get_extensions(const struct di_edid *edid) -{ - return edid && edid->extensions ? edid->extensions : empty_edid_exts; -} - -const struct di_edid_misc_features *di_edid_get_misc_features(const struct di_edid *edid) -{ - return edid ? &edid->misc_features : NULL; -} - -const struct di_edid_cta *di_edid_ext_get_cta(const struct di_edid_ext *ext) -{ - (void)ext; - return NULL; -} - -const struct di_displayid *di_edid_ext_get_displayid(const struct di_edid_ext *ext) -{ - (void)ext; - return NULL; -} - -const struct di_cta_data_block *const *di_edid_cta_get_data_blocks(const struct di_edid_cta *cta) -{ - (void)cta; - return empty_cta_blocks; -} - -const struct di_cta_hdr_static_metadata_block *di_cta_data_block_get_hdr_static_metadata(const struct di_cta_data_block *block) -{ - (void)block; - return NULL; -} - -const struct di_cta_colorimetry_block *di_cta_data_block_get_colorimetry(const struct di_cta_data_block *block) -{ - (void)block; - return NULL; -} - -const struct di_displayid_data_block *const *di_displayid_get_data_blocks(const struct di_displayid *displayid) -{ - (void)displayid; - return empty_displayid_blocks; -} - -const struct di_displayid_display_params *di_displayid_data_block_get_display_params(const struct di_displayid_data_block *block) -{ - (void)block; - return NULL; -} - -const struct di_displayid_type_i_ii_vii_timing *const *di_displayid_data_block_get_type_i_timings(const struct di_displayid_data_block *block) -{ - (void)block; - return empty_timings; -} - -const struct di_displayid_type_i_ii_vii_timing *const *di_displayid_data_block_get_type_ii_timings(const struct di_displayid_data_block *block) -{ - (void)block; - return empty_timings; -} diff --git a/local/recipes/libs/libdisplay-info/source/displayid.c b/local/recipes/libs/libdisplay-info/source/displayid.c new file mode 100644 index 0000000000..26f208bd44 --- /dev/null +++ b/local/recipes/libs/libdisplay-info/source/displayid.c @@ -0,0 +1,920 @@ +#include +#include +#include +#include +#include +#include + +#include "bits.h" +#include "displayid.h" + +/** + * The size of the mandatory fields in a DisplayID section. + */ +#define DISPLAYID_MIN_SIZE 5 +/** + * The maximum size of a DisplayID section. + */ +#define DISPLAYID_MAX_SIZE 256 +/** + * The size of a DisplayID data block header (tag, revision and size). + */ +#define DISPLAYID_DATA_BLOCK_HEADER_SIZE 3 +/** + * The size of a DisplayID type I timing. + */ +#define DISPLAYID_TYPE_I_TIMING_SIZE 20 +/** + * The size of a DisplayID type II timing. + */ +#define DISPLAYID_TYPE_II_TIMING_SIZE 11 +/** + * The size of a DisplayID type III timing. + */ +#define DISPLAYID_TYPE_III_TIMING_SIZE 3 + +static bool +is_all_zeroes(const uint8_t *data, size_t size) +{ + size_t i; + + for (i = 0; i < size; i++) { + if (data[i] != 0) + return false; + } + + return true; +} + +static void +add_failure(struct di_displayid *displayid, const char fmt[], ...) +{ + va_list args; + + va_start(args, fmt); + _di_logger_va_add_failure(displayid->logger, fmt, args); + va_end(args); +} + +static void +check_data_block_revision(struct di_displayid *displayid, + const uint8_t data[static DISPLAYID_DATA_BLOCK_HEADER_SIZE], + const char *block_name, uint8_t max_revision) +{ + uint8_t revision; + + revision = get_bit_range(data[0x01], 2, 0); + + if (revision > max_revision) { + add_failure(displayid, "%s: Unexpected revision (%u != %u).", + block_name, revision, max_revision); + } +} + +static void +check_data_block_flags_reserved(struct di_displayid *displayid, + const uint8_t data[static DISPLAYID_DATA_BLOCK_HEADER_SIZE], + const char *block_name) +{ + uint8_t flags; + + flags = get_bit_range(data[0x01], 7, 3); + + if (flags != 0) { + add_failure(displayid, "%s: Unexpected flags (0x%02x).", + block_name, flags); + } +} + +static bool +parse_display_params_block(struct di_displayid *displayid, + struct di_displayid_display_params_priv *priv, + const uint8_t *data, size_t size) +{ + struct di_displayid_display_params *params = &priv->base; + uint8_t raw_features; + + check_data_block_revision(displayid, data, "Display Parameters Data Block", 0); + check_data_block_flags_reserved(displayid, data, "Display Parameters Data Block"); + + if (size != 0x0F) { + add_failure(displayid, "Display Parameters Data Block: DisplayID payload length is different than expected (%zu != %zu)", size, 0x0F); + return false; + } + + params->horiz_image_mm = 0.1f * (float)(data[0x03] | (data[0x04] << 8)); + params->vert_image_mm = 0.1f * (float)(data[0x05] | (data[0x06] << 8)); + + params->horiz_pixels = data[0x07] | (data[0x08] << 8); + params->vert_pixels = data[0x09] | (data[0x0A] << 8); + + raw_features = data[0x0B]; + params->features = &priv->features; + priv->features.audio = has_bit(raw_features, 7); + priv->features.separate_audio_inputs = has_bit(raw_features, 6); + priv->features.audio_input_override = has_bit(raw_features, 5); + priv->features.power_management = has_bit(raw_features, 4); + priv->features.fixed_timing = has_bit(raw_features, 3); + priv->features.fixed_pixel_format = has_bit(raw_features, 2); + priv->features.ai = has_bit(raw_features, 1); + priv->features.deinterlacing = has_bit(raw_features, 0); + + if (data[0x0C] != 0xFF) + params->gamma = (float)data[0x0C] / 100 + 1; + params->aspect_ratio = (float)data[0x0D] / 100 + 1; + params->bits_per_color_overall = get_bit_range(data[0x0E], 7, 4) + 1; + params->bits_per_color_native = get_bit_range(data[0x0E], 3, 0) + 1; + + return true; +} + +static bool +timing_aspect_ratio_is_valid(uint8_t raw) +{ + switch (raw) { + case DI_DISPLAYID_TIMING_ASPECT_RATIO_1_1: + case DI_DISPLAYID_TIMING_ASPECT_RATIO_5_4: + case DI_DISPLAYID_TIMING_ASPECT_RATIO_4_3: + case DI_DISPLAYID_TIMING_ASPECT_RATIO_15_9: + case DI_DISPLAYID_TIMING_ASPECT_RATIO_16_9: + case DI_DISPLAYID_TIMING_ASPECT_RATIO_16_10: + case DI_DISPLAYID_TIMING_ASPECT_RATIO_64_27: + case DI_DISPLAYID_TIMING_ASPECT_RATIO_256_135: + case DI_DISPLAYID_TIMING_ASPECT_RATIO_UNDEFINED: + return true; + default: + return false; + } +} + +void +_di_displayid_parse_type_1_7_timing(struct di_displayid_type_i_ii_vii_timing *t, + struct di_logger *logger, + const char *prefix, + const uint8_t *data, + bool is_type7) +{ + int raw_pixel_clock; + uint8_t stereo_3d, aspect_ratio; + + raw_pixel_clock = data[0] | (data[1] << 8) | (data[2] << 16); + if (is_type7) + t->pixel_clock_mhz = (double)(1 + raw_pixel_clock) * 0.001; + else + t->pixel_clock_mhz = (double)(1 + raw_pixel_clock) * 0.01; + + t->preferred = has_bit(data[3], 7); + t->interlaced = has_bit(data[3], 4); + + stereo_3d = get_bit_range(data[3], 6, 5); + switch (stereo_3d) { + case DI_DISPLAYID_TYPE_I_II_VII_TIMING_STEREO_3D_NEVER: + case DI_DISPLAYID_TYPE_I_II_VII_TIMING_STEREO_3D_ALWAYS: + case DI_DISPLAYID_TYPE_I_II_VII_TIMING_STEREO_3D_USER: + t->stereo_3d = stereo_3d; + break; + default: + _di_logger_add_failure(logger, "%s: Reserved stereo 0x%02x.", + prefix, stereo_3d); + break; + } + + aspect_ratio = get_bit_range(data[3], 3, 0); + if (timing_aspect_ratio_is_valid(aspect_ratio)) { + t->aspect_ratio = aspect_ratio; + } else { + t->aspect_ratio = DI_DISPLAYID_TIMING_ASPECT_RATIO_UNDEFINED; + _di_logger_add_failure(logger, "%s: Unknown aspect 0x%02x.", + prefix, aspect_ratio); + } + + t->horiz_active = 1 + (data[4] | (data[5] << 8)); + t->horiz_blank = 1 + (data[6] | (data[7] << 8)); + t->horiz_offset = 1 + (data[8] | (get_bit_range(data[9], 6, 0) << 8)); + t->horiz_sync_polarity = has_bit(data[9], 7); + t->horiz_sync_width = 1 + (data[10] | (data[11] << 8)); + t->vert_active = 1 + (data[12] | (data[13] << 8)); + t->vert_blank = 1 + (data[14] | (data[15] << 8)); + t->vert_offset = 1 + (data[16] | (get_bit_range(data[17], 6, 0) << 8)); + t->vert_sync_polarity = has_bit(data[17], 7); + t->vert_sync_width = 1 + (data[18] | (data[19] << 8)); +} + +static bool +parse_type_i_timing(struct di_displayid *displayid, + struct di_displayid_data_block *data_block, + const uint8_t data[static DISPLAYID_TYPE_I_TIMING_SIZE]) +{ + struct di_displayid_type_i_ii_vii_timing timing, *t; + + _di_displayid_parse_type_1_7_timing(&timing, displayid->logger, + "Video Timing Modes Type 1 - Detailed Timings Data Block", + data, false); + + t = calloc(1, sizeof(*t)); + if (t == NULL) { + return false; + } + + *t = timing; + assert(data_block->type_i_timings_len < DISPLAYID_MAX_TYPE_I_TIMINGS); + data_block->type_i_timings[data_block->type_i_timings_len++] = t; + return true; +} + +static bool +parse_type_i_timing_block(struct di_displayid *displayid, + struct di_displayid_data_block *data_block, + const uint8_t *data, size_t size) +{ + size_t i; + + check_data_block_revision(displayid, data, + "Video Timing Modes Type 1 - Detailed Timings Data Block", + 1); + check_data_block_flags_reserved(displayid, data, + "Video Timing Modes Type 1 - Detailed Timings Data Block"); + + if ((size - DISPLAYID_DATA_BLOCK_HEADER_SIZE) % DISPLAYID_TYPE_I_TIMING_SIZE != 0) { + add_failure(displayid, + "Video Timing Modes Type 1 - Detailed Timings Data Block: payload size not divisible by element size."); + } + + for (i = DISPLAYID_DATA_BLOCK_HEADER_SIZE; + i + DISPLAYID_TYPE_I_TIMING_SIZE <= size; + i += DISPLAYID_TYPE_I_TIMING_SIZE) { + if (!parse_type_i_timing(displayid, data_block, &data[i])) { + return false; + } + } + + return true; +} + +static bool +parse_type_ii_timing(struct di_displayid *displayid, + struct di_displayid_data_block *data_block, + const uint8_t data[static DISPLAYID_TYPE_II_TIMING_SIZE]) +{ + int raw_pixel_clock; + uint8_t stereo_3d; + + struct di_displayid_type_i_ii_vii_timing *t = calloc(1, sizeof(*t)); + if (t == NULL) { + return false; + } + + t->aspect_ratio = DI_DISPLAYID_TIMING_ASPECT_RATIO_UNDEFINED; + + raw_pixel_clock = data[0] | (data[1] << 8) | (data[2] << 16); + t->pixel_clock_mhz = (double)(1 + raw_pixel_clock) * 0.01; + + t->preferred = has_bit(data[3], 7); + t->interlaced = has_bit(data[3], 4); + + stereo_3d = get_bit_range(data[3], 6, 5); + switch (stereo_3d) { + case DI_DISPLAYID_TYPE_I_II_VII_TIMING_STEREO_3D_NEVER: + case DI_DISPLAYID_TYPE_I_II_VII_TIMING_STEREO_3D_ALWAYS: + case DI_DISPLAYID_TYPE_I_II_VII_TIMING_STEREO_3D_USER: + t->stereo_3d = stereo_3d; + break; + default: + add_failure(displayid, + "Video Timing Modes Type 2 - Detailed Timings Data Block: Reserved stereo 0x%02x.", + stereo_3d); + break; + } + + t->horiz_sync_polarity = has_bit(data[3], 3); + t->vert_sync_polarity = has_bit(data[3], 2); + + if (get_bit_range(data[3], 1, 0) != 0) { + add_failure(displayid, + "Video Timing Modes Type 2 - Detailed Timings Data Block: " + "Timing Options bit 1-0 are reserved."); + } + + t->horiz_active = 8 + 8 * (data[4] | (get_bit_range(data[5], 0, 0) << 8)); + t->horiz_blank = 8 + 8 * get_bit_range(data[5], 7, 1); + t->horiz_offset = 8 + 8 * get_bit_range(data[6], 7, 4); + t->horiz_sync_width = 8 + 8 * get_bit_range(data[6], 3, 0); + t->vert_active = 1 + (data[7] | (get_bit_range(data[8], 3, 0) << 8)); + if (get_bit_range(data[8], 7, 4) != 0) { + add_failure(displayid, + "Video Timing Modes Type 2 - Detailed Timings Data Block: " + "Vertical Active Image bits 7-4 are reserved."); + } + t->vert_blank = 1 + data[9]; + t->vert_offset = 1 + get_bit_range(data[9], 7, 4); + t->vert_sync_width = 1 + get_bit_range(data[9], 3, 0); + + assert(data_block->type_ii_timings_len < DISPLAYID_MAX_TYPE_II_TIMINGS); + data_block->type_ii_timings[data_block->type_ii_timings_len++] = t; + return true; +} + +static bool +parse_type_ii_timing_block(struct di_displayid *displayid, + struct di_displayid_data_block *data_block, + const uint8_t *data, size_t size) +{ + size_t i; + + check_data_block_revision(displayid, data, + "Video Timing Modes Type 2 - Detailed Timings Data Block", + 0); + check_data_block_flags_reserved(displayid, data, + "Video Timing Modes Type 2 - Detailed Timings Data Block"); + + if ((size - DISPLAYID_DATA_BLOCK_HEADER_SIZE) % DISPLAYID_TYPE_II_TIMING_SIZE != 0) { + add_failure(displayid, + "Video Timing Modes Type 2 - Detailed Timings Data Block: payload size not divisible by element size."); + } + + for (i = DISPLAYID_DATA_BLOCK_HEADER_SIZE; + i + DISPLAYID_TYPE_II_TIMING_SIZE <= size; + i += DISPLAYID_TYPE_II_TIMING_SIZE) { + if (!parse_type_ii_timing(displayid, data_block, &data[i])) { + return false; + } + } + + return true; +} + +static bool +parse_type_iv_timing_block(struct di_displayid *displayid, + struct di_displayid_type_iv_timing *type_iv_timing, + const uint8_t *data, size_t size) +{ + uint8_t flags; + uint8_t type; + size_t i; + uint8_t code; + struct di_dmt_code *dmts = NULL; + struct di_cta_vic *cta_vics = NULL; + struct di_hdmi_vic *hdmi_vics = NULL; + + check_data_block_revision(displayid, data, + "Video Timing Modes Type 4 - Short Timings Data Block", + 1); + + flags = get_bit_range(data[0x01], 5, 3); + if (flags != 0) { + add_failure(displayid, + "Video Timing Modes Type 4 - Short Timings Data Block: Unexpected flags (0x%02x).", + flags); + } + + type = get_bit_range(data[0x01], 7, 6); + switch (type) { + case DI_DISPLAYID_TYPE_IV_TIMING_CODE_TYPE_DMT: + case DI_DISPLAYID_TYPE_IV_TIMING_CODE_TYPE_VIC_CTA: + case DI_DISPLAYID_TYPE_IV_TIMING_CODE_TYPE_VIC_HDMI: + type_iv_timing->type = type; + break; + default: + add_failure(displayid, + "Video Timing Modes Type 4 - Short Timings Data Block: Reserved Timing Code Type 0x%02x.", + type); + break; + } + + type_iv_timing->codes_len = size - DISPLAYID_DATA_BLOCK_HEADER_SIZE; + if (type_iv_timing->codes_len == 0) { + add_failure(displayid, + "Video Timing Modes Type 4 - Short Timings Data Block: No Timing Code present."); + return true; + } + + switch (type) { + case DI_DISPLAYID_TYPE_IV_TIMING_CODE_TYPE_DMT: + dmts = calloc(type_iv_timing->codes_len, sizeof(*dmts)); + if (!dmts) + return false; + + for (i = 0; i < type_iv_timing->codes_len; i++) { + code = data[DISPLAYID_DATA_BLOCK_HEADER_SIZE + i]; + dmts[i] = (struct di_dmt_code) { .code = code }; + } + break; + case DI_DISPLAYID_TYPE_IV_TIMING_CODE_TYPE_VIC_CTA: + cta_vics = calloc(type_iv_timing->codes_len, sizeof(*cta_vics)); + if (!cta_vics) + return false; + + for (i = 0; i < type_iv_timing->codes_len; i++) { + code = data[DISPLAYID_DATA_BLOCK_HEADER_SIZE + i]; + cta_vics[i] = (struct di_cta_vic) { .code = code }; + } + break; + case DI_DISPLAYID_TYPE_IV_TIMING_CODE_TYPE_VIC_HDMI: + hdmi_vics = calloc(type_iv_timing->codes_len, sizeof(*hdmi_vics)); + if (!hdmi_vics) + return false; + + for (i = 0; i < type_iv_timing->codes_len; i++) { + code = data[DISPLAYID_DATA_BLOCK_HEADER_SIZE + i]; + hdmi_vics[i] = (struct di_hdmi_vic) { .code = code }; + } + break; + } + + type_iv_timing->dmts = dmts; + type_iv_timing->cta_vics = cta_vics; + type_iv_timing->hdmi_vics = hdmi_vics; + + return true; +} + +static bool +parse_tiled_topo_block(struct di_displayid *displayid, + struct di_displayid_tiled_topo_priv *priv, + const uint8_t *data, size_t size) +{ + struct di_displayid_tiled_topo *tiled_topo = &priv->base; + uint8_t raw_caps, raw_missing_recv_behavior, raw_single_recv_behavior; + bool has_bezel; + float px_mult; + + check_data_block_revision(displayid, data, "Tiled Display Topology Data Block", 0); + check_data_block_flags_reserved(displayid, data, "Tiled Display Topology Data Block"); + + if (size - DISPLAYID_DATA_BLOCK_HEADER_SIZE != 22) { + add_failure(displayid, + "Tiled Display Topology Data Block: DisplayID payload length is different than expected (%zu != %zu)", + size - DISPLAYID_DATA_BLOCK_HEADER_SIZE, 22); + return false; + } + + raw_caps = data[0x03]; + tiled_topo->caps = &priv->caps; + priv->caps.single_enclosure = has_bit(raw_caps, 7); + has_bezel = has_bit(raw_caps, 6); + raw_missing_recv_behavior = get_bit_range(raw_caps, 4, 3); + raw_single_recv_behavior = get_bit_range(raw_caps, 2, 0); + if (has_bit(raw_caps, 5)) { + add_failure(displayid, "Tiled Display Topology Data Block: Capability bit 5 is reserved."); + } + + switch (raw_missing_recv_behavior) { + case DI_DISPLAYID_TILED_TOPO_MISSING_RECV_UNDEF: + case DI_DISPLAYID_TILED_TOPO_MISSING_RECV_TILE_ONLY: + priv->caps.missing_recv_behavior = raw_missing_recv_behavior; + break; + default: + add_failure(displayid, + "Tiled Display Topology Data Block: Behavior if more than one tile and fewer than total number of tiles set to reserved value 0x%02x.", + raw_missing_recv_behavior); + break; + } + + switch (raw_single_recv_behavior) { + case DI_DISPLAYID_TILED_TOPO_SINGLE_RECV_UNDEF: + case DI_DISPLAYID_TILED_TOPO_SINGLE_RECV_TILE_ONLY: + case DI_DISPLAYID_TILED_TOPO_SINGLE_RECV_SCALED: + case DI_DISPLAYID_TILED_TOPO_SINGLE_RECV_CLONED: + priv->caps.single_recv_behavior = raw_single_recv_behavior; + break; + default: + add_failure(displayid, + "Tiled Display Topology Data Block: Behavior if it is the only tile set to reserved value 0x%02x.", + raw_single_recv_behavior); + break; + } + + tiled_topo->total_horiz_tiles = 1 + (get_bit_range(data[0x04], 7, 4) | + (get_bit_range(data[0x06], 7, 6) << 4)); + tiled_topo->total_vert_tiles = 1 + (get_bit_range(data[0x04], 3, 0) | + (get_bit_range(data[0x06], 5, 4) << 4)); + tiled_topo->horiz_tile_location = 1 + (get_bit_range(data[0x05], 7, 4) | + (get_bit_range(data[0x06], 3, 2) << 4)); + tiled_topo->vert_tile_location = 1 + (get_bit_range(data[0x05], 3, 0) | + (get_bit_range(data[0x06], 1, 0) << 4)); + + tiled_topo->horiz_tile_pixels = 1 + (data[0x07] | (data[0x08] << 8)); + tiled_topo->vert_tile_lines = 1 + (data[0x09] | (data[0x0A] << 8)); + + px_mult = data[0x0B]; + if (has_bezel && px_mult == 0) { + add_failure(displayid, "Tiled Display Topology Data Block: Bezel information bit is set, but the pixel multiplier is zero."); + has_bezel = false; + } + if (has_bezel) { + tiled_topo->bezel = &priv->bezel; + priv->bezel.top_px = px_mult * data[0x0C] * 0.1f; + priv->bezel.bottom_px = px_mult * data[0x0D] * 0.1f; + priv->bezel.right_px = px_mult * data[0x0E] * 0.1f; + priv->bezel.left_px = px_mult * data[0x0F] * 0.1f; + } else { + if (px_mult != 0) + add_failure(displayid, "Tiled Display Topology Data Block: No bezel information, but the pixel multiplier is non-zero."); + if (!is_all_zeroes(&data[0x0C], 4)) + add_failure(displayid, "Tiled Display Topology Data Block: No bezel information, but the bezel size is non-zero."); + } + + memcpy(tiled_topo->vendor_id, &data[0x10], 3); + tiled_topo->product_code = (uint16_t)(data[0x13] | (data[0x14] << 8)); + tiled_topo->serial_number = data[0x15] | + (uint32_t)(data[0x16] << 8) | + (uint32_t)(data[0x17] << 16) | + (uint32_t)(data[0x18] << 24); + + return true; +} + +static bool +parse_type_iii_timing(struct di_displayid *displayid, + struct di_displayid_data_block *data_block, + const uint8_t data[static DISPLAYID_TYPE_III_TIMING_SIZE]) +{ + struct di_displayid_type_iii_timing *t; + uint8_t algo, aspect_ratio; + + t = calloc(1, sizeof(*t)); + if (t == NULL) + return false; + + t->preferred = has_bit(data[0], 7); + + algo = get_bit_range(data[0], 6, 4); + switch (algo) { + case DI_DISPLAYID_TYPE_III_TIMING_CVT_STANDARD_BLANKING: + case DI_DISPLAYID_TYPE_III_TIMING_CVT_REDUCED_BLANKING: + t->algo = algo; + break; + default: + add_failure(displayid, + "Video Timing Modes Type 3 - Short Timings Data Block: Reserved algorithm 0x%02x.", + algo); + goto error_reserved; + } + + aspect_ratio = get_bit_range(data[0], 3, 0); + if (timing_aspect_ratio_is_valid(aspect_ratio)) { + t->aspect_ratio = aspect_ratio; + } else { + add_failure(displayid, + "Video Timing Modes Type 3 - Short Timings Data Block: Reserved aspect ratio 0x%02x.", + aspect_ratio); + goto error_reserved; + } + + t->horiz_active = ((int32_t)data[1] + 1) * 8; + + t->interlaced = has_bit(data[2], 7); + t->refresh_rate_hz = (int32_t)get_bit_range(data[2], 6, 0) + 1; + + assert(data_block->type_iii_timings_len < DISPLAYID_MAX_TYPE_III_TIMINGS); + data_block->type_iii_timings[data_block->type_iii_timings_len++] = t; + return true; + +error_reserved: + free(t); + return true; +} + +static bool +parse_type_iii_timing_block(struct di_displayid *displayid, + struct di_displayid_data_block *data_block, + const uint8_t *data, size_t size) +{ + size_t i; + + check_data_block_revision(displayid, data, + "Video Timing Modes Type 3 - Short Timings Data Block", + 1); + + if ((size - DISPLAYID_DATA_BLOCK_HEADER_SIZE) % DISPLAYID_TYPE_III_TIMING_SIZE != 0) + add_failure(displayid, + "Video Timing Modes Type 3 - Short Timings Data Block: payload size not divisible by element size."); + + for (i = DISPLAYID_DATA_BLOCK_HEADER_SIZE; + i + DISPLAYID_TYPE_III_TIMING_SIZE <= size; + i += DISPLAYID_TYPE_III_TIMING_SIZE) { + if (!parse_type_iii_timing(displayid, data_block, &data[i])) + return false; + } + + return true; +} + +static ssize_t +parse_data_block(struct di_displayid *displayid, const uint8_t *data, + size_t size) +{ + uint8_t tag; + size_t data_block_size; + struct di_displayid_data_block *data_block = NULL; + + assert(size >= DISPLAYID_DATA_BLOCK_HEADER_SIZE); + + tag = data[0x00]; + data_block_size = (size_t) data[0x02] + DISPLAYID_DATA_BLOCK_HEADER_SIZE; + if (data_block_size > size) { + add_failure(displayid, + "The length of this DisplayID data block (%d) exceeds the number of bytes remaining (%zu)", + data_block_size, size); + goto skip; + } + + data_block = calloc(1, sizeof(*data_block)); + if (!data_block) + goto error; + + switch (tag) { + case DI_DISPLAYID_DATA_BLOCK_DISPLAY_PARAMS: + if (!parse_display_params_block(displayid, + &data_block->display_params, + data, data_block_size)) + goto error; + break; + case DI_DISPLAYID_DATA_BLOCK_TYPE_I_TIMING: + if (!parse_type_i_timing_block(displayid, data_block, data, data_block_size)) + goto error; + break; + case DI_DISPLAYID_DATA_BLOCK_TILED_DISPLAY_TOPO: + if (!parse_tiled_topo_block(displayid, &data_block->tiled_topo, data, + data_block_size)) + goto skip; + break; + case DI_DISPLAYID_DATA_BLOCK_TYPE_II_TIMING: + if (!parse_type_ii_timing_block(displayid, data_block, data, data_block_size)) + goto error; + break; + case DI_DISPLAYID_DATA_BLOCK_TYPE_III_TIMING: + if (!parse_type_iii_timing_block(displayid, data_block, data, data_block_size)) + goto error; + break; + case DI_DISPLAYID_DATA_BLOCK_TYPE_IV_TIMING: + if (!parse_type_iv_timing_block(displayid, + &data_block->type_iv_timing, + data, data_block_size)) + goto error; + break; + case DI_DISPLAYID_DATA_BLOCK_PRODUCT_ID: + case DI_DISPLAYID_DATA_BLOCK_COLOR_CHARACT: + case DI_DISPLAYID_DATA_BLOCK_VESA_TIMING: + case DI_DISPLAYID_DATA_BLOCK_CEA_TIMING: + case DI_DISPLAYID_DATA_BLOCK_TIMING_RANGE_LIMITS: + case DI_DISPLAYID_DATA_BLOCK_PRODUCT_SERIAL: + case DI_DISPLAYID_DATA_BLOCK_ASCII_STRING: + case DI_DISPLAYID_DATA_BLOCK_DISPLAY_DEVICE_DATA: + case DI_DISPLAYID_DATA_BLOCK_INTERFACE_POWER_SEQ: + case DI_DISPLAYID_DATA_BLOCK_TRANSFER_CHARACT: + case DI_DISPLAYID_DATA_BLOCK_DISPLAY_INTERFACE: + case DI_DISPLAYID_DATA_BLOCK_STEREO_DISPLAY_INTERFACE: + case DI_DISPLAYID_DATA_BLOCK_TYPE_V_TIMING: + case DI_DISPLAYID_DATA_BLOCK_TYPE_VI_TIMING: + break; /* Supported */ + case 0x7F: + goto skip; /* Vendor-specific */ + default: + add_failure(displayid, + "Unknown DisplayID Data Block (0x%" PRIx8 ", length %" PRIu8 ")", + tag, data_block_size - DISPLAYID_DATA_BLOCK_HEADER_SIZE); + goto skip; + } + + data_block->tag = tag; + + assert(displayid->data_blocks_len < DISPLAYID_MAX_DATA_BLOCKS); + displayid->data_blocks[displayid->data_blocks_len++] = data_block; + return (ssize_t) data_block_size; + +skip: + free(data_block); + return (ssize_t) data_block_size; + +error: + free(data_block); + return -1; +} + +static bool +is_data_block_end(const uint8_t *data, size_t size) +{ + if (size < DISPLAYID_DATA_BLOCK_HEADER_SIZE) + return true; + return is_all_zeroes(data, DISPLAYID_DATA_BLOCK_HEADER_SIZE); +} + +static bool +validate_checksum(const uint8_t *data, size_t size) +{ + uint8_t sum = 0; + size_t i; + + for (i = 0; i < size; i++) { + sum += data[i]; + } + + return sum == 0; +} + +int +_di_displayid_parse_version(const uint8_t *data, size_t size) +{ + if (size == 0) + return 0; + return get_bit_range(data[0x00], 7, 4); +} + +bool +_di_displayid_parse(struct di_displayid *displayid, const uint8_t *data, + size_t size, struct di_logger *logger) +{ + size_t section_size, i, max_data_block_size; + ssize_t data_block_size; + uint8_t product_type; + + if (size < DISPLAYID_MIN_SIZE) { + errno = EINVAL; + return false; + } + + displayid->logger = logger; + + displayid->version = _di_displayid_parse_version(data, size); + displayid->revision = get_bit_range(data[0x00], 3, 0); + if (displayid->version == 0 || displayid->version > 1) { + errno = ENOTSUP; + return false; + } + + section_size = (size_t) data[0x01] + DISPLAYID_MIN_SIZE; + if (section_size > DISPLAYID_MAX_SIZE || section_size > size) { + errno = EINVAL; + return false; + } + + if (!validate_checksum(data, section_size)) { + errno = EINVAL; + return false; + } + + product_type = data[0x02]; + switch (product_type) { + case DI_DISPLAYID_PRODUCT_TYPE_EXTENSION: + case DI_DISPLAYID_PRODUCT_TYPE_TEST: + case DI_DISPLAYID_PRODUCT_TYPE_DISPLAY_PANEL: + case DI_DISPLAYID_PRODUCT_TYPE_STANDALONE_DISPLAY: + case DI_DISPLAYID_PRODUCT_TYPE_TV_RECEIVER: + case DI_DISPLAYID_PRODUCT_TYPE_REPEATER: + case DI_DISPLAYID_PRODUCT_TYPE_DIRECT_DRIVE: + displayid->product_type = product_type; + break; + default: + errno = EINVAL; + return false; + } + + i = DISPLAYID_MIN_SIZE - 1; + max_data_block_size = 0; + while (i < section_size - 1) { + max_data_block_size = section_size - 1 - i; + if (is_data_block_end(&data[i], max_data_block_size)) + break; + data_block_size = parse_data_block(displayid, &data[i], + max_data_block_size); + if (data_block_size < 0) + return false; + assert(data_block_size > 0); + i += (size_t) data_block_size; + } + if (!is_all_zeroes(&data[i], max_data_block_size)) { + if (max_data_block_size < DISPLAYID_DATA_BLOCK_HEADER_SIZE) + add_failure(displayid, + "Not enough bytes remain (%zu) for a DisplayID data block and the DisplayID filler is non-0.", + max_data_block_size); + else + add_failure(displayid, "Padding: Contains non-zero bytes."); + } + + displayid->logger = NULL; + return true; +} + +static void +destroy_data_block(struct di_displayid_data_block *data_block) +{ + size_t i; + + switch (data_block->tag) { + case DI_DISPLAYID_DATA_BLOCK_TYPE_I_TIMING: + for (i = 0; i < data_block->type_i_timings_len; i++) + free(data_block->type_i_timings[i]); + break; + case DI_DISPLAYID_DATA_BLOCK_TYPE_II_TIMING: + for (i = 0; i < data_block->type_ii_timings_len; i++) + free(data_block->type_ii_timings[i]); + break; + case DI_DISPLAYID_DATA_BLOCK_TYPE_III_TIMING: + for (i = 0; i < data_block->type_iii_timings_len; i++) + free(data_block->type_iii_timings[i]); + break; + case DI_DISPLAYID_DATA_BLOCK_TYPE_IV_TIMING: + free((struct di_dmt_code *) data_block->type_iv_timing.dmts); + free((struct di_cta_vic *) data_block->type_iv_timing.cta_vics); + free((struct di_hdmi_vic *) data_block->type_iv_timing.hdmi_vics); + break; + default: + break; /* Nothing to do */ + } + + free(data_block); +} + +void +_di_displayid_finish(struct di_displayid *displayid) +{ + size_t i; + + for (i = 0; i < displayid->data_blocks_len; i++) + destroy_data_block(displayid->data_blocks[i]); +} + +int +di_displayid_get_version(const struct di_displayid *displayid) +{ + return displayid->version; +} + +int +di_displayid_get_revision(const struct di_displayid *displayid) +{ + return displayid->revision; +} + +enum di_displayid_product_type +di_displayid_get_product_type(const struct di_displayid *displayid) +{ + return displayid->product_type; +} + +enum di_displayid_data_block_tag +di_displayid_data_block_get_tag(const struct di_displayid_data_block *data_block) +{ + return data_block->tag; +} + +const struct di_displayid_display_params * +di_displayid_data_block_get_display_params(const struct di_displayid_data_block *data_block) +{ + if (data_block->tag != DI_DISPLAYID_DATA_BLOCK_DISPLAY_PARAMS) { + return NULL; + } + return &data_block->display_params.base; +} + +const struct di_displayid_type_i_ii_vii_timing *const * +di_displayid_data_block_get_type_i_timings(const struct di_displayid_data_block *data_block) +{ + if (data_block->tag != DI_DISPLAYID_DATA_BLOCK_TYPE_I_TIMING) { + return NULL; + } + return (const struct di_displayid_type_i_ii_vii_timing *const *) data_block->type_i_timings; +} + +const struct di_displayid_type_i_ii_vii_timing *const * +di_displayid_data_block_get_type_ii_timings(const struct di_displayid_data_block *data_block) +{ + if (data_block->tag != DI_DISPLAYID_DATA_BLOCK_TYPE_II_TIMING) { + return NULL; + } + return (const struct di_displayid_type_i_ii_vii_timing *const *) data_block->type_ii_timings; +} + +const struct di_displayid_tiled_topo * +di_displayid_data_block_get_tiled_topo(const struct di_displayid_data_block *data_block) +{ + if (data_block->tag != DI_DISPLAYID_DATA_BLOCK_TILED_DISPLAY_TOPO) { + return NULL; + } + return &data_block->tiled_topo.base; +} + +const struct di_displayid_type_iii_timing *const * +di_displayid_data_block_get_type_iii_timings(const struct di_displayid_data_block *data_block) +{ + if (data_block->tag != DI_DISPLAYID_DATA_BLOCK_TYPE_III_TIMING) { + return NULL; + } + return (const struct di_displayid_type_iii_timing *const *) data_block->type_iii_timings; +} + +const struct di_displayid_type_iv_timing * +di_displayid_data_block_get_type_iv_timing(const struct di_displayid_data_block *data_block) +{ + if (data_block->tag != DI_DISPLAYID_DATA_BLOCK_TYPE_IV_TIMING) { + return NULL; + } + return &data_block->type_iv_timing; +} + +const struct di_displayid_data_block *const * +di_displayid_get_data_blocks(const struct di_displayid *displayid) +{ + return (const struct di_displayid_data_block *const *) displayid->data_blocks; +} diff --git a/local/recipes/libs/libdisplay-info/source/displayid2.c b/local/recipes/libs/libdisplay-info/source/displayid2.c new file mode 100644 index 0000000000..6ab9c7b6ea --- /dev/null +++ b/local/recipes/libs/libdisplay-info/source/displayid2.c @@ -0,0 +1,313 @@ +#include +#include +#include +#include + +#include "bits.h" +#include "displayid.h" +#include "displayid2.h" + +/** + * The size of a DisplayID v2 section header. + */ +#define DISPLAYID2_HEADER_SIZE 4 +/** + * The size of the mandatory fields in a DisplayID v2 section (header + checksum). + */ +#define DISPLAYID2_MIN_SIZE (DISPLAYID2_HEADER_SIZE + 1) +/** + * The maximum size of a DisplayID v2 section. + */ +#define DISPLAYID2_MAX_SIZE 256 +/** + * The size of a DisplayID v2 data block header (tag, revision and size). + */ +#define DISPLAYID2_DATA_BLOCK_HEADER_SIZE 3 + +static void +add_failure(struct di_displayid2 *displayid2, const char fmt[], ...) +{ + va_list args; + + va_start(args, fmt); + _di_logger_va_add_failure(displayid2->logger, fmt, args); + va_end(args); +} + +static bool +parse_cta861_block(struct di_displayid2 *displayid2, struct di_displayid2_data_block *data_block, + const uint8_t *data, size_t size) +{ + struct di_cta cta; + size_t i; + uint8_t data_block_tag, data_block_size; + struct di_cta_data_block *cta_data_block; + + cta = (struct di_cta){ + /* The DisplayID block doesn't specify the CTA revision. Use + * the latest one to silence warnings. */ + .revision = 3, + .logger = displayid2->logger, + }; + + i = 0x03; + while (i < size) { + data_block_size = get_bit_range(data[i], 4, 0); + data_block_tag = get_bit_range(data[i], 7, 5); + + if (data_block_size > size - i - 1) { + add_failure(displayid2, + "CTA data block size (%" PRIu8 ") exceeds number of remaining bytes (%zu)", + data_block_size, size - i - 1); + break; + } + + if (!_di_cta_data_block_parse(&cta, data_block_tag, + &data[i + 1], data_block_size, &cta_data_block)) { + return false; + } + + if (cta_data_block != NULL) { + assert(data_block->cta861.data_blocks_len < DISPLAYID2_CTA861_MAX_DATA_BLOCKS); + data_block->cta861.data_blocks[data_block->cta861.data_blocks_len++] = cta_data_block; + } + + i += 1 + data_block_size; + } + + return true; +} + +static ssize_t +parse_data_block(struct di_displayid2 *displayid2, const uint8_t *data, + size_t size) +{ + uint8_t tag; + size_t data_block_size; + struct di_displayid2_data_block *data_block = NULL; + + assert(size >= DISPLAYID2_DATA_BLOCK_HEADER_SIZE); + + tag = data[0x00]; + data_block_size = (size_t) data[0x02] + DISPLAYID2_DATA_BLOCK_HEADER_SIZE; + if (data_block_size > size) { + add_failure(displayid2, + "The length of this DisplayID data block (%d) exceeds the number of bytes remaining (%zu)", + data_block_size, size); + goto skip; + } + + data_block = calloc(1, sizeof(*data_block)); + if (!data_block) + goto error; + + switch (tag) { + case DI_DISPLAYID2_DATA_BLOCK_CTA861: + if (!parse_cta861_block(displayid2, data_block, data, data_block_size)) { + goto error; + } + break; + case DI_DISPLAYID2_DATA_BLOCK_PRODUCT_ID: + case DI_DISPLAYID2_DATA_BLOCK_DISPLAY_PARAMS: + case DI_DISPLAYID2_DATA_BLOCK_TYPE_VII_TIMING: + case DI_DISPLAYID2_DATA_BLOCK_TYPE_VIII_TIMING: + case DI_DISPLAYID2_DATA_BLOCK_TYPE_IX_TIMING: + case DI_DISPLAYID2_DATA_BLOCK_DYN_TIMING_RANGE_LIMITS: + case DI_DISPLAYID2_DATA_BLOCK_DISPLAY_INTERFACE_FEATURES: + case DI_DISPLAYID2_DATA_BLOCK_STEREO_DISPLAY_INTERFACE: + case DI_DISPLAYID2_DATA_BLOCK_TILED_DISPLAY_TOPO: + case DI_DISPLAYID2_DATA_BLOCK_CONTAINERID: + case DI_DISPLAYID2_DATA_BLOCK_TYPE_X_TIMING: + case DI_DISPLAYID2_DATA_BLOCK_ADAPTIVE_SYNC: + case DI_DISPLAYID2_DATA_BLOCK_ARVR_HMD: + case DI_DISPLAYID2_DATA_BLOCK_ARVR_LAYER: + break; /* Supported */ + case 0x7E: + goto skip; /* Vendor-specific */ + default: + add_failure(displayid2, + "Unknown DisplayID v2 Data Block (0x%" PRIx8 ", length %" PRIu8 ")", + tag, data_block_size - DISPLAYID2_DATA_BLOCK_HEADER_SIZE); + goto skip; + } + + data_block->tag = tag; + + assert(displayid2->data_blocks_len < DISPLAYID2_MAX_DATA_BLOCKS); + displayid2->data_blocks[displayid2->data_blocks_len++] = data_block; + return (ssize_t) data_block_size; + +skip: + free(data_block); + return (ssize_t) data_block_size; + +error: + free(data_block); + return -1; +} + +static bool +is_all_zeroes(const uint8_t *data, size_t size) +{ + size_t i; + + for (i = 0; i < size; i++) { + if (data[i] != 0) + return false; + } + + return true; +} + +static bool +is_data_block_end(const uint8_t *data, size_t size) +{ + if (size < DISPLAYID2_DATA_BLOCK_HEADER_SIZE) + return true; + return is_all_zeroes(data, DISPLAYID2_DATA_BLOCK_HEADER_SIZE); +} + +static bool +validate_checksum(const uint8_t *data, size_t size) +{ + uint8_t sum = 0; + size_t i; + + for (i = 0; i < size; i++) { + sum += data[i]; + } + + return sum == 0; +} + +bool +_di_displayid2_parse(struct di_displayid2 *displayid2, const uint8_t *data, + size_t size, struct di_logger *logger) +{ + size_t section_size, i, max_data_block_size; + ssize_t data_block_size; + int version; + uint8_t product_primary_use_case; + + if (size < DISPLAYID2_MIN_SIZE) { + errno = EINVAL; + return false; + } + + displayid2->logger = logger; + + version = _di_displayid_parse_version(data, size); + displayid2->revision = get_bit_range(data[0x00], 3, 0); + if (version != 2) { + errno = ENOTSUP; + return false; + } + + section_size = (size_t) data[0x01] + DISPLAYID2_MIN_SIZE; + if (section_size > DISPLAYID2_MAX_SIZE || section_size > size) { + errno = EINVAL; + return false; + } + + if (!validate_checksum(data, section_size)) { + errno = EINVAL; + return false; + } + + product_primary_use_case = data[0x02]; + switch (product_primary_use_case) { + case DI_DISPLAYID2_PRODUCT_PRIMARY_USE_CASE_EXTENSION: + case DI_DISPLAYID2_PRODUCT_PRIMARY_USE_CASE_TEST: + case DI_DISPLAYID2_PRODUCT_PRIMARY_USE_CASE_GENERIC: + case DI_DISPLAYID2_PRODUCT_PRIMARY_USE_CASE_TV: + case DI_DISPLAYID2_PRODUCT_PRIMARY_USE_CASE_DESKTOP_PRODUCTIVITY: + case DI_DISPLAYID2_PRODUCT_PRIMARY_USE_CASE_DESKTOP_GAMING: + case DI_DISPLAYID2_PRODUCT_PRIMARY_USE_CASE_PRESENTATION: + case DI_DISPLAYID2_PRODUCT_PRIMARY_USE_CASE_HMD_VR: + case DI_DISPLAYID2_PRODUCT_PRIMARY_USE_CASE_HMD_AR: + displayid2->product_primary_use_case = product_primary_use_case; + break; + default: + errno = EINVAL; + return false; + } + + i = DISPLAYID2_HEADER_SIZE; + max_data_block_size = 0; + while (i < section_size - 1) { + max_data_block_size = section_size - 1 - i; + if (is_data_block_end(&data[i], max_data_block_size)) + break; + data_block_size = parse_data_block(displayid2, &data[i], + max_data_block_size); + if (data_block_size < 0) + return false; + assert(data_block_size > 0); + i += (size_t) data_block_size; + } + if (!is_all_zeroes(&data[i], max_data_block_size)) { + if (max_data_block_size < DISPLAYID2_DATA_BLOCK_HEADER_SIZE) { + add_failure(displayid2, + "Not enough bytes remain (%zu) for a DisplayID data block and the DisplayID filler is non-0.", + max_data_block_size); + } else { + add_failure(displayid2, "Padding: Contains non-zero bytes."); + } + } + + displayid2->logger = NULL; + return true; +} + +static void +data_block_destroy(struct di_displayid2_data_block *data_block) +{ + size_t i; + + for (i = 0; i < data_block->cta861.data_blocks_len; i++) + _di_cta_data_block_destroy(data_block->cta861.data_blocks[i]); + + free(data_block); +} + +void +_di_displayid2_finish(struct di_displayid2 *displayid2) +{ + size_t i; + + for (i = 0; i < displayid2->data_blocks_len; i++) + data_block_destroy(displayid2->data_blocks[i]); +} + +int +di_displayid2_get_revision(const struct di_displayid2 *displayid2) +{ + return displayid2->revision; +} + +enum di_displayid2_product_primary_use_case +di_displayid2_get_product_primary_use_case(const struct di_displayid2 *displayid2) +{ + return displayid2->product_primary_use_case; +} + +const struct di_displayid2_data_block *const * +di_displayid2_get_data_blocks(const struct di_displayid2 *displayid2) +{ + return (const struct di_displayid2_data_block *const *) displayid2->data_blocks; +} + +enum di_displayid2_data_block_tag +di_displayid2_data_block_get_tag(const struct di_displayid2_data_block *data_block) +{ + return data_block->tag; +} + +const struct di_cta_data_block *const * +di_displayid2_data_block_get_cta_data_blocks(const struct di_displayid2_data_block *data_block) +{ + if (data_block->tag != DI_DISPLAYID2_DATA_BLOCK_CTA861) { + return NULL; + } + return (const struct di_cta_data_block *const *) data_block->cta861.data_blocks; +} diff --git a/local/recipes/libs/libdisplay-info/source/dmt-table.c b/local/recipes/libs/libdisplay-info/source/dmt-table.c new file mode 100644 index 0000000000..5fd7fc98af --- /dev/null +++ b/local/recipes/libs/libdisplay-info/source/dmt-table.c @@ -0,0 +1,1556 @@ +/* DO NOT EDIT! This file has been generated by gen-dmt.py from VESA-DMT-1.13.pdf. */ + +#include + +const struct di_dmt_timing _di_dmt_timings[] = { + { + .dmt_code = { .code = 0x01 }, + .edid_std_id = 0, + .cvt_id = 0, + .horiz_video = 640, + .vert_video = 350, + .refresh_rate_hz = 85.0, + .pixel_clock_hz = 31500000, + .horiz_blank = 192, + .horiz_front_porch = 32, + .horiz_sync_pulse = 64, + .horiz_border = 0, + .vert_blank = 95, + .vert_front_porch = 32, + .vert_sync_pulse = 3, + .vert_border = 0, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x02 }, + .edid_std_id = 0x3119, + .cvt_id = 0, + .horiz_video = 640, + .vert_video = 400, + .refresh_rate_hz = 85.0, + .pixel_clock_hz = 31500000, + .horiz_blank = 192, + .horiz_front_porch = 32, + .horiz_sync_pulse = 64, + .horiz_border = 0, + .vert_blank = 45, + .vert_front_porch = 1, + .vert_sync_pulse = 3, + .vert_border = 0, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x03 }, + .edid_std_id = 0, + .cvt_id = 0, + .horiz_video = 720, + .vert_video = 400, + .refresh_rate_hz = 85.0, + .pixel_clock_hz = 35500000, + .horiz_blank = 216, + .horiz_front_porch = 36, + .horiz_sync_pulse = 72, + .horiz_border = 0, + .vert_blank = 46, + .vert_front_porch = 1, + .vert_sync_pulse = 3, + .vert_border = 0, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x04 }, + .edid_std_id = 0x3140, + .cvt_id = 0, + .horiz_video = 640, + .vert_video = 480, + .refresh_rate_hz = 60.0, + .pixel_clock_hz = 25175000, + .horiz_blank = 144, + .horiz_front_porch = 8, + .horiz_sync_pulse = 96, + .horiz_border = 8, + .vert_blank = 29, + .vert_front_porch = 2, + .vert_sync_pulse = 2, + .vert_border = 8, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x05 }, + .edid_std_id = 0x314C, + .cvt_id = 0, + .horiz_video = 640, + .vert_video = 480, + .refresh_rate_hz = 72.0, + .pixel_clock_hz = 31500000, + .horiz_blank = 176, + .horiz_front_porch = 16, + .horiz_sync_pulse = 40, + .horiz_border = 8, + .vert_blank = 24, + .vert_front_porch = 1, + .vert_sync_pulse = 3, + .vert_border = 8, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x06 }, + .edid_std_id = 0x314F, + .cvt_id = 0, + .horiz_video = 640, + .vert_video = 480, + .refresh_rate_hz = 75.0, + .pixel_clock_hz = 31500000, + .horiz_blank = 200, + .horiz_front_porch = 16, + .horiz_sync_pulse = 64, + .horiz_border = 0, + .vert_blank = 20, + .vert_front_porch = 1, + .vert_sync_pulse = 3, + .vert_border = 0, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x07 }, + .edid_std_id = 0x3159, + .cvt_id = 0, + .horiz_video = 640, + .vert_video = 480, + .refresh_rate_hz = 85.0, + .pixel_clock_hz = 36000000, + .horiz_blank = 192, + .horiz_front_porch = 56, + .horiz_sync_pulse = 56, + .horiz_border = 0, + .vert_blank = 29, + .vert_front_porch = 1, + .vert_sync_pulse = 3, + .vert_border = 0, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x08 }, + .edid_std_id = 0, + .cvt_id = 0, + .horiz_video = 800, + .vert_video = 600, + .refresh_rate_hz = 56.0, + .pixel_clock_hz = 36000000, + .horiz_blank = 224, + .horiz_front_porch = 24, + .horiz_sync_pulse = 72, + .horiz_border = 0, + .vert_blank = 25, + .vert_front_porch = 1, + .vert_sync_pulse = 2, + .vert_border = 0, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x09 }, + .edid_std_id = 0x4540, + .cvt_id = 0, + .horiz_video = 800, + .vert_video = 600, + .refresh_rate_hz = 60.0, + .pixel_clock_hz = 40000000, + .horiz_blank = 256, + .horiz_front_porch = 40, + .horiz_sync_pulse = 128, + .horiz_border = 0, + .vert_blank = 28, + .vert_front_porch = 1, + .vert_sync_pulse = 4, + .vert_border = 0, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x0A }, + .edid_std_id = 0x454C, + .cvt_id = 0, + .horiz_video = 800, + .vert_video = 600, + .refresh_rate_hz = 72.0, + .pixel_clock_hz = 50000000, + .horiz_blank = 240, + .horiz_front_porch = 56, + .horiz_sync_pulse = 120, + .horiz_border = 0, + .vert_blank = 66, + .vert_front_porch = 37, + .vert_sync_pulse = 6, + .vert_border = 0, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x0B }, + .edid_std_id = 0x454F, + .cvt_id = 0, + .horiz_video = 800, + .vert_video = 600, + .refresh_rate_hz = 75.0, + .pixel_clock_hz = 49500000, + .horiz_blank = 256, + .horiz_front_porch = 16, + .horiz_sync_pulse = 80, + .horiz_border = 0, + .vert_blank = 25, + .vert_front_porch = 1, + .vert_sync_pulse = 3, + .vert_border = 0, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x0C }, + .edid_std_id = 0x4559, + .cvt_id = 0, + .horiz_video = 800, + .vert_video = 600, + .refresh_rate_hz = 85.0, + .pixel_clock_hz = 56250000, + .horiz_blank = 248, + .horiz_front_porch = 32, + .horiz_sync_pulse = 64, + .horiz_border = 0, + .vert_blank = 31, + .vert_front_porch = 1, + .vert_sync_pulse = 3, + .vert_border = 0, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x0D }, + .edid_std_id = 0, + .cvt_id = 0, + .horiz_video = 800, + .vert_video = 600, + .refresh_rate_hz = 120.0, + .pixel_clock_hz = 73250000, + .horiz_blank = 160, + .horiz_front_porch = 48, + .horiz_sync_pulse = 32, + .horiz_border = 0, + .vert_blank = 36, + .vert_front_porch = 3, + .vert_sync_pulse = 4, + .vert_border = 0, + .reduced_blanking = true, + }, + { + .dmt_code = { .code = 0x0E }, + .edid_std_id = 0, + .cvt_id = 0, + .horiz_video = 848, + .vert_video = 480, + .refresh_rate_hz = 60.0, + .pixel_clock_hz = 33750000, + .horiz_blank = 240, + .horiz_front_porch = 16, + .horiz_sync_pulse = 112, + .horiz_border = 0, + .vert_blank = 37, + .vert_front_porch = 6, + .vert_sync_pulse = 8, + .vert_border = 0, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x0F }, + .edid_std_id = 0, + .cvt_id = 0, + .horiz_video = 1024, + .vert_video = 768, + .refresh_rate_hz = 43.0, + .pixel_clock_hz = 44900000, + .horiz_blank = 240, + .horiz_front_porch = 8, + .horiz_sync_pulse = 176, + .horiz_border = 0, + .vert_blank = 24, + .vert_front_porch = 0, + .vert_sync_pulse = 4, + .vert_border = 0, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x10 }, + .edid_std_id = 0x6140, + .cvt_id = 0, + .horiz_video = 1024, + .vert_video = 768, + .refresh_rate_hz = 60.0, + .pixel_clock_hz = 65000000, + .horiz_blank = 320, + .horiz_front_porch = 24, + .horiz_sync_pulse = 136, + .horiz_border = 0, + .vert_blank = 38, + .vert_front_porch = 3, + .vert_sync_pulse = 6, + .vert_border = 0, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x11 }, + .edid_std_id = 0x614A, + .cvt_id = 0, + .horiz_video = 1024, + .vert_video = 768, + .refresh_rate_hz = 70.0, + .pixel_clock_hz = 75000000, + .horiz_blank = 304, + .horiz_front_porch = 24, + .horiz_sync_pulse = 136, + .horiz_border = 0, + .vert_blank = 38, + .vert_front_porch = 3, + .vert_sync_pulse = 6, + .vert_border = 0, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x12 }, + .edid_std_id = 0x614F, + .cvt_id = 0, + .horiz_video = 1024, + .vert_video = 768, + .refresh_rate_hz = 75.0, + .pixel_clock_hz = 78750000, + .horiz_blank = 288, + .horiz_front_porch = 16, + .horiz_sync_pulse = 96, + .horiz_border = 0, + .vert_blank = 32, + .vert_front_porch = 1, + .vert_sync_pulse = 3, + .vert_border = 0, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x13 }, + .edid_std_id = 0x6159, + .cvt_id = 0, + .horiz_video = 1024, + .vert_video = 768, + .refresh_rate_hz = 85.0, + .pixel_clock_hz = 94500000, + .horiz_blank = 352, + .horiz_front_porch = 48, + .horiz_sync_pulse = 96, + .horiz_border = 0, + .vert_blank = 40, + .vert_front_porch = 1, + .vert_sync_pulse = 3, + .vert_border = 0, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x14 }, + .edid_std_id = 0, + .cvt_id = 0, + .horiz_video = 1024, + .vert_video = 768, + .refresh_rate_hz = 120.0, + .pixel_clock_hz = 115500000, + .horiz_blank = 160, + .horiz_front_porch = 48, + .horiz_sync_pulse = 32, + .horiz_border = 0, + .vert_blank = 45, + .vert_front_porch = 3, + .vert_sync_pulse = 4, + .vert_border = 0, + .reduced_blanking = true, + }, + { + .dmt_code = { .code = 0x15 }, + .edid_std_id = 0x714F, + .cvt_id = 0, + .horiz_video = 1152, + .vert_video = 864, + .refresh_rate_hz = 75.0, + .pixel_clock_hz = 108000000, + .horiz_blank = 448, + .horiz_front_porch = 64, + .horiz_sync_pulse = 128, + .horiz_border = 0, + .vert_blank = 36, + .vert_front_porch = 1, + .vert_sync_pulse = 3, + .vert_border = 0, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x55 }, + .edid_std_id = 0x81C0, + .cvt_id = 0, + .horiz_video = 1280, + .vert_video = 720, + .refresh_rate_hz = 60.0, + .pixel_clock_hz = 74250000, + .horiz_blank = 370, + .horiz_front_porch = 110, + .horiz_sync_pulse = 40, + .horiz_border = 0, + .vert_blank = 30, + .vert_front_porch = 5, + .vert_sync_pulse = 5, + .vert_border = 0, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x16 }, + .edid_std_id = 0, + .cvt_id = 0x7F1C21, + .horiz_video = 1280, + .vert_video = 768, + .refresh_rate_hz = 60.0, + .pixel_clock_hz = 68250000, + .horiz_blank = 160, + .horiz_front_porch = 48, + .horiz_sync_pulse = 32, + .horiz_border = 0, + .vert_blank = 22, + .vert_front_porch = 3, + .vert_sync_pulse = 7, + .vert_border = 0, + .reduced_blanking = true, + }, + { + .dmt_code = { .code = 0x17 }, + .edid_std_id = 0, + .cvt_id = 0x7F1C28, + .horiz_video = 1280, + .vert_video = 768, + .refresh_rate_hz = 60.0, + .pixel_clock_hz = 79500000, + .horiz_blank = 384, + .horiz_front_porch = 64, + .horiz_sync_pulse = 128, + .horiz_border = 0, + .vert_blank = 30, + .vert_front_porch = 3, + .vert_sync_pulse = 7, + .vert_border = 0, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x18 }, + .edid_std_id = 0, + .cvt_id = 0x7F1C44, + .horiz_video = 1280, + .vert_video = 768, + .refresh_rate_hz = 75.0, + .pixel_clock_hz = 102250000, + .horiz_blank = 416, + .horiz_front_porch = 80, + .horiz_sync_pulse = 128, + .horiz_border = 0, + .vert_blank = 37, + .vert_front_porch = 3, + .vert_sync_pulse = 7, + .vert_border = 0, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x19 }, + .edid_std_id = 0, + .cvt_id = 0x7F1C62, + .horiz_video = 1280, + .vert_video = 768, + .refresh_rate_hz = 85.0, + .pixel_clock_hz = 117500000, + .horiz_blank = 432, + .horiz_front_porch = 80, + .horiz_sync_pulse = 136, + .horiz_border = 0, + .vert_blank = 41, + .vert_front_porch = 3, + .vert_sync_pulse = 7, + .vert_border = 0, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x1A }, + .edid_std_id = 0, + .cvt_id = 0, + .horiz_video = 1280, + .vert_video = 768, + .refresh_rate_hz = 120.0, + .pixel_clock_hz = 140250000, + .horiz_blank = 160, + .horiz_front_porch = 48, + .horiz_sync_pulse = 32, + .horiz_border = 0, + .vert_blank = 45, + .vert_front_porch = 3, + .vert_sync_pulse = 7, + .vert_border = 0, + .reduced_blanking = true, + }, + { + .dmt_code = { .code = 0x1B }, + .edid_std_id = 0, + .cvt_id = 0x8F1821, + .horiz_video = 1280, + .vert_video = 800, + .refresh_rate_hz = 60.0, + .pixel_clock_hz = 71000000, + .horiz_blank = 160, + .horiz_front_porch = 48, + .horiz_sync_pulse = 32, + .horiz_border = 0, + .vert_blank = 23, + .vert_front_porch = 3, + .vert_sync_pulse = 6, + .vert_border = 0, + .reduced_blanking = true, + }, + { + .dmt_code = { .code = 0x1C }, + .edid_std_id = 0x8100, + .cvt_id = 0x8F1828, + .horiz_video = 1280, + .vert_video = 800, + .refresh_rate_hz = 60.0, + .pixel_clock_hz = 83500000, + .horiz_blank = 400, + .horiz_front_porch = 72, + .horiz_sync_pulse = 128, + .horiz_border = 0, + .vert_blank = 31, + .vert_front_porch = 3, + .vert_sync_pulse = 6, + .vert_border = 0, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x1D }, + .edid_std_id = 0x810F, + .cvt_id = 0x8F1844, + .horiz_video = 1280, + .vert_video = 800, + .refresh_rate_hz = 75.0, + .pixel_clock_hz = 106500000, + .horiz_blank = 416, + .horiz_front_porch = 80, + .horiz_sync_pulse = 128, + .horiz_border = 0, + .vert_blank = 38, + .vert_front_porch = 3, + .vert_sync_pulse = 6, + .vert_border = 0, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x1E }, + .edid_std_id = 0x8119, + .cvt_id = 0x8F1862, + .horiz_video = 1280, + .vert_video = 800, + .refresh_rate_hz = 85.0, + .pixel_clock_hz = 122500000, + .horiz_blank = 432, + .horiz_front_porch = 80, + .horiz_sync_pulse = 136, + .horiz_border = 0, + .vert_blank = 43, + .vert_front_porch = 3, + .vert_sync_pulse = 6, + .vert_border = 0, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x1F }, + .edid_std_id = 0, + .cvt_id = 0, + .horiz_video = 1280, + .vert_video = 800, + .refresh_rate_hz = 120.0, + .pixel_clock_hz = 146250000, + .horiz_blank = 160, + .horiz_front_porch = 48, + .horiz_sync_pulse = 32, + .horiz_border = 0, + .vert_blank = 47, + .vert_front_porch = 3, + .vert_sync_pulse = 6, + .vert_border = 0, + .reduced_blanking = true, + }, + { + .dmt_code = { .code = 0x20 }, + .edid_std_id = 0x8140, + .cvt_id = 0, + .horiz_video = 1280, + .vert_video = 960, + .refresh_rate_hz = 60.0, + .pixel_clock_hz = 108000000, + .horiz_blank = 520, + .horiz_front_porch = 96, + .horiz_sync_pulse = 112, + .horiz_border = 0, + .vert_blank = 40, + .vert_front_porch = 1, + .vert_sync_pulse = 3, + .vert_border = 0, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x21 }, + .edid_std_id = 0x8159, + .cvt_id = 0, + .horiz_video = 1280, + .vert_video = 960, + .refresh_rate_hz = 85.0, + .pixel_clock_hz = 148500000, + .horiz_blank = 448, + .horiz_front_porch = 64, + .horiz_sync_pulse = 160, + .horiz_border = 0, + .vert_blank = 51, + .vert_front_porch = 1, + .vert_sync_pulse = 3, + .vert_border = 0, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x22 }, + .edid_std_id = 0, + .cvt_id = 0, + .horiz_video = 1280, + .vert_video = 960, + .refresh_rate_hz = 120.0, + .pixel_clock_hz = 175500000, + .horiz_blank = 160, + .horiz_front_porch = 48, + .horiz_sync_pulse = 32, + .horiz_border = 0, + .vert_blank = 57, + .vert_front_porch = 3, + .vert_sync_pulse = 4, + .vert_border = 0, + .reduced_blanking = true, + }, + { + .dmt_code = { .code = 0x23 }, + .edid_std_id = 0x8180, + .cvt_id = 0, + .horiz_video = 1280, + .vert_video = 1024, + .refresh_rate_hz = 60.0, + .pixel_clock_hz = 108000000, + .horiz_blank = 408, + .horiz_front_porch = 48, + .horiz_sync_pulse = 112, + .horiz_border = 0, + .vert_blank = 42, + .vert_front_porch = 1, + .vert_sync_pulse = 3, + .vert_border = 0, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x24 }, + .edid_std_id = 0x818F, + .cvt_id = 0, + .horiz_video = 1280, + .vert_video = 1024, + .refresh_rate_hz = 75.0, + .pixel_clock_hz = 135000000, + .horiz_blank = 408, + .horiz_front_porch = 16, + .horiz_sync_pulse = 144, + .horiz_border = 0, + .vert_blank = 42, + .vert_front_porch = 1, + .vert_sync_pulse = 3, + .vert_border = 0, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x25 }, + .edid_std_id = 0x8199, + .cvt_id = 0, + .horiz_video = 1280, + .vert_video = 1024, + .refresh_rate_hz = 85.0, + .pixel_clock_hz = 157500000, + .horiz_blank = 448, + .horiz_front_porch = 64, + .horiz_sync_pulse = 160, + .horiz_border = 0, + .vert_blank = 48, + .vert_front_porch = 1, + .vert_sync_pulse = 3, + .vert_border = 0, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x26 }, + .edid_std_id = 0, + .cvt_id = 0, + .horiz_video = 1280, + .vert_video = 1024, + .refresh_rate_hz = 120.0, + .pixel_clock_hz = 187250000, + .horiz_blank = 160, + .horiz_front_porch = 48, + .horiz_sync_pulse = 32, + .horiz_border = 0, + .vert_blank = 60, + .vert_front_porch = 3, + .vert_sync_pulse = 7, + .vert_border = 0, + .reduced_blanking = true, + }, + { + .dmt_code = { .code = 0x27 }, + .edid_std_id = 0, + .cvt_id = 0, + .horiz_video = 1360, + .vert_video = 768, + .refresh_rate_hz = 60.0, + .pixel_clock_hz = 85500000, + .horiz_blank = 432, + .horiz_front_porch = 64, + .horiz_sync_pulse = 112, + .horiz_border = 0, + .vert_blank = 27, + .vert_front_porch = 3, + .vert_sync_pulse = 6, + .vert_border = 0, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x28 }, + .edid_std_id = 0, + .cvt_id = 0, + .horiz_video = 1360, + .vert_video = 768, + .refresh_rate_hz = 120.0, + .pixel_clock_hz = 148250000, + .horiz_blank = 160, + .horiz_front_porch = 48, + .horiz_sync_pulse = 32, + .horiz_border = 0, + .vert_blank = 45, + .vert_front_porch = 3, + .vert_sync_pulse = 5, + .vert_border = 0, + .reduced_blanking = true, + }, + { + .dmt_code = { .code = 0x51 }, + .edid_std_id = 0, + .cvt_id = 0, + .horiz_video = 1366, + .vert_video = 768, + .refresh_rate_hz = 60.0, + .pixel_clock_hz = 85500000, + .horiz_blank = 426, + .horiz_front_porch = 70, + .horiz_sync_pulse = 143, + .horiz_border = 0, + .vert_blank = 30, + .vert_front_porch = 3, + .vert_sync_pulse = 3, + .vert_border = 0, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x56 }, + .edid_std_id = 0, + .cvt_id = 0, + .horiz_video = 1366, + .vert_video = 768, + .refresh_rate_hz = 60.0, + .pixel_clock_hz = 72000000, + .horiz_blank = 134, + .horiz_front_porch = 14, + .horiz_sync_pulse = 56, + .horiz_border = 0, + .vert_blank = 32, + .vert_front_porch = 1, + .vert_sync_pulse = 3, + .vert_border = 0, + .reduced_blanking = true, + }, + { + .dmt_code = { .code = 0x29 }, + .edid_std_id = 0, + .cvt_id = 0x0C2021, + .horiz_video = 1400, + .vert_video = 1050, + .refresh_rate_hz = 60.0, + .pixel_clock_hz = 101000000, + .horiz_blank = 160, + .horiz_front_porch = 48, + .horiz_sync_pulse = 32, + .horiz_border = 0, + .vert_blank = 30, + .vert_front_porch = 3, + .vert_sync_pulse = 4, + .vert_border = 0, + .reduced_blanking = true, + }, + { + .dmt_code = { .code = 0x2A }, + .edid_std_id = 0x9040, + .cvt_id = 0x0C2028, + .horiz_video = 1400, + .vert_video = 1050, + .refresh_rate_hz = 60.0, + .pixel_clock_hz = 121750000, + .horiz_blank = 464, + .horiz_front_porch = 88, + .horiz_sync_pulse = 144, + .horiz_border = 0, + .vert_blank = 39, + .vert_front_porch = 3, + .vert_sync_pulse = 4, + .vert_border = 0, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x2B }, + .edid_std_id = 0x904F, + .cvt_id = 0x0C2044, + .horiz_video = 1400, + .vert_video = 1050, + .refresh_rate_hz = 75.0, + .pixel_clock_hz = 156000000, + .horiz_blank = 496, + .horiz_front_porch = 104, + .horiz_sync_pulse = 144, + .horiz_border = 0, + .vert_blank = 49, + .vert_front_porch = 3, + .vert_sync_pulse = 4, + .vert_border = 0, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x2C }, + .edid_std_id = 0x9059, + .cvt_id = 0x0C2062, + .horiz_video = 1400, + .vert_video = 1050, + .refresh_rate_hz = 85.0, + .pixel_clock_hz = 179500000, + .horiz_blank = 512, + .horiz_front_porch = 104, + .horiz_sync_pulse = 152, + .horiz_border = 0, + .vert_blank = 55, + .vert_front_porch = 3, + .vert_sync_pulse = 4, + .vert_border = 0, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x2D }, + .edid_std_id = 0, + .cvt_id = 0, + .horiz_video = 1400, + .vert_video = 1050, + .refresh_rate_hz = 120.0, + .pixel_clock_hz = 208000000, + .horiz_blank = 160, + .horiz_front_porch = 48, + .horiz_sync_pulse = 32, + .horiz_border = 0, + .vert_blank = 62, + .vert_front_porch = 3, + .vert_sync_pulse = 4, + .vert_border = 0, + .reduced_blanking = true, + }, + { + .dmt_code = { .code = 0x2E }, + .edid_std_id = 0, + .cvt_id = 0xC11821, + .horiz_video = 1440, + .vert_video = 900, + .refresh_rate_hz = 60.0, + .pixel_clock_hz = 88750000, + .horiz_blank = 160, + .horiz_front_porch = 48, + .horiz_sync_pulse = 32, + .horiz_border = 0, + .vert_blank = 26, + .vert_front_porch = 3, + .vert_sync_pulse = 6, + .vert_border = 0, + .reduced_blanking = true, + }, + { + .dmt_code = { .code = 0x2F }, + .edid_std_id = 0x9500, + .cvt_id = 0xC11828, + .horiz_video = 1440, + .vert_video = 900, + .refresh_rate_hz = 60.0, + .pixel_clock_hz = 106500000, + .horiz_blank = 464, + .horiz_front_porch = 80, + .horiz_sync_pulse = 152, + .horiz_border = 0, + .vert_blank = 34, + .vert_front_porch = 3, + .vert_sync_pulse = 6, + .vert_border = 0, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x30 }, + .edid_std_id = 0x950F, + .cvt_id = 0xC11844, + .horiz_video = 1440, + .vert_video = 900, + .refresh_rate_hz = 75.0, + .pixel_clock_hz = 136750000, + .horiz_blank = 496, + .horiz_front_porch = 96, + .horiz_sync_pulse = 152, + .horiz_border = 0, + .vert_blank = 42, + .vert_front_porch = 3, + .vert_sync_pulse = 6, + .vert_border = 0, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x31 }, + .edid_std_id = 0x9519, + .cvt_id = 0xC11868, + .horiz_video = 1440, + .vert_video = 900, + .refresh_rate_hz = 85.0, + .pixel_clock_hz = 157000000, + .horiz_blank = 512, + .horiz_front_porch = 104, + .horiz_sync_pulse = 152, + .horiz_border = 0, + .vert_blank = 48, + .vert_front_porch = 3, + .vert_sync_pulse = 6, + .vert_border = 0, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x32 }, + .edid_std_id = 0, + .cvt_id = 0, + .horiz_video = 1440, + .vert_video = 900, + .refresh_rate_hz = 120.0, + .pixel_clock_hz = 182750000, + .horiz_blank = 160, + .horiz_front_porch = 48, + .horiz_sync_pulse = 32, + .horiz_border = 0, + .vert_blank = 53, + .vert_front_porch = 3, + .vert_sync_pulse = 6, + .vert_border = 0, + .reduced_blanking = true, + }, + { + .dmt_code = { .code = 0x53 }, + .edid_std_id = 0xA9C0, + .cvt_id = 0, + .horiz_video = 1600, + .vert_video = 900, + .refresh_rate_hz = 60.0, + .pixel_clock_hz = 108000000, + .horiz_blank = 200, + .horiz_front_porch = 24, + .horiz_sync_pulse = 80, + .horiz_border = 0, + .vert_blank = 100, + .vert_front_porch = 1, + .vert_sync_pulse = 3, + .vert_border = 0, + .reduced_blanking = true, + }, + { + .dmt_code = { .code = 0x33 }, + .edid_std_id = 0xA940, + .cvt_id = 0, + .horiz_video = 1600, + .vert_video = 1200, + .refresh_rate_hz = 60.0, + .pixel_clock_hz = 162000000, + .horiz_blank = 560, + .horiz_front_porch = 64, + .horiz_sync_pulse = 192, + .horiz_border = 0, + .vert_blank = 50, + .vert_front_porch = 1, + .vert_sync_pulse = 3, + .vert_border = 0, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x34 }, + .edid_std_id = 0xA945, + .cvt_id = 0, + .horiz_video = 1600, + .vert_video = 1200, + .refresh_rate_hz = 65.0, + .pixel_clock_hz = 175500000, + .horiz_blank = 560, + .horiz_front_porch = 64, + .horiz_sync_pulse = 192, + .horiz_border = 0, + .vert_blank = 50, + .vert_front_porch = 1, + .vert_sync_pulse = 3, + .vert_border = 0, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x35 }, + .edid_std_id = 0xA94A, + .cvt_id = 0, + .horiz_video = 1600, + .vert_video = 1200, + .refresh_rate_hz = 70.0, + .pixel_clock_hz = 189000000, + .horiz_blank = 560, + .horiz_front_porch = 64, + .horiz_sync_pulse = 192, + .horiz_border = 0, + .vert_blank = 50, + .vert_front_porch = 1, + .vert_sync_pulse = 3, + .vert_border = 0, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x36 }, + .edid_std_id = 0xA94F, + .cvt_id = 0, + .horiz_video = 1600, + .vert_video = 1200, + .refresh_rate_hz = 75.0, + .pixel_clock_hz = 202500000, + .horiz_blank = 560, + .horiz_front_porch = 64, + .horiz_sync_pulse = 192, + .horiz_border = 0, + .vert_blank = 50, + .vert_front_porch = 1, + .vert_sync_pulse = 3, + .vert_border = 0, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x37 }, + .edid_std_id = 0xA959, + .cvt_id = 0, + .horiz_video = 1600, + .vert_video = 1200, + .refresh_rate_hz = 85.0, + .pixel_clock_hz = 229500000, + .horiz_blank = 560, + .horiz_front_porch = 64, + .horiz_sync_pulse = 192, + .horiz_border = 0, + .vert_blank = 50, + .vert_front_porch = 1, + .vert_sync_pulse = 3, + .vert_border = 0, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x38 }, + .edid_std_id = 0, + .cvt_id = 0, + .horiz_video = 1600, + .vert_video = 1200, + .refresh_rate_hz = 120.0, + .pixel_clock_hz = 268250000, + .horiz_blank = 160, + .horiz_front_porch = 48, + .horiz_sync_pulse = 32, + .horiz_border = 0, + .vert_blank = 71, + .vert_front_porch = 3, + .vert_sync_pulse = 4, + .vert_border = 0, + .reduced_blanking = true, + }, + { + .dmt_code = { .code = 0x39 }, + .edid_std_id = 0, + .cvt_id = 0x0C2821, + .horiz_video = 1680, + .vert_video = 1050, + .refresh_rate_hz = 60.0, + .pixel_clock_hz = 119000000, + .horiz_blank = 160, + .horiz_front_porch = 48, + .horiz_sync_pulse = 32, + .horiz_border = 0, + .vert_blank = 30, + .vert_front_porch = 3, + .vert_sync_pulse = 6, + .vert_border = 0, + .reduced_blanking = true, + }, + { + .dmt_code = { .code = 0x3A }, + .edid_std_id = 0xB300, + .cvt_id = 0x0C2828, + .horiz_video = 1680, + .vert_video = 1050, + .refresh_rate_hz = 60.0, + .pixel_clock_hz = 146250000, + .horiz_blank = 560, + .horiz_front_porch = 104, + .horiz_sync_pulse = 176, + .horiz_border = 0, + .vert_blank = 39, + .vert_front_porch = 3, + .vert_sync_pulse = 6, + .vert_border = 0, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x3B }, + .edid_std_id = 0xB30F, + .cvt_id = 0x0C2844, + .horiz_video = 1680, + .vert_video = 1050, + .refresh_rate_hz = 75.0, + .pixel_clock_hz = 187000000, + .horiz_blank = 592, + .horiz_front_porch = 120, + .horiz_sync_pulse = 176, + .horiz_border = 0, + .vert_blank = 49, + .vert_front_porch = 3, + .vert_sync_pulse = 6, + .vert_border = 0, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x3C }, + .edid_std_id = 0xB319, + .cvt_id = 0x0C2868, + .horiz_video = 1680, + .vert_video = 1050, + .refresh_rate_hz = 85.0, + .pixel_clock_hz = 214750000, + .horiz_blank = 608, + .horiz_front_porch = 128, + .horiz_sync_pulse = 176, + .horiz_border = 0, + .vert_blank = 55, + .vert_front_porch = 3, + .vert_sync_pulse = 6, + .vert_border = 0, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x3D }, + .edid_std_id = 0, + .cvt_id = 0, + .horiz_video = 1680, + .vert_video = 1050, + .refresh_rate_hz = 120.0, + .pixel_clock_hz = 245500000, + .horiz_blank = 160, + .horiz_front_porch = 48, + .horiz_sync_pulse = 32, + .horiz_border = 0, + .vert_blank = 62, + .vert_front_porch = 3, + .vert_sync_pulse = 6, + .vert_border = 0, + .reduced_blanking = true, + }, + { + .dmt_code = { .code = 0x3E }, + .edid_std_id = 0xC140, + .cvt_id = 0, + .horiz_video = 1792, + .vert_video = 1344, + .refresh_rate_hz = 60.0, + .pixel_clock_hz = 204750000, + .horiz_blank = 656, + .horiz_front_porch = 128, + .horiz_sync_pulse = 200, + .horiz_border = 0, + .vert_blank = 50, + .vert_front_porch = 1, + .vert_sync_pulse = 3, + .vert_border = 0, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x3F }, + .edid_std_id = 0xC14F, + .cvt_id = 0, + .horiz_video = 1792, + .vert_video = 1344, + .refresh_rate_hz = 75.0, + .pixel_clock_hz = 261000000, + .horiz_blank = 664, + .horiz_front_porch = 96, + .horiz_sync_pulse = 216, + .horiz_border = 0, + .vert_blank = 73, + .vert_front_porch = 1, + .vert_sync_pulse = 3, + .vert_border = 0, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x40 }, + .edid_std_id = 0, + .cvt_id = 0, + .horiz_video = 1792, + .vert_video = 1344, + .refresh_rate_hz = 120.0, + .pixel_clock_hz = 333250000, + .horiz_blank = 160, + .horiz_front_porch = 48, + .horiz_sync_pulse = 32, + .horiz_border = 0, + .vert_blank = 79, + .vert_front_porch = 3, + .vert_sync_pulse = 4, + .vert_border = 0, + .reduced_blanking = true, + }, + { + .dmt_code = { .code = 0x41 }, + .edid_std_id = 0xC940, + .cvt_id = 0, + .horiz_video = 1856, + .vert_video = 1392, + .refresh_rate_hz = 60.0, + .pixel_clock_hz = 218250000, + .horiz_blank = 672, + .horiz_front_porch = 96, + .horiz_sync_pulse = 224, + .horiz_border = 0, + .vert_blank = 47, + .vert_front_porch = 1, + .vert_sync_pulse = 3, + .vert_border = 0, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x42 }, + .edid_std_id = 0xC94F, + .cvt_id = 0, + .horiz_video = 1856, + .vert_video = 1392, + .refresh_rate_hz = 75.0, + .pixel_clock_hz = 288000000, + .horiz_blank = 704, + .horiz_front_porch = 128, + .horiz_sync_pulse = 224, + .horiz_border = 0, + .vert_blank = 108, + .vert_front_porch = 1, + .vert_sync_pulse = 3, + .vert_border = 0, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x43 }, + .edid_std_id = 0, + .cvt_id = 0, + .horiz_video = 1856, + .vert_video = 1392, + .refresh_rate_hz = 120.0, + .pixel_clock_hz = 356500000, + .horiz_blank = 160, + .horiz_front_porch = 48, + .horiz_sync_pulse = 32, + .horiz_border = 0, + .vert_blank = 82, + .vert_front_porch = 3, + .vert_sync_pulse = 4, + .vert_border = 0, + .reduced_blanking = true, + }, + { + .dmt_code = { .code = 0x52 }, + .edid_std_id = 0xD1C0, + .cvt_id = 0, + .horiz_video = 1920, + .vert_video = 1080, + .refresh_rate_hz = 60.0, + .pixel_clock_hz = 148500000, + .horiz_blank = 280, + .horiz_front_porch = 88, + .horiz_sync_pulse = 44, + .horiz_border = 0, + .vert_blank = 45, + .vert_front_porch = 4, + .vert_sync_pulse = 5, + .vert_border = 0, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x44 }, + .edid_std_id = 0, + .cvt_id = 0x572821, + .horiz_video = 1920, + .vert_video = 1200, + .refresh_rate_hz = 60.0, + .pixel_clock_hz = 154000000, + .horiz_blank = 160, + .horiz_front_porch = 48, + .horiz_sync_pulse = 32, + .horiz_border = 0, + .vert_blank = 35, + .vert_front_porch = 3, + .vert_sync_pulse = 6, + .vert_border = 0, + .reduced_blanking = true, + }, + { + .dmt_code = { .code = 0x45 }, + .edid_std_id = 0xD100, + .cvt_id = 0x572828, + .horiz_video = 1920, + .vert_video = 1200, + .refresh_rate_hz = 60.0, + .pixel_clock_hz = 193250000, + .horiz_blank = 672, + .horiz_front_porch = 136, + .horiz_sync_pulse = 200, + .horiz_border = 0, + .vert_blank = 45, + .vert_front_porch = 3, + .vert_sync_pulse = 6, + .vert_border = 0, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x46 }, + .edid_std_id = 0xD10F, + .cvt_id = 0x572844, + .horiz_video = 1920, + .vert_video = 1200, + .refresh_rate_hz = 75.0, + .pixel_clock_hz = 245250000, + .horiz_blank = 688, + .horiz_front_porch = 136, + .horiz_sync_pulse = 208, + .horiz_border = 0, + .vert_blank = 55, + .vert_front_porch = 3, + .vert_sync_pulse = 6, + .vert_border = 0, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x47 }, + .edid_std_id = 0xD119, + .cvt_id = 0x572862, + .horiz_video = 1920, + .vert_video = 1200, + .refresh_rate_hz = 85.0, + .pixel_clock_hz = 281250000, + .horiz_blank = 704, + .horiz_front_porch = 144, + .horiz_sync_pulse = 208, + .horiz_border = 0, + .vert_blank = 62, + .vert_front_porch = 3, + .vert_sync_pulse = 6, + .vert_border = 0, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x48 }, + .edid_std_id = 0, + .cvt_id = 0, + .horiz_video = 1920, + .vert_video = 1200, + .refresh_rate_hz = 120.0, + .pixel_clock_hz = 317000000, + .horiz_blank = 160, + .horiz_front_porch = 48, + .horiz_sync_pulse = 32, + .horiz_border = 0, + .vert_blank = 71, + .vert_front_porch = 3, + .vert_sync_pulse = 6, + .vert_border = 0, + .reduced_blanking = true, + }, + { + .dmt_code = { .code = 0x49 }, + .edid_std_id = 0xD140, + .cvt_id = 0, + .horiz_video = 1920, + .vert_video = 1440, + .refresh_rate_hz = 60.0, + .pixel_clock_hz = 234000000, + .horiz_blank = 680, + .horiz_front_porch = 128, + .horiz_sync_pulse = 208, + .horiz_border = 0, + .vert_blank = 60, + .vert_front_porch = 1, + .vert_sync_pulse = 3, + .vert_border = 0, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x4A }, + .edid_std_id = 0xD14F, + .cvt_id = 0, + .horiz_video = 1920, + .vert_video = 1440, + .refresh_rate_hz = 75.0, + .pixel_clock_hz = 297000000, + .horiz_blank = 720, + .horiz_front_porch = 144, + .horiz_sync_pulse = 224, + .horiz_border = 0, + .vert_blank = 60, + .vert_front_porch = 1, + .vert_sync_pulse = 3, + .vert_border = 0, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x4B }, + .edid_std_id = 0, + .cvt_id = 0, + .horiz_video = 1920, + .vert_video = 1440, + .refresh_rate_hz = 120.0, + .pixel_clock_hz = 380500000, + .horiz_blank = 160, + .horiz_front_porch = 48, + .horiz_sync_pulse = 32, + .horiz_border = 0, + .vert_blank = 85, + .vert_front_porch = 3, + .vert_sync_pulse = 4, + .vert_border = 0, + .reduced_blanking = true, + }, + { + .dmt_code = { .code = 0x54 }, + .edid_std_id = 0xE1C0, + .cvt_id = 0, + .horiz_video = 2048, + .vert_video = 1152, + .refresh_rate_hz = 60.0, + .pixel_clock_hz = 162000000, + .horiz_blank = 202, + .horiz_front_porch = 26, + .horiz_sync_pulse = 80, + .horiz_border = 0, + .vert_blank = 48, + .vert_front_porch = 1, + .vert_sync_pulse = 3, + .vert_border = 0, + .reduced_blanking = true, + }, + { + .dmt_code = { .code = 0x4C }, + .edid_std_id = 0, + .cvt_id = 0x1F3821, + .horiz_video = 2560, + .vert_video = 1600, + .refresh_rate_hz = 60.0, + .pixel_clock_hz = 268500000, + .horiz_blank = 160, + .horiz_front_porch = 48, + .horiz_sync_pulse = 32, + .horiz_border = 0, + .vert_blank = 46, + .vert_front_porch = 3, + .vert_sync_pulse = 6, + .vert_border = 0, + .reduced_blanking = true, + }, + { + .dmt_code = { .code = 0x4D }, + .edid_std_id = 0, + .cvt_id = 0x1F3828, + .horiz_video = 2560, + .vert_video = 1600, + .refresh_rate_hz = 60.0, + .pixel_clock_hz = 348500000, + .horiz_blank = 944, + .horiz_front_porch = 192, + .horiz_sync_pulse = 280, + .horiz_border = 0, + .vert_blank = 58, + .vert_front_porch = 3, + .vert_sync_pulse = 6, + .vert_border = 0, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x4E }, + .edid_std_id = 0, + .cvt_id = 0x1F3844, + .horiz_video = 2560, + .vert_video = 1600, + .refresh_rate_hz = 75.0, + .pixel_clock_hz = 443250000, + .horiz_blank = 976, + .horiz_front_porch = 208, + .horiz_sync_pulse = 280, + .horiz_border = 0, + .vert_blank = 72, + .vert_front_porch = 3, + .vert_sync_pulse = 6, + .vert_border = 0, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x4F }, + .edid_std_id = 0, + .cvt_id = 0x1F3862, + .horiz_video = 2560, + .vert_video = 1600, + .refresh_rate_hz = 85.0, + .pixel_clock_hz = 505250000, + .horiz_blank = 976, + .horiz_front_porch = 208, + .horiz_sync_pulse = 280, + .horiz_border = 0, + .vert_blank = 82, + .vert_front_porch = 3, + .vert_sync_pulse = 6, + .vert_border = 0, + .reduced_blanking = false, + }, + { + .dmt_code = { .code = 0x50 }, + .edid_std_id = 0, + .cvt_id = 0, + .horiz_video = 2560, + .vert_video = 1600, + .refresh_rate_hz = 120.0, + .pixel_clock_hz = 552750000, + .horiz_blank = 160, + .horiz_front_porch = 48, + .horiz_sync_pulse = 32, + .horiz_border = 0, + .vert_blank = 94, + .vert_front_porch = 3, + .vert_sync_pulse = 6, + .vert_border = 0, + .reduced_blanking = true, + }, +}; + +const size_t _di_dmt_timings_len = 86; diff --git a/local/recipes/libs/libdisplay-info/source/dmt.c b/local/recipes/libs/libdisplay-info/source/dmt.c new file mode 100644 index 0000000000..f8f3a2a037 --- /dev/null +++ b/local/recipes/libs/libdisplay-info/source/dmt.c @@ -0,0 +1,73 @@ +#include +#include + +#include +#include + +extern const struct di_dmt_timing _di_dmt_timings[]; +extern const size_t _di_dmt_timings_len; + +const struct di_dmt_timing * +di_dmt_timing_from_code(struct di_dmt_code dmt_code) +{ + size_t i; + + for (i = 0; i < _di_dmt_timings_len; i++) { + const struct di_dmt_timing *t = &_di_dmt_timings[i]; + + if (t->dmt_code.code == dmt_code.code) + return t; + } + + return NULL; +} + +struct di_dmt_code +di_dmt_timing_to_code(const struct di_dmt_timing *timing) +{ + size_t i; + + for (i = 0; i < _di_dmt_timings_len; i++) { + const struct di_dmt_timing *t = &_di_dmt_timings[i]; + + if (t->horiz_video == timing->horiz_video && + t->vert_video == timing->vert_video && + fabs(t->refresh_rate_hz - timing->refresh_rate_hz) <= FLT_EPSILON && + t->pixel_clock_hz == timing->pixel_clock_hz && + t->horiz_blank == timing->horiz_blank && + t->vert_blank == timing->vert_blank && + t->horiz_front_porch == timing->horiz_front_porch && + t->vert_front_porch == timing->vert_front_porch && + t->horiz_sync_pulse == timing->horiz_sync_pulse && + t->vert_sync_pulse == timing->vert_sync_pulse && + t->horiz_border == timing->horiz_border && + t->vert_border == timing->vert_border && + t->reduced_blanking == timing->reduced_blanking) + return t->dmt_code; + } + + return (struct di_dmt_code) { .code = 0 }; +} + +const struct di_dmt_timing * +di_dmt_timing_from_edid_standard_timing(const struct di_edid_standard_timing *t) +{ + int32_t vert_video; + size_t i; + const struct di_dmt_timing *dmt; + + vert_video = di_edid_standard_timing_get_vert_video(t); + + for (i = 0; i < _di_dmt_timings_len; i++) { + dmt = &_di_dmt_timings[i]; + + if (dmt->horiz_video == t->horiz_video + && dmt->vert_video == vert_video + && dmt->refresh_rate_hz == (float)t->refresh_rate_hz + && dmt->edid_std_id != 0) { + return dmt; + } + } + + return 0; +} diff --git a/local/recipes/libs/libdisplay-info/source/edid.c b/local/recipes/libs/libdisplay-info/source/edid.c new file mode 100644 index 0000000000..c6d21a82b9 --- /dev/null +++ b/local/recipes/libs/libdisplay-info/source/edid.c @@ -0,0 +1,1627 @@ +#include +#include +#include +#include +#include + +#include + +#include "bits.h" +#include "edid.h" +#include "log.h" + +/** + * The size of an EDID block, defined in section 2.2. + */ +#define EDID_BLOCK_SIZE 128 +/** + * The size of an EDID standard timing, defined in section 3.9. + */ +#define EDID_STANDARD_TIMING_SIZE 2 +/** + * The size of an EDID CVT timing code, defined in section 3.10.3.8. + */ +#define EDID_CVT_TIMING_CODE_SIZE 3 + +/** + * Fixed EDID header, defined in section 3.1. + */ +static const uint8_t header[] = { 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00 }; + +static void +destroy_display_descriptor(struct di_edid_display_descriptor *desc); + +static void +add_failure(struct di_edid *edid, const char fmt[], ...) +{ + va_list args; + + va_start(args, fmt); + _di_logger_va_add_failure(edid->logger, fmt, args); + va_end(args); +} + +static void +add_failure_until(struct di_edid *edid, int revision, const char fmt[], ...) +{ + va_list args; + + if (edid->revision > revision) { + return; + } + + va_start(args, fmt); + _di_logger_va_add_failure(edid->logger, fmt, args); + va_end(args); +} + +static void +parse_version_revision(const uint8_t data[static EDID_BLOCK_SIZE], + int *version, int *revision) +{ + *version = (int) data[0x12]; + *revision = (int) data[0x13]; +} + +static size_t +parse_ext_count(const uint8_t data[static EDID_BLOCK_SIZE]) +{ + return data[0x7E]; +} + +static bool +validate_block_checksum(const uint8_t data[static EDID_BLOCK_SIZE]) +{ + uint8_t sum = 0; + size_t i; + + for (i = 0; i < EDID_BLOCK_SIZE; i++) { + sum += data[i]; + } + + return sum == 0; +} + +static void +parse_vendor_product(struct di_edid *edid, + const uint8_t data[static EDID_BLOCK_SIZE]) +{ + struct di_edid_vendor_product *out = &edid->vendor_product; + uint16_t man, raw_week, raw_year; + int year = 0; + + /* The ASCII 3-letter manufacturer code is encoded in 5-bit codes. */ + man = (uint16_t) ((data[0x08] << 8) | data[0x09]); + out->manufacturer[0] = ((man >> 10) & 0x1F) + '@'; + out->manufacturer[1] = ((man >> 5) & 0x1F) + '@'; + out->manufacturer[2] = ((man >> 0) & 0x1F) + '@'; + + out->product = (uint16_t) ((uint16_t)data[0x0A] | + ((uint16_t)data[0x0B] << 8)); + out->serial = (uint32_t) ((uint32_t)data[0x0C] | + ((uint32_t)data[0x0D] << 8) | + ((uint32_t)data[0x0E] << 16) | + ((uint32_t)data[0x0F] << 24)); + + raw_week = data[0x10]; + raw_year = data[0x11]; + + if (raw_year >= 0x10 || edid->revision < 4) { + year = data[0x11] + 1990; + } else if (edid->revision == 4) { + add_failure(edid, "Year set to reserved value."); + } + + if (raw_week == 0xFF) { + /* Special flag for model year */ + out->model_year = year; + } else { + out->manufacture_year = year; + if (raw_week > 54) { + add_failure_until(edid, 4, + "Invalid week %u of manufacture.", + raw_week); + } else if (raw_week > 0) { + out->manufacture_week = raw_week; + } + } +} + +static void +parse_video_input_digital(struct di_edid *edid, uint8_t video_input) +{ + uint8_t color_bit_depth, interface; + struct di_edid_video_input_digital *digital = &edid->video_input_digital; + + if (edid->revision < 2) { + if (get_bit_range(video_input, 6, 0) != 0) + add_failure(edid, "Digital Video Interface Standard set to reserved value 0x%02x.", + video_input); + return; + } + if (edid->revision < 4) { + if (get_bit_range(video_input, 6, 1) != 0) + add_failure(edid, "Digital Video Interface Standard set to reserved value 0x%02x.", + video_input); + digital->dfp1 = has_bit(video_input, 0); + return; + } + + color_bit_depth = get_bit_range(video_input, 6, 4); + if (color_bit_depth == 0x07) { + /* Reserved */ + add_failure_until(edid, 4, "Color Bit Depth set to reserved value."); + } else if (color_bit_depth != 0) { + digital->color_bit_depth = 2 * color_bit_depth + 4; + } + + interface = get_bit_range(video_input, 3, 0); + switch (interface) { + case DI_EDID_VIDEO_INPUT_DIGITAL_UNDEFINED: + case DI_EDID_VIDEO_INPUT_DIGITAL_DVI: + case DI_EDID_VIDEO_INPUT_DIGITAL_HDMI_A: + case DI_EDID_VIDEO_INPUT_DIGITAL_HDMI_B: + case DI_EDID_VIDEO_INPUT_DIGITAL_MDDI: + case DI_EDID_VIDEO_INPUT_DIGITAL_DISPLAYPORT: + digital->interface = interface; + break; + default: + add_failure_until(edid, 4, + "Digital Video Interface Standard set to reserved value 0x%02x.", + interface); + digital->interface = DI_EDID_VIDEO_INPUT_DIGITAL_UNDEFINED; + break; + } +} + +static void +parse_video_input_analog(struct di_edid *edid, uint8_t video_input) +{ + struct di_edid_video_input_analog *analog = &edid->video_input_analog; + + analog->signal_level_std = get_bit_range(video_input, 6, 5); + analog->video_setup = has_bit(video_input, 4); + analog->sync_separate = has_bit(video_input, 3); + analog->sync_composite = has_bit(video_input, 2); + analog->sync_on_green = has_bit(video_input, 1); + analog->sync_serrations = has_bit(video_input, 0); +} + +static void +parse_basic_params_features(struct di_edid *edid, + const uint8_t data[static EDID_BLOCK_SIZE]) +{ + uint8_t video_input, width, height, features; + struct di_edid_screen_size *screen_size = &edid->screen_size; + + video_input = data[0x14]; + edid->is_digital = has_bit(video_input, 7); + + if (edid->is_digital) { + parse_video_input_digital(edid, video_input); + } else { + parse_video_input_analog(edid, video_input); + } + + /* v1.3 says screen size is undefined if either byte is zero, v1.4 says + * screen size and aspect ratio are undefined if both bytes are zero and + * encodes the aspect ratio if either byte is zero. */ + width = data[0x15]; + height = data[0x16]; + if (width > 0 && height > 0) { + screen_size->width_cm = width; + screen_size->height_cm = height; + } else if (edid->revision >= 4) { + if (width > 0) { + screen_size->landscape_aspect_ratio = ((float) width + 99) / 100; + } else if (height > 0) { + screen_size->portait_aspect_ratio = ((float) height + 99) / 100; + } + } + + if (data[0x17] != 0xFF) { + edid->gamma = ((float) data[0x17] + 100) / 100; + } else { + edid->gamma = 0; + } + + features = data[0x18]; + + edid->dpms.standby = has_bit(features, 7); + edid->dpms.suspend = has_bit(features, 6); + edid->dpms.off = has_bit(features, 5); + + if (edid->is_digital && edid->revision >= 4) { + edid->color_encoding_formats.rgb444 = true; + edid->color_encoding_formats.ycrcb444 = has_bit(features, 3); + edid->color_encoding_formats.ycrcb422 = has_bit(features, 4); + edid->display_color_type = DI_EDID_DISPLAY_COLOR_UNDEFINED; + } else { + edid->display_color_type = get_bit_range(features, 4, 3); + } + + if (edid->revision >= 4) { + edid->misc_features.has_preferred_timing = true; + edid->misc_features.continuous_freq = has_bit(features, 0); + edid->misc_features.preferred_timing_is_native = has_bit(features, 1); + } else { + edid->misc_features.default_gtf = has_bit(features, 0); + edid->misc_features.has_preferred_timing = has_bit(features, 1); + } + edid->misc_features.srgb_is_primary = has_bit(features, 2); +} + +static float +decode_chromaticity_coord(uint8_t hi, uint8_t lo) +{ + uint16_t raw; /* only 10 bits are used */ + + raw = (uint16_t) ((hi << 2) | lo); + return (float) raw / 1024; +} + +static void +parse_chromaticity_coords(struct di_edid *edid, + const uint8_t data[static EDID_BLOCK_SIZE]) +{ + uint8_t lo; + bool all_set, any_set; + struct di_edid_chromaticity_coords *coords; + + coords = &edid->chromaticity_coords; + + lo = data[0x19]; + coords->red_x = decode_chromaticity_coord(data[0x1B], get_bit_range(lo, 7, 6)); + coords->red_y = decode_chromaticity_coord(data[0x1C], get_bit_range(lo, 5, 4)); + coords->green_x = decode_chromaticity_coord(data[0x1D], get_bit_range(lo, 3, 2)); + coords->green_y = decode_chromaticity_coord(data[0x1E], get_bit_range(lo, 1, 0)); + + lo = data[0x1A]; + coords->blue_x = decode_chromaticity_coord(data[0x1F], get_bit_range(lo, 7, 6)); + coords->blue_y = decode_chromaticity_coord(data[0x20], get_bit_range(lo, 5, 4)); + coords->white_x = decode_chromaticity_coord(data[0x21], get_bit_range(lo, 3, 2)); + coords->white_y = decode_chromaticity_coord(data[0x22], get_bit_range(lo, 1, 0)); + + /* Either all primaries coords must be set, either none must be set */ + any_set = coords->red_x != 0 || coords->red_y != 0 + || coords->green_x != 0 || coords->green_y != 0 + || coords->blue_x != 0 || coords->blue_y != 0; + all_set = coords->red_x != 0 && coords->red_y != 0 + && coords->green_x != 0 && coords->green_y != 0 + && coords->blue_x != 0 && coords->blue_y != 0; + if (any_set && !all_set) { + add_failure(edid, "Some but not all primaries coordinates are unset."); + } + + /* Both white-point coords must be set */ + if (coords->white_x == 0 || coords->white_y == 0) { + add_failure(edid, "White-point coordinates are unset."); + } +} + +static void +parse_established_timings_i_ii(struct di_edid *edid, + const uint8_t data[static EDID_BLOCK_SIZE]) +{ + struct di_edid_established_timings_i_ii *timings = &edid->established_timings_i_ii; + + timings->has_720x400_70hz = has_bit(data[0x23], 7); + timings->has_720x400_88hz = has_bit(data[0x23], 6); + timings->has_640x480_60hz = has_bit(data[0x23], 5); + timings->has_640x480_67hz = has_bit(data[0x23], 4); + timings->has_640x480_72hz = has_bit(data[0x23], 3); + timings->has_640x480_75hz = has_bit(data[0x23], 2); + timings->has_800x600_56hz = has_bit(data[0x23], 1); + timings->has_800x600_60hz = has_bit(data[0x23], 0); + + /* Established timings II */ + timings->has_800x600_72hz = has_bit(data[0x24], 7); + timings->has_800x600_75hz = has_bit(data[0x24], 6); + timings->has_832x624_75hz = has_bit(data[0x24], 5); + timings->has_1024x768_87hz_interlaced = has_bit(data[0x24], 4); + timings->has_1024x768_60hz = has_bit(data[0x24], 3); + timings->has_1024x768_70hz = has_bit(data[0x24], 2); + timings->has_1024x768_75hz = has_bit(data[0x24], 1); + timings->has_1280x1024_75hz = has_bit(data[0x24], 0); + + timings->has_1152x870_75hz = has_bit(data[0x25], 7); + /* TODO: manufacturer specified timings in bits 6:0 */ +} + +static bool +parse_standard_timing(struct di_edid *edid, + const uint8_t data[static EDID_STANDARD_TIMING_SIZE], + struct di_edid_standard_timing **out) +{ + struct di_edid_standard_timing *t; + + *out = NULL; + + if (data[0] == 0x01 && data[1] == 0x01) { + /* Unused */ + return true; + } + if (data[0] == 0x00) { + add_failure_until(edid, 4, + "Use 0x0101 as the invalid Standard Timings code, not 0x%02x%02x.", + data[0], data[1]); + return true; + } + + t = calloc(1, sizeof(*t)); + if (!t) { + return false; + } + + t->horiz_video = ((int32_t) data[0] + 31) * 8; + t->aspect_ratio = get_bit_range(data[1], 7, 6); + t->refresh_rate_hz = (int32_t) get_bit_range(data[1], 5, 0) + 60; + + *out = t; + return true; +} + +struct di_edid_detailed_timing_def_priv * +_di_edid_parse_detailed_timing_def(const uint8_t data[static EDID_BYTE_DESCRIPTOR_SIZE]) +{ + struct di_edid_detailed_timing_def_priv *priv; + struct di_edid_detailed_timing_def *def; + struct di_edid_detailed_timing_analog_composite *analog_composite; + struct di_edid_detailed_timing_bipolar_analog_composite *bipolar_analog_composite; + struct di_edid_detailed_timing_digital_composite *digital_composite; + struct di_edid_detailed_timing_digital_separate *digital_separate; + int raw; + uint8_t flags, stereo_hi, stereo_lo; + + priv = calloc(1, sizeof(*priv)); + if (!priv) { + return NULL; + } + + def = &priv->base; + + raw = (data[1] << 8) | data[0]; + def->pixel_clock_hz = raw * 10 * 1000; + + def->horiz_video = (get_bit_range(data[4], 7, 4) << 8) | data[2]; + def->horiz_blank = (get_bit_range(data[4], 3, 0) << 8) | data[3]; + + def->vert_video = (get_bit_range(data[7], 7, 4) << 8) | data[5]; + def->vert_blank = (get_bit_range(data[7], 3, 0) << 8) | data[6]; + + def->horiz_front_porch = (get_bit_range(data[11], 7, 6) << 8) | data[8]; + def->horiz_sync_pulse = (get_bit_range(data[11], 5, 4) << 8) | data[9]; + def->vert_front_porch = (get_bit_range(data[11], 3, 2) << 4) + | get_bit_range(data[10], 7, 4); + def->vert_sync_pulse = (get_bit_range(data[11], 1, 0) << 4) + | get_bit_range(data[10], 3, 0); + + def->horiz_image_mm = (get_bit_range(data[14], 7, 4) << 8) | data[12]; + def->vert_image_mm = (get_bit_range(data[14], 3, 0) << 8) | data[13]; + if ((def->horiz_image_mm == 16 && def->vert_image_mm == 9) + || (def->horiz_image_mm == 4 && def->vert_image_mm == 3)) { + /* Table 3.21 note 18.2: these are special cases and define the + * aspect ratio rather than the size in mm. + * TODO: expose these values */ + def->horiz_image_mm = def->vert_image_mm = 0; + } + + def->horiz_border = data[15]; + def->vert_border = data[16]; + + flags = data[17]; + + def->interlaced = has_bit(flags, 7); + + stereo_hi = get_bit_range(flags, 6, 5); + stereo_lo = get_bit_range(flags, 0, 0); + if (stereo_hi == 0) { + def->stereo = DI_EDID_DETAILED_TIMING_DEF_STEREO_NONE; + } else { + switch ((stereo_hi << 1) | stereo_lo) { + case (1 << 1) | 0: + def->stereo = DI_EDID_DETAILED_TIMING_DEF_STEREO_FIELD_SEQ_RIGHT; + break; + case (2 << 1) | 0: + def->stereo = DI_EDID_DETAILED_TIMING_DEF_STEREO_FIELD_SEQ_LEFT; + break; + case (1 << 1) | 1: + def->stereo = DI_EDID_DETAILED_TIMING_DEF_STEREO_2_WAY_INTERLEAVED_RIGHT; + break; + case (2 << 1) | 1: + def->stereo = DI_EDID_DETAILED_TIMING_DEF_STEREO_2_WAY_INTERLEAVED_LEFT; + break; + case (3 << 1) | 0: + def->stereo = DI_EDID_DETAILED_TIMING_DEF_STEREO_4_WAY_INTERLEAVED; + break; + case (3 << 1) | 1: + def->stereo = DI_EDID_DETAILED_TIMING_DEF_STEREO_SIDE_BY_SIDE_INTERLEAVED; + break; + default: + abort(); /* unreachable */ + } + } + + def->signal_type = get_bit_range(flags, 4, 3); + + switch (def->signal_type) { + case DI_EDID_DETAILED_TIMING_DEF_SIGNAL_ANALOG_COMPOSITE: + analog_composite = &priv->analog_composite; + analog_composite->sync_serrations = has_bit(flags, 2); + analog_composite->sync_on_green = !has_bit(flags, 1); + def->analog_composite = analog_composite; + break; + case DI_EDID_DETAILED_TIMING_DEF_SIGNAL_BIPOLAR_ANALOG_COMPOSITE: + bipolar_analog_composite = &priv->bipolar_analog_composite; + bipolar_analog_composite->sync_serrations = has_bit(flags, 2); + bipolar_analog_composite->sync_on_green = !has_bit(flags, 1); + def->bipolar_analog_composite = bipolar_analog_composite; + break; + case DI_EDID_DETAILED_TIMING_DEF_SIGNAL_DIGITAL_COMPOSITE: + digital_composite = &priv->digital_composite; + digital_composite->sync_serrations = has_bit(flags, 2); + digital_composite->sync_horiz_polarity = has_bit(flags, 1); + def->digital_composite = digital_composite; + break; + case DI_EDID_DETAILED_TIMING_DEF_SIGNAL_DIGITAL_SEPARATE: + digital_separate = &priv->digital_separate; + digital_separate->sync_vert_polarity = has_bit(flags, 2); + digital_separate->sync_horiz_polarity = has_bit(flags, 1); + def->digital_separate = digital_separate; + break; + } + + return priv; +} + +static bool +decode_display_range_limits_offset(struct di_edid *edid, uint8_t flags, + int *max_offset, int *min_offset) +{ + switch (flags) { + case 0x00: + /* No offset */ + break; + case 0x02: + *max_offset = 255; + break; + case 0x03: + *max_offset = 255; + *min_offset = 255; + break; + default: + add_failure_until(edid, 4, + "Range offset flags set to reserved value 0x%02x.", + flags); + return false; + } + + return true; +} + +static bool +parse_display_range_limits(struct di_edid *edid, + const uint8_t data[static EDID_BYTE_DESCRIPTOR_SIZE], + struct di_edid_display_range_limits_priv *priv) +{ + uint8_t offset_flags, vert_offset_flags, horiz_offset_flags; + uint8_t support_flags, preferred_aspect_ratio; + int max_vert_offset = 0, min_vert_offset = 0; + int max_horiz_offset = 0, min_horiz_offset = 0; + size_t i; + struct di_edid_display_range_limits *base; + struct di_edid_display_range_limits_secondary_gtf *secondary_gtf; + struct di_edid_display_range_limits_cvt *cvt; + + base = &priv->base; + + offset_flags = data[4]; + if (edid->revision >= 4) { + vert_offset_flags = get_bit_range(offset_flags, 1, 0); + horiz_offset_flags = get_bit_range(offset_flags, 3, 2); + + if (!decode_display_range_limits_offset(edid, + vert_offset_flags, + &max_vert_offset, + &min_vert_offset)) { + return false; + } + if (!decode_display_range_limits_offset(edid, + horiz_offset_flags, + &max_horiz_offset, + &min_horiz_offset)) { + return false; + } + + if (edid->revision <= 4 && + get_bit_range(offset_flags, 7, 4) != 0) { + add_failure(edid, "Display Range Limits: Bits 7:4 of the range offset flags are reserved."); + } + } else if (offset_flags != 0) { + add_failure(edid, "Display Range Limits: Range offset flags are unsupported in EDID 1.3."); + } + + if (edid->revision <= 4 && (data[5] == 0 || data[6] == 0 || + data[7] == 0 || data[8] == 0)) { + add_failure(edid, "Display Range Limits: Range limits set to reserved values."); + return false; + } + + base->min_vert_rate_hz = data[5] + min_vert_offset; + base->max_vert_rate_hz = data[6] + max_vert_offset; + base->min_horiz_rate_hz = (data[7] + min_horiz_offset) * 1000; + base->max_horiz_rate_hz = (data[8] + max_horiz_offset) * 1000; + + if (base->min_vert_rate_hz > base->max_vert_rate_hz) { + add_failure(edid, "Display Range Limits: Min vertical rate > max vertical rate."); + return false; + } + if (base->min_horiz_rate_hz > base->max_horiz_rate_hz) { + add_failure(edid, "Display Range Limits: Min horizontal freq > max horizontal freq."); + return false; + } + + base->max_pixel_clock_hz = (int64_t) data[9] * 10 * 1000 * 1000; + if (edid->revision == 4 && base->max_pixel_clock_hz == 0) { + add_failure(edid, "Display Range Limits: EDID 1.4 block does not set max dotclock."); + } + + support_flags = data[10]; + switch (support_flags) { + case 0x00: + /* For EDID 1.4 and later, always indicates support for default + * GTF. For EDID 1.3 and earlier, a misc features bit indicates + * support for default GTF. */ + if (edid->revision >= 4 || edid->misc_features.default_gtf) { + base->type = DI_EDID_DISPLAY_RANGE_LIMITS_DEFAULT_GTF; + } else { + base->type = DI_EDID_DISPLAY_RANGE_LIMITS_BARE; + } + break; + case 0x01: + if (edid->revision < 4) { + /* Reserved */ + add_failure(edid, "Display Range Limits: 'Bare Limits' is not allowed for EDID < 1.4."); + return false; + } + base->type = DI_EDID_DISPLAY_RANGE_LIMITS_BARE; + break; + case 0x02: + base->type = DI_EDID_DISPLAY_RANGE_LIMITS_SECONDARY_GTF; + break; + case 0x04: + if (edid->revision < 4) { + /* Reserved */ + add_failure(edid, "Display Range Limits: 'CVT' is not allowed for EDID < 1.4."); + return false; + } + base->type = DI_EDID_DISPLAY_RANGE_LIMITS_CVT; + break; + default: + /* Reserved */ + if (edid->revision <= 4) { + add_failure(edid, + "Display Range Limits: Unknown range class (0x%02x).", + support_flags); + return false; + } + base->type = DI_EDID_DISPLAY_RANGE_LIMITS_BARE; + break; + } + + /* Some types require the display to support continuous frequencies, but + * this flag is only set for EDID 1.4 and later */ + if (edid->revision >= 4 && !edid->misc_features.continuous_freq) { + switch (base->type) { + case DI_EDID_DISPLAY_RANGE_LIMITS_DEFAULT_GTF: + add_failure(edid, "Display Range Limits: GTF is supported, but the display does not support continuous frequencies."); + return false; + case DI_EDID_DISPLAY_RANGE_LIMITS_SECONDARY_GTF: + add_failure(edid, "Display Range Limits: Secondary GTF is supported, but the display does not support continuous frequencies."); + return false; + case DI_EDID_DISPLAY_RANGE_LIMITS_CVT: + add_failure(edid, "Display Range Limits: CVT is supported, but the display does not support continuous frequencies."); + return false; + default: + break; + } + } + + switch (base->type) { + case DI_EDID_DISPLAY_RANGE_LIMITS_SECONDARY_GTF: + secondary_gtf = &priv->secondary_gtf; + + if (data[11] != 0) + add_failure(edid, + "Display Range Limits: Byte 11 is 0x%02x instead of 0x00.", + data[11]); + + secondary_gtf->start_freq_hz = data[12] * 2 * 1000; + secondary_gtf->c = (float) data[13] / 2; + secondary_gtf->m = (float) ((data[15] << 8) | data[14]); + secondary_gtf->k = (float) data[16]; + secondary_gtf->j = (float) data[17] / 2; + + base->secondary_gtf = secondary_gtf; + break; + case DI_EDID_DISPLAY_RANGE_LIMITS_CVT: + cvt = &priv->cvt; + + cvt->version = get_bit_range(data[11], 7, 4); + cvt->revision = get_bit_range(data[11], 3, 0); + + base->max_pixel_clock_hz -= get_bit_range(data[12], 7, 2) * 250 * 1000; + cvt->max_horiz_px = 8 * ((get_bit_range(data[12], 1, 0) << 8) | data[13]); + + cvt->supported_aspect_ratio = data[14]; + if (get_bit_range(data[14], 2, 0) != 0) + add_failure_until(edid, 4, + "Display Range Limits: Reserved bits of byte 14 are non-zero."); + + preferred_aspect_ratio = get_bit_range(data[15], 7, 5); + switch (preferred_aspect_ratio) { + case 0: + cvt->preferred_aspect_ratio = DI_EDID_CVT_ASPECT_RATIO_4_3; + break; + case 1: + cvt->preferred_aspect_ratio = DI_EDID_CVT_ASPECT_RATIO_16_9; + break; + case 2: + cvt->preferred_aspect_ratio = DI_EDID_CVT_ASPECT_RATIO_16_10; + break; + case 3: + cvt->preferred_aspect_ratio = DI_EDID_CVT_ASPECT_RATIO_5_4; + break; + case 4: + cvt->preferred_aspect_ratio = DI_EDID_CVT_ASPECT_RATIO_15_9; + break; + default: + /* Reserved */ + add_failure_until(edid, 4, + "Display Range Limits: Invalid preferred aspect ratio 0x%02x.", + preferred_aspect_ratio); + return false; + } + + cvt->standard_blanking = has_bit(data[15], 3); + cvt->reduced_blanking = has_bit(data[15], 4); + + if (get_bit_range(data[15], 2, 0) != 0) + add_failure_until(edid, 4, + "Display Range Limits: Reserved bits of byte 15 are non-zero."); + + cvt->supported_scaling = data[16]; + if (get_bit_range(data[16], 3, 0) != 0) + add_failure_until(edid, 4, + "Display Range Limits: Reserved bits of byte 16 are non-zero."); + + cvt->preferred_vert_refresh_hz = data[17]; + if (cvt->preferred_vert_refresh_hz == 0) { + add_failure_until(edid, 4, + "Display Range Limits: Preferred vertical refresh rate must be specified."); + return false; + } + + base->cvt = cvt; + break; + case DI_EDID_DISPLAY_RANGE_LIMITS_BARE: + case DI_EDID_DISPLAY_RANGE_LIMITS_DEFAULT_GTF: + if (data[11] != 0x0A) + add_failure(edid, + "Display Range Limits: Byte 11 is 0x%02x instead of 0x0a.", + data[11]); + for (i = 12; i < EDID_BYTE_DESCRIPTOR_SIZE; i++) { + if (data[i] != 0x20) { + add_failure(edid, + "Display Range Limits: Bytes 12-17 must be 0x20."); + break; + } + } + break; + } + + return true; +} + +static bool +parse_standard_timings_descriptor(struct di_edid *edid, + const uint8_t data[static EDID_BYTE_DESCRIPTOR_SIZE], + struct di_edid_display_descriptor *desc) +{ + struct di_edid_standard_timing *t; + size_t i; + const uint8_t *timing_data; + + for (i = 0; i < EDID_MAX_DESCRIPTOR_STANDARD_TIMING_COUNT; i++) { + timing_data = &data[5 + i * EDID_STANDARD_TIMING_SIZE]; + if (!parse_standard_timing(edid, timing_data, &t)) + return false; + if (t) { + assert(desc->standard_timings_len < EDID_MAX_DESCRIPTOR_STANDARD_TIMING_COUNT); + desc->standard_timings[desc->standard_timings_len++] = t; + } + } + + if (data[17] != 0x0A) + add_failure_until(edid, 4, + "Standard Timing Identifications: Last byte must be a line feed."); + + return true; +} + +/** + * Mapping table for established timings III. + * + * Contains one entry per bit, with the value set to the DMT ID. + */ +static const uint8_t established_timings_iii[] = { + /* 0x06 */ + 0x01, /* 640 x 350 @ 85 Hz */ + 0x02, /* 640 x 400 @ 85 Hz */ + 0x03, /* 720 x 400 @ 85 Hz */ + 0x07, /* 640 x 480 @ 85 Hz */ + 0x0e, /* 848 x 480 @ 60 Hz */ + 0x0c, /* 800 x 600 @ 85 Hz */ + 0x13, /* 1024 x 768 @ 85 Hz */ + 0x15, /* 1152 x 864 @ 75 Hz */ + /* 0x07 */ + 0x16, /* 1280 x 768 @ 60 Hz (RB) */ + 0x17, /* 1280 x 768 @ 60 Hz */ + 0x18, /* 1280 x 768 @ 75 Hz */ + 0x19, /* 1280 x 768 @ 85 Hz */ + 0x20, /* 1280 x 960 @ 60 Hz */ + 0x21, /* 1280 x 960 @ 85 Hz */ + 0x23, /* 1280 x 1024 @ 60 Hz */ + 0x25, /* 1280 x 1024 @ 85 Hz */ + /* 0x08 */ + 0x27, /* 1360 x 768 @ 60 Hz */ + 0x2e, /* 1440 x 900 @ 60 Hz (RB) */ + 0x2f, /* 1440 x 900 @ 60 Hz */ + 0x30, /* 1440 x 900 @ 75 Hz */ + 0x31, /* 1440 x 900 @ 85 Hz */ + 0x29, /* 1400 x 1050 @ 60 Hz (RB) */ + 0x2a, /* 1400 x 1050 @ 60 Hz */ + 0x2b, /* 1400 x 1050 @ 75 Hz */ + /* 0x09 */ + 0x2c, /* 1400 x 1050 @ 85 Hz */ + 0x39, /* 1680 x 1050 @ 60 Hz (RB) */ + 0x3a, /* 1680 x 1050 @ 60 Hz */ + 0x3b, /* 1680 x 1050 @ 75 Hz */ + 0x3c, /* 1680 x 1050 @ 85 Hz */ + 0x33, /* 1600 x 1200 @ 60 Hz */ + 0x34, /* 1600 x 1200 @ 65 Hz */ + 0x35, /* 1600 x 1200 @ 70 Hz */ + /* 0x0a */ + 0x36, /* 1600 x 1200 @ 75 Hz */ + 0x37, /* 1600 x 1200 @ 85 Hz */ + 0x3e, /* 1792 x 1344 @ 60 Hz */ + 0x3f, /* 1792 x 1344 @ 75 Hz */ + 0x41, /* 1856 x 1392 @ 60 Hz */ + 0x42, /* 1856 x 1392 @ 75 Hz */ + 0x44, /* 1920 x 1200 @ 60 Hz (RB) */ + 0x45, /* 1920 x 1200 @ 60 Hz */ + /* 0x0b */ + 0x46, /* 1920 x 1200 @ 75 Hz */ + 0x47, /* 1920 x 1200 @ 85 Hz */ + 0x49, /* 1920 x 1440 @ 60 Hz */ + 0x4a, /* 1920 x 1440 @ 75 Hz */ +}; +static_assert(EDID_MAX_DESCRIPTOR_ESTABLISHED_TIMING_III_COUNT + == sizeof(established_timings_iii) / sizeof(established_timings_iii[0]), + "Invalid number of established timings III in table"); + +static bool +parse_established_timings_iii_descriptor(struct di_edid *edid, + const uint8_t data[static EDID_BYTE_DESCRIPTOR_SIZE], + struct di_edid_established_timings_iii *timings_iii) +{ + size_t i, offset, bit; + uint8_t dmt_id; + bool has_zeroes; + struct di_dmt_code dmt_codes[EDID_MAX_DESCRIPTOR_ESTABLISHED_TIMING_III_COUNT]; + size_t dmt_codes_len = 0; + size_t size; + + if (edid->revision < 4) + add_failure(edid, "Established timings III: Not allowed for EDID < 1.4."); + + for (i = 0; i < EDID_MAX_DESCRIPTOR_ESTABLISHED_TIMING_III_COUNT; i++) { + dmt_id = established_timings_iii[i]; + offset = 0x06 + i / 8; + bit = 7 - i % 8; + assert(offset < EDID_BYTE_DESCRIPTOR_SIZE); + if (has_bit(data[offset], bit)) + dmt_codes[dmt_codes_len++] = (struct di_dmt_code) { .code = dmt_id }; + } + + if (dmt_codes_len == 0) { + add_failure(edid, "Established timings III: No DMT codes."); + return true; + } + + size = dmt_codes_len * sizeof(*dmt_codes); + timings_iii->dmt_codes = calloc(1, size); + if (!timings_iii->dmt_codes) + return false; + + memcpy(timings_iii->dmt_codes, dmt_codes, size); + timings_iii->dmt_codes_len = dmt_codes_len; + + has_zeroes = get_bit_range(data[11], 3, 0) == 0; + for (i = 12; i < EDID_BYTE_DESCRIPTOR_SIZE; i++) { + has_zeroes = has_zeroes && data[i] == 0; + } + if (!has_zeroes) { + add_failure_until(edid, 4, + "Established timings III: Reserved bits must be set to zero."); + } + + return true; +} + +static bool +parse_color_point_descriptor(struct di_edid *edid, + const uint8_t data[static EDID_BYTE_DESCRIPTOR_SIZE], + struct di_edid_display_descriptor *desc) +{ + struct di_edid_color_point *c; + + if (data[5] == 0) { + add_failure(edid, "White Point Index Number set to reserved value 0"); + } + + c = calloc(1, sizeof(*c)); + if (!c) { + return false; + } + + c->index = data[5]; + c->white_x = decode_chromaticity_coord(data[7], get_bit_range(data[6], 3, 2)); + c->white_y = decode_chromaticity_coord(data[8], get_bit_range(data[6], 1, 0)); + + if (data[9] != 0xFF) { + c->gamma = ((float) data[9] + 100) / 100; + } + + desc->color_points[desc->color_points_len++] = c; + if (data[10] == 0) { + return true; + } + + c = calloc(1, sizeof(*c)); + if (!c) { + return false; + } + + c->index = data[10]; + c->white_x = decode_chromaticity_coord(data[12], get_bit_range(data[11], 3, 2)); + c->white_y = decode_chromaticity_coord(data[13], get_bit_range(data[11], 1, 0)); + + if (data[14] != 0xFF) { + c->gamma = ((float) data[14] + 100) / 100; + } + + desc->color_points[desc->color_points_len++] = c; + return true; +} + +static void +parse_color_management_data_descriptor(struct di_edid *edid, + const uint8_t data[static EDID_BYTE_DESCRIPTOR_SIZE], + struct di_edid_display_descriptor *desc) +{ + desc->dcm_data.version = data[5]; + + desc->dcm_data.red_a3 = (uint16_t)(data[6] | (data[7] << 8)) / 100.0f; + desc->dcm_data.red_a2 = (uint16_t)(data[8] | (data[9] << 8)) / 100.0f; + desc->dcm_data.green_a3 = (uint16_t)(data[10] | (data[11] << 8)) / 100.0f; + desc->dcm_data.green_a2 = (uint16_t)(data[12] | (data[13] << 8)) / 100.0f; + desc->dcm_data.blue_a3 = (uint16_t)(data[14] | (data[15] << 8)) / 100.0f; + desc->dcm_data.blue_a2 = (uint16_t)(data[16] | (data[17] << 8)) / 100.0f; + + if (desc->dcm_data.version != 3) { + add_failure_until(edid, 4, + "Color Management Data version must be 3"); + } +} + +static bool +is_cvt_timing_code_preferred_vrate_supported(const struct di_edid_cvt_timing_code *t) +{ + switch (t->preferred_vertical_rate) { + case DI_EDID_CVT_TIMING_CODE_PREFERRED_VRATE_50HZ: + return t->supports_50hz_sb; + case DI_EDID_CVT_TIMING_CODE_PREFERRED_VRATE_60HZ: + return t->supports_60hz_sb || t->supports_60hz_rb; + case DI_EDID_CVT_TIMING_CODE_PREFERRED_VRATE_75HZ: + return t->supports_75hz_sb; + case DI_EDID_CVT_TIMING_CODE_PREFERRED_VRATE_85HZ: + return t->supports_85hz_sb; + } + abort(); /* unreachable */ +} + +static bool +parse_cvt_timing_code(struct di_edid *edid, + const uint8_t data[static EDID_CVT_TIMING_CODE_SIZE], + struct di_edid_cvt_timing_code **out, + bool first) +{ + struct di_edid_cvt_timing_code *t; + int32_t raw; + + *out = NULL; + + if (!first && data[0] == 0 && data[1] == 0 && data[2] == 0) { + /* Unused */ + return true; + } + if (data[0] == 0) { + add_failure(edid, + "CVT byte 0 is 0, which is a reserved value."); + } + + t = calloc(1, sizeof(*t)); + if (!t) { + return false; + } + + raw = (int32_t)(data[0] | (get_bit_range(data[1], 7, 4) << 8)); + t->addressable_lines_per_field = (raw + 1) * 2; + t->aspect_ratio = get_bit_range(data[1], 3, 2); + + if (get_bit_range(data[1], 1, 0) != 0) { + add_failure(edid, + "Reserved bits of CVT byte 1 are non-zero."); + } + + t->supports_50hz_sb = has_bit(data[2], 4); + t->supports_60hz_sb = has_bit(data[2], 3); + t->supports_75hz_sb = has_bit(data[2], 2); + t->supports_85hz_sb = has_bit(data[2], 1); + t->supports_60hz_rb = has_bit(data[2], 0); + + if (get_bit_range(data[2], 4, 0) == 0) { + add_failure(edid, + "CVT byte 2 does not support any vertical rates."); + } + + t->preferred_vertical_rate = get_bit_range(data[2], 6, 5); + + if (has_bit(data[2], 7) != 0) { + add_failure(edid, + "Reserved bit of CVT byte 2 is non-zero."); + } + + if (!is_cvt_timing_code_preferred_vrate_supported(t)) + add_failure(edid, "The preferred CVT Vertical Rate is not supported."); + + *out = t; + return true; +} + +static bool +parse_cvt_timing_codes_descriptor(struct di_edid *edid, + const uint8_t data[static EDID_BYTE_DESCRIPTOR_SIZE], + struct di_edid_display_descriptor *desc) +{ + struct di_edid_cvt_timing_code *t; + size_t i; + const uint8_t *timing_data; + + if (data[5] != 1) { + add_failure_until(edid, 4, "Invalid version number %u.", + data[5]); + } + + for (i = 0; i < EDID_MAX_DESCRIPTOR_CVT_TIMING_CODES_COUNT; i++) { + timing_data = &data[6 + i * EDID_CVT_TIMING_CODE_SIZE]; + if (!parse_cvt_timing_code(edid, timing_data, &t, !i)) + return false; + if (t) { + assert(desc->cvt_timing_codes_len < EDID_MAX_DESCRIPTOR_CVT_TIMING_CODES_COUNT); + desc->cvt_timing_codes[desc->cvt_timing_codes_len++] = t; + } + } + + return true; +} + +static bool +parse_byte_descriptor(struct di_edid *edid, + const uint8_t data[static EDID_BYTE_DESCRIPTOR_SIZE]) +{ + struct di_edid_display_descriptor *desc; + struct di_edid_detailed_timing_def_priv *detailed_timing_def; + char *newline; + + if (data[0] || data[1]) { + if (edid->display_descriptors_len > 0) { + /* A detailed timing descriptor is not allowed after a + * display descriptor per note 3 of table 3.20. */ + add_failure(edid, "Invalid detailed timing descriptor ordering."); + } + + detailed_timing_def = _di_edid_parse_detailed_timing_def(data); + if (!detailed_timing_def) { + return false; + } + + assert(edid->detailed_timing_defs_len < EDID_BYTE_DESCRIPTOR_COUNT); + edid->detailed_timing_defs[edid->detailed_timing_defs_len++] = detailed_timing_def; + return true; + } + + if (edid->revision >= 3 && edid->revision <= 4 && + edid->detailed_timing_defs_len == 0) { + /* Per section 3.10.1 */ + add_failure(edid, + "The first byte descriptor must contain the preferred timing."); + } + + desc = calloc(1, sizeof(*desc)); + if (!desc) { + return false; + } + + desc->tag = data[3]; + switch (desc->tag) { + case DI_EDID_DISPLAY_DESCRIPTOR_PRODUCT_SERIAL: + case DI_EDID_DISPLAY_DESCRIPTOR_DATA_STRING: + case DI_EDID_DISPLAY_DESCRIPTOR_PRODUCT_NAME: + memcpy(desc->str, &data[5], 13); + + /* A newline (if any) indicates the end of the string. */ + newline = strchr(desc->str, '\n'); + if (newline) { + newline[0] = '\0'; + } + break; + case DI_EDID_DISPLAY_DESCRIPTOR_RANGE_LIMITS: + if (!parse_display_range_limits(edid, data, &desc->range_limits)) { + destroy_display_descriptor(desc); + return true; + } + break; + case DI_EDID_DISPLAY_DESCRIPTOR_STD_TIMING_IDS: + if (!parse_standard_timings_descriptor(edid, data, desc)) { + destroy_display_descriptor(desc); + return false; + } + break; + case DI_EDID_DISPLAY_DESCRIPTOR_ESTABLISHED_TIMINGS_III: + if (!parse_established_timings_iii_descriptor(edid, data, + &desc->established_timings_iii)) { + destroy_display_descriptor(desc); + return false; + } + break; + case DI_EDID_DISPLAY_DESCRIPTOR_COLOR_POINT: + if (!parse_color_point_descriptor(edid, data, desc)) { + destroy_display_descriptor(desc); + return false; + } + break; + case DI_EDID_DISPLAY_DESCRIPTOR_DCM_DATA: + parse_color_management_data_descriptor(edid, data, desc); + break; + case DI_EDID_DISPLAY_DESCRIPTOR_CVT_TIMING_CODES: + if (!parse_cvt_timing_codes_descriptor(edid, data, desc)) { + destroy_display_descriptor(desc); + return false; + } + break; + case DI_EDID_DISPLAY_DESCRIPTOR_DUMMY: + break; /* Ignore */ + default: + if (desc->tag <= 0x0F) { + /* Manufacturer-specific */ + } else { + add_failure_until(edid, 4, "Unknown Type 0x%02hhx.", desc->tag); + } + destroy_display_descriptor(desc); + return true; + } + + assert(edid->display_descriptors_len < EDID_BYTE_DESCRIPTOR_COUNT); + edid->display_descriptors[edid->display_descriptors_len++] = desc; + return true; +} + +static const char * +ext_tag_name(enum di_edid_ext_tag tag) +{ + switch (tag) { + case DI_EDID_EXT_CEA: + return "CTA-861 Extension Block"; + case DI_EDID_EXT_VTB: + return "Video Timing Extension Block"; + case DI_EDID_EXT_DI: + return "Display Information Extension Block"; + case DI_EDID_EXT_LS: + return "Localized String Extension Block"; + case DI_EDID_EXT_DPVL: + return "Digital Packet Video Link Extension"; + case DI_EDID_EXT_BLOCK_MAP: + return "Block Map Extension Block"; + case DI_EDID_EXT_VENDOR: + return "Manufacturer-Specific Extension Block"; + case DI_EDID_EXT_DISPLAYID: + return "DisplayID Extension Block"; + } + return "Unknown Extension Block"; +} + +static bool +parse_ext(struct di_edid *edid, const uint8_t data[static EDID_BLOCK_SIZE]) +{ + struct di_edid_ext *ext; + struct di_logger logger; + char section_name[64]; + const uint8_t *ext_data; + size_t ext_size; + bool ok; + + ext = calloc(1, sizeof(*ext)); + if (!ext) { + return false; + } + ext->tag = data[0x00]; + + snprintf(section_name, sizeof(section_name), "Block %zu, %s", + edid->exts_len + 1, ext_tag_name(ext->tag)); + logger = (struct di_logger) { + .f = edid->logger->f, + .section = section_name, + }; + + switch (ext->tag) { + case DI_EDID_EXT_CEA: + if (!_di_edid_cta_parse(&ext->cta, data, EDID_BLOCK_SIZE, &logger)) { + free(ext); + return errno == EINVAL; + } + break; + case DI_EDID_EXT_VTB: + case DI_EDID_EXT_DI: + case DI_EDID_EXT_LS: + case DI_EDID_EXT_DPVL: + case DI_EDID_EXT_BLOCK_MAP: + case DI_EDID_EXT_VENDOR: + /* Supported */ + break; + case DI_EDID_EXT_DISPLAYID: + ext_data = &data[1]; + ext_size = EDID_BLOCK_SIZE - 2; + + ext->displayid_version = _di_displayid_parse_version(ext_data, ext_size); + switch (ext->displayid_version) { + case 1: + ok = _di_displayid_parse(&ext->displayid, ext_data, + ext_size, &logger); + break; + case 2: + ok = _di_displayid2_parse(&ext->displayid2, ext_data, + ext_size, &logger); + break; + default: + /* Unsupported */ + free(ext); + return true; + } + if (!ok) { + free(ext); + return errno == ENOTSUP || errno == EINVAL; + } + break; + default: + /* Unsupported */ + free(ext); + add_failure_until(edid, 4, "Unknown Extension Block."); + return true; + } + + if (!validate_block_checksum(data)) + _di_logger_add_failure(&logger, "Invalid extension checksum."); + + assert(edid->exts_len < EDID_MAX_BLOCK_COUNT - 1); + edid->exts[edid->exts_len++] = ext; + return true; +} + +struct di_edid * +_di_edid_parse(const void *data, size_t size, FILE *failure_msg_file) +{ + struct di_edid *edid; + struct di_logger logger; + int version, revision; + size_t exts_len, parsed_ext_len, i; + const uint8_t *standard_timing_data, *byte_desc_data, *ext_data; + struct di_edid_standard_timing *standard_timing; + + if (size < EDID_BLOCK_SIZE) { + errno = EINVAL; + return NULL; + } + + if (memcmp(data, header, sizeof(header)) != 0) { + errno = EINVAL; + return NULL; + } + + parse_version_revision(data, &version, &revision); + if (version != 1) { + /* Only EDID version 1 is supported -- as per section 2.1.7 + * subsequent versions break the structure */ + errno = ENOTSUP; + return NULL; + } + + if (!validate_block_checksum(data)) { + errno = EINVAL; + return NULL; + } + + edid = calloc(1, sizeof(*edid)); + if (!edid) { + return NULL; + } + + logger = (struct di_logger) { + .f = failure_msg_file, + .section = "Block 0, Base EDID", + }; + edid->logger = &logger; + + edid->version = version; + edid->revision = revision; + + if (size % EDID_BLOCK_SIZE != 0) + add_failure(edid, "The data is not a multiple of the block size."); + if (size > EDID_MAX_BLOCK_COUNT * EDID_BLOCK_SIZE) + add_failure(edid, "The data is exceeding the maximum block count."); + + exts_len = (size / EDID_BLOCK_SIZE) - 1; + parsed_ext_len = parse_ext_count(data); + + if (exts_len != parsed_ext_len) + add_failure(edid, "The data size does not match the encoded block count."); + + if (parsed_ext_len < exts_len) + exts_len = parsed_ext_len; + + assert(exts_len < EDID_MAX_BLOCK_COUNT); + + parse_vendor_product(edid, data); + parse_basic_params_features(edid, data); + parse_chromaticity_coords(edid, data); + + parse_established_timings_i_ii(edid, data); + + for (i = 0; i < EDID_MAX_STANDARD_TIMING_COUNT; i++) { + standard_timing_data = (const uint8_t *) data + + 0x26 + i * EDID_STANDARD_TIMING_SIZE; + if (!parse_standard_timing(edid, standard_timing_data, + &standard_timing)) { + _di_edid_destroy(edid); + return NULL; + } + if (standard_timing) { + assert(edid->standard_timings_len < EDID_MAX_STANDARD_TIMING_COUNT); + edid->standard_timings[edid->standard_timings_len++] = standard_timing; + } + } + + for (i = 0; i < EDID_BYTE_DESCRIPTOR_COUNT; i++) { + byte_desc_data = (const uint8_t *) data + + 0x36 + i * EDID_BYTE_DESCRIPTOR_SIZE; + if (!parse_byte_descriptor(edid, byte_desc_data)) { + _di_edid_destroy(edid); + return NULL; + } + } + + for (i = 0; i < exts_len; i++) { + ext_data = (const uint8_t *) data + (i + 1) * EDID_BLOCK_SIZE; + if (!parse_ext(edid, ext_data)) { + _di_edid_destroy(edid); + return NULL; + } + } + + edid->logger = NULL; + return edid; +} + +static void +destroy_display_descriptor(struct di_edid_display_descriptor *desc) +{ + size_t i; + + switch (desc->tag) { + case DI_EDID_DISPLAY_DESCRIPTOR_STD_TIMING_IDS: + for (i = 0; i < desc->standard_timings_len; i++) { + free(desc->standard_timings[i]); + } + break; + case DI_EDID_DISPLAY_DESCRIPTOR_COLOR_POINT: + for (i = 0; i < desc->color_points_len; i++) { + free(desc->color_points[i]); + } + break; + case DI_EDID_DISPLAY_DESCRIPTOR_ESTABLISHED_TIMINGS_III: + free(desc->established_timings_iii.dmt_codes); + break; + case DI_EDID_DISPLAY_DESCRIPTOR_CVT_TIMING_CODES: + for (i = 0; i < desc->cvt_timing_codes_len; i++) { + free(desc->cvt_timing_codes[i]); + } + break; + default: + break; /* Nothing to do */ + } + free(desc); +} + +void +_di_edid_destroy(struct di_edid *edid) +{ + size_t i; + struct di_edid_ext *ext; + + for (i = 0; i < edid->standard_timings_len; i++) { + free(edid->standard_timings[i]); + } + + for (i = 0; i < edid->detailed_timing_defs_len; i++) { + free(edid->detailed_timing_defs[i]); + } + + for (i = 0; i < edid->display_descriptors_len; i++) { + destroy_display_descriptor(edid->display_descriptors[i]); + } + + for (i = 0; edid->exts[i] != NULL; i++) { + ext = edid->exts[i]; + switch (ext->tag) { + case DI_EDID_EXT_CEA: + _di_edid_cta_finish(&ext->cta); + break; + case DI_EDID_EXT_DISPLAYID: + _di_displayid_finish(&ext->displayid); + _di_displayid2_finish(&ext->displayid2); + break; + default: + break; /* Nothing to do */ + } + free(ext); + } + + free(edid); +} + +int +di_edid_get_version(const struct di_edid *edid) +{ + return edid->version; +} + +int +di_edid_get_revision(const struct di_edid *edid) +{ + return edid->revision; +} + +const struct di_edid_vendor_product * +di_edid_get_vendor_product(const struct di_edid *edid) +{ + return &edid->vendor_product; +} + +const struct di_edid_video_input_analog * +di_edid_get_video_input_analog(const struct di_edid *edid) +{ + return edid->is_digital ? NULL : &edid->video_input_analog; +} + +const struct di_edid_video_input_digital * +di_edid_get_video_input_digital(const struct di_edid *edid) +{ + return edid->is_digital ? &edid->video_input_digital : NULL; +} + +const struct di_edid_screen_size * +di_edid_get_screen_size(const struct di_edid *edid) +{ + return &edid->screen_size; +} + +float +di_edid_get_basic_gamma(const struct di_edid *edid) +{ + return edid->gamma; +} + +const struct di_edid_dpms * +di_edid_get_dpms(const struct di_edid *edid) +{ + return &edid->dpms; +} + +enum di_edid_display_color_type +di_edid_get_display_color_type(const struct di_edid *edid) +{ + return edid->display_color_type; +} + +const struct di_edid_color_encoding_formats * +di_edid_get_color_encoding_formats(const struct di_edid *edid) +{ + /* If color encoding formats are specified, RGB 4:4:4 is always + * supported. */ + return edid->color_encoding_formats.rgb444 ? &edid->color_encoding_formats : NULL; +} + +const struct di_edid_misc_features * +di_edid_get_misc_features(const struct di_edid *edid) +{ + return &edid->misc_features; +} + +const struct di_edid_chromaticity_coords * +di_edid_get_chromaticity_coords(const struct di_edid *edid) +{ + return &edid->chromaticity_coords; +} + +const struct di_edid_established_timings_i_ii * +di_edid_get_established_timings_i_ii(const struct di_edid *edid) +{ + return &edid->established_timings_i_ii; +} + +int32_t +di_edid_standard_timing_get_vert_video(const struct di_edid_standard_timing *t) +{ + switch (t->aspect_ratio) { + case DI_EDID_STANDARD_TIMING_16_10: + return t->horiz_video * 10 / 16; + case DI_EDID_STANDARD_TIMING_4_3: + return t->horiz_video * 3 / 4; + case DI_EDID_STANDARD_TIMING_5_4: + return t->horiz_video * 4 / 5; + case DI_EDID_STANDARD_TIMING_16_9: + return t->horiz_video * 9 / 16; + } + abort(); /* unreachable */ +} + +const struct di_edid_standard_timing *const * +di_edid_get_standard_timings(const struct di_edid *edid) +{ + return (const struct di_edid_standard_timing *const *) &edid->standard_timings; +} + +const struct di_edid_detailed_timing_def *const * +di_edid_get_detailed_timing_defs(const struct di_edid *edid) +{ + return (const struct di_edid_detailed_timing_def *const *) &edid->detailed_timing_defs; +} + +const struct di_edid_display_descriptor *const * +di_edid_get_display_descriptors(const struct di_edid *edid) +{ + return (const struct di_edid_display_descriptor *const *) &edid->display_descriptors; +} + +enum di_edid_display_descriptor_tag +di_edid_display_descriptor_get_tag(const struct di_edid_display_descriptor *desc) +{ + return desc->tag; +} + +const char * +di_edid_display_descriptor_get_string(const struct di_edid_display_descriptor *desc) +{ + switch (desc->tag) { + case DI_EDID_DISPLAY_DESCRIPTOR_PRODUCT_SERIAL: + case DI_EDID_DISPLAY_DESCRIPTOR_DATA_STRING: + case DI_EDID_DISPLAY_DESCRIPTOR_PRODUCT_NAME: + return desc->str; + default: + return NULL; + } +} + +const struct di_edid_display_range_limits * +di_edid_display_descriptor_get_range_limits(const struct di_edid_display_descriptor *desc) +{ + if (desc->tag != DI_EDID_DISPLAY_DESCRIPTOR_RANGE_LIMITS) { + return NULL; + } + return &desc->range_limits.base; +} + +const struct di_edid_standard_timing *const * +di_edid_display_descriptor_get_standard_timings(const struct di_edid_display_descriptor *desc) +{ + if (desc->tag != DI_EDID_DISPLAY_DESCRIPTOR_STD_TIMING_IDS) { + return NULL; + } + return (const struct di_edid_standard_timing *const *) desc->standard_timings; +} + +const struct di_edid_color_point *const * +di_edid_display_descriptor_get_color_points(const struct di_edid_display_descriptor *desc) +{ + if (desc->tag != DI_EDID_DISPLAY_DESCRIPTOR_COLOR_POINT) { + return NULL; + } + return (const struct di_edid_color_point *const *) desc->color_points; +} + +const struct di_edid_established_timings_iii * +di_edid_display_descriptor_get_established_timings_iii(const struct di_edid_display_descriptor *desc) +{ + if (desc->tag != DI_EDID_DISPLAY_DESCRIPTOR_ESTABLISHED_TIMINGS_III) { + return NULL; + } + return &desc->established_timings_iii; +} + +const struct di_edid_color_management_data * +di_edid_display_descriptor_get_color_management_data(const struct di_edid_display_descriptor *desc) +{ + if (desc->tag != DI_EDID_DISPLAY_DESCRIPTOR_DCM_DATA) { + return NULL; + } + return &desc->dcm_data; +} + +const struct di_edid_cvt_timing_code *const * +di_edid_display_descriptor_get_cvt_timing_codes(const struct di_edid_display_descriptor *desc) +{ + if (desc->tag != DI_EDID_DISPLAY_DESCRIPTOR_CVT_TIMING_CODES) { + return NULL; + } + return (const struct di_edid_cvt_timing_code *const *) desc->cvt_timing_codes; +} + +const struct di_edid_ext *const * +di_edid_get_extensions(const struct di_edid *edid) +{ + return (const struct di_edid_ext *const *) edid->exts; +} + +enum di_edid_ext_tag +di_edid_ext_get_tag(const struct di_edid_ext *ext) +{ + return ext->tag; +} + +const struct di_edid_cta * +di_edid_ext_get_cta(const struct di_edid_ext *ext) +{ + if (ext->tag != DI_EDID_EXT_CEA) { + return NULL; + } + return &ext->cta; +} + +const struct di_displayid * +di_edid_ext_get_displayid(const struct di_edid_ext *ext) +{ + if (ext->tag != DI_EDID_EXT_DISPLAYID || ext->displayid_version != 1) { + return NULL; + } + return &ext->displayid; +} + +const struct di_displayid2 * +di_edid_ext_get_displayid2(const struct di_edid_ext *ext) +{ + if (ext->tag != DI_EDID_EXT_DISPLAYID || ext->displayid_version != 2) { + return NULL; + } + return &ext->displayid2; +} diff --git a/local/recipes/libs/libdisplay-info/source/gtf.c b/local/recipes/libs/libdisplay-info/source/gtf.c new file mode 100644 index 0000000000..4612617985 --- /dev/null +++ b/local/recipes/libs/libdisplay-info/source/gtf.c @@ -0,0 +1,128 @@ +/* + * Copyright 2006-2012 Red Hat, Inc. + * Copyright 2018-2021 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + * + * Originally imported from edid-decode. + */ + +#include + +#include + +/** + * The assumed character cell granularity of the graphics system, in pixels. + */ +#define CELL_GRAN 8.0 +/** + * The size of the top and bottom overscan margin as a percentage of the active + * vertical image. + */ +#define MARGIN_PERC 1.8 +/** + * The minimum front porch in lines (vertical) and character cells (horizontal). + */ +#define MIN_PORCH 1.0 +/** + * The width of the V sync in lines. + */ +#define V_SYNC_RQD 3.0 +/** + * The width of the H sync as a percentage of the total line period. + */ +#define H_SYNC_PERC 8.0 +/** + * Minimum time of vertical sync + back porch interval (µs). + */ +#define MIN_VSYNC_BP 550.0 + +void di_gtf_compute(struct di_gtf_timing *t, const struct di_gtf_options *options) +{ + double c_prime, m_prime, h_pixels_rnd, v_lines_rnd, h_margin, v_margin, interlace, + total_active_pixels, v_field_rate_rqd, h_period_est, total_v_lines, v_field_rate_est, + h_period, ideal_duty_cycle, h_freq, ideal_h_period, v_back_porch, h_sync, + h_front_porch; + double v_sync_bp = 0.0, h_blank_pixels = 0.0, total_pixels = 0.0, pixel_freq = 0.0; + + /* C' and M' are part of the Blanking Duty Cycle computation */ + c_prime = ((options->c - options->j) * options->k / 256.0) + options->j; + m_prime = options->k / 256.0 * options->m; + + h_pixels_rnd = round(options->h_pixels / CELL_GRAN) * CELL_GRAN; + v_lines_rnd = options->int_rqd ? + round(options->v_lines / 2.0) : + options->v_lines; + h_margin = options->margins_rqd ? + round(h_pixels_rnd * MARGIN_PERC / 100.0 / CELL_GRAN) * CELL_GRAN : + 0; + v_margin = options->margins_rqd ? + round(MARGIN_PERC / 100.0 * v_lines_rnd) : + 0; + interlace = options->int_rqd ? 0.5 : 0; + total_active_pixels = h_pixels_rnd + h_margin * 2; + + switch (options->ip_param) { + case DI_GTF_IP_PARAM_V_FRAME_RATE: + // vertical frame frequency (Hz) + v_field_rate_rqd = options->int_rqd ? + options->ip_freq_rqd * 2 : + options->ip_freq_rqd; + h_period_est = (1.0 / v_field_rate_rqd - MIN_VSYNC_BP / 1000000.0) / + (v_lines_rnd + v_margin * 2 + MIN_PORCH + interlace) * 1000000.0; + v_sync_bp = round(MIN_VSYNC_BP / h_period_est); + total_v_lines = v_lines_rnd + v_margin * 2 + + v_sync_bp + interlace + MIN_PORCH; + v_field_rate_est = 1.0 / h_period_est / total_v_lines * 1000000.0; + h_period = h_period_est / (v_field_rate_rqd / v_field_rate_est); + 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; + break; + case DI_GTF_IP_PARAM_H_FREQ: + // horizontal frequency (kHz) + h_freq = options->ip_freq_rqd; + v_sync_bp = round(MIN_VSYNC_BP * h_freq / 1000.0); + 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; + break; + case DI_GTF_IP_PARAM_H_PIXELS: + // pixel clock rate (MHz) + pixel_freq = options->ip_freq_rqd; + ideal_h_period = (c_prime - 100.0 + + sqrt((100.0 - c_prime) * (100.0 - c_prime) + + 0.4 * m_prime * (total_active_pixels + h_margin * 2) / pixel_freq)) + / 2.0 / m_prime * 1000.0; + 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; + h_freq = pixel_freq / total_pixels * 1000.0; + v_sync_bp = round(MIN_VSYNC_BP * h_freq / 1000.0); + break; + } + + v_back_porch = v_sync_bp - V_SYNC_RQD; + h_sync = round(H_SYNC_PERC / 100.0 * total_pixels / CELL_GRAN) * CELL_GRAN; + h_front_porch = h_blank_pixels / 2.0 - h_sync; + + *t = (struct di_gtf_timing) { + .h_pixels = (int) h_pixels_rnd, + .v_lines = options->v_lines, + .v_sync = V_SYNC_RQD, + .h_sync = (int) h_sync, + .v_front_porch = MIN_PORCH, + .v_back_porch = (int) v_back_porch, + .h_front_porch = (int) h_front_porch, + .h_back_porch = (int) (h_front_porch + h_sync), + .h_border = (int) h_margin, + .v_border = (int) v_margin, + .pixel_freq_mhz = pixel_freq, + }; +} diff --git a/local/recipes/libs/libdisplay-info/source/hdmi-vic.c b/local/recipes/libs/libdisplay-info/source/hdmi-vic.c new file mode 100644 index 0000000000..aed53ac5c9 --- /dev/null +++ b/local/recipes/libs/libdisplay-info/source/hdmi-vic.c @@ -0,0 +1,97 @@ +#include + +#include + +/** + * HDMI video format table. 8.2.3.1 section of the 1.4b HDMI spec. + */ +static const struct di_hdmi_vic_video_format _di_hdmi_vic_video_formats[] = { + { + .vic = { .code = 1 }, + .h_active = 3840, + .v_active = 2160, + .pixel_clock_hz = 297000000, + .h_front = 176, + .h_sync = 88, + .h_back = 296, + .v_front = 8, + .v_sync = 10, + .v_back = 72, + }, + { + .vic = { .code = 2 }, + .h_active = 3840, + .v_active = 2160, + .pixel_clock_hz = 297000000, + .h_front = 1056, + .h_sync = 88, + .h_back = 296, + .v_front = 8, + .v_sync = 10, + .v_back = 72, + }, + { + .vic = { .code = 3 }, + .h_active = 3840, + .v_active = 2160, + .pixel_clock_hz = 297000000, + .h_front = 1276, + .h_sync = 88, + .h_back = 296, + .v_front = 8, + .v_sync = 10, + .v_back = 72, + }, + { + .vic = { .code = 4 }, + .h_active = 4096, + .v_active = 2160, + .pixel_clock_hz = 297000000, + .h_front = 1020, + .h_sync = 88, + .h_back = 296, + .v_front = 8, + .v_sync = 10, + .v_back = 72, + }, +}; + +static const size_t _di_hdmi_vic_video_formats_len = + sizeof(_di_hdmi_vic_video_formats) / sizeof(_di_hdmi_vic_video_formats[0]); + +const struct di_hdmi_vic_video_format * +di_hdmi_vic_video_format_from_vic(struct di_hdmi_vic vic) +{ + size_t i; + + for (i = 0; i < _di_hdmi_vic_video_formats_len; i++) { + if (_di_hdmi_vic_video_formats[i].vic.code == vic.code) + return &_di_hdmi_vic_video_formats[i]; + } + + return NULL; +} + +struct di_hdmi_vic +di_hdmi_vic_video_format_to_vic(const struct di_hdmi_vic_video_format *format) +{ + size_t i; + + for (i = 0; i < _di_hdmi_vic_video_formats_len; i++) { + const struct di_hdmi_vic_video_format *candidate = &_di_hdmi_vic_video_formats[i]; + + if (candidate->vic.code != 0 && + candidate->h_active == format->h_active && + candidate->v_active == format->v_active && + candidate->h_front == format->h_front && + candidate->v_front == format->v_front && + candidate->h_sync == format->h_sync && + candidate->v_sync == format->v_sync && + candidate->h_back == format->h_back && + candidate->v_back == format->v_back && + candidate->pixel_clock_hz == format->pixel_clock_hz) + return candidate->vic; + } + + return (struct di_hdmi_vic) { .code = 0 }; +} diff --git a/local/recipes/libs/libdisplay-info/source/include/bits.h b/local/recipes/libs/libdisplay-info/source/include/bits.h new file mode 100644 index 0000000000..b240127aab --- /dev/null +++ b/local/recipes/libs/libdisplay-info/source/include/bits.h @@ -0,0 +1,40 @@ +#ifndef BITS_H +#define BITS_H + +/** + * Utility functions to operate on bits. + */ + +#include +#include +#include +#include + +/** + * Check whether a byte has a bit set. + */ +static inline bool +has_bit(uint8_t val, size_t index) +{ + return val & (1 << index); +} + +/** + * Extract a bit range from a byte. + * + * Both offsets are inclusive, start from zero, and high must be greater than low. + */ +static inline uint8_t +get_bit_range(uint8_t val, size_t high, size_t low) +{ + size_t n; + uint8_t bitmask; + + assert(high <= 7 && high >= low); + + n = high - low + 1; + bitmask = (uint8_t) ((1 << n) - 1); + return (uint8_t) (val >> low) & bitmask; +} + +#endif diff --git a/local/recipes/libs/libdisplay-info/source/include/cta.h b/local/recipes/libs/libdisplay-info/source/include/cta.h new file mode 100644 index 0000000000..7ec135d4d7 --- /dev/null +++ b/local/recipes/libs/libdisplay-info/source/include/cta.h @@ -0,0 +1,287 @@ +#ifndef CTA_H +#define CTA_H + +/** + * Private header for the low-level CTA API. + */ + +#include +#include +#include + +#include +#include + +/** + * The maximum number of data blocks in an EDID CTA block. + * + * Each data block takes at least 1 byte, the CTA block can hold 128 bytes, and + * the mandatory fields take up 5 bytes (4 header bytes + checksum). + */ +#define EDID_CTA_MAX_DATA_BLOCKS 123 +/** + * The maximum number of detailed timing definitions included in an EDID CTA + * block. + * + * The CTA extension leaves at most 122 bytes for timings, and each timing takes + * 18 bytes. + */ +#define EDID_CTA_MAX_DETAILED_TIMING_DEFS 6 +/** + * The maximum number of SVD entries in a video data block. + * + * Each data block has its size described in a 5-bit field, so its maximum size + * is 63 bytes, and each SVD uses 1 byte. + */ +#define EDID_CTA_MAX_VIDEO_BLOCK_ENTRIES 63 +/** + * The maximum number of SAD entries in an audio data block. + * + * Each data block has its size described in a 5-bit field, so its maximum size + * is 63 bytes, and each SAD uses 3 bytes. + */ +#define EDID_CTA_MAX_AUDIO_BLOCK_ENTRIES 21 +/** + * The maximum number of Capability Bit Map entries in a YCbCr 4:2:0 video data + * block. + * + * Each data block has its size described in a 5-bit field, so its maximum size + * is 63 bytes, and each Capability Bit Map uses 1 byte. + */ +#define EDID_CTA_MAX_YCBCR420_CAP_MAP_BLOCK_ENTRIES 63 +/** + * The maximum number of Short InfoFrame Descriptor or Short Vendor-Specific + * InfoFrame Descriptor entries in a InfoFrame data block. + * + * Each data block has its size described in a 5-bit field, so its maximum size + * is 63 bytes, the header takes up at least 2 bytes and the smallest Short + * InfoFrame Descriptor is 1 byte. + */ +#define EDID_CTA_INFOFRAME_BLOCK_ENTRIES 61 +/** + * The maximum number of Speaker Location Descriptors in a Speaker Location data + * block. + * + * Each data block has its size described in a 5-bit field, so its maximum size + * is 63 bytes, and each Speaker Location Descriptors uses at least 2 byte. + */ +#define EDID_CTA_MAX_SPEAKER_LOCATION_BLOCK_ENTRIES 31 +/** + * The maximum number of SVR entries in a video format preference block. + * + * Each data block has its size described in a 5-bit field, so its maximum size + * is 63 bytes, and each SVR uses 1 byte. + */ +#define EDID_CTA_MAX_VIDEO_FORMAT_PREF_BLOCK_ENTRIES 63 +/** + * The maximum number of format entries in a HDMI audio block. + * + * Each data block has its size described in a 5-bit field, so its maximum size + * is 63 bytes, the header takes up 2 bytes and each format entry uses 4 bytes. + */ +#define EDID_CTA_MAX_HDMI_AUDIO_BLOCK_ENTRIES 15 + +/** + * Context for CTA parsing functions. + */ +struct di_cta { + int revision; + const struct di_edid_cta_flags *flags; /* may be NULL for non-EDID blocks */ + + struct di_logger *logger; +}; + +struct di_edid_cta { + int revision; + struct di_edid_cta_flags flags; + + /* NULL-terminated */ + struct di_cta_data_block *data_blocks[EDID_CTA_MAX_DATA_BLOCKS + 1]; + size_t data_blocks_len; + + /* NULL-terminated */ + struct di_edid_detailed_timing_def_priv *detailed_timing_defs[EDID_CTA_MAX_DETAILED_TIMING_DEFS + 1]; + size_t detailed_timing_defs_len; +}; + +struct di_cta_hdr_static_metadata_block_priv { + struct di_cta_hdr_static_metadata_block base; + struct di_cta_hdr_static_metadata_eotfs eotfs; + struct di_cta_hdr_static_metadata_descriptors descriptors; +}; + +struct di_cta_hdr_dynamic_metadata_type3 { + uint8_t unused; +}; + +struct di_cta_hdr_dynamic_metadata_block_priv { + struct di_cta_hdr_dynamic_metadata_block base; + struct di_cta_hdr_dynamic_metadata_type1 type1; + struct di_cta_hdr_dynamic_metadata_type2 type2; + struct di_cta_hdr_dynamic_metadata_type3 type3; + struct di_cta_hdr_dynamic_metadata_type4 type4; + struct di_cta_hdr_dynamic_metadata_type256 type256; +}; + +struct di_cta_video_block_priv { + struct di_cta_video_block base; + /* NULL-terminated */ + struct di_cta_svd *svds[EDID_CTA_MAX_VIDEO_BLOCK_ENTRIES + 1]; + size_t svds_len; +}; + +struct di_cta_ycbcr420_video_block_priv { + struct di_cta_ycbcr420_video_block base; + /* NULL-terminated */ + struct di_cta_svd *svds[EDID_CTA_MAX_VIDEO_BLOCK_ENTRIES + 1]; + size_t svds_len; +}; + +struct di_cta_sad_priv { + struct di_cta_sad base; + struct di_cta_sad_sample_rates supported_sample_rates; + struct di_cta_sad_lpcm lpcm; + struct di_cta_sad_mpegh_3d mpegh_3d; + struct di_cta_sad_mpeg_aac mpeg_aac; + struct di_cta_sad_mpeg_surround mpeg_surround; + struct di_cta_sad_mpeg_aac_le mpeg_aac_le; + struct di_cta_sad_enhanced_ac3 enhanced_ac3; + struct di_cta_sad_mat mat; + struct di_cta_sad_wma_pro wma_pro; +}; + +struct di_cta_audio_block_priv { + struct di_cta_audio_block audio; + /* NULL-terminated */ + struct di_cta_sad_priv *sads[EDID_CTA_MAX_AUDIO_BLOCK_ENTRIES + 1]; + size_t sads_len; +}; + +struct di_cta_ycbcr420_cap_map_block { + bool all; + uint8_t svd_bitmap[EDID_CTA_MAX_YCBCR420_CAP_MAP_BLOCK_ENTRIES]; +}; + + +struct di_cta_hdmi_audio_block_priv { + struct di_cta_hdmi_audio_block base; + struct di_cta_hdmi_audio_multi_stream ms; + struct di_cta_hdmi_audio_3d a3d; + /* NULL-terminated */ + struct di_cta_sad_priv *sads[EDID_CTA_MAX_HDMI_AUDIO_BLOCK_ENTRIES + 1]; + size_t sads_len; +}; + +struct di_cta_infoframe_block_priv { + struct di_cta_infoframe_block base; + /* NULL-terminated */ + struct di_cta_infoframe_descriptor *infoframes[EDID_CTA_INFOFRAME_BLOCK_ENTRIES + 1]; + size_t infoframes_len; +}; + +struct di_cta_speaker_location_priv { + struct di_cta_speaker_location_block base; + /* NULL-terminated */ + struct di_cta_speaker_location_descriptor *locations[EDID_CTA_MAX_SPEAKER_LOCATION_BLOCK_ENTRIES + 1]; + size_t locations_len; +}; + +struct di_cta_video_format_pref_priv { + struct di_cta_video_format_pref_block base; + /* NULL-terminated */ + struct di_cta_svr *svrs[EDID_CTA_MAX_VIDEO_FORMAT_PREF_BLOCK_ENTRIES + 1]; + size_t svrs_len; +}; + +struct di_cta_type_vii_timing_priv { + struct di_cta_type_vii_timing_block base; + struct di_displayid_type_i_ii_vii_timing timing; +}; + +struct di_cta_vendor_hdmi_block_priv { + struct di_cta_vendor_hdmi_block base; + /* HDMI VIC's. */ + struct di_hdmi_vic *vics; +}; + +struct di_cta_dolby_video_block_priv { + struct di_cta_dolby_video_block base; + struct di_cta_dolby_video_block_v0 v0; + struct di_cta_dolby_video_block_v1 v1; + struct di_cta_dolby_video_block_v2 v2; +}; + +struct di_cta_vendor_hdmi_forum_block_priv { + struct di_cta_vendor_hdmi_forum_block base; + struct di_cta_hdmi_dsc dsc; +}; + +struct di_cta_hdmi_forum_sink_cap_priv { + struct di_cta_hdmi_forum_sink_cap base; + struct di_cta_hdmi_dsc dsc; +}; + +struct di_cta_data_block { + enum di_cta_data_block_tag tag; + + /* Used for DI_CTA_DATA_BLOCK_VIDEO */ + struct di_cta_video_block_priv video; + /* Used for DI_CTA_DATA_BLOCK_YCBCR420 */ + struct di_cta_ycbcr420_video_block_priv ycbcr420; + /* used for DI_CTA_DATA_BLOCK_AUDIO */ + struct di_cta_audio_block_priv audio; + /* Used for DI_CTA_DATA_BLOCK_SPEAKER_ALLOC */ + struct di_cta_speaker_alloc_block speaker_alloc; + /* Used for DI_CTA_DATA_BLOCK_VIDEO_CAP */ + struct di_cta_video_cap_block video_cap; + /* Used for DI_CTA_DATA_BLOCK_VESA_DISPLAY_DEVICE */ + struct di_cta_vesa_display_device_block vesa_display_device; + /* Used for DI_CTA_DATA_BLOCK_COLORIMETRY */ + struct di_cta_colorimetry_block colorimetry; + /* Used for DI_CTA_DATA_BLOCK_HDR_STATIC_METADATA */ + struct di_cta_hdr_static_metadata_block_priv hdr_static_metadata; + /* Used for DI_CTA_DATA_BLOCK_HDR_DYNAMIC_METADATA */ + struct di_cta_hdr_dynamic_metadata_block_priv hdr_dynamic_metadata; + /* Used for DI_CTA_DATA_BLOCK_VESA_DISPLAY_TRANSFER_CHARACTERISTIC */ + struct di_cta_vesa_transfer_characteristics_block vesa_transfer_characteristics; + /* Used for DI_CTA_DATA_BLOCK_YCBCR420_CAP_MAP */ + struct di_cta_ycbcr420_cap_map_block ycbcr420_cap_map; + /* Used for DI_CTA_DATA_BLOCK_HDMI_AUDIO */ + struct di_cta_hdmi_audio_block_priv hdmi_audio; + /* Used for DI_CTA_DATA_BLOCK_INFOFRAME */ + struct di_cta_infoframe_block_priv infoframe; + /* Used for DI_CTA_DATA_BLOCK_ROOM_CONFIG */ + struct di_cta_room_configuration_block room_config; + /* Used for DI_CTA_DATA_BLOCK_SPEAKER_LOCATION */ + struct di_cta_speaker_location_priv speaker_location; + /* Used for DI_CTA_DATA_BLOCK_VIDEO_FORMAT_PREF */ + struct di_cta_video_format_pref_priv video_format_pref; + /* Used for DI_CTA_DATA_BLOCK_DISPLAYID_VIDEO_TIMING_VII */ + struct di_cta_type_vii_timing_priv did_vii_timing; + /* Used for DI_CTA_DATA_BLOCK_VENDOR_HDMI */ + struct di_cta_vendor_hdmi_block_priv vendor_hdmi; + /* Used for DI_CTA_DATA_BLOCK_HDR10PLUS */ + struct di_cta_hdr10plus_block hdr10plus; + /* Used for DI_CTA_DATA_BLOCK_DOLBY_VIDEO */ + struct di_cta_dolby_video_block_priv dolby_video; + /* Used for DI_CTA_DATA_BLOCK_HDMI_SINK_CAP */ + struct di_cta_hdmi_forum_sink_cap_priv hdmi_sink_cap; + /* Used for DI_CTA_DATA_BLOCK_VENDOR_HDMI_FORUM */ + struct di_cta_vendor_hdmi_forum_block_priv vendor_hdmi_forum; +}; + +bool +_di_edid_cta_parse(struct di_edid_cta *cta, const uint8_t *data, size_t size, + struct di_logger *logger); + +void +_di_edid_cta_finish(struct di_edid_cta *cta); + +bool +_di_cta_data_block_parse(struct di_cta *cta, uint8_t raw_tag, const uint8_t *data, size_t size, + struct di_cta_data_block **data_block_out); + +void +_di_cta_data_block_destroy(struct di_cta_data_block *data_block); + +#endif diff --git a/local/recipes/libs/libdisplay-info/source/include/di-edid-decode.h b/local/recipes/libs/libdisplay-info/source/include/di-edid-decode.h new file mode 100644 index 0000000000..15c8aca1d6 --- /dev/null +++ b/local/recipes/libs/libdisplay-info/source/include/di-edid-decode.h @@ -0,0 +1,63 @@ +#ifndef DI_EDID_DECODE_H +#define DI_EDID_DECODE_H + +#include + +#include +#include +#include +#include + +struct uncommon_features { + bool color_point_descriptor; + bool color_management_data; + bool cta_transfer_characteristics; +}; + +extern struct uncommon_features uncommon_features; + +struct di_edid; +struct di_edid_detailed_timing_def; +struct di_edid_cta; +struct di_displayid; + +void +print_edid(const struct di_edid *edid); + +void +print_detailed_timing_def(const struct di_edid_detailed_timing_def *def); + +void +print_cta(const struct di_edid_cta *cta); + +void +print_cta_data_block(const struct di_cta_data_block *data_block, + const struct di_cta_data_block *const *data_blocks); + +void +print_displayid(const struct di_displayid *displayid); + +void +print_displayid2(const struct di_displayid2 *displayid2); + +void +print_displayid_type_i_ii_vii_timing(const struct di_displayid_type_i_ii_vii_timing *t, + int indent, const char *prefix); + +void +print_cta_vic_timing(struct di_cta_vic vic); + +void +print_hdmi_vic_timing(const struct di_hdmi_vic vic); + +void +print_dmt_timing_code(struct di_dmt_code dmt_code); + +void +print_cvt_timing(struct di_cvt_timing *t, struct di_cvt_options *options, + int hratio, int vratio, bool preferred, bool rb); + +void +compute_aspect_ratio(int width, int height, int *horiz_ratio, int *vert_ratio); + +#endif diff --git a/local/recipes/libs/libdisplay-info/source/include/displayid.h b/local/recipes/libs/libdisplay-info/source/include/displayid.h new file mode 100644 index 0000000000..932ffb9ed5 --- /dev/null +++ b/local/recipes/libs/libdisplay-info/source/include/displayid.h @@ -0,0 +1,108 @@ +#ifndef DISPLAYID_H +#define DISPLAYID_H + +/** + * Private header for the low-level DisplayID API. + */ + +#include +#include +#include + +#include + +#include "log.h" + +/** + * The maximum number of data blocks in a DisplayID section. + * + * A DisplayID section has a payload size of 251 bytes, and each data block has + * a minimum size of 3 bytes. + */ +#define DISPLAYID_MAX_DATA_BLOCKS 83 +/** + * The maximum number of type I timings in a data block. + * + * A DisplayID data block has a maximum payload size of 248 bytes, and each type + * I timing takes up 20 bytes. + */ +#define DISPLAYID_MAX_TYPE_I_TIMINGS 12 +/** + * The maximum number of type II timings in a data block. + * + * A DisplayID data block has a maximum payload size of 248 bytes, and each type + * I timing takes up 11 bytes. + */ +#define DISPLAYID_MAX_TYPE_II_TIMINGS 22 +/** + * The maxiumum number of type III timings in a data block. + * + * A DisplayID data block has a maximum payload size of 248 bytes, and each type + * III timing takes up 3 bytes. + */ +#define DISPLAYID_MAX_TYPE_III_TIMINGS 82 + +struct di_displayid { + int version, revision; + enum di_displayid_product_type product_type; + + struct di_displayid_data_block *data_blocks[DISPLAYID_MAX_DATA_BLOCKS + 1]; + size_t data_blocks_len; + + struct di_logger *logger; +}; + +struct di_displayid_display_params_priv { + struct di_displayid_display_params base; + struct di_displayid_display_params_features features; +}; + +struct di_displayid_tiled_topo_priv { + struct di_displayid_tiled_topo base; + struct di_displayid_tiled_topo_caps caps; + struct di_displayid_tiled_topo_bezel bezel; +}; + +struct di_displayid_data_block { + enum di_displayid_data_block_tag tag; + + /* Used for TYPE_I_TIMING, NULL-terminated */ + struct di_displayid_type_i_ii_vii_timing *type_i_timings[DISPLAYID_MAX_TYPE_I_TIMINGS + 1]; + size_t type_i_timings_len; + + /* Used for TYPE_II_TIMING, NULL-terminated */ + struct di_displayid_type_i_ii_vii_timing *type_ii_timings[DISPLAYID_MAX_TYPE_II_TIMINGS + 1]; + size_t type_ii_timings_len; + + /* Used for TYPE_III_TIMING, NULL-terminated */ + struct di_displayid_type_iii_timing *type_iii_timings[DISPLAYID_MAX_TYPE_III_TIMINGS + 1]; + size_t type_iii_timings_len; + + /* Used for TYPE_IV_TIMING */ + struct di_displayid_type_iv_timing type_iv_timing; + + /* Used for DISPLAY_PARAMS */ + struct di_displayid_display_params_priv display_params; + + /* Used for TILED_DISPLAY_TOPO */ + struct di_displayid_tiled_topo_priv tiled_topo; +}; + +bool +_di_displayid_parse(struct di_displayid *displayid, const uint8_t *data, + size_t size, struct di_logger *logger); + +void +_di_displayid_finish(struct di_displayid *displayid); + +void +_di_displayid_parse_type_1_7_timing(struct di_displayid_type_i_ii_vii_timing *timing, + struct di_logger *logger, + const char *prefix, + const uint8_t *data, + bool is_type7); + +int +_di_displayid_parse_version(const uint8_t *data, size_t size); + +#endif diff --git a/local/recipes/libs/libdisplay-info/source/include/displayid2.h b/local/recipes/libs/libdisplay-info/source/include/displayid2.h new file mode 100644 index 0000000000..a58c87fc38 --- /dev/null +++ b/local/recipes/libs/libdisplay-info/source/include/displayid2.h @@ -0,0 +1,64 @@ +#ifndef DISPLAYID2_H +#define DISPLAYID2_H + +/** + * Private header for the low-level DisplayID v2 API. + */ + +#include +#include +#include + +#include + +#include "cta.h" +#include "log.h" + +/** + * The maximum number of data blocks in a DisplayID v2 section. + * + * A DisplayID v2 section has a maximum payload size of 251 bytes (256 bytes + * maximum size, 5 bytes header), and each data block has a minimum size of + * 3 bytes. + */ +#define DISPLAYID2_MAX_DATA_BLOCKS 83 + +/** + * The maximum number of CTA data blocks in a DisplayID v2 CTA-861 data block. + * + * A DisplayID v2 section has a maximum payload size of 251 bytes (256 bytes + * maximum size, 5 bytes header), a DisplayID v2 data block has a maximum + * payload size of 248 bytes (3 bytes header), and each CTA data block takes at + * least 1 byte. + */ +#define DISPLAYID2_CTA861_MAX_DATA_BLOCKS 248 + +struct di_displayid2 { + int revision; + enum di_displayid2_product_primary_use_case product_primary_use_case; + + struct di_displayid2_data_block *data_blocks[DISPLAYID2_MAX_DATA_BLOCKS + 1]; + size_t data_blocks_len; + + struct di_logger *logger; +}; + +struct di_displayid2_data_block { + enum di_displayid2_data_block_tag tag; + + /* Used for DI_DISPLAYID2_DATA_BLOCK_CTA861 */ + struct { + /* NULL-terminated */ + struct di_cta_data_block *data_blocks[DISPLAYID2_CTA861_MAX_DATA_BLOCKS + 1]; + size_t data_blocks_len; + } cta861; +}; + +bool +_di_displayid2_parse(struct di_displayid2 *displayid2, const uint8_t *data, + size_t size, struct di_logger *logger); + +void +_di_displayid2_finish(struct di_displayid2 *displayid2); + +#endif diff --git a/local/recipes/libs/libdisplay-info/source/include/edid.h b/local/recipes/libs/libdisplay-info/source/include/edid.h new file mode 100644 index 0000000000..0971f6e2ce --- /dev/null +++ b/local/recipes/libs/libdisplay-info/source/include/edid.h @@ -0,0 +1,160 @@ +#ifndef EDID_H +#define EDID_H + +/** + * Private header for the low-level EDID API. + */ + +#include +#include +#include + +#include + +#include "cta.h" +#include "displayid.h" +#include "displayid2.h" + +/** + * The maximum number of EDID blocks (including the base block), defined in + * section 2.2.1. + */ +#define EDID_MAX_BLOCK_COUNT 256 +/** + * The maximum number of EDID standard timings, defined in section 3.9. + */ +#define EDID_MAX_STANDARD_TIMING_COUNT 8 +/** + * The number of EDID byte descriptors, defined in section 3.10. + */ +#define EDID_BYTE_DESCRIPTOR_COUNT 4 +/** + * The size of an EDID byte descriptor, defined in section 3.10. + */ +#define EDID_BYTE_DESCRIPTOR_SIZE 18 +/** + * The maximum number of standard timings in an EDID display descriptor, defined + * in section 3.10.3.6. + */ +#define EDID_MAX_DESCRIPTOR_STANDARD_TIMING_COUNT 6 +/** + * The maximum number of color points in an EDID color point descriptor, defined + * in section 3.10.3.5. + */ +#define EDID_MAX_DESCRIPTOR_COLOR_POINT_COUNT 2 +/** + * The maximum number of established timings III in an EDID display descriptor, + * defined in section 3.10.3.9. + */ +#define EDID_MAX_DESCRIPTOR_ESTABLISHED_TIMING_III_COUNT 44 +/** + * The maximum number of CVT timing codes in an EDID display descriptor, + * defined in section 3.10.3.8. + */ +#define EDID_MAX_DESCRIPTOR_CVT_TIMING_CODES_COUNT 4 + +struct di_edid_detailed_timing_def_priv { + struct di_edid_detailed_timing_def base; + struct di_edid_detailed_timing_analog_composite analog_composite; + struct di_edid_detailed_timing_bipolar_analog_composite bipolar_analog_composite; + struct di_edid_detailed_timing_digital_composite digital_composite; + struct di_edid_detailed_timing_digital_separate digital_separate; +}; + +struct di_edid { + struct di_edid_vendor_product vendor_product; + int version, revision; + + bool is_digital; + struct di_edid_video_input_analog video_input_analog; + struct di_edid_video_input_digital video_input_digital; + struct di_edid_screen_size screen_size; + float gamma; + struct di_edid_dpms dpms; + enum di_edid_display_color_type display_color_type; + struct di_edid_color_encoding_formats color_encoding_formats; + struct di_edid_misc_features misc_features; + + struct di_edid_chromaticity_coords chromaticity_coords; + struct di_edid_established_timings_i_ii established_timings_i_ii; + + /* NULL-terminated */ + struct di_edid_standard_timing *standard_timings[EDID_MAX_STANDARD_TIMING_COUNT + 1]; + size_t standard_timings_len; + + /* NULL-terminated */ + struct di_edid_detailed_timing_def_priv *detailed_timing_defs[EDID_BYTE_DESCRIPTOR_COUNT + 1]; + size_t detailed_timing_defs_len; + + /* NULL-terminated */ + struct di_edid_display_descriptor *display_descriptors[EDID_BYTE_DESCRIPTOR_COUNT + 1]; + size_t display_descriptors_len; + + /* NULL-terminated, doesn't include the base block */ + struct di_edid_ext *exts[EDID_MAX_BLOCK_COUNT]; + size_t exts_len; + + struct di_logger *logger; +}; + +struct di_edid_display_range_limits_priv { + struct di_edid_display_range_limits base; + struct di_edid_display_range_limits_secondary_gtf secondary_gtf; + struct di_edid_display_range_limits_cvt cvt; +}; + +struct di_edid_display_descriptor { + enum di_edid_display_descriptor_tag tag; + /* Used for PRODUCT_SERIAL, DATA_STRING and PRODUCT_NAME, + * zero-terminated */ + char str[14]; + /* Used for RANGE_LIMITS */ + struct di_edid_display_range_limits_priv range_limits; + /* Used for STD_TIMING_IDS, NULL-terminated */ + struct di_edid_standard_timing *standard_timings[EDID_MAX_DESCRIPTOR_STANDARD_TIMING_COUNT + 1]; + size_t standard_timings_len; + /* Used for COLOR_POINT, NULL-terminated */ + struct di_edid_color_point *color_points[EDID_MAX_DESCRIPTOR_COLOR_POINT_COUNT + 1]; + size_t color_points_len; + /* Used for ESTABLISHED_TIMINGS_III */ + struct di_edid_established_timings_iii established_timings_iii; + /* Used for DCM_DATA */ + struct di_edid_color_management_data dcm_data; + /* Used for CVT_TIMING_CODES, NULL-terminated */ + struct di_edid_cvt_timing_code *cvt_timing_codes[EDID_MAX_DESCRIPTOR_CVT_TIMING_CODES_COUNT + 1]; + size_t cvt_timing_codes_len; +}; + +struct di_edid_ext { + enum di_edid_ext_tag tag; + /* Used for DI_EDID_EXT_CEA */ + struct di_edid_cta cta; + /* Used for DI_EDID_EXT_DISPLAYID */ + int displayid_version; + struct di_displayid displayid; + struct di_displayid2 displayid2; +}; + +/** + * Create an EDID data structure. + * + * Callers do not need to keep the provided data pointer valid after calling + * this function. Callers should destroy the returned pointer via + * di_edid_destroy(). + */ +struct di_edid * +_di_edid_parse(const void *data, size_t size, FILE *failure_msg_file); + +/** + * Destroy an EDID data structure. + */ +void +_di_edid_destroy(struct di_edid *edid); + +/** + * Parse an EDID detailed timing definition. + */ +struct di_edid_detailed_timing_def_priv * +_di_edid_parse_detailed_timing_def(const uint8_t data[static EDID_BYTE_DESCRIPTOR_SIZE]); + +#endif diff --git a/local/recipes/libs/libdisplay-info/source/include/info.h b/local/recipes/libs/libdisplay-info/source/include/info.h new file mode 100644 index 0000000000..adbfcdff67 --- /dev/null +++ b/local/recipes/libs/libdisplay-info/source/include/info.h @@ -0,0 +1,28 @@ +#ifndef INFO_H +#define INFO_H + +/** + * Private header for the high-level API. + */ + +#include + +/** + * All information here is derived from low-level information contained in + * struct di_info. These are exposed by the high-level API only. + */ +struct di_derived_info { + struct di_hdr_static_metadata hdr_static_metadata; + struct di_color_primaries color_primaries; + struct di_supported_signal_colorimetry supported_signal_colorimetry; +}; + +struct di_info { + struct di_edid *edid; + + char *failure_msg; + + struct di_derived_info derived; +}; + +#endif diff --git a/local/recipes/libs/libdisplay-info/source/include/libdisplay-info/cta-vic.h b/local/recipes/libs/libdisplay-info/source/include/libdisplay-info/cta-vic.h new file mode 100644 index 0000000000..d545fe2e55 --- /dev/null +++ b/local/recipes/libs/libdisplay-info/source/include/libdisplay-info/cta-vic.h @@ -0,0 +1,78 @@ +#ifndef DI_CTA_VIC_H +#define DI_CTA_VIC_H + +#include +#include +#include + +/** + * Low-level API for CTA Video Identification Codes (CTA VIC). + */ + +/** + * A CTA Video Identification Code (VIC). + */ +struct di_cta_vic { + /* Video Identification Code (VIC) */ + uint8_t code; +}; + +/** + * CTA video format picture aspect ratio. + */ +enum di_cta_vic_video_format_picture_aspect_ratio { + DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_4_3, /* 4:3 */ + DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, /* 16:9 */ + DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, /* 64:27 */ + DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_256_135, /* 256:135 */ +}; + +/** + * CTA video format sync pulse polarity. + */ +enum di_cta_vic_video_format_sync_polarity { + DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, /* Negative */ + DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, /* Positive */ +}; + +/** + * A CTA-861 video format, defined in section 4. + */ +struct di_cta_vic_video_format { + /* Video Identification Code (VIC) */ + struct di_cta_vic vic; + /* Horizontal/vertical active pixels/lines */ + int32_t h_active, v_active; + /* Horizontal/vertical front porch */ + int32_t h_front, v_front; + /* Horizontal/vertical sync pulse */ + int32_t h_sync, v_sync; + /* Horizontal/vertical back porch */ + int32_t h_back, v_back; + /* Horizontal/vertical sync pulse polarity */ + enum di_cta_vic_video_format_sync_polarity h_sync_polarity, v_sync_polarity; + /* Pixel clock in Hz */ + int64_t pixel_clock_hz; + /* Whether this timing is interlaced */ + bool interlaced; + /* Picture aspect ratio */ + enum di_cta_vic_video_format_picture_aspect_ratio picture_aspect_ratio; +}; + +/** + * Get a CTA-861 video format from a CTA VIC. + * + * Returns NULL if the CTA VIC is unknown. + */ +const struct di_cta_vic_video_format * +di_cta_vic_video_format_from_vic(struct di_cta_vic vic); + +/** + * Get a CTA VIC from a CTA-861 video format. + * + * Returns 0 if the video format is unknown. + */ +struct di_cta_vic +di_cta_vic_video_format_to_vic(const struct di_cta_vic_video_format *format); + +#endif diff --git a/local/recipes/libs/libdisplay-info/source/include/libdisplay-info/cta.h b/local/recipes/libs/libdisplay-info/source/include/libdisplay-info/cta.h index d62bf07bb6..0b485ef17f 100644 --- a/local/recipes/libs/libdisplay-info/source/include/libdisplay-info/cta.h +++ b/local/recipes/libs/libdisplay-info/source/include/libdisplay-info/cta.h @@ -1,3 +1,1559 @@ -#pragma once +#ifndef DI_CTA_H +#define DI_CTA_H -#include "info.h" +/** + * libdisplay-info's low-level API for Consumer Technology Association + * standards. + * + * The library implements CTA-861-H, available at: + * https://shop.cta.tech/collections/standards/products/a-dtv-profile-for-uncompressed-high-speed-digital-interfaces-cta-861-h + */ + +#include +#include +#include + +#include +#include + +/** + * EDID CTA-861 extension block. + */ +struct di_edid_cta; + +/** + * Get the CTA extension revision (also referred to as "version" by the + * specification). + */ +int +di_edid_cta_get_revision(const struct di_edid_cta *cta); + +/** + * Miscellaneous EDID CTA flags, defined in section 7.3.3. + * + * For CTA revision 1, all of the fields are zero. + */ +struct di_edid_cta_flags { + /* Sink underscans IT Video Formats by default */ + bool it_underscan; + /* Sink supports Basic Audio */ + bool basic_audio; + /* Sink supports YCbCr 4:4:4 in addition to RGB */ + bool ycc444; + /* Sink supports YCbCr 4:2:2 in addition to RGB */ + bool ycc422; + /* Total number of native detailed timing descriptors */ + int native_dtds; +}; + +/** + * Get miscellaneous CTA flags. + */ +const struct di_edid_cta_flags * +di_edid_cta_get_flags(const struct di_edid_cta *cta); + +/** + * Get a list of EDID detailed timing definitions. + * + * The returned array is NULL-terminated. + */ +const struct di_edid_detailed_timing_def *const * +di_edid_cta_get_detailed_timing_defs(const struct di_edid_cta *cta); + +/** + * CTA data block, defined in section 7.4. + */ +struct di_cta_data_block; + +/** + * Get CTA data blocks. + * + * The returned array is NULL-terminated. + */ +const struct di_cta_data_block *const * +di_edid_cta_get_data_blocks(const struct di_edid_cta *cta); + +/** + * CTA data block tag. + * + * Note, the enum values don't match the specification. + */ +enum di_cta_data_block_tag { + /* Audio Data Block */ + DI_CTA_DATA_BLOCK_AUDIO = 1, + /* Video Data Block */ + DI_CTA_DATA_BLOCK_VIDEO, + /* Speaker Allocation Data Block */ + DI_CTA_DATA_BLOCK_SPEAKER_ALLOC, + /* VESA Display Transfer Characteristic Data Block */ + DI_CTA_DATA_BLOCK_VESA_DISPLAY_TRANSFER_CHARACTERISTIC, + /* Video Format Data Block */ + DI_CTA_DATA_BLOCK_VIDEO_FORMAT, + + /* Video Capability Data Block */ + DI_CTA_DATA_BLOCK_VIDEO_CAP, + /* VESA Display Device Data Block */ + DI_CTA_DATA_BLOCK_VESA_DISPLAY_DEVICE, + /* Colorimetry Data Block */ + DI_CTA_DATA_BLOCK_COLORIMETRY, + /* HDR Static Metadata Data Block */ + DI_CTA_DATA_BLOCK_HDR_STATIC_METADATA, + /* HDR Dynamic Metadata Data Block */ + DI_CTA_DATA_BLOCK_HDR_DYNAMIC_METADATA, + /* Native Video Resolution Data Block */ + DI_CTA_DATA_BLOCK_NATIVE_VIDEO_RESOLUTION, + /* Video Format Preference Data Block */ + DI_CTA_DATA_BLOCK_VIDEO_FORMAT_PREF, + /* YCbCr 4:2:0 Video Data Block */ + DI_CTA_DATA_BLOCK_YCBCR420, + /* YCbCr 4:2:0 Capability Map Data Block */ + DI_CTA_DATA_BLOCK_YCBCR420_CAP_MAP, + /* HDMI Audio Data Block */ + DI_CTA_DATA_BLOCK_HDMI_AUDIO, + /* Room Configuration Data Block */ + DI_CTA_DATA_BLOCK_ROOM_CONFIG, + /* Speaker Location Data Block */ + DI_CTA_DATA_BLOCK_SPEAKER_LOCATION, + /* InfoFrame Data Block */ + DI_CTA_DATA_BLOCK_INFOFRAME, + /* DisplayID Type VII Video Timing Data Block */ + DI_CTA_DATA_BLOCK_DISPLAYID_VIDEO_TIMING_VII, + /* DisplayID Type VIII Video Timing Data Block */ + DI_CTA_DATA_BLOCK_DISPLAYID_VIDEO_TIMING_VIII, + /* DisplayID Type X Video Timing Data Block */ + DI_CTA_DATA_BLOCK_DISPLAYID_VIDEO_TIMING_X, + /* HDMI Forum EDID Extension Override Data Block */ + DI_CTA_DATA_BLOCK_HDMI_EDID_EXT_OVERRIDE, + /* HDMI Forum Sink Capability Data Block */ + DI_CTA_DATA_BLOCK_HDMI_SINK_CAP, + + /* HDMI Vendor-Specific Data Block */ + DI_CTA_DATA_BLOCK_VENDOR_HDMI, + /* Dolby Video Vendor-Specific Data Block */ + DI_CTA_DATA_BLOCK_DOLBY_VIDEO, + /* HDR10+ Video Vendor-Specific Data Block */ + DI_CTA_DATA_BLOCK_HDR10PLUS, + /* HDMI Forum Vendor-Specific Data Block */ + DI_CTA_DATA_BLOCK_VENDOR_HDMI_FORUM, +}; + +/** + * Get the tag of the CTA data block. + */ +enum di_cta_data_block_tag +di_cta_data_block_get_tag(const struct di_cta_data_block *block); + +/** + * Audio formats, defined in tables 37 and 39. + * + * Note, the enum values don't match the specification. + */ +enum di_cta_audio_format { + /* L-PCM */ + DI_CTA_AUDIO_FORMAT_LPCM = 1, + /* AC-3 */ + DI_CTA_AUDIO_FORMAT_AC3, + /* MPEG-1 (layers 1 & 2) */ + DI_CTA_AUDIO_FORMAT_MPEG1, + /* MP3 */ + DI_CTA_AUDIO_FORMAT_MP3, + /* MPEG-2 */ + DI_CTA_AUDIO_FORMAT_MPEG2, + /* AAC LC */ + DI_CTA_AUDIO_FORMAT_AAC_LC, + /* DTS */ + DI_CTA_AUDIO_FORMAT_DTS, + /* ATRAC */ + DI_CTA_AUDIO_FORMAT_ATRAC, + /* One Bit Audio */ + DI_CTA_AUDIO_FORMAT_ONE_BIT_AUDIO, + /* Enhanced AC-3 */ + DI_CTA_AUDIO_FORMAT_ENHANCED_AC3, + /* DTS-HD and DTS-UHD */ + DI_CTA_AUDIO_FORMAT_DTS_HD, + /* MAT */ + DI_CTA_AUDIO_FORMAT_MAT, + /* DST */ + DI_CTA_AUDIO_FORMAT_DST, + /* WMA Pro */ + DI_CTA_AUDIO_FORMAT_WMA_PRO, + + /* MPEG-4 HE AAC */ + DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC, + /* MPEG-4 HE AAC v2 */ + DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC_V2, + /* MPEG-4 AAC LC */ + DI_CTA_AUDIO_FORMAT_MPEG4_AAC_LC, + /* DRA */ + DI_CTA_AUDIO_FORMAT_DRA, + /* MPEG-4 HE AAC + MPEG Surround */ + DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC_MPEG_SURROUND, + /* MPEG-4 AAC LC + MPEG Surround */ + DI_CTA_AUDIO_FORMAT_MPEG4_AAC_LC_MPEG_SURROUND, + /* MPEG-H 3D Audio */ + DI_CTA_AUDIO_FORMAT_MPEGH_3D, + /* AC-4 */ + DI_CTA_AUDIO_FORMAT_AC4, + /* L-PCM 3D Audio */ + DI_CTA_AUDIO_FORMAT_LPCM_3D, +}; + +struct di_cta_sad_sample_rates { + bool has_192_khz; /* 192 kHz */ + bool has_176_4_khz; /* 176.4 kHz */ + bool has_96_khz; /* 96 kHz */ + bool has_88_2_khz; /* 88.2 kHz */ + bool has_48_khz; /* 48 kHz */ + bool has_44_1_khz; /* 44.1 kHz */ + bool has_32_khz; /* 32 kHz */ +}; + +enum di_cta_sad_mpegh_3d_level { + DI_CTA_SAD_MPEGH_3D_LEVEL_UNSPECIFIED = 0, + DI_CTA_SAD_MPEGH_3D_LEVEL_1 = 1, + DI_CTA_SAD_MPEGH_3D_LEVEL_2 = 2, + DI_CTA_SAD_MPEGH_3D_LEVEL_3 = 3, + DI_CTA_SAD_MPEGH_3D_LEVEL_4 = 4, + DI_CTA_SAD_MPEGH_3D_LEVEL_5 = 5, +}; + +struct di_cta_sad_mpegh_3d { + /* Maximum supported MPEG-H 3D level, zero if unspecified */ + enum di_cta_sad_mpegh_3d_level level; + /* True if MPEG-H 3D Audio Low Complexity Profile is supported */ + bool low_complexity_profile; + /* True if MPEG-H 3D Audio Baseline Profile is supported */ + bool baseline_profile; +}; + +struct di_cta_sad_mpeg_aac { + /* True if AAC audio frame lengths of 960 samples is supported */ + bool has_frame_length_960; + /* True if AAC audio frame lengths of 1024 samples is supported */ + bool has_frame_length_1024; +}; + +enum di_cta_sad_mpeg_surround_signaling { + /* Only implicitly signaled MPEG Surround data supported */ + DI_CTA_SAD_MPEG_SURROUND_SIGNALING_IMPLICIT = 0, + /* Implicitly and explicitly signaled MPEG Surround data supported */ + DI_CTA_SAD_MPEG_SURROUND_SIGNALING_IMPLICIT_AND_EXPLICIT = 1, +}; + +struct di_cta_sad_mpeg_surround { + /* MPEG Surround signaling */ + enum di_cta_sad_mpeg_surround_signaling signaling; +}; + +struct di_cta_sad_mpeg_aac_le { + /* True if Rec. ITU-R BS.2051 System H 22.2 multichannel sound is supported */ + bool supports_multichannel_sound; +}; + +struct di_cta_sad_lpcm { + bool has_sample_size_24_bits; /* 24 bits */ + bool has_sample_size_20_bits; /* 20 bits */ + bool has_sample_size_16_bits; /* 16 bits */ +}; + +struct di_cta_sad_enhanced_ac3 { + bool supports_joint_object_coding; + bool supports_joint_object_coding_ACMOD28; +}; + +struct di_cta_sad_mat { + bool supports_object_audio_and_channel_based; + bool requires_hash_calculation; +}; + +struct di_cta_sad_wma_pro { + int profile; +}; + +/** + * A CTA short audio descriptor (SAD), defined in section 7.5.2. + */ +struct di_cta_sad { + /* Format */ + enum di_cta_audio_format format; + /* Maximum number of channels, zero if unset */ + int32_t max_channels; + /* Supported sample rates */ + const struct di_cta_sad_sample_rates *supported_sample_rates; + /* Maximum bitrate (kb/s), zero if unset */ + int32_t max_bitrate_kbs; + /* Additional metadata for LPCM, NULL unless format is + * DI_CTA_AUDIO_FORMAT_LPCM or DI_CTA_AUDIO_FORMAT_LPCM_3D */ + const struct di_cta_sad_lpcm *lpcm; + /* Additional metadata for MPEG-H 3D Audio, NULL unless format is + * DI_CTA_AUDIO_FORMAT_MPEGH_3D */ + const struct di_cta_sad_mpegh_3d *mpegh_3d; + /* Additional metadata for MPEG4 AAC, NULL unless format is + * DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC or + * DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC_V2 or + * DI_CTA_AUDIO_FORMAT_MPEG4_AAC_LC or + * DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC_MPEG_SURROUND or + * DI_CTA_AUDIO_FORMAT_MPEG4_AAC_LC_MPEG_SURROUND */ + const struct di_cta_sad_mpeg_aac *mpeg_aac; + /* Additional metadata for MPEG4 Surround, NULL unless format is + * DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC_MPEG_SURROUND or + * DI_CTA_AUDIO_FORMAT_MPEG4_AAC_LC_MPEG_SURROUND */ + const struct di_cta_sad_mpeg_surround *mpeg_surround; + /* Additional metadata for MPEG4 AAC LC, NULL unless format is + * DI_CTA_AUDIO_FORMAT_MPEG4_AAC_LC */ + const struct di_cta_sad_mpeg_aac_le *mpeg_aac_le; + /* Additional metadata for Enhanced AC-3, NULL unless format is + * DI_CTA_AUDIO_FORMAT_ENHANCED_AC3 */ + const struct di_cta_sad_enhanced_ac3 *enhanced_ac3; + /* Additional metadata for Dolby MAT, NULL unless format is + * DI_CTA_AUDIO_FORMAT_MAT */ + const struct di_cta_sad_mat *mat; + /* Additional metadata for WMA Pro, NULL unless format is + * DI_CTA_AUDIO_FORMAT_WMA_PRO */ + const struct di_cta_sad_wma_pro *wma_pro; +}; + + +/** + * Audio Data Block, defined in section 7.5.2. + */ +struct di_cta_audio_block { + /* Short audio descriptors. The array is NULL-terminated. */ + const struct di_cta_sad *const *sads; +}; + +/** + * Get the audio from a CTA data block. + * + * Returns NULL if the data block tag is not DI_CTA_DATA_BLOCK_AUDIO. + */ +const struct di_cta_audio_block * +di_cta_data_block_get_audio(const struct di_cta_data_block *data_block); + +/** + * Indicates which speakers are present. See figure 6 for the meaning of the + * fields. + */ +struct di_cta_speaker_allocation { + bool flw_frw; /* FLw/FRw - Front Left/Right Wide */ + bool flc_frc; /* FLc/FRc - Front Left/Right of Center */ + bool bc; /* BC - Back Center */ + bool bl_br; /* BL/BR - Back Left/Right */ + bool fc; /* FC - Front Center */ + bool lfe1; /* LFE1 - Low Frequency Effects 1 */ + bool fl_fr; /* FL/FR - Front Left/Right */ + bool tpsil_tpsir; /* TpSiL/TpSiR - Top Side Left/Right */ + bool sil_sir; /* SiL/SiR - Side Left/Right */ + bool tpbc; /* TpBC - Top Back Center */ + bool lfe2; /* LFE2 - Low Frequency Effects 2 */ + bool ls_rs; /* LS/RS - Left/Right Surround*/ + bool tpfc; /* TpFC - Top Front Center */ + bool tpc; /* TpC - Top Center*/ + bool tpfl_tpfr; /* TpFL/TpFR - Top Front Left/Right */ + bool btfl_btfr; /* BtFL/BtFR - Bottom Front Left/Right */ + bool btfc; /* BtFC - Bottom Front Center */ + bool tpbl_tpbr; /* TpBL/TpBR - Top Back Left/Right */ +}; + +/** + * Speaker allocation data block (SADB), defined in section 7.5.3. + */ +struct di_cta_speaker_alloc_block { + /* Present speakers */ + struct di_cta_speaker_allocation speakers; +}; + +/** + * Get the speaker allocation from a CTA data block. + * + * Returns NULL if the data block tag is not DI_CTA_DATA_BLOCK_SPEAKER_ALLOC. + */ +const struct di_cta_speaker_alloc_block * +di_cta_data_block_get_speaker_alloc(const struct di_cta_data_block *block); + +/** + * Over- and underscan capability. + */ +enum di_cta_video_cap_over_underscan { + /* No data */ + DI_CTA_VIDEO_CAP_UNKNOWN_OVER_UNDERSCAN = 0x00, + /* Always overscanned */ + DI_CTA_VIDEO_CAP_ALWAYS_OVERSCAN = 0x01, + /* Always underscanned */ + DI_CTA_VIDEO_CAP_ALWAYS_UNDERSCAN = 0x02, + /* Supports both over- and underscan */ + DI_CTA_VIDEO_CAP_BOTH_OVER_UNDERSCAN = 0x03, +}; + +/** + * Video capability data block (VCDB), defined in section 7.5.6. + */ +struct di_cta_video_cap_block { + /* If set to true, YCC quantization range is selectable (via AVI YQ). */ + bool selectable_ycc_quantization_range; + /* If set to true, RGB quantization range is selectable (via AVI Q). */ + bool selectable_rgb_quantization_range; + /* Overscan/underscan behavior for PT video formats (if set to unknown, + * use the IT/CE behavior) */ + enum di_cta_video_cap_over_underscan pt_over_underscan; + /* Overscan/underscan behavior for IT video formats */ + enum di_cta_video_cap_over_underscan it_over_underscan; + /* Overscan/underscan behavior for CE video formats */ + enum di_cta_video_cap_over_underscan ce_over_underscan; +}; + +/** + * Get the video capabilities from a CTA data block. + * + * Returns NULL if the data block tag is not DI_CTA_DATA_BLOCK_VIDEO_CAP. + */ +const struct di_cta_video_cap_block * +di_cta_data_block_get_video_cap(const struct di_cta_data_block *block); + +/** + * Interface types, defined in VESA DDDB section 2.3.1 and 2.3.2. + * + * Note, the enum values don't match the specification. + */ +enum di_cta_vesa_display_device_interface_type { + DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_VGA, /* 15HD/VGA */ + DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_NAVI_V, /* VESA NAVI-V */ + DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_NAVI_D, /* VESA NAVI-D */ + DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_LVDS, /* LVDS */ + DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_RSDS, /* RSDS */ + DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_DVI_D, /* DVI-D */ + DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_DVI_I_ANALOG, /* DVI-I analog */ + DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_DVI_I_DIGITAL, /* DVI-I digital */ + DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_HDMI_A, /* HDMI-A */ + DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_HDMI_B, /* HDMI-B */ + DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_MDDI, /* MDDI */ + DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_DISPLAYPORT, /* DisplayPort */ + DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_IEEE_1394, /* IEEE-1394 */ + DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_M1_ANALOG, /* M1 analog */ + DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_M1_DIGITAL, /* M1 digital */ +}; + +enum di_cta_vesa_display_device_content_protection { + DI_CTA_VESA_DISPLAY_DEVICE_CONTENT_PROTECTION_NONE = 0x00, /* None */ + DI_CTA_VESA_DISPLAY_DEVICE_CONTENT_PROTECTION_HDCP = 0x01, /* HDCP */ + DI_CTA_VESA_DISPLAY_DEVICE_CONTENT_PROTECTION_DTCP = 0x02, /* DTCP */ + DI_CTA_VESA_DISPLAY_DEVICE_CONTENT_PROTECTION_DPCP = 0x03, /* DPCP */ +}; + +enum di_cta_vesa_display_device_default_orientation { + DI_CTA_VESA_DISPLAY_DEVICE_DEFAULT_ORIENTATION_LANDSCAPE = 0, /* Landscape */ + DI_CTA_VESA_DISPLAY_DEVICE_DEFAULT_ORIENTATION_PORTAIT = 1, /* Portrait */ + DI_CTA_VESA_DISPLAY_DEVICE_DEFAULT_ORIENTATION_UNFIXED = 2, /* Not fixed, may be rotated by the user */ + DI_CTA_VESA_DISPLAY_DEVICE_DEFAULT_ORIENTATION_UNDEFINED = 3, /* Undefined */ +}; + +enum di_cta_vesa_display_device_rotation_cap { + DI_CTA_VESA_DISPLAY_DEVICE_ROTATION_CAP_NONE = 0, /* No rotation capability */ + DI_CTA_VESA_DISPLAY_DEVICE_ROTATION_CAP_90DEG_CLOCKWISE = 1, /* 90 degrees clockwise */ + DI_CTA_VESA_DISPLAY_DEVICE_ROTATION_CAP_90DEG_COUNTERCLOCKWISE = 2, /* 90 degrees counterclockwise */ + DI_CTA_VESA_DISPLAY_DEVICE_ROTATION_CAP_90DEG_EITHER = 3, /* 90 degrees in either direction */ +}; + +enum di_cta_vesa_display_device_zero_pixel_location { + DI_CTA_VESA_DISPLAY_DEVICE_ZERO_PIXEL_UPPER_LEFT = 0, /* Upper left corner */ + DI_CTA_VESA_DISPLAY_DEVICE_ZERO_PIXEL_UPPER_RIGHT = 1, /* Upper right corner */ + DI_CTA_VESA_DISPLAY_DEVICE_ZERO_PIXEL_LOWER_LEFT = 2, /* Lower left corner */ + DI_CTA_VESA_DISPLAY_DEVICE_ZERO_PIXEL_LOWER_RIGHT = 3, /* Lower right corner */ +}; + +enum di_cta_vesa_display_device_scan_direction { + /* Undefined */ + DI_CTA_VESA_DISPLAY_DEVICE_SCAN_DIRECTION_UNDEFINED = 0, + /* Fast (line) scan is along the long axis, slow (frame or field) scan + * is along the short axis */ + DI_CTA_VESA_DISPLAY_DEVICE_SCAN_DIRECTION_FAST_LONG_SLOW_SHORT = 1, + /* Fast (line) scan is along the short axis, slow (frame or field) scan + * is along the long axis */ + DI_CTA_VESA_DISPLAY_DEVICE_SCAN_DIRECTION_FAST_SHORT_SLOW_LONG = 2, +}; + +/** + * Subpixel layout, defined in VESA DDDB section 2.9. + * + * For layouts with more than 3 subpixels, the color coordinates of the + * additional subpixels are defined in the additional primary chromaticities. + */ +enum di_cta_vesa_display_device_subpixel_layout { + /* Undefined */ + DI_CTA_VESA_DISPLAY_DEVICE_SUBPIXEL_UNDEFINED = 0x00, + /* Red, green, blue vertical stripes */ + DI_CTA_VESA_DISPLAY_DEVICE_SUBPIXEL_RGB_VERT = 0x01, + /* Red, green, blue horizontal stripes */ + DI_CTA_VESA_DISPLAY_DEVICE_SUBPIXEL_RGB_HORIZ = 0x02, + /* Vertical stripes with the primary ordering given by the order of the + * chromaticity information in the base EDID */ + DI_CTA_VESA_DISPLAY_DEVICE_SUBPIXEL_EDID_CHROM_VERT = 0x03, + /* Horizontal stripes with the primary ordering given by the order of + * the chromaticity information in the base EDID */ + DI_CTA_VESA_DISPLAY_DEVICE_SUBPIXEL_EDID_CHROM_HORIZ = 0x04, + /* Quad subpixels: + * R G + * G B + */ + DI_CTA_VESA_DISPLAY_DEVICE_SUBPIXEL_QUAD_RGGB = 0x05, + /* Quad subpixels: + * G B + * R G + */ + DI_CTA_VESA_DISPLAY_DEVICE_SUBPIXEL_QUAD_GBRG = 0x06, + /* Delta (triad) RGB subpixels */ + DI_CTA_VESA_DISPLAY_DEVICE_SUBPIXEL_DELTA_RGB = 0x07, + /* Mosaic */ + DI_CTA_VESA_DISPLAY_DEVICE_SUBPIXEL_MOSAIC = 0x08, + /* Quad subpixels: one each of red, green, blue, and one additional + * color (including white) in any order */ + DI_CTA_VESA_DISPLAY_DEVICE_SUBPIXEL_QUAD_ANY = 0x09, + /* Five subpixels, including RGB subpixels aligned as in the case of + * DI_CTA_VESA_DISPLAY_DEVICE_SUBPIXEL_RGB_VERT, with two additional subpixels + * located above or below this group */ + DI_CTA_VESA_DISPLAY_DEVICE_SUBPIXEL_FIVE = 0x0A, + /* Six subpixels, including RGB subpixels aligned as in the case of + * DI_CTA_VESA_DISPLAY_DEVICE_SUBPIXEL_RGB_VERT, with three additional subpixels + * located above or below this group */ + DI_CTA_VESA_DISPLAY_DEVICE_SUBPIXEL_SIX = 0x0B, + /* Clairvoyante, Inc. PenTile Matrix™ layout */ + DI_CTA_VESA_DISPLAY_DEVICE_SUBPIXEL_CLAIRVOYANTE_PENTILE = 0x0C, +}; + +enum di_cta_vesa_display_device_dithering_type { + DI_CTA_VESA_DISPLAY_DEVICE_DITHERING_NONE = 0, /* None */ + DI_CTA_VESA_DISPLAY_DEVICE_DITHERING_SPACIAL = 1, /* Spacial */ + DI_CTA_VESA_DISPLAY_DEVICE_DITHERING_TEMPORAL = 2, /* Temporal */ + DI_CTA_VESA_DISPLAY_DEVICE_DITHERING_SPATIAL_AND_TEMPORAL = 3, /* Spacial and temporal */ +}; + +struct di_cta_vesa_display_device_additional_primary_chromaticity { + float x, y; +}; + +enum di_cta_vesa_display_device_frame_rate_conversion { + /* No dedicated rate conversion hardware is provided */ + DI_CTA_VESA_DISPLAY_DEVICE_FRAME_RATE_CONVERSION_NONE = 0, + /* Frame rate conversion is supported, tearing or other artifacts may + * be visible */ + DI_CTA_VESA_DISPLAY_DEVICE_FRAME_RATE_CONVERSION_SINGLE_BUFFERING = 1, + /* Frame rate conversion is supported, input frames may be duplicated or + * dropped */ + DI_CTA_VESA_DISPLAY_DEVICE_FRAME_RATE_CONVERSION_DOUBLE_BUFFERING = 2, + /* Frame rate conversion is supported via a more advanced technique + * (e.g. inter-frame interpolation) */ + DI_CTA_VESA_DISPLAY_DEVICE_FRAME_RATE_CONVERSION_ADVANCED = 3, +}; + +enum di_cta_vesa_display_device_resp_time_transition { + DI_CTA_VESA_DISPLAY_DEVICE_RESP_TIME_BLACK_TO_WHITE = 0, /* Black to white */ + DI_CTA_VESA_DISPLAY_DEVICE_RESP_TIME_WHITE_TO_BLACK = 1, /* White to black */ +}; + +/** + * VESA Display Device Data Block (DDDB), defined in VESA Display Device Data + * Block (DDDB) Standard version 1. + */ +struct di_cta_vesa_display_device_block { + /* Interface type */ + enum di_cta_vesa_display_device_interface_type interface_type; + /* Number of lanes/channels, zero if N/A */ + int32_t num_channels; + /* Interface standard version and release number */ + int32_t interface_version, interface_release; + /* Content protection support */ + enum di_cta_vesa_display_device_content_protection content_protection; + /* Minimum and maximum clock frequency (in mega-hertz), zero if unset + * (ie. maximum and minimum as permitted under the appropriate interface + * specification or standard) */ + int32_t min_clock_freq_mhz, max_clock_freq_mhz; + /* Device native pixel format, zero if unset */ + int32_t native_horiz_pixels, native_vert_pixels; + /* Aspect ratio taken as long axis divided by short axis */ + float aspect_ratio; + /* Default orientation */ + enum di_cta_vesa_display_device_default_orientation default_orientation; + /* Rotation capability */ + enum di_cta_vesa_display_device_rotation_cap rotation_cap; + /* Zero pixel location */ + enum di_cta_vesa_display_device_zero_pixel_location zero_pixel_location; + /* Scan direction */ + enum di_cta_vesa_display_device_scan_direction scan_direction; + /* Subpixel layout */ + enum di_cta_vesa_display_device_subpixel_layout subpixel_layout; + /* Horizontal and vertical dot/pixel pitch (in millimeters) */ + float horiz_pitch_mm, vert_pitch_mm; + /* Dithering type */ + enum di_cta_vesa_display_device_dithering_type dithering_type; + /* Direct drive: no scaling, de-interlacing, frame-rate conversion, etc. + * between this interface and the panel or other display device */ + bool direct_drive; + /* Overdrive not recommended: the source should not apply "overdrive" to + * the video signal */ + bool overdrive_not_recommended; + /* Display can deinterlaced video input */ + bool deinterlacing; + /* Video interface supports audio */ + bool audio_support; + /* Audio inputs are provided separately from the video interface */ + bool separate_audio_inputs; + /* Audio information received via the video interface will automatically + * override any other audio input channels provided */ + bool audio_input_override; + /* True if audio delay information is provided */ + bool audio_delay_provided; + /* Audio delay (in milliseconds, may be negative), with the maximum + * absolute value of 254 ms */ + int32_t audio_delay_ms; + /* Frame rate/mode conversion */ + enum di_cta_vesa_display_device_frame_rate_conversion frame_rate_conversion; + /* Frame rate range (in Hz), with the maximum value of 63 Hz */ + int32_t frame_rate_range_hz; + /* Native/nominal rate (in Hz) */ + int32_t frame_rate_native_hz; + /* Color bit depth for the interface and display device */ + int32_t bit_depth_interface, bit_depth_display; + /* Number of additional primary color chromaticities */ + size_t additional_primary_chromaticities_len; + /* Additional primary color chromaticities given as 1931 CIE xy color + * space coordinates (also defines the color of subpixels for some + * subpixel layouts) */ + struct di_cta_vesa_display_device_additional_primary_chromaticity additional_primary_chromaticities[3]; + /* Response time transition */ + enum di_cta_vesa_display_device_resp_time_transition resp_time_transition; + /* Response time (in milliseconds), with the maximum value of 127 ms */ + int32_t resp_time_ms; + /* Overscan horizontal and vertical percentage */ + int32_t overscan_horiz_pct, overscan_vert_pct; +}; + +/** + * Get the VESA Display Device Data Block (DDDB) from a CTA data block. + * + * Returns NULL if the data block tag is not + * DI_CTA_DATA_BLOCK_VESA_DISPLAY_DEVICE. + */ +const struct di_cta_vesa_display_device_block * +di_cta_data_block_get_vesa_display_device(const struct di_cta_data_block *block); + +/** + * CTA colorimetry data block, defined in section 7.5.5. + */ +struct di_cta_colorimetry_block { + /* Standard Definition Colorimetry based on IEC 61966-2-4 */ + bool xvycc_601; + /* High Definition Colorimetry based on IEC 61966-2-4 */ + bool xvycc_709; + /* Colorimetry based on IEC 61966-2-1/Amendment 1 */ + bool sycc_601; + /* Colorimetry based on IEC 61966-2-5, Annex A */ + bool opycc_601; + /* Colorimetry based on IEC 61966-2-5 */ + bool oprgb; + /* Colorimetry based on Rec. ITU-R BT.2020 Y'cC'bcC'rc */ + bool bt2020_cycc; + /* Colorimetry based on Rec. ITU-R BT.2020 Y'C'bC'r */ + bool bt2020_ycc; + /* Colorimetry based on Rec. ITU-R BT.2020 R'G'B' */ + bool bt2020_rgb; + /* Colorimetry based on SMPTE ST 2113 R'G'B' */ + bool st2113_rgb; + /* Colorimetry based on Rec. ITU-R BT.2100 ICtCp */ + bool ictcp; +}; + +/** + * Get the colorimetry data from a CTA data block. + * + * Returns NULL if the data block tag is not DI_CTA_DATA_BLOCK_COLORIMETRY. + */ +const struct di_cta_colorimetry_block * +di_cta_data_block_get_colorimetry(const struct di_cta_data_block *block); + +/** + * Supported Electro-Optical Transfer Functions for a CTA HDR static metadata + * block. + */ +struct di_cta_hdr_static_metadata_eotfs { + /* Traditional gamma - SDR luminance range */ + bool traditional_sdr; + /* Traditional gamma - HDR luminance range */ + bool traditional_hdr; + /* Perceptual Quantization (PQ) based on SMPTE ST 2084 */ + bool pq; + /* Hybrid Log-Gamma (HLG) based on Rec. ITU-R BT.2100 */ + bool hlg; +}; + +/** + * Supported static metadata descriptors for a CTA HDR static metadata block. + */ +struct di_cta_hdr_static_metadata_descriptors { + /* Static Metadata Type 1 */ + bool type1; +}; + +/** + * CTA HDR static metadata block, defined in section 7.5.13. + */ +struct di_cta_hdr_static_metadata_block { + /* Desired content max luminance (cd/m²), zero if unset */ + float desired_content_max_luminance; + /* Desired content max frame-average luminance (cd/m²), zero if unset */ + float desired_content_max_frame_avg_luminance; + /* Desired content min luminance (cd/m²), zero if unset */ + float desired_content_min_luminance; + /* Supported EOFTs */ + const struct di_cta_hdr_static_metadata_eotfs *eotfs; + /* Supported descriptors */ + const struct di_cta_hdr_static_metadata_descriptors *descriptors; +}; + +/** + * Get the HDR static metadata from a CTA data block. + * + * Returns NULL if the data block tag is not + * DI_CTA_DATA_BLOCK_HDR_STATIC_METADATA. + */ +const struct di_cta_hdr_static_metadata_block * +di_cta_data_block_get_hdr_static_metadata(const struct di_cta_data_block *block); + +/* Additional HDR Dynamic Metadata Type 1 information */ +struct di_cta_hdr_dynamic_metadata_type1 { + uint8_t type_1_hdr_metadata_version; +}; + +/* Additional HDR Dynamic Metadata Type 2 (ETSI TS 103 433-1) information. + * Defined in ETSI TS 103 433-1 Annex G.2 HDR Dynamic Metadata Data Block. */ +struct di_cta_hdr_dynamic_metadata_type2 { + uint8_t ts_103_433_spec_version; + bool ts_103_433_1_capable; + bool ts_103_433_2_capable; + bool ts_103_433_3_capable; +}; + +/* Additional HDR Dynamic Metadata Type 3 information */ +struct di_cta_hdr_dynamic_metadata_type3; + +/* Additional HDR Dynamic Metadata Type 4 information */ +struct di_cta_hdr_dynamic_metadata_type4 { + uint8_t type_4_hdr_metadata_version; +}; + +/* Additional HDR Dynamic Metadata Type 256 information */ +struct di_cta_hdr_dynamic_metadata_type256 { + uint8_t graphics_overlay_flag_version; +}; + +/** + * CTA HDR dynamic metadata block, defined in section 7.5.14. + */ +struct di_cta_hdr_dynamic_metadata_block { + /* non-NULL if Dynamic Metadata Type 1 is supported. */ + const struct di_cta_hdr_dynamic_metadata_type1 *type1; + /* non-NULL if Dynamic Metadata Type 2 is supported. */ + const struct di_cta_hdr_dynamic_metadata_type2 *type2; + /* non-NULL if Dynamic Metadata Type 3 is supported. */ + const struct di_cta_hdr_dynamic_metadata_type3 *type3; + /* non-NULL if Dynamic Metadata Type 4 is supported. */ + const struct di_cta_hdr_dynamic_metadata_type4 *type4; + /* non-NULL if Dynamic Metadata Type 256 (0x0100) is supported. */ + const struct di_cta_hdr_dynamic_metadata_type256 *type256; +}; + +/** + * Get the HDR dynamic metadata from a CTA data block. + * + * Returns NULL if the data block tag is not + * DI_CTA_DATA_BLOCK_HDR_DYNAMIC_METADATA. + */ +const struct di_cta_hdr_dynamic_metadata_block * +di_cta_data_block_get_hdr_dynamic_metadata(const struct di_cta_data_block *block); + +/** + * A Short Video Descriptor (SVD). + */ +struct di_cta_svd { + /* CTA Video Identification Code (VIC) */ + struct di_cta_vic vic; + /* Original index of the VIC in this block's array. Some maps refer to + * this value, such as the YCbCr 4:2:0 Capability Map. */ + uint8_t original_index; + /* Whether this is a native video format */ + bool native; +}; + +/** + * Video Data Block, defined in section 7.5.1. + */ +struct di_cta_video_block { + /* Short video descriptors. The array is NULL-terminated. */ + const struct di_cta_svd *const *svds; +}; + +/** + * Get the video from a CTA data block. + * + * Returns NULL if the data block tag is not DI_CTA_DATA_BLOCK_VIDEO. + */ +const struct di_cta_video_block * +di_cta_data_block_get_video(const struct di_cta_data_block *block); + +/** + * Video Data Block, defined in section 7.5.1. + */ +struct di_cta_ycbcr420_video_block { + /* Short video descriptors which only allow YCbCr 4:2:0 sampling mode. + * The array is NULL-terminated. */ + const struct di_cta_svd *const *svds; +}; + +/** + * Get the YCbCr video from a CTA data block. + * + * Returns NULL if the data block tag is not DI_CTA_DATA_BLOCK_YCBCR420. + */ +const struct di_cta_ycbcr420_video_block * +di_cta_data_block_get_ycbcr420_video(const struct di_cta_data_block *block); + +enum di_cta_vesa_transfer_characteristics_usage { + /* White transfer characteristic */ + DI_CTA_VESA_TRANSFER_CHARACTERISTIC_USAGE_WHITE = 0, + /* Red transfer characteristic */ + DI_CTA_VESA_TRANSFER_CHARACTERISTIC_USAGE_RED = 1, + /* Green transfer characteristic */ + DI_CTA_VESA_TRANSFER_CHARACTERISTIC_USAGE_GREEN = 2, + /* Blue transfer characteristic */ + DI_CTA_VESA_TRANSFER_CHARACTERISTIC_USAGE_BLUE = 3, +}; + +/** + * VESA Display Transfer Characteristic Data Block, defined in VESA Display + * Transfer Characteristics Data Block Standard Version 1.0 + * + * Contains 8, 16 or 32 evenly distributed points on the input axis describing + * the normalized relative luminance at that input. The first value includes the + * relative black level luminance. + */ +struct di_cta_vesa_transfer_characteristics_block { + enum di_cta_vesa_transfer_characteristics_usage usage; + uint8_t points_len; + float points[32]; +}; + +/** + * Get the Display Transfer Characteristic from a CTA data block. + * + * Returns NULL if the data block tag is not + * DI_CTA_DATA_BLOCK_VESA_DISPLAY_TRANSFER_CHARACTERISTIC. + * + * Upstream is not aware of any EDID blob containing a Display Transfer + * Characteristic data block. + * If such a blob is found, please share it with upstream! + */ +const struct di_cta_vesa_transfer_characteristics_block * +di_cta_data_block_get_vesa_transfer_characteristics(const struct di_cta_data_block *block); + +/** + * CTA YCbCr 4:2:0 Capability Map block, defined in section 7.5.11. + */ +struct di_cta_ycbcr420_cap_map_block; + +/** + * Returns true if the SVD in regular Video Data Blocks at index `svd_index` + * supports YCbCr 4:2:0 subsampling. + */ +bool +di_cta_ycbcr420_cap_map_supported(const struct di_cta_ycbcr420_cap_map_block *cap_map, + size_t svd_index); + +/** + * Get the YCbCr 4:2:0 Capability Map from a CTA data block. + * + * Returns NULL if the data block tag is not DI_CTA_DATA_BLOCK_YCBCR420_CAP_MAP. + */ +const struct di_cta_ycbcr420_cap_map_block * +di_cta_data_block_get_ycbcr420_cap_map(const struct di_cta_data_block *block); + +enum di_cta_hdmi_audio_3d_channels { + DI_CTA_HDMI_AUDIO_3D_CHANNELS_UNKNOWN = 0, + DI_CTA_HDMI_AUDIO_3D_CHANNELS_10_2 = 1, + DI_CTA_HDMI_AUDIO_3D_CHANNELS_22_2 = 2, + DI_CTA_HDMI_AUDIO_3D_CHANNELS_30_2 = 3, +}; + +/** + * HDMI 3D Audio + */ +struct di_cta_hdmi_audio_3d { + /* Supported formats. The array is NULL-terminated. */ + const struct di_cta_sad *const *sads; + /* Channels */ + enum di_cta_hdmi_audio_3d_channels channels; + /* Speakers */ + struct di_cta_speaker_allocation speakers; +}; + +/** + * HDMI Multi-Stream Audio + */ +struct di_cta_hdmi_audio_multi_stream { + /* Supports 2 up to max_streams different streams */ + int max_streams; + /* Supports non-mixed main/supplementary audio streams */ + bool supports_non_mixed; +}; + +/** + * HDMI Audio + */ +struct di_cta_hdmi_audio_block { + /* Multi-Stream Audio, NULL if unsupported */ + const struct di_cta_hdmi_audio_multi_stream *multi_stream; + /* 3D Audio, NULL if unsupported */ + const struct di_cta_hdmi_audio_3d *audio_3d; +}; + +/** + * Get the HDMI Audio information from a CTA data block. + * + * Returns NULL if the data block tag is not DI_CTA_DATA_BLOCK_HDMI_AUDIO. + */ +const struct di_cta_hdmi_audio_block * +di_cta_data_block_get_hdmi_audio(const struct di_cta_data_block *block); + +/** + * HDR10+ Vendor-Specific Video Data Block + */ +struct di_cta_hdr10plus_block { + /* Application version */ + int version; + /* Peak Luminance in nits; 0 for invalid. */ + int peak_lum; + /* Full Frame Peak Luminance in nits; 0 for invalid. */ + int ff_peak_lum; +}; + +/** + * Get the HDR10+ information from a CTA data block. + * + * Returns NULL if the data block tag is not DI_CTA_DATA_BLOCK_HDR10PLUS. + */ +const struct di_cta_hdr10plus_block * +di_cta_data_block_get_hdr10plus(const struct di_cta_data_block *block); + +/** + * Dolby Video Colorimetry + */ +enum di_cta_dolby_video_colorimetry { + /* BT.709 Colorimetry */ + DI_CTA_DOLBY_VIDEO_COLORIMETRY_BT_709, + /* P3 D65 Colorimetry */ + DI_CTA_DOLBY_VIDEO_COLORIMETRY_P3_D65, +}; + +/** + * Dolby Video YUV 4:4:4 support + */ +enum di_cta_dolby_video_yuv444 { + /* No YUV 4:4:4 support */ + DI_CTA_DOLBY_VIDEO_YUV444_NONE = 0, + /* YUV 4:4:4 support for 10 bit signals */ + DI_CTA_DOLBY_VIDEO_YUV444_10_BITS = 1, + /* YUV 4:4:4 support for 12 bit signals */ + DI_CTA_DOLBY_VIDEO_YUV444_12_BITS = 2, +}; + +/** + * Dolby Video Version 0 Data + */ +struct di_cta_dolby_video_block_v0 { + /* YUV 4:2:2 support for 12 bit signals */ + bool yuv422_12bit; + /* Support for global dimming */ + bool global_dimming; + /* Support for 2160p60 */ + bool supports_2160p60; + /* Supported dynamic metadata version (major) */ + int dynamic_metadata_version_major; + /* Supported dynamic metadata version (minor) */ + int dynamic_metadata_version_minor; + /* Minimum target luminance as a 12 bit PQ signal level */ + int target_pq_12b_level_min; + /* Maximum target luminance as a 12 bit PQ signal level */ + int target_pq_12b_level_max; + /* Primaries */ + double red_x, red_y, green_x, green_y, blue_x, blue_y, white_x, white_y; +}; + +/** + * Dolby Video Version 1 Data + */ +struct di_cta_dolby_video_block_v1 { + /* YUV 4:2:2 support for 12 bit signals */ + bool yuv422_12bit; + /* Support for global dimming */ + bool global_dimming; + /* Support for 2160p60 */ + bool supports_2160p60; + /* Supported dynamic metadata version */ + int dynamic_metadata_version; + /* Supported colorimetry */ + enum di_cta_dolby_video_colorimetry colorimetry; + /* Minimum target luminance in nits */ + double target_luminance_min; + /* Maximum target luminance in nits */ + double target_luminance_max; + /* Supports the Low-Latency mode in addition to the Standard mode */ + bool mode_low_latency; + /* Indicates if the Primaries are unique */ + bool unique_primaries; + /* Primaries */ + double red_x, red_y, green_x, green_y, blue_x, blue_y; +}; + +/** + * Dolby Video Version 2 Data + */ +struct di_cta_dolby_video_block_v2 { + /* YUV 4:2:2 support for 12 bit signals */ + bool yuv422_12bit; + /* Support for global dimming */ + bool global_dimming; + /* Supported dynamic metadata version */ + int dynamic_metadata_version; + /* Support for Backlight Control */ + bool backlight_control; + /* Minimum backlight luminance in nits */ + double backlight_luminance_min; + /* Supports the Standard mode in addition to the Low-Latency mode */ + bool mode_standard; + /* Supports the Low-Latency-HDMI mode in addition to the Low-Latency mode */ + bool mode_low_latency_hdmi; + /* YUV 4:4:4 support */ + enum di_cta_dolby_video_yuv444 yuv444; + /* Minimum target luminance as a 12 bit PQ signal level */ + int target_pq_12b_level_min; + /* Maximum target luminance as a 12 bit PQ signal level */ + int target_pq_12b_level_max; + /* Unique Primaries */ + double red_x, red_y, green_x, green_y, blue_x, blue_y; +}; + +/** + * Dolby Video Data Block version + */ +enum di_cta_dolby_video_version { + DI_CTA_DOLBY_VIDEO_VERSION0, + DI_CTA_DOLBY_VIDEO_VERSION1, + DI_CTA_DOLBY_VIDEO_VERSION2, +}; + +/** + * Dolby Video Data Block + */ +struct di_cta_dolby_video_block { + /* The version of this block */ + enum di_cta_dolby_video_version version; + /* Version 0 data. NULL if the version of the block is not DI_CTA_DOLBY_VIDEO_VERSION0 */ + const struct di_cta_dolby_video_block_v0 *v0; + /* Version 1 data. NULL if the version of the block is not DI_CTA_DOLBY_VIDEO_VERSION1 */ + const struct di_cta_dolby_video_block_v1 *v1; + /* Version 2 data. NULL if the version of the block is not DI_CTA_DOLBY_VIDEO_VERSION2 */ + const struct di_cta_dolby_video_block_v2 *v2; +}; + +/** + * Get the Dolby Vision information from a CTA data block. + * + * Returns NULL if the data block tag is not DI_CTA_DATA_BLOCK_DOLBY_VIDEO. + */ +const struct di_cta_dolby_video_block * +di_cta_data_block_get_dolby_video(const struct di_cta_data_block *block); + +/** + * InfoFrame types, defined in table 7. + * + * Note, the enum values don't match the specification. + */ +enum di_cta_infoframe_type { + /* Auxiliary Video Information, defined in section 6.4. */ + DI_CTA_INFOFRAME_TYPE_AUXILIARY_VIDEO_INFORMATION, + /* Source Product Description, defined in section 6.5. */ + DI_CTA_INFOFRAME_TYPE_SOURCE_PRODUCT_DESCRIPTION, + /* Audio, defined in section 6.6. */ + DI_CTA_INFOFRAME_TYPE_AUDIO, + /* MPEG Source, defined in section 6.7. */ + DI_CTA_INFOFRAME_TYPE_MPEG_SOURCE, + /* NTSC VBI, defined in section 6.8. */ + DI_CTA_INFOFRAME_TYPE_NTSC_VBI, + /* Dynamic Range and Mastering, defined in section 6.9. */ + DI_CTA_INFOFRAME_TYPE_DYNAMIC_RANGE_AND_MASTERING, +}; + +/** + * CTA InfoFrame descriptor, defined in section 7.5.9. + */ +struct di_cta_infoframe_descriptor { + /* Type of InfoFrame */ + enum di_cta_infoframe_type type; +}; + +/** + * CTA InfoFrame processing, defined in section 7.5.9. + */ +struct di_cta_infoframe_block { + /* Number of Vendor-specific InfoFrames that can be received + * simultaneously */ + int num_simultaneous_vsifs; + /* Supported InfoFrames. The array is NULL-terminated. */ + const struct di_cta_infoframe_descriptor *const *infoframes; +}; + +/** + * Get the InfoFrame information from a CTA data block. + * + * Returns NULL if the data block tag is not DI_CTA_DATA_BLOCK_INFOFRAME. + */ +const struct di_cta_infoframe_block * +di_cta_data_block_get_infoframe(const struct di_cta_data_block *block); + +/** + * Room Configuration Data Block, defined in section 7.5.15. + */ +struct di_cta_room_configuration_block { + /* Present speakers */ + struct di_cta_speaker_allocation speakers; + /* Total number of L-PCM channels */ + int speaker_count; + /* All speakers are defined by Speaker Location Descriptors */ + bool has_speaker_location_descriptors; + /* Rectangular box that contains all of the components of interest + * centered on the Primary Listening Position. + * See 7.5.16.1 Room Coordinate System + * Only valid if any Speaker Location Descriptor has coordinates set. */ + int max_x; /* in dm */ + int max_y; /* in dm */ + int max_z; /* in dm */ + /* The position of the center of the display in normalized coordinates. + * Only valid if any Speaker Location Descriptor has coordinates set. */ + double display_x; /* normalized to max_x */ + double display_y; /* normalized to max_y */ + double display_z; /* normalized to max_z */ +}; + +/** + * Get the Room Configuration from a CTA data block. + * + * Returns NULL if the data block tag is not DI_CTA_DATA_BLOCK_ROOM_CONFIG. + */ +const struct di_cta_room_configuration_block * +di_cta_data_block_get_room_configuration(const struct di_cta_data_block *block); + +enum di_cta_speaker_placement { + /* FL - Front Left */ + DI_CTA_SPEAKER_PLACEMENT_FL = 0x00, + /* FR - Front Right */ + DI_CTA_SPEAKER_PLACEMENT_FR = 0x01, + /* FC - Front Center */ + DI_CTA_SPEAKER_PLACEMENT_FC = 0x02, + /* LFE1 - Low Frequency Effects 1 */ + DI_CTA_SPEAKER_PLACEMENT_LFE1 = 0x03, + /* BL - Back Left */ + DI_CTA_SPEAKER_PLACEMENT_BL = 0x04, + /* BR - Back Right */ + DI_CTA_SPEAKER_PLACEMENT_BR = 0x05, + /* FLc - Front Left of Center */ + DI_CTA_SPEAKER_PLACEMENT_FLC = 0x06, + /* FRc - Front Right of Center */ + DI_CTA_SPEAKER_PLACEMENT_FRC = 0x07, + /* BC - Back Center */ + DI_CTA_SPEAKER_PLACEMENT_BC = 0x08, + /* LFE2 - Low Frequency Effects 2 */ + DI_CTA_SPEAKER_PLACEMENT_LFE2 = 0x09, + /* SiL - Side Left */ + DI_CTA_SPEAKER_PLACEMENT_SIL = 0x0a, + /* SiR - Side Right */ + DI_CTA_SPEAKER_PLACEMENT_SIR = 0x0b, + /* TpFL - Top Front Left */ + DI_CTA_SPEAKER_PLACEMENT_TPFL = 0x0c, + /* TpFR - Top Front Right */ + DI_CTA_SPEAKER_PLACEMENT_TPFR = 0x0d, + /* TpFC - Top Front Center */ + DI_CTA_SPEAKER_PLACEMENT_TPFC = 0x0e, + /* TpC - Top Center */ + DI_CTA_SPEAKER_PLACEMENT_TPC = 0x0f, + /* TpBL - Top Back Left */ + DI_CTA_SPEAKER_PLACEMENT_TPBL = 0x10, + /* TpBR - Top Back Right */ + DI_CTA_SPEAKER_PLACEMENT_TPBR = 0x11, + /* TpSiL - Top Side Left */ + DI_CTA_SPEAKER_PLACEMENT_TPSIL = 0x12, + /* TpSiR - Top Side Right */ + DI_CTA_SPEAKER_PLACEMENT_TPSIR = 0x13, + /* TpBC - Top Back Center */ + DI_CTA_SPEAKER_PLACEMENT_TPBC = 0x14, + /* BtFC - Bottom Front Center */ + DI_CTA_SPEAKER_PLACEMENT_BTFC = 0x15, + /* BtFL - Bottom Front Left */ + DI_CTA_SPEAKER_PLACEMENT_BTFL = 0x16, + /* BtFR - Bottom Front Right */ + DI_CTA_SPEAKER_PLACEMENT_BRFR = 0x17, + /* FLw - Front Left Wide */ + DI_CTA_SPEAKER_PLACEMENT_FLW = 0x18, + /* FRw - Front Right Wide */ + DI_CTA_SPEAKER_PLACEMENT_FRW = 0x19, + /* LS - Left Surround */ + DI_CTA_SPEAKER_PLACEMENT_LS = 0x1a, + /* RS - Right Surround */ + DI_CTA_SPEAKER_PLACEMENT_RS = 0x1b, +}; + +/** + * Speaker Location Descriptor, defined in section 7.5.16. + */ +struct di_cta_speaker_location_descriptor { + /* Index of the audio channel where the audio for the described speaker + * is to be transmitted. */ + int channel_index; + /* If the channel shall be rendered on a speaker by the Sink. */ + bool is_active; + /* If the speaker has coordinates instead of a named speaker placement. */ + bool has_coords; + /* The position of the speaker in normalized coordinates. */ + double x, y, z; + /* The named location of the speaker. Only valid if has_coords is false. */ + enum di_cta_speaker_placement speaker_id; +}; + +/** + * Speaker Location Data Block, defined in section 7.5.16. + */ +struct di_cta_speaker_location_block { + /* Speaker Location Descriptors. The array is NULL-terminated. */ + const struct di_cta_speaker_location_descriptor *const *locations; +}; + +/** + * Get the Speaker Locations from a CTA data block. + * + * Returns NULL if the data block tag is not DI_CTA_DATA_BLOCK_SPEAKER_LOCATION. + */ +const struct di_cta_speaker_location_block * +di_cta_data_block_get_speaker_locations(const struct di_cta_data_block *block); + +/* See */ +struct di_displayid_type_i_ii_vii_timing; + +/** + * Type VII Video Timing Data Block, defined in section 7.5.17.1 + */ +struct di_cta_type_vii_timing_block { + const struct di_displayid_type_i_ii_vii_timing *timing; +}; + +/** + * Get the DisplayID Type VII Video Timing from a CTA data block. + * + * Returns NULL if the data block tag is not + * DI_CTA_DATA_BLOCK_DISPLAYID_VIDEO_TIMING_VII. + */ +const struct di_cta_type_vii_timing_block * +di_cta_data_block_get_did_type_vii_timing(const struct di_cta_data_block *block); + +/** + * HDMI vendor-specific data block. + * + * This block is defined in HDMI 1.4b section 8.3.2. + */ +struct di_cta_vendor_hdmi_block { + /* Source physical address */ + uint16_t source_phys_addr; + /* Supports AI */ + bool supports_ai; + /* Supports DC 48-bit */ + bool supports_dc_48bit; + /* Supports DC 36-bit */ + bool supports_dc_36bit; + /* Supports DC 30-bit */ + bool supports_dc_30bit; + /* Supports DC Y444 */ + bool supports_dc_y444; + /* Supports DVI dual */ + bool supports_dvi_dual; + /* Max TMDS clock (MHz). Zero if the sink doesn't support TMDS clock + * frequencies > 165MHz */ + int max_tmds_clock; + /* Supports content type graphics */ + bool supports_content_graphics; + /* Supports content type photo */ + bool supports_content_photo; + /* Supports content type cinema */ + bool supports_content_cinema; + /* Supports content type game */ + bool supports_content_game; + /** + * If !has_latency and !has_interlaced_latency, we have no latency + * information at all. + * + * If only has_latency, video/audio latency fields are valid and + * should be used for both progressive and interlaced video/audio + * formats. + * + * If both are valid, it means that video_latency and audio_latency + * should be used for progressive video/audio formats, and their + * interlaced counterpart for interlaced formats. + */ + bool has_latency; + bool has_interlaced_latency; + /* Latency values (miliseconds). Invalid if zeroed or unsupported. */ + bool supports_progressive_video; + bool supports_progressive_audio; + bool supports_interlaced_video; + bool supports_interlaced_audio; + int progressive_video_latency; + int progressive_audio_latency; + int interlaced_video_latency; + int interlaced_audio_latency; + /* HDMI VIC's. List may be empty. */ + size_t vics_len; + struct di_hdmi_vic *vics; +}; + +/** + * Get the vendor-specific HDMI information from a CTA data block. + * + * Note, the HDMI and HDMI Forum vendor-specific data blocks are different. + * + * Returns NULL if the data block tag is not + * DI_CTA_DATA_BLOCK_VENDOR_HDMI. + */ +const struct di_cta_vendor_hdmi_block * +di_cta_data_block_get_vendor_hdmi(const struct di_cta_data_block *block); + +/** + * Fixed Rate Link (FRL) support. + */ +enum di_cta_hdmi_frl { + /* Fixed Rate Link is not supported */ + DI_CTA_HDMI_FRL_UNSUPPORTED = 0, + /* 3 Gbit/s per lane on 3 lanes */ + DI_CTA_HDMI_FRL_3GBPS_3LANES = 1, + /* 6 Gbit/s & 3 Gbit/s per lane on 3 lanes */ + DI_CTA_HDMI_FRL_6GBPS_3LANES = 2, + /* 6 Gbit/s per lane on 4 lanes, + * 6 Gbit/s & 3 Gbit/s per lane on 3 lanes */ + DI_CTA_HDMI_FRL_6GBPS_4LANES = 3, + /* 8 Gbit/s & 6 Gbit/s per lane on 4 lanes, + * 6 Gbit/s & 3 Gbit/s per lane on 3 lanes */ + DI_CTA_HDMI_FRL_8GBPS_4LANES = 4, + /* 10 Gbit/s & 8 Gbit/s & 6 Gbit/s per lane on 4 lanes, + * 6 Gbit/s & 3 Gbit/s per lane on 3 lanes */ + DI_CTA_HDMI_FRL_10GBPS_4LANES = 5, + /* 12 Gbit/s & 10 Gbit/s & 8 Gbit/s & 6 Gbit/s per lane on 4 lanes, + * 6 Gbit/s & 3 Gbit/s per lane on 3 lanes */ + DI_CTA_HDMI_FRL_12GBPS_4LANES = 6, +}; + +enum di_cta_hdmi_dsc_max_slices { + /* DSC is not supported */ + DI_CTA_HDMI_DSC_MAX_SLICES_UNSUPPORTED = 0, + /* up to 1 slice, up to 340 MHz/KSliceAdjust */ + DI_CTA_HDMI_DSC_MAX_SLICES_1_340MHZ = 1, + /* up to 2 slice, up to 340 MHz/KSliceAdjust */ + DI_CTA_HDMI_DSC_MAX_SLICES_2_340MHZ = 2, + /* up to 4 slice, up to 340 MHz/KSliceAdjust */ + DI_CTA_HDMI_DSC_MAX_SLICES_4_340MHZ = 3, + /* up to 8 slice, up to 340 MHz/KSliceAdjust */ + DI_CTA_HDMI_DSC_MAX_SLICES_8_340MHZ = 4, + /* up to 8 slice, up to 400 MHz/KSliceAdjust */ + DI_CTA_HDMI_DSC_MAX_SLICES_8_400MHZ = 5, + /* up to 12 slice, up to 400 MHz/KSliceAdjust */ + DI_CTA_HDMI_DSC_MAX_SLICES_12_400MHZ = 6, + /* up to 16 slice, up to 400 MHz/KSliceAdjust */ + DI_CTA_HDMI_DSC_MAX_SLICES_16_400MHZ = 7, +}; + +/** + * Display Stream Compression (DSC) support. + */ +struct di_cta_hdmi_dsc { + /* Supports Display Stream Compression for 10bpc */ + bool supports_10bpc; + /* Supports Display Stream Compression for 12bpc */ + bool supports_12bpc; + /* Supports Display Stream Compression for any bpc between 1 and 16 */ + bool supports_all_bpc; + /* Supports Display Stream Compression for 4:2:0 pixel encodings */ + bool supports_native_420; + /* Maximum number of horizontal slices */ + enum di_cta_hdmi_dsc_max_slices max_slices; + /* Maximum FRL_Rate for DSC */ + enum di_cta_hdmi_frl max_frl_rate; + /* Maximum total number of bytes in a line of chunks, zero if + * unsupported */ + int max_total_chunk_bytes; +}; + +/** + * HDMI Sink Capability Data Structure (SCDS). + * + * This data is exposed via HDMI Forum Vendor-Specific Data Block (HF-VSDB) or + * the HDMI Forum Sink Capability Data Block (HF-SCDB). + */ +struct di_cta_hdmi_scds { + /* Version */ + int version; + /* Maximum TMDS character rate in MHz, zero if TMDS Character Rates <= 340 Mcsc */ + int max_tmds_char_rate_mhz; + /* Supports 3D OSD disparity indication in HF-VSIF */ + bool supports_3d_osd_disparity; + /* Supports 3D dual view signaling in HF-VSIF */ + bool supports_3d_dual_view; + /* Supports 3D independent view signaling in HF-VSIF */ + bool supports_3d_independent_view; + /* Supports scrambling for TMDS character rates at or below 340 Mcsc */ + bool supports_lte_340mcsc_scramble; + /* Supports Color Content Bits Per Component Indication */ + bool supports_ccbpci; + /* Supports Cable Status indication via writes to the SCDC */ + bool supports_cable_status; + /* Supports SCDC read request initiation */ + bool supports_scdc_read_request; + /* Supports SCDC */ + bool supports_scdc; + /* Supports 10 bits per component deep color 4:2:0 pixel encoding */ + bool supports_dc_30bit_420; + /* Supports 12 bits per component deep color 4:2:0 pixel encoding */ + bool supports_dc_36bit_420; + /* Supports 16 bits per component deep color 4:2:0 pixel encoding */ + bool supports_dc_48bit_420; + /* Supports CTA-861-I VIC indication in the AVI InfoFrame in all cases */ + bool supports_uhd_vic; + /* Fixed Rate Link (FRL) support */ + enum di_cta_hdmi_frl max_frl_rate; + /* Supports FAPA beginning on the first horizontal Blank Pixel + * immediately following the first Active Video Pixel of a video + * frame/field */ + bool supports_fapa_start_location; + /* Supports Auto Low-Latency Mode */ + bool supports_allm; + /* Supports Fast VActive */ + bool supports_fva; + /* Supports negative M_VRR values when VRR and FVA are enabled */ + bool supports_neg_mvrr; + /* Supports fractional and integer media rates that lie below the + * specified VRR_MIN when VRR is enabled and M_CONST is in use */ + bool supports_cinema_vrr; + /* Has a limit on rate-of-change variations in M_VRR values */ + bool m_delta; + /* Supports QMS VRR */ + bool supports_qms; + /* Supports FAPA End to Vactive upper bound of 360µs */ + bool supports_fapa_end_extended; + /* Lowest frame rate in Hz for Variable Refresh Rate, zero if VRR is + * not supported */ + int vrr_min_hz; + /* Highest frame rate in Hz for Variable Refresh Rate, zero if unset */ + int vrr_max_hz; + /* Indicates the lowest frame rate supported for QMS. Either VRR_MIN + * when unset, otherwise 24/1.001 Hz */ + bool qms_tfr_min; + /* Indicates the highest frame rate supported for QMS. Either VRR_MAX + * when set, otherwise 60Hz */ + bool qms_tfr_max; + /* Display Stream Compression (DSC) support, NULL if VESA DSC 1.2a is + * unsupported */ + const struct di_cta_hdmi_dsc *dsc; +}; + +/** + * HDMI Forum vendor-specific data block (HF-VSDB). + * + * This block is defined in HDMI 2.1 section 10.3.2. + */ +struct di_cta_vendor_hdmi_forum_block { + /* The content of a HF-VSDB is a HDMI SCDS. */ + struct di_cta_hdmi_scds scds; +}; + +/** + * Get the vendor-specific HDMI Forum information from a CTA data block. + * + * Note, the HDMI and HDMI Forum vendor-specific data blocks are different. + * + * Returns NULL if the data block tag is not + * DI_CTA_DATA_BLOCK_VENDOR_HDMI_FORUM. + */ +const struct di_cta_vendor_hdmi_forum_block * +di_cta_data_block_get_vendor_hdmi_forum(const struct di_cta_data_block *block); + +/** + * HDMI Forum Sink Capability Data Block (HF-SCDB). + * + * This block is defined in HDMI 2.1a + */ +struct di_cta_hdmi_forum_sink_cap { + /* The content of a HF-SCDB is a HDMI SCDS. */ + struct di_cta_hdmi_scds scds; +}; + +/** + * Get the HDMI Forum Sink Capability (HF-SCDB) from a CTA data block. + * + * Returns NULL if the data block tag is not + * DI_CTA_DATA_BLOCK_HDMI_SINK_CAP. + */ +const struct di_cta_hdmi_forum_sink_cap * +di_cta_data_block_get_hdmi_sink_cap(const struct di_cta_data_block *block); + +enum di_cta_svr_type { + /* reference contains a CTA VIC */ + DI_CTA_SVR_TYPE_VIC, + /* reference contains an index into DTDs */ + DI_CTA_SVR_TYPE_DTD_INDEX, + /* reference contains an index into T7VTDB (DisplayID Type VII Video + * Timing Data Block) DTDs and T10VTDB (DisplayID Type X Video Timing + * Data Block) CVT descriptors */ + DI_CTA_SVR_TYPE_T7T10VTDB, + /* references the first code of the first T8VTDB (DisplayID Type VIII + * Video Timing Data Block) */ + DI_CTA_SVR_TYPE_FIRST_T8VTDB, +}; + +/** + * Short Video Reference, defined in section 7.5.12. + */ +struct di_cta_svr { + enum di_cta_svr_type type; + /* A CTA VIC if type is DI_CTA_SVR_TYPE_VIC */ + struct di_cta_vic vic; + /* The index into DTDs in order of appearance if type is + * DI_CTA_SVR_TYPE_DTD_INDEX */ + uint8_t dtd_index; + /* The index into T7VTDB and T10VTDB in order of appearance if type is + * DI_CTA_SVR_TYPE_T7T10VTDB */ + uint8_t t7_t10_vtdb_index; +}; + + +/** + * Video Format Preference Data Block, defined in section 7.5.12. + */ +struct di_cta_video_format_pref_block { + /* Short Video References (SVRs). The array is NULL-terminated. + * The first SVR refers to the most-preferred Video Format, while the + * next SVRs are listed in order of decreasing preference. */ + const struct di_cta_svr *const *svrs; +}; + +/** + * Get the Video Format Preference information from a CTA data block. + * + * Returns NULL if the data block tag is not + * DI_CTA_DATA_BLOCK_VIDEO_FORMAT_PREF. + */ +const struct di_cta_video_format_pref_block * +di_cta_data_block_get_video_format_pref(const struct di_cta_data_block *block); + +#endif diff --git a/local/recipes/libs/libdisplay-info/source/include/libdisplay-info/cvt.h b/local/recipes/libs/libdisplay-info/source/include/libdisplay-info/cvt.h new file mode 100644 index 0000000000..cb6677642f --- /dev/null +++ b/local/recipes/libs/libdisplay-info/source/include/libdisplay-info/cvt.h @@ -0,0 +1,76 @@ +#ifndef DI_CVT_H +#define DI_CVT_H + +/** + * Low-level API for VESA Coordinated Video Timings (CVT) version 2.0. + */ + +#include +#include + +enum di_cvt_reduced_blanking_version { + DI_CVT_REDUCED_BLANKING_NONE, + DI_CVT_REDUCED_BLANKING_V1, + DI_CVT_REDUCED_BLANKING_V2, + DI_CVT_REDUCED_BLANKING_V3, +}; + +/** + * Input parameters, defined in table 3-1. + */ +struct di_cvt_options { + /* Version of the reduced blanking timing formula to be used */ + enum di_cvt_reduced_blanking_version red_blank_ver; + /* Desired active (visible) horizontal pixels and vertical lines per + * frame */ + int32_t h_pixels, v_lines; + /* Target vertical refresh rate (in Hz) */ + double ip_freq_rqd; + /* Whether to generate a "video-optimized" timing variant (RBv2 only) */ + bool video_opt; + /* Desired VBlank time (in µs, RBv3 only, must be greater than 460) */ + double vblank; + /* Desired additional number of pixels to add to the base HBlank + * duration (RBv3 only, must be a multiple of 8 between 0 and 120) */ + int32_t additional_hblank; + /* Indicates whether the VSync location is early (RBv3 only) */ + bool early_vsync_rqd; + /* Whether interlaced is required (no RB and RBv1 only) */ + bool int_rqd; + /* Whether margins are required (no RB and RBv1 only) */ + bool margins_rqd; +}; + +/** + * Output parameters, defined in table 3-4. + */ +struct di_cvt_timing { + /* Pixel clock frequency (in MHz) */ + double act_pixel_freq; + /* Total number of active (visible) pixels per line */ + double total_active_pixels; + /* Total number of active (visible) vertical lines per frame */ + double v_lines_rnd; + /* Number of pixels in the horizontal front porch period */ + double h_front_porch; + /* Number of pixels in the HSync period */ + double h_sync; + /* Number of pixels in the horizontal back porch period */ + double h_back_porch; + /* Number of lines in the vertical front porch period */ + double v_front_porch; + /* Number of lines in the VSync period */ + double v_sync; + /* Number of lines in the vertical back porch period */ + double v_back_porch; + /* Frame rate (in Hz) */ + double act_frame_rate; +}; + +/** + * Compute a timing via the CVT formula. + */ +void +di_cvt_compute(struct di_cvt_timing *t, const struct di_cvt_options *options); + +#endif diff --git a/local/recipes/libs/libdisplay-info/source/include/libdisplay-info/displayid.h b/local/recipes/libs/libdisplay-info/source/include/libdisplay-info/displayid.h index d62bf07bb6..99c42c7f25 100644 --- a/local/recipes/libs/libdisplay-info/source/include/libdisplay-info/displayid.h +++ b/local/recipes/libs/libdisplay-info/source/include/libdisplay-info/displayid.h @@ -1,3 +1,399 @@ -#pragma once +#ifndef DI_DISPLAYID_H +#define DI_DISPLAYID_H -#include "info.h" +/** + * libdisplay-info's low-level API for VESA Display Identification Data + * (DisplayID). + * + * The library implements DisplayID version 1.3, available at: + * https://vesa.org/vesa-standards/ + */ + +#include +#include + +#include +#include +#include + +/** + * DisplayID data structure. + */ +struct di_displayid; + +/** + * Get the DisplayID version. + */ +int +di_displayid_get_version(const struct di_displayid *displayid); + +/** + * Get the DisplayID revision. + */ +int +di_displayid_get_revision(const struct di_displayid *displayid); + +/** + * Product type identifier, defined in section 2.3. + */ +enum di_displayid_product_type { + /* Extension section */ + DI_DISPLAYID_PRODUCT_TYPE_EXTENSION = 0x00, + /* Test structure or equipment */ + DI_DISPLAYID_PRODUCT_TYPE_TEST = 0x01, + /* Display panel or other transducer, LCD or PDP module, etc. */ + DI_DISPLAYID_PRODUCT_TYPE_DISPLAY_PANEL = 0x02, + /* Standalone display device, desktop monitor, TV monitor, etc. */ + DI_DISPLAYID_PRODUCT_TYPE_STANDALONE_DISPLAY = 0x03, + /* Television receiver */ + DI_DISPLAYID_PRODUCT_TYPE_TV_RECEIVER = 0x04, + /* Repeater/translator */ + DI_DISPLAYID_PRODUCT_TYPE_REPEATER = 0x05, + /* Direct drive monitor */ + DI_DISPLAYID_PRODUCT_TYPE_DIRECT_DRIVE = 0x06, +}; + +/** + * Get the DisplayID product type. + */ +enum di_displayid_product_type +di_displayid_get_product_type(const struct di_displayid *displayid); + +/** + * DisplayID data block tag. + */ +enum di_displayid_data_block_tag { + /*Product Identification Data Block */ + DI_DISPLAYID_DATA_BLOCK_PRODUCT_ID = 0x00, + /* Display Parameters Data Block */ + DI_DISPLAYID_DATA_BLOCK_DISPLAY_PARAMS = 0x01, + /* Color Characteristics Data Block */ + DI_DISPLAYID_DATA_BLOCK_COLOR_CHARACT = 0x02, + /* Video Timing Modes Type I - Detailed Timings Data Block */ + DI_DISPLAYID_DATA_BLOCK_TYPE_I_TIMING = 0x03, + /* Video Timing Modes Type II - Detailed Timings Data Block */ + DI_DISPLAYID_DATA_BLOCK_TYPE_II_TIMING = 0x04, + /* Video Timing Modes Type III - Short Timings Data Block */ + DI_DISPLAYID_DATA_BLOCK_TYPE_III_TIMING = 0x05, + /* Video Timing Modes Type IV - DMT Timings Data Block */ + DI_DISPLAYID_DATA_BLOCK_TYPE_IV_TIMING = 0x06, + /* Supported Timing Modes - VESA DMT Timings Data Block */ + DI_DISPLAYID_DATA_BLOCK_VESA_TIMING = 0x07, + /* Supported Timing Modes - CTA-861 Timings Data Block */ + DI_DISPLAYID_DATA_BLOCK_CEA_TIMING = 0x08, + /* Video Timing Range Data Block */ + DI_DISPLAYID_DATA_BLOCK_TIMING_RANGE_LIMITS = 0x09, + /* Product Serial Number Data Block */ + DI_DISPLAYID_DATA_BLOCK_PRODUCT_SERIAL = 0x0A, + /* General-purpose ASCII String Data Block */ + DI_DISPLAYID_DATA_BLOCK_ASCII_STRING = 0x0B, + /* Display Device Data Data Block */ + DI_DISPLAYID_DATA_BLOCK_DISPLAY_DEVICE_DATA = 0x0C, + /* Interface Power Sequencing Data Block */ + DI_DISPLAYID_DATA_BLOCK_INTERFACE_POWER_SEQ = 0x0D, + /* Transfer Characteristics Data Block */ + DI_DISPLAYID_DATA_BLOCK_TRANSFER_CHARACT = 0x0E, + /* Display Interface Data Block */ + DI_DISPLAYID_DATA_BLOCK_DISPLAY_INTERFACE = 0x0F, + /* Stereo Display Interface Data Block */ + DI_DISPLAYID_DATA_BLOCK_STEREO_DISPLAY_INTERFACE = 0x10, + /* Video Timing Modes Type V - Short Timings Data Block */ + DI_DISPLAYID_DATA_BLOCK_TYPE_V_TIMING = 0x11, + /* Tiled Display Topology Data Block */ + DI_DISPLAYID_DATA_BLOCK_TILED_DISPLAY_TOPO = 0x12, + /* Video Timing Modes Type VI - Detailed Timings Data Block */ + DI_DISPLAYID_DATA_BLOCK_TYPE_VI_TIMING = 0x13, +}; + +/** + * A DisplayID data block. + */ +struct di_displayid_data_block; + +/** + * Get a DisplayID data block tag. + */ +enum di_displayid_data_block_tag +di_displayid_data_block_get_tag(const struct di_displayid_data_block *data_block); + +/** + * Display parameters feature support flags, defined in section 4.2.3. + */ +struct di_displayid_display_params_features { + /* Audio support on video interface */ + bool audio; + /* Audio inputs are provided separately from the video interface */ + bool separate_audio_inputs; + /* Audio information received via the video interface will automatically + * override any other audio input channels provided */ + bool audio_input_override; + /* Display supports the VESA Display Power Management (DPM) standard */ + bool power_management; + /* Display is capable of only a single fixed timing */ + bool fixed_timing; + /* Display is capable of supporting timings at only a single fixed pixel + * format */ + bool fixed_pixel_format; + /* Display supports ACP, ISRC1 or ISRC2 packets */ + bool ai; + /* Display by default will de-interlace any interlaced video input */ + bool deinterlacing; +}; + +/** + * Display parameters data block, defined in section 4.2. + */ +struct di_displayid_display_params { + /* Image size in millimeters accurate to the thenths place, zero if unset */ + float horiz_image_mm, vert_image_mm; + /* Native format size in pixels, zero if unset */ + int32_t horiz_pixels, vert_pixels; + /* Feature flags */ + const struct di_displayid_display_params_features *features; + /* Transfer characteristic gamma, zero if unset */ + float gamma; + /* Aspect ratio (long axis divided by short axis) */ + float aspect_ratio; + /* Color bit depth (dynamic range) */ + int32_t bits_per_color_overall, bits_per_color_native; +}; + +/** + * Get display parameters from a DisplayID data block. + * + * Returns NULL if the data block tag isn't + * DI_DISPLAYID_DATA_BLOCK_DISPLAY_PARAMS. + */ +const struct di_displayid_display_params * +di_displayid_data_block_get_display_params(const struct di_displayid_data_block *data_block); + +enum di_displayid_type_i_ii_vii_timing_stereo_3d { + /* This timing is always displayed monoscopic (no stereo) */ + DI_DISPLAYID_TYPE_I_II_VII_TIMING_STEREO_3D_NEVER = 0x00, + /* This timing is always displayed in stereo */ + DI_DISPLAYID_TYPE_I_II_VII_TIMING_STEREO_3D_ALWAYS = 0x01, + /* This timing is displayed in mono or stereo depending on a user action + * (wearing the stereo glasses, etc.) */ + DI_DISPLAYID_TYPE_I_II_VII_TIMING_STEREO_3D_USER = 0x02, +}; + +enum di_displayid_timing_aspect_ratio { + DI_DISPLAYID_TIMING_ASPECT_RATIO_1_1 = 0x00, + DI_DISPLAYID_TIMING_ASPECT_RATIO_5_4 = 0x01, + DI_DISPLAYID_TIMING_ASPECT_RATIO_4_3 = 0x02, + DI_DISPLAYID_TIMING_ASPECT_RATIO_15_9 = 0x03, + DI_DISPLAYID_TIMING_ASPECT_RATIO_16_9 = 0x04, + DI_DISPLAYID_TIMING_ASPECT_RATIO_16_10 = 0x05, + DI_DISPLAYID_TIMING_ASPECT_RATIO_64_27 = 0x06, + DI_DISPLAYID_TIMING_ASPECT_RATIO_256_135 = 0x07, + DI_DISPLAYID_TIMING_ASPECT_RATIO_UNDEFINED = 0x08, +}; + +enum di_displayid_type_i_ii_vii_timing_sync_polarity { + DI_DISPLAYID_TYPE_I_II_VII_TIMING_SYNC_NEGATIVE = 0x00, + DI_DISPLAYID_TYPE_I_II_VII_TIMING_SYNC_POSITIVE = 0x01, +}; + +/** + * Type I timing, defined in DisplayID 1.3 section 4.4.1 and + * Type II timing, defined in DisplayID 1.3 section 4.4.2 and + * Type VII timing, defined in DisplayID 2.0 section 4.3.1. + */ +struct di_displayid_type_i_ii_vii_timing { + double pixel_clock_mhz; /* mega-hertz */ + bool preferred; + enum di_displayid_type_i_ii_vii_timing_stereo_3d stereo_3d; + bool interlaced; + enum di_displayid_timing_aspect_ratio aspect_ratio; + int32_t horiz_active, vert_active; + int32_t horiz_blank, vert_blank; + int32_t horiz_offset, vert_offset; + int32_t horiz_sync_width, vert_sync_width; + enum di_displayid_type_i_ii_vii_timing_sync_polarity horiz_sync_polarity, vert_sync_polarity; +}; + +/** + * Get type I timings from a DisplayID data block. + * + * The returned array is NULL-terminated. + * + * Returns NULL if the data block tag isn't + * DI_DISPLAYID_DATA_BLOCK_TYPE_I_TIMING. + */ +const struct di_displayid_type_i_ii_vii_timing *const * +di_displayid_data_block_get_type_i_timings(const struct di_displayid_data_block *data_block); + +/** + * Get type II timings from a DisplayID data block. + * + * The returned array is NULL-terminated. + * + * Returns NULL if the data block tag isn't + * DI_DISPLAYID_DATA_BLOCK_TYPE_II_TIMING. + */ +const struct di_displayid_type_i_ii_vii_timing *const * +di_displayid_data_block_get_type_ii_timings(const struct di_displayid_data_block *data_block); + +/** + * Behavior when more than 1 tile and less than total number of tiles are driven + * by the source. + */ +enum di_displayid_tiled_topo_missing_recv_behavior { + /* Undefined */ + DI_DISPLAYID_TILED_TOPO_MISSING_RECV_UNDEF = 0, + /* The image is displayed on the tile only */ + DI_DISPLAYID_TILED_TOPO_MISSING_RECV_TILE_ONLY = 1, +}; + +/** + * Behavior of this tile when it is the only tile receiving an image from the + * source. + */ +enum di_displayid_tiled_topo_single_recv_behavior { + /* Undefined */ + DI_DISPLAYID_TILED_TOPO_SINGLE_RECV_UNDEF = 0, + /* Image is displayed on the tile only */ + DI_DISPLAYID_TILED_TOPO_SINGLE_RECV_TILE_ONLY = 1, + /* Image is scaled to fit the entire tiled display */ + DI_DISPLAYID_TILED_TOPO_SINGLE_RECV_SCALED = 2, + /* Image is cloned to all other tiles of the entire tiled display */ + DI_DISPLAYID_TILED_TOPO_SINGLE_RECV_CLONED = 3, +}; + +/** + * Tiled display capabilities. + */ +struct di_displayid_tiled_topo_caps { + /* The tiled display is within a single physical display enclosure */ + bool single_enclosure; + + /* Behavior when subsets of the tiles of the entire tiled display are + * receiving images from source */ + enum di_displayid_tiled_topo_missing_recv_behavior missing_recv_behavior; + enum di_displayid_tiled_topo_single_recv_behavior single_recv_behavior; +}; + +/** + * Tiled display bezel information. + * + * The lengths are measured in pixels, accurate to the tenths place. + */ +struct di_displayid_tiled_topo_bezel { + float top_px, bottom_px, right_px, left_px; +}; + +/** + * Tiled display topology, defined in section 4.14. + */ +struct di_displayid_tiled_topo { + /* Capabilities */ + const struct di_displayid_tiled_topo_caps *caps; + + /* Total number of horizontal/vertical tiles */ + int32_t total_horiz_tiles, total_vert_tiles; + /* Horizontal/vertical tile location */ + int32_t horiz_tile_location, vert_tile_location; + /* Horizontal/vertical size in pixels */ + int32_t horiz_tile_pixels, vert_tile_lines; + + /* Bezel information, NULL if unset */ + const struct di_displayid_tiled_topo_bezel *bezel; + + /* Vendor PnP ID, product code and serial number */ + char vendor_id[3]; + uint16_t product_code; + uint32_t serial_number; +}; + +/** + * Get tiled display topology from a DisplayID data block. + * + * Returns NULL if the data block tag isn't + * DI_DISPLAYID_DATA_BLOCK_TILED_DISPLAY_TOPO. + */ +const struct di_displayid_tiled_topo * +di_displayid_data_block_get_tiled_topo(const struct di_displayid_data_block *data_block); + +/** + * Formula/algorithm for type III timings. + */ +enum di_displayid_type_iii_timing_algo { + /* VESA CVT, standard blanking */ + DI_DISPLAYID_TYPE_III_TIMING_CVT_STANDARD_BLANKING = 0, + /* VESA CVT, reduced blanking */ + DI_DISPLAYID_TYPE_III_TIMING_CVT_REDUCED_BLANKING = 1, +}; + +/** + * Type III timing, defined in section 4.4.3. + */ +struct di_displayid_type_iii_timing { + bool preferred; + enum di_displayid_type_iii_timing_algo algo; + enum di_displayid_timing_aspect_ratio aspect_ratio; + /* Horizontal Active Image (in pixels) */ + int32_t horiz_active; + bool interlaced; + /* Frame/Field Refresh Rate (in Hz) */ + int32_t refresh_rate_hz; +}; + +/** + * Get type III timings from a DisplayID data block. + * + * The returned array is NULL-terminated. + * + * Returns NULL if the data block tag isn't + * DI_DISPLAYID_DATA_BLOCK_TYPE_III_TIMING. + */ +const struct di_displayid_type_iii_timing *const * +di_displayid_data_block_get_type_iii_timings(const struct di_displayid_data_block *data_block); + +/** + * Type IV timing, Timing Code Type + */ +enum di_displayid_type_iv_timing_code_type { + /* DMT Timing Code */ + DI_DISPLAYID_TYPE_IV_TIMING_CODE_TYPE_DMT = 0, + /* CTA/CEA VIC Timing Code */ + DI_DISPLAYID_TYPE_IV_TIMING_CODE_TYPE_VIC_CTA = 1, + /* HDMI VIC Timing Code */ + DI_DISPLAYID_TYPE_IV_TIMING_CODE_TYPE_VIC_HDMI = 2, +}; + +/** + * Type IV timing, defined in section 4.4.4. + */ +struct di_displayid_type_iv_timing { + /* Type of codes */ + enum di_displayid_type_iv_timing_code_type type; + /* Number of codes in one of dmts, cta_vics or hdmi_vics, depending on the value of type*/ + size_t codes_len; + /* DMT Timing Codes, if type is DI_DISPLAYID_TYPE_IV_TIMING_CODE_TYPE_DMT, NULL otherwise */ + const struct di_dmt_code *dmts; + /* CTA VICs, if type is DI_DISPLAYID_TYPE_IV_TIMING_CODE_TYPE_VIC_CTA, NULL otherwise */ + const struct di_cta_vic *cta_vics; + /* HDMI VICs, if type is DI_DISPLAYID_TYPE_IV_TIMING_CODE_TYPE_VIC_HDMI, NULL otherwise */ + const struct di_hdmi_vic *hdmi_vics; +}; + +/** + * Get type IV timing from a DisplayID data block. + * + * The returned array is NULL-terminated. + * + * Returns NULL if the data block tag isn't + * DI_DISPLAYID_DATA_BLOCK_TYPE_IV_TIMING. + */ +const struct di_displayid_type_iv_timing * +di_displayid_data_block_get_type_iv_timing(const struct di_displayid_data_block *data_block); + +/** + * Get DisplayID data blocks. + * + * The returned array is NULL-terminated. + */ +const struct di_displayid_data_block *const * +di_displayid_get_data_blocks(const struct di_displayid *displayid); + +#endif diff --git a/local/recipes/libs/libdisplay-info/source/include/libdisplay-info/displayid2.h b/local/recipes/libs/libdisplay-info/source/include/libdisplay-info/displayid2.h new file mode 100644 index 0000000000..27b147b5f3 --- /dev/null +++ b/local/recipes/libs/libdisplay-info/source/include/libdisplay-info/displayid2.h @@ -0,0 +1,121 @@ +#ifndef DI_DISPLAYID2_H +#define DI_DISPLAYID2_H + +/** + * libdisplay-info's low-level API for VESA Display Identification Data + * (DisplayID) version 2. + * + * The library implements DisplayID version 2.1, available at: + * https://vesa.org/vesa-standards/ + */ + +#include +#include + +/** + * DisplayID v2 data structure. + */ +struct di_displayid2; + +/** + * Get the DisplayID v2 revision. + */ +int +di_displayid2_get_revision(const struct di_displayid2 *displayid2); + +/** + * Get DisplayID v2 data blocks. + * + * The returned array is NULL-terminated. + */ +const struct di_displayid2_data_block *const * +di_displayid2_get_data_blocks(const struct di_displayid2 *displayid2); + +/** + * Product primary use case identifier, defined in table 2-3. + */ +enum di_displayid2_product_primary_use_case { + /* Extension section */ + DI_DISPLAYID2_PRODUCT_PRIMARY_USE_CASE_EXTENSION = 0x00, + /* Test structure */ + DI_DISPLAYID2_PRODUCT_PRIMARY_USE_CASE_TEST = 0x01, + /* Generic display */ + DI_DISPLAYID2_PRODUCT_PRIMARY_USE_CASE_GENERIC = 0x02, + /* Television display */ + DI_DISPLAYID2_PRODUCT_PRIMARY_USE_CASE_TV = 0x03, + /* Desktop productivity display */ + DI_DISPLAYID2_PRODUCT_PRIMARY_USE_CASE_DESKTOP_PRODUCTIVITY = 0x04, + /* Desktop gaming display */ + DI_DISPLAYID2_PRODUCT_PRIMARY_USE_CASE_DESKTOP_GAMING = 0x05, + /* Presentation display */ + DI_DISPLAYID2_PRODUCT_PRIMARY_USE_CASE_PRESENTATION = 0x06, + /* Head-mounted Virtual Reality (VR) display */ + DI_DISPLAYID2_PRODUCT_PRIMARY_USE_CASE_HMD_VR = 0x07, + /* Head-mounted Augmented Reality (AR) display */ + DI_DISPLAYID2_PRODUCT_PRIMARY_USE_CASE_HMD_AR = 0x08, +}; + +/** + * DisplayID v2 data block tag. + */ +enum di_displayid2_data_block_tag { + /* Product Identification Data Block */ + DI_DISPLAYID2_DATA_BLOCK_PRODUCT_ID = 0x20, + /* Display Parameters Data Block */ + DI_DISPLAYID2_DATA_BLOCK_DISPLAY_PARAMS = 0x21, + /* Type VII Timing – Detailed Timing Data Block */ + DI_DISPLAYID2_DATA_BLOCK_TYPE_VII_TIMING = 0x22, + /* Type VIII Timing – Enumerated Timing Code Data Block */ + DI_DISPLAYID2_DATA_BLOCK_TYPE_VIII_TIMING = 0x23, + /* Type IX Timing – Formula-based Timing Data Block */ + DI_DISPLAYID2_DATA_BLOCK_TYPE_IX_TIMING = 0x24, + /* Dynamic Video Timing Range Limits Data Block */ + DI_DISPLAYID2_DATA_BLOCK_DYN_TIMING_RANGE_LIMITS = 0x25, + /* Display Interface Features Data Block */ + DI_DISPLAYID2_DATA_BLOCK_DISPLAY_INTERFACE_FEATURES = 0x26, + /* Stereo Display Interface Data Block */ + DI_DISPLAYID2_DATA_BLOCK_STEREO_DISPLAY_INTERFACE = 0x27, + /* Tiled Display Topology Data Block */ + DI_DISPLAYID2_DATA_BLOCK_TILED_DISPLAY_TOPO = 0x28, + /* ContainerID Data Block */ + DI_DISPLAYID2_DATA_BLOCK_CONTAINERID = 0x29, + /* Type X Timing – Formula-based Timing Data Block */ + DI_DISPLAYID2_DATA_BLOCK_TYPE_X_TIMING = 0x2A, + /* Adaptive-Sync Data Block */ + DI_DISPLAYID2_DATA_BLOCK_ADAPTIVE_SYNC = 0x2B, + /* ARVR_HMD Data Block */ + DI_DISPLAYID2_DATA_BLOCK_ARVR_HMD = 0x2C, + /* ARVR_Layer Data Block */ + DI_DISPLAYID2_DATA_BLOCK_ARVR_LAYER = 0x2D, + /* CTA-861 Data Block Encapsulation DisplayID Data Block */ + DI_DISPLAYID2_DATA_BLOCK_CTA861 = 0x81, +}; + +/** + * A DisplayID v2 data block. + */ +struct di_displayid2_data_block; + +/** + * Get a DisplayID v2 data block tag. + */ +enum di_displayid2_data_block_tag +di_displayid2_data_block_get_tag(const struct di_displayid2_data_block *data_block); + +/** + * Get the DisplayID v2 product primary use case. + */ +enum di_displayid2_product_primary_use_case +di_displayid2_get_product_primary_use_case(const struct di_displayid2 *displayid2); + +/** + * Get CTA data blocks from a DisplayID v2 CTA-861 data block. + * + * The returned array is NULL-terminated. + * + * Returns NULL if the data block tag isn't DI_DISPLAYID2_DATA_BLOCK_CTA861. + */ +const struct di_cta_data_block *const * +di_displayid2_data_block_get_cta_data_blocks(const struct di_displayid2_data_block *data_block); + +#endif diff --git a/local/recipes/libs/libdisplay-info/source/include/libdisplay-info/dmt.h b/local/recipes/libs/libdisplay-info/source/include/libdisplay-info/dmt.h new file mode 100644 index 0000000000..6ee12c1f2b --- /dev/null +++ b/local/recipes/libs/libdisplay-info/source/include/libdisplay-info/dmt.h @@ -0,0 +1,77 @@ +#ifndef DI_DMT_H +#define DI_DMT_H + +/** + * libdisplay-info's low-level API for VESA Display Monitor Timing (DMT). + * + * The library implements VESA DMT version 1.0 revision 13. + */ + +#include +#include +#include + +/** + * A DMT Timing Code. + */ +struct di_dmt_code { + /* Timing Code */ + uint8_t code; +}; + +/** + * A DMT timing. + */ +struct di_dmt_timing { + /* DMT Timing Code */ + struct di_dmt_code dmt_code; + /* EDID standard timing 2-byte code, zero if unset */ + uint16_t edid_std_id; + /* CVT 3-byte code, zero if unset */ + uint32_t cvt_id; + /* Addressable pixels */ + int32_t horiz_video, vert_video; + /* Field Refresh Rate in Hz */ + float refresh_rate_hz; + /* Pixel clock in Hz */ + int32_t pixel_clock_hz; + /* Horizontal/Vertical Blanking in pixels/lines */ + int32_t horiz_blank, vert_blank; + /* Horizontal/Vertical Front Porch in pixels/lines */ + int32_t horiz_front_porch, vert_front_porch; + /* Horizontal/Vertical Sync Pulse Width in pixels/lines */ + int32_t horiz_sync_pulse, vert_sync_pulse; + /* Horizontal/Vertical Border in pixels/lines */ + int32_t horiz_border, vert_border; + /* Whether the timing has reduced blanking */ + bool reduced_blanking; +}; + +/** + * Get a VESA Display Monitor Timing (DMT) from a DMT Timing Code. + * + * Returns NULL if the DMT Timing Code is unknown. + */ +const struct di_dmt_timing * +di_dmt_timing_from_code(struct di_dmt_code dmt_code); + +/** + * Get a DMT Timing Code from a VESA Display Monitor Timing (DMT). + * + * Returns a code of 0 if the DMT Timing Code is unknown. + */ +struct di_dmt_code +di_dmt_timing_to_code(const struct di_dmt_timing *timing); + +/* See */ +struct di_edid_standard_timing; + +/** + * Get a VESA Display Monitor Timing (DMT), if any. + * + * NULL is returned if the standard timing doesn't have a DMT. + */ +const struct di_dmt_timing * +di_dmt_timing_from_edid_standard_timing(const struct di_edid_standard_timing *t); + +#endif diff --git a/local/recipes/libs/libdisplay-info/source/include/libdisplay-info/edid.h b/local/recipes/libs/libdisplay-info/source/include/libdisplay-info/edid.h index d62bf07bb6..07f9f76643 100644 --- a/local/recipes/libs/libdisplay-info/source/include/libdisplay-info/edid.h +++ b/local/recipes/libs/libdisplay-info/source/include/libdisplay-info/edid.h @@ -1,3 +1,833 @@ -#pragma once +#ifndef DI_EDID_H +#define DI_EDID_H -#include "info.h" +/** + * libdisplay-info's low-level API for Extended Display Identification + * Data (EDID). + * + * EDID 1.4 is defined in VESA Enhanced Extended Display Identification Data + * Standard release A revision 2. + */ + +#include +#include +#include + +/** + * EDID data structure. + */ +struct di_edid; + +/** + * Get the EDID version. + */ +int +di_edid_get_version(const struct di_edid *edid); + +/** + * Get the EDID revision. + */ +int +di_edid_get_revision(const struct di_edid *edid); + +/** + * EDID vendor & product identification. + */ +struct di_edid_vendor_product { + char manufacturer[3]; + uint16_t product; + uint32_t serial; /* zero if unset */ + + /* These fields are zero if unset */ + int manufacture_week; + int manufacture_year; + int model_year; +}; + +const struct di_edid_vendor_product * +di_edid_get_vendor_product(const struct di_edid *edid); + +/** + * EDID analog signal level standard. + */ +enum di_edid_video_input_analog_signal_level_std { + /* 0.700 : 0.300 : 1.000 V p-p */ + DI_EDID_VIDEO_INPUT_ANALOG_SIGNAL_LEVEL_0 = 0x00, + /* 0.714 : 0.286 : 1.000 V p-p */ + DI_EDID_VIDEO_INPUT_ANALOG_SIGNAL_LEVEL_1 = 0x01, + /* 1.000 : 0.400 : 1.400 V p-p */ + DI_EDID_VIDEO_INPUT_ANALOG_SIGNAL_LEVEL_2 = 0x02, + /* 0.700 : 0.000 : 0.700 V p-p */ + DI_EDID_VIDEO_INPUT_ANALOG_SIGNAL_LEVEL_3 = 0x03, +}; + +/** + * EDID analog video setup. + */ +enum di_edid_video_input_analog_video_setup { + /* Blank level equals black level */ + DI_EDID_VIDEO_INPUT_ANALOG_BLANK_LEVEL_EQ_BLACK = 0, + /* Blank-to-black setup or pedestal */ + DI_EDID_VIDEO_INPUT_ANALOG_BLANK_TO_BLACK_SETUP_PEDESTAL = 1, +}; + +/** + * EDID analog video input basic information, defined in section 3.6.1. + */ +struct di_edid_video_input_analog { + enum di_edid_video_input_analog_signal_level_std signal_level_std; + enum di_edid_video_input_analog_video_setup video_setup; + + /* Separate Sync H & V Signals are supported */ + bool sync_separate; + /* Composite Sync Signal on Horizontal is supported */ + bool sync_composite; + /* Composite Sync Signal on Green Video is supported */ + bool sync_on_green; + /* Serration on the Vertical Sync is supported */ + bool sync_serrations; +}; + +/** + * Get the analog video input basic information. + * + * Returns NULL if this isn't an analog display. + */ +const struct di_edid_video_input_analog * +di_edid_get_video_input_analog(const struct di_edid *edid); + +/** + * Digital video input interface standard. + */ +enum di_edid_video_input_digital_interface { + DI_EDID_VIDEO_INPUT_DIGITAL_UNDEFINED = 0x00, + DI_EDID_VIDEO_INPUT_DIGITAL_DVI = 0x01, + DI_EDID_VIDEO_INPUT_DIGITAL_HDMI_A = 0x02, + DI_EDID_VIDEO_INPUT_DIGITAL_HDMI_B = 0x03, + DI_EDID_VIDEO_INPUT_DIGITAL_MDDI = 0x04, + DI_EDID_VIDEO_INPUT_DIGITAL_DISPLAYPORT = 0x05, +}; + +/** + * EDID digital video input basic information, defined in section 3.6.1. + */ +struct di_edid_video_input_digital { + /* EDID 1.2 and 1.3 */ + bool dfp1; /* Compatible with VESA DFP 1.x TDMS CRGB */ + + /* EDID 1.4 and later */ + int color_bit_depth; /* zero if undefined */ + enum di_edid_video_input_digital_interface interface; +}; + +/** + * Get the digital video input basic information. + * + * Returns NULL if this isn't a digital display. + */ +const struct di_edid_video_input_digital * +di_edid_get_video_input_digital(const struct di_edid *edid); + +struct di_edid_screen_size { + /* Physical size in centimeters, zero if unset */ + int width_cm, height_cm; + /* Aspect ratio rounded to the hundredth decimal place, zero if unset. */ + float landscape_aspect_ratio, portait_aspect_ratio; +}; + +/** + * Get the screen size. + */ +const struct di_edid_screen_size * +di_edid_get_screen_size(const struct di_edid *edid); + +/** + * Get the display transfer characteristics from the basic EDID parameters, also + * known as "gamma". + * + * Returns 0 if unset (ie, stored in an extension block). + */ +float +di_edid_get_basic_gamma(const struct di_edid *edid); + +/** + * Supported legacy Display Power Management Signaling (DPMS) states, defined in + * section 3.6.4. + * + * Display Power Management (DPM) compliant displays only support "off". + */ +struct di_edid_dpms { + bool standby; + bool suspend; + bool off; +}; + +/** + * Get the set of supported legacy DPMS states. + */ +const struct di_edid_dpms * +di_edid_get_dpms(const struct di_edid *edid); + +/** + * Display color type. + */ +enum di_edid_display_color_type { + /* Monochrome or grayscale display */ + DI_EDID_DISPLAY_COLOR_MONOCHROME = 0x00, + /* RGB color display */ + DI_EDID_DISPLAY_COLOR_RGB = 0x01, + /* Non-RGB color display */ + DI_EDID_DISPLAY_COLOR_NON_RGB = 0x02, + /* Undefined */ + DI_EDID_DISPLAY_COLOR_UNDEFINED = 0x03, +}; + +/** + * Get the display color type. + * + * For digital displays using EDID 1.4 and later, DI_EDID_DISPLAY_COLOR_UNDEFINED + * is always returned. + */ +enum di_edid_display_color_type +di_edid_get_display_color_type(const struct di_edid *edid); + +/** + * Basic color encoding formats, defined in section 3.6.4. + */ +struct di_edid_color_encoding_formats { + bool rgb444; /* RGB 4:4:4 */ + bool ycrcb444; /* YCrCb 4:4:4 */ + bool ycrcb422; /* YCrCb 4:2:2 */ +}; + +/** + * Get the set of supported color encoding formats. + * + * Returns NULL if the display is analog or if the color encoding formats are + * not specified. + */ +const struct di_edid_color_encoding_formats * +di_edid_get_color_encoding_formats(const struct di_edid *edid); + +/** + * Miscellaneous basic features, defined in section 3.6.4. + * + * Note, the enum values don't match the specification. + */ +struct di_edid_misc_features { + /** + * First detailed timing is the preferred timing. + * + * Always set for EDID 1.4 and later. + */ + bool has_preferred_timing; + /** + * GTF using the default parameters is supported. + * + * Never set for EDID 1.4 and later. + */ + bool default_gtf; + /** + * sRGB standard default color space is primary color space. + */ + bool srgb_is_primary; + /** + * Preferred timing mode includes native pixel format and rate. + * + * Never set for EDID 1.3 and earlier. + */ + bool preferred_timing_is_native; + /** + * GTF or CVT generated timings within the display's range limits are + * accepted. + * + * Never set for EDID 1.3 and earlier. + */ + bool continuous_freq; +}; + +/** + * Get the set of miscellaneous basic features. + */ +const struct di_edid_misc_features * +di_edid_get_misc_features(const struct di_edid *edid); + +/** + * EDID display chromaticity coordinates, defined in section 3.7. + * + * The values are accurate to the thousandth place. The red, green and blue + * values are zero for monochrome displays. + */ +struct di_edid_chromaticity_coords { + float red_x, red_y; + float green_x, green_y; + float blue_x, blue_y; + float white_x, white_y; +}; + +/** + * Get chromaticity coordinates. + */ +const struct di_edid_chromaticity_coords * +di_edid_get_chromaticity_coords(const struct di_edid *edid); + +/** + * Established timings I and II, defined in section 3.8. + */ +struct di_edid_established_timings_i_ii { + /* Established timings I */ + bool has_720x400_70hz; /* 720 x 400 @ 70Hz (IBM, VGA) */ + bool has_720x400_88hz; /* 720 x 400 @ 88Hz (IBM, XGA2) */ + bool has_640x480_60hz; /* 640 x 480 @ 60Hz (IBM, VGA) */ + bool has_640x480_67hz; /* 640 x 480 @ 67Hz (Apple, Mac II) */ + bool has_640x480_72hz; /* 640 x 480 @ 72Hz (VESA) */ + bool has_640x480_75hz; /* 640 x 480 @ 75Hz (VESA) */ + bool has_800x600_56hz; /* 800 x 600 @ 56Hz (VESA) */ + bool has_800x600_60hz; /* 800 x 600 @ 60Hz (VESA) */ + + /* Established timings II */ + bool has_800x600_72hz; /* 800 x 600 @ 72Hz (VESA) */ + bool has_800x600_75hz; /* 800 x 600 @ 75Hz (VESA) */ + bool has_832x624_75hz; /* 832 x 624 @ 75Hz (Apple, Mac II) */ + bool has_1024x768_87hz_interlaced; /* 1024 x 768 @ 87Hz interlaced (IBM) */ + bool has_1024x768_60hz; /* 1024 x 768 @ 60Hz (VESA) */ + bool has_1024x768_70hz; /* 1024 x 768 @ 70Hz (VESA) */ + bool has_1024x768_75hz; /* 1024 x 768 @ 75Hz (VESA) */ + bool has_1280x1024_75hz; /* 1280 x 1024 @ 75Hz (VESA) */ + + bool has_1152x870_75hz; /* 1152 x 870 @ 75Hz (Apple, Mac II) */ +}; + +/** + * Get established timings I and II. + */ +const struct di_edid_established_timings_i_ii * +di_edid_get_established_timings_i_ii(const struct di_edid *edid); + +/** + * Aspect ratio for an EDID standard timing. + */ +enum di_edid_standard_timing_aspect_ratio { + DI_EDID_STANDARD_TIMING_16_10 = 0, /* 16:10 */ + DI_EDID_STANDARD_TIMING_4_3 = 1, /* 4:3 */ + DI_EDID_STANDARD_TIMING_5_4 = 2, /* 5:4 */ + DI_EDID_STANDARD_TIMING_16_9 = 3, /* 16:9 */ +}; + +/** + * EDID standard timing, defined in section 3.9. + */ +struct di_edid_standard_timing { + /* Horizontal addressable pixels */ + int32_t horiz_video; + /* Aspect ratio */ + enum di_edid_standard_timing_aspect_ratio aspect_ratio; + /* Field Refresh Rate in Hz */ + int32_t refresh_rate_hz; +}; + +/** + * Get the vertical addressable line count of an EDID standard timing. + */ +int32_t +di_edid_standard_timing_get_vert_video(const struct di_edid_standard_timing *t); + +/** + * Get a list of EDID standard timings. + * + * The returned array is NULL-terminated. + */ +const struct di_edid_standard_timing *const * +di_edid_get_standard_timings(const struct di_edid *edid); + +/** + * Stereo viewing support. + */ +enum di_edid_detailed_timing_def_stereo { + /* Normal Display – No Stereo */ + DI_EDID_DETAILED_TIMING_DEF_STEREO_NONE, + /* Field sequential stereo, right image when stereo sync signal = 1 */ + DI_EDID_DETAILED_TIMING_DEF_STEREO_FIELD_SEQ_RIGHT, + /* Field sequential stereo, left image when stereo sync signal = 1 */ + DI_EDID_DETAILED_TIMING_DEF_STEREO_FIELD_SEQ_LEFT, + /* 2-way interleaved stereo, right image on even lines */ + DI_EDID_DETAILED_TIMING_DEF_STEREO_2_WAY_INTERLEAVED_RIGHT, + /* 2-way interleaved stereo, left image on even lines */ + DI_EDID_DETAILED_TIMING_DEF_STEREO_2_WAY_INTERLEAVED_LEFT, + /* 4-way interleaved stereo */ + DI_EDID_DETAILED_TIMING_DEF_STEREO_4_WAY_INTERLEAVED, + /* Side-by-Side interleaved stereo */ + DI_EDID_DETAILED_TIMING_DEF_STEREO_SIDE_BY_SIDE_INTERLEAVED, +}; + +/** + * Signal definitions for EDID detailed timings, defined in notes for table 3.22. + */ +enum di_edid_detailed_timing_def_signal_type { + /* Analog composite */ + DI_EDID_DETAILED_TIMING_DEF_SIGNAL_ANALOG_COMPOSITE = 0x00, + /* Bipolar analog composite */ + DI_EDID_DETAILED_TIMING_DEF_SIGNAL_BIPOLAR_ANALOG_COMPOSITE = 0x01, + /* Digital composite */ + DI_EDID_DETAILED_TIMING_DEF_SIGNAL_DIGITAL_COMPOSITE = 0x02, + /* Digital separate */ + DI_EDID_DETAILED_TIMING_DEF_SIGNAL_DIGITAL_SEPARATE = 0x03, +}; + +/** + * Digital separate sync polarity for EDID detailed timings. + */ +enum di_edid_detailed_timing_def_sync_polarity { + DI_EDID_DETAILED_TIMING_DEF_SYNC_NEGATIVE = 0, + DI_EDID_DETAILED_TIMING_DEF_SYNC_POSITIVE = 1, +}; + +/** + * Flags for ANALOG_COMPOSITE signals + */ +struct di_edid_detailed_timing_analog_composite { + /* Sync with serrations (H-sync during V-sync) */ + bool sync_serrations; + /* Sync on green signal only (as opposed to all three + * RGB video signals) */ + bool sync_on_green; +}; + +/** + * Flags for BIPOLAR_ANALOG_COMPOSITE signals + */ +struct di_edid_detailed_timing_bipolar_analog_composite { + /* Sync with serrations (H-sync during V-sync) */ + bool sync_serrations; + /* Sync on green signal only (as opposed to all three + * RGB video signals) */ + bool sync_on_green; +}; + +/** + * Flags for DIGITAL_COMPOSITE signals + */ +struct di_edid_detailed_timing_digital_composite { + /* Sync with serrations (H-sync during V-sync) */ + bool sync_serrations; + /* Horizontal polarity (outside of V-sync) */ + enum di_edid_detailed_timing_def_sync_polarity sync_horiz_polarity; +}; + +/** + * Flags for DIGITAL_SEPARATE signals + */ +struct di_edid_detailed_timing_digital_separate { + /* Vertical polarity */ + enum di_edid_detailed_timing_def_sync_polarity sync_vert_polarity; + /* Horizontal polarity (outside of V-sync) */ + enum di_edid_detailed_timing_def_sync_polarity sync_horiz_polarity; +}; + +/** + * EDID detailed timing definition, defined in section 3.10.2. + */ +struct di_edid_detailed_timing_def { + /* Pixel clock */ + int32_t pixel_clock_hz; + /* Horizontal/Vertical Addressable Video in pixels/lines */ + int32_t horiz_video, vert_video; + /* Horizontal/Vertical Blanking in pixels/lines */ + int32_t horiz_blank, vert_blank; + /* Horizontal/Vertical Front Porch in pixels/lines */ + int32_t horiz_front_porch, vert_front_porch; + /* Horizontal/Vertical Sync Pulse Width in pixels/lines */ + int32_t horiz_sync_pulse, vert_sync_pulse; + /* Horizontal/Vertical Addressable Video Image Size in mm, zero if unset */ + int32_t horiz_image_mm, vert_image_mm; + /* Horizontal/Vertical Border in pixels/lines */ + int32_t horiz_border, vert_border; + /* Interlaced signal */ + bool interlaced; + /* Stereo Viewing Support */ + enum di_edid_detailed_timing_def_stereo stereo; + /* Signal type */ + enum di_edid_detailed_timing_def_signal_type signal_type; + /* Flags for ANALOG_COMPOSITE signals, NULL otherwise */ + const struct di_edid_detailed_timing_analog_composite *analog_composite; + /* Flags for BIPOLAR_ANALOG_COMPOSITE signals, NULL otherwise */ + const struct di_edid_detailed_timing_bipolar_analog_composite *bipolar_analog_composite; + /* Flags for DIGITAL_COMPOSITE signals, NULL otherwise */ + const struct di_edid_detailed_timing_digital_composite *digital_composite; + /* Flags for DIGITAL_SEPARATE signals, NULL otherwise */ + const struct di_edid_detailed_timing_digital_separate *digital_separate; +}; + +/** + * Get a list of EDID detailed timing definitions. + * + * The returned array is NULL-terminated. + */ +const struct di_edid_detailed_timing_def *const * +di_edid_get_detailed_timing_defs(const struct di_edid *edid); + +/** + * EDID display descriptor. + */ +struct di_edid_display_descriptor; + +/** + * Get a list of EDID display descriptors. + * + * The returned array is NULL-terminated. + */ +const struct di_edid_display_descriptor *const * +di_edid_get_display_descriptors(const struct di_edid *edid); + +/** + * EDID display descriptor tag, defined in section 3.10.3. + */ +enum di_edid_display_descriptor_tag { + /* Display Product Serial Number */ + DI_EDID_DISPLAY_DESCRIPTOR_PRODUCT_SERIAL = 0xFF, + /* Alphanumeric Data String (ASCII) */ + DI_EDID_DISPLAY_DESCRIPTOR_DATA_STRING = 0xFE, + /* Display Range Limits */ + DI_EDID_DISPLAY_DESCRIPTOR_RANGE_LIMITS = 0xFD, + /* Display Product Name */ + DI_EDID_DISPLAY_DESCRIPTOR_PRODUCT_NAME = 0xFC, + /* Color Point Data */ + DI_EDID_DISPLAY_DESCRIPTOR_COLOR_POINT = 0xFB, + /* Standard Timing Identifications */ + DI_EDID_DISPLAY_DESCRIPTOR_STD_TIMING_IDS = 0xFA, + /* Display Color Management (DCM) Data */ + DI_EDID_DISPLAY_DESCRIPTOR_DCM_DATA = 0xF9, + /* CVT 3 Byte Timing Codes */ + DI_EDID_DISPLAY_DESCRIPTOR_CVT_TIMING_CODES = 0xF8, + /* Established Timings III */ + DI_EDID_DISPLAY_DESCRIPTOR_ESTABLISHED_TIMINGS_III = 0xF7, + /* Dummy Descriptor */ + DI_EDID_DISPLAY_DESCRIPTOR_DUMMY = 0x10, +}; + +/** + * Get the tag of an EDID display descriptor. + */ +enum di_edid_display_descriptor_tag +di_edid_display_descriptor_get_tag(const struct di_edid_display_descriptor *desc); + +/** + * Get the contents of a product serial number, a data string, or a product name + * display descriptor. + * + * Returns NULL if the display descriptor tag isn't either + * DI_EDID_DISPLAY_DESCRIPTOR_PRODUCT_SERIAL_NUMBER, + * DI_EDID_DISPLAY_DESCRIPTOR_DATA_STRING or + * DI_EDID_DISPLAY_DESCRIPTOR_PRODUCT_NAME. + */ +const char * +di_edid_display_descriptor_get_string(const struct di_edid_display_descriptor *desc); + +/** + * EDID display range limits type. + * + * The values do not match the EDID specification. + * + * The CVT entry was introduced in EDID 1.4. + */ +enum di_edid_display_range_limits_type { + /* Range Limits Only - no additional timing information is provided */ + DI_EDID_DISPLAY_RANGE_LIMITS_BARE, + /* Default GTF supported */ + DI_EDID_DISPLAY_RANGE_LIMITS_DEFAULT_GTF, + /* Secondary GTF supported */ + DI_EDID_DISPLAY_RANGE_LIMITS_SECONDARY_GTF, + /* CVT supported */ + DI_EDID_DISPLAY_RANGE_LIMITS_CVT, +}; + +struct di_edid_display_range_limits_secondary_gtf { + /* Start break frequency in Hz */ + int start_freq_hz; + /* C, M, K, J parameters */ + float c, m, k, j; +}; + +enum di_edid_cvt_aspect_ratio { + DI_EDID_CVT_ASPECT_RATIO_4_3 = 1 << 7, + DI_EDID_CVT_ASPECT_RATIO_16_9 = 1 << 6, + DI_EDID_CVT_ASPECT_RATIO_16_10 = 1 << 5, + DI_EDID_CVT_ASPECT_RATIO_5_4 = 1 << 4, + DI_EDID_CVT_ASPECT_RATIO_15_9 = 1 << 3, +}; + +enum di_edid_cvt_scaling { + DI_EDID_CVT_SCALING_HORIZ_SHRINK = 1 << 7, + DI_EDID_CVT_SCALING_HORIZ_STRETCH = 1 << 6, + DI_EDID_CVT_SCALING_VERT_SHRINK = 1 << 5, + DI_EDID_CVT_SCALING_VERT_STRETCH = 1 << 4, +}; + +struct di_edid_display_range_limits_cvt { + int32_t version, revision; + /* Maximum active pixels per line, zero for no limit */ + int32_t max_horiz_px; + /* Supported aspect ratio, bitfield of enum di_edid_cvt_aspect_ratio */ + uint32_t supported_aspect_ratio; + /* Preferred aspect ratio */ + enum di_edid_cvt_aspect_ratio preferred_aspect_ratio; + /* Whether standard CVT blanking is supported */ + bool standard_blanking; + /* Whether reduced CVT blanking is supported */ + bool reduced_blanking; + /* Supported types of display scaling, bitfield of + * enum di_edid_cvt_scaling */ + uint32_t supported_scaling; + /* Preferred vertical refresh rate, in Hz */ + int32_t preferred_vert_refresh_hz; +}; + +/** + * EDID display range limits, defined in section 3.10.3.3.1. + */ +struct di_edid_display_range_limits { + /* Vertical rate limits in Hz, from 1 Hz to 510 Hz */ + int32_t min_vert_rate_hz, max_vert_rate_hz; + /* Horizontal rate limits in Hz, from 1 KHz to 510 KHz, rounded to the + * nearest multiple of 1 KHz */ + int32_t min_horiz_rate_hz, max_horiz_rate_hz; + + /* Maximum pixel clock in Hz, zero if unset, rounded to the nearest + * multiple of 0.25 MHz if CVT, otherwise to the nearest multiple of + * 10 MHz */ + int64_t max_pixel_clock_hz; + + enum di_edid_display_range_limits_type type; + + /* For SECONDARY_GTF limits, NULL otherwise */ + const struct di_edid_display_range_limits_secondary_gtf *secondary_gtf; + /* For CVT limits, NULL otherwise */ + const struct di_edid_display_range_limits_cvt *cvt; +}; + +/** + * Get the contents of a display range limits descriptor. + * + * Returns NULL if the display descriptor tag isn't + * DI_EDID_DISPLAY_DESCRIPTOR_RANGE_LIMITS. + */ +const struct di_edid_display_range_limits * +di_edid_display_descriptor_get_range_limits(const struct di_edid_display_descriptor *desc); + +/** + * Get a standard timing list from an EDID display descriptor. + * + * The returned array is NULL-terminated. + * + * Returns NULL if the display descriptor tag isn't + * DI_EDID_DISPLAY_DESCRIPTOR_STD_TIMING_IDS. + */ +const struct di_edid_standard_timing *const * +di_edid_display_descriptor_get_standard_timings(const struct di_edid_display_descriptor *desc); + +/** + * EDID Color Points, defined in section 3.10.3.5. + */ +struct di_edid_color_point { + /* White point index number */ + int index; + /* The white chromaticity values, accurate to the thousandth place */ + float white_x, white_y; + /* Gamma, zero if unset (ie, stored in an extension block) */ + float gamma; +}; + +/** + * Get a color point list from an EDID display descriptor. + * + * The returned array is NULL-terminated. + * + * Returns NULL if the display descriptor tag isn't + * DI_EDID_DISPLAY_DESCRIPTOR_COLOR_POINT. + * + * Upstream is not aware of any EDID blob containing Color Point Descriptors. + * If such a blob is found, please share it with upstream! + */ +const struct di_edid_color_point *const * +di_edid_display_descriptor_get_color_points(const struct di_edid_display_descriptor *desc); + +/** + * Established timings III + */ +struct di_edid_established_timings_iii { + size_t dmt_codes_len; + struct di_dmt_code *dmt_codes; +}; + +/** + * Get the established timings III from an EDID display descriptor. + * + * Returns NULL if the display descriptor tag isn't + * DI_EDID_DISPLAY_DESCRIPTOR_ESTABLISHED_TIMINGS_III. + */ +const struct di_edid_established_timings_iii * +di_edid_display_descriptor_get_established_timings_iii(const struct di_edid_display_descriptor *desc); + +/** + * EDID display Color Management Data, defined in section 3.10.3.7 + * + * Contains the coefficients for the function `L = a₃ × v³ + a₂ × v²` + * describing the luminance response L to some voltage v [0, 0.7] for each color + * channel. + * + * For more information see VESA DCM Standard, Version 1; January 6, 2003 + */ +struct di_edid_color_management_data { + int version; + /* red polynomial coefficient a3 */ + float red_a3; + /* red polynomial coefficient a2 */ + float red_a2; + /* green polynomial coefficient a3 */ + float green_a3; + /* green polynomial coefficient a2 */ + float green_a2; + /* blue polynomial coefficient a3 */ + float blue_a3; + /* blue polynomial coefficient a2 */ + float blue_a2; +}; + +/** + * Get the contents of a Display Color Management (DCM) Data descriptor. + * + * Returns NULL if the display descriptor tag isn't + * DI_EDID_DISPLAY_DESCRIPTOR_DCM_DATA. + * + * Upstream is not aware of any EDID blob containing DCM Data descriptors. + * If such a blob is found, please share it with upstream! + */ +const struct di_edid_color_management_data * +di_edid_display_descriptor_get_color_management_data(const struct di_edid_display_descriptor *desc); + +/** + * Aspect ratio for an EDID CVT Timing Code. + */ +enum di_edid_cvt_timing_code_aspect_ratio { + DI_EDID_CVT_TIMING_CODE_4_3 = 0, /* 4:3 */ + DI_EDID_CVT_TIMING_CODE_16_9 = 1, /* 16:9 */ + DI_EDID_CVT_TIMING_CODE_16_10 = 2, /* 16:10 */ + DI_EDID_CVT_TIMING_CODE_15_9 = 3, /* 15:9 */ +}; + +/** + * Preferred Vertical Rate for an EDID CVT Timing Code. + */ +enum di_edid_cvt_timing_code_preferred_vrate { + DI_EDID_CVT_TIMING_CODE_PREFERRED_VRATE_50HZ = 0, /* 50 Hz */ + DI_EDID_CVT_TIMING_CODE_PREFERRED_VRATE_60HZ = 1, /* 60 Hz */ + DI_EDID_CVT_TIMING_CODE_PREFERRED_VRATE_75HZ = 2, /* 75 Hz */ + DI_EDID_CVT_TIMING_CODE_PREFERRED_VRATE_85HZ = 3, /* 85 Hz */ +}; + +/** + * EDID CVT Timing Code, defined in section 3.10.3.8 + * + * For more information see VESA Coordinated Video Timings (CVT) Standard. + */ +struct di_edid_cvt_timing_code { + int32_t addressable_lines_per_field; + enum di_edid_cvt_timing_code_aspect_ratio aspect_ratio; + bool supports_50hz_sb; + bool supports_60hz_sb; + bool supports_75hz_sb; + bool supports_85hz_sb; + bool supports_60hz_rb; + enum di_edid_cvt_timing_code_preferred_vrate preferred_vertical_rate; +}; + +/** + * Get a list of CVT timing codes from an EDID display descriptor. + * The highest priority code comes first, the lowest priority code last. + * + * The returned array is NULL-terminated. + * + * Returns NULL if the display descriptor tag isn't + * DI_EDID_DISPLAY_DESCRIPTOR_CVT_TIMING_CODES. + */ +const struct di_edid_cvt_timing_code *const * +di_edid_display_descriptor_get_cvt_timing_codes(const struct di_edid_display_descriptor *desc); + +/** + * Get a list of EDID extensions. + * + * The returned array is NULL-terminated. + */ +const struct di_edid_ext *const * +di_edid_get_extensions(const struct di_edid *edid); + +/** + * EDID extension block tags, defined in section 2.2.4. + */ +enum di_edid_ext_tag { + /* CEA 861 Series Extension */ + DI_EDID_EXT_CEA = 0x02, + /* Video Timing Block Extension */ + DI_EDID_EXT_VTB = 0x10, + /* Display Information Extension */ + DI_EDID_EXT_DI = 0x40, + /* Localized String Extension */ + DI_EDID_EXT_LS = 0x50, + /* Digital Packet Video Link Extension */ + DI_EDID_EXT_DPVL = 0x60, + /* Extension Block Map */ + DI_EDID_EXT_BLOCK_MAP = 0xF0, + /* Extension defined by the display manufacturer */ + DI_EDID_EXT_VENDOR = 0xFF, + + /* DisplayID Extension */ + DI_EDID_EXT_DISPLAYID = 0x70, +}; + +/** + * EDID extension block. + */ +struct di_edid_ext; + +/** + * Get the tag of an EDID extension block. + */ +enum di_edid_ext_tag +di_edid_ext_get_tag(const struct di_edid_ext *ext); + +/* See */ +struct di_edid_cta; + +/** + * Get a CTA-861 extension block. + * + * Returns NULL if the extension block tag is not DI_EDID_EXT_CEA. + */ +const struct di_edid_cta * +di_edid_ext_get_cta(const struct di_edid_ext *ext); + +/* See */ +struct di_displayid; + +/** + * Get a DisplayID extension block. + * + * Returns NULL if the extension block tag is not DI_EDID_EXT_DISPLAYID or if + * the block does not contain a version 1 DisplayID blob. + */ +const struct di_displayid * +di_edid_ext_get_displayid(const struct di_edid_ext *ext); + +/* See */ +struct di_displayid2; + +/** + * Get a DisplayID v2 extension block. + * + * Returns NULL if the extension block tag is not DI_EDID_EXT_DISPLAYID or if + * the block does not contain a version 2 DisplayID blob. + */ +const struct di_displayid2 * +di_edid_ext_get_displayid2(const struct di_edid_ext *ext); + +#endif diff --git a/local/recipes/libs/libdisplay-info/source/include/libdisplay-info/gtf.h b/local/recipes/libs/libdisplay-info/source/include/libdisplay-info/gtf.h new file mode 100644 index 0000000000..7a7e5821e1 --- /dev/null +++ b/local/recipes/libs/libdisplay-info/source/include/libdisplay-info/gtf.h @@ -0,0 +1,72 @@ +#ifndef DI_GTF_H +#define DI_GTF_H + +#include + +/** + * Low-level API for Generalized Timing Formula Standard version 1.1. + */ + +/** + * Type of frequency parameter used in di_gtf_options.ip_freq_rqd. + */ +enum di_gtf_ip_param { + /* Vertical frame frequency (Hz) */ + DI_GTF_IP_PARAM_V_FRAME_RATE, + /* Horizontal frequency (kHz) */ + DI_GTF_IP_PARAM_H_FREQ, + /* Pixel clock rate (MHz) */ + DI_GTF_IP_PARAM_H_PIXELS, +}; + +/** + * Input options for GTF. + */ +struct di_gtf_options { + /* Number of active image pixels displayed on a line, not including any + * margin */ + int h_pixels; + /* Number of vertical lines in the displayed image */ + int v_lines; + /* Whether margins are required */ + bool margins_rqd; + /* Indicates which frequency parameter is specified in ip_freq_rqd */ + enum di_gtf_ip_param ip_param; + /* Vertical frame frequency (in Hz), horizontal frequency (in kHz) or + * pixel clock rate (in MHz) */ + double ip_freq_rqd; + /* Whether interlaced is required */ + bool int_rqd; + /* Blanking formula gradient */ + double m; + /* Blanking formula offset */ + double c; + /* Blanking formula scaling factor */ + double k; + /* Blanking formula scaling factor weighting */ + double j; +}; + +#define DI_GTF_DEFAULT_M 600.0 +#define DI_GTF_DEFAULT_C 40.0 +#define DI_GTF_DEFAULT_K 128.0 +#define DI_GTF_DEFAULT_J 20.0 + +/** + * Output timing data for GTF. + */ +struct di_gtf_timing { + int h_pixels, v_lines; + int h_sync, v_sync; + int h_front_porch, h_back_porch; + int v_front_porch, v_back_porch; + int h_border, v_border; + double pixel_freq_mhz; /* in mega-hertz */ +}; + +/** + * Compute a timing via the GTF formula. + */ +void di_gtf_compute(struct di_gtf_timing *t, const struct di_gtf_options *options); + +#endif diff --git a/local/recipes/libs/libdisplay-info/source/include/libdisplay-info/hdmi-vic.h b/local/recipes/libs/libdisplay-info/source/include/libdisplay-info/hdmi-vic.h new file mode 100644 index 0000000000..f615c0a357 --- /dev/null +++ b/local/recipes/libs/libdisplay-info/source/include/libdisplay-info/hdmi-vic.h @@ -0,0 +1,54 @@ +#ifndef DI_HDMI_VIC_H +#define DI_HDMI_VIC_H + +#include +#include +#include + +/** + * Low-level API for HDMI Video Identification Codes (HDMI VIC). + */ + +/** + * A HDMI Video Identification Code (VIC). + */ +struct di_hdmi_vic { + /* Video Identification Code (VIC) */ + uint8_t code; +}; + +/** + * A HDMI video format, not to be confused with a CTA-861 video format. + */ +struct di_hdmi_vic_video_format { + /* Video Identification Code (VIC) */ + struct di_hdmi_vic vic; + /* Horizontal/vertical active pixels/lines */ + int32_t h_active, v_active; + /* Horizontal/vertical front porch */ + int32_t h_front, v_front; + /* Horizontal/vertical sync pulse */ + int32_t h_sync, v_sync; + /* Horizontal/vertical back porch */ + int32_t h_back, v_back; + /* Pixel clock in Hz */ + int64_t pixel_clock_hz; +}; + +/** + * Get a HDMI video format from a HDMI VIC. + * + * Returns NULL if the HDMI VIC is unknown. + */ +const struct di_hdmi_vic_video_format * +di_hdmi_vic_video_format_from_vic(struct di_hdmi_vic vic); + +/** + * Get a HDMI VIC from a HDMI video format. + * + * Returns 0 if the video format is unknown. + */ +struct di_hdmi_vic +di_hdmi_vic_video_format_to_vic(const struct di_hdmi_vic_video_format *format); + +#endif diff --git a/local/recipes/libs/libdisplay-info/source/include/libdisplay-info/info.h b/local/recipes/libs/libdisplay-info/source/include/libdisplay-info/info.h index 523fa73d94..6aed499bf2 100644 --- a/local/recipes/libs/libdisplay-info/source/include/libdisplay-info/info.h +++ b/local/recipes/libs/libdisplay-info/source/include/libdisplay-info/info.h @@ -1,108 +1,247 @@ -#pragma once +#ifndef DI_INFO_H +#define DI_INFO_H -#include #include -#include +#include -#ifdef __cplusplus -extern "C" { -#endif +/** + * libdisplay-info's high-level API. + */ +/** + * Information about a display device. + * + * This includes at least one EDID or DisplayID blob. + * + * Use di_info_parse_edid() to create a struct di_info from an EDID blob. + * DisplayID blobs are not yet supported. + */ struct di_info; -struct di_edid; -struct di_edid_ext; -struct di_edid_cta; -struct di_displayid; -struct di_cta_data_block; -struct di_displayid_data_block; -struct di_edid_vendor_product { - char manufacturer[4]; - int product; - int serial; - int manufacture_week; - int manufacture_year; - int model_year; +/** + * Parse an EDID blob. + * + * Callers do not need to keep the provided data pointer valid after calling + * this function. Callers should destroy the returned pointer via + * di_info_destroy(). + */ +struct di_info * +di_info_parse_edid(const void *data, size_t size); + +/** + * Destroy a display device information structure. + */ +void +di_info_destroy(struct di_info *info); + +/** + * Returns the EDID the display device information was constructed with. + * + * The returned struct di_edid can be used to query low-level EDID information, + * see . Users should prefer the high-level API if + * possible. + * + * NULL is returned if the struct di_info doesn't contain an EDID. The returned + * struct di_edid is valid until di_info_destroy(). + */ +const struct di_edid * +di_info_get_edid(const struct di_info *info); + +/** + * Get the failure messages for this blob. + * + * NULL is returned if the blob conforms to the relevant specifications. + */ +const char * +di_info_get_failure_msg(const struct di_info *info); + +/** + * Get the make of the display device. + * + * This is the manufacturer name, either company name or PNP ID. + * This string is informational and not meant to be used in programmatic + * decisions, configuration keys, etc. + * + * The string is in UTF-8 and may contain any characters except ASCII control + * codes. + * + * The caller is responsible for free'ing the returned string. + * NULL is returned if the information is not available. + */ +char * +di_info_get_make(const struct di_info *info); + +/** + * Get the model of the display device. + * + * This is the product name/model string or product number. + * This string is informational and not meant to be used in programmatic + * decisions, configuration keys, etc. + * + * The string is in UTF-8 and may contain any characters except ASCII control + * codes. + * + * The caller is responsible for free'ing the returned string. + * NULL is returned if the information is not available. + */ +char * +di_info_get_model(const struct di_info *info); + +/** + * Get the serial of the display device. + * + * This is the product serial string or the serial number. + * This string is informational and not meant to be used in programmatic + * decisions, configuration keys, etc. + * + * The string is in UTF-8 and may contain any characters except ASCII control + * codes. + * + * The caller is responsible for free'ing the returned string. + * NULL is returned if the information is not available. + */ +char * +di_info_get_serial(const struct di_info *info); + +/** + * Display HDR static metadata + */ +struct di_hdr_static_metadata { + /* Desired content max luminance (cd/m²), zero if unset */ + float desired_content_max_luminance; + /* Desired content max frame-average luminance (cd/m²), zero if unset */ + float desired_content_max_frame_avg_luminance; + /* Desired content min luminance (cd/m²), zero if unset */ + float desired_content_min_luminance; + + /* Supported metadata types: */ + + /* Static Metadata Type 1 */ + bool type1; + + /* Supported EOFTs: */ + + /* Traditional gamma - SDR luminance range */ + bool traditional_sdr; + /* Traditional gamma - HDR luminance range */ + bool traditional_hdr; + /* Perceptual Quantization (PQ) based on SMPTE ST 2084 */ + bool pq; + /* Hybrid Log-Gamma (HLG) based on Rec. ITU-R BT.2100 */ + bool hlg; }; -struct di_edid_chromaticity_coords { - float red_x; - float red_y; - float green_x; - float green_y; - float blue_x; - float blue_y; - float white_x; - float white_y; +/** + * Get HDR static metadata support information as defined in ANSI/CTA-861-H + * as HDR Static Metadata Data Block. + * + * The returned pointer is owned by the struct di_info passed in. It remains + * valid only as long as the di_info exists, and must not be freed by the + * caller. + * + * This function does not return NULL. When HDR static metadata does not exist, + * all luminance fields are zero and only traditional_sdr is flagged as + * supported. + */ +const struct di_hdr_static_metadata * +di_info_get_hdr_static_metadata(const struct di_info *info); + +/** CIE 1931 2-degree observer chromaticity coordinates */ +struct di_chromaticity_cie1931 { + float x; + float y; }; -struct di_cta_hdr_static_metadata_eotfs { - bool pq; +/** Display color primaries and default white point */ +struct di_color_primaries { + /* Are primary[] given */ + bool has_primaries; + + /* Is default_white given */ + bool has_default_white_point; + + /* Chromaticities of the primaries in RGB order + * + * Either all values are given, or they are all zeroes. + */ + struct di_chromaticity_cie1931 primary[3]; + + /* Default white point chromaticity + * + * If non-zero, this should be the display white point when it has + * been reset to its factory defaults. Either both x and y are given, + * or they are both zero. + */ + struct di_chromaticity_cie1931 default_white; }; -struct di_cta_hdr_static_metadata_block { - int desired_content_min_luminance; - int desired_content_max_luminance; - int desired_content_max_frame_avg_luminance; - const struct di_cta_hdr_static_metadata_eotfs *eotfs; +/** + * Get display color primaries and default white point + * + * Get the parameters of the default RGB colorimetry mode which is always + * supported. Primaries for monochrome displays might be all zeroes. + * + * These primaries might not be display's physical primaries, but only the + * primaries of the default RGB colorimetry signal when using IT Video Format + * (ANSI/CTA-861-H, Section 5). + * + * The returned pointer is owned by the struct di_info passed in. It remains + * valid only as long as the di_info exists, and must not be freed by the + * caller. + * + * This function does not return NULL. + */ +const struct di_color_primaries * +di_info_get_default_color_primaries(const struct di_info *info); + +/** Additional signal colorimetry encodings supported by the display */ +struct di_supported_signal_colorimetry { + /* Rec. ITU-R BT.2020 constant luminance YCbCr */ + bool bt2020_cycc; + /* Rec. ITU-R BT.2020 non-constant luminance YCbCr */ + bool bt2020_ycc; + /* Rec. ITU-R BT.2020 RGB */ + bool bt2020_rgb; + /* SMPTE ST 2113 RGB: P3D65 and P3DCI */ + bool st2113_rgb; + /* Rec. ITU-R BT.2100 ICtCp HDR (with PQ and/or HLG) */ + bool ictcp; }; -struct di_cta_colorimetry_block { - bool bt2020_rgb; -}; +/** + * Get signal colorimetry encodings supported by the display + * + * These signal colorimetry encodings are supported in addition to the + * display's default RGB colorimetry. When you wish to use one of the additional + * encodings, they need to be explicitly enabled in the video signal. How to + * do that is specific to the signalling used, e.g. HDMI. + * + * Signal colorimetry encoding provides the color space that the signal is + * encoded for. This includes primary and white point chromaticities, and the + * YCbCr-RGB conversion if necessary. Also the transfer function is implied + * unless explicitly set otherwise, e.g. with HDR static metadata. + * See ANSI/CTA-861-H for details. + * + * The signal color volume can be considerably larger than the physically + * displayable color volume. + * + * The returned pointer is owned by the struct di_info passed in. It remains + * valid only as long as the di_info exists, and must not be freed by the + * caller. + * + * This function does not return NULL. + */ +const struct di_supported_signal_colorimetry * +di_info_get_supported_signal_colorimetry(const struct di_info *info); -struct di_displayid_display_params { - int horiz_pixels; - int vert_pixels; -}; +/** + * Get display default transfer characteristic exponent (gamma) + * + * This should be the display gamma value when the display has been reset to + * its factory defaults, and it is driven with the default RGB colorimetry. + * The value is zero when unknown. + */ +float +di_info_get_default_gamma(const struct di_info *info); -struct di_displayid_type_i_ii_vii_timing { - bool preferred; - int horiz_active; - int vert_active; -}; - -struct di_edid_misc_features { - bool preferred_timing_is_native; -}; - -struct di_edid_detailed_timing_def { - int horiz_image_mm; - int vert_image_mm; - int horiz_video; - int vert_video; -}; - -struct di_edid_screen_size { - int width_cm; - int height_cm; -}; - -const struct di_info *di_info_parse_edid(const void *data, size_t size); -void di_info_destroy(const struct di_info *info); -const struct di_edid *di_info_get_edid(const struct di_info *info); -char *di_info_get_model(const struct di_info *info); -char *di_info_get_serial(const struct di_info *info); - -const struct di_edid_detailed_timing_def *const *di_edid_get_detailed_timing_defs(const struct di_edid *edid); -const struct di_edid_screen_size *di_edid_get_screen_size(const struct di_edid *edid); -const struct di_edid_vendor_product *di_edid_get_vendor_product(const struct di_edid *edid); -const struct di_edid_chromaticity_coords *di_edid_get_chromaticity_coords(const struct di_edid *edid); -const struct di_edid_ext *const *di_edid_get_extensions(const struct di_edid *edid); -const struct di_edid_misc_features *di_edid_get_misc_features(const struct di_edid *edid); - -const struct di_edid_cta *di_edid_ext_get_cta(const struct di_edid_ext *ext); -const struct di_displayid *di_edid_ext_get_displayid(const struct di_edid_ext *ext); - -const struct di_cta_data_block *const *di_edid_cta_get_data_blocks(const struct di_edid_cta *cta); -const struct di_cta_hdr_static_metadata_block *di_cta_data_block_get_hdr_static_metadata(const struct di_cta_data_block *block); -const struct di_cta_colorimetry_block *di_cta_data_block_get_colorimetry(const struct di_cta_data_block *block); - -const struct di_displayid_data_block *const *di_displayid_get_data_blocks(const struct di_displayid *displayid); -const struct di_displayid_display_params *di_displayid_data_block_get_display_params(const struct di_displayid_data_block *block); -const struct di_displayid_type_i_ii_vii_timing *const *di_displayid_data_block_get_type_i_timings(const struct di_displayid_data_block *block); -const struct di_displayid_type_i_ii_vii_timing *const *di_displayid_data_block_get_type_ii_timings(const struct di_displayid_data_block *block); - -#ifdef __cplusplus -} #endif diff --git a/local/recipes/libs/libdisplay-info/source/include/log.h b/local/recipes/libs/libdisplay-info/source/include/log.h new file mode 100644 index 0000000000..b9f696c0a5 --- /dev/null +++ b/local/recipes/libs/libdisplay-info/source/include/log.h @@ -0,0 +1,24 @@ +#ifndef LOG_H +#define LOG_H + +/** + * Private logging utilities. + */ + +#include +#include +#include + +struct di_logger { + FILE *f; + const char *section; + bool initialized; +}; + +void +_di_logger_va_add_failure(struct di_logger *logger, const char fmt[], va_list args); + +void +_di_logger_add_failure(struct di_logger *logger, const char fmt[], ...); + +#endif diff --git a/local/recipes/libs/libdisplay-info/source/include/memory-stream.h b/local/recipes/libs/libdisplay-info/source/include/memory-stream.h new file mode 100644 index 0000000000..21dc03a3bf --- /dev/null +++ b/local/recipes/libs/libdisplay-info/source/include/memory-stream.h @@ -0,0 +1,32 @@ +#ifndef MEMORY_STREAM_H +#define MEMORY_STREAM_H + +/** + * Utility functions for memory streams. + */ + +#include +#include +#include + +struct memory_stream { + FILE *fp; + char *str; + size_t str_len; +}; + +bool +memory_stream_open(struct memory_stream *m); + +char * +memory_stream_close(struct memory_stream *m); + +/** + * A small cleanup helper that simply + * calls free(memory_stream_close(m)) to avoid + * any dangling string pointers in cleanup/error paths. + */ +void +memory_stream_cleanup(struct memory_stream *m); + +#endif diff --git a/local/recipes/libs/libdisplay-info/source/info.c b/local/recipes/libs/libdisplay-info/source/info.c new file mode 100644 index 0000000000..08ef31b7a0 --- /dev/null +++ b/local/recipes/libs/libdisplay-info/source/info.c @@ -0,0 +1,483 @@ +#include +#include +#include +#include +#include + +#include "edid.h" +#include "info.h" +#include "memory-stream.h" + +/* Generated file pnp-id-table.c: */ +const char * +pnp_id_table(const char *key); + +static bool +cta_data_block_allowed_multiple(enum di_cta_data_block_tag tag) +{ + /* See CTA-861-H, 7.6 Multiple Instances of Data Blocks. */ + switch (tag) { + case DI_CTA_DATA_BLOCK_SPEAKER_ALLOC: + case DI_CTA_DATA_BLOCK_VESA_DISPLAY_TRANSFER_CHARACTERISTIC: + case DI_CTA_DATA_BLOCK_VIDEO_CAP: + case DI_CTA_DATA_BLOCK_VESA_DISPLAY_DEVICE: + case DI_CTA_DATA_BLOCK_COLORIMETRY: + case DI_CTA_DATA_BLOCK_HDR_STATIC_METADATA: + case DI_CTA_DATA_BLOCK_VIDEO_FORMAT_PREF: + case DI_CTA_DATA_BLOCK_YCBCR420_CAP_MAP: + case DI_CTA_DATA_BLOCK_HDMI_AUDIO: + case DI_CTA_DATA_BLOCK_ROOM_CONFIG: + case DI_CTA_DATA_BLOCK_HDMI_EDID_EXT_OVERRIDE: + case DI_CTA_DATA_BLOCK_HDMI_SINK_CAP: + return false; + default: + return true; + } +} + +static const struct di_cta_data_block * +find_cta_data_block(const struct di_cta_data_block *const *blocks, enum di_cta_data_block_tag tag) +{ + size_t i; + + for (i = 0; blocks[i] != NULL; i++) { + if (di_cta_data_block_get_tag(blocks[i]) == tag) + return blocks[i]; + } + + return NULL; +} + +static const struct di_cta_data_block * +displayid2_get_cta_data_block(const struct di_displayid2 *displayid2, enum di_cta_data_block_tag tag) +{ + size_t i; + const struct di_displayid2_data_block *const *data_blocks; + const struct di_cta_data_block *const *cta_data_blocks, *match; + + data_blocks = di_displayid2_get_data_blocks(displayid2); + for (i = 0; data_blocks[i] != NULL; i++) { + cta_data_blocks = di_displayid2_data_block_get_cta_data_blocks(data_blocks[i]); + if (cta_data_blocks == NULL) { + continue; + } + + match = find_cta_data_block(cta_data_blocks, tag); + if (match != NULL) { + return match; + } + } + + return NULL; +} + +static const struct di_cta_data_block * +edid_get_cta_data_block(const struct di_edid *edid, enum di_cta_data_block_tag tag) +{ + const struct di_edid_ext *const *ext; + + /* + * Here we do not handle blocks that are allowed to occur in + * multiple instances. + */ + assert(!cta_data_block_allowed_multiple(tag)); + + for (ext = di_edid_get_extensions(edid); *ext; ext++) { + const struct di_edid_cta *cta; + const struct di_displayid2 *displayid2; + const struct di_cta_data_block *match; + + match = NULL; + switch (di_edid_ext_get_tag(*ext)) { + case DI_EDID_EXT_CEA: + cta = di_edid_ext_get_cta(*ext); + match = find_cta_data_block(di_edid_cta_get_data_blocks(cta), tag); + break; + case DI_EDID_EXT_DISPLAYID: + displayid2 = di_edid_ext_get_displayid2(*ext); + if (displayid2 == NULL) { + break; + } + match = displayid2_get_cta_data_block(displayid2, tag); + break; + default: + break; /* Ignore */ + } + + if (match != NULL) { + return match; + } + } + + return NULL; +} + +static void +derive_edid_hdr_static_metadata(const struct di_edid *edid, + struct di_hdr_static_metadata *hsm) +{ + const struct di_cta_data_block *block; + const struct di_cta_hdr_static_metadata_block *cta_hsm; + + /* By default, everything unset and only traditional gamma supported. */ + hsm->traditional_sdr = true; + + block = edid_get_cta_data_block(edid, DI_CTA_DATA_BLOCK_HDR_STATIC_METADATA); + if (!block) + return; + + cta_hsm = di_cta_data_block_get_hdr_static_metadata(block); + assert(cta_hsm); + + hsm->desired_content_max_luminance = cta_hsm->desired_content_max_luminance; + hsm->desired_content_max_frame_avg_luminance = cta_hsm->desired_content_max_frame_avg_luminance; + hsm->desired_content_min_luminance = cta_hsm->desired_content_min_luminance; + hsm->type1 = cta_hsm->descriptors->type1; + hsm->traditional_sdr = cta_hsm->eotfs->traditional_sdr; + hsm->traditional_hdr = cta_hsm->eotfs->traditional_hdr; + hsm->pq = cta_hsm->eotfs->pq; + hsm->hlg = cta_hsm->eotfs->hlg; +} + +static void +derive_edid_color_primaries(const struct di_edid *edid, + struct di_color_primaries *cc) +{ + const struct di_edid_chromaticity_coords *cm; + const struct di_edid_misc_features *misc; + + /* Trust the flag more than the fields. */ + misc = di_edid_get_misc_features(edid); + if (misc->srgb_is_primary) { + /* + * https://www.w3.org/Graphics/Color/sRGB.html + * for lack of access to IEC 61966-2-1 + */ + cc->primary[0].x = 0.640f; /* red */ + cc->primary[0].y = 0.330f; + cc->primary[1].x = 0.300f; /* green */ + cc->primary[1].y = 0.600f; + cc->primary[2].x = 0.150f; /* blue */ + cc->primary[2].y = 0.060f; + cc->has_primaries = true; + cc->default_white.x = 0.3127f; /* D65 */ + cc->default_white.y = 0.3290f; + cc->has_default_white_point = true; + + return; + } + + cm = di_edid_get_chromaticity_coords(edid); + + /* + * Broken EDID might have only partial values. + * Require all values to report anything. + */ + if (cm->red_x > 0.0f && + cm->red_y > 0.0f && + cm->green_x > 0.0f && + cm->green_y > 0.0f && + cm->blue_x > 0.0f && + cm->blue_y > 0.0f) { + cc->primary[0].x = cm->red_x; + cc->primary[0].y = cm->red_y; + cc->primary[1].x = cm->green_x; + cc->primary[1].y = cm->green_y; + cc->primary[2].x = cm->blue_x; + cc->primary[2].y = cm->blue_y; + cc->has_primaries = true; + } + if (cm->white_x > 0.0f && cm->white_y > 0.0f) { + cc->default_white.x = cm->white_x; + cc->default_white.y = cm->white_y; + cc->has_default_white_point = true; + } +} + +static void +derive_edid_supported_signal_colorimetry(const struct di_edid *edid, + struct di_supported_signal_colorimetry *ssc) +{ + const struct di_cta_data_block *block; + const struct di_cta_colorimetry_block *cm; + + /* Defaults to all unsupported. */ + + block = edid_get_cta_data_block(edid, DI_CTA_DATA_BLOCK_COLORIMETRY); + if (!block) + return; + + cm = di_cta_data_block_get_colorimetry(block); + assert(cm); + + ssc->bt2020_cycc = cm->bt2020_cycc; + ssc->bt2020_ycc = cm->bt2020_ycc; + ssc->bt2020_rgb = cm->bt2020_rgb; + ssc->st2113_rgb = cm->st2113_rgb; + ssc->ictcp = cm->ictcp; +} + +struct di_info * +di_info_parse_edid(const void *data, size_t size) +{ + struct memory_stream failure_msg; + struct di_edid *edid; + struct di_info *info; + char *failure_msg_str = NULL; + + if (!memory_stream_open(&failure_msg)) + return NULL; + + edid = _di_edid_parse(data, size, failure_msg.fp); + if (!edid) + goto err_failure_msg_file; + + info = calloc(1, sizeof(*info)); + if (!info) + goto err_edid; + + info->edid = edid; + + failure_msg_str = memory_stream_close(&failure_msg); + if (failure_msg_str && failure_msg_str[0] != '\0') + info->failure_msg = failure_msg_str; + else + free(failure_msg_str); + + derive_edid_hdr_static_metadata(info->edid, &info->derived.hdr_static_metadata); + derive_edid_color_primaries(info->edid, &info->derived.color_primaries); + derive_edid_supported_signal_colorimetry(info->edid, &info->derived.supported_signal_colorimetry); + + return info; + +err_edid: + _di_edid_destroy(edid); +err_failure_msg_file: + memory_stream_cleanup(&failure_msg); + return NULL; +} + +void +di_info_destroy(struct di_info *info) +{ + _di_edid_destroy(info->edid); + free(info->failure_msg); + free(info); +} + +const struct di_edid * +di_info_get_edid(const struct di_info *info) +{ + return info->edid; +} + +const char * +di_info_get_failure_msg(const struct di_info *info) +{ + return info->failure_msg; +} + +static void +encode_ascii_byte(FILE *out, char ch) +{ + uint8_t c = (uint8_t)ch; + + /* + * Replace ASCII control codes and non-7-bit codes + * with an escape string. The result is guaranteed to be valid + * UTF-8. + */ + if (c < 0x20 || c >= 0x7f) + fprintf(out, "\\x%02x", c); + else + fputc(c, out); +} + +static void +encode_ascii_string(FILE *out, const char *str) +{ + size_t len = strlen(str); + size_t i; + + for (i = 0; i < len; i++) + encode_ascii_byte(out, str[i]); +} + +char * +di_info_get_make(const struct di_info *info) +{ + const struct di_edid_vendor_product *evp; + char pnp_id[(sizeof(evp->manufacturer)) + 1] = { 0, }; + const char *manuf; + struct memory_stream m; + + if (!info->edid) + return NULL; + + if (!memory_stream_open(&m)) + return NULL; + + evp = di_edid_get_vendor_product(info->edid); + memcpy(pnp_id, evp->manufacturer, sizeof(evp->manufacturer)); + + manuf = pnp_id_table(pnp_id); + if (manuf) { + encode_ascii_string(m.fp, manuf); + return memory_stream_close(&m); + } + + fputs("PNP(", m.fp); + encode_ascii_string(m.fp, pnp_id); + fputs(")", m.fp); + + return memory_stream_close(&m); +} + +char * +di_info_get_model(const struct di_info *info) +{ + const struct di_edid_vendor_product *evp; + const struct di_edid_display_descriptor *const *desc; + struct memory_stream m; + size_t i; + enum di_edid_display_descriptor_tag tag; + const char *str; + + if (!info->edid) + return NULL; + + if (!memory_stream_open(&m)) + return NULL; + + desc = di_edid_get_display_descriptors(info->edid); + for (i = 0; desc[i]; i++) { + tag = di_edid_display_descriptor_get_tag(desc[i]); + if (tag != DI_EDID_DISPLAY_DESCRIPTOR_PRODUCT_NAME) + continue; + str = di_edid_display_descriptor_get_string(desc[i]); + if (str[0] == '\0') + continue; + encode_ascii_string(m.fp, str); + return memory_stream_close(&m); + } + + evp = di_edid_get_vendor_product(info->edid); + fprintf(m.fp, "0x%04" PRIX16, evp->product); + + return memory_stream_close(&m); +} + +char * +di_info_get_serial(const struct di_info *info) +{ + const struct di_edid_display_descriptor *const *desc; + const struct di_edid_vendor_product *evp; + struct memory_stream m; + size_t i; + enum di_edid_display_descriptor_tag tag; + const char *str; + + if (!info->edid) + return NULL; + + if (!memory_stream_open(&m)) + return NULL; + + desc = di_edid_get_display_descriptors(info->edid); + for (i = 0; desc[i]; i++) { + tag = di_edid_display_descriptor_get_tag(desc[i]); + if (tag != DI_EDID_DISPLAY_DESCRIPTOR_PRODUCT_SERIAL) + continue; + str = di_edid_display_descriptor_get_string(desc[i]); + if (str[0] == '\0') + continue; + encode_ascii_string(m.fp, str); + return memory_stream_close(&m); + } + + evp = di_edid_get_vendor_product(info->edid); + if (evp->serial != 0) { + fprintf(m.fp, "0x%08" PRIX32, evp->serial); + return memory_stream_close(&m); + } + + memory_stream_cleanup(&m); + return NULL; +} + +const struct di_hdr_static_metadata * +di_info_get_hdr_static_metadata(const struct di_info *info) +{ + return &info->derived.hdr_static_metadata; +} + +const struct di_color_primaries * +di_info_get_default_color_primaries(const struct di_info *info) +{ + return &info->derived.color_primaries; +} + +const struct di_supported_signal_colorimetry * +di_info_get_supported_signal_colorimetry(const struct di_info *info) +{ + return &info->derived.supported_signal_colorimetry; +} + +static const struct di_displayid * +edid_get_displayid(const struct di_edid *edid) +{ + const struct di_edid_ext *const *ext; + + for (ext = di_edid_get_extensions(edid); *ext; ext++) { + enum di_edid_ext_tag tag = di_edid_ext_get_tag(*ext); + + if (tag == DI_EDID_EXT_DISPLAYID) + return di_edid_ext_get_displayid(*ext); + } + + return NULL; +} + +static const struct di_displayid_display_params * +displayid_get_display_params(const struct di_displayid *did) +{ + const struct di_displayid_data_block *const *block = + di_displayid_get_data_blocks(did); + + for (; *block; block++) { + enum di_displayid_data_block_tag tag = di_displayid_data_block_get_tag(*block); + + if (tag == DI_DISPLAYID_DATA_BLOCK_DISPLAY_PARAMS) + return di_displayid_data_block_get_display_params(*block); + } + + return NULL; +} + +float +di_info_get_default_gamma(const struct di_info *info) +{ + const struct di_edid *edid; + const struct di_displayid *did; + const struct di_edid_misc_features *misc; + + edid = di_info_get_edid(info); + if (!edid) + return 0.0f; + + did = edid_get_displayid(edid); + if (did) { + const struct di_displayid_display_params *did_params; + + did_params = displayid_get_display_params(did); + if (did_params) + return did_params->gamma; + } + + /* Trust the flag more than the gamma field value. */ + misc = di_edid_get_misc_features(edid); + if (misc->srgb_is_primary) + return 2.2f; + + return di_edid_get_basic_gamma(edid); +} diff --git a/local/recipes/libs/libdisplay-info/source/libdisplay-info.map b/local/recipes/libs/libdisplay-info/source/libdisplay-info.map new file mode 100644 index 0000000000..3128bb4644 --- /dev/null +++ b/local/recipes/libs/libdisplay-info/source/libdisplay-info.map @@ -0,0 +1,6 @@ +{ +global: + di_*; +local: + *; +}; diff --git a/local/recipes/libs/libdisplay-info/source/log.c b/local/recipes/libs/libdisplay-info/source/log.c new file mode 100644 index 0000000000..3fb7578a5d --- /dev/null +++ b/local/recipes/libs/libdisplay-info/source/log.c @@ -0,0 +1,33 @@ +#include "log.h" + +void +_di_logger_va_add_failure(struct di_logger *logger, const char fmt[], va_list args) +{ + if (!logger->initialized) { + if (ftell(logger->f) > 0) { + fprintf(logger->f, "\n"); + } + fprintf(logger->f, "%s:\n", logger->section); + logger->initialized = true; + } + + fprintf(logger->f, " "); + vfprintf(logger->f, fmt, args); + fprintf(logger->f, "\n"); +} + +/** + * Sometimes calling the functions that wrap _di_logger_va_add_failure() (e.g. + * add_failure() in edid.c) is not possible, because we want to use a specific + * logger (and not e.g. edid->logger). This allow us to log with a custom + * logger. Avoid using this if not necessary. + */ +void +_di_logger_add_failure(struct di_logger *logger, const char fmt[], ...) +{ + va_list args; + + va_start(args, fmt); + _di_logger_va_add_failure(logger, fmt, args); + va_end(args); +} diff --git a/local/recipes/libs/libdisplay-info/source/memory-stream.c b/local/recipes/libs/libdisplay-info/source/memory-stream.c new file mode 100644 index 0000000000..ba72eb15f4 --- /dev/null +++ b/local/recipes/libs/libdisplay-info/source/memory-stream.c @@ -0,0 +1,36 @@ +#include + +#include "memory-stream.h" + +bool +memory_stream_open(struct memory_stream *m) +{ + *m = (struct memory_stream){ 0 }; + m->fp = open_memstream(&m->str, &m->str_len); + + return m->fp != NULL; +} + +char * +memory_stream_close(struct memory_stream *m) +{ + char *str; + int ret; + + ret = fclose(m->fp); + str = m->str; + *m = (struct memory_stream){ 0 }; + + if (ret != 0) { + free(str); + str = NULL; + } + + return str; +} + +void +memory_stream_cleanup(struct memory_stream *m) +{ + free(memory_stream_close(m)); +} diff --git a/local/recipes/libs/libdisplay-info/source/meson.build b/local/recipes/libs/libdisplay-info/source/meson.build index 82e4fe059a..ad4b118bca 100644 --- a/local/recipes/libs/libdisplay-info/source/meson.build +++ b/local/recipes/libs/libdisplay-info/source/meson.build @@ -1,29 +1,109 @@ -project('libdisplay-info', 'c', - version: '0.2.3', - meson_version: '>= 0.54.0', - default_options: ['warning_level=1', 'buildtype=debugoptimized']) - -inc = include_directories('include') - -libdisplay_info = shared_library('display-info', - 'di.c', - include_directories: inc, - version: meson.project_version(), - install: true) - -install_headers( - 'include/libdisplay-info/cta.h', - 'include/libdisplay-info/displayid.h', - 'include/libdisplay-info/edid.h', - 'include/libdisplay-info/info.h', - subdir: 'libdisplay-info', +project( + 'libdisplay-info', + 'c', + version: '0.4.0', + license: 'MIT', + meson_version: '>= 0.57.0', + default_options: [ + 'c_std=c11', + 'warning_level=3', + 'wrap_mode=nodownload', + ], ) -pkg = import('pkgconfig') -pkg.generate( - libdisplay_info, - name: 'libdisplay-info', - description: 'Display identification data parsing library', - filebase: 'libdisplay-info', - version: meson.project_version(), +version = meson.project_version().split('-')[0] +version_major = version.split('.')[0] +version_minor = version.split('.')[1] +assert(version_major == '0') + +dep_hwdata = dependency('hwdata', required: false, native: true) +if dep_hwdata.found() + hwdata_dir = dep_hwdata.get_variable(pkgconfig: 'pkgdatadir') + pnp_ids = files(hwdata_dir / 'pnp.ids') +else + pnp_ids = files('/usr/share/hwdata/pnp.ids') +endif + +gen_search_table = find_program('tool/gen-search-table.py') +pnp_id_table = custom_target( + 'pnp-id-table.c', + command: [ gen_search_table, pnp_ids, '@OUTPUT@', 'pnp_id_table' ], + output: 'pnp-id-table.c', ) + +cc = meson.get_compiler('c') + +math = cc.find_library('m', required: false) + +add_project_arguments(['-D_POSIX_C_SOURCE=200809L'], language: 'c') + +add_project_arguments(cc.get_supported_arguments([ + '-Wundef', + '-Wmissing-prototypes', + '-Walloca', + '-Wdeclaration-after-statement', + '-Wconversion', + + '-Wno-unused-parameter', + '-Wno-missing-field-initializers', + + '-Werror=implicit', +]), language: 'c') + +symbols_file = 'libdisplay-info.map' +symbols_flag = '-Wl,--version-script,@0@'.format(meson.current_source_dir() / symbols_file) + +di_lib = library( + 'display-info', + [ + 'cta.c', + 'cvt.c', + 'displayid.c', + 'displayid2.c', + 'dmt.c', + 'dmt-table.c', + 'edid.c', + 'gtf.c', + 'info.c', + 'log.c', + 'memory-stream.c', + 'cta-vic.c', + 'cta-vic-table.c', + 'hdmi-vic.c', + pnp_id_table, + ], + include_directories: include_directories('include'), + dependencies: [math], + link_args: symbols_flag, + link_depends: symbols_file, + install: true, + version: version, + soversion: version_minor, +) + +install_subdir( + 'include/libdisplay-info', + install_dir: get_option('includedir'), +) + +pkgconfig = import('pkgconfig') +pkgconfig.generate( + di_lib, + filebase: 'libdisplay-info', + name: 'libdisplay-info', + url: 'https://gitlab.freedesktop.org/emersion/libdisplay-info', + description: 'EDID and DisplayID library', +) + +di_dep = declare_dependency( + link_with: di_lib, + include_directories: include_directories('include'), +) + +meson.override_dependency('libdisplay-info', di_dep) + +# Red Bear: the library is what the distribution consumes. di-edid-decode and +# test/ build extra host-side executables and a shell test harness that are not +# part of the runtime and do not cross-compile cleanly for Redox. +# subdir('di-edid-decode') +# subdir('test') diff --git a/local/recipes/libs/libdisplay-info/source/tool/dump-i2c.sh b/local/recipes/libs/libdisplay-info/source/tool/dump-i2c.sh new file mode 100755 index 0000000000..f5a2199896 --- /dev/null +++ b/local/recipes/libs/libdisplay-info/source/tool/dump-i2c.sh @@ -0,0 +1,36 @@ +#!/bin/sh -eu + +if ! lsmod | grep i2c_dev >/dev/null 2>&1; then + echo "kernel module i2c-dev must be loaded" + exit +fi + +if ! command -v i2cdump >/dev/null 2>&1; then + echo "i2cdump from i2c-tools must be installed" + exit +fi + + +for connector in $(find /sys/class/drm/ -maxdepth 1 -name 'card[0-9]-*'); do + bus=$(find "$connector/" -maxdepth 1 -mindepth 1 -type d -name 'i2c-*' -print -quit | sed -n 's/.*\/i2c\-\(.*\)/\1/p') + if [ "$bus" = "" ]; then + bus=$(find "$connector/ddc/i2c-dev/" -maxdepth 1 -mindepth 1 -type d -name 'i2c-*' -print -quit | sed -n 's/.*\/i2c\-\(.*\)/\1/p') + fi + if [ "$bus" = "" ]; then + echo "Connector $(basename $connector) does not have an i2c bus. Skipping." + continue + fi + + echo "Dumping connector $(basename $connector) i2c bus address 0x50" + i2cdump -y $bus 0x50 b + + echo "Dumping connector $(basename $connector) i2c bus address 0x51" + i2cdump -y $bus 0x51 b + + echo "Dumping connector $(basename $connector) i2c bus address 0x54" + i2cdump -y $bus 0x54 b + + echo "Dumping connector $(basename $connector) i2c bus address 0x55" + i2cdump -y $bus 0x55 b +done + diff --git a/local/recipes/libs/libdisplay-info/source/tool/gen-cta-vic.py b/local/recipes/libs/libdisplay-info/source/tool/gen-cta-vic.py new file mode 100755 index 0000000000..7e7e8ee398 --- /dev/null +++ b/local/recipes/libs/libdisplay-info/source/tool/gen-cta-vic.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python3 + +import os +import sys +import pdfplumber + +if len(sys.argv) != 2: + print("usage: gen-cta-vic.py ", file=sys.stderr) + sys.exit(1) +in_path = sys.argv[1] +in_basename = os.path.basename(in_path) + +tool_dir = os.path.dirname(os.path.realpath(__file__)) +out_path = tool_dir + "/../cta-vic-table.c" + +# Page numbers for CTA-861-I +pages = { + "timing": (42, 45), + "aspect_ratio": (54, 57), +} + +def extract_rows(pdf, page_range): + (first, last) = page_range + rows = [] + for page in pdf.pages: + if page.page_number < first or page.page_number > last: + continue + table = page.extract_table() + rows += table[1:] # strip header + return rows + +def parse_vic_list(s): + return [int(vic.strip()) for vic in s.split(",")] + +def parse_interlaced(s): + if s == "Prog": + return "false" + elif s == "Int": + return "true" + else: + assert(False) + +def parse_polarity(pol): + if pol == "P": + return "POSITIVE" + elif pol == "N": + return "NEGATIVE" + else: + assert(False) + +pages_list = [] +for type, (first, last) in pages.items(): + pages_list += list(range(first, last + 1)) + +with pdfplumber.open(in_path, pages=pages_list) as pdf: + timing_rows = extract_rows(pdf, pages["timing"]) + aspect_ratio_rows = extract_rows(pdf, pages["aspect_ratio"]) + +format_table = {} +for row in timing_rows: + if not row[1]: + continue + for vic in parse_vic_list(row[1]): + # Note, some values have a footnote marker + format_table[vic] = { + "h_active": int(row[3].rstrip("b")), + "v_active": int(row[4]), + "interlaced": parse_interlaced(row[5]), + "pixel_clock_hz": int(float(row[20]) * 1000 * 1000), + "h_front": int(row[8]), + "h_sync": int(row[9]), + "h_back": int(row[10]), + "h_sync_polarity": "DI_CTA_VIC_VIDEO_FORMAT_SYNC_" + parse_polarity(row[11]), + "v_front": int(row[14]), + "v_sync": int(row[15]), + "v_back": int(row[16]), + "v_sync_polarity": "DI_CTA_VIC_VIDEO_FORMAT_SYNC_" + parse_polarity(row[17]), + } + +for row in aspect_ratio_rows: + if len(row) == 15: + # last two pages are extracted differently by pdfplumber due to + # grayed-out rows ("Forbidden" and "Reserved for the Future") + row = [row[0], row[3], row[6], row[9], row[12]] + if not row[3]: + continue + vic = int(row[0]) + fmt = format_table[vic] + # Note, some values have a footnote marker + pic_ar = row[3].rstrip("f").replace(":", "_") + fmt["picture_aspect_ratio"] = "DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_" + pic_ar + +max_vic = 0 +for vic in format_table: + if vic > max_vic: + max_vic = vic + +# Sanity check +for vic, fmt in format_table.items(): + assert("picture_aspect_ratio" in fmt) + +with open(out_path, "w+") as f: + f.write("/* DO NOT EDIT! This file has been generated by gen-cta-vic.py from {}. */\n\n".format(in_basename)) + f.write('#include \n\n') + f.write("const struct di_cta_vic_video_format _di_cta_vic_video_formats[] = {\n") + for vic in format_table: + f.write("\t[{}] = {{\n".format(vic)) + f.write("\t\t.vic = {{ .code = {} }},\n".format(vic)) + for k, v in format_table[vic].items(): + f.write("\t\t.{} = {},\n".format(k, v)) + f.write("\t},\n") + f.write("};\n\n") + f.write("const size_t _di_cta_vic_video_formats_len = {};\n".format(max_vic + 1)) diff --git a/local/recipes/libs/libdisplay-info/source/tool/gen-dmt.py b/local/recipes/libs/libdisplay-info/source/tool/gen-dmt.py new file mode 100755 index 0000000000..af6f1e90e8 --- /dev/null +++ b/local/recipes/libs/libdisplay-info/source/tool/gen-dmt.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python3 + +import os +import subprocess +import sys + +def parse_hex_byte(s): + assert(s.endswith("h")) + s = s[:-1] + assert(0 <= int(s, 16) <= 255) + return "0x" + s + +def parse_hex_list(s): + if s == "n/a": + return None + if s.startswith("("): + assert(s.endswith(")h")) + s = s[1:-2] + l = [b.strip() for b in s.split(",")] + l = [b[:-1] if b.endswith("h") else b for b in l] + for b in l: + assert(0 <= int(b, 16) <= 255) + return "0x" + "".join(l) + +def parse_pixel_param(l): + l = l[2].split(" ") + assert(len(l) >= 2 and l[1] == "Pixels") + return int(l[0]) + +def parse_line_param(l): + l = l[1].split(" ") + assert(len(l) >= 2 and l[1] == "lines") + return int(l[0]) + +def parse_timing(page): + lines = [l.strip() for l in page.splitlines()] + if len(lines) < 6: + return None + try: + i = lines.index("Detailed Timing Parameters") + except ValueError: + return None + raw_metadata = lines[1:i] + raw_params = lines[i+1:] + + metadata = {} + for l in raw_metadata: + if not ": " in l: + continue + k, v = l.split(":", 1) + metadata[k.strip()] = v.strip() + assert("Resolution" in metadata and "EDID ID" in metadata) + + if "Proposed" in metadata and not "Adopted" in metadata: + print("skipping proposed but not adopted timing: " + metadata["Resolution"], + file=sys.stderr) + return None + + params = {} + for l in raw_params: + if not " = " in l: + continue + tokens = [l.strip() for l in l.split("=")] + params[tokens[0]] = tokens[1:] + assert("Timing Name" in params) + + res = metadata["Resolution"] + size, rest = res.split(" at ", 1) + horiz_video, vert_video = size.split("x", 1) + refresh_rate_hz, rest = rest.split(" Hz ", 1) + reduced_blanking = "reduced blanking" in rest.lower() + + horiz_video = int(horiz_video.strip()) + vert_video = int(vert_video.strip()) + refresh_rate_hz = float(refresh_rate_hz.strip()) + + ids = {} + for kv in metadata["EDID ID"].split(";"): + k, v = kv.split(":") + ids[k.strip()] = v.strip() + assert("DMT ID" in ids) + + dmt_id = parse_hex_byte(ids["DMT ID"]) + edid_std_id = parse_hex_list(ids["Std. 2 Byte Code"]) + cvt_id = parse_hex_list(ids["CVT 3 Byte Code"]) + + pixel_clock_mhz = float(params["Pixel Clock"][0].split(";")[0]) + horiz_blank = parse_pixel_param(params["Hor Blank Time"]) + horiz_front_porch = parse_pixel_param(params["// H Front Porch"]) + horiz_sync_pulse = parse_pixel_param(params["Hor Sync Time"]) + horiz_border = parse_pixel_param(params["// H Right Border"]) + assert(horiz_border == parse_pixel_param(params["// H Left Border"])) + vert_blank = parse_line_param(params["Ver Blank Time"]) + vert_front_porch = parse_line_param(params["// V Front Porch"]) + vert_sync_pulse = parse_line_param(params["Ver Sync Time"]) + vert_border = parse_line_param(params["// V Bottom Border"]) + assert(vert_border == parse_line_param(params["// V Top Border"])) + + return { + "dmt_code": "{{ .code = {} }}".format(dmt_id), + "edid_std_id": 0 if edid_std_id is None else edid_std_id, + "cvt_id": 0 if cvt_id is None else cvt_id, + "horiz_video": horiz_video, + "vert_video": vert_video, + "refresh_rate_hz": refresh_rate_hz, + "pixel_clock_hz": int(pixel_clock_mhz * 1000 * 1000), + "horiz_blank": horiz_blank, + "horiz_front_porch": horiz_front_porch, + "horiz_sync_pulse": horiz_sync_pulse, + "horiz_border": horiz_border, + "vert_blank": vert_blank, + "vert_front_porch": vert_front_porch, + "vert_sync_pulse": vert_sync_pulse, + "vert_border": vert_border, + "reduced_blanking": "true" if reduced_blanking else "false", + } + +if len(sys.argv) != 2: + print("usage: gen-dmt.py ", file=sys.stderr) + sys.exit(1) +in_path = sys.argv[1] +in_basename = os.path.basename(in_path) + +tool_dir = os.path.dirname(os.path.realpath(__file__)) +out_path = tool_dir + "/../dmt-table.c" + +cmd = ["pdftotext", "-nodiag", "-layout", in_path, "-"] +page = "" +timings = [] +for l in subprocess.check_output(cmd, text=True): + if l.startswith("\f"): + t = parse_timing(page) + if t is not None: + timings.append(t) + page = l[1:] + else: + page += l + +with open(out_path, "w+") as f: + f.write("/* DO NOT EDIT! This file has been generated by gen-dmt.py from {}. */\n\n".format(in_basename)) + f.write('#include \n\n') + f.write("const struct di_dmt_timing _di_dmt_timings[] = {\n") + for t in timings: + f.write("\t{\n") + for k, v in t.items(): + f.write("\t\t.{} = {},\n".format(k, v)) + f.write("\t},\n") + f.write("};\n\n") + f.write("const size_t _di_dmt_timings_len = {};\n".format(len(timings))) diff --git a/local/recipes/libs/libdisplay-info/source/tool/gen-search-table.py b/local/recipes/libs/libdisplay-info/source/tool/gen-search-table.py new file mode 100755 index 0000000000..5e95c0d8eb --- /dev/null +++ b/local/recipes/libs/libdisplay-info/source/tool/gen-search-table.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python3 + +""" +string to string mapping table + +License: MIT + +Copyright (c) 2020 Simon Ser +Copyright 2022 Collabora, Ltd. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import sys + +def escape_for_c(s): + l = [c if c.isalnum() or c in ' .,' else '\\%03o' % ord(c) for c in s] + return ''.join(l) + +if len(sys.argv) != 4: + print('usage: ' + sys.argv[0] + ' ', file=sys.stderr) + sys.exit(1) + +infile = sys.argv[1] +outfile = sys.argv[2] +ident = sys.argv[3] + +records = {} + +with open(infile, 'r') as f: + for line in f: + [pnpid, name] = line.split(maxsplit=1) + + if len(pnpid) != 3: + print("Warning: skipping invalid PNP ID %s" % (repr(pnpid)), file=sys.stderr) + continue + + records[pnpid] = escape_for_c(name.strip()) + +with open(outfile, 'w') as f: + f.write( +f''' +#include +#include + +const char * +{ident}(const char *key); + +const char * +{ident}(const char *key) +{{ + size_t len = strlen(key); + size_t i; + uint32_t u = 0; + + if (len > 4) + return NULL; + + for (i = 0; i < len; i++) + u = (u << 8) | (uint8_t)key[i]; + + switch (u) {{ +''') + for id in sorted(records.keys()): + u = 0 + for c in id: + u = (u << 8) | ord(c) + f.write(f' case {u}: return "{records[id]}";\n') + f.write( +f''' + default: + return NULL; + }} +}} +''')