# Copyright 2025 Acute Technology Inc. All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause
"""Base I3C Controller Class."""

from enum import IntEnum
from typing import TYPE_CHECKING, ClassVar

from aqpxlib.abstract import PxAbstractController
from aqpxlib.exceptions import PxError
from aqpxlib.i3c.ccc import PxI3CCommonCommandCode
from aqpxlib.message import (
    I2CReadTransaction,
    I2CWriteTransaction,
    I3CBroadcastCccTransaction,
    I3CCommonCommandCode,
    I3Cdaa,
    I3CDirectCccTransaction,
    I3CHdrDdrReadTransaction,
    I3CHdrDdrWriteTransaction,
    I3CInitBus,
    I3CParityMode,
    I3CPrivateReadTransaction,
    I3CPrivateWriteTransaction,
    I3CResetDaa,
    I3CSendTransaction,
    I3CSendTransactionStopType,
    I3CTransaction,
    PxDeviceOperation,
)
from aqpxlib.message import I3CControllerConfig as PxI3CBaseControllerConfig
from aqpxlib.message import I3CControllerState as PxI3CBaseControllerState

if TYPE_CHECKING:
    from aqpxlib.toplevel import AqProtocolExerciser


class PxI3CBaseController(PxAbstractController):
    """An I3C basic controller device class.

    This class provides a high-level interface to an I3C controller with basic
    I3C functionality. It does not automatically create an actual instance on
    the Protocol Exerciser until you call the `attach_to_bus` method.

    Typical usage example:

    ```python
    i3c_bus = PxI3CBus(exerciser_session)
    i3c_controller = PxI3CBaseController(exerciser_session)
    i3c_controller.attach_to_bus(i3c_bus)

    ... # Do any operation initiated from this controller

    i3c_controller.detach_from_bus()
    ```
    """

    # Used to identify the class
    identifier: str = "i3c_base_controller"
    valid_buses: ClassVar[list[str]] = ["i3c"]

    protobuf_key: str = "i3_c_controller"

    # All inherited devices shall add the event fields to the list they accept
    _protobuf_available_event_fields: ClassVar[list[str]] = [
        "i3_c_controller_recv_ibi_data",
    ]

    def __init__(
        self,
        exerciser: "AqProtocolExerciser",
        *args,
        config: PxI3CBaseControllerConfig | None = None,
        **kwargs,
    ) -> None:
        """Initialize an I3C Controller."""
        if config is None:
            config = PxI3CBaseControllerConfig(
                enabled=1,
            )
        else:
            assert isinstance(config, PxI3CBaseControllerConfig)

        super().__init__(exerciser, config=config, *args, **kwargs)

    def init_bus(self, voltage: int | None = None) -> bool:
        """Initialize the bus with a given voltage (in mV).

        Args:
            voltage: The voltage to initialize the bus with (in mV). If not provided, \
                     the controller will try to initiate the initialization procedure \
                     with the voltage previously set on the bus.

        Returns:
            True if the bus is initialized successfully. Otherwise, return False.

        """
        if voltage is not None:
            self.bus.voltage = voltage
        else:
            pass

        try:
            self.perform_operation(
                operation=PxDeviceOperation(
                    i3_c_controller_init_bus=I3CInitBus(),
                ),
            )
        except PxError:
            return False

        return True

    def reset_bus(self):
        """Reset the bus."""
        # TODO: Implement this

    def do_daa(self) -> bool:
        """Perform a ENTDAA operation on the I3C bus.

        This function start initiating Dynamic Address Assignment on the I3C
        devices. All device information are automatically retrieved after the
        operation is completed.

        Returns:
            True if the operation is successful. Otherwise, return False.

        """
        try:
            self.perform_operation(
                operation=PxDeviceOperation(
                    i3_c_controller_do_daa=I3Cdaa(),
                ),
            )
        except PxError:
            return False

        return True

    def do_rstdaa(self) -> bool:
        """Perform a RSTDAA operation on the I3C bus.

        This method initiates a Reset Dynamic Address Assignment process on all
        devices on the I3C bus. This shall remove all assigned dynamic addresses
        from all the devices on the bus.

        Returns:
            True if the operation is successful. Otherwise, return False.

        """
        try:
            self.perform_operation(
                operation=PxDeviceOperation(
                    i3_c_controller_rst_daa=I3CResetDaa(),
                ),
            )
        except PxError:
            return False

        return True

    ###################################################
    # Transaction Functions                           #
    ###################################################

    class TransactionStopType(IntEnum):
        """Enumeration of supported transaction stop types.

        This is intended to avoid exporting protobuf enum to the interface.
        """

        PUSH_PULL = I3CSendTransactionStopType.PUSH_PULL
        OPEN_DRAIN = I3CSendTransactionStopType.OPEN_DRAIN
        HDR_EXIT = I3CSendTransactionStopType.HDR_EXIT
        TARGET_RESET = I3CSendTransactionStopType.TARGET_RESET

    def send_transactions(
        self,
        transactions: list[I3CTransaction],
        stop_type: TransactionStopType,
    ) -> list[I3CTransaction]:
        """Send a sequence of I3C transactions.

        This method enqueues a sequence of I3C transactions to be sent to
        the I3C bus, starting from a `START` pattern end with one of the supported
        stop types determined by the `stop_type` parameter. Each element in
        `transactions` is an `I3CTransaction` object, which contains one of the
        supported transaction types listed below:

        * I2CWriteTransaction
        * I2CReadTransaction
        * I3CBroadcastCccTransaction
        * I3CDirectCccTransaction
        * I3CPrivateWriteTransaction
        * I3CPrivateReadTransaction

        The transactions are sent in the order they are provided.

        Args:
            transactions: A list of `I3CTransaction` objects to be sent.
            stop_type: enumerated value of TransactionStopType

        Returns:
            A list of `I3CTransaction` objects, which contains the responses of
                the transactions sent. If any transaction responds with any data,
                it will be carried along with the `I3CTransaction` object.

        Raises:
            PxError: If the transaction fails.

        """
        return self.perform_operation(
            operation=PxDeviceOperation(
                i3_c_controller_send_transaction=I3CSendTransaction(
                    transactions=transactions,
                    stop_type=(I3CSendTransactionStopType)(stop_type.value),
                ),
            ),
        ).i3_c_controller_send_transaction.transactions

    ###################################################
    # I2C Functions                                   #
    ###################################################

    def i2c_write(self, address: int, data: list[int]) -> int | None:
        """Perform a write operation to an I2C device.

        This method sends data to an I2C device with a given `address`.

        Args:
            address: The address of the I2C device to write to.
            data: The data to be written to the I2C device.

        Returns:
            The number of bytes written. Returns None if the target NACKs.

        Raises:
            ValueError: `data` must be a list of integers within (0, 256).

        """
        transactions = [
            I3CTransaction(
                i2_c_write=I2CWriteTransaction(
                    address=address,
                    data=bytes(data),
                ),
            ),
        ]

        resp = self.send_transactions(
            transactions,
            self.TransactionStopType.OPEN_DRAIN,
        )

        if not resp[0].i2_c_write.is_ack:
            return None
        return resp[0].i2_c_write.bytes_written

    def i2c_read(self, address: int, count: int) -> list[int]:
        """Perform a read operation from an I2C device.

        This method tries to read data from an I2C device with a given `address`.
        If may return less than `count` bytes if the abort the read operation early.

        Args:
            address: The address of the I2C device to read from.
            count: The number of bytes to read from the I2C device.

        Returns:
            The data read from the I2C device. Returns an empty list if the target NACKs.

        Raises:
            ValueError: If the count is 0.

        """
        if count == 0:
            err_msg = "Cannot perform read operation with count 0"
            raise ValueError(err_msg)

        transactions = [
            I3CTransaction(
                i2_c_read=I2CReadTransaction(
                    address=address,
                    count=count,
                ),
            ),
        ]

        resp = self.send_transactions(
            transactions,
            self.TransactionStopType.OPEN_DRAIN,
        )
        if not resp[0].i2_c_read.is_ack:
            return []
        return list(resp[0].i2_c_read.data)

    def i2c_read_addr(
        self,
        i2c_address: int,
        sub_address: int,
        count: int,
    ) -> list[int]:
        """Perform a read operation from an I2C device with subaddresses.

        This method tries to read data from an I2C device with a given `address`.
        If may return less than `count` bytes if the abort the read operation early.

        Args:
            i2c_address: The address of the I2C device to read from.
            sub_address: The subaddress of the I2C device to read from.
            count: The number of bytes to read from the I2C device.

        Returns:
            The data read from the I2C device. Returns an empty list if the target NACKs.

        Raises:
            OverflowError: If the `sub_address` is not within (0, 256).
            ValueError: If the count is 0.

        """
        if count == 0:
            err_msg = "Cannot perform read operation with count 0"
            raise ValueError(err_msg)

        transactions = [
            I3CTransaction(
                i2_c_write=I2CWriteTransaction(
                    address=i2c_address,
                    data=sub_address.to_bytes(1, "big"),
                ),
            ),
            I3CTransaction(
                i2_c_read=I2CReadTransaction(
                    address=i2c_address,
                    count=count,
                ),
            ),
        ]

        resp = self.send_transactions(
            transactions,
            self.TransactionStopType.OPEN_DRAIN,
        )
        if not resp[0].i2_c_write.is_ack:
            return []

        return list(resp[1].i2_c_read.data)

    ###################################################
    # I3C Private Functions                           #
    ###################################################

    class ParityMode(IntEnum):
        """Enumeration of supported parity modes.

        This is intended to avoid exporting protobuf enum to the interface.
        """

        RAW = I3CParityMode.RAW
        FLAG = I3CParityMode.FLAG
        ALL_CORRECT = I3CParityMode.ALL_CORRECT
        ALL_INCORRECT = I3CParityMode.ALL_INCORRECT

    def private_write(
        self,
        address: int,
        data: list[int],
        parity: list[int] | None = None,
        parity_mode: ParityMode = ParityMode.ALL_CORRECT,
        with_broadcast_hdr: bool = True,
    ) -> int:
        """Perform a private write transaction to write data to an I3C device.

        This method sends data to an I3C device with a given `address`. It includes
        various options to specify how the operation is performed, such as the
        parity value or with/without broadcast header. One can choose to send
        an invalid parity by manually setting incorrect parity bit value.

        Parity mode options are:

        | Parity Modes | Value for `parity` | Description |
        |------------- | ------------------ |------------ |
        | ParityMode.RAW | Required | Raw parity provided by the caller. |
        | ParityMode.FLAG | Bitmask for parity of each data byte. | 1 for incorrect parity, 0 for correct parity. |
        | ParityMode.ALL_CORRECT | - | All correct parity calculated internally |
        | ParityMode.ALL_INCORRECT | - | All incorrect parity calculated internally |

        Args:
            address: The address of the I3C device to write to.
            data: The data to be written to the I3C device.
            parity: The parity to use for the operation. This parameter is required
                    when `parity_mode` is either `ParityMode.RAW` or
                    `ParityMode.FLAG`. Otherwise, it will be ignored.
            parity_mode: The parity mode to use for the operation.
            with_broadcast_hdr: Whether to use the broadcast 7'7Eh header.

        Returns:
            1 if the target at `address` acknowledges the command. Otherwise, return 0.

        """
        transactions = [
            I3CTransaction(
                priv_write=I3CPrivateWriteTransaction(
                    address=address,
                    data=bytes(data),
                    parity=bytes(parity) if parity else None,
                    parity_mode=(I3CParityMode)(parity_mode.value),
                    with_broadcast_hdr=with_broadcast_hdr,
                ),
            ),
        ]

        resp = self.send_transactions(
            transactions,
            self.TransactionStopType.PUSH_PULL,
        )

        return resp[0].priv_write.is_ack

    def private_read_addr(
        self,
        address: int,
        sub_address: int,
        count: int,
        parity: list[int] | None = None,
        parity_mode: ParityMode = ParityMode.ALL_CORRECT,
        with_broadcast_hdr: bool = True,
    ) -> list[int]:
        """Read data from an I3C device with a subaddress."""
        transactions = [
            I3CTransaction(
                priv_write=I3CPrivateWriteTransaction(
                    address=address,
                    data=sub_address.to_bytes(1, "big"),
                    parity=bytes(parity) if parity else None,
                    parity_mode=(I3CParityMode)(parity_mode.value),
                    with_broadcast_hdr=with_broadcast_hdr,
                ),
            ),
            I3CTransaction(
                priv_read=I3CPrivateReadTransaction(
                    address=address,
                    count=count,
                    with_broadcast_hdr=False,
                ),
            ),
        ]

        resp = self.send_transactions(
            transactions,
            self.TransactionStopType.PUSH_PULL,
        )
        if not resp[0].priv_write.is_ack or not resp[1].priv_read.is_ack:
            return []
        return list(resp[1].priv_read.data)

    def private_read(
        self,
        address: int,
        count: int,
        with_broadcast_hdr: bool = True,
    ) -> bytes | None:
        """Perform a private read transaction to read data from an I3C device.

        This method tries to read data from an I3C device with a given `address`.
        It includes options to include broadcast header or not.

        Args:
            address: The address of the I3C device to read from.
            count: The number of bytes to read from the I3C device.
            with_broadcast_hdr: Whether to include the broadcast 7'7Eh header.

        Returns:
            The data read from the I3C device. Returns None if the target NACKs.

        Raises:
            ValueError: If the count is 0.

        """
        if count == 0:
            err_msg = "Cannot perform read operation with count 0"
            raise ValueError(err_msg)

        transactions = [
            I3CTransaction(
                priv_read=I3CPrivateReadTransaction(
                    address=address,
                    count=count,
                    with_broadcast_hdr=with_broadcast_hdr,
                ),
            ),
        ]

        resp = self.send_transactions(
            transactions,
            self.TransactionStopType.PUSH_PULL,
        )

        if not resp[0].priv_read.is_ack:
            return None
        return resp[0].priv_read.data

    ###################################################
    # I3C Broadcast Functions                         #
    ###################################################

    def broadcast_ccc_write(
        self,
        ccc: int | PxI3CCommonCommandCode,
        data: list[int],
        defining_byte: int | None = None,
        ccc_parity_mode: ParityMode = ParityMode.ALL_CORRECT,
        parity_mode: ParityMode = ParityMode.ALL_CORRECT,
        parity: list[int] | None = None,
    ) -> bool:
        """Perform a broadcast CCC transaction on the I3C bus.

        This method sends a broadcast CCC command, followed by target addresses and
        the data to be written, to all targets on the I3C bus.

        Parity mode options for `ccc_parity_mode` and `parity_mode` are:

        | Parity Modes | Value for `parity` | Description |
        |------------- | ------------------ |------------ |
        | ParityMode.RAW | Required | Raw parity provided by the caller. |
        | ParityMode.FLAG | Bitmask for parity of each data byte. | 1 for incorrect parity, 0 for correct parity. |
        | ParityMode.ALL_CORRECT | - | All correct parity calculated internally |
        | ParityMode.ALL_INCORRECT | - | All incorrect parity calculated internally |

        Args:
            ccc: The broadcast CCC command to write.
            ccc_parity_mode: The parity mode to use for the broadcast CCC command.
            data: A list of tuples with a format of (address, list[data]) to \
                  write to the I3C device.
            defining_byte: The defining byte to use for this broadcast CCC transaction.
            parity_mode: The parity mode to use for this broadcast CCC transaction.
            parity: The parity to use for this broadcast CCC transaction.

        Returns:
            True if the transaction is successful. Otherwise, return False.

        Raises:
            ValueError: If the `data` or `parity` is not a list of integers within (0, 256).

        """
        transactions = [
            I3CTransaction(
                ccc_broadcast=I3CBroadcastCccTransaction(
                    ccc=PxI3CCommonCommandCode(ccc),
                    ccc_parity_mode=(I3CParityMode)(ccc_parity_mode.value),
                    parity=bytes(parity) if parity else None,
                    data=bytes(data) if defining_byte is None else (defining_byte.to_bytes(1, "big") + bytes(data)),
                    parity_mode=(I3CParityMode)(parity_mode.value),
                ),
            ),
        ]

        try:
            _ = self.send_transactions(
                transactions,
                self.TransactionStopType.PUSH_PULL,
            )
        except PxError:
            return False

        return True

    def broadcast_enec(self, event: int) -> bool:
        """Perform a broadcast ENEC transaction on the I3C bus.

        This allow controllers to enable Target-initiated events on the I3C bus.
        It always includes a 1-byte event byte in the data payload as shown below.

        | Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
        | ----- | ----- | ----- | ----- | ----- | ----- | ----- | ----- |
        | RESV  | RESV  | RESV  | RESV  | ENHJ  | RESV  | ENCR  | ENINT |

        Args:
            event: The events to enable.

        Returns:
            True if the events are enabled successfully. Otherwise, return False.

        """
        return self.broadcast_ccc_write(
            ccc=PxI3CCommonCommandCode.I3C_CCC_BROADCAST_ENEC,
            data=[event],
        )

    def broadcast_disec(self, event: int) -> bool:
        """Perform a broadcast DISEC transaction on the I3C bus.

        This allow controllers to disable Target-initiated events on the I3C bus,
        the opposite operation of ENEC. It always includes a 1-byte event byte in
        the data payload as shown below.

        | Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
        | ----- | ----- | ----- | ----- | ----- | ----- | ----- | ----- |
        | RESV  | RESV  | RESV  | RESV  | ENHJ  | RESV  | ENCR  | ENINT |

        Args:
            event: The events to disable.

        Returns:
            True if the events are disabled successfully. Otherwise, return False.

        """
        return self.broadcast_ccc_write(
            ccc=PxI3CCommonCommandCode.I3C_CCC_BROADCAST_DISEC,
            data=[event],
        )

    def broadcast_entas0(self) -> bool:
        """Perform a broadcast ENTAS0 transaction on the I3C bus.

        ENTAS0 is used when the controller is ready to enter Activity State 0,
        which informs all targets that it will not be active on the I3C Bus for
        approximately 1 us.

        Returns:
            True if the tranasction is sent successfully. Otherwise, return False.

        """
        return self.broadcast_ccc_write(
            ccc=PxI3CCommonCommandCode.I3C_CCC_BROADCAST_ENTAS0,
            data=[],
        )

    def broadcast_entas1(self) -> bool:
        """Perform a broadcast ENTAS1 transaction on the I3C bus.

        ENTAS1 is used when the controller is ready to enter Activity State 1,
        which informs all targets that it will not be active on the I3C Bus for
        approximately 100 us.

        Returns:
            True if the tranasction is sent successfully. Otherwise, return False.

        """
        return self.broadcast_ccc_write(
            ccc=PxI3CCommonCommandCode.I3C_CCC_BROADCAST_ENTAS1,
            data=[],
        )

    def broadcast_entas2(self) -> bool:
        """Perform a broadcast ENTAS2 transaction on the I3C bus.

        ENTAS2 is used when the controller is ready to enter Activity State 2,
        which informs all targets that it will not be active on the I3C Bus for
        approximately 2 ms.

        Returns:
            True if the tranasction is sent successfully. Otherwise, return False.

        """
        return self.broadcast_ccc_write(
            ccc=PxI3CCommonCommandCode.I3C_CCC_BROADCAST_ENTAS2,
            data=[],
        )

    def broadcast_entas3(self) -> bool:
        """Perform a broadcast ENTAS3 transaction on the I3C bus.

        ENTAS3 is used when the controller is ready to enter Activity State 3,
        which informs all targets that it will not be active on the I3C Bus for
        approximately 50 ms.

        Returns:
            True if the tranasction is sent successfully. Otherwise, return False.

        """
        return self.broadcast_ccc_write(
            ccc=PxI3CCommonCommandCode.I3C_CCC_BROADCAST_ENTAS3,
            data=[],
        )

    def broadcast_setmwl(self, max_write_length: int) -> bool:
        """Perform a broadcast SETMWL transaction on the I3C bus.

        The controller sets all targets' maximum write length to the given value.
        However, it does not affect data write length for CCCs, since their data
        payloads are defined by each CCC.

        Args:
            max_write_length: The maximum data write length for all targets.

        Returns:
            True if the maximum data write length for all targets is set successfully.
            Otherwise, return False.

        Raises:
            ValueError: If the `max_write_length` is not within (0, 65536).

        """
        if max_write_length >= 65536 or max_write_length < 0:
            err_msg = "max_write_length must be within (0, 65536)"
            raise ValueError(err_msg)

        return self.broadcast_ccc_write(
            ccc=PxI3CCommonCommandCode.I3C_CCC_BROADCAST_SETMWL,
            data=list(max_write_length.to_bytes(2, "big")),
        )

    def broadcast_setmrl(
        self,
        max_read_length: int,
        ibi_payload_size: int | None = None,
    ) -> bool:
        """Perform a broadcast SETMRL transaction on the I3C bus.

        The controller sets all targets' maximum read length to the given value.
        For devices that its BCR[2] = 1, the maximum IBI payload size value as
        a third byte, where a value of 0 indicates an unlimited payload size.

        Args:
            max_read_length: The maximum data read length for all targets.
            ibi_payload_size: The maximum IBI payload size set to all targets.

        Returns:
            True if the maximum data read length for all targets is set successfully.
            Otherwise, return False.

        Raises:
            ValueError: If the `max_read_length` is not within (0, 65536).
            ValueError: If the `ibi_payload_size` is not within (0, 256).

        """
        if max_read_length >= 65536 or max_read_length < 0:
            err_msg = "max_read_length must be within (0, 65536)"
            raise ValueError(err_msg)

        if ibi_payload_size is not None and (ibi_payload_size >= 256 or ibi_payload_size < 0):
            err_msg = "ibi_payload_size must be length of 1 byte"
            raise ValueError(err_msg)

        data = list(max_read_length.to_bytes(2, "big"))
        if ibi_payload_size is not None:
            data.append(ibi_payload_size)

        return self.broadcast_ccc_write(
            ccc=PxI3CCommonCommandCode.I3C_CCC_BROADCAST_SETMRL,
            data=data,
        )

    def broadcast_endxfer(self, data: list[int], defining_byte: int) -> bool:
        """Perform a broadcast ENDXFER transaction on the I3C bus.

        ENDXFER is used when Controllers and Targets exchange setup
        parameters for ending data transfers in HDR modes. It always requires
        an 1-byte defining byte in the data payload, which acts as a sub-command
        identifier defining the specific function.

        Args:
            data: Additional data bytes to be sent to the target, which
                  are defined by the defining byte.
            defining_byte: The defining byte to use for the transaction.

        Returns:
            True if the transaction is successful. Otherwise, return False.

        """
        return self.broadcast_ccc_write(
            ccc=PxI3CCommonCommandCode.I3C_CCC_BROADCAST_ENDXFER,
            data=data,
            defining_byte=defining_byte,
        )

    def broadcast_setxtime(
        self,
        exchange_time_info: int,
        additional_data: list[int] | None = None,
    ) -> bool:
        """Perform a broadcast SETXTIME transaction on the I3C bus.

        SETXTIME is used when Controllers and Targets exchange event timing
        information. It always includes a 1-byte sub-command byte (`exchange_time_info`)
        in the data payload.

        Args:
            exchange_time_info: The exchange time parameters to set.
            additional_data: The additional data to set.

        Returns:
            True if the exchange time parameters are set successfully. Otherwise, return False.

        """
        data = list(exchange_time_info.to_bytes(1, "big"))
        if additional_data is not None:
            data.extend(additional_data)

        return self.broadcast_ccc_write(
            ccc=PxI3CCommonCommandCode.I3C_CCC_BROADCAST_SETXTIME,
            data=data,
        )

    def broadcast_setaasa(self) -> bool:
        """Instruct all targets to use their static address as dynamic address.

        Controller tells all targets to use their static address as dynamic address.
        This is a faster method to assign dynamic addresses to targets.

        Returns:
            True if the static address is used as dynamic address successfully.
            Otherwise, return False.

        """
        return self.broadcast_ccc_write(
            ccc=PxI3CCommonCommandCode.I3C_CCC_BROADCAST_SETAASA,
            data=[],
        )

    def broadcast_rstgrpa(self) -> bool:
        """Perform a broadcast RSTGRPA transaction on the I3C bus.

        Controller reset the all targets' group addresses.

        Returns:
            True if the all targets' group addresses are reset successfully.
            Otherwise, return False.

        """
        return self.broadcast_ccc_write(
            ccc=PxI3CCommonCommandCode.I3C_CCC_BROADCAST_RSTGRPA,
            data=[],
        )

    ###################################################
    # I3C Direct Functions                            #
    ###################################################

    def direct_ccc_write(
        self,
        ccc: int | PxI3CCommonCommandCode,
        data: list[tuple[int, list[int]]],
        defining_byte: int | None = None,
        ccc_parity_mode: ParityMode = ParityMode.ALL_CORRECT,
        defining_byte_parity_mode: ParityMode = ParityMode.ALL_CORRECT,
        parity_mode: ParityMode = ParityMode.ALL_CORRECT,
    ) -> list[int]:
        """Perform a direct CCC transaction with the data transfer direction from the controller to the targets.

        This method sends a direct CCC command, followed by target addresses and
        the amount of data to be written to each target, from a list of I3C target devices.

        Args:
            ccc: The direct CCC command to write.
            ccc_parity_mode: The parity mode to use for the direct CCC command.
            data: A list of tuples with a format of (address, list[data]) to \
                  write to the I3C device.
            defining_byte: The defining byte to use for this direct CCC transaction.
            defining_byte_parity_mode: The parity mode to use for the defining byte.
            parity_mode: The parity mode to use for the data.

        Returns:
            The list of ACK that response to each address.

        Raises:
            ValueError: If the `data` is not a list of integers within (0, 256).

        """
        transactions = [
            I3CTransaction(
                ccc_direct=I3CDirectCccTransaction(
                    ccc=PxI3CCommonCommandCode(ccc),
                    ccc_parity_mode=(I3CParityMode)(ccc_parity_mode.value),
                    defining_byte=defining_byte,
                    defining_byte_parity_mode=(I3CParityMode)(defining_byte_parity_mode.value),
                    write_ops=[
                        I3CPrivateWriteTransaction(
                            address=addr,
                            data=bytes(d),
                            parity_mode=(I3CParityMode)(parity_mode.value),
                            with_broadcast_hdr=False,
                        )
                        for addr, d in data
                    ],
                ),
            ),
        ]

        resp = self.send_transactions(
            transactions,
            self.TransactionStopType.PUSH_PULL,
        )

        return [op.is_ack for op in resp[0].ccc_direct.write_ops]

    def direct_ccc_read(
        self,
        ccc: int | PxI3CCommonCommandCode,
        addresses: list[int],
        count: int,
        defining_byte: int | None = None,
    ) -> list[list[int] | None]:
        """Perform a direct CCC transaction with the data transfer direction from the targets to the controller.

        This method sends a direct CCC command, followed by target addresses and
        the amount of data to be read, from a list of I3C target devices.

        Args:
            ccc: The direct CCC command to read.
            ccc_parity_mode: The parity mode to use for the direct CCC command.
            addresses: A list of target addresses to read from.
            count: The number of bytes to read from each target address.
            defining_byte: The defining byte to use for this direct CCC transaction.

        Returns:
            A list of bytes read from each corresponding I3C target device, or None if the target NAK'd. \
            Return an empty list if the transaction fails.

        """
        transactions = [
            I3CTransaction(
                ccc_direct=I3CDirectCccTransaction(
                    ccc=PxI3CCommonCommandCode(ccc),
                    ccc_parity_mode=(I3CParityMode)(self.ParityMode.ALL_CORRECT.value),
                    defining_byte=defining_byte,
                    defining_byte_parity_mode=(I3CParityMode)(self.ParityMode.ALL_CORRECT.value),
                    read_ops=[
                        I3CPrivateReadTransaction(
                            address=addr,
                            count=count,
                            with_broadcast_hdr=False,
                        )
                        for addr in addresses
                    ],
                ),
            ),
        ]

        resp = self.send_transactions(
            transactions,
            self.TransactionStopType.PUSH_PULL,
        )

        return [list(op.data) if op.is_ack else None for op in resp[0].ccc_direct.read_ops]

    def direct_enec(self, addresses: list[int], event: int) -> list[int]:
        """Perform a direct ENEC transaction on the I3C bus.

        This allow controllers to enable Target-initiated events to individual
        targets on the I3C bus. It always includes a 1-byte event byte in the
        data payload as shown below.

        | Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
        | ----- | ----- | ----- | ----- | ----- | ----- | ----- | ----- |
        | RESV  | RESV  | RESV  | RESV  | ENHJ  | RESV  | ENCR  | ENINT |

        Args:
            addresses: The addresses of the I3C devices within the transaction.
            event: The events to enable.

        Returns:
            The list of ACKs (1 for ACK, 0 for NAK) that each address responded.

        """
        return self.direct_ccc_write(
            ccc=PxI3CCommonCommandCode.I3C_CCC_DIRECT_ENEC,
            data=[(addr, [event]) for addr in addresses],
        )

    def direct_disec(self, addresses: list[int], event: int) -> list[int]:
        """Perform a direct ENEC transaction on the I3C bus.

        This allow controllers to disable Target-initiated events to individual
        targets on the I3C bus. It always includes a 1-byte event byte in the
        data payload as shown below.

        | Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
        | ----- | ----- | ----- | ----- | ----- | ----- | ----- | ----- |
        | RESV  | RESV  | RESV  | RESV  | ENHJ  | RESV  | ENCR  | ENINT |

        Args:
            addresses: The addresses of the I3C devices within the transaction.
            event: The events to disable.

        Returns:
            The list of ACKs (1 for ACK, 0 for NAK) that each address responded.

        """
        return self.direct_ccc_write(
            ccc=PxI3CCommonCommandCode.I3C_CCC_DIRECT_DISEC,
            data=[(addr, [event]) for addr in addresses],
        )

    def direct_entas0(self, addresses: list[int]) -> list[int]:
        """Perform a direct ENTAS0 transaction on the I3C bus.

        ENTAS0 is used when the controller is ready to enter Activity State 0.
        This tells the targets listed in `addresses` that it will not be active
        on the I3C Bus for approximately 1 us.

        Args:
            addresses: The addresses of the I3C devices within the transaction.

        Returns:
            The list of ACKs (1 for ACK, 0 for NAK) that each address responded.

        """
        return self.direct_ccc_write(
            ccc=PxI3CCommonCommandCode.I3C_CCC_DIRECT_ENTAS0,
            data=[(addr, []) for addr in addresses],
        )

    def direct_entas1(self, addresses: list[int]) -> list[int]:
        """Perform a direct ENTAS1 transaction on the I3C bus.

        ENTAS1 is used when the controller is ready to enter Activity State 0.
        This tells the targets listed in `addresses` that it will not be active
        on the I3C Bus for approximately 100 us.

        Args:
            addresses: The addresses of the I3C devices within the transaction.

        Returns:
            The list of ACKs (1 for ACK, 0 for NAK) that each address responded.

        """
        return self.direct_ccc_write(
            ccc=PxI3CCommonCommandCode.I3C_CCC_DIRECT_ENTAS1,
            data=[(addr, []) for addr in addresses],
        )

    def direct_entas2(self, addresses: list[int]) -> list[int]:
        """Perform a direct ENTAS2 transaction on the I3C bus.

        ENTAS2 is used when the controller is ready to enter Activity State 2.
        This tells the targets listed in `addresses` that it will not be active
        on the I3C Bus for approximately 2 ms.

        Args:
            addresses: The addresses of the I3C devices within the transaction.

        Returns:
            The list of ACKs (1 for ACK, 0 for NAK) that each address responded.

        """
        return self.direct_ccc_write(
            ccc=PxI3CCommonCommandCode.I3C_CCC_DIRECT_ENTAS2,
            data=[(addr, []) for addr in addresses],
        )

    def direct_entas3(self, addresses: list[int]) -> list[int]:
        """Perform a direct ENTAS3 transaction on the I3C bus.

        ENTAS3 is used when the controller is ready to enter Activity State 3.
        This tells the targets listed in `addresses` that it will not be active
        on the I3C Bus for approximately 50 ms.

        Args:
            addresses: The addresses of the I3C devices within the transaction.

        Returns:
            The list of ACKs (1 for ACK, 0 for NAK) that each address responded.

        """
        return self.direct_ccc_write(
            ccc=PxI3CCommonCommandCode.I3C_CCC_DIRECT_ENTAS3,
            data=[(addr, []) for addr in addresses],
        )

    def direct_setdasa(
        self,
        static_addresses: list[int],
        dynamic_addresses: list[int],
    ) -> list[int]:
        """Perform a direct SETDASA transaction on the I3C bus.

        Controller assigns a dynamic address to a target using the target's
        static address. It is generally faster than ENTDAA Dynamic Address Assignment
        procedure. SETDASA shall be used before the ENTDAA is used.

        Args:
            static_addresses: The list of static addresses of the I3C devices.
            dynamic_addresses: The list of dynamic addresses to be assigned to
                               the target devices.

        Returns:
            The list of ACKs (1 for ACK, 0 for NAK) that each address responded.

        """
        return self.direct_ccc_write(
            ccc=PxI3CCommonCommandCode.I3C_CCC_DIRECT_SETDASA,
            data=[
                (static_addr, [dynamic_addr << 1])
                for static_addr, dynamic_addr in zip(
                    static_addresses,
                    dynamic_addresses,
                    strict=True,
                )
            ],
        )

    def direct_setnewda(self, addresses: list[tuple[int, int]]) -> list[int]:
        """Perform a direct SETNEWDA transaction on the I3C bus.

        Controller assigns a dynamic address to a target or a secondary controller
        listed in `addresses`. If the target supports this CCC and acceptes the
        new address, then the target device shall change its current dynamic address.

        Args:
            addresses: The list of current addresses of the I3C devices.

        Returns:
            The list of ACKs (1 for ACK, 0 for NAK) that each address responded.

        """
        return self.direct_ccc_write(
            ccc=PxI3CCommonCommandCode.I3C_CCC_DIRECT_SETNEWDA,
            data=[(old_addr, [new_addr << 1]) for old_addr, new_addr in addresses],
        )

    def direct_setmwl(self, addresses: list[int], max_write_length: int) -> list[int]:
        """Perform a direct SETMWL transaction on the I3C bus.

        The controller maximum data write length to the targets listed in
        `addresses`.

        Args:
            addresses: The addresses of the I3C devices within the transaction.
            max_write_length: The maximum data write length to set.

        Returns:
            The list of ACKs (1 for ACK, 0 for NAK) that each address responded.

        Raises:
            ValueError: If the `max_write_length` is not within (0, 65536).

        """
        if max_write_length >= 65536 or max_write_length < 0:
            err_msg = "max_write_length must be within (0, 65536)"
            raise ValueError(err_msg)

        data = list(max_write_length.to_bytes(2, "big"))

        return self.direct_ccc_write(
            ccc=PxI3CCommonCommandCode.I3C_CCC_DIRECT_SETMWL,
            data=[(addr, data) for addr in addresses],
        )

    def direct_setmrl(
        self,
        addresses: list[int],
        max_read_length: int,
        ibi_payload_size: int | None = None,
    ) -> list[int]:
        """Perform a direct SETMRL transaction on the I3C bus.

        The controller maximum data read length to the targets listed in
        `addresses`. For devices that its BCR[2] = 1, the maximum IBI payload
        size value as a third byte, where a value of 0 indicates an unlimited
        payload size.

        Args:
            addresses: The addresses of the I3C devices within the transaction.
            max_read_length: The maximum data read length to set.
            ibi_payload_size: The maximum IBI payload size to set.

        Returns:
            The list of ACKs (1 for ACK, 0 for NAK) that each address responded.

        Raises:
            ValueError: If the `max_read_length` is not within (0, 65536).
            ValueError: If the `ibi_payload_size` is not within (0, 256).

        """
        if max_read_length >= 65536 or max_read_length < 0:
            err_msg = "max_read_length must be within (0, 65536)"
            raise ValueError(err_msg)

        if ibi_payload_size is not None and (ibi_payload_size >= 256 or ibi_payload_size < 0):
            err_msg = "ibi_payload_size must be within (0, 256)"
            raise ValueError(err_msg)

        data = list(max_read_length.to_bytes(2, "big"))
        if ibi_payload_size is not None:
            data.append(ibi_payload_size)

        return self.direct_ccc_write(
            ccc=PxI3CCommonCommandCode.I3C_CCC_DIRECT_SETMRL,
            data=[(addr, data) for addr in addresses],
        )

    def direct_getmwl(self, addresses: list[int]) -> list[list[int] | None]:
        """Perform a direct GETMWL transaction on the I3C bus.

        The controller gets the maximum data write length from the targets
        listed in `addresses`.

        Args:
            addresses: The addresses of the I3C devices within the transaction.

        Returns:
            A list of maximum data write length values read from each corresponding \
            I3C target device, or None if target devices NACKed the transaction. \
            The returned list is expected to have the same length as the `addresses` list.

        """
        return self.direct_ccc_read(
            ccc=PxI3CCommonCommandCode.I3C_CCC_DIRECT_GETMWL,
            addresses=addresses,
            count=2,
        )

    def direct_getmrl(self, addresses: list[int]) -> list[list[int] | None]:
        """Perform a direct GETMRL transaction on the I3C bus.

        The controller gets the maximum data read length from the targets
        listed in `addresses`.

        Args:
            addresses: The addresses of the I3C devices within the transaction.

        Returns:
            A list of maximum data read length values read from each corresponding \
            I3C target device, or None if target devices NACKed the transaction. \
            The returned list is expected to have the same length as the `addresses` list.

        """
        return self.direct_ccc_read(
            ccc=PxI3CCommonCommandCode.I3C_CCC_DIRECT_GETMRL,
            addresses=addresses,
            count=2,
        )

    def direct_getpid(self, addresses: list[int]) -> list[list[int] | None]:
        """Perform a direct GETPID transaction on the I3C bus.

        Fetch the Provisioned ID (PID) for a list of I3C targets.

        Args:
            addresses: The addresses of the I3C devices within the transaction.

        Returns:
            A list of PID values read from each corresponding I3C target device, \
            or None if target devices NACKed the transaction. The returned list \
            is expected to have the same length as the `addresses` list.

        """
        return self.direct_ccc_read(
            ccc=PxI3CCommonCommandCode.I3C_CCC_DIRECT_GETPID,
            addresses=addresses,
            count=6,
        )

    def direct_getbcr(self, addresses: list[int]) -> list[list[int] | None]:
        """Perform a direct GETBCR transaction on the I3C bus.

        Fetch the Bus Characteristics Register (BCR) for a list of I3C targets.

        Args:
            addresses: The addresses of the I3C devices within the transaction.

        Returns:
            A list of BCR values read from each corresponding I3C target device, \
            or None if target devices NACKed the transaction. The returned list \
            is expected to have the same length as the `addresses` list.

        """
        return self.direct_ccc_read(
            ccc=PxI3CCommonCommandCode.I3C_CCC_DIRECT_GETBCR,
            addresses=addresses,
            count=1,
        )

    def direct_getdcr(self, addresses: list[int]) -> list[list[int] | None]:
        """Perform a direct GETDCR transaction on the I3C bus.

        Fetch the Device Characteristics Register (DCR) for a list of I3C targets.

        Args:
            addresses: The addresses of the I3C devices within the transaction.

        Returns:
            A list of DCR values read from each corresponding I3C target device, \
            or None if target devices NACKed the transaction. The returned list \
            is expected to have the same length as the `addresses` list.

        """
        return self.direct_ccc_read(
            ccc=PxI3CCommonCommandCode.I3C_CCC_DIRECT_GETDCR,
            addresses=addresses,
            count=1,
        )

    def direct_getstatus(
        self,
        addresses: list[int],
        defining_byte: int | None = None,
        count: int = 2,
    ) -> list[list[int] | None]:
        """Perform a direct GETSTATUS transaction on the I3C bus.

        Fetch the current operating status for a list of I3C targets. The returned
        status' formats are defined by the defining byte.

        Args:
            addresses: The addresses of the I3C devices within the transaction.
            defining_byte: The defining byte used within the transaction. This
                           affects the data returned by the transaction.
            count: The number of bytes to read from each target device.

        Returns:
            A list of status values read from each corresponding I3C target device, \
            or None if target devices NACKed the transaction. The returned list \
            is expected to have the same length as the `addresses` list.

        """
        return self.direct_ccc_read(
            ccc=PxI3CCommonCommandCode.I3C_CCC_DIRECT_GETSTATUS,
            addresses=addresses,
            count=count,
            defining_byte=defining_byte,
        )

    def direct_getaccr(self, address: int) -> list[int] | None:
        """Perform a direct GETACCR transaction on the I3C bus.

        Controller gets the Access Control Register from a target
        Args:
            address: The address of the I3C device from which the Access Control Register is read.

        Returns:
            A list of bytes read from each corresponding I3C target device, which contains the capabilities, \
            or None if NAK

        """
        response = self.direct_ccc_read(
            ccc=PxI3CCommonCommandCode.I3C_CCC_DIRECT_GETACCCR,
            addresses=[address],
            count=1,
        )

        return response[0] if response else None

    def direct_endxfer(
        self,
        addresses: list[int],
        data: list[int],
        defining_byte: int,
    ) -> list[int]:
        """Perform a direct ENDXFER transaction on the I3C bus.

        ENDXFERS is used when controller and targets exchange set-up parameters
        for ending data transfers in HDR Modes, and for Monitoring Devices to
        request early termination of running transactions. It always requires
        an 1-byte defining byte in the data payload, which acts as a sub-command
        identifier defining the specific function.

        Args:
            addresses: The addresses of the I3C devices within the transaction.
            data: Additional data bytes to be sent to the target, which
                  are defined by the defining byte.
            defining_byte: The defining byte to use for the transaction.

        Returns:
            The list of ACKs (1 for ACK, 0 for NAK) that each address responded.

        """
        return self.direct_ccc_write(
            ccc=PxI3CCommonCommandCode.I3C_CCC_DIRECT_ENDXFER,
            data=[(addr, data) for addr in addresses],
            defining_byte=defining_byte,
        )

    def direct_getmxds(
        self,
        addresses: list[int],
        defining_byte: int | None = None,
        count: int = 2,
    ) -> list[list[int] | None]:
        """Perform a direct GETMXDS transaction on the I3C bus.

        The controller gets the maximum data speed from the targets listed in
        `addresses`. The maximum data speed is defined by the defining byte.

        Args:
            addresses: The list of addresses of the I3C devices from which the
                       target's maximum data speed is read.
            defining_byte: The defining byte to use for the transaction.
            count: The number of read back bytes.

        Returns:
            A list of maximum data speed values read from each corresponding \
            I3C target device, or None if target devices NACKed the transaction. \
            The returned list is expected to have the same length as the \
            `addresses` list. \

        """
        return self.direct_ccc_read(
            ccc=PxI3CCommonCommandCode.I3C_CCC_DIRECT_GETMXDS,
            addresses=addresses,
            count=count,
            defining_byte=defining_byte,
        )

    def direct_getcaps(
        self,
        addresses: list[int],
        defining_byte: int | None = None,
        count: int = 4,
    ) -> list[list[int] | None]:
        """Perform a direct GETCAPS transaction on the I3C bus.

        The controller gets the capabilities from the targets listed in
        `addresses`. The returned capabilities formats are defined by the
        defining byte.

        Args:
            addresses: The list of addresses of the I3C devices from which the
                       target's capabilities are read.
            defining_byte: The defining byte to use for the transaction.
            count: The number of read back bytes.

        Returns:
            A list of capabilities values read from each corresponding \
            I3C target device, or None if target devices NACKed the transaction. \
            The returned list is expected to have the same length as the \
            `addresses` list. \

        """
        return self.direct_ccc_read(
            ccc=PxI3CCommonCommandCode.I3C_CCC_DIRECT_GETCAPS,
            addresses=addresses,
            count=count,
            defining_byte=defining_byte,
        )

    def direct_setxtime(
        self,
        addresses: list[int],
        exchange_time_info: int,
        additional_data: list[int] | None = None,
    ) -> list[int]:
        """Perform a direct SETXTIME transaction on the I3C bus.

        SETXTIME is used when Controllers and Targets exchange event timing
        information. It always includes a 1-byte sub-command byte (`exchange_time_info`)
        in the data payload.

        Args:
            addresses: The list of addresses of the I3C devices to which the
                       target's exchange time parameters are set.
            exchange_time_info: The exchange time information to set. (Subcommand byte)
            additional_data: Additional data bytes to be sent to the target, which
                             are defined by the `exchange_time_info`.

        Returns:
            The list of ACKs (1 for ACK, 0 for NAK) that each address responded.

        """
        data = list(exchange_time_info.to_bytes(1, "big"))
        if additional_data is not None:
            data.extend(additional_data)

        return self.direct_ccc_write(
            ccc=PxI3CCommonCommandCode.I3C_CCC_DIRECT_SETXTIME,
            data=[(addr, data) for addr in addresses],
        )

    def direct_setgrpa(
        self,
        addresses: list[int],
        assigned_group_addresses: list[int],
    ) -> list[int]:
        """Perform a direct SETGRPA transaction on the I3C bus.

        SETGRPA is used when the controller wants to set the group address
        to a target.

        Args:
            addresses: The list of the target that is going to be assigned group address.
            assigned_group_addresses: The list of group address that is going to be assigned to targets.

        Returns:
            The list of ACK that response to each address.

        """
        data_list = []
        for addr in addresses:
            data_list = [(addr, [assigned_addr << 1]) for assigned_addr in assigned_group_addresses]

        return self.direct_ccc_write(
            ccc=PxI3CCommonCommandCode.I3C_CCC_DIRECT_SETGRPA,
            data=data_list,
        )

    def direct_rstgrpa(
        self,
        addresses: list[int],
    ) -> list[int]:
        """Perform a direct RSTGRPA transaction on the I3C bus.

        RSTGRPA is used when the controller wants to reset the group address of
        a target.

        Args:
            addresses: The list of the target address or group address that want to remove

        Returns:
            The list of ACK that response to each address.

        """
        return self.direct_ccc_write(
            ccc=PxI3CCommonCommandCode.I3C_CCC_DIRECT_RSTGRPA,
            data=[(addr, []) for addr in addresses],
        )

    ###################################################
    # I3C HDR Functions                               #
    ###################################################

    def hdr_ddr_write(
        self,
        command: int,
        address: int,
        data: list[int],
        parity: list[int] | None = None,
        parity_mode: ParityMode = ParityMode.ALL_CORRECT,
        crc5: int | None = None,
    ) -> None:
        """Perform a HDR-DDR write transaction on the I3C bus.

        HDR WRITE is used to write data to a target device.

        Args:
            command: The command to write to the target device.
            address: The address of the target device to write to.
            data: The data to write to the target device. It is expected to be odd \
                  number of bytes, since HDR-DDR write transaction is 16-bit aligned. \
                  If the length of data is not given with odd number of bytes, \
                  it will be padded with a zero byte at the end.
            parity: The parity to write to the target device.
            parity_mode: The parity mode to write to the target device.
            crc5: An optional CRC5 value to write to the target device. \
                  If not provided, the CRC5 will be calculated internally

        """
        if len(data) % 2 != 0:
            data.append(0x00)

        transactions = [
            I3CTransaction(
                ccc_broadcast=I3CBroadcastCccTransaction(
                    ccc=PxI3CCommonCommandCode(I3CCommonCommandCode.I3C_CCC_BROADCAST_ENTHDR0),
                    ccc_parity_mode=I3CParityMode.ALL_CORRECT,
                ),
            ),
            I3CTransaction(
                hdr_ddr_write=I3CHdrDdrWriteTransaction(
                    command=command,
                    address=address,
                    data=bytes(data),
                    parity=bytes(parity) if parity else None,
                    parity_mode=(I3CParityMode)(parity_mode.value),
                    crc5=crc5,
                ),
            ),
        ]

        _ = self.send_transactions(
            transactions=transactions,
            stop_type=self.TransactionStopType.HDR_EXIT,
        )

    def hdr_ddr_read(
        self,
        command: int,
        address: int,
        count: int,
    ) -> list[int]:
        """Perform a HDR-DDR read transaction on the I3C bus.

        HDR READ is used to read data from a target device.

        Args:
            command: The command to write to the target device.
            address: The address of the target device to write to.
            count: The number of bytes to read from the target device.

        Returns:
            The list of bytes read from the target device.

        """
        if count % 2 != 0:
            count += 1

        transactions = [
            I3CTransaction(
                ccc_broadcast=I3CBroadcastCccTransaction(
                    ccc=PxI3CCommonCommandCode(I3CCommonCommandCode.I3C_CCC_BROADCAST_ENTHDR0),
                    ccc_parity_mode=I3CParityMode.ALL_CORRECT,
                ),
            ),
            I3CTransaction(
                hdr_ddr_read=I3CHdrDdrReadTransaction(
                    command=command,
                    address=address,
                    count=count,
                ),
            ),
        ]

        response = self.send_transactions(
            transactions=transactions,
            stop_type=self.TransactionStopType.HDR_EXIT,
        )

        if not response[1].hdr_ddr_read.is_ack:
            return []

        return list(response[1].hdr_ddr_read.data)


__all__ = [
    "PxI3CBaseController",
    "PxI3CBaseControllerConfig",
    "PxI3CBaseControllerState",
]
