summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Kettenis <kettenis@cvs.openbsd.org>2009-11-19 20:46:17 +0000
committerMark Kettenis <kettenis@cvs.openbsd.org>2009-11-19 20:46:17 +0000
commit14fba7e59e92dafb518d15789c8084f184dd468e (patch)
treed8fc816a9ce1c51bbb8da1b2d5e73612296684f9
parenta9941f9c8cae53be46577cc64378d1fcc30d12b5 (diff)
Add code to set up windows on PCI-PCI bridges left unconfigured by the
firmware. Fixes some (laptop) machines with ethernet and/or wiireless behind a bridge. Some help from miod@. Tested by many.
-rw-r--r--sys/dev/pci/ppb.c152
1 files changed, 151 insertions, 1 deletions
diff --git a/sys/dev/pci/ppb.c b/sys/dev/pci/ppb.c
index 1ba62828c96..7149d34637b 100644
--- a/sys/dev/pci/ppb.c
+++ b/sys/dev/pci/ppb.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: ppb.c,v 1.35 2009/09/24 21:26:25 miod Exp $ */
+/* $OpenBSD: ppb.c,v 1.36 2009/11/19 20:46:16 kettenis Exp $ */
/* $NetBSD: ppb.c,v 1.16 1997/06/06 23:48:05 thorpej Exp $ */
/*
@@ -73,6 +73,7 @@ struct cfdriver ppb_cd = {
NULL, "ppb", DV_DULL
};
+void ppb_alloc_resources(struct ppb_softc *, struct pci_attach_args *);
int ppb_intr(void *);
void ppb_hotplug_insert(void *, void *);
void ppb_hotplug_insert_finish(void *);
@@ -163,6 +164,8 @@ ppbattach(struct device *parent, struct device *self, void *aux)
printf("\n");
+ ppb_alloc_resources(sc, pa);
+
for (pin = PCI_INTERRUPT_PIN_A; pin <= PCI_INTERRUPT_PIN_D; pin++) {
pa->pa_intrpin = pa->pa_rawintrpin = pin;
pa->pa_intrline = 0;
@@ -308,6 +311,153 @@ ppbdetach(struct device *self, int flags)
return (rv);
}
+void
+ppb_alloc_resources(struct ppb_softc *sc, struct pci_attach_args *pa)
+{
+ pci_chipset_tag_t pc = sc->sc_pc;
+ pcireg_t id, busdata, blr, bhlcr, type, csr;
+ pcireg_t addr, mask;
+ pcitag_t tag;
+ int bus, dev;
+ int reg, reg_start, reg_end, reg_rom;
+ int io_count = 0;
+ int mem_count = 0;
+ u_long base, size;
+
+ if (pa->pa_memex == NULL)
+ return;
+
+ busdata = pci_conf_read(pc, sc->sc_tag, PPB_REG_BUSINFO);
+ bus = PPB_BUSINFO_SECONDARY(busdata);
+ if (bus == 0)
+ return;
+
+ /*
+ * Count number of devices. If there are no devices behind
+ * this bridge, there's no point in allocating any address
+ * space.
+ */
+ for (dev = 0; dev < pci_bus_maxdevs(pc, bus); dev++) {
+ tag = pci_make_tag(pc, bus, dev, 0);
+ id = pci_conf_read(pc, tag, PCI_ID_REG);
+
+ if (PCI_VENDOR(id) == PCI_VENDOR_INVALID ||
+ PCI_VENDOR(id) == 0)
+ continue;
+
+ bhlcr = pci_conf_read(pc, tag, PCI_BHLC_REG);
+ switch (PCI_HDRTYPE_TYPE(bhlcr)) {
+ case 0:
+ reg_start = PCI_MAPREG_START;
+ reg_end = PCI_MAPREG_END;
+ reg_rom = PCI_ROM_REG;
+ break;
+ case 1: /* PCI-PCI bridge */
+ reg_start = PCI_MAPREG_START;
+ reg_end = PCI_MAPREG_PPB_END;
+ reg_rom = 0; /* 0x38 */
+ break;
+ case 2: /* PCI-Cardbus bridge */
+ reg_start = PCI_MAPREG_START;
+ reg_end = PCI_MAPREG_PCB_END;
+ reg_rom = 0;
+ break;
+ default:
+ return;
+ }
+
+ for (reg = reg_start; reg < reg_end; reg += 4) {
+ if (pci_mapreg_probe(pc, tag, reg, &type) == 0)
+ continue;
+
+ if (type == PCI_MAPREG_TYPE_IO)
+ io_count++;
+ else
+ mem_count++;
+ }
+
+ if (reg_rom != 0) {
+ addr = pci_conf_read(pc, tag, reg_rom);
+ pci_conf_write(pc, tag, reg_rom, ~PCI_ROM_ENABLE);
+ mask = pci_conf_read(pc, tag, reg_rom);
+ pci_conf_write(pc, tag, reg_rom, addr);
+ if (PCI_ROM_SIZE(mask))
+ mem_count++;
+ }
+ }
+
+ csr = pci_conf_read(pc, sc->sc_tag, PCI_COMMAND_STATUS_REG);
+
+ /*
+ * Get the bridge in a consistent state. If memory mapped I/O
+ * is disabled, disabled the associated windows as well.
+ */
+ if ((csr & PCI_COMMAND_MEM_ENABLE) == 0) {
+ pci_conf_write(pc, sc->sc_tag, PPB_REG_MEM, 0x0000ffff);
+ pci_conf_write(pc, sc->sc_tag, PPB_REG_PREFMEM, 0x0000ffff);
+ pci_conf_write(pc, sc->sc_tag, PPB_REG_PREFBASE_HI32, 0);
+ pci_conf_write(pc, sc->sc_tag, PPB_REG_PREFLIM_HI32, 0);
+ }
+
+ /* Allocate I/O address space if necessary. */
+ if (io_count > 0 && pa->pa_ioex) {
+ blr = pci_conf_read(pc, sc->sc_tag, PPB_REG_IOSTATUS);
+ sc->sc_iobase = (blr << PPB_IO_SHIFT) & PPB_IO_MASK;
+ sc->sc_iolimit = (blr & PPB_IO_MASK) | 0x00000fff;
+ blr = pci_conf_read(pc, sc->sc_tag, PPB_REG_IO_HI);
+ sc->sc_iobase |= (blr & 0x0000ffff) << 16;
+ sc->sc_iolimit |= (blr & 0xffff0000);
+ if (sc->sc_iolimit < sc->sc_iobase || sc->sc_iobase == 0) {
+ for (size = 0x2000; size >= PPB_IO_MIN; size >>= 1)
+ if (extent_alloc(pa->pa_ioex, size, size,
+ 0, 0, 0, &base) == 0)
+ break;
+ if (size >= PPB_IO_MIN) {
+ sc->sc_iobase = base;
+ sc->sc_iolimit = base + size - 1;
+ blr = pci_conf_read(pc, sc->sc_tag,
+ PPB_REG_IOSTATUS);
+ blr &= 0xffff0000;
+ blr |= sc->sc_iolimit & PPB_IO_MASK;
+ blr |= (sc->sc_iobase >> PPB_IO_SHIFT);
+ pci_conf_write(pc, sc->sc_tag,
+ PPB_REG_IOSTATUS, blr);
+ blr = (sc->sc_iobase & 0xffff0000) >> 16;
+ blr |= sc->sc_iolimit & 0xffff0000;
+ pci_conf_write(pc, sc->sc_tag,
+ PPB_REG_IO_HI, blr);
+
+ csr |= PCI_COMMAND_IO_ENABLE;
+ }
+ }
+ }
+
+ /* Allocate memory mapped I/O address space if necessary. */
+ if (mem_count > 0 && pa->pa_memex) {
+ blr = pci_conf_read(pc, sc->sc_tag, PPB_REG_MEM);
+ sc->sc_membase = (blr << PPB_MEM_SHIFT) & PPB_MEM_MASK;
+ sc->sc_memlimit = (blr & PPB_MEM_MASK) | 0x000fffff;
+ if (sc->sc_memlimit < sc->sc_membase || sc->sc_membase == 0) {
+ for (size = 0x2000000; size >= PPB_MEM_MIN; size >>= 1)
+ if (extent_alloc(pa->pa_memex, size, size,
+ 0, 0, 0, &base) == 0)
+ break;
+ if (size >= PPB_MEM_MIN) {
+ sc->sc_membase = base;
+ sc->sc_memlimit = base + size - 1;
+ blr = sc->sc_memlimit & PPB_MEM_MASK;
+ blr |= (sc->sc_membase >> PPB_MEM_SHIFT);
+ pci_conf_write(pc, sc->sc_tag,
+ PPB_REG_MEM, blr);
+
+ csr |= PCI_COMMAND_MEM_ENABLE;
+ }
+ }
+ }
+
+ pci_conf_write(pc, sc->sc_tag, PCI_COMMAND_STATUS_REG, csr);
+}
+
int
ppb_intr(void *arg)
{