/*
* Copyright (c) 2006-2008 Advanced Micro Devices,Inc. ("AMD").
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of the
* License, or (at your option) any later version.
*
* This code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.

* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307 USA 
*/

//******************************************************************************
//*    Routines related to the MBus  
//******************************************************************************


#include "VSA2.H"
#include "PROTOS.H"
#include "PCI.H"
#include "GX2.H"
#include "VPCI.H"
#include "CHIPSET.H"
#include "DESCR.H"
#include "MDD.H"
#include "SYSMGR.H"

// Externals:
extern void InitLogicalCounters(void);
extern UCHAR GetPortID(ULONG);
extern void pascal ClearMbiu(ULONG);
extern UCHAR pascal Get_MBus_Status(ULONG, USHORT);
extern UCHAR pascal Is_LBAR_Enabled(PCI_HEADER_ENTRY * Pci);
extern ULONG pascal Compute_IOD_SC(ULONG *, USHORT *, UCHAR);
extern PCI_HEADER_ENTRY * pascal Get_Structure(USHORT);

extern USHORT Class;
extern CAPABILITIES Southbridge_MBIU;
extern ULONG Mbiu2, MPCI_SB;
extern ULONG ACPI_Timer_MSR;
extern Hardware HardwareInfo;
extern MBIU_INFO MbiuInfo[];
extern DESCRIPTOR MSRs[];

// Local Variables:
static ULONG PCI_Value;
ULONG Mbiu0 = PORT_MBIU0;
ULONG Mbiu1;
ULONG LookupMbiu;
ULONG MPCI_NB=0;
ULONG MCP_NB =0;
ULONG FooGlue=0;
UCHAR MBIU1_SelfReference;
UCHAR VG_Port;
UCHAR MC_Port;
CAPABILITIES Northbridge_MBIU0, Northbridge_MBIU1; 

typedef struct {
  ULONG Routing;
  USHORT MBus_ID;
  UCHAR Instance;
} SCAN_RESULTS;

#define MAX_HITS (3*8)  // Num-GLIUs * Max-Ports-per-GLIU
SCAN_RESULTS PriorScans[MAX_HITS];
int j;


//***********************************************************************
// Scans an MBIU for the specified MBus_ID.
// Returns MBIU routing address (LSB = MBIU port)
//***********************************************************************
ULONG pascal Scan_MBIU(ULONG Mbiu, USHORT Port_ID, UCHAR * Instance, UCHAR Shift)
{ USHORT Port;
  ULONG RoutingMask;

  RoutingMask = 1L << Shift;

  LookupMbiu = Mbiu;

  // Adjustment for Southbridge MPCI
  if (Port_ID == ID_MPCI && Mbiu == Mbiu2) {
    Mbiu &= ~(RoutingMask - 1);
  }

  // Scan MBIU's ports for specified ID
  for (Port = 0; Port <= 7; Port++) {
  
    // If correct ID...
    if (GetPortID(Mbiu) == Port_ID) {
	
      // and correct instance...
      if (--(* Instance) == 0) {
	  
        // return the routing address and port # (in 3 LSBs)
        return (Mbiu | Port);
      }
    }

    // Get MSR address to next device
    Mbiu &= ~(RoutingMask - 1);
    Mbiu +=   RoutingMask;

  }

  return 0x00000000;
}

// Port = 1
// Mbiu = Southbridge = 0x51000000
// Port_ID = 0x47
// RoutingMask = 1 << 20 = 0x00100000




//***********************************************************************
// Finds an MBUS ID and returns the routing address.
// The port number is also returned in the 3 LSBs.
//***********************************************************************
ULONG pascal Find_MBus_ID(USHORT MBus_ID, UCHAR Instance)
{ ULONG DeviceAddr;
  int i, SavedInstance=Instance;

  // Check results of previous scans for a match
  for (i=0; i<j; i++) {
    if (PriorScans[i].MBus_ID == MBus_ID && PriorScans[i].Instance == Instance) {
	  return PriorScans[i].Routing;
    }
  }

  // Filter out MBIU1 self-reference
  if (MBus_ID == ID_MBIU) {
    if (Instance > 2) {
      Instance++;
    }
  }  

	// MBus_ID = 0x47
	// Instance = 1
  if (!(DeviceAddr   = Scan_MBIU(Mbiu0, MBus_ID, &Instance, 29))) {
    if (!(DeviceAddr = Scan_MBIU(Mbiu1, MBus_ID, &Instance, 26))) {
      if (!(DeviceAddr = Scan_MBIU(Mbiu2, MBus_ID, &Instance, 20)) && MBus_ID == ID_ATA) {
        DeviceAddr = Scan_MBIU(Mbiu2, ID_ATA100, &Instance, 20);
      }
    }
  } 

  // Clear h/w emulation events generated by scanning empty ports
  ClearMbiu(Mbiu0);
  ClearMbiu(Mbiu1);
  
  // SDG: if Mbiu2 is 0x51020000, then the folliwng call
  // sets bit 32 (EDX[0]) in 0x51022002.  Does this actually talk 
  // MSR 0x51000002?  If so... BUG!
   
  ClearMbiu(Mbiu2);

  // Record this scan for future reference
  if (DeviceAddr && j < MAX_HITS-1) {
    PriorScans[j].Instance = SavedInstance;
    PriorScans[j].MBus_ID  = MBus_ID;
    PriorScans[j].Routing  = DeviceAddr;
    j++;
  }

  return DeviceAddr;
}


//***********************************************************************
// Returns the MSR address of the MPCI associated with the current device
//***********************************************************************
ULONG pascal GetMSR(void)
{
  switch (Class) {
  
    case 0x0600:  // Bridge: Host
      return MPCI_NB;

    case 0x0601:  // Bridge: ISA
      return MPCI_SB;

    default:
      return 0x00000000;
  }
}


//***********************************************************************
// Gets the PCI Latency Timer
//***********************************************************************
UCHAR pascal Get_Latency(PCI_HEADER_ENTRY * Pci)
{ ULONG MsrAddr, MPCI_Ctrl[2];
  UCHAR LatencyTimer=Pci->LatencyTimer;

  // If bridge device, get MSR address of its MPCI
  if (MsrAddr = GetMSR()) {
    // Read Latency timer from MPCI_CTRL.LAT
    Read_MSR(MsrAddr + MPCI_CTRL, MPCI_Ctrl);
    LatencyTimer = (UCHAR)MPCI_Ctrl[1];
  }	
  return LatencyTimer;
}

//***********************************************************************
// Sets the PCI Latency timer
//***********************************************************************
void pascal Set_Latency(UCHAR Latency)
{ ULONG MsrAddr=0, MPCI_Ctrl[2];

  // If bridge device, get MSR address of its MPCI
  if (MsrAddr = GetMSR()) {

    // Set the PCI Latency timer
    Read_MSR(MsrAddr + MPCI_CTRL, MPCI_Ctrl);
    (UCHAR)MPCI_Ctrl[1] = Latency;

    // LDE is only defined in Northbridge MPCI
    if (MsrAddr == MPCI_NB) {
      if (Latency) {
        // Issues 118.176 & 118.197 require LDE to be set unless Latency Timer == 0
        (USHORT)MPCI_Ctrl[0] |=  LDE;
      } else {
        // Clear MPCI_CTRL[LDE] if Latency Timer = 0x00
        (USHORT)MPCI_Ctrl[0] &= ~LDE;
      }
    }
    Write_MSR(MsrAddr + MPCI_CTRL, MPCI_Ctrl);
  }
}


//***********************************************************************
// Enables/Disables a trap on a PCI Address
//***********************************************************************
void pascal Trap_PCI_IDSEL(USHORT PCI_Address, UCHAR EnableFlag)
{ ULONG Pbus, IDSEL_Mask;

  IDSEL_Mask = 1L << (UCHAR)(PCI_Address >> 11);
  Pbus = Read_MSR_LO(MPCI_NB + MPCI_PBUS);

  if (EnableFlag) {
    Pbus |=  IDSEL_Mask;
  } else {
    Pbus &= ~IDSEL_Mask;
  }

  Write_MSR_LO(MPCI_NB + MPCI_PBUS, Pbus);
}





//***********************************************************************
// Initializes MBus related structures and MSRs
// Input:
//   IDSEL_Mask = Mask of IDSELs to be virtualized
//***********************************************************************
void Init_MBus(void)
{ ULONG MsrAddr, MsrData[2];


  //*********************************************
  // Add RCONF0-RCONF7 to available descriptor list
  //*********************************************
  for (MsrAddr = MSR_RCONF0; MsrAddr <= MSR_RCONF7; MsrAddr++) {
    if (Init_Descr(GX2_RCONF, MsrAddr)) {
      break;
	}
  }


  //*********************************************
  // Initialize MBIU0
  //*********************************************
  Read_MSR(Mbiu0 + MBIU_CAP, &MsrData[0]);
  Parse_Capabilities(&MsrData[0], &Northbridge_MBIU0);
  Init_MBIU((UCHAR *)&Northbridge_MBIU0, Mbiu0);




  //*********************************************
  // Initialize MBIU1
  //*********************************************
  // MBIU1 is MBIU0's subtractive port
  Mbiu1 = MbiuInfo[0].SubtrPid;
  // Determine MBIU1 self-reference
  MBIU1_SelfReference = (UCHAR)Read_MSR_LO(Mbiu1 + MBIU_WHOAMI);

  Read_MSR(Mbiu1 + MBIU_CAP, &MsrData[0]);
  Parse_Capabilities(&MsrData[0], &Northbridge_MBIU1);
  Init_MBIU((UCHAR *)&Northbridge_MBIU1, Mbiu1);

  // Find address of MPCI_NB
  MPCI_NB = Find_MBus_ID(ID_MPCI, 1) & 0xFFFF0000;

  // Find address of MCP
  MCP_NB = Find_MBus_ID(ID_MCP, 1) & 0xFFFF0000;

  // Enable SMIs from the companion I/O
  MsrAddr = MCP_NB | MBD_MSR_SMI;
  Write_MSR_LO(MsrAddr, Read_MSR_LO(MsrAddr) & ~0x00000010L);

  PriorScans[j].Routing  = 0x00000000;
  PriorScans[j].MBus_ID  = ID_VAIL;
  PriorScans[j].Instance = 1;
  j++;

  // On LX, FooGlue MSRs == MCP_NB+0x20
  if (HardwareInfo.CPU_ID == DEVICE_ID_LX) {
    PriorScans[j].Routing  = MCP_NB+0x20;
    PriorScans[j].MBus_ID  = ID_FG;
    PriorScans[j].Instance = 1;
    j++;
  }

  // Find address of FooGlue
  FooGlue = Find_MBus_ID(ID_FG, 1) & 0xFFFFFFF0;

  // Find port of Video Generator
  VG_Port = (UCHAR)Find_MBus_ID(ID_VG, 1);


  // Find port of Memory Controller
  MC_Port = (UCHAR)Find_MBus_ID(ID_MC, 1);


  // Enable MPCI error masks
  MsrAddr = MPCI_NB | MBD_MSR_ERROR;
  Write_MSR_LO(MsrAddr, Read_MSR_LO(MsrAddr) & ~(MARM | TARM | BMM | SYSM | PARM));



  // Initialize logical timeout counters
  InitLogicalCounters();

}





//***********************************************************************
// Computes the MSR data corresponding to an enabled PCI BAR
//***********************************************************************
void Compute_Msr_Value(register DESCRIPTOR * Descr, register PCI_HEADER_ENTRY * Pci)
{ ULONG PBase, PMask, POffset=0;


  Descr->MsrData[0] = Pci->Value;

  if (Pci->Flag & IO_BAR) {
    if (Descr->MsrAddr == ACPI_Timer_MSR) {
      PCI_Value += 4;  // Skip over ACPI timer
    }
    Descr->Address = (USHORT)PCI_Value;
  }

  switch (Descr->Type) {

    case USB_LBAR:
      Descr->MsrData[1] |= MEM_SPACE;
      return;

    case MDD_LBAR:
      Descr->MsrData[1] |= Pci->Mask | LBAR_EN;

      // If Memory LBAR, set MEM_IO
      if (!(Pci->Flag & IO_BAR)) {
        Descr->MsrData[1] |= MEM_IO;
      } else {
        Descr->MsrData[1] &= 0x0000FFFF;
        Descr->MsrData[0] &= 0x0000FFFF;
      }
      return;

    case MPCI_RCONF:
      Descr->MsrData[1] = Pci->Value;

      // If I/O BAR, set SPACE bit & shift BASE & TOP
      if (Pci->Flag & IO_BAR) {
        Descr->MsrData[0] <<= 12;       // Move BASE to MSR[31:14]
        Descr->MsrData[1]  += ~(Pci->Mask | 3);
        Descr->MsrData[1] <<= 12;       // Move TOP to MSR[63:46]
        Descr->MsrData[1]  |= 1;        // Set SPACE bit
      }	else {
        Descr->MsrData[1] += ~Pci->Mask & 0xFFFFF000;
      }
      Descr->MsrData[0] |= 1;           // Enable region
      return;

    case EPCI:
      return;

    case GX2_RCONF:
      // Mark region non-cacheable and write-combined
      Descr->MsrData[0] |= REGION_CD | REGION_EN;
      Descr->MsrData[1] = Pci->Value + ~Pci->Mask & 0xFFFFF000;

      if (Pci->Flag & MMIO_BAR) {
        // Memory-mapped I/O must be write-serialized to avoid deadlocks
        // They are also marked write-burstable
        // Descr->MsrData[0] |= REGION_WS | REGION_WT;
      }
      if (Pci->Flag & MEM_BAR) {
        // Mark frame buffers write-combined
        Descr->MsrData[0] |= REGION_WC;
      }
      return;

    case IOD_SC:
      PMask = Descr->Range;
      Descr->MsrData[0] = Compute_IOD_SC(&PCI_Value, &(USHORT)PMask, 1);
      break;

    case IOD_BM:
      PMask = ~(Descr->Range-1) | 0xF0000;
      PBase = PCI_Value & PMask;
      PCI_Value += Descr->Range;
      break;

    case P2D_BMK:
    case P2D_BMO:
    case P2D_BM:
      PMask = Pci->Mask  >> 12;
      PBase = Pci->Value >> 12;
      break;

    case P2D_RO:
      // Fixup for VG alias bug
      if (Descr->Physical) {
        POffset = (~Pci->Value + 1) >> 12;
      }
    case P2D_R:
      PMask = Pci->Value >> 12;
      PBase = PMask + ((Descr->Range-1) >> 12);
      break;

    // These descriptor types are not used for PCI BARs
    case P2D_SCO:
    case P2D_SC:
    default:
      Log_Error("Compute_Msr_Value() was called with an invalid descriptor type: 0x%0x", Descr->Type);
      return;

  } // end switch()

  if (Descr->Type != IOD_SC) {
    if (Pci->Flag & MEM_BAR) {
      POffset = (Descr->Physical - Pci->Value) >> 12;
    }
    // Assemble fields into MSR value
    MergeFields(Descr->MsrData, PMask, PBase, POffset);
  }

  // Set PID field
  Descr->MsrData[1] |= (ULONG)Descr->Port << 29;

  // Set Bizarro bit, if necessary
  if (Pci->Flag & USE_BMK) {
    Descr->MsrData[1] |= BIZARRO;
  }
}



//***********************************************************************
// Updates all descriptors/LBARs/RCONF associated with a PCI BAR
//***********************************************************************
void pascal Update_BAR(register PCI_HEADER_ENTRY * Pci, UCHAR Enable)
{ register DESCRIPTOR * Descr;
  UCHAR Link;
  
  PCI_Value = Pci->Value;
  Link = Pci->Link;


  // For each linked item, update the associated MSR
  while (Link) {
							
    Descr = &MSRs[Link];

    // Get link to next MSR
    Link = Descr->Link;

    // Section 3.2.2 of PCI Spec 2.1:  A BAR of zero is not a valid address.
    // If the BAR value is zero, the corresponding MSR will be disabled.
    if (Enable && Pci->Value != 0) {

      // Don't update MSRs if querying resource requirements 
      if (Pci->Value == Pci->Mask) {
        continue;
      }

      // BAR is being enabled
      Compute_Msr_Value(Descr, Pci);

    } else {

      // Graphics device ignores disabling Command[1] (Issue 118.181)
      if (Class == 0x0300) {
        if (!(Pci->Flag & IO_BAR)) {
          continue;
        }
      }

      // BAR is being disabled
      Read_MSR(Descr->MsrAddr, Descr->MsrData);

      switch (Descr->Type) {

        case USB_LBAR:
          Descr->MsrData[1] &= ~2;
          break;

        case MDD_LBAR:
          Descr->MsrData[1] &= ~LBAR_EN;
          break;

        case EPCI:
          break;

        case GX2_RCONF:
          Descr->MsrData[0] &= ~REGION_EN;
          break;

        case MPCI_RCONF:
          Descr->MsrData[0] &= ~1;
          break;

        case IOD_SC:
          Descr->MsrData[0] &= ~(WEN | REN);
          break;

        default:
          Get_Descriptor_Default(Descr->Type, Descr->MsrData);
          break;
      }
    }

    // Write the MSR
    Write_MSR(Descr->MsrAddr, Descr->MsrData);

  }
}



//***********************************************************************
// Parses a MBD_MSR_ERROR & returns the Status register equivalent.
// NOTE:
//   PERR# is not implemented, so MASTER_PARITY_ERROR always reads 0.
//***********************************************************************
ULONG pascal Get_Device_Status(PCI_HEADER_ENTRY * Pci)
{ ULONG MsrAddr, MsrData, Status=0;

  // Read MBD_MSR_ERROR
  if (MsrAddr = GetMSR()) {
    (USHORT)MsrAddr = MBD_MSR_ERROR;

    MsrData = Read_MSR_LO(MsrAddr);

    if (MsrData & (BME | TASE))
      Status |= SIGNALED_TARGET_ABORT;      // Status[11]

    if (MsrData & TARE)
      Status |= RECEIVED_TARGET_ABORT;      // Status[12]

    if (MsrData & MARE)
      Status |= RECEIVED_MASTER_ABORT;      // Status[13]

    if (MsrData & SYSE)
      Status |= SIGNALED_SYSTEM_ERROR;      // Status[14]

    if (MsrData & PARE)
      Status |= DETECTED_PARITY_ERROR;      // Status[15]
  }
  return Status;
}



//***********************************************************************
// Clears error(s) pending on a device according to the Status mask
//***********************************************************************
void  pascal Clear_MBus_Error(PCI_HEADER_ENTRY * Pci, ULONG Status)
{ ULONG MsrAddr, MsrData;

  // Read MBD_MSR_ERROR
  if (MsrAddr = GetMSR()) {

    (USHORT)MsrAddr = MBD_MSR_ERROR;

    MsrData = Read_MSR_LO(MsrAddr);


    // Status[15:11] write-1-to-clear.
    // Only clear the MSR bits corresponding to Status[15:11]
    MsrData &= ~(TASE | BME | TARE | MARE | SYSE | PARE);

    if (Status & SIGNALED_TARGET_ABORT)     // Status[11]
      MsrData |= TASE | BME;

    if (Status & RECEIVED_TARGET_ABORT)     // Status[12]
      MsrData |= TARE;

    if (Status & RECEIVED_MASTER_ABORT)     // Status[13]
      MsrData |= MARE;

    if (Status & SIGNALED_SYSTEM_ERROR)     // Status[14]
      MsrData |= SYSE;

    if (Status & DETECTED_PARITY_ERROR)     // Status[15]
      MsrData |= PARE;

    Write_MSR_LO(MsrAddr, MsrData);
  }
}





//***********************************************************************
// Enables/disables bus mastering on a GLIU device
// Returns non-zero if no more bus-master MSRs for this device
//***********************************************************************
UCHAR pascal Update_BusMaster(PCI_HEADER_ENTRY * Pci, UCHAR EnableFlag)
{ register DESCRIPTOR * Descr;
  ULONG MsrAddr, MsrData[2];
  USHORT Mask;
  UCHAR Link, Shift;

  switch (Class) {

    // Filter out devices that don't affect PAE when Command[2] is changed
    case 0x0600:  // Bridge: Host
    case 0x0601:  // Bridge: ISA
    case 0x0300:  // Graphics
    case 0x1010:  // AES
      return 1;

    default:

      Link = Pci->Link;

      // For each linked item, update the associated MSR
      while (Link) {

        Descr = &MSRs[Link];

        // Get link to next MSR
        Link = Descr->Link;


        MsrAddr = Descr->MsrAddr;
        // Each USB 2.0 device on port 2 has its own bus-master control
        if (Descr->Type == USB_LBAR) {
          // Modify bus-master control
          Read_MSR(MsrAddr, MsrData);
          (UCHAR)MsrData[1] &= ~BUS_MASTER;
          if (EnableFlag) {
            (UCHAR)MsrData[1] |= BUS_MASTER;
          }
          Write_MSR(MsrAddr, MsrData);
          Descr->MsrData[1] = MsrData[1];
          return 1;
        }
        // Only change PAE in MBIU0 & MBIU2
        MsrAddr &= ROUTING;
        if (MsrAddr == Mbiu0 || MsrAddr == Mbiu2) {
          (USHORT)MsrAddr = MBIU_PAE;

          // Generate shift count from port #
          Shift = 14;   // Port 0 is in 8th position (bits 15:14)
          if (Descr->Port) {
            Shift = (Descr->Port-1) * 2;
          } 

          // Generate mask for PAE MSR.  2 bits/field.
          Mask = 3 << Shift;

          // Modify bus-master control
          Read_MSR(MsrAddr, MsrData);
          (USHORT)MsrData[0] &= ~Mask;
          if (EnableFlag) {
            (USHORT)MsrData[0] |= Mask;
          }
          Write_MSR(MsrAddr, MsrData);
        }
      }
      break;
  }
  return 0;
}




//***********************************************************************
// Supports macro SYS_MBUS_DESCRIPTOR.
// Returns the MSR address associated with a virtualized PCI resource.
//***********************************************************************
ULONG pascal Lookup_PCI(USHORT Address)
{ register PCI_HEADER_ENTRY * Pci;
  register DESCRIPTOR * Descr;
  UCHAR LO_Address, Index=0;

  LO_Address = (UCHAR)Address;

  // Limit search to PCI BARs and OEM registers
  if (((LO_Address >= BAR0) && (LO_Address <= BAR5)) || (LO_Address >= 0x40)) {
  
    Pci = Get_Structure(Address);

    if ((USHORT)Pci > UNIMPLEMENTED_REGISTER) {
      if (Index = Pci->Link) {
        // For OHCI devices, return embedded PCI address
        if (Pci->Flag & EPCI_RW ) {
          do {
            Descr = &MSRs[Index];
            if (Descr->Type == EPCI) {
              break;
            }
          } while (Index = Descr->Link);
        }
      }
    }
  }

  Descr = &MSRs[Index];
  return Descr->MsrAddr;
}

//***********************************************************************
// Supports macro SYS_IO_DESCRIPTOR.
// Returns the MSR address associated with an I/O address.
//***********************************************************************
ULONG pascal Lookup_IO(USHORT Address)
{ USHORT Range;
  ULONG Addr;
  UCHAR Index=0;

  Range = 1;
  Addr = (ULONG)Address;
  Index = Find_Matching_IO_Descriptor(&Addr, &Range, 3);
  return MSRs[Index].MsrAddr;
}
