/*
 *   OpenBIOS pci driver
 *
 *   This driver is compliant to the
 *   PCI bus binding to IEEE 1275-1994 Rev 2.1
 *
 *   (C) 2004 Stefan Reinauer
 *   (C) 2005 Ed Schouten <ed@fxq.nl>
 *
 *   Some parts from OpenHackWare-0.4, Copyright (c) 2004-2005 Jocelyn Mayer
 *
 *   This program is free software; you can redistribute it and/or
 *   modify it under the terms of the GNU General Public License
 *   version 2
 *
 */

#include "config.h"
#include "libopenbios/bindings.h"
#include "libopenbios/ofmem.h"
#include "kernel/kernel.h"
#include "drivers/pci.h"
#include "libc/byteorder.h"
#include "libc/vsprintf.h"

#include "drivers/drivers.h"
#include "drivers/vga.h"
#include "packages/video.h"
#include "libopenbios/video.h"
#include "timer.h"
#include "pci.h"
#include "pci_database.h"
#ifdef CONFIG_DRIVER_MACIO
#include "cuda.h"
#include "macio.h"
#endif
#ifdef CONFIG_DRIVER_USB
#include "drivers/usb.h"
#endif

#if defined (CONFIG_DEBUG_PCI)
# define PCI_DPRINTF(format, ...) printk(format, ## __VA_ARGS__)
#else
# define PCI_DPRINTF(format, ...) do { } while (0)
#endif

#define set_bool_property(ph, name) set_property(ph, name, NULL, 0);

/* DECLARE data structures for the nodes.  */

DECLARE_UNNAMED_NODE( ob_pci_bus_node, INSTALL_OPEN, 2*sizeof(int) );
DECLARE_UNNAMED_NODE( ob_pci_bridge_node, INSTALL_OPEN, 2*sizeof(int) );
DECLARE_UNNAMED_NODE( ob_pci_simple_node, 0, 2*sizeof(int) );
DECLARE_UNNAMED_NODE( ob_pci_empty_node, 0, 2*sizeof(int) );

const pci_arch_t *arch;

#define IS_NOT_RELOCATABLE	0x80000000
#define IS_PREFETCHABLE		0x40000000
#define IS_ALIASED		0x20000000

static int encode_int32_cells(int num_cells, u32 *prop, ucell val)
{
    int i = 0;

    /* hi ... lo */
    for (i=0; i < num_cells; ++i) {
        prop[num_cells - i - 1] = val;
        val >>= 16;
        val >>= 16;
    }

    return num_cells;
}

static inline int pci_encode_phys_addr(u32 *phys, int flags, int space_code,
				 pci_addr dev, uint8_t reg, uint64_t addr)
{

	/* phys.hi */

	phys[0] = flags | (space_code << 24) | dev | reg;

	/* phys.mid */

	phys[1] = addr >> 32;

	/* phys.lo */

	phys[2] = addr;

	return 3;
}

static inline int pci_encode_size(u32 *prop, uint64_t size)
{
    return encode_int32_cells(2, prop, size);
}

static int host_address_cells(void)
{
    return get_int_property(find_dev("/"), "#address-cells", NULL);
}

static int host_encode_phys_addr(u32 *prop, ucell addr)
{
    return encode_int32_cells(host_address_cells(), prop, addr);
}

static int host_size_cells(void)
{
    return get_int_property(find_dev("/"), "#size-cells", NULL);
}

/*
static int parent_address_cells(void)
{
    phandle_t parent_ph = ih_to_phandle(my_parent());
    return get_int_property(parent_ph, "#address-cells", NULL);
}

static int parent_size_cells(void)
{
    phandle_t parent_ph = ih_to_phandle(my_parent());
    return get_int_property(parent_ph, "#size-cells", NULL);
}
*/

#if defined(CONFIG_DEBUG_PCI)
static void dump_reg_property(const char* description, int nreg, u32 *reg)
{
    int i;
    printk("%s reg", description);
    for (i=0; i < nreg; ++i) {
        printk(" %08X", reg[i]);
    }
    printk("\n");
}
#endif

static unsigned long pci_bus_addr_to_host_addr(int space, uint32_t ba)
{
    if (space == IO_SPACE) {
        return arch->io_base + (unsigned long)ba;
    } else if (space == MEMORY_SPACE_32) {
        return arch->host_pci_base + (unsigned long)ba;
    } else {
        /* Return unaltered to aid debugging property values */
        return (unsigned long)ba;
    }
}

static inline void pci_decode_pci_addr(pci_addr addr, int *flags,
                                       int *space_code, uint32_t *mask)
{
    *flags = 0;

    if (addr & 0x01) {
        *space_code = IO_SPACE;
        *mask = 0x00000001;
    } else {
        if (addr & 0x04) {
            *space_code = MEMORY_SPACE_64;
            *flags |= IS_NOT_RELOCATABLE; /* XXX: why not relocatable? */
        } else {
            *space_code = MEMORY_SPACE_32;
        }

        if (addr & 0x08) {
            *flags |= IS_PREFETCHABLE;
        }

        *mask = 0x0000000F;
    }
}

static void
ob_pci_open(int *idx)
{
	int ret=1;
	RET ( -ret );
}

static void
ob_pci_close(int *idx)
{
}

static void
ob_pci_initialize(int *idx)
{
}

/* ( str len -- phys.lo phys.mid phys.hi ) */

static void
ob_pci_decode_unit(int *idx)
{
	ucell hi, mid, lo;
	const char *arg = pop_fstr_copy();
	int dev, fn, reg, ss, n, p, t;
	int bus, len;
	char *ptr;

	PCI_DPRINTF("ob_pci_decode_unit idx=%p\n", idx);

	fn = 0;
	reg = 0;
	n = 0;
	p = 0;
	t = 0;

	ptr = (char*)arg;
	if (*ptr == 'n') {
		n = IS_NOT_RELOCATABLE;
		ptr++;
	}
	if (*ptr == 'i') {
		ss = IO_SPACE;
		ptr++;
		if (*ptr == 't') {
			t = IS_ALIASED;
			ptr++;
		}

		/* DD,F,RR,NNNNNNNN */

		dev = strtol(ptr, &ptr, 16);
		ptr++;
		fn = strtol(ptr, &ptr, 16);
		ptr++;
		reg = strtol(ptr, &ptr, 16);
		ptr++;
		lo = strtol(ptr, &ptr, 16);
		mid = 0;

	} else if (*ptr == 'm') {
		ss = MEMORY_SPACE_32;
		ptr++;
		if (*ptr == 't') {
			t = IS_ALIASED;
			ptr++;
		}
		if (*ptr == 'p') {
			p = IS_PREFETCHABLE;
			ptr++;
		}

		/* DD,F,RR,NNNNNNNN */

		dev = strtol(ptr, &ptr, 16);
		ptr++;
		fn = strtol(ptr, &ptr, 16);
		ptr++;
		reg = strtol(ptr, &ptr, 16);
		ptr++;
		lo = strtol(ptr, &ptr, 16);
		mid = 0;

	} else if (*ptr == 'x') {
		unsigned long long addr64;
		ss = MEMORY_SPACE_64;
		ptr++;
		if (*ptr == 'p') {
			p = IS_PREFETCHABLE;
			ptr++;
		}

		/* DD,F,RR,NNNNNNNNNNNNNNNN */

		dev = strtol(ptr, &ptr, 16);
		ptr++;
		fn = strtol(ptr, &ptr, 16);
		ptr++;
		reg = strtol(ptr, &ptr, 16);
		ptr++;
		addr64 = strtoll(ptr, &ptr, 16);
		lo = (ucell)addr64;
		mid = addr64 >> 32;

	} else {
		ss = CONFIGURATION_SPACE;
		/* "DD" or "DD,FF" */
		dev = strtol(ptr, &ptr, 16);
		if (*ptr == ',') {
			ptr++;
			fn = strtol(ptr, NULL, 16);
		}
		lo = 0;
		mid = 0;
	}
	free((char*)arg);

	bus = get_int_property(get_cur_dev(), "bus-range", &len);

	hi = n | p | t | (ss << 24) | (bus << 16) | (dev << 11) | (fn << 8) | reg;

	PUSH(lo);
	PUSH(mid);
	PUSH(hi);

	PCI_DPRINTF("ob_pci_decode_unit idx=%p addr="
	        FMT_ucellx " " FMT_ucellx " " FMT_ucellx "\n",
	        idx, lo, mid, hi);
}

/*  ( phys.lo phy.mid phys.hi -- str len ) */

static void
ob_pci_encode_unit(int *idx)
{
	char buf[28];
	cell hi = POP();
	cell mid = POP();
	cell lo = POP();
        int n, p, t, ss, dev, fn, reg;

	n = hi & IS_NOT_RELOCATABLE;
	p = hi & IS_PREFETCHABLE;
	t = hi & IS_ALIASED;
	ss = (hi >> 24) & 0x03;

	dev = (hi >> 11) & 0x1F;
	fn = (hi >> 8) & 0x07;
	reg = hi & 0xFF;

	switch(ss) {
	case CONFIGURATION_SPACE:

		if (fn == 0)	/* DD */
        		snprintf(buf, sizeof(buf), "%x", dev);
		else		/* DD,F */
        		snprintf(buf, sizeof(buf), "%x,%x", dev, fn);
		break;

	case IO_SPACE:

		/* [n]i[t]DD,F,RR,NNNNNNNN */
                snprintf(buf, sizeof(buf), "%si%s%x,%x,%x," FMT_ucellx,
			 n ? "n" : "",	/* relocatable */
			 t ? "t" : "",	/* aliased */
			 dev, fn, reg, t ? lo & 0x03FF : lo);
		break;

	case MEMORY_SPACE_32:

		/* [n]m[t][p]DD,F,RR,NNNNNNNN */
                snprintf(buf, sizeof(buf), "%sm%s%s%x,%x,%x," FMT_ucellx,
			 n ? "n" : "",	/* relocatable */
			 t ? "t" : "",	/* aliased */
			 p ? "p" : "",	/* prefetchable */
			 dev, fn, reg, lo );
		break;

	case MEMORY_SPACE_64:

		/* [n]x[p]DD,F,RR,NNNNNNNNNNNNNNNN */
        	snprintf(buf, sizeof(buf), "%sx%s%x,%x,%x,%llx",
			 n ? "n" : "",	/* relocatable */
			 p ? "p" : "",	/* prefetchable */
                         dev, fn, reg, ((long long)mid << 32) | (long long)lo);
		break;
	}
	push_str(buf);

	PCI_DPRINTF("ob_pci_encode_unit space=%d dev=%d fn=%d buf=%s\n",
	        ss, dev, fn, buf);
}

/* Map PCI MMIO or IO space from the BAR address. Note it is up to the caller
   to understand whether the resulting address is in MEM or IO space and
   use the appropriate accesses */
static ucell ob_pci_map(uint32_t ba, ucell size) {
	phys_addr_t phys;
	uint32_t mask;
	int flags, space_code;
	ucell virt;
	
	pci_decode_pci_addr(ba, &flags, &space_code, &mask);

	phys = pci_bus_addr_to_host_addr(space_code,
			ba & ~mask);
	
#if defined(CONFIG_OFMEM)
	ofmem_claim_phys(phys, size, 0);

#if defined(CONFIG_PPC)
	/* For some reason PPC gets upset when virt != phys for map-in... */
	virt = ofmem_claim_virt(phys, size, 0);
#else
	virt = ofmem_claim_virt(-1, size, size);
#endif

	ofmem_map(phys, virt, size, ofmem_arch_io_translation_mode(phys));

#else
	virt = size;	/* Keep compiler quiet */
	virt = phys;
#endif

	return virt;
}

static void ob_pci_unmap(ucell virt, ucell size) {
	ofmem_unmap(virt, size); 
}

/* ( pci-addr.lo pci-addr.mid pci-addr.hi size -- virt ) */

static void
ob_pci_bus_map_in(int *idx)
{
	uint32_t ba;
	ucell size;
	ucell virt;

	PCI_DPRINTF("ob_pci_bar_map_in idx=%p\n", idx);

	size = POP();
	POP();
	POP();
	ba = POP();

	virt = ob_pci_map(ba, size);

	PUSH(virt);
}

NODE_METHODS(ob_pci_bus_node) = {
	{ NULL,			ob_pci_initialize	},
	{ "open",		ob_pci_open		},
	{ "close",		ob_pci_close		},
	{ "decode-unit",	ob_pci_decode_unit	},
	{ "encode-unit",	ob_pci_encode_unit	},
	{ "pci-map-in",		ob_pci_bus_map_in	},
};

/* ( pci-addr.lo pci-addr.mid pci-addr.hi size -- virt ) */

static void
ob_pci_bridge_map_in(int *idx)
{
	/* As per the IEEE-1275 PCI specification, chain up to the parent */
	call_parent_method("pci-map-in");
}

NODE_METHODS(ob_pci_bridge_node) = {
	{ NULL,			ob_pci_initialize	},
	{ "open",		ob_pci_open		},
	{ "close",		ob_pci_close		},
	{ "decode-unit",	ob_pci_decode_unit	},
	{ "encode-unit",	ob_pci_encode_unit	},
	{ "pci-map-in",		ob_pci_bridge_map_in	},
};

NODE_METHODS(ob_pci_simple_node) = {
	{ "open",		ob_pci_open		},
	{ "close",		ob_pci_close		},
};

NODE_METHODS(ob_pci_empty_node) = {
	{ NULL,			ob_pci_initialize	}
};

static void pci_set_bus_range(const pci_config_t *config)
{
	phandle_t dev = find_dev(config->path);
	u32 props[2];

	props[0] = config->secondary_bus;
	props[1] = config->subordinate_bus;

	PCI_DPRINTF("setting bus range for %s PCI device, "
	        "package handle " FMT_ucellx " "
            "bus primary=%d secondary=%d subordinate=%d\n",
            config->path,
            dev,
            config->primary_bus,
            config->secondary_bus,
            config->subordinate_bus);


	set_property(dev, "bus-range", (char *)props, 2 * sizeof(props[0]));
}

static void pci_host_set_reg(phandle_t phandle)
{
    phandle_t dev = phandle;

    /* at most 2 integers for address and size */
    u32 props[4];
    int ncells = 0;

    ncells += encode_int32_cells(host_address_cells(), props + ncells,
            arch->cfg_base);

    ncells += encode_int32_cells(host_size_cells(), props + ncells,
            arch->cfg_len);

    set_property(dev, "reg", (char *)props, ncells * sizeof(props[0]));

#if defined(CONFIG_DEBUG_PCI)
    dump_reg_property("pci_host_set_reg", 4, props);
#endif
}

/* child-phys : parent-phys : size */
/* 3 cells for PCI : 2 cells for 64bit parent : 2 cells for PCI */

static void pci_host_set_ranges(const pci_config_t *config)
{
	phandle_t dev = get_cur_dev();
	u32 props[32];
	int ncells = 0;
	pci_range_t range;
	int i;
	
	for (i = 0; i < 4; i++) {
		range = arch->host_ranges[i];
	    
		/* End of range list reached */
		if (range.type == 0x0 && range.len == 0x0) {
			break;
		}
	
		ncells += pci_encode_phys_addr(props + ncells, 0, range.type,
						0, 0, range.parentaddr);
		ncells += host_encode_phys_addr(props + ncells, range.childaddr);
		ncells += pci_encode_size(props + ncells, range.len);
	}
	
	set_property(dev, "ranges", (char *)props, ncells * sizeof(props[0]));
}

int host_config_cb(const pci_config_t *config)
{
	//XXX this overrides "reg" property
	pci_host_set_reg(get_cur_dev());
	pci_host_set_ranges(config);

	return 0;
}

static int sabre_configure(phandle_t dev)
{
        uint32_t props[28];

        /* Sabre has a custom reg property from the default */
        props[0] = 0x1fe;
        props[1] = 0x0;
        props[2] = 0x0;
        props[3] = 0x10000;
        props[4] = 0x1fe;
        props[5] = 0x1000000;
        props[6] = 0x0;
        props[7] = 0x100;
        set_property(dev, "reg", (char *)props, 8 * sizeof(props[0]));

        props[0] = 0xc0000000;
        props[1] = 0x20000000;
        set_property(dev, "virtual-dma", (char *)props, 2 * sizeof(props[0]));
        props[0] = 1;
        set_property(dev, "#virtual-dma-size-cells", (char *)props,
                     sizeof(props[0]));
        set_property(dev, "#virtual-dma-addr-cells", (char *)props,
                     sizeof(props[0]));

        set_property(dev, "no-streaming-cache", (char *)props, 0);

        props[0] = 0x000007f0;
        props[1] = 0x000007ee;
        props[2] = 0x000007ef;
        props[3] = 0x000007e5;
        set_property(dev, "interrupts", (char *)props, 4 * sizeof(props[0]));
        props[0] = 0x0000001f;
        set_property(dev, "upa-portid", (char *)props, 1 * sizeof(props[0]));
        return 0;
}

int sabre_config_cb(const pci_config_t *config)
{
    host_config_cb(config);

    return sabre_configure(get_cur_dev());
}

int bridge_config_cb(const pci_config_t *config)
{
	phandle_t aliases;

	aliases = find_dev("/aliases");
	set_property(aliases, "bridge", config->path, strlen(config->path) + 1);

	return 0;
}

int simba_config_cb(const pci_config_t *config)
{
    u32 props[128];
    int ncells = 0;

    bridge_config_cb(config);

    /* Configure the simba ranges as per the mostly undocumented
       PCI config register in Linux's apb_fake_ranges():
      
       pci@1,1 (pciA):
           IO: 0x1fe02000000-0x1fe027fffff
          MEM: 0x1ff20000000-0x1ff5fffffff
          
       pci@1 (pciB):
           IO: 0x1fe02800000-0x1fe02ffffff
          MEM: 0x1ff60000000-0x1ff9fffffff
    */

    switch (PCI_FN(config->dev)) {
    case 1:
        /* IO: 0x1fe02000000-0x1fe027fffff */
        pci_config_write8(config->dev, 0xde, 0x0f);

        /* MEM: 0x1ff20000000-0x1ff5fffffff */
        pci_config_write8(config->dev, 0xdf, 0x06);

        /* Onboard NIC: slot 1, intno 0x21 */
        ncells += pci_encode_phys_addr(props + ncells, 0, 0, PCI_ADDR(1, 1, 0), 0, 0);
        props[ncells++] = 0x1;
        props[ncells++] = find_dev("/pci");
        props[ncells++] = 0x21;

        /* Onboard IDE: slot 3, intno 0x20 */
        ncells += pci_encode_phys_addr(props + ncells, 0, 0, PCI_ADDR(1, 3, 0), 0, 0);
        props[ncells++] = 0x1;
        props[ncells++] = find_dev("/pci");
        props[ncells++] = 0x20;
        set_property(get_cur_dev(), "interrupt-map", (char *)props, ncells * sizeof(props[0]));

        props[0] = 0x00fff800;
        props[1] = 0x0;
        props[2] = 0x0;
        props[3] = 0x7;
        set_property(get_cur_dev(), "interrupt-map-mask", (char *)props, 4 * sizeof(props[0]));
        break;

    case 0:
        /* IO: 0x1fe02800000-0x1fe02ffffff */
        pci_config_write8(config->dev, 0xde, 0xf0);

        /* MEM: 0x1ff60000000-0x1ff9fffffff */
        pci_config_write8(config->dev, 0xdf, 0x18);
        break;
    }

    return 0;
}

int ide_config_cb2 (const pci_config_t *config)
{
	ob_ide_init(config->path,
		    config->assigned[0] & ~0x0000000F,
		    (config->assigned[1] & ~0x0000000F) + 2,
		    config->assigned[2] & ~0x0000000F,
		    (config->assigned[3] & ~0x0000000F) + 2);
	return 0;
}

int eth_config_cb (const pci_config_t *config)
{
	phandle_t ph = get_cur_dev();

	set_property(ph, "network-type", "ethernet", 9);
	set_property(ph, "removable", "network", 8);
	set_property(ph, "category", "net", 4);

        return 0;
}

int sunhme_config_cb(const pci_config_t *config)
{
	phandle_t ph = get_cur_dev();
	
	set_int_property(ph, "hm-rev", 0x21);
	
	return eth_config_cb(config);
}

int rtl8139_config_cb(const pci_config_t *config)
{
#ifdef CONFIG_PPC
	/* Apple's OF seemingly enables bus mastering on some cards by
	 * default, which means that some buggy drivers forget to
	 * explicitly set it (OS X, MorphOS). Mimic this behaviour so
	 * that these buggy drivers work under emulation. */
	if (is_apple()) {
		ob_pci_enable_bus_master(config);
	}
#endif

	return eth_config_cb(config);
}

int sungem_config_cb (const pci_config_t *config)
{
	phandle_t ph = get_cur_dev();
	uint32_t val, *mmio;
	uint8_t mac[6];
	ucell virt;

#define MAC_ADDR0	(0x6080UL/4)	/* MAC Address 0 Register	*/
#define MAC_ADDR1	(0x6084UL/4)	/* MAC Address 1 Register	*/
#define MAC_ADDR2	(0x6088UL/4)	/* MAC Address 2 Register	*/

	/* Map PCI memory BAR 0 to access the sungem registers */
	virt = ob_pci_map(config->assigned[0], 0x8000);
	mmio = (void *)(uintptr_t)virt;
	
	val = __le32_to_cpu(*(mmio + MAC_ADDR0));
	mac[5] = val & 0xff;
	mac[4] = (val >> 8) & 0xff;
	val = __le32_to_cpu(*(mmio + MAC_ADDR1));
	mac[3] = val & 0xff;
	mac[2] = (val >> 8) & 0xff;
	val = __le32_to_cpu(*(mmio + MAC_ADDR2));
	mac[1] = val & 0xff;
	mac[0] = (val >> 8) & 0xff;
	set_property(ph, "local-mac-address", (char *)mac, 6);
	
	ob_pci_unmap(virt, 0x8000);
	return 0;
}

/*
 * "Designing PCI Cards and Drivers for Power Macintosh Computers", p. 454
 *
 *  "AAPL,address" provides an array of 32-bit logical addresses
 *  Nth entry corresponding to Nth "assigned-address" base address entry.
 */

static void pci_set_AAPL_address(const pci_config_t *config)
{
	phandle_t dev = get_cur_dev();
	cell props[7];
	uint32_t mask;
	int ncells, i, flags, space_code;

	ncells = 0;
	for (i = 0; i < 6; i++) {
		if (!config->assigned[i] || !config->sizes[i])
			continue;
		pci_decode_pci_addr(config->assigned[i],
				    &flags, &space_code, &mask);

		props[ncells++] = pci_bus_addr_to_host_addr(space_code,
					config->assigned[i] & ~mask);
	}
	if (ncells)
		set_property(dev, "AAPL,address", (char *)props,
			     ncells * sizeof(cell));
}

static void pci_set_assigned_addresses(phandle_t phandle,
                                       const pci_config_t *config, int num_bars)
{
	phandle_t dev = phandle;
	u32 props[32];
	int ncells;
	int i;
	uint32_t mask;
	int flags, space_code;

	ncells = 0;
	for (i = 0; i < num_bars; i++) {
		/* consider only bars with non-zero region size */
		if (!config->sizes[i])
			continue;
		pci_decode_pci_addr(config->assigned[i],
				    &flags, &space_code, &mask);

		ncells += pci_encode_phys_addr(props + ncells,
				     flags, space_code, config->dev,
				     PCI_BASE_ADDR_0 + (i * sizeof(uint32_t)),
				     config->assigned[i] & ~mask);

		props[ncells++] = 0x00000000;
		props[ncells++] = config->sizes[i];
	}
	if (ncells)
		set_property(dev, "assigned-addresses", (char *)props,
			     ncells * sizeof(props[0]));
}

/* call after writing "reg" property to update config->path */
static void ob_pci_reload_device_path(phandle_t phandle, pci_config_t *config)
{
    /* since "name" and "reg" are now assigned
       we need to reload current node name */
    char *new_path = get_path_from_ph(phandle);
    if (new_path) {
        if (0 != strcmp(config->path, new_path)) {
            PCI_DPRINTF("\n=== CHANGED === package path old=%s new=%s\n",
                    config->path, new_path);
            strncpy(config->path, new_path, sizeof(config->path));
            config->path[sizeof(config->path)-1] = '\0';
        }
        free(new_path);
    } else {
        PCI_DPRINTF("\n=== package path old=%s new=NULL\n", config->path);
    }
}

static void pci_set_reg(phandle_t phandle,
                        pci_config_t *config, int num_bars)
{
	phandle_t dev = phandle;
	u32 props[38];
	int ncells;
	int i;
	uint32_t mask;
	int space_code, flags;

    ncells = 0;

    /* first (addr, size) pair is the beginning of configuration address space */
	ncells += pci_encode_phys_addr(props + ncells, 0, CONFIGURATION_SPACE,
			     config->dev, 0, 0);

	ncells += pci_encode_size(props + ncells, 0);

	for (i = 0; i < num_bars; i++) {
		/* consider only bars with non-zero region size */
		if (!config->sizes[i])
			continue;

		pci_decode_pci_addr(config->regions[i],
				    &flags, &space_code, &mask);

		ncells += pci_encode_phys_addr(props + ncells,
				     flags, space_code, config->dev,
				     PCI_BASE_ADDR_0 + (i * sizeof(uint32_t)),
				     config->regions[i] & ~mask);

		/* set size */
		ncells += pci_encode_size(props + ncells, config->sizes[i]);
	}

	set_property(dev, "reg", (char *)props, ncells * sizeof(props[0]));
    ob_pci_reload_device_path(dev, config);

#if defined(CONFIG_DEBUG_PCI)
    dump_reg_property("pci_set_reg", ncells, props);
#endif
}


static void pci_set_ranges(const pci_config_t *config)
{
	phandle_t dev = get_cur_dev();
	u32 props[32];
	int ncells;
  	int i;
	uint32_t mask;
	int flags;
	int space_code;

	ncells = 0;
	for (i = 0; i < 6; i++) {
		if (!config->assigned[i] || !config->sizes[i])
			continue;

		/* child address */

		props[ncells++] = 0x00000000;

		/* parent address */

		pci_decode_pci_addr(config->assigned[i],
				    &flags, &space_code, &mask);
		ncells += pci_encode_phys_addr(props + ncells, flags, space_code,
				     config->dev, 0x10 + i * 4,
				     config->assigned[i] & ~mask);

		/* size */

		props[ncells++] = config->sizes[i];
  	}
	set_property(dev, "ranges", (char *)props, ncells * sizeof(props[0]));
}

int macio_heathrow_config_cb (const pci_config_t *config)
{
	pci_set_ranges(config);

#ifdef CONFIG_DRIVER_MACIO
        ob_macio_heathrow_init(config->path, config->assigned[0] & ~0x0000000F);
#endif
	return 0;
}

int macio_keylargo_config_cb (const pci_config_t *config)
{
        pci_set_ranges(config);

#ifdef CONFIG_DRIVER_MACIO
        ob_macio_keylargo_init(config->path, config->assigned[0] & ~0x0000000F);
#endif
        return 0;
}

int vga_config_cb (const pci_config_t *config)
{
#ifdef CONFIG_PPC
        unsigned long rom;
        uint32_t rom_size, size, bar;
        phandle_t ph;
#endif
        if (config->assigned[0] != 0x00000000) {
            setup_video();

#ifdef CONFIG_PPC
            if (config->assigned[6]) {
                    rom = pci_bus_addr_to_host_addr(MEMORY_SPACE_32,
                                                    config->assigned[6] & ~0x0000000F);
                    rom_size = config->sizes[6];

                    bar = pci_config_read32(config->dev, PCI_ROM_ADDRESS);
                    bar |= PCI_ROM_ADDRESS_ENABLE;
                    pci_config_write32(config->dev, PCI_COMMAND, bar);
                    ph = get_cur_dev();

                    if (rom_size >= 8) {
                            const char *p;

                            p = (const char *)rom;
                            if (p[0] == 'N' && p[1] == 'D' && p[2] == 'R' && p[3] == 'V') {
                                    size = *(uint32_t*)(p + 4);
                                    set_property(ph, "driver,AAPL,MacOS,PowerPC",
                                                 p + 8, size);
                            } else if (p[0] == 'J' && p[1] == 'o' &&
                                       p[2] == 'y' && p[3] == '!') {
                                    set_property(ph, "driver,AAPL,MacOS,PowerPC",
                                                 p, rom_size);
                            }
                    }
            }
#endif

            /* Currently we don't read FCode from the hardware but execute
             * it directly */
            feval("['] vga-driver-fcode 2 cells + 1 byte-load");

#ifdef CONFIG_MOL
            /* Install special words for Mac On Linux */
            molvideo_init();
#endif
        }

	return 0;
}

int ebus_config_cb(const pci_config_t *config)
{
#ifdef CONFIG_DRIVER_EBUS
    phandle_t dev = get_cur_dev();
    uint32_t props[12];
    int ncells;
    int i;
    uint32_t mask;
    int flags, space_code;
    ucell virt;
    phys_addr_t io_phys_base = 0;

    /* Serial */
    props[0] = 0x14;
    props[1] = 0x3f8;
    props[2] = 1;
    props[3] = find_dev("/pci");
    props[4] = 0x2b;
    
    /* PS2 keyboard */
    props[5] = 0x14;
    props[6] = 0x60;
    props[7] = 1;
    props[8] = find_dev("/pci");
    props[9] = 0x29;
    
    set_property(dev, "interrupt-map", (char *)props, 10 * sizeof(props[0]));

    props[0] = 0x000001ff;
    props[1] = 0xffffffff;
    props[2] = 3;
    set_property(dev, "interrupt-map-mask", (char *)props, 3 * sizeof(props[0]));

    /* Build ranges property from the BARs */
    ncells = 0;
    for (i = 0; i < 6; i++) {
        /* consider only bars with non-zero region size */
        if (!config->sizes[i])
            continue;

        pci_decode_pci_addr(config->assigned[i],
                            &flags, &space_code, &mask);

        props[ncells++] = PCI_BASE_ADDR_0 + (i * sizeof(uint32_t));
        props[ncells++] = 0x0;

        ncells += pci_encode_phys_addr(props + ncells,
                                       flags, space_code, config->dev,
                                       PCI_BASE_ADDR_0 + (i * sizeof(uint32_t)),
                                       config->assigned[i] & ~mask);

        props[ncells++] = config->sizes[i];

        /* Store base of IO space for NVRAM */
        if (io_phys_base == 0x0 && space_code == IO_SPACE) {
            io_phys_base = pci_bus_addr_to_host_addr(space_code, config->assigned[i] & ~mask);
        }
    }

    set_property(dev, "ranges", (char *)props, ncells * sizeof(props[0]));

    /*  Build eeprom node */
    fword("new-device");
    PUSH(0x14);
    fword("encode-int");
    PUSH(0x2000);
    fword("encode-int");
    fword("encode+");
    PUSH(0x2000);
    fword("encode-int");
    fword("encode+");
    push_str("reg");
    fword("property");

    push_str("mk48t59");
    fword("model");

    /* OpenSolaris (e.g. Milax) requires the RTC to be pre-mapped by the PROM */
    virt = ofmem_map_io(io_phys_base + 0x2000, 0x2000);
    PUSH(virt);
    fword("encode-int");
    push_str("address");
    fword("property");
    
    push_str("eeprom");
    fword("device-name");
    fword("finish-device");

#ifdef CONFIG_DRIVER_FLOPPY
    ob_floppy_init(config->path, "fdthree", 0x3f0ULL, 0);
#endif
#ifdef CONFIG_DRIVER_PC_SERIAL
    ob_pc_serial_init(config->path, "su", (PCI_BASE_ADDR_1 | 0ULL) << 32, 0x3f8ULL, 0);
#endif
#ifdef CONFIG_DRIVER_PC_KBD
    ob_pc_kbd_init(config->path, "kb_ps2", NULL, (PCI_BASE_ADDR_1 | 0ULL) << 32, 0x60ULL, 1, 0);
#endif
#endif
    return 0;
}

int i82378_config_cb(const pci_config_t *config)
{
#ifdef CONFIG_DRIVER_PC_SERIAL
    ob_pc_serial_init(config->path, "serial", arch->io_base, 0x3f8ULL, 0);
#endif
#ifdef CONFIG_DRIVER_PC_KBD
    ob_pc_kbd_init(config->path, "8042", NULL, arch->io_base, 0x60ULL, 0, 0);
#endif
#ifdef CONFIG_DRIVER_IDE
    ob_ide_init(config->path, 0x1f0, 0x3f6, 0x170, 0x376);
#endif

    return 0;
}

int usb_ohci_config_cb(const pci_config_t *config)
{
#ifdef CONFIG_DRIVER_USB
    ob_usb_ohci_init(config->path, 0x80000000 | config->dev);
#endif
    return 0;
}

void ob_pci_enable_bus_master(const pci_config_t *config)
{
	/* Enable bus mastering for the PCI device */
	uint16_t cmd;
	pci_addr addr = PCI_ADDR(
		PCI_BUS(config->dev), PCI_DEV(config->dev), PCI_FN(config->dev));

	cmd = pci_config_read16(addr, PCI_COMMAND);
	cmd |= PCI_COMMAND_BUS_MASTER;
	pci_config_write16(addr, PCI_COMMAND, cmd);
}

static void ob_pci_add_properties(phandle_t phandle,
                                  pci_addr addr, const pci_dev_t *pci_dev,
                                  const pci_config_t *config, int num_bars)
{
	/* cannot use get_cur_dev() path resolution since "name" and "reg"
	   properties are being changed */
	phandle_t dev=phandle;
	int status,id;
	uint16_t vendor_id, device_id;
	uint8_t rev;
	uint8_t class_prog;
	uint32_t class_code;

	vendor_id = pci_config_read16(addr, PCI_VENDOR_ID);
	device_id = pci_config_read16(addr, PCI_DEVICE_ID);
	rev = pci_config_read8(addr, PCI_REVISION_ID);
	class_prog = pci_config_read8(addr, PCI_CLASS_PROG);
	class_code = pci_config_read16(addr, PCI_CLASS_DEVICE);

    if (pci_dev) {
        /**/
        if (pci_dev->name) {
            push_str(pci_dev->name);
            fword("encode-string");
            push_str("name");
            fword("property");
        } else {
            char path[256];
            snprintf(path, sizeof(path),
                    "pci%x,%x", vendor_id, device_id);
            push_str(path);
            fword("encode-string");
            push_str("name");
            fword("property");
        }
    } else {
        PCI_DPRINTF("*** missing pci_dev\n");
    }

	/* create properties as described in 2.5 */

	set_int_property(dev, "vendor-id", vendor_id);
	set_int_property(dev, "device-id", device_id);
	set_int_property(dev, "revision-id", rev);
	set_int_property(dev, "class-code", class_code << 8 | class_prog);

	if (config->irq_pin) {
		OLDWORLD(set_int_property(dev, "AAPL,interrupts",
					  config->irq_line));
#if defined(CONFIG_SPARC64)
                set_int_property(dev, "interrupts", config->irq_pin);
#else
		NEWWORLD(set_int_property(dev, "interrupts", config->irq_pin));
#endif
	}

	set_int_property(dev, "min-grant", pci_config_read8(addr, PCI_MIN_GNT));
	set_int_property(dev, "max-latency", pci_config_read8(addr, PCI_MAX_LAT));

	status=pci_config_read16(addr, PCI_STATUS);

	set_int_property(dev, "devsel-speed",
			(status&PCI_STATUS_DEVSEL_MASK)>>10);

	if(status&PCI_STATUS_FAST_BACK)
		set_bool_property(dev, "fast-back-to-back");
	if(status&PCI_STATUS_66MHZ)
		set_bool_property(dev, "66mhz-capable");
	if(status&PCI_STATUS_UDF)
		set_bool_property(dev, "udf-supported");

	id=pci_config_read16(addr, PCI_SUBSYSTEM_VENDOR_ID);
	if(id)
		set_int_property(dev, "subsystem-vendor-id", id);
	id=pci_config_read16(addr, PCI_SUBSYSTEM_ID);
	if(id)
		set_int_property(dev, "subsystem-id", id);

	set_int_property(dev, "cache-line-size",
			pci_config_read16(addr, PCI_CACHE_LINE_SIZE));

	if (pci_dev) {
		if (pci_dev->type) {
			push_str(pci_dev->type);
			fword("encode-string");
			push_str("device_type");
			fword("property");
		}
		if (pci_dev->model) {
			push_str(pci_dev->model);
			fword("encode-string");
			push_str("model");
			fword("property");
		}
		if (pci_dev->compat)
			set_property(dev, "compatible",
				     pci_dev->compat, pci_compat_len(pci_dev));

		if (pci_dev->acells)
			set_int_property(dev, "#address-cells",
					      pci_dev->acells);
		if (pci_dev->scells)
			set_int_property(dev, "#size-cells",
					       pci_dev->scells);
		if (pci_dev->icells)
			set_int_property(dev, "#interrupt-cells",
					      pci_dev->icells);
	}

	pci_set_assigned_addresses(phandle, config, num_bars);

	if (is_apple() && is_oldworld())
		pci_set_AAPL_address(config);

	PCI_DPRINTF("\n");
}

#ifdef CONFIG_XBOX
static char pci_xbox_blacklisted (int bus, int devnum, int fn)
{
	/*
	 * The Xbox MCPX chipset is a derivative of the nForce 1
	 * chipset. It almost has the same bus layout; some devices
	 * cannot be used, because they have been removed.
	 */

	/*
	 * Devices 00:00.1 and 00:00.2 used to be memory controllers on
	 * the nForce chipset, but on the Xbox, using them will lockup
	 * the chipset.
	 */
	if ((bus == 0) && (devnum == 0) && ((fn == 1) || (fn == 2)))
		return 1;

	/*
	 * Bus 1 only contains a VGA controller at 01:00.0. When you try
	 * to probe beyond that device, you only get garbage, which
	 * could cause lockups.
	 */
	if ((bus == 1) && ((devnum != 0) || (fn != 0)))
		return 1;

	/*
	 * Bus 2 used to contain the AGP controller, but the Xbox MCPX
	 * doesn't have one. Probing it can cause lockups.
	 */
	if (bus >= 2)
		return 1;

	/*
	 * The device is not blacklisted.
	 */
	return 0;
}
#endif

static void ob_pci_configure_bar(pci_addr addr, pci_config_t *config,
                                 int reg, int config_addr,
                                 uint32_t *p_omask,
                                 unsigned long *mem_base,
                                 unsigned long *io_base)
{
        uint32_t smask, amask, size, reloc, min_align;
        unsigned long base;

        config->assigned[reg] = 0x00000000;
        config->sizes[reg] = 0x00000000;

        if ((*p_omask & 0x0000000f) == 0x4) {
                /* 64 bits memory mapping */
                PCI_DPRINTF("Skipping 64 bit BARs for %s\n", config->path);
                return;
        }

        config->regions[reg] = pci_config_read32(addr, config_addr);

        /* get region size */

        pci_config_write32(addr, config_addr, 0xffffffff);
        smask = pci_config_read32(addr, config_addr);
        if (smask == 0x00000000 || smask == 0xffffffff)
                return;

        if (smask & 0x00000001 && reg != 6) {
                /* I/O space */
                base = *io_base;
                min_align = 1 << 7;
                amask = 0x00000001;
        } else {
                /* Memory Space */
                base = *mem_base;
                min_align = 1 << 16;
                amask = 0x0000000F;
                if (reg == 6) {
                        smask |= 1; /* ROM */
                }
        }
        *p_omask = smask & amask;
        smask &= ~amask;
        size = (~smask) + 1;
        config->sizes[reg] = size;
        reloc = base;
        if (size < min_align)
                size = min_align;
        reloc = (reloc + size -1) & ~(size - 1);
        if (*io_base == base) {
                PCI_DPRINTF("changing io_base from 0x%lx to 0x%x\n",
                            *io_base, reloc + size);
                *io_base = reloc + size;
        } else {
                PCI_DPRINTF("changing mem_base from 0x%lx to 0x%x\n",
                            *mem_base, reloc + size);
                *mem_base = reloc + size;
        }
        PCI_DPRINTF("Configuring BARs for %s: reloc 0x%x omask 0x%x "
                    "io_base 0x%lx mem_base 0x%lx size 0x%x\n",
                    config->path, reloc, *p_omask, *io_base, *mem_base, size);
        pci_config_write32(addr, config_addr, reloc | *p_omask);
        config->assigned[reg] = reloc | *p_omask;
}

static void ob_pci_configure_irq(pci_addr addr, pci_config_t *config)
{
        uint8_t irq_pin, irq_line;

        irq_pin =  pci_config_read8(addr, PCI_INTERRUPT_PIN);
        if (irq_pin) {
                config->irq_pin = irq_pin;
                irq_pin = (((config->dev >> 11) & 0x1F) + irq_pin - 1) & 3;
                irq_line = arch->irqs[irq_pin];
                pci_config_write8(addr, PCI_INTERRUPT_LINE, irq_line);
                config->irq_line = irq_line;
        } else
                config->irq_line = -1;
}

static void
ob_pci_configure(pci_addr addr, pci_config_t *config, int num_regs, int rom_bar,
                 unsigned long *mem_base, unsigned long *io_base)

{
        uint32_t omask, mask;
        uint16_t cmd;
        int reg, flags, space_code;
        pci_addr config_addr;

        ob_pci_configure_irq(addr, config);

        omask = 0x00000000;
        for (reg = 0; reg < num_regs; ++reg) {
                config_addr = PCI_BASE_ADDR_0 + reg * 4;

                ob_pci_configure_bar(addr, config, reg, config_addr,
                                     &omask, mem_base,
                                     io_base);

                /* Ignore 64-bit BAR MSBs (always map in 32-bit space) */
                pci_decode_pci_addr(config->assigned[reg],
                                    &flags, &space_code, &mask);

                if (space_code == MEMORY_SPACE_64) {
                    reg++;
                }
        }

        if (rom_bar) {
                config_addr = rom_bar;
                ob_pci_configure_bar(addr, config, reg, config_addr,
                                     &omask, mem_base, io_base);
        }
        cmd = pci_config_read16(addr, PCI_COMMAND);
        cmd |= PCI_COMMAND_IO | PCI_COMMAND_MEMORY;
        pci_config_write16(addr, PCI_COMMAND, cmd);
}

static phandle_t ob_configure_pci_device(const char* parent_path,
        int *bus_num, unsigned long *mem_base, unsigned long *io_base,
        int bus, int devnum, int fn, int *p_is_multi);

static void ob_scan_pci_bus(int *bus_num, unsigned long *mem_base,
                            unsigned long *io_base, const char *path,
                            int bus)
{
	int devnum, fn, is_multi;

	PCI_DPRINTF("\nScanning bus %d at %s...\n", bus, path);

	for (devnum = 0; devnum < 32; devnum++) {
		is_multi = 0;
		for (fn = 0; fn==0 || (is_multi && fn<8); fn++) {
		    ob_configure_pci_device(path, bus_num, mem_base, io_base,
		            bus, devnum, fn, &is_multi);

		}
	}
}

#if defined(CONFIG_SPARC64)

/* Convert device/irq pin to interrupt property */
#define SUN4U_PCIAINTERRUPT(dev, irq_pin) \
            ((((dev >> 11) << 2) + irq_pin - 1) & 0x1f)
            
#define SUN4U_PCIBINTERRUPT(dev, irq_pin) \
            ((0x10 + (((dev >> 11) << 2) + irq_pin - 1)) & 0x1f)

static void ob_pci_simbaB_bus_interrupt(ucell dnode, u32 *props, int *ncells, u32 addr, u32 intno)
{
    *ncells += pci_encode_phys_addr(props + *ncells, 0, 0, addr, 0, 0);
    props[(*ncells)++] = intno;
    props[(*ncells)++] = dnode;
    props[(*ncells)++] = SUN4U_PCIBINTERRUPT(addr, intno);
}

static void ob_pci_bus_set_interrupt_map(phandle_t pcibus, phandle_t dnode,
              void (*func)(ucell dnode, u32 *props, int *ncells, u32 addr, u32 intno));

static void ob_scan_sabre_pci_bus(int *bus_num, unsigned long *mem_base,
                            unsigned long *io_base, const char *path,
                            int bus)
{
	int devnum, fn, is_multi;
	phandle_t ph;

	PCI_DPRINTF("\nScanning sabre bus %d at %s...\n", bus, path);
	
	/* Horrible sabre hack: the PCI bridge with the on-board devices
	   is located at devfn (1,1) so if we use the standard scan function
	   we end up with our ioports not mapped at io_base == 0x0 which
	   breaks many assumptions in OpenBIOS. Hence do a custom scan for
	   sabre which does things in the right order. */
	for (devnum = 0; devnum < 32; devnum++) {
		is_multi = 0;
		
		if (devnum == 1) {
			/* Force io_base/mem_base to match the pciA simba range */
			*io_base = 0x0;       /* because of arch->iobase */
			*mem_base = 0x20000000;
			
			ob_configure_pci_device(path, bus_num, mem_base, io_base,
				bus, 1, 1, &is_multi);
			
			/* Force io_base/mem_base to match the pciB simba range */
			*io_base = 0x800000;  /* because of arch->iobase */
			*mem_base = 0x60000000;
			
			ph = ob_configure_pci_device(path, bus_num, mem_base, io_base,
				bus, 1, 0, &is_multi);
			
			/* Set up pciB interrupt map */
                        ob_pci_bus_set_interrupt_map(ph, find_dev("/pci"), ob_pci_simbaB_bus_interrupt);
		} else {
			for (fn = 0; fn==0 || (is_multi && fn<8); fn++) {
				ob_configure_pci_device(path, bus_num, mem_base, io_base,
					bus, devnum, fn, &is_multi);
			}
		}
	}
}
#endif

static void ob_configure_pci_bridge(pci_addr addr,
                                    int *bus_num, unsigned long *mem_base,
                                    unsigned long *io_base,
                                    int primary_bus, pci_config_t *config)
{
    unsigned long old_mem_base, old_io_base, io_scan_limit;
    uint16_t cmd;
    phandle_t ph;

    config->primary_bus = primary_bus;
    pci_config_write8(addr, PCI_PRIMARY_BUS, config->primary_bus);

    config->secondary_bus = *bus_num;
    pci_config_write8(addr, PCI_SECONDARY_BUS, config->secondary_bus);

    config->subordinate_bus = 0xff;
    pci_config_write8(addr, PCI_SUBORDINATE_BUS, config->subordinate_bus);

    PCI_DPRINTF("scanning new pci bus %u under bridge %s\n",
            config->secondary_bus, config->path);

    /* Temporarily add bus-range property to allow the secondary bus to
       determine its bus num */
    ph = find_dev(config->path);
    set_int_property(ph, "bus-range", *bus_num);

    /* Always expose the legacy ioports on the first PCI bridge. If we
       must have legacy devices behind a PCI bridge then they must be
       on the first one discovered to ensure that the ioports will work. */
    if (primary_bus > 0 && *io_base < 0x1000) {
        *io_base = 0x0;
    }

    /* Align mem_base up to nearest MB, io_base up to nearest 4K */
    old_mem_base = (*mem_base + 0xfffff - 1) & ~(0xfffff - 1);
    *mem_base = old_mem_base;
    old_io_base = (*io_base + 0xfff - 1) & ~(0xfff - 1);
    *io_base = old_io_base;

    /* Set the base limit registers */
    pci_config_write16(addr, PCI_MEMORY_BASE, ((*mem_base >> 16) & ~(0xf)));
    pci_config_write16(addr, PCI_IO_BASE_UPPER, (*io_base >> 16));
    pci_config_write8(addr, PCI_IO_BASE, ((*io_base >> 8) & ~(0xf)));

    /* Always ensure legacy ioports are accessible during enumeration.
       Some drivers (e.g. IDE) will attempt ioport access as part of
       the configuration process, so we allow them during the secondary
       bus scan and then set the correct IO limit below. */
    io_scan_limit = *io_base + (0xffff - *io_base);
    pci_config_write16(addr, PCI_IO_LIMIT_UPPER, (io_scan_limit >> 16));
    pci_config_write8(addr, PCI_IO_LIMIT, (io_scan_limit >> 8) & ~(0xf));

    /* make pci bridge parent device, prepare for recursion */
    
#if defined(CONFIG_SPARC64)
    /* Horrible hack for sabre */
    int vid = pci_config_read16(addr, PCI_VENDOR_ID);
    int did = pci_config_read16(addr, PCI_DEVICE_ID);

    if (vid == PCI_VENDOR_ID_SUN && did == PCI_DEVICE_ID_SUN_SABRE) {
        ob_scan_sabre_pci_bus(bus_num, mem_base, io_base,
                        config->path, config->secondary_bus);       
    } else {
        ob_scan_pci_bus(bus_num, mem_base, io_base,
                        config->path, config->secondary_bus);
    }
#else
    ob_scan_pci_bus(bus_num, mem_base, io_base,
                    config->path, config->secondary_bus);
#endif
    /* bus scan updates *bus_num to last revealed pci bus number */
    config->subordinate_bus = *bus_num;
    pci_config_write8(addr, PCI_SUBORDINATE_BUS, config->subordinate_bus);

    PCI_DPRINTF("bridge %s PCI bus primary=%d secondary=%d subordinate=%d\n",
            config->path, config->primary_bus, config->secondary_bus,
            config->subordinate_bus);

    /* Align mem_base up to nearest MB, io_base up to nearest 4K */
    *mem_base = (*mem_base + 0xfffff - 1) & ~(0xfffff - 1);
    *io_base = (*io_base + 0xfff - 1) & ~(0xfff - 1);

    /* Set the limit registers */
    pci_config_write16(addr, PCI_MEMORY_LIMIT, (((*mem_base - 1) >> 16) & ~(0xf)));
    pci_config_write16(addr, PCI_IO_LIMIT_UPPER, ((*io_base - 1) >> 16));
    pci_config_write8(addr, PCI_IO_LIMIT, (((*io_base - 1) >> 8) & ~(0xf)));

    /* Disable unused address spaces */
    cmd = pci_config_read16(addr, PCI_COMMAND);
    if (*mem_base == old_mem_base) {
        pci_config_write16(addr, PCI_COMMAND, (cmd & ~PCI_COMMAND_MEMORY));
    }

    if (*io_base == old_io_base) {
        pci_config_write16(addr, PCI_COMMAND, (cmd & ~PCI_COMMAND_IO));
    }

    pci_set_bus_range(config);
}

static int ob_pci_read_identification(int bus, int devnum, int fn,
                                 int *p_vid, int *p_did,
                                 uint8_t *p_class, uint8_t *p_subclass)
{
    int vid, did;
    uint32_t ccode;
    pci_addr addr;

#ifdef CONFIG_XBOX
    if (pci_xbox_blacklisted (bus, devnum, fn))
        return;
#endif
    addr = PCI_ADDR(bus, devnum, fn);
    vid = pci_config_read16(addr, PCI_VENDOR_ID);
    did = pci_config_read16(addr, PCI_DEVICE_ID);

    if (vid==0xffff || vid==0) {
        return 0;
    }

    if (p_vid) {
        *p_vid = vid;
    }

    if (p_did) {
        *p_did = did;
    }

    ccode = pci_config_read16(addr, PCI_CLASS_DEVICE);

    if (p_class) {
        *p_class = ccode >> 8;
    }

    if (p_subclass) {
        *p_subclass = ccode;
    }

    return 1;
}

static phandle_t ob_configure_pci_device(const char* parent_path,
        int *bus_num, unsigned long *mem_base, unsigned long *io_base,
        int bus, int devnum, int fn, int *p_is_multi)
{
    int vid, did;
    unsigned int htype;
    pci_addr addr;
    pci_config_t config = {};
        const pci_dev_t *pci_dev;
    uint8_t class, subclass, iface;
    int num_bars, rom_bar;

    phandle_t phandle = 0;
    int is_host_bridge = 0;

    if (!ob_pci_read_identification(bus, devnum, fn, &vid, &did, &class, &subclass)) {
        return 0;
    }

    addr = PCI_ADDR(bus, devnum, fn);
    iface = pci_config_read8(addr, PCI_CLASS_PROG);

    pci_dev = pci_find_device(class, subclass, iface,
                  vid, did);

    PCI_DPRINTF("%x:%x.%x - %x:%x - ", bus, devnum, fn,
            vid, did);

    htype = pci_config_read8(addr, PCI_HEADER_TYPE);

    if (fn == 0) {
        if (p_is_multi) {
            *p_is_multi = htype & 0x80;
        }
    }

    /* stop adding host bridge accessible from it's primary bus
       PCI host bridge is to be added by host code
    */
    if (class == PCI_BASE_CLASS_BRIDGE &&
            subclass == PCI_SUBCLASS_BRIDGE_HOST) {
        is_host_bridge = 1;
    }

    if (is_host_bridge) {
        /* reuse device tree node */
        PCI_DPRINTF("host bridge found - ");
        snprintf(config.path, sizeof(config.path),
                "%s", parent_path);
    } else if (pci_dev == NULL || pci_dev->name == NULL) {
        snprintf(config.path, sizeof(config.path),
                "%s/pci%x,%x", parent_path, vid, did);
    }
    else {
        snprintf(config.path, sizeof(config.path),
                "%s/%s", parent_path, pci_dev->name);
    }

    PCI_DPRINTF("%s - ", config.path);

    config.dev = addr & 0x00FFFFFF;

    switch (class) {
    case PCI_BASE_CLASS_BRIDGE:
        if (subclass != PCI_SUBCLASS_BRIDGE_HOST) {
            REGISTER_NAMED_NODE_PHANDLE(ob_pci_bridge_node, config.path, phandle);
        }
        break;
    default:
        REGISTER_NAMED_NODE_PHANDLE(ob_pci_empty_node, config.path, phandle);
        break;
    }

    if (is_host_bridge) {
        phandle = find_dev(config.path);

        if (get_property(phandle, "vendor-id", NULL)) {
            PCI_DPRINTF("host bridge already configured\n");
            return 0;
        }
    }

    activate_dev(phandle);

    if (htype & PCI_HEADER_TYPE_BRIDGE) {
        num_bars = 2;
        rom_bar  = PCI_ROM_ADDRESS1;
    } else {
        num_bars = 6;
        rom_bar  = PCI_ROM_ADDRESS;
    }

    ob_pci_configure(addr, &config, num_bars, rom_bar,
                     mem_base, io_base);

    ob_pci_add_properties(phandle, addr, pci_dev, &config, num_bars);

    if (!is_host_bridge) {
        pci_set_reg(phandle, &config, num_bars);
    }

    /* call device-specific configuration callback */
    if (pci_dev && pci_dev->config_cb) {
        //activate_device(config.path);
        pci_dev->config_cb(&config);
    }

    /* if devices haven't supplied open/close words then supply them with simple defaults */
    if (!find_package_method("open", phandle) && !find_package_method("close", phandle)) {
        REGISTER_NODE_METHODS(ob_pci_simple_node, config.path);
    }

    /* device is configured so we may move it out of scope */
    device_end();

    /* scan bus behind bridge device */
    //if (htype & PCI_HEADER_TYPE_BRIDGE && class == PCI_BASE_CLASS_BRIDGE) {
    if ( class == PCI_BASE_CLASS_BRIDGE &&
            ( subclass == PCI_SUBCLASS_BRIDGE_PCI ||
              subclass == PCI_SUBCLASS_BRIDGE_HOST ) ) {

        if (subclass == PCI_SUBCLASS_BRIDGE_PCI) {
            /* reserve next pci bus number for this PCI bridge */
            ++(*bus_num);
        }

        ob_configure_pci_bridge(addr, bus_num, mem_base, io_base, bus, &config);
    }

    return phandle;
}

static void ob_pci_set_available(phandle_t host, unsigned long mem_base, unsigned long io_base)
{
    /* Create an available property for both memory and IO space */
    uint32_t props[10];
    int ncells;

    ncells = 0;
    ncells += pci_encode_phys_addr(props + ncells, 0, MEMORY_SPACE_32, 0, 0, mem_base);
    ncells += pci_encode_size(props + ncells, arch->mem_len - mem_base);
    ncells += pci_encode_phys_addr(props + ncells, 0, IO_SPACE, 0, 0, io_base);
    ncells += pci_encode_size(props + ncells, arch->io_len - io_base);

    set_property(host, "available", (char *)props, ncells * sizeof(props[0]));
}

#if defined(CONFIG_PPC)
static phandle_t ob_pci_host_set_interrupt_map(phandle_t host)
{
    /* Set the host bridge interrupt map, returning the phandle
       of the interrupt controller */
    phandle_t dnode, target_node;
    char *path, buf[256];

    /* Oldworld macs do interrupt maps differently */
    if (!is_newworld())
        return 0;

    PCI_DPRINTF("setting up interrupt map for host %x\n", host);
    dnode = dt_iterate_type(0, "open-pic");
    path = get_path_from_ph(host);
    if (dnode && path) {
        /* patch in openpic interrupt-parent properties */
        snprintf(buf, sizeof(buf), "%s/mac-io", path);
        target_node = find_dev(buf);
        set_int_property(target_node, "interrupt-parent", dnode);

        snprintf(buf, sizeof(buf), "%s/mac-io/escc/ch-a", path);
        target_node = find_dev(buf);
        set_int_property(target_node, "interrupt-parent", dnode);

        snprintf(buf, sizeof(buf), "%s/mac-io/escc/ch-b", path);
        target_node = find_dev(buf);
        set_int_property(target_node, "interrupt-parent", dnode);

        snprintf(buf, sizeof(buf), "%s/mac-io/escc-legacy/ch-a", path);
        target_node = find_dev(buf);
        set_int_property(target_node, "interrupt-parent", dnode);

        snprintf(buf, sizeof(buf), "%s/mac-io/escc-legacy/ch-b", path);
        target_node = find_dev(buf);
        set_int_property(target_node, "interrupt-parent", dnode);

        /* QEMU only emulates 2 of the 3 ata buses currently */
        /* On a new world Mac these are not numbered but named by the
         * ATA version they support. Thus we have: ata-3, ata-3, ata-4
         * On g3beige they all called just ide.
         * We take 2 x ata-3 buses which seems to work for
         * at least the clients we care about */
        snprintf(buf, sizeof(buf), "%s/mac-io/ata-3@20000", path);
        target_node = find_dev(buf);
        set_int_property(target_node, "interrupt-parent", dnode);

        snprintf(buf, sizeof(buf), "%s/mac-io/ata-3@21000", path);
        target_node = find_dev(buf);
        set_int_property(target_node, "interrupt-parent", dnode);

        snprintf(buf, sizeof(buf), "%s/mac-io/via-cuda", path);
        target_node = find_dev(buf);
        set_int_property(target_node, "interrupt-parent", dnode);

        target_node = find_dev(path);
        set_int_property(target_node, "interrupt-parent", dnode);

        return dnode;
    }

    return host;
}

static void ob_pci_host_bus_interrupt(ucell dnode, u32 *props, int *ncells, u32 addr, u32 intno)
{
    *ncells += pci_encode_phys_addr(props + *ncells, 0, 0, addr, 0, 0);
    props[(*ncells)++] = intno;
    props[(*ncells)++] = dnode;
    props[(*ncells)++] = arch->irqs[((intno - 1) + (addr >> 11)) & 3];
    props[(*ncells)++] = 1;
}

#elif defined(CONFIG_SPARC64)
            
static phandle_t ob_pci_host_set_interrupt_map(phandle_t host)
{
    return host;
}
           
static void ob_pci_host_bus_interrupt(ucell dnode, u32 *props, int *ncells, u32 addr, u32 intno)
{
    /* Note: this is invalid when the Simba bridges are in place
       as it is impossible to physically plug hardware into the PCI
       root bus (and no hardware support for mapping these interrupts
       either) */
    return;
}

#else

static phandle_t ob_pci_host_set_interrupt_map(phandle_t host)
{
    return host;
}

static void ob_pci_host_bus_interrupt(ucell dnode, u32 *props, int *ncells, u32 addr, u32 intno)
{
    return;
}
#endif

static void ob_pci_bus_set_interrupt_map(phandle_t pcibus, phandle_t dnode,
              void (*func)(ucell dnode, u32 *props, int *ncells, u32 addr, u32 intno))
{
    /* Set interrupt-map for PCI devices with an interrupt pin present */
    phandle_t pci_childnode = 0;
    u32 props[128], intno;
    int i, ncells = 0, len;
    u32 *val, addr;
    char *reg;
    
    /* If no destination node specified, do nothing */
    if (dnode == 0) {
        return;
    }

    PUSH(pcibus);
    fword("child");
    pci_childnode = POP();
    while (pci_childnode) {
        intno = get_int_property(pci_childnode, "interrupts", &len);
        if (len && intno) {
            reg = get_property(pci_childnode, "reg", &len);
            if (len && reg) {
                val = (u32 *)reg;

                for (i = 0; i < (len / sizeof(u32)); i += 5) {
                    addr = val[i];

                    /* Device address is in 1st 32-bit word of encoded PCI address for config space */
                    if ((addr & PCI_RANGE_TYPE_MASK) == PCI_RANGE_CONFIG) {
                        (*func)(dnode, props, &ncells, addr, intno);
                    }
                }
            }
        }

        PUSH(pci_childnode);
        fword("peer");
        pci_childnode = POP();
    }

    if (ncells) {
        set_property(pcibus, "interrupt-map", (char *)props, ncells * sizeof(props[0]));

        props[0] = 0x00fff800;
        props[1] = 0x0;
        props[2] = 0x0;
        props[3] = 0x7;
        set_property(pcibus, "interrupt-map-mask", (char *)props, 4 * sizeof(props[0]));
    }
}

int ob_pci_init(void)
{
    int bus, devnum, fn;
    uint8_t class, subclass;
    unsigned long mem_base, io_base;

    pci_config_t config = {}; /* host bridge */
    phandle_t phandle_host = 0, intc;

    PCI_DPRINTF("Initializing PCI host bridge...\n");

    activate_device("/");

    /* Find all PCI bridges */

    mem_base = arch->pci_mem_base;
    /* I/O ports under 0x400 are used by devices mapped at fixed
       location. */
    io_base = 0x400;

    bus = 0;

    for (devnum = 0; devnum < 32; devnum++) {
        /* scan only fn 0 */
        fn = 0;

        if (!ob_pci_read_identification(bus, devnum, fn,
                                        0, 0, &class, &subclass)) {
            continue;
        }

        if (class != PCI_BASE_CLASS_BRIDGE || subclass != PCI_SUBCLASS_BRIDGE_HOST) {
            continue;
        }

        /* create root node for host PCI bridge */

        /* configure  */
        snprintf(config.path, sizeof(config.path), "/pci");

        REGISTER_NAMED_NODE_PHANDLE(ob_pci_bus_node, config.path, phandle_host);

        pci_host_set_reg(phandle_host);

        /* update device path after changing "reg" property */
        ob_pci_reload_device_path(phandle_host, &config);

        ob_configure_pci_device(config.path, &bus, &mem_base, &io_base,
                bus, devnum, fn, 0);

        /* we expect single host PCI bridge
           but this may be machine-specific */
        break;
    }

    /* create available attributes for the PCI bridge */
    ob_pci_set_available(phandle_host, mem_base, io_base);

    /* configure the host bridge interrupt map */
    intc = ob_pci_host_set_interrupt_map(phandle_host);
    ob_pci_bus_set_interrupt_map(phandle_host, intc, ob_pci_host_bus_interrupt);

    device_end();

    return 0;
}
