linux 2.6.16.38 w/ vs2.0.3-rc1
[linux-2.6.git] / arch / sparc64 / kernel / pci_common.c
index 827ae30..58310aa 100644 (file)
@@ -9,10 +9,6 @@
 #include <linux/init.h>
 
 #include <asm/pbm.h>
-#include <asm/prom.h>
-#include <asm/of_device.h>
-
-#include "pci_impl.h"
 
 /* Fix self device of BUS and hook it into BUS->self.
  * The pci_scan_bus does not do this for the host bridge.
@@ -32,16 +28,16 @@ void __init pci_fixup_host_bridge_self(struct pci_bus *pbus)
        prom_halt();
 }
 
-/* Find the OBP PROM device tree node for a PCI device.  */
-static struct device_node * __init
-find_device_prom_node(struct pci_pbm_info *pbm, struct pci_dev *pdev,
-                     struct device_node *bus_node,
-                     struct linux_prom_pci_registers **pregs,
-                     int *nregs)
+/* Find the OBP PROM device tree node for a PCI device.
+ * Return zero if not found.
+ */
+static int __init find_device_prom_node(struct pci_pbm_info *pbm,
+                                       struct pci_dev *pdev,
+                                       int bus_prom_node,
+                                       struct linux_prom_pci_registers *pregs,
+                                       int *nregs)
 {
-       struct device_node *dp;
-
-       *nregs = 0;
+       int node;
 
        /*
         * Return the PBM's PROM node in case we are it's PCI device,
@@ -55,31 +51,27 @@ find_device_prom_node(struct pci_pbm_info *pbm, struct pci_dev *pdev,
             pdev->device == PCI_DEVICE_ID_SUN_SCHIZO ||
             pdev->device == PCI_DEVICE_ID_SUN_TOMATILLO ||
             pdev->device == PCI_DEVICE_ID_SUN_SABRE ||
-            pdev->device == PCI_DEVICE_ID_SUN_HUMMINGBIRD))
-               return bus_node;
-
-       dp = bus_node->child;
-       while (dp) {
-               struct linux_prom_pci_registers *regs;
-               struct property *prop;
-               int len;
+            pdev->device == PCI_DEVICE_ID_SUN_HUMMINGBIRD)) {
+               *nregs = 0;
+               return bus_prom_node;
+       }
 
-               prop = of_find_property(dp, "reg", &len);
-               if (!prop)
+       node = prom_getchild(bus_prom_node);
+       while (node != 0) {
+               int err = prom_getproperty(node, "reg",
+                                          (char *)pregs,
+                                          sizeof(*pregs) * PROMREG_MAX);
+               if (err == 0 || err == -1)
                        goto do_next_sibling;
-
-               regs = prop->value;
-               if (((regs[0].phys_hi >> 8) & 0xff) == pdev->devfn) {
-                       *pregs = regs;
-                       *nregs = len / sizeof(struct linux_prom_pci_registers);
-                       return dp;
+               if (((pregs[0].phys_hi >> 8) & 0xff) == pdev->devfn) {
+                       *nregs = err / sizeof(*pregs);
+                       return node;
                }
 
        do_next_sibling:
-               dp = dp->sibling;
+               node = prom_getsibling(node);
        }
-
-       return NULL;
+       return 0;
 }
 
 /* Older versions of OBP on PCI systems encode 64-bit MEM
@@ -136,17 +128,15 @@ static void __init fixup_obp_assignments(struct pci_dev *pdev,
  */
 static void __init pdev_cookie_fillin(struct pci_pbm_info *pbm,
                                      struct pci_dev *pdev,
-                                     struct device_node *bus_node)
+                                     int bus_prom_node)
 {
-       struct linux_prom_pci_registers *pregs = NULL;
+       struct linux_prom_pci_registers pregs[PROMREG_MAX];
        struct pcidev_cookie *pcp;
-       struct device_node *dp;
-       struct property *prop;
-       int nregs, len;
+       int device_prom_node, nregs, err;
 
-       dp = find_device_prom_node(pbm, pdev, bus_node,
-                                  &pregs, &nregs);
-       if (!dp) {
+       device_prom_node = find_device_prom_node(pbm, pdev, bus_prom_node,
+                                                pregs, &nregs);
+       if (device_prom_node == 0) {
                /* If it is not in the OBP device tree then
                 * there must be a damn good reason for it.
                 *
@@ -160,44 +150,45 @@ static void __init pdev_cookie_fillin(struct pci_pbm_info *pbm,
                return;
        }
 
-       pcp = kzalloc(sizeof(*pcp), GFP_ATOMIC);
+       pcp = kmalloc(sizeof(*pcp), GFP_ATOMIC);
        if (pcp == NULL) {
                prom_printf("PCI_COOKIE: Fatal malloc error, aborting...\n");
                prom_halt();
        }
        pcp->pbm = pbm;
-       pcp->prom_node = dp;
-       pcp->op = of_find_device_by_node(dp);
-       memcpy(pcp->prom_regs, pregs,
-              nregs * sizeof(struct linux_prom_pci_registers));
+       pcp->prom_node = device_prom_node;
+       memcpy(pcp->prom_regs, pregs, sizeof(pcp->prom_regs));
        pcp->num_prom_regs = nregs;
-
-       /* We can't have the pcidev_cookie assignments be just
-        * direct pointers into the property value, since they
-        * are potentially modified by the probing process.
-        */
-       prop = of_find_property(dp, "assigned-addresses", &len);
-       if (!prop) {
+       err = prom_getproperty(device_prom_node, "name",
+                              pcp->prom_name, sizeof(pcp->prom_name));
+       if (err > 0)
+               pcp->prom_name[err] = 0;
+       else
+               pcp->prom_name[0] = 0;
+
+       err = prom_getproperty(device_prom_node,
+                              "assigned-addresses",
+                              (char *)pcp->prom_assignments,
+                              sizeof(pcp->prom_assignments));
+       if (err == 0 || err == -1)
                pcp->num_prom_assignments = 0;
-       } else {
-               memcpy(pcp->prom_assignments, prop->value, len);
+       else
                pcp->num_prom_assignments =
-                       (len / sizeof(pcp->prom_assignments[0]));
-       }
+                       (err / sizeof(pcp->prom_assignments[0]));
 
-       if (strcmp(dp->name, "ebus") == 0) {
-               struct linux_prom_ebus_ranges *erng;
+       if (strcmp(pcp->prom_name, "ebus") == 0) {
+               struct linux_prom_ebus_ranges erng[PROM_PCIRNG_MAX];
                int iter;
 
                /* EBUS is special... */
-               prop = of_find_property(dp, "ranges", &len);
-               if (!prop) {
+               err = prom_getproperty(device_prom_node, "ranges",
+                                      (char *)&erng[0], sizeof(erng));
+               if (err == 0 || err == -1) {
                        prom_printf("EBUS: Fatal error, no range property\n");
                        prom_halt();
                }
-               erng = prop->value;
-               len = (len / sizeof(erng[0]));
-               for (iter = 0; iter < len; iter++) {
+               err = (err / sizeof(erng[0]));
+               for(iter = 0; iter < err; iter++) {
                        struct linux_prom_ebus_ranges *ep = &erng[iter];
                        struct linux_prom_pci_registers *ap;
 
@@ -209,7 +200,7 @@ static void __init pdev_cookie_fillin(struct pci_pbm_info *pbm,
                        ap->size_hi = 0;
                        ap->size_lo = ep->size;
                }
-               pcp->num_prom_assignments = len;
+               pcp->num_prom_assignments = err;
        }
 
        fixup_obp_assignments(pdev, pcp);
@@ -219,7 +210,7 @@ static void __init pdev_cookie_fillin(struct pci_pbm_info *pbm,
 
 void __init pci_fill_in_pbm_cookies(struct pci_bus *pbus,
                                    struct pci_pbm_info *pbm,
-                                   struct device_node *dp)
+                                   int prom_node)
 {
        struct pci_dev *pdev, *pdev_next;
        struct pci_bus *this_pbus, *pbus_next;
@@ -227,7 +218,7 @@ void __init pci_fill_in_pbm_cookies(struct pci_bus *pbus,
        /* This must be _safe because the cookie fillin
           routine can delete devices from the tree.  */
        list_for_each_entry_safe(pdev, pdev_next, &pbus->devices, bus_list)
-               pdev_cookie_fillin(pbm, pdev, dp);
+               pdev_cookie_fillin(pbm, pdev, prom_node);
 
        list_for_each_entry_safe(this_pbus, pbus_next, &pbus->children, node) {
                struct pcidev_cookie *pcp = this_pbus->self->sysdata;
@@ -250,6 +241,7 @@ static void __init bad_assignment(struct pci_dev *pdev,
        if (res)
                prom_printf("PCI: RES[%016lx-->%016lx:(%lx)]\n",
                            res->start, res->end, res->flags);
+       prom_printf("Please email this information to davem@redhat.com\n");
        if (do_prom_halt)
                prom_halt();
 }
@@ -281,7 +273,8 @@ __init get_root_resource(struct linux_prom_pci_registers *ap,
                return &pbm->mem_space;
 
        default:
-               printk("PCI: What is resource space %x?\n", space);
+               printk("PCI: What is resource space %x? "
+                      "Tell davem@redhat.com about it!\n", space);
                return NULL;
        };
 }
@@ -330,6 +323,19 @@ __init get_device_resource(struct linux_prom_pci_registers *ap,
        return res;
 }
 
+static int __init pdev_resource_collisions_expected(struct pci_dev *pdev)
+{
+       if (pdev->vendor != PCI_VENDOR_ID_SUN)
+               return 0;
+
+       if (pdev->device == PCI_DEVICE_ID_SUN_RIO_EBUS ||
+           pdev->device == PCI_DEVICE_ID_SUN_RIO_1394 ||
+           pdev->device == PCI_DEVICE_ID_SUN_RIO_USB)
+               return 1;
+
+       return 0;
+}
+
 static void __init pdev_record_assignments(struct pci_pbm_info *pbm,
                                           struct pci_dev *pdev)
 {
@@ -387,23 +393,19 @@ static void __init pdev_record_assignments(struct pci_pbm_info *pbm,
                pbm->parent->resource_adjust(pdev, res, root);
 
                if (request_resource(root, res) < 0) {
-                       int rnum;
-
                        /* OK, there is some conflict.  But this is fine
                         * since we'll reassign it in the fixup pass.
                         *
-                        * Do not print the warning for ROM resources
-                        * as such a conflict is quite common and
-                        * harmless as the ROM bar is disabled.
+                        * We notify the user that OBP made an error if it
+                        * is a case we don't expect.
                         */
-                       rnum = (res - &pdev->resource[0]);
-                       if (rnum != PCI_ROM_RESOURCE)
-                               printk(KERN_ERR "PCI: Resource collision, "
-                                      "region %d "
+                       if (!pdev_resource_collisions_expected(pdev)) {
+                               printk(KERN_ERR "PCI: Address space collision on region %ld "
                                       "[%016lx:%016lx] of device %s\n",
-                                      rnum,
+                                      (res - &pdev->resource[0]),
                                       res->start, res->end,
                                       pci_name(pdev));
+                       }
                }
        }
 }
@@ -539,18 +541,237 @@ void __init pci_assign_unassigned(struct pci_pbm_info *pbm,
                pci_assign_unassigned(pbm, bus);
 }
 
+static int __init pci_intmap_match(struct pci_dev *pdev, unsigned int *interrupt)
+{
+       struct linux_prom_pci_intmap bridge_local_intmap[PROM_PCIIMAP_MAX], *intmap;
+       struct linux_prom_pci_intmask bridge_local_intmask, *intmask;
+       struct pcidev_cookie *dev_pcp = pdev->sysdata;
+       struct pci_pbm_info *pbm = dev_pcp->pbm;
+       struct linux_prom_pci_registers *pregs = dev_pcp->prom_regs;
+       unsigned int hi, mid, lo, irq;
+       int i, num_intmap, map_slot;
+
+       intmap = &pbm->pbm_intmap[0];
+       intmask = &pbm->pbm_intmask;
+       num_intmap = pbm->num_pbm_intmap;
+       map_slot = 0;
+
+       /* If we are underneath a PCI bridge, use PROM register
+        * property of the parent bridge which is closest to
+        * the PBM.
+        *
+        * However if that parent bridge has interrupt map/mask
+        * properties of its own we use the PROM register property
+        * of the next child device on the path to PDEV.
+        *
+        * In detail the two cases are (note that the 'X' below is the
+        * 'next child on the path to PDEV' mentioned above):
+        *
+        * 1) PBM --> PCI bus lacking int{map,mask} --> X ... PDEV
+        *
+        *    Here we use regs of 'PCI bus' device.
+        *
+        * 2) PBM --> PCI bus with int{map,mask} --> X ... PDEV
+        *
+        *    Here we use regs of 'X'.  Note that X can be PDEV.
+        */
+       if (pdev->bus->number != pbm->pci_first_busno) {
+               struct pcidev_cookie *bus_pcp, *regs_pcp;
+               struct pci_dev *bus_dev, *regs_dev;
+               int plen;
+
+               bus_dev = pdev->bus->self;
+               regs_dev = pdev;
+
+               while (bus_dev->bus &&
+                      bus_dev->bus->number != pbm->pci_first_busno) {
+                       regs_dev = bus_dev;
+                       bus_dev = bus_dev->bus->self;
+               }
+
+               regs_pcp = regs_dev->sysdata;
+               pregs = regs_pcp->prom_regs;
+
+               bus_pcp = bus_dev->sysdata;
+
+               /* But if the PCI bridge has it's own interrupt map
+                * and mask properties, use that and the regs of the
+                * PCI entity at the next level down on the path to the
+                * device.
+                */
+               plen = prom_getproperty(bus_pcp->prom_node, "interrupt-map",
+                                       (char *) &bridge_local_intmap[0],
+                                       sizeof(bridge_local_intmap));
+               if (plen != -1) {
+                       intmap = &bridge_local_intmap[0];
+                       num_intmap = plen / sizeof(struct linux_prom_pci_intmap);
+                       plen = prom_getproperty(bus_pcp->prom_node,
+                                               "interrupt-map-mask",
+                                               (char *) &bridge_local_intmask,
+                                               sizeof(bridge_local_intmask));
+                       if (plen == -1) {
+                               printk("pci_intmap_match: Warning! Bridge has intmap "
+                                      "but no intmask.\n");
+                               printk("pci_intmap_match: Trying to recover.\n");
+                               return 0;
+                       }
+
+                       if (pdev->bus->self != bus_dev)
+                               map_slot = 1;
+               } else {
+                       pregs = bus_pcp->prom_regs;
+                       map_slot = 1;
+               }
+       }
+
+       if (map_slot) {
+               *interrupt = ((*interrupt
+                              - 1
+                              + PCI_SLOT(pdev->devfn)) & 0x3) + 1;
+       }
+
+       hi   = pregs->phys_hi & intmask->phys_hi;
+       mid  = pregs->phys_mid & intmask->phys_mid;
+       lo   = pregs->phys_lo & intmask->phys_lo;
+       irq  = *interrupt & intmask->interrupt;
+
+       for (i = 0; i < num_intmap; i++) {
+               if (intmap[i].phys_hi  == hi    &&
+                   intmap[i].phys_mid == mid   &&
+                   intmap[i].phys_lo  == lo    &&
+                   intmap[i].interrupt == irq) {
+                       *interrupt = intmap[i].cinterrupt;
+                       printk("PCI-IRQ: Routing bus[%2x] slot[%2x] map[%d] to INO[%02x]\n",
+                              pdev->bus->number, PCI_SLOT(pdev->devfn),
+                              map_slot, *interrupt);
+                       return 1;
+               }
+       }
+
+       /* We will run this code even if pbm->num_pbm_intmap is zero, just so
+        * we can apply the slot mapping to the PROM interrupt property value.
+        * So do not spit out these warnings in that case.
+        */
+       if (num_intmap != 0) {
+               /* Print it both to OBP console and kernel one so that if bootup
+                * hangs here the user has the information to report.
+                */
+               prom_printf("pci_intmap_match: bus %02x, devfn %02x: ",
+                           pdev->bus->number, pdev->devfn);
+               prom_printf("IRQ [%08x.%08x.%08x.%08x] not found in interrupt-map\n",
+                           pregs->phys_hi, pregs->phys_mid, pregs->phys_lo, *interrupt);
+               prom_printf("Please email this information to davem@redhat.com\n");
+
+               printk("pci_intmap_match: bus %02x, devfn %02x: ",
+                      pdev->bus->number, pdev->devfn);
+               printk("IRQ [%08x.%08x.%08x.%08x] not found in interrupt-map\n",
+                      pregs->phys_hi, pregs->phys_mid, pregs->phys_lo, *interrupt);
+               printk("Please email this information to davem@redhat.com\n");
+       }
+
+       return 0;
+}
+
 static void __init pdev_fixup_irq(struct pci_dev *pdev)
 {
        struct pcidev_cookie *pcp = pdev->sysdata;
-       struct of_device *op = pcp->op;
+       struct pci_pbm_info *pbm = pcp->pbm;
+       struct pci_controller_info *p = pbm->parent;
+       unsigned int portid = pbm->portid;
+       unsigned int prom_irq;
+       int prom_node = pcp->prom_node;
+       int err;
+
+       /* If this is an empty EBUS device, sometimes OBP fails to
+        * give it a valid fully specified interrupts property.
+        * The EBUS hooked up to SunHME on PCI I/O boards of
+        * Ex000 systems is one such case.
+        *
+        * The interrupt is not important so just ignore it.
+        */
+       if (pdev->vendor == PCI_VENDOR_ID_SUN &&
+           pdev->device == PCI_DEVICE_ID_SUN_EBUS &&
+           !prom_getchild(prom_node)) {
+               pdev->irq = 0;
+               return;
+       }
 
-       if (op->irqs[0] == 0xffffffff) {
-               pdev->irq = PCI_IRQ_NONE;
+       err = prom_getproperty(prom_node, "interrupts",
+                              (char *)&prom_irq, sizeof(prom_irq));
+       if (err == 0 || err == -1) {
+               pdev->irq = 0;
                return;
        }
 
-       pdev->irq = op->irqs[0];
+       /* Fully specified already? */
+       if (((prom_irq & PCI_IRQ_IGN) >> 6) == portid) {
+               pdev->irq = p->irq_build(pbm, pdev, prom_irq);
+               goto have_irq;
+       }
+
+       /* An onboard device? (bit 5 set) */
+       if ((prom_irq & PCI_IRQ_INO) & 0x20) {
+               pdev->irq = p->irq_build(pbm, pdev, (portid << 6 | prom_irq));
+               goto have_irq;
+       }
+
+       /* Can we find a matching entry in the interrupt-map? */
+       if (pci_intmap_match(pdev, &prom_irq)) {
+               pdev->irq = p->irq_build(pbm, pdev, (portid << 6) | prom_irq);
+               goto have_irq;
+       }
+
+       /* Ok, we have to do it the hard way. */
+       {
+               unsigned int bus, slot, line;
+
+               bus = (pbm == &pbm->parent->pbm_B) ? (1 << 4) : 0;
+
+               /* If we have a legal interrupt property, use it as
+                * the IRQ line.
+                */
+               if (prom_irq > 0 && prom_irq < 5) {
+                       line = ((prom_irq - 1) & 3);
+               } else {
+                       u8 pci_irq_line;
+
+                       /* Else just directly consult PCI config space. */
+                       pci_read_config_byte(pdev, PCI_INTERRUPT_PIN, &pci_irq_line);
+                       line = ((pci_irq_line - 1) & 3);
+               }
+
+               /* Now figure out the slot.
+                *
+                * Basically, device number zero on the top-level bus is
+                * always the PCI host controller.  Slot 0 is then device 1.
+                * PBM A supports two external slots (0 and 1), and PBM B
+                * supports 4 external slots (0, 1, 2, and 3).  On-board PCI
+                * devices are wired to device numbers outside of these
+                * ranges. -DaveM
+                */
+               if (pdev->bus->number == pbm->pci_first_busno) {
+                       slot = PCI_SLOT(pdev->devfn) - pbm->pci_first_slot;
+               } else {
+                       struct pci_dev *bus_dev;
+
+                       /* Underneath a bridge, use slot number of parent
+                        * bridge which is closest to the PBM.
+                        */
+                       bus_dev = pdev->bus->self;
+                       while (bus_dev->bus &&
+                              bus_dev->bus->number != pbm->pci_first_busno)
+                               bus_dev = bus_dev->bus->self;
+
+                       slot = PCI_SLOT(bus_dev->devfn) - pbm->pci_first_slot;
+               }
+               slot = slot << 2;
+
+               pdev->irq = p->irq_build(pbm, pdev,
+                                        ((portid << 6) & PCI_IRQ_IGN) |
+                                        (bus | slot | line));
+       }
 
+have_irq:
        pci_write_config_byte(pdev, PCI_INTERRUPT_LINE,
                              pdev->irq & PCI_IRQ_INO);
 }
@@ -706,30 +927,33 @@ void pci_register_legacy_regions(struct resource *io_res,
        struct resource *p;
 
        /* VGA Video RAM. */
-       p = kzalloc(sizeof(*p), GFP_KERNEL);
+       p = kmalloc(sizeof(*p), GFP_KERNEL);
        if (!p)
                return;
 
+       memset(p, 0, sizeof(*p));
        p->name = "Video RAM area";
        p->start = mem_res->start + 0xa0000UL;
        p->end = p->start + 0x1ffffUL;
        p->flags = IORESOURCE_BUSY;
        request_resource(mem_res, p);
 
-       p = kzalloc(sizeof(*p), GFP_KERNEL);
+       p = kmalloc(sizeof(*p), GFP_KERNEL);
        if (!p)
                return;
 
+       memset(p, 0, sizeof(*p));
        p->name = "System ROM";
        p->start = mem_res->start + 0xf0000UL;
        p->end = p->start + 0xffffUL;
        p->flags = IORESOURCE_BUSY;
        request_resource(mem_res, p);
 
-       p = kzalloc(sizeof(*p), GFP_KERNEL);
+       p = kmalloc(sizeof(*p), GFP_KERNEL);
        if (!p)
                return;
 
+       memset(p, 0, sizeof(*p));
        p->name = "Video ROM";
        p->start = mem_res->start + 0xc0000UL;
        p->end = p->start + 0x7fffUL;