|
@@ -16,6 +16,8 @@
|
|
|
*
|
|
|
*/
|
|
|
|
|
|
+#undef DEBUG
|
|
|
+
|
|
|
#include <linux/kernel.h>
|
|
|
#include <linux/pci.h>
|
|
|
#include <linux/init.h>
|
|
@@ -531,10 +533,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;
|
|
@@ -706,29 +711,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)
|
|
|
+{
|
|
|
+ return ppc440spe_pciex_init_port_hw(port);
|
|
|
+}
|
|
|
+
|
|
|
+static int ppc440speB_pciex_init_port_hw(struct ppc4xx_pciex_port *port)
|
|
|
{
|
|
|
- void __iomem *utl_base;
|
|
|
+ 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,14 +756,15 @@ 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,
|
|
|
};
|
|
|
|
|
|
|
|
@@ -821,30 +842,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;
|
|
|
}
|
|
@@ -926,17 +938,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 +968,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 +1010,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 +1024,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 +1067,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 +1115,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 +1135,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 +1312,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 +1461,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 +1487,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);
|