Linux Media Controller development
 help / color / mirror / Atom feed
From: Julian Orth <ju.orth@gmail.com>
To: linux-media@vger.kernel.org, Julian Orth <ju.orth@gmail.com>
Subject: [PATCH v4l-utils 2/2] edid-decode: correct and complete AMD VSDB
Date: Fri, 25 Sep 2026 15:46:43 +0200	[thread overview]
Message-ID: <20260925-jorth-amd-2-v1-2-41deccc043f0@gmail.com> (raw)
In-Reply-To: <20260925-jorth-amd-2-v1-0-41deccc043f0@gmail.com>

This completes the work from 07632801b9d39d4fdb41a3346e74f88b16519fa2.

Sources:

- Reverse engineering of the AMD windows kernel driver.
- Patent US10706812B2 describes FreeSync 2 HDR and contains VSDB
  examples.
- Various panel manufacturers have published or leaked data sheets that
  contain annotated EDIDs. For example [1].

Changes:

- The bits in the flags bytes are now properly labeled and printed.
- Global backlight control is no longer advertised in version 3.
- x[12..=14] contain the maximum FSFT speed.
- x[4] is the MCCS VCP code.
- 3 of the flags from x[5] are no longer used in version 3.
- 1 of the flags from x[5] indicates SmartPower OLED support in version
  3.
- x[5]&0x4 was previously labeled to indicate PQ support, but that was
  incorrect and an artifact of how the windows driver converts these
  bits.
- The meaning of PQ-Interim is now documented.
- x[5]&0x20 indicates HDR10 support in version 3.
- x[15] contains another DPCD offset that can be used by the source to
  load information about the sink product and firmware checksum.

[1]: https://www.panelook.cn/upload/202601/NS160QDM-NZ1_Rev.P0_20241125_202601266620.pdf

Signed-off-by: Julian Orth <ju.orth@gmail.com>
---
 utils/edid-decode/parse-cta-block.cpp | 291 ++++++++++++++++++----------------
 1 file changed, 150 insertions(+), 141 deletions(-)

diff --git a/utils/edid-decode/parse-cta-block.cpp b/utils/edid-decode/parse-cta-block.cpp
index a52bc115..e083d968 100644
--- a/utils/edid-decode/parse-cta-block.cpp
+++ b/utils/edid-decode/parse-cta-block.cpp
@@ -1586,151 +1586,160 @@ static void cta_hf_sbtmdb(const unsigned char *x, unsigned length)
 
 static void cta_amd(const unsigned char *x, unsigned length)
 {
-    // x[00]          - major version
-    // x[01] & 0x01   - unknown, set in almost all EDIDs
-    // x[01] & 0x02   - set if x[05..=09] are valid
-    // x[01] & 0x04   - global backlight control support
-    // x[01] & 0x08   - local dimming support
-    // x[01] & 0x10   - FreeSync Panel Replay/PSR switch support
-    // x[01] & 0x20   - called the SPRS bit by AMD, related to Replay
-    // x[01] & 0x40   - FreeSync Panel Replay support
-    // x[01] & 0x80   - set if x[12..=14] are valid
-    // x[02]          - min refresh rate
-    // x[03]          - max refresh rate in versions < 3
-    // x[04]          - MCCS flags
-    // -- start of version 2 fields
-    // x[05] & 0x01   - unknown
-    // x[05] & 0x02   - unknown
-    // x[05] & 0x04   - PQ EOTF support
-    // x[05] & 0x08   - unknown
-    // x[05] & 0x10   - PQ-Interim EOTF support (unknown what that is but enumerated in ADL)
-    // x[05] & 0x20   - unknown but see the calculation of supported_tf below
-    // x[05] & 0xc0   - set to 1 if the display is Mini LED
-    //                  set to 2 if the display is OLED
-    // x[06]          - max luminance
-    // x[07]          - min luminance
-    // x[08]          -      if x[01] & 0x08 or display is OLED: max luminance without local dimming
-    //                  else if x[01] & 0x04                   : max luminance at min backlight
-    // x[09]          -      if x[01] & 0x08 or display is OLED: min luminance without local dimming
-    //                  else if x[01] & 0x04                   : min luminance at min backlight
-    // -- start of version 3 fields
-    // x[10]          - max refresh rate lower 8 bits in version >= 3
-    // x[11] & 0x03   - max refresh rate upper 2 bits in version >= 3
-    // x[11] & 0xfc   - unused
-    // -- end of version 3 mandatory fields, fields below will be parsed only if the VSDB
-    //    block is large enough
-    // x[12]          - unknown
-    // x[13]          - unknown
-    // x[14]          - unknown
-    // x[15] & 0x80   - x[15] & 0x7f is valid
-    // x[15] & 0x7f   - unknown
-    // x[16] & 0x80   - x[16] & 0x7f is valid
-    // x[16] & 0x7f   - DPCD (DisplayPort configuration data) register offset for
-    //                  proprietary AMD settings
-
-    if (length < 5) {
-        printf("    Data block is truncated (length = %d)\n", length);
-        return;
-    }
-
-    unsigned version = x[0];
+	// x[00]          - major version
+	// x[01] & 0x01   - FreeSync support
+	// x[01] & 0x02   - FreeSync 2 HDR support, x[05..=09] are valid
+	// x[01] & 0x04   - In version 2: Global Backlight Control support
+	//                  In version 3: Reserved
+	// x[01] & 0x08   - Local Dimming Control support
+	// x[01] & 0x10   - In version 3: FreeSync Panel Replay/PSR switch support
+	// x[01] & 0x20   - In version 3: Dynamic Link Rate Control support
+	// x[01] & 0x40   - In version 3: FreeSync Panel Replay support
+	// x[01] & 0x80   - In version 3: FreeSync Fast Transport (FSFT) support, x[12..=14] are valid
+	// x[02]          - min refresh rate
+	// x[03]          - max refresh rate in versions < 3
+	// x[04]          - MCCS VCP code
+	// -- start of version 2 fields
+	// x[05] & 0x01   - In version 2: sRGB EOTF support
+	//                  In version 3: Reserved
+	// x[05] & 0x02   - In version 2: BT.709 EOTF support
+	//                  In version 3: Reserved
+	// x[05] & 0x04   - Gamma 2.2 EOTF support
+	// x[05] & 0x08   - In version 2: Gamma 2.6 EOTF support
+	//                  In version 3: Reserved
+	//        NOTE:
+	//          The 4 EOTFs above use the native gamut and scale the input
+	//          of the inverse EOTF such that output 0 maps to 0 luminance
+	//          and output 1 maps to the max luminance.
+	// x[05] & 0x10   - In version 2:
+	//                      PQ-Interim EOTF support
+	//                      This uses the native gamut and an affine function is
+	//                      applied to the inverse EOTF output such that output 0 maps
+	//                      to the min luminance and output 1 maps to the max luminance.
+	//                  In version 3:
+	//                      SmartPower OLED support
+	// x[05] & 0x20   - In version 2: Reserved
+	//                  In version 3: HDR10 support
+	// x[05] & 0xc0   - In version 2: Reserved
+	//                  In version 3: set to 1 if the display is Mini LED
+	//                                set to 2 if the display is OLED
+	//                                value 3 is reserved
+	// x[06]          - max luminance
+	// x[07]          - min luminance
+	// x[08]          -      if x[01] & 0x08 or display is OLED: max luminance without local dimming
+	//                  else if x[01] & 0x04                   : max luminance at min backlight
+	// x[09]          -      if x[01] & 0x08 or display is OLED: min luminance without local dimming
+	//                  else if x[01] & 0x04                   : min luminance at min backlight
+	// -- start of version 3 fields
+	// x[10]          - max refresh rate lower 8 bits in version >= 3
+	// x[11] & 0x03   - max refresh rate upper 2 bits in version >= 3
+	// x[11] & 0xfc   - unused
+	// -- end of version 3 mandatory fields, fields below will be parsed only if the VSDB
+	//    block is large enough
+	// x[12..=14]     - FSFT maximum pixel clock in little-endian kHz.
+	// x[15] & 0x80   - x[15] & 0x7f is valid
+	// x[15] & 0x7f   - DPCD (DisplayPort configuration data) register offset for
+	//                  additional display information. (Base = 0x403)
+	// x[16] & 0x80   - x[16] & 0x7f is valid
+	// x[16] & 0x7f   - DPCD (DisplayPort configuration data) register offset for
+	//                  eDP display features handshake. (Base = 0x380)
+
+	if (length < 5) {
+		printf("    Data block is truncated (length = %d)\n", length);
+		return;
+	}
+
+	unsigned version = x[0];
 	printf("    Version: %u\n", version);
 
-	printf("    Feature Caps: 0x%02x\n", x[1]);
-    bool hdr_fields_valid = false;
-    bool supports_local_dimming = false;
-    bool has_global_backlight_control = false;
-    if (version > 1) {
-        hdr_fields_valid = x[1] & 0x02;
-        has_global_backlight_control = x[1] & 0x04;
-        supports_local_dimming = x[1] & 0x08;
-        if (has_global_backlight_control)
-            printf("      Global Backlight Control Supported\n");
-        if (supports_local_dimming)
-            printf("      Local Dimming Supported\n");
-        if (version > 2) {
-            // Obtained from:
-            // https://github.com/torvalds/linux/commit/ec8e59cb4e0c1a52d5a541fff9dcec398b48f7b4
-            if (x[1] & 0x40)
-                printf("      FreeSync Panel Replay Supported\n");
-        }
-    }
-
-    unsigned short max_refresh_rate;
-    if (version > 2 && length > 0xb) {
-        max_refresh_rate = (x[0xb] & 3) << 8 | x[0xa];
-    } else {
-        max_refresh_rate = x[3];
-    }
+	printf("    Flags 1: 0x%02x\n", x[1]);
+	if (x[1] & 0x01)
+		printf("      FreeSync Supported\n");
+	if (x[1] & 0x02 && version > 1)
+		printf("      FreeSync 2 HDR Supported\n");
+	if (x[1] & 0x04 && version == 2)
+		printf("      Global Backlight Control Supported\n");
+	if (x[1] & 0x08 && version > 1)
+		printf("      Local Dimming Supported\n");
+	if (x[1] & 0x10 && version > 2)
+		printf("      Panel Replay/PSR Switch Supported\n");
+	if (x[1] & 0x20 && version > 2)
+		printf("      Dynamic Link Rate Control Supported\n");
+	if (x[1] & 0x40 && version > 2)
+		printf("      Panel Replay Supported\n");
+	if (x[1] & 0x80 && version > 2)
+		printf("      FreeSync Fast Transport Supported\n");
+
+	unsigned short max_refresh_rate;
+	if (version > 2 && length > 11) {
+		max_refresh_rate = (x[11] & 3) << 8 | x[10];
+	} else {
+		max_refresh_rate = x[3];
+	}
 	printf("    Minimum Refresh Rate: %u Hz\n", x[2]);
 	printf("    Maximum Refresh Rate: %u Hz\n", max_refresh_rate);
-	// Freesync 1.x flags
-	// One or more of the 0xe6 bits signal that the VESA MCCS
-	// protocol is used to switch the Freesync range
-	printf("    Flags 1.x: 0x%02x%s\n", x[4],
-	       (x[4] & 0xe6) ? " (MCCS)" : "");
-
-    if (version < 2)
-        return;
-    if (length < 10) {
-        printf("    Data block is truncated (length = %d)\n", length);
-        return;
-    }
-
-    printf("    Flags 2.x: 0x%02x\n", x[5]);
-
-    if (!hdr_fields_valid)
-        return;
-
-    const unsigned TF_PQ2084         = 0x0004;
-    const unsigned TF_LINEAR_0_125   = 0x0020;
-    const unsigned TF_GAMMA_22       = 0x0080;
-
-    const unsigned CS_BT2020         = 0x0008;
-
-    // the calculation of supported_tf is a bit weird because it doesn't correspond to
-    // the description in the comment at the start of the function. but this is what ADL
-    // (AMD Display Library) reports
-    unsigned supported_tf = 0;
-    unsigned supported_cs = 0;
-    bool supports_hdr10 = x[5] & 0x34;
-    if (supports_hdr10) {
-        supported_tf |= TF_LINEAR_0_125 | TF_PQ2084;
-        supported_cs |= CS_BT2020;
-    }
-    if (x[5] & 0x04)
-        supported_tf |= TF_GAMMA_22;
-
-    if (supported_tf & TF_PQ2084)
-        printf("      ST 2084 (PQ) EOTF Supported\n");
-    if (supported_tf & TF_LINEAR_0_125)
-        printf("      Linear EOTF (Windows scRGB, 0.0 - 125.0) Supported\n");
-    if (supported_tf & TF_GAMMA_22)
-        printf("      Gamma 2.2 EOTF Supported\n");
-
-    if (supported_cs & CS_BT2020)
-        printf("      BT.2020 Gamut Supported\n");
-
-    bool is_mini_led = x[5] >> 6 == 1;
-    bool is_oled     = x[5] >> 6 == 2;
-    if (is_mini_led)
-        printf("      Display is Mini LED\n");
-    if (is_oled)
-        printf("      Display is OLED\n");
-
-    printf("    Maximum luminance: %u (%.3f cd/m^2)\n",
-           x[6], 50.0 * pow(2, x[6] / 32.0));
-    printf("    Minimum luminance: %u (%.3f cd/m^2)\n",
-           x[7], (50.0 * pow(2, x[6] / 32.0)) * pow(x[7] / 255.0, 2) / 100.0);
-    if (supports_local_dimming || is_oled || has_global_backlight_control) {
-        const char *type = "minimum backlight";
-        if (supports_local_dimming || is_oled)
-            type = "without local dimming";
-        printf("    Maximum luminance (%s): %u (%.3f cd/m^2)\n",
-               type, x[8], 50.0 * pow(2, x[8] / 32.0));
-        printf("    Minimum luminance (%s): %u (%.3f cd/m^2)\n",
-               type, x[9], (50.0 * pow(2, x[8] / 32.0)) * pow(x[9] / 255.0, 2) / 100.0);
-    }
+
+	if (x[4] != 0)
+		printf("    MCCS VCP Code: 0x%02x\n", x[4]);
+
+	if (version < 2)
+		return;
+	if (length < 10) {
+		printf("    Data block is truncated (length = %d)\n", length);
+		return;
+	}
+
+	printf("    Flags 5: 0x%02x\n", x[5]);
+	if (version >= 3) {
+		if (x[5] >> 6 == 1)
+			printf("      Display is Mini LED\n");
+		if (x[5] >> 6 == 2)
+			printf("      Display is OLED\n");
+		if (x[5] & 0x10)
+			printf("      SmartPower OLED Supported\n");
+	}
+	if (x[1] & 0x02) {
+		if (x[5] & 0x01 && version < 3)
+			printf("      sRGB EOTF Supported\n");
+		if (x[5] & 0x02 && version < 3)
+			printf("      BT.709 EOTF Supported\n");
+		if (x[5] & 0x04)
+			printf("      Gamma 2.2 EOTF Supported\n");
+		if (x[5] & 0x08 && version < 3)
+			printf("      Gamma 2.6 EOTF Supported\n");
+		if (x[5] & 0x10 && version < 3)
+			printf("      PQ-Interim EOTF Supported\n");
+		if (x[5] & 0x20 && version >= 3)
+			printf("      ST 2084 (PQ) EOTF Supported\n");
+
+		printf("    Maximum luminance: %u (%.3f cd/m^2)\n",
+		       x[6], 50.0 * pow(2, x[6] / 32.0));
+		printf("    Minimum luminance: %u (%.3f cd/m^2)\n",
+		       x[7], (50.0 * pow(2, x[6] / 32.0)) * pow(x[7] / 255.0, 2) / 100.0);
+		if (x[1] & 0x08 || (x[5] >> 6 == 2 && version > 2) || (x[1] & 0x04 && version == 2)) {
+			const char *type = "minimum backlight";
+			if (x[1] & 0x08 || (x[5] >> 6 == 2 && version > 2))
+				type = "without local dimming";
+			printf("    Maximum luminance (%s): %u (%.3f cd/m^2)\n",
+			       type, x[8], 50.0 * pow(2, x[8] / 32.0));
+			printf("    Minimum luminance (%s): %u (%.3f cd/m^2)\n",
+			       type, x[9], (50.0 * pow(2, x[8] / 32.0)) * pow(x[9] / 255.0, 2) / 100.0);
+		}
+	}
+
+	if (version < 3)
+		return;
+
+	if (length > 14 && x[1] & 0x80) {
+		unsigned khz = (unsigned)x[12] | ((unsigned)x[13] << 8) | ((unsigned)x[14] << 16);
+		printf("    FreeSync Fast Transport maximum pixel clock: %u kHz\n", khz);
+	}
+
+	if (length > 15 && x[15] & 0x80)
+		printf("    Additional Sink Information Address: 0x%x\n", 0x403 + (x[15] & 0x7f));
+
+	if (length > 16 && x[16] & 0x80)
+		printf("    eDP Display Features Handshake Address: 0x%x\n", 0x380 + (x[16] & 0x7f));
 }
 
 static std::string display_use_case(unsigned char x)

-- 
2.55.0


  parent reply	other threads:[~2026-09-25 13:47 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-25 13:46 [PATCH 0/2] edid-decode: correct and complete AMD VSDB Julian Orth
2026-09-25 13:46 ` [PATCH v4l-utils 1/2] edid-decode: fix incorrect shift Julian Orth
2026-09-25 13:46 ` Julian Orth [this message]
2026-09-28  8:24 ` [PATCH 0/2] edid-decode: correct and complete AMD VSDB Hans Verkuil

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260925-jorth-amd-2-v1-2-41deccc043f0@gmail.com \
    --to=ju.orth@gmail.com \
    --cc=linux-media@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox