PCI: iproc: Add PAXC interface support
authorRay Jui <rjui@broadcom.com>
Fri, 4 Dec 2015 17:34:59 +0000 (09:34 -0800)
committerBjorn Helgaas <bhelgaas@google.com>
Mon, 7 Dec 2015 16:35:29 +0000 (10:35 -0600)
Traditionally, all iProc PCIe root complexes use PAXB-based wrapper, with
an integrated on-chip Serdes to support external endpoint devices.  On
newer iProc platforms, a PAXC-based wrapper is introduced, for connection
with internally emulated PCIe endpoint devices in the ASIC.

Add support for PAXC-based iProc PCIe root complex in the iProc PCIe core
driver.  This change factors out common logic between PAXB and PAXC, and
uses tables to store register offsets that are different between PAXB and
PAXC.  This allows the driver to be scaled to support subsequent PAXC
revisions in the future.

Signed-off-by: Ray Jui <rjui@broadcom.com>
Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
Reviewed-by: Scott Branden <sbranden@broadcom.com>
drivers/pci/host/pcie-iproc-platform.c
drivers/pci/host/pcie-iproc.c
drivers/pci/host/pcie-iproc.h

index c9550dc8b8ed4b8fc330d1d21cefed43989db030..e8b32d856bd8a43c9a0769b1b9862ee94b565fd6 100644 (file)
 
 #include "pcie-iproc.h"
 
+static const struct of_device_id iproc_pcie_of_match_table[] = {
+       {
+               .compatible = "brcm,iproc-pcie",
+               .data = (int *)IPROC_PCIE_PAXB,
+       }, {
+               .compatible = "brcm,iproc-pcie-paxc",
+               .data = (int *)IPROC_PCIE_PAXC,
+       },
+       { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, iproc_pcie_of_match_table);
+
 static int iproc_pcie_pltfm_probe(struct platform_device *pdev)
 {
+       const struct of_device_id *of_id;
        struct iproc_pcie *pcie;
        struct device_node *np = pdev->dev.of_node;
        struct resource reg;
@@ -35,11 +48,16 @@ static int iproc_pcie_pltfm_probe(struct platform_device *pdev)
        LIST_HEAD(res);
        int ret;
 
+       of_id = of_match_device(iproc_pcie_of_match_table, &pdev->dev);
+       if (!of_id)
+               return -EINVAL;
+
        pcie = devm_kzalloc(&pdev->dev, sizeof(struct iproc_pcie), GFP_KERNEL);
        if (!pcie)
                return -ENOMEM;
 
        pcie->dev = &pdev->dev;
+       pcie->type = (enum iproc_pcie_type)of_id->data;
        platform_set_drvdata(pdev, pcie);
 
        ret = of_address_to_resource(np, 0, &reg);
@@ -114,12 +132,6 @@ static int iproc_pcie_pltfm_remove(struct platform_device *pdev)
        return iproc_pcie_remove(pcie);
 }
 
-static const struct of_device_id iproc_pcie_of_match_table[] = {
-       { .compatible = "brcm,iproc-pcie", },
-       { /* sentinel */ }
-};
-MODULE_DEVICE_TABLE(of, iproc_pcie_of_match_table);
-
 static struct platform_driver iproc_pcie_pltfm_driver = {
        .driver = {
                .name = "iproc-pcie",
index d1ad883eac04066c80e717e6023aa1f59951e3fa..0e11f85ba7b0b46d09f73dfff28113f440732944 100644 (file)
 
 #include "pcie-iproc.h"
 
-#define CLK_CONTROL_OFFSET           0x000
 #define EP_PERST_SOURCE_SELECT_SHIFT 2
 #define EP_PERST_SOURCE_SELECT       BIT(EP_PERST_SOURCE_SELECT_SHIFT)
 #define EP_MODE_SURVIVE_PERST_SHIFT  1
 #define EP_MODE_SURVIVE_PERST        BIT(EP_MODE_SURVIVE_PERST_SHIFT)
 #define RC_PCIE_RST_OUTPUT_SHIFT     0
 #define RC_PCIE_RST_OUTPUT           BIT(RC_PCIE_RST_OUTPUT_SHIFT)
+#define PAXC_RESET_MASK              0x7f
 
-#define CFG_IND_ADDR_OFFSET          0x120
 #define CFG_IND_ADDR_MASK            0x00001ffc
 
-#define CFG_IND_DATA_OFFSET          0x124
-
-#define CFG_ADDR_OFFSET              0x1f8
 #define CFG_ADDR_BUS_NUM_SHIFT       20
 #define CFG_ADDR_BUS_NUM_MASK        0x0ff00000
 #define CFG_ADDR_DEV_NUM_SHIFT       15
 #define CFG_ADDR_CFG_TYPE_SHIFT      0
 #define CFG_ADDR_CFG_TYPE_MASK       0x00000003
 
-#define CFG_DATA_OFFSET              0x1fc
-
-#define SYS_RC_INTX_EN               0x330
 #define SYS_RC_INTX_MASK             0xf
 
-#define PCIE_LINK_STATUS_OFFSET      0xf0c
 #define PCIE_PHYLINKUP_SHIFT         3
 #define PCIE_PHYLINKUP               BIT(PCIE_PHYLINKUP_SHIFT)
 #define PCIE_DL_ACTIVE_SHIFT         2
 #define OARR_SIZE_CFG_SHIFT          1
 #define OARR_SIZE_CFG                BIT(OARR_SIZE_CFG_SHIFT)
 
-#define OARR_LO(window)              (0xd20 + (window) * 8)
-#define OARR_HI(window)              (0xd24 + (window) * 8)
-#define OMAP_LO(window)              (0xd40 + (window) * 8)
-#define OMAP_HI(window)              (0xd44 + (window) * 8)
-
 #define MAX_NUM_OB_WINDOWS           2
+#define MAX_NUM_PAXC_PF              4
+
+#define IPROC_PCIE_REG_INVALID 0xffff
+
+enum iproc_pcie_reg {
+       IPROC_PCIE_CLK_CTRL = 0,
+       IPROC_PCIE_CFG_IND_ADDR,
+       IPROC_PCIE_CFG_IND_DATA,
+       IPROC_PCIE_CFG_ADDR,
+       IPROC_PCIE_CFG_DATA,
+       IPROC_PCIE_INTX_EN,
+       IPROC_PCIE_OARR_LO,
+       IPROC_PCIE_OARR_HI,
+       IPROC_PCIE_OMAP_LO,
+       IPROC_PCIE_OMAP_HI,
+       IPROC_PCIE_LINK_STATUS,
+};
+
+/* iProc PCIe PAXB registers */
+static const u16 iproc_pcie_reg_paxb[] = {
+       [IPROC_PCIE_CLK_CTRL]     = 0x000,
+       [IPROC_PCIE_CFG_IND_ADDR] = 0x120,
+       [IPROC_PCIE_CFG_IND_DATA] = 0x124,
+       [IPROC_PCIE_CFG_ADDR]     = 0x1f8,
+       [IPROC_PCIE_CFG_DATA]     = 0x1fc,
+       [IPROC_PCIE_INTX_EN]      = 0x330,
+       [IPROC_PCIE_OARR_LO]      = 0xd20,
+       [IPROC_PCIE_OARR_HI]      = 0xd24,
+       [IPROC_PCIE_OMAP_LO]      = 0xd40,
+       [IPROC_PCIE_OMAP_HI]      = 0xd44,
+       [IPROC_PCIE_LINK_STATUS]  = 0xf0c,
+};
+
+/* iProc PCIe PAXC v1 registers */
+static const u16 iproc_pcie_reg_paxc[] = {
+       [IPROC_PCIE_CLK_CTRL]     = 0x000,
+       [IPROC_PCIE_CFG_IND_ADDR] = 0x1f0,
+       [IPROC_PCIE_CFG_IND_DATA] = 0x1f4,
+       [IPROC_PCIE_CFG_ADDR]     = 0x1f8,
+       [IPROC_PCIE_CFG_DATA]     = 0x1fc,
+       [IPROC_PCIE_INTX_EN]      = IPROC_PCIE_REG_INVALID,
+       [IPROC_PCIE_OARR_LO]      = IPROC_PCIE_REG_INVALID,
+       [IPROC_PCIE_OARR_HI]      = IPROC_PCIE_REG_INVALID,
+       [IPROC_PCIE_OMAP_LO]      = IPROC_PCIE_REG_INVALID,
+       [IPROC_PCIE_OMAP_HI]      = IPROC_PCIE_REG_INVALID,
+       [IPROC_PCIE_LINK_STATUS]  = IPROC_PCIE_REG_INVALID,
+};
 
 static inline struct iproc_pcie *iproc_data(struct pci_bus *bus)
 {
@@ -91,6 +125,65 @@ static inline struct iproc_pcie *iproc_data(struct pci_bus *bus)
        return pcie;
 }
 
+static inline bool iproc_pcie_reg_is_invalid(u16 reg_offset)
+{
+       return !!(reg_offset == IPROC_PCIE_REG_INVALID);
+}
+
+static inline u16 iproc_pcie_reg_offset(struct iproc_pcie *pcie,
+                                       enum iproc_pcie_reg reg)
+{
+       return pcie->reg_offsets[reg];
+}
+
+static inline u32 iproc_pcie_read_reg(struct iproc_pcie *pcie,
+                                     enum iproc_pcie_reg reg)
+{
+       u16 offset = iproc_pcie_reg_offset(pcie, reg);
+
+       if (iproc_pcie_reg_is_invalid(offset))
+               return 0;
+
+       return readl(pcie->base + offset);
+}
+
+static inline void iproc_pcie_write_reg(struct iproc_pcie *pcie,
+                                       enum iproc_pcie_reg reg, u32 val)
+{
+       u16 offset = iproc_pcie_reg_offset(pcie, reg);
+
+       if (iproc_pcie_reg_is_invalid(offset))
+               return;
+
+       writel(val, pcie->base + offset);
+}
+
+static inline void iproc_pcie_ob_write(struct iproc_pcie *pcie,
+                                      enum iproc_pcie_reg reg,
+                                      unsigned window, u32 val)
+{
+       u16 offset = iproc_pcie_reg_offset(pcie, reg);
+
+       if (iproc_pcie_reg_is_invalid(offset))
+               return;
+
+       writel(val, pcie->base + offset + (window * 8));
+}
+
+static inline bool iproc_pcie_device_is_valid(struct iproc_pcie *pcie,
+                                             unsigned int slot,
+                                             unsigned int fn)
+{
+       if (slot > 0)
+               return false;
+
+       /* PAXC can only support limited number of functions */
+       if (pcie->type == IPROC_PCIE_PAXC && fn >= MAX_NUM_PAXC_PF)
+               return false;
+
+       return true;
+}
+
 /**
  * Note access to the configuration registers are protected at the higher layer
  * by 'pci_lock' in drivers/pci/access.c
@@ -104,28 +197,34 @@ static void __iomem *iproc_pcie_map_cfg_bus(struct pci_bus *bus,
        unsigned fn = PCI_FUNC(devfn);
        unsigned busno = bus->number;
        u32 val;
+       u16 offset;
+
+       if (!iproc_pcie_device_is_valid(pcie, slot, fn))
+               return NULL;
 
        /* root complex access */
        if (busno == 0) {
-               if (slot >= 1)
+               iproc_pcie_write_reg(pcie, IPROC_PCIE_CFG_IND_ADDR,
+                                    where & CFG_IND_ADDR_MASK);
+               offset = iproc_pcie_reg_offset(pcie, IPROC_PCIE_CFG_IND_DATA);
+               if (iproc_pcie_reg_is_invalid(offset))
                        return NULL;
-               writel(where & CFG_IND_ADDR_MASK,
-                      pcie->base + CFG_IND_ADDR_OFFSET);
-               return (pcie->base + CFG_IND_DATA_OFFSET);
+               else
+                       return (pcie->base + offset);
        }
 
-       if (fn > 1)
-               return NULL;
-
        /* EP device access */
        val = (busno << CFG_ADDR_BUS_NUM_SHIFT) |
                (slot << CFG_ADDR_DEV_NUM_SHIFT) |
                (fn << CFG_ADDR_FUNC_NUM_SHIFT) |
                (where & CFG_ADDR_REG_NUM_MASK) |
                (1 & CFG_ADDR_CFG_TYPE_MASK);
-       writel(val, pcie->base + CFG_ADDR_OFFSET);
-
-       return (pcie->base + CFG_DATA_OFFSET);
+       iproc_pcie_write_reg(pcie, IPROC_PCIE_CFG_ADDR, val);
+       offset = iproc_pcie_reg_offset(pcie, IPROC_PCIE_CFG_DATA);
+       if (iproc_pcie_reg_is_invalid(offset))
+               return NULL;
+       else
+               return (pcie->base + offset);
 }
 
 static struct pci_ops iproc_pcie_ops = {
@@ -138,18 +237,29 @@ static void iproc_pcie_reset(struct iproc_pcie *pcie)
 {
        u32 val;
 
+       if (pcie->type == IPROC_PCIE_PAXC) {
+               val = iproc_pcie_read_reg(pcie, IPROC_PCIE_CLK_CTRL);
+               val &= ~PAXC_RESET_MASK;
+               iproc_pcie_write_reg(pcie, IPROC_PCIE_CLK_CTRL, val);
+               udelay(100);
+               val |= PAXC_RESET_MASK;
+               iproc_pcie_write_reg(pcie, IPROC_PCIE_CLK_CTRL, val);
+               udelay(100);
+               return;
+       }
+
        /*
         * Select perst_b signal as reset source. Put the device into reset,
         * and then bring it out of reset
         */
-       val = readl(pcie->base + CLK_CONTROL_OFFSET);
+       val = iproc_pcie_read_reg(pcie, IPROC_PCIE_CLK_CTRL);
        val &= ~EP_PERST_SOURCE_SELECT & ~EP_MODE_SURVIVE_PERST &
                ~RC_PCIE_RST_OUTPUT;
-       writel(val, pcie->base + CLK_CONTROL_OFFSET);
+       iproc_pcie_write_reg(pcie, IPROC_PCIE_CLK_CTRL, val);
        udelay(250);
 
        val |= RC_PCIE_RST_OUTPUT;
-       writel(val, pcie->base + CLK_CONTROL_OFFSET);
+       iproc_pcie_write_reg(pcie, IPROC_PCIE_CLK_CTRL, val);
        msleep(100);
 }
 
@@ -160,7 +270,14 @@ static int iproc_pcie_check_link(struct iproc_pcie *pcie, struct pci_bus *bus)
        u16 pos, link_status;
        bool link_is_active = false;
 
-       val = readl(pcie->base + PCIE_LINK_STATUS_OFFSET);
+       /*
+        * PAXC connects to emulated endpoint devices directly and does not
+        * have a Serdes.  Therefore skip the link detection logic here.
+        */
+       if (pcie->type == IPROC_PCIE_PAXC)
+               return 0;
+
+       val = iproc_pcie_read_reg(pcie, IPROC_PCIE_LINK_STATUS);
        if (!(val & PCIE_PHYLINKUP) || !(val & PCIE_DL_ACTIVE)) {
                dev_err(pcie->dev, "PHY or data link is INACTIVE!\n");
                return -ENODEV;
@@ -221,7 +338,7 @@ static int iproc_pcie_check_link(struct iproc_pcie *pcie, struct pci_bus *bus)
 
 static void iproc_pcie_enable(struct iproc_pcie *pcie)
 {
-       writel(SYS_RC_INTX_MASK, pcie->base + SYS_RC_INTX_EN);
+       iproc_pcie_write_reg(pcie, IPROC_PCIE_INTX_EN, SYS_RC_INTX_MASK);
 }
 
 /**
@@ -272,11 +389,15 @@ static int iproc_pcie_setup_ob(struct iproc_pcie *pcie, u64 axi_addr,
        axi_addr -= ob->axi_offset;
 
        for (i = 0; i < MAX_NUM_OB_WINDOWS; i++) {
-               writel(lower_32_bits(axi_addr) | OARR_VALID |
-                      (ob->set_oarr_size ? 1 : 0), pcie->base + OARR_LO(i));
-               writel(upper_32_bits(axi_addr), pcie->base + OARR_HI(i));
-               writel(lower_32_bits(pci_addr), pcie->base + OMAP_LO(i));
-               writel(upper_32_bits(pci_addr), pcie->base + OMAP_HI(i));
+               iproc_pcie_ob_write(pcie, IPROC_PCIE_OARR_LO, i,
+                                   lower_32_bits(axi_addr) | OARR_VALID |
+                                   (ob->set_oarr_size ? 1 : 0));
+               iproc_pcie_ob_write(pcie, IPROC_PCIE_OARR_HI, i,
+                                   upper_32_bits(axi_addr));
+               iproc_pcie_ob_write(pcie, IPROC_PCIE_OMAP_LO, i,
+                                   lower_32_bits(pci_addr));
+               iproc_pcie_ob_write(pcie, IPROC_PCIE_OMAP_HI, i,
+                                   upper_32_bits(pci_addr));
 
                size -= ob->window_size;
                if (size == 0)
@@ -340,6 +461,19 @@ int iproc_pcie_setup(struct iproc_pcie *pcie, struct list_head *res)
                goto err_exit_phy;
        }
 
+       switch (pcie->type) {
+       case IPROC_PCIE_PAXB:
+               pcie->reg_offsets = iproc_pcie_reg_paxb;
+               break;
+       case IPROC_PCIE_PAXC:
+               pcie->reg_offsets = iproc_pcie_reg_paxc;
+               break;
+       default:
+               dev_err(pcie->dev, "incompatible iProc PCIe interface\n");
+               ret = -EINVAL;
+               goto err_power_off_phy;
+       }
+
        iproc_pcie_reset(pcie);
 
        if (pcie->need_ob_cfg) {
index d3dc940f773a2279d13ad295bc9bd70a86638d23..a38e96ae156db239c4ebb9b0c56dc8cdf558745b 100644 (file)
 #ifndef _PCIE_IPROC_H
 #define _PCIE_IPROC_H
 
+/**
+ * iProc PCIe interface type
+ *
+ * PAXB is the wrapper used in root complex that can be connected to an
+ * external endpoint device.
+ *
+ * PAXC is the wrapper used in root complex dedicated for internal emulated
+ * endpoint devices.
+ */
+enum iproc_pcie_type {
+       IPROC_PCIE_PAXB = 0,
+       IPROC_PCIE_PAXC,
+};
+
 /**
  * iProc PCIe outbound mapping
  * @set_oarr_size: indicates the OARR size bit needs to be set
@@ -29,7 +43,10 @@ struct iproc_pcie_ob {
 
 /**
  * iProc PCIe device
+ *
  * @dev: pointer to device data structure
+ * @type: iProc PCIe interface type
+ * @reg_offsets: register offsets
  * @base: PCIe host controller I/O register base
  * @sysdata: Per PCI controller data (ARM-specific)
  * @root_bus: pointer to root bus
@@ -41,6 +58,8 @@ struct iproc_pcie_ob {
  */
 struct iproc_pcie {
        struct device *dev;
+       enum iproc_pcie_type type;
+       const u16 *reg_offsets;
        void __iomem *base;
 #ifdef CONFIG_ARM
        struct pci_sys_data sysdata;