/** @file
  Functions for accessing I2C registers.
  
  Copyright (c) 2004  - 2015, Intel Corporation. All rights reserved.<BR>
                                                                                   
  This program and the accompanying materials are licensed and made available under
  the terms and conditions of the BSD License that accompanies this distribution.  
  The full text of the license may be found at                                     
  http://opensource.org/licenses/bsd-license.php.                                  
                                                                                  
  THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,            
  WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.    
                                                                               
--*/

#include <Library/DebugLib.h>
#include <Library/TimerLib.h>
#include <PchRegs/PchRegsPcu.h> 
#include <PchRegs.h>
#include <PlatformBaseAddresses.h>
#include <PchRegs/PchRegsLpss.h> 
#include <Library/I2CLib.h>
#include <Protocol/GlobalNvsArea.h>
#include <Library/UefiBootServicesTableLib.h>
#include <I2CRegs.h>

#define GLOBAL_NVS_OFFSET(Field)    (UINTN)((CHAR8*)&((EFI_GLOBAL_NVS_AREA*)0)->Field - (CHAR8*)0)

#define PCIEX_BASE_ADDRESS  0xE0000000
#define PCI_EXPRESS_BASE_ADDRESS ((VOID *) (UINTN) PCIEX_BASE_ADDRESS)
#define MmPciAddress( Segment, Bus, Device, Function, Register ) \
         ((UINTN)PCI_EXPRESS_BASE_ADDRESS + \
         (UINTN)(Bus << 20) + \
         (UINTN)(Device << 15) + \
         (UINTN)(Function << 12) + \
         (UINTN)(Register) \
        )
#define PCI_D31F0_REG_BASE             PCIEX_BASE_ADDRESS + (UINT32) (31 << 15)

typedef struct _LPSS_PCI_DEVICE_INFO {
  UINTN        Segment;
  UINTN        BusNum;
  UINTN        DeviceNum;
  UINTN        FunctionNum;
  UINTN        Bar0;
  UINTN        Bar1;
} LPSS_PCI_DEVICE_INFO;

LPSS_PCI_DEVICE_INFO  mLpssPciDeviceList[] = {
  {0, DEFAULT_PCI_BUS_NUMBER_PCH, PCI_DEVICE_NUMBER_PCH_LPSS_DMAC1, PCI_FUNCTION_NUMBER_PCH_LPSS_DMAC, 0xFE900000, 0xFE908000},
  {0, DEFAULT_PCI_BUS_NUMBER_PCH, PCI_DEVICE_NUMBER_PCH_LPSS_I2C,   PCI_FUNCTION_NUMBER_PCH_LPSS_I2C0, 0xFE910000, 0xFE918000},
  {0, DEFAULT_PCI_BUS_NUMBER_PCH, PCI_DEVICE_NUMBER_PCH_LPSS_I2C,   PCI_FUNCTION_NUMBER_PCH_LPSS_I2C1, 0xFE920000, 0xFE928000},
  {0, DEFAULT_PCI_BUS_NUMBER_PCH, PCI_DEVICE_NUMBER_PCH_LPSS_I2C,   PCI_FUNCTION_NUMBER_PCH_LPSS_I2C2, 0xFE930000, 0xFE938000},
  {0, DEFAULT_PCI_BUS_NUMBER_PCH, PCI_DEVICE_NUMBER_PCH_LPSS_I2C,   PCI_FUNCTION_NUMBER_PCH_LPSS_I2C3, 0xFE940000, 0xFE948000},
  {0, DEFAULT_PCI_BUS_NUMBER_PCH, PCI_DEVICE_NUMBER_PCH_LPSS_I2C,   PCI_FUNCTION_NUMBER_PCH_LPSS_I2C4, 0xFE950000, 0xFE958000},
  {0, DEFAULT_PCI_BUS_NUMBER_PCH, PCI_DEVICE_NUMBER_PCH_LPSS_I2C,   PCI_FUNCTION_NUMBER_PCH_LPSS_I2C5, 0xFE960000, 0xFE968000},
  {0, DEFAULT_PCI_BUS_NUMBER_PCH, PCI_DEVICE_NUMBER_PCH_LPSS_I2C,   PCI_FUNCTION_NUMBER_PCH_LPSS_I2C6, 0xFE970000, 0xFE978000}
};

#define LPSS_PCI_DEVICE_NUMBER  sizeof(mLpssPciDeviceList)/sizeof(LPSS_PCI_DEVICE_INFO)

STATIC UINTN mI2CBaseAddress = 0;
STATIC UINT16 mI2CSlaveAddress = 0;

UINT16 mI2cMode=B_IC_RESTART_EN | B_IC_SLAVE_DISABLE | B_MASTER_MODE ;

UINTN mI2cNvsBaseAddress[] = {
        GLOBAL_NVS_OFFSET(LDMA2Addr),
        GLOBAL_NVS_OFFSET(I2C1Addr),
        GLOBAL_NVS_OFFSET(I2C2Addr),
        GLOBAL_NVS_OFFSET(I2C3Addr),
        GLOBAL_NVS_OFFSET(I2C4Addr),
        GLOBAL_NVS_OFFSET(I2C5Addr),
        GLOBAL_NVS_OFFSET(I2C6Addr),
        GLOBAL_NVS_OFFSET(I2C7Addr)
      };

/**
  This function get I2Cx controller base address (BAR0).

  @param I2cControllerIndex  Bus Number of I2C controller.

  @return I2C BAR. 
**/
UINTN
GetI2cBarAddr(
  IN    UINT8 I2cControllerIndex
  )
{
  EFI_STATUS           Status;
  EFI_GLOBAL_NVS_AREA_PROTOCOL  *GlobalNvsArea;
  UINTN  AcpiBaseAddr;
  UINTN  PciMmBase=0;

  ASSERT(gBS!=NULL);

  Status = gBS->LocateProtocol (
                  &gEfiGlobalNvsAreaProtocolGuid,
                  NULL,
                  &GlobalNvsArea
                  );
                  
  //
  // PCI mode from PEI ( Global NVS is not ready).
  //
  if (EFI_ERROR(Status)) {
    DEBUG ((EFI_D_INFO, "GetI2cBarAddr() gEfiGlobalNvsAreaProtocolGuid:%r\n", Status));
    //
    // Global NVS is not ready.
    //
    return 0;
  }

  AcpiBaseAddr =  *(UINTN*)((CHAR8*)GlobalNvsArea->Area + mI2cNvsBaseAddress[I2cControllerIndex + 1]);
  
  //
  //PCI mode from DXE (global NVS protocal) to LPSS OnReadytoBoot(swith to ACPI).
  //
  if(AcpiBaseAddr==0) {
    PciMmBase = MmPciAddress (
                  mLpssPciDeviceList[I2cControllerIndex + 1].Segment,
                  mLpssPciDeviceList[I2cControllerIndex + 1].BusNum,
                  mLpssPciDeviceList[I2cControllerIndex + 1].DeviceNum,
                  mLpssPciDeviceList[I2cControllerIndex + 1].FunctionNum,
                  0
                  );
    DEBUG((EFI_D_ERROR, "\nGetI2cBarAddr() I2C Device %x %x %x PciMmBase:%x\n", \
           mLpssPciDeviceList[I2cControllerIndex + 1].BusNum, \
           mLpssPciDeviceList[I2cControllerIndex + 1].DeviceNum, \
           mLpssPciDeviceList[I2cControllerIndex + 1].FunctionNum, PciMmBase));

    if (MmioRead32 (PciMmBase) != 0xFFFFFFFF)    {
      if((MmioRead32 (PciMmBase+R_PCH_LPSS_I2C_STSCMD)& B_PCH_LPSS_I2C_STSCMD_MSE)) {
        //
        // Get the address allocted.
        //
        mLpssPciDeviceList[I2cControllerIndex + 1].Bar0=MmioRead32 (PciMmBase+R_PCH_LPSS_I2C_BAR);     
        mLpssPciDeviceList[I2cControllerIndex + 1].Bar1=MmioRead32 (PciMmBase+R_PCH_LPSS_I2C_BAR1);
      }
    }
    AcpiBaseAddr =mLpssPciDeviceList[I2cControllerIndex+1].Bar0;
  }
  
  //
  // ACPI mode from BDS: LPSS OnReadytoBoot
  //
  else {
    DEBUG ((EFI_D_INFO, "GetI2cBarAddr() NVS Varialable is updated by this LIB or LPSS  \n"));
  }
  
  DEBUG ((EFI_D_INFO, "GetI2cBarAddr() I2cControllerIndex+1 0x%x AcpiBaseAddr:0x%x \n", (I2cControllerIndex + 1), AcpiBaseAddr));
  return AcpiBaseAddr;
}


/**
  This function enables I2C controllers.

  @param I2cControllerIndex  Bus Number of I2C controllers.

  @return Result of the I2C initialization.
**/
EFI_STATUS
ProgramPciLpssI2C (
  IN  UINT8 I2cControllerIndex
  )
{
  UINT32                        PmcBase;
  UINTN                         PciMmBase=0;
  EFI_STATUS                    Status;
  EFI_GLOBAL_NVS_AREA_PROTOCOL  *GlobalNvsArea;

  UINT32 PmcFunctionDsiable[]= {
    B_PCH_PMC_FUNC_DIS_LPSS2_FUNC1,
    B_PCH_PMC_FUNC_DIS_LPSS2_FUNC2,
    B_PCH_PMC_FUNC_DIS_LPSS2_FUNC3,
    B_PCH_PMC_FUNC_DIS_LPSS2_FUNC4,
    B_PCH_PMC_FUNC_DIS_LPSS2_FUNC5,
    B_PCH_PMC_FUNC_DIS_LPSS2_FUNC6,
    B_PCH_PMC_FUNC_DIS_LPSS2_FUNC7
  };

  DEBUG ((EFI_D_INFO, "ProgramPciLpssI2C() Start\n"));

  //
  // Set the VLV Function Disable Register to ZERO
  //
  PmcBase = MmioRead32 (PCI_D31F0_REG_BASE + R_PCH_LPC_PMC_BASE) & B_PCH_LPC_PMC_BASE_BAR;
  if(MmioRead32(PmcBase+R_PCH_PMC_FUNC_DIS)&PmcFunctionDsiable[I2cControllerIndex]) {
    DEBUG ((EFI_D_INFO, "ProgramPciLpssI2C() End:I2C[%x] is disabled\n",I2cControllerIndex));
    return EFI_NOT_READY;
  }
  
  DEBUG ((EFI_D_INFO, "ProgramPciLpssI2C()------------I2cControllerIndex=%x,PMC=%x\n",I2cControllerIndex,MmioRead32(PmcBase+R_PCH_PMC_FUNC_DIS)));

  {
    PciMmBase = MmPciAddress (
                  mLpssPciDeviceList[I2cControllerIndex+1].Segment,
                  mLpssPciDeviceList[I2cControllerIndex+1].BusNum,
                  mLpssPciDeviceList[I2cControllerIndex+1].DeviceNum,
                  mLpssPciDeviceList[I2cControllerIndex+1].FunctionNum,
                  0
                  );
                  
    DEBUG((EFI_D_ERROR, "Program Pci Lpss I2C Device  %x %x %x PciMmBase:%x\n", \
           mLpssPciDeviceList[I2cControllerIndex+1].BusNum, \
           mLpssPciDeviceList[I2cControllerIndex+1].DeviceNum, \
           mLpssPciDeviceList[I2cControllerIndex+1].FunctionNum, PciMmBase));

    if (MmioRead32 (PciMmBase) != 0xFFFFFFFF)     {
      if((MmioRead32 (PciMmBase+R_PCH_LPSS_I2C_STSCMD)& B_PCH_LPSS_I2C_STSCMD_MSE)) {
        //
        // Get the address allocted.
        //
        mLpssPciDeviceList[I2cControllerIndex+1].Bar0=MmioRead32 (PciMmBase+R_PCH_LPSS_I2C_BAR);     
        mLpssPciDeviceList[I2cControllerIndex+1].Bar1=MmioRead32 (PciMmBase+R_PCH_LPSS_I2C_BAR1);
        DEBUG((EFI_D_ERROR, "ProgramPciLpssI2C() bar0:0x%x bar1:0x%x\n",mLpssPciDeviceList[I2cControllerIndex+1].Bar0, mLpssPciDeviceList[I2cControllerIndex+1].Bar1));
      } else {
        
        //
        // Program BAR 0
        //
        ASSERT (((mLpssPciDeviceList[I2cControllerIndex+1].Bar0 & B_PCH_LPSS_I2C_BAR_BA) == mLpssPciDeviceList[I2cControllerIndex+1].Bar0) && (mLpssPciDeviceList[I2cControllerIndex+1].Bar0 != 0));
        MmioWrite32 ((UINTN) (PciMmBase + R_PCH_LPSS_I2C_BAR), (UINT32) (mLpssPciDeviceList[I2cControllerIndex+1].Bar0 & B_PCH_LPSS_I2C_BAR_BA));
       
        //
        // Program BAR 1
        //
        ASSERT (((mLpssPciDeviceList[I2cControllerIndex+1].Bar1 & B_PCH_LPSS_I2C_BAR1_BA) == mLpssPciDeviceList[I2cControllerIndex+1].Bar1) && (mLpssPciDeviceList[I2cControllerIndex+1].Bar1 != 0));
        MmioWrite32 ((UINTN) (PciMmBase + R_PCH_LPSS_I2C_BAR1), (UINT32) (mLpssPciDeviceList[I2cControllerIndex+1].Bar1 & B_PCH_LPSS_I2C_BAR1_BA));
        
        //
        // Bus Master Enable & Memory Space Enable
        //
        MmioOr32 ((UINTN) (PciMmBase + R_PCH_LPSS_I2C_STSCMD), (UINT32) (B_PCH_LPSS_I2C_STSCMD_BME | B_PCH_LPSS_I2C_STSCMD_MSE));
        ASSERT (MmioRead32 (mLpssPciDeviceList[I2cControllerIndex+1].Bar0) != 0xFFFFFFFF);
      }
      
      //
      // Release Resets
      //
      MmioWrite32 (mLpssPciDeviceList[I2cControllerIndex+1].Bar0 + R_PCH_LPIO_I2C_MEM_RESETS,(B_PCH_LPIO_I2C_MEM_RESETS_FUNC | B_PCH_LPIO_I2C_MEM_RESETS_APB));
      
      //
      // Activate Clocks
      //
      MmioWrite32 (mLpssPciDeviceList[I2cControllerIndex+1].Bar0 + R_PCH_LPSS_I2C_MEM_PCP,0x80020003);//No use for A0

      DEBUG ((EFI_D_INFO, "ProgramPciLpssI2C() Programmed()\n"));
    }
    
    //
    // BDS: already switched to ACPI mode
    //
    else {
      ASSERT(gBS!=NULL);
      Status = gBS->LocateProtocol (
                      &gEfiGlobalNvsAreaProtocolGuid,
                      NULL,
                      &GlobalNvsArea
                      );
      if (EFI_ERROR(Status)) {
        DEBUG ((EFI_D_INFO, "GetI2cBarAddr() gEfiGlobalNvsAreaProtocolGuid:%r\n", Status));
        //
        // gEfiGlobalNvsAreaProtocolGuid is not ready.
        //
        return 0;
      }
      mLpssPciDeviceList[I2cControllerIndex + 1].Bar0 = *(UINTN*)((CHAR8*)GlobalNvsArea->Area + mI2cNvsBaseAddress[I2cControllerIndex + 1]);
      DEBUG ((EFI_D_INFO, "ProgramPciLpssI2C(): is switched to ACPI 0x:%x \n",mLpssPciDeviceList[I2cControllerIndex + 1].Bar0));
    }
  }
  
  DEBUG ((EFI_D_INFO, "ProgramPciLpssI2C() End\n"));

  return EFI_SUCCESS;
}

/**
  Disable I2C Bus.

  @param VOID.

  @return Result of the I2C disabling.
**/
RETURN_STATUS
I2cDisable (
  VOID
  )
{ 
  //
  // 0.1 seconds
  //
  UINT32 NumTries = 10000;
  
  MmioWrite32 ( mI2CBaseAddress + R_IC_ENABLE, 0 );
  while ( 0 != ( MmioRead32 ( mI2CBaseAddress + R_IC_ENABLE_STATUS) & 1)) {
    MicroSecondDelay (10);
    NumTries --;
    if(0 == NumTries) {
      return RETURN_NOT_READY;
    }
  }
  
  return RETURN_SUCCESS;
}

/**
  Enable I2C Bus.

  @param VOID.

  @return Result of the I2C disabling.
**/
RETURN_STATUS
I2cEnable (
  VOID
  )
{
  //
  // 0.1 seconds
  //
  UINT32 NumTries = 10000;
  
  MmioWrite32 (mI2CBaseAddress + R_IC_ENABLE, 1);
  
  while (0 == (MmioRead32 (mI2CBaseAddress + R_IC_ENABLE_STATUS) & 1)) {
    MicroSecondDelay (10);
    NumTries --;
    if(0 == NumTries){
       return RETURN_NOT_READY;
    }
  }
  
  return RETURN_SUCCESS;
}

/**
  Enable I2C Bus.

  @param VOID.

  @return Result of the I2C enabling.
**/
RETURN_STATUS
I2cBusFrequencySet (
  IN UINTN BusClockHertz
  )
{
  DEBUG((EFI_D_INFO,"InputFreq BusClockHertz: %d\r\n",BusClockHertz));
  
  //
  //  Set the 100 KHz clock divider according to SV result and I2C spec
  //
  MmioWrite32 ( mI2CBaseAddress + R_IC_SS_SCL_HCNT, (UINT16)0x214 );
  MmioWrite32 ( mI2CBaseAddress + R_IC_SS_SCL_LCNT, (UINT16)0x272 );
  
  //
  //  Set the 400 KHz clock divider according to SV result and I2C spec
  //
  MmioWrite32 ( mI2CBaseAddress + R_IC_FS_SCL_HCNT, (UINT16)0x50 );
  MmioWrite32 ( mI2CBaseAddress + R_IC_FS_SCL_LCNT, (UINT16)0xAD );

  switch ( BusClockHertz ) {
    case 100 * 1000:
      MmioWrite32 ( mI2CBaseAddress + R_IC_SDA_HOLD, (UINT16)0x40);//100K
      mI2cMode = V_SPEED_STANDARD;
      break;
    case 400 * 1000:
      MmioWrite32 ( mI2CBaseAddress + R_IC_SDA_HOLD, (UINT16)0x32);//400K
      mI2cMode = V_SPEED_FAST;
      break;
    default:
      MmioWrite32 ( mI2CBaseAddress + R_IC_SDA_HOLD, (UINT16)0x09);//3.4M
      mI2cMode = V_SPEED_HIGH;
  }

  //
  //  Select the frequency counter,
  //  Enable restart condition,
  //  Enable master FSM, disable slave FSM.
  //
  mI2cMode |= B_IC_RESTART_EN | B_IC_SLAVE_DISABLE | B_MASTER_MODE;

  return EFI_SUCCESS;
}

/**
  Initializes the host controller to execute I2C commands.

  @param I2cControllerIndex Index of I2C controller in LPSS device. 0 represents I2C0, which is PCI function 1 of LPSS device.   
                                
  @return EFI_SUCCESS       Opcode initialization on the I2C host controller completed.
  @return EFI_DEVICE_ERROR  Device error, operation failed.
**/
EFI_STATUS
I2CInit (
  IN  UINT8  I2cControllerIndex,
  IN  UINT16 SlaveAddress
  )
{
  EFI_STATUS Status=RETURN_SUCCESS;
  UINT32    NumTries = 0;
  UINTN    GnvsI2cBarAddr=0;
  
  //
  // Verify the parameters
  //
  if ((1023 < SlaveAddress) || (6 < I2cControllerIndex)) {
    Status =  RETURN_INVALID_PARAMETER;
    DEBUG((EFI_D_INFO,"I2CInit Exit with RETURN_INVALID_PARAMETER\r\n"));
    return Status;
  }
  MmioWrite32 ( mI2CBaseAddress + R_IC_TAR, (UINT16)SlaveAddress );
  mI2CSlaveAddress = SlaveAddress;

  //
  // 1.PEI: program and init ( before pci enumeration).
  // 2.DXE:update address and re-init ( after pci enumeration).
  // 3.BDS:update ACPI address and re-init ( after acpi mode is enabled).
  //
  if(mI2CBaseAddress == mLpssPciDeviceList[I2cControllerIndex + 1].Bar0) {
    
    //
    // I2CInit is already  called.
    //
    GnvsI2cBarAddr=GetI2cBarAddr(I2cControllerIndex);
    
    if((GnvsI2cBarAddr == 0)||(GnvsI2cBarAddr == mI2CBaseAddress)) {
      DEBUG((EFI_D_INFO,"I2CInit Exit with mI2CBaseAddress:%x == [%x].Bar0\r\n",mI2CBaseAddress,I2cControllerIndex+1));
      return RETURN_SUCCESS;
    }
  }
  
  Status=ProgramPciLpssI2C(I2cControllerIndex);
  if(Status!=EFI_SUCCESS) {
    return Status;
  }


  mI2CBaseAddress = (UINT32) mLpssPciDeviceList[I2cControllerIndex + 1].Bar0;
  DEBUG ((EFI_D_ERROR, "mI2CBaseAddress = 0x%x \n",mI2CBaseAddress));
  
  //
  // 1 seconds.
  //
  NumTries = 10000; 
  while ((1 == ( MmioRead32 ( mI2CBaseAddress + R_IC_STATUS) & STAT_MST_ACTIVITY ))) {
    MicroSecondDelay(10);
    NumTries --;
    if(0 == NumTries) {
      DEBUG((EFI_D_INFO, "Try timeout\r\n"));
      return RETURN_DEVICE_ERROR;
    }
  }
  
  Status = I2cDisable();
  DEBUG((EFI_D_INFO, "I2cDisable Status = %r\r\n", Status));
  I2cBusFrequencySet(400 * 1000);

  MmioWrite32(mI2CBaseAddress + R_IC_INTR_MASK, 0x0);
  if (0x7f < SlaveAddress )
    SlaveAddress = ( SlaveAddress & 0x3ff ) | IC_TAR_10BITADDR_MASTER;
  MmioWrite32 ( mI2CBaseAddress + R_IC_TAR, (UINT16)SlaveAddress );
  MmioWrite32 ( mI2CBaseAddress + R_IC_RX_TL, 0);
  MmioWrite32 ( mI2CBaseAddress + R_IC_TX_TL, 0 );
  MmioWrite32 ( mI2CBaseAddress + R_IC_CON, mI2cMode);
  Status = I2cEnable();

  DEBUG((EFI_D_INFO, "I2cEnable Status = %r\r\n", Status));
  MmioRead32 ( mI2CBaseAddress + R_IC_CLR_TX_ABRT );
  
  return EFI_SUCCESS;
}

/**
  Reads a Byte from I2C Device.
 
  @param  I2cControllerIndex             I2C Bus no to which the I2C device has been connected
  @param  SlaveAddress      Device Address from which the byte value has to be read
  @param  Offset            Offset from which the data has to be read
  @param  *Byte             Address to which the value read has to be stored
  @param  Start               Whether a RESTART is issued before the byte is sent or received
  @param  End                 Whether STOP is generated after a data byte is sent or received  
                                  
  @return  EFI_SUCCESS       IF the byte value has been successfully read
  @return  EFI_DEVICE_ERROR  Operation Failed, Device Error
**/
EFI_STATUS 
ByteReadI2CBasic(
  IN  UINT8 I2cControllerIndex,
  IN  UINT8 SlaveAddress,
  IN  UINTN ReadBytes,
  OUT UINT8 *ReadBuffer,
  IN  UINT8 Start,
  IN  UINT8 End
  )
{

  EFI_STATUS Status;
  UINT32 I2cStatus;
  UINT16 ReceiveData;
  UINT8 *ReceiveDataEnd;
  UINT8 *ReceiveRequest;
  UINT16 RawIntrStat;
  UINT32 Count=0;

  Status = EFI_SUCCESS;

  ReceiveDataEnd = &ReadBuffer [ReadBytes];
  if( ReadBytes ) {

    ReceiveRequest = ReadBuffer;
    DEBUG((EFI_D_INFO,"Read: ---------------%d bytes to RX\r\n",ReceiveDataEnd - ReceiveRequest));

    while ((ReceiveDataEnd > ReceiveRequest) || (ReceiveDataEnd > ReadBuffer)) {
      
      //
      //  Check for NACK
      //
      RawIntrStat = (UINT16)MmioRead32 (mI2CBaseAddress + R_IC_RawIntrStat);
      if ( 0 != ( RawIntrStat & I2C_INTR_TX_ABRT )) {
        MmioRead32 ( mI2CBaseAddress + R_IC_CLR_TX_ABRT );
        Status = RETURN_DEVICE_ERROR;
        DEBUG((EFI_D_INFO,"TX ABRT ,%d bytes hasn't been transferred\r\n",ReceiveDataEnd - ReceiveRequest));
        break;
      }
      
      //
      // Determine if another byte was received
      //
      I2cStatus = (UINT16)MmioRead32 (mI2CBaseAddress + R_IC_STATUS);
      if (0 != ( I2cStatus & STAT_RFNE )) {
        ReceiveData = (UINT16)MmioRead32 ( mI2CBaseAddress + R_IC_DATA_CMD );
        *ReadBuffer++ = (UINT8)ReceiveData;
        DEBUG((EFI_D_INFO,"MmioRead32 ,1 byte 0x:%x is received\r\n",ReceiveData));
      }

      if(ReceiveDataEnd == ReceiveRequest) {
        MicroSecondDelay ( FIFO_WRITE_DELAY );
        DEBUG((EFI_D_INFO,"ReceiveDataEnd==ReceiveRequest------------%x\r\n",I2cStatus & STAT_RFNE));
        Count++;
        if(Count<1024) {
          //
          // To avoid sys hung  without ul-pmc device  on RVP,
          // waiting the last request to get data and make (ReceiveDataEnd > ReadBuffer) =TRUE.
          //
          continue;
        } else {
          break;
        }
      }
      
      //
      //  Wait until a read request will fit.
      //
      if (0 == (I2cStatus & STAT_TFNF)) {
        DEBUG((EFI_D_INFO,"Wait until a read request will fit\r\n"));
        MicroSecondDelay (10);
        continue;
      }
      
      //
      //  Issue the next read request.
      //
      if(End && Start) {
        MmioWrite32 ( mI2CBaseAddress + R_IC_DATA_CMD, B_READ_CMD|B_CMD_RESTART|B_CMD_STOP);
      } else if (!End && Start) {
        MmioWrite32 ( mI2CBaseAddress + R_IC_DATA_CMD, B_READ_CMD|B_CMD_RESTART);
      } else if (End && !Start) {
        MmioWrite32 ( mI2CBaseAddress + R_IC_DATA_CMD, B_READ_CMD|B_CMD_STOP);
      } else if (!End && !Start) {
        MmioWrite32 ( mI2CBaseAddress + R_IC_DATA_CMD, B_READ_CMD);
      }
      MicroSecondDelay (FIFO_WRITE_DELAY);

      ReceiveRequest += 1;
    }
  }
  
  return Status;
}

/**
  Writes a Byte to I2C Device.
 
  @param  I2cControllerIndex   I2C Bus no to which the I2C device has been connected
  @param  SlaveAddress         Device Address from which the byte value has to be written
  @param  Offset               Offset from which the data has to be read
  @param  *Byte                Address to which the value written is stored
  @param  Start               Whether a RESTART is issued before the byte is sent or received
  @param  End                 Whether STOP is generated after a data byte is sent or received  
                                    
  @return  EFI_SUCCESS         IF the byte value has been successfully written
  @return  EFI_DEVICE_ERROR    Operation Failed, Device Error
**/
EFI_STATUS ByteWriteI2CBasic(
  IN  UINT8 I2cControllerIndex,
  IN  UINT8 SlaveAddress,
  IN  UINTN WriteBytes,
  IN  UINT8 *WriteBuffer,
  IN  UINT8 Start,
  IN  UINT8 End
  )
{

  EFI_STATUS Status;
  UINT32 I2cStatus;
  UINT8 *TransmitEnd;
  UINT16 RawIntrStat;
  UINT32 Count=0;

  Status = EFI_SUCCESS;

  Status=I2CInit(I2cControllerIndex, SlaveAddress);
  if(Status!=EFI_SUCCESS)
    return Status;

  TransmitEnd = &WriteBuffer[WriteBytes];
  if( WriteBytes ) {
    DEBUG((EFI_D_INFO,"Write: --------------%d bytes to TX\r\n",TransmitEnd - WriteBuffer));
    while (TransmitEnd > WriteBuffer) {
      I2cStatus = MmioRead32 (mI2CBaseAddress + R_IC_STATUS);
      RawIntrStat = (UINT16)MmioRead32 (mI2CBaseAddress + R_IC_RawIntrStat);
      if (0 != ( RawIntrStat & I2C_INTR_TX_ABRT)) {
        MmioRead32 ( mI2CBaseAddress + R_IC_CLR_TX_ABRT);
        Status = RETURN_DEVICE_ERROR;
        DEBUG((EFI_D_ERROR,"TX ABRT TransmitEnd:0x%x WriteBuffer:0x%x\r\n", TransmitEnd, WriteBuffer));
        break;
      }
      if (0 == (I2cStatus & STAT_TFNF)) {
        //
        // If TX not full , will  send cmd  or continue to wait
        //
        MicroSecondDelay (FIFO_WRITE_DELAY);
        continue;
      }

      if(End && Start) {
        MmioWrite32 (mI2CBaseAddress + R_IC_DATA_CMD, (*WriteBuffer++)|B_CMD_RESTART|B_CMD_STOP);
      } else if (!End && Start) {
        MmioWrite32 (mI2CBaseAddress + R_IC_DATA_CMD, (*WriteBuffer++)|B_CMD_RESTART);
      } else if (End && !Start) {
        MmioWrite32 (mI2CBaseAddress + R_IC_DATA_CMD, (*WriteBuffer++)|B_CMD_STOP);
      } else if (!End && !Start ) {
        MmioWrite32 (mI2CBaseAddress + R_IC_DATA_CMD, (*WriteBuffer++));
      }
      
      //
      // Add a small delay to work around some odd behavior being seen.  Without this delay bytes get dropped.
      //
      MicroSecondDelay ( FIFO_WRITE_DELAY );//wait after send cmd
      
      //
      // Time out
      //
      while(1) {
        RawIntrStat = MmioRead16 ( mI2CBaseAddress + R_IC_RawIntrStat );
        if (0 != ( RawIntrStat & I2C_INTR_TX_ABRT)) {
          MmioRead16 (mI2CBaseAddress + R_IC_CLR_TX_ABRT);
          Status = RETURN_DEVICE_ERROR;
          DEBUG((EFI_D_ERROR,"TX ABRT TransmitEnd:0x%x WriteBuffer:0x%x\r\n", TransmitEnd, WriteBuffer));
        }
        if(0 == MmioRead16(mI2CBaseAddress + R_IC_TXFLR)) break;

        MicroSecondDelay (FIFO_WRITE_DELAY);
        Count++;
        if(Count<1024) {
          //
          // to avoid sys hung without ul-pmc device on RVP.
          // Waiting the last request to get data and make (ReceiveDataEnd > ReadBuffer) =TRUE.
          //
          continue;
        } else {
          break;
        }
      }//while( 1 )
    }

  }

  return Status;
}

/**
  Reads a Byte from I2C Device.
 
  @param  I2cControllerIndex   I2C Bus no to which the I2C device has been connected
  @param  SlaveAddress         Device Address from which the byte value has to be read
  @param  Offset               Offset from which the data has to be read
  @param  ReadBytes            Number of bytes to be read
  @param  *ReadBuffer          Address to which the value read has to be stored
                                
  @return  EFI_SUCCESS       IF the byte value has been successfully read
  @return  EFI_DEVICE_ERROR  Operation Failed, Device Error
**/
EFI_STATUS ByteReadI2C(
  IN  UINT8 I2cControllerIndex,
  IN  UINT8 SlaveAddress,
  IN  UINT8 Offset,
  IN  UINTN ReadBytes,
  OUT UINT8 *ReadBuffer
  )
{
  EFI_STATUS          Status;

  DEBUG ((EFI_D_INFO, "ByteReadI2C:---offset:0x%x\n",Offset));
  Status = ByteWriteI2CBasic(I2cControllerIndex, SlaveAddress,1,&Offset,TRUE,FALSE);
  Status = ByteReadI2CBasic(I2cControllerIndex, SlaveAddress,ReadBytes,ReadBuffer,TRUE,TRUE);

  return Status;
}

/**
  Writes a Byte to I2C Device.
 
  @param  I2cControllerIndex  I2C Bus no to which the I2C device has been connected
  @param  SlaveAddress        Device Address from which the byte value has to be written
  @param  Offset              Offset from which the data has to be written
  @param  WriteBytes          Number of bytes to be written
  @param  *Byte               Address to which the value written is stored
                                
  @return  EFI_SUCCESS       IF the byte value has been successfully read
  @return  EFI_DEVICE_ERROR  Operation Failed, Device Error
**/
EFI_STATUS ByteWriteI2C(
  IN  UINT8 I2cControllerIndex,
  IN  UINT8 SlaveAddress,
  IN  UINT8 Offset,
  IN  UINTN WriteBytes,
  IN  UINT8 *WriteBuffer
  )
{
  EFI_STATUS          Status;

  DEBUG ((EFI_D_INFO, "ByteWriteI2C:---offset/bytes/buf:0x%x,0x%x,0x%x,0x%x\n",Offset,WriteBytes,WriteBuffer,*WriteBuffer));
  Status = ByteWriteI2CBasic(I2cControllerIndex, SlaveAddress,1,&Offset,TRUE,FALSE);
  Status = ByteWriteI2CBasic(I2cControllerIndex, SlaveAddress,WriteBytes,WriteBuffer,FALSE,TRUE);

  return Status;
}
