/*	$Id: pci_machdep.c,v 1.3 2002/02/19 12:22:17 patrik Exp $ */

/*
 * Copyright (c) 2001 IP Unplugged AB (www.ipunplugged.com)
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. 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.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed for IP Unplugged AB, by
 *      Patrik Lindergren.
 * 4. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
 *
 */

#include <sys/param.h>
#include <sys/device.h>
#include <sys/systm.h>

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

#include <machine/pio.h>

#include <include/powerpmc250.h>
#include <pmon/dev/mpc107reg.h>

#include <pmon.h>

#include <stdlib.h>

#ifndef VA_TO_PA
#define VA_TO_PA(x)	(x)
#endif
#ifndef PA_TO_VA
#define	PA_TO_VA(x)	(x)
#endif

#define	_256MB		0x10000000	/* One BAT reg mapping limit */

static pcireg_t pci_mem_space_pci_base;
static pcireg_t pci_local_mem_pci_base;
static pcireg_t pci_local_mem_isa_base;

extern int _pciverbose;
extern int monarch_mode;

#ifdef POCONO
int InitInterruptRouting(void);
#endif /* POCONO */

/*
 * Called to initialise the bridge at the beginning of time
 */
int
_pci_hwinit(initialise, iot, memt)
	int initialise;
	bus_space_tag_t iot;
	bus_space_tag_t memt;
{
    struct pci_device *pd;
    struct pci_bus *pb;
    unsigned int cfg;
    
    iot->bus_base = PA_TO_VA(MPC107_PCI_ISA_IO_BASE);
    iot->bus_reverse = 1;
    memt->bus_base = PA_TO_VA(MPC107_PCI_MEM_BASE);
    memt->bus_reverse = 1;

    pci_local_mem_pci_base = PCI_LOCAL_MEM_PCI_BASE;
    pci_local_mem_isa_base = PCI_LOCAL_MEM_ISA_BASE;
    pci_mem_space_pci_base = PCI_MEM_SPACE_PCI_BASE;

    if (!initialise) {
        return(0);
    }

    cfg = inl(CONFIG_REGISTER) & CFG_REG_MASK;
    monarch_mode = CFG_REG_MONARCH(cfg) ? 1 : 0;
    
    /*
     * Initialize PCI Head device (Northbridge).
     */
    pd = malloc(sizeof(struct pci_device));        
    if(pd == NULL) {
        /* Can't Alloc memory */
        printf("pci: can't alloc memory for northbridge\n");
        return(-1);
    }
    bzero((void *)pd, sizeof(struct pci_device));
    
    pd->pa.pa_flags = PCI_FLAGS_IO_ENABLED | PCI_FLAGS_MEM_ENABLED;
    pd->pa.pa_iot = iot;
    pd->pa.pa_memt = memt;
    _pci_head = pd;
    
    pd->bridge.secbus = malloc(sizeof(struct pci_bus));        
    if(pd->bridge.secbus == NULL) {
        /* Can't Alloc memory */
        printf("pci: can't alloc memory for new pci bus\n");
        return(-1);
    }
    bzero((void *)pd->bridge.secbus, sizeof(struct pci_bus));
    
    pb = pd->bridge.secbus;
    pb->max_lat = 255;
    pb->fast_b2b = 1;
    pb->prefetch = 1;
    pb->bandwidth = 4000000;
    pb->ndev = 1;
    _pci_bushead = pb;
    
    /* point to start and end of memory region */
    pb->minpcimemaddr = PCI_MEM_SPACE_PCI_BASE;
    pb->nextpcimemaddr = PCI_MEM_SPACE_PCI_BASE + MPC107_PCI_MEM_SIZE;

    /*
     * point to start and end of io region 
     */
    pb->minpciioaddr = PCI_IO_SPACE_PCI_BASE;
    pb->nextpciioaddr = PCI_IO_SPACE_PCI_BASE + MPC107_PCI_IO_SIZE;

#ifdef POCONO
    /* Setup predefined interrupt routing list */
    InitInterruptRouting();
#endif /* POCONO */
    return(1);
}


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

void
_pci_flush (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(va, len)
	vm_offset_t va;
	unsigned int len;
{
	return(pci_local_mem_pci_base + VA_TO_PA (va));
}

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


/*
 *  Map the CPU virtual address of an area of local memory to an ISA
 *  address that can be used by a ISA bus master to access it.
 */
vm_offset_t
_isa_dmamap(va, len)
	vm_offset_t va;
	unsigned int len;
{
	unsigned long pa = VA_TO_PA (va);

    /* restrict ISA DMA access to bottom 8/16MB of local memory */
	if (pa + len > 0x1000000 - pci_local_mem_isa_base) {
		return (vm_offset_t)-1;
	}
	return(pci_local_mem_isa_base + pa);
}

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


int
_pci_canscan (pcitag_t tag)
{
	pcitag_t dev = pci_make_tag(0, 0, 0);
	if(dev == tag)
		return(0);
	else
		return (1);
}

/*
 *  Get contents of PCI Mapping register and do any machine
 *  dependent mapping setup.
 */
int
_pci_map_port(tag, reg, port)
	pcitag_t tag;
	int reg;
	unsigned int *port;
{
	pcireg_t address;
    
	if (reg < PCI_MAPREG_START || reg >= PCI_MAPREG_END || (reg & 3)) {
		if (_pciverbose >= 1) {
			_pci_tagprintf(tag, "_pci_map_port: bad request\r\n");
		}
		return -1;
	}
    
	address = _pci_conf_read(tag, reg);
    
	if (PCI_MAPREG_TYPE(address) != PCI_MAPREG_TYPE_IO) {
		if (_pciverbose >= 1) {
			_pci_tagprintf (tag, "_pci_map_port: attempt to i/o map a memory region\r\n");
		}
		return(-1);
	}

	*port = (address & PCI_MAPREG_IO_ADDR_MASK) - PCI_IO_SPACE_PCI_BASE;
	return(0);
}


int
_pci_map_io(tag, reg, vap, pap)
	pcitag_t tag;
	int reg;
	vm_offset_t *vap;
	vm_offset_t *pap;
{
	pcireg_t address;
	vm_offset_t pa;
    
	if (reg < PCI_MAPREG_START || reg >= PCI_MAPREG_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 (PCI_MAPREG_TYPE(address) != PCI_MAPREG_TYPE_IO) {
		if (_pciverbose >= 1) {
			_pci_tagprintf (tag, "_pci_map_io: attempt to i/o map a memory region\r\n");
		}
		return(-1);
	}

	pa = (address & PCI_MAPREG_IO_ADDR_MASK) - PCI_IO_SPACE_PCI_BASE;
	*pap = pa;
	*vap = (vm_offset_t) PA_TO_VA (MPC107_PCI_IO_BASE + pa);
    
	if (_pciverbose >= 3) {
		_pci_tagprintf(tag, "_pci_map_io: mapping i/o at virtual %08x, physical %08x\n", *vap, *pap);
	}
	return(0);
}


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

	if (reg == PCI_MAPREG_ROM) {
	/* expansion ROM */
		address = _pci_conf_read(tag, reg);
		if (PCI_MAPREG_TYPE(address) != PCI_MAPREG_TYPE_ROM) {
			_pci_tagprintf (tag, "_pci_map_mem: attempt to map missing rom\r\n");
			return(-1);
		}
		pa = PCI_MAPREG_ROM_ADDR(address);
	}
	else {
		if (reg < PCI_MAPREG_START || reg >= PCI_MAPREG_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 (PCI_MAPREG_TYPE(address) != PCI_MAPREG_TYPE_MEM) {
			if (_pciverbose >= 1) {
				_pci_tagprintf(tag, "pci_map_mem: attempt to memory map an I/O region\r\n");
			}
			return(-1);
		}
	
		switch (PCI_MAPREG_MEM_TYPE(address)) {
		case PCI_MAPREG_MEM_TYPE_32BIT:
		case PCI_MAPREG_MEM_TYPE_32BIT_1M:
			break;

		case PCI_MAPREG_MEM_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_MAPREG_MEM_ADDR_MASK;
	}

	/*   
	 *  Physical address returned should be what the PCI bus master uses
	 *  when accessing the PCI bus mem space. Therefore don't remove the
	 *  PCI bus offset. Hope this doesn't break something.
	 */
	*pap = pa;
	pa -= pci_mem_space_pci_base;
	*pap = pa;
	*vap = (vm_offset_t) PA_TO_VA (MPC107_PCI_MEM_BASE + 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);
}


void *
_pci_map_int(tag, level, func, arg)
	pcitag_t tag;
	int level;
	int (*func) __P((void *));
	void *arg;
{
	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 NULL;
	}

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


	_pci_break_tag (tag, &bus, &device, NULL);

	if (bus != 0 || device > 5) {
		return(NULL);
	}

	/* XXX need to work this out based on device number etc. */
	_pci_tagprintf(tag, "_pci_map_int: attempt to map device %d pin %c\n", 
		   device, '@' + pin);
	return(NULL);
}
