summaryrefslogtreecommitdiff
path: root/drivers/pci/host/pcie-iproc.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/pci/host/pcie-iproc.c')
-rw-r--r--drivers/pci/host/pcie-iproc.c221
1 files changed, 187 insertions, 34 deletions
diff --git a/drivers/pci/host/pcie-iproc.c b/drivers/pci/host/pcie-iproc.c
index eac719af1..a576aeeb2 100644
--- a/drivers/pci/host/pcie-iproc.c
+++ b/drivers/pci/host/pcie-iproc.c
@@ -30,20 +30,16 @@
#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
@@ -55,12 +51,8 @@
#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
@@ -71,13 +63,54 @@
#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 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)
{
struct iproc_pcie *pcie;
@@ -91,6 +124,51 @@ 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));
+}
+
/**
* Note access to the configuration registers are protected at the higher layer
* by 'pci_lock' in drivers/pci/access.c
@@ -104,18 +182,29 @@ 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;
/* root complex access */
if (busno == 0) {
- if (slot >= 1)
+ if (slot > 0 || fn > 0)
return NULL;
- writel(where & CFG_IND_ADDR_MASK,
- pcie->base + CFG_IND_ADDR_OFFSET);
- return (pcie->base + CFG_IND_DATA_OFFSET);
+
+ 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;
+ else
+ return (pcie->base + offset);
}
- if (fn > 1)
- return NULL;
+ /*
+ * PAXC is connected to an internally emulated EP within the SoC. It
+ * allows only one device.
+ */
+ if (pcie->type == IPROC_PCIE_PAXC)
+ if (slot > 0)
+ return NULL;
/* EP device access */
val = (busno << CFG_ADDR_BUS_NUM_SHIFT) |
@@ -123,9 +212,12 @@ static void __iomem *iproc_pcie_map_cfg_bus(struct pci_bus *bus,
(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 +230,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 +263,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 +331,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);
}
/**
@@ -245,7 +355,7 @@ static int iproc_pcie_setup_ob(struct iproc_pcie *pcie, u64 axi_addr,
if (size > max_size) {
dev_err(pcie->dev,
- "res size 0x%pap exceeds max supported size 0x%llx\n",
+ "res size %pap exceeds max supported size 0x%llx\n",
&size, max_size);
return -EINVAL;
}
@@ -272,11 +382,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)
@@ -319,6 +433,26 @@ static int iproc_pcie_map_ranges(struct iproc_pcie *pcie,
return 0;
}
+static int iproc_pcie_msi_enable(struct iproc_pcie *pcie)
+{
+ struct device_node *msi_node;
+
+ msi_node = of_parse_phandle(pcie->dev->of_node, "msi-parent", 0);
+ if (!msi_node)
+ return -ENODEV;
+
+ /*
+ * If another MSI controller is being used, the call below should fail
+ * but that is okay
+ */
+ return iproc_msi_init(pcie, msi_node);
+}
+
+static void iproc_pcie_msi_disable(struct iproc_pcie *pcie)
+{
+ iproc_msi_exit(pcie);
+}
+
int iproc_pcie_setup(struct iproc_pcie *pcie, struct list_head *res)
{
int ret;
@@ -340,6 +474,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) {
@@ -373,6 +520,10 @@ int iproc_pcie_setup(struct iproc_pcie *pcie, struct list_head *res)
iproc_pcie_enable(pcie);
+ if (IS_ENABLED(CONFIG_PCI_MSI))
+ if (iproc_pcie_msi_enable(pcie))
+ dev_info(pcie->dev, "not using iProc MSI\n");
+
pci_scan_child_bus(bus);
pci_assign_unassigned_bus_resources(bus);
pci_fixup_irqs(pci_common_swizzle, pcie->map_irq);
@@ -397,6 +548,8 @@ int iproc_pcie_remove(struct iproc_pcie *pcie)
pci_stop_root_bus(pcie->root_bus);
pci_remove_root_bus(pcie->root_bus);
+ iproc_pcie_msi_disable(pcie);
+
phy_power_off(pcie->phy);
phy_exit(pcie->phy);