/*++

Copyright (c) 2005 - 2012, 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         
which 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.             

Module Name:
    PcatPciRootBridgeIo.c
    
Abstract:

    EFI PC AT PCI Root Bridge Io Protocol

Revision History

--*/

#include "PcatPciRootBridge.h"
#include <IndustryStandard/Pci.h>
#include "SalProc.h"

#include EFI_GUID_DEFINITION (SalSystemTable)

//
// Might be good to put this in an include file, but people may start
//  using it! They should always access the EFI abstraction that is
//  contained in this file. Just a little information hiding.
//
#define PORT_TO_MEM(_Port) ( ((_Port) & 0xffffffffffff0000) | (((_Port) & 0xfffc) << 10) | ((_Port) & 0x0fff) )
                                                                           
//                                                                  
// Macro's with casts make this much easier to use and read.
//
#define PORT_TO_MEM8(_Port)     (*(UINT8  *)(PORT_TO_MEM(_Port)))
#define PORT_TO_MEM16(_Port)    (*(UINT16 *)(PORT_TO_MEM(_Port)))
#define PORT_TO_MEM32(_Port)    (*(UINT32 *)(PORT_TO_MEM(_Port)))

#define EFI_PCI_ADDRESS_IA64(_seg, _bus,_dev,_func,_reg) \
    ( (UINT64) ( (((UINTN)_seg) << 24) + (((UINTN)_bus) << 16) + (((UINTN)_dev) << 11) + (((UINTN)_func) << 8) + ((UINTN)_reg)) )

//
// Local variables for performing SAL Proc calls
//
PLABEL         mSalProcPlabel;
CALL_SAL_PROC  mGlobalSalProc;

EFI_STATUS
PcatRootBridgeIoIoRead (
  IN     EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL        *This,
  IN     EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_WIDTH  Width,
  IN     UINT64                                 UserAddress,
  IN     UINTN                                  Count,
  IN OUT VOID                                   *UserBuffer
  )
{
  PCAT_PCI_ROOT_BRIDGE_INSTANCE *PrivateData;
  UINTN                         InStride;
  UINTN                         OutStride;
  UINTN                         AlignMask;
  UINTN                         Address;
  PTR                           Buffer;
  UINT16                        Data16;
  UINT32                        Data32;
  
 
  if ( UserBuffer == NULL ) {
    return EFI_INVALID_PARAMETER;
  }
  
  PrivateData = DRIVER_INSTANCE_FROM_PCI_ROOT_BRIDGE_IO_THIS(This);

  Address    = (UINTN)  UserAddress;
  Buffer.buf = (UINT8 *)UserBuffer;

  if ( Address < PrivateData->IoBase || Address > PrivateData->IoLimit ) {
    return EFI_INVALID_PARAMETER;
  }
    
  if ((UINT32)Width >= EfiPciWidthMaximum) {
    return EFI_INVALID_PARAMETER;
  }

  if ((Width & 0x03) == EfiPciWidthUint64) {
    return EFI_INVALID_PARAMETER;
  }

  AlignMask = (1 << (Width & 0x03)) - 1;
  if ( Address & AlignMask ) {
    return EFI_INVALID_PARAMETER;
  }

  InStride  = 1 << (Width & 0x03);
  OutStride = InStride;
  if (Width >=EfiPciWidthFifoUint8 && Width <= EfiPciWidthFifoUint64) {
    InStride = 0;
  }
  if (Width >=EfiPciWidthFillUint8 && Width <= EfiPciWidthFillUint64) {
    OutStride = 0;
  }
  Width = Width & 0x03;

  Address += PrivateData->PhysicalIoBase;

  //
  // Loop for each iteration and move the data
  //

  switch (Width) {
  case EfiPciWidthUint8:
    for (; Count > 0; Count--, Buffer.buf += OutStride, Address += InStride) {
      MEMORY_FENCE();
      *Buffer.ui8 = PORT_TO_MEM8(Address);
      MEMORY_FENCE();
    }
    break;

  case EfiPciWidthUint16:
    for (; Count > 0; Count--, Buffer.buf += OutStride, Address += InStride) {
      MEMORY_FENCE();
      if (Buffer.ui & 0x1) {
        Data16 = PORT_TO_MEM16(Address);
        *Buffer.ui8     = (UINT8)(Data16 & 0xff);
        *(Buffer.ui8+1) = (UINT8)((Data16 >> 8) & 0xff);
      } else {
        *Buffer.ui16 = PORT_TO_MEM16(Address);
      }
      MEMORY_FENCE();
    }
    break;

  case EfiPciWidthUint32:
    for (; Count > 0; Count--, Buffer.buf += OutStride, Address += InStride) {
      MEMORY_FENCE();
      if (Buffer.ui & 0x3) {
        Data32 = PORT_TO_MEM32(Address);
        *Buffer.ui8     = (UINT8)(Data32 & 0xff);
        *(Buffer.ui8+1) = (UINT8)((Data32 >> 8) & 0xff);
        *(Buffer.ui8+2) = (UINT8)((Data32 >> 16) & 0xff);
        *(Buffer.ui8+3) = (UINT8)((Data32 >> 24) & 0xff);
      } else {
        *Buffer.ui32 = PORT_TO_MEM32(Address);
      }
      MEMORY_FENCE();
    }
    break;
  }

  return EFI_SUCCESS;
}

EFI_STATUS
PcatRootBridgeIoIoWrite (
  IN EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL        *This,
  IN EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_WIDTH  Width,
  IN UINT64                                 UserAddress,
  IN UINTN                                  Count,
  IN OUT VOID                               *UserBuffer
  )
{
  PCAT_PCI_ROOT_BRIDGE_INSTANCE  *PrivateData;
  UINTN                          InStride;
  UINTN                          OutStride;
  UINTN                          AlignMask;
  UINTN                          Address;
  PTR                            Buffer;
  UINT16                         Data16;
  UINT32                         Data32;

  if ( UserBuffer == NULL ) {
    return EFI_INVALID_PARAMETER;
  }

  PrivateData = DRIVER_INSTANCE_FROM_PCI_ROOT_BRIDGE_IO_THIS(This);

  Address    = (UINTN)  UserAddress;
  Buffer.buf = (UINT8 *)UserBuffer;

  if ( Address < PrivateData->IoBase || Address > PrivateData->IoLimit ) {
    return EFI_INVALID_PARAMETER;
  }
    
  if (Width < 0 || Width >= EfiPciWidthMaximum) {
    return EFI_INVALID_PARAMETER;
  }

  if ((Width & 0x03) == EfiPciWidthUint64) {
    return EFI_INVALID_PARAMETER;
  }

  AlignMask = (1 << (Width & 0x03)) - 1;
  if ( Address & AlignMask ) {
    return EFI_INVALID_PARAMETER;
  }

  InStride  = 1 << (Width & 0x03);
  OutStride = InStride;
  if (Width >=EfiPciWidthFifoUint8 && Width <= EfiPciWidthFifoUint64) {
    InStride = 0;
  }
  if (Width >=EfiPciWidthFillUint8 && Width <= EfiPciWidthFillUint64) {
    OutStride = 0;
  }
  Width = Width & 0x03;

  Address += PrivateData->PhysicalIoBase;

  //
  // Loop for each iteration and move the data
  //

  switch (Width) {
  case EfiPciWidthUint8:
    for (; Count > 0; Count--, Buffer.buf += OutStride, Address += InStride) {
      MEMORY_FENCE();
      PORT_TO_MEM8(Address) = *Buffer.ui8;
      MEMORY_FENCE();
    }
    break;

  case EfiPciWidthUint16:
    for (; Count > 0; Count--, Buffer.buf += OutStride, Address += InStride) {
      MEMORY_FENCE();
      if (Buffer.ui & 0x1) {
        Data16 = *Buffer.ui8;
        Data16 = Data16 | (*(Buffer.ui8+1) << 8);
        PORT_TO_MEM16(Address) = Data16;
      } else {
        PORT_TO_MEM16(Address) = *Buffer.ui16;
      }
      MEMORY_FENCE();
    }
    break;
  case EfiPciWidthUint32:
    for (; Count > 0; Count--, Buffer.buf += OutStride, Address += InStride) {
      MEMORY_FENCE();
      if (Buffer.ui & 0x3) {
        Data32 = *Buffer.ui8;
        Data32 = Data32 | (*(Buffer.ui8+1) << 8);
        Data32 = Data32 | (*(Buffer.ui8+2) << 16);
        Data32 = Data32 | (*(Buffer.ui8+3) << 24);
        PORT_TO_MEM32(Address) = Data32;
      } else {
        PORT_TO_MEM32(Address) = *Buffer.ui32;
      }
      MEMORY_FENCE();
    }
    break;
  }

  return EFI_SUCCESS;
}

EFI_STATUS
PcatRootBridgeIoGetIoPortMapping (
  OUT EFI_PHYSICAL_ADDRESS  *IoPortMapping,
  OUT EFI_PHYSICAL_ADDRESS  *MemoryPortMapping
  )
/*++

  Get the IO Port Map from the SAL System Table.
  
--*/
{
  SAL_SYSTEM_TABLE_ASCENDING_ORDER    *SalSystemTable;
  SAL_ST_MEMORY_DESCRIPTOR_ENTRY      *SalMemDesc;
  EFI_STATUS                          Status;

  //
  // On all Itanium architectures, bit 63 is the I/O bit for performming Memory Mapped I/O operations
  //
  *MemoryPortMapping = 0x8000000000000000;

  Status = EfiLibGetSystemConfigurationTable(&gEfiSalSystemTableGuid, &SalSystemTable);
  if (EFI_ERROR(Status)) {
    return EFI_NOT_FOUND;
  }

  //
  // BugBug: Add code to test checksum on the Sal System Table
  //
  if (SalSystemTable->Entry0.Type != 0) {
    return EFI_UNSUPPORTED;
  }

  mSalProcPlabel.ProcEntryPoint = SalSystemTable->Entry0.SalProcEntry; 
  mSalProcPlabel.GP             = SalSystemTable->Entry0.GlobalDataPointer;
  mGlobalSalProc                = (CALL_SAL_PROC)&mSalProcPlabel.ProcEntryPoint;

  //
  // The SalSystemTable pointer includes the Type 0 entry.
  //  The SalMemDesc is Type 1 so it comes next.
  //
  SalMemDesc = (SAL_ST_MEMORY_DESCRIPTOR_ENTRY *)(SalSystemTable + 1);
  while (SalMemDesc->Type == SAL_ST_MEMORY_DESCRIPTOR) {
    if (SalMemDesc->MemoryType == SAL_IO_PORT_MAPPING) {
      *IoPortMapping = SalMemDesc->PhysicalMemoryAddress;
      *IoPortMapping |= 0x8000000000000000;
      return EFI_SUCCESS;
    }
    SalMemDesc++;
  }
  return EFI_UNSUPPORTED;
}

EFI_STATUS
PcatRootBridgeIoPciRW (
  IN     EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL        *This,
  IN     BOOLEAN                                Write,
  IN     EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_WIDTH  Width,
  IN     UINT64                                 UserAddress,
  IN     UINTN                                  Count,
  IN OUT UINT8                                  *UserBuffer
  )
{
  PCAT_PCI_ROOT_BRIDGE_INSTANCE  *PrivateData;
  UINTN                          AlignMask;
  UINTN                          InStride;
  UINTN                          OutStride;
  UINT64                         Address;
  DEFIO_PCI_ADDR                 *Defio;
  PTR                            Buffer;
  UINT32                         Data32;
  UINT16                         Data16;
  rArg                           Return;

  if (Width < 0 || Width >= EfiPciWidthMaximum) {
    return EFI_INVALID_PARAMETER;
  }

  if ((Width & 0x03) == EfiPciWidthUint64) {
    return EFI_INVALID_PARAMETER;
  }

  AlignMask = (1 << (Width & 0x03)) - 1;
  if ( UserAddress & AlignMask ) {
    return EFI_INVALID_PARAMETER;
  }

  InStride  = 1 << (Width & 0x03);
  OutStride = InStride;
  if (Width >=EfiPciWidthFifoUint8 && Width <= EfiPciWidthFifoUint64) {
    InStride = 0;
  }
  if (Width >=EfiPciWidthFillUint8 && Width <= EfiPciWidthFillUint64) {
    OutStride = 0;
  }
  Width = Width & 0x03;

  Defio = (DEFIO_PCI_ADDR *)&UserAddress;

  if ((Defio->Function > PCI_MAX_FUNC) || (Defio->Device > PCI_MAX_DEVICE)) {
    return EFI_UNSUPPORTED;
  }
  
  Buffer.buf = (UINT8 *)UserBuffer;
  
  PrivateData = DRIVER_INSTANCE_FROM_PCI_ROOT_BRIDGE_IO_THIS(This);

  Address = EFI_PCI_ADDRESS_IA64(
              This->SegmentNumber, 
              Defio->Bus, 
              Defio->Device, 
              Defio->Function, 
              Defio->Register
              );

  //
  // PCI Config access are all 32-bit alligned, but by accessing the
  //  CONFIG_DATA_REGISTER (0xcfc) with different widths more cycle types
  //  are possible on PCI.
  //
  // SalProc takes care of reading the proper register depending on stride
  //

  EfiAcquireLock(&PrivateData->PciLock);

  while (Count) {

    if(Write) {

      if (Buffer.ui & 0x3) {
        Data32  = (*(Buffer.ui8+0) << 0);
        Data32 |= (*(Buffer.ui8+1) << 8);
        Data32 |= (*(Buffer.ui8+2) << 16);
        Data32 |= (*(Buffer.ui8+3) << 24);
      } else {
        Data32 = *Buffer.ui32;
      }

      Return.p0 = -3;
      Return    = mGlobalSalProc((UINT64) SAL_PCI_CONFIG_WRITE,
                                 Address, 1 << Width, Data32, 0, 0, 0, 0);
        
      if(Return.p0) {
        EfiReleaseLock(&PrivateData->PciLock);
        return EFI_UNSUPPORTED;
      }

    } else {

      Return.p0 = -3;
      Return    = mGlobalSalProc((UINT64) SAL_PCI_CONFIG_READ,
                                 Address, 1 << Width, 0, 0, 0, 0, 0);

      if(Return.p0) {
        EfiReleaseLock(&PrivateData->PciLock);
        return EFI_UNSUPPORTED;
      }

      switch (Width) {
      case EfiPciWidthUint8:
        *Buffer.ui8 = (UINT8)Return.p1;
        break;
      case EfiPciWidthUint16:
        if (Buffer.ui & 0x1) {
          Data16 = (UINT16)Return.p1;
          *(Buffer.ui8 + 0) = Data16 & 0xff;
          *(Buffer.ui8 + 1) = (Data16 >> 8) & 0xff;
        } else {
          *Buffer.ui16 = (UINT16)Return.p1;
        }
        break;
      case EfiPciWidthUint32:
        if (Buffer.ui & 0x3) {
          Data32 = (UINT32)Return.p1;
          *(Buffer.ui8 + 0) = (UINT8)(Data32 & 0xff);
          *(Buffer.ui8 + 1) = (UINT8)((Data32 >> 8) & 0xff);
          *(Buffer.ui8 + 2) = (UINT8)((Data32 >> 16) & 0xff);
          *(Buffer.ui8 + 3) = (UINT8)((Data32 >> 24) & 0xff);
        } else {
          *Buffer.ui32 = (UINT32)Return.p1;
        }
        break;
      }
    }

    Address += InStride;
    Buffer.buf += OutStride;
    Count -= 1;
  }
  
  EfiReleaseLock(&PrivateData->PciLock);

  return EFI_SUCCESS;
}

EFI_STATUS
ScanPciRootBridgeForRoms(
  EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL  *IoDev
  )
  
{
  return EFI_UNSUPPORTED;
}