[PATCH 1/6] Realview PCIX support - add main support module code
Colin Tuckley
colin.tuckley at arm.com
Wed Oct 20 09:02:54 EDT 2010
This patch adds pcix.c - the main pci sub-system code for
the RealView boards which have PCI and PCIe interfaces
via a custom NEC northbridge chip and two bridges.
Signed-off-by: Colin Tuckley <colin.tuckley at arm.com>
Acked-by: Catalin Marinas <catalin.marinas at arm.com>
---
arch/arm/mach-realview/pcix.c | 538 +++++++++++++++++++++++++++++++++++++++++
1 files changed, 538 insertions(+), 0 deletions(-)
create mode 100755 arch/arm/mach-realview/pcix.c
diff --git a/arch/arm/mach-realview/pcix.c b/arch/arm/mach-realview/pcix.c
new file mode 100755
index 0000000..1318403
--- /dev/null
+++ b/arch/arm/mach-realview/pcix.c
@@ -0,0 +1,538 @@
+/*
+ * Realview PCIX Support
+ *
+ * Copyright (C) 2007-2010 ARM
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <linux/kernel.h>
+#include <linux/pci.h>
+#include <linux/ptrace.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <linux/interrupt.h>
+#include <linux/spinlock.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <mach/platform.h>
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/system.h>
+#include <asm/mach/pci.h>
+
+#define PCIX_READ_ACCESS 0x06000000
+#define PCIX_WRITE_ACCESS 0x07000000
+
+/* PCI-X Configuration registers */
+#define PCI_VENID 0x00
+#define PCI_DEVID 0x02
+#define PCI_COMMAND 0x04
+#define PCI_STATUS 0x06
+#define PCI_REVID 0x08
+#define PCI_CLCODE 0x09
+#define PCI_CALISIZE 0x0C
+#define PCI_LTTIMER 0x0D
+#define PCI_HDTYPE 0x0E
+#define PCI_PTBADDR0L 0x10
+#define PCI_PTBADDR0M 0x14
+#define PCI_PTBADDR1L 0x18
+#define PCI_PTBADDR1M 0x1C
+#define PCI_PCIIOLMT 0x1D
+#define PCI_PCISSR 0x1E
+#define PCI_PCIMBAR 0x20
+#define PCI_PCIMLMT 0x22
+#define PCI_PCIPMBAR 0x24
+#define PCI_PCIPMLMT 0x26
+#define PCI_PCIPMBARU32 0x28
+#define PCI_SUBSYSVENID 0x2C
+#define PCI_PCIPMLMTU32 0x2C
+#define PCI_SUBSYSID 0x2E
+#define PCI_CAPPOINT 0x34
+#define PCI_INTLINE 0x3C
+#define PCI_INTPIN 0x3D
+#define PCI_BCNTRL 0x3E
+#define PCI_CFGADDR 0x40
+#define PCI_CFGDATA 0x44
+#define PCI_CFGATTR 0x48
+#define PCI_PINTACNT 0x50
+#define PCI_PINTXSTAT 0x54
+#define PCI_PINTXMASK 0x58
+#define PCI_SERRSTAT 0x5C
+#define PCI_PERRADDRL 0x60
+#define PCI_PERRADDRH 0x64
+#define PCI_PERRSTAT 0x68
+#define PCI_PERRMASK 0x6C
+#define PCI_OCBERRSTAT 0x78
+#define PCI_OCBERRMASK 0x7C
+#define PCI_MSICAPID 0x80
+#define PCI_MSINXTCAP 0x81
+#define PCI_MSGCNT 0x82
+#define PCI_MSGADDR 0x84
+#define PCI_MSGUPADDR 0x88
+#define PCI_MSGDATA 0x8C
+#define PCI_PCIXCAPID 0x90
+#define PCI_PCIXNXTCAP 0x91
+#define PCI_PCIXCOMMAND 0x92
+#define PCI_PCIXSTAT 0x94
+#define PCI_SPCMPERRMSG 0x98
+#define PCI_PTMEMSEG0 0xA0
+#define PCI_PTMEMMSK0 0xA4
+#define PCI_PTMEMENSW0 0xA8
+#define PCI_PTMEMSEG1 0xB0
+#define PCI_PTMEMMSK1 0xB4
+#define PCI_PTMEMENSW1 0xB8
+#define PCI_PMSEGL 0xC0
+#define PCI_PMSEGH 0xC4
+#define PCI_DRSTCNT 0xEC
+#define PCI_PRST 0xF0
+#define PCI_UNITCNT 0xF4
+#define PCI_CNTOTIMER 0xF8
+#define PCI_PSLADAFLASH 0xFC
+
+static int irq_gic_start;
+
+/*
+ * Software is exposed to downstream half of the PCI-X bridge registers
+ * rather than a real PCI interface. Therefore there's no config memory
+ * region as such.
+ *
+ * PCI Regions:
+ * MEM 0xA0000000 - 0xBFFFFFFF
+ * IO 0x00000000 - 0x0000FFFF
+ * CONFIG -
+ */
+
+static void realview_pb_pcix_sync(void)
+{
+ readl(PCIX_UNIT_BASE + PCI_VENID);
+}
+
+static void realview_pb_pcix_unit_init(void)
+{
+ u32 data = readl(PCIX_UNIT_BASE + PCI_UNITCNT);
+
+ if (data & 0x10)
+ writel(0x00000000, PCIX_UNIT_BASE + PCI_PRST); /* Assert PCI reset */
+ else {
+ printk(KERN_ERR "Error: PCI-X unit not in PCI-X mode.\n");
+ writel(data | 0x80000000, PCIX_UNIT_BASE + PCI_UNITCNT);
+ }
+
+ writew(0x0006, PCIX_UNIT_BASE + PCI_COMMAND); /* Master-Memory enable */
+ writew(0xfb30, PCIX_UNIT_BASE + PCI_STATUS); /* Error bit clear */
+
+ writeb(0x08, PCIX_UNIT_BASE + PCI_CALISIZE); /* Cache line size */
+ writeb(0x40, PCIX_UNIT_BASE + PCI_LTTIMER); /* Latency Timer */
+
+ /* Master Segment Address[31:24] */
+ writel(0x00000003, PCIX_UNIT_BASE + PCI_PMSEGL); /* no swap */
+ /* Master Segment Address[63:32] */
+ writel(0x00000000, PCIX_UNIT_BASE + PCI_PMSEGH);
+
+ /* Data endian swap mode */
+ writel(0x00000003, PCIX_UNIT_BASE + PCI_PTMEMENSW0); /* no swap */
+ writel(0x00000003, PCIX_UNIT_BASE + PCI_PTMEMENSW1); /* no swap */
+
+#ifdef CONFIG_REALVIEW_HIGH_PHYS_OFFSET
+ /* 512MB of DMA capable RAM is available - mapped at 0x70000000
+ * Note :- 0x70000000 is *not* on a 512MB boundary so it
+ * must be mapped in two parts */
+ /* Window#0 256MB and enable */
+ writel(0x0fff0001, PCIX_UNIT_BASE + PCI_PTMEMMSK0);
+ /* Window#1 256MB and enable */
+ writel(0x0fff0001, PCIX_UNIT_BASE + PCI_PTMEMMSK1);
+
+ /* Window base address setting */
+ /* Memory Base Address, Ptrefetch Disable, 64bit */
+ writel(0x70000004, PCIX_UNIT_BASE + PCI_PTBADDR0L);
+ /* Memory Base Address[63:32] */
+ writel(0x00000000, PCIX_UNIT_BASE + PCI_PTBADDR0M);
+
+ /* Memory Base Address, Ptrefetch Disable, 64bit */
+ writel(0x80000004, PCIX_UNIT_BASE + PCI_PTBADDR1L);
+ /* Memory Base Address[63:32] */
+ writel(0x00000000, PCIX_UNIT_BASE + PCI_PTBADDR1M);
+#else
+ /* Only 256MB of RAM is available - mapped at zero */
+ /* Window#0 256MB and enable */
+ writel(0x0fff0001, PCIX_UNIT_BASE + PCI_PTMEMMSK0);
+ /* Window#1 Disable */
+ writel(0x00000000, PCIX_UNIT_BASE + PCI_PTMEMMSK1);
+
+ /* Window base address setting */
+ /* Memory Base Address, Ptrefetch Disable, 64bit */
+ writel(0x00000004, PCIX_UNIT_BASE + PCI_PTBADDR0L);
+ /* Memory Base Address[63:32] */
+ writel(0x00000000, PCIX_UNIT_BASE + PCI_PTBADDR0M);
+
+ /* Memory Base Address, Ptrefetch Disable, 64bit */
+ writel(0x00000004, PCIX_UNIT_BASE + PCI_PTBADDR1L);
+ /* Memory Base Address[63:32] */
+ writel(0x00000000, PCIX_UNIT_BASE + PCI_PTBADDR1M);
+#endif
+
+ /* OnChipBus Address#0[35:24] */
+ writel(0x00000000, PCIX_UNIT_BASE + PCI_PTMEMSEG0);
+ /* OnChipBus Address#1[35:24] */
+ writel(0x00000000, PCIX_UNIT_BASE + PCI_PTMEMSEG1);
+
+ /* 66MHz, 64bit device */
+ writel(0x00010000, PCIX_UNIT_BASE + PCI_PCIXSTAT);
+ /* Interrupt Mask */
+ writel(0x00000000, PCIX_UNIT_BASE + PCI_PINTXMASK);
+ /* Enable PCI error status */
+ writel(0x000307f7, PCIX_UNIT_BASE + PCI_PERRMASK);
+ /* Clear PCI error status */
+ writel(0x000307f7, PCIX_UNIT_BASE + PCI_PERRSTAT);
+ /* Enable count out */
+ writel(0x10FFFFFF, PCIX_UNIT_BASE + PCI_CNTOTIMER);
+
+ realview_pb_pcix_sync();
+ udelay(1500); /* Allow hardware to settle */
+
+ /* Deassert PCI reset */
+ writel(0x00000001, PCIX_UNIT_BASE + PCI_PRST);
+ /* Initial end, Enable arbiter */
+ writel(data | 0x80000020, PCIX_UNIT_BASE + PCI_UNITCNT);
+ realview_pb_pcix_sync();
+ udelay(1500); /* Allow hardware to settle */
+
+ /* Enable bursts on PCI-X */
+ writel(0x1, __io_address(REALVIEW_ISSP_REG_BASE + 0x18));
+}
+
+void realview_pb_pcix_set_attr(unsigned int tag)
+{
+ /* Get Device and Funcion number */
+ u32 data = readl(PCIX_UNIT_BASE + PCI_PCIXSTAT) & 0x0ff;
+ /* attribute set */
+ writel(tag | (data << 8), PCIX_UNIT_BASE + PCI_CFGATTR);
+}
+
+int realview_pb_pcix_set_config(u32 bus, u32 dev, u32 func, int offset)
+{
+ u32 mode;
+ u32 config_addr;
+
+ writel(0x000307F7, PCIX_UNIT_BASE + PCI_PERRSTAT); /* clear error bit */
+ writew(0xfb30, PCIX_UNIT_BASE + PCI_STATUS); /* error bit clear */
+
+ if (bus == 0) {
+ /* Type0 Configuration cycle */
+ mode = readl(PCIX_UNIT_BASE + PCI_UNITCNT);
+ if (mode & 0x1) {
+ /* check PCI mode */
+ printk(KERN_ERR "PCI mode detected during config cycle"
+ "attempt. Should really be in PCI-X mode.\n");
+ BUG();
+ } else {
+ /* PCI-X Mode
+ *
+ * Note that the root complex appears as id 0x1f
+ * since we don't want to allocate this as a device
+ * we stop scanning before we find it.
+ */
+ if (dev < 0x10 || dev > 0x1e) {
+ realview_pb_pcix_sync();
+ return -1;
+ }
+ config_addr = (1 << dev) | ((dev & 0x1f) << 11) |
+ ((func & 0x7) << 8) | (offset & 0xfc);
+ writel(config_addr, PCIX_UNIT_BASE + PCI_CFGADDR);
+ }
+ } else {
+ config_addr = ((bus & 0xff) << 16) | ((dev & 0x1f) << 11) |
+ ((func & 0x7) << 8) | (offset & 0xfc) | 0x1;
+ /* Type1 Configuration cycle */
+ writel(config_addr, PCIX_UNIT_BASE + PCI_CFGADDR);
+ }
+ realview_pb_pcix_sync();
+ return 0;
+}
+
+int realview_pb_pcix_check_error(void)
+{
+ u32 data;
+ realview_pb_pcix_sync();
+ data = readl(PCIX_UNIT_BASE + PCI_PERRSTAT);
+ if (data) {
+ writel(data, PCIX_UNIT_BASE + PCI_PERRSTAT);
+ return data;
+ }
+ return 0;
+}
+
+int realview_pb_pci_read_config(struct pci_bus *pbus, u32 devfn,
+ int offset, int size, u32 *data)
+{
+ u32 bus = pbus->number;
+ u32 slot = PCI_SLOT(devfn);
+ u32 function = PCI_FUNC(devfn);
+
+ realview_pb_pcix_set_attr(PCIX_READ_ACCESS);
+ if (realview_pb_pcix_set_config(bus, slot, function, offset) != 0) {
+ *data = ((size == 1) ? 0xFF : ((size == 2) ? 0xFFFF : 0xFFFFFFFF));
+ return PCIBIOS_DEVICE_NOT_FOUND;
+ }
+ switch (size) {
+ case 1:
+ *data = (readl(PCIX_UNIT_BASE + PCI_CFGDATA) >>
+ (8 * (offset & 3))) & 0xff;
+ break;
+ case 2:
+ *data = (u32)readw(PCIX_UNIT_BASE + PCI_CFGDATA +
+ (offset & 3));
+ break;
+ default:
+ *data = readl(PCIX_UNIT_BASE + PCI_CFGDATA);
+ }
+ /* Error codes from downstream bus don't mean much for software. */
+ if (!realview_pb_pcix_check_error())
+ return PCIBIOS_SUCCESSFUL;
+
+ return PCIBIOS_DEVICE_NOT_FOUND;
+}
+
+int realview_pb_pci_write_config(struct pci_bus *pbus, u32 devfn,
+ int offset, int size, u32 data)
+{
+ u32 slot = PCI_SLOT(devfn);
+ u32 function = PCI_FUNC(devfn);
+ u32 bus = pbus->number;
+ int err;
+
+ realview_pb_pcix_set_attr(PCIX_WRITE_ACCESS);
+ if (realview_pb_pcix_set_config(bus, slot, function, offset) != 0)
+ return PCIBIOS_DEVICE_NOT_FOUND;
+
+ switch (size) {
+ case 1:
+ writeb(data, PCIX_UNIT_BASE + PCI_CFGDATA + (offset & 3));
+ break;
+ case 2:
+ writew(data, PCIX_UNIT_BASE + PCI_CFGDATA + (offset & 3));
+ break;
+ default:
+ writel(data, PCIX_UNIT_BASE + PCI_CFGDATA);
+ break;
+ }
+ /* Write errors don't really matter */
+ err = realview_pb_pcix_check_error();
+ realview_pb_pcix_set_attr(PCIX_READ_ACCESS);
+ return err;
+}
+
+static struct pci_ops pci_realview_pb_ops = {
+ .read = realview_pb_pci_read_config,
+ .write = realview_pb_pci_write_config,
+};
+
+static int __init pci_realview_pb_setup(int nr, struct pci_sys_data *sys)
+{
+ if (machine_is_realview_pb11mp())
+ irq_gic_start = IRQ_PB11MP_GIC_START;
+ else
+ irq_gic_start = IRQ_PBA8_GIC_START;
+
+ realview_pb_pcix_unit_init();
+
+ /* set bus->cpu mapping */
+ sys->mem_offset = 0;
+ sys->io_offset = REALVIEW_PB_PCI_IO_BASE;
+
+ /* Note: we don't reserve the addresses for PCI space here because the
+ * window allocator won't be able to get any if we do.
+ */
+
+ return 1;
+}
+
+
+static struct pci_bus *pci_realview_pb_scan_bus(int nr, struct pci_sys_data *sys)
+{
+ return pci_scan_bus(sys->busnr, &pci_realview_pb_ops, sys);
+}
+
+static void __init pci_realview_pb_preinit(void)
+{
+ u32 data = readl(__io_address(REALVIEW_SYS_PCI_STAT));
+ data &= ~0x00000100; /* Clear the Clock Control bit */
+ writel(data, __io_address(REALVIEW_SYS_PCI_STAT));
+ udelay(1500); /* Allow hardware to settle */
+}
+
+static struct pci_dev *pcie_bridge;
+
+/* Maps scrambled IRQ pins to IRQ lines. Scrambling depends on device slot. */
+static int __init pci_realview_pb_map_irq(struct pci_dev *dev, u8 slot, u8 pin)
+{
+ int irq;
+ int devslot = PCI_SLOT(dev->devfn);
+
+ /* Upstream port of 8518 device */
+ if (unlikely(dev->device == 0x8518 && dev->bus->primary == 0)) {
+ BUG_ON(pcie_bridge); /* Can't be already assigned */
+ pcie_bridge = dev;
+ }
+
+ if (pcie_bridge) {
+ if (dev->bus->primary >= pcie_bridge->bus->secondary &&
+ dev->bus->primary <= pcie_bridge->bus->subordinate) {
+ /*
+ * The problem is that in PCIE in different slots a device
+ * could still get the same slot/pin/devslot triplet. Therefore
+ * we check the slot/pin/devslot triplet of an underlying PCIE port.
+ *
+ * slot pin irq
+ * 0 #A(1) 56
+ * 0 #B(2) 57
+ * 0 #C(3) 54
+ * 0 #D(4) 55
+ * 1 #A(1) 55
+ * 1 #B(2) 56
+ * 1 #C(3) 57
+ * 1 #D(4) 54
+ * 2 1 54
+ * 2 2 55
+ * 2 3 56
+ * 2 4 57
+ * 3 1 57
+ * 3 2 54
+ * 3 3 55
+ * 3 4 56
+ * 4 1 56
+ * 4 2 57
+ * 4 3 54
+ * 4 4 55
+ */
+
+ if (dev->bus->self) {
+ /* We do have a bus above us
+ * Device is right behind a PCIE hub port, and its not a PCIE
+ * hub port itself. */
+ if (dev->bus->self->device == 0x8518 &&
+ dev->device != 0x8518) {
+ /* Depend on devslot of 8518 port we're connected to */
+ devslot = PCI_SLOT(dev->bus->self->devfn);
+ /* These are the two cases for PCIE ports on PB boards.
+ * Any other downstream bus topology (e.g. with further
+ * PCIE bridges) does not scramble, and get the same
+ * irq number as the upstream bus. */
+ irq = irq_gic_start + 54 + ((devslot & 3) + (pin - 1)) % 4;
+ } else if ((dev->bus->self->class & 0xff0000) == 0x060000 &&
+ (dev->class & 0xff0000) != 0x060000) {
+ /* It's a device behind a bridge that isn't an 8518 */
+ irq = irq_gic_start + 54 + ((devslot & 3) + pin +
+ PCI_SLOT(dev->bus->self->bus->self->devfn) - 1) % 4;
+ } else {
+ /* It's another bridge */
+ irq = dev->bus->self->irq;
+ }
+ } else
+ irq = 0;
+
+ printk(KERN_INFO "PCI Express irq mapping: device: 0x%x, slot %d, pin %d, devslot %d, irq: %d\n",
+ dev->device, slot, pin, devslot, irq);
+
+ return irq;
+ }
+ }
+
+ /*
+ * slot pin irq
+ * 0 #A(3) 53
+ * 0 #B(0) 50
+ * 0 #C(1) 51
+ * 0 #D(2) 52
+ *
+ * 1 #A(2) 52
+ * 1 #B(3) 53
+ * 1 #C(0) 50
+ * 1 #D(1) 51
+ *
+ * 2 1 51
+ * 2 2 52
+ * 2 3 53
+ * 2 0 50
+ *
+ * 3 0 50
+ * 3 1 51
+ * 3 2 52
+ * 3 3 53
+ *
+ * Note: pin is actually 1..4 not 0..3
+ */
+
+ irq = irq_gic_start + 50 + (((pin - 1) + 3 - (devslot & 3)) & 3);
+
+ printk(KERN_INFO "PCI map irq: slot %d, pin %d, devslot %d, irq: %d\n",
+ slot, pin, devslot, irq);
+
+ return irq;
+
+}
+
+static struct hw_pci realview_pb_pci __initdata = {
+ .swizzle = NULL,
+ .map_irq = pci_realview_pb_map_irq,
+ .nr_controllers = 1,
+ .setup = pci_realview_pb_setup,
+ .scan = pci_realview_pb_scan_bus,
+ .preinit = pci_realview_pb_preinit
+};
+
+static int __init realview_pb_pci_init(void)
+{
+ if (machine_is_realview_pb11mp() ||
+ machine_is_realview_pba8() || machine_is_realview_pbx())
+ pci_common_init(&realview_pb_pci);
+ return 0;
+}
+
+subsys_initcall(realview_pb_pci_init);
+
+
+static void pcie_fix_sizes(void)
+{
+ struct pci_dev *pdev = NULL;
+ int rrq;
+ int max_rrq = 4096;
+
+ /* Set the max read request size for all devices to the
+ * smallest in the tree. So far, the only device we've seen
+ * that fails without this is the Marvell Yukon 88E8053 but
+ * this may fix other devices too.
+ */
+ while ((pdev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, pdev)) != NULL) {
+ rrq = pcie_get_readrq(pdev);
+ if (rrq > 0 && rrq < max_rrq)
+ max_rrq = rrq;
+ }
+
+ pdev = NULL; /* reset scan */
+ while ((pdev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, pdev)) != NULL) {
+ rrq = pcie_get_readrq(pdev);
+ printk(KERN_DEBUG "%s: %02x:%02x %04x/%04x : RRQSZ %d -> %d\n",
+ __func__, pdev->bus->number, pdev->devfn,
+ pdev->vendor, pdev->device, rrq, max_rrq);
+ pcie_set_readrq(pdev, max_rrq);
+ }
+}
+
+static int __init realview_pcie_fixups(void)
+{
+ pcie_fix_sizes();
+ return 0;
+}
+late_initcall(realview_pcie_fixups);
More information about the linux-arm-kernel
mailing list