This is an automatic generated email to let you know that the following patch were queued at the http://git.linuxtv.org/cgit.cgi/v4l-utils.git tree:
Subject: edid-decode: add SCDC read and parse code Author: Hans Verkuil <[email protected]> Date: Mon May 11 08:18:19 2026 +0200 This allows you to read the SCDC data from the DDC line and to parse that data. Signed-off-by: Hans Verkuil <[email protected]> utils/edid-decode/ddc.cpp | 57 +++++++++++ utils/edid-decode/edid-decode.1.in | 11 ++- utils/edid-decode/edid-decode.cpp | 104 +++++++++++++++++++- utils/edid-decode/edid-decode.h | 5 + utils/edid-decode/meson.build | 3 +- utils/edid-decode/parse-scdc.cpp | 196 +++++++++++++++++++++++++++++++++++++ 6 files changed, 371 insertions(+), 5 deletions(-) --- http://git.linuxtv.org/cgit.cgi/v4l-utils.git/commit/?id=3ab9884b6846b9f8555d804a051e8148d0870388 diff --git a/utils/edid-decode/ddc.cpp b/utils/edid-decode/ddc.cpp index a74e884a4706..26db094d2ef1 100644 --- a/utils/edid-decode/ddc.cpp +++ b/utils/edid-decode/ddc.cpp @@ -41,6 +41,9 @@ #define EDID_ADDR 0x50 #define SEGMENT_POINTER_ADDR 0x30 +// i2c address for SCDC +#define SCDC_ADDR 0x54 + // i2c addresses for HDCP #define HDCP_PRIM_ADDR 0x3a #define HDCP_SEC_ADDR 0x3b @@ -417,3 +420,57 @@ int read_hdcp_ri(int adapter_fd, double ri_time) } return 0; } + +static int read_scdc_registers(int adapter_fd, __u8 *scdc, bool update_only) +{ + struct i2c_rdwr_ioctl_data data; + struct i2c_msg write_message; + struct i2c_msg read_message; + __u8 offset = update_only ? 0x10 : 0; + int err; + + write_message = { + .addr = SCDC_ADDR, + .len = 1, + .buf = &offset + }; + read_message = { + .addr = SCDC_ADDR, + .flags = I2C_M_RD, + .len = (__u16)(update_only ? 2 : 128), + .buf = scdc + }; + + struct i2c_msg msgs[2] = { write_message, read_message }; + + data.msgs = msgs + update_only; + data.nmsgs = ARRAY_SIZE(msgs) - update_only; + err = ioctl(adapter_fd, I2C_RDWR, &data); + + if (err < 0) { + fprintf(stderr, "Unable to read SCDC %02x-%02x: %s\n", + offset, offset + read_message.len - 1, strerror(errno)); + return -1; + } + if (update_only) + return 0; + offset = 128; + data.msgs[1].buf = scdc + 128; + err = ioctl(adapter_fd, I2C_RDWR, &data); + + if (err < 0) { + fprintf(stderr, "Unable to read SCDC %02x-%02x: %s\n", + offset, offset + read_message.len - 1, strerror(errno)); + return -1; + } + return 0; +} + +int read_scdc(int adapter_fd, parse_data &pdata, bool update_only) +{ + if (read_scdc_registers(adapter_fd, pdata.buf, update_only)) + return -1; + + pdata.buf_size = update_only ? 2 : 256; + return 0; +} diff --git a/utils/edid-decode/edid-decode.1.in b/utils/edid-decode/edid-decode.1.in index 9b93b1a8f3a1..3bbcdd0f97f6 100644 --- a/utils/edid-decode/edid-decode.1.in +++ b/utils/edid-decode/edid-decode.1.in @@ -131,7 +131,7 @@ DDDB: VESA Display Device Data Block (DDDB) Standard, Version 1 .TP HDMI 1.4b: High-Definition Multimedia Interface, Version 1.4b .TP -HDMI 2.1b: High-Definition Multimedia Interface, Version 2.1b +HDMI 2.2: High-Definition Multimedia Interface, Version 2.2 .TP HDCP 1.4: High-bandwidth Digital Content Protection System, Revision 1.4 .TP @@ -238,6 +238,9 @@ On Linux systems ELD can be extracted via the amixer command (copy all hex after 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 +.TP +\fB\-D\fR, \fB\-\-scdc\fR \fI<file>\fR +Parse the given SCDC file. Read data from stdin if '-' was passed as argument. .TP \fB\-J\fR, \fB\-\-hdcp\fR \fI<file>\fR Parse the given HDCP file. Read data from stdin if '-' was passed as argument. @@ -341,6 +344,12 @@ Read and parse the HDCP data from the i2c adapter. \fB\-\-i2c\-hdcp-ri\fR \fI<t>\fR Every \fI<t>\fR seconds read and report the HDCP Ri value from the i2c adapter. .TP +\fB\-\-i2c\-scdc\fR +Read and parse the SCDC data from the i2c adapter. +.TP +\fB\-\-i2c\-scdc\-update\fR +Read and parse the SCDC Update Flags from the i2c adapter. +.TP \fB\-\-i2c\-test\-reliability\fR [\fBduration\fR=\fI<secs>\fR][,\fBsleep\fR=\fI<msecs>\fR] Read the EDID continuously for \fI<secs>\fR seconds (default is 0, meaning forever), with a sleep of \fI<msecs>\fR milliseconds (default value is 50 ms) in between diff --git a/utils/edid-decode/edid-decode.cpp b/utils/edid-decode/edid-decode.cpp index 00092f2108b0..d9fc7b37cd82 100644 --- a/utils/edid-decode/edid-decode.cpp +++ b/utils/edid-decode/edid-decode.cpp @@ -44,6 +44,7 @@ enum Option { OptBlockHexDump = 'b', OptCheck = 'c', OptCheckInline = 'C', + OptSCDC = 'D', OptEld = 'E', OptFBModeTimings = 'F', OptHelp = 'h', @@ -67,6 +68,8 @@ enum Option { OptVersion, OptDiag, OptI2CEDID, + OptI2CSCDC, + OptI2CSCDCUpdate, OptI2CHDCP, OptI2CHDCPRi, OptI2CTestReliability, @@ -115,6 +118,8 @@ static struct option long_options[] = { #ifdef __HAS_I2C_DEV__ { "i2c-adapter", required_argument, 0, OptI2CAdapter }, { "i2c-edid", no_argument, 0, OptI2CEDID }, + { "i2c-scdc-update", no_argument, 0, OptI2CSCDCUpdate }, + { "i2c-scdc", no_argument, 0, OptI2CSCDC }, { "i2c-hdcp", no_argument, 0, OptI2CHDCP }, { "i2c-hdcp-ri", required_argument, 0, OptI2CHDCPRi }, { "i2c-test-reliability", optional_argument, 0, OptI2CTestReliability }, @@ -134,6 +139,7 @@ static struct option long_options[] = { { "list-rids", no_argument, 0, OptListRIDs }, { "infoframe", required_argument, 0, OptInfoFrame }, { "eld", required_argument, 0, OptEld }, + { "scdc", required_argument, 0, OptSCDC }, { "hdcp", required_argument, 0, OptHDCP }, { 0, 0, 0, 0 } }; @@ -180,6 +186,8 @@ static void usage(void) " -a, --i2c-adapter <dev> Use <dev> to access the DDC lines.\n" " If <dev> starts with a digit, then /dev/i2c-<dev> is used.\n" " --i2c-edid Read the EDID from the DDC lines.\n" + " --i2c-scdc Read the SCDC from the DDC lines.\n" + " --i2c-scdc-update Read the SCDC Update information (bytes 0x10-0x11) from the DDC lines.\n" " --i2c-hdcp Read the HDCP from the DDC lines.\n" " --i2c-hdcp-ri=<t> Read and print the HDCP Ri information every <t> seconds.\n" " --i2c-test-reliability [duration=<secs>][,sleep=<msecs>]\n" @@ -232,9 +240,9 @@ static void usage(void) " --list-rids List all known RIDs.\n" " --list-rid-timings <rid> List all timings for RID <rid> or all known RIDs if <rid> is 0.\n" " -I, --infoframe <file> Parse the InfoFrame from <file> (or stdin if '-' was specified) that was sent to this display.\n" - " This option can be specified multiple times for different InfoFrame files.\n" " -E, --eld <file> Parse the EDID-Like Data, ELD from <file> (or stdin if '-' was specified).\n" " This option can be specified multiple times for different ELD files.\n" + " -D, --scdc <file> Parse the SCDC data from <file> (or stdin if '-' was specified).\n" " -J, --hdcp <file> Parse the HDCP data from <file> (or stdin if '-' was specified).\n" " -h, --help Display this help message.\n"); } @@ -1663,7 +1671,7 @@ int edid_state::parse_edid() return failures ? -2 : 0; } -/* InfoFrame/HDCP/ELD parsing */ +/* InfoFrame/ELD/SCDC/HDCP parsing */ static unsigned char infoframe[32]; @@ -1683,6 +1691,15 @@ static struct parse_data eld_pdata = { 0 }; +static unsigned char scdc[256]; + +static struct parse_data scdc_pdata = { + "SCDC", + scdc, + sizeof(scdc), + 0 +}; + // primary data, KSV FIFO data, and secondary data static unsigned char hdcp[256 + 128 * 5 + 256]; @@ -1988,6 +2005,58 @@ int edid_state::parse_eld_file(const std::string &fname) return failures ? -2 : 0; } +int edid_state::parse_scdc_pdata(parse_data &pdata) +{ + state.block_nr = 0; + state.data_block.clear(); + + if (!options[OptSkipHexDump]) { + printf("edid-decode %s (hex):\n\n", pdata.name); + if (pdata.buf_size == 2) { + hex_block("", pdata.buf, pdata.buf_size, false); + } else { + hex_block("", pdata.buf, 128, false); + if (pdata.buf_size > 128) { + printf("\n"); + hex_block("", pdata.buf + 128, pdata.buf_size - 128, false); + } + } + if (options[OptOnlyHexDump]) + return 0; + printf("\n----------------\n\n"); + } + + if (pdata.buf_size < 2) { + fail("%s is too small to parse.\n", pdata.name); + return -1; + } + + parse_scdc(pdata.buf, pdata.buf_size == 2 ? 2 : (pdata.buf_size <= 128 ? 128 : 256)); + + if (!options[OptCheck] && !options[OptCheckInline]) + return 0; + + printf("\n----------------\n"); + + if (!options[OptSkipSHA] && strlen(STRING(SHA))) { + options[OptSkipSHA] = 1; + printf("\n"); + print_version(); + } + + if (options[OptCheck]) { + if (warnings) + show_data_msgs(pdata.name, true); + if (failures) + show_data_msgs(pdata.name, false); + } + + printf("\n%s conformity: %s\n", + state.data_block.empty() ? pdata.name : state.data_block.c_str(), + failures ? "FAIL" : "PASS"); + return failures ? -2 : 0; +} + static int hdcp_from_file(parse_data &pdata, const char *from_file) { int ret = data_from_file(pdata, from_file); @@ -2619,6 +2688,7 @@ int main(int argc, char **argv) double hdcp_ri_sleep = 0; std::vector<std::string> if_names; std::vector<std::string> eld_names; + std::string scdc_name; std::string hdcp_name; unsigned test_rel_duration = 0; unsigned test_rel_msleep = 50; @@ -2773,6 +2843,9 @@ int main(int argc, char **argv) case OptInfoFrame: if_names.push_back(optarg); break; + case OptSCDC: + scdc_name = optarg; + break; case OptHDCP: hdcp_name = optarg; break; @@ -2833,6 +2906,10 @@ int main(int argc, char **argv) ret = 0; } } else if (adapter_fd >= 0) { + if (options[OptI2CSCDC]) + ret = read_scdc(adapter_fd, scdc_pdata, false); + if (!ret && options[OptI2CSCDCUpdate]) + ret = read_scdc(adapter_fd, scdc_pdata, true); if (!ret && options[OptI2CHDCP]) ret = read_hdcp(adapter_fd, hdcp_pdata); if (!ret && options[OptI2CHDCPRi]) @@ -2840,7 +2917,8 @@ int main(int argc, char **argv) if (options[OptI2CTestReliability]) ret = test_reliability(adapter_fd, test_rel_duration, test_rel_msleep); } else if ((options[OptInfoFrame] || options[OptEld] || - options[OptHDCP]) && !options[OptGTF]) { + options[OptSCDC] || options[OptHDCP]) && + !options[OptGTF]) { ret = 0; } else { ret = edid_from_file("-", stdout); @@ -2889,6 +2967,26 @@ int main(int argc, char **argv) bool show_line = state.edid_size; + if (scdc_name.length()) { + int r = data_from_file(scdc_pdata, scdc_name.c_str()); + if (r && !ret) + ret = r; + } + if (scdc_pdata.buf_size) { + if (show_line) + printf("\n================\n\n"); + show_line = true; + + state.warnings = state.failures = 0; + for (unsigned i = 0; i < EDID_MAX_BLOCKS + 1; i++) { + s_msgs[i][0].clear(); + s_msgs[i][1].clear(); + } + int r = state.parse_scdc_pdata(scdc_pdata); + if (r && !ret) + ret = r; + } + if (hdcp_name.length()) { int r = hdcp_from_file(hdcp_pdata, hdcp_name.c_str()); if (r && !ret) diff --git a/utils/edid-decode/edid-decode.h b/utils/edid-decode/edid-decode.h index 8ecd44f312fd..9a527818875f 100644 --- a/utils/edid-decode/edid-decode.h +++ b/utils/edid-decode/edid-decode.h @@ -565,6 +565,9 @@ struct edid_state { int parse_eld_file(const std::string &fname); void parse_eld_baseline(const unsigned char *x, unsigned size); + int parse_scdc_pdata(parse_data &pdata); + void parse_scdc(const unsigned char *scdc, unsigned size); + int parse_hdcp_pdata(parse_data &pdata); void parse_hdcp(const unsigned char *hdcp, unsigned size); }; @@ -657,6 +660,7 @@ int read_edid(int adapter_fd, unsigned char *edid, bool silent = false); int test_reliability(int adapter_fd, unsigned secs, unsigned msleep); int read_hdcp(int adapter_fd, parse_data &pdata); int read_hdcp_ri(int adapter_fd, double ri_time); +int read_scdc(int adapter_fd, parse_data &pdata, bool update_only); #else @@ -664,6 +668,7 @@ static inline int read_edid(int adapter_fd, unsigned char *edid) { return -ENODE static inline int test_reliability(int adapter_fd, unsigned secs, unsigned msleep) { return -ENODEV; } static inline int read_hdcp(int adapter_fd) { return -ENODEV; } static inline int read_hdcp_ri(int adapter_fd, double ri_time) { return -ENODEV; } +static inline int read_scdc(int adapter_fd, parse_data &pdata, bool update_only) { return -ENODEV; } #endif diff --git a/utils/edid-decode/meson.build b/utils/edid-decode/meson.build index d0fd3d094e37..1906826cc69b 100644 --- a/utils/edid-decode/meson.build +++ b/utils/edid-decode/meson.build @@ -9,8 +9,9 @@ edid_decode_sources = [ 'parse-ls-ext-block.cpp', 'parse-vtb-ext-block.cpp', 'parse-if.cpp', - 'parse-eld.cpp', + 'parse-scdc.cpp', 'parse-hdcp.cpp', + 'parse-eld.cpp', ] edid_decode_args = [] diff --git a/utils/edid-decode/parse-scdc.cpp b/utils/edid-decode/parse-scdc.cpp new file mode 100644 index 000000000000..d27972ee7a0c --- /dev/null +++ b/utils/edid-decode/parse-scdc.cpp @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright 2024 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + * + * Author: Hans Verkuil <[email protected]> + */ + +#include <stdio.h> + +#include "edid-decode.h" + +static void print_scdc_update(const unsigned char *scdc_update) +{ + unsigned char flags = scdc_update[0]; + + printf("Update Flags: 0x%02x 0x%02x\n", flags, scdc_update[1]); + if (flags & 0x01) + printf("\tStatus_Update\n"); + if (flags & 0x02) + printf("\tCED_Update\n"); + if (flags & 0x04) + printf("\tRR_Test\n"); + if (flags & 0x08) + printf("\tSource_Test_Update\n"); + if (flags & 0x10) + printf("\tFRL_start\n"); + if (flags & 0x20) + printf("\tFLT_update\n"); + if (flags & 0x40) + printf("\tRSED_Update\n"); + if (flags & 0x80) + printf("\tLIP_Update\n"); +} + +void edid_state::parse_scdc(const unsigned char *scdc, unsigned size) +{ + static const char *frl_rates[] = { + "Disable FRL", + "3 Lanes at 3 Gbps per Lane", + "3 Lanes at 6 Gbps per Lane", + "4 Lanes at 6 Gbps per Lane", + "4 Lanes at 8 Gbps per Lane", + "4 Lanes at 10 Gbps per Lane", + "4 Lanes at 12 Gbps per Lane", + "4 Lanes at 16 Gbps per Lane", + "4 Lanes at 20 Gbps per Lane", + "4 Lanes at 24 Gbps per Lane", + }; + unsigned char v; + + if (size == 2) { + print_scdc_update(scdc); + return; + } + + printf("Sink Version: %u Source Version: %u\n", scdc[0x01], scdc[0x02]); + + v = scdc[0x03]; + printf("Sink Supported Features: 0x%02x\n", v); + if (v & 0x80) + printf("\tSink_LIP_Sup\n"); + + v = scdc[0x05]; + printf("Source Supported Features: 0x%02x\n", v); + if (v & 0x80) + printf("\tSource_LIP_Sup\n"); + + print_scdc_update(scdc + 0x10); + + v = scdc[0x20]; + printf("TMDS Configuration: 0x%02x\n", v); + if (v & 0x01) + printf("\tScrambling_Enable\n"); + printf("\tTMDS_Bit_Clock_Ratio: 1/%u\n", (v & 0x02) ? 40 : 10); + + v = scdc[0x21]; + printf("TMDS Scrambler Status: 0x%02x\n", v); + if (v & 0x01) + printf("\tTMDS_Scrambling_Status\n"); + + v = scdc[0x30]; + printf("Sink Configuration: 0x%02x 0x%02x\n", v, scdc[0x31]); + if (v & 0x01) + printf("\tRR_Enable\n"); + if (v & 0x02) + printf("\tFLT_no_retrain\n"); + if (v & 0x04) + printf("\tDAISY_ERR\n"); + if (v & 0x08) + printf("\tMONO_DIR_ON\n"); + if (v & 0x10) + printf("\tMONO_DIR_ERR\n"); + if (v & 0x20) + printf("\tCA_PWR_ERR\n"); + v = scdc[0x31]; + if ((v & 0xf) >= ARRAY_SIZE(frl_rates)) + printf("\tFRL_Rate: %u\n", v & 0xf); + else + printf("\tFRL_Rate: %s\n", frl_rates[v & 0xf]); + printf("\tFFE_Levels: %u\n", v >> 4); + + bool uses_4lanes = (v & 0xf) >= 3; + + v = scdc[0x35]; + printf("Source Test Configuration: 0x%02x\n", v); + if (v & 0x02) + printf("\tTxFFE_Pre_Shoot_Only\n"); + if (v & 0x04) + printf("\tTxFFE_De_Emphasis_Only\n"); + if (v & 0x08) + printf("\tTxFFE_No_FFE\n"); + if (v & 0x20) + printf("\tFLT_no_timeout\n"); + if (v & 0x40) + printf("\tDSC_FRL_Max\n"); + if (v & 0x80) + printf("\tFRL_Max\n"); + + v = scdc[0x40]; + printf("Status Flags: 0x%02x 0x%02x 0x%02x\n", v, scdc[0x41], scdc[0x42]); + if (v & 0x01) + printf("\tClock_Detected\n"); + if (v & 0x02) + printf("\tCh0_Ln0_Locked\n"); + if (v & 0x04) + printf("\tCh1_Ln1_Locked\n"); + if (v & 0x08) + printf("\tCh2_Ln2_Locked\n"); + if (v & 0x10) + printf("\tLane3_Locked\n"); + if (v & 0x40) + printf("\tFLT_Ready\n"); + if (v & 0x80) + printf("\tDSC_DecodeFail\n"); + if (scdc[0x41] || scdc[0x42]) { + v = scdc[0x41]; + printf("\tLn0_LTP_req: %u\n", v & 0xf); + printf("\tLn1_LTP_req: %u\n", v >> 4); + v = scdc[0x42]; + printf("\tLn2_LTP_req: %u\n", v & 0xf); + printf("\tLn3_LTP_req: %u\n", v >> 4); + } + + if ((scdc[0x51] & 0x80) || (scdc[0x53] & 0x80) || (scdc[0x55] & 0x80) || + (scdc[0x58] & 0x80) || (scdc[0x5a] & 0x80)) { + printf("Character Error Detection:\n"); + unsigned short cnt = scdc[0x51] << 8 | scdc[0x50]; + if (cnt & 0x8000) + printf("\tChannel 0 Error Count: %u\n", cnt & 0x7fff); + cnt = scdc[0x53] << 8 | scdc[0x52]; + if (cnt & 0x8000) + printf("\tChannel 1 Error Count: %u\n", cnt & 0x7fff); + cnt = scdc[0x55] << 8 | scdc[0x54]; + if (cnt & 0x8000) + printf("\tChannel 2 Error Count: %u\n", cnt & 0x7fff); + cnt = scdc[0x58] << 8 | scdc[0x57]; + if (cnt & 0x8000) + printf("\tLane 3 Error Count: %u\n", cnt & 0x7fff); + unsigned char sum = 0; + for (unsigned i = 0x50; i <= 0x55; i++) + sum += scdc[i]; + if (uses_4lanes) + sum += scdc[0x57] + scdc[0x58]; + sum = 0xff - sum; + sum++; + if (sum != scdc[0x56]) + printf("\tInvalid Checksum: expected 0x%02x, got 0x%02x\n", + sum, scdc[0x56]); + cnt = scdc[0x5a] << 8 | scdc[0x59]; + if (cnt & 0x8000) + printf("\tReed-Solomon Corrections Counter: %u\n", cnt & 0x7fff); + } else { + printf("Character Error Detection: no errors detected\n"); + } + + v = scdc[0xc0]; + if (v) { + printf("Test Read Request: 0x%02x\n", v); + if (v & 0x80) + printf("\tTestReadRequest\n"); + printf("\tTestReadRequestDelay: %ums\n", v & 0x7f); + } + + if (scdc[0xd0] || scdc[0xd1] || scdc[0xd2]) { + printf("Manufacturer Specific:\n"); + printf("\tOUI: %02x-%02x-%02x\n", scdc[0xd2], scdc[0xd1], scdc[0xd0]); + char s[9] = {}; + memcpy(s, scdc + 0xd3, 8); + printf("\tDevice_ID_String: '%s'\n", s); + printf("\tHW Revision: %u.%u SW Revision: %u.%u\n", + scdc[0xdb] >> 4, scdc[0xdb] & 0xf, + scdc[0xdc], scdc[0xdd]); + } else { + printf("Manufacturer Specific: none\n"); + } +} _______________________________________________ linuxtv-commits mailing list -- [email protected] To unsubscribe send an email to [email protected]
