#ident "kit/GAL9B/sbdpci.c: Copyright (c) MIPS Technologies, Inc. All rights reserved."

/*
 * Copyright (c) 1997-2007 MIPS Technologies, Inc.
 * 
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 
 *      * Redistributions of source code must retain the above copyright
 *        notice, this list of conditions and the following disclaimer.
 *      * Redistributions in binary form must reproduce the above
 *      copyright
 *        notice, this list of conditions and the following disclaimer
 *        in the documentation and/or other materials provided with
 *        the distribution.
 *      * Neither the name of MIPS Technologies Inc. nor the names of its
 *        contributors may be used to endorse or promote products derived
 *        from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*
 * GAL9/pci_machdep.c: Machine-specific functions for PCI autoconfiguration.
 */


#if #pci(yes)

#include <sys/types.h>
#include <stdlib.h>
#include <mips/xcpt.h>
#include <mips/cpu.h>

#include <pci/pcivar.h>
#include <pci/pcireg.h>

#include "kit_impl.h"
#include "sbd.h"
#include "gt64011.h"

#ifdef DBGPCI
#define DBG(fmt, args...) \
	_mon_printf (fmt , ## args);
#else
#define DBG(fmt, args...)
#endif

/* reserve bottom 64KB of i/o space for ISA decodes */
static pcireg_t minpciioaddr  = PCI_IO_BASE + 0x10000;
static pcireg_t nextpciioaddr = PCI_IO_BASE + PCI_IO_SIZE;

/* reserve bottom 1MB of mem space for ISA decodes */
static pcireg_t minpcimemaddr =  PCI_MEM_BASE + 0x100000;
static pcireg_t nextpcimemaddr = PCI_MEM_BASE + PCI_MEM_SIZE;

static pcitag_t mytag;
static unsigned int reserveddevices;

static const struct pci_bus gt64011_pci_bus = {
	0,		/* minimum grant */
	255,		/* maximum latency */
	0,		/* devsel time = fast */
	1,		/* we support fast back-to-back */
	1,		/* we support prefetch */
	0,		/* we don't support 66 MHz */
	0,		/* we don't support 64 bits */
	4000000,	/* bandwidth: in 0.25us cycles / sec */
	1		/* initially one device on bus (i.e. us) */
};

/* one such structure for each PCI bus (allowing for PCI/PCI bridges) */
#define MAXBUS	1
struct pci_bus _pci_bus[MAXBUS];

__attribute__ ((section (".sdata")))
int _pci_maxbus = MAXBUS;

extern int _pciverbose;

/*
 * Called to initialise at the beginning of time
 */
int
_pci_hwinit (int initialise)
{
    pcireg_t reg;
    int master = 0;

    /* find out what devices we should access */
    {
	char *m = getenv ("pcimaster");
	char *s = getenv ("pcislave");

	master = (m != NULL);
	reserveddevices = ((1 << 0) | /* us! */
			   (1 << 31)); /* special function */

	if (m == NULL && s == NULL) {
#if 0
	    _mon_printf ("Attempting to initialise PCI bus without $pcimaster or $pcislave variable!\n");
#endif
	    return -1;
	}
	else if (m != NULL && s != NULL) {
	    _mon_printf ("Can't initialise PCI bus with both $pcimaster and $pcislave set!\n");
	    return -1;
	}
	else if ((m && *m == 'n') || (s && *s == 'n')) {
	    /*
	     * Scan complete bus with enough feedback to allow pcimaster
	     * to be set correctly
	     */
	    _pciverbose = 2;		/* verbose probe reports... */
	    unsetenv ("pcimaster");	/* in case we hang up during probe */
	    unsetenv ("pcislave");	/* in case we hang up during probe */
	}
	else {
	    char *sp, *ep;
	    long device;

	    /* parse the string to find the devives to be skipped */
	    sp = m ? m : s;
	    while (*sp) {
		if (*sp == ',')
		    sp++;
		device = strtol (sp, &ep, 10);
		if (sp == ep || device < 0 || device > 31) {
		    _mon_printf ("Invalid $%s value %s\n", 
				 m ? "pcimaster" : "pcislave",
				 m ? m : s);
		    return -1;
		}
		reserveddevices |= 1 << device;
		sp = ep;
	    }
#if 0
	    /*
	     * This is a bit naff, but there needs to be some indication
	     * of which slot the board will work in...
	     */
	    _mon_printf ("PCI reserved slots:");
	    for (device = 0; device < 32; device++) {
		if (device == 0 || device == 31)
		    continue;
		if (reserveddevices & (1 << device))
		    _mon_printf (" %d", device);
	    }
	    _mon_printf ("\n");
#endif
	}
    }

    mytag = _pci_make_tag (0, 0, 0);

    if (initialise) {
	/* say we will respond to memory requests and we are a master */
	reg = _pci_conf_read (mytag, PCI_COMMAND_STATUS_REG);
	reg |= PCI_COMMAND_MEM_ENABLE |
	    PCI_COMMAND_MASTER_ENABLE |
	    PCI_COMMAND_SERR_ENABLE;
	_pci_conf_write (mytag, PCI_COMMAND_STATUS_REG, reg);

	/* min latency to 6 or greater */
	reg = _pci_conf_read (mytag, PCI_MISC_REG);
	PCI_MISC_LTIM_SET(reg,7);
	_pci_conf_write (mytag, PCI_MISC_REG, reg);

	{
	    unsigned int rassize[4];
	    unsigned int rasbase[4];
	    unsigned int raslo[4];
	    unsigned int rashi[4];
	    unsigned int baren;

	    unsigned int top = 0;
	    unsigned int rbase = 0, rlo = 0, rhi = 0;
	    int i;

	    /* memory has been configured by sbdreset */
	    /* determine our configuration */

	    for (i = 0; i < 4; i++) {
		switch (i) {
		case 0:
		    rbase = ltohl(GT_PAS_RAS10LO);
		    rlo = ltohl(GT_DDAS_RAS0LO);
		    rhi = ltohl(GT_DDAS_RAS0HI);
		    break;
		case 1:
		    rbase = ltohl(GT_PAS_RAS10LO);
		    rlo = ltohl(GT_DDAS_RAS1LO);
		    rhi = ltohl(GT_DDAS_RAS1HI);
		    break;
		case 2:
		    rbase = ltohl(GT_PAS_RAS32LO);
		    rlo = ltohl(GT_DDAS_RAS2LO);
		    rhi = ltohl(GT_DDAS_RAS2HI);
		    break;
		case 3:
		    rbase = ltohl(GT_PAS_RAS32LO);
		    rlo = ltohl(GT_DDAS_RAS3LO);
		    rhi = ltohl(GT_DDAS_RAS3HI);
		    break;
		}
		rbase = ((rbase & GT_PAS_LOMASK_Low) >> 7) << 28;
		rlo = rbase | ((rlo & GT_DDAS_LOMASK_Low) << 20);
		rhi = rbase | ((rhi & GT_DDAS_HIMASK_High) << 20) + (1 << 20);
		raslo[i] = rlo;
		rashi[i] = rhi;
		
		if (rhi > rlo) {
		    rasbase[i] = rlo;
		    rassize[i] = rhi - rlo;
		    if (rhi > top)
			top = rhi;
		}
		else
		    rasbase[i] = rassize[i] = 0;
	    }

	    /* allow access to all of our memory */
	    baren = GT_IPCI_BAREN;
	    if (rassize[0]+rassize[1]) {
		baren &= htoll(~GT_IPCI_BAREN_Ras10Dis);
		_pci_conf_write (mytag, PCI_MAP_REG_START + 0, rasbase[0]);
		GT_IPCI_RAS10SIZE = htoll(((rassize[0]+rassize[1])-1) &
					  GT_IPCI_SIZE_BankSizeMASK);
	    }
	    else
		baren |= htoll(GT_IPCI_BAREN_Ras10Dis);
	    if (rassize[2]+rassize[3]) {
		baren &= htoll(~GT_IPCI_BAREN_Ras32Dis);
		_pci_conf_write (mytag, PCI_MAP_REG_START + 4, rasbase[2]);
		GT_IPCI_RAS32SIZE = htoll(((rassize[2]+rassize[3])-1) &
					  GT_IPCI_SIZE_BankSizeMASK);
	    }
	    else
		baren |= htoll(GT_IPCI_BAREN_Ras32Dis);
	    GT_IPCI_BAREN = baren;
	}
    }


#if 0
   /* only allow address matching on RAS[1:0] */
   pciPutCfgDouble (0, 0, 13, pciReadConfigDouble(0, 0, 13) | 0xff); 
#endif

    /* initialise global data */
    minpciioaddr  = PCI_IO_BASE + 0x10000;
    nextpciioaddr = PCI_IO_BASE + PCI_IO_SIZE;

    minpcimemaddr =  PCI_MEM_BASE + 0x100000;
    nextpcimemaddr = PCI_MEM_BASE + PCI_MEM_SIZE;

    _pci_bus[0] = gt64011_pci_bus;

    return initialise && master;
}


/*
 * Called to reinitialise the bridge after we've scanned each PCI device
 * and know what is possible.
 */
void
_pci_hwreinit (void)
{
}


/* Map the CPU virtual address of an area of local memory to a PCI
   address that can be used by a PCI bus master to access it. */
vm_offset_t
_pci_dmamap (vm_offset_t va, unsigned int len)
{
    return 0x00000000 + KVA_TO_PA (va);
}

/* Map the PCI address of an area of local memory to a CPU physical
   address. */
vm_offset_t
_pci_cpumap (vm_offset_t pcia, unsigned int len)
{
    return pcia - 0x00000000;
}


pcitag_t
_pci_make_tag(int bus, int device, int function)
{
    pcitag_t tag;
    tag = GT_IPCI_CFGADDR_ConfigEn |
	GT_IPCI_CFGADDR_BusNum(bus) |
	GT_IPCI_CFGADDR_DevNum(device) |
	GT_IPCI_CFGADDR_FunctNum(function);
    return tag;
}

void
_pci_break_tag(pcitag_t tag, int *busp, int *devicep, int *functionp)
{
    if (busp)
	*busp = (tag & GT_IPCI_CFGADDR_BusNumMASK) >> GT_IPCI_CFGADDR_BusNumSHIFT;
    if (devicep)
	*devicep = (tag & GT_IPCI_CFGADDR_DevNumMASK) >> GT_IPCI_CFGADDR_DevNumSHIFT;
    if (functionp)
	*functionp = (tag & GT_IPCI_CFGADDR_FunctNumMASK) >> GT_IPCI_CFGADDR_FunctNumSHIFT;
}


pcireg_t
_pci_conf_read(pcitag_t tag, int reg)
{
    pcireg_t data;

    DBG ("conf_read tag=0x%x reg=0x%x -> ", tag, reg);

    GT_IPCI_CFGADDR = htoll(tag | reg);
    GT_IPCI_INTRCAUSE = htoll(~(GT_INTR_MASABORT|GT_INTR_TARABORT));
    mips_wbflush ();

    data = (pcireg_t)GT_IPCI_CFGDATA;
    if (((tag & GT_IPCI_CFGADDR_BusNumMASK) == 0) &&
	((tag & GT_IPCI_CFGADDR_DevNumMASK) == 0))
	data = ltohl(data);
    DBG ("0x%x\n", data);

    if (GT_IPCI_INTRCAUSE & htoll(GT_INTR_MASABORT)) {
	GT_IPCI_INTRCAUSE = htoll(~GT_INTR_MASABORT);
#if 0
	if (_pciverbose >= 1)
	    _pci_tagprintf (tag, "_pci_conf_read: master abort\r\n");
#endif
	data = ~0;
    }

    if (GT_IPCI_INTRCAUSE & htoll(GT_INTR_TARABORT)) {
	GT_IPCI_INTRCAUSE = htoll(~GT_INTR_TARABORT);
	if (_pciverbose >= 1)
	    _pci_tagprintf (tag, "_pci_conf_read: target abort\r\n");
	data = ~0;
    }

    return data;
}


void
_pci_conf_write(pcitag_t tag, int reg, pcireg_t data)
{
    DBG ("conf_write tag=0x%x reg=0x%x data=0x%x\n", tag, reg, data);

    GT_IPCI_CFGADDR = htoll(tag | reg);
    GT_IPCI_INTRCAUSE = htoll(~(GT_INTR_MASABORT|GT_INTR_TARABORT));
    mips_wbflush ();

    if (((tag & GT_IPCI_CFGADDR_BusNumMASK) == 0) &&
	((tag & GT_IPCI_CFGADDR_DevNumMASK) == 0))
	data = ltohl(data);
    GT_IPCI_CFGDATA = data;
    mips_wbflush ();

    if (GT_IPCI_INTRCAUSE & htoll(GT_INTR_MASABORT)) {
	GT_IPCI_INTRCAUSE = htoll(~GT_INTR_MASABORT);
	if (_pciverbose >= 1)
	    _pci_tagprintf (tag, "_pci_conf_write: master abort\r\n");
    }

    if (GT_IPCI_INTRCAUSE & htoll(GT_INTR_TARABORT)) {
	GT_IPCI_INTRCAUSE = htoll(~GT_INTR_TARABORT);
	if (_pciverbose >= 1)
	    _pci_tagprintf (tag, "_pci_conf_write: target abort\r\n");
    }
}

int
_pci_canscan (pcitag_t tag)
{
    int bus, device;
    _pci_break_tag(tag, &bus, &device, NULL);

    if (bus == 0 && (reserveddevices & (1 << device)))
	return 0;
    return 1;
}


int
_pci_map_io(pcitag_t tag, int reg, vm_offset_t *vap, vm_offset_t *pap)
{
    pcireg_t address;
    vm_offset_t pa;

    if (reg < PCI_MAP_REG_START || reg >= PCI_MAP_REG_END || (reg & 3)) {
	if (_pciverbose >= 1)
	    _pci_tagprintf(tag, "_pci_map_io: bad request\r\n");
	return -1;
    }

    address = _pci_conf_read(tag, reg);

    if ((address & PCI_MAP_IO) == 0) {
	if (_pciverbose >= 1)
	    _pci_tagprintf (tag, "_pci_map_io: attempt to memory map a memory region\r\n");
	return -1;
    }

    /* There is a 1<->1 correspondence between PCI and local addresses */
    pa = address & PCI_MAP_IO_ADDRESS_MASK;
    *pap = pa;

    *vap = (vm_offset_t) PA_TO_KVA1 (pa);

    if (_pciverbose >= 3)
	_pci_tagprintf (tag, "_pci_map_io: mapping io at virtual 0x%x, physical 0x%x\r\n", *vap, *pap);

    return 0;
}


int
_pci_map_mem(pcitag_t tag, int reg, vm_offset_t *vap, vm_offset_t *pap)
{
    pcireg_t address;
    vm_offset_t pa;

    if (reg == PCI_MAP_REG_ROM) {
	/* expansion ROM */
	address = _pci_conf_read(tag, reg);
	if (!(address & PCI_MAP_ROM)) {
	    _pci_tagprintf (tag, "_pci_map_mem: attempt to map missing rom\r\n");
	    return -1;
	}
	pa = address & PCI_MAP_ROM_ADDRESS_MASK;
    }
    else {
	if (reg < PCI_MAP_REG_START || reg >= PCI_MAP_REG_END || (reg & 3)) {
	    if (_pciverbose >= 1)
		_pci_tagprintf (tag, "_pci_map_mem: bad request\r\n");
	    return -1;
	}
	address = _pci_conf_read(tag, reg);

	if ((address & PCI_MAP_IO) != 0) {
	    if (_pciverbose >= 1)
		_pci_tagprintf (tag, "_pci_map_mem: attempt to memory map an I/O region\r\n");
	    return -1;
	}
	switch (address & PCI_MAP_MEMORY_TYPE_MASK) {
	case PCI_MAP_MEMORY_TYPE_32BIT:
	case PCI_MAP_MEMORY_TYPE_32BIT_1M:
	    break;
	case PCI_MAP_MEMORY_TYPE_64BIT:
	    if (_pciverbose >= 1)
		_pci_tagprintf (tag, "_pci_map_mem: attempt to map 64-bit region\r\n");
	    return -1;
	default:
	    if (_pciverbose >= 1)
		_pci_tagprintf (tag, "_pci_map_mem: reserved mapping type\r\n");
	    return -1;
	}
	pa = address & PCI_MAP_MEMORY_ADDRESS_MASK;
    }

    *pap = pa;
    if (address & PCI_MAP_MEMORY_CACHABLE) 
	*vap = (vm_offset_t) PA_TO_KVA0 (pa);
    else
	*vap = (vm_offset_t) PA_TO_KVA1 (pa);


    if (_pciverbose >= 3)
	_pci_tagprintf (tag, "_pci_map_mem: mapping memory at virtual 0x%x, physical 0x%x\r\n", *vap, *pap);
    return 0;
}


int
_pci_map_int (pcitag_t tag)
{
    pcireg_t data;
    int pin, bus, device;

    data = _pci_conf_read(tag, PCI_INTERRUPT_REG);
    pin = PCI_INTERRUPT_PIN(data);
    if (pin == 0)
	/* No IRQ used. */
	return 0;

    if (--pin > 3) {
	if (_pciverbose >= 1)
	    _pci_tagprintf (tag, "_pci_map_int: bad interrupt pin %d\n", pin);
	return -1;
    }

    _pci_break_tag (tag, &bus, &device, NULL);
    if (bus != 0)
	return -1;

    /* XXX TO BE IMPLEMNTED */
    return -1;
}

pcireg_t
_pci_allocate_mem (pcitag_t tag, size_t size)
{
    pcireg_t address;

    /* allocate downwards, then round to size boundary */
    address = (nextpcimemaddr - size) & ~(size - 1);
    if (address > nextpcimemaddr || address < minpcimemaddr)
	return -1;
    nextpcimemaddr = address;
    return address;
}


pcireg_t
_pci_allocate_io (pcitag_t tag, size_t size)
{
    pcireg_t address;

    /* allocate downwards, then round to size boundary */
    address = (nextpciioaddr - size) & ~(size - 1);
    if (address > nextpciioaddr || address < minpciioaddr)
	return -1;
    nextpciioaddr = address;
    return address;
}

#endif
