]> err.no Git - linux-2.6/blobdiff - arch/powerpc/sysdev/ppc4xx_pci.c
[POWERPC] 4xx: Add 440SPe revA runtime detection to PCIe
[linux-2.6] / arch / powerpc / sysdev / ppc4xx_pci.c
index b986eff09f6b7340b5e9b225af02508d2b533297..a656dc004b82522e7e77473966b0ed0ad48079e1 100644 (file)
@@ -16,6 +16,8 @@
  *
  */
 
+#undef DEBUG
+
 #include <linux/kernel.h>
 #include <linux/pci.h>
 #include <linux/init.h>
@@ -47,6 +49,15 @@ extern unsigned long total_memory;
 #define RES_TO_U32_HIGH(val)   (0)
 #endif
 
+static inline int ppc440spe_revA(void)
+{
+       /* Catch both 440SPe variants, with and without RAID6 support */
+        if ((mfspr(SPRN_PVR) & 0xffefffff) == 0x53421890)
+                return 1;
+        else
+                return 0;
+}
+
 static void fixup_ppc4xx_pci_bridge(struct pci_dev *dev)
 {
        struct pci_controller *hose;
@@ -514,8 +525,7 @@ static void __init ppc4xx_probe_pcix_bridge(struct device_node *np)
  *
  * We support 3 parts currently based on the compatible property:
  *
- * ibm,plb-pciex-440speA
- * ibm,plb-pciex-440speB
+ * ibm,plb-pciex-440spe
  * ibm,plb-pciex-405ex
  *
  * Anything else will be rejected for now as they are all subtly
@@ -531,10 +541,13 @@ struct ppc4xx_pciex_port
        struct device_node      *node;
        unsigned int            index;
        int                     endpoint;
+       int                     link;
+       int                     has_ibpre;
        unsigned int            sdr_base;
        dcr_host_t              dcrs;
        struct resource         cfg_space;
        struct resource         utl_regs;
+       void __iomem            *utl_base;
 };
 
 static struct ppc4xx_pciex_port *ppc4xx_pciex_ports;
@@ -683,7 +696,7 @@ static int ppc440spe_pciex_init_port_hw(struct ppc4xx_pciex_port *port)
 
        mtdcri(SDR0, port->sdr_base + PESDRn_DLPSET, val);
        mtdcri(SDR0, port->sdr_base + PESDRn_UTLSET1, 0x20222222);
-       if (of_device_is_compatible(port->node, "ibm,plb-pciex-440speA"))
+       if (ppc440spe_revA())
                mtdcri(SDR0, port->sdr_base + PESDRn_UTLSET2, 0x11000000);
        mtdcri(SDR0, port->sdr_base + PESDRn_440SPE_HSSL0SET1, 0x35000000);
        mtdcri(SDR0, port->sdr_base + PESDRn_440SPE_HSSL1SET1, 0x35000000);
@@ -706,29 +719,44 @@ static int ppc440spe_pciex_init_port_hw(struct ppc4xx_pciex_port *port)
        return 0;
 }
 
-static int ppc440speA_pciex_init_utl(struct ppc4xx_pciex_port *port)
+static int ppc440speA_pciex_init_port_hw(struct ppc4xx_pciex_port *port)
 {
-       void __iomem *utl_base;
+       return ppc440spe_pciex_init_port_hw(port);
+}
+
+static int ppc440speB_pciex_init_port_hw(struct ppc4xx_pciex_port *port)
+{
+       int rc = ppc440spe_pciex_init_port_hw(port);
+
+       port->has_ibpre = 1;
 
+       return rc;
+}
+
+static int ppc440speA_pciex_init_utl(struct ppc4xx_pciex_port *port)
+{
        /* XXX Check what that value means... I hate magic */
        dcr_write(port->dcrs, DCRO_PEGPL_SPECIAL, 0x68782800);
 
-       utl_base = ioremap(port->utl_regs.start, 0x100);
-       BUG_ON(utl_base == NULL);
-
        /*
         * Set buffer allocations and then assert VRB and TXE.
         */
-       out_be32(utl_base + PEUTL_OUTTR,   0x08000000);
-       out_be32(utl_base + PEUTL_INTR,    0x02000000);
-       out_be32(utl_base + PEUTL_OPDBSZ,  0x10000000);
-       out_be32(utl_base + PEUTL_PBBSZ,   0x53000000);
-       out_be32(utl_base + PEUTL_IPHBSZ,  0x08000000);
-       out_be32(utl_base + PEUTL_IPDBSZ,  0x10000000);
-       out_be32(utl_base + PEUTL_RCIRQEN, 0x00f00000);
-       out_be32(utl_base + PEUTL_PCTL,    0x80800066);
+       out_be32(port->utl_base + PEUTL_OUTTR,   0x08000000);
+       out_be32(port->utl_base + PEUTL_INTR,    0x02000000);
+       out_be32(port->utl_base + PEUTL_OPDBSZ,  0x10000000);
+       out_be32(port->utl_base + PEUTL_PBBSZ,   0x53000000);
+       out_be32(port->utl_base + PEUTL_IPHBSZ,  0x08000000);
+       out_be32(port->utl_base + PEUTL_IPDBSZ,  0x10000000);
+       out_be32(port->utl_base + PEUTL_RCIRQEN, 0x00f00000);
+       out_be32(port->utl_base + PEUTL_PCTL,    0x80800066);
 
-       iounmap(utl_base);
+       return 0;
+}
+
+static int ppc440speB_pciex_init_utl(struct ppc4xx_pciex_port *port)
+{
+       /* Report CRS to the operating system */
+       out_be32(port->utl_base + PEUTL_PBCTL,    0x08000000);
 
        return 0;
 }
@@ -736,17 +764,17 @@ static int ppc440speA_pciex_init_utl(struct ppc4xx_pciex_port *port)
 static struct ppc4xx_pciex_hwops ppc440speA_pcie_hwops __initdata =
 {
        .core_init      = ppc440spe_pciex_core_init,
-       .port_init_hw   = ppc440spe_pciex_init_port_hw,
+       .port_init_hw   = ppc440speA_pciex_init_port_hw,
        .setup_utl      = ppc440speA_pciex_init_utl,
 };
 
 static struct ppc4xx_pciex_hwops ppc440speB_pcie_hwops __initdata =
 {
        .core_init      = ppc440spe_pciex_core_init,
-       .port_init_hw   = ppc440spe_pciex_init_port_hw,
+       .port_init_hw   = ppc440speB_pciex_init_port_hw,
+       .setup_utl      = ppc440speB_pciex_init_utl,
 };
 
-
 #endif /* CONFIG_44x */
 
 #ifdef CONFIG_40x
@@ -821,30 +849,21 @@ static int ppc405ex_pciex_init_port_hw(struct ppc4xx_pciex_port *port)
 
 static int ppc405ex_pciex_init_utl(struct ppc4xx_pciex_port *port)
 {
-       void __iomem *utl_base;
-
        dcr_write(port->dcrs, DCRO_PEGPL_SPECIAL, 0x0);
 
-       utl_base = ioremap(port->utl_regs.start, 0x100);
-       BUG_ON(utl_base == NULL);
-
        /*
         * Set buffer allocations and then assert VRB and TXE.
         */
-       out_be32(utl_base + PEUTL_OUTTR,   0x02000000);
-       out_be32(utl_base + PEUTL_INTR,    0x02000000);
-       out_be32(utl_base + PEUTL_OPDBSZ,  0x04000000);
-       out_be32(utl_base + PEUTL_PBBSZ,   0x21000000);
-       out_be32(utl_base + PEUTL_IPHBSZ,  0x02000000);
-       out_be32(utl_base + PEUTL_IPDBSZ,  0x04000000);
-       out_be32(utl_base + PEUTL_RCIRQEN, 0x00f00000);
-       out_be32(utl_base + PEUTL_PCTL,    0x80800066);
+       out_be32(port->utl_base + PEUTL_OUTTR,   0x02000000);
+       out_be32(port->utl_base + PEUTL_INTR,    0x02000000);
+       out_be32(port->utl_base + PEUTL_OPDBSZ,  0x04000000);
+       out_be32(port->utl_base + PEUTL_PBBSZ,   0x21000000);
+       out_be32(port->utl_base + PEUTL_IPHBSZ,  0x02000000);
+       out_be32(port->utl_base + PEUTL_IPDBSZ,  0x04000000);
+       out_be32(port->utl_base + PEUTL_RCIRQEN, 0x00f00000);
+       out_be32(port->utl_base + PEUTL_PCTL,    0x80800066);
 
-       out_be32(utl_base + PEUTL_PBCTL,   0x0800000c);
-       out_be32(utl_base + PEUTL_RCSTA,
-                in_be32(utl_base + PEUTL_RCSTA) | 0x000040000);
-
-       iounmap(utl_base);
+       out_be32(port->utl_base + PEUTL_PBCTL,   0x08000000);
 
        return 0;
 }
@@ -869,10 +888,12 @@ static int __init ppc4xx_pciex_check_core_init(struct device_node *np)
                return 0;
 
 #ifdef CONFIG_44x
-       if (of_device_is_compatible(np, "ibm,plb-pciex-440speA"))
-               ppc4xx_pciex_hwops = &ppc440speA_pcie_hwops;
-       else if (of_device_is_compatible(np, "ibm,plb-pciex-440speB"))
-               ppc4xx_pciex_hwops = &ppc440speB_pcie_hwops;
+       if (of_device_is_compatible(np, "ibm,plb-pciex-440spe")) {
+               if (ppc440spe_revA())
+                       ppc4xx_pciex_hwops = &ppc440speA_pcie_hwops;
+               else
+                       ppc4xx_pciex_hwops = &ppc440speB_pcie_hwops;
+       }
 #endif /* CONFIG_44x    */
 #ifdef CONFIG_40x
        if (of_device_is_compatible(np, "ibm,plb-pciex-405ex"))
@@ -926,17 +947,29 @@ static void __init ppc4xx_pciex_port_init_mapping(struct ppc4xx_pciex_port *port
        dcr_write(port->dcrs, DCRO_PEGPL_MSGMSK, 0);
 }
 
-static int __init ppc4xx_pciex_port_init(struct ppc4xx_pciex_port *port)
+static int __init ppc4xx_pciex_wait_on_sdr(struct ppc4xx_pciex_port *port,
+                                          unsigned int sdr_offset,
+                                          unsigned int mask,
+                                          unsigned int value,
+                                          int timeout_ms)
 {
-       int attempts, rc = 0;
        u32 val;
 
-       /* Check if it's endpoint or root complex
-        *
-        * XXX Do we want to use the device-tree instead ? --BenH.
-        */
-       val = mfdcri(SDR0, port->sdr_base + PESDRn_DLPSET);
-       port->endpoint = (((val >> 20) & 0xf) != PTYPE_ROOT_PORT);
+       while(timeout_ms--) {
+               val = mfdcri(SDR0, port->sdr_base + sdr_offset);
+               if ((val & mask) == value) {
+                       pr_debug("PCIE%d: Wait on SDR %x success with tm %d (%08x)\n",
+                                port->index, sdr_offset, timeout_ms, val);
+                       return 0;
+               }
+               msleep(1);
+       }
+       return -1;
+}
+
+static int __init ppc4xx_pciex_port_init(struct ppc4xx_pciex_port *port)
+{
+       int rc = 0;
 
        /* Init HW */
        if (ppc4xx_pciex_hwops->port_init_hw)
@@ -944,44 +977,40 @@ static int __init ppc4xx_pciex_port_init(struct ppc4xx_pciex_port *port)
        if (rc != 0)
                return rc;
 
-       /*
-        * Notice: the following delay has critical impact on device
-        * initialization - if too short (<50ms) the link doesn't get up.
-        *
-        * XXX FIXME: There are various issues with that link up thingy,
-        * we could just wait for the link with a timeout but Stefan says
-        * some cards need more time even after the link is up. I'll
-        * investigate. For now, we keep a fixed 1s delay.
-        *
-        * Ultimately, it should be made asynchronous so all ports are
-        * brought up simultaneously though.
-        */
-       printk(KERN_INFO "PCIE%d: Waiting for link to go up...\n",
+       printk(KERN_INFO "PCIE%d: Checking link...\n",
               port->index);
-       msleep(1000);
 
-       /*
-        * Check that we exited the reset state properly
-        */
-       val = mfdcri(SDR0, port->sdr_base + PESDRn_RCSSTS);
-       if (val & (1 << 20)) {
-               printk(KERN_WARNING "PCIE%d: PGRST failed %08x\n",
-                      port->index, val);
+       /* Wait for reset to complete */
+       if (ppc4xx_pciex_wait_on_sdr(port, PESDRn_RCSSTS, 1 << 20, 0, 10)) {
+               printk(KERN_WARNING "PCIE%d: PGRST failed\n",
+                      port->index);
                return -1;
        }
 
-       /*
-        * Verify link is up
+       /* Check for card presence detect if supported, if not, just wait for
+        * link unconditionally.
+        *
+        * note that we don't fail if there is no link, we just filter out
+        * config space accesses. That way, it will be easier to implement
+        * hotplug later on.
         */
-       val = mfdcri(SDR0, port->sdr_base + PESDRn_LOOP);
-       if (!(val & 0x00001000)) {
-               printk(KERN_INFO "PCIE%d: link is not up !\n",
+       if (!port->has_ibpre ||
+           !ppc4xx_pciex_wait_on_sdr(port, PESDRn_LOOP,
+                                     1 << 28, 1 << 28, 100)) {
+               printk(KERN_INFO
+                      "PCIE%d: Device detected, waiting for link...\n",
                       port->index);
-               return -1;
-       }
-
-       printk(KERN_INFO "PCIE%d: link is up !\n",
-              port->index);
+               if (ppc4xx_pciex_wait_on_sdr(port, PESDRn_LOOP,
+                                            0x1000, 0x1000, 2000))
+                       printk(KERN_WARNING
+                              "PCIE%d: Link up failed\n", port->index);
+               else {
+                       printk(KERN_INFO
+                              "PCIE%d: link is up !\n", port->index);
+                       port->link = 1;
+               }
+       } else
+               printk(KERN_INFO "PCIE%d: No device detected.\n", port->index);
 
        /*
         * Initialize mapping: disable all regions and configure
@@ -990,12 +1019,13 @@ static int __init ppc4xx_pciex_port_init(struct ppc4xx_pciex_port *port)
        ppc4xx_pciex_port_init_mapping(port);
 
        /*
-        * Setup UTL registers - but only on revA!
-        * We use default settings for revB chip.
-        *
-        * To be reworked. We may also be able to move that to
-        * before the link wait
-        * --BenH.
+        * Map UTL
+        */
+       port->utl_base = ioremap(port->utl_regs.start, 0x100);
+       BUG_ON(port->utl_base == NULL);
+
+       /*
+        * Setup UTL registers --BenH.
         */
        if (ppc4xx_pciex_hwops->setup_utl)
                ppc4xx_pciex_hwops->setup_utl(port);
@@ -1003,15 +1033,13 @@ static int __init ppc4xx_pciex_port_init(struct ppc4xx_pciex_port *port)
        /*
         * Check for VC0 active and assert RDY.
         */
-       attempts = 10;
-       while (!(mfdcri(SDR0, port->sdr_base + PESDRn_RCSSTS) & (1 << 16))) {
-               if (!(attempts--)) {
-                       printk(KERN_INFO "PCIE%d: VC0 not active\n",
-                              port->index);
-                       return -1;
-               }
-               msleep(1000);
+       if (port->link &&
+           ppc4xx_pciex_wait_on_sdr(port, PESDRn_RCSSTS,
+                                    1 << 16, 1 << 16, 5000)) {
+               printk(KERN_INFO "PCIE%d: VC0 not active\n", port->index);
+               port->link = 0;
        }
+
        mtdcri(SDR0, port->sdr_base + PESDRn_RCSSET,
               mfdcri(SDR0, port->sdr_base + PESDRn_RCSSET) | 1 << 20);
        msleep(100);
@@ -1048,6 +1076,10 @@ static int ppc4xx_pciex_validate_bdf(struct ppc4xx_pciex_port *port,
            PCI_SLOT(devfn) != 0)
                return PCIBIOS_DEVICE_NOT_FOUND;
 
+       /* Check if we have a link */
+       if ((bus->number != port->hose->first_busno) && !port->link)
+               return PCIBIOS_DEVICE_NOT_FOUND;
+
        return 0;
 }
 
@@ -1092,6 +1124,9 @@ static int ppc4xx_pciex_read_config(struct pci_bus *bus, unsigned int devfn,
        gpl_cfg = dcr_read(port->dcrs, DCRO_PEGPL_CFG);
        dcr_write(port->dcrs, DCRO_PEGPL_CFG, gpl_cfg | GPL_DMER_MASK_DISA);
 
+       /* Make sure no CRS is recorded */
+       out_be32(port->utl_base + PEUTL_RCSTA, 0x00040000);
+
        switch (len) {
        case 1:
                *val = in_8((u8 *)(addr + offset));
@@ -1109,6 +1144,14 @@ static int ppc4xx_pciex_read_config(struct pci_bus *bus, unsigned int devfn,
                 bus->number, hose->first_busno, hose->last_busno,
                 devfn, offset, len, addr + offset, *val);
 
+       /* Check for CRS (440SPe rev B does that for us but heh ..) */
+       if (in_be32(port->utl_base + PEUTL_RCSTA) & 0x00040000) {
+               pr_debug("Got CRS !\n");
+               if (len != 4 || offset != 0)
+                       return PCIBIOS_DEVICE_NOT_FOUND;
+               *val = 0xffff0001;
+       }
+
        dcr_write(port->dcrs, DCRO_PEGPL_CFG, gpl_cfg);
 
        return PCIBIOS_SUCCESSFUL;
@@ -1278,8 +1321,11 @@ static void __init ppc4xx_pciex_port_setup_hose(struct ppc4xx_pciex_port *port)
        void __iomem *mbase = NULL, *cfg_data = NULL;
 
        /* XXX FIXME: Handle endpoint mode properly */
-       if (port->endpoint)
+       if (port->endpoint) {
+               printk(KERN_WARNING "PCIE%d: Port in endpoint mode !\n",
+                      port->index);
                return;
+       }
 
        /* Check if primary bridge */
        if (of_get_property(port->node, "primary", NULL))
@@ -1424,6 +1470,9 @@ static void __init ppc4xx_probe_pciex_bridge(struct device_node *np)
        }
        port->sdr_base = *pval;
 
+       /* XXX Currently, we only support root complex mode */
+       port->endpoint = 0;
+
        /* Fetch config space registers address */
        if (of_address_to_resource(np, 0, &port->cfg_space)) {
                printk(KERN_ERR "%s: Can't get PCI-E config space !",
@@ -1447,8 +1496,10 @@ static void __init ppc4xx_probe_pciex_bridge(struct device_node *np)
        port->dcrs = dcr_map(np, dcrs, dcr_resource_len(np, 0));
 
        /* Initialize the port specific registers */
-       if (ppc4xx_pciex_port_init(port))
+       if (ppc4xx_pciex_port_init(port)) {
+               printk(KERN_WARNING "PCIE%d: Port init failed\n", port->index);
                return;
+       }
 
        /* Setup the linux hose data structure */
        ppc4xx_pciex_port_setup_hose(port);