diff options
Diffstat (limited to 'roms/openbios/drivers/pci.c')
-rw-r--r-- | roms/openbios/drivers/pci.c | 2137 |
1 files changed, 2137 insertions, 0 deletions
diff --git a/roms/openbios/drivers/pci.c b/roms/openbios/drivers/pci.c new file mode 100644 index 000000000..f30e427ba --- /dev/null +++ b/roms/openbios/drivers/pci.c @@ -0,0 +1,2137 @@ +/* + * 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 "macio.h" +#endif +#ifdef CONFIG_DRIVER_USB +#include "drivers/usb.h" +#endif +#ifdef CONFIG_DRIVER_VIRTIO_BLK +#include "virtio.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) ); + +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) +{ +} + +/* ( 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) { +#if defined(CONFIG_OFMEM) + ofmem_unmap(virt, size); +#endif +} + +/* ( 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); +} + +static void +ob_pci_dma_alloc(int *idx) +{ + call_parent_method("dma-alloc"); +} + +static void +ob_pci_dma_free(int *idx) +{ + call_parent_method("dma-free"); +} + +static void +ob_pci_dma_map_in(int *idx) +{ + call_parent_method("dma-map-in"); +} + +static void +ob_pci_dma_map_out(int *idx) +{ + call_parent_method("dma-map-out"); +} + +static void +ob_pci_dma_sync(int *idx) +{ + call_parent_method("dma-sync"); +} + +NODE_METHODS(ob_pci_bus_node) = { + { "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 }, + { "dma-alloc", ob_pci_dma_alloc }, + { "dma-free", ob_pci_dma_free }, + { "dma-map-in", ob_pci_dma_map_in }, + { "dma-map-out", ob_pci_dma_map_out }, + { "dma-sync", ob_pci_dma_sync }, +}; + +/* ( 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) = { + { "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 }, + { "dma-alloc", ob_pci_dma_alloc }, + { "dma-free", ob_pci_dma_free }, + { "dma-map-in", ob_pci_dma_map_in }, + { "dma-map-out", ob_pci_dma_map_out }, + { "dma-sync", ob_pci_dma_sync }, +}; + +NODE_METHODS(ob_pci_simple_node) = { + { "open", ob_pci_open }, + { "close", ob_pci_close }, +}; + +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 ob_pci_reload_device_path(phandle_t phandle, pci_config_t *config); + +static void pci_host_set_reg(phandle_t phandle, pci_config_t *config) +{ + 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])); + + ob_pci_reload_device_path(dev, config); + +#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) +{ + 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; +} + +int virtio_blk_config_cb(const pci_config_t *config) +{ +#ifdef CONFIG_DRIVER_VIRTIO_BLK + pci_addr addr; + uint8_t idx, cap_idx, cap_vndr; + uint8_t cfg_type, bar; + uint16_t status; + uint32_t offset, notify_mult = 0; + uint64_t common_cfg = 0, device_cfg = 0, notify_base = 0; + + addr = PCI_ADDR( + PCI_BUS(config->dev), + PCI_DEV(config->dev), + PCI_FN(config->dev)); + + idx = (uint8_t)(pci_config_read16(addr, PCI_DEVICE_ID) & 0xff) - 1; + + /* Check PCI capabilties: if they don't exist then we're certainly not + a 1.0 device */ + status = pci_config_read16(addr, PCI_STATUS); + if (!(status & PCI_STATUS_CAP_LIST)) { + return 0; + } + + /* Locate VIRTIO_PCI_CAP_COMMON_CFG and VIRTIO_PCI_CAP_DEVICE_CFG */ + cap_idx = pci_config_read8(addr, PCI_CAPABILITY_LIST); + while ((cap_vndr = pci_config_read8(addr, cap_idx)) != 0) { + if (cap_vndr == PCI_CAP_ID_VNDR) { + cfg_type = pci_config_read8(addr, cap_idx + 0x3); + bar = pci_config_read8(addr, cap_idx + 0x4); + offset = pci_config_read32(addr, cap_idx + 0x8); + + switch (cfg_type) { + case VIRTIO_PCI_CAP_COMMON_CFG: + common_cfg = arch->host_pci_base + (config->assigned[bar] & ~0x0000000F) + offset; + break; + case VIRTIO_PCI_CAP_NOTIFY_CFG: + notify_base = arch->host_pci_base + (config->assigned[bar] & ~0x0000000F) + offset; + notify_mult = pci_config_read32(addr, cap_idx + 16); + break; + case VIRTIO_PCI_CAP_DEVICE_CFG: + device_cfg = arch->host_pci_base + (config->assigned[bar] & ~0x0000000F) + offset; + break; + } + } + + cap_idx = pci_config_read8(addr, cap_idx + 1); + } + + /* If we didn't find the required configuration then exit */ + if (common_cfg == 0 || device_cfg == 0 || notify_base == 0) { + return 0; + } + + /* Enable bus mastering to ensure vring processing will run. */ + ob_pci_enable_bus_master(config); + + ob_virtio_init(config->path, "virtio-blk", common_cfg, device_cfg, + notify_base, notify_mult, idx); +#endif + 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"); + + /* Build power node */ + fword("new-device"); + PUSH(0x14); + fword("encode-int"); + PUSH(0x7240); + fword("encode-int"); + fword("encode+"); + PUSH(0x4); + fword("encode-int"); + fword("encode+"); + push_str("reg"); + fword("property"); + + PUSH(0); + PUSH(0); + push_str("button"); + fword("property"); + + PUSH(1); + fword("encode-int"); + push_str("interrupts"); + fword("property"); + + /* Map the power registers so we can use them */ + virt = ofmem_map_io(io_phys_base + 0x7240, 0x4); + PUSH(virt); + fword("encode-int"); + push_str("address"); + fword("property"); + + push_str("power"); + 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, "keyboard", 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 + pci_addr addr = PCI_ADDR( + PCI_BUS(config->dev), PCI_DEV(config->dev), PCI_FN(config->dev)); + + ob_usb_ohci_init(config->path, addr); +#endif + return 0; +} + +int lsi53c810_config_cb(const pci_config_t *config) +{ +#ifdef CONFIG_DRIVER_LSI_53C810 + uint64_t mmio, ram; + + /* Enable PCI bus mastering */ + ob_pci_enable_bus_master(config); + + /* Map PCI memory BAR 1: LSI MMIO */ + mmio = ob_pci_map(config->assigned[1], 0x400); + + /* Map PCI memory BAR 2: LSI RAM */ + ram = ob_pci_map(config->assigned[2], 0x400); + + ob_lsi_init(config->path, mmio, ram); +#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; + char path[256]; + + 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); + + /* Default path if we don't match anything */ + snprintf(path, sizeof(path), "pci%x,%x", vendor_id, device_id); + + if (pci_dev) { + /**/ + if (pci_dev->name) { + push_str(pci_dev->name); + fword("device-name"); + } else { + push_str(path); + fword("device-name"); + } + } else { + PCI_DPRINTF("*** missing pci_dev\n"); + push_str(path); + fword("device-name"); + } + + /* 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) { + if (is_oldworld()) { + set_int_property(dev, "AAPL,interrupts", config->irq_line); + } else { + set_int_property(dev, "interrupts", config->irq_pin); + } + } + + 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("device-type"); + } + if (pci_dev->model) { + push_str(pci_dev->model); + fword("model"); + } + 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_ignore_device (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; + + 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; + 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_ignore_device (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; + uint32_t props[3]; + char *unit_str; + + phandle_t phandle = 0, t; + 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; + } + } + + if (class == PCI_BASE_CLASS_BRIDGE && + subclass == PCI_SUBCLASS_BRIDGE_HOST) { + is_host_bridge = 1; + } + + /* stop adding host bridge accessible from it's primary bus + PCI host bridge is to be added by host code + */ + if (is_host_bridge) { + /* reuse device tree node */ + PCI_DPRINTF("host bridge found - "); + + t = find_dev(parent_path); + if (get_property(t, "vendor-id", NULL)) { + PCI_DPRINTF("host bridge already configured\n"); + return 0; + } + } 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); + } + + fword("new-device"); + + PCI_DPRINTF("%s - ", config.path); + + config.dev = addr & 0x00FFFFFF; + + if (!is_host_bridge) { + /* Get encoded address for set-args */ + pci_encode_phys_addr(props, 0, CONFIGURATION_SPACE, config.dev, 0, 0); + PUSH(props[2]); + PUSH(props[1]); + PUSH(props[0]); + call_parent_method("encode-unit"); + + unit_str = pop_fstr_copy(); + + /* Call set-args to set up my-space and my-address */ + PUSH(0); + PUSH(0); + push_str(unit_str); + fword("set-args"); + + free(unit_str); + } + + phandle = get_cur_dev(); + + switch (class) { + case PCI_BASE_CLASS_BRIDGE: + if (subclass != PCI_SUBCLASS_BRIDGE_HOST) { + BIND_NODE_METHODS(phandle, ob_pci_bridge_node); + } else { + BIND_NODE_METHODS(phandle, ob_pci_bus_node); + } + break; + } + + 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); + } else { + pci_host_set_reg(phandle, &config); + } + + /* 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)) { + BIND_NODE_METHODS(phandle, ob_pci_simple_node); + } + + /* 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); + } + + fword("finish-device"); + + 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_oldworld()) { + 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); + + snprintf(buf, sizeof(buf), "%s/mac-io/via-pmu", path); + target_node = find_dev(buf); + if (target_node) { + set_int_property(target_node, "interrupt-parent", dnode); + } + + snprintf(buf, sizeof(buf), "%s/mac-io/gpio/extint-gpio1", path); + target_node = find_dev(buf); + if (target_node) { + set_int_property(target_node, "interrupt-parent", dnode); + } + + snprintf(buf, sizeof(buf), "%s/mac-io/gpio/programmer-switch", path); + target_node = find_dev(buf); + if (target_node) { + 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; + if (!is_apple() && (PCI_DEV(addr) == 1 && PCI_FN(addr) == 0)) { + /* On PReP machine the LSI SCSI has fixed routing to IRQ 13 */ + props[(*ncells)++] = 13; + } else { + 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"); + + /* 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 */ + strncpy(config.path, "", sizeof(config.path)); + + phandle_host = 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); + + return 0; +} |