/** @file

  Copyright (c) 2004  - 2014, 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.    

                                                                                   


Module Name:


  PciDevice.c

Abstract:

  Platform Initialization Driver.

Revision History

--*/

#include "PlatformDxe.h"
#include "Library/DxeServicesTableLib.h"
#include "PciBus.h"
#include "Guid/PciLanInfo.h"

extern  VOID    *mPciLanInfo;
extern  UINTN   mPciLanCount;

extern  EFI_HANDLE  mImageHandle;
extern  SYSTEM_CONFIGURATION    mSystemConfiguration;


VOID       *mPciRegistration;
#define NCR_VENDOR_ID  0x1000
#define ATI_VENDOR_ID  0x1002
#define INTEL_VENDOR_ID 0x8086
#define ATI_RV423_ID   0x5548
#define ATI_RV423_ID2  0x5d57
#define ATI_RV380_ID   0x3e50
#define ATI_RV370_ID   0x5b60
#define SI_VENDOR_ID   0x1095
#define SI_SISATA_ID   0x3114
#define SI_SIRAID_PCIUNL 0x40
#define INTEL_82573E_IDER 0x108D

typedef struct {
  UINT8               ClassCode;
  UINT8               SubClassCode;
  UINT16              VendorId;
  UINT16              DeviceId;
} BAD_DEVICE_TABLE;

BAD_DEVICE_TABLE BadDeviceTable[] = {
                    {(UINT8)PCI_CLASS_MASS_STORAGE,(UINT8)PCI_CLASS_MASS_STORAGE_SCSI,(UINT16)NCR_VENDOR_ID, (UINT16)0xffff}, // Any NCR cards
                    {(UINT8)PCI_CLASS_MASS_STORAGE,(UINT8)PCI_CLASS_MASS_STORAGE_IDE,(UINT16)INTEL_VENDOR_ID, (UINT16)INTEL_82573E_IDER},  // Intel i82573E Tekoa GBit Lan IDE-R
                    {(UINT8)0xff,(UINT8)0xff,(UINT16)0xffff,(UINT16)0xffff}
                  };

EFI_STATUS
PciBusDriverHook (
  )
{
  EFI_STATUS                Status;
  EFI_EVENT                 FilterEvent;

  //
  // Register for callback to PCI I/O protocol
  //
  Status = gBS->CreateEvent (
                  EVT_NOTIFY_SIGNAL,
                  TPL_CALLBACK,
                  PciBusEvent,
                  NULL,
                  &FilterEvent
                  );
  ASSERT_EFI_ERROR(Status);

  //
  // Register for protocol notifications on this event
  //
  Status = gBS->RegisterProtocolNotify (
                  &gEfiPciIoProtocolGuid,
                  FilterEvent,
                  &mPciRegistration
                  );
  ASSERT_EFI_ERROR (Status);

  return  EFI_SUCCESS;
}

VOID
InitBadBars(
  IN    EFI_PCI_IO_PROTOCOL           *PciIo,
  IN    UINT16                        VendorId,
  IN    UINT16                        DeviceId
  )
{

  UINT64                              BaseAddress = 0;
  UINT64                              TempBaseAddress = 0;
  UINT8                               RevId = 0;
  UINT32                              Bar;
  UINT64                              IoSize;
  UINT64                              MemSize;
  UINTN                               MemSizeBits;

  switch ( VendorId) {
    case ATI_VENDOR_ID:
      //
      //  ATI fix-ups. At this time all ATI cards in BadDeviceTable
      //  have same problem in that OPROM BAR needs to be increased.
      //
      Bar = 0x30 ;
      //
      // Get original BAR address
      //
      PciIo->Pci.Read (
                   PciIo,
                   EfiPciIoWidthUint32,
                   Bar,
                   1,
                       (VOID *) &BaseAddress
                   );
      //
      // Find BAR size
      //
      TempBaseAddress = 0xffffffff;
      PciIo->Pci.Write (
                   PciIo,
                   EfiPciIoWidthUint32,
                   Bar,
                   1,
                        (VOID *) &TempBaseAddress
                   );
      PciIo->Pci.Read (
                   PciIo,
                   EfiPciIoWidthUint32,
                   Bar,
                   1,
                       (VOID *) &TempBaseAddress
                   );
      TempBaseAddress &= 0xfffffffe;
      MemSize = 1;
      while ((TempBaseAddress & 0x01) == 0) {
        TempBaseAddress = TempBaseAddress >> 1;
        MemSize = MemSize << 1;
      }

      //
      // Free up allocated memory memory and re-allocate with increased size.
      //
      gDS->FreeMemorySpace (
             BaseAddress,
             MemSize
             );
      //
      // Force new alignment
      //
      MemSize = 0x8000000;
      MemSizeBits = 28;

      gDS->AllocateMemorySpace (
             EfiGcdAllocateAnySearchBottomUp,
             EfiGcdMemoryTypeMemoryMappedIo,
             MemSizeBits,           // Alignment
             MemSize,
             &BaseAddress,
             mImageHandle,
             NULL
             );
      PciIo->Pci.Write (
                   PciIo,
                   EfiPciIoWidthUint32,
                   Bar,
                   1,
                        (VOID *) &BaseAddress
                   );

      break;
    case    NCR_VENDOR_ID:
#define MIN_NCR_IO_SIZE  0x800
#define NCR_GRAN  11  // 2**11 = 0x800
  //
  // NCR SCSI cards like 8250S lie about IO needed. Assign as least 0x80.
  //
  for (Bar = 0x10; Bar < 0x28; Bar+= 4) {

    PciIo->Pci.Read (
                 PciIo,
                 EfiPciIoWidthUint32,
                 Bar,
                 1,
                     (VOID *) &BaseAddress
                 );
    if (BaseAddress && 0x01) {
      TempBaseAddress = 0xffffffff;
      PciIo->Pci.Write (
                   PciIo,
                   EfiPciIoWidthUint32,
                   Bar,
                   1,
                        (VOID *) &TempBaseAddress
                   );
      TempBaseAddress &= 0xfffffffc;
      IoSize = 1;
      while ((TempBaseAddress & 0x01) == 0) {
        TempBaseAddress = TempBaseAddress >> 1;
        IoSize = IoSize << 1;
      }
      if (IoSize < MIN_NCR_IO_SIZE) {
        gDS->FreeIoSpace (
               BaseAddress,
               IoSize
               );

        gDS->AllocateIoSpace (
               EfiGcdAllocateAnySearchTopDown,
               EfiGcdIoTypeIo,
               NCR_GRAN,           // Alignment
               MIN_NCR_IO_SIZE,
               &BaseAddress,
               mImageHandle,
               NULL
               );
        TempBaseAddress = BaseAddress + 1;
        PciIo->Pci.Write (
                     PciIo,
                     EfiPciIoWidthUint32,
                     Bar,
                     1,
                          (VOID *) &TempBaseAddress
                     );
      }
    }
  }

      break;

    case INTEL_VENDOR_ID:
      if (DeviceId == INTEL_82573E_IDER) {
        //
        //  Tekoa i82573E IDE-R fix-ups. At this time A2 step and earlier parts do not
        //  support any BARs except BAR0. Other BARS will actualy map to BAR0 so disable
        //  them all for Control Blocks and Bus mastering ops as well as Secondary IDE
        //  Controller.
        //  All Tekoa A2 or earlier step chips for now.
        //
        PciIo->Pci.Read (
                     PciIo,
                     EfiPciIoWidthUint8,
                     PCI_REVISION_ID_OFFSET,
                     1,
                     &RevId
                     );
        if (RevId <= 0x02) {
          for (Bar = 0x14; Bar < 0x24; Bar+= 4) {
            //
            // Maybe want to clean this up a bit later but for now just clear out the secondary
            // Bars don't worry aboyut freeing up thge allocs.
            //
            TempBaseAddress = 0x0;
            PciIo->Pci.Write (
                         PciIo,
                         EfiPciIoWidthUint32,
                         Bar,
                         1,
                              (VOID *) &TempBaseAddress
                         );
          } // end for
        }
        else
        {
        	//
          //Tekoa A3 or above:
          //Clear bus master base address (PCI register 0x20)
          //since Tekoa does not fully support IDE Bus Mastering
          //
          TempBaseAddress = 0x0;
          PciIo->Pci.Write (
                       PciIo,
                       EfiPciIoWidthUint32,
                       0x20,
                       1,
                            (VOID *) &TempBaseAddress
                       );
        }
      }
      break;

    default:
      break;
  }
  return;
}

VOID
ProgramPciLatency(
  IN    EFI_PCI_IO_PROTOCOL           *PciIo
  )
{
  //
  // Program Master Latency Timer
  //
  if (mSystemConfiguration.PciLatency != 0) {
     PciIo->Pci.Write (
                  PciIo,
                  EfiPciIoWidthUint8,
                  PCI_LATENCY_TIMER_OFFSET,
                  1,
                  &mSystemConfiguration.PciLatency
                  );
  }
  return;
}

/**
During S5 shutdown, we need to program PME in all LAN devices.
Here we identify LAN devices and save their bus/dev/func.

**/
VOID
SavePciLanAddress(
  IN EFI_PCI_IO_PROTOCOL    *PciIo
  )
{
  EFI_STATUS        Status;
  UINTN             PciSegment,
                    PciBus,
                    PciDevice,
                    PciFunction;
  VOID              *NewBuffer;
  PCI_LAN_INFO      *x;

  Status = PciIo->GetLocation (
                    PciIo,
                    &PciSegment,
                    &PciBus,
                    &PciDevice,
                    &PciFunction
                    );
  if (EFI_ERROR (Status)) {
    return;
  }

  mPciLanCount ++;
  Status = gBS->AllocatePool (
                  EfiBootServicesData,
                  mPciLanCount * sizeof(PCI_LAN_INFO),
                  &NewBuffer
                  );
  if (EFI_ERROR (Status)) {
    return;
  }

  if (mPciLanCount > 1) {
    //
    // copy old data into new, larger buffer
    //
    gBS->CopyMem (
           NewBuffer,
           mPciLanInfo,
           (mPciLanCount - 1) * sizeof(PCI_LAN_INFO)
           );

    //
    // free the old memory buffer
    //
    gBS->FreePool (mPciLanInfo);

  }

  //
  // init the new entry
  //
  x = (PCI_LAN_INFO *)NewBuffer + (mPciLanCount - 1);
  x->PciBus = (UINT8)PciBus;
  x->PciDevice = (UINT8)PciDevice;
  x->PciFunction = (UINT8)PciFunction;

  mPciLanInfo = NewBuffer;

  return;
}

/**
  @param  Event          the event that is signaled.
  @param Context        not used here.


**/
VOID
EFIAPI
PciBusEvent (
  IN EFI_EVENT    Event,
  IN VOID*        Context
  )
{

  EFI_STATUS                    Status;
  UINTN                         BufferSize;
  EFI_HANDLE                    Handle;
  EFI_PCI_IO_PROTOCOL           *PciIo;
  PCI_IO_DEVICE                 *PciIoDevice;
  UINT64                        Supports;
  UINTN                         Index;
  UINT8                         mCacheLineSize = 0x10;

  while (TRUE) {
    BufferSize = sizeof (EFI_HANDLE);
    Status = gBS->LocateHandle (
                    ByRegisterNotify,
                    NULL,
                    mPciRegistration,
                    &BufferSize,
                    &Handle
                    );
    if (EFI_ERROR (Status)) {
      //
      // If no more notification events exist
      //
      return;
    }

    Status = gBS->HandleProtocol (
                    Handle,
                    &gEfiPciIoProtocolGuid,
                    (void **)&PciIo
                    );

    PciIoDevice = PCI_IO_DEVICE_FROM_PCI_IO_THIS (PciIo);

    //
    // Enable I/O for bridge so port 0x80 codes will come out
    //
    if (PciIoDevice->Pci.Hdr.VendorId == V_PCH_INTEL_VENDOR_ID)
    {
      Status = PciIo->Attributes(
                        PciIo,
                        EfiPciIoAttributeOperationSupported,
                        0,
                        &Supports
                        );
      Supports &= EFI_PCI_DEVICE_ENABLE;
      Status = PciIo->Attributes (
                        PciIo,
                        EfiPciIoAttributeOperationEnable,
                        Supports,
                        NULL
                        );
      break;
    }

    //
    // Program PCI Latency Timer
    //
    ProgramPciLatency(PciIo);

    //
    // Program Cache Line Size to 64 bytes (0x10 DWORDs)
    //
    Status = PciIo->Pci.Write (
                          PciIo,
                          EfiPciIoWidthUint8,
                          PCI_CACHELINE_SIZE_OFFSET,
                          1,
                          &mCacheLineSize
                          );

    //
    // If PCI LAN device, save bus/dev/func info
    // so we can program PME during S5 shutdown
    //
    if (PciIoDevice->Pci.Hdr.ClassCode[2] == PCI_CLASS_NETWORK) {
      SavePciLanAddress(PciIo);
      break;
    }

    //
    // Workaround for cards with bad BARs
    //
    Index = 0;
    while (BadDeviceTable[Index].ClassCode != 0xff) {
      if (BadDeviceTable[Index].DeviceId == 0xffff) {
        if ((PciIoDevice->Pci.Hdr.ClassCode[2] == BadDeviceTable[Index].ClassCode) &&
            (PciIoDevice->Pci.Hdr.ClassCode[1] == BadDeviceTable[Index].SubClassCode) &&
            (PciIoDevice->Pci.Hdr.VendorId == BadDeviceTable[Index].VendorId)) {
          InitBadBars(PciIo,BadDeviceTable[Index].VendorId,BadDeviceTable[Index].DeviceId);
        }
      } else {
        if ((PciIoDevice->Pci.Hdr.ClassCode[2] == BadDeviceTable[Index].ClassCode) &&
            (PciIoDevice->Pci.Hdr.ClassCode[1] == BadDeviceTable[Index].SubClassCode) &&
            (PciIoDevice->Pci.Hdr.VendorId == BadDeviceTable[Index].VendorId) &&
            (PciIoDevice->Pci.Hdr.DeviceId == BadDeviceTable[Index].DeviceId)) {

          InitBadBars(PciIo,BadDeviceTable[Index].VendorId,BadDeviceTable[Index].DeviceId);
        }
      }
      ++Index;
    }
      break;
    }

  return;
}