1.1.0版本
This commit is contained in:
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)
|
||||
Reference in New Issue
Block a user