/*
 * 
 * (C) Copyright 2000
 * Wolfgang Denk, DENX Software Engineering, wd@denx.de.
 * 
 * (C) Copyright 2001
 * James Dougherty (jfd@cs.stanford.edu)
 *
 * See file CREDITS for list of people who contributed to this
 * project.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 * MA 02111-1307 USA
 */

/*
 * PCI Configuration space access support for MPC824x/MPC107 PCI Bridge
 */
#include <ppcboot.h>
#include "mpc824x.h"
#include "pci.h"

int  numPciBus = 1; /* One bus: MPC107 Internal bridge */

/*
 *
 * pci_dev_find - find the nth device with the given device & vendor ID
 *
 */
int pci_dev_find(int   vendorId,      /* vendor ID */
		 int   deviceId,      /* device ID */
		 int   index,	      /* desired instance of device */
		 int*  pBusNo,	      /* bus number */
		 int*  pDeviceNo,     /* device number */
		 int*  pFuncNo)	      /* function number */
{
  int status = ERROR;
  int   cont   = TRUE;
  int    busNo;
  int    deviceNo;
  int    funcNo;
  uint32 device;
  uint32 vendor;
  char   header;
  
  for (busNo = 0; cont == TRUE && busNo <= numPciBus; busNo++)
    for (deviceNo = 0;
	 ((cont == TRUE) && (deviceNo < PCI_MAX_DEV));
	 ++deviceNo)
      for (funcNo = 0; cont == TRUE && funcNo < PCI_MAX_FUNC; funcNo++){
	/* avoid a special bus cycle */
	
	if ((deviceNo == 0x1f) && (funcNo == 0x07))
	  continue;
	
	pci_config_inl (busNo, deviceNo, funcNo, PCI_CFG_VENDOR_ID,
			&vendor);
	
	/*
	 * If nonexistent device, skip to next, only look at
	 * vendor ID field for existence check
	 */
	if (((vendor & 0x0000ffff) == 0x0000FFFF) && (funcNo == 0))
	  break;
	device  = vendor >> 16;
	device &= 0x0000FFFF;
	vendor &= 0x0000FFFF;
	if ((vendor == (uint32)vendorId) &&
	    (device == (uint32)deviceId) &&
	    (index-- == 0)){
	  *pBusNo	= busNo;
	  *pDeviceNo	= deviceNo;
	  *pFuncNo	= funcNo;
	  status	= OK;
	  cont	= FALSE;	/* terminate all loops */
	  continue;
	}
	
	/* goto next if current device is single function */
	
	pci_config_inb (busNo, deviceNo, funcNo, PCI_CFG_HEADER_TYPE, 
			&header);
	if ((header & PCI_HEADER_MULTI_FUNC) !=
	    PCI_HEADER_MULTI_FUNC && funcNo == 0)
	  break;
      }
  
  return status;
}

/*
 *
 * pci_class_find - find the nth occurence of a device by PCI class code.
 *
 * RETURNS:
 * OK, or ERROR if the class didn't match.
 */

int pci_class_find(int    classCode,	/* 24-bit class code */
		   int	   index,	/* desired instance of device */
		   int *  pBusNo,	/* bus number */
		   int *  pDeviceNo,	/* device number */
		   int *  pFuncNo)	/* function number */
{
  int status = ERROR;
  int   cont   = TRUE;
  int    busNo;
  int    deviceNo;
  int    funcNo;
  int    classCodeReg;
  int    vendor;
  char   header;
  
  
  for (busNo = 0; cont == TRUE && busNo <= numPciBus; busNo++)
    for (deviceNo = 0;
	 ((cont == TRUE) && (deviceNo < PCI_MAX_DEV));
	 ++deviceNo)
      for (funcNo = 0; cont == TRUE && funcNo < PCI_MAX_FUNC; funcNo++){
	
	/* avoid a special bus cycle */
	if ((deviceNo == 0x1f) && (funcNo == 0x07))
	  continue;
	
	pci_config_inl (busNo, deviceNo, funcNo, PCI_CFG_VENDOR_ID,
			&vendor);
	
	/*
	 * If nonexistent device, skip to next, only look at
	 * vendor ID field for existence check
	 */
	if (((vendor & 0x0000ffff) == 0x0000FFFF) && (funcNo == 0))
	  break;
	
	pci_config_inl (busNo, deviceNo, funcNo, PCI_CFG_REVISION,
			&classCodeReg);
	
	if ((((classCodeReg >> 8) & 0x00ffffff) == classCode) &&
	    (index-- == 0))
	  {
	    *pBusNo	= busNo;
	    *pDeviceNo	= deviceNo;
	    *pFuncNo	= funcNo;
	    status	= OK;
	    cont 	= FALSE;	/* terminate all loops */
	    continue;
	  }
	
	/* goto next if current device is single function */
	
	pci_config_inb (busNo, deviceNo, funcNo, PCI_CFG_HEADER_TYPE, 
			&header);
	if ((header & PCI_HEADER_MULTI_FUNC) !=
	    PCI_HEADER_MULTI_FUNC && funcNo == 0)
	  break;
      }
  return status;
}


/*
 *
 * pci_dev_config - configure a device on a PCI bus
 *
 * This routine configures a device that is on a Peripheral Component
 * Interconnect (PCI) bus by writing to the configuration header of the
 * selected device.
 *
 * It first disables the device by clearing the command register in the
 * configuration header.  It then sets the I/O and/or memory space base
 * address registers, the latency timer value and the cache line size.
 * Finally, it re-enables the device by loading the command register with
 * the specified command.
 *
 * NOTE: This routine is designed for Type 0 PCI Configuration Headers ONLY.
 * It is NOT usable for configuring, for example, a PCI-to-PCI bridge.
 *
 * RETURNS: OK always.
 */
int pci_dev_config(int pciBusNo,          /* PCI bus number */
		   int pciDevNo,          /* PCI device number */
		   int pciFuncNo,         /* PCI function number */
		   uint32 devIoBaseAdrs,  /* device IO base address */
		   uint32 devMemBaseAdrs, /* device memory base address */
		   uint32 command)         /* command to issue */
{
  int    ix;
  uint32 tmp32;
  
  /*
   * Disable device by clearing its command register field in its
   * configuration header. Write 0 clears command and preserves status.
   */
  pci_config_outl (pciBusNo, pciDevNo, pciFuncNo, PCI_CFG_COMMAND, 0);
  
  for (ix = PCI_CFG_BASE_ADDRESS_0; ix <= PCI_CFG_BASE_ADDRESS_5; ix+=4){

    /* Write all f's and read back value */
    pci_config_outl (pciBusNo, pciDevNo, pciFuncNo, ix, 0xffffffff);
    pci_config_inl  (pciBusNo, pciDevNo, pciFuncNo, ix, &tmp32);
    
    if (tmp32 == 0){
      /* No bar */
      break;
    }
    
    /* I/O space requested */
    if (tmp32 & 0x1){
      pci_config_outl (pciBusNo, pciDevNo, pciFuncNo, ix,
		       devIoBaseAdrs | 0x1);
    } else{
      /* Memory space required, set specified base address */
      pci_config_outl (pciBusNo, pciDevNo, pciFuncNo, ix,
		       devMemBaseAdrs & ~0x1);
    }
  }
  
  /* Configure Cache Line Size Register */
  pci_config_outb (pciBusNo, pciDevNo, pciFuncNo,
		   PCI_CFG_CACHE_LINE_SIZE,
		   PCI_CACHE_LINE_SIZE);
  
  /* Configure Latency Timer */
  pci_config_outb (pciBusNo, pciDevNo, pciFuncNo,
		   PCI_CFG_LATENCY_TIMER,
		   PCI_LATENCY_TIMER);
  
  /*
   * Enable the device's capabilities as specified, do not
   * reset any status bits in doing so.
   */  
  pci_config_modl (pciBusNo, pciDevNo, pciFuncNo,
		   PCI_CFG_COMMAND,
		   (PCI_CMD_MASK | command), command);
  
  return OK;
}


/*
 *
 * pci_config_bdf_pack - pack parameters for the Configuration Address Register
 *
 * This routine packs three parameters into one integer for accessing the
 * Configuration Address Register
 *
 */

int pci_config_bdf_pack(int	busNo,    /* bus number */
			int	deviceNo, /* device number */
			int	funcNo)    /* function number */
{
  return (((busNo    << 16) & 0x00ff0000) |
	  ((deviceNo << 11) & 0x0000f800) |
	  ((funcNo   << 8)  & 0x00000700));
}	

/*
 * pci_config_inb - read one byte from the PCI configuration space
 *
 * This routine reads one byte from the PCI configuration space
 *
 */
int pci_config_inb(int busNo,    /* bus number */
		   int deviceNo, /* device number */
		   int funcNo,	  /* function number */
		   int offset,	  /* offset into config space */
		   uint8 * pData) /* data read from the offset */
{
  uint8 retval = 0;
  int retStat = ERROR;
  
  mpc824x_mpc107_write32 (CHRP_REG_ADDR, 
			  pci_config_bdf_pack (busNo, deviceNo, funcNo) |
			  (offset & 0xfc) | 0x80000000);
  retval = mpc824x_mpc107_read8 (CHRP_REG_DATA + (offset & 0x3));
  retStat = OK;
  
  *pData = retval;
  
  return retStat;
}

/*
 *
 * pci_config_inw - read one word from the PCI configuration space
 *
 * This routine reads one word from the PCI configuration space
 *
 */
int pci_config_inw(int busNo,      /* bus number */
		   int deviceNo,   /* device number */
		   int funcNo,     /* function number */
		   int offset,     /* offset into config space */
		   uint16 * pData)  /* data read from the offset */
{
  int retStat = ERROR;
  uint16 retval = 0;
  
  if (((offset & (int)0x1) > 0) ) {
    return (retStat);
  }
  
  mpc824x_mpc107_write32 (CHRP_REG_ADDR, 
			  pci_config_bdf_pack (busNo, deviceNo, funcNo) |
			  (offset & 0xfc) | 0x80000000);
  retval = mpc824x_mpc107_read16 (CHRP_REG_DATA + (offset & 0x2));
  retStat = OK;
  *pData = retval;
  
  return retStat;
}

/*
 *
 * pci_config_inl - read one longword from the PCI configuration space
 *
 * This routine reads one longword from the PCI configuration space
 *
 */
int pci_config_inl(int	busNo,     /* bus number */
		   int	deviceNo,  /* device number */
		   int	funcNo,    /* function number */
		   int	offset,    /* offset into config space */
		   uint32 * pData) /* data read from the offset */
{
  int retStat = ERROR;
  uint32 retval = 0;
  
  if (((offset & (int)0x3) > 0) ) {
    return (retStat);
  }
  mpc824x_mpc107_write32 (CHRP_REG_ADDR, 
			  pci_config_bdf_pack (busNo, deviceNo, funcNo) |
			  (offset & 0xfc) | 0x80000000);
  retval = mpc824x_mpc107_read32 (CHRP_REG_DATA);
  asm volatile ("eieio");
  retStat = OK;
  *pData = retval;
  return retStat;
}

/*
 * pci_config_outb - write one byte to the PCI configuration space
 *
 * This routine writes one byte to the PCI configuration space.
 *
 */
int pci_config_outb(int busNo,    /* bus number */
		    int deviceNo, /* device number */
		    int funcNo,   /* function number */
		    int offset,   /* offset into config space */
		    uint8 data)    /* data written to the offset */
{
  
  mpc824x_mpc107_write32 (CHRP_REG_ADDR, 
			  pci_config_bdf_pack (busNo, deviceNo, funcNo) |
			  (offset & 0xfc) | 0x80000000);
  mpc824x_mpc107_write8 ((CHRP_REG_DATA + (offset & 0x3)), data);
  return OK;
}

/*
 * pci_config_outw - write one 16-bit word to the PCI configuration space
 *
 * This routine writes one 16-bit word to the PCI configuration space.
 *
 * RETURNS: OK, or ERROR if this library is not initialized
 */
int pci_config_outw(int busNo,    /* bus number */
		    int deviceNo, /* device number */
		    int funcNo,   /* function number */
		    int offset,   /* offset into config space */
		    uint16 data)   /* data written to the offset */
{
  if (((offset & (int)0x1) > 0) ){
    return (ERROR);
  }

  mpc824x_mpc107_write32 (CHRP_REG_ADDR,
			  pci_config_bdf_pack (busNo, deviceNo, funcNo) |
			  (offset & 0xfc) | 0x80000000);
  mpc824x_mpc107_write16 ((CHRP_REG_DATA + (offset & 0x2)), data);
  return OK;
}


/*
 *
 * pci_config_outl - write one longword to the PCI configuration space
 *
 * This routine writes one longword to the PCI configuration space.
 *
 */
int pci_config_outl(int busNo,    /* bus number */
		    int deviceNo, /* device number */
		    int funcNo,   /* function number */
		    int offset,   /* offset into config space */
		    uint32 data)   /* data written to the offset */
{
  if (((offset & (int)0x3) > 0) ){
    return (ERROR);
  }
  mpc824x_mpc107_write32 (CHRP_REG_ADDR,
			  pci_config_bdf_pack (busNo, deviceNo, funcNo) |
			  (offset & 0xfc) | 0x80000000);
  mpc824x_mpc107_write32 (CHRP_REG_DATA, data);
  asm volatile ("eieio");
  return OK;
}


/*
 *
 * pci_config_modl - Perform a masked longword register update
 *
 * This function writes a field into a PCI configuration header without
 * altering any bits not present in the field.  It does this by first
 * doing a PCI configuration read (into a temporary location) of the PCI
 * configuration header word which contains the field to be altered.
 * It then alters the bits in the temporary location to match the desired
 * value of the field.  It then writes back the temporary location with
 * a configuration write.  All configuration accesses are long and the
 * field to alter is specified by the "1" bits in the 'bitMask' parameter.
 *
 */

int pci_config_modl(int busNo,        /* bus number */
		    int deviceNo,     /* device number */
		    int funcNo,       /* function number */
		    int offset,       /* offset into config space */
		    uint32 bitMask,   /* Mask of bits to alter */
		    uint32 data)      /* data written to the offset */
  
{
  uint32 temp;
  int stat;
  
  if (((offset & (int)0x3) > 0) ){
    return (ERROR);
  }

  stat = pci_config_inl (busNo, deviceNo, funcNo, offset, &temp);	
  if (stat == OK){
    temp = (temp & ~bitMask) | (data & bitMask);
    stat = pci_config_outl (busNo, deviceNo, funcNo, offset, temp);
  }
  return stat;
}


/*
 *
 * pci_config_modw - Perform a masked short register update
 *
 */
int pci_config_modw(int busNo,          /* bus number */
		    int deviceNo,       /* device number */
		    int funcNo,         /* function number */
		    int offset,         /* offset into config space */
		    uint16 bitMask,     /* Mask of bits to alter */
		    uint16 data)        /* data written to the offset */
{
  uint16 temp;
  int stat;    
  if (((offset & (int)0x1) > 0) ){
    return (ERROR);
  }
  stat = pci_config_inw (busNo, deviceNo, funcNo, offset, &temp);
  if (stat == OK){
    temp = (temp & ~bitMask) | (data & bitMask);
    stat = pci_config_outw (busNo, deviceNo, funcNo, offset, temp);
  }
  return stat;
}



/*
 * pci_config_modb - Perform a masked byte register update
 *
 */
int pci_config_modb(int busNo,          /* bus number */
		    int deviceNo,       /* device number */
		    int funcNo,         /* function number */
		    int offset,         /* offset into config space */
		    uint8 bitMask,      /* Mask of bits to alter */
		    uint8 data)         /* data written to the offset */
{
  uint8 temp;
  int stat;
  stat = pci_config_inb (busNo, deviceNo, funcNo, offset, &temp);
  if (stat == OK){
    temp = (temp & ~bitMask) | (data & bitMask);
    stat = pci_config_outb (busNo, deviceNo, funcNo, offset, temp);
  }
  return stat;
}

/*
 * pci_special_cycle - generate a special cycle with a message
 */
int
pci_special_cycle(int busNo,       /* bus number */
		  uint32 message)  /* data driven onto AD[31:0] */
{
  int deviceNo	= 0x0000001f;	
  int funcNo	= 0x00000007;
  
  mpc824x_mpc107_write32 (CHRP_REG_ADDR, 
			  pci_config_bdf_pack (busNo, deviceNo, funcNo) |
			  0x80000000);
  mpc824x_mpc107_write32 (CHRP_REG_DATA, message);
  return OK;
}


/*
 * Find the extent of a PCI decode..
 */
unsigned int
pci_size_bar(unsigned int base, unsigned long mask)
{
  uint32 size = mask & base;  /* Find the significant bits */
  size = size & ~(size-1); /* Get the lowest of them to find the decode size */
  return size-1;	   /* extent = size - 1 */
}

/*
 * Show/Init PCI devices on the specified bus number.
 */
int pci_dev_init(int busNo)
{
  int deviceNo;
  int devices;
  uint16 vendorId;
  uint16 deviceId;
  uint32 mbar0;
  uint32 mbar1;
  uint32 revId;
  uint32 iline,ipin;

  union {
    int classCode;
    char array[4];
  } u;
    
  printf("PCI:    scanning bus%d ...\n", busNo);
  printf("  bus dev fn venID devID class"
	 "  rev MBAR0    MBAR1    IPIN ILINE\n");
  
  devices = 0x1f;
  for (deviceNo=0; deviceNo < devices; deviceNo++) {

    /* Get device and vendor ID */
    pci_config_inw (busNo, deviceNo, 0, PCI_CFG_VENDOR_ID, &vendorId);
    pci_config_inw (busNo, deviceNo, 0, PCI_CFG_DEVICE_ID, &deviceId);
    
    /* MPC107 Bridge */
    if( deviceNo == 0x0){
      /* Don't do anything */
    }
    
    /* Make sure IRQ's on PCI devices get configured correctly */
    if( deviceNo == 0x0f){
      /* Onboard */
      pci_dev_config(busNo, deviceNo, 0,
		     PCI_ENET0_IOADDR,
		     PCI_ENET0_MEMADDR,
		     PCI_CMD_MEM_ENABLE |			  
		     PCI_CMD_MASTER_ENABLE);
    }  /* PCI Slot */
    else if (deviceNo == 0x10) {
      /* Onboard */
      pci_dev_config(busNo, deviceNo, 0,
		     PCI_ENET1_IOADDR,
		     PCI_ENET1_MEMADDR,
		     PCI_CMD_MEM_ENABLE |			  
		     PCI_CMD_MASTER_ENABLE);
    }

    pci_config_inb (busNo, deviceNo, 0, PCI_CFG_PROGRAMMING_IF, 
		    &u.array[3]);
    pci_config_inb (busNo, deviceNo, 0, PCI_CFG_SUBCLASS, &u.array[2]);
    pci_config_inb (busNo, deviceNo, 0, PCI_CFG_CLASS, &u.array[1]);
    u.array[0] = 0;
    
    pci_config_inl(busNo, deviceNo, 0, PCI_CFG_BASE_ADDRESS_0, &mbar0);
    pci_config_inl(busNo, deviceNo, 0, PCI_CFG_BASE_ADDRESS_1, &mbar1);
    
    pci_config_inl(busNo, deviceNo, 0, PCI_CFG_REVISION, &revId);
    revId &= 0x000000ff;
    
    pci_config_inl(busNo, deviceNo, 0, PCI_CFG_DEV_INT_LINE, &iline);
    iline &= 0x000000ff;
    
    pci_config_inl(busNo, deviceNo, 0, PCI_CFG_DEV_INT_LINE, &ipin);
    ipin  &= 0x0000ff00;
    ipin >>= 8;


    /* There are two ways to find out an empty device.
     *   1. check Master Abort bit after the access.
     *   2. check whether the read value is 0xffff.
     * Since I didn't see the Master Abort bit of the host/PCI bridge
     * changing, I use the second method.
     */
    if (vendorId != 0xffff)
      
      printf("  %02x  %02x  %02x %04x  %04x  "
	     "%06x %02x  %08x %08x %02x   %02x\n",
	     busNo, deviceNo, 0,
	     vendorId, deviceId, u.classCode, revId,
	     mbar0, mbar1, ipin, iline);
  }
  return OK;
}


