NetBSD/sys/arch/sparc64/dev/pci_machdep.c

546 lines
14 KiB
C

/* $NetBSD: pci_machdep.c,v 1.21 2001/03/21 01:33:48 mrg Exp $ */
/*
* Copyright (c) 1999, 2000 Matthew R. Green
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
/*
* functions expected by the MI PCI code.
*/
#ifdef DEBUG
#define SPDB_CONF 0x01
#define SPDB_INTR 0x04
#define SPDB_INTMAP 0x08
#define SPDB_INTFIX 0x10
int sparc_pci_debug = 0x0;
#define DPRINTF(l, s) do { if (sparc_pci_debug & l) printf s; } while (0)
#else
#define DPRINTF(l, s)
#endif
#include <sys/types.h>
#include <sys/param.h>
#include <sys/time.h>
#include <sys/systm.h>
#include <sys/errno.h>
#include <sys/device.h>
#include <sys/malloc.h>
#define _SPARC_BUS_DMA_PRIVATE
#include <machine/bus.h>
#include <machine/autoconf.h>
#include <dev/pci/pcivar.h>
#include <dev/pci/pcireg.h>
#include <dev/ofw/openfirm.h>
#include <dev/ofw/ofw_pci.h>
#include <sparc64/dev/iommureg.h>
#include <sparc64/dev/iommuvar.h>
#include <sparc64/dev/psychoreg.h>
#include <sparc64/dev/psychovar.h>
/* this is a base to be copied */
struct sparc_pci_chipset _sparc_pci_chipset = {
NULL,
};
/*
* functions provided to the MI code.
*/
void
pci_attach_hook(parent, self, pba)
struct device *parent;
struct device *self;
struct pcibus_attach_args *pba;
{
pci_chipset_tag_t pc = pba->pba_pc;
struct psycho_pbm *pp = pc->cookie;
struct psycho_registers *pr;
pcitag_t tag;
char *name, *devtype;
u_int32_t hi, mid, lo, intr, line;
u_int32_t dev, fn, bus;
int node, i, n, *ip, *ap;
DPRINTF((SPDB_INTFIX|SPDB_INTMAP), ("\npci_attach_hook:"));
/*
* ok, here we look in the OFW for each PCI device and fix it's
* "interrupt line" register to be useful.
*/
for (node = firstchild(pc->node); node; node = nextsibling(node)) {
pr = NULL;
ip = ap = NULL;
/*
* ok, for each child we get the "interrupts" property,
* which contains a value to match against later.
* XXX deal with multiple "interrupts" values XXX.
* then we get the "assigned-addresses" property which
* contains, in the first entry, the PCI bus, device and
* function associated with this node, which we use to
* generate a pcitag_t to use pci_conf_read() and
* pci_conf_write(). next, we get the 'reg" property
* which is structured like the following:
* u_int32_t phys_hi;
* u_int32_t phys_mid;
* u_int32_t phys_lo;
* u_int32_t size_hi;
* u_int32_t size_lo;
* we mask these values with the "interrupt-map-mask"
* property of our parent and them compare with each
* entry in the "interrupt-map" property (also of our
* parent) which is structred like the following:
* u_int32_t phys_hi;
* u_int32_t phys_mid;
* u_int32_t phys_lo;
* u_int32_t intr;
* int32_t child_node;
* u_int32_t child_intr;
* if there is an exact match with phys_hi, phys_mid,
* phys_lo and the interrupt, we have a match and we
* know that this interrupt's value is really the
* child_intr of the interrupt map entry. we put this
* into the PCI interrupt line register so that when
* the driver for this node wants to attach, we know
* it's INO already.
*/
name = getpropstring(node, "name");
DPRINTF((SPDB_INTFIX|SPDB_INTMAP), ("\n\tnode %x name `%s'", node, name));
devtype = getpropstring(node, "device_type");
DPRINTF((SPDB_INTFIX|SPDB_INTMAP), (" devtype `%s':", devtype));
/* ignore PCI bridges, we'll get them later */
if (strcmp(devtype, "pci") == 0)
continue;
/* if there isn't any "interrupts" then we don't care to fix it */
ip = NULL;
if (getprop(node, "interrupts", sizeof(int), &n, (void **)&ip))
continue;
DPRINTF(SPDB_INTFIX, (" got interrupts"));
/* and if there isn't an "assigned-addresses" we can't find b/d/f */
if (getprop(node, "assigned-addresses", sizeof(int), &n,
(void **)&ap))
goto clean1;
DPRINTF(SPDB_INTFIX, (" got assigned-addresses"));
/* ok, and now the "reg" property, so we know what we're talking about. */
if (getprop(node, "reg", sizeof(*pr), &n,
(void **)&pr))
goto clean2;
DPRINTF(SPDB_INTFIX, (" got reg"));
bus = TAG2BUS(ap[0]);
dev = TAG2DEV(ap[0]);
fn = TAG2FN(ap[0]);
DPRINTF(SPDB_INTFIX, ("; bus %u dev %u fn %u", bus, dev, fn));
tag = pci_make_tag(pc, bus, dev, fn);
DPRINTF(SPDB_INTFIX, ("; tag %08x\n\t; reg: hi %x mid %x lo %x intr %x", tag, pr->phys_hi, pr->phys_mid, pr->phys_lo, *ip));
/*
* if there is no interrupt-map property in the parent, we must
* assume our "interrupts" property is valid.
*/
if (pp->pp_nintmap == 0) {
intr = *ip;
DPRINTF((SPDB_INTFIX|SPDB_INTMAP), ("\n\t ; no interrupt-map, using intr %x", *ip));
goto bingo;
}
DPRINTF(SPDB_INTFIX, ("\n\t; intmapmask: hi %x mid %x lo %x intr %x", pp->pp_intmapmask.phys_hi, pp->pp_intmapmask.phys_mid,
pp->pp_intmapmask.phys_lo, pp->pp_intmapmask.intr));
hi = pr->phys_hi & pp->pp_intmapmask.phys_hi;
mid = pr->phys_mid & pp->pp_intmapmask.phys_mid;
lo = pr->phys_lo & pp->pp_intmapmask.phys_lo;
intr = *ip & pp->pp_intmapmask.intr;
DPRINTF(SPDB_INTFIX, ("\n\t; after: hi %x mid %x lo %x intr %x", hi, mid, lo, intr));
for (i = 0; i < pp->pp_nintmap; i++) {
DPRINTF(SPDB_INTFIX, ("\n\t\tmatching for: hi %x mid %x lo %x intr %x", pp->pp_intmap[i].phys_hi, pp->pp_intmap[i].phys_mid,
pp->pp_intmap[i].phys_lo, pp->pp_intmap[i].intr));
if (pp->pp_intmap[i].phys_hi != hi ||
pp->pp_intmap[i].phys_mid != mid ||
pp->pp_intmap[i].phys_lo != lo ||
pp->pp_intmap[i].intr != intr)
continue;
intr = pp->pp_intmap[i].child_intr;
DPRINTF(SPDB_INTFIX, ("... BINGO! ..."));
bingo:
/*
* OK! we found match. pull out the old interrupt
* register, patch in the new value, and put it back.
*/
line = pci_conf_read(pc, tag, PCI_INTERRUPT_REG);
DPRINTF(SPDB_INTFIX, ("\n\t ; read %x from intreg", line));
line = PCI_INTERRUPT_CODE(PCI_INTERRUPT_LATENCY(line),
PCI_INTERRUPT_GRANT(line),
PCI_INTERRUPT_PIN(line),
PCI_INTERRUPT_LINE(intr));
DPRINTF((SPDB_INTFIX|SPDB_INTMAP), ("\n\t ; gonna write %x to intreg", line));
pci_conf_write(pc, tag, PCI_INTERRUPT_REG, intr);
break;
}
if (i == pp->pp_nintmap) {
/*
* Not matched by parent interrupt map. If the
* interrupt property has the INTMAP_OBIO bit
* set, assume the PROM has (wrongly) supplied it
* in the parent's bus format, rather than as a
* PCI interrupt line number.
*
* This seems to be an issue only with the
* psycho host-to-pci bridge.
*/
if (pp->pp_sc->sc_mode == PSYCHO_MODE_PSYCHO &&
(*ip & INTMAP_OBIO) != 0) {
DPRINTF((SPDB_INTFIX|SPDB_INTMAP),
("\n\t; PSYCHO: no match but obio interrupt in parent format"));
intr = *ip;
i = -1;
goto bingo; /* hackish */
}
}
/* enable mem & dma if not already */
pci_conf_write(pc, tag, PCI_COMMAND_STATUS_REG,
PCI_COMMAND_MEM_ENABLE|PCI_COMMAND_MASTER_ENABLE|PCI_COMMAND_IO_ENABLE);
/* clean up */
if (pr)
free(pr, M_DEVBUF);
clean2:
if (ap)
free(ap, M_DEVBUF);
clean1:
if (ip)
free(ip, M_DEVBUF);
}
DPRINTF(SPDB_INTFIX, ("\n"));
}
int
pci_bus_maxdevs(pc, busno)
pci_chipset_tag_t pc;
int busno;
{
return 32;
}
#ifdef __PCI_BUS_DEVORDER
int
pci_bus_devorder(pc, busno, devs)
pci_chipset_tag_t pc;
int busno;
char *devs;
{
struct ofw_pci_register reg0;
int node, len, device, i = 0;
u_int32_t done = 0;
for (node = OF_child(pc->node); node; node = OF_peer(node)) {
len = OF_getproplen(node, "reg");
if (len < sizeof(reg0))
continue;
if (OF_getprop(node, "reg", (void *)&reg0, sizeof(reg0)) != len)
panic("pci_probe_bus: OF_getprop len botch");
device = OFW_PCI_PHYS_HI_DEVICE(reg0.phys_hi);
if (done & (1 << device))
continue;
devs[i++] = device;
done |= 1 << device;
if (i == 32)
break;
}
if (i < 32)
devs[i] = -1;
return i;
}
#endif
#ifdef __PCI_DEV_FUNCORDER
int
pci_dev_funcorder(pc, busno, device, funcs)
pci_chipset_tag_t pc;
int busno;
int device;
char *funcs;
{
struct ofw_pci_register reg0;
int node, len, i = 0;
for (node = OF_child(pc->node); node; node = OF_peer(node)) {
len = OF_getproplen(node, "reg");
if (len < sizeof(reg0))
continue;
if (OF_getprop(node, "reg", (void *)&reg0, sizeof(reg0)) != len)
panic("pci_probe_bus: OF_getprop len botch");
if (device != OFW_PCI_PHYS_HI_DEVICE(reg0.phys_hi))
continue;
funcs[i++] = OFW_PCI_PHYS_HI_FUNCTION(reg0.phys_hi);
if (i == 8)
break;
}
if (i < 8)
funcs[i] = -1;
return i;
}
#endif
pcitag_t
pci_make_tag(pc, b, d, f)
pci_chipset_tag_t pc;
int b;
int d;
int f;
{
/* make me a useable offset */
return (b << 16) | (d << 11) | (f << 8);
}
static int confaddr_ok __P((struct psycho_softc *, pcitag_t));
/*
* this function is a large hack. ideally, we should also trap accesses
* properly, but we have to avoid letting anything read various parts
* of bus 0 dev 0 fn 0 space or the machine may hang. so, even if we
* do properly implement PCI config access trap handling, this function
* should remain in place Just In Case.
*/
static int
confaddr_ok(sc, tag)
struct psycho_softc *sc;
pcitag_t tag;
{
int bus, dev, fn;
bus = TAG2BUS(tag);
dev = TAG2DEV(tag);
fn = TAG2FN(tag);
if (sc->sc_mode == PSYCHO_MODE_SABRE) {
/*
* bus 0 is only ok for dev 0 fn 0, dev 1 fn 0 and dev fn 1.
*/
if (bus == 0 &&
((dev == 0 && fn > 0) ||
(dev == 1 && fn > 1) ||
(dev > 1))) {
DPRINTF(SPDB_CONF, (" confaddr_ok: rejecting bus %d dev %d fn %d -", bus, dev, fn));
return (0);
}
} else if (sc->sc_mode == PSYCHO_MODE_PSYCHO) {
/*
* make sure we are reading our own bus
*/
/* XXX??? */
paddr_t addr = sc->sc_configaddr + tag;
int asi = bus_type_asi[sc->sc_configtag->type];
if (probeget(addr, asi, 4) == -1) {
DPRINTF(SPDB_CONF, (" confaddr_ok: rejecting bus %d dev %d fn %d -", bus, dev, fn));
return (0);
}
}
return (1);
}
/* assume we are mapped little-endian/side-effect */
pcireg_t
pci_conf_read(pc, tag, reg)
pci_chipset_tag_t pc;
pcitag_t tag;
int reg;
{
struct psycho_pbm *pp = pc->cookie;
struct psycho_softc *sc = pp->pp_sc;
pcireg_t val;
DPRINTF(SPDB_CONF, ("pci_conf_read: tag %lx; reg %x; ", (long)tag, reg));
DPRINTF(SPDB_CONF, ("asi = %x; readaddr = %qx (offset = %x) ...",
bus_type_asi[sc->sc_configtag->type],
(long long)(sc->sc_configaddr + tag + reg), (int)tag + reg));
if (confaddr_ok(sc, tag) == 0) {
val = (pcireg_t)~0;
} else {
membar_sync();
val = bus_space_read_4(sc->sc_configtag, sc->sc_configaddr,
tag + reg);
membar_sync();
}
DPRINTF(SPDB_CONF, (" returning %08x\n", (u_int)val));
return (val);
}
void
pci_conf_write(pc, tag, reg, data)
pci_chipset_tag_t pc;
pcitag_t tag;
int reg;
pcireg_t data;
{
struct psycho_pbm *pp = pc->cookie;
struct psycho_softc *sc = pp->pp_sc;
DPRINTF(SPDB_CONF, ("pci_conf_write: tag %lx; reg %x; data %x; ", (long)tag, reg, (int)data));
DPRINTF(SPDB_CONF, ("asi = %x; readaddr = %qx (offset = %x)\n",
bus_type_asi[sc->sc_configtag->type],
(long long)(sc->sc_configaddr + tag + reg), (int)tag + reg));
if (confaddr_ok(sc, tag) == 0)
panic("pci_conf_write: bad addr");
membar_sync();
bus_space_write_4(sc->sc_configtag, sc->sc_configaddr, tag + reg, data);
membar_sync();
}
/*
* interrupt mapping foo.
* XXX: how does this deal with multiple interrupts for a device?
*/
int
pci_intr_map(pa, ihp)
struct pci_attach_args *pa;
pci_intr_handle_t *ihp;
{
int rv, pin, line;
pin = pa->pa_intrpin;
line = pa->pa_intrline;
DPRINTF(SPDB_INTMAP, ("pci_intr_map: dev %u fn %u: ", pa->pa_device,
pa->pa_function));
/*
* XXX
* UltraSPARC PCI does not use PCI_INTERRUPT_REG, but we have
* used this space for our own purposes...
*/
DPRINTF(SPDB_INTR, ("pci_intr_map: tag %lx; line %d",
(long)pa->pa_intrtag, line));
if (line >= 0x40) {
*ihp = -1;
rv = 1;
goto out;
}
if (pin > 4)
panic("pci_intr_map: pin > 4");
(*ihp) = line & 0x3f;
rv = 0;
out:
DPRINTF(SPDB_INTR, ("; handle = %x; returning %d\n", (u_int)*ihp, rv));
return (rv);
}
const char *
pci_intr_string(pc, ih)
pci_chipset_tag_t pc;
pci_intr_handle_t ih;
{
static char str[16];
DPRINTF(SPDB_INTR, ("pci_intr_string: ih %u", ih));
if (ih < 0 || ih >= 0x40) {
printf("\n"); /* i'm *so* beautiful */
panic("pci_intr_string: bogus handle\n");
}
sprintf(str, "ipl %u", ih);
DPRINTF(SPDB_INTR, ("; returning %s\n", str));
return (str);
}
const struct evcnt *
pci_intr_evcnt(pc, ih)
pci_chipset_tag_t pc;
pci_intr_handle_t ih;
{
/* XXX for now, no evcnt parent reported */
return NULL;
}
void *
pci_intr_establish(pc, ih, level, func, arg)
pci_chipset_tag_t pc;
pci_intr_handle_t ih;
int level;
int (*func) __P((void *));
void *arg;
{
void *cookie;
struct psycho_pbm *pp = (struct psycho_pbm *)pc->cookie;
DPRINTF(SPDB_INTR, ("pci_intr_establish: ih %lu; level %d", (u_long)ih, level));
cookie = bus_intr_establish(pp->pp_memt, ih, level, 0, func, arg);
DPRINTF(SPDB_INTR, ("; returning handle %p\n", cookie));
return (cookie);
}
void
pci_intr_disestablish(pc, cookie)
pci_chipset_tag_t pc;
void *cookie;
{
DPRINTF(SPDB_INTR, ("pci_intr_disestablish: cookie %p\n", cookie));
/* XXX */
panic("can't disestablish PCI interrupts yet");
}