diff options
Diffstat (limited to 'kernel/drivers/pci/host/pcie-iproc.c')
-rw-r--r-- | kernel/drivers/pci/host/pcie-iproc.c | 223 |
1 files changed, 182 insertions, 41 deletions
diff --git a/kernel/drivers/pci/host/pcie-iproc.c b/kernel/drivers/pci/host/pcie-iproc.c index 329e1b545..eac719af1 100644 --- a/kernel/drivers/pci/host/pcie-iproc.c +++ b/kernel/drivers/pci/host/pcie-iproc.c @@ -1,6 +1,6 @@ /* * Copyright (C) 2014 Hauke Mehrtens <hauke@hauke-m.de> - * Copyright (C) 2015 Broadcom Corporatcommon ion + * Copyright (C) 2015 Broadcom Corporation * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -31,6 +31,8 @@ #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 @@ -58,9 +60,35 @@ #define SYS_RC_INTX_EN 0x330 #define SYS_RC_INTX_MASK 0xf -static inline struct iproc_pcie *sys_to_pcie(struct pci_sys_data *sys) +#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 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) { - return sys->private_data; + struct iproc_pcie *pcie; +#ifdef CONFIG_ARM + struct pci_sys_data *sys = bus->sysdata; + + pcie = sys->private_data; +#else + pcie = bus->sysdata; +#endif + return pcie; } /** @@ -71,8 +99,7 @@ static void __iomem *iproc_pcie_map_cfg_bus(struct pci_bus *bus, unsigned int devfn, int where) { - struct pci_sys_data *sys = bus->sysdata; - struct iproc_pcie *pcie = sys_to_pcie(sys); + struct iproc_pcie *pcie = iproc_data(bus); unsigned slot = PCI_SLOT(devfn); unsigned fn = PCI_FUNC(devfn); unsigned busno = bus->number; @@ -112,23 +139,32 @@ static void iproc_pcie_reset(struct iproc_pcie *pcie) u32 val; /* - * Configure the PCIe controller as root complex and send a downstream - * reset + * Select perst_b signal as reset source. Put the device into reset, + * and then bring it out of reset */ - val = EP_MODE_SURVIVE_PERST | RC_PCIE_RST_OUTPUT; + val = readl(pcie->base + CLK_CONTROL_OFFSET); + val &= ~EP_PERST_SOURCE_SELECT & ~EP_MODE_SURVIVE_PERST & + ~RC_PCIE_RST_OUTPUT; writel(val, pcie->base + CLK_CONTROL_OFFSET); udelay(250); - val &= ~EP_MODE_SURVIVE_PERST; + + val |= RC_PCIE_RST_OUTPUT; writel(val, pcie->base + CLK_CONTROL_OFFSET); - msleep(250); + msleep(100); } static int iproc_pcie_check_link(struct iproc_pcie *pcie, struct pci_bus *bus) { u8 hdr_type; - u32 link_ctrl; + u32 link_ctrl, class, val; u16 pos, link_status; - int link_is_active = 0; + bool link_is_active = false; + + val = readl(pcie->base + PCIE_LINK_STATUS_OFFSET); + if (!(val & PCIE_PHYLINKUP) || !(val & PCIE_DL_ACTIVE)) { + dev_err(pcie->dev, "PHY or data link is INACTIVE!\n"); + return -ENODEV; + } /* make sure we are not in EP mode */ pci_bus_read_config_byte(bus, 0, PCI_HEADER_TYPE, &hdr_type); @@ -138,14 +174,19 @@ static int iproc_pcie_check_link(struct iproc_pcie *pcie, struct pci_bus *bus) } /* force class to PCI_CLASS_BRIDGE_PCI (0x0604) */ - pci_bus_write_config_word(bus, 0, PCI_CLASS_DEVICE, - PCI_CLASS_BRIDGE_PCI); +#define PCI_BRIDGE_CTRL_REG_OFFSET 0x43c +#define PCI_CLASS_BRIDGE_MASK 0xffff00 +#define PCI_CLASS_BRIDGE_SHIFT 8 + pci_bus_read_config_dword(bus, 0, PCI_BRIDGE_CTRL_REG_OFFSET, &class); + class &= ~PCI_CLASS_BRIDGE_MASK; + class |= (PCI_CLASS_BRIDGE_PCI << PCI_CLASS_BRIDGE_SHIFT); + pci_bus_write_config_dword(bus, 0, PCI_BRIDGE_CTRL_REG_OFFSET, class); /* check link status to see if link is active */ pos = pci_bus_find_capability(bus, 0, PCI_CAP_ID_EXP); pci_bus_read_config_word(bus, 0, pos + PCI_EXP_LNKSTA, &link_status); if (link_status & PCI_EXP_LNKSTA_NLW) - link_is_active = 1; + link_is_active = true; if (!link_is_active) { /* try GEN 1 link speed */ @@ -169,7 +210,7 @@ static int iproc_pcie_check_link(struct iproc_pcie *pcie, struct pci_bus *bus) pci_bus_read_config_word(bus, 0, pos + PCI_EXP_LNKSTA, &link_status); if (link_status & PCI_EXP_LNKSTA_NLW) - link_is_active = 1; + link_is_active = true; } } @@ -183,35 +224,140 @@ static void iproc_pcie_enable(struct iproc_pcie *pcie) writel(SYS_RC_INTX_MASK, pcie->base + SYS_RC_INTX_EN); } -int iproc_pcie_setup(struct iproc_pcie *pcie) +/** + * 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; + void *sysdata; struct pci_bus *bus; if (!pcie || !pcie->dev || !pcie->base) return -EINVAL; - if (pcie->phy) { - ret = phy_init(pcie->phy); - if (ret) { - dev_err(pcie->dev, "unable to initialize PCIe PHY\n"); - return ret; - } - - ret = phy_power_on(pcie->phy); - if (ret) { - dev_err(pcie->dev, "unable to power on PCIe PHY\n"); - goto err_exit_phy; - } + ret = phy_init(pcie->phy); + if (ret) { + dev_err(pcie->dev, "unable to initialize PCIe PHY\n"); + return ret; + } + ret = phy_power_on(pcie->phy); + if (ret) { + dev_err(pcie->dev, "unable to power on PCIe PHY\n"); + goto err_exit_phy; } 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; +#else + sysdata = pcie; +#endif - bus = pci_create_root_bus(pcie->dev, 0, &iproc_pcie_ops, - &pcie->sysdata, pcie->resources); + bus = pci_create_root_bus(pcie->dev, 0, &iproc_pcie_ops, sysdata, res); if (!bus) { dev_err(pcie->dev, "unable to create PCI root bus\n"); ret = -ENOMEM; @@ -229,7 +375,7 @@ int iproc_pcie_setup(struct iproc_pcie *pcie) pci_scan_child_bus(bus); pci_assign_unassigned_bus_resources(bus); - pci_fixup_irqs(pci_common_swizzle, of_irq_parse_and_map_pci); + pci_fixup_irqs(pci_common_swizzle, pcie->map_irq); pci_bus_add_devices(bus); return 0; @@ -239,12 +385,9 @@ err_rm_root_bus: pci_remove_root_bus(bus); err_power_off_phy: - if (pcie->phy) - phy_power_off(pcie->phy); + phy_power_off(pcie->phy); err_exit_phy: - if (pcie->phy) - phy_exit(pcie->phy); - + phy_exit(pcie->phy); return ret; } EXPORT_SYMBOL(iproc_pcie_setup); @@ -254,10 +397,8 @@ int iproc_pcie_remove(struct iproc_pcie *pcie) pci_stop_root_bus(pcie->root_bus); pci_remove_root_bus(pcie->root_bus); - if (pcie->phy) { - phy_power_off(pcie->phy); - phy_exit(pcie->phy); - } + phy_power_off(pcie->phy); + phy_exit(pcie->phy); return 0; } |