[POWERPC] 4xx: PLB to PCI-X support
authorBenjamin Herrenschmidt <benh@kernel.crashing.org>
Fri, 21 Dec 2007 04:39:22 +0000 (15:39 +1100)
committerJosh Boyer <jwboyer@linux.vnet.ibm.com>
Sun, 23 Dec 2007 19:12:20 +0000 (13:12 -0600)
This adds base support code for the 4xx PCI-X bridge. It also provides
placeholders for the PCI and PCI-E version but they aren't supported
with this patch.

The bridges are configured based on device-tree properties.

Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Signed-off-by: Josh Boyer <jwboyer@linux.vnet.ibm.com>
arch/powerpc/sysdev/Makefile
arch/powerpc/sysdev/ppc4xx_pci.c [new file with mode: 0644]
arch/powerpc/sysdev/ppc4xx_pci.h [new file with mode: 0644]

index 85cf8c6..9a20ef4 100644 (file)
@@ -27,6 +27,9 @@ obj-$(CONFIG_PPC_I8259)               += i8259.o
 obj-$(CONFIG_PPC_83xx)         += ipic.o
 obj-$(CONFIG_4xx)              += uic.o
 obj-$(CONFIG_XILINX_VIRTEX)    += xilinx_intc.o
+ifeq ($(CONFIG_PCI),y)
+obj-$(CONFIG_4xx)              += ppc4xx_pci.o
+endif
 endif
 
 # Temporary hack until we have migrated to asm-powerpc
diff --git a/arch/powerpc/sysdev/ppc4xx_pci.c b/arch/powerpc/sysdev/ppc4xx_pci.c
new file mode 100644 (file)
index 0000000..a0b6239
--- /dev/null
@@ -0,0 +1,339 @@
+/*
+ * PCI / PCI-X / PCI-Express support for 4xx parts
+ *
+ * Copyright 2007 Ben. Herrenschmidt <benh@kernel.crashing.org>, IBM Corp.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/pci.h>
+#include <linux/init.h>
+#include <linux/of.h>
+
+#include <asm/io.h>
+#include <asm/pci-bridge.h>
+#include <asm/machdep.h>
+
+#include "ppc4xx_pci.h"
+
+static int dma_offset_set;
+
+/* Move that to a useable header */
+extern unsigned long total_memory;
+
+static int __init ppc4xx_parse_dma_ranges(struct pci_controller *hose,
+                                         void __iomem *reg,
+                                         struct resource *res)
+{
+       u64 size;
+       const u32 *ranges;
+       int rlen;
+       int pna = of_n_addr_cells(hose->dn);
+       int np = pna + 5;
+
+       /* Default */
+       res->start = 0;
+       res->end = size = 0x80000000;
+       res->flags = IORESOURCE_MEM | IORESOURCE_PREFETCH;
+
+       /* Get dma-ranges property */
+       ranges = of_get_property(hose->dn, "dma-ranges", &rlen);
+       if (ranges == NULL)
+               goto out;
+
+       /* Walk it */
+       while ((rlen -= np * 4) >= 0) {
+               u32 pci_space = ranges[0];
+               u64 pci_addr = of_read_number(ranges + 1, 2);
+               u64 cpu_addr = of_translate_dma_address(hose->dn, ranges + 3);
+               size = of_read_number(ranges + pna + 3, 2);
+               ranges += np;
+               if (cpu_addr == OF_BAD_ADDR || size == 0)
+                       continue;
+
+               /* We only care about memory */
+               if ((pci_space & 0x03000000) != 0x02000000)
+                       continue;
+
+               /* We currently only support memory at 0, and pci_addr
+                * within 32 bits space
+                */
+               if (cpu_addr != 0 || pci_addr > 0xffffffff) {
+                       printk(KERN_WARNING "%s: Ignored unsupported dma range"
+                              " 0x%016llx...0x%016llx -> 0x%016llx\n",
+                              hose->dn->full_name,
+                              pci_addr, pci_addr + size - 1, cpu_addr);
+                       continue;
+               }
+
+               /* Check if not prefetchable */
+               if (!(pci_space & 0x40000000))
+                       res->flags &= ~IORESOURCE_PREFETCH;
+
+
+               /* Use that */
+               res->start = pci_addr;
+#ifndef CONFIG_RESOURCES_64BIT
+               /* Beware of 32 bits resources */
+               if ((pci_addr + size) > 0x100000000ull)
+                       res->end = 0xffffffff;
+               else
+#endif
+                       res->end = res->start + size - 1;
+               break;
+       }
+
+       /* We only support one global DMA offset */
+       if (dma_offset_set && pci_dram_offset != res->start) {
+               printk(KERN_ERR "%s: dma-ranges(s) mismatch\n",
+                      hose->dn->full_name);
+               return -ENXIO;
+       }
+
+       /* Check that we can fit all of memory as we don't support
+        * DMA bounce buffers
+        */
+       if (size < total_memory) {
+               printk(KERN_ERR "%s: dma-ranges too small "
+                      "(size=%llx total_memory=%lx)\n",
+                      hose->dn->full_name, size, total_memory);
+               return -ENXIO;
+       }
+
+       /* Check we are a power of 2 size and that base is a multiple of size*/
+       if (!is_power_of_2(size) ||
+           (res->start & (size - 1)) != 0) {
+               printk(KERN_ERR "%s: dma-ranges unaligned\n",
+                      hose->dn->full_name);
+               return -ENXIO;
+       }
+
+       /* Check that we are fully contained within 32 bits space */
+       if (res->end > 0xffffffff) {
+               printk(KERN_ERR "%s: dma-ranges outside of 32 bits space\n",
+                      hose->dn->full_name);
+               return -ENXIO;
+       }
+ out:
+       dma_offset_set = 1;
+       pci_dram_offset = res->start;
+
+       printk(KERN_INFO "4xx PCI DMA offset set to 0x%08lx\n",
+              pci_dram_offset);
+       return 0;
+}
+
+/*
+ * 4xx PCI 2.x part
+ */
+static void __init ppc4xx_probe_pci_bridge(struct device_node *np)
+{
+       /* NYI */
+}
+
+/*
+ * 4xx PCI-X part
+ */
+
+static void __init ppc4xx_configure_pcix_POMs(struct pci_controller *hose,
+                                             void __iomem *reg)
+{
+       u32 lah, lal, pciah, pcial, sa;
+       int i, j;
+
+       /* Setup outbound memory windows */
+       for (i = j = 0; i < 3; i++) {
+               struct resource *res = &hose->mem_resources[i];
+
+               /* we only care about memory windows */
+               if (!(res->flags & IORESOURCE_MEM))
+                       continue;
+               if (j > 1) {
+                       printk(KERN_WARNING "%s: Too many ranges\n",
+                              hose->dn->full_name);
+                       break;
+               }
+
+               /* Calculate register values */
+#ifdef CONFIG_PTE_64BIT
+               lah = res->start >> 32;
+               lal = res->start & 0xffffffffu;
+               pciah = (res->start - hose->pci_mem_offset) >> 32;
+               pcial = (res->start - hose->pci_mem_offset) & 0xffffffffu;
+#else
+               lah = pciah = 0;
+               lal = res->start;
+               pcial = res->start - hose->pci_mem_offset;
+#endif
+               sa = res->end + 1 - res->start;
+               if (!is_power_of_2(sa) || sa < 0x100000 ||
+                   sa > 0xffffffffu) {
+                       printk(KERN_WARNING "%s: Resource out of range\n",
+                              hose->dn->full_name);
+                       continue;
+               }
+               sa = (0xffffffffu << ilog2(sa)) | 0x1;
+
+               /* Program register values */
+               if (j == 0) {
+                       writel(lah, reg + PCIX0_POM0LAH);
+                       writel(lal, reg + PCIX0_POM0LAL);
+                       writel(pciah, reg + PCIX0_POM0PCIAH);
+                       writel(pcial, reg + PCIX0_POM0PCIAL);
+                       writel(sa, reg + PCIX0_POM0SA);
+               } else {
+                       writel(lah, reg + PCIX0_POM1LAH);
+                       writel(lal, reg + PCIX0_POM1LAL);
+                       writel(pciah, reg + PCIX0_POM1PCIAH);
+                       writel(pcial, reg + PCIX0_POM1PCIAL);
+                       writel(sa, reg + PCIX0_POM1SA);
+               }
+               j++;
+       }
+}
+
+static void __init ppc4xx_configure_pcix_PIMs(struct pci_controller *hose,
+                                             void __iomem *reg,
+                                             const struct resource *res,
+                                             int big_pim,
+                                             int enable_msi_hole)
+{
+       resource_size_t size = res->end - res->start + 1;
+       u32 sa;
+
+       /* RAM is always at 0 */
+       writel(0x00000000, reg + PCIX0_PIM0LAH);
+       writel(0x00000000, reg + PCIX0_PIM0LAL);
+
+       /* Calculate window size */
+       sa = (0xffffffffu << ilog2(size)) | 1;
+       sa |= 0x1;
+       if (res->flags & IORESOURCE_PREFETCH)
+               sa |= 0x2;
+       if (enable_msi_hole)
+               sa |= 0x4;
+       writel(sa, reg + PCIX0_PIM0SA);
+       if (big_pim)
+               writel(0xffffffff, reg + PCIX0_PIM0SAH);
+
+       /* Map on PCI side */
+       writel(0x00000000, reg + PCIX0_BAR0H);
+       writel(res->start, reg + PCIX0_BAR0L);
+       writew(0x0006, reg + PCIX0_COMMAND);
+}
+
+static void __init ppc4xx_probe_pcix_bridge(struct device_node *np)
+{
+       struct resource rsrc_cfg;
+       struct resource rsrc_reg;
+       struct resource dma_window;
+       struct pci_controller *hose = NULL;
+       void __iomem *reg = NULL;
+       const int *bus_range;
+       int big_pim = 0, msi = 0, primary = 0;
+
+       /* Fetch config space registers address */
+       if (of_address_to_resource(np, 0, &rsrc_cfg)) {
+               printk(KERN_ERR "%s:Can't get PCI-X config register base !",
+                      np->full_name);
+               return;
+       }
+       /* Fetch host bridge internal registers address */
+       if (of_address_to_resource(np, 3, &rsrc_reg)) {
+               printk(KERN_ERR "%s: Can't get PCI-X internal register base !",
+                      np->full_name);
+               return;
+       }
+
+       /* Check if it supports large PIMs (440GX) */
+       if (of_get_property(np, "large-inbound-windows", NULL))
+               big_pim = 1;
+
+       /* Check if we should enable MSIs inbound hole */
+       if (of_get_property(np, "enable-msi-hole", NULL))
+               msi = 1;
+
+       /* Check if primary bridge */
+       if (of_get_property(np, "primary", NULL))
+               primary = 1;
+
+       /* Get bus range if any */
+       bus_range = of_get_property(np, "bus-range", NULL);
+
+       /* Map registers */
+       reg = ioremap(rsrc_reg.start, rsrc_reg.end + 1 - rsrc_reg.start);
+       if (reg == NULL) {
+               printk(KERN_ERR "%s: Can't map registers !", np->full_name);
+               goto fail;
+       }
+
+       /* Allocate the host controller data structure */
+       hose = pcibios_alloc_controller(np);
+       if (!hose)
+               goto fail;
+
+       hose->first_busno = bus_range ? bus_range[0] : 0x0;
+       hose->last_busno = bus_range ? bus_range[1] : 0xff;
+
+       /* Setup config space */
+       setup_indirect_pci(hose, rsrc_cfg.start, rsrc_cfg.start + 0x4, 0);
+
+       /* Disable all windows */
+       writel(0, reg + PCIX0_POM0SA);
+       writel(0, reg + PCIX0_POM1SA);
+       writel(0, reg + PCIX0_POM2SA);
+       writel(0, reg + PCIX0_PIM0SA);
+       writel(0, reg + PCIX0_PIM1SA);
+       writel(0, reg + PCIX0_PIM2SA);
+       if (big_pim) {
+               writel(0, reg + PCIX0_PIM0SAH);
+               writel(0, reg + PCIX0_PIM2SAH);
+       }
+
+       /* Parse outbound mapping resources */
+       pci_process_bridge_OF_ranges(hose, np, primary);
+
+       /* Parse inbound mapping resources */
+       if (ppc4xx_parse_dma_ranges(hose, reg, &dma_window) != 0)
+               goto fail;
+
+       /* Configure outbound ranges POMs */
+       ppc4xx_configure_pcix_POMs(hose, reg);
+
+       /* Configure inbound ranges PIMs */
+       ppc4xx_configure_pcix_PIMs(hose, reg, &dma_window, big_pim, msi);
+
+       /* We don't need the registers anymore */
+       iounmap(reg);
+       return;
+
+ fail:
+       if (hose)
+               pcibios_free_controller(hose);
+       if (reg)
+               iounmap(reg);
+}
+
+/*
+ * 4xx PCI-Express part
+ */
+static void __init ppc4xx_probe_pciex_bridge(struct device_node *np)
+{
+       /* NYI */
+}
+
+static int __init ppc4xx_pci_find_bridges(void)
+{
+       struct device_node *np;
+
+       for_each_compatible_node(np, NULL, "ibm,plb-pciex")
+               ppc4xx_probe_pciex_bridge(np);
+       for_each_compatible_node(np, NULL, "ibm,plb-pcix")
+               ppc4xx_probe_pcix_bridge(np);
+       for_each_compatible_node(np, NULL, "ibm,plb-pci")
+               ppc4xx_probe_pci_bridge(np);
+
+       return 0;
+}
+arch_initcall(ppc4xx_pci_find_bridges);
+
diff --git a/arch/powerpc/sysdev/ppc4xx_pci.h b/arch/powerpc/sysdev/ppc4xx_pci.h
new file mode 100644 (file)
index 0000000..f9b4e6f
--- /dev/null
@@ -0,0 +1,106 @@
+/*
+ * PCI / PCI-X / PCI-Express support for 4xx parts
+ *
+ * Copyright 2007 Ben. Herrenschmidt <benh@kernel.crashing.org>, IBM Corp.
+ *
+ * Bits and pieces extracted from arch/ppc support by
+ *
+ * Matt Porter <mporter@kernel.crashing.org>
+ *
+ * Copyright 2002-2005 MontaVista Software Inc.
+ */
+#ifndef __PPC4XX_PCI_H__
+#define __PPC4XX_PCI_H__
+
+/*
+ * 4xx PCI-X bridge register definitions
+ */
+#define PCIX0_VENDID           0x000
+#define PCIX0_DEVID            0x002
+#define PCIX0_COMMAND          0x004
+#define PCIX0_STATUS           0x006
+#define PCIX0_REVID            0x008
+#define PCIX0_CLS              0x009
+#define PCIX0_CACHELS          0x00c
+#define PCIX0_LATTIM           0x00d
+#define PCIX0_HDTYPE           0x00e
+#define PCIX0_BIST             0x00f
+#define PCIX0_BAR0L            0x010
+#define PCIX0_BAR0H            0x014
+#define PCIX0_BAR1             0x018
+#define PCIX0_BAR2L            0x01c
+#define PCIX0_BAR2H            0x020
+#define PCIX0_BAR3             0x024
+#define PCIX0_CISPTR           0x028
+#define PCIX0_SBSYSVID         0x02c
+#define PCIX0_SBSYSID          0x02e
+#define PCIX0_EROMBA           0x030
+#define PCIX0_CAP              0x034
+#define PCIX0_RES0             0x035
+#define PCIX0_RES1             0x036
+#define PCIX0_RES2             0x038
+#define PCIX0_INTLN            0x03c
+#define PCIX0_INTPN            0x03d
+#define PCIX0_MINGNT           0x03e
+#define PCIX0_MAXLTNCY         0x03f
+#define PCIX0_BRDGOPT1         0x040
+#define PCIX0_BRDGOPT2         0x044
+#define PCIX0_ERREN            0x050
+#define PCIX0_ERRSTS           0x054
+#define PCIX0_PLBBESR          0x058
+#define PCIX0_PLBBEARL         0x05c
+#define PCIX0_PLBBEARH         0x060
+#define PCIX0_POM0LAL          0x068
+#define PCIX0_POM0LAH          0x06c
+#define PCIX0_POM0SA           0x070
+#define PCIX0_POM0PCIAL                0x074
+#define PCIX0_POM0PCIAH                0x078
+#define PCIX0_POM1LAL          0x07c
+#define PCIX0_POM1LAH          0x080
+#define PCIX0_POM1SA           0x084
+#define PCIX0_POM1PCIAL                0x088
+#define PCIX0_POM1PCIAH                0x08c
+#define PCIX0_POM2SA           0x090
+#define PCIX0_PIM0SAL          0x098
+#define PCIX0_PIM0SA           PCIX0_PIM0SAL
+#define PCIX0_PIM0LAL          0x09c
+#define PCIX0_PIM0LAH          0x0a0
+#define PCIX0_PIM1SA           0x0a4
+#define PCIX0_PIM1LAL          0x0a8
+#define PCIX0_PIM1LAH          0x0ac
+#define PCIX0_PIM2SAL          0x0b0
+#define PCIX0_PIM2SA           PCIX0_PIM2SAL
+#define PCIX0_PIM2LAL          0x0b4
+#define PCIX0_PIM2LAH          0x0b8
+#define PCIX0_OMCAPID          0x0c0
+#define PCIX0_OMNIPTR          0x0c1
+#define PCIX0_OMMC             0x0c2
+#define PCIX0_OMMA             0x0c4
+#define PCIX0_OMMUA            0x0c8
+#define PCIX0_OMMDATA          0x0cc
+#define PCIX0_OMMEOI           0x0ce
+#define PCIX0_PMCAPID          0x0d0
+#define PCIX0_PMNIPTR          0x0d1
+#define PCIX0_PMC              0x0d2
+#define PCIX0_PMCSR            0x0d4
+#define PCIX0_PMCSRBSE         0x0d6
+#define PCIX0_PMDATA           0x0d7
+#define PCIX0_PMSCRR           0x0d8
+#define PCIX0_CAPID            0x0dc
+#define PCIX0_NIPTR            0x0dd
+#define PCIX0_CMD              0x0de
+#define PCIX0_STS              0x0e0
+#define PCIX0_IDR              0x0e4
+#define PCIX0_CID              0x0e8
+#define PCIX0_RID              0x0ec
+#define PCIX0_PIM0SAH          0x0f8
+#define PCIX0_PIM2SAH          0x0fc
+#define PCIX0_MSGIL            0x100
+#define PCIX0_MSGIH            0x104
+#define PCIX0_MSGOL            0x108
+#define PCIX0_MSGOH            0x10c
+#define PCIX0_IM               0x1f8
+
+
+
+#endif /* __PPC4XX_PCI_H__ */