pci: tegra: Add Tegra PCIe driver
authorThierry Reding <treding@nvidia.com>
Wed, 10 Dec 2014 05:25:12 +0000 (22:25 -0700)
committerTom Warren <twarren@nvidia.com>
Thu, 18 Dec 2014 20:19:20 +0000 (13:19 -0700)
Add support for the PCIe controller found on some generations of Tegra.
Tegra20 has 2 root ports with a total of 4 lanes, Tegra30 has 3 root
ports with a total of 6 lanes and Tegra124 has 2 root ports with a total
of 5 lanes.

This is based on the Linux kernel driver, originally submitted upstream
by Mike Rapoport.

Signed-off-by: Mike Rapoport <mike@compulab.co.il>
Signed-off-by: Thierry Reding <treding@nvidia.com>
Signed-off-by: Simon Glass <sjg@chromium.org>
Signed-off-by: Tom Warren <twarren@nvidia.com>
drivers/pci/Makefile
drivers/pci/pci_tegra.c [new file with mode: 0644]
include/fdtdec.h
lib/fdtdec.c

index 85e82bdb8c3dd385bb34b61c391e77c311b7775c..50b7be53cae82268bb561d1f8fcbba059d29831a 100644 (file)
@@ -15,6 +15,7 @@ obj-$(CONFIG_FTPCI100) += pci_ftpci100.o
 obj-$(CONFIG_SH4_PCI) += pci_sh4.o
 obj-$(CONFIG_SH7751_PCI) +=pci_sh7751.o
 obj-$(CONFIG_SH7780_PCI) +=pci_sh7780.o
+obj-$(CONFIG_PCI_TEGRA) += pci_tegra.o
 obj-$(CONFIG_TSI108_PCI) += tsi108_pci.o
 obj-$(CONFIG_WINBOND_83C553) += w83c553f.o
 obj-$(CONFIG_PCIE_LAYERSCAPE) += pcie_layerscape.o
diff --git a/drivers/pci/pci_tegra.c b/drivers/pci/pci_tegra.c
new file mode 100644 (file)
index 0000000..a03ad5f
--- /dev/null
@@ -0,0 +1,1143 @@
+/*
+ * Copyright (c) 2010, CompuLab, Ltd.
+ * Author: Mike Rapoport <mike@compulab.co.il>
+ *
+ * Based on NVIDIA PCIe driver
+ * Copyright (c) 2008-2009, NVIDIA Corporation.
+ *
+ * Copyright (c) 2013-2014, NVIDIA Corporation.
+ *
+ * SPDX-License-Identifier:    GPL-2.0
+ */
+
+#define DEBUG
+#define pr_fmt(fmt) "tegra-pcie: " fmt
+
+#include <common.h>
+#include <errno.h>
+#include <fdtdec.h>
+#include <malloc.h>
+#include <pci.h>
+
+#include <asm/io.h>
+#include <asm/gpio.h>
+
+#include <asm/arch/clock.h>
+#include <asm/arch/powergate.h>
+#include <asm/arch-tegra/xusb-padctl.h>
+
+#include <linux/list.h>
+
+#include <dt-bindings/pinctrl/pinctrl-tegra-xusb.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+#define AFI_AXI_BAR0_SZ        0x00
+#define AFI_AXI_BAR1_SZ        0x04
+#define AFI_AXI_BAR2_SZ        0x08
+#define AFI_AXI_BAR3_SZ        0x0c
+#define AFI_AXI_BAR4_SZ        0x10
+#define AFI_AXI_BAR5_SZ        0x14
+
+#define AFI_AXI_BAR0_START     0x18
+#define AFI_AXI_BAR1_START     0x1c
+#define AFI_AXI_BAR2_START     0x20
+#define AFI_AXI_BAR3_START     0x24
+#define AFI_AXI_BAR4_START     0x28
+#define AFI_AXI_BAR5_START     0x2c
+
+#define AFI_FPCI_BAR0  0x30
+#define AFI_FPCI_BAR1  0x34
+#define AFI_FPCI_BAR2  0x38
+#define AFI_FPCI_BAR3  0x3c
+#define AFI_FPCI_BAR4  0x40
+#define AFI_FPCI_BAR5  0x44
+
+#define AFI_CACHE_BAR0_SZ      0x48
+#define AFI_CACHE_BAR0_ST      0x4c
+#define AFI_CACHE_BAR1_SZ      0x50
+#define AFI_CACHE_BAR1_ST      0x54
+
+#define AFI_MSI_BAR_SZ         0x60
+#define AFI_MSI_FPCI_BAR_ST    0x64
+#define AFI_MSI_AXI_BAR_ST     0x68
+
+#define AFI_CONFIGURATION              0xac
+#define  AFI_CONFIGURATION_EN_FPCI     (1 << 0)
+
+#define AFI_FPCI_ERROR_MASKS   0xb0
+
+#define AFI_INTR_MASK          0xb4
+#define  AFI_INTR_MASK_INT_MASK        (1 << 0)
+#define  AFI_INTR_MASK_MSI_MASK        (1 << 8)
+
+#define AFI_SM_INTR_ENABLE     0xc4
+#define  AFI_SM_INTR_INTA_ASSERT       (1 << 0)
+#define  AFI_SM_INTR_INTB_ASSERT       (1 << 1)
+#define  AFI_SM_INTR_INTC_ASSERT       (1 << 2)
+#define  AFI_SM_INTR_INTD_ASSERT       (1 << 3)
+#define  AFI_SM_INTR_INTA_DEASSERT     (1 << 4)
+#define  AFI_SM_INTR_INTB_DEASSERT     (1 << 5)
+#define  AFI_SM_INTR_INTC_DEASSERT     (1 << 6)
+#define  AFI_SM_INTR_INTD_DEASSERT     (1 << 7)
+
+#define AFI_AFI_INTR_ENABLE            0xc8
+#define  AFI_INTR_EN_INI_SLVERR                (1 << 0)
+#define  AFI_INTR_EN_INI_DECERR                (1 << 1)
+#define  AFI_INTR_EN_TGT_SLVERR                (1 << 2)
+#define  AFI_INTR_EN_TGT_DECERR                (1 << 3)
+#define  AFI_INTR_EN_TGT_WRERR         (1 << 4)
+#define  AFI_INTR_EN_DFPCI_DECERR      (1 << 5)
+#define  AFI_INTR_EN_AXI_DECERR                (1 << 6)
+#define  AFI_INTR_EN_FPCI_TIMEOUT      (1 << 7)
+#define  AFI_INTR_EN_PRSNT_SENSE       (1 << 8)
+
+#define AFI_PCIE_CONFIG                                        0x0f8
+#define  AFI_PCIE_CONFIG_PCIE_DISABLE(x)               (1 << ((x) + 1))
+#define  AFI_PCIE_CONFIG_PCIE_DISABLE_ALL              0xe
+#define  AFI_PCIE_CONFIG_SM2TMS0_XBAR_CONFIG_MASK      (0xf << 20)
+#define  AFI_PCIE_CONFIG_SM2TMS0_XBAR_CONFIG_SINGLE    (0x0 << 20)
+#define  AFI_PCIE_CONFIG_SM2TMS0_XBAR_CONFIG_420       (0x0 << 20)
+#define  AFI_PCIE_CONFIG_SM2TMS0_XBAR_CONFIG_X2_X1     (0x0 << 20)
+#define  AFI_PCIE_CONFIG_SM2TMS0_XBAR_CONFIG_DUAL      (0x1 << 20)
+#define  AFI_PCIE_CONFIG_SM2TMS0_XBAR_CONFIG_222       (0x1 << 20)
+#define  AFI_PCIE_CONFIG_SM2TMS0_XBAR_CONFIG_X4_X1     (0x1 << 20)
+#define  AFI_PCIE_CONFIG_SM2TMS0_XBAR_CONFIG_411       (0x2 << 20)
+
+#define AFI_FUSE                       0x104
+#define  AFI_FUSE_PCIE_T0_GEN2_DIS     (1 << 2)
+
+#define AFI_PEX0_CTRL                  0x110
+#define AFI_PEX1_CTRL                  0x118
+#define AFI_PEX2_CTRL                  0x128
+#define  AFI_PEX_CTRL_RST              (1 << 0)
+#define  AFI_PEX_CTRL_CLKREQ_EN                (1 << 1)
+#define  AFI_PEX_CTRL_REFCLK_EN                (1 << 3)
+#define  AFI_PEX_CTRL_OVERRIDE_EN      (1 << 4)
+
+#define AFI_PLLE_CONTROL               0x160
+#define  AFI_PLLE_CONTROL_BYPASS_PADS2PLLE_CONTROL (1 << 9)
+#define  AFI_PLLE_CONTROL_PADS2PLLE_CONTROL_EN (1 << 1)
+
+#define AFI_PEXBIAS_CTRL_0             0x168
+
+#define PADS_CTL_SEL           0x0000009C
+
+#define PADS_CTL               0x000000A0
+#define  PADS_CTL_IDDQ_1L      (1 <<  0)
+#define  PADS_CTL_TX_DATA_EN_1L        (1 <<  6)
+#define  PADS_CTL_RX_DATA_EN_1L        (1 << 10)
+
+#define PADS_PLL_CTL_TEGRA20                   0x000000B8
+#define PADS_PLL_CTL_TEGRA30                   0x000000B4
+#define  PADS_PLL_CTL_RST_B4SM                 (0x1 <<  1)
+#define  PADS_PLL_CTL_LOCKDET                  (0x1 <<  8)
+#define  PADS_PLL_CTL_REFCLK_MASK              (0x3 << 16)
+#define  PADS_PLL_CTL_REFCLK_INTERNAL_CML      (0x0 << 16)
+#define  PADS_PLL_CTL_REFCLK_INTERNAL_CMOS     (0x1 << 16)
+#define  PADS_PLL_CTL_REFCLK_EXTERNAL          (0x2 << 16)
+#define  PADS_PLL_CTL_TXCLKREF_MASK            (0x1 << 20)
+#define  PADS_PLL_CTL_TXCLKREF_DIV10           (0x0 << 20)
+#define  PADS_PLL_CTL_TXCLKREF_DIV5            (0x1 << 20)
+#define  PADS_PLL_CTL_TXCLKREF_BUF_EN          (0x1 << 22)
+
+#define PADS_REFCLK_CFG0                       0x000000C8
+#define PADS_REFCLK_CFG1                       0x000000CC
+
+/*
+ * Fields in PADS_REFCLK_CFG*. Those registers form an array of 16-bit
+ * entries, one entry per PCIe port. These field definitions and desired
+ * values aren't in the TRM, but do come from NVIDIA.
+ */
+#define PADS_REFCLK_CFG_TERM_SHIFT             2  /* 6:2 */
+#define PADS_REFCLK_CFG_E_TERM_SHIFT           7
+#define PADS_REFCLK_CFG_PREDI_SHIFT            8  /* 11:8 */
+#define PADS_REFCLK_CFG_DRVI_SHIFT             12 /* 15:12 */
+
+/* Default value provided by HW engineering is 0xfa5c */
+#define PADS_REFCLK_CFG_VALUE \
+       ( \
+               (0x17 << PADS_REFCLK_CFG_TERM_SHIFT)   | \
+               (0    << PADS_REFCLK_CFG_E_TERM_SHIFT) | \
+               (0xa  << PADS_REFCLK_CFG_PREDI_SHIFT)  | \
+               (0xf  << PADS_REFCLK_CFG_DRVI_SHIFT)     \
+       )
+
+#define RP_VEND_XP     0x00000F00
+#define  RP_VEND_XP_DL_UP      (1 << 30)
+
+#define RP_PRIV_MISC   0x00000FE0
+#define  RP_PRIV_MISC_PRSNT_MAP_EP_PRSNT (0xE << 0)
+#define  RP_PRIV_MISC_PRSNT_MAP_EP_ABSNT (0xF << 0)
+
+#define RP_LINK_CONTROL_STATUS                 0x00000090
+#define  RP_LINK_CONTROL_STATUS_DL_LINK_ACTIVE 0x20000000
+#define  RP_LINK_CONTROL_STATUS_LINKSTAT_MASK  0x3fff0000
+
+struct tegra_pcie;
+
+struct tegra_pcie_port {
+       struct tegra_pcie *pcie;
+
+       struct fdt_resource regs;
+       unsigned int num_lanes;
+       unsigned int index;
+
+       struct list_head list;
+};
+
+struct tegra_pcie_soc {
+       unsigned int num_ports;
+       unsigned long pads_pll_ctl;
+       unsigned long tx_ref_sel;
+       bool has_pex_clkreq_en;
+       bool has_pex_bias_ctrl;
+       bool has_cml_clk;
+       bool has_gen2;
+};
+
+struct tegra_pcie {
+       struct pci_controller hose;
+
+       struct fdt_resource pads;
+       struct fdt_resource afi;
+       struct fdt_resource cs;
+
+       struct fdt_resource prefetch;
+       struct fdt_resource mem;
+       struct fdt_resource io;
+
+       struct list_head ports;
+       unsigned long xbar;
+
+       const struct tegra_pcie_soc *soc;
+       struct tegra_xusb_phy *phy;
+};
+
+static inline struct tegra_pcie *to_tegra_pcie(struct pci_controller *hose)
+{
+       return container_of(hose, struct tegra_pcie, hose);
+}
+
+static void afi_writel(struct tegra_pcie *pcie, unsigned long value,
+                      unsigned long offset)
+{
+       writel(value, pcie->afi.start + offset);
+}
+
+static unsigned long afi_readl(struct tegra_pcie *pcie, unsigned long offset)
+{
+       return readl(pcie->afi.start + offset);
+}
+
+static void pads_writel(struct tegra_pcie *pcie, unsigned long value,
+                       unsigned long offset)
+{
+       writel(value, pcie->pads.start + offset);
+}
+
+static unsigned long pads_readl(struct tegra_pcie *pcie, unsigned long offset)
+{
+       return readl(pcie->pads.start + offset);
+}
+
+static unsigned long rp_readl(struct tegra_pcie_port *port,
+                             unsigned long offset)
+{
+       return readl(port->regs.start + offset);
+}
+
+static void rp_writel(struct tegra_pcie_port *port, unsigned long value,
+                     unsigned long offset)
+{
+       writel(value, port->regs.start + offset);
+}
+
+static unsigned long tegra_pcie_conf_offset(pci_dev_t bdf, int where)
+{
+       return ((where & 0xf00) << 16) | (PCI_BUS(bdf) << 16) |
+              (PCI_DEV(bdf) << 11) | (PCI_FUNC(bdf) << 8) |
+              (where & 0xfc);
+}
+
+static int tegra_pcie_conf_address(struct tegra_pcie *pcie, pci_dev_t bdf,
+                                  int where, unsigned long *address)
+{
+       unsigned int bus = PCI_BUS(bdf);
+
+       if (bus == 0) {
+               unsigned int dev = PCI_DEV(bdf);
+               struct tegra_pcie_port *port;
+
+               list_for_each_entry(port, &pcie->ports, list) {
+                       if (port->index + 1 == dev) {
+                               *address = port->regs.start + (where & ~3);
+                               return 0;
+                       }
+               }
+       } else {
+               *address = pcie->cs.start + tegra_pcie_conf_offset(bdf, where);
+               return 0;
+       }
+
+       return -1;
+}
+
+static int tegra_pcie_read_conf(struct pci_controller *hose, pci_dev_t bdf,
+                               int where, u32 *value)
+{
+       struct tegra_pcie *pcie = to_tegra_pcie(hose);
+       unsigned long address;
+       int err;
+
+       err = tegra_pcie_conf_address(pcie, bdf, where, &address);
+       if (err < 0) {
+               *value = 0xffffffff;
+               return 1;
+       }
+
+       *value = readl(address);
+
+       /* fixup root port class */
+       if (PCI_BUS(bdf) == 0) {
+               if (where == PCI_CLASS_REVISION) {
+                       *value &= ~0x00ff0000;
+                       *value |= PCI_CLASS_BRIDGE_PCI << 16;
+               }
+       }
+
+       return 0;
+}
+
+static int tegra_pcie_write_conf(struct pci_controller *hose, pci_dev_t bdf,
+                                int where, u32 value)
+{
+       struct tegra_pcie *pcie = to_tegra_pcie(hose);
+       unsigned long address;
+       int err;
+
+       err = tegra_pcie_conf_address(pcie, bdf, where, &address);
+       if (err < 0)
+               return 1;
+
+       writel(value, address);
+
+       return 0;
+}
+
+static int tegra_pcie_port_parse_dt(const void *fdt, int node,
+                                   struct tegra_pcie_port *port)
+{
+       const u32 *addr;
+       int len;
+
+       addr = fdt_getprop(fdt, node, "assigned-addresses", &len);
+       if (!addr) {
+               error("property \"assigned-addresses\" not found");
+               return -FDT_ERR_NOTFOUND;
+       }
+
+       port->regs.start = fdt32_to_cpu(addr[2]);
+       port->regs.end = port->regs.start + fdt32_to_cpu(addr[4]);
+
+       return 0;
+}
+
+static int tegra_pcie_get_xbar_config(const void *fdt, int node, u32 lanes,
+                                     unsigned long *xbar)
+{
+       enum fdt_compat_id id = fdtdec_lookup(fdt, node);
+
+       switch (id) {
+       case COMPAT_NVIDIA_TEGRA20_PCIE:
+               switch (lanes) {
+               case 0x00000004:
+                       debug("single-mode configuration\n");
+                       *xbar = AFI_PCIE_CONFIG_SM2TMS0_XBAR_CONFIG_SINGLE;
+                       return 0;
+
+               case 0x00000202:
+                       debug("dual-mode configuration\n");
+                       *xbar = AFI_PCIE_CONFIG_SM2TMS0_XBAR_CONFIG_DUAL;
+                       return 0;
+               }
+               break;
+
+       case COMPAT_NVIDIA_TEGRA30_PCIE:
+               switch (lanes) {
+               case 0x00000204:
+                       debug("4x1, 2x1 configuration\n");
+                       *xbar = AFI_PCIE_CONFIG_SM2TMS0_XBAR_CONFIG_420;
+                       return 0;
+
+               case 0x00020202:
+                       debug("2x3 configuration\n");
+                       *xbar = AFI_PCIE_CONFIG_SM2TMS0_XBAR_CONFIG_222;
+                       return 0;
+
+               case 0x00010104:
+                       debug("4x1, 1x2 configuration\n");
+                       *xbar = AFI_PCIE_CONFIG_SM2TMS0_XBAR_CONFIG_411;
+                       return 0;
+               }
+               break;
+
+       case COMPAT_NVIDIA_TEGRA124_PCIE:
+               switch (lanes) {
+               case 0x0000104:
+                       debug("4x1, 1x1 configuration\n");
+                       *xbar = AFI_PCIE_CONFIG_SM2TMS0_XBAR_CONFIG_X4_X1;
+                       return 0;
+
+               case 0x0000102:
+                       debug("2x1, 1x1 configuration\n");
+                       *xbar = AFI_PCIE_CONFIG_SM2TMS0_XBAR_CONFIG_X2_X1;
+                       return 0;
+               }
+               break;
+
+       default:
+               break;
+       }
+
+       return -FDT_ERR_NOTFOUND;
+}
+
+static int tegra_pcie_parse_dt_ranges(const void *fdt, int node,
+                                     struct tegra_pcie *pcie)
+{
+       const u32 *ptr, *end;
+       int len;
+
+       ptr = fdt_getprop(fdt, node, "ranges", &len);
+       if (!ptr) {
+               error("missing \"ranges\" property");
+               return -FDT_ERR_NOTFOUND;
+       }
+
+       end = ptr + len / 4;
+
+       while (ptr < end) {
+               struct fdt_resource *res = NULL;
+               u32 space = fdt32_to_cpu(*ptr);
+
+               switch ((space >> 24) & 0x3) {
+               case 0x01:
+                       res = &pcie->io;
+                       break;
+
+               case 0x02: /* 32 bit */
+               case 0x03: /* 64 bit */
+                       if (space & (1 << 30))
+                               res = &pcie->prefetch;
+                       else
+                               res = &pcie->mem;
+
+                       break;
+               }
+
+               if (res) {
+                       res->start = fdt32_to_cpu(ptr[3]);
+                       res->end = res->start + fdt32_to_cpu(ptr[5]);
+               }
+
+               ptr += 3 + 1 + 2;
+       }
+
+       debug("PCI regions:\n");
+       debug("  I/O: %#x-%#x\n", pcie->io.start, pcie->io.end);
+       debug("  non-prefetchable memory: %#x-%#x\n", pcie->mem.start,
+             pcie->mem.end);
+       debug("  prefetchable memory: %#x-%#x\n", pcie->prefetch.start,
+             pcie->prefetch.end);
+
+       return 0;
+}
+
+static int tegra_pcie_parse_port_info(const void *fdt, int node,
+                                     unsigned int *index,
+                                     unsigned int *lanes)
+{
+       pci_dev_t bdf;
+       int err;
+
+       err = fdtdec_get_int(fdt, node, "nvidia,num-lanes", 0);
+       if (err < 0) {
+               error("failed to parse \"nvidia,num-lanes\" property");
+               return err;
+       }
+
+       *lanes = err;
+
+       err = fdtdec_pci_get_bdf(fdt, node, &bdf);
+       if (err < 0) {
+               error("failed to parse \"reg\" property");
+               return err;
+       }
+
+       *index = PCI_DEV(bdf) - 1;
+
+       return 0;
+}
+
+static int tegra_pcie_parse_dt(const void *fdt, int node,
+                              struct tegra_pcie *pcie)
+{
+       int err, subnode;
+       u32 lanes = 0;
+
+       err = fdt_get_named_resource(fdt, node, "reg", "reg-names", "pads",
+                                    &pcie->pads);
+       if (err < 0) {
+               error("resource \"pads\" not found");
+               return err;
+       }
+
+       err = fdt_get_named_resource(fdt, node, "reg", "reg-names", "afi",
+                                    &pcie->afi);
+       if (err < 0) {
+               error("resource \"afi\" not found");
+               return err;
+       }
+
+       err = fdt_get_named_resource(fdt, node, "reg", "reg-names", "cs",
+                                    &pcie->cs);
+       if (err < 0) {
+               error("resource \"cs\" not found");
+               return err;
+       }
+
+       pcie->phy = tegra_xusb_phy_get(TEGRA_XUSB_PADCTL_PCIE);
+       if (pcie->phy) {
+               err = tegra_xusb_phy_prepare(pcie->phy);
+               if (err < 0) {
+                       error("failed to prepare PHY: %d", err);
+                       return err;
+               }
+       }
+
+       err = tegra_pcie_parse_dt_ranges(fdt, node, pcie);
+       if (err < 0) {
+               error("failed to parse \"ranges\" property");
+               return err;
+       }
+
+       fdt_for_each_subnode(fdt, subnode, node) {
+               unsigned int index = 0, num_lanes = 0;
+               struct tegra_pcie_port *port;
+
+               err = tegra_pcie_parse_port_info(fdt, subnode, &index,
+                                                &num_lanes);
+               if (err < 0) {
+                       error("failed to obtain root port info");
+                       continue;
+               }
+
+               lanes |= num_lanes << (index << 3);
+
+               if (!fdtdec_get_is_enabled(fdt, subnode))
+                       continue;
+
+               port = malloc(sizeof(*port));
+               if (!port)
+                       continue;
+
+               memset(port, 0, sizeof(*port));
+               port->num_lanes = num_lanes;
+               port->index = index;
+
+               err = tegra_pcie_port_parse_dt(fdt, subnode, port);
+               if (err < 0) {
+                       free(port);
+                       continue;
+               }
+
+               list_add_tail(&port->list, &pcie->ports);
+               port->pcie = pcie;
+       }
+
+       err = tegra_pcie_get_xbar_config(fdt, node, lanes, &pcie->xbar);
+       if (err < 0) {
+               error("invalid lane configuration");
+               return err;
+       }
+
+       return 0;
+}
+
+int __weak tegra_pcie_board_init(void)
+{
+       return 0;
+}
+
+static int tegra_pcie_power_on(struct tegra_pcie *pcie)
+{
+       const struct tegra_pcie_soc *soc = pcie->soc;
+       unsigned long value;
+       int err;
+
+       /* reset PCIEXCLK logic, AFI controller and PCIe controller */
+       reset_set_enable(PERIPH_ID_PCIEXCLK, 1);
+       reset_set_enable(PERIPH_ID_AFI, 1);
+       reset_set_enable(PERIPH_ID_PCIE, 1);
+
+       err = tegra_powergate_power_off(TEGRA_POWERGATE_PCIE);
+       if (err < 0) {
+               error("failed to power off PCIe partition: %d", err);
+               return err;
+       }
+
+       tegra_pcie_board_init();
+
+       err = tegra_powergate_sequence_power_up(TEGRA_POWERGATE_PCIE,
+                                               PERIPH_ID_PCIE);
+       if (err < 0) {
+               error("failed to power up PCIe partition: %d", err);
+               return err;
+       }
+
+       /* take AFI controller out of reset */
+       reset_set_enable(PERIPH_ID_AFI, 0);
+
+       /* enable AFI clock */
+       clock_enable(PERIPH_ID_AFI);
+
+       if (soc->has_cml_clk) {
+               /* enable CML clock */
+               value = readl(NV_PA_CLK_RST_BASE + 0x48c);
+               value |= (1 << 0);
+               value &= ~(1 << 1);
+               writel(value, NV_PA_CLK_RST_BASE + 0x48c);
+       }
+
+       err = tegra_plle_enable();
+       if (err < 0) {
+               error("failed to enable PLLE: %d\n", err);
+               return err;
+       }
+
+       return 0;
+}
+
+static int tegra_pcie_pll_wait(struct tegra_pcie *pcie, unsigned long timeout)
+{
+       const struct tegra_pcie_soc *soc = pcie->soc;
+       unsigned long start = get_timer(0);
+       u32 value;
+
+       while (get_timer(start) < timeout) {
+               value = pads_readl(pcie, soc->pads_pll_ctl);
+               if (value & PADS_PLL_CTL_LOCKDET)
+                       return 0;
+       }
+
+       return -ETIMEDOUT;
+}
+
+static int tegra_pcie_phy_enable(struct tegra_pcie *pcie)
+{
+       const struct tegra_pcie_soc *soc = pcie->soc;
+       u32 value;
+       int err;
+
+       /* initialize internal PHY, enable up to 16 PCIe lanes */
+       pads_writel(pcie, 0, PADS_CTL_SEL);
+
+       /* override IDDQ to 1 on all 4 lanes */
+       value = pads_readl(pcie, PADS_CTL);
+       value |= PADS_CTL_IDDQ_1L;
+       pads_writel(pcie, value, PADS_CTL);
+
+       /*
+        * Set up PHY PLL inputs select PLLE output as refclock, set TX
+        * ref sel to div10 (not div5).
+        */
+       value = pads_readl(pcie, soc->pads_pll_ctl);
+       value &= ~(PADS_PLL_CTL_REFCLK_MASK | PADS_PLL_CTL_TXCLKREF_MASK);
+       value |= PADS_PLL_CTL_REFCLK_INTERNAL_CML | soc->tx_ref_sel;
+       pads_writel(pcie, value, soc->pads_pll_ctl);
+
+       /* reset PLL */
+       value = pads_readl(pcie, soc->pads_pll_ctl);
+       value &= ~PADS_PLL_CTL_RST_B4SM;
+       pads_writel(pcie, value, soc->pads_pll_ctl);
+
+       udelay(20);
+
+       /* take PLL out of reset */
+       value = pads_readl(pcie, soc->pads_pll_ctl);
+       value |= PADS_PLL_CTL_RST_B4SM;
+       pads_writel(pcie, value, soc->pads_pll_ctl);
+
+       /* configure the reference clock driver */
+       value = PADS_REFCLK_CFG_VALUE | (PADS_REFCLK_CFG_VALUE << 16);
+       pads_writel(pcie, value, PADS_REFCLK_CFG0);
+
+       if (soc->num_ports > 2)
+               pads_writel(pcie, PADS_REFCLK_CFG_VALUE, PADS_REFCLK_CFG1);
+
+       /* wait for the PLL to lock */
+       err = tegra_pcie_pll_wait(pcie, 500);
+       if (err < 0) {
+               error("PLL failed to lock: %d", err);
+               return err;
+       }
+
+       /* turn off IDDQ override */
+       value = pads_readl(pcie, PADS_CTL);
+       value &= ~PADS_CTL_IDDQ_1L;
+       pads_writel(pcie, value, PADS_CTL);
+
+       /* enable TX/RX data */
+       value = pads_readl(pcie, PADS_CTL);
+       value |= PADS_CTL_TX_DATA_EN_1L | PADS_CTL_RX_DATA_EN_1L;
+       pads_writel(pcie, value, PADS_CTL);
+
+       return 0;
+}
+
+static int tegra_pcie_enable_controller(struct tegra_pcie *pcie)
+{
+       const struct tegra_pcie_soc *soc = pcie->soc;
+       struct tegra_pcie_port *port;
+       u32 value;
+       int err;
+
+       if (pcie->phy) {
+               value = afi_readl(pcie, AFI_PLLE_CONTROL);
+               value &= ~AFI_PLLE_CONTROL_BYPASS_PADS2PLLE_CONTROL;
+               value |= AFI_PLLE_CONTROL_PADS2PLLE_CONTROL_EN;
+               afi_writel(pcie, value, AFI_PLLE_CONTROL);
+       }
+
+       if (soc->has_pex_bias_ctrl)
+               afi_writel(pcie, 0, AFI_PEXBIAS_CTRL_0);
+
+       value = afi_readl(pcie, AFI_PCIE_CONFIG);
+       value &= ~AFI_PCIE_CONFIG_SM2TMS0_XBAR_CONFIG_MASK;
+       value |= AFI_PCIE_CONFIG_PCIE_DISABLE_ALL | pcie->xbar;
+
+       list_for_each_entry(port, &pcie->ports, list)
+               value &= ~AFI_PCIE_CONFIG_PCIE_DISABLE(port->index);
+
+       afi_writel(pcie, value, AFI_PCIE_CONFIG);
+
+       value = afi_readl(pcie, AFI_FUSE);
+
+       if (soc->has_gen2)
+               value &= ~AFI_FUSE_PCIE_T0_GEN2_DIS;
+       else
+               value |= AFI_FUSE_PCIE_T0_GEN2_DIS;
+
+       afi_writel(pcie, value, AFI_FUSE);
+
+       if (pcie->phy)
+               err = tegra_xusb_phy_enable(pcie->phy);
+       else
+               err = tegra_pcie_phy_enable(pcie);
+
+       if (err < 0) {
+               error("failed to power on PHY: %d\n", err);
+               return err;
+       }
+
+       /* take the PCIEXCLK logic out of reset */
+       reset_set_enable(PERIPH_ID_PCIEXCLK, 0);
+
+       /* finally enable PCIe */
+       value = afi_readl(pcie, AFI_CONFIGURATION);
+       value |= AFI_CONFIGURATION_EN_FPCI;
+       afi_writel(pcie, value, AFI_CONFIGURATION);
+
+       /* disable all interrupts */
+       afi_writel(pcie, 0, AFI_AFI_INTR_ENABLE);
+       afi_writel(pcie, 0, AFI_SM_INTR_ENABLE);
+       afi_writel(pcie, 0, AFI_INTR_MASK);
+       afi_writel(pcie, 0, AFI_FPCI_ERROR_MASKS);
+
+       return 0;
+}
+
+static void tegra_pcie_setup_translations(struct tegra_pcie *pcie)
+{
+       unsigned long fpci, axi, size;
+
+       /* BAR 0: type 1 extended configuration space */
+       fpci = 0xfe100000;
+       size = fdt_resource_size(&pcie->cs);
+       axi = pcie->cs.start;
+
+       afi_writel(pcie, axi, AFI_AXI_BAR0_START);
+       afi_writel(pcie, size >> 12, AFI_AXI_BAR0_SZ);
+       afi_writel(pcie, fpci, AFI_FPCI_BAR0);
+
+       /* BAR 1: downstream I/O */
+       fpci = 0xfdfc0000;
+       size = fdt_resource_size(&pcie->io);
+       axi = pcie->io.start;
+
+       afi_writel(pcie, axi, AFI_AXI_BAR1_START);
+       afi_writel(pcie, size >> 12, AFI_AXI_BAR1_SZ);
+       afi_writel(pcie, fpci, AFI_FPCI_BAR1);
+
+       /* BAR 2: prefetchable memory */
+       fpci = (((pcie->prefetch.start >> 12) & 0x0fffffff) << 4) | 0x1;
+       size = fdt_resource_size(&pcie->prefetch);
+       axi = pcie->prefetch.start;
+
+       afi_writel(pcie, axi, AFI_AXI_BAR2_START);
+       afi_writel(pcie, size >> 12, AFI_AXI_BAR2_SZ);
+       afi_writel(pcie, fpci, AFI_FPCI_BAR2);
+
+       /* BAR 3: non-prefetchable memory */
+       fpci = (((pcie->mem.start >> 12) & 0x0fffffff) << 4) | 0x1;
+       size = fdt_resource_size(&pcie->mem);
+       axi = pcie->mem.start;
+
+       afi_writel(pcie, axi, AFI_AXI_BAR3_START);
+       afi_writel(pcie, size >> 12, AFI_AXI_BAR3_SZ);
+       afi_writel(pcie, fpci, AFI_FPCI_BAR3);
+
+       /* NULL out the remaining BARs as they are not used */
+       afi_writel(pcie, 0, AFI_AXI_BAR4_START);
+       afi_writel(pcie, 0, AFI_AXI_BAR4_SZ);
+       afi_writel(pcie, 0, AFI_FPCI_BAR4);
+
+       afi_writel(pcie, 0, AFI_AXI_BAR5_START);
+       afi_writel(pcie, 0, AFI_AXI_BAR5_SZ);
+       afi_writel(pcie, 0, AFI_FPCI_BAR5);
+
+       /* map all upstream transactions as uncached */
+       afi_writel(pcie, NV_PA_SDRAM_BASE, AFI_CACHE_BAR0_ST);
+       afi_writel(pcie, 0, AFI_CACHE_BAR0_SZ);
+       afi_writel(pcie, 0, AFI_CACHE_BAR1_ST);
+       afi_writel(pcie, 0, AFI_CACHE_BAR1_SZ);
+
+       /* MSI translations are setup only when needed */
+       afi_writel(pcie, 0, AFI_MSI_FPCI_BAR_ST);
+       afi_writel(pcie, 0, AFI_MSI_BAR_SZ);
+       afi_writel(pcie, 0, AFI_MSI_AXI_BAR_ST);
+       afi_writel(pcie, 0, AFI_MSI_BAR_SZ);
+}
+
+static unsigned long tegra_pcie_port_get_pex_ctrl(struct tegra_pcie_port *port)
+{
+       unsigned long ret = 0;
+
+       switch (port->index) {
+       case 0:
+               ret = AFI_PEX0_CTRL;
+               break;
+
+       case 1:
+               ret = AFI_PEX1_CTRL;
+               break;
+
+       case 2:
+               ret = AFI_PEX2_CTRL;
+               break;
+       }
+
+       return ret;
+}
+
+static void tegra_pcie_port_reset(struct tegra_pcie_port *port)
+{
+       unsigned long ctrl = tegra_pcie_port_get_pex_ctrl(port);
+       unsigned long value;
+
+       /* pulse reset signel */
+       value = afi_readl(port->pcie, ctrl);
+       value &= ~AFI_PEX_CTRL_RST;
+       afi_writel(port->pcie, value, ctrl);
+
+       udelay(2000);
+
+       value = afi_readl(port->pcie, ctrl);
+       value |= AFI_PEX_CTRL_RST;
+       afi_writel(port->pcie, value, ctrl);
+}
+
+static void tegra_pcie_port_enable(struct tegra_pcie_port *port)
+{
+       unsigned long ctrl = tegra_pcie_port_get_pex_ctrl(port);
+       unsigned long value;
+
+       /* enable reference clock */
+       value = afi_readl(port->pcie, ctrl);
+       value |= AFI_PEX_CTRL_REFCLK_EN;
+
+       if (port->pcie->soc->has_pex_clkreq_en)
+               value |= AFI_PEX_CTRL_CLKREQ_EN;
+
+       value |= AFI_PEX_CTRL_OVERRIDE_EN;
+
+       afi_writel(port->pcie, value, ctrl);
+
+       tegra_pcie_port_reset(port);
+}
+
+static bool tegra_pcie_port_check_link(struct tegra_pcie_port *port)
+{
+       unsigned int retries = 3;
+       unsigned long value;
+
+       value = rp_readl(port, RP_PRIV_MISC);
+       value &= ~RP_PRIV_MISC_PRSNT_MAP_EP_ABSNT;
+       value |= RP_PRIV_MISC_PRSNT_MAP_EP_PRSNT;
+       rp_writel(port, value, RP_PRIV_MISC);
+
+       do {
+               unsigned int timeout = 200;
+
+               do {
+                       value = rp_readl(port, RP_VEND_XP);
+                       if (value & RP_VEND_XP_DL_UP)
+                               break;
+
+                       udelay(2000);
+               } while (--timeout);
+
+               if (!timeout) {
+                       debug("link %u down, retrying\n", port->index);
+                       goto retry;
+               }
+
+               timeout = 200;
+
+               do {
+                       value = rp_readl(port, RP_LINK_CONTROL_STATUS);
+                       if (value & RP_LINK_CONTROL_STATUS_DL_LINK_ACTIVE)
+                               return true;
+
+                       udelay(2000);
+               } while (--timeout);
+
+retry:
+               tegra_pcie_port_reset(port);
+       } while (--retries);
+
+       return false;
+}
+
+static void tegra_pcie_port_disable(struct tegra_pcie_port *port)
+{
+       unsigned long ctrl = tegra_pcie_port_get_pex_ctrl(port);
+       unsigned long value;
+
+       /* assert port reset */
+       value = afi_readl(port->pcie, ctrl);
+       value &= ~AFI_PEX_CTRL_RST;
+       afi_writel(port->pcie, value, ctrl);
+
+       /* disable reference clock */
+       value = afi_readl(port->pcie, ctrl);
+       value &= ~AFI_PEX_CTRL_REFCLK_EN;
+       afi_writel(port->pcie, value, ctrl);
+}
+
+static void tegra_pcie_port_free(struct tegra_pcie_port *port)
+{
+       list_del(&port->list);
+       free(port);
+}
+
+static int tegra_pcie_enable(struct tegra_pcie *pcie)
+{
+       struct tegra_pcie_port *port, *tmp;
+
+       list_for_each_entry_safe(port, tmp, &pcie->ports, list) {
+               debug("probing port %u, using %u lanes\n", port->index,
+                     port->num_lanes);
+
+               tegra_pcie_port_enable(port);
+
+               if (tegra_pcie_port_check_link(port))
+                       continue;
+
+               debug("link %u down, ignoring\n", port->index);
+
+               tegra_pcie_port_disable(port);
+               tegra_pcie_port_free(port);
+       }
+
+       return 0;
+}
+
+static const struct tegra_pcie_soc tegra20_pcie_soc = {
+       .num_ports = 2,
+       .pads_pll_ctl = PADS_PLL_CTL_TEGRA20,
+       .tx_ref_sel = PADS_PLL_CTL_TXCLKREF_DIV10,
+       .has_pex_clkreq_en = false,
+       .has_pex_bias_ctrl = false,
+       .has_cml_clk = false,
+       .has_gen2 = false,
+};
+
+static const struct tegra_pcie_soc tegra30_pcie_soc = {
+       .num_ports = 3,
+       .pads_pll_ctl = PADS_PLL_CTL_TEGRA30,
+       .tx_ref_sel = PADS_PLL_CTL_TXCLKREF_BUF_EN,
+       .has_pex_clkreq_en = true,
+       .has_pex_bias_ctrl = true,
+       .has_cml_clk = true,
+       .has_gen2 = false,
+};
+
+static const struct tegra_pcie_soc tegra124_pcie_soc = {
+       .num_ports = 2,
+       .pads_pll_ctl = PADS_PLL_CTL_TEGRA30,
+       .tx_ref_sel = PADS_PLL_CTL_TXCLKREF_BUF_EN,
+       .has_pex_clkreq_en = true,
+       .has_pex_bias_ctrl = true,
+       .has_cml_clk = true,
+       .has_gen2 = true,
+};
+
+static int process_nodes(const void *fdt, int nodes[], unsigned int count)
+{
+       unsigned int i;
+
+       for (i = 0; i < count; i++) {
+               const struct tegra_pcie_soc *soc;
+               struct tegra_pcie *pcie;
+               enum fdt_compat_id id;
+               int err;
+
+               if (!fdtdec_get_is_enabled(fdt, nodes[i]))
+                       continue;
+
+               id = fdtdec_lookup(fdt, nodes[i]);
+               switch (id) {
+               case COMPAT_NVIDIA_TEGRA20_PCIE:
+                       soc = &tegra20_pcie_soc;
+                       break;
+
+               case COMPAT_NVIDIA_TEGRA30_PCIE:
+                       soc = &tegra30_pcie_soc;
+                       break;
+
+               case COMPAT_NVIDIA_TEGRA124_PCIE:
+                       soc = &tegra124_pcie_soc;
+                       break;
+
+               default:
+                       error("unsupported compatible: %s",
+                             fdtdec_get_compatible(id));
+                       continue;
+               }
+
+               pcie = malloc(sizeof(*pcie));
+               if (!pcie) {
+                       error("failed to allocate controller");
+                       continue;
+               }
+
+               memset(pcie, 0, sizeof(*pcie));
+               pcie->soc = soc;
+
+               INIT_LIST_HEAD(&pcie->ports);
+
+               err = tegra_pcie_parse_dt(fdt, nodes[i], pcie);
+               if (err < 0) {
+                       free(pcie);
+                       continue;
+               }
+
+               err = tegra_pcie_power_on(pcie);
+               if (err < 0) {
+                       error("failed to power on");
+                       continue;
+               }
+
+               err = tegra_pcie_enable_controller(pcie);
+               if (err < 0) {
+                       error("failed to enable controller");
+                       continue;
+               }
+
+               tegra_pcie_setup_translations(pcie);
+
+               err = tegra_pcie_enable(pcie);
+               if (err < 0) {
+                       error("failed to enable PCIe");
+                       continue;
+               }
+
+               pcie->hose.first_busno = 0;
+               pcie->hose.current_busno = 0;
+               pcie->hose.last_busno = 0;
+
+               pci_set_region(&pcie->hose.regions[0], NV_PA_SDRAM_BASE,
+                              NV_PA_SDRAM_BASE, gd->ram_size,
+                              PCI_REGION_MEM | PCI_REGION_SYS_MEMORY);
+
+               pci_set_region(&pcie->hose.regions[1], pcie->io.start,
+                              pcie->io.start, fdt_resource_size(&pcie->io),
+                              PCI_REGION_IO);
+
+               pci_set_region(&pcie->hose.regions[2], pcie->mem.start,
+                              pcie->mem.start, fdt_resource_size(&pcie->mem),
+                              PCI_REGION_MEM);
+
+               pci_set_region(&pcie->hose.regions[3], pcie->prefetch.start,
+                              pcie->prefetch.start,
+                              fdt_resource_size(&pcie->prefetch),
+                              PCI_REGION_MEM | PCI_REGION_PREFETCH);
+
+               pcie->hose.region_count = 4;
+
+               pci_set_ops(&pcie->hose,
+                           pci_hose_read_config_byte_via_dword,
+                           pci_hose_read_config_word_via_dword,
+                           tegra_pcie_read_conf,
+                           pci_hose_write_config_byte_via_dword,
+                           pci_hose_write_config_word_via_dword,
+                           tegra_pcie_write_conf);
+
+               pci_register_hose(&pcie->hose);
+
+#ifdef CONFIG_PCI_SCAN_SHOW
+               printf("PCI: Enumerating devices...\n");
+               printf("---------------------------------------\n");
+               printf("  Device        ID          Description\n");
+               printf("  ------        --          -----------\n");
+#endif
+
+               pcie->hose.last_busno = pci_hose_scan(&pcie->hose);
+       }
+
+       return 0;
+}
+
+void pci_init_board(void)
+{
+       const void *fdt = gd->fdt_blob;
+       int count, nodes[1];
+
+       count = fdtdec_find_aliases_for_id(fdt, "pcie-controller",
+                                          COMPAT_NVIDIA_TEGRA124_PCIE,
+                                          nodes, ARRAY_SIZE(nodes));
+       if (process_nodes(fdt, nodes, count))
+               return;
+
+       count = fdtdec_find_aliases_for_id(fdt, "pcie-controller",
+                                          COMPAT_NVIDIA_TEGRA30_PCIE,
+                                          nodes, ARRAY_SIZE(nodes));
+       if (process_nodes(fdt, nodes, count))
+               return;
+
+       count = fdtdec_find_aliases_for_id(fdt, "pcie-controller",
+                                          COMPAT_NVIDIA_TEGRA20_PCIE,
+                                          nodes, ARRAY_SIZE(nodes));
+       if (process_nodes(fdt, nodes, count))
+               return;
+}
+
+int pci_skip_dev(struct pci_controller *hose, pci_dev_t dev)
+{
+       if (PCI_BUS(dev) != 0 && PCI_DEV(dev) > 0)
+               return 1;
+
+       return 0;
+}
index 71aa82c725a9b35830a3a32d0117b6be1e90849f..5effa240afef3e8a84a2c166b51ee3427bc58c4e 100644 (file)
@@ -86,6 +86,9 @@ enum fdt_compat_id {
        COMPAT_NVIDIA_TEGRA20_SFLASH,   /* Tegra 2 SPI flash controller */
        COMPAT_NVIDIA_TEGRA20_SLINK,    /* Tegra 2 SPI SLINK controller */
        COMPAT_NVIDIA_TEGRA114_SPI,     /* Tegra 114 SPI controller */
+       COMPAT_NVIDIA_TEGRA124_PCIE,    /* Tegra 124 PCIe controller */
+       COMPAT_NVIDIA_TEGRA30_PCIE,     /* Tegra 30 PCIe controller */
+       COMPAT_NVIDIA_TEGRA20_PCIE,     /* Tegra 20 PCIe controller */
        COMPAT_NVIDIA_TEGRA124_XUSB_PADCTL,
                                        /* Tegra124 XUSB pad controller */
        COMPAT_SMSC_LAN9215,            /* SMSC 10/100 Ethernet LAN9215 */
index 6f88db98ee1603e749ff24a3119dfe9ec87d126e..745b39083672f47497d68f4fd95a63c33b385419 100644 (file)
@@ -41,6 +41,9 @@ static const char * const compat_names[COMPAT_COUNT] = {
        COMPAT(NVIDIA_TEGRA20_SFLASH, "nvidia,tegra20-sflash"),
        COMPAT(NVIDIA_TEGRA20_SLINK, "nvidia,tegra20-slink"),
        COMPAT(NVIDIA_TEGRA114_SPI, "nvidia,tegra114-spi"),
+       COMPAT(NVIDIA_TEGRA124_PCIE, "nvidia,tegra124-pcie"),
+       COMPAT(NVIDIA_TEGRA30_PCIE, "nvidia,tegra30-pcie"),
+       COMPAT(NVIDIA_TEGRA20_PCIE, "nvidia,tegra20-pcie"),
        COMPAT(NVIDIA_TEGRA124_XUSB_PADCTL, "nvidia,tegra124-xusb-padctl"),
        COMPAT(SMSC_LAN9215, "smsc,lan9215"),
        COMPAT(SAMSUNG_EXYNOS5_SROMC, "samsung,exynos-sromc"),