From c157e774e5fc40353f4b09ffb5f96d57fc6c98f3 Mon Sep 17 00:00:00 2001 From: "xinzhu.yin" Date: Thu, 16 Apr 2026 16:51:05 +0800 Subject: [PATCH] =?UTF-8?q?1.1.0=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 33 + RELEASE.md | 94 + UniTAP/.pylintrc | 644 ++ UniTAP/__init__.py | 11 + UniTAP/__main__.py | 6 + UniTAP/common/__init__.py | 9 + UniTAP/common/audio_mode.py | 271 + UniTAP/common/color_info.py | 111 + UniTAP/common/data_info.py | 64 + UniTAP/common/dsc_compression_info.py | 96 + UniTAP/common/dsc_video_frame.py | 29 + UniTAP/common/timestamp.py | 64 + UniTAP/common/timing.py | 90 + UniTAP/common/video_frame.py | 94 + UniTAP/common/video_mode.py | 52 + UniTAP/dev/__init__.py | 2 + UniTAP/dev/dev_3xx_roles.py | 459 + UniTAP/dev/dev_4xx_roles.py | 220 + UniTAP/dev/dev_5xx_roles.py | 258 + UniTAP/dev/device.py | 154 + UniTAP/dev/modules/__init__.py | 5 + UniTAP/dev/modules/capturer/__init__.py | 2 + UniTAP/dev/modules/capturer/capture.py | 550 + UniTAP/dev/modules/capturer/result_object.py | 108 + UniTAP/dev/modules/capturer/statuses.py | 100 + UniTAP/dev/modules/capturer/types.py | 180 + UniTAP/dev/modules/capturer/utils.py | 1 + UniTAP/dev/modules/dut_tests/__init__.py | 4 + .../dut_tests/cfg/[0x01]src_dut_crc.json | 195 + .../dut_tests/cfg/[0x02]src_dut_audio.json | 86 + .../cfg/[0x03]src_dut_dp_8b10b_ll_cts.json | 2041 ++++ .../cfg/[0x04]src_dut_link_config.json | 91 + .../cfg/[0x05]src_dut_dp_electrical.json | 222 + .../cfg/[0x06]src_dut_hdmi_electrical.json | 174 + .../cfg/[0x07]any_dut_usbc_electrical.json | 300 + .../cfg/[0x08]src_dut_dp_128b132b_ll_cts.json | 2917 +++++ .../cfg/[0x09]src_dut_hdcp_23_1a.json | 41 + .../cfg/[0x0A]src_dut_hdcp_23_1b.json | 20 + .../cfg/[0x0B]src_dut_hdcp_23_3a.json | 20 + .../cfg/[0x0C]src_dut_hdcp_23_3b.json | 20 + .../cfg/[0x0D]snk_dut_hdcp_23_2c.json | 20 + .../cfg/[0x0E]snk_dut_hdcp_23_3c.json | 32 + .../dut_tests/cfg/[0x0F]snk_dut_vrr.json | 111 + .../dut_tests/cfg/[0x10]src_dut_vrr.json | 111 + .../dut_tests/cfg/[0x11]src_dut_cec.json | 26 + .../cfg/[0x12]src_dut_pixel_level_video.json | 223 + .../cfg/[0x13]snk_dut_dp_8b10b_ll_cts.json | 551 + .../dut_tests/cfg/[0x14]src_dut_hdr10+.json | 29 + .../[0x15]lttpr_dut_dp_128b132b_ll_cts.json | 116 + .../cfg/[0x16]src_dut_hdr10+sstm.json | 6 + .../cfg/[0x17]src_epr_power_sink.json | 18 + .../cfg/[0x18]snk_dut_dp_128b132b_ll_cts.json | 727 ++ .../cfg/[0x19]snk_epr_power_source.json | 18 + .../cfg/[0x20]src_dut_hdmi_dsc_cts.json | 21 + .../cfg/[0x21]snk_dut_hdmi_dsc_cts.json | 210 + .../cfg/[0x22]snk_dut_hdmi_continuity.json | 79 + .../cfg/[0x23]snk_dut_hdmi_gaming.json | 172 + .../cfg/[0x24]snk_dut_hdmi_cable_check.json | 96 + .../cfg/[0x25]src_dut_hdmi_hdcp_23_1a.json | 20 + .../cfg/[0x26]src_dut_hdmi_hdcp_23_1b.json | 20 + .../cfg/[0x27]snk_dut_hdmi_hdcp_23_2c.json | 42 + .../cfg/[0x28]src_dut_hdmi_hdcp_23_3a.json | 20 + .../cfg/[0x29]src_dut_hdmi_hdcp_23_3b.json | 20 + .../cfg/[0x2A]snk_dut_hdmi_hdcp_23_3c.json | 20 + .../dut_tests/cfg/hdr10/hdr10_edid.bin | Bin 0 -> 256 bytes .../modules/dut_tests/cfg/hdr10/sstm_edid.bin | Bin 0 -> 256 bytes .../dut_tests/cfg/hdr10/sstm_edid1_0.bin | Bin 0 -> 256 bytes .../dut_tests/cfg/hdr10/sstm_edid5_1.bin | Bin 0 -> 256 bytes .../dut_tests/cfg/hdr10/sstm_edid7_2.bin | Bin 0 -> 256 bytes .../dut_tests/cfg/hdr10/sstm_edid9_3.bin | Bin 0 -> 256 bytes .../dev/modules/dut_tests/cfg/hdr10/vsif1.bin | Bin 0 -> 36 bytes .../dut_tests/dut_default_params/__init__.py | 56 + .../dut_default_params/audio_test.py | 146 + .../dut_tests/dut_default_params/cec_tests.py | 43 + .../dut_default_params/crc_video_tests.py | 311 + .../dut_default_params/dp1_4_sink_tests.py | 773 ++ .../dut_default_params/dp1_4_source_tests.py | 73 + .../dut_default_params/dp2_1_sink_tests.py | 355 + .../dut_default_params/dp2_1_source_tests.py | 83 + .../dp_1_4_source_general_tab.py | 1526 +++ .../dp_2_1_common_video_modes.py | 288 + .../dp_2_1_dsc_video_modes.py | 202 + .../dp_2_1_source_general_tab.py | 758 ++ .../dut_default_params/dp_2_1_video_modes.py | 365 + .../dut_default_params/dp_electrical_tests.py | 418 + .../dp_source_adaptive_sync_tab.py | 343 + .../dut_default_params/dp_source_audio_tab.py | 305 + .../dp_source_display_id_tab.py | 1411 +++ .../dut_default_params/dp_source_dsc_tab.py | 703 ++ .../dut_default_params/hdcp_1a_tests.py | 61 + .../dut_default_params/hdcp_1b_tests.py | 27 + .../dut_default_params/hdcp_2c_tests.py | 27 + .../dut_default_params/hdcp_3a_tests.py | 27 + .../dut_default_params/hdcp_3b_tests.py | 27 + .../dut_default_params/hdcp_3c_tests.py | 43 + .../hdmi_electrical_tests.py | 293 + .../hdmi_sink_cable_check_tests.py | 217 + .../hdmi_sink_continuity_tests.py | 120 + .../dut_default_params/hdmi_sink_tests.py | 1172 ++ .../dut_default_params/hdmi_source_tests.py | 27 + .../dut_default_params/hdr10_tests.py | 27 + .../dut_default_params/link_config_test.py | 129 + .../dut_default_params/lttpr_tests.py | 125 + .../dut_default_params/pixel_video_test.py | 295 + .../usbc_electrical_tests.py | 546 + .../dut_tests/dut_default_params/vrr_tests.py | 303 + UniTAP/dev/modules/dut_tests/dut_tests.py | 572 + .../dev/modules/dut_tests/parser/__init__.py | 1 + UniTAP/dev/modules/dut_tests/parser/parser.py | 315 + UniTAP/dev/modules/dut_tests/private_info.py | 174 + .../dev/modules/dut_tests/report/__init__.py | 2 + .../modules/dut_tests/report/report_maker.py | 179 + .../dut_tests/report/report_template.py | 153 + .../modules/dut_tests/report/report_utils.py | 780 ++ .../dut_tests/test_group_params_types.py | 346 + UniTAP/dev/modules/dut_tests/test_info.py | 315 + UniTAP/dev/modules/dut_tests/test_utils.py | 190 + UniTAP/dev/modules/memory_manager/__init__.py | 1 + .../modules/memory_manager/memory_manager.py | 46 + UniTAP/dev/modules/opf/__init__.py | 2 + UniTAP/dev/modules/opf/handler.py | 66 + UniTAP/dev/modules/opf/handlers/__init__.py | 3 + UniTAP/dev/modules/opf/handlers/base.py | 51 + UniTAP/dev/modules/opf/handlers/default.py | 46 + UniTAP/dev/modules/opf/handlers/internal.py | 110 + .../dev/modules/opf/handlers/opf_functions.py | 1390 +++ UniTAP/dev/modules/opf/parsers/__init__.py | 1 + UniTAP/dev/modules/opf/parsers/main_handle.py | 44 + UniTAP/dev/modules/opf/parsers/opf_utils.py | 327 + UniTAP/dev/modules/terminal/__init__.py | 1 + UniTAP/dev/modules/terminal/dev_terminal.py | 58 + UniTAP/dev/ports/__init__.py | 7 + UniTAP/dev/ports/dprx.py | 117 + UniTAP/dev/ports/dprx4xx.py | 89 + UniTAP/dev/ports/dprx5xx.py | 18 + UniTAP/dev/ports/dptx.py | 118 + UniTAP/dev/ports/dptx4xx.py | 62 + UniTAP/dev/ports/dptx5xx.py | 15 + UniTAP/dev/ports/hdrx.py | 102 + UniTAP/dev/ports/hdrx4xx.py | 15 + UniTAP/dev/ports/hdtx.py | 103 + UniTAP/dev/ports/hdtx4xx.py | 16 + UniTAP/dev/ports/modules/__init__.py | 11 + UniTAP/dev/ports/modules/ag/__init__.py | 1 + UniTAP/dev/ports/modules/ag/ag.py | 156 + UniTAP/dev/ports/modules/ag/ag_utils.py | 109 + UniTAP/dev/ports/modules/ag/private_types.py | 19 + UniTAP/dev/ports/modules/ag/types.py | 22 + UniTAP/dev/ports/modules/capturer/__init__.py | 2 + .../ports/modules/capturer/audio/__init__.py | 0 .../modules/capturer/audio/audio_capturer.py | 117 + .../modules/capturer/audio/result_audio.py | 60 + .../ports/modules/capturer/bulk/__init__.py | 1 + .../modules/capturer/bulk/bulk_capturer.py | 132 + .../ports/modules/capturer/bulk/bulk_types.py | 979 ++ .../capturer/bulk/private_bulk_types.py | 45 + .../modules/capturer/bulk/result_bulk.py | 60 + .../ports/modules/capturer/event/__init__.py | 2 + .../modules/capturer/event/event_capturer.py | 205 + .../capturer/event/event_report_template.py | 235 + .../modules/capturer/event/event_types.py | 774 ++ .../modules/capturer/event/event_utils.py | 193 + .../capturer/event/private_event_types.py | 75 + .../modules/capturer/event/result_event.py | 108 + .../ports/modules/capturer/video/__init__.py | 1 + .../modules/capturer/video/result_video.py | 63 + .../modules/capturer/video/video_capturer.py | 224 + UniTAP/dev/ports/modules/cec/__init__.py | 2 + .../ports/modules/cec/cec_private_types.py | 41 + UniTAP/dev/ports/modules/cec/cec_rx.py | 269 + UniTAP/dev/ports/modules/cec/cec_tx.py | 269 + UniTAP/dev/ports/modules/cec/cec_types.py | 134 + UniTAP/dev/ports/modules/device_constants.py | 304 + UniTAP/dev/ports/modules/dpcd/__init__.py | 1 + UniTAP/dev/ports/modules/dpcd/dpcd.py | 187 + UniTAP/dev/ports/modules/edid/__init__.py | 2 + UniTAP/dev/ports/modules/edid/edid.py | 323 + UniTAP/dev/ports/modules/edid/edid_parser.py | 21 + .../ports/modules/edid/edid_private_types.py | 54 + UniTAP/dev/ports/modules/edid/edid_types.py | 22 + UniTAP/dev/ports/modules/edid/edid_utils.py | 44 + UniTAP/dev/ports/modules/fec/__init__.py | 3 + UniTAP/dev/ports/modules/fec/fec_rx.py | 131 + UniTAP/dev/ports/modules/fec/fec_shared.py | 38 + UniTAP/dev/ports/modules/fec/fec_tx.py | 204 + UniTAP/dev/ports/modules/hdcp/__init__.py | 4 + UniTAP/dev/ports/modules/hdcp/hdcp_rx.py | 246 + UniTAP/dev/ports/modules/hdcp/hdcp_tx.py | 288 + UniTAP/dev/ports/modules/hdcp/types.py | 221 + .../ports/modules/internal_utils/__init__.py | 6 + .../modules/internal_utils/image_formats.py | 122 + .../modules/internal_utils/image_utils.py | 27 + .../dev/ports/modules/internal_utils/math.py | 2 + UniTAP/dev/ports/modules/link/__init__.py | 2 + UniTAP/dev/ports/modules/link/dp/__init__.py | 8 + .../ports/modules/link/dp/dp_cable_info.py | 19 + UniTAP/dev/ports/modules/link/dp/dp_utils.py | 137 + UniTAP/dev/ports/modules/link/dp/link_rx.py | 121 + .../modules/link/dp/link_rx_aux_controller.py | 218 + .../dev/ports/modules/link/dp/link_rx_caps.py | 332 + .../ports/modules/link/dp/link_rx_status.py | 407 + .../ports/modules/link/dp/link_rx_types.py | 177 + .../modules/link/dp/link_status_common.py | 173 + UniTAP/dev/ports/modules/link/dp/link_tx.py | 279 + .../ports/modules/link/dp/link_tx_config.py | 360 + .../modules/link/dp/link_tx_force_config.py | 86 + .../ports/modules/link/dp/link_tx_status.py | 432 + .../ports/modules/link/dp/link_tx_types.py | 329 + .../modules/link/dp/private_link_rx_types.py | 122 + .../link/dp/private_link_status_common.py | 157 + .../modules/link/dp/private_link_tx_types.py | 107 + .../dev/ports/modules/link/hdmi/__init__.py | 1 + UniTAP/dev/ports/modules/link/hdmi/arc_rx.py | 134 + .../ports/modules/link/hdmi/capabilities.py | 45 + .../ports/modules/link/hdmi/frl_caps_rx.py | 139 + .../ports/modules/link/hdmi/frl_control_tx.py | 366 + .../dev/ports/modules/link/hdmi/hdmi_utils.py | 53 + UniTAP/dev/ports/modules/link/hdmi/link.py | 171 + .../ports/modules/link/hdmi/private_types.py | 52 + .../dev/ports/modules/link/hdmi/status_rx.py | 151 + .../dev/ports/modules/link/hdmi/status_tx.py | 140 + UniTAP/dev/ports/modules/link/hdmi/tmds_rx.py | 57 + UniTAP/dev/ports/modules/link/hdmi/tmds_tx.py | 99 + UniTAP/dev/ports/modules/link/hdmi/types.py | 372 + .../ports/modules/panel_replay/__init__.py | 4 + UniTAP/dev/ports/modules/panel_replay/pr.py | 73 + .../ports/modules/panel_replay/pr_config.py | 132 + .../modules/panel_replay/pr_private_types.py | 161 + .../dev/ports/modules/panel_replay/pr_sink.py | 59 + .../modules/panel_replay/pr_sink_caps.py | 111 + .../panel_replay/pr_sink_private_types.py | 111 + .../modules/panel_replay/pr_sink_status.py | 62 + .../modules/panel_replay/pr_sink_types.py | 844 ++ .../ports/modules/panel_replay/pr_status.py | 48 + .../ports/modules/panel_replay/pr_types.py | 399 + UniTAP/dev/ports/modules/pdc/__init__.py | 6 + .../dev/ports/modules/pdc/pdc_bus_status.py | 111 + .../dev/ports/modules/pdc/pdc_capabilities.py | 340 + .../ports/modules/pdc/pdc_contract_control.py | 253 + UniTAP/dev/ports/modules/pdc/pdc_controls.py | 268 + .../dev/ports/modules/pdc/pdc_dp_alt_mode.py | 491 + .../dev/ports/modules/pdc/pdc_dpam_types.py | 43 + UniTAP/dev/ports/modules/pdc/pdc_io.py | 115 + .../dev/ports/modules/pdc/pdc_power_sink.py | 144 + .../dev/ports/modules/pdc/pdc_power_source.py | 152 + .../modules/pdc/pdc_private_dpam_types.py | 104 + .../ports/modules/pdc/pdc_private_types.py | 227 + UniTAP/dev/ports/modules/pdc/pdc_types.py | 125 + UniTAP/dev/ports/modules/pdc/pdc_utils.py | 72 + UniTAP/dev/ports/modules/pdc/pdo.py | 99 + .../ports/modules/pdc/pdo_private_types.py | 74 + UniTAP/dev/ports/modules/pdc/pdo_types.py | 488 + UniTAP/dev/ports/modules/pdc/pdo_utils.py | 31 + UniTAP/dev/ports/modules/vtg/__init__.py | 3 + UniTAP/dev/ports/modules/vtg/pg.py | 898 ++ .../ports/modules/vtg/pg_pattern_params.py | 133 + UniTAP/dev/ports/modules/vtg/pg_utils.py | 163 + UniTAP/dev/ports/modules/vtg/private_types.py | 109 + .../dev/ports/modules/vtg/timing_manager.py | 135 + UniTAP/dev/ports/modules/vtg/types.py | 208 + UniTAP/dev/ports/pdc_port.py | 215 + UniTAP/dev/ports/port.py | 9 + UniTAP/dev/ports/rx.py | 25 + UniTAP/dev/ports/tx.py | 25 + UniTAP/libs/__init__.py | 3 + UniTAP/libs/lib_dscl/DSCL.dll | Bin 0 -> 775896 bytes UniTAP/libs/lib_dscl/Dsc.exe | Bin 0 -> 152280 bytes UniTAP/libs/lib_dscl/__init__.py | 3 + UniTAP/libs/lib_dscl/dsc_info.py | 156 + UniTAP/libs/lib_dscl/dscl.py | 194 + UniTAP/libs/lib_dscl/dscl_types.py | 206 + UniTAP/libs/lib_dscl/dscl_utils.py | 219 + UniTAP/libs/lib_helper/__init__.py | 1 + UniTAP/libs/lib_helper/lib_os_helpers.py | 131 + UniTAP/libs/lib_pdl/Default_16K.png | Bin 0 -> 621058 bytes UniTAP/libs/lib_pdl/__init__.py | 1 + UniTAP/libs/lib_pdl/pdl.py | 21 + UniTAP/libs/lib_pdl/pdl_types.py | 7 + UniTAP/libs/lib_tsi/TSI.dll | Bin 0 -> 7519448 bytes UniTAP/libs/lib_tsi/__init__.py | 3 + UniTAP/libs/lib_tsi/tsi.py | 624 ++ UniTAP/libs/lib_tsi/tsi_io.py | 253 + UniTAP/libs/lib_tsi/tsi_private_types.py | 140 + UniTAP/libs/lib_tsi/tsi_types.py | 2414 ++++ UniTAP/libs/lib_uicl/UICL.dll | Bin 0 -> 533208 bytes UniTAP/libs/lib_uicl/__init__.py | 0 UniTAP/libs/lib_uicl/uicl.py | 73 + UniTAP/libs/lib_uicl/uicl_types.py | 169 + UniTAP/libs/lib_uicl/uicl_utils.py | 331 + UniTAP/tsi_lib.py | 233 + UniTAP/utils/__init__.py | 4 + UniTAP/utils/dscl_api.py | 56 + UniTAP/utils/function_wrapper.py | 43 + UniTAP/utils/tsi_logging.py | 302 + UniTAP/utils/uicl_api.py | 85 + UniTAP/version.py | 1 + algorithm/pq_algorithm.py | 559 + app_version.py | 6 + assets/IMG_9473.PNG | Bin 0 -> 648366 bytes assets/cie.png | Bin 0 -> 42352 bytes assets/cie_uv.png | Bin 0 -> 43887 bytes assets/connect-svgrepo-com.png | Bin 0 -> 32459 bytes assets/connect-svgrepo-com.svg | 3 + assets/disconnect-svgrepo-com.png | Bin 0 -> 21616 bytes assets/disconnect-svgrepo-com.svg | 5 + assets/entry_1.png | Bin 0 -> 1003 bytes assets/entry_2.png | Bin 0 -> 1003 bytes assets/entry_3.png | Bin 0 -> 164 bytes assets/entry_4.png | Bin 0 -> 164 bytes assets/gamma.ico | Bin 0 -> 4286 bytes assets/icon_check.bat | 93 + assets/icons8_double_right_24px.png | Bin 0 -> 476 bytes assets/icons8_double_up_24px.png | Bin 0 -> 490 bytes assets/pq.ico | Bin 0 -> 107609 bytes assets/refresh-svgrepo-com.png | Bin 0 -> 32180 bytes assets/refresh-svgrepo-com.svg | 5 + assets/zx_LOGO.png | Bin 0 -> 110016 bytes installer.nsi | 131 + pqAutomationApp.py | 9751 +++++++++++++++++ pqAutomationApp.spec | 111 + settings/pq_config.json | 311 + utils/UCD323_Enum.py | 590 + utils/UCD323_Function.py | 589 + utils/baseSerail.py | 117 + utils/caSerail.py | 181 + utils/data_range_converter.py | 288 + utils/local_dimming_test.py | 585 + utils/pq/pq_config.py | 601 + utils/pq/pq_result.py | 469 + utils/tvSerail.py | 423 + views/collapsing_frame.py | 126 + views/pq_debug_panel.py | 1230 +++ views/pq_log_gui.py | 31 + 333 files changed, 70759 insertions(+) create mode 100644 .gitignore create mode 100644 RELEASE.md create mode 100644 UniTAP/.pylintrc create mode 100644 UniTAP/__init__.py create mode 100644 UniTAP/__main__.py create mode 100644 UniTAP/common/__init__.py create mode 100644 UniTAP/common/audio_mode.py create mode 100644 UniTAP/common/color_info.py create mode 100644 UniTAP/common/data_info.py create mode 100644 UniTAP/common/dsc_compression_info.py create mode 100644 UniTAP/common/dsc_video_frame.py create mode 100644 UniTAP/common/timestamp.py create mode 100644 UniTAP/common/timing.py create mode 100644 UniTAP/common/video_frame.py create mode 100644 UniTAP/common/video_mode.py create mode 100644 UniTAP/dev/__init__.py create mode 100644 UniTAP/dev/dev_3xx_roles.py create mode 100644 UniTAP/dev/dev_4xx_roles.py create mode 100644 UniTAP/dev/dev_5xx_roles.py create mode 100644 UniTAP/dev/device.py create mode 100644 UniTAP/dev/modules/__init__.py create mode 100644 UniTAP/dev/modules/capturer/__init__.py create mode 100644 UniTAP/dev/modules/capturer/capture.py create mode 100644 UniTAP/dev/modules/capturer/result_object.py create mode 100644 UniTAP/dev/modules/capturer/statuses.py create mode 100644 UniTAP/dev/modules/capturer/types.py create mode 100644 UniTAP/dev/modules/capturer/utils.py create mode 100644 UniTAP/dev/modules/dut_tests/__init__.py create mode 100644 UniTAP/dev/modules/dut_tests/cfg/[0x01]src_dut_crc.json create mode 100644 UniTAP/dev/modules/dut_tests/cfg/[0x02]src_dut_audio.json create mode 100644 UniTAP/dev/modules/dut_tests/cfg/[0x03]src_dut_dp_8b10b_ll_cts.json create mode 100644 UniTAP/dev/modules/dut_tests/cfg/[0x04]src_dut_link_config.json create mode 100644 UniTAP/dev/modules/dut_tests/cfg/[0x05]src_dut_dp_electrical.json create mode 100644 UniTAP/dev/modules/dut_tests/cfg/[0x06]src_dut_hdmi_electrical.json create mode 100644 UniTAP/dev/modules/dut_tests/cfg/[0x07]any_dut_usbc_electrical.json create mode 100644 UniTAP/dev/modules/dut_tests/cfg/[0x08]src_dut_dp_128b132b_ll_cts.json create mode 100644 UniTAP/dev/modules/dut_tests/cfg/[0x09]src_dut_hdcp_23_1a.json create mode 100644 UniTAP/dev/modules/dut_tests/cfg/[0x0A]src_dut_hdcp_23_1b.json create mode 100644 UniTAP/dev/modules/dut_tests/cfg/[0x0B]src_dut_hdcp_23_3a.json create mode 100644 UniTAP/dev/modules/dut_tests/cfg/[0x0C]src_dut_hdcp_23_3b.json create mode 100644 UniTAP/dev/modules/dut_tests/cfg/[0x0D]snk_dut_hdcp_23_2c.json create mode 100644 UniTAP/dev/modules/dut_tests/cfg/[0x0E]snk_dut_hdcp_23_3c.json create mode 100644 UniTAP/dev/modules/dut_tests/cfg/[0x0F]snk_dut_vrr.json create mode 100644 UniTAP/dev/modules/dut_tests/cfg/[0x10]src_dut_vrr.json create mode 100644 UniTAP/dev/modules/dut_tests/cfg/[0x11]src_dut_cec.json create mode 100644 UniTAP/dev/modules/dut_tests/cfg/[0x12]src_dut_pixel_level_video.json create mode 100644 UniTAP/dev/modules/dut_tests/cfg/[0x13]snk_dut_dp_8b10b_ll_cts.json create mode 100644 UniTAP/dev/modules/dut_tests/cfg/[0x14]src_dut_hdr10+.json create mode 100644 UniTAP/dev/modules/dut_tests/cfg/[0x15]lttpr_dut_dp_128b132b_ll_cts.json create mode 100644 UniTAP/dev/modules/dut_tests/cfg/[0x16]src_dut_hdr10+sstm.json create mode 100644 UniTAP/dev/modules/dut_tests/cfg/[0x17]src_epr_power_sink.json create mode 100644 UniTAP/dev/modules/dut_tests/cfg/[0x18]snk_dut_dp_128b132b_ll_cts.json create mode 100644 UniTAP/dev/modules/dut_tests/cfg/[0x19]snk_epr_power_source.json create mode 100644 UniTAP/dev/modules/dut_tests/cfg/[0x20]src_dut_hdmi_dsc_cts.json create mode 100644 UniTAP/dev/modules/dut_tests/cfg/[0x21]snk_dut_hdmi_dsc_cts.json create mode 100644 UniTAP/dev/modules/dut_tests/cfg/[0x22]snk_dut_hdmi_continuity.json create mode 100644 UniTAP/dev/modules/dut_tests/cfg/[0x23]snk_dut_hdmi_gaming.json create mode 100644 UniTAP/dev/modules/dut_tests/cfg/[0x24]snk_dut_hdmi_cable_check.json create mode 100644 UniTAP/dev/modules/dut_tests/cfg/[0x25]src_dut_hdmi_hdcp_23_1a.json create mode 100644 UniTAP/dev/modules/dut_tests/cfg/[0x26]src_dut_hdmi_hdcp_23_1b.json create mode 100644 UniTAP/dev/modules/dut_tests/cfg/[0x27]snk_dut_hdmi_hdcp_23_2c.json create mode 100644 UniTAP/dev/modules/dut_tests/cfg/[0x28]src_dut_hdmi_hdcp_23_3a.json create mode 100644 UniTAP/dev/modules/dut_tests/cfg/[0x29]src_dut_hdmi_hdcp_23_3b.json create mode 100644 UniTAP/dev/modules/dut_tests/cfg/[0x2A]snk_dut_hdmi_hdcp_23_3c.json create mode 100644 UniTAP/dev/modules/dut_tests/cfg/hdr10/hdr10_edid.bin create mode 100644 UniTAP/dev/modules/dut_tests/cfg/hdr10/sstm_edid.bin create mode 100644 UniTAP/dev/modules/dut_tests/cfg/hdr10/sstm_edid1_0.bin create mode 100644 UniTAP/dev/modules/dut_tests/cfg/hdr10/sstm_edid5_1.bin create mode 100644 UniTAP/dev/modules/dut_tests/cfg/hdr10/sstm_edid7_2.bin create mode 100644 UniTAP/dev/modules/dut_tests/cfg/hdr10/sstm_edid9_3.bin create mode 100644 UniTAP/dev/modules/dut_tests/cfg/hdr10/vsif1.bin create mode 100644 UniTAP/dev/modules/dut_tests/dut_default_params/__init__.py create mode 100644 UniTAP/dev/modules/dut_tests/dut_default_params/audio_test.py create mode 100644 UniTAP/dev/modules/dut_tests/dut_default_params/cec_tests.py create mode 100644 UniTAP/dev/modules/dut_tests/dut_default_params/crc_video_tests.py create mode 100644 UniTAP/dev/modules/dut_tests/dut_default_params/dp1_4_sink_tests.py create mode 100644 UniTAP/dev/modules/dut_tests/dut_default_params/dp1_4_source_tests.py create mode 100644 UniTAP/dev/modules/dut_tests/dut_default_params/dp2_1_sink_tests.py create mode 100644 UniTAP/dev/modules/dut_tests/dut_default_params/dp2_1_source_tests.py create mode 100644 UniTAP/dev/modules/dut_tests/dut_default_params/dp_1_4_source_general_tab.py create mode 100644 UniTAP/dev/modules/dut_tests/dut_default_params/dp_2_1_common_video_modes.py create mode 100644 UniTAP/dev/modules/dut_tests/dut_default_params/dp_2_1_dsc_video_modes.py create mode 100644 UniTAP/dev/modules/dut_tests/dut_default_params/dp_2_1_source_general_tab.py create mode 100644 UniTAP/dev/modules/dut_tests/dut_default_params/dp_2_1_video_modes.py create mode 100644 UniTAP/dev/modules/dut_tests/dut_default_params/dp_electrical_tests.py create mode 100644 UniTAP/dev/modules/dut_tests/dut_default_params/dp_source_adaptive_sync_tab.py create mode 100644 UniTAP/dev/modules/dut_tests/dut_default_params/dp_source_audio_tab.py create mode 100644 UniTAP/dev/modules/dut_tests/dut_default_params/dp_source_display_id_tab.py create mode 100644 UniTAP/dev/modules/dut_tests/dut_default_params/dp_source_dsc_tab.py create mode 100644 UniTAP/dev/modules/dut_tests/dut_default_params/hdcp_1a_tests.py create mode 100644 UniTAP/dev/modules/dut_tests/dut_default_params/hdcp_1b_tests.py create mode 100644 UniTAP/dev/modules/dut_tests/dut_default_params/hdcp_2c_tests.py create mode 100644 UniTAP/dev/modules/dut_tests/dut_default_params/hdcp_3a_tests.py create mode 100644 UniTAP/dev/modules/dut_tests/dut_default_params/hdcp_3b_tests.py create mode 100644 UniTAP/dev/modules/dut_tests/dut_default_params/hdcp_3c_tests.py create mode 100644 UniTAP/dev/modules/dut_tests/dut_default_params/hdmi_electrical_tests.py create mode 100644 UniTAP/dev/modules/dut_tests/dut_default_params/hdmi_sink_cable_check_tests.py create mode 100644 UniTAP/dev/modules/dut_tests/dut_default_params/hdmi_sink_continuity_tests.py create mode 100644 UniTAP/dev/modules/dut_tests/dut_default_params/hdmi_sink_tests.py create mode 100644 UniTAP/dev/modules/dut_tests/dut_default_params/hdmi_source_tests.py create mode 100644 UniTAP/dev/modules/dut_tests/dut_default_params/hdr10_tests.py create mode 100644 UniTAP/dev/modules/dut_tests/dut_default_params/link_config_test.py create mode 100644 UniTAP/dev/modules/dut_tests/dut_default_params/lttpr_tests.py create mode 100644 UniTAP/dev/modules/dut_tests/dut_default_params/pixel_video_test.py create mode 100644 UniTAP/dev/modules/dut_tests/dut_default_params/usbc_electrical_tests.py create mode 100644 UniTAP/dev/modules/dut_tests/dut_default_params/vrr_tests.py create mode 100644 UniTAP/dev/modules/dut_tests/dut_tests.py create mode 100644 UniTAP/dev/modules/dut_tests/parser/__init__.py create mode 100644 UniTAP/dev/modules/dut_tests/parser/parser.py create mode 100644 UniTAP/dev/modules/dut_tests/private_info.py create mode 100644 UniTAP/dev/modules/dut_tests/report/__init__.py create mode 100644 UniTAP/dev/modules/dut_tests/report/report_maker.py create mode 100644 UniTAP/dev/modules/dut_tests/report/report_template.py create mode 100644 UniTAP/dev/modules/dut_tests/report/report_utils.py create mode 100644 UniTAP/dev/modules/dut_tests/test_group_params_types.py create mode 100644 UniTAP/dev/modules/dut_tests/test_info.py create mode 100644 UniTAP/dev/modules/dut_tests/test_utils.py create mode 100644 UniTAP/dev/modules/memory_manager/__init__.py create mode 100644 UniTAP/dev/modules/memory_manager/memory_manager.py create mode 100644 UniTAP/dev/modules/opf/__init__.py create mode 100644 UniTAP/dev/modules/opf/handler.py create mode 100644 UniTAP/dev/modules/opf/handlers/__init__.py create mode 100644 UniTAP/dev/modules/opf/handlers/base.py create mode 100644 UniTAP/dev/modules/opf/handlers/default.py create mode 100644 UniTAP/dev/modules/opf/handlers/internal.py create mode 100644 UniTAP/dev/modules/opf/handlers/opf_functions.py create mode 100644 UniTAP/dev/modules/opf/parsers/__init__.py create mode 100644 UniTAP/dev/modules/opf/parsers/main_handle.py create mode 100644 UniTAP/dev/modules/opf/parsers/opf_utils.py create mode 100644 UniTAP/dev/modules/terminal/__init__.py create mode 100644 UniTAP/dev/modules/terminal/dev_terminal.py create mode 100644 UniTAP/dev/ports/__init__.py create mode 100644 UniTAP/dev/ports/dprx.py create mode 100644 UniTAP/dev/ports/dprx4xx.py create mode 100644 UniTAP/dev/ports/dprx5xx.py create mode 100644 UniTAP/dev/ports/dptx.py create mode 100644 UniTAP/dev/ports/dptx4xx.py create mode 100644 UniTAP/dev/ports/dptx5xx.py create mode 100644 UniTAP/dev/ports/hdrx.py create mode 100644 UniTAP/dev/ports/hdrx4xx.py create mode 100644 UniTAP/dev/ports/hdtx.py create mode 100644 UniTAP/dev/ports/hdtx4xx.py create mode 100644 UniTAP/dev/ports/modules/__init__.py create mode 100644 UniTAP/dev/ports/modules/ag/__init__.py create mode 100644 UniTAP/dev/ports/modules/ag/ag.py create mode 100644 UniTAP/dev/ports/modules/ag/ag_utils.py create mode 100644 UniTAP/dev/ports/modules/ag/private_types.py create mode 100644 UniTAP/dev/ports/modules/ag/types.py create mode 100644 UniTAP/dev/ports/modules/capturer/__init__.py create mode 100644 UniTAP/dev/ports/modules/capturer/audio/__init__.py create mode 100644 UniTAP/dev/ports/modules/capturer/audio/audio_capturer.py create mode 100644 UniTAP/dev/ports/modules/capturer/audio/result_audio.py create mode 100644 UniTAP/dev/ports/modules/capturer/bulk/__init__.py create mode 100644 UniTAP/dev/ports/modules/capturer/bulk/bulk_capturer.py create mode 100644 UniTAP/dev/ports/modules/capturer/bulk/bulk_types.py create mode 100644 UniTAP/dev/ports/modules/capturer/bulk/private_bulk_types.py create mode 100644 UniTAP/dev/ports/modules/capturer/bulk/result_bulk.py create mode 100644 UniTAP/dev/ports/modules/capturer/event/__init__.py create mode 100644 UniTAP/dev/ports/modules/capturer/event/event_capturer.py create mode 100644 UniTAP/dev/ports/modules/capturer/event/event_report_template.py create mode 100644 UniTAP/dev/ports/modules/capturer/event/event_types.py create mode 100644 UniTAP/dev/ports/modules/capturer/event/event_utils.py create mode 100644 UniTAP/dev/ports/modules/capturer/event/private_event_types.py create mode 100644 UniTAP/dev/ports/modules/capturer/event/result_event.py create mode 100644 UniTAP/dev/ports/modules/capturer/video/__init__.py create mode 100644 UniTAP/dev/ports/modules/capturer/video/result_video.py create mode 100644 UniTAP/dev/ports/modules/capturer/video/video_capturer.py create mode 100644 UniTAP/dev/ports/modules/cec/__init__.py create mode 100644 UniTAP/dev/ports/modules/cec/cec_private_types.py create mode 100644 UniTAP/dev/ports/modules/cec/cec_rx.py create mode 100644 UniTAP/dev/ports/modules/cec/cec_tx.py create mode 100644 UniTAP/dev/ports/modules/cec/cec_types.py create mode 100644 UniTAP/dev/ports/modules/device_constants.py create mode 100644 UniTAP/dev/ports/modules/dpcd/__init__.py create mode 100644 UniTAP/dev/ports/modules/dpcd/dpcd.py create mode 100644 UniTAP/dev/ports/modules/edid/__init__.py create mode 100644 UniTAP/dev/ports/modules/edid/edid.py create mode 100644 UniTAP/dev/ports/modules/edid/edid_parser.py create mode 100644 UniTAP/dev/ports/modules/edid/edid_private_types.py create mode 100644 UniTAP/dev/ports/modules/edid/edid_types.py create mode 100644 UniTAP/dev/ports/modules/edid/edid_utils.py create mode 100644 UniTAP/dev/ports/modules/fec/__init__.py create mode 100644 UniTAP/dev/ports/modules/fec/fec_rx.py create mode 100644 UniTAP/dev/ports/modules/fec/fec_shared.py create mode 100644 UniTAP/dev/ports/modules/fec/fec_tx.py create mode 100644 UniTAP/dev/ports/modules/hdcp/__init__.py create mode 100644 UniTAP/dev/ports/modules/hdcp/hdcp_rx.py create mode 100644 UniTAP/dev/ports/modules/hdcp/hdcp_tx.py create mode 100644 UniTAP/dev/ports/modules/hdcp/types.py create mode 100644 UniTAP/dev/ports/modules/internal_utils/__init__.py create mode 100644 UniTAP/dev/ports/modules/internal_utils/image_formats.py create mode 100644 UniTAP/dev/ports/modules/internal_utils/image_utils.py create mode 100644 UniTAP/dev/ports/modules/internal_utils/math.py create mode 100644 UniTAP/dev/ports/modules/link/__init__.py create mode 100644 UniTAP/dev/ports/modules/link/dp/__init__.py create mode 100644 UniTAP/dev/ports/modules/link/dp/dp_cable_info.py create mode 100644 UniTAP/dev/ports/modules/link/dp/dp_utils.py create mode 100644 UniTAP/dev/ports/modules/link/dp/link_rx.py create mode 100644 UniTAP/dev/ports/modules/link/dp/link_rx_aux_controller.py create mode 100644 UniTAP/dev/ports/modules/link/dp/link_rx_caps.py create mode 100644 UniTAP/dev/ports/modules/link/dp/link_rx_status.py create mode 100644 UniTAP/dev/ports/modules/link/dp/link_rx_types.py create mode 100644 UniTAP/dev/ports/modules/link/dp/link_status_common.py create mode 100644 UniTAP/dev/ports/modules/link/dp/link_tx.py create mode 100644 UniTAP/dev/ports/modules/link/dp/link_tx_config.py create mode 100644 UniTAP/dev/ports/modules/link/dp/link_tx_force_config.py create mode 100644 UniTAP/dev/ports/modules/link/dp/link_tx_status.py create mode 100644 UniTAP/dev/ports/modules/link/dp/link_tx_types.py create mode 100644 UniTAP/dev/ports/modules/link/dp/private_link_rx_types.py create mode 100644 UniTAP/dev/ports/modules/link/dp/private_link_status_common.py create mode 100644 UniTAP/dev/ports/modules/link/dp/private_link_tx_types.py create mode 100644 UniTAP/dev/ports/modules/link/hdmi/__init__.py create mode 100644 UniTAP/dev/ports/modules/link/hdmi/arc_rx.py create mode 100644 UniTAP/dev/ports/modules/link/hdmi/capabilities.py create mode 100644 UniTAP/dev/ports/modules/link/hdmi/frl_caps_rx.py create mode 100644 UniTAP/dev/ports/modules/link/hdmi/frl_control_tx.py create mode 100644 UniTAP/dev/ports/modules/link/hdmi/hdmi_utils.py create mode 100644 UniTAP/dev/ports/modules/link/hdmi/link.py create mode 100644 UniTAP/dev/ports/modules/link/hdmi/private_types.py create mode 100644 UniTAP/dev/ports/modules/link/hdmi/status_rx.py create mode 100644 UniTAP/dev/ports/modules/link/hdmi/status_tx.py create mode 100644 UniTAP/dev/ports/modules/link/hdmi/tmds_rx.py create mode 100644 UniTAP/dev/ports/modules/link/hdmi/tmds_tx.py create mode 100644 UniTAP/dev/ports/modules/link/hdmi/types.py create mode 100644 UniTAP/dev/ports/modules/panel_replay/__init__.py create mode 100644 UniTAP/dev/ports/modules/panel_replay/pr.py create mode 100644 UniTAP/dev/ports/modules/panel_replay/pr_config.py create mode 100644 UniTAP/dev/ports/modules/panel_replay/pr_private_types.py create mode 100644 UniTAP/dev/ports/modules/panel_replay/pr_sink.py create mode 100644 UniTAP/dev/ports/modules/panel_replay/pr_sink_caps.py create mode 100644 UniTAP/dev/ports/modules/panel_replay/pr_sink_private_types.py create mode 100644 UniTAP/dev/ports/modules/panel_replay/pr_sink_status.py create mode 100644 UniTAP/dev/ports/modules/panel_replay/pr_sink_types.py create mode 100644 UniTAP/dev/ports/modules/panel_replay/pr_status.py create mode 100644 UniTAP/dev/ports/modules/panel_replay/pr_types.py create mode 100644 UniTAP/dev/ports/modules/pdc/__init__.py create mode 100644 UniTAP/dev/ports/modules/pdc/pdc_bus_status.py create mode 100644 UniTAP/dev/ports/modules/pdc/pdc_capabilities.py create mode 100644 UniTAP/dev/ports/modules/pdc/pdc_contract_control.py create mode 100644 UniTAP/dev/ports/modules/pdc/pdc_controls.py create mode 100644 UniTAP/dev/ports/modules/pdc/pdc_dp_alt_mode.py create mode 100644 UniTAP/dev/ports/modules/pdc/pdc_dpam_types.py create mode 100644 UniTAP/dev/ports/modules/pdc/pdc_io.py create mode 100644 UniTAP/dev/ports/modules/pdc/pdc_power_sink.py create mode 100644 UniTAP/dev/ports/modules/pdc/pdc_power_source.py create mode 100644 UniTAP/dev/ports/modules/pdc/pdc_private_dpam_types.py create mode 100644 UniTAP/dev/ports/modules/pdc/pdc_private_types.py create mode 100644 UniTAP/dev/ports/modules/pdc/pdc_types.py create mode 100644 UniTAP/dev/ports/modules/pdc/pdc_utils.py create mode 100644 UniTAP/dev/ports/modules/pdc/pdo.py create mode 100644 UniTAP/dev/ports/modules/pdc/pdo_private_types.py create mode 100644 UniTAP/dev/ports/modules/pdc/pdo_types.py create mode 100644 UniTAP/dev/ports/modules/pdc/pdo_utils.py create mode 100644 UniTAP/dev/ports/modules/vtg/__init__.py create mode 100644 UniTAP/dev/ports/modules/vtg/pg.py create mode 100644 UniTAP/dev/ports/modules/vtg/pg_pattern_params.py create mode 100644 UniTAP/dev/ports/modules/vtg/pg_utils.py create mode 100644 UniTAP/dev/ports/modules/vtg/private_types.py create mode 100644 UniTAP/dev/ports/modules/vtg/timing_manager.py create mode 100644 UniTAP/dev/ports/modules/vtg/types.py create mode 100644 UniTAP/dev/ports/pdc_port.py create mode 100644 UniTAP/dev/ports/port.py create mode 100644 UniTAP/dev/ports/rx.py create mode 100644 UniTAP/dev/ports/tx.py create mode 100644 UniTAP/libs/__init__.py create mode 100644 UniTAP/libs/lib_dscl/DSCL.dll create mode 100644 UniTAP/libs/lib_dscl/Dsc.exe create mode 100644 UniTAP/libs/lib_dscl/__init__.py create mode 100644 UniTAP/libs/lib_dscl/dsc_info.py create mode 100644 UniTAP/libs/lib_dscl/dscl.py create mode 100644 UniTAP/libs/lib_dscl/dscl_types.py create mode 100644 UniTAP/libs/lib_dscl/dscl_utils.py create mode 100644 UniTAP/libs/lib_helper/__init__.py create mode 100644 UniTAP/libs/lib_helper/lib_os_helpers.py create mode 100644 UniTAP/libs/lib_pdl/Default_16K.png create mode 100644 UniTAP/libs/lib_pdl/__init__.py create mode 100644 UniTAP/libs/lib_pdl/pdl.py create mode 100644 UniTAP/libs/lib_pdl/pdl_types.py create mode 100644 UniTAP/libs/lib_tsi/TSI.dll create mode 100644 UniTAP/libs/lib_tsi/__init__.py create mode 100644 UniTAP/libs/lib_tsi/tsi.py create mode 100644 UniTAP/libs/lib_tsi/tsi_io.py create mode 100644 UniTAP/libs/lib_tsi/tsi_private_types.py create mode 100644 UniTAP/libs/lib_tsi/tsi_types.py create mode 100644 UniTAP/libs/lib_uicl/UICL.dll create mode 100644 UniTAP/libs/lib_uicl/__init__.py create mode 100644 UniTAP/libs/lib_uicl/uicl.py create mode 100644 UniTAP/libs/lib_uicl/uicl_types.py create mode 100644 UniTAP/libs/lib_uicl/uicl_utils.py create mode 100644 UniTAP/tsi_lib.py create mode 100644 UniTAP/utils/__init__.py create mode 100644 UniTAP/utils/dscl_api.py create mode 100644 UniTAP/utils/function_wrapper.py create mode 100644 UniTAP/utils/tsi_logging.py create mode 100644 UniTAP/utils/uicl_api.py create mode 100644 UniTAP/version.py create mode 100644 algorithm/pq_algorithm.py create mode 100644 app_version.py create mode 100644 assets/IMG_9473.PNG create mode 100644 assets/cie.png create mode 100644 assets/cie_uv.png create mode 100644 assets/connect-svgrepo-com.png create mode 100644 assets/connect-svgrepo-com.svg create mode 100644 assets/disconnect-svgrepo-com.png create mode 100644 assets/disconnect-svgrepo-com.svg create mode 100644 assets/entry_1.png create mode 100644 assets/entry_2.png create mode 100644 assets/entry_3.png create mode 100644 assets/entry_4.png create mode 100644 assets/gamma.ico create mode 100644 assets/icon_check.bat create mode 100644 assets/icons8_double_right_24px.png create mode 100644 assets/icons8_double_up_24px.png create mode 100644 assets/pq.ico create mode 100644 assets/refresh-svgrepo-com.png create mode 100644 assets/refresh-svgrepo-com.svg create mode 100644 assets/zx_LOGO.png create mode 100644 installer.nsi create mode 100644 pqAutomationApp.py create mode 100644 pqAutomationApp.spec create mode 100644 settings/pq_config.json create mode 100644 utils/UCD323_Enum.py create mode 100644 utils/UCD323_Function.py create mode 100644 utils/baseSerail.py create mode 100644 utils/caSerail.py create mode 100644 utils/data_range_converter.py create mode 100644 utils/local_dimming_test.py create mode 100644 utils/pq/pq_config.py create mode 100644 utils/pq/pq_result.py create mode 100644 utils/tvSerail.py create mode 100644 views/collapsing_frame.py create mode 100644 views/pq_debug_panel.py create mode 100644 views/pq_log_gui.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..406b4fc --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +# Python cache +__pycache__/ +*.pyc +*.pyo +*.pyd + +# Virtual environments +.venv/ +venv/ +env/ + +# Build outputs +build/ +dist/ +*.spec.bak + +# Test and runtime outputs +*.log +*.tmp +*.bak +*.csv +*.xlsx + +# IDE files +.vscode/ +.idea/ + +# OS files +Thumbs.db +Desktop.ini + +# Local configuration overrides +settings/*.local.json \ No newline at end of file diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 0000000..ea6231c --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,94 @@ +# 发布说明 + +## 版本入口 + +软件版本统一维护在 `app_version.py`: + +- `APP_NAME`:软件名称 +- `APP_VERSION`:软件发布版本 + +发布新版本时,优先修改这里,不要直接改界面标题字符串。 + +## 版本规则 + +建议使用语义化版本:`主版本.次版本.修订号` + +- `1.0.0`:首个正式版 +- `1.1.0`:兼容性新功能 +- `1.1.1`:问题修复 +- `2.0.0`:不兼容变更 + +## Git 管理建议 + +- `main`:始终保持可发布状态 +- 功能开发使用独立分支 +- 每次发布打 Git Tag,例如 `v1.1.0` + +建议命令: + +```powershell +git checkout main +git pull +git checkout -b feature/xxx +``` + +完成功能后: + +```powershell +git add . +git commit -m "feat: xxx" +git checkout main +git merge --no-ff feature/xxx +``` + +发布时: + +```powershell +git add app_version.py RELEASE.md +git commit -m "release: v1.1.0" +git tag v1.1.0 +git push +git push origin v1.1.0 +``` + +## 打包流程 + +当前打包入口:`pqAutomationApp.spec` + +推荐发布步骤: + +1. 修改 `app_version.py` 中的 `APP_VERSION` +2. 更新本文件中的变更说明 +3. 提交代码并打 Tag +4. 执行打包命令 +5. 将 `dist/pqAutomationApp` 复制为带版本号的发布目录 + +打包命令: + +```powershell +pyinstaller pqAutomationApp.spec +``` + +建议发布目录命名: + +- `PQAutomationApp_v1.1.0` +- `PQAutomationApp_v1.1.0_20260416` + +## 每次发布前检查 + +1. 版本号是否已更新 +2. 关键功能是否回归测试 +3. `dist/` 和 `build/` 是否未提交到 Git +4. 发布说明是否已更新 +5. Tag 是否与版本号一致 + +## 变更记录模板 + +每次发布可按下面格式补充: + +### v0.1.0 + +- 首次建立版本发布机制 +- 软件标题显示版本号 +- 增加 `.gitignore` +- 增加发布流程文档 \ No newline at end of file diff --git a/UniTAP/.pylintrc b/UniTAP/.pylintrc new file mode 100644 index 0000000..a11f546 --- /dev/null +++ b/UniTAP/.pylintrc @@ -0,0 +1,644 @@ +[MAIN] + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Clear in-memory caches upon conclusion of linting. Useful if running pylint +# in a server-like mode. +clear-cache-post-run=no + +# Load and enable all available extensions. Use --list-extensions to see a list +# all available extensions. +#enable-all-extensions= + +# In error mode, messages with a category besides ERROR or FATAL are +# suppressed, and no reports are done by default. Error mode is compatible with +# disabling specific errors. +#errors-only= + +# Always return a 0 (non-error) status code, even if lint errors are found. +# This is primarily useful in continuous integration scripts. +#exit-zero= + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. +extension-pkg-allow-list= + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. (This is an alternative name to extension-pkg-allow-list +# for backward compatibility.) +extension-pkg-whitelist= + +# Return non-zero exit code if any of these messages/categories are detected, +# even if score is above --fail-under value. Syntax same as enable. Messages +# specified are enabled, while categories only check already-enabled messages. +fail-on= + +# Specify a score threshold under which the program will exit with error. +fail-under=10 + +# Interpret the stdin as a python script, whose filename needs to be passed as +# the module_or_package argument. +#from-stdin= + +# Files or directories to be skipped. They should be base names, not paths. +ignore=CVS + +# Add files or directories matching the regular expressions patterns to the +# ignore-list. The regex matches against paths and can be in Posix or Windows +# format. Because '\\' represents the directory delimiter on Windows systems, +# it can't be used as an escape character. +ignore-paths= + +# Files or directories matching the regular expression patterns are skipped. +# The regex matches against base names, not paths. The default value ignores +# Emacs file locks +ignore-patterns=^\.# + +# List of module names for which member attributes should not be checked and +# will not be imported (useful for modules/projects where namespaces are +# manipulated during runtime and thus existing member attributes cannot be +# deduced by static analysis). It supports qualified module names, as well as +# Unix pattern matching. +ignored-modules= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the +# number of processors available to use, and will cap the count on Windows to +# avoid hangs. +jobs=1 + +# Control the amount of potential inferred values when inferring a single +# object. This can help the performance when dealing with large functions or +# complex, nested conditions. +limit-inference-results=100 + +# List of plugins (as comma separated values of python module names) to load, +# usually to register additional checkers. +load-plugins= + +# Pickle collected data for later comparisons. +persistent=yes + +# Resolve imports to .pyi stubs if available. May reduce no-member messages and +# increase not-an-iterable messages. +prefer-stubs=no + +# Minimum Python version to use for version dependent checks. Will default to +# the version used to run pylint. +py-version=3.10 + +# Discover python modules and packages in the file system subtree. +recursive=no + +# Add paths to the list of the source roots. Supports globbing patterns. The +# source root is an absolute path or a path relative to the current working +# directory used to determine a package namespace for modules located under the +# source root. +source-roots= + +# When enabled, pylint would attempt to guess common misconfiguration and emit +# user-friendly hints instead of false-positive error messages. +suggestion-mode=yes + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + +# In verbose mode, extra non-checker-related info will be displayed. +#verbose= + + +[BASIC] + +# Naming style matching correct argument names. +argument-naming-style=snake_case + +# Regular expression matching correct argument names. Overrides argument- +# naming-style. If left empty, argument names will be checked with the set +# naming style. +#argument-rgx= + +# Naming style matching correct attribute names. +attr-naming-style=snake_case + +# Regular expression matching correct attribute names. Overrides attr-naming- +# style. If left empty, attribute names will be checked with the set naming +# style. +#attr-rgx= + +# Bad variable names which should always be refused, separated by a comma. +bad-names=foo, + bar, + baz, + toto, + tutu, + tata + +# Bad variable names regexes, separated by a comma. If names match any regex, +# they will always be refused +bad-names-rgxs= + +# Naming style matching correct class attribute names. +class-attribute-naming-style=any + +# Regular expression matching correct class attribute names. Overrides class- +# attribute-naming-style. If left empty, class attribute names will be checked +# with the set naming style. +#class-attribute-rgx= + +# Naming style matching correct class constant names. +class-const-naming-style=UPPER_CASE + +# Regular expression matching correct class constant names. Overrides class- +# const-naming-style. If left empty, class constant names will be checked with +# the set naming style. +#class-const-rgx= + +# Naming style matching correct class names. +class-naming-style=PascalCase + +# Regular expression matching correct class names. Overrides class-naming- +# style. If left empty, class names will be checked with the set naming style. +#class-rgx= + +# Naming style matching correct constant names. +const-naming-style=UPPER_CASE + +# Regular expression matching correct constant names. Overrides const-naming- +# style. If left empty, constant names will be checked with the set naming +# style. +#const-rgx= + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + +# Naming style matching correct function names. +function-naming-style=snake_case + +# Regular expression matching correct function names. Overrides function- +# naming-style. If left empty, function names will be checked with the set +# naming style. +#function-rgx= + +# Good variable names which should always be accepted, separated by a comma. +good-names=i, + j, + k, + ex, + Run, + _ + +# Good variable names regexes, separated by a comma. If names match any regex, +# they will always be accepted +good-names-rgxs= + +# Include a hint for the correct naming format with invalid-name. +include-naming-hint=no + +# Naming style matching correct inline iteration names. +inlinevar-naming-style=any + +# Regular expression matching correct inline iteration names. Overrides +# inlinevar-naming-style. If left empty, inline iteration names will be checked +# with the set naming style. +#inlinevar-rgx= + +# Naming style matching correct method names. +method-naming-style=snake_case + +# Regular expression matching correct method names. Overrides method-naming- +# style. If left empty, method names will be checked with the set naming style. +#method-rgx= + +# Naming style matching correct module names. +module-naming-style=snake_case + +# Regular expression matching correct module names. Overrides module-naming- +# style. If left empty, module names will be checked with the set naming style. +#module-rgx= + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +# These decorators are taken in consideration only for invalid-name. +property-classes=abc.abstractproperty + +# Regular expression matching correct type alias names. If left empty, type +# alias names will be checked with the set naming style. +#typealias-rgx= + +# Regular expression matching correct type variable names. If left empty, type +# variable names will be checked with the set naming style. +#typevar-rgx= + +# Naming style matching correct variable names. +variable-naming-style=snake_case + +# Regular expression matching correct variable names. Overrides variable- +# naming-style. If left empty, variable names will be checked with the set +# naming style. +#variable-rgx= + + +[CLASSES] + +# Warn about protected attribute access inside special methods +check-protected-access-in-special-methods=no + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__, + __new__, + setUp, + asyncSetUp, + __post_init__ + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict,_fields,_replace,_source,_make,os._exit + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=mcs + + +[DESIGN] + +# List of regular expressions of class ancestor names to ignore when counting +# public methods (see R0903) +exclude-too-few-public-methods= + +# List of qualified class names to ignore when counting class parents (see +# R0901) +ignored-parents= + +# Maximum number of arguments for function / method. +max-args=5 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Maximum number of boolean expressions in an if statement (see R0916). +max-bool-expr=5 + +# Maximum number of branch for function / method body. +max-branches=12 + +# Maximum number of locals for function / method body. +max-locals=15 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of positional arguments for function / method. +max-positional-arguments=5 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of return / yield for function / method body. +max-returns=6 + +# Maximum number of statements in function / method body. +max-statements=50 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when caught. +overgeneral-exceptions=builtins.BaseException,builtins.Exception + + +[FORMAT] + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=100 + +# Maximum number of lines in a module. +max-module-lines=1000 + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + + +[IMPORTS] + +# List of modules that can be imported at any level, not just the top level +# one. +allow-any-import-level= + +# Allow explicit reexports by alias from a package __init__. +allow-reexport-from-package=no + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Deprecated modules which should not be used, separated by a comma. +deprecated-modules= + +# Output a graph (.gv or any supported image format) of external dependencies +# to the given file (report RP0402 must not be disabled). +ext-import-graph= + +# Output a graph (.gv or any supported image format) of all (i.e. internal and +# external) dependencies to the given file (report RP0402 must not be +# disabled). +import-graph= + +# Output a graph (.gv or any supported image format) of internal dependencies +# to the given file (report RP0402 must not be disabled). +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + +# Couples of modules and preferred modules, separated by a comma. +preferred-modules= + + +[LOGGING] + +# The type of string formatting that logging methods do. `old` means using % +# formatting, `new` is for `{}` formatting. +logging-format-style=old + +# Logging modules to check that the string format arguments are in logging +# function parameter format. +logging-modules=logging + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE, +# UNDEFINED. +confidence=HIGH, + CONTROL_FLOW, + INFERENCE, + INFERENCE_FAILURE, + UNDEFINED + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once). You can also use "--disable=all" to +# disable everything first and then re-enable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use "--disable=all --enable=classes +# --disable=W". +disable=all + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +enable=syntax-error, + no-member, + method-hidden, + unreachable, + attribute-defined-outside-init, + no-name-in-module, + undefined-variable + + +[METHOD_ARGS] + +# List of qualified names (i.e., library.method) which require a timeout +# parameter e.g. 'requests.api.get,requests.api.post' +timeout-methods=requests.api.delete,requests.api.get,requests.api.head,requests.api.options,requests.api.patch,requests.api.post,requests.api.put,requests.api.request + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME, + XXX, + TODO + +# Regular expression of note tags to take in consideration. +notes-rgx= + + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + +# Complete name of functions that never returns. When checking for +# inconsistent-return-statements if a never returning function is called then +# it will be considered as an explicit return statement and no message will be +# printed. +never-returning-functions=sys.exit,argparse.parse_error + +# Let 'consider-using-join' be raised when the separator to join on would be +# non-empty (resulting in expected fixes of the type: ``"- " + " - +# ".join(items)``) +suggest-join-with-non-empty-separator=yes + + +[REPORTS] + +# Python expression which should return a score less than or equal to 10. You +# have access to the variables 'fatal', 'error', 'warning', 'refactor', +# 'convention', and 'info' which contain the number of messages in each +# category, as well as 'statement' which is the total number of statements +# analyzed. This score is used by the global evaluation report (RP0004). +evaluation=max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details. +msg-template= + +# Set the output format. Available formats are: text, parseable, colorized, +# json2 (improved json format), json (old json format) and msvs (visual +# studio). You can also give a reporter class, e.g. +# mypackage.mymodule.MyReporterClass. +#output-format= + +# Tells whether to display a full report or only the messages. +reports=no + +# Activate the evaluation score. +score=yes + + +[SIMILARITIES] + +# Comments are removed from the similarity computation +ignore-comments=yes + +# Docstrings are removed from the similarity computation +ignore-docstrings=yes + +# Imports are removed from the similarity computation +ignore-imports=yes + +# Signatures are removed from the similarity computation +ignore-signatures=yes + +# Minimum lines number of a similarity. +min-similarity-lines=4 + + +[SPELLING] + +# Limits count of emitted suggestions for spelling mistakes. +max-spelling-suggestions=4 + +# Spelling dictionary name. No available dictionaries : You need to install +# both the python package and the system dependency for enchant to work. +spelling-dict= + +# List of comma separated words that should be considered directives if they +# appear at the beginning of a comment and should not be checked. +spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy: + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains the private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to the private dictionary (see the +# --spelling-private-dict-file option) instead of raising a message. +spelling-store-unknown-words=no + + +[STRING] + +# This flag controls whether inconsistent-quotes generates a warning when the +# character used as a quote delimiter is used inconsistently within a module. +check-quote-consistency=no + +# This flag controls whether the implicit-str-concat should generate a warning +# on implicit string concatenation in sequences defined over several lines. +check-str-concat-over-line-jumps=no + + +[TYPECHECK] + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + +# Tells whether to warn about missing members when the owner of the attribute +# is inferred to be None. +ignore-none=yes + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# List of symbolic message names to ignore for Mixin members. +ignored-checks-for-mixins=no-member, + not-async-context-manager, + not-context-manager, + attribute-defined-outside-init + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local,argparse.Namespace + +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes + +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 + +# Regex pattern to define which classes are considered mixins. +mixin-class-rgx=.*[Mm]ixin + +# List of decorators that change the signature of a decorated function. +signature-mutators= + + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid defining new builtins when possible. +additional-builtins= + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# List of names allowed to shadow builtins +allowed-redefined-builtins= + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_, + _cb + +# A regular expression matching the name of dummy variables (i.e. expected to +# not be used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io diff --git a/UniTAP/__init__.py b/UniTAP/__init__.py new file mode 100644 index 0000000..c86273d --- /dev/null +++ b/UniTAP/__init__.py @@ -0,0 +1,11 @@ +from UniTAP.tsi_lib import * +from UniTAP.common import * +from UniTAP.version import __version__ +from UniTAP import dev +from UniTAP import utils + +# +# Logging configuration. +# +from UniTAP.utils import tsi_logging as logging +logging.set_enabled(False) diff --git a/UniTAP/__main__.py b/UniTAP/__main__.py new file mode 100644 index 0000000..c859cc3 --- /dev/null +++ b/UniTAP/__main__.py @@ -0,0 +1,6 @@ +from UniTAP import TsiLib + +if __name__ == '__main__': + tsi = TsiLib() + + # UniTAP.print_device_list() diff --git a/UniTAP/common/__init__.py b/UniTAP/common/__init__.py new file mode 100644 index 0000000..bf6b509 --- /dev/null +++ b/UniTAP/common/__init__.py @@ -0,0 +1,9 @@ +from .color_info import ColorInfo +from .data_info import DataInfo +from .timing import Timing +from .video_mode import VideoMode +from .video_frame import VideoFrame, ImageFileFormat, get_vf_from_image +from .audio_mode import AudioMode, AudioFileFormat, AudioFrameData, AudioFormat +from .timestamp import Timestamp +from .dsc_video_frame import VideoFrameDSC, CompressionInfo +from .dsc_compression_info import create_from_pps diff --git a/UniTAP/common/audio_mode.py b/UniTAP/common/audio_mode.py new file mode 100644 index 0000000..3ecb91d --- /dev/null +++ b/UniTAP/common/audio_mode.py @@ -0,0 +1,271 @@ +from enum import IntEnum +from UniTAP.common.timestamp import Timestamp + + +class AudioFileFormat(IntEnum): + """ + Describe all supported audio file formats for saving audio: + - BIN. + - WAV. + """ + UNKNOWN = -1 + BIN = 0 + WAV = 1 + + +class AudioFormat(IntEnum): + """ + Describe all supported audio formats: + - PCMAudio. + """ + Unknown = -1 + L_PCM = 0xFFFF + + +class AudioMode: + """ + Class `AudioMode` contains part information of audio: sample rate, count of bits and channel count. + """ + def __init__(self, sample_rate: int = 44100, bits: int = 16, channel_count: int = 2): + self.sample_rate = sample_rate + self.bits = bits + self.channel_count = channel_count + + def __str__(self): + return f"Sample rate: {self.sample_rate}\n" \ + f"Bits: {self.bits}\n" \ + f"Channel count: {self.channel_count}\n" + + def __eq__(self, other): + return self.sample_rate == other.sample_rate and \ + self.bits == other.bits and \ + self.channel_count == other.channel_count + + def is_valid(self) -> bool: + """ + + Check that information is valid (all values more than 0). + + Returns: + object of bool type. + """ + return self.sample_rate > 0 and \ + self.bits > 0 and \ + self.channel_count > 0 + + +class AudioFrameData: + """ + Class `AudioFrameData` describes captured frame from Sink (RX - receiver) side. Contains information of audio: + `AudioMode`, samples, `AudioFormat`, frame counter, `Timestamp`, audio data. + """ + def __init__(self, audio_mode: AudioMode = AudioMode(), samples: int = 0, + sample_format: AudioFormat = AudioFormat.Unknown, frame_counter: int = 0, + timestamp: Timestamp = Timestamp(0), data: bytearray = bytearray()): + self.__audio_mode = audio_mode + self.__samples = samples + self.__sample_format = sample_format + self.__frame_counter = frame_counter + self.__timestamp = timestamp + self.__data = data + + @property + def channel_count(self) -> int: + """ + + Returns channel count. + + Returns: + object of int type. + """ + return self.__audio_mode.channel_count + + @property + def samples(self) -> int: + """ + + Returns samples. + + Returns: + object of int type. + """ + return self.__samples + + @property + def sample_size(self) -> int: + """ + + Returns sample size. + + Returns: + object of int type. + """ + return self.__audio_mode.bits + + @property + def sample_rate(self) -> int: + """ + + Returns sample rate. + + Returns: + object of int type. + """ + return self.__audio_mode.sample_rate + + @property + def sample_format(self) -> AudioFormat: + """ + + Returns sample format. + + Returns: + object of AudioFormat type. + """ + return self.__sample_format + + @property + def frame_counter(self) -> int: + """ + + Returns frame counter. + + Returns: + object of int type. + """ + return self.__frame_counter + + @property + def timestamp(self) -> Timestamp: + """ + + Returns timestamp. + + Returns: + object of Timestamp type. + """ + return self.__timestamp + + @property + def data(self) -> bytearray: + """ + + Returns data. + + Returns: + object of bytearray type. + """ + return self.__data + + @channel_count.setter + def channel_count(self, channel_count: int): + """ + + Allows setting new value to channel count. + + Args: + channel_count (int) - must be more than 0 + """ + if channel_count <= 0: + raise ValueError(f"Channel count must be more than 0.") + self.__audio_mode.channel_count = channel_count + + @samples.setter + def samples(self, samples: int): + """ + + Allows setting new value to samples. + + Args: + samples (int) - must be more than 0 + """ + if samples <= 0: + raise ValueError(f"Samples must be more than 0.") + self.__samples = samples + + @sample_size.setter + def sample_size(self, sample_size: int): + """ + + Allows setting new value to sample size. + + Args: + sample_size (int) - must be more than 0 + """ + if sample_size <= 0: + raise ValueError(f"Sample size must be more than 0.") + self.__audio_mode.bits = sample_size + + @sample_rate.setter + def sample_rate(self, sample_rate: int): + """ + + Allows setting new value to sample rate. + + Args: + sample_rate (int) - must be more than 0 + """ + if sample_rate <= 0: + raise ValueError(f"Sample rate must be more than 0.") + self.__audio_mode.sample_rate = sample_rate + + @sample_format.setter + def sample_format(self, sample_format: int): + """ + + Allows setting new value to sample format. + + Args: + sample_format (int) - must be more than 0 + """ + if sample_format <= 0: + raise ValueError(f"Sample format must be more than 0.") + self.__sample_format = AudioFormat(sample_format) + + @timestamp.setter + def timestamp(self, timestamp: int): + """ + + Allows setting new value to timestamp. + + Args: + timestamp (int) - must be more than 0 + """ + if timestamp <= 0: + raise ValueError(f"Timestamp must be more than 0.") + self.__timestamp.value = timestamp + + @frame_counter.setter + def frame_counter(self, frame_counter: int): + """ + + Allows setting new value to frame counter. + + Args: + frame_counter (int) - must be more than -1 + """ + if frame_counter < 0: + raise ValueError(f"Frame counter must be more than 0.") + self.__frame_counter = frame_counter + + @data.setter + def data(self, value: bytearray): + """ + + Allows setting new value to data. + + Args: + value (bytearray) - length of value must be more than 0 + """ + if len(value) <= 0: + raise ValueError(f"Audio data length must be more than 0.") + self.__data = value + + def __str__(self): + return f"Sample rate: {self.sample_rate}\n" \ + f"Bits (Sample size): {self.sample_size}\n" \ + f"Channel count: {self.channel_count}\n" \ + f"Sample format: {self.sample_format.name}\n" \ + f"Timestamp: {self.timestamp.to_n_sec} n sec\n" \ + f"Frame counter: {self.frame_counter}\n" \ + f"Length of data: {self.data} bytes\n" diff --git a/UniTAP/common/color_info.py b/UniTAP/common/color_info.py new file mode 100644 index 0000000..bba2814 --- /dev/null +++ b/UniTAP/common/color_info.py @@ -0,0 +1,111 @@ +from enum import IntEnum + + +class ColorInfo: + """ + + Class contains information of frame `ColorFormat`, `DynamicRange`, `Colorimetry`. + + """ + class ColorFormat(IntEnum): + """ + Contains values of possible color format. + """ + CF_NONE = 0 + CF_UNKNOWN = 1 + CF_RGB = 2 + CF_YCbCr_422 = 3 + CF_YCbCr_444 = 4 + CF_YCbCr_420 = 5 + CF_IDO_DEFINED = 6 + CF_Y_ONLY = 7 + CF_RAW = 8 + CF_DSC = 9 + + class DynamicRange(IntEnum): + """ + Contains values of possible dynamic range. + """ + DR_UNKNOWN = -1 + DR_VESA = 0 + DR_CTA = 1 + + class Colorimetry(IntEnum): + """ + Contains values of possible colorimetry. + """ + CM_NONE = 0 + CM_RESERVED = 1 + CM_sRGB = 2 + CM_SMPTE_170M = 3 + CM_ITUR_BT601 = 4 + CM_ITUR_BT709 = 5 + CM_xvYCC601 = 6 + CM_xvYCC709 = 7 + CM_sYCC601 = 8 + CM_AdobeYCC601 = 9 + CM_AdobeRGB = 10 + CM_ITUR_BT2020_YcCbcCrc = 11 + CM_ITUR_BT2020_YCbCr = 12 + CM_ITUR_BT2020_RGB = 13 + CM_RGB_WIDE_GAMUT_FIX = 14 + CM_RGB_WIDE_GAMUT_FLT = 15 + CM_DCI_P3 = 16 + CM_DICOM_1_4_GRAY_SCALE = 17 + CM_CUSTOM_COLOR_PROFILE = 18 + + CM_opYCC601 = CM_AdobeYCC601 + CM_opRGB = CM_AdobeRGB + + __COMPONENT_MULTIPLIER = { + ColorFormat.CF_RGB: 3, + ColorFormat.CF_YCbCr_422: 2, + ColorFormat.CF_YCbCr_444: 3, + ColorFormat.CF_YCbCr_420: 3 / 2, + ColorFormat.CF_Y_ONLY: 1, + ColorFormat.CF_RAW: 3 + } + + def __init__(self): + self.colorimetry = self.Colorimetry.CM_NONE + self.color_format = self.ColorFormat.CF_NONE + self.dynamic_range = self.DynamicRange.DR_VESA + self.bpc = 0 + + def __str__(self): + return f"Color format: {self.color_format.name}\n" \ + f"Colorimetry: {self.colorimetry.name}\n" \ + f"Dynamic Range: {self.dynamic_range.name}\n" \ + f"BPC: {self.bpc}" + + def __eq__(self, other): + return self.bpc == other.bpc and\ + self.color_format == other.color_format and\ + self.colorimetry == other.colorimetry and\ + self.dynamic_range == other.dynamic_range + + def is_valid(self) -> bool: + """ + + Check that information is valid (not equal NONE state and bpc more than 0). + + Returns: + object of bool type. + """ + return self.bpc > 0 and\ + self.color_format != self.ColorFormat.CF_NONE and\ + self.colorimetry != self.Colorimetry.CM_NONE + + @property + def bpp(self) -> int: + """ + + Returns calculated bits per pixel for this color info (except DSC). 0 if color info is not valid. + + Returns: + object of int type. + """ + if self.is_valid() and self.color_format != self.ColorFormat.CF_DSC: + return round(self.bpc * self.__COMPONENT_MULTIPLIER.get(self.color_format, 1)) + else: + return 0 diff --git a/UniTAP/common/data_info.py b/UniTAP/common/data_info.py new file mode 100644 index 0000000..42f8485 --- /dev/null +++ b/UniTAP/common/data_info.py @@ -0,0 +1,64 @@ +from enum import IntEnum + + +class DataInfo: + """ + + Class contains information of frame `Packing`, `ComponentOrder`, `Alignment`. + + """ + class Packing(IntEnum): + """ + Contains values of possible packing. + """ + P_UNKNOWN = 0 + P_PLANAR = 1 + P_PACKED = 2 + + class ComponentOrder(IntEnum): + """ + Contains values of possible component order. + """ + CO_UNKNOWN = 0 + CO_UCDRX = 1 + CO_RGB = 2 + CO_RGBA = 3 + CO_BGR = 4 + CO_BGRA = 5 + CO_YCbCr = 6 + CO_CbY0CrY1 = 7 + + class Alignment(IntEnum): + """ + Contains values of possible alignment. + """ + A_UNKNOWN = 0 + A_MSB = 1 + A_LSB = 2 + + def __init__(self): + self.packing = self.Packing.P_UNKNOWN + self.component_order = self.ComponentOrder.CO_UNKNOWN + self.alignment = self.Alignment.A_UNKNOWN + + def is_valid(self) -> bool: + """ + + Check that information is valid (not equal UNKNOWN state). + + Returns: + object of bool type. + """ + return self.packing != self.Packing.P_UNKNOWN and\ + self.component_order != self.ComponentOrder.CO_UNKNOWN and\ + self.alignment != self.Alignment.A_UNKNOWN + + def __str__(self): + return f"Packing: {self.packing.name}\n" \ + f"Component Order: {self.component_order.name}\n" \ + f"Alignment: {self.alignment.name}\n" + + def __eq__(self, other): + return self.packing == other.packing and \ + self.component_order == other.component_order and \ + self.alignment == other.alignment \ No newline at end of file diff --git a/UniTAP/common/dsc_compression_info.py b/UniTAP/common/dsc_compression_info.py new file mode 100644 index 0000000..6075074 --- /dev/null +++ b/UniTAP/common/dsc_compression_info.py @@ -0,0 +1,96 @@ +from enum import IntEnum + + +class DscCompressionInfo: + """ + + Class contains information about DSC compression used on frame. + + """ + class DscColorFormat(IntEnum): + """ + Contains values of possible color format. + """ + CF_NONE = -1 + CF_RGB = 0 + CF_YCbCr_422 = 1 + CF_YCbCr_444 = 2 + CF_YCbCr_420 = 3 + CF_Simple_422 = 4 + + def __init__(self): + self.color_format = DscCompressionInfo.DscColorFormat.CF_NONE + self.bpp = 0 + self.is_block_prediction_enabled = False + self.h_slice_size = 0 + self.v_slice_size = 0 + self.buffer_bit_depth = 0 + self.version = (0, 0) + self.is_simple_as_444 = False + + def is_valid(self) -> bool: + """ + Return state of the video frame and check color_format, bpp, h and v slice_size and DSC version. + If everything ok, return True, otherwise - False. + + Returns: + object of `bool` type. + """ + return self.color_format is not None and\ + self.bpp > 0 and\ + self.h_slice_size > 0 and\ + self.v_slice_size > 0 and\ + self.version in [(1, 2), (1, 1)] + + +def create_from_pps(pps_bytearray: bytearray) -> DscCompressionInfo: + """ + Fill structure 'DscCompressionInfo' from PPS header of the DSC image. + + Returns: + object of `DscCompressionInfo` type. + """ + if b'DSCF' not in pps_bytearray: + if len(pps_bytearray) < 128: + raise ValueError("Incorrect PPS size!") + pps = pps_bytearray + else: + if len(pps_bytearray) < 132: + raise ValueError("Incorrect PPS size!") + pps = pps_bytearray[4:] + + is_yuv = ((pps[4] >> 4) & 1) == 0 + is_simple422 = ((pps[4] >> 3) & 1) == 1 + is_native422 = (pps[88] & 1) == 1 + is_native420 = ((pps[88] >> 1) & 1) == 1 + width = (pps[8] << 8) | pps[9] + height = (pps[6] << 8) | pps[7] + slice_height = (pps[10] << 8) | pps[11] + slice_width = (pps[12] << 8) | pps[13] + + info = DscCompressionInfo() + + info.version = (pps[0] >> 4 & 0xf, pps[0] & 0xf) + info.buffer_bit_depth = pps[3] & 0xf + info.is_block_prediction_enabled = bool(pps[4] >> 5 & 0x1) + info.h_slice_size = int(width / slice_width) + info.v_slice_size = int(height / slice_height) + + if is_native422 or is_native420: + info.bpp = int(pps[4] & 0x3 << 8 | pps[5]) / 2 + else: + info.bpp = int(pps[4] & 0x3 << 8 | pps[5]) + + if is_yuv: + if is_simple422: + info.color_format = DscCompressionInfo.DscColorFormat.CF_Simple_422 + elif is_native422: + info.color_format = DscCompressionInfo.DscColorFormat.CF_YCbCr_422 + elif is_native420: + info.color_format = DscCompressionInfo.DscColorFormat.CF_YCbCr_420 + else: + info.color_format = DscCompressionInfo.DscColorFormat.CF_YCbCr_444 + else: + info.color_format = DscCompressionInfo.DscColorFormat.CF_RGB + + return info diff --git a/UniTAP/common/dsc_video_frame.py b/UniTAP/common/dsc_video_frame.py new file mode 100644 index 0000000..942e5a7 --- /dev/null +++ b/UniTAP/common/dsc_video_frame.py @@ -0,0 +1,29 @@ +from .dsc_compression_info import DscCompressionInfo as CompressionInfo +from .video_frame import VideoFrame + + +class VideoFrameDSC(VideoFrame): + + """ + Class `VideoFrameDSC` contains base information about DSC compressed video frame: + - Height (int). + - Width (int). + - Data (bytearray). + - Color info (object of `ColorInfo`). + - Data info (object of `DataInfo`). + - Timestamp (object of `Timestamp`). + - CompressionInfo (object of `CompressionInfo`) + """ + + def __init__(self): + super().__init__() + self.compression_info = CompressionInfo() + + def is_compressed(self) -> bool: + """ + Return state of the video frame, compressed it or not. + + Returns: + object of `bool` type. + """ + return not self._compressed diff --git a/UniTAP/common/timestamp.py b/UniTAP/common/timestamp.py new file mode 100644 index 0000000..2212e2e --- /dev/null +++ b/UniTAP/common/timestamp.py @@ -0,0 +1,64 @@ +class Timestamp: + + """ + + Class contains information about timestamp in several representation variant: + - Seconds `to_sec`. + - Milliseconds `to_m_sec`. + - Microseconds `to_u_sec`. + - Nanoseconds `to_n_sec` or `value`. + + """ + + def __init__(self, nano_secs: int): + self.__value = nano_secs + + @property + def to_sec(self) -> float: + """ + Returns time in seconds. + """ + return self.__value / (10 ** 9) + + @property + def to_m_sec(self) -> float: + """ + Returns time milliseconds seconds. + """ + return self.__value / (10 ** 6) + + @property + def to_u_sec(self) -> float: + """ + Returns time microseconds seconds. + """ + return self.__value / (10 ** 3) + + @property + def to_n_sec(self) -> float: + """ + Returns time nanoseconds seconds. + """ + return self.__value + + @property + def value(self) -> float: + """ + Returns time nanoseconds seconds. + """ + return self.__value + + @value.setter + def value(self, value: int): + """ + Set time in nanoseconds seconds. + """ + if value <= 0: + raise ValueError(f"Value of timestamp cannot be less than 0.") + self.__value = value + + def __str__(self): + return f"{self.to_u_sec} milliseconds" + + def __eq__(self, other): + return self.__value == other.__value diff --git a/UniTAP/common/timing.py b/UniTAP/common/timing.py new file mode 100644 index 0000000..be70a94 --- /dev/null +++ b/UniTAP/common/timing.py @@ -0,0 +1,90 @@ +from enum import IntEnum + + +class Timing: + + """ + Class `Timing` contains information about Timing: all resolutions, timing id, frame rate, `AspectRatio`, + `Standard`, `ReduceBlanking`. + """ + + class Standard(IntEnum): + """ + Class `Standard` contains all possible variants of timing standards. + """ + SD_NONE = 0 + SD_CVT = 1 + SD_DMT = 2 + SD_CTA = 3 + SD_UGF = 4 + SD_OVT = 5 + + class AspectRatio(IntEnum): + """ + Class `AspectRatio` contains all possible variants of timing aspect ratio. + """ + AR_NONE = 0 + AR_4_3 = 1 + AR_16_9 = 2 + + class ReduceBlanking(IntEnum): + """ + Class `ReduceBlanking` contains all possible variants of timing reduce blanking. + """ + RB_NONE = 0 + RB1 = 1 + RB2 = 2 + RB3 = 3 + + def __init__(self): + self.frame_rate = 0.0 + self.hactive = 0 + self.vactive = 0 + self.htotal = 0 + self.vtotal = 0 + self.hstart = 0 + self.vstart = 0 + self.hswidth = 0 + self.vswidth = 0 + self.id = 0 + self.aspect_ratio = self.AspectRatio.AR_NONE + self.standard = self.Standard.SD_NONE + self.reduce_blanking = self.ReduceBlanking.RB_NONE + + def __str__(self): + return f"{self.frame_rate / 1000:03} " \ + f"{self.htotal} {self.hstart} {self.hactive} {self.hswidth:+} " \ + f"{self.vtotal} {self.vstart} {self.vactive} {self.vswidth:+}" + + def __eq__(self, other) -> bool: + return self.hactive == other.hactive and self.vactive == other.vactive and self.htotal == other.htotal and \ + self.vtotal == other.vtotal and self.hstart == other.hstart and self.vstart == other.vstart and \ + self.hswidth == other.hswidth and self.vswidth == other.vswidth + + def is_valid(self) -> bool: + """ + Check that timing is correct (Resolutions and frame rate more than 0) + + Returns: + is valid (bool) - valid (True) or not (False) + """ + return self.hactive > 0 and \ + self.vactive > 0 and \ + self.htotal > 0 and \ + self.vtotal > 0 and \ + self.hstart > 0 and \ + self.vstart > 0 and \ + self.frame_rate > 0 + + @property + def pixel_clock(self) -> float: + """ + Returns calculated pixel clock required for this video mode in MHz. 0.0 if video mode is not valid. + + Returns: + pixel clock (float) + """ + if self.is_valid(): + return round(self.htotal * self.vtotal * self.frame_rate / 1000000000.0, 3) + else: + return 0.0 diff --git a/UniTAP/common/video_frame.py b/UniTAP/common/video_frame.py new file mode 100644 index 0000000..26a0b71 --- /dev/null +++ b/UniTAP/common/video_frame.py @@ -0,0 +1,94 @@ +import os + +from enum import IntEnum +from .color_info import ColorInfo +from .data_info import DataInfo +from .timestamp import Timestamp +from PIL import Image + + +class ImageFileFormat(IntEnum): + """ + Describe all supported image file formats for saving `VideoFrame`: + - BIN. + - PPM. + - BMP. + - DSC. + """ + IFF_BIN = 0 + IFF_PPM = 1 + IFF_BMP = 2 + IFF_DSC = 3 + + +class VideoFrame: + + """ + Class `VideoFrame` contains base information about video frame: + - Height (int). + - Width (int). + - Data (bytearray). + - Color info (object of `ColorInfo`). + - Data info (object of `DataInfo`). + - Timestamp (object of `Timestamp`). + """ + + def __init__(self): + self.width = 0 + self.height = 0 + self.data = bytearray() + self.color_info = ColorInfo() + self.data_info = DataInfo() + self.timestamp = Timestamp(0) + self._compressed = False + + def __str__(self): + return f"Resolution: {self.width}x{self.height}\n" \ + f"Data length: {len(self.data)}\n" \ + f"Color Info:\n{self.color_info}\n" \ + f"Data Info:\n{self.data_info}\n" \ + f"Timestamp:\n{self.timestamp}\n" + + def is_compressed(self) -> bool: + return self._compressed + + +def get_vf_from_image(path: str, width: int, height: int) -> VideoFrame: + """ + + Function allows getting prepared object of `VideoFrame` from external (custom) image. + + Args: + path (str) - full path to image. + width (int) - width of image. + height (int) - height of image. + + """ + if not os.path.exists(path): + raise FileNotFoundError(f"Image: {path} is missing!") + + image = Image.open(path) + + if width < image.size[0] and height < image.size[1]: + image = image.crop((0, 0, width, height)) + + size = image.size + + if size == [0, 0]: + raise ValueError("Invalid image size.") + + convert_image = image.convert('RGB').resize(size=(width, height), resample=Image.Resampling.LANCZOS) + + video_frame = VideoFrame() + video_frame.data = bytearray(convert_image.tobytes()) + video_frame.width = width + video_frame.height = height + video_frame.color_info.bpc = 8 + video_frame.color_info.color_format = ColorInfo.ColorFormat.CF_RGB + video_frame.color_info.colorimetry = ColorInfo.Colorimetry.CM_sRGB + # video_frame.color_info.dynamic_range = ColorInfo.DynamicRange.DR_VESA + video_frame.data_info.packing = DataInfo.Packing.P_PACKED + video_frame.data_info.component_order = DataInfo.ComponentOrder.CO_RGB + video_frame.data_info.alignment = DataInfo.Alignment.A_LSB + + return video_frame diff --git a/UniTAP/common/video_mode.py b/UniTAP/common/video_mode.py new file mode 100644 index 0000000..96be19f --- /dev/null +++ b/UniTAP/common/video_mode.py @@ -0,0 +1,52 @@ +from .timing import Timing +from .color_info import ColorInfo + + +class VideoMode: + + """ + + Class `VideoMode` combines information about `Timing` and `ColorInfo`. + + """ + + def __init__(self, timing: Timing = Timing(), color_info: ColorInfo = ColorInfo()): + self.timing = timing + self.color_info = color_info + + def __str__(self): + return f"{self.timing.frame_rate / 1000:03} " \ + f"{self.timing.htotal} {self.timing.hstart} " \ + f"{self.timing.hactive} {self.timing.hswidth:+} " \ + f"{self.timing.vtotal} {self.timing.vstart} " \ + f"{self.timing.vactive} {self.timing.vswidth:+} " \ + f"{self.color_info.color_format.name}/" \ + f"{self.color_info.colorimetry.name}/" \ + f"{self.color_info.dynamic_range.name} {self.color_info.bpc}" + + def __eq__(self, other): + return self.timing == other.timing and self.color_info == other.color_info + + def is_valid(self) -> bool: + """ + + Check that `Timing` and `ColorInfo` of Video mode is valid. + + Returns: + object of bool type - Video mode valid or not + """ + return self.timing.is_valid() and self.color_info.is_valid() + + @property + def bit_rate(self) -> float: + """ + + Returns calculated bit rate required for this video mode in Gbps. 0 if video mode is not valid + + Returns: + object of float type + """ + if self.is_valid(): + return round(self.timing.pixel_clock * self.color_info.bpp / 1000.0, 3) + else: + return 0.0 diff --git a/UniTAP/dev/__init__.py b/UniTAP/dev/__init__.py new file mode 100644 index 0000000..8c42c11 --- /dev/null +++ b/UniTAP/dev/__init__.py @@ -0,0 +1,2 @@ +from .device import * +from .ports import * diff --git a/UniTAP/dev/dev_3xx_roles.py b/UniTAP/dev/dev_3xx_roles.py new file mode 100644 index 0000000..d510aa7 --- /dev/null +++ b/UniTAP/dev/dev_3xx_roles.py @@ -0,0 +1,459 @@ +from .ports import DPRX, DPTX, HDRX, HDTX, PDC340 +from .modules import MemoryManager, Capturer, DUTTests +from UniTAP.libs.lib_tsi.tsi_io import DeviceIO, PortProtocol + + +class UCD340: + """ + Class `UCD340` describes of device UCD-340. Device has several possible role: + - 'USB-C, DP Alt Mode Reference Sink' `USBCSink` + - 'USB-C, DP Alt Mode Reference Source' `USBCSource`. + """ + class USBCSink: + """ + Class `USBCSink` contains information of available functionality modules for role USB-C Sink (RX - receiver) + role: + - `DPRX`. + - `DUTTests`. + - `PDC`. + """ + def __init__(self, dev_io: DeviceIO, memory_manager: MemoryManager, capturer: Capturer): + self.__port_io = dev_io.create_port_io(0, PortProtocol.DisplayPortThrowUSBC) + self.__dprx = DPRX(self.__port_io, memory_manager, capturer) + self.__dut_tests = DUTTests(dev_io) + self.__pdcrx = PDC340(self.__port_io) + + @property + def dprx(self) -> DPRX: + """ + Returns DP Sink (RX - receiver) role. + + Returns: + object of DPRX type. + """ + return self.__dprx + + @property + def pdcrx(self) -> PDC340: + """ + Returns PDC Sink (RX - receiver) role. + + Returns: + object of `PDC340` type. + """ + return self.__pdcrx + + @property + def dut_tests(self) -> DUTTests: + """ + Returns DUT Test module. + + Returns: + object of DUTTests type. + """ + return self.__dut_tests + + @property + def name(self) -> str: + """ + Returns name of role. + + Returns: + object of str type. + """ + return list(UCD340.ROLE_DICT.keys())[list(UCD340.ROLE_DICT.values()).index(type(self))] + + class USBCSource: + """ + Class `USBCSource` contains information of available functionality modules for role USB-C Source + (TX - transmitter) role: + - `DPTX`. + - `DUTTests`. + - `PDC`. + """ + def __init__(self, dev_io: DeviceIO, memory_manager: MemoryManager, capturer: Capturer): + self.__port_io = dev_io.create_port_io(0, PortProtocol.DisplayPortThrowUSBC) + self.__dptx = DPTX(self.__port_io, memory_manager, capturer) + self.__dut_tests = DUTTests(dev_io) + self.__pdctx = PDC340(self.__port_io) + + @property + def dptx(self) -> DPTX: + """ + Returns DP Source (TX - transmitter) role. + + Returns: + object of DPTX type. + """ + return self.__dptx + + @property + def pdctx(self) -> PDC340: + """ + Returns PDC Source (TX - transmitter) role. + + Returns: + object of `PDC340` type. + """ + return self.__pdctx + + @property + def dut_tests(self) -> DUTTests: + """ + Returns DUT Test module. + + Returns: + object of DUTTests type. + """ + return self.__dut_tests + + @property + def name(self) -> str: + """ + Returns name of role. + + Returns: + object of str type. + """ + return list(UCD340.ROLE_DICT.keys())[list(UCD340.ROLE_DICT.values()).index(type(self))] + + ROLE_DICT = { + "USB-C, DP Alt Mode Reference Sink": USBCSink, + "USB-C, DP Alt Mode Reference Source": USBCSource + } + + +class UCD240(UCD340): + """ + Class `UCD240` describes of device UCD-240 and have the same functionality as the class 'UCD340'. + Device has several possible role: + - 'USB-C, DP Alt Mode Reference Sink' `USBCSink` + - 'USB-C, DP Alt Mode Reference Source' `USBCSource`. + """ + + +class UCD323: + """ + Class `UCD323` describes of device UCD-323. Device has several possible role: + - 'DisplayPort Reference Sink (SST, HDCP 2.3)' `DPSink` + - 'DisplayPort Reference Source (SST, HDCP 2.3)' `DPSource` + - 'HDMI Reference Sink (HDCP 2.3)' `HDMISink` + - 'HDMI Reference Source (HDCP 2.3)' `HDMISource` + """ + class DPSink: + """ + Class `DPSink` contains information of available functionality modules for role DP Sink (RX - receiver) role: + - `DPRX`. + - `DUTTests`. + """ + def __init__(self, dev_io: DeviceIO, memory_manager: MemoryManager, capturer: Capturer): + self.__dprx = DPRX(dev_io.create_port_io(0, PortProtocol.DisplayPort), memory_manager, capturer) + self.__dut_tests = DUTTests(dev_io) + + @property + def dprx(self) -> DPRX: + """ + Returns DP Sink (RX - receiver) role. + + Returns: + object of DPRX type. + """ + return self.__dprx + + @property + def dut_tests(self) -> DUTTests: + """ + Returns DUT Test module. + + Returns: + object of DUTTests type. + """ + return self.__dut_tests + + @property + def name(self) -> str: + """ + Returns name of role. + + Returns: + object of str type. + """ + return list(UCD323.ROLE_DICT.keys())[list(UCD323.ROLE_DICT.values()).index(type(self))] + + class DPSource: + """ + Class `DPSink` contains information of available functionality modules for role DP Source (TX - transmitter) + role: + - `DPTX`. + - `DUTTests`. + """ + def __init__(self, dev_io: DeviceIO, memory_manager: MemoryManager, capturer: Capturer): + self.__dptx = DPTX(dev_io.create_port_io(0, PortProtocol.DisplayPort), memory_manager, capturer) + self.__dut_tests = DUTTests(dev_io) + + @property + def dptx(self) -> DPTX: + """ + Returns DP Source (TX - transmitter) role. + + Returns: + object of DPTX type. + """ + return self.__dptx + + @property + def dut_tests(self) -> DUTTests: + """ + Returns DUT Test module. + + Returns: + object of DUTTests type. + """ + return self.__dut_tests + + @property + def name(self) -> str: + """ + Returns name of role. + + Returns: + object of str type. + """ + return list(UCD323.ROLE_DICT.keys())[list(UCD323.ROLE_DICT.values()).index(type(self))] + + class HDMISink: + """ + Class `HDMISink` contains information of available functionality modules for role HDMI Sink (RX - receiver) + role: + - `HDRX`. + - `DUTTests`. + """ + def __init__(self, dev_io: DeviceIO, memory_manager: MemoryManager, capturer: Capturer): + self.__hdrx = HDRX(dev_io.create_port_io(0, PortProtocol.HDMI), memory_manager, capturer) + self.__dut_tests = DUTTests(dev_io) + + @property + def hdrx(self) -> HDRX: + """ + Returns HDMI Sink (RX - receiver) role. + + Returns: + object of HDRX type. + """ + return self.__hdrx + + @property + def dut_tests(self) -> DUTTests: + """ + Returns DUT Test module. + + Returns: + object of DUTTests type. + """ + return self.__dut_tests + + @property + def name(self) -> str: + """ + Returns name of role. + + Returns: + object of str type. + """ + return list(UCD323.ROLE_DICT.keys())[list(UCD323.ROLE_DICT.values()).index(type(self))] + + class HDMISource: + """ + Class `HDMISource` contains information of available functionality modules for role HDMI Source + (TX - transmitter) role: + - `HDTX`. + - `DUTTests`. + """ + def __init__(self, dev_io: DeviceIO, memory_manager: MemoryManager, capturer: Capturer): + self.__hdtx = HDTX(dev_io.create_port_io(0, PortProtocol.HDMI), memory_manager, capturer) + self.__dut_tests = DUTTests(dev_io) + + @property + def hdtx(self) -> HDTX: + """ + Returns HDMI Source (TX - transmitter) role. + + Returns: + object of HDTX type. + """ + return self.__hdtx + + @property + def dut_tests(self) -> DUTTests: + """ + Returns DUT Test module. + + Returns: + object of DUTTests type. + """ + return self.__dut_tests + + @property + def name(self) -> str: + """ + Returns name of role. + + Returns: + object of str type. + """ + return list(UCD323.ROLE_DICT.keys())[list(UCD323.ROLE_DICT.values()).index(type(self))] + + ROLE_DICT = { + "DisplayPort Reference Sink (SST, HDCP 2.3)": DPSink, + "DisplayPort Reference Source (SST, HDCP 2.3)": DPSource, + "HDMI Reference Sink (HDCP 2.3)": HDMISink, + "HDMI Reference Source (HDCP 2.3)": HDMISource, + } + + +class UCD301: + """ + Class `UCD301` describes of device UCD-301. Device has several possible role: + - 'DisplayPort Reference Sink (SST, HDCP 2.3)' `DPSink` + - 'HDMI Reference Sink (HDCP 2.3)' `HDMISink` + """ + class DPSink: + """ + Class `DPSink` contains information of available functionality modules for role DP Sink (RX - receiver) role: + - `DPRX`. + - `DUTTests`. + """ + def __init__(self, dev_io: DeviceIO, memory_manager: MemoryManager, capturer: Capturer): + self.__dprx = DPRX(dev_io.create_port_io(0, PortProtocol.DisplayPort), memory_manager, capturer) + self.__dut_tests = DUTTests(dev_io) + + @property + def dprx(self) -> DPRX: + """ + Returns DP Sink (RX - receiver) role. + + Returns: + object of DPRX type. + """ + return self.__dprx + + @property + def dut_tests(self) -> DUTTests: + """ + Returns DUT Test module. + + Returns: + object of DUTTests type. + """ + return self.__dut_tests + + @property + def name(self) -> str: + """ + Returns name of role. + + Returns: + object of str type. + """ + return list(UCD301.ROLE_DICT.keys())[list(UCD301.ROLE_DICT.values()).index(type(self))] + + class HDMISink: + """ + Class `HDMISink` contains information of available functionality modules for role HDMI Sink (RX - receiver) + role: + - `HDRX`. + - `DUTTests`. + """ + def __init__(self, dev_io: DeviceIO, memory_manager: MemoryManager, capturer: Capturer): + self.__hdrx = HDRX(dev_io.create_port_io(0, PortProtocol.HDMI), memory_manager, capturer) + self.__dut_tests = DUTTests(dev_io) + + @property + def hdrx(self) -> HDRX: + """ + Returns HDMI Sink (RX - receiver) role. + + Returns: + object of HDRX type. + """ + return self.__hdrx + + @property + def dut_tests(self) -> DUTTests: + """ + Returns DUT Test module. + + Returns: + object of DUTTests type. + """ + return self.__dut_tests + + @property + def name(self) -> str: + """ + Returns name of role. + + Returns: + object of str type. + """ + return list(UCD301.ROLE_DICT.keys())[list(UCD301.ROLE_DICT.values()).index(type(self))] + + class DPSinkHDMISink: + """ + Class `DPSinkHDMISink` contains information of available functionality modules for role DP and HDMI Sink + (RX - receiver) role: + - `DPRX`. + - `HDRX`. + - `DUTTests`. + """ + + def __init__(self, dev_io: DeviceIO, memory_manager: MemoryManager, capturer: Capturer): + self.__dprx = DPRX(dev_io.create_port_io(0, PortProtocol.DisplayPort), memory_manager, capturer) + self.__hdrx = HDRX(dev_io.create_port_io(0, PortProtocol.HDMI), memory_manager, capturer) + self.__dut_tests = DUTTests(dev_io) + + @property + def hdrx(self) -> HDRX: + """ + Returns HDMI Sink (RX - receiver) role. + + Returns: + object of HDRX type. + """ + return self.__hdrx + + @property + def dprx(self) -> DPRX: + """ + Returns DP Sink (RX - receiver) role. + + Returns: + object of DPRX type. + """ + return self.__dprx + + @property + def dut_tests(self) -> DUTTests: + """ + Returns DUT Test module. + + Returns: + object of DUTTests type. + """ + return self.__dut_tests + + @property + def name(self) -> str: + """ + Returns name of role. + + Returns: + object of str type. + """ + return list(UCD301.ROLE_DICT.keys())[list(UCD301.ROLE_DICT.values()).index(type(self))] + + ROLE_DICT = { + "DisplayPort Reference Sink (SST, HDCP 2.3)": DPSink, + "HDMI, DisplayPort (SST), SPDIF Reference Sink": DPSinkHDMISink, + "HDMI Reference Sink (HDCP 2.3)": HDMISink, + } diff --git a/UniTAP/dev/dev_4xx_roles.py b/UniTAP/dev/dev_4xx_roles.py new file mode 100644 index 0000000..3a4b27a --- /dev/null +++ b/UniTAP/dev/dev_4xx_roles.py @@ -0,0 +1,220 @@ +from UniTAP.dev.ports import DPRX4xx, DPTX4xx, HDRX4xx, HDTX4xx, PDC424 +from .modules import MemoryManager, Capturer, DUTTests +from UniTAP.libs.lib_tsi.tsi_io import DeviceIO, PortProtocol + + +class UCD400: + """ + Class `UCD400` describes of device UCD-400. Device has one possible role: + - 'DisplayPort Source and Sink' `DPSourceDPSink` + """ + class DPSourceDPSink: + """ + Class `DPSourceDPSink` contains information of available functionality modules for role DP Sink + (RX - receiver) and DP Source (TX - transmitter) roles: + - `DPRX4xx`. + - `DPTX4xx`. + - `DUTTests`. + """ + def __init__(self, dev_io: DeviceIO, memory_manager: MemoryManager, capturer: Capturer): + super().__init__() + self.__dprx = DPRX4xx(dev_io.create_port_io(0, PortProtocol.DisplayPort), memory_manager, capturer) + self.__dptx = DPTX4xx(dev_io.create_port_io(1, PortProtocol.DisplayPort), memory_manager, capturer) + self.__dut_tests = DUTTests(dev_io) + + @property + def dprx(self) -> DPRX4xx: + """ + Returns DP Sink (RX - receiver) role. + + Returns: + object of DPRX4xx type. + """ + return self.__dprx + + @property + def dptx(self) -> DPTX4xx: + """ + Returns DP Source (TX - transmitter) role. + + Returns: + object of DPTX4xx type. + """ + return self.__dptx + + @property + def dut_tests(self) -> DUTTests: + """ + Returns DUT Test module. + + Returns: + object of DUTTests type. + """ + return self.__dut_tests + + @property + def name(self) -> str: + """ + Returns name of role. + + Returns: + object of str type. + """ + return list(UCD400.ROLE_DICT.keys())[list(UCD400.ROLE_DICT.values()).index(type(self))] + + ROLE_DICT = { + "DisplayPort Source and Sink": DPSourceDPSink + } + + +class UCD422: + """ + Class `UCD422` describes of device UCD-422. Device has one possible role: + - 'HDMI Source and Sink' `HDMISourceHDMISink` + """ + class HDMISourceHDMISink: + """ + Class `DPSourceDPSink` contains information of available functionality modules for role HDMI Sink + (RX - receiver) and HDMI Source (TX - transmitter) roles: + - `HDRX4xx`. + - `HDTX4xx`. + - `DUTTests`. + """ + def __init__(self, dev_io: DeviceIO, memory_manager: MemoryManager, capturer: Capturer): + super().__init__() + self.__hdrx = HDRX4xx(dev_io.create_port_io(0, PortProtocol.HDMI), memory_manager, capturer) + self.__hdtx = HDTX4xx(dev_io.create_port_io(1, PortProtocol.HDMI), memory_manager, capturer) + self.__dut_tests = DUTTests(dev_io) + + @property + def hdtx(self) -> HDTX4xx: + """ + Returns HDMI Source (TX - transmitter) role. + + Returns: + object of HDTX4xx type. + """ + return self.__hdtx + + @property + def hdrx(self) -> HDRX4xx: + """ + Returns HDMI Sink (RX - receiver) role. + + Returns: + object of HDRX4xx type. + """ + return self.__hdrx + + @property + def dut_tests(self) -> DUTTests: + """ + Returns DUT Test module. + + Returns: + object of DUTTests type. + """ + return self.__dut_tests + + @property + def name(self) -> str: + """ + Returns name of role. + + Returns: + object of str type. + """ + return list(UCD422.ROLE_DICT.keys())[list(UCD422.ROLE_DICT.values()).index(type(self))] + + ROLE_DICT = { + "HDMI Source and Sink": HDMISourceHDMISink + } + + +class UCD424: + """ + Class `UCD424` describes of device UCD-424. Device has one possible role: + - 'USB-C Source and Sink' `USBCSourceUSBCSink`. + """ + class USBCSourceUSBCSink: + """ + Class `DPSourceDPSink` contains information of available functionality modules for role HDMI Sink + (RX - receiver) and HDMI Source (TX - transmitter) roles: + - `DPRX4xx`. + - `DPTX4xx`. + - `DUTTests`. + - `PDC`. + """ + def __init__(self, dev_io: DeviceIO, memory_manager: MemoryManager, capturer: Capturer): + super().__init__() + self.__port_rx = dev_io.create_port_io(0, PortProtocol.DisplayPortThrowUSBC) + self.__port_tx = dev_io.create_port_io(1, PortProtocol.DisplayPortThrowUSBC) + self.__dprx = DPRX4xx(self.__port_rx, memory_manager, capturer) + self.__dptx = DPTX4xx(self.__port_tx, memory_manager, capturer) + self.__dut_tests = DUTTests(dev_io) + self.__pdcrx = PDC424(self.__port_rx) + self.__pdctx = PDC424(self.__port_tx) + + @property + def dprx(self) -> DPRX4xx: + """ + Returns DP Sink (RX - receiver) role. + + Returns: + object of DPRX4xx type. + """ + return self.__dprx + + @property + def dptx(self) -> DPTX4xx: + """ + Returns DP Source (TX - transmitter) role. + + Returns: + object of DPTX4xx type. + """ + return self.__dptx + + @property + def pdcrx(self) -> PDC424: + """ + Returns PDC Sink (RX - receiver) role. + + Returns: + object of `PDC424` type. + """ + return self.__pdcrx + + @property + def pdctx(self) -> PDC424: + """ + Returns PDC source (TX - transmitter) role. + + Returns: + object of `PDC424` type. + """ + return self.__pdctx + + @property + def dut_tests(self) -> DUTTests: + """ + Returns DUT Test module. + + Returns: + object of DUTTests type. + """ + return self.__dut_tests + + @property + def name(self) -> str: + """ + Returns name of role. + + Returns: + object of str type. + """ + return list(UCD424.ROLE_DICT.keys())[list(UCD424.ROLE_DICT.values()).index(type(self))] + + ROLE_DICT = { + "USB-C Source and Sink": USBCSourceUSBCSink + } diff --git a/UniTAP/dev/dev_5xx_roles.py b/UniTAP/dev/dev_5xx_roles.py new file mode 100644 index 0000000..fcde995 --- /dev/null +++ b/UniTAP/dev/dev_5xx_roles.py @@ -0,0 +1,258 @@ +from UniTAP.dev.ports import DPRX5xx, DPTX5xx, PDC500 +from .modules import MemoryManager, Capturer, DUTTests +from UniTAP.libs.lib_tsi.tsi_io import DeviceIO, PortProtocol + + +class UCD500: + """ + Class `UCD500` describes of device UCD-500. Device has one possible role: + - 'DisplayPort Source and Sink' `DPSourceDPSink`. + - 'DisplayPort Source and USB-C, DP Alt Mode Sink' `DPSourceUSBCSink`. + - 'DisplayPort Sink and USB-C, DP Alt Mode Source' `USBCSourceDPSink`. + - 'USB-C, DP Alt Mode Source and Sink' `USBCSourceUSBCSink`. + """ + class DPSourceDPSink: + """ + Class `DPSourceDPSink` contains information of available functionality modules for role DP Sink + (RX - receiver) and DP Source (TX - transmitter) roles: + - `DPRX5xx`. + - `DPTX5xx`. + - `DUTTests`. + """ + def __init__(self, dev_io: DeviceIO, memory_manager: MemoryManager, capturer: Capturer): + self.__dprx = DPRX5xx(dev_io.create_port_io(0, PortProtocol.DisplayPort), memory_manager, capturer) + self.__dptx = DPTX5xx(dev_io.create_port_io(1, PortProtocol.DisplayPort), memory_manager, capturer) + self.__dut_tests = DUTTests(dev_io) + + @property + def dprx(self) -> DPRX5xx: + """ + Returns DP Sink (RX - receiver) role. + + Returns: + object of DPRX5xx type. + """ + return self.__dprx + + @property + def dptx(self) -> DPTX5xx: + """ + Returns DP Source (TX - transmitter) role. + + Returns: + object of DPTX5xx type. + """ + return self.__dptx + + @property + def dut_tests(self) -> DUTTests: + """ + Returns DUT Test module. + + Returns: + object of DUTTests type. + """ + return self.__dut_tests + + @property + def name(self) -> str: + """ + Returns name of role. + + Returns: + object of str type. + """ + return list(UCD500.ROLE_DICT.keys())[list(UCD500.ROLE_DICT.values()).index(type(self))] + + class DPSourceUSBCSink: + def __init__(self, dev_io: DeviceIO, memory_manager: MemoryManager, capturer: Capturer): + self.__port_rx = dev_io.create_port_io(0, PortProtocol.DisplayPortThrowUSBC) + self.__dptx = DPTX5xx(dev_io.create_port_io(1, PortProtocol.DisplayPort), memory_manager, capturer) + self.__dprx = DPRX5xx(self.__port_rx, memory_manager, capturer) + self.__pdcrx = PDC500(self.__port_rx) + self.__dut_tests = DUTTests(dev_io) + + @property + def dprx(self) -> DPRX5xx: + """ + Returns DP Sink (RX - receiver) role. + + Returns: + object of DPRX5xx type. + """ + return self.__dprx + + @property + def dptx(self) -> DPTX5xx: + """ + Returns DP Source (TX - transmitter) role. + + Returns: + object of DPTX5xx type. + """ + return self.__dptx + + @property + def pdcrx(self) -> PDC500: + """ + Returns PDC Sink (RX - receiver) role. + + Returns: + object of `PDC500` type. + """ + return self.__pdcrx + + @property + def dut_tests(self) -> DUTTests: + """ + Returns DUT Test module. + + Returns: + object of DUTTests type. + """ + return self.__dut_tests + + @property + def name(self) -> str: + """ + Returns name of role. + + Returns: + object of str type. + """ + return list(UCD500.ROLE_DICT.keys())[list(UCD500.ROLE_DICT.values()).index(type(self))] + + class USBCSourceDPSink: + def __init__(self, dev_io: DeviceIO, memory_manager: MemoryManager, capturer: Capturer): + self.__port_tx = dev_io.create_port_io(1, PortProtocol.DisplayPortThrowUSBC) + self.__dptx = DPTX5xx(self.__port_tx, memory_manager, capturer) + self.__dprx = DPRX5xx(dev_io.create_port_io(0, PortProtocol.DisplayPort), memory_manager, capturer) + self.__pdctx = PDC500(self.__port_tx) + self.__dut_tests = DUTTests(dev_io) + + @property + def dprx(self) -> DPRX5xx: + """ + Returns DP Sink (RX - receiver) role. + + Returns: + object of DPRX5xx type. + """ + return self.__dprx + + @property + def dptx(self) -> DPTX5xx: + """ + Returns DP Source (TX - transmitter) role. + + Returns: + object of DPTX5xx type. + """ + return self.__dptx + + @property + def pdctx(self) -> PDC500: + """ + Returns PDC source (TX - transmitter) role. + + Returns: + object of `PDC500` type. + """ + return self.__pdctx + + @property + def dut_tests(self) -> DUTTests: + """ + Returns DUT Test module. + + Returns: + object of DUTTests type. + """ + return self.__dut_tests + + @property + def name(self) -> str: + """ + Returns name of role. + + Returns: + object of str type. + """ + return list(UCD500.ROLE_DICT.keys())[list(UCD500.ROLE_DICT.values()).index(type(self))] + + class USBCSourceUSBCSink: + def __init__(self, dev_io: DeviceIO, memory_manager: MemoryManager, capturer: Capturer): + self.__port_rx = dev_io.create_port_io(0, PortProtocol.DisplayPortThrowUSBC) + self.__port_tx = dev_io.create_port_io(1, PortProtocol.DisplayPortThrowUSBC) + self.__dprx = DPRX5xx(self.__port_rx, memory_manager, capturer) + self.__dptx = DPTX5xx(self.__port_tx, memory_manager, capturer) + self.__pdcrx = PDC500(self.__port_rx) + self.__pdctx = PDC500(self.__port_tx) + self.__dut_tests = DUTTests(dev_io) + + @property + def dprx(self) -> DPRX5xx: + """ + Returns DP Sink (RX - receiver) role. + + Returns: + object of DPRX5xx type. + """ + return self.__dprx + + @property + def dptx(self) -> DPTX5xx: + """ + Returns DP Source (TX - transmitter) role. + + Returns: + object of DPTX5xx type. + """ + return self.__dptx + + @property + def pdcrx(self) -> PDC500: + """ + Returns PDC Sink (RX - receiver) role. + + Returns: + object of `PDC500` type. + """ + return self.__pdcrx + + @property + def pdctx(self) -> PDC500: + """ + Returns PDC source (TX - transmitter) role. + + Returns: + object of `PDC500` type. + """ + return self.__pdctx + + @property + def dut_tests(self) -> DUTTests: + """ + Returns DUT Test module. + + Returns: + object of DUTTests type. + """ + return self.__dut_tests + + @property + def name(self) -> str: + """ + Returns name of role. + + Returns: + object of str type. + """ + return list(UCD500.ROLE_DICT.keys())[list(UCD500.ROLE_DICT.values()).index(type(self))] + + ROLE_DICT = { + "DisplayPort Source and Sink": DPSourceDPSink, + "DisplayPort Source and USB-C, DP Alt Mode Sink": DPSourceUSBCSink, + "DisplayPort Sink and USB-C, DP Alt Mode Source": USBCSourceDPSink, + "USB-C, DP Alt Mode Source and Sink": USBCSourceUSBCSink + } diff --git a/UniTAP/dev/device.py b/UniTAP/dev/device.py new file mode 100644 index 0000000..feece27 --- /dev/null +++ b/UniTAP/dev/device.py @@ -0,0 +1,154 @@ +import copy +import weakref +from typing import TypeVar, Type, List + +from .dev_5xx_roles import UCD500 +from .dev_4xx_roles import UCD400, UCD422, UCD424 +from .dev_3xx_roles import UCD240, UCD340, UCD323, UCD301 +from .modules import * + +from UniTAP.libs.lib_tsi.tsi_io import DeviceIO +from UniTAP.utils import tsi_logging as logging + +RoleType = TypeVar("RoleType", + UCD500.DPSourceUSBCSink, + UCD500.DPSourceDPSink, + UCD500.USBCSourceUSBCSink, + UCD500.USBCSourceDPSink, + + UCD424.USBCSourceUSBCSink, + UCD422.HDMISourceHDMISink, + UCD400.DPSourceDPSink, + + UCD340.USBCSink, + UCD340.USBCSource, + + UCD323.DPSink, + UCD323.DPSource, + UCD323.HDMISink, + UCD323.HDMISource, + + UCD301.DPSink, + UCD301.HDMISink, + + UCD240.USBCSink, + UCD240.USBCSource) + +MODEL_TO_CLASS = { + "UCD-500": UCD500, + "UCD-424": UCD424, + "UCD-422": UCD422, + "UCD-400": UCD400, + "UCD-340": UCD340, + "UCD-323": UCD323, + "UCD-301": UCD301, + "UCD-240": UCD240 +} + + +class NotExistingRoleSelected(Exception): + """ + Redefinition of base exception. + Define error of 'not existing role selected'. + """ + pass + + +class DeviceRoleIsAlreadySelected(Exception): + """ + Redefinition of base exception. + Define error of 'device role already selected'. + """ + pass + + +class TSIDevice: + """ + Class `TSIDevice` ... Allows: + - Select role of the device `select_role`. + - Get and redefine OPF (Operator feedback dialog) handler `opf_handler`. + """ + + def __init__(self, io: DeviceIO, name: str, serial_number: str, roles: List[RoleType]): + self.__io = io + self.__io_proxy = weakref.proxy(self.__io) + self.__memory_manager = MemoryManager(self.__io_proxy) + self.__capturer = Capturer(self.__io_proxy) + self.__opf_handler = OperatorFeedbackHandler(self.__io_proxy) + self.__terminal = Terminal(self.__io_proxy) + self.__name = name + self.__serial_number = serial_number + self.__role_list = roles + logging.info(f"[UniTAP] TSIDevice.init: {self}") + + def __del__(self): + del self.__io + logging.info(f"[UniTAP] TSIDevice.del: {self}") + + def __str__(self): + return f"{self.__name} [{self.__serial_number}]" + + def __define_internal_handler(self, role, role_type): + if role_type in [UCD500.DPSourceUSBCSink, UCD500.DPSourceDPSink, UCD500.USBCSourceUSBCSink, + UCD500.USBCSourceDPSink, UCD424.USBCSourceUSBCSink, UCD400.DPSourceDPSink]: + self.opf_handler._set_roles(role.dptx, role.dprx) + elif role_type in [UCD422.HDMISourceHDMISink]: + self.opf_handler._set_roles(role.hdtx, role.hdrx) + + def select_role(self, role_type: Type[RoleType]) -> RoleType: + """ + Function allows selecting role of the chosen device. + For example: `UniTAP.dev.UCD500.DPSourceDPSink` - will be opened UCD-500 in role DisplayPort Source and Sink. + + Args: + role_type (RoleType) - one of the possible roles. + + Returns: + object of `RoleType` type. Selected role. + """ + # TODO: Remove this limitation. Caused by TSI lib problem. + if self.__io.get_role() is not None: + raise DeviceRoleIsAlreadySelected(f"Device role is already selected.") + if role_type not in self.__role_list: + raise NotExistingRoleSelected(f"Role: {role_type} is not available on this device.") + self.__io.select_role(self.__role_list.index(role_type)) + self.__io.set_role(role_type(self.__io_proxy, + weakref.proxy(self.__memory_manager), + weakref.proxy(self.__capturer))) + self.__define_internal_handler(self.__io.get_role(), role_type) + + return self.__io.get_role() + + @property + def available_roles(self) -> list: + """ + Returns available list of roles. + + Returns: + object of list type. + """ + return copy.deepcopy(self.__role_list) + + @property + def opf_handler(self): + """ + Returns current OPF handler. + + Returns: + object of handler type. + """ + return self.__opf_handler.handler + + @opf_handler.setter + def opf_handler(self, value): + """ + Set new OPF handler. + + Args: + value - new OPF handler + """ + self.__opf_handler.handler = value + + @property + def _terminal(self) -> Terminal: + return self.__terminal diff --git a/UniTAP/dev/modules/__init__.py b/UniTAP/dev/modules/__init__.py new file mode 100644 index 0000000..b97b1d8 --- /dev/null +++ b/UniTAP/dev/modules/__init__.py @@ -0,0 +1,5 @@ +from .memory_manager import MemoryManager +from .capturer import * +from .opf import * +from .dut_tests import * +from .terminal import * diff --git a/UniTAP/dev/modules/capturer/__init__.py b/UniTAP/dev/modules/capturer/__init__.py new file mode 100644 index 0000000..ca06179 --- /dev/null +++ b/UniTAP/dev/modules/capturer/__init__.py @@ -0,0 +1,2 @@ +from .capture import Capturer, CaptureConfig, CaptureStatus, CaptureError, AudioCaptureStatus,\ + EventCaptureStatus, VideoCaptureStatus diff --git a/UniTAP/dev/modules/capturer/capture.py b/UniTAP/dev/modules/capturer/capture.py new file mode 100644 index 0000000..3a96b14 --- /dev/null +++ b/UniTAP/dev/modules/capturer/capture.py @@ -0,0 +1,550 @@ +import time +from typing import Union + +from UniTAP.libs.lib_tsi.tsi_io import DeviceIO +from UniTAP.libs.lib_tsi.tsi import * +from UniTAP.dev.modules.capturer.statuses import CaptureStatus, AudioCaptureStatus, EventCaptureStatus, \ + VideoCaptureStatus, BulkCaptureStatus +from UniTAP.common import AudioFrameData, VideoFrameDSC, create_from_pps +from UniTAP.dev.ports.modules.capturer.event.event_types import EventData +from threading import Lock +from UniTAP.utils import function_scheduler +from UniTAP.dev.ports.modules.capturer.bulk.private_bulk_types import * +from UniTAP.dev.ports.modules.capturer.bulk.bulk_types import * +from .types import * + + +TIMEOUT = 10 + + +class CaptureConfig: + class Type(IntEnum): + NONE = -1 + LIVE = 0 + BUFFERED = 1 + + def __init__(self): + self.audio = False + self.video = False + self.event = False + self.type = CaptureConfig.Type.NONE + self.frame_count = 0 + + def from_int(self, value: int): + self.video = bool(value & 1 << 3) + self.audio = bool(value & 1 << 4) + self.event = bool(value & 1 << 5) + self.type = CaptureConfig.Type.LIVE if value & (1 << 2) else CaptureConfig.Type.BUFFERED + self.frame_count = value >> 8 + + def to_int(self) -> int: + value = 0 + + if self.video: + value |= (1 << 3) + + if self.audio: + value |= (1 << 4) + + if self.event: + value |= (1 << 5) + + if self.video or self.audio: + if self.type == CaptureConfig.Type.LIVE: + value |= (1 << 2) + else: + value |= (self.frame_count << 8) + + return value + + +class Capturer: + + def __init__(self, port_io: DeviceIO): + self.__io = port_io + self.__config = CaptureConfig() + self.__status = CaptureStatus.Unknown + self.__mutex = Lock() + + @property + def config(self): + return self.__config + + @property + def status(self): + return self.__status + + @status.setter + def status(self, value: CaptureStatus): + self.__status = value + + @property + def device(self): + return self.__io + + @property + def video_capturer_status(self) -> VideoCaptureStatus: + return VideoCaptureStatus(self.__io.get(TSI_VIDCAP_CAPTURE_STATUS_R, c_int)[1]) + + @property + def audio_capturer_status(self) -> AudioCaptureStatus: + return AudioCaptureStatus(self.__io.get(TSI_R_AUDCAP_STATUS, c_int)[1]) + + @property + def event_capturer_status(self) -> EventCaptureStatus: + return EventCaptureStatus(self.__io.get(TSI_EVCAP_CTRL, c_uint32)[1] & 0xFF) + + @property + def bulk_capturer_status(self) -> BulkCaptureStatus: + return BulkCaptureStatus(self.__io.get(TSI_BULK_CAPTURE_STATUS_R, c_uint32)[1] & 0x3) + + def start_capture(self, config: CaptureConfig): + self.__mutex.acquire() + + self.__config.from_int(self.__current_config().to_int() | config.to_int()) + self.__io.set(TSI_CAP_CONFIG, self.__config.to_int()) + + if self.__io.set(TSI_W_CAP_COMMAND, 1) == 0: + self.__status = CaptureStatus.Running + else: + self.__status = CaptureStatus.Unknown + + self.__mutex.release() + + def stop_capture(self, config: CaptureConfig): + self.__mutex.acquire() + + self.__config.from_int(self.__current_config().to_int() & ~config.to_int()) + self.__io.set(TSI_CAP_CONFIG, self.__config.to_int()) + + if self.__io.set(TSI_W_CAP_COMMAND, 2) == 0: + self.__status = CaptureStatus.Stop + else: + self.__status = CaptureStatus.Unknown + + self.__mutex.release() + + def __capture_events(self, event_count=0): + event = EventData() + + if event_count > 0: + event_size = self.__io.get(TSI_R_EVCAP_DATA, None, 0)[0] + if event_size > 0: + event.data = bytearray(self.__io.get(TSI_R_EVCAP_DATA, c_ubyte, event_size)[1]) + + return event + + def get_buffer_capacity(self, stream_number: int = None): + if stream_number is not None: + self.__io.set(TSI_DPRX_STREAM_SELECT, stream_number, c_uint32) + self.__io.set(TSI_DPRX_MSA_COMMAND_W, 2, c_uint32) + + width = self.__io.get(TSI_R_INPUT_WIDTH , c_uint32)[1] + height = self.__io.get(TSI_R_INPUT_HEIGHT , c_uint32)[1] + total_memory_bytes = self.__io.get(TSI_MEMORY_SIZE_R, c_uint64)[1] + bpc = self.__io.get(TSI_INPUT_COLOR_DEPTH_R , c_uint32)[1] + color_format = self.__io.get(TSI_INPUT_COLOR_MODE_R , c_uint32)[1] + + bpp = self._get_bits_per_pixel(bpc, color_format) + line_alignment = 1023 + pixel_size = self._get_pixel_size(color_format, bpp) + line_size = width * pixel_size + line_pitch = self._align(line_size, line_alignment) + one_frame_size_bytes = (height + 1) * line_pitch + + if one_frame_size_bytes == 0: + return 0 + else: + return int(total_memory_bytes / one_frame_size_bytes) + + @staticmethod + def _get_bits_per_pixel(bpc, color_format) -> int: + if color_format in [0, 1, 6]: + return 0 + elif color_format in [2, 4, 9]: + return 3 * bpc + elif color_format == 3: + return 2 * bpc + elif color_format == 5: + return 3 * bpc / 2 + elif color_format == 7: + return bpc + elif color_format == 8: + return bpc + + @staticmethod + def _get_pixel_size(color_format, bpp): + if color_format == 5: + return 4 if bpp >= 18 else 2 + elif color_format in [7, 8]: + return 6 if bpp > 10 else 4 + else: + return 6 if bpp > 32 else 4 + + @staticmethod + def _align(value, alignment): + return (value + alignment) & ~alignment + + def get_available_events_count(self) -> int: + return self.__io.get(TSI_R_EVCAP_COUNT, c_uint32)[1] + + def capture_n_events(self, events_count: int): + if events_count <= 0: + raise ValueError(f"Events count must be more than 0.") + + buffer = [] + + def is_enough_events(): + return self.get_available_events_count() >= events_count + + function_scheduler(is_enough_events, interval=1, timeout=TIMEOUT) + + for i in range(events_count): + buffer.append(self.__capture_events(self.get_available_events_count())) + + return buffer + + def read_all_events(self): + buffer = [] + while self.get_available_events_count() > 0: + buffer.append(self.__capture_events(self.get_available_events_count())) + + return buffer + + def __capture_audio(self, m_sec=1000): + + audio_frame = AudioFrameData() + + audio_frame.channel_count = self.__io.get(TSI_R_AUDCAP_CHANNEL_COUNT, c_int)[1] + audio_frame.sample_size = self.__io.get(TSI_R_AUDCAP_SAMPLE_SIZE, c_int)[1] * 8 + audio_frame.sample_rate = self.__io.get(TSI_R_AUDCAP_SAMPLE_RATE, c_int)[1] + audio_frame.timestamp = self.__io.get(TSI_R_AUDCAP_TIMESTAMP, c_uint64)[1] + audio_frame.samples = self.__io.get(TSI_R_AUDCAP_SAMPLE_COUNT, c_int)[1] + audio_frame.frame_counter = self.__io.get(TSI_R_AUDCAP_FRAME_COUNTER, c_int)[1] + audio_frame.sample_format = self.__io.get(TSI_R_AUDCAP_SAMPLE_FORMAT, c_int)[1] + + min_buff_size = self.__io.get(TSI_R_AUDCAP_MIN_BUFFER_SIZE, c_uint32)[1] + audio_frame.data = self.__io.get(TSI_R_AUDCAP_SAMPLE_DATA, c_uint8, min_buff_size)[1] + + m_sec -= 1000 * len(audio_frame.data) / 2 / audio_frame.channel_count / audio_frame.sample_rate + + return audio_frame, m_sec + + def capture_audio_by_n_frames(self, frames_count: int, timeout: int = None): + if frames_count <= 0: + raise ValueError(f"Frames count must be more than 0.") + + buffer = [] + time_break = False + + timeout = timeout if timeout is not None else TIMEOUT + + while not time_break and len(buffer) < frames_count: + captured = 0 + start_time = time.time() + while captured < 10: + status = self.audio_capturer_status + current_time = time.time() + if current_time - start_time > timeout: + time_break = True + break + if status == AudioCaptureStatus.Stop: + continue + + audio_frame, m_sec = self.__capture_audio() + if len(audio_frame.data) > 0: + captured += 1 + buffer.append(audio_frame) + + return buffer + + def capture_audio_by_m_sec(self, m_sec: int): + if m_sec <= 0: + raise ValueError(f"Seconds count must be more than 0.") + + buffer = [] + time_break = False + + while m_sec > 0 and not time_break: + captured = 0 + start_time = time.time() + while captured < 10 and m_sec > 0: + status = self.audio_capturer_status + current_time = time.time() + if current_time - start_time > TIMEOUT: + time_break = True + break + if status == AudioCaptureStatus.Stop: + continue + + audio_frame, m_sec = self.__capture_audio(m_sec=m_sec) + if len(audio_frame.data) > 0: + captured += 1 + buffer.append(audio_frame) + + return buffer + + def get_available_video_frame_count(self): + return self.__io.get(TSI_VIDCAP_AVAILABLE_FRAME_COUNT, c_int)[1] + + def __check_available_video(self, timeout) -> bool: + def is_video_available(capturer): + return capturer.video_capturer_status == VideoCaptureStatus.LiveModeActive + + return function_scheduler(is_video_available, self, interval=1, timeout=timeout) + + def __check_available_buffered_video(self, timeout) -> bool: + def is_video_available(capturer): + return capturer.video_capturer_status == VideoCaptureStatus.Transferring + + return function_scheduler(is_video_available, self, interval=1, timeout=timeout) + + def __check_available_bulk_data(self, timeout) -> bool: + def is_bulk_available(capturer): + return capturer.bulk_capturer_status == BulkCaptureStatus.Transferring + + return function_scheduler(is_bulk_available, self, interval=1, timeout=timeout) + + def __capture_video(self, timeout=TIMEOUT, + capture_type=CaptureConfig.Type.LIVE) -> Union[VideoFrame, VideoFrameDSC]: + if timeout <= 0: + raise ValueError(f"Timeout must be more than 0.") + + if capture_type == CaptureConfig.Type.BUFFERED: + if not self.__check_available_buffered_video(timeout): + raise BufferedCaptureError( + f"Cannot get frames from buffer. " + f"Current buffered capture status is {self.video_capturer_status.name}" + ) + else: + if not self.__check_available_video(timeout): + raise CaptureError( + f"Cannot start to capture video. Current video capture status {self.video_capturer_status.name}") + + try: + result = self.__io.set(TSI_VIDCAP_CAPTURE_NEXT_W, 0) + if result == TSI_ERROR_DATA_PROTECTION_ENABLED: + raise CaptureError("Video data is HDCP protected. Capturing is not available.") + except AssertionError as e: + raise CaptureError(f"Error: {e}") + + try: + min_buffer_size = self.__io.get(TSI_R_VIDCAP_MIN_BUFFER_SIZE, c_int)[1] + if min_buffer_size <= 0: + raise ValueError("Minimum buffer size must be more than 0") + except AssertionError as e: + raise CaptureError(f"Error: {e}") + + try: + frame_data = bytearray(self.__io.get(TSI_R_VIDCAP_FRAME_DATA, c_uint8, min_buffer_size)[1]) + if len(frame_data) <= 0: + raise ValueError("Minimum length of captured data must be more than 0") + except AssertionError as e: + raise CaptureError(f"Error: {e}") + + frame_attributes = self.__io.get(TSI_VIDCAP_FRAME_HEADER_R, VideoFrameHeader)[1] + + if frame_attributes.is_dsc() and len(frame_data) > 128: + vf = VideoFrameDSC() + vf.compression_info = create_from_pps(frame_data[:128]) + else: + vf = VideoFrame() + + vf.width = frame_attributes.width + vf.height = frame_attributes.height + vf.color_info.bpc = frame_attributes.bpc + vf.color_info.dynamic_range = frame_attributes.dynamic_range + vf.color_info.color_format = frame_attributes.color_format + vf.color_info.colorimetry = frame_attributes.colorimetry + vf.data_info.component_order = DataInfo.ComponentOrder.CO_UCDRX + vf.data_info.alignment = DataInfo.Alignment.A_MSB + vf.data_info.packing = DataInfo.Packing.P_PACKED + vf.timestamp = frame_attributes.timestamp + vf.data = frame_data + + return vf + + def capture_video_by_n_frames(self, frames_count: int, capture_type: CaptureConfig.Type = CaptureConfig.Type.LIVE): + if frames_count <= 0: + raise ValueError(f"Frames count must be more than 0.") + + buffer = [] + + if capture_type == CaptureConfig.Type.BUFFERED: + timeout = max(10, round(0.006 * frames_count)) + try: + for i in range(frames_count): + buffer.append(self.__capture_video(timeout=timeout, capture_type=capture_type)) + except BufferedCaptureError as e: + return buffer + else: + for i in range(frames_count): + buffer.append(self.__capture_video()) + + return buffer + + def capture_video_by_n_sec(self, sec: int): + if sec <= 0: + raise ValueError(f"Seconds count must be more than 0.") + + buffer = [] + + time_start = time.time() + while time.time() - time_start < sec: + buffer.append(self.__capture_video()) + + return buffer + + def set_video_stream_number(self, number: int): + self.__mutex.acquire() + self.__io.set(TSI_DPRX_STREAM_SELECT, number, c_uint32) + self.__mutex.release() + + def __current_config(self) -> CaptureConfig: + config = CaptureConfig() + config.from_int(self.__io.get(TSI_CAP_CONFIG, c_uint)[1]) + return config + + # Capture CRC + def capture_crc(self, crc_frame_count: int = 1) -> List[tuple[int, int, int]]: + if crc_frame_count <= 0: + raise ValueError(f"Incorrect crc frame count: {crc_frame_count}") + + crc_values = self.__io.get(TSI_VIDCAP_SIGNAL_CRC_R, CrcStruct, crc_frame_count)[1] + crc_list = [] + if crc_frame_count == 1: + crc_list = [(crc_values.r, crc_values.g, crc_values.b)] + else: + [crc_list.append((crc.r, crc.g, crc.b)) for crc in crc_values] + return crc_list + + # Bulk Capturer + def read_bulk_capture_caps(self) -> CaptureCaps: + return self.__io.get(TSI_BULK_CAPTURE_CAPS_R, CaptureCaps)[1] + + def read_bulk_trigger_caps(self) -> int: + return self.__io.get(TSI_BULK_TRIGGER_CAPS_R, c_uint32)[1] + + def write_bulk_trigger_settings(self, trigger_mask: int, trigger_config: list, trigger_config_ext: list): + self.__io.set(TSI_BULK_TRIGGER_MASK_W, trigger_mask, c_uint32) + self.__io.set(TSI_BULK_TRIGGER_CONFIGURATION_W, trigger_config, c_uint32, data_count=len(trigger_config)) + self.__io.set(TSI_BULK_TRIGGER_CONFIGURATION_EXT_W, trigger_config_ext, c_uint32, + data_count=len(trigger_config_ext)) + + def write_bulk_size(self, size: int): + data = SBlock() + data.BLOCK = 0 + data.OFFSET = 0 + data.SIZE = size + self.__io.set(TSI_BULK_CAPTURE_BLOCK, data, SBlock) + + def write_encoding_type(self, value: EncodingTypeEnum): + self.__io.set(TSI_BULK_CAPTURE_TYPE, value.value, c_uint32) + + def read_encoding_type(self) -> EncodingTypeEnum: + return EncodingTypeEnum(self.__io.get(TSI_BULK_CAPTURE_TYPE, c_uint32)[1]) + + def write_lane_count(self, value: LaneCountEnum): + self.__io.set(TSI_BULK_CAPTURE_LANE_COUNT, value.value, c_uint32) + + def read_lane_count(self) -> LaneCountEnum: + return LaneCountEnum(self.__io.get(TSI_BULK_CAPTURE_LANE_COUNT, c_uint32)[1]) + + def write_bulk_gpio(self, gpio: bool): + self.__io.set(TSI_BULK_CAPTURE_GPIO_W, TSI_BULK_CAPTURE_GPIO_5BIT if gpio else TSI_BULK_CAPTURE_GPIO_OFF, + c_uint32) + + def write_bulk_trigger_position(self, position: int): + self.__io.set(TSI_BULK_TRIGGER_POS, position) + + def start_bulk_capture(self): + + self.__mutex.acquire() + + self.__io.set(TSI_EVCAP_CTRL, 1) + self.__io.set(TSI_BULK_CAPTURE_CONTROL_W, TSI_BULK_CAPTURE_START) + + self.__mutex.release() + + def stop_bulk_capture(self): + self.__mutex.acquire() + + self.__io.set(TSI_EVCAP_CTRL, 0) + self.__io.set(TSI_BULK_CAPTURE_CONTROL_W, TSI_BULK_CAPTURE_STOP) + + self.__mutex.release() + + def start_event_capture(self): + self.__mutex.acquire() + + self.__io.set(TSI_EVCAP_CTRL, 1) + self.__io.set(TSI_EVCAP_EVENT_SRC_EN, UCD_ALL_EVENTS) + + self.__mutex.release() + + def stop_event_capture(self): + self.__mutex.acquire() + + self.__io.set(TSI_EVCAP_CTRL, 0) + + self.__mutex.release() + + def clear_bulk_buffer(self): + max_time_waiting = 5 + value = -1 + time_waited = time.time() + + while value != TSI_BULK_STATUS_IDLE and time.time() - time_waited < max_time_waiting: + self.__io.set(TSI_BULK_CAPTURE_CLEAR_W, 0) + value = self.__io.get(TSI_BULK_CAPTURE_STATUS_R)[1] + + def bulk_capture(self, all_size: int, trigger_enabled: Optional[TriggerVarType]) -> list: + buffer = [] + iterations = int(all_size / (1024 * 1024)) + prev_status = 0 + last_bulk_capture_time = time.time() + for i in range(iterations): + event_count = self.__io.get(TSI_EVCAP_COUNT_R, c_uint32)[1] + if event_count > 0: + event_number = 0 + prev_timestamp = 0 + events_captured = 0 + while event_count > 0 and events_captured < 500: + event_size = self.__io.get(TSI_R_EVCAP_DATA, None, 0)[0] + if event_size > 0: + cap_data = CapturedData() + cap_data.data = bytearray(self.__io.get(TSI_R_EVCAP_DATA, c_ubyte, event_size)[1]) + cap_data.data = cap_data.data[3:] + cap_data.type = CapturedDataType.Event + cap_data.timestamp = int.from_bytes(bytes=cap_data.data[:8], byteorder='big') + if cap_data.timestamp == prev_timestamp: + event_number += 1 + else: + event_number = 0 + buffer.append(cap_data) + prev_timestamp = cap_data.timestamp + events_captured += 1 + event_count -= 1 + + bulk_status = self.__io.get(TSI_BULK_CAPTURE_STATUS_R, c_int32)[1] + if bulk_status == TSI_BULK_STATUS_IDLE and prev_status == TSI_BULK_STATUS_TRANSFERRING: + return buffer + prev_status = bulk_status + + now = time.time() + if trigger_enabled is not None and now - last_bulk_capture_time >= TIMEOUT: + return buffer + + if not self.__check_available_bulk_data(TIMEOUT): + return buffer + + self.__io.set(TSI_EVCAP_CTRL, 0) + cap_data = CapturedData() + cap_data.type = CapturedDataType.Bulk + result, data, _ = self.__io.get(TSI_BULK_CAPTURE_DATA_R, c_ubyte, 1024 * 1024) + cap_data.data = bytearray(data) + if result >= TSI_SUCCESS and len(cap_data.data) > 0: + buffer.append(cap_data) + last_bulk_capture_time = time.time() + + return buffer diff --git a/UniTAP/dev/modules/capturer/result_object.py b/UniTAP/dev/modules/capturer/result_object.py new file mode 100644 index 0000000..34e67e7 --- /dev/null +++ b/UniTAP/dev/modules/capturer/result_object.py @@ -0,0 +1,108 @@ +from UniTAP.common.timestamp import Timestamp + + +class ResultObject: + """ + The base class of all capture results. + Contains information about `start_capture_time`, `end_capture_time`, `timestamp` and `buffer` with captured data. + """ + def __init__(self): + self.__start_capture_time = 0 + self.__end_capture_time = 0 + self.__timestamp = Timestamp(0) + self.__buffer = [] + + @property + def start_capture_time(self) -> int: + """ + Return start capture time. + + Returns: + object of `int` type + """ + return self.__start_capture_time + + @property + def end_capture_time(self) -> int: + """ + Return end capture time. + + Returns: + object of `int` type + """ + return self.__end_capture_time + + @property + def timestamp(self) -> Timestamp: + """ + Return timestamp. + + Returns: + object of `Timestamp` type + """ + return self.__timestamp + + @property + def buffer(self) -> list: + """ + Return buffer with captured data. + + Returns: + object of list type + """ + return self.__buffer + + @buffer.setter + def buffer(self, value): + """ + Set data to buffer + + Args: + value - any type of object + """ + self.__buffer.append(value) + + @start_capture_time.setter + def start_capture_time(self, start_capture_time: int): + """ + Set start capture time. + + Args: + start_capture_time (int) - must be more than 0. + """ + if start_capture_time <= 0: + raise ValueError(f"Start capture time cannot be less than 0.") + self.__start_capture_time = start_capture_time + + @end_capture_time.setter + def end_capture_time(self, end_capture_time: int): + """ + Set end capture time. + + Args: + end_capture_time (int) - must be more than 0. + """ + if end_capture_time <= 0: + raise ValueError(f"End capture time cannot be less than 0.") + self.__end_capture_time = end_capture_time + + @timestamp.setter + def timestamp(self, timestamp: int): + """ + Set timestamp. + + Args: + timestamp (int) - must be more than 0. + """ + if timestamp <= 0: + raise ValueError(f"Timestamp cannot be less than 0.") + self.__timestamp.value = timestamp + + def clear(self): + """ + Clear all data. + """ + self.__start_capture_time = 0 + self.__end_capture_time = 0 + self.__timestamp = Timestamp(0) + self.__buffer = [] diff --git a/UniTAP/dev/modules/capturer/statuses.py b/UniTAP/dev/modules/capturer/statuses.py new file mode 100644 index 0000000..4d129b4 --- /dev/null +++ b/UniTAP/dev/modules/capturer/statuses.py @@ -0,0 +1,100 @@ +from enum import IntEnum + + +class CaptureStatus(IntEnum): + Unknown = -1 + Stop = 0 + Running = 1 + + def __str__(self): + if self.value == CaptureStatus.Unknown: + return f"Capture status: {self.value}" + elif self.value == CaptureStatus.Stop: + return f"Capture status: is not working" + elif self.value == CaptureStatus.Running: + return f"Capture status: working" + else: + return f"Capture status: Unknown state" + + +class BulkCaptureStatus(IntEnum): + Unknown = -1 + Idle = 0 + Waiting = 1 + Capturing = 2 + Transferring = 3 + + def __str__(self): + if self.value == BulkCaptureStatus.Unknown: + return f"Bulk capture status: {self.value}" + elif self.value == BulkCaptureStatus.Idle: + return f"Bulk capture status: Doing nothing" + elif self.value == BulkCaptureStatus.Waiting: + return f"Bulk capture status: Waiting" + elif self.value == BulkCaptureStatus.Capturing: + return f"Bulk capture status: Capturing in progress" + elif self.value == BulkCaptureStatus.Transferring: + return f"Bulk capture status: Transferring" + else: + return f"Bulk capture status: Unknown state" + + +class AudioCaptureStatus(IntEnum): + Unknown = -1 + Stop = 0 + Running = 1 + + def __str__(self): + if self.value == AudioCaptureStatus.Unknown: + return f"Audio capture status: {self.value}" + elif self.value == AudioCaptureStatus.Stop: + return f"Audio capture status: is not working" + elif self.value == AudioCaptureStatus.Running: + return f"Audio capture status: working" + else: + return f"Video capture status: Unknown state" + + +class VideoCaptureStatus(IntEnum): + Unknown = -1 + Idle = 0 # Doing nothing + Capturing = 1 # Capturing in progress + Transferring = 2 # Transferring in progress + LiveModeActive = 3 # Live mode active + Malfunction = 4 # Malfunction (requires restart) + Done = 7 # Capturing and transferring done + + def __str__(self): + if self.value == VideoCaptureStatus.Unknown: + return f"Video capture status: {self.value}" + elif self.value == VideoCaptureStatus.Idle: + return f"Video capture status: Doing nothing" + elif self.value == VideoCaptureStatus.Capturing: + return f"Video capture status: Capturing in progress" + elif self.value == VideoCaptureStatus.Transferring: + return f"Video capture status: Transferring in progress" + elif self.value == VideoCaptureStatus.LiveModeActive: + return f"Video capture status: Live mode active" + elif self.value == VideoCaptureStatus.Malfunction: + return f"Video capture status: Malfunction (requires restart)" + elif self.value == VideoCaptureStatus.Done: + return f"Video capture status: Capturing and transferring done" + else: + return f"Video capture status: Unknown state" + + +class EventCaptureStatus(IntEnum): + Unknown = -1 + Stop = 0 + Running = 1 + Invalid = 0xFF + + def __str__(self): + if self.value == EventCaptureStatus.Unknown: + return f"Event capture status: {self.value}" + elif self.value == EventCaptureStatus.Stop: + return f"Event capture status: is not working" + elif self.value == EventCaptureStatus.Running: + return f"Event capture status: working" + else: + return f"Video capture status: Unknown state" diff --git a/UniTAP/dev/modules/capturer/types.py b/UniTAP/dev/modules/capturer/types.py new file mode 100644 index 0000000..12c8b83 --- /dev/null +++ b/UniTAP/dev/modules/capturer/types.py @@ -0,0 +1,180 @@ +from UniTAP.common.timestamp import Timestamp +from ctypes import c_uint8, c_ubyte, c_uint16, c_uint32, c_uint64, Structure +from enum import IntEnum +from UniTAP.common import ColorInfo, DataInfo, VideoFrame + + +class CapturedDataType(IntEnum): + Unknown = 0 + Video = 1 + Audio = 2 + Event = 3 + Bulk = 4 + + +class CapturedData: + + def __init__(self): + self.frame_number = 0 + self.timestamp = 0 + self.width = 0 + self.height = 0 + self.dataFormat = 0 + self.colorimetry = 0 + self.colorMode = 0 + self.bpc = 0 + self.type = 0 + self.data = 0 + + +class CrcStruct(Structure): + _fields_ = [ + ("r", c_uint16), + ("g", c_uint16), + ("b", c_uint16), + ] + + +class VideoFrameHeader(Structure): + __BPC_PA_TO_INT = { + 0: 6, + 1: 8, + 2: 10, + 3: 12, + 4: 16, + 5: 7, + 6: 14 + } + + __PA_PACKING_TO_VF_PACKING = { + 0: DataInfo.Packing.P_PLANAR, + 1: DataInfo.Packing.P_PLANAR + } + + __PA_CF_TO_CI_CF = { + 0: ColorInfo.ColorFormat.CF_RGB, + 1: ColorInfo.ColorFormat.CF_YCbCr_422, + 2: ColorInfo.ColorFormat.CF_YCbCr_444, + 3: ColorInfo.ColorFormat.CF_YCbCr_420, + 4: ColorInfo.ColorFormat.CF_Y_ONLY, + 5: ColorInfo.ColorFormat.CF_RAW, + 6: ColorInfo.ColorFormat.CF_DSC, + 7: ColorInfo.ColorFormat.CF_IDO_DEFINED + } + __PA_CR_TO_CI_CR = { + 0: ColorInfo.Colorimetry.CM_NONE, + 1: ColorInfo.Colorimetry.CM_SMPTE_170M, + 2: ColorInfo.Colorimetry.CM_ITUR_BT709, + 3: ColorInfo.Colorimetry.CM_NONE + } + + __PA_EXT_CR_TO_CI_CR = { + 0: ColorInfo.Colorimetry.CM_xvYCC601, + 1: ColorInfo.Colorimetry.CM_xvYCC709, + 2: ColorInfo.Colorimetry.CM_sYCC601, + 3: ColorInfo.Colorimetry.CM_AdobeYCC601, + 4: ColorInfo.Colorimetry.CM_AdobeRGB, + 5: ColorInfo.Colorimetry.CM_ITUR_BT2020_YcCbcCrc, + 6: ColorInfo.Colorimetry.CM_ITUR_BT2020_RGB, + 7: ColorInfo.Colorimetry.CM_ITUR_BT601 + } + + class PixelAttributes(Structure): + _fields_ = [ + ("component_format", c_uint32, 4), + ("", c_uint32, 1), + ("bpc", c_uint32, 3), + ("video_mode", c_uint32, 1), + ("stereo_mode", c_uint32, 1), + ("field_id", c_uint32, 1), + ("stereo_id", c_uint32, 1), + ("blanked", c_uint32, 1), + ("encrypted", c_uint32, 1), + ("pixel_packing_format", c_uint32, 2), + ("color_format", c_uint32, 3), + ("dynamic_range", c_uint32, 1), + ("colorimetry", c_uint32, 2), + ("ext_colorimetry", c_uint32, 3), + ("", c_uint32, 1), + ("dsc_compressed", c_uint32, 1), + ("ext_color_format", c_uint32, 2), + ("video_footer", c_uint32, 1), + ("video_packet_crc", c_uint32, 1), + ] + + _fields_ = [ + ('sync', c_uint32), + ('number', c_uint32), + ('', c_uint32), + ('size_words', c_uint32), + ('frame_timestamp', c_uint64, 62), + ('time_unit', c_uint64, 2), + ('attributes', PixelAttributes), + ("f_width", c_uint32, 16), + ("f_height", c_uint32, 16) + ] + + @property + def bpc(self) -> int: + return self.__BPC_PA_TO_INT.get(self.attributes.bpc, 0) + + @property + def dynamic_range(self) -> ColorInfo.DynamicRange: + if self.attributes.dynamic_range: + return ColorInfo.DynamicRange.DR_CTA + else: + return ColorInfo.DynamicRange.DR_VESA + + @property + def color_format(self) -> ColorInfo.ColorFormat: + return self.__PA_CF_TO_CI_CF.get(self.attributes.color_format, + ColorInfo.ColorFormat.CF_UNKNOWN) + + @property + def colorimetry(self) -> ColorInfo.Colorimetry: + if self.attributes.colorimetry == 3: + return self.__PA_EXT_CR_TO_CI_CR.get(self.attributes.ext_colorimetry, + ColorInfo.Colorimetry.CM_NONE) + else: + return self.__PA_CR_TO_CI_CR.get(self.attributes.colorimetry, + ColorInfo.Colorimetry.CM_NONE) + + @property + def timestamp(self) -> Timestamp: + value = self.frame_timestamp + value &= 0x7FFFFFFFFFFFFFFF + value *= 100 if (self.time_unit & 0x1) else 1000 + return Timestamp(value) + + @property + def packing(self) -> DataInfo.Packing: + return self.__PA_PACKING_TO_VF_PACKING.get(self.attributes.pixel_packing_format, + DataInfo.Packing.P_UNKNOWN) + + def is_dsc(self) -> bool: + return self.attributes.color_format == 6 + + @property + def width(self): + if self.color_format == ColorInfo.ColorFormat.CF_YCbCr_420: + return self.f_width << 1 + else: + return self.f_width + + @property + def height(self): + return self.f_height + + +class CaptureError(Exception): + + def __init__(self, message: str): + self.__message = message + super().__init__(self.__message) + + +class BufferedCaptureError(Exception): + + def __init__(self, message: str): + self.__message = message + super().__init__(self.__message) \ No newline at end of file diff --git a/UniTAP/dev/modules/capturer/utils.py b/UniTAP/dev/modules/capturer/utils.py new file mode 100644 index 0000000..77e46f0 --- /dev/null +++ b/UniTAP/dev/modules/capturer/utils.py @@ -0,0 +1 @@ +from UniTAP.libs.lib_uicl.uicl_utils import * diff --git a/UniTAP/dev/modules/dut_tests/__init__.py b/UniTAP/dev/modules/dut_tests/__init__.py new file mode 100644 index 0000000..0883ad8 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/__init__.py @@ -0,0 +1,4 @@ +from .dut_tests import DUTTests, TestGroupId, SubTestResultObject, TestResultObject, TestResult, \ + PackedTimings1Lane, PackedTimings2Lane, PackedTimings4Lane, EventIndication +from .dut_default_params import * +from .report import * diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x01]src_dut_crc.json b/UniTAP/dev/modules/dut_tests/cfg/[0x01]src_dut_crc.json new file mode 100644 index 0000000..78ebca5 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x01]src_dut_crc.json @@ -0,0 +1,195 @@ +{ + "descriptions": [ + { + "configId": "0x10300", + "defaultValue": "10000", + "description": "Defines timeout for all CRC based video tests, in milliseconds. Default setting is 10000ms.\n", + "flag": 3, + "id": "TSI_CRC_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout, in milliseconds", + "type": 4 + }, + { + "configId": "0x10301", + "defaultValue": "200", + "description": "Total number of frames to be tested. After calculation of CRC for total number of frames test will be completed. If total number is equal to 0, then test will be executing up to test timeout. Default setting is 200.\n", + "flag": 3, + "id": "TSI_CRC_FRAMES_TO_TEST", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test duration, in frames", + "type": 4 + }, + { + "configId": "0x10302", + "defaultValue": "20", + "description": "Number of reference frames. Default setting is 20.\n", + "flag": 3, + "id": "TSI_CRC_REF_FRAME_COUNT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Reference frames", + "type": 4 + }, + { + "configId": "0x10303", + "defaultValue": "20", + "description": "Number of bad frames allowed in single CRC tests. Default setting is 20\n", + "flag": 3, + "id": "TSI_CRC_LIM_FRAME_MISMATCHES", + "maxValue": 2147483647, + "minValue": 0, + "name": "Allowed mismatches, in frames", + "type": 4 + }, + { + "configId": "0x10304", + "defaultValue": "1920", + "description": "Defined the expected video width, in pixels. If the video being received does not match this setting, the test will fail. Default setting is 1920.\n", + "flag": 3, + "id": "TSI_CRC_REF_WIDTH", + "maxValue": 2147483647, + "minValue": 0, + "name": "Expected video width, in pixels", + "type": 4 + }, + { + "configId": "0x10305", + "defaultValue": "1080", + "description": "Defines the expected video height, in pixels. If the video being received does not match this setting, the test will fail. Default setting is 1080.\n", + "flag": 3, + "id": "TSI_CRC_REF_HEIGHT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Expected video weight, in pixels", + "type": 4 + }, + { + "configId": "0x10306", + "defaultValue": "24", + "description": "Defines the color depth as bits per pixel. If the input video color depth does not match this setting, the test will fail. Default setting is 24.\n", + "enumerationVariants": "12\n15\n16\n18\n20\n21\n24\n30\n32\n36\n48", + "flag": 3, + "id": "TSI_CRC_REF_COLORDEPTH", + "maxValue": 2147480000, + "minValue": 0, + "name": "Expected color depth, as bits per pixel", + "type": 4 + }, + { + "configId": "0x10307", + "defaultValue": "0", + "description": "Defines the required frame rate for CRC based tests, in millihertz. Setting of zero (0) disables the frame-rate requirement. Default setting is 0.\n", + "flag": 3, + "id": "TSI_CRC_REQUIRED_FRAME_RATE", + "maxValue": 2147483647, + "minValue": 0, + "name": "Expected frame rate, in millihertz (mHz)", + "type": 4 + }, + { + "configId": "0x10308", + "defaultValue": "0", + "description": "Defines the maximum allowed deviation of input frame-rate from the required frame rate (TSI_CRC_REQUIRED_FRAME_RATE), in millihertz. When this setting is non-zero, it defines the range of allowed frame rate as requirements ± tolerance. If the frame-rate requirement is set to zero, this setting has no effect. Default setting is 0 mHz.\n", + "flag": 3, + "id": "TSI_CRC_FRAME_RATE_TOLERANCE", + "maxValue": 2147483647, + "minValue": 0, + "name": "Frame rate tolerance, in millihertz (mHz)", + "type": 4 + }, + { + "configId": "0x10309", + "defaultValue": [46750, 45886, 6835], + "description": "Contains CRC reference values. Each CRC set consists of 3 16-bit words; One word for each color channel. Red / Cr color channel CRC is at the lowest address (first word), followed by Green / Y channel (second word) and then Blue / Cb channel (third word). Maximum number of CRC value sets is 65535. Default CRC set is empty (=no default value).", + "flag": 3, + "id": "TSI_CRC_REFERENCE_CRC_VALUES", + "name": "Provided CRC value sets", + "type": 6 + }, + { + "configId": "0x1030c", + "defaultValue": "1", + "description": "Defines the number of iterations the defined CRC sequence must be found in order to pass the test. Default is 1.\n", + "flag": 3, + "id": "TSI_CRC_MOTION_TEST_ITERATIONS", + "maxValue": 2147483647, + "minValue": 0, + "name": "Motion test iterations (# loops)", + "type": 4 + }, + { + "configId": "0x1030d", + "defaultValue": "0", + "description": "Color format. Default setting is 0.\n", + "flag": 3, + "id": "TSI_CRC_COLOR_FORMAT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Color format", + "type": 4 + }, + { + "configId": "0x10317", + "defaultValue": "0", + "description": "Data transfer timeout in milliseconds. Default setting is 0.\n", + "flag": 3, + "id": "TSI_CRC_DATA_TRANSFER_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Data transfer timeout.", + "type": 4 + }, + { + "configId": "0x10320", + "defaultValue": "", + "description": "Contains the full path to the folder where failed frames are to be saved without trailing backslash (‘\\’). No default. Failed frame file-name will be “Failed_<#>.ppm”, where <#> is replaced with an auto-incremented number.", + "flag": 3, + "id": "TSI_CRC_FAILED_FRAME_TARGET_FOLDER", + "maxLength": 10000, + "name": "Location where the failed frames are to be saved", + "type": 11 + }, + { + "configId": "0x10322", + "defaultValue": "0", + "description": "Defines the number of failed frames to be exported from the video test. Default setting is 0. If the setting is 0, no frames are exported.", + "flag": 3, + "id": "TSI_CRC_MAX_EXPORT_FAILED", + "maxValue": 2147483647, + "minValue": 0, + "name": "Maximum number of exported frames", + "type": 4 + }, + { + "configId": "0x10323", + "defaultValue": "0", + "description": "Export format", + "flag": 3, + "enumerationVariants": "0 # Binary file\n1 # PPM image\n2 # BMP image", + "maxValue": 2147483647, + "minValue": 0, + "id": "TSI_CRC_EXPORT_FORMAT", + "maxLength": 10000, + "name": "Export format", + "type": 4 + }, + { + "configId": "0x10321", + "defaultValue": "0", + "description": "Align 12", + "flag": 3, + "id": "TSI_CRC_ALIGN_12", + "mask": 1, + "name": "Align 12", + "type": 3 + } + ], + "id": "0x01", + "name": "CRC Video Tests", + "old_names": [ + "CRC based Video Test Set" + ] +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x02]src_dut_audio.json b/UniTAP/dev/modules/dut_tests/cfg/[0x02]src_dut_audio.json new file mode 100644 index 0000000..3d668f8 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x02]src_dut_audio.json @@ -0,0 +1,86 @@ +{ + "descriptions": [ + { + "configId": "0x2020", + "defaultValue": "44100", + "description": "Sample rate that should be present when running the test, in Hz. Default setting is 44100 Hz. If the audio stream sample rate does not match, the test will result fail.", + "flag": 3, + "id": "TSI_EXPECTED_SAMPLE_RATE", + "maxValue": 2147483647, + "minValue": 0, + "name": "Expected sampling rate of audio signal", + "type": 4 + }, + { + "configId": "0x2021", + "defaultValue": "1000", + "description": "Expected audible signal frequency that should be present when running the test, in Hz. Default setting is 1000 Hz.", + "flag": 3, + "id": "TSI_EXPECTED_AUDIO_FREQUENCY", + "maxValue": 2147483647, + "minValue": 0, + "name": "Expected audible (sine) frequency as Hz", + "type": 4 + }, + { + "configId": "0x2022", + "defaultValue": "1", + "description": "Maximum allowed frequency deviation for the audible signal from the reference frequency, in Hz. Default setting is 1 Hz.", + "flag": 3, + "id": "TSI_AUDIO_FREQUENCY_TOLERANCE", + "maxValue": 2147483647, + "minValue": 0, + "name": "Allowed deviation from expected frequency as Hz", + "type": 4 + }, + { + "configId": "0x2023", + "defaultValue": "5", + "description": "This value defines the accepted RDV range by adding/subtracting it from the calculated base RDV when performing glitch detection. Lower values mean more sensitive to glitches – please note that setting this value too low will cause even perfectly good signal to fail the test. Valid range for this setting is 0 to 32767.0; The default setting is 5.0 (327680 scaled). Important: FIXED POINT ENCODING. When setting this value parameter, the value being set must be multiplied by 65536 and set as a 32-bit integer. When reading the value, the received value must be divided by 65536 and shown as a floating point quantity.", + "flag": 3, + "id": "TSI_AUDIO_GLITCH_DETECT_TRESHOLD", + "maxValue": 100, + "minValue": 0, + "name": "The percentage deviation from the ideal sine", + "type": 4 + }, + { + "configId": "0x2024", + "defaultValue": "0", + "description": "Defines how many glitches are allowed before the audio test is considered failed. Default setting is 0. Important: Due to implementation specific characteristics, a single (but very audible) glitch is probably detected multiple times. The number of times a glitch is detected depends greatly on the severity of the glitch, and it's location respective to the sine waveform. Because of this, setting a non-zero but very low value may not make sense.", + "flag": 3, + "id": "TSI_AUDIO_GLITCHES_ALLOWED", + "maxValue": 2147483647, + "minValue": 0, + "name": "Number of audio glitches allowed per test", + "type": 4 + }, + { + "configId": "0x2025", + "defaultValue": "0", + "description": "Maximum number of frames failed frames saved per test run. Default setting is 0. If the setting is “0”, no frames are saved.", + "flag": 3, + "enumerationVariants": "0 # Save none\n1 # Save failed\n2 # Save all", + "id": "TSI_AUDIO_TEST_SAVE_CONDITIONS", + "maxValue": 2147483647, + "minValue": 0, + "name": "Tested audio save conditions", + "type": 4 + }, + { + "configId": "0x2026", + "defaultValue": "", + "description": "Contains the full path to the folder where failed frames are to be saved without trailing backslash (‘\\’). No default. Failed frame file-name will be “Failed_<#>.ppm”, where <#> is replaced with an auto-incremented number.", + "flag": 3, + "id": "TSI_AUDIO_TEST_STORAGE_FOLDER", + "maxLength": 10000, + "name": "Location where the captured audio is to be saved", + "type": 11 + } + ], + "id": "0x02", + "name": "Audio Test", + "old_names": [ + "Validate audio signal frequency and glitch-free audio reproduction" + ] +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x03]src_dut_dp_8b10b_ll_cts.json b/UniTAP/dev/modules/dut_tests/cfg/[0x03]src_dut_dp_8b10b_ll_cts.json new file mode 100644 index 0000000..6717eb2 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x03]src_dut_dp_8b10b_ll_cts.json @@ -0,0 +1,2041 @@ +{ + "descriptions": [ + { + "configId": "0x10e0000", + "defaultValue": "5000", + "description": "Defines the timeout for DP LL CTS Source DUT tests. The timeout is defined as milliseconds, and has a default setting of 5000ms. Please note that if the test contains internal iterations, this timeout is re-used for each iteration. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP14_SRCCTS_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout in milliseconds.", + "type": 4 + }, + { + "configId": "0x10e0001", + "defaultValue": "4", + "description": "Defines the maximum number of lanes supported by the DUT. Typical settings are 1, 2 and 4. Default value is 4. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.\n", + "enumerationVariants": "1\n2\n4", + "flag": 3, + "id": "TSI_DP14_SRCCTS_MAX_LANES", + "maxValue": 2147480000, + "minValue": 0, + "name": "Maximum number of lanes supported by the DUT.", + "type": 4 + }, + { + "configId": "0x10e0002", + "defaultValue": "30", + "description": "Defines the maximum link rate as multiplier for 0.27Gbps. Typical settings are 6 (RBR), 10 (HBR), 20 (HBR2) and 30 (HBR3). Notice that HBR3 link rate is usable only with UCD-400. The default setting is 30 (HBR3). Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "6 # RBR (1.62 Gbps)\n10 # HBR (2.7 Gbps)\n20 # HBR2 (5.4 Gbps)\n30 # HBR3 (8.10 Gbps)", + "flag": 3, + "id": "TSI_DP14_SRCCTS_MAX_LINK_RATE", + "maxValue": 30, + "minValue": 6, + "name": "Maximum link rate supported by the DUT as multiplier for 0.27Gbps.", + "type": 4 + }, + { + "configId": "0x10e0003", + "defaultValue": "0x00F03A53", + "description": "Defines the DUT capabilities as flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "Voltage swing level 3 (1.2V) supported"}, + {"mask": "0x00000002", "description": "Pre-emphasis level 3 (9.5dB) supported"}, + {"mask": "0x00000004", "description": "Fixed timing DUT supported"}, + {"mask": "0x00000008", "description": "Spread Spectrum support"}, + {"mask": "0x00000010", "description": "Video format change without LT supported"}, + {"mask": "0x00000020", "description": "Lane count reduction without LT supported"}, + {"mask": "0x00000040", "description": "E-DDC protocol supported"}, + {"mask": "0x00000080", "description": "Audio Info Frame for 2 channel audio transmission supported"}, + {"mask": "0x00000100", "description": "DUT is Type-C device"}, + {"mask": "0x00000200", "description": "FEC supported"}, + {"mask": "0x00000400", "description": "FEC disable sequence supported"}, + {"mask": "0x00000800", "description": "Audio without Video supported"}, + {"mask": "0x00001000", "description": "DSC supported"}, + {"mask": "0x00002000", "description": "DSC block prediction supported"}, + {"mask": "0x00080000", "description": "DUT supports native Display ID read"}, + {"mask": "0x00100000", "description": "DisplayID Type VII Detailed Timing Descriptor supported"}, + {"mask": "0x00200000", "description": "DisplayID Type VIII Detailed Timing Descriptor supported"}, + {"mask": "0x00400000", "description": "DisplayID Type IX Detailed Timing Descriptor supported"}, + {"mask": "0x00800000", "description": "DisplayID Type X Detailed Timing Descriptor supported"}, + {"mask": "0x01000000", "description": "2x1 tiled display and DisplayID Tiled Display Topology data block supported"}, + {"mask": "0x02000000", "description": "Field sequential stereo and DisplayID Tiled Stereo Display Interface data block supported"}, + {"mask": "0x04000000", "description": "Stacked frame stereo and DisplayID Tiled Stereo Display Interface data block supported"} + ], + "flag": 3, + "id": "TSI_DP14_SRCCTS_DUT_CAPS", + "name": "DUT Capability flags.", + "type": 12 + }, + { + "configId": "0x10e0004", + "defaultValue": "0x0000001A", + "description": "Defines the DUT Test automation capabilities as flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "TEST_LINK_TRAINING"}, + {"mask": "0x00000002", "description": "TEST_EDID_READ"}, + {"mask": "0x00000004", "description": "TEST_VIDEO_PATTERN"}, + {"mask": "0x00000020", "description": "TEST_AUDIO_PATTERN"}, + {"mask": "0x00000018", "description": "Event indicating DUT ready", "enumerationVariants": "0 # Always ready\n1 # EDID read\n2 # Link Training end\n3 # Active Video"} + ], + "flag": 3, + "id": "TSI_DP14_SRCCTS_DUT_TA", + "name": "DUT Test automation flags.", + "type": 12 + }, + { + "configId": "0x10e0005", + "defaultValue": "1000", + "description": "Defines the duration of long HPD pulses generated by the tests. The duration is defined in millieconds. Default setting is 1000ms. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.\n", + "flag": 3, + "id": "TSI_DP14_SRCCTS_LONG_HPD_PULSE", + "maxValue": 2147483647, + "minValue": 0, + "name": "Long HPD pulse duration, in milliseconds.", + "type": 4 + }, + { + "configId": "0x10e0006", + "defaultValue": "5000", + "description": "Defines a timeout for Link Training start, in milliseconds. Default setting is 5000ms. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP14_SRCCTS_LT_START_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Link Training start timeout, in milliseconds.", + "type": 4 + }, + { + "configId": "0x10e0007", + "defaultValue": "5000", + "description": "Defines an internal cycle delay used within tests that repeat a number of test steps. The delay is defined in milliseconds. Default setting is 3000ms. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_TEST_CYCLE_DELAY", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test cycle delay, in milliseconds", + "type": 4 + }, + { + "configId": "0x10e0008", + "defaultValue": "2", + "description": "Defines number of color formats programmed into the CI’s TSI_DP14_SRCCTS_COLOR_MODE_0 to TSI_DP14_SRCCTS_COLOR_MODE_31. The default setting is 2. Important: The CTS Specification defines two mandatory color formats: 0x0 and 0x20. These color-format definitions must be present in the color mode table. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_FORMATS", + "maxValue": 2147483647, + "minValue": 0, + "name": "Number of valid color format definitions.", + "type": 4 + }, + { + "configId": "0x10e0009", + "defaultValue": "1", + "description": "Defines the fail-safe mode ID. The default setting is 1 ( = 640 x 480 @ 60 Hz, 6 BPC). Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This setting should not be modified. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "enumerationVariants": "1 # 640 x 480 @ 60Hz 6 BPC", + "flag": 3, + "id": "TSI_DP14_SRCCTS_FAILSAFE_MODE", + "maxValue": 2147480000, + "minValue": 0, + "name": "Fail-safe video mode ID.", + "type": 4 + }, + { + "configId": "0x10e000a", + "defaultValue": "22", + "description": "Defines the maximum resolution supported by the DUT. Default setting is 22 ( = 1920 x 1080 @ 60Hz, 8 BPC). Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "1 # 640 x 480 @ 60Hz 6 BPC\n2 # 848 x 480 @ 60Hz 8 BPC\n3 # 1280 x 720 @ 60Hz 8 BPC\n4 # 1280 x 960 @ 60Hz 8 BPC\n5 # 1920 x 1080 @ 60Hz 8 BPC\n6 # 1920 x 1440 @ 60Hz 8 BPC\n7 # 1920 x 1080 @ 120Hz 8 BPC\n8 # 1280 x 800 @ 60Hz RB1 6 BPC\n9 # 1280 x 768 @ 60Hz RB1 6 BPC\n10 # 800 x 600 @ 60Hz 10 BPC\n11 # 1024 x 768 @ 60Hz 10 BPC\n12 # 1280 x 1024 @ 60Hz 8 BPC\n13 # 1360 x 768 @ 60Hz 10 BPC\n14 # 1280 x 800 @ 60Hz RB1 10 BPC\n15 # 1400 x 1050 @ 60Hz RB1 8 BPC\n16 # 1280 x 768 @ 60Hz RB1 10 BPC\n17 # 1600 x 1200 @ 60Hz RB1 6 BPC\n18 # 2048 x 1536 @ 60Hz RB1 8 BPC\n19 # 1792 x 1344 @ 60Hz 8 BPC\n20 # 1600 x 1200 @ 60Hz RB1 10 BPC\n21 # 3840 x 2160 @ 30Hz 8 BPC\n22 # 3840 x 2160 @ 60Hz 8 BPC", + "flag": 3, + "id": "TSI_DP14_SRCCTS_MAX_RESOLUTION", + "maxValue": 2147480000, + "minValue": 0, + "name": "Maximum resolution supported by the DUT.", + "type": 4 + }, + { + "configId": "0x10e000b", + "defaultValue": "0x00000001", + "description": "Defines link rates supported by DUT as it defined in the DP 2.0 specification.\n ", + "flag": 3, + "bitList": + [ + {"mask": "0x00000001", "description": "10 Gbps per lane support. Default setting is 1."}, + {"mask": "0x00000002", "description": "20 Gbps per lane support. Default setting is 0."}, + {"mask": "0x00000004", "description": "13.5 Gbps per lane support. Default setting is 0."} + ], + "id": "TSI_DP14_SRCCTS_DUT_LINK_RATES", + "name": "Link rates supported by DUT", + "type": 12 + }, + { + "configId": "0x10e000c", + "defaultValue": "0", + "description": "Defines number of LTTPR devices present between Source DUT and Reference Sink. Up to the eight devices. Default setting is 0.\n ", + "flag": 3, + "id": "TSI_DP14_SRCCTS_LTTPR_DEVICE_COUNT", + "maxValue": 8, + "minValue": 0, + "name": "Number of LTTPR devices present between Source DUT and Reference Sink", + "type": 4 + }, + { + "configId": "0x10e000e", + "defaultValue": "0x00000000", + "description": "Defines the Debug mode configuration.", + "bitList": + [ + {"mask": "0x00000001", "description": "Continue on fail enabled"} + ], + "flag": 3, + "id": "TSI_DP14_SRCCTS_DUT_DEBUG_CONF", + "name": "Debug mode configuration.", + "type": 12 + }, + { + "configId": "0x10e0010", + "defaultValue": "32000", + "description": "Defines the minimum sample rate supported by the DUT in Hz. Allowed values are 32000, 44100, 48000, 88200, 96000, 176400 or 192000 Hz. Default setting is 44100 Hz.\n ", + "enumerationVariants": "32000\n44100\n48000\n88200\n96000\n176400\n192000", + "flag": 3, + "id": "TSI_DP14_SRCCTS_MIN_SAMPLE_RATE", + "maxValue": 2147480000, + "minValue": 0, + "name": "Minimum supported sample rate", + "type": 4 + }, + { + "configId": "0x10e0011", + "defaultValue": "192000", + "description": "Defines the maximum sample rate supported by the DUT in Hz. Allowed values are 32000, 44100, 48000, 88200, 96000, 176400 or 192000 Hz. Default setting is 44100 Hz.\n ", + "enumerationVariants": "32000\n44100\n48000\n88200\n96000\n176400\n192000", + "flag": 3, + "id": "TSI_DP14_SRCCTS_MAX_SAMPLE_RATE", + "maxValue": 2147480000, + "minValue": 0, + "name": "Maximum supported sample rate", + "type": 4 + }, + { + "configId": "0x10e0012", + "defaultValue": "2", + "description": "Defines the minimum number of channels for minimum sample rate. Default is 2 channels (Stereo). Highest valid channel count is 8. If the number is different from 2 channels, then the appropriate channel allocation must be provided through the CI 7.9.19 TSI_DP14_SRCCTS_CH_ALLOC1.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_CHANNELS1", + "maxValue": 8, + "minValue": 0, + "name": "Minimum number of channels for minimum sample rate", + "type": 4 + }, + { + "configId": "0x10e0013", + "defaultValue": "1", + "description": "The audio parameters contain four pairs of audio channel count and channel allocation values. Please notice that maximum channel count allowed is 8. For two channels, the channel allocation of zero is considered valid; In this case, stereo is assumed (FL/FR).\nAllocation value\nChannel count/bit\nChannel assignments\n0x001 2 FL/FR. (Front Left, Front Right)\n0x002 1 LFE. (Low Frequency Effects)\n0x004 1 FC. (Front Center) \n0x008 2 RL/RR. (Rear Left, Rear Right).\n0x010 1 RC. (Rear Center). \n0x020 2 FLC/FRC. (Front Left off Center, Front Right off Center).\n0x040 2 RLC/RRC (Rear Left off Center, Rear Right off Center). \n0x080 2 FLW/FRW (Front Left Wide, Front Right Wide) \n0x100 2 FLH/FRH (Front Left High, Front Right High)\n0x200 1 TC (Top Center) \n0x400 1 FCH (Front Center High)\n", + "flag": 3, + "id": "TSI_DP14_SRCCTS_CH_ALLOC1", + "maxValue": 2147480000, + "minValue": 0, + "name": "Channel allocation bits", + "type": 4 + }, + { + "configId": "0x10e0014", + "defaultValue": "6", + "description": "Defines the maximum number of channels for minimum sample rate. Default is 2 channels (Stereo). Highest valid channel count is 8. If the number is different from 2 channels, then the appropriate channel allocation must be provided through the CI 7.9.21 TSI_DP14_SRCCTS_CH_ALLOC2.\n ", + "flag": 3, + "id": "TSI_DP14_SRCCTS_CHANNELS2", + "maxValue": 8, + "minValue": 0, + "name": "Maximum number of channels for minimum sample rate", + "type": 4 + }, + { + "configId": "0x10e0015", + "defaultValue": "15", + "description": "The audio parameters contain four pairs of audio channel count and channel allocation values. Please notice that maximum channel count allowed is 8. For two channels, the channel allocation of zero is considered valid; In this case, stereo is assumed (FL/FR).\nAllocation value\nChannel count/bit\nChannel assignments\n0x001 2 FL/FR. (Front Left, Front Right)\n0x002 1 LFE. (Low Frequency Effects)\n0x004 1 FC. (Front Center) \n0x008 2 RL/RR. (Rear Left, Rear Right).\n0x010 1 RC. (Rear Center). \n0x020 2 FLC/FRC. (Front Left off Center, Front Right off Center).\n0x040 2 RLC/RRC (Rear Left off Center, Rear Right off Center). \n0x080 2 FLW/FRW (Front Left Wide, Front Right Wide) \n0x100 2 FLH/FRH (Front Left High, Front Right High)\n0x200 1 TC (Top Center) \n0x400 1 FCH (Front Center High)\n", + "flag": 3, + "id": "TSI_DP14_SRCCTS_CH_ALLOC2", + "maxValue": 2147480000, + "minValue": 0, + "name": "Channel allocation bits 2", + "type": 4 + }, + { + "configId": "0x10e0016", + "defaultValue": "2", + "description": "Defines the minimum number of channels for maximum sample rate. Default is 2 channels (Stereo). Highest valid channel count is 8. If the number is different from 2 channels, then the appropriate channel allocation must be provided through the CI 7.9.23 TSI_DP14_SRCCTS_CH_ALLOC3.\n ", + "flag": 3, + "id": "TSI_DP14_SRCCTS_CHANNELS3", + "maxValue": 8, + "minValue": 0, + "name": "Minimum number of channels for maximum sample rate", + "type": 4 + }, + { + "configId": "0x10e0017", + "defaultValue": "1", + "description": "The audio parameters contain four pairs of audio channel count and channel allocation values. Please notice that maximum channel count allowed is 8. For two channels, the channel allocation of zero is considered valid; In this case, stereo is assumed (FL/FR).\nAllocation value\nChannel count/bit\nChannel assignments\n0x001 2 FL/FR. (Front Left, Front Right)\n0x002 1 LFE. (Low Frequency Effects)\n0x004 1 FC. (Front Center) \n0x008 2 RL/RR. (Rear Left, Rear Right).\n0x010 1 RC. (Rear Center). \n0x020 2 FLC/FRC. (Front Left off Center, Front Right off Center).\n0x040 2 RLC/RRC (Rear Left off Center, Rear Right off Center). \n0x080 2 FLW/FRW (Front Left Wide, Front Right Wide) \n0x100 2 FLH/FRH (Front Left High, Front Right High)\n0x200 1 TC (Top Center) \n0x400 1 FCH (Front Center High)\n", + "flag": 3, + "id": "TSI_DP14_SRCCTS_CH_ALLOC3", + "maxValue": 2147480000, + "minValue": 0, + "name": "Channel allocations bits 3", + "type": 4 + }, + { + "configId": "0x10e0018", + "defaultValue": "6", + "description": "Defines the maximum number of channels for maximum sample rate. Default is 2 channels (Stereo). Highest valid channel count is 8. If the number is different from 2 channels, then the appropriate channel allocation must be provided through the CI 7.9.25 TSI_DP14_SRCCTS_CH_ALLOC4.\n ", + "flag": 3, + "id": "TSI_DP14_SRCCTS_CHANNELS4", + "maxValue": 8, + "minValue": 0, + "name": "Maximum number of channels for maximum sample rate", + "type": 4 + }, + { + "configId": "0x10e0019", + "defaultValue": "15", + "description": "The audio parameters contain four pairs of audio channel count and channel allocation values. Please notice that maximum channel count allowed is 8. For two channels, the channel allocation of zero is considered valid; In this case, stereo is assumed (FL/FR).\nAllocation value\nChannel count/bit\nChannel assignments\n0x001 2 FL/FR. (Front Left, Front Right)\n0x002 1 LFE. (Low Frequency Effects)\n0x004 1 FC. (Front Center) \n0x008 2 RL/RR. (Rear Left, Rear Right).\n0x010 1 RC. (Rear Center). \n0x020 2 FLC/FRC. (Front Left off Center, Front Right off Center).\n0x040 2 RLC/RRC (Rear Left off Center, Rear Right off Center). \n0x080 2 FLW/FRW (Front Left Wide, Front Right Wide) \n0x100 2 FLH/FRH (Front Left High, Front Right High)\n0x200 1 TC (Top Center) \n0x400 1 FCH (Front Center High)\n", + "flag": 3, + "id": "TSI_DP14_SRCCTS_CH_ALLOC4", + "maxValue": 2147480000, + "minValue": 0, + "name": "Channel allocation bits 4", + "type": 4 + }, + { + "configId": "0x10e001a", + "defaultValue": "0", + "description": "Defines the audio test pattern. Default setting is zero. Please see table below for allowed settings:\nValue Description \n0 Operator specific waveform \n1 Sawtooth waveform", + "enumerationVariants": "0 # Operator specific waveform\n1 # Sawtooth waveform", + "flag": 3, + "id": "TSI_DP14_SRCCTS_AUDIO_TEST_PATTERN", + "maxValue": 2147480000, + "minValue": 0, + "name": "Audio test pattern", + "type": 4 + }, + { + "configId": "0x10e001b", + "defaultValue": "16", + "description": "Defines sample size used for EDID configuration while testing. The size is indicated as number of bits per sample. Default setting is 16. Valid settings are 16, 20 or 24.\n ", + "enumerationVariants": "16 # 16 bits / sample\n20 # 20 bits / sample\n24 # 24 bits / sample", + "flag": 3, + "id": "TSI_DP14_SRCCTS_EDID_SAMPLE_SIZE", + "maxValue": 2147480000, + "minValue": 0, + "name": "Sample size for EDID while testing", + "type": 4 + }, + { + "configId": "0x10e0020", + "defaultValue": "9", + "description": "These three CI’s define the most packed timings for 1, 2 and 4 lane modes. Default for 1 lane is 8 ( = 1280 x 800 @ 60Hz RB1, 18BPP). Default 2 lanes is 12 ( = 1280 x 1024 @ 60Hz, 24BPP). Default for 4 lanes is 18 ( = 2048 x 1536 @ 60Hz RB1, 24BPP). Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n10 # DMT 800 x 600p @ 60.317Hz, 10 bpc\n11 # DMT 1024 x 768p @ 60Hz, 6 bpc\n8 # CVT 1280 x 800p @ 60Hz, RB1, 6 bpc\n9 # DMT 1280 x 768p @ 60Hz, RB1, 6 bpc\n23 # CTA 1440 x 480p @ 59.94Hz, 8 bpc\n24 # CTA 1440 x 576p @ 50Hz, 8 bpc", + "flag": 3, + "id": "TSI_DP14_SRCCTS_1L_MOST_PACKED", + "maxValue": 2147480000, + "minValue": 0, + "name": "Most packed video timing definitions for 1 lanes links", + "type": 4 + }, + { + "configId": "0x10e0021", + "defaultValue": "15", + "description": "These three CI’s define the most packed timings for 1, 2 and 4 lane modes. Default for 1 lane is 8 ( = 1280 x 800 @ 60Hz RB1, 18BPP). Default 2 lanes is 12 ( = 1280 x 1024 @ 60Hz, 24BPP). Default for 4 lanes is 18 ( = 2048 x 1536 @ 60Hz RB1, 24BPP). Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n16 # DMT 1280 x 768p @ 60Hz, 10 bpc\n14 # CVT 1280 x 800p @ 60Hz, 10 bpc\n4 # DMT 1280 x 960p @ 60Hz, 8 bpc\n12 # DMT 1280 x 1024p @ 60Hz, 8 bpc\n13 # DMT 1360 x 768p @ 60Hz, 10 bpc\n15 # DMT 1400 x 1050p @ 60Hz, RB1, 8 bpc\n17 # CVT 1600 x 1200p @ 60Hz, RB1, 6 bpc\n13 # DMT 1360 x 768p @ 60Hz, 8 bpc\n26 # CVT 1280 x 800p @ 60Hz, 8 bpc\n25 # DMT 1280 x 768p @ 60Hz, 8 bpc", + "flag": 3, + "id": "TSI_DP14_SRCCTS_2L_MOST_PACKED", + "maxValue": 2147480000, + "minValue": 0, + "name": "Most packed video timing definitions for 2 lanes links", + "type": 4 + }, + { + "configId": "0x10e0022", + "defaultValue": "29", + "description": "These three CI’s define the most packed timings for 1, 2 and 4 lane modes. Default for 1 lane is 8 ( = 1280 x 800 @ 60Hz RB1, 18BPP). Default 2 lanes is 12 ( = 1280 x 1024 @ 60Hz, 24BPP). Default for 4 lanes is 18 ( = 2048 x 1536 @ 60Hz RB1, 24BPP). Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n20 # DMT 1600 x 1200p @ 60Hz, 10 bpc\n28 # DMT 1600 x 1200p @ 60Hz, 8 bpc\n19 # DMT 1792 x 1344p @ 60Hz, 8 bpc\n29 # CTA 1920 x 1080p @ 60Hz, 10 bpc\n5 # CTA 1920 x 1080p @ 60Hz, 8 bpc\n18 # CVT 2048 x 1536p @ 60Hz, RB1, 8 bpc", + "flag": 3, + "id": "TSI_DP14_SRCCTS_4L_MOST_PACKED", + "maxValue": 2147480000, + "minValue": 0, + "name": "Most packed video timing definitions for 4 lanes links", + "type": 4 + }, + { + "configId": "0x10e0023", + "defaultValue": "2", + "description": "These 12 CI’s define the video modes used for timestamp generations for RBR, HBR, HBR2 and HBR3 link rates with 1, 2 and 4 lanes configurations. Please see 6.9.1 Test ID definitions, configuration items for defaults. Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n1 # 640 x 480 @ 60Hz 6 BPC\n2 # 848 x 480 @ 60Hz 8 BPC", + "flag": 3, + "id": "TSI_DP14_SRCCTS_1L_RBB", + "maxValue": 2147480000, + "minValue": 0, + "name": "Video timings used for Reduced Bitrate (RBR, 1.62Gbps) testing for 1 lanes.", + "type": 4 + }, + { + "configId": "0x10e0024", + "defaultValue": "3", + "description": "These 12 CI’s define the video modes used for timestamp generations for RBR, HBR, HBR2 and HBR3 link rates with 1, 2 and 4 lanes configurations. Please see 6.9.1 Test ID definitions, configuration items for defaults. Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n1 # 640 x 480 @ 60Hz 6 BPC\n2 # 848 x 480 @ 60Hz 8 BPC\n3 # 1280 x 720 @ 60Hz 8 BPC", + "flag": 3, + "id": "TSI_DP14_SRCCTS_2L_RBB", + "maxValue": 2147480000, + "minValue": 0, + "name": "Video timings used for Reduced Bitrate (RBR, 1.62Gbps) testing for 2 lanes.", + "type": 4 + }, + { + "configId": "0x10e0025", + "defaultValue": "5", + "description": "These 12 CI’s define the video modes used for timestamp generations for RBR, HBR, HBR2 and HBR3 link rates with 1, 2 and 4 lanes configurations. Please see 6.9.1 Test ID definitions, configuration items for defaults. Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n1 # 640 x 480 @ 60Hz 6 BPC\n2 # 848 x 480 @ 60Hz 8 BPC\n3 # 1280 x 720 @ 60Hz 8 BPC\n4 # 1280 x 960 @ 60Hz 8 BPC\n5 # 1920 x 1080 @ 60Hz 8 BPC", + "flag": 3, + "id": "TSI_DP14_SRCCTS_4L_RBB", + "maxValue": 2147480000, + "minValue": 0, + "name": "Video timings used for Reduced Bitrate (RBR, 1.62Gbps) testing for 4 lanes.", + "type": 4 + }, + { + "configId": "0x10e0026", + "defaultValue": "3", + "description": "These 12 CI’s define the video modes used for timestamp generations for RBR, HBR, HBR2 and HBR3 link rates with 1, 2 and 4 lanes configurations. Please see 6.9.1 Test ID definitions, configuration items for defaults. Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n1 # 640 x 480 @ 60Hz 6 BPC\n2 # 848 x 480 @ 60Hz 8 BPC\n3 # 1280 x 720 @ 60Hz 8 BPC", + "flag": 3, + "id": "TSI_DP14_SRCCTS_1L_HBR", + "maxValue": 2147480000, + "minValue": 0, + "name": "Video timings used for High Bitrate (HBR, 2.7Gbps) testing for 1 lanes", + "type": 4 + }, + { + "configId": "0x10e0027", + "defaultValue": "4", + "description": "These 12 CI’s define the video modes used for timestamp generations for RBR, HBR, HBR2 and HBR3 link rates with 1, 2 and 4 lanes configurations. Please see 6.9.1 Test ID definitions, configuration items for defaults. Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n1 # 640 x 480 @ 60Hz 6 BPC\n2 # 848 x 480 @ 60Hz 8 BPC\n3 # 1280 x 720 @ 60Hz 8 BPC\n4 # 1280 x 960 @ 60Hz 8 BPC", + "flag": 3, + "id": "TSI_DP14_SRCCTS_2L_HBR", + "maxValue": 2147480000, + "minValue": 0, + "name": "Video timings used for High Bitrate (HBR, 2.7Gbps) testing for 2 lanes", + "type": 4 + }, + { + "configId": "0x10e0028", + "defaultValue": "5", + "description": "These 12 CI’s define the video modes used for timestamp generations for RBR, HBR, HBR2 and HBR3 link rates with 1, 2 and 4 lanes configurations. Please see 6.9.1 Test ID definitions, configuration items for defaults. Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n1 # 640 x 480 @ 60Hz 6 BPC\n2 # 848 x 480 @ 60Hz 8 BPC\n3 # 1280 x 720 @ 60Hz 8 BPC\n4 # 1280 x 960 @ 60Hz 8 BPC\n5 # 1920 x 1080 @ 60Hz 8 BPC\n6 # 1920 x 1440 @ 60Hz 8 BPC", + "flag": 3, + "id": "TSI_DP14_SRCCTS_4L_HBR", + "maxValue": 2147480000, + "minValue": 0, + "name": "Video timings used for High Bitrate (HBR, 2.7Gbps) testing for 4 lanes", + "type": 4 + }, + { + "configId": "0x10e0029", + "defaultValue": "4", + "description": "These 12 CI’s define the video modes used for timestamp generations for RBR, HBR, HBR2 and HBR3 link rates with 1, 2 and 4 lanes configurations. Please see 6.9.1 Test ID definitions, configuration items for defaults. Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n1 # 640 x 480 @ 60Hz 6 BPC\n2 # 848 x 480 @ 60Hz 8 BPC\n3 # 1280 x 720 @ 60Hz 8 BPC\n4 # 1280 x 960 @ 60Hz 8 BPC", + "flag": 3, + "id": "TSI_DP14_SRCCTS_1L_HBR2", + "maxValue": 2147480000, + "minValue": 0, + "name": "Video timings used for High Bitrate-2 (HBR2, 5.4Gbps) testing for 1 lanes.", + "type": 4 + }, + { + "configId": "0x10e002a", + "defaultValue": "5", + "description": "These 12 CI’s define the video modes used for timestamp generations for RBR, HBR, HBR2 and HBR3 link rates with 1, 2 and 4 lanes configurations. Please see 6.9.1 Test ID definitions, configuration items for defaults. Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n1 # 640 x 480 @ 60Hz 6 BPC\n2 # 848 x 480 @ 60Hz 8 BPC\n3 # 1280 x 720 @ 60Hz 8 BPC\n4 # 1280 x 960 @ 60Hz 8 BPC\n5 # 1920 x 1080 @ 60Hz 8 BPC\n6 # 1920 x 1440 @ 60Hz 8 BPC", + "flag": 3, + "id": "TSI_DP14_SRCCTS_2L_HBR2", + "maxValue": 2147480000, + "minValue": 0, + "name": "Video timings used for High Bitrate-2 (HBR2, 5.4Gbps) testing for 2 lanes.", + "type": 4 + }, + { + "configId": "0x10e002b", + "defaultValue": "7", + "description": "These 12 CI’s define the video modes used for timestamp generations for RBR, HBR, HBR2 and HBR3 link rates with 1, 2 and 4 lanes configurations. Please see 6.9.1 Test ID definitions, configuration items for defaults. Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n1 # 640 x 480 @ 60Hz 6 BPC\n2 # 848 x 480 @ 60Hz 8 BPC\n3 # 1280 x 720 @ 60Hz 8 BPC\n4 # 1280 x 960 @ 60Hz 8 BPC\n5 # 1920 x 1080 @ 60Hz 8 BPC\n6 # 1920 x 1440 @ 60Hz 8 BPC\n7 # 1920 x 1080 @ 120Hz 8 BPC", + "flag": 3, + "id": "TSI_DP14_SRCCTS_4L_HBR2", + "maxValue": 2147480000, + "minValue": 0, + "name": "Video timings used for High Bitrate-2 (HBR2, 5.4Gbps) testing for 4 lanes.", + "type": 4 + }, + { + "configId": "0x10e002c", + "defaultValue": "6", + "description": "These 12 CI’s define the video modes used for timestamp generations for RBR, HBR, HBR2 and HBR3 link rates with 1, 2 and 4 lanes configurations. Please see 6.9.1 Test ID definitions, configuration items for defaults. Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n1 # 640 x 480 @ 60Hz 6 BPC\n2 # 848 x 480 @ 60Hz 8 BPC\n3 # 1280 x 720 @ 60Hz 8 BPC\n4 # 1280 x 960 @ 60Hz 8 BPC\n5 # 1920 x 1080 @ 60Hz 8 BPC\n6 # 1920 x 1440 @ 60Hz 8 BPC", + "flag": 3, + "id": "TSI_DP14_SRCCTS_1L_HBR3", + "maxValue": 2147480000, + "minValue": 0, + "name": "Video timings used for High Bitrate-3 (HBR3, 8.1Gbps) testing for 1 lanes.", + "type": 4 + }, + { + "configId": "0x10e002d", + "defaultValue": "21", + "description": "These 12 CI’s define the video modes used for timestamp generations for RBR, HBR, HBR2 and HBR3 link rates with 1, 2 and 4 lanes configurations. Please see 6.9.1 Test ID definitions, configuration items for defaults. Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n1 # 640 x 480 @ 60Hz 6 BPC\n2 # 848 x 480 @ 60Hz 8 BPC\n3 # 1280 x 720 @ 60Hz 8 BPC\n4 # 1280 x 960 @ 60Hz 8 BPC\n5 # 1920 x 1080 @ 60Hz 8 BPC\n6 # 1920 x 1440 @ 60Hz 8 BPC\n7 # 1920 x 1080 @ 120Hz 8 BPC\n8 # 1280 x 800 @ 60Hz RB1 6 BPC\n9 # 1280 x 768 @ 60Hz RB1 6 BPC\n10 # 800 x 600 @ 60Hz 10 BPC\n11 # 1024 x 768 @ 60Hz 10 BPC\n12 # 1280 x 1024 @ 60Hz 8 BPC\n13 # 1360 x 768 @ 60Hz 10 BPC\n14 # 1280 x 800 @ 60Hz RB1 10 BPC\n15 # 1400 x 1050 @ 60Hz RB1 8 BPC\n16 # 1280 x 768 @ 60Hz RB1 10 BPC\n17 # 1600 x 1200 @ 60Hz RB1 6 BPC\n18 # 2048 x 1536 @ 60Hz RB1 8 BPC\n19 # 1792 x 1344 @ 60Hz 8 BPC\n20 # 1600 x 1200 @ 60Hz RB1 10 BPC\n21 # 3840 x 2160 @ 30Hz 8 BPC", + "flag": 3, + "id": "TSI_DP14_SRCCTS_2L_HBR3", + "maxValue": 2147480000, + "minValue": 0, + "name": "Video timings used for High Bitrate-3 (HBR3, 8.1Gbps) testing for 2 lanes.", + "type": 4 + }, + { + "configId": "0x10e002e", + "defaultValue": "22", + "description": "These 12 CI’s define the video modes used for timestamp generations for RBR, HBR, HBR2 and HBR3 link rates with 1, 2 and 4 lanes configurations. Please see 6.9.1 Test ID definitions, configuration items for defaults. Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n1 # 640 x 480 @ 60Hz 6 BPC\n2 # 848 x 480 @ 60Hz 8 BPC\n3 # 1280 x 720 @ 60Hz 8 BPC\n4 # 1280 x 960 @ 60Hz 8 BPC\n5 # 1920 x 1080 @ 60Hz 8 BPC\n6 # 1920 x 1440 @ 60Hz 8 BPC\n7 # 1920 x 1080 @ 120Hz 8 BPC\n8 # 1280 x 800 @ 60Hz RB1 6 BPC\n9 # 1280 x 768 @ 60Hz RB1 6 BPC\n10 # 800 x 600 @ 60Hz 10 BPC\n11 # 1024 x 768 @ 60Hz 10 BPC\n12 # 1280 x 1024 @ 60Hz 8 BPC\n13 # 1360 x 768 @ 60Hz 10 BPC\n14 # 1280 x 800 @ 60Hz RB1 10 BPC\n15 # 1400 x 1050 @ 60Hz RB1 8 BPC\n16 # 1280 x 768 @ 60Hz RB1 10 BPC\n17 # 1600 x 1200 @ 60Hz RB1 6 BPC\n18 # 2048 x 1536 @ 60Hz RB1 8 BPC\n19 # 1792 x 1344 @ 60Hz 8 BPC\n20 # 1600 x 1200 @ 60Hz RB1 10 BPC\n21 # 3840 x 2160 @ 30Hz 8 BPC\n22 # 3840 x 2160 @ 60Hz 8 BPC", + "flag": 3, + "id": "TSI_DP14_SRCCTS_4L_HBR3", + "maxValue": 2147480000, + "minValue": 0, + "name": "Video timings used for High Bitrate-3 (HBR3, 8.1Gbps) testing for 4 lanes.", + "type": 4 + }, + { + "configId": "0x10e0038", + "defaultValue": "0", + "description": "These parameters are considered mandatory and should not be changed.", + "flag": 1, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_0", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_0", + "type": 4 + }, + { + "configId": "0x10e0039", + "defaultValue": "32", + "description": "These parameters are considered mandatory and should not be changed.", + "flag": 1, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_1", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_1", + "type": 4 + }, + { + "configId": "0x10e003a", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_2", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_2", + "type": 4 + }, + { + "configId": "0x10e003b", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_3", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_3", + "type": 4 + }, + { + "configId": "0x10e003c", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_4", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_4", + "type": 4 + }, + { + "configId": "0x10e003d", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_5", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_5", + "type": 4 + }, + { + "configId": "0x10e003e", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_6", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_6", + "type": 4 + }, + { + "configId": "0x10e003f", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_7", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_7", + "type": 4 + }, + { + "configId": "0x10e0040", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_8", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_8", + "type": 4 + }, + { + "configId": "0x10e0041", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_9", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_9", + "type": 4 + }, + { + "configId": "0x10e0042", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_10", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_10", + "type": 4 + }, + { + "configId": "0x10e0043", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_11", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_11", + "type": 4 + }, + { + "configId": "0x10e0044", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_12", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_12", + "type": 4 + }, + { + "configId": "0x10e0045", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_13", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_13", + "type": 4 + }, + { + "configId": "0x10e0046", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_14", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_14", + "type": 4 + }, + { + "configId": "0x10e0047", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_15", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_15", + "type": 4 + }, + { + "configId": "0x10e0048", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_16", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_16", + "type": 4 + }, + { + "configId": "0x10e0049", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_17", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_17", + "type": 4 + }, + { + "configId": "0x10e004a", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_18", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_18", + "type": 4 + }, + { + "configId": "0x10e004b", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_19", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_19", + "type": 4 + }, + { + "configId": "0x10e004c", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_20", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_20", + "type": 4 + }, + { + "configId": "0x10e004d", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_21", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_21", + "type": 4 + }, + { + "configId": "0x10e004e", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_22", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_22", + "type": 4 + }, + { + "configId": "0x10e004f", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_23", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_23", + "type": 4 + }, + { + "configId": "0x10e0050", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_24", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_24", + "type": 4 + }, + { + "configId": "0x10e0051", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_25", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_25", + "type": 4 + }, + { + "configId": "0x10e0052", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_26", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_26", + "type": 4 + }, + { + "configId": "0x10e0053", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_27", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_27", + "type": 4 + }, + { + "configId": "0x10e0054", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_28", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_28", + "type": 4 + }, + { + "configId": "0x10e0055", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_29", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_29", + "type": 4 + }, + { + "configId": "0x10e0056", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_30", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_30", + "type": 4 + }, + { + "configId": "0x10e0057", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_31", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_31", + "type": 4 + }, + { + "configId": "0x10e0058", + "defaultValue": "1", + "description": "Defines DUT DSC maximum supported slice. Default setting is 1. Valid settings are 1, 2, 4, 8, 10, 12, 16, 20, 24.\n ", + "enumerationVariants": "1\n2\n4\n8\n10\n12\n16\n20\n24", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_DUT_MAX_SLICE", + "maxValue": 24, + "minValue": 1, + "name": "DUT DSC maximum supported slice", + "type": 4 + }, + { + "configId": "0x10e0059", + "defaultValue": "65538", + "description": "Defines DSC version. Default setting is 1.2. Valid settings are 1.1 and 1.2.\n ", + "enumerationVariants": "65537 # 1.1\n65538 # 1.2", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VERSION", + "maxValue": 65538, + "minValue": 65537, + "name": "DSC version", + "type": 4 + }, + { + "configId": "0x10e005a", + "defaultValue": "0", + "description": "1920x1080 @ 30Hz", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_0_ID", + "maxValue": 0, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_VMT_0_ID", + "type": 4 + }, + { + "configId": "0x10e005b", + "defaultValue": "1", + "description": "Defines the bitmap with Lane count and Link rate. Default setting is zero. Please see table below for defined capability flags:\nBits Description \n0 ONE_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n1 ONE_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n2 ONE_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n 3 ONE_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n4 TWO_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n5 TWO_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n6 TWO_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n7 TWO_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n8 FOUR_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported.\n9 FOUR_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported.\n10 FOUR_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n11 FOUR_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported.\n31:12 RESERVED, SET TO ZERO\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_0_LC_LR", + "mask": 1, + "name": "Lane count and Link rate flags", + "type": 4 + }, + { + "configId": "0x10e005c", + "defaultValue": "0x00000001", + "description": "Defines the video mode timing.\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "bitList": + [ + {"mask": "0x00000001", "description": "CTA support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000002", "description": "RB1 support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000004", "description": "RB2 support. 0 = Not supported, 1 = Supported."} + ], + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_0_TIMINGS", + "name": "Lane count and Link rate flags.", + "type": 12 + }, + { + "configId": "0x10e005d", + "defaultValue": "1", + "description": "1920x1080 @ 60Hz", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_1_ID", + "maxValue": 1, + "minValue": 1, + "name": "TSI_DP14_SRCCTS_DSC_VMT_1_ID", + "type": 4 + }, + { + "configId": "0x10e005e", + "defaultValue": "1", + "description": "Defines the bitmap with Lane count and Link rate. Default setting is zero. Please see table below for defined capability flags:\nBits Description \n0 ONE_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n1 ONE_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n2 ONE_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n 3 ONE_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n4 TWO_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n5 TWO_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n6 TWO_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n7 TWO_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n8 FOUR_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported.\n9 FOUR_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported.\n10 FOUR_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n11 FOUR_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported.\n31:12 RESERVED, SET TO ZERO\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_1_LC_LR", + "mask": 1, + "name": "Lane count and Link rate flags", + "type": 4 + }, + { + "configId": "0x10e005f", + "defaultValue": "0x00000000", + "description": "Defines the video mode timing.\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "bitList": + [ + {"mask": "0x00000001", "description": "CTA support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000002", "description": "RB1 support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000004", "description": "RB2 support. 0 = Not supported, 1 = Supported."} + ], + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_1_TIMINGS", + "name": "Lane count and Link rate flags.", + "type": 12 + }, + { + "configId": "0x10e0060", + "defaultValue": "2", + "description": "1920x1080 @ 120Hz", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_2_ID", + "maxValue": 2, + "minValue": 2, + "name": "TSI_DP14_SRCCTS_DSC_VMT_2_ID", + "type": 4 + }, + { + "configId": "0x10e0061", + "defaultValue": "292", + "description": "Defines the bitmap with Lane count and Link rate. Default setting is zero. Please see table below for defined capability flags:\nBits Description \n0 ONE_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n1 ONE_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n2 ONE_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n 3 ONE_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n4 TWO_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n5 TWO_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n6 TWO_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n7 TWO_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n8 FOUR_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported.\n9 FOUR_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported.\n10 FOUR_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n11 FOUR_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported.\n31:12 RESERVED, SET TO ZERO\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_2_LC_LR", + "mask": 292, + "name": "Lane count and Link rate flags.", + "type": 4 + }, + { + "configId": "0x10e0062", + "defaultValue": "0x00000000", + "description": "Defines the video mode timing.\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "bitList": + [ + {"mask": "0x00000001", "description": "CTA support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000002", "description": "RB1 support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000004", "description": "RB2 support. 0 = Not supported, 1 = Supported."} + ], + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_2_TIMINGS", + "name": "Lane count and Link rate flags.", + "type": 12 + }, + { + "configId": "0x10e0063", + "defaultValue": "3", + "description": "3840x2160 @ 30Hz", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_3_ID", + "maxValue": 3, + "minValue": 3, + "name": "TSI_DP14_SRCCTS_DSC_VMT_3_ID", + "type": 4 + }, + { + "configId": "0x10e0064", + "defaultValue": "292", + "description": "Defines the bitmap with Lane count and Link rate. Default setting is zero. Please see table below for defined capability flags:\nBits Description \n0 ONE_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n1 ONE_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n2 ONE_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n 3 ONE_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n4 TWO_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n5 TWO_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n6 TWO_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n7 TWO_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n8 FOUR_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported.\n9 FOUR_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported.\n10 FOUR_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n11 FOUR_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported.\n31:12 RESERVED, SET TO ZERO\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_3_LC_LR", + "mask": 292, + "name": "Lane count and Link rate flags.", + "type": 4 + }, + { + "configId": "0x10e0065", + "defaultValue": "0x00000000", + "description": "Defines the video mode timing.\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "bitList": + [ + {"mask": "0x00000001", "description": "CTA support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000002", "description": "RB1 support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000004", "description": "RB2 support. 0 = Not supported, 1 = Supported."} + ], + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_3_TIMINGS", + "name": "Lane count and Link rate flags.", + "type": 12 + }, + { + "configId": "0x10e0066", + "defaultValue": "4", + "description": "3840x2160 @ 60Hz", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_4_ID", + "maxValue": 4, + "minValue": 4, + "name": "TSI_DP14_SRCCTS_DSC_VMT_4_ID", + "type": 4 + }, + { + "configId": "0x10e0067", + "defaultValue": "328", + "description": "Defines the bitmap with Lane count and Link rate. Default setting is zero. Please see table below for defined capability flags:\nBits Description \n0 ONE_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n1 ONE_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n2 ONE_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n 3 ONE_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n4 TWO_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n5 TWO_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n6 TWO_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n7 TWO_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n8 FOUR_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported.\n9 FOUR_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported.\n10 FOUR_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n11 FOUR_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported.\n31:12 RESERVED, SET TO ZERO\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_4_LC_LR", + "mask": 328, + "name": "Lane count and Link rate flags.", + "type": 4 + }, + { + "configId": "0x10e0068", + "defaultValue": "0x00000000", + "description": "Defines the video mode timing.\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "bitList": + [ + {"mask": "0x00000001", "description": "CTA support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000002", "description": "RB1 support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000004", "description": "RB2 support. 0 = Not supported, 1 = Supported."} + ], + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_4_TIMINGS", + "name": "Lane count and Link rate flags.", + "type": 12 + }, + { + "configId": "0x10e0069", + "defaultValue": "5", + "description": "3840x2160 @ 120Hz", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_5_ID", + "maxValue": 5, + "minValue": 5, + "name": "TSI_DP14_SRCCTS_DSC_VMT_5_ID", + "type": 4 + }, + { + "configId": "0x10e006a", + "defaultValue": "1152", + "description": "Defines the bitmap with Lane count and Link rate. Default setting is zero. Please see table below for defined capability flags:\nBits Description \n0 ONE_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n1 ONE_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n2 ONE_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n 3 ONE_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n4 TWO_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n5 TWO_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n6 TWO_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n7 TWO_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n8 FOUR_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported.\n9 FOUR_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported.\n10 FOUR_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n11 FOUR_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported.\n31:12 RESERVED, SET TO ZERO\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_5_LC_LR", + "mask": 1152, + "name": "Lane count and Link rate flags.", + "type": 4 + }, + { + "configId": "0x10e006b", + "defaultValue": "0x00000000", + "description": "Defines the video mode timing.\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "bitList": + [ + {"mask": "0x00000001", "description": "CTA support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000002", "description": "RB1 support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000004", "description": "RB2 support. 0 = Not supported, 1 = Supported."} + ], + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_5_TIMINGS", + "name": "Lane count and Link rate flags.", + "type": 12 + }, + { + "configId": "0x10e006c", + "defaultValue": "6", + "description": "5120x2160 @ 30Hz", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_6_ID", + "maxValue": 6, + "minValue": 6, + "name": "TSI_DP14_SRCCTS_DSC_VMT_6_ID", + "type": 4 + }, + { + "configId": "0x10e006d", + "defaultValue": "292", + "description": "Defines the bitmap with Lane count and Link rate. Default setting is zero. Please see table below for defined capability flags:\nBits Description \n0 ONE_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n1 ONE_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n2 ONE_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n 3 ONE_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n4 TWO_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n5 TWO_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n6 TWO_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n7 TWO_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n8 FOUR_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported.\n9 FOUR_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported.\n10 FOUR_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n11 FOUR_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported.\n31:12 RESERVED, SET TO ZERO\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_6_LC_LR", + "mask": 292, + "name": "Lane count and Link rate flags.", + "type": 4 + }, + { + "configId": "0x10e006e", + "defaultValue": "0x00000000", + "description": "Defines the video mode timing.\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "bitList": + [ + {"mask": "0x00000001", "description": "CTA support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000002", "description": "RB1 support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000004", "description": "RB2 support. 0 = Not supported, 1 = Supported."} + ], + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_6_TIMINGS", + "name": "Lane count and Link rate flags.", + "type": 12 + }, + { + "configId": "0x10e006f", + "defaultValue": "7", + "description": "5120x2160 @ 60Hz", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_7_ID", + "maxValue": 7, + "minValue": 7, + "name": "TSI_DP14_SRCCTS_DSC_VMT_6_ID", + "type": 4 + }, + { + "configId": "0x10e0070", + "defaultValue": "584", + "description": "Defines the bitmap with Lane count and Link rate. Default setting is zero. Please see table below for defined capability flags:\nBits Description \n0 ONE_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n1 ONE_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n2 ONE_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n 3 ONE_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n4 TWO_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n5 TWO_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n6 TWO_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n7 TWO_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n8 FOUR_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported.\n9 FOUR_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported.\n10 FOUR_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n11 FOUR_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported.\n31:12 RESERVED, SET TO ZERO\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_7_LC_LR", + "mask": 584, + "name": "Lane count and Link rate flags.", + "type": 4 + }, + { + "configId": "0x10e0071", + "defaultValue": "0x00000000", + "description": "Defines the video mode timing.\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "bitList": + [ + {"mask": "0x00000001", "description": "CTA support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000002", "description": "RB1 support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000004", "description": "RB2 support. 0 = Not supported, 1 = Supported."} + ], + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_7_TIMINGS", + "name": "Lane count and Link rate flags.", + "type": 12 + }, + { + "configId": "0x10e0072", + "defaultValue": "8", + "description": "5120x2160 @ 120Hz", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_8_ID", + "maxValue": 8, + "minValue": 8, + "name": "TSI_DP14_SRCCTS_DSC_VMT_8_ID", + "type": 4 + }, + { + "configId": "0x10e0073", + "defaultValue": "1152", + "description": "Defines the bitmap with Lane count and Link rate. Default setting is zero. Please see table below for defined capability flags:\nBits Description \n0 ONE_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n1 ONE_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n2 ONE_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n 3 ONE_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n4 TWO_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n5 TWO_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n6 TWO_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n7 TWO_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n8 FOUR_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported.\n9 FOUR_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported.\n10 FOUR_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n11 FOUR_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported.\n31:12 RESERVED, SET TO ZERO\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_8_LC_LR", + "mask": 1152, + "name": "Lane count and Link rate flags.", + "type": 4 + }, + { + "configId": "0x10e0074", + "defaultValue": "0x00000000", + "description": "Defines the video mode timing.\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "bitList": + [ + {"mask": "0x00000001", "description": "CTA support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000002", "description": "RB1 support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000004", "description": "RB2 support. 0 = Not supported, 1 = Supported."} + ], + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_8_TIMINGS", + "name": "Lane count and Link rate flags.", + "type": 12 + }, + { + "configId": "0x10e0075", + "defaultValue": "9", + "description": "7680x4320 @ 30Hz", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_9_ID", + "maxValue": 9, + "minValue": 9, + "name": "TSI_DP14_SRCCTS_DSC_VMT_9_ID", + "type": 4 + }, + { + "configId": "0x10e0076", + "defaultValue": "1728", + "description": "Defines the bitmap with Lane count and Link rate. Default setting is zero. Please see table below for defined capability flags:\nBits Description \n0 ONE_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n1 ONE_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n2 ONE_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n 3 ONE_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n4 TWO_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n5 TWO_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n6 TWO_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n7 TWO_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n8 FOUR_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported.\n9 FOUR_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported.\n10 FOUR_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n11 FOUR_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported.\n31:12 RESERVED, SET TO ZERO\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_9_LC_LR", + "mask": 1728, + "name": "Lane count and Link rate flags.", + "type": 4 + }, + { + "configId": "0x10e0077", + "defaultValue": "0x00000000", + "description": "Defines the video mode timing.\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "bitList": + [ + {"mask": "0x00000001", "description": "CTA support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000002", "description": "RB1 support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000004", "description": "RB2 support. 0 = Not supported, 1 = Supported."} + ], + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_9_TIMINGS", + "name": "Lane count and Link rate flags.", + "type": 12 + }, + { + "configId": "0x10e0078", + "defaultValue": "10", + "description": "7680x4320 @ 60Hz", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_10_ID", + "maxValue": 10, + "minValue": 10, + "name": "TSI_DP14_SRCCTS_DSC_VMT_10_ID", + "type": 4 + }, + { + "configId": "0x10e0079", + "defaultValue": "3072", + "description": "Defines the bitmap with Lane count and Link rate. Default setting is zero. Please see table below for defined capability flags:\nBits Description \n0 ONE_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n1 ONE_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n2 ONE_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n 3 ONE_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n4 TWO_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n5 TWO_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n6 TWO_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n7 TWO_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n8 FOUR_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported.\n9 FOUR_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported.\n10 FOUR_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n11 FOUR_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported.\n31:12 RESERVED, SET TO ZERO\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_10_LC_LR", + "mask": 3072, + "name": "Lane count and Link rate flags.", + "type": 4 + }, + { + "configId": "0x10e007a", + "defaultValue": "0x00000000", + "description": "Defines the video mode timing.\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "bitList": + [ + {"mask": "0x00000001", "description": "CTA support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000002", "description": "RB1 support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000004", "description": "RB2 support. 0 = Not supported, 1 = Supported."} + ], + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_10_TIMINGS", + "name": "Lane count and Link rate flags.", + "type": 12 + }, + { + "configId": "0x10e007b", + "defaultValue": "11", + "description": "7680x4320 @ 100Hz", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_11_ID", + "maxValue": 11, + "minValue": 11, + "name": "TSI_DP14_SRCCTS_DSC_VMT_11_ID", + "type": 4 + }, + { + "configId": "0x10e007c", + "defaultValue": "2048", + "description": "Defines the bitmap with Lane count and Link rate. Default setting is zero. Please see table below for defined capability flags:\nBits Description \n0 ONE_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n1 ONE_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n2 ONE_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n 3 ONE_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n4 TWO_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n5 TWO_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n6 TWO_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n7 TWO_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n8 FOUR_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported.\n9 FOUR_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported.\n10 FOUR_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n11 FOUR_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported.\n31:12 RESERVED, SET TO ZERO\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_11_LC_LR", + "mask": 2048, + "name": "Lane count and Link rate flags.", + "type": 4 + }, + { + "configId": "0x10e007d", + "defaultValue": "0x00000000", + "description": "Defines the video mode timing.\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "bitList": + [ + {"mask": "0x00000001", "description": "CTA support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000002", "description": "RB1 support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000004", "description": "RB2 support. 0 = Not supported, 1 = Supported."} + ], + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_11_TIMINGS", + "name": "Lane count and Link rate flags.", + "type": 12 + }, + { + "configId": "0x10e007e", + "defaultValue": "1", + "description": "Defined the number of items in DSC color format table.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_FORMATS", + "maxValue": 2147483647, + "minValue": 0, + "name": "Number of items in DSC color format table", + "type": 4 + }, + { + "configId": "0x10e007f", + "defaultValue": "32", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 1, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_0", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_0", + "type": 4 + }, + { + "configId": "0x10e0080", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_1", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_1", + "type": 4 + }, + { + "configId": "0x10e0081", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_2", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_2", + "type": 4 + }, + { + "configId": "0x10e0082", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_3", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_3", + "type": 4 + }, + { + "configId": "0x10e0083", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_4", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_4", + "type": 4 + }, + { + "configId": "0x10e0084", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_5", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_5", + "type": 4 + }, + { + "configId": "0x10e0085", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_6", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_6", + "type": 4 + }, + { + "configId": "0x10e0086", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_7", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_7", + "type": 4 + }, + { + "configId": "0x10e0087", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_8", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_8", + "type": 4 + }, + { + "configId": "0x10e0088", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_9", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_9", + "type": 4 + }, + { + "configId": "0x10e0089", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_10", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_10", + "type": 4 + }, + { + "configId": "0x10e008a", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_11", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_11", + "type": 4 + }, + { + "configId": "0x10e008b", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_12", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_12", + "type": 4 + }, + { + "configId": "0x10e008c", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_13", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_13", + "type": 4 + }, + { + "configId": "0x10e008d", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_14", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_14", + "type": 4 + }, + { + "configId": "0x10e008e", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_15", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_15", + "type": 4 + }, + { + "configId": "0x10e008f", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_16", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_16", + "type": 4 + }, + { + "configId": "0x10e0090", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_17", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_17", + "type": 4 + }, + { + "configId": "0x10e0091", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_18", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_18", + "type": 4 + }, + { + "configId": "0x10e0092", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_19", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_19", + "type": 4 + }, + { + "configId": "0x10e0093", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_20", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_20", + "type": 4 + }, + { + "configId": "0x10e0094", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_21", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_21", + "type": 4 + }, + { + "configId": "0x10e0095", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_22", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_22", + "type": 4 + }, + { + "configId": "0x10e0096", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_23", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_23", + "type": 4 + }, + { + "configId": "0x10e0097", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_24", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_24", + "type": 4 + }, + { + "configId": "0x10e0098", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_25", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_25", + "type": 4 + }, + { + "configId": "0x10e0099", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_26", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_26", + "type": 4 + }, + { + "configId": "0x10e009a", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_27", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_27", + "type": 4 + }, + { + "configId": "0x10e009b", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_28", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_28", + "type": 4 + }, + { + "configId": "0x10e009c", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_29", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_29", + "type": 4 + }, + { + "configId": "0x10e009d", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_30", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_30", + "type": 4 + }, + { + "configId": "0x10e009e", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_31", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_31", + "type": 4 + }, + { + "configId": "0x10e009f", + "defaultValue": "5120", + "description": "Defines maximum HActive per stream.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_MAX_STREAM_HACTIVE", + "maxValue": 2147483647, + "minValue": 0, + "name": "Maximum HActive per stream", + "type": 4 + }, + { + "configId": "0x10e00a0", + "defaultValue": "2160", + "description": "Defines maximum VActive per stream.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_MAX_STREAM_VACTIVE", + "maxValue": 2147483647, + "minValue": 0, + "name": "Maximum VActive per stream", + "type": 4 + }, + { + "configId": "0x10e00a1", + "defaultValue": "742500", + "description": "Defines maximum pixel clock per stream, kHz.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_MAX_STREAM_PIXEL_CLOCK", + "maxValue": 2147483647, + "minValue": 0, + "name": "Maximum pixel clock per stream, in kHz", + "type": 4 + }, + { + "configId": "0x10e00a2", + "defaultValue": "1297", + "description": "Defines tested audio mode. Bytes 2...0 of the word match data bytes 3...1 of a corresponding Audio InfoFrame (See CTA-861-G, Section 6.6). Byte 3 is set to 0. Unused entries are set to 0.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_TESTED_AUDIO_MODE_0", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_TESTED_AUDIO_MODE_0", + "type": 4 + }, + { + "configId": "0x10e00a3", + "defaultValue": "2321", + "description": "Defines tested audio mode. Bytes 2...0 of the word match data bytes 3...1 of a corresponding Audio InfoFrame (See CTA-861-G, Section 6.6). Byte 3 is set to 0. Unused entries are set to 0.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_TESTED_AUDIO_MODE_1", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_TESTED_AUDIO_MODE_1", + "type": 4 + }, + { + "configId": "0x10e00a4", + "defaultValue": "3345", + "description": "Defines tested audio mode. Bytes 2...0 of the word match data bytes 3...1 of a corresponding Audio InfoFrame (See CTA-861-G, Section 6.6). Byte 3 is set to 0. Unused entries are set to 0.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_TESTED_AUDIO_MODE_2", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_TESTED_AUDIO_MODE_2", + "type": 4 + }, + { + "configId": "0x10e00a5", + "defaultValue": "2833", + "description": "Defines tested audio mode. Bytes 2...0 of the word match data bytes 3...1 of a corresponding Audio InfoFrame (See CTA-861-G, Section 6.6). Byte 3 is set to 0. Unused entries are set to 0.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_TESTED_AUDIO_MODE_3", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_TESTED_AUDIO_MODE_3", + "type": 4 + }, + { + "configId": "0x10e00a6", + "defaultValue": "3857", + "description": "Defines tested audio mode. Bytes 2...0 of the word match data bytes 3...1 of a corresponding Audio InfoFrame (See CTA-861-G, Section 6.6). Byte 3 is set to 0. Unused entries are set to 0.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_TESTED_AUDIO_MODE_4", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_TESTED_AUDIO_MODE_4", + "type": 4 + }, + { + "configId": "0x10e00a7", + "defaultValue": "0", + "description": "Defines tested audio mode. Bytes 2...0 of the word match data bytes 3...1 of a corresponding Audio InfoFrame (See CTA-861-G, Section 6.6). Byte 3 is set to 0. Unused entries are set to 0.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_TESTED_AUDIO_MODE_5", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_TESTED_AUDIO_MODE_5", + "type": 4 + }, + { + "configId": "0x10e00a8", + "defaultValue": "0", + "description": "Defines tested audio mode. Bytes 2...0 of the word match data bytes 3...1 of a corresponding Audio InfoFrame (See CTA-861-G, Section 6.6). Byte 3 is set to 0. Unused entries are set to 0.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_TESTED_AUDIO_MODE_6", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_TESTED_AUDIO_MODE_6", + "type": 4 + }, + { + "configId": "0x10e00a9", + "defaultValue": "0", + "description": "Defines tested audio mode. Bytes 2...0 of the word match data bytes 3...1 of a corresponding Audio InfoFrame (See CTA-861-G, Section 6.6). Byte 3 is set to 0. Unused entries are set to 0.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_TESTED_AUDIO_MODE_7", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_TESTED_AUDIO_MODE_7", + "type": 4 + }, + { + "configId": "0x10e00aa", + "defaultValue": "0x00000103", + "description": "DMT timings (Progressive Scan) supported by the DUT without DSC.", + "bitList": + [ + {"mask": "0x00000001", "description": "800x600 @ 60Hz (DMT 09h)"}, + {"mask": "0x00000002", "description": "1024x768 @ 60Hz (DMT 10h)"}, + {"mask": "0x00000004", "description": "1280x768 @ 60Hz (DMT 17h)"}, + {"mask": "0x00000008", "description": "1280x800 @ 60Hz (DMT 1Ch)"}, + {"mask": "0x00000010", "description": "1280x960 @ 60Hz (DMT 20h)"}, + {"mask": "0x00000020", "description": "1280x1024 @ 60Hz (DMT 23h)"}, + {"mask": "0x00000040", "description": "1360x768 @ 60Hz (DMT 27h)"}, + {"mask": "0x00000080", "description": "1400x1050 @ 60Hz (DMT 2Ah)"}, + {"mask": "0x00000100", "description": "1600x1200 @ 60Hz (DMT 33h)"}, + {"mask": "0x00000200", "description": "1680x1050 @ 60Hz (DMT 3Ah)"}, + {"mask": "0x00000400", "description": "1856x1392 @ 60Hz (DMT 41h)"}, + {"mask": "0x00000800", "description": "1920x1080 @ 60Hz (DMT 52h)"}, + {"mask": "0x00001000", "description": "1920x1200 @ 60Hz (DMT 45h)"}, + {"mask": "0x00002000", "description": "1920x1440 @ 60Hz (DMT 49h)"} + ], + "flag": 3, + "id": "TSI_DP14_SRCCTS_DMT_TIMING", + "name": "DMT timings.", + "type": 12 + }, + { + "configId": "0x10e00ab", + "defaultValue": "0x00000106", + "description": "CTA timings (Progressive Scan) supported by the DUT without DSC.", + "bitList": + [ + {"mask": "0x00000001", "description": "720x480 @ 60Hz (VIC 2)"}, + {"mask": "0x00000002", "description": "1280x720 @ 60Hz (VIC 4)"}, + {"mask": "0x00000004", "description": "1920x1080 @ 60Hz (VIC 16)"}, + {"mask": "0x00000008", "description": "1280x720 @ 120Hz (VIC 47)"}, + {"mask": "0x00000010", "description": "720x480 @ 120Hz (VIC 48)"}, + {"mask": "0x00000020", "description": "1920x1080 @ 120Hz (VIC 63)"}, + {"mask": "0x00000040", "description": "1680x720 @ 60Hz (VIC 83)"}, + {"mask": "0x00000080", "description": "1680x720 @ 120Hz (VIC 85)"}, + {"mask": "0x00000100", "description": "2560x1080 @ 60Hz (VIC 90)"}, + {"mask": "0x00000200", "description": "2560x1080 @ 120Hz (VIC 92)"}, + {"mask": "0x00000400", "description": "3840x2160 @ 60Hz (VIC 97)"}, + {"mask": "0x00000800", "description": "4096x2160 @ 60Hz (VIC 102)"}, + {"mask": "0x00001000", "description": "3840x2160 @ 120Hz (VIC 118)"}, + {"mask": "0x00002000", "description": "5120x2160 @ 60Hz (VIC 126)"}, + {"mask": "0x00004000", "description": "7680x4320 @ 24Hz (VIC 194)"}, + {"mask": "0x00008000", "description": "7680x4320 @ 30Hz (VIC 196)"} + ], + "flag": 3, + "id": "TSI_DP14_SRCCTS_CTA_TIMING", + "name": "CTA timings.", + "type": 12 + }, + { + "configId": "0x10e00ac", + "defaultValue": "0x00000492", + "description": "CVT timings (Progressive Scan) supported by the DUT without DSC.", + "bitList": + [ + {"mask": "0x00000001", "description": "1920x1080 @ 60Hz RBv1"}, + {"mask": "0x00000002", "description": "2048x1536 @ 60Hz RBv1"}, + {"mask": "0x00000004", "description": "2560x1080 @ 60Hz RBv1"}, + {"mask": "0x00000008", "description": "2560x1440 @ 60Hz RBv1"}, + {"mask": "0x00000010", "description": "2560x1440 @ 60Hz RBv2"}, + {"mask": "0x00000020", "description": "3840x2160 @ 60Hz RBv1"}, + {"mask": "0x00000040", "description": "3840x2160 @ 60Hz RBv2"}, + {"mask": "0x00000080", "description": "3840x2160 @ 60Hz RBv3"}, + {"mask": "0x00000100", "description": "4096x2160 @ 60Hz RBv1"}, + {"mask": "0x00000200", "description": "4096x2160 @ 60Hz RBv2"}, + {"mask": "0x00000400", "description": "4096x2160 @ 60Hz RBv3"}, + {"mask": "0x00000800", "description": "5120x2880 @ 60Hz RBv1"}, + {"mask": "0x00001000", "description": "5120x2880 @ 60Hz RBv2"}, + {"mask": "0x00002000", "description": "5120x2880 @ 60Hz RBv3"} + ], + "flag": 3, + "id": "TSI_DP14_SRCCTS_CVT_TIMING", + "name": "CVT timings.", + "type": 12 + }, + { + "configId": "0x10e00ad", + "defaultValue": "0x00000007", + "description": "DUT AdaptiveSync capabilitie.", + "bitList": + [ + {"mask": "0x00000001", "description": "DUT supports AdaptiveSync"}, + {"mask": "0x00000002", "description": "Device supports Fixed Average VTotal mode"}, + {"mask": "0x00000004", "description": "Device supports Duration Increase and Decrease constraints"}, + {"mask": "0x00000008", "description": "Adaptive-Sync must be enabled manually"} + ], + "flag": 1, + "id": "TSI_DP14_SRCCTS_AS_DUT_CAPS", + "name": "DUT AdaptiveSync capabilitie.", + "type": 12 + }, + { + "configId": "0x10e00ae", + "defaultValue": "3", + "description": "Adaptive-Sync range minimum refresh rate supported by the Source. Default value is 3.", + "enumerationVariants": "0 # 59.940 Hz\n1 # 47.952 Hz\n2 # 29.970 Hz\n3 # 23.976 Hz", + "flag": 3, + "id": "TSI_DP14_SRCCTS_AS_RANGE_MIN_RATE", + "maxValue": 3, + "minValue": 0, + "name": "Adaptive-Sync range minimum refresh rate supported by the Source", + "type": 4 + }, + { + "configId": "0x10e00af", + "defaultValue": "120", + "description": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz. A zero value means the timing is not supported.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_AS_RANGE_MAX_RATE_0", + "maxValue": 360, + "minValue": 0, + "name": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz.", + "type": 4 + }, + { + "configId": "0x10e00b0", + "defaultValue": "120", + "description": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz. A zero value means the timing is not supported.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_AS_RANGE_MAX_RATE_1", + "maxValue": 360, + "minValue": 0, + "name": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz.", + "type": 4 + }, + { + "configId": "0x10e00b1", + "defaultValue": "120", + "description": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz. A zero value means the timing is not supported.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_AS_RANGE_MAX_RATE_2", + "maxValue": 360, + "minValue": 0, + "name": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz.", + "type": 4 + }, + { + "configId": "0x10e00b2", + "defaultValue": "120", + "description": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz. A zero value means the timing is not supported.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_AS_RANGE_MAX_RATE_3", + "maxValue": 360, + "minValue": 0, + "name": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz.", + "type": 4 + }, + { + "configId": "0x10e00b3", + "defaultValue": "0", + "description": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz. A zero value means the timing is not supported.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_AS_RANGE_MAX_RATE_4", + "maxValue": 360, + "minValue": 0, + "name": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz.", + "type": 4 + }, + { + "configId": "0x10e00b4", + "defaultValue": "0", + "description": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz. A zero value means the timing is not supported.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_AS_RANGE_MAX_RATE_5", + "maxValue": 360, + "minValue": 0, + "name": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz.", + "type": 4 + }, + { + "configId": "0x10e00b5", + "defaultValue": "0", + "description": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz. A zero value means the timing is not supported.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_AS_RANGE_MAX_RATE_6", + "maxValue": 360, + "minValue": 0, + "name": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz.", + "type": 4 + }, + { + "configId": "0x10e00b6", + "defaultValue": "0", + "description": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz. A zero value means the timing is not supported.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_AS_RANGE_MAX_RATE_7", + "maxValue": 360, + "minValue": 0, + "name": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz.", + "type": 4 + }, + { + "configId": "0x10e000d", + "defaultValue": "0", + "description": "Reserved for extension.", + "bitList": + [ + {"description": "0x10e000d", "defaultValue": "0"}, + {"description": "0x10e000f", "defaultValue": "0"}, + {"description": "0x10e001c", "defaultValue": "0"}, + {"description": "0x10e001d", "defaultValue": "0"}, + {"description": "0x10e001e", "defaultValue": "0"}, + {"description": "0x10e001f", "defaultValue": "0"}, + {"description": "0x10e002f", "defaultValue": "0"}, + {"description": "0x10e0030", "defaultValue": "0"}, + {"description": "0x10e0031", "defaultValue": "0"}, + {"description": "0x10e0032", "defaultValue": "0"}, + {"description": "0x10e0033", "defaultValue": "0"}, + {"description": "0x10e0034", "defaultValue": "0"}, + {"description": "0x10e0035", "defaultValue": "0"}, + {"description": "0x10e0036", "defaultValue": "0"}, + {"description": "0x10e0037", "defaultValue": "0"}, + {"description": "0x10e00b7", "defaultValue": "0"}, + {"description": "0x10e00b8", "defaultValue": "0"}, + {"description": "0x10e00b9", "defaultValue": "0"}, + {"description": "0x10e00ba", "defaultValue": "0"}, + {"description": "0x10e00bb", "defaultValue": "0"}, + {"description": "0x10e00bc", "defaultValue": "0"}, + {"description": "0x10e00bd", "defaultValue": "0"}, + {"description": "0x10e00be", "defaultValue": "0"} + ], + "flag": 3, + "id": "RESERVED", + "type": 4 + } + ], + "id": "0x03", + "name": "DP 1.4 LL CTS", + "old_names": [ + "DP 1.4 LL CTS", + "DP 1.4 Link Layer Source DUT CTS" + ] +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x04]src_dut_link_config.json b/UniTAP/dev/modules/dut_tests/cfg/[0x04]src_dut_link_config.json new file mode 100644 index 0000000..6c5e90c --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x04]src_dut_link_config.json @@ -0,0 +1,91 @@ +{ + "descriptions": [ + { + "configId": "0x10700", + "defaultValue": "5000", + "description": "Defines timeout for each test iteration, in milliseconds. The test iterates through a number of iterations depending on other tests. Each iteration must complete within this timeout in order for the test succeed. Default setting is 5000ms.", + "flag": 3, + "id": "TSI_DP_LTT_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout, in milliseconds", + "type": 4 + }, + { + "configId": "0x10701", + "defaultValue": "4", + "description": "Defines the maximum number of lanes to be tested. Valid settings are 1, 2 and 4. Default setting is 4.\n", + "enumerationVariants": "1\n2\n4", + "flag": 3, + "id": "TSI_DP_LTT_MAX_LANE_COUNT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Max lanes count supported by DUT", + "type": 4 + }, + { + "configId": "0x10702", + "defaultValue": "20", + "description": "Defines the maximum link rate to be tested. The setting is in multiplier of 0.27Gbps. Valid settings are 6, 10, 20 and 30. Default setting is 20.", + "enumerationVariants": "6 # 1.62 Gbps\n10 # 2.7 Gbps\n20 # 5.4 Gbps\n30 # 8.1Gbps", + "flag": 3, + "id": "TSI_DP_LTT_MAX_RATE", + "maxValue": 2147483647, + "minValue": 0, + "name": "Max lane rate supported by DUT", + "type": 4 + }, + { + "configId": "0x10705", + "defaultValue": "1000", + "description": "Defines the length of the HPD pulse used to start each test iteration, in milliseconds. Default setting is 1000ms.", + "flag": 3, + "id": "TSI_DP_LTT_HPD_PULSE_DURATION", + "maxValue": 2147483647, + "minValue": 0, + "name": "Long HPD pulse duration, in milliseconds", + "type": 4 + }, + { + "configId": "0x10706", + "defaultValue": "5000", + "description": "Defines how long the test waits for LT start after issuing HPD pulse, in milliseconds. Default setting is 5000ms", + "flag": 3, + "id": "TSI_DP_LTT_LT_START_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Link training start timeout, in milliseconds", + "type": 4 + }, + { + "configId": "0x10707", + "defaultValue": "3000", + "description": "Defines the additional delay inserted in between test iterations, in milliseconds. Default setting is 3000ms.", + "flag": 3, + "id": "TSI_DP_LTT_TEST_LOOP_DELAY", + "maxValue": 2147483647, + "minValue": 0, + "name": "Delay between test cycles, in milliseconds", + "type": 4 + }, + { + "configId": "0x10703", + "defaultValue": "0", + "description": "Reserved for DUT Capabilities flags and DUT Test automation capabilities flags.", + "bitList": + [ + {"description": "0x10703", "defaultValue": "0"}, + {"description": "0x10704", "defaultValue": "0"} + ], + "flag": 3, + "id": "RESERVED", + "name": "Reserved", + "type": 4 + } + ], + "id": "0x04", + "name": "Link Config Tests", + "old_names": [ + "Link Test Set" + ] +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x05]src_dut_dp_electrical.json b/UniTAP/dev/modules/dut_tests/cfg/[0x05]src_dut_dp_electrical.json new file mode 100644 index 0000000..b1c4a9e --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x05]src_dut_dp_electrical.json @@ -0,0 +1,222 @@ +{ + "descriptions": [ + { + "configId": "0x10100", + "defaultValue": "5000", + "description": "Timeout period used for all DP RX electrical tests, in milliseconds. Default timeout is 5000ms.\n", + "flag": 3, + "id": "TSI_DP_RX_TEST_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout, in milliseconds", + "type": 4 + }, + { + "configId": "0x10101", + "defaultValue": "2600", + "description": "These two CI’s define the acceptable voltage range DP link lines. The measured voltage must be higher than TSI_DP_RX_LINKS_LOW_VOLTAGE setting, and lower than TSI_DP_RX_LINKS_HI_VOLTAGE setting in order to pass test. Default setting for low voltage limit is 2600mV, and for high voltage limit 4000mV.\n", + "flag": 3, + "id": "TSI_DP_RX_LINKS_LOW_VOLTAGE", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "Main link low voltage limit, mV", + "type": 0 + }, + { + "configId": "0x10102", + "defaultValue": "4000", + "description": "These two CI’s define the acceptable voltage range DP link lines. The measured voltage must be higher than TSI_DP_RX_LINKS_LOW_VOLTAGE setting, and lower than TSI_DP_RX_LINKS_HI_VOLTAGE setting in order to pass test. Default setting for low voltage limit is 2600mV, and for high voltage limit 4000mV.\n", + "flag": 3, + "id": "TSI_DP_RX_LINKS_HI_VOLTAGE", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "Main link high voltage limit, mV", + "type": 0 + }, + { + "configId": "0x10103", + "defaultValue": "-100", + "description": "These to CI’s define the acceptable voltage range for HDP line when it is in logical zero state. The measured voltage must be higher than TSI_DP_RX_HDP_ZERO_LOW_VOLTAGE setting, and lower than TSI_DP_RX_HPD_ZERO_HI_VOLTAGE setting in order to pass test. Default setting for low voltage limit is -100mV, and for high voltage limit 800mV.\n", + "flag": 3, + "id": "TSI_DP_RX_HPD_ZERO_LOW_VOLTAGE", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "HPD line logical zero low voltage level limit, mV", + "type": 0 + }, + { + "configId": "0x10104", + "defaultValue": "800", + "description": "These to CI’s define the acceptable voltage range for HDP line when it is in logical zero state. The measured voltage must be higher than TSI_DP_RX_HDP_ZERO_LOW_VOLTAGE setting, and lower than TSI_DP_RX_HPD_ZERO_HI_VOLTAGE setting in order to pass test. Default setting for low voltage limit is -100mV, and for high voltage limit 800mV.\n", + "flag": 3, + "id": "TSI_DP_RX_HPD_ZERO_HI_VOLTAGE", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "HPD line logical zero high voltage level limit, mV", + "type": 0 + }, + { + "configId": "0x10105", + "defaultValue": "800", + "description": "These two CI’s define the acceptable voltage range for HPD line when it is in logical one state. The measured voltage must be higher than TSI_DP_RX_HDP_ONE_LOW_VOLTAGE setting, and lower than TSI_DP_RX_HDP_ONE_HI_VOLTAGE setting in order to pass test. Default setting for low voltage limit is 2000mV, and for high voltage limit 5500mV.\n", + "flag": 3, + "id": "TSI_DP_RX_HPD_ONE_LOW_VOLTAGE", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "HPD line logical one low voltage level limit, mV", + "type": 0 + }, + { + "configId": "0x10106", + "defaultValue": "5500", + "description": "These two CI’s define the acceptable voltage range for HPD line when it is in logical one state. The measured voltage must be higher than TSI_DP_RX_HDP_ONE_LOW_VOLTAGE setting, and lower than TSI_DP_RX_HDP_ONE_HI_VOLTAGE setting in order to pass test. Default setting for low voltage limit is 800mV, and for high voltage limit 5500mV.", + "flag": 3, + "id": "TSI_DP_RX_HPD_ONE_HI_VOLTAGE", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "HPD line logical one high voltage level limit, mV", + "type": 0 + }, + { + "configId": "0x10107", + "defaultValue": "20", + "description": "These two CI’s define the acceptable AUX+ line idle voltage range when the AUX is idle. The measured voltage must be higher than TSI_DP_RX_AUX_P_IDLE_LOW_VOLTAGE setting, and lower than TSI_DP_RX_AUX_P_IDLE_HI_VOLTAGE setting in order to pass test. Default setting for low voltage limit is 2400mV, and for high voltage limit 2600mV.", + "flag": 3, + "id": "TSI_DP_RX_AUX_P_IDLE_LOW_VOLTAGE", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "AUX + line idle low voltage level limit, mV", + "type": 0 + }, + { + "configId": "0x10108", + "defaultValue": "500", + "description": "These two CI’s define the acceptable AUX+ line idle voltage range when the AUX is idle. The measured voltage must be higher than TSI_DP_RX_AUX_P_IDLE_LOW_VOLTAGE setting, and lower than TSI_DP_RX_AUX_P_IDLE_HI_VOLTAGE setting in order to pass test. Default setting for low voltage limit is 3600mV, and for high voltage limit 2600mV.\n", + "flag": 3, + "id": "TSI_DP_RX_AUX_P_IDLE_HI_VOLTAGE", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "AUX + line idle high voltage level limit, mV", + "type": 0 + }, + { + "configId": "0x10109", + "defaultValue": "2600", + "description": "These two CI’s defined the acceptable AUX- line idle voltage range when the AUX is idle. The measured voltage must be higher than TSI_DP_RX_AUX_N_IDLE_LOW_VOLTAGE setting, and lower than TSI_DP_RX_AUX_N_IDLE_HI_VOLTAGE setting in order to pass test. Default setting for low voltage limit is 2400mV, and for high voltage limit 3600mV.", + "flag": 3, + "id": "TSI_DP_RX_AUX_N_IDLE_LOW_VOLTAGE", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "AUX - line idle low voltage level limit, mV", + "type": 0 + }, + { + "configId": "0x1010a", + "defaultValue": "3600", + "description": "These two CI’s defined the acceptable AUX- line idle voltage range when the AUX is idle. The measured voltage must be higher than TSI_DP_RX_AUX_N_IDLE_LOW_VOLTAGE setting, and lower than TSI_DP_RX_AUX_N_IDLE_HI_VOLTAGE setting in order to pass test. Default setting for low voltage limit is 3600mV, and for high voltage limit 3600mV.", + "flag": 3, + "id": "TSI_DP_RX_AUX_N_IDLE_HI_VOLTAGE", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "AUX - line idle high voltage level limit, mV", + "type": 0 + }, + { + "configId": "0x1010b", + "defaultValue": "150", + "description": "These two CI’s define the AUX+ (TSI_DP_RX_AUX_P_TRIG_VOLTAGE) and AUX(TSI_DP_RX_AUX_N_TRIG_VOLTAGE) line state change trigger levels. Default settings are for AUX+ 150mV and for AUX- 200mV.", + "flag": 3, + "id": "TSI_DP_RX_AUX_P_TRIG_VOLTAGE", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "AUX + line signal trigger level, mV", + "type": 0 + }, + { + "configId": "0x1010c", + "defaultValue": "200", + "description": "These two CI’s define the AUX+ (TSI_DP_RX_AUX_P_TRIG_VOLTAGE) and AUX(TSI_DP_RX_AUX_N_TRIG_VOLTAGE) line state change trigger levels. Default settings are for AUX+ 150mV and for AUX- 200mV.", + "flag": 3, + "id": "TSI_DP_RX_AUX_N_TRIG_VOLTAGE", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "AUX - line signal trigger level, mV", + "type": 0 + }, + { + "configId": "0x1010d", + "defaultValue": "200", + "description": "Timeout for AUX signal capture, in milliseconds. When the TE generates a HPD pulse during test, it waits for this amount of time (max.) for DUT to read DPCD locations 0x200 to 0x205. If this transaction is not seen, the test will fail. Default setting is 200ms.\n", + "flag": 3, + "id": "TSI_DP_RX_AUX_SIGNAL_CAPT_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "AUX signal capture timeout, milliseconds", + "type": 4 + }, + { + "configId": "0x1010e", + "defaultValue": "5", + "description": "Retry count for AUX signal capture. If the AUX signal capture after TE generated a HPD pulse fails, the TE will re-try this many times. Default setting is 5.\n", + "flag": 3, + "id": "TSI_DP_RX_AUX_SIGNAL_CAPT_TRIES", + "maxValue": 2147483647, + "minValue": 0, + "name": "AUX signal capture attempts, times", + "type": 4 + }, + { + "configId": "0x1010f", + "defaultValue": "4", + "description": "Maximum number of lanes supported by the connected DUT. Typical values are 1, 2 or 4. Default setting is 4.", + "flag": 3, + "id": "TSI_DP_RX_MAX_DUT_MAX_LANES", + "maxValue": 2147483647, + "minValue": 0, + "name": "Maximum lanes count supported by DUT", + "type": 4 + }, + { + "configId": "0x10110", + "defaultValue": "20", + "description": "Maximum link rate supported by the connected DUT, as multiplier of 0.27Gbps. Typical values are 6 (RBR), 10 (HBR), 20 (HBR-2) or 30 (HBR-3). Please note that HBR3 speed is not supported on all TE devices.\n", + "flag": 3, + "id": "TSI_DP_RX_MAX_DUT_LANE_RATE", + "maxValue": 2147483647, + "minValue": 0, + "name": "Maximum data rate supported by DUT in 0.27Gbps", + "type": 4 + }, + { + "configId": "0x10111", + "defaultValue": "0", + "description": "DUT Capabilities flags", + "flag": 3, + "id": "TSI_DP_RX_DUT_CAPS", + "maxValue": 2147483647, + "minValue": 0, + "name": "DUT Capabilities flags", + "type": 4 + }, + { + "configId": "0x10112", + "defaultValue": "0x00000000", + "description": "DUT Test automation capabilities flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "DUT is capable for test link training"}, + {"mask": "0x00000002", "description": "DUT is capable for test video pattern"}, + {"mask": "0x00000004", "description": "DUT is capable for test EDID read"} + ], + "flag": 3, + "id": "TSI_DP_RX_DUT_TA_CAPS", + "name": "DUT Test automation capabilities flags.", + "type": 12 + } + ], + "id": "0x05", + "name": "Electrical Tests", + "old_names": [ + "DP Electrical Test Set" + ] +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x06]src_dut_hdmi_electrical.json b/UniTAP/dev/modules/dut_tests/cfg/[0x06]src_dut_hdmi_electrical.json new file mode 100644 index 0000000..32a3fa4 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x06]src_dut_hdmi_electrical.json @@ -0,0 +1,174 @@ +{ + "descriptions": [ + { + "configId": "0x10200", + "defaultValue": "5000", + "description": "Timeout period used for all HDMI RX electrical tests, in milliseconds. Default timeout is 5000ms.\n", + "flag": 3, + "id": "TSI_HDMI_RX_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout, in milliseconds", + "type": 4 + }, + { + "configId": "0x10201", + "defaultValue": "4700", + "description": "HDMI power line voltage low limit, in millivolts. The voltage detected from HDMI power line must be higher than this value in order to pass tests. Default setting is 4700mV.\n", + "flag": 3, + "id": "TSI_HDMI_RX_POWER_LOW_LIMIT", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "Power line low voltage limit, mV", + "type": 0 + }, + { + "configId": "0x10202", + "defaultValue": "5300", + "description": "HDMI power line voltage high limit, in millivolts. The voltage detected from HDMI power line must be less than this value in order to pass tests. Default setting is 5300mV.\n", + "flag": 3, + "id": "TSI_HDMI_RX_POWER_HIGH_LIMIT", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "Power line high voltage limit, mV", + "type": 0 + }, + { + "configId": "0x10203", + "defaultValue": "2900", + "description": "HDMI link line voltage low limit, in millivolts. The voltage detected from HDMI link line(s) during test must be higher than this value in order to pass test. Default setting is 2900mV. Important: The acceptable setting for this value can be different for different types of DUT’s. Proper calibration of this value will require testing multiple DUT’s of same type in order to find typical value for the DUT in question.\n", + "flag": 3, + "id": "TSI_HDMI_RX_LINK_LOW_LIMIT", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "Main link low voltage limit, mV", + "type": 0 + }, + { + "configId": "0x10204", + "defaultValue": "3100", + "description": "HDMI link line voltage high limit, in millivolts. The voltage detected from HDMI link line(s) during test must be less than this value in order to pass test. Default setting is 3100mV. Important: The acceptable setting for this value can be different for different types of DUT’s. Proper calibration of this value will require testing multiple DUT’s of same type in order to find typical value for the DUT in question.\n", + "flag": 3, + "id": "TSI_HDMI_RX_LINK_HIGH_LIMIT", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "Main link high voltage limit, mV", + "type": 0 + }, + { + "configId": "0x10205", + "defaultValue": "-50", + "description": "HDMI HPD logical zero voltage level, lower limit, in millivolts. When HPD line is expected to be in logical zero state, the measured voltage must be higher than this value in order to pass test. Default setting is 0mV.\n", + "flag": 3, + "id": "TSI_HDMI_RX_HPD_ZERO_LOW_LIMIT", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "HPD line logical zero low voltage limit, mV", + "type": 0 + }, + { + "configId": "0x10206", + "defaultValue": "400", + "description": "HDMI HPD logical zero voltage level, higher limit, in millivolts. When HDP line is expected to be in logical zero state, the measured voltage must be lower than this value in order to pass test. Default setting is 400mV.", + "flag": 3, + "id": "TSI_HDMI_RX_HPD_ZERO_HIGH_LIMIT", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "HPD line logical zero high voltage limit, mV", + "type": 0 + }, + { + "configId": "0x10207", + "defaultValue": "2400", + "description": "HDMI HPD logical one voltage level, lower limit, in millivolts. When HPD line is expected to be in logical one state, the measured voltage must be less than this value in order to pass test. Default setting is 2400mV.", + "flag": 3, + "id": "TSI_HDMI_RX_HPD_ONE_LOW_LIMIT", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "HPD line logical one low voltage limit, mV", + "type": 0 + }, + { + "configId": "0x10208", + "defaultValue": "5300", + "description": "HDMI HPD logical one voltage level, higher limit, in millivolts. When HPD line is expected to be in logical one state, the measured voltage must be less than this value in order to pass test. Default setting is 5300mV.", + "flag": 3, + "id": "TSI_HDMI_RX_HPD_ONE_HIGHT_LIMIT", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "HPD line logical one high voltage limit, mV", + "type": 0 + }, + { + "configId": "0x10209", + "defaultValue": "4500", + "description": "DDC Line voltage low limit, in millivolts. Test will measure DDC line voltage when the line is not being driven low. The measured value must be higher than this value in order to pass test. Default setting is 4500mV.", + "flag": 3, + "id": "TSI_HDMI_RX_DDC_LOW_LIMIT", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "DDC lines low voltage limit, mV", + "type": 0 + }, + { + "configId": "0x1020a", + "defaultValue": "5500", + "description": "DDC Line voltage high limit, in millivolts. Test will measure DDC line voltage when the line is not being driven low. The measured value must be lower than this value in order to pass test. Default setting is 5500mV.", + "flag": 3, + "id": "TSI_HDMI_RX_DDC_HIGH_LIMIT", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "DDC lines high voltage limit, mV", + "type": 0 + }, + { + "configId": "0x1020b", + "defaultValue": "-50", + "description": "CCE Line logical zero voltage level, lower limit, in millivolts. The CCE line voltage is measured when CCE line state is logical zero. The measured value must be higher than this value in order to pass test. Default setting is 0mV.", + "flag": 3, + "id": "TSI_HDMI_RX_CEC_ZERO_LOW_LIMIT", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "CEC line logical zero low voltage limit, mV", + "type": 0 + }, + { + "configId": "0x1020c", + "defaultValue": "600", + "description": "CCE Line logical zero voltage level, higher limit, in millivolts. The CCE line voltage is measured when CCE line state is logical zero. The measured value must be lower than this value in order to pass test. Default setting is 600mV.", + "flag": 3, + "id": "TSI_HDMI_RX_CEC_ZERO_HIGH_LIMIT", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "CEC line logical zero high voltage limit, mV", + "type": 0 + }, + { + "configId": "0x1020d", + "defaultValue": "2500", + "description": "CCE Line logical one voltage level, lower limit, in millivolts. The CCE line voltage is measured when CCE line state is logical one. The measured value must be higher than this setting in order to pass test. Default setting is 2500mV.", + "flag": 3, + "id": "TSI_HDMI_RX_CEC_ONE_LOW_LIMIT", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "CEC line logical one low voltage limit, mV", + "type": 0 + }, + { + "configId": "0x1020e", + "defaultValue": "3600", + "description": "CCE Line logical one voltage level, higher limit, in millivolts. The CCE line voltage is measured when CCE line state is logical one. The measured value must be lower that this setting in order to pass test. Default setting is 3600mV.", + "flag": 3, + "id": "TSI_HDMI_RX_CEC_ONE_HIGH_LIMIT", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "CEC line logical one high voltage limit, mV", + "type": 0 + } + ], + "id": "0x06", + "name": "Electrical Tests", + "old_names": [ + "HDMI Electrical Test Set" + ] +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x07]any_dut_usbc_electrical.json b/UniTAP/dev/modules/dut_tests/cfg/[0x07]any_dut_usbc_electrical.json new file mode 100644 index 0000000..1ec19a2 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x07]any_dut_usbc_electrical.json @@ -0,0 +1,300 @@ +{ + "descriptions": [ + { + "configId": "0x10500", + "defaultValue": "5000", + "description": "Defines the test activity maximum run-time, in milliseconds. Default setting is 5000ms. Important: When the test is waiting for DUT with a max. delay this timeout is not advancing during the wait.", + "flag": 3, + "id": "TSI_USBC_EL_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout, in milliseconds", + "type": 4 + }, + { + "configId": "0x10501", + "defaultValue": "0x0000000F", + "description": "Defines DUT capabilities.", + "bitList": + [ + {"mask": "0x00000001", "description": "DUT Support DisplayPort Alternate mode"}, + {"mask": "0x00000002", "description": "DUT can act as a power source"}, + {"mask": "0x00000004", "description": "DUT can act as a power sink"}, + {"mask": "0x00000008", "description": "DUT does not support PD Contract"} + ], + "flag": 3, + "id": "TSI_USBC_EL_DUT_CAPS", + "name": "Defines DUT USBC capabilities.", + "type": 12 + }, + { + "configId": "0x10502", + "defaultValue": "1500", + "description": "Defines the time period for USB Type-C re-plug simulation “disconnected” state. The period is defined in milliseconds. Default value is 1500ms.", + "flag": 3, + "id": "TSI_USBC_EL_REPLUG_TIME", + "maxValue": 2147483647, + "minValue": 0, + "name": "Re-plug duration, in milliseconds", + "type": 4 + }, + { + "configId": "0x10503", + "defaultValue": "10000", + "description": "Defines the time period that the TE will wait for DUT to complete power contract negotiation. Time is defined in milliseconds. Default value is 5000ms.\n", + "flag": 3, + "id": "TSI_USBC_EL_DUT_ATTACH_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "DUT attach timeout, in milliseconds", + "type": 4 + }, + { + "configId": "0x10504", + "defaultValue": "5000", + "description": "Defines power contract timeout, in milliseconds", + "flag": 3, + "id": "TSI_USBC_EL_PWR_CONTRACT_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Power Contract timeout, in milliseconds", + "type": 4 + }, + { + "configId": "0x10505", + "defaultValue": "261", + "description": "Defines the low limit for the voltage window when power sink current is 0.5A or 0.9A. The limit is defined in millivolts (mV). Default setting is 261mV.\n", + "flag": 3, + "id": "TSI_USBC_EL_CC_LOW_VOLTAGE_1", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "CC low voltage limit for default current (0.5A/0.9A), in mV", + "type": 0 + }, + { + "configId": "0x10506", + "defaultValue": "588", + "description": "Defines the high limit for the voltage window when power sink current is 0.5A or 0.9A. The limit is defined in millivolts (mV). Default settingf is 588mV.\n", + "flag": 3, + "id": "TSI_USBC_EL_CC_HI_VOLTAGE_1", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "CC high voltage limit for default current (0.5A/0.9A), in mV", + "type": 0 + }, + { + "configId": "0x10507", + "defaultValue": "675", + "description": "Defines the low limit for the voltage window when power sink current is 1.5A. The limit is defined in millivolts (mV). Default setting is 675mV.", + "flag": 3, + "id": "TSI_USBC_EL_CC_LOW_VOLTAGE_2", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "CC low voltage limit for 1.5A current, in mV", + "type": 0 + }, + { + "configId": "0x10508", + "defaultValue": "1189", + "description": "Defines the high limit for the voltage window when power sink current is 1.5A. The limit is defined in millivolts (mV). Default setting is 1189mV.", + "flag": 3, + "id": "TSI_USBC_EL_CC_HI_VOLTAGE_2", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "CC high voltage limit for 1.5A current, in mV", + "type": 0 + }, + { + "configId": "0x10509", + "defaultValue": "1238", + "description": "Defines the low limit for the voltage window when power sink current is 3.0A. The limit is defined in millivolts (mV). Default setting is 1238mV.\n", + "flag": 3, + "id": "TSI_USBC_EL_CC_LOW_VOLTAGE_3", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "CC low voltage limit for 3.0A, in mV", + "type": 0 + }, + { + "configId": "0x1050a", + "defaultValue": "2181", + "description": "Defines the high limit for the voltage window when power sink current is 3.0A. The limit is defined in millivolts (mV). Default setting is 2181mV.\n", + "flag": 3, + "id": "TSI_USBC_EL_CC_HI_VOLTAGE_3", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "CC high voltage limit for 3.0A, in mV", + "type": 0 + }, + { + "configId": "0x1050b", + "defaultValue": "4750", + "description": "Defines the low limit for the Vcon voltage window. The limit is defined in millivolts (mV). Default setting is 4750mV.\n", + "flag": 3, + "id": "TSI_USBC_EL_VCON_LOW_VOLTAGE", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "Vconn low voltage limit, in mV", + "type": 0 + }, + { + "configId": "0x1050c", + "defaultValue": "5500", + "description": "Defines the high limit for the Vcon voltage window. The limit is defined in millivolts (mV). Default setting is 5500mV.", + "flag": 3, + "id": "TSI_USBC_EL_VCON_HI_VOLTAGE", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "Vconn high voltage limit, in mV", + "type": 0 + }, + { + "configId": "0x1050d", + "defaultValue": "5000", + "description": "Defines the timeout the TE will wait for the DUT to enter into DisplayPort alternate mode. The timeout is defined in milliseconds. Default setting is 5000ms.\n ", + "flag": 3, + "id": "TSI_USBC_EL_DP_ALT_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "DP Alt timeout, in milliseconds", + "type": 4 + }, + { + "configId": "0x1050e", + "defaultValue": "100", + "description": "Defines the low voltage limit for the positive DP AUX line when idle. The limit is defined in millivolts (mV). Default setting 100mV.\n", + "flag": 3, + "id": "TSI_USBC_EL_AUX_P_IDLE_LOW_VOLTAGE", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "DP Alt Mode AUX + line idle low voltage limit, signed, mV", + "type": 0 + }, + { + "configId": "0x1050f", + "defaultValue": "600", + "description": "Defines the high voltage limit for the positive DP AUX line when idle. The limit is defined in millivolts (mV). Default setting is 600mV.", + "flag": 3, + "id": "TSI_USBC_EL_AUX_P_IDLE_HI_VOLTAGE", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "DP Alt Mode AUX + line idle high voltage limit, signed, mV", + "type": 0 + }, + { + "configId": "0x10510", + "defaultValue": "2500", + "description": "Defines the low voltage limit for the negative DP AUX line when idle. The limit is defined in millivolts (mV). Default setting is 2500mV.", + "flag": 3, + "id": "TSI_USBC_EL_AUX_N_IDLE_LOW_VOLTAGE", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "DP Alt Mode AUX - line idle low voltage limit, signed, mV", + "type": 0 + }, + { + "configId": "0x10511", + "defaultValue": "3000", + "description": "Defines the high voltage limit for the negative DP AUX line when idle. The limit is defined in millivolts (mV). Default setting is 3000mV", + "flag": 3, + "id": "TSI_USBC_EL_AUX_N_IDLE_HI_VOLTAGE", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "DP Alt Mode AUX - line idle high voltage limit, signed, mV", + "type": 0 + }, + { + "configId": "0x10512", + "defaultValue": "4750", + "description": "Defines the low limit for Vbus voltage window. The limit is defined in millivolts (mV). Default setting is 4750mV.", + "flag": 3, + "id": "TSI_USBC_EL_VBUS_LOW_VOLTAGE", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "Vbus voltage low limit, mV", + "type": 0 + }, + { + "configId": "0x10513", + "defaultValue": "5500", + "description": "Defines the high limit for Vbus voltage window. The limit is defined in millivolts (mV). Default setting is 5500mV.", + "flag": 3, + "id": "TSI_USBC_EL_VBUS_HI_VOLTAGE", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "Vbus voltage high limit, mV", + "type": 0 + }, + { + "configId": "0x10514", + "defaultValue": "100", + "description": "Defines the highest allowed deviation between maximum and minimum currents measured from the individual Vbus pins as per-mill (‰) of total measured current. This means that if the total measured current is 3000mA, and the setting 100, the maximum difference that is allowed between maximum and minimum currents is 300mA. Default setting is 100‰.", + "flag": 3, + "id": "TSI_USBC_EL_VBUS_CURRENT_MAX_DEV", + "maxValue": 2147483647, + "minValue": 0, + "name": "Vbus current deviation (between wires), mA", + "type": 4 + }, + { + "configId": "0x10515", + "defaultValue": "100", + "description": "Defines the highest allowed deviation between maximum and minimum currents measured from the individual GND pins as per-mill (‰) of total measured current. This means that if the total measured current is 3000mA, and the setting 100, the maximum difference that is allowed between maximum and minimum currents is 300mA. Default setting is 100‰.", + "flag": 3, + "id": "TSI_USBC_EL_GND_CURRENT_MAX_DEV", + "maxValue": 2147483647, + "minValue": 0, + "name": "Return (GND) current deviation (between wires), mA", + "type": 4 + }, + { + "configId": "0x10516", + "defaultValue": "2000", + "description": "Defines delay from end of power contract negotiation to voltage / current measurements. The delay is defined in milliseconds, and the default setting is 2000ms\n", + "flag": 3, + "id": "TSI_USBC_EL_PWR_MEASURE_DELAY", + "maxValue": 2147483647, + "minValue": 0, + "name": "Measurement delay, msec", + "type": 4 + }, + { + "configId": "0x10517", + "defaultValue": "5", + "description": "Defines the minimum current, in mA, that a Power Sink DUT must use in order to pass the test. Set this value to zero (0) to disable minimum current check. Default value is 5.", + "flag": 3, + "id": "TSI_USBC_EL_MIN_DUT_CURRENT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Minimum current, mA", + "type": 4 + }, + { + "configId": "0x10518", + "defaultValue": "0", + "description": "Delay after load resistor on, milliseconds. This helps to skip transient processes.\n", + "flag": 3, + "id": "TSI_USBC_EL_RES_ON_DELAY", + "maxValue": 2147483647, + "minValue": 0, + "name": "Delay after connection load resistors, ms", + "type": 4 + }, + { + "configId": "0x10519", + "defaultValue": "0", + "description": "Delay for measure CC lines voltage after DUT plug detection, milliseconds. For Pegatron factory default value is 1500 ms.\n", + "flag": 3, + "id": "TSI_USBC_EL_CC_MEASURE_DELAY", + "maxValue": 2147483647, + "minValue": 0, + "name": "Delay for measure CC lines voltage after DUT plug detection, milliseconds", + "type": 4 + } + ], + "id": "0x07", + "name": "USB-C Electrical Tests", + "old_names": [ + "USBC Electrical Tests" + ] +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x08]src_dut_dp_128b132b_ll_cts.json b/UniTAP/dev/modules/dut_tests/cfg/[0x08]src_dut_dp_128b132b_ll_cts.json new file mode 100644 index 0000000..9368997 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x08]src_dut_dp_128b132b_ll_cts.json @@ -0,0 +1,2917 @@ +{ + "descriptions": [ + { + "configId": "0x1190000", + "defaultValue": "5000", + "description": "Defines the timeout for DP LL CTS Source DUT tests. The timeout is defined as milliseconds, and has a default setting of 5000ms. Please note that if the test contains internal iterations, this timeout is re-used for each iteration. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP20_SRCCTS_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout in milliseconds.", + "type": 4 + }, + { + "configId": "0x1190001", + "defaultValue": "4", + "description": "Defines the maximum number of lanes supported by the DUT. Typical settings are 1, 2 and 4. Default value is 4. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.\n", + "enumerationVariants": "1\n2\n4", + "flag": 3, + "id": "TSI_DP20_SRCCTS_MAX_LANES", + "maxValue": 2147480000, + "minValue": 0, + "name": "Maximum number of lanes supported by the DUT.", + "type": 4 + }, + { + "configId": "0x1190002", + "defaultValue": "30", + "description": "Defines the maximum link rate as multiplier for 0.27Gbps. Typical settings are 6 (RBR), 10 (HBR), 20 (HBR2) and 30 (HBR3). Notice that HBR3 link rate is usable only with UCD-400. The default setting is 30 (HBR3). Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "6 # RBR (1.62 Gbps)\n10 # HBR (2.7 Gbps)\n20 # HBR2 (5.4 Gbps)\n30 # HBR3 (8.10 Gbps)", + "flag": 3, + "id": "TSI_DP20_SRCCTS_MAX_LINK_RATE", + "maxValue": 30, + "minValue": 6, + "name": "Maximum link rate supported by the DUT as multiplier for 0.27Gbps.", + "type": 4 + }, + { + "configId": "0x1190003", + "defaultValue": "0x00F03A53", + "description": "Defines the DUT capabilities as flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "Voltage swing level 3 (1.2V) supported"}, + {"mask": "0x00000002", "description": "Pre-emphasis level 3 (9.5dB) supported"}, + {"mask": "0x00000004", "description": "Fixed timing DUT supported"}, + {"mask": "0x00000008", "description": "Spread Spectrum support"}, + {"mask": "0x00000010", "description": "Video format change without LT supported"}, + {"mask": "0x00000040", "description": "E-DDC protocol supported"}, + {"mask": "0x00000080", "description": "Audio Info Frame for 2 channel audio transmission supported"}, + {"mask": "0x00000100", "description": "DUT is Type-C device"}, + {"mask": "0x00000200", "description": "FEC supported"}, + {"mask": "0x00000400", "description": "FEC disable sequence supported"}, + {"mask": "0x00000800", "description": "Audio without Video supported"}, + {"mask": "0x00001000", "description": "DSC supported"}, + {"mask": "0x00002000", "description": "DSC block prediction supported"}, + {"mask": "0x00004000", "description": "Max Link Bandwidth Policy supported"}, + {"mask": "0x00008000", "description": "Use YCbCr 444->422/420 3-tap filter"}, + {"mask": "0x00020000", "description": "USB4 tunnel presented"}, + {"mask": "0x00080000", "description": "DUT supports native Display ID read"}, + {"mask": "0x00100000", "description": "DisplayID Type VII Detailed Timing Descriptor supported"}, + {"mask": "0x00200000", "description": "DisplayID Type VIII Detailed Timing Descriptor supported"}, + {"mask": "0x00400000", "description": "DisplayID Type IX Detailed Timing Descriptor supported"}, + {"mask": "0x00800000", "description": "DisplayID Type X Detailed Timing Descriptor supported"}, + {"mask": "0x01000000", "description": "2x1 tiled display and DisplayID Tiled Display Topology data block supported"}, + {"mask": "0x02000000", "description": "Field sequential stereo and DisplayID Tiled Stereo Display Interface data block supported"}, + {"mask": "0x04000000", "description": "Stacked frame stereo and DisplayID Tiled Stereo Display Interface data block supported"}, + {"mask": "0x08000000", "description": "Dynamic Refresh Rate with VBlank stretch with MSA_TIMING_PAR_IGNORED supported"} + ], + "flag": 3, + "id": "TSI_DP20_SRCCTS_DUT_CAPS", + "name": "DUT Capability flags.", + "type": 12 + }, + { + "configId": "0x1190004", + "defaultValue": "0x0000001A", + "description": "Defines the DUT Test automation capabilities as flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "TEST_LINK_TRAINING"}, + {"mask": "0x00000002", "description": "TEST_EDID_READ"}, + {"mask": "0x00000004", "description": "TEST_VIDEO_PATTERN"}, + {"mask": "0x00000020", "description": "TEST_AUDIO_PATTERN"}, + {"mask": "0x00000018", "description": "Event indicating DUT ready", "enumerationVariants": "0 # Always ready\n1 # EDID read\n2 # Link Training end\n3 # Active Video"}, + {"mask": "0x00000040", "description": "VIDEO_OPERATOR_INPUT"}, + {"mask": "0x00000080", "description": "DSC_VIS_VAL"} + ], + "flag": 3, + "id": "TSI_DP20_SRCCTS_DUT_TA", + "name": "DUT Test automation flags.", + "type": 12 + }, + { + "configId": "0x1190005", + "defaultValue": "1000", + "description": "Defines the duration of long HPD pulses generated by the tests. The duration is defined in millieconds. Default setting is 1000ms. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.\n", + "flag": 3, + "id": "TSI_DP20_SRCCTS_LONG_HPD_PULSE", + "maxValue": 2147483647, + "minValue": 0, + "name": "Long HPD pulse duration, in milliseconds.", + "type": 4 + }, + { + "configId": "0x1190006", + "defaultValue": "5000", + "description": "Defines a timeout for Link Training start, in milliseconds. Default setting is 5000ms. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP20_SRCCTS_LT_START_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Link Training start timeout, in milliseconds.", + "type": 4 + }, + { + "configId": "0x1190007", + "defaultValue": "5000", + "description": "Defines an internal cycle delay used within tests that repeat a number of test steps. The delay is defined in milliseconds. Default setting is 3000ms. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_TEST_CYCLE_DELAY", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test cycle delay, in milliseconds", + "type": 4 + }, + { + "configId": "0x1190008", + "defaultValue": "2", + "description": "Defines number of color formats programmed into the CI’s TSI_DP20_SRCCTS_COLOR_MODE_0 to TSI_DP20_SRCCTS_COLOR_MODE_31. The default setting is 2. Important: The CTS Specification defines two mandatory color formats: 0x0 and 0x20. These color-format definitions must be present in the color mode table. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_FORMATS", + "maxValue": 2147483647, + "minValue": 0, + "name": "Number of valid color format definitions.", + "type": 4 + }, + { + "configId": "0x1190009", + "defaultValue": "1", + "description": "Defines the fail-safe mode ID. The default setting is 1 ( = 640 x 480 @ 60 Hz, 6 BPC). Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This setting should not be modified. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "enumerationVariants": "1 # 640 x 480 @ 60Hz 6 BPC", + "flag": 3, + "id": "TSI_DP20_SRCCTS_FAILSAFE_MODE", + "maxValue": 2147480000, + "minValue": 0, + "name": "Fail-safe video mode ID.", + "type": 4 + }, + { + "configId": "0x119000a", + "defaultValue": "22", + "description": "Defines the maximum resolution supported by the DUT. Default setting is 22 ( = 1920 x 1080 @ 60Hz, 8 BPC). Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "1 # 640 x 480 @ 60Hz 6 BPC\n2 # 848 x 480 @ 60Hz 8 BPC\n3 # 1280 x 720 @ 60Hz 8 BPC\n4 # 1280 x 960 @ 60Hz 8 BPC\n5 # 1920 x 1080 @ 60Hz 8 BPC\n6 # 1920 x 1440 @ 60Hz 8 BPC\n7 # 1920 x 1080 @ 120Hz 8 BPC\n8 # 1280 x 800 @ 60Hz RB1 6 BPC\n9 # 1280 x 768 @ 60Hz RB1 6 BPC\n10 # 800 x 600 @ 60Hz 10 BPC\n11 # 1024 x 768 @ 60Hz 10 BPC\n12 # 1280 x 1024 @ 60Hz 8 BPC\n13 # 1360 x 768 @ 60Hz 10 BPC\n14 # 1280 x 800 @ 60Hz RB1 10 BPC\n15 # 1400 x 1050 @ 60Hz RB1 8 BPC\n16 # 1280 x 768 @ 60Hz RB1 10 BPC\n17 # 1600 x 1200 @ 60Hz RB1 6 BPC\n18 # 2048 x 1536 @ 60Hz RB1 8 BPC\n19 # 1792 x 1344 @ 60Hz 8 BPC\n20 # 1600 x 1200 @ 60Hz RB1 10 BPC\n21 # 3840 x 2160 @ 30Hz 8 BPC\n22 # 3840 x 2160 @ 60Hz 8 BPC", + "flag": 3, + "id": "TSI_DP20_SRCCTS_MAX_RESOLUTION", + "maxValue": 2147480000, + "minValue": 0, + "name": "Maximum resolution supported by the DUT.", + "type": 4 + }, + { + "configId": "0x119000b", + "defaultValue": "0x00000001", + "description": "Defines link rates supported by DUT as it defined in the DP 2.1 specification.\n ", + "flag": 3, + "bitList": + [ + {"mask": "0x00000001", "description": "10 Gbps per lane supported"}, + {"mask": "0x00000002", "description": "20 Gbps per lane supported"}, + {"mask": "0x00000004", "description": "13.5 Gbps per lane supported"} + ], + "id": "TSI_DP20_SRCCTS_DUT_LINK_RATES", + "name": "Link rates supported by DUT", + "type": 12 + }, + { + "configId": "0x119000c", + "defaultValue": "0", + "description": "Defines number of LTTPR devices present between Source DUT and Reference Sink. Up to the eight devices. Default setting is 0.\n ", + "flag": 3, + "id": "TSI_DP20_SRCCTS_LTTPR_DEVICE_COUNT", + "maxValue": 8, + "minValue": 0, + "name": "Number of LTTPR devices present between Source DUT and Reference Sink", + "type": 4 + }, + { + "configId": "0x119000d", + "defaultValue": "262", + "description": "Defines number of minimum link bandwidth supported by Source DUT. Default setting is 0x106 (1LRBR - 1 lane and 1.62 Gbpc).\n ", + "enumerationVariants": "262 # 1LRBR\n266 # 1LHBR\n518 # 2LRBR\n522 # 2LHBR/1LHBR2", + "flag": 3, + "id": "TSI_DP20_SRCCTS_MAX_LINK_BW_POLICY", + "maxValue": 2147480000, + "minValue": 0, + "name": "Number of minimum link bandwidth supported by Source DUT", + "type": 4 + }, + { + "configId": "0x119000e", + "defaultValue": "0x00000000", + "description": "Defines the Debug mode configuration.", + "bitList": + [ + {"mask": "0x00000001", "description": "Continue on fail enabled"}, + {"mask": "0x00000002", "description": "Force video check enabled"} + ], + "flag": 3, + "id": "TSI_DP20_SRCCTS_DUT_DEBUG_CONF", + "name": "Debug mode configuration.", + "type": 12 + }, + { + "configId": "0x1190010", + "defaultValue": "32000", + "description": "Defines the minimum sample rate supported by the DUT in Hz. Allowed values are 32000, 44100, 48000, 88200, 96000, 176400 or 192000 Hz. Default setting is 32000 Hz.\n ", + "enumerationVariants": "32000\n44100\n48000\n88200\n96000\n176400\n192000", + "flag": 3, + "id": "TSI_DP20_SRCCTS_MIN_SAMPLE_RATE", + "maxValue": 2147480000, + "minValue": 0, + "name": "Minimum supported sample rate", + "type": 4 + }, + { + "configId": "0x1190011", + "defaultValue": "192000", + "description": "Defines the maximum sample rate supported by the DUT in Hz. Allowed values are 32000, 44100, 48000, 88200, 96000, 176400 or 192000 Hz. Default setting is 192000 Hz.\n ", + "enumerationVariants": "32000\n44100\n48000\n88200\n96000\n176400\n192000", + "flag": 3, + "id": "TSI_DP20_SRCCTS_MAX_SAMPLE_RATE", + "maxValue": 2147480000, + "minValue": 0, + "name": "Maximum supported sample rate", + "type": 4 + }, + { + "configId": "0x1190012", + "defaultValue": "2", + "description": "Defines the minimum number of channels for minimum sample rate. Default is 2 channels (Stereo). Highest valid channel count is 8. If the number is different from 2 channels, then the appropriate channel allocation must be provided through the CI 7.9.19 TSI_DP20_SRCCTS_CH_ALLOC1.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_CHANNELS1", + "maxValue": 8, + "minValue": 0, + "name": "Minimum number of channels for minimum sample rate", + "type": 4 + }, + { + "configId": "0x1190013", + "defaultValue": "1", + "description": "The audio parameters contain four pairs of audio channel count and channel allocation values. Please notice that maximum channel count allowed is 8. For two channels, the channel allocation of zero is considered valid; In this case, stereo is assumed (FL/FR).\nAllocation value\nChannel count/bit\nChannel assignments\n0x001 2 FL/FR. (Front Left, Front Right)\n0x002 1 LFE. (Low Frequency Effects)\n0x004 1 FC. (Front Center) \n0x008 2 RL/RR. (Rear Left, Rear Right).\n0x010 1 RC. (Rear Center). \n0x020 2 FLC/FRC. (Front Left off Center, Front Right off Center).\n0x040 2 RLC/RRC (Rear Left off Center, Rear Right off Center). \n0x080 2 FLW/FRW (Front Left Wide, Front Right Wide) \n0x100 2 FLH/FRH (Front Left High, Front Right High)\n0x200 1 TC (Top Center) \n0x400 1 FCH (Front Center High)\n", + "flag": 3, + "id": "TSI_DP20_SRCCTS_CH_ALLOC1", + "maxValue": 2147480000, + "minValue": 0, + "name": "Channel allocation bits", + "type": 4 + }, + { + "configId": "0x1190014", + "defaultValue": "6", + "description": "Defines the maximum number of channels for minimum sample rate. Default is 2 channels (Stereo). Highest valid channel count is 8. If the number is different from 2 channels, then the appropriate channel allocation must be provided through the CI 7.9.21 TSI_DP20_SRCCTS_CH_ALLOC2.\n ", + "flag": 3, + "id": "TSI_DP20_SRCCTS_CHANNELS2", + "maxValue": 8, + "minValue": 0, + "name": "Maximum number of channels for minimum sample rate", + "type": 4 + }, + { + "configId": "0x1190015", + "defaultValue": "15", + "description": "The audio parameters contain four pairs of audio channel count and channel allocation values. Please notice that maximum channel count allowed is 8. For two channels, the channel allocation of zero is considered valid; In this case, stereo is assumed (FL/FR).\nAllocation value\nChannel count/bit\nChannel assignments\n0x001 2 FL/FR. (Front Left, Front Right)\n0x002 1 LFE. (Low Frequency Effects)\n0x004 1 FC. (Front Center) \n0x008 2 RL/RR. (Rear Left, Rear Right).\n0x010 1 RC. (Rear Center). \n0x020 2 FLC/FRC. (Front Left off Center, Front Right off Center).\n0x040 2 RLC/RRC (Rear Left off Center, Rear Right off Center). \n0x080 2 FLW/FRW (Front Left Wide, Front Right Wide) \n0x100 2 FLH/FRH (Front Left High, Front Right High)\n0x200 1 TC (Top Center) \n0x400 1 FCH (Front Center High)\n", + "flag": 3, + "id": "TSI_DP20_SRCCTS_CH_ALLOC2", + "maxValue": 2147480000, + "minValue": 0, + "name": "Channel allocation bits 2", + "type": 4 + }, + { + "configId": "0x1190016", + "defaultValue": "2", + "description": "Defines the minimum number of channels for maximum sample rate. Default is 2 channels (Stereo). Highest valid channel count is 8. If the number is different from 2 channels, then the appropriate channel allocation must be provided through the CI 7.9.23 TSI_DP20_SRCCTS_CH_ALLOC3.\n ", + "flag": 3, + "id": "TSI_DP20_SRCCTS_CHANNELS3", + "maxValue": 8, + "minValue": 0, + "name": "Minimum number of channels for maximum sample rate", + "type": 4 + }, + { + "configId": "0x1190017", + "defaultValue": "1", + "description": "The audio parameters contain four pairs of audio channel count and channel allocation values. Please notice that maximum channel count allowed is 8. For two channels, the channel allocation of zero is considered valid; In this case, stereo is assumed (FL/FR).\nAllocation value\nChannel count/bit\nChannel assignments\n0x001 2 FL/FR. (Front Left, Front Right)\n0x002 1 LFE. (Low Frequency Effects)\n0x004 1 FC. (Front Center) \n0x008 2 RL/RR. (Rear Left, Rear Right).\n0x010 1 RC. (Rear Center). \n0x020 2 FLC/FRC. (Front Left off Center, Front Right off Center).\n0x040 2 RLC/RRC (Rear Left off Center, Rear Right off Center). \n0x080 2 FLW/FRW (Front Left Wide, Front Right Wide) \n0x100 2 FLH/FRH (Front Left High, Front Right High)\n0x200 1 TC (Top Center) \n0x400 1 FCH (Front Center High)\n", + "flag": 3, + "id": "TSI_DP20_SRCCTS_CH_ALLOC3", + "maxValue": 2147480000, + "minValue": 0, + "name": "Channel allocations bits 3", + "type": 4 + }, + { + "configId": "0x1190018", + "defaultValue": "6", + "description": "Defines the maximum number of channels for maximum sample rate. Default is 2 channels (Stereo). Highest valid channel count is 8. If the number is different from 2 channels, then the appropriate channel allocation must be provided through the CI 7.9.25 TSI_DP20_SRCCTS_CH_ALLOC4.\n ", + "flag": 3, + "id": "TSI_DP20_SRCCTS_CHANNELS4", + "maxValue": 8, + "minValue": 0, + "name": "Maximum number of channels for maximum sample rate", + "type": 4 + }, + { + "configId": "0x1190019", + "defaultValue": "15", + "description": "The audio parameters contain four pairs of audio channel count and channel allocation values. Please notice that maximum channel count allowed is 8. For two channels, the channel allocation of zero is considered valid; In this case, stereo is assumed (FL/FR).\nAllocation value\nChannel count/bit\nChannel assignments\n0x001 2 FL/FR. (Front Left, Front Right)\n0x002 1 LFE. (Low Frequency Effects)\n0x004 1 FC. (Front Center) \n0x008 2 RL/RR. (Rear Left, Rear Right).\n0x010 1 RC. (Rear Center). \n0x020 2 FLC/FRC. (Front Left off Center, Front Right off Center).\n0x040 2 RLC/RRC (Rear Left off Center, Rear Right off Center). \n0x080 2 FLW/FRW (Front Left Wide, Front Right Wide) \n0x100 2 FLH/FRH (Front Left High, Front Right High)\n0x200 1 TC (Top Center) \n0x400 1 FCH (Front Center High)\n", + "flag": 3, + "id": "TSI_DP20_SRCCTS_CH_ALLOC4", + "maxValue": 2147480000, + "minValue": 0, + "name": "Channel allocation bits 4", + "type": 4 + }, + { + "configId": "0x119001a", + "defaultValue": "0", + "description": "Defines the audio test pattern. Default setting is zero. Please see table below for allowed settings:\nValue Description \n0 Operator specific waveform \n1 Sawtooth waveform", + "enumerationVariants": "0 # Operator specific waveform\n1 # Sawtooth waveform", + "flag": 3, + "id": "TSI_DP20_SRCCTS_AUDIO_TEST_PATTERN", + "maxValue": 2147480000, + "minValue": 0, + "name": "Audio test pattern", + "type": 4 + }, + { + "configId": "0x119001b", + "defaultValue": "16", + "description": "Defines sample size used for EDID configuration while testing. The size is indicated as number of bits per sample. Default setting is 16. Valid settings are 16, 20 or 24.\n ", + "enumerationVariants": "16 # 16 bits / sample\n20 # 20 bits / sample\n24 # 24 bits / sample", + "flag": 3, + "id": "TSI_DP20_SRCCTS_EDID_SAMPLE_SIZE", + "maxValue": 2147480000, + "minValue": 0, + "name": "Sample size for EDID while testing", + "type": 4 + }, + { + "configId": "0x1190020", + "defaultValue": "9", + "description": "These three CI’s define the most packed timings for 1, 2 and 4 lane modes. Default for 1 lane is 8 ( = 1280 x 800 @ 60Hz RB1, 18BPP). Default 2 lanes is 12 ( = 1280 x 1024 @ 60Hz, 24BPP). Default for 4 lanes is 18 ( = 2048 x 1536 @ 60Hz RB1, 24BPP). Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n10 # DMT 800 x 600p @ 60.317Hz, 10 bpc\n11 # DMT 1024 x 768p @ 60Hz, 6 bpc\n8 # CVT 1280 x 800p @ 60Hz, RB1, 6 bpc\n9 # DMT 1280 x 768p @ 60Hz, RB1, 6 bpc\n23 # CTA 1440 x 480p @ 59.94Hz, 8 bpc\n24 # CTA 1440 x 576p @ 50Hz, 8 bpc", + "flag": 3, + "id": "TSI_DP20_SRCCTS_1L_MOST_PACKED", + "maxValue": 2147480000, + "minValue": 0, + "name": "Most packed video timing definitions for 1 lanes links", + "type": 4 + }, + { + "configId": "0x1190021", + "defaultValue": "15", + "description": "These three CI’s define the most packed timings for 1, 2 and 4 lane modes. Default for 1 lane is 8 ( = 1280 x 800 @ 60Hz RB1, 18BPP). Default 2 lanes is 12 ( = 1280 x 1024 @ 60Hz, 24BPP). Default for 4 lanes is 18 ( = 2048 x 1536 @ 60Hz RB1, 24BPP). Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n16 # DMT 1280 x 768p @ 60Hz, 10 bpc\n14 # CVT 1280 x 800p @ 60Hz, 10 bpc\n4 # DMT 1280 x 960p @ 60Hz, 8 bpc\n12 # DMT 1280 x 1024p @ 60Hz, 8 bpc\n13 # DMT 1360 x 768p @ 60Hz, 10 bpc\n15 # DMT 1400 x 1050p @ 60Hz, RB1, 8 bpc\n17 # CVT 1600 x 1200p @ 60Hz, RB1, 6 bpc\n13 # DMT 1360 x 768p @ 60Hz, 8 bpc\n26 # CVT 1280 x 800p @ 60Hz, 8 bpc\n25 # DMT 1280 x 768p @ 60Hz, 8 bpc", + "flag": 3, + "id": "TSI_DP20_SRCCTS_2L_MOST_PACKED", + "maxValue": 2147480000, + "minValue": 0, + "name": "Most packed video timing definitions for 2 lanes links", + "type": 4 + }, + { + "configId": "0x1190022", + "defaultValue": "29", + "description": "These three CI’s define the most packed timings for 1, 2 and 4 lane modes. Default for 1 lane is 8 ( = 1280 x 800 @ 60Hz RB1, 18BPP). Default 2 lanes is 12 ( = 1280 x 1024 @ 60Hz, 24BPP). Default for 4 lanes is 18 ( = 2048 x 1536 @ 60Hz RB1, 24BPP). Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n20 # DMT 1600 x 1200p @ 60Hz, 10 bpc\n28 # DMT 1600 x 1200p @ 60Hz, 8 bpc\n16 # DMT 1792 x 1344p @ 60Hz, 8 bpc\n29 # CTA 1920 x 1080p @ 60Hz, 10 bpc\n5 # CTA 1920 x 1080p @ 60Hz, 8 bpc\n18 # CVT 2048 x 1536p @ 60Hz, RB1, 8 bpc", + "flag": 3, + "id": "TSI_DP20_SRCCTS_4L_MOST_PACKED", + "maxValue": 2147480000, + "minValue": 0, + "name": "Most packed video timing definitions for 4 lanes links", + "type": 4 + }, + { + "configId": "0x1190023", + "defaultValue": "2", + "description": "These 12 CI’s define the video modes used for timestamp generations for RBR, HBR, HBR2 and HBR3 link rates with 1, 2 and 4 lanes configurations. Please see 6.9.1 Test ID definitions, configuration items for defaults. Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n1 # 640 x 480 @ 60Hz 6 BPC\n2 # 848 x 480 @ 60Hz 8 BPC", + "flag": 3, + "id": "TSI_DP20_SRCCTS_1L_RBB", + "maxValue": 2147480000, + "minValue": 0, + "name": "Video timings used for Reduced Bitrate (RBR, 1.62Gbps) testing for 1 lanes.", + "type": 4 + }, + { + "configId": "0x1190024", + "defaultValue": "3", + "description": "These 12 CI’s define the video modes used for timestamp generations for RBR, HBR, HBR2 and HBR3 link rates with 1, 2 and 4 lanes configurations. Please see 6.9.1 Test ID definitions, configuration items for defaults. Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n1 # 640 x 480 @ 60Hz 6 BPC\n2 # 848 x 480 @ 60Hz 8 BPC\n3 # 1280 x 720 @ 60Hz 8 BPC", + "flag": 3, + "id": "TSI_DP20_SRCCTS_2L_RBB", + "maxValue": 2147480000, + "minValue": 0, + "name": "Video timings used for Reduced Bitrate (RBR, 1.62Gbps) testing for 2 lanes.", + "type": 4 + }, + { + "configId": "0x1190025", + "defaultValue": "5", + "description": "These 12 CI’s define the video modes used for timestamp generations for RBR, HBR, HBR2 and HBR3 link rates with 1, 2 and 4 lanes configurations. Please see 6.9.1 Test ID definitions, configuration items for defaults. Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n1 # 640 x 480 @ 60Hz 6 BPC\n2 # 848 x 480 @ 60Hz 8 BPC\n3 # 1280 x 720 @ 60Hz 8 BPC\n4 # 1280 x 960 @ 60Hz 8 BPC\n5 # 1920 x 1080 @ 60Hz 8 BPC", + "flag": 3, + "id": "TSI_DP20_SRCCTS_4L_RBB", + "maxValue": 2147480000, + "minValue": 0, + "name": "Video timings used for Reduced Bitrate (RBR, 1.62Gbps) testing for 4 lanes.", + "type": 4 + }, + { + "configId": "0x1190026", + "defaultValue": "3", + "description": "These 12 CI’s define the video modes used for timestamp generations for RBR, HBR, HBR2 and HBR3 link rates with 1, 2 and 4 lanes configurations. Please see 6.9.1 Test ID definitions, configuration items for defaults. Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n1 # 640 x 480 @ 60Hz 6 BPC\n2 # 848 x 480 @ 60Hz 8 BPC\n3 # 1280 x 720 @ 60Hz 8 BPC", + "flag": 3, + "id": "TSI_DP20_SRCCTS_1L_HBR", + "maxValue": 2147480000, + "minValue": 0, + "name": "Video timings used for High Bitrate (HBR, 2.7Gbps) testing for 1 lanes", + "type": 4 + }, + { + "configId": "0x1190027", + "defaultValue": "4", + "description": "These 12 CI’s define the video modes used for timestamp generations for RBR, HBR, HBR2 and HBR3 link rates with 1, 2 and 4 lanes configurations. Please see 6.9.1 Test ID definitions, configuration items for defaults. Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n1 # 640 x 480 @ 60Hz 6 BPC\n2 # 848 x 480 @ 60Hz 8 BPC\n3 # 1280 x 720 @ 60Hz 8 BPC\n4 # 1280 x 960 @ 60Hz 8 BPC", + "flag": 3, + "id": "TSI_DP20_SRCCTS_2L_HBR", + "maxValue": 2147480000, + "minValue": 0, + "name": "Video timings used for High Bitrate (HBR, 2.7Gbps) testing for 2 lanes", + "type": 4 + }, + { + "configId": "0x1190028", + "defaultValue": "5", + "description": "These 12 CI’s define the video modes used for timestamp generations for RBR, HBR, HBR2 and HBR3 link rates with 1, 2 and 4 lanes configurations. Please see 6.9.1 Test ID definitions, configuration items for defaults. Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n1 # 640 x 480 @ 60Hz 6 BPC\n2 # 848 x 480 @ 60Hz 8 BPC\n3 # 1280 x 720 @ 60Hz 8 BPC\n4 # 1280 x 960 @ 60Hz 8 BPC\n5 # 1920 x 1080 @ 60Hz 8 BPC\n6 # 1920 x 1440 @ 60Hz 8 BPC", + "flag": 3, + "id": "TSI_DP20_SRCCTS_4L_HBR", + "maxValue": 2147480000, + "minValue": 0, + "name": "Video timings used for High Bitrate (HBR, 2.7Gbps) testing for 4 lanes", + "type": 4 + }, + { + "configId": "0x1190029", + "defaultValue": "4", + "description": "These 12 CI’s define the video modes used for timestamp generations for RBR, HBR, HBR2 and HBR3 link rates with 1, 2 and 4 lanes configurations. Please see 6.9.1 Test ID definitions, configuration items for defaults. Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n1 # 640 x 480 @ 60Hz 6 BPC\n2 # 848 x 480 @ 60Hz 8 BPC\n3 # 1280 x 720 @ 60Hz 8 BPC\n4 # 1280 x 960 @ 60Hz 8 BPC", + "flag": 3, + "id": "TSI_DP20_SRCCTS_1L_HBR2", + "maxValue": 2147480000, + "minValue": 0, + "name": "Video timings used for High Bitrate-2 (HBR2, 5.4Gbps) testing for 1 lanes.", + "type": 4 + }, + { + "configId": "0x119002a", + "defaultValue": "5", + "description": "These 12 CI’s define the video modes used for timestamp generations for RBR, HBR, HBR2 and HBR3 link rates with 1, 2 and 4 lanes configurations. Please see 6.9.1 Test ID definitions, configuration items for defaults. Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n1 # 640 x 480 @ 60Hz 6 BPC\n2 # 848 x 480 @ 60Hz 8 BPC\n3 # 1280 x 720 @ 60Hz 8 BPC\n4 # 1280 x 960 @ 60Hz 8 BPC\n5 # 1920 x 1080 @ 60Hz 8 BPC\n6 # 1920 x 1440 @ 60Hz 8 BPC", + "flag": 3, + "id": "TSI_DP20_SRCCTS_2L_HBR2", + "maxValue": 2147480000, + "minValue": 0, + "name": "Video timings used for High Bitrate-2 (HBR2, 5.4Gbps) testing for 2 lanes.", + "type": 4 + }, + { + "configId": "0x119002b", + "defaultValue": "7", + "description": "These 12 CI’s define the video modes used for timestamp generations for RBR, HBR, HBR2 and HBR3 link rates with 1, 2 and 4 lanes configurations. Please see 6.9.1 Test ID definitions, configuration items for defaults. Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n1 # 640 x 480 @ 60Hz 6 BPC\n2 # 848 x 480 @ 60Hz 8 BPC\n3 # 1280 x 720 @ 60Hz 8 BPC\n4 # 1280 x 960 @ 60Hz 8 BPC\n5 # 1920 x 1080 @ 60Hz 8 BPC\n6 # 1920 x 1440 @ 60Hz 8 BPC\n7 # 1920 x 1080 @ 120Hz 8 BPC", + "flag": 3, + "id": "TSI_DP20_SRCCTS_4L_HBR2", + "maxValue": 2147480000, + "minValue": 0, + "name": "Video timings used for High Bitrate-2 (HBR2, 5.4Gbps) testing for 4 lanes.", + "type": 4 + }, + { + "configId": "0x119002c", + "defaultValue": "6", + "description": "These 12 CI’s define the video modes used for timestamp generations for RBR, HBR, HBR2 and HBR3 link rates with 1, 2 and 4 lanes configurations. Please see 6.9.1 Test ID definitions, configuration items for defaults. Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n1 # 640 x 480 @ 60Hz 6 BPC\n2 # 848 x 480 @ 60Hz 8 BPC\n3 # 1280 x 720 @ 60Hz 8 BPC\n4 # 1280 x 960 @ 60Hz 8 BPC\n5 # 1920 x 1080 @ 60Hz 8 BPC\n6 # 1920 x 1440 @ 60Hz 8 BPC", + "flag": 3, + "id": "TSI_DP20_SRCCTS_1L_HBR3", + "maxValue": 2147480000, + "minValue": 0, + "name": "Video timings used for High Bitrate-3 (HBR3, 8.1Gbps) testing for 1 lanes.", + "type": 4 + }, + { + "configId": "0x119002d", + "defaultValue": "21", + "description": "These 12 CI’s define the video modes used for timestamp generations for RBR, HBR, HBR2 and HBR3 link rates with 1, 2 and 4 lanes configurations. Please see 6.9.1 Test ID definitions, configuration items for defaults. Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n1 # 640 x 480 @ 60Hz 6 BPC\n2 # 848 x 480 @ 60Hz 8 BPC\n3 # 1280 x 720 @ 60Hz 8 BPC\n4 # 1280 x 960 @ 60Hz 8 BPC\n5 # 1920 x 1080 @ 60Hz 8 BPC\n6 # 1920 x 1440 @ 60Hz 8 BPC\n7 # 1920 x 1080 @ 120Hz 8 BPC\n8 # 1280 x 800 @ 60Hz RB1 6 BPC\n9 # 1280 x 768 @ 60Hz RB1 6 BPC\n10 # 800 x 600 @ 60Hz 10 BPC\n11 # 1024 x 768 @ 60Hz 10 BPC\n12 # 1280 x 1024 @ 60Hz 8 BPC\n13 # 1360 x 768 @ 60Hz 10 BPC\n14 # 1280 x 800 @ 60Hz RB1 10 BPC\n15 # 1400 x 1050 @ 60Hz RB1 8 BPC\n16 # 1280 x 768 @ 60Hz RB1 10 BPC\n17 # 1600 x 1200 @ 60Hz RB1 6 BPC\n18 # 2048 x 1536 @ 60Hz RB1 8 BPC\n19 # 1792 x 1344 @ 60Hz 8 BPC\n20 # 1600 x 1200 @ 60Hz RB1 10 BPC\n21 # 3840 x 2160 @ 30Hz 8 BPC", + "flag": 3, + "id": "TSI_DP20_SRCCTS_2L_HBR3", + "maxValue": 2147480000, + "minValue": 0, + "name": "Video timings used for High Bitrate-3 (HBR3, 8.1Gbps) testing for 2 lanes.", + "type": 4 + }, + { + "configId": "0x119002e", + "defaultValue": "22", + "description": "These 12 CI’s define the video modes used for timestamp generations for RBR, HBR, HBR2 and HBR3 link rates with 1, 2 and 4 lanes configurations. Please see 6.9.1 Test ID definitions, configuration items for defaults. Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n1 # 640 x 480 @ 60Hz 6 BPC\n2 # 848 x 480 @ 60Hz 8 BPC\n3 # 1280 x 720 @ 60Hz 8 BPC\n4 # 1280 x 960 @ 60Hz 8 BPC\n5 # 1920 x 1080 @ 60Hz 8 BPC\n6 # 1920 x 1440 @ 60Hz 8 BPC\n7 # 1920 x 1080 @ 120Hz 8 BPC\n8 # 1280 x 800 @ 60Hz RB1 6 BPC\n9 # 1280 x 768 @ 60Hz RB1 6 BPC\n10 # 800 x 600 @ 60Hz 10 BPC\n11 # 1024 x 768 @ 60Hz 10 BPC\n12 # 1280 x 1024 @ 60Hz 8 BPC\n13 # 1360 x 768 @ 60Hz 10 BPC\n14 # 1280 x 800 @ 60Hz RB1 10 BPC\n15 # 1400 x 1050 @ 60Hz RB1 8 BPC\n16 # 1280 x 768 @ 60Hz RB1 10 BPC\n17 # 1600 x 1200 @ 60Hz RB1 6 BPC\n18 # 2048 x 1536 @ 60Hz RB1 8 BPC\n19 # 1792 x 1344 @ 60Hz 8 BPC\n20 # 1600 x 1200 @ 60Hz RB1 10 BPC\n21 # 3840 x 2160 @ 30Hz 8 BPC\n22 # 3840 x 2160 @ 60Hz 8 BPC", + "flag": 3, + "id": "TSI_DP20_SRCCTS_4L_HBR3", + "maxValue": 2147480000, + "minValue": 0, + "name": "Video timings used for High Bitrate-3 (HBR3, 8.1Gbps) testing for 4 lanes.", + "type": 4 + }, + { + "configId": "0x1190038", + "defaultValue": "0", + "description": "These parameters are considered mandatory and should not be changed.", + "flag": 1, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_0", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_0", + "type": 4 + }, + { + "configId": "0x1190039", + "defaultValue": "32", + "description": "These parameters are considered mandatory and should not be changed.", + "flag": 1, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_1", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_1", + "type": 4 + }, + { + "configId": "0x119003a", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_2", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_2", + "type": 4 + }, + { + "configId": "0x119003b", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_3", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_3", + "type": 4 + }, + { + "configId": "0x119003c", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_4", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_4", + "type": 4 + }, + { + "configId": "0x119003d", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_5", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_5", + "type": 4 + }, + { + "configId": "0x119003e", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_6", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_6", + "type": 4 + }, + { + "configId": "0x119003f", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_7", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_7", + "type": 4 + }, + { + "configId": "0x1190040", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_8", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_8", + "type": 4 + }, + { + "configId": "0x1190041", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_9", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_9", + "type": 4 + }, + { + "configId": "0x1190042", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_10", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_10", + "type": 4 + }, + { + "configId": "0x1190043", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_11", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_11", + "type": 4 + }, + { + "configId": "0x1190044", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_12", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_12", + "type": 4 + }, + { + "configId": "0x1190045", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_13", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_13", + "type": 4 + }, + { + "configId": "0x1190046", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_14", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_14", + "type": 4 + }, + { + "configId": "0x1190047", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_15", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_15", + "type": 4 + }, + { + "configId": "0x1190048", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_16", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_16", + "type": 4 + }, + { + "configId": "0x1190049", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_17", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_17", + "type": 4 + }, + { + "configId": "0x119004a", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_18", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_18", + "type": 4 + }, + { + "configId": "0x119004b", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_19", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_19", + "type": 4 + }, + { + "configId": "0x119004c", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_20", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_20", + "type": 4 + }, + { + "configId": "0x119004d", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_21", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_21", + "type": 4 + }, + { + "configId": "0x119004e", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_22", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_22", + "type": 4 + }, + { + "configId": "0x119004f", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_23", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_23", + "type": 4 + }, + { + "configId": "0x1190050", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_24", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_24", + "type": 4 + }, + { + "configId": "0x1190051", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_25", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_25", + "type": 4 + }, + { + "configId": "0x1190052", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_26", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_26", + "type": 4 + }, + { + "configId": "0x1190053", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_27", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_27", + "type": 4 + }, + { + "configId": "0x1190054", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_28", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_28", + "type": 4 + }, + { + "configId": "0x1190055", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_29", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_29", + "type": 4 + }, + { + "configId": "0x1190056", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_30", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_30", + "type": 4 + }, + { + "configId": "0x1190057", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_31", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_31", + "type": 4 + }, + { + "configId": "0x1190058", + "defaultValue": "1", + "description": "Defines DUT DSC maximum supported slice. Default setting is 1. Valid settings are 1, 2, 4, 8, 10, 12, 16, 20, 24.\n ", + "enumerationVariants": "1\n2\n4\n8\n10\n12\n16\n20\n24", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_DUT_MAX_SLICE", + "maxValue": 24, + "minValue": 1, + "name": "DUT DSC maximum supported slice", + "type": 4 + }, + { + "configId": "0x1190059", + "defaultValue": "65538", + "description": "Defines DSC version. Default setting is 1.2. Valid settings are 1.1 and 1.2.\n ", + "enumerationVariants": "65537 # 1.1\n65538 # 1.2", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VERSION", + "maxValue": 65538, + "minValue": 65537, + "name": "DSC version", + "type": 4 + }, + { + "configId": "0x119005a", + "defaultValue": "0", + "description": "1920x1080 @ 30Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_0_ID", + "maxValue": 0, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_VMT_0_ID", + "type": 4 + }, + { + "configId": "0x119005b", + "defaultValue": "1", + "description": "Defines the bitmap with Lane count and Link rate. Default setting is zero. Please see table below for defined capability flags:\nBits Description \n0 ONE_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n1 ONE_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n2 ONE_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n 3 ONE_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n4 TWO_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n5 TWO_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n6 TWO_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n7 TWO_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n8 FOUR_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported.\n9 FOUR_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported.\n10 FOUR_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n11 FOUR_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported.\n31:12 RESERVED, SET TO ZERO\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_0_LC_LR", + "mask": 1, + "name": "Lane count and Link rate flags", + "type": 4 + }, + { + "configId": "0x119005c", + "defaultValue": "0x00000001", + "description": "Defines the video mode timing.\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "bitList": + [ + {"mask": "0x00000001", "description": "CTA support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000002", "description": "RB1 support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000004", "description": "RB2 support. 0 = Not supported, 1 = Supported."} + ], + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_0_TIMINGS", + "name": "Lane count and Link rate flags.", + "type": 12 + }, + { + "configId": "0x119005d", + "defaultValue": "1", + "description": "1920x1080 @ 60Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_1_ID", + "maxValue": 1, + "minValue": 1, + "name": "TSI_DP20_SRCCTS_DSC_VMT_1_ID", + "type": 4 + }, + { + "configId": "0x119005e", + "defaultValue": "1", + "description": "Defines the bitmap with Lane count and Link rate. Default setting is zero. Please see table below for defined capability flags:\nBits Description \n0 ONE_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n1 ONE_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n2 ONE_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n 3 ONE_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n4 TWO_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n5 TWO_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n6 TWO_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n7 TWO_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n8 FOUR_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported.\n9 FOUR_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported.\n10 FOUR_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n11 FOUR_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported.\n31:12 RESERVED, SET TO ZERO\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_1_LC_LR", + "mask": 1, + "name": "Lane count and Link rate flags", + "type": 4 + }, + { + "configId": "0x119005f", + "defaultValue": "0x00000000", + "description": "Defines the video mode timing.\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "bitList": + [ + {"mask": "0x00000001", "description": "CTA support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000002", "description": "RB1 support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000004", "description": "RB2 support. 0 = Not supported, 1 = Supported."} + ], + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_1_TIMINGS", + "name": "Lane count and Link rate flags.", + "type": 12 + }, + { + "configId": "0x1190060", + "defaultValue": "2", + "description": "1920x1080 @ 120Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_2_ID", + "maxValue": 2, + "minValue": 2, + "name": "TSI_DP20_SRCCTS_DSC_VMT_2_ID", + "type": 4 + }, + { + "configId": "0x1190061", + "defaultValue": "292", + "description": "Defines the bitmap with Lane count and Link rate. Default setting is zero. Please see table below for defined capability flags:\nBits Description \n0 ONE_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n1 ONE_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n2 ONE_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n 3 ONE_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n4 TWO_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n5 TWO_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n6 TWO_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n7 TWO_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n8 FOUR_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported.\n9 FOUR_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported.\n10 FOUR_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n11 FOUR_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported.\n31:12 RESERVED, SET TO ZERO\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_2_LC_LR", + "mask": 1911, + "name": "Lane count and Link rate flags.", + "type": 4 + }, + { + "configId": "0x1190062", + "defaultValue": "0x00000000", + "description": "Defines the video mode timing.\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "bitList": + [ + {"mask": "0x00000001", "description": "CTA support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000002", "description": "RB1 support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000004", "description": "RB2 support. 0 = Not supported, 1 = Supported."} + ], + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_2_TIMINGS", + "name": "Lane count and Link rate flags.", + "type": 12 + }, + { + "configId": "0x1190063", + "defaultValue": "3", + "description": "3840x2160 @ 30Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_3_ID", + "maxValue": 3, + "minValue": 3, + "name": "TSI_DP20_SRCCTS_DSC_VMT_3_ID", + "type": 4 + }, + { + "configId": "0x1190064", + "defaultValue": "292", + "description": "Defines the bitmap with Lane count and Link rate. Default setting is zero. Please see table below for defined capability flags:\nBits Description \n0 ONE_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n1 ONE_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n2 ONE_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n 3 ONE_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n4 TWO_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n5 TWO_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n6 TWO_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n7 TWO_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n8 FOUR_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported.\n9 FOUR_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported.\n10 FOUR_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n11 FOUR_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported.\n31:12 RESERVED, SET TO ZERO\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_3_LC_LR", + "mask": 1911, + "name": "Lane count and Link rate flags.", + "type": 4 + }, + { + "configId": "0x1190065", + "defaultValue": "0x00000000", + "description": "Defines the video mode timing.\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "bitList": + [ + {"mask": "0x00000001", "description": "CTA support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000002", "description": "RB1 support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000004", "description": "RB2 support. 0 = Not supported, 1 = Supported."} + ], + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_3_TIMINGS", + "name": "Lane count and Link rate flags.", + "type": 12 + }, + { + "configId": "0x1190066", + "defaultValue": "4", + "description": "3840x2160 @ 60Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_4_ID", + "maxValue": 4, + "minValue": 4, + "name": "TSI_DP20_SRCCTS_DSC_VMT_4_ID", + "type": 4 + }, + { + "configId": "0x1190067", + "defaultValue": "328", + "description": "Defines the bitmap with Lane count and Link rate. Default setting is zero. Please see table below for defined capability flags:\nBits Description \n0 ONE_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n1 ONE_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n2 ONE_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n 3 ONE_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n4 TWO_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n5 TWO_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n6 TWO_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n7 TWO_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n8 FOUR_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported.\n9 FOUR_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported.\n10 FOUR_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n11 FOUR_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported.\n31:12 RESERVED, SET TO ZERO\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_4_LC_LR", + "mask": 3549, + "name": "Lane count and Link rate flags.", + "type": 4 + }, + { + "configId": "0x1190068", + "defaultValue": "0x00000000", + "description": "Defines the video mode timing.\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "bitList": + [ + {"mask": "0x00000001", "description": "CTA support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000002", "description": "RB1 support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000004", "description": "RB2 support. 0 = Not supported, 1 = Supported."} + ], + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_4_TIMINGS", + "name": "Lane count and Link rate flags.", + "type": 12 + }, + { + "configId": "0x1190069", + "defaultValue": "5", + "description": "3840x2160 @ 120Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_5_ID", + "maxValue": 5, + "minValue": 5, + "name": "TSI_DP20_SRCCTS_DSC_VMT_5_ID", + "type": 4 + }, + { + "configId": "0x119006a", + "defaultValue": "1152", + "description": "Defines the bitmap with Lane count and Link rate. Default setting is zero. Please see table below for defined capability flags:\nBits Description \n0 ONE_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n1 ONE_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n2 ONE_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n 3 ONE_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n4 TWO_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n5 TWO_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n6 TWO_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n7 TWO_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n8 FOUR_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported.\n9 FOUR_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported.\n10 FOUR_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n11 FOUR_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported.\n31:12 RESERVED, SET TO ZERO\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_5_LC_LR", + "mask": 3264, + "name": "Lane count and Link rate flags.", + "type": 4 + }, + { + "configId": "0x119006b", + "defaultValue": "0x00000000", + "description": "Defines the video mode timing.\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "bitList": + [ + {"mask": "0x00000001", "description": "CTA support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000002", "description": "RB1 support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000004", "description": "RB2 support. 0 = Not supported, 1 = Supported."} + ], + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_5_TIMINGS", + "name": "Lane count and Link rate flags.", + "type": 12 + }, + { + "configId": "0x119006c", + "defaultValue": "6", + "description": "5120x2160 @ 30Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_6_ID", + "maxValue": 6, + "minValue": 6, + "name": "TSI_DP20_SRCCTS_DSC_VMT_6_ID", + "type": 4 + }, + { + "configId": "0x119006d", + "defaultValue": "292", + "description": "Defines the bitmap with Lane count and Link rate. Default setting is zero. Please see table below for defined capability flags:\nBits Description \n0 ONE_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n1 ONE_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n2 ONE_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n 3 ONE_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n4 TWO_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n5 TWO_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n6 TWO_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n7 TWO_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n8 FOUR_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported.\n9 FOUR_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported.\n10 FOUR_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n11 FOUR_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported.\n31:12 RESERVED, SET TO ZERO\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_6_LC_LR", + "mask": 1911, + "name": "Lane count and Link rate flags.", + "type": 4 + }, + { + "configId": "0x119006e", + "defaultValue": "0x00000000", + "description": "Defines the video mode timing.\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "bitList": + [ + {"mask": "0x00000001", "description": "CTA support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000002", "description": "RB1 support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000004", "description": "RB2 support. 0 = Not supported, 1 = Supported."} + ], + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_6_TIMINGS", + "name": "Lane count and Link rate flags.", + "type": 12 + }, + { + "configId": "0x119006f", + "defaultValue": "7", + "description": "5120x2160 @ 60Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_7_ID", + "maxValue": 7, + "minValue": 7, + "name": "TSI_DP20_SRCCTS_DSC_VMT_6_ID", + "type": 4 + }, + { + "configId": "0x1190070", + "defaultValue": "584", + "description": "Defines the bitmap with Lane count and Link rate. Default setting is zero. Please see table below for defined capability flags:\nBits Description \n0 ONE_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n1 ONE_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n2 ONE_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n 3 ONE_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n4 TWO_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n5 TWO_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n6 TWO_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n7 TWO_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n8 FOUR_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported.\n9 FOUR_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported.\n10 FOUR_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n11 FOUR_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported.\n31:12 RESERVED, SET TO ZERO\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_7_LC_LR", + "mask": 3822, + "name": "Lane count and Link rate flags.", + "type": 4 + }, + { + "configId": "0x1190071", + "defaultValue": "0x00000000", + "description": "Defines the video mode timing.\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "bitList": + [ + {"mask": "0x00000001", "description": "CTA support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000002", "description": "RB1 support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000004", "description": "RB2 support. 0 = Not supported, 1 = Supported."} + ], + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_7_TIMINGS", + "name": "Lane count and Link rate flags.", + "type": 12 + }, + { + "configId": "0x1190072", + "defaultValue": "8", + "description": "5120x2160 @ 120Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_8_ID", + "maxValue": 8, + "minValue": 8, + "name": "TSI_DP20_SRCCTS_DSC_VMT_8_ID", + "type": 4 + }, + { + "configId": "0x1190073", + "defaultValue": "1152", + "description": "Defines the bitmap with Lane count and Link rate. Default setting is zero. Please see table below for defined capability flags:\nBits Description \n0 ONE_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n1 ONE_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n2 ONE_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n 3 ONE_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n4 TWO_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n5 TWO_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n6 TWO_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n7 TWO_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n8 FOUR_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported.\n9 FOUR_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported.\n10 FOUR_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n11 FOUR_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported.\n31:12 RESERVED, SET TO ZERO\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_8_LC_LR", + "mask": 3264, + "name": "Lane count and Link rate flags.", + "type": 4 + }, + { + "configId": "0x1190074", + "defaultValue": "0x00000000", + "description": "Defines the video mode timing.\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "bitList": + [ + {"mask": "0x00000001", "description": "CTA support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000002", "description": "RB1 support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000004", "description": "RB2 support. 0 = Not supported, 1 = Supported."} + ], + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_8_TIMINGS", + "name": "Lane count and Link rate flags.", + "type": 12 + }, + { + "configId": "0x1190075", + "defaultValue": "9", + "description": "7680x4320 @ 30Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_9_ID", + "maxValue": 9, + "minValue": 9, + "name": "TSI_DP20_SRCCTS_DSC_VMT_9_ID", + "type": 4 + }, + { + "configId": "0x1190076", + "defaultValue": "576", + "description": "Defines the bitmap with Lane count and Link rate. Default setting is zero. Please see table below for defined capability flags:\nBits Description \n0 ONE_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n1 ONE_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n2 ONE_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n 3 ONE_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n4 TWO_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n5 TWO_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n6 TWO_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n7 TWO_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n8 FOUR_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported.\n9 FOUR_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported.\n10 FOUR_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n11 FOUR_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported.\n31:12 RESERVED, SET TO ZERO\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_9_LC_LR", + "mask": 1632, + "name": "Lane count and Link rate flags.", + "type": 4 + }, + { + "configId": "0x1190077", + "defaultValue": "0x00000000", + "description": "Defines the video mode timing.\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "bitList": + [ + {"mask": "0x00000001", "description": "CTA support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000002", "description": "RB1 support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000004", "description": "RB2 support. 0 = Not supported, 1 = Supported."} + ], + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_9_TIMINGS", + "name": "Lane count and Link rate flags.", + "type": 12 + }, + { + "configId": "0x1190078", + "defaultValue": "10", + "description": "7680x4320 @ 60Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_10_ID", + "maxValue": 10, + "minValue": 10, + "name": "TSI_DP20_SRCCTS_DSC_VMT_10_ID", + "type": 4 + }, + { + "configId": "0x1190079", + "defaultValue": "2048", + "description": "Defines the bitmap with Lane count and Link rate. Default setting is zero. Please see table below for defined capability flags:\nBits Description \n0 ONE_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n1 ONE_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n2 ONE_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n 3 ONE_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n4 TWO_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n5 TWO_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n6 TWO_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n7 TWO_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n8 FOUR_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported.\n9 FOUR_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported.\n10 FOUR_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n11 FOUR_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported.\n31:12 RESERVED, SET TO ZERO\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_10_LC_LR", + "mask": 1024, + "name": "Lane count and Link rate flags.", + "type": 4 + }, + { + "configId": "0x119007a", + "defaultValue": "0x00000000", + "description": "Defines the video mode timing.\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "bitList": + [ + {"mask": "0x00000001", "description": "CTA support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000002", "description": "RB1 support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000004", "description": "RB2 support. 0 = Not supported, 1 = Supported."} + ], + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_10_TIMINGS", + "name": "Lane count and Link rate flags.", + "type": 12 + }, + { + "configId": "0x119007b", + "defaultValue": "11", + "description": "7680x4320 @ 100Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_11_ID", + "maxValue": 11, + "minValue": 11, + "name": "TSI_DP20_SRCCTS_DSC_VMT_11_ID", + "type": 4 + }, + { + "configId": "0x119007c", + "defaultValue": "2048", + "description": "Defines the bitmap with Lane count and Link rate. Default setting is zero. Please see table below for defined capability flags:\nBits Description \n0 ONE_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n1 ONE_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n2 ONE_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n 3 ONE_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n4 TWO_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n5 TWO_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n6 TWO_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n7 TWO_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n8 FOUR_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported.\n9 FOUR_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported.\n10 FOUR_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n11 FOUR_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported.\n31:12 RESERVED, SET TO ZERO\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_11_LC_LR", + "mask": 2048, + "name": "Lane count and Link rate flags.", + "type": 4 + }, + { + "configId": "0x119007d", + "defaultValue": "0x00000000", + "description": "Defines the video mode timing.\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "bitList": + [ + {"mask": "0x00000001", "description": "CTA support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000002", "description": "RB1 support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000004", "description": "RB2 support. 0 = Not supported, 1 = Supported."} + ], + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_11_TIMINGS", + "name": "Lane count and Link rate flags.", + "type": 12 + }, + { + "configId": "0x119007e", + "defaultValue": "1", + "description": "Defined the number of items in DSC color format table.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_FORMATS", + "maxValue": 2147483647, + "minValue": 0, + "name": "Number of items in DSC color format table", + "type": 4 + }, + { + "configId": "0x119007f", + "defaultValue": "32", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 1, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_0", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_0", + "type": 4 + }, + { + "configId": "0x1190080", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_1", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_1", + "type": 4 + }, + { + "configId": "0x1190081", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_2", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_2", + "type": 4 + }, + { + "configId": "0x1190082", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_3", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_3", + "type": 4 + }, + { + "configId": "0x1190083", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_4", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_4", + "type": 4 + }, + { + "configId": "0x1190084", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_5", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_5", + "type": 4 + }, + { + "configId": "0x1190085", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_6", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_6", + "type": 4 + }, + { + "configId": "0x1190086", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_7", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_7", + "type": 4 + }, + { + "configId": "0x1190087", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_8", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_8", + "type": 4 + }, + { + "configId": "0x1190088", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_9", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_9", + "type": 4 + }, + { + "configId": "0x1190089", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_10", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_10", + "type": 4 + }, + { + "configId": "0x119008a", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_11", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_11", + "type": 4 + }, + { + "configId": "0x119008b", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_12", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_12", + "type": 4 + }, + { + "configId": "0x119008c", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_13", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_13", + "type": 4 + }, + { + "configId": "0x119008d", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_14", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_14", + "type": 4 + }, + { + "configId": "0x119008e", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_15", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_15", + "type": 4 + }, + { + "configId": "0x119008f", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_16", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_16", + "type": 4 + }, + { + "configId": "0x1190090", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_17", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_17", + "type": 4 + }, + { + "configId": "0x1190091", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_18", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_18", + "type": 4 + }, + { + "configId": "0x1190092", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_19", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_19", + "type": 4 + }, + { + "configId": "0x1190093", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_20", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_20", + "type": 4 + }, + { + "configId": "0x1190094", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_21", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_21", + "type": 4 + }, + { + "configId": "0x1190095", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_22", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_22", + "type": 4 + }, + { + "configId": "0x1190096", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_23", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_23", + "type": 4 + }, + { + "configId": "0x1190097", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_24", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_24", + "type": 4 + }, + { + "configId": "0x1190098", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_25", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_25", + "type": 4 + }, + { + "configId": "0x1190099", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_26", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_26", + "type": 4 + }, + { + "configId": "0x119009a", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_27", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_27", + "type": 4 + }, + { + "configId": "0x119009b", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_28", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_28", + "type": 4 + }, + { + "configId": "0x119009c", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_29", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_29", + "type": 4 + }, + { + "configId": "0x119009d", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_30", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_30", + "type": 4 + }, + { + "configId": "0x119009e", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_31", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_31", + "type": 4 + }, + { + "configId": "0x119009f", + "defaultValue": "5120", + "description": "Defines maximum HActive per stream.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_MAX_STREAM_HACTIVE", + "maxValue": 2147483647, + "minValue": 0, + "name": "Maximum HActive per stream", + "type": 4 + }, + { + "configId": "0x11900a0", + "defaultValue": "2160", + "description": "Defines maximum VActive per stream.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_MAX_STREAM_VACTIVE", + "maxValue": 2147483647, + "minValue": 0, + "name": "Maximum VActive per stream", + "type": 4 + }, + { + "configId": "0x11900a1", + "defaultValue": "742500", + "description": "Defines maximum pixel clock per stream, kHz.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_MAX_STREAM_PIXEL_CLOCK", + "maxValue": 2147483647, + "minValue": 0, + "name": "Maximum pixel clock per stream, in kHz", + "type": 4 + }, + { + "configId": "0x11900a2", + "defaultValue": "1297", + "description": "Defines tested audio mode. Bytes 2...0 of the word match data bytes 3...1 of a corresponding Audio InfoFrame (See CTA-861-G, Section 6.6). Byte 3 is set to 0. Unused entries are set to 0.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_TESTED_AUDIO_MODE_0", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_TESTED_AUDIO_MODE_0", + "type": 4 + }, + { + "configId": "0x11900a3", + "defaultValue": "2321", + "description": "Defines tested audio mode. Bytes 2...0 of the word match data bytes 3...1 of a corresponding Audio InfoFrame (See CTA-861-G, Section 6.6). Byte 3 is set to 0. Unused entries are set to 0.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_TESTED_AUDIO_MODE_1", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_TESTED_AUDIO_MODE_1", + "type": 4 + }, + { + "configId": "0x11900a4", + "defaultValue": "3345", + "description": "Defines tested audio mode. Bytes 2...0 of the word match data bytes 3...1 of a corresponding Audio InfoFrame (See CTA-861-G, Section 6.6). Byte 3 is set to 0. Unused entries are set to 0.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_TESTED_AUDIO_MODE_2", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_TESTED_AUDIO_MODE_2", + "type": 4 + }, + { + "configId": "0x11900a5", + "defaultValue": "2833", + "description": "Defines tested audio mode. Bytes 2...0 of the word match data bytes 3...1 of a corresponding Audio InfoFrame (See CTA-861-G, Section 6.6). Byte 3 is set to 0. Unused entries are set to 0.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_TESTED_AUDIO_MODE_3", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_TESTED_AUDIO_MODE_3", + "type": 4 + }, + { + "configId": "0x11900a6", + "defaultValue": "3857", + "description": "Defines tested audio mode. Bytes 2...0 of the word match data bytes 3...1 of a corresponding Audio InfoFrame (See CTA-861-G, Section 6.6). Byte 3 is set to 0. Unused entries are set to 0.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_TESTED_AUDIO_MODE_4", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_TESTED_AUDIO_MODE_4", + "type": 4 + }, + { + "configId": "0x11900a7", + "defaultValue": "0", + "description": "Defines tested audio mode. Bytes 2...0 of the word match data bytes 3...1 of a corresponding Audio InfoFrame (See CTA-861-G, Section 6.6). Byte 3 is set to 0. Unused entries are set to 0.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_TESTED_AUDIO_MODE_5", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_TESTED_AUDIO_MODE_5", + "type": 4 + }, + { + "configId": "0x11900a8", + "defaultValue": "0", + "description": "Defines tested audio mode. Bytes 2...0 of the word match data bytes 3...1 of a corresponding Audio InfoFrame (See CTA-861-G, Section 6.6). Byte 3 is set to 0. Unused entries are set to 0.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_TESTED_AUDIO_MODE_6", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_TESTED_AUDIO_MODE_6", + "type": 4 + }, + { + "configId": "0x11900a9", + "defaultValue": "0", + "description": "Defines tested audio mode. Bytes 2...0 of the word match data bytes 3...1 of a corresponding Audio InfoFrame (See CTA-861-G, Section 6.6). Byte 3 is set to 0. Unused entries are set to 0.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_TESTED_AUDIO_MODE_7", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_TESTED_AUDIO_MODE_7", + "type": 4 + }, + { + "configId": "0x11900aa", + "defaultValue": "0x00000103", + "description": "DMT timings (Progressive Scan) supported by the DUT without DSC.", + "bitList": + [ + {"mask": "0x00000001", "description": "800x600 @ 60Hz (DMT 09h)"}, + {"mask": "0x00000002", "description": "1024x768 @ 60Hz (DMT 10h)"}, + {"mask": "0x00000004", "description": "1280x768 @ 60Hz (DMT 17h)"}, + {"mask": "0x00000008", "description": "1280x800 @ 60Hz (DMT 1Ch)"}, + {"mask": "0x00000010", "description": "1280x960 @ 60Hz (DMT 20h)"}, + {"mask": "0x00000020", "description": "1280x1024 @ 60Hz (DMT 23h)"}, + {"mask": "0x00000040", "description": "1360x768 @ 60Hz (DMT 27h)"}, + {"mask": "0x00000080", "description": "1400x1050 @ 60Hz (DMT 2Ah)"}, + {"mask": "0x00000100", "description": "1600x1200 @ 60Hz (DMT 33h)"}, + {"mask": "0x00000200", "description": "1680x1050 @ 60Hz (DMT 3Ah)"}, + {"mask": "0x00000400", "description": "1856x1392 @ 60Hz (DMT 41h)"}, + {"mask": "0x00000800", "description": "1920x1080 @ 60Hz (DMT 52h)"}, + {"mask": "0x00001000", "description": "1920x1200 @ 60Hz (DMT 45h)"}, + {"mask": "0x00002000", "description": "1920x1440 @ 60Hz (DMT 49h)"} + ], + "flag": 3, + "id": "TSI_DP20_SRCCTS_DMT_TIMING", + "name": "DMT timings.", + "type": 12 + }, + { + "configId": "0x11900ab", + "defaultValue": "0x00000106", + "description": "CTA timings (Progressive Scan) supported by the DUT without DSC.", + "bitList": + [ + {"mask": "0x00000001", "description": "720x480 @ 60Hz (VIC 2)"}, + {"mask": "0x00000002", "description": "1280x720 @ 60Hz (VIC 4)"}, + {"mask": "0x00000004", "description": "1920x1080 @ 60Hz (VIC 16)"}, + {"mask": "0x00000008", "description": "1280x720 @ 120Hz (VIC 47)"}, + {"mask": "0x00000010", "description": "720x480 @ 120Hz (VIC 48)"}, + {"mask": "0x00000020", "description": "1920x1080 @ 120Hz (VIC 63)"}, + {"mask": "0x00000040", "description": "1680x720 @ 60Hz (VIC 83)"}, + {"mask": "0x00000080", "description": "1680x720 @ 120Hz (VIC 85)"}, + {"mask": "0x00000100", "description": "2560x1080 @ 60Hz (VIC 90)"}, + {"mask": "0x00000200", "description": "2560x1080 @ 120Hz (VIC 92)"}, + {"mask": "0x00000400", "description": "3840x2160 @ 60Hz (VIC 97)"}, + {"mask": "0x00000800", "description": "4096x2160 @ 60Hz (VIC 102)"}, + {"mask": "0x00001000", "description": "3840x2160 @ 120Hz (VIC 118)"}, + {"mask": "0x00002000", "description": "5120x2160 @ 60Hz (VIC 126)"}, + {"mask": "0x00004000", "description": "7680x4320 @ 24Hz (VIC 194)"}, + {"mask": "0x00008000", "description": "7680x4320 @ 30Hz (VIC 196)"} + ], + "flag": 3, + "id": "TSI_DP20_SRCCTS_CTA_TIMING", + "name": "CTA timings.", + "type": 12 + }, + { + "configId": "0x11900ac", + "defaultValue": "0x00000492", + "description": "CVT timings (Progressive Scan) supported by the DUT without DSC.", + "bitList": + [ + {"mask": "0x00000001", "description": "1920x1080 @ 60Hz RBv1"}, + {"mask": "0x00000002", "description": "2048x1536 @ 60Hz RBv1"}, + {"mask": "0x00000004", "description": "2560x1080 @ 60Hz RBv1"}, + {"mask": "0x00000008", "description": "2560x1440 @ 60Hz RBv1"}, + {"mask": "0x00000010", "description": "2560x1440 @ 60Hz RBv2"}, + {"mask": "0x00000020", "description": "3840x2160 @ 60Hz RBv1"}, + {"mask": "0x00000040", "description": "3840x2160 @ 60Hz RBv2"}, + {"mask": "0x00000080", "description": "3840x2160 @ 60Hz RBv3"}, + {"mask": "0x00000100", "description": "4096x2160 @ 60Hz RBv1"}, + {"mask": "0x00000200", "description": "4096x2160 @ 60Hz RBv2"}, + {"mask": "0x00000400", "description": "4096x2160 @ 60Hz RBv3"}, + {"mask": "0x00000800", "description": "5120x2880 @ 60Hz RBv1"}, + {"mask": "0x00001000", "description": "5120x2880 @ 60Hz RBv2"}, + {"mask": "0x00002000", "description": "5120x2880 @ 60Hz RBv3"} + ], + "flag": 3, + "id": "TSI_DP20_SRCCTS_CVT_TIMING", + "name": "CVT timings.", + "type": 12 + }, + { + "configId": "0x11900ad", + "defaultValue": "0x00000007", + "description": "DUT AdaptiveSync capabilitie.", + "bitList": + [ + {"mask": "0x00000001", "description": "DUT supports AdaptiveSync"}, + {"mask": "0x00000002", "description": "Device supports Fixed Average VTotal mode"}, + {"mask": "0x00000004", "description": "Device supports Duration Increase and Decrease constraints"}, + {"mask": "0x00000008", "description": "Adaptive-Sync must be enabled manually"} + ], + "flag": 1, + "id": "TSI_DP20_SRCCTS_AS_DUT_CAPS", + "name": "DUT AdaptiveSync capabilitie.", + "type": 12 + }, + { + "configId": "0x11900ae", + "defaultValue": "3", + "description": "Adaptive-Sync range minimum refresh rate supported by the Source. Default value is 3.", + "enumerationVariants": "0 # 59.940 Hz\n1 # 47.952 Hz\n2 # 29.970 Hz\n3 # 23.976 Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_AS_RANGE_MIN_RATE", + "maxValue": 3, + "minValue": 0, + "name": "Adaptive-Sync range minimum refresh rate supported by the Source", + "type": 4 + }, + { + "configId": "0x11900af", + "defaultValue": "120", + "description": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz. A zero value means the timing is not supported.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_AS_RANGE_MAX_RATE_0", + "maxValue": 360, + "minValue": 0, + "name": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz.", + "type": 4 + }, + { + "configId": "0x11900b0", + "defaultValue": "120", + "description": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz. A zero value means the timing is not supported.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_AS_RANGE_MAX_RATE_1", + "maxValue": 360, + "minValue": 0, + "name": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz.", + "type": 4 + }, + { + "configId": "0x11900b1", + "defaultValue": "120", + "description": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz. A zero value means the timing is not supported.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_AS_RANGE_MAX_RATE_2", + "maxValue": 360, + "minValue": 0, + "name": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz.", + "type": 4 + }, + { + "configId": "0x11900b2", + "defaultValue": "120", + "description": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz. A zero value means the timing is not supported.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_AS_RANGE_MAX_RATE_3", + "maxValue": 360, + "minValue": 0, + "name": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz.", + "type": 4 + }, + { + "configId": "0x11900b3", + "defaultValue": "0", + "description": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz. A zero value means the timing is not supported.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_AS_RANGE_MAX_RATE_4", + "maxValue": 360, + "minValue": 0, + "name": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz.", + "type": 4 + }, + { + "configId": "0x11900b4", + "defaultValue": "0", + "description": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz. A zero value means the timing is not supported.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_AS_RANGE_MAX_RATE_5", + "maxValue": 360, + "minValue": 0, + "name": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz.", + "type": 4 + }, + { + "configId": "0x11900b5", + "defaultValue": "0", + "description": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz. A zero value means the timing is not supported.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_AS_RANGE_MAX_RATE_6", + "maxValue": 360, + "minValue": 0, + "name": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz.", + "type": 4 + }, + { + "configId": "0x11900b6", + "defaultValue": "0", + "description": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz. A zero value means the timing is not supported.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_AS_RANGE_MAX_RATE_7", + "maxValue": 360, + "minValue": 0, + "name": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz.", + "type": 4 + }, + { + "configId": "0x11900bf", + "defaultValue": "61440", + "description": "Timing 768x480 @ 85Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_768_480_85", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900c0", + "defaultValue": "61440", + "description": "Timing 1024x640 @ 60Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_1024_640_60", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900c1", + "defaultValue": "61440", + "description": "Timing 1152x720 @ 75Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_1152_720_75", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900c2", + "defaultValue": "61440", + "description": "Timing 1280x720 @ 24Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_1280_720_24", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900c3", + "defaultValue": "61440", + "description": "Timing 1280x720 @ 120Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_1280_720_120", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900c4", + "defaultValue": "61440", + "description": "Timing 1280x768 @ 60Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_1280_768_60", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900c5", + "defaultValue": "61440", + "description": "Timing 1280x960 @ 60Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_1280_960_60", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900c6", + "defaultValue": "61440", + "description": "Timing 1440x240 @ 60Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_1440_240_60", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900c7", + "defaultValue": "61440", + "description": "Timing 1440x480 @ 60Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_1440_480_60", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900c8", + "defaultValue": "61440", + "description": "Timing 1440x900 @ 60Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_1440_900_60", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900c9", + "defaultValue": "4096", + "description": "Timing 1536x960 @ 85Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_1536_960_85", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900ca", + "defaultValue": "61465", + "description": "Timing 1920x1080p @ 30Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_1920_1080_30", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900cb", + "defaultValue": "61465", + "description": "Timing 1920x1080p @ 60Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_1920_1080_60", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900cc", + "defaultValue": "61440", + "description": "Timing 1920x1080 @ 85Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_1920_1080_85", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900cd", + "defaultValue": "61465", + "description": "Timing 1920x1080p @ 100Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_1920_1080_100", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900ce", + "defaultValue": "61465", + "description": "Timing 1920x1080p @ 120Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_1920_1080_120", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900cf", + "defaultValue": "61465", + "description": "Timing 1920x1080p @ 144Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_1920_1080_144", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900d0", + "defaultValue": "61465", + "description": "Timing 1920x1080p @ 240Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_1920_1080_240", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900d1", + "defaultValue": "61440", + "description": "Timing 1920x1440 @ 60Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_1920_1440_60", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900d2", + "defaultValue": "61440", + "description": "Timing 2048x1280 @ 60Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_2048_1280_60", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900d3", + "defaultValue": "61440", + "description": "Timing 2048x1536 @ 60Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_2048_1536_60", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900d4", + "defaultValue": "61440", + "description": "Timing 2128x1200 @ 60Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_2128_1200_60", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900d5", + "defaultValue": "61440", + "description": "Timing 2456x1536 @ 50Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_2456_1536_50", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900d6", + "defaultValue": "61440", + "description": "Timing 2456x1536 @ 75Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_2456_1536_75", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900d7", + "defaultValue": "61440", + "description": "Timing 2560x1080 @ 30Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_2560_1080_30", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900d8", + "defaultValue": "61441", + "description": "Timing 2560x1080 @ 60Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_2560_1080_60", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900d9", + "defaultValue": "61441", + "description": "Timing 2560x1080 @ 120Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_2560_1080_120", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900da", + "defaultValue": "61440", + "description": "Timing 2560x1600 @ 60Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_2560_1600_60", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900db", + "defaultValue": "61440", + "description": "Timing 2560x1920 @ 75Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_2560_1920_75", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900dc", + "defaultValue": "61440", + "description": "Timing 2728x1536 @ 60Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_2728_1536_60", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900dd", + "defaultValue": "61469", + "description": "Timing 3840x2160p @ 30Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_3840_2160_30", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900de", + "defaultValue": "61469", + "description": "Timing 3840x2160p @ 60Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_3840_2160_60", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900df", + "defaultValue": "61469", + "description": "Timing 3840x2160p @ 120Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_3840_2160_120", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900e0", + "defaultValue": "61496", + "description": "Timing 3840x2160p @ 144Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_3840_2160_144", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900e1", + "defaultValue": "61496", + "description": "Timing 3840x2160p @ 240Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_3840_2160_240", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900e2", + "defaultValue": "61440", + "description": "Timing 3840x2400 @ 60Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_3840_2400_60", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900e3", + "defaultValue": "61440", + "description": "Timing 4096x2160 @ 30Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_4096_2160_30", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900e4", + "defaultValue": "61465", + "description": "Timing 5120x2160p @ 30Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_5120_2160_30", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900e5", + "defaultValue": "61465", + "description": "Timing 5120x2160p @ 60Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_5120_2160_60", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900e6", + "defaultValue": "61465", + "description": "Timing 5120x2160p @ 120Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_5120_2160_120", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900e7", + "defaultValue": "61496", + "description": "Timing 5120x2160p @ 144Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_5120_2160_144", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900e8", + "defaultValue": "61496", + "description": "Timing 5120x2160p @ 240Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_5120_2160_240", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900e9", + "defaultValue": "61440", + "description": "Timing 5120x2880 @ 30Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_5120_2880_30", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900ea", + "defaultValue": "61440", + "description": "Timing 5120x2880 @ 60Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_5120_2880_60", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900eb", + "defaultValue": "61440", + "description": "Timing 5120x2880 @ 120Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_5120_2880_120", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900ec", + "defaultValue": "61456", + "description": "Timing 7680x4320 @ 24Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_7680_4320_24", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900ed", + "defaultValue": "61469", + "description": "Timing 7680x4320 @ 30Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_7680_4320_30", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900ee", + "defaultValue": "61440", + "description": "Timing 7680x4320 @ 50Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_7680_4320_50", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900ef", + "defaultValue": "61469", + "description": "Timing 7680x4320 @ 60Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_7680_4320_60", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900f0", + "defaultValue": "61469", + "description": "Timing 7680x4320 @ 120Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_7680_4320_120", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900f1", + "defaultValue": "61440", + "description": "Timing 10240x4320 @ 24Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_10240_4320_24", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900f2", + "defaultValue": "61465", + "description": "Timing 10240x4320 @ 30Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_10240_4320_30", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900f3", + "defaultValue": "61465", + "description": "Timing 10240x4320 @ 60Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_10240_4320_60", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x119000f", + "defaultValue": "0", + "description": "Reserved for extension.", + "bitList": + [ + {"description": "0x119000f", "defaultValue": "0"}, + {"description": "0x119001c", "defaultValue": "0"}, + {"description": "0x119001d", "defaultValue": "0"}, + {"description": "0x119001e", "defaultValue": "0"}, + {"description": "0x119001f", "defaultValue": "0"}, + {"description": "0x119002f", "defaultValue": "0"}, + {"description": "0x1190030", "defaultValue": "0"}, + {"description": "0x1190031", "defaultValue": "0"}, + {"description": "0x1190032", "defaultValue": "0"}, + {"description": "0x1190033", "defaultValue": "0"}, + {"description": "0x1190034", "defaultValue": "0"}, + {"description": "0x1190035", "defaultValue": "0"}, + {"description": "0x1190036", "defaultValue": "0"}, + {"description": "0x1190037", "defaultValue": "0"}, + {"description": "0x11900b7", "defaultValue": "0"}, + {"description": "0x11900b8", "defaultValue": "0"}, + {"description": "0x11900b9", "defaultValue": "0"}, + {"description": "0x11900ba", "defaultValue": "0"}, + {"description": "0x11900bb", "defaultValue": "0"}, + {"description": "0x11900bc", "defaultValue": "0"}, + {"description": "0x11900bd", "defaultValue": "0"}, + {"description": "0x11900be", "defaultValue": "0"} + ], + "flag": 3, + "id": "RESERVED", + "type": 4 + }, + { + "configId": "0x11900f4", + "defaultValue": "1920", + "description": "HActive in pixels", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DRR_HACTIVE", + "maxValue": 2147483647, + "minValue": 0, + "name": "HActive", + "type": 4 + }, + { + "configId": "0x11900f5", + "defaultValue": "1080", + "description": "VActive in lines", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DRR_VACTIVE", + "maxValue": 2147483647, + "minValue": 0, + "name": "VActive", + "type": 4 + }, + { + "configId": "0x11900f6", + "defaultValue": "297000", + "description": "Pixel Clock, kHz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DRR_PIXEL_CLOCK", + "maxValue": 2147483647, + "minValue": 0, + "name": "Pixel Clock", + "type": 4 + }, + { + "configId": "0x11900f7", + "defaultValue": "50", + "description": "Minimum Vertical Refresh Rate, Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DRR_MIN_VRR", + "maxValue": 2147483647, + "minValue": 0, + "name": "Minimum Vertical Refresh Rate", + "type": 4 + }, + { + "configId": "0x11900f8", + "defaultValue": "100", + "description": "Maximum Vertical Refresh Rate, Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DRR_MAX_VRR", + "maxValue": 2147483647, + "minValue": 0, + "name": "Maximum Vertical Refresh Rate", + "type": 4 + }, + { + "configId": "0x01190100", + "defaultValue": "61465", + "description": "Timing 1920x1080p @ 30Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_1920_1080_30", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x01190101", + "defaultValue": "61465", + "description": "Timing 1920x1080p @ 60Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_1920_1080_60", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x01190102", + "defaultValue": "61465", + "description": "Timing 1920x1080p @ 120Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_1920_1080_120", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x01190103", + "defaultValue": "61465", + "description": "Timing 1920x1080p @ 144Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_1920_1080_144", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x01190104", + "defaultValue": "61465", + "description": "Timing 1920x1080p @ 240Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_1920_1080_240", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x01190105", + "defaultValue": "61469", + "description": "Timing 3840x2160p @ 30Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_3840_2160_30", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x01190106", + "defaultValue": "61469", + "description": "Timing 3840x2160p @ 60Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_3840_2160_60", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x01190107", + "defaultValue": "61469", + "description": "Timing 3840x2160p @ 120Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_3840_2160_120", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x01190108", + "defaultValue": "61496", + "description": "Timing 3840x2160p @ 144Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_3840_2160_144", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x01190109", + "defaultValue": "61496", + "description": "Timing 3840x2160p @ 240Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_3840_2160_240", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x0119010a", + "defaultValue": "61465", + "description": "Timing 5120x2160p @ 30Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_5120_2160_30", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x0119010b", + "defaultValue": "61465", + "description": "Timing 5120x2160p @ 60Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_5120_2160_60", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x0119010c", + "defaultValue": "61465", + "description": "Timing 5120x2160p @ 120Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_5120_2160_120", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x0119010d", + "defaultValue": "61496", + "description": "Timing 5120x2160p @ 144Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_5120_2160_144", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x0119010e", + "defaultValue": "61496", + "description": "Timing 5120x2160p @ 240Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_5120_2160_240", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x0119010f", + "defaultValue": "61469", + "description": "Timing 7680x4320 @ 30Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_7680_4320_30", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x01190110", + "defaultValue": "61469", + "description": "Timing 7680x4320 @ 60Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_7680_4320_60", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x01190111", + "defaultValue": "61469", + "description": "Timing 7680x4320 @ 120Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_7680_4320_120", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x01190112", + "defaultValue": "61465", + "description": "Timing 10240x4320 @ 30Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_10240_4320_30", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x01190113", + "defaultValue": "61465", + "description": "Timing 10240x4320 @ 60Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_10240_4320_60", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + } + ], + "id": "0x08", + "name": "DP 2.1 LL CTS", + "old_names": [ + "DP 2.1 LL CTS", + "DP 2.0 Link Layer Source DUT CTS", + "DP 2.1 Link Layer Source DUT CTS" + ] +} diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x09]src_dut_hdcp_23_1a.json b/UniTAP/dev/modules/dut_tests/cfg/[0x09]src_dut_hdcp_23_1a.json new file mode 100644 index 0000000..157256a --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x09]src_dut_hdcp_23_1a.json @@ -0,0 +1,41 @@ +{ + "descriptions": [ + { + "configId": "0x10F0000", + "defaultValue": "200000", + "description": "Defines test timeout in milliseconds, default value is 200000ms.", + "flag": 3, + "id": "TSI_TEST_CFG_HDCP2_1A_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout, in milliseconds", + "type": 4 + }, + { + "configId": "0x10F0001", + "defaultValue": [48, 187, 226, 110, 98], + "description": "Defines Revoke ID.", + "flag": 3, + "id": "TSI_TEST_CFG_HDCP2_1A_REVOKEID", + "name": "Revoke ID", + "type": 7 + }, + { + "configId": "0x10F0002", + "defaultValue": "1", + "description": "Defines source DUT capabilities flags.", + "enumerationVariants": "0 # False\n1 # True", + "flag": 3, + "id": "TSI_TEST_CFG_HDCP2_1A_SRC_DUT_CAP", + "maxValue": 1, + "minValue": 0, + "name": "Source_EncDisableBootstrapping", + "type": 4 + } + ], + "id": "0x09", + "name": "HDCP 2.3 CTS 1A", + "old_names": [ + "HDCP 2.3 CTS 1A Test Set" + ] +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x0A]src_dut_hdcp_23_1b.json b/UniTAP/dev/modules/dut_tests/cfg/[0x0A]src_dut_hdcp_23_1b.json new file mode 100644 index 0000000..bfe2c30 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x0A]src_dut_hdcp_23_1b.json @@ -0,0 +1,20 @@ +{ + "descriptions": [ + { + "configId": "0x1110000", + "defaultValue": "200000", + "description": "Defines test timeout in milliseconds, default value is 200000ms.", + "flag": 3, + "id": "TSI_TEST_CFG_HDCP2_1B_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout, in milliseconds", + "type": 4 + } + ], + "id": "0x0A", + "name": "HDCP 2.3 CTS 1B", + "old_names": [ + "HDCP 2.3 CTS 1B Test Set" + ] +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x0B]src_dut_hdcp_23_3a.json b/UniTAP/dev/modules/dut_tests/cfg/[0x0B]src_dut_hdcp_23_3a.json new file mode 100644 index 0000000..c74b388 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x0B]src_dut_hdcp_23_3a.json @@ -0,0 +1,20 @@ +{ + "descriptions": [ + { + "configId": "0x1130000", + "defaultValue": "200000", + "description": "Defines test timeout in milliseconds, default value is 200000ms.", + "flag": 3, + "id": "TSI_TEST_CFG_HDCP2_3A_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout, in milliseconds", + "type": 4 + } + ], + "id": "0x0B", + "name": "HDCP 2.3 CTS 3A", + "old_names": [ + "HDCP 2.3 CTS 3A Test Set" + ] +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x0C]src_dut_hdcp_23_3b.json b/UniTAP/dev/modules/dut_tests/cfg/[0x0C]src_dut_hdcp_23_3b.json new file mode 100644 index 0000000..321b6ab --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x0C]src_dut_hdcp_23_3b.json @@ -0,0 +1,20 @@ +{ + "descriptions": [ + { + "configId": "0x1140000", + "defaultValue": "200000", + "description": "Defines test timeout in milliseconds, default value is 200000ms.", + "flag": 3, + "id": "TSI_TEST_CFG_HDCP2_3B_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout, in milliseconds", + "type": 4 + } + ], + "id": "0x0C", + "name": "HDCP 2.3 CTS 3B", + "old_names": [ + "HDCP 2.3 CTS 3B Test Set" + ] +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x0D]snk_dut_hdcp_23_2c.json b/UniTAP/dev/modules/dut_tests/cfg/[0x0D]snk_dut_hdcp_23_2c.json new file mode 100644 index 0000000..4db5ee2 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x0D]snk_dut_hdcp_23_2c.json @@ -0,0 +1,20 @@ +{ + "descriptions": [ + { + "configId": "0x1120000", + "defaultValue": "200000", + "description": "Defines test timeout in milliseconds, default value is 200000ms.", + "flag": 3, + "id": "TSI_TEST_CFG_HDCP2_2C_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout, in milliseconds", + "type": 4 + } + ], + "id": "0x0D", + "name": "HDCP 2.3 CTS 2C", + "old_names": [ + "HDCP 2.3 CTS 2C Test Set" + ] +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x0E]snk_dut_hdcp_23_3c.json b/UniTAP/dev/modules/dut_tests/cfg/[0x0E]snk_dut_hdcp_23_3c.json new file mode 100644 index 0000000..949ff8c --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x0E]snk_dut_hdcp_23_3c.json @@ -0,0 +1,32 @@ +{ + "descriptions": [ + { + "configId": "0x1150000", + "defaultValue": "200000", + "description": "Defines test timeout in milliseconds, default value is 200000ms.", + "flag": 3, + "id": "TSI_TEST_CFG_HDCP2_3C_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout, in milliseconds", + "type": 4 + }, + { + "configId": "0x1150001", + "defaultValue": "1", + "description": "Defines Repeater_MultipleOutputs.", + "enumerationVariants": "0 # False\n1 # True", + "flag": 3, + "id": "TSI_TEST_CFG_HDCP2_3C_REPEATER_MULTIPLE_OUTPUTS", + "maxValue": 1, + "minValue": 0, + "name": "Repeater_MultipleOutputs", + "type": 4 + } + ], + "id": "0x0E", + "name": "HDCP 2.3 CTS 3C", + "old_names": [ + "HDCP 2.3 CTS 3C Test Set" + ] +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x0F]snk_dut_vrr.json b/UniTAP/dev/modules/dut_tests/cfg/[0x0F]snk_dut_vrr.json new file mode 100644 index 0000000..159692c --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x0F]snk_dut_vrr.json @@ -0,0 +1,111 @@ +{ + "descriptions": [ + { + "configId": "0x1180000", + "defaultValue": "10000", + "description": "Timeout period used for all VRR tests, in milliseconds. Default timeout is 10000ms.", + "flag": 3, + "id": "TSI_VRR_SINK_DUT_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout, in milliseconds", + "type": 4 + }, + { + "configId": "0x1180001", + "defaultValue": "60", + "description": "VRR Max value. Default timeout is 60.", + "flag": 3, + "id": "TSI_VRR_SINK_DUT_VRR_MAX", + "maxValue": 2147483647, + "minValue": 0, + "name": "VRR Max value", + "type": 4 + }, + { + "configId": "0x1180002", + "defaultValue": "30", + "description": "VRR Min value. Default timeout is 30.", + "flag": 3, + "id": "TSI_VRR_SINK_DUT_VRR_MIN", + "maxValue": 2147483647, + "minValue": 0, + "name": "VRR Min value", + "type": 4 + }, + { + "configId": "0x1180003", + "defaultValue": "45", + "description": "VRR Static value. Default timeout is 45.", + "flag": 3, + "id": "TSI_VRR_SINK_DUT_VRR_STATIC", + "maxValue": 2147483647, + "minValue": 0, + "name": "VRR Static value", + "type": 4 + }, + { + "configId": "0x1180004", + "defaultValue": "1", + "description": "Determines step of changing frame rate. Default timeout is 1.", + "flag": 3, + "id": "TSI_VRR_SINK_DUT_VRR_STEP", + "maxValue": 2147483647, + "minValue": 0, + "name": "VRR Step value", + "type": 4 + }, + { + "configId": "0x1180005", + "defaultValue": "1000", + "description": "Determines timer to change frame rate. Default timeout is 1000.", + "flag": 3, + "id": "TSI_VRR_SINK_DUT_VRR_TIME_STEP", + "maxValue": 2147483647, + "minValue": 0, + "name": "VRR Time step value", + "type": 4 + }, + { + "configId": "0x1180006", + "defaultValue": "0x00000003", + "description": "VRR Enable and M_CONST", + "bitList": + [ + {"mask": "0x00000001", "description": "VRR Enable value"}, + {"mask": "0x00000002", "description": "M_CONST"} + ], + "flag": 3, + "id": "TSI_VRR_SINK_DUT_VRR_ENABLE", + "name": "VRR Enable and M_CONST", + "type": 12 + }, + { + "configId": "0x1180007", + "defaultValue": "3", + "description": "VFront and RB.", + "bitList": + [ + {"mask": "0x00000003", "description": "Base VFront"}, + {"mask": "0x00000004", "description": "RB"} + ], + "flag": 3, + "id": "TSI_VRR_SINK_DUT_BASE_VFRONT", + "name": "VFront and RB", + "type": 12 + }, + { + "configId": "0x1180008", + "defaultValue": "50", + "description": "Base Refresh Rate. Default timeout is 50.", + "flag": 3, + "id": "TSI_VRR_SINK_DUT_BASE_RATE", + "maxValue": 2147483647, + "minValue": 0, + "name": "Base Refresh Rate", + "type": 4 + } + ], + "id": "0x0F", + "name": "VRR Sink DUT Tests" +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x10]src_dut_vrr.json b/UniTAP/dev/modules/dut_tests/cfg/[0x10]src_dut_vrr.json new file mode 100644 index 0000000..64b9c58 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x10]src_dut_vrr.json @@ -0,0 +1,111 @@ +{ + "descriptions": [ + { + "configId": "0x1170000", + "defaultValue": "10000", + "description": "Timeout period used for all VRR tests, in milliseconds. Default timeout is 10000ms.", + "flag": 3, + "id": "TSI_VRR_SRC_DUT_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout, in milliseconds", + "type": 4 + }, + { + "configId": "0x1170001", + "defaultValue": "60", + "description": "VRR Max value. Default timeout is 60.", + "flag": 3, + "id": "TSI_VRR_SRC_DUT_VRR_MAX", + "maxValue": 2147483647, + "minValue": 0, + "name": "VRR Max value", + "type": 4 + }, + { + "configId": "0x1170002", + "defaultValue": "30", + "description": "VRR Min value. Default timeout is 30.", + "flag": 3, + "id": "TSI_VRR_SRC_DUT_VRR_MIN", + "maxValue": 2147483647, + "minValue": 0, + "name": "VRR Min value", + "type": 4 + }, + { + "configId": "0x1170003", + "defaultValue": "45", + "description": "VRR Static value. Default timeout is 45.", + "flag": 3, + "id": "TSI_VRR_SRC_DUT_VRR_STATIC", + "maxValue": 2147483647, + "minValue": 0, + "name": "VRR Static value", + "type": 4 + }, + { + "configId": "0x1170004", + "defaultValue": "1", + "description": "Determines step of changing frame rate. Default timeout is 1.", + "flag": 3, + "id": "TSI_VRR_SRC_DUT_VRR_STEP", + "maxValue": 2147483647, + "minValue": 0, + "name": "VRR Step value", + "type": 4 + }, + { + "configId": "0x1170005", + "defaultValue": "1000", + "description": "Determines timer to change frame rate. Default timeout is 1000.", + "flag": 3, + "id": "TSI_VRR_SRC_DUT_VRR_TIME_STEP", + "maxValue": 2147483647, + "minValue": 0, + "name": "VRR Time step value", + "type": 4 + }, + { + "configId": "0x1170006", + "defaultValue": "0x00000003", + "description": "VRR Enable and M_CONST", + "bitList": + [ + {"mask": "0x00000001", "description": "VRR Enable value"}, + {"mask": "0x00000002", "description": "M_CONST"} + ], + "flag": 3, + "id": "TSI_VRR_SRC_DUT_VRR_ENABLE", + "name": "VRR Enable and M_CONST", + "type": 12 + }, + { + "configId": "0x1170007", + "defaultValue": "3", + "description": "VFront and RB.", + "bitList": + [ + {"mask": "0x00000003", "description": "Base VFront"}, + {"mask": "0x00000004", "description": "RB"} + ], + "flag": 3, + "id": "TSI_VRR_SRC_DUT_BASE_VFRONT", + "name": "VFront value", + "type": 12 + }, + { + "configId": "0x1170008", + "defaultValue": "50", + "description": "Base Refresh Rate. Default timeout is 50.", + "flag": 3, + "id": "TSI_VRR_SRC_DUT_BASE_RATE", + "maxValue": 2147483647, + "minValue": 0, + "name": "Base Refresh Rate", + "type": 4 + } + ], + "id": "0x10", + "name": "VRR Source DUT Tests" +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x11]src_dut_cec.json b/UniTAP/dev/modules/dut_tests/cfg/[0x11]src_dut_cec.json new file mode 100644 index 0000000..b0d2ee6 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x11]src_dut_cec.json @@ -0,0 +1,26 @@ +{ + "descriptions": [ + { + "configId": "0x10400", + "defaultValue": "5000", + "description": "Defines the CEC functional test timeout, in milliseconds. The test must complete within this time-period in order to succeed. Default setting is 5000ms.", + "flag": 3, + "id": "TSI_HDMI_RX_CEC_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout, in milliseconds", + "type": 4 + }, + { + "configId": "0x10401", + "defaultValue": "16384", + "description": "Defines the CEC local PHY address. The address is stored in lowest 16-bits. Default setting is 0x4000 (“4.0.0.0”).\nTypically these addresses are given as “A.B.C.D”, similar to IP addresses. Each number in the address can be a value between 0 and 15. Therefore, address “8.9.10.11” would become HEX value 0x000089AB.", + "flag": 3, + "id": "TSI_HDMI_RX_CEC_LOCAL_PHY_ADDR", + "name": "Local CEC physical address", + "type": 8 + } + ], + "id": "0x11", + "name": "CEC functional Test Set" +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x12]src_dut_pixel_level_video.json b/UniTAP/dev/modules/dut_tests/cfg/[0x12]src_dut_pixel_level_video.json new file mode 100644 index 0000000..9912ea7 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x12]src_dut_pixel_level_video.json @@ -0,0 +1,223 @@ +{ + "descriptions": [ + { + "configId": "0x10", + "defaultValue": "640", + "description": "Defines frame width as number of elements. Actual width in pixels is therefore this value multiplied by the element width.", + "flag": 3, + "id": "TSI_REF1_WIDTH", + "maxValue": 2147483647, + "minValue": 0, + "name": "Reference width as count of elements", + "type": 4 + }, + { + "configId": "0x11", + "defaultValue": "480", + "description": "Defines frame height as number of elements. Actual height in pixels is therefore this value multiplied by the element height.", + "flag": 3, + "id": "TSI_REF1_HEIGHT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Reference height as count of elements", + "type": 4 + }, + { + "configId": "0x12", + "defaultValue": "3", + "description": "Defines the size of the element body, in as bytes of storage required. Certain formats allow this container to be of different size: For example RGB 8:8:8 can have size of 3 and/or 4. Often, the 4 byte version is referred to as ARGB, but TSI does not process the Alpha (“A”) channel, so the presence of that is ignored.", + "flag": 3, + "id": "TSI_REF1_ELEMENT_SIZE", + "maxValue": 2147483647, + "minValue": 0, + "name": "Size of a single element as bytes", + "type": 4 + }, + { + "configId": "0x13", + "defaultValue": "1", + "description": "Defines the width of a single element as number of pixels. Important: TSI_REF1_PIXELS_PER_ELEMENT name define is considered obsolete, however it continues to be defined for backwards compatibility. The new name was incorporated as it is more descriptive.", + "flag": 3, + "id": "TSI_REF1_ELEMENT_WIDTH", + "maxValue": 2147483647, + "minValue": 0, + "name": "Element width as pixels", + "type": 4 + }, + { + "configId": "0x14", + "defaultValue": "1", + "description": "Defines the height of a single element as number of pixels. Important: TSI_REF1_LINES_PER_ELEMENT name define is considered obsolete, however it continues to be defined for backwards compatibility. The new name was incorporated as it is more descriptive.", + "flag": 3, + "id": "TSI_REF1_ELEMENT_HEIGHT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Element height as pixels", + "type": 4 + }, + { + "configId": "0x15", + "defaultValue": "8", + "description": "Defines the color depth of the image as number of bits per color channel regardless of the color format.", + "enumerationVariants": "6\n8\n10\n12\n16", + "flag": 3, + "id": "TSI_REF1_COLOR_DEPTH", + "maxValue": 2147483647, + "minValue": 0, + "name": "Active color bits per channel", + "type": 4 + }, + { + "configId": "0x16", + "defaultValue": "17", + "description": "Defines the element format used to encode the pixel data of the bitmap. Please see table below for currently defined format ID values:\nDefine -- ID -- Description\n\n TSI_ELF_RGB_080808 -- 0 -- RGB color, max color depth 8 bits per channel. Encoded as 3 unsigned bytes or 4 unsigned bytes per element. \n\nTSI_ELF_RGB_161616 -- 1 -- RGB color, max color depth 16 bits per channel. Encoded as 3 unsigned shorts or 4 unsigned shorts per element. \n\nTSI_ELF_YCbCr_080808 -- 0x100 -- YCbCr color, max color depth 8 bits per channel. Encoded as 3 unsigned bytes or 4 unsigned bytes per element. \n\nTSI_ELF_YCbCr_161616 -- 0x101 -- YCbCr color, max color depth 16 bits per channel. Encoded as 3 unsigned shorts or 4 unsigned shorts per element.\n\nImportant: TSI_REF1_PIXEL_FORMAT name define is considered obsolete, however it continues to be defined for backwards compatibility. The new name was incorporated as it is more descriptive.", + "enumerationVariants": "16 # RGB 6bpc\n17 # RGB 8bpc\n18 # RGB 10bpc\n19 # RGB 12bpc\n20 # RGB 16bpc\n256 # YCbCr 444 080808\n257 # YCbCr 444 161616\n800 # YCbCr 420 080808\n801 # YCbCr 420 10bpc\n802 # YCbCr 420 12bpc 161616\n803 # YCbCr 420 161616", + "flag": 3, + "id": "TSI_REF1_ELEMENT_FORMAT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Element data layout ID", + "type": 4 + }, + { + "configId": "0x17", + "defaultValue": "", + "description": "Contains bitmap data encoded as defined in other TSI_REF1_* CI’s.", + "flag": 3, + "id": "TSI_REF1_FRAME_DATA", + "maxLength": 10000, + "name": "Array of bytes that form the bitmap", + "type": 10 + }, + { + "configId": "0x119", + "defaultValue": "RGB", + "description": "Ref Image colorspace", + "flag": 3, + "enumerationVariants": "RGB\nYCbCr 444\nYCbCr 422\nYCbCr 420", + "maxValue": 2147483647, + "minValue": 0, + "id": "UI_VIDEO_PARAMS_COLORSPACE", + "maxLength": 10000, + "name": "Reference image colorspace", + "type": 4 + }, + { + "configId": "0x18", + "defaultValue": "1", + "description": "LSB-MSB data alignment", + "flag": 3, + "enumerationVariants": "1 # LSB\n0 # MSB", + "maxValue": 2147483647, + "minValue": 0, + "id": "TSI_REF1_LSB_MSB", + "maxLength": 10000, + "name": "LSB-MSB data alignment", + "type": 4 + }, + { + "configId": "0x1000", + "defaultValue": "60", + "description": "Defines the length of the video test as number of frames. Default setting is 60 frames. Important: 32-bit version of TSI with very high resolution inputs may require the test length to be reduced.", + "flag": 3, + "id": "TSI_TEST_LENGTH", + "maxValue": 2147483647, + "minValue": 0, + "name": "Length of video test in frames", + "type": 4 + }, + { + "configId": "0x1001", + "defaultValue": "0", + "description": "Defines number of frame that are allowed to be considered as “failed” before the entire test is considered as “failed”. Default setting is 0.", + "flag": 3, + "id": "TSI_LIM_FRAME_MISMATCHES", + "maxValue": 2147483647, + "minValue": 0, + "name": "Maximum number of failed frames allowed per test", + "type": 4 + }, + { + "configId": "0x1002", + "defaultValue": "0", + "description": "Defines the number of pixels that allowed to be considered as “failed” before the frame is considered as “failed”. Default setting is 0.", + "flag": 3, + "id": "TSI_LIM_PIXEL_MISMATCHES", + "maxValue": 2147483647, + "minValue": 0, + "name": "Maximum number of failed pixels allowed per frame", + "type": 4 + }, + { + "configId": "0x1003", + "defaultValue": "0", + "description": "Defines maximum difference allowed between reference image and captured image. If the difference is larger than the value of this CI on any color channel, the pixel is considered “failed”. Default setting is 0.", + "flag": 3, + "id": "TSI_PIXEL_TOLERANCE", + "maxValue": 2147483647, + "minValue": 0, + "name": "Maximum difference between color values allowed", + "type": 4 + }, + { + "configId": "0x1080", + "defaultValue": "0", + "description": "Maximum number of frames failed frames saved per test run. Default setting is 0. If the setting is “0”, no frames are saved.", + "flag": 3, + "id": "TSI_MAX_AUTO_SAVE_FAILED", + "maxValue": 2147483647, + "minValue": 0, + "name": "Maximum number of failed frames to auto-save", + "type": 4 + }, + { + "configId": "0x1081", + "defaultValue": "", + "description": "Contains the full path to the folder where failed frames are to be saved without trailing backslash (‘\\’). No default. Failed frame file-name will be “Failed_<#>.ppm”, where <#> is replaced with an auto-incremented number.", + "flag": 3, + "id": "TSI_FAILED_FRAME_TARGET_FOLDER", + "maxLength": 10000, + "name": "Location where the failed frames are to be saved", + "type": 11 + }, + { + "configId": "0x1082", + "defaultValue": "0", + "description": "Defines the number of failed frames to be exported from the video test. Default setting is 0. If the setting is 0, no frames are exported.", + "flag": 3, + "id": "TSI_MAX_EXPORT_FAILED", + "maxValue": 2147483647, + "minValue": 0, + "name": "Maximum number of exported frames", + "type": 4 + }, + { + "configId": "0x1084", + "defaultValue": "0", + "description": "Export format", + "flag": 3, + "enumerationVariants": "0 # Binary file\n1 # PPM image\n2 # BMP image", + "maxValue": 2147483647, + "minValue": 0, + "id": "TSI_EXPORT_FORMAT", + "maxLength": 10000, + "name": "Export format", + "type": 4 + }, + { + "configId": "0x1085", + "defaultValue": "0", + "description": "Align 12", + "flag": 3, + "id": "TSI_EXPORT_ALIGN_12", + "mask": 1, + "name": "Align 12", + "type": 3 + } + ], + "id": "0x12", + "name": "Pixel Level Video Tests", + "old_names": [ + "Compare video frame sequence with a single reference" + ] +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x13]snk_dut_dp_8b10b_ll_cts.json b/UniTAP/dev/modules/dut_tests/cfg/[0x13]snk_dut_dp_8b10b_ll_cts.json new file mode 100644 index 0000000..a03a7e2 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x13]snk_dut_dp_8b10b_ll_cts.json @@ -0,0 +1,551 @@ +{ + "descriptions": [ + { + "configId": "0x01160000", + "defaultValue": "10000", + "description": "Defines a test timeout, in milliseconds. Default setting is 10000ms. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP14_SINKCTS_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout, in milliseconds", + "type": 4 + }, + { + "configId": "0x01160001", + "defaultValue": "2", + "description": "Defines a DSC video mode: 1920x1080 @ 30Hz. Default and only ID: 2.", + "flag": 3, + "id": "TSI_DP14_SINKCTS_DSC_VM_0", + "maxValue": 2147483647, + "minValue": 0, + "name": "DSC video mode: 1920x1080 @ 30Hz.", + "type": 4 + }, + { + "configId": "0x01160002", + "defaultValue": "0x00000000", + "description": "DUT Capability flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines the CTA resolution support. Default value: 0."}, + {"mask": "0x00000002", "description": "Defines the RB1 resolution support. Default value: 0."}, + {"mask": "0x00000004", "description": "Defines the RB2 resolution support. Default value: 0."} + ], + "flag": 1, + "id": "TSI_DP14_SINKCTS_DSC_VM_T_0", + "name": "DUT Capability flags.", + "type": 12 + }, + { + "configId": "0x01160003", + "defaultValue": "3", + "description": "Defines a DSC video mode: 1920x1080 @ 60Hz. Default and only ID: 3.", + "flag": 3, + "id": "TSI_DP14_SINKCTS_DSC_VM_1", + "maxValue": 2147483647, + "minValue": 0, + "name": "DSC video mode: 1920x1080 @ 60Hz.", + "type": 4 + }, + { + "configId": "0x01160004", + "defaultValue": "0x00000001", + "description": "DUT Capability flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines the CTA resolution support. Default value: 0."}, + {"mask": "0x00000002", "description": "Defines the RB1 resolution support. Default value: 0."}, + {"mask": "0x00000004", "description": "Defines the RB2 resolution support. Default value: 0."} + ], + "flag": 1, + "id": "TSI_DP14_SINKCTS_DSC_VM_T_1", + "name": "DUT Capability flags.", + "type": 12 + }, + { + "configId": "0x01160005", + "defaultValue": "4", + "description": "Defines a DSC video mode: 1920x1080 @ 120Hz. Default and only ID: 4.", + "flag": 3, + "id": "TSI_DP14_SINKCTS_DSC_VM_2", + "maxValue": 2147483647, + "minValue": 0, + "name": "DSC video mode: 1920x1080 @ 120Hz.", + "type": 4 + }, + { + "configId": "0x01160006", + "defaultValue": "0x00000000", + "description": "DUT Capability flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines the CTA resolution support. Default value: 0."}, + {"mask": "0x00000002", "description": "Defines the RB1 resolution support. Default value: 0."}, + {"mask": "0x00000004", "description": "Defines the RB2 resolution support. Default value: 0."} + ], + "flag": 1, + "id": "TSI_DP14_SINKCTS_DSC_VM_T_2", + "name": "DUT Capability flags.", + "type": 12 + }, + { + "configId": "0x01160007", + "defaultValue": "5", + "description": "Defines a DSC video mode: 3840x2160 @ 30Hz. Default and only ID: 5.", + "flag": 3, + "id": "TSI_DP14_SINKCTS_DSC_VM_3", + "maxValue": 2147483647, + "minValue": 0, + "name": "DSC video mode: 3840x2160 @ 30Hz.", + "type": 4 + }, + { + "configId": "0x01160008", + "defaultValue": "0x00000000", + "description": "DUT Capability flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines the CTA resolution support. Default value: 0."}, + {"mask": "0x00000002", "description": "Defines the RB1 resolution support. Default value: 0."}, + {"mask": "0x00000004", "description": "Defines the RB2 resolution support. Default value: 0."} + ], + "flag": 1, + "id": "TSI_DP14_SINKCTS_DSC_VM_T_3", + "name": "DUT Capability flags.", + "type": 12 + }, + { + "configId": "0x01160009", + "defaultValue": "6", + "description": "Defines a DSC video mode: 3840x2160 @ 60Hz. Default and only ID: 6.", + "flag": 3, + "id": "TSI_DP14_SINKCTS_DSC_VM_4", + "maxValue": 2147483647, + "minValue": 0, + "name": "DSC video mode: 3840x2160 @ 60Hz.", + "type": 4 + }, + { + "configId": "0x0116000A", + "defaultValue": "0x00000000", + "description": "DUT Capability flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines the CTA resolution support. Default value: 0."}, + {"mask": "0x00000002", "description": "Defines the RB1 resolution support. Default value: 0."}, + {"mask": "0x00000004", "description": "Defines the RB2 resolution support. Default value: 0."} + ], + "flag": 1, + "id": "TSI_DP14_SINKCTS_DSC_VM_T_4", + "name": "DUT Capability flags.", + "type": 12 + }, + { + "configId": "0x0116000B", + "defaultValue": "7", + "description": "Defines a DSC video mode: 3840x2160 @ 120Hz. Default and only ID: 7.", + "flag": 3, + "id": "TSI_DP14_SINKCTS_DSC_VM_5", + "maxValue": 2147483647, + "minValue": 0, + "name": "DSC video mode: 3840x2160 @ 120Hz.", + "type": 4 + }, + { + "configId": "0x0116000C", + "defaultValue": "0x00000000", + "description": "DUT Capability flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines the CTA resolution support. Default value: 0."}, + {"mask": "0x00000002", "description": "Defines the RB1 resolution support. Default value: 0."}, + {"mask": "0x00000004", "description": "Defines the RB2 resolution support. Default value: 0."} + ], + "flag": 1, + "id": "TSI_DP14_SINKCTS_DSC_VM_T_5", + "name": "DUT Capability flags.", + "type": 12 + }, + { + "configId": "0x0116000D", + "defaultValue": "8", + "description": "Defines a DSC video mode: 5120x2160 @ 30Hz. Default and only ID: 8.", + "flag": 3, + "id": "TSI_DP14_SINKCTS_DSC_VM_6", + "maxValue": 2147483647, + "minValue": 0, + "name": "DSC video mode: 5120x2160 @ 30Hz.", + "type": 4 + }, + { + "configId": "0x0116000E", + "defaultValue": "0x00000000", + "description": "DUT Capability flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines the CTA resolution support. Default value: 0."}, + {"mask": "0x00000002", "description": "Defines the RB1 resolution support. Default value: 0."}, + {"mask": "0x00000004", "description": "Defines the RB2 resolution support. Default value: 0."} + ], + "flag": 1, + "id": "TSI_DP14_SINKCTS_DSC_VM_T_6", + "name": "DUT Capability flags.", + "type": 12 + }, + { + "configId": "0x0116000F", + "defaultValue": "9", + "description": "Defines a DSC video mode: 5120x2160 @ 60Hz. Default and only ID: 9.", + "flag": 3, + "id": "TSI_DP14_SINKCTS_DSC_VM_7", + "maxValue": 2147483647, + "minValue": 0, + "name": "DSC video mode: 5120x2160 @ 60Hz.", + "type": 4 + }, + { + "configId": "0x01160010", + "defaultValue": "0x00000000", + "description": "DUT Capability flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines the CTA resolution support. Default value: 0."}, + {"mask": "0x00000002", "description": "Defines the RB1 resolution support. Default value: 0."}, + {"mask": "0x00000004", "description": "Defines the RB2 resolution support. Default value: 0."} + ], + "flag": 1, + "id": "TSI_DP14_SINKCTS_DSC_VM_T_7", + "name": "DUT Capability flags.", + "type": 12 + }, + { + "configId": "0x01160011", + "defaultValue": "10", + "description": "Defines a DSC video mode: 5120x2160 @ 120Hz. Default and only ID: 10.", + "flag": 3, + "id": "TSI_DP14_SINKCTS_DSC_VM_8", + "maxValue": 2147483647, + "minValue": 0, + "name": "DSC video mode: 5120x2160 @ 120Hz.", + "type": 4 + }, + { + "configId": "0x01160012", + "defaultValue": "0x00000000", + "description": "DUT Capability flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines the CTA resolution support. Default value: 0."}, + {"mask": "0x00000002", "description": "Defines the RB1 resolution support. Default value: 0."}, + {"mask": "0x00000004", "description": "Defines the RB2 resolution support. Default value: 0."} + ], + "flag": 1, + "id": "TSI_DP14_SINKCTS_DSC_VM_T_8", + "name": "DUT Capability flags.", + "type": 12 + }, + { + "configId": "0x01160013", + "defaultValue": "11", + "description": "Defines a DSC video mode: 7680x4320 @ 30Hz. Default and only ID: 11.", + "flag": 3, + "id": "TSI_DP14_SINKCTS_DSC_VM_9", + "maxValue": 2147483647, + "minValue": 0, + "name": "DSC video mode: 7680x4320 @ 30Hz.", + "type": 4 + }, + { + "configId": "0x01160014", + "defaultValue": "0x00000000", + "description": "DUT Capability flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines the CTA resolution support. Default value: 0."}, + {"mask": "0x00000002", "description": "Defines the RB1 resolution support. Default value: 0."}, + {"mask": "0x00000004", "description": "Defines the RB2 resolution support. Default value: 0."} + ], + "flag": 1, + "id": "TSI_DP14_SINKCTS_DSC_VM_T_9", + "name": "DUT Capability flags.", + "type": 12 + }, + { + "configId": "0x01160015", + "defaultValue": "12", + "description": "Defines a DSC video mode: 7680x4320 @ 60Hz. Default and only ID: 12.", + "flag": 3, + "id": "TSI_DP14_SINKCTS_DSC_VM_10", + "maxValue": 2147483647, + "minValue": 0, + "name": "DSC video mode: 7680x4320 @ 60Hz.", + "type": 4 + }, + { + "configId": "0x01160016", + "defaultValue": "0x00000000", + "description": "DUT Capability flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines the CTA resolution support. Default value: 0."}, + {"mask": "0x00000002", "description": "Defines the RB1 resolution support. Default value: 0."}, + {"mask": "0x00000004", "description": "Defines the RB2 resolution support. Default value: 0."} + ], + "flag": 1, + "id": "TSI_DP14_SINKCTS_DSC_VM_T_10", + "name": "DUT Capability flags.", + "type": 12 + }, + { + "configId": "0x01160017", + "defaultValue": "13", + "description": "Defines a DSC video mode: 7680x4320 @ 100Hz. Default and only ID: 13.", + "flag": 3, + "id": "TSI_DP14_SINKCTS_DSC_VM_11", + "maxValue": 2147483647, + "minValue": 0, + "name": "DSC video mode: 7680x4320 @ 100Hz.", + "type": 4 + }, + { + "configId": "0x01160018", + "defaultValue": "0x00000000", + "description": "DUT Capability flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines the CTA resolution support. Default value: 0."}, + {"mask": "0x00000002", "description": "Defines the RB1 resolution support. Default value: 0."}, + {"mask": "0x00000004", "description": "Defines the RB2 resolution support. Default value: 0."} + ], + "flag": 1, + "id": "TSI_DP14_SINKCTS_DSC_VM_T_11", + "name": "DUT Capability flags.", + "type": 12 + }, + { + "configId": "0x01160019", + "defaultValue": "1", + "description": "Defines the DSC video mode source. Default value is 0. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.\n", + "enumerationVariants": "0 # Use test configuration (below)\n1 # Use sink DUT EDID", + "flag": 3, + "id": "TSI_DP14_SINKCTS_DSC_SOURCE", + "maxValue": 2147480000, + "minValue": 0, + "name": "Source of the most packet video modes table", + "type": 4 + }, + { + "configId": "0x0116001A", + "defaultValue": "1", + "description": "Defines the Sink DUT support 444 CRC for Simple 422 bitstream. Default value is 1. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.\n", + "enumerationVariants": "0 # YCbCr 422 bitstream\n1 # YCbCr 444 bitstream", + "flag": 3, + "id": "TSI_DP14_SINKCTS_SUPPORT_444CRC", + "maxValue": 2147480000, + "minValue": 0, + "name": "Sink DUT support 444 CRC for Simple 422 bitstream.", + "type": 4 + }, + { + "configId": "0x0116001B", + "defaultValue": "1", + "description": "Defines the Source of the most packet video modes table. Default value is 1. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.\n", + "enumerationVariants": "0 # Use test configuration (below)\n1 # Use sink DUT EDID", + "flag": 3, + "id": "TSI_DP14_SINKCTS_PACKET_SOURCE", + "maxValue": 2147480000, + "minValue": 0, + "name": "Source of the most packet video modes table.", + "type": 4 + }, + { + "configId": "0x0116001C", + "defaultValue": "0", + "description": "Defines support for video mode: CVT 1280 x 800 @ 60p [RB1] 18bpp 99%.", + "flag": 3, + "id": "TSI_DP14_SINKCTS_VIDEO_MODE_0", + "mask": 1, + "name": "Support for video mode: CVT 1280 x 800 @ 60p [RB1] 18bpp 99%.", + "type": 3 + }, + { + "configId": "0x0116001D", + "defaultValue": "0", + "description": "Defines support for video mode: DMT 1280 x 768 @ 60p [RB1] 18bpp 95%.", + "flag": 3, + "id": "TSI_DP14_SINKCTS_VIDEO_MODE_1", + "mask": 1, + "name": "Support for video mode: DMT 1280 x 768 @ 60p [RB1] 18bpp 95%.", + "type": 3 + }, + { + "configId": "0x0116001E", + "defaultValue": "0", + "description": "Defines support for video mode: DMT 800 x 600 @ 60.317p 30bpp 93%.", + "flag": 3, + "id": "TSI_DP14_SINKCTS_VIDEO_MODE_2", + "mask": 1, + "name": "Support for video mode: DMT 800 x 600 @ 60.317p 30bpp 93%.", + "type": 3 + }, + { + "configId": "0x0116001F", + "defaultValue": "0", + "description": "Defines support for video mode: DMT 1024 x 768 @ 60p 18bpp 90%.", + "flag": 3, + "id": "TSI_DP14_SINKCTS_VIDEO_MODE_3", + "mask": 1, + "name": "Support for video mode: DMT 1024 x 768 @ 60p 18bpp 90%.", + "type": 3 + }, + { + "configId": "0x01160020", + "defaultValue": "0", + "description": "Defines support for video mode: DMT 1280 x 1024 @ 60p 24bpp 100%.", + "flag": 3, + "id": "TSI_DP14_SINKCTS_VIDEO_MODE_4", + "mask": 1, + "name": "Support for video mode: DMT 1280 x 1024 @ 60p 24bpp 100%.", + "type": 3 + }, + { + "configId": "0x01160021", + "defaultValue": "0", + "description": "Defines support for video mode: DMT 1280 x 960 @ 60p 24bpp 100%.", + "flag": 3, + "id": "TSI_DP14_SINKCTS_VIDEO_MODE_5", + "mask": 1, + "name": "Support for video mode: DMT 1280 x 960 @ 60p 24bpp 100%.", + "type": 3 + }, + { + "configId": "0x01160022", + "defaultValue": "0", + "description": "Defines support for video mode: DMT 1360 x 768 @ 60p 30bpp 99%.", + "flag": 3, + "id": "TSI_DP14_SINKCTS_VIDEO_MODE_6", + "mask": 1, + "name": "Support for video mode: DMT 1360 x 768 @ 60p 30bpp 99%.", + "type": 3 + }, + { + "configId": "0x01160023", + "defaultValue": "0", + "description": "Defines support for video mode: CVT 1280 x 800 @ 60p 30bpp 97%.", + "flag": 3, + "id": "TSI_DP14_SINKCTS_VIDEO_MODE_7", + "mask": 1, + "name": "Support for video mode: CVT 1280 x 800 @ 60p 30bpp 97%.", + "type": 3 + }, + { + "configId": "0x01160024", + "defaultValue": "0", + "description": "Defines support for video mode: DMT 1400 x 1050 @ 60p [RB1] 24bpp 94%.", + "flag": 3, + "id": "TSI_DP14_SINKCTS_VIDEO_MODE_8", + "mask": 1, + "name": "Support for video mode: DMT 1400 x 1050 @ 60p [RB1] 24bpp 94%.", + "type": 3 + }, + { + "configId": "0x01160025", + "defaultValue": "0", + "description": "Defines support for video mode: DMT 1280 x 768 @ 60p 30bpp 92%.", + "flag": 3, + "id": "TSI_DP14_SINKCTS_VIDEO_MODE_9", + "mask": 1, + "name": "Support for video mode: DMT 1280 x 768 @ 60p 30bpp 92%.", + "type": 3 + }, + { + "configId": "0x01160026", + "defaultValue": "0", + "description": "Defines support for video mode: CVT 1600 x 1200 @ 60p [RB1] 18bpp 90%.", + "flag": 3, + "id": "TSI_DP14_SINKCTS_VIDEO_MODE_10", + "mask": 1, + "name": "Support for video mode: CVT 1600 x 1200 @ 60p [RB1] 18bpp 90%.", + "type": 3 + }, + { + "configId": "0x01160027", + "defaultValue": "0", + "description": "Defines support for video mode: CVT 2048 x 1536 @ 60p [RB1] 24bpp 97%.", + "flag": 3, + "id": "TSI_DP14_SINKCTS_VIDEO_MODE_11", + "mask": 1, + "name": "Support for video mode: CVT 2048 x 1536 @ 60p [RB1] 24bpp 97%.", + "type": 3 + }, + { + "configId": "0x01160028", + "defaultValue": "0", + "description": "Defines support for video mode: DMT 1792 x 1344 @ 60p 24bpp 95%.", + "flag": 3, + "id": "TSI_DP14_SINKCTS_VIDEO_MODE_12", + "mask": 1, + "name": "Support for video mode: DMT 1792 x 1344 @ 60p 24bpp 95%.", + "type": 3 + }, + { + "configId": "0x01160029", + "defaultValue": "0", + "description": "Defines support for video mode: DMT 1600 x 1200 @ 60p 30bpp 94%.", + "flag": 3, + "id": "TSI_DP14_SINKCTS_VIDEO_MODE_13", + "mask": 1, + "name": "Support for video mode: DMT 1600 x 1200 @ 60p 30bpp 94%.", + "type": 3 + }, + { + "configId": "0x0116002A", + "defaultValue": "0", + "description": "Defines support for video mode: CTA 1440 x 480 @ 59.94p 24bpp 100%.", + "flag": 3, + "id": "TSI_DP14_SINKCTS_VIDEO_MODE_14", + "mask": 1, + "name": "Support for video mode: CTA 1440 x 480 @ 59.94p 24bpp 100%.", + "type": 3 + }, + { + "configId": "0x0116002B", + "defaultValue": "0", + "description": "Defines support for video mode: CTA 1440 x 576 @ 50p 24bpp 100%.", + "flag": 3, + "id": "TSI_DP14_SINKCTS_VIDEO_MODE_15", + "mask": 1, + "name": "Support for video mode: CTA 1440 x 576 @ 50p 24bpp 100%.", + "type": 3 + }, + { + "configId": "0x0116002C", + "defaultValue": "0", + "description": "Defines support for video mode: CTA 1920 x 1080 @ 60p 30bpp 86%.", + "flag": 3, + "id": "TSI_DP14_SINKCTS_VIDEO_MODE_16", + "mask": 1, + "name": "Support for video mode: CTA 1920 x 1080 @ 60p 30bpp 86%.", + "type": 3 + }, + { + "configId": "0x0116002D", + "defaultValue": "1", + "description": "Defines the Skip visual video check during DisplayID CTS tests. Default value is 0. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.\n", + "enumerationVariants": "0 # Never skip\n1 # Skip if CRC matches", + "flag": 3, + "id": "TSI_DP14_SINKCTS_VISUAL_TEST_CHECK", + "maxValue": 2147480000, + "minValue": 0, + "name": "Skip visual video check during DisplayID CTS tests.", + "type": 4 + } + ], + "id": "0x13", + "name": "DP 1.4 LL CTS", + "old_names": [ + "DP 1.4 LL CTS", + "DP 1.4 Link Layer Sink DUT CTS" + ] +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x14]src_dut_hdr10+.json b/UniTAP/dev/modules/dut_tests/cfg/[0x14]src_dut_hdr10+.json new file mode 100644 index 0000000..e0878a3 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x14]src_dut_hdr10+.json @@ -0,0 +1,29 @@ +{ + "descriptions": [ + { + "configId": "0x202A", + "defaultValue": "0", + "description": "Metadata at the same frame of associated video (0) or at one frame advance (1). Default 0", + "flag": 3, + "id": "TSI_HDR10_CTS_DISTR_SAME_FRAME", + "maxValue": 1, + "minValue": 0, + "name": "Video / Metadata frame offset", + "type": 4, + "enumerationVariants": "0 # Metadata at the same frame of associated Video data\n1 # Metadata at one frame advance from associated Video data" + }, + { + "configId": "0x202B", + "defaultValue": "10", + "description": "Defines the time period the TE will wait for metadata change; defined in seconds; default value is 10sec.\n", + "flag": 3, + "id": "TSI_HDR10_CTS_DISTR_TIMEOUT", + "maxValue": 1000, + "minValue": 1, + "name": "Metadata change timeout", + "type": 4 + } + ], + "id": "0x14", + "name": "HDR10+ Distribution Device Tests" +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x15]lttpr_dut_dp_128b132b_ll_cts.json b/UniTAP/dev/modules/dut_tests/cfg/[0x15]lttpr_dut_dp_128b132b_ll_cts.json new file mode 100644 index 0000000..d0a217d --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x15]lttpr_dut_dp_128b132b_ll_cts.json @@ -0,0 +1,116 @@ +{ + "descriptions": [ + { + "configId": "0x11B0000", + "defaultValue": "5000", + "description": "Defines test timeout in milliseconds, default value is 5000ms.", + "flag": 3, + "id": "TSI_DP20_LTTPR_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout in milliseconds.", + "reportFormat": "Test timeout = %2ms
", + "type": 4 + }, + { + "configId": "0x11B0001", + "defaultValue": "4", + "description": "Defines the maximum number of lanes supported by the DUT. Typical settings are 1, 2 and 4. Default value is 4. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.\n", + "enumerationVariants": "1\n2\n4", + "flag": 3, + "id": "TSI_DP20_LTTPR_MAX_LANES", + "maxValue": 2147480000, + "minValue": 0, + "name": "Maximum number of lanes supported by the DUT.", + "visible": false, + "reportFormat": "DUT capability settings and flags:
- Max lanes = %1
", + "type": 4 + }, + { + "configId": "0x11B0002", + "defaultValue": "30", + "description": "Defines the maximum link rate as multiplier for 0.27Gbps. Typical settings are 6 (RBR), 10 (HBR), 20 (HBR2) and 30 (HBR3). Notice that HBR3 link rate is usable only with UCD-400. The default setting is 30 (HBR3). Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "6 # RBR (1.62 Gbps)\n10 # HBR (2.7 Gbps)\n20 # HBR2 (5.4 Gbps)\n30 # HBR3 (8.10 Gbps)", + "flag": 3, + "id": "TSI_DP20_LTTPR_MAX_LINK_RATE", + "maxValue": 30, + "minValue": 6, + "name": "Maximum link rate supported by the DUT as multiplier for 0.27Gbps.", + "visible": false, + "reportFormat": "- Max link rate = %1
", + "type": 4 + }, + { + "configId": "0x11B0003", + "defaultValue": "0x00000000", + "description": "Defines the DUT capabilities as flags.", + "bitList": + [ + {"mask": "0x00000100", "description": "DUT is Type-C device", "reportFormat": "- DUT is Type-C device
"} + ], + "flag": 3, + "id": "TSI_DP20_LTTPR_DUT_CAPS", + "name": "DUT Capability flags.", + "reportFormat": "Test DUT capabilities flags:
", + "type": 12 + }, + { + "configId": "0x11B0004", + "defaultValue": "0x0000001A", + "description": "Defines the DUT Test automation capabilities as flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "TEST_LINK_TRAINING", "reportFormat": "- TEST_LINK_TRAINING
"}, + {"mask": "0x00000002", "description": "TEST_EDID_READ", "reportFormat": "- TEST_EDID_READ
"}, + {"mask": "0x00000004", "description": "TEST_VIDEO_PATTERN", "reportFormat": "- TEST_VIDEO_PATTERN
"}, + {"mask": "0x00000020", "description": "TEST_AUDIO_PATTERN", "reportFormat": "- TEST_AUDIO_PATTERN
"}, + {"mask": "0x00000018", "description": "Event indicating DUT ready", "enumerationVariants": "0 # Always ready\n1 # EDID read\n2 # Link Training end\n3 # Active Video", "reportFormat": "- Event indicating DUT ready: %1
"} + ], + "flag": 3, + "id": "TSI_DP20_LTTPR_DUT_TA", + "name": "DUT Test automation flags.", + "visible": false, + "reportFormat": "
Test automation flags and settings:
", + "type": 12 + }, + { + "configId": "0x11B0005", + "defaultValue": "1000", + "description": "Defines the duration of long HPD pulses generated by the tests. The duration is defined in millieconds. Default setting is 1000ms. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.\n", + "flag": 3, + "id": "TSI_DP20_LTTPR_LONG_HPD_PULSE", + "maxValue": 2147483647, + "minValue": 0, + "name": "Long HPD pulse duration, in milliseconds.", + "reportFormat": "Long HPD Duration = %1ms
", + "type": 4 + }, + { + "configId": "0x11B0006", + "defaultValue": "100", + "description": "Defines the delay of CR iteration. The duration is defined in microseconds. Default setting is 100us. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.\n", + "flag": 3, + "id": "TSI_DP20_LTTPR_DEBUG_CR_ITERATION_DELAY", + "maxValue": 2147483647, + "minValue": 0, + "name": "CR iteration delay, in microseconds.", + "reportFormat": "CR Iteration Delay = %1us

", + "type": 4 + }, + { + "configId": "0x11B0007", + "defaultValue": "0x00000000", + "description": " Defines the Debug mode configuration.\n", + "bitList": + [ + {"mask": "0x00000001", "description": "Force video check enabled"} + ], + "flag": 3, + "id": "TSI_DP20_LTTPR_SINKCTS_DEBUG_CONF", + "name": "Debug mode configuration.", + "type": 12 + } + ], + "id": "0x15", + "name": "DP 2.1 LTTPR CTS" +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x16]src_dut_hdr10+sstm.json b/UniTAP/dev/modules/dut_tests/cfg/[0x16]src_dut_hdr10+sstm.json new file mode 100644 index 0000000..7b49b26 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x16]src_dut_hdr10+sstm.json @@ -0,0 +1,6 @@ +{ + "descriptions": [ + ], + "id": "0x16", + "name": "HDR10+ SSTM Tests for Source" +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x17]src_epr_power_sink.json b/UniTAP/dev/modules/dut_tests/cfg/[0x17]src_epr_power_sink.json new file mode 100644 index 0000000..3cdbb7d --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x17]src_epr_power_sink.json @@ -0,0 +1,18 @@ +{ + "descriptions": [ + { + "configId": "0x11C0000", + "defaultValue": "5000", + "description": "Defines test timeout in milliseconds, default value is 5000ms.", + "flag": 3, + "id": "TSI_USBC_EPR_POWER_SINK_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout in milliseconds.", + "reportFormat": "Test timeout = %2ms
", + "type": 4 + } + ], + "id": "0x17", + "name": "EPR Power Sink Tests" +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x18]snk_dut_dp_128b132b_ll_cts.json b/UniTAP/dev/modules/dut_tests/cfg/[0x18]snk_dut_dp_128b132b_ll_cts.json new file mode 100644 index 0000000..038cba7 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x18]snk_dut_dp_128b132b_ll_cts.json @@ -0,0 +1,727 @@ +{ + "descriptions": [ + { + "configId": "0x011A0000", + "defaultValue": "10000", + "description": "Defines a test timeout, in milliseconds. Default setting is 10000ms. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP20_SINKCTS_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout, in milliseconds", + "type": 4 + }, + { + "configId": "0x011A0001", + "defaultValue": "2", + "description": "Defines a DSC video mode: 1920x1080 @ 30Hz. Default and only ID: 2.", + "flag": 3, + "id": "TSI_DP20_SINKCTS_DSC_VM_0", + "maxValue": 2147483647, + "minValue": 0, + "name": "DSC video mode: 1920x1080 @ 30Hz.", + "type": 4 + }, + { + "configId": "0x011A0002", + "defaultValue": "0x00000000", + "description": "DUT Capability flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines the CTA resolution support. Default value: 0."}, + {"mask": "0x00000002", "description": "Defines the RB1 resolution support. Default value: 0."}, + {"mask": "0x00000004", "description": "Defines the RB2 resolution support. Default value: 0."} + ], + "flag": 1, + "id": "TSI_DP20_SINKCTS_DSC_VM_T_0", + "name": "DUT Capability flags.", + "type": 12 + }, + { + "configId": "0x011A0003", + "defaultValue": "3", + "description": "Defines a DSC video mode: 1920x1080 @ 60Hz. Default and only ID: 3.", + "flag": 3, + "id": "TSI_DP20_SINKCTS_DSC_VM_1", + "maxValue": 2147483647, + "minValue": 0, + "name": "DSC video mode: 1920x1080 @ 60Hz.", + "type": 4 + }, + { + "configId": "0x011A0004", + "defaultValue": "0x00000001", + "description": "DUT Capability flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines the CTA resolution support. Default value: 0."}, + {"mask": "0x00000002", "description": "Defines the RB1 resolution support. Default value: 0."}, + {"mask": "0x00000004", "description": "Defines the RB2 resolution support. Default value: 0."} + ], + "flag": 1, + "id": "TSI_DP20_SINKCTS_DSC_VM_T_1", + "name": "DUT Capability flags.", + "type": 12 + }, + { + "configId": "0x011A0005", + "defaultValue": "4", + "description": "Defines a DSC video mode: 1920x1080 @ 120Hz. Default and only ID: 4.", + "flag": 3, + "id": "TSI_DP20_SINKCTS_DSC_VM_2", + "maxValue": 2147483647, + "minValue": 0, + "name": "DSC video mode: 1920x1080 @ 120Hz.", + "type": 4 + }, + { + "configId": "0x011A0006", + "defaultValue": "0x00000000", + "description": "DUT Capability flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines the CTA resolution support. Default value: 0."}, + {"mask": "0x00000002", "description": "Defines the RB1 resolution support. Default value: 0."}, + {"mask": "0x00000004", "description": "Defines the RB2 resolution support. Default value: 0."} + ], + "flag": 1, + "id": "TSI_DP20_SINKCTS_DSC_VM_T_2", + "name": "DUT Capability flags.", + "type": 12 + }, + { + "configId": "0x011A0007", + "defaultValue": "5", + "description": "Defines a DSC video mode: 3840x2160 @ 30Hz. Default and only ID: 5.", + "flag": 3, + "id": "TSI_DP20_SINKCTS_DSC_VM_3", + "maxValue": 2147483647, + "minValue": 0, + "name": "DSC video mode: 3840x2160 @ 30Hz.", + "type": 4 + }, + { + "configId": "0x011A0008", + "defaultValue": "0x00000000", + "description": "DUT Capability flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines the CTA resolution support. Default value: 0."}, + {"mask": "0x00000002", "description": "Defines the RB1 resolution support. Default value: 0."}, + {"mask": "0x00000004", "description": "Defines the RB2 resolution support. Default value: 0."} + ], + "flag": 1, + "id": "TSI_DP20_SINKCTS_DSC_VM_T_3", + "name": "DUT Capability flags.", + "type": 12 + }, + { + "configId": "0x011A0009", + "defaultValue": "6", + "description": "Defines a DSC video mode: 3840x2160 @ 60Hz. Default and only ID: 6.", + "flag": 3, + "id": "TSI_DP20_SINKCTS_DSC_VM_4", + "maxValue": 2147483647, + "minValue": 0, + "name": "DSC video mode: 3840x2160 @ 60Hz.", + "type": 4 + }, + { + "configId": "0x011A000A", + "defaultValue": "0x00000000", + "description": "DUT Capability flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines the CTA resolution support. Default value: 0."}, + {"mask": "0x00000002", "description": "Defines the RB1 resolution support. Default value: 0."}, + {"mask": "0x00000004", "description": "Defines the RB2 resolution support. Default value: 0."} + ], + "flag": 1, + "id": "TSI_DP20_SINKCTS_DSC_VM_T_4", + "name": "DUT Capability flags.", + "type": 12 + }, + { + "configId": "0x011A000B", + "defaultValue": "7", + "description": "Defines a DSC video mode: 3840x2160 @ 120Hz. Default and only ID: 7.", + "flag": 3, + "id": "TSI_DP20_SINKCTS_DSC_VM_5", + "maxValue": 2147483647, + "minValue": 0, + "name": "DSC video mode: 3840x2160 @ 120Hz.", + "type": 4 + }, + { + "configId": "0x011A000C", + "defaultValue": "0x00000000", + "description": "DUT Capability flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines the CTA resolution support. Default value: 0."}, + {"mask": "0x00000002", "description": "Defines the RB1 resolution support. Default value: 0."}, + {"mask": "0x00000004", "description": "Defines the RB2 resolution support. Default value: 0."} + ], + "flag": 1, + "id": "TSI_DP20_SINKCTS_DSC_VM_T_5", + "name": "DUT Capability flags.", + "type": 12 + }, + { + "configId": "0x011A000D", + "defaultValue": "8", + "description": "Defines a DSC video mode: 5120x2160 @ 30Hz. Default and only ID: 8.", + "flag": 3, + "id": "TSI_DP20_SINKCTS_DSC_VM_6", + "maxValue": 2147483647, + "minValue": 0, + "name": "DSC video mode: 5120x2160 @ 30Hz.", + "type": 4 + }, + { + "configId": "0x011A000E", + "defaultValue": "0x00000000", + "description": "DUT Capability flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines the CTA resolution support. Default value: 0."}, + {"mask": "0x00000002", "description": "Defines the RB1 resolution support. Default value: 0."}, + {"mask": "0x00000004", "description": "Defines the RB2 resolution support. Default value: 0."} + ], + "flag": 1, + "id": "TSI_DP20_SINKCTS_DSC_VM_T_6", + "name": "DUT Capability flags.", + "type": 12 + }, + { + "configId": "0x011A000F", + "defaultValue": "9", + "description": "Defines a DSC video mode: 5120x2160 @ 60Hz. Default and only ID: 9.", + "flag": 3, + "id": "TSI_DP20_SINKCTS_DSC_VM_7", + "maxValue": 2147483647, + "minValue": 0, + "name": "DSC video mode: 5120x2160 @ 60Hz.", + "type": 4 + }, + { + "configId": "0x011A0010", + "defaultValue": "0x00000000", + "description": "DUT Capability flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines the CTA resolution support. Default value: 0."}, + {"mask": "0x00000002", "description": "Defines the RB1 resolution support. Default value: 0."}, + {"mask": "0x00000004", "description": "Defines the RB2 resolution support. Default value: 0."} + ], + "flag": 1, + "id": "TSI_DP20_SINKCTS_DSC_VM_T_7", + "name": "DUT Capability flags.", + "type": 12 + }, + { + "configId": "0x011A0011", + "defaultValue": "10", + "description": "Defines a DSC video mode: 5120x2160 @ 120Hz. Default and only ID: 10.", + "flag": 3, + "id": "TSI_DP20_SINKCTS_DSC_VM_8", + "maxValue": 2147483647, + "minValue": 0, + "name": "DSC video mode: 5120x2160 @ 120Hz.", + "type": 4 + }, + { + "configId": "0x011A0012", + "defaultValue": "0x00000000", + "description": "DUT Capability flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines the CTA resolution support. Default value: 0."}, + {"mask": "0x00000002", "description": "Defines the RB1 resolution support. Default value: 0."}, + {"mask": "0x00000004", "description": "Defines the RB2 resolution support. Default value: 0."} + ], + "flag": 1, + "id": "TSI_DP20_SINKCTS_DSC_VM_T_8", + "name": "DUT Capability flags.", + "type": 12 + }, + { + "configId": "0x011A0013", + "defaultValue": "11", + "description": "Defines a DSC video mode: 7680x4320 @ 30Hz. Default and only ID: 11.", + "flag": 3, + "id": "TSI_DP20_SINKCTS_DSC_VM_9", + "maxValue": 2147483647, + "minValue": 0, + "name": "DSC video mode: 7680x4320 @ 30Hz.", + "type": 4 + }, + { + "configId": "0x011A0014", + "defaultValue": "0x00000000", + "description": "DUT Capability flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines the CTA resolution support. Default value: 0."}, + {"mask": "0x00000002", "description": "Defines the RB1 resolution support. Default value: 0."}, + {"mask": "0x00000004", "description": "Defines the RB2 resolution support. Default value: 0."} + ], + "flag": 1, + "id": "TSI_DP20_SINKCTS_DSC_VM_T_9", + "name": "DUT Capability flags.", + "type": 12 + }, + { + "configId": "0x011A0015", + "defaultValue": "12", + "description": "Defines a DSC video mode: 7680x4320 @ 60Hz. Default and only ID: 12.", + "flag": 3, + "id": "TSI_DP20_SINKCTS_DSC_VM_10", + "maxValue": 2147483647, + "minValue": 0, + "name": "DSC video mode: 7680x4320 @ 60Hz.", + "type": 4 + }, + { + "configId": "0x011A0016", + "defaultValue": "0x00000000", + "description": "DUT Capability flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines the CTA resolution support. Default value: 0."}, + {"mask": "0x00000002", "description": "Defines the RB1 resolution support. Default value: 0."}, + {"mask": "0x00000004", "description": "Defines the RB2 resolution support. Default value: 0."} + ], + "flag": 1, + "id": "TSI_DP20_SINKCTS_DSC_VM_T_10", + "name": "DUT Capability flags.", + "type": 12 + }, + { + "configId": "0x011A0017", + "defaultValue": "13", + "description": "Defines a DSC video mode: 7680x4320 @ 100Hz. Default and only ID: 13.", + "flag": 3, + "id": "TSI_DP20_SINKCTS_DSC_VM_11", + "maxValue": 2147483647, + "minValue": 0, + "name": "DSC video mode: 7680x4320 @ 100Hz.", + "type": 4 + }, + { + "configId": "0x011A0018", + "defaultValue": "0x00000000", + "description": "DUT Capability flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines the CTA resolution support. Default value: 0."}, + {"mask": "0x00000002", "description": "Defines the RB1 resolution support. Default value: 0."}, + {"mask": "0x00000004", "description": "Defines the RB2 resolution support. Default value: 0."} + ], + "flag": 1, + "id": "TSI_DP20_SINKCTS_DSC_VM_T_11", + "name": "DUT Capability flags.", + "type": 12 + }, + { + "configId": "0x011A0019", + "defaultValue": "1", + "description": "Defines the DSC video mode source. Default value is 0. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.\n", + "enumerationVariants": "0 # Use test configuration (below)\n1 # Use sink DUT EDID", + "flag": 3, + "id": "TSI_DP20_SINKCTS_DSC_SOURCE", + "maxValue": 2147480000, + "minValue": 0, + "name": "Source of the most packet video modes table", + "type": 4 + }, + { + "configId": "0x011A001A", + "defaultValue": "1", + "description": "Defines the Sink DUT support 444 CRC for Simple 422 bitstream. Default value is 1. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.\n", + "enumerationVariants": "0 # YCbCr 422 bitstream\n1 # YCbCr 444 bitstream", + "flag": 3, + "id": "TSI_DP20_SINKCTS_SUPPORT_444CRC", + "maxValue": 2147480000, + "minValue": 0, + "name": "Sink DUT support 444 CRC for Simple 422 bitstream.", + "type": 4 + }, + { + "configId": "0x011A001B", + "defaultValue": "1", + "description": "Defines the Source of the most packet video modes table. Default value is 1. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.\n", + "enumerationVariants": "0 # Use test configuration (below)\n1 # Use sink DUT EDID", + "flag": 3, + "id": "TSI_DP20_SINKCTS_PACKET_SOURCE", + "maxValue": 2147480000, + "minValue": 0, + "name": "Source of the most packet video modes table.", + "type": 4 + }, + { + "configId": "0x011A001C", + "defaultValue": "0", + "description": "Defines support for video mode: CVT 1280 x 800 @ 60p [RB1] 18bpp 99%.", + "flag": 3, + "id": "TSI_DP20_SINKCTS_VIDEO_MODE_0", + "mask": 1, + "name": "Support for video mode: CVT 1280 x 800 @ 60p [RB1] 18bpp 99%.", + "type": 3 + }, + { + "configId": "0x011A001D", + "defaultValue": "0", + "description": "Defines support for video mode: DMT 1280 x 768 @ 60p [RB1] 18bpp 95%.", + "flag": 3, + "id": "TSI_DP20_SINKCTS_VIDEO_MODE_1", + "mask": 1, + "name": "Support for video mode: DMT 1280 x 768 @ 60p [RB1] 18bpp 95%.", + "type": 3 + }, + { + "configId": "0x011A001E", + "defaultValue": "0", + "description": "Defines support for video mode: DMT 800 x 600 @ 60.317p 30bpp 93%.", + "flag": 3, + "id": "TSI_DP20_SINKCTS_VIDEO_MODE_2", + "mask": 1, + "name": "Support for video mode: DMT 800 x 600 @ 60.317p 30bpp 93%.", + "type": 3 + }, + { + "configId": "0x011A001F", + "defaultValue": "0", + "description": "Defines support for video mode: DMT 1024 x 768 @ 60p 18bpp 90%.", + "flag": 3, + "id": "TSI_DP20_SINKCTS_VIDEO_MODE_3", + "mask": 1, + "name": "Support for video mode: DMT 1024 x 768 @ 60p 18bpp 90%.", + "type": 3 + }, + { + "configId": "0x011A0020", + "defaultValue": "0", + "description": "Defines support for video mode: DMT 1280 x 1024 @ 60p 24bpp 100%.", + "flag": 3, + "id": "TSI_DP20_SINKCTS_VIDEO_MODE_4", + "mask": 1, + "name": "Support for video mode: DMT 1280 x 1024 @ 60p 24bpp 100%.", + "type": 3 + }, + { + "configId": "0x011A0021", + "defaultValue": "0", + "description": "Defines support for video mode: DMT 1280 x 960 @ 60p 24bpp 100%.", + "flag": 3, + "id": "TSI_DP20_SINKCTS_VIDEO_MODE_5", + "mask": 1, + "name": "Support for video mode: DMT 1280 x 960 @ 60p 24bpp 100%.", + "type": 3 + }, + { + "configId": "0x011A0022", + "defaultValue": "0", + "description": "Defines support for video mode: DMT 1360 x 768 @ 60p 30bpp 99%.", + "flag": 3, + "id": "TSI_DP20_SINKCTS_VIDEO_MODE_6", + "mask": 1, + "name": "Support for video mode: DMT 1360 x 768 @ 60p 30bpp 99%.", + "type": 3 + }, + { + "configId": "0x011A0023", + "defaultValue": "0", + "description": "Defines support for video mode: CVT 1280 x 800 @ 60p 30bpp 97%.", + "flag": 3, + "id": "TSI_DP20_SINKCTS_VIDEO_MODE_7", + "mask": 1, + "name": "Support for video mode: CVT 1280 x 800 @ 60p 30bpp 97%.", + "type": 3 + }, + { + "configId": "0x011A0024", + "defaultValue": "0", + "description": "Defines support for video mode: DMT 1400 x 1050 @ 60p [RB1] 24bpp 94%.", + "flag": 3, + "id": "TSI_DP20_SINKCTS_VIDEO_MODE_8", + "mask": 1, + "name": "Support for video mode: DMT 1400 x 1050 @ 60p [RB1] 24bpp 94%.", + "type": 3 + }, + { + "configId": "0x011A0025", + "defaultValue": "0", + "description": "Defines support for video mode: DMT 1280 x 768 @ 60p 30bpp 92%.", + "flag": 3, + "id": "TSI_DP20_SINKCTS_VIDEO_MODE_9", + "mask": 1, + "name": "Support for video mode: DMT 1280 x 768 @ 60p 30bpp 92%.", + "type": 3 + }, + { + "configId": "0x011A0026", + "defaultValue": "0", + "description": "Defines support for video mode: CVT 1600 x 1200 @ 60p [RB1] 18bpp 90%.", + "flag": 3, + "id": "TSI_DP20_SINKCTS_VIDEO_MODE_10", + "mask": 1, + "name": "Support for video mode: CVT 1600 x 1200 @ 60p [RB1] 18bpp 90%.", + "type": 3 + }, + { + "configId": "0x011A0027", + "defaultValue": "0", + "description": "Defines support for video mode: CVT 2048 x 1536 @ 60p [RB1] 24bpp 97%.", + "flag": 3, + "id": "TSI_DP20_SINKCTS_VIDEO_MODE_11", + "mask": 1, + "name": "Support for video mode: CVT 2048 x 1536 @ 60p [RB1] 24bpp 97%.", + "type": 3 + }, + { + "configId": "0x011A0028", + "defaultValue": "0", + "description": "Defines support for video mode: DMT 1792 x 1344 @ 60p 24bpp 95%.", + "flag": 3, + "id": "TSI_DP20_SINKCTS_VIDEO_MODE_12", + "mask": 1, + "name": "Support for video mode: DMT 1792 x 1344 @ 60p 24bpp 95%.", + "type": 3 + }, + { + "configId": "0x011A0029", + "defaultValue": "0", + "description": "Defines support for video mode: DMT 1600 x 1200 @ 60p 30bpp 94%.", + "flag": 3, + "id": "TSI_DP20_SINKCTS_VIDEO_MODE_13", + "mask": 1, + "name": "Support for video mode: DMT 1600 x 1200 @ 60p 30bpp 94%.", + "type": 3 + }, + { + "configId": "0x011A002A", + "defaultValue": "0", + "description": "Defines support for video mode: CTA 1440 x 480 @ 59.94p 24bpp 100%.", + "flag": 3, + "id": "TSI_DP20_SINKCTS_VIDEO_MODE_14", + "mask": 1, + "name": "Support for video mode: CTA 1440 x 480 @ 59.94p 24bpp 100%.", + "type": 3 + }, + { + "configId": "0x011A002B", + "defaultValue": "0", + "description": "Defines support for video mode: CTA 1440 x 576 @ 50p 24bpp 100%.", + "flag": 3, + "id": "TSI_DP20_SINKCTS_VIDEO_MODE_15", + "mask": 1, + "name": "Support for video mode: CTA 1440 x 576 @ 50p 24bpp 100%.", + "type": 3 + }, + { + "configId": "0x011A002C", + "defaultValue": "0", + "description": "Defines support for video mode: CTA 1920 x 1080 @ 60p 30bpp 86%.", + "flag": 3, + "id": "TSI_DP20_SINKCTS_VIDEO_MODE_16", + "mask": 1, + "name": "Support for video mode: CTA 1920 x 1080 @ 60p 30bpp 86%.", + "type": 3 + }, + { + "configId": "0x011A002D", + "defaultValue": "1", + "description": "Defines the Skip visual video check during DisplayID CTS tests. Default value is 0. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.\n", + "enumerationVariants": "0 # Never skip\n1 # Skip if CRC matches", + "flag": 3, + "id": "TSI_DP20_SINKCTS_VISUAL_TEST_CHECK", + "maxValue": 2147480000, + "minValue": 0, + "name": "Skip visual video check during DisplayID CTS tests.", + "type": 4 + }, + { + "configId": "0x011A002E", + "defaultValue": "0x00000010", + "description": "Resolution 1920x1080", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines 1920x1080 @ 30Hz CTA timing support."}, + {"mask": "0x00000002", "description": "Defines 1920x1080 @ 30Hz RBv1 timing support."}, + {"mask": "0x00000004", "description": "Defines 1920x1080 @ 30Hz RBv2 timing support."}, + {"mask": "0x00000010", "description": "Defines 1920x1080 @ 60Hz CTA timing support."}, + {"mask": "0x00000020", "description": "Defines 1920x1080 @ 60Hz RBv1 timing support."}, + {"mask": "0x00000040", "description": "Defines 1920x1080 @ 60Hz RBv2 timing support."}, + {"mask": "0x00000100", "description": "Defines 1920x1080 @ 120Hz CTA timing support."}, + {"mask": "0x00000200", "description": "Defines 1920x1080 @ 120Hz RBv1 timing support."}, + {"mask": "0x00000400", "description": "Defines 1920x1080 @ 120Hz RBv2 timing support."}, + {"mask": "0x00002000", "description": "Defines 1920x1080 @ 144Hz RBv1 timing support.."}, + {"mask": "0x00004000", "description": "Defines 1920x1080 @ 144Hz RBv2 timing support."}, + {"mask": "0x00008000", "description": "Defines 1920x1080 @ 144Hz OVT timing support."}, + {"mask": "0x00020000", "description": "Defines 1920x1080 @ 240Hz RBv1 timing support."}, + {"mask": "0x00040000", "description": "Defines 1920x1080 @ 240Hz RBv2 timing support."}, + {"mask": "0x00080000", "description": "Defines 1920x1080 @ 240Hz OVT timing support."} + ], + "flag": 1, + "id": "TSI_DP20_SINKCTS_VMT_1920_1080", + "name": "Resolution supported by Sink DUT", + "type": 12 + }, + { + "configId": "0x011A002F", + "defaultValue": "0x00000000", + "description": "Resolution 3840x2160", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines 3840x2160 @ 30Hz CTA timing support."}, + {"mask": "0x00000002", "description": "Defines 3840x2160 @ 30Hz RBv1 timing support."}, + {"mask": "0x00000004", "description": "Defines 3840x2160 @ 30Hz RBv2 timing support."}, + {"mask": "0x00000010", "description": "Defines 3840x2160 @ 60Hz CTA timing support."}, + {"mask": "0x00000020", "description": "Defines 3840x2160 @ 60Hz RBv1 timing support."}, + {"mask": "0x00000040", "description": "Defines 3840x2160 @ 60Hz RBv2 timing support."}, + {"mask": "0x00000100", "description": "Defines 3840x2160 @ 120Hz CTA timing support."}, + {"mask": "0x00000200", "description": "Defines 3840x2160 @ 120Hz RBv1 timing support."}, + {"mask": "0x00000400", "description": "Defines 3840x2160 @ 120Hz RBv2 timing support."}, + {"mask": "0x00002000", "description": "Defines 3840x2160 @ 144Hz RBv1 timing support.."}, + {"mask": "0x00004000", "description": "Defines 3840x2160 @ 144Hz RBv2 timing support."}, + {"mask": "0x00008000", "description": "Defines 3840x2160 @ 144Hz OVT timing support."}, + {"mask": "0x00020000", "description": "Defines 3840x2160 @ 240Hz RBv1 timing support."}, + {"mask": "0x00040000", "description": "Defines 3840x2160 @ 240Hz RBv2 timing support."}, + {"mask": "0x00080000", "description": "Defines 3840x2160 @ 240Hz OVT timing support."} + ], + "flag": 1, + "id": "TSI_DP20_SINKCTS_VMT_3840_2160", + "name": "Resolution supported by Sink DUT", + "type": 12 + }, + { + "configId": "0x011A0030", + "defaultValue": "0x00000000", + "description": "Resolution 5120x2160", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines 5120x2160 @ 30Hz CTA timing support."}, + {"mask": "0x00000002", "description": "Defines 5120x2160 @ 30Hz RBv1 timing support."}, + {"mask": "0x00000004", "description": "Defines 5120x2160 @ 30Hz RBv2 timing support."}, + {"mask": "0x00000010", "description": "Defines 5120x2160 @ 60Hz CTA timing support."}, + {"mask": "0x00000020", "description": "Defines 5120x2160 @ 60Hz RBv1 timing support."}, + {"mask": "0x00000040", "description": "Defines 5120x2160 @ 60Hz RBv2 timing support."}, + {"mask": "0x00000100", "description": "Defines 5120x2160 @ 120Hz CTA timing support."}, + {"mask": "0x00000200", "description": "Defines 5120x2160 @ 120Hz RBv1 timing support."}, + {"mask": "0x00000400", "description": "Defines 5120x2160 @ 120Hz RBv2 timing support."}, + {"mask": "0x00002000", "description": "Defines 5120x2160 @ 144Hz RBv1 timing support.."}, + {"mask": "0x00004000", "description": "Defines 5120x2160 @ 144Hz RBv2 timing support."}, + {"mask": "0x00008000", "description": "Defines 5120x2160 @ 144Hz OVT timing support."}, + {"mask": "0x00020000", "description": "Defines 5120x2160 @ 240Hz RBv1 timing support."}, + {"mask": "0x00040000", "description": "Defines 5120x2160 @ 240Hz RBv2 timing support."}, + {"mask": "0x00080000", "description": "Defines 5120x2160 @ 240Hz OVT timing support."} + ], + "flag": 1, + "id": "TSI_DP20_SINKCTS_VMT_5120_2160", + "name": "Resolution supported by Sink DUT", + "type": 12 + }, + { + "configId": "0x011A0031", + "defaultValue": "0x00000000", + "description": "Resolution 7680x4320", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines 7680x4320 @ 30Hz CTA timing support."}, + {"mask": "0x00000002", "description": "Defines 7680x4320 @ 30Hz RBv1 timing support."}, + {"mask": "0x00000004", "description": "Defines 7680x4320 @ 30Hz RBv2 timing support."}, + {"mask": "0x00000010", "description": "Defines 7680x4320 @ 60Hz CTA timing support."}, + {"mask": "0x00000020", "description": "Defines 7680x4320 @ 60Hz RBv1 timing support."}, + {"mask": "0x00000040", "description": "Defines 7680x4320 @ 60Hz RBv2 timing support."}, + {"mask": "0x00000100", "description": "Defines 7680x4320 @ 120Hz CTA timing support."}, + {"mask": "0x00000200", "description": "Defines 7680x4320 @ 120Hz RBv1 timing support."}, + {"mask": "0x00000400", "description": "Defines 7680x4320 @ 120Hz RBv2 timing support."}, + {"mask": "0x00002000", "description": "Defines 7680x4320 @ 144Hz RBv1 timing support.."}, + {"mask": "0x00004000", "description": "Defines 7680x4320 @ 144Hz RBv2 timing support."}, + {"mask": "0x00008000", "description": "Defines 7680x4320 @ 144Hz OVT timing support."}, + {"mask": "0x00020000", "description": "Defines 7680x4320 @ 240Hz RBv1 timing support."}, + {"mask": "0x00040000", "description": "Defines 7680x4320 @ 240Hz RBv2 timing support."}, + {"mask": "0x00080000", "description": "Defines 7680x4320 @ 240Hz OVT timing support."} + ], + "flag": 1, + "id": "TSI_DP20_SINKCTS_VMT_7680_4320", + "name": "Resolution supported by Sink DUT", + "type": 12 + }, + { + "configId": "0x011A0032", + "defaultValue": "0x00000000", + "description": "Resolution 10240x4320", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines 10240x4320 @ 30Hz CTA timing support."}, + {"mask": "0x00000002", "description": "Defines 10240x4320 @ 30Hz RBv1 timing support."}, + {"mask": "0x00000004", "description": "Defines 10240x4320 @ 30Hz RBv2 timing support."}, + {"mask": "0x00000010", "description": "Defines 10240x4320 @ 60Hz CTA timing support."}, + {"mask": "0x00000020", "description": "Defines 10240x4320 @ 60Hz RBv1 timing support."}, + {"mask": "0x00000040", "description": "Defines 10240x4320 @ 60Hz RBv2 timing support."}, + {"mask": "0x00000100", "description": "Defines 10240x4320 @ 120Hz CTA timing support."}, + {"mask": "0x00000200", "description": "Defines 10240x4320 @ 120Hz RBv1 timing support."}, + {"mask": "0x00000400", "description": "Defines 10240x4320 @ 120Hz RBv2 timing support."}, + {"mask": "0x00002000", "description": "Defines 10240x4320 @ 144Hz RBv1 timing support.."}, + {"mask": "0x00004000", "description": "Defines 10240x4320 @ 144Hz RBv2 timing support."}, + {"mask": "0x00008000", "description": "Defines 10240x4320 @ 144Hz OVT timing support."}, + {"mask": "0x00020000", "description": "Defines 10240x4320 @ 240Hz RBv1 timing support."}, + {"mask": "0x00040000", "description": "Defines 10240x4320 @ 240Hz RBv2 timing support."}, + {"mask": "0x00080000", "description": "Defines 10240x4320 @ 240Hz OVT timing support."} + ], + "flag": 1, + "id": "TSI_DP20_SINKCTS_VMT_10240_4320", + "name": "Resolution supported by Sink DUT", + "type": 12 + }, + { + "configId": "0x011A0033", + "defaultValue": "0x00000000", + "description": "Resolution 15360x8640", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines 15360x8640 @ 30Hz CTA timing support."}, + {"mask": "0x00000002", "description": "Defines 15360x8640 @ 30Hz RBv1 timing support."}, + {"mask": "0x00000004", "description": "Defines 15360x8640 @ 30Hz RBv2 timing support."}, + {"mask": "0x00000010", "description": "Defines 15360x8640 @ 60Hz CTA timing support."}, + {"mask": "0x00000020", "description": "Defines 15360x8640 @ 60Hz RBv1 timing support."}, + {"mask": "0x00000040", "description": "Defines 15360x8640 @ 60Hz RBv2 timing support."}, + {"mask": "0x00000100", "description": "Defines 15360x8640 @ 120Hz CTA timing support."}, + {"mask": "0x00000200", "description": "Defines 15360x8640 @ 120Hz RBv1 timing support."}, + {"mask": "0x00000400", "description": "Defines 15360x8640 @ 120Hz RBv2 timing support."}, + {"mask": "0x00002000", "description": "Defines 15360x8640 @ 144Hz RBv1 timing support.."}, + {"mask": "0x00004000", "description": "Defines 15360x8640 @ 144Hz RBv2 timing support."}, + {"mask": "0x00008000", "description": "Defines 15360x8640 @ 144Hz OVT timing support."}, + {"mask": "0x00020000", "description": "Defines 15360x8640 @ 240Hz RBv1 timing support."}, + {"mask": "0x00040000", "description": "Defines 15360x8640 @ 240Hz RBv2 timing support."}, + {"mask": "0x00080000", "description": "Defines 15360x8640 @ 240Hz OVT timing support."} + ], + "flag": 1, + "id": "TSI_DP20_SINKCTS_VMT_15360_8640", + "name": "Resolution supported by Sink DUT", + "type": 12 + }, + { + "configId": "0x011A0034", + "defaultValue": "0x00000000", + "description": "Defines the Debug mode configuration.", + "bitList": + [ + {"mask": "0x00000001", "description": "Force video check enabled"} + ], + "flag": 3, + "id": "TSI_DP20_SINKCTS_DEBUG_CONF", + "name": "Debug mode configuration.", + "type": 12 + } + ], + "id": "0x18", + "name": "DP 2.1 LL CTS", + "old_names": [ + "DP 2.1 LL CTS", + "DP 2.0 Link Layer Sink DUT CTS", + "DP 2.1 Link Layer Sink DUT CTS" + ] +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x19]snk_epr_power_source.json b/UniTAP/dev/modules/dut_tests/cfg/[0x19]snk_epr_power_source.json new file mode 100644 index 0000000..5f7dd8f --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x19]snk_epr_power_source.json @@ -0,0 +1,18 @@ +{ + "descriptions": [ + { + "configId": "0x11D0000", + "defaultValue": "5000", + "description": "Defines test timeout in milliseconds, default value is 5000ms.", + "flag": 3, + "id": "TSI_USBC_EPR_POWER_SOURCE_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout in milliseconds.", + "reportFormat": "Test timeout = %2ms
", + "type": 4 + } + ], + "id": "0x19", + "name": "EPR Power Source Tests" +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x20]src_dut_hdmi_dsc_cts.json b/UniTAP/dev/modules/dut_tests/cfg/[0x20]src_dut_hdmi_dsc_cts.json new file mode 100644 index 0000000..d42b507 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x20]src_dut_hdmi_dsc_cts.json @@ -0,0 +1,21 @@ +{ + "descriptions": [ + { + "configId": "0x11C0000", + "defaultValue": "10000", + "description": "Defines a test timeout, in milliseconds. Default setting is 10000ms. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_HDMI_SRCCTS_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout, in milliseconds", + "type": 4 + } + ], + "id": "0x20", + "name": "HDMI RX DSC LL CTS", + "old_names": [ + "HDMI LL CTS", + "HDMI 2.1 Source DUT DSC Tests" + ] +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x21]snk_dut_hdmi_dsc_cts.json b/UniTAP/dev/modules/dut_tests/cfg/[0x21]snk_dut_hdmi_dsc_cts.json new file mode 100644 index 0000000..acf173c --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x21]snk_dut_hdmi_dsc_cts.json @@ -0,0 +1,210 @@ +{ + "descriptions": [ + { + "configId": "0x11D0000", + "defaultValue": "1", + "description": "Defines the Test mode. Default value is 0. Available variants: 0 - Test with CDF and EDID, 1 - Test only with EDID, 2 - Test with VIC selection", + "enumerationVariants": "0 # Force VIC output\n1 # CTS test", + "flag": 3, + "id": "TSI_HDMI_SNKCTS_TEST_MODE", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test Mode", + "type": 4 + }, + { + "configId": "0x11D0001", + "defaultValue": "10000", + "description": "Defines a test timeout, in milliseconds. Default setting is 10000ms. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_HDMI_SNKCTS_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout, in milliseconds", + "type": 4 + }, + { + "configId": "0x11D0002", + "defaultValue": "6", + "description": "Defines a MAX FRL Rate. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "enumerationVariants": "0 # None\n1 # 3G 3 Lane\n2 # 6G 3 Lane\n3 # 6G 4 Lane\n4 # 8G 4 Lane\n5 # 10G 4 Lane\n6 # 12G 4 Lane", + "flag": 3, + "id": "TSI_HDMI_SNKCTS_MAX_FRL_RATE", + "maxValue": 2147483647, + "minValue": 0, + "name": "Maximum FRL rate", + "type": 4 + }, + { + "configId": "0x11D0003", + "defaultValue": "600", + "description": "Defines a Sink Max TMDS Clock in MHz. Default setting is 600. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_HDMI_SNKCTS_MAX_TMDS_CLOCK", + "maxValue": 2147483647, + "minValue": 0, + "name": "Max TMDS Clock in MHz", + "type": 4 + }, + { + "configId": "0x11D0004", + "defaultValue": "0x70", + "description": "Defines a DUT capabilies. Default setting is 0x70. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "bitList": + [ + {"mask": "0x00000001", "description": "Is Sink DC 30bit. Default value: 0."}, + {"mask": "0x00000002", "description": "Is Sink DC 36bit. Default value: 0."}, + {"mask": "0x00000004", "description": "Is Sink DC 48bit. Default value: 0."}, + {"mask": "0x00000008", "description": "Is Sink DC Y444. Default value: 0."}, + {"mask": "0x00000010", "description": "Is Sink Supports DSC. Default value: 1."}, + {"mask": "0x00000020", "description": "Is Sink DSC 10bpc. Default value: 1."}, + {"mask": "0x00000040", "description": "Is Sink DSC 12bpc. Default value: 1."}, + {"mask": "0x00000080", "description": "Is Sink DSC Native 420. Default value: 0."}, + {"mask": "0x00000100", "description": "Is Sink DSC 16bpc. Default value: 0."}, + {"mask": "0x00000200", "description": "Is Sink Support DSC all bpp. Default value: 0."} + ], + "flag": 1, + "id": "TSI_HDMI_SNKCTS_DUT_CAPS", + "name": "DUT Capability flags.", + "type": 12 + }, + { + "configId": "0x11D0005", + "defaultValue": "6", + "description": "Defines a DSC MAX FRL Rate. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "enumerationVariants": "0 # None\n1 # 3G 3 Lane\n2 # 6G 3 Lane\n3 # 6G 4 Lane\n4 # 8G 4 Lane\n5 # 10G 4 Lane\n6 # 12G 4 Lane", + "flag": 3, + "id": "TSI_HDMI_SNKCTS_DSC_MAX_FRL_RATE", + "maxValue": 2147483647, + "minValue": 0, + "name": "Maximum DSC FRL rate", + "type": 4 + }, + { + "configId": "0x11D0006", + "defaultValue": "0", + "description": "Defines a Sink HF EEODB Support. Default setting is 0. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_HDMI_SNKCTS_HF_EEODB_SUPPORT", + "maxValue": 2147483647, + "minValue": 0, + "name": "HF EEODB Support", + "type": 4 + }, + { + "configId": "0x11D0007", + "defaultValue": "0x8FF8C600", + "description": "Defines a support of DSC Video formats. Default setting is 0x8FF8C600. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "bitList": + [ + {"mask": "0x00000004", "description": "VIC 63 1920x1080 120Hz (16:9)"}, + {"mask": "0x00000008", "description": "VIC 64 1920x1080 100Hz (16:9)"}, + {"mask": "0x00000010", "description": "VIC 77 1920x1080 100Hz (64:27)"}, + {"mask": "0x00000020", "description": "VIC 78 1920x1080 120Hz (64:27)"}, + {"mask": "0x00000001", "description": "VIC 91 2560x1080 100Hz (64:27)"}, + {"mask": "0x00000002", "description": "VIC 92 2560x1080 120Hz (64:27)"}, + {"mask": "0x00000040", "description": "VIC 93 3840x2160 24Hz (16:9)"}, + {"mask": "0x00000080", "description": "VIC 94 3840x2160 25Hz (16:9)"}, + {"mask": "0x00000100", "description": "VIC 95 3840x2160 30Hz (16:9)"}, + {"mask": "0x00000200", "description": "VIC 96 3840x2160 50Hz (16:9)"}, + {"mask": "0x00000400", "description": "VIC 97 3840x2160 60Hz (16:9)"}, + {"mask": "0x00000800", "description": "VIC 98 4096x2160 24Hz (256:135)"}, + {"mask": "0x00001000", "description": "VIC 99 4096x2160 25Hz (256:135)"}, + {"mask": "0x00002000", "description": "VIC 100 4096x2160 30Hz (256:135)"}, + {"mask": "0x00004000", "description": "VIC 101 4096x2160 50Hz (256:135)"}, + {"mask": "0x00008000", "description": "VIC 102 4096x2160 60Hz (256:135)"}, + {"mask": "0x00010000", "description": "VIC 103 3840x2160 24Hz (64:27)"}, + {"mask": "0x00020000", "description": "VIC 104 3840x2160 25Hz (64:27)"}, + {"mask": "0x00040000", "description": "VIC 105 3840x2160 30Hz (64:27)"}, + {"mask": "0x00080000", "description": "VIC 106 3840x2160 50Hz (64:27)"}, + {"mask": "0x00100000", "description": "VIC 107 3840x2160 60Hz (64:27)"}, + {"mask": "0x00200000", "description": "VIC 114 3840x2160 48Hz (16:9)"}, + {"mask": "0x00400000", "description": "VIC 115 4096x2160 48Hz (256:135)"}, + {"mask": "0x00800000", "description": "VIC 116 3840x2160 48Hz (64:27)"}, + {"mask": "0x01000000", "description": "VIC 117 3840x2160 100Hz (64:27)"}, + {"mask": "0x02000000", "description": "VIC 118 3840x2160 120Hz (64:27)"}, + {"mask": "0x04000000", "description": "VIC 119 3840x2160 100Hz (64:27)"}, + {"mask": "0x08000000", "description": "VIC 120 3840x2160 120Hz (64:27)"}, + {"mask": "0x10000000", "description": "VIC 121 5120x2160 24Hz (64:27)"}, + {"mask": "0x20000000", "description": "VIC 122 5120x2160 25Hz (64:27)"}, + {"mask": "0x40000000", "description": "VIC 123 5120x2160 30Hz (64:27)"}, + {"mask": "0x80000000", "description": "VIC 124 5120x2160 48Hz (64:27)"} + ], + "flag": 1, + "id": "TSI_HDMI_SNKCTS_DSC_VIDEO_FORMAT", + "name": "DSC Video formats 1.", + "type": 12 + }, + { + "configId": "0x11D0008", + "defaultValue": "0x3000000F", + "description": "Defines a support of DSC Video formats 2. Default setting is 0x3000000F. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "bitList": + [ + {"mask": "0x00000001", "description": "VIC 125 5120x2160 50Hz (64:27)"}, + {"mask": "0x00000002", "description": "VIC 126 5120x2160 60Hz (64:27)"}, + {"mask": "0x00000004", "description": "VIC 127 5120x2160 100Hz (64:27)"}, + {"mask": "0x00000008", "description": "VIC 193 5120x2160 120Hz (64:27)"}, + {"mask": "0x00000010", "description": "VIC 194 7680x4320 24Hz (16:9)"}, + {"mask": "0x00000020", "description": "VIC 195 7680x4320 25Hz (16:9)"}, + {"mask": "0x00000040", "description": "VIC 196 7680x4320 30Hz (16:9)"}, + {"mask": "0x00000080", "description": "VIC 197 7680x4320 48Hz (16:9)"}, + {"mask": "0x00000100", "description": "VIC 198 7680x4320 50Hz (16:9)"}, + {"mask": "0x00000200", "description": "VIC 199 7680x4320 60Hz (16:9)"}, + {"mask": "0x00000400", "description": "VIC 200 7680x4320 100Hz (16:9)"}, + {"mask": "0x00000800", "description": "VIC 201 7680x4320 120Hz (16:9)"}, + {"mask": "0x00001000", "description": "VIC 202 7680x4320 24Hz (64:27)"}, + {"mask": "0x00002000", "description": "VIC 203 7680x4320 25Hz (64:27)"}, + {"mask": "0x00004000", "description": "VIC 204 7680x4320 30Hz (64:27)"}, + {"mask": "0x00008000", "description": "VIC 205 7680x4320 48Hz (64:27)"}, + {"mask": "0x00010000", "description": "VIC 206 7680x4320 50Hz (64:27)"}, + {"mask": "0x00020000", "description": "VIC 207 7680x4320 60Hz (64:27)"}, + {"mask": "0x00040000", "description": "VIC 208 7680x4320 100Hz (64:27)"}, + {"mask": "0x00080000", "description": "VIC 209 7680x4320 120Hz (64:27)"}, + {"mask": "0x00100000", "description": "VIC 210 10240x4320 24Hz (64:27)"}, + {"mask": "0x00200000", "description": "VIC 211 10240x4320 25Hz (64:27)"}, + {"mask": "0x00400000", "description": "VIC 212 10240x4320 30Hz (64:27)"}, + {"mask": "0x00800000", "description": "VIC 213 10240x4320 48Hz (64:27)"}, + {"mask": "0x01000000", "description": "VIC 214 10240x4320 50Hz (64:27)"}, + {"mask": "0x02000000", "description": "VIC 215 10240x4320 60Hz (64:27)"}, + {"mask": "0x04000000", "description": "VIC 216 10240x4320 100Hz (64:27)"}, + {"mask": "0x08000000", "description": "VIC 217 10240x4320 120Hz (64:27)"}, + {"mask": "0x10000000", "description": "VIC 218 4096x2160 100Hz (256:135)"}, + {"mask": "0x20000000", "description": "VIC 219 4096x2160 120Hz (256:135)"} + ], + "flag": 1, + "id": "TSI_HDMI_SNKCTS_DSC_VIDEO_FORMAT_2", + "name": "DSC Video formats 2.", + "type": 12 + }, + { + "configId": "0x11D0009", + "defaultValue": "4", + "description": "Defines a maxumim DSC slice number. Default setting is 4. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "enumerationVariants": "0 # 0 (DSC 1.2a is not supported)\n1 # 1 (up to 1 slice and 340MHz per slice)\n2 # 2 (up to 2 slices and 340MHz per slice)\n3 # 3 (up to 4 slices and 340MHz per slice)\n4 # 4 (up to 8 slices and 340MHz per slice)\n5 # 5 (up to 8 slices and 400MHz per slice)\n6 # 6 (up to 12 slices and 400MHz per slice)\n7 # 7 (up to 16 slices and 600MHz per slice)", + "flag": 3, + "id": "TSI_HDMI_SNKCTS_DSC_MAX_SLICES", + "maxValue": 2147483647, + "minValue": 0, + "name": "DSC Max Slices", + "type": 4 + }, + { + "configId": "0x11D000A", + "defaultValue": "11", + "description": "Defines a total DSC chunk size in KBytes. Default setting is 11. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_HDMI_SNKCTS_DSC_TOTAL_CHUNK_BYTES", + "maxValue": 2147483647, + "minValue": 0, + "name": "DSC Max Slices", + "type": 4 + } + ], + "id": "0x21", + "name": "HDMI TX DSC LL CTS", + "old_names": [ + "HDMI LL CTS", + "HDMI 2.1 Sink DUT DSC Tests" + ] +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x22]snk_dut_hdmi_continuity.json b/UniTAP/dev/modules/dut_tests/cfg/[0x22]snk_dut_hdmi_continuity.json new file mode 100644 index 0000000..8af9c63 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x22]snk_dut_hdmi_continuity.json @@ -0,0 +1,79 @@ +{ + "descriptions": [ + { + "configId": "0x011E0000", + "defaultValue": "60", + "description": "Total test time in seconds. Default value is 60s.", + "flag": 3, + "id": "TSI_HDMI_SNKCTS_TEST_TIME", + "maxValue": 2147483647, + "minValue": 0, + "name": "Total test time, in seconds", + "type": 4 + }, + { + "configId": "0x011E0001", + "defaultValue": "1", + "description": "Check status period in seconds. Default value is 1s.", + "flag": 3, + "id": "TSI_HDMI_SNKCTS_STATUS_PERIOD", + "maxValue": 2147483647, + "minValue": 0, + "name": "Period, in seconds", + "type": 4 + }, + { + "configId": "0x011E0002", + "defaultValue": "0", + "description": "Stop testing when status fail", + "flag": 3, + "enumerationVariants": "0 # False\n1 # True", + "id": "TSI_HDMI_SNKCTS_STOP_TESTING", + "name": "Stop testing when status fail", + "type": 4 + }, + { + "configId": "0x011E0003", + "defaultValue": "1", + "description": "Enable to check SCDC version", + "flag": 3, + "enumerationVariants": "0 # False\n1 # True", + "id": "TSI_HDMI_SNKCTS_SCDC_VERSION", + "name": "Enable to check SCDC version", + "type": 4 + }, + { + "configId": "0x011E0004", + "defaultValue": "1", + "description": "Enable to check SCDC status flag", + "flag": 3, + "enumerationVariants": "0 # False\n1 # True", + "id": "TSI_HDMI_SNKCTS_SCDC_STATUS", + "name": "Enable to check SCDC status flag ", + "type": 4 + }, + { + "configId": "0x011E0005", + "defaultValue": "1", + "description": "Enable to check SCDC error counters", + "flag": 3, + "enumerationVariants": "0 # False\n1 # True", + "id": "TSI_HDMI_SNKCTS_SCDC_ERROR", + "name": "Enable to check SCDC error counters", + "type": 4 + }, + { + "configId": "0x011E0006", + "defaultValue": "10", + "description": "SDCD error counter fail threshold. Default value is 10.", + "flag": 3, + "id": "TSI_HDMI_SNKCTS_SCDC_ERROR_COUNTER", + "maxValue": 2147483647, + "minValue": 0, + "name": "SDCD error counter fail threshold", + "type": 4 + } + ], + "id": "0x22", + "name": "Connection Test" +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x23]snk_dut_hdmi_gaming.json b/UniTAP/dev/modules/dut_tests/cfg/[0x23]snk_dut_hdmi_gaming.json new file mode 100644 index 0000000..e7ce346 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x23]snk_dut_hdmi_gaming.json @@ -0,0 +1,172 @@ +{ + "descriptions": [ + { + "configId": "0x2100", + "defaultValue": "0", + "description": "Does the Sink support Fast Vactive (FVA), also known as Quick Frame Transport (QFT)?", + "flag": 3, + "id": "TSI_HDMI_GAMING_FVA", + "maxValue": 1, + "minValue": 0, + "name": "Sink_Supports_FVA", + "type": 3, + "enumerationVariants": "0 # NO\n1 # YES" + }, + { + "configId": "0x2101", + "defaultValue": "1", + "description": "Does the Sink support Variable Refresh Rate (VRR)", + "flag": 3, + "id": "TSI_HDMI_GAMING_VRR", + "maxValue": 1, + "minValue": 0, + "name": "Sink_Supports_VRR", + "type": 3, + "enumerationVariants": "0 # NO\n1 # YES" + }, + { + "configId": "0x2102", + "defaultValue": "0", + "description": "Does the Sink support Negative Mvrr values?", + "flag": 3, + "id": "TSI_HDMI_GAMING_NMVRR", + "maxValue": 1, + "minValue": 0, + "name": "Sink_Supports_Negative_MVRR", + "type": 3, + "enumerationVariants": "0 # NO\n1 # YES" + }, + { + "configId": "0x2103", + "defaultValue": "40", + "description": "What is the Sink’s supported VRR Range? Minimum value.\n", + "flag": 3, + "id": "TSI_HDMI_GAMING_RANGE_MIN", + "maxValue": 480, + "minValue": 20, + "name": "Sink_Supported_VRR_Range VRRMIN", + "type": 4 + }, + { + "configId": "0x2104", + "defaultValue": "0", + "description": "What is the Sink’s supported VRR Range? Maximum value or BRR\n", + "flag": 3, + "id": "TSI_HDMI_GAMING_RANGE_MAX", + "maxValue": 480, + "minValue": 20, + "name": "Sink_Supported_VRR_Range VRRMAX or 0 for BRR", + "type": 4 + }, + { + "configId": "0x2105", + "defaultValue": "0", + "description": "Does the Sink implement Auto Low-Latency Mode (ALLM)?", + "flag": 3, + "id": "TSI_HDMI_GAMING_ALLM", + "maxValue": 1, + "minValue": 0, + "name": "Sink_Supports_ALLM", + "type": 3, + "enumerationVariants": "0 # NO\n1 # YES" + }, + { + "configId": "0x2106", + "defaultValue": "0", + "description": "Which display processing mode is the preferred mode with normal latency?", + "flag": 3, + "id": "TSI_HDMI_GAMING_NLM", + "maxValue": 2, + "minValue": 0, + "name": "Sink_Preferred_NLM", + "type": 4, + "enumerationVariants": "0 # Cinema\n1 # Vivid\n2 # Normal" + }, + { + "configId": "0x2107", + "defaultValue": "0", + "description": "Which display processing mode is the preferred mode to minimize latency?", + "flag": 3, + "id": "TSI_HDMI_GAMING_LLM", + "maxValue": 1, + "minValue": 0, + "name": "Sink_Preferred_LLM", + "type": 4, + "enumerationVariants": "0 # Game\n1 # PC" + }, + { + "configId": "0x2108", + "defaultValue": "1", + "description": "Does the Sink DUT support QMS-VRR?", + "flag": 3, + "id": "TSI_HDMI_GAMING_QMS", + "maxValue": 1, + "minValue": 0, + "name": "Sink_QMS_VRR_Supported", + "type": 3, + "enumerationVariants": "0 # NO\n1 # YES" + }, + { + "configId": "0x2109", + "defaultValue": "97", + "description": "What is the highest resolution (i.e., width x height) and base refresh rate (BRR) for which all TFRs up to Sink_QMS_Max_TFR_TMDS are supported by the Sink using TMDS?", + "flag": 3, + "id": "TSI_HDMI_GAMING_VIDEO_TMDS", + "maxValue": 255, + "minValue": 16, + "name": "Sink_QMS_Video_Format_TMDS", + "type": 4, + "enumerationVariants": "78 # VIC=78; 1920x1080@120\n97 # VIC=97; 3840x2160@60" + }, + { + "configId": "0x210A", + "defaultValue": "118", + "description": "What is the highest resolution (i.e., width x height) and base refresh rate (BRR) for which all TFRs up to Sink_QMS_Max_TFR_FRL are supported by the Sink using FRL?", + "flag": 3, + "id": "TSI_HDMI_GAMING_VIDEO_FRL", + "maxValue": 255, + "minValue": 16, + "name": "Sink_QMS_Video_Format_FRL", + "type": 4, + "enumerationVariants": "0 # N/A\n78 # VIC=78; 1920x1080@120\n97 # VIC=97; 3840x2160@60\n118 # VIC=118; 3840x2160@120" + }, + { + "configId": "0x210B", + "defaultValue": "1", + "description": "If the Sink DUT supports QMS-VRR, what is the minimum Next_TFR value supported? (Refer to the Next_TFR field definition in HDMI 2.1a Table 10-37.)", + "flag": 3, + "id": "TSI_HDMI_GAMING_MIN_TFR", + "maxValue": 13, + "minValue": 0, + "name": "Sink_QMS_Min_TFR", + "type": 4, + "enumerationVariants": "0 # N/A\n1 # TFR=1; 24/1.001\n2 # TFR=2; 24\n3 # TFR=3; 25\n4 # TFR=4; 30/1.001\n5 # TFR=5; 30\n6 # TFR=6; 48/1.001\n7 # TFR=7; 48\n8 # TFR=8; 50\n9 # TFR=9; 60/1.001\n10 # TFR=10; 60\n11 # TFR=11; 100\n12 # TFR=12; 120/1.001\n13 # TFR=13; 120" + }, + { + "configId": "0x210C", + "defaultValue": "10", + "description": "If the Sink DUT supports QMS-VRR, what is the maximum Next_TFR value supported using TMDS? (Refer to the Next_TFR field definition in HDMI 2.1a Table 10-37.)", + "flag": 3, + "id": "TSI_HDMI_GAMING_MAX_TFR_TMDS", + "maxValue": 13, + "minValue": 0, + "name": "Sink_QMS_Max_TFR_TMDS", + "type": 4, + "enumerationVariants": "0 # N/A\n1 # TFR=1; 24/1.001\n2 # TFR=2; 24\n3 # TFR=3; 25\n4 # TFR=4; 30/1.001\n5 # TFR=5; 30\n6 # TFR=6; 48/1.001\n7 # TFR=7; 48\n8 # TFR=8; 50\n9 # TFR=9; 60/1.001\n10 # TFR=10; 60\n11 # TFR=11; 100\n12 # TFR=12; 120/1.001\n13 # TFR=13; 120" + }, + { + "configId": "0x210D", + "defaultValue": "13", + "description": "If the Sink DUT supports QMS-VRR, what is the maximum Next_TFR value supported using FRL? (Refer to the Next_TFR field definition in HDMI 2.1a Table 10-37.)", + "flag": 3, + "id": "TSI_HDMI_GAMING_MAX_TFR_FRL", + "maxValue": 13, + "minValue": 0, + "name": "Sink_QMS_Max_TFR_FRL", + "type": 4, + "enumerationVariants": "0 # N/A\n1 # TFR=1; 24/1.001\n2 # TFR=2; 24\n3 # TFR=3; 25\n4 # TFR=4; 30/1.001\n5 # TFR=5; 30\n6 # TFR=6; 48/1.001\n7 # TFR=7; 48\n8 # TFR=8; 50\n9 # TFR=9; 60/1.001\n10 # TFR=10; 60\n11 # TFR=11; 100\n12 # TFR=12; 120/1.001\n13 # TFR=13; 120" + } + ], + "id": "0x23", + "name": "HDMI CTS MOI (VRR-QMS)" +} diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x24]snk_dut_hdmi_cable_check.json b/UniTAP/dev/modules/dut_tests/cfg/[0x24]snk_dut_hdmi_cable_check.json new file mode 100644 index 0000000..2d94a2c --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x24]snk_dut_hdmi_cable_check.json @@ -0,0 +1,96 @@ +{ + "descriptions": [ + { + "configId": "0x11F0000", + "defaultValue": "120", + "description": "Defines a test timeout, in seconds. Default setting is 120s. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_HDMI_SNKCTS_CABLE_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout, in seconds", + "type": 4 + }, + { + "configId": "0x11F0001", + "defaultValue": "2", + "description": "Defines a Cable test mode. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "enumerationVariants": "0 # High speed\n1 # Low speed\n2 # All", + "flag": 3, + "id": "TSI_HDMI_SNKCTS_CABLE_TEST_MODE", + "maxValue": 2147483647, + "minValue": 0, + "name": "Cable test mode", + "type": 4 + }, + { + "configId": "0x11F0002", + "defaultValue": "1", + "description": "Defines a MIN FRL Rate. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "enumerationVariants": "0 # None\n1 # 3G 3 Lane\n2 # 6G 3 Lane\n3 # 6G 4 Lane\n4 # 8G 4 Lane\n5 # 10G 4 Lane\n6 # 12G 4 Lane", + "flag": 3, + "id": "TSI_HDMI_SNKCTS_CABLE_MIN_FRL_RATE", + "maxValue": 2147483647, + "minValue": 0, + "name": "Minimum FRL rate", + "type": 4 + }, + { + "configId": "0x11F0003", + "defaultValue": "6", + "description": "Defines a MAX FRL Rate. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "enumerationVariants": "0 # None\n1 # 3G 3 Lane\n2 # 6G 3 Lane\n3 # 6G 4 Lane\n4 # 8G 4 Lane\n5 # 10G 4 Lane\n6 # 12G 4 Lane", + "flag": 3, + "id": "TSI_HDMI_SNKCTS_CABLE_MAX_FRL_RATE", + "maxValue": 2147483647, + "minValue": 0, + "name": "Maximum FRL rate", + "type": 4 + }, + { + "configId": "0x11F0004", + "defaultValue": "10", + "description": "Threshold of allowed errors per lane. Default setting is 10. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_HDMI_SNKCTS_CABLE_ERROR_PER_LANE", + "maxValue": 2147483647, + "minValue": 0, + "name": "Threshold of allowed errors per lane", + "type": 4 + }, + { + "configId": "0x11F0005", + "defaultValue": "5", + "description": "Defines a timeout for capturing errors, in seconds. Default setting is 5s. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_HDMI_SNKCTS_CABLE_CAPTURE_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Timeout for capturing errors, in seconds", + "type": 4 + }, + { + "configId": "0x11F0006", + "defaultValue": "0x1F", + "description": "Defines a Low Speed Line selection. Default setting is 0x1F. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "bitList": + [ + {"mask": "0x00000001", "description": "Enable HPD line test."}, + {"mask": "0x00000002", "description": "Enable I2C line test."}, + {"mask": "0x00000004", "description": "Enable CEC line test."}, + {"mask": "0x00000008", "description": "Enable 5V line test."}, + {"mask": "0x00000010", "description": "Enable utility line test."} + ], + "flag": 3, + "id": "TSI_HDMI_SNKCTS_CABLE_LOW_SPEED_LINE", + "name": "Low Speed Line Select", + "type": 12 + } + ], + "id": "0x24", + "name": "Cable Test", + "old_names": [ + "HDMI LL CTS", + "HDMI Sink Cable Tests" + ] +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x25]src_dut_hdmi_hdcp_23_1a.json b/UniTAP/dev/modules/dut_tests/cfg/[0x25]src_dut_hdmi_hdcp_23_1a.json new file mode 100644 index 0000000..6549ad5 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x25]src_dut_hdmi_hdcp_23_1a.json @@ -0,0 +1,20 @@ +{ + "descriptions": [ + { + "configId": "0x1200000", + "defaultValue": "30000", + "description": "Defines test timeout in milliseconds, default value is 30000ms.", + "flag": 3, + "id": "TSI_HDMI_HDCP_1A_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout, in milliseconds", + "type": 4 + } + ], + "id": "0x25", + "name": "HDMI HDCP 2.3 CTS 1A", + "old_names": [ + "HDMI HDCP 2.3 CTS 1A Test Set" + ] +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x26]src_dut_hdmi_hdcp_23_1b.json b/UniTAP/dev/modules/dut_tests/cfg/[0x26]src_dut_hdmi_hdcp_23_1b.json new file mode 100644 index 0000000..8b84646 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x26]src_dut_hdmi_hdcp_23_1b.json @@ -0,0 +1,20 @@ +{ + "descriptions": [ + { + "configId": "0x1210000", + "defaultValue": "30000", + "description": "Defines test timeout in milliseconds, default value is 30000ms.", + "flag": 3, + "id": "TSI_HDMI_HDCP_1B_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout, in milliseconds", + "type": 4 + } + ], + "id": "0x26", + "name": "HDMI HDCP 2.3 CTS 1B", + "old_names": [ + "HDMI HDCP 2.3 CTS 1B Test Set" + ] +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x27]snk_dut_hdmi_hdcp_23_2c.json b/UniTAP/dev/modules/dut_tests/cfg/[0x27]snk_dut_hdmi_hdcp_23_2c.json new file mode 100644 index 0000000..8a2acce --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x27]snk_dut_hdmi_hdcp_23_2c.json @@ -0,0 +1,42 @@ +{ + "descriptions": [ + { + "configId": "0x1220000", + "defaultValue": "30000", + "description": "Defines test timeout in milliseconds, default value is 30000ms.", + "flag": 3, + "id": "TSI_HDMI_HDCP_2C_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout, in milliseconds", + "type": 4 + }, + { + "configId": "0x1220001", + "defaultValue": "0", + "description": "Defines a flag to do or not link training before starting test, default value is 0.", + "enumerationVariants": "0 # NO\n1 # YES", + "flag": 3, + "id": "TSI_HDMI_HDCP_2C_LT_BEFORE_TEST", + "name": "Link training before starting test", + "type": 4 + }, + { + "configId": "0x1220002", + "defaultValue": "0", + "description": "Defines a Link rate for link training. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "enumerationVariants": "0 # TMDS mode\n1 # 3G 3 Lane\n2 # 6G 3 Lane\n3 # 6G 4 Lane\n4 # 8G 4 Lane\n5 # 10G 4 Lane\n6 # 12G 4 Lane", + "flag": 3, + "id": "TSI_HDMI_HDCP_2C_LINK_RATE", + "maxValue": 2147483647, + "minValue": 0, + "name": "Link rate for link training", + "type": 4 + } + ], + "id": "0x27", + "name": "HDMI HDCP 2.3 CTS 2C", + "old_names": [ + "HDMI HDCP 2.3 CTS 2C Test Set" + ] +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x28]src_dut_hdmi_hdcp_23_3a.json b/UniTAP/dev/modules/dut_tests/cfg/[0x28]src_dut_hdmi_hdcp_23_3a.json new file mode 100644 index 0000000..e95a6a3 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x28]src_dut_hdmi_hdcp_23_3a.json @@ -0,0 +1,20 @@ +{ + "descriptions": [ + { + "configId": "0x1230000", + "defaultValue": "30000", + "description": "Defines test timeout in milliseconds, default value is 30000ms.", + "flag": 3, + "id": "TSI_HDMI_HDCP_3A_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout, in milliseconds", + "type": 4 + } + ], + "id": "0x28", + "name": "HDMI HDCP 2.3 CTS 3A", + "old_names": [ + "HDMI HDCP 2.3 CTS 3A Test Set" + ] +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x29]src_dut_hdmi_hdcp_23_3b.json b/UniTAP/dev/modules/dut_tests/cfg/[0x29]src_dut_hdmi_hdcp_23_3b.json new file mode 100644 index 0000000..2af90e4 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x29]src_dut_hdmi_hdcp_23_3b.json @@ -0,0 +1,20 @@ +{ + "descriptions": [ + { + "configId": "0x1240000", + "defaultValue": "30000", + "description": "Defines test timeout in milliseconds, default value is 30000ms.", + "flag": 3, + "id": "TSI_HDMI_HDCP_3B_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout, in milliseconds", + "type": 4 + } + ], + "id": "0x29", + "name": "HDMI HDCP 2.3 CTS 3B", + "old_names": [ + "HDMI HDCP 2.3 CTS 3B Test Set" + ] +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x2A]snk_dut_hdmi_hdcp_23_3c.json b/UniTAP/dev/modules/dut_tests/cfg/[0x2A]snk_dut_hdmi_hdcp_23_3c.json new file mode 100644 index 0000000..2906d43 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x2A]snk_dut_hdmi_hdcp_23_3c.json @@ -0,0 +1,20 @@ +{ + "descriptions": [ + { + "configId": "0x1250000", + "defaultValue": "30000", + "description": "Defines test timeout in milliseconds, default value is 30000ms.", + "flag": 3, + "id": "TSI_HDMI_HDCP_3C_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout, in milliseconds", + "type": 4 + } + ], + "id": "0x2A", + "name": "HDMI HDCP 2.3 CTS 3C", + "old_names": [ + "HDMI HDCP 2.3 CTS 3C Test Set" + ] +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/hdr10/hdr10_edid.bin b/UniTAP/dev/modules/dut_tests/cfg/hdr10/hdr10_edid.bin new file mode 100644 index 0000000000000000000000000000000000000000..e0642432915cb0650b44a7307a31d443bc33c578 GIT binary patch literal 256 zcmZSh4+acAx>|({d<=|?@{G(4@n#iVD~wkMxGq)a_3+sHzM;^+@jxR(W5ZO2l?OI6 zTs*+>g2CWZK~%$rh%SZz*%Af@x$~zP8ooMMB`7Sg3IL19FfcIuWsvaZZ?|FKQUC*> z$RCDaN8jL3KX-@_<5ec+*bm`}0`e@vtRf1EO0jYA38`sGDePj*LQ(qs;q0R90paWn z=^+f9?Cj0@3=EGL{2#L(X3S&eVK89WVWYv&z{teRl5r#U2xCP9Gt*->UdG4#j1QhN gcDGDmkjh|K@WDdf!9c-^VP$|L1A`RM2`Jz?0FqWliU0rr literal 0 HcmV?d00001 diff --git a/UniTAP/dev/modules/dut_tests/cfg/hdr10/sstm_edid.bin b/UniTAP/dev/modules/dut_tests/cfg/hdr10/sstm_edid.bin new file mode 100644 index 0000000000000000000000000000000000000000..8b73c129e52f13fc951d2dd78eab751163840ff8 GIT binary patch literal 256 zcmZSh4+acAx>|({d<=|?@{G(4@n#iVD~wkMxGq)a_3+sHzM;^+@jxR(W5ZO2l?OI6 zTs*+>g2CWZK~%$rh%SZz*%Af@x$~zP8ooMMB`7Sg3IL19FfcIuWsvaZZ?|FKQUC*> z$RCDaN8jL3KX-@_<5ec+*bm`}0`e@vtRf1EO0jYA38`sGDePj*LQ(qs;q0R90paWn z=^+f9?Cj0@3=EGL{2#L(X3S&eVK89WVWYv&z{teRl5r#U2xCP9Gt*->UdG4#j1QhN gcDGC@m&#yR@WDdf!9c-^VP$|L1A`RM2`Jz?0HYE|0RR91 literal 0 HcmV?d00001 diff --git a/UniTAP/dev/modules/dut_tests/cfg/hdr10/sstm_edid1_0.bin b/UniTAP/dev/modules/dut_tests/cfg/hdr10/sstm_edid1_0.bin new file mode 100644 index 0000000000000000000000000000000000000000..e725c901a0e839739d63c05b7b9af6424a1e58a4 GIT binary patch literal 256 zcmZSh4+acAx>|({d<=|?@{G(4@n#iVD~wkMxGq)a_3+sHzM;^+@jxR(W5ZO2l?OI6 zTs*+>g2CWZK~%$rh%SZz*%Af@x$~zP8ooMMB`7Sg3IL19FfcIuWsvaZZ?|FKQUC*> z$RCDaN8jL3KX-@_<5ec+*bm`}0`e@vtRf1EO0jYA38`sGDePj*LQ(qs;q0R90paWn z=^+f9?Cj0@3=EGL{2#L(X3S&eVK89WVWYv&z{teRl5r#U2xCP9Gt*->UdG4#j1QhN gcDGCrl*(XO@WDdf!9c-^VP$|L1A`RM2`Jz?0F=5$n*aa+ literal 0 HcmV?d00001 diff --git a/UniTAP/dev/modules/dut_tests/cfg/hdr10/sstm_edid5_1.bin b/UniTAP/dev/modules/dut_tests/cfg/hdr10/sstm_edid5_1.bin new file mode 100644 index 0000000000000000000000000000000000000000..a99086590b8233003578669e9254927df9bdbc19 GIT binary patch literal 256 zcmZSh4+acAx>|({d<=|?@{G(4@n#iVD~wkMxGq)a_3+sHzM;^+@jxR(W5ZO2l?OI6 zTs*+>g2CWZK~%$rh%SZz*%Af@x$~zP8ooMMB`7Sg3IL19FfcIuWsvaZZ?|FKQUC*> z$RCDaN8jL3KX-@_<5ec+*bm`}0`e@vtRf1EO0jYA38`sGDePj*LQ(qs;q0R90paWn z=^+f9?Cj0@3=EGL{2#L(X3S&eVK89WVWYv&z{teRl5r#U2xCP9Gt*->UdG4#j1QhN gcDGCjmC9gP@WDdf!9c-^VP$|L1A`RM2`Jz?0G>)l-v9sr literal 0 HcmV?d00001 diff --git a/UniTAP/dev/modules/dut_tests/cfg/hdr10/sstm_edid7_2.bin b/UniTAP/dev/modules/dut_tests/cfg/hdr10/sstm_edid7_2.bin new file mode 100644 index 0000000000000000000000000000000000000000..27ed427aaf71c9393812d06c3a5ab30789c803cb GIT binary patch literal 256 zcmZSh4+acAx>|({d<=|?@{G(4@n#iVD~wkMxGq)a_3+sHzM;^+@jxR(W5ZO2l?OI6 zTs*+>g2CWZK~%$rh%SZz*%Af@x$~zP8ooMMB`7Sg3IL19FfcIuWsvaZZ?|FKQUC*> z$RCDaN8jL3KX-@_<5ec+*bm`}0`e@vtRf1EO0jYA38`sGDePj*LQ(qs;q0R90paWn z=^+f9?Cj0@3=EGL{2#L(X3S&eVK89WVWYv&z{teRl5r#U2xCP9Gt*->UdG4#j1QhN gcDGEZl*(XO@WDdf!9c-^VP$|L1A`RM2`Jz?0Haz*0{{R3 literal 0 HcmV?d00001 diff --git a/UniTAP/dev/modules/dut_tests/cfg/hdr10/sstm_edid9_3.bin b/UniTAP/dev/modules/dut_tests/cfg/hdr10/sstm_edid9_3.bin new file mode 100644 index 0000000000000000000000000000000000000000..ce3871d07eca74f18a9f65869050f2cfcd3cbc25 GIT binary patch literal 256 zcmZSh4+acAx>|({d<=|?@{G(4@n#iVD~wkMxGq)a_3+sHzM;^+@jxR(W5ZO2l?OI6 zTs*+>g2CWZK~%$rh%SZz*%Af@x$~zP8ooMMB`7Sg3IL19FfcIuWsvaZZ?|FKQUC*> z$RCDaN8jL3KX-@_<5ec+*bm`}0`e@vtRf1EO0jYA38`sGDePj*LQ(qs;q0R90paWn z=^+f9?Cj0@3=EGL{2#L(X3S&eVK89WVWYv&z{teRl5r#U2xCP9Gt*->UdG4#j1QhN gcDGEJE0w{p;Dd#{gMoq-!^!|h1_mjh6Hvf)0H|t5CjbBd literal 0 HcmV?d00001 diff --git a/UniTAP/dev/modules/dut_tests/cfg/hdr10/vsif1.bin b/UniTAP/dev/modules/dut_tests/cfg/hdr10/vsif1.bin new file mode 100644 index 0000000000000000000000000000000000000000..3cff59cf90ee8196edba47c7448a8a78c70d726b GIT binary patch literal 36 bcmZo{z?Q-Q1dPle5)}XdT>t|C literal 0 HcmV?d00001 diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/__init__.py b/UniTAP/dev/modules/dut_tests/dut_default_params/__init__.py new file mode 100644 index 0000000..6e3507e --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/__init__.py @@ -0,0 +1,56 @@ +from typing import TypeVar + +from .audio_test import AudioTestParam +from .crc_video_tests import CrcVideoTestParam, BrokenFrameExportFormat, CrcVideoTestBpp +from .cec_tests import CecFunctionalTestParam +from .dp1_4_sink_tests import Dp14SinkTestParam +from .dp2_1_sink_tests import Dp21SinkTestParam +from .dp1_4_source_tests import Dp14SourceDUTTestParam, PackedTimings1Lane, PackedTimings2Lane, \ + PackedTimings4Lane, EventIndication +from .dp2_1_source_tests import Dp21SourceDUTTestParam +from .dp_electrical_tests import DpElectricalTestParam +from .hdmi_electrical_tests import HdmiElectricalTestParam +from .usbc_electrical_tests import UsbcElectricalTestParam +from .hdcp_1a_tests import Hdcp1ATestParam +from .hdcp_1b_tests import Hdcp1BTestParam +from .hdcp_2c_tests import Hdcp2CTestParam +from .hdcp_3a_tests import Hdcp3ATestParam +from .hdcp_3b_tests import Hdcp3BTestParam +from .hdcp_3c_tests import Hdcp3CTestParam +from .hdr10_tests import Hdr10TestParam +from .pixel_video_test import VideoPixelTestParam +from .link_config_test import LinkConfigTestParam +from .vrr_tests import VrrSinkDUTTestParam, VrrSourceDUTTestParam +from .lttpr_tests import DpLttprTestParam +from .hdmi_sink_tests import HdmiSinkDUTTestParam, HdmiTestMode, HdmiFrlRate +from .hdmi_source_tests import HdmiSourceDUTTestParam +from .hdmi_sink_continuity_tests import HdmiSinkContinuityDUTTestParam +from .hdmi_sink_cable_check_tests import HdmiSinkCableCheckTestParam +from ..test_group_params_types import Param, get_param_list + +DUTTestParameters = TypeVar("DUTTestParameters", + AudioTestParam, + DpElectricalTestParam, + HdmiElectricalTestParam, + CecFunctionalTestParam, + CrcVideoTestParam, + LinkConfigTestParam, + UsbcElectricalTestParam, + Hdcp1ATestParam, + Hdcp1BTestParam, + Hdcp2CTestParam, + Hdcp3ATestParam, + Hdcp3BTestParam, + Hdcp3CTestParam, + VrrSinkDUTTestParam, + VrrSourceDUTTestParam, + Dp14SourceDUTTestParam, + Dp21SourceDUTTestParam, + Dp14SinkTestParam, + Dp21SinkTestParam, + VideoPixelTestParam, + DpLttprTestParam, + Hdr10TestParam, + HdmiSinkDUTTestParam, + HdmiSinkContinuityDUTTestParam, + HdmiSinkCableCheckTestParam) diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/audio_test.py b/UniTAP/dev/modules/dut_tests/dut_default_params/audio_test.py new file mode 100644 index 0000000..f46e972 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/audio_test.py @@ -0,0 +1,146 @@ +import os.path +from UniTAP.dev.modules.dut_tests.test_group_params_types import Param + + +class AudioTestParam: + """ + Class `AudioTestParam` describes requirement parameters for audio test: + - Set and get `sample_rate`. Describes expected sampling rate of audio signal. + - Set and get `audio_frequency`. Describes expected audible (sine) frequency as Hz. + - Set and get `frequency_tolerance`. Describes allowed deviation from expected frequency as Hz. + - Set and get `audio_glitches_allowed`. Describes number of audio glitches allowed per test. + - Set and get `save_conditions`. Describes tested audio save conditions. + - Set and get `storage_folder`. Describes location where the captured audio is to be saved. + """ + __SAVE_CONDITION = {"None": 0, + 'Failed': 1, + 'All': 2} + + def __init__(self, json_obj): + self.__sample_rate = Param(json_obj["TSI_EXPECTED_SAMPLE_RATE"]) + self.__audio_frequency = Param(json_obj["TSI_EXPECTED_AUDIO_FREQUENCY"]) + self.__frequency_tolerance = Param(json_obj["TSI_AUDIO_FREQUENCY_TOLERANCE"]) + self.__glitch_detect_threshold = Param(json_obj["TSI_AUDIO_GLITCH_DETECT_TRESHOLD"]) + self.__audio_glitches_allowed = Param(json_obj["TSI_AUDIO_GLITCHES_ALLOWED"]) + self.__save_conditions = Param(json_obj["TSI_AUDIO_TEST_SAVE_CONDITIONS"]) + self.__storage_folder = Param(json_obj["TSI_AUDIO_TEST_STORAGE_FOLDER"]) + + @property + def sample_rate(self) -> int: + """ + Set and get sampling rate of audio signal. + + Returns: + object of int type + """ + return self.__sample_rate.default_value + + @sample_rate.setter + def sample_rate(self, sample_rate: int): + if not(self.__sample_rate.min_value < sample_rate < self.__sample_rate.max_value): + raise ValueError(f"Sample rate cannot be less than {self.__sample_rate.min_value} and more than " + f"{self.__sample_rate.max_value}.") + self.__sample_rate.default_value = sample_rate + + @property + def audio_frequency(self) -> int: + """ + Set and get audible (sine) frequency as Hz. + + Returns: + object of int type + """ + return self.__audio_frequency.default_value + + @audio_frequency.setter + def audio_frequency(self, audio_frequency: int): + if not(self.__audio_frequency.min_value < audio_frequency < self.__audio_frequency.max_value): + raise ValueError(f"Audio frequency cannot be less than {self.__audio_frequency.min_value} and more than " + f"{self.__audio_frequency.max_value}.") + self.__audio_frequency.default_value = audio_frequency + + @property + def frequency_tolerance(self) -> int: + """ + Set and get allowed deviation from expected frequency as Hz. + + Returns: + object of int type + """ + return self.__frequency_tolerance.default_value + + @frequency_tolerance.setter + def frequency_tolerance(self, frequency_tolerance: int): + if not(self.__frequency_tolerance.min_value < frequency_tolerance < self.__frequency_tolerance.max_value): + raise ValueError(f"Frequency tolerance cannot be less than {self.__frequency_tolerance.min_value} " + f"and more than {self.__frequency_tolerance.max_value}.") + self.__frequency_tolerance.default_value = frequency_tolerance + + @property + def audio_glitches_allowed(self) -> int: + """ + Set and get number of audio glitches allowed per test. + + Returns: + object of int type + """ + return self.__audio_glitches_allowed.default_value + + @audio_glitches_allowed.setter + def audio_glitches_allowed(self, audio_glitches_allowed: int): + if not(self.__audio_glitches_allowed.min_value < audio_glitches_allowed < self.__audio_glitches_allowed.max_value): + raise ValueError(f"Audio glitches allowed cannot be less than {self.__audio_glitches_allowed.min_value} " + f"and more than {self.__audio_glitches_allowed.max_value}.") + self.__audio_glitches_allowed.default_value = audio_glitches_allowed + + @property + def glitch_detect_threshold(self) -> int: + """ + Set and get number of audio glitches allowed per test. + + Returns: + object of int type + """ + return self.__glitch_detect_threshold.default_value + + @glitch_detect_threshold.setter + def glitch_detect_threshold(self, glitch_detect_threshold: int): + if not ( + self.__glitch_detect_threshold.min_value < glitch_detect_threshold < self.__glitch_detect_threshold.max_value): + raise ValueError(f"Glitch detect threshold cannot be less than {self.__glitch_detect_threshold.min_value} " + f"and more than {self.__glitch_detect_threshold.max_value}.") + self.__glitch_detect_threshold.default_value = glitch_detect_threshold + + @property + def save_conditions(self) -> str: + """ + Set and get tested audio save conditions. + + Returns: + object of str type + """ + return self.__save_conditions.default_value + + @save_conditions.setter + def save_conditions(self, save_conditions: str = 'None'): + if self.__SAVE_CONDITION.get(save_conditions) is None: + available_variants = '\n'.join(self.__SAVE_CONDITION.keys()) + raise ValueError(f"Incorrect input parameter {save_conditions}.\n" + f"Available variants: {available_variants}") + self.__save_conditions.default_value = self.__SAVE_CONDITION.get(save_conditions) + + @property + def storage_folder(self) -> str: + """ + Set and get location where the captured audio is to be saved. + + Returns: + object of str type + """ + return self.__storage_folder.default_value + + @storage_folder.setter + def storage_folder(self, storage_folder: str): + if self.__save_conditions.default_value != 0 and not os.path.exists(storage_folder): + raise ValueError('Incorrect input path. Path is not exist.') + self.__storage_folder.default_value = storage_folder diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/cec_tests.py b/UniTAP/dev/modules/dut_tests/dut_default_params/cec_tests.py new file mode 100644 index 0000000..71c32fa --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/cec_tests.py @@ -0,0 +1,43 @@ +from UniTAP.dev.modules.dut_tests.test_group_params_types import Param + + +class CecFunctionalTestParam: + """ + Class `CecFunctionalTestParam` describes requirement parameters for CEC tests: + - Set and get `timeout`. Describes test timeout, in milliseconds. + - Set and get `physical_address`. Describes Local CEC physical address. + """ + def __init__(self, json_obj): + self.__timeout = Param(json_obj["TSI_HDMI_RX_CEC_TIMEOUT"]) + self.__physical_address = Param(json_obj["TSI_HDMI_RX_CEC_LOCAL_PHY_ADDR"]) + + @property + def timeout(self) -> int: + """ + Set and get test timeout, in milliseconds. + + Returns: + object of int type + """ + return self.__timeout.default_value + + @timeout.setter + def timeout(self, timeout: int): + if not (self.__timeout.min_value < timeout < self.__timeout.max_value): + raise ValueError(f"Timeout cannot be less than {self.__timeout.min_value} and more than " + f"{self.__timeout.max_value}.") + self.__timeout.default_value = timeout + + @property + def physical_address(self) -> int: + """ + Set and get Local CEC physical address. + + Returns: + object of int type + """ + return self.__physical_address.default_value + + @physical_address.setter + def physical_address(self, physical_address: int): + self.__physical_address.default_value = physical_address diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/crc_video_tests.py b/UniTAP/dev/modules/dut_tests/dut_default_params/crc_video_tests.py new file mode 100644 index 0000000..efda7a9 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/crc_video_tests.py @@ -0,0 +1,311 @@ +from enum import IntEnum +from UniTAP.dev.modules.dut_tests.test_group_params_types import Param + + +class CrcVideoTestBpp(IntEnum): + BPP12 = 12 + BPP15 = 15 + BPP16 = 16 + BPP18 = 18 + BPP20 = 20 + BPP21 = 21 + BPP24 = 24 + BPP30 = 30 + BPP32 = 32 + BPP36 = 36 + BPP48 = 48 + + +class BrokenFrameExportFormat(IntEnum): + BIN = 0 + PPM = 1 + BMP = 2 + + +class CrcVideoTestParam: + """ + Class `CrcVideoTestParam` describes requirement parameters for CRC tests: + - Set and get `timeout`. Describes test timeout, in milliseconds. + - Set and get `number_frames_to_test`. Describes total number of frames to be tested. + - Set and get `number_reference_frames`. Describes number of reference frames. + - Set and get `number_frames_mismatch`. Describes number of bad frames allowed in single CRC tests. + - Set and get `reference_width`. Describes expected video width, in pixels. + - Set and get `reference_height`. Describes expected video weight, in pixels. + - Set and get `reference_color_depth`. Describes expected color depth, as bits per pixel `CrcVideoTestBpp`. + - Set and get `required_frame_rate`. Describes expected frame rate, in millihertz (mHz). + - Set and get `frame_rate_tolerance`. Describes frame rate tolerance, in millihertz (mHz). + - Set and get `reference_crc_values`. Describes CRC reference values. Each CRC set consists of 3 16-bit words. + - Set and get `motion_test_iteration`. Describes the number of iterations the defined CRC sequence must + be found in order to pass the test. + - Set and get `data_transfer_timeout`. Describes data transfer timeout in milliseconds. + - Set and get `failed_frames_folder`. Describes location where the failed frames are to be saved. + - Set and get `max_export_failed`. Describes the number of failed frames to be exported from the video test. + """ + def __init__(self, json_obj): + self.__timeout = Param(json_obj["TSI_CRC_TIMEOUT"]) + self.__number_frames_to_test = Param(json_obj["TSI_CRC_FRAMES_TO_TEST"]) + self.__number_reference_frames = Param(json_obj["TSI_CRC_REF_FRAME_COUNT"]) + self.__number_frames_mismatch = Param(json_obj["TSI_CRC_LIM_FRAME_MISMATCHES"]) + self.__reference_width = Param(json_obj["TSI_CRC_REF_WIDTH"]) + self.__reference_height = Param(json_obj["TSI_CRC_REF_HEIGHT"]) + self.__reference_color_depth = Param(json_obj["TSI_CRC_REF_COLORDEPTH"]) # CrcVideoTestBpp + self.__required_frame_rate = Param(json_obj["TSI_CRC_REQUIRED_FRAME_RATE"]) + self.__frame_rate_tolerance = Param(json_obj["TSI_CRC_FRAME_RATE_TOLERANCE"]) + self.__reference_crc_values = Param(json_obj["TSI_CRC_REFERENCE_CRC_VALUES"]) + self.__motion_test_iteration = Param(json_obj["TSI_CRC_MOTION_TEST_ITERATIONS"]) + self.__color_format = Param(json_obj["TSI_CRC_COLOR_FORMAT"]) # Do not provide. Must be 0 + self.__data_transfer_timeout = Param(json_obj["TSI_CRC_DATA_TRANSFER_TIMEOUT"]) + self.__failed_frames_folder = Param(json_obj["TSI_CRC_FAILED_FRAME_TARGET_FOLDER"]) + self.__max_export_failed = Param(json_obj["TSI_CRC_MAX_EXPORT_FAILED"]) + self.__export_format = Param(json_obj["TSI_CRC_EXPORT_FORMAT"]) + + @property + def timeout(self) -> int: + """ + Set and get test timeout, in milliseconds. + + Returns: + object of int type + """ + return self.__timeout.default_value + + @timeout.setter + def timeout(self, timeout: int): + if not(self.__timeout.min_value < timeout < self.__timeout.max_value): + raise ValueError(f"Timeout cannot be less than {self.__timeout.min_value} and more than " + f"{self.__timeout.max_value}.") + self.__timeout.default_value = timeout + + @property + def number_frames_to_test(self) -> int: + """ + Set and get total number of frames to be tested. + + Returns: + object of int type + """ + return self.__number_frames_to_test.default_value + + @number_frames_to_test.setter + def number_frames_to_test(self, number_frames_to_test: int): + if not(self.__number_frames_to_test.min_value < number_frames_to_test < self.__number_frames_to_test.max_value): + raise ValueError(f"Number frames to test cannot be less than {self.__number_frames_to_test.min_value} " + f"and more than {self.__number_frames_to_test.max_value}.") + self.__number_frames_to_test.default_value = number_frames_to_test + + @property + def number_reference_frames(self) -> int: + """ + Set and get number of reference frames. + + Returns: + object of int type + """ + return self.__number_reference_frames.default_value + + @number_reference_frames.setter + def number_reference_frames(self, number_reference_frames: int): + if not(self.__number_reference_frames.min_value < number_reference_frames < + self.__number_reference_frames.max_value): + raise ValueError(f"Number reference frames cannot be less than {self.__number_reference_frames.min_value} " + f"and more than {self.__number_reference_frames.max_value}.") + self.__number_reference_frames.default_value = number_reference_frames + + @property + def number_frames_mismatch(self) -> int: + """ + Set and get number of bad frames allowed in single CRC tests. + + Returns: + object of int type + """ + return self.__number_frames_mismatch.default_value + + @number_frames_mismatch.setter + def number_frames_mismatch(self, number_frames_mismatch: int): + if not(self.__number_frames_mismatch.min_value <= number_frames_mismatch < + self.__number_frames_mismatch.max_value): + raise ValueError(f"Number frames mismatch cannot be less than {self.__number_frames_mismatch.min_value} " + f"and more than {self.__number_frames_mismatch.max_value}.") + self.__number_frames_mismatch.default_value = number_frames_mismatch + + @property + def reference_width(self) -> int: + """ + Set and get expected video width, in pixels. + + Returns: + object of int type + """ + return self.__reference_width.default_value + + @reference_width.setter + def reference_width(self, reference_width: int): + if not(self.__reference_width.min_value < reference_width < self.__reference_width.max_value): + raise ValueError(f"Reference width cannot be less than {self.__reference_width.min_value} and more than " + f"{self.__reference_width.max_value}.") + self.__reference_width.default_value = reference_width + + @property + def reference_height(self) -> int: + """ + Set and get expected video weight, in pixels. + + Returns: + object of int type + """ + return self.__reference_height.default_value + + @reference_height.setter + def reference_height(self, reference_height: int): + if not(self.__reference_height.min_value < reference_height < self.__reference_height.max_value): + raise ValueError(f"Reference height cannot be less than {self.__reference_height.min_value} and more than " + f"{self.__reference_height.max_value}.") + self.__reference_height.default_value = reference_height + + @property + def reference_color_depth(self) -> int: + """ + Set and get expected color depth, as bits per pixel `CrcVideoTestBpp`. + + Returns: + object of int type + """ + return self.__reference_color_depth.default_value + + @reference_color_depth.setter + def reference_color_depth(self, reference_color_depth: CrcVideoTestBpp): + self.__reference_color_depth.default_value = reference_color_depth.value + + @property + def required_frame_rate(self) -> int: + """ + Set and get expected frame rate, in millihertz (mHz). + + Returns: + object of int type + """ + return self.__required_frame_rate.default_value + + @required_frame_rate.setter + def required_frame_rate(self, required_frame_rate: int): + if not(self.__required_frame_rate.min_value <= required_frame_rate < self.__required_frame_rate.max_value): + raise ValueError(f"Required frame rate cannot be less than {self.__required_frame_rate.min_value} " + f"and more than {self.__required_frame_rate.max_value}.") + self.__required_frame_rate.default_value = required_frame_rate + + @property + def frame_rate_tolerance(self) -> int: + """ + Set and get frame rate tolerance, in millihertz (mHz). + + Returns: + object of int type + """ + return self.__frame_rate_tolerance.default_value + + @frame_rate_tolerance.setter + def frame_rate_tolerance(self, frame_rate_tolerance: int): + if not(self.__frame_rate_tolerance.min_value <= frame_rate_tolerance < self.__frame_rate_tolerance.max_value): + raise ValueError(f"Frame rate tolerance cannot be less than {self.__frame_rate_tolerance.min_value} " + f"and more than {self.__frame_rate_tolerance.max_value}.") + self.__frame_rate_tolerance.default_value = frame_rate_tolerance + + @property + def reference_crc_values(self) -> list: + """ + Set and get CRC reference values. Each CRC set consists of 3 16-bit words. + + Returns: + object of list type + """ + return self.__reference_crc_values.default_value + + @reference_crc_values.setter + def reference_crc_values(self, reference_crc_values: list): + if len(reference_crc_values) <= 0: + raise ValueError(f"Length of reference crc list must be more that 0") + self.__reference_crc_values.default_value = reference_crc_values + + @property + def motion_test_iteration(self) -> int: + """ + Set and get number of iterations the defined CRC sequence must be found in order to pass the test. + + Returns: + object of int type + """ + return self.__motion_test_iteration.default_value + + @motion_test_iteration.setter + def motion_test_iteration(self, motion_test_iteration: int): + if not(self.__motion_test_iteration.min_value <= motion_test_iteration < self.__motion_test_iteration.max_value): + raise ValueError(f"Motion test iteration cannot be less motion_test_iteration " + f"{self.__motion_test_iteration.min_value} and more than " + f"{self.__motion_test_iteration.max_value}.") + self.__motion_test_iteration.default_value = motion_test_iteration + + @property + def data_transfer_timeout(self) -> int: + """ + Set and get data transfer timeout in milliseconds. + + Returns: + object of int type + """ + return self.__data_transfer_timeout.default_value + + @data_transfer_timeout.setter + def data_transfer_timeout(self, data_transfer_timeout: int): + if not(self.__data_transfer_timeout.min_value < data_transfer_timeout < self.__data_transfer_timeout.max_value): + raise ValueError(f"Data transfer timeout cannot be less than {self.__data_transfer_timeout.min_value} " + f"and more than {self.__data_transfer_timeout.max_value}.") + self.__data_transfer_timeout.default_value = data_transfer_timeout + + @property + def failed_frames_folder(self) -> str: + """ + Set and get location where the failed frames are to be saved. + + Returns: + object of int type + """ + return self.__failed_frames_folder.default_value + + @failed_frames_folder.setter + def failed_frames_folder(self, failed_frames_folder: str): + if len(failed_frames_folder) <= 0: + raise ValueError(f"Path length of folder list must be more that 0") + self.__failed_frames_folder.default_value = failed_frames_folder + + @property + def max_export_failed(self) -> int: + """ + Set and get number of failed frames to be exported from the video test. + + Returns: + object of int type + """ + return self.__max_export_failed.default_value + + @max_export_failed.setter + def max_export_failed(self, max_export_failed: int): + if not(self.__max_export_failed.min_value <= max_export_failed < self.__max_export_failed.max_value): + raise ValueError(f"Number of max export failed frames cannot be less than " + f"{self.__max_export_failed.min_value} and more than " + f"{self.__max_export_failed.max_value}.") + self.__max_export_failed.default_value = max_export_failed + + @property + def export_format(self) -> BrokenFrameExportFormat: + """ + Set and get crc failed frame file format. + + Returns: + object of int type + """ + return BrokenFrameExportFormat(self.__export_format.default_value) + + @export_format.setter + def export_format(self, export_format: BrokenFrameExportFormat): + self.__export_format.default_value = export_format.value diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/dp1_4_sink_tests.py b/UniTAP/dev/modules/dut_tests/dut_default_params/dp1_4_sink_tests.py new file mode 100644 index 0000000..3657a33 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/dp1_4_sink_tests.py @@ -0,0 +1,773 @@ +from enum import IntEnum +from UniTAP.dev.modules.dut_tests.test_group_params_types import Param +from UniTAP.dev.modules.dut_tests.test_utils import update_default_value + + +class BitStream(IntEnum): + """ + Describes available values for bit stream. + Sink DUT support 444 CRC for Simple 422 bitstream + """ + YCbCr422 = 0 + YCbCr444 = 1 + + +class PackedSource(IntEnum): + """ + Describes available values for packer source. + Source of the most packet video modes table. + """ + UseTestConfig = 0 + UseSinkDutEdid = 1 + + +class DisplayIdVisualCheck(IntEnum): + """ + Describes available values for visual check during DisplayID CTS tests. + """ + NeverSkip = 0 + SkipIfCrcMatches = 1 + + +class Timing: + """ + Class `Timing` describes available supported timings standard. + - CTA `cta` (enable/disable). + - RB1 `rb1` (enable/disable). + - RB2 `rb2` (enable/disable). + """ + def __init__(self, timing_data): + self.__data = Param(timing_data) + self.__cta = False + self.__rb1 = False + self.__rb2 = False + + @property + def cta(self): + """ + Set and get CTA flag support. + + Returns: + object of bool type + """ + return self.__cta + + @cta.setter + def cta(self, cta: bool): + self.__cta = cta + self.__data.default_value = update_default_value(value=self.__cta, + default_value=self.__data.default_value, + mask=self.__data.bit_field_list[0].mask) + + @property + def rb1(self): + """ + Set and get RB1 flag support. + + Returns: + object of bool type + """ + return self.__rb1 + + @rb1.setter + def rb1(self, rb1: bool): + self.__rb1 = rb1 + self.__data.default_value = update_default_value(value=self.__rb1, + default_value=self.__data.default_value, + mask=self.__data.bit_field_list[1].mask) + + @property + def rb2(self): + """ + Set and get RB2 flag support. + + Returns: + object of bool type + """ + return self.__rb2 + + @rb2.setter + def rb2(self, rb2: bool): + self.__rb2 = rb2 + self.__data.default_value = update_default_value(value=self.__rb2, + default_value=self.__data.default_value, + mask=self.__data.bit_field_list[2].mask) + + def set_all(self): + self.cta = True + self.rb1 = True + self.rb2 = True + + def clear(self): + self.cta = False + self.rb1 = False + self.rb2 = False + + +class Dp14SinkTimings: + """ + Class `Dp14SinkTimings` defines DSC video modes adn allows settings values. + - 1920x1080 30Hz `T_1920_x_1080_30`. + - 1920x1080 60Hz `T_1920_x_1080_60`. + - 1920x1080 120Hz `T_1920_x_1080_120`. + - 3840x2160 30Hz `T_3840_x_2160_30`. + - 3840x2160 60Hz `T_3840_x_2160_60`. + - 3840x2160 120Hz `T_3840_x_2160_120`. + - 5120x2160 30Hz `T_5120_x_2160_30`. + - 5120x2160 60Hz `T_5120_x_2160_60`. + - 5120x2160 120Hz `T_5120_x_2160_120`. + - 7680x4320 30Hz `T_7680_x_4320_30`. + - 7680x4320 60Hz `T_7680_x_4320_60`. + - 7680x4320 100Hz `T_7680_x_4320_100`. + """ + def __init__(self, json_obj): + self.__1920_x_1080_30_id = Param(json_obj["TSI_DP14_SINKCTS_DSC_VM_0"]) + self.__1920_x_1080_30 = Timing(timing_data=json_obj["TSI_DP14_SINKCTS_DSC_VM_T_0"]) + + self.__1920_x_1080_60_id = Param(json_obj["TSI_DP14_SINKCTS_DSC_VM_1"]) + self.__1920_x_1080_60 = Timing(timing_data=json_obj["TSI_DP14_SINKCTS_DSC_VM_T_1"]) + + self.__1920_x_1080_120_id = Param(json_obj["TSI_DP14_SINKCTS_DSC_VM_2"]) + self.__1920_x_1080_120 = Timing(timing_data=json_obj["TSI_DP14_SINKCTS_DSC_VM_T_2"]) + + self.__3840_x_2160_30_id = Param(json_obj["TSI_DP14_SINKCTS_DSC_VM_3"]) + self.__3840_x_2160_30 = Timing(timing_data=json_obj["TSI_DP14_SINKCTS_DSC_VM_T_3"]) + + self.__3840_x_2160_60_id = Param(json_obj["TSI_DP14_SINKCTS_DSC_VM_4"]) + self.__3840_x_2160_60 = Timing(timing_data=json_obj["TSI_DP14_SINKCTS_DSC_VM_T_4"]) + + self.__3840_x_2160_120_id = Param(json_obj["TSI_DP14_SINKCTS_DSC_VM_5"]) + self.__3840_x_2160_120 = Timing(timing_data=json_obj["TSI_DP14_SINKCTS_DSC_VM_T_5"]) + + self.__5120_x_2160_30_id = Param(json_obj["TSI_DP14_SINKCTS_DSC_VM_6"]) + self.__5120_x_2160_30 = Timing(timing_data=json_obj["TSI_DP14_SINKCTS_DSC_VM_T_6"]) + + self.__5120_x_2160_60_id = Param(json_obj["TSI_DP14_SINKCTS_DSC_VM_7"]) + self.__5120_x_2160_60 = Timing(timing_data=json_obj["TSI_DP14_SINKCTS_DSC_VM_T_7"]) + + self.__5120_x_2160_120_id = Param(json_obj["TSI_DP14_SINKCTS_DSC_VM_8"]) + self.__5120_x_2160_120 = Timing(timing_data=json_obj["TSI_DP14_SINKCTS_DSC_VM_T_8"]) + + self.__7680_x_4320_30_id = Param(json_obj["TSI_DP14_SINKCTS_DSC_VM_9"]) + self.__7680_x_4320_30 = Timing(timing_data=json_obj["TSI_DP14_SINKCTS_DSC_VM_T_9"]) + + self.__7680_x_4320_60_id = Param(json_obj["TSI_DP14_SINKCTS_DSC_VM_10"]) + self.__7680_x_4320_60 = Timing(timing_data=json_obj["TSI_DP14_SINKCTS_DSC_VM_T_10"]) + + self.__7680_x_4320_100_id = Param(json_obj["TSI_DP14_SINKCTS_DSC_VM_11"]) + self.__7680_x_4320_100 = Timing(timing_data=json_obj["TSI_DP14_SINKCTS_DSC_VM_T_11"]) + + @property + def T_1920_x_1080_30(self) -> Timing: + """ + Set and get 1920x1080 30Hz timing. + + Returns: + object of `Timing` type + """ + return self.__1920_x_1080_30 + + @T_1920_x_1080_30.setter + def T_1920_x_1080_30(self, _1920_x_1080_30: Timing): + self.__1920_x_1080_30 = _1920_x_1080_30 + + @property + def T_1920_x_1080_60(self) -> Timing: + """ + Set and get 1920x1080 60Hz timing. + + Returns: + object of `Timing` type + """ + return self.__1920_x_1080_60 + + @T_1920_x_1080_60.setter + def T_1920_x_1080_60(self, _1920_x_1080_60: Timing): + self.__1920_x_1080_60 = _1920_x_1080_60 + + @property + def T_1920_x_1080_120(self) -> Timing: + """ + Set and get 1920x1080 120Hz timing. + + Returns: + object of `Timing` type + """ + return self.__1920_x_1080_120 + + @T_1920_x_1080_120.setter + def T_1920_x_1080_120(self, _1920_x_1080_120: Timing): + self.__1920_x_1080_120 = _1920_x_1080_120 + + @property + def T_3840_x_2160_30(self) -> Timing: + """ + Set and get 3840x2160 30Hz timing. + + Returns: + object of `Timing` type + """ + return self.__3840_x_2160_30 + + @T_3840_x_2160_30.setter + def T_3840_x_2160_30(self, _3840_x_2160_30: Timing): + self.__3840_x_2160_30 = _3840_x_2160_30 + + @property + def T_3840_x_2160_60(self) -> Timing: + """ + Set and get 3840x2160 60Hz timing. + + Returns: + object of `Timing` type + """ + return self.__3840_x_2160_60 + + @T_3840_x_2160_60.setter + def T_3840_x_2160_60(self, _3840_x_2160_60: Timing): + self.__3840_x_2160_60 = _3840_x_2160_60 + + @property + def T_3840_x_2160_120(self) -> Timing: + """ + Set and get 3840x2160 120Hz timing. + + Returns: + object of `Timing` type + """ + return self.__3840_x_2160_120 + + @T_3840_x_2160_120.setter + def T_3840_x_2160_120(self, _3840_x_2160_120: Timing): + self.__3840_x_2160_120 = _3840_x_2160_120 + + @property + def T_5120_x_2160_30(self) -> Timing: + """ + Set and get 5120x2160 30Hz timing. + + Returns: + object of `Timing` type + """ + return self.__5120_x_2160_30 + + @T_5120_x_2160_30.setter + def T_5120_x_2160_30(self, _5120_x_2160_30: Timing): + self.__15120_x_2160_30 = _5120_x_2160_30 + + @property + def T_5120_x_2160_60(self) -> Timing: + """ + Set and get 5120x2160 60Hz timing. + + Returns: + object of `Timing` type + """ + return self.__5120_x_2160_60 + + @T_5120_x_2160_60.setter + def T_5120_x_2160_60(self, _5120_x_2160_60: Timing): + self.__5120_x_2160_60 = _5120_x_2160_60 + + @property + def T_5120_x_2160_120(self) -> Timing: + """ + Set and get 5120x2160 120Hz timing. + + Returns: + object of `Timing` type + """ + return self.__5120_x_2160_120 + + @T_5120_x_2160_120.setter + def T_5120_x_2160_120(self, _5120_x_2160_120: Timing): + self.__5120_x_2160_120 = _5120_x_2160_120 + + @property + def T_7680_x_4320_30(self) -> Timing: + """ + Set and get 7680x4320 30Hz timing. + + Returns: + object of `Timing` type + """ + return self.__7680_x_4320_30 + + @T_7680_x_4320_30.setter + def T_7680_x_4320_30(self, _7680_x_4320_30: Timing): + self.__7680_x_4320_30 = _7680_x_4320_30 + + @property + def T_7680_x_4320_60(self) -> Timing: + """ + Set and get 7680x4320 60Hz timing. + + Returns: + object of `Timing` type + """ + return self.__7680_x_4320_60 + + @T_7680_x_4320_60.setter + def T_7680_x_4320_60(self, _7680_x_4320_60: Timing): + self.__7680_x_4320_60 = _7680_x_4320_60 + + @property + def T_7680_x_4320_100(self) -> Timing: + """ + Set and get 7680x4320 100Hz timing. + + Returns: + object of `Timing` type + """ + return self.__7680_x_4320_100 + + @T_7680_x_4320_100.setter + def T_7680_x_4320_100(self, _7680_x_4320_100: Timing): + self.__7680_x_4320_100 = _7680_x_4320_100 + + +class ConfigVideoMode1LaneParam: + """ + Class `ConfigVideoMode1LaneParam` defines support for video mode for 1 lane and allows settings values. + - CVT 1280x800 `cvt_1280x800`. + - DMT 1280x768 `dmt_1280x768`. + - DMT 800x600 `dmt_800x600`. + - DMT 1024x768 `dmt_1024x768`. + - CTA 1440x480 `cta_1440x480`. + - CTA 1440x576 `cta_1440x576`. + """ + def __init__(self, json_obj): + self.__cvt_1280x800 = Param(json_obj["TSI_DP14_SINKCTS_VIDEO_MODE_0"]) + self.__dmt_1280x768 = Param(json_obj["TSI_DP14_SINKCTS_VIDEO_MODE_1"]) + self.__dmt_800x600 = Param(json_obj["TSI_DP14_SINKCTS_VIDEO_MODE_2"]) + self.__dmt_1024x768 = Param(json_obj["TSI_DP14_SINKCTS_VIDEO_MODE_3"]) + self.__cta_1440x480 = Param(json_obj["TSI_DP14_SINKCTS_VIDEO_MODE_14"]) + self.__cta_1440x576 = Param(json_obj["TSI_DP14_SINKCTS_VIDEO_MODE_15"]) + + @property + def cvt_1280x800(self) -> bool: + """ + Set and get CVT 1280x800 timing flag support. + + Returns: + object of bool type + """ + return self.__cvt_1280x800.default_value + + @cvt_1280x800.setter + def cvt_1280x800(self, cvt_1280x800: bool): + self.__cvt_1280x800.default_value = cvt_1280x800 + + @property + def dmt_1280x768(self) -> bool: + """ + Set and get DMT 1280x768 timing flag support. + + Returns: + object of bool type + """ + return self.__dmt_1280x768.default_value + + @dmt_1280x768.setter + def dmt_1280x768(self, dmt_1280x768: bool): + self.__dmt_1280x768.default_value = dmt_1280x768 + + @property + def dmt_800x600(self) -> bool: + """ + Set and get DMT 800x600 timing flag support. + + Returns: + object of bool type + """ + return self.__dmt_800x600.default_value + + @dmt_800x600.setter + def dmt_800x600(self, dmt_800x600: bool): + self.__dmt_800x600.default_value = dmt_800x600 + + @property + def dmt_1024x768(self) -> bool: + """ + Set and get DMT 1024x768 timing flag support. + + Returns: + object of bool type + """ + return self.__dmt_1024x768.default_value + + @dmt_1024x768.setter + def dmt_1024x768(self, dmt_1024x768: bool): + self.__dmt_1024x768.default_value = dmt_1024x768 + + @property + def cta_1440x480(self) -> bool: + """ + Set and get CTA 1440x480 timing flag support. + + Returns: + object of bool type + """ + return self.__cta_1440x480.default_value + + @cta_1440x480.setter + def cta_1440x480(self, cta_1440x480: bool): + self.__cta_1440x480.default_value = cta_1440x480 + + @property + def cta_1440x576(self) -> bool: + """ + Set and get CTA 1440x576 timing flag support. + + Returns: + object of bool type + """ + return self.__cta_1440x576.default_value + + @cta_1440x576.setter + def cta_1440x576(self, cta_1440x576: bool): + self.__cta_1440x576.default_value = cta_1440x576 + + +class ConfigVideoMode2LaneParam: + """ + Class `ConfigVideoMode2LaneParam` defines support for video mode for 2 lane and allows settings values. + - DMT 1280x1024 `dmt_1280x1024`. + - DMT 1280x960 `dmt_1280x960`. + - DMT 1360x768 `dmt_1360x768`. + - CVT 1280x800 `cvt_1280x800`. + - DMT 1400x1050 `dmt_1400x1050`. + - DMT 1280x768 `dmt_1280x768`. + - CVT 1600x1200 `cvt_1600x1200` + """ + def __init__(self, json_obj): + self.__dmt_1280x1024 = Param(json_obj["TSI_DP14_SINKCTS_VIDEO_MODE_4"]) + self.__dmt_1280x960 = Param(json_obj["TSI_DP14_SINKCTS_VIDEO_MODE_5"]) + self.__dmt_1360x768 = Param(json_obj["TSI_DP14_SINKCTS_VIDEO_MODE_6"]) + self.__cvt_1280x800 = Param(json_obj["TSI_DP14_SINKCTS_VIDEO_MODE_7"]) + self.__dmt_1400x1050 = Param(json_obj["TSI_DP14_SINKCTS_VIDEO_MODE_8"]) + self.__dmt_1280x768 = Param(json_obj["TSI_DP14_SINKCTS_VIDEO_MODE_9"]) + self.__cvt_1600x1200 = Param(json_obj["TSI_DP14_SINKCTS_VIDEO_MODE_10"]) + + @property + def dmt_1280x1024(self) -> bool: + """ + Set and get DMT 1280x1024 timing flag support. + + Returns: + object of bool type + """ + return self.__dmt_1280x1024.default_value + + @dmt_1280x1024.setter + def dmt_1280x1024(self, dmt_1280x1024: bool): + self.__dmt_1280x1024.default_value = dmt_1280x1024 + + @property + def dmt_1280x960(self) -> bool: + """ + Set and get DMT 1280x960 timing flag support. + + Returns: + object of bool type + """ + return self.__dmt_1280x960.default_value + + @dmt_1280x960.setter + def dmt_1280x960(self, dmt_1280x960: bool): + self.__dmt_1280x960.default_value = dmt_1280x960 + + @property + def dmt_1360x768(self) -> bool: + """ + Set and get DMT 1360x768 timing flag support. + + Returns: + object of bool type + """ + return self.__dmt_1360x768.default_value + + @dmt_1360x768.setter + def dmt_1360x768(self, dmt_1360x768: bool): + self.__dmt_1360x768.default_value = dmt_1360x768 + + @property + def cvt_1280x800(self) -> bool: + """ + Set and get CVT 1280x800 timing flag support. + + Returns: + object of bool type + """ + return self.__cvt_1280x800.default_value + + @cvt_1280x800.setter + def cvt_1280x800(self, cvt_1280x800: bool): + self.__cvt_1280x800.default_value = cvt_1280x800 + + @property + def dmt_1400x1050(self) -> bool: + """ + Set and get DMT 1400x1050 timing flag support. + + Returns: + object of bool type + """ + return self.__dmt_1400x1050.default_value + + @dmt_1400x1050.setter + def dmt_1400x1050(self, dmt_1400x1050: bool): + self.__dmt_1400x1050.default_value = dmt_1400x1050 + + @property + def dmt_1280x768(self) -> bool: + """ + Set and get DMT 1280x768 timing flag support. + + Returns: + object of bool type + """ + return self.__dmt_1280x768.default_value + + @dmt_1280x768.setter + def dmt_1280x768(self, dmt_1280x768: bool): + self.__dmt_1280x768.default_value = dmt_1280x768 + + @property + def cvt_1600x1200(self) -> bool: + """ + Set and get CVT 1600x1200 timing flag support. + + Returns: + object of bool type + """ + return self.__cvt_1600x1200.default_value + + @cvt_1600x1200.setter + def cvt_1600x1200(self, cvt_1600x1200: bool): + self.__cvt_1600x1200.default_value = cvt_1600x1200 + + +class ConfigVideoMode4LaneParam: + """ + Class `ConfigVideoMode4LaneParam` defines support for video mode for 4 lane and allows settings values. + - CVT 2048x1536 `cvt_2048x1536`. + - DMT 1792x1344 `dmt_1792x1344`. + - DMT 1600x1200 `dmt_1600x1200`. + - CTA 1920x1080 `cta_1920x1080`. + """ + def __init__(self, json_obj): + self.__cvt_2048x1536 = Param(json_obj["TSI_DP14_SINKCTS_VIDEO_MODE_11"]) + self.__dmt_1792x1344 = Param(json_obj["TSI_DP14_SINKCTS_VIDEO_MODE_12"]) + self.__dmt_1600x1200 = Param(json_obj["TSI_DP14_SINKCTS_VIDEO_MODE_13"]) + self.__cta_1920x1080 = Param(json_obj["TSI_DP14_SINKCTS_VIDEO_MODE_16"]) + + @property + def cvt_2048x1536(self) -> bool: + """ + Set and get CVT 2048x1536 timing flag support. + + Returns: + object of bool type + """ + return self.__cvt_2048x1536.default_value + + @cvt_2048x1536.setter + def cvt_2048x1536(self, cvt_2048x1536: bool): + self.__cvt_2048x1536.default_value = cvt_2048x1536 + + @property + def dmt_1792x1344(self) -> bool: + """ + Set and get DMT 1792x1344 timing flag support. + + Returns: + object of bool type + """ + return self.__dmt_1792x1344.default_value + + @dmt_1792x1344.setter + def dmt_1792x1344(self, dmt_1792x1344: bool): + self.__dmt_1792x1344.default_value = dmt_1792x1344 + + @property + def dmt_1600x1200(self) -> bool: + """ + Set and get DMT 1600x1200 timing flag support. + + Returns: + object of bool type + """ + return self.__dmt_1600x1200.default_value + + @dmt_1600x1200.setter + def dmt_1600x1200(self, dmt_1600x1200: bool): + self.__dmt_1600x1200.default_value = dmt_1600x1200 + + @property + def cta_1920x1080(self) -> bool: + """ + Set and get CTA 1920x1080 timing flag support. + + Returns: + object of bool type + """ + return self.__cta_1920x1080.default_value + + @cta_1920x1080.setter + def cta_1920x1080(self, cta_1920x1080: bool): + self.__cta_1920x1080.default_value = cta_1920x1080 + + +class Dp14SinkTestParam: + """ + Class `Dp14SinkTestParam` allows working with parameters for Sink DP 1.4 LLCTS tests. + - Set and get test timeout, in milliseconds `timeout`. + - Set and get DSC timings `timings` type `Dp14SinkTimings`. + - Set and get capabilities of DSC video mode `dsc_video_mode`. + - Set and get bitstream type `bitstream` - type `BitStream`. + - Set and get packed source `packed_source` - type `PackedSource`. + - Set and get configuration of video modes for 1 lane `config_video_mode_1_lane` type `ConfigVideoMode1LaneParam`. + - Set and get configuration of video modes for 2 lane `config_video_mode_2_lane` type `ConfigVideoMode2LaneParam`. + - Set and get configuration of video modes for 4 lane `config_video_mode_4_lane` type `ConfigVideoMode4LaneParam`. + - Set and get flag of display id visual `display_id_visual` type `DisplayIdVisualCheck`. + """ + def __init__(self, json_obj): + self.__timeout = Param(json_obj["TSI_DP14_SINKCTS_TIMEOUT"]) + self.__timings = Dp14SinkTimings(json_obj) + self.__dsc_video_mode = Param(json_obj["TSI_DP14_SINKCTS_DSC_SOURCE"]) # PackedSource + self.__bitstream = Param(json_obj["TSI_DP14_SINKCTS_SUPPORT_444CRC"]) # BitStream + self.__packed_source = Param(json_obj["TSI_DP14_SINKCTS_PACKET_SOURCE"]) # PackedSource + self.__config_video_mode_1_lane = ConfigVideoMode1LaneParam(json_obj) + self.__config_video_mode_2_lane = ConfigVideoMode2LaneParam(json_obj) + self.__config_video_mode_4_lane = ConfigVideoMode4LaneParam(json_obj) + self.__display_id_visual = Param(json_obj["TSI_DP14_SINKCTS_VISUAL_TEST_CHECK"]) + + @property + def timeout(self) -> int: + """ + Set and get test timeout, in milliseconds. + + Returns: + object of int type + """ + return self.__timeout.default_value + + @timeout.setter + def timeout(self, timeout: int): + if not(self.__timeout.min_value < timeout < self.__timeout.max_value): + raise ValueError(f"Timeout cannot be less than {self.__timeout.min_value} and more than " + f"{self.__timeout.max_value}.") + self.__timeout.default_value = timeout + + @property + def timings(self): + """ + Set and get timings. + + Returns: + object `Dp14SinkTimings` + """ + return self.__timings + + @timings.setter + def timings(self, timings: Dp14SinkTimings): + self.__timings = timings + + @property + def dsc_video_mode(self) -> PackedSource: + """ + Set and get dsc video packed source. + + Returns: + object `PackedSource` + """ + return PackedSource(self.__dsc_video_mode.default_value) + + @dsc_video_mode.setter + def dsc_video_mode(self, dsc_video_mode: PackedSource): + self.__dsc_video_mode.default_value = dsc_video_mode.value + + @property + def bitstream(self) -> BitStream: + """ + Set and get bitstream. + + Returns: + object `BitStream` + """ + return BitStream(self.__bitstream.default_value) + + @bitstream.setter + def bitstream(self, bitstream: BitStream): + self.__bitstream.default_value = bitstream.value + + @property + def packed_source(self) -> PackedSource: + """ + Set and get packed source. + + Returns: + object `PackedSource` + """ + return PackedSource(self.__packed_source.default_value) + + @packed_source.setter + def packed_source(self, packed_source: PackedSource): + self.__packed_source.default_value = packed_source.value + + @property + def config_video_mode_1_lane(self): + """ + Set and get configuration of video modes for 1 lane. + + Returns: + object `ConfigVideoMode1LaneParam` + """ + return self.__config_video_mode_1_lane + + @config_video_mode_1_lane.setter + def config_video_mode_1_lane(self, config_video_mode_1_lane: ConfigVideoMode1LaneParam): + self.__config_video_mode_1_lane = config_video_mode_1_lane + + @property + def config_video_mode_2_lane(self): + """ + Set and get configuration of video modes for 2 lane. + + Returns: + object `ConfigVideoMode2LaneParam` + """ + return self.__config_video_mode_2_lane + + @config_video_mode_2_lane.setter + def config_video_mode_2_lane(self, config_video_mode_2_lane: ConfigVideoMode2LaneParam): + self.__config_video_mode_2_lane = config_video_mode_2_lane + + @property + def config_video_mode_4_lane(self): + """ + Set and get configuration of video modes for 4 lane. + + Returns: + object `ConfigVideoMode4LaneParam` + """ + return self.__config_video_mode_4_lane + + @config_video_mode_4_lane.setter + def config_video_mode_4_lane(self, config_video_mode_4_lane: ConfigVideoMode4LaneParam): + self.__config_video_mode_4_lane = config_video_mode_4_lane + + @property + def display_id_visual(self) -> DisplayIdVisualCheck: + """ + Set and get flag of visual check in Display ID. + + Returns: + object `DisplayIdVisualCheck` + """ + return DisplayIdVisualCheck(self.__display_id_visual.default_value) + + @display_id_visual.setter + def display_id_visual(self, display_id_visual: DisplayIdVisualCheck): + self.__display_id_visual.default_value = display_id_visual diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/dp1_4_source_tests.py b/UniTAP/dev/modules/dut_tests/dut_default_params/dp1_4_source_tests.py new file mode 100644 index 0000000..666bca1 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/dp1_4_source_tests.py @@ -0,0 +1,73 @@ +from UniTAP.dev.modules.dut_tests.dut_default_params.dp_1_4_source_general_tab import GeneralSourceDUTDp14SettingTab, \ + PackedTimings1Lane, PackedTimings2Lane, PackedTimings4Lane, EventIndication +from UniTAP.dev.modules.dut_tests.dut_default_params.dp_source_audio_tab import AudioSourceDp14SettingTab +from UniTAP.dev.modules.dut_tests.dut_default_params.dp_source_dsc_tab import DscConfigDp14Tab +from UniTAP.dev.modules.dut_tests.dut_default_params.dp_source_display_id_tab import DisplayIdDp14ConfigTab +from UniTAP.dev.modules.dut_tests.dut_default_params.dp_source_adaptive_sync_tab import AdaptiveSyncDp14ConfigTab + + +class Dp14SourceDUTTestParam: + """ + Class `Dp14SourceDUTTestParam` allows working with default group of parameters for DP 1.4 LLCTS tests: + - Set and get `GeneralSourceDUTDp14SettingTab`. Allows working with parameters from General source part `general`. + - Set and get `AudioSourceDp14SettingTab`. Allows working with parameters from Audio source part `audio`. + - Set and get `DscConfigDp14Tab`. Allows working with parameters from DSC part `dsc`. + - Set and get `DisplayIdDp14ConfigTab`. Allows working with parameters from Display ID part `display_id`. + - Set and get `AdaptiveSyncDp14ConfigTab`. Allows working with parameters from Adaptive-Sync part `adaptive_sync`. + """ + def __init__(self, json_obj): + self.__general_tab = GeneralSourceDUTDp14SettingTab(json_obj) + self.__audio_tab = AudioSourceDp14SettingTab(json_obj) + self.__dsc_tab = DscConfigDp14Tab(json_obj) + self.__display_id_tab = DisplayIdDp14ConfigTab(json_obj) + self.__adaptive_sync_tab = AdaptiveSyncDp14ConfigTab(json_obj) + + @property + def general(self) -> GeneralSourceDUTDp14SettingTab: + """ + Get object of parameters from General source part. + + Returns: + object of `GeneralSourceDUTDp14SettingTab` type + """ + return self.__general_tab + + @property + def audio(self) -> AudioSourceDp14SettingTab: + """ + Get object of parameters from Audio source part. + + Returns: + object of `AudioSourceDp14SettingTab` type + """ + return self.__audio_tab + + @property + def dsc(self) -> DscConfigDp14Tab: + """ + Get object of parameters from DSC source part. + + Returns: + object of `DscConfigDp14Tab` type + """ + return self.__dsc_tab + + @property + def display_id(self) -> DisplayIdDp14ConfigTab: + """ + Get object of parameters from Display ID source part. + + Returns: + object of `DisplayIdDp14ConfigTab` type + """ + return self.__display_id_tab + + @property + def adaptive_sync(self) -> AdaptiveSyncDp14ConfigTab: + """ + Get object of parameters from Adaptive-Sync source part. + + Returns: + object of `AdaptiveSyncDp14ConfigTab` type + """ + return self.__adaptive_sync_tab diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/dp2_1_sink_tests.py b/UniTAP/dev/modules/dut_tests/dut_default_params/dp2_1_sink_tests.py new file mode 100644 index 0000000..fee4eed --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/dp2_1_sink_tests.py @@ -0,0 +1,355 @@ +from enum import IntEnum +from UniTAP.dev.modules.dut_tests.test_group_params_types import Param +from UniTAP.dev.modules.dut_tests.test_utils import update_default_value + + +class BitStream(IntEnum): + """ + Describes available values for bit stream. + Sink DUT support 444 CRC for Simple 422 bitstream + """ + YCbCr422 = 0 + YCbCr444 = 1 + + +class PackedSource(IntEnum): + """ + Describes available values for packer source. + Source of the most packet video modes table. + """ + UseTestConfig = 0 + UseSinkDutEdid = 1 + + +class VisualCheck(IntEnum): + """ + Describes available values for visual check during DisplayID CTS tests. + """ + NeverSkip = 0 + SkipIfCrcMatches = 1 + + +class StandardBase: + + __rate_shift_rb1 = {30: 1, 60: 4, 120: 7, 144: 9, 240: 12} + __rate_shift_rb2 = {30: 2, 60: 5, 120: 8, 144: 10, 240: 13} + + def __init__(self, value: Param, rate: int): + self._value = value + self._rate = rate + + @property + def rb1(self) -> bool: + """ + Set and get RB1 flag support. + + Returns: + object of bool type + """ + return self._value.default_value & self._value.bit_field_list[self.__rate_shift_rb1.get(self._rate)].mask != 0 + + @rb1.setter + def rb1(self, value: bool): + self._value._set_by_bit_number(value, 1, self.__rate_shift_rb1.get(self._rate)) + + @property + def rb2(self) -> bool: + """ + Set and get RB2 flag support. + + Returns: + object of bool type + """ + return self._value.default_value & self._value.bit_field_list[self.__rate_shift_rb2.get(self._rate)].mask != 0 + + @rb2.setter + def rb2(self, value: bool): + self._value._set_by_bit_number(value, 1, self.__rate_shift_rb2.get(self._rate)) + + +class Rate30Hz(StandardBase): + + __rate_shift_cta = {30: 0, 60: 3, 120: 6} + + def __init__(self, value: Param, rate: int): + super().__init__(value, rate) + + @property + def cta(self) -> bool: + """ + Set and get CTA flag support. + + Returns: + object of bool type + """ + return self._value.default_value & self._value.bit_field_list[self.__rate_shift_cta.get(self._rate)].mask != 0 + + @cta.setter + def cta(self, value: bool): + self._value._set_by_bit_number(value, 1, self.__rate_shift_cta.get(self._rate)) + + def set_all(self): + self.cta = True + self.rb1 = True + self.rb2 = True + + def clear_all(self): + self.cta = False + self.rb1 = False + self.rb2 = False + + +class Rate144Hz(StandardBase): + + __rate_shift_ovt = {144: 11, 240: 14} + + def __init__(self, value: Param, rate: int): + super().__init__(value, rate) + + @property + def ovt(self) -> bool: + """ + Set and get OVT flag support. + + Returns: + object of bool type + """ + return self._value.default_value & self._value.bit_field_list[self.__rate_shift_ovt.get(self._rate)].mask != 0 + + @ovt.setter + def ovt(self, value: bool): + self._value._set_by_bit_number(value, 1, self.__rate_shift_ovt.get(self._rate)) + + def set_all(self): + self.ovt = True + self.rb1 = True + self.rb2 = True + + def clear_all(self): + self.ovt = False + self.rb1 = False + self.rb2 = False + + +class VideoModeInfo: + + def __init__(self, json_obj): + self.__value = Param(json_obj) + self.__30hz = Rate30Hz(self.__value, 30) + self.__60hz = Rate30Hz(self.__value, 60) + self.__120hz = Rate30Hz(self.__value, 120) + self.__144hz = Rate144Hz(self.__value, 144) + self.__240hz = Rate144Hz(self.__value, 240) + + @property + def rate_30hz(self) -> Rate30Hz: + return self.__30hz + + @property + def rate_60hz(self) -> Rate30Hz: + return self.__60hz + + @property + def rate_120hz(self) -> Rate30Hz: + return self.__120hz + + @property + def rate_144hz(self) -> Rate144Hz: + return self.__144hz + + @property + def rate_240hz(self) -> Rate144Hz: + return self.__240hz + + def set_all(self): + self.rate_30hz.set_all() + self.rate_60hz.set_all() + self.rate_120hz.set_all() + self.rate_144hz.set_all() + self.rate_240hz.set_all() + + def clear_all(self): + self.rate_30hz.clear_all() + self.rate_60hz.clear_all() + self.rate_120hz.clear_all() + self.rate_144hz.clear_all() + self.rate_240hz.clear_all() + + +class Dp21SinkVideoModes: + + def __init__(self, json_obj): + self.__1920x1080 = VideoModeInfo(json_obj["TSI_DP20_SINKCTS_VMT_1920_1080"]) + self.__3840x2160 = VideoModeInfo(json_obj["TSI_DP20_SINKCTS_VMT_3840_2160"]) + self.__5120x2160 = VideoModeInfo(json_obj["TSI_DP20_SINKCTS_VMT_5120_2160"]) + self.__7680x4320 = VideoModeInfo(json_obj["TSI_DP20_SINKCTS_VMT_7680_4320"]) + self.__10240x4320 = VideoModeInfo(json_obj["TSI_DP20_SINKCTS_VMT_10240_4320"]) + self.__15360x8640 = VideoModeInfo(json_obj["TSI_DP20_SINKCTS_VMT_15360_8640"]) + + @property + def vm_1920x1080(self) -> VideoModeInfo: + return self.__1920x1080 + + @property + def vm_3840x2160(self) -> VideoModeInfo: + return self.__3840x2160 + + @property + def vm_5120x2160(self) -> VideoModeInfo: + return self.__5120x2160 + + @property + def vm_7680x4320(self) -> VideoModeInfo: + return self.__7680x4320 + + @property + def vm_10240x4320(self) -> VideoModeInfo: + return self.__10240x4320 + + @property + def vm_15360x8640(self) -> VideoModeInfo: + return self.__15360x8640 + + def set_all(self): + self.vm_1920x1080.set_all() + self.vm_3840x2160.set_all() + self.vm_5120x2160.set_all() + self.vm_7680x4320.set_all() + self.vm_10240x4320.set_all() + self.vm_15360x8640.set_all() + + def clear_all(self): + self.vm_1920x1080.clear_all() + self.vm_3840x2160.clear_all() + self.vm_5120x2160.clear_all() + self.vm_7680x4320.clear_all() + self.vm_10240x4320.clear_all() + self.vm_15360x8640.clear_all() + + +class DebugOptions: + + def __init__(self, json_obj): + self.__param = Param(json_obj["TSI_DP20_SINKCTS_DEBUG_CONF"]) + + @property + def force_visual_check(self) -> bool: + """ + Set and get Force manual visual check flag. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(0, bool) + + @force_visual_check.setter + def force_visual_check(self, force_visual_check: bool): + self.__param.default_value = update_default_value(value=force_visual_check, + default_value=self.__param.default_value, + mask=self.__param.bit_field_list[0].mask) + + +class Dp21SinkTestParam: + """ + Class `Dp21SinkTestParam` allows working with parameters for Sink DP 2.1 LLCTS tests. + - Set and get test timeout, in milliseconds `timeout`. + - Set and get DSC VideoModeInfo `video_modes ` type `Dp21SinkVideoModes`. + - Set and get capabilities of DSC video mode `dsc_video_mode`. + - Set and get bitstream type `bitstream` - type `BitStream`. + - Set and get flag of display id visual `visual_check` type `VisualCheck`. + - Set and get debug options `debug_options` type `DebugOptions` + """ + def __init__(self, json_obj): + self.__timeout = Param(json_obj["TSI_DP20_SINKCTS_TIMEOUT"]) + self.__dsc_video_mode = Param(json_obj["TSI_DP20_SINKCTS_DSC_SOURCE"]) # PackedSource + self.__bitstream = Param(json_obj["TSI_DP20_SINKCTS_SUPPORT_444CRC"]) # BitStream + self.__display_id_visual = Param(json_obj["TSI_DP20_SINKCTS_VISUAL_TEST_CHECK"]) + self.__video_modes = Dp21SinkVideoModes(json_obj) + self.__debug_options = DebugOptions(json_obj) + + @property + def timeout(self) -> int: + """ + Set and get test timeout, in milliseconds. + + Returns: + object of int type + """ + return self.__timeout.default_value + + @timeout.setter + def timeout(self, timeout: int): + if not(self.__timeout.min_value < timeout < self.__timeout.max_value): + raise ValueError(f"Timeout cannot be less than {self.__timeout.min_value} and more than " + f"{self.__timeout.max_value}.") + self.__timeout.default_value = timeout + + @property + def dsc_video_mode(self) -> PackedSource: + """ + Set and get dsc video packed source. + + Returns: + object `PackedSource` + """ + return PackedSource(self.__dsc_video_mode.default_value) + + @dsc_video_mode.setter + def dsc_video_mode(self, dsc_video_mode: PackedSource): + self.__dsc_video_mode.default_value = dsc_video_mode.value + + @property + def bitstream(self) -> BitStream: + """ + Set and get bitstream. + + Returns: + object `BitStream` + """ + return BitStream(self.__bitstream.default_value) + + @bitstream.setter + def bitstream(self, bitstream: BitStream): + self.__bitstream.default_value = bitstream.value + + @property + def visual_check(self) -> VisualCheck: + """ + Set and get flag of visual check. + + Returns: + object `DisplayIdVisualCheck` + """ + return VisualCheck(self.__display_id_visual.default_value) + + @visual_check.setter + def visual_check(self, display_id_visual: VisualCheck): + self.__display_id_visual.default_value = display_id_visual + + @property + def video_modes(self) -> Dp21SinkVideoModes: + """ + Set and get DP 2.1 Sink video modes. + + Returns: + object `Dp21SinkVideoModes` + """ + return self.__video_modes + + @video_modes.setter + def video_modes(self, video_modes: Dp21SinkVideoModes): + self.__video_modes = video_modes + + @property + def debug_options(self) -> DebugOptions: + """ + Set and get debug options. + + Returns: + object of 'DebugOptions' type + """ + return self.__debug_options + + @debug_options.setter + def debug_options(self, debug_config: DebugOptions): + self.__debug_options = debug_config diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/dp2_1_source_tests.py b/UniTAP/dev/modules/dut_tests/dut_default_params/dp2_1_source_tests.py new file mode 100644 index 0000000..e4e0e06 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/dp2_1_source_tests.py @@ -0,0 +1,83 @@ +from UniTAP.dev.modules.dut_tests.dut_default_params.dp_2_1_source_general_tab import GeneralSourceDUTDp21SettingTab +from UniTAP.dev.modules.dut_tests.dut_default_params.dp_source_display_id_tab import DisplayIdDp21ConfigTab +from UniTAP.dev.modules.dut_tests.dut_default_params.dp_source_adaptive_sync_tab import AdaptiveSyncDp21ConfigTab +from UniTAP.dev.modules.dut_tests.dut_default_params.dp_2_1_video_modes import Dp21AvailableVideoModes +from UniTAP.dev.modules.dut_tests.dut_default_params.dp_2_1_dsc_video_modes import Dp21AvailableDscVideoModes +from UniTAP.dev.modules.dut_tests.dut_default_params.dp_source_audio_tab import AudioSourceDp21SettingTab + + +class Dp21SourceDUTTestParam: + """ + Class `Dp21SourceDUTTestParam` allows working with default group of parameters for DP 2.1 LLCTS tests: + - Set and get `GeneralSourceDUTDp21SettingTab`. Allows working with parameters from General source part `general`. + - Set and get `DisplayIdDp21ConfigTab`. Allows working with parameters from Display ID part `display_id`. + - Set and get `AdaptiveSyncDp21ConfigTab`. Allows working with parameters from Adaptive-Sync part `adaptive_sync`. + - Set and get `Dp21AvailableVideoModes`. Allows working with parameters from Video modes part `video_modes`. + """ + def __init__(self, json_obj): + self.__general_tab = GeneralSourceDUTDp21SettingTab(json_obj) + self.__audio_tab = AudioSourceDp21SettingTab(json_obj) + self.__display_id_tab = DisplayIdDp21ConfigTab(json_obj) + self.__adaptive_sync_tab = AdaptiveSyncDp21ConfigTab(json_obj) + self.__video_modes = Dp21AvailableVideoModes(json_obj) + self.__dsc_video_modes = Dp21AvailableDscVideoModes(json_obj) + + @property + def general(self) -> GeneralSourceDUTDp21SettingTab: + """ + Get object of parameters from General source part. + + Returns: + object of `GeneralSourceDUTDp21SettingTab` type + """ + return self.__general_tab + + @property + def audio(self) -> AudioSourceDp21SettingTab: + """ + Get object of parameters from Audio source part. + + Returns: + object of `AudioSourceDp21SettingTab` type + """ + return self.__audio_tab + + @property + def display_id(self) -> DisplayIdDp21ConfigTab: + """ + Get object of parameters from Display ID source part. + + Returns: + object of `DisplayIdDp21ConfigTab` type + """ + return self.__display_id_tab + + @property + def adaptive_sync(self) -> AdaptiveSyncDp21ConfigTab: + """ + Get object of parameters from Adaptive-Sync source part. + + Returns: + object of `AdaptiveSyncDp21ConfigTab` type + """ + return self.__adaptive_sync_tab + + @property + def video_modes(self) -> Dp21AvailableVideoModes: + """ + Get object of parameters from Video modes source part. + + Returns: + object of `Dp21AvailableVideoModes` type + """ + return self.__video_modes + + @property + def dsc_video_modes(self) -> Dp21AvailableDscVideoModes: + """ + Get object of parameters from Video modes source part. + + Returns: + object of `Dp21AvailableVideoModes` type + """ + return self.__dsc_video_modes diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/dp_1_4_source_general_tab.py b/UniTAP/dev/modules/dut_tests/dut_default_params/dp_1_4_source_general_tab.py new file mode 100644 index 0000000..c8151e2 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/dp_1_4_source_general_tab.py @@ -0,0 +1,1526 @@ +from enum import IntEnum +from UniTAP.dev.modules.dut_tests.test_group_params_types import Param +from UniTAP.dev.modules.dut_tests.test_utils import CMTFormat, CMTVSCFormat, CMTBitDepth, CMTVSCBitDepth,\ + make_cf, make_vsc_cf, update_default_value + + +class EventIndication(IntEnum): + """ + Describes available events indications for test automation. + """ + Unknown = -1 + AlwaysReady = 0 + EdidRead = 1 + LinkTrainingEnd = 2 + ActiveVideo = 3 + + +class MaxSupportedVideoMode(IntEnum): + """ + Describes available video modes for field `max_supported_video_mode`. + """ + T_640x480_60Hz_6BPC = 1 + T_848x480_60Hz_8BPC = 2 + T_1280x720_60Hz_8BPC = 3 + T_1280x960_60Hz_8BPC = 4 + T_1920x1080_60Hz_8BPC = 5 + T_1920x1440_60Hz_8BPC = 6 + T_1920x1080_120Hz_8BPC = 7 + T_1280x800_60Hz_RB1_6BPC = 8 + T_1280x768_60Hz_RB1_6BPC = 9 + T_800x600_60Hz_10BPC = 10 + T_1024x768_60Hz_10BPC = 11 + T_1280x1024_60Hz_8BPC = 12 + T_1360x768_60Hz_10BPC = 13 + T_1280x800_60Hz_RB1_10BPC = 14 + T_1400x1050_60Hz_RB1_8BPC = 15 + T_1280x768_60Hz_RB1_10BPC = 16 + T_1600x1200_60Hz_RB1_6BPC = 17 + T_2048x1536_60Hz_RB1_8BPC = 18 + T_1792x1344_60Hz_8BPC = 19 + T_1600x1200_60Hz_RB1_10BPC = 20 + T_3840x2160_30Hz_8BPC = 21 + T_3840x2160_60Hz_8BPC = 22 + + +class PackedTimings1Lane(IntEnum): + """ + Describes available timings for field `packed_timings_1_lane`. + """ + NoneTiming = 0 + DMT_800x600_60Hz_10BPC = 10 + DMT_1024x768_60Hz_6BPC = 11 + CVT_1280x800_60Hz_RB1_6BPC = 8 + DMT_1280x768_60Hz_RB1_6BPC = 9 + CTA_1440x480_60Hz_8BPC = 23 + CTA_1440x576_50Hz_8BPC = 24 + + +class PackedTimings2Lane(IntEnum): + """ + Describes available timings for field `packed_timings_2_lane`. + """ + NoneTiming = 0 + DMT_1280x768_60Hz_10BPC = 16 + CVT_1280x800_60Hz_10BPC = 14 + DMT_1280x960_60Hz_8BPC = 4 + DMT_1280x1024_60Hz_8BPC = 12 + DMT_1360x768_60Hz_10BPC = 13 + DMT_1400x1050_60Hz_RB1_8BPC = 15 + CVT_1600x1200_60Hz_RB1_6BPC = 17 + DMT_1360x768_60Hz_8BPC = 13 + CVT_1280x800_60Hz_8BPC = 26 + DMT_1280x768_60Hz_8BPC = 25 + + +class PackedTimings4Lane(IntEnum): + """ + Describes available timings for field `packed_timings_4_lane`. + """ + NoneTiming = 0 + DMT_1600x1200_60Hz_10BPC = 20 + DMT_1600x1200_60Hz_8BPC = 28 + DMT_1792x1344_60Hz_8BPC = 19 + CTA_1920x1080_60Hz_10BPC = 29 + CTA_1920x1080_60Hz_8BPC = 5 + CVT_2048x1536_60Hz_RB1_8BPC = 18 + + +class Rbb1Lane(IntEnum): + """ + Describes available timings for field `rbb_1l`. + """ + NoneTiming = 0 + T_640x480_60Hz_6BPC = 1 + T_848x480_60Hz_8BPC = 2 + + +class Rbb2Lane(IntEnum): + """ + Describes available timings for field `rbb_2l`. + """ + NoneTiming = 0 + T_640x480_60Hz_6BPC = 1 + T_848x480_60Hz_8BPC = 2 + T_1280x720_60Hz_8BPC = 3 + + +class Rbb4Lane(IntEnum): + """ + Describes available timings for field `rbb_4l`. + """ + NoneTiming = 0 + T_640x480_60Hz_6BPC = 1 + T_848x480_60Hz_8BPC = 2 + T_1280x720_60Hz_8BPC = 3 + T_1280x960_60Hz_8BPC = 4 + T_1920_1080_60Hz_8BPC = 5 + + +class Hbr1Lane(IntEnum): + """ + Describes available timings for field `hbr_1l`. + """ + NoneTiming = 0 + T_640x480_60Hz_6BPC = 1 + T_848x480_60Hz_8BPC = 2 + T_1280x720_60Hz_8BPC = 3 + + +class Hbr2Lane(IntEnum): + """ + Describes available timings for field `hbr_2l`. + """ + NoneTiming = 0 + T_640x480_60Hz_6BPC = 1 + T_848x480_60Hz_8BPC = 2 + T_1280x720_60Hz_8BPC = 3 + T_1280x960_60Hz_8BPC = 4 + + +class Hbr4Lane(IntEnum): + """ + Describes available timings for field `hbr_4l`. + """ + NoneTiming = 0 + T_640x480_60Hz_6BPC = 1 + T_848x480_60Hz_8BPC = 2 + T_1280x720_60Hz_8BPC = 3 + T_1280x960_60Hz_8BPC = 4 + T_1920_1080_60Hz_8BPC = 5 + T_1920_1440_60Hz_8BPC = 6 + + +class Hbr2_1Lane(IntEnum): + """ + Describes available timings for field `hbr2_1l`. + """ + NoneTiming = 0 + T_640x480_60Hz_6BPC = 1 + T_848x480_60Hz_8BPC = 2 + T_1280x720_60Hz_8BPC = 3 + T_1280x960_60Hz_8BPC = 4 + + +class Hbr2_2Lane(IntEnum): + """ + Describes available timings for field `hbr2_2l`. + """ + NoneTiming = 0 + T_640x480_60Hz_6BPC = 1 + T_848x480_60Hz_8BPC = 2 + T_1280x720_60Hz_8BPC = 3 + T_1280x960_60Hz_8BPC = 4 + T_1920_1080_60Hz_8BPC = 5 + T_1920_1440_60Hz_8BPC = 6 + + +class Hbr2_4Lane(IntEnum): + """ + Describes available timings for field `hbr2_4l`. + """ + NoneTiming = 0 + T_640x480_60Hz_6BPC = 1 + T_848x480_60Hz_8BPC = 2 + T_1280x720_60Hz_8BPC = 3 + T_1280x960_60Hz_8BPC = 4 + T_1920_1080_60Hz_8BPC = 5 + T_1920_1440_60Hz_8BPC = 6 + T_1920x1080_120Hz_8BPC = 7 + + +class Hbr3_1Lane(IntEnum): + """ + Describes available timings for field `hbr3_1l`. + """ + NoneTiming = 0 + T_640x480_60Hz_6BPC = 1 + T_848x480_60Hz_8BPC = 2 + T_1280x720_60Hz_8BPC = 3 + T_1280x960_60Hz_8BPC = 4 + T_1920_1080_60Hz_8BPC = 5 + T_1920_1440_60Hz_8BPC = 6 + + +class Hbr3_2Lane(IntEnum): + """ + Describes available timings for field `hbr3_2l`. + """ + NoneTiming = 0 + T_640x480_60Hz_6BPC = 1 + T_848x480_60Hz_8BPC = 2 + T_1280x720_60Hz_8BPC = 3 + T_1280x960_60Hz_8BPC = 4 + T_1920_1080_60Hz_8BPC = 5 + T_1920_1440_60Hz_8BPC = 6 + T_1920x1080_120Hz_8BPC = 7 + T_1280x800_60Hz_RB1_6BPC = 8 + T_1280x768_60Hz_RB1_6BPC = 9 + T_800x600_60Hz_10BPC = 10 + T_1024x768_60Hz_10BPC = 11 + T_1280x1024_60Hz_8BPC = 12 + T_1360x768_60Hz_10BPC = 13 + T_1280x800_60Hz_RB1_10BPC = 14 + T_1400x1050_60Hz_RB1_8BPC = 15 + T_1280x768_60Hz_RB1_10BPC = 16 + T_1600x1200_60Hz_RB1_6BPC = 17 + T_2048x1536_60Hz_RB1_8BPC = 18 + T_1792x1344_60Hz_8BPC = 19 + T_1600x1200_60Hz_RB1_10BPC = 20 + T_3840x2160_30Hz_8BPC = 21 + + +class Hbr3_4Lane(IntEnum): + """ + Describes available timings for field `hbr3_4l`. + """ + NoneTiming = 0 + T_640x480_60Hz_6BPC = 1 + T_848x480_60Hz_8BPC = 2 + T_1280x720_60Hz_8BPC = 3 + T_1280x960_60Hz_8BPC = 4 + T_1920_1080_60Hz_8BPC = 5 + T_1920_1440_60Hz_8BPC = 6 + T_1920x1080_120Hz_8BPC = 7 + T_1280x800_60Hz_RB1_6BPC = 8 + T_1280x768_60Hz_RB1_6BPC = 9 + T_800x600_60Hz_10BPC = 10 + T_1024x768_60Hz_10BPC = 11 + T_1280x1024_60Hz_8BPC = 12 + T_1360x768_60Hz_10BPC = 13 + T_1280x800_60Hz_RB1_10BPC = 14 + T_1400x1050_60Hz_RB1_8BPC = 15 + T_1280x768_60Hz_RB1_10BPC = 16 + T_1600x1200_60Hz_RB1_6BPC = 17 + T_2048x1536_60Hz_RB1_8BPC = 18 + T_1792x1344_60Hz_8BPC = 19 + T_1600x1200_60Hz_RB1_10BPC = 20 + T_3840x2160_30Hz_8BPC = 21 + T_3840x2160_60Hz_8BPC = 22 + + +class TestAutomationFlags: + """ + Class `TestAutomationFlags` allows configuring test automation flags: + - Set and get Test link training flag `test_link_training`. + - Set and get Test EDID read flag `test_edid_read`. + - Set and get Test video pattern flag `test_video_pattern`. + - Set and get Test audio pattern flag `test_audio_pattern`. + - Set and get Test Event indication `event_indication` type `EventIndication`. + """ + def __init__(self, json_obj): + self._param = Param(json_obj) + + @property + def test_link_training(self) -> bool: + """ + Set and get link training flag. + + Returns: + object of bool type + """ + return self._param._get_by_bitmask(0, bool) + + @test_link_training.setter + def test_link_training(self, test_link_training: bool): + self._param._set_by_bitmask(int(test_link_training), 0) + + @property + def test_edid_read(self) -> bool: + """ + Set and get EDID read flag. + + Returns: + object of bool type + """ + return self._param._get_by_bitmask(1, bool) + + @test_edid_read.setter + def test_edid_read(self, test_edid_read: bool): + self._param._set_by_bitmask(int(test_edid_read), 1) + + @property + def test_video_pattern(self) -> bool: + """ + Set and get video pattern flag. + + Returns: + object of bool type + """ + return self._param._get_by_bitmask(2, bool) + + @test_video_pattern.setter + def test_video_pattern(self, test_video_pattern: bool): + self._param._set_by_bitmask(int(test_video_pattern), 2) + + @property + def test_audio_pattern(self) -> bool: + """ + Set and get audio pattern flag. + + Returns: + object of bool type + """ + return self._param._get_by_bitmask(3, bool) + + @test_audio_pattern.setter + def test_audio_pattern(self, test_audio_pattern: bool): + self._param._set_by_bitmask(int(test_audio_pattern), 3) + + @property + def event_indication(self) -> EventIndication: + """ + Set and get link training flag. + + Returns: + object of `EventIndication` type + """ + return self._param._get_by_bitmask(4, EventIndication) + + @event_indication.setter + def event_indication(self, event_indication: EventIndication): + self._param._set_by_bitmask(event_indication.value, 4) + + +class DutCapsFlags(Param): + """ + Class `DutCapsFlags` defines the DUT capabilities as flags and allows setting: + - Voltage swing level 3 (1.2V) supported `voltage_swing_supported`. + - Pre-emphasis level 3 (9.5dB) supported `pre_emphasis_supported`. + - Fixed timing DUT supported `fixed_timing_dut_supported`. + - Spread Spectrum supported `spread_spectrum_supported`. + - Video format change without LT supported `change_vf_without_lt_supported`. + - Lane count reduction without LT supported `lane_count_reduction_without_lt_supported`. + - E-DDC protocol supported `e_ddc_protocol_supported`. + - Audio Info Frame for 2 channel audio transmission supported `audio_transmission_supported`. + - Define that DUT is Type-C device `dut_is_type_c_device`. + - FEC supported `fec_supported`. + - FEC disable sequence supported `fec_disable_sequence_supported`. + - Audio without Video supported `audio_without_video_supported`. + - DSC supported `dsc_supported`. + - DSC block prediction supported `dsc_block_prediction_supported`. + - DisplayID Type VII Detailed Timing Descriptor supported `display_id_vii_supported`. + - DisplayID Type VIII Detailed Timing Descriptor supported `display_id_viii_supported`. + - DisplayID Type IX Detailed Timing Descriptor supported `display_id_ix_supported`. + - DisplayID Type X Detailed Timing Descriptor supported `display_id_x_supported`. + - 2x1 tiled display and DisplayID Tiled Display Topology data block supported `display_id_tiled_display_topology`. + - Field sequential stereo and DisplayID Tiled Stereo Display Interface data block supported + `display_id_tiled_stereo_display` + - Stacked frame stereo and DisplayID Tiled Stereo Display Interface data block supported + `stacked_frame_stereo_supported`. + """ + def __init__(self, json_obj): + super().__init__(json_obj) + + @property + def voltage_swing_supported(self) -> bool: + """ + Set and get Voltage swing level flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(0, bool) + + @voltage_swing_supported.setter + def voltage_swing_supported(self, voltage_swing_supported: bool): + self._set_by_bitmask(voltage_swing_supported, 0) + + + @property + def pre_emphasis_supported(self) -> bool: + """ + Set and get Pre-emphasis level flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(1, bool) + + @pre_emphasis_supported.setter + def pre_emphasis_supported(self, pre_emphasis_supported: bool): + self._set_by_bitmask(pre_emphasis_supported, 1) + + @property + def fixed_timing_dut_supported(self) -> bool: + """ + Set and get Fixed timing DUT flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(2, bool) + + @fixed_timing_dut_supported.setter + def fixed_timing_dut_supported(self, fixed_timing_dut_supported: bool): + self._set_by_bitmask(fixed_timing_dut_supported, 2) + + @property + def spread_spectrum_supported(self) -> bool: + """ + Set and get Spread Spectrum flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(3, bool) + + @spread_spectrum_supported.setter + def spread_spectrum_supported(self, spread_spectrum_supported: bool): + self._set_by_bitmask(spread_spectrum_supported, 3) + + @property + def change_vf_without_lt_supported(self) -> bool: + """ + Set and get Video format change without LT flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(4, bool) + + @change_vf_without_lt_supported.setter + def change_vf_without_lt_supported(self, change_vf_without_lt_supported: bool): + self._set_by_bitmask(change_vf_without_lt_supported, 4) + + @property + def lane_count_reduction_without_lt_supported(self) -> bool: + """ + Set and get Lane count reduction without LT flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(5, bool) + + @lane_count_reduction_without_lt_supported.setter + def lane_count_reduction_without_lt_supported(self, lane_count_reduction_without_lt_supported: bool): + self._set_by_bitmask(lane_count_reduction_without_lt_supported, 5) + + @property + def e_ddc_protocol_supported(self) -> bool: + """ + Set and get E-DDC protocol flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(6, bool) + + @e_ddc_protocol_supported.setter + def e_ddc_protocol_supported(self, e_ddc_protocol_supported: bool): + self._set_by_bitmask(e_ddc_protocol_supported, 6) + + @property + def audio_transmission_supported(self) -> bool: + """ + Set and get Audio Info Frame for 2 channel audio transmission flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(7, bool) + + @audio_transmission_supported.setter + def audio_transmission_supported(self, audio_transmission_supported: bool): + self._set_by_bitmask(audio_transmission_supported, 7) + + @property + def dut_is_type_c_device(self) -> bool: + """ + Set and get Define that DUT is Type-C device flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(8, bool) + + @dut_is_type_c_device.setter + def dut_is_type_c_device(self, dut_is_type_c_device: bool): + self._set_by_bitmask(dut_is_type_c_device, 8) + + @property + def fec_supported(self) -> bool: + """ + Set and get FEC flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(9, bool) + + @fec_supported.setter + def fec_supported(self, fec_supported: bool): + self._set_by_bitmask(fec_supported, 9) + + @property + def fec_disable_sequence_supported(self) -> bool: + """ + Set and get FEC disable sequence flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(10, bool) + + @fec_disable_sequence_supported.setter + def fec_disable_sequence_supported(self, fec_disable_sequence_supported: bool): + self._set_by_bitmask(fec_disable_sequence_supported, 10) + + @property + def audio_without_video_supported(self) -> bool: + """ + Set and get Audio without Video flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(11, bool) + + @audio_without_video_supported.setter + def audio_without_video_supported(self, audio_without_video_supported: bool): + self._set_by_bitmask(audio_without_video_supported, 11) + + @property + def dsc_supported(self) -> bool: + """ + Set and get DSC flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(12, bool) + + @dsc_supported.setter + def dsc_supported(self, dsc_supported: bool): + self._set_by_bitmask(dsc_supported, 12) + + @property + def dsc_block_prediction_supported(self) -> bool: + """ + Set and get DSC block prediction flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(13, bool) + + @dsc_block_prediction_supported.setter + def dsc_block_prediction_supported(self, dsc_block_prediction_supported: bool): + self._set_by_bitmask(dsc_block_prediction_supported, 13) + + @property + def native_display_id_read(self) -> bool: + """ + Set and get DUT supports native Display ID read flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(14, bool) + + @native_display_id_read.setter + def native_display_id_read(self, native_display_id_read: bool): + self._set_by_bitmask(native_display_id_read, 14) + + @property + def display_id_vii_supported(self) -> bool: + """ + Set and get DisplayID Type VII Detailed Timing Descriptor flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(15, bool) + + @display_id_vii_supported.setter + def display_id_vii_supported(self, display_id_vii_supported: bool): + self._set_by_bitmask(display_id_vii_supported, 15) + + @property + def display_id_viii_supported(self) -> bool: + """ + Set and get DisplayID Type VIII Detailed Timing Descriptor flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(16, bool) + + @display_id_viii_supported.setter + def display_id_viii_supported(self, display_id_viii_supported: bool): + self._set_by_bitmask(display_id_viii_supported, 16) + + @property + def display_id_ix_supported(self) -> bool: + """ + Set and get DisplayID Type IX Detailed Timing Descriptor flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(17, bool) + + @display_id_ix_supported.setter + def display_id_ix_supported(self, display_id_ix_supported: bool): + self._set_by_bitmask(display_id_ix_supported, 17) + + @property + def display_id_x_supported(self) -> bool: + """ + Set and get DisplayID Type X Detailed Timing Descriptor flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(18, bool) + + @display_id_x_supported.setter + def display_id_x_supported(self, display_id_x_supported: bool): + self._set_by_bitmask(display_id_x_supported, 18) + + @property + def display_id_tiled_display_topology(self) -> bool: + """ + Set and get 2x1 tiled display and DisplayID Tiled Display Topology data block flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(19, bool) + + @display_id_tiled_display_topology.setter + def display_id_tiled_display_topology(self, display_id_tiled_display_topology: bool): + self._set_by_bitmask(display_id_tiled_display_topology, 19) + + @property + def display_id_tiled_stereo_display(self) -> bool: + """ + Set and get Field sequential stereo and DisplayID Tiled Stereo Display Interface data block flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(20, bool) + + @display_id_tiled_stereo_display.setter + def display_id_tiled_stereo_display(self, display_id_tiled_stereo_display: bool): + self._set_by_bitmask(display_id_tiled_stereo_display, 20) + + @property + def stacked_frame_stereo_supported(self) -> bool: + """ + Set and get Stacked frame stereo and DisplayID Tiled Stereo Display Interface data flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(21, bool) + + @stacked_frame_stereo_supported.setter + def stacked_frame_stereo_supported(self, stacked_frame_stereo_supported: bool): + self._set_by_bitmask(stacked_frame_stereo_supported, 21) + + def clear_all_caps(self): + """ + Clear all settings. + """ + self.voltage_swing_supported = False + self.pre_emphasis_supported = False + self.fixed_timing_dut_supported = False + self.spread_spectrum_supported = False + self.change_vf_without_lt_supported = False + self.lane_count_reduction_without_lt_supported = False + self.e_ddc_protocol_supported = False + self.audio_transmission_supported = False + self.dut_is_type_c_device = False + self.fec_supported = False + self.fec_disable_sequence_supported = False + self.audio_without_video_supported = False + self.dsc_supported = False + self.dsc_block_prediction_supported = False + self.native_display_id_read = False + self.display_id_vii_supported = False + self.display_id_viii_supported = False + self.display_id_ix_supported = False + self.display_id_x_supported = False + self.display_id_tiled_display_topology = False + self.display_id_tiled_stereo_display = False + self.stacked_frame_stereo_supported = False + + +class DutCapsDp14: + """ + Class `DutCapsDp14` defines the DUT capabilities and allows setting: + - Defines the maximum number of lanes supported by the DUT `max_lanes`. + - Maximum link rate supported by the DUT `max_link_rate`. + - Dut capabilities flags `dut_caps_flags` type `DutCapsFlags`. + """ + def __init__(self, json_obj): + self.__max_lanes = Param(json_obj["TSI_DP14_SRCCTS_MAX_LANES"]) + self.__max_link_rate = Param(json_obj["TSI_DP14_SRCCTS_MAX_LINK_RATE"]) + self.__dut_caps_flags = DutCapsFlags(json_obj["TSI_DP14_SRCCTS_DUT_CAPS"]) + + @property + def max_lanes(self) -> int: + """ + Set and get number of maximum lanes. + + Returns: + object of int type + """ + return self.__max_lanes.default_value + + @max_lanes.setter + def max_lanes(self, max_lanes: int): + if max_lanes not in [1, 2, 4]: + raise ValueError(f"Max lane count must be from list: 1, 2, 4. Current value: {max_lanes}") + self.__max_lanes.default_value = max_lanes + + @property + def max_link_rate(self): + """ + Set and get number of maximum link rate. + + Returns: + object of int type + """ + return self.__max_link_rate + + @max_link_rate.setter + def max_link_rate(self, max_link_rate: float): + if max_link_rate not in [1.62, 2.7, 5.4, 8.1]: + raise ValueError(f"Max link rate must be from list: 1.62, 2.7, 5.4, 8.1. Current value: {max_link_rate}") + self.__max_link_rate.default_value = round(max_link_rate / 0.27) + + @property + def dut_caps_flags(self) -> DutCapsFlags: + """ + Set and get DUT capabilities flags. + + Returns: + object of `DutCapsFlags` type + """ + return self.__dut_caps_flags + + @dut_caps_flags.setter + def dut_caps_flags(self, dut_caps_flags: DutCapsFlags): + self.__dut_caps_flags = dut_caps_flags + + +class TimeStampGenerationDp14: + """ + Class `TimeStampGenerationDp14` defines timings which used for different rates and allows setting: + - Video timings used for Reduced Bitrate (RBR, 1.62Gbps) testing for 1 lanes `rbb_1l`. + - Video timings used for Reduced Bitrate (RBR, 1.62Gbps) testing for 2 lanes `rbb_2l`. + - Video timings used for Reduced Bitrate (RBR, 1.62Gbps) testing for 4 lanes `rbb_4l`. + - Video timings used for High Bitrate (HBR, 2.7Gbps) testing for 1 lanes `hbr_1l`. + - Video timings used for High Bitrate (HBR, 2.7Gbps) testing for 2 lanes `hbr_2l`. + - Video timings used for High Bitrate (HBR, 2.7Gbps) testing for 4 lanes `hbr_4l`. + - Video timings used for High Bitrate-2 (HBR2, 5.4Gbps) testing for 1 lane `hbr2_1l`. + - Video timings used for High Bitrate-2 (HBR2, 5.4Gbps) testing for 2 lane `hbr2_2l`. + - Video timings used for High Bitrate-2 (HBR2, 5.4Gbps) testing for 4 lane `hbr2_4l`. + - Video timings used for High Bitrate-3 (HBR3, 8.1Gbps) testing for 1 lanes `hbr3_1l`. + - Video timings used for High Bitrate-3 (HBR3, 8.1Gbps) testing for 2 lanes `hbr3_2l`. + - Video timings used for High Bitrate-3 (HBR3, 8.1Gbps) testing for 4 lanes `hbr3_4l`. + """ + def __init__(self, json_obj): + self.__rbb_1l = Param(json_obj["TSI_DP14_SRCCTS_1L_RBB"]) + self.__rbb_2l = Param(json_obj["TSI_DP14_SRCCTS_2L_RBB"]) + self.__rbb_4l = Param(json_obj["TSI_DP14_SRCCTS_4L_RBB"]) + + self.__hbr_1l = Param(json_obj["TSI_DP14_SRCCTS_1L_HBR"]) + self.__hbr_2l = Param(json_obj["TSI_DP14_SRCCTS_2L_HBR"]) + self.__hbr_4l = Param(json_obj["TSI_DP14_SRCCTS_4L_HBR"]) + + self.__hbr2_1l = Param(json_obj["TSI_DP14_SRCCTS_1L_HBR2"]) + self.__hbr2_2l = Param(json_obj["TSI_DP14_SRCCTS_2L_HBR2"]) + self.__hbr2_4l = Param(json_obj["TSI_DP14_SRCCTS_4L_HBR2"]) + + self.__hbr3_1l = Param(json_obj["TSI_DP14_SRCCTS_1L_HBR3"]) + self.__hbr3_2l = Param(json_obj["TSI_DP14_SRCCTS_2L_HBR3"]) + self.__hbr3_4l = Param(json_obj["TSI_DP14_SRCCTS_4L_HBR3"]) + + @property + def rbb_1l(self): + """ + Set and get Video timings used for Reduced Bitrate (RBR, 1.62Gbps) testing for 1 lanes. + + Returns: + object of `Rbb1Lane` type + """ + return self.__rbb_1l + + @rbb_1l.setter + def rbb_1l(self, rbb_1l: Rbb1Lane): + self.__rbb_1l.default_value = rbb_1l.value + + @property + def rbb_2l(self): + """ + Set and get Video timings used for Reduced Bitrate (RBR, 1.62Gbps) testing for 2 lanes. + + Returns: + object of `Rbb2Lane` type + """ + return self.__rbb_2l + + @rbb_2l.setter + def rbb_2l(self, rbb_2l: Rbb2Lane): + self.__rbb_2l.default_value = rbb_2l.value + + @property + def rbb_4l(self): + """ + Set and get Video timings used for Reduced Bitrate (RBR, 1.62Gbps) testing for 4 lanes. + + Returns: + object of `Rbb4Lane` type + """ + return self.__rbb_4l + + @rbb_4l.setter + def rbb_4l(self, rbb_4l: Rbb4Lane): + self.__rbb_4l.default_value = rbb_4l.value + + @property + def hbr_1l(self): + """ + Set and get Video timings used for High Bitrate (HBR, 2.7Gbps) testing for 1 lanes. + + Returns: + object of `Hbr1Lane` type + """ + return self.__hbr_1l + + @hbr_1l.setter + def hbr_1l(self, hbr_1l: Hbr1Lane): + self.__hbr_1l.default_value = hbr_1l.value + + @property + def hbr_2l(self): + """ + Set and get Video timings used for High Bitrate (HBR, 2.7Gbps) testing for 2 lanes. + + Returns: + object of `Hbr2Lane` type + """ + return self.__hbr_2l + + @hbr_2l.setter + def hbr_2l(self, hbr_2l: Hbr2Lane): + self.__hbr_2l.default_value = hbr_2l.value + + @property + def hbr_4l(self): + """ + Set and get Video timings used for High Bitrate (HBR, 2.7Gbps) testing for 4 lanes. + + Returns: + object of `Hbr4Lane` type + """ + return self.__hbr_4l + + @hbr_4l.setter + def hbr_4l(self, hbr_4l: Hbr4Lane): + self.__hbr_4l.default_value = hbr_4l.value + + @property + def hbr2_1l(self): + """ + Set and get Video timings used for High Bitrate-2 (HBR2, 5.4Gbps) testing for 1 lane. + + Returns: + object of `Hbr2_1Lane` type + """ + return self.__hbr2_1l + + @hbr2_1l.setter + def hbr2_1l(self, hbr2_1l: Hbr2_1Lane): + self.__hbr2_1l.default_value = hbr2_1l.value + + @property + def hbr2_2l(self): + """ + Set and get Video timings used for High Bitrate-2 (HBR2, 5.4Gbps) testing for 2 lane. + + Returns: + object of `Hbr2_2Lane` type + """ + return self.__hbr2_2l + + @hbr2_2l.setter + def hbr2_2l(self, hbr2_2l: Hbr2_2Lane): + self.__hbr2_2l.default_value = hbr2_2l.value + + @property + def hbr2_4l(self): + """ + Set and get Video timings used for High Bitrate-2 (HBR2, 5.4Gbps) testing for 4 lane. + + Returns: + object of `Hbr2_4Lane` type + """ + return self.__hbr2_4l + + @hbr2_4l.setter + def hbr2_4l(self, hbr2_4l: Hbr2_4Lane): + self.__hbr2_4l.default_value = hbr2_4l.value + + @property + def hbr3_1l(self): + """ + Set and get Video timings used for High Bitrate-3 (HBR3, 8.1Gbps) testing for 1 lanes. + + Returns: + object of `Hbr3_1Lane` type + """ + return self.__hbr3_1l + + @hbr3_1l.setter + def hbr3_1l(self, hbr3_1l: Hbr3_1Lane): + self.__hbr3_1l.default_value = hbr3_1l.value + + @property + def hbr3_2l(self): + """ + Set and get Video timings used for High Bitrate-3 (HBR3, 8.1Gbps) testing for 2 lanes. + + Returns: + object of `Hbr3_2Lane` type + """ + return self.__hbr3_2l + + @hbr3_2l.setter + def hbr3_2l(self, hbr3_2l: Hbr3_2Lane): + self.__hbr3_2l.default_value = hbr3_2l.value + + @property + def hbr3_4l(self): + """ + Set and get Video timings used for High Bitrate-3 (HBR3, 8.1Gbps) testing for 4 lanes. + + Returns: + object of `Hbr3_4Lane` type + """ + return self.__hbr3_4l + + @hbr3_4l.setter + def hbr3_4l(self, hbr3_4l: Hbr3_4Lane): + self.__hbr3_4l.default_value = hbr3_4l.value + + +class ColorimetryDp14: + """ + Class `ColorimetryDp14` describes optional and additional color modes to be used with DP CTS tests. + - RGB 6 bpc VESA and RGB 8 bpc VESA - Mandatory. + - RGB 10 bpc VESA `rgb_10bpc_vesa`. + - RGB 8 bpc CTA `rgb_8bpc_cta`. + - RGB 10 bpc CTA `rgb_10bpc_cta`. + - YCbCr-422 8 bpc CTA ITU-601 `ycbcr422_8bpc_cta_itu601`. + - YCbCr-422 10 bpc CTA ITU-601 `ycbcr422_10bpc_cta_itu601`. + - YCbCr-422 8 bpc CTA ITU-709 `ycbcr422_8bpc_cta_itu709`. + - YCbCr-422 10 bpc CTA ITU-709 `ycbcr422_10bpc_cta_itu709`. + - YCbCr-420 8 bpc CTA ITU-601 `ycbcr420_8bpc_cta_itu601`. + - YCbCr-420 10 bpc CTA ITU-601 `ycbcr420_10bpc_cta_itu601`. + - YCbCr-420 8 bpc CTA ITU-709 `ycbcr420_8bpc_cta_itu709`. + - YCbCr-420 10 bpc CTA ITU-709 `ycbcr420_10bpc_cta_itu709`. + - YCbCr-444 8 bpc CTA ITU-601 `ycbcr444_8bpc_cta_itu601`. + - YCbCr-444 10 bpc CTA ITU-601 `ycbcr444_10bpc_cta_itu601`. + - YCbCr-444 8 bpc CTA ITU-709 `ycbcr444_8bpc_cta_itu709`. + - YCbCr-444 10 bpc CTA ITU-709 `ycbcr444_10bpc_cta_itu709`. + """ + class __ColorMode: + + def __init__(self, value: int): + self.value = value + self.state = False + + def __init__(self, json_obj): + self.__color_mode_count = Param(json_obj["TSI_DP14_SRCCTS_COLOR_FORMATS"]) + self.__rgb_6bpc_vesa = Param(json_obj["TSI_DP14_SRCCTS_COLOR_MODE_0"]) + self.__rgb_8bpc_vesa = Param(json_obj["TSI_DP14_SRCCTS_COLOR_MODE_1"]) + self.__rgb_10bpc_vesa = self.__ColorMode(make_cf(CMTFormat.RGB_LEGACY, CMTBitDepth.BPC_10)) + self.__rgb_8bpc_cta = self.__ColorMode(make_cf(CMTFormat.RGB_CEA, CMTBitDepth.BPC_8)) + self.__rgb_10bpc_cta = self.__ColorMode(make_cf(CMTFormat.RGB_CEA, CMTBitDepth.BPC_10)) + self.__ycbcr422_8bpc_cta_itu601 = self.__ColorMode(make_cf(CMTFormat.YCBCR_422_BT601, CMTBitDepth.BPC_8)) + self.__ycbcr422_10bpc_cta_itu601 = self.__ColorMode(make_cf(CMTFormat.YCBCR_422_BT601, CMTBitDepth.BPC_10)) + self.__ycbcr422_8bpc_cta_itu709 = self.__ColorMode(make_cf(CMTFormat.YCBCR_422_BT709, CMTBitDepth.BPC_8)) + self.__ycbcr422_10bpc_cta_itu709 = self.__ColorMode(make_cf(CMTFormat.YCBCR_422_BT709, CMTBitDepth.BPC_10)) + self.__ycbcr444_8bpc_cta_itu601 = self.__ColorMode(make_cf(CMTFormat.YCBCR_444_BT601, CMTBitDepth.BPC_8)) + self.__ycbcr444_10bpc_cta_itu601 = self.__ColorMode(make_cf(CMTFormat.YCBCR_444_BT601, CMTBitDepth.BPC_10)) + self.__ycbcr444_8bpc_cta_itu709 = self.__ColorMode(make_cf(CMTFormat.YCBCR_444_BT709, CMTBitDepth.BPC_8)) + self.__ycbcr444_10bpc_cta_itu709 = self.__ColorMode(make_cf(CMTFormat.YCBCR_444_BT709, CMTBitDepth.BPC_10)) + + self.__color_mode_count.default_value = 2 + self.__queue = [ + Param(json_obj["TSI_DP14_SRCCTS_COLOR_MODE_2"]), + Param(json_obj["TSI_DP14_SRCCTS_COLOR_MODE_3"]), + Param(json_obj["TSI_DP14_SRCCTS_COLOR_MODE_4"]), + Param(json_obj["TSI_DP14_SRCCTS_COLOR_MODE_5"]), + Param(json_obj["TSI_DP14_SRCCTS_COLOR_MODE_6"]), + Param(json_obj["TSI_DP14_SRCCTS_COLOR_MODE_7"]), + Param(json_obj["TSI_DP14_SRCCTS_COLOR_MODE_8"]), + Param(json_obj["TSI_DP14_SRCCTS_COLOR_MODE_9"]), + Param(json_obj["TSI_DP14_SRCCTS_COLOR_MODE_10"]), + Param(json_obj["TSI_DP14_SRCCTS_COLOR_MODE_11"]), + Param(json_obj["TSI_DP14_SRCCTS_COLOR_MODE_12"]) + ] + + def __find_index_by_value(self, value: int) -> int: + for index, item in enumerate(self.__queue): + if item.default_value == value: + return index + return -1 + + def __last_free_space_in_queue(self) -> int: + return self.__find_index_by_value(0) + + def __shift_values_in_queue(self, index: int): + for i in range(index, len(self.__queue) - 1): + self.__queue[i].default_value = self.__queue[i + 1].default_value + + def __check_color_mode(self, color_mode): + if color_mode.state: + if self.__find_index_by_value(color_mode.value) == -1: + self.__queue[self.__last_free_space_in_queue()].default_value = color_mode.value + self.__color_mode_count.default_value += 1 + else: + if self.__find_index_by_value(color_mode.value) != -1: + index = self.__find_index_by_value(color_mode.value) + self.__queue[index].default_value = 0 + self.__shift_values_in_queue(index) + if self.__color_mode_count.default_value >= 3: + self.__color_mode_count.default_value -= 1 + + @property + def rgb_10bpc_vesa(self) -> bool: + """ + Set and get RGB 10 bpc VESA flag support. + + Returns: + object of bool type + """ + return self.__rgb_10bpc_vesa.state + + @rgb_10bpc_vesa.setter + def rgb_10bpc_vesa(self, rgb_10bpc_vesa: bool): + if self.__rgb_10bpc_vesa.state != rgb_10bpc_vesa: + self.__rgb_10bpc_vesa.state = rgb_10bpc_vesa + self.__check_color_mode(self.__rgb_10bpc_vesa) + + @property + def rgb_8bpc_cta(self) -> bool: + """ + Set and get RGB 8 bpc CTA flag support. + + Returns: + object of bool type + """ + return self.__rgb_8bpc_cta.state + + @rgb_8bpc_cta.setter + def rgb_8bpc_cta(self, rgb_8bpc_cta: bool): + if self.__rgb_8bpc_cta.state != rgb_8bpc_cta: + self.__rgb_8bpc_cta.state = rgb_8bpc_cta + self.__check_color_mode(self.__rgb_8bpc_cta) + + @property + def rgb_10bpc_cta(self) -> bool: + """ + Set and get RGB 10 bpc CTA flag support. + + Returns: + object of bool type + """ + return self.__rgb_10bpc_cta.state + + @rgb_10bpc_cta.setter + def rgb_10bpc_cta(self, rgb_10bpc_cta: bool): + if self.__rgb_10bpc_cta.state != rgb_10bpc_cta: + self.__rgb_10bpc_cta.state = rgb_10bpc_cta + self.__check_color_mode(self.__rgb_10bpc_cta) + + @property + def ycbcr422_8bpc_cta_itu601(self) -> bool: + """ + Set and get YCbCr-422 8 bpc CTA ITU-601 flag support. + + Returns: + object of bool type + """ + return self.__ycbcr422_8bpc_cta_itu601.state + + @ycbcr422_8bpc_cta_itu601.setter + def ycbcr422_8bpc_cta_itu601(self, ycbcr422_8bpc_cta_itu601: bool): + if self.__ycbcr422_8bpc_cta_itu601.state != ycbcr422_8bpc_cta_itu601: + self.__ycbcr422_8bpc_cta_itu601.state = ycbcr422_8bpc_cta_itu601 + self.__check_color_mode(self.__ycbcr422_8bpc_cta_itu601) + + @property + def ycbcr422_10bpc_cta_itu601(self) -> bool: + """ + Set and get YCbCr-422 10 bpc CTA ITU-601 flag support. + + Returns: + object of bool type + """ + return self.__ycbcr422_10bpc_cta_itu601.state + + @ycbcr422_10bpc_cta_itu601.setter + def ycbcr422_10bpc_cta_itu601(self, ycbcr422_10bpc_cta_itu601: bool): + if self.__ycbcr422_10bpc_cta_itu601.state != ycbcr422_10bpc_cta_itu601: + self.__ycbcr422_10bpc_cta_itu601.state = ycbcr422_10bpc_cta_itu601 + self.__check_color_mode(self.__ycbcr422_10bpc_cta_itu601) + + @property + def ycbcr422_8bpc_cta_itu709(self) -> bool: + """ + Set and get YCbCr-422 8 bpc CTA ITU-709 flag support. + + Returns: + object of bool type + """ + return self.__ycbcr422_8bpc_cta_itu709.state + + @ycbcr422_8bpc_cta_itu709.setter + def ycbcr422_8bpc_cta_itu709(self, ycbcr422_8bpc_cta_itu709: bool): + if self.__ycbcr422_8bpc_cta_itu709.state != ycbcr422_8bpc_cta_itu709: + self.__ycbcr422_8bpc_cta_itu709.state = ycbcr422_8bpc_cta_itu709 + self.__check_color_mode(self.__ycbcr422_8bpc_cta_itu709) + + @property + def ycbcr422_10bpc_cta_itu709(self) -> bool: + """ + Set and get YCbCr-422 10 bpc CTA ITU-709 flag support. + + Returns: + object of bool type + """ + return self.__ycbcr422_10bpc_cta_itu709.state + + @ycbcr422_10bpc_cta_itu709.setter + def ycbcr422_10bpc_cta_itu709(self, ycbcr422_10bpc_cta_itu709: bool): + if self.__ycbcr422_10bpc_cta_itu709.state != ycbcr422_10bpc_cta_itu709: + self.__ycbcr422_10bpc_cta_itu709.state = ycbcr422_10bpc_cta_itu709 + self.__check_color_mode(self.__ycbcr422_10bpc_cta_itu709) + + @property + def ycbcr444_8bpc_cta_itu601(self) -> bool: + """ + Set and get YCbCr-444 8 bpc CTA ITU-601 flag support. + + Returns: + object of bool type + """ + return self.__ycbcr444_8bpc_cta_itu601.state + + @ycbcr444_8bpc_cta_itu601.setter + def ycbcr444_8bpc_cta_itu601(self, ycbcr444_8bpc_cta_itu601: bool): + if self.__ycbcr444_8bpc_cta_itu601.state != ycbcr444_8bpc_cta_itu601: + self.__ycbcr444_8bpc_cta_itu601.state = ycbcr444_8bpc_cta_itu601 + self.__check_color_mode(self.__ycbcr444_8bpc_cta_itu601) + + @property + def ycbcr444_10bpc_cta_itu601(self) -> bool: + """ + Set and get YCbCr-444 10 bpc CTA ITU-601 flag support. + + Returns: + object of bool type + """ + return self.__ycbcr444_10bpc_cta_itu601.state + + @ycbcr444_10bpc_cta_itu601.setter + def ycbcr444_10bpc_cta_itu601(self, ycbcr444_10bpc_cta_itu601: bool): + if self.__ycbcr444_10bpc_cta_itu601.state != ycbcr444_10bpc_cta_itu601: + self.__ycbcr444_10bpc_cta_itu601.state = ycbcr444_10bpc_cta_itu601 + self.__check_color_mode(self.__ycbcr444_10bpc_cta_itu601) + + @property + def ycbcr444_8bpc_cta_itu709(self) -> bool: + """ + Set and get YCbCr-444 8 bpc CTA ITU-709 flag support. + + Returns: + object of bool type + """ + return self.__ycbcr444_8bpc_cta_itu709.state + + @ycbcr444_8bpc_cta_itu709.setter + def ycbcr444_8bpc_cta_itu709(self, ycbcr444_8bpc_cta_itu709: bool): + if self.__ycbcr444_8bpc_cta_itu709.state != ycbcr444_8bpc_cta_itu709: + self.__ycbcr444_8bpc_cta_itu709.state = ycbcr444_8bpc_cta_itu709 + self.__check_color_mode(self.__ycbcr444_8bpc_cta_itu709) + + @property + def ycbcr444_10bpc_cta_itu709(self) -> bool: + """ + Set and get YCbCr-444 10 bpc CTA ITU-709 flag support. + + Returns: + object of bool type + """ + return self.__ycbcr444_10bpc_cta_itu709.state + + @ycbcr444_10bpc_cta_itu709.setter + def ycbcr444_10bpc_cta_itu709(self, ycbcr444_10bpc_cta_itu709: bool): + if self.__ycbcr444_10bpc_cta_itu709.state != ycbcr444_10bpc_cta_itu709: + self.__ycbcr444_10bpc_cta_itu709.state = ycbcr444_10bpc_cta_itu709 + self.__check_color_mode(self.__ycbcr444_10bpc_cta_itu709) + + def clear_all(self): + """ + Clear all settings. + """ + self.rgb_10bpc_vesa = False + self.rgb_8bpc_cta = False + self.rgb_10bpc_cta = False + self.ycbcr422_8bpc_cta_itu601 = False + self.ycbcr422_10bpc_cta_itu601 = False + self.ycbcr422_8bpc_cta_itu709 = False + self.ycbcr422_10bpc_cta_itu709 = False + self.ycbcr444_8bpc_cta_itu601 = False + self.ycbcr444_10bpc_cta_itu601 = False + self.ycbcr444_8bpc_cta_itu709 = False + self.ycbcr444_10bpc_cta_itu709 = False + + for item in self.__queue: + item.default_value = 0 + + def select_all(self): + """ + Select all modes. + """ + self.rgb_10bpc_vesa = True + self.rgb_8bpc_cta = True + self.rgb_10bpc_cta = True + self.ycbcr422_8bpc_cta_itu601 = True + self.ycbcr422_10bpc_cta_itu601 = True + self.ycbcr422_8bpc_cta_itu709 = True + self.ycbcr422_10bpc_cta_itu709 = True + self.ycbcr444_8bpc_cta_itu601 = True + self.ycbcr444_10bpc_cta_itu601 = True + self.ycbcr444_8bpc_cta_itu709 = True + self.ycbcr444_10bpc_cta_itu709 = True + + +class DebugOptions: + + def __init__(self, json_obj): + self.__param = Param(json_obj["TSI_DP14_SRCCTS_DUT_DEBUG_CONF"]) + + @property + def continue_on_fail(self) -> bool: + """ + Set and get continue on fail flag. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(0, bool) + + @continue_on_fail.setter + def continue_on_fail(self, continue_on_fail: bool): + self.__param.default_value = update_default_value(value=continue_on_fail, + default_value=self.__param.default_value, + mask=self.__param.bit_field_list[0].mask) + + +class GeneralSourceDUTDp14SettingTab: + """ + Class `GeneralSourceDUTDp14SettingTab` allows working with parameters from General source part. + - Set and get timeout `timeout`. + - Set and get DUT capabilities `dut_caps` type `DutCapsDp14`. + - Set and get test automation flags `test_automation` type `TestAutomationFlags`. + - Set and get HPD pulse duration `hpd_pulse_duration`. + - Set and get link training start timeout `lt_start_timeout`. + - Set and get test cycle delay `test_cycle_delay`. + - Get fail safe mode `fail_safe_mode`. + - Set and get maximum supported video modes `max_supported_video_mode` type `MaxSupportedVideoMode`. + - Set and get packed timings for 1 lane `packed_timings_1_lane` type `PackedTimings1Lane`. + - Set and get packed timings for 2 lane `packed_timings_2_lane` type `PackedTimings2Lane`. + - Set and get packed timings for 4 lane `packed_timings_4_lane` type `PackedTimings4Lane`. + - Set and get time stamp generation `time_stamp_generation` type `TimeStampGenerationDp14`. + - Set and get colorimetry modes `colorimetry` type `ColorimetryDp14`. + - Set and get debug options `debug_options` type `DebugOptions` + """ + def __init__(self, json_obj): + self.__timeout = Param(json_obj["TSI_DP14_SRCCTS_TIMEOUT"]) + self.__dut_caps = DutCapsDp14(json_obj) + self.__test_automation = TestAutomationFlags(json_obj["TSI_DP14_SRCCTS_DUT_TA"]) + self.__hpd_pulse_duration = Param(json_obj["TSI_DP14_SRCCTS_LONG_HPD_PULSE"]) + self.__lt_start_timeout = Param(json_obj["TSI_DP14_SRCCTS_LT_START_TIMEOUT"]) + self.__test_cycle_delay = Param(json_obj["TSI_DP14_SRCCTS_TEST_CYCLE_DELAY"]) + self.__fail_safe_mode = Param(json_obj["TSI_DP14_SRCCTS_FAILSAFE_MODE"]) + self.__max_supported_video_mode = Param(json_obj["TSI_DP14_SRCCTS_MAX_RESOLUTION"]) # MaxSupportedVideoMode Enum + self.__packed_timings_1_lane = Param(json_obj["TSI_DP14_SRCCTS_1L_MOST_PACKED"]) # PackedTimings1Lane + self.__packed_timings_2_lane = Param(json_obj["TSI_DP14_SRCCTS_2L_MOST_PACKED"]) # PackedTimings2Lane + self.__packed_timings_4_lane = Param(json_obj["TSI_DP14_SRCCTS_4L_MOST_PACKED"]) # PackedTimings4Lane + self.__time_stamp_generation = TimeStampGenerationDp14(json_obj) + self.__colorimetry = ColorimetryDp14(json_obj) + self.__debug_options = DebugOptions(json_obj) + + @property + def timeout(self) -> int: + """ + Set and get test timeout, in milliseconds. + + Returns: + object of int type + """ + return self.__timeout.default_value + + @timeout.setter + def timeout(self, timeout: int): + if not(self.__timeout.min_value < timeout < self.__timeout.max_value): + raise ValueError(f"Timeout cannot be less than {self.__timeout.min_value} and more than " + f"{self.__timeout.max_value}.") + self.__timeout.default_value = timeout + + @property + def dut_caps(self): + """ + Set and get DUT caps. + + Returns: + object DutCapsDp14 + """ + return self.__dut_caps + + @dut_caps.setter + def dut_caps(self, dut_caps: DutCapsDp14): + self.__dut_caps = dut_caps + + @property + def test_automation(self) -> TestAutomationFlags: + """ + Set and get test automation flags. + + Returns: + object TestAutomationFlags + """ + return self.__test_automation + + @test_automation.setter + def test_automation(self, test_automation: TestAutomationFlags): + self.__test_automation = test_automation + + @property + def hpd_pulse_duration(self) -> int: + """ + Set and get HPD pulse duration. + + Returns: + object of int type + """ + return self.__hpd_pulse_duration.default_value + + @hpd_pulse_duration.setter + def hpd_pulse_duration(self, hpd_pulse_duration: int): + if not(self.__hpd_pulse_duration.min_value < hpd_pulse_duration < self.__hpd_pulse_duration.max_value): + raise ValueError(f"HPD pulse duration cannot be less than {self.__timeout.min_value} and more than " + f"{self.__hpd_pulse_duration.max_value}.") + self.__hpd_pulse_duration.default_value = hpd_pulse_duration + + @property + def lt_start_timeout(self) -> int: + """ + Set and get link training start timeout. + + Returns: + object of int type + """ + return self.__lt_start_timeout.default_value + + @lt_start_timeout.setter + def lt_start_timeout(self, lt_start_timeout: int): + if not(self.__lt_start_timeout.min_value < lt_start_timeout < self.__lt_start_timeout.max_value): + raise ValueError(f"LT start timeout cannot be less than {self.__lt_start_timeout.min_value} and more than " + f"{self.__lt_start_timeout.max_value}.") + self.__lt_start_timeout.default_value = lt_start_timeout + + @property + def test_cycle_delay(self) -> int: + """ + Set and get test cycle delay. + + Returns: + object of int type + """ + return self.__test_cycle_delay.default_value + + @test_cycle_delay.setter + def test_cycle_delay(self, test_cycle_delay: int): + if not(self.__test_cycle_delay.min_value < test_cycle_delay < self.__test_cycle_delay.max_value): + raise ValueError(f"Test cycle delay cannot be less than {self.__test_cycle_delay.min_value} and more than " + f"{self.__test_cycle_delay.max_value}.") + self.__test_cycle_delay.default_value = test_cycle_delay + + @property + def fail_safe_mode(self): + """ + Get fail safe mode. + + Returns: + object of int type + """ + return self.__fail_safe_mode.default_value + + @property + def max_supported_video_mode(self): + """ + Set and get maximum supported video modes. + + Returns: + object MaxSupportedVideoMode + """ + return self.__max_supported_video_mode + + @max_supported_video_mode.setter + def max_supported_video_mode(self, max_supported_video_mode: MaxSupportedVideoMode): + self.__max_supported_video_mode.default_value = max_supported_video_mode.value + + @property + def packed_timings_1_lane(self): + """ + Set and get packed timings for 1 lane. + + Returns: + object PackedTimings1Lane + """ + return self.__packed_timings_1_lane + + @packed_timings_1_lane.setter + def packed_timings_1_lane(self, packed_timings_1_lane: PackedTimings1Lane): + self.__packed_timings_1_lane.default_value = packed_timings_1_lane.value + + @property + def packed_timings_2_lane(self): + """ + Set and get packed timings for 2 lane. + + Returns: + object PackedTimings2Lane + """ + return self.__packed_timings_2_lane + + @packed_timings_2_lane.setter + def packed_timings_2_lane(self, packed_timings_2_lane: PackedTimings2Lane): + self.__packed_timings_2_lane.default_value = packed_timings_2_lane.value + + @property + def packed_timings_4_lane(self): + """ + Set and get packed timings for 4 lane. + + Returns: + object PackedTimings4Lane + """ + return self.__packed_timings_4_lane + + @packed_timings_4_lane.setter + def packed_timings_4_lane(self, packed_timings_4_lane: PackedTimings4Lane): + self.__packed_timings_4_lane.default_value = packed_timings_4_lane.value + + @property + def time_stamp_generation(self): + """ + Set and get time stamp generation. + + Returns: + object TimeStampGenerationDp14 + """ + return self.__time_stamp_generation + + @time_stamp_generation.setter + def time_stamp_generation(self, time_stamp_generation: TimeStampGenerationDp14): + self.__time_stamp_generation = time_stamp_generation + + @property + def colorimetry(self) -> ColorimetryDp14: + """ + Set and get colorimetry modes. + + Returns: + object ColorimetryDp14 + """ + return self.__colorimetry + + @colorimetry.setter + def colorimetry(self, colorimetry: ColorimetryDp14): + self.__colorimetry = colorimetry + + @property + def debug_options(self) -> DebugOptions: + """ + Set and get debug options. + + Returns: + object of 'DebugOptions' type + """ + return self.__debug_options + + @debug_options.setter + def debug_options(self, debug_config: DebugOptions): + self.__debug_options = debug_config diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/dp_2_1_common_video_modes.py b/UniTAP/dev/modules/dut_tests/dut_default_params/dp_2_1_common_video_modes.py new file mode 100644 index 0000000..d6e75fa --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/dp_2_1_common_video_modes.py @@ -0,0 +1,288 @@ +from UniTAP.dev.modules.dut_tests.test_group_params_types import Param + + +class VideoModeStandard: + """ + Class `VideoModeStandard` describes available supported timings standard. + - CTA `cta` (enable/disable). + - DMT `dmt` (enable/disable). + - CVT `cvt` (enable/disable). + - CVT RB1 `cvt_rb1` (enable/disable). + - CVT RB2 `cvt_rb2` (enable/disable). + - OVT `ovt` (enable/disable). + - Set all standards `set_all`. + """ + def __init__(self, value: Param): + self.__value = value + + @property + def cta(self) -> bool: + """ + Set and get CTA flag support. + + Returns: + object of bool type + """ + return self.__value._get_by_bit_number(0) + + @property + def dmt(self) -> bool: + """ + Set and get DMT flag support. + + Returns: + object of bool type + """ + return self.__value._get_by_bit_number(1) + + @property + def cvt(self) -> bool: + """ + Set and get CVT flag support. + + Returns: + object of bool type + """ + return self.__value._get_by_bit_number(2) + + @property + def cvt_rb1(self) -> bool: + """ + Set and get RB1 flag support. + + Returns: + object of bool type + """ + return self.__value._get_by_bit_number(3) + + @property + def cvt_rb2(self) -> bool: + """ + Set and get RB2 flag support. + + Returns: + object of bool type + """ + return self.__value._get_by_bit_number(4) + + @property + def ovt(self) -> bool: + """ + Set and get OVT flag support. + + Returns: + object of bool type + """ + return self.__value._get_by_bit_number(5) + + @cta.setter + def cta(self, value: bool): + self.__value._set_by_bit_number(value, 1, 0) + + @dmt.setter + def dmt(self, value: bool): + self.__value._set_by_bit_number(value, 1, 1) + + @cvt.setter + def cvt(self, value: bool): + self.__value._set_by_bit_number(value, 1, 2) + + @cvt_rb1.setter + def cvt_rb1(self, value: bool): + self.__value._set_by_bit_number(value, 1, 3) + + @cvt_rb2.setter + def cvt_rb2(self, value: bool): + self.__value._set_by_bit_number(value, 1, 4) + + @ovt.setter + def ovt(self, value: bool): + self.__value._set_by_bit_number(value, 1, 5) + + def set_all(self): + self.cta = True + self.dmt = True + self.cvt = True + self.cvt_rb1 = True + self.cvt_rb2 = True + self.ovt = True + + def clear_all(self): + self.cta = False + self.dmt = False + self.cvt = False + self.cvt_rb1 = False + self.cvt_rb2 = False + self.ovt = False + + +class ColorimetryModes: + """ + Class `ColorimetryModes` describes available supported colorimetry. + - RGB `rgb` (enable/disable, set bcp). + - YCbCr 444 `ycbcr444` (enable/disable, set bcp). + - YCbCr 422 `ycbcr422` (enable/disable, set bcp). + - YCbCr Simple 422 `ycbcr_simple422` (enable/disable, set bcp). + - YCbCr 420 `ycbcr420` (enable/disable, set bcp). + """ + + __COLORIMETRY_VALUES = {8: 1, 10: 3, 12: 7, 16: 15} + __COLORIMETRY_VALUES_MAP = {0: 0, 1: 8, 3: 10, 7: 12, 15: 16} + __AVAILABLE_BPC_VALUES = [0, 8, 10, 12, 16] + __STR_AVAILABLE_BPC_VALUES = [f" {_str}" for _str in __AVAILABLE_BPC_VALUES] + + def __init__(self, value: Param): + self.__value = value + + @property + def rgb(self) -> int: + """ + Set and get RGB bpc value. + + Returns: + object of int type + """ + return self.__COLORIMETRY_VALUES_MAP.get((self.__value.default_value >> 12) & 0xF) + + @property + def ycbcr444(self) -> int: + """ + Set and get YCbCr 444 bpc value. + + Returns: + object of int type + """ + return self.__COLORIMETRY_VALUES_MAP.get((self.__value.default_value >> 16) & 0xF) + + @property + def ycbcr422(self) -> int: + """ + Set and get YCbCr 422 bpc value. + + Returns: + object of int type + """ + return self.__COLORIMETRY_VALUES_MAP.get((self.__value.default_value >> 20) & 0xF) + + @property + def ycbcr_simple422(self) -> int: + """ + Set and get YCbCr Simple 422 bpc value. + + Returns: + object of bool type + """ + return self.__COLORIMETRY_VALUES_MAP.get((self.__value.default_value >> 24) & 0xF) + + @property + def ycbcr420(self) -> int: + """ + Set and get YCbCr 420 bpc value. + + Returns: + object of bool type + """ + return self.__COLORIMETRY_VALUES_MAP.get((self.__value.default_value >> 28) & 0xF) + + @rgb.setter + def rgb(self, value: int): + if value not in self.__AVAILABLE_BPC_VALUES: + raise ValueError(f'Incorrect input bpc value {value}. ' + f'Value must be from list:{self.__STR_AVAILABLE_BPC_VALUES}') + + new_value = self.__value.default_value + new_value &= ~(0xF << 12) + if value == 0: + self.__value.default_value = new_value + else: + new_value |= (self.__COLORIMETRY_VALUES.get(value) << 12) + self.__value.default_value = new_value + + @ycbcr444.setter + def ycbcr444(self, value: int): + if value not in self.__AVAILABLE_BPC_VALUES: + raise ValueError(f'Incorrect input bpc value {value}. ' + f'Value must be from list:{self.__STR_AVAILABLE_BPC_VALUES}') + + new_value = self.__value.default_value + new_value &= ~(0xF << 16) + if value == 0: + self.__value.default_value = new_value + else: + new_value |= (self.__COLORIMETRY_VALUES.get(value) << 16) + self.__value.default_value = new_value + + @ycbcr422.setter + def ycbcr422(self, value: int): + if value not in self.__AVAILABLE_BPC_VALUES: + raise ValueError(f'Incorrect input bpc value {value}. ' + f'Value must be from list:{self.__STR_AVAILABLE_BPC_VALUES}') + + new_value = self.__value.default_value + new_value &= ~(0xF << 20) + if value == 0: + self.__value.default_value = new_value + else: + new_value |= (self.__COLORIMETRY_VALUES.get(value) << 20) + self.__value.default_value = new_value + + @ycbcr_simple422.setter + def ycbcr_simple422(self, value: int): + if value not in self.__AVAILABLE_BPC_VALUES: + raise ValueError(f'Incorrect input bpc value {value}. ' + f'Value must be from list:{self.__STR_AVAILABLE_BPC_VALUES}') + + new_value = self.__value.default_value + new_value &= ~(0xF << 24) + if value == 0: + self.__value.default_value = new_value + else: + new_value |= (self.__COLORIMETRY_VALUES.get(value) << 24) + self.__value.default_value = new_value + + @ycbcr420.setter + def ycbcr420(self, value: int): + if value not in self.__AVAILABLE_BPC_VALUES: + raise ValueError(f'Incorrect input bpc value {value}. ' + f'Value must be from list:{self.__STR_AVAILABLE_BPC_VALUES}') + + new_value = self.__value.default_value + new_value &= ~(0xF << 28) + if value == 0: + self.__value.default_value = new_value + else: + new_value |= (self.__COLORIMETRY_VALUES.get(value) << 28) + self.__value.default_value = new_value + + def set_all(self, bpc: int): + if bpc not in self.__AVAILABLE_BPC_VALUES: + raise ValueError(f'Incorrect input bpc value {bpc}. ' + f'Value must be from list:{self.__STR_AVAILABLE_BPC_VALUES}') + self.rgb = bpc + self.ycbcr444 = bpc + self.ycbcr422 = bpc + self.ycbcr_simple422 = bpc + self.ycbcr420 = bpc + + def clear_all(self): + self.rgb = 0 + self.ycbcr444 = 0 + self.ycbcr422 = 0 + self.ycbcr_simple422 = 0 + self.ycbcr420 = 0 + + +class VideoModeInfo: + + def __init__(self, json_obj): + self.__value = Param(json_obj) + self.__standard = VideoModeStandard(self.__value) + self.__colorimetry = ColorimetryModes(self.__value) + + @property + def standard(self) -> VideoModeStandard: + return self.__standard + + @property + def colorimetry(self) -> ColorimetryModes: + return self.__colorimetry diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/dp_2_1_dsc_video_modes.py b/UniTAP/dev/modules/dut_tests/dut_default_params/dp_2_1_dsc_video_modes.py new file mode 100644 index 0000000..85c4452 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/dp_2_1_dsc_video_modes.py @@ -0,0 +1,202 @@ +from UniTAP.dev.modules.dut_tests.dut_default_params.dp_2_1_common_video_modes import VideoModeInfo + + +class Dp21AvailableDscVideoModes: + """ + Class `Dp21AvailableDscVideoModes` allows working with video modes. + - Set and get... + """ + def __init__(self, json_obj): + self.__is_config_changed = False + + self.__1920x1080_30hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_DSC_VMT_1920_1080_30"]) + self.__1920x1080_60hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_DSC_VMT_1920_1080_60"]) + self.__1920x1080_120hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_DSC_VMT_1920_1080_120"]) + self.__1920x1080_144hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_DSC_VMT_1920_1080_144"]) + self.__1920x1080_240hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_DSC_VMT_1920_1080_240"]) + self.__3840x2160_30hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_DSC_VMT_3840_2160_30"]) + self.__3840x2160_60hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_DSC_VMT_3840_2160_60"]) + self.__3840x2160_120hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_DSC_VMT_3840_2160_120"]) + self.__3840x2160_144hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_DSC_VMT_3840_2160_144"]) + self.__3840x2160_240hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_DSC_VMT_3840_2160_240"]) + self.__5120x2160_30hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_DSC_VMT_5120_2160_30"]) + self.__5120x2160_60hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_DSC_VMT_5120_2160_60"]) + self.__5120x2160_120hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_DSC_VMT_5120_2160_120"]) + self.__5120x2160_144hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_DSC_VMT_5120_2160_144"]) + self.__5120x2160_240hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_DSC_VMT_5120_2160_240"]) + self.__7680x4320_30hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_DSC_VMT_7680_4320_30"]) + self.__7680x4320_60hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_DSC_VMT_7680_4320_60"]) + self.__7680x4320_120hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_DSC_VMT_7680_4320_120"]) + self.__10240x4320_30hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_DSC_VMT_10240_4320_30"]) + self.__10240x4320_60hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_DSC_VMT_10240_4320_60"]) + + def __getattribute__(self, name): + attr = super().__getattribute__(name) + if "vm_" in name or "_all" in name: + self.__is_config_changed = True + return attr + + @property + def vm_1920x1080_30hz(self) -> VideoModeInfo: + return self.__1920x1080_30hz + + @property + def vm_1920x1080_60hz(self) -> VideoModeInfo: + return self.__1920x1080_60hz + + @property + def vm_1920x1080_120hz(self) -> VideoModeInfo: + return self.__1920x1080_120hz + + @property + def vm_1920x1080_144hz(self) -> VideoModeInfo: + return self.__1920x1080_144hz + + @property + def vm_1920x1080_240hz(self) -> VideoModeInfo: + return self.__1920x1080_240hz + + @property + def vm_3840x2160_30hz(self) -> VideoModeInfo: + return self.__3840x2160_30hz + + @property + def vm_3840x2160_60hz(self) -> VideoModeInfo: + return self.__3840x2160_60hz + + @property + def vm_3840x2160_120hz(self) -> VideoModeInfo: + return self.__3840x2160_120hz + + @property + def vm_3840x2160_144hz(self) -> VideoModeInfo: + return self.__3840x2160_144hz + + @property + def vm_3840x2160_240hz(self) -> VideoModeInfo: + return self.__3840x2160_240hz + + @property + def vm_5120x2160_30hz(self) -> VideoModeInfo: + return self.__5120x2160_30hz + + @property + def vm_5120x2160_60hz(self) -> VideoModeInfo: + return self.__5120x2160_60hz + + @property + def vm_5120x2160_120hz(self) -> VideoModeInfo: + return self.__5120x2160_120hz + + @property + def vm_5120x2160_144hz(self) -> VideoModeInfo: + return self.__5120x2160_144hz + + @property + def vm_5120x2160_240hz(self) -> VideoModeInfo: + return self.__5120x2160_240hz + + @property + def vm_7680x4320_30hz(self) -> VideoModeInfo: + return self.__7680x4320_30hz + + @property + def vm_7680x4320_60hz(self) -> VideoModeInfo: + return self.__7680x4320_60hz + + @property + def vm_7680x4320_120hz(self) -> VideoModeInfo: + return self.__7680x4320_120hz + + @property + def vm_10240x4320_30hz(self) -> VideoModeInfo: + return self.__10240x4320_30hz + + @property + def vm_10240x4320_60hz(self) -> VideoModeInfo: + return self.__10240x4320_60hz + + def select_all_standards(self): + var_list = self.__dict__ + for item in var_list: + if isinstance(self.__getattribute__(item), VideoModeInfo): + self.__getattribute__(item).standard.set_all() + + def select_all_cta_standard(self): + var_list = self.__dict__ + for item in var_list: + if isinstance(self.__getattribute__(item), VideoModeInfo): + self.__getattribute__(item).standard.cta = True + + def select_all_dmt_standard(self): + var_list = self.__dict__ + for item in var_list: + if isinstance(self.__getattribute__(item), VideoModeInfo): + self.__getattribute__(item).standard.dmt = True + + def select_all_cvt_standard(self): + var_list = self.__dict__ + for item in var_list: + if isinstance(self.__getattribute__(item), VideoModeInfo): + self.__getattribute__(item).standard.cvt = True + + def select_all_cvt_rb1_standard(self): + var_list = self.__dict__ + for item in var_list: + if isinstance(self.__getattribute__(item), VideoModeInfo): + self.__getattribute__(item).standard.cvt_rb1 = True + + def select_all_cvt_rb2_standard(self): + var_list = self.__dict__ + for item in var_list: + if isinstance(self.__getattribute__(item), VideoModeInfo): + self.__getattribute__(item).standard.cvt_rb2 = True + + def select_all_ovt_standard(self): + var_list = self.__dict__ + for item in var_list: + if isinstance(self.__getattribute__(item), VideoModeInfo): + self.__getattribute__(item).standard.ovt = True + + def select_all_colorimetries(self, bpc: int = 8): + var_list = self.__dict__ + for item in var_list: + if isinstance(self.__getattribute__(item), VideoModeInfo): + self.__getattribute__(item).colorimetry.set_all(bpc) + + def select_all_rgb_colorimetry(self, bpc: int = 8): + var_list = self.__dict__ + for item in var_list: + if isinstance(self.__getattribute__(item), VideoModeInfo): + self.__getattribute__(item).colorimetry.rgb = bpc + + def select_all_ycbcr444_colorimetry(self, bpc: int = 8): + var_list = self.__dict__ + for item in var_list: + if isinstance(self.__getattribute__(item), VideoModeInfo): + self.__getattribute__(item).colorimetry.ycbcr444 = bpc + + def select_all_ycbcr422_colorimetry(self, bpc: int = 8): + var_list = self.__dict__ + for item in var_list: + if isinstance(self.__getattribute__(item), VideoModeInfo): + self.__getattribute__(item).colorimetry.ycbcr422 = bpc + + def select_all_ycbcr_simple422_colorimetry(self, bpc: int = 8): + var_list = self.__dict__ + for item in var_list: + if isinstance(self.__getattribute__(item), VideoModeInfo): + self.__getattribute__(item).colorimetry.ycbcr_simple422 = bpc + + def select_all_ycbcr420_colorimetry(self, bpc: int = 8): + var_list = self.__dict__ + for item in var_list: + if isinstance(self.__getattribute__(item), VideoModeInfo): + self.__getattribute__(item).colorimetry.ycbcr420 = bpc + + def clear_all(self): + var_list = self.__dict__ + for item in var_list: + if isinstance(self.__getattribute__(item), VideoModeInfo): + self.__getattribute__(item).colorimetry.clear_all() + self.__getattribute__(item).standard.clear_all() diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/dp_2_1_source_general_tab.py b/UniTAP/dev/modules/dut_tests/dut_default_params/dp_2_1_source_general_tab.py new file mode 100644 index 0000000..9ef398f --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/dp_2_1_source_general_tab.py @@ -0,0 +1,758 @@ +from UniTAP.dev.modules.dut_tests.dut_default_params.dp_1_4_source_general_tab import DutCapsFlags,\ + update_default_value, Param, IntEnum, TestAutomationFlags + + +class MaxLinkBwPolicy(IntEnum): + """ + Describes available values for maximum link bandwidth policy. + """ + Link1LRBR = 262 + Link1LHBR = 266 + Link2LRBR = 518 + Link2LHBR = 522 + + +class TestAutomationFlagsDP21(TestAutomationFlags): + """ + Class `TestAutomationFlagsDP21` allows configuring test automation flags: + - Set and get video operator input flag `video_operator_input`. + - Set and get DSC VIC flag `dsc_vis_val`. + + Also has all the `TestAutomationFlags` functionality. + """ + + @property + def video_operator_input(self) -> bool: + """ + Set and get video operator input flag. + If flag == true -> If Source DUT does not generate the requested format then GUI request will be made to + operator to generate the requested format from the EDID (request and waits for test operator input). + If flag == false -> If Source DUT does not generate the requested Video Format, then no GUI request will be + made, and test will fail if parameters does not match (request and waits for test operator input). + + Usually use for manual testing. + + Returns: + object of bool type + """ + return self._param._get_by_bitmask(5, bool) + + @video_operator_input.setter + def video_operator_input(self, video_operator_input: bool): + self._param._set_by_bitmask(int(video_operator_input), 5) + + @property + def dsc_vis_val(self) -> bool: + """ + Set and get DSC visual validation flag. + If flag == true -> will check timing, color depth, color space and video CRC. + If flag == false -> will check timing, color depth, color space without checking video CRC. + + Usually use for manual checking. + + Returns: + object of bool type + """ + return self._param._get_by_bitmask(6, bool) + + @dsc_vis_val.setter + def dsc_vis_val(self, dsc_vis_val: bool): + self._param._set_by_bitmask(int(dsc_vis_val), 6) + + +class LinkRateDp21(Param): + """ + Class `LinkRateDp21` describes support DP 2.1 rates. Allows getting and setting values. + - Support 10 Gbps `support_10Gbps`. + - Support 13.5 Gbps `support_13_5Gbps`. + - Support 20 Gbps `support_20Gbps`. + """ + def __init__(self, json_obj): + super().__init__(json_obj) + + @property + def support_10Gbps(self) -> bool: + """ + Set and get 10 Gbps flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(0, bool) + + + @support_10Gbps.setter + def support_10Gbps(self, support_10Gbps: bool): + self._set_by_bitmask(support_10Gbps, 0) + + @property + def support_20Gbps(self) -> bool: + """ + Set and get 20 Gbps flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(1, bool) + + @support_20Gbps.setter + def support_20Gbps(self, support_20Gbps: bool): + self._set_by_bitmask(support_20Gbps, 1) + + @property + def support_13_5Gbps(self) -> bool: + """ + Set and get 13.5 Gbps flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(2, bool) + + @support_13_5Gbps.setter + def support_13_5Gbps(self, support_13_5Gbps: bool): + self._set_by_bitmask(support_13_5Gbps, 2) + + +class DutCapsDp21Flags(Param): + """ + Class `DutCapsDp21Flags` inherited of class`DutCapsFlags` which defines the DUT capabilities as flags and allows + setting: + - Maximum link bandwidth policy supported flag `max_link_bandwidth_policy_supported`. + + Also has all the `DutCapsFlags` functionality. + """ + def __init__(self, json_obj): + super().__init__(json_obj) + + @property + def voltage_swing_supported(self) -> bool: + """ + Set and get Voltage swing level flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(0, bool) + + @voltage_swing_supported.setter + def voltage_swing_supported(self, voltage_swing_supported: bool): + self._set_by_bitmask(voltage_swing_supported, 0) + + @property + def pre_emphasis_supported(self) -> bool: + """ + Set and get Pre-emphasis level flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(1, bool) + + @pre_emphasis_supported.setter + def pre_emphasis_supported(self, pre_emphasis_supported: bool): + self._set_by_bitmask(pre_emphasis_supported, 1) + + @property + def fixed_timing_dut_supported(self) -> bool: + """ + Set and get Fixed timing DUT flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(2, bool) + + @fixed_timing_dut_supported.setter + def fixed_timing_dut_supported(self, fixed_timing_dut_supported: bool): + self._set_by_bitmask(fixed_timing_dut_supported, 2) + + @property + def spread_spectrum_supported(self) -> bool: + """ + Set and get Spread Spectrum flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(3, bool) + + @spread_spectrum_supported.setter + def spread_spectrum_supported(self, spread_spectrum_supported: bool): + self._set_by_bitmask(spread_spectrum_supported, 3) + + @property + def change_vf_without_lt_supported(self) -> bool: + """ + Set and get Video format change without LT flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(4, bool) + + @change_vf_without_lt_supported.setter + def change_vf_without_lt_supported(self, change_vf_without_lt_supported: bool): + self._set_by_bitmask(change_vf_without_lt_supported, 4) + + @property + def e_ddc_protocol_supported(self) -> bool: + """ + Set and get E-DDC protocol flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(5, bool) + + @e_ddc_protocol_supported.setter + def e_ddc_protocol_supported(self, e_ddc_protocol_supported: bool): + self._set_by_bitmask(e_ddc_protocol_supported, 5) + + @property + def audio_transmission_supported(self) -> bool: + """ + Set and get Audio Info Frame for 2 channel audio transmission flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(6, bool) + + @audio_transmission_supported.setter + def audio_transmission_supported(self, audio_transmission_supported: bool): + self._set_by_bitmask(audio_transmission_supported, 6) + + @property + def dut_is_type_c_device(self) -> bool: + """ + Set and get Define that DUT is Type-C device flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(7, bool) + + @dut_is_type_c_device.setter + def dut_is_type_c_device(self, dut_is_type_c_device: bool): + self._set_by_bitmask(dut_is_type_c_device, 7) + + @property + def fec_supported(self) -> bool: + """ + Set and get FEC flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(8, bool) + + @fec_supported.setter + def fec_supported(self, fec_supported: bool): + self._set_by_bitmask(fec_supported, 8) + + @property + def fec_disable_sequence_supported(self) -> bool: + """ + Set and get FEC disable sequence flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(9, bool) + + @fec_disable_sequence_supported.setter + def fec_disable_sequence_supported(self, fec_disable_sequence_supported: bool): + self._set_by_bitmask(fec_disable_sequence_supported, 9) + + @property + def audio_without_video_supported(self) -> bool: + """ + Set and get Audio without Video flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(10, bool) + + @audio_without_video_supported.setter + def audio_without_video_supported(self, audio_without_video_supported: bool): + self._set_by_bitmask(audio_without_video_supported, 10) + + @property + def dsc_supported(self) -> bool: + """ + Set and get DSC flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(11, bool) + + @dsc_supported.setter + def dsc_supported(self, dsc_supported: bool): + self._set_by_bitmask(dsc_supported, 11) + + @property + def dsc_block_prediction_supported(self) -> bool: + """ + Set and get DSC block prediction flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(12, bool) + + @dsc_block_prediction_supported.setter + def dsc_block_prediction_supported(self, dsc_block_prediction_supported: bool): + self._set_by_bitmask(dsc_block_prediction_supported, 12) + + @property + def max_link_bandwidth_policy_supported(self): + """ + Set and get Maximum link bandwidth policy flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(13, bool) + + @max_link_bandwidth_policy_supported.setter + def max_link_bandwidth_policy_supported(self, max_link_bandwidth_policy_supported: bool): + self._set_by_bitmask(max_link_bandwidth_policy_supported, 13) + + @property + def use_3tap_conversion(self): + """ + Set and get 3TAP conversion flag using. + + Returns: + object of bool type + """ + return self._get_by_bitmask(14, bool) + + @use_3tap_conversion.setter + def use_3tap_conversion(self, use_3tap_filter: bool): + self._set_by_bitmask(use_3tap_filter, 14) + + @property + def usb4_tunnel_presented(self): + """ + Set and get USB4 tunnel presented flag. + + Returns: + object of bool type + """ + return self._get_by_bitmask(15, bool) + + @usb4_tunnel_presented.setter + def usb4_tunnel_presented(self, usb4_tunnel_presented: bool): + self._set_by_bitmask(usb4_tunnel_presented, 15) + + @property + def native_display_id_read(self) -> bool: + """ + Set and get DUT supports native Display ID read flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(16, bool) + + @native_display_id_read.setter + def native_display_id_read(self, native_display_id_read: bool): + self._set_by_bitmask(native_display_id_read, 16) + + @property + def display_id_vii_supported(self) -> bool: + """ + Set and get DisplayID Type VII Detailed Timing Descriptor flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(17, bool) + + @display_id_vii_supported.setter + def display_id_vii_supported(self, display_id_vii_supported: bool): + self._set_by_bitmask(display_id_vii_supported, 17) + + @property + def display_id_viii_supported(self) -> bool: + """ + Set and get DisplayID Type VIII Detailed Timing Descriptor flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(18, bool) + + @display_id_viii_supported.setter + def display_id_viii_supported(self, display_id_viii_supported: bool): + self._set_by_bitmask(display_id_viii_supported, 18) + + + @property + def display_id_ix_supported(self) -> bool: + """ + Set and get DisplayID Type IX Detailed Timing Descriptor flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(19, bool) + + @display_id_ix_supported.setter + def display_id_ix_supported(self, display_id_ix_supported: bool): + self._set_by_bitmask(display_id_ix_supported, 19) + + @property + def display_id_x_supported(self) -> bool: + """ + Set and get DisplayID Type X Detailed Timing Descriptor flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(20, bool) + + @display_id_x_supported.setter + def display_id_x_supported(self, display_id_x_supported: bool): + self._set_by_bitmask(display_id_x_supported, 20) + + @property + def display_id_tiled_display_topology(self) -> bool: + """ + Set and get 2x1 tiled display and DisplayID Tiled Display Topology data block flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(21, bool) + + @display_id_tiled_display_topology.setter + def display_id_tiled_display_topology(self, display_id_tiled_display_topology: bool): + self._set_by_bitmask(display_id_tiled_display_topology, 21) + + @property + def display_id_tiled_stereo_display(self) -> bool: + """ + Set and get Field sequential stereo and DisplayID Tiled Stereo Display Interface data block flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(22, bool) + + @display_id_tiled_stereo_display.setter + def display_id_tiled_stereo_display(self, display_id_tiled_stereo_display: bool): + self._set_by_bitmask(display_id_tiled_stereo_display, 22) + + @property + def stacked_frame_stereo_supported(self) -> bool: + """ + Set and get Stacked frame stereo and DisplayID Tiled Stereo Display Interface data flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(23, bool) + + @stacked_frame_stereo_supported.setter + def stacked_frame_stereo_supported(self, stacked_frame_stereo_supported: bool): + self._set_by_bitmask(stacked_frame_stereo_supported, 23) + + @property + def dynamic_refresh_rate_support(self) -> bool: + """ + Set and get Dynamic Refresh Rate with VBlank stretch with MSA_TIMING_PAR_IGNORED flag supported. + + Returns: + object of bool type + """ + return self._get_by_bitmask(24, bool) + + @dynamic_refresh_rate_support.setter + def dynamic_refresh_rate_support(self, dynamic_refresh_rate_support: bool): + self._set_by_bitmask(dynamic_refresh_rate_support, 24) + + +class DutCapsDp21: + """ + Class `DutCapsDp21` defines the DUT capabilities and allows setting: + - Defines the maximum number of lanes supported by the DUT `max_lanes`. + - Maximum link rate supported by the DUT `max_link_rate`. + - Dut capabilities flags `dut_caps_flags` type `DutCapsFlags`. + """ + def __init__(self, json_obj): + self.__max_lanes = Param(json_obj["TSI_DP20_SRCCTS_MAX_LANES"]) + self.__max_link_rate = Param(json_obj["TSI_DP20_SRCCTS_MAX_LINK_RATE"]) + self.__dut_caps_flags = DutCapsDp21Flags(json_obj["TSI_DP20_SRCCTS_DUT_CAPS"]) + + @property + def max_lanes(self) -> int: + """ + Set and get number of maximum lanes. + + Returns: + object of int type + """ + return self.__max_lanes.default_value + + @max_lanes.setter + def max_lanes(self, max_lanes: int): + if max_lanes not in [1, 2, 4]: + raise ValueError(f"Max lane count must be from list: 1, 2, 4. Current value: {max_lanes}") + self.__max_lanes.default_value = max_lanes + + @property + def max_link_rate(self): + """ + Set and get number of maximum link rate. + + Returns: + object of int type + """ + return self.__max_link_rate + + @max_link_rate.setter + def max_link_rate(self, max_link_rate: float): + if max_link_rate not in [1.62, 2.7, 5.4, 8.1]: + raise ValueError(f"Max link rate must be from list: 1.62, 2.7, 5.4, 8.1. Current value: {max_link_rate}") + self.__max_link_rate.default_value = round(max_link_rate / 0.27) + + @property + def dut_caps_flags(self) -> DutCapsDp21Flags: + """ + Set and get DUT capabilities flags. + + Returns: + object of `DutCapsFlags` type + """ + return self.__dut_caps_flags + + @dut_caps_flags.setter + def dut_caps_flags(self, dut_caps_flags: DutCapsDp21Flags): + self.__dut_caps_flags = dut_caps_flags + + +class DebugOptions: + + def __init__(self, json_obj): + self.__param = Param(json_obj["TSI_DP20_SRCCTS_DUT_DEBUG_CONF"]) + + @property + def continue_on_fail(self) -> bool: + """ + Set and get continue on fail flag. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(0, bool) + + @continue_on_fail.setter + def continue_on_fail(self, continue_on_fail: bool): + self.__param.default_value = update_default_value(value=continue_on_fail, + default_value=self.__param.default_value, + mask=self.__param.bit_field_list[0].mask) + + @property + def force_visual_check(self) -> bool: + """ + Set and get Force manual visual check flag. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(1, bool) + + @force_visual_check.setter + def force_visual_check(self, force_visual_check: bool): + self.__param.default_value = update_default_value(value=force_visual_check, + default_value=self.__param.default_value, + mask=self.__param.bit_field_list[1].mask) + + +class GeneralSourceDUTDp21SettingTab: + """ + Class `GeneralSourceDUTDp21SettingTab` inherited of class`GeneralSourceDUTDp14SettingTab` allows working with + parameters from General source part. + - Set and get timeout `timeout`. + - Set and get `hpd_pulse_duration`. Describes duration of long HPD pulses generated, in milliseconds. + - Set and get DUT capabilities `dut_caps` type `DutCapsDp21`. + - Set and get DUT link rates `dut_link_rates` type `LinkRateDp21`. + - Set and get LTTPR device count `lttpr_device_count`. + - Set and get maximum link bandwidth policy `max_link_bw_policy` type `MaxLinkBwPolicy`. + - Set and get DSC maximum slice number `dsc_max_slice`. + - Set and get DSC version `dsc_version`. + - Set and get debug options `debug_options` type `DebugOptions` + """ + def __init__(self, json_obj): + self.__timeout = Param(json_obj["TSI_DP20_SRCCTS_TIMEOUT"]) + self.__dut_caps = DutCapsDp21(json_obj) + self.__test_automation = TestAutomationFlagsDP21(json_obj["TSI_DP20_SRCCTS_DUT_TA"]) + self.__hpd_pulse_duration = Param(json_obj["TSI_DP20_SRCCTS_LONG_HPD_PULSE"]) + self.__dut_link_rates = LinkRateDp21(json_obj["TSI_DP20_SRCCTS_DUT_LINK_RATES"]) + self.__lttpr_device_count = Param(json_obj["TSI_DP20_SRCCTS_LTTPR_DEVICE_COUNT"]) + self.__max_link_bw_policy = Param(json_obj["TSI_DP20_SRCCTS_MAX_LINK_BW_POLICY"]) # MaxLinkBwPolicy + self.__dsc_max_slice = Param(json_obj["TSI_DP20_SRCCTS_DSC_DUT_MAX_SLICE"]) + self.__dsc_version = Param(json_obj["TSI_DP20_SRCCTS_DSC_VERSION"]) + self.__debug_options = DebugOptions(json_obj) + + @property + def timeout(self) -> int: + """ + Set and get test timeout, in milliseconds. + + Returns: + object of int type + """ + return self.__timeout.default_value + + @timeout.setter + def timeout(self, timeout: int): + if not(self.__timeout.min_value < timeout < self.__timeout.max_value): + raise ValueError(f"Timeout cannot be less than {self.__timeout.min_value} and more than " + f"{self.__timeout.max_value}.") + self.__timeout.default_value = timeout + + @property + def dut_caps(self) -> DutCapsDp21: + """ + Set and get DUT caps. + + Returns: + object DutCapsDp21 + """ + return self.__dut_caps + + @dut_caps.setter + def dut_caps(self, dut_caps: DutCapsDp21): + self.__dut_caps = dut_caps + + @property + def hpd_pulse_duration(self) -> int: + """ + Set and get HPD pulse duration. + + Returns: + object of int type + """ + return self.__hpd_pulse_duration.default_value + + @hpd_pulse_duration.setter + def hpd_pulse_duration(self, hpd_pulse_duration: int): + if not(self.__hpd_pulse_duration.min_value < hpd_pulse_duration < self.__hpd_pulse_duration.max_value): + raise ValueError(f"HPD pulse duration cannot be less than {self.__timeout.min_value} and more than " + f"{self.__hpd_pulse_duration.max_value}.") + self.__hpd_pulse_duration.default_value = hpd_pulse_duration + + @property + def test_automation(self) -> TestAutomationFlagsDP21: + """ + Set and get test automation flags. + + Returns: + object TestAutomationFlags + """ + return self.__test_automation + + @test_automation.setter + def test_automation(self, test_automation: TestAutomationFlagsDP21): + self.__test_automation = test_automation + + @property + def dut_link_rates(self) -> LinkRateDp21: + """ + Set and get DUT link rates. + + Returns: + object LinkRateDp21 + """ + return self.__dut_link_rates + + @dut_link_rates.setter + def dut_link_rates(self, dut_link_rates: LinkRateDp21): + self.__dut_link_rates = dut_link_rates + + @property + def max_link_bw_policy(self) -> int: + """ + Set and get maximum link bandwidth policy. + + Returns: + object int + """ + return self.__max_link_bw_policy.default_value + + @max_link_bw_policy.setter + def max_link_bw_policy(self, max_link_bw_policy: MaxLinkBwPolicy): + self.__max_link_bw_policy = max_link_bw_policy.value + + @property + def lttpr_device_count(self) -> int: + """ + Set and get LTTPR device count. + + Returns: + object int + """ + return self.__lttpr_device_count.default_value + + @lttpr_device_count.setter + def lttpr_device_count(self, lttpr_device_count: int): + if not(self.__lttpr_device_count.min_value < lttpr_device_count < self.__lttpr_device_count.max_value): + raise ValueError(f"LTTPR device count cannot be less than " + f"{self.__lttpr_device_count.min_value} and more than " + f"{self.__lttpr_device_count.max_value}.") + self.__lttpr_device_count.default_value = lttpr_device_count + + @property + def dsc_max_slice(self) -> int: + """ + Set and get DSC maximum slice number. + + Returns: + object of int type + """ + return self.__dsc_max_slice.default_value + + @dsc_max_slice.setter + def dsc_max_slice(self, dsc_max_slice: int): + if dsc_max_slice not in [1, 2, 4, 8, 10, 12, 16, 20, 24]: + raise ValueError(f"DSC Max slice count available values {[1, 2, 4, 8, 10, 12, 16, 20, 24]}") + self.__dsc_max_slice.default_value = dsc_max_slice + + @property + def dsc_version(self) -> list: + """ + Set and get DSC version. + Setter: .dsc_version((1, 2)) + + Returns: + object of list type + """ + return [self.__dsc_version.default_value >> 16 & 0xF, self.__dsc_version.default_value & 0xF] + + @dsc_version.setter + def dsc_version(self, dsc_version: tuple): + version = (dsc_version[0] << 16) | dsc_version[1] + if version not in [65537, 65538]: + raise ValueError("DSC Version must be 1.1 or 1.2") + self.__dsc_version.default_value = version + + @property + def debug_options(self) -> DebugOptions: + """ + Set and get debug options. + + Returns: + object of 'DebugOptions' type + """ + return self.__debug_options + + @debug_options.setter + def debug_options(self, debug_config: DebugOptions): + self.__debug_options = debug_config + diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/dp_2_1_video_modes.py b/UniTAP/dev/modules/dut_tests/dut_default_params/dp_2_1_video_modes.py new file mode 100644 index 0000000..bbf2a5c --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/dp_2_1_video_modes.py @@ -0,0 +1,365 @@ +from warnings import warn + +from UniTAP.dev.modules.dut_tests.dut_default_params.dp_2_1_common_video_modes import VideoModeInfo + + +class Dp21AvailableVideoModes: + """ + Class `Dp21AvailableVideoModes` allows working with video modes. + - Set and get... + """ + def __init__(self, json_obj): + self.__768x480_85hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_768_480_85"]) + self.__1024x640_60hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_1024_640_60"]) + self.__1152x720_75hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_1152_720_75"]) + self.__1280x720_24hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_1280_720_24"]) + self.__1280x720_120hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_1280_720_120"]) + self.__1280x768_60hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_1280_768_60"]) + self.__1280x960_60hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_1280_960_60"]) + self.__1440x240_60hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_1440_240_60"]) + self.__1440x480_60hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_1440_480_60"]) + self.__1440x900_60hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_1440_900_60"]) + self.__1536x960_85hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_1536_960_85"]) + self.__1920x1080_30hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_1920_1080_30"]) + self.__1920x1080_60hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_1920_1080_60"]) + self.__1920x1080_85hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_1920_1080_85"]) + self.__1920x1080_100hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_1920_1080_100"]) + self.__1920x1080_120hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_1920_1080_120"]) + self.__1920x1080_144hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_1920_1080_144"]) + self.__1920x1080_240hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_1920_1080_240"]) + self.__1920x1440_60hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_1920_1440_60"]) + self.__2048x1280_60hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_2048_1280_60"]) + self.__2048x1536_60hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_2048_1536_60"]) + self.__2128x1200_60hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_2128_1200_60"]) + self.__2456x1536_50hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_2456_1536_50"]) + self.__2456x1536_75hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_2456_1536_75"]) + self.__2560x1080_30hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_2560_1080_30"]) + self.__2560x1080_60hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_2560_1080_60"]) + self.__2560x1080_120hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_2560_1080_120"]) + self.__2560x1600_60hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_2560_1600_60"]) + self.__2560x1920_75hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_2560_1920_75"]) + self.__2728x1536_60hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_2728_1536_60"]) + self.__3840x2160_30hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_3840_2160_30"]) + self.__3840x2160_60hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_3840_2160_60"]) + self.__3840x2160_120hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_3840_2160_120"]) + self.__3840x2160_144hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_3840_2160_144"]) + self.__3840x2160_240hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_3840_2160_240"]) + self.__3840x2400_60hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_3840_2400_60"]) + self.__4096x2160_30hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_4096_2160_30"]) + self.__5120x2160_30hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_5120_2160_30"]) + self.__5120x2160_60hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_5120_2160_60"]) + self.__5120x2160_120hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_5120_2160_120"]) + self.__5120x2160_144hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_5120_2160_144"]) + self.__5120x2160_240hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_5120_2160_240"]) + self.__5120x2880_30hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_5120_2880_30"]) + self.__5120x2880_60hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_5120_2880_60"]) + self.__5120x2880_120hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_5120_2880_120"]) + self.__7680x4320_24hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_7680_4320_24"]) + self.__7680x4320_30hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_7680_4320_30"]) + self.__7680x4320_50hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_7680_4320_50"]) + self.__7680x4320_60hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_7680_4320_60"]) + self.__7680x4320_120hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_7680_4320_120"]) + self.__10240x4320_24hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_10240_4320_24"]) + self.__10240x4320_30hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_10240_4320_30"]) + self.__10240x4320_60hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_10240_4320_60"]) + + @property + def vm_768x480_85hz(self) -> VideoModeInfo: + return self.__768x480_85hz + + @property + def vm_1024x640_60hz(self) -> VideoModeInfo: + return self.__1024x640_60hz + + @property + def vm_1152x720_75hz(self) -> VideoModeInfo: + return self.__1152x720_75hz + + @property + def vm_1280x720_24hz(self) -> VideoModeInfo: + return self.__1280x720_24hz + + @property + def vm_1280x720_120hz(self) -> VideoModeInfo: + return self.__1280x720_120hz + + @property + def vm_1280x768_60hz(self) -> VideoModeInfo: + return self.__1280x768_60hz + + @property + def vm_1280x960_60hz(self) -> VideoModeInfo: + return self.__1280x960_60hz + + @property + def vm_1440x240_60hz(self) -> VideoModeInfo: + return self.__1440x240_60hz + + @property + def vm_1440x480_60hz(self) -> VideoModeInfo: + return self.__1440x480_60hz + + @property + def vm_1440x900_60hz(self) -> VideoModeInfo: + return self.__1440x900_60hz + + @property + def vm_1536x960_85hz(self) -> VideoModeInfo: + return self.__1536x960_85hz + + @property + def vm_1920x1080_30hz(self) -> VideoModeInfo: + return self.__1920x1080_30hz + + @property + def vm_1920x1080_60hz(self) -> VideoModeInfo: + return self.__1920x1080_60hz + + @property + def vm_1920x1080_85hz(self) -> VideoModeInfo: + return self.__1920x1080_85hz + + @property + def vm_1920x1080_100hz(self) -> VideoModeInfo: + return self.__1920x1080_100hz + + @property + def vm_1920x1080_120hz(self) -> VideoModeInfo: + return self.__1920x1080_120hz + + @property + def vm_1920x1080_144hz(self) -> VideoModeInfo: + warn("This video mode is DSC only, please configure this video mode using dsc_video_modes.", DeprecationWarning, + 2) + return self.__1920x1080_144hz + + @property + def vm_1920x1080_240hz(self) -> VideoModeInfo: + warn("This video mode is DSC only, please configure this video mode using dsc_video_modes.", DeprecationWarning, + 2) + return self.__1920x1080_240hz + + @property + def vm_1920x1440_60hz(self) -> VideoModeInfo: + return self.__1920x1440_60hz + + @property + def vm_2048x1280_60hz(self) -> VideoModeInfo: + return self.__2048x1280_60hz + + @property + def vm_2048x1536_60hz(self) -> VideoModeInfo: + return self.__2048x1536_60hz + + @property + def vm_2128x1200_60hz(self) -> VideoModeInfo: + return self.__2128x1200_60hz + + @property + def vm_2456x1536_50hz(self) -> VideoModeInfo: + return self.__2456x1536_50hz + + @property + def vm_2456x1536_75hz(self) -> VideoModeInfo: + return self.__2456x1536_75hz + + @property + def vm_2560x1080_30hz(self) -> VideoModeInfo: + return self.__2560x1080_30hz + + @property + def vm_2560x1080_60hz(self) -> VideoModeInfo: + return self.__2560x1080_60hz + + @property + def vm_2560x1080_120hz(self) -> VideoModeInfo: + return self.__2560x1080_120hz + + @property + def vm_2560x1600_60hz(self) -> VideoModeInfo: + return self.__2560x1600_60hz + + @property + def vm_2560x1920_75hz(self) -> VideoModeInfo: + return self.__2560x1920_75hz + + @property + def vm_2728x1536_60hz(self) -> VideoModeInfo: + return self.__2728x1536_60hz + + @property + def vm_3840x2160_30hz(self) -> VideoModeInfo: + warn("This video mode is DSC only, please configure this video mode using dsc_video_modes.", DeprecationWarning, + 2) + return self.__3840x2160_30hz + + @property + def vm_3840x2160_60hz(self) -> VideoModeInfo: + return self.__3840x2160_60hz + + @property + def vm_3840x2160_120hz(self) -> VideoModeInfo: + return self.__3840x2160_120hz + + @property + def vm_3840x2160_144hz(self) -> VideoModeInfo: + return self.__3840x2160_144hz + + @property + def vm_3840x2160_240hz(self) -> VideoModeInfo: + warn("This video mode is DSC only, please configure this video mode using dsc_video_modes.", DeprecationWarning, + 2) + return self.__3840x2160_240hz + + @property + def vm_3840x2400_60hz(self) -> VideoModeInfo: + return self.__3840x2400_60hz + + @property + def vm_4096x2160_30hz(self) -> VideoModeInfo: + return self.__4096x2160_30hz + + @property + def vm_5120x2160_30hz(self) -> VideoModeInfo: + return self.__5120x2160_30hz + + @property + def vm_5120x2160_60hz(self) -> VideoModeInfo: + return self.__5120x2160_60hz + + @property + def vm_5120x2160_120hz(self) -> VideoModeInfo: + warn("This video mode is DSC only, please configure this video mode using dsc_video_modes.", DeprecationWarning, + 2) + return self.__5120x2160_120hz + + @property + def vm_5120x2160_144hz(self) -> VideoModeInfo: + return self.__5120x2160_144hz + + @property + def vm_5120x2160_240hz(self) -> VideoModeInfo: + warn("This video mode is DSC only, please configure this video mode using dsc_video_modes.", DeprecationWarning, + 2) + return self.__5120x2160_240hz + + @property + def vm_5120x2880_30hz(self) -> VideoModeInfo: + return self.__5120x2880_30hz + + @property + def vm_5120x2880_60hz(self) -> VideoModeInfo: + return self.__5120x2880_60hz + + @property + def vm_5120x2880_120hz(self) -> VideoModeInfo: + return self.__5120x2880_120hz + + @property + def vm_7680x4320_24hz(self) -> VideoModeInfo: + return self.__7680x4320_24hz + + @property + def vm_7680x4320_30hz(self) -> VideoModeInfo: + return self.__7680x4320_30hz + + @property + def vm_7680x4320_50hz(self) -> VideoModeInfo: + return self.__7680x4320_50hz + + @property + def vm_7680x4320_60hz(self) -> VideoModeInfo: + return self.__7680x4320_60hz + + @property + def vm_7680x4320_120hz(self) -> VideoModeInfo: + warn("This video mode is DSC only, please configure this video mode using dsc_video_modes.", DeprecationWarning, + 2) + return self.__7680x4320_120hz + + @property + def vm_10240x4320_24hz(self) -> VideoModeInfo: + return self.__10240x4320_24hz + + @property + def vm_10240x4320_30hz(self) -> VideoModeInfo: + warn("This video mode is DSC only, please configure this video mode using dsc_video_modes.", DeprecationWarning, + 2) + return self.__10240x4320_30hz + + @property + def vm_10240x4320_60hz(self) -> VideoModeInfo: + warn("This video mode is DSC only, please configure this video mode using dsc_video_modes.", DeprecationWarning, + 2) + return self.__10240x4320_60hz + + def select_all_standards(self): + var_list = self.__dict__ + for item in var_list: + self.__getattribute__(item).standard.set_all() + + def select_all_cta_standard(self): + var_list = self.__dict__ + for item in var_list: + self.__getattribute__(item).standard.cta = True + + def select_all_dmt_standard(self): + var_list = self.__dict__ + for item in var_list: + self.__getattribute__(item).standard.dmt = True + + def select_all_cvt_standard(self): + var_list = self.__dict__ + for item in var_list: + self.__getattribute__(item).standard.cvt = True + + def select_all_cvt_rb1_standard(self): + var_list = self.__dict__ + for item in var_list: + self.__getattribute__(item).standard.cvt_rb1 = True + + def select_all_cvt_rb2_standard(self): + var_list = self.__dict__ + for item in var_list: + self.__getattribute__(item).standard.cvt_rb2 = True + + def select_all_ovt_standard(self): + var_list = self.__dict__ + for item in var_list: + self.__getattribute__(item).standard.ovt = True + + def select_all_colorimetries(self, bpc: int = 8): + var_list = self.__dict__ + for item in var_list: + self.__getattribute__(item).colorimetry.set_all(bpc) + + def select_all_rgb_colorimetry(self, bpc: int = 8): + var_list = self.__dict__ + for item in var_list: + self.__getattribute__(item).colorimetry.rgb = bpc + + def select_all_ycbcr444_colorimetry(self, bpc: int = 8): + var_list = self.__dict__ + for item in var_list: + self.__getattribute__(item).colorimetry.ycbcr444 = bpc + + def select_all_ycbcr422_colorimetry(self, bpc: int = 8): + var_list = self.__dict__ + for item in var_list: + self.__getattribute__(item).colorimetry.ycbcr422 = bpc + + def select_all_ycbcr_simple422_colorimetry(self, bpc: int = 8): + var_list = self.__dict__ + for item in var_list: + self.__getattribute__(item).colorimetry.ycbcr_simple422 = bpc + + def select_all_ycbcr420_colorimetry(self, bpc: int = 8): + var_list = self.__dict__ + for item in var_list: + self.__getattribute__(item).colorimetry.ycbcr420 = bpc + + def clear_all(self): + var_list = self.__dict__ + for item in var_list: + self.__getattribute__(item).colorimetry.clear_all() + self.__getattribute__(item).standard.clear_all() diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/dp_electrical_tests.py b/UniTAP/dev/modules/dut_tests/dut_default_params/dp_electrical_tests.py new file mode 100644 index 0000000..6212f3a --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/dp_electrical_tests.py @@ -0,0 +1,418 @@ +from UniTAP.dev.modules.dut_tests.test_group_params_types import Param +from UniTAP.dev.modules.dut_tests.test_utils import update_default_value + + +class DpDutTaCaps(Param): + """ + Class `DpDutTaCaps` describes DUT Test automation capabilities flags and allows settings values. + - DUT is capable for test link training `dut_capable_link_training`. + - DUT is capable for test video pattern `dut_capable_video_pattern` + - DUT is capable for test EDID read `dut_capable_edid_read`. + """ + def __init__(self, json_obj): + super().__init__(json_obj) + + @property + def dut_capable_link_training(self) -> bool: + """ + Set and get DUT is capable for test link training flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(0, bool) + + @dut_capable_link_training.setter + def dut_capable_link_training(self, dut_capable_link_training: bool): + self._set_by_bitmask(dut_capable_link_training, 0) + + @property + def dut_capable_video_pattern(self) -> bool: + """ + Set and get DUT is capable for test video pattern flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(1, bool) + + @dut_capable_video_pattern.setter + def dut_capable_video_pattern(self, dut_capable_video_pattern: bool): + self._set_by_bitmask(dut_capable_video_pattern, 1) + + @property + def dut_capable_edid_read(self) -> bool: + """ + Set and get DUT is capable for test EDID read flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(2, bool) + + @dut_capable_edid_read.setter + def dut_capable_edid_read(self, dut_capable_edid_read: bool): + self._set_by_bitmask(dut_capable_edid_read, 2) + + +class DpElectricalTestParam: + """ + Class `DpElectricalTestParam` describes parameters for DP electrical tests. + - Test timeout, in milliseconds `timeout`. + - Main link low voltage limit, mV `links_low_voltage`. + - Main link high voltage limit, mV `links_high_voltage`. + - HPD line logical zero low voltage level limit, mV `hpd_zero_low_voltage`. + - HPD line logical zero high voltage level limit, mV `hpd_zero_high_voltage`. + - HPD line logical one low voltage level limit, mV `hpd_one_low_voltage`. + - HPD line logical one high voltage level limit, mV `hpd_one_high_voltage`. + - AUX + line idle low voltage level limit, mV `aux_positive_idle_low_voltage`. + - AUX + line idle high voltage level limit, mV `aux_positive_idle_high_voltage`. + - AUX - line idle low voltage level limit, mV `aux_negative_idle_low_voltage`. + - AUX - line idle high voltage level limit, mV `aux_negative_idle_high_voltage`. + - AUX + line signal trigger level, mV `aux_positive_trig_voltage`. + - AUX - line signal trigger level, mV `aux_negative_trig_voltage`. + - AUX signal capture timeout, milliseconds `aux_signal_capture_timeout`. + - AUX signal capture attempts, times `aux_signal_capture_count`. + - Maximum lanes count supported by DUT `dut_max_lanes`. + - Maximum data rate supported by DUT in 0.27Gbps `dut_max_link_rate`. + - DUT Test automation capabilities flags `dut_ta_caps` type `DpDutTaCaps`. + """ + def __init__(self, json_obj): + self.__timeout = Param(json_obj["TSI_DP_RX_TEST_TIMEOUT"]) + self.__links_low_voltage = Param(json_obj["TSI_DP_RX_LINKS_LOW_VOLTAGE"]) + self.__links_high_voltage = Param(json_obj["TSI_DP_RX_LINKS_HI_VOLTAGE"]) + self.__hpd_zero_low_voltage = Param(json_obj["TSI_DP_RX_HPD_ZERO_LOW_VOLTAGE"]) + self.__hpd_zero_high_voltage = Param(json_obj["TSI_DP_RX_HPD_ZERO_HI_VOLTAGE"]) + self.__hpd_one_low_voltage = Param(json_obj["TSI_DP_RX_HPD_ONE_LOW_VOLTAGE"]) + self.__hpd_one_high_voltage = Param(json_obj["TSI_DP_RX_HPD_ONE_HI_VOLTAGE"]) + self.__aux_positive_idle_low_voltage = Param(json_obj["TSI_DP_RX_AUX_P_IDLE_LOW_VOLTAGE"]) + self.__aux_positive_idle_high_voltage = Param(json_obj["TSI_DP_RX_AUX_P_IDLE_HI_VOLTAGE"]) + self.__aux_negative_idle_low_voltage = Param(json_obj["TSI_DP_RX_AUX_N_IDLE_LOW_VOLTAGE"]) + self.__aux_negative_idle_high_voltage = Param(json_obj["TSI_DP_RX_AUX_N_IDLE_HI_VOLTAGE"]) + self.__aux_positive_trig_voltage = Param(json_obj["TSI_DP_RX_AUX_P_TRIG_VOLTAGE"]) + self.__aux_negative_trig_voltage = Param(json_obj["TSI_DP_RX_AUX_N_TRIG_VOLTAGE"]) + self.__aux_signal_capture_timeout = Param(json_obj["TSI_DP_RX_AUX_SIGNAL_CAPT_TIMEOUT"]) + self.__aux_signal_capture_count = Param(json_obj["TSI_DP_RX_AUX_SIGNAL_CAPT_TRIES"]) + self.__dut_max_lanes = Param(json_obj["TSI_DP_RX_MAX_DUT_MAX_LANES"]) + self.__dut_max_link_rate = Param(json_obj["TSI_DP_RX_MAX_DUT_LANE_RATE"]) + self.__unused_dut_caps_flag = Param(json_obj["TSI_DP_RX_DUT_CAPS"]) + self.__dut_ta_caps = DpDutTaCaps(json_obj["TSI_DP_RX_DUT_TA_CAPS"]) + + @property + def timeout(self) -> int: + """ + Set and get test timeout, in milliseconds. + + Returns: + object of int type + """ + return self.__timeout.default_value + + @timeout.setter + def timeout(self, timeout: int): + if not(self.__timeout.min_value < timeout < self.__timeout.max_value): + raise ValueError(f"Timeout cannot be less than {self.__timeout.min_value} and more than " + f"{self.__timeout.max_value}.") + self.__timeout.default_value = timeout + + @property + def links_low_voltage(self) -> int: + """ + Set and get Main link low voltage limit. + + Returns: + object of int type + """ + return self.__links_low_voltage.default_value + + @links_low_voltage.setter + def links_low_voltage(self, links_low_voltage: int): + if not(self.__links_low_voltage.min_value < links_low_voltage < self.__links_low_voltage.max_value): + raise ValueError(f"Links low voltage cannot be less than {self.__links_low_voltage.min_value} and more than" + f" {self.__links_low_voltage.max_value}.") + self.__links_low_voltage.default_value = links_low_voltage + + @property + def links_high_voltage(self) -> int: + """ + Set and get Main link high voltage limit. + + Returns: + object of int type + """ + return self.__links_high_voltage.default_value + + @links_high_voltage.setter + def links_high_voltage(self, links_high_voltage: int): + if not(self.__links_high_voltage.min_value < links_high_voltage < self.__links_high_voltage.max_value): + raise ValueError(f"Links high voltage cannot be less than {self.__links_high_voltage.min_value} " + f"and more than {self.__links_high_voltage.max_value}.") + self.__links_high_voltage.default_value = links_high_voltage + + @property + def hpd_zero_low_voltage(self) -> int: + """ + Set and get HPD line logical zero low voltage level limit. + + Returns: + object of int type + """ + return self.__hpd_zero_low_voltage.default_value + + @hpd_zero_low_voltage.setter + def hpd_zero_low_voltage(self, hpd_zero_low_voltage: int): + if not(self.__hpd_zero_low_voltage.min_value < hpd_zero_low_voltage < self.__hpd_zero_low_voltage.max_value): + raise ValueError(f"HDP zero low voltage cannot be less than {self.__hpd_zero_low_voltage.min_value} " + f"and more than {self.__hpd_zero_low_voltage.max_value}.") + self.__hpd_zero_low_voltage.default_value = hpd_zero_low_voltage + + @property + def hpd_zero_high_voltage(self) -> int: + """ + Set and get HPD line logical zero high voltage level limit. + + Returns: + object of int type + """ + return self.__hpd_zero_high_voltage.default_value + + @hpd_zero_high_voltage.setter + def hpd_zero_high_voltage(self, hpd_zero_high_voltage: int): + if not(self.__hpd_zero_high_voltage.min_value < hpd_zero_high_voltage < self.__hpd_zero_high_voltage.max_value): + raise ValueError(f"HDP zero high voltage cannot be less than {self.__hpd_zero_high_voltage.min_value} " + f"and more than {self.__hpd_zero_high_voltage.max_value}.") + self.__hpd_zero_high_voltage.default_value = hpd_zero_high_voltage + + @property + def hpd_one_low_voltage(self) -> int: + """ + Set and get HPD line logical one low voltage level limit. + + Returns: + object of int type + """ + return self.__hpd_one_low_voltage.default_value + + @hpd_one_low_voltage.setter + def hpd_one_low_voltage(self, hpd_one_low_voltage: int): + if not(self.__hpd_one_low_voltage.min_value < hpd_one_low_voltage < self.__hpd_one_low_voltage.max_value): + raise ValueError(f"HDP one low voltage cannot be less than {self.__hpd_one_low_voltage.min_value} " + f"and more than {self.__timeout.max_value}.") + self.__hpd_one_low_voltage.default_value = hpd_one_low_voltage + + @property + def hpd_one_high_voltage(self) -> int: + """ + Set and get HPD line logical one high voltage level limit. + + Returns: + object of int type + """ + return self.__hpd_one_high_voltage.default_value + + @hpd_one_high_voltage.setter + def hpd_one_high_voltage(self, hpd_one_high_voltage: int): + if not(self.__hpd_one_high_voltage.min_value < hpd_one_high_voltage < self.__hpd_one_high_voltage.max_value): + raise ValueError(f"HDP one high voltage cannot be less than {self.__hpd_one_high_voltage.min_value} " + f"and more than {self.__hpd_one_high_voltage.max_value}.") + self.__hpd_one_high_voltage.default_value = hpd_one_high_voltage + + @property + def aux_positive_idle_low_voltage(self) -> int: + """ + Set and get AUX + line idle low voltage level limit. + + Returns: + object of int type + """ + return self.__aux_positive_idle_low_voltage.default_value + + @aux_positive_idle_low_voltage.setter + def aux_positive_idle_low_voltage(self, aux_positive_idle_low_voltage: int): + if not(self.__aux_positive_idle_low_voltage.min_value < aux_positive_idle_low_voltage < + self.__aux_positive_idle_low_voltage.max_value): + raise ValueError(f"AUX positive IDLE low voltage cannot be less than " + f"{self.__aux_positive_idle_low_voltage.min_value} and more than " + f"{self.__aux_positive_idle_low_voltage.max_value}.") + self.__aux_positive_idle_low_voltage.default_value = aux_positive_idle_low_voltage + + @property + def aux_positive_idle_high_voltage(self) -> int: + """ + Set and get AUX + line idle high voltage level limit. + + Returns: + object of int type + """ + return self.__aux_positive_idle_high_voltage.default_value + + @aux_positive_idle_high_voltage.setter + def aux_positive_idle_high_voltage(self, aux_positive_idle_high_voltage: int): + if not(self.__aux_positive_idle_high_voltage.min_value < aux_positive_idle_high_voltage < + self.__aux_positive_idle_high_voltage.max_value): + raise ValueError(f"AUX positive IDLE high voltage cannot be less than " + f"{self.__aux_positive_idle_high_voltage.min_value} and more than " + f"{self.__aux_positive_idle_high_voltage.max_value}.") + self.__aux_positive_idle_high_voltage.default_value = aux_positive_idle_high_voltage + + @property + def aux_negative_idle_low_voltage(self) -> int: + """ + Set and get AUX - line idle low voltage level limit. + + Returns: + object of int type + """ + return self.__aux_negative_idle_low_voltage.default_value + + @aux_negative_idle_low_voltage.setter + def aux_negative_idle_low_voltage(self, aux_negative_idle_low_voltage: int): + if not(self.__aux_negative_idle_low_voltage.min_value < aux_negative_idle_low_voltage < + self.__aux_negative_idle_low_voltage.max_value): + raise ValueError(f"AUX negative IDLE low voltage cannot be less than " + f"{self.__aux_negative_idle_low_voltage.min_value} and more than " + f"{self.__aux_negative_idle_low_voltage.max_value}.") + self.__aux_negative_idle_low_voltage.default_value = aux_negative_idle_low_voltage + + @property + def aux_negative_idle_high_voltage(self) -> int: + """ + Set and get AUX - line idle high voltage level limit. + + Returns: + object of int type + """ + return self.__aux_negative_idle_high_voltage.default_value + + @aux_negative_idle_high_voltage.setter + def aux_negative_idle_high_voltage(self, aux_negative_idle_high_voltage: int): + if not(self.__aux_negative_idle_high_voltage.min_value < aux_negative_idle_high_voltage < + self.__aux_negative_idle_high_voltage.max_value): + raise ValueError(f"AUX negative IDLE high voltage cannot be less than " + f"{self.__aux_negative_idle_high_voltage.min_value} and more than " + f"{self.__aux_negative_idle_high_voltage.max_value}.") + self.__aux_negative_idle_high_voltage.default_value = aux_negative_idle_high_voltage + + @property + def aux_positive_trig_voltage(self) -> int: + """ + Set and get AUX + line signal trigger level. + + Returns: + object of int type + """ + return self.__aux_positive_trig_voltage.default_value + + @aux_positive_trig_voltage.setter + def aux_positive_trig_voltage(self, aux_positive_trig_voltage: int): + if not(self.__aux_positive_trig_voltage.min_value < aux_positive_trig_voltage < + self.__aux_positive_trig_voltage.max_value): + raise ValueError(f"AUX positive trig voltage cannot be less than " + f"{self.__aux_positive_trig_voltage.min_value} and more than " + f"{self.__aux_positive_trig_voltage.max_value}.") + self.__aux_positive_trig_voltage.default_value = aux_positive_trig_voltage + + @property + def aux_negative_trig_voltage(self) -> int: + """ + Set and get AUX - line signal trigger level. + + Returns: + object of int type + """ + return self.__aux_negative_trig_voltage.default_value + + @aux_negative_trig_voltage.setter + def aux_negative_trig_voltage(self, aux_negative_trig_voltage: int): + if not(self.__aux_negative_trig_voltage.min_value < aux_negative_trig_voltage < + self.__aux_negative_trig_voltage.max_value): + raise ValueError(f"AUX negative trig voltage cannot be less than " + f"{self.__aux_negative_trig_voltage.min_value} and more than " + f"{self.__aux_negative_trig_voltage.max_value}.") + self.__aux_negative_trig_voltage.default_value = aux_negative_trig_voltage + + @property + def aux_signal_capture_timeout(self) -> int: + """ + Set and get AUX signal capture timeout, milliseconds. + + Returns: + object of int type + """ + return self.__aux_signal_capture_timeout.default_value + + @aux_signal_capture_timeout.setter + def aux_signal_capture_timeout(self, aux_signal_capture_timeout: int): + if not(self.__aux_signal_capture_timeout.min_value < aux_signal_capture_timeout < + self.__aux_signal_capture_timeout.max_value): + raise ValueError(f"AUX signal capture timeout cannot be less than " + f"{self.__aux_signal_capture_timeout.min_value} and more than " + f"{self.__aux_signal_capture_timeout.max_value}.") + self.__aux_signal_capture_timeout.default_value = aux_signal_capture_timeout + + @property + def aux_signal_capture_count(self) -> int: + """ + Set and get AUX signal capture attempts. + + Returns: + object of int type + """ + return self.__aux_signal_capture_count.default_value + + @aux_signal_capture_count.setter + def aux_signal_capture_count(self, aux_signal_capture_count: int): + if not(self.__aux_signal_capture_count.min_value < aux_signal_capture_count < + self.__aux_signal_capture_count.max_value): + raise ValueError(f"AUX signal capture count cannot be less than " + f"{self.__aux_signal_capture_count.min_value} and more than " + f"{self.__aux_signal_capture_count.max_value}.") + self.__aux_signal_capture_count.default_value = aux_signal_capture_count + + @property + def dut_max_lanes(self) -> int: + """ + Set and get Maximum lanes count supported by DUT. + + Returns: + object of int type + """ + return self.__dut_max_lanes.default_value + + @dut_max_lanes.setter + def dut_max_lanes(self, dut_max_lanes: int): + if not(self.__dut_max_lanes.min_value < dut_max_lanes < self.__dut_max_lanes.max_value): + raise ValueError(f"DUT max lanes cannot be less than {self.__dut_max_lanes.min_value} and more than " + f"{self.__dut_max_lanes.max_value}.") + self.__dut_max_lanes.default_value = dut_max_lanes + + @property + def dut_max_link_rate(self) -> int: + """ + Set and get Maximum data rate supported by DUT in 0.27Gbps. + + Returns: + object of int type + """ + return self.__dut_max_link_rate.default_value + + @dut_max_link_rate.setter + def dut_max_link_rate(self, dut_max_link_rate: int): + if not(self.__dut_max_link_rate.min_value < dut_max_link_rate < self.__dut_max_link_rate.max_value): + raise ValueError(f"DUT max link rate cannot be less than {self.__dut_max_link_rate.min_value} and more than " + f"{self.__dut_max_link_rate.max_value}.") + self.__dut_max_link_rate.default_value = dut_max_link_rate + + @property + def dut_ta_caps(self): + """ + Set and get DUT Test automation capabilities flags. + + Returns: + object DpDutTaCaps + """ + return self.__dut_ta_caps + + @dut_ta_caps.setter + def dut_ta_caps(self, dut_ta_caps: DpDutTaCaps): + self.__dut_ta_caps = dut_ta_caps diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/dp_source_adaptive_sync_tab.py b/UniTAP/dev/modules/dut_tests/dut_default_params/dp_source_adaptive_sync_tab.py new file mode 100644 index 0000000..15c9656 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/dp_source_adaptive_sync_tab.py @@ -0,0 +1,343 @@ +from enum import IntEnum +from UniTAP.dev.modules.dut_tests.test_group_params_types import Param +from UniTAP.dev.modules.dut_tests.test_utils import update_default_value + + +class RangeMinRate(IntEnum): + """ + Describes available values for rate in Adaptive-Sync tests. + """ + Rate_59_940Hz = 0 + Rate_47_952Hz = 1 + Rate_29_970Hz = 2 + Rate_23_976Hz = 3 + + +class AdaptiveSyncDpCaps(Param): + """ + Class `AdaptiveSyncDp14Caps` defines adaptive-sync capabilities and allows setting: + - Support Adaptive-Sync `support_adaptive_sync`. + - Support fixed average `support_fixed_average`. + - Support duration increase and decrease `support_duration_increase_and_decrease`. + """ + def __init__(self, json_obj): + super().__init__(json_obj) + + @property + def support_adaptive_sync(self): + """ + Set and get adaptive-sync flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(0, bool) + + @support_adaptive_sync.setter + def support_adaptive_sync(self, support: bool): + self._set_by_bitmask(support, 0) + + @property + def support_fixed_average(self): + """ + Set and get fixed average flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(1, bool) + + @support_fixed_average.setter + def support_fixed_average(self, support: bool): + self._set_by_bitmask(support, 1) + + @property + def support_duration_increase_and_decrease(self): + """ + Set and get duration increase and decrease flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(2, bool) + + @support_duration_increase_and_decrease.setter + def support_duration_increase_and_decrease(self, support: bool): + self._set_by_bitmask(support, 2) + + @property + def enable_manually(self): + """ + Set and get enable manually flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(3, bool) + + @enable_manually.setter + def enable_manually(self, enable: bool): + self._set_by_bitmask(enable, 3) + + +class AdaptiveSyncDpConfigTabBase: + def __init__(self, caps: AdaptiveSyncDpCaps, as_range_min_rate, + max_rate_1920x1080, + max_rate_2560x1080, + max_rate_2560x1440, + max_rate_3840x2160, + max_rate_4096x2160, + max_rate_5120x2160, + max_rate_7680x4320, + max_rate_10240x4320): + self._caps = caps + self._as_range_min_rate = as_range_min_rate + self._max_rate_1920x1080 = max_rate_1920x1080 + self._max_rate_2560x1080 = max_rate_2560x1080 + self._max_rate_2560x1440 = max_rate_2560x1440 + self._max_rate_3840x2160 = max_rate_3840x2160 + self._max_rate_4096x2160 = max_rate_4096x2160 + self._max_rate_5120x2160 = max_rate_5120x2160 + self._max_rate_7680x4320 = max_rate_7680x4320 + self._max_rate_10240x4320 = max_rate_10240x4320 + + @property + def as_caps(self) -> AdaptiveSyncDpCaps: + """ + Set and get adaptive-sync capabilities. + + Returns: + object `AdaptiveSyncDp14Caps` + """ + return self._caps + + @as_caps.setter + def as_caps(self, caps: AdaptiveSyncDpCaps): + self._caps = caps + + @property + def as_range_min_rate(self) -> int: + """ + Set and get adaptive-sync minimum rate. + + Returns: + object `RangeMinRate` + """ + return self._as_range_min_rate + + @as_range_min_rate.setter + def as_range_min_rate(self, as_range_min_rate: RangeMinRate): + self._as_range_min_rate.default_value = as_range_min_rate.value + + @property + def max_rate_1920x1080(self) -> int: + """ + Set and get maximum rate for resolution 1920x1080 + + Returns: + object of int type + """ + return self._max_rate_1920x1080.default_value + + @max_rate_1920x1080.setter + def max_rate_1920x1080(self, max_rate_1920x1080: int): + if not ( + self._max_rate_1920x1080.min_value <= max_rate_1920x1080 < self._max_rate_1920x1080.max_value): + raise ValueError(f"Max rate for 1920x1080 cannot be less than " + f"{self._max_rate_1920x1080.min_value} and more than " + f"{self._max_rate_1920x1080.max_value}.") + self._max_rate_1920x1080.default_value = max_rate_1920x1080 + + @property + def max_rate_2560x1080(self) -> int: + """ + Set and get maximum rate for resolution 2560x1080 + + Returns: + object of int type + """ + return self._max_rate_2560x1080.default_value + + @max_rate_2560x1080.setter + def max_rate_2560x1080(self, max_rate_2560x1080: int): + if not ( + self._max_rate_2560x1080.min_value <= max_rate_2560x1080 < self._max_rate_2560x1080.max_value): + raise ValueError(f"Max rate for 2560x1080 cannot be less than " + f"{self._max_rate_2560x1080.min_value} and more than " + f"{self._max_rate_2560x1080.max_value}.") + self._max_rate_2560x1080.default_value = max_rate_2560x1080 + + @property + def max_rate_2560x1440(self) -> int: + """ + Set and get maximum rate for resolution 2560x1440 + + Returns: + object of int type + """ + return self._max_rate_2560x1440.default_value + + @max_rate_2560x1440.setter + def max_rate_2560x1440(self, max_rate_2560x1440: int): + if not ( + self._max_rate_2560x1440.min_value <= max_rate_2560x1440 < self._max_rate_2560x1440.max_value): + raise ValueError(f"Max rate for 2560x1080 cannot be less than " + f"{self._max_rate_2560x1440.min_value} and more than " + f"{self._max_rate_2560x1440.max_value}.") + self._max_rate_2560x1440.default_value = max_rate_2560x1440 + + @property + def max_rate_3840x2160(self) -> int: + """ + Set and get maximum rate for resolution 3840x2160 + + Returns: + object of int type + """ + return self._max_rate_3840x2160.default_value + + @max_rate_3840x2160.setter + def max_rate_3840x2160(self, max_rate_3840x2160: int): + if not ( + self._max_rate_3840x2160.min_value <= max_rate_3840x2160 < self._max_rate_3840x2160.max_value): + raise ValueError(f"Max rate for 3840x2160 cannot be less than " + f"{self._max_rate_3840x2160.min_value} and more than " + f"{self._max_rate_3840x2160.max_value}.") + self._max_rate_3840x2160.default_value = max_rate_3840x2160 + + @property + def max_rate_4096x2160(self) -> int: + """ + Set and get maximum rate for resolution 4096x2160 + + Returns: + object of int type + """ + return self._max_rate_4096x2160.default_value + + @max_rate_4096x2160.setter + def max_rate_4096x2160(self, max_rate_4096x2160: int): + if not ( + self._max_rate_4096x2160.min_value <= max_rate_4096x2160 < self._max_rate_4096x2160.max_value): + raise ValueError(f"Max rate for 4096x2160 cannot be less than " + f"{self._max_rate_4096x2160.min_value} and more than " + f"{self._max_rate_4096x2160.max_value}.") + self._max_rate_4096x2160.default_value = max_rate_4096x2160 + + @property + def max_rate_5120x2160(self) -> int: + """ + Set and get maximum rate for resolution 5120x2160 + + Returns: + object of int type + """ + return self._max_rate_5120x2160.default_value + + @max_rate_5120x2160.setter + def max_rate_5120x2160(self, max_rate_5120x2160: int): + if not ( + self._max_rate_5120x2160.min_value <= max_rate_5120x2160 < self._max_rate_5120x2160.max_value): + raise ValueError(f"Max rate for 5120x2160 cannot be less than " + f"{self._max_rate_5120x2160.min_value} and more than " + f"{self._max_rate_5120x2160.max_value}.") + self._max_rate_5120x2160.default_value = max_rate_5120x2160 + + @property + def max_rate_7680x4320(self) -> int: + """ + Set and get maximum rate for resolution 7680x4320 + + Returns: + object of int type + """ + return self._max_rate_7680x4320.default_value + + @max_rate_7680x4320.setter + def max_rate_7680x4320(self, max_rate_7680x4320: int): + if not ( + self._max_rate_7680x4320.min_value <= max_rate_7680x4320 < self._max_rate_7680x4320.max_value): + raise ValueError(f"Max rate for 7680x4320 cannot be less than " + f"{self._max_rate_7680x4320.min_value} and more than " + f"{self._max_rate_7680x4320.max_value}.") + self._max_rate_7680x4320.default_value = max_rate_7680x4320 + + @property + def max_rate_10240x4320(self) -> int: + """ + Set and get maximum rate for resolution 10240x4320 + + Returns: + object of int type + """ + return self._max_rate_10240x4320.default_value + + @max_rate_10240x4320.setter + def max_rate_10240x4320(self, max_rate_10240x4320: int): + if not ( + self._max_rate_10240x4320.min_value <= max_rate_10240x4320 < self._max_rate_10240x4320.max_value): + raise ValueError(f"Max rate for 10240x4320 cannot be less than " + f"{self._max_rate_10240x4320.min_value} and more than " + f"{self._max_rate_10240x4320.max_value}.") + self._max_rate_10240x4320.default_value = max_rate_10240x4320 + + def clear_max_rates(self): + """ + Clear rates settings. + """ + self.max_rate_1920x1080 = 0 + self.max_rate_2560x1080 = 0 + self.max_rate_2560x1440 = 0 + self.max_rate_3840x2160 = 0 + self.max_rate_4096x2160 = 0 + self.max_rate_5120x2160 = 0 + self.max_rate_7680x4320 = 0 + self.max_rate_10240x4320 = 0 + + +class AdaptiveSyncDp14ConfigTab(AdaptiveSyncDpConfigTabBase): + """ + Class `AdaptiveSyncDp14ConfigTab` allows working with parameters for DP 1.4 Adaptive-Sync tests. + - Set and get capabilities `as_caps` type `AdaptiveSyncDp14Caps`. + - Set and get adaptive-sync range minimum rate `as_range_min_rate` type `RangeMinRate`. + - Set and get maximum rate for resolution 1920x1080 `max_rate_1920x1080`. + - Set and get maximum rate for resolution 2560x1080 `max_rate_2560x1080`. + - Set and get maximum rate for resolution 2560x1440 `max_rate_2560x1440`. + - Set and get maximum rate for resolution 3840x2160 `max_rate_3840x2160`. + - Set and get maximum rate for resolution 4096x2160 `max_rate_4096x2160`. + - Set and get maximum rate for resolution 5120x2160 `max_rate_5120x2160`. + - Set and get maximum rate for resolution 7680x4320 `max_rate_7680x4320`. + - Set and get maximum rate for resolution 10240x4320 `max_rate_10240x4320`. + """ + def __init__(self, json_obj): + super().__init__(AdaptiveSyncDpCaps(json_obj["TSI_DP14_SRCCTS_AS_DUT_CAPS"]), + Param(json_obj["TSI_DP14_SRCCTS_AS_RANGE_MIN_RATE"]), + Param(json_obj["TSI_DP14_SRCCTS_AS_RANGE_MAX_RATE_0"]), + Param(json_obj["TSI_DP14_SRCCTS_AS_RANGE_MAX_RATE_1"]), + Param(json_obj["TSI_DP14_SRCCTS_AS_RANGE_MAX_RATE_2"]), + Param(json_obj["TSI_DP14_SRCCTS_AS_RANGE_MAX_RATE_3"]), + Param(json_obj["TSI_DP14_SRCCTS_AS_RANGE_MAX_RATE_4"]), + Param(json_obj["TSI_DP14_SRCCTS_AS_RANGE_MAX_RATE_5"]), + Param(json_obj["TSI_DP14_SRCCTS_AS_RANGE_MAX_RATE_6"]), + Param(json_obj["TSI_DP14_SRCCTS_AS_RANGE_MAX_RATE_7"]) + ) + + +class AdaptiveSyncDp21ConfigTab(AdaptiveSyncDpConfigTabBase): + """ + Class `AdaptiveSyncDp21ConfigTab` inherited of class`AdaptiveSyncDp14ConfigTab` allows working with + parameters for DP 2.1 Adaptive-Sync tests + Class `AdaptiveSyncDp21ConfigTab` has all the `AdaptiveSyncDp14ConfigTab` functionality. + """ + def __init__(self, json_obj): + super().__init__(AdaptiveSyncDpCaps(json_obj["TSI_DP20_SRCCTS_AS_DUT_CAPS"]), + Param(json_obj["TSI_DP20_SRCCTS_AS_RANGE_MIN_RATE"]), + Param(json_obj["TSI_DP20_SRCCTS_AS_RANGE_MAX_RATE_0"]), + Param(json_obj["TSI_DP20_SRCCTS_AS_RANGE_MAX_RATE_1"]), + Param(json_obj["TSI_DP20_SRCCTS_AS_RANGE_MAX_RATE_2"]), + Param(json_obj["TSI_DP20_SRCCTS_AS_RANGE_MAX_RATE_3"]), + Param(json_obj["TSI_DP20_SRCCTS_AS_RANGE_MAX_RATE_4"]), + Param(json_obj["TSI_DP20_SRCCTS_AS_RANGE_MAX_RATE_5"]), + Param(json_obj["TSI_DP20_SRCCTS_AS_RANGE_MAX_RATE_6"]), + Param(json_obj["TSI_DP20_SRCCTS_AS_RANGE_MAX_RATE_7"]) + ) diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/dp_source_audio_tab.py b/UniTAP/dev/modules/dut_tests/dut_default_params/dp_source_audio_tab.py new file mode 100644 index 0000000..8cd5aa5 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/dp_source_audio_tab.py @@ -0,0 +1,305 @@ +from typing import List +from enum import IntEnum + +from UniTAP.dev.modules.dut_tests.test_group_params_types import Param + + +class AudioTestPattern(IntEnum): + """ + Describes available values for audio test pattern. + """ + OperatorSpecificWaveform = 0 + SawtoothWaveform = 1 + + +class AudioDpChannelsConfig: + """ + Class `AudioDp14ChannelsConfig` allows working with audio channel configurations. + - Get Channel count `channels_count`. + - Select type of audio channel `select_channels`. + + List of audio channel types: + - FL+FR + - RL+RR + - FLH+FRH + - TC + - LFE + - RLC+RRC + - FLW+FRW + - FHC + - FC + - RC + - FLC+FRC + """ + __AUDIO_CHANNELS_INFO = {"FL+FR": 0x001, "RL+RR": 0x008, "FLH+FRH": 0x100, "TC": 0x200, 'LFE': 0x002, + "RLC+RRC": 0x040, "FLW+FRW": 0x080, "FHC": 0x400, 'FC': 0x004, 'RC': 0x010, + "FLC+FRC": 0x020} + + __AUDIO_CHANNELS_COUNT = {0x001: 2, 0x008: 2, 0x100: 2, 0x200: 1, 0x002: 1, 0x040: 2, 0x080: 2, 0x400: 1, 0x004: 1, + 0x010: 1, 0x020: 2} + + __AUDIO_CHANNELS_VARIANTS = '\n'.join(__AUDIO_CHANNELS_INFO.keys()) + + def __init__(self, parameters): + self.__channels_count = parameters[0] + self.__channels_config = parameters[1] + + @property + def channels_count(self) -> int: + """ + Get channels count. + + Returns: + object of int type + """ + return self.__channels_count.default_value + + def __check_audio(self, channel_type: str) -> bool: + old_value = new_value = self.__channels_config.default_value + new_value &= ~ self.__AUDIO_CHANNELS_INFO.get(channel_type) + return old_value == new_value + + def __already_selected(self) -> List[str]: + selected = [] + for item in self.__AUDIO_CHANNELS_INFO.keys(): + if self.__channels_config.default_value & self.__AUDIO_CHANNELS_INFO.get(item): + selected.append(item) + return selected + + def select_channels(self, channels: List[str], enable: bool): + """ + Set channels types. + + Args: + channels (list[str]) + enable (bool) + """ + if all(item in list(self.__AUDIO_CHANNELS_INFO.keys()) for item in channels) is False: + channels_type = '\n'.join(channels) + raise ValueError(f"Incorrect channels type{'s' if len(channels) > 1 else ''} {channels_type}. " + f"Available audio channels variants:\n{self.__AUDIO_CHANNELS_VARIANTS}") + if enable: + count = int(self.channels_count) + new_value = self.__channels_config.default_value + for channel_type in channels: + if self.__check_audio(channel_type): + count += self.__AUDIO_CHANNELS_COUNT.get(self.__AUDIO_CHANNELS_INFO.get(channel_type)) + new_value |= self.__AUDIO_CHANNELS_INFO.get(channel_type) + if count >= 9: + already_selected_list = self.__already_selected() + already_selected_list.extend(channels) + already_selected = ', '.join(already_selected_list) + raise ValueError(f"Max supported audio channels = 8. Current channels count = {count}, " + f"selected - {already_selected}") + else: + self.__channels_config.default_value |= new_value + self.__channels_count.default_value = count + else: + count = 0 + old_value = new_value = self.__channels_config.default_value + for channel_type in channels: + if not self.__check_audio(channel_type): + count += self.__AUDIO_CHANNELS_COUNT.get(self.__AUDIO_CHANNELS_INFO.get(channel_type)) + new_value &= ~self.__AUDIO_CHANNELS_INFO.get(channel_type) + if old_value != new_value: + self.__channels_count.default_value -= count + self.__channels_config.default_value = new_value + + def clear_all(self): + """ + Clear channel count and configs. + """ + self.__channels_count.default_value = 0 + self.__channels_config.default_value = 0 + + +class AudioSourceDpSettingTabBase: + __SAMPLE_RATE = [32000, 44100, 48000, 88200, 96000, 176400, 192000] + __available_variants = '\n'.join(map(str, __SAMPLE_RATE)) + + def __init__(self, + min_sample_rate, + max_sample_rate, + min_ch_config_min_rate, + max_ch_config_min_rate, + min_ch_config_max_rate, + max_ch_config_max_rate, + audio_pattern, + sample_size): + self.__min_sample_rate = min_sample_rate + self.__max_sample_rate = max_sample_rate + self.__min_ch_config_min_rate = AudioDpChannelsConfig(min_ch_config_min_rate) + self.__max_ch_config_min_rate = AudioDpChannelsConfig(max_ch_config_min_rate) + self.__min_ch_config_max_rate = AudioDpChannelsConfig(min_ch_config_max_rate) + self.__max_ch_config_max_rate = AudioDpChannelsConfig(max_ch_config_max_rate) + self.__audio_pattern = audio_pattern # AudioTestPattern + self.__sample_size = sample_size + + @property + def min_sample_rate(self) -> int: + """ + Set and get minimum sample rate. + + Returns: + object of int type + """ + return int(self.__min_sample_rate.default_value) + + @min_sample_rate.setter + def min_sample_rate(self, min_sample_rate: int): + if min_sample_rate not in self.__SAMPLE_RATE: + raise ValueError( + f"Value {min_sample_rate} not in available variants:\n{self.__available_variants}") + self.__min_sample_rate.default_value = min_sample_rate + + @property + def max_sample_rate(self) -> int: + """ + Set and get maximum sample rate. + + Returns: + object of int type + """ + return int(self.__max_sample_rate.default_value) + + @max_sample_rate.setter + def max_sample_rate(self, max_sample_rate: int): + if max_sample_rate not in self.__SAMPLE_RATE: + raise ValueError( + f"Value {max_sample_rate} not in available variants:\n{self.__available_variants}") + self.__max_sample_rate.default_value = max_sample_rate + + @property + def min_ch_config_min_rate(self) -> AudioDpChannelsConfig: + """ + Get object of config minimum channels and minimum sample rate. + + Returns: + object of `AudioDp14ChannelsConfig` type + """ + return self.__min_ch_config_min_rate + + @property + def max_ch_config_min_rate(self) -> AudioDpChannelsConfig: + """ + Get object of config maximum channels and minimum sample rate. + + Returns: + object of `AudioDp14ChannelsConfig` type + """ + return self.__max_ch_config_min_rate + + @property + def min_ch_config_max_rate(self) -> AudioDpChannelsConfig: + """ + Get object of config minimum channels and maximum sample rate. + + Returns: + object of `AudioDp14ChannelsConfig` type + """ + return self.__min_ch_config_max_rate + + @property + def max_ch_config_max_rate(self) -> AudioDpChannelsConfig: + """ + Get object of config maximum channels and maximum sample rate. + + Returns: + object of `AudioDp14ChannelsConfig` type + """ + return self.__max_ch_config_max_rate + + @property + def audio_pattern(self): + """ + Set and get audio pattern. + + Returns: + object of `AudioTestPattern` type + """ + return self.__audio_pattern + + @audio_pattern.setter + def audio_pattern(self, audio_pattern: AudioTestPattern): + self.__audio_pattern.default_value = audio_pattern.value + + @property + def sample_size(self) -> int: + """ + Set and get sample size. + + Returns: + object of int type + """ + return int(self.__sample_size.default_value) + + @sample_size.setter + def sample_size(self, sample_size: int): + if sample_size not in [16, 20, 24]: + raise ValueError(f"Value {sample_size} not in available variants:\n16\n20\n24\n") + self.__sample_size.default_value = sample_size + + def clear_all(self): + """ + Clear all settings. + """ + self.__min_sample_rate.default_value = 32000 + self.__max_sample_rate.default_value = 32000 + self.min_ch_config_min_rate.clear_all() + self.max_ch_config_min_rate.clear_all() + self.min_ch_config_max_rate.clear_all() + self.max_ch_config_max_rate.clear_all() + self.__sample_size.default_value = 16 + + +class AudioSourceDp14SettingTab(AudioSourceDpSettingTabBase): + """ + Class `AudioSourceDp14SettingTab` allows working with audio parameters for DP 1.4 DP LLCTS tests. + - Set and get minimum sample rate `min_sample_rate`. + - Set and get maximum sample rate `max_sample_rate`. + - Set and get minimum channels and minimum sample rate `min_ch_config_min_rate` type `AudioDp14ChannelsConfig`. + - Set and get maximum channels and minimum sample rate `max_ch_config_min_rate` type `AudioDp14ChannelsConfig`. + - Set and get minimum channels and maximum sample rate `min_ch_config_max_rate` type `AudioDp14ChannelsConfig`. + - Set and get maximum channels and maximum sample rate `max_ch_config_max_rate` type `AudioDp14ChannelsConfig`. + - Set and get audio pattern `audio_pattern` type `AudioTestPattern`. + - Set and get sample `sample_size``. + """ + + def __init__(self, json_obj): + super().__init__( + Param(json_obj["TSI_DP14_SRCCTS_MIN_SAMPLE_RATE"]), + Param(json_obj["TSI_DP14_SRCCTS_MAX_SAMPLE_RATE"]), + (Param(json_obj["TSI_DP14_SRCCTS_CHANNELS1"]), + Param(json_obj["TSI_DP14_SRCCTS_CH_ALLOC1"])), + (Param(json_obj["TSI_DP14_SRCCTS_CHANNELS2"]), + Param(json_obj["TSI_DP14_SRCCTS_CH_ALLOC2"])), + (Param(json_obj["TSI_DP14_SRCCTS_CHANNELS3"]), + Param(json_obj["TSI_DP14_SRCCTS_CH_ALLOC3"])), + (Param(json_obj["TSI_DP14_SRCCTS_CHANNELS4"]), + Param(json_obj["TSI_DP14_SRCCTS_CH_ALLOC4"])), + Param(json_obj["TSI_DP14_SRCCTS_AUDIO_TEST_PATTERN"]), + Param(json_obj["TSI_DP14_SRCCTS_EDID_SAMPLE_SIZE"]) + ) + + +class AudioSourceDp21SettingTab(AudioSourceDpSettingTabBase): + """ + Class `AudioSourceDp21SettingTab` inherited of class`AudioSourceDp14SettingTab` allows working with + audio parameters for DP 1.4 DP LLCTS tests. + Class `AudioSourceDp21SettingTab` has all the `AudioSourceDp14SettingTab` functionality. + """ + def __init__(self, json_obj): + super().__init__( + Param(json_obj["TSI_DP20_SRCCTS_MIN_SAMPLE_RATE"]), + Param(json_obj["TSI_DP20_SRCCTS_MAX_SAMPLE_RATE"]), + (Param(json_obj["TSI_DP20_SRCCTS_CHANNELS1"]), + Param(json_obj["TSI_DP20_SRCCTS_CH_ALLOC1"])), + (Param(json_obj["TSI_DP20_SRCCTS_CHANNELS2"]), + Param(json_obj["TSI_DP20_SRCCTS_CH_ALLOC2"])), + (Param(json_obj["TSI_DP20_SRCCTS_CHANNELS3"]), + Param(json_obj["TSI_DP20_SRCCTS_CH_ALLOC3"])), + (Param(json_obj["TSI_DP20_SRCCTS_CHANNELS4"]), + Param(json_obj["TSI_DP20_SRCCTS_CH_ALLOC4"])), + Param(json_obj["TSI_DP20_SRCCTS_AUDIO_TEST_PATTERN"]), + Param(json_obj["TSI_DP20_SRCCTS_EDID_SAMPLE_SIZE"]) + ) diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/dp_source_display_id_tab.py b/UniTAP/dev/modules/dut_tests/dut_default_params/dp_source_display_id_tab.py new file mode 100644 index 0000000..d65435b --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/dp_source_display_id_tab.py @@ -0,0 +1,1411 @@ +from UniTAP.dev.modules.dut_tests.test_group_params_types import Param +from UniTAP.dev.modules.dut_tests.test_utils import update_default_value +from UniTAP.common import AudioFormat +from typing import TypeVar + +_dict_sample_rate = { + 1: 32000, + 2: 44100, + 3: 48000, + 4: 88200, + 5: 96000, + 6: 176400, + 7: 192000 +} + +_rev_dict_sample_rate = { + 32000: 1, + 44100: 2, + 48000: 3, + 88200: 4, + 96000: 5, + 176400: 6, + 192000: 7 +} + +_dict_sample_size = { + 1: 16, + 2: 20, + 3: 24 +} + +_rev_dict_sample_size = { + 16: 1, + 20: 2, + 24: 3 +} + +_dict_audio_type = { + 1: AudioFormat.L_PCM +} + +_rev_dict_audio_type = { + AudioFormat.L_PCM: 1 +} + + +class AudioMode: + """ + Class `AudioMode` describes audio mode for Display ID tests. Allows working with audio mode parameters. + - Set and get channel count `channel_count`. + - Set and get sample size `sample_size`. + - Set and get sample rate `sample_rate`. + - Set and get audio type `audio_type`. + """ + + def __init__(self, json_obj): + self.__value = Param(json_obj) + + def clear(self): + """ + Disable audio mode. + """ + self.__value.clear() + + @property + def channel_count(self) -> int: + """ + Set and get channels count. + + Returns: + object of int type + """ + return (self.__value.default_value & 0x7) + 1 + + @channel_count.setter + def channel_count(self, channel_count: int): + if channel_count >= 9 or channel_count <= 1: + raise ValueError(f"Channel count must be between 2-8") + self.__value.default_value &= ~0x7 + self.__value.default_value |= ((channel_count - 1) & 0x7) + + @property + def sample_size(self) -> int: + """ + Set and get sample size. + + Returns: + object of int type + """ + return _dict_sample_size.get((self.__value.default_value >> 8) & 0x3, 0) + + @sample_size.setter + def sample_size(self, sample_size: int): + if sample_size not in [16, 20, 24]: + raise ValueError(f"Value {sample_size} not in available variants:\n16\n20\n24\n") + self.__value.default_value &= ~(0x3 << 8) + self.__value.default_value |= _rev_dict_sample_size.get(sample_size, 0) << 8 + + @property + def sample_rate(self) -> int: + """ + Set and get sample rate. + + Returns: + object of int type + """ + return _dict_sample_rate.get(self.__value.default_value >> 10 & 0x7, 0) + + @sample_rate.setter + def sample_rate(self, sample_rate: int): + if _rev_dict_sample_rate.get(sample_rate) is None: + available_variants = '\n'.join(map(str, _rev_dict_sample_rate.keys())) + raise ValueError(f"Value {sample_rate} not in available variants:\n{available_variants}") + self.__value.default_value &= ~(0x7 << 10) + self.__value.default_value |= _rev_dict_sample_rate.get(sample_rate, 0) << 10 + + @property + def audio_type(self) -> AudioFormat: + """ + Get audio type. + + Returns: + object of audio type + """ + return _dict_audio_type.get(self.__value.default_value >> 4, AudioFormat.Unknown) + + @audio_type.setter + def audio_type(self, value: AudioFormat): + """ + Get audio type. + + Returns: + object of audio type + """ + if isinstance(value, AudioFormat): + self.__value.default_value &= ~(0xF << 4) + self.__value.default_value |= _rev_dict_audio_type.get(value, 0) << 4 + else: + raise TypeError(f"Incorrect value type {type(value)}. " + f"It must be UniTAP.AudioFormat.") + + +class DisplayIdDpAudioBase: + def __init__(self, audio_mode_list): + self._audio_mode_1 = AudioMode(audio_mode_list[0]) + self._audio_mode_2 = AudioMode(audio_mode_list[1]) + self._audio_mode_3 = AudioMode(audio_mode_list[2]) + self._audio_mode_4 = AudioMode(audio_mode_list[3]) + self._audio_mode_5 = AudioMode(audio_mode_list[4]) + self._audio_mode_6 = AudioMode(audio_mode_list[5]) + self._audio_mode_7 = AudioMode(audio_mode_list[6]) + self._audio_mode_8 = AudioMode(audio_mode_list[7]) + + @property + def audio_mode_1(self) -> AudioMode: + """ + Set and get audio mode 1. + + Returns: + object of `AudioMode` type + """ + return self._audio_mode_1 + + @audio_mode_1.setter + def audio_mode_1(self, audio_mode: AudioMode): + self._audio_mode_1 = audio_mode + + @property + def audio_mode_2(self) -> AudioMode: + """ + Set and get audio mode 2. + + Returns: + object of `AudioMode` type + """ + return self._audio_mode_2 + + @audio_mode_2.setter + def audio_mode_2(self, audio_mode: AudioMode): + self._audio_mode_2 = audio_mode + + @property + def audio_mode_3(self) -> AudioMode: + """ + Set and get audio mode 3. + + Returns: + object of `AudioMode` type + """ + return self._audio_mode_3 + + @audio_mode_3.setter + def audio_mode_3(self, audio_mode: AudioMode): + self._audio_mode_3 = audio_mode + + @property + def audio_mode_4(self) -> AudioMode: + """ + Set and get audio mode 4. + + Returns: + object of `AudioMode` type + """ + return self._audio_mode_4 + + @audio_mode_4.setter + def audio_mode_4(self, audio_mode: AudioMode): + self._audio_mode_4 = audio_mode + + @property + def audio_mode_5(self) -> AudioMode: + """ + Set and get audio mode 5. + + Returns: + object of `AudioMode` type + """ + return self._audio_mode_5 + + @audio_mode_5.setter + def audio_mode_5(self, audio_mode: AudioMode): + self._audio_mode_5 = audio_mode + + @property + def audio_mode_6(self) -> AudioMode: + """ + Set and get audio mode 6. + + Returns: + object of `AudioMode` type + """ + return self._audio_mode_6 + + @audio_mode_6.setter + def audio_mode_6(self, audio_mode: AudioMode): + self._audio_mode_6 = audio_mode + + @property + def audio_mode_7(self) -> AudioMode: + """ + Set and get audio mode 7. + + Returns: + object of `AudioMode` type + """ + return self._audio_mode_7 + + @audio_mode_7.setter + def audio_mode_7(self, audio_mode: AudioMode): + self._audio_mode_7 = audio_mode + + @property + def audio_mode_8(self) -> AudioMode: + """ + Set and get audio mode 8. + + Returns: + object of `AudioMode` type + """ + return self._audio_mode_8 + + @audio_mode_8.setter + def audio_mode_8(self, audio_mode: AudioMode): + self._audio_mode_8 = audio_mode + + def clear_all(self): + """ + Clear all settings. + """ + self._audio_mode_1.clear() + self._audio_mode_2.clear() + self._audio_mode_3.clear() + self._audio_mode_4.clear() + self._audio_mode_5.clear() + self._audio_mode_6.clear() + self._audio_mode_7.clear() + self._audio_mode_8.clear() + + +class DisplayIdDp14Audio(DisplayIdDpAudioBase): + """ + Class `DisplayIdDp14Audio` contains 8 available audio modes for Display ID tests. Allows settings each mode. + - Audio mode 1 `audio_mode_1`. + - Audio mode 2 `audio_mode_2`. + - Audio mode 3 `audio_mode_3`. + - Audio mode 4 `audio_mode_4`. + - Audio mode 5 `audio_mode_5`. + - Audio mode 6 `audio_mode_6`. + - Audio mode 7 `audio_mode_7`. + - Audio mode 8 `audio_mode_8`. + """ + + def __init__(self, json_obj): + super().__init__([ + json_obj["TSI_DP14_SRCCTS_TESTED_AUDIO_MODE_0"], + json_obj["TSI_DP14_SRCCTS_TESTED_AUDIO_MODE_1"], + json_obj["TSI_DP14_SRCCTS_TESTED_AUDIO_MODE_2"], + json_obj["TSI_DP14_SRCCTS_TESTED_AUDIO_MODE_3"], + json_obj["TSI_DP14_SRCCTS_TESTED_AUDIO_MODE_4"], + json_obj["TSI_DP14_SRCCTS_TESTED_AUDIO_MODE_5"], + json_obj["TSI_DP14_SRCCTS_TESTED_AUDIO_MODE_6"], + json_obj["TSI_DP14_SRCCTS_TESTED_AUDIO_MODE_7"] + ]) + + +class DisplayIdDp21Audio(DisplayIdDpAudioBase): + """ + Class `DisplayIdDp21Audio` inherited of class`DisplayIdDp14Audio` allows working with parameters for Display ID + LLCTS tests. + Class `DisplayIdDp21Audio` has all the `DisplayIdDp14Audio` functionality. + """ + + def __init__(self, json_obj): + super().__init__([ + json_obj["TSI_DP20_SRCCTS_TESTED_AUDIO_MODE_0"], + json_obj["TSI_DP20_SRCCTS_TESTED_AUDIO_MODE_1"], + json_obj["TSI_DP20_SRCCTS_TESTED_AUDIO_MODE_2"], + json_obj["TSI_DP20_SRCCTS_TESTED_AUDIO_MODE_3"], + json_obj["TSI_DP20_SRCCTS_TESTED_AUDIO_MODE_4"], + json_obj["TSI_DP20_SRCCTS_TESTED_AUDIO_MODE_5"], + json_obj["TSI_DP20_SRCCTS_TESTED_AUDIO_MODE_6"], + json_obj["TSI_DP20_SRCCTS_TESTED_AUDIO_MODE_7"] + ]) + + +class DmtTiming: + """ + Class `DmtTiming` describes all available DMT timings for Display ID LLCTS tests. Allows enabling and disable + timings. + - 800x600 60Hz `T_800x600_60Hz`. + - 1024x768 60Hz `T_1024x768_60Hz`. + - 1280x768 60Hz `T_1280x768_60Hz`. + - 1280x800 60Hz `T_1280x800_60Hz`. + - 1280x960 60Hz `T_1280x960_60Hz`. + - 1280x1024 60Hz `T_1280x1024_60Hz`. + - 1360x768 60Hz `T_1360x768_60Hz`. + - 1400x1050 60Hz `T_1400x1050_60Hz`. + - 1600x1200 60Hz `T_1600x1200_60Hz`. + - 1680x1050 60Hz `T_1680x1050_60Hz`. + - 1856x1392 60Hz `T_1856x1392_60Hz`. + - 1920x1080 60Hz `T_1920x1080_60Hz`. + - 1920x1200 60Hz `T_1920x1200_60Hz`. + - 1920x1440 60Hz `T_1920x1440_60Hz`. + - Disable all timings `clear_all`. + """ + + def __init__(self, json_obj): + self.__param = Param(json_obj) + + @property + def T_800x600_60Hz(self) -> bool: + """ + Set and get 800x600 60Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(0, bool) + + @T_800x600_60Hz.setter + def T_800x600_60Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 0) + + @property + def T_1024x768_60Hz(self) -> bool: + """ + Set and get 1024x768 60Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(1, bool) + + @T_1024x768_60Hz.setter + def T_1024x768_60Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 1) + + @property + def T_1280x768_60Hz(self) -> bool: + """ + Set and get 1280x768 60Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(2, bool) + + @T_1280x768_60Hz.setter + def T_1280x768_60Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 2) + + @property + def T_1280x800_60Hz(self) -> bool: + """ + Set and get 1280x800 60Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(3, bool) + + @T_1280x800_60Hz.setter + def T_1280x800_60Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 3) + + @property + def T_1280x960_60Hz(self) -> bool: + """ + Set and get 1280x960 60Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(4, bool) + + @T_1280x960_60Hz.setter + def T_1280x960_60Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 4) + + @property + def T_1280x1024_60Hz(self) -> bool: + """ + Set and get 1280x1024 60Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(5, bool) + + @T_1280x1024_60Hz.setter + def T_1280x1024_60Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 5) + + @property + def T_1360x768_60Hz(self) -> bool: + """ + Set and get 1360x768 60Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(6, bool) + + @T_1360x768_60Hz.setter + def T_1360x768_60Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 6) + + @property + def T_1400x1050_60Hz(self) -> bool: + """ + Set and get 1400x1050 60Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(7, bool) + + @T_1400x1050_60Hz.setter + def T_1400x1050_60Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 7) + + @property + def T_1600x1200_60Hz(self) -> bool: + """ + Set and get 1600x1200 60Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(8, bool) + + @T_1600x1200_60Hz.setter + def T_1600x1200_60Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 8) + + @property + def T_1680x1050_60Hz(self) -> bool: + """ + Set and get 1680x1050 60Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(9, bool) + + @T_1680x1050_60Hz.setter + def T_1680x1050_60Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 9) + + @property + def T_1856x1392_60Hz(self) -> bool: + """ + Set and get 1856x1392 60Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(10, bool) + + @T_1856x1392_60Hz.setter + def T_1856x1392_60Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 10) + + @property + def T_1920x1080_60Hz(self) -> bool: + """ + Set and get 1920x1080 60Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(11, bool) + + @T_1920x1080_60Hz.setter + def T_1920x1080_60Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 11) + + @property + def T_1920x1200_60Hz(self) -> bool: + """ + Set and get 1920x1200 60Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(12, bool) + + @T_1920x1200_60Hz.setter + def T_1920x1200_60Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 12) + + @property + def T_1920x1440_60Hz(self) -> bool: + """ + Set and get 1920x1440 60Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(13, bool) + + @T_1920x1440_60Hz.setter + def T_1920x1440_60Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 13) + + def clear_all(self): + """ + Disable all timings. + """ + self.T_800x600_60Hz = False + self.T_1024x768_60Hz = False + self.T_1280x768_60Hz = False + self.T_1280x800_60Hz = False + self.T_1280x960_60Hz = False + self.T_1280x1024_60Hz = False + self.T_1360x768_60Hz = False + self.T_1400x1050_60Hz = False + self.T_1600x1200_60Hz = False + self.T_1680x1050_60Hz = False + self.T_1856x1392_60Hz = False + self.T_1920x1080_60Hz = False + self.T_1920x1200_60Hz = False + self.T_1920x1440_60Hz = False + + +class CtaTiming: + """ + Class `CtaTiming` describes all available DMT timings for Display ID LLCTS tests. Allows enabling and disable + timings. + - 720x480 60Hz `T_720x480_60Hz` + - 1280x720 60Hz `T_1280x720_60Hz` + - 1920x1080 60Hz `T_1920x1080_60Hz` + - 1280x720 120Hz `T_1280x720_120Hz` + - 720x480 120Hz `T_720x480_120Hz` + - 1920x1080 120Hz `T_1920x1080_120Hz` + - 1680x720 60Hz `T_1680x720_60Hz` + - 1680x720 120Hz `T_1680x720_120Hz` + - 2560x1080 60Hz `T_2560x1080_60Hz` + - 2560x1080 120Hz `T_2560x1080_120Hz` + - 3840x2160 60Hz `T_3840x2160_60Hz` + - 4096x2160 60H `T_4096x2160_60Hz` + - 3840x2160 120Hz `T_3840x2160_120Hz` + - 5120x2160 60Hz `T_5120x2160_60Hz` + - 7680x4320 24Hz `T_7680x4320_24Hz` + - 7680x4320 30Hz `T_7680x4320_30Hz` + - Disable all timings `clear_all`. + """ + + def __init__(self, json_obj): + self.__param = Param(json_obj) + + @property + def T_720x480_60Hz(self) -> bool: + """ + Set and get 720x480 60Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(0, bool) + + @T_720x480_60Hz.setter + def T_720x480_60Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 0) + + @property + def T_1280x720_60Hz(self) -> bool: + """ + Set and get 1280x720 60Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(1, bool) + + @T_1280x720_60Hz.setter + def T_1280x720_60Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 1) + + @property + def T_1920x1080_60Hz(self) -> bool: + """ + Set and get 1920x1080 60Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(2, bool) + + @T_1920x1080_60Hz.setter + def T_1920x1080_60Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 2) + + @property + def T_1280x720_120Hz(self) -> bool: + """ + Set and get 1280x720 120Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(3, bool) + + @T_1280x720_120Hz.setter + def T_1280x720_120Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 3) + + @property + def T_720x480_120Hz(self) -> bool: + """ + Set and get 720x480 120Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(4, bool) + + @T_720x480_120Hz.setter + def T_720x480_120Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 4) + + @property + def T_1920x1080_120Hz(self) -> bool: + """ + Set and get 1920x1080 120Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(5, bool) + + @T_1920x1080_120Hz.setter + def T_1920x1080_120Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 5) + + @property + def T_1680x720_60Hz(self) -> bool: + """ + Set and get 1680x720 60Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(6, bool) + + @T_1680x720_60Hz.setter + def T_1680x720_60Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 6) + + @property + def T_1680x720_120Hz(self) -> bool: + """ + Set and get 1680x720 120Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(7, bool) + + @T_1680x720_120Hz.setter + def T_1680x720_120Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 7) + + @property + def T_2560x1080_60Hz(self) -> bool: + """ + Set and get 2560x1080 60Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(8, bool) + + @T_2560x1080_60Hz.setter + def T_2560x1080_60Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 8) + + @property + def T_2560x1080_120Hz(self) -> bool: + """ + Set and get 2560x1080 120Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(9, bool) + + @T_2560x1080_120Hz.setter + def T_2560x1080_120Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 9) + + @property + def T_3840x2160_60Hz(self) -> bool: + """ + Set and get 3840x2160 60Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(10, bool) + + @T_3840x2160_60Hz.setter + def T_3840x2160_60Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 10) + + @property + def T_4096x2160_60Hz(self) -> bool: + """ + Set and get 4096x2160 60Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(11, bool) + + @T_4096x2160_60Hz.setter + def T_4096x2160_60Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 11) + + @property + def T_3840x2160_120Hz(self) -> bool: + """ + Set and get 3840x2160 120Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(12, bool) + + @T_3840x2160_120Hz.setter + def T_3840x2160_120Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 12) + + @property + def T_5120x2160_60Hz(self) -> bool: + """ + Set and get 5120x2160 60Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(13, bool) + + @T_5120x2160_60Hz.setter + def T_5120x2160_60Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 13) + + @property + def T_7680x4320_24Hz(self) -> bool: + """ + Set and get 7680x4320 24Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(14, bool) + + @T_7680x4320_24Hz.setter + def T_7680x4320_24Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 14) + + @property + def T_7680x4320_30Hz(self) -> bool: + """ + Set and get 7680x4320 30Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(15, bool) + + @T_7680x4320_30Hz.setter + def T_7680x4320_30Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 15) + + def clear_all(self): + """ + Disable all timings. + """ + self.T_720x480_60Hz = False + self.T_1280x720_60Hz = False + self.T_1920x1080_60Hz = False + self.T_1280x720_120Hz = False + self.T_720x480_120Hz = False + self.T_1920x1080_120Hz = False + self.T_1680x720_60Hz = False + self.T_1680x720_120Hz = False + self.T_2560x1080_60Hz = False + self.T_2560x1080_120Hz = False + self.T_3840x2160_60Hz = False + self.T_4096x2160_60Hz = False + self.T_3840x2160_120Hz = False + self.T_5120x2160_60Hz = False + self.T_7680x4320_24Hz = False + self.T_7680x4320_30Hz = False + + +class CvtTiming: + """ + Class `CvtTiming` describes all available DMT timings for Display ID LLCTS tests. Allows enabling and disable + timings. + - 1920x1080 60Hz RB1 `T_1920x1080_60Hz_RB1`. + - 2048x1536 60Hz RB1 `T_2048x1536_60Hz_RB1`. + - 2560x1080 60Hz RB1 `T_2560x1080_60Hz_RB1`. + - 2560x1440 60Hz RB1 `T_2560x1440_60Hz_RB1`. + - 2560x1440 60Hz RB2 `T_2560x1440_60Hz_RB2`. + - 3840x2160 60Hz RB1 `T_3840x2160_60Hz_RB1`. + - 3840x2160 60Hz RB2 `T_3840x2160_60Hz_RB2`. + - 3840x2160 60Hz RB3 `T_3840x2160_60Hz_RB3`. + - 4096x2160 60Hz RB1 `T_4096x2160_60Hz_RB1`. + - 4096x2160 60Hz RB2 `T_4096x2160_60Hz_RB2`. + - 4096x2160 60Hz RB3 `T_4096x2160_60Hz_RB3`. + - 5120x2880 60Hz RB1 `T_5120x2880_60Hz_RB1`. + - 5120x2880 60Hz RB2 `T_5120x2880_60Hz_RB2`. + - 5120x2880 60Hz RB3 `T_5120x2880_60Hz_RB3`. + - Disable all timings `clear_all`. + """ + + def __init__(self, json_obj): + self.__param = Param(json_obj) + + @property + def T_1920x1080_60Hz_RB1(self) -> bool: + """ + Set and get 1920x1080 60Hz RB1 timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(0, bool) + + @T_1920x1080_60Hz_RB1.setter + def T_1920x1080_60Hz_RB1(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 0) + + @property + def T_2048x1536_60Hz_RB1(self) -> bool: + """ + Set and get 2048x1536 60Hz RB1 timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(1, bool) + + @T_2048x1536_60Hz_RB1.setter + def T_2048x1536_60Hz_RB1(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 1) + + @property + def T_2560x1080_60Hz_RB1(self) -> bool: + """ + Set and get 2560x1080 60Hz RB1 timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(2, bool) + + @T_2560x1080_60Hz_RB1.setter + def T_2560x1080_60Hz_RB1(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 2) + + @property + def T_2560x1440_60Hz_RB1(self) -> bool: + """ + Set and get 2560x1440 60Hz RB1 timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(3, bool) + + @T_2560x1440_60Hz_RB1.setter + def T_2560x1440_60Hz_RB1(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 3) + + @property + def T_2560x1440_60Hz_RB2(self) -> bool: + """ + Set and get 2560x1440 60Hz RB2 timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(4, bool) + + @T_2560x1440_60Hz_RB2.setter + def T_2560x1440_60Hz_RB2(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 4) + + @property + def T_3840x2160_60Hz_RB1(self) -> bool: + """ + Set and get 3840x2160 60Hz RB1 timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(5, bool) + + @T_3840x2160_60Hz_RB1.setter + def T_3840x2160_60Hz_RB1(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 5) + + @property + def T_3840x2160_60Hz_RB2(self) -> bool: + """ + Set and get 3840x2160 60Hz RB2 timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(6, bool) + + @T_3840x2160_60Hz_RB2.setter + def T_3840x2160_60Hz_RB2(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 6) + + @property + def T_3840x2160_60Hz_RB3(self) -> bool: + """ + Set and get 3840x2160 60Hz RB3 timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(7, bool) + + @T_3840x2160_60Hz_RB3.setter + def T_3840x2160_60Hz_RB3(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 7) + + @property + def T_4096x2160_60Hz_RB1(self) -> bool: + """ + Set and get 4096x2160 60Hz RB1 timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(8, bool) + + @T_4096x2160_60Hz_RB1.setter + def T_4096x2160_60Hz_RB1(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 8) + + @property + def T_4096x2160_60Hz_RB2(self) -> bool: + """ + Set and get 4096x2160 60Hz RB2 timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(9, bool) + + @T_4096x2160_60Hz_RB2.setter + def T_4096x2160_60Hz_RB2(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 9) + + @property + def T_4096x2160_60Hz_RB3(self) -> bool: + """ + Set and get 4096x2160 60Hz RB3 timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(10, bool) + + @T_4096x2160_60Hz_RB3.setter + def T_4096x2160_60Hz_RB3(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 10) + + @property + def T_5120x2880_60Hz_RB1(self) -> bool: + """ + Set and get 5120x2880 60Hz RB1 timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(11, bool) + + @T_5120x2880_60Hz_RB1.setter + def T_5120x2880_60Hz_RB1(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 11) + + @property + def T_5120x2880_60Hz_RB2(self) -> bool: + """ + Set and get 5120x2880 60Hz RB2 timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(12, bool) + + @T_5120x2880_60Hz_RB2.setter + def T_5120x2880_60Hz_RB2(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 12) + + @property + def T_5120x2880_60Hz_RB3(self) -> bool: + """ + Set and get 5120x2880 60Hz RB3 timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(13, bool) + + @T_5120x2880_60Hz_RB3.setter + def T_5120x2880_60Hz_RB3(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 13) + + def clear_all(self): + """ + Disable all timings. + """ + self.T_1920x1080_60Hz_RB1 = False + self.T_2048x1536_60Hz_RB1 = False + self.T_2560x1080_60Hz_RB1 = False + self.T_2560x1440_60Hz_RB1 = False + self.T_2560x1440_60Hz_RB2 = False + self.T_3840x2160_60Hz_RB1 = False + self.T_3840x2160_60Hz_RB2 = False + self.T_3840x2160_60Hz_RB3 = False + self.T_4096x2160_60Hz_RB1 = False + self.T_4096x2160_60Hz_RB2 = False + self.T_4096x2160_60Hz_RB3 = False + self.T_5120x2880_60Hz_RB1 = False + self.T_5120x2880_60Hz_RB2 = False + self.T_5120x2880_60Hz_RB3 = False + + +class DisplayIdDpVideoTimingsBase: + def __init__(self, dmt_timings, cta_timings, cvt_timings): + self._dmt_timings = dmt_timings + self._cta_timings = cta_timings + self._cvt_timings = cvt_timings + + @property + def dmt_timings(self) -> DmtTiming: + """ + Set and get DMT timings. + + Returns: + object of `DmtTiming` type + """ + return self._dmt_timings + + @dmt_timings.setter + def dmt_timings(self, timings: DmtTiming): + self._dmt_timings = timings + + @property + def cta_timings(self) -> CtaTiming: + """ + Set and get CTA timings. + + Returns: + object of `CtaTiming` type + """ + return self._cta_timings + + @cta_timings.setter + def cta_timings(self, timings: CtaTiming): + self._cta_timings = timings + + @property + def cvt_timings(self) -> CvtTiming: + """ + Set and get CVT timings. + + Returns: + object of `CvtTiming` type + """ + return self._cvt_timings + + @cvt_timings.setter + def cvt_timings(self, timings: CvtTiming): + self._cvt_timings = timings + + def clear_all(self): + """ + Disable all timings. + """ + self.cta_timings.clear_all() + self.cvt_timings.clear_all() + self.dmt_timings.clear_all() + + +class DisplayIdDp14VideoTimings(DisplayIdDpVideoTimingsBase): + """ + Class `DisplayIdDp14VideoTimings` allows working with all timings for Display ID LLCTS tests. + - Configure DMT timings `dmt_timings`. + - Configure CTA timings `cta_timings`. + - Configure CVT timings `cvt_timings`. + - Disable all timings `clear_all`. + """ + + def __init__(self, json_obj): + super().__init__( + DmtTiming(json_obj["TSI_DP14_SRCCTS_DMT_TIMING"]), + CtaTiming(json_obj["TSI_DP14_SRCCTS_CTA_TIMING"]), + CvtTiming(json_obj["TSI_DP14_SRCCTS_CVT_TIMING"]) + ) + + +class DisplayIdDp21VideoTimings(DisplayIdDpVideoTimingsBase): + """ + Class `DisplayIdDp21VideoTimings` inherited of class`DisplayIdDp14VideoTimings` allows working with timings for + Display ID LLCTS tests. + Class `DisplayIdDp21VideoTimings` has all the `DisplayIdDp14VideoTimings` functionality. + """ + + def __init__(self, json_obj): + super().__init__( + DmtTiming(json_obj["TSI_DP20_SRCCTS_DMT_TIMING"]), + CtaTiming(json_obj["TSI_DP20_SRCCTS_CTA_TIMING"]), + CvtTiming(json_obj["TSI_DP20_SRCCTS_CVT_TIMING"]) + ) + + +GeneralDisplayIdParameters = TypeVar("GeneralDisplayIdParameters", + DisplayIdDp14VideoTimings, + DisplayIdDp21VideoTimings) + + +AudioDisplayIdParameters = TypeVar("AudioDisplayIdParameters", + DisplayIdDp14Audio, + DisplayIdDp21Audio) + + +class DisplayIdDpConfigTabBase: + def __init__(self, max_stream_h_active, max_stream_v_active, max_stream_pixel_clock, + display_id_audio, display_id_timings): + self.__max_stream_h_active = max_stream_h_active + self.__max_stream_v_active = max_stream_v_active + self.__max_stream_pixel_clock = max_stream_pixel_clock + self.__display_id_audio = display_id_audio + self.__display_id_timings = display_id_timings + + @property + def max_stream_h_active(self) -> int: + """ + Set and get maximum stream resolution H Active. + + Returns: + object of int type + """ + return self.__max_stream_h_active.default_value + + @max_stream_h_active.setter + def max_stream_h_active(self, max_stream_h_active: int): + if not ( + self.__max_stream_h_active.min_value < max_stream_h_active < self.__max_stream_h_active.max_value): + raise ValueError(f"Max stream H Active cannot be less than " + f"{self.__max_stream_h_active.min_value} and more than " + f"{self.__max_stream_h_active.max_value}.") + self.__max_stream_h_active.default_value = max_stream_h_active + + @property + def max_stream_v_active(self) -> int: + """ + Set and get maximum stream resolution V Active. + + Returns: + object of int type + """ + return self.__max_stream_v_active.default_value + + @max_stream_v_active.setter + def max_stream_v_active(self, max_stream_v_active: int): + if not ( + self.__max_stream_v_active.min_value < max_stream_v_active < self.__max_stream_v_active.max_value): + raise ValueError(f"Max stream V Active cannot be less than " + f"{self.__max_stream_v_active.min_value} and more than " + f"{self.__max_stream_v_active.max_value}.") + self.__max_stream_v_active.default_value = max_stream_v_active + + @property + def max_stream_pixel_clock(self) -> int: + """ + Set and get maximum stream pixel clock. + + Returns: + object of int type + """ + return self.__max_stream_pixel_clock.default_value + + @max_stream_pixel_clock.setter + def max_stream_pixel_clock(self, max_stream_pixel_clock: int): + if not (self.__max_stream_pixel_clock.min_value < max_stream_pixel_clock < + self.__max_stream_pixel_clock.max_value): + raise ValueError(f"Max stream pixel clock cannot be less than " + f"{self.__max_stream_pixel_clock.min_value} and more than " + f"{self.__max_stream_pixel_clock.max_value}.") + self.__max_stream_pixel_clock.default_value = max_stream_pixel_clock + + @property + def audio(self) -> AudioDisplayIdParameters: + """ + Set and get audio settings. + + Returns: + object of `DisplayIdDp14Audio` type + """ + return self.__display_id_audio + + @audio.setter + def audio(self, display_id_audio: DisplayIdDp14Audio): + self.__display_id_audio = display_id_audio + + @property + def general(self) -> GeneralDisplayIdParameters: + """ + Set and get timings settings. + + Returns: + object of `DisplayIdDp14VideoTimings` type + """ + return self.__display_id_timings + + @general.setter + def general(self, display_id_timings: DisplayIdDp14VideoTimings): + self.__display_id_timings = display_id_timings + + def clear_all(self): + self.__max_stream_h_active.default_value = 0 + self.__max_stream_v_active.default_value = 0 + self.__max_stream_pixel_clock.default_value = 0 + self.__display_id_timings.clear_all() + self.__display_id_audio.clear_all() + + +class DisplayIdDp14ConfigTab(DisplayIdDpConfigTabBase): + """ + Class `DisplayIdDp14ConfigTab` allows setting parameters for Display ID tests. + - Maximum stream resolution H Active `max_stream_h_active`. + - Maximum stream resolution V Active `max_stream_v_active`. + - Maximum stream pixel clock `max_stream_pixel_clock`. + - Audio modes `display_id_audio` type `DisplayIdDp14Audio`. + - Timings `display_id_timings` type `DisplayIdDp14VideoTimings`. + - Clear all settings `clear_all`. + """ + + def __init__(self, json_obj): + super().__init__(Param(json_obj["TSI_DP14_SRCCTS_MAX_STREAM_HACTIVE"]), + Param(json_obj["TSI_DP14_SRCCTS_MAX_STREAM_VACTIVE"]), + Param(json_obj["TSI_DP14_SRCCTS_MAX_STREAM_PIXEL_CLOCK"]), + DisplayIdDp14Audio(json_obj), + DisplayIdDp14VideoTimings(json_obj)) + + +class DisplayIdDp21ConfigTab(DisplayIdDpConfigTabBase): + """ + Class `DisplayIdDp21ConfigTab` inherited of class`DisplayIdDp14ConfigTab` allows working with parameters for + Display ID LLCTS tests. + Class `DisplayIdDp21ConfigTab` has all the `DisplayIdDp14ConfigTab` functionality. + """ + + def __init__(self, json_obj): + super().__init__(Param(json_obj["TSI_DP20_SRCCTS_MAX_STREAM_HACTIVE"]), + Param(json_obj["TSI_DP20_SRCCTS_MAX_STREAM_VACTIVE"]), + Param(json_obj["TSI_DP20_SRCCTS_MAX_STREAM_PIXEL_CLOCK"]), + DisplayIdDp21Audio(json_obj), + DisplayIdDp21VideoTimings(json_obj)) + + self.__dynamic_refresh_rate_h_active = Param(json_obj["TSI_DP20_SRCCTS_DRR_HACTIVE"]) + self.__dynamic_refresh_rate_v_active = Param(json_obj["TSI_DP20_SRCCTS_DRR_VACTIVE"]) + self.__dynamic_refresh_rate_pixel_clock = Param(json_obj["TSI_DP20_SRCCTS_DRR_PIXEL_CLOCK"]) + self.__dynamic_refresh_rate_min_vertical = Param(json_obj["TSI_DP20_SRCCTS_DRR_MIN_VRR"]) + self.__dynamic_refresh_rate_max_vertical = Param(json_obj["TSI_DP20_SRCCTS_DRR_MAX_VRR"]) + + @property + def dynamic_refresh_rate_h_active(self) -> int: + """ + Set and get Dynamic Refresh Rate h active. + + Returns: + object of int type + """ + return self.__dynamic_refresh_rate_h_active.default_value + + @dynamic_refresh_rate_h_active.setter + def dynamic_refresh_rate_h_active(self, dynamic_refresh_rate_h_active: int): + if not ( + self.__dynamic_refresh_rate_h_active.min_value < dynamic_refresh_rate_h_active < self.__dynamic_refresh_rate_h_active.max_value): + raise ValueError(f"Dynamic refresh rate h active be less than " + f"{self.__dynamic_refresh_rate_h_active.min_value} and more than " + f"{self.__dynamic_refresh_rate_h_active.max_value}.") + self.__dynamic_refresh_rate_h_active.default_value = dynamic_refresh_rate_h_active + + @property + def dynamic_refresh_rate_v_active(self) -> int: + """ + Set and get Dynamic Refresh Rate v active. + + Returns: + object of int type + """ + return self.__dynamic_refresh_rate_v_active.default_value + + @dynamic_refresh_rate_v_active.setter + def dynamic_refresh_rate_v_active(self, dynamic_refresh_rate_v_active: int): + if not ( + self.__dynamic_refresh_rate_v_active.min_value < dynamic_refresh_rate_v_active < self.__dynamic_refresh_rate_v_active.max_value): + raise ValueError(f"Dynamic refresh rate v active be less than " + f"{self.__dynamic_refresh_rate_v_active.min_value} and more than " + f"{self.__dynamic_refresh_rate_v_active.max_value}.") + self.__dynamic_refresh_rate_v_active.default_value = dynamic_refresh_rate_v_active + + @property + def dynamic_refresh_rate_pixel_clock(self) -> int: + """ + Set and get Dynamic Refresh Rate Pixel Clock. + + Returns: + object of int type + """ + return self.__dynamic_refresh_rate_pixel_clock.default_value + + @dynamic_refresh_rate_pixel_clock.setter + def dynamic_refresh_rate_pixel_clock(self, dynamic_refresh_rate_pixel_clock: int): + if not ( + self.__dynamic_refresh_rate_pixel_clock.min_value < dynamic_refresh_rate_pixel_clock < self.__dynamic_refresh_rate_pixel_clock.max_value): + raise ValueError(f"Dynamic refresh rate h active be less than " + f"{self.__dynamic_refresh_rate_pixel_clock.min_value} and more than " + f"{self.__dynamic_refresh_rate_pixel_clock.max_value}.") + self.__dynamic_refresh_rate_pixel_clock.default_value = dynamic_refresh_rate_pixel_clock + + @property + def dynamic_refresh_rate_min_vertical(self) -> int: + """ + Set and get Dynamic Refresh Rate minimum vertical. + + Returns: + object of int type + """ + return self.__dynamic_refresh_rate_min_vertical.default_value + + @dynamic_refresh_rate_min_vertical.setter + def dynamic_refresh_rate_min_vertical(self, dynamic_refresh_rate_min_vertical: int): + if not ( + self.__dynamic_refresh_rate_min_vertical.min_value < dynamic_refresh_rate_min_vertical < self.__dynamic_refresh_rate_min_vertical.max_value): + raise ValueError(f"Dynamic refresh rate h active be less than " + f"{self.__dynamic_refresh_rate_min_vertical.min_value} and more than " + f"{self.__dynamic_refresh_rate_min_vertical.max_value}.") + self.__dynamic_refresh_rate_min_vertical.default_value = dynamic_refresh_rate_min_vertical + + @property + def dynamic_refresh_rate_max_vertical(self) -> int: + """ + Set and get Dynamic Refresh Rate maximum vertical. + + Returns: + object of int type + """ + return self.__dynamic_refresh_rate_max_vertical.default_value + + @dynamic_refresh_rate_max_vertical.setter + def dynamic_refresh_rate_max_vertical(self, dynamic_refresh_rate_max_vertical: int): + if not ( + self.__dynamic_refresh_rate_max_vertical.min_value < dynamic_refresh_rate_max_vertical < self.__dynamic_refresh_rate_max_vertical.max_value): + raise ValueError(f"Dynamic refresh rate h active be less than " + f"{self.__dynamic_refresh_rate_max_vertical.min_value} and more than " + f"{self.__dynamic_refresh_rate_max_vertical.max_value}.") + self.__dynamic_refresh_rate_max_vertical.default_value = dynamic_refresh_rate_max_vertical \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/dp_source_dsc_tab.py b/UniTAP/dev/modules/dut_tests/dut_default_params/dp_source_dsc_tab.py new file mode 100644 index 0000000..2856825 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/dp_source_dsc_tab.py @@ -0,0 +1,703 @@ +from enum import IntEnum +from typing import Tuple +from UniTAP.dev.modules.dut_tests.test_utils import CMTFormat, CMTVSCFormat, CMTBitDepth, CMTVSCBitDepth,\ + make_cf, make_vsc_cf +from UniTAP.dev.modules.dut_tests.test_group_params_types import Param + + +class Timing: + """ + Class `Timing` describes available supported timings standard. + - CTA `cta` (enable/disable). + - RB1 `rb1` (enable/disable). + - RB2 `rb2` (enable/disable). + - Set all standards `set_all`. + """ + def __init__(self, timing_value): + self.__value = Param(timing_value) + + @property + def cta(self) -> bool: + """ + Set and get CTA flag support. + + Returns: + object of bool type + """ + return bool(self.__value._get_by_bitmask(0)) + + @property + def rb1(self) -> bool: + """ + Set and get RB1 flag support. + + Returns: + object of bool type + """ + return bool(self.__value._get_by_bitmask(1)) + + @property + def rb2(self) -> bool: + """ + Set and get RB2 flag support. + + Returns: + object of bool type + """ + return bool(self.__value._get_by_bitmask(2)) + + @cta.setter + def cta(self, value: bool): + self.__value._set_by_bitmask(value, 0) + + @rb1.setter + def rb1(self, value: bool): + self.__value._set_by_bitmask(value, 1) + + @rb2.setter + def rb2(self, value: bool): + self.__value._set_by_bitmask(value, 2) + + def set_all(self): + self.cta = True + self.rb1 = True + self.rb2 = True + + def clear_all(self): + self.cta = False + self.rb1 = False + self.rb2 = False + + +class ColorimetryDp14: + """ + Class `ColorimetryDp14` describes optional and additional color modes to be used with DP CTS tests. + - RGB 8 bpc VESA `rgb_8bpc_vesa`. + - RGB 10 bpc VESA `rgb_10bpc_vesa`. + - RGB 12 bpc VESA `rgb_12bpc_vesa`. + - YCbCr-444 8 bpc CTA ITU-709 `ycbcr444_8bpc_cta_itu709`. + - YCbCr-444 10 bpc CTA ITU-709 `ycbcr444_10bpc_cta_itu709`. + - YCbCr-444 12 bpc CTA ITU-709 `ycbcr444_12bpc_cta_itu709`. + - YCbCr-422 8 bpc CTA ITU-709 `ycbcr422_8bpc_cta_itu709`. + - YCbCr-422 10 bpc CTA ITU-709 `ycbcr422_10bpc_cta_itu709`. + - YCbCr-422 12 bpc CTA ITU-709 `ycbcr422_10bpc_cta_itu709`. + - YCbCr-422 simple 8 bpc CTA ITU-709 `ycbcr422_simple_8bpc_cta_itu709`. + - YCbCr-422 simple 10 bpc CTA ITU-709 `ycbcr422_simple_10bpc_cta_itu709`. + - YCbCr-422 simple 12 bpc CTA ITU-709 `ycbcr422_simple_12bpc_cta_itu709`. + - YCbCr-420 8 bpc CTA ITU-709 `ycbcr420_8bpc_cta_itu709`. + - YCbCr-420 10 bpc CTA ITU-709 `ycbcr420_10bpc_cta_itu709`. + - YCbCr-420 12 bpc CTA ITU-709 `ycbcr420_12bpc_cta_itu709`. + - Clear all settings `clear_all`. + - Select all settings `select_all`. + """ + def __init__(self, json_obj): + self.__rgb_8bpc_vesa = Param(json_obj["TSI_DP14_SRCCTS_DSC_COLOR_MODE_0"]) + self.__rgb_10bpc_vesa = Param(json_obj["TSI_DP14_SRCCTS_DSC_COLOR_MODE_1"]) + self.__rgb_12bpc_vesa = Param(json_obj["TSI_DP14_SRCCTS_DSC_COLOR_MODE_2"]) + + self.__ycbcr444_8bpc_cta_itu709 = Param(json_obj["TSI_DP14_SRCCTS_DSC_COLOR_MODE_3"]) + self.__ycbcr444_10bpc_cta_itu709 = Param(json_obj["TSI_DP14_SRCCTS_DSC_COLOR_MODE_4"]) + self.__ycbcr444_12bpc_cta_itu709 = Param(json_obj["TSI_DP14_SRCCTS_DSC_COLOR_MODE_5"]) + + self.__ycbcr422_8bpc_cta_itu709 = Param(json_obj["TSI_DP14_SRCCTS_DSC_COLOR_MODE_6"]) + self.__ycbcr422_10bpc_cta_itu709 = Param(json_obj["TSI_DP14_SRCCTS_DSC_COLOR_MODE_7"]) + self.__ycbcr422_12bpc_cta_itu709 = Param(json_obj["TSI_DP14_SRCCTS_DSC_COLOR_MODE_8"]) + + self.__ycbcr422_simple_8bpc_cta_itu709 = Param(json_obj["TSI_DP14_SRCCTS_DSC_COLOR_MODE_9"]) + self.__ycbcr422_simple_10bpc_cta_itu709 = Param(json_obj["TSI_DP14_SRCCTS_DSC_COLOR_MODE_10"]) + self.__ycbcr422_simple_12bpc_cta_itu709 = Param(json_obj["TSI_DP14_SRCCTS_DSC_COLOR_MODE_11"]) + + self.__ycbcr420_8bpc_cta_itu709 = Param(json_obj["TSI_DP14_SRCCTS_DSC_COLOR_MODE_12"]) + self.__ycbcr420_10bpc_cta_itu709 = Param(json_obj["TSI_DP14_SRCCTS_DSC_COLOR_MODE_13"]) + self.__ycbcr420_12bpc_cta_itu709 = Param(json_obj["TSI_DP14_SRCCTS_DSC_COLOR_MODE_14"]) + + # Pass 126-142 + + self.__color_formats = Param(json_obj["TSI_DP14_SRCCTS_DSC_COLOR_FORMATS"]) + self.__color_formats.default_value = 32 + + def clear_all(self): + """ + Clear all modes (disable). + """ + self.rgb_8bpc_vesa = False + self.rgb_10bpc_vesa = False + self.rgb_12bpc_vesa = False + + self.ycbcr444_8bpc_cta_itu709 = False + self.ycbcr444_10bpc_cta_itu709 = False + self.ycbcr444_12bpc_cta_itu709 = False + + self.ycbcr422_8bpc_cta_itu709 = False + self.ycbcr422_10bpc_cta_itu709 = False + self.ycbcr422_12bpc_cta_itu709 = False + + self.ycbcr422_simple_8bpc_cta_itu709 = False + self.ycbcr422_simple_10bpc_cta_itu709 = False + self.ycbcr422_simple_12bpc_cta_itu709 = False + + self.ycbcr420_8bpc_cta_itu709 = False + self.ycbcr420_10bpc_cta_itu709 = False + self.ycbcr420_12bpc_cta_itu709 = False + + def select_all(self): + """ + Select all modes (enable). + """ + self.rgb_8bpc_vesa = True + self.rgb_10bpc_vesa = True + self.rgb_12bpc_vesa = True + + self.ycbcr444_8bpc_cta_itu709 = True + self.ycbcr444_10bpc_cta_itu709 = True + self.ycbcr444_12bpc_cta_itu709 = True + + self.ycbcr422_8bpc_cta_itu709 = True + self.ycbcr422_10bpc_cta_itu709 = True + self.ycbcr422_12bpc_cta_itu709 = True + + self.ycbcr422_simple_8bpc_cta_itu709 = True + self.ycbcr422_simple_10bpc_cta_itu709 = True + self.ycbcr422_simple_12bpc_cta_itu709 = True + + self.ycbcr420_8bpc_cta_itu709 = True + self.ycbcr420_10bpc_cta_itu709 = True + self.ycbcr420_12bpc_cta_itu709 = True + + @property + def rgb_8bpc_vesa(self): + """ + Set and get RGB 8 bpc VESA flag support. + + Returns: + object + """ + return self.__rgb_8bpc_vesa + + @rgb_8bpc_vesa.setter + def rgb_8bpc_vesa(self, value: bool): + cf_value = make_cf(CMTFormat.RGB_LEGACY, CMTBitDepth.BPC_8) if value else 0 + if self.__rgb_8bpc_vesa.default_value != cf_value: + self.__rgb_8bpc_vesa.default_value = cf_value + + @property + def rgb_10bpc_vesa(self): + """ + Set and get RGB 10 bpc VESA flag support. + + Returns: + object + """ + return self.__rgb_10bpc_vesa + + @rgb_10bpc_vesa.setter + def rgb_10bpc_vesa(self, value: bool): + cf_value = make_cf(CMTFormat.RGB_LEGACY, CMTBitDepth.BPC_10) if value else 0 + if self.__rgb_10bpc_vesa.default_value != cf_value: + self.__rgb_10bpc_vesa.default_value = cf_value + + @property + def rgb_12bpc_vesa(self): + """ + Set and get RGB 12 bpc VESA flag support. + + Returns: + object + """ + return self.__rgb_12bpc_vesa + + @rgb_12bpc_vesa.setter + def rgb_12bpc_vesa(self, value: bool): + cf_value = make_cf(CMTFormat.RGB_LEGACY, CMTBitDepth.BPC_12) if value else 0 + if self.__rgb_12bpc_vesa.default_value != cf_value: + self.__rgb_12bpc_vesa.default_value = cf_value + + @property + def ycbcr444_8bpc_cta_itu709(self): + """ + Set and get YCbCr-444 8 bpc CTA ITU-709 flag support. + + Returns: + object + """ + return self.__ycbcr444_8bpc_cta_itu709 + + @ycbcr444_8bpc_cta_itu709.setter + def ycbcr444_8bpc_cta_itu709(self, value: bool): + cf_value = make_cf(CMTFormat.YCBCR_444_BT709, CMTBitDepth.BPC_8) if value else 0 + if self.__ycbcr444_8bpc_cta_itu709.default_value != cf_value: + self.__ycbcr444_8bpc_cta_itu709.default_value = cf_value + + @property + def ycbcr444_10bpc_cta_itu709(self): + """ + Set and get YCbCr-444 10 bpc CTA ITU-709 flag support. + + Returns: + object + """ + return self.__ycbcr444_10bpc_cta_itu709 + + @ycbcr444_10bpc_cta_itu709.setter + def ycbcr444_10bpc_cta_itu709(self, value: bool): + cf_value = make_cf(CMTFormat.YCBCR_444_BT709, CMTBitDepth.BPC_10) if value else 0 + if self.__ycbcr444_10bpc_cta_itu709.default_value != cf_value: + self.__ycbcr444_10bpc_cta_itu709.default_value = cf_value + + @property + def ycbcr444_12bpc_cta_itu709(self): + """ + Set and get YCbCr-444 12 bpc CTA ITU-709 flag support. + + Returns: + object + """ + return self.__ycbcr444_12bpc_cta_itu709 + + @ycbcr444_12bpc_cta_itu709.setter + def ycbcr444_12bpc_cta_itu709(self, value: bool): + cf_value = make_cf(CMTFormat.YCBCR_444_BT709, CMTBitDepth.BPC_12) if value else 0 + if self.__ycbcr444_12bpc_cta_itu709.default_value != cf_value: + self.__ycbcr444_12bpc_cta_itu709.default_value = cf_value + + @property + def ycbcr422_8bpc_cta_itu709(self): + """ + Set and get YCbCr-422 8 bpc CTA ITU-709 flag support. + + Returns: + object + """ + return self.__ycbcr422_8bpc_cta_itu709 + + @ycbcr422_8bpc_cta_itu709.setter + def ycbcr422_8bpc_cta_itu709(self, value: bool): + cf_value = make_cf(CMTFormat.YCBCR_422_BT709, CMTBitDepth.BPC_8) if value else 0 + if self.__ycbcr444_12bpc_cta_itu709.default_value != cf_value: + self.__ycbcr444_12bpc_cta_itu709.default_value = cf_value + + @property + def ycbcr422_10bpc_cta_itu709(self): + """ + Set and get YCbCr-422 10 bpc CTA ITU-709 flag support. + + Returns: + object + """ + return self.__ycbcr422_10bpc_cta_itu709 + + @ycbcr422_10bpc_cta_itu709.setter + def ycbcr422_10bpc_cta_itu709(self, value: bool): + cf_value = make_cf(CMTFormat.YCBCR_422_BT709, CMTBitDepth.BPC_10) if value else 0 + if self.__ycbcr422_10bpc_cta_itu709.default_value != cf_value: + self.__ycbcr422_10bpc_cta_itu709.default_value = cf_value + + @property + def ycbcr422_12bpc_cta_itu709(self): + """ + Set and get YCbCr-422 12 bpc CTA ITU-709 flag support. + + Returns: + object + """ + return self.__ycbcr422_12bpc_cta_itu709 + + @ycbcr422_12bpc_cta_itu709.setter + def ycbcr422_12bpc_cta_itu709(self, value: bool): + cf_value = make_cf(CMTFormat.YCBCR_422_BT709, CMTBitDepth.BPC_12) if value else 0 + if self.__ycbcr422_12bpc_cta_itu709.default_value != cf_value: + self.__ycbcr422_12bpc_cta_itu709.default_value = cf_value + + @property + def ycbcr422_simple_8bpc_cta_itu709(self): + """ + Set and get YCbCr-422 Simple 8 bpc CTA ITU-709 flag support. + + Returns: + object + """ + return self.__ycbcr422_simple_8bpc_cta_itu709 + + @ycbcr422_simple_8bpc_cta_itu709.setter + def ycbcr422_simple_8bpc_cta_itu709(self, value: bool): + cf_value = make_vsc_cf(CMTVSCFormat.YCBCR_SIMPLE_422_BT709, CMTVSCBitDepth.BPC_YCBCR_8) if value else 0 + if self.__ycbcr422_simple_8bpc_cta_itu709.default_value != cf_value: + self.__ycbcr422_simple_8bpc_cta_itu709.default_value = cf_value + + @property + def ycbcr422_simple_10bpc_cta_itu709(self): + """ + Set and get YCbCr-422 Simple 10 bpc CTA ITU-709 flag support. + + Returns: + object + """ + return self.__ycbcr422_simple_10bpc_cta_itu709 + + @ycbcr422_simple_10bpc_cta_itu709.setter + def ycbcr422_simple_10bpc_cta_itu709(self, value: bool): + cf_value = make_vsc_cf(CMTVSCFormat.YCBCR_SIMPLE_422_BT709, CMTVSCBitDepth.BPC_YCBCR_10) if value else 0 + if self.__ycbcr422_simple_10bpc_cta_itu709.default_value != cf_value: + self.__ycbcr422_simple_10bpc_cta_itu709.default_value = cf_value + + @property + def ycbcr422_simple_12bpc_cta_itu709(self): + """ + Set and get YCbCr-422 Simple 12 bpc CTA ITU-709 flag support. + + Returns: + object + """ + return self.__ycbcr422_simple_12bpc_cta_itu709 + + @ycbcr422_simple_12bpc_cta_itu709.setter + def ycbcr422_simple_12bpc_cta_itu709(self, value: bool): + cf_value = make_vsc_cf(CMTVSCFormat.YCBCR_SIMPLE_422_BT709, CMTVSCBitDepth.BPC_YCBCR_12) if value else 0 + if self.__ycbcr422_simple_12bpc_cta_itu709.default_value != cf_value: + self.__ycbcr422_simple_12bpc_cta_itu709.default_value = cf_value + + @property + def ycbcr420_8bpc_cta_itu709(self): + """ + Set and get YCbCr-420 8 bpc CTA ITU-709 flag support. + + Returns: + object + """ + return self.__ycbcr420_8bpc_cta_itu709 + + @ycbcr420_8bpc_cta_itu709.setter + def ycbcr420_8bpc_cta_itu709(self, value: bool): + cf_value = make_vsc_cf(CMTVSCFormat.YCBCR_420_BT709, CMTVSCBitDepth.BPC_YCBCR_8) if value else 0 + if self.__ycbcr420_8bpc_cta_itu709.default_value != cf_value: + self.__ycbcr420_8bpc_cta_itu709.default_value = cf_value + + @property + def ycbcr420_10bpc_cta_itu709(self): + """ + Set and get YCbCr-420 10 bpc CTA ITU-709 flag support. + + Returns: + object + """ + return self.__ycbcr420_10bpc_cta_itu709 + + @ycbcr420_10bpc_cta_itu709.setter + def ycbcr420_10bpc_cta_itu709(self, value: bool): + cf_value = make_vsc_cf(CMTVSCFormat.YCBCR_420_BT709, CMTVSCBitDepth.BPC_YCBCR_10) if value else 0 + if self.__ycbcr420_10bpc_cta_itu709.default_value != cf_value: + self.__ycbcr420_10bpc_cta_itu709.default_value = cf_value + + @property + def ycbcr420_12bpc_cta_itu709(self): + """ + Set and get YCbCr-420 12 bpc CTA ITU-709 flag support. + + Returns: + object + """ + return self.__ycbcr420_12bpc_cta_itu709 + + @ycbcr420_12bpc_cta_itu709.setter + def ycbcr420_12bpc_cta_itu709(self, value: bool): + cf_value = make_vsc_cf(CMTVSCFormat.YCBCR_420_BT709, CMTVSCBitDepth.BPC_YCBCR_12) if value else 0 + if self.__ycbcr420_12bpc_cta_itu709.default_value != cf_value: + self.__ycbcr420_12bpc_cta_itu709.default_value = cf_value + + +class DscVideoModesDp14: + """ + Class `DscVideoModesDp14` describes available timings for DSC tests. + - 1920x1080 30Hz `vm_1920x1080_30hz`. + - 1920x1080 60Hz `vm_1920x1080_60hz`. + - 1920x1080 120Hz `vm_1920x1080_120hz`. + - 3840x2160 30Hz `vm_3840x2160_30hz`. + - 3840x2160 60Hz `vm_3840x2160_60hz`. + - 3840x2160 120Hz `vm_3840x2160_120hz`. + - 5120x2160 30Hz `vm_5120x2160_30hz`. + - 5120x2160 60Hz `vm_5120x2160_60hz`. + - 5120x2160 120Hz `vm_5120x2160_120hz`. + - 7680x4320 30Hz `vm_7680x4320_30hz`. + - 7680x4320 60Hz `vm_7680x4320_60hz`. + - 7680x4320 100Hz `vm_7680x4320_120hz`. + - Disable all timings `clear_all`. + - Select all timings `select_all`. + """ + def __init__(self, json_obj): + self.__id_1920x1080_30Hz = Param(json_obj["TSI_DP14_SRCCTS_DSC_VMT_0_ID"]) + self.__lc_lr_1920x1080_30Hz = Param(json_obj["TSI_DP14_SRCCTS_DSC_VMT_0_LC_LR"]) + self.__1920x1080_30Hz = Timing(json_obj["TSI_DP14_SRCCTS_DSC_VMT_0_TIMINGS"]) + + self.__id_1920x1080_60Hz = Param(json_obj["TSI_DP14_SRCCTS_DSC_VMT_1_ID"]) + self.__lc_lr_1920x1080_60Hz = Param(json_obj["TSI_DP14_SRCCTS_DSC_VMT_1_LC_LR"]) + self.__1920x1080_60Hz = Timing(json_obj["TSI_DP14_SRCCTS_DSC_VMT_1_TIMINGS"]) + + self.__id_1920x1080_120Hz = Param(json_obj["TSI_DP14_SRCCTS_DSC_VMT_2_ID"]) + self.__lc_lr_1920x1080_120Hz = Param(json_obj["TSI_DP14_SRCCTS_DSC_VMT_2_LC_LR"]) + self.__1920x1080_120Hz = Timing(json_obj["TSI_DP14_SRCCTS_DSC_VMT_2_TIMINGS"]) + + self.__id_3840x2160_30Hz = Param(json_obj["TSI_DP14_SRCCTS_DSC_VMT_3_ID"]) + self.__lc_lr_3840x2160_30Hz = Param(json_obj["TSI_DP14_SRCCTS_DSC_VMT_3_LC_LR"]) + self.__3840x2160_30Hz = Timing(json_obj["TSI_DP14_SRCCTS_DSC_VMT_3_TIMINGS"]) + + self.__id_3840x2160_60Hz = Param(json_obj["TSI_DP14_SRCCTS_DSC_VMT_4_ID"]) + self.__lc_lr_3840x2160_60Hz = Param(json_obj["TSI_DP14_SRCCTS_DSC_VMT_4_LC_LR"]) + self.__3840x2160_60Hz = Timing(json_obj["TSI_DP14_SRCCTS_DSC_VMT_4_TIMINGS"]) + + self.__id_3840x2160_120Hz = Param(json_obj["TSI_DP14_SRCCTS_DSC_VMT_5_ID"]) + self.__lc_lr_3840x2160_120Hz = Param(json_obj["TSI_DP14_SRCCTS_DSC_VMT_5_LC_LR"]) + self.__3840x2160_120Hz = Timing(json_obj["TSI_DP14_SRCCTS_DSC_VMT_5_TIMINGS"]) + + self.__id_5120x2160_30Hz = Param(json_obj["TSI_DP14_SRCCTS_DSC_VMT_6_ID"]) + self.__lc_lr_5120x2160_30Hz = Param(json_obj["TSI_DP14_SRCCTS_DSC_VMT_6_LC_LR"]) + self.__5120x2160_30Hz = Timing(json_obj["TSI_DP14_SRCCTS_DSC_VMT_6_TIMINGS"]) + + self.__id_5120x2160_60Hz = Param(json_obj["TSI_DP14_SRCCTS_DSC_VMT_7_ID"]) + self.__lc_lr_5120x2160_60Hz = Param(json_obj["TSI_DP14_SRCCTS_DSC_VMT_7_LC_LR"]) + self.__5120x2160_60Hz = Timing(json_obj["TSI_DP14_SRCCTS_DSC_VMT_7_TIMINGS"]) + + self.__id_5120x2160_120Hz = Param(json_obj["TSI_DP14_SRCCTS_DSC_VMT_8_ID"]) + self.__lc_lr_5120x2160_120Hz = Param(json_obj["TSI_DP14_SRCCTS_DSC_VMT_8_LC_LR"]) + self.__5120x2160_120Hz = Timing(json_obj["TSI_DP14_SRCCTS_DSC_VMT_8_TIMINGS"]) + + self.__id_7680x4320_30Hz = Param(json_obj["TSI_DP14_SRCCTS_DSC_VMT_9_ID"]) + self.__lc_lr_7680x4320_30Hz = Param(json_obj["TSI_DP14_SRCCTS_DSC_VMT_9_LC_LR"]) + self.__7680x4320_30Hz = Timing(json_obj["TSI_DP14_SRCCTS_DSC_VMT_9_TIMINGS"]) + + self.__id_7680x4320_60Hz = Param(json_obj["TSI_DP14_SRCCTS_DSC_VMT_10_ID"]) + self.__lc_lr_7680x4320_60Hz = Param(json_obj["TSI_DP14_SRCCTS_DSC_VMT_10_LC_LR"]) + self.__7680x4320_60Hz = Timing(json_obj["TSI_DP14_SRCCTS_DSC_VMT_10_TIMINGS"]) + + self.__id_7680x4320_100Hz = Param(json_obj["TSI_DP14_SRCCTS_DSC_VMT_11_ID"]) + self.__lc_lr_7680x4320_100Hz = Param(json_obj["TSI_DP14_SRCCTS_DSC_VMT_11_LC_LR"]) + self.__7680x4320_100Hz = Timing(json_obj["TSI_DP14_SRCCTS_DSC_VMT_11_TIMINGS"]) + + @property + def vm_1920x1080_30hz(self) -> Timing: + """ + Set and get 1920x1080 30Hz timing flag support. + + Returns: + object of bool type + """ + return self.__1920x1080_30Hz + + @property + def vm_1920x1080_60hz(self) -> Timing: + """ + Set and get 1920x1080 60Hz timing flag support. + + Returns: + object of bool type + """ + return self.__1920x1080_60Hz + + @property + def vm_1920x1080_120hz(self) -> Timing: + """ + Set and get 1920x1080 120Hz timing flag support. + + Returns: + object of bool type + """ + return self.__1920x1080_120Hz + + @property + def vm_3840x2160_30hz(self) -> Timing: + """ + Set and get 3840x2160 30Hz timing flag support. + + Returns: + object of bool type + """ + return self.__3840x2160_30Hz + + @property + def vm_3840x2160_60hz(self) -> Timing: + """ + Set and get 3840x2160 60Hz timing flag support. + + Returns: + object of bool type + """ + return self.__3840x2160_60Hz + + @property + def vm_3840x2160_120hz(self) -> Timing: + """ + Set and get 3840x2160 120Hz timing flag support. + + Returns: + object of bool type + """ + return self.__3840x2160_120Hz + + @property + def vm_5120x2160_30hz(self) -> Timing: + """ + Set and get 5120x2160 30Hz timing flag support. + + Returns: + object of bool type + """ + return self.__5120x2160_30Hz + + @property + def vm_5120x2160_60hz(self) -> Timing: + """ + Set and get 5120x2160 60Hz timing flag support. + + Returns: + object of bool type + """ + return self.__5120x2160_60Hz + + @property + def vm_5120x2160_120hz(self) -> Timing: + """ + Set and get 5120x2160 120Hz timing flag support. + + Returns: + object of bool type + """ + return self.__5120x2160_120Hz + + @property + def vm_7680x4320_30hz(self) -> Timing: + """ + Set and get 7680x4320 30Hz timing flag support. + + Returns: + object of bool type + """ + return self.__7680x4320_30Hz + + @property + def vm_7680x4320_60hz(self) -> Timing: + """ + Set and get 7680x4320 60Hz timing flag support. + + Returns: + object of bool type + """ + return self.__7680x4320_60Hz + + @property + def vm_7680x4320_100hz(self) -> Timing: + """ + Set and get 7680x4320 100Hz timing flag support. + + Returns: + object of bool type + """ + return self.__7680x4320_100Hz + + def clear_all(self): + """ + Clear (disable) all timings. + """ + self.vm_1920x1080_30hz.clear_all() + self.vm_1920x1080_60hz.clear_all() + self.vm_1920x1080_120hz.clear_all() + self.vm_3840x2160_30hz.clear_all() + self.vm_3840x2160_60hz.clear_all() + self.vm_3840x2160_120hz.clear_all() + self.vm_5120x2160_30hz.clear_all() + self.vm_5120x2160_60hz.clear_all() + self.vm_5120x2160_120hz.clear_all() + self.vm_7680x4320_30hz.clear_all() + self.vm_7680x4320_60hz.clear_all() + self.vm_7680x4320_100hz.clear_all() + + def select_all(self): + """ + Select (enable) all timings. + """ + self.vm_1920x1080_30hz.set_all() + self.vm_1920x1080_60hz.set_all() + self.vm_1920x1080_120hz.set_all() + self.vm_3840x2160_30hz.set_all() + self.vm_3840x2160_60hz.set_all() + self.vm_3840x2160_120hz.set_all() + self.vm_5120x2160_30hz.set_all() + self.vm_5120x2160_60hz.set_all() + self.vm_5120x2160_120hz.set_all() + self.vm_7680x4320_30hz.set_all() + self.vm_7680x4320_60hz.set_all() + self.vm_7680x4320_100hz.set_all() + + +class DscConfigDp14Tab: + """ + Class `DscConfigDp14Tab` allows working with settings for DP DSC LLCTS tests. + - Set and get DSC maximum slice number `dsc_max_slice`. + - Set and get DSC version `dsc_version`. + - Set and get DSC video modes `dsc_video_modes` type `DscVideoModesDp14`. + - Set and get colorimetry `ColorimetryDp14`. + """ + def __init__(self, json_obj): + self.__dsc_max_slice = Param(json_obj["TSI_DP14_SRCCTS_DSC_DUT_MAX_SLICE"]) # DscMaxSliceNumber + self.__dsc_version = Param(json_obj["TSI_DP14_SRCCTS_DSC_VERSION"]) # DscVersion + self.__dsc_video_modes = DscVideoModesDp14(json_obj) + self.__colorimetry = ColorimetryDp14(json_obj) + + @property + def dsc_max_slice(self) -> int: + """ + Set and get DSC maximum slice number. + + Returns: + object of int type + """ + return self.__dsc_max_slice.default_value + + @dsc_max_slice.setter + def dsc_max_slice(self, dsc_max_slice: int): + if dsc_max_slice not in [1, 2, 4, 8, 10, 12, 16, 20, 24]: + raise ValueError(f"DSC Max slice count available values {[1, 2, 4, 8, 10, 12, 16, 20, 24]}") + self.__dsc_max_slice.default_value = dsc_max_slice + + @property + def dsc_version(self) -> list: + """ + Set and get DSC version. + + Returns: + object of list type + """ + return [self.__dsc_version.default_value >> 16 & 0xF, self.__dsc_version.default_value & 0xF] + + @dsc_version.setter + def dsc_version(self, dsc_version: Tuple[int, int]): + version = (dsc_version[0] << 16) | dsc_version[1] + if version not in [65537, 65538]: + raise ValueError("DSC Version must be 1.1 or 1.2") + self.__dsc_version.default_value = version + + @property + def dsc_video_modes(self) -> DscVideoModesDp14: + """ + Set and get DSC video modes. + + Returns: + object of `DscVideoModesDp14` type + """ + return self.__dsc_video_modes + + @dsc_video_modes.setter + def dsc_video_modes(self, dsc_video_modes: DscVideoModesDp14): + self.__dsc_video_modes = dsc_video_modes + + @property + def colorimetry(self) -> ColorimetryDp14: + """ + Set and get colorimetry. + + Returns: + object of `ColorimetryDp14` type + """ + return self.__colorimetry + + @colorimetry.setter + def colorimetry(self, colorimetry: ColorimetryDp14): + self.__colorimetry = colorimetry diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/hdcp_1a_tests.py b/UniTAP/dev/modules/dut_tests/dut_default_params/hdcp_1a_tests.py new file mode 100644 index 0000000..fdda225 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/hdcp_1a_tests.py @@ -0,0 +1,61 @@ +from UniTAP.dev.modules.dut_tests.test_group_params_types import Param + + +class Hdcp1ATestParam: + """ + Class `Hdcp1ATestParam` describes requirement parameters for HDCP 1A tests: + - Set and get `timeout`. Describes test timeout, in milliseconds. + - Set and get `revoke_id`. Describes Revoke ID. + - Set and get `dut_caps_flag`. Describes source DUT capabilities flags. + """ + def __init__(self, json_obj): + self.__timeout = Param(json_obj["TSI_TEST_CFG_HDCP2_1A_TIMEOUT"]) + self.__revoke_id = Param(json_obj["TSI_TEST_CFG_HDCP2_1A_REVOKEID"]) + self.__dut_caps_flag = Param(json_obj["TSI_TEST_CFG_HDCP2_1A_SRC_DUT_CAP"]) + + @property + def timeout(self) -> int: + """ + Set and get test timeout, in milliseconds. + + Returns: + object of int type + """ + return self.__timeout.default_value + + @timeout.setter + def timeout(self, timeout: int): + if not(self.__timeout.min_value < timeout < self.__timeout.max_value): + raise ValueError(f"Timeout cannot be less than {self.__timeout.min_value} and more than " + f"{self.__timeout.max_value}.") + self.__timeout.default_value = timeout + + @property + def revoke_id(self) -> int: + """ + Set and get Revoke ID. + + Returns: + object of int type + """ + return self.__revoke_id.default_value + + @revoke_id.setter + def revoke_id(self, revoke_id: list): + if len(revoke_id) == 0: + raise ValueError(f"Revoke ID list length must more than 0") + self.__revoke_id.default_value = revoke_id + + @property + def dut_caps_flag(self) -> bool: + """ + Set and get DUT caps flag support. + + Returns: + object of bool type + """ + return self.__dut_caps_flag.default_value + + @dut_caps_flag.setter + def dut_caps_flag(self, dut_caps_flag: bool): + self.__dut_caps_flag.default_value = dut_caps_flag diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/hdcp_1b_tests.py b/UniTAP/dev/modules/dut_tests/dut_default_params/hdcp_1b_tests.py new file mode 100644 index 0000000..f8a620d --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/hdcp_1b_tests.py @@ -0,0 +1,27 @@ +from UniTAP.dev.modules.dut_tests.test_group_params_types import Param + + +class Hdcp1BTestParam: + """ + Class `Hdcp1BTestParam` describes requirement parameters for HDCP 1B tests: + - Set and get `timeout`. Describes test timeout, in milliseconds. + """ + def __init__(self, json_obj): + self.__timeout = Param(json_obj["TSI_TEST_CFG_HDCP2_1B_TIMEOUT"]) + + @property + def timeout(self): + """ + Set and get test timeout, in milliseconds. + + Returns: + object of int type + """ + return self.__timeout.default_value + + @timeout.setter + def timeout(self, timeout: int): + if not(self.__timeout.min_value < timeout < self.__timeout.max_value): + raise ValueError(f"Timeout cannot be less than {self.__timeout.min_value} and more than " + f"{self.__timeout.max_value}.") + self.__timeout.default_value = timeout diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/hdcp_2c_tests.py b/UniTAP/dev/modules/dut_tests/dut_default_params/hdcp_2c_tests.py new file mode 100644 index 0000000..7322ca6 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/hdcp_2c_tests.py @@ -0,0 +1,27 @@ +from UniTAP.dev.modules.dut_tests.test_group_params_types import Param + + +class Hdcp2CTestParam: + """ + Class `Hdcp2CTestParam` describes requirement parameters for HDCP 2C tests: + - Set and get `timeout`. Describes test timeout, in milliseconds. + """ + def __init__(self, json_obj): + self.__timeout = Param(json_obj["TSI_TEST_CFG_HDCP2_2C_TIMEOUT"]) + + @property + def timeout(self): + """ + Set and get test timeout, in milliseconds. + + Returns: + object of int type + """ + return self.__timeout.default_value + + @timeout.setter + def timeout(self, timeout: int): + if not(self.__timeout.min_value < timeout < self.__timeout.max_value): + raise ValueError(f"Timeout cannot be less than {self.__timeout.min_value} and more than " + f"{self.__timeout.max_value}.") + self.__timeout.default_value = timeout diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/hdcp_3a_tests.py b/UniTAP/dev/modules/dut_tests/dut_default_params/hdcp_3a_tests.py new file mode 100644 index 0000000..f467316 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/hdcp_3a_tests.py @@ -0,0 +1,27 @@ +from UniTAP.dev.modules.dut_tests.test_group_params_types import Param + + +class Hdcp3ATestParam: + """ + Class `Hdcp3ATestParam` describes requirement parameters for HDCP 3A tests: + - Set and get `timeout`. Describes test timeout, in milliseconds. + """ + def __init__(self, json_obj): + self.__timeout = Param(json_obj["TSI_TEST_CFG_HDCP2_3A_TIMEOUT"]) + + @property + def timeout(self): + """ + Set and get test timeout, in milliseconds. + + Returns: + object of int type + """ + return self.__timeout.default_value + + @timeout.setter + def timeout(self, timeout: int): + if not(self.__timeout.min_value < timeout < self.__timeout.max_value): + raise ValueError(f"Timeout cannot be less than {self.__timeout.min_value} and more than " + f"{self.__timeout.max_value}.") + self.__timeout.default_value = timeout diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/hdcp_3b_tests.py b/UniTAP/dev/modules/dut_tests/dut_default_params/hdcp_3b_tests.py new file mode 100644 index 0000000..50eb37e --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/hdcp_3b_tests.py @@ -0,0 +1,27 @@ +from UniTAP.dev.modules.dut_tests.test_group_params_types import Param + + +class Hdcp3BTestParam: + """ + Class `Hdcp3BTestParam` describes requirement parameters for HDCP 3B tests: + - Set and get `timeout`. Describes test timeout, in milliseconds. + """ + def __init__(self, json_obj): + self.__timeout = Param(json_obj["TSI_TEST_CFG_HDCP2_3B_TIMEOUT"]) + + @property + def timeout(self): + """ + Set and get test timeout, in milliseconds. + + Returns: + object of int type + """ + return self.__timeout.default_value + + @timeout.setter + def timeout(self, timeout: int): + if not(self.__timeout.min_value < timeout < self.__timeout.max_value): + raise ValueError(f"Timeout cannot be less than {self.__timeout.min_value} and more than " + f"{self.__timeout.max_value}.") + self.__timeout.default_value = timeout diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/hdcp_3c_tests.py b/UniTAP/dev/modules/dut_tests/dut_default_params/hdcp_3c_tests.py new file mode 100644 index 0000000..15848cf --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/hdcp_3c_tests.py @@ -0,0 +1,43 @@ +from UniTAP.dev.modules.dut_tests.test_group_params_types import Param + + +class Hdcp3CTestParam: + """ + Class `Hdcp3CTestParam` describes requirement parameters for HDCP 3C tests: + - Set and get `timeout`. Describes test timeout, in milliseconds. + - Set and get `repeater_multiple_outputs`. Describes repeater multiple outputs (enable/disable). + """ + def __init__(self, json_obj): + self.__timeout = Param(json_obj["TSI_TEST_CFG_HDCP2_3C_TIMEOUT"]) + self.__repeater_multiple_outputs = Param(json_obj["TSI_TEST_CFG_HDCP2_3C_REPEATER_MULTIPLE_OUTPUTS"]) + + @property + def timeout(self): + """ + Set and get test timeout, in milliseconds. + + Returns: + object of int type + """ + return self.__timeout.default_value + + @timeout.setter + def timeout(self, timeout: int): + if not(self.__timeout.min_value < timeout < self.__timeout.max_value): + raise ValueError(f"Timeout cannot be less than {self.__timeout.min_value} and more than " + f"{self.__timeout.max_value}.") + self.__timeout.default_value = timeout + + @property + def repeater_multiple_outputs(self) -> bool: + """ + Set and get repeater multiple outputs (enable/disable). + + Returns: + object of bool type + """ + return self.__repeater_multiple_outputs.default_value + + @repeater_multiple_outputs.setter + def repeater_multiple_outputs(self, repeater_multiple_outputs: bool): + self.__repeater_multiple_outputs.default_value = repeater_multiple_outputs diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/hdmi_electrical_tests.py b/UniTAP/dev/modules/dut_tests/dut_default_params/hdmi_electrical_tests.py new file mode 100644 index 0000000..d398cdd --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/hdmi_electrical_tests.py @@ -0,0 +1,293 @@ +from UniTAP.dev.modules.dut_tests.test_group_params_types import Param + + +class HdmiElectricalTestParam: + """ + Class `DpElectricalTestParam` describes parameters for DP electrical tests. + - Test timeout, in milliseconds `timeout`. + - HDMI power line voltage low limit, in millivolts `power_low_limit`. + - HDMI power line voltage high limit, in millivolts `power_high_limit`. + - HDMI link line voltage low limit, in millivolts `link_low_limit`. + - HDMI link line voltage high limit, in millivolts `link_high_limit`. + - HDMI HPD logical zero voltage level, lower limit, in millivolts `hpd_zero_low_limit`. + - HDMI HPD logical zero voltage level, higher limit, in millivolts `hpd_zero_high_limit`. + - HDMI HPD logical one voltage level, lower limit, in millivolts `hpd_one_low_limit`. + - HDMI HPD logical one voltage level, higher limit, in millivolts `hpd_one_high_limit`. + - DDC Line voltage low limit, in millivolts `ddc_low_limit`. + - DDC Line voltage high limit, in millivolts `_ddc_high_limit`. + - CCE Line logical zero voltage level, lower limit, in millivolts `cec_zero_low_limit`. + - CCE Line logical zero voltage level, higher limit, in millivolts `cec_zero_high_limit`. + - CCE Line logical one voltage level, lower limit, in millivolts `cec_one_low_limit`. + - CCE Line logical one voltage level, higher limit, in millivolts `cec_one_high_limit`. + """ + def __init__(self, json_obj): + self.__timeout = Param(json_obj["TSI_HDMI_RX_TIMEOUT"]) + self.__power_low_limit = Param(json_obj["TSI_HDMI_RX_POWER_LOW_LIMIT"]) + self.__power_high_limit = Param(json_obj["TSI_HDMI_RX_POWER_HIGH_LIMIT"]) + self.__link_low_limit = Param(json_obj["TSI_HDMI_RX_LINK_LOW_LIMIT"]) + self.__link_high_limit = Param(json_obj["TSI_HDMI_RX_LINK_HIGH_LIMIT"]) + self.__hpd_zero_low_limit = Param(json_obj["TSI_HDMI_RX_HPD_ZERO_LOW_LIMIT"]) + self.__hpd_zero_high_limit = Param(json_obj["TSI_HDMI_RX_HPD_ZERO_HIGH_LIMIT"]) + self.__hpd_one_low_limit = Param(json_obj["TSI_HDMI_RX_HPD_ONE_LOW_LIMIT"]) + self.__hpd_one_high_limit = Param(json_obj["TSI_HDMI_RX_HPD_ONE_HIGHT_LIMIT"]) + self.__ddc_low_limit = Param(json_obj["TSI_HDMI_RX_DDC_LOW_LIMIT"]) + self.__ddc_high_limit = Param(json_obj["TSI_HDMI_RX_DDC_HIGH_LIMIT"]) + self.__cec_zero_low_limit = Param(json_obj["TSI_HDMI_RX_CEC_ZERO_LOW_LIMIT"]) + self.__cec_zero_high_limit = Param(json_obj["TSI_HDMI_RX_CEC_ZERO_HIGH_LIMIT"]) + self.__cec_one_low_limit = Param(json_obj["TSI_HDMI_RX_CEC_ONE_LOW_LIMIT"]) + self.__cec_one_high_limit = Param(json_obj["TSI_HDMI_RX_CEC_ONE_HIGH_LIMIT"]) + + @property + def timeout(self) -> int: + """ + Set and get test timeout, in milliseconds. + + Returns: + object of int type + """ + return self.__timeout.default_value + + @timeout.setter + def timeout(self, timeout: int): + if not(self.__timeout.min_value < timeout < self.__timeout.max_value): + raise ValueError(f"Timeout cannot be less than {self.__timeout.min_value} and more than " + f"{self.__timeout.max_value}.") + self.__timeout.default_value = timeout + + @property + def power_low_limit(self) -> int: + """ + Set and get HDMI power line voltage low limit. + + Returns: + object of int type + """ + return self.__power_low_limit.default_value + + @power_low_limit.setter + def power_low_limit(self, power_low_limit: int): + if not(self.__power_low_limit.min_value < power_low_limit < self.__power_low_limit.max_value): + raise ValueError(f"Power low limit cannot be less than {self.__power_low_limit.min_value} and more than " + f"{self.__power_low_limit.max_value}.") + self.__power_low_limit.default_value = power_low_limit + + @property + def power_high_limit(self) -> int: + """ + Set and get HDMI power line voltage high limit. + + Returns: + object of int type + """ + return self.__power_high_limit.default_value + + @power_high_limit.setter + def power_high_limit(self, power_high_limit: int): + if not(self.__power_high_limit.min_value < power_high_limit < self.__power_high_limit.max_value): + raise ValueError(f"Power high limit cannot be less than {self.__power_high_limit.min_value} and more than " + f"{self.__power_high_limit.max_value}.") + self.__power_high_limit.default_value = power_high_limit + + @property + def link_low_limit(self) -> int: + """ + Set and get HDMI link line voltage low limit. + + Returns: + object of int type + """ + return self.__link_low_limit.default_value + + @link_low_limit.setter + def link_low_limit(self, link_low_limit: int): + if not(self.__link_low_limit.min_value < link_low_limit < self.__link_low_limit.max_value): + raise ValueError(f"Link low limit cannot be less than {self.__link_low_limit.min_value} and more than " + f"{self.__link_low_limit.max_value}.") + self.__link_low_limit.default_value = link_low_limit + + @property + def link_high_limit(self) -> int: + """ + Set and get HDMI link line voltage high limit. + + Returns: + object of int type + """ + return self.__link_high_limit.default_value + + @link_high_limit.setter + def link_high_limit(self, link_high_limit: int): + if not(self.__link_high_limit.min_value < link_high_limit < self.__link_high_limit.max_value): + raise ValueError(f"Link high limit cannot be less than {self.__link_high_limit.min_value} and more than " + f"{self.__link_high_limit.max_value}.") + self.__link_high_limit.default_value = link_high_limit + + @property + def hpd_zero_low_limit(self) -> int: + """ + Set and get HDMI HPD logical zero voltage level, lower limit. + + Returns: + object of int type + """ + return self.__hpd_zero_low_limit.default_value + + @hpd_zero_low_limit.setter + def hpd_zero_low_limit(self, hpd_zero_low_limit: int): + if not(self.__hpd_zero_low_limit.min_value < hpd_zero_low_limit < self.__hpd_zero_low_limit.max_value): + raise ValueError(f"HPD zero low limit cannot be less than {self.__hpd_zero_low_limit.min_value} " + f"and more than {self.__hpd_zero_low_limit.max_value}.") + self.__hpd_zero_low_limit.default_value = hpd_zero_low_limit + + @property + def hpd_zero_high_limit(self) -> int: + """ + Set and get HDMI HPD logical zero voltage level, higher limit. + + Returns: + object of int type + """ + return self.__hpd_zero_high_limit.default_value + + @hpd_zero_high_limit.setter + def hpd_zero_high_limit(self, hpd_zero_high_limit: int): + if not(self.__hpd_zero_high_limit.min_value < hpd_zero_high_limit < self.__hpd_zero_high_limit.max_value): + raise ValueError(f"HPD zero high limit cannot be less than {self.__hpd_zero_high_limit.min_value} " + f"and more than {self.__hpd_zero_high_limit.max_value}.") + self.__hpd_zero_high_limit.default_value = hpd_zero_high_limit + + @property + def hpd_one_low_limit(self) -> int: + """ + Set and get HDMI HPD logical one voltage level, lower limit. + + Returns: + object of int type + """ + return self.__hpd_one_low_limit.default_value + + @hpd_one_low_limit.setter + def hpd_one_low_limit(self, hpd_one_low_limit: int): + if not(self.__hpd_one_low_limit.min_value < hpd_one_low_limit < self.__hpd_one_low_limit.max_value): + raise ValueError(f"HPD one low limit cannot be less than {self.__hpd_one_low_limit.min_value} " + f"and more than {self.__hpd_one_low_limit.max_value}.") + self.__hpd_one_low_limit.default_value = hpd_one_low_limit + + @property + def hpd_one_high_limit(self) -> int: + """ + Set and get HDMI HPD logical one voltage level, higher limit. + + Returns: + object of int type + """ + return self.__hpd_one_high_limit.default_value + + @hpd_one_high_limit.setter + def hpd_one_high_limit(self, hpd_one_high_limit: int): + if not(self.__hpd_one_high_limit.min_value < hpd_one_high_limit < self.__hpd_one_high_limit.max_value): + raise ValueError(f"HPD one high limit cannot be less than {self.__hpd_one_high_limit.min_value} " + f"and more than {self.__hpd_one_high_limit.max_value}.") + self.__hpd_one_high_limit.default_value = hpd_one_high_limit + + @property + def ddc_low_limit(self) -> int: + """ + Set and get DDC Line voltage low limit. + + Returns: + object of int type + """ + return self.__ddc_low_limit.default_value + + @ddc_low_limit.setter + def ddc_low_limit(self, ddc_low_limit: int): + if not(self.__ddc_low_limit.min_value < ddc_low_limit < self.__ddc_low_limit.max_value): + raise ValueError(f"DDC low limit cannot be less than {self.__ddc_low_limit.min_value} and more than " + f"{self.__ddc_low_limit.max_value}.") + self.__ddc_low_limit.default_value = ddc_low_limit + + @property + def ddc_high_limit(self) -> int: + """ + Set and get DDC Line voltage high limit. + + Returns: + object of int type + """ + return self.__ddc_high_limit.default_value + + @ddc_high_limit.setter + def ddc_high_limit(self, ddc_high_limit: int): + if not(self.__ddc_high_limit.min_value < ddc_high_limit < self.__ddc_high_limit.max_value): + raise ValueError(f"DDC high limit cannot be less than {self.__ddc_high_limit.min_value} and more than " + f"{self.__ddc_high_limit.max_value}.") + self.__ddc_high_limit.default_value = ddc_high_limit + + @property + def cec_zero_low_limit(self) -> int: + """ + Set and get CCE Line logical zero voltage level, lower limit. + + Returns: + object of int type + """ + return self.__cec_zero_low_limit.default_value + + @cec_zero_low_limit.setter + def cec_zero_low_limit(self, cec_zero_low_limit: int): + if not(self.__cec_zero_low_limit.min_value < cec_zero_low_limit < self.__cec_zero_low_limit.max_value): + raise ValueError(f"CEC zero low limit cannot be less than {self.__cec_zero_low_limit.min_value} " + f"and more than {self.__cec_zero_low_limit.max_value}.") + self.__cec_zero_low_limit.default_value = cec_zero_low_limit + + @property + def cec_zero_high_limit(self) -> int: + """ + Set and get CCE Line logical zero voltage level, higher limit. + + Returns: + object of int type + """ + return self.__cec_zero_high_limit.default_value + + @cec_zero_high_limit.setter + def cec_zero_high_limit(self, cec_zero_high_limit: int): + if not(self.__cec_zero_high_limit.min_value < cec_zero_high_limit < self.__cec_zero_high_limit.max_value): + raise ValueError(f"CEC zero high limit cannot be less than {self.__cec_zero_high_limit.min_value} " + f"and more than {self.__cec_zero_high_limit.max_value}.") + self.__cec_zero_high_limit.default_value = cec_zero_high_limit + + @property + def cec_one_low_limit(self) -> int: + """ + Set and get CCE Line logical one voltage level, lower limit. + + Returns: + object of int type + """ + return self.__cec_one_low_limit.default_value + + @cec_one_low_limit.setter + def cec_one_low_limit(self, cec_one_low_limit: int): + if not(self.__cec_one_low_limit.min_value < cec_one_low_limit < self.__cec_one_low_limit.max_value): + raise ValueError(f"CEC one low limit cannot be less than {self.__cec_one_low_limit.min_value} " + f"and more than {self.__cec_one_low_limit.max_value}.") + self.__cec_one_low_limit.default_value = cec_one_low_limit + + @property + def cec_one_high_limit(self) -> int: + """ + Set and get CCE Line logical one voltage level, higher limit. + + Returns: + object of int type + """ + return self.__cec_one_high_limit.default_value + + @cec_one_high_limit.setter + def cec_one_high_limit(self, cec_one_high_limit: int): + if not(self.__cec_one_high_limit.min_value < cec_one_high_limit < self.__cec_one_high_limit.max_value): + raise ValueError(f"CEC one high limit cannot be less than {self.__cec_one_high_limit.min_value} " + f"and more than {self.__cec_one_high_limit.max_value}.") + self.__cec_one_high_limit.default_value = cec_one_high_limit diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/hdmi_sink_cable_check_tests.py b/UniTAP/dev/modules/dut_tests/dut_default_params/hdmi_sink_cable_check_tests.py new file mode 100644 index 0000000..55e8b13 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/hdmi_sink_cable_check_tests.py @@ -0,0 +1,217 @@ +from UniTAP.dev.modules.dut_tests.test_group_params_types import Param +from .hdmi_sink_tests import HdmiFrlRate +from enum import IntEnum + + +class CableTestMode(IntEnum): + HighSpeed = 0 + LowSpeed = 1 + All = 2 + + +class LowSpeedLineSelection(Param): + """ + Class `LowSpeedLineSelection` defines variants of tests: + - Voltage swing level 3 (1.2V) supported `voltage_swing_supported`. + - Pre-emphasis level 3 (9.5dB) supported `pre_emphasis_supported`. + + """ + def __init__(self, json_obj): + super().__init__(json_obj) + + @property + def hpd(self) -> bool: + """ + Configure if HPD line will be tested. + + Returns: + object of bool type + """ + return self._get_by_bitmask(0, bool) + + @hpd.setter + def hpd(self, hpd_test: bool): + self._set_by_bitmask(hpd_test, 0) + + @property + def i2c(self) -> bool: + """ + Configure if I2C line will be tested. + + Returns: + object of bool type + """ + return self._get_by_bitmask(1, bool) + + @i2c.setter + def i2c(self, i2c_test: bool): + self._set_by_bitmask(i2c_test, 1) + + @property + def cec(self) -> bool: + """ + Configure if CEC line will be tested. + + Returns: + object of bool type + """ + return self._get_by_bitmask(2, bool) + + @cec.setter + def cec(self, cec_test: bool): + self._set_by_bitmask(cec_test, 2) + + @property + def voltage_5v(self) -> bool: + """ + Configure if 5V line will be tested. + + Returns: + object of bool type + """ + return self._get_by_bitmask(3, bool) + + @voltage_5v.setter + def voltage_5v(self, voltage_5v_test: bool): + self._set_by_bitmask(voltage_5v_test, 3) + + @property + def utility(self) -> bool: + """ + Configure if utility line will be tested. + + Returns: + object of bool type + """ + return self._get_by_bitmask(4, bool) + + @utility.setter + def utility(self, utility_test: bool): + self._set_by_bitmask(utility_test, 4) + + +class HdmiSinkCableCheckTestParam: + """ + Class `HdmiSinkDUTTestParam` allows working with parameters from HDMI DSC source part. + - Set and get test timeout `test_timeout`. + - Seg and get test mode `test_mode` type `CableTestMode`. + - Set and get MIN FRL rate `min_frl_rate` type `HdmiFrlRate`. + - Set and get MAX FRL rate `max_frl_rate` type `HdmiFrlRate`. + - Set and get amount of allowed errors per lane `allowed_errors_per_lane`. + - Set and get errors capture timeout `errors_capture_timeout `. + """ + def __init__(self, json_obj): + self.__test_timeout = Param(json_obj["TSI_HDMI_SNKCTS_CABLE_TIMEOUT"]) + self.__test_mode = Param(json_obj["TSI_HDMI_SNKCTS_CABLE_TEST_MODE"]) + self.__min_frl_rate = Param(json_obj["TSI_HDMI_SNKCTS_CABLE_MIN_FRL_RATE"]) + self.__max_frl_rate = Param(json_obj["TSI_HDMI_SNKCTS_CABLE_MAX_FRL_RATE"]) + self.__allowed_errors_per_lane = Param(json_obj["TSI_HDMI_SNKCTS_CABLE_ERROR_PER_LANE"]) + self.__errors_capture_timeout = Param(json_obj["TSI_HDMI_SNKCTS_CABLE_CAPTURE_TIMEOUT"]) + self.__low_speed_line_selection = LowSpeedLineSelection(json_obj["TSI_HDMI_SNKCTS_CABLE_LOW_SPEED_LINE"]) + + @property + def test_timeout(self) -> int: + """ + Set and get test timeout, in seconds. + + Returns: + object of int type + """ + return self.__test_timeout.default_value + + @test_timeout.setter + def test_timeout(self, test_timeout: int): + if not(self.__test_timeout.min_value < test_timeout < self.__test_timeout.max_value): + raise ValueError(f"Test timeout cannot be less than {self.__test_timeout.min_value} and more than " + f"{self.__test_timeout.max_value}.") + self.__test_timeout.default_value = test_timeout + + @property + def test_mode(self) -> CableTestMode: + """ + Set and get test mode. + + Returns: + object of `CableTestMode` type + """ + return CableTestMode(self.__test_mode.default_value) + + @test_mode.setter + def test_mode(self, test_mode: CableTestMode): + self.__test_mode.default_value = test_mode.value + + @property + def min_frl_rate(self) -> HdmiFrlRate: + """ + Set and get the minimum FRL rate supported by the cable. + + Returns: + object of `HdmiFrlRate` type + """ + return HdmiFrlRate(self.__min_frl_rate.default_value) + + @min_frl_rate.setter + def min_frl_rate(self, min_frl_rate: HdmiFrlRate): + self.__min_frl_rate.default_value = min_frl_rate.value + + @property + def max_frl_rate(self) -> HdmiFrlRate: + """ + Set and get the maximum FRL rate supported by the cable. + + Returns: + object of `HdmiFrlRate` type + """ + return HdmiFrlRate(self.__max_frl_rate.default_value) + + @max_frl_rate.setter + def max_frl_rate(self, max_frl_rate: HdmiFrlRate): + self.__max_frl_rate.default_value = max_frl_rate.value + + @property + def allowed_errors_per_lane(self) -> int: + """ + Set and get allowed errors per lane. + + Returns: + object of int type + """ + return self.__allowed_errors_per_lane.default_value + + @allowed_errors_per_lane.setter + def allowed_errors_per_lane(self, allowed_errors_per_lane: int): + if not(self.__allowed_errors_per_lane.min_value < allowed_errors_per_lane < self.__allowed_errors_per_lane.max_value): + raise ValueError(f"Amount of allowed errors per lane cannot be less than {self.__allowed_errors_per_lane.min_value} and more than " + f"{self.__allowed_errors_per_lane.max_value}.") + self.__allowed_errors_per_lane.default_value = allowed_errors_per_lane + + @property + def errors_capture_timeout(self) -> int: + """ + Set and get errors capture timeout, in seconds. + + Returns: + object of int type + """ + return self.__errors_capture_timeout.default_value + + @errors_capture_timeout.setter + def errors_capture_timeout(self, errors_capture_timeout: int): + if not(self.__errors_capture_timeout.min_value < errors_capture_timeout < self.__errors_capture_timeout.max_value): + raise ValueError(f"Errors capture timeout cannot be less than {self.__errors_capture_timeout.min_value} and more than " + f"{self.__errors_capture_timeout.max_value}.") + self.__errors_capture_timeout.default_value = errors_capture_timeout + + @property + def low_speed_line_selection(self) -> LowSpeedLineSelection: + """ + Set and get low speed lines for testing. + + Returns: + object `LowSpeedLineSelection` + """ + return self.__low_speed_line_selection + + @low_speed_line_selection.setter + def low_speed_line_selection(self, low_speed_line_selection: LowSpeedLineSelection): + self.__low_speed_line_selection = low_speed_line_selection diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/hdmi_sink_continuity_tests.py b/UniTAP/dev/modules/dut_tests/dut_default_params/hdmi_sink_continuity_tests.py new file mode 100644 index 0000000..c120e92 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/hdmi_sink_continuity_tests.py @@ -0,0 +1,120 @@ +from UniTAP.dev.modules.dut_tests.test_group_params_types import Param + + +class HdmiSinkContinuityDUTTestParam: + """ + Class `HdmiSinkContinuityDUTTestParam` allows working with parameters from HDMI Continuity part. + - Set and get total test time `test_time`, in seconds. Default value is 60s. + - Set and get status period `status_period`, in seconds. + - Set and get stop flag `stop_flag`. Stop testing when status fail. + - Set and get flag of enabling scdc version `enable_scdc_version`. Enable to check SCDC version. + - Set and get flag of enabling scdc status `enable_scdc_status`. Enable to check SCDC status flag. + - Set and get flag of enabling scdc error count `enable_scdc_error_count`. Enable to check SCDC error counters. + - Set and get SDCD error counter fail threshold `scdc_error_count`. Default value is 10. + """ + def __init__(self, json_obj): + self.__test_time = Param(json_obj["TSI_HDMI_SNKCTS_TEST_TIME"]) + self.__status_period = Param(json_obj["TSI_HDMI_SNKCTS_STATUS_PERIOD"]) + self.__stop_flag = Param(json_obj["TSI_HDMI_SNKCTS_STOP_TESTING"]) + self.__enable_scdc_version = Param(json_obj["TSI_HDMI_SNKCTS_SCDC_VERSION"]) + self.__enable_scdc_status = Param(json_obj["TSI_HDMI_SNKCTS_SCDC_STATUS"]) + self.__enable_scdc_error_count = Param(json_obj["TSI_HDMI_SNKCTS_SCDC_ERROR"]) + self.__scdc_error_count = Param(json_obj["TSI_HDMI_SNKCTS_SCDC_ERROR_COUNTER"]) + + @property + def test_time(self) -> int: + """ + Set and get test time. + + Returns: + object of `int` type + """ + return self.__test_time.default_value + + @test_time.setter + def test_time(self, test_time: int): + self.__test_time.default_value = test_time + + @property + def status_period(self) -> int: + """ + Set and get status period. + + Returns: + object of `int` type + """ + return self.__status_period.default_value + + @status_period.setter + def status_period(self, status_period: int): + self.__status_period.default_value = status_period + + @property + def stop_flag(self) -> bool: + """ + Set and get flag of stop testing when status fail. + + Returns: + object of `bool` type + """ + return self.__stop_flag.default_value + + @stop_flag.setter + def stop_flag(self, stop_flag: bool): + self.__stop_flag.default_value = int(stop_flag) + + @property + def enable_scdc_version(self) -> int: + """ + Set and get flag of enabling scdc version. + + Returns: + object of `bool` type + """ + return self.__enable_scdc_version.default_value + + @enable_scdc_version.setter + def enable_scdc_version(self, enable_scdc_version: bool): + self.__enable_scdc_version.default_value = int(enable_scdc_version) + + @property + def enable_scdc_status(self) -> bool: + """ + Set and get flag of enabling scdc status. + + Returns: + object of `bool` type + """ + return self.__test_time.default_value + + @enable_scdc_status.setter + def enable_scdc_status(self, enable_scdc_status: bool): + self.__enable_scdc_status.default_value = int(enable_scdc_status) + + @property + def enable_scdc_error_count(self) -> bool: + """ + Set and get flag of enabling scdc error count. + + Returns: + object of `int` type + """ + return self.__enable_scdc_error_count.default_value + + @enable_scdc_error_count.setter + def enable_scdc_error_count(self, enable_scdc_error_count: bool): + self.__enable_scdc_error_count.default_value = int(enable_scdc_error_count) + + @property + def scdc_error_count(self) -> int: + """ + Set and get scdc error count. + + Returns: + object of `int` type + """ + return self.__scdc_error_count.default_value + + @scdc_error_count.setter + def scdc_error_count(self, error_count: int): + self.__scdc_error_count.default_value = error_count diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/hdmi_sink_tests.py b/UniTAP/dev/modules/dut_tests/dut_default_params/hdmi_sink_tests.py new file mode 100644 index 0000000..f074229 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/hdmi_sink_tests.py @@ -0,0 +1,1172 @@ +from UniTAP.dev.modules.dut_tests.test_group_params_types import Param, param_by_ci_name +from enum import IntEnum + + +class HdmiTestMode(IntEnum): + ForceVICoutput = 0 + CTStest = 1 + + +class HdmiFrlRate(IntEnum): + Mode_Disable = 0 + Mode_3lanes_3gbps = 1 + Mode_3lanes_6gbps = 2 + Mode_4lanes_6gbps = 3 + Mode_4lanes_8gbps = 4 + Mode_4lanes_10gbps = 5 + Mode_4lanes_12gbps = 6 + + +class HdmiAvailableVideoModes: + + def __init__(self, param_1: Param, param_2: Param): + self.__param_1 = param_1 + self.__param_2 = param_2 + + @property + def T_2560x1080_100hz_vic_91(self) -> bool: + """ + Set and get state of support timing VIC 91 2560x1080 100Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(0) + + @T_2560x1080_100hz_vic_91.setter + def T_2560x1080_100hz_vic_91(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=0) + + @property + def T_2560x1080_120hz_vic_92(self) -> bool: + """ + Set and get state of support timing VIC 92 2560x1080 120Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(1) + + @T_2560x1080_120hz_vic_92.setter + def T_2560x1080_120hz_vic_92(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=1) + + @property + def T_1920x1080_120hz_vic_63(self) -> bool: + """ + Set and get state of support timing VIC 63 1920x1080 120Hz (16:9). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(2) + + @T_1920x1080_120hz_vic_63.setter + def T_1920x1080_120hz_vic_63(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=2) + + @property + def T_1920x1080_100hz_vic_64(self) -> bool: + """ + Set and get state of support timing VIC 64 1920x1080 100Hz (16:9). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(3) + + @T_1920x1080_100hz_vic_64.setter + def T_1920x1080_100hz_vic_64(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=3) + + @property + def T_1920x1080_100hz_vic_77(self) -> bool: + """ + Set and get state of support timing VIC 77 1920x1080 100Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(4) + + @T_1920x1080_100hz_vic_77.setter + def T_1920x1080_100hz_vic_77(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=4) + + @property + def T_1920x1080_120hz_vic_78(self) -> bool: + """ + Set and get state of support timing VIC 78 1920x1080 120Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(5) + + @T_1920x1080_120hz_vic_78.setter + def T_1920x1080_120hz_vic_78(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=5) + + @property + def T_3840x2160_24hz_vic_93(self) -> bool: + """ + Set and get state of support timing VIC 93 3840x2160 24Hz (16:9). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(6) + + @T_3840x2160_24hz_vic_93.setter + def T_3840x2160_24hz_vic_93(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=6) + + @property + def T_3840x2160_25hz_vic_94(self) -> bool: + """ + Set and get state of support timing VIC 94 3840x2160 25Hz (16:9). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(7) + + @T_3840x2160_25hz_vic_94.setter + def T_3840x2160_25hz_vic_94(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=7) + + @property + def T_3840x2160_30hz_vic_95(self) -> bool: + """ + Set and get state of support timing VIC 95 3840x2160 30Hz (16:9). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(8) + + @T_3840x2160_30hz_vic_95.setter + def T_3840x2160_30hz_vic_95(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=8) + + @property + def T_3840x2160_50hz_vic_96(self) -> bool: + """ + Set and get state of support timing VIC 96 3840x2160 50Hz (16:9). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(9) + + @T_3840x2160_50hz_vic_96.setter + def T_3840x2160_50hz_vic_96(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=9) + + @property + def T_3840x2160_60hz_vic_97(self) -> bool: + """ + Set and get state of support timing VIC 97 3840x2160 60Hz (16:9). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(10) + + @T_3840x2160_60hz_vic_97.setter + def T_3840x2160_60hz_vic_97(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=10) + + @property + def T_4096x2160_24hz_vic_98(self) -> bool: + """ + Set and get state of support timing VIC 98 4096x2160 24Hz (256:135). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(11) + + @T_4096x2160_24hz_vic_98.setter + def T_4096x2160_24hz_vic_98(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=11) + + @property + def T_4096x2160_25hz_vic_99(self) -> bool: + """ + Set and get state of support timing VIC 99 4096x2160 25Hz (256:135). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(12) + + @T_4096x2160_25hz_vic_99.setter + def T_4096x2160_25hz_vic_99(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=12) + + @property + def T_4096x2160_30hz_vic_100(self) -> bool: + """ + Set and get state of support timing VIC 100 4096x2160 30Hz (256:135). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(13) + + @T_4096x2160_30hz_vic_100.setter + def T_4096x2160_30hz_vic_100(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=13) + + @property + def T_4096x2160_50hz_vic_101(self) -> bool: + """ + Set and get state of support timing VIC 101 4096x2160 50Hz (256:135). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(14) + + @T_4096x2160_50hz_vic_101.setter + def T_4096x2160_50hz_vic_101(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=14) + + @property + def T_4096x2160_60hz_vic_102(self) -> bool: + """ + Set and get state of support timing VIC 102 4096x2160 60Hz (256:135). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(15) + + @T_4096x2160_60hz_vic_102.setter + def T_4096x2160_60hz_vic_102(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=15) + + @property + def T_3840x2160_24hz_vic_103(self) -> bool: + """ + Set and get state of support timing VIC 103 3840x2160 24Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(16) + + @T_3840x2160_24hz_vic_103.setter + def T_3840x2160_24hz_vic_103(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=16) + + @property + def T_3840x2160_25hz_vic_104(self) -> bool: + """ + Set and get state of support timing VIC 104 3840x2160 25Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(17) + + @T_3840x2160_25hz_vic_104.setter + def T_3840x2160_25hz_vic_104(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=17) + + @property + def T_3840x2160_30hz_vic_105(self) -> bool: + """ + Set and get state of support timing VIC 105 3840x2160 30Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(18) + + @T_3840x2160_30hz_vic_105.setter + def T_3840x2160_30hz_vic_105(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=18) + + @property + def T_3840x2160_50hz_vic_106(self) -> bool: + """ + Set and get state of support timing VIC 106 3840x2160 50Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(19) + + @T_3840x2160_50hz_vic_106.setter + def T_3840x2160_50hz_vic_106(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=19) + + @property + def T_3840x2160_60hz_vic_107(self) -> bool: + """ + Set and get state of support timing VIC 107 3840x2160 60Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(20) + + @T_3840x2160_60hz_vic_107.setter + def T_3840x2160_60hz_vic_107(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=20) + + @property + def T_3840x2160_48hz_vic_114(self) -> bool: + """ + Set and get state of support timing VIC 114 3840x2160 48Hz (16:9). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(21) + + @T_3840x2160_48hz_vic_114.setter + def T_3840x2160_48hz_vic_114(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=21) + + @property + def T_4096x2160_48hz_vic_115(self) -> bool: + """ + Set and get state of support timing VIC 115 4096x2160 48Hz (256:135). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(22) + + @T_4096x2160_48hz_vic_115.setter + def T_4096x2160_48hz_vic_115(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=22) + + @property + def T_3840x2160_48hz_vic_116(self) -> bool: + """ + Set and get state of support timing VIC 116 3840x2160 48Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(23) + + @T_3840x2160_48hz_vic_116.setter + def T_3840x2160_48hz_vic_116(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=23) + + @property + def T_3840x2160_100hz_vic_117(self) -> bool: + """ + Set and get state of support timing VIC 117 3840x2160 100Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(24) + + @T_3840x2160_100hz_vic_117.setter + def T_3840x2160_100hz_vic_117(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=24) + + @property + def T_3840x2160_120hz_vic_118(self) -> bool: + """ + Set and get state of support timing VIC 118 3840x2160 120Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(25) + + @T_3840x2160_120hz_vic_118.setter + def T_3840x2160_120hz_vic_118(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=25) + + @property + def T_3840x2160_100hz_vic_119(self) -> bool: + """ + Set and get state of support timing VIC 119 3840x2160 100Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(26) + + @T_3840x2160_100hz_vic_119.setter + def T_3840x2160_100hz_vic_119(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=26) + + @property + def T_3840x2160_120hz_vic_120(self) -> bool: + """ + Set and get state of support timing VIC 120 3840x2160 120Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(27) + + @T_3840x2160_120hz_vic_120.setter + def T_3840x2160_120hz_vic_120(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=27) + + @property + def T_5120x2160_24hz_vic_121(self) -> bool: + """ + Set and get state of support timing VIC 121 5120x2160 24Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(28) + + @T_5120x2160_24hz_vic_121.setter + def T_5120x2160_24hz_vic_121(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=28) + + @property + def T_5120x2160_25hz_vic_122(self) -> bool: + """ + Set and get state of support timing VIC 122 5120x2160 25Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(29) + + @T_5120x2160_25hz_vic_122.setter + def T_5120x2160_25hz_vic_122(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=29) + + @property + def T_5120x2160_30hz_vic_123(self) -> bool: + """ + Set and get state of support timing VIC 123 5120x2160 30Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(30) + + @T_5120x2160_30hz_vic_123.setter + def T_5120x2160_30hz_vic_123(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=30) + + @property + def T_5120x2160_48hz_vic_124(self) -> bool: + """ + Set and get state of support timing VIC 124 5120x2160 48Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(31) + + @T_5120x2160_48hz_vic_124.setter + def T_5120x2160_48hz_vic_124(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=31) + + @property + def T_5120x2160_50hz_vic_125(self) -> bool: + """ + Set and get state of support timing VIC 125 5120x2160 50Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(0) + + @T_5120x2160_50hz_vic_125.setter + def T_5120x2160_50hz_vic_125(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=0) + + @property + def T_5120x2160_60hz_vic_126(self) -> bool: + """ + Set and get state of support timing VIC 126 5120x2160 60Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(1) + + @T_5120x2160_60hz_vic_126.setter + def T_5120x2160_60hz_vic_126(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=1) + + @property + def T_5120x2160_100hz_vic_127(self) -> bool: + """ + Set and get state of support timing VIC 127 5120x2160 100Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(2) + + @T_5120x2160_100hz_vic_127.setter + def T_5120x2160_100hz_vic_127(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=2) + + @property + def T_5120x2160_120hz_vic_193(self) -> bool: + """ + Set and get state of support timing VIC 193 5120x2160 120Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(3) + + @T_5120x2160_120hz_vic_193.setter + def T_5120x2160_120hz_vic_193(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=3) + + @property + def T_7680x4320_24hz_vic_194(self) -> bool: + """ + Set and get state of support timing VIC 194 7680x4320 24Hz (16:9). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(4) + + @T_7680x4320_24hz_vic_194.setter + def T_7680x4320_24hz_vic_194(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=4) + + @property + def T_7680x4320_25hz_vic_195(self) -> bool: + """ + Set and get state of support timing VIC 195 7680x4320 25Hz (16:9). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(5) + + @T_7680x4320_25hz_vic_195.setter + def T_7680x4320_25hz_vic_195(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=5) + + @property + def T_7680x4320_30hz_vic_196(self) -> bool: + """ + Set and get state of support timing VIC 196 7680x4320 30Hz (16:9). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(6) + + @T_7680x4320_30hz_vic_196.setter + def T_7680x4320_30hz_vic_196(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=6) + + @property + def T_7680x4320_48hz_vic_197(self) -> bool: + """ + Set and get state of support timing VIC 197 7680x4320 48Hz (16:9). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(7) + + @T_7680x4320_48hz_vic_197.setter + def T_7680x4320_48hz_vic_197(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=7) + + @property + def T_7680x4320_50hz_vic_198(self) -> bool: + """ + Set and get state of support timing VIC 198 7680x4320 50Hz (16:9). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(8) + + @T_7680x4320_50hz_vic_198.setter + def T_7680x4320_50hz_vic_198(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=8) + + @property + def T_7680x4320_60hz_vic_199(self) -> bool: + """ + Set and get state of support timing VIC 199 7680x4320 60Hz (16:9). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(9) + + @T_7680x4320_60hz_vic_199.setter + def T_7680x4320_60hz_vic_199(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=9) + + @property + def T_7680x4320_100hz_vic_200(self) -> bool: + """ + Set and get state of support timing VIC 200 7680x4320 100Hz (16:9). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(10) + + @T_7680x4320_100hz_vic_200.setter + def T_7680x4320_100hz_vic_200(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=10) + + @property + def T_7680x4320_120hz_vic_201(self) -> bool: + """ + Set and get state of support timing VIC 201 7680x4320 120Hz (16:9). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(11) + + @T_7680x4320_120hz_vic_201.setter + def T_7680x4320_120hz_vic_201(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=11) + + @property + def T_7680x4320_24hz_vic_202(self) -> bool: + """ + Set and get state of support timing VIC 202 7680x4320 24Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(12) + + @T_7680x4320_24hz_vic_202.setter + def T_7680x4320_24hz_vic_202(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=12) + + @property + def T_7680x4320_25hz_vic_203(self) -> bool: + """ + Set and get state of support timing VIC 203 7680x4320 25Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(13) + + @T_7680x4320_25hz_vic_203.setter + def T_7680x4320_25hz_vic_203(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=13) + + @property + def T_7680x4320_30hz_vic_204(self) -> bool: + """ + Set and get state of support timing VIC 204 7680x4320 30Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(14) + + @T_7680x4320_30hz_vic_204.setter + def T_7680x4320_30hz_vic_204(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=14) + + @property + def T_7680x4320_48hz_vic_205(self) -> bool: + """ + Set and get state of support timing VIC 205 7680x4320 48Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(15) + + @T_7680x4320_48hz_vic_205.setter + def T_7680x4320_48hz_vic_205(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=15) + + @property + def T_7680x4320_50hz_vic_206(self) -> bool: + """ + Set and get state of support timing VIC 206 7680x4320 50Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(16) + + @T_7680x4320_50hz_vic_206.setter + def T_7680x4320_50hz_vic_206(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=16) + + @property + def T_7680x4320_60hz_vic_207(self) -> bool: + """ + Set and get state of support timing VIC 207 7680x4320 60Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(17) + + @T_7680x4320_60hz_vic_207.setter + def T_7680x4320_60hz_vic_207(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=17) + + @property + def T_7680x4320_100hz_vic_208(self) -> bool: + """ + Set and get state of support timing VIC 208 7680x4320 100Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(18) + + @T_7680x4320_100hz_vic_208.setter + def T_7680x4320_100hz_vic_208(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=18) + + @property + def T_7680x4320_120hz_vic_209(self) -> bool: + """ + Set and get state of support timing VIC 209 7680x4320 120Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(19) + + @T_7680x4320_120hz_vic_209.setter + def T_7680x4320_120hz_vic_209(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=19) + + @property + def T_10240x4320_24hz_vic_210(self) -> bool: + """ + Set and get state of support timing VIC 210 10240x4320 24Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(20) + + @T_10240x4320_24hz_vic_210.setter + def T_10240x4320_24hz_vic_210(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=20) + + @property + def T_10240x4320_25hz_vic_211(self) -> bool: + """ + Set and get state of support timing VIC 211 10240x4320 25Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(21) + + @T_10240x4320_25hz_vic_211.setter + def T_10240x4320_25hz_vic_211(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=21) + + @property + def T_10240x4320_30hz_vic_212(self) -> bool: + """ + Set and get state of support timing VIC 212 10240x4320 30Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(22) + + @T_10240x4320_30hz_vic_212.setter + def T_10240x4320_30hz_vic_212(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=22) + + @property + def T_10240x4320_48hz_vic_213(self) -> bool: + """ + Set and get state of support timing VIC 213 10240x4320 48Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(23) + + @T_10240x4320_48hz_vic_213.setter + def T_10240x4320_48hz_vic_213(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=23) + + @property + def T_10240x4320_50hz_vic_214(self) -> bool: + """ + Set and get state of support timing VIC 214 10240x4320 50Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(24) + + @T_10240x4320_50hz_vic_214.setter + def T_10240x4320_50hz_vic_214(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=24) + + @property + def T_10240x4320_60hz_vic_215(self) -> bool: + """ + Set and get state of support timing VIC 215 10240x4320 60Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(25) + + @T_10240x4320_60hz_vic_215.setter + def T_10240x4320_60hz_vic_215(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=25) + + @property + def T_10240x4320_100hz_vic_216(self) -> bool: + """ + Set and get state of support timing VIC 216 10240x4320 10Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(26) + + @T_10240x4320_100hz_vic_216.setter + def T_10240x4320_100hz_vic_216(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=26) + + @property + def T_10240x4320_120hz_vic_217(self) -> bool: + """ + Set and get state of support timing VIC 217 10240x4320 120Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(27) + + @T_10240x4320_120hz_vic_217.setter + def T_10240x4320_120hz_vic_217(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=27) + + @property + def T_4096x2160_100hz_vic_218(self) -> bool: + """ + Set and get state of support timing VIC 218 4096x2160 100Hz (256:135). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(28) + + @T_4096x2160_100hz_vic_218.setter + def T_4096x2160_100hz_vic_218(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=28) + + @property + def T_4096x2160_120hz_vic_219(self) -> bool: + """ + Set and get state of support timing VIC 219 4096x2160 120Hz (256:135). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(29) + + @T_4096x2160_120hz_vic_219.setter + def T_4096x2160_120hz_vic_219(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=29) + + def clear_all(self): + self.__param_1.default_value = 0 + self.__param_2.default_value = 0 + + +class HdmiDutCaps: + """ + Class `DutCapsDp14` defines the DUT capabilities and allows setting: + - Set and get state of sink DSC support `is_sink_dsc_support`. + - Set and get state of sink DSC 10 BPC support `is_sink_dsc_10bpc`. + - Set and get state of sink DSC 12 BPC support `is_sink_dsc_12bpc`. + - Set and get state of sink Native 420 support `is_sink_dsc_native_420`. + - Set and get state of sink Native 420 support `is_sink_dsc_16bpc`. + - Set and get state of sink Native 420 support `is_sink_dsc_all_bpp`. + """ + def __init__(self, param: Param): + self.__value = param + + @property + def is_sink_dsc_support(self) -> bool: + """ + Set and get state of SINK DSC support. + + Returns: + object of bool type + """ + return self.__value._get_by_bit_number(4) + + @is_sink_dsc_support.setter + def is_sink_dsc_support(self, state: bool): + self.__value._set_by_bit_number(state=state, value=1, bit_number=4) + + @property + def is_sink_dsc_10bpc(self) -> bool: + """ + Set and get state of SINK DSC 10 bpc support. + + Returns: + object of bool type + """ + return self.__value._get_by_bit_number(5) + + @is_sink_dsc_10bpc.setter + def is_sink_dsc_10bpc(self, state: bool): + self.__value._set_by_bit_number(state=state, value=1, bit_number=5) + + @property + def is_sink_dsc_12bpc(self) -> bool: + """ + Set and get state of SINK DSC 12 bpc support. + + Returns: + object of bool type + """ + return self.__value._get_by_bit_number(6) + + @is_sink_dsc_12bpc.setter + def is_sink_dsc_12bpc(self, state: bool): + self.__value._set_by_bit_number(state=state, value=1, bit_number=6) + + @property + def is_sink_dsc_native_420(self) -> bool: + """ + Set and get state of SINK DSC Native 420 support. + + Returns: + object of bool type + """ + return self.__value._get_by_bit_number(7) + + @is_sink_dsc_native_420.setter + def is_sink_dsc_native_420(self, state: bool): + self.__value._set_by_bit_number(state=state, value=1, bit_number=7) + + @property + def is_sink_dsc_16bpc(self) -> bool: + """ + Set and get state of SINK DSC 16 bpc support. + + Returns: + object of bool type + """ + return self.__value._get_by_bit_number(8) + + @is_sink_dsc_16bpc.setter + def is_sink_dsc_16bpc(self, state: bool): + self.__value._set_by_bit_number(state=state, value=1, bit_number=8) + + @property + def is_sink_dsc_all_bpp(self) -> bool: + """ + Set and get state of SINK DSC all BPP support. + + Returns: + object of bool type + """ + return self.__value._get_by_bit_number(9) + + @is_sink_dsc_all_bpp.setter + def is_sink_dsc_all_bpp(self, state: bool): + self.__value._set_by_bit_number(state=state, value=1, bit_number=9) + + @property + def is_sink_ycbcr_support(self) -> bool: + """ + Set and get state of SINK YCbCr support. + + Returns: + object of bool type + """ + return self.__value._get_by_bit_number(10) + + @is_sink_ycbcr_support.setter + def is_sink_ycbcr_support(self, state: bool): + self.__value._set_by_bit_number(state=state, value=1, bit_number=10) + + +class HdmiSinkDUTTestParam: + """ + Class `HdmiSinkDUTTestParam` allows working with parameters from HDMI DSC source part. + - Seg and get test mode `test_mode`. + - Set and get timeout `timeout`. + - Set and get MAX FRL rate `max_frl_rate` type `HdmiFrlRate`. + - Set and get MAX TMDS Clock 'max_tmds_clock'. + - Set and get HDMI DUT Caps 'dut_caps' type 'HdmiDutCaps'. + - Set and get DSC MAX FRL rate `dsc_max_frl_rate` type `HdmiFrlRate`. + - Set and get available video modes `video_modes`. + - Set and get DSC max slice (from 0 to 7) 'dsc_max_slice'. + - Set and get Total chunk bytes `total_chunk_bytes`. + """ + def __init__(self, json_obj): + self.__test_mode = Param(json_obj["TSI_HDMI_SNKCTS_TEST_MODE"]) + self.__timeout = Param(json_obj["TSI_HDMI_SNKCTS_TIMEOUT"]) + self.__max_frl_rate = Param(json_obj["TSI_HDMI_SNKCTS_MAX_FRL_RATE"]) + self.__max_tmds_clock = Param(json_obj["TSI_HDMI_SNKCTS_MAX_TMDS_CLOCK"]) + self.__dut_caps = HdmiDutCaps(Param(json_obj["TSI_HDMI_SNKCTS_DUT_CAPS"])) + self.__dsc_max_frl_rate = Param(json_obj["TSI_HDMI_SNKCTS_DSC_MAX_FRL_RATE"]) + self.__video_modes = HdmiAvailableVideoModes(Param(json_obj["TSI_HDMI_SNKCTS_DSC_VIDEO_FORMAT"]), + Param(json_obj["TSI_HDMI_SNKCTS_DSC_VIDEO_FORMAT_2"])) + self.__dsc_max_slice = Param(json_obj["TSI_HDMI_SNKCTS_DSC_MAX_SLICES"]) + self.__total_chunk_bytes = Param(json_obj["TSI_HDMI_SNKCTS_DSC_TOTAL_CHUNK_BYTES"]) + + @property + def test_mode(self) -> HdmiTestMode: + """ + Set and get test mode. + + Returns: + object of `HdmiTestMode` type + """ + return HdmiTestMode(self.__test_mode.default_value) + + @test_mode.setter + def test_mode(self, test_mode: HdmiTestMode): + self.__test_mode.default_value = test_mode.value + + @property + def timeout(self) -> int: + """ + Set and get test timeout, in milliseconds. + + Returns: + object of int type + """ + return self.__timeout.default_value + + @timeout.setter + def timeout(self, timeout: int): + if not(self.__timeout.min_value < timeout < self.__timeout.max_value): + raise ValueError(f"Timeout cannot be less than {self.__timeout.min_value} and more than " + f"{self.__timeout.max_value}.") + self.__timeout.default_value = timeout + + @property + def max_frl_rate(self) -> HdmiFrlRate: + """ + Set and get Max DSC FRL mode. + + Returns: + object of `HdmiFrlRate` type + """ + return HdmiFrlRate(self.__max_frl_rate.default_value) + + @max_frl_rate.setter + def max_frl_rate(self, max_frl_rate: HdmiFrlRate): + self.__max_frl_rate.default_value = max_frl_rate.value + + @property + def max_tmds_clock(self) -> int: + """ + Set and get test MAX TMDS clock, in MHz. + + Returns: + object of int type + """ + return self.__max_tmds_clock.default_value + + @max_tmds_clock.setter + def max_tmds_clock(self, max_tmds_clock: int): + if not (self.__max_tmds_clock.min_value < max_tmds_clock < self.__max_tmds_clock.max_value): + raise ValueError(f"MAX TMDS clock cannot be less than {self.__max_tmds_clock.min_value} and more than " + f"{self.__max_tmds_clock.max_value}.") + self.__max_tmds_clock.default_value = max_tmds_clock + + @property + def dut_caps(self) -> HdmiDutCaps: + """ + Set and get DUT caps. + + Returns: + object HdmiDutCaps + """ + return self.__dut_caps + + @dut_caps.setter + def dut_caps(self, dut_caps: HdmiDutCaps): + self.__dut_caps = dut_caps + + @property + def dsc_max_frl_rate(self) -> HdmiFrlRate: + """ + Set and get Max DSC FRL mode. + + Returns: + object of `HdmiFrlRate` type + """ + return HdmiFrlRate(self.__dsc_max_frl_rate.default_value) + + @dsc_max_frl_rate.setter + def dsc_max_frl_rate(self, max_dsc_frl_rate: HdmiFrlRate): + self.__dsc_max_frl_rate.default_value = max_dsc_frl_rate.value + + @property + def video_modes(self) -> HdmiAvailableVideoModes: + """ + Set and get supported video modes. + + Returns: + object `HdmiAvailableVideoModes` + """ + return self.__video_modes + + @video_modes.setter + def video_modes(self, video_modes: HdmiAvailableVideoModes): + self.__video_modes = video_modes + + @property + def dsc_max_slice(self) -> int: + """ + Set and get test DSC max slice, in milliseconds. + + Returns: + object of int type + """ + return self.__dsc_max_slice.default_value + + @dsc_max_slice.setter + def dsc_max_slice(self, dsc_max_slice: int): + if not(self.__dsc_max_slice.min_value < dsc_max_slice < self.__dsc_max_slice.max_value): + raise ValueError(f"DSC max slice cannot be less than {self.__dsc_max_slice.min_value} and more than " + f"{self.__dsc_max_slice.max_value}.") + self.__dsc_max_slice.default_value = dsc_max_slice + + @property + def total_chunk_bytes(self) -> int: + """ + Set and get test Total chunk bytes, in milliseconds. + + Returns: + object of int type + """ + return self.__total_chunk_bytes.default_value + + @total_chunk_bytes.setter + def total_chunk_bytes(self, total_chunk_bytes: int): + if not(self.__total_chunk_bytes.min_value < total_chunk_bytes < self.__total_chunk_bytes.max_value): + raise ValueError(f"Total chunk bytes cannot be less than {self.__total_chunk_bytes.min_value} and more than" + f" {self.__total_chunk_bytes.max_value}.") + self.__total_chunk_bytes.default_value = total_chunk_bytes diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/hdmi_source_tests.py b/UniTAP/dev/modules/dut_tests/dut_default_params/hdmi_source_tests.py new file mode 100644 index 0000000..cf9ca53 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/hdmi_source_tests.py @@ -0,0 +1,27 @@ +from UniTAP.dev.modules.dut_tests.test_group_params_types import param_by_ci_name + + +class HdmiSourceDUTTestParam: + """ + Class `HdmiSourceDUTTestParam` allows working with parameters from HDMI DSC source part. + - Set and get timeout `timeout`. + """ + def __init__(self, json_obj): + self.__timeout = json_obj["TSI_HDMI_SRCCTS_TIMEOUT"] + + @property + def timeout(self) -> int: + """ + Set and get test timeout, in milliseconds. + + Returns: + object of int type + """ + return self.__timeout.default_value + + @timeout.setter + def timeout(self, timeout: int): + if not(self.__timeout.min_value < timeout < self.__timeout.max_value): + raise ValueError(f"Timeout cannot be less than {self.__timeout.min_value} and more than " + f"{self.__timeout.max_value}.") + self.__timeout.default_value = timeout diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/hdr10_tests.py b/UniTAP/dev/modules/dut_tests/dut_default_params/hdr10_tests.py new file mode 100644 index 0000000..798c358 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/hdr10_tests.py @@ -0,0 +1,27 @@ +from UniTAP.dev.modules.dut_tests.test_group_params_types import Param + + +class Hdr10TestParam: + + def __init__(self, json_obj): + self.__same_frame = Param(json_obj["TSI_HDR10_CTS_DISTR_SAME_FRAME"]) + self.__timeout = Param(json_obj["TSI_HDR10_CTS_DISTR_TIMEOUT"]) + + @property + def timeout(self): + return self.__timeout.default_value + + @timeout.setter + def timeout(self, timeout: int): + if not(self.__timeout.min_value < timeout < self.__timeout.max_value): + raise ValueError(f"Timeout cannot be less than {self.__timeout.min_value} and more than " + f"{self.__timeout.max_value}.") + self.__timeout.default_value = timeout + + @property + def same_frame(self): + return self.__same_frame.default_value + + @same_frame.setter + def same_frame(self, same_frame: bool): + self.__same_frame.default_value = same_frame diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/link_config_test.py b/UniTAP/dev/modules/dut_tests/dut_default_params/link_config_test.py new file mode 100644 index 0000000..a00e2ff --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/link_config_test.py @@ -0,0 +1,129 @@ +from enum import IntEnum +from UniTAP.dev.modules.dut_tests.test_group_params_types import Param + + +class LinkRate(IntEnum): + """ + Class `LinkRate` describes available supported link rates. + """ + Rate_1_62_Gbps = 6 + Rate_2_7_Gbps = 10 + Rate_5_4_Gbps = 20 + Rate_8_1_Gbpc = 30 + + +class LinkConfigTestParam: + """ + Class `LinkConfigTestParam` describes parameters for Link configuration test. + - Test timeout, in milliseconds `timeout`. + - Defines the maximum number of lanes to be tested `max_lane_count`. Possible variants: 1, 2, 4. + - Defines the maximum link rate to be tested `max_rate` type `LinkRate`. + - Defines the length of the HPD pulse used to start each test iteration, in milliseconds `hpd_pulse_duration`. + - Defines how long the test waits for LT start after issuing HPD pulse, in milliseconds `lt_start_timeout`. + - Defines the additional delay inserted in between test iterations, in milliseconds `test_loop_delay`. + """ + def __init__(self, json_obj): + self.__timeout = Param(json_obj["TSI_DP_LTT_TIMEOUT"]) + self.__max_lane_count = Param(json_obj["TSI_DP_LTT_MAX_LANE_COUNT"]) + self.__max_rate = Param(json_obj["TSI_DP_LTT_MAX_RATE"]) + self.__hpd_pulse_duration = Param(json_obj["TSI_DP_LTT_HPD_PULSE_DURATION"]) + self.__lt_start_timeout = Param(json_obj["TSI_DP_LTT_LT_START_TIMEOUT"]) + self.__test_loop_delay = Param(json_obj["TSI_DP_LTT_TEST_LOOP_DELAY"]) + + @property + def timeout(self): + """ + Set and get test timeout, in milliseconds. + + Returns: + object of int type + """ + return self.__timeout.default_value + + @timeout.setter + def timeout(self, timeout: int): + if not(self.__timeout.min_value < timeout < self.__timeout.max_value): + raise ValueError(f"Timeout cannot be less than {self.__timeout.min_value} and more than " + f"{self.__timeout.max_value}.") + self.__timeout.default_value = timeout + + @property + def max_lane_count(self) -> int: + """ + Set and get maximum number of lanes. + + Returns: + object of int type + """ + return self.__max_lane_count.default_value + + @max_lane_count.setter + def max_lane_count(self, max_lane_count: int): + if not max_lane_count in [1, 2, 4]: + raise ValueError("Max lane count must be in range: 1, 2, or 4.") + self.__max_lane_count.default_value = max_lane_count + + @property + def max_rate(self): + """ + Set and get maximum link rate. + + Returns: + object `LinkRate` + """ + return self.__max_rate.default_value + + @max_rate.setter + def max_rate(self, max_rate: LinkRate): + self.__max_rate.default_value = max_rate.value + + @property + def hpd_pulse_duration(self) -> int: + """ + Set and get length of the HPD pulse. + + Returns: + object of int type + """ + return self.__hpd_pulse_duration.default_value + + @hpd_pulse_duration.setter + def hpd_pulse_duration(self, hpd_pulse_duration: int): + if not(self.__hpd_pulse_duration.min_value < hpd_pulse_duration < self.__hpd_pulse_duration.max_value): + raise ValueError(f"HPD pulse duration must be less than {self.__hpd_pulse_duration.min_value} and more than" + f" {self.__hpd_pulse_duration.max_value}.") + self.__hpd_pulse_duration.default_value = hpd_pulse_duration + + @property + def lt_start_timeout(self) -> int: + """ + Set and get link training start timeout. + + Returns: + object of int type + """ + return self.__lt_start_timeout.default_value + + @lt_start_timeout.setter + def lt_start_timeout(self, lt_start_timeout: int): + if not(self.__lt_start_timeout.min_value < lt_start_timeout < self.__lt_start_timeout.max_value): + raise ValueError(f"Link training start timeout cannot be less than {self.__lt_start_timeout.min_value} " + f"and more than {self.__lt_start_timeout.max_value}.") + self.__lt_start_timeout.default_value = lt_start_timeout + + @property + def test_loop_delay(self) -> int: + """ + Set and get test loop delay. + + Returns: + object of int type + """ + return self.__test_loop_delay.default_value + + @test_loop_delay.setter + def test_loop_delay(self, test_loop_delay: int): + if not(self.__test_loop_delay.min_value < test_loop_delay < self.__test_loop_delay.max_value): + raise ValueError(f"Test loop delay cannot be less than {self.__test_loop_delay.min_value} " + f"and more than {self.__test_loop_delay.max_value}.") + self.__test_loop_delay.default_value = test_loop_delay diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/lttpr_tests.py b/UniTAP/dev/modules/dut_tests/dut_default_params/lttpr_tests.py new file mode 100644 index 0000000..d4e139b --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/lttpr_tests.py @@ -0,0 +1,125 @@ +from UniTAP.dev.modules.dut_tests.test_group_params_types import Param +from .dp_1_4_source_general_tab import TestAutomationFlags, update_default_value + + +class LttprDutCapsFlags(Param): + """ + Class `LttprDutCapsFlags` defines the DUT capabilities as flags and allows setting: + - Define that DUT is Type-C device `dut_is_type_c_device`. + """ + + def __init__(self, json_obj): + super().__init__(json_obj) + + @property + def dut_is_type_c_device(self) -> bool: + """ + Set and get Define that DUT is Type-C device flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(0, bool) + + @dut_is_type_c_device.setter + def dut_is_type_c_device(self, dut_is_type_c_device: bool): + self._set_by_bitmask(dut_is_type_c_device, 0) + + def set_all_caps(self): + """ + Set all setting. + """ + self.dut_is_type_c_device = True + + def clear_all_caps(self): + """ + Clear all settings. + """ + self.dut_is_type_c_device = False + + +class DebugOptions: + def __init__(self, json_obj): + self.__cr_iteration_delay = Param(json_obj["TSI_DP20_LTTPR_DEBUG_CR_ITERATION_DELAY"]) + + @property + def cr_iteration_delay(self) -> bool: + """ + Set and get Force manual visual check flag. + + Returns: + object of bool type + """ + return self.__cr_iteration_delay.default_value + + @cr_iteration_delay.setter + def cr_iteration_delay(self, delay: int): + if not(self.__cr_iteration_delay.min_value < delay < self.__cr_iteration_delay.max_value): + raise ValueError(f"Timeout cannot be less than {self.__cr_iteration_delay.min_value} and more than " + f"{self.__cr_iteration_delay.max_value}.") + self.__cr_iteration_delay.default_value = delay + + +class DpLttprTestParam: + """ + Class `DpLttprTestParam` describes requirement parameters for LTTPR DP 1228b/132b tests: + - Set and get `timeout`. Describes test timeout, in milliseconds. + - Set and get `hpd_duration`. Describes duration of long HPD pulses generated, in milliseconds. + - Set and get `dut_caps_flag`. Describes source DUT capabilities flags. + """ + def __init__(self, json_obj): + self.__timeout = Param(json_obj["TSI_DP20_LTTPR_TIMEOUT"]) + self.__max_lanes = Param(json_obj["TSI_DP20_LTTPR_MAX_LANES"]) # Not access + self.__max_link_rate = Param(json_obj["TSI_DP20_LTTPR_MAX_LINK_RATE"]) # Not access + self.__dut_caps = LttprDutCapsFlags(json_obj["TSI_DP20_LTTPR_DUT_CAPS"]) + self.__test_automation = TestAutomationFlags(json_obj["TSI_DP20_LTTPR_DUT_TA"]) # Not access + self.__hpd_duration = Param(json_obj["TSI_DP20_LTTPR_LONG_HPD_PULSE"]) + self.__debug_options = DebugOptions(json_obj) + + @property + def timeout(self) -> int: + """ + Set and get test timeout, in milliseconds. + + Returns: + object of int type + """ + return self.__timeout.default_value + + @timeout.setter + def timeout(self, timeout: int): + if not(self.__timeout.min_value < timeout < self.__timeout.max_value): + raise ValueError(f"Timeout cannot be less than {self.__timeout.min_value} and more than " + f"{self.__timeout.max_value}.") + self.__timeout.default_value = timeout + + @property + def hpd_duration(self) -> int: + """ + Set and get duration of long HPD pulses generated, in milliseconds. + + Returns: + object of int type + """ + return self.__hpd_duration.default_value + + @hpd_duration.setter + def hpd_duration(self, hpd_duration: int): + if not(self.__hpd_duration.min_value < hpd_duration < self.__hpd_duration.max_value): + raise ValueError(f"Timeout cannot be less than {self.__hpd_duration.min_value} and more than " + f"{self.__hpd_duration.max_value}.") + self.__hpd_duration.default_value = hpd_duration + + @property + def dut_caps_flags(self) -> LttprDutCapsFlags: + """ + Set and get LTTPR DUT capabilities flags. + + Returns: + object of `DutCapsFlags` type + """ + return self.__dut_caps + + @dut_caps_flags.setter + def dut_caps_flags(self, dut_caps_flags: LttprDutCapsFlags): + self.__dut_caps = dut_caps_flags diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/pixel_video_test.py b/UniTAP/dev/modules/dut_tests/dut_default_params/pixel_video_test.py new file mode 100644 index 0000000..0201aa8 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/pixel_video_test.py @@ -0,0 +1,295 @@ +from typing import List, Union, Optional +from enum import IntEnum +from UniTAP.dev.modules.dut_tests.test_group_params_types import Param + + +class VideoPixelTestColorDepth(IntEnum): + """ + Class `VideoPixelTestColorDepth` describes available supported color depth. + """ + ColorDepth6 = 6 + ColorDepth8 = 8 + ColorDepth10 = 10 + ColorDepth12 = 12 + ColorDepth16 = 16 + + +class VideoPixelTestElementFormat(IntEnum): + """ + Class `VideoPixelTestElementFormat` describes available supported color formats. + """ + RGB_6BPC = 16 + RGB_8BPC = 17 + RGB_10BPC = 18 + RGB_12BPC = 19 + RGB_16BPC = 20 + YCbCr444_080808 = 256 + YCbCr444_161616 = 257 + YCbCr420_8BPC_080808 = 800 + YCbCr420_10BPC_080808 = 801 + YCbCr420_12BPC_161616 = 802 + YCbCr420_16BPC_161616 = 803 + + +class VideoPixelTestAlignment(IntEnum): + """ + Class `VideoPixelTestColorDepth` describes available supported alignment. + """ + MSB = 0 + LSB = 1 + + +class VideoPixelTestExportFormat(IntEnum): + """ + Class `VideoPixelTestColorDepth` describes available supported files format. + """ + BIN = 0 + PPM = 1 + BMP = 2 + + +class VideoPixelTestParam: + """ + Class `VideoPixelTestParam` describes parameters for video pixel test. + - Defines frame width as number of elements `image_width`. + - Defines frame height as number of elements `image_height`. + - Defines the color depth of the image as number of bits per color channel regardless of the color format + `color_depth` type `VideoPixelTestColorDepth`. + - Defines the element format used to encode the pixel data of the bitmap `frame_data`. + - Defines type of alignment (LSB-MSB data alignment) `alignment` type `VideoPixelTestAlignment`. + - Defines the length of the video test as number of frames `frames_number`. + - Defines number of frame that are allowed to be considered as 'failed' before the entire test is considered as + 'failed' `frames_number_mismatch`. + - Defines the number of pixels that allowed to be considered as 'failed' before the frame is considered as 'failed' + `pixel_tolerance`. + - Maximum number of frames failed frames saved per test run `max_auto_save_failed`. + - Contains the full path to the folder where failed frames are to be saved without trailing backslash + `failed_frame_folder`. + - Defines the number of failed frames to be exported from the video test `max_export_failed_frames`. + - Defines export file format `export_data_format` type `VideoPixelTestExportFormat`. + """ + def __init__(self, json_obj): + self.__image_width = Param(json_obj["TSI_REF1_WIDTH"]) + self.__image_height = Param(json_obj["TSI_REF1_HEIGHT"]) + self.__element_size = Param(json_obj["TSI_REF1_ELEMENT_SIZE"]) # Do not provide + self.__element_width = Param(json_obj["TSI_REF1_ELEMENT_WIDTH"]) # Do not provide + self.__element_height = Param(json_obj["TSI_REF1_ELEMENT_HEIGHT"]) # Do not provide + self.__color_depth = Param(json_obj["TSI_REF1_COLOR_DEPTH"]) # VideoPixelTestColorDepth + self.__element_format = Param(json_obj["TSI_REF1_ELEMENT_FORMAT"]) # VideoPixelTestElementFormat + self.__frame_data = Param(json_obj["TSI_REF1_FRAME_DATA"]) + self.__alignment = Param(json_obj["TSI_REF1_LSB_MSB"]) # VideoPixelTestAlignment + self.__frames_number = Param(json_obj["TSI_TEST_LENGTH"]) + self.__frames_number_mismatch = Param(json_obj["TSI_LIM_FRAME_MISMATCHES"]) + self.__pixel_tolerance = Param(json_obj["TSI_PIXEL_TOLERANCE"]) + self.__max_auto_save_failed = Param(json_obj["TSI_MAX_AUTO_SAVE_FAILED"]) + self.__failed_frame_folder = Param(json_obj["TSI_FAILED_FRAME_TARGET_FOLDER"]) + self.__max_export_failed_frames = Param(json_obj["TSI_MAX_EXPORT_FAILED"]) + self.__export_data_format = Param(json_obj["TSI_EXPORT_FORMAT"]) # Export format VideoPixelTestExportFormat + + @property + def image_width(self) -> int: + """ + Set and get frame width as number of elements. + + Returns: + object of int type + """ + return self.__image_width.default_value + + @image_width.setter + def image_width(self, image_width: int): + if not(self.__image_width.min_value < image_width < self.__image_width.max_value): + raise ValueError(f"Image width cannot be less than {self.__image_width.min_value} and more than " + f"{self.__image_width.max_value}.") + self.__image_width.default_value = image_width + + @property + def image_height(self) -> int: + """ + Set and get frame height as number of elements. + + Returns: + object of int type + """ + return self.__image_height.default_value + + @image_height.setter + def image_height(self, image_height: int): + if not(self.__image_height.min_value < image_height < self.__image_height.max_value): + raise ValueError(f"Image height cannot be less than {self.__image_height.min_value} and more than " + f"{self.__image_height.max_value}.") + self.__image_height.default_value = image_height + + @property + def color_depth(self): + """ + Set and get color depth of the image. + + Returns: + object `VideoPixelTestColorDepth` + """ + return self.__color_depth.default_value + + @color_depth.setter + def color_depth(self, color_depth: VideoPixelTestColorDepth): + self.__color_depth.default_value = color_depth.value + + @property + def element_format(self): + """ + Set and get element format used to encode the pixel data. + + Returns: + object `VideoPixelTestElementFormat` + """ + return self.__element_format.default_value + + @element_format.setter + def element_format(self, element_format: VideoPixelTestElementFormat): + self.__element_format.direct_set_def_value(element_format.value) + + @property + def frame_data(self) -> Optional[Union[bytearray, List[int], str]]: + """ + Set and get pixel data of image. + + Returns: + object bytearray | list[int] | None | str + """ + return self.__image_height.default_value + + @frame_data.setter + def frame_data(self, frame_data: Optional[Union[bytearray, List[int], str]]): + self.__frame_data.default_value = frame_data + + @property + def alignment(self): + """ + Set and get type of alignment. + + Returns: + object `VideoPixelTestAlignment` + """ + return self.__alignment.default_value + + @alignment.setter + def alignment(self, alignment: VideoPixelTestAlignment): + self.__alignment.default_value = alignment.value + + @property + def frames_number(self) -> int: + """ + Set and get length of the video test as number of frames. + + Returns: + object `VideoPixelTestAlignment` + """ + return self.__frames_number.default_value + + @frames_number.setter + def frames_number(self, frames_number: int): + if not(self.__frames_number.min_value < frames_number < self.__frames_number.max_value): + raise ValueError(f"Frames number cannot be less than {self.__frames_number.min_value} and more than " + f"{self.__frames_number.max_value}.") + self.__frames_number.default_value = frames_number + + @property + def frames_number_mismatch(self) -> int: + """ + Set and get number of frame that are allowed to be considered as 'failed' before the entire test is considered + as 'failed'. + + Returns: + object `VideoPixelTestAlignment` + """ + return self.__frames_number_mismatch.default_value + + @frames_number_mismatch.setter + def frames_number_mismatch(self, frames_number_mismatch: int): + if not(self.__frames_number_mismatch.min_value < frames_number_mismatch < + self.__frames_number_mismatch.max_value): + raise ValueError(f"Image height cannot be less than {self.__frames_number_mismatch.min_value} and more than" + f" {self.__frames_number_mismatch.max_value}.") + self.__frames_number_mismatch.default_value = frames_number_mismatch + + @property + def pixel_tolerance(self) -> int: + """ + Set and get number of pixels that allowed to be considered as 'failed' before the frame is considered as + 'failed'. + + Returns: + object `VideoPixelTestAlignment` + """ + return self.__pixel_tolerance.default_value + + @pixel_tolerance.setter + def pixel_tolerance(self, pixel_tolerance: int): + if not(self.__pixel_tolerance.min_value < pixel_tolerance < self.__pixel_tolerance.max_value): + raise ValueError(f"Pixel tolerance cannot be less than {self.__pixel_tolerance.min_value} and more than " + f"{self.__image_height.max_value}.") + self.__pixel_tolerance.default_value = pixel_tolerance + + @property + def max_auto_save_failed(self) -> int: + """ + Set and get maximum number of frames failed frames saved per test run. + + Returns: + object `VideoPixelTestAlignment` + """ + return self.__max_auto_save_failed.default_value + + @max_auto_save_failed.setter + def max_auto_save_failed(self, max_auto_save_failed: int): + if not(self.__max_auto_save_failed.min_value < max_auto_save_failed < self.__max_auto_save_failed.max_value): + raise ValueError(f"Max auto save failed frames cannot be less than {self.__max_auto_save_failed.min_value}" + f" and more than {self.__max_auto_save_failed.max_value}.") + self.__max_auto_save_failed.default_value = max_auto_save_failed + + @property + def failed_frame_folder(self): + """ + Set and get full path to the folder where failed frames are to be saved without trailing backslash. + + Returns: + object str + """ + return self.failed_frame_folder.default_value + + @failed_frame_folder.setter + def failed_frame_folder(self, failed_frame_folder: str): + self.failed_frame_folder.default_value = failed_frame_folder + + @property + def max_export_failed_frames(self) -> int: + """ + Set and get number of failed frames to be exported from the video test. + + Returns: + object int + """ + return self.__max_export_failed_frames.default_value + + @max_export_failed_frames.setter + def max_export_failed_frames(self, max_export_failed_frames: int): + if not(self.__max_export_failed_frames.min_value < max_export_failed_frames < + self.__max_export_failed_frames.max_value): + raise ValueError(f"Max export failed frames number cannot be less than " + f"{self.__max_export_failed_frames.min_value} and more than " + f"{self.__image_height.max_value}.") + self.__max_export_failed_frames.default_value = max_export_failed_frames + + @property + def export_data_format(self): + """ + Set and get file format. + + Returns: + object `VideoPixelTestExportFormat` + """ + return self.__export_data_format.default_value + + @export_data_format.setter + def export_data_format(self, export_data_format: VideoPixelTestExportFormat): + self.__export_data_format.default_value = export_data_format.value diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/usbc_electrical_tests.py b/UniTAP/dev/modules/dut_tests/dut_default_params/usbc_electrical_tests.py new file mode 100644 index 0000000..5d092fb --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/usbc_electrical_tests.py @@ -0,0 +1,546 @@ +from UniTAP.dev.modules.dut_tests.test_group_params_types import Param +from UniTAP.dev.modules.dut_tests.test_utils import update_default_value + + +class UsbcElTestDutCaps(Param): + """ + Class `UsbcElTestDutCaps` describes DUT capabilities flags and allows settings values. + - DUT Support DisplayPort Alternate mode `support_dp_alt_mode`. + - DUT can act as a power source `can_act_power_source` + - DUT can act as a power sink `cab_act_power_sink`. + - DUT does not support PD Contract `not_support_pd_contract`. + """ + def __init__(self, json_obj): + super().__init__(json_obj) + + @property + def support_dp_alt_mode(self): + """ + Set and get DUT Support DisplayPort Alternate mode flag support. + + Returns: + object bool + """ + return self._get_by_bitmask(0, bool) + + @support_dp_alt_mode.setter + def support_dp_alt_mode(self, support_dp_alt_mode: bool): + self._set_by_bitmask(support_dp_alt_mode, 0) + + @property + def can_act_power_source(self): + """ + Set and get DUT can act as a power source flag support. + + Returns: + object bool + """ + return self._get_by_bitmask(1, bool) + + @can_act_power_source.setter + def can_act_power_source(self, can_act_power_source: bool): + self._set_by_bitmask(can_act_power_source, 1) + + @property + def cab_act_power_sink(self): + """ + Set and get DUT can act as a power sink flag support. + + Returns: + object bool + """ + return self._get_by_bitmask(2, bool) + + @cab_act_power_sink.setter + def cab_act_power_sink(self, cab_act_power_sink: bool): + self._set_by_bitmask(cab_act_power_sink, 2) + + @property + def not_support_pd_contract(self): + """ + Set and get DUT does not support PD Contract flag support. + + Returns: + object bool + """ + return self._get_by_bitmask(3, bool) + + @not_support_pd_contract.setter + def not_support_pd_contract(self, not_support_pd_contract: bool): + self._set_by_bitmask(not_support_pd_contract, 3) + + +class UsbcElectricalTestParam: + """ + Class `DpElectricalTestParam` describes parameters for DP electrical tests. + - Test timeout, in milliseconds `timeout`. + - Defines DUT capabilities `dut_caps` type `UsbcElTestDutCaps`. + - Defines the time period for USB Type-C re-plug simulation 'disconnected' state `re_plug_time`. + - Defines the time period that the TE will wait for DUT to complete power contract negotiation `attach_timeout`. + - Defines Power Contract timeout, in milliseconds `pwr_contract_timeout`. + - Defines the low limit for the voltage window when power sink current is 0.5A or 0.9A `cc_low_voltage_1 `. + - Defines the high limit for the voltage window when power sink current is 0.5A or 0.9A `cc_high_voltage_1`. + - Defines the low limit for the voltage window when power sink current is 1.5A `cc_low_voltage_2`. + - Defines the high limit for the voltage window when power sink current is 1.5A `cc_high_voltage_2`. + - Defines the low limit for the voltage window when power sink current is 3.0A `cc_low_voltage_3`. + - Defines the high limit for the voltage window when power sink current is 3.0A `cc_high_voltage_3`. + - Defines the low limit for the Vcon voltage window `vconn_low_voltage`. + - Defines the high limit for the Vcon voltage window `vconn_high_voltage`. + - Defines the timeout the TE will wait for the DUT to enter into DisplayPort alternate mode `dp_alt_mode_timeout`. + - Defines the low voltage limit for the positive DP AUX line when idle `aux_positive_idle_low_voltage`. + - Defines the high voltage limit for the positive DP AUX line when idle `aux_positive_idle_high_voltage`. + - Defines the low voltage limit for the negative DP AUX line when idle `aux_negative_idle_low_voltage`. + - Defines the high voltage limit for the negative DP AUX line when idle `aux_negative_idle_high_voltage`. + - Defines the low limit for Vbus voltage window. The limit is defined in millivolts (mV) `vbus_low_voltage`. + - Defines the high limit for Vbus voltage window. The limit is defined in millivolts (mV) `vbus_high_voltage`. + - Defines the highest allowed deviation between maximum and minimum currents measured from the individual Vbus + pins as per-mill (‰) of total measured current. This means that if the total measured current is 3000mA, and the + setting 100, the maximum difference that is allowed between maximum and minimum currents is 300mA + `vbus_current_max`. + - Defines the highest allowed deviation between maximum and minimum currents measured from the individual GND pins + as per-mill (‰) of total measured current `gnd_current_max`. + - Defines delay from end of power contract negotiation to voltage / current measurements `power_measure_delay`. + - Defines the minimum current, in mA, that a Power Sink DUT must use in order to pass the test `min_dut_current`. + - Delay after load resistor on, milliseconds. This helps to skip transient processes `resistor_on_delay`. + - Delay for measure CC lines voltage after DUT plug detection, milliseconds `cc_measure_delay`. + """ + def __init__(self, json_obj): + self.__timeout = Param(json_obj["TSI_USBC_EL_TIMEOUT"]) + self.__dut_caps = UsbcElTestDutCaps(json_obj["TSI_USBC_EL_DUT_CAPS"]) + self.__re_plug_time = Param(json_obj["TSI_USBC_EL_REPLUG_TIME"]) + self.__attach_timeout = Param(json_obj["TSI_USBC_EL_DUT_ATTACH_TIMEOUT"]) + self.__pwr_contract_timeout = Param(json_obj["TSI_USBC_EL_PWR_CONTRACT_TIMEOUT"]) + self.__cc_low_voltage_1 = Param(json_obj["TSI_USBC_EL_CC_LOW_VOLTAGE_1"]) + self.__cc_high_voltage_1 = Param(json_obj["TSI_USBC_EL_CC_HI_VOLTAGE_1"]) + self.__cc_low_voltage_2 = Param(json_obj["TSI_USBC_EL_CC_LOW_VOLTAGE_2"]) + self.__cc_high_voltage_2 = Param(json_obj["TSI_USBC_EL_CC_HI_VOLTAGE_2"]) + self.__cc_low_voltage_3 = Param(json_obj["TSI_USBC_EL_CC_LOW_VOLTAGE_3"]) + self.__cc_high_voltage_3 = Param(json_obj["TSI_USBC_EL_CC_HI_VOLTAGE_3"]) + self.__vconn_low_voltage = Param(json_obj["TSI_USBC_EL_VCON_LOW_VOLTAGE"]) + self.__vconn_high_voltage = Param(json_obj["TSI_USBC_EL_VCON_HI_VOLTAGE"]) + self.__dp_alt_mode_timeout = Param(json_obj["TSI_USBC_EL_DP_ALT_TIMEOUT"]) + self.__aux_positive_idle_low_voltage = Param(json_obj["TSI_USBC_EL_AUX_P_IDLE_LOW_VOLTAGE"]) + self.__aux_positive_idle_high_voltage = Param(json_obj["TSI_USBC_EL_AUX_P_IDLE_HI_VOLTAGE"]) + self.__aux_negative_idle_low_voltage = Param(json_obj["TSI_USBC_EL_AUX_N_IDLE_LOW_VOLTAGE"]) + self.__aux_negative_idle_high_voltage = Param(json_obj["TSI_USBC_EL_AUX_N_IDLE_HI_VOLTAGE"]) + self.__vbus_low_voltage = Param(json_obj["TSI_USBC_EL_VBUS_LOW_VOLTAGE"]) + self.__vbus_high_voltage = Param(json_obj["TSI_USBC_EL_VBUS_HI_VOLTAGE"]) + self.__vbus_current_max = Param(json_obj["TSI_USBC_EL_VBUS_CURRENT_MAX_DEV"]) + self.__gnd_current_max = Param(json_obj["TSI_USBC_EL_GND_CURRENT_MAX_DEV"]) + self.__power_measure_delay = Param(json_obj["TSI_USBC_EL_PWR_MEASURE_DELAY"]) + self.__min_dut_current = Param(json_obj["TSI_USBC_EL_MIN_DUT_CURRENT"]) + self.__resistor_on_delay = Param(json_obj["TSI_USBC_EL_RES_ON_DELAY"]) + self.__cc_measure_delay = Param(json_obj["TSI_USBC_EL_CC_MEASURE_DELAY"]) + + @property + def timeout(self) -> int: + """ + Set and get test timeout, in milliseconds. + + Returns: + object of int type + """ + return self.__timeout.default_value + + @timeout.setter + def timeout(self, timeout: int): + if not(self.__timeout.min_value < timeout < self.__timeout.max_value): + raise ValueError(f"Timeout cannot be less than {self.__timeout.min_value} and more than " + f"{self.__timeout.max_value}.") + self.__timeout.default_value = timeout + + @property + def dut_caps(self) -> UsbcElTestDutCaps: + """ + Set and get USB-C DUT caps. + + Returns: + object of `UsbcElTestDutCaps` type + """ + return self.__dut_caps + + @dut_caps.setter + def dut_caps(self, dut_caps: UsbcElTestDutCaps): + self.__dut_caps = dut_caps + + @property + def pwr_contract_timeout(self) -> int: + """ + Set and get power contract timeout. + + Returns: + object of int type + """ + return self.__pwr_contract_timeout.default_value + + @pwr_contract_timeout.setter + def pwr_contract_timeout(self, pwr_contract_timeout: int): + if not(self.__pwr_contract_timeout.min_value < pwr_contract_timeout < self.__pwr_contract_timeout.max_value): + raise ValueError(f"Power contract timeout cannot be less than {self.__pwr_contract_timeout.min_value} " + f"and more than {self.__pwr_contract_timeout.max_value}") + self.__pwr_contract_timeout.default_value = pwr_contract_timeout + + @property + def cc_low_voltage_1(self) -> int: + """ + Set and get low limit for the voltage window when power sink current is 0.5A or 0.9A. + + Returns: + object of int type + """ + return self.__cc_low_voltage_1.default_value + + @cc_low_voltage_1.setter + def cc_low_voltage_1(self, cc_low_voltage_1: int): + if not(self.__cc_low_voltage_1.min_value < cc_low_voltage_1 < self.__cc_low_voltage_1.max_value): + raise ValueError(f"First CC low voltage cannot be less than {self.__cc_low_voltage_1.min_value} " + f"and more than {self.__cc_low_voltage_1.max_value}") + self.__cc_low_voltage_1.default_value = cc_low_voltage_1 + + @property + def cc_high_voltage_1(self) -> int: + """ + Set and get high limit for the voltage window when power sink current is 0.5A or 0.9A. + + Returns: + object of int type + """ + return self.__cc_high_voltage_1.default_value + + @cc_high_voltage_1.setter + def cc_high_voltage_1(self, cc_high_voltage_1: int): + if not(self.__cc_high_voltage_1.min_value < cc_high_voltage_1 < self.__cc_high_voltage_1.max_value): + raise ValueError(f"First CC high voltage cannot be less than {self.__cc_high_voltage_1.min_value} " + f"and more than {self.__cc_high_voltage_1.max_value}") + self.__cc_high_voltage_1.default_value = cc_high_voltage_1 + + @property + def cc_low_voltage_2(self) -> int: + """ + Set and get low limit for the voltage window when power sink current is 1.5A. + + Returns: + object of int type + """ + return self.__cc_low_voltage_2.default_value + + @cc_low_voltage_2.setter + def cc_low_voltage_2(self, cc_low_voltage_2: int): + if not(self.__cc_low_voltage_2.min_value < cc_low_voltage_2 < self.__cc_low_voltage_2.max_value): + raise ValueError(f"Second CC low voltage cannot be less than {self.__cc_low_voltage_2.min_value} " + f"and more than {self.__cc_low_voltage_2.max_value}") + self.__cc_low_voltage_2.default_value = cc_low_voltage_2 + + @property + def cc_high_voltage_2(self) -> int: + """ + Set and get high limit for the voltage window when power sink current is 1.5A. + + Returns: + object of int type + """ + return self.__cc_high_voltage_2.default_value + + @cc_high_voltage_2.setter + def cc_high_voltage_2(self, cc_high_voltage_2: int): + if not(self.__cc_high_voltage_2.min_value < cc_high_voltage_2 < self.__cc_high_voltage_2.max_value): + raise ValueError(f"Second CC high voltage cannot be less than {self.__cc_high_voltage_2.min_value} " + f"and more than {self.__cc_high_voltage_2.max_value}") + self.__cc_high_voltage_2.default_value = cc_high_voltage_2 + + @property + def cc_low_voltage_3(self) -> int: + """ + Set and get low limit for the voltage window when power sink current is 3.0A. + + Returns: + object of int type + """ + return self.__cc_low_voltage_3.default_value + + @cc_low_voltage_3.setter + def cc_low_voltage_3(self, cc_low_voltage_3: int): + if not(self.__cc_low_voltage_3.min_value < cc_low_voltage_3 < self.__cc_low_voltage_3.max_value): + raise ValueError(f"Third CC low voltage cannot be less than {self.__cc_low_voltage_3.min_value} " + f"and more than {self.__cc_low_voltage_3.max_value}") + self.__cc_low_voltage_3.default_value = cc_low_voltage_3 + + @property + def cc_high_voltage_3(self): + """ + Set and get high limit for the voltage window when power sink current is 3.0A. + + Returns: + object of int type + """ + return self.__cc_high_voltage_3.default_value + + @cc_high_voltage_3.setter + def cc_high_voltage_3(self, cc_high_voltage_3: int): + if not(self.__cc_high_voltage_3.min_value < cc_high_voltage_3 < self.__cc_high_voltage_3.max_value): + raise ValueError(f"Third CC high voltage cannot be less than {self.__cc_high_voltage_3.min_value} " + f"and more than {self.__cc_high_voltage_3.max_value}") + self.__cc_high_voltage_3.default_value = cc_high_voltage_3 + + @property + def vconn_low_voltage(self) -> int: + """ + Set and get low limit for the Vcon voltage window. + + Returns: + object of int type + """ + return self.__vconn_low_voltage.default_value + + @vconn_low_voltage.setter + def vconn_low_voltage(self, vconn_low_voltage: int): + if not(self.__vconn_low_voltage.min_value < vconn_low_voltage < self.__vconn_low_voltage.max_value): + raise ValueError(f"VCONN low voltage cannot be less than {self.__vconn_low_voltage.min_value} " + f"and more than {self.__vconn_low_voltage.max_value}") + self.__vconn_low_voltage.default_value = vconn_low_voltage + + @property + def vconn_high_voltage(self) -> int: + """ + Set and get high limit for the Vcon voltage window. + + Returns: + object of int type + """ + return self.__vconn_high_voltage.default_value + + @vconn_high_voltage.setter + def vconn_high_voltage(self, vconn_high_voltage: int): + if not(self.__vconn_high_voltage.min_value < vconn_high_voltage < self.__vconn_high_voltage.max_value): + raise ValueError(f"VCONN high voltage cannot be less than {self.__vconn_high_voltage.min_value} " + f"and more than {self.__vconn_high_voltage.max_value}") + self.__vconn_high_voltage.default_value = vconn_high_voltage + + @property + def dp_alt_mode_timeout(self) -> int: + """ + Set and get DP alt mode timeout. + + Returns: + object of int type + """ + return self.__dp_alt_mode_timeout.default_value + + @dp_alt_mode_timeout.setter + def dp_alt_mode_timeout(self, dp_alt_mode_timeout: int): + if not(self.__dp_alt_mode_timeout.min_value < dp_alt_mode_timeout < self.__dp_alt_mode_timeout.max_value): + raise ValueError(f"DP Alt mode timeout cannot be less than {self.__dp_alt_mode_timeout.min_value} " + f"and more than {self.__dp_alt_mode_timeout.max_value}") + self.__dp_alt_mode_timeout.default_value = dp_alt_mode_timeout + + @property + def aux_positive_idle_low_voltage(self) -> int: + """ + Set and get low voltage limit for the positive DP AUX. + + Returns: + object of int type + """ + return self.__aux_positive_idle_low_voltage.default_value + + @aux_positive_idle_low_voltage.setter + def aux_positive_idle_low_voltage(self, aux_positive_idle_low_voltage: int): + if not(self.__aux_positive_idle_low_voltage.min_value < aux_positive_idle_low_voltage < + self.__aux_positive_idle_low_voltage.max_value): + raise ValueError(f"AUX positive IDLE low voltage cannot be less than " + f"{self.__aux_positive_idle_low_voltage.min_value} and more than " + f"{self.__aux_positive_idle_low_voltage.max_value}") + self.__aux_positive_idle_low_voltage.default_value = aux_positive_idle_low_voltage + + @property + def aux_positive_idle_high_voltage(self) -> int: + """ + Set and get high voltage limit for the positive DP AUX. + + Returns: + object of int type + """ + return self.__aux_positive_idle_high_voltage.default_value + + @aux_positive_idle_high_voltage.setter + def aux_positive_idle_high_voltage(self, aux_positive_idle_high_voltage: int): + if not(self.__aux_positive_idle_high_voltage.min_value < aux_positive_idle_high_voltage < + self.__aux_positive_idle_high_voltage.max_value): + raise ValueError(f"AUX positive IDLE high voltage cannot be less than " + f"{self.__aux_positive_idle_high_voltage.min_value} and more than " + f"{self.__aux_positive_idle_high_voltage.max_value}") + self.__aux_positive_idle_high_voltage.default_value = aux_positive_idle_high_voltage + + @property + def aux_negative_idle_low_voltage(self) -> int: + """ + Set and get low voltage limit for the negative DP AUX. + + Returns: + object of int type + """ + return self.__aux_negative_idle_low_voltage.default_value + + @aux_negative_idle_low_voltage.setter + def aux_negative_idle_low_voltage(self, aux_negative_idle_low_voltage: int): + if not(self.__aux_negative_idle_low_voltage.min_value < aux_negative_idle_low_voltage < + self.__aux_negative_idle_low_voltage.max_value): + raise ValueError(f"AUX negative IDLE low voltage cannot be less than " + f"{self.__aux_negative_idle_low_voltage.min_value} and more than " + f"{self.__aux_negative_idle_low_voltage.max_value}") + self.__aux_negative_idle_low_voltage.default_value = aux_negative_idle_low_voltage + + @property + def aux_negative_idle_high_voltage(self) -> int: + """ + Set and get high voltage limit for the negative DP AUX. + + Returns: + object of int type + """ + return self.__aux_negative_idle_high_voltage.default_value + + @aux_negative_idle_high_voltage.setter + def aux_negative_idle_high_voltage(self, aux_negative_idle_high_voltage: int): + if not(self.__aux_negative_idle_high_voltage.min_value < aux_negative_idle_high_voltage < + self.__aux_negative_idle_high_voltage.max_value): + raise ValueError(f"AUX negative IDLE high voltage cannot be less than " + f"{self.__aux_negative_idle_high_voltage.min_value} and more than " + f"{self.__aux_negative_idle_high_voltage.max_value}") + self.__aux_negative_idle_high_voltage.default_value = aux_negative_idle_high_voltage + + @property + def vbus_low_voltage(self) -> int: + """ + Set and get low limit for Vbus voltage window. + + Returns: + object of int type + """ + return self.__vbus_low_voltage.default_value + + @vbus_low_voltage.setter + def vbus_low_voltage(self, vbus_low_voltage: int): + if not(self.__vbus_low_voltage.min_value < vbus_low_voltage < self.__vbus_low_voltage.max_value): + raise ValueError(f"VBUS low voltage cannot be less than {self.__vbus_low_voltage.min_value} " + f"and more than {self.__vbus_low_voltage.max_value}") + self.__vbus_low_voltage.default_value = vbus_low_voltage + + @property + def vbus_high_voltage(self) -> int: + """ + Set and get high limit for Vbus voltage window. + + Returns: + object of int type + """ + return self.__vbus_high_voltage.default_value + + @vbus_high_voltage.setter + def vbus_high_voltage(self, vbus_high_voltage: int): + if not(self.__vbus_high_voltage.min_value < vbus_high_voltage < self.__vbus_high_voltage.max_value): + raise ValueError(f"VBUS high voltage cannot be less than {self.__vbus_high_voltage.min_value} " + f"and more than {self.__vbus_high_voltage.max_value}") + self.__vbus_high_voltage.default_value = vbus_high_voltage + + @property + def vbus_current_max(self) -> int: + """ + Set and get the highest allowed deviation of current VBUS. + + Returns: + object of int type + """ + return self.__vbus_current_max.default_value + + @vbus_current_max.setter + def vbus_current_max(self, vbus_current_max: int): + if not(self.__vbus_current_max.min_value < vbus_current_max < self.__vbus_current_max.max_value): + raise ValueError(f"VBUS current max cannot be less than {self.__vbus_current_max.min_value} " + f"and more than {self.__vbus_current_max.max_value}") + self.__vbus_current_max.default_value = vbus_current_max + + @property + def gnd_current_max(self) -> int: + """ + Set and get the highest allowed deviation of current GND. + + Returns: + object of int type + """ + return self.__gnd_current_max.default_value + + @gnd_current_max.setter + def gnd_current_max(self, gnd_current_max: int): + if not(self.__gnd_current_max.min_value < gnd_current_max < self.__gnd_current_max.max_value): + raise ValueError(f"GND current max cannot be less than {self.__gnd_current_max.min_value} " + f"and more than {self.__gnd_current_max.max_value}") + self.__gnd_current_max.default_value = gnd_current_max + + @property + def power_measure_delay(self) -> int: + """ + Set and get delay from end of power contract negotiation to voltage / current measurements. + + Returns: + object of int type + """ + return self.__power_measure_delay.default_value + + @power_measure_delay.setter + def power_measure_delay(self, power_measure_delay: int): + if not(self.__power_measure_delay.min_value < power_measure_delay < self.__power_measure_delay.max_value): + raise ValueError(f"Power measure delay cannot be less than {self.__power_measure_delay.min_value} " + f"and more than {self.__power_measure_delay.max_value}") + self.__power_measure_delay.default_value = power_measure_delay + + @property + def min_dut_current(self) -> int: + """ + Set and get the minimum current. + + Returns: + object of int type + """ + return self.__min_dut_current.default_value + + @min_dut_current.setter + def min_dut_current(self, min_dut_current: int): + if not(self.__min_dut_current.min_value < min_dut_current < self.__min_dut_current.max_value): + raise ValueError(f"Minimum current on DUT cannot be less than {self.__min_dut_current.min_value} " + f"and more than {self.__min_dut_current.max_value}") + self.__min_dut_current.default_value = min_dut_current + + @property + def resistor_on_delay(self) -> int: + """ + Set and get delay after load resistor. + + Returns: + object of int type + """ + return self.__resistor_on_delay.default_value + + @resistor_on_delay.setter + def resistor_on_delay(self, resistor_on_delay: int): + if not(self.__resistor_on_delay.min_value < resistor_on_delay < self.__resistor_on_delay.max_value): + raise ValueError(f"Resistor delay cannot be less than {self.__resistor_on_delay.min_value} " + f"and more than {self.__resistor_on_delay.max_value}") + self.__resistor_on_delay.default_value = resistor_on_delay + + @property + def cc_measure_delay(self) -> int: + """ + Set and get delay for measure CC lines voltage after DUT plug detection. + + Returns: + object of int type + """ + return self.__cc_measure_delay.default_value + + @cc_measure_delay.setter + def cc_measure_delay(self, cc_measure_delay: int): + if not(self.__cc_measure_delay.min_value < cc_measure_delay < self.__cc_measure_delay.max_value): + raise ValueError(f"CC measure delay cannot be less than {self.__cc_measure_delay.min_value} " + f"and more than {self.__cc_measure_delay.max_value}") + self.__cc_measure_delay.default_value = cc_measure_delay diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/vrr_tests.py b/UniTAP/dev/modules/dut_tests/dut_default_params/vrr_tests.py new file mode 100644 index 0000000..0923bb0 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/vrr_tests.py @@ -0,0 +1,303 @@ +from UniTAP.dev.modules.dut_tests.test_group_params_types import Param +from UniTAP.dev.modules.dut_tests.test_utils import update_default_value + + +class VrrCaps(Param): + """ + Class `VrrCaps` describes DUT VRR capabilities flags and allows settings values. + - VRR enable/disable `vrr_enable`. + - Defines M_CONST `m_const`. + """ + def __init__(self, json_obj): + super().__init__(json_obj) + + @property + def vrr_enable(self): + """ + Set and get VRR enable/disable flag. + + Returns: + object bool + """ + return self._get_by_bitmask(0, bool) + + @vrr_enable.setter + def vrr_enable(self, vrr_enable: bool): + self._set_by_bitmask(vrr_enable, 0) + + @property + def m_const(self): + """ + Set and get m const enable/disable flag. + + Returns: + object bool + """ + return self._get_by_bitmask(1, bool) + + @m_const.setter + def m_const(self, m_const: bool): + self._set_by_bitmask(m_const, 1) + + +class BaseFront(Param): + """ + Class `BaseFront` describes DUT BaseFront capabilities flags and allows settings values. + - Base front value `vrr_enable`. + - Reduced blanking enable/disable `m_const`. + """ + def __init__(self, json_obj): + super().__init__(json_obj) + + @property + def base_front(self): + """ + Set and get va;ue of base front. + + Returns: + object int + """ + return self._get_by_bitmask(0, int) + + @base_front.setter + def base_front(self, base_front: int): + if not (0 <= base_front <= 3): + raise ValueError(f"Base front value cannot be less than 0 and more than 3") + self._set_by_bitmask(base_front, 0) + + @property + def reduced_blanking(self): + """ + Set and get reduced blanking flag support. + + Returns: + object bool + """ + return self._get_by_bitmask(1, bool) + + @reduced_blanking.setter + def reduced_blanking(self, reduced_blanking: bool): + self._set_by_bitmask(reduced_blanking, 1) + + +class VrrTestParamBase: + """ + Class `VrrTestParam` describes parameters for VRR Sink and Source tests. + - Test timeout, in milliseconds `timeout`. + - Defines VRR Max value `vrr_max`. + - Defines VRR Min value `vrr_min`. + - Defines VRR Static value `vrr_static`. + - Defines step of changing frame rate `vrr_step`. + - Defines timer to change frame rate `vrr_time_step`. + - Defines VRR capabilities `vrr_enable_cups` type `VrrCaps`. + - Defines VRR Base front `base_vfront` type `BaseFront`. + - Defines Base Refresh Rate `base_rate`. + """ + def __init__(self, timeout, vrr_max, vrr_min, vrr_static, vrr_step, vrr_time_step, + vrr_enable_cups, base_vfront, base_rate): + self.__timeout = timeout + self.__vrr_max = vrr_max + self.__vrr_min = vrr_min + self.__vrr_static = vrr_static + self.__vrr_step = vrr_step + self.__vrr_time_step = vrr_time_step + self.__vrr_enable_cups = vrr_enable_cups + self.__base_vfront = base_vfront + self.__base_rate = base_rate + + @property + def timeout(self) -> int: + """ + Set and get test timeout, in milliseconds. + + Returns: + object of int type + """ + return self.__timeout.default_value + + @timeout.setter + def timeout(self, timeout: int): + if not(self.__timeout.min_value < timeout < self.__timeout.max_value): + raise ValueError(f"Timeout cannot be less than {self.__timeout.min_value} and more than " + f"{self.__timeout.max_value}.") + self.__timeout.default_value = timeout + + @property + def vrr_max(self) -> int: + """ + Set and get VRR max. + + Returns: + object of int type + """ + return self.__vrr_max.default_value + + @vrr_max.setter + def vrr_max(self, vrr_max: int): + if not(self.__vrr_max.min_value < vrr_max < self.__vrr_max.max_value): + raise ValueError(f"VRR max cannot be less than {self.__vrr_max.min_value} and more than " + f"{self.__vrr_max.max_value}.") + self.__vrr_max.default_value = vrr_max + + @property + def vrr_min(self) -> int: + """ + Set and get VRR min. + + Returns: + object of int type + """ + return self.__vrr_min.default_value + + @vrr_min.setter + def vrr_min(self, vrr_min: int): + if not(self.__vrr_min.min_value < vrr_min < self.__vrr_min.max_value): + raise ValueError(f"VRR min cannot be less than {self.__vrr_min.min_value} and more than " + f"{self.__vrr_min.max_value}.") + self.__vrr_min.default_value = vrr_min + + @property + def vrr_static(self) -> int: + """ + Set and get VRR static. + + Returns: + object of int type + """ + return self.__vrr_static.default_value + + @vrr_static.setter + def vrr_static(self, vrr_static: int): + if not(self.__vrr_static.min_value < vrr_static < self.__vrr_static.max_value): + raise ValueError(f"VRR stasic cannot be less than {self.__vrr_static.min_value} and more than " + f"{self.__vrr_static.max_value}.") + self.__vrr_static.default_value = vrr_static + + @property + def vrr_step(self) -> int: + """ + Set and get VRR step. + + Returns: + object of int type + """ + return self.__vrr_step.default_value + + @vrr_step.setter + def vrr_step(self, vrr_step: int): + if not(self.__vrr_step.min_value < vrr_step < self.__vrr_step.max_value): + raise ValueError(f"VRR step cannot be less than {self.__vrr_step.min_value} and more than " + f"{self.__vrr_step.max_value}.") + self.__vrr_step.default_value = vrr_step + + @property + def vrr_time_step(self) -> int: + """ + Set and get VRR time step. + + Returns: + object of int type + """ + return self.__vrr_time_step.default_value + + @vrr_time_step.setter + def vrr_time_step(self, vrr_time_step: int): + if not(self.__vrr_time_step.min_value < vrr_time_step < self.__vrr_time_step.max_value): + raise ValueError(f"VRR time step cannot be less than {self.__vrr_time_step.min_value} and more than " + f"{self.__vrr_time_step.max_value}.") + self.__vrr_time_step.default_value = vrr_time_step + + @property + def vrr_enable_cups(self): + """ + Set and get VRR caps. + + Returns: + object of `VrrCaps` type + """ + return self.__vrr_enable_cups + + @vrr_enable_cups.setter + def vrr_enable_cups(self, vrr_enable_cups: VrrCaps): + self.__vrr_enable_cups = vrr_enable_cups + + @property + def base_vfront(self): + """ + Set and get base vfront value. + + Returns: + object of BaseFront type + """ + return self.__base_vfront + + @base_vfront.setter + def base_vfront(self, base_vfront: BaseFront): + self.__base_vfront = base_vfront + + @property + def base_rate(self) -> int: + """ + Set and get base rate. + + Returns: + object of int type + """ + return self.__base_rate.default_value + + @base_rate.setter + def base_rate(self, base_rate: int): + if not(self.__base_rate.min_value < base_rate < self.__base_rate.max_value): + raise ValueError(f"VRR time step cannot be less than {self.__vrr_time_step.min_value} and more than " + f"{self.__base_rate.max_value}.") + self.__base_rate.default_value = base_rate + + +class VrrSinkDUTTestParam(VrrTestParamBase): + """ + Class `VrrSinkDUTTestParam` describes parameters for VRR Sink and Source tests. + - Test timeout, in milliseconds `timeout`. + - Defines VRR Max value `vrr_max`. + - Defines VRR Min value `vrr_min`. + - Defines VRR Static value `vrr_static`. + - Defines step of changing frame rate `vrr_step`. + - Defines timer to change frame rate `vrr_time_step`. + - Defines VRR capabilities `vrr_enable_cups` type `VrrCaps`. + - Defines VRR Base front `base_vfront` type `BaseFront`. + - Defines Base Refresh Rate `base_rate`. + """ + def __init__(self, json_obj): + super().__init__(Param(json_obj["TSI_VRR_SINK_DUT_TIMEOUT"]), + Param(json_obj["TSI_VRR_SINK_DUT_VRR_MAX"]), + Param(json_obj["TSI_VRR_SINK_DUT_VRR_MIN"]), + Param(json_obj["TSI_VRR_SINK_DUT_VRR_STATIC"]), + Param(json_obj["TSI_VRR_SINK_DUT_VRR_STEP"]), + Param(json_obj["TSI_VRR_SINK_DUT_VRR_TIME_STEP"]), + VrrCaps(json_obj["TSI_VRR_SINK_DUT_VRR_ENABLE"]), + BaseFront(json_obj["TSI_VRR_SINK_DUT_BASE_VFRONT"]), + Param(json_obj["TSI_VRR_SINK_DUT_BASE_RATE"])) + + +class VrrSourceDUTTestParam(VrrTestParamBase): + """ + Class `VrrSourceDUTTestParam` describes parameters for VRR Sink and Source tests. + - Test timeout, in milliseconds `timeout`. + - Defines VRR Max value `vrr_max`. + - Defines VRR Min value `vrr_min`. + - Defines VRR Static value `vrr_static`. + - Defines step of changing frame rate `vrr_step`. + - Defines timer to change frame rate `vrr_time_step`. + - Defines VRR capabilities `vrr_enable_cups` type `VrrCaps`. + - Defines VRR Base front `base_vfront` type `BaseFront`. + - Defines Base Refresh Rate `base_rate`. + """ + def __init__(self, json_obj): + super().__init__(Param(json_obj["TSI_VRR_SRC_DUT_TIMEOUT"]), + Param(json_obj["TSI_VRR_SRC_DUT_VRR_MAX"]), + Param(json_obj["TSI_VRR_SRC_DUT_VRR_MIN"]), + Param(json_obj["TSI_VRR_SRC_DUT_VRR_STATIC"]), + Param(json_obj["TSI_VRR_SRC_DUT_VRR_STEP"]), + Param(json_obj["TSI_VRR_SRC_DUT_VRR_TIME_STEP"]), + VrrCaps(json_obj["TSI_VRR_SRC_DUT_VRR_ENABLE"]), + BaseFront(json_obj["TSI_VRR_SRC_DUT_BASE_VFRONT"]), + Param(json_obj["TSI_VRR_SRC_DUT_BASE_RATE"])) diff --git a/UniTAP/dev/modules/dut_tests/dut_tests.py b/UniTAP/dev/modules/dut_tests/dut_tests.py new file mode 100644 index 0000000..5ddd551 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_tests.py @@ -0,0 +1,572 @@ +import concurrent.futures +import copy +import os +import json +import re +import time + +from typing import TypeVar, Generic, Optional, Type, Tuple, List + +from UniTAP.utils import tsi_logging as logging + +from UniTAP.dev.modules.dut_tests.test_info import TestResultObject, TestGroupId +from UniTAP.dev.modules.dut_tests.private_info import MergedTestGroups, group_params_dict, SubTestResultObject, \ + MERGED_GROUP_PARAMS_TYPE, test_group_to_merged_group +from .private_info import TestResultObject, TestGroupId, MergedTestGroups, group_params_dict, SubTestResultObject, \ + MERGED_GROUP_PARAMS_TYPE, test_group_to_merged_group +from UniTAP.dev.modules.dut_tests.test_group_params_types import dict_types, read_image_file, uicl_image_preparation +from UniTAP.libs.lib_tsi.tsi_io import DeviceIO, TestDialogConfig +from UniTAP.version import __version__ +from .dut_default_params import * +from .test_utils import is_need_to_update_test_configuration, TestReportConfigType, convert_to_cdf_json_base64 +from .report import update_configuration, dp21_source_timing_table, detect_warning_message +from .report import generate_html_report, TestAdditionalInfo, TestResult +from UniTAP.utils import tsi_logging as logging +from .test_info import TestStatusEnum +from .parser import Parser + + +class Test: + """ + Class `TestGroup` describes template of usual test. + Contains info of: + - Test name `name`. + - Test ID `id` + """ + + def __init__(self, name: str, test_id: int): + self.__name = name + self.__id = test_id + + @property + def name(self): + return self.__name + + @property + def id(self): + return self.__id + + def __str__(self): + return f"Test ID: {self.id} - {self.name}\n" + + +class TestGroup(Generic[DUTTestParameters]): + """ + Class `TestGroup` describes template of usual test group. + Contains info of: + - Test count `test_count`. + - Tests `tests`. + - Name `name`. + - ID `id`. + """ + + def __init__(self, _id: TestGroupId, name: str): + self.__name = name + self.__id = _id + self.__tests = list() + + @property + def test_count(self) -> int: + """ + Test count of test group. + + Returns: + object of int type + """ + return len(self.__tests) + + @property + def tests(self): + """ + Returns tests. + + Returns: + object of `` type + """ + return self.__tests + + @property + def name(self) -> str: + """ + Returns test group name. + + Returns: + object of str type + """ + return self.__name + + @property + def id(self) -> TestGroupId: + """ + Returns ID of test group. + + Returns: + object of id type + """ + return self.__id + + def _add_new_test(self, new_test: Test): + if not self.__test_presence_check(new_test): + self.__tests.append(new_test) + + def __test_presence_check(self, test_to_check: Test) -> bool: + for test in self.__tests: + if test.id == test_to_check.id: + return True + return False + + def __search_test_by_id(self, test_id: int) -> Optional[Test]: + for test in self.__tests: + if test.id == test_id: + return test + return None + + def __str__(self): + tests_info = "" + for test in self.tests: + tests_info += test.__str__() + return f"Test group name: {self.name}. ID Group: {self.id}\n" \ + f"Number of tests in a group: {self.test_count}\n" \ + f"{tests_info}" + + +class DUTTests: + """ + Class `DUTTests` allows working with available tests on the device. + - Run `run`. + - Get default parameters for selected test group `get_default_parameters`. + - Get available test count in selected group `number_tests_in_group`. + - Get all test results `get_all_tests_results`. + - Clear all test results `clear_results`. + - Get information of available test groups `info_of_available_test_groups`. + - Make report after testing `make_report`. + - Run test from file `run_from_file` - Not implemented. Will be added later. + """ + + def __init__(self, dev_io: DeviceIO): + self.__io = dev_io + self.__opf_device = None + self.__results = [] + self.__default_parameters = {} + self.__test_groups = {} + self.__parser = Parser() + + self.__io.set_test_config(0) + cfp_path = os.path.join(os.path.dirname(__file__), 'cfg') + files = os.listdir(cfp_path) + for file in files: + try: + if not os.path.isfile(os.path.join(cfp_path, file)): + continue + + with open(os.path.join(cfp_path, file), encoding='UTF-8') as cfg_json: + json_obj = json.load(cfg_json) + item_group = MergedTestGroups(int(json_obj.get('id'), 16)) + group_type = MERGED_GROUP_PARAMS_TYPE.get(item_group, None) + + if group_type is None: + continue + + group_parameters_dict = {} + for parameter in json_obj.get('descriptions'): + group_parameters_dict[parameter["id"]] = parameter + + self.__default_parameters[group_type] = group_type(group_parameters_dict) + except ValueError: + logging.info( + f"[UniTAP] Failed to load {os.path.join(cfp_path, file)}.\n Value {int(json_obj.get('id'), 16)} is missing in MergedTestGroups.") + + test_list = self.__io.get_test_list() + + for test in test_list: + test_id = test[0] & 0xFFFF + group_id = TestGroupId(test[0] >> 16) + name = test[2] + + if test[2].find(' / ') != -1: + name = test[2].split(" / ") + else: + name = [name] + if test[2].find("Validate audio") != -1: + name.insert(0, "Audio Test") + else: + name.insert(0, "Pixel Level Video Test") + group_id = TestGroupId.PIXEL_VIDEO_TEST + + group_name = name[0] + test_name = name[1] + + if self.__test_groups.get(group_id, None) is None: + self.__test_groups[group_id] = TestGroup(_id=group_id, name=group_name) + + self.__test_groups[group_id]._add_new_test(Test(name=test_name, test_id=test_id)) + + def run(self, group_id: TestGroupId, test_id: int, params: Optional[DUTTestParameters] = None, + print_fw_logs: bool = True, test_delay: int = 0) -> SubTestResultObject: + """ + Run selected test of test group. + + Args: + group_id (TestGroupId) - test group id + test_id (int) + params (DUTTestParameters) - one of the variants of params + print_fw_logs (bool) - print FW logs (enable/disable) + test_delay (int) - delay between tests (in seconds) + """ + if self.__test_groups.get(group_id, None) is None: + raise ValueError(f"Group {group_id} is not available.") + + if group_id in [TestGroupId.AUDIO_TEST, TestGroupId.PIXEL_VIDEO_TEST]: + if group_id == TestGroupId.AUDIO_TEST and test_id != 3 or \ + group_id == TestGroupId.PIXEL_VIDEO_TEST and test_id != 2: + raise ValueError(f"Test with id {test_id} is not available in {self.__test_groups.get(group_id).name}.") + group = self.__test_groups.get(group_id, None) + test_name = self.__test_groups.get(group_id).tests[0].name + group_id = TestGroupId.AUDIO_TEST + else: + if test_id > self.__test_groups.get(group_id).test_count: + raise ValueError(f"Test with id {test_id} is not available in {self.__test_groups.get(group_id).name}.") + + group = self.__test_groups.get(group_id, None) + test_name = self.__test_groups.get(group_id).tests[test_id].name + + merged_group_id = test_group_to_merged_group(group_id, test_id) + + if params is not None: + params_list = get_param_list(params) + if isinstance(params, Dp21SourceDUTTestParam) and not params.dsc_video_modes._Dp21AvailableDscVideoModes__is_config_changed: + logging.warning("Detected that used didn't change new DSC config. Copy setting from video config.") + dsc_dp2_ci_remapper = { + "TSI_DP20_SRCCTS_DSC_VMT_1920_1080_30": "TSI_DP20_SRCCTS_VMT_1920_1080_30", + "TSI_DP20_SRCCTS_DSC_VMT_1920_1080_60": "TSI_DP20_SRCCTS_VMT_1920_1080_60", + "TSI_DP20_SRCCTS_DSC_VMT_1920_1080_120": "TSI_DP20_SRCCTS_VMT_1920_1080_120", + "TSI_DP20_SRCCTS_DSC_VMT_1920_1080_144": "TSI_DP20_SRCCTS_VMT_1920_1080_144", + "TSI_DP20_SRCCTS_DSC_VMT_1920_1080_240": "TSI_DP20_SRCCTS_VMT_1920_1080_240", + "TSI_DP20_SRCCTS_DSC_VMT_3840_2160_30": "TSI_DP20_SRCCTS_VMT_3840_2160_30", + "TSI_DP20_SRCCTS_DSC_VMT_3840_2160_60": "TSI_DP20_SRCCTS_VMT_3840_2160_60", + "TSI_DP20_SRCCTS_DSC_VMT_3840_2160_120": "TSI_DP20_SRCCTS_VMT_3840_2160_120", + "TSI_DP20_SRCCTS_DSC_VMT_3840_2160_144": "TSI_DP20_SRCCTS_VMT_3840_2160_144", + "TSI_DP20_SRCCTS_DSC_VMT_3840_2160_240": "TSI_DP20_SRCCTS_VMT_3840_2160_240", + "TSI_DP20_SRCCTS_DSC_VMT_5120_2160_30": "TSI_DP20_SRCCTS_VMT_5120_2160_30", + "TSI_DP20_SRCCTS_DSC_VMT_5120_2160_60": "TSI_DP20_SRCCTS_VMT_5120_2160_60", + "TSI_DP20_SRCCTS_DSC_VMT_5120_2160_120": "TSI_DP20_SRCCTS_VMT_5120_2160_120", + "TSI_DP20_SRCCTS_DSC_VMT_5120_2160_144": "TSI_DP20_SRCCTS_VMT_5120_2160_144", + "TSI_DP20_SRCCTS_DSC_VMT_5120_2160_240": "TSI_DP20_SRCCTS_VMT_5120_2160_240", + "TSI_DP20_SRCCTS_DSC_VMT_7680_4320_30": "TSI_DP20_SRCCTS_VMT_7680_4320_30", + "TSI_DP20_SRCCTS_DSC_VMT_7680_4320_60": "TSI_DP20_SRCCTS_VMT_7680_4320_60", + "TSI_DP20_SRCCTS_DSC_VMT_7680_4320_120": "TSI_DP20_SRCCTS_VMT_7680_4320_120", + "TSI_DP20_SRCCTS_DSC_VMT_10240_4320_30": "TSI_DP20_SRCCTS_VMT_10240_4320_30", + "TSI_DP20_SRCCTS_DSC_VMT_10240_4320_60": "TSI_DP20_SRCCTS_VMT_10240_4320_60" + } + + for key in dsc_dp2_ci_remapper.keys(): + dst = next((obj for obj in params_list if obj.config_id_name == key), None) + src = next((obj for obj in params_list if obj.config_id_name == dsc_dp2_ci_remapper[key]), None) + + if dst.default_value != src.default_value: + logging.warn(f"DSC Config missmatch detected. Apply workaround: {key}:{hex(dst.default_value)}; {dsc_dp2_ci_remapper[key]}:{hex(src.default_value)}") + dst.default_value = src.default_value + else: + logging.warn(f"DSC Config modification detected. Skip workaround.") + else: + params_list = self.__default_parameters[merged_group_id] + + for item in params_list: + if group_id == TestGroupId.AUDIO_TEST and item.config_id_name == "TSI_REF1_FRAME_DATA": + if isinstance(item.default_value, str) and os.path.exists(item.default_value): + image_data = read_image_file(item.default_value) + if len(image_data) > 0: + item.default_value = uicl_image_preparation(image_data, params) + elif isinstance(item, bytearray): + # TODO: add 'bytearray' support + item.default_value = uicl_image_preparation(item.default_value, params) + + self.__io.set(item.config_id, item.default_value, dict_types.get(item.value_type), + item.data_length) + + report_type = is_need_to_update_test_configuration(params_list) + result_obj = SubTestResultObject(group.name, detect_warning_message(report_type, params_list)) + result_obj.test_name = test_name + result_obj.json_config_info = convert_to_cdf_json_base64(params_list, merged_group_id) + + if report_type != TestReportConfigType.Another: + result_obj.config_info = update_configuration(report_type, params_list) + else: + for item in params_list: + result_obj.config_info += "{} = {}
".format(item.name, item.__str__()) + + run_id = (group_id << 16) | test_id + + self.__io.set_opf_config(TestDialogConfig(disable_auto_precessing=True)) + + with concurrent.futures.ThreadPoolExecutor() as executor: + future = executor.submit(self.__io.run_test, run_id) + + try: + state = True + while future.running() or state: + state = self.__read_logs(result_obj, print_fw_logs) + except KeyboardInterrupt as e: + self.__io.abort_test() + raise + + result_obj.test_result = TestResult(future.result()) + result_obj.error_code = self.__io.get_test_error_code() + result_obj.test_delay = test_delay + + self.__results.append(result_obj) + + time.sleep(test_delay) + + return copy.deepcopy(self.__results[-1]) + + def get_params_from_file(self, path: str) -> Tuple[TestGroupId, int, DUTTestParameters]: + """ + Get test parameters from transferred file: td or json (not cdf). + + Args: + path ('str') - full path to config file + """ + self.__parser.set_file_name(path) + group_id = self.__parser.detect_group() + test_id = self.__parser.detect_test() + group_type = group_params_dict.get(group_id) + params = self.__parser.parse(self.get_default_parameters(group_type)) + return group_id, test_id, params + + # TODO - add support parsing '.td' (other test groups) file extension + + def get_params_from_cdf_file(self, path: str) -> DUTTestParameters: + """ + Get test parameters from transferred file: json (cdf). + + Args: + path ('str') - full path to config file + """ + self.__parser.set_file_name(path) + group_id = self.__parser.detect_merged_group() + group_type = MERGED_GROUP_PARAMS_TYPE.get(group_id) + params = self.__parser.parse(self.get_default_parameters(group_type)) + return params + + def get_default_parameters(self, group_type: Type[DUTTestParameters]) -> DUTTestParameters: + """ + Get predefined (default) parameters of test group. + + Args: + group_type (`DUTTestParameters`) - test group id + + Returns: + object of `DUTTestParameters` type + """ + for group_id in self.__default_parameters.keys(): + if isinstance(self.__default_parameters.get(group_id), group_type): + return copy.deepcopy(self.__default_parameters.get(group_id)) + raise ValueError(f"Group {group_type} is not available.") + + def number_tests_in_group(self, group_id: TestGroupId) -> int: + """ + Returns all count of available tests of selected test group. + + Args: + group_id (`TestGroupId`) - test group id + + Returns: + object of int type + """ + return self.__search_group_by_id(group_id).test_count + + def get_test_name(self, group_id: TestGroupId, test_id: int) -> str: + """ + Returns test name by test id in selected test group. + + Args: + group_id (`TestGroupId`) - test group id + test_id (`int`) - test id + + Returns: + object of str type + """ + if group_id in [TestGroupId.AUDIO_TEST, TestGroupId.PIXEL_VIDEO_TEST]: + if group_id == TestGroupId.AUDIO_TEST and test_id != 3 or \ + group_id == TestGroupId.PIXEL_VIDEO_TEST and test_id != 2: + raise ValueError(f"Test with id {test_id} is not available in {self.__test_groups.get(group_id).name}.") + group_id = TestGroupId.AUDIO_TEST + return self.__test_groups.get(group_id).tests[0 if test_id == 3 else 1].name + else: + if test_id > self.__test_groups.get(group_id).test_count: + raise ValueError(f"Test with id {test_id} is not available in {self.__test_groups.get(group_id).name}.") + return self.__test_groups.get(group_id).tests[test_id].name + + def get_test_id_by_name(self, group_id: TestGroupId, name: str) -> int: + """ + Returns test ID by test name id in selected test group. + + Args: + group_id (`TestGroupId`) - test group id + name (`str`) - test name or part name of the test + + Returns: + object of int type + """ + + if group_id == TestGroupId.AUDIO_TEST: + return 3 + elif group_id == TestGroupId.PIXEL_VIDEO_TEST: + return 2 + else: + for test in self.__test_groups.get(group_id).tests: + if test.name.find(name) != -1: + return test.id + + raise ValueError(f"Incorrect test name: {name}") + + def get_test_id_list_by_name(self, group_id: TestGroupId, name: str) -> List[int]: + """ + Returns list of test ID by test name id in selected test group. + + Args: + group_id (`TestGroupId`) - test group id + name (`str`) - test name or part name of the test + + Returns: + object of list[int] type + """ + if group_id == TestGroupId.AUDIO_TEST: + return [3] + elif group_id == TestGroupId.PIXEL_VIDEO_TEST: + return [2] + else: + id_list = [] + for test in self.__test_groups.get(group_id).tests: + if test.name.find(name) != -1: + id_list.append(test.id) + + return id_list + + @staticmethod + def print_test_params(params: DUTTestParameters): + """ + Print all test parameters in readable format. + + Args: + params (`DUTTestParameters`) - type of params + """ + params_list = get_param_list(params) + report_type = is_need_to_update_test_configuration(params_list) + if report_type != TestReportConfigType.Another: + info = update_configuration(report_type, params_list).replace("
", '\n') + if report_type == TestReportConfigType.Dp2_1_Source: + info = info[: info.find(" TestResultObject: + """ + Returns all test result. Combined in one `TestResultObject` object. + + Returns: + object of TestResultObject type + """ + return TestResultObject(self.__results) + + def clear_results(self): + """ + Clear all results. + """ + self.__results.clear() + + @property + def info_of_available_test_groups(self) -> str: + """ + Returns all info in string format of test groups. + + Returns: + object of str type + """ + test_groups_info = "" + for group_key in self.__test_groups.keys(): + test_groups_info += self.__test_groups.get(group_key).__str__() + '\n' + return test_groups_info + + def __search_group_by_id(self, group_id: TestGroupId) -> TestGroup: + for group in self.__test_groups: + if group == group_id: + return self.__test_groups[group] + return TestGroup(TestGroupId.UNKNOWN, 'Unknown') + + def make_report(self, path: str = "Report.html", tested_by="", dut_driver_version="", dut_firmware_version="", + dut_model_name="", dut_revision="", dut_serial_number="", remarks="", results=None): + """ + Make report after testing. + + Args: + path (str) - path to save report + tested_by (str) - who tested + dut_driver_version (str) - DUT driver version + dut_firmware_version (str) - DUT FW version + dut_model_name (str) - DUT model name + dut_revision (str) - DUT revision + dut_serial_number (str) - DUT serial number + remarks (int) - additional remarks of testing + results (list|None) - results of testing (if list is empty or None, usually use internal list of test + results, which was filled during testing) + """ + test_additional_info = TestAdditionalInfo() + + device_name, serial_number, fw_version, front_end, memory_size = self.__io.get_prepared_fw_info().split("|") + + test_additional_info.device_name = f"{device_name} [{serial_number.replace(' ', '')}]: " \ + f"{self.__io.get_role().name}" + test_additional_info.device_detailed_data = fw_version + test_additional_info.device_frontend_version = front_end + test_additional_info.memory_size = memory_size + test_additional_info.python_version = __version__ + test_additional_info.bundle_version = self.__io.get_bundle_version() + + test_additional_info.tested_by = tested_by + test_additional_info.dut_driver_version = dut_driver_version + test_additional_info.dut_firmware_version = dut_firmware_version + test_additional_info.dut_model_name = dut_model_name + test_additional_info.dut_revision = dut_revision + test_additional_info.dut_serial_number = dut_serial_number + test_additional_info.remarks = remarks + + if results is None: + generate_html_report(path, test_additional_info, self.__results) + else: + generate_html_report(path, test_additional_info, results) + + def __read_logs(self, test_result: SubTestResultObject, print_logs: bool) -> bool: + try: + msg_count = self.__io.is_log_message_available(250) + except ReferenceError as e: + return False + + if msg_count <= 0: + return False + + for _ in range(msg_count): + result, message, length = self.__io.get_test_log_message() + if result < 0: + continue + if print_logs: + print(message) + + try: + test_result.fw_logs += message + "\n" + if str(message).find("Error:") != -1: + try: + new_str = message.split("Error:")[1] + error_code = int(re.findall(r'\d+', new_str)[0]) + test_result.error_logs = new_str.replace(f"{error_code}:", "") + except BaseException as e: + pass + except BaseException as e: + pass + + return True diff --git a/UniTAP/dev/modules/dut_tests/parser/__init__.py b/UniTAP/dev/modules/dut_tests/parser/__init__.py new file mode 100644 index 0000000..2a3855a --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/parser/__init__.py @@ -0,0 +1 @@ +from .parser import Parser diff --git a/UniTAP/dev/modules/dut_tests/parser/parser.py b/UniTAP/dev/modules/dut_tests/parser/parser.py new file mode 100644 index 0000000..9ed096e --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/parser/parser.py @@ -0,0 +1,315 @@ +import os +import json +import re +import warnings + +from UniTAP.utils import tsi_logging as logging +from ..private_info import MergedTestGroups, test_group_to_merged_group +from ..test_group_params_types import DataType +from ..test_info import TestGroupId +from ..dut_default_params import * +from ..dut_default_params.crc_video_tests import CrcVideoTestBpp, BrokenFrameExportFormat +from enum import IntEnum + + +class ParserFileBase: + + def __init__(self): + self.__file_data = "" + self.__group_id = TestGroupId.UNKNOWN + self.__test_id = 0 + + +class ParserTd(ParserFileBase): + + def __init__(self): + super().__init__() + + @staticmethod + def __open_file(file_path: str) -> str: + with open(file_path, "r") as file: + data = file.read() + return data + + @property + def group_id(self) -> TestGroupId: + return self.__group_id + + @property + def test_id(self) -> int: + return self.__test_id + + def __detect_group(self) -> TestGroupId: + group_str = self.__file_data.split("\n")[0] + group_int = int(group_str[:4], 16) + if not TestGroupId.has_value(group_int): + raise ValueError(f"Incorrect test group with ID {group_int}.") + return TestGroupId(group_int) + + def __detect_test(self) -> int: + group_str = self.__file_data.split("\n")[0] + return int(group_str[4:], 16) + + def set_file_name(self, file_path: str): + self.__file_data = self.__open_file(file_path) + self.__group_id = self.__detect_group() + self.__test_id = self.__detect_test() + + def parse(self, params: DUTTestParameters) -> DUTTestParameters: + if isinstance(params, CrcVideoTestParam): + data = self.__file_data.split("\n") + if data[1][len(data[1]) - 1] == " ": + data[1] = data[1][: -2] + + params_count, timeout, frames_to_test, ref_frame_count, frame_mismatches, width, height, bpp, frame_rate, \ + rate_tolerance, crc_r, crc_g, crc_b, motion_iteration, *_ = data[1].split(" ") + + crc_values = [] + if len(data) > 2: + crc_values_str = data[2].split(" ") + crc_count = int(crc_values_str[0]) + if crc_count > 0: + for i in range(0, crc_count, 2): + crc_values.append(int(crc_values_str[i + 1]) & 0xFFFF) + crc_values.append((int(crc_values_str[i + 1]) >> 16) & 0xFFFF) + crc_values.append(int(crc_values_str[i + 2]) & 0xFFFF) + else: + crc_values.extend([int(crc_r), int(crc_g), int(crc_b)]) + else: + crc_values.extend([int(crc_r), int(crc_g), int(crc_b)]) + + params.timeout = int(timeout) + params.number_frames_to_test = int(frames_to_test) + params.number_reference_frames = int(ref_frame_count) + params.number_frames_mismatch = int(frame_mismatches) + params.reference_width = int(width) + params.reference_height = int(height) + params.reference_color_depth = CrcVideoTestBpp(int(bpp)) + params.required_frame_rate = int(frame_rate) + params.frame_rate_tolerance = int(rate_tolerance) + params.motion_test_iteration = int(motion_iteration) + params.reference_crc_values = crc_values + + return params + elif isinstance(params, HdmiSinkContinuityDUTTestParam): + data = self.__file_data.split("\n") + if data[1][len(data[1]) - 1] == " ": + data[1] = data[1][: -2] + + params_count, test_time, status_period, stop_flag, enable_scdc_version, enable_scdc_status, \ + enable_scdc_error_count, scdc_error_count, *_ = data[1].split(" ") + + params.test_time = int(test_time) + params.status_period = int(status_period) + params.stop_flag = bool(stop_flag) + params.enable_scdc_version = bool(enable_scdc_version) + params.enable_scdc_status = bool(enable_scdc_status) + params.enable_scdc_error_count = bool(enable_scdc_error_count) + params.scdc_error_count = int(scdc_error_count) + + return params + else: + raise NotImplementedError(f"Group {params.__name__} is not currently supported.") + + +class ParserJson(ParserFileBase): + + class JsonFormat(IntEnum): + UNKNOWN = -1 + EXPORT = 0 + CDF = 1 + + __DSC_VMT_CI_LIST = [ "TSI_DP20_SRCCTS_DSC_VMT_1920_1080_30", + "TSI_DP20_SRCCTS_DSC_VMT_1920_1080_60", + "TSI_DP20_SRCCTS_DSC_VMT_1920_1080_120", + "TSI_DP20_SRCCTS_DSC_VMT_1920_1080_144", + "TSI_DP20_SRCCTS_DSC_VMT_1920_1080_240", + "TSI_DP20_SRCCTS_DSC_VMT_3840_2160_30", + "TSI_DP20_SRCCTS_DSC_VMT_3840_2160_60", + "TSI_DP20_SRCCTS_DSC_VMT_3840_2160_120", + "TSI_DP20_SRCCTS_DSC_VMT_3840_2160_144", + "TSI_DP20_SRCCTS_DSC_VMT_3840_2160_240", + "TSI_DP20_SRCCTS_DSC_VMT_5120_2160_30", + "TSI_DP20_SRCCTS_DSC_VMT_5120_2160_60", + "TSI_DP20_SRCCTS_DSC_VMT_5120_2160_120", + "TSI_DP20_SRCCTS_DSC_VMT_5120_2160_144", + "TSI_DP20_SRCCTS_DSC_VMT_5120_2160_240", + "TSI_DP20_SRCCTS_DSC_VMT_7680_4320_30", + "TSI_DP20_SRCCTS_DSC_VMT_7680_4320_60", + "TSI_DP20_SRCCTS_DSC_VMT_7680_4320_120", + "TSI_DP20_SRCCTS_DSC_VMT_10240_4320_30", + "TSI_DP20_SRCCTS_DSC_VMT_10240_4320_60" ] + + def __init__(self): + super().__init__() + self.__type = self.JsonFormat.UNKNOWN + self.__merged_group_id = MergedTestGroups.Unknown + self.__test_id = -1 + self.__group_id = TestGroupId.UNKNOWN + + def __open_file(self, file_path: str) -> dict: + with open(file_path, encoding='UTF-8') as cfg_json: + data = json.load(cfg_json) + + if data.get('TestId') is not None: + self.__type = self.JsonFormat.EXPORT + elif data.get('Header_Type') is not None: + self.__type = self.JsonFormat.CDF + + return data + + @property + def group_id(self) -> TestGroupId: + return self.__group_id + + @property + def merged_group_id(self) -> MergedTestGroups: + return self.__merged_group_id + + @property + def test_id(self) -> int: + return self.__test_id + + @property + def json_type(self) -> JsonFormat: + return self.__type + + def __detect_group(self) -> TestGroupId: + group_int = int(self.__file_data.get('TestId')[:4], 16) + if group_int == 0x0 and self.__file_data.get('Data').get("TSI_REF1_FRAME_DATA") is not None: + group_int = 0x3E8 + if not TestGroupId.has_value(group_int): + raise ValueError(f"Incorrect test group with ID {group_int}.") + return TestGroupId(group_int) + + def __detect_merged_group(self) -> MergedTestGroups: + return MergedTestGroups(int(re.findall(r"0x\d+\w+_", self.__file_data.get("Header_Type"))[0][:-1], 16)) + + def __detect_test(self) -> int: + return int(self.__file_data.get('TestId')[4:], 16) + + def set_file_name(self, file_path: str): + self.__file_data = self.__open_file(file_path) + if self.json_type == self.JsonFormat.EXPORT: + self.__group_id = self.__detect_group() + self.__test_id = self.__detect_test() + elif self.json_type == self.JsonFormat.CDF: + self.__merged_group_id = self.__detect_merged_group() + else: + raise ValueError(f"Incorrect json file type {self.__type.name}.") + + def parse(self, params: DUTTestParameters) -> DUTTestParameters: + try: + param_list = get_param_list(params) + + if self.json_type == self.JsonFormat.EXPORT: + json_data = self.__file_data.get('Data') + else: + json_data = self.__file_data + + for param in param_list: + if param.config_id_name in json_data.keys(): + value = self.__json_to_param_value(param, json_data.get(param.config_id_name)) + if param.config_id_name == "TSI_REF1_ELEMENT_FORMAT": + param.direct_set_def_value(value) + else: + param.default_value = value + else: + if (self.__merged_group_id == MergedTestGroups.DPRX_128b132b_LL_CTS + or self.__group_id == TestGroupId.DP_2_1_RX_DSC_CTS): + if param.config_id_name in self.__DSC_VMT_CI_LIST: + param.default_value = self.__json_to_param_value(param, json_data.get(param.config_id_name.replace("DSC_", ""))) + + logging.warn(f"Required CDF key {param.config_id_name} is missing in provided CDF file. It will be skipped.") + + return params + except BaseException as e: + raise ValueError(f"Group {str(params)} cannot be parsed. Error {e}") + + @staticmethod + def __json_to_param_value(param, json_value): + if param.value_type in [ DataType.ArrayU8, DataType.ArrayU16, DataType.ArrayU32 ]: + str_vals = json_value.split(" ")[ :-1 ] + return [ int(i) for i in str_vals ] + elif param.value_type in [ DataType.Int, DataType.Uint32, DataType.BitList ]: + return int(json_value) + elif param.value_type == DataType.Bool: + return bool(json_value) + elif param.value_type == DataType.Double: + return float(json_value) + elif param.value_type == DataType.ArrayFromFilePath: + return str(json_value) + else: + return json_value + + +class Parser: + class ConfigFileFormat(IntEnum): + UNKNOWN = -1 + TD = 0 + JSON = 1 + + def __init__(self): + self.__extension = "" + self.__parser_td = ParserTd() + self.__parser_json = ParserJson() + + @staticmethod + def __detect_file_format(file_path: str) -> ConfigFileFormat: + filename, file_extension = os.path.splitext(file_path) + if file_extension.lower() == '.json': + return Parser.ConfigFileFormat.JSON + elif file_extension.lower() == '.td': + return Parser.ConfigFileFormat.TD + else: + return Parser.ConfigFileFormat.UNKNOWN + + def set_file_name(self, file_path: str): + self.__extension = self.__detect_file_format(file_path) + if self.__extension == Parser.ConfigFileFormat.JSON: + self.__parser_json.set_file_name(file_path) + elif self.__extension == Parser.ConfigFileFormat.TD: + self.__parser_td.set_file_name(file_path) + else: + raise ValueError("Incorrect file format. Must be: TD or JSON.") + + def detect_group(self) -> TestGroupId: + if self.__extension == Parser.ConfigFileFormat.JSON: + if self.__parser_json.json_type == ParserJson.JsonFormat.EXPORT: + return self.__parser_json.group_id + else: + raise ValueError("Cannot get Test Group ID from JSON (CDF type) file") + elif self.__extension == Parser.ConfigFileFormat.TD: + return self.__parser_td.group_id + else: + raise ValueError("Incorrect file format. Must be: TD or JSON.") + + def detect_merged_group(self) -> MergedTestGroups: + if self.__extension == Parser.ConfigFileFormat.JSON: + if self.__parser_json.json_type == ParserJson.JsonFormat.CDF: + return self.__parser_json.merged_group_id + else: + return test_group_to_merged_group(self.__parser_json.group_id, self.__parser_json.test_id) + elif self.__extension == Parser.ConfigFileFormat.TD: + return test_group_to_merged_group(self.__parser_json.group_id, self.__parser_json.test_id) + else: + raise ValueError("Incorrect file format. Must be: TD or JSON.") + + def detect_test(self) -> int: + if self.__extension == Parser.ConfigFileFormat.JSON: + if self.__parser_json.json_type == ParserJson.JsonFormat.EXPORT: + return self.__parser_json.test_id + else: + raise ValueError("Cannot get Test ID from JSON (CDF type) file") + elif self.__extension == Parser.ConfigFileFormat.TD: + return self.__parser_td.test_id + else: + raise ValueError("Incorrect file format. Must be: TD or JSON.") + + def parse(self, params: DUTTestParameters) -> DUTTestParameters: + if self.__extension == Parser.ConfigFileFormat.JSON: + return self.__parser_json.parse(params) + elif self.__extension == Parser.ConfigFileFormat.TD: + return self.__parser_td.parse(params) + else: + raise ValueError("Incorrect file format. Must be: TD or JSON.") diff --git a/UniTAP/dev/modules/dut_tests/private_info.py b/UniTAP/dev/modules/dut_tests/private_info.py new file mode 100644 index 0000000..b85a673 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/private_info.py @@ -0,0 +1,174 @@ +from .test_info import * + + +class MergedTestGroups(IntEnum): + Unknown = 0x00 + CRCVideoTest = 0x01 + AudioTest = 0x02 + DPRX_8b10b_LL_CTS = 0x03 + LinkConfigTest = 0x04 + DPRX_ELECTRICAL = 0x05 + HDRX_ELECTRICAL = 0x06 + USBC_ELECTRICAL = 0x07 + DPRX_128b132b_LL_CTS = 0x08 + DP_HDCP_CTS_1A = 0x09 + DP_HDCP_CTS_1B = 0x0A + DP_HDCP_CTS_3A = 0x0B + DP_HDCP_CTS_3B = 0x0C + DP_HDCP_CTS_2C = 0x0D + DP_HDCP_CTS_3C = 0x0E + HD_TX_VRR = 0x0F + HD_RX_VRR = 0x10 + HD_RX_CEC = 0x11 + PixelVideoTest = 0x12 + DPTX_8b10b_LL_CTS = 0x13 + HDR10PlusSourceTest = 0x14, + LTTPR_128b132b_LL_CTS = 0x15, + HDR10PlusSSTMTest = 0x16, + EPRPowerSink = 0x17, + DPTX_128b132b_LL_CTS = 0x18, + EPRPowerSource = 0x19, + HDRX_DSC_CTS = 0x20, + HDTX_DSC_CTS = 0x21, + HDTX_CONTINUITY = 0x22, + HDTX_CABLE_CHECK = 0x24 + + +MERGED_GROUP_PARAMS_TYPE = { + MergedTestGroups.CRCVideoTest: CrcVideoTestParam, + MergedTestGroups.AudioTest: AudioTestParam, + MergedTestGroups.DPRX_8b10b_LL_CTS: Dp14SourceDUTTestParam, + MergedTestGroups.LinkConfigTest: LinkConfigTestParam, + MergedTestGroups.DPRX_ELECTRICAL: DpElectricalTestParam, + MergedTestGroups.HDRX_ELECTRICAL: HdmiElectricalTestParam, + MergedTestGroups.USBC_ELECTRICAL: UsbcElectricalTestParam, + MergedTestGroups.DPRX_128b132b_LL_CTS: Dp21SourceDUTTestParam, + MergedTestGroups.DP_HDCP_CTS_1A: Hdcp1ATestParam, + MergedTestGroups.DP_HDCP_CTS_1B: Hdcp1BTestParam, + MergedTestGroups.DP_HDCP_CTS_3A: Hdcp3ATestParam, + MergedTestGroups.DP_HDCP_CTS_3B: Hdcp3BTestParam, + MergedTestGroups.DP_HDCP_CTS_2C: Hdcp2CTestParam, + MergedTestGroups.DP_HDCP_CTS_3C: Hdcp3CTestParam, + MergedTestGroups.HD_TX_VRR: VrrSinkDUTTestParam, + MergedTestGroups.HD_RX_VRR: VrrSourceDUTTestParam, + MergedTestGroups.HD_RX_CEC: CecFunctionalTestParam, + MergedTestGroups.PixelVideoTest: VideoPixelTestParam, + MergedTestGroups.DPTX_8b10b_LL_CTS: Dp14SinkTestParam, + MergedTestGroups.HDR10PlusSourceTest: Hdr10TestParam, + MergedTestGroups.DPTX_128b132b_LL_CTS: Dp21SinkTestParam, + MergedTestGroups.LTTPR_128b132b_LL_CTS: DpLttprTestParam, + MergedTestGroups.HDRX_DSC_CTS: HdmiSourceDUTTestParam, + MergedTestGroups.HDTX_DSC_CTS: HdmiSinkDUTTestParam, + MergedTestGroups.HDTX_CONTINUITY: HdmiSinkContinuityDUTTestParam, + MergedTestGroups.HDTX_CABLE_CHECK: HdmiSinkCableCheckTestParam +} + +group_params_dict = { + TestGroupId.AUDIO_TEST: AudioTestParam, + TestGroupId.DP_RX_ELECTRICAL: DpElectricalTestParam, + TestGroupId.HDMI_RX_ELECTRICAL: HdmiElectricalTestParam, + TestGroupId.CEC_FUNCTIONAL: CecFunctionalTestParam, + TestGroupId.DP_RX_CRC: CrcVideoTestParam, + TestGroupId.DP_RX_SIMPLE_LT: LinkConfigTestParam, + TestGroupId.HDMI_RX_CRC: CrcVideoTestParam, + TestGroupId.USB_TYPE_C_ELECTRICAL: UsbcElectricalTestParam, + TestGroupId.DP_RX_LL_CTS: Dp14SourceDUTTestParam, + TestGroupId.DP_TX_LL_CTS: Dp14SinkTestParam, + TestGroupId.DP_TX_LL_CTS_DSC: Dp14SinkTestParam, + TestGroupId.DP_RX_LL_CTS_DSC: Dp14SourceDUTTestParam, + TestGroupId.DP_2_1_RX_LL_CTS: Dp21SourceDUTTestParam, + TestGroupId.DP_2_1_TX_LL_CTS: Dp21SinkTestParam, + TestGroupId.DP_HDCP_CTS_1A: Hdcp1ATestParam, + TestGroupId.DP_HDCP_CTS_1B: Hdcp1BTestParam, + TestGroupId.DP_HDCP_CTS_2C: Hdcp2CTestParam, + TestGroupId.DP_HDCP_CTS_3A: Hdcp3ATestParam, + TestGroupId.DP_HDCP_CTS_3B: Hdcp3BTestParam, + TestGroupId.DP_HDCP_CTS_3C: Hdcp3CTestParam, + TestGroupId.DP_TX_DISPLAYID: Dp14SinkTestParam, + TestGroupId.DP_RX_DISPLAYID: Dp14SourceDUTTestParam, + TestGroupId.HDMI_RX_VRR: VrrSourceDUTTestParam, + TestGroupId.HDMI_TX_VRR: VrrSinkDUTTestParam, + TestGroupId.DP_TX_ADAPTIVESYNC: Dp14SinkTestParam, + TestGroupId.DP_RX_ADAPTIVESYNC: Dp14SourceDUTTestParam, + TestGroupId.DP_2_1_RX_LTTPR_CTS: Dp21SourceDUTTestParam, + TestGroupId.DP_2_1_TX_LTTPR_CTS: Dp21SinkTestParam, + TestGroupId.DP_2_1_RX_DSC_CTS: Dp21SourceDUTTestParam, + TestGroupId.DP_2_1_TX_DSC_CTS: Dp21SinkTestParam, + TestGroupId.DP_2_1_RX_DISPAYID: Dp21SourceDUTTestParam, + TestGroupId.DP_2_1_TX_DISPAYID: Dp21SinkTestParam, + TestGroupId.DP_2_1_RX_ADAPTIVESYNC: Dp21SourceDUTTestParam, + TestGroupId.DP_2_1_TX_ADAPTIVESYNC: Dp21SinkTestParam, + TestGroupId.PIXEL_VIDEO_TEST: VideoPixelTestParam, + TestGroupId.HDR10_TEST: Hdr10TestParam, + TestGroupId.DP_2_1_LTTPR_CTS: DpLttprTestParam, + TestGroupId.HD_RX_DSC_CTS: HdmiSourceDUTTestParam, + TestGroupId.HD_TX_DSC_CTS: HdmiSinkDUTTestParam, + TestGroupId.HD_TX_CONTINUITY: HdmiSinkContinuityDUTTestParam, + TestGroupId.HD_TX_CABLE_CHECK: HdmiSinkCableCheckTestParam +} + + +def test_group_to_merged_group(fw_group: TestGroupId, test_id: int) -> MergedTestGroups: + merged_group_id = MergedTestGroups.Unknown + if fw_group == TestGroupId.AUDIO_TEST or fw_group == TestGroupId.PIXEL_VIDEO_TEST: + if test_id == 0x02: + merged_group_id = MergedTestGroups.PixelVideoTest + elif test_id == 0x12: + merged_group_id = MergedTestGroups.AudioTest + elif fw_group in [TestGroupId.DP_RX_LL_CTS, TestGroupId.DP_RX_LL_CTS_DSC, TestGroupId.DP_RX_ADAPTIVESYNC, + TestGroupId.DP_RX_DISPLAYID]: + merged_group_id = MergedTestGroups.DPRX_8b10b_LL_CTS + elif fw_group in [TestGroupId.DP_TX_LL_CTS, TestGroupId.DP_TX_LL_CTS_DSC, TestGroupId.DP_TX_ADAPTIVESYNC, + TestGroupId.DP_TX_DISPLAYID]: + merged_group_id = MergedTestGroups.DPTX_8b10b_LL_CTS + elif fw_group == TestGroupId.DP_RX_CRC: + merged_group_id = MergedTestGroups.CRCVideoTest + elif fw_group == TestGroupId.HDMI_RX_CRC: + merged_group_id = MergedTestGroups.CRCVideoTest + elif fw_group in [TestGroupId.DP_2_1_RX_LL_CTS, TestGroupId.DP_2_1_RX_DSC_CTS, + TestGroupId.DP_2_1_RX_LTTPR_CTS, TestGroupId.DP_2_1_RX_DISPAYID, + TestGroupId.DP_2_1_RX_ADAPTIVESYNC]: + merged_group_id = MergedTestGroups.DPRX_128b132b_LL_CTS + elif fw_group in [TestGroupId.DP_2_1_TX_LL_CTS, TestGroupId.DP_2_1_TX_DSC_CTS, + TestGroupId.DP_2_1_TX_LTTPR_CTS, TestGroupId.DP_2_1_TX_DISPAYID, + TestGroupId.DP_2_1_TX_ADAPTIVESYNC]: + merged_group_id = MergedTestGroups.DPTX_128b132b_LL_CTS + elif fw_group == TestGroupId.DP_2_1_LTTPR_CTS: + merged_group_id = MergedTestGroups.LTTPR_128b132b_LL_CTS + elif fw_group == TestGroupId.DP_HDCP_CTS_1A: + merged_group_id = MergedTestGroups.DP_HDCP_CTS_1A + elif fw_group == TestGroupId.DP_HDCP_CTS_1B: + merged_group_id = MergedTestGroups.DP_HDCP_CTS_1B + elif fw_group == TestGroupId.DP_HDCP_CTS_2C: + merged_group_id = MergedTestGroups.DP_HDCP_CTS_2C + elif fw_group == TestGroupId.DP_HDCP_CTS_3A: + merged_group_id = MergedTestGroups.DP_HDCP_CTS_3A + elif fw_group == TestGroupId.DP_HDCP_CTS_3B: + merged_group_id = MergedTestGroups.DP_HDCP_CTS_3B + elif fw_group == TestGroupId.DP_HDCP_CTS_3C: + merged_group_id = MergedTestGroups.DP_HDCP_CTS_3C + elif fw_group == TestGroupId.DP_RX_ELECTRICAL: + merged_group_id = MergedTestGroups.DPRX_ELECTRICAL + elif fw_group == TestGroupId.HDMI_RX_ELECTRICAL: + merged_group_id = MergedTestGroups.HDRX_ELECTRICAL + elif fw_group == TestGroupId.USB_TYPE_C_ELECTRICAL: + merged_group_id = MergedTestGroups.USBC_ELECTRICAL + elif fw_group == TestGroupId.DP_RX_SIMPLE_LT: + merged_group_id = MergedTestGroups.LinkConfigTest + elif fw_group == TestGroupId.CEC_FUNCTIONAL: + merged_group_id = MergedTestGroups.HD_RX_CEC + elif fw_group == TestGroupId.HDMI_RX_VRR: + merged_group_id = MergedTestGroups.HD_RX_VRR + elif fw_group == TestGroupId.HDMI_TX_VRR: + merged_group_id = MergedTestGroups.HD_TX_VRR + elif fw_group == TestGroupId.HD_TX_DSC_CTS: + merged_group_id = MergedTestGroups.HDTX_DSC_CTS + elif fw_group == TestGroupId.HD_RX_DSC_CTS: + merged_group_id = MergedTestGroups.HDRX_DSC_CTS + elif fw_group == TestGroupId.HD_TX_CONTINUITY: + merged_group_id = MergedTestGroups.HDTX_CONTINUITY + elif fw_group == TestGroupId.HD_TX_CABLE_CHECK: + merged_group_id = MergedTestGroups.HDTX_CABLE_CHECK + else: + merged_group_id = MergedTestGroups.Unknown + return merged_group_id diff --git a/UniTAP/dev/modules/dut_tests/report/__init__.py b/UniTAP/dev/modules/dut_tests/report/__init__.py new file mode 100644 index 0000000..aa54cbd --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/report/__init__.py @@ -0,0 +1,2 @@ +from .report_maker import generate_html_report, TestAdditionalInfo, TestResult +from .report_utils import update_configuration, dp21_source_timing_table, detect_warning_message diff --git a/UniTAP/dev/modules/dut_tests/report/report_maker.py b/UniTAP/dev/modules/dut_tests/report/report_maker.py new file mode 100644 index 0000000..7c818af --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/report/report_maker.py @@ -0,0 +1,179 @@ +import platform +from datetime import datetime +from enum import IntEnum + +from .report_template import * + + +class TestResult(IntEnum): + Unknown = -1 + Passed = 0 + Failed = 1 + Skipped = 2 + Aborted = 3 + + +class TestAdditionalInfo: + + def __init__(self, dut_model_name: str = "", dut_revision: str = "", dut_serial_number: str = "", + dut_firmware_version: str = "", dut_driver_version: str = "", tested_by: str = "", remarks: str = ""): + self.device_name = str() + self.device_serial_number = str() + self.device_detailed_data = str() + self.device_frontend_version = str() + self.memory_size = str() + self.python_version = platform.python_version() + self.bundle_version = str() + self.dut_model_name = dut_model_name + self.dut_revision = dut_revision + self.dut_serial_number = dut_serial_number + self.dut_firmware_version = dut_firmware_version + self.dut_driver_version = dut_driver_version + self.tested_by = tested_by + self.remarks = remarks + + +def write_to_file(file_name, text: str): + with open(file_name, 'w', encoding='UTF-8') as file: + file.write(text) + + +def replace_additional_info(test_additional_info: TestAdditionalInfo): + result = DeviceDescription + + result = result.replace("#device_name#", test_additional_info.device_name) + result = result.replace("#detailed_vdata#", test_additional_info.device_detailed_data) + result = result.replace("#frontend_version#", test_additional_info.device_frontend_version) + result = result.replace("#memory_size#", test_additional_info.memory_size) + result = result.replace("#console_app_version#", test_additional_info.python_version) + result = result.replace("#console_bundle_version#", test_additional_info.bundle_version) + result = result.replace("#date_time#", datetime.now().strftime("%d-%m-%Y %H:%M")) + result = result.replace("#model_name#", test_additional_info.dut_model_name) + result = result.replace("#revision#", test_additional_info.dut_revision) + result = result.replace("#serial_number#", test_additional_info.dut_serial_number) + result = result.replace("#firmware_version#", test_additional_info.dut_firmware_version) + result = result.replace("#driver_version#", test_additional_info.dut_driver_version) + result = result.replace("#conducted_by#", test_additional_info.tested_by) + result = result.replace("#remarks#", test_additional_info.remarks) + + return result + + +def generate_html_report(path: str, test_additional_info: TestAdditionalInfo, reports: list): + result = str() + + head = ReportHead + head = head.replace("#count#", str((len(reports) + 2))) + + body = ReportBody + body = body.replace("#device_description#", replace_additional_info(test_additional_info)) + + runs = 0 + failed = 0 + skipped = 0 + aborted = 0 + + dict_result_2 = {TestResult.Failed: "FAILED", + TestResult.Skipped: "SKIPPED", + TestResult.Aborted: "ABORTED", + TestResult.Passed: "PASSED"} + + json_config_info = '[' + ",".join(f'"{report.json_config_info}"' for report in reports) + ']' + + for item in reports: + runs += 1 + if item.test_result == TestResult.Failed: + failed += 1 + elif item.test_result == TestResult.Skipped: + skipped += 1 + elif item.test_result == TestResult.Aborted: + aborted += 1 + + head = head.replace("#configInfo#", json_config_info) + result += head + + passed = runs - failed - skipped - aborted + + body = body.replace("#runs#", str(runs)) + body = body.replace("#passed#", str(passed)) + body = body.replace("#failed#", str(failed)) + body = body.replace("#skipped#", str(skipped)) + body = body.replace("#aborted#", str(aborted)) + + options_list = "" + for i in range(len(reports)): + line = OptionListLine + line = line.replace("#number#", str(i + 2)) + line = line.replace("#test_name#", f"{reports[i].group_name} / {reports[i].test_name}") + line = line.replace("#test_result#", dict_result_2.get(reports[i].test_result)) + options_list += line + "\n" + + body = body.replace("#optionList#", options_list) + + tables = Tables + table_lines = "" + for i in range(len(reports)): + line = TestDescriptionLine + line = line.replace("#handler_number#", str(i + 2)) + line = line.replace("#test_name#", reports[i].test_name) + line = line.replace("#test_group#", reports[i].group_name) + line = line.replace("#test_result#", dict_result_2.get(reports[i].test_result)) + line = line.replace("#error#", f"{reports[i].error_code}" if reports[i].error_code != 0 else "-") + line = line.replace("#ending#", "2" if i % 2 else "") + test_result = dict_result_2.get(reports[i].test_result) + line = line.replace("#SUCCESS#", test_result if test_result is not None else "PASSED") + table_lines += line + "\n" + + tables = tables.replace("#test_description_lines#", table_lines) + + def escape_html(log: str) -> str: + res = [] + + for c in log: + if c == '&': + res.append("&") + elif c == '<': + res.append("<") + elif c == '>': + res.append(">") + elif c == '"': + res.append(""") + elif c == '\'': + res.append("'") + else: + res.append(c) + + return ''.join(res) + + tables_list = "" + warnings = "" + for i in range(len(reports)): + line = TestTable + line = line.replace("#number#", f"{i + 2}") + line = line.replace("#test_number#", f"{i + 1}") + line = line.replace("#test_group#", reports[i].group_name) + line = line.replace("#test_name#", reports[i].test_name) + line = line.replace("#result#", dict_result_2.get(reports[i].test_result)) + line = line.replace("#test_delay#", f"{reports[i].test_delay * 1000}") + line = line.replace("#test_parameters#", reports[i].config_info) + line = line.replace("#warning#", "Debug options enabled!

" if reports[i].debug else "") + + if reports[i].debug: + warnings = "Debug options enabled!

" + + line = line.replace("#test_log#", escape_html(reports[i].fw_logs)) + tables_list += line + "\n" + + body = body.replace("#warning#", warnings) + tables = tables.replace("#test_tables_list#", tables_list) + tables = tables.replace("#warning#", warnings) + + result += body + result += tables + + footer = ReportFooter + footer = footer.replace("#result#", "PASSED" if failed == 0 else "FAILED") + result += footer + + full_name = path if path.lower().find(".html") != -1 else (path + ".html") + write_to_file(full_name, result) diff --git a/UniTAP/dev/modules/dut_tests/report/report_template.py b/UniTAP/dev/modules/dut_tests/report/report_template.py new file mode 100644 index 0000000..866b351 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/report/report_template.py @@ -0,0 +1,153 @@ +ReportHead = "\n" \ + "Unigraf Test Report\n" \ + "\n" \ + "\n" \ + "\n" \ + "\n" + +OptionListLine = " \n" + +TestDescriptionLine = ("\n") + +Tables = "
\n" \ + "

\n" \ + "

   Summary of individual test runs

\n" \ + "

\n#warning#

\n" \ + "

" + "#test_group# / #test_name#

" + " #test_result# 

 #error# 

\n" \ + "\n" \ + "#test_description_lines#\n" \ + "

Test name

Result

Error

\n" \ + "#test_tables_list#\n" + +DeviceDescription = "

\n" \ + "Device: #device_name#\n" \ + "
Firmware Package: #detailed_vdata#\n" \ + "
UniTAP: #console_app_version#\n" \ + "
Software Bundle: #console_bundle_version#\n" \ + "
Frontend Info: #frontend_version#\n" \ + "
Device total memory size: #memory_size#\n" \ + "

Report generated: #date_time#
\n" \ + "

   DUT

\n" \ + "Device/Model name: #model_name#\n" \ + "
HW Revision: #revision#\n" \ + "
Serial number: #serial_number#\n" \ + "
Firmware: #firmware_version#\n" \ + "
Driver: #driver_version#\n" \ + "

Testing conducted by: #conducted_by#\n" \ + "

Remarks: #remarks#\n" \ + "

\n" + +TestTable = "

\n" \ + "

   Test Details, Test #test_number#

\n" \ + "

   #test_name#

\n" \ + "

\n" \ + "#warning#

\n" \ + "

\n" \ + "Test Set: #test_group#
\n" \ + "Test Result: #result#\n" \ + "

Test Settings:

\n" \ + "

\n" \ + "Delay between tests = #test_delay#ms\n
" \ + "#test_parameters#\n" \ + "

   Test Log

" \
+            "#test_log#" \
+            "\n"
+
+ReportBody = "\n" \
+             "    \n" \
+             "    

  Unigraf Test Report

\n" \ + "
\n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + "

Select item to display

\n" \ + "
\n" \ + "
\n" \ + "\n" \ + "\n" \ + "\n" \ + "\n" \ + "
\n" \ + "
\n" \ + "
\n" \ + "
\n" \ + "

   Report summary

\n" \ + "

   Unigraf device\n" \ + "#device_description#\n" \ + "

   Test results summary

\n#warning#

\n

\n" \ + "Total number of test runs: #runs#
Passed test runs: #passed#
Failed test runs: #failed#
Skipped test runs: #skipped#
Aborted test runs: #aborted#\n" \ + "

\n" + +ReportFooter = "

\n" \ + "
\n" \ + "\n" \ + "\n" \ + "\n" \ + "\n" \ + "
\n" \ + "\n" \ + "
\n" \ + "

\n" \ + " Unigraf Oy | Piispantilankuja 4 | 02240 Espoo | Finland | +358-9-859 550
\n" \ + " E-mail: info@unigraf.fi " \ + "| Web site: www.unigraf.fi\n" \ + "

\n" \ + "\n" \ + "\n" diff --git a/UniTAP/dev/modules/dut_tests/report/report_utils.py b/UniTAP/dev/modules/dut_tests/report/report_utils.py new file mode 100644 index 0000000..0848872 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/report/report_utils.py @@ -0,0 +1,780 @@ +import re +from UniTAP.dev.modules.dut_tests.test_utils import TestReportConfigType, make_cf, CMTFormat, CMTBitDepth + + +def value_from_enumeration_var(_data, _value): + + if len(_data.str_presentation) > 0: + return _data.str_presentation[_data.available_values.index(str(_value))] + else: + return _value + + +def copy_data(data): + _data = {} + for item in data: + if item.config_id_name in ["TSI_DP14_SRCCTS_DUT_CAPS", "TSI_DP14_SRCCTS_DUT_TA", + "TSI_DP14_SRCCTS_DMT_TIMING", "TSI_DP14_SRCCTS_CTA_TIMING", + "TSI_DP14_SRCCTS_CVT_TIMING", "TSI_DP14_SRCCTS_AS_DUT_CAPS", + "TSI_DP20_SRCCTS_DUT_CAPS", "TSI_DP20_SRCCTS_DUT_TA", + "TSI_DP20_SRCCTS_DMT_TIMING", "TSI_DP20_SRCCTS_CTA_TIMING", + "TSI_DP20_SRCCTS_CVT_TIMING", "TSI_DP20_SRCCTS_AS_DUT_CAPS", + "TSI_DP20_SRCCTS_DUT_LINK_RATES", "TSI_DP14_SRCCTS_DUT_DEBUG_CONF", + "TSI_DP20_SRCCTS_DUT_DEBUG_CONF", "TSI_DP20_SINKCTS_DEBUG_CONF"]: + _data.update({item.config_id_name: [item.default_value, item.bit_field_list]}) + elif item.enumeration_variants is not None: + _data.update({item.config_id_name: [item.default_value, item.enumeration_variants]}) + else: + _data.update({item.config_id_name: item.default_value}) + return _data + + +def detect_warning_message(type_of_test: TestReportConfigType, data): + _data = copy_data(data) + _dict = {TestReportConfigType.Dp1_4_Source: "TSI_DP14_SRCCTS_DUT_DEBUG_CONF", + TestReportConfigType.Dp2_1_Source: "TSI_DP20_SRCCTS_DUT_DEBUG_CONF", + TestReportConfigType.Dp2_1_Sink: "TSI_DP20_SINKCTS_DEBUG_CONF", + TestReportConfigType.Dp2_1_Lttpr: "TSI_DP20_LTTPR_DEBUG_CR_ITERATION_DELAY"} + key = _dict.get(type_of_test) + val = _data.get(key) + if val is not None: + if key in ["TSI_DP14_SRCCTS_DUT_DEBUG_CONF", + "TSI_DP20_SRCCTS_DUT_DEBUG_CONF", + "TSI_DP20_SINKCTS_DEBUG_CONF"] and val[0] != 0: + return True + elif key == "TSI_DP20_LTTPR_DEBUG_CR_ITERATION_DELAY" and int(val[0] != 100): + return True + else: + return False + else: + return False + + +def update_configuration(type_of_test: TestReportConfigType, data): + configuration = "" + _data = copy_data(data) + if type_of_test == TestReportConfigType.Dp1_4_Source: + val = _data.get("TSI_DP14_SRCCTS_DUT_DEBUG_CONF") + if val is not None and val[0] != 0: + configuration += "
Debug options:
" + for _bit in val[1]: + if val[0] & _bit.mask: + configuration += f" - {_bit.description}
" + configuration += "
" + + configuration += "Test timeout = {}ms
".format(_data.get("TSI_DP14_SRCCTS_TIMEOUT")) + configuration += "Long HPD Duration = {}ms

".format(_data.get("TSI_DP14_SRCCTS_LONG_HPD_PULSE")) + configuration += "DUT capability settings and flags:
" + configuration += "- Max lanes = {}
".format(_data.get("TSI_DP14_SRCCTS_MAX_LANES")[0]) + configuration += "- Max link rate = {}
".format( + value_from_enumeration_var(_data.get("TSI_DP14_SRCCTS_MAX_LINK_RATE")[1], + _data.get("TSI_DP14_SRCCTS_MAX_LINK_RATE")[0])) + value = _data.get("TSI_DP14_SRCCTS_DUT_CAPS") + dut_caps_value = int(value[0]) + for _bit in _data.get("TSI_DP14_SRCCTS_DUT_CAPS")[1]: + if dut_caps_value & _bit.mask: + configuration += "- {}
".format(_bit.description) + + configuration += "
Test automation flags and settings:
" + + ta_flags = ["Always ready", "EDID read", "Link Training end", "Active Video"] + ta_flags_value = _data.get("TSI_DP14_SRCCTS_DUT_TA")[0] + for bit in _data.get("TSI_DP14_SRCCTS_DUT_TA")[1]: + if bit.mask & ta_flags_value or bit.enumeration_variants is not None: + if bit.enumeration_variants is not None: + configuration += "- Event indicating DUT ready: {}
".format( + ta_flags[(ta_flags_value >> 3) & 0x3]) + else: + configuration += "- {}
".format(bit.description) + + fail_mode = "640 x 480 @ 60Hz 6 BPC" + configuration += "
Failsafe timing: {}
".format( + fail_mode if int(_data.get("TSI_DP14_SRCCTS_FAILSAFE_MODE")[0]) else "") + configuration += "Max supported timing: {}
".format( + value_from_enumeration_var(_data.get("TSI_DP14_SRCCTS_MAX_RESOLUTION")[1], + _data.get("TSI_DP14_SRCCTS_MAX_RESOLUTION")[0])) + + configuration += "
Most packed timings, 1 lane: {}
".format( + value_from_enumeration_var(_data.get("TSI_DP14_SRCCTS_1L_MOST_PACKED")[1], + _data.get("TSI_DP14_SRCCTS_1L_MOST_PACKED")[0])) + configuration += "Most packed timings, 2 lane: {}
".format( + value_from_enumeration_var(_data.get("TSI_DP14_SRCCTS_2L_MOST_PACKED")[1], + _data.get("TSI_DP14_SRCCTS_2L_MOST_PACKED")[0])) + configuration += "Most packed timings, 4 lane: {}
".format( + value_from_enumeration_var(_data.get("TSI_DP14_SRCCTS_4L_MOST_PACKED")[1], + _data.get("TSI_DP14_SRCCTS_4L_MOST_PACKED")[0])) + + configuration += "
Time stamp generation timigns:
" + configuration += "RBR, 1 Lane: {}
".format( + value_from_enumeration_var(_data.get("TSI_DP14_SRCCTS_1L_RBB")[1], + _data.get("TSI_DP14_SRCCTS_1L_RBB")[0])) + configuration += "RBR, 2 Lane: {}
".format( + value_from_enumeration_var(_data.get("TSI_DP14_SRCCTS_2L_RBB")[1], + _data.get("TSI_DP14_SRCCTS_2L_RBB")[0])) + configuration += "RBR, 4 Lane: {}

".format( + value_from_enumeration_var(_data.get("TSI_DP14_SRCCTS_4L_RBB")[1], + _data.get("TSI_DP14_SRCCTS_4L_RBB")[0])) + + configuration += "HBR, 1 Lane: {}
".format( + value_from_enumeration_var(_data.get("TSI_DP14_SRCCTS_1L_HBR")[1], + _data.get("TSI_DP14_SRCCTS_1L_HBR")[0])) + configuration += "HBR, 2 Lane: {}
".format( + value_from_enumeration_var(_data.get("TSI_DP14_SRCCTS_2L_HBR")[1], + _data.get("TSI_DP14_SRCCTS_2L_HBR")[0])) + configuration += "HBR, 4 Lane: {}

".format( + value_from_enumeration_var(_data.get("TSI_DP14_SRCCTS_4L_HBR")[1], + _data.get("TSI_DP14_SRCCTS_4L_HBR")[0])) + + configuration += "HBR2, 1 Lane: {}
".format( + value_from_enumeration_var(_data.get("TSI_DP14_SRCCTS_1L_HBR2")[1], + _data.get("TSI_DP14_SRCCTS_1L_HBR2")[0])) + configuration += "HBR2, 2 Lane: {}
".format( + value_from_enumeration_var(_data.get("TSI_DP14_SRCCTS_2L_HBR2")[1], + _data.get("TSI_DP14_SRCCTS_2L_HBR2")[0])) + configuration += "HBR2, 4 Lane: {}

".format( + value_from_enumeration_var(_data.get("TSI_DP14_SRCCTS_4L_HBR2")[1], + _data.get("TSI_DP14_SRCCTS_4L_HBR2")[0])) + + configuration += "HBR3, 1 Lane: {}
".format( + value_from_enumeration_var(_data.get("TSI_DP14_SRCCTS_1L_HBR3")[1], + _data.get("TSI_DP14_SRCCTS_1L_HBR3")[0])) + configuration += "HBR3, 2 Lane: {}
".format( + value_from_enumeration_var(_data.get("TSI_DP14_SRCCTS_2L_HBR3")[1], + _data.get("TSI_DP14_SRCCTS_2L_HBR3")[0])) + configuration += "HBR3, 4 Lane: {}
".format( + value_from_enumeration_var(_data.get("TSI_DP14_SRCCTS_4L_HBR3")[1], + _data.get("TSI_DP14_SRCCTS_4L_HBR3")[0])) + + configuration += "
Colorimetry:
" + configuration += "- RGB 6 bpc VESA
" + + for i in range(1, 13): + value = int(_data.get(f"TSI_DP14_SRCCTS_COLOR_MODE_{i}")) + if value == make_cf(CMTFormat.RGB_LEGACY, CMTBitDepth.BPC_8): + configuration += "- RGB 8 bpc VESA
" + elif value == make_cf(CMTFormat.RGB_LEGACY, CMTBitDepth.BPC_10): + configuration += "- RGB 10 bpc VESA
" + elif value == make_cf(CMTFormat.RGB_CEA, CMTBitDepth.BPC_8): + configuration += "- RGB 8 bpc CTA
" + elif value == make_cf(CMTFormat.RGB_CEA, CMTBitDepth.BPC_10): + configuration += "- RGB 10 bpc CTA
" + elif value == make_cf(CMTFormat.YCBCR_422_BT601, CMTBitDepth.BPC_8): + configuration += "- YCbCr 4:2:2 8 bpc CTA (ITU.601)
" + elif value == make_cf(CMTFormat.YCBCR_422_BT601, CMTBitDepth.BPC_10): + configuration += "- YCbCr 4:2:2 10 bpc CTA (ITU.601)
" + elif value == make_cf(CMTFormat.YCBCR_422_BT709, CMTBitDepth.BPC_8): + configuration += "- YCbCr 4:2:2 8 bpc CTA (ITU.709)
" + elif value == make_cf(CMTFormat.YCBCR_422_BT709, CMTBitDepth.BPC_10): + configuration += "- YCbCr 4:2:2 10 bpc CTA (ITU.709)
" + elif value == make_cf(CMTFormat.YCBCR_444_BT601, CMTBitDepth.BPC_8): + configuration += "- YCbCr 4:4:4 8 bpc CTA (ITU.601)
" + elif value == make_cf(CMTFormat.YCBCR_444_BT601, CMTBitDepth.BPC_10): + configuration += "- YCbCr 4:4:4 10 bpc CTA (ITU.601)
" + elif value == make_cf(CMTFormat.YCBCR_444_BT709, CMTBitDepth.BPC_8): + configuration += "- YCbCr 4:4:4 8 bpc CTA (ITU.709)
" + elif value == make_cf(CMTFormat.YCBCR_444_BT709, CMTBitDepth.BPC_10): + configuration += "- YCbCr 4:4:4 10 bpc CTA (ITU.709)
" + + configuration += "
Audio format:
" + configuration += "- Minimum supported sample frequency: {}Hz
".format( + value_from_enumeration_var(_data.get("TSI_DP14_SRCCTS_MIN_SAMPLE_RATE")[1], + _data.get("TSI_DP14_SRCCTS_MIN_SAMPLE_RATE")[0])) + configuration += "- Maximum supported sample frequency: {}Hz
".format( + value_from_enumeration_var(_data.get("TSI_DP14_SRCCTS_MAX_SAMPLE_RATE")[1], + _data.get("TSI_DP14_SRCCTS_MAX_SAMPLE_RATE")[0])) + configuration += "- Audio test pattern: {}
". \ + format(value_from_enumeration_var(_data.get("TSI_DP14_SRCCTS_AUDIO_TEST_PATTERN")[1], + _data.get("TSI_DP14_SRCCTS_AUDIO_TEST_PATTERN")[0])) + configuration += "- Audio sample size: {}
". \ + format(value_from_enumeration_var(_data.get("TSI_DP14_SRCCTS_EDID_SAMPLE_SIZE")[1], + _data.get("TSI_DP14_SRCCTS_EDID_SAMPLE_SIZE")[0])) + + ch_alloc_title = ["Minimum channel configuration / Min. sample rate", + "Maximum channel configuration / Min. sample rate", + "Minimum channel configuration / Max. sample rate", + "Maximum channel configuration / Max. sample rate"] + + ch_alloc_str = [ + "- Front Left + Front Right (2 Channels)", + "- Low Frequency Effect (1 Channel)", + "- Front Center (1 Channel)", + "- Rear Left + Rear Right (2 Channels)", + "- Rear Center (1 Channel)", + "- Front Left Center + Front Right Center (2 Channels)", + "- Rear Left Center + Rear Right Center (2 Channels)", + "- Front Left Wide + Front Right Wide (2 Channels)", + "- Front Left High + Front Right High (2 Channels)", + "- Top Center (1 Channel)", + "- Front Center High (1 Channel)"] + + for i in range(len(ch_alloc_title)): + CH_ALLOC_CI = f"TSI_DP14_SRCCTS_CH_ALLOC{i + 1}" + CH_ALLOC_VAL = int(_data.get(CH_ALLOC_CI)) + + CH_COUNT_CI = f"TSI_DP14_SRCCTS_CHANNELS{i + 1}" + CH_COUNT_VAL = int(_data.get(CH_COUNT_CI)) + + configuration += "
{}:
".format(ch_alloc_title[i]) + configuration += "- Channel count: {}
".format(CH_COUNT_VAL) + + for j in range(len(ch_alloc_str)): + if CH_ALLOC_VAL >> j & 1: + configuration += "{}
".format(ch_alloc_str[j]) + + configuration += "
DSC Info:
" + dsc_version = int(_data.get("TSI_DP14_SRCCTS_DSC_VERSION")[0]) + configuration += "- DSC Version: {}.{}
".format((dsc_version >> 16) & 0xff, dsc_version & 0xff) + configuration += "- DSC Max. Slices: {}
".format( + value_from_enumeration_var(_data.get("TSI_DP14_SRCCTS_DSC_DUT_MAX_SLICE")[1], + _data.get("TSI_DP14_SRCCTS_DSC_DUT_MAX_SLICE")[0])) + + configuration += "
DSC Colorimetry:
" + configuration += "- RGB 8 bpc VESA
" + if int(_data.get("TSI_DP14_SRCCTS_DSC_COLOR_MODE_1")): + configuration += "- RGB 10 bpc VESA
" + if int(_data.get("TSI_DP14_SRCCTS_DSC_COLOR_MODE_2")): + configuration += "- RGB 12 bpc VESA
" + if int(_data.get("TSI_DP14_SRCCTS_DSC_COLOR_MODE_3")): + configuration += "- YCbCr 4:4:4 8 bpc CTA (ITU.709)
" + if int(_data.get("TSI_DP14_SRCCTS_DSC_COLOR_MODE_4")): + configuration += "- YCbCr 4:4:4 10 bpc CTA (ITU.709)
" + if int(_data.get("TSI_DP14_SRCCTS_DSC_COLOR_MODE_5")): + configuration += "- YCbCr 4:4:4 12 bpc CTA (ITU.709)
" + if int(_data.get("TSI_DP14_SRCCTS_DSC_COLOR_MODE_6")): + configuration += "- YCbCr 4:2:2 8 bpc CTA (ITU.709)
" + if int(_data.get("TSI_DP14_SRCCTS_DSC_COLOR_MODE_7")): + configuration += "- YCbCr 4:2:2 10 bpc CTA (ITU.709)
" + if int(_data.get("TSI_DP14_SRCCTS_DSC_COLOR_MODE_8")): + configuration += "- YCbCr 4:2:2 12 bpc CTA (ITU.709)
" + if int(_data.get("TSI_DP14_SRCCTS_DSC_COLOR_MODE_9")): + configuration += "- YCbCr 4:2:2 Simple 8 bpc CTA (ITU.709)
" + if int(_data.get("TSI_DP14_SRCCTS_DSC_COLOR_MODE_10")): + configuration += "- YCbCr 4:2:2 Simple 10 bpc CTA (ITU.709)
" + if int(_data.get("TSI_DP14_SRCCTS_DSC_COLOR_MODE_11")): + configuration += "- YCbCr 4:2:2 Simple 12 bpc CTA (ITU.709)
" + if int(_data.get("TSI_DP14_SRCCTS_DSC_COLOR_MODE_12")): + configuration += "- YCbCr 4:2:0 Simple 8 bpc CTA (ITU.709)
" + if int(_data.get("TSI_DP14_SRCCTS_DSC_COLOR_MODE_13")): + configuration += "- YCbCr 4:2:0 Simple 10 bpc CTA (ITU.709)
" + if int(_data.get("TSI_DP14_SRCCTS_DSC_COLOR_MODE_14")): + configuration += "- YCbCr 4:2:0 Simple 12 bpc CTA (ITU.709)
" + configuration += "
DSC resolutions to be tested:
" + + dsc_timings = ["1920x1080 @ 30Hz", "1920x1080 @ 60Hz", "1920x1080 @ 120Hz", "3840x2160 @ 30Hz", + "3840x2160 @ 60Hz", "3840x2160 @ 120Hz", "5120x2160 @ 30Hz", "5120x2160 @ 60Hz", + "5120x2160 @ 120Hz", "7680x4320 @ 30Hz", "7680x4320 @ 60Hz", "7680x4320 @ 100Hz"] + + for item in enumerate(dsc_timings): + if int(_data.get(f"TSI_DP14_SRCCTS_DSC_VMT_{item[0]}_TIMINGS")): + lane_count_link_rate = _data.get(f"TSI_DP14_SRCCTS_DSC_VMT_{item[0]}_LC_LR") + if lane_count_link_rate in [1 << 0, 1 << 1, 1 << 2, 1 << 3]: + n_lanes = "1" + elif lane_count_link_rate in [1 << 4, 1 << 5, 1 << 6, 1 << 7]: + n_lanes = "2" + else: + n_lanes = "4" + if lane_count_link_rate in [1 << 0, 1 << 4, 1 << 8]: + bit_rate = "1.62" + elif lane_count_link_rate in [1 << 1, 1 << 5, 1 << 9]: + bit_rate = "2.7" + elif lane_count_link_rate in [1 << 2, 1 << 6, 1 << 10]: + bit_rate = '5.4' + else: + bit_rate = "8.1" + configuration += "- {} with {} lanes(s) @ {} Gbps
".format(item[1], n_lanes, bit_rate) + + configuration += "
DisplayID test configuration:
" + configuration += "- Max HActive = {}
".format(_data.get("TSI_DP14_SRCCTS_MAX_STREAM_HACTIVE")) + configuration += "- Max VActive = {}
".format(_data.get("TSI_DP14_SRCCTS_MAX_STREAM_VACTIVE")) + configuration += "- Max Pixel Clock (kHz) = {}
".format( + _data.get("TSI_DP14_SRCCTS_MAX_STREAM_PIXEL_CLOCK")) + + if int(_data.get("TSI_DP14_SRCCTS_DUT_CAPS")[1][15].mask) & int( + _data.get("TSI_DP14_SRCCTS_DUT_CAPS")[0]): + configuration += "
DMT Timings:
" + dmt_timing_value = int(_data.get("TSI_DP14_SRCCTS_DMT_TIMING")[0]) + for _bit in _data.get("TSI_DP14_SRCCTS_DMT_TIMING")[1]: + if dmt_timing_value & int(_bit.mask): + configuration += "- {}
".format(_bit.description) + + if int(_data.get("TSI_DP14_SRCCTS_DUT_CAPS")[1][16].mask) & int( + _data.get("TSI_DP14_SRCCTS_DUT_CAPS")[0]): + configuration += "
CTA Timings:
" + cta_timing_value = int(_data.get("TSI_DP14_SRCCTS_CTA_TIMING")[0]) + for _bit in _data.get("TSI_DP14_SRCCTS_CTA_TIMING")[1]: + if cta_timing_value & int(_bit.mask): + configuration += "- {}
".format(_bit.description) + + if (int(_data.get("TSI_DP14_SRCCTS_DUT_CAPS")[1][17].mask) & int( + _data.get("TSI_DP14_SRCCTS_DUT_CAPS")[0])) or \ + (int(_data.get("TSI_DP14_SRCCTS_DUT_CAPS")[1][18].mask) & + _data.get("TSI_DP14_SRCCTS_DUT_CAPS")[0]): + configuration += "
CVT Timings:
" + cvt_timing_value = int(_data.get("TSI_DP14_SRCCTS_CVT_TIMING")[0]) + for _bit in _data.get("TSI_DP14_SRCCTS_CVT_TIMING")[1]: + if cvt_timing_value & int(_bit.mask): + configuration += "- {}
".format(_bit.description) + + configuration += "
Audio modes:
" + + for i in range(8): + audio_mode = int(_data.get(f"TSI_DP14_SRCCTS_TESTED_AUDIO_MODE_{i}")) + audio_type = ["Refer to Stream Header", "L-PCM", "AC-3", "MPEG-1", "MP3", "MPEG2", "AAC LC", "DTS", + "ATRAC", "One Bit Audio", "Enhanced AC-3", "DTS-HD", "MAT", "DST", "WMA Pro", + "Refer to Audio Coding Extension Type (CXT)"] + audio_sampling = ["Refer to Stream Header", "32 kHz", "44.1 kHz (CD)", "48 kHz", "88.2 kHz", "96 kHz", + "176.4 kHz", "192 kHz"] + audio_ch_count = ["Refer to Stream Header", "2 channels", "3 channels", "4 channels", "5 channels", + "6 channels", "7 channels", "8 channels"] + audio_sample_size = ["Refer to Stream Header", "16 bit", "20 bit", "24 bit"] + + if audio_mode: + configuration += "- Audio Mode {}: {}, {}, {} @ {}
".format(i, audio_type[ + audio_mode >> 4 & 0xF], + audio_ch_count[ + audio_mode & 0x7], + audio_sample_size[ + audio_mode >> 8 & 0x3], + audio_sampling[ + audio_mode >> 10 & 0x7]) + else: + configuration += f"- Audio Mode {i}: Disabled
" + + configuration += "
AdaptiveSync configuration:
" + as_value = _data.get("TSI_DP14_SRCCTS_AS_DUT_CAPS")[0] + for _bit in _data.get("TSI_DP14_SRCCTS_AS_DUT_CAPS")[1]: + if as_value & _bit.mask: + configuration += "- {}
".format(_bit.description) + + configuration += "- Adaptive-Sync range minimum refresh rate supported by the Source: {}
" \ + .format(value_from_enumeration_var(_data.get("TSI_DP14_SRCCTS_AS_RANGE_MIN_RATE")[1], + _data.get("TSI_DP14_SRCCTS_AS_RANGE_MIN_RATE")[0])) + + as_timings = ["1920x1080p", "2560x1080p", "2560x1440p", "3840x2160p", "4096x2160p", "5120x2160p", + "7680x4320p", "10240x4320p"] + + for item in enumerate(as_timings): + as_value = int(_data.get(f"TSI_DP14_SRCCTS_AS_RANGE_MAX_RATE_{item[0]}")) + if as_value: + configuration += "- {} maximum refresh rate: {}Hz
".format(item[1], as_value) + else: + configuration += "- {} is not supported
".format(item[1]) + + elif type_of_test == TestReportConfigType.Dp2_1_Source: + + val = _data.get("TSI_DP20_SRCCTS_DUT_DEBUG_CONF") + if val is not None and val[0] != 0: + configuration += "
Debug options:
" + for _bit in val[1]: + if val[0] & _bit.mask: + configuration += f" - {_bit.description}
" + configuration += "
" + + configuration += "Test timeout = {}ms
".format(_data.get("TSI_DP20_SRCCTS_TIMEOUT")) + configuration += "Long HPD Duration = {}ms

".format(_data.get("TSI_DP20_SRCCTS_LONG_HPD_PULSE")) + configuration += "DUT capability settings and flags:
" + configuration += "- Max lanes = {}
".format(_data.get("TSI_DP20_SRCCTS_MAX_LANES")[0]) + configuration += "- Max link rate = {}
".format( + value_from_enumeration_var(_data.get("TSI_DP20_SRCCTS_MAX_LINK_RATE")[1], + _data.get("TSI_DP20_SRCCTS_MAX_LINK_RATE")[0])) + + dut_caps_value = _data.get("TSI_DP20_SRCCTS_DUT_CAPS")[0] + for _bit in _data.get("TSI_DP20_SRCCTS_DUT_CAPS")[1]: + if dut_caps_value & _bit.mask: + configuration += "- {}
".format(_bit.description) + + dut_link_rates_value = _data.get("TSI_DP20_SRCCTS_DUT_LINK_RATES")[0] + for _bit in _data.get("TSI_DP20_SRCCTS_DUT_LINK_RATES")[1]: + if dut_link_rates_value & _bit.mask: + configuration += "- {}
".format(_bit.description) + + configuration += f"- Number of LTTPR devices: {_data.get('TSI_DP20_SRCCTS_LTTPR_DEVICE_COUNT')}
" + configuration += "- Number of minimum link bandwidth: {}
".format( + value_from_enumeration_var(_data.get("TSI_DP20_SRCCTS_MAX_LINK_BW_POLICY")[1], + _data.get("TSI_DP20_SRCCTS_MAX_LINK_BW_POLICY")[0])) + + configuration += "
Settings for Dynamic Refresh Rate with VBlank stretch with MSA_TIMING_PAR_IGNORED:
" + configuration += f"- HActive: {_data.get('TSI_DP20_SRCCTS_DRR_HACTIVE')} pixels
" + configuration += f"- VActive: {_data.get('TSI_DP20_SRCCTS_DRR_VACTIVE')} lines
" + configuration += f"- Pixel Clock: {_data.get('TSI_DP20_SRCCTS_DRR_PIXEL_CLOCK')} kHz
" + configuration += f"- Minimum Vertical Refresh Rate: {_data.get('TSI_DP20_SRCCTS_DRR_MIN_VRR')} Hz
" + configuration += f"- Maximum Vertical Refresh Rate: {_data.get('TSI_DP20_SRCCTS_DRR_MAX_VRR')} Hz
" + + configuration += "
Test automation flags and settings:
" + + ta_flags = ["Always ready", "EDID read", "Link Training end", "Active Video"] + ta_flags_value = _data.get("TSI_DP20_SRCCTS_DUT_TA")[0] + for bit in _data.get("TSI_DP20_SRCCTS_DUT_TA")[1]: + if bit.mask & ta_flags_value or bit.enumeration_variants is not None: + if bit.enumeration_variants is not None: + configuration += "- Event indicating DUT ready: {}
".format(ta_flags[(ta_flags_value >> 3) & 0x3]) + else: + configuration += "- {}
".format(bit.description) + + configuration += "
Audio format:
" + configuration += "- Minimum supported sample frequency: {}Hz
".format( + value_from_enumeration_var(_data.get("TSI_DP20_SRCCTS_MIN_SAMPLE_RATE")[1], + _data.get("TSI_DP20_SRCCTS_MIN_SAMPLE_RATE")[0])) + configuration += "- Maximum supported sample frequency: {}Hz
".format( + value_from_enumeration_var(_data.get("TSI_DP20_SRCCTS_MAX_SAMPLE_RATE")[1], + _data.get("TSI_DP20_SRCCTS_MAX_SAMPLE_RATE")[0])) + configuration += "- Audio test pattern: {}
". \ + format(value_from_enumeration_var(_data.get("TSI_DP20_SRCCTS_AUDIO_TEST_PATTERN")[1], + _data.get("TSI_DP20_SRCCTS_AUDIO_TEST_PATTERN")[0])) + configuration += "- Audio sample size: {}
". \ + format(value_from_enumeration_var(_data.get("TSI_DP20_SRCCTS_EDID_SAMPLE_SIZE")[1], + _data.get("TSI_DP20_SRCCTS_EDID_SAMPLE_SIZE")[0])) + + ch_alloc_title = ["Minimum channel configuration / Min. sample rate", + "Maximum channel configuration / Min. sample rate", + "Minimum channel configuration / Max. sample rate", + "Maximum channel configuration / Max. sample rate"] + + ch_alloc_str = [ + "- Front Left + Front Right (2 Channels)", + "- Low Frequency Effect (1 Channel)", + "- Front Center (1 Channel)", + "- Rear Left + Rear Right (2 Channels)", + "- Rear Center (1 Channel)", + "- Front Left Center + Front Right Center (2 Channels)", + "- Rear Left Center + Rear Right Center (2 Channels)", + "- Front Left Wide + Front Right Wide (2 Channels)", + "- Front Left High + Front Right High (2 Channels)", + "- Top Center (1 Channel)", + "- Front Center High (1 Channel)"] + + for i in range(len(ch_alloc_title)): + CH_ALLOC_CI = f"TSI_DP20_SRCCTS_CH_ALLOC{i + 1}" + CH_ALLOC_VAL = int(_data.get(CH_ALLOC_CI)) + + CH_COUNT_CI = f"TSI_DP20_SRCCTS_CHANNELS{i + 1}" + CH_COUNT_VAL = int(_data.get(CH_COUNT_CI)) + + configuration += "
{}:
".format(ch_alloc_title[i]) + configuration += "- Channel count: {}
".format(CH_COUNT_VAL) + + for j in range(len(ch_alloc_str)): + if CH_ALLOC_VAL >> j & 1: + configuration += "{}
".format(ch_alloc_str[j]) + + configuration += "
DSC Info:
" + dsc_version = int(_data.get("TSI_DP20_SRCCTS_DSC_VERSION")[0]) + configuration += "- DSC Version: {}.{}
".format((dsc_version >> 16) & 0xff, dsc_version & 0xff) + configuration += "- DSC Max. Slices: {}
".format( + value_from_enumeration_var(_data.get("TSI_DP20_SRCCTS_DSC_DUT_MAX_SLICE")[1], + _data.get("TSI_DP20_SRCCTS_DSC_DUT_MAX_SLICE")[0])) + + configuration += "
DisplayID test configuration:
" + configuration += "- Max HActive = {}
".format(_data.get("TSI_DP20_SRCCTS_MAX_STREAM_HACTIVE")) + configuration += "- Max VActive = {}
".format(_data.get("TSI_DP20_SRCCTS_MAX_STREAM_VACTIVE")) + configuration += "- Max Pixel Clock (kHz) = {}
".format( + _data.get("TSI_DP20_SRCCTS_MAX_STREAM_PIXEL_CLOCK")) + + if _data.get("TSI_DP20_SRCCTS_DUT_CAPS")[1][17].mask & _data.get("TSI_DP20_SRCCTS_DUT_CAPS")[0]: + configuration += "
DMT Timings:
" + dmt_timing_value = _data.get("TSI_DP20_SRCCTS_DMT_TIMING")[0] + for _bit in _data.get("TSI_DP20_SRCCTS_DMT_TIMING")[1]: + if dmt_timing_value & _bit.mask: + configuration += "- {}
".format(_bit.description) + + if _data.get("TSI_DP20_SRCCTS_DUT_CAPS")[1][18].mask & _data.get("TSI_DP20_SRCCTS_DUT_CAPS")[0]: + configuration += "
CTA Timings:
" + cta_timing_value = _data.get("TSI_DP20_SRCCTS_CTA_TIMING")[0] + for _bit in _data.get("TSI_DP20_SRCCTS_CTA_TIMING")[1]: + if cta_timing_value & _bit.mask: + configuration += "- {}
".format(_bit.description) + + if (_data.get("TSI_DP20_SRCCTS_DUT_CAPS")[1][19].mask & _data.get("TSI_DP20_SRCCTS_DUT_CAPS")[0]) or \ + (_data.get("TSI_DP20_SRCCTS_DUT_CAPS")[1][20].mask & _data.get("TSI_DP20_SRCCTS_DUT_CAPS")[0]): + configuration += "
CVT Timings:
" + cvt_timing_value = _data.get("TSI_DP20_SRCCTS_CVT_TIMING")[0] + for _bit in _data.get("TSI_DP20_SRCCTS_CVT_TIMING")[1]: + if cvt_timing_value & _bit.mask: + configuration += "- {}
".format(_bit.description) + + configuration += "
Audio modes:
" + + for i in range(8): + audio_mode = int(_data.get(f"TSI_DP20_SRCCTS_TESTED_AUDIO_MODE_{i}")) + audio_type = ["Refer to Stream Header", "L-PCM", "AC-3", "MPEG-1", "MP3", "MPEG2", "AAC LC", "DTS", + "ATRAC", "One Bit Audio", "Enhanced AC-3", "DTS-HD", "MAT", "DST", "WMA Pro", + "Refer to Audio Coding Extension Type (CXT)"] + audio_sampling = ["Refer to Stream Header", "32 kHz", "44.1 kHz (CD)", "48 kHz", "88.2 kHz", "96 kHz", + "176.4 kHz", "192 kHz"] + audio_ch_count = ["Refer to Stream Header", "2 channels", "3 channels", "4 channels", "5 channels", + "6 channels", "7 channels", "8 channels"] + audio_sample_size = ["Refer to Stream Header", "16 bit", "20 bit", "24 bit"] + + if audio_mode: + configuration += "- Audio Mode {}: {}, {}, {} @ {}
".format(i, audio_type[ + audio_mode >> 4 & 0xF], + audio_ch_count[ + audio_mode & 0x7], + audio_sample_size[ + audio_mode >> 8 & 0x3], + audio_sampling[ + audio_mode >> 10 & 0x7]) + else: + configuration += f"- Audio Mode {i}: Disabled
" + + configuration += "
AdaptiveSync configuration:
" + as_value = _data.get("TSI_DP20_SRCCTS_AS_DUT_CAPS")[0] + for _bit in _data.get("TSI_DP20_SRCCTS_AS_DUT_CAPS")[1]: + if as_value & _bit.mask: + configuration += "- {}
".format(_bit.description) + + configuration += "- Adaptive-Sync range minimum refresh rate supported by the Source: {}
" \ + .format(value_from_enumeration_var(_data.get("TSI_DP20_SRCCTS_AS_RANGE_MIN_RATE")[1], + _data.get("TSI_DP20_SRCCTS_AS_RANGE_MIN_RATE")[0])) + + as_timings = ["1920x1080p", "2560x1080p", "2560x1440p", "3840x2160p", "4096x2160p", "5120x2160p", + "7680x4320p", "10240x4320p"] + + for item in enumerate(as_timings): + as_value = int(_data.get(f"TSI_DP20_SRCCTS_AS_RANGE_MAX_RATE_{item[0]}")) + if as_value: + configuration += "- {} maximum refresh rate: {}Hz
".format(item[1], as_value) + else: + configuration += "- {} is not supported
".format(item[1]) + + colorimetry_map = {0: '-', 1: 8, 3: 10, 7: 12, 15: 16} + + def add_column_title(include_dsc : bool): + title = "" + style = 'style="padding: 10px; border: 1px solid black;"' + title_list = [f'Timing\n', + f'Timing vendors\n', + f'Colorimetry\n'] + timing_vendors = ['CTA', 'DMT', 'CVT', 'CVT RB1', 'CVT RB2', 'OVT'] + colorimetry = ["RGB", "YCbCr444", "YCbCr422", "YCbCr422Simple", "YCbCr420"] if include_dsc else [ "RGB", "YCbCr444", "YCbCr422", "YCbCr420" ] + + for title_item in title_list: + title += title_item + title += "\n" + + for timing_vendors_item in timing_vendors: + title += f'{timing_vendors_item}' + + for colorimetry_item in colorimetry: + title += f'{colorimetry_item}' + + title += "\n" + + return title + + def add_video_row(timing_info, tim_value): + style = 'align="center";' + row = f'{timing_info}' \ + f'{"+" if tim_value & 0x1 else "-"}' \ + f'{"+" if (tim_value >> 1) & 0x1 else "-"}' \ + f'{"+" if (tim_value >> 2) & 0x1 else "-"}' \ + f'{"+" if (tim_value >> 3) & 0x1 else "-"}' \ + f'{"+" if (tim_value >> 4) & 0x1 else "-"}' \ + f'{"+" if (tim_value >> 5) & 0x1 else "-"}' + row += f'{colorimetry_map.get((tim_value >> 12) & 0xF)}' \ + f'{colorimetry_map.get((tim_value >> 16) & 0xF)}' \ + f'{colorimetry_map.get((tim_value >> 20) & 0xF)}' \ + f'{colorimetry_map.get((tim_value >> 28) & 0xF)}' + return row + + def add_dsc_row(timing_info, tim_value): + style = 'align="center";' + row = f'{timing_info}' \ + f'{"+" if tim_value & 0x1 else "-"}' \ + f'{"+" if (tim_value >> 1) & 0x1 else "-"}' \ + f'{"+" if (tim_value >> 2) & 0x1 else "-"}' \ + f'{"+" if (tim_value >> 3) & 0x1 else "-"}' \ + f'{"+" if (tim_value >> 4) & 0x1 else "-"}' \ + f'{"+" if (tim_value >> 5) & 0x1 else "-"}' + row += f'{colorimetry_map.get((tim_value >> 12) & 0xF)}' \ + f'{colorimetry_map.get((tim_value >> 16) & 0xF)}' \ + f'{colorimetry_map.get((tim_value >> 20) & 0xF)}' \ + f'{colorimetry_map.get((tim_value >> 24) & 0xF)}' \ + f'{colorimetry_map.get((tim_value >> 28) & 0xF)}' + return row + + configuration += '
' + add_column_title(False) + + deprecated_video_modes = [ "TSI_DP20_SRCCTS_VMT_1920_1080_144", "TSI_DP20_SRCCTS_VMT_1920_1080_240", + "TSI_DP20_SRCCTS_VMT_3840_2160_30", "TSI_DP20_SRCCTS_VMT_3840_2160_240", + "TSI_DP20_SRCCTS_VMT_5120_2160_120", "TSI_DP20_SRCCTS_VMT_5120_2160_240", + "TSI_DP20_SRCCTS_VMT_7680_4320_120", "TSI_DP20_SRCCTS_VMT_10240_4320_30", + "TSI_DP20_SRCCTS_VMT_10240_4320_60"] + + for key in _data.keys(): + if key in deprecated_video_modes: + continue + if key.find("TSI_DP20_SRCCTS_VMT_") != -1: + value = _data.get(key) + _, h_active, w_active, rate = re.findall(r'\d+', key) + configuration += add_video_row(f"{h_active}x{w_active} @ {rate}Hz", value) + configuration += "

" + + configuration += '
' + add_column_title(True) + + for key in _data.keys(): + if key.find("TSI_DP20_SRCCTS_DSC_VMT_") != -1: + value = _data.get(key) + _, h_active, w_active, rate = re.findall(r'\d+', key) + configuration += add_dsc_row(f"{h_active}x{w_active} @ {rate}Hz", value) + configuration += "

" + + + elif type_of_test == TestReportConfigType.Dp1_4_Sink: + configuration = "Test timeout = {}ms
".format(_data.get("TSI_DP14_SINKCTS_TIMEOUT")) + if not int(_data.get("TSI_DP14_SINKCTS_DSC_SOURCE")[0]): + configuration += "
DSC resolutions to be tested:
" + dsc_timings = ["1920x1080 @ 30Hz", "1920x1080 @ 60Hz", "1920x1080 @ 120Hz", "3840x2160 @ 30Hz", + "3840x2160 @ 60Hz", "3840x2160 @ 120Hz", "5120x2160 @ 30Hz", "5120x2160 @ 60Hz", + "5120x2160 @ 120Hz", "7680x4320 @ 30Hz", "7680x4320 @ 60Hz", "7680x4320 @ 100Hz"] + + for item in enumerate(dsc_timings): + timing_flags = int(str(_data.get(f"TSI_DP14_SINKCTS_DSC_VM_T_{item[0]}")), 16) + if timing_flags: + configuration += "- {}{}{}{}
".format(item[1], " CTA" if timing_flags & 0x1 else "", + " RB1" if timing_flags & 0x2 else "", + " RB2" if timing_flags & 0x4 else "") + else: + configuration += "
DSC resolutions to be tested are defined by DUT EDID
" + + configuration += "
Sink DUT supports DSC Simple 422 CRC for {}
". \ + format(value_from_enumeration_var(_data.get("TSI_DP14_SINKCTS_SUPPORT_444CRC")[1], + _data.get("TSI_DP14_SINKCTS_SUPPORT_444CRC")[0])) + + if int(_data.get("TSI_DP14_SINKCTS_VISUAL_TEST_CHECK")[0]): + configuration += "Never skip Display ID test's visual video checks
" + else: + configuration += "Skip Display ID test's visual video checks if CRC available and matching
" + + if not int(_data.get("TSI_DP14_SINKCTS_PACKET_SOURCE")[0]): + configuration += "
Most packed resolutions to be tested:
" + video_timings = ["CVT 1280 x 800 @ 60p [RB1] 18bpp 99", "DMT 1280 x 768 @ 60p [RB1] 18bpp 95", + "DMT 800 x 600 @ 60.317p 30bpp 93", "DMT 1024 x 768 @ 60p 18bpp 90", + "DMT 1280 x 1024 @ 60p 24bpp 100", "DMT 1280 x 960 @ 60p 24bpp 100", + "DMT 1360 x 768 @ 60p 30bpp 99", "CVT 1280 x 800 @ 60p 30bpp 97", + "DMT 1400 x 1050 @ 60p [RB1] 24bpp 94", "DMT 1280 x 768 @ 60p 30bpp 92", + "CVT 1600 x 1200 @ 60p [RB1] 18bpp 90", "CVT 2048 x 1536 @ 60p [RB1] 24bpp 97", + "DMT 1792 x 1344 @ 60p 24bpp 95", "DMT 1600 x 1200 @ 60p 30bpp 94", + "CTA 1440 x 480 @ 59.94p 24bpp 100", "CTA 1440 x 576 @ 50p 24bpp 100", + "CTA 1920 x 1080 @ 60p 30bpp 86"] + + for item in video_timings: + configuration += "- {}
".format(item) + else: + configuration += "
Most packed resolutions to be tested are defined by DUT EDID
" + elif type_of_test == TestReportConfigType.Dp2_1_Sink: + configuration = "" + + val = _data.get("TSI_DP20_SINKCTS_DEBUG_CONF") + if val is not None and val[0] != 0: + configuration += "
Debug options:
" + for _bit in val[1]: + if val[0] & _bit.mask: + configuration += f" - {_bit.description}
" + configuration += "
" + + configuration += "Test timeout = {}ms
".format(_data.get("TSI_DP20_SINKCTS_TIMEOUT")) + if not int(_data.get("TSI_DP20_SINKCTS_DSC_SOURCE")[0]): + configuration += "
DSC resolutions to be tested:
" + video_modes_list = [[1920, 1080, 30], [1920, 1080, 60], [1920, 1080, 120], [1920, 1080, 144], + [1920, 1080, 240], + [3840, 2160, 30], [3840, 2160, 60], [3840, 2160, 120], [3840, 2160, 144], + [3840, 2160, 240], + [5120, 2160, 30], [5120, 2160, 60], [5120, 2160, 120], [5120, 2160, 144], + [5120, 2160, 240], + [7680, 4320, 30], [7680, 4320, 60], [7680, 4320, 120], [7680, 4320, 144], + [7680, 4320, 240], + [10240, 4320, 30], [10240, 4320, 60], [10240, 4320, 120], [10240, 4320, 144], + [10240, 4320, 240], + [15360, 8640, 30], [15360, 8640, 60], [15360, 8640, 120], [15360, 8640, 144], + [15360, 8640, 240]] + for vm in video_modes_list: + timing_flag = _data.get(f"TSI_DP20_SINKCTS_VMT_{vm[0]}_{vm[1]}") + bit_n = 0 + if vm[2] == 60: + bit_n = 4 + elif vm[2] == 120: + bit_n = 8 + elif vm[2] == 144: + bit_n = 13 + elif vm[2] == 240: + bit_n = 17 + + if vm[2] in [144, 240]: + rb1_value = (timing_flag >> bit_n) & 0x1 + rb2_value = (timing_flag >> (bit_n + 1)) & 0x1 + ovt_value = (timing_flag >> (bit_n + 2)) & 0x1 + if rb1_value or rb2_value or ovt_value: + configuration += f"- {vm[0]}x{vm[1]} @ {vm[2]}Hz" \ + f"{' RB1' if rb1_value else ''}" \ + f"{' RB2' if rb2_value else ''}" \ + f"{' OVT' if ovt_value else ''}
" + else: + cta_value = (timing_flag >> bit_n) & 0x1 + rb1_value = (timing_flag >> (bit_n + 1)) & 0x1 + rb2_value = (timing_flag >> (bit_n + 2)) & 0x1 + if rb1_value or rb2_value or cta_value: + configuration += f"- {vm[0]}x{vm[1]} @ {vm[2]}Hz" \ + f"{' CTA' if cta_value else ''}" \ + f"{' RB1' if rb1_value else ''}" \ + f"{' RB2' if rb2_value else ''}
" + else: + configuration += "
DSC resolutions to be tested are defined by DUT EDID
" + + if _data.get("TSI_DP20_SINKCTS_SUPPORT_444CRC")[0] == 0: + configuration += "
Sink DUT supports YCbCR 422 CRC for Simple 422 bitstream
" + elif _data.get("TSI_DP20_SINKCTS_SUPPORT_444CRC")[0] == 1: + configuration += "
Sink DUT supports YCbCR 444 CRC for Simple 422 bitstream
" + else: + configuration += "

" + + if int(_data.get("TSI_DP20_SINKCTS_VISUAL_TEST_CHECK")[0]): + configuration += "
Skip Display ID test's visual video checks if CRC available and matching
" + else: + configuration += "
Never skip Display ID test's visual video checks
" + + return configuration + + +def dp21_source_timing_table(data) -> str: + _data = copy_data(data) + colorimetry_map = {0: '-', 1: 8, 3: 10, 7: 12, 15: 16} + info = f"Timing {'Timing vendors':>35} {'Colorimetry':>40}\n" + info += f"{'CTA DMT CVT CVT-RB1 CVT-RB2 OVT':>50}" + info += f"{'RGB YCbCr444 YCbCr422 YCbCr422Simple YCbCr420':>50}\n" + + def shift(str_length) -> int: + if str_length == 14: + return 7 + elif str_length == 15: + return 6 + elif str_length == 16: + return 5 + elif str_length == 17: + return 4 + + def support(flag) -> str: + return "+" if flag else "-" + + def add_row(timing_info, tim_value): + row = f'{timing_info}' \ + f'{support(tim_value & 0x1):>{shift(len(timing_info))}}' \ + f'{support((tim_value >> 1) & 0x1):>4}' \ + f'{support((tim_value >> 2) & 0x1):>4}' \ + f'{support((tim_value >> 3) & 0x1):>6}' \ + f'{support((tim_value >> 4) & 0x1):>8}' \ + f'{support((tim_value >> 5) & 0x1):>6}' + row += f'{colorimetry_map.get((tim_value >> 12) & 0xF):>8}' \ + f'{colorimetry_map.get((tim_value >> 16) & 0xF):>7}' \ + f'{colorimetry_map.get((tim_value >> 20) & 0xF):>9}' \ + f'{colorimetry_map.get((tim_value >> 24) & 0xF):>12}' \ + f'{colorimetry_map.get((tim_value >> 28) & 0xF):>12}\n' + return row + + for key in _data.keys(): + if key.find("TSI_DP20_SRCCTS_VMT_") != -1: + value = _data.get(key) + _, h_active, w_active, rate = re.findall(r'\d+', key) + info += add_row(f"{h_active}x{w_active} @ {rate}Hz", value) + + return info diff --git a/UniTAP/dev/modules/dut_tests/test_group_params_types.py b/UniTAP/dev/modules/dut_tests/test_group_params_types.py new file mode 100644 index 0000000..51a77e8 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/test_group_params_types.py @@ -0,0 +1,346 @@ +import math +import sys +import os +from enum import IntEnum +from PIL import Image + +from ctypes import c_int, c_uint8, c_uint16, c_uint32, c_float, c_char_p, c_bool + +from UniTAP.libs.lib_uicl.uicl import UICL_GetRequiredBufferSize, UICL_Convert +from UniTAP.libs.lib_uicl.uicl_types import UICL_Image, UICL_ImageParameters, UICL_Colorspace, UICL_Packing, \ + UICL_Sampling, UICL_Colorimetry, UICL_ComponentOrder, UICL_Alignment, UICL_SUCCESS +from .test_utils import search_bit + + +class DataType(IntEnum): + Int = 0 + Double = 1 + String = 2 + Bool = 3 + Uint32 = 4 + ArrayU8 = 5 + ArrayU16 = 6 + ArrayU32 = 7 + PHYAddress = 8 + BitFlag = 9 + ArrayFromFilePath = 10 + FolderPath = 11 + BitList = 12 + + +dict_types = { + DataType.Int: c_int, + DataType.Double: c_float, + DataType.String: c_char_p, + DataType.Bool: c_bool, + DataType.Uint32: c_uint32, + DataType.ArrayU8: c_uint8, + DataType.ArrayU16: c_uint16, + DataType.ArrayU32: c_uint32, + DataType.PHYAddress: c_uint32, + DataType.BitFlag: c_bool, + DataType.ArrayFromFilePath: c_uint8, + DataType.FolderPath: c_char_p, + DataType.BitList: c_uint32 +} + + +class EnumVar: + + def __init__(self, enumeration_variants: str): + self.__available_values = [] + self.__str_present = [] + if enumeration_variants.find("#") != -1: + available_values = enumeration_variants.split("\n") + for item in available_values: + self.__available_values.append(item.split(" # ")[0]) + self.__str_present.append(item.split(" # ")[1]) + else: + self.__available_values = enumeration_variants.split("\n") + + @property + def available_values(self) -> list: + return self.__available_values + + @property + def str_presentation(self) -> list: + return self.__str_present + + +class BitField: + + def __init__(self, json_obj): + self.__description = json_obj.get('description') + self.__mask = int(json_obj.get('mask'), 16) + if 'enumerationVariants' in json_obj: + self.__enumeration_variants = EnumVar(json_obj.get('enumerationVariants')) + else: + self.__enumeration_variants = None + if 'defaultValue' in json_obj: + self.__default_value = json_obj.get('defaultValue') + else: + self.__default_value = None + + @property + def description(self): + return self.__description + + @property + def mask(self): + return self.__mask + + @property + def enumeration_variants(self): + return self.__enumeration_variants + + @description.setter + def description(self, value: str): + self.__description = value + + @mask.setter + def mask(self, value: int): + self.__mask = value + + @enumeration_variants.setter + def enumeration_variants(self, value: list): + self.__enumeration_variants = value + + +class Param: + + def __init__(self, json_obj): + self.__min_value = sys.float_info.min + self.__max_value = sys.float_info.max + self.__type = DataType.Int + self.__config_id = 0 + self.__config_id_name = "" + self.__name = "" + self.__default_value = None + self.__enumeration_variants = None + self.__bit_field_list = [] + + self.__process_value(json_obj) + + def _set_by_bitmask(self, value, bitmask_index): + if len(self.__bit_field_list) > bitmask_index: + mask = self.__bit_field_list[bitmask_index].mask + self.__default_value &= ~mask + self.__default_value |= mask & (value << search_bit(mask)) + + def _get_by_bitmask(self, bitmask_index: int, value_type=int): + if len(self.__bit_field_list) > bitmask_index: + mask = self.__bit_field_list[bitmask_index].mask + masked_value = self.__default_value & mask + return value_type(masked_value >> search_bit(mask)) + return 0 + + def _set_by_bit_number(self, state, value, bit_number): + if state: + self.__default_value |= (value << bit_number) + else: + self.__default_value &= ~(value << bit_number) + + def _get_by_bit_number(self, bit_number) -> bool: + return (self.__default_value >> bit_number) & 0x1 + + def clear(self): + self.__default_value = 0 + + def direct_set_def_value(self, default_value): + self.__default_value = default_value + + @property + def default_value(self): + return self.__default_value + + @default_value.setter + def default_value(self, default_value): + if self.__enumeration_variants is not None: + if str(default_value) in self.__enumeration_variants.available_values: + self.__default_value = default_value + else: + available_variants = '\n'.join(self.__enumeration_variants.available_values) + raise ValueError(f"Value {default_value} is not in available variants:\n {available_variants}.") + else: + self.__default_value = default_value + + @property + def min_value(self): + return self.__min_value + + @property + def max_value(self): + return self.__max_value + + @property + def config_id(self): + return self.__config_id + + @property + def config_id_name(self): + return self.__config_id_name + + @property + def name(self): + return self.__name + + @property + def value_type(self): + return self.__type + + @property + def enumeration_variants(self): + return self.__enumeration_variants + + @property + def bit_field_list(self): + return self.__bit_field_list + + @property + def data_length(self): + if isinstance(self.__default_value, (list, tuple, bytes, bytearray, str)): + return len(self.__default_value) + else: + return 1 + + def __str__(self): + return f"{self.default_value if self.data_length <= 50 else self.default_value[:50]}" + + def __process_value(self, json_obj): + if 'minValue' in json_obj: + self.__min_value = int(json_obj.get('minValue')) + if 'maxValue' in json_obj: + self.__max_value = int(json_obj.get('maxValue')) + if 'enumerationVariants' in json_obj: + self.__enumeration_variants = EnumVar(json_obj.get('enumerationVariants')) + if 'bitList' in json_obj: + bit_list = json_obj.get('bitList') + for item in bit_list: + self.__bit_field_list.append(BitField(item)) + + self.__config_id = int(json_obj.get('configId'), 16) + self.__type = DataType(json_obj.get('type')) + self.__name = json_obj.get('name') + self.__config_id_name = json_obj.get('id') + if self.__type in [DataType.Int, DataType.Uint32]: + self.__default_value = int(json_obj.get('defaultValue'), 16) \ + if json_obj.get('defaultValue').find("0x") != -1 \ + else int(json_obj.get('defaultValue')) + elif self.__type == DataType.BitList: + self.__default_value = int(json_obj.get('defaultValue'), 16) + elif self.__type == DataType.Bool: + self.__default_value = bool(json_obj.get('defaultValue')) + elif self.__type == DataType.Double: + self.__default_value = float(json_obj.get('defaultValue')) + else: + self.__default_value = json_obj.get('defaultValue') + + def __eq__(self, other): + return self.default_value == other.default_value + + +def check_have_dict_attribute(obj) -> bool: + try: + vars(obj) + return True + except TypeError: + return False + + +def get_param_list(param_obj) -> list: + param_list = [] + if check_have_dict_attribute(param_obj): + var_list = vars(param_obj) + else: + return param_list + + for item in var_list.keys(): + if isinstance(var_list.get(item), Param) or issubclass(type(var_list.get(item)), Param): + param_list.append(var_list.get(item)) + elif isinstance(var_list.get(item), list): + for elem in var_list.get(item): + if isinstance(elem, Param): + param_list.append(elem) + else: + param_list.extend(get_param_list(var_list.get(item))) + + return param_list + + +def param_by_ci_name(json_obj, name: str) -> Param: + for obj in json_obj: + if obj.get('id') == name: + return Param(obj) + return Param({}) + + +def read_image_file(full_filename: str) -> bytearray: + file_name, file_extension = os.path.splitext(full_filename) + if file_extension.lower() == '.bin': + with open(full_filename, 'rb') as file: + image_data = bytearray(file.read()) + return image_data + elif file_extension.lower() in [".png", '.jpg', ".ppm", '.bmp', '.tif', '.tiff']: + image = Image.open(full_filename) + image = image.convert('RGB') + image_data = bytearray(image.tobytes()) + return image_data + else: + return bytearray() + + +def uicl_image_preparation(data: bytearray, params): + ref_image = UICL_Image() + ref_param = UICL_ImageParameters() + + ref_param.Width = params.image_width + ref_param.Height = params.image_height + # VideoPixelTestElementFormat: RGB_16BPC = 20 + ref_param.Colorspace = UICL_Colorspace.Colorspace_RGB + ref_param.BitsPerColor = 8 + ref_param.ComponentOrder = UICL_ComponentOrder.Order_RGB + ref_param.Packing = UICL_Packing.Packing_Packed + ref_param.Alignment = UICL_Alignment.Alignment_MSB + # VideoPixelTestElementFormat: YCbCr444_161616 = 257 + ref_param.Sampling = UICL_Sampling.Sampling_444 + ref_param.IsFullRange = False + ref_param.Colorimetry = UICL_Colorimetry.Colorimetry_Unknown + ref_param.IsMonochrome = False + ref_image.Parameters = ref_param + ref_image.DataSize = UICL_GetRequiredBufferSize(ref_image) + ref_image.DataPtr = (c_uint8 * ref_image.DataSize)(*data) + + _dest_params = UICL_ImageParameters() + _dest_params.Width = params.image_width + _dest_params.Height = params.image_height + _dest_params.BitsPerColor = params.color_depth + _dest_params.Colorspace = UICL_Colorspace.Colorspace_RGB if params.element_format <= 20 else UICL_Colorspace.Colorspace_YCbCr + _dest_params.ComponentOrder = UICL_ComponentOrder.Order_RGB if params.element_format <= 20 else UICL_ComponentOrder.Order_YCbCr + _dest_params.Sampling = UICL_Sampling.Sampling_444 if params.element_format <= 257 else UICL_Sampling.Sampling_420 + _dest_params.Packing = UICL_Packing.Packing_Packed if params.element_format <= 257 else UICL_Packing.Packing_Planar + _dest_params.Alignment = UICL_Alignment.Alignment_LSB + _dest_params.IsFullRange = False + _dest_params.Colorimetry = UICL_Colorimetry.Colorimetry_Unknown if params.element_format <= 20 else UICL_Colorimetry.Colorimetry_ITU_R_BT601 + _dest_params.IsMonochrome = False + + _dest_image = UICL_Image() + _dest_image.Parameters = _dest_params + _dest_image.DataSize = UICL_GetRequiredBufferSize(_dest_image) + _dest_image.DataPtr = (c_uint8 * _dest_image.DataSize)() + + result = UICL_Convert(ref_image, _dest_image) + + del ref_image + del data + + if result < UICL_SUCCESS: + print(f"UICL Error {result}") + + _data = [] + + for i in range(_dest_image.DataSize): + _data.append(_dest_image.DataPtr[i]) + + del _dest_image + + return _data diff --git a/UniTAP/dev/modules/dut_tests/test_info.py b/UniTAP/dev/modules/dut_tests/test_info.py new file mode 100644 index 0000000..a47e6e9 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/test_info.py @@ -0,0 +1,315 @@ +import warnings +from enum import IntEnum +from typing import Optional, Union, List + +from .dut_default_params import * +from .report import generate_html_report, TestAdditionalInfo, TestResult + + +class TestStatusEnum(IntEnum): + Idle = 0 + Completed = 1 + InProgress = 2 + + +class TestGroupId(IntEnum): + """ + Class `TestGroupId` contains all possible variants of Test groups ID. + """ + UNKNOWN = -1 + AUDIO_TEST = 0x00 + DP_RX_ELECTRICAL = 0x01 + HDMI_RX_ELECTRICAL = 0x02 + DVI_RX_ELECTRICAL = 0x03 + CEC_FUNCTIONAL = 0x05 + DP_RX_CRC = 0x06 + DP_RX_SIMPLE_LT = 0x07 + DP_HDCP_CTS_AUTHENTICATION = 0x0A + HDMI_RX_CRC = 0x0B + USB_TYPE_C_ELECTRICAL = 0x0C + DP_RX_LL_CTS = 0x0E + DP_TX_LL_CTS = 0x0F + DP_TX_LL_CTS_DSC = 0x10 + DP_RX_LL_CTS_DSC = 0x11 + DP_2_1_RX_LL_CTS = 0x12 + DP_2_1_TX_LL_CTS = 0x13 + DP_2_1_LTTPR_CTS = 0x14 + DP_HDCP_CTS_1A = 0x26 + DP_HDCP_CTS_1B = 0x27 + DP_HDCP_CTS_2C = 0x28 + DP_HDCP_CTS_3A = 0x29 + DP_HDCP_CTS_3B = 0x2A + DP_HDCP_CTS_3C = 0x2B + DP_TX_DISPLAYID = 0x32 + DP_RX_DISPLAYID = 0x33 + HDMI_RX_VRR = 0x34 + HDMI_TX_VRR = 0x35 + DP_TX_ADAPTIVESYNC = 0x36 + DP_RX_ADAPTIVESYNC = 0x37 + DP_2_1_RX_LTTPR_CTS = 0x38 + DP_2_1_TX_LTTPR_CTS = 0x39 + DP_2_1_RX_DSC_CTS = 0x3A + DP_2_1_TX_DSC_CTS = 0x3B + DP_2_1_TX_DISPAYID = 0x3C + DP_2_1_RX_DISPAYID = 0x3D + DP_2_1_TX_ADAPTIVESYNC = 0x3E + DP_2_1_RX_ADAPTIVESYNC = 0x3F + HD_RX_DSC_CTS = 0x40 + HD_TX_DSC_CTS = 0x41 + HD_TX_CONTINUITY = 0x42 + HD_TX_CABLE_CHECK = 0x43 + PIXEL_VIDEO_TEST = 0x3E8 + HDR10_TEST = -2 + + @classmethod + def has_value(cls, value) -> bool: + return value in iter(cls) + + +class SubTestResultObject: + """ + Class `SubTestResultObject` contains information about testing of one test. + - Set and get Test result `test_result`. + - Set and get FW logs after testing `fw_logs`. + - Set and get Configuration information `config_info`. + - Set and get Error code `error_code`. + - Set and get Error logs `error_logs`. + - Set and get Test name `test_name`. + - Set and get Group name `group_name`. + """ + def __init__(self, group_name: str, debug: bool): + self.__test_result = TestResult.Unknown + self.__fw_logs = "" + self.__config_info = "" + self.__error_code = 0 + self.__error_logs = "" + self.__test_name = "" + self.__group_name = group_name + self.__test_delay = 0 + self.__debug = debug + self.__json_config_info = "" + + @property + def test_result(self) -> TestResult: + """ + Returns result after testing. + + Returns: + object of `TestResult` type + """ + return self.__test_result + + @test_result.setter + def test_result(self, test_result: TestResult): + """ + Set new test result. + + Args: + test_result (TestResult) + """ + self.__test_result = test_result + + @property + def fw_logs(self) -> str: + """ + Returns FW logs after testing. + + Returns: + object of str type + """ + return self.__fw_logs + + @fw_logs.setter + def fw_logs(self, fw_logs: str): + """ + Set new test result. + + Args: + fw_logs (str) + """ + self.__fw_logs = fw_logs + + @property + def config_info(self) -> str: + """ + Returns config info after testing. + + Returns: + object of str type + """ + return self.__config_info + + @config_info.setter + def config_info(self, config_info: str): + """ + Set new config info. + + Args: + config_info (str) + """ + self.__config_info = config_info + + @property + def error_code(self) -> int: + """ + Returns error code after testing. + + Returns: + object of int type + """ + return self.__error_code + + @error_code.setter + def error_code(self, error_code: int): + """ + Set new error code. + + Args: + error_code (int) + """ + self.__error_code = error_code + + @property + def error_logs(self) -> str: + """ + Returns error logs after testing. + + Returns: + object of str type + """ + return self.__error_logs + + @error_logs.setter + def error_logs(self, error_logs: str): + """ + Set new error logs. + + Args: + error_logs (str) + """ + self.__error_logs = error_logs + + @property + def test_name(self) -> str: + """ + Returns test name. + + Returns: + object of str type + """ + return self.__test_name + + @test_name.setter + def test_name(self, test_name: str): + """ + Set new test name. + + Args: + test_name (str) + """ + self.__test_name = test_name + + @property + def group_name(self) -> str: + """ + Returns test group name. + + Returns: + object of str type + """ + return self.__group_name + + @group_name.setter + def group_name(self, group_name: str): + """ + Set new test group name. + + Args: + group_name (str) + """ + self.__group_name = group_name + + @property + def test_delay(self) -> int: + """ + Returns test delay. + + Returns: + object of int type + """ + return self.__test_delay + + @test_delay.setter + def test_delay(self, test_delay: int): + """ + Set new test delay. + + Args: + test_delay (int) + """ + self.__test_delay = test_delay + + @property + def debug(self) -> bool: + """ + Returns debug test state (if you want to use special debug flags, your report will not valid). + + Returns: + object of bool type + """ + return self.__debug + + @property + def json_config_info(self): + """ + Returns json test config info. + + Returns: + object of base64 str type + """ + return self.__json_config_info + + @json_config_info.setter + def json_config_info(self, json_config_info): + """ + Set new json config info. + + Args: + json_config_info: base64 str type + """ + self.__json_config_info = json_config_info + + +class TestResultObject: + """ + Class `TestResultObject` allows get test results. + - Test result of selected test `test_result`. + - All test results `all_test_results`. + """ + def __init__(self, results: Union[SubTestResultObject, List[SubTestResultObject]]): + if isinstance(results, SubTestResultObject): + self.__sub_tests = [results] + else: + self.__sub_tests = results + + def test_result(self, index: int) -> Optional[SubTestResultObject]: + """ + Test result of selected test. + + Args: + index (int) + + Returns: + object of `SubTestResultObject`|None type + """ + if index < 0 or index > len(self.__sub_tests) - 1: + warnings.warn("Incorrect test result object index. Please, select index again.") + return None + return self.__sub_tests[index] + + def all_test_results(self) -> list: + """ + Returns: + object of list type + """ + return self.__sub_tests diff --git a/UniTAP/dev/modules/dut_tests/test_utils.py b/UniTAP/dev/modules/dut_tests/test_utils.py new file mode 100644 index 0000000..efbe1db --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/test_utils.py @@ -0,0 +1,190 @@ +import json +import base64 +from enum import IntEnum +from typing import Union + + +class CMTFormat(IntEnum): + RGB_LEGACY = 0 + XVYCC_422_BT601 = 1 + XVYCC_444_BT601 = 2 + RGB_WIDE_FIXED = 3 + RGB_CEA = 4 + YCBCR_422_BT601 = 5 + YCBCR_444_BT601 = 6 + DCI_P3 = 7 + XVYCC_422_BT709 = 9 + XVYCC_444_BT709 = 10 + RGB_WIDE_FLOAT = 11 + RGB_ADOBE = 12 + YCBCR_422_BT709 = 13 + YCBCR_444_BT709 = 14 + COLOR_PROFILE = 15 + Y_ONLY = 16 + RAW = 17 + + +class CMTVSCFormat(IntEnum): + SRGB = 0x00 + RGB_WIDE_FIXED_VCS = 0x01 + RGB_WIDE_FLOAT_VCS = 0x02 + RGB_ADOBE_V = 0x03 + RGB_DCI_P3 = 0x04 + RGB_CUSTOM_PROFILE = 0x05 + RGB_BT2020 = 0x06 + + YCBCR_444_BT601_VCS = 0x10 + YCBCR_444_BT709_VCS = 0x11 + YCBCR_444_XVYCC601 = 0x12 + YCBCR_444_XVYCC709 = 0x13 + YCBCR_444_SYCC601 = 0x14 + YCBCR_444_ADOBE_YCC601 = 0x15 + YCBCR_444_BT2020_YCCBCCRC = 0x16 + YCBCR_444_BT2020_YCBCR = 0x17 + + YCBCR_422_BT601_VCS = 0x20 + YCBCR_422_BT709_VCS = 0x21 + YCBCR_422_XVYCC601 = 0x22 + YCBCR_422_XVYCC709 = 0x23 + YCBCR_422_SYCC601 = 0x24 + YCBCR_422_ADOBE_YCC601 = 0x25 + YCBCR_422_BT2020_YCCBCCRC = 0x26 + YCBCR_422_BT2020_YCBCR = 0x27 + + YCBCR_420_BT601 = 0x30 + YCBCR_420_BT709 = 0x31 + YCBCR_420_XVYCC601 = 0x32 + YCBCR_420_XVYCC709 = 0x33 + YCBCR_420_SYCC601 = 0x34 + YCBCR_420_ADOBE_YCC601 = 0x35 + YCBCR_420_BT2020_YCCBCCRC = 0x36 + YCBCR_420_BT2020_YCBCR = 0x37 + + Y_ONLY_DICOM = 0x40 + RAW_CUSTOM_PROFILE = 0x50 + + YCBCR_SIMPLE_422_BT601 = 0x60 + YCBCR_SIMPLE_422_BT709 = 0x61 + YCBCR_SIMPLE_422_XVYCC601 = 0x62 + YCBCR_SIMPLE_422_XVYCC709 = 0x63 + YCBCR_SIMPLE_422_SYCC601 = 0x64 + YCBCR_SIMPLE_422_ADOBE_YCC601 = 0x65 + YCBCR_SIMPLE_422_BT2020_YCCBCCRC = 0x66 + YCBCR_SIMPLE_422_BT2020_YCBCR = 0x67 + + +class CMTBitDepth(IntEnum): + BPC_6 = 0 + BPC_8 = 1 + BPC_10 = 2 + BPC_12 = 3 + BPC_16 = 4 + + # BPP_RAW_6 = 1 + # BPP_RAW_7 = 2 + # BPP_RAW_8 = 3 + # BPP_RAW_10 = 4 + # BPP_RAW_12 = 5 + # BPP_RAW_14 = 6 + # BPP_RAW_16 = 7 + + +class CMTVSCBitDepth(IntEnum): + BPC_RGB_6 = 0 + BPC_RGB_8 = 1 + BPC_RGB_10 = 2 + BPC_RGB_12 = 3 + BPC_RGB_16 = 4 + + BPC_YCBCR_8 = 1 + BPC_YCBCR_10 = 2 + BPC_YCBCR_12 = 3 + BPC_YCBCR_16 = 4 + + # BPP_RAW_6 = 1 + # BPP_RAW_7 = 2 + # BPP_RAW_8 = 3 + # BPP_RAW_10 = 4 + # BPP_RAW_12 = 5 + # BPP_RAW_14 = 6 + # BPP_RAW_16 = 7 + + +def make_cf(color_format: CMTFormat, bpc: CMTBitDepth): + return (((color_format & 15) << 1) | ((bpc & 7) << 5)) + + +def make_vsc_cf(color_format: CMTVSCFormat, bpc: CMTVSCBitDepth): + return ((1 << 14) | ((color_format << 16) | ((bpc & 7) << 24))) + + +class TestReportConfigType(IntEnum): + Another = -1 + Dp1_4_Source = 0 + Dp2_1_Source = 1 + Dp1_4_Sink = 2 + Dp2_1_Sink = 3 + Dp2_1_Lttpr = 4 + + +class TestResult(IntEnum): + Unknown = -1 + Passed = 0 + Failed = 1 + Skipped = 2 + Aborted = 3 + + +def contains(params, config_id): + for item in params: + if item.config_id_name == config_id: + return True + return False + + +def is_need_to_update_test_configuration(params): + if contains(params, "TSI_DP14_SRCCTS_TIMEOUT"): + return TestReportConfigType.Dp1_4_Source + elif contains(params, "TSI_DP20_SRCCTS_TIMEOUT"): + return TestReportConfigType.Dp2_1_Source + elif contains(params, "TSI_DP14_SINKCTS_TIMEOUT"): + return TestReportConfigType.Dp1_4_Sink + elif contains(params, "TSI_DP20_SINKCTS_TIMEOUT"): + return TestReportConfigType.Dp2_1_Sink + else: + return TestReportConfigType.Another + + +def search_bit(mask): + count = 0 + if mask: + while not (mask & 1): + mask >>= 1 + count += 1 + return count + + +def update_default_value(value, default_value, mask): + old_value = default_value + offset = search_bit(mask) + value = (value << offset) & mask + old_value &= ~mask + old_value |= value + return old_value + + +def convert_to_cdf_json_base64(params, test_group): + group_info = str(hex(test_group)).replace("0x", "").zfill(8) + result_json = {"Header_Type": f"Group_0x{group_info}_Config"} + + for item in params: + result_json.update({item.config_id_name: + f"{int(item.default_value) if isinstance(item.default_value, bool) else item.default_value}"}) + + temp = json.dumps(result_json, indent=4) + + sample_string_bytes = temp.encode() + base64_bytes = base64.b64encode(sample_string_bytes) + base64_string = base64_bytes.decode() + + return base64_string diff --git a/UniTAP/dev/modules/memory_manager/__init__.py b/UniTAP/dev/modules/memory_manager/__init__.py new file mode 100644 index 0000000..5cd9046 --- /dev/null +++ b/UniTAP/dev/modules/memory_manager/__init__.py @@ -0,0 +1 @@ +from .memory_manager import MemoryManager diff --git a/UniTAP/dev/modules/memory_manager/memory_manager.py b/UniTAP/dev/modules/memory_manager/memory_manager.py new file mode 100644 index 0000000..7f6a196 --- /dev/null +++ b/UniTAP/dev/modules/memory_manager/memory_manager.py @@ -0,0 +1,46 @@ +from typing import List +from ctypes import c_uint64, c_uint32, c_uint8 +from enum import IntEnum + +from UniTAP.libs.lib_tsi.tsi_types import TSI_R_MEMORY_SIZE, TSI_MEMORY_LAYOUT,\ + TSI_MEMORY_BLOCK_INDEX, TSI_MEMORY_RESET_W, TSI_MEMORY_WRITE_W +from UniTAP.libs.lib_tsi.tsi import TSIX_TS_GetConfigItem, TSIX_TS_SetConfigItem +from UniTAP.libs.lib_tsi.tsi_io import DeviceIO + + +class MemoryManager: + class MemoryOwner(IntEnum): + MO_PatternGenerator = 0 + MO_AudioGenerator = 1 + MO_Events = 2 + + MAX_BLOCK_COUNT = 4 + + DEFAULT_MEMOTY_ALLOCATION = [534600704, 534600704, 534600704] + RESERVED_MEMORY_BYTES = 8192 * (5120 + 1) * 4 + 0x80000 + + def __init__(self, io: DeviceIO): + self.__io = io + + def get_total_memory(self) -> int: + result = self.__io.get(TSI_R_MEMORY_SIZE, c_uint64)[1] + return result + + def set_memory_layout(self, layout: List[int]): + self.__io.set(TSI_MEMORY_LAYOUT, layout, c_uint64, data_count=len(layout)) + + def get_memory_layout(self) -> List[int]: + return self.__io.get(TSI_MEMORY_LAYOUT, c_uint64, self.MAX_BLOCK_COUNT) + + def set_memory_block_index(self, index: MemoryOwner): + self.__io.set(TSI_MEMORY_BLOCK_INDEX, index.value) + + def make_default(self): + self.__io.set(TSI_MEMORY_LAYOUT, self.DEFAULT_MEMOTY_ALLOCATION, c_uint64, + data_count=len(self.DEFAULT_MEMOTY_ALLOCATION)) + + def reset(self): + self.__io.set(TSI_MEMORY_RESET_W, 0) + + def memory_write(self, data, size): + self.__io.set(TSI_MEMORY_WRITE_W, bytearray(data), data_type=c_uint8, data_count=size) diff --git a/UniTAP/dev/modules/opf/__init__.py b/UniTAP/dev/modules/opf/__init__.py new file mode 100644 index 0000000..06dc6d1 --- /dev/null +++ b/UniTAP/dev/modules/opf/__init__.py @@ -0,0 +1,2 @@ +from UniTAP.dev.modules.opf.handlers import OpfHandlerInternal, OpfHandlerDefault, OPFDialogAnswer +from UniTAP.dev.modules.opf.handler import OperatorFeedbackHandler diff --git a/UniTAP/dev/modules/opf/handler.py b/UniTAP/dev/modules/opf/handler.py new file mode 100644 index 0000000..ac658bd --- /dev/null +++ b/UniTAP/dev/modules/opf/handler.py @@ -0,0 +1,66 @@ +import platform +import warnings + +from UniTAP.libs.lib_tsi.tsi_types import TSI_OPF_CALLBACK_STRUCT, TSI_OPF_RETURN_CODE_ABORT +from UniTAP.libs.lib_tsi.tsi_io import DeviceIO +from UniTAP.utils import tsi_logging as logging +from .handlers import OpfHandlerBase, OpfHandlerDefault + +if platform.system() == 'Windows': + from ctypes import c_int, POINTER, c_void_p, WINFUNCTYPE + + OPF_CALLBACK_TYPE = WINFUNCTYPE(c_int, POINTER(TSI_OPF_CALLBACK_STRUCT), c_void_p) +else: + from ctypes import c_int, POINTER, c_void_p, CFUNCTYPE + + OPF_CALLBACK_TYPE = CFUNCTYPE(c_int, POINTER(TSI_OPF_CALLBACK_STRUCT), c_void_p) + + +class OperatorFeedbackHandler: + """ + Class `OperatorFeedbackHandler` helps to do required actions during DUT tests. Contains object of `OpfHandlerBase`, + that can be overridden. + - `handler` - set and get OPF Handler. `OpfHandlerBase` does OPF number 19 and 103, + `OpfHandlerInternal` does all OPF, `OpfHandlerDefault` does overridden OPF by user. + """ + + def __init__(self, device_io: DeviceIO): + global OPF_CALLBACK_TYPE + self.__io = device_io + self.__tsi_callback = OPF_CALLBACK_TYPE(self.__get_callback()) + self.__io.set_opf_callback(self.__tsi_callback) + + self.__handler = OpfHandlerDefault() + + def __get_callback(self): + def ofp_impl(_struct: POINTER(TSI_OPF_CALLBACK_STRUCT), context_ptr: c_void_p): + opf_struct: TSI_OPF_CALLBACK_STRUCT = _struct.contents + logging.debug(f"Received OPF dialog with ID: {opf_struct.id()}, Session: {opf_struct.session_id()}.") + + try: + return self.handler.handle(opf_struct.id(), + opf_struct.write_opf_result, + opf_struct.title(), + opf_struct.request1(), + opf_struct.request2(), + opf_struct.parameters()) + except BaseException as e: + warnings.warn(f"OPF Dialog {opf_struct.id()} was aborted due an exception. Exception: {e}") + return opf_struct.write_opf_result(TSI_OPF_RETURN_CODE_ABORT) + + return ofp_impl + + @property + def handler(self) -> OpfHandlerBase: + """ + + Return current OPF handler. Can be overriden in `set` method. + + Returns: + object of `OpfHandlerBase` type + """ + return self.__handler + + @handler.setter + def handler(self, value): + self.__handler = value diff --git a/UniTAP/dev/modules/opf/handlers/__init__.py b/UniTAP/dev/modules/opf/handlers/__init__.py new file mode 100644 index 0000000..4ed016a --- /dev/null +++ b/UniTAP/dev/modules/opf/handlers/__init__.py @@ -0,0 +1,3 @@ +from .base import OPFDialogAnswer, OpfHandlerBase +from .default import OpfHandlerDefault +from .internal import OpfHandlerInternal diff --git a/UniTAP/dev/modules/opf/handlers/base.py b/UniTAP/dev/modules/opf/handlers/base.py new file mode 100644 index 0000000..54527de --- /dev/null +++ b/UniTAP/dev/modules/opf/handlers/base.py @@ -0,0 +1,51 @@ +import abc +from typing import Callable +from enum import IntEnum + +from .opf_functions import OPFFunctions, logging, OPFDialogAnswer +from UniTAP.dev.modules.opf.parsers import OPFParametersParser + + +class WrongOPFFunctionSignature(Exception): + pass + + +class OpfHandlerBase: + """ + Class `OpfHandlerBase` allows working with functions that helps to do required actions during DUT tests. + - Use DSC content library `set_dsc_content_library`. If DSC image does not exist, it will be generated. + """ + __FUNCTIONS_MAP = { + 19: OPFFunctions.opf_19_handler, + 103: OPFFunctions.opf_103_handler, + } + + def __init__(self): + self.__parser = OPFParametersParser() + self.__tx = None + self.__rx = None + self.__callback_before_handling = {} + self._dsc_content_library_path = "" + + def _set_roles(self, role_tx, role_rx): + self.__tx = role_tx + self.__rx = role_rx + + def set_dsc_content_library(self, dsc_content_library_path: str = ""): + """ + Set new path to DSC content library folder. + If path is empty, content library will not use (all images will be generated into OPF functions without saving). + + Arguments: + dsc_content_library_path (`str`) - full path to DSC content library folder. + """ + self._dsc_content_library_path = dsc_content_library_path + + def handle(self, opf_id, reply_answer, *args) -> OPFDialogAnswer: + opf_params = self.__parser.parse(opf_id, *args) + + OPFFunctions.dsc_content_library_path = self._dsc_content_library_path + return reply_answer(self.__FUNCTIONS_MAP.get(opf_id)(self.__tx, self.__rx, *opf_params)) + + def _is_opf_processed(self, opf_id: int): + return self.__FUNCTIONS_MAP.get(opf_id) is not None diff --git a/UniTAP/dev/modules/opf/handlers/default.py b/UniTAP/dev/modules/opf/handlers/default.py new file mode 100644 index 0000000..cc53dd0 --- /dev/null +++ b/UniTAP/dev/modules/opf/handlers/default.py @@ -0,0 +1,46 @@ +import inspect +import warnings +from typing import Callable + +from UniTAP.dev.modules.opf.handlers.base import OpfHandlerBase, OPFDialogAnswer,\ + WrongOPFFunctionSignature +from UniTAP.dev.modules.opf.parsers import OPFParametersParser + + +class OpfHandlerDefault(OpfHandlerBase): + """ + Class `OpfHandlerDefault` inherit functionality from `OpfHandlerBase` and allows overriding opf functions. + Function `assign_function_for_opf_id` allows doing it. + """ + def __init__(self): + super().__init__() + self.__user_callback_dict = {} + self.__parser = OPFParametersParser() + + def handle(self, opf_id, reply_answer, *args) -> OPFDialogAnswer: + base_opf_handler = super()._is_opf_processed(opf_id) + user_opf_handler = self.__user_callback_dict.get(opf_id, None) is not None + if not user_opf_handler and not base_opf_handler: + warnings.warn(f"Test requested operator feedback dialog with id: {opf_id}, but user " + f"function was not provided. Test will be aborted.") + return reply_answer(OPFDialogAnswer.ABORT) + + if user_opf_handler: + opf_params_for_user = self.__parser.parse(opf_id, *args) + return reply_answer(self.__user_callback_dict.get(opf_id, None)(*opf_params_for_user)) + else: + return super().handle(opf_id, reply_answer, *args) + + def assign_function_for_opf_id(self, opf_id: int, func: Callable): + """ + Assign new function for doing required actions from OPF. + + Arguments: + opf_id (`int`) - id of OPF. + func (`Callable`) - object of function + """ + signature = inspect.getfullargspec(func) + if signature.annotations['return'] != OPFDialogAnswer: + raise WrongOPFFunctionSignature(f"Function MUST declare {OPFDialogAnswer} " + f"as return value") + self.__user_callback_dict[opf_id] = func diff --git a/UniTAP/dev/modules/opf/handlers/internal.py b/UniTAP/dev/modules/opf/handlers/internal.py new file mode 100644 index 0000000..aae2804 --- /dev/null +++ b/UniTAP/dev/modules/opf/handlers/internal.py @@ -0,0 +1,110 @@ +import inspect +from typing import Callable + +from UniTAP.dev.modules.opf.handlers.base import OpfHandlerBase, WrongOPFFunctionSignature +from UniTAP.dev.modules.opf.parsers import OPFParametersParser +from .opf_functions import * + + +class OpfHandlerInternal(OpfHandlerBase): + + __FUNCTIONS_MAP = { + 1: OPFFunctions.opf_1_handler, + 2: OPFFunctions.opf_2_handler, + 3: OPFFunctions.opf_3_handler, + 4: OPFFunctions.opf_4_handler, + 5: OPFFunctions.opf_5_handler, + 6: OPFFunctions.opf_6_handler, + 7: OPFFunctions.opf_7_handler, + 8: OPFFunctions.opf_8_handler, + 9: OPFFunctions.opf_9_handler, + 10: OPFFunctions.opf_10_handler, + 11: OPFFunctions.opf_11_handler, + 12: OPFFunctions.opf_12_handler, + 13: OPFFunctions.opf_13_handler, + 14: OPFFunctions.opf_14_handler, + 15: OPFFunctions.opf_15_handler, + 16: OPFFunctions.opf_16_handler, + 17: OPFFunctions.opf_17_handler, + 18: OPFFunctions.opf_18_handler, + 19: OPFFunctions.opf_19_handler, + 20: OPFFunctions.opf_20_handler, + 21: OPFFunctions.opf_21_handler, + 101: OPFFunctions.opf_101_handler, + 102: OPFFunctions.opf_102_handler, + 103: OPFFunctions.opf_103_handler, + 104: OPFFunctions.opf_104_handler, + 105: OPFFunctions.opf_105_handler, + 106: OPFFunctions.opf_106_handler, + 107: OPFFunctions.opf_107_handler, + 120: OPFFunctions.opf_120_handler, + 121: OPFFunctions.opf_121_handler, + 122: OPFFunctions.opf_122_handler, + 123: OPFFunctions.opf_123_handler, + 140: OPFFunctions.opf_140_handler, + 141: OPFFunctions.opf_141_handler, + 142: OPFFunctions.opf_142_handler, + 143: OPFFunctions.opf_143_handler, + 144: OPFFunctions.opf_144_handler, + 145: OPFFunctions.opf_145_handler, + 150: OPFFunctions.opf_150_handler, + 161: OPFFunctions.opf_161_handler + } + + __FUNCTIONS_MAP_AFTER = { + 1: OPFFunctions.opf_1_after_handler + } + + def __init__(self, port_tx, port_rx): + super().__init__() + self.__parser = OPFParametersParser() + self.__tx = port_tx + self.__rx = port_rx + self.__callback_before_handling = {} + self.__callback_after_handling = {} + + def handle(self, opf_id, reply_answer, *args) -> OPFDialogAnswer: + opf_params = self.__parser.parse(opf_id, *args) + user_opf_before_callback = self.__callback_before_handling.get(opf_id, None) + user_opf_after_callback = self.__callback_after_handling.get(opf_id, None) + internal_opf_after_callback = self.__FUNCTIONS_MAP_AFTER.get(opf_id, None) + + # + # Callback before main callback + # + if user_opf_before_callback is not None: + opf_before_result = user_opf_before_callback(*opf_params) + # Interrupt OPF in case of fail "before" OPF + if opf_before_result not in [OPFDialogAnswer.PASS, OPFDialogAnswer.PROCEED]: + return reply_answer(opf_before_result) + + # + # Main callback + # + OPFFunctions.dsc_content_library_path = self._dsc_content_library_path + opf_internal_result = self.__FUNCTIONS_MAP.get(opf_id)(self.__tx, self.__rx, *opf_params) + reply_answer(opf_internal_result) + + # + # Callback after main callback + # + if user_opf_after_callback is not None: + user_opf_after_callback(*opf_params) + elif internal_opf_after_callback is not None: + internal_opf_after_callback(self.__tx, self.__rx, *opf_params) + + return opf_internal_result + + def assign_function_before_handling_opf_id(self, opf_id: int, func: Callable): + signature = inspect.getfullargspec(func) + if signature.annotations['return'] != OPFDialogAnswer: + raise WrongOPFFunctionSignature(f"Function MUST declare {OPFDialogAnswer} " + f"as return value") + self.__callback_before_handling[opf_id] = func + + def assign_function_after_handling_opf_id(self, opf_id: int, func: Callable): + signature = inspect.getfullargspec(func) + if signature.annotations['return'] != OPFDialogAnswer: + raise WrongOPFFunctionSignature(f"Function MUST declare {OPFDialogAnswer} " + f"as return value") + self.__callback_after_handling[opf_id] = func diff --git a/UniTAP/dev/modules/opf/handlers/opf_functions.py b/UniTAP/dev/modules/opf/handlers/opf_functions.py new file mode 100644 index 0000000..c713319 --- /dev/null +++ b/UniTAP/dev/modules/opf/handlers/opf_functions.py @@ -0,0 +1,1390 @@ +import copy +import re +import threading +import time +import warnings +import UniTAP +from UniTAP.dev.modules.dut_tests.test_info import TestGroupId +from UniTAP.libs.lib_uicl.uicl import UICL_GeneratePattern, UICL_GeneratePattern_3Tap +from UniTAP.utils import tsi_logging as logging +from UniTAP.dev.ports.modules.link.dp.link_tx_types import LinkConfig +from UniTAP.common.audio_mode import AudioMode +from UniTAP.dev.ports.modules.vtg import * +from UniTAP.utils.uicl_api import * +from UniTAP.dev.ports.modules.edid import MainBlockType, AdditionalBlockType +from enum import IntEnum +from UniTAP.utils.function_wrapper import function_scheduler +from UniTAP.libs.lib_dscl.dscl import DSC_TOOLS_FOLDER, DSCL_ExtractPPSFromData, DSCL_Encode, DSCL_Decode +from UniTAP.libs.lib_dscl.dscl_utils import calculate_slice_size, dscl_image_to_dsc_vf +from UniTAP.libs.lib_pdl.pdl import generate_pattern_as_vf, PatternType +from UniTAP.libs.lib_tsi.tsi_types import TSI_MEMORY_WRITE_W, TSI_MEMORY_BLOCK_INDEX, TSI_MEMORY_LAYOUT, \ + TSI_DP20_SINKCTS_SUPPORT_444CRC, TSI_DP14_SINKCTS_SUPPORT_444CRC +from UniTAP.libs.lib_tsi.tsi_private_types import TSI_DSC_MEMORY_BLOCK, TSI_DSC_DATA_SIZE, \ + TSI_DSC_TX_CRC, TSI_DPRX_DSC_TEST_CRC, TSI_SELECT_SUITE +from UniTAP.libs.lib_tsi.tsi import TSIX_TS_SetConfigItem, TSIX_TS_GetConfigItem +from ctypes import c_uint64, c_uint32 +from UniTAP.libs.lib_tsi import TSI_OPF_RETURN_CODE_ABORT, TSI_OPF_RETURN_CODE_PASS, \ + TSI_OPF_RETURN_CODE_PROCEED, TSI_OPF_RETURN_CODE_FAIL, TSI_OPF_RETURN_CODE_AUTO_CLOSED + + +class OPFDialogAnswer(IntEnum): + ABORT = TSI_OPF_RETURN_CODE_ABORT + PASS = TSI_OPF_RETURN_CODE_PASS + PROCEED = TSI_OPF_RETURN_CODE_PROCEED + FAIL = TSI_OPF_RETURN_CODE_FAIL + NOTHING = TSI_OPF_RETURN_CODE_AUTO_CLOSED + + +class OPFFunctions: + dict_color_formats = {"RGB": ColorInfo.ColorFormat.CF_RGB, + "YCbCr 4:2:2": ColorInfo.ColorFormat.CF_YCbCr_422, + "YCbCr 4:4:4": ColorInfo.ColorFormat.CF_YCbCr_444, + "YCbCr 4:2:0": ColorInfo.ColorFormat.CF_YCbCr_420, + "Simple 4:2:2": ColorInfo.ColorFormat.CF_YCbCr_422, + "YCbCr422": ColorInfo.ColorFormat.CF_YCbCr_422, + "YCbCr444": ColorInfo.ColorFormat.CF_YCbCr_444, + "YCbCr420": ColorInfo.ColorFormat.CF_YCbCr_420, + "Simple422": ColorInfo.ColorFormat.CF_YCbCr_422 + } + + dict_dsc_color_formats = {"RGB": CompressionInfo.DscColorFormat.CF_RGB, + "YCbCr 4:2:2": CompressionInfo.DscColorFormat.CF_YCbCr_422, + "YCbCr 4:4:4": CompressionInfo.DscColorFormat.CF_YCbCr_444, + "YCbCr 4:2:0": CompressionInfo.DscColorFormat.CF_YCbCr_420, + "Simple 4:2:2": CompressionInfo.DscColorFormat.CF_Simple_422, + "YCbCr422": CompressionInfo.DscColorFormat.CF_YCbCr_422, + "YCbCr444": CompressionInfo.DscColorFormat.CF_YCbCr_444, + "YCbCr420": CompressionInfo.DscColorFormat.CF_YCbCr_420, + "Simple422": CompressionInfo.DscColorFormat.CF_Simple_422 + } + + dict_colorimetry = {None: ColorInfo.Colorimetry.CM_sRGB, + "ITU-601": ColorInfo.Colorimetry.CM_ITUR_BT601, + "ITU-709": ColorInfo.Colorimetry.CM_ITUR_BT709} + + dict_reduce_blanking = {None: Timing.ReduceBlanking.RB_NONE, + 'RB1': Timing.ReduceBlanking.RB1, + 'RB2': Timing.ReduceBlanking.RB2, + 'RB3': Timing.ReduceBlanking.RB3} + + dict_patterns = { + "Color Ramp": VideoPattern.ColorRamp, + "Color Square": VideoPattern.ColorSquares + } + + dict_dsc_color_names = { + CompressionInfo.DscColorFormat.CF_RGB: "RGB444", + CompressionInfo.DscColorFormat.CF_YCbCr_444: "YUV444", + CompressionInfo.DscColorFormat.CF_YCbCr_422: "YUV422", + CompressionInfo.DscColorFormat.CF_YCbCr_420: "YUV420", + CompressionInfo.DscColorFormat.CF_Simple_422: "SIMPL422" + } + + path_custom_image = os.path.join(DSC_TOOLS_FOLDER, "Default_16K.png") + dsc_content_library_path = "" + + g_vm = None + g_vf = None + g_h_slice_size = 0 + g_v_slice_size = 0 + + @staticmethod + def opf_pass_handler(dptx, dprx, message: str) -> OPFDialogAnswer: + logging.info(message) + return OPFDialogAnswer.PASS + + @staticmethod + def check_dptx(dptx) -> bool: + return isinstance(dptx, (UniTAP.DPTX, UniTAP.DPTX4xx, UniTAP.DPTX5xx)) + + @staticmethod + def check_dprx(dprx) -> bool: + return isinstance(dprx, (UniTAP.DPRX, UniTAP.DPRX4xx, UniTAP.DPRX5xx)) + + @staticmethod + def check_hdtx(hdtx) -> bool: + return isinstance(hdtx, (UniTAP.HDTX, UniTAP.HDTX4xx)) + + @staticmethod + def check_hdrx(hdrx) -> bool: + return isinstance(hdrx, (UniTAP.HDRX, UniTAP.DPRX4xx)) + + @staticmethod + def check_video(dev_rx) -> bool: + def is_video_available(_dev_rx): + return _dev_rx.link.status.stream(0).crc != [0, 0, 0] and \ + _dev_rx.link.status.stream(0).video_mode.timing.hactive != 0 + + return function_scheduler(is_video_available, dev_rx, interval=2, timeout=10) + + @staticmethod + def opf_1_after_handler(dptx, dprx, dp_lanes, dp_link_rate, encoding): + if not OPFFunctions.check_dptx(dptx): + return OPFDialogAnswer.ABORT + + logging.info((dp_lanes, dp_link_rate, encoding)) + if encoding is None: + config = LinkConfig.DP8b10b() + else: + if encoding == '8b/10b': + config = LinkConfig.DP8b10b() + else: + config = LinkConfig.DP128b132b() + config.lane_count = dp_lanes + config.bit_rate = dp_link_rate + dptx.link.config.set(config) + dptx.link.start_link_training() + + @staticmethod + def opf_1_handler(dptx, dprx, dp_lanes, dp_link_rate, encoding): + if not OPFFunctions.check_dptx(dptx): + return OPFDialogAnswer.ABORT + + return OPFDialogAnswer.PROCEED + + @staticmethod + def opf_2_handler(dptx, dprx, res_x, res_y, res_frate, res_bpc, tim_std, tim_rb): + if not OPFFunctions.check_dptx(dptx): + return OPFDialogAnswer.ABORT + + logging.info((res_x, res_y, res_frate, tim_std)) + timing_manager = dptx.pg.timing_manager + if tim_std.find("VIC") != -1: + tim_id = int(re.findall(r'\d+', tim_std)[0]) + timing = timing_manager.get_cta(tim_id) + elif tim_std.find("DMT") != -1: + tim_id = int(tim_std.replace("h", "").replace("DMT ", ""), 16) + timing = timing_manager.get_dmt(tim_id) + elif tim_std.find("CVT") != -1: + tim_id = int(tim_std.replace("h", "").replace("CVT ", ""), 16) + timing = timing_manager.get_cvt(tim_id) + else: + timing = timing_manager.search(res_x, res_y, res_frate, OPFFunctions.dict_reduce_blanking.get(tim_rb)) + + if timing is None: + timing = timing_manager.search(res_x, res_y, res_frate, OPFFunctions.dict_reduce_blanking.get(tim_rb)) + + color_mode = ColorInfo() + color_mode.color_format = ColorInfo.ColorFormat.CF_RGB + color_mode.bpc = res_bpc + color_mode.colorimetry = ColorInfo.Colorimetry.CM_sRGB + + video_mode = VideoMode(timing=timing, color_info=color_mode) + + dptx.pg.set_vm(video_mode) + dptx.pg.set_pattern(pattern=VideoPattern.ColorBars) + res_pg = dptx.pg.apply() + + res_app = dptx.pg.status().error + + if not res_pg: + logging.info(f"[UniTAP] Stream {0} - Apply {res_app.__str__()}") + return OPFDialogAnswer.ABORT + + return OPFDialogAnswer.PROCEED + + @staticmethod + def opf_3_handler(dptx, dprx, message: str): + logging.info(message) + return OPFDialogAnswer.PROCEED + + @staticmethod + def opf_4_handler(dptx, dprx, message: str): + if not OPFFunctions.check_dptx(dptx): + return OPFDialogAnswer.ABORT + + logging.info(message) + + val = dptx.dpcd.read(0x00101, 1).data[0] & 0xFF + val &= ~ 0x7 + val |= 1 + dptx.dpcd.write(0x00101, val) + + return OPFDialogAnswer.PROCEED + + @staticmethod + def opf_5_handler(dptx, dprx, message: str): + if not OPFFunctions.check_dptx(dptx): + return OPFDialogAnswer.ABORT + + val = dptx.dpcd.read(0x00101, 1).data[0] & 0xFF + val &= ~ 0x7 + val |= 4 + dptx.dpcd.write(0x00101, val) + + logging.info(message) + + return OPFDialogAnswer.PROCEED + + @staticmethod + def opf_6_handler(dptx, dprx, res_x, res_y, res_frate, res_bpc, timing_standard, col_format, + col_range, col_yc, + pattern_name) -> OPFDialogAnswer: + if not OPFFunctions.check_dptx(dptx): + return OPFDialogAnswer.ABORT + + logging.info((res_x, res_y, res_frate, res_bpc, timing_standard, col_format, col_range, col_yc, pattern_name)) + + if dptx.link.config.get(UniTAP.LinkConfig.DP8b10b).force_edid_timings_after_lt: + timing = dptx.pg.get_stream_video_mode().timing + else: + timing = dptx.pg.timing_manager.search(res_x, res_y, res_frate, + rb=OPFFunctions.dict_reduce_blanking.get(timing_standard)) + + color_mode = ColorInfo() + color_mode.color_format = OPFFunctions.dict_color_formats.get(col_format) + color_mode.bpc = res_bpc + color_mode.colorimetry = OPFFunctions.dict_colorimetry.get(col_yc) + color_mode.dynamic_range = ColorInfo.DynamicRange.DR_VESA if col_range == 'VESA' else ColorInfo.DynamicRange.DR_CTA + + video_mode = VideoMode(timing=timing, color_info=color_mode) + + dptx.pg.set_vm(video_mode) + dptx.pg.set_pattern(pattern=OPFFunctions.dict_patterns.get(pattern_name)) + res_pg = dptx.pg.apply() + + if not res_pg: + return OPFDialogAnswer.ABORT + + return OPFDialogAnswer.PROCEED + + @staticmethod + def opf_7_handler(dptx, dprx, message: str) -> OPFDialogAnswer: + if not OPFFunctions.check_dptx(dptx): + return OPFDialogAnswer.ABORT + + logging.info(message) + + dptx.dpcd.write(0x600, 2) + dptx.link.start_link_training() + + return OPFDialogAnswer.PROCEED + + @staticmethod + def opf_8_handler(dptx, dprx, message: str) -> OPFDialogAnswer: + if not OPFFunctions.check_dptx(dptx): + return OPFDialogAnswer.ABORT + + logging.info(message) + + dptx.dpcd.write(0x600, 1) + dptx.link.start_link_training() + + return OPFDialogAnswer.PROCEED + + @staticmethod + def opf_9_handler(dptx, dprx, res_x, res_y, res_frate, res_bpc, tim_std, + pattern_name) -> OPFDialogAnswer: + if not OPFFunctions.check_dptx(dptx): + return OPFDialogAnswer.ABORT + + logging.info((res_x, res_y, res_frate, res_bpc, tim_std)) + if not dptx.link.config.get(UniTAP.LinkConfig.DP8b10b).force_edid_timings_after_lt: + timing_manager = dptx.pg.timing_manager + rb = OPFFunctions.dict_reduce_blanking.get(tim_std) + timing = timing_manager.search(res_x, res_y, res_frate, rb=rb) + else: + timing = dptx.pg.get_stream_video_mode().timing + color_mode = ColorInfo() + color_mode.color_format = ColorInfo.ColorFormat.CF_RGB + color_mode.bpc = res_bpc + color_mode.colorimetry = ColorInfo.Colorimetry.CM_sRGB + + video_mode = VideoMode(timing=timing, color_info=color_mode) + + dptx.pg.set_vm(video_mode) + dptx.pg.set_pattern(pattern=OPFFunctions.dict_patterns.get(pattern_name)) + res_pg = dptx.pg.apply() + + if not res_pg: + return OPFDialogAnswer.ABORT + + return OPFDialogAnswer.PROCEED + + @staticmethod + def opf_10_handler(dptx, dprx, res_x, res_y, res_frate, res_bpc, timing_standard, audio_pattern, + ch_count, sample_freq, + sample_size, ch_alloc, ch_info) -> OPFDialogAnswer: + if not OPFFunctions.check_dptx(dptx): + return OPFDialogAnswer.ABORT + + logging.info((audio_pattern, ch_count, sample_freq, sample_size)) + + audio_mode = AudioMode() + audio_mode.channel_count = ch_count + audio_mode.bits = sample_size + audio_mode.sample_rate = sample_freq + + dptx.ag.setup(audio_mode=audio_mode, + audio_pattern=UniTAP.AudioPattern.SignalSine, + signal_frequency=2000, + amplitude=60) + dptx.ag.apply() + + if res_x != 0: + timing_manager = dptx.pg.timing_manager + rb = OPFFunctions.dict_reduce_blanking.get(timing_standard) + + for standard in [UniTAP.Timing.Standard.SD_CTA, UniTAP.Timing.Standard.SD_DMT, + UniTAP.Timing.Standard.SD_CVT]: + timing = timing_manager.search(res_x, res_y, res_frate, standard=standard, rb=rb) + if timing is not None: + break + + color_mode = ColorInfo() + color_mode.color_format = ColorInfo.ColorFormat.CF_RGB + color_mode.bpc = res_bpc + color_mode.colorimetry = ColorInfo.Colorimetry.CM_sRGB + + video_mode = VideoMode(timing=timing, color_info=color_mode) + + dptx.pg.set_vm(video_mode) + dptx.pg.set_pattern(pattern=VideoPattern.ColorRamp) + res_pg = dptx.pg.apply() + + if dptx.pg.status().error != PGStatus.PGError.OK and not res_pg: + return OPFDialogAnswer.ABORT + else: + for i in range(dptx.pg.max_stream_count): + dptx.pg[i].set_pattern(UniTAP.VideoPattern.Disabled) + res_pg = dptx.pg.apply() + + if dptx.pg.status().error != PGStatus.PGError.OK and not res_pg: + return OPFDialogAnswer.ABORT + + return OPFDialogAnswer.PROCEED + + @staticmethod + def opf_11_handler(dptx, dprx, message: str) -> OPFDialogAnswer: + return OPFFunctions.opf_pass_handler(dptx, dprx, message) + + @staticmethod + def opf_12_handler(dptx, dprx, message: str) -> OPFDialogAnswer: + + if not OPFFunctions.check_dprx(dprx): + return OPFDialogAnswer.ABORT + + logging.info(message) + + def capture_frame(_dprx): + try: + dprx.video_capturer.start() + _vf = dprx.video_capturer.pop_element() + dprx.video_capturer.stop() + return _vf + except BaseException: + return None + + iterations = 5 + + for i in range(iterations): + + vf = capture_frame(dprx) + vf = video_frame_to_ci(vf, vf.color_info, OPFFunctions.g_vf.data_info) + + if vf is not None and len(vf.data) > 0: + if OPFFunctions.g_vf is not None: + if vf.color_info.color_format == UniTAP.ColorInfo.ColorFormat.CF_DSC: + if OPFFunctions.g_vf.data[4:] in vf.data: + return OPFDialogAnswer.PASS + else: + if OPFFunctions.g_vf.data in vf.data: + return OPFDialogAnswer.PASS + return OPFDialogAnswer.FAIL + + @staticmethod + def opf_13_handler(dptx, dprx, message: str) -> OPFDialogAnswer: + return OPFFunctions.opf_pass_handler(dptx, dprx, message) + + @staticmethod + def opf_14_handler(dptx, dprx, message: str) -> OPFDialogAnswer: + if not OPFFunctions.check_dptx(dptx): + return OPFDialogAnswer.ABORT + + logging.info(message) + + data = dptx.dpcd.read(0x120, 1).data + data.append(1) + dptx.dpcd.write(0x120, data) + + if OPFFunctions.check_dprx(dprx): + data = dptx.dpcd.read(0x120, 1).data + if len(data) > 0 and data[0] == 1: + return OPFDialogAnswer.PROCEED + else: + return OPFDialogAnswer.ABORT + + return OPFDialogAnswer.PROCEED + + @staticmethod + def opf_15_handler(dptx, dprx, message: str) -> OPFDialogAnswer: + if not OPFFunctions.check_dptx(dptx): + return OPFDialogAnswer.ABORT + + logging.info(message) + + dptx.fec.enable(True) + + def is_fec_enabled(_dptx): + return _dptx.fec.is_enabled() + + if not function_scheduler(is_fec_enabled, dptx, interval=2, timeout=10): + return OPFDialogAnswer.ABORT + + return OPFDialogAnswer.PROCEED + + @staticmethod + def opf_16_handler(dptx, dprx, message: str) -> OPFDialogAnswer: + if not OPFFunctions.check_dptx(dptx): + return OPFDialogAnswer.ABORT + + logging.info(message) + + dptx.fec.enable(False) + + def is_fec_disable(_dptx): + return not _dptx.fec.is_enabled() + + if not function_scheduler(is_fec_disable, dptx, interval=2, timeout=10): + return OPFDialogAnswer.ABORT + + return OPFDialogAnswer.PROCEED + + @staticmethod + def opf_17_handler(dptx, dprx, message: str) -> OPFDialogAnswer: + logging.info(message) + res_x = 1920 + res_y = 1080 + res_frate = 60000 + res_bpc = 8 + col_yc = None + col_format = "RGB" + timing_manager = dptx.pg.timing_manager + standard = Timing.Standard.SD_CTA + timing = timing_manager.search(res_x, res_y, res_frate, standard=standard) + + return OPFFunctions.opf_18_20_support(dptx, dprx, col_format, res_x, res_y, res_bpc, col_yc, timing, + pattern_name=None, enable_dsc=True) + + @staticmethod + def opf_18_20_support(dptx, dprx, col_format, res_x, res_y, res_bpc, col_yc, timing, pattern_name=None, + enable_dsc=True): + color_mode = ColorInfo() + color_mode.color_format = OPFFunctions.dict_color_formats.get(col_format) + color_mode.bpc = res_bpc + color_mode.colorimetry = ColorInfo.Colorimetry.CM_sRGB if col_yc is None else \ + ColorInfo.Colorimetry.CM_ITUR_BT601 + color_mode.dynamic_range = ColorInfo.DynamicRange.DR_VESA if col_yc is None else ColorInfo.DynamicRange.DR_CTA + data_info = DataInfo() + data_info.packing = DataInfo.Packing.P_PACKED if col_yc is None else DataInfo.Packing.P_PLANAR + data_info.component_order = DataInfo.ComponentOrder.CO_RGB if col_yc is None else DataInfo.ComponentOrder.CO_YCbCr + data_info.alignment = DataInfo.Alignment.A_LSB + + video_mode = VideoMode(timing=timing, color_info=color_mode) + + if enable_dsc: + sink_dsc_caps = dptx.dpcd.read(0x60, 16) + + block_prediction = bool(sink_dsc_caps.data[6] & 0x1) + dsc_minor = sink_dsc_caps.data[1] >> 4 & 0xf + dsc_major = sink_dsc_caps.data[1] & 0xf + + h_slice_size = int(calculate_slice_size(res_x, 10, color_mode.color_format)) + v_slice_size = int(calculate_slice_size(res_y, 10, color_mode.color_format)) + + dptx.pg.set_vm(video_mode) + + pixel_rate = timing.htotal * timing.vtotal * (timing.frame_rate / 1000) + link_bandwidth = dptx.link.status.available_link_rate + + bpp = (link_bandwidth * 0.9 / pixel_rate) * 16 + bpp = res_bpc * 16 if res_bpc * 16 < bpp else round(bpp) + + compression_info = CompressionInfo() + compression_info.v_slice_size = v_slice_size + compression_info.h_slice_size = h_slice_size + compression_info.bpp = bpp + compression_info.color_format = OPFFunctions.dict_dsc_color_formats.get(col_format) + compression_info.buffer_bit_depth = res_bpc + 1 + compression_info.is_block_prediction_enabled = block_prediction + compression_info.version = (dsc_major, dsc_minor) + + if OPFFunctions.dsc_content_library_path != "": + if not os.path.exists(OPFFunctions.dsc_content_library_path): + os.makedirs(OPFFunctions.dsc_content_library_path) + dsc_image_name = f"{res_x}x{res_y}_{OPFFunctions.dict_dsc_color_names.get(compression_info.color_format)}_" \ + f"{'BPY' if block_prediction else 'NBP'}_bpc{res_bpc}_" \ + f"bpp{compression_info.bpp}_{10}slicew_{10}sliceh_" \ + f"{compression_info.buffer_bit_depth}lb_v{dsc_major}{dsc_minor}.dsc" + full_path = os.path.join(OPFFunctions.dsc_content_library_path, dsc_image_name) + if os.path.exists(full_path): + # + # Temporary message "Update DSC folder" + # + warnings.warn("Since version 3.5.X/3.6.X the usual file for generating DSC images has been updated." + "Please update/regenerate your DSC folder/library with using new file.") + + with open(full_path, 'rb') as file: + data = bytearray(file.read()) + encoded_video_frame = dsc_video_frame_from_data(data) + else: + custom_vf = generate_pattern_as_vf(PatternType.Unigraf, res_x, res_y, color_mode, data_info) + encoded_video_frame = encode_video_frame(custom_vf, compression_info) + with open(full_path, 'wb') as file: + file.write(encoded_video_frame.data) + else: + custom_vf = generate_pattern_as_vf(PatternType.Unigraf, res_x, res_y, color_mode, data_info) + encoded_video_frame = encode_video_frame(custom_vf, compression_info) + + dptx.pg.set_pattern(pattern=encoded_video_frame) + res = dptx.pg.apply() + + if not res and dptx.pg.status().error != PGStatus.PGError.OK: + return OPFDialogAnswer.ABORT + + OPFFunctions.g_vf = encoded_video_frame + elif pattern_name is not None: + dptx.pg.set_vm(video_mode) + dptx.pg.set_pattern(pattern=pattern_name) + res = dptx.pg.apply() + + if not res and dptx.pg.status().error != PGStatus.PGError.OK: + return OPFDialogAnswer.ABORT + + try: + if not OPFFunctions.check_video(dprx): + print('Video is not available.') + return OPFDialogAnswer.ABORT + except BaseException as e: + print(e) + return OPFDialogAnswer.ABORT + + return OPFDialogAnswer.PROCEED + + @staticmethod + def opf_18_handler(dptx, dprx, res_x, res_y, res_frate, res_bpc, col_format, col_range, col_yc, + tim_std) \ + -> OPFDialogAnswer: + if not OPFFunctions.check_dptx(dptx): + return OPFDialogAnswer.ABORT + + logging.info((res_x, res_y, res_frate, res_bpc, col_format, col_range, col_yc, tim_std)) + + timing_manager = dptx.pg.timing_manager + standard = Timing.Standard.SD_CTA if OPFFunctions.dict_reduce_blanking.get( + tim_std) is None else Timing.Standard.SD_CVT + timing = timing_manager.search(res_x, res_y, res_frate, standard=standard, + rb=OPFFunctions.dict_reduce_blanking.get(tim_std)) + + return OPFFunctions.opf_18_20_support(dptx, dprx, col_format, res_x, res_y, res_bpc, col_yc, timing) + + @staticmethod + def opf_19_handler(dptx, dprx, use_3tap) -> OPFDialogAnswer: + + if not OPFFunctions.check_dprx(dprx): + return OPFDialogAnswer.ABORT + + # check DSC state + if not dprx.link.capabilities.link_caps_status().dsc: + return OPFDialogAnswer.ABORT + + incorrect_crc = [0xFFFF, 0xFFFF, 0xFFFF, 0x1111, 0x1111, 0x1111] + + def load_dsc(handle: int, values: list) -> OPFDialogAnswer: + TSIX_TS_SetConfigItem(handle, TSI_DPRX_DSC_TEST_CRC, values, data_count=6) + return OPFDialogAnswer.FAIL + + # TODO: Need to change on public interface. Temporary solution. + device_handle = dprx.bulk_capturer.__getattribute__("_BulkCapturer__memory_manager"). \ + __getattribute__('_MemoryManager__io'). \ + __getattribute__('device_handle') + + iterations = 5 + + for i in range(iterations): + # capture frame + try: + dprx.video_capturer.start(frames_count=1) + vf = dprx.video_capturer.capture_result.buffer[-1] + dprx.video_capturer.stop() + except BaseException: + dprx.video_capturer.stop() + return load_dsc(device_handle, incorrect_crc) + + def create_reference_image(video_frame, crc_values: list): + try: + pps_info = DSCL_ExtractPPSFromData(video_frame.data) + except BaseException: + return + + sampling = UICL_Sampling.Sampling_444 + if pps_info.is_yuv(): + if pps_info.is_420(): + sampling = UICL_Sampling.Sampling_420 + elif pps_info.is_422(): + sampling = UICL_Sampling.Sampling_422 + elif pps_info.is_simple_422(): + sampling = UICL_Sampling.Sampling_422 + + image = UICL_Image() + parameters = UICL_ImageParameters() + parameters.Width = pps_info.width() + parameters.Height = pps_info.height() + parameters.BitsPerColor = pps_info.bpc() + parameters.Alignment = UICL_Alignment.Alignment_LSB + parameters.IsFullRange = False + + if pps_info.is_yuv(): + parameters.Colorspace = UICL_Colorspace.Colorspace_YCbCr + parameters.Colorimetry = UICL_Colorimetry.Colorimetry_ITU_R_BT709 + parameters.Sampling = sampling + parameters.ComponentOrder = UICL_ComponentOrder.Order_YCbCr + parameters.Packing = UICL_Packing.Packing_Planar + image.Parameters = parameters + + try: + image.DataSize = UICL_GetRequiredBufferSize(image) + image.DataPtr = (c_uint8 * image.DataSize)() + if use_3tap: + res = UICL_GeneratePattern_3Tap(image, 1) + else: + res = UICL_GeneratePattern(image, 1) + + if res < UICL_SUCCESS: + return + + except BaseException: + return + else: + parameters.Colorspace = UICL_Colorspace.Colorspace_RGB + parameters.Colorimetry = UICL_Colorimetry.Colorimetry_Unknown + parameters.Sampling = sampling + parameters.ComponentOrder = UICL_ComponentOrder.Order_RGB + parameters.Packing = UICL_Packing.Packing_Packed + image.Parameters = parameters + + try: + image.DataSize = UICL_GetRequiredBufferSize(image) + image.DataPtr = (c_uint8 * image.DataSize)() + if use_3tap: + res = UICL_GeneratePattern_3Tap(image, 0) + else: + res = UICL_GeneratePattern(image, 0) + + if res < UICL_SUCCESS: + return + + except BaseException: + return + + dsc_encoded_image = DSCL_Encode(image, pps_info) + OPFFunctions.g_vf = dscl_image_to_dsc_vf(dsc_encoded_image) + dsc_decoded_uicl_image = DSCL_Decode(dsc_encoded_image) + + reference_image_crc = UICL_CRC16() + try: + res = UICL_CalculateCRC16(dsc_decoded_uicl_image, reference_image_crc) + if res < UICL_SUCCESS: + crc_values.extend([0xFFFF, 0xFFFF, 0xFFFF]) + else: + crc_values.extend([reference_image_crc.R, reference_image_crc.G, reference_image_crc.B]) + except BaseException: + return + + def decompress_dsc(video_frame, crc_values: list): + decoded_captured_vf = decode_video_frame(video_frame) + uicl_decoded_image_2 = image_from_vf(decoded_captured_vf) + decompressed_image_crc = UICL_CRC16() + try: + res = UICL_CalculateCRC16(uicl_decoded_image_2, decompressed_image_crc) + if res < UICL_SUCCESS: + crc_values.extend([0x1111, 0x1111, 0x1111]) + else: + crc_values.extend([decompressed_image_crc.R, decompressed_image_crc.G, + decompressed_image_crc.B]) + except BaseException: + return + + reference_image_crc_values = [] + decompressed_image_crc_values = [] + + thread_create_reference_image = threading.Thread(target=create_reference_image, + args=(vf, reference_image_crc_values)) + thread_decompressed_image = threading.Thread(target=decompress_dsc, + args=(vf, decompressed_image_crc_values)) + + thread_create_reference_image.start() + thread_decompressed_image.start() + + thread_create_reference_image.join() + thread_decompressed_image.join() + + if len(reference_image_crc_values) > 0 and len(decompressed_image_crc_values) > 0 and \ + ((reference_image_crc_values == decompressed_image_crc_values) or + (reference_image_crc_values != decompressed_image_crc_values and (i == iterations - 1))): + # Write crc to register + load_dsc(device_handle, [decompressed_image_crc_values[0], + decompressed_image_crc_values[1], + decompressed_image_crc_values[2], + reference_image_crc_values[0], + reference_image_crc_values[1], + reference_image_crc_values[2]]) + + return OPFDialogAnswer.PROCEED + + return load_dsc(device_handle, incorrect_crc) + + @staticmethod + def opf_20_handler(dptx, dprx, res_x, res_y, res_frate, res_bpc, col_format, col_range, col_yc, tim_std, + pattern_name, enable_dsc) -> OPFDialogAnswer: + if not OPFFunctions.check_dptx(dptx): + return OPFDialogAnswer.ABORT + + logging.info((res_x, res_y, res_frate, res_bpc, col_format, col_range, col_yc, tim_std, pattern_name)) + + if pattern_name is not None: + if pattern_name == 'No Video': + pattern_name = VideoPattern.Disabled + elif pattern_name == 'Color Ramp': + pattern_name = VideoPattern.ColorRamp + elif pattern_name == 'Color Square': + pattern_name = VideoPattern.ColorSquares + elif pattern_name == 'Black and Vertical lines': + pattern_name = VideoPattern.WhiteVStrips + elif pattern_name == "Any": + if col_format == "RGB": + pattern_name = UniTAP.VideoPattern.ColorRamp + else: + pattern_name = UniTAP.VideoPattern.ColorSquares + else: + pattern_name = VideoPattern.ColorBars + + timing_manager = dptx.pg.timing_manager + + for item in tim_std: + if item.find("VIC") != -1: + tim_id = int(re.findall(r'\d+', item)[0]) + timing = timing_manager.get_cta(tim_id) + if timing is None: + continue + break + elif item.find("DMT") != -1: + res = re.findall(r'\w+', item)[1][:-1] + tim_id = int(res, 16) + timing = timing_manager.get_dmt(tim_id) + if timing is None: + continue + break + elif item.find("CVT") != -1: + res = re.findall(r'RB1|RB2|RB3', item) + tim_id = res[0] if len(res) > 0 else None + standard = Timing.Standard.SD_CVT + timing = timing_manager.search(res_x, res_y, res_frate, + standard=standard, + rb=OPFFunctions.dict_reduce_blanking.get(tim_id)) + if timing is None: + continue + break + elif item.find("UGF") != -1: + pass + elif item.find("OVT") != -1: + standard = Timing.Standard.SD_OVT + timing = timing_manager.search(res_x, res_y, res_frate, + standard=standard) + if timing is None: + continue + break + else: + timing = timing_manager.search(res_x, res_y, res_frate) + + return OPFFunctions.opf_18_20_support(dptx, dprx, col_format, res_x, res_y, res_bpc, col_yc, timing, + pattern_name, enable_dsc) + + @staticmethod + def opf_21_handler(dptx, dprx, audio_pattern, ch_count, sample_freq, sample_size, ch_alloc, ch_info): + if not OPFFunctions.check_dptx(dptx): + return OPFDialogAnswer.ABORT + + logging.info((audio_pattern, ch_count, sample_freq, sample_size)) + + audio_mode = AudioMode() + audio_mode.channel_count = ch_count + audio_mode.bits = sample_size + audio_mode.sample_rate = sample_freq + audio_pattern_value = UniTAP.AudioPattern.SignalSawtooth if audio_pattern.find("Sawtooth") != -1 else ( + UniTAP.AudioPattern.SignalSine) + + dptx.ag.setup(audio_mode=audio_mode, + audio_pattern=audio_pattern_value, + signal_frequency=2000, + amplitude=60) + res = dptx.ag.apply() + if not res: + return OPFDialogAnswer.ABORT + return OPFDialogAnswer.PROCEED + + @staticmethod + def opf_101_handler(dptx, dprx, message: str) -> OPFDialogAnswer: + if not OPFFunctions.check_dprx(dprx): + return OPFDialogAnswer.FAIL + + try: + dprx.audio_capturer.start(10) + dprx.audio_capturer.stop() + return UniTAP.OPFDialogAnswer.PASS if len( + dprx.audio_capturer.capture_result.buffer) > 0 else UniTAP.OPFDialogAnswer.FAIL + except BaseException as e: + print(f"Cannot capture audio. Error: {e}") + return UniTAP.OPFDialogAnswer.FAIL + + @staticmethod + def opf_102_handler(dptx, dprx, message: str) -> OPFDialogAnswer: + + if not OPFFunctions.check_dprx(dprx): + return OPFDialogAnswer.FAIL + + def is_crc_valid(rx, dsc_flag): + return rx.link.status.stream(0).dsc_crc != [0, 0, 0] if dsc_flag else \ + rx.link.status.stream(0).crc != [0, 0, 0] + + logging.info(message) + if OPFFunctions.g_vm is not None: + try: + if UniTAP.utils.function_scheduler(is_crc_valid, dprx, True, interval=2, timeout=10): + + try: + dprx.video_capturer.start(frames_count=5) + vf_list = dprx.video_capturer.capture_result.buffer + dprx.video_capturer.stop() + except BaseException as e: + logging.info(f'{e}\nCannot capture frames.') + dprx.video_capturer.stop() + return OPFDialogAnswer.FAIL + + if isinstance(vf_list, list) and len(vf_list) == 5: + for vf in vf_list: + delta = len(vf.data) - len(OPFFunctions.g_vm.data) + 4 + if OPFFunctions.g_vm.data[4:] in vf.data[:-delta]: + return OPFDialogAnswer.PASS + + logging.info("Incorrect sequence of frames.") + return OPFDialogAnswer.FAIL + else: + logging.info("CRC is not valid.") + return OPFDialogAnswer.FAIL + except BaseException as e: + logging.info(f"{e}\nCannot check CRC.") + return OPFDialogAnswer.FAIL + else: + if UniTAP.utils.function_scheduler(is_crc_valid, dprx, False, interval=2, timeout=10): + + def capture_frame(_dprx): + try: + dprx.video_capturer.start() + _vf = dprx.video_capturer.pop_element() + dprx.video_capturer.stop() + return _vf + except BaseException: + return None + + iterations = 5 + + for i in range(iterations): + vf = capture_frame(dprx) + if len(vf.data) > 0: + return OPFDialogAnswer.PASS + + return OPFDialogAnswer.FAIL + + @staticmethod + def opf_103_handler(dptx, dprx, width, height, rate, color_format, is_block_prediction_enabled, bpc, + bpp, + h_slice_number, buffer_bit_depth, v_slice_number, dsc_v_minor, dsc_v_major): + # TODO: Need to resolve problem with writing TSI_VR_DSC_MEMORY_BLOCK and TSI_VR_DSC_DATA_SIZE + + if not OPFFunctions.check_dptx(dptx): + return OPFDialogAnswer.ABORT + + logging.info((width, height, rate, color_format, is_block_prediction_enabled, bpc, bpp, + h_slice_number, + buffer_bit_depth, v_slice_number, dsc_v_major, dsc_v_minor)) + + # TODO: Need to change on public interface. Temporary solution. + device_handle = dptx.pg.__getattribute__('_DpMstPatternGenerator__pg_list')[0]. \ + __getattribute__("_PatternGenerator__memory_manager"). \ + __getattribute__('_MemoryManager__io'). \ + __getattribute__('device_handle') + + color_mode = ColorInfo() + color_mode.color_format = OPFFunctions.dict_color_formats.get(color_format) + col_rgb = color_mode.color_format == ColorInfo.ColorFormat.CF_RGB + color_mode.bpc = bpc + color_mode.colorimetry = ColorInfo.Colorimetry.CM_sRGB if col_rgb else ColorInfo.Colorimetry.CM_ITUR_BT709 + + h_slice_size = int(calculate_slice_size(width, h_slice_number, color_mode.color_format)) + v_slice_size = int(calculate_slice_size(height, v_slice_number, color_mode.color_format)) + OPFFunctions.g_h_slice_size = h_slice_size + OPFFunctions.g_v_slice_size = v_slice_size + + res, suite_id = TSIX_TS_GetConfigItem(device_handle, TSI_SELECT_SUITE, c_uint32) + if res > 0: + suite_id = TestGroupId(suite_id) + else: + suite_id = TestGroupId.UNKNOWN + + res, is_simple_as_444 = 0, False + try: + if suite_id in [TestGroupId.DP_TX_LL_CTS, TestGroupId.DP_TX_LL_CTS_DSC, + TestGroupId.DP_TX_DISPLAYID, TestGroupId.DP_TX_ADAPTIVESYNC]: + res, is_simple_as_444 = TSIX_TS_GetConfigItem(device_handle, TSI_DP14_SINKCTS_SUPPORT_444CRC, c_uint32) + elif suite_id in [TestGroupId.DP_2_1_TX_LL_CTS, TestGroupId.DP_2_1_TX_DSC_CTS, + TestGroupId.DP_2_1_TX_DISPAYID, TestGroupId.DP_2_1_TX_ADAPTIVESYNC]: + res, is_simple_as_444 = TSIX_TS_GetConfigItem(device_handle, TSI_DP20_SINKCTS_SUPPORT_444CRC, c_uint32) + except BaseException as e: + pass + + compression_info = OPFFunctions.get_compression_info(v_slice_size, h_slice_size, bpp, color_format, + buffer_bit_depth, is_block_prediction_enabled, + dsc_v_major, dsc_v_minor, is_simple_as_444) + + data_info = DataInfo() + data_info.packing = DataInfo.Packing.P_PACKED if col_rgb else DataInfo.Packing.P_PLANAR + data_info.component_order = DataInfo.ComponentOrder.CO_RGB if col_rgb else DataInfo.ComponentOrder.CO_YCbCr + data_info.alignment = DataInfo.Alignment.A_LSB + + try: + encoded_video_frame = OPFFunctions.check_dsc_content_library(width, height, color_mode, + is_block_prediction_enabled, bpc, bpp, + h_slice_number, v_slice_number, + compression_info, dsc_v_major, + dsc_v_minor, data_info) + except BaseException as e: + logging.info(e) + return OPFDialogAnswer.ABORT + + OPFFunctions.g_vm = copy.deepcopy(encoded_video_frame) + + TSIX_TS_SetConfigItem(device_handle, TSI_MEMORY_LAYOUT, len(encoded_video_frame.data), c_uint64) + TSIX_TS_SetConfigItem(device_handle, TSI_MEMORY_BLOCK_INDEX, 0, data_size=4) + TSIX_TS_SetConfigItem(device_handle, TSI_MEMORY_WRITE_W, bytearray(encoded_video_frame.data), + data_type=c_uint8, + data_count=len(encoded_video_frame.data)) + TSIX_TS_SetConfigItem(device_handle, TSI_DSC_MEMORY_BLOCK, 0) + TSIX_TS_SetConfigItem(device_handle, TSI_DSC_DATA_SIZE, len(encoded_video_frame.data)) + + decoded_vf = decode_video_frame(encoded_video_frame) + + crc0, crc1, crc2 = calculate_crc(decoded_vf) + TSIX_TS_SetConfigItem(device_handle, TSI_DSC_TX_CRC, [crc0, crc1, crc2], data_count=3) + + return OPFDialogAnswer.PROCEED + + @staticmethod + def opf_104_handler(dptx, dprx, message: str) -> OPFDialogAnswer: + + if not OPFFunctions.check_dprx(dprx): + return OPFDialogAnswer.FAIL + + logging.info(message) + + def capture_frame(_dprx): + try: + dprx.video_capturer.start() + _vf = dprx.video_capturer.pop_element() + dprx.video_capturer.stop() + return _vf + except BaseException: + return None + + iterations = 5 + + for i in range(iterations): + + vf = capture_frame(dprx) + + if vf is not None and isinstance(vf, VideoFrameDSC): + slice_size = OPFFunctions.g_v_slice_size * OPFFunctions.g_h_slice_size + pps_size = 128 + delta = len(vf.data) - len(OPFFunctions.g_vm.data) + 4 + + # Slice #1 + if OPFFunctions.g_vm.data[4 + pps_size:slice_size] in vf.data[pps_size:slice_size]: + # Slice #3 + if OPFFunctions.g_vm.data[4 + pps_size + slice_size * 2:slice_size * 3] in \ + vf.data[pps_size + slice_size * 2:slice_size * 3]: + # Slice #4 + if OPFFunctions.g_vm.data[4 + pps_size + slice_size * 3:slice_size * 4] in \ + vf.data[pps_size + slice_size * 3:]: + # Slice #2 + if OPFFunctions.g_vm.data[4 + pps_size + slice_size:slice_size * 2] not in \ + vf.data[pps_size + slice_size:slice_size * 2]: + return OPFDialogAnswer.PASS + else: + print(f"Frame {i + 1}: Image looks good and distortion free for slice #2. It is wrong.") + else: + print( + f"Frame {i + 1}: Image does not look good and distortion free for slice #4. It is wrong.") + else: + print(f"Frame {i + 1}: Image does not look good and distortion free for slice #3. It is wrong.") + else: + print(f"Frame {i + 1}: Image does not look good and distortion free for slice #1. It is wrong.") + + return OPFDialogAnswer.FAIL + + @staticmethod + def opf_105_handler(dptx, dprx, message: str) -> OPFDialogAnswer: + return OPFFunctions.opf_pass_handler(dptx, dprx, message) + + @staticmethod + def opf_106_handler(dptx, dprx, message: str) -> OPFDialogAnswer: + return OPFFunctions.opf_pass_handler(dptx, dprx, message) + + @staticmethod + def opf_107_handler(dptx, dprx, message: str) -> OPFDialogAnswer: + return OPFFunctions.opf_pass_handler(dptx, dprx, message) + + @staticmethod + def opf_120_handler(dptx, dprx, res_x, res_y, res_frate, res_bpc, color_format) -> OPFDialogAnswer: + if not OPFFunctions.check_dprx(dprx): + return OPFDialogAnswer.ABORT + + logging.info((res_x, res_y, res_frate, res_bpc, color_format)) + stream_status = dprx.link.status.stream(0) + + if res_x == stream_status.video_mode.timing.hactive and res_y == stream_status.video_mode.timing.vactive and \ + abs(res_frate - stream_status.video_mode.timing.frame_rate) <= 500 and \ + res_bpc == stream_status.video_mode.color_info.bpc and \ + OPFFunctions.dict_color_formats.get( + color_format) == stream_status.video_mode.color_info.color_format: + return OPFDialogAnswer.PASS + else: + return OPFDialogAnswer.FAIL + + @staticmethod + def opf_121_handler(dptx, dprx, message: str) -> OPFDialogAnswer: + return OPFFunctions.opf_pass_handler(dptx, dprx, message) + + @staticmethod + def opf_122_handler(dptx, dprx, message: str) -> OPFDialogAnswer: + return OPFFunctions.opf_pass_handler(dptx, dprx, message) + + @staticmethod + def opf_123_handler(dptx, dprx, message: str) -> OPFDialogAnswer: + return OPFFunctions.opf_pass_handler(dptx, dprx, message) + + @staticmethod + def opf_140_handler(dptx, dprx, res_x, res_y, res_frate, tim_std, tim_std_num) -> OPFDialogAnswer: + if not OPFFunctions.check_dptx(dptx): + return OPFDialogAnswer.ABORT + + logging.info((res_x, res_y, res_frate, tim_std, tim_std_num)) + + if tim_std_num is not None and tim_std_num.find('h') != -1: + tim_std_num = tim_std_num.replace('h', '') + + timing_manager = dptx.pg.timing_manager + if tim_std == 'DMT': + timing = timing_manager.get_dmt(int(tim_std_num, 16)) + elif tim_std == 'VIC': + timing = timing_manager.get_cta(int(tim_std_num)) + else: + timing = timing_manager.search(res_x, res_y, res_frate, + rb=OPFFunctions.dict_reduce_blanking.get(tim_std)) + + color_mode = ColorInfo() + color_mode.color_format = ColorInfo.ColorFormat.CF_RGB + color_mode.bpc = 8 + color_mode.colorimetry = ColorInfo.Colorimetry.CM_sRGB + + video_mode = VideoMode(timing=timing, color_info=color_mode) + + dptx.pg.set_vm(video_mode) + dptx.pg.set_pattern(pattern=VideoPattern.ColorBars) + res_pg = dptx.pg.apply() + + if not res_pg: + return OPFDialogAnswer.ABORT + + time.sleep(5) + rx_vm = dprx.link.status.stream(0).video_mode + logging.info((rx_vm.timing.hactive, video_mode.timing.hactive, " | ", rx_vm.timing.vactive, + video_mode.timing.vactive)) + if rx_vm.timing.hactive == video_mode.timing.hactive and rx_vm.timing.vactive == video_mode.timing.vactive: + return OPFDialogAnswer.PROCEED + else: + return OPFDialogAnswer.ABORT + + @staticmethod + def opf_141_handler(dptx, dprx, audio_format, channels, size, rate) -> OPFDialogAnswer: + if not OPFFunctions.check_dptx(dptx): + return OPFDialogAnswer.ABORT + + logging.info((audio_format, channels, size, rate)) + + audio_mode = AudioMode() + audio_mode.channel_count = channels + audio_mode.bits = size + audio_mode.sample_rate = rate + + dptx.ag.setup(audio_mode=audio_mode, + audio_pattern=UniTAP.AudioPattern.SignalSine, + signal_frequency=2000, + amplitude=60) + dptx.ag.apply() + time.sleep(3) + + return OPFDialogAnswer.PROCEED + + @staticmethod + def opf_142_handler(dptx, dprx, message: str): + if not OPFFunctions.check_dptx(dptx): + return OPFDialogAnswer.ABORT + + logging.info(message) + + if dptx.link.status.link_encoding == UniTAP.DpLinkEncoding.LE_8b10b: + config = UniTAP.LinkConfig.DP8b10b() + else: + config = UniTAP.LinkConfig.DP128b132b() + config.adaptive_sync_auto_enable = True + dptx.link.config.set(config) + dptx.link.start_link_training() + time.sleep(1) + + res_app = dptx.pg.status().error + + def is_adaptive_sync_enabled(_dptx): + status = _dptx.pg[0].adaptive_sync_status() + return status + + if not function_scheduler(is_adaptive_sync_enabled, dptx, interval=3, timeout=30): + return OPFDialogAnswer.ABORT + + if res_app == PGStatus.PGError.OK: + return OPFDialogAnswer.PROCEED + else: + return OPFDialogAnswer.ABORT + + @staticmethod + def opf_143_handler(dptx, dprx, rate: int) -> OPFDialogAnswer: + if not OPFFunctions.check_dptx(dptx): + return OPFDialogAnswer.ABORT + + logging.info(rate) + + dptx.pg.set_as_config(as_config=FixedASParams(refresh_rate=rate, + divide_by_1_001=False, + increase_lines=100, + decrease_lines=100)) + dptx.pg.apply() + + res_app = dptx.pg.status().error + + def is_adaptive_sync_enabled(_dptx): + status = _dptx.pg[0].adaptive_sync_status() + return status + + if not function_scheduler(is_adaptive_sync_enabled, dptx, interval=3, timeout=30): + return OPFDialogAnswer.ABORT + + if res_app == PGStatus.PGError.OK: + return OPFDialogAnswer.PROCEED + else: + return OPFDialogAnswer.ABORT + + @staticmethod + def opf_144_handler(dptx, dprx, message: str) -> OPFDialogAnswer: + if not OPFFunctions.check_dptx(dptx): + return OPFDialogAnswer.ABORT + + logging.info(message) + if message.find("Zigzag Sweep") != -1 or message.find("Square") != -1: + + increase_lines, decrease_lines, max_lanes, min_lanes = OPFFunctions.as_parse_edid(dptx) + + if message.find("Zigzag Sweep") != -1: + dptx.pg.set_as_config(as_config=ZigzagASParams(min_lanes=min_lanes, + max_lanes=max_lanes, + increase_lines=increase_lines, + decrease_lines=decrease_lines)) + else: + dptx.pg.set_as_config(as_config=SquareASParams(min_lanes=min_lanes, + max_lanes=max_lanes, + period_frames=20)) + else: + dptx.pg.set_as_config(as_config=ConstantASParams(lines=20)) + dptx.pg.apply() + res_app = dptx.pg.status().error + + def is_adaptive_sync_enabled(_dptx): + status = _dptx.pg[0].adaptive_sync_status() + return status + + if not function_scheduler(is_adaptive_sync_enabled, dptx, interval=3, timeout=30): + return OPFDialogAnswer.ABORT + + if res_app == PGStatus.PGError.OK: + return OPFDialogAnswer.PROCEED + else: + return OPFDialogAnswer.ABORT + + @staticmethod + def opf_145_handler(dptx, dprx, message: str) -> OPFDialogAnswer: + if not OPFFunctions.check_dptx(dptx): + return OPFDialogAnswer.ABORT + + increase_lines, decrease_lines, max_lanes, min_lanes = OPFFunctions.as_parse_edid(dptx) + + dptx.pg.set_as_config(as_config=ZigzagASParams(min_lanes=min_lanes, + max_lanes=max_lanes, + increase_lines=increase_lines, + decrease_lines=decrease_lines)) + + dptx.pg.apply() + res_app = dptx.pg.status().error + + def is_adaptive_sync_enabled(_dptx): + time.sleep(0.5) + status = _dptx.pg[0].adaptive_sync_status() + return status + + if not function_scheduler(is_adaptive_sync_enabled, dptx, interval=3, timeout=30): + return OPFDialogAnswer.ABORT + + if res_app == PGStatus.PGError.OK: + return OPFDialogAnswer.PROCEED + else: + return OPFDialogAnswer.ABORT + + @staticmethod + def opf_150_handler(dptx, dprx, message: str) -> OPFDialogAnswer: + return OPFFunctions.opf_pass_handler(dptx, dprx, message) + + @staticmethod + def opf_151_handler(hdtx, hdrx, width, height, rate, color_format, is_block_prediction_enabled, bpc, + bpp, h_slice_number, buffer_bit_depth, v_slice_number, dsc_v_minor, dsc_v_major): + + if not OPFFunctions.check_hdtx(hdtx): + return OPFDialogAnswer.ABORT + + logging.info(width, height, rate, color_format, is_block_prediction_enabled, bpc, bpp, + h_slice_number, + buffer_bit_depth, v_slice_number, dsc_v_major, dsc_v_minor) + + color_mode = ColorInfo() + color_mode.color_format = OPFFunctions.dict_color_formats.get(color_format) + col_rgb = color_mode.color_format == ColorInfo.ColorFormat.CF_RGB + color_mode.bpc = bpc + color_mode.colorimetry = ColorInfo.Colorimetry.CM_sRGB if col_rgb else ColorInfo.Colorimetry.CM_ITUR_BT709 + + h_slice_size = int(calculate_slice_size(width, h_slice_number)) + v_slice_size = int(calculate_slice_size(height, v_slice_number)) + OPFFunctions.g_h_slice_size = h_slice_size + OPFFunctions.g_v_slice_size = v_slice_size + + compression_info = OPFFunctions.get_compression_info(v_slice_size, h_slice_size, bpp, color_format, + buffer_bit_depth, is_block_prediction_enabled, + dsc_v_major, dsc_v_minor, False) + + data_info = DataInfo() + data_info.packing = DataInfo.Packing.P_PACKED if col_rgb else DataInfo.Packing.P_PLANAR + data_info.component_order = DataInfo.ComponentOrder.CO_RGB if col_rgb else DataInfo.ComponentOrder.CO_YCbCr + data_info.alignment = DataInfo.Alignment.A_LSB + + try: + encoded_video_frame = OPFFunctions.check_dsc_content_library(width, height, color_mode, + is_block_prediction_enabled, bpc, bpp, + h_slice_number, v_slice_number, + compression_info, dsc_v_major, + dsc_v_minor, data_info) + + except BaseException as e: + logging.info(e) + return OPFDialogAnswer.ABORT + + OPFFunctions.g_vm = copy.deepcopy(encoded_video_frame) + + hdtx.pg.set_pattern(pattern=encoded_video_frame) + res = hdtx.pg.apply() + + if not res and hdtx.pg.status().error != PGStatus.PGError.OK: + return OPFDialogAnswer.ABORT + + return OPFDialogAnswer.PROCEED + + @staticmethod + def opf_161_handler(dptx, dprx, message: str) -> OPFDialogAnswer: + return OPFDialogAnswer.PROCEED + + @staticmethod + def as_parse_edid(dptx): + edid_data = dptx.edid.read_i2c() + display_id_data = dptx.edid._parser.find_main_block(MainBlockType.DisplayID, edid_data) + if len(display_id_data) == 0: + return OPFDialogAnswer.ABORT + + as_data = dptx.edid._parser.find_additional_block(AdditionalBlockType.AdaptiveSync, display_id_data) + if len(as_data) == 0: + return OPFDialogAnswer.ABORT + + increase_lines = as_data[4] + decrease_lines = as_data[8] + min_ref_rate = round(as_data[5] / 1.001, 3) + frame_rate = dptx.link.status.stream(0).video_mode.timing.frame_rate / 1000 + v_total = dptx.link.status.stream(0).video_mode.timing.vtotal + max_lanes = round(v_total * (frame_rate - min_ref_rate) / min_ref_rate) + + max_ref_rate = round((int.from_bytes(as_data[6: 8], "little") + 1) * 1.00035) + min_lanes = round(v_total * (frame_rate - max_ref_rate) / max_ref_rate) + + return increase_lines, decrease_lines, max_lanes, min_lanes + + @staticmethod + def check_dsc_content_library(*args): + width, height, color_mode, is_block_prediction_enabled, bpc, bpp, h_slice_number, v_slice_number, \ + compression_info, dsc_v_major, dsc_v_minor, data_info = args + if OPFFunctions.dsc_content_library_path != "": + if not os.path.exists(OPFFunctions.dsc_content_library_path): + os.makedirs(OPFFunctions.dsc_content_library_path) + dsc_image_name = f"{width}x{height}_{OPFFunctions.dict_dsc_color_names.get(compression_info.color_format)}_" \ + f"{'BPY' if is_block_prediction_enabled else 'NBP'}_bpc{bpc}_" \ + f"bpp{bpp}_{h_slice_number}slicew_{v_slice_number}sliceh_" \ + f"{compression_info.buffer_bit_depth}lb_v{dsc_v_major}{dsc_v_minor}.dsc" + full_path = os.path.join(OPFFunctions.dsc_content_library_path, dsc_image_name) + if os.path.exists(full_path): + # + # Temporary message "Update DSC folder" + # + warnings.warn("Since version 3.5.X/3.6.X the usual file for generating DSC images has been updated." + "Please update/regenerate your DSC folder/library with using new file.") + + with open(full_path, 'rb') as file: + data = bytearray(file.read()) + encoded_video_frame = dsc_video_frame_from_data(data) + else: + vf = generate_pattern_as_vf(PatternType.Unigraf, width, height, color_mode, data_info) + encoded_video_frame = encode_video_frame(vf, compression_info) + with open(full_path, 'wb') as file: + file.write(encoded_video_frame.data) + else: + vf = generate_pattern_as_vf(PatternType.Unigraf, width, height, color_mode, data_info) + encoded_video_frame = encode_video_frame(vf, compression_info) + + return encoded_video_frame + + @staticmethod + def get_compression_info(*args): + v_slice_size, h_slice_size, bpp, color_mode, buffer_bit_depth, is_block_prediction_enabled, dsc_v_major, \ + dsc_v_minor, is_simple_as_444 = args + + compression_info = CompressionInfo() + compression_info.v_slice_size = v_slice_size + compression_info.h_slice_size = h_slice_size + compression_info.bpp = bpp + compression_info.color_format = OPFFunctions.dict_dsc_color_formats.get(color_mode) + compression_info.buffer_bit_depth = buffer_bit_depth + compression_info.is_block_prediction_enabled = is_block_prediction_enabled + compression_info.version = (dsc_v_major, dsc_v_minor) + compression_info.is_simple_as_444 = is_simple_as_444 + + return compression_info diff --git a/UniTAP/dev/modules/opf/parsers/__init__.py b/UniTAP/dev/modules/opf/parsers/__init__.py new file mode 100644 index 0000000..10069b9 --- /dev/null +++ b/UniTAP/dev/modules/opf/parsers/__init__.py @@ -0,0 +1 @@ +from .main_handle import OPFParametersParser diff --git a/UniTAP/dev/modules/opf/parsers/main_handle.py b/UniTAP/dev/modules/opf/parsers/main_handle.py new file mode 100644 index 0000000..5d4f6f4 --- /dev/null +++ b/UniTAP/dev/modules/opf/parsers/main_handle.py @@ -0,0 +1,44 @@ +from .opf_utils import * + + +class OPFParametersParser: + @staticmethod + def parse(opf_id: int, *args) -> list: + if opf_id in [1]: + return parse_link_training(args[3]) + if opf_id in [2]: + return parse_video_mode_2(args[3]) + if opf_id in [3, 4, 5, 7, 8, 11, 12, 13, 14, 15, 16, 17, 101, 102, 104, 105, 106, 121, 122, 123, 145, 150, 152, 161]: + return parse_message(args[1]) + if opf_id in [6]: + return parse_video_mode_6(args[1], args[3]) + if opf_id in [9]: + return parse_video_mode_9(args[3], args[1]) + if opf_id in [10]: + return parse_opf_10(args[3]) + if opf_id in [18]: + return parse_video_mode_18(args[3]) + if opf_id in [19]: + return parse_video_mode_19(args[3]) + if opf_id in [20]: + return parse_video_mode_20(args[3], args[1]) + if opf_id in [21]: + return parse_opf_21(args[3]) + if opf_id in [103]: + return parse_video_mode_103(args[3]) + if opf_id in [120]: + return parse_video_mode_120(args[3]) + if opf_id in [140]: + return parse_video_mode_140(args[3]) + if opf_id in [141]: + return parse_video_mode_141(args[3]) + if opf_id in [142]: + return [args[1]] + if opf_id in [143]: + return parse_video_mode_143(args[3]) + if opf_id in [144]: + return parse_video_mode_144(args[3]) + if opf_id in [151]: + return parse_video_mode_103(args[3]) + else: + raise ValueError(f"Received OPF request with unknown code: {opf_id}") diff --git a/UniTAP/dev/modules/opf/parsers/opf_utils.py b/UniTAP/dev/modules/opf/parsers/opf_utils.py new file mode 100644 index 0000000..7989b17 --- /dev/null +++ b/UniTAP/dev/modules/opf/parsers/opf_utils.py @@ -0,0 +1,327 @@ +import re +from typing import List, Optional + +from UniTAP.libs.lib_tsi.tsi_types import TSI_OPF_PARAMETER + + +def parse_link_training(parameters_list: List[TSI_OPF_PARAMETER]): + dp_lanes = 0 + dp_link_rate = 0 + encoding = None + + for param in parameters_list: + if param.name == 'Lane Count': + dp_lanes, = re.findall(r'\d+', param.description) + if param.name == 'Link Rate': + dp_link_rate = re.findall(r'\d+', param.description) + if len(dp_link_rate) == 0: + dp_link_rate = 0 + elif len(dp_link_rate) == 2: + dp_link_rate = f"{dp_link_rate[0]}.{dp_link_rate[1]}" + if len(parameters_list) >= 3: + if param.name == 'Encoding': + encoding = re.findall(r'\d+\w+/\d+\w+', param.description) + if len(encoding) == 0: + encoding = "8b/10b" + else: + encoding = encoding[0] + + return int(dp_lanes), float(dp_link_rate), encoding + + +def parse_video_mode_2(parameters_list: List[TSI_OPF_PARAMETER]): + res_x = 0 + res_y = 0 + res_frate = 0 + res_bpc = 0 + tim_std = None + tim_rb = None + + for param in parameters_list: + if param.name == 'Video Mode': + result = re.findall(r'\d+|RB1|RB2|VIC \d+|DMT \w+|CVT \w+', param.description) + if len(result) == 5: + res_x, res_y, res_frate, res_bpc, tim_std = result + else: + res_x, res_y, res_frate, res_bpc, tim_rb, tim_std = result + + return int(res_x), int(res_y), int(float(res_frate) * 1000), int(res_bpc), tim_std, tim_rb + + +def parse_video_mode_6(pattern_request: str, parameters_list: List[TSI_OPF_PARAMETER]): + res_x = 0 + res_y = 0 + res_frate = 0 + res_bpc = 0 + col_format = 0 + col_range = 0 + col_yc = None + timing_standard = None + + pattern_name = re.findall(r'Color Ramp|Color Square', pattern_request)[0] + + for param in parameters_list: + if param.name == 'Video Mode': + result = re.findall(r'\d+|RB1|RB2', param.description) + if len(result) == 3: + res_x, res_y, res_frate = result + else: + res_x, res_y, res_frate, timing_standard = result + elif param.name == 'Color Format': + result = re.findall(r'RGB|YCbCr 4:4:4|YCbCr 4:2:2|YCbCr 4:2:0|Simple 4:2:2|ITU-601|ITU-709|\d+|VESA|CTA', + param.description) + if len(result) == 3: + col_format, res_bpc, col_range = result + elif len(result) == 4: + col_format, col_yc, res_bpc, col_range = result + + return int(res_x), int(res_y), int(float(res_frate) * 1000), int( + res_bpc), timing_standard, col_format, col_range, col_yc, pattern_name + + +def parse_video_mode_9(parameters_list: List[TSI_OPF_PARAMETER], pattern_request: Optional[str] = None): + res_x = 0 + res_y = 0 + res_frate = 0 + res_bpc = 0 + tim_std = None + pattern_name = None + + if pattern_request is not None: + pattern_name = re.findall(r'Color Ramp|Color Square', pattern_request)[0] + + for param in parameters_list: + if param.name == 'Video Mode': + result = re.findall(r'\d+|RB1|RB2', param.description) + if len(result) == 4: + res_x, res_y, res_frate, res_bpc = result + elif len(result) == 5: + res_x, res_y, res_frate, tim_std, res_bpc = result + + return int(res_x), int(res_y), int(float(res_frate) * 1000), int(res_bpc), tim_std, pattern_name + + +def parse_opf_10(parameters_list: List[TSI_OPF_PARAMETER]): + res_x, res_y, res_frate, res_bpc, timing_standard = [0] * 5 + + audio_pattern = '' + ch_count = 0 + sample_freq = 0 + sample_size = 0 + ch_alloc = 0 + ch_info = [] + + for ind, param in enumerate(parameters_list): + if param.name == 'Video Mode' and param.description != "Video is not required": + res_x, res_y, res_frate, res_bpc, timing_standard, _ = parse_video_mode_9(parameters_list=parameters_list) + elif param.name == 'Audio Pattern': + audio_pattern = param.description + elif param.name == 'Channel Count': + ch_count, = re.findall(r'\d+', param.description) + elif param.name == 'Sample Frequency': + sample_freq, = re.findall(r'\d+', param.description) + elif param.name == 'Sample Size': + sample_size, = re.findall(r'\d+', param.description) + elif param.name == 'Channel Allocation' and ind == 5: + ch_alloc = re.findall(r'FL/FR|LFE1|FC|BL/BR|BC|FLC/FRC|RLC/RRC|FLW/FRW|TpFL/TpFR|TpC|TpFC|LS/RS|LFE2|TpBC' + r'|SiL/SiR|TpSiL/TpSiR|TpBL/TpBR|BtFC|BtFL/BtFR|TpLS/TpRS', param.description) + elif param.name == 'Channel Allocation' and ind > 5: + ch_info.append(re.findall(r'\d+', param.description)[0]) + + return int(res_x), int(res_y), int(res_frate), int(res_bpc), timing_standard, audio_pattern, \ + int(ch_count), int(sample_freq), int(sample_size), ch_alloc, ch_info if len(ch_info) > 0 else None + + +def parse_message(message: str): + return [str(message)] + + +def parse_video_mode_19(parameters_list: List[TSI_OPF_PARAMETER]): + use_3tap = False + for param in parameters_list: + if param.name == 'Conversion type filter': + use_3tap = param.description.find('3-Tap Filter') != -1 + return [use_3tap] + + +def parse_video_mode_18(parameters_list: List[TSI_OPF_PARAMETER]): + res_x = 0 + res_y = 0 + res_frate = 0 + res_bpc = 0 + col_format = 0 + col_range = 0 + col_yc = 0 + tim_std = 0 + + for param in parameters_list: + if param.name == 'Video Mode': + res_x, res_y, res_frate, tim_std = re.findall(r'\d+|CTA|RB1|RB2', param.description) + elif param.name == 'Color Format': + color_format_str = param.description.replace("ITU-709", '') + col_format, res_bpc, col_range = re.findall( + r'RGB|YCbCr 4:2:2|YCbCr 4:4:4|YCbCr 4:2:0|Simple 4:2:2|\d+|VESA|CTA', color_format_str) + col_yc = 'ITU-709' if col_format != 'RGB' else None + + return int(res_x), int(res_y), int(float(res_frate) * 1000), int(res_bpc), col_format, col_range, col_yc, tim_std + + +def parse_video_mode_20(parameters_list: List[TSI_OPF_PARAMETER], pattern_request: Optional[str] = None): + pattern_name = None + res_x = 0 + res_y = 0 + res_frate = 0 + res_bpc = 0 + col_format = 0 + col_range = 0 + col_yc = None + tim_std = "" + enable_dsc = False + + if pattern_request is not None: + if pattern_request.find('DSC compressed') != -1: + enable_dsc = True + else: + pattern_name = re.findall(r'No Video|Color Ramp|Color Square|Black and Vertical lines|Any', pattern_request)[0] + enable_dsc = False + + for param in parameters_list: + if param.name == 'Video Mode': + line = param.description.split("Hz") + res_x, res_y, res_frate = re.findall(r'\d+', line[0]) + tim_std = re.findall(r'VIC \d+|DMT \w+|UFG \d+|CVT RB\d+|CVT|OVT', line[1]) + elif param.name == 'Color Format': + color_format_str = param.description + result = re.findall(r'RGB|YCbCr 4:2:2|YCbCr 4:4:4|YCbCr 4:2:0|Simple 4:2:2|ITU-601|ITU-709|\d+|VESA|CTA', + color_format_str) + if len(result) == 3: + col_format, res_bpc, col_range = result + elif len(result) == 4: + col_format, col_yc, res_bpc, col_range = result + + return int(res_x), int(res_y), int(float(res_frate) * 1000), int(res_bpc), col_format, col_range, col_yc, tim_std,\ + pattern_name, enable_dsc + + +def parse_opf_21(parameters_list: List[TSI_OPF_PARAMETER]): + audio_pattern = '' + ch_count = 0 + sample_freq = 0 + sample_size = 0 + ch_alloc = 0 + ch_info = [] + + for ind, param in enumerate(parameters_list): + if param.name == 'Audio Pattern': + audio_pattern = param.description + elif param.name == 'Channel Count': + ch_count, = re.findall(r'\d+', param.description) + elif param.name == 'Sample Frequency': + sample_freq, = re.findall(r'\d+', param.description) + elif param.name == 'Sample Size': + sample_size, = re.findall(r'\d+', param.description) + elif param.name == 'Channel Allocation' and ind == 5: + ch_alloc = re.findall(r'FL/FR|LFE1|FC|BL/BR|BC|FLC/FRC|RLC/RRC|FLW/FRW|TpFL/TpFR|TpC|TpFC|LS/RS|LFE2|TpBC' + r'|SiL/SiR|TpSiL/TpSiR|TpBL/TpBR|BtFC|BtFL/BtFR|TpLS/TpRS', param.description) + elif param.name == 'Channel Allocation' and ind > 5: + ch_info.append(re.findall(r'\d+', param.description)[0]) + + return (audio_pattern, int(ch_count), int(sample_freq), int(sample_size), ch_alloc, + ch_info if len(ch_info) > 0 else None) + + +def parse_video_mode_103(parameters_list: List[TSI_OPF_PARAMETER]): + width = 0 + height = 0 + res_frate = 60000 + color_format = "" + block_prediction = False + bpc = 8 + bpp = 8 + h_slice_number = 0 + buffer_bit_depth = 8 + v_slice_number = 0 + dsc_v_minor = 1 + dsc_v_major = 1 + + for param in parameters_list: + if param.name == "Video mode": + width, height, res_frate = re.findall(r'\d+', param.description) + elif param.name == "Color format": + color_format = re.findall(r'RGB|YCbCr 4:2:2|YCbCr 4:4:4|YCbCr 4:2:0|Simple 4:2:2', param.description)[0] + elif param.name == 'Block prediction': + block_prediction = False if param.description.find("disabled") != -1 else True + elif param.name == 'Bits per component': + bpc = re.findall(r'\d+', param.description)[0] + elif param.name == 'Bits per pixel (1/16 units)': + bpp = re.findall(r'\d+', param.description)[2] + elif param.name == 'Horizontal slice number': + h_slice_number = re.findall(r'\d+', param.description)[0] + elif param.name == 'Buffer bit depth': + buffer_bit_depth = re.findall(r'\d+', param.description)[0] + elif param.name == 'Vertical slice number': + v_slice_number = re.findall(r'\d+', param.description)[0] + elif param.name == 'DSC Algorithm revision': + dsc_v_minor, dsc_v_major = re.findall(r'\d+', param.description) + + return int(width), int(height), int(float(res_frate) * 1000), color_format, block_prediction, int(bpc), int(bpp),\ + int(h_slice_number), int(buffer_bit_depth), int(v_slice_number), int(dsc_v_major), int(dsc_v_minor) + + +def parse_video_mode_120(parameters_list: List[TSI_OPF_PARAMETER]): + res_x = 0 + res_y = 0 + res_frate = 0 + res_bpc = 0 + color_format = "" + + for param in parameters_list: + if param.name == "Video pattern": + res_x, res_y, res_frate, res_bpc, color_format = re.findall(r'\d+|RGB|YCbCr 4:2:2|YCbCr 4:4:4|YCbCr 4:2:0|Simple 4:2:2|YCbCr422|YCbCr444|YCbCr420|Simple422', param.description) + + return int(res_x), int(res_y), int(float(res_frate) * 1000), int(res_bpc), color_format + + +def parse_video_mode_140(parameters_list: List[TSI_OPF_PARAMETER]): + res_x = 0 + res_y = 0 + res_frate = 0 + tim_std = 0 + tim_std_num = None + + for param in parameters_list: + if param.name == 'Timing': + result = re.findall(r'\d[a-fA-F]h|\d+|VIC|DMT|RB1|RB2|RB3', param.description) + if len(result) == 5: + res_x, res_y, res_frate, tim_std, tim_std_num = result + elif len(result) == 4: + res_x, res_y, res_frate, tim_std = result + + return int(res_x), int(res_y), int(float(res_frate) * 1000), tim_std, tim_std_num + + +def parse_video_mode_141(parameters_list: List[TSI_OPF_PARAMETER]): + audio_format = '' + channels = 0 + size = 0 + rate = 0 + + for param in parameters_list: + if param.name == 'Audio': + audio_format, channels, size, rate = re.findall(r'LPCM|\d+.\d+|\d+', param.description) + + return audio_format, int(channels), int(size), int(float(rate) * 1000) + + +def parse_video_mode_143(parameters_list: List[TSI_OPF_PARAMETER]): + res_frate = 0 + + for param in parameters_list: + if param.name == 'Refresh Rate': + res_frate = re.findall(r'\d+', param.description)[0] + + return [int(float(res_frate))] + + +def parse_video_mode_144(parameters_list: List[TSI_OPF_PARAMETER]): + return [parameters_list[0].description] diff --git a/UniTAP/dev/modules/terminal/__init__.py b/UniTAP/dev/modules/terminal/__init__.py new file mode 100644 index 0000000..d2a3fab --- /dev/null +++ b/UniTAP/dev/modules/terminal/__init__.py @@ -0,0 +1 @@ +from .dev_terminal import Terminal diff --git a/UniTAP/dev/modules/terminal/dev_terminal.py b/UniTAP/dev/modules/terminal/dev_terminal.py new file mode 100644 index 0000000..ae414ea --- /dev/null +++ b/UniTAP/dev/modules/terminal/dev_terminal.py @@ -0,0 +1,58 @@ +import time +from UniTAP.libs.lib_tsi.tsi_io import DeviceIO +from UniTAP.libs.lib_tsi.tsi import TSI_TERMINAL_RW +from ctypes import c_char + + +class Terminal: + + def __init__(self, device_io: DeviceIO): + self.__io = device_io + + def write_vr(self, register: int, value: int): + self.__execute(f"vw {hex(register)} {value}") + + def read_vr(self, register: int) -> str: + self.__execute(f"vr {hex(register)}") + return self.__get_logs() + + def write_fifo_vr(self, register: int, value: int): + self.__execute(f"vfw {hex(register)} {value}") + + def read_fifo_vr(self, register: int) -> str: + self.__execute(f"vfr {hex(register)}") + return self.__get_logs() + + def run_command(self, command: str) -> str: + self.__execute(command) + return self.__get_logs() + + def __execute(self, command): + command += '\r' + str_len = (len(command) / 4 + (1 if len(command) % 4 else 0)) * 4 + self.__io.set(config_id=TSI_TERMINAL_RW, + data=command, + data_type=c_char, + data_count=len(command), + data_size=int(str_len)) + + def __get_logs(self, timeout: int = 5) -> str: + try: + start = time.time() + result = [] + while time.time() - start < timeout: + result = self.__io.get(TSI_TERMINAL_RW, c_char, 1024)[1] + if result[0] != b'\x00': + break + + output = "" + for i in result: + if i != b'\x00': + output += i.decode() + + if output == "": + print("No output message") + return output + except BaseException: + print("No output message") + return "" diff --git a/UniTAP/dev/ports/__init__.py b/UniTAP/dev/ports/__init__.py new file mode 100644 index 0000000..9b16249 --- /dev/null +++ b/UniTAP/dev/ports/__init__.py @@ -0,0 +1,7 @@ +from .dptx5xx import DPTX, DPTX4xx, DPTX5xx +from .dprx5xx import DPRX, DPRX4xx, DPRX5xx +from .hdrx4xx import HDRX, HDRX4xx +from .hdtx4xx import HDTX, HDTX4xx +from .pdc_port import PDC340, PDC424, PDC500 +from .modules import * +import UniTAP.dev.ports.modules.capturer.bulk as bulk diff --git a/UniTAP/dev/ports/dprx.py b/UniTAP/dev/ports/dprx.py new file mode 100644 index 0000000..f2fb187 --- /dev/null +++ b/UniTAP/dev/ports/dprx.py @@ -0,0 +1,117 @@ +from .rx import * +from .modules.dpcd.dpcd import DPCDRegisters +from .modules.link.dp.link_rx import LinkDisplayPortRx +from .modules.edid.edid import EdidSink +from .modules.hdcp import HdcpSink +from .modules.capturer.event.event_capturer import EventCapturer, EventFilterDpRx, EventFilterUsbc +from .modules.link.dp.private_link_rx_types import DPRXHWCaps +from UniTAP.libs.lib_tsi.tsi_types import TSI_DPRX_HDCP_CAPS_R, TSI_DPRX_HDCP_STATUS_R +from UniTAP.libs.lib_tsi.tsi_types import TSI_DPRX_DPCD_BASE_W, TSI_DPRX_DPCD_DATA +from UniTAP.libs.lib_tsi.tsi_types import TSI_PDC_LOG_CONTROL, TSI_DPRX_HW_CAPS_R +from UniTAP.libs.lib_tsi.tsi_io import PortProtocol + + +class DPRX(RX): + + """ + + Main class of `DPRX` object. + Inherited from class `RX`. + Class describes capabilities of 300th (3XX) series of DP and USB-C devices in Sink (RX - receiver) role. + + Attributes: + __link (LinkDisplayPortRx): object of `LinkDisplayPortRx`. + __dpcd (DPCDRegisters): object of `DPCDRegisters`. + __edid (EdidSink): object of `EdidSink`. + __hdcp (HdcpSink): object of `HdcpSink`. + __event_capturer (EventCapturer): object of `EventCapturer`. + __video_capturer (VideoCapturer): object of `VideoCapturerDP`. + """ + + __CHECK_EVENT_FILTER = {PortProtocol.DisplayPort: [EventFilterDpRx], + PortProtocol.DisplayPortThrowUSBC: [EventFilterDpRx, EventFilterUsbc]} + + def __init__(self, port_io: PortIO, memory_manager: MemoryManager, capturer: Capturer): + super().__init__(port_io, memory_manager, capturer) + + hw_caps = port_io.get(TSI_DPRX_HW_CAPS_R, DPRXHWCaps)[1] + self.__dpcd = DPCDRegisters(port_io, TSI_DPRX_DPCD_BASE_W, TSI_DPRX_DPCD_DATA) + self.__link = LinkDisplayPortRx(port_io, hw_caps, self.__dpcd) + self.__edid = EdidSink(port_io, self.__link.status.mst_stream_count) + self.__hdcp = HdcpSink(port_io, TSI_DPRX_HDCP_CAPS_R, TSI_DPRX_HDCP_STATUS_R) + self.__video_capturer = VideoCapturerDP(capturer, hw_caps.mst_stream_count) + + event_filters = [] + for item in self.__CHECK_EVENT_FILTER.get(port_io.protocol()): + if item == EventFilterDpRx: + event_filters.append(item(hw_caps)) + else: + event_filters.append(item(None)) + + self.__event_capturer = EventCapturer(capturer, port_io.index(), event_filters) + + @property + def link(self) -> LinkDisplayPortRx: + """ + + Should be used to control link capabilities on Sink (RX - receiver) role. + + Returns: + object of `LinkDisplayPortRx` type. + """ + return self.__link + + @property + def dpcd(self) -> DPCDRegisters: + """ + + Should be used to work with DPCD registers on Sink (RX - receiver) role. + + Returns: + object of `DPCDRegisters` type. + """ + return self.__dpcd + + @property + def edid(self) -> EdidSink: + """ + + Should be used to work with EDID on Sink (RX - receiver) role. + + Returns: + object of `EdidSink` type. + """ + return self.__edid + + @property + def hdcp(self) -> HdcpSink: + """ + + Should be used to work with HDCP on Sink (RX - receiver) role. + + Returns: + object of `HdcpSink`. + """ + return self.__hdcp + + @property + def event_capturer(self) -> EventCapturer: + """ + + Should be used to control `EventCapturer` on Sink (RX - receiver) role. + + Returns: + object of `EventCapturer` type. + """ + return self.__event_capturer + + @property + def video_capturer(self) -> VideoCapturerDP: + """ + + Should be used to control `VideoCapturerDP` on Sink (RX - receiver) role. + + Returns: + object of `VideoCapturerDP` type. + """ + return self.__video_capturer diff --git a/UniTAP/dev/ports/dprx4xx.py b/UniTAP/dev/ports/dprx4xx.py new file mode 100644 index 0000000..251850b --- /dev/null +++ b/UniTAP/dev/ports/dprx4xx.py @@ -0,0 +1,89 @@ +from .dprx import * +from .modules.fec import FecRx +from .modules.capturer.bulk.bulk_capturer import BulkCapturer +from .modules.edid.edid import DisplayIdSink +from .modules.panel_replay import SinkPanelReplay, SinkPanelSelfRefresh +from typing import Optional + + +class DPRX4xx(DPRX): + + """ + + Main class of `DPRX4xx` object. + Inherited from class `DPRX`. + Class describes capabilities of 400th (4XX) series of DP and USB-C devices in Sink (RX - receiver) role. + + Attributes: + __fec (FecTx): object of `FecTx`. + __bulk_capturer (BulkCapturer): object of `BulkCapturer` + __display_id (DisplayIdSink): object of `DisplayIdSink` + __panel_replay (SinkPanelReplay): object of `SinkPanelReplay` + __psr (SinkPanelSelfRefresh): object of `SinkPanelSelfRefresh` + + """ + + def __init__(self, port_io: PortIO, memory_manager: MemoryManager, capturer: Capturer): + super().__init__(port_io, memory_manager, capturer) + + hw_caps = port_io.get(TSI_DPRX_HW_CAPS_R, DPRXHWCaps)[1] + self.__fec = FecRx(port_io, self.dpcd) + self.__bulk_capturer = BulkCapturer(capturer, memory_manager) + self.__display_id = DisplayIdSink(port_io, hw_caps.mst_stream_count) + self.__panel_replay = SinkPanelReplay(port_io) if hw_caps.pr != 0 else None + self.__psr = SinkPanelSelfRefresh(port_io) if hw_caps.psr != 0 else None + + @property + def fec(self) -> FecRx: + """ + + Should be used to control FEC functionality on Sink (RX - receiver) side. + + Returns: + object of `FecRx` type. + """ + return self.__fec + + @property + def bulk_capturer(self) -> BulkCapturer: + """ + + Should be used to control Bulk capturer functionality on Sink (RX - receiver) side. + + Returns: + object of `BulkCapturer` type. + """ + return self.__bulk_capturer + + @property + def display_id(self) -> DisplayIdSink: + """ + + Should be used to control DisplayID functionality on Sink (RX - receiver) side. + + Returns: + object of `DisplayIdSink` type. + """ + return self.__display_id + + @property + def panel_replay(self) -> Optional[SinkPanelReplay]: + """ + + Should be used to control Panel Replay on Sink (RX - receiver) side. + + Returns: + object of `SinkPanelReplay` type. + """ + return self.__panel_replay + + @property + def panel_self_refresh(self) -> Optional[SinkPanelSelfRefresh]: + """ + + Should be used to control Panel Self Refresh on Sink (RX - receiver) side. + + Returns: + object of `SinkPanelSelfRefresh` type. + """ + return self.__psr diff --git a/UniTAP/dev/ports/dprx5xx.py b/UniTAP/dev/ports/dprx5xx.py new file mode 100644 index 0000000..82ef1c5 --- /dev/null +++ b/UniTAP/dev/ports/dprx5xx.py @@ -0,0 +1,18 @@ +from .dprx4xx import * + + +class DPRX5xx(DPRX4xx): + + """ + + Main class of `DPRX5xx` object. + Inherited from class `DPRX4xx`. + Class describes capabilities of 500th (5XX) series of DP and USB-C devices in Sink (RX - receiver) role. + + Attributes: + __panel_replay (SinkPanelReplay): object of `SinkPanelReplay`. + __psr (SinkPanelSelfRefresh): object of `SinkPanelSelfRefresh`. + """ + + def __init__(self, port_io: PortIO, memory_manager: MemoryManager, capturer: Capturer): + super().__init__(port_io, memory_manager, capturer) diff --git a/UniTAP/dev/ports/dptx.py b/UniTAP/dev/ports/dptx.py new file mode 100644 index 0000000..7436f59 --- /dev/null +++ b/UniTAP/dev/ports/dptx.py @@ -0,0 +1,118 @@ +from .tx import * +from .modules import DPCDRegisters, LinkDisplayPortTx +from .modules.edid.edid import EdidSource +from .modules.vtg.pg import DpPatternGenerator +from .modules.hdcp import HdcpSource +from .modules.capturer.event.event_capturer import EventCapturer, EventFilterDpTx, EventFilterUsbc +from .modules.link.dp.private_link_tx_types import DPTXHWCaps +from UniTAP.libs.lib_tsi.tsi_types import TSI_DPTX_DPCD_BASE_W, TSI_DPTX_DPCD_DATA +from UniTAP.libs.lib_tsi.tsi_types import TSI_DPTX_HDCP_CAPS_R, TSI_DPTX_HDCP_STATUS_R +from UniTAP.libs.lib_tsi.tsi_types import TSI_DPTX_HW_CAPS_R +from UniTAP.libs.lib_tsi.tsi_io import PortProtocol + + +class DPTX(TX): + + """ + + Main class of `DPTX` object. + Inherited from class `TX`. + Class describes capabilities of 300th (3XX) series of DP and USB-C devices in Source (TX - transmitter) role. + + Attributes: + __link (LinkDisplayPortTx): object of `LinkDisplayPortTx`. + __dpcd (DPCDRegisters): object of `DPCDRegisters`. + __edid (EdidSource): object of `EdidSource`. + __hdcp (HdcpSource): object of `HdcpSource`. + __pg (DpPatternGenerator): object of `DpPatternGenerator`. + __event_capturer (EventCapturer): object of `EventCapturer`. + """ + + __CHECK_EVENT_FILTER = {PortProtocol.DisplayPort: [EventFilterDpTx], + PortProtocol.DisplayPortThrowUSBC: [EventFilterDpTx, EventFilterUsbc]} + + def __init__(self, port_io: PortIO, memory_manager: MemoryManager, capturer: Capturer): + super().__init__(port_io, memory_manager, capturer) + + hw_caps = port_io.get(TSI_DPTX_HW_CAPS_R, DPTXHWCaps)[1] + self.__dpcd = DPCDRegisters(port_io, TSI_DPTX_DPCD_BASE_W, TSI_DPTX_DPCD_DATA) + self.__link = LinkDisplayPortTx(port_io, self.__dpcd, hw_caps) + self.__pg = DpPatternGenerator(port_io, memory_manager, 0) + self.__edid = EdidSource(port_io, self.__link.max_stream_count) + self.__hdcp = HdcpSource(port_io, TSI_DPTX_HDCP_CAPS_R, TSI_DPTX_HDCP_STATUS_R) + # event_filters = [e(hw_caps) for e in self.__CHECK_EVENT_FILTER.get(port_io.protocol())] + + event_filters = [] + for item in self.__CHECK_EVENT_FILTER.get(port_io.protocol()): + if item == EventFilterDpTx: + event_filters.append(item(hw_caps)) + else: + event_filters.append(item(None)) + + self.__event_capturer = EventCapturer(capturer, port_io.index(), event_filters) + + @property + def dpcd(self) -> DPCDRegisters: + """ + + Should be used to work with DPCD registers on Source (TX - transmitter) side. + + Returns: + object of `DPCDRegisters` type. + """ + return self.__dpcd + + @property + def pg(self) -> DpPatternGenerator: + """ + + Should be used to control Pattern generator functionality on Source (TX - transmitter) side. + + Returns: + object of `DpPatternGenerator` type. + """ + return self.__pg + + @property + def link(self) -> LinkDisplayPortTx: + """ + + Should be used to control link settings on Source (TX - transmitter) side. + + Returns: + object of `LinkDisplayPortTx` type. + """ + return self.__link + + @property + def edid(self) -> EdidSource: + """ + + Should be used to work with EDID on Source (TX - transmitter) side. + + Returns: + object of `EdidSource` type. + """ + return self.__edid + + @property + def hdcp(self) -> HdcpSource: + """ + + Should be used to work with HDCP on Source (TX - transmitter) side. + + Returns: + object of `HdcpSource`. + """ + return self.__hdcp + + @property + def event_capturer(self) -> EventCapturer: + """ + + Should be used to control `EventCapturer` on Source (TX - transmitter) role. + + Returns: + object of `EventCapturer` type. + """ + return self.__event_capturer diff --git a/UniTAP/dev/ports/dptx4xx.py b/UniTAP/dev/ports/dptx4xx.py new file mode 100644 index 0000000..fba65ba --- /dev/null +++ b/UniTAP/dev/ports/dptx4xx.py @@ -0,0 +1,62 @@ +from .dptx import * +from .modules.fec import FecTx, FECCounters, FECErrorType8b10b +from .modules.vtg.pg import DpMstPatternGenerator +from .modules.edid.edid import DisplayIdSource + + +class DPTX4xx(DPTX): + + """ + + Main class of `DPTX4xx` object. + Inherited from class `DPTX`. + Class describes capabilities of 400th (4XX) series of DP and USB-C devices in Source (TX - transmitter) role. + + Attributes: + __fec (FecTx): object of `FecTx`. + __pg (DpMstPatternGenerator): object of `DpMstPatternGenerator` + + """ + + def __init__(self, port_io: PortIO, memory_manager: MemoryManager, capturer: Capturer): + super().__init__(port_io, memory_manager, capturer) + + hw_caps = port_io.get(TSI_DPTX_HW_CAPS_R, DPTXHWCaps)[1] + self.__fec = FecTx(port_io, self.dpcd) + self.__pg = DpMstPatternGenerator(port_io, memory_manager, hw_caps.mst_stream_count) + self.__display_id = DisplayIdSource(port_io, hw_caps.mst_stream_count) + + @property + def fec(self) -> FecTx: + """ + + Should be used to control FEC functionality on Source (TX - transmitter) side. + + Returns: + object of `FecTx` type. + """ + return self.__fec + + @property + def pg(self) -> DpMstPatternGenerator: + """ + + Should be used to control Pattern generator functionality on Source (TX - transmitter) side. + `DpMstPatternGenerator` contain list of `DpPatternGenerator` objects. For access to element in list, + use expression `pg.[index]`. + + Returns: + object of `DpMstPatternGenerator` type. + """ + return self.__pg + + @property + def display_id(self) -> DisplayIdSource: + """ + + Should be used to control DisplayID functionality on Source (TX - transmitter) side. + + Returns: + object of `DisplayIdSource` type. + """ + return self.__display_id \ No newline at end of file diff --git a/UniTAP/dev/ports/dptx5xx.py b/UniTAP/dev/ports/dptx5xx.py new file mode 100644 index 0000000..954bb8a --- /dev/null +++ b/UniTAP/dev/ports/dptx5xx.py @@ -0,0 +1,15 @@ +from .dptx4xx import * + + +class DPTX5xx(DPTX4xx): + + """ + + Main class of `DPTX5xx` object. + Inherited from class `DPTX4xx`. + Class describes capabilities of 500th (5XX) series of DP and USB-C devices in Source (TX - transmitter) role. + + """ + + def __init__(self, port_io: PortIO, memory_manager: MemoryManager, capturer: Capturer): + super().__init__(port_io, memory_manager, capturer) diff --git a/UniTAP/dev/ports/hdrx.py b/UniTAP/dev/ports/hdrx.py new file mode 100644 index 0000000..72add26 --- /dev/null +++ b/UniTAP/dev/ports/hdrx.py @@ -0,0 +1,102 @@ +from .rx import * +from .modules.edid.edid import EdidSink +from .modules.link.hdmi.link import HdmiLinkRx +from .modules.hdcp import HdcpSink +from .modules.cec.cec_rx import CecRx +from .modules.capturer.event.event_capturer import EventCapturer, EventFilterHdRx +from UniTAP.libs.lib_tsi.tsi_types import TSI_HDRX_HDCP_CAPS_R, TSI_HDRX_HDCP_STATUS_R +from UniTAP.libs.lib_tsi.tsi_types import TSI_HDRX_LOG_CONTROL + + +class HDRX(RX): + + """ + + Main class of `HDRX` object. + Inherited from class `RX`. + Class describes capabilities of 300th (3XX) series of HDMI devices in Sink (RX - receiver) role. + + Attributes: + __link (HdmiLinkRx): object of `HdmiLinkRx`. + __edid (EdidSink): object of `EdidSink`. + __hdcp (HdcpSink): object of `HdcpSink`. + __event_capturer (EventCapturer): object of `EventCapturer`. + __video_capturer (VideoCapturer): object of `VideoCapturerHDMI`. + __cec (CecRx): object of `CecRx`. + """ + + def __init__(self, port_io: PortIO, memory_manager: MemoryManager, capturer: Capturer): + super().__init__(port_io, memory_manager, capturer) + + self.__link = HdmiLinkRx(port_io) + self.__edid = EdidSink(port_io, 1) + self.__hdcp = HdcpSink(port_io, TSI_HDRX_HDCP_CAPS_R, TSI_HDRX_HDCP_STATUS_R) + self.__event_capturer = EventCapturer(capturer, port_io.index(), [EventFilterHdRx(0)]) + self.__video_capturer = VideoCapturerHDMI(capturer, 1) + self.__cec = CecRx(port_io) + + @property + def link(self) -> HdmiLinkRx: + """ + + Should be used to control link capabilities on Sink (RX - receiver) role. + + Returns: + object of `HdmiLinkRx` type. + """ + return self.__link + + @property + def edid(self) -> EdidSink: + """ + + Should be used to work with EDID on Sink (RX - receiver) role. + + Returns: + object of `EdidSink` type. + """ + return self.__edid + + @property + def hdcp(self) -> HdcpSink: + """ + + Should be used to work with HDCP on Sink (RX - receiver) role. + + Returns: + object of `HdcpSink`. + """ + return self.__hdcp + + @property + def event_capturer(self) -> EventCapturer: + """ + + Should be used to control `EventCapturer` on Sink (RX - receiver) role. + + Returns: + object of `EventCapturer` type. + """ + return self.__event_capturer + + @property + def video_capturer(self) -> VideoCapturerHDMI: + """ + + Should be used to control `VideoCapturerHDMI` on Sink (RX - receiver) role. + + Returns: + object of `VideoCapturerHDMI` type. + """ + return self.__video_capturer + + @property + def cec(self) -> CecRx: + """ + + Should be used to control `CecRx` on Sink (RX - receiver) role. + + Returns: + object of `CecRx` type. + """ + return self.__cec diff --git a/UniTAP/dev/ports/hdrx4xx.py b/UniTAP/dev/ports/hdrx4xx.py new file mode 100644 index 0000000..9f43ae8 --- /dev/null +++ b/UniTAP/dev/ports/hdrx4xx.py @@ -0,0 +1,15 @@ +from .hdrx import * + + +class HDRX4xx(HDRX): + + """ + + Main class of `HDRX4xx` object. + Inherited from class `HDRX`. + Class describes capabilities of 400th (4XX) series of HDMI devices in Sink (RX - receiver) rolee. + + """ + + def __init__(self, port_io: PortIO, memory_manager: MemoryManager, capturer: Capturer): + super().__init__(port_io, memory_manager, capturer) diff --git a/UniTAP/dev/ports/hdtx.py b/UniTAP/dev/ports/hdtx.py new file mode 100644 index 0000000..461b846 --- /dev/null +++ b/UniTAP/dev/ports/hdtx.py @@ -0,0 +1,103 @@ +from .tx import * +from .modules.edid.edid import EdidSource +from .modules.link.hdmi.link import HdmiLinkTx +from .modules.vtg.pg import HdmiPatternGenerator +from .modules.hdcp import HdcpSource +from .modules.capturer.event.event_capturer import EventCapturer, EventFilterHdTx +from .modules.cec.cec_tx import CecTx +from UniTAP.libs.lib_tsi.tsi_types import TSI_HDTX_HDCP_CAPS_R, TSI_HDTX_HDCP_STATUS_R +from UniTAP.libs.lib_tsi.tsi_types import TSI_HDTX_LOG_CONTROL + + +class HDTX(TX): + + """ + + Main class of `HDTX` object. + Inherited from class `TX`. + Class describes capabilities of 300th (3XX) series of HDMI devices in Source (TX - transmitter) role. + + Attributes: + __link (HdmiLinkTx): object of `HdmiLinkTx`. + __edid (EdidSource): object of `EdidSource`. + __hdcp (HdcpSource): object of `HdcpSource`. + __pg (HdmiPatternGenerator): object of `HdmiPatternGenerator`. + __event_capturer (EventCapturer): object of `EventCapturer`. + __cec (CecTx): object of `CecTx`. + """ + + def __init__(self, port_io: PortIO, memory_manager: MemoryManager, capturer: Capturer): + super().__init__(port_io, memory_manager, capturer) + + self.__link = HdmiLinkTx(port_io) + self.__edid = EdidSource(port_io, 1) + self.__pg = HdmiPatternGenerator(port_io, memory_manager) + self.__hdcp = HdcpSource(port_io, TSI_HDTX_HDCP_CAPS_R, TSI_HDTX_HDCP_STATUS_R) + self.__event_capturer = EventCapturer(capturer, port_io.index(), [EventFilterHdTx(0)]) + self.__cec = CecTx(port_io) + + @property + def link(self) -> HdmiLinkTx: + """ + + Should be used to control link settings on Source (TX - transmitter) side. + + Returns: + object of `HdmiLinkTx` type. + """ + return self.__link + + @property + def pg(self) -> HdmiPatternGenerator: + """ + + Should be used to control Pattern generator functionality on Source (TX - transmitter) side. + + Returns: + object of `HdmiPatternGenerator` type. + """ + return self.__pg + + @property + def edid(self): + """ + + Should be used to work with EDID on Source (TX - transmitter) side. + + Returns: + object of `EdidSource` type. + """ + return self.__edid + + @property + def hdcp(self) -> HdcpSource: + """ + + Should be used to work with HDCP on Source (TX - transmitter) side. + + Returns: + object of `HdcpSource`. + """ + return self.__hdcp + + @property + def event_capturer(self) -> EventCapturer: + """ + + Should be used to control `EventCapturer` on Source (TX - transmitter) role. + + Returns: + object of `EventCapturer` type. + """ + return self.__event_capturer + + @property + def cec(self) -> CecTx: + """ + + Should be used to control `CecTx` on Source (TX - transmitter) role. + + Returns: + object of `CecTx` type. + """ + return self.__cec diff --git a/UniTAP/dev/ports/hdtx4xx.py b/UniTAP/dev/ports/hdtx4xx.py new file mode 100644 index 0000000..6096adc --- /dev/null +++ b/UniTAP/dev/ports/hdtx4xx.py @@ -0,0 +1,16 @@ +from .hdtx import * + + +class HDTX4xx(HDTX): + + """ + + Main class of `HDTX4xx` object. + Inherited from class `HDTX`. + Class describes capabilities of 400th (4XX) series of HDMI devices in Source (TX - transmitter) role. + + """ + + def __init__(self, port_io: PortIO, memory_manager: MemoryManager, capturer: Capturer): + super().__init__(port_io, memory_manager, capturer) + diff --git a/UniTAP/dev/ports/modules/__init__.py b/UniTAP/dev/ports/modules/__init__.py new file mode 100644 index 0000000..f218638 --- /dev/null +++ b/UniTAP/dev/ports/modules/__init__.py @@ -0,0 +1,11 @@ +from .dpcd import * +from .link import * +from .fec import * +from .vtg import * +from .ag import * +from .hdcp import * +from .capturer import * +from .edid import * +from .panel_replay import * +import UniTAP.dev.ports.modules.cec as cec +import UniTAP.dev.ports.modules.pdc as pdc diff --git a/UniTAP/dev/ports/modules/ag/__init__.py b/UniTAP/dev/ports/modules/ag/__init__.py new file mode 100644 index 0000000..23e43d5 --- /dev/null +++ b/UniTAP/dev/ports/modules/ag/__init__.py @@ -0,0 +1 @@ +from .ag import AudioPattern diff --git a/UniTAP/dev/ports/modules/ag/ag.py b/UniTAP/dev/ports/modules/ag/ag.py new file mode 100644 index 0000000..e3ed288 --- /dev/null +++ b/UniTAP/dev/ports/modules/ag/ag.py @@ -0,0 +1,156 @@ +from UniTAP.libs.lib_tsi.tsi_types import * +from UniTAP.utils import function_scheduler +from .ag_utils import * +from UniTAP.libs.lib_tsi.tsi_io import PortIO, PortProtocol +from UniTAP.dev.modules import MemoryManager +from .types import * +from .private_types import * +from typing import Union + + +class AudioGenerator: + """ + Class `AudioGenerator` allows working with generating audio from Source (TX - transmitter). You can configure + audio generator `setup`, apply settings and start generate audio `apply`, stop generate audio `stop_generate`, + read audio generator `status` and get current `audio_mode`. + """ + def __init__(self, port_io: PortIO, memory_manager: MemoryManager): + self.__io = port_io + self.__memory_manager = memory_manager + self.__status = AGStatus.Unknown + self.__audio_mode = AudioMode() + + def setup(self, audio_mode: AudioMode = AudioMode(), + audio_pattern: Union[AudioPattern, str] = AudioPattern.SignalSine, + signal_frequency: int = 1000, amplitude: int = 60): + """ + Configure audio generator. Possible two variants of configuration: + - From 'wav' or 'bin' file. + - From `AudioPattern` parameters. + + Args: + audio_mode (AudioMode) - object of `AudioMode` + audio_pattern (Union[AudioPattern, str]) - object of `AudioPattern` or path to audio file ('bin' or 'wave') + signal_frequency (int) + amplitude (int) + """ + self.stop_generate() + if isinstance(audio_pattern, str) and check_file_format(audio_pattern) != AudioFileFormat.UNKNOWN: + if check_file_format(audio_pattern) == AudioFileFormat.BIN: + data, _, size = load_from_bin_file(path=audio_pattern) + else: + data, audio_mode, size = load_from_wave_file(path=audio_pattern) + + self.__memory_manager.make_default() + self.__memory_manager.set_memory_block_index(MemoryManager.MemoryOwner.MO_AudioGenerator) + self.__io.set(TSI_AUDGEN_SIGNAL_BLOCK, MemoryManager.MemoryOwner.MO_AudioGenerator.value) + self.__memory_manager.memory_write(data, size) + self.__io.set(TSI_AUDGEN_AUDIO_SIZE, size) + + ag_struct = self.__io.get(TSI_AUDGEN_CONFIG, AudioConfigStructure)[1] + ag_struct.non_lpcm = 0 + ag_struct.loop = 1 + + audio_sts = create_audio_sts(audio_mode=audio_mode) + self.__io.set(TSI_AUDGEN_CHANNELS_STS, audio_sts, data_type=c_uint8, data_count=len(audio_sts)) + + if self.__io.protocol() == PortProtocol.HDMI: + tp = 0 + + if audio_mode.channel_count == 8 and audio_mode.sample_rate >= 64000: + tp = 3 + + ag_struct.n_selector = 1 + ag_struct.cts_selector = 1 + + self.__io.set(TSI_AUDGEN_CONFIG, ag_struct.value()) + + self.__io.set(TSI_AUDGEN_PACKET_TYPE, tp) + else: + self.__io.set(TSI_AUDGEN_CONFIG, ag_struct.value()) + + self.__io.set(TSI_AUDGEN_SIGNAL_TYPE, AudioPattern.CustomAudio.value) + self.__io.set(TSI_AUDGEN_CHANNEL_COUNT, audio_mode.channel_count) + self.__io.set(TSI_AUDGEN_SAMPLE_RATE, audio_mode.sample_rate) + self.__io.set(TSI_AUDGEN_SAMPLE_SIZE, audio_mode.bits) + elif isinstance(audio_pattern, AudioPattern): + list_sample_rate = [22050, 44100, 88200, 176400, 24000, 48000, 96000, 192000, 32000, 768000] + list_bits = [16, 20, 24] + audio_pattern = AudioPattern(audio_pattern.value) + + if not (1 <= audio_mode.channel_count <= 8): + raise ValueError(f"'Channel count' must be in range: 1 - 8") + + if audio_mode.sample_rate not in list_sample_rate: + raise ValueError(f"'Sample rate' must be in list {list_sample_rate}") + + if audio_mode.bits not in list_bits: + raise ValueError(f"'Bits' must be in list {list_bits}") + + if signal_frequency <= 0: + raise ValueError(f"'Signal frequency' must be more than 0") + + if amplitude not in [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]: + raise ValueError(f"'Amplitude' must be more than 0") + + memory_block_no = 2 + + self.__memory_manager.make_default() + self.__io.set(TSI_AUDGEN_SIGNAL_BLOCK, memory_block_no) + self.__io.set(TSI_AUDGEN_SIGNAL_TYPE, audio_pattern.value) + self.__io.set(TSI_AUDGEN_CHANNEL_COUNT, audio_mode.channel_count) + self.__io.set(TSI_AUDGEN_SAMPLE_RATE, audio_mode.sample_rate) + self.__io.set(TSI_AUDGEN_SAMPLE_SIZE, audio_mode.bits) + self.__io.set(TSI_AUDGEN_SIGNAL_FREQ, signal_frequency) + self.__io.set(TSI_AUDGEN_SIGNAL_VOLUME, amplitude) + else: + available_variants = '\n'.join([e.name for e in AudioPattern]) + raise ValueError(f"Incorrect value of audio pattern - {audio_pattern}\n" + f"Available audio patterns: {available_variants}") + + def apply(self) -> bool: + """ + Apply settings and start generate audio. + + Returns: + object of `bool` type - generation was enabled successfully or not. + """ + self.__io.set(TSI_W_AUDGEN_CONTROL, 1) + + def is_apply_ag_success(ag: AudioGenerator): + return ag.status == AGStatus.Running + + return function_scheduler(is_apply_ag_success, self, interval=1, timeout=10) + + def stop_generate(self) -> bool: + """ + Stop generate audio. + + Returns: + object of `bool` type - generation was disabled successfully or not. + """ + return True if self.__io.set(TSI_W_AUDGEN_CONTROL, 0) >= TSI_SUCCESS else False + + @property + def status(self) -> AGStatus: + """ + Return audio generator status. + + Returns: + object of `AGStatus` type + """ + self.__status = AGStatus(self.__io.get(TSI_AUDGEN_STATUS_R, c_int)[1] & 0x1) + return self.__status + + @property + def audio_mode(self) -> AudioMode: + """ + Return current audio mode. + + Returns: + object of `AudioMode` type + """ + self.__audio_mode.sample_rate = self.__io.get(TSI_AUDGEN_SAMPLE_RATE, c_int32)[1] + self.__audio_mode.bits = self.__io.get(TSI_AUDGEN_SAMPLE_SIZE, c_int32)[1] + self.__audio_mode.channel_count = self.__io.get(TSI_AUDGEN_CHANNEL_COUNT, c_int32)[1] + return self.__audio_mode diff --git a/UniTAP/dev/ports/modules/ag/ag_utils.py b/UniTAP/dev/ports/modules/ag/ag_utils.py new file mode 100644 index 0000000..453bc9f --- /dev/null +++ b/UniTAP/dev/ports/modules/ag/ag_utils.py @@ -0,0 +1,109 @@ +import os.path +import warnings +import wave +import pickle +from collections import deque +from UniTAP.common.audio_mode import AudioMode, AudioFileFormat +from UniTAP.libs.lib_tsi.tsi import sizeof, c_uint32 + + +def save_to_wave_file(path: str, audio_mode: AudioMode, data: bytearray): + if len(data) <= 0: + raise ValueError(f"Audio data must not be empty! Current size {len(data)}") + + file = wave.Wave_write(path + ".wav") + file.setframerate(audio_mode.sample_rate) + file.setnchannels(audio_mode.channel_count) + file.setsampwidth(int(audio_mode.bits / 8)) + file.writeframesraw(data) + file.close() + + +def save_to_bin_file(path: str, data: bytearray): + if len(data) <= 0: + raise ValueError(f"Audio data must not be empty! Current size {len(data)}") + + bin_file = open(path + '.bin', 'wb') + bin_file.write(data) + bin_file.close() + + +def load_from_bin_file(path: str): + file = open(path, "rb") + data = pickle.load(file) + file.close() + + return data, AudioMode(), len(data) + + +def load_from_wave_file(path: str): + try: + data = wave.open(path, 'rb') + except wave.Error: + raise NotImplementedError("Python Wave package doesn't support not PCM formats. Please, use PCM format WAV " + "file.") + + channels = 2 if data.getnchannels() <= 2 else 8 + size = data.getnframes() * channels * sizeof(c_uint32) + sizeof(c_uint32) + channel_count = data.getnchannels() + sample_rate = data.getframerate() + bits = data.getsampwidth() * 8 + bytes_block = data.readframes(size) + data.close() + ret_list = [0] * size + + list_of_data = deque(list(bytes_block)) + for i in range(0, len(ret_list), (channels * sizeof(c_uint32))): + for j in range(data.getsampwidth() * channel_count): + if len(list_of_data) == 0: + break + idx = i // (channels * sizeof(c_uint32)) * channels * sizeof(c_uint32) + j + chunk_num = j // data.getsampwidth() + addition = chunk_num * data.getsampwidth() + if data.getsampwidth() == 2: + ret_list[idx + addition + 1] = list_of_data.popleft() + else: + ret_list[idx + addition] = list_of_data.popleft() + + audio_mode = AudioMode() + audio_mode.channel_count = channel_count + audio_mode.sample_rate = sample_rate + audio_mode.bits = bits + + return bytes(ret_list), audio_mode, len(ret_list) + + +def check_file_format(path: str): + if not os.path.exists(path): + return AudioFileFormat.UNKNOWN + + filename, file_extension = os.path.splitext(path) + if file_extension.lower() == 'bin': + return AudioFileFormat.BIN + elif file_extension.lower() not in ["wav", "wave"]: + return AudioFileFormat.WAV + else: + return AudioFileFormat.UNKNOWN + + +def create_audio_sts(audio_mode: AudioMode): + sts_b = [0] * 48 + + sts_b[0] = 0 + sts_b[0] |= 4 + sts_b[24] = sts_b[0] + rate = audio_mode.sample_rate + bits = audio_mode.bits + + dict_sample_rate = {22050: 4, 44100: 0, 88200: 8, 176400: 12, 24000: 6, 48000: 2, 96000: 10, 192000: 14, + 32000: 3, + 768000: 9} + dict_bits = {16: 2, 20: 10, 24: 11} + smpl = dict_sample_rate.get(rate) + sts_b[3] = smpl + sts_b[27] = sts_b[3] + smlen = dict_bits.get(bits) + sts_b[4] = smlen + sts_b[28] = sts_b[4] + + return sts_b diff --git a/UniTAP/dev/ports/modules/ag/private_types.py b/UniTAP/dev/ports/modules/ag/private_types.py new file mode 100644 index 0000000..3dac6ba --- /dev/null +++ b/UniTAP/dev/ports/modules/ag/private_types.py @@ -0,0 +1,19 @@ +from ctypes import Structure, c_uint32 + + +class AudioConfigStructure(Structure): + _fields_ = [ + ('auto_ch_sts', c_uint32, 1), + ('use_raw_data', c_uint32, 1), + ('non_lpcm', c_uint32, 1), + ('loop', c_uint32, 1), + ('auto_info_frame', c_uint32, 1), + ('', c_uint32, 3), + ('n_selector', c_uint32, 4), + ('cts_selector', c_uint32, 4), + ('', c_uint32, 16), + ] + + def value(self) -> int: + return self.auto_ch_sts | self.use_raw_data << 1 | self.non_lpcm << 2 | self.loop << 3 | \ + self.auto_info_frame << 4 | self.n_selector << 8 | self.cts_selector << 12 diff --git a/UniTAP/dev/ports/modules/ag/types.py b/UniTAP/dev/ports/modules/ag/types.py new file mode 100644 index 0000000..252e348 --- /dev/null +++ b/UniTAP/dev/ports/modules/ag/types.py @@ -0,0 +1,22 @@ +from enum import IntEnum + + +class AudioPattern(IntEnum): + """ + Class `AudioPattern` contains all possible variants of audio templates. + """ + SignalSine = 0 + SignalSawtooth = 1 + SignalSquare = 2 + CustomAudio = 3 + SignalIncremental = 4 + Unknown = 5 + + +class AGStatus(IntEnum): + """ + Class `AGStatus` contains all possible variants of Audio generator states. + """ + Unknown = -1 + Stop = 0 + Running = 1 diff --git a/UniTAP/dev/ports/modules/capturer/__init__.py b/UniTAP/dev/ports/modules/capturer/__init__.py new file mode 100644 index 0000000..048b6d3 --- /dev/null +++ b/UniTAP/dev/ports/modules/capturer/__init__.py @@ -0,0 +1,2 @@ +from .event import * +from .video import * diff --git a/UniTAP/dev/ports/modules/capturer/audio/__init__.py b/UniTAP/dev/ports/modules/capturer/audio/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/UniTAP/dev/ports/modules/capturer/audio/audio_capturer.py b/UniTAP/dev/ports/modules/capturer/audio/audio_capturer.py new file mode 100644 index 0000000..67e576b --- /dev/null +++ b/UniTAP/dev/ports/modules/capturer/audio/audio_capturer.py @@ -0,0 +1,117 @@ +import time +import copy +import warnings + +from typing import List + +from UniTAP.common.audio_mode import AudioFrameData +from UniTAP.dev.modules.capturer.capture import Capturer, CaptureConfig +from UniTAP.dev.modules.capturer.statuses import AudioCaptureStatus +from .result_audio import ResultAudioObject + + +class AudioCapturer: + """ + Class `AudioCapturer` allows working with capturing audio frames on Sink (RX - receiver) side. + You can `start` capturing in several modes, `stop` capturing, getting current `status` and result of capturing + `capture_result`. + """ + def __init__(self, capturer: Capturer): + self.__capturer = capturer + self.__result = ResultAudioObject() + + @property + def status(self) -> AudioCaptureStatus: + """ + Returns current audio capturer status. + + Returns: + object of `AudioCaptureStatus` type + """ + return self.__capturer.audio_capturer_status + + @property + def capture_result(self) -> ResultAudioObject: + """ + Returns result of audio capturing. + + Returns: + object of `ResultAudioObject` type + """ + return self.__result + + def start(self, frames_count=0, m_sec=0, timeout=None): + """ + Start capturing. Possible some variants of capturing: + - Capture with fixed frames count (will be captured fixed frames count and capturing will be stopped). + - Capture with fixed audio duration (captures audio for the specified duration in milliseconds). + - Capture without parameters - Live capturing (for getting frames you need to use functions `pop_element`) + - Capture with timeout (maximum duration for capturing operations before stopping, in seconds). + + All results can be obtained using the function `capture_result`. + + Args: + frames_count (int) + m_sec (int) + """ + config = CaptureConfig() + config.audio = True + config.type = CaptureConfig.Type.LIVE + config.video = True + + self.__capturer.start_capture(config) + + self.__result.clear() + self.__result.start_capture_time = time.time() + + if frames_count > 0: + self.__result.buffer.extend(self.__capturer.capture_audio_by_n_frames(frames_count, timeout)) + self.__fill_audio_mode() + elif m_sec > 0: + self.__result.buffer.extend(self.__capturer.capture_audio_by_m_sec(m_sec)) + self.__fill_audio_mode() + + def __fill_audio_mode(self): + if len(self.__result.buffer) > 0: + self.__result.audio_mode.channel_count = self.__result.buffer[0].channel_count + self.__result.audio_mode.bits = self.__result.buffer[0].sample_size + self.__result.audio_mode.sample_rate = self.__result.buffer[0].sample_rate + + def stop(self): + """ + Stop capture audio. + """ + config = CaptureConfig() + config.audio = True + config.type = CaptureConfig.Type.LIVE + config.video = True + + self.__capturer.stop_capture(config) + self.__result.end_capture_time = time.time() + + def pop_element(self) -> List[AudioFrameData]: + """ + Return first object of `AudioFrameData`. + + Returns: + object of `AudioFrameData` type + """ + if self.status == AudioCaptureStatus.Stop: + warnings.warn("Audio capture is not working now. Please, turn it on.") + captured_audio_frames = self.__capturer.capture_audio_by_n_frames(1) + self.__result.buffer.extend(copy.deepcopy(captured_audio_frames)) + self.__fill_audio_mode() + return captured_audio_frames[0] + + def pop_element_as_result_object(self) -> ResultAudioObject: + """ + Return captured audio frame(objects of `AudioFrameData`) as `ResultAudioObject`. + + Returns: + object of `ResultAudioObject` type + """ + captured_audio_frames = self.__capturer.capture_audio_by_n_frames(1) + + res = ResultAudioObject() + res.buffer.extend(captured_audio_frames) + return res diff --git a/UniTAP/dev/ports/modules/capturer/audio/result_audio.py b/UniTAP/dev/ports/modules/capturer/audio/result_audio.py new file mode 100644 index 0000000..277cc7f --- /dev/null +++ b/UniTAP/dev/ports/modules/capturer/audio/result_audio.py @@ -0,0 +1,60 @@ +from UniTAP.dev.modules.capturer.result_object import ResultObject + +from UniTAP.common.audio_mode import AudioMode, AudioFileFormat + +from UniTAP.dev.ports.modules.ag.ag_utils import save_to_bin_file, save_to_wave_file +import warnings + + +class ResultAudioObject(ResultObject): + """ + Class `ResultAudioObject` inherited from class `ResultObject`. + Class `ResultAudioObject` allows saving captured frames to image `save_image_to_file`. + Also has all the `ResultObject` functionality. + """ + def __init__(self): + super().__init__() + self.__audio_mode = AudioMode() + + @property + def audio_mode(self) -> AudioMode: + """ + Returns current audio mode for captured audio frames. + + Returns: + object of `AudioMode` type + """ + return self.__audio_mode + + def save_to_file(self, file_format: AudioFileFormat, path: str): + """ + Saving audio frames to file. Supported file formats describe in `AudioFileFormat`. + + Args: + file_format (`AudioFileFormat`) - file format + path (str) - path to save + """ + if len(self.buffer) > 0: + if file_format == AudioFileFormat.BIN: + save_to_bin_file(path=path, data=self.__convert_buffer()) + elif file_format == AudioFileFormat.WAV: + save_to_wave_file(path=path, audio_mode=self.audio_mode, data=self.__convert_buffer()) + else: + raise ValueError(f"Incorrect audio format. Available formats: " + f"{AudioFileFormat.BIN.name}, {AudioFileFormat.WAV.name}.\n" + f"Transferred audio format: {file_format.name}") + else: + warnings.warn("Buffer size is equal 0.") + + def __convert_buffer(self): + data = [] + for audio_frame in self.buffer: + data.append(audio_frame.data) + return bytearray().join(data) + + def __str__(self): + return f"Start capture time: {self.start_capture_time}\n" \ + f"End capture time: {self.end_capture_time}\n" \ + f"Timestamp: {self.timestamp.__str__()}\n" \ + f"Audio mode:\n{self.audio_mode.__str__()}\n" + diff --git a/UniTAP/dev/ports/modules/capturer/bulk/__init__.py b/UniTAP/dev/ports/modules/capturer/bulk/__init__.py new file mode 100644 index 0000000..d7d9be7 --- /dev/null +++ b/UniTAP/dev/ports/modules/capturer/bulk/__init__.py @@ -0,0 +1 @@ +from .bulk_types import TriggerType, TriggerPosition, TriggerTypeEnum diff --git a/UniTAP/dev/ports/modules/capturer/bulk/bulk_capturer.py b/UniTAP/dev/ports/modules/capturer/bulk/bulk_capturer.py new file mode 100644 index 0000000..3a89bd8 --- /dev/null +++ b/UniTAP/dev/ports/modules/capturer/bulk/bulk_capturer.py @@ -0,0 +1,132 @@ +import warnings +import time + +from typing import Optional + +from UniTAP.dev.modules.capturer.capture import Capturer, CaptureConfig +from UniTAP.dev.modules.memory_manager import MemoryManager +from UniTAP.dev.ports.modules.capturer.bulk.bulk_types import (TriggerPosition, TriggerVarType, _get_trigger_value, + EncodingTypeEnum, LaneCountEnum) +from UniTAP.dev.ports.modules.capturer.bulk.result_bulk import ResultBulkObject + +megabyte = 1024 * 1024 + + +class BulkCapturer: + """ + Class `BulkCapturer` allows working with capturing Bulk data on Sink (RX - receiver) side. + You can `start` capturing in several modes, `stop` capturing, getting current `status` and result of + capturing `capture_result`. + """ + def __init__(self, capturer: Capturer, memory_manager: MemoryManager): + self.__capturer = capturer + self.__memory_manager = memory_manager + self.__trigger_position = TriggerPosition.TP_Start + + self.__result = ResultBulkObject() + + @property + def status(self): + """ + Returns current bulk capturer status. + + Returns: + object of `VideoCaptureStatus` type + """ + return self.__capturer.bulk_capturer_status + + @property + def capture_result(self) -> ResultBulkObject: + """ + Returns result of bulk capturing. + + Returns: + object of `ResultBulkObject` type + """ + return self.__result + + @property + def encoding_type(self) -> EncodingTypeEnum: + """ + Returns current encoding type of capturing. + + Returns: + object of `EncodingTypeEnum` type + """ + return self.__capturer.read_encoding_type() + + @property + def lane_count(self) -> LaneCountEnum: + """ + Returns current lane count for capturing. + + Returns: + object of `LaneCountEnum` type + """ + return self.__capturer.read_lane_count() + + def start(self, bulk_size: int = 1, trigger_position: TriggerPosition = TriggerPosition.TP_Start, + trigger_config: Optional[TriggerVarType] = None, assume_scrambler: bool = False, gpio: bool = False, + encoding_type: Optional[EncodingTypeEnum] = None, lane_count: Optional[LaneCountEnum] = None): + """ + Start capturing. All results can be obtained using the function `capture_result`. + + Args: + bulk_size (int) - bulk data size in megabytes + trigger_position (`TriggerPosition`) + trigger_config (`TriggerVarType`|None) + assume_scrambler (bool) + gpio (bool) + encoding_type (`EncodingTypeEnum`|None) + lane_count (`LaneCountEnum`|None) + """ + self.__result.clear() + + if trigger_config is None: + trigger_position = TriggerPosition.TP_Start + + capture_config = CaptureConfig() + capture_config.video = True + capture_config.event = True + capture_config.audio = True + capture_config.type = CaptureConfig.Type.LIVE + self.__capturer.stop_capture(capture_config) + self.__capturer.stop_bulk_capture() + self.__capturer.clear_bulk_buffer() + + max_bulk_block_bytes = self.__memory_manager.get_total_memory() - self.__memory_manager.RESERVED_MEMORY_BYTES + bulk_block_bytes = max_bulk_block_bytes if bulk_size * megabyte > max_bulk_block_bytes else bulk_size * megabyte + self.__memory_manager.set_memory_layout([bulk_block_bytes]) + self.__capturer.write_bulk_size(bulk_block_bytes) + + if encoding_type is None: + encoding_type = EncodingTypeEnum.Encoding_Auto + self.__capturer.write_encoding_type(encoding_type) + + if lane_count is None: + lane_count = LaneCountEnum.Auto + self.__capturer.write_lane_count(lane_count) + + if self.__capturer.read_bulk_capture_caps().bitGrabWidth: + self.__capturer.write_bulk_gpio(gpio) + + self.__capturer.write_bulk_trigger_position(trigger_position.value) + + if self.__capturer.read_bulk_capture_caps().triggers: + self.__capturer.write_bulk_trigger_settings(*_get_trigger_value(trigger_config, + self.__capturer.read_bulk_trigger_caps())) + else: + warnings.warn("Triggers are not supported.") + + self.__result.assume_scrambler_disabled = assume_scrambler + self.__result.start_capture_time = time.time() + + self.__capturer.start_bulk_capture() + self.__result.buffer.extend(self.__capturer.bulk_capture(bulk_block_bytes, trigger_config)) + + def stop(self): + """ + Stop capture video. + """ + self.__capturer.stop_bulk_capture() + self.__result.end_capture_time = time.time() diff --git a/UniTAP/dev/ports/modules/capturer/bulk/bulk_types.py b/UniTAP/dev/ports/modules/capturer/bulk/bulk_types.py new file mode 100644 index 0000000..9b82d87 --- /dev/null +++ b/UniTAP/dev/ports/modules/capturer/bulk/bulk_types.py @@ -0,0 +1,979 @@ +from enum import IntEnum +from typing import TypeVar, Optional + + +class EncodingTypeEnum(IntEnum): + Encoding_Auto = 0, + Encoding_10Bit = 1, + Encoding_32Bit = 2 + + +class LaneCountEnum(IntEnum): + Auto = 0, + Lane_1 = 1, + Lane_2 = 2, + Lane_4 = 4 + + +class TriggerPosition(IntEnum): + """ + Trigger position relative to the start of capture, in percent of total capture size. + - TP_Start – 0% + - TP_25 – 25% + - TP_50 – 50% + - TP_75 – 75% + - TP_End - 100% + """ + TP_Start = 0 + TP_25 = 1 + TP_50 = 2 + TP_75 = 3 + TP_End = 4 + + +class TriggerTypeEnum: + """ + Class `TriggerTypeEnum` contains all necessary enum types for describing values. + """ + + class SourceType(IntEnum): + TPS1 = 0 + TPS2 = 1 + TPS3 = 2 + TPS4 = 3 + + class SourceTypePosition(IntEnum): + InitialLT = 0 + AfterALPM = 1 + InitialLTORAfterALPM = 2 + + class SourceMLPHY(IntEnum): + Standby = 0 + Sleep = 1 + + class SourceVBIDWithMask(IntEnum): + AnyVB_IDChange = 0 + VB_IDMatchWithMask = 1 + ChangeAnyBitSetInMask = 2 + + class SourceVBID(IntEnum): + BS = 0 + SR = 1 + CPBS = 2 + CPSR = 3 + + class SDPTypeReceived(IntEnum): + MatchHB0 = 1 + MatchHB1 = 2 + MatchHB0AndHB1 = 3 + + class MSA(IntEnum): + AnyMSAChange = 0 + ChangeMSAAttribute = 1 + MatchMSAAttribute = 2 + + class Error8b_10b(IntEnum): + CodeError = 0 + DisparityError = 0 + Both = 0 + + class TypeAUX(IntEnum): + NativeWrite = 0x8 + NativeRead = 0x9 + + +class TriggerType: + """ + Main class `TriggerType` defines possible variants of trigger types. + - `U1` - Start of TPS1/TPS2/TPS3/TPS4 - initial LT, after ALPM exit, both. + - `U2` - Exit of TPS1/TPS2/TPS3/TPS4 - initial LT, after ALPM exit, both. + - `U3` - Start of ML_PHY_STANDBY or ML_PHY_SLEEP. + - `U4` - Exit of ML_PHY_STANDBY or ML_PHY_SLEEP. + - `U5` - Start of EIEOS - initial LT, after ALPM exit, both. + - `U6` - Exit of EIEOS - initial LT, after ALPM exit, both. + - `U7` - VB-ID with the MASK - any change, match, selected bit transition. + - `U8` - VB-ID on TYPE - BS/SR/CPBS/CPSR. + - `U9` - Up to 8 control or data symbols, 8b/10b encoded. + - `U10` - SDP Type received – HB0 and/or HB1 match. + - `U11` - MSA – any change, change by mask, match by mask. + - `U12` - 8b/10b error – code error, disparity error, both. + - `U13` - Any AUX transaction - initial LT, after ALPM exit, both. + - `U17` - AUX read or write of specific address. + """ + + class U1: + """ + Class `U1` describes one of the possible trigger type. Allows: + - Get trigger mask `trigger_mask`. + - Set and get source type `source_type` - `TriggerTypeEnum.SourceType`. + - Set and get position `position` - `TriggerTypeEnum.SourceTypePosition. + """ + + def __init__(self): + self.__trigger_mask = 1 + self.__source_type = TriggerTypeEnum.SourceType.TPS1 + self.__position = TriggerTypeEnum.SourceTypePosition.InitialLT + + @property + def trigger_mask(self) -> int: + """ + Returns trigger mask. + + Returns: + object of int value + """ + return self.__trigger_mask + + @property + def source_type(self) -> TriggerTypeEnum.SourceType: + """ + Returns source type. + + Returns: + object of `TriggerTypeEnum.SourceType` value + """ + return self.__source_type + + @property + def position(self) -> TriggerTypeEnum.SourceTypePosition: + """ + Returns position. + + Returns: + object of `TriggerTypeEnum.SourceTypePosition` value + """ + return self.__position + + @source_type.setter + def source_type(self, value: TriggerTypeEnum.SourceType): + self.__source_type = value + + @position.setter + def position(self, value: TriggerTypeEnum.SourceTypePosition): + self.__position = value + + class U2: + """ + Class `U2` describes one of the possible trigger type. Allows: + - Get trigger mask `trigger_mask`. + - Set and get source type `source_type` - `TriggerTypeEnum.SourceType`. + - Set and get position `position` - `TriggerTypeEnum.SourceTypePosition. + """ + + def __init__(self): + self.__trigger_mask = 1 << 1 + self.__source_type = TriggerTypeEnum.SourceType.TPS1 + self.__position = TriggerTypeEnum.SourceTypePosition.InitialLT + + @property + def trigger_mask(self) -> int: + """ + Returns trigger mask. + + Returns: + object of int value + """ + return self.__trigger_mask + + @property + def source_type(self) -> TriggerTypeEnum.SourceType: + """ + Returns source type. + + Returns: + object of `TriggerTypeEnum.SourceType` value + """ + return self.__source_type + + @property + def position(self) -> TriggerTypeEnum.SourceTypePosition: + """ + Returns position. + + Returns: + object of `TriggerTypeEnum.SourceTypePosition` value + """ + return self.__position + + @source_type.setter + def source_type(self, value: TriggerTypeEnum.SourceType): + self.__source_type = value + + @position.setter + def position(self, value: TriggerTypeEnum.SourceTypePosition): + self.__position = value + + class U3: + """ + Class `U3` describes one of the possible trigger type. Allows: + - Get trigger mask `trigger_mask`. + - Set and get source `source` - `TriggerTypeEnum.SourceMLPHY`. + """ + + def __init__(self): + self.__trigger_mask = 1 << 2 + self.__source = TriggerTypeEnum.SourceMLPHY.Standby + + @property + def trigger_mask(self) -> int: + """ + Returns trigger mask. + + Returns: + object of int value + """ + return self.__trigger_mask + + @property + def source(self) -> TriggerTypeEnum.SourceMLPHY: + """ + Returns source. + + Returns: + object of `TriggerTypeEnum.SourceMLPHY` value + """ + return self.__source + + @source.setter + def source(self, value: TriggerTypeEnum.SourceMLPHY): + self.__source = value + + class U4: + """ + Class `U4` describes one of the possible trigger type. Allows: + - Get trigger mask `trigger_mask`. + - Set and get source `source` - `TriggerTypeEnum.SourceMLPHY`. + """ + + def __init__(self): + self.__trigger_mask = 1 << 3 + self.__source = TriggerTypeEnum.SourceMLPHY.Standby + + @property + def trigger_mask(self) -> int: + """ + Returns trigger mask. + + Returns: + object of int value + """ + return self.__trigger_mask + + @property + def source(self) -> TriggerTypeEnum.SourceMLPHY: + """ + Returns source. + + Returns: + object of `TriggerTypeEnum.SourceMLPHY` value + """ + return self.__source + + @source.setter + def source(self, value: TriggerTypeEnum.SourceMLPHY): + self.__source = value + + class U5: + """ + Class `U5` describes one of the possible trigger type. Allows: + - Get trigger mask `trigger_mask`. + - Set and get position `position` - `TriggerTypeEnum.SourceTypePosition`. + """ + + def __init__(self): + self.__trigger_mask = 1 << 4 + self.__position = TriggerTypeEnum.SourceTypePosition.InitialLT + + @property + def trigger_mask(self) -> int: + """ + Returns trigger mask. + + Returns: + object of int value + """ + return self.__trigger_mask + + @property + def position(self) -> TriggerTypeEnum.SourceTypePosition: + """ + Returns position. + + Returns: + object of `TriggerTypeEnum.SourceTypePosition` value + """ + return self.__position + + @position.setter + def position(self, value: TriggerTypeEnum.SourceTypePosition): + self.__position = value + + class U6: + """ + Class `U6` describes one of the possible trigger type. Allows: + - Get trigger mask `trigger_mask`. + - Set and get position `position` - `TriggerTypeEnum.SourceTypePosition`. + """ + + def __init__(self): + self.__trigger_mask = 1 << 5 + self.__position = TriggerTypeEnum.SourceTypePosition.InitialLT + + @property + def trigger_mask(self) -> int: + """ + Returns trigger mask. + + Returns: + object of int value + """ + return self.__trigger_mask + + @property + def position(self) -> TriggerTypeEnum.SourceTypePosition: + """ + Returns position. + + Returns: + object of `TriggerTypeEnum.SourceTypePosition` value + """ + return self.__position + + @position.setter + def position(self, value: TriggerTypeEnum.SourceTypePosition): + self.__position = value + + class U7: + """ + Class `U7` describes one of the possible trigger type. Allows: + - Get trigger mask `trigger_mask`. + - Set and get mask `mask`. + - Set and get source `source` - `TriggerTypeEnum.SourceVBIDWithMask`. + """ + + def __init__(self): + self.__trigger_mask = 1 << 6 + self.__source = TriggerTypeEnum.SourceVBIDWithMask.AnyVB_IDChange + self.__mask = 0 + + @property + def trigger_mask(self) -> int: + """ + Returns trigger mask. + + Returns: + object of int value + """ + return self.__trigger_mask + + @property + def source(self) -> TriggerTypeEnum.SourceVBIDWithMask: + """ + Returns source. + + Returns: + object of `TriggerTypeEnum.SourceVBIDWithMask` value + """ + return self.__source + + @property + def mask(self) -> int: + """ + Returns mask. + + Returns: + object of int value + """ + return self.__mask + + @source.setter + def source(self, value: TriggerTypeEnum.SourceVBIDWithMask): + self.__source = value + + @mask.setter + def mask(self, value: int): + if value < 0: + raise ValueError("Mask must be more than 0.") + self.__mask = value + + class U8: + """ + Class `U8` describes one of the possible trigger type. Allows: + - Get trigger mask `trigger_mask`. + - Set and get source `source` - `TriggerTypeEnum.SourceVBID`. + """ + + def __init__(self): + self.__trigger_mask = 1 << 7 + self.__source = TriggerTypeEnum.SourceVBID.BS + + @property + def trigger_mask(self) -> int: + """ + Returns trigger mask. + + Returns: + object of int value + """ + return self.__trigger_mask + + @property + def source(self) -> TriggerTypeEnum.SourceVBID: + """ + Returns source. + + Returns: + object of `TriggerTypeEnum.SourceVBID` value + """ + return self.__source + + @source.setter + def source(self, value: TriggerTypeEnum.SourceVBID): + self.__source = value + + class U9: + """ + Class `U9` describes one of the possible trigger type. Allows: + - Get trigger mask `trigger_mask`. + - Set and get value for count of symbols `count`. + - Set and get value for symbol 0 `symbol_0`. + - Set and get value for symbol 1 `symbol_1`. + - Set and get value for symbol 2 `symbol_2`. + - Set and get value for symbol 3 `symbol_3`. + - Set and get value for symbol 4 `symbol_4`. + - Set and get value for symbol 5 `symbol_5`. + - Set and get value for symbol 6 `symbol_6`. + - Set and get value for symbol 7 `symbol_7`. + """ + + def __init__(self): + self.__trigger_mask = 1 << 8 + self.__symbol_0 = 0 + self.__symbol_1 = 0 + self.__symbol_2 = 0 + self.__symbol_3 = 0 + self.__symbol_4 = 0 + self.__symbol_5 = 0 + self.__symbol_6 = 0 + self.__symbol_7 = 0 + self.__count = 0 + + @property + def trigger_mask(self) -> int: + """ + Returns trigger mask. + + Returns: + object of int value + """ + return self.__trigger_mask + + @property + def count(self) -> int: + """ + Returns count of symbols. + + Returns: + object of int value + """ + return self.__count + + @count.setter + def count(self, value: int): + if value < 0: + raise ValueError("Mask must be more than 0 and length must be less than 5") + self.__count = value + + @property + def symbol_0(self) -> int: + """ + Returns value of symbol 0. + + Returns: + object of int value + """ + return self.__symbol_0 + + @symbol_0.setter + def symbol_0(self, value: int): + if value < 0 or len(str(value)) >= 5: + raise ValueError("Mask must be more than 0 and length must be less than 5") + self.__symbol_0 = value + + @property + def symbol_1(self) -> int: + """ + Returns value of symbol 1. + + Returns: + object of int value + """ + return self.__symbol_1 + + @symbol_1.setter + def symbol_1(self, value: int): + if value < 0 or len(str(value)) >= 5: + raise ValueError("Mask must be more than 0 and length must be less than 5") + self.__symbol_1 = value + + @property + def symbol_2(self) -> int: + """ + Returns value of symbol 2. + + Returns: + object of int value + """ + return self.__symbol_2 + + @symbol_2.setter + def symbol_2(self, value: int): + if value < 0 or len(str(value)) >= 5: + raise ValueError("Mask must be more than 0 and length must be less than 5") + self.__symbol_2 = value + + @property + def symbol_3(self) -> int: + """ + Returns value of symbol 3. + + Returns: + object of int value + """ + return self.__symbol_3 + + @symbol_3.setter + def symbol_3(self, value: int): + if value < 0 or len(str(value)) >= 5: + raise ValueError("Mask must be more than 0 and length must be less than 5") + self.__symbol_3 = value + + @property + def symbol_4(self) -> int: + """ + Returns value of symbol 4. + + Returns: + object of int value + """ + return self.__symbol_4 + + @symbol_4.setter + def symbol_4(self, value: int): + if value < 0 or len(str(value)) >= 5: + raise ValueError("Mask must be more than 0 and length must be less than 5") + self.__symbol_4 = value + + @property + def symbol_5(self) -> int: + """ + Returns value of symbol 5. + + Returns: + object of int value + """ + return self.__symbol_5 + + @symbol_5.setter + def symbol_5(self, value: int): + if value < 0 or len(str(value)) >= 5: + raise ValueError("Mask must be more than 0 and length must be less than 5") + self.__symbol_5 = value + + @property + def symbol_6(self) -> int: + """ + Returns value of symbol 6. + + Returns: + object of int value + """ + return self.__symbol_6 + + @symbol_6.setter + def symbol_6(self, value: int): + if value < 0 or len(str(value)) >= 5: + raise ValueError("Mask must be more than 0 and length must be less than 5") + self.__symbol_6 = value + + @property + def symbol_7(self) -> int: + """ + Returns value of symbol 7. + + Returns: + object of int value + """ + return self.__symbol_7 + + @symbol_7.setter + def symbol_7(self, value: int): + if value < 0 or len(str(value)) >= 5: + raise ValueError("Mask must be more than 0 and length must be less than 5") + self.__symbol_7 = value + + class U10: + """ + Class `U10` describes one of the possible trigger type. Allows: + - Get trigger mask `trigger_mask`. + - Set and get SDP type `sdp_type` - `TriggerTypeEnum.SDPTypeReceived`. + - Set and get value for HB 0 `hb0`. + - Set and get value for HB 1 `hb1`. + """ + + def __init__(self): + self.__trigger_mask = 1 << 9 + self.__sdp_type = TriggerTypeEnum.SDPTypeReceived.MatchHB0 + self.__hb0 = 0 + self.__hb1 = 0 + + @property + def trigger_mask(self) -> int: + """ + Returns trigger mask. + + Returns: + object of int value + """ + return self.__trigger_mask + + @property + def sdp_type(self) -> TriggerTypeEnum.SDPTypeReceived: + """ + Returns SDP type. + + Returns: + object of `TriggerTypeEnum.SDPTypeReceived` value + """ + return self.__sdp_type + + @sdp_type.setter + def sdp_type(self, value: TriggerTypeEnum.SDPTypeReceived): + self.__sdp_type = value + + @property + def hb0(self) -> int: + """ + Returns value of HB0. + + Returns: + object of int value + """ + return self.__hb0 + + @hb0.setter + def hb0(self, value: int): + if value < 0: + raise ValueError("Mask must be more than 0.") + self.__hb0 = value + + @property + def hb1(self) -> int: + """ + Returns value of HB1. + + Returns: + object of int value + """ + return self.__hb1 + + @hb1.setter + def hb1(self, value: int): + if value < 0: + raise ValueError("Mask must be more than 0.") + self.__hb1 = value + + class U11: + """ + Class `U11` describes one of the possible trigger type. Allows: + - Get trigger mask `trigger_mask`. + - Set and get source `source` - `TriggerTypeEnum.MSA`. + - Set and get MSa attributes (mvid, nvid, hactive, vactive and so on.) + """ + + def __init__(self): + self.__trigger_mask = 1 << 10 + self.__source = TriggerTypeEnum.MSA.AnyMSAChange + self.mvid_flag = False + self.nvid_flag = False + self.hactive_flag = False + self.vactive_flag = False + self.htotal_flag = False + self.vtotal_flag = False + self.hsyncw_flag = False + self.vsyncw_flag = False + self.hsyncp_flag = False + self.vsyncp_flag = False + self.hsyncs_flag = False + self.vsyncs_flag = False + self.misc0_flag = False + self.misc1_flag = False + + self.mvid = 0 + self.nvid = 0 + self.hactive = 0 + self.vactive = 0 + self.htotal = 0 + self.vtotal = 0 + self.hsyncw = 0 + self.vsyncw = 0 + self.hsyncs = 0 + self.vsyncs = 0 + self.misc0 = 0 + self.misc1 = 0 + + @property + def trigger_mask(self) -> int: + """ + Returns trigger mask. + + Returns: + object of int value + """ + return self.__trigger_mask + + @property + def source(self) -> TriggerTypeEnum.MSA: + """ + Returns source. + + Returns: + object of `TriggerTypeEnum.MSA` value + """ + return self.__source + + @source.setter + def source(self, value: TriggerTypeEnum.MSA): + self.__source = value + + class U12: + """ + Class `U12` describes one of the possible trigger type. Allows: + - Get trigger mask `trigger_mask`. + - Set and get source `source` - `TriggerTypeEnum.Error8b_10b`. + """ + + def __init__(self): + self.__trigger_mask = 1 << 11 + self.__source = TriggerTypeEnum.Error8b_10b.CodeError + + @property + def trigger_mask(self) -> int: + """ + Returns trigger mask. + + Returns: + object of int value + """ + return self.__trigger_mask + + @property + def source(self) -> TriggerTypeEnum.Error8b_10b: + """ + Returns source. + + Returns: + object of `TriggerTypeEnum.Error8b_10b` value + """ + return self.__source + + @source.setter + def source(self, value: TriggerTypeEnum.Error8b_10b): + self.__source = value + + class U13: + """ + Class `U13` describes one of the possible trigger type. Allows: + - Get trigger mask `trigger_mask`. + - Set and get position `position` - `TriggerTypeEnum.SourceTypePosition`. + """ + + def __init__(self): + self.__trigger_mask = 1 << 12 + self.__position = TriggerTypeEnum.SourceTypePosition.InitialLT + + @property + def trigger_mask(self) -> int: + """ + Returns trigger mask. + + Returns: + object of int value + """ + return self.__trigger_mask + + @property + def position(self) -> TriggerTypeEnum.SourceTypePosition: + """ + Returns position. + + Returns: + object of `TriggerTypeEnum.SourceTypePosition` value + """ + return self.__position + + @position.setter + def position(self, value: TriggerTypeEnum.SourceTypePosition): + self.__position = value + + class U17: + """ + Class `U17` describes one of the possible trigger type. Allows: + - Get trigger mask `trigger_mask`. + - Set and get address `address`. + - Set and get type `type` - `TriggerTypeEnum.TypeAUX`. + """ + + def __init__(self): + self.__trigger_mask = 1 << 16 + self.__address = 0 + self.__type = TriggerTypeEnum.TypeAUX.NativeWrite + + @property + def trigger_mask(self) -> int: + """ + Returns trigger mask. + + Returns: + object of int value + """ + return self.__trigger_mask + + @property + def address(self) -> int: + """ + Returns address. + + Returns: + object of int value + """ + return self.__address + + @address.setter + def address(self, value: int): + if value < 0: + raise ValueError("Mask must be more than 0.") + self.__address = value + + @property + def type(self) -> TriggerTypeEnum.TypeAUX: + """ + Returns type. + + Returns: + object of `TriggerTypeEnum.TypeAUX` value + """ + return self.__type + + @type.setter + def type(self, value: TriggerTypeEnum.TypeAUX): + self.__type = value + + +TriggerVarType = TypeVar("TriggerVarType", TriggerType.U1, TriggerType.U2, TriggerType.U3, TriggerType.U4, + TriggerType.U5, TriggerType.U6, TriggerType.U7, TriggerType.U8, TriggerType.U9, + TriggerType.U10, TriggerType.U11, TriggerType.U12, TriggerType.U13, TriggerType.U17) + + +def _get_trigger_value(value: Optional[TriggerVarType], caps: int): + trigger_config = [0] * 39 + trigger_config_ext = [0] * 35 + + if value is None: + return 0, trigger_config, trigger_config_ext + + if isinstance(value, TriggerType.U1) and caps & 0x1: + trigger_config[0] |= value.source_type.value + trigger_config[0] |= (value.position.value << 4) + if isinstance(value, TriggerType.U2) and (caps >> 1) & 0x1: + trigger_config[1] |= value.source_type.value + trigger_config[1] |= (value.position.value << 4) + if isinstance(value, TriggerType.U3) and (caps >> 2) & 0x1: + trigger_config[2] |= value.source.value + if isinstance(value, TriggerType.U4) and (caps >> 3) & 0x1: + trigger_config[3] |= value.source.value + if isinstance(value, TriggerType.U5) and (caps >> 4) & 0x1: + trigger_config[4] |= value.position.value << 4 + if isinstance(value, TriggerType.U6) and (caps >> 5) & 0x1: + trigger_config[5] |= value.position.value << 4 + if isinstance(value, TriggerType.U7) and (caps >> 6) & 0x1: + trigger_config[6] |= value.source.value + trigger_config[6] |= value.mask.value << 8 + if isinstance(value, TriggerType.U8) and (caps >> 7) & 0x1: + trigger_config[7] |= value.source.value + if isinstance(value, TriggerType.U9) and (caps >> 8) & 0x1: + trigger_config[8] |= value.count + if value.symbol_0: + trigger_config_ext[0] |= value.symbol_0 + if value.symbol_1: + trigger_config_ext[0] |= (value.symbol_1 << 16) + if value.symbol_0: + trigger_config_ext[1] |= value.symbol_2 + if value.symbol_0: + trigger_config_ext[1] |= (value.symbol_3 << 16) + if value.symbol_0: + trigger_config_ext[2] |= value.symbol_4 + if value.symbol_0: + trigger_config_ext[2] |= (value.symbol_5 << 16) + if value.symbol_0: + trigger_config_ext[3] |= value.symbol_6 + if value.symbol_0: + trigger_config_ext[3] |= (value.symbol_7 << 16) + if isinstance(value, TriggerType.U10) and (caps >> 10) & 0x1: + trigger_config[9] |= value.sdp_type.value + trigger_config[9] |= (value.hb0 << 8) + trigger_config[9] |= (value.hb1 << 16) + if isinstance(value, TriggerType.U11) and (caps >> 11) & 0x1: + trigger_config[10] |= value.source.value + trigger_config[10] |= value.source + trigger_config[10] |= (1 << 8) if value.mvid_flag else 0 + trigger_config[10] |= (1 << 9) if value.nvid_flag else 0 + trigger_config[10] |= (1 << 10) if value.htotal_flag else 0 + trigger_config[10] |= (1 << 11) if value.vtotal_flag else 0 + trigger_config[10] |= (1 << 12) if value.hactive_flag else 0 + trigger_config[10] |= (1 << 13) if value.vactive_flag else 0 + trigger_config[10] |= (1 << 14) if value.hsyncw_flag else 0 + trigger_config[10] |= (1 << 15) if value.vsyncw_flag else 0 + trigger_config[10] |= (1 << 16) if value.hsyncp_flag else 0 + trigger_config[10] |= (1 << 17) if value.vsyncp_flag else 0 + trigger_config[10] |= (1 << 18) if value.hsyncs_flag else 0 + trigger_config[10] |= (1 << 19) if value.vsyncs_flag else 0 + trigger_config[10] |= (1 << 20) if value.misc0_flag else 0 + trigger_config[10] |= (1 << 21) if value.misc1_flag else 0 + + if value.mvid_flag: + trigger_config_ext[4] |= value.mvid & 0xFFFFFF + if value.nvid_flag: + trigger_config_ext[5] |= value.nvid & 0xFFFFFF + if value.htotal_flag: + trigger_config_ext[6] |= value.htotal & 0xFFFF + if value.vtotal_flag: + trigger_config_ext[6] |= ((value.vtotal & 0xFFFF) << 16) + if value.hactive_flag: + trigger_config_ext[7] |= value.hactive & 0xFFFF + if value.vactive_flag: + trigger_config_ext[7] |= ((value.vactive & 0xFFFF) << 16) + if value.hsyncw_flag: + trigger_config_ext[8] |= value.hsyncw & 0x7FFF + if value.hsyncp_flag: + trigger_config_ext[8] |= (value.hsyncp_flag << 15) + if value.vsyncw_flag: + trigger_config_ext[8] |= ((value.vsyncw & 0x7FFF) << 16) + if value.vsyncp_flag: + trigger_config_ext[8] |= (value.vsyncp_flag << 31) + if value.hsyncs_flag: + trigger_config_ext[9] |= value.hsyncs & 0xFFFF + if value.vsyncs_flag: + trigger_config_ext[9] |= ((value.vsyncs & 0xFFFF) << 16) + if value.misc0_flag: + trigger_config_ext[10] |= value.misc0 & 0xFF + if value.misc1_flag: + trigger_config_ext[10] |= ((value.misc1 & 0xFF) << 8) + if isinstance(value, TriggerType.U12) and (caps >> 11) & 0x1: + trigger_config[11] |= value.position + if isinstance(value, TriggerType.U13) and (caps >> 12) & 0x1: + trigger_config[12] |= (value.position << 4) + if isinstance(value, TriggerType.U17) and (caps >> 16) & 0x1: + trigger_config[16] |= (value.type.value << 24) | value.address + + return value.trigger_mask, trigger_config, trigger_config_ext diff --git a/UniTAP/dev/ports/modules/capturer/bulk/private_bulk_types.py b/UniTAP/dev/ports/modules/capturer/bulk/private_bulk_types.py new file mode 100644 index 0000000..61ee919 --- /dev/null +++ b/UniTAP/dev/ports/modules/capturer/bulk/private_bulk_types.py @@ -0,0 +1,45 @@ +from ctypes import Structure, c_uint8, c_uint16, c_uint32, c_uint64 + + +class BulkHeader(Structure): + class Attributes(Structure): + _fields_ = [ + ('LINK_BW', c_uint8), + ('LANE_COUNT', c_uint8, 4), + ('Packing1', c_uint8, 1), + ('Packing', c_uint8, 2), + ('MST', c_uint8, 1), + ('n_val', c_uint16), + ] + + _fields_ = [ + ('Sync', c_uint32), + ('Tns', c_uint32), + ('SourcId', c_uint32), + ('Length', c_uint32), + ('TimeStamp', c_uint64), + ('Sync', c_uint32), + ('Attribute', Attributes) + ] + + +class CaptureCaps(Structure): + _fields_ = [ + ('triggers', c_uint32, 1), + ('multipleTriggersSelection', c_uint32, 1), + ('multipleTriggersAndingSelection', c_uint32, 1), + ('alpmTriggers', c_uint32, 1), + ('', c_uint32, 4), + ('grab128_132', c_uint32, 1), + ('', c_uint32, 3), + ("bitGrabWidth", c_uint32, 4), + ("", c_uint32, 16) + ] + + +class SBlock(Structure): + _fields_ = [ + ('BLOCK', c_uint32), + ('OFFSET', c_uint64), + ('SIZE', c_uint64), + ] diff --git a/UniTAP/dev/ports/modules/capturer/bulk/result_bulk.py b/UniTAP/dev/ports/modules/capturer/bulk/result_bulk.py new file mode 100644 index 0000000..ff23782 --- /dev/null +++ b/UniTAP/dev/ports/modules/capturer/bulk/result_bulk.py @@ -0,0 +1,60 @@ +import os +import warnings +import shutil +from ctypes import sizeof +from datetime import datetime +from UniTAP.dev.modules.capturer.result_object import ResultObject +from UniTAP.dev.modules.capturer.types import CapturedDataType +from UniTAP.dev.ports.modules.capturer.bulk.private_bulk_types import BulkHeader + + +class ResultBulkObject(ResultObject): + """ + Class `ResultBulkObject` inherited from class `ResultObject`. + Class `ResultBulkObject` allows saving captured data to file `save_to_bin_file`. + Also has all the `ResultObject` functionality. + """ + def __init__(self, assume_scrambler_disabled: bool = False): + super().__init__() + self.assume_scrambler_disabled = assume_scrambler_disabled + + def save_to_bin_file(self, directory_name: str): + """ + Saving captured bulk data to file. + + Args: + directory_name (str) - path to save + """ + if len(self.buffer) > 0: + if not os.path.exists(directory_name): + os.makedirs(directory_name) + else: + shutil.rmtree(directory_name) + os.makedirs(directory_name) + + main_link_file = f'capture_{datetime.now().strftime("%Y%m%d_%H%M%S")}.mainlink.bin' + events_file = f'capture_{datetime.now().strftime("%Y%m%d_%H%M%S")}.events.bin' + for data in self.buffer: + if data.type == CapturedDataType.Event: + e_file = open(os.path.join(directory_name, events_file), 'a+b') + sync = 0x0B41550B + e_file.write(sync.to_bytes(length=4, byteorder='little')) + e_file.write(0x0.to_bytes(length=4, byteorder='little')) + e_file.write(int(data.data[13] & 0xF).to_bytes(length=4, byteorder='little')) + e_file.write(int((len(data.data) - 12) / 4).to_bytes(length=4, byteorder='little')) + e_file.write(data.data[12:]) + elif data.type == CapturedDataType.Bulk: + b_file = open(os.path.join(directory_name, main_link_file), 'a+b') + if len(data.data) > sizeof(BulkHeader): + header = BulkHeader.from_buffer(data.data) + if self.assume_scrambler_disabled: + header.Attribute.Packing |= 0x2 + b_file.write(bytearray(header)) + b_file.write(data.data[sizeof(BulkHeader):]) + else: + warnings.warn("Buffer size is equal 0, without bulk data.") + + def __str__(self): + return f"Start capture time: {self.start_capture_time}\n" \ + f"End capture time: {self.end_capture_time}\n" \ + f"Timestamp: {self.timestamp.__str__()}\n" diff --git a/UniTAP/dev/ports/modules/capturer/event/__init__.py b/UniTAP/dev/ports/modules/capturer/event/__init__.py new file mode 100644 index 0000000..a8e3415 --- /dev/null +++ b/UniTAP/dev/ports/modules/capturer/event/__init__.py @@ -0,0 +1,2 @@ +from .event_types import EventSDP, EventLinkPattern, EventVBID, EventMSA, EventInfoFrame, EventLCE, EventFileFormat, \ + EventFilterDpRx, EventFilterDpTx, EventFilterHdRx, EventFilterHdTx, EventFilterUsbc diff --git a/UniTAP/dev/ports/modules/capturer/event/event_capturer.py b/UniTAP/dev/ports/modules/capturer/event/event_capturer.py new file mode 100644 index 0000000..dfbf2a7 --- /dev/null +++ b/UniTAP/dev/ports/modules/capturer/event/event_capturer.py @@ -0,0 +1,205 @@ +import time +import warnings +import copy + +from typing import Union, TypeVar, Type, List + +from UniTAP.libs.lib_tsi.tsi import TSIX_TS_SetPortConfigItem +from UniTAP.dev.modules.capturer.capture import Capturer, CaptureConfig +from UniTAP.dev.modules.capturer.statuses import EventCaptureStatus +from .result_event import ResultEventObject +from .event_types import EventFilterDpRx, EventFilterDpTx, EventFilterHdRx, EventFilterHdTx, EventFilterUsbc, EventData +from UniTAP.version import __version__ + +EventFilterType = TypeVar("EventFilterType", + EventFilterDpRx, + EventFilterDpTx, + EventFilterHdRx, + EventFilterHdTx, + EventFilterUsbc + ) + + +class EventCapturer: + """ + Class `EventCapturer` allows working with capturing events on Sink (RX - receiver) side. + You can `start` capturing in several modes, `stop` capturing, getting current `status` and result of capturing + `capture_result`. + """ + + def __init__(self, capturer: Capturer, port_id: int, event_filter: list): + self.__device = capturer.device + self.__capturer = capturer + self.__status = EventCaptureStatus.Unknown + + device_name, serial_number, fw_version, front_end, memory_size = \ + capturer.device.get_prepared_fw_info().split("|") + + self.__fw_info = {"bundle_version": capturer.device.get_bundle_version(), + "app_version": __version__, + "frontend_version": front_end, + "memory_size": memory_size, + "ucd_device": f"{device_name} [{serial_number.replace(' ','')}]", + "ucd_fw_version": fw_version} + self.__result = ResultEventObject(self.__fw_info) + self.__event_filter = event_filter + self.__port_id = port_id + + @property + def status(self) -> EventCaptureStatus: + """ + Returns current event capturer status. + + Returns: + object of `VideoCaptureStatus` type + """ + return self.__capturer.event_capturer_status + + @property + def capture_result(self) -> ResultEventObject: + """ + Returns result of event capturing. + + Returns: + object of `ResultEventObject` type + """ + return self.__result + + def event_filter(self, event_filter_type: Type[EventFilterType]) -> EventFilterType: + """ + Returns event filter for current `EventCapturer`. + + Returns: + object of one of available [EventFilterDpRx, EventFilterDpTx, EventFilterHdRx, EventFilterHdTx, + EventFilterUsbc] type + """ + for item in self.__event_filter: + if isinstance(item, event_filter_type): + return item + available_variants = '\n'.join([str(type(e)) for e in self.__event_filter]) + raise TypeError(f"Unsupported type of EventFilter: {event_filter_type}. Available variants:\n" + f"{available_variants}") + + def configure_capturer(self, event_filter: Union[EventFilterDpRx, EventFilterDpTx, EventFilterHdRx, EventFilterHdTx, + EventFilterUsbc]): + """ + Configure + + Args: + event_filter (Union[EventFilterDpRx, EventFilterDpTx, EventFilterHdRx, EventFilterHdTx, EventFilterUsbc]) + """ + if not self.__check_event_filter_type(event_filter): + available_variants = '\n'.join([str(type(e)) for e in self.__event_filter]) + raise TypeError(f"Unsupported type of EventFilter: {type(event_filter)}. " + f"Available variants:\n{available_variants}") + + for i, item in enumerate(self.__event_filter): + if isinstance(item, type(event_filter)): + self.__event_filter[i] = event_filter + + for item in event_filter.additional_filter: + TSIX_TS_SetPortConfigItem(self.__device.device_handle, self.__port_id, item.ci_control, item.value, + data_count=len(item.value) if isinstance(item.value, list) else 1) + + TSIX_TS_SetPortConfigItem(self.__device.device_handle, self.__port_id, event_filter._control_ci, + event_filter.config) + + def clear_capturer_config(self): + """ + Clear event captuter configuration (filter). + """ + for item in self.__event_filter: + item.clear() + TSIX_TS_SetPortConfigItem(self.__device.device_handle, self.__port_id, item._control_ci, 0) + + def start(self, sec=0, n_elements=0): + """ + Start capturing. Possible some variants of capturing: \n + - Capture with fixed event count (will be captured fixed event count and capturing will be stopped) + - Capture with fixed time (capturing will be continued fixed seconds and capturing will be stopped). + - Capture without parameters Live capturing (for getting events you need to use functions `pop_element` and + `pop_all_elements`). Here you need to manually call the `stop` after capture. + + All results can be obtained using the function `capture_result`. + + Args: + n_elements (int) + sec (int) + """ + config = CaptureConfig() + config.event = True + config.type = CaptureConfig.Type.LIVE + + self.__capturer.start_event_capture() + + self.__capturer.start_capture(config) + + self.__result.clear() + self.__result.start_capture_time = time.time() + + if n_elements > 0: + self.__result.buffer.extend(self.__capturer.capture_n_events(n_elements)) + self.stop() + elif sec > 0: + time.sleep(sec) + self.stop() + self.__result.buffer.extend(self.__capturer.read_all_events()) + + def stop(self): + """ + Stop capture events. + """ + config = CaptureConfig() + config.event = True + config.type = CaptureConfig.Type.LIVE + + self.__capturer.stop_event_capture() + self.__capturer.stop_capture(config) + self.__result.end_capture_time = time.time() + + def pop_element(self) -> EventData: + """ + Return first captured object of `EventData`. + + Returns: + object of `EventData` type or `ResultEventObject` + """ + if self.status == EventCaptureStatus.Stop: + warnings.warn("Event capture is not working now. Please, turn it on.") + captured_events = self.__capturer.capture_n_events(1) + self.__result.buffer.extend(copy.deepcopy(captured_events)) + + return captured_events[0] + + def __get_events_count(self) -> int: + return self.__capturer.get_available_events_count() + + def __check_event_filter_type(self, event_filter: Union[EventFilterDpRx, EventFilterDpTx, EventFilterHdRx, + EventFilterHdTx, EventFilterUsbc]) -> bool: + for item in self.__event_filter: + if isinstance(item, type(event_filter)): + return True + return False + + def pop_all_elements(self) -> List[EventData]: + """ + Return all captured event frames(objects of `EventData`). + + Returns: + object of list[`EventData`] type + """ + + return self.__capturer.read_all_events() + + def pop_all_elements_as_result_object(self) -> ResultEventObject: + """ + Return all captured event frames(objects of `EventData`) as `ResultEventObject`. + + Returns: + object of `ResultEventObject` type + """ + captured_events = self.__capturer.read_all_events() + + res = ResultEventObject(self.__fw_info) + res.buffer.extend(captured_events) + return res diff --git a/UniTAP/dev/ports/modules/capturer/event/event_report_template.py b/UniTAP/dev/ports/modules/capturer/event/event_report_template.py new file mode 100644 index 0000000..afbd3d7 --- /dev/null +++ b/UniTAP/dev/ports/modules/capturer/event/event_report_template.py @@ -0,0 +1,235 @@ +event_entry_template = """ +var TxtEntry{$num} = [ + 'Event Details', + '{$num} {$event_name} ', + '{$time}
Entry type {$entry_type}
Sent from {$send_from}
', + '{$event_data}

',] +var ShortTxtEntry{$num} = [ + '', + '

'] +var Entry{$num} = [ + EventProto, + TxtEntry{$num}, + ShortEventProto, + ShortTxtEntry{$num} ] +""" + +event_table_row = '' + + +events_template = """ +UCD Console event monitoring report + + + + +
+

  {$report_name}

+
+
+ + + + + + + +
+

+    Transactions and events +

+

+ Click entry to see details

+

+  # TimestampSource Type Info + + +

+ +

+ +
+
+   +
+
+
+ + + +""" + +packet_entry_template = """ +var TxtEntry{$num} = [ + 'Event Details', + '{$num} {$event_name} ', + '{$time}
Entry type {$entry_type}
Sent from {$send_from}
', + '{$hex}', + '{$event_data}

',] +var ShortTxtEntry{$num} = [ + '
{$num_just} {$time}   {$send_from_just} {$type_just} {$event_name}
', + '{$hex_just}

'] +var Entry{$num} = [ + PacketProto, + TxtEntry{$num}, + ShortPacketProto, + ShortTxtEntry{$num} ] + +""" \ No newline at end of file diff --git a/UniTAP/dev/ports/modules/capturer/event/event_types.py b/UniTAP/dev/ports/modules/capturer/event/event_types.py new file mode 100644 index 0000000..6ac7024 --- /dev/null +++ b/UniTAP/dev/ports/modules/capturer/event/event_types.py @@ -0,0 +1,774 @@ +from .event_utils import set_bit_in_vector, search_in_list +from .private_event_types import AdditionalSDPEventFilter, AdditionalLinkPatternEventFilter, \ + AdditionalInfoFrameEventFilter, AdditionalVBIDEventFilter, AdditionalMSAEventFilter, AdditionalLSEEventFilter +from UniTAP.libs.lib_tsi.tsi_types import * +import warnings + + +class EventFileFormat(IntEnum): + """ + Describe all supported file formats for saving events: + - BIN. + - TXT (Support will be added later). + """ + UNKNOWN = -1 + BIN = 0 + TXT = 1 + HTML = 2 + CSV = 3 + + +class EventSDP(IntEnum): + """ + Describe all supported SDP packets types: + """ + AudioTimeStamp = 0x1 + AudioStream = 0x2 + Extension = 0x4 + AudioCopyManagement = 0x5 + ISRC = 0x6 + VSC = 0x7 + CG0 = 0x8 + CG1 = 0x9 + CG2 = 0xA + CG3 = 0xB + CG4 = 0xC + CG5 = 0xD + CG6 = 0xE + CG7 = 0xF + PictureParamSet = 0x10 + VSC_EXT_VESA = 0x20 + VSC_EXT_CTA = 0x21 + Adaptive_Sync = 0x22 + VS = 0x81 + AVI = 0x82 + SPD = 0x83 + Audio = 0x84 + MPEG = 0x85 + NTSC_VBI = 0x86 + DRM = 0x87 + + +class EventLinkPattern(IntEnum): + """ + Describe all supported Link Pattern packets types: + """ + TPS1Begin = 0 + TPS1End = 1 + TPS2Begin = 2 + TPS2End = 3 + TPS3Begin = 4 + TPS3End = 5 + TPS4Begin = 6 + TPS4End = 7 + IdleBegin = 10 + IdleEnd = 11 + ActiveBegin = 12 + ActiveEnd = 13 + SleepBegin = 14 + SleepEnd = 15 + StandbyBegin = 16 + StandbyEnd = 17 + EIEOSBegin = 18 + EIEOSEnd = 19 + CustomBegin = 20 + CustomEnd = 21 + Begin25201 = 22 + End25201 = 23 + Begin25202 = 24 + End25202 = 25 + PRBS7Begin = 26 + PRBS7End = 27 + PRBS31Begin = 28 + PRBS31End = 29 + + +class EventVBID(IntEnum): + """ + Describe all supported VBID packets types: + """ + SetVBlank = 0x1 + ClearVBlank = 0x2 + AnyVBlank = 0x3 + SetFieldID = 0x4 + CleatFieldID = 0x8 + AnyFieldID = 0xC + SetInterface = 0x10 + CleatInterface = 0x20 + AnyInterface = 0x30 + SetNoVideo = 0x40 + CleatNoVideo = 0x80 + AnyNoVideo = 0xC0 + SetNoAudio = 0x100 + CleatNoAudio = 0x200 + AnyNoAudio = 0x300 + SetHDCPSYNC = 0x400 + CleatHDCPSYNC = 0x800 + AnyHDCPSYNC = 0xC00 + SetCompressed = 0x1000 + CleatCompressed = 0x2000 + AnyCompressed = 0x3000 + SetReserved = 0x4000 + CleatReserved = 0x8000 + AnyReserved = 0xC000 + MVID = 0x10000 + MAUD = 0x20000 + + +class EventMSA(IntEnum): + """ + Describe all supported MSA packets types: + """ + MVID = 0x1 + NVID = 0x2 + HTOTAL = 0x4 + VTOTAL = 0x8 + HSTART = 0x10 + VSTART = 0x20 + HSP = 0x40 + HSW = 0x80 + VSP = 0x100 + VSW = 0x200 + HWIDTH = 0x400 + VHEIGHT = 0x800 + MISC0 = 0x1000 + MISC1 = 0x2000 + + +class EventInfoFrame(IntEnum): + """ + Describe all supported Info Frame packets types: + """ + VS = 0x81 + AVI = 0x82 + SPD = 0x83 + Audio = 0x84 + MPEG = 0x85 + NTSC_VBI = 0x86 + DRM = 0x87 + + +class EventData: + """ + Class `EventData` describe one event. Contains following information: + - `source` + - `type` + - `brief` info + - `info` + - `time` + - `duration` + - `data` + """ + + def __init__(self): + self.__source = "" + self.__type = "" + self.__brief = "" + self.__info = "" + self.__time = 0 + self.__duration = 0 + self.__data = bytearray() + + @property + def source(self): + return self.__source + + @property + def type(self): + return self.__type + + @property + def brief(self): + return self.__brief + + @property + def info(self): + return self.__info + + @property + def time(self): + return self.__time + + @property + def duration(self): + return self.__duration + + @property + def data(self): + return self.__data + + @source.setter + def source(self, source: str): + self.__source = source + + @type.setter + def type(self, _type: str): + self.__type = _type + + @brief.setter + def brief(self, brief: str): + self.__brief = brief + + @info.setter + def info(self, info: str): + self.__info = info + + @time.setter + def time(self, time: int): + if time <= 0: + raise ValueError(f"Time must be more than 0.") + self.__time = time + + @duration.setter + def duration(self, duration: int): + if duration <= 0: + raise ValueError(f"Duration must be more than 0.") + self.__duration = duration + + @data.setter + def data(self, data: bytearray): + if len(data) <= 0: + raise ValueError(f"Data length must be more than 0.") + self.__data = data + + +class EventLCE: + """ + Describe settings for LCE USB-C events: + - `v_bus` + - `iv_bus` + - `vcc` + - `vsbu` + - `i_vconn` + """ + + def __init__(self, v_bus: int, iv_bus: int, vcc: int, vsbu: int, i_vconn: int): + self.__v_bus = v_bus + self.__iv_bus = iv_bus + self.__vcc = vcc + self.__vsbu = vsbu + self.__i_vconn = i_vconn + + @property + def v_bus(self): + return self.__v_bus + + @property + def iv_bus(self): + return self.__iv_bus + + @property + def vcc(self): + return self.__vcc + + @property + def vsbu(self): + return self.__vsbu + + @property + def i_vconn(self): + return self.__i_vconn + + @v_bus.setter + def v_bus(self, v_bus: int): + if v_bus <= 0: + raise ValueError(f"V bus must be more than 0.") + self.__v_bus = v_bus + + @iv_bus.setter + def iv_bus(self, iv_bus: int): + if iv_bus <= 0: + raise ValueError(f"I V bus must be more than 0.") + self.__iv_bus = iv_bus + + @vcc.setter + def vcc(self, vcc: int): + if vcc <= 0: + raise ValueError(f"VCC must be more than 0.") + self.__vcc = vcc + + @vsbu.setter + def vsbu(self, vsbu: int): + if vsbu <= 0: + raise ValueError(f"V sbu must be more than 0.") + self.__vsbu = vsbu + + @i_vconn.setter + def i_vconn(self, i_vconn: int): + if i_vconn <= 0: + raise ValueError(f"I vconn must be more than 0.") + self.i_vconn = i_vconn + + def values(self) -> list: + """ + Returns all values how list. + + Returns: + object of list type + """ + return [self.v_bus, self.iv_bus, self.vcc, self.vsbu, self.i_vconn] + + +def _set_bit_in_link_pattern(enable, data, bit_no: EventLinkPattern): + _list = list(EventLinkPattern) + _ind = _list.index(bit_no) + if 0 <= _ind <= 7: + if enable: + data[0] |= (1 << bit_no.value) + else: + data[0] &= ~(1 << bit_no.value) + elif 8 <= _ind <= 17: + if enable: + data[1] |= (1 << (bit_no.value - 10)) + else: + data[1] &= ~(1 << (bit_no.value - 10)) + else: + if enable: + data[2] |= (1 << (bit_no.value - 20)) + else: + data[2] &= ~(1 << (bit_no.value - 20)) + + +class EventFilter: + """ + Base class of all filters. + """ + + def __init__(self, hw_caps=None): + self.__config = 0 + self._control_ci = 0 + self._hw_caps = hw_caps + self.__additional_filter = [] + + @property + def config(self) -> int: + """ + Returns current configuration value of filter. + + Returns: + object of int type + """ + return self.__config + + @property + def additional_filter(self) -> list: + """ + Returns current additional filters for main filter. + + Returns: + object of list type + """ + return self.__additional_filter + + @config.setter + def config(self, config: int): + """ + Set configuration value of filter. + + Args: + config (int) + """ + if config < 0: + raise ValueError(f"Config value must be more than 0.") + self.__config = config + + @additional_filter.setter + def additional_filter(self, additional_filter: list): + """ + Set additional filters of main filter. + + Args: + additional_filter (list) + """ + self.__additional_filter = additional_filter + + def clear(self): + """ + Clear all config. + """ + self.config = 0 + + for item in self.additional_filter: + if isinstance(item.value, int): + item.value = 0 + elif isinstance(item.value, list): + item.value = [0] * len(item.value) + + +class EventFilterDpRx(EventFilter): + """ + Class `EventFilterDpRx` allows setting filter for DPRX available events: `config_hpd_events`, `config_aux_events`, + `config_sdp_events`, `config_link_pattern_events`, `config_vb_id_events`, `config_msa_events`, + `config_aux_bw_events`. + Inherited from class `EventFilter`. + """ + + def __init__(self, hw_caps): + super().__init__(hw_caps) + self._control_ci = TSI_DPRX_LOG_CONTROL + self.additional_filter = [AdditionalSDPEventFilter()] + if hw_caps.link_pat_log: + self.additional_filter.append(AdditionalLinkPatternEventFilter()) + if hw_caps.vbid_hw_log: + self.additional_filter.append(AdditionalVBIDEventFilter()) + if self._hw_caps.msa_hw_log: + self.additional_filter.append(AdditionalMSAEventFilter()) + + def config_hpd_events(self, enable: bool): + """ + Configure HDP events. + + Args: + enable (bool) - enable/disable HDP events + """ + if enable: + self.config |= TSI_DPRX_LOG_CTRL_VALUE_HPD + else: + self.config &= ~TSI_DPRX_LOG_CTRL_VALUE_HPD + + def config_aux_events(self, enable: bool): + """ + Configure AUX events. + + Args: + enable (bool) - enable/disable AUX events + """ + if enable: + self.config |= TSI_DPRX_LOG_CTRL_VALUE_AUX + else: + self.config &= ~TSI_DPRX_LOG_CTRL_VALUE_AUX + + def config_sdp_events(self, enable: bool, *args: EventSDP): + """ + Configure SDP events. + + Args: + enable (bool) - enable/disable SDP events + *args (`EventSDP`) - SDP packet types + """ + if enable: + self.config |= TSI_DPRX_LOG_CTRL_VALUE_SDP + else: + self.config &= ~TSI_DPRX_LOG_CTRL_VALUE_SDP + + if len(args) > 0: + for item in args: + if isinstance(item, EventSDP): + set_bit_in_vector(enable, self.additional_filter[0].value, item.value) + + def config_link_pattern_events(self, enable: bool, *args: EventLinkPattern): + """ + Configure Link Pattern events. + + Args: + enable (bool) - enable/disable Link Pattern events + *args (`EventLinkPattern`) - Link Pattern packet types + """ + if self._hw_caps is not None and self._hw_caps.link_pat_log: + if enable: + self.config |= TSI_DPRX_LOG_CTRL_VALUE_LINK_PAT + else: + self.config &= ~TSI_DPRX_LOG_CTRL_VALUE_LINK_PAT + + if len(args) > 0: + for item in args: + if isinstance(item, EventLinkPattern): + _set_bit_in_link_pattern(enable, + self.additional_filter[search_in_list(AdditionalLinkPatternEventFilter, + self.additional_filter)].value, + item) + else: + warnings.warn("HW does not support Link Pattern Logger") + + def config_vb_id_events(self, enable: bool, *args: EventVBID): + """ + Configure VB ID events. + + Args: + enable (bool) - enable/disable VB ID events + *args (`EventVBID`) - VB ID packet types + """ + if self._hw_caps is not None and self._hw_caps.vbid_hw_log: + if enable: + self.config |= TSI_DPRX_LOG_CTRL_VALUE_VBID_HW + else: + self.config &= ~TSI_DPRX_LOG_CTRL_VALUE_VBID_HW + + if len(args) > 0: + for item in args: + if isinstance(item, EventVBID): + if enable: + self.additional_filter[search_in_list(AdditionalVBIDEventFilter, + self.additional_filter)].value |= item.value + else: + self.additional_filter[search_in_list(AdditionalVBIDEventFilter, + self.additional_filter)].value &= ~item.value + else: + warnings.warn("HW does not support Vbid Hw Logger") + + def config_msa_events(self, enable: bool, *args: EventMSA): + """ + Configure MSA events. + + Args: + enable (bool) - enable/disable MSA events + *args (`EventMSA`) - MSA packet types + """ + if self._hw_caps is not None and self._hw_caps.msa_hw_log: + if enable: + self.config |= TSI_DPRX_LOG_CTRL_VALUE_MSA_HW + else: + self.config &= ~TSI_DPRX_LOG_CTRL_VALUE_MSA_HW + + if len(args) > 0: + for item in args: + if isinstance(item, EventMSA): + if enable: + self.additional_filter[search_in_list(AdditionalMSAEventFilter, + self.additional_filter)].value |= item.value + else: + self.additional_filter[search_in_list(AdditionalMSAEventFilter, + self.additional_filter)].value &= ~item.value + else: + warnings.warn("HW does not support Msa Hw Logger") + + def config_aux_bw_events(self, enable: bool): + """ + Configure AUX BW events. + + Args: + enable (bool) - enable/disable AUX BW events + """ + if self._hw_caps is not None and self._hw_caps.aux_bw_log: + if enable: + self.config |= TSI_DPRX_LOG_CTRL_VALUE_AUX_BW + else: + self.config &= ~TSI_DPRX_LOG_CTRL_VALUE_AUX_BW + else: + warnings.warn("HW does not support Aux Bw Logger") + + +class EventFilterDpTx(EventFilter): + """ + Class `EventFilterDpTx` allows setting filter for DPTX available events: `config_hpd_events`, `config_aux_events`. + Inherited from class `EventFilter`. + """ + + def __init__(self, hw_caps): + super().__init__(hw_caps) + self._control_ci = TSI_DPTX_LOG_CONTROL + + def config_hpd_events(self, enable: bool): + """ + Configure HDP events. + + Args: + enable (bool) - enable/disable HDP events + """ + if enable: + self.config |= TSI_DPTX_LOG_CONTROL_VALUE_HPD + else: + self.config &= ~TSI_DPTX_LOG_CONTROL_VALUE_HPD + + def config_aux_events(self, enable: bool): + """ + Configure AUX events. + + Args: + enable (bool) - enable/disable AUX events + """ + if enable: + self.config |= TSI_DPTX_LOG_CONTROL_VALUE_AUX + else: + self.config &= ~TSI_DPTX_LOG_CONTROL_VALUE_AUX + + +class EventFilterHdRx(EventFilter): + """ + Class `EventFilterHdRx` allows setting filter for HDRX available events: `config_hpd_events`, + `config_packets_events`, `config_i2c_events`, `config_cec_events`. + Inherited from class `EventFilter`. + """ + + def __init__(self, hw_caps): + super().__init__(hw_caps) + self.additional_filter = [AdditionalInfoFrameEventFilter()] + self._control_ci = TSI_HDRX_LOG_CONTROL + + def config_hpd_events(self, enable: bool): + """ + Configure HPD events. + + Args: + enable (bool) - enable/disable HPD events + """ + if enable: + self.config |= TSI_HDRX_LOG_CTRL_VALUE_HPD + else: + self.config &= ~TSI_HDRX_LOG_CTRL_VALUE_HPD + + def config_packets_events(self, enable: bool, *args: EventInfoFrame): + """ + Configure InfoFrame events. + + Args: + enable (bool) - enable/disable InfoFrame events + *args (`EventInfoFrame`) - InfoFrame packet types + """ + if enable: + self.config |= TSI_HDRX_LOG_CTRL_VALUE_INFO + else: + self.config &= ~TSI_HDRX_LOG_CTRL_VALUE_INFO + + if len(args) > 0: + for item in args: + if isinstance(item, EventInfoFrame): + set_bit_in_vector(enable, self.additional_filter[0].value, item.value) + + def config_i2c_events(self, enable: bool): + """ + Configure I2C events. + + Args: + enable (bool) - enable/disable I2C events + """ + if enable: + self.config |= TSI_HDRX_LOG_CTRL_VALUE_I2C + else: + self.config &= ~TSI_HDRX_LOG_CTRL_VALUE_I2C + + def config_cec_events(self, enable: bool): + """ + Configure CEC events. + + Args: + enable (bool) - enable/disable CEC events + """ + if enable: + self.config |= TSI_HDRX_LOG_CTRL_VALUE_CEC + else: + self.config &= ~TSI_HDRX_LOG_CTRL_VALUE_CEC + + +class EventFilterHdTx(EventFilter): + """ + Class `EventFilterHdTx` allows setting filter for HDTX available events: `config_hpd_events`, `config_i2c_events`, + `config_cec_events`. + Inherited from class `EventFilter`. + """ + + def __init__(self, hw_caps): + super().__init__(hw_caps) + self._control_ci = TSI_HDTX_LOG_CONTROL + + def config_hpd_events(self, enable: bool): + """ + Configure HPD events. + + Args: + enable (bool) - enable/disable HPD events + """ + if enable: + self.config |= TSI_HDTX_LOG_CTRL_VALUE_HPD + else: + self.config &= ~TSI_HDTX_LOG_CTRL_VALUE_HPD + + def config_i2c_events(self, enable: bool): + """ + Configure I2C events. + + Args: + enable (bool) - enable/disable I2C events + """ + if enable: + self.config |= TSI_HDTX_LOG_CTRL_VALUE_I2C + else: + self.config &= ~TSI_HDTX_LOG_CTRL_VALUE_I2C + + def config_cec_events(self, enable: bool): + """ + Configure CEC events. + + Args: + enable (bool) - enable/disable CEC events + """ + if enable: + self.config |= TSI_HDTX_LOG_CTRL_VALUE_CEC + else: + self.config &= ~TSI_HDTX_LOG_CTRL_VALUE_CEC + + +class EventFilterUsbc(EventFilter): + """ + Class `EventFilterUsbc` allows setting filter for USB-C available events: `config_pd_events`, + `config_voltage_events`, `config_usbc_events`, `config_port_state_events`. + Inherited from class `EventFilter`. + """ + + def __init__(self, hw_caps): + super().__init__(hw_caps) + self._control_ci = TSI_PDC_LOG_CONTROL + if hw_caps is not None and hw_caps.voltage: + self.additional_filter.append(AdditionalLSEEventFilter()) + + def config_pd_events(self, enable: bool): + """ + Configure PD events. + + Args: + enable (bool) - enable/disable PD events + """ + if enable: + self.config |= TSI_PDC_LOG_CTRL_VALUE_PD + else: + self.config &= ~TSI_PDC_LOG_CTRL_VALUE_PD + + def config_voltage_events(self, enable: bool, value: EventLCE): + """ + Configure LCE (voltage) events. + + Args: + enable (bool) - enable/disable LCE events + value (`EventLCE`) - LCE packet types + """ + if self._hw_caps is not None and self._hw_caps.voltage: + if enable: + self.config |= TSI_PDC_LOG_CTRL_VALUE_USBC_VOLTAGE + else: + self.config &= ~TSI_PDC_LOG_CTRL_VALUE_USBC_VOLTAGE + + for i in enumerate(value.values()): + self.additional_filter[search_in_list(AdditionalLSEEventFilter, + self.additional_filter)].value[i[0]] = i[1] + else: + warnings.warn("HW does not support 'LCE voltage' events") + + def config_usbc_events(self, enable: bool): + """ + Configure USB-C events. + + Args: + enable (bool) - enable/disable USB-C events + """ + if self._hw_caps is not None and self._hw_caps.events: + if enable: + self.config |= TSI_PDC_LOG_CTRL_VALUE_USBC_EVENT + else: + self.config &= ~TSI_PDC_LOG_CTRL_VALUE_USBC_EVENT + else: + warnings.warn("HW does not support 'USB-C' events") + + def config_port_state_events(self, enable: bool): + """ + Configure USB-C state events. + + Args: + enable (bool) - enable/disable USB-C state events + """ + if self._hw_caps is not None and self._hw_caps.states: + if enable: + self.config |= TSI_PDC_LOG_CTRL_VALUE_USBC_STATE + else: + self.config &= ~TSI_PDC_LOG_CTRL_VALUE_USBC_STATE + else: + warnings.warn("HW does not support 'USB-C Port State' events") diff --git a/UniTAP/dev/ports/modules/capturer/event/event_utils.py b/UniTAP/dev/ports/modules/capturer/event/event_utils.py new file mode 100644 index 0000000..b7ef859 --- /dev/null +++ b/UniTAP/dev/ports/modules/capturer/event/event_utils.py @@ -0,0 +1,193 @@ +import csv +import warnings +from .event_report_template import * + + +def set_bit_in_vector(enable, data, bit_no): + + bits_in_word = 32 + total_words = len(data) + selected_word = int(bit_no / bits_in_word) + bit_offset = bits_in_word - (((selected_word + 1) * bits_in_word) - bit_no) + + if selected_word >= total_words: + warnings.warn(f"Bit {bit_no} is out of provided vector's range {total_words} words") + return + + if enable: + data[selected_word] |= (1 << bit_offset) + else: + data[selected_word] &= ~(1 << bit_offset) + + +def search_in_list(_type, _list): + for item in enumerate(_list): + if isinstance(item[1], _type): + return item[0] + + return None + + +def byterarray_to_hex_str(data: bytearray) -> str: + return ' '.join(format(x, '02x') for x in data) + + +def content_to_str(data: str) -> str: + data = data.replace("\r\n", "
") + data = data.replace("\n\r", "
") + data = data.replace("\r", "
") + data = data.replace("\n", "
") + data = data.replace("'", "\\'") + data = data.replace(" ", "  ") + return data + + +def timestamp_to_str(value) -> str: + nsec = value % 1000 + value -= nsec + value /= 1000 + + mcsec = value % 1000 + value -= mcsec + value /= 1000 + + msec = value % 1000 + value -= msec + value /= 1000 + + sec = value % 60 + value -= sec + value /= 60 + + min_v = value % 60 + value -= min_v + + hour = value / 60 + + return "{:02d}:{:02d}:{:02d}.{:03d}.{:03d}.{:03d}".format(int(hour), int(min_v), int(sec), int(msec), int(mcsec), + int(nsec)) + + +def save_to_txt_file(path: str, events: list, index=None): + file = open(path, 'w') + if index is not None: + if len(events[index]) == 6: + file.write(f"{events[index].get('content')}\n{events[index].get('brief')}\n" + f"{byterarray_to_hex_str(events[index].get('data'))}\n" + f"{events[index].get('source')}\n") + else: + warnings.warn("Empty event") + else: + for data in events: + if len(data) == 6: + file.write(f"{data.get('content')}\n{data.get('brief')}\n" + f"{byterarray_to_hex_str(data.get('data'))}\n" + f"{data.get('source')}\n") + else: + warnings.warn("Empty event") + + file.close() + + +def save_to_bin_file(path: str, events: list, index=None): + file = open(path, 'wb') + if index is not None: + data = events[index].data + if len(data) > 14: + sync = 0x0B41550B + file.write(sync.to_bytes(length=4, byteorder='little')) + file.write(0x0.to_bytes(length=4, byteorder='little')) + file.write(int(data[13] & 0xF).to_bytes(length=4, byteorder='little')) + file.write(int((len(data) - 12) / 4).to_bytes(length=4, byteorder='little')) + file.write(data[12:]) + else: + for data in events: + if len(data.data) > 14: + sync = 0x0B41550B + file.write(sync.to_bytes(length=4, byteorder='little')) + file.write(0x0.to_bytes(length=4, byteorder='little')) + file.write(int(data.data[13] & 0xF).to_bytes(length=4, byteorder='little')) + file.write(int((len(data.data) - 12) / 4).to_bytes(length=4, byteorder='little')) + file.write(data.data[12:]) + file.close() + + +def save_to_csv_file(path: str, events: list): + with (open(path, 'w', newline='') as file): + writer = csv.writer(file, delimiter=';') + writer.writerow(["Source", "Type", "Start", "Brief info", "Full info"]) + for data in events: + if len(data) == 6: + processed_info = data.get('content')[:-1] + '.' + processed_info = processed_info.replace("\n", "; ") + + writer.writerow([ + data.get('source'), + data.get('type'), + timestamp_to_str(data.get('timestamp')), + data.get('brief'), + f'"{processed_info}"' + ]) + else: + warnings.warn("Empty event") + + + +def save_to_html_file(path: str, events: list, fw_info: dict, long_type_string: bool): + file = open(path, 'w') + + new_events_template = events_template.replace("{$report_name}", "Unigraf UCD Console event monitoring report").\ + replace("{$bundle_version}", fw_info.get('bundle_version')).\ + replace("{$app_version}", fw_info.get('app_version')).\ + replace("{$frontend_version}", fw_info.get('frontend_version')).\ + replace("{$memory_size}", fw_info.get('memory_size')).\ + replace("{$ucd_device}", fw_info.get('ucd_device')).\ + replace("{$ucd_fw_version}", fw_info.get('ucd_fw_version')).\ + replace("{$remarks}", '').\ + replace("{$tested}", "") + + events_data = [] + entries_list = [] + entries_rows = [] + + for index, item in enumerate(events): + + new_entry_value = event_entry_template.replace("{$event_name}", item.get('brief')).\ + replace("{$time}", f'{timestamp_to_str(item.get("timestamp"))}').\ + replace("{$entry_type}", "").\ + replace("{$send_from}", item.get('type')).\ + replace("{$num}", f'{index}').\ + replace("{$hex}", byterarray_to_hex_str(item.get('data'))).\ + replace("{$hex_just}", byterarray_to_hex_str(item.get('data'))).\ + replace("{$event_data}", content_to_str(item.get('content'))).\ + replace("{$num_just}", f"{index}".ljust(6, " ").replace(" ", " ")).\ + replace("{$send_from_just}", f"{item.get('source')}".ljust(10, " ").replace(" ", " ")).\ + replace("{$type_just}", f"{item.get('type')}".ljust(16 if long_type_string else 8, " ").replace(" ", " ")).\ + replace("{$text_color}", "#000000").\ + replace("{$back_color}", '#ffffff').\ + replace("{$font_bold}", "").\ + replace("{$font_italic}", "").\ + replace("{$font_family}", "") + + new_event_table_row = event_table_row.replace("{$text_color}", "#000000").\ + replace("{$back_color}", '#ffffff').\ + replace("{$font_family}", "").\ + replace("{$font_italic}", "").\ + replace("{$font_bold}", "").\ + replace("{$num}", f'{index}').\ + replace("{$num_just}", f"{index}".ljust(6, " ").replace(" ", " ")).\ + replace("{$time}", f'{timestamp_to_str(item.get("timestamp"))}').\ + replace("{$send_from_just}", f"{item.get('source')}".ljust(10, " ").replace(" ", " ")).\ + replace("{$type_just}", f"{item.get('type')}".ljust(16 if long_type_string else 8, " ").replace(" ", " ")).\ + replace("{$event_name}", item.get('brief')) + + events_data.append(new_entry_value) + entries_rows.append(new_event_table_row) + entries_list.append(f"Entry{index},") + + new_events_template = new_events_template.replace("{$events_data}", "\n".join(e for e in events_data)).\ + replace("{$events_list}", "\n".join(e for e in entries_list)).\ + replace("{$events_table}", "\n".join(e for e in entries_rows)) + + file.write(new_events_template) + file.close() diff --git a/UniTAP/dev/ports/modules/capturer/event/private_event_types.py b/UniTAP/dev/ports/modules/capturer/event/private_event_types.py new file mode 100644 index 0000000..a308d5c --- /dev/null +++ b/UniTAP/dev/ports/modules/capturer/event/private_event_types.py @@ -0,0 +1,75 @@ +from UniTAP.libs.lib_tsi.tsi_types import TSI_DPRX_LOG_IF_SEL, TSI_HDRX_LOG_IF_SEL, TSI_DPRX_LOG_CTRL_VALUE_LINK_PAT, \ + TSI_DPRX_VBID_LOG_SEL, TSI_DPRX_MSA_LOG_SEL, TSI_PDC_USBC_LOG_THRESH + + +class AdditionalEventFilter: + + def __init__(self): + self.__ci_control = 0 + self.__value = None + + @property + def ci_control(self): + return self.__ci_control + + @property + def value(self): + return self.__value + + @ci_control.setter + def ci_control(self, ci_control: int): + if ci_control <= 0: + raise ValueError(f"CI control must be more than 0.") + self.__ci_control = ci_control + + @value.setter + def value(self, value): + self.__value = value + + +class AdditionalSDPEventFilter(AdditionalEventFilter): + + def __init__(self): + super().__init__() + self.ci_control = TSI_DPRX_LOG_IF_SEL + self.value = [0] * 8 + + +class AdditionalInfoFrameEventFilter(AdditionalEventFilter): + + def __init__(self): + super().__init__() + self.ci_control = TSI_HDRX_LOG_IF_SEL + self.value = [0] * 8 + + +class AdditionalLinkPatternEventFilter(AdditionalEventFilter): + + def __init__(self): + super().__init__() + self.ci_control = TSI_DPRX_LOG_CTRL_VALUE_LINK_PAT + self.value = [0] * 3 + + +class AdditionalVBIDEventFilter(AdditionalEventFilter): + + def __init__(self): + super().__init__() + self.ci_control = TSI_DPRX_VBID_LOG_SEL + self.value = 0 + + +class AdditionalMSAEventFilter(AdditionalEventFilter): + + def __init__(self): + super().__init__() + self.ci_control = TSI_DPRX_MSA_LOG_SEL + self.value = 0 + + +class AdditionalLSEEventFilter(AdditionalEventFilter): + + def __init__(self): + super().__init__() + self.ci_control = TSI_PDC_USBC_LOG_THRESH + self.value = [0] * 5 diff --git a/UniTAP/dev/ports/modules/capturer/event/result_event.py b/UniTAP/dev/ports/modules/capturer/event/result_event.py new file mode 100644 index 0000000..4ebecf1 --- /dev/null +++ b/UniTAP/dev/ports/modules/capturer/event/result_event.py @@ -0,0 +1,108 @@ +import warnings + +from UniTAP.dev.modules.capturer.result_object import ResultObject +from UniTAP.libs.lib_tsi.tsi import TSIEventParser +from .event_utils import save_to_bin_file, save_to_csv_file, save_to_txt_file, save_to_html_file +from .event_types import EventFileFormat + + +class ResultEventObject(ResultObject): + """ + Class `ResultEventObject` inherited from class `ResultObject`. + Class `ResultEventObject` allows saving captured events to file `save_to_file_selected_event` or + `save_to_file_all_events`. + Also has all the `ResultObject` functionality. + """ + + def __init__(self, fw_info: dict): + super().__init__() + self.__parsed_buffer = [] + self.__fw_info = fw_info + self.__long_type_string = False + + def __str__(self): + return f"Start capture time: {self.start_capture_time}\n" \ + f"End capture time: {self.end_capture_time}\n" \ + f"Timestamp: {self.timestamp.__str__()}\n" + + def save_to_file_selected_event(self, file_format: EventFileFormat, path: str, index: int): + """ + + Saving selected event to file. Supported file formats describe in `EventFileFormat`. + + Args: + file_format (`PictureFileFormat`) - file format + path (str) - path to save + index (int) - number of event in list + """ + if len(self.buffer) > 0: + if file_format == EventFileFormat.BIN: + save_to_bin_file(path=path, events=self.buffer, index=index) + elif file_format == EventFileFormat.TXT: + if len(self.__parsed_buffer) == 0: + self.__parse_buffer() + save_to_txt_file(path=path, events=self.__parsed_buffer, index=index) + elif file_format == EventFileFormat.HTML: + raise NotImplementedError('Temporary not supported to txt files.') + else: + raise ValueError(f"Incorrect file format. Available formats: " + f"{EventFileFormat.BIN.name}, {EventFileFormat.TXT.name}, " + f"{EventFileFormat.HTML.name}.\n" + f"Transferred audio format: {file_format.name}") + self.__parsed_buffer.clear() + else: + warnings.warn("Buffer size is equal 0.") + + def save_to_file_all_events(self, file_format: EventFileFormat, path: str): + """ + + Saving all events to file. Supported file formats describe in `EventFileFormat`. + + Args: + file_format (`EventFileFormat`) - file format + path (str) - path to save + """ + if len(self.buffer) > 0: + if file_format == EventFileFormat.BIN: + if path.find(".bin") == -1: + path += ".bin" + save_to_bin_file(path=path, events=self.buffer) + elif file_format == EventFileFormat.CSV: + if path.find(".csv") == -1: + path += ".csv" + if len(self.__parsed_buffer) == 0: + self.__parse_buffer() + save_to_csv_file(path=path, events=self.__parsed_buffer) + elif file_format == EventFileFormat.TXT: + if path.find(".txt") == -1: + path += ".txt" + if len(self.__parsed_buffer) == 0: + self.__parse_buffer() + save_to_txt_file(path=path, events=self.__parsed_buffer) + elif file_format == EventFileFormat.HTML: + if path.find(".html") == -1: + path += ".html" + if len(self.__parsed_buffer) == 0: + self.__parse_buffer() + save_to_html_file(path=path, events=self.__parsed_buffer, fw_info=self.__fw_info, + long_type_string=self.__long_type_string) + else: + raise ValueError(f"Incorrect file format. Available formats: " + f"{EventFileFormat.BIN.name}, {EventFileFormat.TXT.name}, " + f"{EventFileFormat.HTML.name}.\n" + f"Transferred audio format: {file_format.name}") + self.__parsed_buffer.clear() + else: + warnings.warn("Buffer size is equal 0.") + + def __parse_buffer(self): + with TSIEventParser() as parser: + for index, item in enumerate(self.buffer): + if len(item.data) > 12: + res = parser.parse(item.data, to_dict=True) + if res[0] < 0: + warnings.warn(f"Cannot parse element {index}. Error {res[0]}") + else: + self.__parsed_buffer.append(res[1]) + if len(res[1].get('type')) > 8 and self.__long_type_string is False: + self.__long_type_string = True diff --git a/UniTAP/dev/ports/modules/capturer/video/__init__.py b/UniTAP/dev/ports/modules/capturer/video/__init__.py new file mode 100644 index 0000000..81331b6 --- /dev/null +++ b/UniTAP/dev/ports/modules/capturer/video/__init__.py @@ -0,0 +1 @@ +from .result_video import PictureFileFormat diff --git a/UniTAP/dev/ports/modules/capturer/video/result_video.py b/UniTAP/dev/ports/modules/capturer/video/result_video.py new file mode 100644 index 0000000..b98e4df --- /dev/null +++ b/UniTAP/dev/ports/modules/capturer/video/result_video.py @@ -0,0 +1,63 @@ +import warnings +from UniTAP.dev.modules.capturer.result_object import ResultObject +from UniTAP.dev.ports.modules.internal_utils.image_formats import PictureFileFormat, VideoFileFormat +from UniTAP.dev.ports.modules.internal_utils.image_utils import save_video_to_bin, save_video_to_mp4 +from UniTAP.utils.uicl_api import video_frame_save_to_file, ImageFileFormat + + +class ResultVideoObject(ResultObject): + """ + Class `ResultVideoObject` inherited from class `ResultObject`. + Class `ResultVideoObject` allows saving captured frames to image `save_image_to_file`. + Also has all the `ResultObject` functionality. + """ + def __init__(self): + super().__init__() + + def save_image_to_file(self, file_format: PictureFileFormat, path: str, index: int): + """ + + Saving selected video frame to file. Supported file formats describe in `PictureFileFormat`. + + Args: + file_format (`PictureFileFormat`) - file format + path (str) - path to save + index (int) - number of video frame in list + """ + if len(self.buffer) > 0: + if file_format == PictureFileFormat.BIN: + video_frame_save_to_file(video_frame=self.buffer[index], path=path, file_type=ImageFileFormat.IFF_BIN) + elif file_format == PictureFileFormat.BMP: + video_frame_save_to_file(video_frame=self.buffer[index], path=path, file_type=ImageFileFormat.IFF_BMP) + elif file_format == PictureFileFormat.PPM: + video_frame_save_to_file(video_frame=self.buffer[index], path=path, file_type=ImageFileFormat.IFF_PPM) + elif file_format == PictureFileFormat.DSC: + video_frame_save_to_file(video_frame=self.buffer[index], path=path, file_type=ImageFileFormat.IFF_DSC) + else: + raise ValueError(f"Incorrect image format. Available formats: " + f"{PictureFileFormat.BIN.name}, {PictureFileFormat.BMP.name}, " + f"{PictureFileFormat.PPM.name}.\n" + f"Transferred image format: {file_format.name}") + else: + warnings.warn("Buffer size is equal 0.") + + def __save_to_video_file(self, file_format: VideoFileFormat, path: str): + """ + Will be implemented later. + """ + if len(self.buffer) > 0: + if file_format == VideoFileFormat.BIN: + save_video_to_bin(path=path, data=self.buffer) + elif file_format == VideoFileFormat.MP4: + save_video_to_mp4(path=path, data=self.buffer) + else: + raise ValueError(f"Incorrect video format. Available formats: " + f"{VideoFileFormat.BIN.name}, {VideoFileFormat.MP4.name}.\n" + f"Transferred video format: {file_format.name}") + else: + warnings.warn("Buffer size is equal 0.") + + def __str__(self): + return f"Start capture time: {self.start_capture_time}\n" \ + f"End capture time: {self.end_capture_time}\n" \ + f"Timestamp: {self.timestamp.__str__()}\n" diff --git a/UniTAP/dev/ports/modules/capturer/video/video_capturer.py b/UniTAP/dev/ports/modules/capturer/video/video_capturer.py new file mode 100644 index 0000000..b4d3071 --- /dev/null +++ b/UniTAP/dev/ports/modules/capturer/video/video_capturer.py @@ -0,0 +1,224 @@ +import time +import warnings +import copy + +from typing import Union, List + +from UniTAP.dev.modules.capturer.capture import Capturer, CaptureConfig +from UniTAP.dev.modules.capturer.statuses import VideoCaptureStatus +from UniTAP.common import VideoFrame, VideoFrameDSC +from .result_video import ResultVideoObject + + +class VideoCapturer: + """ + Class `VideoCapturer` allows working with capturing video frames on Sink (RX - receiver) side. + You can `start` capturing in several modes, `stop` capturing, getting current `status` and result of + capturing `capture_result`. + """ + + def __init__(self, capturer: Capturer, max_stream_number: int): + self.__capturer = capturer + self.__result = ResultVideoObject() + self.__max_stream_number = max_stream_number + + @property + def status(self) -> VideoCaptureStatus: + """ + Returns current video capturer status. + + Returns: + object of `VideoCaptureStatus` type + """ + return self.__capturer.video_capturer_status + + @property + def capture_result(self) -> ResultVideoObject: + """ + Returns result of video capturing. + + Returns: + object of `ResultVideoObject` type + """ + return self.__result + + @property + def max_stream_number(self) -> int: + """ + Returns max stream number supported for capturing. + + Returns: + object of `int` type + """ + return self.__max_stream_number + + def stop(self): + """ + Stop capture video. + """ + config = CaptureConfig() + config.video = True + config.type = CaptureConfig.Type.LIVE + + self.__capturer.stop_capture(config) + self.__result.end_capture_time = time.time() + + def pop_element(self) -> Union[VideoFrame, VideoFrameDSC]: + """ + Return first object of `VideoFrame` or `VideoFrameDSC`. + + Returns: + object of `VideoFrame` or `VideoFrameDSC` type + """ + if self.status == VideoCaptureStatus.Idle: + warnings.warn("Video capture is not working now. Please, turn it on.") + captured_video_frames = self.__capturer.capture_video_by_n_frames(1) + self.__result.buffer.extend(copy.deepcopy(captured_video_frames)) + return captured_video_frames[0] + + def pop_element_as_result_object(self) -> ResultVideoObject: + """ + Return captured video frame(objects of `VideoFrame` or VideoFrameDSC`) as `ResultVideoObject`. + + Returns: + object of `ResultVideoObject` type + """ + available_frame_count = self.__capturer.get_available_video_frame_count() + captured_video_frames = self.__capturer.capture_video_by_n_frames(available_frame_count) + + res = ResultVideoObject() + res.buffer.extend(captured_video_frames) + return res + + def pop_all_elements(self) -> Union[List[VideoFrame], List[VideoFrameDSC]]: + """ + Return all captured video frames(objects of `VideoFrame` or `VideoFrameDSC`). + + Returns: + object of list[`VideoFrame` or `VideoFrameDSC`] type + """ + if self.status == VideoCaptureStatus.Idle: + warnings.warn("Video capture is not working now. Please, turn it on.") + available_frame_count = self.__capturer.get_available_video_frame_count() + print(f"Available frames count {available_frame_count}") + captured_video_frames = self.__capturer.capture_video_by_n_frames(available_frame_count) + self.__result.buffer.extend(copy.deepcopy(captured_video_frames)) + return captured_video_frames + + def get_crc(self, crc_frame_count: int = 1) -> List[tuple[int, int, int]]: + """ + Returns captured crc values. + + Returns: + list[tuple[int, int, int]] + """ + return self.__capturer.capture_crc(crc_frame_count) + + +class VideoCapturerDP(VideoCapturer): + """ + Class `VideoCapturerDP` inherited from class `VideoCapturer` and also allows working with capturing video frames + on DP Sink (RX - receiver) side. + You can `start` capturing in several modes, `stop` capturing, getting current `status` and result of + capturing `capture_result`. + """ + + def __init__(self, capturer: Capturer, max_stream_number: int): + super().__init__(capturer, max_stream_number) + self.__capturer = capturer + + def start(self, frames_count: int = 0, sec: int = 0, stream_number: int = 0, + capture_type: CaptureConfig.Type = CaptureConfig.Type.LIVE): + """ + Start capturing. Possible some variants of capturing: + - Capture with fixed frames count (will be captured fixed frames count and capturing will be stopped). + - Capture with fixed time (capturing will be continued fixed seconds and capturing will be stopped). + - Capture without parameters - Live capturing (for getting frames you need to use functions `pop_element` and + `pop_all_elements`) + + All results can be obtained using the function `capture_result`. + + Args: + frames_count (int) + sec (int) + stream_number (int) + capture_type (CaptureConfig.Type) + """ + + if stream_number >= self.max_stream_number: + assert ValueError(f"Incorrect index of stream. Value must be from available options: " + f"{', '.join([str(i) for i in range(self.max_stream_number)])}") + + self.__capturer.set_video_stream_number(stream_number) + + config = CaptureConfig() + config.video = True + config.type = capture_type + + self.__capturer.start_capture(config) + + self.capture_result.clear() + self.capture_result.start_capture_time = time.time() + + if frames_count > 0: + self.capture_result.buffer.extend(self.__capturer.capture_video_by_n_frames(frames_count, capture_type)) + elif sec > 0: + self.capture_result.buffer.extend(self.__capturer.capture_video_by_n_sec(sec)) + + + def get_buffer_capacity(self, stream_number: int = 0): + return self.__capturer.get_buffer_capacity(stream_number) + + +class VideoCapturerHDMI(VideoCapturer): + """ + Class `VideoCapturerHDMI` inherited from class `VideoCapturer` and also allows working with capturing video frames + on HDMI Sink (RX - receiver) side. + You can `start` capturing in several modes, `stop` capturing, getting current `status` and result of + capturing `capture_result`. + """ + + def __init__(self, capturer: Capturer, max_stream_number: int): + super().__init__(capturer, max_stream_number) + self.__capturer = capturer + + def start(self, frames_count: int = 0, sec: int = 0, stream_number: int = 0): + """ + Start capturing. Possible some variants of capturing: + - Capture with fixed frames count (will be captured fixed frames count and capturing will be stopped). + - Capture with fixed time (capturing will be continued fixed seconds and capturing will be stopped). + - Capture without parameters - Live capturing (for getting frames you need to use functions `pop_element` and + `pop_all_elements`) + + All results can be obtained using the function `capture_result`. + + Args: + frames_count (int) + sec (int) + stream_number (int) + """ + + if stream_number >= self.max_stream_number: + assert ValueError(f"Incorrect index of stream. Value must be from available options: " + f"{', '.join([str(i) for i in range(self.max_stream_number)])}") + + config = CaptureConfig() + config.video = True + config.type = CaptureConfig.Type.LIVE + + self.__capturer.start_capture(config) + + self.capture_result.clear() + self.capture_result.start_capture_time = time.time() + + if frames_count > 0: + if frames_count > 1: + self.capture_result.buffer.extend(self.__capturer.capture_video_by_n_frames(frames_count)) + else: + self.capture_result.buffer.extend(self.__capturer.capture_video_by_n_frames(frames_count)) + elif sec > 0: + self.capture_result.buffer.extend(self.__capturer.capture_video_by_n_sec(sec)) + + + def get_buffer_capacity(self): + return self.__capturer.get_buffer_capacity() diff --git a/UniTAP/dev/ports/modules/cec/__init__.py b/UniTAP/dev/ports/modules/cec/__init__.py new file mode 100644 index 0000000..5b7be48 --- /dev/null +++ b/UniTAP/dev/ports/modules/cec/__init__.py @@ -0,0 +1,2 @@ +from .cec_types import (CECStatus, DeviceType, CecResetDataCommand, LogicalAddressEnum, CECCommand, CECCommand, + DeviceTypeEnum) diff --git a/UniTAP/dev/ports/modules/cec/cec_private_types.py b/UniTAP/dev/ports/modules/cec/cec_private_types.py new file mode 100644 index 0000000..5604ad5 --- /dev/null +++ b/UniTAP/dev/ports/modules/cec/cec_private_types.py @@ -0,0 +1,41 @@ +from ctypes import Structure, c_uint32, c_uint16 +from .cec_types import DeviceType + + +class DeviceTypePrivate(Structure): + _fields_ = [ + ("reserved", c_uint32, 2), + ("cecSwitch", c_uint32, 1), + ('audioSystem', c_uint32, 1), + ('pbDevice', c_uint32, 1), + ('tuner', c_uint32, 1), + ('recDev', c_uint32, 1), + ('tv', c_uint32, 1), + ] + + @property + def value(self) -> int: + return (self.reserved | self.cecSwitch << 2 | self.audioSystem << 3 | self.pbDevice << 4 | self.tuner << 5 + | self.recDev << 6 | self.tv << 7) + + @value.setter + def value(self, dev_type: DeviceType): + self.cecSwitch = dev_type.cec_switch + self.audioSystem = dev_type.audio_system + self.pbDevice = dev_type.pb_device + self.tuner = dev_type.tuner + self.recDev = dev_type.rec_dev + self.tv = dev_type.tv + + +class CECStatusPrivate(Structure): + _fields_ = [ + ("bufferOverflowed", c_uint32, 1) + ] + + +class CECVersion(Structure): + _fields_ = [ + ("minor", c_uint16), + ("major", c_uint16) + ] diff --git a/UniTAP/dev/ports/modules/cec/cec_rx.py b/UniTAP/dev/ports/modules/cec/cec_rx.py new file mode 100644 index 0000000..0ef45e4 --- /dev/null +++ b/UniTAP/dev/ports/modules/cec/cec_rx.py @@ -0,0 +1,269 @@ +from UniTAP.libs.lib_tsi.tsi_io import PortIO +from UniTAP.libs.lib_tsi.tsi import * +from .cec_types import * +from .cec_private_types import * +from typing import Union + + +class CecRx: + + def __init__(self, port_io: PortIO): + self.__io = port_io + self.__version = self.__read_version() + self.__commands = [] + + @property + def version(self) -> (int, int): + """ + Read CEC version from the RX side. + + Returns: + object of tuple of `int` type + """ + return self.__version + + def enable(self, state: bool): + """ + Enable/Disable CEC. + + Args: + state (`bool`) + """ + self.__io.set(TSI_HDRX_CEC_CONTROL, state, c_uint32) + + def is_enabled(self) -> bool: + """ + Return state of CEC (enabled or not). + + Returns: + object of `bool` type + """ + return self.__io.get(TSI_HDRX_CEC_CONTROL, c_uint32)[1] & 0x1 == 1 + + @property + def logical_address(self) -> LogicalAddressEnum: + """ + Set/Get CEC logical address. + + Returns: + object of `LogicalAddressEnum` type + """ + return LogicalAddressEnum(self.__io.get(TSI_HDRX_CEC_LOGICAL_ADDRESS, c_uint32)[1]) + + @logical_address.setter + def logical_address(self, address: LogicalAddressEnum): + self.__io.set(TSI_HDRX_CEC_LOGICAL_ADDRESS, address.value, c_uint32) + + @property + def destination(self) -> LogicalAddressEnum: + """ + Set/Get CEC destination address. + + Returns: + object of `LogicalAddressEnum` type + """ + return LogicalAddressEnum(self.__io.get(TSI_HDRX_CEC_DESTINATION, c_uint32)[1]) + + @destination.setter + def destination(self, address: LogicalAddressEnum): + self.__io.set(TSI_HDRX_CEC_DESTINATION, address.value, c_uint32) + + @property + def phy_address(self) -> int: + """ + Set/Get CEC physical address. + + Returns: + object of `int` type + """ + return self.__io.get(TSI_HDRX_CEC_PHYSICAL_ADDRESS, c_uint32)[1] + + @phy_address.setter + def phy_address(self, address: int): + self.__io.set(TSI_HDRX_CEC_PHYSICAL_ADDRESS, address, c_uint32) + + @property + def op_code(self) -> int: + """ + Set/Get CEC operation code. + + Returns: + object of `int` type + """ + return self.__io.get(TSI_HDRX_CEC_OP_CODE, c_uint32)[1] + + @op_code.setter + def op_code(self, code: int): + self.__io.set(TSI_HDRX_CEC_OP_CODE, code, c_uint32) + + @property + def op_code_param(self) -> Union[int, list]: + """ + Set/Get CEC additional parameter for operation code. + + Returns: + object of `int` type + """ + return self.__io.get(TSI_HDRX_CEC_OP_CODE_PARAM, c_uint32)[1] + + @op_code_param.setter + def op_code_param(self, code_param: Union[int, list]): + data_count = len(code_param) if isinstance(code_param, list) else 1 + self.__io.set(TSI_HDRX_CEC_OP_CODE_PARAM, code_param, c_uint32, data_count=data_count) + + @property + def device_type(self) -> DeviceType: + """ + Set/Get CEC Device type. + + Returns: + object of `DeviceType` type + """ + data = self.__io.get(TSI_HDRX_CEC_DEVICE_TYPE, DeviceTypePrivate)[1] + return DeviceType(data.cecSwitch, data.audioSystem, data.pbDevice, data.tuner, data.recDev, data.tv) + + @device_type.setter + def device_type(self, dev_type: Union[DeviceTypeEnum, DeviceType, int]): + if isinstance(dev_type, DeviceType): + dev_type_value = DeviceTypePrivate() + dev_type_value.value = dev_type + value = dev_type_value.value + elif isinstance(dev_type, DeviceTypeEnum): + value = dev_type.value + elif isinstance(dev_type, int): + value = dev_type & 0xFF + else: + raise TypeError(f"Unsupported type: {type(dev_type)}") + self.__io.set(TSI_HDRX_CEC_DEVICE_TYPE, value, c_uint32) + + @property + def status(self) -> CECStatus: + """ + Get CEC status. + + Returns: + object of `CECStatus` type + """ + data = self.__io.get(TSI_HDRX_CEC_STATUS_R, CECStatusPrivate)[1] + return CECStatus(data.bufferOverflowed) + + @property + def command_number(self) -> int: + """ + Get CEC command number. + + Returns: + object of `int` type + """ + return self.__io.get(TSI_HDRX_CEC_RECEIVED_CNT_R, c_uint32)[1] + + @property + def data(self) -> list: + """ + Get CEC raw data. + + Returns: + object of `list` type + """ + data_size = self.__io.get(TSI_HDRX_CEC_DATA_SIZE_R, c_uint32)[1] + return self.__io.get(TSI_HDRX_CEC_DATA_RECIEVED, c_uint32, data_size)[1] + + def reset_data(self, command: CecResetDataCommand): + """ + Get CEC reset data. + + Returns: + object of `CecResetDataCommand` type + """ + self.__io.set(TSI_HDRX_CEC_DATA_RESET_W, command.value, c_uint32) + + def __read_version(self) -> (int, int): + version = self.__io.get(TSI_HDRX_CEC_VERSION_R, CECVersion)[1] + return version.minor, version.major + + def add_command(self, command: CECCommand = CECCommand()): + """ + Add CEC command in list. + + Args: + command (`CECCommand`) + """ + self.__commands.append(command) + + def delete_command(self, index: int = 0): + """ + Delete CEC command in list by index. + + Args: + index (`int`) + """ + self.__commands.pop(index) + + def send_command(self, logical_address: LogicalAddressEnum = LogicalAddressEnum.Tv, + destination: LogicalAddressEnum = LogicalAddressEnum.Broadcast, + phy_address: int = 0x0, op_code: int = 0x0, op_code_param: Union[int, list] = 0x0, + device_type: Union[DeviceTypeEnum, DeviceType, int] = DeviceTypeEnum.TV): + """ + Send CEC command with the transferred parameters. + + Args: + logical_address (`LogicalAddressEnum`) + destination (`LogicalAddressEnum`) + phy_address (`int`) + op_code (`int`) + op_code_param (`int`) + device_type (`DeviceTypeEnum` | `DeviceType`) + """ + self.enable(True) + self.logical_address = logical_address + self.phy_address = phy_address + self.op_code = op_code + self.op_code_param = op_code_param + self.device_type = device_type + self.destination = destination + self.__send_cec_command() + + def send_command_by_index(self, index: int): + """ + Send CEC command from the list by index. + + Args: + index (`int`) + """ + if len(self.__commands) > 0 and 0 <= index < len(self.__commands): + self.__send_command(self.__commands[index]) + + def send_commands(self): + """ + Send all saved commands. + """ + for command in self.__commands: + self.__send_command(command) + + def __send_command(self, command: CECCommand = CECCommand()): + self.logical_address = command.logical_address + self.destination = command.destination + self.phy_address = command.phy_address + self.op_code = command.op_code + self.op_code_param = command.op_code_param + self.device_type = command.device_type + self.__send_cec_command() + + def __send_cec_command(self): + self.__io.set(TSI_HDRX_CEC_COMMAND, 1, c_uint32) + + @property + def commands(self) -> list: + """ + Get CEC command list. + + Returns: + object of `list` type + """ + return self.__commands + + def clear_commands(self): + """ + Clear saved commands. + """ + self.__commands.clear() diff --git a/UniTAP/dev/ports/modules/cec/cec_tx.py b/UniTAP/dev/ports/modules/cec/cec_tx.py new file mode 100644 index 0000000..cdfefa3 --- /dev/null +++ b/UniTAP/dev/ports/modules/cec/cec_tx.py @@ -0,0 +1,269 @@ +from UniTAP.libs.lib_tsi.tsi_io import PortIO +from UniTAP.libs.lib_tsi.tsi import * +from .cec_types import * +from .cec_private_types import * +from typing import Union + + +class CecTx: + + def __init__(self, port_io: PortIO): + self.__io = port_io + self.__version = self.__read_version() + self.__commands = [] + + @property + def version(self) -> (int, int): + """ + Read CEC version from the TX side. + + Returns: + object of tuple of `int` type + """ + return self.__version + + def enable(self, state: bool): + """ + Enable/Disable CEC. + + Args: + state (`bool`) + """ + self.__io.set(TSI_HDTX_CEC_CONTROL, state, c_uint32) + + def is_enabled(self) -> bool: + """ + Return state of CEC (enabled or not). + + Returns: + object of `bool` type + """ + return self.__io.get(TSI_HDTX_CEC_CONTROL, c_uint32)[1] & 0x1 == 1 + + @property + def logical_address(self) -> LogicalAddressEnum: + """ + Set/Get CEC logical address. + + Returns: + object of `LogicalAddressEnum` type + """ + return LogicalAddressEnum(self.__io.get(TSI_HDTX_CEC_LOGICAL_ADDRESS, c_uint32)[1]) + + @logical_address.setter + def logical_address(self, address: LogicalAddressEnum): + self.__io.set(TSI_HDTX_CEC_LOGICAL_ADDRESS, address.value, c_uint32) + + @property + def destination(self) -> LogicalAddressEnum: + """ + Set/Get CEC destination address. + + Returns: + object of `LogicalAddressEnum` type + """ + return LogicalAddressEnum(self.__io.get(TSI_HDTX_CEC_DESTINATION, c_uint32)[1]) + + @destination.setter + def destination(self, address: LogicalAddressEnum): + self.__io.set(TSI_HDTX_CEC_DESTINATION, address.value, c_uint32) + + @property + def phy_address(self) -> int: + """ + Set/Get CEC physical address. + + Returns: + object of `int` type + """ + return self.__io.get(TSI_HDTX_CEC_PHYSICAL_ADDRESS, c_uint32)[1] + + @phy_address.setter + def phy_address(self, address: int): + self.__io.set(TSI_HDTX_CEC_PHYSICAL_ADDRESS, address, c_uint32) + + @property + def op_code(self) -> int: + """ + Set/Get CEC operation code. + + Returns: + object of `int` type + """ + return self.__io.get(TSI_HDTX_CEC_OP_CODE, c_uint32)[1] + + @op_code.setter + def op_code(self, code: int): + self.__io.set(TSI_HDTX_CEC_OP_CODE, code, c_uint32) + + @property + def op_code_param(self) -> Union[int, list]: + """ + Set/Get CEC additional parameter for operation code. + + Returns: + object of `int` type + """ + return self.__io.get(TSI_HDTX_CEC_OP_CODE_PARAM, c_uint32)[1] + + @op_code_param.setter + def op_code_param(self, code_param: Union[int, list]): + data_count = len(code_param) if isinstance(code_param, list) else 1 + self.__io.set(TSI_HDTX_CEC_OP_CODE_PARAM, code_param, c_uint32, data_count=data_count) + + @property + def device_type(self) -> DeviceType: + """ + Set/Get CEC Device type. + + Returns: + object of `DeviceType` type + """ + data = self.__io.get(TSI_HDTX_CEC_DEVICE_TYPE, DeviceTypePrivate)[1] + return DeviceType(data.cecSwitch, data.audioSystem, data.pbDevice, data.tuner, data.recDev, data.tv) + + @device_type.setter + def device_type(self, dev_type: Union[DeviceTypeEnum, DeviceType, int]): + if isinstance(dev_type, DeviceType): + dev_type_value = DeviceTypePrivate() + dev_type_value.value = dev_type + value = dev_type_value.value + elif isinstance(dev_type, DeviceTypeEnum): + value = dev_type.value + elif isinstance(dev_type, int): + value = dev_type & 0xFF + else: + raise TypeError(f"Unsupported type: {type(dev_type)}") + self.__io.set(TSI_HDTX_CEC_DEVICE_TYPE, value, c_uint32) + + @property + def status(self) -> CECStatus: + """ + Get CEC status. + + Returns: + object of `CECStatus` type + """ + data = self.__io.get(TSI_HDTX_CEC_STATUS_R, CECStatusPrivate)[1] + return CECStatus(data.bufferOverflowed) + + @property + def command_number(self) -> int: + """ + Get CEC command number. + + Returns: + object of `int` type + """ + return self.__io.get(TSI_HDTX_CEC_RECEIVED_CNT_R, c_uint32)[1] + + @property + def data(self) -> list: + """ + Get CEC raw data. + + Returns: + object of `list` type + """ + data_size = self.__io.get(TSI_HDTX_CEC_DATA_SIZE_R, c_uint32)[1] + return self.__io.get(TSI_HDTX_CEC_DATA_RECEIVED_R, c_uint32, data_size)[1] + + def reset_data(self, command: CecResetDataCommand): + """ + Get CEC reset data. + + Returns: + object of `CecResetDataCommand` type + """ + self.__io.set(TSI_HDTX_CEC_DATA_RESET_W, command.value, c_uint32) + + def __read_version(self) -> (int, int): + version = self.__io.get(TSI_HDTX_CEC_VERSION_R, CECVersion)[1] + return version.minor, version.major + + def add_command(self, command: CECCommand = CECCommand()): + """ + Add CEC command in list. + + Args: + command (`CECCommand`) + """ + self.__commands.append(command) + + def delete_command(self, index: int = 0): + """ + Delete CEC command in list by index. + + Args: + index (`int`) + """ + self.__commands.pop(index) + + def send_command(self, logical_address: LogicalAddressEnum = LogicalAddressEnum.Tv, + destination: LogicalAddressEnum = LogicalAddressEnum.Broadcast, + phy_address: int = 0x0, op_code: int = 0x0, op_code_param: Union[int, list] = 0x0, + device_type: Union[DeviceTypeEnum, DeviceType, int] = DeviceTypeEnum.TV): + """ + Send CEC command with the transferred parameters. + + Args: + logical_address (`LogicalAddressEnum`) + destination (`LogicalAddressEnum`) + phy_address (`int`) + op_code (`int`) + op_code_param (`int`) + device_type (`DeviceTypeEnum` | `DeviceType`) + """ + self.enable(True) + self.logical_address = logical_address + self.phy_address = phy_address + self.op_code = op_code + self.op_code_param = op_code_param + self.device_type = device_type + self.destination = destination + self.__send_cec_command() + + def send_command_by_index(self, index: int): + """ + Send CEC command from the list by index. + + Args: + index (`int`) + """ + if len(self.__commands) > 0 and 0 <= index < len(self.__commands): + self.__send_command(self.__commands[index]) + + def send_commands(self): + """ + Send all saved commands. + """ + for command in self.__commands: + self.__send_command(command) + + def __send_command(self, command: CECCommand = CECCommand()): + self.logical_address = command.logical_address + self.destination = command.destination + self.phy_address = command.phy_address + self.op_code = command.op_code + self.op_code_param = command.op_code_param + self.device_type = command.device_type + self.__send_cec_command() + + def __send_cec_command(self): + self.__io.set(TSI_HDTX_CEC_COMMAND, 1, c_uint32) + + @property + def commands(self) -> list: + """ + Get CEC command list. + + Returns: + object of `list` type + """ + return self.__commands + + def clear_commands(self): + """ + Clear saved commands. + """ + self.__commands.clear() diff --git a/UniTAP/dev/ports/modules/cec/cec_types.py b/UniTAP/dev/ports/modules/cec/cec_types.py new file mode 100644 index 0000000..916d2d3 --- /dev/null +++ b/UniTAP/dev/ports/modules/cec/cec_types.py @@ -0,0 +1,134 @@ +from enum import IntEnum +from typing import Union + + +class LogicalAddressEnum(IntEnum): + """ + Class `PRCommand` contains all possible variants of Panel Replay Command. + """ + + Tv = 0x0, + RecDevice1 = 0x1, + RecDevice2 = 0x2, + Tuner1 = 0x3, + PlayDevice1 = 0x4, + AudioSystem = 0x5, + Tuner2 = 0x6, + Tuner3 = 0x7, + PlayDevice2 = 0x8, + RecDevice3 = 0x9, + Tuner4 = 0xA, + PlayDevice3 = 0xB, + Reserved1 = 0xC, + Reserved2 = 0xD, + SpecificUse = 0xE, + Broadcast = 0xF, + Unregistered = Broadcast + + +class DeviceTypeEnum(IntEnum): + CECSwitch = 0x1 << 2, + AudioSystem = 0x1 << 3, + PlayBack = 0x1 << 4, + Tuner = 0x1 << 5, + Rec = 0x1 << 6, + TV = 0x1 << 7 + + +class CecResetDataCommand(IntEnum): + """ + Class `PRCommand` contains all possible variants of Panel Replay Command. + """ + OneFrame = 1, + AllFrames = 2 + + +class DeviceType: + + def __init__(self, cec_switch: bool = False, audio_system: bool = False, pb_device: bool = False, + tuner: bool = False, rec_dev: bool = False, tv: bool = False): + self.cec_switch = cec_switch + self.audio_system = audio_system + self.pb_device = pb_device + self.tuner = tuner + self.rec_dev = rec_dev + self.tv = tv + + def __str__(self) -> str: + return f"CEC switch - {self.cec_switch}\n" \ + f"Audio System - {self.audio_system}\n" \ + f"PD Device - {self.pb_device}\n" \ + f"Tuner - {self.tuner}\n" \ + f"Rec dev - {self.rec_dev}\n" \ + f"TV - {self.tv}\n" + + +class CECStatus: + + def __init__(self, buffer_overflowed: bool = False): + self.buffer_overflowed = buffer_overflowed + + def __str__(self) -> str: + return f"Buffer Overflowed - {self.buffer_overflowed}\n" + + +class CECCommand: + + def __init__(self, logical_address: LogicalAddressEnum = LogicalAddressEnum.Tv, + phy_address: int = 0x0, op_code: int = 0x0, op_code_param: Union[int, list] = 0x0, + device_type: DeviceType = DeviceType(), + destination: LogicalAddressEnum = LogicalAddressEnum.Broadcast): + self.__logical_address = logical_address + self.__phy_address = phy_address + self.__op_code = op_code + self.__op_code_param = op_code_param + self.__device_type = device_type + self.__destination = destination + + @property + def logical_address(self) -> LogicalAddressEnum: + return self.__logical_address + + @logical_address.setter + def logical_address(self, address: LogicalAddressEnum): + self.__logical_address = address + + @property + def phy_address(self) -> int: + return self.__phy_address + + @phy_address.setter + def phy_address(self, address: int): + self.__phy_address = address + + @property + def op_code(self) -> int: + return self.__op_code + + @op_code.setter + def op_code(self, op_code: int): + self.__op_code = op_code + + @property + def op_code_param(self) -> Union[int, list]: + return self.__op_code_param + + @op_code_param.setter + def op_code_param(self, param: Union[int, list]): + self.__op_code_param = param + + @property + def device_type(self) -> DeviceType: + return self.__device_type + + @device_type.setter + def device_type(self, dev_type: DeviceType): + self.__device_type = dev_type + + @property + def destination(self) -> LogicalAddressEnum: + return self.__destination + + @destination.setter + def destination(self, address: LogicalAddressEnum): + self.__destination = address diff --git a/UniTAP/dev/ports/modules/device_constants.py b/UniTAP/dev/ports/modules/device_constants.py new file mode 100644 index 0000000..07074b8 --- /dev/null +++ b/UniTAP/dev/ports/modules/device_constants.py @@ -0,0 +1,304 @@ +import time +import warnings + +case_values_pg = [[800, 525, 640, 480, 144, 35, 96, 2, 60000, 0, 1, False, False, 0, "Color Bars"], + [858, 525, 720, 480, 122, 36, 62, 6, 60000, 0, 1, False, False, 0, "Color Bars"], + [858, 525, 720, 480, 122, 36, 62, 6, 120000, 0, 1, False, False, 0, "Color Bars"], + [1056, 628, 800, 600, 216, 27, 128, 4, 60000, 0, 1, True, True, 0, "Color Bars"], + [1088, 517, 848, 480, 224, 31, 112, 8, 60000, 0, 1, True, True, 0, "Color Bars"], + [1344, 806, 1024, 768, 296, 35, 136, 6, 60000, 0, 1, False, False, 0, "Color Bars"], + [1650, 750, 1280, 720, 260, 25, 40, 5, 60000, 0, 1, True, True, 0, "Color Bars"], + [1650, 750, 1280, 720, 260, 25, 40, 5, 120000, 0, 1, True, True, 0, "Color Bars"], + [1440, 790, 1280, 768, 112, 19, 32, 7, 60000, 0, 1, True, False, 0, "Color Bars"], + [1664, 798, 1280, 768, 320, 27, 128, 7, 60000, 0, 1, False, True, 0, "Color Bars"], + [1440, 823, 1280, 800, 112, 20, 32, 6, 60000, 0, 1, True, False, 1, "Color Bars"], + [1680, 831, 1280, 800, 328, 28, 128, 6, 60000, 0, 1, False, True, 0, "Color Bars"], + [1800, 1000, 1280, 960, 424, 39, 112, 3, 60000, 0, 1, True, True, 0, "Color Bars"], + [1688, 1066, 1280, 1024, 360, 41, 112, 3, 60000, 0, 1, True, True, 0, "Color Bars"], + [1792, 795, 1360, 768, 368, 24, 112, 6, 60000, 0, 1, True, True, 0, "Color Bars"], + [1560, 1080, 1400, 1050, 112, 27, 32, 4, 60000, 0, 1, False, False, 1, "Color Bars"], + [1864, 1089, 1400, 1050, 376, 36, 144, 4, 60000, 0, 1, False, False, 1, "Color Bars"], + # [1716, 525, 1440, 480, 244, 36, 124, 6, 59940, 0, 1, False, False, 0, "Color Bars"], + [1728, 625, 1440, 576, 264, 44, 128, 5, 50000, 0, 1, False, False, 0, "Color Bars"], + [1760, 1235, 1600, 1200, 112, 32, 32, 4, 60000, 0, 1, False, False, 1, "Color Bars"], + [2160, 1250, 1600, 1200, 496, 49, 192, 3, 60000, 0, 1, True, True, 0, "Color Bars"], + [2200, 750, 1680, 720, 260, 25, 40, 5, 60000, 0, 1, True, True, 0, "Color Bars"], + [2000, 825, 1680, 720, 260, 100, 40, 5, 120000, 0, 1, True, True, 0, "Color Bars"], + [1840, 1080, 1680, 1050, 112, 27, 32, 6, 60000, 0, 1, True, False, 1, "Color Bars"], + [2240, 1089, 1680, 1050, 456, 36, 176, 6, 60000, 0, 1, False, True, 0, "Color Bars"], + [2448, 1394, 1792, 1344, 528, 49, 200, 3, 60000, 0, 1, False, True, 0, "Color Bars"], + [2528, 1439, 1856, 1392, 576, 46, 224, 3, 60000, 0, 1, False, True, 0, "Color Bars"], + [2080, 1096, 1920, 1080, 112, 13, 32, 5, 30000, 0, 1, True, False, 1, "Color Bars"], + [2000, 1096, 1920, 1080, 72, 14, 32, 8, 30000, 0, 1, True, False, 2, "Color Bars"], + [2200, 1125, 1920, 1080, 192, 41, 44, 5, 30000, 0, 1, True, True, 0, "Color Bars"], + # [2080, 1157, 1920, 1080, 152, 14, 32, 8, 144050, 0, 1, False, False, 3, "Color Bars"], + # [2080, 1190, 1920, 1080, 152, 14, 32, 8, 200070, 0, 1, False, False, 3, "Color Bars"], + [2080, 1111, 1920, 1080, 112, 28, 32, 5, 60000, 0, 1, False, False, 1, "Color Bars"], + [2080, 1111, 1920, 1080, 72, 14, 32, 8, 60000, 0, 1, True, True, 2, "Color Bars"], + [2200, 1125, 1920, 1080, 192, 41, 44, 5, 60000, 0, 1, True, True, 0, "Color Bars"], + [2080, 1144, 1920, 1080, 112, 61, 32, 5, 120000, 0, 1, True, False, 1, "Color Bars"], + [2000, 1144, 1920, 1080, 72, 14, 32, 8, 120000, 0, 1, True, False, 2, "Color Bars"], + [2200, 1125, 1920, 1080, 192, 41, 44, 5, 120000, 0, 1, True, True, 0, "Color Bars"], + [2592, 1245, 1920, 1200, 536, 42, 200, 6, 60000, 0, 1, False, True, 0, "Color Bars"], + [2600, 1500, 1920, 1440, 552, 59, 208, 3, 60000, 0, 1, False, True, 0, "Color Bars"], + [2208, 1580, 2048, 1536, 112, 41, 32, 4, 60000, 0, 1, True, False, 1, "Color Bars"], + [2640, 1481, 2560, 1440, 72, 14, 32, 8, 60000, 0, 1, True, False, 2, "Color Bars"], + [2720, 1481, 2560, 1440, 112, 38, 32, 5, 60000, 0, 1, True, False, 1, "Color Bars"], + # [2720, 1543, 2560, 1440, 152, 14, 32, 8, 144050, 0, 1, True, False, 3, "Color Bars"], + # [2720, 1586, 2560, 1440, 152, 14, 32, 8, 200070, 0, 1, True, False, 3, "Color Bars"], + [3424, 1120, 2560, 1080, 704, 37, 272, 10, 60000, 0, 1, False, True, 0, "Color Bars"], + [2720, 1111, 2560, 1080, 112, 28, 32, 10, 60000, 0, 1, True, False, 1, "Color Bars"], + # [2720, 1157, 2560, 1080, 152, 14, 32, 8, 144051, 0, 1, True, False, 3, "Color Bars"], + # [2720, 1190, 2560, 1080, 152, 14, 32, 8, 200070, 0, 1, True, False, 3, "Color Bars"], + [3000, 1100, 2560, 1080, 192, 16, 44, 5, 60000, 0, 1, True, True, 0, "Color Bars"], + [3300, 1250, 2560, 1080, 192, 16, 44, 5, 120000, 0, 1, True, True, 0, "Color Bars"], + [3504, 1658, 2560, 1600, 752, 55, 280, 6, 60000, 0, 1, False, True, 0, "Color Bars"], + [2720, 1646, 2560, 1600, 112, 43, 32, 6, 60000, 0, 1, True, False, 1, "Color Bars"], + [2976, 1456, 2880, 1440, 48, 8, 8, 1, 60000, 0, 1, True, True, 0, "Color Bars"], + [4176, 2222, 4096, 2160, 72, 14, 32, 8, 60000, 0, 1, True, False, 2, "Color Bars"], + [4000, 2191, 3840, 2160, 112, 28, 32, 5, 30000, 0, 1, True, False, 1, "Color Bars"], + [3920, 2191, 3840, 2160, 72, 14, 32, 8, 30000, 0, 1, True, False, 2, "Color Bars"], + # [4000, 2314, 3840, 2160, 152, 14, 32, 8, 144050, 0, 1, True, False, 3, "Color Bars"], + [4400, 2250, 3840, 2160, 384, 82, 88, 10, 30000, 0, 1, True, True, 0, "Color Bars"], + [5280, 2250, 3840, 2160, 384, 82, 88, 10, 50000, 0, 1, True, True, 0, "Color Bars"], + [5280, 2250, 4096, 2160, 216, 82, 88, 10, 50000, 0, 1, True, True, 0, "Color Bars"], + [3840, 2160, 4000, 2222, 112, 59, 32, 5, 60000, 0, 1, True, False, 1, "Color Bars"], + [3920, 2222, 3840, 2160, 72, 14, 32, 8, 60000, 0, 1, True, False, 2, "Color Bars"], + # [4000, 2222, 3840, 2160, 152, 14, 32, 8, 60021, 0, 1, True, False, 3, "Color Bars"], + [4400, 2250, 3840, 2160, 384, 82, 88, 10, 60000, 0, 1, True, True, 0, "Color Bars"], + [4256, 2222, 4096, 2160, 112, 59, 32, 10, 60000, 0, 1, True, False, 1, "Color Bars"], + # [4256, 2222, 4096, 2160, 152, 14, 32, 8, 60021, 0, 1, True, False, 3, "Color Bars"], + # [4256, 2314, 4096, 2160, 152, 14, 32, 8, 144050, 0, 1, True, False, 3, "Color Bars"], + [4400, 2250, 4096, 2160, 216, 82, 88, 10, 60000, 0, 1, True, True, 0, "Color Bars"], + [4000, 2287, 3840, 2160, 112, 124, 32, 5, 120000, 0, 1, True, False, 1, "Color Bars"], + [3920, 2287, 3840, 2160, 72, 14, 32, 8, 120000, 0, 1, True, False, 2, "Color Bars"], + [4400, 2250, 3840, 2160, 384, 82, 88, 10, 120000, 0, 1, True, True, 0, "Color Bars"], + [5280, 2191, 5120, 2160, 112, 28, 32, 10, 30000, 0, 1, True, False, 1, "Color Bars"], + [5200, 2191, 5120, 2160, 72, 14, 32, 8, 30000, 0, 1, True, False, 2, "Color Bars"], + [6000, 2200, 5120, 2160, 216, 32, 88, 10, 30000, 0, 1, True, True, 0, "Color Bars"], + [5280, 2222, 5120, 2160, 112, 59, 32, 10, 60000, 0, 1, True, False, 1, "Color Bars"], + [5200, 2222, 5120, 2160, 72, 14, 32, 6, 60000, 0, 1, True, False, 2, "Color Bars"], + [5500, 2250, 5120, 2160, 216, 82, 88, 10, 60000, 0, 1, True, True, 0, "Color Bars"], + [5280, 2287, 5120, 2160, 112, 124, 32, 10, 120000, 0, 1, True, False, 1, "Color Bars"], + # [5280, 2287, 5120, 2160, 152, 14, 32, 8, 120042, 0, 1, True, False, 3, "Color Bars"], + [5200, 2287, 5120, 2160, 72, 14, 32, 8, 120000, 0, 1, True, False, 2, "Color Bars"], + [5500, 2250, 5120, 2160, 216, 82, 88, 10, 120000, 0, 1, True, True, 0, "Color Bars"], + [5280, 2962, 5120, 2880, 112, 79, 32, 5, 60000, 0, 1, True, False, 1, "Color Bars"], + [5200, 2962, 5120, 2880, 72, 14, 32, 8, 60000, 0, 1, True, False, 2, "Color Bars"], + # [5280, 2962, 5120, 2880, 152, 14, 32, 8, 60021, 0, 1, True, False, 3, "Color Bars"], + [11000, 4500, 7680, 4320, 768, 164, 176, 20, 24000, 0, 1, True, True, 0, "Color Bars"], + [7840, 4381, 7680, 4320, 112, 58, 32, 5, 30000, 0, 1, True, False, 1, "Color Bars"], + [7760, 4381, 7680, 4320, 72, 14, 32, 8, 30000, 0, 1, True, False, 2, "Color Bars"], + [9000, 4400, 7680, 4320, 768, 64, 176, 20, 30000, 0, 1, True, True, 0, "Color Bars"], + [7840, 4443, 7680, 4320, 112, 120, 32, 5, 60000, 0, 1, True, True, 1, "Color Bars"], + [7760, 4443, 7680, 4320, 72, 14, 32, 8, 60000, 0, 1, True, False, 2, "Color Bars"], + [9000, 4400, 7680, 4320, 768, 64, 176, 20, 60000, 0, 1, True, True, 0, "Color Bars"], + # [7840, 4529, 7680, 4320, 112, 206, 32, 5, 100000, 0, 1, True, False, 1, "Color Bars"], + # [7760, 4529, 7680, 4320, 72, 14, 32, 8, 100000, 0, 1, True, False, 2, "Color Bars"], + # [10560, 4500, 7680, 4320, 768, 164, 176, 20, 100000, 0, 1, True, True, 0, "Color Bars"], + [11000, 4500, 10240, 4320, 112, 58, 32, 5, 30000, 0, 1, True, False, 1, "Color Bars"], + [11000, 4500, 10240, 4320, 72, 14, 32, 8, 30000, 0, 1, True, False, 2, "Color Bars"], + [12500, 4950, 10240, 4320, 768, 614, 176, 20, 24000, 0, 1, True, True, 0, "Color Bars"], + [13500, 4400, 10240, 4320, 768, 64, 176, 20, 25000, 0, 1, True, True, 0, "Color Bars"], + [11000, 4500, 10240, 4320, 472, 164, 176, 20, 30000, 0, 1, True, True, 0, "Color Bars"] + # [12500, 4950, 10240, 4320, 768, 614, 176, 20, 48000, 0, 1, True, True, 0, "Color Bars"], + # [13500, 4400, 10240, 4320, 768, 64, 176, 20, 50000, 0, 1, True, True, 0, "Color Bars"], + # [11000, 4500, 10240, 4320, 472, 164, 176, 20, 60000, 0, 1, True, True, 0, "Color Bars"], + # [13200, 4500, 10240, 4320, 768, 164, 176, 20, 100000, 0, 1, True, True, 0, "Color Bars"], + # [11000, 4500, 10240, 4320, 472, 164, 176, 20, 120000, 0, 1, True, True, 0, "Color Bars"] + ] + +dict_rate = {1.62: 6, 2.70: 10, 5.40: 20, 6.75: 25, 8.10: 30} + +dict_color_formate = {"RGB": 0, "YCbCr 4:4:4 ITU-601": 1, "YCbCr 4:2:2 ITU-601": 2, "YCbCr 4:2:0 ITU-601": 3, + "YCbCr 4:4:4 ITU-709": 4, "YCbCr 4:2:2 ITU-709": 5, "YCbCr 4:2:0 ITU-709": 6} + +dict_color_formate_rev = {0: "RGB", 1: "YCbCr 4:4:4 ITU-601", 2: "YCbCr 4:2:2 ITU-601", 3: "YCbCr 4:2:0 ITU-601", + 4: "YCbCr 4:4:4 ITU-709", 5: "YCbCr 4:2:2 ITU-709", 6: "YCbCr 4:2:0 ITU-709"} + +dict_color_formate_2 = {"RGB": 0, "YCbCr 4:4:4": 1, "YCbCr 4:2:2": 2, "YCbCr 4:2:0": 3, "Simple 4:2:2": 4} + +dict_colorimetry_rev = {"ITU-601": 0, "ITU-709": 1} + +dict_color_mode_rev = {0: 0, 1: 2, 2: 1, 3: 3, 4: 4} + +dict_color_mode_dp = {0: "No Data", 1: "Unknown", 2: "RGB", 3: "YCbCr 4:2:2", 4: "YCbCr 4:4:4", 5: "YCbCr 4:2:0", + 6: "IDO", 7: "Y only", 8: "Raw", 9: "DSC"} + +dict_colorimetry_dp = {0: "No Data", 1: "Unknown", 2: "RGB", 3: "SMPTE 170M", 4: "ITU-601", 5: "ITU-709", + 6: "xvYCC.601", 7: "xvYCC.709", 8: "sYCC.601", 9: "Adobe YCC.601", 10: "Adobe RGB", + 11: "ITU-R BT2020 (YcCbcCrc)", 12: "ITU-R BT2020 (YCbCr)", 13: "ITU-R BT2020 (RGB)", + 14: "Wide gamut RGB fixed point", 15: "Wide gamut RGB floating point", + 16: "DCI-3 (SMPTE RP 431-1)", 17: "DICOM 1.4 gray scale", 18: "Custom color profile"} + +dict_build_color_format = {"PGImageFormatRGB_080808": 0x000, + "PGImageFormatRGB_161616": 0x001, + "PGImageFormatRGBA_080808A": 0x002, + "PGImageFormatRGBA_161616A": 0x003, + "PGImageFormatRGB_121212": 0x004, + "PGImageFormatYCbCr_080808": 0x100, + "PGImageFormatYCbCr_161616": 0x101, + "PGImageFormatYCbYCr_08": 0x200, + "PGImageFormatYCbYCr_16": 0x201, + "PGImageFormatYYCbYYCr_08": 0x300, + "PGImageFormatYYCbYYCr_16": 0x301, + "PGImageFormatI420_08": 0x310, + "PGImageFormatI420_10": 0x311, + "PGImageFormatI420_12": 0x312, + "PGImageFormatI420_16": 0x313, + "PGImageFormatUnknown": 2147483647} + +dict_bpc = {6: 0, 8: 1, 10: 2, 12: 3, 16: 4, 18: 0, 24: 1, 32: 2, 7: 5, 14: 6} + +dict_bpc_rev = {0: 6, 1: 8, 2: 10, 3: 12, 4: 16, 5: 7, 6: 14} + +dict_usbc_bit_rate_values = {10: 1, 20: 2, 13: 4} +dict_usbc_bit_rate_rev_values = {1: 10, 2: 20, 4: 13} + +dict_color_mode_hdmi = { + 0: "No Data", 1: "Unknown", 2: "RGB", 3: "YCbCr4:2:2", 4: "YCbCr4:4:4", 5: "YCbCr4:2:0", 6: "IDO", 7: "Y-Only", + 8: "Raw" +} + +dict_colorimetry_hdmi = { + 0: "No Data", 1: "Unknown", 2: "RGB", 3: "SMPTE 170M", 4: "ITU-R BT.601", 5: "ITU-R BT.709", 6: "xvYCC.601", + 7: "xvYCC.709", 8: "sYCC.601", 9: "Adobe YCC.601", 10: "Adobe RGB", 11: "ITU-R BT2020 (YcCbcCrc)", + 12: "ITU-R BT2020 (YCbCr)", 13: "ITU-R BT2020 (RGB)", 14: "Wide gamut RGB fixed point", + 15: "Wide gamut RGB floating point", 16: "DCI-3 (SMPTE RP 431-1)", 17: "DICOM 1.4 gray scale", + 18: "Custom color profile" +} + +dict_link_mode_frl = { + 0: "FRL_Disable", + 1: "FRL_3L_03G", + 2: "FRL_3L_06G", + 3: "FRL_4L_06G", + 4: "FRL_4L_08G", + 5: "FRL_4L_10G", + 6: "FRL_4L_12G" +} + +dict_hdmi_behavior = {0: "HDMI 1.4", 1: "HDMI 2.0", 2: "HDMI 2.1"} + +device_roles = {"sink": "RX", "source": "TX"} + +gbit = 1000000000 +available_dp20_link_rates = {10: 10 * gbit, 13.5: 135 * gbit / 10, 20: 20 * gbit} + + +def info_hex(idx: int, data: str, sz: int, gap: int) -> str: + list_r_dp_sdp = ["ACR", "ASP", "AIF"] + tmp = list_r_dp_sdp[idx] + for i in range(sz): + tmp += " " + hex(int(data[i])) + (" " if i == gap else "") + tmp += "\n" + return tmp + + +def info_header(num: int, hbsz: int) -> str: + s = " " + n = 0 + for i in range(num): + if i != hbsz: + s += " " + str(n) + n += 1 + else: + n = 0 + s += " " + return s + + +PDC_STATE_PULLUP_POS = 0 +PDC_STATE_PULLUP_09A = 0 << PDC_STATE_PULLUP_POS +PDC_STATE_PULLUP_15A = 1 << PDC_STATE_PULLUP_POS +PDC_STATE_PULLUP_30A = 2 << PDC_STATE_PULLUP_POS +PDC_STATE_PULLUP_UNKNOWN = 3 << PDC_STATE_PULLUP_POS + +PDC_STATE_DEV_ROLE_POS = 2 +PDC_STATE_DEV_ROLE_UFP = 0 << PDC_STATE_DEV_ROLE_POS +PDC_STATE_DEV_ROLE_DFP = 1 << PDC_STATE_DEV_ROLE_POS +PDC_STATE_DEV_ROLE_DRP = 2 << PDC_STATE_DEV_ROLE_POS +PDC_STATE_DEV_ROLE_UNKNOWN = 3 << PDC_STATE_DEV_ROLE_POS + +PDC_STATE_PD_MODE_POS = 4 +PDC_STATE_PD_MODE_NORMAL = 0 << PDC_STATE_PD_MODE_POS +PDC_STATE_PD_MODE_LSOURCE = 1 << PDC_STATE_PD_MODE_POS +PDC_STATE_PD_MODE_LSINK = 2 << PDC_STATE_PD_MODE_POS +PDC_STATE_PD_MODE_UNKNOWN = 3 << PDC_STATE_PD_MODE_POS + +PDC_STATE_USB3_MODE_POS = 6 +PDC_STATE_USB3_MODE_DISABLED = 0 << PDC_STATE_USB3_MODE_POS +PDC_STATE_USB3_MODE_GEN_1 = 1 << PDC_STATE_USB3_MODE_POS +PDC_STATE_USB3_MODE_GEN_2 = 2 << PDC_STATE_USB3_MODE_POS +PDC_STATE_USB3_MODE_UNKNOWN = 3 << PDC_STATE_USB3_MODE_POS + +PDC_STATE_USB2_ENABLED = 1 << 8 +PDC_STATE_VCONN_SWAP_EN = 1 << 9 +PDC_STATE_PR_SWAP_EN = 1 << 10 +PDC_STATE_DR_SWAP_EN = 1 << 11 +PDC_STATE_DEBUG_ACCESSORY = 1 << 12 +PDC_STATE_AUDIO_ACCESSORY = 1 << 13 +PDC_STATE_FR_SWAP_EN = 1 << 14 + +PDC_STATE_DRP_TRY_MODE_POS = 16 +PDC_STATE_DRP_TRY_MODE_PURE_DRP = 0 << PDC_STATE_DRP_TRY_MODE_POS +PDC_STATE_DRP_TRY_MODE_DRP_TRYSNK = 1 << PDC_STATE_DRP_TRY_MODE_POS +PDC_STATE_DRP_TRY_MODE_DRP_TRYSRC = 2 << PDC_STATE_DRP_TRY_MODE_POS +PDC_STATE_DRP_TRY_MODE_DRP_UNKNOWN = 3 << PDC_STATE_DRP_TRY_MODE_POS + +PG_TIMF_META_REDUCED_BLANK = (1 << 24) +PG_TIMF_META_REDUCED_BLANK_2 = (2 << 24) +PG_TIMF_META_REDUCED_BLANK_3 = (3 << 24) + +CAP_OPTION_AUDIO = 1 +CAP_OPTION_VIDEO = 1 << 1 +CAP_OPTION_EVENTS = 1 << 2 +CAP_OPTION_MODE_LIVE = 1 << 3 +CRC_MAX_READ_COUNT = 125 + +PG_TIMF_COLOR_RGB = (0 << 11) +PG_TIMF_COLOR_YUV = (1 << 11) +PG_TIMF_COLOR_YUV422 = (2 << 11) +PG_TIMF_COLOR_YUV420 = (3 << 11) +PG_TIMF_COLOR_Y = (4 << 11) +PG_TIMF_COLOR_RAW = (5 << 11) + +dict_color_formate_tx_rev = {PG_TIMF_COLOR_RGB: "RGB", PG_TIMF_COLOR_YUV: "YCbCr 4:4:4", + PG_TIMF_COLOR_YUV422: "YCbCr 4:2:2", PG_TIMF_COLOR_YUV420: "YCbCr 4:2:0", + PG_TIMF_COLOR_Y: "Y only", PG_TIMF_COLOR_RAW: "Raw"} + +PG_TIMF_COLORIMETRY_MASK = (0x01 << 16) +PG_TIMF_ITU_601 = (0 << 16) +PG_TIMF_ITU_709 = (1 << 16) + +DP2_LINK_BW_LOSS_M = 128 * 383 +DP2_LINK_BW_LOSS_N = 132 * 384 + +MAX_EDID_SIZE = 4096 + +dict_colorimetry_tx = {PG_TIMF_ITU_601: "ITU-601", PG_TIMF_ITU_709: "ITU-709"} + + +class DeviceOpenError(Exception): + + def __init__(self, message: str): + self.__message = message + super().__init__(self.__message) + + +def check_frame_size(h_active, v_active, input_color_mode, count, memory): + color_mode = 4 if input_color_mode in [2, 3, 4, "RGB", "YCbCr4:2:2", "YCbCr4:4:4"] else 3 + frame_size = h_active * v_active * color_mode + + max_device_memory = memory + try: + count_frames_available = int(max_device_memory // frame_size) + except BaseException: + count_frames_available = 0 + + if count > count_frames_available: + warnings.warn("Exceeded the maximum number of frames available to capture {}. Will be captured {} frames" + .format(count_frames_available, count_frames_available)) + count = count_frames_available + + return count diff --git a/UniTAP/dev/ports/modules/dpcd/__init__.py b/UniTAP/dev/ports/modules/dpcd/__init__.py new file mode 100644 index 0000000..d59b403 --- /dev/null +++ b/UniTAP/dev/ports/modules/dpcd/__init__.py @@ -0,0 +1 @@ +from .dpcd import DPCDRegisters, DPCDRegion diff --git a/UniTAP/dev/ports/modules/dpcd/dpcd.py b/UniTAP/dev/ports/modules/dpcd/dpcd.py new file mode 100644 index 0000000..03aac7b --- /dev/null +++ b/UniTAP/dev/ports/modules/dpcd/dpcd.py @@ -0,0 +1,187 @@ +import os +from typing import Union, Tuple + +from UniTAP.libs.lib_tsi.tsi import * +from UniTAP.libs.lib_tsi.tsi_io import PortIO + + +class DPCDRegion: + + """ + Class `DPCDRegion` describe a byte range of DPCD registers. + Allows saving DPCD data to dpd file format `save_to_dpd`, hex files format `save_to_hex`, + dsc file format `save_to_csv`. + + Arguments: + base - start DPCD address of byte range. + data - DPCD data of byte range. + """ + + def __init__(self, base: int, data: Union[bytearray, int]): + + if not isinstance(data, bytearray): + self.data = bytearray() + self.data.append(data) + else: + self.data = data + self.base = base + + @property + def size(self) -> int: + """ + + Returns length fo data. + + Returns: + result of int object + """ + return len(self.data) + + def save_to_dpd(self, path: str): + """ + + Save DPCD data to 'dpd' file format. + + Args: + path (str) - full path to file + + """ + if not path.lower().endswith('.dpd'): + path += '.DPD' + with open(path, 'bw') as file: + dpd_mark = 1 + file.write(dpd_mark.to_bytes(4, 'little')) + file.write(self.base.to_bytes(4, 'little')) + file.write(len(self.data).to_bytes(4, 'little')) + file.write(self.data) + + def save_to_hex(self, path: str): + """ + + Save DPCD data to 'hex' file format. + + Args: + path (str) - full path to file + + """ + if not path.lower().endswith('.hex'): + path += '.HEX' + with open(path, 'w') as file: + file.write(f"\nRange 1. Start {self.base:#0{8}x}, Length {len(self.data):#0{8}x}\n") + file.write( + '\n'.join( + ' '.join( + f'{value:#0{2}x}' for value in self.data[i:i + 16]) + for i in range(0, len(self.data), 16) + ) + ) + + def save_to_csv(self, path: str): + """ + + Save DPCD data to 'csv' file format. + + Args: + path (str) - full path to file + + """ + if not path.lower().endswith('.csv'): + path += '.csv' + with open(path, 'w') as file: + file.write("Address (A), Data A+0, Data A+1, Data A+2, Data A+3, Data A+4, Data A+5," + " Data A+6, Data A+7\n") + file.write( + '\n'.join( + f'{self.base + 8 * i:#0{8}x},' + ','.join(f'{value:#0{2}x}' + for value in self.data[i:i + 8]) + for i in range(0, len(self.data), 8) + ) + ) + + +class DPCDRegisters: + + """ + Class `DPCDRegisters` allows working with DPCD registers: writing `write` DPCD data to device, + reading `read` DPCD data from device, loading `load_from_file` DPCD data from file. + """ + + def __init__(self, port_io: PortIO, base_ci: int, data_ci: int): + self.__io = port_io + self.__base_ci = base_ci + self.__data_ci = data_ci + + def write(self, base: int, data: Union[bytearray, int, list]) -> int: + """ + + Write transferred DPCD data to device from base address. + + Args: + base (int) - start (base) address. + data (Union[bytearray, int]) - DPCD data. + + Returns: + result of operation + """ + if isinstance(data, int): + data = bytearray(data.to_bytes(1, "big")) + elif isinstance(data, list): + data = bytearray(data) + + result = self.__io.set(self.__base_ci, base) + if result < TSI_SUCCESS: + return result + result = self.__io.set(self.__data_ci, data, c_ubyte, len(data)) + return result + + def read(self, base: int, count: int) -> DPCDRegion: + """ + + Read DPCD data from base address in a certain quantity. + + Args: + base (int) - start (base) address. + count (int) - quantity of DPCD bytes. + + Returns: + object of `DPCDRegion` + """ + self.__io.set(self.__base_ci, base) + + result = self.__io.get(self.__data_ci, c_ubyte, count) + + return DPCDRegion(base, result[1]) + + @staticmethod + def load_from_file(path: str) -> Tuple[DPCDRegion, DPCDRegion]: + """ + + Read DPCD data from file. + Supported formats: + - DPD. + + Args: + path (str) - full path to file. + + Returns: + object of tuple with two `DPCDRegion` objects + """ + if not os.path.exists(path): + raise FileNotFoundError(f"DPCD load_from_file function can't find file: {path}") + + if '.DPD' not in os.path.splitext(path): + raise TypeError("Wrong file extension. It should be .DPD") + + with open(path, 'rb') as file: + file.read(4) + base1 = int.from_bytes(file.read(4)[::-1], 'little') + size1 = int.from_bytes(file.read(4)[::-1], 'little') + data1 = bytearray(file.read(size1)) + region1 = DPCDRegion(base1, data1) + + base2 = int.from_bytes(file.read(4)[::-1], 'little') + size2 = int.from_bytes(file.read(4)[::-1], 'little') + data2 = bytearray(file.read(size2)) + region2 = DPCDRegion(base2, data2) + + return region1, region2 diff --git a/UniTAP/dev/ports/modules/edid/__init__.py b/UniTAP/dev/ports/modules/edid/__init__.py new file mode 100644 index 0000000..c1b0dcb --- /dev/null +++ b/UniTAP/dev/ports/modules/edid/__init__.py @@ -0,0 +1,2 @@ +from .edid_parser import EdidParser, MainBlockType, AdditionalBlockType +from .edid import EdidFileType, DisplayIDReadMode diff --git a/UniTAP/dev/ports/modules/edid/edid.py b/UniTAP/dev/ports/modules/edid/edid.py new file mode 100644 index 0000000..7d86d8d --- /dev/null +++ b/UniTAP/dev/ports/modules/edid/edid.py @@ -0,0 +1,323 @@ +import warnings +from typing import List + +from UniTAP.common import Timing + +from UniTAP.libs.lib_tsi.tsi_types import TSI_EDID_SELECT_STREAM, TSI_EDID_TE_INPUT, TSI_EDID_TE_OUTPUT, \ + TSI_EDID_TE_OUTPUT_REMOTE_R, TSI_DID_TE_OUTPUT, TSI_DID_TE_INPUT, TSI_DID_SELECT_STREAM, \ + TSI_DID_TE_OUTPUT_REMOTE_R, TSI_DPRX_DISPLAYID_CTRL, TSI_DPTX_DISPLAYID_CTRL, TSI_EDID_DUT_TIMINGS_COUNT, \ + TSI_EDID_DUT_TIMINGS_DATA +from UniTAP.libs.lib_tsi.tsi_io import PortIO +from UniTAP.dev.ports.modules.device_constants import MAX_EDID_SIZE +from ctypes import c_ubyte, c_uint32 +from .edid_utils import save_file, load_file, EdidFileType +from .edid_parser import EdidParser +from .edid_types import DisplayIDReadMode +from .edid_private_types import ParsedTimingStruct + + +class Edid: + + """ + Main class for working with EDID. + Allows reading and saving EDID. This functionality is used by child classes `EdidSource` and `EdidSink`. + You cannot use a class `Edid` object directly. + """ + + def __init__(self, port_io: PortIO, control_ci: int, max_stream_count: int, select_stream_ci: int): + self._io = port_io + self._control_ci = control_ci + self.__select_stream_ci = select_stream_ci + self._max_stream_count = max_stream_count + self.__parser = EdidParser() + + def read_i2c(self) -> bytearray: + """ + + Allows reading from DUT `EdidSource` or TE `EdidSink` side EDID block(s) over connecting signal cable. + + Returns: + object of bytearray + """ + result, edid_data, size = self._io.get(self._control_ci, c_ubyte, MAX_EDID_SIZE) + if result > 0: + return bytearray(edid_data[:result]) + else: + return bytearray() + + def save_edid(self, path: str, file_type: EdidFileType, data: bytearray): + """ + + Save received EDID data into file. + Supported formats: + - BIN. + - HEX. + + Args: + path (str) - full path to file + file_type (`EdidFileType`) - one of the Supported formats. + data (bytearray) - EDID data for saving + """ + if len(data) > 0: + save_file(data, path, file_type) + else: + warnings.warn("EDID is empty.") + + def read_timings(self) -> List[Timing]: + """ + + """ + timings_count = self._io.get(TSI_EDID_DUT_TIMINGS_COUNT, c_uint32)[1] + + if timings_count == 0: + return [] + + timings = self._io.get(TSI_EDID_DUT_TIMINGS_DATA, ParsedTimingStruct, timings_count)[1] + parsed_timings = [] + + for i in range(timings_count): + timing = Timing() + timing.htotal = timings[i].h_total + timing.vtotal = timings[i].v_total + timing.hactive = timings[i].h_active + timing.vactive = timings[i].v_active + timing.hstart = timings[i].h_start + timing.vstart = timings[i].v_start + timing.hswidth = timings[i].h_sync + timing.vswidth = timings[i].v_sync + # timing.id = timings[i].id + + # timing.aspect_ratio = timings[i].aspect_ratio + timing.frame_rate = timings[i].frame_rate_millihz + # timing.standard = timings[i].standard + # timing.reduce_blanking = timings[i].reduce_blanking + parsed_timings.append(timing) + + return parsed_timings + + def _select_stream(self, stream): + """ + Set Virtual Sink's EDID index. + stream = 0 - UCD local EDID + stream > 0 - Virtual Sink's EDID + + Args: + stream (int) - Virtual Sink's EDID index. + """ + if not (0 <= stream < self._max_stream_count): + warnings.warn(f"Stream must be in range 0 - {self._max_stream_count}. Current value: {stream}") + return + self._io.set(self.__select_stream_ci, stream, c_uint32) + + @property + def _parser(self) -> EdidParser: + return self.__parser + + +class EdidSource(Edid): + + """ + Class `EdidSource` inherited from class `Edid`. + Allows read EDID from remote devices `read_sbm`, save received EDID data to file `save_edid` and + read EDID from DUT `read_i2c` + """ + + def __init__(self, port_io: PortIO, max_stream_count: int): + super().__init__(port_io, TSI_EDID_TE_OUTPUT, max_stream_count, TSI_EDID_SELECT_STREAM) + + def read_sbm(self, stream: int): + """ + + Allows reading remote EDID block(s) of remote device(s) attached to DUT over connecting signal cable. + stream = 0 - UCD local EDID + stream > 0 - Virtual Sink's EDID + + Args: + stream (int) - Virtual Sink index. + + """ + self._select_stream(stream) + result, edid_data, size = self._io.get(TSI_EDID_TE_OUTPUT_REMOTE_R, c_ubyte, MAX_EDID_SIZE) + if result > 0: + return bytearray(edid_data[:result]) + else: + return bytearray() + + +class EdidSink(Edid): + + """ + Class `EdidSink` inherited from class `Edid`. + Allows writing EDID to device `write_edid`, load EDID data from file `load_edid`, + save received EDID data to file `save_edid` and read EDID from TE `read_i2c`. + """ + + def __init__(self, port_io: PortIO, max_stream_count: int): + super().__init__(port_io, TSI_EDID_TE_INPUT, max_stream_count, TSI_EDID_SELECT_STREAM) + + def write_edid(self, data: bytearray, stream: int = 0): + """ + + Write transferred EDID to device. + + Args: + data (bytearray) - EDID data for writing + stream (int) - Virtual Sink's EDID index + """ + self._select_stream(stream) + self._io.set(self._control_ci, data, c_ubyte, len(data)) + + def load_edid(self, path: str, load_on_device: bool, stream: int = 0) -> bytearray: + """ + + Read EDID data from file. If needed to write data to device, select load_on_device = True. + Supported formats: + - BIN. + - HEX. + + Args: + path (str) - full path to file. + load_on_device (bool) - write loaded data to device or not. + stream (int) - Virtual Sink's EDID index + + Returns: + object of bytearray + """ + data = load_file(path) + if len(data) > 0: + if load_on_device: + self.write_edid(data, stream) + return data + else: + warnings.warn("File is empty.") + return bytearray() + + def read_sbm(self, stream: int): + """ + + Allows reading remote EDID block(s) of remote device(s) attached to DUT over connecting signal cable. + stream = 0 - UCD local EDID + stream > 0 - Virtual Sink's EDID + + Args: + stream (int) - Virtual Sink index. + + """ + self._select_stream(stream) + return self.read_i2c() + + +class DisplayIdSource(Edid): + + """ + Class `DisplayIdSource` inherited from class `Edid`. + Allows read DisplayId from remote devices `read_sbm`, save received DisplayId data to file `save_edid` and + read DisplayId from DUT `read_i2c` + """ + + def __init__(self, port_io: PortIO, max_stream_count: int): + super().__init__(port_io, TSI_DID_TE_OUTPUT, max_stream_count, TSI_DID_SELECT_STREAM) + + def read_sbm(self, stream: int): + """ + + Allows reading remote DisplayId block(s) of remote device(s) attached to DUT over connecting signal cable. + stream = 0 - UCD local EDID + stream > 0 - Virtual Sink's EDID + + Args: + stream (int) - Virtual Sink index. + + """ + self._select_stream(stream) + result, data, size = self._io.get(TSI_DID_TE_OUTPUT_REMOTE_R, c_ubyte, MAX_EDID_SIZE) + if result > 0: + return bytearray(data[:result]) + else: + return bytearray() + + def set_display_id_mode(self, mode: DisplayIDReadMode): + """ + Set DisplayID read mode + + Args: + mode (`DisplayIDReadMode`) + """ + self._io.set(TSI_DPTX_DISPLAYID_CTRL, mode.value, c_uint32) + + def get_display_id_mode(self) -> DisplayIDReadMode: + """ + Returns DisplayID read mode. + + Returns: + object of `DisplayIDReadMode` type. + """ + result = self._io.get(TSI_DPTX_DISPLAYID_CTRL, c_uint32)[1] + return DisplayIDReadMode(result) + + +class DisplayIdSink(Edid): + + """ + Class `DisplayIdSink` inherited from class `Edid`. + Allows writing DisplayId to device `write_display_id`, load DisplayId data from file `load_display_id`, + save received DisplayId data to file `save_edid` and read DisplayId from TE `read_i2c`. + """ + + def __init__(self, port_io: PortIO, max_stream_count: int): + super().__init__(port_io, TSI_DID_TE_INPUT, max_stream_count, TSI_DID_SELECT_STREAM) + + def is_enabled(self) -> bool: + """ + Returns status of DisplayId, is enabled or not. + + Returns: + object of `bool` type. + """ + result = self._io.get(TSI_DPRX_DISPLAYID_CTRL, c_uint32) + status_display_id = ((result[1] & 0x1) != 0) + return bool(status_display_id) + + def enable(self, enable: bool): + """ + Enable/Disable DisplayId. + + Args: + enable (bool) - enable (True) or disable (False) + """ + val = 0x1 if enable else 0x0 + self._io.set(TSI_DPRX_DISPLAYID_CTRL, val) + + def write_display_id(self, data: bytearray): + + """ + Write transferred EDID to device. + + Args: + data (bytearray) - EDID data for writing + """ + self._io.set(self._control_ci, data, c_ubyte, len(data)) + + def load_display_id(self, path: str, load_on_device: bool) -> bytearray: + + """ + Read EDID data from file. If needed to write data to device, select load_on_device = True. + Supported formats: + - BIN. + - HEX. + + Args: + path (str) - full path to file. + load_on_device (bool) - write loaded data to device or not. + + Returns: + object of bytearray + """ + data = load_file(path) + if len(data) > 0: + if load_on_device: + self.write_display_id(data) + return data + else: + warnings.warn("File is empty.") + return bytearray() \ No newline at end of file diff --git a/UniTAP/dev/ports/modules/edid/edid_parser.py b/UniTAP/dev/ports/modules/edid/edid_parser.py new file mode 100644 index 0000000..3156989 --- /dev/null +++ b/UniTAP/dev/ports/modules/edid/edid_parser.py @@ -0,0 +1,21 @@ +from .edid_types import * + + +class EdidParser: + + __BLOCK_SIZE = 128 + + def __init__(self): + pass + + def find_main_block(self, block_type: MainBlockType, data: bytearray) -> bytearray: + for i in range(0, len(data), self.__BLOCK_SIZE): + if data[i] == block_type.value: + return data[i: i + self.__BLOCK_SIZE] + return bytearray() + + def find_additional_block(self, block_type: AdditionalBlockType, data: bytearray) -> bytearray: + for i in range(0, len(data)): + if data[i] == block_type.value: + return data[i:] + return bytearray() diff --git a/UniTAP/dev/ports/modules/edid/edid_private_types.py b/UniTAP/dev/ports/modules/edid/edid_private_types.py new file mode 100644 index 0000000..03b5342 --- /dev/null +++ b/UniTAP/dev/ports/modules/edid/edid_private_types.py @@ -0,0 +1,54 @@ +from ctypes import Structure, c_uint32, c_bool + + +class ParsedTimingStruct(Structure): + _fields_ = [ + ("pixel_clock_khz",c_uint32), + ("h_active", c_uint32), + ("v_active", c_uint32), + ("h_total", c_uint32), + ("h_start", c_uint32), + ("h_sync", c_uint32), + ("v_total", c_uint32), + ("v_start", c_uint32), + ("v_sync", c_uint32), + ("is_hsync_neg", c_bool), + ("is_vsync_neg", c_bool), + ("interlaced", c_bool) + ] + + @property + def total_pixels(self) -> int: + return self.v_total * self.h_total + + @property + def frame_rate_hz(self): + return self.pixel_clock_khz * 1000 / self.total_pixels + + @property + def frame_rate_millihz(self): + return self.pixel_clock_khz * 1000000 / self.total_pixels + + @property + def interlaced_frame_rate_hz(self): + return (2 * self.frame_rate_hz) if self.interlaced else self.frame_rate_hz + + @property + def interlaced_frame_rate_millihz(self): + return (2 * self.frame_rate_millihz) if self.interlaced else self.frame_rate_millihz + + @property + def is_rb(self): + return not self.is_hsync_neg and self.is_vsync_neg and self.h_sync == 32 + + @property + def is_ovt(self): + return not self.is_hsync_neg and not self.is_vsync_neg and self.h_sync == 32 and self.v_sync == 8 + + @property + def line_duration_ms(self): + return self.h_total / self.pixel_clock_khz + + @property + def pixel_clock_hz(self): + return self.pixel_clock_khz * 1000 diff --git a/UniTAP/dev/ports/modules/edid/edid_types.py b/UniTAP/dev/ports/modules/edid/edid_types.py new file mode 100644 index 0000000..bc81283 --- /dev/null +++ b/UniTAP/dev/ports/modules/edid/edid_types.py @@ -0,0 +1,22 @@ +from enum import IntEnum + + +class MainBlockType(IntEnum): + VESA = 0x0 + CTA = 0x2 + DisplayID = 0x70 + + +class AdditionalBlockType(IntEnum): + AdaptiveSync = 0x2B + + +class DisplayIDReadMode(IntEnum): + """ + Disabled - Disable native DisplayID read + Try - Prefer native DisplayID (if failed to read use EDID instead) + Both - Read both DisplayID and EDID + """ + Disabled = 0 + Try = 1 + Both = 2 diff --git a/UniTAP/dev/ports/modules/edid/edid_utils.py b/UniTAP/dev/ports/modules/edid/edid_utils.py new file mode 100644 index 0000000..38a7cca --- /dev/null +++ b/UniTAP/dev/ports/modules/edid/edid_utils.py @@ -0,0 +1,44 @@ +import os +from enum import IntEnum + + +class EdidFileType(IntEnum): + BIN = 0 + HEX = 1 + + +def save_file(data: bytearray, path: str, file_type: EdidFileType): + if file_type == EdidFileType.BIN: + file_dpd = open(path + '.bin', 'bw+') + file_dpd.write(data) + file_dpd.close() + elif file_type == EdidFileType.HEX: + file = open(path + '.hex', 'w') + str_for_save = '' + for i in data: + if i < 16: + str_for_save += str(hex(i)).replace("0x", "0") + else: + str_for_save += str(hex(i)).replace("0x", "") + file.write(str_for_save) + file.close() + + +def load_file(path: str) -> bytearray: + filename, file_extension = os.path.splitext(path) + if file_extension.find('.hex') != -1: + file = open(path, 'r') + data = file.read() + file.close() + i = 0 + size = len(data) + byte_array = bytearray() + while i < size: + byte_array.append(int('0x' + data[i] + data[i + 1], 16)) + i += 2 + return byte_array + elif file_extension.find('.bin') != -1: + file = open(path, 'rb') + data = file.read() + file.close() + return bytearray(data) diff --git a/UniTAP/dev/ports/modules/fec/__init__.py b/UniTAP/dev/ports/modules/fec/__init__.py new file mode 100644 index 0000000..877be94 --- /dev/null +++ b/UniTAP/dev/ports/modules/fec/__init__.py @@ -0,0 +1,3 @@ +from .fec_tx import FecTx +from .fec_rx import FecRx +from .fec_shared import FECCounters, FECErrorType128b132b, FECErrorType8b10b diff --git a/UniTAP/dev/ports/modules/fec/fec_rx.py b/UniTAP/dev/ports/modules/fec/fec_rx.py new file mode 100644 index 0000000..28cb318 --- /dev/null +++ b/UniTAP/dev/ports/modules/fec/fec_rx.py @@ -0,0 +1,131 @@ +from UniTAP.libs.lib_tsi.tsi import * +from UniTAP.libs.lib_tsi.tsi_io import PortIO + +from .fec_shared import FECCounters +from UniTAP.dev.ports.modules.dpcd.dpcd import DPCDRegisters + + +class FecRx: + """ + Class `FecRx` allows working with FEC functionality from Sink (RX - receiver) side. You can: + - Check capable FEC or not `is_capable`. + - Check enabled FEC or not `is_enabled`. + - Enable/Disable FEC `enable`. + - Enable/Disable calculating sum of errors `aggregate_errors`. + - Get error counters `get_error_counters`. + - Clear all errors `clear`. + """ + def __init__(self, port_io: PortIO, dpcd: DPCDRegisters): + self.__io = port_io + self.__dpcd = dpcd + self.__aggregate_error = 0 + + def is_enabled(self) -> bool: + """ + Returns status of FEC, is enabled or not. + + Returns: + object of `bool` type. + """ + result = self.__io.get(TSI_DPRX_FEC_STATUS_R, c_int) + status_fec = ((result[1] & 0x1) != 0) + return bool(status_fec) + + def is_capable(self) -> bool: + """ + Returns status of FEC, is capable or not. + + Returns: + object of `bool` type. + """ + result = self.__io.get(TSI_DPRX_FEC_CTRL, c_int) + enabled_fec = (result[1] & 0x1) != 0 + return enabled_fec + + def enable(self, enable: bool): + """ + Enable/Disable FEC. + + Args: + enable (bool) - enable (True) or disable (False) + """ + val = 0x1 if enable else 0x0 + self.__io.set(TSI_DPRX_FEC_CTRL, val) + + def aggregate_errors(self, enable: bool): + """ + Enable/Disable calculating sum of errors. + + Args: + enable (bool) - enable (True) or disable (False) + """ + result = self.__io.get(TSI_DPRX_FEC_CONTROL, c_int) + val = result[1] + if enable: + val |= 0x2 + self.__aggregate_error = 1 + else: + val &= ~0x2 + self.__aggregate_error = 0 + self.__io.set(TSI_DPRX_FEC_CONTROL, val) + + def get_error_counters(self) -> FECCounters: + """ + + Get current error counters. + + Returns: + object of `FECCounters` type + """ + result = FECCounters() + lane_count = self.__io.get(TSI_R_DPRX_LINK_LANE_COUNT, c_int)[1] + if lane_count == 4: + lane_count += 1 if self.__aggregate_error else 0 + + dpcd = self.__dpcd.read(0x120, 1).data[0] + dpcdaggr = bool(dpcd & (1 << 6)) + + for i in range(lane_count): + if i == 0 and dpcdaggr: + dpcd &= ~(1 << 6) + + if i == 4 and dpcdaggr: + dpcd |= 1 << 6 + elif i == 4: + continue + + if i < 4: + dpcd &= ~(0x3 << 4) + dpcd |= (i << 4) + + for j in range(1, 6): + dpcd &= ~(0x7 << 1) + v = dpcd | (j << 1) + self.__dpcd.write(0x120, v) + val = int.from_bytes(self.__dpcd.read(0x281, 2).data, 'little') + + if j == 1: + result.uncorrectedBlockErrors[i] = val & 0x7FFF if val & 0x8000 else None + elif j == 2: + result.correctedBlockErrors[i] = val & 0x7FFF if val & 0x8000 else None + elif j == 3: + result.bitErrors[i] = val & 0x7FFF if val & 0x8000 else None + elif j == 4: + result.parityBlockErrors[i] = val & 0x7FFF if val & 0x8000 else None + elif j == 5: + result.parityBitErrors[i] = val & 0x7FFF if val & 0x8000 else None + + return result + + def clear(self): + """ + Clear all errors. + """ + address = 0x120 + dpcd = self.__dpcd.read(address, 1).data[0] + old_fec = dpcd + fec_ready = dpcd & ~0xE + dpcd = fec_ready + self.__dpcd.write(address, dpcd) + dpcd = old_fec + self.__dpcd.write(address, dpcd) diff --git a/UniTAP/dev/ports/modules/fec/fec_shared.py b/UniTAP/dev/ports/modules/fec/fec_shared.py new file mode 100644 index 0000000..449d45c --- /dev/null +++ b/UniTAP/dev/ports/modules/fec/fec_shared.py @@ -0,0 +1,38 @@ +from enum import IntEnum + + +class FECCounters: + """ + Class `FECCounters` possible errors: + - Uncorrected block errors. + - Corrected block errors. + - Bit errors. + - Parity block errors. + - Parity bit errors. + """ + def __init__(self): + self.uncorrectedBlockErrors = [None, None, None, None, None] + self.correctedBlockErrors = [None, None, None, None, None] + self.bitErrors = [None, None, None, None, None] + self.parityBlockErrors = [None, None, None, None, None] + self.parityBitErrors = [None, None, None, None, None] + + +class FECErrorType8b10b(IntEnum): + """ + Describes possible FEC 8b/10b errors. + """ + UNCORRECTED_BLOCK = 0 + CORRECTED_BLOCK = 1 + CORRECTED_PARITY = 2 + CORRECTED_BLOCK_1 = 3 + CORRECTED_PARITY_1 = 4 + + +class FECErrorType128b132b(IntEnum): + """ + Describes possible FEC 128b/132b errors. + """ + UNCORRECTED_BLOCK = 0 + CORRECTED_BLOCK_4 = 1 + CORRECTED_BLOCK_2 = 3 diff --git a/UniTAP/dev/ports/modules/fec/fec_tx.py b/UniTAP/dev/ports/modules/fec/fec_tx.py new file mode 100644 index 0000000..5ef27d4 --- /dev/null +++ b/UniTAP/dev/ports/modules/fec/fec_tx.py @@ -0,0 +1,204 @@ +from UniTAP.libs.lib_tsi.tsi import * +from UniTAP.libs.lib_tsi.tsi_io import PortIO + +from .fec_shared import FECCounters, FECErrorType8b10b, FECErrorType128b132b +from UniTAP.dev.ports.modules.dpcd.dpcd import DPCDRegisters +from typing import Union + + +class FecTx: + """ + Class `FecTx` allows working with FEC functionality from Source (TX - transmitter) side. You can: + - Check enabled FEC or not `is_enabled`. + - Check state that FEC is prefers after link training `is_prefer_after_lt`. + - Enable/Disable FEC `enable`. + - Enable/Disable intent FEC `enable_intent`. + - Enable/Disable calculating sum of errors `aggregate_errors`. + - Get error counters `get_error_counters`. + - Clear all errors `clear`. + """ + def __init__(self, port_io: PortIO, dpcd: DPCDRegisters): + self.__io = port_io + self.__dpcd = dpcd + self.__aggregate_error = 0 + + def is_enabled(self) -> bool: + """ + Returns status of FEC, is enabled or not. + + Returns: + object of `bool` type. + """ + result = self.__io.get(TSI_DPTX_FEC_STATUS_R, c_int) + status_fec = ((result[1] & 0x1) != 0) + return bool(status_fec) + + def is_prefer_after_lt(self) -> bool: + """ + Check state that FEC is prefers after link training. + + Returns: + object of `bool` type. + """ + result = self.__io.get(TSI_DPTX_FEC_CTRL, c_int) + enabled_fec = (result[1] & 0x2) != 0 + return enabled_fec + + def enable(self, enable: bool): + """ + Enable/Disable FEC. + + Args: + enable (bool) - enable (True) or disable (False) + """ + result = self.__io.get(TSI_DPTX_FEC_CTRL, c_int) + val = result[1] + val |= 0x8 if enable else 0x10 + self.__io.set(TSI_DPTX_FEC_CTRL, val) + + def enable_intent(self, enable: bool): + """ + Enable/Disable intent FEC. + + Args: + enable (bool) - enable (True) or disable (False) + """ + result = self.__io.get(TSI_DPTX_FEC_CTRL, c_int) + val = result[1] + val |= 1 if enable else 4 + self.__io.set(TSI_DPTX_FEC_CTRL, val) + + def aggregate_errors(self, enable: bool): + """ + Enable/Disable calculating sum of errors. + + Args: + enable (bool) - enable (True) or disable (False) + """ + result = self.__io.get(TSI_DPTX_FEC_CONTROL, c_int) + val = result[1] + if enable: + val |= 0x40 + self.__aggregate_error = 1 + else: + val &= ~0x40 + self.__aggregate_error = 0 + self.__io.set(TSI_DPTX_FEC_CONTROL, val) + + def generate_errors(self, error_type: Union[FECErrorType8b10b, FECErrorType128b132b], lane: list, ms: int = 100): + """ + Generate FEC errors. + + Args: + error_type (Union[`FECErrorType8b10b`, `FECErrorType128b132b`]) + lane (list) + ms (int) - time in m seconds + """ + result, status = self.__io.get(TSI_DPTX_LINK_MODE_R, c_int) + resut, hw_caps, size = self.__io.get(TSI_DPTX_HW_CAPS_R, c_uint32, 4) + if status == 0: + if isinstance(error_type, FECErrorType128b132b): + assert False, "This device doesn't support 128b/132b" if (hw_caps[1] & 0x7) == 0 else "Change link mode!" + if status == 1: + if isinstance(error_type, FECErrorType8b10b): + assert False, "This device doesn't support 8b/10b" if (hw_caps[1] & 0x7) == 0 else "Change link mode!" + + dpcd = self.__dpcd.read(0x120, 1).data[0] + dpcd |= 2 + self.__dpcd.write(0x120, dpcd) + + delay = ms * 100 + + self.__io.set(TSI_MLEG_CONTROL, 0) + nl = self.__io.get(TSI_R_DPTX_LINK_STATUS_LANE_COUNT, c_uint32)[1] + + self.__io.set(TSI_MLEG_SYMBOL_REPLACE_A, 0x00010000) + self.__io.set(TSI_MLEG_SYMBOL_REPLACE_MASK_A, 0x00010001) + self.__io.set(TSI_MLEG_SYMBOL_REPLACE_B, 0x00000001) + self.__io.set(TSI_MLEG_SYMBOL_REPLACE_MASK_B, 0x00010001) + + self.__io.set(TSI_MLEG_DELAY_COUNTER, delay) + + n = lane[0] << 16 + self.__io.set(TSI_MLEG_LANE0_REPLACE_COUNTERS, n) + n = lane[1] << 16 + self.__io.set(TSI_MLEG_LANE1_REPLACE_COUNTERS, n) + n = lane[2] << 16 + self.__io.set(TSI_MLEG_LANE2_REPLACE_COUNTERS, n) + n = lane[3] << 16 + self.__io.set(TSI_MLEG_LANE3_REPLACE_COUNTERS, n) + + ctrl = 0 + ctrl |= (error_type.value << 16) + if nl == 1: + ctrl |= (1 << 13) + ctrl |= (1 << 12) + ctrl |= (0xF << 4) + + if lane[0]: + ctrl |= (1 << 0) + if lane[1]: + ctrl |= (1 << 1) + if lane[2]: + ctrl |= (1 << 2) + if lane[3]: + ctrl |= (1 << 3) + + self.__io.set(TSI_MLEG_CONTROL, ctrl) + + def get_error_counters(self) -> FECCounters: + """ + Get current error counters. + + Returns: + object of `FECCounters` type + """ + result = FECCounters() + lane_count = self.__io.get(TSI_R_DPTX_LINK_STATUS_LANE_COUNT, c_uint32)[1] + if lane_count == 4: + lane_count += 1 if self.__aggregate_error else 0 + + dpcd = self.__dpcd.read(0x120, 1).data[0] + dpcdaggr = bool(dpcd & (1 << 6)) + + for i in range(lane_count): + if i == 0 and dpcdaggr: + dpcd &= ~(1 << 6) + + if i == 4 and dpcdaggr: + dpcd |= 1 << 6 + elif i == 4: + continue + + if i < 4: + dpcd &= ~(0x3 << 4) + dpcd |= (i << 4) + + for j in range(1, 6): + dpcd &= ~(0x7 << 1) + v = dpcd | (j << 1) + self.__dpcd.write(0x120, v) + val = int.from_bytes(self.__dpcd.read(0x281, 2).data, 'little') + + if j == 1: + result.uncorrectedBlockErrors[i] = val & 0x7FFF if val & 0x8000 else None + elif j == 2: + result.correctedBlockErrors[i] = val & 0x7FFF if val & 0x8000 else None + elif j == 3: + result.bitErrors[i] = val & 0x7FFF if val & 0x8000 else None + elif j == 4: + result.parityBlockErrors[i] = val & 0x7FFF if val & 0x8000 else None + elif j == 5: + result.parityBitErrors[i] = val & 0x7FFF if val & 0x8000 else None + + return result + + def clear(self): + """ + Clear all errors. + """ + address = 0x120 + dpcd = self.__dpcd.read(address, 1).data[0] + fec_ready = dpcd & ~0xE + dpcd = fec_ready + self.__dpcd.write(address, dpcd) diff --git a/UniTAP/dev/ports/modules/hdcp/__init__.py b/UniTAP/dev/ports/modules/hdcp/__init__.py new file mode 100644 index 0000000..bb93044 --- /dev/null +++ b/UniTAP/dev/ports/modules/hdcp/__init__.py @@ -0,0 +1,4 @@ +from .hdcp_tx import HdcpSource, HdcpSourceStatus, HdcpSourceConfig +from .hdcp_rx import HdcpSink, HdcpSinkStatus, HdcpSinkConfig +from .types import HdcpMode, HdcpRxConfig, HdcpTxConfig, HdcpStatus, HdcpSink1XKeys, HdcpSink2XKeys, HdcpSource1XKeys, \ + HdcpSource2XKeys diff --git a/UniTAP/dev/ports/modules/hdcp/hdcp_rx.py b/UniTAP/dev/ports/modules/hdcp/hdcp_rx.py new file mode 100644 index 0000000..dabea42 --- /dev/null +++ b/UniTAP/dev/ports/modules/hdcp/hdcp_rx.py @@ -0,0 +1,246 @@ +import warnings +from typing import Optional, Union + +from UniTAP.libs.lib_tsi.tsi_io import PortIO, PortProtocol +from .types import * +from UniTAP.libs.lib_tsi.tsi import c_int + + +class HdcpSinkStatus: + + """ + + Class `HdcpSinkStatus` contains information about HDCP 1.4 and 2.3 statuses. + If you want to get object of one the status, use function `get`. + + """ + + def __init__(self, port_io: PortIO, ci_status_control: int, caps_1x: HdcpHwSinkCaps, caps_2x: HdcpHwSinkCaps): + super().__init__() + self.__io = port_io + self.__ci_status_control = ci_status_control + self.__caps_1x = caps_1x + self.__caps_2x = caps_2x + + def get(self, hdcp_mode: Type[HdcpStatusType]) -> HdcpStatusType: + """ + + Returns one of possible HDCP Status Type: + - Status1x (HDCP 1.4). + - StatusRx2x (HDCP 2.3). + + Object contains info about: + - HDCP keys (HdcpSink1XKeys if HDCP type 1.4; HdcpSink2XKeys if HDCP type 2.3). + - Active state (True or False). + - Authenticated state (True or False). + - Capable state (True or False). + + Returns: + object of `HdcpStatusType` (`Status1x` or `StatusRx2x`) + """ + if hdcp_mode not in [HdcpStatus.Status1x, HdcpStatus.StatusRx2x]: + raise TypeError(f"Wrong type HDCP status, provided type {hdcp_mode}") + + if hdcp_mode == HdcpStatus.Status1x: + if self.__caps_1x.hw_supported: + status_value = self.__read_status(HdcpMode.Mode1_4) + status = HdcpStatus.Status1x() + status.active = status_value & 1 + status.keys = HdcpSink1XKeys((status_value >> 1) & 3) + status.capable = ((status_value >> 3) & 1) + status.authenticated = ((status_value >> 4) & 1) + return status + else: + warnings.warn(f"Not supported 'HDCP 1.4'") + elif hdcp_mode == HdcpStatus.StatusRx2x: + if self.__caps_2x.hw_supported: + status_value = self.__read_status(HdcpMode.Mode2_3) + status = HdcpStatus.StatusRx2x() + if self.__io.protocol() == PortProtocol.HDMI: + status.keys = HdcpSink2XKeys.Production if (status_value >> 9) & 1 else HdcpSink2XKeys.Unload + else: + if (status_value >> 11) & 1: + status.keys = HdcpSink2XKeys.TestR1 + elif (status_value >> 13) & 1: + status.keys = HdcpSink2XKeys.TestR2 + elif (status_value >> 9) & 1: + status.keys = HdcpSink2XKeys.Production + else: + status.keys = HdcpSink2XKeys.Unload + + status.active = (status_value >> 8) & 1 + status.capable = (status_value >> 10) & 1 + status.authenticated = (status_value >> 12) & 1 + return status + else: + warnings.warn(f"Not supported 'HDCP 2.3'") + + def __read_status(self, mode: HdcpMode) -> int: + if mode == HdcpMode.Mode1_4: + return self.__io.get(TSI_HDCP_1X_STATUS_R, c_int)[1] + else: + return self.__io.get(self.__ci_status_control, c_int)[1] + + +class HdcpSinkConfig: + + """ + Class `HdcpSinkConfig` contains information about HDCP 1.4 and 2.3 configurations. + If you want to set configuration, use function `set`. + If you want to get current config, use function `get`. + """ + + def __init__(self, port_io: PortIO, caps_1x: HdcpHwSinkCaps, caps_2x: HdcpHwSinkCaps, status: HdcpSinkStatus): + self.__io = port_io + self.__caps_1x = caps_1x + self.__caps_2x = caps_2x + self.__status = status + + def set(self, config: HdcpRxConfigType): + """ + + This function is used to set the HDCP on Sink (RX - receiver) side. + Possible to load HDCP keys and enable/disable HDCP. + + Args: + config (HdcpRxConfigType) - one of the available HDCP config type: Config1x (HDCP 1.4), Config2x (HDCP 2.3) + """ + if isinstance(config, HdcpRxConfig.Config1x): + if self.__caps_1x.hw_supported: + self.__load_keys(config.keys, HdcpMode.Mode1_4) + self.__set_capable(config.capable, HdcpMode.Mode1_4) + else: + warnings.warn(f"Not supported 'HDCP 1.4'") + elif isinstance(config, HdcpRxConfig.Config2x): + if self.__caps_2x.hw_supported: + self.__load_keys(config.keys, HdcpMode.Mode2_3) + self.__set_capable(config.capable, HdcpMode.Mode2_3) + else: + warnings.warn(f"Not supported 'HDCP 2.3'") + + def get(self, hdcp_mode: Type[HdcpRxConfigType]) -> HdcpRxConfigType: + """ + + This function is used to get current HDCP configuration on Sink (RX - receiver) side. + + Returns: + object of HdcpRxConfigType - one of the available HDCP config type: Config1x (HDCP 1.4), Config2x (HDCP 2.3) + """ + if hdcp_mode == HdcpRxConfig.Config1x: + if self.__caps_1x.hw_supported: + config = HdcpRxConfig.Config1x() + status = self.__status.get(HdcpStatus.Status1x) + config.keys = status.keys + config.capable = status.capable + return config + else: + warnings.warn(f"Not supported 'HDCP 1.4'") + elif hdcp_mode == HdcpRxConfig.Config2x: + if self.__caps_2x.hw_supported: + config = HdcpRxConfig.Config2x() + status = self.__status.get(HdcpStatus.StatusRx2x) + config.keys = status.keys + config.capable = status.capable + return config + else: + warnings.warn(f"Not supported 'HDCP 2.3'") + + def __load_keys(self, keys: Optional[Union[HdcpSink1XKeys, HdcpSink2XKeys]], hdcp_mode: HdcpMode): + if keys is None: + return + if not self.__full_check(hdcp_mode): + return + + if hdcp_mode == HdcpMode.Mode1_4 and not isinstance(keys, HdcpSink1XKeys): + raise TypeError("Was selected HDCP 1.4 mode. Need to use 'HdcpSink1XKeys' type.") + elif hdcp_mode == HdcpMode.Mode1_4 and isinstance(keys, HdcpSink1XKeys): + if keys == HdcpSink1XKeys.Production and not self.__caps_1x.production_keys_available: + warnings.warn("Production keys is not hw supported. Please, select another keys.") + elif keys == HdcpSink1XKeys.Test and not self.__caps_1x.test_keys_available: + warnings.warn("Test keys is not hw supported. Please, select another keys.") + elif hdcp_mode == HdcpMode.Mode2_3 and not isinstance(keys, HdcpSink2XKeys): + raise TypeError("Was selected HDCP 2.3 mode. Need to use 'HdcpSink2XKeys' type.") + elif hdcp_mode == HdcpMode.Mode2_3 and isinstance(keys, HdcpSink2XKeys): + if keys == HdcpSink2XKeys.Production and not self.__caps_2x.production_keys_available: + warnings.warn("Production keys is not hw supported. Please, select another keys.") + + self.__io.set(TSI_HDCP_1X_COMMAND_W if hdcp_mode == HdcpMode.Mode1_4 else TSI_HDCP_2X_COMMAND_W, keys.value) + + def __set_capable(self, value: Optional[bool], hdcp_mode: HdcpMode): + if value is None: + return + if not self.__full_check(hdcp_mode): + return + + self.__io.set(TSI_HDCP_1X_COMMAND_W if hdcp_mode == HdcpMode.Mode1_4 else TSI_HDCP_2X_COMMAND_W, + (H1_SINK_SET_CAPABLE if value else H1_SINK_CLEAR_CAPABLE) + if hdcp_mode == HdcpMode.Mode1_4 else (H2_SINK_SET_CAPABLE if value else H2_SINK_CLEAR_CAPABLE)) + + def __full_check(self, hdcp_mode: HdcpMode) -> bool: + if hdcp_mode == HdcpMode.Unknown: + warnings.warn(f"Did not select HDCP mode. Please, select it from available variants: " + f"{'HDCP 1.4 ' if self.__caps_1x.hw_supported else ''}" + f"{'HDCP 2.3 ' if self.__caps_2x.hw_supported else ''}") + return False + + if not self.__availability_mode_check(hdcp_mode): + warnings.warn(f"Selected HDCP mode is not HW supported. Please, select it from available variants: " + f"{'HDCP 1.4 ' if self.__caps_1x.hw_supported else ''}" + f"{'HDCP 2.3 ' if self.__caps_2x.hw_supported else ''}") + return False + + return True + + def __availability_mode_check(self, hdcp_mode: HdcpMode) -> bool: + if hdcp_mode == HdcpMode.Mode1_4 and self.__caps_1x.hw_supported: + return True + elif hdcp_mode == HdcpMode.Mode2_3 and self.__caps_2x.hw_supported: + return True + else: + return False + + +class HdcpSink: + + """ + Main class contains info of HDCP on Sink (RX - receiver) side. + If you need to configurate HDCP, use `config` for getting object responsible for the configuration. + If you need to read HDCP status, use `status` for getting object responsible for the reading current status. + """ + + def __init__(self, port_io: PortIO, ci_caps_control: int, ci_status_control: int): + self.__io = port_io + self.__hw_caps_1x = HdcpHwSinkCaps(self.__read_hw_caps(HdcpMode.Mode1_4), HdcpMode.Mode1_4) + self.__hw_caps_2x = HdcpHwSinkCaps(self.__read_hw_caps(HdcpMode.Mode2_3, ci_caps_control), HdcpMode.Mode2_3) + self.__status = HdcpSinkStatus(port_io, ci_status_control, self.__hw_caps_1x, self.__hw_caps_2x) + self.__config = HdcpSinkConfig(port_io, self.__hw_caps_1x, self.__hw_caps_2x, self.__status) + + @property + def config(self) -> HdcpSinkConfig: + """ + + Should be used to configure HDCP on Sink (RX - receiver) role. + + Returns: + object of `HdcpSinkConfig`. + """ + return self.__config + + @property + def status(self) -> HdcpSinkStatus: + """ + + Should be used to read HDCP current status on Sink (RX - receiver) role. + + Returns: + object of `HdcpSinkStatus`. + """ + return self.__status + + def __read_hw_caps(self, mode: HdcpMode, ci_caps_control: int = 0) -> int: + if mode == HdcpMode.Mode1_4: + return self.__io.get(TSI_HDCP_1X_STATUS_R, c_int)[1] + elif mode == HdcpMode.Mode2_3: + return self.__io.get(ci_caps_control, c_int)[1] + else: + return 0 diff --git a/UniTAP/dev/ports/modules/hdcp/hdcp_tx.py b/UniTAP/dev/ports/modules/hdcp/hdcp_tx.py new file mode 100644 index 0000000..f252c0e --- /dev/null +++ b/UniTAP/dev/ports/modules/hdcp/hdcp_tx.py @@ -0,0 +1,288 @@ +import warnings +from typing import Optional, Union + +from UniTAP.libs.lib_tsi.tsi_io import PortIO, PortProtocol +from .types import * +from UniTAP.libs.lib_tsi.tsi_private_types import * +from ctypes import c_int + + +class HdcpSourceStatus: + + """ + + Class `HdcpSourceStatus` contains information about HDCP 1.4 and 2.3 statuses. + If you want to get object of one the status, use function `get`. + + """ + + def __init__(self, port_io: PortIO, ci_status_control: int, caps_1x: HdcpHwSourceCaps, caps_2x: HdcpHwSourceCaps): + super().__init__() + self.__io = port_io + self.__ci_status_control = ci_status_control + self.__caps_1x = caps_1x + self.__caps_2x = caps_2x + + def get(self, hdcp_mode: Type[HdcpStatusType]) -> HdcpStatusType: + """ + + Returns one of possible HDCP Status Type: + - Status1x (HDCP 1.4). + - StatusTx2x (HDCP 2.3). + + Object contains info about: + - HDCP keys (HdcpSource1XKeys if HDCP type 1.4; HdcpSource2XKeys if HDCP type 2.3). + - Active state (True or False). + - Authenticated state (True or False). + - Capable state (True or False). + + If HDCP type is 2.3, then it contains more information: + - KM stored state (True or False). + - Try to authenticate state (True or False). + - Try to encrypt state (True or False). + - Content level. + + Returns: + object of `HdcpStatusType` (`Status1x` or `StatusTx2x`) + """ + if hdcp_mode not in [HdcpStatus.Status1x, HdcpStatus.StatusTx2x]: + raise TypeError(f"Wrong type HDCP status, provided type {hdcp_mode}") + + if hdcp_mode == HdcpStatus.Status1x: + if self.__caps_1x.hw_supported: + status_value = self.__read_status(HdcpMode.Mode1_4) + status = HdcpStatus.Status1x() + status.active = (status_value >> 8) & 1 + status.keys = HdcpSource1XKeys(((status_value >> 9) & 3) + 0x100) + status.capable = ((status_value >> 11) & 1) + status.authenticated = ((status_value >> 12) & 1) + return status + else: + warnings.warn(f"Not supported 'HDCP 1.4'") + elif hdcp_mode == HdcpStatus.StatusTx2x: + if self.__caps_2x.hw_supported: + status_value = self.__read_status(HdcpMode.Mode2_3) + status = HdcpStatus.StatusTx2x() + if self.__io.protocol() == PortProtocol.HDMI: + status.keys = HdcpSource2XKeys.Production if (status_value >> 9) & 1 else HdcpSource2XKeys.Unload + else: + if (status_value >> 14) & 1: + status.keys = HdcpSource2XKeys.TestR1 + elif (status_value >> 15) & 1: + status.keys = HdcpSource2XKeys.TestR2 + elif (status_value >> 9) & 1: + status.keys = HdcpSource2XKeys.Production + else: + status.keys = HdcpSource2XKeys.Unload + + status.km_is_stored = (status_value >> 13) & 1 + status.try_authenticate = (status_value >> 18) & 1 + status.try_encrypt = (status_value >> 19) & 1 + status.content_level = self.__io.get(TSI_HDCP_2X_CFG, c_uint32)[1] + + status.active = (status_value >> 8) & 1 + status.capable = (status_value >> 10) & 1 + status.authenticated = (status_value >> 12) & 1 + + return status + else: + warnings.warn(f"Not supported 'HDCP 2.3'") + + def __read_status(self, mode: HdcpMode): + if mode == HdcpMode.Mode1_4: + return self.__io.get(TSI_HDCP_1X_STATUS_R, c_int)[1] + elif mode == HdcpMode.Mode2_3: + return self.__io.get(self.__ci_status_control, c_int)[1] + else: + return 0 + + +class HdcpSourceConfig: + + """ + Class `HdcpSourceConfig` contains information about HDCP 1.4 and 2.3 configurations. + If you want to set configuration, use function `set`. + If you want to get current config, use function `get`. + """ + + def __init__(self, port_io: PortIO, caps_1x: HdcpHwSourceCaps, caps_2x: HdcpHwSourceCaps, status: HdcpSourceStatus): + self.__io = port_io + self.__caps_1x = caps_1x + self.__caps_2x = caps_2x + self.__status = status + + def set(self, config: HdcpTxConfigType): + """ + + This function is used to set the HDCP on Source (TX - transmitter) side. + Possible to load HDCP keys and enable/disable HDCP. + + Args: + config (HdcpTxConfigType) - one of the available HDCP config type: Config1x (HDCP 1.4), Config2x (HDCP 2.3) + """ + if isinstance(config, HdcpTxConfig.Config1x): + if self.__caps_1x.hw_supported: + self.__load_keys(config.keys, HdcpMode.Mode1_4) + self.__set_authenticate(config.authenticate, HdcpMode.Mode1_4) + self.__set_encryption(config.encryption, HdcpMode.Mode1_4) + else: + warnings.warn(f"Not supported 'HDCP 1.4'") + elif isinstance(config, HdcpTxConfig.Config2x): + if self.__caps_2x.hw_supported: + self.__load_keys(config.keys, HdcpMode.Mode2_3) + self.__set_content_level(config.content_level) + self.__set_authenticate(config.authenticate, HdcpMode.Mode2_3) + self.__set_store_km(config.store_km) + self.__set_encryption(config.encryption, HdcpMode.Mode2_3) + else: + warnings.warn(f"Not supported 'HDCP 2.3'") + + def get(self, hdcp_mode: Type[HdcpTxConfigType]) -> HdcpTxConfigType: + """ + + This function is used to get current HDCP configuration on Source (TX - transmitter) side. + + Returns: + object of HdcpTxConfigType - one of the available HDCP config type: Config1x (HDCP 1.4), Config2x (HDCP 2.3) + """ + if hdcp_mode == HdcpTxConfig.Config1x: + if self.__caps_1x.hw_supported: + config = HdcpTxConfig.Config1x() + status = self.__status.get(HdcpStatus.Status1x) + config.keys = status.keys + config.encryption = status.active + config.authenticate = status.authenticated + return config + else: + warnings.warn(f"Not supported 'HDCP 1.4'") + elif hdcp_mode == HdcpTxConfig.Config2x: + if self.__caps_2x.hw_supported: + config = HdcpTxConfig.Config2x() + status = self.__status.get(HdcpStatus.StatusTx2x) + config.keys = status.keys + config.encryption = status.try_encrypt + config.authenticate = status.try_authenticate + config.store_km = status.km_is_stored + config.content_level = status.content_level + return config + else: + warnings.warn(f"Not supported 'HDCP 2.3'") + + def __set_encryption(self, value: Optional[bool], hdcp_mode: HdcpMode): + if value is None: + return + if hdcp_mode == HdcpMode.Mode1_4: + self.__io.set(TSI_HDCP_1X_COMMAND_W, H1_SOURCE_ENABLE_ENCRYPT if value else H1_SOURCE_DISABLE_ENCRYPT) + elif hdcp_mode == HdcpMode.Mode2_3: + self.__io.set(TSI_HDCP_2X_COMMAND_W, H2_SOURCE_ENABLE_ENCRYPT if value else H2_SOURCE_DISABLE_ENCRYPT) + + def __set_authenticate(self, value: Optional[bool], hdcp_mode: HdcpMode): + if value is None: + return + if hdcp_mode == HdcpMode.Mode1_4: + self.__io.set(TSI_HDCP_1X_COMMAND_W, H1_SOURCE_AUTHENTICATE if value else H1_SOURCE_DE_AUTHENTICATE) + elif hdcp_mode == HdcpMode.Mode2_3: + self.__io.set(TSI_HDCP_2X_COMMAND_W, H2_SOURCE_AUTHENTICATE if value else H2_SOURCE_DE_AUTHENTICATE) + + def __load_keys(self, keys: Optional[Union[HdcpSource1XKeys, HdcpSource2XKeys]], hdcp_mode: HdcpMode): + if keys is None: + return + if not self.__full_check(hdcp_mode): + return + + if hdcp_mode == HdcpMode.Mode1_4 and not isinstance(keys, HdcpSource1XKeys): + raise TypeError("Was selected HDCP 1.4 mode. Need to use 'HdcpSource1XKeys' type.") + elif hdcp_mode == HdcpMode.Mode1_4 and isinstance(keys, HdcpSource1XKeys): + if keys == HdcpSource1XKeys.Production and not self.__caps_1x.production_keys_available: + warnings.warn("Production keys is not hw supported. Please, select another keys.") + elif keys == HdcpSource1XKeys.Test and not self.__caps_1x.test_keys_available: + warnings.warn("Test keys is not hw supported. Please, select another keys.") + elif hdcp_mode == HdcpMode.Mode2_3 and not isinstance(keys, HdcpSource2XKeys): + raise TypeError("Was selected HDCP 2.3 mode. Need to use 'HdcpSource2XKeys' type.") + elif hdcp_mode == HdcpMode.Mode2_3 and isinstance(keys, HdcpSource2XKeys): + if keys == HdcpSource2XKeys.Production and not self.__caps_2x.production_keys_available: + warnings.warn("Production keys is not hw supported. Please, select another keys.") + + self.__io.set(TSI_HDCP_1X_COMMAND_W if hdcp_mode == HdcpMode.Mode1_4 else TSI_HDCP_2X_COMMAND_W, keys.value) + + def __full_check(self, hdcp_mode: HdcpMode) -> bool: + if hdcp_mode == HdcpMode.Unknown: + warnings.warn(f"Did not select HDCP mode. Please, select it from available variants: " + f"{'HDCP 1.4 ' if self.__caps_1x.hw_supported else ''}" + f"{'HDCP 2.3 ' if self.__caps_2x.hw_supported else ''}") + return False + + if not self.__availability_mode_check(hdcp_mode): + warnings.warn(f"Selected HDCP mode is not HW supported. Please, select it from available variants: " + f"{'HDCP 1.4 ' if self.__caps_1x.hw_supported else ''}" + f"{'HDCP 2.3 ' if self.__caps_2x.hw_supported else ''}") + return False + + return True + + def __availability_mode_check(self, hdcp_mode: HdcpMode) -> bool: + if hdcp_mode == HdcpMode.Mode1_4 and self.__caps_1x.hw_supported: + return True + elif hdcp_mode == HdcpMode.Mode2_3 and self.__caps_2x.hw_supported: + return True + else: + return False + + def __set_store_km(self, value: Optional[bool]): + if value is None: + return + self.__io.set(TSI_HDCP_2X_COMMAND_W, H2_SOURCE_AUTHENTICATE_STORE_KM if value else H2_SOURCE_CLEAR_STORE_KM) + + def __set_content_level(self, value: Optional[int]): + if value is None: + return + if value not in [0, 1]: + raise ValueError(f"Incorrect input value of 'Content level' - {value}. Must be from range: 0-1") + self.__io.set(TSI_HDCP_2X_CFG, value) + + +class HdcpSource: + + """ + Main class contains info of HDCP on Source (TX - transmitter) side. + If you need to configurate HDCP, use `config` for getting object responsible for the configuration. + If you need to read HDCP status, use `status` for getting object responsible for the reading current status. + """ + + def __init__(self, port_io: PortIO, ci_caps_control: int, ci_status_control: int): + self.__io = port_io + self.__hw_caps_1x = HdcpHwSourceCaps(self.__read_hw_caps(HdcpMode.Mode1_4), HdcpMode.Mode1_4) + self.__hw_caps_2x = HdcpHwSourceCaps(self.__read_hw_caps(HdcpMode.Mode2_3, ci_caps_control), HdcpMode.Mode2_3) + self.__status = HdcpSourceStatus(port_io, ci_status_control, self.__hw_caps_1x, self.__hw_caps_2x) + self.__config = HdcpSourceConfig(port_io, self.__hw_caps_1x, self.__hw_caps_2x, self.__status) + + @property + def config(self) -> HdcpSourceConfig: + """ + + Should be used to configure HDCP on Source (TX - transmitter) role. + + Returns: + object of `HdcpSourceConfig`. + """ + return self.__config + + @property + def status(self) -> HdcpSourceStatus: + """ + + Should be used to read HDCP current status onSource (TX - transmitter) role. + + Returns: + object of `HdcpSourceStatus`. + """ + return self.__status + + def __read_hw_caps(self, mode: HdcpMode, ci_caps_control: int = 0) -> int: + if mode == HdcpMode.Mode1_4: + return self.__io.get(TSI_HDCP_1X_STATUS_R, c_int)[1] + elif mode == HdcpMode.Mode2_3: + return self.__io.get(ci_caps_control, c_int)[1] + else: + return 0 + diff --git a/UniTAP/dev/ports/modules/hdcp/types.py b/UniTAP/dev/ports/modules/hdcp/types.py new file mode 100644 index 0000000..ea9d643 --- /dev/null +++ b/UniTAP/dev/ports/modules/hdcp/types.py @@ -0,0 +1,221 @@ +from typing import TypeVar + +from UniTAP.libs.lib_tsi.tsi_types import * +from enum import IntEnum +from typing import Type + + +class HdcpSink1XKeys(IntEnum): + Unknown = -1 + Unload = H1_SINK_UNLOAD_KEYS + Test = H1_SINK_LOAD_TEST_KEYS + Production = H1_SINK_LOAD_PROD_KEYS + + +class HdcpSink2XKeys(IntEnum): + Unknown = -1 + Unload = H2_SINK_UNLOAD_KEYS + Production = H2_SINK_LOAD_PROD_KEYS + TestR1 = H2_SINK_LOAD_TEST_KEYS_R1 + TestR2 = H2_SINK_LOAD_TEST_KEYS_R2 + + +class HdcpSource1XKeys(IntEnum): + Unknown = -1 + Unload = H1_SOURCE_UNLOAD_KEYS + Test = H1_SOURCE_LOAD_TEST_KEYS + Production = H1_SOURCE_LOAD_PROD_KEYS + + +class HdcpSource2XKeys(IntEnum): + Unknown = -1 + Unload = H2_SOURCE_UNLOAD_KEYS + Production = H2_SOURCE_LOAD_PROD_KEYS + TestR1 = H2_SOURCE_LOAD_TEST_KEYS_R1 + TestR2 = H2_SOURCE_LOAD_TEST_KEYS_R2 + + +class HdcpMode(IntEnum): + Unknown = -1 + Mode1_4 = 0 + Mode2_3 = 1 + + +class HdcpHwCaps: + + def __init__(self): + self.hw_supported = False + self.production_keys_available = False + self.test_keys_available = False + + def __str__(self): + return f"HW Supported: {self.hw_supported}\n" \ + f"Production keys available: {self.production_keys_available}\n" \ + f"Test keys available: {self.test_keys_available}\n" + + +class HdcpHwSinkCaps(HdcpHwCaps): + + def __init__(self, caps: int, hdcp_mode: HdcpMode): + super().__init__() + if hdcp_mode == HdcpMode.Mode1_4: + self.hw_supported = ((caps >> 16) & 1) != 0 + self.production_keys_available = ((caps >> 17) & 1) != 0 + self.test_keys_available = ((caps >> 18) & 1) != 0 + else: + self.hw_supported = ((caps >> 8) & 1) != 0 + self.production_keys_available = ((caps >> 9) & 1) != 0 + self.test_keys_available = ((caps >> 10) & 1) != 0 + + +class HdcpHwSourceCaps(HdcpHwCaps): + + def __init__(self, caps: int, hdcp_mode: HdcpMode): + super().__init__() + if hdcp_mode == HdcpMode.Mode1_4: + self.hw_supported = ((caps >> 24) & 1) != 0 + self.production_keys_available = ((caps >> 25) & 1) != 0 + self.test_keys_available = ((caps >> 26) & 1) != 0 + elif hdcp_mode == HdcpMode.Mode2_3: + self.hw_supported = ((caps >> 8) & 1) != 0 + self.production_keys_available = ((caps >> 9) & 1) != 0 + self.test_keys_available = ((caps >> 10) & 1) != 0 + + +class HdcpStatus: + + class Status1x: + + def __init__(self): + self.active = False + self.keys = None + self.capable = False + self.authenticated = False + + def __str__(self): + return f"Active: {bool(self.active)}\n" \ + f"Keys: {self.keys.name}\n" \ + f"Capable: {bool(self.capable)}\n" \ + f"Authenticated: {bool(self.authenticated)}\n" + + def __eq__(self, other): + return self.active == other.active and \ + self.keys == other.keys and \ + self.capable == other.capable and \ + self.authenticated == other.authenticated + + class StatusRx2x: + + def __init__(self): + self.active = False + self.keys = None + self.capable = False + self.authenticated = False + self.km_is_stored = False + self.content_level = 0 + + def __str__(self): + return f"Active: {bool(self.active)}\n" \ + f"Keys: {self.keys.name}\n" \ + f"Capable: {bool(self.capable)}\n" \ + f"Authenticated: {bool(self.authenticated)}\n" \ + f"Km is stored: {bool(self.km_is_stored)}\n" \ + f"Content level: {self.content_level}\n" + + def __eq__(self, other): + return self.active == other.active and \ + self.keys == other.keys and \ + self.capable == other.capable and \ + self.authenticated == other.authenticated and \ + self.km_is_stored == other.km_is_stored and \ + self.content_level == other.content_level + + class StatusTx2x: + + def __init__(self): + self.active = False + self.keys = None + self.capable = False + self.authenticated = False + self.km_is_stored = False + self.content_level = 0 + self.try_authenticate = False + self.try_encrypt = False + + def __str__(self): + return f"Active: {bool(self.active)}\n" \ + f"Keys: {self.keys.name}\n" \ + f"Capable: {bool(self.capable)}\n" \ + f"Authenticated: {bool(self.authenticated)}\n" \ + f"Km is stored: {bool(self.km_is_stored)}\n" \ + f"Content level: {self.content_level}\n" + + def __eq__(self, other): + return self.active == other.active and \ + self.keys == other.keys and \ + self.capable == other.capable and \ + self.authenticated == other.authenticated and \ + self.km_is_stored == other.km_is_stored and \ + self.content_level == other.content_level and \ + self.try_authenticate == other.try_authenticate and \ + self.try_encrypt == other.try_encrypt + + +class HdcpRxConfig: + + class Config1x: + + def __init__(self): + self.keys = None + self.capable = None + + class Config2x: + + def __init__(self): + self.keys = None + self.capable = None + + +class HdcpTxConfig: + + class Config1x: + + def __init__(self): + self.encryption = None + self.authenticate = None + self.keys = None + + def __eq__(self, other): + return self.encryption == other.encryption and \ + self.authenticate == other.authenticate and \ + self.keys == other.keys + + class Config2x: + + def __init__(self): + self.encryption = None + self.authenticate = None + self.keys = None + self.store_km = None + self.content_level = None + + def __eq__(self, other): + return self.encryption == other.encryption and \ + self.authenticate == other.authenticate and \ + self.keys == other.keys and \ + self.store_km == other.store_km and \ + self.content_level == other.content_level + + +HdcpRxConfigType = TypeVar("HdcpRxConfigType", + HdcpRxConfig.Config1x, + HdcpRxConfig.Config2x) + +HdcpTxConfigType = TypeVar("HdcpTxConfigType", + HdcpTxConfig.Config1x, + HdcpTxConfig.Config2x) + +HdcpStatusType = TypeVar("HdcpStatusType", + HdcpStatus.Status1x, + HdcpStatus.StatusRx2x, + HdcpStatus.StatusTx2x) diff --git a/UniTAP/dev/ports/modules/internal_utils/__init__.py b/UniTAP/dev/ports/modules/internal_utils/__init__.py new file mode 100644 index 0000000..cf114fa --- /dev/null +++ b/UniTAP/dev/ports/modules/internal_utils/__init__.py @@ -0,0 +1,6 @@ +from .math import aligned +from .image_formats import uicl_cf_from_vm, uicl_sampling_from_vm, uicl_colorimetry_from_vm,\ + uicl_parameters_from_vm, uicl_image_from_vm_and_data, uicl_image_convert_to_vm + + + diff --git a/UniTAP/dev/ports/modules/internal_utils/image_formats.py b/UniTAP/dev/ports/modules/internal_utils/image_formats.py new file mode 100644 index 0000000..2c84da2 --- /dev/null +++ b/UniTAP/dev/ports/modules/internal_utils/image_formats.py @@ -0,0 +1,122 @@ +from UniTAP.libs.lib_uicl.uicl import * +from UniTAP.common import VideoMode, ColorInfo + + +class VideoFileFormat(IntEnum): + UNKNOWN = -1 + BIN = 0 + MP4 = 1 + + +class PictureFileFormat(IntEnum): + UNKNOWN = -1 + BIN = 0 + BMP = 1 + PPM = 2 + DSC = 3 + + +def uicl_cf_from_vm(color_format: ColorInfo.ColorFormat) -> UICL_Colorspace: + if color_format == ColorInfo.ColorFormat.CF_RGB: + return UICL_Colorspace.Colorspace_RGB + elif color_format in [ColorInfo.ColorFormat.CF_YCbCr_444, ColorInfo.ColorFormat.CF_YCbCr_422, + ColorInfo.ColorFormat.CF_YCbCr_420]: + return UICL_Colorspace.Colorspace_YCbCr + else: + return UICL_Colorspace.Colorspace_Unknown + + +def uicl_sampling_from_vm(color_format: ColorInfo.ColorFormat) -> UICL_Sampling: + if color_format in [ColorInfo.ColorFormat.CF_RGB, ColorInfo.ColorFormat.CF_YCbCr_444]: + return UICL_Sampling.Sampling_444 + elif color_format == ColorInfo.ColorFormat.CF_YCbCr_422: + return UICL_Sampling.Sampling_422 + elif color_format == ColorInfo.ColorFormat.CF_YCbCr_420: + return UICL_Sampling.Sampling_420 + else: + return UICL_Sampling.Sampling_Unknown + + +def uicl_colorimetry_from_vm(colorimetry: ColorInfo.Colorimetry) -> UICL_Colorimetry: + if colorimetry == ColorInfo.Colorimetry.CM_ITUR_BT601: + return UICL_Colorimetry.Colorimetry_ITU_R_BT601 + elif colorimetry == ColorInfo.Colorimetry.CM_ITUR_BT709: + return UICL_Colorimetry.Colorimetry_ITU_R_BT709 + elif colorimetry == ColorInfo.Colorimetry.CM_ITUR_BT2020_YCbCr: + return UICL_Colorimetry.Colorimetry_ITU_R_BT2020 + else: + return UICL_Colorimetry.Colorimetry_Unknown + + +def uicl_parameters_from_vm(vm: VideoMode): + parameters = UICL_ImageParameters() + + parameters.Width = vm.timing.hactive + parameters.Height = vm.timing.vactive + parameters.BitsPerColor = vm.color_info.bpc + parameters.Colorimetry = uicl_colorimetry_from_vm(vm.color_info.colorimetry) + + if vm.color_info.color_format == ColorInfo.ColorFormat.CF_RGB: + parameters.Colorspace = UICL_Colorspace.Colorspace_RGB + parameters.ComponentOrder = UICL_ComponentOrder.Order_RGB + parameters.Sampling = UICL_Sampling.Sampling_444 + parameters.Packing = UICL_Packing.Packing_Packed + parameters.Alignment = UICL_Alignment.Alignment_LSB + parameters.IsFullRange = vm.color_info.DynamicRange == ColorInfo.DynamicRange.DR_VESA + + elif vm.color_info.color_format == ColorInfo.ColorFormat.CF_YCbCr_444: + parameters.Colorspace = UICL_Colorspace.Colorspace_YCbCr + parameters.ComponentOrder = UICL_ComponentOrder.Order_YCbCr + parameters.Sampling = UICL_Sampling.Sampling_444 + parameters.Packing = UICL_Packing.Packing_Packed + parameters.Alignment = UICL_Alignment.Alignment_LSB + + parameters.IsFullRange = False + + elif vm.color_info.color_format == ColorInfo.ColorFormat.CF_YCbCr_422: + parameters.Colorspace = UICL_Colorspace.Colorspace_YCbCr + parameters.ComponentOrder = UICL_ComponentOrder.Order_CbY0CrY1 + parameters.Sampling = UICL_Sampling.Sampling_422 + parameters.Packing = UICL_Packing.Packing_Packed + parameters.Alignment = UICL_Alignment.Alignment_LSB + + parameters.IsFullRange = False + + elif vm.color_info.color_format == ColorInfo.ColorFormat.CF_YCbCr_420: + parameters.Colorspace = UICL_Colorspace.Colorspace_YCbCr + parameters.ComponentOrder = UICL_ComponentOrder.Order_YCbCr + parameters.Sampling = UICL_Sampling.Sampling_420 + parameters.Packing = UICL_Packing.Packing_Planar + parameters.Alignment = UICL_Alignment.Alignment_LSB + + parameters.IsFullRange = False + + return parameters + + +def uicl_image_from_vm_and_data(video_mode: VideoMode, data: bytearray) -> UICL_Image: + image = UICL_Image() + + image.DataPtr = (c_uint8 * len(data))(*data) + image.DataSize = len(data) + image.Parameters = uicl_parameters_from_vm(video_mode) + + return image + + +def uicl_image_convert_to_vm(src_image: UICL_Image, video_mode: VideoMode) -> bytearray: + dest_image = UICL_Image() + + dest_image.Parameters = uicl_parameters_from_vm(video_mode) + result = UICL_GetRequiredBufferSize(dest_image) + + assert result >= UICL_SUCCESS, f"Calculation required buffer size failed with error {result}" + + dest_image.DataPtr = (c_uint8 * result)() + dest_image.DataSize = result + + result = UICL_Convert(src_image, dest_image) + + assert result == UICL_SUCCESS, f"Image conversion failed with error {result}" + + return bytearray(dest_image.DataPtr[:dest_image.DataSize]) diff --git a/UniTAP/dev/ports/modules/internal_utils/image_utils.py b/UniTAP/dev/ports/modules/internal_utils/image_utils.py new file mode 100644 index 0000000..d42994f --- /dev/null +++ b/UniTAP/dev/ports/modules/internal_utils/image_utils.py @@ -0,0 +1,27 @@ +from UniTAP.libs.lib_uicl.uicl import * + + +def save_video_to_bin(path: str, data: list): + raise NotImplementedError(f"Temporary not supported saving to bin file format.") + + +def save_video_to_mp4(path: str, data: list): + raise NotImplementedError(f"Temporary not supported saving to mp4 file format.") + + +def save_image_to_bin(path: str, image_object: UICL_Image): + res = UICL_SaveToFile(image_object, path, ImageFileFormat.FILE_FORMAT_BIN) + if res != 0: + raise SystemError(f"Error saving image to BIN format. Error code {res}. Description {uicl_errors.get(res)}") + + +def save_image_to_bmp(path: str, image_object: UICL_Image): + res = UICL_SaveToFile(image_object, path, ImageFileFormat.FILE_FORMAT_BMP) + if res != 0: + raise SystemError(f"Error saving image to BMP format. Error code {res}. Description {uicl_errors.get(res)}") + + +def save_image_to_ppm(path: str, image_object: UICL_Image): + res = UICL_SaveToFile(image_object, path, ImageFileFormat.FILE_FORMAT_PPM) + if res != 0: + raise SystemError(f"Error saving image to PPM format. Error code {res}. Description {uicl_errors.get(res)}") diff --git a/UniTAP/dev/ports/modules/internal_utils/math.py b/UniTAP/dev/ports/modules/internal_utils/math.py new file mode 100644 index 0000000..c31bf39 --- /dev/null +++ b/UniTAP/dev/ports/modules/internal_utils/math.py @@ -0,0 +1,2 @@ +def aligned(v, by): + return (v + (by - 1)) / by * by diff --git a/UniTAP/dev/ports/modules/link/__init__.py b/UniTAP/dev/ports/modules/link/__init__.py new file mode 100644 index 0000000..c49c1e7 --- /dev/null +++ b/UniTAP/dev/ports/modules/link/__init__.py @@ -0,0 +1,2 @@ +from .dp import * +from .hdmi import * diff --git a/UniTAP/dev/ports/modules/link/dp/__init__.py b/UniTAP/dev/ports/modules/link/dp/__init__.py new file mode 100644 index 0000000..060e9ba --- /dev/null +++ b/UniTAP/dev/ports/modules/link/dp/__init__.py @@ -0,0 +1,8 @@ +from .link_rx import LinkDisplayPortRx +from .link_rx_types import LinkCapabilities, LinkEDPCapabilities +from .link_tx import LinkDisplayPortTx, DpLinkTrainingResult, DPLinkPattern +from .link_tx_config import LinkConfig, SSCConfig +from .link_status_common import DpLinkEncoding +from .link_tx_types import DPLinkPattern, DP128b132bLinkPattern, DPOutLinkMode +from .dp_cable_info import CableCapabilitiesEnum +from .link_rx_aux_controller import RoutedLTConfig diff --git a/UniTAP/dev/ports/modules/link/dp/dp_cable_info.py b/UniTAP/dev/ports/modules/link/dp/dp_cable_info.py new file mode 100644 index 0000000..02e6c68 --- /dev/null +++ b/UniTAP/dev/ports/modules/link/dp/dp_cable_info.py @@ -0,0 +1,19 @@ +from UniTAP.libs.lib_tsi import PortIO +from .private_link_rx_types import CableAttributesStruct, PortCableAttributesStruct +from .link_rx_types import CableCapabilitiesEnum + + +class DpCableInfo: + + def __init__(self, port_io: PortIO, ci_name: int): + self.__io = port_io + self.__ci = ci_name + + def __read_cable_attributes(self) -> CableAttributesStruct: + return self.__io.get(self.__ci, CableAttributesStruct)[1] + + def get_current_port_cable_type(self) -> CableCapabilitiesEnum: + return PortCableAttributesStruct(self.__read_cable_attributes().currentPort).get_cable_caps() + + def get_another_port_cable_type(self) -> CableCapabilitiesEnum: + return PortCableAttributesStruct(self.__read_cable_attributes().anotherPort).get_cable_caps() diff --git a/UniTAP/dev/ports/modules/link/dp/dp_utils.py b/UniTAP/dev/ports/modules/link/dp/dp_utils.py new file mode 100644 index 0000000..0b9cf1e --- /dev/null +++ b/UniTAP/dev/ports/modules/link/dp/dp_utils.py @@ -0,0 +1,137 @@ +from UniTAP.common import ColorInfo + + +def get_vm_color_format(vsc_sdp_db16: int) -> ColorInfo.ColorFormat: + color_format_value = vsc_sdp_db16 >> 4 & 0xf + + if color_format_value == 0: + return ColorInfo.ColorFormat.CF_RGB + elif color_format_value == 1: + return ColorInfo.ColorFormat.CF_YCbCr_444 + elif color_format_value == 2: + return ColorInfo.ColorFormat.CF_YCbCr_422 + elif color_format_value == 3: + return ColorInfo.ColorFormat.CF_YCbCr_420 + elif color_format_value == 4: + return ColorInfo.ColorFormat.CF_Y_ONLY + elif color_format_value == 5: + return ColorInfo.ColorFormat.CF_RAW + else: + return ColorInfo.ColorFormat.CF_UNKNOWN + + +def get_vm_colorimetry(vsc_sdp_db16: int) -> ColorInfo.Colorimetry: + color_format_value = vsc_sdp_db16 >> 4 & 0xf + colorimetry_value = vsc_sdp_db16 & 0xf + + if color_format_value == 0: + if colorimetry_value == 0: + return ColorInfo.Colorimetry.CM_sRGB + elif colorimetry_value == 1: + return ColorInfo.Colorimetry.CM_RGB_WIDE_GAMUT_FIX + elif colorimetry_value == 2: + return ColorInfo.Colorimetry.CM_RGB_WIDE_GAMUT_FLT + elif colorimetry_value == 3: + return ColorInfo.Colorimetry.CM_AdobeRGB + elif colorimetry_value == 4: + return ColorInfo.Colorimetry.CM_DCI_P3 + elif colorimetry_value == 5: + return ColorInfo.Colorimetry.CM_CUSTOM_COLOR_PROFILE + elif colorimetry_value == 6: + return ColorInfo.Colorimetry.CM_ITUR_BT2020_RGB + else: + return ColorInfo.Colorimetry.CM_RESERVED + + elif color_format_value in [1, 2, 3]: + if colorimetry_value == 0: + return ColorInfo.Colorimetry.CM_ITUR_BT601 + elif colorimetry_value == 1: + return ColorInfo.Colorimetry.CM_ITUR_BT709 + elif colorimetry_value == 2: + return ColorInfo.Colorimetry.CM_xvYCC601 + elif colorimetry_value == 3: + return ColorInfo.Colorimetry.CM_xvYCC709 + elif colorimetry_value == 4: + return ColorInfo.Colorimetry.CM_sYCC601 + elif colorimetry_value == 5: + return ColorInfo.Colorimetry.CM_opYCC601 + elif colorimetry_value == 6: + return ColorInfo.Colorimetry.CM_ITUR_BT2020_YcCbcCrc + elif colorimetry_value == 7: + return ColorInfo.Colorimetry.CM_ITUR_BT2020_YCbCr + else: + return ColorInfo.Colorimetry.CM_RESERVED + + elif color_format_value == 4: + if colorimetry_value == 0: + return ColorInfo.Colorimetry.CM_DCI_P3 + else: + return ColorInfo.Colorimetry.CM_RESERVED + + elif color_format_value == 5: + if colorimetry_value == 0: + return ColorInfo.Colorimetry.CM_CUSTOM_COLOR_PROFILE + else: + return ColorInfo.Colorimetry.CM_RESERVED + + else: + return ColorInfo.Colorimetry.CM_RESERVED + + +def get_vm_bpc(vsc_sdp_db16: int, vsc_sdp_db17: int) -> int: + color_format_value = vsc_sdp_db16 >> 4 & 0xf + bpc_value = vsc_sdp_db17 & 0x7 + + if color_format_value == 0: + if bpc_value == 0: + return 6 + elif bpc_value == 1: + return 8 + elif bpc_value == 2: + return 10 + elif bpc_value == 3: + return 12 + elif bpc_value == 4: + return 16 + else: + return 0 + + elif color_format_value in [1, 2, 3, 4]: + if bpc_value == 1: + return 8 + elif bpc_value == 2: + return 10 + elif bpc_value == 3: + return 12 + elif bpc_value == 4: + return 16 + else: + return 0 + + elif color_format_value == 5: + if bpc_value == 1: + return 6 + elif bpc_value == 2: + return 7 + elif bpc_value == 3: + return 8 + elif bpc_value == 4: + return 10 + elif bpc_value == 5: + return 12 + elif bpc_value == 6: + return 14 + elif bpc_value == 7: + return 16 + else: + return 0 + + else: + return 0 + + +def get_vm_dynamic_range(vsc_sdp_db17: int) -> ColorInfo.DynamicRange: + if vsc_sdp_db17 >> 7 & 1: + return ColorInfo.DynamicRange.DR_CTA + else: + return ColorInfo.DynamicRange.DR_VESA diff --git a/UniTAP/dev/ports/modules/link/dp/link_rx.py b/UniTAP/dev/ports/modules/link/dp/link_rx.py new file mode 100644 index 0000000..e684f8c --- /dev/null +++ b/UniTAP/dev/ports/modules/link/dp/link_rx.py @@ -0,0 +1,121 @@ +from .link_rx_caps import LinkDisplayPortCaps +from .link_rx_status import * +from UniTAP.libs.lib_tsi.tsi_io import PortIO +from .dp_cable_info import DpCableInfo, CableCapabilitiesEnum +from .link_rx_aux_controller import DisplayPortAUXController +from UniTAP.dev.ports.modules.dpcd import DPCDRegisters + + +class LinkDisplayPortRx: + """ + Class `LinkDisplayPortRx` contains information about DP link. + - Read link status `status`. + - Configure and read link capabilities `capabilities`. + - Make `hpd_pulse`. + - Assert/Deassert HPD state `set_assert_state`. + - Read and write scrambler seed value `scrambler_seed`. + """ + def __init__(self, port_io: PortIO, hw_caps: DPRXHWCaps, dpcd: DPCDRegisters): + self.__io = port_io + self.__HW_CAPS = hw_caps + self.__status = LinkDisplayPortStatusSink(port_io, self.__HW_CAPS) + self.__capabilities = LinkDisplayPortCaps(port_io, self.__HW_CAPS) + self.__cable_info = DpCableInfo(port_io, TSI_DPRX_CABLE_ATTRIBUTES_R) + self.__aux_controller = DisplayPortAUXController(port_io, dpcd) + + @property + def status(self) -> LinkDisplayPortStatusSink: + """ + Returns object of class `LinkDisplayPortStatusSink` for working with link status. + + Returns: + object of `LinkDisplayPortStatusSink` type + """ + return self.__status + + @property + def capabilities(self) -> LinkDisplayPortCaps: + """ + Returns object of class `LinkDisplayPortCaps` for working with link capabilities. + + Returns: + object of `LinkDisplayPortStatusSink` type + """ + return self.__capabilities + + @property + def aux_controller(self) -> DisplayPortAUXController: + """ + Returns object of class `DisplayPortAUXController` for working with DP AUX Controller. + + Returns: + object of `DisplayPortAUXController` type + """ + return self.__aux_controller + + def hpd_pulse(self, duration_us: int = 500000): + """ + Start HPD pulse. + + Args: + duration_us (int) + """ + self.__io.set(TSI_DPRX_HPD_PULSE_W, duration_us, c_int) + + def set_assert_state(self, state: bool): + """ + Assert/Deassert HPD state. + + Args: + state (bool) + """ + val = 0x1 if state else 0x0 + self.__io.set(TSI_FORCE_HOT_PLUG_STATE_W, val) + + @property + def scrambler_seed(self) -> int: + """ + Returns scrambler seed value. + + Returns: + object of `int` type + """ + if not self.__HW_CAPS.scrambler_seed: + warnings.warn("Scrambler Seed is not supported.") + return 0 + + return self.__io.get(TSI_DPRX_SCR_SEED, c_uint32)[1] + + @scrambler_seed.setter + def scrambler_seed(self, value: int = 0): + """ + Write new value to scrambler seed + + Args: + value (int) - new scrambler seed value + """ + if value < 0 or value > 65535: + raise ValueError(f"Scrambler seed {value} is not available.") + + if self.__HW_CAPS.scrambler_seed: + self.__io.set(TSI_DPRX_SCR_SEED, value) + else: + warnings.warn("Scrambler Seed is not supported.") + + def cable_rx_type(self) -> CableCapabilitiesEnum: + """ + Get cable type from the RX side. + + Returns: + object of `CableCapabilitiesEnum` type + """ + return self.__cable_info.get_current_port_cable_type() + + def cable_tx_type(self) -> CableCapabilitiesEnum: + """ + Get cable type from the TX side. + + Returns: + object of `CableCapabilitiesEnum` type + """ + return self.__cable_info.get_another_port_cable_type() diff --git a/UniTAP/dev/ports/modules/link/dp/link_rx_aux_controller.py b/UniTAP/dev/ports/modules/link/dp/link_rx_aux_controller.py new file mode 100644 index 0000000..6344181 --- /dev/null +++ b/UniTAP/dev/ports/modules/link/dp/link_rx_aux_controller.py @@ -0,0 +1,218 @@ +from typing import Optional +from UniTAP.libs.lib_tsi import PortIO +from UniTAP.libs.lib_tsi.tsi_private_types import * +from UniTAP.libs.lib_tsi.tsi_types import * +from .private_link_rx_types import RoutedLTStatusPrivate +from .link_rx_types import RoutedLTStatus, RoutedLTConfig +from UniTAP.dev.ports.modules.dpcd import DPCDRegisters + + +class DisplayPortAUXController: + """ + Class `DisplayPortAUXController` describes information about DP AUX Controller. Contains following info: + - configure routed LT `exec_routed_lt` + - read routed T status `status` + - request PHY test pattern `request_phy_test_pattern` + """ + def __init__(self, port_io: PortIO, dpcd: DPCDRegisters): + self.__io = port_io + self.__dpcd = dpcd + + def enable(self, enable: bool): + """ + Enable or disable Routed LT. + + Args: + enable ('bool') + + """ + status = self.status() + + ctrl = status.dp20_old_lt << 1 + if enable: + ctrl |= 0x01 + else: + ctrl &= ~0x01 + + res = self.__io.set(TSI_DPRX_LT_ROUTE_CONTROL_W, ctrl, c_uint32) + if not res: + raise Exception("The feature requires a license key and the required license key is not installed.") + + def exec_routed_lt(self, config: RoutedLTConfig, use_ta_request: bool = False): + """ + + Execute routed link training with transferred configuration. + + Args: + config ('RoutedLTConfig') + use_ta_request (`bool`) + """ + max_rate_dp14 = 0 + max_rate_dp20 = 0 + + if config.is128b132b: + if config.link_bw == 1: + max_rate_dp20 = 1 + elif config.link_bw == 2: + max_rate_dp20 = 3 + elif config.link_bw == 4: + max_rate_dp20 = 5 + + max_rate_dp14 = 0x1e + else: + max_rate_dp14 = config.link_bw + + res = self.__set_routed_lt_config(config.int_value()) + if not res: + raise Exception("The feature requires a license key and the required license key is not installed.") + self.__set_force_cable_status_to_plugged(True) + + dpcd_rev = 0x14 + dpcd_01_val = max_rate_dp14 + dpcd_02_val = config.lane_count | 0xc0 + dpcd_03_val = 0x81 + dpcd_06_val = 0x03 if config.is128b132b else 0x01 + + self.__dpcd.write(0x00, dpcd_rev) + self.__dpcd.write(0x01, dpcd_01_val) + self.__dpcd.write(0x02, dpcd_02_val) + self.__dpcd.write(0x03, dpcd_03_val) + self.__dpcd.write(0x06, dpcd_06_val) + + self.__dpcd.write(0x0e, 0x80) + self.__dpcd.write(0x2200, dpcd_rev) + self.__dpcd.write(0x2201, dpcd_01_val) + self.__dpcd.write(0x2202, dpcd_02_val) + self.__dpcd.write(0x2203, dpcd_03_val) + self.__dpcd.write(0x2206, dpcd_06_val) + + self.__dpcd.write(0x2215, max_rate_dp20) + + if config.is128b132b: + self.__dpcd.write(0x21, 0x01) + self.__dpcd.write(0x90, 0xff) + dsc_data = [0x0f, 0x21, 0x03, 0x03, 0xeb, 0x07, 0x01, 0x00, 0x00, 0x1f, 0x0e, 0x11, 0x08, 0x07, 0x00, 0x00] + self.__dpcd.write(0x60, dsc_data) + self.__dpcd.write(0x2217, 0x06) + else: + self.__dpcd.write(0x21, 0x00) + self.__dpcd.write(0x90, 0x00) + dsc_data = [0x00] * 16 + self.__dpcd.write(0x60, dsc_data) + self.__dpcd.write(0x2217, 0x00) + + self.__dpcd.write(0x100, 0) + self.__dpcd.write(0x101, 0) + self.__dpcd.write(0x103, 0) + self.__dpcd.write(0x104, 0) + self.__dpcd.write(0x105, 0) + self.__dpcd.write(0x106, 0) + self.__dpcd.write(0x107, 0) + self.__dpcd.write(0x108, 1) + self.__dpcd.write(0x206, 0) + self.__dpcd.write(0x207, 0) + + if use_ta_request: + self.__dpcd.write(0x219, config.link_bw) + self.__dpcd.write(0x220, config.lane_count) + + dpcd_218 = self.__dpcd.read(0x218, 1) + dpcd_218_data = int.from_bytes(dpcd_218.data, byteorder='big', signed=True) + dpcd_218_data |= 0x01 + dpcd_218_data &= ~0x30 + if config.is128b132b: + dpcd_218_data |= 0x10 + self.__dpcd.write(dpcd_218.base, dpcd_218_data) + + dpcd_201 = self.__dpcd.read(0x201, 1) + dpcd_201_data = int.from_bytes(dpcd_201.data, byteorder='big', signed=True) + dpcd_201_data |= 0x02 + self.__dpcd.write(dpcd_201.base, dpcd_201_data) + + self.__generate_short_hpd_pulse(750) + else: + dpcd_201 = self.__dpcd.read(0x201, 1) + dpcd_201_data = int.from_bytes(dpcd_201.data, byteorder='big', signed=True) + dpcd_201_data &= ~2 + self.__dpcd.write(dpcd_201.base, dpcd_201_data) + + dpcd_218 = self.__dpcd.read(0x218, 1) + dpcd_218_data = int.from_bytes(dpcd_218.data, byteorder='big', signed=True) + dpcd_218_data &= ~0xf + self.__dpcd.write(dpcd_218.base, dpcd_218_data) + + self.__generate_short_hpd_pulse(500 * 1000) + + def request_phy_test_pattern(self, pattern: int, sq_num: int, config: RoutedLTConfig): + """ + + Request PHY test pattern. + + Args: + config ('RoutedLTConfig') + pattern (`int`) + sq_num (`int`) + """ + + self.__set_force_cable_status_to_plugged(True) + + self.__dpcd.write(0x00, 0x14) + self.__dpcd.write(0x218, 0x18 if config.is128b132b else 0x08) + self.__dpcd.write(0x219, config.link_bw) + self.__dpcd.write(0x220, config.lane_count) + + self.__dpcd.write(0x248, pattern) + + if config.is128b132b and 0x48 <= pattern <= 0x4b: + self.__dpcd.write(0x249, sq_num) + + if config.is128b132b: + value = config.ffe | (config.ffe << 4) + else: + value = (config.vs & 3) | ((config.pe & 3) << 2) + value |= value << 4 + + self.__dpcd.write(0x206, value) + self.__dpcd.write(0x207, value) + self.__dpcd.write(0x260, 0x0) + + dpcd_201 = self.__dpcd.read(0x201, 1) + dpcd_201_data = int.from_bytes(dpcd_201.data, byteorder='big', signed=True) + dpcd_201_data |= 0x02 + self.__dpcd.write(dpcd_201.base, dpcd_201_data) + + self.__generate_short_hpd_pulse(750) + + def status(self) -> RoutedLTStatus: + """ + Get Routed link training status. + + Returns: + object of `RoutedLTStatus` + """ + routed_lt_status = self.__read_status() + if routed_lt_status is None: + raise Exception("The feature requires a license key and the required license key is not installed.") + return RoutedLTStatus(enabled=routed_lt_status.enabled, + dp20_old_lt=routed_lt_status.dp2OldLt, + state=routed_lt_status.state, + success=routed_lt_status.success, + step=routed_lt_status.step) + + def __set_routed_lt_config(self, value: int) -> bool: + res = self.__io.set(TSI_DPRX_LT_ROUTE_CREATE, value, c_uint32) + return res >= 0 + + def __set_force_cable_status_to_plugged(self, enable: bool): + val = 0x3C if enable else 0 + self.__io.set(TSI_DPRX_HPD_FORCE, val, c_int) + + def __read_status(self) -> Optional[RoutedLTStatusPrivate]: + res, status = self.__io.get(TSI_DPRX_LT_ROUTE_STATUS_R, RoutedLTStatusPrivate) + + if res >= 0: + return status + return None + + def __generate_short_hpd_pulse(self, value: int): + self.__io.set(TSI_DPRX_HPD_PULSE_W, value, c_int) diff --git a/UniTAP/dev/ports/modules/link/dp/link_rx_caps.py b/UniTAP/dev/ports/modules/link/dp/link_rx_caps.py new file mode 100644 index 0000000..e3769c6 --- /dev/null +++ b/UniTAP/dev/ports/modules/link/dp/link_rx_caps.py @@ -0,0 +1,332 @@ +import warnings +from typing import List, Optional, Union, Type + +from .link_rx_types import LinkCapabilities, LinkEDPCapabilities, DisplayPortLinkCaps +from .private_link_rx_types import DPRXHWCaps, DPRXLinkControl +from UniTAP.libs.lib_tsi import PortIO +from UniTAP.libs.lib_tsi.tsi_types import * + + +class LinkDisplayPortCaps: + """ + Class `LinkDisplayPortCaps` allows settings link capabilities on Sink (RX - receiver) side. + - Set configuration `set`. + - Get current configuration on link `link_caps_status`. + """ + def __init__(self, port_io: PortIO, caps: DPRXHWCaps): + self.__io = port_io + self.__caps = caps + + def set(self, capabilities: Union[LinkCapabilities, LinkEDPCapabilities]): + """ + Set new settings on link. Only those values will be written that were specified. + + Args: + capabilities (`LinkCapabilities` or `LinkEDPCapabilities`) + """ + if isinstance(capabilities, LinkCapabilities): + self.__set_max_lanes(capabilities.max_lane) + self.__set_max_bitrate(capabilities.bit_rate) + self.__set_force_cable_status_to_plugged(capabilities.force_cable_status_to_plugged) + self.__set_dp_link_control(old_dp2_lt=capabilities.old_dp_2_0_lt) + self.__set_dp_128_132_bitrates(capabilities.dp_128_132_bitrates) + self.__set_bitrate_override(10.0, capabilities.override_10g) + self.__set_dsc(capabilities.dsc) + self.__set_ss_sbm(capabilities.ss_sbm) + self.__set_fec(capabilities.fec) + self.__set_tps3(capabilities.tps3) + self.__set_tps4(capabilities.tps4) + self.__set_mst(capabilities.mst) + self.__set_edp_support(False) + self.__set_mst_sink_count(capabilities.mst_sink_count) + elif isinstance(capabilities, LinkEDPCapabilities): + self.__set_max_lanes(capabilities.max_lane) + self.__set_edp_sel_rates(capabilities.eDp_cur_rate) + self.__set_dp_link_control(edp_aux_preamble=capabilities.eDp_aux_preamble) + self.__set_edp_support(capabilities.eDp_support) + else: + raise TypeError("Incorrect LinkCapabilities format!") + + def link_caps_status(self, config_type: Union[Type[DisplayPortLinkCaps], None] = None) -> DisplayPortLinkCaps: + """ + Returns current configuration on link `LinkCapabilities`. + + Returns: + object of `LinkCapabilities` type + """ + if config_type is None or issubclass(config_type, LinkCapabilities): + link_status = LinkCapabilities() + link_status.max_lane = self.__get_max_lanes() + link_status.bit_rate = self.__get_max_bitrate() + link_status.force_cable_status_to_plugged = self.__get_force_cable_status_to_plugged() + link_status.old_dp_2_0_lt = self.__get_dp_link_control().old_dp2_lt + link_status.dp_128_132_bitrates = self.__get_dp_128_132_bitrates() + link_status.override_10g = self.__get_bitrate_override(10.0) + link_status.dsc = self.__get_dsc() + link_status.ss_sbm = self.__get_ss_sbm() + link_status.fec = self.__get_fec() + link_status.tps3 = self.__get_tps3() + link_status.tps4 = self.__get_tps4() + link_status.mst = self.__get_mst() + link_status.mst_sink_count = self.__get_mst_sink_count() + return link_status + elif issubclass(config_type, LinkEDPCapabilities): + link_status = LinkEDPCapabilities() + link_status.max_lane = self.__get_max_lanes() + link_status.eDp_cur_rate = self.__get_edp_sel_rates() + link_status.eDp_supported_rates = self.__get_edp_caps_rates() + link_status.eDp_aux_preamble = self.__get_dp_link_control().edp_aux_preamble + link_status.eDp_support = self.__get_edp_support() + return link_status + else: + raise TypeError("Incorrect LinkCapabilities format!") + + def __set_max_lanes(self, lane_count: Optional[int]): + if lane_count is None: + return + if lane_count not in [1, 2, 4]: + raise ValueError(f"Incorrect lane count number {lane_count}. Must be from list: 1, 2, 4") + self.__io.set(TSI_DPRX_MAX_LANES, lane_count, c_int) + + def __get_max_lanes(self) -> int: + return self.__io.get(TSI_DPRX_MAX_LANES)[1] + + def __set_max_bitrate(self, bitrate: Optional[float]): + if bitrate is None: + return + bitrate = round(bitrate / 0.27) + if bitrate not in [6, 10, 20, 25, 30]: + raise ValueError(f"Incorrect bit rate number {bitrate}. Must be from list: 1.62, 2.7, 5.4, 6.75, 8.1") + self.__io.set(TSI_DPRX_MAX_LINK_RATE, bitrate, c_int) + + def __get_max_bitrate(self) -> int: + return round(self.__io.get(TSI_DPRX_MAX_LINK_RATE)[1] * 0.27, 2) + + def __set_bitrate_override(self, to_override: float, with_override: Optional[float]): + if with_override is None: + return + + if not isinstance(to_override, float) or not isinstance(with_override, float): + raise TypeError("DP 128b/132b bitrate override settings must float type!") + + list_value = self.__io.get(TSI_DP2RX_CUSTOM_RATE_MAP, c_uint, 3)[1] + + if with_override not in [2.5, 2.7, 5.0, 5.4]: + raise ValueError(f"Incorrect bit rate number {with_override}. " + f"Must be from list: 2.5, 2.7, 5.0, 5.4") + list_value[0] = int(with_override * 1000000000 / 200000) + self.__io.set(TSI_DP2RX_CUSTOM_RATE_MAP, list_value, c_uint, 3) + + def __get_bitrate_override(self, to_override: float) -> Optional[float]: + if not isinstance(to_override, float): + raise TypeError("DP 128b/132b bitrate override settings must float type!") + + override_list = self.__io.get(TSI_DP2RX_CUSTOM_RATE_MAP, c_uint, 3)[1] + if override_list.count(0) == len(override_list): + return None + + if to_override == 10.0 and override_list[0] > 0: + return round((override_list[0] * 200000 / 1000000000), 2) + + return None + + def __set_dp_link_control(self, old_dp2_lt: Optional[bool] = None, edp_aux_preamble: Optional[bool] = None): + ctrl = self.__get_dp_link_control() + + if old_dp2_lt is not None: + ctrl.old_dp2_lt = old_dp2_lt + if edp_aux_preamble is not None: + ctrl.edp_aux_preamble = edp_aux_preamble + + self.__io.set(TSI_DPRX_LINK_CONTROL, ctrl.value(), c_uint32) + + def __get_dp_link_control(self) -> DPRXLinkControl: + return self.__io.get(TSI_DPRX_LINK_CONTROL, DPRXLinkControl)[1] + + def __set_dp_128_132_bitrates(self, rates: Optional[List[float]]): + flags = self.__io.get(TSI_DPRX_LINK_FLAGS)[1] & ~0xc0 + if rates is None: + return + elif isinstance(rates, list) and len(rates) == 0: + flags &= ~(1 << 4) + self.__io.set(TSI_DPRX_LINK_FLAGS, flags, c_int) + else: + flags |= (1 << 4) + self.__io.set(TSI_DPRX_LINK_FLAGS, flags, c_int) + val = 0 + if 10.0 in rates: + val |= 0x01 + if 13.5 in rates: + val |= 0x04 + if 20.0 in rates: + val |= 0x02 + self.__io.set(TSI_DP2RX_LINK_RATE_CAPS, val, c_int) + + def __get_dp_128_132_bitrates(self) -> Optional[List[float]]: + val = (self.__io.get(TSI_DPRX_LINK_FLAGS)[1] >> 4) & 0x1 + if val: + rates = self.__io.get(TSI_DP2RX_LINK_RATE_CAPS)[1] + list_rates = [] + if rates & 0x01: + list_rates.append(10.0) + if rates & 0x04: + list_rates.append(13.5) + if rates & 0x02: + list_rates.append(20.0) + return list_rates + else: + return None + + def __set_force_cable_status_to_plugged(self, enable: Optional[bool]): + if enable is None: + return + val = 0x3C if enable else 0 + self.__io.set(TSI_DPRX_HPD_FORCE, val, c_int) + + def __get_force_cable_status_to_plugged(self) -> bool: + return bool(self.__io.get(TSI_DPRX_HPD_FORCE)[1]) + + def __set_dsc(self, enable: Optional[bool]): + if enable is None: + return + val = 0x1 if enable else 0x0 + if self.__caps.dsc: + self.__io.set(TSI_DPRX_DSC_CONTROL, val, c_int) + + def __get_dsc(self) -> Optional[bool]: + if self.__io.get(TSI_DPRX_DSC_STATUS_R)[1] & 0x1: + return bool(self.__io.get(TSI_DPRX_DSC_CONTROL)[1] & 0x1) + + def __set_fec(self, enable: Optional[bool]): + if enable is None: + return + val = 0x1 if enable else 0x0 + if self.__caps.fec: + self.__io.set(TSI_DPRX_FEC_CONTROL, val, c_int) + + def __get_fec(self) -> Optional[bool]: + if self.__caps.fec: + return bool(self.__io.get(TSI_DPRX_FEC_CONTROL)[1] & 0x1) + + def __set_mst(self, enable: Optional[bool]): + if enable is None: + return + flags = self.__io.get(TSI_DPRX_LINK_FLAGS)[1] & ~0xc0 + if enable: + flags |= 1 + else: + flags &= ~1 + if self.__caps.mst: + self.__io.set(TSI_DPRX_LINK_FLAGS, flags, c_int) + + def __get_mst(self) -> Optional[bool]: + if self.__caps.mst: + return bool(self.__io.get(TSI_DPRX_LINK_FLAGS)[1] & 0x1) + + def __set_mst_sink_count(self, count: int): + if count is None: + return + if self.__caps.sink_cnt_config and self.__caps.mst: + if count > self.__caps.mst_stream_count: + warnings.warn(f"Maximum sink count: {self.__caps.mst_stream_count}") + else: + self.__io.set(TSI_DPRX_MST_SINK_COUNT, count, c_uint32) + else: + warnings.warn("MST or Custom Sink Count are not supported") + + def __get_mst_sink_count(self) -> int: + if self.__caps.sink_cnt_config and self.__caps.mst: + return int(self.__io.get(TSI_DPRX_MST_SINK_COUNT)[1]) + else: + return 0 + + def __set_tps3(self, enable: Optional[bool]): + if enable is None: + return + flags = self.__io.get(TSI_DPRX_LINK_FLAGS)[1] & ~0xc0 + if enable: + flags |= (1 << 1) + else: + flags &= ~(1 << 1) + if self.__caps.fec: + self.__io.set(TSI_DPRX_LINK_FLAGS, flags, c_int) + + def __get_tps3(self) -> Optional[bool]: + if self.__caps.fec: + return bool((self.__io.get(TSI_DPRX_LINK_FLAGS)[1] >> 1) & 0x1) + + def __set_tps4(self, enable: Optional[bool]): + if enable is None: + return + flags = self.__io.get(TSI_DPRX_LINK_FLAGS)[1] & ~0xc0 + if enable: + flags |= (1 << 2) + else: + flags &= ~(1 << 2) + if self.__caps.fec: + self.__io.set(TSI_DPRX_LINK_FLAGS, flags, c_int) + + def __get_tps4(self) -> Optional[bool]: + if self.__caps.fec: + return bool((self.__io.get(TSI_DPRX_LINK_FLAGS)[1] >> 2) & 0x1) + + def __set_ss_sbm(self, enable: bool): + if enable is None: + return + if self.__caps.dp2_support_rates: + flags = self.__io.get(TSI_DPRX_LINK_FLAGS)[1] & ~0xc0 + if enable: + flags |= (1 << 5) + else: + flags &= ~(1 << 5) + self.__io.set(TSI_DPRX_LINK_FLAGS, flags, c_int) + + def __get_ss_sbm(self) -> Optional[bool]: + if self.__caps.dp2_support_rates: + return bool((self.__io.get(TSI_DPRX_LINK_FLAGS)[1] >> 5) & 0x1) + + def __set_edp_support(self, enable: bool): + if self.__caps.edp: + flags = self.__io.get(TSI_DPRX_LINK_FLAGS)[1] & ~0xc0 + if enable: + flags |= (1 << 3) + else: + flags &= ~(1 << 3) + self.__io.set(TSI_DPRX_LINK_FLAGS, flags, c_int) + + def __get_edp_support(self) -> Optional[bool]: + if self.__caps.edp: + return bool((self.__io.get(TSI_DPRX_LINK_FLAGS)[1] >> 3) & 0x1) + + def __set_edp_sel_rates(self, rates: List[float]): + if self.__caps.edp: + rates.sort() + rates = rates[:8] + to_write_list = [0 for i in range(8)] + j = 0 + for i in range(len(rates)): + if rates[i] in self.__get_edp_caps_rates(): + to_write_list[j] = int(rates[i] / 0.0002) + j += 1 + + rates.sort() + self.__io.set(TSI_DPRX_EDP_SEL_RATES, to_write_list, data_type=c_uint32, data_count=8) + + def __get_edp_sel_rates(self) -> List[float]: + if self.__caps.edp: + rates = self.__io.get(TSI_DPRX_EDP_SEL_RATES, data_type=c_uint32, data_count=8)[1] + res_rates = [] + for rate in rates: + if rate == 0: + return res_rates + res_rates.append(rate * 0.0002) + return res_rates + + def __get_edp_caps_rates(self) -> List[float]: + if self.__caps.edp: + rates = self.__io.get(TSI_DPRX_EDP_CAPS_RATES_R, data_type=c_uint32, data_count=32)[1] + new_rates = [] + for rate in rates: + if rate > 0: + new_rates.append(rate * 0.0002) + return new_rates diff --git a/UniTAP/dev/ports/modules/link/dp/link_rx_status.py b/UniTAP/dev/ports/modules/link/dp/link_rx_status.py new file mode 100644 index 0000000..e26b99e --- /dev/null +++ b/UniTAP/dev/ports/modules/link/dp/link_rx_status.py @@ -0,0 +1,407 @@ +import time +import warnings + +from typing import Optional + +from UniTAP.libs.lib_tsi import PortIO +from UniTAP.libs.lib_tsi.tsi_private_types import * +from UniTAP.libs.lib_tsi.tsi_types import * +from UniTAP.utils.function_wrapper import function_scheduler +from .private_link_rx_types import DPRXHWCaps +from .link_status_common import * +from .private_link_status_common import VCPTable, DpMsa, MSAInfo, DpCrc, msa_info_to_video_mode +from typing import Union + + +class LinkDisplayPortStatusSink: + """ + Class `LinkDisplayPortStatusSink` describes information about DP link status. Contains following info: + - MST stream count `mst_stream_count`. + - Lane count `lane_count`. + - Link encoding `link_encoding`. + - Link rate `link_rate`. + - State of HPD `hpd_asserted`. + - Cable state `cable_state`. + - State of framing `enhanced_framing`. + - State of scrambling `scrambling_enabled`. + - State of DSC `dsc_enabled` + - State of FEC `fec_enabled`. + - State of MST `mst_enabled`. + - State of SSC `ssc_enabled`. + - State of ILA `ila`. + - State of EQ ILA `eq_ila`. + - State of CDS ILA `cds_ila`. + - State of LT fail `lt_fail`. + - State of selected lane `lane`. + - State of VCP `vcp`. + - State of selected stream `stream`. + """ + def __init__(self, port_io: PortIO, caps: DPRXHWCaps): + self.__io = port_io + self.__caps = caps + + @property + def mst_stream_count(self) -> int: + """ + Returns current MST count. + + Returns: + object of int type + """ + res, value = self.__io.get(TSI_DPRX_MST_STATUS_R, c_uint) + return value >> 8 + + @property + def lane_count(self) -> int: + """ + Returns current lane count. + + Returns: + object of int type + """ + return self.__io.get(TSI_DPRX_LINK_LANE_COUNT_R, c_int)[1] + + @property + def link_encoding(self) -> DpLinkEncoding: + """ + Returns current link encoding `DpLinkEncoding`. + + Returns: + object of `DpLinkEncoding` type + """ + result, value = self.__io.get(TSI_DPRX_LINK_STATUS_FLAGS_R, c_uint) + + if value >> 20 & 1: + return DpLinkEncoding.LE_128b132b + else: + return DpLinkEncoding.LE_8b10b + + @property + def link_rate(self) -> float: + """ + Returns current link rate. + + Returns: + object of float type + """ + current_link_rate = self.__io.get(TSI_DPRX_LINK_BR_R, c_int)[1] + if self.link_encoding == DpLinkEncoding.LE_8b10b: + return round(current_link_rate * 0.27, 2) + else: + if current_link_rate == 1: + return 10.0 + elif current_link_rate == 2: + return 20.0 + elif current_link_rate == 4: + return 13.5 + else: + return 0.0 + + @property + def hpd_asserted(self) -> bool: + """ + Returns current state of HDP asserted. + + Returns: + object of bool type + """ + return bool(self.__io.get(TSI_DPRX_HPD_STATUS_R, c_int)[1] & 1) + + @property + def cable_state(self) -> bool: + """ + Returns current cable state. + + Returns: + object of bool type + """ + return bool(self.__io.get(TSI_DPRX_HPD_STATUS_R, c_int)[1] & 4) + + @property + def enhanced_framing(self) -> bool: + """ + Returns current enhanced framing state. + + Returns: + object of bool type + """ + return bool((self.__io.get(TSI_DPRX_LINK_STATUS_FLAGS_R, c_uint32)[1] >> 17) & 1) + + @property + def scrambling_enabled(self) -> bool: + """ + Returns current scrambling enabled state. + + Returns: + object of bool type + """ + return bool((self.__io.get(TSI_DPRX_LINK_STATUS_FLAGS_R, c_uint32)[1] >> 18) & 1) + + @property + def dsc_enabled(self) -> Union[bool, None]: + """ + Returns current DSC state. + + Returns: + object of bool|None type + """ + if self.__caps.dsc or self.__caps.dsc2: + return bool((self.__io.get(TSI_DPRX_DSC_STATUS_R)[1] >> 1) & 1) + else: + return None + + @property + def fec_enabled(self) -> Union[bool, None]: + """ + Returns current FEC state. + + Returns: + object of bool|None type + """ + if self.__caps.fec or self.__caps.fec2: + return bool(self.__io.get(TSI_DPRX_FEC_STATUS_R)[1] & 1) + else: + return None + + @property + def mst_enabled(self) -> Union[bool, None]: + """ + Returns current MST state. + + Returns: + object of bool|None type + """ + if self.__caps.mst: + return bool((self.__io.get(TSI_DPRX_LINK_STATUS_FLAGS_R, c_uint32)[1] >> 19) & 1) + else: + return None + + @property + def ssc_enabled(self) -> Union[bool, None]: + """ + Returns current SSC state. + + Returns: + object of bool|None type + """ + if (self.__io.get(TSI_DPRX_SSC_STATUS_R, c_int)[1] >> 1) & 1: + return bool(self.__io.get(TSI_DPRX_SSC_STATUS_R, c_int)[1] & 1) + else: + return None + + @property + def ila(self) -> bool: + """ + Returns current ILA state. + + Returns: + object of bool type + """ + return bool((self.__io.get(TSI_DPRX_LINK_STATUS_FLAGS_R, c_uint32)[1] >> 16) & 1) + + @property + def eq_ila(self) -> bool: + """ + Returns current EQ ILA state. + + Returns: + object of bool type + """ + return bool((self.__io.get(TSI_DPRX_LINK_STATUS_FLAGS_R, c_uint32)[1] >> 30) & 1) + + @property + def cds_ila(self) -> bool: + """ + Returns current CDS ILA state. + + Returns: + object of bool type + """ + return bool((self.__io.get(TSI_DPRX_LINK_STATUS_FLAGS_R, c_uint32)[1] >> 29) & 1) + + @property + def lt_fail(self) -> bool: + """ + Returns current LT fail state. + + Returns: + object of bool type + """ + return bool((self.__io.get(TSI_DPRX_LINK_STATUS_FLAGS_R, c_uint32)[1] >> 28) & 1) + + def lane(self, lane_number: int) -> LaneStatus: + """ + Returns status of lane `LaneStatus`. + + Args: + lane_number (int) - number of selected number + + Returns: + object of `LaneStatus` type + """ + if not (0 <= lane_number <= 3): + raise ValueError(f"Incorrect lane number {lane_number}. Available range: 0-{self.lane_count}") + + status = self.__io.get(TSI_DPRX_LINK_STATUS_FLAGS_R, c_uint32)[1] + voltage_swing = self.__io.get(TSI_DPRX_LINK_VS_R, c_uint32)[1] + pre_emphasis = self.__io.get(TSI_DPRX_LINK_PE_R, c_uint32)[1] + _, error_counters = self.__io.get(TSI_DPRX_GET_ERROR_COUNTS_R, c_uint32 * 4) + + lane_status = LaneStatus() + + if lane_number == 0: + lane_status.cr = bool(status & 0x1) + lane_status.sl = bool((status >> 1) & 0x1) + lane_status.eq = bool((status >> 2) & 0x1) + lane_status.error_count = error_counters[0] + elif lane_number == 1: + lane_status.cr = bool((status >> 4) & 0x1) + lane_status.sl = bool((status >> 5) & 0x1) + lane_status.eq = bool((status >> 6) & 0x1) + lane_status.error_count = error_counters[1] + elif lane_number == 2: + lane_status.cr = bool((status >> 8) & 0x1) + lane_status.sl = bool((status >> 9) & 0x1) + lane_status.eq = bool((status >> 10) & 0x1) + lane_status.error_count = error_counters[2] + elif lane_number == 3: + lane_status.cr = bool((status >> 12) & 0x1) + lane_status.sl = bool((status >> 13) & 0x1) + lane_status.eq = bool((status >> 14) & 0x1) + lane_status.error_count = error_counters[3] + + if self.__caps.dp2_support_rates and self.link_encoding == DpLinkEncoding.LE_128b132b: + lane_status.voltage_swing = 0 + lane_status.pre_emphasis = 0 + _, ffe_preset = self.__io.get(TSI_DP2RX_LINK_FFE_PRESET_R, c_uint32) + lane_status.ffe_preset = (ffe_preset >> (8 * lane_number)) & 0xF + else: + lane_status.voltage_swing = (voltage_swing >> (8 * lane_number)) & 0x3 + lane_status.pre_emphasis = (pre_emphasis >> (8 * lane_number)) & 0x3 + lane_status.ffe_preset = 0 + + return lane_status + + def vcp(self, stream_index: int = 0) -> Optional[VCPStatus]: + """ + Returns VCP status of selected stream `VCPStatus`. + + Args: + stream_index (int) - number of selected number + + Returns: + object of `VCPStatus` | None type + """ + if self.__caps.mst: + if not stream_index < self.mst_stream_count: + raise ValueError(f"Selected stream {stream_index} is not available. " + f"Available stream number: {self.mst_stream_count}") + + res = self.__io.get(TSI_DPRX_VC_TABLE_R, VCPTable, 4)[1] + + if self.mst_enabled: + status = VCPStatus() + + status.port_number = res[stream_index].port_number + status.stream_id = res[stream_index].stream_id + status.req_pbn = res[stream_index].req_pbn + status.alloc_pbn = res[stream_index].alloc_pbn + status.first_slot = res[stream_index].first_slot + status.slot_num = res[stream_index].slot_num + + return status + else: + warnings.warn("MST does not work. Cannot show VCP status.") + return VCPStatus() + + def __check_video(self) -> bool: + def is_msa_available(io): + result, msa_info, size = io.get(TSI_DPRX_MSA_INFO_R, DpMsa, 4) + return size > 0 + + return function_scheduler(is_msa_available, self.__io, interval=5, timeout=10) + + def stream(self, stream_index: int) -> StreamStatusDP: + """ + Returns status of selected stream `StreamStatusDP`. + + Args: + stream_index (int) - number of selected number + + Returns: + object of `StreamStatusDP` type + """ + stream_status = StreamStatusDP() + + if not self.__check_video(): + warnings.warn("Video is not available.") + return stream_status + + time.sleep(1) + + result, msa_info, size = self.__io.get(TSI_DPRX_MSA_INFO_R, DpMsa, 4) + + if stream_index < size: + msa_info_private = MSAInfo(msa_info[stream_index]) + stream_status.video_mode = msa_info_to_video_mode(msa_info_private, self.link_rate * 100000000, + self.link_encoding) + stream_status.mvid = msa_info_private.m_video + stream_status.nvid = msa_info_private.n_video + + result = self.__io.get(TSI_DPRX_CRC_LOG_R, DpCrc, self.mst_stream_count) + if result[0] >= TSI_SUCCESS and self.mst_stream_count > 1: + stream_status.crc = [result[1][stream_index].r, result[1][stream_index].g, result[1][stream_index].b] + elif result[0] >= TSI_SUCCESS and size == 1: + stream_status.crc = [result[1].r, result[1].g, result[1].b] + + result = self.__io.get(TSI_DPRX_DSC_CRC_R, DpCrc, self.mst_stream_count) + if result[0] >= TSI_SUCCESS and self.mst_stream_count > 1: + stream_status.dsc_crc = [result[1][stream_index].r, result[1][stream_index].g, result[1][stream_index].b] + elif result[0] >= TSI_SUCCESS and size == 1: + stream_status.dsc_crc = [result[1].r, result[1].g, result[1].b] + + if self.__caps.dp2_support_rates: + res = self.__io.get(TSI_DPRX_SDP_CRC16_CTRL, c_uint32) + stream_status.sdp_crc16.state = False if res[0] < TSI_SUCCESS else (res[1] & 0x1 == 1) + stream_status.sdp_crc16.errors = self.__io.get(TSI_DPRX_SDP_CRC16_COUNTERS, c_uint8, + self.__caps.mst_stream_count)[1][stream_index] + else: + raise ValueError(f"Selected stream {stream_index} is not available. " + f"Available stream number: {size}") + + return stream_status + + def reset_sdp_crc16_errors(self): + """ + Reset SDP CRC16 errors. + """ + if not self.__caps.dp2_support_rates: + warnings.warn("SDP CRC16 is not supported. Cannot reset errors.") + return + self.__io.set(TSI_DPRX_SDP_CRC16_COUNTERS, 0) + + def __str__(self) -> str: + lane_status_str = "" + stream_info_str = "" + vcp_table_str = "" + for i in range(self.mst_stream_count): + lane_status_str += f"Lane {i}\n{self.lane(i)}\n" + stream_info_str += f"Stream {i}\n{self.stream(i)}\n" + vcp_table_str += f"#{i}\n{self.vcp(i)}" + + return f"Lane count: {self.lane_count}\n" \ + f"Bit rate: {self.link_rate} Gbps\n" \ + f"Enhanced framing mode: {self.enhanced_framing}\n" \ + f"MST: {self.mst_enabled}\n" \ + f"DSC: {self.dsc_enabled}\n" \ + f"Link Encoding: {self.link_encoding.name}\n" \ + f"Scrambling: {self.scrambling_enabled}\n" \ + f"SSC: {self.ssc_enabled}\n" \ + f"FEC: {self.fec_enabled}\n" \ + f"ILA: {self.ila}\n" \ + f"EQ_ILA: {self.eq_ila}\n" \ + f"CDS_ILA: {self.cds_ila}\n" \ + f"LT_FAIL: {self.lt_fail}\n\n" \ + f"Lane status:\n{lane_status_str}\n" \ + f"Stream info:\n{stream_info_str}\n" \ + f"VCP status:\n{vcp_table_str}\n" diff --git a/UniTAP/dev/ports/modules/link/dp/link_rx_types.py b/UniTAP/dev/ports/modules/link/dp/link_rx_types.py new file mode 100644 index 0000000..91d465e --- /dev/null +++ b/UniTAP/dev/ports/modules/link/dp/link_rx_types.py @@ -0,0 +1,177 @@ +from typing import TypeVar +from enum import IntEnum + + +class LinkCapabilities: + """ + Class `LinkCapabilities` describes capabilities of DP link. + """ + def __init__(self): + self.max_lane = None + self.bit_rate = None + self.dp_128_132_bitrates = None + self.override_10g = None + self.old_dp_2_0_lt = None + self.force_cable_status_to_plugged = None + self.mst = None + self.ss_sbm = None + self.fec = None + self.tps4 = None + self.tps3 = None + self.dsc = None + self.mst_sink_count = None + + def __str__(self) -> str: + return f"Max Lanes - {self.max_lane}\n" \ + f"Max Bitrate - {self.bit_rate}\n" \ + f"DP (128b/132b) Supported Bitrates - {self.dp_128_132_bitrates}\n" \ + f"Instead of 10 Gbps, use - {self.override_10g}\n" \ + f"Old DP 2.0 LT - {self.old_dp_2_0_lt}\n" \ + f"Force Cable Status to Plugged - {self.force_cable_status_to_plugged}\n" \ + f"MST - {self.mst} (Sink Count - {self.mst_sink_count})\n" \ + f"SS SBM - {self.ss_sbm}\n" \ + f"FEC - {self.fec}\n" \ + f"TPS4 - {self.tps4}\n" \ + f"TPS3 - {self.tps3}\n" \ + f"DSC - {self.dsc}\n" + + +class LinkEDPCapabilities: + """ + Class `LinkEDPCapabilities` describes capabilities of eDP link. + """ + + def __init__(self): + self.max_lane = None + self.eDp_cur_rate = None + self.eDp_supported_rates = None + self.eDp_aux_preamble = None + self.eDp_support = None + + def __str__(self) -> str: + return f"Max Lanes - {self.max_lane}\n" \ + f"eDP rate - {self.eDp_cur_rate}\n" \ + f"eDP supported rates - {self.eDp_supported_rates}\n" \ + f"eDP AUX preamble - {self.eDp_aux_preamble}\n" \ + f"eDp Support - {self.eDp_support}" + + +DisplayPortLinkCaps = TypeVar("DisplayPortLinkCaps", + LinkEDPCapabilities, + LinkCapabilities) + + +class CableCapabilitiesEnum(IntEnum): + Unknown = 0, + DP40 = 1 + DP54 = 2 + DP80 = 3 + + def __str__(self) -> str: + return self.name + + +class RoutedLTConfig: + """ + Class `RoutedLTConfig` describes configuration fields for Routed LinkTraining. + """ + def __init__(self): + self.__is128b132b = False + self.__is_old_dp20_lt = False + self.__vs = 0 + self.__pe = 0 + self.__ffe = 0 + self.__link_bw = 0 + self.__lane_count = 0 + + @property + def is128b132b(self) -> bool: + return self.__is128b132b + + @is128b132b.setter + def is128b132b(self, value: bool = False): + self.__is128b132b = value + + @property + def is_old_dp20_lt(self) -> bool: + return self.__is_old_dp20_lt + + @is_old_dp20_lt.setter + def is_old_dp20_lt(self, value: bool = False): + self.__is_old_dp20_lt = value + + @property + def vs(self) -> int: + return self.__vs + + @vs.setter + def vs(self, value: int): + if not (0 <= value < 3): + raise ValueError(f"VS cannot be less than 0 and more than 3") + self.__vs = value + + @property + def pe(self) -> int: + return self.__pe + + @pe.setter + def pe(self, value: int): + if not (0 <= value <= 0x3): + raise ValueError(f"PE cannot be less than 0 and more than 0x3") + self.__pe = value + + @property + def ffe(self) -> int: + return self.__ffe + + @ffe.setter + def ffe(self, value: int): + if not (0 <= value <= 0xF): + raise ValueError(f"FFE cannot be less than 0 and more than 0xF") + self.__ffe = value + + @property + def link_bw(self) -> int: + return self.__link_bw + + @link_bw.setter + def link_bw(self, value: int): + if not (0 <= value <= 0xFF): + raise ValueError(f"link_bw cannot be less than 0 and more than 0xFF") + self.__link_bw = value + + @property + def lane_count(self) -> int: + return self.__lane_count + + @lane_count.setter + def lane_count(self, value: int): + if not (0 <= value <= 0xFF): + raise ValueError(f"lane_count cannot be less than 0 and more than 0xFF") + self.__lane_count = value + + def int_value(self) -> int: + val = (self.ffe << 8) if self.is128b132b else ((self.vs << 8) | (self.pe << 10)) + return self.is128b132b | (self.__is_old_dp20_lt << 1) | val | (self.link_bw << 16) | (self.lane_count << 24) + + +class RoutedLTStatus: + + __StateToStr = {0: "Not started", 1: "In progress", 2: "Finished", 3: "Incorrect"} + + def __init__(self, enabled: bool, dp20_old_lt: bool, state: int, success: bool, step: int): + self.enabled = enabled + self.dp20_old_lt = dp20_old_lt + self.state = state + self.success = success + self.step = step + + def __str__(self) -> str: + + return (f"Is Enabled: {'yes' if self.enabled else 'no'}\n" + f"Is DP 20 old LT: {'yes' if self.dp20_old_lt else 'no'}\n" + f"State: {self.__StateToStr.get(self.state)}\n" + f"Result: {'Successful' if self.success else 'Unsuccessful'}\n" + f"Steps: {self.step}") + + diff --git a/UniTAP/dev/ports/modules/link/dp/link_status_common.py b/UniTAP/dev/ports/modules/link/dp/link_status_common.py new file mode 100644 index 0000000..50d6827 --- /dev/null +++ b/UniTAP/dev/ports/modules/link/dp/link_status_common.py @@ -0,0 +1,173 @@ +from enum import IntEnum +from UniTAP.common.video_mode import VideoMode + + +class VCPStatus: + """ + Class `VCPStatus` describes МСЗ status. Contains following information: + - Port number. + - Stream ID. + - Requested PBN. + - Allocated PBN. + - Number of slots allocated for VC. + - Number of first time slot allocated for VC. + """ + def __init__(self): + self.port_number = 0 + self.stream_id = 0 + self.req_pbn = 0 + self.alloc_pbn = 0 + self.first_slot = 0 + self.slot_num = 0 + + def __eq__(self, other): + return self.port_number == other.port_number and \ + self.stream_id == other.stream_id and \ + self.req_pbn == other.req_pbn and \ + self.alloc_pbn == other.alloc_pbn and \ + self.first_slot == other.first_slot and \ + self.slot_num == other.slot_num + + def __str__(self) -> str: + return f"Port # - {self.port_number}\n" \ + f"SID - {self.stream_id}\n" \ + f"REQ PBN - {self.req_pbn}\n" \ + f"Alloc PBN - {self.alloc_pbn}\n" \ + f"First Slot - {self.first_slot}\n" \ + f"Slot num - {self.slot_num}\n" + + +class DpLinkEncoding(IntEnum): + """ + Class `DpLinkEncoding` contains all possible variants of DP link encoding. + """ + LE_NONE = 0 + LE_8b10b = 1 + LE_128b132b = 2 + + def __str__(self): + if self.value == DpLinkEncoding.LE_NONE: + return "None" + elif self.value == DpLinkEncoding.LE_8b10b: + return "8b/10b" + elif self.value == DpLinkEncoding.LE_128b132b: + return "128b/132b" + else: + return "Invalid" + + +class DpLinkTrainingResult(IntEnum): + """ + Class `DpLinkTrainingResult` contains all possible variants of Link training results. + """ + LTR_NOT_STARTED = 0 + LTR_IN_PROGRESS = 1 + LTR_FAIL = 2 + LTR_SUCCESS = 3 + + +class LaneStatus: + """ + Class `LaneStatus` describes lane status. Contains following information: + - CR state. + - SL state. + - EQ state. + - Voltage swing value. + - Pre Emphasis value. + - FFE preset value. + - Error count. + """ + def __init__(self): + self.cr = False + self.sl = False + self.eq = False + self.voltage_swing = 0 + self.pre_emphasis = 0 + self.ffe_preset = 0 + self.error_count = 0 + + def __eq__(self, other) -> bool: + return self.cr == other.cr and \ + self.sl == other.sl and \ + self.eq == other.eq and \ + self.voltage_swing == other.voltage_swing and \ + self.pre_emphasis == other.pre_emphasis and \ + self.ffe_preset == other.ffe_preset and \ + self.error_count == other.error_count + + def __str__(self) -> str: + return f"CR - {bool(self.cr)}\nSL - {bool(self.sl)}\nEQ - {bool(self.eq)}\n" \ + f"Voltage Swing - {self.voltage_swing}\n" \ + f"Pre Emphasis - {self.pre_emphasis}\nFFE Preset - {self.ffe_preset}\nError count - {self.error_count}\n" + + +class SdpCrc16: + """ + Class `SdpCrc16` describes SDP CRC16 errors on the stream. It contains 'State' - enabled or disabled, and errors - + count of the errors on the stream. + """ + def __init__(self): + self.state = False + self.errors = 0 + + def __eq__(self, other) -> bool: + return self.state == other.state and self.errors == other.errors + + def __str__(self) -> str: + return f"State: {'Enabled' if self.state else 'Disabled'}\n" \ + f"Errors: {hex(self.errors)}" + + +class StreamStatus: + """ + Class `StreamStatus` describes stream status. Contains following information: + - Video mode `VideoMode`. + - CRC value of stream. + - DSC CRC value of stream. + - SDP CRC16 error of stream + """ + def __init__(self): + self.video_mode = VideoMode() + self.crc = [0, 0, 0] + self.dsc_crc = [0, 0, 0] + self.sdp_crc16 = SdpCrc16() + + def __str__(self) -> str: + return f"Video mode:\n{self.video_mode.__str__()}\n" \ + f"CRC: r - {self.crc[0]}, g - {self.crc[1]}, b - {self.crc[2]}\n" \ + f"DSC CRC: r - {self.dsc_crc[0]}, g - {self.dsc_crc[1]}, b - {self.dsc_crc[2]}\n" + + +class StreamStatusDP(StreamStatus): + """ + The `StreamStatusDP` class inherited from the `StreamStatus` class and contains all the functionality. + - MVID + - NVID + - VFREQ `vfreq` + """ + def __init__(self): + super().__init__() + self.mvid = 0 + self.nvid = 0 + + @property + def vfreq(self) -> int: + """ + Return value of VFREQ. + + Returns: + object of `int` type + + """ + return ((self.mvid & 0xFFFFFF) << 24) | (self.nvid & 0xFFFFFF) + + def __str__(self) -> str: + return f"Video mode:\n{self.video_mode.__str__()}\n" \ + f"CRC: r - {self.crc[0]}, g - {self.crc[1]}, b - {self.crc[2]}\n" \ + f"DSC CRC: r - {self.dsc_crc[0]}, g - {self.dsc_crc[1]}, b - {self.dsc_crc[2]}\n" \ + f"MVID / NVID - {self.mvid:06X} / {self.nvid:06X}\n" \ + f"VFREQ - {self.vfreq}\n" + + +DP21_LinkRate = {1: 10.0, 2: 20.0, 4: 13.5} +DP21_LinkRateRev = {10.0: 1, 20.0: 2, 13.5: 4} diff --git a/UniTAP/dev/ports/modules/link/dp/link_tx.py b/UniTAP/dev/ports/modules/link/dp/link_tx.py new file mode 100644 index 0000000..206df95 --- /dev/null +++ b/UniTAP/dev/ports/modules/link/dp/link_tx.py @@ -0,0 +1,279 @@ +from warnings import warn + +from UniTAP.libs.lib_tsi import * +from UniTAP.dev.ports.modules.link.dp.link_status_common import DpLinkTrainingResult +from UniTAP.libs.lib_tsi.tsi_io import PortIO +from UniTAP.utils.function_wrapper import * +from .link_tx_status import LinkDisplayPortStatusSource +from .link_tx_config import LinkDisplayPortConfig +from .link_tx_force_config import LinkDisplayPortForceConfig +from .private_link_tx_types import * +from UniTAP.dev.ports.modules.dpcd import DPCDRegisters +from .link_tx_types import DPLinkPattern, DPOutLinkMode +from .dp_cable_info import DpCableInfo, CableCapabilitiesEnum + + +class LinkDisplayPortTx: + """ + Class `LinkDisplayPortTx` contains information about DP link. + - Read link status `status`. + - Configure and read link configuration `config`. + - Get maximum stream count `max_stream_count`. + - Do link training `link_training`. + - Get last result of link training `last_lt_result`. + - Read and write scrambler seed value `scrambler_seed`. + - Set `set_override_voltage` and get `get_override_voltage` override voltage. + - Set `set_override_pre_emp` and get `get_override_pre_emp` override pre-emphasis. + - Set and get override FFE presets `override_ffe_presets`. + - Set and get link pattern `link_pattern` 'set_link_pattern'. + - Set and get force link config `LinkDisplayPortForceConfig`. + """ + def __init__(self, port_io: PortIO, dpcd: DPCDRegisters, hw_caps: DPTXHWCaps): + self.__io = port_io + self.__HW_CAPS = hw_caps + self.__status = LinkDisplayPortStatusSource(self.__io, self.__HW_CAPS, dpcd) + self.__config = LinkDisplayPortConfig(self.__io, self.__HW_CAPS) + self.__force_config = LinkDisplayPortForceConfig(self.__io, self.__HW_CAPS) + self.__cable_info = DpCableInfo(port_io, TSI_DPTX_CABLE_ATTRIBUTES_R) + + @property + def status(self) -> LinkDisplayPortStatusSource: + """ + Returns object of class `LinkDisplayPortStatusSource` for working with link status. + + Returns: + object of `LinkDisplayPortStatusSource` type + """ + return self.__status + + @property + def config(self) -> LinkDisplayPortConfig: + """ + Returns object of class `LinkDisplayPortConfig` for working with link configuration. + + Returns: + object of `LinkDisplayPortConfig` type + """ + return self.__config + + @property + def force_config(self) -> LinkDisplayPortForceConfig: + """ + Returns object of class `LinkDisplayPortForceConfig` for working with link configuration. + + Returns: + object of `LinkDisplayPortForceConfig` type + """ + return self.__force_config + + @property + def max_stream_count(self): + """ + Returns maximum supported stream count. + + Returns: + object of int type + """ + return self.__HW_CAPS.mst_stream_count + + def start_link_training(self) -> bool: + """ + Make link training. Returns 'True' state if link training was success, 'False' - if not. + + Returns: + object of bool type + """ + def is_link_training_success(link: LinkDisplayPortTx): + return link.last_lt_result() == DpLinkTrainingResult.LTR_SUCCESS + + self.__io.set(TSI_W_DPTX_COMMAND, 1, c_uint32) + + return function_scheduler(is_link_training_success, self, interval=0.1, timeout=5) + + def last_lt_result(self) -> DpLinkTrainingResult: + """ + Returns last result of link training. + + Returns: + object of `DpLinkTrainingResult` type + """ + _, value = self.__io.get(TSI_DPTX_LT_RESULT_R, c_uint) + return DpLinkTrainingResult(value) + + @property + def scrambler_seed(self) -> int: + """ + Returns scrambler seed value. + + Returns: + object of `int` type + """ + if not self.__HW_CAPS.scrambler_seed: + warnings.warn("Scrambler Seed is not supported.") + return 0 + + return self.__io.get(TSI_DPTX_SCR_SEED, c_uint32)[1] + + @scrambler_seed.setter + def scrambler_seed(self, value: int = 0): + """ + Write new value to scrambler seed + + Args: + value (int) - new scrambler seed value + """ + if not self.__HW_CAPS.scrambler_seed: + warnings.warn("Scrambler Seed is not supported.") + return + if self.config._read_lt_features().auto_seed is not None and not self.config._read_lt_features().auto_seed: + self.__io.set(TSI_DPTX_SCR_SEED, value, c_uint32) + else: + warnings.warn("AutoSeed has already enabled. Transferred value will not be written") + + def get_override_voltage(self, stream_index: int) -> int: + """ + Returns override voltage of selected stream. + + Args: + stream_index (int) - number of selected stream + + Returns: + object of `int` type + """ + return (self.__get_override_voltage() >> stream_index) & 0x3 + + def set_override_voltage(self, stream_index: int, value: int): + """ + Returns override voltage of selected stream. + + Args: + stream_index (int) - number of selected stream + value (int) - new override voltage value + """ + if stream_index < self.status.mst_stream_count: + raise ValueError(f"Selected stream {stream_index} is not available. " + f"Available stream number: {self.status.mst_stream_count}") + + if not (0 <= value <= 3): + raise ValueError(f"Incorrect input value {value}. Available range: 0-{self.status.lane_count}") + + old_value = self.__get_override_voltage() + old_value &= ~(3 << (stream_index * 8)) + old_value |= (value << (stream_index * 8)) + self.__io.set(TSI_DPTX_OVERRIDE_VOLTAGE_SWING, old_value, c_uint32) + + def get_override_pre_emp(self, stream_index: int) -> int: + """ + Returns override pre-emphasis of selected stream. + + Args: + stream_index (int) - number of selected stream + + Returns: + object of `int` type + """ + return (self.__get_override_pre_emp() >> stream_index) & 0x3 + + def set_override_pre_emp(self, stream_index: int, value: int): + """ + Returns override pre-emphasis of selected stream. + + Args: + stream_index (int) - number of selected stream + value (int) - new override pre-emphasis value + """ + if stream_index < self.status.mst_stream_count: + raise ValueError(f"Selected stream {stream_index} is not available. " + f"Available stream number: {self.status.mst_stream_count}") + + if not (0 <= value <= 3): + raise ValueError(f"Incorrect input value {value}. Available range: 0-{self.status.lane_count}") + + old_value = self.__get_override_pre_emp() + old_value &= ~(3 << (stream_index * 8)) + old_value |= (value << (stream_index * 8)) + self.__io.set(TSI_DPTX_OVERRIDE_PRE_EMPHASIS, old_value, c_uint32) + + @property + def override_ffe_presets(self) -> list: + """ + Returns override FFE presets values. + + Returns: + object of `list` type + """ + if not self.__HW_CAPS.dp2_custom_rates: + warnings.warn("FFE presets does not support.") + return [] + return self.__io.get(TSI_DP2TX_OUT_FFE, c_uint8, 4)[1] + + @override_ffe_presets.setter + def override_ffe_presets(self, value: List[int]): + """ + Returns override FFE presets. + + Args: + value list[int] - new values for override FFE presets. + """ + if not self.__HW_CAPS.dp2_custom_rates: + warnings.warn("FFE presets does not support.") + return + new_value = value[0] | (value[0] << 8) | (value[0] << 16) | (value[0] << 32) + self.__io.set(TSI_DP2TX_OUT_FFE, new_value) + + def __get_override_voltage(self) -> int: + return self.__io.get(TSI_DPTX_OVERRIDE_VOLTAGE_SWING, c_uint32)[1] + + def __get_override_pre_emp(self) -> int: + return self.__io.get(TSI_DPTX_OVERRIDE_PRE_EMPHASIS, c_uint32)[1] + + @property + def link_pattern(self) -> DPLinkPattern: + """ + Returns current DP link pattern `DPLinkPattern`. + + Returns: + object of `DPLinkPattern` type + """ + return DPLinkPattern(self.__io.get(TSI_DPTX_OUTPUT_PATTERN, c_uint32)[1]) + + def set_link_pattern(self, pattern: DPLinkPattern, additional_param: int = 1): + """ + Write DP link pattern value `DPLinkPattern`. + + Args: + pattern (DPLinkPattern) - new pattern value. + additional_param (int) + """ + if not self.__HW_CAPS.dp2_custom_rates and pattern not in [e.value for e in DPLinkPattern]: + raise ValueError(f"Current pattern {pattern.name} is not supported") + self.__io.set(TSI_DPTX_OUTPUT_PATTERN, pattern.value, c_uint32) + + if pattern == DPLinkPattern.LinkSquarePattern: + self.__io.set(TSI_DPTX_SQUARE_PATTERN_NUMBER, additional_param, c_uint32) + + def set_force_link_mode(self, link_mode: DPOutLinkMode): + self.__io.set(TSI_DPTX_OUT_LINK_MODE, link_mode.value, c_uint32) + + def get_force_link_mode(self) -> DPOutLinkMode: + return DPOutLinkMode(self.__io.get(TSI_DPTX_OUT_LINK_MODE, c_uint32)[1]) + + def cable_rx_type(self) -> CableCapabilitiesEnum: + """ + Get cable type from the RX side. + + Returns: + object of `CableCapabilitiesEnum` type + """ + return self.__cable_info.get_another_port_cable_type() + + def cable_tx_type(self) -> CableCapabilitiesEnum: + """ + Get cable type from the TX side. + + Returns: + object of `CableCapabilitiesEnum` type + """ + warn("TX cable information is no longer available on the TX port.", DeprecationWarning,2) + + return CableCapabilitiesEnum.Unknown diff --git a/UniTAP/dev/ports/modules/link/dp/link_tx_config.py b/UniTAP/dev/ports/modules/link/dp/link_tx_config.py new file mode 100644 index 0000000..e5009e9 --- /dev/null +++ b/UniTAP/dev/ports/modules/link/dp/link_tx_config.py @@ -0,0 +1,360 @@ +from typing import Optional, Union, Type +from UniTAP.libs.lib_tsi import PortIO +from UniTAP.libs.lib_tsi.tsi_private_types import * +from .link_tx_types import * +from .private_link_tx_types import * +from .link_status_common import * + + +class LinkDisplayPortConfig: + """ + Class `LinkDisplayPortConfig` allows settings link configuration on Source (TX - transmitter) side. + - Set configuration `set`. + - Get current configuration on link `get`. + """ + def __init__(self, port_io: PortIO, caps: DPTXHWCaps): + self.__io = port_io + self.__caps = caps + self.__dp2_custom_rate = self.__get_dp2_custom_rates() + + def set(self, config: DisplayPortLinkConfig): + """ + Write new configuration on DP link. + + Args: + config (`DisplayPortLinkConfig`) - `LinkConfig.DP8b10b`, `LinkConfig.DP128b132b` or `LinkConfig.eDP`. + """ + if isinstance(config, LinkConfig.DP8b10b): + self.__set_lane_count_dp14(config.lane_count) + self.__set_bit_rate_14(config.bit_rate) + self.__set_lt_features(config) + self.__set_config_ssc(config.ssc) + self.__enable_mst(config.mst) + self.__set_mst_channel_count(config.mst_stream_count) + self.__try_fec_after_lt(config.fec) + self.__set_post_lt_features(config) + self.__sdp_control(config) + elif isinstance(config, LinkConfig.DP128b132b): + self.__set_lane_count_dp21(config.lane_count) + self.__set_bit_rate_21(config.bit_rate) + self.__set_lt_features(config) + self.__set_lttpr(config.lttpr) + self.__set_config_ssc(config.ssc) + self.__set_post_lt_features(config) + self.__enable_mst(config.mst) + self.__set_mst_channel_count(config.mst_stream_count) + self.__sdp_control(config) + elif isinstance(config, LinkConfig.eDP): + self.__set_lane_count_dp14(config.lane_count) + self.__set_lt_features(config) + self.__set_edp_sel_rate(config.eDp_cur_rate) + else: + raise TypeError("Incorrect DisplayPortLinkConfig format!") + + def get(self, config_type: Union[Type[DisplayPortLinkConfig], None] = None) -> DisplayPortLinkConfig: + """ + Returns current DP source link configuration. + + Returns: + object of `DisplayPortLinkConfig` type + """ + if config_type is not None and issubclass(config_type, LinkConfig.eDP): + lt_features = self._read_lt_features() + link_status = LinkConfig.eDP() + link_status.lane_count = self.__get_lane_count_dp14() + link_status.force_edp = lt_features.force_edp + link_status.eDp_cur_rate = self.__get_edp_sel_rate() + link_status.eDp_supported_rates = self.__get_edp_caps_rates() + link_status.eDp_aux_preamble = lt_features.edp_aux_preamble + return link_status + elif config_type is not None and issubclass(config_type, LinkConfig.DP8b10b): + return self.__fill_dp14_config() + elif config_type is not None and issubclass(config_type, LinkConfig.DP128b132b) or \ + self.__link_encoding() == DpLinkEncoding.LE_128b132b: + return self.__fill_dp21_config() + elif config_type is None and self.__link_encoding() == DpLinkEncoding.LE_8b10b: + return self.__fill_dp14_config() + elif config_type is None and \ + (self.__caps.support_10gbps or self.__caps.support_20gbps or self.__caps.support_13_5gbps) and \ + self.__link_encoding() == DpLinkEncoding.LE_128b132b: + return self.__fill_dp21_config() + else: + raise ValueError('Incorrect link encoding type') + + def __fill_dp14_config(self) -> LinkConfig.DP8b10b: + lt_features = self._read_lt_features() + post_lt_features = self.__get_post_lt_features() + link_status = LinkConfig.DP8b10b() + link_status.lane_count = self.__get_lane_count_dp14() + link_status.bit_rate = self.__get_bit_rate_14() + link_status.mst = self.__mst_enabled() + link_status.mst_stream_count = self.__mst_stream_count() + link_status.enhanced_framing_mode = lt_features.framing + link_status.auto_seed = lt_features.auto_seed + link_status.ssc = self.__get_config_ssc() + link_status.fec = self.__fec_after_lt_state() + link_status.force_edid_timings_after_lt = post_lt_features.force_edid_timings + link_status.adaptive_sync_auto_enable = post_lt_features.as_auto_enable + link_status.split_sdp = self.__read_sdp_control().dp_split_sdp != 0 if self.__caps.sdp_split else False + return link_status + + def __fill_dp21_config(self) -> LinkConfig.DP128b132b: + link_status = LinkConfig.DP128b132b() + link_status.lane_count = self.__get_lane_count_dp21() + if self.__check_custom_bit_rate() != 0: + link_status.bit_rate = self.__check_custom_bit_rate() + else: + link_status.bit_rate = self.__get_bit_rate_21() + lt_features = self._read_lt_features() + post_lt_features = self.__get_post_lt_features() + link_status.force_dp_128_132 = lt_features.force_dp2 + link_status.enhanced_framing_mode = lt_features.framing + link_status.max_link_bandwidth_supported = lt_features.max_link_bw_policy + link_status.old_dp2_lt = lt_features.old_dp2_lt + link_status.lttpr = self.__lttpr_active() + link_status.try_dp_128_132 = lt_features.try_dp2 + link_status.auto_seed = lt_features.auto_seed + link_status.ssc = self.__get_config_ssc() + link_status.force_edid_timings_after_lt = post_lt_features.force_edid_timings + link_status.adaptive_sync_auto_enable = post_lt_features.as_auto_enable + link_status.mst = self.__mst_enabled() + link_status.mst_stream_count = self.__mst_stream_count() + link_status.crc_16 = self.__read_sdp_control().dp2_crc_sdp != 0 if self.__caps.dp2_sdp_crc else False + return link_status + + def _read_lt_features(self) -> LTFeatures: + return self.__io.get(TSI_DPTX_LT_FEATURES, LTFeatures)[1] + + def __get_post_lt_features(self) -> PostLtFeatures: + return self.__io.get(TSI_DPTX_POST_LT_FEATURES, PostLtFeatures)[1] + + def __set_post_lt_features(self, config: DisplayPortLinkConfig): + post_lt_features = self.__get_post_lt_features() + if config.force_edid_timings_after_lt is not None and self.__caps.edid_parser: + post_lt_features.force_edid_timings = config.force_edid_timings_after_lt + if config.adaptive_sync_auto_enable is not None and self.__caps.adaptive_sync: + post_lt_features.as_auto_enable = config.adaptive_sync_auto_enable + self.__io.set(TSI_DPTX_POST_LT_FEATURES, post_lt_features.value(), c_uint32) + + def __get_dp2_custom_rates(self) -> list: + rates = self.__io.get(TSI_DP2TX_CUSTOM_RATE_CAPS_R, c_uint32, 32)[1] + custom_rates = [] + for i in range(len(rates)): + custom_rates.append(float(rates[i]) * 200000 / 1000000000) + + return custom_rates + + def __set_lane_count_dp14(self, lane_count: Optional[int]): + if lane_count is None: + return + if lane_count not in [1, 2, 4]: + raise ValueError(f"Incorrect lane count number {lane_count}. Must be from list: 1, 2, 4") + self.__io.set(TSI_DPTX_LINK_CFG_LANES, lane_count, c_uint32) + + def __get_lane_count_dp14(self) -> int: + return self.__io.get(TSI_DPTX_LINK_CFG_LANES)[1] + + def __set_lane_count_dp21(self, lane_count: Optional[int]): + if lane_count is None: + return + if lane_count not in [1, 2, 4]: + raise ValueError(f"Incorrect lane count number {lane_count}. Must be from list: 1, 2, 4") + self.__io.set(TSI_DP2TX_LT_SLC, lane_count, c_uint32) + + def __get_lane_count_dp21(self) -> int: + return self.__io.get(TSI_DP2TX_LT_SLC)[1] + + def __set_bit_rate_14(self, bit_rate: Optional[float]): + if bit_rate is None: + return + if int(round(bit_rate / 0.27)) > self.__caps.max_link_rate: + raise ValueError(f"Following 8b/10b bit rate {bit_rate} is not supported on device. " + f"Max supported link rate {round(self.__caps.max_link_rate * 0.27, 2)}") + self.__io.set(TSI_DPTX_LINK_CFG_BIT_RATE, int(round(bit_rate / 0.27)), c_uint32) + + def __get_bit_rate_14(self) -> float: + return round(self.__io.get(TSI_DPTX_LINK_CFG_BIT_RATE)[1] * 0.27, 2) + + def __set_bit_rate_21(self, bit_rate: Optional[Union[float, int]]): + if bit_rate is None: + return + if bit_rate in self.__dp2_custom_rate: + if not self.__caps.support_10gbps: + raise ValueError(f"Following bit rate {bit_rate} is not supported on device.") + else: + res = self.__io.get(TSI_DP2TX_CUSTOM_RATE_MAP, c_uint32, 3)[1] + self.__io.set(TSI_DP2TX_CUSTOM_RATE_MAP, (int(bit_rate * 1000000000 / 200000), res[1], res[2]), + c_uint32, 3) + elif (bit_rate == 10.0 and not self.__caps.support_10gbps) or \ + (bit_rate == 13.5 and not self.__caps.support_13_5gbps) or \ + (bit_rate == 20.0 and not self.__caps.support_20gbps): + raise ValueError(f"Following bit rate {bit_rate} is not supported on device.") + self.__io.set(TSI_DP2TX_LT_SBR, DP21_LinkRateRev.get(bit_rate), c_uint32) + + def __get_bit_rate_21(self) -> float: + return DP21_LinkRate.get(self.__io.get(TSI_DP2TX_LT_SBR)[1]) + + def __support_ssc(self) -> bool: + return (self.__io.get(TSI_DPTX_DOWNSPREAD_STATUS_R, c_uint32)[1] >> 1) & 1 != 0 + + def __set_config_ssc(self, ssc_conf: Optional[SSCConfig]): + if ssc_conf is None: + return + if not self.__support_ssc(): + raise ValueError(f"SSC is not supported on the device.") + self.__io.set(TSI_DPTX_DOWNSPREAD_CONTROL, int(ssc_conf.enabled), c_uint32) + if ssc_conf.enabled: + if not (1 <= ssc_conf.amplitude <= 50): + raise ValueError(f"Incorrect amplitude value {ssc_conf.amplitude}. Must be from range: 0.1 - 0.5") + self.__io.set(TSI_DPTX_DOWNSPREAD_AMP, ssc_conf.amplitude, c_uint32) + if not (30000 <= ssc_conf.frequency <= 63000): + raise ValueError(f"Incorrect frequency value {ssc_conf.frequency}. Must be from range: 30000 - 63000") + self.__io.set(TSI_DPTX_DOWNSPREAD_FREQ, ssc_conf.frequency, c_uint32) + + def __get_config_ssc(self) -> SSCConfig: + if not self.__support_ssc(): + return None + ssc_conf = SSCConfig() + ssc_conf.enabled = (self.__io.get(TSI_DPTX_DOWNSPREAD_STATUS_R)[1] & 1) != 0 + ssc_conf.frequency = self.__io.get(TSI_DPTX_DOWNSPREAD_FREQ)[1] + ssc_conf.amplitude = self.__io.get(TSI_DPTX_DOWNSPREAD_AMP)[1] + return ssc_conf + + def __dp2_support_rates(self) -> bool: + return self.__caps.support_10gbps or self.__caps.support_13_5gbps or self.__caps.support_20gbps + + def __set_lttpr(self, enable: Optional[bool]): + if enable is None: + return + if not self.__dp2_support_rates(): + raise ValueError(f"LTTPR is not supported on the device.") + self.__io.set(TSI_DPTX_LTTPR_CONTROL, int(enable)) + + def __set_lt_features(self, config: Optional[DisplayPortLinkConfig]): + if config is None: + return + res = self._read_lt_features() + if isinstance(config, LinkConfig.DP8b10b): + if config.enhanced_framing_mode is not None: + res.framing = int(config.enhanced_framing_mode) + if config.auto_seed is not None: + res.auto_seed = int(config.auto_seed) + if self.__caps.support_10gbps or self.__caps.support_20gbps or self.__caps.support_13_5gbps: + res.try_dp2 = 0 + res.try_edp = 0 + elif isinstance(config, LinkConfig.DP128b132b): + if not self.__dp2_support_rates(): + raise ValueError(f"DP 2.1 is not supported on the device.") + if config.try_dp_128_132 is not None: + res.try_dp2 = int(config.try_dp_128_132) + if config.force_dp_128_132 is not None: + res.force_dp2 = int(config.force_dp_128_132) + if config.old_dp2_lt is not None: + res.old_dp2_lt = int(config.old_dp2_lt) + if config.enhanced_framing_mode is not None: + res.framing = int(config.enhanced_framing_mode) + if config.auto_seed is not None: + res.auto_seed = int(config.auto_seed) + if config.max_link_bandwidth_supported is not None: + res.max_link_bw_policy = int(config.max_link_bandwidth_supported) + res.try_edp = 0 + elif isinstance(config, LinkConfig.eDP): + if self.__caps.edp: + if config.force_edp is not None: + res.force_edp = config.force_edp + if config.eDp_aux_preamble is not None: + res.edp_aux_preamble = config.eDp_aux_preamble + res.try_edp = 1 + elif not self.__caps.edp: + raise ValueError("EDP is not supported.") + else: + raise TypeError("Incorrect DisplayPortLinkConfig format!") + + self.__io.set(TSI_DPTX_LT_FEATURES, res.value(), c_uint32) + + def __enable_mst(self, enable: Optional[bool]): + if enable is None: + return + self.__io.set(TSI_DPTX_COMMAND_W, 3 if enable else 4, c_uint32) + + def __set_mst_channel_count(self, count: Optional[int]): + if count is None: + return + if not (0 <= count <= self.__caps.mst_stream_count): + raise ValueError(f"Incorrect stream count {count}. Available stream count {self.__caps.mst_stream_count}") + self.__io.set(TSI_PG_ENABLED_STREAM_COUNT, count, c_uint32) + + def __link_encoding(self) -> DpLinkEncoding: + result, value = self.__io.get(TSI_DPTX_LINK_MODE_R, c_uint) + + if value == 0: + return DpLinkEncoding.LE_8b10b + else: + return DpLinkEncoding.LE_128b132b + + def __mst_enabled(self) -> bool: + return (self.__io.get(TSI_DPTX_LINK_STATUS_BITS_R)[1] & (1 << 30)) != 0 + + def __mst_stream_count(self) -> int: + result, mst_status = self.__io.get(TSI_DPTX_MST_STATUS_R, c_uint) + return (mst_status >> 8) & 0xFF + + def __lttpr_active(self) -> Optional[bool]: + if self.__link_encoding == DpLinkEncoding.LE_128b132b: + return (self.__io.get(TSI_DPTX_LTTPR_CONTROL, c_uint32)[1] & 1) != 0 + else: + return None + + def __check_custom_bit_rate(self) -> float: + res = self.__io.get(TSI_DP2TX_CUSTOM_RATE_MAP, c_uint32, 3)[1] + for i in range(3): + if res[i] != 0: + return res[i] * 200000 / 1000000000 + return 0 + + def __try_fec_after_lt(self, enable: bool): + result = self.__io.get(TSI_DPTX_FEC_CTRL, c_int) + val = result[1] + if enable: + val |= 0x2 + else: + val &= ~0x2 + self.__io.set(TSI_DPTX_FEC_CTRL, val) + + def __fec_after_lt_state(self) -> bool: + return (self.__io.get(TSI_DPTX_FEC_CTRL, c_int)[1] & 0x2) != 0 + + def __sdp_control(self, config: Optional[DisplayPortLinkConfig]): + if config is None: + return + sdp_control = self.__read_sdp_control() + if self.__caps.dp2_sdp_crc and isinstance(config, LinkConfig.DP128b132b) and config.crc_16 is not None: + sdp_control.dp2_crc_sdp = config.crc_16 + if self.__caps.sdp_split and isinstance(config, LinkConfig.DP8b10b) and config.split_sdp is not None: + sdp_control.dp_split_sdp = config.split_sdp + self.__write_sdp_control(sdp_control) + + def __write_sdp_control(self, sdp_control: SdpControl): + self.__io.set(TSI_DPTX_SDP_CTRL, sdp_control.value()) + + def __read_sdp_control(self) -> SdpControl: + return self.__io.get(TSI_DPTX_SDP_CTRL, SdpControl)[1] + + def __set_edp_sel_rate(self, rate: float): + if self.__caps.edp and rate in self.__get_edp_caps_rates(): + self.__io.set(TSI_DPTX_EDP_LT_SBR, int(rate / 0.0002), c_uint32) + elif not self.__caps.edp: + raise ValueError("EDP is not supported.") + + def __get_edp_sel_rate(self) -> int: + if self.__caps.edp: + return self.__io.get(TSI_DPTX_EDP_LT_SBR)[1] * 0.0002 + + def __get_edp_caps_rates(self) -> List[float]: + if self.__caps.edp: + rates = self.__io.get(TSI_DPTX_EXTRA_DP14_RATES, data_type=c_uint32, data_count=8)[1] + new_rates = [] + for rate in rates: + if rate > 0: + new_rates.append(rate * 0.27) + return new_rates diff --git a/UniTAP/dev/ports/modules/link/dp/link_tx_force_config.py b/UniTAP/dev/ports/modules/link/dp/link_tx_force_config.py new file mode 100644 index 0000000..c26b069 --- /dev/null +++ b/UniTAP/dev/ports/modules/link/dp/link_tx_force_config.py @@ -0,0 +1,86 @@ +from typing import Optional, Union, Type +from UniTAP.libs.lib_tsi import PortIO +from UniTAP.libs.lib_tsi.tsi_private_types import * +from .link_tx_types import * +from .private_link_tx_types import * +from .link_status_common import * + + +class LinkDisplayPortForceConfig: + """ + Class `LinkDisplayPortForceConfig` allows settings link configuration on Source (TX - transmitter) side. + - Set configuration `set`. + - Get current configuration on link `get`. + """ + def __init__(self, port_io: PortIO, caps: DPTXHWCaps): + self.__io = port_io + self.__caps = caps + + def set(self, config: DisplayPortLinkConfig): + """ + Write new configuration on DP link. + + Args: + config (`DisplayPortLinkConfig`). + """ + if isinstance(config, LinkConfig.Force8b10b): + # TODO + pass + elif isinstance(config, LinkConfig.Force128b132b): + self.__force_set_bit_rate(config.bit_rate) + self.__force_set_lane_count(config.lane_count) + self.__force_set_pattern(config.pattern) + else: + raise TypeError("Incorrect LinkConfig Force format!") + + def get(self, config_type: Type[DisplayPortLinkConfig]) -> DisplayPortLinkConfig: + """ + Returns current DP source link configuration. + + Args: + config_type Type[DisplayPortLinkConfig]. + + Returns: + object of `DisplayPortLinkConfig` type + """ + if issubclass(config_type, LinkConfig.Force8b10b): + # TODO + return LinkConfig.Force8b10b() + elif issubclass(config_type, LinkConfig.Force128b132b): + link_status = LinkConfig.Force128b132b() + link_status.lane_count = self.__force_get_lane_count() + link_status.bit_rate = self.__force_get_bit_rate() + link_status.pattern = self.__force_get_pattern() + return link_status + else: + raise TypeError("Incorrect type!") + + def __force_set_bit_rate(self, bit_rate: Optional[Union[float, int]]): + if bit_rate is None: + return + elif (bit_rate == 10.0 and not self.__caps.support_10gbps) or \ + (bit_rate == 13.5 and not self.__caps.support_13_5gbps) or \ + (bit_rate == 20.0 and not self.__caps.support_20gbps): + raise ValueError(f"Following bit rate {bit_rate} is not supported on device.") + self.__io.set(TSI_DP2TX_OUT_BR, DP21_LinkRateRev.get(bit_rate), c_uint32) + + def __force_get_bit_rate(self) -> float: + return DP21_LinkRate.get(self.__io.get(TSI_DP2TX_OUT_BR)[1]) + + def __force_set_lane_count(self, lane_count: Optional[int]): + if lane_count is None: + return + if lane_count not in [1, 2, 4]: + raise ValueError(f"Incorrect lane count number {lane_count}. Must be from list: 1, 2, 4") + self.__io.set(TSI_DP2TX_OUT_LC, lane_count, c_uint32) + + def __force_get_lane_count(self) -> int: + return self.__io.get(TSI_DP2TX_OUT_LC)[1] + + def __force_set_pattern(self, pattern: Optional[DP128b132bLinkPattern]): + if pattern is None: + return + self.__io.set(TSI_DP2TX_OUT_TEST_PATTERN, pattern.value, c_uint32) + + def __force_get_pattern(self) -> DP128b132bLinkPattern: + return DP128b132bLinkPattern(self.__io.get(TSI_DP2TX_OUT_TEST_PATTERN, c_uint32)[1]) diff --git a/UniTAP/dev/ports/modules/link/dp/link_tx_status.py b/UniTAP/dev/ports/modules/link/dp/link_tx_status.py new file mode 100644 index 0000000..db2123d --- /dev/null +++ b/UniTAP/dev/ports/modules/link/dp/link_tx_status.py @@ -0,0 +1,432 @@ +import warnings +from typing import Optional + +from UniTAP.libs.lib_tsi import PortIO +from UniTAP.libs.lib_tsi.tsi_types import * +from .link_tx_types import * +from .private_link_tx_types import * +from .link_status_common import * +from .private_link_status_common import * +from typing import Union +from UniTAP.dev.ports.modules.dpcd import DPCDRegisters + + +class LinkDisplayPortStatusSource: + """ + Class `LinkDisplayPortStatusSource` describes information about DP link status. Contains following info: + - MST stream count `mst_stream_count`. + - Link encoding `link_encoding`. + - Link rate `link_rate`. + - Lane count `lane_count`. + - State of HPD `hpd_asserted`. + - State of framing `enhanced_framing`. + - State of scrambling `scrambling_enabled`. + - State of DSC `dsc_enabled` + - State of FEC `fec_enabled`. + - State of MST `mst_enabled`. + - State of SSC `ssc_enabled`. + - State of ILA `ila`. + - State of EQ ILA `eq_ila`. + - State of CDS ILA `cds_ila`. + - State of LT fail `lt_fail`. + - State of selected lane `lane`. + - State of VCP `vcp`. + - State of selected stream `stream`. + - Send ACT command `send_act`. + """ + def __init__(self, port_io: PortIO, caps: DPTXHWCaps, dpcd: DPCDRegisters): + self.__io = port_io + self.__caps = caps + self.__dpcd = dpcd + + @property + def mst_stream_count(self) -> int: + """ + Returns current mst stream count. + + Returns: + object of int type + """ + result, mst_status = self.__io.get(TSI_DPTX_MST_STATUS_R, c_uint) + return (mst_status >> 8) & 0xFF + + @property + def link_encoding(self) -> DpLinkEncoding: + """ + Returns current link encoding `DpLinkEncoding`. + + Returns: + object of DpLinkEncoding type + """ + result, value = self.__io.get(TSI_DPTX_LINK_MODE_R, c_uint) + + if value == 0: + return DpLinkEncoding.LE_8b10b + else: + return DpLinkEncoding.LE_128b132b + + @property + def link_rate(self) -> float: + """ + Returns current link rate. + + Returns: + object of float type + """ + if self.link_encoding == DpLinkEncoding.LE_128b132b: + if self.__check_custom_bit_rate() != 0: + return self.__check_custom_bit_rate() + else: + return DP21_LinkRate.get(self.__io.get(TSI_DP2TX_LT_RATE_R, c_uint)[1]) + else: + return round(self.__io.get(TSI_DPTX_LINK_RATE_R, c_uint)[1] * 0.27, 2) + + @property + def lane_count(self) -> int: + """ + Returns current lane count. + + Returns: + object of int type + """ + return self.__io.get(TSI_DPTX_LINK_LANE_COUNT_R, c_int)[1] + + @property + def hpd_asserted(self) -> bool: + """ + Returns current state of HPD asserted. + + Returns: + object of bool type + """ + return (self.__io.get(TSI_DPTX_HPD_STATUS_R, c_int)[1] & 1) != 0 + + @property + def available_link_rate(self) -> float: + """ + + Returns available link rate. + + Returns: + object of float type + """ + + if self.link_encoding == DpLinkEncoding.LE_128b132b: + link_rate = self.link_rate * 1000000000 * self.lane_count * ((128 * 383) / (132 * 384)) + else: + link_rate = self.link_rate * 1000000000 * self.lane_count + if self.mst_enabled: + if self.fec_enabled: + link_rate = link_rate * 7690 / 10000 + else: + link_rate = link_rate * 7875 / 10000 + else: + if self.fec_enabled: + link_rate = link_rate * 7813 / 10000 + else: + link_rate = link_rate * 8000 / 10000 + + return link_rate + + def lane(self, lane_number: int) -> LaneStatus: + """ + Returns current status of selected lane `LaneStatus`. + + Args: + lane_number (int) - number of selected lane + + Returns: + object of LaneStatus type + """ + if not (0 <= lane_number <= 3): + raise ValueError(f"Incorrect lane number {lane_number}. Available range: 0-{self.lane_count}") + + status = self.__read_link_status() + voltage_swing = self.__io.get(TSI_DPTX_LINK_VOLTAGE_SWING_R, LinkVoltageSwing)[1] + pre_emphasis = self.__io.get(TSI_DPTX_LINK_PRE_EMPHASIS_R, LinkPreEmphasis)[1] + error_counters = self.__dpcd.read(0x210, 8).data + + lane_status = LaneStatus() + + if lane_number == 0: + lane_status.cr = status.l0_cr + lane_status.sl = status.l0_sl + lane_status.eq = status.l0_eq + lane_status.voltage_swing = voltage_swing.vs_l0 + lane_status.pre_emphasis = pre_emphasis.pe_l0 + lane_status.error_count = error_counters[0] | ((error_counters[1] & 0x7F) << 8) + elif lane_number == 1: + lane_status.cr = status.l1_cr + lane_status.sl = status.l1_sl + lane_status.eq = status.l1_eq + lane_status.voltage_swing = voltage_swing.vs_l1 + lane_status.pre_emphasis = pre_emphasis.pe_l1 + lane_status.error_count = error_counters[2] | ((error_counters[3] & 0x7F) << 8) + elif lane_number == 2: + lane_status.cr = status.l2_cr + lane_status.sl = status.l2_sl + lane_status.eq = status.l2_eq + lane_status.voltage_swing = voltage_swing.vs_l2 + lane_status.pre_emphasis = pre_emphasis.pe_l2 + lane_status.error_count = error_counters[4] | ((error_counters[5] & 0x7F) << 8) + elif lane_number == 3: + lane_status.cr = status.l3_cr + lane_status.sl = status.l3_sl + lane_status.eq = status.l3_eq + lane_status.voltage_swing = voltage_swing.vs_l3 + lane_status.pre_emphasis = pre_emphasis.pe_l3 + lane_status.error_count = error_counters[6] | ((error_counters[7] & 0x7F) << 8) + + if self.link_encoding == DpLinkEncoding.LE_128b132b: + ffe_preset = self.__io.get(TSI_DP2TX_LT_FFE_PRESET_R, c_uint32)[1] + lane_status.ffe_preset = (ffe_preset >> (8 * lane_number)) & 0xFF + else: + lane_status.ffe_preset = 0 + + return lane_status + + @property + def dsc_enabled(self) -> Union[bool, None]: + """ + Returns current state of DSC (enabled or disabled). + None if DSC does not support. + + Returns: + object of bool or None type + """ + if self.__caps.dsc or self.__caps.dsc2: + return (self.__io.get(TSI_DPTX_DSC_STATUS_R)[1] & 1) != 0 + else: + return None + + @property + def mst_enabled(self) -> Union[bool, None]: + """ + Returns current state of MST (enabled or disabled). + None if MST does not support. + + Returns: + object of bool or None type + """ + if self.__caps.mst: + return (self.__io.get(TSI_DPTX_MST_STATUS_R, c_uint32)[1] & 1) != 0 + else: + return None + + @property + def ssc_enabled(self) -> Union[bool, None]: + """ + Returns current state of SSC (enabled or disabled). + None if SSC does not support. + + Returns: + object of bool or None type + """ + res = self.__io.get(TSI_DPTX_DOWNSPREAD_STATUS_R)[1] + if ((res >> 1) & 1) != 0: + return (res & 1) != 0 + else: + return None + + @property + def fec_enabled(self) -> Union[bool, None]: + """ + Returns current state of FEC (enabled or disabled). + None if FEC does not support. + + Returns: + object of bool or None type + """ + if self.__caps.fec or self.__caps.fec2: + return (self.__io.get(TSI_DPTX_FEC_STATUS_R)[1] & 1) != 0 + else: + return None + + @property + def enhanced_framing(self) -> bool: + """ + Returns current state of enhanced framing (enabled or disabled). + + Returns: + object of bool type + """ + return self.__read_lt_features().framing + + @property + def scrambling_enabled(self) -> bool: + """ + Returns current state of scrambling (enabled or disabled). + + Returns: + object of bool type + """ + return bool(self.__caps.scrambler_seed) + + @property + def lttpr_active(self) -> Union[bool, None]: + """ + Returns current state of LTTPR (enabled or disabled). + None if LTTPR does not support. + + Returns: + object of bool or None type + """ + if self.link_encoding == DpLinkEncoding.LE_128b132b: + return (self.__io.get(TSI_DPTX_LTTPR_STATUS_R, c_uint32)[1] & 1) != 0 + else: + return None + + @property + def ila(self) -> bool: + """ + Returns current ILA state. + + Returns: + object of bool type + """ + return bool(self.__read_link_status().ila) + + @property + def eq_ila(self) -> bool: + """ + Returns current EQ ILA state. + + Returns: + object of bool type + """ + return bool(self.__read_link_status().eq_ila) + + @property + def cds_ila(self) -> bool: + """ + Returns current CDS ILA state. + + Returns: + object of bool type + """ + return bool(self.__read_link_status().cds_ila) + + @property + def lt_fail(self) -> bool: + """ + Returns current LT fail state. + + Returns: + object of bool type + """ + return bool(self.__read_link_status().cds_ila) + + def stream(self, stream_index: int) -> StreamStatusDP: + """ + Returns status of selected stream `StreamStatusDP`. + + Args: + stream_index (int) - number of selected number + + Returns: + object of `StreamStatusDP` type + """ + stream_status = StreamStatusDP() + result, msa_info, size = self.__io.get(TSI_DPTX_MSA_INFO_R, DpMsa, 4) + + if stream_index < size: + msa_info_private = MSAInfo(msa_info[stream_index]) + stream_status.video_mode = msa_info_to_video_mode(msa_info_private, self.link_rate * 100000000, + self.link_encoding) + stream_status.mvid = msa_info_private.m_video + stream_status.nvid = msa_info_private.n_video + + result = self.__io.get(TSI_DPTX_CRC_R, DpCrc, self.mst_stream_count) + if result[0] >= TSI_SUCCESS and self.mst_stream_count > 1: + stream_status.crc = [result[1][stream_index].r, result[1][stream_index].g, result[1][stream_index].b] + elif result[0] >= TSI_SUCCESS and self.mst_stream_count == 1: + stream_status.crc = [result[1].r, result[1].g, result[1].b] + + result = self.__io.get(TSI_DPTX_DSC_CRC_R, DpCrc, self.mst_stream_count) + if result[0] >= TSI_SUCCESS and self.mst_stream_count > 1: + stream_status.dsc_crc = [result[1][stream_index].r, result[1][stream_index].g, result[1][stream_index].b] + elif result[0] >= TSI_SUCCESS and self.mst_stream_count == 1: + stream_status.dsc_crc = [result[1].r, result[1].g, result[1].b] + else: + raise ValueError(f"Selected stream {stream_index} is not available. " + f"Available stream number: {size}") + + return stream_status + + def vcp(self, stream_index: int = 0) -> Optional[VCPStatus]: + """ + Returns VCP status of selected stream `VCPStatus`. + + Args: + stream_index (int) - number of selected number + + Returns: + object of `VCPStatus` | None type + """ + if self.__caps.mst: + if not stream_index < self.mst_stream_count: + raise ValueError(f"Selected stream {stream_index} is not available. " + f"Available stream number: {self.mst_stream_count}") + + res = self.__io.get(TSI_DPTX_VCP_TABLE_R, VCPTable, 4)[1] + + if self.mst_enabled: + status = VCPStatus() + + status.port_number = res[stream_index].port_number + status.stream_id = res[stream_index].stream_id + status.req_pbn = res[stream_index].req_pbn + status.alloc_pbn = res[stream_index].alloc_pbn + status.first_slot = res[stream_index].first_slot + status.slot_num = res[stream_index].slot_num + + return status + else: + warnings.warn("MST not enabled. Cannot show VCP status.") + return VCPStatus() + + def send_act(self): + """ + Send ACT command. + """ + self.__io.set(TSI_DPTX_MST_COMMAND_W, 1, c_uint32) + + def __read_lt_features(self) -> LTFeatures: + return self.__io.get(TSI_DPTX_LT_FEATURES, LTFeatures)[1] + + def __read_link_status(self) -> LinkTxStatus: + return self.__io.get(TSI_DPTX_LINK_STATUS_R, LinkTxStatus)[1] + + def __check_custom_bit_rate(self) -> float: + res = self.__io.get(TSI_DP2TX_CUSTOM_RATE_MAP, c_uint32, 3)[1] + for i in range(3): + if res[i] != 0: + return res[i] * 200000 / 1000000000 + return 0 + + def __str__(self) -> str: + lane_status_str = "" + stream_info_str = "" + vcp_table_str = "" + for i in range(self.mst_stream_count): + lane_status_str += f"Lane {i}\n{self.lane(i)}\n" + stream_info_str += f"Stream {i}\n{self.stream(i)}\n" + vcp_table_str += f"#{i}\n{self.vcp(i)}" + + return f"Lane count: {self.lane_count}" \ + f"Bit rate: {self.link_rate:.3} Gbps\n" \ + f"Enhanced framing mode: {self.enhanced_framing}\n" \ + f"MST: {self.mst_enabled}\n" \ + f"DSC: {self.dsc_enabled}\n" \ + f"LTTPR: {self.lttpr_active}\n" \ + f"Link Encoding: {self.link_encoding.name}\n" \ + f"Scrambling: {self.scrambling_enabled}\n" \ + f"SSC: {self.ssc_enabled}\n" \ + f"FEC: {self.fec_enabled}\n" \ + f"ILA: {self.ila}\n" \ + f"EQ_ILA: {self.eq_ila}\n" \ + f"CDS_ILA: {self.cds_ila}\n" \ + f"LT_FAIL: {self.lt_fail}\n" \ + f"Lane status\n{lane_status_str}\n" \ + f"Stream info:\n{stream_info_str}\n" \ + f"VCP status:\n{vcp_table_str}\n" diff --git a/UniTAP/dev/ports/modules/link/dp/link_tx_types.py b/UniTAP/dev/ports/modules/link/dp/link_tx_types.py new file mode 100644 index 0000000..3261867 --- /dev/null +++ b/UniTAP/dev/ports/modules/link/dp/link_tx_types.py @@ -0,0 +1,329 @@ +from typing import TypeVar +from enum import IntEnum +from .link_status_common import DpLinkEncoding + + +class DPLinkPattern(IntEnum): + """ + Class `DPLinkPattern` contains all possible variants of DP link patterns. + Names TrainingPattern1, TrainingPattern2, TrainingPattern3, TrainingPattern4 will be deleted in RC 3.7 + """ + ActiveVideo = 0 + IdlePattern = 1 + TrainingPattern1 = 2 + TrainingPattern2 = 3 + TrainingPattern3 = 4 + TrainingPattern4 = 5 + PRBS7 = 6 + HBR2 = 7 + SER = 8 + ForceVideo = 10 + ForceIdle = 11 + PRBS9 = 12 + PRBS11 = 13 + PRBS15 = 14 + PRBS23 = 15 + PRBS31 = 16 + LinkSquarePattern = 17 + Undefined = 18 + TPS1 = TrainingPattern1 + TSP2 = TrainingPattern2 + TSP3 = TrainingPattern3 + TSP4 = TrainingPattern4 + + +class DP128b132bLinkPattern(IntEnum): + """ + Class `DP128b132bLinkPattern` contains all possible output patterns in DP2.0 force link mode. + """ + ActiveVideo = 0 + IdlePattern = 1 + TPS1 = 2 + TPS2 = 3 + PRBS9 = 4 + PRBS11 = 5 + PRBS15 = 6 + PRBS23 = 7 + PRBS31 = 8 + Custom80bit = 9 + LinkSquarePattern = 10 + PRBS7 = 11 + + +class DPOutLinkMode(IntEnum): + """ + Class `DPOutLinkMode` contains all possible out link modes. + """ + Normal = 0 + Force8b10b = 1 + Force128b132b = 2 + + +class SSCConfig: + """ + Class `SSCConfig` contains information about SSC configuration parameters. + - Set and get amplitude `amplitude`. + - Set and get frequency `frequency`. + - Enable flag. + """ + def __init__(self): + self.enabled = False + self.amplitude = 0.5 + self.frequency = 30000 + + def __eq__(self, other) -> bool: + return self.enabled == other.enabled and \ + self.amplitude == other.amplitude and \ + self.frequency == other.frequency + + def __str__(self) -> str: + return f"Enabled - {self.enabled}\n" \ + f"Amplitude - {self.amplitude}\n" \ + f"Frequency - {self.frequency}\n" + + +class LinkConfig: + """ + Main class contains variants of link configuration: + - DP 1.4 config `DP8b10b`. + - DP 2.1 config `DP128b132b`. + """ + class DP8b10b: + """ + Class `DP8b10b` contains information of possible DP 1.4 configuration. Contains following field: + - lane count + - link bit rate + - MST + - MST stream count + - set enhanced framing mode + - Auto seed + - SSC + - FEC + - Force EDID timings after link training + - Adaptive-Sync auto enable + - Split SDP (if supported) + """ + def __init__(self): + self.lane_count = None + self.bit_rate = None + self.mst = None + self.mst_stream_count = None + self.enhanced_framing_mode = None + self.auto_seed = None + self.ssc = None + self.fec = None + self.force_edid_timings_after_lt = None + self.adaptive_sync_auto_enable = None + self.split_sdp = None + + def __eq__(self, other) -> bool: + return self.lane_count == other.lane_count and \ + self.bit_rate == other.bit_rate and \ + self.mst == other.mst and \ + self.mst_stream_count == other.mst_stream_count and \ + self.enhanced_framing_mode == other.enhanced_framing_mode and \ + self.auto_seed == other.auto_seed and \ + self.ssc == other.ssc and \ + self.fec == other.fec and \ + self.force_edid_timings_after_lt == other.force_edid_timings_after_lt and \ + self.adaptive_sync_auto_enable == other.adaptive_sync_auto_enable and \ + self.split_sdp == other.split_sdp + + def __str__(self) -> str: + return f"Lane Count - {self.lane_count}\n" \ + f"Bit rate - {self.bit_rate:.3}\nMST enabled - {self.mst}\n" \ + f"MST stream count - {self.mst_stream_count}\n" \ + f"Enhanced framing mode - {bool(self.enhanced_framing_mode)}\n" \ + f"Auto Seed - {bool(self.auto_seed)}\nSSC info:\n{self.ssc.__str__()}\nFEC: {self.fec}\n" \ + f"Force EDID Timings after LT: {self.force_edid_timings_after_lt}\n" \ + f"Adaptive Sync auto enable: {self.adaptive_sync_auto_enable}\n" \ + f"Split SDP: {self.split_sdp}\n" + + class DP128b132b: + """ + Class `DP128b132b` contains information of possible DP 2.1 configuration. Contains following field: + - lane count + - link bit rate + - Force DP 128/132 + - Maximum link bandwidth supported + - Set enhanced framing mode + - Auto seed + - SSC + - LLTPR + - Try (enable/disable) DP 128/132 + - Old DP2 link training + - Force EDID timings after link training + - Adaptive-Sync auto enable + - Split SDP (if supported) + - Force eDP rates + """ + def __init__(self): + self.lane_count = None + self.bit_rate = None + self.force_dp_128_132 = None + self.enhanced_framing_mode = None + self.max_link_bandwidth_supported = None + self.old_dp2_lt = None + self.lttpr = None + self.try_dp_128_132 = None + self.auto_seed = None + self.ssc = None + self.force_edid_timings_after_lt = None + self.adaptive_sync_auto_enable = None + self.mst = None + self.mst_stream_count = None + self.crc_16 = None + + def __eq__(self, other) -> bool: + return self.lane_count == other.lane_count and \ + self.bit_rate == other.bit_rate and \ + self.force_dp_128_132 == other.force_dp_128_132 and \ + self.max_link_bandwidth_supported == other.max_link_bandwidth_supported and \ + self.old_dp2_lt == other.old_dp2_lt and \ + self.lttpr == other.lttpr and \ + self.try_dp_128_132 == other.try_dp_128_132 and \ + self.enhanced_framing_mode == other.enhanced_framing_mode and \ + self.auto_seed == other.auto_seed and \ + self.ssc == other.ssc and \ + self.force_edid_timings_after_lt == other.force_edid_timings_after_lt and \ + self.adaptive_sync_auto_enable == other.adaptive_sync_auto_enable and \ + self.mst == other.mst and \ + self.mst_stream_count == other.mst_stream_count and \ + self.crc_16 == other.crc_16 + + def __str__(self) -> str: + return f"Lane Count - {self.lane_count}\n" \ + f"Bit rate - {self.bit_rate:.3}\nMST enabled - {self.mst}\n" \ + f"MST stream count - {self.mst_stream_count}\n" \ + f"Force DP 128b/132b enabled - {self.force_dp_128_132}\n" \ + f"Max link bandwidth supported - {self.max_link_bandwidth_supported}\n" \ + f"Enhanced framing mode - {self.enhanced_framing_mode}\n" \ + f"Try DP 128b/132b enabled - {self.try_dp_128_132}\n"\ + f"Old DP 2.1 Link training enabled - {self.old_dp2_lt}\n" \ + f"LTTPR enabled - {self.lttpr}\n" \ + f"Auto Seed - {self.auto_seed}\nSSC info - {self.ssc.__str__()}\n" \ + f"Force EDID Timings after LT: {self.force_edid_timings_after_lt}\n" \ + f"Adaptive Sync auto enable: {self.adaptive_sync_auto_enable}\n" \ + f"Split SDP: {self.crc_16}\n" + + class Force8b10b: + """ + + """ + def __init__(self): + self.lane_count = None + self.bit_rate = None + self.pattern = None + + def __str__(self) -> str: + return f"Lane Count - {self.lane_count}\n" \ + f"Bit rate - {self.bit_rate:.3}\n" \ + f"Link Pattern - {self.pattern.name}" + + class Force128b132b: + """ + + """ + def __init__(self): + self.lane_count = None + self.bit_rate = None + self.pattern = None + + def __str__(self) -> str: + return f"Lane Count - {self.lane_count}\n" \ + f"Bit rate - {self.bit_rate:.3}\n" \ + f"Link Pattern - {self.pattern.name}" + + class eDP: + """ + + """ + def __init__(self): + self.lane_count = None + self.force_edp = None + self.eDp_cur_rate = None + self.eDp_supported_rates = None + self.eDp_aux_preamble = None + + def __str__(self) -> str: + return f"Lane Count - {self.lane_count}\n" \ + f"Force eDP: {bool(self.force_edp)}\n" \ + f"eDP rate - {self.eDp_cur_rate}\n" \ + f"eDP supported rates - {self.eDp_supported_rates}\n" \ + f"eDP AUX preamble - {self.eDp_aux_preamble}" + + def __eq__(self, other) -> bool: + return self.lane_count == other.lane_count and \ + self.force_edp == other.force_edp and \ + self.eDp_cur_rate == other.eDp_cur_rate and \ + self.eDp_aux_preamble == other.eDp_aux_preamble + + +class LinkStatus: + """ + Main class describes current link status. Contains following field: + - Lane count + - BIt rate + - State of enhanced framing + - State of MST mode + - State of DSC mode + - LTTPR + - Link encoding `DpLinkEncoding` + - State of scrambling + - State of SSC + - State of FEC + - State of eDP + """ + def __init__(self): + self.lane_count = 0 + self.bit_rate = 0 + self.enhanced_framing = False + self.mst_enabled = False + self.dsc_enabled = False + self.lttpr = False + self.link_encoding = DpLinkEncoding.LE_NONE + self.scrambling_enabled = False + self.ssc_enabled = False + self.fec_enabled = False + self.force_edp_enabled = False + + def __eq__(self, other) -> bool: + return self.lane_count == other.lane_count and \ + self.bit_rate == other.bit_rate and \ + self.enhanced_framing == other.enhanced_framing and \ + self.mst_enabled == other.mst_enabled and \ + self.dsc_enabled == other.dsc_enabled and \ + self.lttpr == other.lttpr and \ + self.link_encoding == other.link_encoding and \ + self.scrambling_enabled == other.scrambling_enabled and \ + self.ssc_enabled == other.ssc_enabled and \ + self.fec_enabled == other.fec_enabled and \ + self.force_edp_enabled == other.force_edp_enabled + + def __str__(self) -> str: + return f"Lane Count - {self.lane_count}\n" \ + f"Bit rate - {self.bit_rate:.3}\n" \ + f"Enhanced framing - {self.enhanced_framing}\n" \ + f"MST enabled - {self.mst_enabled}\n" \ + f"DSC enabled - {self.dsc_enabled}\n" \ + f"LTTPR - {self.lttpr}\n" \ + f"Link Encoding - {self.link_encoding.name}\n" \ + f"Scrambling enabled - {self.link_encoding.name}\n" \ + f"SSC enabled - {self.ssc_enabled}\n" \ + f"FEC enabled - {self.fec_enabled}\n" \ + f"Force eDP enabled: {self.force_edp_enabled}" + + # class LinkOverrides: + # + # def __init__(self): + # self.override_voltage = 0 + # self.override_pre_emp = 0 + # self.override_ffe_presets = [0] * 4 + + +DisplayPortLinkConfig = TypeVar("DisplayPortLinkConfig", + LinkConfig.DP8b10b, + LinkConfig.DP128b132b, + LinkConfig.eDP, + LinkConfig.Force8b10b, + LinkConfig.Force128b132b) diff --git a/UniTAP/dev/ports/modules/link/dp/private_link_rx_types.py b/UniTAP/dev/ports/modules/link/dp/private_link_rx_types.py new file mode 100644 index 0000000..e39fbad --- /dev/null +++ b/UniTAP/dev/ports/modules/link/dp/private_link_rx_types.py @@ -0,0 +1,122 @@ +from ctypes import Structure, c_uint32, c_uint8, c_uint16 +from enum import IntEnum +from .link_rx_types import CableCapabilitiesEnum + + +class DPRXHWCaps(Structure): + _fields_ = [ + ('mst', c_uint32, 1), + ('hdcp_1_x', c_uint32, 1), + ('hdcp_2_x', c_uint32, 1), + ('fec', c_uint32, 1), + ('dsc', c_uint32, 1), + ('alpm', c_uint32, 1), + ('lanecount3', c_uint32, 1), + ('edp', c_uint32, 1), + ('mst_stream_count', c_uint32, 4), + ('pr', c_uint32, 1), + ('psr', c_uint32, 1), + ('', c_uint32, 1), + ('display_id_capability', c_uint32, 1), + ('max_link_rate', c_uint32, 8), + ('force_link_congiguration', c_uint32, 1), + ('cirdan', c_uint32, 1), + ('dp_power', c_uint32, 1), + ('aux_swing', c_uint32, 1), + ('dp2_custom_rates', c_uint32, 1), + ('custom_rate_ex', c_uint32, 1), + ('fec2', c_uint32, 1), + ('dsc2', c_uint32, 1), + ('dp2_support_rates', c_uint32, 8), + ('', c_uint32, 24), + ('link_pat_log', c_uint32, 1), + ('vbid_hw_log', c_uint32, 1), + ('msa_hw_log', c_uint32, 1), + ('aux_bw_log', c_uint32, 1), + ('', c_uint32, 28), + ('scrambler_seed', c_uint32, 1), + ('sdp_err_counters', c_uint32, 1), + ('sink_cnt_config', c_uint32, 1), + ('', c_uint32, 29) + ] + + +class DPRXLinkControl(Structure): + _fields_ = [ + ('tps4_type', c_uint32, 1), + ('alpm_aux_wake', c_uint32, 1), + ('old_dp2_lt', c_uint32, 1), + ('edp_aux_preamble', c_uint32, 1) + ] + + def value(self) -> int: + return self.tps4_type << 0 | self.alpm_aux_wake << 1 | self.old_dp2_lt << 2 | self.edp_aux_preamble << 3 + + +class CableTypeEnum(IntEnum): + Unknown = 0 + Passive = 1 + LRD = 2 + Retimer = 3 + + +class PortCableAttributesEnum(IntEnum): + DP_CABLE_ATTR_UHBR_10_20_CAPABILITY_POS = 0 + DP_CABLE_ATTR_UHBR_10_20_CAPABILITY_MASK = 0x03 << DP_CABLE_ATTR_UHBR_10_20_CAPABILITY_POS + DP_CABLE_ATTR_UHBR_10_20_NOT_CAPABLE = 0x00 << DP_CABLE_ATTR_UHBR_10_20_CAPABILITY_POS + DP_CABLE_ATTR_UHBR_10_CAPABLE = 0x01 << DP_CABLE_ATTR_UHBR_10_20_CAPABILITY_POS + DP_CABLE_ATTR_UHBR_10_20_CAPABLE = 0x02 << DP_CABLE_ATTR_UHBR_10_20_CAPABILITY_POS + + DP_CABLE_ATTR_UHBR_13_5_CAPABILITY_POS = 2 + DP_CABLE_ATTR_UHBR_13_5_CAPABLE = 0x01 << DP_CABLE_ATTR_UHBR_13_5_CAPABILITY_POS + + DP_CABLE_ATTR_CABLE_TYPE_POS = 3 + DP_CABLE_ATTR_CABLE_TYPE_MASK = 0x7 << DP_CABLE_ATTR_CABLE_TYPE_POS + + +class PortCableAttributesStruct(Structure): + _fields_ = [ + ('data', c_uint8) + ] + + def isUHBR20Supported(self) -> bool: + return (self.data & PortCableAttributesEnum.DP_CABLE_ATTR_UHBR_10_20_CAPABILITY_MASK) == PortCableAttributesEnum.DP_CABLE_ATTR_UHBR_10_20_CAPABLE + + def isUHBR13_5Supported(self) -> bool: + return (self.data & PortCableAttributesEnum.DP_CABLE_ATTR_UHBR_13_5_CAPABLE) != 0 + + def isUHBR10Supported(self) -> bool: + return ((self.data & PortCableAttributesEnum.DP_CABLE_ATTR_UHBR_10_20_CAPABILITY_MASK) == PortCableAttributesEnum.DP_CABLE_ATTR_UHBR_10_CAPABLE) or self.isUHBR20Supported() + + def get_cable_caps(self) -> CableCapabilitiesEnum: + if self.isUHBR20Supported(): + return CableCapabilitiesEnum.DP80 + elif self.isUHBR13_5Supported(): + return CableCapabilitiesEnum.DP54 + elif self.isUHBR10Supported(): + return CableCapabilitiesEnum.DP40 + else: + return CableCapabilitiesEnum.Unknown + + def get_cable_type(self) -> CableTypeEnum: + return CableTypeEnum(self.data & PortCableAttributesEnum.DP_CABLE_ATTR_CABLE_TYPE_MASK >> PortCableAttributesEnum.DP_CABLE_ATTR_CABLE_TYPE_POS) + + +class CableAttributesStruct(Structure): + _fields_ = [ + ('currentPort', c_uint8), + ('anotherPort', c_uint8), + ('reserved', c_uint16) + ] + + +class RoutedLTStatusPrivate(Structure): + _fields_ = [ + ('enabled', c_uint32, 1), + ('dp2OldLt', c_uint32, 1), + ('state', c_uint32, 2), + ('success', c_uint32, 1), + ('', c_uint32, 11), + ('step', c_uint32, 9), + ('', c_uint32, 7) + ] diff --git a/UniTAP/dev/ports/modules/link/dp/private_link_status_common.py b/UniTAP/dev/ports/modules/link/dp/private_link_status_common.py new file mode 100644 index 0000000..21c42c5 --- /dev/null +++ b/UniTAP/dev/ports/modules/link/dp/private_link_status_common.py @@ -0,0 +1,157 @@ +from ctypes import * +from .dp_utils import * +from .link_status_common import DpLinkEncoding +from UniTAP.common.video_mode import VideoMode + + +class DpMsa(Structure): + class PixelAttributes(Structure): + _fields_ = [ + ("n_video_or_freq_low", c_uint32, 24), + ("", c_uint32, 8), + ("m_video_or_freq_high", c_uint32, 24), + ("", c_uint32, 8), + ] + + _fields_ = [ + ('attributes', PixelAttributes), + ('h_total', c_uint16), + ('v_total', c_uint16), + ('h_active', c_uint16), + ('v_active', c_uint16), + ('h_sync', c_int16), + ('v_sync', c_int16), + ('h_sync_start', c_uint16), + ('v_sync_start', c_uint16), + ('misc0', c_uint8), + ('misc1', c_uint8), + ('vbid', c_uint8), + ('port_number', c_uint8), + + ('vsc_sdp_db16', c_uint8), + ('vsc_sdp_db17', c_uint8), + ('vsc_sdp_db18', c_uint8), + ('', c_uint8), + + ('', c_uint32 * 8) + ] + + +class DpCrc(Structure): + _fields_ = [ + ('r', c_uint16), + ('g', c_uint16), + ('b', c_uint16), + ('', c_uint16) + ] + + +class VCPTable(Structure): + _fields_ = [ + ('port_number', c_int), + ('stream_id', c_int), + ('req_pbn', c_int), + ('alloc_pbn', c_int), + ('first_slot', c_int), + ('slot_num', c_int), + ('reserved1', c_int), + ('reserved2', c_int), + ] + + +class LinkVoltageSwing(Structure): + _fields_ = [ + ('vs_l0', c_uint32, 2), + ('', c_uint32, 6), + ('vs_l1', c_uint32, 2), + ('', c_uint32, 6), + ('vs_l2', c_uint32, 2), + ('', c_uint32, 1), + ('vs_l3', c_uint32, 2), + ('', c_uint32, 6), + ] + + +class LinkPreEmphasis(Structure): + _fields_ = [ + ('pe_l0', c_uint32, 2), + ('', c_uint32, 6), + ('pe_l1', c_uint32, 2), + ('', c_uint32, 6), + ('pe_l2', c_uint32, 2), + ('', c_uint32, 1), + ('pe_l3', c_uint32, 2), + ('', c_uint32, 6), + ] + + +class MSAInfo: + + def __init__(self, dp_msa: DpMsa): + self.n_video = dp_msa.attributes.n_video_or_freq_low + self.m_video = dp_msa.attributes.m_video_or_freq_high + self.video_frequency = (dp_msa.attributes.m_video_or_freq_high << 24) | dp_msa.attributes.n_video_or_freq_low + self.h_total = dp_msa.h_total + self.v_total = dp_msa.v_total + self.h_active = dp_msa.h_active + self.v_active = dp_msa.v_active + self.h_sync = dp_msa.h_sync + self.v_sync = dp_msa.v_sync + self.h_sync_start = dp_msa.h_sync_start + self.v_sync_start = dp_msa.v_sync_start + self.misc0 = dp_msa.misc0 + self.misc1 = dp_msa.misc1 + self.vbid = dp_msa.vbid + self.port_number = dp_msa.port_number + + self.vsc_sdp_db16 = dp_msa.vsc_sdp_db16 + self.vsc_sdp_db17 = dp_msa.vsc_sdp_db17 + self.vsc_sdp_db18 = dp_msa.vsc_sdp_db18 + + def is_eq(self, other, enc128b132b: bool) -> bool: + if enc128b132b: + if self.video_frequency != other.video_frequency: + return False + else: + # m_video changes frequently + if self.n_video != other.n_video: + return False + + return self.h_total == other.h_total and self.v_total == other.v_total and self.h_active == other.h_active and \ + self.v_active == other.v_active and self.h_sync == other.h_sync and self.v_sync == other.v_sync and \ + self.h_sync_start == other.h_sync_start and self.v_sync_start == other.v_sync_start and \ + self.misc0 == other.misc0 and self.misc1 == other.misc1 and self.vbid == other.vbid and \ + self.vsc_sdp_db16 == self.vsc_sdp_db16 and self.vsc_sdp_db17 == self.vsc_sdp_db17 and \ + self.vsc_sdp_db18 == self.vsc_sdp_db18 + + +def msa_info_to_video_mode(msa_info: MSAInfo, f_ls_clock: float, link_encoding: DpLinkEncoding): + video_mode = VideoMode() + + video_mode.timing.hactive = msa_info.h_active + video_mode.timing.vactive = msa_info.v_active + video_mode.timing.htotal = msa_info.h_total + video_mode.timing.vtotal = msa_info.v_total + video_mode.timing.hstart = msa_info.h_sync_start + video_mode.timing.vstart = msa_info.v_sync_start + video_mode.timing.hswidth = msa_info.h_sync + video_mode.timing.vswidth = msa_info.v_sync + + video_mode.color_info.colorimetry = get_vm_colorimetry(msa_info.vsc_sdp_db16) + video_mode.color_info.color_format = get_vm_color_format(msa_info.vsc_sdp_db16) + video_mode.color_info.bpc = get_vm_bpc(msa_info.vsc_sdp_db16, msa_info.vsc_sdp_db17) + video_mode.color_info.dynamic_range = get_vm_dynamic_range(msa_info.vsc_sdp_db17) + + frame_rate = 0 + if (msa_info.v_total * msa_info.h_total) > 0: + if link_encoding == DpLinkEncoding.LE_8b10b and msa_info.n_video > 0: + f_strm_clock = f_ls_clock * (msa_info.m_video / msa_info.n_video) + frame_rate = 1000 * f_strm_clock / (msa_info.v_total * msa_info.h_total) + elif link_encoding == DpLinkEncoding.LE_128b132b: + frame_rate = 1000 * msa_info.video_frequency / (msa_info.v_total * msa_info.h_total) + else: + pass + + video_mode.timing.frame_rate = round(frame_rate) + + return video_mode diff --git a/UniTAP/dev/ports/modules/link/dp/private_link_tx_types.py b/UniTAP/dev/ports/modules/link/dp/private_link_tx_types.py new file mode 100644 index 0000000..65b81e4 --- /dev/null +++ b/UniTAP/dev/ports/modules/link/dp/private_link_tx_types.py @@ -0,0 +1,107 @@ +from ctypes import Structure, c_uint32 + + +class DPTXHWCaps(Structure): + _fields_ = [ + ('mst', c_uint32, 1), + ('hdcp_1_x', c_uint32, 1), + ('hdcp_2_x', c_uint32, 1), + ('fec', c_uint32, 1), + ('dsc', c_uint32, 1), + ('alpm', c_uint32, 1), + ('lane_3', c_uint32, 1), + ('edp', c_uint32, 1), + ('mst_stream_count', c_uint32, 4), + ('', c_uint32, 4), + ('max_link_rate', c_uint32, 8), + ('', c_uint32, 1), + ('dp2_custom_rates', c_uint32, 1), + ('custom_rates_ext', c_uint32, 1), + ('adaptive_sync', c_uint32, 1), + ('edid_parser', c_uint32, 1), + ('', c_uint32, 1), + ('fec2', c_uint32, 1), + ('dsc2', c_uint32, 1), + ('support_10gbps', c_uint32, 1), + ('support_20gbps', c_uint32, 1), + ('support_13_5gbps', c_uint32, 1), + ('', c_uint32, 29), + ('link_pat_log', c_uint32, 1), + ('vbid_hw_log', c_uint32, 1), + ('msa_hw_log', c_uint32, 1), + ('aux_bw_log', c_uint32, 1), + ('', c_uint32, 28), + ('scrambler_seed', c_uint32, 1), + ('dp2_sdp_crc', c_uint32, 1), + ('sdp_split', c_uint32, 1), + ('dp2_sdp_split', c_uint32, 1), + ('', c_uint32, 28), + ('supported_dp2_custom_rates', c_uint32 * 32), + ] + + +class LTFeatures(Structure): + _fields_ = [ + ('skew', c_uint32, 1), + ('', c_uint32, 1), + ('framing', c_uint32, 1), + ('try_3_lane', c_uint32, 1), + ('try_edp', c_uint32, 1), + ('force_edp', c_uint32, 1), + ('try_dp2', c_uint32, 1), + ('force_dp2', c_uint32, 1), + ('old_dp2_lt', c_uint32, 1), + ('auto_seed', c_uint32, 1), + ('max_link_bw_policy', c_uint32, 1), + ('edp_aux_preamble', c_uint32, 1) + ] + + def value(self) -> int: + return self.framing << 2 | self.try_edp << 4 | self.force_edp << 5 | self.try_dp2 << 6 | self.force_dp2 << 7 |\ + self.old_dp2_lt << 8 | self.auto_seed << 9 | self.max_link_bw_policy << 10 | self.edp_aux_preamble << 11 + + +class LinkTxStatus(Structure): + _fields_ = [ + ('l0_cr', c_uint32, 1), + ('l0_eq', c_uint32, 1), + ('l0_sl', c_uint32, 1), + ('', c_uint32, 1), + ('l1_cr', c_uint32, 1), + ('l1_eq', c_uint32, 1), + ('l1_sl', c_uint32, 1), + ('', c_uint32, 1), + ('l2_cr', c_uint32, 1), + ('l2_eq', c_uint32, 1), + ('l2_sl', c_uint32, 1), + ('', c_uint32, 1), + ('l3_cr', c_uint32, 1), + ('l3_eq', c_uint32, 1), + ('l3_sl', c_uint32, 1), + ('', c_uint32, 13), + ('lt_fail', c_uint32, 1), + ('cds_ila', c_uint32, 1), + ('eq_ila', c_uint32, 1), + ('ila', c_uint32, 1) + ] + + +class PostLtFeatures(Structure): + _fields_ = [ + ('force_edid_timings', c_uint32, 1), + ('as_auto_enable', c_uint32, 1) + ] + + def value(self) -> int: + return self.force_edid_timings | self.as_auto_enable << 1 + + +class SdpControl(Structure): + _fields_ = [ + ('dp2_crc_sdp', c_uint32, 1), + ('dp_split_sdp', c_uint32, 1), + ('dp2_split_sdp', c_uint32, 1) + ] + + def value(self) -> int: + return self.dp2_crc_sdp | self.dp_split_sdp << 1 diff --git a/UniTAP/dev/ports/modules/link/hdmi/__init__.py b/UniTAP/dev/ports/modules/link/hdmi/__init__.py new file mode 100644 index 0000000..e873a98 --- /dev/null +++ b/UniTAP/dev/ports/modules/link/hdmi/__init__.py @@ -0,0 +1 @@ +from .types import FrlMode, FrlCaps, ClockRate, HdmiModeTx, HdmiModeRx, LinkMode diff --git a/UniTAP/dev/ports/modules/link/hdmi/arc_rx.py b/UniTAP/dev/ports/modules/link/hdmi/arc_rx.py new file mode 100644 index 0000000..855a025 --- /dev/null +++ b/UniTAP/dev/ports/modules/link/hdmi/arc_rx.py @@ -0,0 +1,134 @@ +from UniTAP.libs.lib_tsi.tsi_io import PortIO +from enum import IntEnum +from UniTAP.libs.lib_tsi.tsi_types import TSI_ARC_CONTROL_W, TSI_HDRX_ARC_STATUS_R +from ctypes import c_uint32 + + +class ArcLoopbackAudioSource(IntEnum): + """ + Class `ArcLoopbackAudioSource` contains all possible variants of ARC loopback audio source type. + """ + Unknown = -1 + TPG = 0 + HDMI = 1 + DVI = 2 + DP = 3 + SPDIF = 4 + + +class ArcRx: + """ + Class `ArcRx` contains information about caps and states of Audio return channel. + - Support ARC `supported`. + - Loopback support TGP `loopback_supported_tpg`. + - Loopback support HDMI `loopback_supported_hdmi`. + - Loopback support DVI `loopback_supported_dvi`. + - Loopback support DP `loopback_supported_dp`. + - Loopback support SPDIF `loopback_supported_spdif`. + - Current state (enabled/disabled) - `enabled`. + - Control of ARC Source `arc_source`. + - Control of single mode `single_mode`. + + """ + def __init__(self, port_io: PortIO): + self.__io = port_io + self.__supported = False + self.__loopback_supported_tpg = False + self.__loopback_supported_hdmi = False + self.__loopback_supported_dvi = False + self.__loopback_supported_dp = False + self.__loopback_supported_spdif = False + self.__enabled = False + self.__arc_source = ArcLoopbackAudioSource.HDMI + self.__arc_source_value = 2 + self.__single_mode = True + + def __read_arc_status(self) -> int: + return self.__io.get(TSI_HDRX_ARC_STATUS_R, c_uint32)[1] + + def __write_arc(self, value: int): + self.__io.set(TSI_ARC_CONTROL_W, value, c_uint32) + + @property + def supported(self) -> bool: + self.__supported = (self.__read_arc_status() & 0x1) != 0 + return self.__supported + + @property + def loopback_supported_tpg(self) -> bool: + self.__loopback_supported_tpg = ((self.__read_arc_status() >> 1) & 0x1) != 0 + return self.__loopback_supported_tpg + + @property + def loopback_supported_hdmi(self) -> bool: + self.__loopback_supported_hdmi = ((self.__read_arc_status() >> 2) & 0x1) != 0 + return self.__loopback_supported_hdmi + + @property + def loopback_supported_dvi(self) -> bool: + self.__loopback_supported_dvi = ((self.__read_arc_status() >> 8) & 0x1) != 0 + return self.__loopback_supported_dvi + + @property + def loopback_supported_dp(self) -> bool: + self.__loopback_supported_dp = ((self.__read_arc_status() >> 9) & 0x1) != 0 + return self.__loopback_supported_dp + + @property + def loopback_supported_spdif(self) -> bool: + self.__loopback_supported_spdif = ((self.__read_arc_status() >> 10) & 0x1) != 0 + return self.__loopback_supported_spdif + + @property + def enabled(self) -> bool: + self.__enabled = ((self.__read_arc_status() >> 31) & 0x1) != 0 + return self.__enabled + + @property + def arc_source(self) -> ArcLoopbackAudioSource: + return self.__arc_source + + @property + def single_mode(self) -> bool: + return self.__single_mode + + @single_mode.setter + def single_mode(self, single_mode: bool): + value = self.__arc_source_value | ((1 << 16) if single_mode else 0) + self.__write_arc(value) + self.__single_mode = single_mode + + @arc_source.setter + def arc_source(self, arc_source: ArcLoopbackAudioSource): + + self.__arc_source_value = 0 + + if arc_source == ArcLoopbackAudioSource.HDMI: + self.__arc_source_value |= 2 + self.__arc_source_value |= (0 << 8) + elif arc_source == ArcLoopbackAudioSource.TPG: + self.__arc_source_value |= 1 + elif arc_source == ArcLoopbackAudioSource.DVI: + self.__arc_source_value |= 2 + self.__arc_source_value |= (1 << 8) + elif arc_source == ArcLoopbackAudioSource.DP: + self.__arc_source_value |= 2 + self.__arc_source_value |= (2 << 8) + elif arc_source == ArcLoopbackAudioSource.SPDIF: + self.__arc_source_value |= 2 + self.__arc_source_value |= (3 << 8) + + value = self.__arc_source_value | ((1 << 16) if self.__single_mode else 0) + self.__write_arc(value) + self.__arc_source = arc_source + + def __str__(self): + return f"Audio return channel supported: {self.supported}\n" \ + f"Loopback supported TPG: {self.loopback_supported_tpg}\n" \ + f"Loopback supported HDMI: {self.loopback_supported_hdmi}\n" \ + f"Loopback supported DVI: {self.loopback_supported_dvi}\n" \ + f"Loopback supported DP: {self.loopback_supported_dp}\n" \ + f"Loopback supported SPDIF: {self.loopback_supported_spdif}\n" \ + f"Enabled: {self.enabled}\n" \ + f"ARC Source: {self.arc_source.name}\n" \ + f"Single mode: {self.single_mode}\n" diff --git a/UniTAP/dev/ports/modules/link/hdmi/capabilities.py b/UniTAP/dev/ports/modules/link/hdmi/capabilities.py new file mode 100644 index 0000000..4ab88fe --- /dev/null +++ b/UniTAP/dev/ports/modules/link/hdmi/capabilities.py @@ -0,0 +1,45 @@ +from UniTAP.libs.lib_tsi.tsi_io import PortIO +from ctypes import c_uint32 + + +class HdmiCapabilities: + + def __init__(self, ci_control: int, port_io: PortIO): + self.__io = port_io + + caps = self.__io.get(ci_control, c_uint32)[1] + self.__cap_tmds = caps & 0x1 + self.__cap_frl = (caps >> 1) & 0x1 + self.__cap_arc = (caps >> 2) & 0x1 + self.__cap_hdcp = (caps >> 3) & 0x1 + self.__cap_behavior_14 = (caps >> 4) & 0x1 + self.__cap_behavior_20 = (caps >> 5) & 0x1 + self.__cap_behavior_21 = (caps >> 6) & 0x1 + + @property + def support_tmds(self) -> bool: + return self.__cap_tmds + + @property + def support_frl(self) -> bool: + return self.__cap_tmds + + @property + def support_arc(self) -> bool: + return self.__cap_tmds + + @property + def support_hdcp(self) -> bool: + return self.__cap_tmds + + @property + def support_hdmi_mode_14(self) -> bool: + return self.__cap_tmds + + @property + def support_hdmi_mode_20(self) -> bool: + return self.__cap_tmds + + @property + def support_hdmi_mode_21(self) -> bool: + return self.__cap_tmds diff --git a/UniTAP/dev/ports/modules/link/hdmi/frl_caps_rx.py b/UniTAP/dev/ports/modules/link/hdmi/frl_caps_rx.py new file mode 100644 index 0000000..c2c549a --- /dev/null +++ b/UniTAP/dev/ports/modules/link/hdmi/frl_caps_rx.py @@ -0,0 +1,139 @@ +from UniTAP.libs.lib_tsi.tsi_io import PortIO +from .types import FrlMode, LtpLanesPattern, FrlCaps, LtpPattern, _update_frl_values, _update_ltp_pattern_values +from UniTAP.libs.lib_tsi.tsi_types import TSI_HDRX_FRL_CAPABILITY, TSI_HDRX_FRL_PATTERN, TSI_HDRX_LINK_STATUS_R +from ctypes import c_uint32 + + +class FrlControlRx: + """ + Class `FrlControlRx` contains information about FRL on Sink (RX - receiver) side. + allows working with: + - Set and get FRL mode `frl_mode`. + - Set and get FRL capabilities `frl_caps`. + - Set and get LTP requested `ltp_request` and additional `ltp_additional`. + """ + def __init__(self, port_io: PortIO): + self.__io = port_io + self.__frl_caps = FrlCaps() + self.__ltp_request = LtpLanesPattern() + self.__ltp_additional = LtpLanesPattern() + + def __read_caps(self) -> int: + return self.__io.get(TSI_HDRX_FRL_CAPABILITY, c_uint32)[1] + + def __write_caps(self, value: int): + self.__io.set(TSI_HDRX_FRL_CAPABILITY, value, c_uint32) + + def __read_frl_pattern(self) -> int: + return self.__io.get(TSI_HDRX_FRL_PATTERN, c_uint32)[1] + + def __write_frl_pattern(self, value: int): + self.__io.set(TSI_HDRX_FRL_PATTERN, value, c_uint32) + + @property + def frl_mode(self) -> FrlMode: + """ + Returns current FRL mode. + + Returns: + object of `FrlMode` type + """ + return FrlMode(self.__read_caps() & 0xF) + + @frl_mode.setter + def frl_mode(self, frl_mode: FrlMode): + """ + Set new FRL mode. + + Args: + frl_mode (`FrlMode`) + """ + new_value = frl_mode.value + new_value |= self.__read_caps() & ~0xF + self.__write_caps(new_value) + + @property + def frl_caps(self) -> FrlCaps: + """ + Returns current FRL capabilities. + + Returns: + object of `FrlCaps` type + """ + _update_frl_values(self.__frl_caps, self.__read_caps()) + return self.__frl_caps + + @frl_caps.setter + def frl_caps(self, frl_caps: FrlCaps): + """ + Set new FRL capabilities. + + Args: + frl_caps (`FrlCaps`) + """ + new_value = frl_caps.value() + new_value |= self.__read_caps() & ~0xF + self.__write_caps(new_value) + self.__frl_caps = frl_caps + + @property + def ltp_request(self) -> LtpLanesPattern: + """ + Returns current LTP lanes pattern. Current Pattern that is being checking during Link Training. + Each lane can request different pattern. + + Returns: + object of `LtpLanesPattern` type + """ + _update_ltp_pattern_values(self.__ltp_request, self.__read_frl_pattern()) + return self.__ltp_request + + @ltp_request.setter + def ltp_request(self, ltp_request: LtpLanesPattern): + """ + Set new LTP lanes pattern. + + Args: + ltp_request (`LtpLanesPattern`) + """ + new_value = ltp_request.value() + new_value |= self.__read_frl_pattern() + self.__write_frl_pattern(new_value) + self.__ltp_request = ltp_request + + @property + def ltp_additional(self) -> LtpLanesPattern: + """ + Returns current additional LTP lanes pattern. Current Pattern that is being checking during Link Training. + Each lane can request different pattern. + + Returns: + object of `LtpLanesPattern` type + """ + _update_ltp_pattern_values(self.__ltp_additional, self.__read_frl_pattern() >> 0xFF) + return self.__ltp_additional + + @ltp_additional.setter + def ltp_additional(self, ltp_request: LtpLanesPattern): + """ + Set new additional LTP lanes pattern. + + Args: + ltp_request (`LtpLanesPattern`) + """ + new_value = ltp_request.value() << 0xFF + new_value |= self.__read_frl_pattern() + self.__write_frl_pattern(new_value) + self.__ltp_additional = ltp_request + + def re_train(self): + """ + DO re train. + """ + self.__io.set(TSI_HDRX_LINK_STATUS_R, 0x1c0000, c_uint32) + + def __str__(self): + return f"FRL Mode: {self.frl_mode.name}\n" \ + f"FRL Caps:\n{self.frl_caps.__str__()}" \ + f"LTP Lanes Pattern:\n{self.ltp_request.__str__()}" \ + f"LTP Lanes Pattern Additional:\n{self.ltp_additional.__str__()}\n" diff --git a/UniTAP/dev/ports/modules/link/hdmi/frl_control_tx.py b/UniTAP/dev/ports/modules/link/hdmi/frl_control_tx.py new file mode 100644 index 0000000..1c1cb3c --- /dev/null +++ b/UniTAP/dev/ports/modules/link/hdmi/frl_control_tx.py @@ -0,0 +1,366 @@ +from UniTAP.libs.lib_tsi.tsi_io import PortIO +from .types import FrlMode, LtpLanesPattern, FrlCaps, _update_frl_values, _update_ltp_pattern_values +from UniTAP.libs.lib_tsi.tsi_types import TSI_HDTX_FRL_CAPABILITY, TSI_HDTX_FRL_PATTERN_R, TSI_HDTX_FRL_TIMERS, \ + TSI_HDTX_SINK_FEATURE_W, TSI_HDTX_FRL_STATUS_R +from ctypes import c_uint32 + + +class FfeMax: + """ + Class `FfeMax` allows working with FFE on HDMI. + Possible to configure: + - Set and get Value for mode 3 lanes and 3 Gbps `mode_3lanes_3gbps`. + - Set and get Value for mode 3 lanes and 6 Gbps `mode_3lanes_6gbps`. + - Set and get Value for mode 4 lanes and 6 Gbps `mode_4lanes_6gbps`. + - Set and get Value for mode 4 lanes and 8 Gbps `mode_4lanes_8gbps`. + - Set and get Value for mode 4 lanes and 10 Gbps `mode_4lanes_10gbps`. + - Set and get Value for mode 4 lanes and 12 Gbps `mode_4lanes_12gbps`. + """ + def __init__(self, mode_3lanes_3gbps: int = 0, mode_3lanes_6gbps: int = 0, mode_4lanes_6gbps: int = 0, + mode_4lanes_8gbps: int = 0, mode_4lanes_10gbps: int = 0, mode_4lanes_12gbps: int = 0): + self.__mode_3lanes_3gbps = mode_3lanes_3gbps + self.__mode_3lanes_6gbps = mode_3lanes_6gbps + self.__mode_4lanes_6gbps = mode_4lanes_6gbps + self.__mode_4lanes_8gbps = mode_4lanes_8gbps + self.__mode_4lanes_10gbps = mode_4lanes_10gbps + self.__mode_4lanes_12gbps = mode_4lanes_12gbps + + @property + def mode_3lanes_3gbps(self) -> int: + """ + Returns current value for mode 3 lanes and 3 Gbps. + + Returns: + object of int type + """ + return self.__mode_3lanes_3gbps + + @mode_3lanes_3gbps.setter + def mode_3lanes_3gbps(self, ffe_value: int): + """ + Set new FFE value for mode 3 lanes and 3 Gbps. + + Args: + ffe_value (int) + """ + if not(0 <= ffe_value <= 3): + raise ValueError(f"FFE value must be in range 0-3. Current value = {ffe_value}") + self.__mode_3lanes_3gbps = ffe_value + + @property + def mode_3lanes_6gbps(self) -> int: + """ + Returns current value for mode 3 lanes and 6 Gbps. + + Returns: + object of int type + """ + return self.__mode_3lanes_6gbps + + @mode_3lanes_6gbps.setter + def mode_3lanes_6gbps(self, ffe_value: int): + """ + Set new FFE value for mode 3 lanes and 6 Gbps. + + Args: + ffe_value (int) + """ + if not(0 <= ffe_value <= 3): + raise ValueError(f"FFE value must be in range 0-3. Current value = {ffe_value}") + self.__mode_3lanes_6gbps = ffe_value + + @property + def mode_4lanes_6gbps(self) -> int: + """ + Returns current value for mode 6 lanes and 6 Gbps. + + Returns: + object of int type + """ + return self.__mode_4lanes_6gbps + + @mode_4lanes_6gbps.setter + def mode_4lanes_6gbps(self, ffe_value: int): + """ + Set new FFE value for mode 4 lanes and 6 Gbps. + + Args: + ffe_value (int) + """ + if not (0 <= ffe_value <= 3): + raise ValueError(f"FFE value must be in range 0-3. Current value = {ffe_value}") + self.__mode_4lanes_6gbps = ffe_value + + @property + def mode_4lanes_8gbps(self) -> int: + """ + Returns current value for mode 6 lanes and 8 Gbps. + + Returns: + object of int type + """ + return self.__mode_4lanes_8gbps + + @mode_4lanes_8gbps.setter + def mode_4lanes_8gbps(self, ffe_value: int): + """ + Set new FFE value for mode 4 lanes and 8 Gbps. + + Args: + ffe_value (int) + """ + if not (0 <= ffe_value <= 3): + raise ValueError(f"FFE value must be in range 0-3. Current value = {ffe_value}") + self.__mode_4lanes_8gbps = ffe_value + + @property + def mode_4lanes_10gbps(self) -> int: + """ + Returns current value for mode 6 lanes and 10 Gbps. + + Returns: + object of int type + """ + return self.__mode_4lanes_10gbps + + @mode_4lanes_10gbps.setter + def mode_4lanes_10gbps(self, ffe_value: int): + """ + Set new FFE value for mode 4 lanes and 10 Gbps. + + Args: + ffe_value (int) + """ + if not (0 <= ffe_value <= 3): + raise ValueError(f"FFE value must be in range 0-3. Current value = {ffe_value}") + self.__mode_4lanes_10gbps = ffe_value + + @property + def mode_4lanes_12gbps(self) -> int: + """ + Returns current value for mode 6 lanes and 12 Gbps. + + Returns: + object of int type + """ + return self.__mode_4lanes_12gbps + + @mode_4lanes_12gbps.setter + def mode_4lanes_12gbps(self, ffe_value: int): + """ + Set new FFE value for mode 4 lanes and 12 Gbps. + + Args: + ffe_value (int) + """ + if not (0 <= ffe_value <= 3): + raise ValueError(f"FFE value must be in range 0-3. Current value = {ffe_value}") + self.__mode_4lanes_12gbps = ffe_value + + def value(self) -> int: + """ + Returns current combined value from all modes. + + Returns: + object of int type + """ + return ((self.mode_3lanes_3gbps & 0x3) << 5) | ((self.mode_3lanes_6gbps & 0x3) << 7) |\ + ((self.mode_4lanes_6gbps & 0x3) << 9) | ((self.mode_4lanes_8gbps & 0x3) << 11) | \ + ((self.mode_4lanes_10gbps & 0x3) << 13) | ((self.mode_4lanes_12gbps & 0x3) << 15) + + def __str__(self): + return f"3 lanes 3 Gbps - {self.mode_3lanes_3gbps}\n" \ + f"3 lanes 6 Gbps - {self.mode_3lanes_6gbps}\n" \ + f"4 lanes 6 Gbps - {self.mode_4lanes_6gbps}\n" \ + f"4 lanes 8 Gbps - {self.mode_4lanes_8gbps}\n" \ + f"4 lanes 10 Gbps - {self.mode_4lanes_10gbps}\n" \ + f"4 lanes 12 Gbps - {self.mode_4lanes_12gbps}\n" + + +class FrlControlTx: + """ + Class `FrlControlTx` contains information about FRL on Source (TX - transmitter) side. + allows working with: + - Set and get FRL mode `frl_mode`. + - Set and get FRL capabilities `frl_caps`. + - Set and get FFE max `ffe_max`. + - Set and get LTP requested `ltp_pattern` and additional `ltp_additional_pattern`. + - Set link training timeout `lt_timeout` and link training poll timeout `lt_poll_timeout`. + """ + def __init__(self, port_io: PortIO): + self.__io = port_io + self.__ffe_max = FfeMax(0) + self.__frl_caps = FrlCaps() + self.__lt_timeout = 0 + self.__lt_poll_timeout = 0 + self.__ltp_request = LtpLanesPattern() + self.__ltp_additional = LtpLanesPattern() + + def __read_caps(self) -> int: + return self.__io.get(TSI_HDTX_FRL_CAPABILITY, c_uint32)[1] + + def __write_caps(self, value: int): + self.__io.set(TSI_HDTX_FRL_CAPABILITY, value, c_uint32) + + def __read_frl_timers(self) -> int: + return self.__io.get(TSI_HDTX_FRL_TIMERS, c_uint32)[1] + + def __write_frl_timers(self, value: int): + self.__io.set(TSI_HDTX_FRL_TIMERS, value, c_uint32) + + def __read_frl_patterns(self) -> int: + return self.__io.get(TSI_HDTX_FRL_PATTERN_R, c_uint32)[1] + + @property + def ltp_pattern(self) -> LtpLanesPattern: + """ + Returns current LTP lanes pattern. Current Pattern that is being checking during Link Training. + Each lane can request different pattern. + + Returns: + object of `LtpLanesPattern` type + """ + _update_ltp_pattern_values(self.__ltp_request, self.__read_frl_patterns()) + return self.__ltp_request + + @property + def ltp_additional_pattern(self) -> LtpLanesPattern: + """ + Returns current additional LTP lanes pattern. Current Pattern that is being checking during Link Training. + Each lane can request different pattern. + + Returns: + object of `LtpLanesPattern` type + """ + _update_ltp_pattern_values(self.__ltp_additional, self.__read_frl_patterns() >> 16) + return self.__ltp_additional + + @property + def frl_caps(self) -> FrlCaps: + """ + Returns current FRL capabilities. + + Returns: + object of `FrlCaps` type + """ + _update_frl_values(self.__frl_caps, self.__io.get(TSI_HDTX_FRL_STATUS_R, c_uint32)[1]) + return self.__frl_caps + + @property + def frl_mode(self) -> FrlMode: + """ + Returns current FRL mode. + + Returns: + object of `FrlMode` type + """ + return FrlMode(self.__read_caps() & 0x7) + + @frl_mode.setter + def frl_mode(self, frl_mode: FrlMode): + """ + Set new FRL mode. + + Args: + frl_mode (FrlMode) + """ + new_value = frl_mode.value + new_value |= self.__read_caps() & ~0x7 + self.__write_caps(new_value) + + @property + def ffe_max(self) -> FfeMax: + """ + Returns current FFE MAX values. + + Returns: + object of `FfeMax` type + """ + ffe_values = self.__read_caps() + self.__update_values(self.__ffe_max, ffe_values) + return self.__ffe_max + + @ffe_max.setter + def ffe_max(self, ffe_max: FfeMax): + """ + Set new FFE max values. + + Args: + ffe_max (FfeMax) + """ + new_value = ffe_max.value() + new_value |= (self.__read_caps() >> 5) & ~0xFFF + self.__write_caps(new_value) + self.__ffe_max = ffe_max + + @property + def lt_timeout(self) -> int: + """ + Returns current value of link training timeout. + + Returns: + object of int type + """ + self.__lt_timeout = self.__read_frl_timers() & 0xFFFF + return self.__lt_timeout + + @lt_timeout.setter + def lt_timeout(self, lt_timeout: int): + """ + Set new link training timeout. + + Args: + lt_timeout (int) + """ + new_value = lt_timeout + new_value |= self.__read_frl_timers() & ~0xFFFF + self.__write_frl_timers(new_value) + self.__lt_timeout = lt_timeout + + @property + def lt_poll_timeout(self) -> int: + """ + Returns current value of link training poll timeout. + + Returns: + object of int type + """ + self.__lt_timeout = self.__read_frl_timers() & 0xFFFF + return self.__lt_timeout + + @lt_poll_timeout.setter + def lt_poll_timeout(self, lt_poll_timeout: int): + """ + Set new link training poll timeout. + + Args: + lt_poll_timeout (int) + """ + new_value = lt_poll_timeout + new_value |= self.__read_frl_timers() & ~(0xFFFF << 16) + self.__write_frl_timers(new_value) + self.__lt_poll_timeout = lt_poll_timeout + + def link_training(self): + """ + Do link training. + """ + self.__io.set(TSI_HDTX_SINK_FEATURE_W, 5, c_uint32) + + @staticmethod + def __update_values(ffe_max: FfeMax, value: int): + ffe_max.mode_3lanes_3gbps = (value >> 5) & 0x3 + ffe_max.mode_3lanes_6gbps = (value >> 7) & 0x3 + ffe_max.mode_4lanes_6gbps = (value >> 9) & 0x3 + ffe_max.mode_4lanes_8gbps = (value >> 11) & 0x3 + ffe_max.mode_4lanes_10gbps = (value >> 13) & 0x3 + ffe_max.mode_4lanes_12gbps = (value >> 15) & 0x3 + + def __str__(self): + return f"FRL Mode: {self.frl_mode.name}\n" \ + f"FRL Caps:\n{self.frl_caps.__str__()}" \ + f"LTP Lanes Pattern:\n{self.ltp_pattern.__str__()}" \ + f"LTP Lanes Pattern Additional:\n{self.ltp_additional_pattern.__str__()}" \ + f"LT timeout: {self.lt_timeout}\n" \ + f"LT poll timeout: {self.lt_poll_timeout}\n" \ + f"FFE Max:\n{self.ffe_max.__str__()}\n" diff --git a/UniTAP/dev/ports/modules/link/hdmi/hdmi_utils.py b/UniTAP/dev/ports/modules/link/hdmi/hdmi_utils.py new file mode 100644 index 0000000..d067bf7 --- /dev/null +++ b/UniTAP/dev/ports/modules/link/hdmi/hdmi_utils.py @@ -0,0 +1,53 @@ +from UniTAP.common import VideoMode, ColorInfo, Timing + + +def get_vm_color_format(color_mode: int) -> ColorInfo.ColorFormat: + + if color_mode == 0: + return ColorInfo.ColorFormat.CF_NONE + elif color_mode == 1: + return ColorInfo.ColorFormat.CF_UNKNOWN + elif color_mode == 2: + return ColorInfo.ColorFormat.CF_RGB + elif color_mode == 3: + return ColorInfo.ColorFormat.CF_YCbCr_422 + elif color_mode == 4: + return ColorInfo.ColorFormat.CF_YCbCr_444 + elif color_mode == 5: + return ColorInfo.ColorFormat.CF_YCbCr_420 + else: + return ColorInfo.ColorFormat.CF_IDO_DEFINED + + +def get_vm_colorimetry(colorimetry: int) -> ColorInfo.Colorimetry: + + if colorimetry == 0: + return ColorInfo.Colorimetry.CM_RESERVED + elif colorimetry == 1: + return ColorInfo.Colorimetry.CM_NONE + elif colorimetry == 2: + return ColorInfo.Colorimetry.CM_sRGB + elif colorimetry == 3: + return ColorInfo.Colorimetry.CM_SMPTE_170M + elif colorimetry == 4: + return ColorInfo.Colorimetry.CM_ITUR_BT601 + elif colorimetry == 5: + return ColorInfo.Colorimetry.CM_ITUR_BT709 + elif colorimetry == 6: + return ColorInfo.Colorimetry.CM_xvYCC601 + elif colorimetry == 7: + return ColorInfo.Colorimetry.CM_xvYCC709 + elif colorimetry == 8: + return ColorInfo.Colorimetry.CM_sYCC601 + elif colorimetry == 9: + return ColorInfo.Colorimetry.CM_AdobeYCC601 + elif colorimetry == 10: + return ColorInfo.Colorimetry.CM_AdobeRGB + elif colorimetry == 11: + return ColorInfo.Colorimetry.CM_ITUR_BT2020_YcCbcCrc + elif colorimetry == 12: + return ColorInfo.Colorimetry.CM_ITUR_BT2020_YCbCr + elif colorimetry == 13: + return ColorInfo.Colorimetry.CM_ITUR_BT2020_RGB + else: + return ColorInfo.Colorimetry.CM_RESERVED diff --git a/UniTAP/dev/ports/modules/link/hdmi/link.py b/UniTAP/dev/ports/modules/link/hdmi/link.py new file mode 100644 index 0000000..ca158de --- /dev/null +++ b/UniTAP/dev/ports/modules/link/hdmi/link.py @@ -0,0 +1,171 @@ +import warnings + +from UniTAP.libs.lib_tsi.tsi_io import PortIO +from .arc_rx import ArcRx +from .tmds_rx import TmdsRx +from .tmds_tx import TmdsTx +from .status_rx import StatusRx, HdmiModeRx +from .status_tx import StatusTx, HdmiModeTx +from .capabilities import HdmiCapabilities +from .frl_control_tx import FrlControlTx +from .frl_caps_rx import FrlControlRx +from UniTAP.libs.lib_tsi.tsi_types import TSI_HDTX_CAPABILITY_R, TSI_HDRX_CAPABILITY_R +from typing import Optional + + +class HdmiLinkTx: + """ + Main class describes HDMI link on Source (TX - transmitter) side. Contains following objects for working with link: + - TMDS `tmds`. + - FRL `frl`. + - Status `status`. + """ + def __init__(self, port_io: PortIO): + self.__io = port_io + self.__tmds = TmdsTx(self.__io) + self.__status = StatusTx(self.__io) + self.__frl = FrlControlTx(self.__io) + self.__caps = HdmiCapabilities(TSI_HDTX_CAPABILITY_R, self.__io) + + @property + def tmds(self) -> Optional[TmdsTx]: + """ + Returns object of class `TmdsTx` for working with TMDS. + None if TMDS does not support on the device. + + Returns: + object of `TmdsTx`|None type + """ + if self.__status.hdmi_mode == HdmiModeTx.HDMI_2_1: + warnings.warn("Current device mode is HDMI 2.1, please, use FRL controls or change HDMI mode.") + return None + if not self.__caps.support_tmds: + warnings.warn("Current device does not support TMDS.") + return None + + return self.__tmds + + @property + def frl(self) -> Optional[FrlControlTx]: + """ + Returns object of class `FrlControlTx` for working with FRL. + None if FRL does not support on the device. + + Returns: + object of `FrlControlTx`|None type + """ + if self.__status.hdmi_mode != HdmiModeTx.HDMI_2_1: + warnings.warn("Current device mode is not HDMI 2.1, please, use TMDS controls or change HDMI mode.") + return None + if not self.__caps.support_frl: + warnings.warn("Current device does not support FRl.") + return None + + return self.__frl + + @property + def status(self) -> StatusTx: + """ + Returns object of class `StatusTx` for working with link status. + + Returns: + object of `StatusTx` type + """ + return self.__status + + def __str__(self): + __str = f"Status:\n{self.status.__str__()}\n" + if self.__status.hdmi_mode == HdmiModeTx.HDMI_2_1 and self.__caps.support_frl: + __str += f"FRL:\n{self.frl.__str__()}\n" + if self.__status.hdmi_mode != HdmiModeTx.HDMI_2_1 and self.__caps.support_tmds: + __str += f"TMDS:\n{self.tmds.__str__()}\n" + return __str + + +class HdmiLinkRx: + """ + Main class describes HDMI link on Sink (RX - receiver) side. Contains following objects for working with link: + - TMDS `tmds`. + - FRL `frl`. + - ARC `arc`. + - Status `status`. + """ + def __init__(self, port_io: PortIO): + self.__io = port_io + self.__tmds = TmdsRx(self.__io) + self.__status = StatusRx(self.__io) + self.__frl = FrlControlRx(self.__io) + self.__arc = ArcRx(self.__io) + self.__caps = HdmiCapabilities(TSI_HDRX_CAPABILITY_R, self.__io) + + @property + def tmds(self) -> Optional[TmdsRx]: + """ + Returns object of class `TmdsRx` for working with TMDS. + None if TMDS does not support on the device. + + Returns: + object of `TmdsRx`|None type + """ + if self.__status.hdmi_mode == HdmiModeTx.HDMI_2_1: + warnings.warn("Current device mode is HDMI 2.1, please, use FRL controls or change HDMI mode.") + return None + if not self.__caps.support_tmds: + warnings.warn("Current device does not support TMDS.") + return None + + return self.__tmds + + @property + def frl(self) -> Optional[FrlControlRx]: + """ + Returns object of class `FrlControlRx` for working with FRL. + None if FRL does not support on the device. + + Returns: + object of `FrlControlRx`|None type + """ + if self.__status.hdmi_mode != HdmiModeRx.HDMI_2_1: + warnings.warn("Current device mode is not HDMI 2.1, please, use TMDS controls or change HDMI mode.") + return None + if not self.__caps.support_frl: + warnings.warn("Current device does not support FRl.") + return None + + return self.__frl + + @property + def arc(self) -> Optional[ArcRx]: + """ + Returns object of class `ArcRx` for working with ARC. + None if ARC does not support on the device. + + Returns: + object of `ArcRx`|None type + """ + if not self.__caps.support_arc: + warnings.warn("Current device does not support ARC.") + return None + + return self.__arc + + @property + def status(self) -> StatusRx: + """ + Returns object of class `StatusRx` for working with link status. + + Returns: + object of `StatusRx` type + """ + return self.__status + + def __str__(self): + __str = f"Status:\n{self.status.__str__()}\n" + if self.__status.hdmi_mode == HdmiModeTx.HDMI_2_1 and self.__caps.support_frl: + __str += f"FRL:\n{self.frl.__str__()}\n" + if self.__status.hdmi_mode != HdmiModeTx.HDMI_2_1 and self.__caps.support_tmds: + __str += f"TMDS:\n{self.tmds.__str__()}\n" + if self.__caps.support_arc: + __str += f"ARC:\n{self.arc.__str__()}\n" + __str += f"Stream info:\n{self.status.stream(0).__str__()}\n" + return __str diff --git a/UniTAP/dev/ports/modules/link/hdmi/private_types.py b/UniTAP/dev/ports/modules/link/hdmi/private_types.py new file mode 100644 index 0000000..ed6badb --- /dev/null +++ b/UniTAP/dev/ports/modules/link/hdmi/private_types.py @@ -0,0 +1,52 @@ +from ctypes import Structure, c_uint16, c_uint32 + + +class HdmiCrc(Structure): + _fields_ = [ + ('r', c_uint16), + ('g', c_uint16), + ('b', c_uint16) + ] + + +class HdmiVideoMode(Structure): + _fields_ = [ + ('h_total', c_uint32), + ('v_total', c_uint32), + ('h_active', c_uint32), + ('v_active', c_uint32), + ('f_rate', c_uint32), + ('color_mode', c_uint32), + ('colorimetry', c_uint32), + ('bpp', c_uint32), + ('h_start', c_uint32), + ('v_start', c_uint32), + ('h_sync_width', c_uint32), + ('v_sync_width', c_uint32), + ('bpc', c_uint32) + ] + + +class HdmiVideoModeInfo: + + def __init__(self, hdmi_vm: HdmiVideoMode): + self.h_total = hdmi_vm.h_total + self.v_total = hdmi_vm.v_total + self.h_active = hdmi_vm.h_active + self.v_active = hdmi_vm.v_active + self.h_start = hdmi_vm.h_start + self.v_start = hdmi_vm.v_start + self.h_sync_width = hdmi_vm.h_sync_width + self.v_sync_width = hdmi_vm.v_sync_width + self.frame_rate = hdmi_vm.f_rate + self.bpp = hdmi_vm.bpp + self.color_mode = hdmi_vm.color_mode + self.colorimetry = hdmi_vm.colorimetry + self.bpc = hdmi_vm.bpc + + def is_eq(self, other) -> bool: + return self.h_total == other.h_total and self.v_total == other.v_total and self.h_active == other.h_active and \ + self.v_active == other.v_active and self.h_start == other.h_start and self.v_start == other.v_start and \ + self.h_sync_width == other.h_sync_start and self.v_sync_width == other.v_sync_start and \ + self.frame_rate == other.frame_rate and self.bpp == other.bpp and self.color_mode == other.color_mode \ + and self.colorimetry == self.colorimetry and self.bpc == self.bpc diff --git a/UniTAP/dev/ports/modules/link/hdmi/status_rx.py b/UniTAP/dev/ports/modules/link/hdmi/status_rx.py new file mode 100644 index 0000000..74dedcf --- /dev/null +++ b/UniTAP/dev/ports/modules/link/hdmi/status_rx.py @@ -0,0 +1,151 @@ +import time + +from UniTAP.libs.lib_tsi.tsi_io import PortIO +from .types import HdmiModeRx, FrlMode, _update_error_counters_rx +from UniTAP.libs.lib_tsi.tsi_types import TSI_HDRX_BEHAVIOR, TSI_HDRX_LANES_ERR_COUNTERS_R, TSI_HDRX_HPD_STATUS_R,\ + TSI_HDRX_FRL_CAPABILITY, TSI_HDRX_LINK_STATUS_R, TSI_HDRX_HPD_CONTROL_W, TSI_HDRX_VIDEO_MODE_R, \ + TSI_VIDCAP_SIGNAL_CRC_R, TSI_SUCCESS +from ctypes import c_uint8, c_uint32, c_uint64 +from UniTAP.utils.function_wrapper import function_scheduler +from .types import _hdmi_vm_info_to_video_mode, HdmiVideoModeInfo +from .private_types import HdmiCrc, HdmiVideoMode +from ..dp.link_status_common import StreamStatus + + +class StatusRx: + """ + Class `StatusRx` describes information about HDMI link status on Sink (RX - receiver) side. Contains following info: + - HDMI mode `hdmi_mode`. + - Link Error counters `error_counters`. + - Channel lock `channel_lock`. + - HPD status `hpd_status` and HPD assert `set_assert_state`. + - Stream info `stream`. + """ + def __init__(self, port_io: PortIO): + self.__io = port_io + + def __frl_status(self) -> FrlMode: + return FrlMode(self.__io.get(TSI_HDRX_FRL_CAPABILITY, c_uint32)[1] & 0xF) + + @property + def hdmi_mode(self) -> HdmiModeRx: + """ + Returns current HDMI mode. + + Returns: + object of `HdmiModeRx` type + """ + return HdmiModeRx(self.__io.get(TSI_HDRX_BEHAVIOR, c_uint32)[1]) + + @hdmi_mode.setter + def hdmi_mode(self, hdmi_mode: HdmiModeRx): + """ + Set new HDMI mode. + + Args: + hdmi_mode (HdmiModeRx) + """ + self.__io.set(TSI_HDRX_BEHAVIOR, hdmi_mode.value, c_uint32) + + @property + def error_counters(self) -> list: + """ + Returns values of current errors on link. + + Returns: + object of list type + """ + return _update_error_counters_rx(self.__io.get(TSI_HDRX_LANES_ERR_COUNTERS_R, c_uint64)[1]) + + def set_assert_state(self, asserted: bool = True): + """ + Assert/Deassert HPD state. + + Args: + asserted (bool) + """ + self.__io.set(TSI_HDRX_HPD_CONTROL_W, int(asserted), c_uint32) + + @property + def channel_lock(self) -> list: + """ + Returns channel lock states of current link. + + Returns: + object of list type + """ + return self.__update_channel_lock(self.__io.get(TSI_HDRX_LINK_STATUS_R, c_uint32)[1], self.hdmi_mode) + + @staticmethod + def __update_channel_lock(value: int, mode: HdmiModeRx) -> list: + if mode != HdmiModeRx.HDMI_2_1: + lane_0 = (value & (1 << 4)) != 0 + lane_1 = (value & (1 << 5)) != 0 + lane_2 = (value & (1 << 6)) != 0 + lane_3 = False + + else: + lane_0 = (value & (1 << 13)) != 0 + lane_1 = (value & (1 << 14)) != 0 + lane_2 = (value & (1 << 15)) != 0 + lane_3 = (value & (1 << 16)) != 0 + return [lane_0, lane_1, lane_2, lane_3] + + @property + def hpd_status(self) -> bool: + """ + Returns True if HDP is enabled, False - if not. + + Returns: + object of bool type + """ + return (self.__io.get(TSI_HDRX_HPD_STATUS_R, c_uint32)[1] & 0x1) != 0 + + def __check_video(self) -> bool: + def is_msa_available(io): + result, msa_info, size = io.get(TSI_HDRX_VIDEO_MODE_R, HdmiVideoMode, 4) + return result > 0 + + return function_scheduler(is_msa_available, self.__io, interval=5, timeout=10) + + def stream(self, stream_index: int = 0) -> StreamStatus: + """ + Returns status of selected stream `StreamStatus`. + + Args: + stream_index (int) - number of selected number + + Returns: + object of `StreamStatus` type + """ + stream_status = StreamStatus() + + if not self.__check_video(): + raise ValueError("Video is not available.") + + time.sleep(1) + + result, hdmi_video_mode_info, size = self.__io.get(TSI_HDRX_VIDEO_MODE_R, HdmiVideoMode, 4) + + # TODO - Size temporary will be equal 1 + size = 1 + + if 0 <= stream_index < size: + stream_status.video_mode = _hdmi_vm_info_to_video_mode(HdmiVideoModeInfo(hdmi_video_mode_info + [stream_index])) + result = self.__io.get(TSI_VIDCAP_SIGNAL_CRC_R, data_type=c_uint8, data_count=6) + if result[0] >= TSI_SUCCESS: + hdmi_crc = HdmiCrc.from_buffer(bytearray(result[1])) + stream_status.crc = [hdmi_crc.r, hdmi_crc.g, hdmi_crc.b] + + stream_status.dsc_crc = [0, 0, 0] + else: + raise ValueError(f"Selected stream {stream_index} is not available. " + f"Available stream number: {size}") + + return stream_status + + def __str__(self): + return f"HDMI Mode: {self.hdmi_mode.name}\n" \ + f"Channel Lock:\n{self.channel_lock}\n" \ + f"Error Counters:\n{self.error_counters.__str__()}\n" diff --git a/UniTAP/dev/ports/modules/link/hdmi/status_tx.py b/UniTAP/dev/ports/modules/link/hdmi/status_tx.py new file mode 100644 index 0000000..3b230a0 --- /dev/null +++ b/UniTAP/dev/ports/modules/link/hdmi/status_tx.py @@ -0,0 +1,140 @@ +from UniTAP.libs.lib_tsi.tsi_io import PortIO +from .types import HdmiModeTx, FrlMode, _update_error_counters_tx +from UniTAP.libs.lib_tsi.tsi_types import TSI_HDTX_FRL_STATUS_R, TSI_HDTX_CONTROL_W, TSI_HDTX_STATUS_R, \ + TSI_HDTX_SINK_STATUS_R, TSI_HDTX_LANES_ERR_COUNTERS_R, TSI_HDTX_HPD_STATUS_R +from ctypes import c_uint32, c_uint64 + + +class StatusTx: + """ + Class `StatusTx` describes information about HDMI link status on Source (TX - transmitter) side. + Contains following info: + - Set and get HDMI mode `hdmi_mode`. + - Link Error counters `error_counters`. + - Video status `video_status`. + - Channel lock `channel_lock`. + - HPD status `hpd_status`. + """ + def __init__(self, port_io: PortIO): + self.__io = port_io + + def __read_status(self) -> int: + return self.__io.get(TSI_HDTX_STATUS_R, c_uint32)[1] + + def __frl_status(self) -> FrlMode: + return FrlMode(self.__io.get(TSI_HDTX_FRL_STATUS_R, c_uint32)[1] & 0xF) + + @property + def video_status(self) -> bool: + """ + Returns current video status (video enable or not). + + Returns: + object of bool type + """ + return (self.__read_status() & 0x2) != 0 + + @property + def error_counters(self) -> list: + """ + Returns values of current errors on link. + + Returns: + object of list type + """ + return _update_error_counters_tx(self.__io.get(TSI_HDTX_LANES_ERR_COUNTERS_R, c_uint64)[1]) + + @property + def channel_lock(self) -> list: + """ + Returns channel lock states of current link. + + Returns: + object of list type + """ + if self.__frl_status().Mode_Disable or self.hdmi_mode in [HdmiModeTx.HDMI_1_4, HdmiModeTx.HDMI_2_0]: + return self.__update_channel_lock(self.__io.get(TSI_HDTX_SINK_STATUS_R, c_uint32)[1], self.hdmi_mode) + else: + return self.__update_channel_lock(self.__io.get(TSI_HDTX_FRL_STATUS_R, c_uint32)[1], self.hdmi_mode) + + @property + def hpd_status(self) -> bool: + """ + Returns True if HDP is enabled, False - if not. + + Returns: + object of bool type + """ + return (self.__io.get(TSI_HDTX_HPD_STATUS_R, c_uint32)[1] & 0x1) != 0 + + @property + def hdmi_mode(self) -> HdmiModeTx: + """ + Returns current HDMI mode. + + Returns: + object of `HdmiModeTx` type + """ + return HdmiModeTx((self.__read_status() >> 2) & 0x3) + + @hdmi_mode.setter + def hdmi_mode(self, hdmi_mode: HdmiModeTx): + """ + Set new HDMI mode. + + Args: + hdmi_mode (HdmiModeTx) + """ + self.__io.set(TSI_HDTX_CONTROL_W, hdmi_mode.value << 2, c_uint32) + + @property + def available_link_rate(self) -> float: + """ + + Returns available link rate. + + Returns: + object of float type + """ + + gbps = 1000000000 + frl_status = self.__frl_status() + if frl_status == FrlMode.Mode_Disable: + link_rate = 3 * 6 * gbps + elif frl_status == FrlMode.Mode_3lanes_3gbps: + link_rate = 3 * 3 * gbps + elif frl_status == FrlMode.Mode_3lanes_6gbps: + link_rate = 3 * 6 * gbps + elif frl_status == FrlMode.Mode_4lanes_6gbps: + link_rate = 4 * 6 * gbps + elif frl_status == FrlMode.Mode_4lanes_8gbps: + link_rate = 4 * 8 * gbps + elif frl_status == FrlMode.Mode_4lanes_10gbps: + link_rate = 4 * 10 * gbps + elif frl_status == FrlMode.Mode_4lanes_12gbps: + link_rate = 4 * 12 * gbps + else: + link_rate = 0 + + return link_rate + + @staticmethod + def __update_channel_lock(value, mode: HdmiModeTx) -> list: + if mode != HdmiModeTx.HDMI_2_1: + lane_0 = (value & (1 << 6)) != 0 + lane_1 = (value & (1 << 7)) != 0 + lane_2 = (value & (1 << 8)) != 0 + lane_3 = False + + else: + lane_0 = (((value >> 25) & 0xF) >> 0) & 0x1 != 0 + lane_1 = (((value >> 25) & 0xF) >> 1) & 0x1 != 0 + lane_2 = (((value >> 25) & 0xF) >> 2) & 0x1 != 0 + lane_3 = (((value >> 25) & 0xF) >> 3) & 0x1 != 0 + + return [lane_0, lane_1, lane_2, lane_3] + + def __str__(self): + return f"HDMI Mode: {self.hdmi_mode.name}\n" \ + f"Channel Lock:\n{self.channel_lock}\n" \ + f"Error Counters:\n{self.error_counters.__str__()}\n" diff --git a/UniTAP/dev/ports/modules/link/hdmi/tmds_rx.py b/UniTAP/dev/ports/modules/link/hdmi/tmds_rx.py new file mode 100644 index 0000000..5681ee5 --- /dev/null +++ b/UniTAP/dev/ports/modules/link/hdmi/tmds_rx.py @@ -0,0 +1,57 @@ +from UniTAP.libs.lib_tsi.tsi_io import PortIO +from .types import LinkMode +from UniTAP.libs.lib_tsi.tsi_types import TSI_HDRX_LINK_STATUS_R +from ctypes import c_uint32 + + +class TmdsRx: + """ + CLass `TmdsRx` allows working with TMDS on Sink (RX - receiver) side. + - Get link mode `link_mode`. + - Get clock rate `clock_rate`. + - Get input stream lock state `input_stream_lock`. + """ + def __init__(self, port_io: PortIO): + self.__io = port_io + self.__link_mode = LinkMode.Unknown + self.__input_stream_lock = False + + def __read_link_status(self) -> int: + return self.__io.get(TSI_HDRX_LINK_STATUS_R, c_uint32)[1] + + @property + def clock_rate(self) -> int: + """ + Returns current clock rate. + + Returns: + object of int type + """ + return 6 if self.__read_link_status() & 0x2 else 3 + + @property + def link_mode(self) -> LinkMode: + """ + Returns current link mode. + + Returns: + object of `LinkMode` type + """ + self.__link_mode = LinkMode(self.__read_link_status() & 0x8) + return self.__link_mode + + @property + def input_stream_lock(self) -> bool: + """ + Returns current state of input stream lock. + + Returns: + object of bool type + """ + self.__input_stream_lock = (self.__read_link_status() & 0x4) != 0 + return self.__input_stream_lock + + def __str__(self): + return f"Clock rate: {self.clock_rate}G\n" \ + f"Link mode: {self.link_mode.name}\n" \ + f"Input stream lock: {'Enable' if self.input_stream_lock else 'Disable'}" diff --git a/UniTAP/dev/ports/modules/link/hdmi/tmds_tx.py b/UniTAP/dev/ports/modules/link/hdmi/tmds_tx.py new file mode 100644 index 0000000..1af216a --- /dev/null +++ b/UniTAP/dev/ports/modules/link/hdmi/tmds_tx.py @@ -0,0 +1,99 @@ +import warnings + +from UniTAP.libs.lib_tsi.tsi_io import PortIO +from .types import ClockRate, LinkMode, ScramblerState +from UniTAP.libs.lib_tsi.tsi_types import TSI_HDTX_SINK_FEATURE_W, TSI_HDTX_STATUS_R, TSI_HDTX_SINK_STATUS_R, \ + TSI_HDTX_CONTROL_W +from ctypes import c_uint32 + + +class TmdsTx: + """ + CLass `TmdsRx` allows working with TMDS on Source (TX - transmitter) side. + - Get link mode `link_mode`. + - Set and get clock rate `clock_rate`. + - Set and get scrambler `scrambler`. + """ + def __init__(self, port_io: PortIO): + self.__io = port_io + self.__clock_rate = ClockRate.Unknown + self.__link_mode = LinkMode.Unknown + self.__scrambler = ScramblerState.Unknown + + def __read_sink_status(self) -> int: + return self.__io.get(TSI_HDTX_SINK_STATUS_R, c_uint32)[1] + + def __read_hdtx_status(self) -> int: + return self.__io.get(TSI_HDTX_STATUS_R, c_uint32)[1] + + def __write_sink_feature(self, value: int): + self.__io.set(TSI_HDTX_SINK_FEATURE_W, value, c_uint32) + + @property + def clock_rate(self) -> int: + """ + Returns current clock rate. + + Returns: + object of int type + """ + return 6 if self.__read_sink_status() & 0x2 else 3 + + @clock_rate.setter + def clock_rate(self, clock_rate: int): + """ + Set new clock rate value. Available variants: 3, 6. + + Args: + clock_rate (int) + """ + if clock_rate not in [3, 6]: + warnings.warn("Incorrect value. Must be from available values: 3, 6") + return + self.__write_sink_feature(3 if clock_rate == 3 else 4) + + @property + def link_mode(self) -> LinkMode: + """ + Returns current link mode. + + Returns: + object of `LinkMode` type + """ + self.__link_mode = LinkMode(self.__read_hdtx_status() & 0x1) + return self.__link_mode + + @link_mode.setter + def link_mode(self, link_mode: LinkMode): + """ + Set new link mode. + + Args: + link_mode (LinkMode) + """ + self.__io.set(TSI_HDTX_CONTROL_W, link_mode.value, c_uint32) + + @property + def scrambler(self) -> bool: + """ + Returns current scrambler state. + + Returns: + object of bool type + """ + return self.__read_sink_status() & 0x1 != 0 + + @scrambler.setter + def scrambler(self, scrambler: bool): + """ + Enable/disable scrambler. + + Args: + scrambler (bool) + """ + self.__write_sink_feature(1 if scrambler else 2) + + def __str__(self): + return f"Clock rate: {self.clock_rate}G\n" \ + f"Link mode: {self.link_mode.name}\n" \ + f"Scrambler: {'Enable' if self.scrambler else 'Disable'}\n" diff --git a/UniTAP/dev/ports/modules/link/hdmi/types.py b/UniTAP/dev/ports/modules/link/hdmi/types.py new file mode 100644 index 0000000..2d1af37 --- /dev/null +++ b/UniTAP/dev/ports/modules/link/hdmi/types.py @@ -0,0 +1,372 @@ +from enum import IntEnum +from .hdmi_utils import * +from .private_types import HdmiVideoModeInfo + + +class FrlMode(IntEnum): + """ + Class `FrlMode` contains all possible variants of FRL modes. + """ + Mode_Unknown = -1 + Mode_Disable = 0 + Mode_3lanes_3gbps = 1 + Mode_3lanes_6gbps = 2 + Mode_4lanes_6gbps = 3 + Mode_4lanes_8gbps = 4 + Mode_4lanes_10gbps = 5 + Mode_4lanes_12gbps = 6 + + +class LtpPattern(IntEnum): + """ + Class `LtpPattern` contains all possible variants of LTP pattern types. + """ + Unknown = -1 + NoLinkPattern = 0 + All_1s = 1 + All_0s = 2 + NyquistClockPattern = 3 + SourceTxFFECompliance = 4 + LFSR0 = 5 + LFSR1 = 6 + LFSR2 = 7 + LFSR3 = 8 + + +class ScramblerState(IntEnum): + """ + Class `ScramblerState` contains all possible variants of scrambler states. + """ + Unknown = -1 + Disable = 0 + Enable = 1 + + +class LinkMode(IntEnum): + """ + Class `LinkMode` contains all possible variants of Link modes. + """ + Unknown = -1 + HDMI = 0 + DVI = 1 + + +class ClockRate(IntEnum): + """ + Class `ClockRate` contains all possible variants of clock rate. + """ + Unknown = -1 + Rate3G = 0 + Rate6G = 1 + + +class HdmiModeTx(IntEnum): + """ + Class `HdmiModeTx` contains all possible variants of HDMI modes on TX side. + """ + Unknown = -1 + HDMI_2_0 = 0 + HDMI_1_4 = 1 + HDMI_2_1 = 2 + + +class HdmiModeRx(IntEnum): + """ + Class `HdmiModeRx` contains all possible variants of HDMI modes on RX side. + """ + Unknown = -1 + HDMI_1_4 = 0 + HDMI_2_0 = 1 + HDMI_2_1 = 2 + + +class LtpLanesPattern: + """ + Class `LtpLanesPattern` describes LTP pattern values on all lanes. + - Lane 0 `lane0`. + - Lane 1 `lane1`. + - Lane 2 `lane2`. + - Lane 3 `lane3`. + """ + def __init__(self): + self.__lane0 = LtpPattern.Unknown + self.__lane1 = LtpPattern.Unknown + self.__lane2 = LtpPattern.Unknown + self.__lane3 = LtpPattern.Unknown + + @property + def lane0(self) -> LtpPattern: + """ + Returns `LtpPattern` value of line 0. + + Returns: + object of `LtpPattern` type + """ + return self.__lane0 + + @lane0.setter + def lane0(self, lane0: LtpPattern): + """ + Set new value `LtpPattern` to lane 0. + + Args: + lane0 (LtpPattern) + """ + self.__lane0 = lane0 + + @property + def lane1(self) -> LtpPattern: + """ + Returns `LtpPattern` value of line 1. + + Returns: + object of `LtpPattern` type + """ + return self.__lane1 + + @lane1.setter + def lane1(self, lane1: LtpPattern): + """ + Set new value `LtpPattern` to lane 1. + + Args: + lane1 (LtpPattern) + """ + self.__lane1 = lane1 + + @property + def lane2(self) -> LtpPattern: + """ + Returns `LtpPattern` value of line 2. + + Returns: + object of `LtpPattern` type + """ + return self.__lane2 + + @lane2.setter + def lane2(self, lane2: LtpPattern): + """ + Set new value `LtpPattern` to lane 1. + + Args: + lane2 (LtpPattern) + """ + self.__lane2 = lane2 + + @property + def lane3(self) -> LtpPattern: + """ + Returns `LtpPattern` value of line 3. + + Returns: + object of `LtpPattern` type + """ + return self.__lane3 + + @lane3.setter + def lane3(self, lane3: LtpPattern): + """ + Set new value `LtpPattern` to lane 3. + + Args: + lane3 (LtpPattern) + """ + self.__lane3 = lane3 + + def value(self) -> int: + """ + Returns combines value of all lines. + + Returns: + object of int type + """ + return self.lane0.value | (self.lane1.value << 4) | (self.lane2.value << 8) | (self.lane3.value << 12) + + def __str__(self): + return f"Lane 0: {self.lane0}\n" \ + f"Lane 1: {self.lane1}\n" \ + f"Lane 2: {self.lane2}\n" \ + f"Lane 3: {self.lane3}\n" + + +class FrlCaps: + """ + Class `FrlCaps` describes FRL capabilities. + - FRL start `frl_start`. + - FLT no timeout `flt_no_timeout`. + - FLT ready `flt_ready` + - FRL max `frl_max`. + - Check patterns state `check_patterns`. + """ + def __init__(self): + self.__frl_start = False + self.__flt_no_timeout = False + self.__flt_ready = False + self.__frl_max = False + self.__check_patterns = False + + @property + def frl_start(self) -> bool: + """ + Returns state of FRL start. + + Returns: + object of bool type + """ + return self.__frl_start + + @frl_start.setter + def frl_start(self, frl_start: bool): + """ + Set new state to FRL start. + + Args: + frl_start (bool) + """ + self.__frl_start = frl_start + + @property + def flt_no_timeout(self) -> bool: + """ + Returns state of FLT no timeout. + + Returns: + object of bool type + """ + return self.__flt_no_timeout + + @flt_no_timeout.setter + def flt_no_timeout(self, flt_no_timeout: bool): + """ + Set new state to FLT no timeout. + + Args: + flt_no_timeout (bool) + """ + self.__flt_no_timeout = flt_no_timeout + + @property + def flt_ready(self) -> bool: + """ + Returns state of FLT ready. + + Returns: + object of bool type + """ + return self.__flt_ready + + @flt_ready.setter + def flt_ready(self, flt_ready: bool): + """ + Set new state to FLT ready. + + Args: + flt_ready (bool) + """ + self.__flt_ready = flt_ready + + @property + def frl_max(self) -> bool: + """ + Returns state of FLT max. + + Returns: + object of bool type + """ + return self.__frl_max + + @frl_max.setter + def frl_max(self, frl_max: bool): + """ + Set new state to FLT max. + + Args: + frl_max (bool) + """ + self.__frl_max = frl_max + + @property + def check_patterns(self) -> bool: + """ + Returns state of check patterns. + + Returns: + object of bool type + """ + return self.__check_patterns + + @check_patterns.setter + def check_patterns(self, check_patterns: bool): + """ + Set new state to check patterns. + + Args: + check_patterns (bool) + """ + self.__check_patterns = check_patterns + + def value(self) -> int: + """ + Returns combined value of all flags. + + Returns: + object of int type + """ + return (self.frl_start << 4) | (self.flt_ready << 5) | (self.flt_no_timeout << 6) | (self.frl_max << 7) | \ + (self.check_patterns << 8) + + def __str__(self): + return f"FRL Start: {self.__frl_start}\n" \ + f"FRL No Timeout: {self.__flt_no_timeout}\n" \ + f"FRL Max: {self.__frl_max}\n" \ + f"Check patterns: {self.__check_patterns}\n" + + +def _hdmi_vm_info_to_video_mode(hdmi_vm_info: HdmiVideoModeInfo): + video_mode = VideoMode() + + video_mode.timing.hactive = hdmi_vm_info.h_active + video_mode.timing.vactive = hdmi_vm_info.v_active + video_mode.timing.htotal = hdmi_vm_info.h_total + video_mode.timing.vtotal = hdmi_vm_info.v_total + video_mode.timing.hstart = hdmi_vm_info.h_start + video_mode.timing.vstart = hdmi_vm_info.v_start + video_mode.timing.hswidth = hdmi_vm_info.h_sync_width + video_mode.timing.vswidth = hdmi_vm_info.v_sync_width + + video_mode.color_info.colorimetry = get_vm_colorimetry(hdmi_vm_info.colorimetry) + video_mode.color_info.color_format = get_vm_color_format(hdmi_vm_info.color_mode) + video_mode.color_info.bpc = hdmi_vm_info.bpc + video_mode.color_info.dynamic_range = ColorInfo.DynamicRange.DR_VESA + + video_mode.timing.frame_rate = hdmi_vm_info.frame_rate + + return video_mode + + +def _update_frl_values(frl_caps: FrlCaps, value: int): + frl_caps.frl_start = bool(value >> 4 & 0x1) + frl_caps.flt_ready = bool(value >> 5 & 0x1) + frl_caps.flt_no_timeout = bool(value >> 6 & 0x1) + frl_caps.frl_max = bool(value >> 7 & 0x1) + frl_caps.check_patterns = bool(value >> 8 & 0x1) + + +def _update_ltp_pattern_values(pattern: LtpLanesPattern, value: int): + pattern.lane0 = LtpPattern(value & 0xF) + pattern.lane1 = LtpPattern((value >> 4) & 0xF) + pattern.lane2 = LtpPattern((value >> 8) & 0xF) + pattern.lane3 = LtpPattern((value >> 12) & 0xF) + + +def _update_error_counters_rx(value: int) -> list: + lane0 = value & 0x7FFF if (value >> 15) & 0x1 else 0 + lane1 = ((value >> 16) & 0xFFFF) if (value >> 31) & 0x1 else 0 + lane2 = ((value >> 32) & 0xFFFF) if (value >> 47) & 0x1 else 0 + lane3 = ((value >> 48) & 0xFFFF) if (value >> 63) & 0x1 else 0 + return [lane0, lane1, lane2, lane3] + + +def _update_error_counters_tx(value: int) -> list: + return [value & 0xFFFF, (value >> 16) & 0xFFFF, (value >> 32) & 0xFFFF, (value >> 48) & 0xFFFF] diff --git a/UniTAP/dev/ports/modules/panel_replay/__init__.py b/UniTAP/dev/ports/modules/panel_replay/__init__.py new file mode 100644 index 0000000..94c80fa --- /dev/null +++ b/UniTAP/dev/ports/modules/panel_replay/__init__.py @@ -0,0 +1,4 @@ +from .pr import PanelReplay, PanelReplayConfig, PanelReplayStatus +from .pr_types import * +from .pr_sink import SinkPanelReplay, SinkPanelSelfRefresh +from .pr_sink_types import PRCapsFlags, PrGranularityCaps, SuYGranularity, PSRCapsFlags, PSRCaps, PSRSetupTime diff --git a/UniTAP/dev/ports/modules/panel_replay/pr.py b/UniTAP/dev/ports/modules/panel_replay/pr.py new file mode 100644 index 0000000..5ccae64 --- /dev/null +++ b/UniTAP/dev/ports/modules/panel_replay/pr.py @@ -0,0 +1,73 @@ +from UniTAP.libs.lib_tsi.tsi_io import PortIO +from UniTAP.libs.lib_tsi.tsi import * +from .pr_private_types import * +from .pr_status import PanelReplayStatus +from .pr_config import PanelReplayConfig + + +class PanelReplay: + """ + Class `PanelReplay` contains information about Panel Replay feature. + - Read Panel Replay `status`. + - Configure Panel Replay `config`. + - Disable Panel Replay `disable`. + - Enable Active mode `active_mode`. + - Enable Inactive mode `inactive_mode`. + - Enable selective update `selective_update`. + """ + + def __init__(self, port_io: PortIO, pg_caps): + self.__io = port_io + self.__status = PanelReplayStatus(self.__io) + self.__config = PanelReplayConfig(self.__io, pg_caps) + self.__caps = self.__config.pr_caps + + @property + def status(self) -> PanelReplayStatus: + """ + Returns object of class `PanelReplayStatus` for working with Panel Replay Status. + + Returns: + object of `PanelReplayStatus` type + """ + return self.__status + + @property + def config(self) -> PanelReplayConfig: + """ + Returns object of class `PanelReplayConfig` for configuration PR. + + Returns: + object of `PanelReplayStatus` type + """ + return self.__config + + def disable(self): + """ + Disable Panel Replay + """ + self.__write_pr_control(PrControl.Disable) + + def active_mode(self): + """ + Enable active mode + """ + self.__write_pr_control(PrControl.EnableActiveMode) + + def inactive_mode(self): + """ + Enable inactive mode + """ + self.__write_pr_control(PrControl.EnableInactiveMode) + + def selective_update(self): + """ + Enable selective update mode (if device supports this feature) + """ + if self.__caps.pr_flags().selective_update: + self.__write_pr_control(PrControl.EnableSelectiveUpdate) + + def __write_pr_control(self, command: PrControl): + self.__io.set(TSI_PG_STREAM_SELECT, 0) + self.__io.set(TSI_PG_PR_CTRL_W, command.value) + diff --git a/UniTAP/dev/ports/modules/panel_replay/pr_config.py b/UniTAP/dev/ports/modules/panel_replay/pr_config.py new file mode 100644 index 0000000..c7eeca6 --- /dev/null +++ b/UniTAP/dev/ports/modules/panel_replay/pr_config.py @@ -0,0 +1,132 @@ +from UniTAP.libs.lib_tsi.tsi_io import PortIO +from UniTAP.libs.lib_tsi.tsi import * +from .pr_types import * +from .pr_private_types import * + + +class PanelReplayConfig: + """ + Class `PanelReplayConfig` contains information about Panel Replay Configuration. + - Set configuration object `PrSettings`, function `set`. + - Get configuration object `PrSettings`, function `get`. + """ + + def __init__(self, port_io: PortIO, pg_caps): + self.__io = port_io + self.__y_granularity_list = [e for e in YGranularity] + + self.__mode_list = [] + if pg_caps.flags.panel_replay: + self.__mode_list.append(PRMode.PR) + if pg_caps.flags.psr: + self.__mode_list.append(PRMode.PSR1) + self.__mode_list.append(PRMode.PSR2) + + @property + def pr_caps(self): + return self.__read_caps() + + def set(self, config: PrSettings): + """ + Write new configuration. + + Args: + config (PrSettings). + """ + caps = self.pr_caps + + self.__io.set(TSI_PG_STREAM_SELECT, 0) + pr_config, _ = self.__read_config() + + pr_config.early_transport = config.flags.early_transport if caps.pr_flags().early_transport else 0 + pr_config.crc_in_vsc_sdp = config.flags.crc_in_vsc_sdp if caps.pr_flags().crc_sel_update else 0 + pr_config.hpd_irq_as_sdp = config.flags.hpd_irq + pr_config.hpd_irq_vsc_sdp = config.flags.hpd_irq_vsc_sdp + pr_config.hpd_irq_rfb = config.flags.hpd_irq_rfb + pr_config.hpd_irq_crc = config.flags.hpd_irq_crc + pr_config.self_refresh = config.flags.refresh_rate_unlock + pr_config.ext_y_granularity = config.flags.ext_y_gran + pr_config.main_link_on = config.flags.main_link_remain_on + + if config.flags.y_granularity in self.get_available_y_granularity_values(): + y_gran_value = (caps.pr_flags().y_gran_value & (1 << config.flags.y_granularity.value)) > 0 + pr_config.y_gran_value = 1 << config.flags.y_granularity.value if y_gran_value else 0 + + if config.flags.mode in self.__mode_list: + pr_config.mode = config.flags.mode.value + + data = [pr_config.value] + for item in config.regions: + region = RegionConfig(0) + region.value = item + data.extend(region.value) + self.__io.set(TSI_PG_PR_CFG, data, data_count=len(data)) + + def get(self) -> PrSettings: + """ + Returns current Panel Replay Configuration. + + Returns: + object of `PrSettings` type + """ + pr_config, pr_regions = self.__read_config() + pr_settings = PrSettings() + pr_settings.flags = Flags(early_transport=pr_config.early_transport == 1, + crc_in_vsc_sdp=pr_config.crc_in_vsc_sdp == 1, + pr_mode=PRMode(pr_config.mode), + hpd_irq=pr_config.hpd_irq_as_sdp == 1, + hpd_irq_vsc_sdp=pr_config.hpd_irq_vsc_sdp == 1, + hpd_irq_rfb=pr_config.hpd_irq_rfb == 1, + hpd_irq_crc=pr_config.hpd_irq_crc == 1, + refresh_rate_unlock=pr_config.self_refresh == 1, + ext_y_gran=pr_config.ext_y_granularity == 1, + main_link_remain_on=pr_config.main_link_on == 1) + + value = pr_config.y_gran_value + bit_number = 0 + + while value: + if value & 0x1: + pr_settings.y_granularity = YGranularity(bit_number) + break + bit_number += 1 + value >>= 1 + + pr_settings.regions = pr_regions + return pr_settings + + def get_available_y_granularity_values(self) -> List[YGranularity]: + """ + Get available values for Y Granularity. + + Returns: + list of `YGranularity` type + """ + caps = self.pr_caps + gran_list = [] + + for i, enum_value in enumerate(YGranularity): + if caps.pr_flags().y_gran_value >> i & 0x1: + gran_list.append(enum_value) + + return gran_list + + def __read_config(self): + caps = self.pr_caps + + self.__io.set(TSI_PG_STREAM_SELECT, 0) + result = self.__io.get(TSI_PG_PR_CFG, c_uint32, caps.calculate_size())[1] + if not isinstance(result, list): + result = [0] + pr_config = PrConfig(result[0]) + pr_regions = [] + for i in range(1, len(result) - 1, 2): + pr_regions.append(RegionConfig(result[i] & 0xFFFF, + result[i] >> 16 & 0xFFFF, + result[i + 1] & 0xFFFF, + result[i + 1] >> 16 & 0xFFFF)) + return pr_config, pr_regions + + def __read_caps(self) -> PRCaps: + self.__io.set(TSI_PG_STREAM_SELECT, 0) + return self.__io.get(TSI_PG_PR_CAPS_R, PRCaps)[1] diff --git a/UniTAP/dev/ports/modules/panel_replay/pr_private_types.py b/UniTAP/dev/ports/modules/panel_replay/pr_private_types.py new file mode 100644 index 0000000..b797385 --- /dev/null +++ b/UniTAP/dev/ports/modules/panel_replay/pr_private_types.py @@ -0,0 +1,161 @@ +from ctypes import Structure, c_uint32, c_uint16 +from enum import IntEnum +from .pr_types import Flags, Region + + +class PrControl(IntEnum): + Disable = 0 + EnableInactiveMode = 1 + EnableActiveMode = 2 + EnableSelectiveUpdate = 3 + FullScreenLiveFrameUpdate = 4 + + +class PrConfig(Structure): + _fields_ = [ + ("early_transport", c_uint32, 1), + ("crc_in_vsc_sdp", c_uint32, 1), + ('mode', c_uint32, 2), + ('hpd_irq_as_sdp', c_uint32, 1), + ('hpd_irq_vsc_sdp', c_uint32, 1), + ('hpd_irq_rfb', c_uint32, 1), + ('hpd_irq_crc', c_uint32, 1), + ('self_refresh', c_uint32, 1), + ('ext_y_granularity', c_uint32, 1), + ('main_link_on', c_uint32, 1), + ("y_gran_value", c_uint32, 4), + ('reserved', c_uint32, 17) + ] + + @property + def value(self) -> int: + return (self.early_transport | self.crc_in_vsc_sdp << 1 | self.mode << 3 | self.hpd_irq_as_sdp << 4 | + self.hpd_irq_vsc_sdp << 5 | self.hpd_irq_rfb << 6 | self.hpd_irq_crc << 7 | self.self_refresh << 8 | + self.ext_y_granularity << 9 | self.main_link_on << 10 | self.y_gran_value << 13) + + +class RegionConfig(Structure): + _fields_ = [ + ("x", c_uint32, 16), + ("y", c_uint32, 16), + ('width', c_uint32, 16), + ('height', c_uint32, 16), + ] + + @property + def value(self) -> list: + return [self.x | self.y << 16, self.width | self.height << 16] + + @value.setter + def value(self, config: Region): + self.x = config.x + self.y = config.y + self.width = config.width + self.height = config.height + + +class PatternCaps(IntEnum): + PrNotSupported = 0 + PrOnly = 1 + PrSelectiveUpdate = 2 + PrSelectiveUpdateEarlyTransport = 3 + + +class PRCaps(Structure): + class PRFlags(Structure): + _fields_ = [ + ("selective_update", c_uint32, 1), + ("early_transport", c_uint32, 1), + ("crc_sel_update", c_uint32, 1), + ("y_gran_value", c_uint16, 16), + ('reserved', c_uint32, 13) + ] + + class SuRegions(Structure): + _fields_ = [ + ("number_regions", c_uint32, 8), + ("buffer_capacity", c_uint32, 8), + ('reserved', c_uint32, 16) + ] + + _fields_ = [ + ('flags', c_uint32), + ('su_regions', c_uint32), + ('reserved1', c_uint32, 4), + ('color_bars', c_uint32, 4), + ('chessboard', c_uint32, 4), + ('solid_color', c_uint32, 4), + ('solid_white', c_uint32, 4), + ('solid_red', c_uint32, 4), + ('solid_green', c_uint32, 4), + ('solid_blue', c_uint32, 4), + ('white_v_strips', c_uint32, 4), + ('gradient_h_strips', c_uint32, 4), + ('color_ramp', c_uint32, 4), + ('color_square', c_uint32, 4), + ('motion_pattern', c_uint32, 4), + ('custom', c_uint32, 4), + ('reserved2', c_uint32, 4), + ('square_window', c_uint32, 4), + ('dsc', c_uint32, 4) + ] + + def color_bars_caps(self) -> PatternCaps: + return PatternCaps(self.color_bars & 0x3) + + def chessboard_caps(self) -> PatternCaps: + return PatternCaps(self.chessboard & 0x3) + + def solid_color_caps(self) -> PatternCaps: + return PatternCaps(self.solid_color & 0x3) + + def solid_white_caps(self) -> PatternCaps: + return PatternCaps(self.solid_white & 0x3) + + def solid_red_caps(self) -> PatternCaps: + return PatternCaps(self.solid_red & 0x3) + + def solid_green_caps(self) -> PatternCaps: + return PatternCaps(self.solid_green & 0x3) + + def white_v_strips_caps(self) -> PatternCaps: + return PatternCaps(self.white_v_strips & 0x3) + + def gradient_h_strips_caps(self) -> PatternCaps: + return PatternCaps(self.gradient_h_strips & 0x3) + + def color_ramp_caps(self) -> PatternCaps: + return PatternCaps(self.color_ramp & 0x3) + + def color_square_caps(self) -> PatternCaps: + return PatternCaps(self.color_square & 0x3) + + def motion_pattern_caps(self) -> PatternCaps: + return PatternCaps(self.motion_pattern & 0x3) + + def custom_caps(self) -> PatternCaps: + return PatternCaps(self.custom & 0x3) + + def square_window_caps(self) -> PatternCaps: + return PatternCaps(self.square_window & 0x3) + + def dsc_caps(self) -> PatternCaps: + return PatternCaps(self.dsc & 0x3) + + def calculate_size(self) -> int: + return self.pr_su_regions().number_regions * self.pr_su_regions().buffer_capacity * 2 + 1 + + def pr_flags(self) -> PRFlags: + return self.PRFlags(self.flags & 0x1, (self.flags >> 1) & 0x1, (self.flags >> 2) & 0x1) + + def pr_su_regions(self) -> SuRegions: + return self.SuRegions(self.su_regions & 0xFF, (self.su_regions >> 8) & 0xFF) + + +class PrStatus(Structure): + _fields_ = [ + ("command_status", c_uint32, 8), + ("status", c_uint32, 8), + ('reserved', c_uint32, 8), + ('error', c_uint32, 8) + ] diff --git a/UniTAP/dev/ports/modules/panel_replay/pr_sink.py b/UniTAP/dev/ports/modules/panel_replay/pr_sink.py new file mode 100644 index 0000000..41ef4de --- /dev/null +++ b/UniTAP/dev/ports/modules/panel_replay/pr_sink.py @@ -0,0 +1,59 @@ +from UniTAP.libs.lib_tsi.tsi_io import PortIO +from .pr_sink_status import SinkPanelReplayStatus, SinkPanelSelfRefreshStatus +from .pr_sink_caps import SinkPanelReplayCaps, SinkPanelSelfRefreshCaps + + +class SinkPanelReplay: + + def __init__(self, port_io: PortIO): + self.__io = port_io + self.__status = SinkPanelReplayStatus(self.__io) + self.__caps = SinkPanelReplayCaps(self.__io) + + @property + def status(self) -> SinkPanelReplayStatus: + """ + Returns object of class `SinkPanelReplayStatus` for working with Sink Panel Replay Status. + + Returns: + object of `SinkPanelReplayStatus` type + """ + return self.__status + + @property + def caps(self) -> SinkPanelReplayCaps: + """ + Returns object of class `SinkPanelReplayCaps` for configuration Sink Panel Replay capabilities. + + Returns: + object of `SinkPanelReplayCaps` type + """ + return self.__caps + + +class SinkPanelSelfRefresh: + + def __init__(self, port_io: PortIO): + self.__io = port_io + self.__status = SinkPanelSelfRefreshStatus(self.__io) + self.__caps = SinkPanelSelfRefreshCaps(self.__io) + + @property + def status(self) -> SinkPanelSelfRefreshStatus: + """ + Returns object of class `SinkPanelSelfRefreshStatus` for working with Sink Panel Self Refresh Status. + + Returns: + object of `SinkPanelSelfRefreshStatus` type + """ + return self.__status + + @property + def caps(self) -> SinkPanelSelfRefreshCaps: + """ + Returns object of class `SinkPanelSelfRefreshCaps` for configuration Sink Panel Self Refresh capabilities. + + Returns: + object of `SinkPanelSelfRefreshCaps` type + """ + return self.__caps diff --git a/UniTAP/dev/ports/modules/panel_replay/pr_sink_caps.py b/UniTAP/dev/ports/modules/panel_replay/pr_sink_caps.py new file mode 100644 index 0000000..53a2a0b --- /dev/null +++ b/UniTAP/dev/ports/modules/panel_replay/pr_sink_caps.py @@ -0,0 +1,111 @@ +from UniTAP.libs.lib_tsi.tsi_io import PortIO +from .pr_sink_private_types import SinkPanelReplayCapsStruct, SinkPanelSelfRefreshCapsStruct +from .pr_sink_types import PRCapsFlags, PrGranularityCaps, SuYGranularity, PSRCapsFlags, PSRCaps, PSRSetupTime +from UniTAP.libs.lib_tsi.tsi import * +from typing import Tuple + + +class SinkPanelReplayCaps: + """ + Class `SinkPanelReplayCaps` contains information about Sink Panel Replay capabilities. + - Set configuration with parameters: `PRCapsFlags`, `PrGranularityCaps`, PrGranularityCaps`, `SuYGranularity`. + Function `set`. + - Get configuration: Tuple[`PRCapsFlags`, `PrGranularityCaps`, `PrGranularityCaps`, `SuYGranularity``]. + Function `get`. + """ + + def __init__(self, port_io: PortIO): + self.__io = port_io + + def set(self, flags: PRCapsFlags = PRCapsFlags(), + x_granularity: PrGranularityCaps = PrGranularityCaps.Line_1, + y_granularity: PrGranularityCaps = PrGranularityCaps.Line_1, + granularity: SuYGranularity = SuYGranularity()): + flags_value = (flags.pr_support | (flags.selective_update_support << 1) | (flags.early_transport_support << 2) | + (flags.dsc_decode_func_support << 10) | (flags.asynch_video_timing_support << 11) | + (flags.dsc_src_support << 12) | (flags.granularity_needed << 13) | + (flags.y_gran_extended_supported << 14) | (flags.link_off_supported << 15)) + + self.__write([flags_value, x_granularity.value, y_granularity.value, granularity.combined_value()]) + self.__io.set(TSI_DPRX_HPD_PULSE_W, 500000, c_int) + + def get(self) -> Tuple[PRCapsFlags, PrGranularityCaps, PrGranularityCaps, SuYGranularity]: + caps = self.__read() + + flags = PRCapsFlags(pr_support=caps.pr_support, + selective_update_support=caps.selective_update_support, + early_transport_support=caps.early_transport_support, + dsc_decode_func_support=caps.dsc_decode_func_support, + asynch_video_timing_support=caps.asynch_video_timing_support, + dsc_src_support=caps.dsc_src_support, + granularity_needed=caps.granularity_needed, + y_gran_extended_supported=caps.y_gran_extended_supported, + link_off_supported=caps.link_off_supported) + + x_granularity = PrGranularityCaps(caps.pr_x_granularity if caps.pr_x_granularity != 0 else 1) + y_granularity = PrGranularityCaps(caps.pr_y_granularity if caps.pr_y_granularity != 0 else 1) + + granularity = SuYGranularity(value_of_8=caps.su_y_granularity.value_of_8, + value_of_10=caps.su_y_granularity.value_of_10, + value_of_12=caps.su_y_granularity.value_of_12, + value_of_14=caps.su_y_granularity.value_of_14, + value_of_15=caps.su_y_granularity.value_of_15, + value_of_16=caps.su_y_granularity.value_of_16, + value_of_18=caps.su_y_granularity.value_of_18, + value_of_20=caps.su_y_granularity.value_of_20, + value_of_24=caps.su_y_granularity.value_of_24, + value_of_30=caps.su_y_granularity.value_of_30, + value_of_32=caps.su_y_granularity.value_of_32, + value_of_36=caps.su_y_granularity.value_of_36, + value_of_40=caps.su_y_granularity.value_of_40, + value_of_48=caps.su_y_granularity.value_of_48, + value_of_54=caps.su_y_granularity.value_of_54, + value_of_64=caps.su_y_granularity.value_of_64) + + return flags, x_granularity, y_granularity, granularity + + def __read(self) -> SinkPanelReplayCapsStruct: + return self.__io.get(TSI_DPRX_PR_CAPS, SinkPanelReplayCapsStruct)[1] + + def __write(self, data: list): + self.__io.set(TSI_DPRX_PR_CAPS, data, data_type=c_uint32, data_count=len(data)) + + +class SinkPanelSelfRefreshCaps: + """ + Class `SinkPanelSelfRefreshCaps` contains information about Sink Panel Self Refresh capabilities. + - Set configuration with parameters: `PSRCapsFlags`, `PrGranularityCaps`, PrGranularityCaps`. Function `set`. + - Get configuration: Tuple[`PSRCapsFlags`, `PrGranularityCaps`, `PrGranularityCaps`]. Function `get`. + """ + + def __init__(self, port_io: PortIO): + self.__io = port_io + + def set(self, flags: PSRCapsFlags = PSRCapsFlags(), + x_granularity: PrGranularityCaps = PrGranularityCaps.Line_1, + y_granularity: PrGranularityCaps = PrGranularityCaps.Line_1): + flags_value = (flags.psr_caps.value | (flags.link_training_req_psr1 << 8) | (flags.psr_setup_time.value << 9) | + (flags.y_coor_psr2_su << 12) | (flags.su_coordinates_shall_adhere << 13) | + (flags.no_update_aux_frame_sync << 14)) + + self.__write([flags_value, x_granularity.value, y_granularity.value]) + + def get(self) -> Tuple[PSRCapsFlags, PrGranularityCaps, PrGranularityCaps]: + caps = self.__read() + + flags = PSRCapsFlags(psr_caps=PSRCaps(caps.psr_caps), + link_training_req_psr1=caps.link_training_req_psr1, + psr_setup_time=PSRSetupTime(caps.psr_setup_time), y_coor_psr2_su=caps.y_coor_psr2_su, + su_coordinates_shall_adhere=caps.su_coordinates_shall_adhere, + no_update_aux_frame_sync=caps.no_update_aux_frame_sync) + + x_granularity = PrGranularityCaps(caps.pr_x_granularity if caps.pr_x_granularity != 0 else 1) + y_granularity = PrGranularityCaps(caps.pr_y_granularity if caps.pr_y_granularity != 0 else 1) + + return flags, x_granularity, y_granularity + + def __read(self) -> SinkPanelSelfRefreshCapsStruct: + return self.__io.get(TSI_DPRX_PSR_CAPS, SinkPanelSelfRefreshCapsStruct)[1] + + def __write(self, data: list): + self.__io.set(TSI_DPRX_PSR_CAPS, data, data_type=c_uint32, data_count=len(data)) \ No newline at end of file diff --git a/UniTAP/dev/ports/modules/panel_replay/pr_sink_private_types.py b/UniTAP/dev/ports/modules/panel_replay/pr_sink_private_types.py new file mode 100644 index 0000000..1b7b75b --- /dev/null +++ b/UniTAP/dev/ports/modules/panel_replay/pr_sink_private_types.py @@ -0,0 +1,111 @@ +from ctypes import Structure, c_uint32 + + +class SinkPanelReplayCapsStruct(Structure): + class SuYGranularityStruct(Structure): + _fields_ = [ + ("value_of_8", c_uint32, 1), + ("value_of_10", c_uint32, 1), + ("value_of_12", c_uint32, 1), + ("value_of_14", c_uint32, 1), + ("value_of_15", c_uint32, 1), + ("value_of_16", c_uint32, 1), + ("value_of_18", c_uint32, 1), + ("value_of_20", c_uint32, 1), + ("value_of_24", c_uint32, 1), + ("value_of_30", c_uint32, 1), + ("value_of_32", c_uint32, 1), + ("value_of_36", c_uint32, 1), + ("value_of_40", c_uint32, 1), + ("value_of_48", c_uint32, 1), + ("value_of_54", c_uint32, 1), + ("value_of_64", c_uint32, 1), + ("res", c_uint32, 16), + ] + + _fields_ = [ + ("pr_support", c_uint32, 1), + ("selective_update_support", c_uint32, 1), + ("early_transport_support", c_uint32, 1), + ("res", c_uint32, 7), + ("dsc_decode_func_support", c_uint32, 1), + ("asynch_video_timing_support", c_uint32, 1), + ("dsc_src_support", c_uint32, 1), + ("granularity_needed", c_uint32, 1), + ("y_gran_extended_supported", c_uint32, 1), + ("link_off_supported", c_uint32, 1), + ("res1", c_uint32, 16), + ("pr_x_granularity", c_uint32), + ("pr_y_granularity", c_uint32), + ("su_y_granularity", SuYGranularityStruct) + ] + + +class SinkPanelReplayStatusStruct(Structure): + + class PRStatusDebugStruct(Structure): + _fields_ = [ + ("res", c_uint32, 8), + ("psr_state", c_uint32, 1), + ("res1", c_uint32, 1), + ("crc_valid", c_uint32, 1), + ("su_coordinate_valid", c_uint32, 1), + ("res2", c_uint32, 20), + ] + + _fields_ = [ + ("active_frame_crc_err", c_uint32, 1), + ("rfb_storage_err", c_uint32, 1), + ("vsc_sdp_err", c_uint32, 1), + ("ass_dp_missing", c_uint32, 1), + ("res", c_uint32, 12), + ("self_status", c_uint32, 3), + ("frame_locked", c_uint32, 2), + ("frame_locked_status_valid", c_uint32, 1), + ("res1", c_uint32, 10), + ("debug", PRStatusDebugStruct) + ] + + +class SinkPanelSelfRefreshCapsStruct(Structure): + + _fields_ = [ + ("psr_caps", c_uint32, 8), + ("link_training_req_psr1", c_uint32, 1), + ("psr_setup_time", c_uint32, 3), + ("y_coor_psr2_su", c_uint32, 1), + ("su_coordinates_shall_adhere", c_uint32, 1), + ("no_update_aux_frame_sync", c_uint32, 1), + ("res", c_uint32, 17), + ("pr_x_granularity", c_uint32), + ("pr_y_granularity", c_uint32), + ] + + +class SinkPanelSelfRefreshStatusStruct(Structure): + + class PSRStatusDebugStruct(Structure): + _fields_ = [ + ("min_frame_count_reentry", c_uint32, 1), + ("last_actual_syn_latency", c_uint32, 1), + ("psr_state", c_uint32, 1), + ("update_rfb", c_uint32, 1), + ("crc_valid", c_uint32, 1), + ("su_valid", c_uint32, 1), + ("first_scan_line_su", c_uint32, 1), + ("last_scan_line_su", c_uint32, 1), + ("y_coordinate_valid", c_uint32, 1), + ("res", c_uint32, 17) + ] + + _fields_ = [ + ("link_crc_err", c_uint32, 1), + ("rfb_storage_err", c_uint32, 1), + ("vsc_sdp_err", c_uint32, 1), + ("res", c_uint32, 5), + ("su_psr_caps_change", c_uint32, 1), + ("res1", c_uint32, 1), + ("self_refresh", c_uint32, 3), + ("res2", c_uint32, 13), + ("debugStatus", PSRStatusDebugStruct) + ] diff --git a/UniTAP/dev/ports/modules/panel_replay/pr_sink_status.py b/UniTAP/dev/ports/modules/panel_replay/pr_sink_status.py new file mode 100644 index 0000000..889fccc --- /dev/null +++ b/UniTAP/dev/ports/modules/panel_replay/pr_sink_status.py @@ -0,0 +1,62 @@ +from UniTAP.libs.lib_tsi.tsi_io import PortIO +from .pr_sink_private_types import SinkPanelSelfRefreshStatusStruct, SinkPanelReplayStatusStruct +from .pr_sink_types import (SinkPRStatusFlags, SelfStatus, FrameStatus, SinkPSRStatusFlags, PRSSelfRefreshStatus) +from UniTAP.libs.lib_tsi.tsi import * + + +class SinkPanelReplayStatus: + """ + Class `SinkPanelReplayStatus` contains information about Sink Panel Replay Status. + - Get Flags status `flags`. + - Get Debug status `debug`. + """ + + def __init__(self, port_io: PortIO): + self.__io = port_io + + def flags(self) -> SinkPRStatusFlags: + """ + Returns current flags status. + + Returns: + object of `SinkPRStatusFlags` type + """ + pr_flags = self.__read() + return SinkPRStatusFlags(active_frame_crc_err=pr_flags.active_frame_crc_err, + rfb_storage_err=pr_flags.rfb_storage_err, + vsc_sdp_err=pr_flags.vsc_sdp_err, ass_dp_missing=pr_flags.ass_dp_missing, + self_status=SelfStatus(pr_flags.self_status), + frame_locked=FrameStatus(pr_flags.frame_locked), + frame_locked_status_valid=pr_flags.frame_locked_status_valid) + + def __read(self) -> SinkPanelReplayStatusStruct: + + return self.__io.get(TSI_DPRX_PR_STATUS, SinkPanelReplayStatusStruct)[1] + + +class SinkPanelSelfRefreshStatus: + """ + Class `SinkPanelReplayStatus` contains information about Sink Panel Self Refresh Status. + - Get Flags status `flags`. + - Get Debug status `debug`. + """ + + def __init__(self, port_io: PortIO): + self.__io = port_io + + def flags(self) -> SinkPSRStatusFlags: + """ + Returns current flags status. + + Returns: + object of `SinkPSRStatusFlags` type + """ + pr_flags = self.__read() + return SinkPSRStatusFlags(link_crc_err=pr_flags.link_crc_err, + rfb_storage_err=pr_flags.rfb_storage_err, + vsc_sdp_err=pr_flags.vsc_sdp_err, + su_psr_caps_change=pr_flags.su_psr_caps_change, + self_refresh=PRSSelfRefreshStatus(pr_flags.self_refresh)) + + def __read(self) -> SinkPanelSelfRefreshStatusStruct: + return self.__io.get(TSI_DPRX_PSR_STATUS, SinkPanelSelfRefreshStatusStruct)[1] diff --git a/UniTAP/dev/ports/modules/panel_replay/pr_sink_types.py b/UniTAP/dev/ports/modules/panel_replay/pr_sink_types.py new file mode 100644 index 0000000..d2785b3 --- /dev/null +++ b/UniTAP/dev/ports/modules/panel_replay/pr_sink_types.py @@ -0,0 +1,844 @@ +from enum import IntEnum + + +class SelfStatus(IntEnum): + """ + Class `SelfStatus` contains all possible variants of Sink Panel Replay Self Status. + """ + + Disable = 0 + Inactive = 1 + Entry = 2 + Active = 3 + InternalError = 7 + + def __str__(self): + if self.value == self.Disable: + return "Disable" + elif self.value == self.Inactive: + return "Inactive" + elif self.value == self.Entry: + return "Entry" + elif self.value == self.Active: + return "Active" + elif self.value == self.InternalError: + return "Internal error" + else: + return "Incorrect value" + + +class FrameStatus(IntEnum): + """ + Class `FrameStatus` contains all possible variants of Sink Panel Replay frame locked. + """ + Locked = 0 + Coasting = 1 + Governing = 2 + ReLocking = 3 + + def __str__(self): + if self.value == self.Locked: + return "Locked" + elif self.value == self.Coasting: + return "Coasting" + elif self.value == self.Governing: + return "Governing" + elif self.value == self.ReLocking: + return "Re Locking" + else: + return "Incorrect value" + + +class SinkPRStatusFlags: + + def __init__(self, active_frame_crc_err: bool = False, rfb_storage_err: bool = False, + vsc_sdp_err: bool = False, ass_dp_missing: bool = False, self_status: SelfStatus = SelfStatus.Disable, + frame_locked: FrameStatus = FrameStatus.Locked, frame_locked_status_valid: bool = False): + self.__active_frame_crc_err = active_frame_crc_err + self.__rfb_storage_err = rfb_storage_err + self.__vsc_sdp_err = vsc_sdp_err + self.__ass_dp_missing = ass_dp_missing + self.__self_status = self_status + self.__frame_locked = frame_locked + self.__frame_locked_status_valid = frame_locked_status_valid + + @property + def active_frame_crc_err(self) -> bool: + """ + Returns state of flag active frame crc error . + + Returns: + object of `bool` type + """ + return self.__active_frame_crc_err + + @property + def rfb_storage_err(self) -> bool: + """ + Returns state of flag rfb storage error. + + Returns: + object of `bool` type + """ + return self.__rfb_storage_err + + @property + def vsc_sdp_err(self) -> bool: + """ + Returns state of flag VSC SDP error. + + Returns: + object of `bool` type + """ + return self.__vsc_sdp_err + + @property + def ass_dp_missing(self) -> bool: + """ + Returns state of flag ASSDP missing. + + Returns: + object of `bool` type + """ + return self.__ass_dp_missing + + @property + def self_status(self) -> SelfStatus: + """ + Returns self status. + + Returns: + object of `SelfStatus` type + """ + return self.__self_status + + @property + def frame_locked(self) -> FrameStatus: + """ + Returns frame locked status. + + Returns: + object of `FrameStatus` type + """ + return self.__frame_locked + + @property + def frame_locked_status_valid(self) -> bool: + """ + Returns state of valid frame locked status. + + Returns: + object of `bool` type + """ + return self.__frame_locked_status_valid + + def __str__(self) -> str: + return (f"Active frame CRC error: {self.active_frame_crc_err}\n" + f"RFB Storage error: {self.rfb_storage_err}\n" + f"PR VSC SDP uncorrectable error: {self.vsc_sdp_err}\n" + f"Adaptive sync SDP missing and not disabled: {self.ass_dp_missing}\n" + f"Sink device self refresh status: {self.self_status.__str__()}\n" + f"Sink frame locked: {self.frame_locked.__str__()}\n" + f"Sink frame locked status valid: {self.frame_locked_status_valid}\n") + + +class PRSSelfRefreshStatus(IntEnum): + InActive = 0 + CaptureAndDisplayEntry = 1 + DisplayFromRFBEntry = 2 + CaptureAndDisplayActive = 3 + CaptureAndDisplayExit = 4 + InternalError = 7 + + def __str__(self): + if self.value == self.InActive: + return "Inactive" + elif self.value == self.CaptureAndDisplayEntry: + return "Capture and display (Entry)" + elif self.value == self.DisplayFromRFBEntry: + return "Display from RFB (Entry)" + elif self.value == self.CaptureAndDisplayActive: + return "Capture and display (Active)" + elif self.value == self.CaptureAndDisplayExit: + return "Capture and display (Exit_" + elif self.value == self.InternalError: + return "Internal Error" + else: + return "Incorrect version" + + +class SinkPSRStatusFlags: + + def __init__(self, link_crc_err: bool = False, rfb_storage_err: bool = False, vsc_sdp_err: bool = False, + su_psr_caps_change: bool = False, self_refresh: PRSSelfRefreshStatus = PRSSelfRefreshStatus.InActive): + self.__link_crc_err = link_crc_err + self.__rfb_storage_err = rfb_storage_err + self.__vsc_sdp_err = vsc_sdp_err + self.__su_psr_caps_change = su_psr_caps_change + self.__self_refresh = self_refresh + + @property + def link_crc_err(self) -> bool: + """ + Returns state of link crc error. + + Returns: + object of `bool` type + """ + return self.__link_crc_err + + @property + def rfb_storage_err(self) -> bool: + """ + Returns state of RFB storage error. + + Returns: + object of `bool` type + """ + return self.__rfb_storage_err + + @property + def vsc_sdp_err(self) -> bool: + """ + Returns state of vsc sdp error. + + Returns: + object of `bool` type + """ + return self.__vsc_sdp_err + + @property + def su_psr_caps_change(self) -> bool: + """ + Returns state of su PSR caps change. + + Returns: + object of `bool` type + """ + return self.__su_psr_caps_change + + @property + def self_refresh(self) -> PRSSelfRefreshStatus: + """ + Returns state of self refresh. + + Returns: + object of `bool` type + """ + return self.__self_refresh + + def __str__(self) -> str: + return (f"Link CRC error: {self.link_crc_err}\n" + f"FRB storage error: {self.rfb_storage_err}\n" + f"PSR2 VSC SDP uncorrectable error: {self.vsc_sdp_err}\n" + f"Sink device SU_PSR capability change: {self.su_psr_caps_change}\n" + f"Sink device self-refresh status: {self.self_refresh.__str__()}\n") + + +class PrGranularityCaps(IntEnum): + Line_1 = 1 + Line_2 = 2 + Line_4 = 4 + Line_8 = 8 + Line_16 = 16 + + def __str__(self) -> str: + if self.value == self.Line_1: + return "Line 1" + elif self.value == self.Line_2: + return "Line 2" + elif self.value == self.Line_4: + return "Line 4" + elif self.value == self.Line_8: + return "Line 8" + elif self.value == self.Line_16: + return "Line 16" + else: + return "Incorrect version" + + +class PRCapsFlags: + + def __init__(self, pr_support: bool = False, selective_update_support: bool = False, + early_transport_support: bool = False, dsc_decode_func_support: bool = False, + asynch_video_timing_support: bool = False, dsc_src_support: bool = False, + granularity_needed: bool = False, y_gran_extended_supported: bool = False, + link_off_supported: bool = False): + self.__pr_support = pr_support + self.__selective_update_support = selective_update_support + self.__early_transport_support = early_transport_support + self.__dsc_decode_func_support = dsc_decode_func_support + self.__asynch_video_timing_support = asynch_video_timing_support + self.__dsc_src_support = dsc_src_support + self.__granularity_needed = granularity_needed + self.__y_gran_extended_supported = y_gran_extended_supported + self.__link_off_supported = link_off_supported + + @property + def pr_support(self) -> bool: + """ + Returns PR support state. + + Returns: + object of `bool` type + """ + return self.__pr_support + + @pr_support.setter + def pr_support(self, value: bool): + self.__pr_support = value + + @property + def selective_update_support(self) -> bool: + """ + Returns state of selective update support. + + Returns: + object of `bool` type + """ + return self.__selective_update_support + + @selective_update_support.setter + def selective_update_support(self, value: bool): + self.__selective_update_support = value + + @property + def early_transport_support(self) -> bool: + """ + Returns state of early transport support. + + Returns: + object of `bool` type + """ + return self.__early_transport_support + + @early_transport_support.setter + def early_transport_support(self, value: bool): + self.__early_transport_support = value + + @property + def dsc_decode_func_support(self) -> bool: + """ + Returns state of DSC decode function support. + + Returns: + object of `bool` type + """ + return self.__dsc_decode_func_support + + @dsc_decode_func_support.setter + def dsc_decode_func_support(self, value: bool): + self.__dsc_decode_func_support = value + + @property + def asynch_video_timing_support(self) -> bool: + """ + Returns state of supports Asynchronous Video Timing. + + Returns: + object of `bool` type + """ + return self.__asynch_video_timing_support + + @asynch_video_timing_support.setter + def asynch_video_timing_support(self, value: bool): + self.__asynch_video_timing_support = value + + @property + def dsc_src_support(self) -> bool: + """ + Returns state of DSC CRC of multiple SU regions support. + + Returns: + object of `bool` type + """ + return self.__dsc_src_support + + @dsc_src_support.setter + def dsc_src_support(self, value: bool): + self.__dsc_src_support = value + + @property + def granularity_needed(self) -> bool: + """ + Returns state of Panel Replay Selective Update Granularity Needed. + + Returns: + object of `bool` type + """ + return self.__granularity_needed + + @granularity_needed.setter + def granularity_needed(self, value: bool): + self.__granularity_needed = value + + @property + def y_gran_extended_supported(self) -> bool: + """ + Returns state of SU Y Granularity Extended Capabilities supported. + + Returns: + object of `bool` type + """ + return self.__y_gran_extended_supported + + @y_gran_extended_supported.setter + def y_gran_extended_supported(self, value: bool): + self.__y_gran_extended_supported = value + + @property + def link_off_supported(self) -> bool: + """ + Returns state of Link OFF Supported in PR after Adaptive Sync SDP. + + Returns: + object of `bool` type + """ + return self.__link_off_supported + + @link_off_supported.setter + def link_off_supported(self, value: bool): + self.__link_off_supported = value + + def __str__(self) -> str: + return (f"Panel Replay support: {self.pr_support}\n" + f"Selective Update support: {self.selective_update_support}\n" + f"Early Transport support: {self.early_transport_support}\n" + f"DSC decode functionality support: {self.dsc_decode_func_support}\n" + f"Asynchronous Video Timing support: {self.asynch_video_timing_support}\n" + f"DSC CRC of multiple SU regions support: {self.dsc_src_support}\n" + f"Panel Replay Selective Update granularity needed: {self.granularity_needed}\n" + f"SU Y Granularity Extended Capabilities supported: {self.y_gran_extended_supported}\n" + f"Link OFF Supported in PR after Adaptive Sync SDP: {self.link_off_supported}\n") + + +class SuYGranularity: + + def __init__(self, value_of_8: bool = False, value_of_10: bool = False, value_of_12: bool = False, + value_of_14: bool = False, value_of_15: bool = False, value_of_16: bool = False, + value_of_18: bool = False, value_of_20: bool = False, value_of_24: bool = False, + value_of_30: bool = False, value_of_32: bool = False, value_of_36: bool = False, + value_of_40: bool = False, value_of_48: bool = False, value_of_54: bool = False, + value_of_64: bool = False): + self.__value_of_8 = value_of_8 + self.__value_of_10 = value_of_10 + self.__value_of_12 = value_of_12 + self.__value_of_14 = value_of_14 + self.__value_of_15 = value_of_15 + self.__value_of_16 = value_of_16 + self.__value_of_18 = value_of_18 + self.__value_of_20 = value_of_20 + self.__value_of_24 = value_of_24 + self.__value_of_30 = value_of_30 + self.__value_of_32 = value_of_32 + self.__value_of_36 = value_of_36 + self.__value_of_40 = value_of_40 + self.__value_of_48 = value_of_48 + self.__value_of_54 = value_of_54 + self.__value_of_64 = value_of_64 + + @property + def value_of_8(self) -> bool: + """ + Supported Y granularity value of 8. + + Returns: + object of `bool` type + """ + return self.__value_of_8 + + @value_of_8.setter + def value_of_8(self, value: bool): + self.__value_of_8 = value + + @property + def value_of_10(self) -> bool: + """ + Supported Y granularity value of 10. + + Returns: + object of `bool` type + """ + return self.__value_of_10 + + @value_of_10.setter + def value_of_10(self, value: bool): + self.__value_of_10 = value + + @property + def value_of_12(self) -> bool: + """ + Supported Y granularity value of 12. + + Returns: + object of `bool` type + """ + return self.__value_of_12 + + @value_of_12.setter + def value_of_12(self, value: bool): + self.__value_of_12 = value + + @property + def value_of_14(self) -> bool: + """ + Supported Y granularity value of 14. + + Returns: + object of `bool` type + """ + return self.__value_of_14 + + @value_of_14.setter + def value_of_14(self, value: bool): + self.__value_of_14 = value + + @property + def value_of_15(self) -> bool: + """ + Supported Y granularity value of 15. + + Returns: + object of `bool` type + """ + return self.__value_of_15 + + @value_of_15.setter + def value_of_15(self, value: bool): + self.__value_of_15 = value + + @property + def value_of_16(self) -> bool: + """ + Supported Y granularity value of 16. + + Returns: + object of `bool` type + """ + return self.__value_of_16 + + @value_of_16.setter + def value_of_16(self, value: bool): + self.__value_of_16 = value + + @property + def value_of_18(self) -> bool: + """ + Supported Y granularity value of 18. + + Returns: + object of `bool` type + """ + return self.__value_of_18 + + @value_of_18.setter + def value_of_18(self, value: bool): + self.__value_of_18 = value + + @property + def value_of_20(self) -> bool: + """ + Supported Y granularity value of 20. + + Returns: + object of `bool` type + """ + return self.__value_of_20 + + @value_of_20.setter + def value_of_20(self, value: bool): + self.__value_of_20 = value + + @property + def value_of_24(self) -> bool: + """ + Supported Y granularity value of 24. + + Returns: + object of `bool` type + """ + return self.__value_of_24 + + @value_of_24.setter + def value_of_24(self, value: bool): + self.__value_of_24 = value + + @property + def value_of_30(self) -> bool: + """ + Supported Y granularity value of 30. + + Returns: + object of `bool` type + """ + return self.__value_of_30 + + @value_of_30.setter + def value_of_30(self, value: bool): + self.__value_of_30 = value + + @property + def value_of_32(self) -> bool: + """ + Supported Y granularity value of 32. + + Returns: + object of `bool` type + """ + return self.__value_of_32 + + @value_of_32.setter + def value_of_32(self, value: bool): + self.__value_of_32 = value + + @property + def value_of_36(self) -> bool: + """ + Supported Y granularity value of 36. + + Returns: + object of `bool` type + """ + return self.__value_of_36 + + @value_of_36.setter + def value_of_36(self, value: bool): + self.__value_of_36 = value + + @property + def value_of_40(self) -> bool: + """ + Supported Y granularity value of 40. + + Returns: + object of `bool` type + """ + return self.__value_of_40 + + @value_of_40.setter + def value_of_40(self, value: bool): + self.__value_of_40 = value + + @property + def value_of_48(self) -> bool: + """ + Supported Y granularity value of 48. + + Returns: + object of `bool` type + """ + return self.__value_of_48 + + @value_of_48.setter + def value_of_48(self, value: bool): + self.__value_of_48 = value + + @property + def value_of_54(self) -> bool: + """ + Supported Y granularity value of 54. + + Returns: + object of `bool` type + """ + return self.__value_of_54 + + @value_of_54.setter + def value_of_54(self, value: bool): + self.__value_of_54 = value + + @property + def value_of_64(self) -> bool: + """ + Supported Y granularity value of 64. + + Returns: + object of `bool` type + """ + return self.__value_of_64 + + @value_of_64.setter + def value_of_64(self, value: bool): + self.__value_of_64 = value + + def combined_value(self) -> int: + return (self.value_of_8 | (self.value_of_10 << 1) | (self.value_of_12 << 2) | (self.value_of_14 << 3) | + (self.value_of_15 << 4) | (self.value_of_16 << 5) | (self.value_of_18 << 6) | (self.value_of_20 << 7) | + (self.value_of_24 << 8) | (self.value_of_30 << 9) | (self.value_of_32 << 10) | + (self.value_of_36 << 11) | (self.value_of_40 << 12) | (self.value_of_48 << 13) | + (self.value_of_54 << 14) | (self.value_of_64 << 15)) + + def __str__(self) -> str: + return (f"Value of 8: {self.value_of_8}\n" + f"Value of 10: {self.value_of_10}\n" + f"Value of 12: {self.value_of_12}\n" + f"Value of 14: {self.value_of_14}\n" + f"Value of 15: {self.value_of_15}\n" + f"Value of 16: {self.value_of_16}\n" + f"Value of 18: {self.value_of_18}\n" + f"Value of 20: {self.value_of_20}\n" + f"Value of 24: {self.value_of_24}\n" + f"Value of 30: {self.value_of_30}\n" + f"Value of 32: {self.value_of_32}\n" + f"Value of 36: {self.value_of_36}\n" + f"Value of 40: {self.value_of_40}\n" + f"Value of 48: {self.value_of_48}\n" + f"Value of 54: {self.value_of_54}\n" + f"Value of 64: {self.value_of_64}\n" + ) + + +class PSRCaps(IntEnum): + NotSupported = 0, + PSR1Supported = 1, + PSR2SUSupported = 2, + PSR2YcoorSupported = 3, + PSR2EarlyTransportSupported = 4 + + def __str__(self) -> str: + if self.value == self.NotSupported: + return "Not supported" + elif self.value == self.PSR1Supported: + return "PSR 1 supported" + elif self.value == self.PSR2SUSupported: + return "PSR 2 SU supported" + elif self.value == self.PSR2YcoorSupported: + return "PSR 2 SU and Y coordinates supported" + elif self.value == self.PSR2EarlyTransportSupported: + return "PSR 2 SU, Y coordinates and Early Transport supported" + else: + return "Incorrect version" + + +class PSRSetupTime(IntEnum): + T_330us = 0, + T_275us = 1, + T_220us = 2, + T_165us = 3, + T_110us = 4, + T_55us = 5, + T_0us = 6, + T_Reserved = 7 + + def __str__(self) -> str: + if self.value == self.T_330us: + return "330 us" + elif self.value == self.T_275us: + return "275 us" + elif self.value == self.T_220us: + return "220 us" + elif self.value == self.T_165us: + return "165 us" + elif self.value == self.T_110us: + return "110 us" + elif self.value == self.T_55us: + return "55 us" + elif self.value == self.T_0us: + return "0 us" + else: + return "Reserved" + + +class PSRCapsFlags: + + def __init__(self, psr_caps: PSRCaps = PSRCaps.NotSupported, link_training_req_psr1: bool = False, + psr_setup_time: PSRSetupTime = PSRSetupTime.T_330us, y_coor_psr2_su: bool = False, + su_coordinates_shall_adhere: bool = False, no_update_aux_frame_sync: bool = False): + self.__psr_caps = psr_caps + self.__link_training_req_psr1 = link_training_req_psr1 + self.__psr_setup_time = psr_setup_time + self.__y_coor_psr2_su = y_coor_psr2_su + self.__su_coordinates_shall_adhere = su_coordinates_shall_adhere + self.__no_update_aux_frame_sync = no_update_aux_frame_sync + + @property + def psr_caps(self) -> PSRCaps: + """ + Returns PSR caps value from enum. + + Returns: + object of `PSRCaps` type + """ + return self.__psr_caps + + @psr_caps.setter + def psr_caps(self, value: PSRCaps): + self.__psr_caps = value + + @property + def link_training_req_psr1(self) -> bool: + """ + Returns link training request PSR1 flag. + + Returns: + object of `bool` type + """ + return self.__link_training_req_psr1 + + @link_training_req_psr1.setter + def link_training_req_psr1(self, value: bool): + self.__link_training_req_psr1 = value + + @property + def psr_setup_time(self) -> PSRSetupTime: + """ + Returns PSR setup time value. + + Returns: + object of `PSRSetupTime` type + """ + return self.__psr_setup_time + + @psr_setup_time.setter + def psr_setup_time(self, value: PSRSetupTime): + self.__psr_setup_time = value + + @property + def y_coor_psr2_su(self) -> bool: + """ + Returns flag for "Y-coordinate for PSR2 SU". + + Returns: + object of `bool` type + """ + return self.__y_coor_psr2_su + + @y_coor_psr2_su.setter + def y_coor_psr2_su(self, value: bool): + self.__y_coor_psr2_su = value + + @property + def su_coordinates_shall_adhere(self) -> bool: + """ + Returns flag for "SU coordinates shall adhere" + + Returns: + object of `bool` type + """ + return self.__su_coordinates_shall_adhere + + @su_coordinates_shall_adhere.setter + def su_coordinates_shall_adhere(self, value: bool): + self.__su_coordinates_shall_adhere = value + + @property + def no_update_aux_frame_sync(self) -> bool: + """ + Returns value for flag "Do not update AUX FRAME SYNC". + + Returns: + object of `bool` type + """ + return self.__no_update_aux_frame_sync + + @no_update_aux_frame_sync.setter + def no_update_aux_frame_sync(self, value: bool): + self.__no_update_aux_frame_sync = value + + def __str__(self) -> str: + return (f"Panel Self Refresh supported: {self.psr_caps.__str__()}\n" + f"Link Training Requirement of Sink: {self.link_training_req_psr1}\n" + f"PSR Setup time: {self.psr_setup_time.__str__()}\n" + f"Y-coordinate Requirement of Sink Device: {self.y_coor_psr2_su}\n" + f"Panel Self Refresh 2 Selective Update Granularity Required: {self.su_coordinates_shall_adhere}\n" + f"Frame Sync Function Is Not Needed for Selective Update: {self.no_update_aux_frame_sync}\n") diff --git a/UniTAP/dev/ports/modules/panel_replay/pr_status.py b/UniTAP/dev/ports/modules/panel_replay/pr_status.py new file mode 100644 index 0000000..4ee3170 --- /dev/null +++ b/UniTAP/dev/ports/modules/panel_replay/pr_status.py @@ -0,0 +1,48 @@ +from UniTAP.libs.lib_tsi.tsi_io import PortIO +from .pr_private_types import * +from UniTAP.libs.lib_tsi.tsi import * +from .pr_types import * + + +class PanelReplayStatus: + """ + Class `PanelReplayStatus` contains information about Panel Replay Status. + - Get PR command `command`. + - Get current state (enabled/disabled) `status`. + - Get PR Error `error`. + """ + + def __init__(self, port_io: PortIO): + self.__io = port_io + + def command(self) -> PRCommand: + """ + Returns current command of PR. + + Returns: + object of `PRCommand` type + """ + return PRCommand(self.__read().command_status) + + def status(self) -> PRStatus: + """ + Returns current status of PR (enabled/disabled). + + Returns: + object of `PRStatus` type + """ + return PRStatus(self.__read().status) + + def error(self) -> PRError: + """ + Returns current error of PR. + + Returns: + object of `PRError` type + """ + return PRError(self.__read().error) + + def __read(self) -> PrStatus: + self.__io.set(TSI_PG_STREAM_SELECT, 0) + return self.__io.get(TSI_PG_PR_STATUS_R, PrStatus)[1] + diff --git a/UniTAP/dev/ports/modules/panel_replay/pr_types.py b/UniTAP/dev/ports/modules/panel_replay/pr_types.py new file mode 100644 index 0000000..f1e2610 --- /dev/null +++ b/UniTAP/dev/ports/modules/panel_replay/pr_types.py @@ -0,0 +1,399 @@ +from enum import IntEnum +from typing import List + + +class PRCommand(IntEnum): + """ + Class `PRCommand` contains all possible variants of Panel Replay Command. + """ + + No = 0 + EnablingInactive = 1 + EnablingActive = 2 + RFB = 3 + + def __str__(self): + if self.value == self.No: + return "No command under execution." + if self.value == self.EnablingInactive: + return "Enabling inactive mode is in progress." + if self.value == self.EnablingActive: + return "Enabling active mode is in progress." + if self.value == self.RFB: + return "RFB selective update is in progress." + + +class PRStatus(IntEnum): + """ + Class `PRStatus` contains all possible variants of Panel Replay Status. + """ + + Disabled = 0 + EnabledInactive = 1 + EnabledActive = 2 + + def __str__(self): + if self.value == self.Disabled: + return "Disabled." + if self.value == self.EnabledInactive: + return "Enabled in an inactive mode." + if self.value == self.EnabledActive: + return "Enabled in an active mode." + + +class PRError(IntEnum): + """ + Class `PRError` contains all possible variants of Panel Replay Error type. + """ + + No = 0 + Internal = 1 + IncorrectCommandReceived = 2 + FailedAccess = 3 + LTFailed = 4 + SelUpdIncorrect = 5 + + def __str__(self): + if self.value == self.No: + return "No errors." + if self.value == self.Internal: + return "Internal error." + if self.value == self.IncorrectCommandReceived: + return "Incorrect command received." + if self.value == self.FailedAccess: + return "Failed to access DPCD." + if self.value == self.LTFailed: + return "LinkTraining failed." + if self.value == self.SelUpdIncorrect: + return "Selective update config is incorrect." + + +class PRMode(IntEnum): + """ + Class `PRMode` contains all possible variants of Panel Replay mode. + """ + PR = 0 + PSR1 = 1 + PSR2 = 2 + Res = 3 + + +class YGranularity(IntEnum): + """ + Class `YGranularity` contains all possible variants of SU Y granularity. + """ + Value_8 = 0 + Value_10 = 1 + Value_12 = 2 + Value_14 = 3 + Value_15 = 4 + Value_16 = 5 + Value_18 = 6 + Value_20 = 7 + Value_24 = 8 + Value_30 = 9 + Value_32 = 10 + Value_36 = 11 + Value_40 = 12 + Value_48 = 13 + Value_54 = 14 + Value_64 = 15 + + +class Region: + """ + Class `Region` contains information about each PR Region (Frame). + - Set and get X coordinate 'x'. + - Set and get Y coordinate 'y'. + - Set and get Width 'width'. + - Set and get Height `height` + """ + + def __init__(self, x: int = 0, y: int = 0, width: int = 0, height: int = 0): + self.__x = x + self.__y = y + self.__width = width + self.__height = height + + @property + def x(self) -> int: + """ + Returns current X coordinate value. + + Returns: + object of `int` type + """ + return self.__x + + @x.setter + def x(self, value: int): + self.__x = value + + @property + def y(self) -> int: + """ + Returns current Y coordinate value. + + Returns: + object of `int` type + """ + return self.__y + + @y.setter + def y(self, value: int): + self.__y = value + + @property + def width(self) -> int: + """ + Returns current Width value. + + Returns: + object of `int` type + """ + return self.__width + + @width.setter + def width(self, value: int): + self.__width = value + + @property + def height(self) -> int: + """ + Returns current Height value. + + Returns: + object of `int` type + """ + return self.__height + + @height.setter + def height(self, value: int): + self.__height = value + + +class Flags: + """ + Class `Flags` contains information about Panel Replay flags capabilities. + - Set and get `early_transport` Early transport during selective update is supported. + - Set and get `crc_in_vsc_sdp`. CRC for selective update region/full active frame in VSC SDP is supported. + - Set and get `mode`. PanelReplay mode. + - Set and get `hpd_irq`. Generate HPD_IRQ with Adaptive-Sync SDP missing (PR). + - Set and get `hpd_irq_vsc_sdp`. IRQ_HPD with VSC SDP uncorrectable error (PR). + - Set and get `hpd_irq_rfb`. IRQ_HPD with RFB storage error (PR). + - Set and get `hpd_irq_crc`. IRQ_HPD with active frame CRC error. + - Set and get `refresh_rate_unlock`. Sink refresh rate unlock granted. + - Set and get `ext_y_gran`. Use SU extended Y granularity (PSR2 and PR). + - Set and get `main_link_remain_on`. Main-link remains ON during PSR1/PSR2 active states. + - Set and get `y_granularity`. SU Y granularity extended value selection. + """ + + def __init__(self, early_transport: bool = False, crc_in_vsc_sdp: bool = False, pr_mode: PRMode = PRMode.PR, + hpd_irq: bool = False, hpd_irq_vsc_sdp: bool = False, hpd_irq_rfb: bool = False, + hpd_irq_crc: bool = False, refresh_rate_unlock: bool = False, ext_y_gran: bool = False, + main_link_remain_on: bool = False, y_granularity: YGranularity = YGranularity.Value_8): + self.__early_transport = early_transport + self.__crc_in_vsc_sdp = crc_in_vsc_sdp + self.__mode = pr_mode + self.__hpd_irq = hpd_irq + self.__hpd_irq_vsc_sdp = hpd_irq_vsc_sdp + self.__hpd_irq_rfb = hpd_irq_rfb + self.__hpd_irq_crc = hpd_irq_crc + self.__refresh_rate_unlock = refresh_rate_unlock + self.__ext_y_gran = ext_y_gran + self.__main_link_remain_on = main_link_remain_on + self.__y_granularity = y_granularity + + @property + def early_transport(self) -> bool: + """ + Returns current early transport value. + + Returns: + object of `bool` type + """ + return self.__early_transport + + @early_transport.setter + def early_transport(self, value: bool): + self.__early_transport = value + + @property + def crc_in_vsc_sdp(self) -> bool: + """ + Returns current crc in vsc sdp value. + + Returns: + object of `bool` type + """ + return self.__crc_in_vsc_sdp + + @crc_in_vsc_sdp.setter + def crc_in_vsc_sdp(self, value: bool): + self.__crc_in_vsc_sdp = value + + @property + def mode(self) -> PRMode: + """ + Returns current PR mode. + + Returns: + object of `PRMode` type + """ + return self.__mode + + @mode.setter + def mode(self, value: PRMode): + self.__mode = value + + @property + def hpd_irq(self) -> bool: + """ + Returns current state of HPD_IRQ with Adaptive-Sync SDP missing (PR). + + Returns: + object of `bool` type + """ + return self.__hpd_irq + + @hpd_irq.setter + def hpd_irq(self, value: bool): + self.__hpd_irq = value + + @property + def hpd_irq_vsc_sdp(self) -> bool: + """ + Returns current state of IRQ_HPD with VSC SDP uncorrectable error (PR). + + Returns: + object of `bool` type + """ + return self.__hpd_irq_vsc_sdp + + @hpd_irq_vsc_sdp.setter + def hpd_irq_vsc_sdp(self, value: bool): + self.__hpd_irq_vsc_sdp = value + + @property + def hpd_irq_rfb(self) -> bool: + """ + Returns current state of IRQ_HPD with RFB storage error (PR). + + Returns: + object of `bool` type + """ + return self.__hpd_irq_rfb + + @hpd_irq_rfb.setter + def hpd_irq_rfb(self, value: bool): + self.__hpd_irq_rfb = value + + @property + def hpd_irq_crc(self) -> bool: + """ + Returns current state of IRQ_HPD with active frame CRC error. + + Returns: + object of `bool` type + """ + return self.__hpd_irq_crc + + @hpd_irq_crc.setter + def hpd_irq_crc(self, value: bool): + self.__hpd_irq_crc = value + + @property + def refresh_rate_unlock(self) -> bool: + """ + Returns current state of Sink refresh rate unlock granted. + + Returns: + object of `bool` type + """ + return self.__refresh_rate_unlock + + @refresh_rate_unlock.setter + def refresh_rate_unlock(self, value: bool): + self.__refresh_rate_unlock = value + + @property + def ext_y_gran(self) -> bool: + """ + Returns current state of SU extended Y granularity (PSR2 and PR). + + Returns: + object of `bool` type + """ + return self.__ext_y_gran + + @ext_y_gran.setter + def ext_y_gran(self, value: bool): + self.__ext_y_gran = value + + @property + def main_link_remain_on(self) -> bool: + """ + Returns current state of Main-link remains ON during PSR1/PSR2 active states. + + Returns: + object of `bool` type + """ + return self.__main_link_remain_on + + @main_link_remain_on.setter + def main_link_remain_on(self, value: bool): + self.__main_link_remain_on = value + + @property + def y_granularity(self) -> YGranularity: + """ + Returns current value of SU Y granularity. + + Returns: + object of `YGranularity` type + """ + return self.__y_granularity + + @y_granularity.setter + def y_granularity(self, y_granularity: YGranularity): + self.__y_granularity = y_granularity + + +class PrSettings: + """ + Class `PrSettings` contains information about Panel Replay capabilities. + - Set and get `flags` Flags capabilities. + - Set and get `regions`. Regions capabilities. + """ + + def __init__(self): + self.__regions: [Region] = [] + self.__flags: Flags = Flags() + + @property + def flags(self) -> Flags: + """ + Returns current Flags caps. + + Returns: + object of `Flags` type + """ + return self.__flags + + @flags.setter + def flags(self, value: Flags): + self.__flags = value + + @property + def regions(self) -> List[Region]: + """ + Returns current Regions caps. + + Returns: + object of `Region` list type + """ + return self.__regions + + @regions.setter + def regions(self, value: List[Region]): + self.__regions = value diff --git a/UniTAP/dev/ports/modules/pdc/__init__.py b/UniTAP/dev/ports/modules/pdc/__init__.py new file mode 100644 index 0000000..65693a9 --- /dev/null +++ b/UniTAP/dev/ports/modules/pdc/__init__.py @@ -0,0 +1,6 @@ +from .pdo import Pdo +from .pdc_types import ExternalResistance, InternalResistance, ContractTypePriority, DifferentialPair, \ + CableControlOrientation, DRPTryMode, USB3Mode, PDMode, PdcDeviceRole, FrSwapCurrent, PowerRole, CCPullUp +from .pdo_types import PdoSide, PeakCurrent, PdoTypeEnum, FixedPdoSink, FixedPdoSource, BatteryPdo, VariablePdo +from .pdc_dp_alt_mode import PinAssignment +from .pdc_dpam_types import PinAssignmentModes, DPAMVersion diff --git a/UniTAP/dev/ports/modules/pdc/pdc_bus_status.py b/UniTAP/dev/ports/modules/pdc/pdc_bus_status.py new file mode 100644 index 0000000..ed933f8 --- /dev/null +++ b/UniTAP/dev/ports/modules/pdc/pdc_bus_status.py @@ -0,0 +1,111 @@ +from .pdc_io import PDCPortIO + + +class BusElectricalStatus: + + """ + Class `BusElectricalStatus` contains information about current BUU Electrical status. + - Get VBus voltage `vbus_voltage`, type `int`. + - Get VBus current `vbus_current`, type `int`. + - Get CC1 voltage `cc1_voltage`, type `int`. + - Get CC2 voltage `cc2_voltage`, type `int`. + - Get VCONN voltage `vconn_voltage`, type `int`. + - Get VCONN current `vconn_current`, type `int`. + - Get SBU-1 voltage `sbu_1_voltage`, type `int`. + - Get SBU-2 voltage `sbu_2_voltage`, type `int`. + """ + + def __init__(self, pdc_io: PDCPortIO): + self.__io = pdc_io + + @property + def vbus_voltage(self) -> int: + """ + Returns current VBus voltage. + + Returns: + object of `int` type + """ + return self.__io.read_adc_vbus_status()[0] + + @property + def vbus_current(self) -> int: + """ + Returns current VBus current. + + Returns: + object of `int` type + """ + return self.__io.read_adc_vbus_status()[1] + + @property + def cc1_voltage(self) -> int: + """ + Returns current CC1 voltage. + + Returns: + object of `int` type + """ + return self.__io.read_adc_vbus_status()[2] + + @property + def cc2_voltage(self) -> int: + """ + Returns current CC2 voltage. + + Returns: + object of `int` type + """ + return self.__io.read_adc_vbus_status()[3] + + @property + def vconn_voltage(self) -> int: + """ + Returns current VCONN voltage. + + Returns: + object of `int` type + """ + return self.__io.read_adc_vbus_status()[4] + + @property + def vconn_current(self) -> int: + """ + Returns current VCONN current. + + Returns: + object of `int` type + """ + return self.__io.read_adc_vbus_status()[5] + + @property + def sbu_1_voltage(self) -> int: + """ + Returns current SBU-1 voltage. + + Returns: + object of `int` type + """ + return self.__io.read_adc_vbus_status()[6] + + @property + def sbu_2_voltage(self) -> int: + """ + Returns current SBU-2 voltage. + + Returns: + object of `int` type + """ + return self.__io.read_adc_vbus_status()[7] + + def __str__(self) -> str: + electrical_status = self.__io.read_adc_vbus_status() + return f"Bus Electrical Status\n" \ + f"VBus current: {electrical_status[0]}\n" \ + f"VBus voltage: {electrical_status[1]}\n" \ + f"CC1 voltage: {electrical_status[2]}\n" \ + f"CC2 voltage: {electrical_status[3]}\n" \ + f"VCONN voltage: {electrical_status[4]}\n" \ + f"VCONN current: {electrical_status[5]}\n" \ + f"SBU-1 voltage: {electrical_status[6]}\n" \ + f"SBU-2 voltage: {electrical_status[7]}\n" diff --git a/UniTAP/dev/ports/modules/pdc/pdc_capabilities.py b/UniTAP/dev/ports/modules/pdc/pdc_capabilities.py new file mode 100644 index 0000000..64813bd --- /dev/null +++ b/UniTAP/dev/ports/modules/pdc/pdc_capabilities.py @@ -0,0 +1,340 @@ +from .pdc_io import PDCPortIO, PDCControlCommand, HWControlCommand +from .pdc_types import PdcDeviceRole, CCPullUp, DRPTryMode, USB3Mode, PowerRole +from .pdc_utils import support_or_not + + +class PcdCapsStatus: + + """ + Class `PcdCapsStatus` contains information about PDC capabilities. + - Get initial device role `initial_role`, type `PdcDeviceRole`. + - Get data device role `data_role`, type `PdcDeviceRole`. + - Get power device role `power_role`, type `PowerRole`. + - Get Cable Control Pull Up `cc_pull_up`, type `CCPullUp`. + - Get current behavior, DRP try mode `behavior`, type `DRPTryMode`. + - Get state of PR Swap `pr_swap`, type `bool`. + - Get state of DR Swap `dr_swap`, type `bool`. + - Get state of FR Swap `fr_swap`, type `bool`. + - Get state of VCONN Swap `vconn_swap`, type `bool`. + - Get state of Debug accessory `debug_accessory`, type `bool`. + - Get state of Audio accessory `audio_accessory`, type `bool`. + """ + + def __init__(self, pdc_io: PDCPortIO): + self.__io = pdc_io + + def initial_role(self) -> PdcDeviceRole: + """ + Returns current initial role. + + Returns: + object of `PdcDeviceRole` type + """ + return PdcDeviceRole(self.__io.read_pdc_state().dev_role) + + def data_role(self) -> PdcDeviceRole: + """ + Returns current data role. + + Returns: + object of `PdcDeviceRole` type + """ + return PdcDeviceRole(self.__io.read_pdc_status().data_role) + + def power_role(self) -> PowerRole: + """ + Returns current power role. + + Returns: + object of `PowerRole` type + """ + return PowerRole(self.__io.read_pdc_status().power_role) + + def cc_pull_up(self) -> CCPullUp: + """ + Returns current cable control pull up. + + Returns: + object of `CCPullUp` type + """ + return CCPullUp(self.__io.read_pdc_state().cc_pull_up) + + def behavior(self) -> DRPTryMode: + """ + Returns current behavior of DPR mode. + + Returns: + object of `DRPTryMode` type + """ + return DRPTryMode(self.__io.read_pdc_state().drp_try_mode) + + def pr_swap(self) -> bool: + """ + Returns current state of PR Swap. + + Returns: + object of `bool` type + """ + return bool(self.__io.read_pdc_state().pr_swap_en) + + def dr_swap(self) -> bool: + """ + Returns current state of DR Swap. + + Returns: + object of `bool` type + """ + return bool(self.__io.read_pdc_state().dr_swap_en) + + def fr_swap(self) -> bool: + """ + Returns current state of FR Swap. + + Returns: + object of `bool` type + """ + return bool(self.__io.read_pdc_state().fr_swap_en) + + def vconn_swap(self) -> bool: + """ + Returns current state of VCONN Swap. + + Returns: + object of `bool` type + """ + return bool(self.__io.read_pdc_state().vconn_swap_en) + + def debug_accessory(self) -> bool: + """ + Returns current state of Debug accessory. + + Returns: + object of `bool` type + """ + return bool(self.__io.read_pdc_state().dbg_accessory) + + def audio_accessory(self) -> bool: + """ + Returns current state of Audio accessory. + + Returns: + object of `bool` type + """ + return bool(self.__io.read_pdc_state().aud_accessory) + + def __str__(self): + status = self.__io.read_pdc_state() + return f"PDC Capabilities:\n" \ + f"Initial role: {PdcDeviceRole(status.dev_role).name}\n" \ + f"Power role: {self.power_role().name}\n" \ + f"Data role: {self.data_role().name}\n" \ + f"CC Pull-Up: {CCPullUp(status.cc_pull_up).name}\n" \ + f"Try behavior: {DRPTryMode(status.drp_try_mode).name}\n" \ + f"PR Swap enabled: {support_or_not(status.pr_swap_en)}\n" \ + f"DR Swap enabled: {support_or_not(status.dr_swap_en)}\n" \ + f"FR Swap enabled: {support_or_not(status.fr_swap_en)}\n" \ + f"VCONN Swap enabled: {support_or_not(status.vconn_swap_en)}\n" \ + f"Debug accessory enabled: {support_or_not(status.dbg_accessory)}\n" \ + f"Audio accessory enabled: {support_or_not(status.aud_accessory)}\n" + + +class PcdCapsStatus340(PcdCapsStatus): + + """ + Class `PcdCapsStatus340` inherited of class`PcdCapsStatus` allows working with PDC. + Class `PcdCapsStatus340` has all the `PcdCapsStatus` functionality. + - Get state of USB 2.0 mode `usb20_mode`, type `bool`. + - Get state of UCd 3.0 mode `usb30_mode`, type `USB3Mode`. + """ + + def __init__(self, pdc_io: PDCPortIO): + super().__init__(pdc_io) + + def usb20_mode(self) -> bool: + """ + Returns current state of USB 2.0 mode. + + Returns: + object of `bool` type + """ + return bool(self.__io.read_pdc_state().usb2_enabled) + + def usb30_mode(self) -> USB3Mode: + """ + Returns current state of USB 3.0 mode. + + Returns: + object of `USB3Mode` type + """ + return USB3Mode(self.__io.read_pdc_state().usb30_mode) + + def __str__(self): + status = self.__io.read_pdc_state() + return f"PDC Capabilities:" \ + f"Initial role: {PdcDeviceRole(status.dev_role).name}\n" \ + f"CC Pull-Up: {CCPullUp(status.cc_pull_up).name}\n" \ + f"Try behavior: {DRPTryMode(status.drp_try_mode).name}\n" \ + f"USB 2 enabled: {support_or_not(status.usb2_enabled)}\n" \ + f"USB 3: {USB3Mode(status.usb30_mode).name}\n" \ + f"PR Swap enabled: {support_or_not(status.pr_swap_en)}\n" \ + f"DR Swap enabled: {support_or_not(status.dr_swap_en)}\n" \ + f"FR Swap enabled: {support_or_not(status.fr_swap_en)}\n" \ + f"VCONN Swap enabled: {support_or_not(status.vconn_swap_en)}\n" \ + f"Debug accessory enabled: {support_or_not(status.dbg_accessory)}\n" \ + f"Audio accessory enabled: {support_or_not(status.aud_accessory)}\n" + + +class PdcCapabilities: + + """ + Class `PdcCapabilities` allows controlling PDC capabilities. + - Set device initial role `set_initial_role`. + - Enable/Disable PR Swap `enable_pr_swap`. + - Enable/Disable DR Swap `enable_dr_swap`. + - Enable/Disable FR Swap `enable_fr_swap`. + - Enable/Disable VCONN Swap `enable_vconn_swap`. + - Set CC Pull Up `cc_pull_up`. + - Set DPR try mode behavior `try_behavior`. + - Enable/Disable Debug accessory `enable_debug_accessory`. + - Enable/Disable Audio accessory `enable_audio_accessory`. + """ + + __device_role = {PdcDeviceRole.DFP: PDCControlCommand.SetDataRoleDFP, + PdcDeviceRole.UFP: PDCControlCommand.SetDataRoleUFP, + PdcDeviceRole.DRP: PDCControlCommand.SetDataRoleDRP} + + __cc_pull_up = {CCPullUp.Current_05_09A: PDCControlCommand.PullCCLineToDefault, + CCPullUp.Current_1_5A: PDCControlCommand.PullCCLineTo1_5A, + CCPullUp.Current_3A: PDCControlCommand.PullCCLineTo3_0A} + + __behavior = {DRPTryMode.PureDRP: PDCControlCommand.SetDataRoleDRP, + DRPTryMode.DRP_try_SNK: PDCControlCommand.SetDataRoleDRP_SNK_Support, + DRPTryMode.DRP_try_SRC: PDCControlCommand.SetDataRoleDRP_SRC_Support} + + def __init__(self, pdc_io: PDCPortIO): + self.__io = pdc_io + self.__status = PcdCapsStatus(self.__io) + + @property + def status(self) -> PcdCapsStatus: + """ + Returns current PDC Capabilities status. + + Returns: + object of `PcdCapsStatus` type + """ + return self.__status + + def set_initial_role(self, role: PdcDeviceRole): + """ + Set device initial role. + + Args: + role (`PdcDeviceRole`) + """ + self.__io.write_pdc_command(self.__device_role.get(role)) + + def enable_pr_swap(self, enable: bool): + """ + Enable/Disable PR Swap. + + Args: + enable (`bool`) + """ + self.__io.write_pdc_command(PDCControlCommand.EnablePRSwap if enable else PDCControlCommand.DisablePRSwap) + + def enable_dr_swap(self, enable: bool): + """ + Enable/Disable DR Swap. + + Args: + enable (`bool`) + """ + self.__io.write_pdc_command(PDCControlCommand.EnableDRSwap if enable else PDCControlCommand.DisableDRSwap) + + def enable_fr_swap(self, enable: bool): + """ + Enable/Disable FR Swap. + + Args: + enable (`bool`) + """ + self.__io.write_pdc_command(PDCControlCommand.EnableFRSwap if enable else PDCControlCommand.DisableFRSwap) + + def enable_vconn_swap(self, enable: bool): + """ + Enable/Disable VCONN Swap. + + Args: + enable (`bool`) + """ + self.__io.write_pdc_command(PDCControlCommand.EnableVconnSwap if enable else PDCControlCommand.DisableVconnSwap) + + def cc_pull_up(self, cc_pull_up: CCPullUp): + """ + Set Cable Control Pull Up. + + Args: + cc_pull_up (`CCPullUp`) + """ + self.__io.write_pdc_command(self.__cc_pull_up.get(cc_pull_up)) + + def try_behavior(self, behavior: DRPTryMode): + """ + Set DRP try mode behavior. + + Args: + behavior (`DRPTryMode`) + """ + self.__io.write_pdc_command(self.__behavior.get(behavior)) + + def enable_debug_accessory(self, enable: bool): + """ + Enable/Disable Debug Accessory. + + Args: + enable (`bool`) + """ + self.__io.write_pdc_command(PDCControlCommand.EnableDebugAccessorySupport if enable else + PDCControlCommand.EnableDebugAccessorySupport) + + def enable_audio_accessory(self, enable: bool): + """ + Enable/Disable Audio Accessory. + + Args: + enable (`bool`) + """ + self.__io.write_pdc_command(PDCControlCommand.EnableAudioAccessorySupport if enable else + PDCControlCommand.DisableAudioAccessorySupport) + + +class PdcCapabilities340(PdcCapabilities): + + """ + Class `PdcCapabilities340` inherited of class`PdcCapabilities` allows working with PDC. + Class `PdcCapabilities340` has all the `PdcCapabilities` functionality. + - Enable/Disable USB 2.0 bypass `usb_2_bypass_function`. + - Enable/Disable USB 3.0 bypass `usb_3_bypass_function`. + """ + + def __init__(self, pdc_io: PDCPortIO): + super().__init__(pdc_io) + + def usb_2_bypass_function(self, enable: bool): + """ + Enable/Disable USB 2.0 bypass. + + Args: + enable (`bool`) + """ + self.__io.write_hw_command(HWControlCommand.Enable20PHY if enable else HWControlCommand.Disable20PHY) + + def usb_3_bypass_function(self, enable: bool): + """ + Enable/Disable USB 3.0 bypass. + + Args: + enable (`bool`) + """ + self.__io.write_hw_command(HWControlCommand.Enable30PHY if enable else HWControlCommand.Disable30PHY) \ No newline at end of file diff --git a/UniTAP/dev/ports/modules/pdc/pdc_contract_control.py b/UniTAP/dev/ports/modules/pdc/pdc_contract_control.py new file mode 100644 index 0000000..dce1bc7 --- /dev/null +++ b/UniTAP/dev/ports/modules/pdc/pdc_contract_control.py @@ -0,0 +1,253 @@ +import math +import warnings +from .pdc_io import PDCPortIO, PDCControlCommand +from .pdc_utils import support_or_not +from .pdc_types import FrSwapCurrent, ContractTypePriority, InternalResistance, ExternalResistance +from typing import Union + + +class PowerContractControlBase: + + """ + Class `PowerContractControlBase` allows setting and getting field related with power contract. + - Set and get Give Back flag `give_back_flag`, type `bool`. + - Set and get No USB suspend `no_usb_suspend`, type `bool`. + - Set and get PDO type priority `pdo_type_priority`, type `ContractTypePriority`. + - Set and get Maximum operation current `max_operation_current`, type `int`. + - Set FW Swap required current `fw_swap_required_current`, type `FrSwapCurrent`. + """ + + __fr_swap_current = {FrSwapCurrent.Disable: PDCControlCommand.SetFRSwapNoSupported, + FrSwapCurrent.EnableDefaultUSBPower: PDCControlCommand.SetFRSwapUSBPower, + FrSwapCurrent.Enable_1_5A_5V: PDCControlCommand.SetFRSwap1_5A_5V, + FrSwapCurrent.Enable_3A_5V: PDCControlCommand.SetFRSwap3A_5V} + + def __init__(self, pdc_io: PDCPortIO): + self._io = pdc_io + self.__caps = self._io.read_pdc_caps() + self.__pdo_count = self._io.read_hw_caps().pdo_count + + @property + def give_back_flag(self) -> bool: + """ + Returns state of Give back flag. + + Returns: + object of `bool` type + """ + return self._io.read_pdc_contract_control().give_back_flag != 0 + + @give_back_flag.setter + def give_back_flag(self, enable: bool): + data = self._io.read_pdc_contract_control() + data.give_back_flag = int(enable) + self._io.write_pdc_contract_control(data) + + @property + def no_usb_suspend(self) -> bool: + """ + Returns state of No USB suspend flag. + + Returns: + object of `bool` type + """ + return self._io.read_pdc_contract_control().no_usb_suspend != 0 + + @no_usb_suspend.setter + def no_usb_suspend(self, enable: bool): + data = self._io.read_pdc_contract_control() + data.no_usb_suspend = int(enable) + self._io.write_pdc_contract_control(data) + + def fw_swap_required_current(self, fr_swap_current: FrSwapCurrent): + """ + Set FW Swap required current. + + Args: + fr_swap_current (`FrSwapCurrent`) + """ + if self.__caps.fr_swap: + self._io.write_pdc_command(self.__fr_swap_current.get(fr_swap_current)) + else: + warnings.warn("Do not support Fast Role Swap.") + + @property + def pdo_type_priority(self) -> ContractTypePriority: + """ + Returns current PDO Type priority. + + Returns: + object of `ContractTypePriority` type + """ + return ContractTypePriority(self._io.read_pdc_contract_control().type_priority) + + @pdo_type_priority.setter + def pdo_type_priority(self, type_priority: ContractTypePriority): + data = self._io.read_pdc_contract_control() + data.type_priority = type_priority.value + self._io.write_pdc_contract_control(data) + + @property + def max_operation_current(self) -> int: + """ + Returns current value of maximum operation current. + + Returns: + object of `int` type + """ + data = self._io.read_contract_data() + return data[3] & 0x03FF + + @max_operation_current.setter + def max_operation_current(self, current: int): + if current < 0: + raise ValueError(f"Incorrect current {current}. Must be more than 0.") + data = self._io.read_contract_data() + data[3] = (data[3] & 0xFFFFFC00) | (current & 0x03FF) + self._io.write_contract_data(data) + + def established(self) -> bool: + """ + Returns state of power contract (established or not). + + Returns: + object of `bool` type + """ + return self._io.read_pdc_status().pwr_contract == 1 + + def __str__(self) -> str: + return f"Power Contract Control\n" \ + f"Give Back flag active: {support_or_not(self.give_back_flag)}\n" \ + f"No USB suspend flag active: {support_or_not(self.no_usb_suspend)}\n" \ + f"Contract type priority: {self.pdo_type_priority.name}\n" \ + f"Max operation current: {self.max_operation_current} mA" + + +class PowerContractControl340(PowerContractControlBase): + + """ + Class `PowerContractControl340` inherited from class `PowerContractControlBase`. + Class `PowerContractControl340` allows setting and getting internal and external resistance, + index of power contract. + Also has all the `PowerContractControlBase` functionality. + - Set and get flag Selecting power contract by index `selecting_by_index`, type `bool`. + - Set and get index of power contract `index_of_power_contract`, type `int`. + - Set and get internal resistance `internal_resistance`, type `int` or `InternalResistance`. + - Set and get external resistance `external_resistance`, type `int` or `ExternalResistance`. + """ + + __internal_resistance = {10000: InternalResistance.Resistance_10_Ohm, + 5500: InternalResistance.Resistance_5_5_Ohm, + 3550: InternalResistance.Resistance_3_55_Ohm, + 3500: InternalResistance.Resistance_3_5_Ohm, + 2600: InternalResistance.Resistance_2_6_Ohm, + 2140: InternalResistance.Resistance_2_14_Ohm, + 1760: InternalResistance.Resistance_1_76_Ohm} + + __external_resistance = {13900: ExternalResistance.Resistance_13_9_Ohm, + 10600: ExternalResistance.Resistance_10_6_Ohm, + 9100: ExternalResistance.Resistance_9_1_Ohm, + 7600: ExternalResistance.Resistance_7_6_Ohm, + 6600: ExternalResistance.Resistance_6_6_Ohm, + 5600: ExternalResistance.Resistance_5_6_Ohm, + 4600: ExternalResistance.Resistance_4_6_Ohm, + 3600: ExternalResistance.Resistance_3_6_Ohm, + 1800: ExternalResistance.Resistance_1_8_Ohm} + + def __init__(self, pdc_io: PDCPortIO): + super().__init__(pdc_io) + + @property + def selecting_by_index(self) -> bool: + """ + Returns state of Selecting by index flag. + + Returns: + object of `bool` type + """ + return self._io.read_pdc_contract_control().manual != 0 + + @selecting_by_index.setter + def selecting_by_index(self, enable: bool): + data = self._io.read_pdc_contract_control() + data.manual = enable + self._io.write_pdc_contract_control(data) + + @property + def index_of_power_contract(self) -> int: + """ + Returns index of current power contract. + + Returns: + object of `int` type + """ + if not self._io.read_pdc_contract_control().manual: + return self._io.read_power_contract_select() + else: + warnings.warn("Negotiate power contract automatically disabled. " + "Please, enable (use function 'enable_selecting_by_index'.)") + return -1 + + @index_of_power_contract.setter + def index_of_power_contract(self, index: int): + if not self._io.read_pdc_contract_control().manual: + if index < 0: + raise ValueError(f"Incorrect index {index} of power contract. " + f"Must be between 0 and {self.__pdo_count}.") + self._io.write_power_contract_select(index) + else: + warnings.warn("Negotiate power contract automatically disabled. " + "Please, enable (use function 'enable_selecting_by_index'.)") + + @property + def internal_resistance(self) -> Union[InternalResistance, int]: + """ + Returns internal resistance. + + Returns: + object of `int` or `InternalResistance` type + """ + res = self._io.read_internal_resistance() + for key in self.__internal_resistance.keys(): + if math.fabs(res - key) < 50: + return self.__internal_resistance.get(key) + return res + + @internal_resistance.setter + def internal_resistance(self, resistance: InternalResistance): + self._io.write_resistance(resistance.value) + + @property + def external_resistance(self) -> Union[ExternalResistance, int]: + """ + Returns external resistance. + + Returns: + object of `int` or `ExternalResistance` type + """ + if self._io.read_hw_status().ext_power: + res = self._io.read_external_resistance() + for key in self.__external_resistance.keys(): + if math.fabs(res - key) < 50: + return self.__external_resistance.get(key) + return res + else: + return ExternalResistance.Disable + + @external_resistance.setter + def external_resistance(self, resistance: ExternalResistance): + if self._io.read_hw_status().ext_power: + self._io.write_resistance(resistance.value) + else: + warnings.warn("Does not enabled external power. Please, attach external power device.") + + def __str__(self) -> str: + return f"Power Contract Control\n" \ + f"Give Back flag active: {support_or_not(self.give_back_flag)}\n" \ + f"No USB suspend flag active: {support_or_not(self.no_usb_suspend)}\n" \ + f"Contract type priority: {self.pdo_type_priority.name}\n" \ + f"Max operation current: {self.max_operation_current} mA\n" \ + f"Internal resistance: {self.internal_resistance} Om\n" \ + f"External resistance: {self.external_resistance} Om\n" \ + f"Selecting power contract by index flag active: {support_or_not(self.selecting_by_index)}\n" \ + f"Power contract index: {self.index_of_power_contract if self.selecting_by_index else '0'}" diff --git a/UniTAP/dev/ports/modules/pdc/pdc_controls.py b/UniTAP/dev/ports/modules/pdc/pdc_controls.py new file mode 100644 index 0000000..cce2d74 --- /dev/null +++ b/UniTAP/dev/ports/modules/pdc/pdc_controls.py @@ -0,0 +1,268 @@ +import warnings +from typing import Optional + +from UniTAP.utils import function_scheduler +from .pdc_io import PDCPortIO, PDCControlCommand, HWControlCommand +from .pdc_types import CableControlOrientation, DifferentialPair + + +class PdcControlsBase: + + """ + Class `PdcControlsBase` allows controlling some PDC commands: + - Send PR Swap `send_pr_swap`. + - Send DR Swap `send_dr_swap`. + - Send FR Swap `send_fr_swap`. + - Send VCONN Swap `send_vconn_swap`. + - Change cable control orientation `orientation`. + - Attach/DeAttach `attach`. + - Enable/Disable auto negotiate power contract `enable_auto_negotiate_power_contract`. + - Enable/Disable communication capable as PD Sink `communication_capable_as_pd_sink`. + - Enable/Disable communication capable as PD Source `communication_capable_as_pd_source`. + """ + + def __init__(self, pdc_io: PDCPortIO): + self.__io = pdc_io + self.__caps = self.__io.read_pdc_caps() + + def send_pr_swap(self): + """ + Send Power Role swap. It will be necessary to manually check that the changes have been made. + """ + self.__io.write_pdc_command(PDCControlCommand.TogglePowerRole) + + def send_dr_swap(self): + """ + Send Dual Role swap. It will be necessary to manually check that the changes have been made. + """ + self.__io.write_pdc_command(PDCControlCommand.ToggleDataRole) + + def send_fr_swap(self): + """ + Send Fast Role swap. It will be necessary to manually check that the changes have been made. + """ + if self.__caps.fr_swap: + self.__io.write_pdc_command(PDCControlCommand.FastRoleSwap) + else: + warnings.warn("Do not support Fast Role Swap.") + + def send_vconn_swap(self): + """ + Send VCONN Role swap. It will be necessary to manually check that the changes have been made. + """ + self.__io.write_pdc_command(PDCControlCommand.SwapVconn) + + @property + def orientation(self) -> Optional[CableControlOrientation]: + if self.__io.read_hw_status().et_cable: + if self.__io.read_hw_status().cc1_closed: + return CableControlOrientation.CC1 + elif self.__io.read_hw_status().cc2_closed: + return CableControlOrientation.CC2 + else: + return None + else: + return None + + @orientation.setter + def orientation(self, orientation: CableControlOrientation): + """ + Change cable control orientation. + + Args: + orientation (`CableControlOrientation`) + """ + if self.__io.read_hw_status().et_cable: + self.__io.write_hw_command(HWControlCommand.SetCableFlipToOn + if orientation == CableControlOrientation.CC2 else + HWControlCommand.SetCableFlipToOff) + else: + warnings.warn("Unigraf ET cable is not connected. Cannot switch cable control orientation.") + + def enable_auto_negotiate_power_contract(self, enable: bool): + """ + Enable/Disable auto negotiate power contract. + + Args: + enable (`bool`) + """ + caps = self.__io.read_pdc_contract_control() + caps.auto_negotate = enable + self.__io.write_pdc_contract_control(caps) + + def communication_capable_as_pd_sink(self, capable: bool): + """ + Enable/Disable communication capable as PD Sink. + + Args: + capable (`bool`) + """ + self.enable_auto_negotiate_power_contract(capable) + pdo = self.__io.read_local_spr_source_pdo() + vdo = self.__io.read_tx_id_vdo() + if isinstance(vdo, list): + if capable: + vdo[0] |= 1 << 30 + else: + vdo[0] &= ~(1 << 30) + self.__io.write_tx_id_vdo(vdo) + if isinstance(pdo, list): + if capable: + pdo[0].data |= 1 << 26 + else: + pdo[0].data &= ~(1 << 26) + self.__io.write_local_spr_source_pdo([v.data for v in pdo]) + + def communication_capable_as_pd_source(self, capable: bool): + """ + Enable/Disable communication capable as PD Source. + + Args: + capable (`bool`) + """ + pdo = self.__io.read_local_spr_source_pdo() + vdo = self.__io.read_tx_id_vdo() + if isinstance(vdo, list): + if capable: + vdo[0] |= 1 << 31 + else: + vdo[0] &= ~(1 << 31) + self.__io.write_tx_id_vdo(vdo) + if isinstance(pdo, list): + if capable: + pdo[0].data |= 1 << 26 + else: + pdo[0].data &= ~(1 << 26) + self.__io.write_local_spr_source_pdo([v.data for v in pdo]) + + +class PdcControls340(PdcControlsBase): + + """ + Class `PdcControls340` inherited from class `PdcControlsBase`. + Class `PdcControls340` allows controlling additional commands. + Also has all the `PdcControlsBase` functionality. + - Reconnect device `reconnect`. + - Reset device `reset`. + - Set ET cable different pairs `et_cable_diff_pairs`. + """ + + def __init__(self, pdc_io: PDCPortIO): + super().__init__(pdc_io) + self.__io = pdc_io + + def reset(self): + """ + Reset device. + """ + self.__io.write_hw_command(HWControlCommand.ResetPDController) + + def attach(self, attach: bool): + """ + Attach/DeAttach device. + """ + self.__io.write_hw_command(HWControlCommand.ConnectCCLines if attach else HWControlCommand.DisconnectCCLines) + + def reconnect(self): + """ + Reconnect device. + + Returns: + result of reconnection, type `bool`. + """ + self.__io.write_hw_command(HWControlCommand.RePlugCable) + + def is_reconnect_success(__io): + return self.__io.read_pdc_status().cable_plug != 0 + + return function_scheduler(is_reconnect_success, self.__io, interval=1, timeout=10) + + def et_cable_diff_pairs(self, differential_pair: DifferentialPair): + """ + Set ET cable different pairs. + + Args: + differential_pair (`DifferentialPair`) + """ + self.__io.write_hw_command(HWControlCommand.SetNewETCableMode if differential_pair.OnePair else + HWControlCommand.SetOldETCableMode) + + +class PdcControls424(PdcControlsBase): + + """ + Class `PdcControls424` inherited from class `PdcControlsBase`. + Class `PdcControls424` allows controlling additional commands. + Also has all the `PdcControlsBase` functionality. + - Reconnect device `reconnect`. + - Reset device `reset`. + """ + + def __init__(self, pdc_io: PDCPortIO): + super().__init__(pdc_io) + self.__io = pdc_io + + def reset(self): + """ + Reset device. + """ + self.__io.write_hw_command(HWControlCommand.ResetPDController) + + def reconnect(self) -> bool: + """ + Reconnect device. + + Returns: + result of reconnection, type `bool`. + """ + self.__io.write_pdc_command(PDCControlCommand.SimulateReConnect) + + def is_reconnect_success(__io): + return self.__io.read_pdc_status().cable_plug != 0 + + return function_scheduler(is_reconnect_success, self.__io, interval=1, timeout=10) + + +class PdcControls500(PdcControlsBase): + + """ + Class `PdcControls500` inherited from class `PdcControlsBase`. + Class `PdcControls500` allows controlling additional commands. + Also has all the `PdcControlsBase` functionality. + - Reconnect device `reconnect`. + - Enable/Disable internal resistance 10 Ohm `enable_internal_load_10_ohm`. + """ + + def __init__(self, pdc_io: PDCPortIO): + super().__init__(pdc_io) + self.__io = pdc_io + + def reconnect(self) -> bool: + """ + Reconnect device. + + Returns: + result of reconnection, type `bool`. + """ + self.__io.write_hw_command(HWControlCommand.RePlugCable) + + def is_reconnect_success(__io): + return self.__io.read_pdc_status().cable_plug != 0 + + return function_scheduler(is_reconnect_success, self.__io, interval=1, timeout=10) + + def attach(self, attach: bool): + """ + Attach/DeAttach device. + """ + self.__io.write_hw_command(HWControlCommand.ConnectCCLines if attach else HWControlCommand.DisconnectCCLines) + + def enable_internal_load_10_ohm(self, enable: bool): + """ + Enable/Disable internal resistance 10 Ohm. + + Args: + enable (`bool`) + """ + self.__io.write_hw_command(HWControlCommand.EnableIntLoad_10_ohm if enable else + HWControlCommand.DisableIntLoad_10_ohm) diff --git a/UniTAP/dev/ports/modules/pdc/pdc_dp_alt_mode.py b/UniTAP/dev/ports/modules/pdc/pdc_dp_alt_mode.py new file mode 100644 index 0000000..bd7a893 --- /dev/null +++ b/UniTAP/dev/ports/modules/pdc/pdc_dp_alt_mode.py @@ -0,0 +1,491 @@ +import warnings + +from UniTAP.dev.ports.modules.pdc.pdc_dpam_types import DPAMVersion +from .pdc_io import PDCPortIO, DPAMCommand +from .pdc_private_dpam_types import PinAssignmentModes, DISCDpSignaling, DISCCapability +from .pdc_utils import support_or_not + + +class PinAssignment: + + """ + Class `PinAssignment` allows enabling and disabling assignment. + - Enable/Disable mode C 4 lanes `c_4_lanes`, type `bool`. + - Enable/Disable mode D 2 lanes `d_2_lanes`, type `bool`. + - Enable/Disable mode E 4 lanes `e_4_lanes`, type `bool`. + """ + + def __init__(self): + self.__c_4_lanes = False + self.__d_2_lanes = False + self.__e_4_lanes = False + + @property + def c_4_lanes(self) -> bool: + """ + Returns state of C 4 lanes mode. + + Returns: + object of `bool` type. + """ + return self.__c_4_lanes + + @c_4_lanes.setter + def c_4_lanes(self, enable: bool): + self.__c_4_lanes = enable + + @property + def d_2_lanes(self) -> bool: + """ + Returns state of D 2 lanes mode. + + Returns: + object of `bool` type. + """ + return self.__d_2_lanes + + @d_2_lanes.setter + def d_2_lanes(self, enable: bool): + self.__d_2_lanes = enable + + @property + def e_4_lanes(self) -> bool: + """ + Returns state of E 4 lanes mode. + + Returns: + object of `bool` type. + """ + return self.__e_4_lanes + + @e_4_lanes.setter + def e_4_lanes(self, enable: bool): + self.__e_4_lanes = enable + + def __str__(self) -> str: + return f"C 4 lanes: {self.c_4_lanes}\n" \ + f"D 2 lanes: {self.d_2_lanes}\n" \ + f"E 4 lanes: {self.e_4_lanes}\n" + + +class DutDpAltModeStatus: + + """ + Class `DutDpAltModeStatus` describes DUT DP Alt mode status. + - Get DUT connection state `dut_connection`, type `str`. + - Get DUT multi-function state `dut_multi_function`, type `bool`. + - Get DUT power low state `dut_power_low`, type `bool`. + """ + + __dut_connection_status = {DISCCapability.Reserved: "No connection", + DISCCapability.DPF_D_capable: "DFP_D is connected", + DISCCapability.UFP_D_capable: "UFP_D is connected", + DISCCapability.UFP_D_and_DPF_D: "Both DFP_D and UFP_D are connected"} + + def __init__(self, pdc_io: PDCPortIO): + self.__io = pdc_io + + @property + def dut_connection(self) -> str: + """ + Returns DUT connection state. + + Returns: + object of `str` type. + """ + return self.__dut_connection_status.get(DISCCapability(self.__io.read_dpam_recv().connection)) + + @property + def dut_multi_function(self) -> bool: + """ + Returns DUT multi-function state. + + Returns: + object of `bool` type. + """ + return bool(self.__io.read_dpam_recv().multi_func) + + @property + def dut_power_low(self) -> bool: + """ + Returns DUT power low state. + + Returns: + object of `bool` type. + """ + return bool(self.__io.read_dpam_recv().power_low) + + def __str__(self): + return f"DUT Alt mode: {self.dut_connection}\n" \ + f"DUT Multi-function preferred: {support_or_not(self.dut_multi_function)}\n" \ + f"DUT Power low: {'Low power or DP support disabled' if self.dut_power_low else 'Normal operation'}\n" + + +class TeDpAltModeStatus: + + """ + Class `DutDpAltModeStatus` describes TE DP Alt mode status. + - Get active state `te_active`, type `bool`. + - Get DISC signaling DP v1.3 state, type `bool`. + - Get DISC signaling USB Gen 2 state, type `bool`. + - Get PIN assignment, type `PinAssignmentModes`. + """ + + def __init__(self, pdc_io: PDCPortIO): + self.__io = pdc_io + + @property + def te_active(self) -> bool: + """ + Returns active state. + + Returns: + object of `bool` type. + """ + return bool(self.__io.read_pdc_status().cable_plug) + + @property + def te_select_dp_1_3(self) -> bool: + """ + Returns DISC signaling DP v1.3 state. + + Returns: + object of `bool` type. + """ + return self.__io.read_dpam_config().signaling == DISCDpSignaling.Dp_v13 + + @property + def te_select_usb_gen2(self) -> bool: + """ + Returns DISC signaling USB Gen 2 state. + + Returns: + object of `bool` type. + """ + return self.__io.read_dpam_config().signaling == DISCDpSignaling.Gen2 + + @property + def te_pin_assignment(self) -> PinAssignmentModes: + """ + Returns PIN assignment. + + Returns: + object of `PinAssignmentModes` type. + """ + + return PinAssignmentModes(self.__io.read_dpam_config().pin_assign) + + def __str__(self): + return f"TE Status Active: {support_or_not(self.te_active)}\n" \ + f"TE Select DP v1.3: {self.te_select_dp_1_3}\n" \ + f"TE Select USB gen2: {self.te_select_usb_gen2}\n" \ + f"TE Pin Assignment: {self.te_pin_assignment.name}\n" + + +class DpAltModeStatus: + """ + Class `DpAltModeStatus` describes DP Alt mode status. + - Get DUT DP Alt mode status `dut_dp_alt_mode`, type `DutDpAltModeStatus`. + - Get TE DP Alt mode status, type `TeDpAltModeStatus`. + - Get support state of DISC Signaling DP v13 `support_dp_1_3`, type `bool`. + - Get support state of DISC Signaling USB Gen2 `support_usb_gen2`, type `bool`. + - Get support state of DFP D `support_dfp_d`, type `bool`. + - Get support state of UFP D `support_ufp_d`, type `bool`. + """ + + def __init__(self, pdc_io: PDCPortIO): + self.__io = pdc_io + self.__dut_dp_alt_mode = DutDpAltModeStatus(self.__io) + self.__te_dp_alt_mode = TeDpAltModeStatus(self.__io) + + @property + def support_dp_1_3(self) -> bool: + """ + Returns support state of DISC Signaling DP v13. + + Returns: + object of `bool` type. + """ + return self.__io.read_dpam_disc_modes().signaling == DISCDpSignaling.Dp_v13 + + @property + def support_usb_gen2(self) -> bool: + """ + Returns support state of DISC Signaling USB Gen2. + + Returns: + object of `bool` type. + """ + return self.__io.read_dpam_disc_modes().signaling == DISCDpSignaling.Gen2 + + @property + def support_dfp_d(self) -> bool: + """ + Returns support state of DFP D. + + Returns: + object of `bool` type. + """ + return DISCCapability(self.__io.read_dpam_disc_modes().capability) in [DISCCapability.DPF_D_capable, + DISCCapability.UFP_D_and_DPF_D] + + @property + def support_ufp_d(self) -> bool: + """ + Returns support state of UFP D. + + Returns: + object of `bool` type. + """ + return DISCCapability(self.__io.read_dpam_disc_modes().capability) in [DISCCapability.UFP_D_capable, + DISCCapability.UFP_D_and_DPF_D] + + def multifunction_preferred(self) -> bool: + """ + Returns state of multifunction preferred. + + Returns: + object of `bool` type. + """ + return self.__io.read_dpam_caps2() + + def auto_enter(self) -> bool: + """ + Returns state of auto enter. + + Returns: + object of `bool` type. + """ + return self.__io.read_dpam_state().auto_dpam != 0 + + @property + def dut_dp_alt_mode(self) -> DutDpAltModeStatus: + """ + Returns DUT DP Alt mode status. + + Returns: + object of `DutDpAltModeStatus` type. + """ + return self.__dut_dp_alt_mode + + @property + def te_dp_alt_mode(self) -> TeDpAltModeStatus: + """ + Returns TE DP Alt mode status. + + Returns: + object of `TeDpAltModeStatus` type. + """ + return self.__te_dp_alt_mode + + def __str__(self): + return f"Supports DP v1.3: {support_or_not(self.support_dp_1_3)}\n" \ + f"Supports USB gen2: {support_or_not(self.support_usb_gen2)}\n" \ + f"Multifunction preferred: {support_or_not(self.multifunction_preferred())}\n" \ + f"Auto enter: {support_or_not(self.auto_enter())}\n" \ + f"Pin Assignment supported:\n" \ + f" - Support DFP_D: {support_or_not(self.support_dfp_d)}\n" \ + f" - Support UFP_D: {support_or_not(self.support_ufp_d)}\n" \ + f"{self.__dut_dp_alt_mode.__str__()}" \ + f"{self.__te_dp_alt_mode.__str__()}" + + +class DpAltModeBase: + + """ + Class `DpAltModeBase` describes basic DP Alt mode functionality. + - Get DP Alt mode status `status`, type `DpAltModeStatus`. + - Enter to 2 lane mode `enter_2_lane`. + - Enter to 4 lane mode `enter_4_lane`. + - Exit from DP Alt mode `exit`. + - Disable DP Alt mode `disable`. + - Enable/Disable auto enter to DP Alt mode `auto_enter`. + - Enable/Disable multifunction preferred `multifunction_preferred`. + - Enable/Disable align Dp and USB Data role `align_dp_and_usb_data_role`. + - Set and get UFP caps `ufp_caps`, type `PinAssignment`. + - Set and get DFP caps `dfp_caps`, type `PinAssignment`. + """ + + def __init__(self, pdc_io: PDCPortIO): + self._io = pdc_io + self.__caps = self._io.read_pdc_caps() + self.__status = DpAltModeStatus(pdc_io) + + @property + def status(self) -> DpAltModeStatus: + """ + Returns DP Alt mode status. + + Returns: + object of `DpAltModeStatus` type. + """ + return self.__status + + def enter_2_lane(self): + """ + Enter to 2 lane mode. + """ + self._io.write_dpam_control(DPAMCommand.EnterDPAM2lanes) + + def enter_4_lane(self): + """ + Enter to 4 lane mode. + """ + self._io.write_dpam_control(DPAMCommand.EnterDPAM4lanes) + + def exit(self): + """ + Exit from DP Alt mode. + """ + self._io.write_dpam_control(DPAMCommand.ExitDPAM) + + def disable(self): + """ + Disable DP Alt mode. + """ + self._io.write_dpam_control(DPAMCommand.DisableDPAM) + + def auto_enter(self, enable: bool): + """ + Enable/Disable auto enter to DP Alt mode. + + Args: + enable (`bool`) + """ + self._io.write_dpam_control(DPAMCommand.AutoEnterDPAM if enable else DPAMCommand.ManualDPAM) + + def multifunction_preferred(self, enable: bool): + """ + Enable/Disable multifunction preferred. + + Args: + enable (`bool`) + """ + self._io.write_dpam_control(DPAMCommand.SetMFPreferredFlag if enable else DPAMCommand.ClearMFPreferredFlag) + + def align_dp_and_usb_data_role(self, enable: bool): + """ + Enable/Disable align Dp and USB Data role. + + Args: + enable (`bool`) + """ + if self.__caps.align_dp_usb: + self._io.write_dpam_control(DPAMCommand.SetAlignDpCUsb if enable else DPAMCommand.ClearAlignDpCUsb) + self._io.write_dpam_control(DPAMCommand.SetAlignDpDUsb if enable else DPAMCommand.ClearAlignDpDUsb) + self._io.write_dpam_control(DPAMCommand.SetAlignDpEUsb if enable else DPAMCommand.ClearAlignDpEUsb) + else: + warnings.warn("Align DP and USB data role is not supported.") + + @property + def ufp_caps(self) -> PinAssignment: + """ + Returns current UFP caps. + + Returns: + object of `PinAssignment` + """ + caps = self._io.read_dpam_caps() + pin_assignment = PinAssignment() + pin_assignment.c_4_lanes = ((caps.ufp_assign >> 2 & 1) != 0) + pin_assignment.d_2_lanes = ((caps.ufp_assign >> 3 & 1) != 0) + pin_assignment.e_4_lanes = ((caps.ufp_assign >> 4 & 1) != 0) + return pin_assignment + + @ufp_caps.setter + def ufp_caps(self, ufp_caps: PinAssignment): + caps = self._io.read_dpam_caps() + if ufp_caps.c_4_lanes: + caps.ufp_assign |= PinAssignmentModes.C_4lanes.value + else: + caps.ufp_assign &= ~PinAssignmentModes.C_4lanes.value + if ufp_caps.d_2_lanes: + caps.ufp_assign |= PinAssignmentModes.D_2lanes.value + else: + caps.ufp_assign &= ~PinAssignmentModes.D_2lanes.value + if ufp_caps.e_4_lanes: + caps.ufp_assign |= PinAssignmentModes.E_4lanes.value + else: + caps.ufp_assign &= ~PinAssignmentModes.E_4lanes.value + self._io.write_dpam_caps(caps) + + @property + def dfp_caps(self) -> PinAssignment: + """ + Returns current DFP caps. + + Returns: + object of `PinAssignment` + """ + caps = self._io.read_dpam_caps() + pin_assignment = PinAssignment() + pin_assignment.c_4_lanes = ((caps.ufp_assign >> 2 & 1) != 0) + pin_assignment.d_2_lanes = ((caps.ufp_assign >> 3 & 1) != 0) + pin_assignment.e_4_lanes = ((caps.ufp_assign >> 4 & 1) != 0) + return pin_assignment + + @dfp_caps.setter + def dfp_caps(self, dfp_caps: PinAssignment): + caps = self._io.read_dpam_caps() + if dfp_caps.c_4_lanes: + caps.dfp_assign |= PinAssignmentModes.C_4lanes.value + else: + caps.dfp_assign &= ~PinAssignmentModes.C_4lanes.value + if dfp_caps.d_2_lanes: + caps.dfp_assign |= PinAssignmentModes.D_2lanes.value + else: + caps.dfp_assign &= ~PinAssignmentModes.D_2lanes.value + if dfp_caps.e_4_lanes: + caps.dfp_assign |= PinAssignmentModes.E_4lanes.value + else: + caps.dfp_assign &= ~PinAssignmentModes.E_4lanes.value + self._io.write_dpam_caps(caps) + + @property + def version(self) -> DPAMVersion: + return DPAMVersion(self._io.read_dpam_caps().dpam_version) + + +class DpAltMode500(DpAltModeBase): + + """ + Class `DpAltMode500` inherited from class `DpAltModeBase`. + Class `DpAltMode500` allows controlling additional commands. + Also has all the `DpAltModeBase` functionality. + - Enable/Disable DP 2.1 Alt mode `enable_dp21`. + """ + + def __init__(self, pdc_io: PDCPortIO): + super().__init__(pdc_io) + + def enable_dp21(self, enable: bool): + """ + Enable/Disable DP 2.1 Alt mode. + + Args: + enable (`bool`) + """ + self._io.write_dpam_control(DPAMCommand.EnableDPAM2_1 if enable else DPAMCommand.DisableDPAM2_1) + + +class DpAltMode340(DpAltModeBase): + + """ + Class `DpAltMode340` inherited from class `DpAltModeBase`. + Class `DpAltMode340` allows controlling additional commands. + Also has all the `DpAltModeBase` functionality. + - Enable/Disable DP to Type-C cable adapter mode `dp_to_type_c_cable_adapter_mode`. + """ + + def __init__(self, pdc_io: PDCPortIO): + super().__init__(pdc_io) + + def dp_to_type_c_cable_adapter_mode(self, enable: bool): + """ + Enable/Disable DP to Type-C cable adapter mode. + + Args: + enable (`bool`) + """ + self._io.write_dpam_control(DPAMCommand.EnableDPtoTypeCAdapter if enable else + DPAMCommand.DisableDPtoTypeCAdapter) diff --git a/UniTAP/dev/ports/modules/pdc/pdc_dpam_types.py b/UniTAP/dev/ports/modules/pdc/pdc_dpam_types.py new file mode 100644 index 0000000..d5486cd --- /dev/null +++ b/UniTAP/dev/ports/modules/pdc/pdc_dpam_types.py @@ -0,0 +1,43 @@ +from enum import IntEnum + + +class DISCCapability(IntEnum): + """ + Class `DISCCapability` contains all possible variants of DISC capability. + """ + Reserved = 0 + UFP_D_capable = 1 + DPF_D_capable = 2 + UFP_D_and_DPF_D = 3 + + +class DISCDpSignaling(IntEnum): + """ + Class `DISCDpSignaling` contains all possible variants of signaling. + """ + Unspecified = 0 + Dp_v13 = 1 + Gen2 = 2 + + +class PinAssignmentModes(IntEnum): + """ + Class `PinAssignmentModes` contains all possible variants of pin assignment. + """ + NotSupported = 0x00 + A_4_2lanes = 0x01 + B_2_1lanes = 0x02 + C_4lanes = 0x04 + D_2lanes = 0x08 + E_4lanes = 0x10 + F_2lanes_USB_GEN1 = 0x20 + + +class DPAMVersion(IntEnum): + """ + Class `PinAssignmentModes` contains all possible variants of DP Alt mode version. + """ + V_20_or_Earlier = 0 + V_21_or_Higher = 1 + Reserved = 2 + Reserved2 = 3 diff --git a/UniTAP/dev/ports/modules/pdc/pdc_io.py b/UniTAP/dev/ports/modules/pdc/pdc_io.py new file mode 100644 index 0000000..359670d --- /dev/null +++ b/UniTAP/dev/ports/modules/pdc/pdc_io.py @@ -0,0 +1,115 @@ +from UniTAP.libs.lib_tsi.tsi_io import PortIO +from UniTAP.libs.lib_tsi.tsi_private_types import * +from .pdc_private_dpam_types import DPAMCaps, DPAMState, DPAMCommand, DPAMConfig, DPAMDiscModes, DPAMRecv +from .pdc_private_types import HWControlCommand, HWStatus, HWCaps, PDCStatus, PDCControlCommand, PDCState, PDCCaps, \ + PDCContractInfo +from .pdo_private_types import PdoUnion + + +class PDCPortIO: + + def __init__(self, port_io: PortIO): + self.__io = port_io + self.write_adc_control() + + def write_dpam_caps(self, caps: DPAMCaps): + self.__io.set(TSI_PDC_DPAM_CAPS, caps.value(), c_uint32) + + def read_dpam_caps(self) -> DPAMCaps: + return self.__io.get(TSI_PDC_DPAM_CAPS, DPAMCaps)[1] + + def read_dpam_caps2(self) -> bool: + return (self.__io.get(TSI_PDC_DPAM_CAPS2, c_uint32)[1] & 0x1) != 0 + + def read_dpam_state(self) -> DPAMState: + return self.__io.get(TSI_PDC_DPAM_STATE_R, DPAMState)[1] + + def write_dpam_control(self, command: DPAMCommand): + self.__io.set(TSI_PDC_DPAM_CONTROL_W, command.value, c_uint32) + + def read_dpam_config(self) -> DPAMConfig: + return self.__io.get(TSI_PDC_DPAM_CONFIG_R, DPAMConfig)[1] + + def read_dpam_disc_modes(self) -> DPAMDiscModes: + return self.__io.get(TSI_PDC_DPAM_DISC_MODES_R, DPAMDiscModes)[1] + + def read_dpam_recv(self) -> DPAMRecv: + return self.__io.get(TSI_PDC_DPAM_RECV_STS_R, DPAMRecv)[1] + + def write_hw_command(self, command: HWControlCommand): + self.__io.set(TSI_PDC_HW_CONTROL_W, command.value, c_uint32) + + def read_hw_status(self) -> HWStatus: + return self.__io.get(TSI_PDC_HW_STATUS_R, HWStatus)[1] + + def read_hw_caps(self) -> HWCaps: + return self.__io.get(TSI_PDC_HW_CAPS_R, HWCaps)[1] + + def read_pdc_status(self) -> PDCStatus: + return self.__io.get(TSI_PDC_STATUS_R, PDCStatus)[1] + + def write_pdc_command(self, command: PDCControlCommand): + self.__io.set(TSI_PDC_CONTROL, command.value, c_uint32) + + def read_pdc_state(self) -> PDCState: + return self.__io.get(TSI_PDC_STATE, PDCState)[1] + + def read_pdc_caps(self) -> PDCCaps: + return self.__io.get(TSI_PDC_CAPS, PDCCaps)[1] + + def write_pdc_contract_control(self, caps: PDCContractInfo): + self.__io.set(TSI_USBC_PWR_CONTRACT_CONTROL, caps.value(), c_uint32) + + def read_pdc_contract_control(self) -> PDCContractInfo: + return self.__io.get(TSI_USBC_PWR_CONTRACT_CONTROL, PDCContractInfo)[1] + + def read_local_spr_source_pdo(self) -> list: + return self.__io.get(TSI_USBC_PWR_LOCAL_SOURCE_PDO, PdoUnion, self.read_hw_caps().pdo_count)[1] + + def write_local_spr_source_pdo(self, data: list): + self.__io.set(TSI_USBC_PWR_LOCAL_SOURCE_PDO, data, c_uint32, len(data)) + + def read_local_spr_sink_pdo(self) -> list: + return self.__io.get(TSI_USBC_PWR_LOCAL_SINK_PDO, PdoUnion, self.read_hw_caps().pdo_count)[1] + + def write_local_spr_sink_pdo(self, data: list): + self.__io.set(TSI_USBC_PWR_LOCAL_SINK_PDO, data, c_uint32, len(data)) + + def read_tx_id_vdo_count(self) -> int: + return self.__io.get(TSI_PDC_TX_ID_VDO_CNT, c_uint32)[1] + + def read_tx_id_vdo(self) -> list: + return self.__io.get(TSI_PDC_TX_ID_VDO, c_uint32, self.read_tx_id_vdo_count())[1] + + def write_tx_id_vdo(self, data: list): + self.__io.set(TSI_PDC_TX_ID_VDO, data, c_uint32, len(data)) + + def read_contract_data(self) -> list: + return self.__io.get(TSI_PDC_CONTRACT_DATA, c_uint32, 4)[1] + + def write_contract_data(self, data: list): + self.__io.set(TSI_PDC_CONTRACT_DATA, data, c_uint32, len(data)) + + def read_power_contract_select(self) -> int: + return self.__io.get(TSI_USBC_PWR_CONTRACT_SELECT, c_uint32)[1] + + def write_power_contract_select(self, index: int): + self.__io.set(TSI_USBC_PWR_CONTRACT_SELECT, index, c_uint32) + + def read_resistance(self) -> int: + return self.__io.get(TSI_USBC_RESISTANCE_CONTROL, c_uint32)[1] + + def write_resistance(self, resistance: int): + self.__io.set(TSI_USBC_RESISTANCE_CONTROL, resistance, c_uint32) + + def read_adc_vbus_status(self) -> list: + return self.__io.get(TSI_ADC_VBUS_VOLTAGE_R, c_uint32, TSI_ADC_BLOCK_SIZE_WORDS)[1] + + def read_internal_resistance(self) -> int: + return self.__io.get(TSI_USBC_INT_RESISTANCE_STATUS_R, c_uint32)[1] + + def read_external_resistance(self) -> int: + return self.__io.get(TSI_USBC_EXT_RESISTANCE_STATUS_R, c_uint32)[1] + + def write_adc_control(self): + self.__io.set(TSI_W_USBC_ADC_CTRL, 1, c_uint32) diff --git a/UniTAP/dev/ports/modules/pdc/pdc_power_sink.py b/UniTAP/dev/ports/modules/pdc/pdc_power_sink.py new file mode 100644 index 0000000..e40e02f --- /dev/null +++ b/UniTAP/dev/ports/modules/pdc/pdc_power_sink.py @@ -0,0 +1,144 @@ +import copy + +from .pdc_io import PDCPortIO +from .pdc_utils import load_pdo, save_pdo +from .pdo_types import FixedPdoSink, BatteryPdo, VariablePdo, PdoTypeEnum, PdoSide +from .pdo import Pdo +from .pdo_utils import get_pdo_value +from typing import List + + +class PowerSink: + + """ + Class `PowerSink` contains information about PDO's on Sink side. + - Get PDO count `pdo_count`, type `int`. + - Set and get PDO list, `set_pdo_list`, `get_pdo_list`, type `list` with `Pdo`. + - Set and get PDO by index, `set_pdo_by_index`, `get_pdo_by_index`, type `Pdo`. + - Save information about PDO's to file, `save_pdo`. + - Load information about PDO's from file, `load_pdo`. + """ + + def __init__(self, pdc_io: PDCPortIO): + self.__io = pdc_io + self.__pdo_count = self.__io.read_hw_caps().pdo_count + self.__pdo_list = self.__read_pdo() + + def __read_pdo(self) -> List[Pdo]: + pdo_list = [] + values = self.__io.read_local_spr_sink_pdo() + for i in range(self.__pdo_count): + if PdoTypeEnum(values[i].brief_info.pdo_type) == PdoTypeEnum.Fixed and values[i].data > 0: + pdo_list.append(Pdo(pdo=FixedPdoSink(values[i].fixed_pdo_sink, disable=False), side=PdoSide.Sink)) + elif PdoTypeEnum(values[i].brief_info.pdo_type) == PdoTypeEnum.Fixed and values[i].data == 0: + pdo_list.append(Pdo(pdo=FixedPdoSink(values[i].fixed_pdo_sink, disable=True), side=PdoSide.Sink)) + elif PdoTypeEnum(values[i].brief_info.pdo_type) == PdoTypeEnum.Battery: + pdo_list.append(Pdo(pdo=BatteryPdo(values[i].battery_pdo), side=PdoSide.Sink)) + elif PdoTypeEnum(values[i].brief_info.pdo_type) == PdoTypeEnum.Variable: + pdo_list.append(Pdo(pdo=VariablePdo(values[i].variable_pdo), side=PdoSide.Sink)) + return pdo_list + + def __write_pdo(self) -> List[int]: + pdo_list = [] + for index, item in enumerate(self.__pdo_list): + if index == 0: + pdo_list.append(get_pdo_value(item.get_pdo_as_selected_type(FixedPdoSink))) + elif item.pdo_type != PdoTypeEnum.Disabled: + pdo_list.append(get_pdo_value(item.pdo_object)) + else: + pdo_list.append(0) + return pdo_list + + @property + def pdo_count(self) -> int: + """ + Returns current pdo count. + + Returns: + object of `int` type + """ + return self.__pdo_count + + def set_pdo_list(self, pdo_list: List[Pdo]): + """ + Set new Pdo list. + + Args: + pdo_list (`list` with 'Pdo') + """ + self.__pdo_list = copy.deepcopy(pdo_list) + self.__io.write_local_spr_sink_pdo(self.__write_pdo()) + + def set_pdo_by_index(self, pdo_object, index: int): + """ + Set new Pdo by index. + + Args: + pdo_object ('Pdo') + index (`int`) + """ + if not 0 <= index < self.__pdo_count: + raise ValueError(f"Incorrect PDO index. Supported PDO amount: {self.__pdo_count}") + self.__pdo_list[index] = copy.deepcopy(pdo_object) + self.__io.write_local_spr_sink_pdo(self.__write_pdo()) + + def get_pdo_list(self, read_from_device: bool = False) -> List[Pdo]: + """ + Returns current pdo list. + + Args: + read_from_device (`bool`) + + Returns: + object of `list` type with `Pdo`. + """ + if read_from_device: + self.__pdo_list = self.__read_pdo() + return copy.deepcopy(self.__pdo_list) + + def get_pdo_by_index(self, index: int, read_from_device: bool = False) -> Pdo: + """ + Returns current pdo by index. + + Args: + index (`int`) + read_from_device (`bool`) + + Returns: + object of `Pdo` type. + """ + if read_from_device: + self.__pdo_list = self.__read_pdo() + if not 0 <= index < self.__pdo_count: + raise ValueError(f"Incorrect PDO index. Supported PDO amount: {self.__pdo_count}") + return copy.deepcopy(self.__pdo_list[index]) + + def save_pdo(self, path: str): + """ + Save information about PDO's to file. + Supported formats: + - txt + - json + + Args: + path (`str`) + """ + save_pdo(path, self.__pdo_list, False) + + def load_pdo(self, path: str): + """ + Load information about PDO's from file. + Supported formats: + - txt + - json + + Args: + path (`str`) + """ + self.__pdo_list = load_pdo(path, False) + + def __str__(self) -> str: + pdo_status = "" + for index, item in enumerate(self.__pdo_list): + pdo_status += f"PDO {index}\n{item.pdo_object.__str__()}\n" + return f"Power Sink\n{pdo_status}\n" diff --git a/UniTAP/dev/ports/modules/pdc/pdc_power_source.py b/UniTAP/dev/ports/modules/pdc/pdc_power_source.py new file mode 100644 index 0000000..47bb77f --- /dev/null +++ b/UniTAP/dev/ports/modules/pdc/pdc_power_source.py @@ -0,0 +1,152 @@ +import copy + +from .pdc_io import PDCPortIO +from .pdc_private_types import PDCControlCommand +from .pdc_utils import load_pdo, save_pdo +from .pdo_types import FixedPdoSource, BatteryPdo, VariablePdo, PdoTypeEnum, PdoSide +from .pdo import Pdo +from .pdo_utils import get_pdo_value +from typing import List + + +class PowerSource: + + """ + Class `PowerSource` contains information about PDO's on Source side. + - Get PDO count `pdo_count`, type `int`. + - Set and get PDO list, `set_pdo_list`, `get_pdo_list`, type `list` with `Pdo`. + - Set and get PDO by index, `set_pdo_by_index`, `get_pdo_by_index`, type `Pdo`. + - Send Source PDO's, `send_pdo`. + - Save information about PDO's to file, `save_pdo`. + - Load information about PDO's from file, `load_pdo`. + """ + + def __init__(self, pdc_io: PDCPortIO): + self.__io = pdc_io + self.__pdo_count = self.__io.read_hw_caps().pdo_count + self.__pdo_list = self.__read_pdo() + + def __read_pdo(self) -> List[Pdo]: + pdo_list = [] + values = self.__io.read_local_spr_source_pdo() + for i in range(self.__pdo_count): + if PdoTypeEnum(values[i].brief_info.pdo_type) == PdoTypeEnum.Fixed and values[i].data > 0: + pdo_list.append(Pdo(pdo=FixedPdoSource(values[i].fixed_pdo_source, disable=False), side=PdoSide.Source)) + elif PdoTypeEnum(values[i].brief_info.pdo_type) == PdoTypeEnum.Fixed and values[i].data == 0: + pdo_list.append(Pdo(pdo=FixedPdoSource(values[i].fixed_pdo_source, disable=True), side=PdoSide.Source)) + elif PdoTypeEnum(values[i].brief_info.pdo_type) == PdoTypeEnum.Battery: + pdo_list.append(Pdo(pdo=BatteryPdo(values[i].battery_pdo), side=PdoSide.Source)) + elif PdoTypeEnum(values[i].brief_info.pdo_type) == PdoTypeEnum.Variable: + pdo_list.append(Pdo(pdo=VariablePdo(values[i].variable_pdo), side=PdoSide.Source)) + return pdo_list + + def __write_pdo(self) -> List[int]: + pdo_list = [] + for index, item in enumerate(self.__pdo_list): + if index == 0: + pdo_list.append(get_pdo_value(item.get_pdo_as_selected_type(FixedPdoSource))) + elif item.pdo_type != PdoTypeEnum.Disabled: + pdo_list.append(get_pdo_value(item.pdo_object)) + else: + pdo_list.append(0) + return pdo_list + + @property + def pdo_count(self) -> int: + """ + Returns current pdo count. + + Returns: + object of `int` type + """ + return self.__pdo_count + + def send_pdo(self): + """ + Send Source PDO's. + """ + self.__io.write_pdc_command(PDCControlCommand.SendSourceCaps) + + def get_pdo_list(self, read_from_device: bool = False) -> List[Pdo]: + """ + Returns current pdo list. + + Args: + read_from_device (`bool`) + + Returns: + object of `list` type with `Pdo`. + """ + if read_from_device: + self.__pdo_list = self.__read_pdo() + return copy.deepcopy(self.__pdo_list) + + def get_pdo_by_index(self, index: int, read_from_device: bool = False): + """ + Returns current pdo by index. + + Args: + index (`int`) + read_from_device (`bool`) + + Returns: + object of `Pdo` type. + """ + if read_from_device: + self.__pdo_list = self.__read_pdo() + if not 0 <= index < self.__pdo_count: + raise ValueError(f"Incorrect PDO index. Supported PDO amount: {self.__pdo_count}") + return copy.deepcopy(self.__pdo_list[index]) + + def set_pdo_list(self, pdo_list: List[Pdo]): + """ + Set new Pdo list. + + Args: + pdo_list (`list` with 'Pdo') + """ + self.__pdo_list = copy.deepcopy(pdo_list) + self.__io.write_local_spr_source_pdo(self.__write_pdo()) + + def set_pdo_by_index(self, pdo_object, index: int): + """ + Set new Pdo by index. + + Args: + pdo_object ('Pdo') + index (`int`) + """ + if not 0 <= index < self.__pdo_count: + raise ValueError(f"Incorrect PDO index. Supported PDO amount: {self.__pdo_count}") + self.__pdo_list[index] = copy.deepcopy(pdo_object) + self.__io.write_local_spr_source_pdo(self.__write_pdo()) + + def save_pdo(self, path: str): + """ + Save information about PDO's to file. + Supported formats: + - txt + - json + + Args: + path (`str`) + """ + save_pdo(path, self.__pdo_list, True) + + def load_pdo(self, path: str): + """ + Load information about PDO's from file. + Supported formats: + - txt + - json + + Args: + path (`str`) + """ + self.__pdo_list = load_pdo(path, True) + + def __str__(self) -> str: + pdo_status = "" + for index, item in enumerate(self.__pdo_list): + pdo_status += f"PDO {index}\n{item.pdo_object.__str__()}\n" + return f"Power Source\n{pdo_status}\n" diff --git a/UniTAP/dev/ports/modules/pdc/pdc_private_dpam_types.py b/UniTAP/dev/ports/modules/pdc/pdc_private_dpam_types.py new file mode 100644 index 0000000..996cd43 --- /dev/null +++ b/UniTAP/dev/ports/modules/pdc/pdc_private_dpam_types.py @@ -0,0 +1,104 @@ +from ctypes import Structure, c_uint32 +from enum import IntEnum +from .pdc_dpam_types import DISCCapability, DISCDpSignaling, PinAssignmentModes + + +class DPAMCommand(IntEnum): + DoesNothing = 0x00 + AutoEnterDPAM = 0x01 + EnterDPAM2lanes = 0x02 + EnterDPAM4lanes = 0x03 + ExitDPAM = 0x04 + DisableDPAM = 0x05 + ManualDPAM = 0x06 + DisableDPtoTypeCAdapter = 0x08 + EnableDPtoTypeCAdapter = 0x09 + ClearMFPreferredFlag = 0x0E + SetMFPreferredFlag = 0x0F + ClearAlignDpCUsb = 0x30 + SetAlignDpCUsb = 0x31 + ClearAlignDpDUsb = 0x32 + SetAlignDpDUsb = 0x33 + ClearAlignDpEUsb = 0x34 + SetAlignDpEUsb = 0x35 + EnableDPAM2_1 = 0x38 + DisableDPAM2_1 = 0x39 + + +class DPAMCaps(Structure): + _fields_ = [ + ('', c_uint32, 8), + ('dfp_assign', c_uint32, 8), + ('ufp_assign', c_uint32, 8), + ('', c_uint32, 6), + ('dpam_version', c_uint32, 2) + ] + + def value(self) -> int: + return self.dfp_assign << 8 | self.dfp_assign << 16 | self.dpam_version << 30 + + +class DPAMState(Structure): + _fields_ = [ + ('auto_dpam', c_uint32, 1), + ('dpam_enable', c_uint32, 1), + ('adapter_mode', c_uint32, 1), + ('', c_uint32, 1), + ('align_dp_c_usb', c_uint32, 1), + ('align_dp_d_usb', c_uint32, 1), + ('align_dp_e_usb', c_uint32, 1), + ('', c_uint32, 25) + ] + + +class DPAMConfig(Structure): + _fields_ = [ + ('config', c_uint32, 2), + ('signaling', c_uint32, 4), + ('', c_uint32, 2), + ('pin_assign', c_uint32, 8), + ('', c_uint32, 10), + ('uhbr_13_5_support', c_uint32, 1), + ('', c_uint32, 1), + ('cable_active_component', c_uint32, 2), + ('dpam_version', c_uint32, 2) + ] + + +class DPAMDiscModes(Structure): + _fields_ = [ + ('capability', c_uint32, 2), + ('signaling', c_uint32, 4), + ('dp_placement', c_uint32, 1), + ('usb_signaling', c_uint32, 1), + ('dfp_assign', c_uint32, 8), + ('ufp_assign', c_uint32, 8), + ('', c_uint32, 6), + ('dpam_version', c_uint32, 2) + ] + + @property + def capability(self) -> DISCCapability: + return DISCCapability(self.capability) + + @property + def signaling(self) -> DISCDpSignaling: + return DISCDpSignaling(self.signaling) + + +class DPAMRecv(Structure): + _fields_ = [ + ('connection', c_uint32, 2), + ('power_low', c_uint32, 1), + ('enable', c_uint32, 1), + ('multi_func', c_uint32, 1), + ('usb_cfg_rq', c_uint32, 1), + ('exit_rq', c_uint32, 1), + ('hpd_state', c_uint32, 1), + ('hpd_irq', c_uint32, 1), + ('no_dpam_suspend', c_uint32, 1), + ] + + @property + def connection(self) -> DISCCapability: + return DISCCapability(self.capability) diff --git a/UniTAP/dev/ports/modules/pdc/pdc_private_types.py b/UniTAP/dev/ports/modules/pdc/pdc_private_types.py new file mode 100644 index 0000000..8813683 --- /dev/null +++ b/UniTAP/dev/ports/modules/pdc/pdc_private_types.py @@ -0,0 +1,227 @@ +from ctypes import Structure, c_uint32, c_uint64 +from enum import IntEnum +from .pdc_types import CCPullUp, ContractTypePriority, DRPTryMode, USB3Mode, PdcDeviceRole, PDMode + + +class HWControlCommand(IntEnum): + DoNothing = 0x00 + ToggleCableFlip = 0x01 + SetCableFlipToOff = 0x02 + SetCableFlipToOn = 0x03 + EnableUSBDeviceCharger = 0x04 + DisableUSBDeviceCharger = 0x05 + SwitchToHostMode = 0x06 + SwitchToDeviceMode = 0x07 + Enable20PHY = 0x08 + Disable20PHY = 0x09 + Enable20PHYDeviceModeEnableCharger = 0x0A + Enable20PHYHostModeDisableCharger = 0x0B + Disable20PHYDisableCharger = 0x0C + DisableIntLoad_5_5_ohm = 0x0E + EnableIntLoad_5_5_ohm = 0x0F + DisableIntLoad_3_5_ohm = 0x10 + EnableIntLoad_3_5_ohm = 0x11 + DisableIntLoad_10_ohm = 0x12 + EnableIntLoad_10_ohm = 0x13 + DisconnectCCLines = 0x14 + ConnectCCLines = 0x15 + RePlugCable = 0x16 + SetNewETCableMode = 0x18 + SetOldETCableMode = 0x19 + Enable30PHY = 0x1A + Disable30PHY = 0x1B + ResetPDController = 0x1C + + +class PDCControlCommand(IntEnum): + DoNothing = 0x00 + ToggleDataRole = 0x01 + SetDataRoleUFP = 0x02 + SetDataRoleDFP = 0x03 + SwapVconn = 0x04 + TogglePowerRole = 0x05 + FastRoleSwap = 0x06 + HardReset = 0x07 + SimulateReConnect = 0x0A + PullCCLineToDefault = 0x0C + PullCCLineTo1_5A = 0x0D + PullCCLineTo3_0A = 0x0E + SetDataRoleDRP = 0x14 + SetDataRoleDRP_SNK_Support = 0x15 + SetDataRoleDRP_SRC_Support = 0x16 + DisableUSB3 = 0x18 + EnableUSB3_gen1 = 0x19 + EnableUSB3_gen2 = 0x1A + DisableUSB2 = 0x1C + EnableUSB2 = 0x1D + DisableVconnSwap = 0x1E + EnableVconnSwap = 0x1F + DisablePRSwap = 0x20 + EnablePRSwap = 0x21 + DisableDRSwap = 0x22 + EnableDRSwap = 0x23 + DisableDebugAccessorySupport = 0x24 + EnableDebugAccessorySupport = 0x25 + DisableAudioAccessorySupport = 0x26 + EnableAudioAccessorySupport = 0x27 + DisableFRSwap = 0x28 + EnableFRSwap = 0x29 + SetFRSwapNoSupported = 0x2A + SetFRSwapUSBPower = 0x2B + SetFRSwap1_5A_5V = 0x2C + SetFRSwap3A_5V = 0x2C + EnableEPRasPowerSink = 0x2E + DisableEPRasPowerSink = 0x2F + EnableEPRAutoEnter = 0x30 + DisableEPRAutoEnter = 0x31 + InitiateEPRModeEnter = 0x32 + InitiateEPRModeExit = 0x33 + EnableRejectEPRModeEnter = 0x34 + DisableRejectEPRModeEnter = 0x35 + SendSourceCaps = 0x41 + + +class HWCaps(Structure): + _fields_ = [ + ('', c_uint32, 1), + ('et_board', c_uint32, 1), + ('', c_uint32, 2), + ('pdo_count', c_uint32, 4), + ('', c_uint32, 24) + ] + + +class HWStatus(Structure): + _fields_ = [ + ('cable_flip', c_uint32, 1), + ('ext_power', c_uint32, 1), + ('charger', c_uint32, 1), + ('usb_mode', c_uint32, 1), + ('usb_20_phy', c_uint32, 1), + ('et_cable', c_uint32, 1), + ('int_3_5_ohm', c_uint32, 1), + ('int_10_ohm', c_uint32, 1), + ('cc1_closed', c_uint32, 1), + ('cc2_closed', c_uint32, 1), + ('int_5_5_ohm', c_uint32, 1), + ('et_cable_mode', c_uint32, 1), + ('usb_30_phy', c_uint32, 1), + ('int_3_5_ovp', c_uint32, 1), + ('int_10_ovp', c_uint32, 1), + ('int_5_5_ovp', c_uint32, 1), + ('', c_uint32, 13), + ('pdc_fault', c_uint32, 1), + ('', c_uint32, 1), + ('pdc_reset', c_uint32, 1) + ] + + +class PDCStatus(Structure): + _fields_ = [ + ('cable_plug', c_uint64, 1), + ('cable_orientation', c_uint64, 1), + ('power_role', c_uint64, 1), + ('data_role', c_uint64, 1), + ('rmt_snk_pdo', c_uint64, 1), + ('rmt_src_pdo', c_uint64, 1), + ('pwr_contract', c_uint64, 1), + ('rmt_svid', c_uint64, 1), + ('vconn_enabled', c_uint64, 1), + ('emarked_cable', c_uint64, 1), + ('dp_alt_mode', c_uint64, 1), + ('usb_device', c_uint64, 1), + ('usb_host', c_uint64, 1), + ('usb_2_conn', c_uint64, 1), + ('usb_3_conn', c_uint64, 1), + ('sop_identity', c_uint64, 1), + ('current', c_uint64, 2), + ('adc_data', c_uint64, 1), + ('am_sw_inprog', c_uint64, 1), + ('pullup_sence', c_uint64, 2), + ('rx_identity', c_uint64, 1), + ('', c_uint64, 6), + ('pdc_fault', c_uint64, 1), + ('', c_uint64, 2), + ('rx_sink_pdp', c_uint64, 1), + ('rx_source_pdp', c_uint64, 1), + ('rx_snk_epr_pdo', c_uint64, 1), + ('rx_src_epr_pdo', c_uint64, 1), + ('', c_uint64, 28) + ] + + +class PDCState(Structure): + _fields_ = [ + ('cc_pull_up', c_uint32, 2), + ('dev_role', c_uint32, 2), + ('pd_mode', c_uint32, 2), + ('usb30_mode', c_uint32, 2), + ('usb2_enabled', c_uint32, 1), + ('vconn_swap_en', c_uint32, 1), + ('pr_swap_en', c_uint32, 1), + ('dr_swap_en', c_uint32, 1), + ('dbg_accessory', c_uint32, 1), + ('aud_accessory', c_uint32, 1), + ('fr_swap_en', c_uint32, 1), + ('', c_uint32, 1), + ('drp_try_mode', c_uint32, 2), + ('', c_uint32, 2), + ('epr_snk_en', c_uint32, 1), + ('epr_auto_enter_en', c_uint32, 1), + ('epr_mode', c_uint32, 1), + ('epr_enter_en', c_uint32, 1), + ] + + @property + def cc_pull_up(self) -> CCPullUp: + return CCPullUp(self.cc_pull_up) + + @property + def dev_role(self) -> PdcDeviceRole: + return PdcDeviceRole(self.dev_role) + + @property + def pd_mode(self) -> PDMode: + return PDMode(self.pd_mode) + + @property + def usb30_mode(self) -> USB3Mode: + return USB3Mode(self.usb30_mode) + + @property + def drp_try_mode(self) -> DRPTryMode: + return DRPTryMode(self.drp_try_mode) + + +class PDCCaps(Structure): + _fields_ = [ + ('try_snk', c_uint32, 1), + ('try_src', c_uint32, 1), + ('fr_swap', c_uint32, 1), + ('cable_sim', c_uint32, 1), + ('ext_sink_pc', c_uint32, 1), + ('align_dp_usb', c_uint32, 1), + ('', c_uint32, 16) + ] + + +class PDCContractInfo(Structure): + _fields_ = [ + ('auto_negotate', c_uint32, 1), + ('use_battery_pdo', c_uint32, 1), + ('use_variable_pdo', c_uint32, 1), + ('comm_capable', c_uint32, 1), + ('type_priority', c_uint32, 2), + ('no_usb_suspend', c_uint32, 1), + ('give_back_flag', c_uint32, 1), + ('auto_min_power', c_uint32, 1), + ('', c_uint32, 6), + ('min_power', c_uint32, 10), + ('', c_uint32, 5), + ('manual', c_uint32, 1), + ] + + def value(self) -> int: + return self.auto_negotate | self.use_battery_pdo << 1 | self.use_variable_pdo << 2 | self.comm_capable << 3 | \ + self.type_priority << 4 | self.no_usb_suspend << 6 | self.give_back_flag << 7 | \ + self.auto_min_power << 8 | self.min_power << 15 | self.manual << 31 diff --git a/UniTAP/dev/ports/modules/pdc/pdc_types.py b/UniTAP/dev/ports/modules/pdc/pdc_types.py new file mode 100644 index 0000000..6ab7bff --- /dev/null +++ b/UniTAP/dev/ports/modules/pdc/pdc_types.py @@ -0,0 +1,125 @@ +from enum import IntEnum + + +class CCPullUp(IntEnum): + """ + Class `CCPullUp` contains all possible variants of CC Pull Up. + """ + Current_05_09A = 0 + Current_1_5A = 1 + Current_3A = 2 + Unknown = 3 + + +class PowerRole(IntEnum): + """ + Class `PowerRole` contains all possible variants of device power role. + """ + Source = 0 + Sink = 1 + + +class FrSwapCurrent(IntEnum): + """ + Class `FrSwapCurrent` contains all possible variants of FR Swap current. + """ + Disable = 0 + EnableDefaultUSBPower = 1 + Enable_1_5A_5V = 2 + Enable_3A_5V = 3 + + +class PdcDeviceRole(IntEnum): + """ + Class `PdcDeviceRole` contains all possible variants of PDC device role. + """ + UFP = 0 + DFP = 1 + DRP = 2 + Unknown = 3 + + +class PDMode(IntEnum): + """ + Class `PDMode` contains all possible variants of PD modes. + """ + NormalPD = 0 + LegacyUSBSource = 1 + LegacyUSBSink = 2 + Unknown = 3 + + +class USB3Mode(IntEnum): + """ + Class `USB3Mode` contains all possible variants of USB-3 modes. + """ + Disabled = 0 + EnabledGen1 = 1 + EnabledGen2 = 2 + Unknown = 3 + + +class DRPTryMode(IntEnum): + """ + Class `DRPTryMode` contains all possible variants of DRP behavior modes. + """ + PureDRP = 0 + DRP_try_SNK = 1 + DRP_try_SRC = 2 + Unknown = 3 + + +class CableControlOrientation(IntEnum): + """ + Class `CableControlOrientation` contains all possible variants of orientation. + """ + CC1 = 0 + CC2 = 1 + + +class DifferentialPair(IntEnum): + """ + Class `DifferentialPair` contains all possible variants of differential pairs. + """ + OnePair = 0 + TwoPair = 1 + + +class ContractTypePriority(IntEnum): + """ + Class `ContractTypePriority` contains all possible variants of contract type priority. + """ + HigherCurrent = 0 + HigherVoltage = 1 + HigherPower = 2 + Unknown = 3 + + +class InternalResistance(IntEnum): + """ + Class `InternalResistance` contains all possible variants of internal resistance. + """ + Disable = 1 + Resistance_10_Ohm = 40001 + Resistance_5_5_Ohm = 22001 + Resistance_3_55_Ohm = 14161 + Resistance_3_5_Ohm = 14001 + Resistance_2_6_Ohm = 10361 + Resistance_2_14_Ohm = 8521 + Resistance_1_76_Ohm = 7001 + + +class ExternalResistance(IntEnum): + """ + Class `ExternalResistance` contains all possible variants of external resistance. + """ + Disable = 3 + Resistance_13_9_Ohm = 55603 + Resistance_10_6_Ohm = 42403 + Resistance_9_1_Ohm = 36403 + Resistance_7_6_Ohm = 30403 + Resistance_6_6_Ohm = 26403 + Resistance_5_6_Ohm = 22403 + Resistance_4_6_Ohm = 18403 + Resistance_3_6_Ohm = 14403 + Resistance_1_8_Ohm = 7203 diff --git a/UniTAP/dev/ports/modules/pdc/pdc_utils.py b/UniTAP/dev/ports/modules/pdc/pdc_utils.py new file mode 100644 index 0000000..b6eacf1 --- /dev/null +++ b/UniTAP/dev/ports/modules/pdc/pdc_utils.py @@ -0,0 +1,72 @@ +import warnings +import os +import json + +from UniTAP.dev.ports.modules.pdc.pdo import Pdo +from UniTAP.dev.ports.modules.pdc.pdo_utils import get_pdo_value +from UniTAP.dev.ports.modules.pdc.pdo_types import * +from UniTAP.dev.ports.modules.pdc.pdo_private_types import PdoUnion + + +def support_or_not(value) -> str: + return "Yes" if bool(value) else "No" + + +def save_pdo(path: str, pdo_list: list, source: bool): + filename, file_extension = os.path.splitext(path) + if file_extension.find('.txt') != -1: + file = open(path, 'w') + file.write("TSI_USBC_PWR_LOCAL_SOURCE_PDO\n" if source else "TSI_USBC_PWR_LOCAL_SINK_PDO\n") + file.write("1\n") + file.write(str(len(pdo_list) * 4)) + for pdo in pdo_list: + file.write(str(get_pdo_value(pdo.get_pdo_object()))) + file.close() + elif file_extension.find('.json') != -1: + values = {} + for index, pdo in enumerate(pdo_list): + values.update({f"EPR{index}": get_pdo_value(pdo.get_pdo_object())}) + with open(path, "w") as outfile: + json.dump(values, outfile) + else: + warnings.warn(f"Unsupported file format: {file_extension}. " + f"Please, select file format from available variants: 'txt', 'json'") + + +def load_pdo(path: str, source: bool) -> list: + if os.path.exists(path): + raise ValueError(f"Incorrect path {path} to file. Path does not exist.") + filename, file_extension = os.path.splitext(path) + pdo_list = [] + if file_extension.find('.txt') != -1: + file = open(path, 'r') + data = file.read().split("\n") + for index, line in enumerate(data): + if index >= 3: + fill_list(int(line), pdo_list, source) + file.close() + elif file_extension.find('.json') != -1: + with open(path, "r", encoding='utf-8') as read_file: + data = json.load(read_file) + keys = list(data.keys()) + for index, key in enumerate(keys): + fill_list(int(data.get(key)), pdo_list, source) + else: + raise ValueError(f"Unsupported file format: {file_extension}. " + f"Please, select file format from available variants: 'txt', 'json'") + return pdo_list + + +def fill_list(data, pdo_list, source): + pdo_data = PdoUnion(data) + pdo_type = PdoTypeEnum(pdo_data.brief_info.pdo_type) + if pdo_type == PdoTypeEnum.Fixed: + pdo_list.append(Pdo(pdo=FixedPdoSource(pdo_data.fixed_pdo_source) if source else + FixedPdoSink(pdo_data.fixed_pdo_sink), + side=PdoSide.Source if source else PdoSide.Sink)) + elif pdo_type == PdoTypeEnum.Battery: + pdo_list.append(Pdo(pdo=BatteryPdo(pdo_data.battery_pdo), + side=PdoSide.Source if source else PdoSide.Sink)) + elif pdo_type == PdoTypeEnum.Variable: + pdo_list.append(Pdo(pdo=VariablePdo(pdo_data.variable_pdo), + side=PdoSide.Source if source else PdoSide.Sink)) diff --git a/UniTAP/dev/ports/modules/pdc/pdo.py b/UniTAP/dev/ports/modules/pdc/pdo.py new file mode 100644 index 0000000..b079eab --- /dev/null +++ b/UniTAP/dev/ports/modules/pdc/pdo.py @@ -0,0 +1,99 @@ +from typing import Type + +from UniTAP.dev.ports.modules.pdc.pdo_utils import interpret_pdo_value +from UniTAP.dev.ports.modules.pdc.pdo_types import PdoSide, PdoType, FixedPdoSink, FixedPdoSource, BatteryPdo, \ + VariablePdo, PdoTypeEnum + + +class Pdo: + + """ + Class `Pdo` describes power delivery object in PDC module. Contains information about side of PDO `PdoSide` and + main PDO data. May have one of the available type `PdoType`: `FixedPdoSink`, `FixedPdoSource`, `BatteryPdo`, + `VariablePdo`. + - Get PDO type `pdo_type`, type of `PdoTypeEnum`. + - Get PDO side `pdo_side`, type of `PdoSide`. + - Set and get main PDO object `pdo_object`, type of `PdoType`. + - Get pdo object as selected PDO type `PdoType` `get_pdo_as_selected_type`, type of `PdoType`. + - Convert (interpret) from one PDO type to another `interpret_pdo_as_selected_type`. + """ + + def __init__(self, pdo: PdoType, side: PdoSide): + self.__pdo = pdo + self.__side = side + + @property + def pdo_type(self) -> PdoTypeEnum: + """ + Returns current PDO type. + + Returns: + object of `PdoTypeEnum` type + """ + return self.__pdo.pdo_type + + @property + def pdo_side(self) -> PdoSide: + """ + Returns current PDO side. + + Returns: + object of `PdoSide` type + """ + return self.__side + + @property + def pdo_object(self) -> PdoType: + """ + Returns current PDO object. + + Returns: + object of `PdoType` type + """ + return self.__pdo + + @pdo_object.setter + def pdo_object(self, pdo: PdoType): + self.__pdo = pdo + + def disable_pdo(self): + """ + Disable PDO. Will be filled with zeros. + """ + self.__pdo = FixedPdoSink(disable=True) if self.__side == PdoSide.Sink else FixedPdoSource(disable=True) + + def get_pdo_as_selected_type(self, pdo: Type[PdoType]) -> PdoType: + """ + Returns PDO object as selected new PDO type. + + Args: + pdo (`PdoType`) - type of PDO + + Returns: + object of `PdoType` type + """ + self.interpret_pdo_as_selected_type(pdo) + return self.__pdo + + def interpret_pdo_as_selected_type(self, pdo: Type[PdoType]): + """ + Convert (interpret) from one PDO type to another. + + Args: + pdo (`PdoType`) - type of PDO + """ + if pdo == FixedPdoSink and self.__side == PdoSide.Sink: + self.__pdo = FixedPdoSink(interpret_pdo_value(self.__pdo, pdo)) + elif pdo == FixedPdoSource and self.__side == PdoSide.Source: + self.__pdo = FixedPdoSource(interpret_pdo_value(self.__pdo, pdo)) + elif pdo == BatteryPdo: + self.__pdo = BatteryPdo(interpret_pdo_value(self.__pdo, pdo)) + elif pdo == VariablePdo: + self.__pdo = VariablePdo(interpret_pdo_value(self.__pdo, pdo)) + else: + raise ValueError(f"Incorrect PDO type: {pdo}. Available types: BatteryPdo, VariablePdo, " + f"{'FixedPdoSink' if self.__side == PdoSide.Sink else 'FixedPdoSource'}") + + def __str__(self): + return f'PDO type: {self.pdo_type.name}\n' \ + f'{self.__pdo.__str__()}' diff --git a/UniTAP/dev/ports/modules/pdc/pdo_private_types.py b/UniTAP/dev/ports/modules/pdc/pdo_private_types.py new file mode 100644 index 0000000..2c1e67a --- /dev/null +++ b/UniTAP/dev/ports/modules/pdc/pdo_private_types.py @@ -0,0 +1,74 @@ +from typing import TypeVar +from ctypes import Structure, Union, c_uint32 + + +class FixedPdoSourceStruct(Structure): + _fields_ = [ + ('max_current', c_uint32, 10), + ('voltage', c_uint32, 10), + ('peak_current', c_uint32, 2), + ('', c_uint32, 3), + ('dual_data_role', c_uint32, 1), + ('usb_communication_capable', c_uint32, 1), + ('externally_powered', c_uint32, 1), + ('usb_suspend_supported', c_uint32, 1), + ('dual_power_role_capable', c_uint32, 1), + ('pdo_type', c_uint32, 2), + ] + + +class FixedPdoSinkStruct(Structure): + _fields_ = [ + ('oper_current', c_uint32, 10), + ('voltage', c_uint32, 10), + ('', c_uint32, 5), + ('dual_data_role', c_uint32, 1), + ('usb_communication_capable', c_uint32, 1), + ('externally_powered', c_uint32, 1), + ('usb_suspend_supported', c_uint32, 1), + ('dual_power_role_capable', c_uint32, 1), + ('pdo_type', c_uint32, 2), + ] + + +class VariablePdoStruct(Structure): + _fields_ = [ + ('max_current', c_uint32, 10), + ('min_voltage', c_uint32, 10), + ('max_voltage', c_uint32, 10), + ('pdo_type', c_uint32, 2), + ] + + +class BatteryPdoStruct(Structure): + _fields_ = [ + ('max_power', c_uint32, 10), + ('min_voltage', c_uint32, 10), + ('max_voltage', c_uint32, 10), + ('pdo_type', c_uint32, 2), + ] + + +class PdoBriefInfo(Structure): + _fields_ = [ + ('data', c_uint32, 30), + ('pdo_type', c_uint32, 2) + ] + + +class PdoUnion(Union): + _fields_ = [ + ('data', c_uint32), + ('brief_info', PdoBriefInfo), + ('fixed_pdo_source', FixedPdoSourceStruct), + ('fixed_pdo_sink', FixedPdoSinkStruct), + ('variable_pdo', VariablePdoStruct), + ('battery_pdo', BatteryPdoStruct) + ] + + +PdoTypeStruct = TypeVar("PdoTypeStruct", + FixedPdoSinkStruct, + FixedPdoSourceStruct, + BatteryPdoStruct, + VariablePdoStruct) diff --git a/UniTAP/dev/ports/modules/pdc/pdo_types.py b/UniTAP/dev/ports/modules/pdc/pdo_types.py new file mode 100644 index 0000000..c3c4354 --- /dev/null +++ b/UniTAP/dev/ports/modules/pdc/pdo_types.py @@ -0,0 +1,488 @@ +from enum import IntEnum +from typing import TypeVar +from .pdo_private_types import FixedPdoSourceStruct, FixedPdoSinkStruct, BatteryPdoStruct, VariablePdoStruct + + +class PdoTypeEnum(IntEnum): + """ + Class `PdoTypeEnum` contains all possible variants of PDO types. + """ + Fixed = 0 + Battery = 1 + Variable = 2 + Disabled = 3 + Mandatory = 4 + + +class PdoSide(IntEnum): + """ + Class `PdoTypeEnum` contains all possible variants of PDO side. + """ + Sink = 0 + Source = 1 + + +class PeakCurrent(IntEnum): + """ + Class `PdoTypeEnum` contains all possible variants of peak current for PDO. + """ + Percent_100 = 0 + Percent_110 = 1 + Percent_125 = 2 + Percent_150 = 3 + + +class FixedPdoSource: + + """ + Class `FixedPdoSource` contains information about Fixed PDO on Source side. + - Set and get maximum current `max_current`, type `int`. + - Set and get voltage `voltage`, type `int`. + - Set and get peak_current `peak_current`, type `PeakCurrent`. + - Set and get dual data role flag `dual_data_role`, type `bool`. + - Set and get usb communication flag `usb_communication`, type `bool`. + - Set and get unconstrained power flag `unconstrained_power`, type `bool`. + - Set and get higher capability flag `higher_capability`, type `bool`. + - Set and get dual power role flag `dual_power_role`, type `bool`. + - Get PDO type `pdo_type`, type `PdoTypeEnum`. + """ + + def __init__(self, pdo: FixedPdoSourceStruct = FixedPdoSourceStruct(0), disable=False): + self.__max_current = pdo.max_current * 10 + self.__voltage = pdo.voltage * 50 + self.__peak_current = PeakCurrent(pdo.peak_current) + self.__dual_data_role = pdo.dual_data_role + self.__usb_communication = pdo.usb_communication_capable + self.__unconstrained_power = pdo.externally_powered + self.__higher_capability = pdo.usb_suspend_supported + self.__dual_power_role = pdo.dual_power_role_capable + self.__type = PdoTypeEnum.Disabled if disable else PdoTypeEnum.Fixed + + @property + def max_current(self) -> int: + """ + Returns maximum current. + + Returns: + object of `int` type + """ + return self.__max_current + + @max_current.setter + def max_current(self, max_current: int): + self.__max_current = max_current + + @property + def voltage(self) -> int: + """ + Returns voltage. + + Returns: + object of `int` type + """ + return self.__voltage + + @voltage.setter + def voltage(self, voltage: int): + self.__voltage = voltage + + @property + def peak_current(self) -> PeakCurrent: + """ + Returns peak current. + + Returns: + object of `PeakCurrent` type + """ + return self.__peak_current + + @peak_current.setter + def peak_current(self, peak_current: PeakCurrent): + self.__peak_current = peak_current + + @property + def dual_data_role(self) -> bool: + """ + Returns flag of dual data role. + + Returns: + object of `bool` type + """ + return bool(self.__dual_data_role) + + @dual_data_role.setter + def dual_data_role(self, dual_data_role: bool): + self.__dual_data_role = dual_data_role + + @property + def usb_communication(self) -> bool: + """ + Returns flag of usb communication. + + Returns: + object of `bool` type + """ + return bool(self.__usb_communication) + + @usb_communication.setter + def usb_communication(self, usb_communication: bool): + self.__usb_communication = usb_communication + + @property + def unconstrained_power(self) -> bool: + """ + Returns flag of unconstrained power. + + Returns: + object of `bool` type + """ + return bool(self.__unconstrained_power) + + @unconstrained_power.setter + def unconstrained_power(self, unconstrained_power: bool): + self.__unconstrained_power = unconstrained_power + + @property + def higher_capability(self) -> bool: + """ + Returns flag of higher capability. + + Returns: + object of `bool` type + """ + return bool(self.__higher_capability) + + @higher_capability.setter + def higher_capability(self, higher_capability: bool): + self.__higher_capability = higher_capability + + @property + def dual_power_role(self) -> bool: + """ + Returns flag of dual power role. + + Returns: + object of `bool` type + """ + return bool(self.__dual_power_role) + + @dual_power_role.setter + def dual_power_role(self, dual_power_role: bool): + self.__dual_power_role = dual_power_role + + @property + def pdo_type(self) -> PdoTypeEnum: + """ + Returns flag of PDO type. + + Returns: + object of `PdoTypeEnum` type + """ + return self.__type + + def __str__(self): + return f"Max Current: {self.__max_current}\n" \ + f"Voltage: {self.__voltage}\n" \ + f"Peak Current: {self.__peak_current.name}\n" \ + f"Dual Role: {'Yes' if self.__dual_data_role else 'No'}\n" \ + f"USB Communication: {'Yes' if self.__usb_communication else 'No'}\n" \ + f"Unconstrained Power: {'Yes' if self.__unconstrained_power else 'No'}\n" \ + f"Higher capability: {'Yes' if self.__higher_capability else 'No'}\n" \ + f"Dual Power role: {'Yes' if self.__dual_power_role else 'No'}\n" + + +class VariablePdo: + + """ + Class `VariablePdo` contains information about Fixed PDO on Sink and Source side. + - Set and get maximum current `max_current`, type `int`. + - Set and get minimum voltage `min_voltage`, type `int`. + - Set and get maximum voltage `max_voltage`, type `bool`. + - Get PDO type `pdo_type`, type `PdoTypeEnum`. + """ + + def __init__(self, pdo: VariablePdoStruct = VariablePdoStruct(0)): + self.__max_current = pdo.max_current * 10 + self.__min_voltage = pdo.min_voltage * 50 + self.__max_voltage = pdo.max_voltage * 50 + self.__type = PdoTypeEnum.Variable + + @property + def max_current(self) -> int: + """ + Returns maximum current. + + Returns: + object of `int` type + """ + return self.__max_current + + @max_current.setter + def max_current(self, max_current: int): + self.__max_current = max_current + + @property + def min_voltage(self) -> int: + """ + Returns minimum voltage. + + Returns: + object of `int` type + """ + return self.__min_voltage + + @min_voltage.setter + def min_voltage(self, min_voltage: int): + self.__min_voltage = min_voltage + + @property + def max_voltage(self) -> int: + """ + Returns maximum voltage. + + Returns: + object of `int` type + """ + return self.__max_voltage + + @max_voltage.setter + def max_voltage(self, max_voltage: int): + self.__max_voltage = max_voltage + + @property + def pdo_type(self) -> PdoTypeEnum: + """ + Returns flag of PDO type. + + Returns: + object of `PdoTypeEnum` type + """ + return self.__type + + def __str__(self): + return f"Max Current: {self.__max_current}\n" \ + f"Min Voltage: {self.__min_voltage}\n" \ + f"Max Voltage: {self.__max_voltage}" + + +class BatteryPdo: + + """ + Class `BatteryPdo` contains information about Fixed PDO on Sink and Source side. + - Set and get maximum power `max_power`, type `int`. + - Set and get minimum voltage `min_voltage`, type `int`. + - Set and get maximum voltage `max_voltage`, type `bool`. + - Get PDO type `pdo_type`, type `PdoTypeEnum`. + """ + + def __init__(self, pdo: BatteryPdoStruct = BatteryPdoStruct(0)): + self.__max_power = pdo.max_power * 250 + self.__min_voltage = pdo.min_voltage * 50 + self.__max_voltage = pdo.max_voltage * 50 + self.__type = PdoTypeEnum.Battery + + @property + def max_power(self) -> int: + """ + Returns maximum power. + + Returns: + object of `int` type + """ + return self.__max_power + + @max_power.setter + def max_power(self, max_power: int): + self.__max_power = max_power + + @property + def min_voltage(self) -> int: + """ + Returns minimum voltage. + + Returns: + object of `int` type + """ + return self.__min_voltage + + @min_voltage.setter + def min_voltage(self, min_voltage: int): + self.__min_voltage = min_voltage + + @property + def max_voltage(self) -> int: + """ + Returns maximum voltage. + + Returns: + object of `int` type + """ + return self.__max_voltage + + @max_voltage.setter + def max_voltage(self, max_voltage: int): + self.__max_voltage = max_voltage + + @property + def pdo_type(self) -> PdoTypeEnum: + """ + Returns flag of PDO type. + + Returns: + object of `PdoTypeEnum` type + """ + return self.__type + + def __str__(self): + return f"Max power: {self.__max_power}\n" \ + f"Min Voltage: {self.__min_voltage}\n" \ + f"Max Voltage: {self.__max_voltage}" + + +class FixedPdoSink: + + """ + Class `FixedPdoSink` contains information about Fixed PDO on Sink side. + - Set and get maximum current `max_current`, type `int`. + - Set and get voltage `voltage`, type `int`. + - Set and get dual data role flag `dual_data_role`, type `bool`. + - Set and get usb communication flag `usb_communication`, type `bool`. + - Set and get unconstrained power flag `unconstrained_power`, type `bool`. + - Set and get higher capability flag `higher_capability`, type `bool`. + - Set and get dual power role flag `dual_power_role`, type `bool`. + - Get PDO type `pdo_type`, type `PdoTypeEnum`. + """ + + def __init__(self, pdo: FixedPdoSinkStruct = FixedPdoSinkStruct(0), disable=False): + self.__oper_current = pdo.oper_current * 10 + self.__voltage = pdo.voltage * 50 + self.__dual_data_role = bool(pdo.dual_data_role) + self.__usb_communication = bool(pdo.usb_communication_capable) + self.__unconstrained_power = bool(pdo.externally_powered) + self.__higher_capability = bool(pdo.usb_suspend_supported) + self.__dual_power_role = bool(pdo.dual_power_role_capable) + self.__type = PdoTypeEnum.Disabled if disable else PdoTypeEnum.Fixed + + @property + def oper_current(self) -> int: + """ + Returns operation current. + + Returns: + object of `int` type + """ + return self.__oper_current + + @oper_current.setter + def oper_current(self, oper_current: int): + self.__oper_current = oper_current + + @property + def voltage(self) -> int: + """ + Returns voltage. + + Returns: + object of `int` type + """ + return self.__voltage + + @voltage.setter + def voltage(self, voltage: int): + self.__voltage = voltage + + @property + def dual_data_role(self) -> bool: + """ + Returns flag of dual data role. + + Returns: + object of `bool` type + """ + return bool(self.__dual_data_role) + + @dual_data_role.setter + def dual_data_role(self, dual_data_role: bool): + self.__dual_data_role = dual_data_role + + @property + def usb_communication(self) -> bool: + """ + Returns flag of usb communication. + + Returns: + object of `bool` type + """ + return bool(self.__usb_communication) + + @usb_communication.setter + def usb_communication(self, usb_communication: bool): + self.__usb_communication = usb_communication + + @property + def unconstrained_power(self) -> bool: + """ + Returns flag of unconstrained power. + + Returns: + object of `bool` type + """ + return bool(self.__unconstrained_power) + + @unconstrained_power.setter + def unconstrained_power(self, unconstrained_power: bool): + self.__unconstrained_power = unconstrained_power + + @property + def higher_capability(self) -> bool: + """ + Returns flag of higher capability. + + Returns: + object of `bool` type + """ + return bool(self.__higher_capability) + + @higher_capability.setter + def higher_capability(self, higher_capability: bool): + self.__higher_capability = higher_capability + + @property + def dual_power_role(self) -> bool: + """ + Returns flag of dual power role. + + Returns: + object of `bool` type + """ + return bool(self.__dual_power_role) + + @dual_power_role.setter + def dual_power_role(self, dual_power_role: bool): + self.__dual_power_role = dual_power_role + + @property + def pdo_type(self) -> PdoTypeEnum: + """ + Returns flag of PDO type. + + Returns: + object of `PdoTypeEnum` type + """ + return self.__type + + def __str__(self): + return f"Oper Current: {self.__oper_current}\n" \ + f"Voltage: {self.__voltage}\n" \ + f"Dual Role: {'Yes' if self.__dual_data_role else 'No'}\n" \ + f"USB Communication: {'Yes' if self.__usb_communication else 'No'}\n" \ + f"Unconstrained Power: {'Yes' if self.__unconstrained_power else 'No'}\n" \ + f"Higher capability: {'Yes' if self.__higher_capability else 'No'}\n" \ + f"Dual Power role: {'Yes' if self.__dual_power_role else 'No'}\n" + + +PdoType = TypeVar("PdoType", + FixedPdoSink, + FixedPdoSource, + BatteryPdo, + VariablePdo) diff --git a/UniTAP/dev/ports/modules/pdc/pdo_utils.py b/UniTAP/dev/ports/modules/pdc/pdo_utils.py new file mode 100644 index 0000000..9cc5fe9 --- /dev/null +++ b/UniTAP/dev/ports/modules/pdc/pdo_utils.py @@ -0,0 +1,31 @@ +from UniTAP.dev.ports.modules.pdc.pdo_private_types import * +from UniTAP.dev.ports.modules.pdc.pdo_types import * + + +def get_pdo_value(pdo: PdoType) -> int: + if isinstance(pdo, FixedPdoSink): + value = int(pdo.oper_current / 10) | (int(pdo.voltage / 50) << 10) | (pdo.dual_data_role << 25) | (pdo.usb_communication << 26) | \ + (pdo.unconstrained_power << 27) | (pdo.higher_capability << 28) | (pdo.dual_power_role << 29) + return value + elif isinstance(pdo, FixedPdoSource): + value = int(pdo.max_current / 10) | (int(pdo.voltage / 50) << 10) | (pdo.peak_current.value << 20) | (pdo.dual_data_role << 25) | \ + (pdo.usb_communication << 26) | (pdo.unconstrained_power << 27) | (pdo.higher_capability << 28) | \ + pdo.dual_power_role << 29 + return value + elif isinstance(pdo, BatteryPdo): + value = int(pdo.max_power / 250) | (int(pdo.min_voltage / 50) << 10) | (int(pdo.max_voltage / 50) << 20) | (1 << 30) + return value + elif isinstance(pdo, VariablePdo): + value = int(pdo.max_current / 10) | (int(pdo.min_voltage / 50) << 10) | (int(pdo.max_voltage / 50) << 20) | (2 << 30) + return value + + +def interpret_pdo_value(old_type: PdoType, new_type: PdoType) -> PdoTypeStruct: + if new_type == FixedPdoSink: + return PdoUnion(get_pdo_value(old_type)).fixed_pdo_sink + elif new_type == FixedPdoSource: + return PdoUnion(get_pdo_value(old_type)).fixed_pdo_source + elif new_type == BatteryPdo: + return PdoUnion(get_pdo_value(old_type)).battery_pdo + elif new_type == VariablePdo: + return PdoUnion(get_pdo_value(old_type)).variable_pdo diff --git a/UniTAP/dev/ports/modules/vtg/__init__.py b/UniTAP/dev/ports/modules/vtg/__init__.py new file mode 100644 index 0000000..46d3751 --- /dev/null +++ b/UniTAP/dev/ports/modules/vtg/__init__.py @@ -0,0 +1,3 @@ +from .pg import PGPatternParams, SolidColorParams, WhiteVStripsParams, GradientStripsParams, MotionParams, \ + SquareWindowParams, StepsScrollingParams, ASParams, ConstantASParams, SquareASParams, ZigzagASParams, FixedASParams, \ + VideoPattern, PGStatus diff --git a/UniTAP/dev/ports/modules/vtg/pg.py b/UniTAP/dev/ports/modules/vtg/pg.py new file mode 100644 index 0000000..7615f03 --- /dev/null +++ b/UniTAP/dev/ports/modules/vtg/pg.py @@ -0,0 +1,898 @@ +import copy +import os.path +from typing import Union, List, Optional + +from PIL import Image +from UniTAP.utils import function_scheduler +from UniTAP.libs.lib_tsi.tsi import * +from UniTAP.libs.lib_tsi.tsi_io import PortIO +from UniTAP.dev.modules import MemoryManager +from UniTAP.dev.ports.modules.internal_utils.math import aligned +from UniTAP.common import VideoMode, Timing, ColorInfo, VideoFrame, VideoFrameDSC +from UniTAP.dev.ports.modules.internal_utils import uicl_image_from_vm_and_data, uicl_image_convert_to_vm +from .types import VideoPattern, PGVideoMode, PGAdaptiveSyncPatternType, PGColorimetry, \ + PGColorInfo, PGColorDepth, PGStandard, PGDynamicRange, PGAspectRatio, ConstantASParams, SquareASParams, \ + ZigzagASParams, FixedASParams, ASParams +from .private_types import get_as_params_value, PgCaps, CustomTimingFlags, PGPatternID, get_pattern_params_value, \ + PGPatternType +from .pg_utils import pg_timingflags_from_vm, pg_pixel_format_from_vm, pg_colorformat_to_ci_colorformat, \ + pg_dynamicrange_to_ci_dynamicrange, pg_colorimetry_to_ci_colorimetry, pg_bpc_to_ci_bpc +from .timing_manager import TimingManager +from .pg_pattern_params import * +from ..panel_replay import * + +# Override MAX_IMAGE_PIXELS +Image.MAX_IMAGE_PIXELS = None + + +def _read_predefined_timings(io: PortIO) -> List[Timing]: + count = io.get(TSI_R_PG_PREDEF_TIMING_COUNT)[1] + + predefined_list = [] + + for i in range(count): + io.set(TSI_W_PG_PREDEF_TIMING_SELECT, i) + + timing = Timing() + timing.htotal = io.get(TSI_PG_CUSTOM_TIMING_HTOTAL)[1] + timing.vtotal = io.get(TSI_PG_CUSTOM_TIMING_VTOTAL)[1] + timing.hactive = io.get(TSI_PG_CUSTOM_TIMING_HACTIVE)[1] + timing.vactive = io.get(TSI_PG_CUSTOM_TIMING_VACTIVE)[1] + timing.hstart = io.get(TSI_PG_CUSTOM_TIMING_HSTART)[1] + timing.vstart = io.get(TSI_PG_CUSTOM_TIMING_VSTART)[1] + timing.hswidth = io.get(TSI_PG_CUSTOM_TIMING_HSYNCW)[1] + timing.vswidth = io.get(TSI_PG_CUSTOM_TIMING_VSYNCW)[1] + timing.id = io.get(TSI_PG_CUSTOM_TIMING_ID)[1] + # TODO: add conversion from pg AR to Timing AR + timing.aspect_ratio = io.get(TSI_PG_CUSTOM_TIMING_ASPECT_RATIO)[1] + timing.frame_rate = io.get(TSI_PG_CUSTOM_TIMING_FIELD_RATE)[1] + timing.standard = Timing.Standard(io.get(TSI_PG_CUSTOM_TIMING_STANDARD)[1]) + + timing_flags = io.get(TSI_PG_CUSTOM_TIMING_FLAGS)[1] + timing.hswidth *= (-1 if timing_flags >> 9 & 1 else 1) + timing.vswidth *= (-1 if timing_flags >> 10 & 1 else 1) + timing.reduce_blanking = Timing.ReduceBlanking((timing_flags >> 24) & 0x3) + + predefined_list.append(timing) + + return predefined_list + + +class PGStatus: + """ + + Class `PGStatus` describes possible states of `PatternGenerator`. + + """ + + class PGError(IntEnum): + """ + + Class `PGError` contains codes of errors with the possibility of string representation. + + """ + NotReady = -1 + OK = 0 + HWFault = 1 + PixelClock = 2 + MemoryError = 3 + DscFileZero = 4 + DscPixelClockExceeds = 5 + DscSourceNotSupport = 6 + DscSinkNotSupport = 7 + DscFailReadDpcd = 8 + DscFailWriteDpcd = 9 + WrongColorFormat = 10 + WrongColorimetry = 11 + WrongBitsPerComponent = 12 + WrongDynamicRange = 13 + NotEnoughMemoryForPattern = 14 + + def __str__(self): + if self.value == self.OK: + return "OK" + if self.value == self.HWFault: + return "HW Fault." + if self.value == self.PixelClock: + return "Pixel clock exceed HW Caps." + if self.value == self.MemoryError: + return "Not enough memory for custom pattern." + if self.value == self.DscFileZero: + return "DSC isn’t enabled. DSC file has zero size." + if self.value == self.DscPixelClockExceeds: + return "DSC isn’t enabled. Video pixel clock exceeds hardware capabilities." + if self.value == self.DscSourceNotSupport: + return "DSC isn’t enabled. Source does not support DSC." + if self.value == self.DscSinkNotSupport: + return "DSC isn’t enabled. Sink does not support DSC." + if self.value == self.DscFailReadDpcd: + return "DSC isn’t enabled. Fail to read DSC DPCD 0x160." + if self.value == self.DscFailWriteDpcd: + return "DSC isn’t enabled. Fail to write DSC DPCD 0x160." + if self.value == self.WrongColorFormat: + return "Selected Color Format does not match the pattern." + if self.value == self.WrongColorimetry: + return "Selected Colorimetry does not match the pattern." + if self.value == self.WrongBitsPerComponent: + return "Selected BPC does not match the port capabilities." + if self.value == self.WrongDynamicRange: + return "Selected Dynamic Range does not match the pattern." + else: + return f"Unknown error: {self.value}" + + def __init__(self, value: int): + self._error = self.PGError(value & 0xFF) + self._is_video_produced = (value >> 30) & 1 != 0 + self._non_applied_changes = (value >> 31) & 1 != 0 + + @property + def error(self) -> PGError: + """ + + Returns pg error. + + Returns: + object of `PGError` + """ + return self._error + + @property + def is_video_produced(self) -> bool: + """ + + Returns state of video produced. + + Returns: + object of bool - is video produced or not + """ + return self._is_video_produced + + @property + def non_applied_changes(self) -> bool: + """ + + Returns state of applied changes. + + Returns: + object of bool - were there any changes or not + """ + return self._non_applied_changes + + +class PatternGenerator: + """ + + Main class `PatternGenerator` allows working with PG functionality on the device: set different types of pattern + `set_pattern`, set different video modes `set_vm`, set additional parameters for some patterns `set_pattern_params`, + get information about current video mode on stream `get_stream_video_mode`, `apply` all transferred setting, + `reset` settings and read pattern generator `status`. + + """ + + __available_patterns = '\n'.join([e.name for e in VideoPattern]) + __hardware_generated_patterns = ''.join([(e.name + '\n') if i <= 7 else '' for i, e in enumerate(VideoPattern)]) + + _MAP_PATTERN_PARAMETER = {PGPatternID.SolidColor: SolidColorParams, + PGPatternID.WhiteVStrips: WhiteVStripsParams, + PGPatternID.GradientRGBStripes: GradientStripsParams, + PGPatternID.Motion_Pattern: MotionParams, + PGPatternID.SquareWindow: SquareWindowParams} + + def __init__(self, port_io: PortIO, memory_manager: MemoryManager, stream: int): + self.__io = port_io + self.__memory_manager = memory_manager + self.__stream = stream + self.__caps = self.__read_caps() + self._pattern = None + self._pattern_id = PGPatternID.Disabled + self._pattern_params = None + self._vm = None + self.__panel_replay = PanelReplay(port_io, self.__caps) + + def __read_caps(self): + self._become_active() + return self.__io.get(TSI_PG_CAPS_R, PgCaps)[1] + + def _caps(self) -> PgCaps: + return self.__caps + + def __data_from_image_file(self, target_video_mode: VideoMode, path: str) -> bytearray: + if target_video_mode is None: + target_video_mode = self.get_stream_video_mode() + current_video_mode = copy.deepcopy(target_video_mode) + target_size = (target_video_mode.timing.hactive, target_video_mode.timing.vactive) + + image = Image.open(path) + image = image.resize(target_size) + image = image.convert('RGB') + data = bytearray([x for sets in list(image.getdata()) for x in sets]) + + current_video_mode.color_info.color_format = ColorInfo.ColorFormat.CF_RGB + current_video_mode.color_info.bpc = 8 + current_video_mode.color_info.colorimetry = ColorInfo.Colorimetry.CM_sRGB + + if current_video_mode != target_video_mode: + image = uicl_image_from_vm_and_data(current_video_mode, data) + data = uicl_image_convert_to_vm(image, target_video_mode) + + return data + + @staticmethod + def __data_from_dsc_file(target_video_mode: VideoMode, path: str) -> bytearray: + with open(path, "rb") as dsc_file: + data = bytearray(dsc_file.read()) + assert data[:4] == b"DSCF", 'DSC File missing \'DSCF\' header.' + + return data + + def __load_custom_image(self, video_mode: VideoMode, content_data: Union[str, bytearray, VideoFrame]): + if video_mode is None: + video_mode = self.get_stream_video_mode() + + if isinstance(content_data, str): + data = self.__data_from_image_file(video_mode, str(content_data)) + elif isinstance(content_data, VideoFrame): + data = content_data.data + else: + image = uicl_image_from_vm_and_data(video_mode, content_data) + data = uicl_image_convert_to_vm(image, video_mode) + + block_sizes = PatternGenerator.__calculate_frame_size(video_mode.timing.hactive, + video_mode.timing.vactive) + self.__memory_manager.set_memory_layout([block_sizes]) + self.__memory_manager.set_memory_block_index(MemoryManager.MemoryOwner.MO_PatternGenerator) + + self.__io.set(TSI_PG_CUSTOM_PATTERN_MEMORY_BLOCK_INDEX, 0) + self.__io.set(TSI_PG_CUSTOM_PATTERN_WIDTH, video_mode.timing.hactive) + self.__io.set(TSI_PG_CUSTOM_PATTERN_HEIGHT, video_mode.timing.vactive) + self.__io.set(TSI_PG_CUSTOM_PATTERN_PIXEL_FORMAT, pg_pixel_format_from_vm(video_mode)) + self.__io.set(TSI_PG_CUSTOM_PATTERN_DATA, data, c_uint8, data_count=len(data)) + + def __load_dsc_image(self, video_mode: VideoMode, content_data: Union[str, bytearray, VideoFrameDSC]): + if isinstance(content_data, str): + data = self.__data_from_dsc_file(video_mode, str(content_data)) + elif isinstance(content_data, VideoFrameDSC): + data = content_data.data + else: + data = content_data + + block_sizes = len(data) + self.__memory_manager.set_memory_layout([block_sizes]) + self.__memory_manager.set_memory_block_index(MemoryManager.MemoryOwner.MO_PatternGenerator) + self.__io.set(TSI_PG_CUSTOM_PATTERN_MEMORY_BLOCK_INDEX, 0) + self.__io.set(TSI_PG_CUSTOM_PATTERN_DATA, data, c_uint8, data_count=len(data)) + + @staticmethod + def __calculate_frame_size(width: int, height: int) -> int: + line_stride_bytes = width * 4 + line_stride_aligned_bytes = aligned(line_stride_bytes, 1024) + + line_stride_bytes2 = width * 2 + line_stride_aligned_bytes2 = aligned(line_stride_bytes2, 1024) + + return int((line_stride_aligned_bytes + line_stride_aligned_bytes2) * height) + + def set_pattern(self, pattern: Union[VideoPattern, str, bytearray, VideoFrame, VideoFrameDSC]): + """ + + Allows setting video pattern on current stream. + Possible variants: + - type `VideoPattern` - value from enum `VideoPattern` (one of th e possible predefined patterns). + - type str - path to image (bmp, png, jpeg, dsc and so on). + - type bytearray - raw image data, which will be loaded to device memory. + - type `VideoFrame` - object of class that contains the image data. + - type `VideoFrameDSC` - object of class that contains dsc image data. + + Args: + pattern (Union[`VideoPattern`, str, bytearray, `VideoFrame`, `VideoFrameDSC`]) + + """ + self._pattern = pattern + + def _setup_pattern(self): + if self._pattern is not None: + if isinstance(self._pattern, VideoPattern): + self._pattern_id = PGPatternID(self._pattern.value) + if not self.__caps.flags.custom_vp and self._pattern_id in [PGPatternID.WhiteVStrips, + PGPatternID.GradientRGBStripes, + PGPatternID.ColorRamp, + PGPatternID.ColorSquares, + PGPatternID.Motion_Pattern]: + raise ValueError(f'Current PG (stream {self.__stream}) does not support pattern ' + f'{self._pattern.name}. Please select another supported pattern from ' + f'"VideoPattern":\n{self.__hardware_generated_patterns}') + elif isinstance(self._pattern, str) and os.path.exists(self._pattern): + filename, file_extension = os.path.splitext(self._pattern) + if file_extension.lower() == '.dsc' and self.__support_dsc(): + self.__load_dsc_image(self._vm, self._pattern) + self._pattern_id = PGPatternID.DscImage + else: + self.__load_custom_image(self._vm, self._pattern) + self._pattern_id = PGPatternID.ImageFile + elif isinstance(self._pattern, VideoFrame): + if self._pattern.color_info.color_format == ColorInfo.ColorFormat.CF_DSC and self.__support_dsc(): + self.__load_dsc_image(self._vm, self._pattern) + self._pattern_id = PGPatternID.DscImage + else: + self.__load_custom_image(self._vm, self._pattern) + self._pattern_id = PGPatternID.ImageFile + elif isinstance(self._pattern, VideoFrameDSC): + if self._pattern.data[:4] == b'DSCF' and self.__support_dsc(): + self.__load_dsc_image(self._vm, self._pattern) + self._pattern_id = PGPatternID.DscImage + elif isinstance(self._pattern, bytearray): + if self._pattern[:4] == b'DSCF' and self.__support_dsc(): + self.__load_dsc_image(self._vm, self._pattern) + self._pattern_id = PGPatternID.DscImage + else: + self.__load_custom_image(self._vm, self._pattern) + self._pattern_id = PGPatternID.ImageFile + else: + raise TypeError(f"Incorrect input value: {self._pattern}.\n" + f"If you want to select pattern, see following list of available patterns:\n" + f"{self.__available_patterns}") + self.__io.set(TSI_PG_PREDEF_PATTERN_SELECT, self._pattern_id.value) + return True + return False + + def __support_dsc(self) -> bool: + if not self.__caps.flags.dsc_supported: + warnings.warn("Display Stream Compression is not supported.") + return False + return True + + def set_vm(self, vm: VideoMode): + """ + + Allows setting `VideoMode` on current stream. + + Args: + vm (VideoMode) + """ + self._vm = vm + + def _setup_vm(self): + if self._vm is None: + self.__read_pg_settings() + return False + if not self._vm.is_valid(): + raise ValueError(f"Incorrect Video Mode") + if self.__caps.max_h_active >= self._vm.timing.hactive and \ + self.__caps.max_v_active >= self._vm.timing.vactive and \ + self.__caps.max_h_total >= self._vm.timing.htotal and \ + self.__caps.max_v_total >= self._vm.timing.vtotal and \ + self.__caps.max_frame >= int(self._vm.timing.frame_rate / 1000): + self.__io.set(TSI_PG_CUSTOM_TIMING_FLAGS, pg_timingflags_from_vm(self._vm)) + self.__io.set(TSI_PG_CUSTOM_TIMING_HTOTAL, self._vm.timing.htotal, c_uint) + self.__io.set(TSI_PG_CUSTOM_TIMING_VTOTAL, self._vm.timing.vtotal, c_uint) + self.__io.set(TSI_PG_CUSTOM_TIMING_HACTIVE, self._vm.timing.hactive, c_uint) + self.__io.set(TSI_PG_CUSTOM_TIMING_VACTIVE, self._vm.timing.vactive, c_uint) + self.__io.set(TSI_PG_CUSTOM_TIMING_HSTART, self._vm.timing.hstart, c_uint) + self.__io.set(TSI_PG_CUSTOM_TIMING_VSTART, self._vm.timing.vstart, c_uint) + self.__io.set(TSI_PG_CUSTOM_TIMING_HSYNCW, abs(self._vm.timing.hswidth), c_uint) + self.__io.set(TSI_PG_CUSTOM_TIMING_VSYNCW, abs(self._vm.timing.vswidth), c_uint) + self.__io.set(TSI_PG_CUSTOM_TIMING_FIELD_RATE, self._vm.timing.frame_rate, c_uint) + self.__io.set(TSI_PG_CUSTOM_TIMING_STANDARD, self._vm.timing.standard.value, c_uint) + return True + else: + raise ValueError(f"Incorrect Video Mode (incorrect resolution and(or) frame rate") + + def set_pattern_params(self, pattern_params: PGPatternParams): + """ + + Allows setting additional parameters for some patters on current stream. + See available `PGPatternParams` types: `SolidColorParams`, `WhiteVStripsParams`, `GradientStripsParams`, + `MotionParams`,`SquareWindowParams` (see in pg pattern params). + + Args: + pattern_params (PGPatternParams) + """ + self._pattern_params = pattern_params + + def _setup_pattern_params(self): + if self._pattern_params is not None and isinstance(self._pattern, VideoPattern): + if self._MAP_PATTERN_PARAMETER.get(PGPatternID(self._pattern_id.value)) is not None: + if isinstance(self._pattern_params, + self._MAP_PATTERN_PARAMETER.get(PGPatternID(self._pattern_id.value))): + self.__io.set(TSI_PG_PREDEF_PATTERN_PARAMS, get_pattern_params_value(self._pattern_params, + self._vm.color_info.bpc), + data_count=len(get_pattern_params_value(self._pattern_params))) + return True + else: + warnings.warn(f"Incorrect pattern name {self._pattern} and " + f"pattern params {type(self._pattern_params)}.\n" + f"For {self._pattern} you need to use " + f"{self._MAP_PATTERN_PARAMETER.get(self._pattern_id)}") + return False + + def __get_custom_timing_flags_to_video_mode(self) -> (ColorInfo, bool, bool): + timing_flags = self.__io.get(TSI_PG_CUSTOM_TIMING_FLAGS, CustomTimingFlags)[1] + + vm_color_format = pg_colorformat_to_ci_colorformat(PGColorInfo(timing_flags.color_space)) + + vm_colorimetry = pg_colorimetry_to_ci_colorimetry(PGColorInfo(timing_flags.color_space), + PGColorimetry(timing_flags.colorimetry)) + + vm_dynamic_range = pg_dynamicrange_to_ci_dynamicrange(PGDynamicRange(timing_flags.dynamic_range)) + + vm_h_sync_polarity = bool(timing_flags.h_sync_polarity) + vm_v_sync_polarity = bool(timing_flags.v_sync_polarity) + + vm_bpc = pg_bpc_to_ci_bpc(timing_flags.color_depth) + + color_info = ColorInfo() + color_info.color_format = vm_color_format + color_info.colorimetry = vm_colorimetry + color_info.dynamic_range = vm_dynamic_range + color_info.bpc = vm_bpc + + return color_info, vm_h_sync_polarity, vm_v_sync_polarity + + def get_stream_video_mode(self) -> VideoMode: + """ + + Returns `VideoMode` information about current stream. + + Returns: + object of VideoMode type + """ + self.__read_pg_settings() + + video_mode = VideoMode() + + video_mode.timing.hactive = self.__io.get(TSI_PG_CUSTOM_TIMING_HACTIVE, c_uint32)[1] + video_mode.timing.vactive = self.__io.get(TSI_PG_CUSTOM_TIMING_VACTIVE, c_uint32)[1] + video_mode.timing.htotal = self.__io.get(TSI_PG_CUSTOM_TIMING_HTOTAL, c_uint32)[1] + video_mode.timing.vtotal = self.__io.get(TSI_PG_CUSTOM_TIMING_VTOTAL, c_uint32)[1] + video_mode.timing.hstart = self.__io.get(TSI_PG_CUSTOM_TIMING_HSTART, c_uint32)[1] + video_mode.timing.vstart = self.__io.get(TSI_PG_CUSTOM_TIMING_VSTART, c_uint32)[1] + + video_mode.color_info, h_sync_polarity, v_sync_polarity = self.__get_custom_timing_flags_to_video_mode() + + video_mode.timing.hswidth = self.__io.get(TSI_PG_CUSTOM_TIMING_HSYNCW, c_uint32)[1] * ( + -1 if h_sync_polarity else 1) + video_mode.timing.vswidth = self.__io.get(TSI_PG_CUSTOM_TIMING_VSYNCW, c_uint32)[1] * ( + -1 if v_sync_polarity else 1) + video_mode.timing.frame_rate = self.__io.get(TSI_PG_CUSTOM_TIMING_FIELD_RATE, c_uint32)[1] + + return video_mode + + def _become_active(self): + self.__io.set(TSI_PG_STREAM_SELECT, self.__stream) + + def __read_pg_settings(self): + self.__io.set(TSI_PG_COMMAND_W, 2) + + def apply(self) -> bool: + """ + + Apply all settings on current stream. + + Returns: + object of bool type - settings were set successfully or not + """ + self.__io.set(TSI_PG_COMMAND_W, 3) + + def is_apply_pg_success(pg: PatternGenerator): + return pg.status().error == PGStatus.PGError.OK + + return function_scheduler(is_apply_pg_success, self, interval=1, timeout=10) + + def status(self) -> PGStatus: + """ + + Returns `PGStatus` of current stream. + + Returns: + object of PGStatus type. + """ + self._become_active() + return PGStatus(self.__io.get(TSI_PG_STATUS_R, c_uint32)[1]) + + def reset(self): + """ + + Reset all setting on current stream. + + """ + self._vm = VideoMode() + self._pattern = None + self._pattern_id = PGPatternID.Disabled + self._pattern_params = None + + def get_pixel_rate(self) -> int: + """ + + Returns current pixel rate. + + Returns: + object of int type + """ + vm = self.get_stream_video_mode() + return vm.timing.htotal * vm.timing.vtotal * int(vm.timing.frame_rate / 1000) + + def panel_replay(self) -> Optional[PanelReplay]: + """ + + Returns object of PanelReplay if device supports this feature. + + Returns: + object of 'PanelReplay' type or None + """ + if self.__caps.flags.panel_replay != 0 or self.__caps.flags.psr != 0: + return self.__panel_replay + return None + + +class HdmiPatternGenerator(PatternGenerator): + """ + Class `HdmiPatternGenerator` inherited from class `PatternGenerator`. + Allows getting `timing_manager`, `max_stream_count`, `apply` PG settings. + Also has all the `PatternGenerator` functionality. + """ + + def __init__(self, port_io: PortIO, memory_manager: MemoryManager): + super().__init__(port_io, memory_manager, 0) + self.__timing_manager = TimingManager(_read_predefined_timings(port_io)) + + @property + def timing_manager(self) -> TimingManager: + """ + + Should be used for working with available timings on device. + + Returns: + object of `TimingManager` type. + """ + return self.__timing_manager + + @property + def max_stream_count(self) -> int: + """ + + Returns maximum count of available streams. + + Returns: + object of int type. + """ + return 1 + + def apply(self) -> bool: + """ + + Apply all settings. + + Returns: + object of bool type - settings were set successfully or not + """ + self._become_active() + res = self._setup_vm() + res += self._setup_pattern() + res += self._setup_pattern_params() + if res != 0: + return super().apply() + else: + return False + + +class DpPatternGenerator(PatternGenerator): + """ + Class `DpPatternGenerator` inherited from class `PatternGenerator`. + Allows getting `timing_manager`, `adaptive_sync_status`, `apply` and `reset` PG settings and set additional settings: + `set_as_config`, `set_scrolling_params`. + Also has all the `PatternGenerator` functionality. + """ + + def __init__(self, port_io: PortIO, memory_manager: MemoryManager, stream: int): + super().__init__(port_io, memory_manager, stream) + self.__scrolling_params = None + self.__as_config = None + self.__io = port_io + self.__timing_manager = TimingManager(_read_predefined_timings(port_io)) + + @property + def timing_manager(self) -> TimingManager: + """ + + Should be used for working with available timings on device. + + Returns: + object of `TimingManager` type. + """ + return self.__timing_manager + + def set_as_config(self, as_config: ASParams): + """ + + Allows setting adaptive sync configuration. + See available `ASParams` types: `ConstantASParams`, `SquareASParams`, `ZigzagASParams`, `FixedASParams` + (see in types). + + Args: + as_config (ASParams) + """ + self.__as_config = as_config + + def set_scrolling_params(self, scrolling_params: PGScrollingParams): + """ + + Allows setting additional configuration for "Scrolling pattern". See available `PGScrollingParams` types: + `StepsScrollingParams` (see in pg pattern params). + + Args: + scrolling_params (PGScrollingParams) + """ + self.__scrolling_params = scrolling_params + + def _setup_as_config(self): + if self.__as_config is not None: + if not self._caps().flags.adaptive_sync: + warnings.warn("AdaptiveSync is not supported") + self.__as_config = None + return False + + self.__io.set(TSI_PG_ADAPTIVE_SYNC_CTRL, + get_as_params_value(self.__as_config), + data_count=len(get_as_params_value(self.__as_config))) + return True + return False + + def _setup_scrolling_params(self): + if self.__scrolling_params is not None: + if not self._caps().flags.scrolling_pattern: + warnings.warn("Scrolling params is not supported") + self.__scrolling_params = None + return False + if self._vm.color_info.color_format == ColorInfo.ColorFormat.CF_YCbCr_422 and \ + self.__scrolling_params.horizontally % 2: + warnings.warn(f"Wrong horizontally param. Number must be even. Will be used param - 1: " + f"{self.__scrolling_params.horizontally - 1}") + self.__scrolling_params.horizontally -= 1 + elif self._vm.color_info.color_format == ColorInfo.ColorFormat.CF_YCbCr_420 and \ + self.__scrolling_params.vertically % 2: + warnings.warn(f"Wrong vertically param. Number must be even. Will be used param - 1: " + f"{self.__scrolling_params.vertically - 1}") + self.__scrolling_params.vertically -= 1 + + if not (self._caps().patterns & (1 << self._pattern_id.value)): + warnings.warn(f"Patterns scrolling feature is not supported on pattern {self._pattern_id.name}") + return False + value = 1 + value |= (1 << 31) + self.__io.set(TSI_PG_CUSTOM_PATTERN_SCROLLING_CONTROL, value, c_uint32) + self.__io.set(TSI_PG_CUSTOM_PATTERN_SCROLLING_CONFIG, get_pattern_params_value(self.__scrolling_params), + data_count=len(get_pattern_params_value(self.__scrolling_params))) + return True + return False + + def adaptive_sync_status(self) -> bool: + """ + + Returns work status of adaptive sync. + + Returns: + object of bool type - adaptive sync enabled or not + """ + self._become_active() + + res = self.__io.get(TSI_PG_ADAPTIVE_SYNC_STS, c_uint32) + + if res[0] < TSI_SUCCESS: + return False + + return res[1] & 1 != 0 + + def apply(self) -> bool: + """ + + Apply all settings. + + Returns: + object of bool type - settings were set successfully or not + """ + self._become_active() + res = self._setup_vm() + res += self._setup_pattern() + res += self._setup_pattern_params() + res += self._setup_scrolling_params() + if res != 0: + res = super().apply() + else: + res = False + self._setup_as_config() + return res + + def reset(self): + """ + + Reset all setting. + + """ + self.__scrolling_params = None + self.__as_config = None + super().reset() + + +class DpMstPatternGenerator: + """ + Class `DpMstPatternGenerator` allows working with one of the supported streams on the device (contains list of the + `DpPatternGenerator` objects). To access the selected stream, use an override of `[ ]`. + Also, allows working with stream number 0 directly and applying all settings of all streams together. + """ + + def __init__(self, port_io: PortIO, memory_manager: MemoryManager, max_stream_count: int): + self.__pg_list = [] + self.__io = port_io + self.__MAX_STREAM_COUNT = max_stream_count + for i in range(self.max_stream_count): + self.__pg_list.append(DpPatternGenerator(port_io, memory_manager, i)) + self.__timing_manager = self.__pg_list[0].timing_manager + + def __getitem__(self, index: int): + """ + + Allows selecting one of the supported streams. + Example: + `'object of device role'.pg[index]` + + """ + if not (0 <= index < self.max_stream_count): + raise ValueError(f"Index of PG must be in range [0..{self.max_stream_count}].") + + return self.__pg_list[index] + + @property + def timing_manager(self) -> TimingManager: + """ + + Should be used for working with available timings on device. + + Returns: + object of `TimingManager` type. + """ + return self.__timing_manager + + @property + def max_stream_count(self) -> int: + """ + + Returns maximum count of available streams. + + Returns: + object of int type. + """ + return self.__MAX_STREAM_COUNT + + def set_pattern(self, pattern: Union[VideoPattern, str, bytearray, VideoFrame, VideoFrameDSC]): + """ + + Allows setting video pattern on stream number 0 of pattern generator. + Possible variants: + - type `VideoPattern` - value from enum `VideoPattern` (one of th e possible predefined patterns). + - type str - path to image (bmp, png, jpeg, dsc and so on). + - type bytearray - raw image data, which will be loaded to device memory. + - type `VideoFrame` - object of class that contains the image data. + - type `VideoFrameDSC` - object of class that contains the dsc image data. + + Args: + pattern (Union[`VideoPattern`, str, bytearray, `VideoFrame`, `VideoFrameDSC`]) + + """ + self.__pg_list[0].set_pattern(pattern) + + def set_vm(self, vm: VideoMode): + """ + + Allows setting `VideoMode` on stream number 0 of pattern generator. + + Args: + vm (VideoMode) + """ + self.__pg_list[0].set_vm(vm) + + def set_pattern_params(self, pattern_params: PGPatternParams): + """ + + Allows setting additional parameters for some patters on stream number 0 of pattern generator. + See available `PGPatternParams` types: `SolidColorParams`, `WhiteVStripsParams`, `GradientStripsParams`, + `MotionParams`,`SquareWindowParams` (see in pg pattern params). + + Args: + pattern_params (PGPatternParams) + """ + self.__pg_list[0].set_pattern_params(pattern_params) + + def set_as_config(self, as_config: ASParams): + """ + + Allows setting adaptive sync configuration on stream number 0 of pattern generator. + See available `ASParams` types: `ConstantASParams`, `SquareASParams`, `ZigzagASParams`, `FixedASParams`. + (see in types). + + Args: + as_config (ASParams) + """ + self.__pg_list[0].set_as_config(as_config) + + def set_scrolling_params(self, scrolling_params: PGScrollingParams): + """ + + Allows setting additional configuration for "Scrolling pattern" on stream number 0 of pattern generator. + See available `PGScrollingParams` types: `StepsScrollingParams` (see in pg pattern params). + + Args: + scrolling_params (PGScrollingParams) + """ + self.__pg_list[0].set_scrolling_params(scrolling_params) + + def get_stream_video_mode(self) -> VideoMode: + """ + + Returns `VideoMode` information about current configuration of PG on stream 0. + + Returns: + object of VideoMode type + """ + return self.__pg_list[0].get_stream_video_mode() + + def apply(self) -> bool: + """ + + Apply all setting on stream number 0 of pattern generator. + + Returns: + object of bool type - settings were set successfully or not + """ + return self.__pg_list[0].apply() + + def status(self) -> PGStatus: + """ + + Returns `PGStatus` on stream number 0 of pattern generator. + + Returns: + object of PGStatus type. + """ + return self.__pg_list[0].status() + + def reset(self): + """ + + Reset all setting on stream number 0 of pattern generator. + + """ + self.__pg_list[0].reset() + + def apply_all(self): + """ + + Apply all setting on all supported streams of pattern generator. + + """ + for i in range(self.max_stream_count): + self.__pg_list[i].apply() + + def get_pixel_rate(self, stream: int = 0) -> int: + """ + + Returns current pixel rate for selected stream. + + Returns: + object of int type + """ + return self.__pg_list[stream].get_pixel_rate() + + @property + def panel_replay(self) -> PanelReplay: + """ + + Returns object of PanelReplay if device supports this feature (first stream). + + Returns: + object of 'PanelReplay' type or None + """ + return self.__pg_list[0].panel_replay() + + def __read_max_stream_count(self) -> int: + result = self.__io.get(TSI_PG_MAX_STREAM_COUNT_R, c_uint32) + return result[1] diff --git a/UniTAP/dev/ports/modules/vtg/pg_pattern_params.py b/UniTAP/dev/ports/modules/vtg/pg_pattern_params.py new file mode 100644 index 0000000..42abf12 --- /dev/null +++ b/UniTAP/dev/ports/modules/vtg/pg_pattern_params.py @@ -0,0 +1,133 @@ +import warnings +from typing import TypeVar, Union + + +class SolidColorParams: + + """ + Special configuration class for configure Solid color pattern. Contains information about R (Y) - first, + G (Cb) - second, B(Cr) - third components. + """ + + def __init__(self, first: int = 0, second: int = 0, third: int = 0): + if not (0 <= first <= 255): + warnings.warn("Incorrect value. Must use value from range: 0 - 255. Will be used default value = 0") + first = 0 + self.first = first + + if not (0 <= second <= 255): + warnings.warn("Incorrect value. Must use value from range: 0 - 255. Will be used default value = 0") + second = 0 + self.second = second + + if not (0 <= third <= 255): + warnings.warn("Incorrect value. Must use value from range: 0 - 255. Will be used default value = 0") + third = 0 + self.third = third + + +class WhiteVStripsParams: + + """ + Special configuration class for configure White V Strips pattern. Contains information about white stripes width + and black stripes width. + """ + + def __init__(self, white_stripes_width: int = 1, black_stripes_width: int = 1): + if not (1 <= white_stripes_width <= 1000): + warnings.warn("Incorrect value. Must use value from range: 1 - 1000. Will be used default value = 1") + white_stripes_width = 1 + self.white_stripes_width = white_stripes_width + + if not (0 <= black_stripes_width <= 1000): + warnings.warn("Incorrect value. Must use value from range: 1 - 1000. Will be used default value = 1") + black_stripes_width = 1 + self.black_stripes_width = black_stripes_width + + +class GradientStripsParams: + + """ + Special configuration class for configure Gradient Strips pattern. Contains information about color steps conut. + """ + + def __init__(self, color_step: int = 10000): + if not (0 <= color_step <= 10000): + warnings.warn("Incorrect value. Must use value from range: 1 - 10000. Will be used default value = 1") + color_step = 1 + self.color_step = color_step + + +class MotionParams: + + """ + Special configuration class for configure Motion pattern. Contains information about frames conut. + """ + + def __init__(self, frames_count: int = 10000): + if not (0 <= frames_count <= 10000): + warnings.warn("Incorrect value. Must use value from range: 1 - 10000. Will be used default value = 1") + frames_count = 1 + self.frames_count = frames_count + + +class SquareWindowParams: + + """ + Special configuration class for configure Square Window pattern. Contains information about white square size. + """ + + def __init__(self, white_square: int = 30): + if not (0 <= white_square <= 100): + warnings.warn("Incorrect value. Must use value from range: 0 - 100. Will be used default value = 30") + white_square = 30 + self.white_square = white_square + + +class StepsScrollingParams: + + """ + Special configuration class for configure Scrolling (Steps type) pattern. Contains information about horizontally + delta, vertically delta and frames count. + """ + + def __init__(self, horizontally: int = 0, vertically: int = 0, frames: int = 0): + if not (-128 <= horizontally <= 127): + warnings.warn("Incorrect value (horizontally). Must use value from range: (-)128 - 127. " + "For YCbCr 422 range: (-)128 - 126 \n" + "Will be used default value = 0") + horizontally = 0 + if not (-128 <= vertically <= 127): + warnings.warn("Incorrect value (vertically). Must use value from range: (-)128 - 127. " + "For YCbCr 420 range: (-)128 - 126 \n" + "Will be used default value = 0") + vertically = 0 + if not (0 <= frames <= 255): + warnings.warn("Incorrect value (frames). Must use value from range: 0 - 255. " + "Will be used default value = 0") + frames = 0 + self.horizontally = (-1) * horizontally + self.vertically = vertically + self.frames = frames + + +class DistanceScrollingParams: + """ + Support of DistanceScrolling will be added later. + """ + def __init__(self): + pass + + +PGPatternParams = TypeVar("PGPatternParams", + SolidColorParams, + WhiteVStripsParams, + GradientStripsParams, + MotionParams, + SquareWindowParams + ) + + +PGScrollingParams = TypeVar("PGScrollingParams", + StepsScrollingParams, + DistanceScrollingParams) diff --git a/UniTAP/dev/ports/modules/vtg/pg_utils.py b/UniTAP/dev/ports/modules/vtg/pg_utils.py new file mode 100644 index 0000000..53bdec5 --- /dev/null +++ b/UniTAP/dev/ports/modules/vtg/pg_utils.py @@ -0,0 +1,163 @@ +from UniTAP.common import ColorInfo, VideoMode +from UniTAP.libs.lib_tsi.tsi_types import TSI_IMF_PK444_RGB_W8_C8, TSI_IMF_PK444_RGB_W16_C12, \ + TSI_IMF_PK444_RGB_W16_C16, TSI_IMF_PK444_YCbCr_W8_C8, TSI_IMF_PK444_YCbCr_W16_C16, \ + TSI_IMF_PK422_CbYCrY_W16_C8, TSI_IMF_PK422_CbYCrY_W16_C16, TSI_IMF_PK420_CxYY0_W8_C8, \ + TSI_IMF_PK420_CxYY0_W8_C10, TSI_IMF_PK420_CxYY_W16_C12, TSI_IMF_PK420_CxYY_W16_C16 +from .types import PGColorInfo, PGColorDepth, PGColorimetry, PGDynamicRange + + +def pg_colorformat_to_ci_colorformat(color_mode: PGColorInfo) -> ColorInfo.ColorFormat: + if color_mode == PGColorInfo.RGB: + return ColorInfo.ColorFormat.CF_RGB + elif color_mode == PGColorInfo.YCbCr444: + return ColorInfo.ColorFormat.CF_YCbCr_444 + elif color_mode == PGColorInfo.YCbCr422: + return ColorInfo.ColorFormat.CF_YCbCr_422 + elif color_mode == PGColorInfo.YCbCr420: + return ColorInfo.ColorFormat.CF_YCbCr_420 + elif color_mode == PGColorInfo.Y_only: + return ColorInfo.ColorFormat.CF_Y_ONLY + elif color_mode == PGColorInfo.RAW: + return ColorInfo.ColorFormat.CF_RAW + else: + return ColorInfo.ColorFormat.CF_UNKNOWN + + +def pg_colorformat_from_ci_colorformat(color_mode: ColorInfo.ColorFormat) -> PGColorInfo: + if color_mode == ColorInfo.ColorFormat.CF_RGB: + return PGColorInfo.RGB + elif color_mode == ColorInfo.ColorFormat.CF_YCbCr_444: + return PGColorInfo.YCbCr444 + elif color_mode == ColorInfo.ColorFormat.CF_YCbCr_422: + return PGColorInfo.YCbCr422 + elif color_mode == ColorInfo.ColorFormat.CF_YCbCr_420: + return PGColorInfo.YCbCr420 + elif color_mode == ColorInfo.ColorFormat.CF_Y_ONLY: + return PGColorInfo.Y_only + elif color_mode == ColorInfo.ColorFormat.CF_RAW: + return PGColorInfo.RAW + else: + return PGColorInfo.Unknown + + +def pg_colorimetry_to_ci_colorimetry(color_mode: PGColorInfo, colorimetry: PGColorimetry)\ + -> ColorInfo.Colorimetry: + if color_mode == PGColorInfo.RGB: + return ColorInfo.Colorimetry.CM_sRGB + elif color_mode in [PGColorInfo.YCbCr444, PGColorInfo.YCbCr422, PGColorInfo.YCbCr420]: + if colorimetry == PGColorimetry.ITU601: + return ColorInfo.Colorimetry.CM_ITUR_BT601 + elif colorimetry == PGColorimetry.ITU709: + return ColorInfo.Colorimetry.CM_ITUR_BT709 + else: + return ColorInfo.Colorimetry.CM_RESERVED + else: + return ColorInfo.Colorimetry.CM_RESERVED + + +def pg_colorimetry_from_ci_colorimetry(colorimetry: ColorInfo.Colorimetry) -> PGColorimetry: + if colorimetry == ColorInfo.Colorimetry.CM_ITUR_BT601: + return PGColorimetry.ITU601 + elif colorimetry == ColorInfo.Colorimetry.CM_ITUR_BT709: + return PGColorimetry.ITU709 + else: + return PGColorimetry.ITU601 + + +def pg_bpc_to_ci_bpc(bpc: PGColorDepth) -> int: + if bpc == PGColorDepth.BPC6: + return 6 + elif bpc == PGColorDepth.BPC8: + return 8 + elif bpc == PGColorDepth.BPC10: + return 10 + elif bpc == PGColorDepth.BPC12: + return 12 + elif bpc == PGColorDepth.BPC16: + return 16 + elif bpc == PGColorDepth.BPC7: + return 7 + elif bpc == PGColorDepth.BPC14: + return 14 + return 0 + + +def pg_bpc_from_ci_bpc(bpc: int) -> PGColorDepth: + if bpc == 6: + return PGColorDepth.BPC6 + elif bpc == 8: + return PGColorDepth.BPC8 + elif bpc == 10: + return PGColorDepth.BPC10 + elif bpc == 12: + return PGColorDepth.BPC12 + elif bpc == 16: + return PGColorDepth.BPC16 + elif bpc == 7: + return PGColorDepth.BPC7 + elif bpc == 14: + return PGColorDepth.BPC14 + return PGColorDepth.Unknown + + +def pg_pixel_format_from_vm(vm: VideoMode) -> int: + bpc = vm.color_info.bpc + color_format = vm.color_info.color_format + + if color_format == ColorInfo.ColorFormat.CF_RGB: + if bpc == 8: + return TSI_IMF_PK444_RGB_W8_C8 + elif bpc in [10, 12]: + return TSI_IMF_PK444_RGB_W16_C12 + elif bpc == 16: + return TSI_IMF_PK444_RGB_W16_C16 + elif color_format == ColorInfo.ColorFormat.CF_YCbCr_444: + if bpc == 8: + return TSI_IMF_PK444_YCbCr_W8_C8 + elif bpc in [10, 12, 16]: + return TSI_IMF_PK444_YCbCr_W16_C16 + elif color_format == ColorInfo.ColorFormat.CF_YCbCr_422: + if bpc == 8: + return TSI_IMF_PK422_CbYCrY_W16_C8 + elif bpc in [10, 12, 16]: + return TSI_IMF_PK422_CbYCrY_W16_C16 + elif color_format == ColorInfo.ColorFormat.CF_YCbCr_420: + if bpc == 8: + return TSI_IMF_PK420_CxYY0_W8_C8 + elif bpc == 10: + return TSI_IMF_PK420_CxYY0_W8_C10 + elif bpc == 12: + return TSI_IMF_PK420_CxYY_W16_C12 + elif bpc == 16: + return TSI_IMF_PK420_CxYY_W16_C16 + + return 0xffffffff + + +def pg_dynamicrange_to_ci_dynamicrange(dynamic_range: PGDynamicRange) -> ColorInfo.DynamicRange: + if dynamic_range == PGDynamicRange.CTA: + return ColorInfo.DynamicRange.DR_CTA + elif dynamic_range == PGDynamicRange.VESA: + return ColorInfo.DynamicRange.DR_VESA + else: + return ColorInfo.DynamicRange.DR_UNKNOWN + + +def pg_dynamicrange_from_ci_dynamicrange(dynamic_range: ColorInfo.DynamicRange) -> PGDynamicRange: + if dynamic_range == ColorInfo.DynamicRange.DR_CTA: + return PGDynamicRange.CTA + else: + return PGDynamicRange.VESA + + +def pg_timingflags_from_vm(vm: VideoMode) -> int: + timing_flags = 0 + + timing_flags |= pg_bpc_from_ci_bpc(vm.color_info.bpc) + timing_flags |= (1 << 9) if vm.timing.hswidth < 0 else 0 + timing_flags |= (1 << 10) if vm.timing.vswidth < 0 else 0 + timing_flags |= pg_colorformat_from_ci_colorformat(vm.color_info.color_format) << 11 + timing_flags |= pg_colorimetry_from_ci_colorimetry(vm.color_info.colorimetry) << 16 + timing_flags |= pg_dynamicrange_from_ci_dynamicrange(vm.color_info.dynamic_range) << 31 + + return timing_flags diff --git a/UniTAP/dev/ports/modules/vtg/private_types.py b/UniTAP/dev/ports/modules/vtg/private_types.py new file mode 100644 index 0000000..7724c36 --- /dev/null +++ b/UniTAP/dev/ports/modules/vtg/private_types.py @@ -0,0 +1,109 @@ +from ctypes import Structure, c_uint32, c_uint64 +from enum import IntEnum +from typing import Union +from .types import ASParams, ConstantASParams, PGAdaptiveSyncPatternType, SquareASParams, ZigzagASParams, FixedASParams +from .pg_pattern_params import PGPatternParams, PGScrollingParams, SolidColorParams, WhiteVStripsParams, \ + GradientStripsParams, MotionParams, SquareWindowParams, StepsScrollingParams + + +class PGPatternID(IntEnum): + Disabled = 0 + ColorBars = 1 + Chessboard = 2 + SolidColor = 3 + SolidWhite = 4 + SolidRed = 5 + SolidGreen = 6 + SolidBlue = 7 + WhiteVStrips = 8 + GradientRGBStripes = 9 + ColorRamp = 10 + ColorSquares = 11 + Motion_Pattern = 12 + ImageFile = 13 + Playback = 14 + SquareWindow = 15 + DscImage = 16 + + +class PGPatternType(IntEnum): + FPGA = 0 + NIOS = 1 + Image = 2 + Scalable = 3 + VTG = 4 + Software = 5 + + +class PgCaps(Structure): + class PgCapsFlags(Structure): + _fields_ = [ + ("custom_vp", c_uint32, 1), + ("playback_vp", c_uint32, 1), + ("vrr_supported", c_uint32, 1), + ("color_modes_for_non_fw_pattern", c_uint32, 1), + ("cea_vesa_dynamic_range", c_uint32, 1), + ("dsc_supported", c_uint32, 1), + ("high_dynamic_range", c_uint32, 1), + ("adaptive_sync", c_uint32, 1), + ("scrolling_pattern", c_uint32, 1), + ("panel_replay", c_uint32, 1), + ("alpm", c_uint32, 1), + ("raw_y_only", c_uint32, 1), + ("psr", c_uint32, 1), + ('reserved', c_uint32, 19) + ] + + _fields_ = [ + ('max_h_active', c_uint32), + ('max_v_active', c_uint32), + ('max_frame', c_uint32), + ('flags', PgCapsFlags), + ('max_h_total', c_uint32), + ('max_v_total', c_uint32), + ('max_pix_rate', c_uint64), + ('patterns', c_uint32) + ] + + +class CustomTimingFlags(Structure): + _fields_ = [ + ('color_depth', c_uint32, 8), + ('interlace', c_uint32, 1), + ('h_sync_polarity', c_uint32, 1), + ('v_sync_polarity', c_uint32, 1), + ('color_space', c_uint32, 4), + ('', c_uint32, 1), + ('colorimetry', c_uint32, 4), + ('', c_uint32, 10), + ('dynamic_range', c_uint32, 1) + ] + + +def get_as_params_value(params: ASParams) -> list: + if isinstance(params, ConstantASParams): + return [PGAdaptiveSyncPatternType.AS_Constant.value, params.lines] + elif isinstance(params, SquareASParams): + return [PGAdaptiveSyncPatternType.AS_Square.value, params.min_lanes, params.max_lanes, params.period_frames] + elif isinstance(params, ZigzagASParams): + return [PGAdaptiveSyncPatternType.AS_Zigzag.value, params.min_lanes, params.max_lanes, params.increase_lines, + params.decrease_lines] + elif isinstance(params, FixedASParams): + return [PGAdaptiveSyncPatternType.AS_Fixed.value, params.refresh_rate, params.divide_by_1_001, + params.increase_lines, params.decrease_lines] + + +def get_pattern_params_value(params: Union[PGPatternParams, PGScrollingParams], bpc: int = 8) -> list: + if isinstance(params, SolidColorParams): + up_shift = 16 - bpc + return [(((params.first << up_shift) << 16) | (params.second << up_shift)), (params.third << up_shift)] + elif isinstance(params, WhiteVStripsParams): + return [params.white_stripes_width, params.black_stripes_width] + elif isinstance(params, GradientStripsParams): + return [params.color_step] + elif isinstance(params, MotionParams): + return [params.frames_count] + elif isinstance(params, SquareWindowParams): + return [params.white_square] + elif isinstance(params, StepsScrollingParams): + return [params.frames, params.horizontally, params.frames, params.vertically] diff --git a/UniTAP/dev/ports/modules/vtg/timing_manager.py b/UniTAP/dev/ports/modules/vtg/timing_manager.py new file mode 100644 index 0000000..779855f --- /dev/null +++ b/UniTAP/dev/ports/modules/vtg/timing_manager.py @@ -0,0 +1,135 @@ +import copy +from UniTAP.common import Timing +from typing import Union, List, Optional + + +class TimingManager: + """ + + Class `TimingManager` allows working with all available predefined timings from device. + You can get cvt timing by index `get_cvt`, dmt timing by index `get_dmt`, cta timing by index `get_cta`, + get list of all timing `get_all` ot search timing by parameters `search`. + + """ + def __init__(self, available_list: List[Timing]): + self.__timings = available_list + + def get_cvt(self, index: int) -> Optional[Timing]: + """ + + Returns cvt `Timing` by index. + + Args: + index (int): CVT timing index + + Returns: + timing (Timing | None) - type `Timing` if search was success, `None` if not. + """ + return copy.deepcopy(self.__search_by_standard_and_index(Timing.Standard.SD_CVT, index)) + + def get_dmt(self, index: int) -> Optional[Timing]: + """ + + Returns cvt `Timing` by index. + + Args: + index (int): DMT timing index + + Returns: + timing (Timing | None) - type `Timing` if search was success, `None` if not. + """ + return copy.deepcopy(self.__search_by_standard_and_index(Timing.Standard.SD_DMT, index)) + + def get_cta(self, index: int) -> Optional[Timing]: + """ + + Returns cvt `Timing` by index. + + Args: + index (int): CTA timing index + + Returns: + timing (Timing | None) - type `Timing` if search was success, `None` if not. + """ + return copy.deepcopy(self.__search_by_standard_and_index(Timing.Standard.SD_CTA, index)) + + def get_all(self) -> List[Timing]: + """ + + Returns list of `Timing` objects. + + Returns: + timing (list[Timing]) + """ + return copy.deepcopy(self.__timings) + + def get_by_list_index(self, index: int) -> Optional[Timing]: + """ + + Returns `Timing` objects by index in timings list. + + Args: + index (int) index of timing in list + + Returns: + timing (Timing) + """ + if index < 0 or index >= len(self.__timings): + raise ValueError(f"Incorrect index {index} of timings. Must be between 0 and {len(self.__timings)}") + return self.__timings[index] + + def print_all(self) -> str: + """ + + Print list of `Timing` objects. + + Returns: + str + """ + return " \n".join(f'# {index} - ' + f'{item.standard.name.replace("SD_", "") if item.standard != Timing.Standard.SD_NONE else ""} ' + f'{item.hactive}x{item.vactive} @ {item.frame_rate / 1000}Hz' + f'{self.__print_timing_id(item)}' + for index, item in enumerate(self.__timings)) + + def search(self, h_active: Optional[int] = None, v_active: Optional[int] = None, f_rate: Optional[int] = None, + standard: Optional[Timing.Standard] = None, + rb: Optional[Timing.ReduceBlanking] = None) -> Optional[Timing]: + """ + + Search timing by transferred parameters. + + Args: + h_active (int | None): h active resolution of timing + v_active (int | None): v active resolution of timing + f_rate (int | None): frame rate of timing + standard (Standard | None): timing `Standard` + rb (ReduceBlanking | None): timing `ReduceBlanking` + + Returns: + timing (Timing | None) - type `Timing` if search was success, `None` if not. + """ + for timing in self.__timings: + if (timing.hactive == h_active or h_active is None) and (timing.vactive == v_active or v_active is None)\ + and (f_rate is None or abs(timing.frame_rate - f_rate) <= 500) \ + and (timing.standard == standard or standard is None) \ + and (timing.reduce_blanking == rb or rb is None): + return copy.deepcopy(timing) + + def __search_by_standard_and_index(self, sd: Timing.Standard, index: int) -> Union[Timing, None]: + search_result = [ + t for t in self.__timings if t.id == index and t.standard == sd + ] + return None if len(search_result) == 0 else search_result[0] + + @staticmethod + def __print_timing_id(timing: Timing) -> str: + if timing.standard == Timing.Standard.SD_CTA: + return f" (VIC {timing.id})" + elif timing.standard == Timing.Standard.SD_DMT: + return f" (ID {hex(timing.id).replace('0x', '').upper()}h)" \ + f"{f' [{timing.reduce_blanking.name}]' if timing.reduce_blanking != Timing.ReduceBlanking.RB_NONE else ''}" + elif timing.standard == Timing.Standard.SD_CVT: + return f" {f'[{timing.reduce_blanking.name}]' if timing.reduce_blanking != Timing.ReduceBlanking.RB_NONE else ''}" + else: + return "" diff --git a/UniTAP/dev/ports/modules/vtg/types.py b/UniTAP/dev/ports/modules/vtg/types.py new file mode 100644 index 0000000..f3bc920 --- /dev/null +++ b/UniTAP/dev/ports/modules/vtg/types.py @@ -0,0 +1,208 @@ +import warnings +from enum import IntEnum +from typing import TypeVar + + +class VideoPattern(IntEnum): + """ + Class `VideoPattern` contains all possible variants of patterns which can be set in the function `set_pattern`. + """ + Disabled = 0 + ColorBars = 1 + Chessboard = 2 + SolidColor = 3 + SolidWhite = 4 + SolidRed = 5 + SolidGreen = 6 + SolidBlue = 7 + WhiteVStrips = 8 + GradientRGBStripes = 9 + ColorRamp = 10 + ColorSquares = 11 + MotionPattern = 12 + SquareWindow = 15 + + +class PGDynamicRange(IntEnum): + """ + Class `PGDynamicRange` contains all possible variants of Dynamic Range. + """ + VESA = 0x00 + CTA = 0x01 + + +class PGStandard(IntEnum): + """ + Class `PGStandard` contains all possible variants of Standard. + """ + CVT = 0x01 + DMT = 0x02 + CTA = 0x03 + + +class PGVideoMode(IntEnum): + """ + Class `PGVideoMode` contains all possible variants of Video mode. + """ + CTA = 0x00 + RB1 = 0x01 + RB2 = 0x02 + RB3 = 0x03 + + +class PGAspectRatio(IntEnum): + """ + Class `PGVideoMode` contains all possible variants of Aspect ratio. + """ + NoData = 0x00 + Ratio4x3 = 0x01 + Ratio16x9 = 0x02 + + +class PGColorInfo(IntEnum): + """ + Class `PGColorInfo` contains all possible variants of Color info. + """ + Unknown = -1 + RGB = 0 + YCbCr444 = 1 + YCbCr422 = 2 + YCbCr420 = 3 + Y_only = 4 + RAW = 5 + + +class PGColorimetry(IntEnum): + """ + Class `PGColorimetry` contains all possible variants of Colorimetry. + """ + Unknown = 0 + ITU601 = 0 + ITU709 = 1 + + +class PGColorDepth(IntEnum): + """ + Class `PGColorDepth` contains all possible variants of Color Depth. + """ + Unknown = -1 + BPC6 = 0 + BPC8 = 1 + BPC10 = 2 + BPC12 = 3 + BPC16 = 4 + BPC7 = 5 + BPC14 = 6 + + +class PGAdaptiveSyncPatternType(IntEnum): + """ + Class `PGAdaptiveSyncPatternType` contains all possible variants of Adaptive Sync Pattern. + """ + AS_None = 0 + AS_Constant = 1 + AS_Square = 2 + AS_Zigzag = 3 + AS_Fixed = 4 + + +class ConstantASParams: + """ + Special configuration class for configure Adaptive-Sync. Contains information about blank lines count. + """ + def __init__(self, lines: int = 0): + if lines < 0: + warnings.warn("Incorrect blank lines count. Must use more then 0. " + "Will be used default value = 0") + lines = 0 + self.lines = lines + + +class SquareASParams: + + """ + Special configuration class for configure Adaptive-Sync. Contains information about blank lines minimum and maximum + count and period frames count. + """ + + def __init__(self, min_lanes: int = 0, max_lanes: int = 1000, period_frames: int = 10): + if min_lanes < 0: + warnings.warn("Incorrect blank lines min count. Must use more then 0. " + "Will be used default value = 0") + min_lanes = 0 + if max_lanes < 0: + warnings.warn("Incorrect blank lines max count. Must use more then 0. " + "Will be used default value = 1000") + max_lanes = 1000 + if period_frames < 0: + warnings.warn("Incorrect period frames count. Must use more then 0. " + "Will be used default value = 10") + period_frames = 10 + self.min_lanes = min_lanes + self.max_lanes = max_lanes + self.period_frames = period_frames + + +class ZigzagASParams: + + """ + Special configuration class for configure Adaptive-Sync. Contains information about blank lines minimum and maximum + count, increase and decrease lanes count. + """ + + def __init__(self, min_lanes: int = 0, max_lanes: int = 1000, increase_lines: int = 100, decrease_lines: int = 100): + if min_lanes < 0: + warnings.warn("Incorrect blank lines min count. Must use more then 0. " + "Will be used default value = 0") + min_lanes = 0 + if max_lanes < 0: + warnings.warn("Incorrect blank lines max count. Must use more then 0. " + "Will be used default value = 1000") + max_lanes = 1000 + if increase_lines < 0: + warnings.warn("Incorrect increase lines count. Must use more then 0. " + "Will be used default value = 100") + increase_lines = 100 + if decrease_lines < 0: + warnings.warn("Incorrect decrease lines count. Must use more then 0. " + "Will be used default value = 100") + decrease_lines = 100 + self.min_lanes = min_lanes + self.max_lanes = max_lanes + self.increase_lines = increase_lines + self.decrease_lines = decrease_lines + + +class FixedASParams: + + """ + Special configuration class for configure Adaptive-Sync. Contains information about refresh rate count, increase + and decrease lanes count. + """ + + def __init__(self, refresh_rate: int = 60, divide_by_1_001: bool = False, increase_lines: int = 100, + decrease_lines: int = 100): + if refresh_rate < 0: + warnings.warn("Incorrect refresh rate count. Must use more then 0. " + "Will be used default value = 60") + refresh_rate = 60 + if increase_lines < 0: + warnings.warn("Incorrect increase lines count. Must use more then 0. " + "Will be used default value = 100") + increase_lines = 100 + if decrease_lines < 0: + warnings.warn("Incorrect decrease lines count. Must use more then 0. " + "Will be used default value = 100") + decrease_lines = 100 + self.refresh_rate = refresh_rate + self.divide_by_1_001 = int(divide_by_1_001) + self.increase_lines = increase_lines + self.decrease_lines = decrease_lines + + +ASParams = TypeVar("ASParams", + ConstantASParams, + SquareASParams, + ZigzagASParams, + FixedASParams, + ) diff --git a/UniTAP/dev/ports/pdc_port.py b/UniTAP/dev/ports/pdc_port.py new file mode 100644 index 0000000..de17d45 --- /dev/null +++ b/UniTAP/dev/ports/pdc_port.py @@ -0,0 +1,215 @@ +from UniTAP.libs.lib_tsi.tsi_io import PortIO +from UniTAP.dev.ports.modules.pdc.pdc_io import PDCPortIO +from UniTAP.dev.ports.modules.pdc.pdc_power_sink import PowerSink +from UniTAP.dev.ports.modules.pdc.pdc_contract_control import PowerContractControl340, PowerContractControlBase +from UniTAP.dev.ports.modules.pdc.pdc_power_source import PowerSource +from UniTAP.dev.ports.modules.pdc.pdc_controls import PdcControls340, PdcControls424, PdcControls500 +from UniTAP.dev.ports.modules.pdc.pdc_dp_alt_mode import DpAltMode500, DpAltMode340 +from UniTAP.dev.ports.modules.pdc.pdc_capabilities import PdcCapabilities, PdcCapabilities340 +from UniTAP.dev.ports.modules.pdc.pdc_bus_status import BusElectricalStatus + + +class PDC: + + """ + Class `PDC` describes capabilities of power delivery controller. + Contains following field: + - Power contract control `power_contract_control`, type `PowerContractControlBase`. + - Power Sink `power_sink`, type `PowerSink`. + - Power Source `power_source`, type `PowerSource`. + - DP Alt mode `dp_alt_mode`, type `DpAltMode340`. + - PDC Capabilities `capabilities`, type `PdcCapabilities`. + - BUS electrical status `bus_electrical_status`, type `BusElectricalStatus`. + """ + + def __init__(self, port_io: PortIO): + self._pdc_io = PDCPortIO(port_io) + self.__power_contract_control = PowerContractControlBase(self._pdc_io) + self.__power_sink = PowerSink(self._pdc_io) + self.__power_source = PowerSource(self._pdc_io) + self.__dpam = DpAltMode340(self._pdc_io) + self.__caps = PdcCapabilities(self._pdc_io) + self.__bus_electrical_status = BusElectricalStatus(self._pdc_io) + + @property + def power_contract_control(self) -> PowerContractControlBase: + """ + Returns Power contract control. + + Returns: + object of `PowerContractControlBase` type + """ + return self.__power_contract_control + + @property + def power_sink(self) -> PowerSink: + """ + Returns Power Sink. + + Returns: + object of `PowerSink` type + """ + return self.__power_sink + + @property + def power_source(self) -> PowerSource: + """ + Returns Power Source. + + Returns: + object of `PowerSource` type + """ + return self.__power_source + + @property + def dp_alt_mode(self) -> DpAltMode340: + """ + Returns DP Alt mode. + + Returns: + object of `DpAltMode340` type + """ + return self.__dpam + + @property + def capabilities(self) -> PdcCapabilities: + """ + Returns PDC capabilities. + + Returns: + object of `PdcCapabilities` type + """ + return self.__caps + + @property + def bus_electrical_status(self) -> BusElectricalStatus: + """ + Returns BUS electrical status. + + Returns: + object of `BusElectricalStatus` type + """ + return self.__bus_electrical_status + + def __str__(self) -> str: + return (f"DP Alt Mode\n{self.dp_alt_mode}\n" + f"{self.power_sink}\n" + f"{self.power_source}\n" + f"{self.power_contract_control}\n" + f"{self.capabilities.status}\n" + f"{self.bus_electrical_status}\n") + + +class PDC340(PDC): + + """ + Class `PDC340` inherited from class `PDC`. + Class `PDC340` allows working with overriding PDc controls and power contract control. + Also has all the `PDC` functionality. + - Power Contract control `power_contract_control`, type `PowerContractControl340`. + - PDC Controls `controls`, type `PdcControls340`. + """ + + def __init__(self, port_io: PortIO): + super().__init__(port_io) + self.__power_contract_control = PowerContractControl340(self._pdc_io) + self.__controls = PdcControls340(self._pdc_io) + self.__caps = PdcCapabilities340(self._pdc_io) + + @property + def capabilities(self) -> PdcCapabilities340: + """ + Returns PDC capabilities. + + Returns: + object of `PdcCapabilities340` type + """ + return self.__caps + + @property + def power_contract_control(self) -> PowerContractControl340: + """ + Returns Power contract control. + + Returns: + object of `PowerContractControl340` type + """ + return self.__power_contract_control + + @property + def controls(self) -> PdcControls340: + """ + Returns PDC Controls. + + Returns: + object of `PdcControls340` type + """ + return self.__controls + + def __str__(self) -> str: + return (f"DP Alt Mode\n{self.dp_alt_mode}\n" + f"{self.power_sink}\n" + f"{self.power_source}\n" + f"{self.power_contract_control}\n" + f"{self.capabilities.status}" + f"{self.bus_electrical_status}\n") + + +class PDC424(PDC): + + """ + Class `PDC424` inherited from class `PDC`. + Class `PDC424` allows working with overriding controls. + Also has all the `PDC` functionality. + - PDC Controls `controls`, type `PdcControls340`. + """ + + def __init__(self, port_io: PortIO): + super().__init__(port_io) + self.__controls = PdcControls424(self._pdc_io) + + @property + def controls(self) -> PdcControls424: + """ + Returns PDC Controls. + + Returns: + object of `PdcControls424` type + """ + return self.__controls + + +class PDC500(PDC): + + """ + Class `PDC500` inherited from class `PDC`. + Class `PDC500` allows working with overriding controls and DP Alt mode. + Also has all the `PDC` functionality. + - PDC Controls `controls`, type `PdcControls500`. + - DP Alt mode `dp_alt_mode`, type `DpAltMode500`. + """ + + def __init__(self, port_io: PortIO): + super().__init__(port_io) + self.__controls = PdcControls500(self._pdc_io) + self.__dpam = DpAltMode500(self._pdc_io) + + @property + def controls(self) -> PdcControls500: + """ + Returns PDC Controls. + + Returns: + object of `PdcControls500` type + """ + return self.__controls + + @property + def dp_alt_mode(self) -> DpAltMode500: + """ + Returns DP Alt mode. + + Returns: + object of `DpAltMode500` type + """ + return self.__dpam diff --git a/UniTAP/dev/ports/port.py b/UniTAP/dev/ports/port.py new file mode 100644 index 0000000..d58d216 --- /dev/null +++ b/UniTAP/dev/ports/port.py @@ -0,0 +1,9 @@ +from UniTAP.dev.modules import MemoryManager, Capturer +from UniTAP.libs.lib_tsi.tsi_io import PortIO + + +class TSIPort: + def __init__(self, port_io: PortIO, memory_manager: MemoryManager, capturer: Capturer): + self.__io = port_io + self.__memory_manager = memory_manager + self.__capturer = capturer diff --git a/UniTAP/dev/ports/rx.py b/UniTAP/dev/ports/rx.py new file mode 100644 index 0000000..8647e02 --- /dev/null +++ b/UniTAP/dev/ports/rx.py @@ -0,0 +1,25 @@ +from .port import TSIPort, MemoryManager, Capturer, PortIO +from .modules.capturer.video.video_capturer import VideoCapturerDP, VideoCapturerHDMI +from .modules.capturer.audio.audio_capturer import AudioCapturer + + +class RX(TSIPort): + """ + Class describe base capabilities of Sink (RX - receiver). + This functionality is used by child classes `DPRX` and `HDRX`. + You cannot use a class `RX` object directly. + """ + def __init__(self, port_io: PortIO, memory_manager: MemoryManager, capturer: Capturer): + super().__init__(port_io, memory_manager, capturer) + self.__audio_capturer = AudioCapturer(capturer) + + @property + def audio_capturer(self) -> AudioCapturer: + """ + + Should be used to control `AudioCapturer` on Sink (RX - receiver) role. + + Returns: + object of `AudioCapturer` type. + """ + return self.__audio_capturer diff --git a/UniTAP/dev/ports/tx.py b/UniTAP/dev/ports/tx.py new file mode 100644 index 0000000..8794acf --- /dev/null +++ b/UniTAP/dev/ports/tx.py @@ -0,0 +1,25 @@ +from .port import TSIPort, MemoryManager, PortIO, Capturer +from .modules.ag.ag import AudioGenerator + + +class TX(TSIPort): + """ + Class describe base capabilities of Source (TX - transmitter). + This functionality is used by child classes `DPTX` and `HDTX`. + You cannot use a class `TX` object directly. + """ + def __init__(self, port_io: PortIO, memory_manager: MemoryManager, capturer: Capturer): + super().__init__(port_io, memory_manager, capturer) + + self.__ag = AudioGenerator(port_io, memory_manager) + + @property + def ag(self) -> AudioGenerator: + """ + + Should be used to control `AudioGenerator` on Source (TX - transmitter) role. + + Returns: + object of `AudioGenerator` type. + """ + return self.__ag diff --git a/UniTAP/libs/__init__.py b/UniTAP/libs/__init__.py new file mode 100644 index 0000000..b0e9711 --- /dev/null +++ b/UniTAP/libs/__init__.py @@ -0,0 +1,3 @@ +from UniTAP.libs.lib_helper.lib_os_helpers import OS_Requirements +from UniTAP.libs.lib_dscl import * +from UniTAP.libs.lib_uicl import * diff --git a/UniTAP/libs/lib_dscl/DSCL.dll b/UniTAP/libs/lib_dscl/DSCL.dll new file mode 100644 index 0000000000000000000000000000000000000000..fd352feec0b3aba10e2bcae1a6c9334cf3de20bf GIT binary patch literal 775896 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%0A=F`_4wLs>jM(VS7gVD`dJ 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 z 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

1}j6i`bVo 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�$Q}J+W5R;;= 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

tL>qUN|0KtZOyf!! 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

wE_h6*Lz8^bnybSo zUlS-_+ad=7o}J;EK)n8_?s+|Y$)xDj7f0~ojW-|9&d7O_qBR#s&PtZ)(f!o&dKlH{ z)feMt!wN<|;^Zs3IaH&^MJ0tcMQ#g;idCrc0U3hZVLc$xdb4A%LZW`TNK||RNc7u> z1&MAtRUy&oj6**QiHiSyB)U-*SwN8@jwf0yaeRlG6E>cArEDjH?D?wD=g?*Z;-e>X z3@D62Bc|=(ld>v3Q&?3Wn7VQRz`RbMc|RKt=a_mu2!MUx@T0*h)^_Wcj|JJx4)j-| z3cP&MoSLz~WMOszYw%odKMc}^hW9qv0OX}+0sWDPl zpPWvO39C-so34o7AHP#?NJXzHYxQBkG~+J723_CkV7!*h(6dd;eNxLGQHG>J=d(Z; zuxpWo=~`O5fbQmxsAY<(B{V>k=#3b4s$0Ywlo zNq^mBnH|RFE{WBCkEkA}=tklEI-8fX2o)tDN7cO851>8v%A=ZpcBe$0Ch88hu~}@^ z89fGWTb2UFQ|=j4mCE=w+%w7w-piF%${b6V+~Y(CH$kS9x0wwN$aYu{3ky z$$~{>Nx3he5G(R|)+4f!q${flloDcxkvv6@c|_t%uYA1CNO)kfEWH7whZyMT(2 zo2Fp;(*MdCw66bk5qW-3J?~Y|`_=RNJS{z?SC4YGh3v2MTS?b&0mDujd!TeNPj&H$ z?@N~xOqFwW<5c#M1CA});u17bA0NoiYZ#oL$0iT4?9fE^i3%sW+AD@^I`xw^)&Epg zr&~QwmzuLW(p~C067wf4F9pCgf>j*$cW?QP=(v!=#sSU3l@dZAL>lZrYPuBZRL^ay z$kpn3g(_H$eLds)+|bOpLL^_8L#X8(H(gc2s|j^9kVSCNCY0e(=zefXYo&s9Z4ic{`(;I_eNOWrEo z#IO9Ge0bs6(U0;~iE>Vp(#RtPzGqeZoGOSDmK1cARnS?xOSm-U{vMSKT+4F(i5AdQl9Bj58N(^yPGykhqvvZ68Y`7WM9O_V8Te8S zSbC-0zo#BTgOY`y`XC@DV;^0ui>IBS@n#_QJ&y5BD`T;zoEOvwj1IqX0G+#Wc+O#I zMfj#Uho5?l-_geZs>v3J*E%U{3Z_1_TLRHL+w)j8REAx?N7dq-rZOpt9l2)ok1 z;VO~wu9sWMr4V|O@v)g6bG&QfcPIJ}+@3I{JEBK>XpIY)pPaJEPq{y)7Ut(Et&Pe- zV}2c;8Xu#hNq1ed<+Cy69wsemRMFxPPz^6zwJjax(ZlsxQxk<8-L)>q9~}2 zUw3Jp;xooZew0pqU?Szdkdj1;CiLX=l>7bfsme>!RdrNRgvt-GR{2?KE@!QrCxx({ zO1bxwgyKhsW~KUg^CgqRtU4?h-*Tu2#k%;J*)b!9F#wiJKbA*P;4~61xOu(>Iyf6)O%&O z0kZxxt2w3r)>1FAE0zEsqaFGGd|z}eYkOFyy$=KwY8fD-U5Jm6nijKQ zQNC8USqjL5RE-w`G)|C|2_)C~STt{xRT+kpio(eQMtb0GMkj}6mZ?+%j)RbWn`T-# zjq_${Jf!Vv)ZSQhyv_!TFamhVUW%bTWLqKG=ujmJ>$Nh;%2}beA>sMr*MaE?sW0YG zm@_2h{yxEI=Vjr-Xy;9lOS)wfIDu|PPA16HV1h!Pq>Emef}+%^R{gAD{_+*lQ*O~_ zmldi|gs1{U{{bkC@6nyneMccQ`0Z(U zH$Qx;(bi)2SdvahA1#CNd8N_Rcl#0_!{9zFikWCc&S{b=4go{<3$ZD@QWe3D&q zq{8sOhno~5gaP8-a^2C+G1xt_v5Z%h=)PCAMscr&0-vM0p@a`cY}rWqumJ|a@ha># z2T99CF6f8q9IQYrI63A1<9DQLt?>@2b5+LxemMD?h^C6d<7oQ1;p;77ht|ri9KxhM zR%_2sx&MpIT_l7Fsx(22-@qV{5IW#PZyLRmMpJH4IOtpEyz7NV$WmWPc*M?=VFg{?)NzeSFJM0p|G{4~Ofep;mz~hy_-z z3&8J5>85Ip6V-+jJ^2%nqPD+*h?M&iSOHej{w>jc``D^?(%9IrGHgVyPy_^;LY7BV zmhcsVkD&u1;T6D)(!vRhZd#2nyW3cJC%XKtZ1gw7Q+4F~5jVS*KdQ%n(d2&q#hn@BZjKl+M};d-76y;#=B0tbd; zY^j3)!Qj_1`H@7F5nEk+INe-AJ?V+h9G*!~Xb40K>Y&I}+WyYiDs3;vpifD$d3h}F zVhjg}I>KmIR1P21VRy76KkSNjTo&#bJv=O21USa4Sw3EMky&ox^V@e>`24i$yHG}m z1;RH~my2*(?_0)riQH6=q(fdoJpeOI0VbSFoI}trN;BG}>fX_gOZj@Zf4Eq+DOqK? z3m~8?sh8z}6y->{KUYnN{H2-NQ|?)Wq_KhxTBEhsxxQnJ!nir`oDHe|%b8HA{=Zkx zU#jN<^_;7oVfCD(o+hnK_1~|a@2Tep>e;EDpQ+~;>UmT>$AKxS{@+#4d(`tj^_-%f zQ`Ix9p3~KHrg{o~rTRato`UbG{tfE+D6=WmzfrxJ)cm| zAFJn6>iJXk{JDDmQau-{=QHa0EA{-1dM;AW|54B1tLGop^Ld`a$3lh=R9zP^wwk4k z=2B)4@gmX7+R$&5!M1$ScRPc|THnDuu2R?s2*sa$h~%7 z;)U0~lTSj(LH@y&@W|ythRsnlQWOiSGqSd-Y}yvSD!M;ETq%#!#E`rsJ67g?W@56? ze32h~#;frYAAXEuhemlJNc59xq1I6*>?tFk2obW@4&@fZ9=O*07PA}Ui^g_o$X;09 zqdS?0Uhk6p=#Z^HmKvJY@Tsk^g@B-oc_ES9+cVl+4+@zV0BAG71I^jTB8bFG-{8l5 z%%uqD`Q}>6BzZaCms{UIXKI?0_}*4{4~6nQ&wE+w(e>svg|b#pMXojbNP!}Q;{t#M zbGvD%W`;R2)T(UU5d$~^5;RJGOkuv%)>Pp+6s2I4mVFot`%=F2Ff1#U_P2vwa<61Y z-mLaMde|u3%cUeuiP@!T!AZ$XsrFc5J0Hw8x~?Eo9v!lrh#rwE%{OSdc{Qb(M`&)e z|1-qr(Ty3jb>KGdqnphi@OE(LnQuhj%{Sj7H9GERtVYyIBNo_rSssje%VPB`?{uea zr0ES92b3>F_HVVT+;tQ^8`-qu19}i2n!@AA3_r41=WfsmzE=W$u9neB_DQ*;kR4+u zG?WFn*7yRkKn=Hp?0PI@D?0n>KXX3~DM{ywrt%|KQ0Qf3Qv#QH4&QW^Pfxa(t;`B4 zo^4ee=74~eCgtuWvO@}4HRXt~mtfa;aZZil^U3MAn|xAyTt1jto$QlNn{uy(Oj*rK z77ctuzKXhSyjnkcmBnd#+(a%_@-{QLQ;FJ4l1QPs-YLCplT#%ih?F zOMd2;IS|~1fSjWYIIDpJhnAd4t|n>^_ZgTtplg3Bee*{Urw=Z(=GXK2$wTS9hZt@0 z7MnvE#N-*~Zo;7GZ>%BJ{=TIQ6vp>S9{I`9q}*tn7G|IP*3RAAlo;=603T)*aE$d%r1J zAw_rbV$nTqap8ZGxoPc-f}&d8s^-G^gv}nHl{tiDHU#>Z)mGpn%IVK*a{r@gs+i^n z4MT!t%`iuYkZeQAyxj*SDUs>b^wwtsizCIjDDbrEsF7dJB z(5oRB@3l^k^{EJWwuZ}$5xuauo?2oIbvjzObsS%A(Uq0S-K-`c27)Plq7PQ&&62kg z@Un4V0rq zA`M{gqH?-_WQEbZ;O}0dphj*H6CkWZ=1~XZI-j59*@)k0G6t@-R#Bfbha%3js}+ZaoWLDqf~>Nr_$*LD^^MFQodJ}znt$;PhO;Vv}Y^p;5SkPe71z&XPS7{02!{{3BRdRIO1dGG2NQQ7QKa z<5ZD15n#xT?bx+QCGpZwPL3852`>)Dbj6Exn6X?sBWl)~kikCjP6Vb_Z#%8~ETBEt zsXGp%heh&Tg;#nJB&JA{&d{s4Xyswier^_TK8CyVc2)q}k~>)SlnXbg5hlhajbs5U zc9-uRYodgBcK z3|xzzM1g_1at$vjx2}Ti%TFrEQwS}CHCW=LHPY#2r!(#;cQtWztaup&EN|oafUO9y znZG4uEqh~y?+YARD8ghDvS~C+!bah1@&jJRC4@l9`$(eN3eO=%s%~f1jK4>VWTr$+0p2WZyo@AW z@+zI;0F+X?@#jto9ccMfyFrEvoswI=(ATRh9k?v7CkvCD5JZg(SgK*65;;WzFmxH5 z@|61@Hv*@&XsXA(Pm2WDS+2 z+|SS!-SC4`epkx<*#BeiOW>obuK!;$lZ7=wi2{O-2pYv@fB*qhCXhg)0U`v&HAE65 z3Q3v;ai1ipK#WF<`%-Gr;!Z1W)Fmh=iej|xwVJl56R|dJu~J3zKj*&totYfDzc-Tv zYWw%`A>TRo-goag-*fJL_uaRVL{|kz^6#bg?}q6fTb{>qmWOtuv#%CU_GtP&Ix9JQ z^u}y;Q+<0o)pQsMqeD&6@J<&g<6b+QJ<2b9__~?I$62KA&dWDnr1B&znciYm@HRQN znnu3^siDxB%d6y`8+n**A0AH`WM4y{D@ktcNk$V#E9keXCs6PPU8Su{92WSL`Ke2c zr8fB3(GXY7W$K0(6Hr;=Cy}oHKRC?CU#+S6qKSB`e_uy$#T*sA?)t5-9r+X$w*vg} z0y>U1OmEA0vYFZpET&`@+f({b;q3Z515_-#;l}q^q57>fWYGFVKK-W1I3AJF(4ulx zp-^wu^LL&iP2B(MZ$$oNYDj!zs zUPz={aVlEv`8D~T)|S~1*!Kc^-y*T^7wYJ?=Y5maSjZJ5^Km)pZW`RPM$>bUTQh3f zN~?+&Wz_tAT5)N0X8oJ>ZG7CG5|hoaK>9Rr59O=?d%p@}})T$OslJX(;hI`o8chCy-b{=J*dr92l7%ceV@`5&2= z^s9}P{WI(15^1=~sM|tUo8rEsv1msErR}62>XDethU{-Awq1HS>q@O>*7Mnl8*L|T z_)CwKc!hFoLk9aMP{ zl^9%EN!72+H?FU6&iNZzH4QS|Fc%W^em9`;@)-5W(KJ#cj2B?@)iQRe$~JZ-x5%wEBCK`kSZz zPF8=@)ZfF^-{tD>e(G1>aJTw9R{ecarC+Rm_g8-pP=5nq<#(#z zcd5UP>TiWAQ>FecQGe&EzmwJ9H1+p;RkBU}-A|?Lrr$IK2RHpaw0u5rQ;c=|C>@>f z(lML9S=3`X-QruHk(e`LWA?^f@j3WFl#9N5lCrdRWBqf)wnDKa!^U{m_#A8mwj3ai?dxYIR=|d|y6F1SFb??zlZ%J3L;=1bp znX~azx~g%9y-Usa>bp=!@=kOYVbWD)R4DMpC|E!6=gEuLs;cOpwc`yH=856B}<<>uK zUz_KJhSvhmQA>>(^m2`i6adf76D(owtq7=jp2nfv1TRwSvWE>GLEi=Y_yMl-$U#G@^H}=I7R5POlvPxavT@ zpOzL1Id1vc8A_}Eo9Vqr>Mr(^cV&;Dnfa7mbnWes3)6Uwj`m+quc6CcabnKK?eV#; z&vI8RpqCo=K9MdW(N!Y;n4T~62n*jb{RR!RuLcgIo1$M0oc#&?4c~mTr}H$D?$>=K zY1yr$^>jLgB36@gN4!Cwa`TR+mumRJz6Y68pVyL8pZy+vNvJtz#nKk~{~rBsK6yoM zEd6lhkXnn)vFRM;a!H`2Y0FL-0n?x(1zYl!D{;EHbZ``E&3F_Yte9MPuwc0S^JM~Gz%gN>X zol-va9h2Y<=rq)XACKza=Zh#gsd$JgJ?u#=IuFkvr*(YYQ2{?h^>r&d?-B~|p z8#t0Hr0FCbO!>}N>W3}V53_gepvl{fTp}!~GnJ#00eZ}fHco8I=7%ll`S`w+-awz)<_}}>I}CFhDpUQJchu~QQ3dnc@zPTg>8VE1Br#ZW^Yz2IVNppn+^>*+BkdPa&a z0?^}4O*tc|L%dSi)4s7VYs-%GCG-GpR&&mXCLX2tqsyjw>YhS+80XC;yW8m|;}Ngq zG#pRwy5Go)rY{HR!3he#%eK^CM$L`pRt70;`=3a!yGgqGc{*S#O{u$W3ml>}-JjNk zcF+yP^ujvVJLDI{~kQxX~ z;(AEmuP; zOrQ8&)rHgRznVVcYkERg9C<59l|VW@o28DtFLFKMN8S`Z^7diZ_vbS8yLPyE%veDO zm&+6B9X#Xs;dk{VJi4v_^}uug;*XQHQ^s+8seSaz_Qm%4tPfXA9Za`2teEN{?P#c* zL1V-WIvVpG^^4o4PyDLtvg!5TO&{@nPEEVg@MyZNn&t+tCtdXl=@ht0JqN)wz*V#v0NY;8bd5syD~=5&?~+m(xW>IzH1)L9Yh}DlNa)TGAPF zbs(oMb6BE}7^6EJ7th_XzkUD0JM`__zynm8i!c+JbdNfP0j0l)aZ^2YI=y}gCDV(M zo790h>FQWgnpqpfdg~EEj}$a0(0v-T>P9c?n?s+pqKkRKXLt7e2|4 z->k-;i8TJy($&OvS_7pMq}7y9V`Yc;jI^ih$fX$v-9O1dO$NBh6IlO_O{VPO@wf}8 z1`4S;lj1b(kcyk0Ls>wrzZ&RImC=iD@@NPSJorAnVjyrY{j~>nwfxuOH7*C)`sG3kV#ChNI0*wYMuqWXemcwu@1fxhWQkDAj1BmDRVDCON9zBU+mIH+;&Di($HijkHJw!ltM_I+domrLD(T=_d$LxLCRh`{ zs{T|eNQ&i-_$Tu{qS-!?pyy8Mg*0t+lZP^461@P3t}`4+TJiHP?M?JL8tNox@m1R^>~ceRsl^;>A&l4SlE2km$;-DiA~vWt#XDd*YcQ@Q*U zDo-6t&QtiTisyycNKWn`lfLSG9(*5h%@?Zg*2f*(pH68<(;c+S)aeN=&4_E_!VNUHzx?9xp%DgBPY#$i&sYz(&xGk$=izMd`KUJRT_Zla_rl4s)8qlw6>E< z1!zU!QvS*x-3v}Ha*J(G*-f0%ypN`c=Vow8oA(hXJV9lvhVh-K`qovtn&ECwd4`MA zY7gDcKa=+P-pBh)qoV`OBH4yDv>UC4 z-{PAfN2@2v`(lh9z+)WGpL0fRp{L*Nlc!U7?tJk_Jj_j{DFC(qlKru=7vt@>IbCDB zV)UzlU4NufG|;wishUlz2~-Ty_ZYeHBdPI6V|LWg^BzPf9Iui4f3iA1QRm~lctjo~ z_tMD%k&>sD-)Qj2o$sR$SLP;$jV6ga5b~9y0c0lK?vP5?dZ-h+^uRMPyFDeIa?}$A z^rl8yKcV_|#4eBDL8A%v8aivqLva@`64t-lp0b81Lu!5Qz_9jc^s+BxtFAVex*Fe) z`NLHN&-8jaZ0m&tx)pl*2pY2NwFJJ9#@iJ%<|Hkn<=GDF2z28LzRkq5Q+kcZS+sin z)ZtW&Hh*BK0!H< zCr;;ZjrMxMQq|gy-n>}A%@SElb6sE46B=1o#`5@FdY54~zo+r_=@WNV$I$?^V<1f# z2m`HuRqGq{CWEPo)9FQMbe8`PB{k;OZ=Fte?C=wf+XC)*KH}39z#gA6&d#7W9iI6@ zVE5a6n*I2jINs@az)7ne6rmf;Tj?Hib$9vu>Lb0}$Hv=B7FBfYi#z-Z>IZakTeEQ; z*-PuI)QuXcOL}5R%x<`ZUf#gh4DF{ZDIPf+&Tfcnxt#ZQ57a3Q*Ygp1MgG9u`fQlS zkhj$y^c@aWG~vZ*ms&4Q`x*63%3DEsxhtOYb5r$k`=3uk){1FWxht+%Shp=Evwm|u z&5O1LyhH@OYE@0u-lL*aMNF`Ysj3QEqfxh}^PhBnF$DeLfsMwcRCSdjiJYj8-8`bu ze-8^ZWt^5y|9vd*59&M9=4P*WLMv2T5>iOtYfehjGonY*6FYv+h)$$*M^rtFUYJ2+V79$Ky&jD2 zh_o+uUEsyNqxwM!yFbw=KsLQko(fy3SdlScd4`7;EokbRLDteua4%(Kb9V~dOGK*P zN#PYX8KE&Ic>RoC44fLE7i*D`C({_J-snA>PWOVZ^d7LM6@5XvI7%j1k2s`A_^+|e|gDSC~0hyyho8DQ~ zNC&%eiqfPb%ju}hg9jhkyn)`guv?n58)l>iT1fllQ$2M5$(2>H6ahRJ;hD!IM0mzi zJ|ZHkA>&UR;k18V;6Z-F0|i7LkBYesC+xl?hJy6c-g&`9gn92=dS^+CRXr`3V=PBC z&kOwdSA3TQU0-WYIbsrN(=eSn++zAgQ(3+RJXlR#QSXJ{+s8qrQ+vv1Cvy3G4r$Vn zzPCzpQnHt3qP}FBiPCit>ZJB8mvWz?T>jwvKj^#UA0_gyHg%>qmAprT(!}N^gR`+f zO9LyNm0!%wH_S*3&_`A1hrYPtrR%rps@-&(8^3Z;V)pz~-2cjqO{O&y>P394r^^Rf zbdE$4za`46-V)_iJ@#74%&n(4rjJ;Xm~;poE9jV-oIUZ>0abnYi(2XQiq7__$vgUT zhox3Xg+Fj3WNSLl&&i*nO?vpBo^JA5)sNFj7>%$?l22+FJT80Uw^fJp-1bnO57Idq zUqDrNDQ*q?OL42t)OIL4*%^<~2?HrueMpdX+iVxVddKd4ubiOz2tvUW1IoAY{9YT| zw%wH-J+orf)E(Hq+EEczo{{KE1{I7FCX(en8auWG-hG?-Yj9=md?D@?VH?RLvzsf zl&>h3I&@e*-q4tW>j9y+E855JpC#sw7>RijT@O6z2wG2CQW6->t*QmUhPku=_?H%H zcGr%B+EZ3>1ANa4-8lbx;QP0@_t8m{eH%cYK8n&&laIdZ=&~psCG9&<$<69sH=b9h z_cFG=K`VZNJGRo1v3+>fM6R~}d0Lm^ug2Hy@>S#~Z5V!N54ti`dD7dBSAyCa1jbEUU!R_&LuGI3>W#&Z7j&~nOq zzWP&&kaaQPVG_E(xV@!=yR;CNKm3diF7?f^j~$=aXU(I>EXhSFb;a}O(H44t6=nrn zCQwKd%lwpdcKymc?(lUR2hjEGb10`{d!hW~);2oJAbBpAZy)}=Dx2M~(nE47C+WA^pc~g0W{=mlr%zq6m+hGs zXl8x19bA?-d#P5pN2_&oJfU3Ar)Zi_mU7G2OiSzj+T+B%G0ZOHov+uNYb zte@@)RMXuJ!Dh&QKEG*Qrps%Y`17?|l-8mB?D_^j6%Hgk&%|xXtykB@Nu%_@CYtvt zZGKLRA3<$|{pWQSHHNFTp)jd$Z@*SFP(m&&3{D^g4;-GiWTOiOEZ9 z5VchqO#O-a+zWJ*fU1)g7O9`!OXp0qE=&^(mulksKcfjCIhUq5^*1&u z4ei>WeMO!0hAMh2SP_#;8}aSq9omQQH;!YCWRAbqW#miF5At81l^%@WboRHG;+Iu! zPf1Lt3PSPA{v-Z9ROb>^C)b0G{Nv&J-FNDEv}G>en#b{@{lEQ=V7`inD##5h=kfkU zkn8PuDbo5<{kr|s*40!dBxkoa+Om)LPYac2nc7cnEf3|BoZUK)<%X4Hoz9bn74=Wq+dC=ml0Dg<72shC|{K?3dtVM-x;z`*}ES8YgoCK^WVBH$j3sKH-+ToEUypA%`C6c za+rOdx|UcPA89=Ce5dU5Q)#kznsqfTi1l-HqPG}lJjT~ZrvV|*Kz(^A(`({YTXo) zn??P~e^nvA{j6V6NN(f(mxcCk{$|d1hw|5P{X6NrLievmmY0R(X5POlBqy_61X+*&`H&k{CiDJlLUJ?9t3q-c zw;xvjI#Iuu%ezs&!}0P-qEcsvk2DCkr<~<5dsed?#+Q%VTNSF$&-D!p$t|2;6_V$1`$ZwSnP%6m z%P@Z+{pqS=>-L+1_O4d?g=CBK!|GqH><`uFV|h)eypPN849Qkv$ND{-|5hm9&--r* z$sR7BACmoSPgwna-hWjn-^2OCLh@?fzbYiRukAwI==kcR@o7L^ar$%F4Gl~?9b(s#H``P0@$xaso+9GVppfEE z6{JUudA>-+I%Ma=|qPqpI5!KhsqCN|L>%IRsXDC znauKb$TZ(%`K^#l^%6hRzd?Ui@}`je9u=RVcqnK2tx)-OEN=?Qs{FFh{_1!f=C3VO zo@}3Wte(F#sr|LTg8Xh^HE06YOb{5g1yjgie*KlapM8kvS9cE!^d9o*p7xY}$AocD zeCAPBcs%%7?hk$IS2nS{>F2?CS*_$y{P>l8az}pFL)P=7W!OJ#epF6r)P5@OUnKI$ z{t?RlVEN=$AD0ix-d1l1Ij_|dR^QH(g8C@?hCv^FKCu(_HLU!B{_Ovt{tYWzSY8v_ zUmagoh2({-|N2n8sQT7~WFO}jg~}^=RY<>ioS&Z?)Yn5Y`~PU$|9$>6hwlB|PH(MM z@v8MtyCE3A*}VVG2lW0tzR%{9G)3DUQV!k8TtA0O*lE*F z_|`sISnw|9<>+<9w~EhIruwO$-FS;#zkd()tF$9ffBX;Y*Yo4d2ZQ!|shU-ZB%c%{DKh@rLs!NXt z9&Ufl{Xtp9=PJmW|Mj8r&0M|;`SmMXST2IB>kpQ<&re!+F8Q{DKetzQl!sM!kV$`E zYci#gz8ilUQmmk&`93NGGc`L+!F#bHGBih?Zy6FC__D>7> zU&&QyU;DEtBr87Lq4G-34CQ-SpY0X4{y{!Acqo}1lp2V89tR6jAec`>f_<<^uH*BG zhK47peBiYPL0QN0e16lH%6~^k7B9V@;c)J09ggZ0Z`^-~uiD|7FQwnA@?d=Wc)ngl z4S^4q!{S$szf~bV=W_g3h3xmUTojUhJRj*Fs;`CGZ_NzltN0J+o6B#%CfL50>+2sX zzl-IaA=$6aPeSEav%DrGH?h1bB=ZX_T9<|77S68<$$V3KYf(sM`_y>VMn0r?Vf&Qa zV#{oglACRr?NM@*EwlbgUPp46zr^|Z!ay5611Q|Z^BZ&9B1#}?*qQ%DZ5ygnr7 zvAjmlXTsXk=bN-XsN#dy2XCRG?I~-+!i3u6{YhUB#mE7a!>xw4j`ivDV-KGn`vTrQ z>#yRWDx{x^w_$Srq~e|HWBvGzti1lSPU)A}Q9s_F^yBr*yHpH^vFxTu+#N!|eV?`V8ZlnlGr&Qfd``()DS5`tC^| zCb@MD`d9tR)!d$2w@3RgRQcBikGG91SB2&)Tey5$sJu_*>+)gxQao}0r}@2?`oEe< zkjiVRG`$yaSV-k?x;VbLJ?4uzx}1u2$maE8ZjbVwS9v6tvp$(Cg7Nqy%d0}N>YvL( zvf{UYNWMkcACl)${jHdfcC?4jS7`k6K1cR&HG$Wbi!MU?zsL2V{_y>WThz0ek(y#} zJ3SjqPMjBH+BJ7^P(VN2#hKk=l8)s1anW6eTT;H0bg8DYGy#4B+Ki*?`*=cru zu)X>#n<*WK)O%P^UI~0YhdfdytVKU=u|v}M}=dOc&!jE?-R3dt&7tMYU{oo_P#t3vgqv%D-M zb6~bsL2j_ev!K28E7x-S`JsGOU$DPwzT9X}kB`H2`4AsIHkEQn#I=AEXIeLkokKSueA5y-tiC#x?6veJ5@b^bmY+RU4aM>F8`Q>(OdjjuLe#f}cv8~##x920v5TCj~S3#Ej$;Xu+e(i30^90?E!R_C7 z8@ELF@KDB(V5_640=h*nIWS=&{h%8e-n^cQg|6RH2c+{=emsTW%|$oY<1<1cuAi0e(>K+6I;KS3aBrKvxrbNj2V2=+JbPpZFqxV*l;&+}zp zEgd;@{I9u8uWyn6sXy8Mo9dwQT25{Cb&#`Ly&dF*t)4KsX5(^JV=*O#)5*s-Ri8ak zZJln;SHs3J=KKKH-^ZM<4#R)F+}M694;!zU^RqesA#?sh&Tla1FX#Nl=KQssKi!rgtP+HMA@wE5)ip zvNE(NBrj*h^F#6$KK~vzKe!&Tkn`WVRL3t}f8*;xn?iCnm+v3ie=W=Iki3xPouTro zy=5UemGi^uPh>f)es#SsKU97>=dTILsciqMkgWI!tA7jUSB3Jo@c!JPA- z8OqOQIV~hN#*sWMB$ugpDWJIA7Yora0R^ zUusxRZ3pJ^etKN#Se~k)>9H#R91pGyTgX>|<;L=sFIc|)IW8aI^5*(2%Y*vc+droA ziL6g*Sp5`lYCNO$zy5sg#PP@j!OlyA;}?&Aw*43L_}1svsdPRujAk$##8h4#|KFlK zEhlrlZ3@Y&xxV!wc`nP#P`|!jkrv8tQsplR*6(MzO6Q01muFv(qx0Lm=g2fJ7WnH$ zA*xj$BmJ5vL+RhlnfiRwy|g{#yOp#!!TxFeEgkVghlrJQFkY?Z0JigjZQF%oXdYk} zwvW9r^>nnQw}8+Q%0p*oM?K7kqpWgTW6tK&%E{{RShtCfxl|J$Tw_MRT$4`GX1`?L zPFHs_f9}7`V^z~z(exP_2w51Y>L@kJf~ zGA}b6f2l>DPuujDzMu}lw=m=O^%y_>4BSk~>L~oqGxkwfxd#66P?1(QE#j*9sb9H{ z<+m;k&IeR}(VM}16+iB!!FW^o>tELS9p-=ab7*>HKUo%j|J>i`;p2`Iq2SEF!}*}S z1fcJSoBW7A59}9iL@*|T^9g&sf+ioq^{(aAoOT-XSx)v*Jo5V6!ITr?LffzNlUvs< zp?J0Vr?R!GEaLYqie+8iYuA_9y84pv>qV)p-dA``(bq%$x_n1|Xw~ty+qe}z-_0hU z52YJ23pl81i$a8zFgFh z@(|}X^tqQHa8VLZpto+|xTddT1vcN;-p+ef(Ji%omQe{ihkDtWt@lx02L}b)yCA&1 zQ}~Gw^<`1r-gLXYsoY+PYVTYA5Ep&hh#nsg9N2evyKesVz*IC(kG}LtQMFQCVLA*C z*MTpD693;FAL2iT_vtSF-5h1i|08R`e*?7z{`q$Y{?nx zyi^LEHqnD#Uhc2FV4>~%WMYW#R{Gs-_N_|R_D%XV_^zhrQ># z(C3kRq0e{Jzo8HR-jhCGg^dUQ?#}U`Dhd7%>T_k7KKy%6`dk*KPfd5~vz^}NpyKhm zd%#bhFn##XQ$B+Sfpz8^gv+{=FxC8Yms(_w`(+yZY-UdX0qQr!1(? zkT8At_n!1g3Daj_cj}V{eLnsb__-%N6wmy7Px{EP#=T&BDDS<_qdGc%~qF=6`f z?>*@=JWQV>yHg*2=eF|Kb9aNE$Hs;D;om>dhhA|xoZeNZp0%gxUmuGF`quRS^=h4? z_?U7gwLu5;58tM<5ZdVH`}C7~IK6du11GAc+EcFRMM~Klk9XAG+mst@ZygJfw727z zs=aHtYP-D^^wVzdd`{HuCH{ zcjtDrZwxnq_L5nMq`h8tdtba2Y_FAm+U>oa`Y z!SbxT=zMQBMU!VohC0&IL!B$)-r?}Br?-CilG{@u*OjuPttg*#_^I~tQ; zxAPZiT%++Ljl-Jl{J9$M(D#&d{N_SUGF&>2W#B<2fN;8&F5Vj&(V0W#`iYcv19(-x_pzaH&x5KHU2}?m#5_qMf$;7PS$vp#zc*6y8bt{T(0qAjRQ2^ zpz$`1uw(5zw%*rhd`0JPgRJXs(sHKGFW2%6jb5FersYJ9{WT8Nh;}FF@_*L+ZPEC! z#;Y`5sqKoV*a`NXKGs`nM|JbqKk(?5g$9czhkVkn(@xu4ye7oS} ze7nHpd^;m=QDJEzRi0Nk|AOKodcRiQ$lSuxV!}}x(-h8`l9@l(lU>4f^JdGrW%~Cl zzSuyzX0SYY+ARLBeor~~bfr6?cS^?sJ?Ut0zD!hcFVqRsXHT|r=|6AY zRXvpdo3FP0k#|sxk~Ke`;-&M8%d1MtN>ypgT5BkudgGq!Ik&LLQ&>?^c#)^H_(IR3 z;?nc@3wf4RSyfR|dY-4Mtjx2htn@r<_yW6ub^k{_^9w7hv>KNGraiZpce9=D7xql> z*n7Tb$X|Giyqk7mQ_)_ zxNJ#rLE)lBb_!LsxTLa@0>$2uj4BW9RZw1NyWJ`&T~fHHq{yTFUtU&OqM~bwvLX}@ z)@`=^ZMWL?H*54?Y0FJ_+6aAY9N@EYkjB9p57y|>c&Nsq8V}PrOyh8khig1SW2(j@ zH6EpLgvO&a9;5MCjmK&9+UVD4jnuM6Ym}BXTBEhB(Mr>@Mr(|gHCkh}tkD{$WsO$4 zEnCS^Vxlf@LQChHu#H}?#G6Dr^~d`c@dWJevvmbV%cCqGDW8(JeI-Zg8T2dkH=v0( z@E0jc-XMcN8F%}NAG8xKPfmP+w`kGCr{v4To8-|+hW*`z;BlzW{so?Zh^wxmeMgp$YZ;w@G4 z0G@#0Nn)gY$#Rmv&hlmA2kCp8r)c?-{6UudKsNDY;>#3g;KPJnK+Oepz+48A0SFJQF&Qt~Ec$%~Yocmi*dCzC9B`(NV;=kF#2UqBO2NSAy`S@MT? zGNDs^A5;}4)-ixs=Kz}cLpt~af-e(F-XKfrei4pPfJ6M!!aDf|fN}6Sb_-I$p~ftrN7Y(K^wVE%-fBl<@%>{)GR5(*Lp_ zLAJC$ks`+LeTCLu-q3G>Lp2}3Nd1Fy@Cy+BkqAElrN5*Me>q{Kenh#>`mwv+lB#)^ zlN}AAlx*z%S9F$zSjb`~j5x2D0>v zNtXVRyhkf{)<4nurL*%A=`WKk{RY3l4?rhBMC%XuN%{qHr2de8kg`+1iPRq^KbZQ5 ziEmT7^aErkf50yi;SUpne-lRQ2Pc0?zd(-GKkx$(`~yw=Mw<@)ft~dO{L)oFz#oyK z$sgd|giiiIKF+6r@COk7=qgITNq?C9;_+E(1Hum`jMhJq`T^xk{y=(X{ekmbAp8M@ zKO#kwzf68G`2&8D2!EJR`Uie7jZ-omd`F7lU!ut$NJl>adVMO~EcgKkKS+$!AF`bE z5Bvf@0448IHt~;ilOH1WN2L7Aa?%fw!Mh2iAENb#^bcgoKV*|1O#U$W1$>*($q(>H zq$v9fWa$UU;9sK24@d|9645Utf^QRocN0o~K!!gg!XH4{Kcp=EBmE*}Cx3u<6M}ye zM(PjAzm%mPOtSRLPkyk}CjB5~^atEGXToUxBK;v{=^yk56FT(|^b?>{|8VLr(e@8h zKR~+VUCQtS?oR~b{zah4KS=K`KS}>UHu(qX(fX;g{(}FyIeevOvTmVFxi8k;m)89HC1Ut^O-YqFhhA#Ni@8Q+jiaf~RXvqXnE%BJ{`=`tQ5o8qCf{*Tt*PUWq= zym>X>a-InP<2cq?lyLwVae#3EI8^H+QTp8^W847BIFK^pK%yxwOzEb0aq=(nt-ZX# z&p?yE;rD3K6d%&xCcne~==V-2{U52nrT-y!*8e8Io6_NLV0XK{v-4UR4<=d01^f;_ z1D*UFt-lcu((jPre<1uSQTkcRrv8m|lV4>z{M%h937KRvk9f2o#f8?-)76gb9zC9e}N|dMVk))0z2zx_#ORQ zqLcsO=V(#-AF}kjl;L-Y(*I6=Hsw3{AAZJq1rYrk2!D4KWn4&qoBW>Yvo!_6&nA?9 zhwRbyNbD|uJNezo&(iNwHu)LpPX5L^6%hUg!rzgiDIQFIHu)QVmk58GQ2HN!9%}IO z-txQb*HVtw@9<}&DE*$M?T4R%@Uz57{VmH$|HJR_Gf?_f$|nCJ-Q;KKZ}?rJ^t(xx z;{f`zMCo5COMjc>MC~UhlyLxm!_PqY87TeiBuoE7Hu>4)Z3=Cl>i=l{48Oy_65(fwo%OTH-|#>DEfM|(%Kj~7>3`{WDLeTael?-V-$;j_ zfzrQ_rJo_2`Zv<0-{Dt@(!Wxcem2R{|M06s_*J6xFJ$-^DE%tsAG@F7Z{WW2Gu~Tb z!f5?2{VnB4{f;<*zk%?#37!1z6bI4vZ}eLygkL4X&n85_2BIIE(A2;2{uvbvMEflCQlbcb6Yb zJYjy+-8`Ybea#pA&{^KV6A=6WVOMt}c#;Tv_7?uH@FaPQR*sh6NO?mX0TD;Q?v5kW z7p?uBE#FeZGVul8x{8wT&dN^y=_*f>SM*EpBeAQzM7w`y zd6WA|9wl#5mb^&Wi6`(Dt-X7@yyQj7dyA)`KASNUdUQTeu2bx78U6w~`N4@dCmv-y zLGJ3hQnc$yk@_Q2zkokrv^;`W$)A)_|4-{rcE2*LKkfDRFWIgC^!Go_0QyaLq3mz+ z_}Ez)<6L*+f0}=^kK+`^Cy7qup;NxeztUe$a-{z2EcnI5S>P)_;*vdJGvx5O6h zeI?*+Z(*eUBGPiwUy^qzgWqTo{7VGyCWJfS2O#=IccaH=w`jsh{TeC%C@1|8tt|Zn z+2kkb2goKrARYWm>~23e)o1dLOpnxGCa+Q^nL9I_yY)kr1~tibr;HY>aNPs#)b3~{9!`)0~oDe5ceho-zJ0~{?qq4 z<33kY960$gT0f!xM(Z#55&i;pm)}s|PuCypap6C7{b8@af62D)r@#N}3`9FFnC3mv zrpx1jY5b2i9mk7E@qgXFun)&4({V^1XQGvH+>wamg^V*(yvVpP$$L94B8?xE1JD0y zgr9*Xe-HK9nnsJJxPaf`Zzq)TV3K8=nBpPQ^l0M-<>BW@(Pnq={WZ-;;rFg$q;Z3I zfd5Ts@~>@({0x6Pp(zeb;{f8r)bE|rqxC<^M;Zt4XS5ip=iy%< z{0r==Kb`i6zaz!H<$uJ16LvNpq`%RxBSk0wJNX;_1;W2TCqM1$e5ZJjae#hr>gO`u z$37_>3_kb2^t+Uu{BH6y(oOzGI{XZTUnNRELzaG)vh+LrDpC4Z%8~kGZT{_uuR*j5h9M97w-QxvTvf{niQLSBdbm3A@Yhc+V0L z?^Ob(X+N7#zF$pVZegccP64#7DGoF;w%1aUSRt7s&6fxIjHW^6Sr* zQ(QZZUusa=2Y;lVCK=4$u&r{ooS1D*7g`A&L64=0RPPv{GjdYNRCz8;?)2~G$* zO!|UfAoMe#Nl&CpeTQm2B}%=eo>E`LEfD$wP5L4o`k64&<0$l&`a&-gLQjd%PhzBc zLT?kl&=Uw=B}%=eo{%He6Y~=w^aMt#uSxIj(ii$kbkbAC0rUh$%dZo!sk$ElP5O3L z@2=_#{V|^fLSNv%)*Eqx_yxk=ANl;#@6~?S=P7=iujum_Kh9_Ld5<6GKWTbD#3>N* zY2pQb1e(SnQ@R}gBbBW#Zm>@x{0xlLZrCr;WVb2ZWPfMP9qP09lnA@wUz5Gkp3cf9 zdy(E%yD^TL5cWIS4S$;u;{ni#57;NsWUo`Y91o=o`=!0ozb5-k>9AL#$$n`sWGDNO zFYPkPCVOREch-Jsuasdw>;u9MCp6h5)1B;uy(WbH5~aVTeUM=%5Ozv5*_-OK)aHb+ z+hngt*8?=!i*(p4?Su^bB*IRhw9_PG9wSlOE$x){4%K!7VJ{H&Np!MT+70`FCc9*M zr0bTjTiOe|Ob9zA!d}=3gk2K5>Q`weWRrhkClG#>DD9SZLWaFS*elV=PAC7$>mINZ z7^%G`ySvL?_}9r^%V*h}KxrprCp%>v!%kqd{)In*(w`Lv7d`m>!Nkkk*i>7%}r0G@{H}E47{Th5l z8o%IAqT~^>aVz9389AWNPgOTMIR ziaQfOGTp?Nj5DWr15XkqUs5*l1zseA7ZXZ;oMg#|Np|81d`XnNMH+8TJc2igh%<>! zJUa2@_1Tf;gy7G_SE{ZD2)@9RMDXR&`$?2MK{myiFYqG~ z`~W3Sk}t#;Q1XWOl4#-&JOZ6~Gw}yL(GR2L3%ubv9I&%|nRx3izD)53et_UbB6tEu z%NO{O2)+~5aYe3sgPCcZk$8+ZbC7hfjcz!T7k zuSnwy>kB~e1>D!Xfyd5{W8e>ncmskziB5bX|3|+5q+3~9!?1TfKKgpHs7SLlYTNE zdUh3^?2!6OJ)GpO>Sx;DNpB~;_Evth`eGj9gmNAnt!%Pa>TBZFNk3D5w0fd^XZ1Dd z(cS6L6Daj{;??7`dlpd6x1yCX{{l*Tp(oHuU+ABz^#n$GUNcnKXR;Ie0-f|W>5u(? z?}shM_28JJx(a?lyYb7#eOEck&bwf_C^}dPWB;R+7+z~dz=t< zNrat1X)k1x-I2~WU~i=8#KYdU7j^^pmYtFhlig1CA|L*RKY_5r2|YgB)4)jWjrBy8&IBCMJwYvB~bDr?e46-(fZe`?F8uRuH z+6_5c`{94s4}`zbAO6SBKf8SYtqS^|{x~v#_;NzTpG3>2n{YtW@y1cXA4!3s+CGW0 zpQC@G9|N8EmH8%~O*|s~KTQ{o3nm0#5;0DKKPQyDO1`9Qic|0wEq0Y>r{ga8bBZ_c zCQ{mI&hiM}B%0z&rh_k_6K_uZ$b89@Nd{lwNur4-@P;^(Xo@q*my{(>kiiqs z#FyluyJYa!RlX!|o#n}ixBnDR;2S(iM0_ExfZ#=9cf}R>LOcP%6Hvw#?k97?RG+

Eh(@&wt5FH^jMAE4yLB!ed) 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%0DM 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 literal 0 HcmV?d00001 diff --git a/UniTAP/libs/lib_dscl/Dsc.exe b/UniTAP/libs/lib_dscl/Dsc.exe new file mode 100644 index 0000000000000000000000000000000000000000..e6740b41138ae1a473875784caec09b7146d9489 GIT binary patch literal 152280 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 DB6Gc3 literal 0 HcmV?d00001 diff --git a/UniTAP/libs/lib_dscl/__init__.py b/UniTAP/libs/lib_dscl/__init__.py new file mode 100644 index 0000000..e1ef127 --- /dev/null +++ b/UniTAP/libs/lib_dscl/__init__.py @@ -0,0 +1,3 @@ +from .dsc_info import DscParameters +from .dscl_utils import (dscl_cf_from_dsc_cf, dscl_cf_to_dsc_cf, image_from_dsc_vf, + dscl_image_calculate_crc, calculate_slice_size) diff --git a/UniTAP/libs/lib_dscl/dsc_info.py b/UniTAP/libs/lib_dscl/dsc_info.py new file mode 100644 index 0000000..68c73c1 --- /dev/null +++ b/UniTAP/libs/lib_dscl/dsc_info.py @@ -0,0 +1,156 @@ +from UniTAP.common.color_info import ColorInfo + + +class DscParameters: + """ + Class `DscParameters` describes all parameters necessary for formatting input image to DSC image. + - Width of image `width`. + - Height of image `height`. + - Color format of image `color_format` type `ColorInfo.ColorFormat`. + - BPC `bpc`. + - BPP `bpp`. + - Flag of is block prediction enabled `is_block_prediction_enabled`. + - Horizontal slice number `horizontal_slice_number`. + - Number of buffer bit depth `buffer_bit_depth`. + - Vertical slice number `vertical_slice_number`. + - Vertical slice size `vertical_slice_size`. + - DSC major version `version_major`. + - DSC minor version `version_minor`. + """ + def __init__(self, width: int = 1920, height: int = 1080, + color_format: ColorInfo.ColorFormat = ColorInfo.ColorFormat.CF_RGB, bpc: int = 8, bpp: int = 128, + is_block_prediction_enabled: bool = False, horizontal_slice_number: int = 8, buffer_bit_depth: int = 8, + vertical_slice_number: int = 8, vertical_slice_size: int = 1, version_major: int = 1, + version_minor: int = 2): + self.__width = width + self.__height = height + self.__color_format = color_format + self.__bpc = bpc + self.__bpp = bpp + self.__is_block_prediction_enabled = is_block_prediction_enabled + self.__horizontal_slice_number = horizontal_slice_number + self.__buffer_bit_depth = buffer_bit_depth + self.__vertical_slice_number = vertical_slice_number + self.__vertical_slice_size = vertical_slice_size + self.__version_major = version_major + self.__version_minor = version_minor + + @property + def width(self) -> int: + return self.__width + + @width.setter + def width(self, width: int = 1920): + if width <= 0: + raise ValueError(f"Incorrect width of image. Must be more, than 0. Current value: {width}") + self.__width = width + + @property + def height(self) -> int: + return self.__height + + @height.setter + def height(self, height: int = 1080): + if height <= 0: + raise ValueError(f"Incorrect height of image. Must be more, than 0. Current value: {height}") + self.__height = height + + @property + def color_format(self) -> ColorInfo.ColorFormat: + return self.__color_format + + @color_format.setter + def color_format(self, color_format: ColorInfo.ColorFormat.CF_RGB): + self.__color_format = color_format + + @property + def bpc(self) -> int: + return self.__bpc + + @bpc.setter + def bpc(self, bpc: int = 8): + if bpc <= 5: + raise ValueError(f"Incorrect bpc of image. Must be more, than 5. Current value: {bpc}") + self.__bpc = bpc + + @property + def bpp(self) -> int: + return self.__bpp + + @bpp.setter + def bpp(self, bpp: int = 10): + if bpp <= 0: + raise ValueError(f"Incorrect bpp of image. Must be more, than 0. Current value: {bpp}") + self.__bpp = bpp + + @property + def is_block_prediction_enabled(self) -> bool: + return self.__is_block_prediction_enabled + + @width.setter + def width(self, is_block_prediction_enabled: bool = False): + self.__is_block_prediction_enabled = is_block_prediction_enabled + + @property + def horizontal_slice_number(self) -> int: + return self.__horizontal_slice_number + + @horizontal_slice_number.setter + def horizontal_slice_number(self, horizontal_slice_number: int = 1920): + if horizontal_slice_number <= 0: + raise ValueError(f"Incorrect horizontal_slice_number of image. Must be more, than 0. " + f"Current value: {horizontal_slice_number}") + self.__horizontal_slice_number = horizontal_slice_number + + @property + def buffer_bit_depth(self) -> int: + return self.__buffer_bit_depth + + @buffer_bit_depth.setter + def buffer_bit_depth(self, buffer_bit_depth: int = 8): + if buffer_bit_depth <= 0: + raise ValueError(f"Incorrect buffer_bit_depth of image. Must be more, than 0. " + f"Current value: {buffer_bit_depth}") + self.__buffer_bit_depth = buffer_bit_depth + + @property + def vertical_slice_number(self) -> int: + return self.__vertical_slice_number + + @vertical_slice_number.setter + def vertical_slice_number(self, vertical_slice_number: int = 8): + if vertical_slice_number <= 0: + raise ValueError(f"Incorrect vertical_slice_number of image. Must be more, than 0. " + f"Current value: {vertical_slice_number}") + self.__vertical_slice_number = vertical_slice_number + + @property + def vertical_slice_size(self) -> int: + return self.__vertical_slice_size + + @vertical_slice_size.setter + def vertical_slice_size(self, vertical_slice_size: int = 1): + if vertical_slice_size <= 0: + raise ValueError(f"Incorrect vertical_slice_size of image. Must be more, than 0. " + f"Current value: {vertical_slice_size}") + self.__vertical_slice_size = vertical_slice_size + + @property + def version_major(self) -> int: + return self.__version_major + + @version_major.setter + def version_major(self, version_major: int = 1): + if version_major <= 0: + raise ValueError(f"Incorrect version_major of image. Must be more, than 0. Current value: {version_major}") + self.__version_major = version_major + + @property + def version_minor(self) -> int: + return self.__version_minor + + @version_minor.setter + def version_minor(self, version_minor: int = 2): + if version_minor <= 0: + raise ValueError(f"Incorrect version_minor of image. Must be more, than 0. Current value: {version_minor}") + self.__version_minor = version_minor diff --git a/UniTAP/libs/lib_dscl/dscl.py b/UniTAP/libs/lib_dscl/dscl.py new file mode 100644 index 0000000..a57d428 --- /dev/null +++ b/UniTAP/libs/lib_dscl/dscl.py @@ -0,0 +1,194 @@ +import os +from typing import Optional + +from .dscl_types import * +from UniTAP.libs.lib_uicl.uicl_types import UICL_Image, UICL_ImageParameters +from UniTAP.libs.lib_helper import OS_Requirements, lib_method_wrapper + +DSCL_CURRENT_VERSION = 1 +DSCL = OS_Requirements("DSCL").get_lib() +DSCL_PATH = OS_Requirements("DSCL").get_path_to_lib() +DSC_TOOLS_FOLDER = os.path.dirname(DSCL_PATH) + + +def from_cstr(src_str): + try: + return src_str.value.decode('cp1252') + except BaseException: + return src_str.value.decode('utf-8') + + +class DSCLError(Exception): + def __init__(self, message, errors=None): + super().__init__(message) + self.errors = errors + + +def DSCL_GetRequiredBufferSize(image_to_encode: DSCL_Image): + _DSCL_GetRequiredBufferSize = lib_method_wrapper(DSCL.DSCL_GetRequiredBufferSize, + [POINTER(DSCL_Image)], + c_uint64) + + return _DSCL_GetRequiredBufferSize(byref(image_to_encode)) + + +def DSCL_GetRequiredBufferSizeFromPPS(pps: DSCL_PPS): + _DSCL_GetRequiredBufferSizeFromPPS = lib_method_wrapper(DSCL.DSCL_GetRequiredBufferSizeFromPPS, + [POINTER(DSCL_PPS)], + c_uint64) + + return _DSCL_GetRequiredBufferSizeFromPPS(byref(pps)) + + +def DSCL_Encode(image: UICL_Image, pps: DSCL_PPS) -> DSCL_Image: + _DSCL_Encode = lib_method_wrapper(DSCL.DSCL_Encode, + [POINTER(UICL_Image), POINTER(DSCL_PPS), POINTER(DSCL_Image), + POINTER(DSCL_EncoderConfig), c_char_p, c_int], + DSCL_RESULT) + + max_enc_image_size = DSCL_GetRequiredBufferSizeFromPPS(pps) + + if max_enc_image_size <= 132: + raise DSCLError("Failed to calculate buffer size for DSC encoding!") + + encoded_image = DSCL_Image() + encoded_image.DataPtr = (c_uint8 * max_enc_image_size)() + encoded_image.DataSize = max_enc_image_size + + _log_struct = DSCL_EncoderConfig() + _log_string_size = 65536 + _log_string = create_string_buffer(_log_string_size) + + result = _DSCL_Encode(byref(image), byref(pps), byref(encoded_image), byref(_log_struct), + _log_string, _log_string_size) + + log = str(from_cstr(_log_string)) + + if result < DSCL_SUCCESS: + raise DSCLError(f"Encoding finished with error: {result}. Error log: {log}", result) + + return encoded_image + + +def DSCL_Decode(encoded_image: DSCL_Image, decode_simple422_to_444: Optional[bool] = False) -> UICL_Image: + _DSCL_Decode = lib_method_wrapper(DSCL.DSCL_Decode, + [POINTER(DSCL_Image), POINTER(UICL_Image), + POINTER(DSCL_DecoderConfig)], + DSCL_RESULT) + + decoded_image = UICL_Image() + decoded_image.Parameters = UICL_ImageParameters() + decoded_image.DataPtr = POINTER(c_uint8)() + decoded_image.DataSize = 0 + + decoder_config = DSCL_DecoderConfig() + decoder_config.decodeSimple422to444 = False if decode_simple422_to_444 is None else decode_simple422_to_444 + + result = _DSCL_Decode(byref(encoded_image), byref(decoded_image), byref(decoder_config)) + + if result < DSCL_SUCCESS: + raise DSCLError(f"Decoding failed with error code: {result}", result) + + return decoded_image + + +def DSCL_ResultToString(result): + _DSCL_ResultToString = lib_method_wrapper(DSCL.DSCL_ResultToString, + [DSCL_RESULT, c_char_p, c_uint64], + c_uint64) + + _error_string = create_string_buffer(1024) + _DSCL_ResultToString(result, _error_string, 1024) + return from_cstr(_error_string) + + +def DSCL_CalculateDSCCRC(image: DSCL_Image): + _DSCL_CalculateDSCCRC = lib_method_wrapper(DSCL.DSCL_CalculateDSCCRC, + [POINTER(DSCL_Image), POINTER(DSCL_CRC16)], + c_uint64) + crc_value = DSCL_CRC16() + result = _DSCL_CalculateDSCCRC(byref(image), byref(crc_value)) + + if result < DSCL_SUCCESS: + raise DSCLError(f"Calculate DSC CRC: Failed to calculate CRC", result) + + return crc_value + + +def DSCL_ExtractPPSFromData(data): + _DSCL_ExtractPPSFromData = lib_method_wrapper(DSCL.DSCL_ExtractPPSFromData, + [POINTER(c_uint8), c_uint64, POINTER(DSCL_PPS)], + c_uint64) + _pps = DSCL_PPS() + _size = len(data) + _data = (c_uint8 * _size)(*data) + result = _DSCL_ExtractPPSFromData(_data, _size, byref(_pps)) + if result < DSCL_SUCCESS: + raise DSCLError(f"Cannot extract PPS from data. Code: {result}") + + return _pps + + +def DSCL_ExtractPPS(image: DSCL_Image): + _DSCL_ExtractPPS = lib_method_wrapper(DSCL.DSCL_ExtractPPS, + [POINTER(DSCL_Image), POINTER(DSCL_PPS)], + c_uint64) + _pps = DSCL_PPS() + result = _DSCL_ExtractPPS(byref(image), byref(_pps)) + if result < DSCL_SUCCESS: + raise DSCLError(f"Cannot extract PPS from DSCL Image. Code: {result}") + + return _pps + + +def DSCL_VerifyPPS(image: DSCL_Image): + _DSCL_VerifyPPS = lib_method_wrapper(DSCL.DSCL_VerifyPPS, + [POINTER(DSCL_Image), c_char_p, c_uint64], + c_bool) + + _error_string = create_string_buffer(1024) + return _DSCL_VerifyPPS(byref(image), _error_string, 1024) + + +def DSCL_GeneratePPS(decoded_image_parameters: UICL_ImageParameters, + is_simple_422: bool, is_block_prediction_enabled: bool, bits_per_pixel: int, + horizontal_slice_count: int, vertical_slice_count: int, buffer_bit_depth: int, + dsc_version: DSCL_DscVersion) -> DSCL_PPS: + _DSCL_GeneratePPS = lib_method_wrapper(DSCL.DSCL_GeneratePPS, + [POINTER(DSCL_PPS), POINTER(UICL_ImageParameters), c_bool, c_bool, + c_uint16, c_uint16, c_uint16, c_uint8, DSCL_DscVersion], + c_bool) + _pps = DSCL_PPS() + result = _DSCL_GeneratePPS(byref(_pps), byref(decoded_image_parameters), is_simple_422, + is_block_prediction_enabled, bits_per_pixel, horizontal_slice_count, + vertical_slice_count, buffer_bit_depth, dsc_version) + if result < DSCL_SUCCESS: + raise DSCLError(f"Cannot generate PPS. Error code {result}") + + return _pps + + +def DSCL_LoadFromFile(file_name: str) -> DSCL_RESULT: + _DSCL_LoadFromFile = lib_method_wrapper(DSCL.DSCL_LoadFromFile, + [c_char_p, POINTER(DSCL_Image)], + DSCL_Image) + image = DSCL_Image() + image.DataPtr = POINTER(c_uint8)() + image.DataSize = 0 + + result = _DSCL_LoadFromFile(c_char_p(file_name.encode('utf-8')), byref(image)) + + if result < DSCL_SUCCESS: + raise DSCLError(f"Cannot load image from file. Error code {result}.") + + return result + + +def DSCL_FreeImage(image: DSCL_Image) -> DSCL_RESULT: + _DSCL_FreeImage = lib_method_wrapper(DSCL.DSCL_FreeImage, + [POINTER(POINTER(DSCL_Image))], + c_uint64) + + result = _DSCL_FreeImage(byref(byref(image))) + + return result diff --git a/UniTAP/libs/lib_dscl/dscl_types.py b/UniTAP/libs/lib_dscl/dscl_types.py new file mode 100644 index 0000000..593054c --- /dev/null +++ b/UniTAP/libs/lib_dscl/dscl_types.py @@ -0,0 +1,206 @@ +from ctypes import * +from enum import IntEnum + +DSCL_RESULT = c_int32 + +DSCL_SUCCESS = 0 +DSCL_INTERNAL_ERROR = -1 +DSCL_ERROR_NULL_IMAGE = -2 +DSCL_NOT_ENOUGH_SPACE_FOR_ENCODED_IMAGE = -3 +DSCL_COMPRESSION_FAILED = -4 +DSCL_DECOMPRESSION_FAILED = -5 +DSCL_DSC_EXE_NOT_FOUND = -6 +DSCL_DP_CRC_EXE_NOT_FOUND = -7 +DSCL_PB_EXE_NOT_FOUND = -8 +DSCL_CFG_NOT_CREATED = -9 +DSCL_CFG_NOT_FOUND = -10 +DSCL_FAIL_TO_CREATE_TMP_FOLDER = -11 +DSCL_FAIL_TO_CREATE_TMP_FILE = -12 + +RC_BUF_THRESH_SIZE = 14 +RC_RANGE_PARAMETERS_SIZE = 30 +RC_RANGE_PARAMETERS_COUNT = int(RC_RANGE_PARAMETERS_SIZE / 2) + + +class CtypesEnum(IntEnum): + @classmethod + def from_param(cls, obj): + return int(obj) + + +class DSCL_Colorformat(CtypesEnum): + Colorformat_RGB = 0, + Colorformat_YCbCr422 = 1, + Colorformat_YCbCr444 = 2, + Colorformat_YCbCr420 = 3, + Colorformat_Simple422 = 4, + Colorformat_MaxValue = 5 + + +class DSCL_DscVersion(Structure): + _fields_ = [ + ('major', c_uint8), + ('minor', c_uint8), + ] + + +DSCL_ColorformatStr = ["RGB", + "YCbCr 4:2:2", + "YCbCr 4:4:4", + "YCbCr 4:2:0", + "Simple 4:2:2"] + +class DSCL_Image(Structure): + _fields_ = [ + ('DataPtr', POINTER(c_uint8)), + ('DataSize', c_uint64), + ] + + +class DSCL_Compression(Structure): + _fields_ = [ + ('bitsPerPixel', c_uint32), + ('bitsPerComponent', c_uint32), + ('is422', c_bool), + ('is420', c_bool), + ] + + +class DSCL_CRC16(Structure): + _fields_ = [ + ('eng0', c_uint16), + ('eng1', c_uint16), + ('eng2', c_uint16) + ] + + +class DSCL_PPS(Structure): + _fields_ = [ + ('dsc_version_minor', c_uint8, 4), + ('dsc_version_major', c_uint8, 4), + ('pps_identifier', c_uint8, 8), + ('', c_uint8, 8), + ('linebuf_depth', c_uint8, 4), + ('bits_per_component', c_uint8, 4), + ('bits_per_pixel_high', c_uint8, 2), + ('vbr_enable', c_uint8, 1), + ('simple_422', c_uint8, 1), + ('convert_rgb', c_uint8, 1), + ('block_pred_enable', c_uint8, 1), + ('', c_uint8, 2), + ('bits_per_pixel_low', c_uint8, 8), + ('pic_height_high', c_uint8, 8), + ('pic_height_low', c_uint8, 8), + ('pic_width_high', c_uint8, 8), + ('pic_width_low', c_uint8, 8), + ('slice_height_high', c_uint8, 8), + ('slice_height_low', c_uint8, 8), + ('slice_width_high', c_uint8, 8), + ('slice_width_low', c_uint8, 8), + ('chunk_size_high', c_uint8, 8), + ('chunk_size_low', c_uint8, 8), + ('initial_xmit_delay_high', c_uint8, 2), + ('', c_uint8, 6), + ('initial_xmit_delay_low', c_uint8, 8), + ('initial_dec_delay_high', c_uint8, 8), + ('initial_dec_delay_low', c_uint8, 8), + ('', c_uint8, 8), + ('initial_scale_value', c_uint8, 6), + ('', c_uint8, 2), + ('scale_increment_interval_high', c_uint8, 8), + ('scale_increment_interval_low', c_uint8, 8), + ('scale_decrement_interval_high', c_uint8, 4), + ('', c_uint8, 4), + ('scale_decrement_interval_low', c_uint8, 8), + ('', c_uint8, 8), + ('first_line_bpg_offset', c_uint8, 5), + ('', c_uint8, 3), + ('nfl_bpg_offset_high', c_uint8, 8), + ('nfl_bpg_offset_low', c_uint8, 8), + ('slice_bpg_offset_high', c_uint8, 8), + ('slice_bpg_offset_low', c_uint8, 8), + ('initial_offset_high', c_uint8, 8), + ('initial_offset_low', c_uint8, 8), + ('final_offset_high', c_uint8, 8), + ('final_offset_low', c_uint8, 8), + ('flatness_min_qp', c_uint8, 5), + ('', c_uint8, 3), + ('flatness_min_qp', c_uint8, 5), + ('', c_uint8, 3), + ('rc_model_size_high', c_uint8, 8), + ('rc_model_size_low', c_uint8, 8), + ('rc_edge_factor', c_uint8, 4), + ('', c_uint8, 4), + ('rc_quant_incr_limit0', c_uint8, 5), + ('', c_uint8, 3), + ('rc_quant_incr_limit1', c_uint8, 5), + ('', c_uint8, 3), + ('rc_tgt_offset_lo', c_uint8, 4), + ('rc_tgt_offset_hi', c_uint8, 4), + ('rc_buf_thresh', c_uint8 * RC_BUF_THRESH_SIZE), + ('rc_range_parameters', c_uint8 * RC_RANGE_PARAMETERS_SIZE), + ('native_422', c_uint8, 1), + ('native_420', c_uint8, 1), + ('', c_uint8, 6), + ('second_line_bpg_offset', c_uint8, 5), + ('', c_uint8, 3), + ('nsl_bpg_offset_high', c_uint8, 8), + ('nsl_bpg_offset_low', c_uint8, 8), + ('second_line_offset_adj_high', c_uint8, 8), + ('second_line_offset_adj_low', c_uint8, 8), + ('', c_uint8 * 34) + ] + + def width(self) -> int: + return (self.pic_width_high << 8) | self.pic_width_low + + def height(self) -> int: + return (self.pic_height_high << 8) | self.pic_height_low + + def is_yuv(self) -> bool: + return self.convert_rgb == 0 + + def bpp(self) -> int: + if self.native_422 == 1 or self.native_420 == 1: + return int((((self.bits_per_pixel_high & 0x3) << 8) | self.bits_per_pixel_low) / 2) + else: + return ((self.bits_per_pixel_high & 0x3) << 8) | self.bits_per_pixel_low + + def bpc(self) -> int: + return self.bits_per_component & 0x0F + + def is_422(self) -> bool: + return self.native_422 != 0 + + def is_420(self) -> bool: + return self.native_420 != 0 + + def is_simple_422(self) -> bool: + return self.simple_422 != 0 + + def v_slice(self) -> int: + return (self.slice_height_high << 8) | self.slice_height_low + + def h_slice(self) -> int: + return (self.slice_width_high << 8) | self.slice_width_low + + def buffer_bit_depth(self) -> int: + return self.linebuf_depth + + def is_block_prediction_enabled(self) -> bool: + return self.block_pred_enable != 0 + + def dsc_version(self) -> tuple: + return self.dsc_version_major, self.dsc_version_minor + + +class DSCL_EncoderConfig(Structure): + _fields_ = [ + # ('log_buffer', c_char * 2048), + ] + + +class DSCL_DecoderConfig(Structure): + _fields_ = [ + ('decodeSimple422to444', c_bool), + ] diff --git a/UniTAP/libs/lib_dscl/dscl_utils.py b/UniTAP/libs/lib_dscl/dscl_utils.py new file mode 100644 index 0000000..448c077 --- /dev/null +++ b/UniTAP/libs/lib_dscl/dscl_utils.py @@ -0,0 +1,219 @@ +from UniTAP.libs.lib_dscl.dscl_types import * +from UniTAP.libs.lib_dscl.dscl import (DSCL_Encode, DSCL_Decode, DSCL_CalculateDSCCRC, DSCL_ExtractPPSFromData, + DSCL_GeneratePPS) +from UniTAP.common import VideoFrameDSC, CompressionInfo +from UniTAP.libs.lib_uicl.uicl_utils import * + + +def dscl_cf_from_dsc_cf(color_format: CompressionInfo.DscColorFormat) -> DSCL_Colorformat: + assert isinstance(color_format, CompressionInfo.DscColorFormat) + if color_format == CompressionInfo.DscColorFormat.CF_RGB: + return DSCL_Colorformat.Colorformat_RGB + elif color_format == CompressionInfo.DscColorFormat.CF_YCbCr_444: + return DSCL_Colorformat.Colorformat_YCbCr444 + elif color_format == CompressionInfo.DscColorFormat.CF_YCbCr_422: + return DSCL_Colorformat.Colorformat_YCbCr422 + elif color_format == CompressionInfo.DscColorFormat.CF_YCbCr_420: + return DSCL_Colorformat.Colorformat_YCbCr420 + elif color_format == CompressionInfo.DscColorFormat.CF_Simple_422: + return DSCL_Colorformat.Colorformat_Simple422 + else: + return DSCL_Colorformat.Colorformat_MaxValue + + +def dscl_cf_to_dsc_cf(color_format: DSCL_Colorformat) -> CompressionInfo.DscColorFormat: + if color_format == DSCL_Colorformat.Colorformat_RGB: + return CompressionInfo.DscColorFormat.CF_RGB + elif color_format == DSCL_Colorformat.Colorformat_YCbCr444: + return CompressionInfo.DscColorFormat.CF_YCbCr_444 + elif color_format == DSCL_Colorformat.Colorformat_YCbCr422: + return CompressionInfo.DscColorFormat.CF_YCbCr_422 + elif color_format == DSCL_Colorformat.Colorformat_YCbCr420: + return CompressionInfo.DscColorFormat.CF_YCbCr_420 + elif color_format == DSCL_Colorformat.Colorformat_Simple422: + return CompressionInfo.DscColorFormat.CF_Simple_422 + else: + return CompressionInfo.DscColorFormat.CF_NONE + + +def vm_cf_to_dscl_cf(color_format: ColorInfo.ColorFormat) -> DSCL_Colorformat: + if color_format == ColorInfo.ColorFormat.CF_RGB: + return DSCL_Colorformat.Colorformat_RGB + elif color_format == ColorInfo.ColorFormat.CF_YCbCr_444: + return DSCL_Colorformat.Colorformat_YCbCr444 + elif color_format == ColorInfo.ColorFormat.CF_YCbCr_422: + return DSCL_Colorformat.Colorformat_YCbCr422 + elif color_format == ColorInfo.ColorFormat.CF_YCbCr_420: + return DSCL_Colorformat.Colorformat_YCbCr420 + else: + return DSCL_Colorformat.Colorformat_MaxValue + + +def vm_cf_to_dsc_cf(color_format: ColorInfo.ColorFormat) -> CompressionInfo.DscColorFormat: + if color_format == ColorInfo.ColorFormat.CF_RGB: + return CompressionInfo.DscColorFormat.CF_RGB + elif color_format == ColorInfo.ColorFormat.CF_YCbCr_444: + return CompressionInfo.DscColorFormat.CF_YCbCr_444 + elif color_format == ColorInfo.ColorFormat.CF_YCbCr_422: + return CompressionInfo.DscColorFormat.CF_YCbCr_422 + elif color_format == ColorInfo.ColorFormat.CF_YCbCr_420: + return CompressionInfo.DscColorFormat.CF_YCbCr_420 + else: + return CompressionInfo.DscColorFormat.CF_NONE + + +def dscl_pps_cf_to_compression_info_cf(dscl_pps: DSCL_PPS) -> CompressionInfo.DscColorFormat: + if dscl_pps.convert_rgb: + return CompressionInfo.DscColorFormat.CF_RGB + elif dscl_pps.native_422: + return CompressionInfo.DscColorFormat.CF_YCbCr_422 + elif dscl_pps.native_420: + return CompressionInfo.DscColorFormat.CF_YCbCr_420 + elif dscl_pps.simple_422: + return CompressionInfo.DscColorFormat.CF_Simple_422 + else: + return CompressionInfo.DscColorFormat.CF_YCbCr_444 + + +def is_simple_as_444(dscl_pps: DSCL_PPS) -> bool: + return dscl_pps_cf_to_compression_info_cf(dscl_pps) == CompressionInfo.DscColorFormat.CF_YCbCr_444 + + +def dscl_pps_to_compression_info(dscl_pps: DSCL_PPS) -> CompressionInfo: + compression_info = CompressionInfo() + + compression_info.color_format = dscl_pps_cf_to_compression_info_cf(dscl_pps) + compression_info.bpp = ((dscl_pps.bits_per_pixel_high << 8) | dscl_pps.bits_per_pixel_low) + compression_info.is_block_prediction_enabled = dscl_pps.block_pred_enable + compression_info.h_slice_size = ((dscl_pps.slice_height_high << 8) | dscl_pps.slice_height_low) + compression_info.v_slice_size = ((dscl_pps.slice_width_high << 8) | dscl_pps.slice_width_low) + compression_info.buffer_bit_depth = dscl_pps.linebuf_depth + compression_info.version = (dscl_pps.dsc_version_major, dscl_pps.dsc_version_minor) + compression_info.is_simple_as_444 = is_simple_as_444(dscl_pps) + + return compression_info + + +def image_from_dsc_vf(video_frame: VideoFrameDSC) -> DSCL_Image: + image = DSCL_Image() + image.DataSize = len(video_frame.data) + + if isinstance(video_frame.data, bytearray): + image.DataPtr = (c_uint8 * image.DataSize)(*video_frame.data) + elif isinstance(video_frame.data, bytes): + image.DataPtr = (c_uint8 * image.DataSize)(*bytearray(video_frame.data)) + else: + image.DataPtr = video_frame.data + + return image + + +def dscl_image_from_vf(video_frame: VideoFrame, parameters: CompressionInfo) -> DSCL_Image: + image = DSCL_Image() + image.DataSize = len(video_frame.data) + + if isinstance(video_frame.data, bytearray): + image.DataPtr = (c_uint8 * image.DataSize).from_buffer(video_frame.data) + elif isinstance(video_frame.data, bytes): + image.DataPtr = (c_uint8 * image.DataSize).from_buffer(bytearray(video_frame.data)) + else: + image.DataPtr = video_frame.data + + return image + + +def dscl_image_to_dsc_vf(image: DSCL_Image, width: int = 0, height: int = 0, bpc: int = 0) -> VideoFrameDSC: + video_frame = VideoFrameDSC() + + video_frame.data = bytearray(image.DataPtr[:image.DataSize]) + video_frame.width = width + video_frame.height = height + video_frame.color_info.bpc = bpc + video_frame.color_info.color_format = ColorInfo.ColorFormat.CF_DSC + + return video_frame + + +def encode_vf(video_frame: VideoFrame, parameters: CompressionInfo) -> VideoFrameDSC: + if video_frame.color_info.color_format == ColorInfo.ColorFormat.CF_DSC: + raise AssertionError(f"Image must not have color format DSC. Current format: " + f"{video_frame.color_info.color_format.name}") + + uicl_image = image_from_vf(video_frame) + + width, height, color_info, data_info = image_params_to_size_and_ci(uicl_image.Parameters) + horizontal_slice_count = calculate_slice_number(width, parameters.h_slice_size, color_info.color_format) + vertical_slice_count = calculate_slice_number(height, parameters.v_slice_size, color_info.color_format) + dsc_version = DSCL_DscVersion() + dsc_version.major = parameters.version[0] + dsc_version.minor = parameters.version[1] + target_pps = DSCL_GeneratePPS(decoded_image_parameters=uicl_image.Parameters, + is_simple_422=parameters.color_format == CompressionInfo.DscColorFormat.CF_Simple_422, + is_block_prediction_enabled=parameters.is_block_prediction_enabled, + bits_per_pixel=parameters.bpp, + horizontal_slice_count=horizontal_slice_count, + vertical_slice_count=vertical_slice_count, + buffer_bit_depth=parameters.buffer_bit_depth, + dsc_version=dsc_version) + encoded_image = DSCL_Encode(uicl_image, target_pps) + encoded_vf = dscl_image_to_dsc_vf(encoded_image, width, height, color_info.bpc) + encoded_vf.compression_info = parameters + + return encoded_vf + + +def decode_vf(video_frame: VideoFrameDSC) -> VideoFrame: + if bytearray("DSCF".encode()) not in video_frame.data: + video_frame.data = bytearray("DSCF".encode()) + video_frame.data + encoded_image = image_from_dsc_vf(video_frame) + decoded_image = DSCL_Decode(encoded_image, decode_simple422_to_444=video_frame.compression_info.is_simple_as_444) + decoded_vf = image_to_vf(decoded_image) + + return decoded_vf + + +def dscl_image_calculate_crc(video_frame: VideoFrameDSC): + image = DSCL_Image() + image.DataSize = len(video_frame.data) + + if isinstance(video_frame.data, bytearray): + image.DataPtr = (c_uint8 * image.DataSize)(*video_frame.data) + elif isinstance(video_frame.data, bytes): + image.DataPtr = (c_uint8 * image.DataSize)(*bytearray(video_frame.data)) + else: + image.DataPtr = video_frame.data + crc = DSCL_CalculateDSCCRC(image) + return crc.eng0, crc.eng1, crc.eng2 + + +def dscl_data_processing(data: bytearray) -> VideoFrameDSC: + vf = VideoFrameDSC() + pps_data = DSCL_ExtractPPSFromData(data) + vf.compression_info = dscl_pps_to_compression_info(pps_data) + vf.height = ((pps_data.pic_height_high << 8) | pps_data.pic_height_low) + vf.width = ((pps_data.pic_width_high << 8) | pps_data.pic_width_low) + vf.color_info.bpc = pps_data.bits_per_component + vf.color_info.color_format = ColorInfo.ColorFormat.CF_DSC + vf.data = data + return vf + + +def calculate_slice_size(value: int, slice_number: int, + color_format: ColorInfo.ColorFormat = ColorInfo.ColorFormat.CF_RGB): + slice_size = value / slice_number + (1 if value % slice_number else 0) + + if color_format in [ColorInfo.ColorFormat.CF_YCbCr_422, ColorInfo.ColorFormat.CF_YCbCr_420, + ColorInfo.ColorFormat.CF_Y_ONLY]: + slice_size += (1 if slice_size % 2 else 0) + + return slice_size + + +def calculate_slice_number(value: int, slice_size: int, + color_format: ColorInfo.ColorFormat = ColorInfo.ColorFormat.CF_RGB): + if color_format in [ColorInfo.ColorFormat.CF_YCbCr_422, ColorInfo.ColorFormat.CF_YCbCr_420, + ColorInfo.ColorFormat.CF_Y_ONLY]: + slice_size -= (1 if slice_size % 2 else 0) + + slice_number = (value + slice_size - 1) // slice_size + return slice_number diff --git a/UniTAP/libs/lib_helper/__init__.py b/UniTAP/libs/lib_helper/__init__.py new file mode 100644 index 0000000..b329354 --- /dev/null +++ b/UniTAP/libs/lib_helper/__init__.py @@ -0,0 +1 @@ +from .lib_os_helpers import OS_Requirements, lib_method_wrapper \ No newline at end of file diff --git a/UniTAP/libs/lib_helper/lib_os_helpers.py b/UniTAP/libs/lib_helper/lib_os_helpers.py new file mode 100644 index 0000000..27cc2bc --- /dev/null +++ b/UniTAP/libs/lib_helper/lib_os_helpers.py @@ -0,0 +1,131 @@ +import warnings +from ctypes import * +import os +import platform +import glob + +LIBS_PATH = os.path.dirname(os.path.dirname(__file__)) + + +def lib_method_wrapper(named_func_pointer, argtypes, restype): + wrapper = named_func_pointer + wrapper.argtypes = argtypes + wrapper.restype = restype + + return wrapper + + +class OS_Requirements(): + + def __init__(self, lib_type: str, lib_path=""): + self.__os_name = platform.system() + self.__lib_type = lib_type + self.__lib_specific_path = os.path.join(LIBS_PATH, lib_path if lib_path != "" else f"lib_{self.__lib_type.lower()}") + + def __gen_dll_path_for_win(self): + dll_path = os.path.join(self.__lib_specific_path, f'{self.__lib_type}.dll') + if not os.path.isfile(dll_path): + dll_path = f'C:\\Program Files\\Unigraf\\Unigraf UCD Tools\\{self.__lib_type}.dll' + + return dll_path + + def __gen_lib_path_for_osx(self): + lib_path = os.path.join(self.__lib_specific_path, f'lib{self.__lib_type}.dylib') + return lib_path + + def __get_exe_path_for_win(self): + dll_path = os.path.join(self.__lib_specific_path, f'{self.__lib_type}.exe') + if not os.path.isfile(dll_path): + dll_path = f'C:\\Program Files\\Unigraf\\Unigraf UCD Tools\\{self.__lib_type}.exe' + + return dll_path + + def __gen_lib_path_for_lin(self): + full_dll_name = f'lib{self.__lib_type}.so' + + dll_path = os.path.join(self.__lib_specific_path, full_dll_name) + if not os.path.isfile(dll_path): + dll_path = glob.glob(f'/home/user/Downloads/*/sdk/python/common/tsi/tsi_devices/libs/lib_{self.__lib_type.lower()}/{full_dll_name}') + if len(dll_path) > 0: + dll_path = dll_path[0] + else: + dll_path = "" + + return dll_path + + def __gen_exe_path_for_lin_and_mac(self, is_linux): + full_dll_name = f'lib{self.__lib_type}.so' if is_linux else f'lib{self.__lib_type}.' + + dll_path = os.path.join(self.__lib_specific_path, full_dll_name) + if not os.path.isfile(dll_path): + dll_path = glob.glob(f'/home/user/Downloads/*/sdk/python/common/tsi/tsi_devices/libs/lib_{self.__lib_type.lower()}/{full_dll_name}') + if len(dll_path) > 0: + dll_path = dll_path[0] + else: + dll_path = "" + + return dll_path + + def __get_lib_for_win(self): + if os.path.exists(self.__gen_dll_path_for_win()): + return windll.LoadLibrary(self.__gen_dll_path_for_win()) + else: + warnings.warn(f"Failed to find library at {self.__gen_dll_path_for_win()}") + + def __get_lib_for_lin(self): + if os.path.exists(self.__gen_lib_path_for_lin()): + return cdll.LoadLibrary(self.__gen_lib_path_for_lin()) + else: + warnings.warn(f"Failed to find library at {self.__gen_lib_path_for_lin()}") + return None + + def __get_lib_for_osx(self): + if os.path.exists(self.__gen_lib_path_for_osx()): + return cdll.LoadLibrary(self.__gen_lib_path_for_osx()) + else: + warnings.warn(f"Failed to find library at {self.__gen_lib_path_for_osx()}") + return None + + def get_lib(self): + if self.__os_name == 'Windows': + return self.__get_lib_for_win() + elif self.__os_name == 'Linux': + return self.__get_lib_for_lin() + elif self.__os_name == 'Darwin': + return self.__get_lib_for_osx() + else: + raise Exception('Un-supported OS!') + + def get_additional_file(self): + try: + if self.__os_name == 'Windows': + return self.__get_exe_path_for_win() + elif self.__os_name == 'Linux': + return self.__gen_exe_path_for_lin_and_mac(True) + elif self.__os_name == 'Darwin': + return self.__gen_exe_path_for_lin_and_mac(False) + else: + raise Exception('Un-supported OS!') + except OSError: + error = get_errno() + print(f'Get error = {error} while loading {self.__lib_type} lib for {self.__os_name}!') + if error == 193: + print('Perhaps you are using a 64-bit python interpreter along with a 32-bit library, or vice versa.' + ' Need the same.') + + def get_path_to_lib(self): + try: + if self.__os_name == 'Windows': + return self.__gen_dll_path_for_win() + elif self.__os_name == 'Linux': + return self.__gen_lib_path_for_lin() + elif self.__os_name == 'Darwin': + return self.__gen_lib_path_for_osx() + else: + raise Exception('Un-supported OS!') + except OSError: + error = GetLastError() + print(f'Get error = {error} while loading {self.__lib_type} lib for {self.__os_name}!') + if error == 193: + print('Perhaps you are using a 64-bit python interpreter along with a 32-bit library, or vice versa.' + ' Need the same.') diff --git a/UniTAP/libs/lib_pdl/Default_16K.png b/UniTAP/libs/lib_pdl/Default_16K.png new file mode 100644 index 0000000000000000000000000000000000000000..d4faaa143de1a9e47db8579b768efd19ba83f69f GIT binary patch literal 621058 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¾ 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≪F>Lwv^NcKgLbn#>|`1?_-hO)L+K+W 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 literal 0 HcmV?d00001 diff --git a/UniTAP/libs/lib_pdl/__init__.py b/UniTAP/libs/lib_pdl/__init__.py new file mode 100644 index 0000000..0ab4947 --- /dev/null +++ b/UniTAP/libs/lib_pdl/__init__.py @@ -0,0 +1 @@ +from .pdl import generate_pattern_as_vf, PatternType diff --git a/UniTAP/libs/lib_pdl/pdl.py b/UniTAP/libs/lib_pdl/pdl.py new file mode 100644 index 0000000..f4244ea --- /dev/null +++ b/UniTAP/libs/lib_pdl/pdl.py @@ -0,0 +1,21 @@ +import os + +from UniTAP.common import VideoFrame, ColorInfo, DataInfo, get_vf_from_image +from UniTAP.utils import video_frame_to_ci +from .pdl_types import * + +UNIGRAF_IMAGE_PATH = os.path.join(os.path.dirname(__file__), "Default_16K.png") + + +def generate_pattern_as_vf(pattern_type: PatternType, width: int, height: int, + color_info: ColorInfo, data_info: DataInfo) -> VideoFrame: + if pattern_type != PatternType.Unigraf: + raise NotImplementedError("Generating ColorRamp and ColorSquares not supported yet.") + + if pattern_type == PatternType.Unigraf: + if not os.path.exists(UNIGRAF_IMAGE_PATH): + raise FileNotFoundError("Unigraf default image not found. Not possible to generate " + "Unigraf pattern.") + vf = get_vf_from_image(UNIGRAF_IMAGE_PATH, width, height) + + return video_frame_to_ci(vf, color_info, data_info) diff --git a/UniTAP/libs/lib_pdl/pdl_types.py b/UniTAP/libs/lib_pdl/pdl_types.py new file mode 100644 index 0000000..50dea0a --- /dev/null +++ b/UniTAP/libs/lib_pdl/pdl_types.py @@ -0,0 +1,7 @@ +from enum import IntEnum + + +class PatternType(IntEnum): + Unigraf = 0, + ColorSquares = 1, + ColorRamp = 2 diff --git a/UniTAP/libs/lib_tsi/TSI.dll b/UniTAP/libs/lib_tsi/TSI.dll new file mode 100644 index 0000000000000000000000000000000000000000..017e92c9aa3687bc76b8bd4276ab56e0b9cbdff6 GIT binary patch literal 7519448 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%0D`*+RTtG!GC^G6s-2R_z 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

*2>x4srIWJ!n} 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 -QqqsPBd%@?n? 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

1ZvAm~FC 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 z zrgvCH4de-dl<%Ma``>LO`s-p*M-77i|G1_{b_5of3yr`^Mu2RyyJ{uF*g}(!W#o50 z0zMz8&Ev~7Sp|BCmq1VaumOEVQRfCa5-)$7h~pBc?hWIES%G{8hS9!VOcTytD(u+u zTea=%cBI*m-)$~#4ZEqC-2&z10Vq$i@vb|;jH;kr4;+JAH+xXfunAB~`8QdJ0!qbQ zc=!*GV?q&kgRf_DH%%MdMjAW+V?>#R89hKT@gS{qefWs(cRSMM%zv4l`V~~k2SW_m zgbNK!_`d0@Tkb;i9FarVAz?qNq52p@d5yN(okz0Ms4 zn(xV(BZ!PkBrolVrKa~Ll2@;fdvC|qeI8Fuo9*I!m`KiSkLUY|Oywk)x zhLH0PPhi*e4#aqYiRsmA9lX}TYlfclT01ETfls1`>bu=!eep6n>n0ZzH<{yI#cdq< zMn4i9B)Xkp^(KT@g)Zh9xfL9&UA&af1i8CWx8OumsY1-TW;sL5!(M(3XOu{rIm$T#hI1Vtk1>7r&evoVZHO=^w3&y4I=CcZ7>g z)z=2JJ+durcY|mox0Eh4!x4N+dWcTJ2Fmhr>A}cYPug{`kae&~%8Mt}43T-}up#rB z#AA!%Nhi)k=U0^QHeuLv7CdQ0S~JeVB=9|qzgA6gWMflq%{SFU=wdDL7XcgzUOw7C zWH=Tb@E0PaGIRKvVUt*0GuQS`f{-4<#F-b7R4$Gr9!`}=zUHA46rbnRj zt>j*(MV%Kw5qpO>PiAVxNg(0V@3{A;Vy(*x6c-1g#1axVs@C)I?>po)hIRcbl#^y zMzOS(f}Zd@&`W{cL&)zj)1jclWJb|*-aja)G`Uiz+~P_gDMg^ARZTB6{gS|2MR3!1@jJpWyjm<3JaXsFvu zE`d65pLJVHZoSy;ZQ&Wo={K7E3(aq%=s0Dc@u`C2(6IRt&=yY}E{{g_g$8Xk{qRf8 z-OtgFqCAV0t^u@q1Y_nG@kr+eRo8aqi&^7~Pr

NmB*rZH%|ow}cK8cUH$R{2nG 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 z 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

H-^|z1MxJ#R6_1$PT?yKvOwR(m$?r3|X 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