diff options
author | Ray Jui <rjui@broadcom.com> | 2015-10-16 15:18:24 +0200 |
---|---|---|
committer | Bjorn Helgaas <bhelgaas@google.com> | 2015-10-16 15:18:24 +0200 |
commit | e99a187b5c5f60fe55ca586f82ac1a3557fb166a (patch) | |
tree | 2da61f1c9e854b6f1eebadb3a74dc1a1429ce333 /drivers/pci/host/pcie-iproc.c | |
parent | PCI: iproc: Update PCIe device tree bindings (diff) | |
download | linux-e99a187b5c5f60fe55ca586f82ac1a3557fb166a.tar.xz linux-e99a187b5c5f60fe55ca586f82ac1a3557fb166a.zip |
PCI: iproc: Add outbound mapping support
Certain SoCs require the PCIe outbound mapping to be configured in
software. Add support for those chips.
[jonmason: Use %pap format when printing size_t to avoid warnings in 32-bit
build.]
[arnd: Use div64_u64() instead of "%" to avoid __aeabi_uldivmod link error
in 32-bit build.]
Signed-off-by: Ray Jui <rjui@broadcom.com>
Signed-off-by: Jon Mason <jonmason@broadcom.com>
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
Diffstat (limited to 'drivers/pci/host/pcie-iproc.c')
-rw-r--r-- | drivers/pci/host/pcie-iproc.c | 115 |
1 files changed, 115 insertions, 0 deletions
diff --git a/drivers/pci/host/pcie-iproc.c b/drivers/pci/host/pcie-iproc.c index 62e80855aed1..9193951ae861 100644 --- a/drivers/pci/host/pcie-iproc.c +++ b/drivers/pci/host/pcie-iproc.c @@ -66,6 +66,18 @@ #define PCIE_DL_ACTIVE_SHIFT 2 #define PCIE_DL_ACTIVE BIT(PCIE_DL_ACTIVE_SHIFT) +#define OARR_VALID_SHIFT 0 +#define OARR_VALID BIT(OARR_VALID_SHIFT) +#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 + static inline struct iproc_pcie *iproc_data(struct pci_bus *bus) { struct iproc_pcie *pcie; @@ -212,6 +224,101 @@ static void iproc_pcie_enable(struct iproc_pcie *pcie) writel(SYS_RC_INTX_MASK, pcie->base + SYS_RC_INTX_EN); } +/** + * Some iProc SoCs require the SW to configure the outbound address mapping + * + * Outbound address translation: + * + * iproc_pcie_address = axi_address - axi_offset + * OARR = iproc_pcie_address + * OMAP = pci_addr + * + * axi_addr -> iproc_pcie_address -> OARR -> OMAP -> pci_address + */ +static int iproc_pcie_setup_ob(struct iproc_pcie *pcie, u64 axi_addr, + u64 pci_addr, resource_size_t size) +{ + struct iproc_pcie_ob *ob = &pcie->ob; + unsigned i; + u64 max_size = (u64)ob->window_size * MAX_NUM_OB_WINDOWS; + u64 remainder; + + if (size > max_size) { + dev_err(pcie->dev, + "res size 0x%pap exceeds max supported size 0x%llx\n", + &size, max_size); + return -EINVAL; + } + + div64_u64_rem(size, ob->window_size, &remainder); + if (remainder) { + dev_err(pcie->dev, + "res size %pap needs to be multiple of window size %pap\n", + &size, &ob->window_size); + return -EINVAL; + } + + if (axi_addr < ob->axi_offset) { + dev_err(pcie->dev, + "axi address %pap less than offset %pap\n", + &axi_addr, &ob->axi_offset); + return -EINVAL; + } + + /* + * Translate the AXI address to the internal address used by the iProc + * PCIe core before programming the OARR + */ + 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)); + + size -= ob->window_size; + if (size == 0) + break; + + axi_addr += ob->window_size; + pci_addr += ob->window_size; + } + + return 0; +} + +static int iproc_pcie_map_ranges(struct iproc_pcie *pcie, + struct list_head *resources) +{ + struct resource_entry *window; + int ret; + + resource_list_for_each_entry(window, resources) { + struct resource *res = window->res; + u64 res_type = resource_type(res); + + switch (res_type) { + case IORESOURCE_IO: + case IORESOURCE_BUS: + break; + case IORESOURCE_MEM: + ret = iproc_pcie_setup_ob(pcie, res->start, + res->start - window->offset, + resource_size(res)); + if (ret) + return ret; + break; + default: + dev_err(pcie->dev, "invalid resource %pR\n", res); + return -EINVAL; + } + } + + return 0; +} + int iproc_pcie_setup(struct iproc_pcie *pcie, struct list_head *res) { int ret; @@ -235,6 +342,14 @@ int iproc_pcie_setup(struct iproc_pcie *pcie, struct list_head *res) iproc_pcie_reset(pcie); + if (pcie->need_ob_cfg) { + ret = iproc_pcie_map_ranges(pcie, res); + if (ret) { + dev_err(pcie->dev, "map failed\n"); + goto err_power_off_phy; + } + } + #ifdef CONFIG_ARM pcie->sysdata.private_data = pcie; sysdata = &pcie->sysdata; |