diff options
-rw-r--r-- | arch/arm/mach-tegra/Makefile | 1 | ||||
-rw-r--r-- | arch/arm/mach-tegra/include/mach/pci.h | 323 | ||||
-rw-r--r-- | arch/arm/mach-tegra/include/mach/platform.h | 1 | ||||
-rw-r--r-- | arch/arm/mach-tegra/pci-enum.c | 637 | ||||
-rw-r--r-- | arch/arm/mach-tegra/pci.c | 561 | ||||
-rw-r--r-- | arch/arm/mach-tegra/tegra_sysmap.c | 2 |
6 files changed, 1513 insertions, 12 deletions
diff --git a/arch/arm/mach-tegra/Makefile b/arch/arm/mach-tegra/Makefile index 6b8ac3027872..075d18a45193 100644 --- a/arch/arm/mach-tegra/Makefile +++ b/arch/arm/mach-tegra/Makefile @@ -65,4 +65,5 @@ obj-$(CONFIG_TEGRA_NVEC_USER) += nvec_user.o # PCIe support obj-$(CONFIG_TEGRA_PCI) += pci.o +obj-$(CONFIG_TEGRA_PCI) += pci-enum.o diff --git a/arch/arm/mach-tegra/include/mach/pci.h b/arch/arm/mach-tegra/include/mach/pci.h new file mode 100644 index 000000000000..e5eac3ad5640 --- /dev/null +++ b/arch/arm/mach-tegra/include/mach/pci.h @@ -0,0 +1,323 @@ +/* + * arch/arm/mach-tegra/include/mach/pci.h + * + * Header file containing constants for the tegra PCIe driver. + * + * Copyright (c) 2008-2009, NVIDIA Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __MACH_TEGRA_PCI_H + +#include <linux/pci.h> +#include <mach/platform.h> + +#include "nvrm_drf.h" +#include "ap20/dev_ap_pcie2_root_port.h" +#include "ap20/dev_ap_pcie2_pads.h" +#include "ap20/arafi.h" + +extern void __iomem * volatile pci_tegra_regs; + +/* + * AXI address map for the PCIe aperture. AP20, defines 1GB in the AXI + * address map for PCIe. + * + * That address space is split into different regions, with sizes and + * offsets as follows. Exepct for the Register space, SW is free to slice the + * regions as it chooces. + * + * The split below seems to work fine for now. + * + * 0x8000_0000 to 0x80ff_ffff - Register space 16MB. + * 0x8100_0000 to 0x81ff_ffff - Config space 16MB. + * 0x8200_0000 to 0x82ff_ffff - Extended config space 16MB. + * 0x8300_0000 to 0x83ff_ffff - Downstream IO space + * ... Will be filled with other BARS like MSI/upstream IO etc. + * 0x9000_0000 to 0x9fff_ffff - non-prefetchable memory aperture + * 0xa000_0000 to 0xbfff_ffff - Prefetchable memory aperture + * + * Config and Extended config sizes are choosen to support + * maximum of 256 devices, + * which is good enough for all the AP20 use cases. + * */ + +#define PCIE_REGS_SIZE 0x01000000UL +#define PCIE_CONFIG_OFFSET PCIE_REGS_SIZE +#define PCIE_CONFIG_SIZE 0x01000000UL +#define PCIE_EXTENDED_CONFIG_OFFSET (PCIE_CONFIG_SIZE + PCIE_CONFIG_OFFSET) +#define PCIE_EXTENDED_CONFIG_SIZE 0x01000000UL +#define PCIE_DOWNSTREAM_IO_OFFSET (PCIE_EXTENDED_CONFIG_SIZE + \ + PCIE_EXTENDED_CONFIG_OFFSET) +#define PCIE_DOWNSTREAM_IO_SIZE 0x00100000UL + +#define PCIE_NON_PREFETCH_MEMORY_OFFSET 0x10000000UL +#define PCIE_NON_PREFETCH_MEMORY_SIZE 0x10000000UL +#define PCIE_PREFETCH_MEMORY_OFFSET (PCIE_NON_PREFETCH_MEMORY_OFFSET + \ + PCIE_NON_PREFETCH_MEMORY_SIZE) +#define PCIE_PREFETCH_MEMORY_SIZE 0x20000000UL + +/* PCIe registers can be classified into 4 regions. + * + * 1. AFI registers - AFI is a wrapper between PCIE and ARM AXI bus. These + * registers define the address translation registers, interrupt registers and + * some configuration (a.k.a CYA) registers. + * 2. PAD registers - PAD control registers which are inside the PCIE CORE. + * 3. Configuration 0 and Configuration 1 registers - These registers are PCIe + * configuration registers of Root port 0 and root port 1. + * + * Check the PcieRegType enumeration for the list of Registers banks inside the + * PCIE aperture. + * + * */ +#define NV_PCIE_AXI_AFI_REGS_OFSET 0x3800UL +#define NV_PCIE_AXI_PADS_OFSET 0x3000UL +#define NV_PCIE_AXI_RP_T0C0_OFFSET 0x0000UL +#define NV_PCIE_AXI_RP_T0C1_OFFSET 0x1000UL + +/* During the boot only registers/config and extended config apertures are + * mapped. Rest are mapped on demand by the PCI device drivers. + */ +#define PCI_TEGRA_IOMAPPED_REG_APERTURE_SIZE \ + (PCIE_REGS_SIZE + PCIE_CONFIG_SIZE + PCIE_EXTENDED_CONFIG_SIZE) + +/* + * PCI address map for memory mapped devices. Still using 32-bit aperture. + * + * 1GB for the system memory. + * Everything mapped as cpu physical = pci + * + */ +#define FPCI_SYSTEM_MEMORY_OFFSET 0x0UL +#define FPCI_SYSTEM_MEMORY_SIZE 0x40000000UL +#define FPCI_NON_PREFETCH_MEMORY_OFFSET 0x90000000UL +#define FPCI_NON_PREFETCH_MEMORY_SIZE PCIE_NON_PREFETCH_MEMORY_SIZE +#define FPCI_PREFETCH_MEMORY_OFFSET (FPCI_NON_PREFETCH_MEMORY_OFFSET+ \ + FPCI_NON_PREFETCH_MEMORY_SIZE) +#define FPCI_PREFETCH_MEMORY_SIZE 0x40000000UL + + + + +/* PCIE DRF macros to read and write PRI registers */ + +/** NVPCIE_DRF_DEF - define a new register value. + + @param d register domain (hardware block) + @param r register name + @param f register field + @param c defined value for the field + */ +#define NVPCIE_DRF_DEF(d,r,f,c) \ + ((NV_PROJ__PCIE2_##d##_##r##_##f##_##c) \ + << NV_FIELD_SHIFT(NV_PROJ__PCIE2_##d##_##r##_##f)) + +/** NVPCIE_DRF_NUM - define a new register value. + + @param d register domain (hardware block) + @param r register name + @param f register field + @param n numeric value for the field + */ +#define NVPCIE_DRF_NUM(d,r,f,n) \ + (((n)& NV_FIELD_MASK(NV_PROJ__PCIE2_##d##_##r##_##f)) << \ + NV_FIELD_SHIFT(NV_PROJ__PCIE2_##d##_##r##_##f)) + +/** NVPCIE_DRF_VAL - read a field from a register. + + @param d register domain (hardware block) + @param r register name + @param f register field + @param v register value + */ +#define NVPCIE_DRF_VAL(d,r,f,v) \ + (((v)>> NV_FIELD_SHIFT(NV_PROJ__PCIE2_##d##_##r##_##f)) & \ + NV_FIELD_MASK(NV_PROJ__PCIE2_##d##_##r##_##f)) + +/** NVPCIE_FLD_SET_DRF_NUM - modify a register field. + + @param d register domain (hardware block) + @param r register name + @param f register field + @param n numeric field value + @param v register value + */ +#define NVPCIE_FLD_SET_DRF_NUM(d,r,f,n,v) \ + ((v & ~NV_FIELD_SHIFTMASK(NV_PROJ__PCIE2_##d##_##r##_##f)) | \ + NVPCIE_DRF_NUM(d,r,f,n)) + +/** NVPCIE_FLD_SET_DRF_DEF - modify a register field. + + @param d register domain (hardware block) + @param r register name + @param f register field + @param c defined field value + @param v register value + */ +#define NVPCIE_FLD_SET_DRF_DEF(d,r,f,c,v) \ + (((v) & ~NV_FIELD_SHIFTMASK(NV_PROJ__PCIE2_##d##_##r##_##f)) | \ + NVPCIE_DRF_DEF(d,r,f,c)) + +/** NVPCIE_RESETVAL - get the reset value for a register. + + @param d register domain (hardware block) + @param r register name + */ +#define NVPCIE_RESETVAL(d,r) (d##_##r##_0_RESET_VAL) + +/* Register access inline functions */ + +static inline void pci_tegra_afi_writel(u32 value,unsigned long offset) +{ + writel(value, offset + NV_PCIE_AXI_AFI_REGS_OFSET + pci_tegra_regs); +} + +static inline void pci_tegra_rp_writel(u32 value, unsigned long offset, int rp) +{ + BUG_ON(rp != 0 && rp != 1); + + if (rp == 0) offset += NV_PCIE_AXI_RP_T0C0_OFFSET; + if (rp == 1) offset += NV_PCIE_AXI_RP_T0C1_OFFSET; + + writel(value, offset + pci_tegra_regs); +} + +static inline void pci_tegra_rp_writew(u16 value, unsigned long offset, int rp) +{ + u32 reg; + + BUG_ON(rp != 0 && rp != 1); + + if (rp == 0) offset += NV_PCIE_AXI_RP_T0C0_OFFSET; + if (rp == 1) offset += NV_PCIE_AXI_RP_T0C1_OFFSET; + + reg = readl((offset & ~0x3) + pci_tegra_regs); + reg &= ~(0xffff << ((offset & 0x3) * 8)); + reg |= (u32)value << ((offset & 0x3) * 8); + writel(reg, (offset & ~0x3) + pci_tegra_regs); +} + +static inline void pci_tegra_rp_writeb(u8 value, unsigned long offset, int rp) +{ + u32 reg; + + BUG_ON(rp != 0 && rp != 1); + + if (rp == 0) offset += NV_PCIE_AXI_RP_T0C0_OFFSET; + if (rp == 1) offset += NV_PCIE_AXI_RP_T0C1_OFFSET; + + reg = readl((offset & ~0x3) + pci_tegra_regs); + reg &= ~(0xff << ((offset & 0x3) * 8)); + reg |= (u32)value << ((offset & 0x3) * 8); + writel(reg, (offset & ~0x3) + pci_tegra_regs); +} + +static inline void pci_tegra_pads_writel(u32 value, unsigned long offset) +{ + writel(value, offset + NV_PCIE_AXI_PADS_OFSET + pci_tegra_regs); +} + +static inline u32 pci_tegra_afi_readl(unsigned long offset) +{ + return readl(offset + NV_PCIE_AXI_AFI_REGS_OFSET + pci_tegra_regs); +} + +static inline u32 pci_tegra_rp_readl(unsigned long offset, int rp) +{ + BUG_ON(rp != 0 && rp != 1); + + if (rp == 0) offset += NV_PCIE_AXI_RP_T0C0_OFFSET; + if (rp == 1) offset += NV_PCIE_AXI_RP_T0C1_OFFSET; + + return readl(offset + pci_tegra_regs); +} + +static inline u16 pci_tegra_rp_readw(unsigned long offset, int rp) +{ + u32 val; + + BUG_ON(rp != 0 && rp != 1); + + if (rp == 0) offset += NV_PCIE_AXI_RP_T0C0_OFFSET; + if (rp == 1) offset += NV_PCIE_AXI_RP_T0C1_OFFSET; + + val = readl((offset & ~0x3) + pci_tegra_regs); + val >>= 8 * (offset & 3); + val &= 0xffff; + + return (u16)val; +} + +static inline u8 pci_tegra_rp_readb(unsigned long offset, int rp) +{ + u32 val; + + BUG_ON(rp != 0 && rp != 1); + + if (rp == 0) offset += NV_PCIE_AXI_RP_T0C0_OFFSET; + if (rp == 1) offset += NV_PCIE_AXI_RP_T0C1_OFFSET; + + val = readl((offset & ~0x3) + pci_tegra_regs); + val >>= 8 * (offset & 3); + val &= 0xff; + + return (u8)val; +} + +static inline u32 pci_tegra_pads_reedl(unsigned long offset) +{ + return readl(offset + NV_PCIE_AXI_PADS_OFSET + pci_tegra_regs); +} + +static inline bool pci_tegra_is_rp(u32 bus_number, int *rp) +{ + if (bus_number == pci_tegra_rp_readb(PCI_PRIMARY_BUS, 0)) { + *rp = 0; + return true; + } else if (bus_number == pci_tegra_rp_readb(PCI_PRIMARY_BUS, 1)) { + *rp = 1; + return true; + } else + return false; +} + +/* + * Given the bus number, devfn and the offset this API returns the mapped + * address of the config space. + */ +static inline void __iomem *pci_tegra_config_addr(u8 bus_number, + u32 devfn, u32 where) +{ + void *addr; + u32 function; + u32 device; + + function = PCI_FUNC(devfn); + device = PCI_SLOT(devfn); + + addr = pci_tegra_regs; + addr += (where < 256) ? PCIE_CONFIG_OFFSET + : PCIE_EXTENDED_CONFIG_OFFSET; + addr += bus_number << 16; + addr += device << 11; + addr += function << 8; + addr += where; + return addr; +} + +void pci_tegra_enumerate(void); + +#endif diff --git a/arch/arm/mach-tegra/include/mach/platform.h b/arch/arm/mach-tegra/include/mach/platform.h index 54cb086e58b3..f80632aff0a8 100644 --- a/arch/arm/mach-tegra/include/mach/platform.h +++ b/arch/arm/mach-tegra/include/mach/platform.h @@ -27,6 +27,7 @@ extern unsigned int tegra_get_module_inst_irq(const char *, int, int); #define TEGRA_PL310_IRQ (tegra_get_module_inst_irq("pl310", 0, 0)) #define TEGRA_SCU_BASE (tegra_get_module_inst_base("scu", 0)) +#define TEGRA_PCIE_BASE (tegra_get_module_inst_base("pcie", 0)) #define TEGRA_SCU0_IRQ NO_IRQ #define TEGRA_SCU1_IRQ NO_IRQ diff --git a/arch/arm/mach-tegra/pci-enum.c b/arch/arm/mach-tegra/pci-enum.c new file mode 100644 index 000000000000..1dab1de0a86d --- /dev/null +++ b/arch/arm/mach-tegra/pci-enum.c @@ -0,0 +1,637 @@ +/* + * arch/arm/mach-tegra/pci-enum.c + * + * Code to enumerate the PCI devices on the PCI bus. Unlike x86 we cannot + * rely on BIOS to allocate the PCIe resources for the devices. + * + * Copyright (c) 2008-2009, NVIDIA Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include <mach/pci.h> +#include <linux/delay.h> +#include <linux/ioport.h> + +struct pci_tegra_device +{ + /* Bus number */ + u8 bus; + + /* Device + function encoding. + * Use macros PCI_DEVFN/PCI_SLOT/PCI_FUNC to encode and decode + * */ + u32 devfn; + + /* Secondary bus nummber. Non-zero only for bridge devices. */ + u32 sec_bus; + + /* Subordinate bus number. Non-zero only for the bridge devices. */ + u32 sub_bus; + + /* Device ID/vendor ID of the PCI device/bridge. + * Upper 16 bits are device ID and lower 16 bits are vendor ID. + */ + u32 id; + + /* For a bridge device only 3 bars are used. + */ +#define PCI_BRIDGE_IO_RES 0 +#define PCI_BRIDGE_MEM_RES 1 +#define PCI_BRIDGE_PREFETCH_RES 2 + + /* Here we are limiting to the standard PCI resources */ + struct resource res[PCI_STD_RESOURCE_END + 1]; + + bool disabled; + + struct pci_tegra_device *parent; + struct pci_tegra_device *next; + struct pci_tegra_device *prev; + struct pci_tegra_device *child; + bool root_port; +}; + +#define TEGRA_MAX_PCI_DEVICES 64 +static struct pci_tegra_device pci_devices[TEGRA_MAX_PCI_DEVICES]; +static int max_devices; +static struct pci_tegra_device *pci_root; + +static u32 pci_tegra_io_base; +static u32 pci_tegra_mem_base; +static u32 pci_tegra_prefetech_base; + +static u32 pci_tegra_io_limt; +static u32 pci_tegra_mem_limit; +static u32 pci_tegra_prefetech_limit; + +static void pci_tegra_print_device_tree(struct pci_tegra_device *dev); +static void pcie_scanbus(struct pci_tegra_device *dev_parent); +static void pci_tegra_allocate_resources(struct pci_tegra_device *dev); + + +static struct pci_tegra_device *alloc_pci_tegra_device(void) +{ + static u32 index = 0; + struct pci_tegra_device *dev; + + if (index == 0) + memset(pci_devices, 0, sizeof(pci_devices)); + + dev = &pci_devices[index]; + index++; + max_devices = index; + return dev; +} + +static inline void pci_conf_write8(u8 bus, u32 devfn, u32 where , u8 val) +{ + u32 addr; + u32 temp; + + addr = (u32)pci_tegra_config_addr(bus, devfn, where); + pr_err("Issuing pci_conf_write8 at addr 0x%x with data 0x%x\n", + addr, val); + + temp = readl((addr & ~0x3)); + temp &= ~(0xff << ((addr & 0x3) * 8)); + temp |= (u32)val << ((addr & 0x3) * 8); + writel(temp, (addr & ~0x3)); +} + +static inline void pci_conf_write16(u8 bus, u32 devfn, u32 where, u16 val) +{ + u32 addr; + u32 temp; + + BUG_ON(where & 0x1); + + addr = (u32)pci_tegra_config_addr(bus, devfn, where); + pr_err("Issuing pci_conf_write16 at addr 0x%x with data 0x%x\n", + addr, val); + + temp = readl((addr & ~0x3)); + temp &= ~(0xffff << ((addr& 0x3) * 8)); + temp |= (u32)val << ((addr & 0x3) * 8); + writel(temp, (addr & ~0x3)); +} + +static inline void pci_conf_write32(u8 bus, u32 devfn, u32 where, u32 val) +{ + u32 addr; + + BUG_ON(where & 0x3); + + addr = (u32)pci_tegra_config_addr(bus, devfn, where); + pr_err("Issuing pci_conf_write32 at addr 0x%x with data 0x%x\n", + addr, val); + writel(val, addr); +} + +static inline u8 pci_conf_read8(u8 bus, u32 devfn, u32 where) +{ + u32 temp; + u32 addr; + + addr = (u32)pci_tegra_config_addr(bus, devfn, where); + pr_err("Issuing pci_conf_read8 at 0x%x\n", addr); + temp = readl(addr & ~0x3); + temp >>= 8 * (addr & 3); + temp &= 0xff; + pr_err("pci_conf_read8 at 0x%x = %d\n", addr, temp); + + return (u8)temp; +} + +static u16 pci_conf_read16(u8 bus, u32 devfn, u32 where) +{ + u32 temp; + u32 addr; + + BUG_ON(where & 0x1); + + addr = (u32)pci_tegra_config_addr(bus, devfn, where); + pr_err("Issuing pci_conf_read16 at 0x%x\n", addr); + temp = readl(addr & ~0x3); + temp >>= 8 * (addr & 3); + temp &= 0xffff; + pr_err("pci_conf_read16 at 0x%x = %d\n", addr, temp); + + return (u16)temp; +} + +static u32 pci_conf_read32(u8 bus, u32 devfn, u32 where) +{ + u32 temp; + + BUG_ON(where & 0x3); + + pr_err("Issuing pci_conf_read32 at 0x%x\n", + (u32)(pci_tegra_config_addr(bus, devfn, where))); + + temp = readl(pci_tegra_config_addr(bus, devfn, where)); + + pr_err("pci_conf_read32 at 0x%x = %d\n", where, temp); + return temp; +} + +static void pcie_scanbus(struct pci_tegra_device *dev_parent) +{ + u8 subordinate_bus; + u8 hdr_type; + u8 next_bus_number; + u32 device = 0; + u32 id; + struct pci_tegra_device *dev; + u32 retry_count; + + next_bus_number = dev_parent->sec_bus; + +next_device: + retry_count = 6; + if (device == 0x20) { + /* Termination condition: Max number of devices reached. + * PCIe bus segment can only have 32 devices. + * */ + dev_parent->sub_bus = next_bus_number; + if (!dev_parent->root_port) { + /* Change the subordinate bus-number to the actual + * value of all buses on the hierarcy. + * + * Do this execpt for the root port. + */ + pci_conf_write8(dev_parent->bus, dev_parent->devfn, + PCI_SUBORDINATE_BUS, next_bus_number); + } + return; + } + + if (dev_parent->root_port && device != 0) { + /* Sepcial Exit condition for root port. + * Root port only connect to one bridge or device. + */ + dev_parent->sub_bus = dev_parent->sec_bus; + return; + } + + while (--retry_count) { + id = pci_conf_read32(dev_parent->sec_bus, + PCI_DEVFN(device, 0), 0); + if (id != 0xFFFFFFFF) + { + /* Found a valid device, break. Otherwise, retry a couple of + * times. It is possible that the bridges can take some time + * to settle and it will take couple of transcations to find + * the devcies behind the bridge. + * */ + /* FIXME: What should be the delay? */ + msleep(100); + break; + } + } + if (id == 0xFFFFFFFF) { + /* Invalid device. Skip that one and look for next device */ + device++; + goto next_device; + } + + dev = alloc_pci_tegra_device(); + + /* Fill the device information */ + dev->parent = dev_parent; + dev->id = id; + dev->bus = dev_parent->sec_bus; + dev->devfn = PCI_DEVFN(device, 0); + if (dev_parent->child == NULL) { + dev_parent->child = dev; + dev->prev = NULL; + } else { + /* Add dev to the list of devices on the same bus */ + struct pci_tegra_device *temp; + + temp = dev_parent->child; + BUG_ON(temp != NULL); + while (temp->next != NULL) + temp = temp->next; + temp->next = dev; + dev->prev = temp; + } + + hdr_type = pci_conf_read8(dev->bus, dev->devfn, PCI_HEADER_TYPE); + if ((hdr_type & 0x7f) == 0x1) { + /* Bridge device */ + + /* Temporarily assign 0xff for the subordinate bus number as + * we don't * know how many devices are preset behind this + * bridge. + * */ + subordinate_bus = 0xff; + dev->sec_bus = next_bus_number + 1; + + pci_conf_write8(dev->bus, dev->devfn, PCI_PRIMARY_BUS, + dev_parent->sec_bus); + pci_conf_write8(dev->bus, dev->devfn, PCI_SECONDARY_BUS, + dev->sec_bus); + pci_conf_write8(dev->bus, dev->devfn, PCI_SUBORDINATE_BUS, + subordinate_bus); + + /* Scan all the buses behind this bridge */ + pcie_scanbus(dev); + + next_bus_number = dev->sub_bus; + } else if ((hdr_type & 0x7f) == 0x0) { + + /* PCI endpoint - Can be single function or multie function */ + pr_info("PCI endpoint (0x%x) is on bus = %d, device = %d\n", + id, dev_parent->sec_bus, device); + + } else if ((hdr_type & 0x7f) == 0x2) { + /* PC card device - Not handled */ + BUG(); + } else { + BUG(); + } + device++; + goto next_device; +} + +static void pci_tegra_enumerate_root_port(int rp) +{ + struct pci_tegra_device *root; + u32 reg; + + root = alloc_pci_tegra_device(); + + if (pci_root) { + pci_root->next = root; + root->bus = pci_root->sub_bus + 1; + } else { + pci_root = root; + root->bus = 0; + } + + root->sec_bus = root->bus + 1; + root->root_port = true; + /* Set the Inital value to the max bus number */ + root->sub_bus = 0xff; + root->id = pci_tegra_rp_readl(0, rp); + + pci_tegra_rp_writeb(root->bus, PCI_PRIMARY_BUS, rp); + pci_tegra_rp_writeb(root->sec_bus, PCI_SECONDARY_BUS, rp); + pci_tegra_rp_writeb(root->sub_bus, PCI_SUBORDINATE_BUS, rp); + + /* Just assigns the bus numbers and sets up the SW hirerarchy */ + pcie_scanbus(root); + + /* Write the udpated root port subordinate bus number */ + pci_tegra_rp_writeb(root->sub_bus, PCI_SUBORDINATE_BUS, rp); + + pci_tegra_allocate_resources(root); + + /* IO base and limits */ + reg = root->res[PCI_BRIDGE_IO_RES].start; + reg = ALIGN(reg, 0x1000); + pci_tegra_rp_writeb((((reg & 0xf000) >> 8) | PCI_IO_RANGE_TYPE_32), + PCI_IO_BASE, rp); + pci_tegra_rp_writew(reg>>16, PCI_IO_BASE_UPPER16, rp); + + reg = root->res[PCI_BRIDGE_IO_RES].end; + reg = ALIGN(reg, 0x1000); + pci_tegra_rp_writeb((((reg & 0xf000) >> 8) | PCI_IO_RANGE_TYPE_32), + PCI_IO_LIMIT, rp); + pci_tegra_rp_writew(reg>>16, PCI_IO_LIMIT_UPPER16, rp); + + /* Memory base and limits */ + reg = root->res[PCI_BRIDGE_MEM_RES].start; + reg = ALIGN(reg, 0x100000); + pci_tegra_rp_writew(reg >> 16, PCI_MEMORY_BASE, rp); + reg = root->res[PCI_BRIDGE_MEM_RES].end; + reg = ALIGN(reg, 0x100000); + pci_tegra_rp_writew(reg >> 16, PCI_MEMORY_LIMIT, rp); + + /* Prefetch base and limit - 32 bit addressing */ + reg = root->res[PCI_BRIDGE_PREFETCH_RES].start; + reg = ALIGN(reg, 0x100000); + pci_tegra_rp_writew(reg >> 16, PCI_PREF_MEMORY_BASE, rp); + pci_tegra_rp_writel(0, PCI_PREF_BASE_UPPER32, rp); + reg = root->res[PCI_BRIDGE_PREFETCH_RES].end; + reg = ALIGN(reg, 0x100000); + pci_tegra_rp_writew(reg >> 16, PCI_PREF_MEMORY_LIMIT, rp); + pci_tegra_rp_writel(0, PCI_PREF_LIMIT_UPPER32, rp); + + reg = 0; + reg |= PCI_COMMAND_IO; + reg |= PCI_COMMAND_MEMORY; + reg |= PCI_COMMAND_MASTER; + reg |= PCI_COMMAND_SERR; + pci_tegra_rp_writew(reg, PCI_COMMAND, rp); +} + +static void pci_tegra_setup_pci_bridge(struct pci_tegra_device *dev) +{ + u32 reg; + + dev->res[PCI_BRIDGE_IO_RES].end = pci_tegra_io_base; + dev->res[PCI_BRIDGE_MEM_RES].end = pci_tegra_mem_base; + dev->res[PCI_BRIDGE_PREFETCH_RES].end = + pci_tegra_prefetech_base; + + /* Only set here for the non-root port devices */ + if (dev->root_port) + return; + + /* IO base and limits */ + reg = dev->res[PCI_BRIDGE_IO_RES].start; + reg = ALIGN(reg, 0x1000); + pci_conf_write8(dev->bus, dev->devfn, PCI_IO_BASE, + (((reg & 0xf000) >> 8) | PCI_IO_RANGE_TYPE_32)); + pci_conf_write16(dev->bus, dev->devfn, PCI_IO_BASE_UPPER16, reg>>16); + + reg = dev->res[PCI_BRIDGE_IO_RES].end; + reg = ALIGN(reg, 0x1000); + pci_conf_write8(dev->bus, dev->devfn, PCI_IO_LIMIT, + (((reg & 0xf000) >> 8) | PCI_IO_RANGE_TYPE_32)); + pci_conf_write16(dev->bus, dev->devfn, PCI_IO_LIMIT_UPPER16, reg>>16); + + /* Memory base and limits */ + reg = dev->res[PCI_BRIDGE_MEM_RES].start; + reg = ALIGN(reg, 0x100000); + pci_conf_write16(dev->bus, dev->devfn, PCI_MEMORY_BASE, reg >> 16); + + reg = dev->res[PCI_BRIDGE_MEM_RES].end; + reg = ALIGN(reg, 0x100000); + pci_conf_write16(dev->bus, dev->devfn, PCI_MEMORY_LIMIT, reg >> 16); + + /* Prefetch base and limit - 32 bit addressing */ + reg = dev->res[PCI_BRIDGE_PREFETCH_RES].start; + reg = ALIGN(reg, 0x100000); + pci_conf_write16(dev->bus, dev->devfn, PCI_PREF_MEMORY_BASE, + reg >> 16); + pci_conf_write16(dev->bus, dev->devfn, PCI_PREF_BASE_UPPER32, 0); + + reg = dev->res[PCI_BRIDGE_PREFETCH_RES].end; + reg = ALIGN(reg, 0x100000); + pci_conf_write16(dev->bus, dev->devfn, PCI_PREF_MEMORY_LIMIT, + reg >> 16); + pci_conf_write16(dev->bus, dev->devfn, PCI_PREF_LIMIT_UPPER32, 0); + + reg = 0; + reg |= PCI_COMMAND_IO; + reg |= PCI_COMMAND_MEMORY; + reg |= PCI_COMMAND_MASTER; + reg |= PCI_COMMAND_SERR; + pci_conf_write16(dev->bus, dev->devfn, PCI_COMMAND, reg); + + /* FIXME how to handle interrutps */ + pci_conf_write8(dev->bus, dev->devfn, PCI_INTERRUPT_LINE, 0x82); + pci_conf_write8(dev->bus, dev->devfn, PCI_INTERRUPT_PIN, 0xa); +} + +static void pci_tegra_setup_pci_device(struct pci_tegra_device *dev) +{ + u8 flags; + u32 bar_index; + u32 reg; + u32 addr; + + for (bar_index = 0x0; bar_index <= PCI_STD_RESOURCE_END; + bar_index ++) { + u32 size; + pci_conf_write32(dev->bus, dev->devfn, bar_index * 4 + + PCI_BASE_ADDRESS_0, 0xFFFFFFFFUL); + + size = pci_conf_read32(dev->bus, dev->devfn, bar_index * 4 + + PCI_BASE_ADDRESS_0); + + if (size == 0xFFFFFFFFUL) continue; + if (size == 0) continue; /* A broken device? */ + flags = (size & 0x000f); + + /* Size align the addr and write that BAR offset */ + if (flags & 0x1) { + size &= ~0xF; /* Ignore the last 4 bits */ + /* some devices hardwire the high bits of IO bars to 0 + * So, ignore those bits. + */ + size |= 0xffff0000; + size = ~size + 1; /* Do the 1's complement */ + + addr = ALIGN(pci_tegra_io_base, size); + + if (addr + size > pci_tegra_io_limt) { + pr_err("pci_tegra: " + "Cannot asign IO res\n"); + continue; + } + dev->res[bar_index].flags = IORESOURCE_IO; + dev->res[bar_index].start = addr; + dev->res[bar_index].end = addr + size -1; + + pci_tegra_io_base = addr + size; + } else { + size &= ~0xF; /* Ignore the last 4 bits */ + size = ~size + 1; /* Do the 1's complement */ + + if (flags & 0x08) { + addr = ALIGN(pci_tegra_mem_base, size); + + if (addr + size > pci_tegra_mem_limit) { + pr_err("pci_tegra: " + "Cannot asign mem res\n"); + continue; + } + + dev->res[bar_index].flags = IORESOURCE_MEM; + dev->res[bar_index].start = 0; + dev->res[bar_index].end = + dev->res[bar_index].start + size - 1; + + pci_tegra_mem_base = addr + size; + } else { + addr = ALIGN(pci_tegra_prefetech_base, size); + + if (addr + size > pci_tegra_prefetech_limit) { + pr_err("pci_tegra: " + "Cannot asign prefetch res\n"); + continue; + } + + dev->res[bar_index].flags = + IORESOURCE_MEM | IORESOURCE_PREFETCH; + dev->res[bar_index].start = addr; + dev->res[bar_index].end = addr + size - 1; + + pci_tegra_prefetech_base = addr + size; + } + } + pci_conf_write32(dev->bus, dev->devfn, bar_index * 4 + + PCI_BASE_ADDRESS_0, dev->res[bar_index].start); + + /* Handle 64 bit addresses by forcing to 32 bit addresses */ + if ((flags == 0x0c) || (flags==0x04)) { + bar_index++; + BUG_ON(bar_index < 6); + pci_conf_write32(dev->bus, dev->devfn, bar_index * 4 + + PCI_BASE_ADDRESS_0, 0); + } + } + + reg = 0; + reg |= PCI_COMMAND_IO; + reg |= PCI_COMMAND_MEMORY; + reg |= PCI_COMMAND_MASTER; + reg |= PCI_COMMAND_SERR; + pci_conf_write16(dev->bus, dev->devfn, PCI_COMMAND, reg); + + /* FIXME how to handle interrutps */ + +} + +static void pci_tegra_print_device_tree(struct pci_tegra_device *dev) +{ + u32 i; + + if (!dev) + return; + + if (dev->sub_bus) + pr_err("PCIe bridge/Root port\n"); + else + pr_err("PCIe device\n"); + + pr_err(" Vendor/Device = 0x%x bus = %d sec bus %d sub bus %d\n", + dev->id, dev->bus, dev->sec_bus, dev->sub_bus); + if (dev->disabled) { + pr_err(" Slot disabled\n"); + } else { + for (i=0; i<= PCI_STD_RESOURCE_END; i++) { + /* Skip printing the empty ones */ + if (!dev->res[i].start) + continue; + pr_err(" bar(%d) \n", i); + pr_err(" start = 0x%x\n", dev->res[i].start); + pr_err(" end = 0x%x\n", dev->res[i].end); + pr_err(" flags = 0x%lx\n", dev->res[i].flags); + } + } + + if (dev->child != NULL) + pci_tegra_print_device_tree(dev->child); + + if (dev->next != NULL) + pci_tegra_print_device_tree(dev->next); +} + +static void pci_tegra_allocate_resources(struct pci_tegra_device *dev) +{ + /* Employing a depth first search for resource allocation. */ + if (!dev) + return; + + if (dev->sub_bus) { + + dev->res[PCI_BRIDGE_IO_RES].flags = IORESOURCE_IO; + dev->res[PCI_BRIDGE_IO_RES].start = pci_tegra_io_base; + + dev->res[PCI_BRIDGE_PREFETCH_RES].flags = + IORESOURCE_MEM | IORESOURCE_PREFETCH; + dev->res[PCI_BRIDGE_PREFETCH_RES].start = + pci_tegra_prefetech_base; + + dev->res[PCI_BRIDGE_MEM_RES].flags = IORESOURCE_MEM; + dev->res[PCI_BRIDGE_MEM_RES].start = pci_tegra_mem_base; + } + + if (dev->child) + pci_tegra_allocate_resources(dev->child); + if (dev->next) + pci_tegra_allocate_resources(dev->next); + + if (dev->sub_bus) + pci_tegra_setup_pci_bridge(dev); + else + pci_tegra_setup_pci_device(dev); +} + +void pci_tegra_enumerate(void) +{ + u32 reg; + + /* Disable all execptions */ + pci_tegra_afi_writel(0, AFI_FPCI_ERROR_MASKS_0); + + /* Set the base and limits of the resources */ + pci_tegra_io_base = TEGRA_PCIE_BASE + PCIE_DOWNSTREAM_IO_OFFSET; + pci_tegra_io_limt = pci_tegra_io_base + PCIE_DOWNSTREAM_IO_SIZE; + + pci_tegra_mem_base = FPCI_NON_PREFETCH_MEMORY_OFFSET; + pci_tegra_mem_limit = FPCI_NON_PREFETCH_MEMORY_OFFSET + + PCIE_NON_PREFETCH_MEMORY_SIZE; + + pci_tegra_prefetech_base = FPCI_PREFETCH_MEMORY_OFFSET; + pci_tegra_prefetech_limit = FPCI_PREFETCH_MEMORY_OFFSET + + PCIE_PREFETCH_MEMORY_SIZE; + + /* Enumerate only if the Link is UP. */ + reg = pci_tegra_rp_readl(NV_PROJ__PCIE2_RP_VEND_XP, 0); + if (NVPCIE_DRF_VAL(RP, VEND_XP, DL_UP, reg) == 1) + pci_tegra_enumerate_root_port(0); + + reg = pci_tegra_rp_readl(NV_PROJ__PCIE2_RP_VEND_XP, 1); + if (NVPCIE_DRF_VAL(RP, VEND_XP, DL_UP, reg) == 1) + pci_tegra_enumerate_root_port(1); + + pci_tegra_print_device_tree(pci_root); +} diff --git a/arch/arm/mach-tegra/pci.c b/arch/arm/mach-tegra/pci.c index dcfdb7981464..2ecba6015229 100644 --- a/arch/arm/mach-tegra/pci.c +++ b/arch/arm/mach-tegra/pci.c @@ -20,11 +20,29 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#define DEBUG +#define VERBOSE_DEBUG + + #include <linux/kernel.h> #include <linux/pci.h> #include <asm/mach/pci.h> #include <linux/interrupt.h> #include <linux/irq.h> +#include <linux/delay.h> + +#include <mach/pci.h> +#include <mach/nvrm_linux.h> + +#include "nvrm_pmu.h" +#include "nvodm_query_discovery.h" +#include "nvrm_power.h" +#include "nvrm_interrupt.h" + + +void __iomem * volatile pci_tegra_regs; +static bool pci_tegra_device_attached = false; +static unsigned int pci_tegra_powerid; static int __init pcie_tegra_init(void); @@ -40,12 +58,20 @@ static int pci_tegra_read_conf(struct pci_bus *bus, u32 devfn, static int __init pcie_tegra_init(void); + +static void pci_tegra_power(int on); +static void pci_tegra_setup_translations(void); +static irqreturn_t pci_tegra_isr(int irq, void *arg); +static bool pci_tegra_check_rp(int rp); + unsigned long pci_tegra_get_base(char *aperture) { if (!strcmp(aperture, "mem")) - return 0x90000000; + return TEGRA_PCIE_BASE + + PCIE_NON_PREFETCH_MEMORY_OFFSET; else if (!strcmp(aperture, "io")) - return 0x82000000; + return TEGRA_PCIE_BASE + + PCIE_DOWNSTREAM_IO_OFFSET; else return (unsigned long)-1; } @@ -54,32 +80,302 @@ static int pci_tegra_read_conf(struct pci_bus *bus, u32 devfn, int where, int size, u32 *val) { int i; + u32 v; + int rp; + + pr_debug("Issuing read conf: bus %d, devfn 0x%x, where 0x%x size %d\n", + bus->number, devfn, where, size); + + if (!pci_tegra_device_attached) goto fail; + if (where > 4096) goto fail; + + /* Root port config registers are directly mapped in different + * aperture - not same as the config registers for devices on the PCI + * bus + */ + if (pci_tegra_is_rp(bus->number, &rp)) { + /* Root port is just one bridge! */ + if (devfn != 0) goto fail; + v = pci_tegra_rp_readl(where & ~3, rp); + } else { + void __iomem *addr; + + /* Root is only attached to one device/bridge */ + if (bus->number == 1 && PCI_SLOT(devfn) != 0) goto fail; + addr = pci_tegra_config_addr(bus->number, devfn, where & ~3); + v = readl(addr); + } + + switch (size) { + case 1: + if (where & 2) v >>= 16; + if (where & 1) v >>= 8; + v &= 0xff; + break; + case 2: + if (where & 2) v >>= 16; + v &= 0xffff; + break; + default: + break; + } + *val = v; + pr_debug(" Value = 0x%x\n", v); + + return PCIBIOS_SUCCESSFUL; +fail: for (i=0; i<size; i++) ((__u8 *)val)[i] = 0xff; - return PCIBIOS_SUCCESSFUL; } static int pci_tegra_write_conf(struct pci_bus *bus, u32 devfn, int where, int size, u32 val) { + void __iomem *addr; + int rp; + u32 temp; + + pr_debug("Issuing write conf: bus %d, devfn 0x%x, " + "where 0x%x size %d value = 0x%x\n", + bus->number, devfn, where, size, val); + + if (!pci_tegra_device_attached) goto fail; + + if (where > 4096) goto fail; + + /* Root port config registers are directly mapped in different + * aperture - not same as the config registers for devices on the PCI + * bus. + */ + if (pci_tegra_is_rp(bus->number, &rp)) { + /* Root port is just one bridge! */ + if (devfn != 0) goto fail; + addr = pci_tegra_regs; + if (rp == 0) addr += NV_PCIE_AXI_RP_T0C0_OFFSET; + if (rp == 1) addr += NV_PCIE_AXI_RP_T0C1_OFFSET; + addr += where; + } else { + /* Root is only attached to one device/bridge */ + if (bus->number == 1 && PCI_SLOT(devfn) != 0) goto fail; + + addr = pci_tegra_config_addr(bus->number, devfn, where); + } + + switch (size) { + case 1: + temp = readl((u32)addr & ~0x3); + temp &= ~(0xff << ((where & 0x3) * 8)); + temp |= val << ((where & 0x3) * 8); + writel(temp, (u32)addr & ~0x3); + break; + case 2: + temp = readl((u32)addr & ~0x3); + temp &= ~(0xffff << ((where & 0x3) * 8)); + temp |= val << ((where & 0x3) * 8); + writel(temp, (u32)addr & ~0x3); + break; + default: + writel(val, addr); + break; + } +fail: return PCIBIOS_SUCCESSFUL; } static struct pci_ops pci_tegra_ops = { - .read = pci_tegra_read_conf, - .write = pci_tegra_write_conf, + .read = pci_tegra_read_conf, + .write = pci_tegra_write_conf, }; static void __init pci_tegra_preinit(void) { - + pcibios_setup("firmware"); } static int __init pci_tegra_setup(int nr, struct pci_sys_data *data) { - return (nr == 0); + u32 volatile reg; + unsigned int irq; + + if (nr != 0) return 0; + + pci_tegra_power(1); + if (NvRmSetModuleTristate(s_hRmGlobal, + NVRM_MODULE_ID(NvRmPrivModuleID_Pcie, 0), NV_FALSE) + != NvSuccess) { + /* No valid Pinmux for PCIe? */ + return 0; + } + + pci_tegra_regs = ioremap_nocache(TEGRA_PCIE_BASE, + PCI_TEGRA_IOMAPPED_REG_APERTURE_SIZE); + if (pci_tegra_regs == NULL) { + pr_err("pci_tegra_setup: Failed to map the PCI/AFI regs\n"); + return 0; + } + + if (NvRmPowerRegister(s_hRmGlobal, 0, &pci_tegra_powerid) != NvSuccess) + goto fail; + if (NvRmPowerModuleClockControl(s_hRmGlobal, NvRmPrivModuleID_Pcie, + pci_tegra_powerid, NV_TRUE) != NvSuccess) + goto fail; + + NvRmModuleReset(s_hRmGlobal, NvRmPrivModuleID_Afi); + NvRmModuleReset(s_hRmGlobal, NvRmPrivModuleID_Pcie); + NvRmModuleResetWithHold(s_hRmGlobal, NvRmPrivModuleID_PcieXclk, + NV_TRUE); + + /* Enable slot clock and pulse the reset signals */ + reg = pci_tegra_afi_readl(AFI_PEX0_CTRL_0); + reg = NV_FLD_SET_DRF_NUM(AFI, PEX0_CTRL, PEX0_REFCLK_EN, 1, reg); + pci_tegra_afi_writel(reg, AFI_PEX0_CTRL_0); + reg = NV_FLD_SET_DRF_NUM(AFI, PEX0_CTRL, PEX0_RST_L, 0, reg); + pci_tegra_afi_writel(reg, AFI_PEX0_CTRL_0); + + reg = pci_tegra_afi_readl(AFI_PEX1_CTRL_0); + reg = NV_FLD_SET_DRF_NUM(AFI, PEX1_CTRL, PEX1_REFCLK_EN, 1, reg); + pci_tegra_afi_writel(reg, AFI_PEX1_CTRL_0); + reg = NV_FLD_SET_DRF_NUM(AFI, PEX1_CTRL, PEX1_RST_L, 0, reg); + pci_tegra_afi_writel(reg, AFI_PEX1_CTRL_0); + + msleep(100); + + reg = pci_tegra_afi_readl(AFI_PEX0_CTRL_0); + reg = NV_FLD_SET_DRF_NUM(AFI, PEX0_CTRL, PEX0_RST_L, 1, reg); + pci_tegra_afi_writel(reg, AFI_PEX0_CTRL_0); + + reg = pci_tegra_afi_readl(AFI_PEX1_CTRL_0); + reg = NV_FLD_SET_DRF_NUM(AFI, PEX1_CTRL, PEX1_RST_L, 1, reg); + pci_tegra_afi_writel(reg, AFI_PEX1_CTRL_0); + + /* Validate by reading the ROOT port IDs that we are infact working on + * the TEGRA root port */ + reg = pci_tegra_rp_readl(NV_PROJ__PCIE2_RP_DEV_ID, 0); + BUG_ON((NVPCIE_DRF_VAL(RP, DEV_ID, VENDOR_ID, reg)) != + NV_PROJ__PCIE2_RP_DEV_ID_VENDOR_ID_NVIDIA); + reg = pci_tegra_rp_readl(NV_PROJ__PCIE2_RP_DEV_ID, 1); + BUG_ON((NVPCIE_DRF_VAL(RP, DEV_ID, VENDOR_ID, reg)) != + NV_PROJ__PCIE2_RP_DEV_ID_VENDOR_ID_NVIDIA); + + /* Enable dual controller and both ports*/ + reg = pci_tegra_afi_readl(AFI_PCIE_CONFIG_0); + reg = NV_FLD_SET_DRF_NUM(AFI, PCIE_CONFIG, PCIEC0_DISABLE_DEVICE, 0, + reg); + reg = NV_FLD_SET_DRF_NUM(AFI, PCIE_CONFIG, SM2TMS0_XBAR_CONFIG, 1, + reg); + reg = NV_FLD_SET_DRF_NUM(AFI, PCIE_CONFIG, PCIEC1_DISABLE_DEVICE, 0, + reg); + pci_tegra_afi_writel(reg, AFI_PCIE_CONFIG_0); + + reg = pci_tegra_afi_readl(AFI_FUSE_0); + reg = NV_FLD_SET_DRF_NUM(AFI, FUSE, FUSE_PCIE_T0_GEN2_DIS, 0, reg); + pci_tegra_afi_writel(reg, AFI_FUSE_0); + + /* Initialze AP20 internal PHY */ + /* ENABLE up to 16 PCIE lanes */ + pci_tegra_pads_writel(0x0, NV_PROJ__PCIE2_PADS_CTL_SEL_1); + + /* override IDDQ to 1 on all 4 lanes */ + reg = pci_tegra_pads_reedl(NV_PROJ__PCIE2_PADS_CTL_1); + reg = NVPCIE_FLD_SET_DRF_NUM(PADS, CTL_1, IDDQ_1L, 1, reg); + pci_tegra_pads_writel(reg, NV_PROJ__PCIE2_PADS_CTL_1); + + /* set up PHY PLL inputs select PLLE output as refclock */ + reg = pci_tegra_pads_reedl(NV_PROJ__PCIE2_PADS_PLL_CTL1); + reg = NVPCIE_FLD_SET_DRF_NUM(PADS, PLL_CTL1, PLL_REFCLK_SEL, + NV_PROJ__PCIE2_PADS_PLL_CTL1_PLL_REFCLK_SEL_INTERNAL_CML, reg); + + /* set TX ref sel to div10 (not div5) */ + reg = NVPCIE_FLD_SET_DRF_NUM(PADS, PLL_CTL1, PLL_TXCLKREF_SEL, + NV_PROJ__PCIE2_PADS_PLL_CTL1_PLL_TXCLKREF_SEL_DIV10, reg); + pci_tegra_pads_writel(reg, NV_PROJ__PCIE2_PADS_PLL_CTL1); + + /* take PLL out of reset */ + reg = pci_tegra_pads_reedl(NV_PROJ__PCIE2_PADS_PLL_CTL1); + reg = NVPCIE_FLD_SET_DRF_NUM(PADS, PLL_CTL1, PLL_RST_B4SM, + NV_PROJ__PCIE2_PADS_PLL_CTL1_PLL_RST_B4SM_DEASSERT, reg); + pci_tegra_pads_writel(reg, NV_PROJ__PCIE2_PADS_PLL_CTL1); + + /* Hack, set the clock voltage to the DEFAULT provided by hw folks. + * This doesn't exist in the documentation + * */ + reg = 0xFA5CFA5C; + pci_tegra_pads_writel(reg, 0xc8); + + /* Wait for the PLL to lock */ + reg = pci_tegra_pads_reedl(NV_PROJ__PCIE2_PADS_PLL_CTL1); + while (NVPCIE_DRF_VAL(PADS, PLL_CTL1, PLL_LOCKDET, reg) + != NV_PROJ__PCIE2_PADS_PLL_CTL1_PLL_LOCKDET_LOCKED) + { + reg = pci_tegra_pads_reedl(NV_PROJ__PCIE2_PADS_PLL_CTL1); + } + + /* turn off IDDQ override */ + reg = pci_tegra_pads_reedl(NV_PROJ__PCIE2_PADS_CTL_1); + reg = NVPCIE_FLD_SET_DRF_NUM(PADS, CTL_1, IDDQ_1L, 0, reg); + pci_tegra_pads_writel(reg, NV_PROJ__PCIE2_PADS_CTL_1); + + /* ENABLE TX/RX data */ + reg = pci_tegra_pads_reedl(NV_PROJ__PCIE2_PADS_CTL_1); + reg = NVPCIE_FLD_SET_DRF_NUM(PADS, CTL_1, TX_DATA_EN_1L, + NV_PROJ__PCIE2_PADS_CTL_1_TX_DATA_EN_1L_ENABLE, reg); + reg = NVPCIE_FLD_SET_DRF_NUM(PADS, CTL_1, RX_DATA_EN_1L, + NV_PROJ__PCIE2_PADS_CTL_1_RX_DATA_EN_1L_ENABLE, reg); + pci_tegra_pads_writel(reg, NV_PROJ__PCIE2_PADS_CTL_1); + + irq = tegra_get_module_inst_irq("pcie", 0, 0); + BUG_ON(irq == NO_IRQ); + if (request_irq(irq, pci_tegra_isr, IRQF_SHARED, "PCIE", s_hRmGlobal)) + { + pr_err("pci_tegra_setup: Cannot register the IRQ %d\n", irq); + } + set_irq_flags(irq, IRQF_VALID); + + /* setup the AFI address translations */ + pci_tegra_setup_translations(); + + /* Take the PCIe interface module out of reset to start the PCIe + * training sequence */ + NvRmModuleResetWithHold(s_hRmGlobal, NvRmPrivModuleID_PcieXclk, + NV_FALSE); + + /* Finally enable PCIe */ + reg = pci_tegra_afi_readl(AFI_CONFIGURATION_0); + reg = reg | AFI_CONFIGURATION_0_EN_FPCI_DEFAULT_MASK; + pci_tegra_afi_writel(reg, AFI_CONFIGURATION_0); + + if (!pci_tegra_check_rp(0) && !pci_tegra_check_rp(1)) { + pci_tegra_device_attached = false; + return 0; + } + pci_tegra_device_attached = true; + + /* Enable PCIe interrupts */ + reg = 0; + reg |= NV_DRF_NUM(AFI, AFI_INTR_ENABLE, EN_INI_SLVERR, 1); + reg |= NV_DRF_NUM(AFI, AFI_INTR_ENABLE, EN_INI_DECERR, 1); + reg |= NV_DRF_NUM(AFI, AFI_INTR_ENABLE, EN_TGT_SLVERR, 1); + reg |= NV_DRF_NUM(AFI, AFI_INTR_ENABLE, EN_TGT_DECERR, 1); + reg |= NV_DRF_NUM(AFI, AFI_INTR_ENABLE, EN_TGT_WRERR, 1); + reg |= NV_DRF_NUM(AFI, AFI_INTR_ENABLE, EN_DFPCI_DECERR, 1); + pci_tegra_afi_writel(reg, AFI_AFI_INTR_ENABLE_0); + pci_tegra_afi_writel(0xffffffff, AFI_SM_INTR_ENABLE_0); + + reg = 0; + reg |= NV_DRF_NUM(AFI, INTR_MASK, INT_MASK, 1); + /* FIXME: No MSI for now */ + reg |= NV_DRF_NUM(AFI, INTR_MASK, MSI_MASK, 0); + pci_tegra_afi_writel(reg, AFI_INTR_MASK_0); + + pci_tegra_enumerate(); + + pr_err("pci_tegra_setup: Successfull\n"); + return 1; +fail: + pr_err("pci_tegra_setup: failed\n"); + return 0; } static struct pci_bus __init *pci_tegra_scan_bus(int nr, @@ -92,11 +388,11 @@ static struct pci_bus __init *pci_tegra_scan_bus(int nr, } static struct hw_pci pci_tegra_data __initdata = { - .nr_controllers = 2, - .preinit = pci_tegra_preinit, - .setup = pci_tegra_setup, - .scan = pci_tegra_scan_bus, - .swizzle = pci_std_swizzle, + .nr_controllers = 2, + .preinit = pci_tegra_preinit, + .setup = pci_tegra_setup, + .scan = pci_tegra_scan_bus, + .swizzle = pci_std_swizzle, }; late_initcall(pcie_tegra_init); @@ -106,3 +402,244 @@ static int __init pcie_tegra_init(void) pci_common_init(&pci_tegra_data); return 0; } + +/* + * PCIe support functions + */ + +static void pci_tegra_power(int on) +{ + u32 settling_time; + const NvOdmPeripheralConnectivity *con = NULL; + int i; + + con = NvOdmPeripheralGetGuid(NV_VDD_PEX_CLK_ODM_ID); + if (con == NULL) + return; + + for (i = 0; i < con->NumAddress; i++) { + if (con->AddressList[i].Interface != NvOdmIoModule_Vdd) + continue; + if (on) { + NvRmPmuVddRailCapabilities rail; + NvRmPmuGetCapabilities(s_hRmGlobal, + con->AddressList[i].Address, &rail); + NvRmPmuSetVoltage(s_hRmGlobal, + con->AddressList[i].Address, + rail.requestMilliVolts, &settling_time); + } else + NvRmPmuSetVoltage(s_hRmGlobal, + con->AddressList[i].Address, NVODM_VOLTAGE_OFF, + &settling_time); + udelay(settling_time); + } +} + +static void pci_tegra_setup_translations(void) +{ + u32 fpci_bar; + u32 size; + u32 axi_address; + + /* Bar 0: Config Bar */ + fpci_bar = ((u32)0xfdff << 16); + size = PCIE_CONFIG_SIZE; + axi_address = TEGRA_PCIE_BASE + PCIE_CONFIG_OFFSET; + pci_tegra_afi_writel(axi_address, AFI_AXI_BAR0_START_0); + pci_tegra_afi_writel(size>>12, AFI_AXI_BAR0_SZ_0); + pci_tegra_afi_writel(fpci_bar, AFI_FPCI_BAR0_0); + + /* Bar 1: Extended config Bar */ + fpci_bar = ((u32)0xfe1 << 20); + size = PCIE_EXTENDED_CONFIG_SIZE; + axi_address = TEGRA_PCIE_BASE + PCIE_EXTENDED_CONFIG_OFFSET; + pci_tegra_afi_writel(axi_address, AFI_AXI_BAR1_START_0); + pci_tegra_afi_writel(size >>12, AFI_AXI_BAR1_SZ_0); + pci_tegra_afi_writel(fpci_bar, AFI_FPCI_BAR1_0); + + /* Bar 2: Downstream IO bar */ + fpci_bar = ((__u32)0xfdfc << 16); + size = PCIE_DOWNSTREAM_IO_SIZE; + axi_address = TEGRA_PCIE_BASE + PCIE_DOWNSTREAM_IO_OFFSET; + pci_tegra_afi_writel(axi_address, AFI_AXI_BAR2_START_0); + pci_tegra_afi_writel(size>>12, AFI_AXI_BAR2_SZ_0); + pci_tegra_afi_writel(fpci_bar, AFI_FPCI_BAR2_0); + + /* Bar 3: Pre-fetchable memory BAR */ + /* Bits 39:12 of 40 bit FPCI address goes to bits 31:4 */ + fpci_bar = (((FPCI_PREFETCH_MEMORY_OFFSET >> 12) & 0x0FFFFFFF) << 4); + fpci_bar |= 0x1; + size = PCIE_PREFETCH_MEMORY_SIZE; + axi_address = TEGRA_PCIE_BASE + PCIE_PREFETCH_MEMORY_OFFSET; + pci_tegra_afi_writel(axi_address, AFI_AXI_BAR3_START_0); + pci_tegra_afi_writel(size >> 12, AFI_AXI_BAR3_SZ_0); + pci_tegra_afi_writel(fpci_bar, AFI_FPCI_BAR3_0); + + /* Bar 4: Non pre-fetchable memory BAR */ + /* Bits 39:12 of 40 bit FPCI address goes to bits 31:4 */ + fpci_bar = (((FPCI_NON_PREFETCH_MEMORY_OFFSET >> 12) + & 0x0FFFFFFF) << 4); + fpci_bar |= 0x1; + size = PCIE_NON_PREFETCH_MEMORY_SIZE; + axi_address = TEGRA_PCIE_BASE + + PCIE_NON_PREFETCH_MEMORY_OFFSET; + pci_tegra_afi_writel(axi_address, AFI_AXI_BAR4_START_0); + pci_tegra_afi_writel(size >> 12, AFI_AXI_BAR4_SZ_0); + pci_tegra_afi_writel(fpci_bar, AFI_FPCI_BAR4_0); + + /* Bar 5: NULL out the remaining BAR as it is not used */ + fpci_bar = 0; + size = 0; + axi_address = 0; + pci_tegra_afi_writel(axi_address, AFI_AXI_BAR5_START_0); + pci_tegra_afi_writel(size >> 12, AFI_AXI_BAR5_SZ_0); + pci_tegra_afi_writel(fpci_bar, AFI_FPCI_BAR5_0); + + /* map all upstream transactions as uncached */ + pci_tegra_afi_writel(FPCI_SYSTEM_MEMORY_OFFSET, AFI_CACHE_BAR0_ST_0); + pci_tegra_afi_writel(0, AFI_CACHE_BAR0_SZ_0); + pci_tegra_afi_writel(0, AFI_CACHE_BAR1_ST_0); + pci_tegra_afi_writel(0, AFI_CACHE_BAR1_SZ_0); + + /* Map MSI bar */ + pci_tegra_afi_writel(0, AFI_MSI_FPCI_BAR_ST_0); + pci_tegra_afi_writel(0, AFI_MSI_BAR_SZ_0); + pci_tegra_afi_writel(0, AFI_MSI_AXI_BAR_ST_0); + pci_tegra_afi_writel(0, AFI_MSI_BAR_SZ_0); +} + +static irqreturn_t pci_tegra_isr(int irq, void *arg) +{ + u32 intr_info, intr_extended_info; + irqreturn_t ret = IRQ_HANDLED; + + intr_info = pci_tegra_afi_readl(AFI_INTR_CODE_0); + intr_info = NV_DRF_VAL(AFI, INTR_CODE, INT_CODE, intr_info); + intr_extended_info = pci_tegra_afi_readl(AFI_INTR_SIGNATURE_0); + + /* pr_err("+pci_tegra_isr\n"); */ + + switch (intr_info) { + case 6: /* legacy */ + ret = IRQ_NONE; + break; + case 1: /* SLVERR */ + pr_err("pci_tegra_isr: AXI slave error\n"); + break; + case 2: /* DECERR */ + pr_err("pci_tegra_isr: AXI decode error\n"); + break; + case 3: /* PCIE target abort */ + pr_err("pci_tegra_isr: Target abort\n"); + break; + case 4: /* PCIE master abort */ + /* Don't print this, as this error is a common error during + * enumeration. + */ + /* pr_err("pci_tegra_isr: Master abort\n"); */ + break; + case 5: /* Bufferable write to non-posted write */ + pr_err("pci_tegra_isr: Invalid write" + " - Bufferable write to non-posted region\n"); + break; + case 7: /* Response address mapping error */ + pr_err("pci_tegra_isr: Response decoding error \n"); + break; + case 8: /* Response address mapping error */ + pr_err("pci_tegra_isr: AXI response decoding error\n"); + break; + case 9: /* PCIE timeout */ + pr_err("pci_tegra_isr: Transcation timeout\n"); + break; + default: + pr_err("pci_tegra_isr: Unknown interrupt\n"); + break; + } + + /* Clear the interrupt code register to sample the next interrupt */ + pci_tegra_afi_writel(0, AFI_INTR_CODE_0); + + /* pr_err("-pci_tegra_isr\n"); */ + return ret; +} + + +/* + * FIXME: If there are no PCIe cards attached, then calling this function + * can result in the increase of the bootup time as there are big timeout + * loops. + */ +static bool pci_tegra_check_rp(int rp) +{ +#define PCI_TEGRA_LINKUP_TIMEOUT 50 /* i.e .2 seconds */ + u32 reg; + int retry_count = 0; + int loop_counter; + + BUG_ON(rp != 0 && rp != 1); +retry: + if (retry_count > 2 ) { + pr_err("pci_tegra_check_rp: RP %d Failed\n", rp); + return false; + } + + if (retry_count != 0) { + u32 offset; + + /* Reset before retrying again. */ + if (rp == 0) offset = AFI_PEX0_CTRL_0; + if (rp == 1) offset = AFI_PEX1_CTRL_0; + + BUG_ON(AFI_PEX0_CTRL_0_PEX0_RST_L_SHIFT != + AFI_PEX1_CTRL_0_PEX1_RST_L_SHIFT); + + /* Pulse the PEX reset */ + reg = pci_tegra_afi_readl(offset); + reg = NV_FLD_SET_DRF_NUM(AFI, PEX0_CTRL, PEX0_RST_L, 1, reg); + pci_tegra_afi_writel(reg, offset); + + msleep(100); + + reg = pci_tegra_afi_readl(offset); + reg = NV_FLD_SET_DRF_NUM(AFI, PEX0_CTRL, PEX0_RST_L, 0, reg); + pci_tegra_afi_writel(reg, offset); + } + + loop_counter = PCI_TEGRA_LINKUP_TIMEOUT; + + reg = pci_tegra_rp_readl(NV_PROJ__PCIE2_RP_VEND_XP, rp); + while (loop_counter && (NVPCIE_DRF_VAL(RP, VEND_XP, DL_UP, reg)!= 1)) { + msleep(1); + reg = pci_tegra_rp_readl(NV_PROJ__PCIE2_RP_VEND_XP, rp); + loop_counter --; + } + + if (!loop_counter) { + retry_count++; + pr_err("pci_tegra_check_rp: " + "RP %d LINK is not up...retrying...\n", rp); + goto retry; + } + + loop_counter = PCI_TEGRA_LINKUP_TIMEOUT; + + reg = pci_tegra_rp_readl(NV_PROJ__PCIE2_RP_LINK_CONTROL_STATUS, rp); + reg = NVPCIE_DRF_VAL(RP, LINK_CONTROL_STATUS, LINKSTAT, reg); + while (loop_counter && ((reg & 0x2000) != 0x2000)) { + msleep(1); + reg = pci_tegra_rp_readl(NV_PROJ__PCIE2_RP_LINK_CONTROL_STATUS, + rp); + reg = NVPCIE_DRF_VAL(RP, LINK_CONTROL_STATUS, LINKSTAT, reg); + pr_err("pci_tegra_check_rp: " + "RP %d LINK status 0x%x...retrying...\n", rp, reg); + loop_counter --; + } + + if (!loop_counter) { + retry_count++; + goto retry; + } + + pr_info("pci_tegra_check_rp: RP %d success\n", rp); + return true; +} diff --git a/arch/arm/mach-tegra/tegra_sysmap.c b/arch/arm/mach-tegra/tegra_sysmap.c index 13550ff45322..cb5b88ced984 100644 --- a/arch/arm/mach-tegra/tegra_sysmap.c +++ b/arch/arm/mach-tegra/tegra_sysmap.c @@ -34,6 +34,8 @@ static NvRmModuleID tegra_map_name_to_mod(const char *name, int inst) return NVRM_MODULE_ID(NvRmPrivModuleID_Pl310, inst); else if (!strcmp(name, "scu")) return NVRM_MODULE_ID(NvRmPrivModuleID_ArmPerif, inst); + else if (!strcmp(name, "pcie")) + return NVRM_MODULE_ID(NvRmPrivModuleID_Pcie, inst); return (NvRmModuleID) 0; } |