1.1.0版本
This commit is contained in:
7
UniTAP/dev/ports/__init__.py
Normal file
7
UniTAP/dev/ports/__init__.py
Normal 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
117
UniTAP/dev/ports/dprx.py
Normal 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
|
||||
89
UniTAP/dev/ports/dprx4xx.py
Normal file
89
UniTAP/dev/ports/dprx4xx.py
Normal 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
|
||||
18
UniTAP/dev/ports/dprx5xx.py
Normal file
18
UniTAP/dev/ports/dprx5xx.py
Normal 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
118
UniTAP/dev/ports/dptx.py
Normal 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
|
||||
62
UniTAP/dev/ports/dptx4xx.py
Normal file
62
UniTAP/dev/ports/dptx4xx.py
Normal 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
|
||||
15
UniTAP/dev/ports/dptx5xx.py
Normal file
15
UniTAP/dev/ports/dptx5xx.py
Normal 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
102
UniTAP/dev/ports/hdrx.py
Normal 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
|
||||
15
UniTAP/dev/ports/hdrx4xx.py
Normal file
15
UniTAP/dev/ports/hdrx4xx.py
Normal 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
103
UniTAP/dev/ports/hdtx.py
Normal 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
|
||||
16
UniTAP/dev/ports/hdtx4xx.py
Normal file
16
UniTAP/dev/ports/hdtx4xx.py
Normal 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)
|
||||
|
||||
11
UniTAP/dev/ports/modules/__init__.py
Normal file
11
UniTAP/dev/ports/modules/__init__.py
Normal 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
|
||||
1
UniTAP/dev/ports/modules/ag/__init__.py
Normal file
1
UniTAP/dev/ports/modules/ag/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .ag import AudioPattern
|
||||
156
UniTAP/dev/ports/modules/ag/ag.py
Normal file
156
UniTAP/dev/ports/modules/ag/ag.py
Normal 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
|
||||
109
UniTAP/dev/ports/modules/ag/ag_utils.py
Normal file
109
UniTAP/dev/ports/modules/ag/ag_utils.py
Normal 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
|
||||
19
UniTAP/dev/ports/modules/ag/private_types.py
Normal file
19
UniTAP/dev/ports/modules/ag/private_types.py
Normal 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
|
||||
22
UniTAP/dev/ports/modules/ag/types.py
Normal file
22
UniTAP/dev/ports/modules/ag/types.py
Normal 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
|
||||
2
UniTAP/dev/ports/modules/capturer/__init__.py
Normal file
2
UniTAP/dev/ports/modules/capturer/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from .event import *
|
||||
from .video import *
|
||||
0
UniTAP/dev/ports/modules/capturer/audio/__init__.py
Normal file
0
UniTAP/dev/ports/modules/capturer/audio/__init__.py
Normal file
117
UniTAP/dev/ports/modules/capturer/audio/audio_capturer.py
Normal file
117
UniTAP/dev/ports/modules/capturer/audio/audio_capturer.py
Normal 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
|
||||
60
UniTAP/dev/ports/modules/capturer/audio/result_audio.py
Normal file
60
UniTAP/dev/ports/modules/capturer/audio/result_audio.py
Normal 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"
|
||||
|
||||
1
UniTAP/dev/ports/modules/capturer/bulk/__init__.py
Normal file
1
UniTAP/dev/ports/modules/capturer/bulk/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .bulk_types import TriggerType, TriggerPosition, TriggerTypeEnum
|
||||
132
UniTAP/dev/ports/modules/capturer/bulk/bulk_capturer.py
Normal file
132
UniTAP/dev/ports/modules/capturer/bulk/bulk_capturer.py
Normal 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()
|
||||
979
UniTAP/dev/ports/modules/capturer/bulk/bulk_types.py
Normal file
979
UniTAP/dev/ports/modules/capturer/bulk/bulk_types.py
Normal 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
|
||||
45
UniTAP/dev/ports/modules/capturer/bulk/private_bulk_types.py
Normal file
45
UniTAP/dev/ports/modules/capturer/bulk/private_bulk_types.py
Normal 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),
|
||||
]
|
||||
60
UniTAP/dev/ports/modules/capturer/bulk/result_bulk.py
Normal file
60
UniTAP/dev/ports/modules/capturer/bulk/result_bulk.py
Normal 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"
|
||||
2
UniTAP/dev/ports/modules/capturer/event/__init__.py
Normal file
2
UniTAP/dev/ports/modules/capturer/event/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from .event_types import EventSDP, EventLinkPattern, EventVBID, EventMSA, EventInfoFrame, EventLCE, EventFileFormat, \
|
||||
EventFilterDpRx, EventFilterDpTx, EventFilterHdRx, EventFilterHdTx, EventFilterUsbc
|
||||
205
UniTAP/dev/ports/modules/capturer/event/event_capturer.py
Normal file
205
UniTAP/dev/ports/modules/capturer/event/event_capturer.py
Normal 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
|
||||
235
UniTAP/dev/ports/modules/capturer/event/event_report_template.py
Normal file
235
UniTAP/dev/ports/modules/capturer/event/event_report_template.py
Normal file
@@ -0,0 +1,235 @@
|
||||
event_entry_template = """
|
||||
var TxtEntry{$num} = [
|
||||
'Event Details',
|
||||
'{$num} {$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}   {$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}   {$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;"> 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;"> ',
|
||||
'</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;"> ',
|
||||
'</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;"> ',
|
||||
'</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;"> ',
|
||||
'<p class="PRTBODY">'
|
||||
]
|
||||
var ShortEventProto =
|
||||
[
|
||||
'<p class="PRTROW" style="margin-left: 0; margin-top: 0;"> ',
|
||||
'</p><p class="PRTBODY">'
|
||||
]
|
||||
var tbl_RepDetails =
|
||||
[
|
||||
'<p class="SUBHEADING" style="margin-left: 0; margin-top: 0;"> 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;"> 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"> {$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;"> 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"> {$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;">
|
||||
Transactions and events
|
||||
</p>
|
||||
<p class="CONTENTS">
|
||||
Click entry to see details</p>
|
||||
<p class="CONTENTS">
|
||||
 # <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">
|
||||
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
"""
|
||||
|
||||
packet_entry_template = """
|
||||
var TxtEntry{$num} = [
|
||||
'Event Details',
|
||||
'{$num} {$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}   {$send_from_just} {$type_just} {$event_name}</div>',
|
||||
'{$hex_just}</p>']
|
||||
var Entry{$num} = [
|
||||
PacketProto,
|
||||
TxtEntry{$num},
|
||||
ShortPacketProto,
|
||||
ShortTxtEntry{$num} ]
|
||||
|
||||
"""
|
||||
774
UniTAP/dev/ports/modules/capturer/event/event_types.py
Normal file
774
UniTAP/dev/ports/modules/capturer/event/event_types.py
Normal 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")
|
||||
193
UniTAP/dev/ports/modules/capturer/event/event_utils.py
Normal file
193
UniTAP/dev/ports/modules/capturer/event/event_utils.py
Normal 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(" ", "  ")
|
||||
return data
|
||||
|
||||
|
||||
def timestamp_to_str(value) -> str:
|
||||
nsec = value % 1000
|
||||
value -= nsec
|
||||
value /= 1000
|
||||
|
||||
mcsec = value % 1000
|
||||
value -= mcsec
|
||||
value /= 1000
|
||||
|
||||
msec = value % 1000
|
||||
value -= msec
|
||||
value /= 1000
|
||||
|
||||
sec = value % 60
|
||||
value -= sec
|
||||
value /= 60
|
||||
|
||||
min_v = value % 60
|
||||
value -= min_v
|
||||
|
||||
hour = value / 60
|
||||
|
||||
return "{:02d}:{:02d}:{:02d}.{:03d}.{:03d}.{:03d}".format(int(hour), int(min_v), int(sec), int(msec), int(mcsec),
|
||||
int(nsec))
|
||||
|
||||
|
||||
def save_to_txt_file(path: str, events: list, index=None):
|
||||
file = open(path, 'w')
|
||||
if index is not None:
|
||||
if len(events[index]) == 6:
|
||||
file.write(f"{events[index].get('content')}\n{events[index].get('brief')}\n"
|
||||
f"{byterarray_to_hex_str(events[index].get('data'))}\n"
|
||||
f"{events[index].get('source')}\n")
|
||||
else:
|
||||
warnings.warn("Empty event")
|
||||
else:
|
||||
for data in events:
|
||||
if len(data) == 6:
|
||||
file.write(f"{data.get('content')}\n{data.get('brief')}\n"
|
||||
f"{byterarray_to_hex_str(data.get('data'))}\n"
|
||||
f"{data.get('source')}\n")
|
||||
else:
|
||||
warnings.warn("Empty event")
|
||||
|
||||
file.close()
|
||||
|
||||
|
||||
def save_to_bin_file(path: str, events: list, index=None):
|
||||
file = open(path, 'wb')
|
||||
if index is not None:
|
||||
data = events[index].data
|
||||
if len(data) > 14:
|
||||
sync = 0x0B41550B
|
||||
file.write(sync.to_bytes(length=4, byteorder='little'))
|
||||
file.write(0x0.to_bytes(length=4, byteorder='little'))
|
||||
file.write(int(data[13] & 0xF).to_bytes(length=4, byteorder='little'))
|
||||
file.write(int((len(data) - 12) / 4).to_bytes(length=4, byteorder='little'))
|
||||
file.write(data[12:])
|
||||
else:
|
||||
for data in events:
|
||||
if len(data.data) > 14:
|
||||
sync = 0x0B41550B
|
||||
file.write(sync.to_bytes(length=4, byteorder='little'))
|
||||
file.write(0x0.to_bytes(length=4, byteorder='little'))
|
||||
file.write(int(data.data[13] & 0xF).to_bytes(length=4, byteorder='little'))
|
||||
file.write(int((len(data.data) - 12) / 4).to_bytes(length=4, byteorder='little'))
|
||||
file.write(data.data[12:])
|
||||
file.close()
|
||||
|
||||
|
||||
def save_to_csv_file(path: str, events: list):
|
||||
with (open(path, 'w', newline='') as file):
|
||||
writer = csv.writer(file, delimiter=';')
|
||||
writer.writerow(["Source", "Type", "Start", "Brief info", "Full info"])
|
||||
for data in events:
|
||||
if len(data) == 6:
|
||||
processed_info = data.get('content')[:-1] + '.'
|
||||
processed_info = processed_info.replace("\n", "; ")
|
||||
|
||||
writer.writerow([
|
||||
data.get('source'),
|
||||
data.get('type'),
|
||||
timestamp_to_str(data.get('timestamp')),
|
||||
data.get('brief'),
|
||||
f'"{processed_info}"'
|
||||
])
|
||||
else:
|
||||
warnings.warn("Empty event")
|
||||
|
||||
|
||||
|
||||
def save_to_html_file(path: str, events: list, fw_info: dict, long_type_string: bool):
|
||||
file = open(path, 'w')
|
||||
|
||||
new_events_template = events_template.replace("{$report_name}", "Unigraf UCD Console event monitoring report").\
|
||||
replace("{$bundle_version}", fw_info.get('bundle_version')).\
|
||||
replace("{$app_version}", fw_info.get('app_version')).\
|
||||
replace("{$frontend_version}", fw_info.get('frontend_version')).\
|
||||
replace("{$memory_size}", fw_info.get('memory_size')).\
|
||||
replace("{$ucd_device}", fw_info.get('ucd_device')).\
|
||||
replace("{$ucd_fw_version}", fw_info.get('ucd_fw_version')).\
|
||||
replace("{$remarks}", '').\
|
||||
replace("{$tested}", "")
|
||||
|
||||
events_data = []
|
||||
entries_list = []
|
||||
entries_rows = []
|
||||
|
||||
for index, item in enumerate(events):
|
||||
|
||||
new_entry_value = event_entry_template.replace("{$event_name}", item.get('brief')).\
|
||||
replace("{$time}", f'{timestamp_to_str(item.get("timestamp"))}').\
|
||||
replace("{$entry_type}", "").\
|
||||
replace("{$send_from}", item.get('type')).\
|
||||
replace("{$num}", f'{index}').\
|
||||
replace("{$hex}", byterarray_to_hex_str(item.get('data'))).\
|
||||
replace("{$hex_just}", byterarray_to_hex_str(item.get('data'))).\
|
||||
replace("{$event_data}", content_to_str(item.get('content'))).\
|
||||
replace("{$num_just}", f"{index}".ljust(6, " ").replace(" ", " ")).\
|
||||
replace("{$send_from_just}", f"{item.get('source')}".ljust(10, " ").replace(" ", " ")).\
|
||||
replace("{$type_just}", f"{item.get('type')}".ljust(16 if long_type_string else 8, " ").replace(" ", " ")).\
|
||||
replace("{$text_color}", "#000000").\
|
||||
replace("{$back_color}", '#ffffff').\
|
||||
replace("{$font_bold}", "").\
|
||||
replace("{$font_italic}", "").\
|
||||
replace("{$font_family}", "")
|
||||
|
||||
new_event_table_row = event_table_row.replace("{$text_color}", "#000000").\
|
||||
replace("{$back_color}", '#ffffff').\
|
||||
replace("{$font_family}", "").\
|
||||
replace("{$font_italic}", "").\
|
||||
replace("{$font_bold}", "").\
|
||||
replace("{$num}", f'{index}').\
|
||||
replace("{$num_just}", f"{index}".ljust(6, " ").replace(" ", " ")).\
|
||||
replace("{$time}", f'{timestamp_to_str(item.get("timestamp"))}').\
|
||||
replace("{$send_from_just}", f"{item.get('source')}".ljust(10, " ").replace(" ", " ")).\
|
||||
replace("{$type_just}", f"{item.get('type')}".ljust(16 if long_type_string else 8, " ").replace(" ", " ")).\
|
||||
replace("{$event_name}", item.get('brief'))
|
||||
|
||||
events_data.append(new_entry_value)
|
||||
entries_rows.append(new_event_table_row)
|
||||
entries_list.append(f"Entry{index},")
|
||||
|
||||
new_events_template = new_events_template.replace("{$events_data}", "\n".join(e for e in events_data)).\
|
||||
replace("{$events_list}", "\n".join(e for e in entries_list)).\
|
||||
replace("{$events_table}", "\n".join(e for e in entries_rows))
|
||||
|
||||
file.write(new_events_template)
|
||||
file.close()
|
||||
@@ -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
|
||||
108
UniTAP/dev/ports/modules/capturer/event/result_event.py
Normal file
108
UniTAP/dev/ports/modules/capturer/event/result_event.py
Normal 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
|
||||
1
UniTAP/dev/ports/modules/capturer/video/__init__.py
Normal file
1
UniTAP/dev/ports/modules/capturer/video/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .result_video import PictureFileFormat
|
||||
63
UniTAP/dev/ports/modules/capturer/video/result_video.py
Normal file
63
UniTAP/dev/ports/modules/capturer/video/result_video.py
Normal 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"
|
||||
224
UniTAP/dev/ports/modules/capturer/video/video_capturer.py
Normal file
224
UniTAP/dev/ports/modules/capturer/video/video_capturer.py
Normal 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()
|
||||
2
UniTAP/dev/ports/modules/cec/__init__.py
Normal file
2
UniTAP/dev/ports/modules/cec/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from .cec_types import (CECStatus, DeviceType, CecResetDataCommand, LogicalAddressEnum, CECCommand, CECCommand,
|
||||
DeviceTypeEnum)
|
||||
41
UniTAP/dev/ports/modules/cec/cec_private_types.py
Normal file
41
UniTAP/dev/ports/modules/cec/cec_private_types.py
Normal 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)
|
||||
]
|
||||
269
UniTAP/dev/ports/modules/cec/cec_rx.py
Normal file
269
UniTAP/dev/ports/modules/cec/cec_rx.py
Normal 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()
|
||||
269
UniTAP/dev/ports/modules/cec/cec_tx.py
Normal file
269
UniTAP/dev/ports/modules/cec/cec_tx.py
Normal 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()
|
||||
134
UniTAP/dev/ports/modules/cec/cec_types.py
Normal file
134
UniTAP/dev/ports/modules/cec/cec_types.py
Normal 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
|
||||
304
UniTAP/dev/ports/modules/device_constants.py
Normal file
304
UniTAP/dev/ports/modules/device_constants.py
Normal 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
|
||||
1
UniTAP/dev/ports/modules/dpcd/__init__.py
Normal file
1
UniTAP/dev/ports/modules/dpcd/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .dpcd import DPCDRegisters, DPCDRegion
|
||||
187
UniTAP/dev/ports/modules/dpcd/dpcd.py
Normal file
187
UniTAP/dev/ports/modules/dpcd/dpcd.py
Normal 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
|
||||
2
UniTAP/dev/ports/modules/edid/__init__.py
Normal file
2
UniTAP/dev/ports/modules/edid/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from .edid_parser import EdidParser, MainBlockType, AdditionalBlockType
|
||||
from .edid import EdidFileType, DisplayIDReadMode
|
||||
323
UniTAP/dev/ports/modules/edid/edid.py
Normal file
323
UniTAP/dev/ports/modules/edid/edid.py
Normal 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()
|
||||
21
UniTAP/dev/ports/modules/edid/edid_parser.py
Normal file
21
UniTAP/dev/ports/modules/edid/edid_parser.py
Normal 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()
|
||||
54
UniTAP/dev/ports/modules/edid/edid_private_types.py
Normal file
54
UniTAP/dev/ports/modules/edid/edid_private_types.py
Normal 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
|
||||
22
UniTAP/dev/ports/modules/edid/edid_types.py
Normal file
22
UniTAP/dev/ports/modules/edid/edid_types.py
Normal 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
|
||||
44
UniTAP/dev/ports/modules/edid/edid_utils.py
Normal file
44
UniTAP/dev/ports/modules/edid/edid_utils.py
Normal 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)
|
||||
3
UniTAP/dev/ports/modules/fec/__init__.py
Normal file
3
UniTAP/dev/ports/modules/fec/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from .fec_tx import FecTx
|
||||
from .fec_rx import FecRx
|
||||
from .fec_shared import FECCounters, FECErrorType128b132b, FECErrorType8b10b
|
||||
131
UniTAP/dev/ports/modules/fec/fec_rx.py
Normal file
131
UniTAP/dev/ports/modules/fec/fec_rx.py
Normal 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)
|
||||
38
UniTAP/dev/ports/modules/fec/fec_shared.py
Normal file
38
UniTAP/dev/ports/modules/fec/fec_shared.py
Normal 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
|
||||
204
UniTAP/dev/ports/modules/fec/fec_tx.py
Normal file
204
UniTAP/dev/ports/modules/fec/fec_tx.py
Normal 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)
|
||||
4
UniTAP/dev/ports/modules/hdcp/__init__.py
Normal file
4
UniTAP/dev/ports/modules/hdcp/__init__.py
Normal 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
|
||||
246
UniTAP/dev/ports/modules/hdcp/hdcp_rx.py
Normal file
246
UniTAP/dev/ports/modules/hdcp/hdcp_rx.py
Normal 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
|
||||
288
UniTAP/dev/ports/modules/hdcp/hdcp_tx.py
Normal file
288
UniTAP/dev/ports/modules/hdcp/hdcp_tx.py
Normal 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
|
||||
|
||||
221
UniTAP/dev/ports/modules/hdcp/types.py
Normal file
221
UniTAP/dev/ports/modules/hdcp/types.py
Normal 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)
|
||||
6
UniTAP/dev/ports/modules/internal_utils/__init__.py
Normal file
6
UniTAP/dev/ports/modules/internal_utils/__init__.py
Normal 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
|
||||
|
||||
|
||||
|
||||
122
UniTAP/dev/ports/modules/internal_utils/image_formats.py
Normal file
122
UniTAP/dev/ports/modules/internal_utils/image_formats.py
Normal 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])
|
||||
27
UniTAP/dev/ports/modules/internal_utils/image_utils.py
Normal file
27
UniTAP/dev/ports/modules/internal_utils/image_utils.py
Normal 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)}")
|
||||
2
UniTAP/dev/ports/modules/internal_utils/math.py
Normal file
2
UniTAP/dev/ports/modules/internal_utils/math.py
Normal file
@@ -0,0 +1,2 @@
|
||||
def aligned(v, by):
|
||||
return (v + (by - 1)) / by * by
|
||||
2
UniTAP/dev/ports/modules/link/__init__.py
Normal file
2
UniTAP/dev/ports/modules/link/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from .dp import *
|
||||
from .hdmi import *
|
||||
8
UniTAP/dev/ports/modules/link/dp/__init__.py
Normal file
8
UniTAP/dev/ports/modules/link/dp/__init__.py
Normal 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
|
||||
19
UniTAP/dev/ports/modules/link/dp/dp_cable_info.py
Normal file
19
UniTAP/dev/ports/modules/link/dp/dp_cable_info.py
Normal 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()
|
||||
137
UniTAP/dev/ports/modules/link/dp/dp_utils.py
Normal file
137
UniTAP/dev/ports/modules/link/dp/dp_utils.py
Normal 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
|
||||
121
UniTAP/dev/ports/modules/link/dp/link_rx.py
Normal file
121
UniTAP/dev/ports/modules/link/dp/link_rx.py
Normal 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()
|
||||
218
UniTAP/dev/ports/modules/link/dp/link_rx_aux_controller.py
Normal file
218
UniTAP/dev/ports/modules/link/dp/link_rx_aux_controller.py
Normal 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)
|
||||
332
UniTAP/dev/ports/modules/link/dp/link_rx_caps.py
Normal file
332
UniTAP/dev/ports/modules/link/dp/link_rx_caps.py
Normal 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
|
||||
407
UniTAP/dev/ports/modules/link/dp/link_rx_status.py
Normal file
407
UniTAP/dev/ports/modules/link/dp/link_rx_status.py
Normal 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"
|
||||
177
UniTAP/dev/ports/modules/link/dp/link_rx_types.py
Normal file
177
UniTAP/dev/ports/modules/link/dp/link_rx_types.py
Normal 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}")
|
||||
|
||||
|
||||
173
UniTAP/dev/ports/modules/link/dp/link_status_common.py
Normal file
173
UniTAP/dev/ports/modules/link/dp/link_status_common.py
Normal 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}
|
||||
279
UniTAP/dev/ports/modules/link/dp/link_tx.py
Normal file
279
UniTAP/dev/ports/modules/link/dp/link_tx.py
Normal 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
|
||||
360
UniTAP/dev/ports/modules/link/dp/link_tx_config.py
Normal file
360
UniTAP/dev/ports/modules/link/dp/link_tx_config.py
Normal 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
|
||||
86
UniTAP/dev/ports/modules/link/dp/link_tx_force_config.py
Normal file
86
UniTAP/dev/ports/modules/link/dp/link_tx_force_config.py
Normal 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])
|
||||
432
UniTAP/dev/ports/modules/link/dp/link_tx_status.py
Normal file
432
UniTAP/dev/ports/modules/link/dp/link_tx_status.py
Normal 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"
|
||||
329
UniTAP/dev/ports/modules/link/dp/link_tx_types.py
Normal file
329
UniTAP/dev/ports/modules/link/dp/link_tx_types.py
Normal 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)
|
||||
122
UniTAP/dev/ports/modules/link/dp/private_link_rx_types.py
Normal file
122
UniTAP/dev/ports/modules/link/dp/private_link_rx_types.py
Normal 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)
|
||||
]
|
||||
157
UniTAP/dev/ports/modules/link/dp/private_link_status_common.py
Normal file
157
UniTAP/dev/ports/modules/link/dp/private_link_status_common.py
Normal 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
|
||||
107
UniTAP/dev/ports/modules/link/dp/private_link_tx_types.py
Normal file
107
UniTAP/dev/ports/modules/link/dp/private_link_tx_types.py
Normal 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
|
||||
1
UniTAP/dev/ports/modules/link/hdmi/__init__.py
Normal file
1
UniTAP/dev/ports/modules/link/hdmi/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .types import FrlMode, FrlCaps, ClockRate, HdmiModeTx, HdmiModeRx, LinkMode
|
||||
134
UniTAP/dev/ports/modules/link/hdmi/arc_rx.py
Normal file
134
UniTAP/dev/ports/modules/link/hdmi/arc_rx.py
Normal 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"
|
||||
45
UniTAP/dev/ports/modules/link/hdmi/capabilities.py
Normal file
45
UniTAP/dev/ports/modules/link/hdmi/capabilities.py
Normal 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
|
||||
139
UniTAP/dev/ports/modules/link/hdmi/frl_caps_rx.py
Normal file
139
UniTAP/dev/ports/modules/link/hdmi/frl_caps_rx.py
Normal 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"
|
||||
366
UniTAP/dev/ports/modules/link/hdmi/frl_control_tx.py
Normal file
366
UniTAP/dev/ports/modules/link/hdmi/frl_control_tx.py
Normal 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"
|
||||
53
UniTAP/dev/ports/modules/link/hdmi/hdmi_utils.py
Normal file
53
UniTAP/dev/ports/modules/link/hdmi/hdmi_utils.py
Normal 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
|
||||
171
UniTAP/dev/ports/modules/link/hdmi/link.py
Normal file
171
UniTAP/dev/ports/modules/link/hdmi/link.py
Normal 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
|
||||
52
UniTAP/dev/ports/modules/link/hdmi/private_types.py
Normal file
52
UniTAP/dev/ports/modules/link/hdmi/private_types.py
Normal 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
|
||||
151
UniTAP/dev/ports/modules/link/hdmi/status_rx.py
Normal file
151
UniTAP/dev/ports/modules/link/hdmi/status_rx.py
Normal 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"
|
||||
140
UniTAP/dev/ports/modules/link/hdmi/status_tx.py
Normal file
140
UniTAP/dev/ports/modules/link/hdmi/status_tx.py
Normal 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"
|
||||
57
UniTAP/dev/ports/modules/link/hdmi/tmds_rx.py
Normal file
57
UniTAP/dev/ports/modules/link/hdmi/tmds_rx.py
Normal 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'}"
|
||||
99
UniTAP/dev/ports/modules/link/hdmi/tmds_tx.py
Normal file
99
UniTAP/dev/ports/modules/link/hdmi/tmds_tx.py
Normal 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"
|
||||
372
UniTAP/dev/ports/modules/link/hdmi/types.py
Normal file
372
UniTAP/dev/ports/modules/link/hdmi/types.py
Normal 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]
|
||||
4
UniTAP/dev/ports/modules/panel_replay/__init__.py
Normal file
4
UniTAP/dev/ports/modules/panel_replay/__init__.py
Normal 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
|
||||
73
UniTAP/dev/ports/modules/panel_replay/pr.py
Normal file
73
UniTAP/dev/ports/modules/panel_replay/pr.py
Normal 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)
|
||||
|
||||
132
UniTAP/dev/ports/modules/panel_replay/pr_config.py
Normal file
132
UniTAP/dev/ports/modules/panel_replay/pr_config.py
Normal 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]
|
||||
161
UniTAP/dev/ports/modules/panel_replay/pr_private_types.py
Normal file
161
UniTAP/dev/ports/modules/panel_replay/pr_private_types.py
Normal 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)
|
||||
]
|
||||
59
UniTAP/dev/ports/modules/panel_replay/pr_sink.py
Normal file
59
UniTAP/dev/ports/modules/panel_replay/pr_sink.py
Normal 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
|
||||
111
UniTAP/dev/ports/modules/panel_replay/pr_sink_caps.py
Normal file
111
UniTAP/dev/ports/modules/panel_replay/pr_sink_caps.py
Normal 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))
|
||||
111
UniTAP/dev/ports/modules/panel_replay/pr_sink_private_types.py
Normal file
111
UniTAP/dev/ports/modules/panel_replay/pr_sink_private_types.py
Normal 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
Reference in New Issue
Block a user