1.1.0版本

This commit is contained in:
xinzhu.yin
2026-04-16 16:51:05 +08:00
commit c157e774e5
333 changed files with 70759 additions and 0 deletions

View File

@@ -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

117
UniTAP/dev/ports/dprx.py Normal file
View File

@@ -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

View File

@@ -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

View File

@@ -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)

118
UniTAP/dev/ports/dptx.py Normal file
View File

@@ -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

View File

@@ -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

View File

@@ -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)

102
UniTAP/dev/ports/hdrx.py Normal file
View File

@@ -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

View File

@@ -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)

103
UniTAP/dev/ports/hdtx.py Normal file
View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -0,0 +1 @@
from .ag import AudioPattern

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,2 @@
from .event import *
from .video import *

View File

@@ -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

View File

@@ -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"

View File

@@ -0,0 +1 @@
from .bulk_types import TriggerType, TriggerPosition, TriggerTypeEnum

View File

@@ -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()

View File

@@ -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

View File

@@ -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),
]

View File

@@ -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"

View File

@@ -0,0 +1,2 @@
from .event_types import EventSDP, EventLinkPattern, EventVBID, EventMSA, EventInfoFrame, EventLCE, EventFileFormat, \
EventFilterDpRx, EventFilterDpTx, EventFilterHdRx, EventFilterHdTx, EventFilterUsbc

View File

@@ -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

View File

@@ -0,0 +1,235 @@
event_entry_template = """
var TxtEntry{$num} = [
'Event Details',
'{$num}&nbsp{$event_name} ',
'{$time}<br>Entry type {$entry_type}<br>Sent from {$send_from}<br>',
'<font face="Courier">{$event_data}</font></p>',]
var ShortTxtEntry{$num} = [
'<option style="color:{$text_color};background-color:{$back_color};{$font_family}{$font_italic}{$font_bold}">{$num_just} {$time}&nbsp&nbsp {$send_from_just} {$type_just} {$event_name} </option>',
'</p>']
var Entry{$num} = [
EventProto,
TxtEntry{$num},
ShortEventProto,
ShortTxtEntry{$num} ]
"""
event_table_row = '<option style="color:{$text_color};background-color:{$back_color}";{$font_family}{$font_italic}' \
'{$font_bold} value="{$num}">{$num_just} {$time}&nbsp&nbsp {$send_from_just} {$type_just} ' \
'{$event_name} </option>'
events_template = """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html><head><title>UCD Console event monitoring report</title>
<meta http-equiv="Content-Type" content="text/html; charset=Windows-1252">
<script type="text/javascript">
var tbl_InternalStyleSheet =
[
'<style>',
'TABLE.DOCUMENT{width: 100%;table-layout: fixed;}',
'TABLE.DOCUMENT TD.CONTENTS{border-right-style: solid;border-right-color: #000000;border-right-width: thin;table-layout: fixed;width: 55%;}',
'TABLE.DOCUMENT TD.DOC{table-layout: fixed; width: 45%; word-wrap: break-word;}',
'P.CONTENTS{font-weight: bold;font-family: "Arial";font-size: 10pt;margin-left: 0.3cm;margin-top: 0.5cm;margin-bottom: 0.3cm;color: #000000;}',
'P.CONTENTS SELECT{font-weight: normal;font-family: "Courier";font-size: 10pt;width: 98%;height: 500pt;margin-top: 0;text-align: left;}',
'P.CONTENTS BUTTON{width: 49%;height: 18pt;margin-top: 0.1cm;text-align: center;}',
'P.COMPANYNAME{margin-top: 0cm;margin-bottom: 0.5cm;margin-left: 0.9cm;font-weight: medium;font-family: "Arial";font-size: 14pt;color: #000000;}',
'P.COMPANYINFO{margin-top: 0cm;margin-bottom: 0.5cm;margin-left: 1.1cm;font-weight: medium;font-family: "Arial";font-size: 10pt;color: #000000;}',
'P.HEADING{background-color: #BBC3FF;margin-top: 0;margin-bottom: 0.5cm;margin-left: 0cm;font-weight: bold;font-family: "Arial";font-size: 16pt;color: #000000; word-wrap: break-word;}',
'P.SUBHEADING{background-color: #BBC3FF;margin-top: 1.25cm;margin-bottom: 0.5cm;margin-left: 0.5cm;margin-right: 0;font-weight: bold;font-family: "Arial";font-size: 11pt;color: #000000;text-transform: uppercase; word-wrap: break-word;}',
'P.BLUESUBHEADING{background-color: #BBC3FF;margin-top: 0.5cm;margin-bottom: 0.5cm;margin-left: 0.3cm;margin-right: 0;font-weight: bold;font-family: "Arial";font-size: 11pt;color: #000000; word-wrap: break-word;}',
'P.GREENSUBHEADING{background-color: #DCFABC;margin-top: 0.5cm;margin-bottom: 0.5cm;margin-left: 0.6cm; margin-right: 0; font-weight: bold;font-family: "Arial";font-size: 11pt;color: #000000; word-wrap: break-word;}',
'P.BODYTEXT{margin-top: 0;margin-bottom: 0;margin-left: 0.9cm;font-weight: medium;font-family: "Arial";font-size: 10pt;color: #000000; word-wrap: break-word;}',
'P.INDENTBODYTEXT{margin-top: 0;margin-bottom: 0.5cm;margin-left: 1.2cm;font-weight: medium;font-family: "Arial";font-size: 10pt;color: #000000;}',
'P.PRTGREEN{background-color: #DCFABC;margin-top: 0;margin-bottom: 0.05cm;margin-left: 0.6cm; margin-right: 0; font-weight: medium;font-family: "Courier";font-size: 11pt;color: #000000;}',
'P.PRTROW{margin-top: 0;margin-bottom: 0.05cm;margin-left: 0.6cm; margin-right: 0; font-weight: medium;font-family: "Courier";font-size: 11pt;color: #000000;}',
'P.PRTBODY{margin-top: 0;margin-bottom: 0.1cm;margin-left: 0.6cm;font-weight: medium;font-family: "Courier";font-size: 10pt;color: #000000;}',
'</style>'
]
var tbl_CompanyInfo =
[
'<p class="COMPANYINFO">',
'Created with <a href="http://www.unigraf.fi">Unigraf</a> UCD Console',
'</p>'
]
var tbl_InitialRight =
[
'<p class="SUBHEADING" style="margin-left: 0; margin-top: 0;">&nbsp;&nbsp;&nbsp;Transaction / Event details</p>',
'<p class="BODYTEXT">Select an entry from the list to see details</p>'
]
var PacketProto =
[
'<p class="SUBHEADING" style="margin-left: 0; margin-top: 0;">&nbsp;&nbsp;&nbsp;',
'</p><p class="BLUESUBHEADING">',
'</p><p class="GREENSUBHEADING">Info</p><p class="BODYTEXT">',
'</p><p class="GREENSUBHEADING">HEX Dump</p><p class="BODYTEXT">',
'</p><p class="GREENSUBHEADING">Content decoder</p><p class="BODYTEXT">'
]
var EventProto =
[
'<p class="SUBHEADING" style="margin-left: 0; margin-top: 0;">&nbsp;&nbsp;&nbsp;',
'</p><p class="BLUESUBHEADING">',
'</p><p class="GREENSUBHEADING">Info</p><p class="BODYTEXT">',
'</p><p class="GREENSUBHEADING">Content decoder</p><p class="BODYTEXT">'
]
var TraceProto =
[
'<p class="SUBHEADING" style="margin-left: 0; margin-top: 0;">&nbsp;&nbsp;&nbsp;',
'</p><p class="BLUESUBHEADING">',
'</p><p class="GREENSUBHEADING">Info</p><p class="BODYTEXT">',
'</p><p class="GREENSUBHEADING">Content decoder</p><p class="BODYTEXT">'
]
var ShortPacketProto =
[
'<p class="PRTROW" style="margin-left: 0; margin-top: 0;">&nbsp;&nbsp;&nbsp;',
'<p class="PRTBODY">'
]
var ShortEventProto =
[
'<p class="PRTROW" style="margin-left: 0; margin-top: 0;">&nbsp;&nbsp;&nbsp;',
'</p><p class="PRTBODY">'
]
var tbl_RepDetails =
[
'<p class="SUBHEADING" style="margin-left: 0; margin-top: 0;">&nbsp;&nbsp;&nbsp;Report details</p>',
'<p class="GREENSUBHEADING">Unigraf test equipment</p>',
'<p class="BODYTEXT">Device: {$ucd_device}<br></p>',
'<p class="BODYTEXT">Firmware Package: {$ucd_fw_version}<br></p>',
'<p class="BODYTEXT">Console Application: {$app_version}<br></p>',
'<p class="BODYTEXT">Software Bundle: {$bundle_version}<br></p>',
'<p class="BODYTEXT">Frontend Info: {$frontend_version}<br></p>',
'<p class="BODYTEXT">Memory size: {$memory_size}<br><br></p>',
'<p class="GREENSUBHEADING">Remarks</p>',
'<p class="BODYTEXT">{$remarks}</p>',
'<p class="GREENSUBHEADING">Tested by</p>',
'<p class="BODYTEXT">{$tested}</p>',
]
{$events_data}
var tbl_AllDetails = [
{$events_list}
]
function MakeLongEntry (iEntryTable)
{
var lp;
var res = '';
for (lp in iEntryTable[0])
{
res += iEntryTable [0] [lp];
res += iEntryTable [1] [lp];
}
return res;
}
function MakeShortEntry(iEntryTable)
{
var lp;
var res = '';
for (lp in iEntryTable[2])
{
res += iEntryTable [2] [lp];
res += iEntryTable [3] [lp];
}
return res;
}
function GenerateDocument (iTable)
{
var loop;
var result = '';
for (loop in iTable)
result += iTable [loop];
return result;
}
function ShowTransaction (iTable)
{
var loop;
document.getElementById ('document').innerHTML = '<p class="SUBHEADING" style="margin-left: 0; margin-top: 0;">&nbsp;&nbsp;&nbsp;Transaction details</p>';
for(loop in iTable)
if(loop != 0)
document.getElementById ('document').innerHTML += MakeLongEntry(iTable[loop]);
}
function ListHandler (iOptionValue)
{
document.getElementById ('document').innerHTML = MakeLongEntry (tbl_AllDetails [iOptionValue]);
}
function ShowPrinterFriendlyDocument ()
{
var loop;
document.open ();
document.write ('<p class="HEADING">&nbsp;&nbsp;{$report_name}</p>');
document.write (GenerateDocument (tbl_RepDetails));
document.write (GenerateDocument (tbl_InternalStyleSheet));
document.write ('<p class="GREENSUBHEADING">Unigraf Oy contact information</p>');
document.write (GenerateDocument (tbl_CompanyInfo));
document.write ('<br><br><p class="SUBHEADING" style="margin-left: 0; margin-top: 0;">&nbsp;&nbsp;Events list</p><br>');
for(loop in tbl_AllDetails)
document.write (MakeShortEntry (tbl_AllDetails[loop]));
ShowReportDetails ();
document.close ();
}
function ShowReportDetails ()
{
document.getElementById ('document').innerHTML = GenerateDocument (tbl_RepDetails);
}
var styleloop;
for (styleloop in tbl_InternalStyleSheet)
{
document.write (tbl_InternalStyleSheet [styleloop]);
}
</script>
</head>
<body onload="document.getElementById ('document').innerHTML = GenerateDocument (tbl_RepDetails)">
<div>
<p class="HEADING" id="top">&nbsp;&nbsp;{$report_name}</p>
</div>
<div>
<table class="DOCUMENT" cellpadding="0" cellspacing="0">
<colgroup>
</colgroup>
<tr valign="top">
<td class="CONTENTS">
<p class="SUBHEADING" style="margin-left: 0; margin-top: 0;">
&nbsp;&nbsp;&nbsp;Transactions and events
</p>
<p class="CONTENTS">
Click entry to see details</p>
<p class="CONTENTS">
&nbsp# <span style='padding-left:95px;'></span>Timestamp<span style='padding-left:62px;'></span>Source <span style='padding-left:40px;'> </span>Type <span style='padding-left:103px;'> </span> Info
<select id="testlist" size="2" onchange="ListHandler (document.getElementById ('testlist').value)" onkeyup="ListHandler (document.getElementById ('testlist').value)">
{$events_table}
</select>
<button onclick="ShowPrinterFriendlyDocument ()">Show printer friendly format ...</button>
<button onclick="ShowReportDetails ()">Show report information ...</button><br><br>
<script type="text/javascript">
document.write (GenerateDocument (tbl_CompanyInfo));
</script>
</p>
</td>
<td class="DOC">
<div id="document">
&nbsp;
</div>
</td>
</tr>
</table>
</div>
</body>
</html>
"""
packet_entry_template = """
var TxtEntry{$num} = [
'Event Details',
'{$num}&nbsp{$event_name} ',
'{$time}<br>Entry type {$entry_type}<br>Sent from {$send_from}<br>',
'{$hex}',
'<font face="Courier">{$event_data}</font></p>',]
var ShortTxtEntry{$num} = [
'<div style="color:{$text_color};margin-left: 0; margin-top: 0;background-color:{$back_color};{$font_family}{$font_italic}{$font_bold}">{$num_just} {$time}&nbsp&nbsp {$send_from_just} {$type_just} {$event_name}</div>',
'{$hex_just}</p>']
var Entry{$num} = [
PacketProto,
TxtEntry{$num},
ShortPacketProto,
ShortTxtEntry{$num} ]
"""

View File

@@ -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")

View File

@@ -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", "<br>")
data = data.replace("\n\r", "<br>")
data = data.replace("\r", "<br>")
data = data.replace("\n", "<br>")
data = data.replace("'", "\\'")
data = data.replace(" ", " &nbsp")
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(" ", "&nbsp")).\
replace("{$send_from_just}", f"{item.get('source')}".ljust(10, " ").replace(" ", "&nbsp")).\
replace("{$type_just}", f"{item.get('type')}".ljust(16 if long_type_string else 8, " ").replace(" ", "&nbsp")).\
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(" ", "&nbsp")).\
replace("{$time}", f'{timestamp_to_str(item.get("timestamp"))}').\
replace("{$send_from_just}", f"{item.get('source')}".ljust(10, " ").replace(" ", "&nbsp")).\
replace("{$type_just}", f"{item.get('type')}".ljust(16 if long_type_string else 8, " ").replace(" ", "&nbsp")).\
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()

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1 @@
from .result_video import PictureFileFormat

View File

@@ -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"

View File

@@ -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()

View File

@@ -0,0 +1,2 @@
from .cec_types import (CECStatus, DeviceType, CecResetDataCommand, LogicalAddressEnum, CECCommand, CECCommand,
DeviceTypeEnum)

View File

@@ -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)
]

View File

@@ -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()

View File

@@ -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()

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1 @@
from .dpcd import DPCDRegisters, DPCDRegion

View File

@@ -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

View File

@@ -0,0 +1,2 @@
from .edid_parser import EdidParser, MainBlockType, AdditionalBlockType
from .edid import EdidFileType, DisplayIDReadMode

View File

@@ -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()

View File

@@ -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()

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -0,0 +1,3 @@
from .fec_tx import FecTx
from .fec_rx import FecRx
from .fec_shared import FECCounters, FECErrorType128b132b, FECErrorType8b10b

View File

@@ -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)

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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])

View File

@@ -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)}")

View File

@@ -0,0 +1,2 @@
def aligned(v, by):
return (v + (by - 1)) / by * by

View File

@@ -0,0 +1,2 @@
from .dp import *
from .hdmi import *

View File

@@ -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

View File

@@ -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()

View File

@@ -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

View File

@@ -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()

View File

@@ -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)

View File

@@ -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

View File

@@ -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"

View File

@@ -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}")

View File

@@ -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}

View File

@@ -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

View File

@@ -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

View File

@@ -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])

View File

@@ -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"

View File

@@ -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)

View File

@@ -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)
]

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1 @@
from .types import FrlMode, FrlCaps, ClockRate, HdmiModeTx, HdmiModeRx, LinkMode

View File

@@ -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"

View File

@@ -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

View File

@@ -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"

View File

@@ -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"

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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"

View File

@@ -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"

View File

@@ -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'}"

View File

@@ -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"

View File

@@ -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]

View File

@@ -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

View File

@@ -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)

View File

@@ -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]

View File

@@ -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)
]

View File

@@ -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

View File

@@ -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))

View File

@@ -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)
]

Some files were not shown because too many files have changed in this diff Show More