2153 lines
58 KiB
C
2153 lines
58 KiB
C
/*
|
|
* 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_blacklisted (int bus, int devnum, int fn)
|
|
{
|
|
/*
|
|
* The Xbox MCPX chipset is a derivative of the nForce 1
|
|
* chipset. It almost has the same bus layout; some devices
|
|
* cannot be used, because they have been removed.
|
|
*/
|
|
|
|
/*
|
|
* Devices 00:00.1 and 00:00.2 used to be memory controllers on
|
|
* the nForce chipset, but on the Xbox, using them will lockup
|
|
* the chipset.
|
|
*/
|
|
if ((bus == 0) && (devnum == 0) && ((fn == 1) || (fn == 2)))
|
|
return 1;
|
|
|
|
/*
|
|
* Bus 1 only contains a VGA controller at 01:00.0. When you try
|
|
* to probe beyond that device, you only get garbage, which
|
|
* could cause lockups.
|
|
*/
|
|
if ((bus == 1) && ((devnum != 0) || (fn != 0)))
|
|
return 1;
|
|
|
|
/*
|
|
* Bus 2 used to contain the AGP controller, but the Xbox MCPX
|
|
* doesn't have one. Probing it can cause lockups.
|
|
*/
|
|
if (bus >= 2)
|
|
return 1;
|
|
|
|
/*
|
|
* The device is not blacklisted.
|
|
*/
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static void ob_pci_configure_bar(pci_addr addr, pci_config_t *config,
|
|
int reg, int config_addr,
|
|
uint32_t *p_omask,
|
|
unsigned long *mem_base,
|
|
unsigned long *io_base)
|
|
{
|
|
uint32_t smask, amask, size, reloc, min_align;
|
|
unsigned long base;
|
|
|
|
config->assigned[reg] = 0x00000000;
|
|
config->sizes[reg] = 0x00000000;
|
|
|
|
if ((*p_omask & 0x0000000f) == 0x4) {
|
|
/* 64 bits memory mapping */
|
|
PCI_DPRINTF("Skipping 64 bit BARs for %s\n", config->path);
|
|
return;
|
|
}
|
|
|
|
config->regions[reg] = pci_config_read32(addr, config_addr);
|
|
|
|
/* get region size */
|
|
|
|
pci_config_write32(addr, config_addr, 0xffffffff);
|
|
smask = pci_config_read32(addr, config_addr);
|
|
if (smask == 0x00000000 || smask == 0xffffffff)
|
|
return;
|
|
|
|
if (smask & 0x00000001 && reg != 6) {
|
|
/* I/O space */
|
|
base = *io_base;
|
|
min_align = 1 << 7;
|
|
amask = 0x00000001;
|
|
} else {
|
|
/* Memory Space */
|
|
base = *mem_base;
|
|
min_align = 1 << 16;
|
|
amask = 0x0000000F;
|
|
if (reg == 6) {
|
|
smask |= 1; /* ROM */
|
|
}
|
|
}
|
|
*p_omask = smask & amask;
|
|
smask &= ~amask;
|
|
size = (~smask) + 1;
|
|
config->sizes[reg] = size;
|
|
reloc = base;
|
|
if (size < min_align)
|
|
size = min_align;
|
|
reloc = (reloc + size -1) & ~(size - 1);
|
|
if (*io_base == base) {
|
|
PCI_DPRINTF("changing io_base from 0x%lx to 0x%x\n",
|
|
*io_base, reloc + size);
|
|
*io_base = reloc + size;
|
|
} else {
|
|
PCI_DPRINTF("changing mem_base from 0x%lx to 0x%x\n",
|
|
*mem_base, reloc + size);
|
|
*mem_base = reloc + size;
|
|
}
|
|
PCI_DPRINTF("Configuring BARs for %s: reloc 0x%x omask 0x%x "
|
|
"io_base 0x%lx mem_base 0x%lx size 0x%x\n",
|
|
config->path, reloc, *p_omask, *io_base, *mem_base, size);
|
|
pci_config_write32(addr, config_addr, reloc | *p_omask);
|
|
config->assigned[reg] = reloc | *p_omask;
|
|
}
|
|
|
|
static void ob_pci_configure_irq(pci_addr addr, pci_config_t *config)
|
|
{
|
|
uint8_t irq_pin, irq_line;
|
|
|
|
irq_pin = pci_config_read8(addr, PCI_INTERRUPT_PIN);
|
|
if (irq_pin) {
|
|
config->irq_pin = irq_pin;
|
|
irq_pin = (((config->dev >> 11) & 0x1F) + irq_pin - 1) & 3;
|
|
irq_line = arch->irqs[irq_pin];
|
|
pci_config_write8(addr, PCI_INTERRUPT_LINE, irq_line);
|
|
config->irq_line = irq_line;
|
|
} else
|
|
config->irq_line = -1;
|
|
}
|
|
|
|
static void
|
|
ob_pci_configure(pci_addr addr, pci_config_t *config, int num_regs, int rom_bar,
|
|
unsigned long *mem_base, unsigned long *io_base)
|
|
|
|
{
|
|
uint32_t omask, mask;
|
|
uint16_t cmd;
|
|
int reg, flags, space_code;
|
|
pci_addr config_addr;
|
|
|
|
ob_pci_configure_irq(addr, config);
|
|
|
|
omask = 0x00000000;
|
|
for (reg = 0; reg < num_regs; ++reg) {
|
|
config_addr = PCI_BASE_ADDR_0 + reg * 4;
|
|
|
|
ob_pci_configure_bar(addr, config, reg, config_addr,
|
|
&omask, mem_base,
|
|
io_base);
|
|
|
|
/* Ignore 64-bit BAR MSBs (always map in 32-bit space) */
|
|
pci_decode_pci_addr(config->assigned[reg],
|
|
&flags, &space_code, &mask);
|
|
|
|
if (space_code == MEMORY_SPACE_64) {
|
|
reg++;
|
|
}
|
|
}
|
|
|
|
if (rom_bar) {
|
|
config_addr = rom_bar;
|
|
ob_pci_configure_bar(addr, config, reg, config_addr,
|
|
&omask, mem_base, io_base);
|
|
}
|
|
cmd = pci_config_read16(addr, PCI_COMMAND);
|
|
cmd |= PCI_COMMAND_IO | PCI_COMMAND_MEMORY;
|
|
pci_config_write16(addr, PCI_COMMAND, cmd);
|
|
}
|
|
|
|
static phandle_t ob_configure_pci_device(const char* parent_path,
|
|
int *bus_num, unsigned long *mem_base, unsigned long *io_base,
|
|
int bus, int devnum, int fn, int *p_is_multi);
|
|
|
|
static void ob_scan_pci_bus(int *bus_num, unsigned long *mem_base,
|
|
unsigned long *io_base, const char *path,
|
|
int bus)
|
|
{
|
|
int devnum, fn, is_multi;
|
|
|
|
PCI_DPRINTF("\nScanning bus %d at %s...\n", bus, path);
|
|
|
|
for (devnum = 0; devnum < 32; devnum++) {
|
|
is_multi = 0;
|
|
for (fn = 0; fn==0 || (is_multi && fn<8); fn++) {
|
|
ob_configure_pci_device(path, bus_num, mem_base, io_base,
|
|
bus, devnum, fn, &is_multi);
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
#if defined(CONFIG_SPARC64)
|
|
|
|
/* Convert device/irq pin to interrupt property */
|
|
#define SUN4U_PCIAINTERRUPT(dev, irq_pin) \
|
|
((((dev >> 11) << 2) + irq_pin - 1) & 0x1f)
|
|
|
|
#define SUN4U_PCIBINTERRUPT(dev, irq_pin) \
|
|
((0x10 + (((dev >> 11) << 2) + irq_pin - 1)) & 0x1f)
|
|
|
|
static void ob_pci_simbaB_bus_interrupt(ucell dnode, u32 *props, int *ncells, u32 addr, u32 intno)
|
|
{
|
|
*ncells += pci_encode_phys_addr(props + *ncells, 0, 0, addr, 0, 0);
|
|
props[(*ncells)++] = intno;
|
|
props[(*ncells)++] = dnode;
|
|
props[(*ncells)++] = SUN4U_PCIBINTERRUPT(addr, intno);
|
|
}
|
|
|
|
static void ob_pci_bus_set_interrupt_map(phandle_t pcibus, phandle_t dnode,
|
|
void (*func)(ucell dnode, u32 *props, int *ncells, u32 addr, u32 intno));
|
|
|
|
static void ob_scan_sabre_pci_bus(int *bus_num, unsigned long *mem_base,
|
|
unsigned long *io_base, const char *path,
|
|
int bus)
|
|
{
|
|
int devnum, fn, is_multi;
|
|
phandle_t ph;
|
|
|
|
PCI_DPRINTF("\nScanning sabre bus %d at %s...\n", bus, path);
|
|
|
|
/* Horrible sabre hack: the PCI bridge with the on-board devices
|
|
is located at devfn (1,1) so if we use the standard scan function
|
|
we end up with our ioports not mapped at io_base == 0x0 which
|
|
breaks many assumptions in OpenBIOS. Hence do a custom scan for
|
|
sabre which does things in the right order. */
|
|
for (devnum = 0; devnum < 32; devnum++) {
|
|
is_multi = 0;
|
|
|
|
if (devnum == 1) {
|
|
/* Force io_base/mem_base to match the pciA simba range */
|
|
*io_base = 0x0; /* because of arch->iobase */
|
|
*mem_base = 0x20000000;
|
|
|
|
ob_configure_pci_device(path, bus_num, mem_base, io_base,
|
|
bus, 1, 1, &is_multi);
|
|
|
|
/* Force io_base/mem_base to match the pciB simba range */
|
|
*io_base = 0x800000; /* because of arch->iobase */
|
|
*mem_base = 0x60000000;
|
|
|
|
ph = ob_configure_pci_device(path, bus_num, mem_base, io_base,
|
|
bus, 1, 0, &is_multi);
|
|
|
|
/* Set up pciB interrupt map */
|
|
ob_pci_bus_set_interrupt_map(ph, find_dev("/pci"), ob_pci_simbaB_bus_interrupt);
|
|
} else {
|
|
for (fn = 0; fn==0 || (is_multi && fn<8); fn++) {
|
|
ob_configure_pci_device(path, bus_num, mem_base, io_base,
|
|
bus, devnum, fn, &is_multi);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static void ob_configure_pci_bridge(pci_addr addr,
|
|
int *bus_num, unsigned long *mem_base,
|
|
unsigned long *io_base,
|
|
int primary_bus, pci_config_t *config)
|
|
{
|
|
unsigned long old_mem_base, old_io_base, io_scan_limit;
|
|
uint16_t cmd;
|
|
phandle_t ph;
|
|
|
|
config->primary_bus = primary_bus;
|
|
pci_config_write8(addr, PCI_PRIMARY_BUS, config->primary_bus);
|
|
|
|
config->secondary_bus = *bus_num;
|
|
pci_config_write8(addr, PCI_SECONDARY_BUS, config->secondary_bus);
|
|
|
|
config->subordinate_bus = 0xff;
|
|
pci_config_write8(addr, PCI_SUBORDINATE_BUS, config->subordinate_bus);
|
|
|
|
PCI_DPRINTF("scanning new pci bus %u under bridge %s\n",
|
|
config->secondary_bus, config->path);
|
|
|
|
/* Temporarily add bus-range property to allow the secondary bus to
|
|
determine its bus num */
|
|
ph = find_dev(config->path);
|
|
set_int_property(ph, "bus-range", *bus_num);
|
|
|
|
/* Always expose the legacy ioports on the first PCI bridge. If we
|
|
must have legacy devices behind a PCI bridge then they must be
|
|
on the first one discovered to ensure that the ioports will work. */
|
|
if (primary_bus > 0 && *io_base < 0x1000) {
|
|
*io_base = 0x0;
|
|
}
|
|
|
|
/* Align mem_base up to nearest MB, io_base up to nearest 4K */
|
|
old_mem_base = (*mem_base + 0xfffff - 1) & ~(0xfffff - 1);
|
|
*mem_base = old_mem_base;
|
|
old_io_base = (*io_base + 0xfff - 1) & ~(0xfff - 1);
|
|
*io_base = old_io_base;
|
|
|
|
/* Set the base limit registers */
|
|
pci_config_write16(addr, PCI_MEMORY_BASE, ((*mem_base >> 16) & ~(0xf)));
|
|
pci_config_write16(addr, PCI_IO_BASE_UPPER, (*io_base >> 16));
|
|
pci_config_write8(addr, PCI_IO_BASE, ((*io_base >> 8) & ~(0xf)));
|
|
|
|
/* Always ensure legacy ioports are accessible during enumeration.
|
|
Some drivers (e.g. IDE) will attempt ioport access as part of
|
|
the configuration process, so we allow them during the secondary
|
|
bus scan and then set the correct IO limit below. */
|
|
io_scan_limit = *io_base + 0xffff;
|
|
pci_config_write16(addr, PCI_IO_LIMIT_UPPER, (io_scan_limit >> 16));
|
|
pci_config_write8(addr, PCI_IO_LIMIT, (io_scan_limit >> 8) & ~(0xf));
|
|
|
|
/* make pci bridge parent device, prepare for recursion */
|
|
|
|
#if defined(CONFIG_SPARC64)
|
|
/* Horrible hack for sabre */
|
|
int vid = pci_config_read16(addr, PCI_VENDOR_ID);
|
|
int did = pci_config_read16(addr, PCI_DEVICE_ID);
|
|
|
|
if (vid == PCI_VENDOR_ID_SUN && did == PCI_DEVICE_ID_SUN_SABRE) {
|
|
ob_scan_sabre_pci_bus(bus_num, mem_base, io_base,
|
|
config->path, config->secondary_bus);
|
|
} else {
|
|
ob_scan_pci_bus(bus_num, mem_base, io_base,
|
|
config->path, config->secondary_bus);
|
|
}
|
|
#else
|
|
ob_scan_pci_bus(bus_num, mem_base, io_base,
|
|
config->path, config->secondary_bus);
|
|
#endif
|
|
/* bus scan updates *bus_num to last revealed pci bus number */
|
|
config->subordinate_bus = *bus_num;
|
|
pci_config_write8(addr, PCI_SUBORDINATE_BUS, config->subordinate_bus);
|
|
|
|
PCI_DPRINTF("bridge %s PCI bus primary=%d secondary=%d subordinate=%d\n",
|
|
config->path, config->primary_bus, config->secondary_bus,
|
|
config->subordinate_bus);
|
|
|
|
/* Align mem_base up to nearest MB, io_base up to nearest 4K */
|
|
*mem_base = (*mem_base + 0xfffff - 1) & ~(0xfffff - 1);
|
|
*io_base = (*io_base + 0xfff - 1) & ~(0xfff - 1);
|
|
|
|
/* Set the limit registers */
|
|
pci_config_write16(addr, PCI_MEMORY_LIMIT, (((*mem_base - 1) >> 16) & ~(0xf)));
|
|
pci_config_write16(addr, PCI_IO_LIMIT_UPPER, ((*io_base - 1) >> 16));
|
|
pci_config_write8(addr, PCI_IO_LIMIT, (((*io_base - 1) >> 8) & ~(0xf)));
|
|
|
|
/* Disable unused address spaces */
|
|
cmd = pci_config_read16(addr, PCI_COMMAND);
|
|
if (*mem_base == old_mem_base) {
|
|
pci_config_write16(addr, PCI_COMMAND, (cmd & ~PCI_COMMAND_MEMORY));
|
|
}
|
|
|
|
if (*io_base == old_io_base) {
|
|
pci_config_write16(addr, PCI_COMMAND, (cmd & ~PCI_COMMAND_IO));
|
|
}
|
|
|
|
pci_set_bus_range(config);
|
|
}
|
|
|
|
static int ob_pci_read_identification(int bus, int devnum, int fn,
|
|
int *p_vid, int *p_did,
|
|
uint8_t *p_class, uint8_t *p_subclass)
|
|
{
|
|
int vid, did;
|
|
uint32_t ccode;
|
|
pci_addr addr;
|
|
|
|
#ifdef CONFIG_XBOX
|
|
if (pci_xbox_blacklisted (bus, devnum, fn))
|
|
return;
|
|
#endif
|
|
addr = PCI_ADDR(bus, devnum, fn);
|
|
vid = pci_config_read16(addr, PCI_VENDOR_ID);
|
|
did = pci_config_read16(addr, PCI_DEVICE_ID);
|
|
|
|
if (vid==0xffff || vid==0) {
|
|
return 0;
|
|
}
|
|
|
|
if (p_vid) {
|
|
*p_vid = vid;
|
|
}
|
|
|
|
if (p_did) {
|
|
*p_did = did;
|
|
}
|
|
|
|
ccode = pci_config_read16(addr, PCI_CLASS_DEVICE);
|
|
|
|
if (p_class) {
|
|
*p_class = ccode >> 8;
|
|
}
|
|
|
|
if (p_subclass) {
|
|
*p_subclass = ccode;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static phandle_t ob_configure_pci_device(const char* parent_path,
|
|
int *bus_num, unsigned long *mem_base, unsigned long *io_base,
|
|
int bus, int devnum, int fn, int *p_is_multi)
|
|
{
|
|
int vid, did;
|
|
unsigned int htype;
|
|
pci_addr addr;
|
|
pci_config_t config = {};
|
|
const pci_dev_t *pci_dev;
|
|
uint8_t class, subclass, iface;
|
|
int num_bars, rom_bar;
|
|
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);
|
|
|
|
if (is_oldworld() || is_newworld()) {
|
|
/* Mac machines */
|
|
props[(*ncells)++] = intno;
|
|
props[(*ncells)++] = dnode;
|
|
props[(*ncells)++] = arch->irqs[((intno - 1) + (addr >> 11)) & 3];
|
|
props[(*ncells)++] = 1;
|
|
} else {
|
|
/* PReP machines */
|
|
props[(*ncells)++] = intno;
|
|
props[(*ncells)++] = dnode;
|
|
|
|
if (PCI_DEV(addr) == 1 && PCI_FN(addr) == 0) {
|
|
/* LSI SCSI has fixed routing to IRQ 13 */
|
|
props[(*ncells)++] = 13;
|
|
} else {
|
|
/* Use the same "physical" routing as QEMU's raven_map_irq() although
|
|
ultimately all 4 PCI interrupts are ORd to IRQ 15 as indicated
|
|
by the PReP specification */
|
|
props[(*ncells)++] = arch->irqs[((intno - 1) + (addr >> 11)) & 1];
|
|
}
|
|
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;
|
|
}
|