/*
 *   OpenBIOS pci driver
 *
 *   This driver is compliant to the
 *   PCI bus binding to IEEE 1275-1994 Rev 2.1
 *
 *   (C) 2004 Stefan Reinauer <stepan@openbios.org>
 *   (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_simple_node, INSTALL_OPEN, 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

enum {
	CONFIGURATION_SPACE = 0,
	IO_SPACE = 1,
	MEMORY_SPACE_32 = 2,
	MEMORY_SPACE_64 = 3,
};

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 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 = 0;		/* no information */
	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);

	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);
}

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

static void
ob_pci_map_in(int *idx)
{
	phys_addr_t phys;
	uint32_t ba;
	ucell size, virt, tmp;
	int space;

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

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

	/* Get the space from the pci-addr.hi */
	space = ((tmp & PCI_RANGE_TYPE_MASK) >> 24);

	phys = pci_bus_addr_to_host_addr(space, ba);

#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

	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_map_in		},
};

NODE_METHODS(ob_pci_simple_node) = {
	{ NULL,			ob_pci_initialize	},
	{ "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;

	ncells = 0;
	
#ifdef CONFIG_SPARC64
        /* While configuration space isn't mentioned in the IEEE-1275 PCI
           bindings, it appears in the PCI host bridge ranges property in
           real device trees. Hence we disable this range for all host
           bridges except for SPARC, particularly as it causes Darwin/OS X
           to incorrectly calculated PCI memory space ranges on PPC. */
	ncells += pci_encode_phys_addr(props + ncells, 0, CONFIGURATION_SPACE,
                     config->dev, 0, 0);
        ncells += host_encode_phys_addr(props + ncells, arch->cfg_addr);
        ncells += pci_encode_size(props + ncells, arch->cfg_len);
#endif

	if (arch->io_base) {
	    ncells += pci_encode_phys_addr(props + ncells, 0, IO_SPACE,
				     config->dev, 0, 0);
        ncells += host_encode_phys_addr(props + ncells, arch->io_base);
        ncells += pci_encode_size(props + ncells, arch->io_len);
	}
	if (arch->rbase) {
	    ncells += pci_encode_phys_addr(props + ncells, 0, MEMORY_SPACE_32,
				     config->dev, 0, 0);
        ncells += host_encode_phys_addr(props + ncells, arch->rbase);
        ncells += pci_encode_size(props + ncells, arch->rlen);
	}
	if (arch->pci_mem_base) {
	    ncells += pci_encode_phys_addr(props + ncells, 0, MEMORY_SPACE_32,
				     config->dev, 0, arch->pci_mem_base);
        ncells += host_encode_phys_addr(props + ncells, arch->host_pci_base +
				     arch->pci_mem_base);
	ncells += pci_encode_size(props + ncells, arch->mem_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];

        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 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;
}

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;
	}
}

/*
 * "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 */

    PUSH(phandle);
    fword("get-package-path");
    char *new_path = pop_fstr_copy();
    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)
{
        unsigned long rom;
        uint32_t rom_size, size, mask;
        int flags, space_code;
        phandle_t ph;

        if (config->assigned[0] != 0x00000000) {
            setup_video();

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

            rom = pci_bus_addr_to_host_addr(space_code,
                                            config->assigned[1] & ~0x0000000F);

            rom_size = config->sizes[1];

            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);
                }
            }

            /* 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;

    props[0] = 0x14;
    props[1] = 0x3f8;
    props[2] = 1;
    props[3] = find_dev("/");
    props[4] = 0x2b;
    set_property(dev, "interrupt-map", (char *)props, 5 * 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];
    }

    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");

    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", (PCI_BASE_ADDR_1 | 0ULL) << 32, 0x60ULL, 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", arch->io_base, 0x60ULL, 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;
}

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()) {
		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;
        uint16_t cmd;
        int reg;
        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);
        }

        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 void 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);

		}
	}
}

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)
{
    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);

    /* make pci bridge parent device, prepare for recursion */

    ob_scan_pci_bus(bus_num, mem_base, io_base,
                    config->path, config->secondary_bus);

    /* 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);

    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 void 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;
    }

    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_bus_node, config.path, phandle);
        }
        break;
    case PCI_CLASS_DISPLAY:
	REGISTER_NAMED_NODE_PHANDLE(ob_pci_empty_node, config.path, phandle);
	break;
    default:
        REGISTER_NAMED_NODE_PHANDLE(ob_pci_simple_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;
        }
    }

    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);
    }

    /* 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);
    }
}

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]));
}

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

static void ob_pci_host_set_interrupt_map(phandle_t host)
{
    phandle_t dnode = 0, pci_childnode = 0;
    u32 props[128], intno;
    int i, ncells, len;
    u32 *val, addr;
    char *reg;

#if defined(CONFIG_PPC)
    phandle_t target_node;

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

    dnode = dt_iterate_type(0, "open-pic");
    if (dnode) {
        /* patch in openpic interrupt-parent properties */
        target_node = find_dev("/pci/mac-io");
        set_int_property(target_node, "interrupt-parent", dnode);

        target_node = find_dev("/pci/mac-io/escc/ch-a");
        set_int_property(target_node, "interrupt-parent", dnode);

        target_node = find_dev("/pci/mac-io/escc/ch-b");
        set_int_property(target_node, "interrupt-parent", dnode);

        target_node = find_dev("/pci/mac-io/escc-legacy/ch-a");
        set_int_property(target_node, "interrupt-parent", dnode);

        target_node = find_dev("/pci/mac-io/escc-legacy/ch-b");
        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 */
        target_node = find_dev("/pci/mac-io/ata-3@20000");
        set_int_property(target_node, "interrupt-parent", dnode);

        target_node = find_dev("/pci/mac-io/ata-3@21000");
        set_int_property(target_node, "interrupt-parent", dnode);

        target_node = find_dev("/pci/mac-io/via-cuda");
        set_int_property(target_node, "interrupt-parent", dnode);

        target_node = find_dev("/pci");
        set_int_property(target_node, "interrupt-parent", dnode);
    }
#else
    /* PCI host bridge is the default interrupt controller */
    dnode = host;
#endif

    /* Set interrupt-map for PCI devices with an interrupt pin present */
    ncells = 0;

    PUSH(host);
    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) {
#if defined(CONFIG_SPARC64)
                        ncells += pci_encode_phys_addr(props + ncells, 0, 0, addr, 0, 0);
                        props[ncells++] = intno;
                        props[ncells++] = dnode;
                        props[ncells++] = SUN4U_INTERRUPT(addr, intno);
#elif defined(CONFIG_PPC)
                        ncells += pci_encode_phys_addr(props + ncells, 0, 0, addr, 0, 0);
                        props[ncells++] = intno;
                        props[ncells++] = dnode;
                        props[ncells++] = arch->irqs[intno - 1];
                        props[ncells++] = 3;
#else
                        /* Keep compiler quiet */
                        dnode = dnode;
#endif
                    }
                }
            }
        }

        PUSH(pci_childnode);
        fword("peer");
        pci_childnode = POP();
    }
    set_property(host, "interrupt-map", (char *)props, ncells * sizeof(props[0]));

    props[0] = 0x0000f800;
    props[1] = 0x0;
    props[2] = 0x0;
    props[3] = 0x7;
    set_property(host, "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;

    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 */
    ob_pci_host_set_interrupt_map(phandle_host);

    device_end();

    return 0;
}
