/** @file
  GMBUS I/O Implementation

  Copyright (c) 2022, Intel Corporation. All rights reserved.<BR>
  SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include <Uefi.h>
#include <Library/BaseLib.h>
#include <Library/DebugLib.h>
#include <Library/PcdLib.h>
#include <Library/IoLib.h>
#include <Library/PciLib.h>

#include <IgfxI2c.h>
#include <Gmbus.h>

/**
  Gets the GttMmAdr BAR value

  @retval  The current value of the GTTMMADR BAR
**/
UINTN
GmbusGetGttMmAdr (
  VOID
  )
{
  UINTN                   GttMmPciAddress;
  UINT32                  GttMmAdr;

  //
  // Check if GTT Memory Mapped BAR has been already assigned, initialize if not
  //
  GttMmPciAddress = PCI_LIB_ADDRESS (SA_IGD_BUS, SA_IGD_DEV, SA_IGD_FUN_0, R_SA_IGD_GTTMMADR);
  GttMmAdr = PciRead32 (GttMmPciAddress) & 0xFFFFFFF0;
  if (GttMmAdr == 0) {
    GttMmAdr = (UINT32) FixedPcdGet32 (PcdGttMmAddress);
    if (GttMmAdr == 0) {
      return 0;
    }
    //
    // Program and read back GTT Memory Mapped BAR
    //
    PciWrite32 (GttMmPciAddress, (UINT32) (GttMmAdr & 0xFF000000));
    GttMmAdr = PciRead32 (GttMmPciAddress) & 0xFFFFFFF0;
  }
  //
  // Check if Bus Master and Memory access on 0:2:0 is enabled, enable it if not
  //
  if ((PciRead16 (
        PCI_LIB_ADDRESS (SA_IGD_BUS, SA_IGD_DEV, SA_IGD_FUN_0, R_SA_IGD_CMD)
        )
      & (BIT2 | BIT1)) != (BIT2 | BIT1)) {
    //
    // Enable Bus Master and Memory access on 0:2:0
    //
    PciOr16 (PCI_LIB_ADDRESS (SA_IGD_BUS, SA_IGD_DEV, SA_IGD_FUN_0, R_SA_IGD_CMD), (BIT2 | BIT1));
    //
    // Set the Reset Bus Master flag so that it will be disabled when the current transaction is done
    //
    SetIgdBusMasterReset (TRUE);
  }

  return GttMmAdr;
}

/**
  Reset Bus Master and Memory access on the IGD device to the initial state at
  the start of the current transaction.
**/
VOID
GmbusResetBusMaster (
  VOID
  )
{
  if (GetIgdBusMasterReset ()) {
    //
    // Check if Bus Master and Memory access on 0:2:0 is enabled, disable it if so
    //
    if ((PciRead16 (
          PCI_LIB_ADDRESS (SA_IGD_BUS, SA_IGD_DEV, SA_IGD_FUN_0, R_SA_IGD_CMD)
          )
        & (BIT2 | BIT1)) != 0) {
      //
      // Disable Bus Master and Memory access on 0:2:0
      //
      PciAnd16 (PCI_LIB_ADDRESS (SA_IGD_BUS, SA_IGD_DEV, SA_IGD_FUN_0, R_SA_IGD_CMD), (UINT16) ~(BIT2 | BIT1));
    }
    //
    // Clear the Reset Bus Master flag
    //
    SetIgdBusMasterReset (FALSE);
  }
}

/**
  Writes to the GMBUS0 register (Clock/Port Select)

  @param[in]  GmbusClkPrtSel              - The value to write to GMBUS0

  @retval  EFI_SUCCESS                    - GMBUS0 was successfully written.
  @retval  EFI_DEVICE_ERROR               - An error occurred while writting to GMBUS0
**/
EFI_STATUS
SetGmbus0ClockPortSelect (
  IN  UINT32             GmbusClkPrtSel
  )
{
  UINTN                   GttMmAdr;

  GttMmAdr = GmbusGetGttMmAdr ();
  MmioWrite32 (GttMmAdr + R_SA_GTTMMADR_GMBUS0_CLKPRTSEL, GmbusClkPrtSel);
  return EFI_SUCCESS;
}

/**
  Writes to the GMBUS1 register (Command/Status)

  @param[in]  GmbusCmdSts                 - The value to write to GMBUS1

  @retval  EFI_SUCCESS                    - GMBUS1 was successfully written.
  @retval  EFI_DEVICE_ERROR               - An error occurred while writting to GMBUS1
**/
EFI_STATUS
SetGmbus1Command (
  IN  UINT32             GmbusCmdSts
  )
{
  UINTN                   GttMmAdr;

  GttMmAdr = GmbusGetGttMmAdr ();
  MmioWrite32 (GttMmAdr + R_SA_GTTMMADR_GMBUS1_CMDSTS, GmbusCmdSts);
  return EFI_SUCCESS;
}

/**
  Reads from the GMBUS2 register (GMBUS Status)

  @param[out]  GmbusStatus                - The value read from GMBUS2

  @retval  EFI_SUCCESS                    - GMBUS2 was successfully read.
  @retval  EFI_DEVICE_ERROR               - An error occurred while reading from GMBUS2
**/
EFI_STATUS
GetGmbus2Status (
  OUT  UINT32             *GmbusStatus
  )
{
  UINTN                   GttMmAdr;

  GttMmAdr = GmbusGetGttMmAdr ();
  if (GttMmAdr == 0) {
    return EFI_UNSUPPORTED;
  }
  *GmbusStatus = MmioRead32 (GttMmAdr + R_SA_GTTMMADR_GMBUS2_STATUS);
  return EFI_SUCCESS;
}

/**
  Writes to the GMBUS2 register (GMBUS Status)

  @param[in]  GmbusStatus                 - The value to write to GMBUS2

  @retval  EFI_SUCCESS                    - GMBUS2 was successfully written.
  @retval  EFI_DEVICE_ERROR               - An error occurred while writting to GMBUS2
**/
EFI_STATUS
SetGmbus2Status (
  IN  UINT32             GmbusStatus
  )
{
  UINTN                   GttMmAdr;

  GttMmAdr = GmbusGetGttMmAdr ();
  if (GttMmAdr == 0) {
    return EFI_UNSUPPORTED;
  }
  MmioWrite32 (GttMmAdr + R_SA_GTTMMADR_GMBUS2_STATUS, GmbusStatus);
  return EFI_SUCCESS;
}

/**
  Reads from the GMBUS3 register (GMBUS Data Buffer)

  @param[out]  GmbusData                  - The value read from GMBUS3

  @retval  EFI_SUCCESS                    - GMBUS2 was successfully read.
  @retval  EFI_DEVICE_ERROR               - An error occurred while reading from GMBUS2
**/
EFI_STATUS
GetGmbus3Data (
  OUT  UINT32             *GmbusData
  )
{
  UINTN                   GttMmAdr;

  GttMmAdr = GmbusGetGttMmAdr ();
  if (GttMmAdr == 0) {
    return EFI_UNSUPPORTED;
  }
  *GmbusData = MmioRead32 (GttMmAdr + R_SA_GTTMMADR_GMBUS3_DATA);
  return EFI_SUCCESS;
}

/**
  Writes to the GMBUS3 register (GMBUS Data Buffer)

  @param[in]  GmbusData                   - The value to write to GMBUS3

  @retval  EFI_SUCCESS                    - GMBUS3 was successfully written.
  @retval  EFI_DEVICE_ERROR               - An error occurred while writting to GMBUS3
**/
EFI_STATUS
SetGmbus3Data (
  IN  UINT32             GmbusData
  )
{
  UINTN                   GttMmAdr;

  GttMmAdr = GmbusGetGttMmAdr ();
  if (GttMmAdr == 0) {
    return EFI_UNSUPPORTED;
  }
  MmioWrite32 (GttMmAdr + R_SA_GTTMMADR_GMBUS3_DATA, GmbusData);
  return EFI_SUCCESS;
}

/**
  Set and clear the software clear interrupt bit. This causes a local reset on the GMBUS controller.

  @retval  EFI_SUCCESS                    - The GMBUS error was successfully cleared.
  @retval  EFI_TIMEOUT                    - The GMBUS I2C controller did not respond within the required timeout period.
  @retval  EFI_DEVICE_ERROR               - An error occurred while accessing the GMBUS I2C controller.
**/
EFI_STATUS
GmbusRecoverError (
  VOID
  )
{
  EFI_STATUS              Status;

  //
  // Setting B_SA_GTTMMADR_GMBUS1_SW_CLR_INT
  // causes a local reset on the GMBUS controller
  //
  Status = SetGmbus1Command ((UINT32) B_SA_GTTMMADR_GMBUS1_SW_CLR_INT);
  if (EFI_ERROR (Status)) {
    return Status;
  }
  Status = SetGmbus1Command (0);
  if (EFI_ERROR (Status)) {
    return Status;
  }
  //
  // Wait for reset to complete
  //
  Status = GmbusWaitForReady (B_SA_GTTMMADR_GMBUS2_BUS_ACTIVE, FALSE);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  return Status;
}

/**
  Wait for a given bitmask of status bits to de-assert to zero.

  @param[in]  StatusBitMask               - A bitmask of status bits to be compared to the present value of GMBUS2
  @param[in]  WaitForAssertion            - If TRUE, the Status Bit indicated must be 1, otherwise it must be 0.

  @retval  EFI_SUCCESS                    - The GMBUS controller has cleared all of the bits in the bitmask.
  @retval  EFI_TIMEOUT                    - The GMBUS controller did not clear all of the bits in the bitmask
                                            within the required timeout period.
  @retval  EFI_DEVICE_ERROR               - An error occurred while accessing the GMBUS I2C controller.
**/
EFI_STATUS
GmbusWaitForReady (
  IN  UINT32              StatusBitMask,
  IN  BOOLEAN             WaitForAssertion
  )
{
  EFI_STATUS              Status;
  EFI_STATUS              Status2;
  UINTN                   Index;
  UINT32                  GmbusStatus;

  Status = EFI_TIMEOUT;
  for (Index = 0; Index < GMBUS_TIMEOUT; Index++) {
    Status2 = GetGmbus2Status (&GmbusStatus);
    if (EFI_ERROR (Status2)) {
      return Status2;
    }
    if (WaitForAssertion) {
      if (GmbusStatus & StatusBitMask) {
        Status = EFI_SUCCESS;
        break;
      }
    } else {
      if (!(GmbusStatus & StatusBitMask)) {
        Status = EFI_SUCCESS;
        break;
      }
    }
  }
  return Status;
}

/**
  Initialize the GMBUS to use a given GPIO pin pair and clock speed in preparation
  for sending a I2C command to the GMBUS controller.

  @param[in]  BusSpeed                    - The clock rate for the I2C bus.
  @param[in]  DdcBusPinPair               - The GPIO pin pair the GMBUS controller should use.

  @retval  EFI_SUCCESS                    - The GMBUS has been initialized successfully.
  @retval  EFI_INVALID_PARAMETER          - The given BusSpeed does not match a valid clock rate value.
  @retval  EFI_TIMEOUT                    - The GMBUS I2C controller did not respond within the required timeout period.
  @retval  EFI_DEVICE_ERROR               - An error occurred while accessing the GMBUS I2C controller.
**/
EFI_STATUS
GmbusPrepare (
  IN  UINT8             BusSpeed,
  IN  UINT8             DdcBusPinPair
  )
{
  EFI_STATUS              Status;
  UINT32                  GmbusClkPrtSel;
  UINT32                  GmbusStatus;

  //
  // Check that the user provided a valid bus speed
  //
  if ((BusSpeed != GMBUS_CLOCK_RATE_100K) && (BusSpeed != GMBUS_CLOCK_RATE_50K) &&
      (BusSpeed != GMBUS_CLOCK_RATE_400K) && (BusSpeed != GMBUS_CLOCK_RATE_1M)) {
    return EFI_INVALID_PARAMETER;
  }
  //
  // Wait for GMBUS to complete any pending commands
  //
  Status = GmbusWaitForReady (B_SA_GTTMMADR_GMBUS2_INUSE, FALSE);
  if (EFI_ERROR (Status)) {
    return Status;
  }
  //
  // Program the GMBUS Port and Clock
  //
  GmbusClkPrtSel = (BusSpeed << 8) | DdcBusPinPair;
  Status = SetGmbus0ClockPortSelect (GmbusClkPrtSel);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Check for a NACK that has not been cleared yet. Clear it if found.
  //
  Status = GetGmbus2Status (&GmbusStatus);
  if (EFI_ERROR (Status)) {
    return Status;
  }
  if (GmbusStatus & B_SA_GTTMMADR_GMBUS2_NACK_INDICATOR) {
    Status = GmbusRecoverError ();
    if (EFI_ERROR (Status)) {
      return Status;
    }
  }

  return Status;
}

/**
  Release the GMBUS controller

  @retval  EFI_SUCCESS                    - The GMBUS has been released successfully.
  @retval  EFI_DEVICE_ERROR               - An error occurred while accessing the GMBUS I2C controller.
**/
EFI_STATUS
GmbusRelease (
  VOID
  )
{
  EFI_STATUS              Status;

  //
  // Clear the GMBUS Port and Clock
  //
  Status = SetGmbus0ClockPortSelect (0);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Setting the INUSE bit to 1 indicates that software has released the GMBUS resource.
  // The GMBUS controller will then reset the INUSE bit to 0.
  //
  Status = SetGmbus2Status (B_SA_GTTMMADR_GMBUS2_INUSE);
  if (EFI_ERROR (Status)) {
  }

  return Status;
}

/**
  Reads data from the I2C bus using the GMBUS I2C controller

  @param[in]  DdcBusPinPair               - The GPIO pin pair to use for the read operation
  @param[in]  SlaveAddress                - The I2C device address to read data from
  @param[in]  SendStopCondition           - TRUE:  After the read is complete, send a STOP condition to the I2C bus
                                            FALSE: Don't send a STOP after the read is complete, this allows one to
                                                   immediately send a repeated START condition to the I2C bus after
                                                   GmbusRead() exits by calling either GmbusRead() or GmbusWrite()
                                                   immediately after this function returns.
  @param[in]  SendIndexBeforeRead         - TRUE:  Before executing the read on the I2C bus, first send a WRITE to the
                                                   I2C bus using the same SlaveAddress (but with BIT0 set to 0 because
                                                   the operation is a write) the write will contain a single byte, that
                                                   byte is the data given in the IndexData parameter.
                                            FALSE: Just send a read to the I2C bus, the IndexData parameter is ignored.
  @param[in]  IndexData                   - If SendIndexBeforeRead is TRUE, this byte of data will be written to the I2C
                                            bus before the I2C bus is read. If SendIndexBeforeRead is FALSE, this
                                            parameter is ignored.
  @param[in, out]  ByteCount              - The number of bytes to read from the I2C bus. On output, the number of bytes
                                            actually read.
  @param[out] ReadBuffer                  - The memory buffer to return the read data.

  @retval  EFI_SUCCESS                    - The data was successfully read.
  @retval  EFI_INVALID_PARAMETER          - One of the following conditions:
                                            * ByteCount is 0 or >GMBUS_MAX_BYTES.
                                            * ReadBuffer is NULL
                                            * SlaveAddress does not have BIT0 set (required for reads.)
  @retval  EFI_TIMEOUT                    - The GMBUS I2C controller did not respond within the required timeout period.
  @retval  EFI_DEVICE_ERROR               - An error occurred while accessing the GMBUS I2C controller.
**/
EFI_STATUS
GmbusRead (
  IN      UINT8         DdcBusPinPair,
  IN      UINT8         SlaveAddress,
  IN      BOOLEAN       SendStopCondition,
  IN      BOOLEAN       SendIndexBeforeRead,
  IN      UINT8         IndexData,
  IN OUT  UINT32        *ByteCount,
  OUT     UINT8         *ReadBuffer
  )
{
  EFI_STATUS              Status;
  EFI_STATUS              Status2;
  UINT8                   GmbusClockRate;
  UINT32                  Index;
  UINT32                  GmbusCmdSts;
  UINT32                  GmbusStatus;
  UINT32                  GmbusData;
  UINT32                  BytesRead;

  Status      = EFI_SUCCESS;
  BytesRead   = 0;
  GmbusStatus = 0;

  //
  // Input Validation
  //
  if ((*ByteCount) <= 0) {
    return EFI_INVALID_PARAMETER;
  }
  if ((*ByteCount) > GMBUS_MAX_BYTES) {
    return EFI_INVALID_PARAMETER;
  }
  if (ReadBuffer == NULL) {
    return EFI_INVALID_PARAMETER;
  }
  if ((SlaveAddress & BIT0) != BIT0) {
    return EFI_INVALID_PARAMETER;
  }

  //
  // Configure Gmbus port and clock speed
  //
  GmbusClockRate = FixedPcdGet8 (PcdI2cHdmiDebugPortGmbusClockRate);
  Status = GmbusPrepare (GmbusClockRate, (DdcBusPinPair & B_SA_GTTMMADR_GMBUS0_PIN_PAIR_MASK));
  if (EFI_ERROR (Status)) {
    goto Done;
  }

  //
  // Construct the command for the GMBUS controller
  //
  GmbusCmdSts     = ((UINT32) SlaveAddress)                   |
                    B_SA_GTTMMADR_GMBUS1_BUS_CYCLE_SEL_START  |
                    B_SA_GTTMMADR_GMBUS1_SW_RDY               |
                    B_SA_GTTMMADR_GMBUS1_EN_TIMEOUT;
  GmbusCmdSts    |= (((*ByteCount) << N_SA_GTTMMADR_GMBUS1_TOTAL_BYTE_COUNT) & B_SA_GTTMMADR_GMBUS1_TOTAL_BYTE_COUNT_MASK);
  if (SendStopCondition) {
    GmbusCmdSts  |= B_SA_GTTMMADR_GMBUS1_BUS_CYCLE_SEL_STOP;
  }
  if (SendIndexBeforeRead) {
    GmbusCmdSts  |= B_SA_GTTMMADR_GMBUS1_BUS_CYCLE_SEL_INDEX;
    GmbusCmdSts  |= ((IndexData << N_SA_GTTMMADR_GMBUS1_INDEX) & B_SA_GTTMMADR_GMBUS1_INDEX_MASK);
  }

  //
  // Send the command to the GMBUS controller, this will cause the I2C transaction to begin immediately
  //
  Status = SetGmbus1Command (GmbusCmdSts);
  if (EFI_ERROR (Status)) {
    goto Done;
  }

  //
  // Read the data from the GMBUS controller as it arrives
  //
  while (BytesRead < (*ByteCount)) {
    //
    // Wait for the GMBUS controller to set the HW_RDY bit to 1
    //
    // The HW_RDY bit is set under the following conditions:
    //
    // * After a reset
    // * When a transaction is aborted by the setting the SW_CLR_INT bit in the GMBUS1 register
    // * When an active GMBUS cycle has terminated with a STOP condition
    // * During a GMBUS write transaction, when the data register can accept another four bytes of data
    // * During a GMBUS read transaction, when the data register has four bytes of new data or when the read
    //   transaction DATA phase is complete and the data register contains all remaining data.
    //
    Status = GmbusWaitForReady (B_SA_GTTMMADR_GMBUS2_HW_RDY, TRUE);
    //
    // Check the GMBUS2 register for error conditions (NACK or Slave Stall Timeout)
    //
    Status2 = GetGmbus2Status (&GmbusStatus);
    if (EFI_ERROR (Status2)) {
      Status = Status2;
      goto Done;
    }
    if (EFI_ERROR (Status) && ((GmbusStatus & B_SA_GTTMMADR_GMBUS2_NACK_INDICATOR) == 0)) {
      Status = EFI_DEVICE_ERROR;
      goto Done;
    }
    if (((GmbusStatus & B_SA_GTTMMADR_GMBUS2_NACK_INDICATOR) != 0) ||
        ((GmbusStatus & B_SA_GTTMMADR_GMBUS2_SLAVE_STALL_TIMEOUT_ERROR) != 0)) {
      //
      // If a NACK or Slave Stall Timeout occurs, then a bus error has occurred.
      // In the event of a bus error, one must reset the GMBUS controller to resume normal operation.
      //
      Status = GmbusRecoverError ();
      if (EFI_ERROR (Status)) {
        goto Done;
      }
      Status = EFI_DEVICE_ERROR;
      goto Done;
    }
    //
    // No error conditions were encountered, read the data and write it to the data buffer
    //
    Status = GetGmbus3Data (&GmbusData);
    if (EFI_ERROR (Status)) {
      goto Done;
    }
    for (Index = 0; (Index < sizeof (UINT32)) && (BytesRead < (*ByteCount)); Index++) {
      ReadBuffer[BytesRead] = (GmbusData >> (Index * 8)) & 0xFF;
      BytesRead++;
    }
  }

  //
  // Wait for the GMBUS controller to enter the IDLE state
  //
  Status = GmbusWaitForReady (B_SA_GTTMMADR_GMBUS2_BUS_ACTIVE, FALSE);
  if (EFI_ERROR (Status)) {
    return Status;
  }

Done:
  Status2 = GmbusRelease ();
  if (EFI_ERROR (Status2)) {
    Status = Status2;
  }
  GmbusResetBusMaster ();

  (*ByteCount) = BytesRead;
  return Status;
}

/**
  Writes data to the I2C bus using the GMBUS I2C controller

  @param[in]  DdcBusPinPair               - The GPIO pin pair to use for the write operation
  @param[in]  SlaveAddress                - The I2C device address to write data to
  @param[in]  SendStopCondition           - TRUE:  After the write is complete, send a STOP condition to the I2C bus
                                            FALSE: Don't send a STOP after the write is complete, this allows one to
                                                   immediately send a repeated START condition to the I2C bus after
                                                   GmbusRead() exits by calling either GmbusRead() or GmbusWrite()
                                                   immediately after this function returns.
  @param[in]  ByteCount                   - The number of bytes to write to the I2C bus.
  @param[in]  WriteBuffer                 - The data to be written to the I2C bus.

  @retval  EFI_SUCCESS                    - The data was successfully written.
  @retval  EFI_INVALID_PARAMETER          - One of the following conditions:
                                            * ByteCount is 0 or >GMBUS_MAX_BYTES.
                                            * WriteBuffer is NULL
                                            * SlaveAddress does not have BIT0 cleared (required for writes.)
  @retval  EFI_TIMEOUT                    - The GMBUS I2C controller did not respond within the required timeout period.
  @retval  EFI_DEVICE_ERROR               - An error occurred while accessing the GMBUS I2C controller.
**/
EFI_STATUS
GmbusWrite (
  IN  UINT8             DdcBusPinPair,
  IN  UINT8             SlaveAddress,
  IN  BOOLEAN           SendStopCondition,
  IN  UINT32            ByteCount,
  IN  UINT8             *WriteBuffer
  )
{
  EFI_STATUS              Status;
  EFI_STATUS              Status2;
  UINT8                   GmbusClockRate;
  UINT32                  Index;
  UINT32                  GmbusCmdSts;
  UINT32                  GmbusStatus;
  UINT32                  GmbusData;
  UINT32                  BytesWritten;
  BOOLEAN                 FirstLoop;

  Status        = EFI_SUCCESS;
  BytesWritten  = 0;
  GmbusStatus   = 0;
  FirstLoop     = TRUE;

  //
  // Input Validation
  //
  if (ByteCount <= 0) {
    return EFI_INVALID_PARAMETER;
  }
  if (ByteCount > GMBUS_MAX_BYTES) {
    return EFI_INVALID_PARAMETER;
  }
  if (WriteBuffer == NULL) {
    return EFI_INVALID_PARAMETER;
  }
  if ((SlaveAddress & BIT0) != 0) {
    return EFI_INVALID_PARAMETER;
  }

  //
  // Configure Gmbus port and clock speed
  //
  GmbusClockRate = FixedPcdGet8 (PcdI2cHdmiDebugPortGmbusClockRate);
  Status = GmbusPrepare (GmbusClockRate, (DdcBusPinPair & B_SA_GTTMMADR_GMBUS0_PIN_PAIR_MASK));
  if (EFI_ERROR (Status)) {
    goto Done;
  }

  //
  // Construct the command for the GMBUS controller
  //
  GmbusCmdSts     = ((UINT32) SlaveAddress)                   |
                    B_SA_GTTMMADR_GMBUS1_BUS_CYCLE_SEL_START  |
                    B_SA_GTTMMADR_GMBUS1_SW_RDY               |
                    B_SA_GTTMMADR_GMBUS1_EN_TIMEOUT;
  GmbusCmdSts    |= ((ByteCount << N_SA_GTTMMADR_GMBUS1_TOTAL_BYTE_COUNT) & B_SA_GTTMMADR_GMBUS1_TOTAL_BYTE_COUNT_MASK);
  if (SendStopCondition) {
    GmbusCmdSts  |= B_SA_GTTMMADR_GMBUS1_BUS_CYCLE_SEL_STOP;
  }

  //
  // Preload the first 4 bytes of data so that when we send the command to the GMBUS
  // controller the first 4 bytes of data are ready for transmission. The GMBUS controller requires this.
  //
  GmbusData = 0;
  for (Index = 0; (Index < sizeof (UINT32)) && (BytesWritten < ByteCount); Index++) {
    GmbusData |= (WriteBuffer[BytesWritten] << (Index * 8));
    BytesWritten++;
  }
  Status = SetGmbus3Data (GmbusData);
  if (EFI_ERROR (Status)) {
    goto Done;
  }

  //
  // Send the command to the GMBUS controller, this will cause the I2C transaction to begin immediately
  //
  Status = SetGmbus1Command (GmbusCmdSts);
  if (EFI_ERROR (Status)) {
    goto Done;
  }

  while ((BytesWritten < ByteCount) || FirstLoop) {
    //
    // If this is not the first loop, load the next 4 bytes of data into the
    // GMBUS controller's data buffer.
    //
    if(!FirstLoop) {
      GmbusData = 0;
      for (Index = 0; (Index < sizeof (UINT32)) && (BytesWritten < ByteCount); Index++) {
        GmbusData |= (WriteBuffer[BytesWritten] << (Index * 8));
        BytesWritten++;
      }
      Status = SetGmbus3Data (GmbusData);
      if (EFI_ERROR (Status)) {
        goto Done;
      }
    }
    FirstLoop = FALSE;

    //
    // Wait for the GMBUS controller to set the HW_RDY bit to 1
    //
    // The HW_RDY bit is set under the following conditions:
    //
    // * After a reset
    // * When a transaction is aborted by the setting the SW_CLR_INT bit in the GMBUS1 register
    // * When an active GMBUS cycle has terminated with a STOP condition
    // * During a GMBUS write transaction, when the data register can accept another four bytes of data
    // * During a GMBUS read transaction, when the data register has four bytes of new data or when the read
    //   transaction DATA phase is complete and the data register contains all remaining data.
    //
    Status = GmbusWaitForReady (B_SA_GTTMMADR_GMBUS2_HW_RDY, TRUE);
    if (EFI_ERROR (Status)) {
    }
    //
    // Check the GMBUS2 register for error conditions (NACK or Slave Stall Timeout)
    //
    Status2 = GetGmbus2Status (&GmbusStatus);
    if (EFI_ERROR (Status2)) {
      Status = Status2;
      goto Done;
    }
    if (EFI_ERROR (Status) && ((GmbusStatus & B_SA_GTTMMADR_GMBUS2_NACK_INDICATOR) == 0)) {
      Status = EFI_DEVICE_ERROR;
      goto Done;
    }
    if (((GmbusStatus & B_SA_GTTMMADR_GMBUS2_NACK_INDICATOR) != 0) ||
        ((GmbusStatus & B_SA_GTTMMADR_GMBUS2_SLAVE_STALL_TIMEOUT_ERROR) != 0)) {
      //
      // If a NACK or Slave Stall Timeout occurs, then a bus error has occurred.
      // In the event of a bus error, one must reset the GMBUS controller to resume normal operation.
      //
      Status = GmbusRecoverError ();
      if (EFI_ERROR (Status)) {
        goto Done;
      }
      Status = EFI_DEVICE_ERROR;
      goto Done;
    }
  }

  //
  // Wait for the GMBUS controller to enter the IDLE state
  //
  Status = GmbusWaitForReady (B_SA_GTTMMADR_GMBUS2_BUS_ACTIVE, FALSE);
  if (EFI_ERROR (Status)) {
    return Status;
  }

Done:
  Status2 = GmbusRelease ();
  if (EFI_ERROR (Status2)) {
    Status = Status2;
  }
  GmbusResetBusMaster ();

  return Status;
}
