#include "ati_pciids_gen.h"
#include "radeon_chipset_gen.h"
#include "radeon_chipinfo_gen.h"



const char *
xf86TokenToString(SymTabPtr table, int token)
{
    int i;

    for (i = 0; table[i].token >= 0 && table[i].token != token; i++){};

    if (table[i].token < 0)
      return NULL;
    else
      return(table[i].name);
}



const RADEONCardInfo *RadeonDevMatch(u16_t dev,const RADEONCardInfo *list)
{
  while(list->pci_device_id)
  {
    if(dev == list->pci_device_id)
      return list;
    list++;
  }
  return 0;
}


RHDPtr FindPciDevice()
{
    const RADEONCardInfo *dev;
    u32_t bus, last_bus;

    if( (last_bus = PciApi(1))==-1)
        return 0;

    for(bus=0;bus<=last_bus;bus++)
    {
        u32_t devfn;

        for(devfn=0;devfn<256;devfn++)
        {
            u32_t id;
            id = PciRead32(bus,devfn, 0);

            if( (u16_t)id != VENDOR_ATI)
                continue;

            rhd.PciDeviceID = (id>>16);

            if( (dev = RadeonDevMatch(rhd.PciDeviceID, RADEONCards))!=NULL)
            {
                u32_t reg2C;
                int i;

                rhd.chipset = (char*)xf86TokenToString(RADEONChipsets, rhd.PciDeviceID);
                if (!rhd.chipset){
                    dbgprintf("ChipID 0x%04x is not recognized\n", rhd.PciDeviceID);
                    return FALSE;
                }
                dbgprintf("Chipset: \"%s\" (ChipID = 0x%04x)\n",
                           rhd.chipset,rhd.PciDeviceID);

                rhd.bus = bus;
                rhd.devfn = devfn;
                rhd.PciTag = pciTag(bus,(devfn>>3)&0x1F,devfn&0x7);

                rhd.ChipFamily  = dev->chip_family;
                rhd.IsMobility  = dev->mobility;
                rhd.IsIGP       = dev->igp;
                rhd.HasCRTC2    = !dev->nocrtc2;

                reg2C = PciRead32(bus,devfn, 0x2C);

                rhd.subvendor_id = reg2C & 0xFFFF;;
                rhd.subdevice_id = reg2C >> 16;

                if (rhd.ChipFamily >= CHIP_FAMILY_R600)
                    dbgprintf("R600 unsupported yet.\nExit\n");

                if( rhd.ChipFamily >= CHIP_FAMILY_R420)
                    rhd.gart_type = RADEON_IS_PCIE;
                else
                    rhd.gart_type = RADEON_IS_PCI;

                for (i = 0; i < 6; i++)
                {
                    u32_t base;
                    Bool validSize;

                    base = PciRead32(bus,devfn, PCI_MAP_REG_START + (i << 2));
                    if(base)
                    {
                        if (base & PCI_MAP_IO){
                            rhd.ioBase[i] = (u32_t)PCIGETIO(base);
                            rhd.memtype[i]   = base & PCI_MAP_IO_ATTR_MASK;
                        }
                        else{
                            rhd.memBase[i] = (u32_t)PCIGETMEMORY(base);
                            rhd.memtype[i] = base & PCI_MAP_MEMORY_ATTR_MASK;
                        }
                    }
                    rhd.memsize[i] = pciGetBaseSize(bus,devfn, i, TRUE, &validSize);
                }
                return &rhd;
            }
        }
    };
    return NULL;
}



u32_t pciGetBaseSize(int bus, int devfn, int index, Bool destructive, Bool *min)
{
  int offset;
  u32_t addr1;
  u32_t addr2;
  u32_t mask1;
  u32_t mask2;
  int bits = 0;

  /*
   * silently ignore bogus index values.  Valid values are 0-6.  0-5 are
   * the 6 base address registers, and 6 is the ROM base address register.
   */
  if (index < 0 || index > 6)
    return 0;

  if (min)
    *min = destructive;

  /* Get the PCI offset */
  if (index == 6)
    offset = PCI_MAP_ROM_REG;
  else
    offset = PCI_MAP_REG_START + (index << 2);

  addr1 = PciRead32(bus, devfn, offset);
  /*
   * Check if this is the second part of a 64 bit address.
   * XXX need to check how endianness affects 64 bit addresses.
   */
  if (index > 0 && index < 6) {
    addr2 = PciRead32(bus, devfn, offset - 4);
    if (PCI_MAP_IS_MEM(addr2) && PCI_MAP_IS64BITMEM(addr2))
      return 0;
  }

  if (destructive) {
     PciWrite32(bus, devfn, offset, 0xffffffff);
     mask1 = PciRead32(bus, devfn, offset);
     PciWrite32(bus, devfn, offset, addr1);
  } else {
    mask1 = addr1;
  }

  /* Check if this is the first part of a 64 bit address. */
  if (index < 5 && PCI_MAP_IS_MEM(mask1) && PCI_MAP_IS64BITMEM(mask1))
  {
    if (PCIGETMEMORY(mask1) == 0)
    {
      addr2 = PciRead32(bus, devfn, offset + 4);
      if (destructive)
      {
        PciWrite32(bus, devfn, offset + 4, 0xffffffff);
        mask2 = PciRead32(bus, devfn, offset + 4);
        PciWrite32(bus, devfn, offset + 4, addr2);
      }
      else
     {
       mask2 = addr2;
     }
     if (mask2 == 0)
       return 0;
     bits = 32;
     while ((mask2 & 1) == 0)
     {
       bits++;
       mask2 >>= 1;
     }
     if (bits > 32)
	  return bits;
    }
  }
  if (index < 6)
    if (PCI_MAP_IS_MEM(mask1))
      mask1 = PCIGETMEMORY(mask1);
    else
      mask1 = PCIGETIO(mask1);
  else
    mask1 = PCIGETROM(mask1);
  if (mask1 == 0)
    return 0;
  bits = 0;
  while ((mask1 & 1) == 0) {
    bits++;
    mask1 >>= 1;
  }
  /* I/O maps can be no larger than 8 bits */

  if ((index < 6) && PCI_MAP_IS_IO(addr1) && bits > 8)
    bits = 8;
  /* ROM maps can be no larger than 24 bits */
  if (index == 6 && bits > 24)
    bits = 24;
  return bits;
}



#define PCI_FIND_CAP_TTL    48

static int __pci_find_next_cap_ttl(PCITAG pciTag, u8_t pos,
                                   int cap, int *ttl)
{
    u8_t id;

    while ((*ttl)--)
    {
        pos = pciReadByte(pciTag, pos);
		if (pos < 0x40)
			break;
		pos &= ~3;
        id = pciReadByte(pciTag, pos + PCI_CAP_LIST_ID);
		if (id == 0xff)
			break;
		if (id == cap)
			return pos;
		pos += PCI_CAP_LIST_NEXT;
	}
	return 0;
}

static int __pci_find_next_cap(PCITAG pciTag, u8_t pos, int cap)
{
	int ttl = PCI_FIND_CAP_TTL;

    return __pci_find_next_cap_ttl(pciTag, pos, cap, &ttl);
}

static int __pci_bus_find_cap_start(PCITAG pciTag)
{
    u16_t status;
    u8_t  hdr_type;

    status = pciReadWord(pciTag, PCI_STATUS);
	if (!(status & PCI_STATUS_CAP_LIST))
		return 0;

    hdr_type = pciReadByte(pciTag, 0x0E);
    switch (hdr_type)
    {
        case PCI_HEADER_TYPE_NORMAL:
        case PCI_HEADER_TYPE_BRIDGE:
            return PCI_CAPABILITY_LIST;
        case PCI_HEADER_TYPE_CARDBUS:
            return PCI_CB_CAPABILITY_LIST;
        default:
            return 0;
	}
	return 0;
}


int pci_find_capability(PCITAG pciTag, int cap)
{
	int pos;

    pos = __pci_bus_find_cap_start(pciTag);
	if (pos)
        pos = __pci_find_next_cap(pciTag, pos, cap);

	return pos;
}


static __inline__ int drm_device_is_pcie(PCITAG pciTag)
{
    return pci_find_capability(pciTag, PCI_CAP_ID_EXP);
}