diff options
Diffstat (limited to 'drivers/pci')
30 files changed, 2452 insertions, 635 deletions
diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig index 73de4ef..616165d 100644 --- a/drivers/pci/Kconfig +++ b/drivers/pci/Kconfig @@ -1,6 +1,9 @@ # # PCI configuration # + +source "drivers/pci/pcie/Kconfig" + config PCI_BUS_ADDR_T_64BIT def_bool y if (ARCH_DMA_ADDR_T_64BIT || 64BIT) depends on PCI @@ -118,4 +121,5 @@ config PCI_LABEL def_bool y if (DMI || ACPI) select NLS +source "drivers/pci/hotplug/Kconfig" source "drivers/pci/host/Kconfig" diff --git a/drivers/pci/access.c b/drivers/pci/access.c index 8c05b5c..01b9d0a 100644 --- a/drivers/pci/access.c +++ b/drivers/pci/access.c @@ -174,38 +174,6 @@ struct pci_ops *pci_bus_set_ops(struct pci_bus *bus, struct pci_ops *ops) } EXPORT_SYMBOL(pci_bus_set_ops); -/** - * pci_read_vpd - Read one entry from Vital Product Data - * @dev: pci device struct - * @pos: offset in vpd space - * @count: number of bytes to read - * @buf: pointer to where to store result - * - */ -ssize_t pci_read_vpd(struct pci_dev *dev, loff_t pos, size_t count, void *buf) -{ - if (!dev->vpd || !dev->vpd->ops) - return -ENODEV; - return dev->vpd->ops->read(dev, pos, count, buf); -} -EXPORT_SYMBOL(pci_read_vpd); - -/** - * pci_write_vpd - Write entry to Vital Product Data - * @dev: pci device struct - * @pos: offset in vpd space - * @count: number of bytes to write - * @buf: buffer containing write data - * - */ -ssize_t pci_write_vpd(struct pci_dev *dev, loff_t pos, size_t count, const void *buf) -{ - if (!dev->vpd || !dev->vpd->ops) - return -ENODEV; - return dev->vpd->ops->write(dev, pos, count, buf); -} -EXPORT_SYMBOL(pci_write_vpd); - /* * The following routines are to prevent the user from accessing PCI config * space when it's unsafe to do so. Some devices require this during BIST and @@ -277,15 +245,91 @@ PCI_USER_WRITE_CONFIG(dword, u32) /* VPD access through PCI 2.2+ VPD capability */ -#define PCI_VPD_PCI22_SIZE (PCI_VPD_ADDR_MASK + 1) +/** + * pci_read_vpd - Read one entry from Vital Product Data + * @dev: pci device struct + * @pos: offset in vpd space + * @count: number of bytes to read + * @buf: pointer to where to store result + */ +ssize_t pci_read_vpd(struct pci_dev *dev, loff_t pos, size_t count, void *buf) +{ + if (!dev->vpd || !dev->vpd->ops) + return -ENODEV; + return dev->vpd->ops->read(dev, pos, count, buf); +} +EXPORT_SYMBOL(pci_read_vpd); -struct pci_vpd_pci22 { - struct pci_vpd base; - struct mutex lock; - u16 flag; - bool busy; - u8 cap; -}; +/** + * pci_write_vpd - Write entry to Vital Product Data + * @dev: pci device struct + * @pos: offset in vpd space + * @count: number of bytes to write + * @buf: buffer containing write data + */ +ssize_t pci_write_vpd(struct pci_dev *dev, loff_t pos, size_t count, const void *buf) +{ + if (!dev->vpd || !dev->vpd->ops) + return -ENODEV; + return dev->vpd->ops->write(dev, pos, count, buf); +} +EXPORT_SYMBOL(pci_write_vpd); + +#define PCI_VPD_MAX_SIZE (PCI_VPD_ADDR_MASK + 1) + +/** + * pci_vpd_size - determine actual size of Vital Product Data + * @dev: pci device struct + * @old_size: current assumed size, also maximum allowed size + */ +static size_t pci_vpd_size(struct pci_dev *dev, size_t old_size) +{ + size_t off = 0; + unsigned char header[1+2]; /* 1 byte tag, 2 bytes length */ + + while (off < old_size && + pci_read_vpd(dev, off, 1, header) == 1) { + unsigned char tag; + + if (header[0] & PCI_VPD_LRDT) { + /* Large Resource Data Type Tag */ + tag = pci_vpd_lrdt_tag(header); + /* Only read length from known tag items */ + if ((tag == PCI_VPD_LTIN_ID_STRING) || + (tag == PCI_VPD_LTIN_RO_DATA) || + (tag == PCI_VPD_LTIN_RW_DATA)) { + if (pci_read_vpd(dev, off+1, 2, + &header[1]) != 2) { + dev_warn(&dev->dev, + "invalid large VPD tag %02x size at offset %zu", + tag, off + 1); + return 0; + } + off += PCI_VPD_LRDT_TAG_SIZE + + pci_vpd_lrdt_size(header); + } + } else { + /* Short Resource Data Type Tag */ + off += PCI_VPD_SRDT_TAG_SIZE + + pci_vpd_srdt_size(header); + tag = pci_vpd_srdt_tag(header); + } + + if (tag == PCI_VPD_STIN_END) /* End tag descriptor */ + return off; + + if ((tag != PCI_VPD_LTIN_ID_STRING) && + (tag != PCI_VPD_LTIN_RO_DATA) && + (tag != PCI_VPD_LTIN_RW_DATA)) { + dev_warn(&dev->dev, + "invalid %s VPD tag %02x at offset %zu", + (header[0] & PCI_VPD_LRDT) ? "large" : "short", + tag, off); + return 0; + } + } + return 0; +} /* * Wait for last operation to complete. @@ -295,55 +339,71 @@ struct pci_vpd_pci22 { * * Returns 0 on success, negative values indicate error. */ -static int pci_vpd_pci22_wait(struct pci_dev *dev) +static int pci_vpd_wait(struct pci_dev *dev) { - struct pci_vpd_pci22 *vpd = - container_of(dev->vpd, struct pci_vpd_pci22, base); - unsigned long timeout = jiffies + HZ/20 + 2; + struct pci_vpd *vpd = dev->vpd; + unsigned long timeout = jiffies + msecs_to_jiffies(50); + unsigned long max_sleep = 16; u16 status; int ret; if (!vpd->busy) return 0; - for (;;) { + while (time_before(jiffies, timeout)) { ret = pci_user_read_config_word(dev, vpd->cap + PCI_VPD_ADDR, &status); if (ret < 0) return ret; if ((status & PCI_VPD_ADDR_F) == vpd->flag) { - vpd->busy = false; + vpd->busy = 0; return 0; } - if (time_after(jiffies, timeout)) { - dev_printk(KERN_DEBUG, &dev->dev, "vpd r/w failed. This is likely a firmware bug on this device. Contact the card vendor for a firmware update\n"); - return -ETIMEDOUT; - } if (fatal_signal_pending(current)) return -EINTR; - if (!cond_resched()) - udelay(10); + + usleep_range(10, max_sleep); + if (max_sleep < 1024) + max_sleep *= 2; } + + dev_warn(&dev->dev, "VPD access failed. This is likely a firmware bug on this device. Contact the card vendor for a firmware update\n"); + return -ETIMEDOUT; } -static ssize_t pci_vpd_pci22_read(struct pci_dev *dev, loff_t pos, size_t count, - void *arg) +static ssize_t pci_vpd_read(struct pci_dev *dev, loff_t pos, size_t count, + void *arg) { - struct pci_vpd_pci22 *vpd = - container_of(dev->vpd, struct pci_vpd_pci22, base); + struct pci_vpd *vpd = dev->vpd; int ret; loff_t end = pos + count; u8 *buf = arg; - if (pos < 0 || pos > vpd->base.len || end > vpd->base.len) + if (pos < 0) return -EINVAL; + if (!vpd->valid) { + vpd->valid = 1; + vpd->len = pci_vpd_size(dev, vpd->len); + } + + if (vpd->len == 0) + return -EIO; + + if (pos > vpd->len) + return 0; + + if (end > vpd->len) { + end = vpd->len; + count = end - pos; + } + if (mutex_lock_killable(&vpd->lock)) return -EINTR; - ret = pci_vpd_pci22_wait(dev); + ret = pci_vpd_wait(dev); if (ret < 0) goto out; @@ -355,9 +415,9 @@ static ssize_t pci_vpd_pci22_read(struct pci_dev *dev, loff_t pos, size_t count, pos & ~3); if (ret < 0) break; - vpd->busy = true; + vpd->busy = 1; vpd->flag = PCI_VPD_ADDR_F; - ret = pci_vpd_pci22_wait(dev); + ret = pci_vpd_wait(dev); if (ret < 0) break; @@ -380,22 +440,32 @@ out: return ret ? ret : count; } -static ssize_t pci_vpd_pci22_write(struct pci_dev *dev, loff_t pos, size_t count, - const void *arg) +static ssize_t pci_vpd_write(struct pci_dev *dev, loff_t pos, size_t count, + const void *arg) { - struct pci_vpd_pci22 *vpd = - container_of(dev->vpd, struct pci_vpd_pci22, base); + struct pci_vpd *vpd = dev->vpd; const u8 *buf = arg; loff_t end = pos + count; int ret = 0; - if (pos < 0 || (pos & 3) || (count & 3) || end > vpd->base.len) + if (pos < 0 || (pos & 3) || (count & 3)) + return -EINVAL; + + if (!vpd->valid) { + vpd->valid = 1; + vpd->len = pci_vpd_size(dev, vpd->len); + } + + if (vpd->len == 0) + return -EIO; + + if (end > vpd->len) return -EINVAL; if (mutex_lock_killable(&vpd->lock)) return -EINTR; - ret = pci_vpd_pci22_wait(dev); + ret = pci_vpd_wait(dev); if (ret < 0) goto out; @@ -415,9 +485,9 @@ static ssize_t pci_vpd_pci22_write(struct pci_dev *dev, loff_t pos, size_t count if (ret < 0) break; - vpd->busy = true; + vpd->busy = 1; vpd->flag = 0; - ret = pci_vpd_pci22_wait(dev); + ret = pci_vpd_wait(dev); if (ret < 0) break; @@ -428,15 +498,9 @@ out: return ret ? ret : count; } -static void pci_vpd_pci22_release(struct pci_dev *dev) -{ - kfree(container_of(dev->vpd, struct pci_vpd_pci22, base)); -} - -static const struct pci_vpd_ops pci_vpd_pci22_ops = { - .read = pci_vpd_pci22_read, - .write = pci_vpd_pci22_write, - .release = pci_vpd_pci22_release, +static const struct pci_vpd_ops pci_vpd_ops = { + .read = pci_vpd_read, + .write = pci_vpd_write, }; static ssize_t pci_vpd_f0_read(struct pci_dev *dev, loff_t pos, size_t count, @@ -472,12 +536,11 @@ static ssize_t pci_vpd_f0_write(struct pci_dev *dev, loff_t pos, size_t count, static const struct pci_vpd_ops pci_vpd_f0_ops = { .read = pci_vpd_f0_read, .write = pci_vpd_f0_write, - .release = pci_vpd_pci22_release, }; -int pci_vpd_pci22_init(struct pci_dev *dev) +int pci_vpd_init(struct pci_dev *dev) { - struct pci_vpd_pci22 *vpd; + struct pci_vpd *vpd; u8 cap; cap = pci_find_capability(dev, PCI_CAP_ID_VPD); @@ -488,18 +551,24 @@ int pci_vpd_pci22_init(struct pci_dev *dev) if (!vpd) return -ENOMEM; - vpd->base.len = PCI_VPD_PCI22_SIZE; + vpd->len = PCI_VPD_MAX_SIZE; if (dev->dev_flags & PCI_DEV_FLAGS_VPD_REF_F0) - vpd->base.ops = &pci_vpd_f0_ops; + vpd->ops = &pci_vpd_f0_ops; else - vpd->base.ops = &pci_vpd_pci22_ops; + vpd->ops = &pci_vpd_ops; mutex_init(&vpd->lock); vpd->cap = cap; - vpd->busy = false; - dev->vpd = &vpd->base; + vpd->busy = 0; + vpd->valid = 0; + dev->vpd = vpd; return 0; } +void pci_vpd_release(struct pci_dev *dev) +{ + kfree(dev->vpd); +} + /** * pci_cfg_access_lock - Lock PCI config reads/writes * @dev: pci device struct diff --git a/drivers/pci/bus.c b/drivers/pci/bus.c index 89b3bef..f2187d4 100644 --- a/drivers/pci/bus.c +++ b/drivers/pci/bus.c @@ -291,7 +291,12 @@ void pci_bus_add_device(struct pci_dev *dev) dev->match_driver = true; retval = device_attach(&dev->dev); - WARN_ON(retval < 0); + if (retval < 0) { + dev_warn(&dev->dev, "device attach failed (%d)\n", retval); + pci_proc_detach_device(dev); + pci_remove_sysfs_dev_files(dev); + return; + } dev->is_added = 1; } diff --git a/drivers/pci/host/Kconfig b/drivers/pci/host/Kconfig index 75a6054..4f8e951 100644 --- a/drivers/pci/host/Kconfig +++ b/drivers/pci/host/Kconfig @@ -16,6 +16,16 @@ config PCI_MVEBU depends on ARCH_MVEBU || ARCH_DOVE depends on OF +config PCIE_XILINX_NWL + bool "NWL PCIe Core" + depends on ARCH_ZYNQMP + select PCI_MSI_IRQ_DOMAIN if PCI_MSI + help + Say 'Y' here if you want kernel support for Xilinx + NWL PCIe controller. The controller can act as Root Port + or End Point. The current option selection will only + support root port enabling. + config PCIE_DW bool @@ -41,7 +51,7 @@ config PCI_TEGRA config PCI_RCAR_GEN2 bool "Renesas R-Car Gen2 Internal PCI controller" depends on ARM - depends on ARCH_SHMOBILE || COMPILE_TEST + depends on ARCH_RENESAS || COMPILE_TEST help Say Y here if you want internal PCI support on R-Car Gen2 SoC. There are 3 internal PCI controllers available with a single @@ -49,13 +59,17 @@ config PCI_RCAR_GEN2 config PCI_RCAR_GEN2_PCIE bool "Renesas R-Car PCIe controller" - depends on ARCH_SHMOBILE || (ARM && COMPILE_TEST) + depends on ARCH_RENESAS || (ARM && COMPILE_TEST) help Say Y here if you want PCIe controller support on R-Car Gen2 SoCs. +config PCI_HOST_COMMON + bool + config PCI_HOST_GENERIC bool "Generic PCI host controller" depends on (ARM || ARM64) && OF + select PCI_HOST_COMMON help Say Y here if you want to support a simple generic PCI host controller, such as the one emulated by kvmtool. @@ -81,7 +95,7 @@ config PCI_KEYSTONE config PCIE_XILINX bool "Xilinx AXI PCIe host bridge support" - depends on ARCH_ZYNQ + depends on ARCH_ZYNQ || MICROBLAZE help Say 'Y' here if you want kernel to support the Xilinx AXI PCIe Host Bridge driver. @@ -191,4 +205,18 @@ config PCIE_QCOM PCIe controller uses the Designware core plus Qualcomm-specific hardware wrappers. +config PCI_HOST_THUNDER_PEM + bool "Cavium Thunder PCIe controller to off-chip devices" + depends on OF && ARM64 + select PCI_HOST_COMMON + help + Say Y here if you want PCIe support for CN88XX Cavium Thunder SoCs. + +config PCI_HOST_THUNDER_ECAM + bool "Cavium Thunder ECAM controller to on-chip devices on pass-1.x silicon" + depends on OF && ARM64 + select PCI_HOST_COMMON + help + Say Y here if you want ECAM support for CN88XX-Pass-1.x Cavium Thunder SoCs. + endmenu diff --git a/drivers/pci/host/Makefile b/drivers/pci/host/Makefile index 7b2f20c..2c7efd8 100644 --- a/drivers/pci/host/Makefile +++ b/drivers/pci/host/Makefile @@ -6,10 +6,12 @@ obj-$(CONFIG_PCI_MVEBU) += pci-mvebu.o obj-$(CONFIG_PCI_TEGRA) += pci-tegra.o obj-$(CONFIG_PCI_RCAR_GEN2) += pci-rcar-gen2.o obj-$(CONFIG_PCI_RCAR_GEN2_PCIE) += pcie-rcar.o +obj-$(CONFIG_PCI_HOST_COMMON) += pci-host-common.o obj-$(CONFIG_PCI_HOST_GENERIC) += pci-host-generic.o obj-$(CONFIG_PCIE_SPEAR13XX) += pcie-spear13xx.o obj-$(CONFIG_PCI_KEYSTONE) += pci-keystone-dw.o pci-keystone.o obj-$(CONFIG_PCIE_XILINX) += pcie-xilinx.o +obj-$(CONFIG_PCIE_XILINX_NWL) += pcie-xilinx-nwl.o obj-$(CONFIG_PCI_XGENE) += pci-xgene.o obj-$(CONFIG_PCI_XGENE_MSI) += pci-xgene-msi.o obj-$(CONFIG_PCI_LAYERSCAPE) += pci-layerscape.o @@ -22,3 +24,5 @@ obj-$(CONFIG_PCIE_ALTERA) += pcie-altera.o obj-$(CONFIG_PCIE_ALTERA_MSI) += pcie-altera-msi.o obj-$(CONFIG_PCI_HISI) += pcie-hisi.o obj-$(CONFIG_PCIE_QCOM) += pcie-qcom.o +obj-$(CONFIG_PCI_HOST_THUNDER_ECAM) += pci-thunder-ecam.o +obj-$(CONFIG_PCI_HOST_THUNDER_PEM) += pci-thunder-pem.o diff --git a/drivers/pci/host/pci-host-common.c b/drivers/pci/host/pci-host-common.c new file mode 100644 index 0000000..e9f850f --- /dev/null +++ b/drivers/pci/host/pci-host-common.c @@ -0,0 +1,194 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Copyright (C) 2014 ARM Limited + * + * Author: Will Deacon <will.deacon@arm.com> + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/of_pci.h> +#include <linux/platform_device.h> + +#include "pci-host-common.h" + +static void gen_pci_release_of_pci_ranges(struct gen_pci *pci) +{ + pci_free_resource_list(&pci->resources); +} + +static int gen_pci_parse_request_of_pci_ranges(struct gen_pci *pci) +{ + int err, res_valid = 0; + struct device *dev = pci->host.dev.parent; + struct device_node *np = dev->of_node; + resource_size_t iobase; + struct resource_entry *win; + + err = of_pci_get_host_bridge_resources(np, 0, 0xff, &pci->resources, + &iobase); + if (err) + return err; + + resource_list_for_each_entry(win, &pci->resources) { + struct resource *parent, *res = win->res; + + switch (resource_type(res)) { + case IORESOURCE_IO: + parent = &ioport_resource; + err = pci_remap_iospace(res, iobase); + if (err) { + dev_warn(dev, "error %d: failed to map resource %pR\n", + err, res); + continue; + } + break; + case IORESOURCE_MEM: + parent = &iomem_resource; + res_valid |= !(res->flags & IORESOURCE_PREFETCH); + break; + case IORESOURCE_BUS: + pci->cfg.bus_range = res; + default: + continue; + } + + err = devm_request_resource(dev, parent, res); + if (err) + goto out_release_res; + } + + if (!res_valid) { + dev_err(dev, "non-prefetchable memory resource required\n"); + err = -EINVAL; + goto out_release_res; + } + + return 0; + +out_release_res: + gen_pci_release_of_pci_ranges(pci); + return err; +} + +static int gen_pci_parse_map_cfg_windows(struct gen_pci *pci) +{ + int err; + u8 bus_max; + resource_size_t busn; + struct resource *bus_range; + struct device *dev = pci->host.dev.parent; + struct device_node *np = dev->of_node; + u32 sz = 1 << pci->cfg.ops->bus_shift; + + err = of_address_to_resource(np, 0, &pci->cfg.res); + if (err) { + dev_err(dev, "missing \"reg\" property\n"); + return err; + } + + /* Limit the bus-range to fit within reg */ + bus_max = pci->cfg.bus_range->start + + (resource_size(&pci->cfg.res) >> pci->cfg.ops->bus_shift) - 1; + pci->cfg.bus_range->end = min_t(resource_size_t, + pci->cfg.bus_range->end, bus_max); + + pci->cfg.win = devm_kcalloc(dev, resource_size(pci->cfg.bus_range), + sizeof(*pci->cfg.win), GFP_KERNEL); + if (!pci->cfg.win) + return -ENOMEM; + + /* Map our Configuration Space windows */ + if (!devm_request_mem_region(dev, pci->cfg.res.start, + resource_size(&pci->cfg.res), + "Configuration Space")) + return -ENOMEM; + + bus_range = pci->cfg.bus_range; + for (busn = bus_range->start; busn <= bus_range->end; ++busn) { + u32 idx = busn - bus_range->start; + + pci->cfg.win[idx] = devm_ioremap(dev, + pci->cfg.res.start + idx * sz, + sz); + if (!pci->cfg.win[idx]) + return -ENOMEM; + } + + return 0; +} + +int pci_host_common_probe(struct platform_device *pdev, + struct gen_pci *pci) +{ + int err; + const char *type; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct pci_bus *bus, *child; + + type = of_get_property(np, "device_type", NULL); + if (!type || strcmp(type, "pci")) { + dev_err(dev, "invalid \"device_type\" %s\n", type); + return -EINVAL; + } + + of_pci_check_probe_only(); + + pci->host.dev.parent = dev; + INIT_LIST_HEAD(&pci->host.windows); + INIT_LIST_HEAD(&pci->resources); + + /* Parse our PCI ranges and request their resources */ + err = gen_pci_parse_request_of_pci_ranges(pci); + if (err) + return err; + + /* Parse and map our Configuration Space windows */ + err = gen_pci_parse_map_cfg_windows(pci); + if (err) { + gen_pci_release_of_pci_ranges(pci); + return err; + } + + /* Do not reassign resources if probe only */ + if (!pci_has_flag(PCI_PROBE_ONLY)) + pci_add_flags(PCI_REASSIGN_ALL_RSRC | PCI_REASSIGN_ALL_BUS); + + + bus = pci_scan_root_bus(dev, pci->cfg.bus_range->start, + &pci->cfg.ops->ops, pci, &pci->resources); + if (!bus) { + dev_err(dev, "Scanning rootbus failed"); + return -ENODEV; + } + + pci_fixup_irqs(pci_common_swizzle, of_irq_parse_and_map_pci); + + if (!pci_has_flag(PCI_PROBE_ONLY)) { + pci_bus_size_bridges(bus); + pci_bus_assign_resources(bus); + + list_for_each_entry(child, &bus->children, node) + pcie_bus_configure_settings(child); + } + + pci_bus_add_devices(bus); + return 0; +} + +MODULE_DESCRIPTION("Generic PCI host driver common code"); +MODULE_AUTHOR("Will Deacon <will.deacon@arm.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pci/host/pci-host-common.h b/drivers/pci/host/pci-host-common.h new file mode 100644 index 0000000..09f3fa0 --- /dev/null +++ b/drivers/pci/host/pci-host-common.h @@ -0,0 +1,47 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Copyright (C) 2014 ARM Limited + * + * Author: Will Deacon <will.deacon@arm.com> + */ + +#ifndef _PCI_HOST_COMMON_H +#define _PCI_HOST_COMMON_H + +#include <linux/kernel.h> +#include <linux/platform_device.h> + +struct gen_pci_cfg_bus_ops { + u32 bus_shift; + struct pci_ops ops; +}; + +struct gen_pci_cfg_windows { + struct resource res; + struct resource *bus_range; + void __iomem **win; + + struct gen_pci_cfg_bus_ops *ops; +}; + +struct gen_pci { + struct pci_host_bridge host; + struct gen_pci_cfg_windows cfg; + struct list_head resources; +}; + +int pci_host_common_probe(struct platform_device *pdev, + struct gen_pci *pci); + +#endif /* _PCI_HOST_COMMON_H */ diff --git a/drivers/pci/host/pci-host-generic.c b/drivers/pci/host/pci-host-generic.c index 1652bc7..e8aa78f 100644 --- a/drivers/pci/host/pci-host-generic.c +++ b/drivers/pci/host/pci-host-generic.c @@ -25,24 +25,7 @@ #include <linux/of_pci.h> #include <linux/platform_device.h> -struct gen_pci_cfg_bus_ops { - u32 bus_shift; - struct pci_ops ops; -}; - -struct gen_pci_cfg_windows { - struct resource res; - struct resource *bus_range; - void __iomem **win; - - struct gen_pci_cfg_bus_ops *ops; -}; - -struct gen_pci { - struct pci_host_bridge host; - struct gen_pci_cfg_windows cfg; - struct list_head resources; -}; +#include "pci-host-common.h" static void __iomem *gen_pci_map_cfg_bus_cam(struct pci_bus *bus, unsigned int devfn, @@ -93,175 +76,19 @@ static const struct of_device_id gen_pci_of_match[] = { }; MODULE_DEVICE_TABLE(of, gen_pci_of_match); -static void gen_pci_release_of_pci_ranges(struct gen_pci *pci) -{ - pci_free_resource_list(&pci->resources); -} - -static int gen_pci_parse_request_of_pci_ranges(struct gen_pci *pci) -{ - int err, res_valid = 0; - struct device *dev = pci->host.dev.parent; - struct device_node *np = dev->of_node; - resource_size_t iobase; - struct resource_entry *win; - - err = of_pci_get_host_bridge_resources(np, 0, 0xff, &pci->resources, - &iobase); - if (err) - return err; - - resource_list_for_each_entry(win, &pci->resources) { - struct resource *parent, *res = win->res; - - switch (resource_type(res)) { - case IORESOURCE_IO: - parent = &ioport_resource; - err = pci_remap_iospace(res, iobase); - if (err) { - dev_warn(dev, "error %d: failed to map resource %pR\n", - err, res); - continue; - } - break; - case IORESOURCE_MEM: - parent = &iomem_resource; - res_valid |= !(res->flags & IORESOURCE_PREFETCH); - break; - case IORESOURCE_BUS: - pci->cfg.bus_range = res; - default: - continue; - } - - err = devm_request_resource(dev, parent, res); - if (err) - goto out_release_res; - } - - if (!res_valid) { - dev_err(dev, "non-prefetchable memory resource required\n"); - err = -EINVAL; - goto out_release_res; - } - - return 0; - -out_release_res: - gen_pci_release_of_pci_ranges(pci); - return err; -} - -static int gen_pci_parse_map_cfg_windows(struct gen_pci *pci) -{ - int err; - u8 bus_max; - resource_size_t busn; - struct resource *bus_range; - struct device *dev = pci->host.dev.parent; - struct device_node *np = dev->of_node; - u32 sz = 1 << pci->cfg.ops->bus_shift; - - err = of_address_to_resource(np, 0, &pci->cfg.res); - if (err) { - dev_err(dev, "missing \"reg\" property\n"); - return err; - } - - /* Limit the bus-range to fit within reg */ - bus_max = pci->cfg.bus_range->start + - (resource_size(&pci->cfg.res) >> pci->cfg.ops->bus_shift) - 1; - pci->cfg.bus_range->end = min_t(resource_size_t, - pci->cfg.bus_range->end, bus_max); - - pci->cfg.win = devm_kcalloc(dev, resource_size(pci->cfg.bus_range), - sizeof(*pci->cfg.win), GFP_KERNEL); - if (!pci->cfg.win) - return -ENOMEM; - - /* Map our Configuration Space windows */ - if (!devm_request_mem_region(dev, pci->cfg.res.start, - resource_size(&pci->cfg.res), - "Configuration Space")) - return -ENOMEM; - - bus_range = pci->cfg.bus_range; - for (busn = bus_range->start; busn <= bus_range->end; ++busn) { - u32 idx = busn - bus_range->start; - - pci->cfg.win[idx] = devm_ioremap(dev, - pci->cfg.res.start + idx * sz, - sz); - if (!pci->cfg.win[idx]) - return -ENOMEM; - } - - return 0; -} - static int gen_pci_probe(struct platform_device *pdev) { - int err; - const char *type; - const struct of_device_id *of_id; struct device *dev = &pdev->dev; - struct device_node *np = dev->of_node; + const struct of_device_id *of_id; struct gen_pci *pci = devm_kzalloc(dev, sizeof(*pci), GFP_KERNEL); - struct pci_bus *bus, *child; if (!pci) return -ENOMEM; - type = of_get_property(np, "device_type", NULL); - if (!type || strcmp(type, "pci")) { - dev_err(dev, "invalid \"device_type\" %s\n", type); - return -EINVAL; - } - - of_pci_check_probe_only(); - - of_id = of_match_node(gen_pci_of_match, np); + of_id = of_match_node(gen_pci_of_match, dev->of_node); pci->cfg.ops = (struct gen_pci_cfg_bus_ops *)of_id->data; - pci->host.dev.parent = dev; - INIT_LIST_HEAD(&pci->host.windows); - INIT_LIST_HEAD(&pci->resources); - - /* Parse our PCI ranges and request their resources */ - err = gen_pci_parse_request_of_pci_ranges(pci); - if (err) - return err; - - /* Parse and map our Configuration Space windows */ - err = gen_pci_parse_map_cfg_windows(pci); - if (err) { - gen_pci_release_of_pci_ranges(pci); - return err; - } - - /* Do not reassign resources if probe only */ - if (!pci_has_flag(PCI_PROBE_ONLY)) - pci_add_flags(PCI_REASSIGN_ALL_RSRC | PCI_REASSIGN_ALL_BUS); - - - bus = pci_scan_root_bus(dev, pci->cfg.bus_range->start, - &pci->cfg.ops->ops, pci, &pci->resources); - if (!bus) { - dev_err(dev, "Scanning rootbus failed"); - return -ENODEV; - } - - pci_fixup_irqs(pci_common_swizzle, of_irq_parse_and_map_pci); - - if (!pci_has_flag(PCI_PROBE_ONLY)) { - pci_bus_size_bridges(bus); - pci_bus_assign_resources(bus); - - list_for_each_entry(child, &bus->children, node) - pcie_bus_configure_settings(child); - } - pci_bus_add_devices(bus); - return 0; + return pci_host_common_probe(pdev, pci); } static struct platform_driver gen_pci_driver = { diff --git a/drivers/pci/host/pci-imx6.c b/drivers/pci/host/pci-imx6.c index fe60096..8c9b389 100644 --- a/drivers/pci/host/pci-imx6.c +++ b/drivers/pci/host/pci-imx6.c @@ -39,6 +39,11 @@ struct imx6_pcie { struct pcie_port pp; struct regmap *iomuxc_gpr; void __iomem *mem_base; + u32 tx_deemph_gen1; + u32 tx_deemph_gen2_3p5db; + u32 tx_deemph_gen2_6db; + u32 tx_swing_full; + u32 tx_swing_low; }; /* PCIe Root Complex registers (memory-mapped) */ @@ -202,6 +207,23 @@ static int pcie_phy_write(void __iomem *dbi_base, int addr, int data) return 0; } +static void imx6_pcie_reset_phy(struct pcie_port *pp) +{ + u32 tmp; + + pcie_phy_read(pp->dbi_base, PHY_RX_OVRD_IN_LO, &tmp); + tmp |= (PHY_RX_OVRD_IN_LO_RX_DATA_EN | + PHY_RX_OVRD_IN_LO_RX_PLL_EN); + pcie_phy_write(pp->dbi_base, PHY_RX_OVRD_IN_LO, tmp); + + usleep_range(2000, 3000); + + pcie_phy_read(pp->dbi_base, PHY_RX_OVRD_IN_LO, &tmp); + tmp &= ~(PHY_RX_OVRD_IN_LO_RX_DATA_EN | + PHY_RX_OVRD_IN_LO_RX_PLL_EN); + pcie_phy_write(pp->dbi_base, PHY_RX_OVRD_IN_LO, tmp); +} + /* Added for PCI abort handling */ static int imx6q_pcie_abort_handler(unsigned long addr, unsigned int fsr, struct pt_regs *regs) @@ -317,31 +339,50 @@ static void imx6_pcie_init_phy(struct pcie_port *pp) IMX6Q_GPR12_LOS_LEVEL, 9 << 4); regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR8, - IMX6Q_GPR8_TX_DEEMPH_GEN1, 0 << 0); + IMX6Q_GPR8_TX_DEEMPH_GEN1, + imx6_pcie->tx_deemph_gen1 << 0); regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR8, - IMX6Q_GPR8_TX_DEEMPH_GEN2_3P5DB, 0 << 6); + IMX6Q_GPR8_TX_DEEMPH_GEN2_3P5DB, + imx6_pcie->tx_deemph_gen2_3p5db << 6); regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR8, - IMX6Q_GPR8_TX_DEEMPH_GEN2_6DB, 20 << 12); + IMX6Q_GPR8_TX_DEEMPH_GEN2_6DB, + imx6_pcie->tx_deemph_gen2_6db << 12); regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR8, - IMX6Q_GPR8_TX_SWING_FULL, 127 << 18); + IMX6Q_GPR8_TX_SWING_FULL, + imx6_pcie->tx_swing_full << 18); regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR8, - IMX6Q_GPR8_TX_SWING_LOW, 127 << 25); + IMX6Q_GPR8_TX_SWING_LOW, + imx6_pcie->tx_swing_low << 25); } static int imx6_pcie_wait_for_link(struct pcie_port *pp) { unsigned int retries; + /* + * Test if the PHY reports that the link is up and also that the LTSSM + * training finished. There are three possible states of the link when + * this code is called: + * 1) The link is DOWN (unlikely) + * The link didn't come up yet for some reason. This usually means + * we have a real problem somewhere, if it happens with a peripheral + * connected. This state calls for inspection of the DEBUG registers. + * 2) The link is UP, but still in LTSSM training + * Wait for the training to finish, which should take a very short + * time. If the training does not finish, we have a problem and we + * need to inspect the DEBUG registers. If the training does finish, + * the link is up and operating correctly. + * 3) The link is UP and no longer in LTSSM training + * The link is up and operating correctly. + */ for (retries = 0; retries < 200; retries++) { - if (dw_pcie_link_up(pp)) + u32 reg = readl(pp->dbi_base + PCIE_PHY_DEBUG_R1); + if ((reg & PCIE_PHY_DEBUG_R1_XMLH_LINK_UP) && + !(reg & PCIE_PHY_DEBUG_R1_XMLH_LINK_IN_TRAINING)) return 0; - usleep_range(100, 1000); + usleep_range(1000, 2000); } - dev_err(pp->dev, "phy link never came up\n"); - dev_dbg(pp->dev, "DEBUG_R0: 0x%08x, DEBUG_R1: 0x%08x\n", - readl(pp->dbi_base + PCIE_PHY_DEBUG_R0), - readl(pp->dbi_base + PCIE_PHY_DEBUG_R1)); return -EINVAL; } @@ -390,8 +431,10 @@ static int imx6_pcie_establish_link(struct pcie_port *pp) IMX6Q_GPR12_PCIE_CTL_2, 1 << 10); ret = imx6_pcie_wait_for_link(pp); - if (ret) - return ret; + if (ret) { + dev_info(pp->dev, "Link never came up\n"); + goto err_reset_phy; + } /* Allow Gen2 mode after the link is up. */ tmp = readl(pp->dbi_base + PCIE_RC_LCR); @@ -410,19 +453,28 @@ static int imx6_pcie_establish_link(struct pcie_port *pp) ret = imx6_pcie_wait_for_speed_change(pp); if (ret) { dev_err(pp->dev, "Failed to bring link up!\n"); - return ret; + goto err_reset_phy; } /* Make sure link training is finished as well! */ ret = imx6_pcie_wait_for_link(pp); if (ret) { dev_err(pp->dev, "Failed to bring link up!\n"); - return ret; + goto err_reset_phy; } tmp = readl(pp->dbi_base + PCIE_RC_LCSR); dev_dbg(pp->dev, "Link up, Gen=%i\n", (tmp >> 16) & 0xf); + return 0; + +err_reset_phy: + dev_dbg(pp->dev, "PHY DEBUG_R0=0x%08x DEBUG_R1=0x%08x\n", + readl(pp->dbi_base + PCIE_PHY_DEBUG_R0), + readl(pp->dbi_base + PCIE_PHY_DEBUG_R1)); + imx6_pcie_reset_phy(pp); + + return ret; } static void imx6_pcie_host_init(struct pcie_port *pp) @@ -441,81 +493,10 @@ static void imx6_pcie_host_init(struct pcie_port *pp) dw_pcie_msi_init(pp); } -static void imx6_pcie_reset_phy(struct pcie_port *pp) -{ - u32 tmp; - - pcie_phy_read(pp->dbi_base, PHY_RX_OVRD_IN_LO, &tmp); - tmp |= (PHY_RX_OVRD_IN_LO_RX_DATA_EN | - PHY_RX_OVRD_IN_LO_RX_PLL_EN); - pcie_phy_write(pp->dbi_base, PHY_RX_OVRD_IN_LO, tmp); - - usleep_range(2000, 3000); - - pcie_phy_read(pp->dbi_base, PHY_RX_OVRD_IN_LO, &tmp); - tmp &= ~(PHY_RX_OVRD_IN_LO_RX_DATA_EN | - PHY_RX_OVRD_IN_LO_RX_PLL_EN); - pcie_phy_write(pp->dbi_base, PHY_RX_OVRD_IN_LO, tmp); -} - static int imx6_pcie_link_up(struct pcie_port *pp) { - u32 rc, debug_r0, rx_valid; - int count = 5; - - /* - * Test if the PHY reports that the link is up and also that the LTSSM - * training finished. There are three possible states of the link when - * this code is called: - * 1) The link is DOWN (unlikely) - * The link didn't come up yet for some reason. This usually means - * we have a real problem somewhere. Reset the PHY and exit. This - * state calls for inspection of the DEBUG registers. - * 2) The link is UP, but still in LTSSM training - * Wait for the training to finish, which should take a very short - * time. If the training does not finish, we have a problem and we - * need to inspect the DEBUG registers. If the training does finish, - * the link is up and operating correctly. - * 3) The link is UP and no longer in LTSSM training - * The link is up and operating correctly. - */ - while (1) { - rc = readl(pp->dbi_base + PCIE_PHY_DEBUG_R1); - if (!(rc & PCIE_PHY_DEBUG_R1_XMLH_LINK_UP)) - break; - if (!(rc & PCIE_PHY_DEBUG_R1_XMLH_LINK_IN_TRAINING)) - return 1; - if (!count--) - break; - dev_dbg(pp->dev, "Link is up, but still in training\n"); - /* - * Wait a little bit, then re-check if the link finished - * the training. - */ - usleep_range(1000, 2000); - } - /* - * From L0, initiate MAC entry to gen2 if EP/RC supports gen2. - * Wait 2ms (LTSSM timeout is 24ms, PHY lock is ~5us in gen2). - * If (MAC/LTSSM.state == Recovery.RcvrLock) - * && (PHY/rx_valid==0) then pulse PHY/rx_reset. Transition - * to gen2 is stuck - */ - pcie_phy_read(pp->dbi_base, PCIE_PHY_RX_ASIC_OUT, &rx_valid); - debug_r0 = readl(pp->dbi_base + PCIE_PHY_DEBUG_R0); - - if (rx_valid & PCIE_PHY_RX_ASIC_OUT_VALID) - return 0; - - if ((debug_r0 & 0x3f) != 0x0d) - return 0; - - dev_err(pp->dev, "transition to gen2 is stuck, reset PHY!\n"); - dev_dbg(pp->dev, "debug_r0=%08x debug_r1=%08x\n", debug_r0, rc); - - imx6_pcie_reset_phy(pp); - - return 0; + return readl(pp->dbi_base + PCIE_PHY_DEBUG_R1) & + PCIE_PHY_DEBUG_R1_XMLH_LINK_UP; } static struct pcie_host_ops imx6_pcie_host_ops = { @@ -562,6 +543,7 @@ static int __init imx6_pcie_probe(struct platform_device *pdev) struct imx6_pcie *imx6_pcie; struct pcie_port *pp; struct resource *dbi_base; + struct device_node *node = pdev->dev.of_node; int ret; imx6_pcie = devm_kzalloc(&pdev->dev, sizeof(*imx6_pcie), GFP_KERNEL); @@ -614,6 +596,27 @@ static int __init imx6_pcie_probe(struct platform_device *pdev) return PTR_ERR(imx6_pcie->iomuxc_gpr); } + /* Grab PCIe PHY Tx Settings */ + if (of_property_read_u32(node, "fsl,tx-deemph-gen1", + &imx6_pcie->tx_deemph_gen1)) + imx6_pcie->tx_deemph_gen1 = 0; + + if (of_property_read_u32(node, "fsl,tx-deemph-gen2-3p5db", + &imx6_pcie->tx_deemph_gen2_3p5db)) + imx6_pcie->tx_deemph_gen2_3p5db = 0; + + if (of_property_read_u32(node, "fsl,tx-deemph-gen2-6db", + &imx6_pcie->tx_deemph_gen2_6db)) + imx6_pcie->tx_deemph_gen2_6db = 20; + + if (of_property_read_u32(node, "fsl,tx-swing-full", + &imx6_pcie->tx_swing_full)) + imx6_pcie->tx_swing_full = 127; + + if (of_property_read_u32(node, "fsl,tx-swing-low", + &imx6_pcie->tx_swing_low)) + imx6_pcie->tx_swing_low = 127; + ret = imx6_add_pcie_port(pp, pdev); if (ret < 0) return ret; diff --git a/drivers/pci/host/pci-keystone.c b/drivers/pci/host/pci-keystone.c index 0aa81bd..cd70345 100644 --- a/drivers/pci/host/pci-keystone.c +++ b/drivers/pci/host/pci-keystone.c @@ -359,6 +359,9 @@ static int __init ks_pcie_probe(struct platform_device *pdev) /* initialize SerDes Phy if present */ phy = devm_phy_get(dev, "pcie-phy"); + if (PTR_ERR_OR_ZERO(phy) == -EPROBE_DEFER) + return PTR_ERR(phy); + if (!IS_ERR_OR_NULL(phy)) { ret = phy_init(phy); if (ret < 0) diff --git a/drivers/pci/host/pci-layerscape.c b/drivers/pci/host/pci-layerscape.c index 3923bed..c40d8b2 100644 --- a/drivers/pci/host/pci-layerscape.c +++ b/drivers/pci/host/pci-layerscape.c @@ -203,6 +203,7 @@ static const struct of_device_id ls_pcie_of_match[] = { { .compatible = "fsl,ls1021a-pcie", .data = &ls1021_drvdata }, { .compatible = "fsl,ls1043a-pcie", .data = &ls1043_drvdata }, { .compatible = "fsl,ls2080a-pcie", .data = &ls2080_drvdata }, + { .compatible = "fsl,ls2085a-pcie", .data = &ls2080_drvdata }, { }, }; MODULE_DEVICE_TABLE(of, ls_pcie_of_match); diff --git a/drivers/pci/host/pci-tegra.c b/drivers/pci/host/pci-tegra.c index 3032311..68d1f41 100644 --- a/drivers/pci/host/pci-tegra.c +++ b/drivers/pci/host/pci-tegra.c @@ -281,6 +281,11 @@ struct tegra_pcie { struct resource prefetch; struct resource busn; + struct { + resource_size_t mem; + resource_size_t io; + } offset; + struct clk *pex_clk; struct clk *afi_clk; struct clk *pll_e; @@ -295,7 +300,6 @@ struct tegra_pcie { struct tegra_msi msi; struct list_head ports; - unsigned int num_ports; u32 xbar_config; struct regulator_bulk_data *supplies; @@ -426,31 +430,38 @@ free: return ERR_PTR(err); } -/* - * Look up a virtual address mapping for the specified bus number. If no such - * mapping exists, try to create one. - */ -static void __iomem *tegra_pcie_bus_map(struct tegra_pcie *pcie, - unsigned int busnr) +static int tegra_pcie_add_bus(struct pci_bus *bus) { - struct tegra_pcie_bus *bus; + struct tegra_pcie *pcie = sys_to_pcie(bus->sysdata); + struct tegra_pcie_bus *b; - list_for_each_entry(bus, &pcie->buses, list) - if (bus->nr == busnr) - return (void __iomem *)bus->area->addr; + b = tegra_pcie_bus_alloc(pcie, bus->number); + if (IS_ERR(b)) + return PTR_ERR(b); - bus = tegra_pcie_bus_alloc(pcie, busnr); - if (IS_ERR(bus)) - return NULL; + list_add_tail(&b->list, &pcie->buses); - list_add_tail(&bus->list, &pcie->buses); + return 0; +} - return (void __iomem *)bus->area->addr; +static void tegra_pcie_remove_bus(struct pci_bus *child) +{ + struct tegra_pcie *pcie = sys_to_pcie(child->sysdata); + struct tegra_pcie_bus *bus, *tmp; + + list_for_each_entry_safe(bus, tmp, &pcie->buses, list) { + if (bus->nr == child->number) { + vunmap(bus->area->addr); + list_del(&bus->list); + kfree(bus); + break; + } + } } -static void __iomem *tegra_pcie_conf_address(struct pci_bus *bus, - unsigned int devfn, - int where) +static void __iomem *tegra_pcie_map_bus(struct pci_bus *bus, + unsigned int devfn, + int where) { struct tegra_pcie *pcie = sys_to_pcie(bus->sysdata); void __iomem *addr = NULL; @@ -466,7 +477,12 @@ static void __iomem *tegra_pcie_conf_address(struct pci_bus *bus, } } } else { - addr = tegra_pcie_bus_map(pcie, bus->number); + struct tegra_pcie_bus *b; + + list_for_each_entry(b, &pcie->buses, list) + if (b->nr == bus->number) + addr = (void __iomem *)b->area->addr; + if (!addr) { dev_err(pcie->dev, "failed to map cfg. space for bus %u\n", @@ -481,7 +497,9 @@ static void __iomem *tegra_pcie_conf_address(struct pci_bus *bus, } static struct pci_ops tegra_pcie_ops = { - .map_bus = tegra_pcie_conf_address, + .add_bus = tegra_pcie_add_bus, + .remove_bus = tegra_pcie_remove_bus, + .map_bus = tegra_pcie_map_bus, .read = pci_generic_config_read32, .write = pci_generic_config_write32, }; @@ -598,6 +616,17 @@ static int tegra_pcie_setup(int nr, struct pci_sys_data *sys) struct tegra_pcie *pcie = sys_to_pcie(sys); int err; + sys->mem_offset = pcie->offset.mem; + sys->io_offset = pcie->offset.io; + + err = devm_request_resource(pcie->dev, &pcie->all, &pcie->io); + if (err < 0) + return err; + + err = devm_request_resource(pcie->dev, &ioport_resource, &pcie->pio); + if (err < 0) + return err; + err = devm_request_resource(pcie->dev, &pcie->all, &pcie->mem); if (err < 0) return err; @@ -606,6 +635,7 @@ static int tegra_pcie_setup(int nr, struct pci_sys_data *sys) if (err) return err; + pci_add_resource_offset(&sys->resources, &pcie->pio, sys->io_offset); pci_add_resource_offset(&sys->resources, &pcie->mem, sys->mem_offset); pci_add_resource_offset(&sys->resources, &pcie->prefetch, sys->mem_offset); @@ -741,7 +771,7 @@ static void tegra_pcie_setup_translations(struct tegra_pcie *pcie) afi_writel(pcie, 0, AFI_FPCI_BAR5); /* map all upstream transactions as uncached */ - afi_writel(pcie, PHYS_OFFSET, AFI_CACHE_BAR0_ST); + afi_writel(pcie, 0, 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); @@ -1601,6 +1631,9 @@ static int tegra_pcie_parse_dt(struct tegra_pcie *pcie) switch (res.flags & IORESOURCE_TYPE_BITS) { case IORESOURCE_IO: + /* Track the bus -> CPU I/O mapping offset. */ + pcie->offset.io = res.start - range.pci_addr; + memcpy(&pcie->pio, &res, sizeof(res)); pcie->pio.name = np->full_name; @@ -1621,6 +1654,14 @@ static int tegra_pcie_parse_dt(struct tegra_pcie *pcie) break; case IORESOURCE_MEM: + /* + * Track the bus -> CPU memory mapping offset. This + * assumes that the prefetchable and non-prefetchable + * regions will be the last of type IORESOURCE_MEM in + * the ranges property. + * */ + pcie->offset.mem = res.start - range.pci_addr; + if (res.flags & IORESOURCE_PREFETCH) { memcpy(&pcie->prefetch, &res, sizeof(res)); pcie->prefetch.name = "prefetchable"; diff --git a/drivers/pci/host/pci-thunder-ecam.c b/drivers/pci/host/pci-thunder-ecam.c new file mode 100644 index 0000000..d71935cb --- /dev/null +++ b/drivers/pci/host/pci-thunder-ecam.c @@ -0,0 +1,403 @@ +/* + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * Copyright (C) 2015, 2016 Cavium, Inc. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/ioport.h> +#include <linux/of_pci.h> +#include <linux/of.h> +#include <linux/platform_device.h> + +#include "pci-host-common.h" + +/* Mapping is standard ECAM */ +static void __iomem *thunder_ecam_map_bus(struct pci_bus *bus, + unsigned int devfn, + int where) +{ + struct gen_pci *pci = bus->sysdata; + resource_size_t idx = bus->number - pci->cfg.bus_range->start; + + return pci->cfg.win[idx] + ((devfn << 12) | where); +} + +static void set_val(u32 v, int where, int size, u32 *val) +{ + int shift = (where & 3) * 8; + + pr_debug("set_val %04x: %08x\n", (unsigned)(where & ~3), v); + v >>= shift; + if (size == 1) + v &= 0xff; + else if (size == 2) + v &= 0xffff; + *val = v; +} + +static int handle_ea_bar(u32 e0, int bar, struct pci_bus *bus, + unsigned int devfn, int where, int size, u32 *val) +{ + void __iomem *addr; + u32 v; + + /* Entries are 16-byte aligned; bits[2,3] select word in entry */ + int where_a = where & 0xc; + + if (where_a == 0) { + set_val(e0, where, size, val); + return PCIBIOS_SUCCESSFUL; + } + if (where_a == 0x4) { + addr = bus->ops->map_bus(bus, devfn, bar); /* BAR 0 */ + if (!addr) { + *val = ~0; + return PCIBIOS_DEVICE_NOT_FOUND; + } + v = readl(addr); + v &= ~0xf; + v |= 2; /* EA entry-1. Base-L */ + set_val(v, where, size, val); + return PCIBIOS_SUCCESSFUL; + } + if (where_a == 0x8) { + u32 barl_orig; + u32 barl_rb; + + addr = bus->ops->map_bus(bus, devfn, bar); /* BAR 0 */ + if (!addr) { + *val = ~0; + return PCIBIOS_DEVICE_NOT_FOUND; + } + barl_orig = readl(addr + 0); + writel(0xffffffff, addr + 0); + barl_rb = readl(addr + 0); + writel(barl_orig, addr + 0); + /* zeros in unsettable bits */ + v = ~barl_rb & ~3; + v |= 0xc; /* EA entry-2. Offset-L */ + set_val(v, where, size, val); + return PCIBIOS_SUCCESSFUL; + } + if (where_a == 0xc) { + addr = bus->ops->map_bus(bus, devfn, bar + 4); /* BAR 1 */ + if (!addr) { + *val = ~0; + return PCIBIOS_DEVICE_NOT_FOUND; + } + v = readl(addr); /* EA entry-3. Base-H */ + set_val(v, where, size, val); + return PCIBIOS_SUCCESSFUL; + } + return PCIBIOS_DEVICE_NOT_FOUND; +} + +static int thunder_ecam_p2_config_read(struct pci_bus *bus, unsigned int devfn, + int where, int size, u32 *val) +{ + struct gen_pci *pci = bus->sysdata; + int where_a = where & ~3; + void __iomem *addr; + u32 node_bits; + u32 v; + + /* EA Base[63:32] may be missing some bits ... */ + switch (where_a) { + case 0xa8: + case 0xbc: + case 0xd0: + case 0xe4: + break; + default: + return pci_generic_config_read(bus, devfn, where, size, val); + } + + addr = bus->ops->map_bus(bus, devfn, where_a); + if (!addr) { + *val = ~0; + return PCIBIOS_DEVICE_NOT_FOUND; + } + + v = readl(addr); + + /* + * Bit 44 of the 64-bit Base must match the same bit in + * the config space access window. Since we are working with + * the high-order 32 bits, shift everything down by 32 bits. + */ + node_bits = (pci->cfg.res.start >> 32) & (1 << 12); + + v |= node_bits; + set_val(v, where, size, val); + + return PCIBIOS_SUCCESSFUL; +} + +static int thunder_ecam_config_read(struct pci_bus *bus, unsigned int devfn, + int where, int size, u32 *val) +{ + u32 v; + u32 vendor_device; + u32 class_rev; + void __iomem *addr; + int cfg_type; + int where_a = where & ~3; + + addr = bus->ops->map_bus(bus, devfn, 0xc); + if (!addr) { + *val = ~0; + return PCIBIOS_DEVICE_NOT_FOUND; + } + + v = readl(addr); + + /* Check for non type-00 header */ + cfg_type = (v >> 16) & 0x7f; + + addr = bus->ops->map_bus(bus, devfn, 8); + if (!addr) { + *val = ~0; + return PCIBIOS_DEVICE_NOT_FOUND; + } + + class_rev = readl(addr); + if (class_rev == 0xffffffff) + goto no_emulation; + + if ((class_rev & 0xff) >= 8) { + /* Pass-2 handling */ + if (cfg_type) + goto no_emulation; + return thunder_ecam_p2_config_read(bus, devfn, where, + size, val); + } + + /* + * All BARs have fixed addresses specified by the EA + * capability; they must return zero on read. + */ + if (cfg_type == 0 && + ((where >= 0x10 && where < 0x2c) || + (where >= 0x1a4 && where < 0x1bc))) { + /* BAR or SR-IOV BAR */ + *val = 0; + return PCIBIOS_SUCCESSFUL; + } + + addr = bus->ops->map_bus(bus, devfn, 0); + if (!addr) { + *val = ~0; + return PCIBIOS_DEVICE_NOT_FOUND; + } + + vendor_device = readl(addr); + if (vendor_device == 0xffffffff) + goto no_emulation; + + pr_debug("%04x:%04x - Fix pass#: %08x, where: %03x, devfn: %03x\n", + vendor_device & 0xffff, vendor_device >> 16, class_rev, + (unsigned) where, devfn); + + /* Check for non type-00 header */ + if (cfg_type == 0) { + bool has_msix; + bool is_nic = (vendor_device == 0xa01e177d); + bool is_tns = (vendor_device == 0xa01f177d); + + addr = bus->ops->map_bus(bus, devfn, 0x70); + if (!addr) { + *val = ~0; + return PCIBIOS_DEVICE_NOT_FOUND; + } + /* E_CAP */ + v = readl(addr); + has_msix = (v & 0xff00) != 0; + + if (!has_msix && where_a == 0x70) { + v |= 0xbc00; /* next capability is EA at 0xbc */ + set_val(v, where, size, val); + return PCIBIOS_SUCCESSFUL; + } + if (where_a == 0xb0) { + addr = bus->ops->map_bus(bus, devfn, where_a); + if (!addr) { + *val = ~0; + return PCIBIOS_DEVICE_NOT_FOUND; + } + v = readl(addr); + if (v & 0xff00) + pr_err("Bad MSIX cap header: %08x\n", v); + v |= 0xbc00; /* next capability is EA at 0xbc */ + set_val(v, where, size, val); + return PCIBIOS_SUCCESSFUL; + } + if (where_a == 0xbc) { + if (is_nic) + v = 0x40014; /* EA last in chain, 4 entries */ + else if (is_tns) + v = 0x30014; /* EA last in chain, 3 entries */ + else if (has_msix) + v = 0x20014; /* EA last in chain, 2 entries */ + else + v = 0x10014; /* EA last in chain, 1 entry */ + set_val(v, where, size, val); + return PCIBIOS_SUCCESSFUL; + } + if (where_a >= 0xc0 && where_a < 0xd0) + /* EA entry-0. PP=0, BAR0 Size:3 */ + return handle_ea_bar(0x80ff0003, + 0x10, bus, devfn, where, + size, val); + if (where_a >= 0xd0 && where_a < 0xe0 && has_msix) + /* EA entry-1. PP=0, BAR4 Size:3 */ + return handle_ea_bar(0x80ff0043, + 0x20, bus, devfn, where, + size, val); + if (where_a >= 0xe0 && where_a < 0xf0 && is_tns) + /* EA entry-2. PP=0, BAR2, Size:3 */ + return handle_ea_bar(0x80ff0023, + 0x18, bus, devfn, where, + size, val); + if (where_a >= 0xe0 && where_a < 0xf0 && is_nic) + /* EA entry-2. PP=4, VF_BAR0 (9), Size:3 */ + return handle_ea_bar(0x80ff0493, + 0x1a4, bus, devfn, where, + size, val); + if (where_a >= 0xf0 && where_a < 0x100 && is_nic) + /* EA entry-3. PP=4, VF_BAR4 (d), Size:3 */ + return handle_ea_bar(0x80ff04d3, + 0x1b4, bus, devfn, where, + size, val); + } else if (cfg_type == 1) { + bool is_rsl_bridge = devfn == 0x08; + bool is_rad_bridge = devfn == 0xa0; + bool is_zip_bridge = devfn == 0xa8; + bool is_dfa_bridge = devfn == 0xb0; + bool is_nic_bridge = devfn == 0x10; + + if (where_a == 0x70) { + addr = bus->ops->map_bus(bus, devfn, where_a); + if (!addr) { + *val = ~0; + return PCIBIOS_DEVICE_NOT_FOUND; + } + v = readl(addr); + if (v & 0xff00) + pr_err("Bad PCIe cap header: %08x\n", v); + v |= 0xbc00; /* next capability is EA at 0xbc */ + set_val(v, where, size, val); + return PCIBIOS_SUCCESSFUL; + } + if (where_a == 0xbc) { + if (is_nic_bridge) + v = 0x10014; /* EA last in chain, 1 entry */ + else + v = 0x00014; /* EA last in chain, no entries */ + set_val(v, where, size, val); + return PCIBIOS_SUCCESSFUL; + } + if (where_a == 0xc0) { + if (is_rsl_bridge || is_nic_bridge) + v = 0x0101; /* subordinate:secondary = 1:1 */ + else if (is_rad_bridge) + v = 0x0202; /* subordinate:secondary = 2:2 */ + else if (is_zip_bridge) + v = 0x0303; /* subordinate:secondary = 3:3 */ + else if (is_dfa_bridge) + v = 0x0404; /* subordinate:secondary = 4:4 */ + set_val(v, where, size, val); + return PCIBIOS_SUCCESSFUL; + } + if (where_a == 0xc4 && is_nic_bridge) { + /* Enabled, not-Write, SP=ff, PP=05, BEI=6, ES=4 */ + v = 0x80ff0564; + set_val(v, where, size, val); + return PCIBIOS_SUCCESSFUL; + } + if (where_a == 0xc8 && is_nic_bridge) { + v = 0x00000002; /* Base-L 64-bit */ + set_val(v, where, size, val); + return PCIBIOS_SUCCESSFUL; + } + if (where_a == 0xcc && is_nic_bridge) { + v = 0xfffffffe; /* MaxOffset-L 64-bit */ + set_val(v, where, size, val); + return PCIBIOS_SUCCESSFUL; + } + if (where_a == 0xd0 && is_nic_bridge) { + v = 0x00008430; /* NIC Base-H */ + set_val(v, where, size, val); + return PCIBIOS_SUCCESSFUL; + } + if (where_a == 0xd4 && is_nic_bridge) { + v = 0x0000000f; /* MaxOffset-H */ + set_val(v, where, size, val); + return PCIBIOS_SUCCESSFUL; + } + } +no_emulation: + return pci_generic_config_read(bus, devfn, where, size, val); +} + +static int thunder_ecam_config_write(struct pci_bus *bus, unsigned int devfn, + int where, int size, u32 val) +{ + /* + * All BARs have fixed addresses; ignore BAR writes so they + * don't get corrupted. + */ + if ((where >= 0x10 && where < 0x2c) || + (where >= 0x1a4 && where < 0x1bc)) + /* BAR or SR-IOV BAR */ + return PCIBIOS_SUCCESSFUL; + + return pci_generic_config_write(bus, devfn, where, size, val); +} + +static struct gen_pci_cfg_bus_ops thunder_ecam_bus_ops = { + .bus_shift = 20, + .ops = { + .map_bus = thunder_ecam_map_bus, + .read = thunder_ecam_config_read, + .write = thunder_ecam_config_write, + } +}; + +static const struct of_device_id thunder_ecam_of_match[] = { + { .compatible = "cavium,pci-host-thunder-ecam", + .data = &thunder_ecam_bus_ops }, + + { }, +}; +MODULE_DEVICE_TABLE(of, thunder_ecam_of_match); + +static int thunder_ecam_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + const struct of_device_id *of_id; + struct gen_pci *pci = devm_kzalloc(dev, sizeof(*pci), GFP_KERNEL); + + if (!pci) + return -ENOMEM; + + of_id = of_match_node(thunder_ecam_of_match, dev->of_node); + pci->cfg.ops = (struct gen_pci_cfg_bus_ops *)of_id->data; + + return pci_host_common_probe(pdev, pci); +} + +static struct platform_driver thunder_ecam_driver = { + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = thunder_ecam_of_match, + }, + .probe = thunder_ecam_probe, +}; +module_platform_driver(thunder_ecam_driver); + +MODULE_DESCRIPTION("Thunder ECAM PCI host driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pci/host/pci-thunder-pem.c b/drivers/pci/host/pci-thunder-pem.c new file mode 100644 index 0000000..cabb92a --- /dev/null +++ b/drivers/pci/host/pci-thunder-pem.c @@ -0,0 +1,346 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Copyright (C) 2015 - 2016 Cavium, Inc. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/of_pci.h> +#include <linux/platform_device.h> + +#include "pci-host-common.h" + +#define PEM_CFG_WR 0x28 +#define PEM_CFG_RD 0x30 + +struct thunder_pem_pci { + struct gen_pci gen_pci; + u32 ea_entry[3]; + void __iomem *pem_reg_base; +}; + +static void __iomem *thunder_pem_map_bus(struct pci_bus *bus, + unsigned int devfn, int where) +{ + struct gen_pci *pci = bus->sysdata; + resource_size_t idx = bus->number - pci->cfg.bus_range->start; + + return pci->cfg.win[idx] + ((devfn << 16) | where); +} + +static int thunder_pem_bridge_read(struct pci_bus *bus, unsigned int devfn, + int where, int size, u32 *val) +{ + u64 read_val; + struct thunder_pem_pci *pem_pci; + struct gen_pci *pci = bus->sysdata; + + pem_pci = container_of(pci, struct thunder_pem_pci, gen_pci); + + if (devfn != 0 || where >= 2048) { + *val = ~0; + return PCIBIOS_DEVICE_NOT_FOUND; + } + + /* + * 32-bit accesses only. Write the address to the low order + * bits of PEM_CFG_RD, then trigger the read by reading back. + * The config data lands in the upper 32-bits of PEM_CFG_RD. + */ + read_val = where & ~3ull; + writeq(read_val, pem_pci->pem_reg_base + PEM_CFG_RD); + read_val = readq(pem_pci->pem_reg_base + PEM_CFG_RD); + read_val >>= 32; + + /* + * The config space contains some garbage, fix it up. Also + * synthesize an EA capability for the BAR used by MSI-X. + */ + switch (where & ~3) { + case 0x40: + read_val &= 0xffff00ff; + read_val |= 0x00007000; /* Skip MSI CAP */ + break; + case 0x70: /* Express Cap */ + /* PME interrupt on vector 2*/ + read_val |= (2u << 25); + break; + case 0xb0: /* MSI-X Cap */ + /* TableSize=4, Next Cap is EA */ + read_val &= 0xc00000ff; + read_val |= 0x0003bc00; + break; + case 0xb4: + /* Table offset=0, BIR=0 */ + read_val = 0x00000000; + break; + case 0xb8: + /* BPA offset=0xf0000, BIR=0 */ + read_val = 0x000f0000; + break; + case 0xbc: + /* EA, 1 entry, no next Cap */ + read_val = 0x00010014; + break; + case 0xc0: + /* DW2 for type-1 */ + read_val = 0x00000000; + break; + case 0xc4: + /* Entry BEI=0, PP=0x00, SP=0xff, ES=3 */ + read_val = 0x80ff0003; + break; + case 0xc8: + read_val = pem_pci->ea_entry[0]; + break; + case 0xcc: + read_val = pem_pci->ea_entry[1]; + break; + case 0xd0: + read_val = pem_pci->ea_entry[2]; + break; + default: + break; + } + read_val >>= (8 * (where & 3)); + switch (size) { + case 1: + read_val &= 0xff; + break; + case 2: + read_val &= 0xffff; + break; + default: + break; + } + *val = read_val; + return PCIBIOS_SUCCESSFUL; +} + +static int thunder_pem_config_read(struct pci_bus *bus, unsigned int devfn, + int where, int size, u32 *val) +{ + struct gen_pci *pci = bus->sysdata; + + if (bus->number < pci->cfg.bus_range->start || + bus->number > pci->cfg.bus_range->end) + return PCIBIOS_DEVICE_NOT_FOUND; + + /* + * The first device on the bus is the PEM PCIe bridge. + * Special case its config access. + */ + if (bus->number == pci->cfg.bus_range->start) + return thunder_pem_bridge_read(bus, devfn, where, size, val); + + return pci_generic_config_read(bus, devfn, where, size, val); +} + +/* + * Some of the w1c_bits below also include read-only or non-writable + * reserved bits, this makes the code simpler and is OK as the bits + * are not affected by writing zeros to them. + */ +static u32 thunder_pem_bridge_w1c_bits(int where) +{ + u32 w1c_bits = 0; + + switch (where & ~3) { + case 0x04: /* Command/Status */ + case 0x1c: /* Base and I/O Limit/Secondary Status */ + w1c_bits = 0xff000000; + break; + case 0x44: /* Power Management Control and Status */ + w1c_bits = 0xfffffe00; + break; + case 0x78: /* Device Control/Device Status */ + case 0x80: /* Link Control/Link Status */ + case 0x88: /* Slot Control/Slot Status */ + case 0x90: /* Root Status */ + case 0xa0: /* Link Control 2 Registers/Link Status 2 */ + w1c_bits = 0xffff0000; + break; + case 0x104: /* Uncorrectable Error Status */ + case 0x110: /* Correctable Error Status */ + case 0x130: /* Error Status */ + case 0x160: /* Link Control 4 */ + w1c_bits = 0xffffffff; + break; + default: + break; + } + return w1c_bits; +} + +static int thunder_pem_bridge_write(struct pci_bus *bus, unsigned int devfn, + int where, int size, u32 val) +{ + struct gen_pci *pci = bus->sysdata; + struct thunder_pem_pci *pem_pci; + u64 write_val, read_val; + u32 mask = 0; + + pem_pci = container_of(pci, struct thunder_pem_pci, gen_pci); + + if (devfn != 0 || where >= 2048) + return PCIBIOS_DEVICE_NOT_FOUND; + + /* + * 32-bit accesses only. If the write is for a size smaller + * than 32-bits, we must first read the 32-bit value and merge + * in the desired bits and then write the whole 32-bits back + * out. + */ + switch (size) { + case 1: + read_val = where & ~3ull; + writeq(read_val, pem_pci->pem_reg_base + PEM_CFG_RD); + read_val = readq(pem_pci->pem_reg_base + PEM_CFG_RD); + read_val >>= 32; + mask = ~(0xff << (8 * (where & 3))); + read_val &= mask; + val = (val & 0xff) << (8 * (where & 3)); + val |= (u32)read_val; + break; + case 2: + read_val = where & ~3ull; + writeq(read_val, pem_pci->pem_reg_base + PEM_CFG_RD); + read_val = readq(pem_pci->pem_reg_base + PEM_CFG_RD); + read_val >>= 32; + mask = ~(0xffff << (8 * (where & 3))); + read_val &= mask; + val = (val & 0xffff) << (8 * (where & 3)); + val |= (u32)read_val; + break; + default: + break; + } + + /* + * By expanding the write width to 32 bits, we may + * inadvertently hit some W1C bits that were not intended to + * be written. Calculate the mask that must be applied to the + * data to be written to avoid these cases. + */ + if (mask) { + u32 w1c_bits = thunder_pem_bridge_w1c_bits(where); + + if (w1c_bits) { + mask &= w1c_bits; + val &= ~mask; + } + } + + /* + * Low order bits are the config address, the high order 32 + * bits are the data to be written. + */ + write_val = where & ~3ull; + write_val |= (((u64)val) << 32); + writeq(write_val, pem_pci->pem_reg_base + PEM_CFG_WR); + return PCIBIOS_SUCCESSFUL; +} + +static int thunder_pem_config_write(struct pci_bus *bus, unsigned int devfn, + int where, int size, u32 val) +{ + struct gen_pci *pci = bus->sysdata; + + if (bus->number < pci->cfg.bus_range->start || + bus->number > pci->cfg.bus_range->end) + return PCIBIOS_DEVICE_NOT_FOUND; + /* + * The first device on the bus is the PEM PCIe bridge. + * Special case its config access. + */ + if (bus->number == pci->cfg.bus_range->start) + return thunder_pem_bridge_write(bus, devfn, where, size, val); + + + return pci_generic_config_write(bus, devfn, where, size, val); +} + +static struct gen_pci_cfg_bus_ops thunder_pem_bus_ops = { + .bus_shift = 24, + .ops = { + .map_bus = thunder_pem_map_bus, + .read = thunder_pem_config_read, + .write = thunder_pem_config_write, + } +}; + +static const struct of_device_id thunder_pem_of_match[] = { + { .compatible = "cavium,pci-host-thunder-pem", + .data = &thunder_pem_bus_ops }, + + { }, +}; +MODULE_DEVICE_TABLE(of, thunder_pem_of_match); + +static int thunder_pem_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + const struct of_device_id *of_id; + resource_size_t bar4_start; + struct resource *res_pem; + struct thunder_pem_pci *pem_pci; + + pem_pci = devm_kzalloc(dev, sizeof(*pem_pci), GFP_KERNEL); + if (!pem_pci) + return -ENOMEM; + + of_id = of_match_node(thunder_pem_of_match, dev->of_node); + pem_pci->gen_pci.cfg.ops = (struct gen_pci_cfg_bus_ops *)of_id->data; + + /* + * The second register range is the PEM bridge to the PCIe + * bus. It has a different config access method than those + * devices behind the bridge. + */ + res_pem = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!res_pem) { + dev_err(dev, "missing \"reg[1]\"property\n"); + return -EINVAL; + } + + pem_pci->pem_reg_base = devm_ioremap(dev, res_pem->start, 0x10000); + if (!pem_pci->pem_reg_base) + return -ENOMEM; + + /* + * The MSI-X BAR for the PEM and AER interrupts is located at + * a fixed offset from the PEM register base. Generate a + * fragment of the synthesized Enhanced Allocation capability + * structure here for the BAR. + */ + bar4_start = res_pem->start + 0xf00000; + pem_pci->ea_entry[0] = (u32)bar4_start | 2; + pem_pci->ea_entry[1] = (u32)(res_pem->end - bar4_start) & ~3u; + pem_pci->ea_entry[2] = (u32)(bar4_start >> 32); + + return pci_host_common_probe(pdev, &pem_pci->gen_pci); +} + +static struct platform_driver thunder_pem_driver = { + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = thunder_pem_of_match, + }, + .probe = thunder_pem_probe, +}; +module_platform_driver(thunder_pem_driver); + +MODULE_DESCRIPTION("Thunder PEM PCIe host driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pci/host/pcie-designware.c b/drivers/pci/host/pcie-designware.c index 2171682..f85f10d 100644 --- a/drivers/pci/host/pcie-designware.c +++ b/drivers/pci/host/pcie-designware.c @@ -517,6 +517,11 @@ int dw_pcie_host_init(struct pcie_port *pp) if (pp->ops->host_init) pp->ops->host_init(pp); + /* + * If the platform provides ->rd_other_conf, it means the platform + * uses its own address translation component rather than ATU, so + * we should not program the ATU here. + */ if (!pp->ops->rd_other_conf) dw_pcie_prog_outbound_atu(pp, PCIE_ATU_REGION_INDEX1, PCIE_ATU_TYPE_MEM, pp->mem_base, @@ -551,13 +556,11 @@ int dw_pcie_host_init(struct pcie_port *pp) pci_fixup_irqs(pci_common_swizzle, of_irq_parse_and_map_pci); #endif - if (!pci_has_flag(PCI_PROBE_ONLY)) { - pci_bus_size_bridges(bus); - pci_bus_assign_resources(bus); + pci_bus_size_bridges(bus); + pci_bus_assign_resources(bus); - list_for_each_entry(child, &bus->children, node) - pcie_bus_configure_settings(child); - } + list_for_each_entry(child, &bus->children, node) + pcie_bus_configure_settings(child); pci_bus_add_devices(bus); return 0; diff --git a/drivers/pci/host/pcie-rcar.c b/drivers/pci/host/pcie-rcar.c index 4edb518..3509218 100644 --- a/drivers/pci/host/pcie-rcar.c +++ b/drivers/pci/host/pcie-rcar.c @@ -390,9 +390,7 @@ static int rcar_pcie_enable(struct rcar_pcie *pcie) rcar_pcie_setup(&res, pcie); - /* Do not reassign resources if probe only */ - if (!pci_has_flag(PCI_PROBE_ONLY)) - pci_add_flags(PCI_REASSIGN_ALL_RSRC | PCI_REASSIGN_ALL_BUS); + pci_add_flags(PCI_REASSIGN_ALL_RSRC | PCI_REASSIGN_ALL_BUS); if (IS_ENABLED(CONFIG_PCI_MSI)) bus = pci_scan_root_bus_msi(pcie->dev, pcie->root_bus_nr, @@ -408,13 +406,11 @@ static int rcar_pcie_enable(struct rcar_pcie *pcie) pci_fixup_irqs(pci_common_swizzle, of_irq_parse_and_map_pci); - if (!pci_has_flag(PCI_PROBE_ONLY)) { - pci_bus_size_bridges(bus); - pci_bus_assign_resources(bus); + pci_bus_size_bridges(bus); + pci_bus_assign_resources(bus); - list_for_each_entry(child, &bus->children, node) - pcie_bus_configure_settings(child); - } + list_for_each_entry(child, &bus->children, node) + pcie_bus_configure_settings(child); pci_bus_add_devices(bus); diff --git a/drivers/pci/host/pcie-xilinx-nwl.c b/drivers/pci/host/pcie-xilinx-nwl.c new file mode 100644 index 0000000..5139e64 --- /dev/null +++ b/drivers/pci/host/pcie-xilinx-nwl.c @@ -0,0 +1,881 @@ +/* + * PCIe host controller driver for NWL PCIe Bridge + * Based on pcie-xilinx.c, pci-tegra.c + * + * (C) Copyright 2014 - 2015, Xilinx, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/irqdomain.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/msi.h> +#include <linux/of_address.h> +#include <linux/of_pci.h> +#include <linux/of_platform.h> +#include <linux/of_irq.h> +#include <linux/pci.h> +#include <linux/platform_device.h> +#include <linux/irqchip/chained_irq.h> + +/* Bridge core config registers */ +#define BRCFG_PCIE_RX0 0x00000000 +#define BRCFG_INTERRUPT 0x00000010 +#define BRCFG_PCIE_RX_MSG_FILTER 0x00000020 + +/* Egress - Bridge translation registers */ +#define E_BREG_CAPABILITIES 0x00000200 +#define E_BREG_CONTROL 0x00000208 +#define E_BREG_BASE_LO 0x00000210 +#define E_BREG_BASE_HI 0x00000214 +#define E_ECAM_CAPABILITIES 0x00000220 +#define E_ECAM_CONTROL 0x00000228 +#define E_ECAM_BASE_LO 0x00000230 +#define E_ECAM_BASE_HI 0x00000234 + +/* Ingress - address translations */ +#define I_MSII_CAPABILITIES 0x00000300 +#define I_MSII_CONTROL 0x00000308 +#define I_MSII_BASE_LO 0x00000310 +#define I_MSII_BASE_HI 0x00000314 + +#define I_ISUB_CONTROL 0x000003E8 +#define SET_ISUB_CONTROL BIT(0) +/* Rxed msg fifo - Interrupt status registers */ +#define MSGF_MISC_STATUS 0x00000400 +#define MSGF_MISC_MASK 0x00000404 +#define MSGF_LEG_STATUS 0x00000420 +#define MSGF_LEG_MASK 0x00000424 +#define MSGF_MSI_STATUS_LO 0x00000440 +#define MSGF_MSI_STATUS_HI 0x00000444 +#define MSGF_MSI_MASK_LO 0x00000448 +#define MSGF_MSI_MASK_HI 0x0000044C + +/* Msg filter mask bits */ +#define CFG_ENABLE_PM_MSG_FWD BIT(1) +#define CFG_ENABLE_INT_MSG_FWD BIT(2) +#define CFG_ENABLE_ERR_MSG_FWD BIT(3) +#define CFG_ENABLE_SLT_MSG_FWD BIT(5) +#define CFG_ENABLE_VEN_MSG_FWD BIT(7) +#define CFG_ENABLE_OTH_MSG_FWD BIT(13) +#define CFG_ENABLE_VEN_MSG_EN BIT(14) +#define CFG_ENABLE_VEN_MSG_VEN_INV BIT(15) +#define CFG_ENABLE_VEN_MSG_VEN_ID GENMASK(31, 16) +#define CFG_ENABLE_MSG_FILTER_MASK (CFG_ENABLE_PM_MSG_FWD | \ + CFG_ENABLE_INT_MSG_FWD | \ + CFG_ENABLE_ERR_MSG_FWD | \ + CFG_ENABLE_SLT_MSG_FWD | \ + CFG_ENABLE_VEN_MSG_FWD | \ + CFG_ENABLE_OTH_MSG_FWD | \ + CFG_ENABLE_VEN_MSG_EN | \ + CFG_ENABLE_VEN_MSG_VEN_INV | \ + CFG_ENABLE_VEN_MSG_VEN_ID) + +/* Misc interrupt status mask bits */ +#define MSGF_MISC_SR_RXMSG_AVAIL BIT(0) +#define MSGF_MISC_SR_RXMSG_OVER BIT(1) +#define MSGF_MISC_SR_SLAVE_ERR BIT(4) +#define MSGF_MISC_SR_MASTER_ERR BIT(5) +#define MSGF_MISC_SR_I_ADDR_ERR BIT(6) +#define MSGF_MISC_SR_E_ADDR_ERR BIT(7) +#define MSGF_MISC_SR_UR_DETECT BIT(20) + +#define MSGF_MISC_SR_PCIE_CORE GENMASK(18, 16) +#define MSGF_MISC_SR_PCIE_CORE_ERR GENMASK(31, 22) + +#define MSGF_MISC_SR_MASKALL (MSGF_MISC_SR_RXMSG_AVAIL | \ + MSGF_MISC_SR_RXMSG_OVER | \ + MSGF_MISC_SR_SLAVE_ERR | \ + MSGF_MISC_SR_MASTER_ERR | \ + MSGF_MISC_SR_I_ADDR_ERR | \ + MSGF_MISC_SR_E_ADDR_ERR | \ + MSGF_MISC_SR_UR_DETECT | \ + MSGF_MISC_SR_PCIE_CORE | \ + MSGF_MISC_SR_PCIE_CORE_ERR) + +/* Legacy interrupt status mask bits */ +#define MSGF_LEG_SR_INTA BIT(0) +#define MSGF_LEG_SR_INTB BIT(1) +#define MSGF_LEG_SR_INTC BIT(2) +#define MSGF_LEG_SR_INTD BIT(3) +#define MSGF_LEG_SR_MASKALL (MSGF_LEG_SR_INTA | MSGF_LEG_SR_INTB | \ + MSGF_LEG_SR_INTC | MSGF_LEG_SR_INTD) + +/* MSI interrupt status mask bits */ +#define MSGF_MSI_SR_LO_MASK BIT(0) +#define MSGF_MSI_SR_HI_MASK BIT(0) + +#define MSII_PRESENT BIT(0) +#define MSII_ENABLE BIT(0) +#define MSII_STATUS_ENABLE BIT(15) + +/* Bridge config interrupt mask */ +#define BRCFG_INTERRUPT_MASK BIT(0) +#define BREG_PRESENT BIT(0) +#define BREG_ENABLE BIT(0) +#define BREG_ENABLE_FORCE BIT(1) + +/* E_ECAM status mask bits */ +#define E_ECAM_PRESENT BIT(0) +#define E_ECAM_CR_ENABLE BIT(0) +#define E_ECAM_SIZE_LOC GENMASK(20, 16) +#define E_ECAM_SIZE_SHIFT 16 +#define ECAM_BUS_LOC_SHIFT 20 +#define ECAM_DEV_LOC_SHIFT 12 +#define NWL_ECAM_VALUE_DEFAULT 12 + +#define CFG_DMA_REG_BAR GENMASK(2, 0) + +#define INT_PCI_MSI_NR (2 * 32) +#define INTX_NUM 4 + +/* Readin the PS_LINKUP */ +#define PS_LINKUP_OFFSET 0x00000238 +#define PCIE_PHY_LINKUP_BIT BIT(0) +#define PHY_RDY_LINKUP_BIT BIT(1) + +/* Parameters for the waiting for link up routine */ +#define LINK_WAIT_MAX_RETRIES 10 +#define LINK_WAIT_USLEEP_MIN 90000 +#define LINK_WAIT_USLEEP_MAX 100000 + +struct nwl_msi { /* MSI information */ + struct irq_domain *msi_domain; + unsigned long *bitmap; + struct irq_domain *dev_domain; + struct mutex lock; /* protect bitmap variable */ + int irq_msi0; + int irq_msi1; +}; + +struct nwl_pcie { + struct device *dev; + void __iomem *breg_base; + void __iomem *pcireg_base; + void __iomem *ecam_base; + phys_addr_t phys_breg_base; /* Physical Bridge Register Base */ + phys_addr_t phys_pcie_reg_base; /* Physical PCIe Controller Base */ + phys_addr_t phys_ecam_base; /* Physical Configuration Base */ + u32 breg_size; + u32 pcie_reg_size; + u32 ecam_size; + int irq_intx; + int irq_misc; + u32 ecam_value; + u8 last_busno; + u8 root_busno; + struct nwl_msi msi; + struct irq_domain *legacy_irq_domain; +}; + +static inline u32 nwl_bridge_readl(struct nwl_pcie *pcie, u32 off) +{ + return readl(pcie->breg_base + off); +} + +static inline void nwl_bridge_writel(struct nwl_pcie *pcie, u32 val, u32 off) +{ + writel(val, pcie->breg_base + off); +} + +static bool nwl_pcie_link_up(struct nwl_pcie *pcie) +{ + if (readl(pcie->pcireg_base + PS_LINKUP_OFFSET) & PCIE_PHY_LINKUP_BIT) + return true; + return false; +} + +static bool nwl_phy_link_up(struct nwl_pcie *pcie) +{ + if (readl(pcie->pcireg_base + PS_LINKUP_OFFSET) & PHY_RDY_LINKUP_BIT) + return true; + return false; +} + +static int nwl_wait_for_link(struct nwl_pcie *pcie) +{ + int retries; + + /* check if the link is up or not */ + for (retries = 0; retries < LINK_WAIT_MAX_RETRIES; retries++) { + if (nwl_phy_link_up(pcie)) + return 0; + usleep_range(LINK_WAIT_USLEEP_MIN, LINK_WAIT_USLEEP_MAX); + } + + dev_err(pcie->dev, "PHY link never came up\n"); + return -ETIMEDOUT; +} + +static bool nwl_pcie_valid_device(struct pci_bus *bus, unsigned int devfn) +{ + struct nwl_pcie *pcie = bus->sysdata; + + /* Check link before accessing downstream ports */ + if (bus->number != pcie->root_busno) { + if (!nwl_pcie_link_up(pcie)) + return false; + } + + /* Only one device down on each root port */ + if (bus->number == pcie->root_busno && devfn > 0) + return false; + + return true; +} + +/** + * nwl_pcie_map_bus - Get configuration base + * + * @bus: Bus structure of current bus + * @devfn: Device/function + * @where: Offset from base + * + * Return: Base address of the configuration space needed to be + * accessed. + */ +static void __iomem *nwl_pcie_map_bus(struct pci_bus *bus, unsigned int devfn, + int where) +{ + struct nwl_pcie *pcie = bus->sysdata; + int relbus; + + if (!nwl_pcie_valid_device(bus, devfn)) + return NULL; + + relbus = (bus->number << ECAM_BUS_LOC_SHIFT) | + (devfn << ECAM_DEV_LOC_SHIFT); + + return pcie->ecam_base + relbus + where; +} + +/* PCIe operations */ +static struct pci_ops nwl_pcie_ops = { + .map_bus = nwl_pcie_map_bus, + .read = pci_generic_config_read, + .write = pci_generic_config_write, +}; + +static irqreturn_t nwl_pcie_misc_handler(int irq, void *data) +{ + struct nwl_pcie *pcie = data; + u32 misc_stat; + + /* Checking for misc interrupts */ + misc_stat = nwl_bridge_readl(pcie, MSGF_MISC_STATUS) & + MSGF_MISC_SR_MASKALL; + if (!misc_stat) + return IRQ_NONE; + + if (misc_stat & MSGF_MISC_SR_RXMSG_OVER) + dev_err(pcie->dev, "Received Message FIFO Overflow\n"); + + if (misc_stat & MSGF_MISC_SR_SLAVE_ERR) + dev_err(pcie->dev, "Slave error\n"); + + if (misc_stat & MSGF_MISC_SR_MASTER_ERR) + dev_err(pcie->dev, "Master error\n"); + + if (misc_stat & MSGF_MISC_SR_I_ADDR_ERR) + dev_err(pcie->dev, + "In Misc Ingress address translation error\n"); + + if (misc_stat & MSGF_MISC_SR_E_ADDR_ERR) + dev_err(pcie->dev, + "In Misc Egress address translation error\n"); + + if (misc_stat & MSGF_MISC_SR_PCIE_CORE_ERR) + dev_err(pcie->dev, "PCIe Core error\n"); + + /* Clear misc interrupt status */ + nwl_bridge_writel(pcie, misc_stat, MSGF_MISC_STATUS); + + return IRQ_HANDLED; +} + +static void nwl_pcie_leg_handler(struct irq_desc *desc) +{ + struct irq_chip *chip = irq_desc_get_chip(desc); + struct nwl_pcie *pcie; + unsigned long status; + u32 bit; + u32 virq; + + chained_irq_enter(chip, desc); + pcie = irq_desc_get_handler_data(desc); + + while ((status = nwl_bridge_readl(pcie, MSGF_LEG_STATUS) & + MSGF_LEG_SR_MASKALL) != 0) { + for_each_set_bit(bit, &status, INTX_NUM) { + virq = irq_find_mapping(pcie->legacy_irq_domain, + bit + 1); + if (virq) + generic_handle_irq(virq); + } + } + + chained_irq_exit(chip, desc); +} + +static void nwl_pcie_handle_msi_irq(struct nwl_pcie *pcie, u32 status_reg) +{ + struct nwl_msi *msi; + unsigned long status; + u32 bit; + u32 virq; + + msi = &pcie->msi; + + while ((status = nwl_bridge_readl(pcie, status_reg)) != 0) { + for_each_set_bit(bit, &status, 32) { + nwl_bridge_writel(pcie, 1 << bit, status_reg); + virq = irq_find_mapping(msi->dev_domain, bit); + if (virq) + generic_handle_irq(virq); + } + } +} + +static void nwl_pcie_msi_handler_high(struct irq_desc *desc) +{ + struct irq_chip *chip = irq_desc_get_chip(desc); + struct nwl_pcie *pcie = irq_desc_get_handler_data(desc); + + chained_irq_enter(chip, desc); + nwl_pcie_handle_msi_irq(pcie, MSGF_MSI_STATUS_HI); + chained_irq_exit(chip, desc); +} + +static void nwl_pcie_msi_handler_low(struct irq_desc *desc) +{ + struct irq_chip *chip = irq_desc_get_chip(desc); + struct nwl_pcie *pcie = irq_desc_get_handler_data(desc); + + chained_irq_enter(chip, desc); + nwl_pcie_handle_msi_irq(pcie, MSGF_MSI_STATUS_LO); + chained_irq_exit(chip, desc); +} + +static int nwl_legacy_map(struct irq_domain *domain, unsigned int irq, + irq_hw_number_t hwirq) +{ + irq_set_chip_and_handler(irq, &dummy_irq_chip, handle_simple_irq); + irq_set_chip_data(irq, domain->host_data); + + return 0; +} + +static const struct irq_domain_ops legacy_domain_ops = { + .map = nwl_legacy_map, +}; + +#ifdef CONFIG_PCI_MSI +static struct irq_chip nwl_msi_irq_chip = { + .name = "nwl_pcie:msi", + .irq_enable = unmask_msi_irq, + .irq_disable = mask_msi_irq, + .irq_mask = mask_msi_irq, + .irq_unmask = unmask_msi_irq, + +}; + +static struct msi_domain_info nwl_msi_domain_info = { + .flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS | + MSI_FLAG_MULTI_PCI_MSI), + .chip = &nwl_msi_irq_chip, +}; +#endif + +static void nwl_compose_msi_msg(struct irq_data *data, struct msi_msg *msg) +{ + struct nwl_pcie *pcie = irq_data_get_irq_chip_data(data); + phys_addr_t msi_addr = pcie->phys_pcie_reg_base; + + msg->address_lo = lower_32_bits(msi_addr); + msg->address_hi = upper_32_bits(msi_addr); + msg->data = data->hwirq; +} + +static int nwl_msi_set_affinity(struct irq_data *irq_data, + const struct cpumask *mask, bool force) +{ + return -EINVAL; +} + +static struct irq_chip nwl_irq_chip = { + .name = "Xilinx MSI", + .irq_compose_msi_msg = nwl_compose_msi_msg, + .irq_set_affinity = nwl_msi_set_affinity, +}; + +static int nwl_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, + unsigned int nr_irqs, void *args) +{ + struct nwl_pcie *pcie = domain->host_data; + struct nwl_msi *msi = &pcie->msi; + int bit; + int i; + + mutex_lock(&msi->lock); + bit = bitmap_find_next_zero_area(msi->bitmap, INT_PCI_MSI_NR, 0, + nr_irqs, 0); + if (bit >= INT_PCI_MSI_NR) { + mutex_unlock(&msi->lock); + return -ENOSPC; + } + + bitmap_set(msi->bitmap, bit, nr_irqs); + + for (i = 0; i < nr_irqs; i++) { + irq_domain_set_info(domain, virq + i, bit + i, &nwl_irq_chip, + domain->host_data, handle_simple_irq, + NULL, NULL); + } + mutex_unlock(&msi->lock); + return 0; +} + +static void nwl_irq_domain_free(struct irq_domain *domain, unsigned int virq, + unsigned int nr_irqs) +{ + struct irq_data *data = irq_domain_get_irq_data(domain, virq); + struct nwl_pcie *pcie = irq_data_get_irq_chip_data(data); + struct nwl_msi *msi = &pcie->msi; + + mutex_lock(&msi->lock); + bitmap_clear(msi->bitmap, data->hwirq, nr_irqs); + mutex_unlock(&msi->lock); +} + +static const struct irq_domain_ops dev_msi_domain_ops = { + .alloc = nwl_irq_domain_alloc, + .free = nwl_irq_domain_free, +}; + +static void nwl_msi_free_irq_domain(struct nwl_pcie *pcie) +{ + struct nwl_msi *msi = &pcie->msi; + + if (msi->irq_msi0) + irq_set_chained_handler_and_data(msi->irq_msi0, NULL, NULL); + if (msi->irq_msi1) + irq_set_chained_handler_and_data(msi->irq_msi1, NULL, NULL); + + if (msi->msi_domain) + irq_domain_remove(msi->msi_domain); + if (msi->dev_domain) + irq_domain_remove(msi->dev_domain); + + kfree(msi->bitmap); + msi->bitmap = NULL; +} + +static void nwl_pcie_free_irq_domain(struct nwl_pcie *pcie) +{ + int i; + u32 irq; + + for (i = 0; i < INTX_NUM; i++) { + irq = irq_find_mapping(pcie->legacy_irq_domain, i + 1); + if (irq > 0) + irq_dispose_mapping(irq); + } + if (pcie->legacy_irq_domain) + irq_domain_remove(pcie->legacy_irq_domain); + + nwl_msi_free_irq_domain(pcie); +} + +static int nwl_pcie_init_msi_irq_domain(struct nwl_pcie *pcie) +{ +#ifdef CONFIG_PCI_MSI + struct fwnode_handle *fwnode = of_node_to_fwnode(pcie->dev->of_node); + struct nwl_msi *msi = &pcie->msi; + + msi->dev_domain = irq_domain_add_linear(NULL, INT_PCI_MSI_NR, + &dev_msi_domain_ops, pcie); + if (!msi->dev_domain) { + dev_err(pcie->dev, "failed to create dev IRQ domain\n"); + return -ENOMEM; + } + msi->msi_domain = pci_msi_create_irq_domain(fwnode, + &nwl_msi_domain_info, + msi->dev_domain); + if (!msi->msi_domain) { + dev_err(pcie->dev, "failed to create msi IRQ domain\n"); + irq_domain_remove(msi->dev_domain); + return -ENOMEM; + } +#endif + return 0; +} + +static int nwl_pcie_init_irq_domain(struct nwl_pcie *pcie) +{ + struct device_node *node = pcie->dev->of_node; + struct device_node *legacy_intc_node; + + legacy_intc_node = of_get_next_child(node, NULL); + if (!legacy_intc_node) { + dev_err(pcie->dev, "No legacy intc node found\n"); + return -EINVAL; + } + + pcie->legacy_irq_domain = irq_domain_add_linear(legacy_intc_node, + INTX_NUM, + &legacy_domain_ops, + pcie); + + if (!pcie->legacy_irq_domain) { + dev_err(pcie->dev, "failed to create IRQ domain\n"); + return -ENOMEM; + } + + nwl_pcie_init_msi_irq_domain(pcie); + return 0; +} + +static int nwl_pcie_enable_msi(struct nwl_pcie *pcie, struct pci_bus *bus) +{ + struct platform_device *pdev = to_platform_device(pcie->dev); + struct nwl_msi *msi = &pcie->msi; + unsigned long base; + int ret; + int size = BITS_TO_LONGS(INT_PCI_MSI_NR) * sizeof(long); + + mutex_init(&msi->lock); + + msi->bitmap = kzalloc(size, GFP_KERNEL); + if (!msi->bitmap) + return -ENOMEM; + + /* Get msi_1 IRQ number */ + msi->irq_msi1 = platform_get_irq_byname(pdev, "msi1"); + if (msi->irq_msi1 < 0) { + dev_err(&pdev->dev, "failed to get IRQ#%d\n", msi->irq_msi1); + ret = -EINVAL; + goto err; + } + + irq_set_chained_handler_and_data(msi->irq_msi1, + nwl_pcie_msi_handler_high, pcie); + + /* Get msi_0 IRQ number */ + msi->irq_msi0 = platform_get_irq_byname(pdev, "msi0"); + if (msi->irq_msi0 < 0) { + dev_err(&pdev->dev, "failed to get IRQ#%d\n", msi->irq_msi0); + ret = -EINVAL; + goto err; + } + + irq_set_chained_handler_and_data(msi->irq_msi0, + nwl_pcie_msi_handler_low, pcie); + + /* Check for msii_present bit */ + ret = nwl_bridge_readl(pcie, I_MSII_CAPABILITIES) & MSII_PRESENT; + if (!ret) { + dev_err(pcie->dev, "MSI not present\n"); + ret = -EIO; + goto err; + } + + /* Enable MSII */ + nwl_bridge_writel(pcie, nwl_bridge_readl(pcie, I_MSII_CONTROL) | + MSII_ENABLE, I_MSII_CONTROL); + + /* Enable MSII status */ + nwl_bridge_writel(pcie, nwl_bridge_readl(pcie, I_MSII_CONTROL) | + MSII_STATUS_ENABLE, I_MSII_CONTROL); + + /* setup AFI/FPCI range */ + base = pcie->phys_pcie_reg_base; + nwl_bridge_writel(pcie, lower_32_bits(base), I_MSII_BASE_LO); + nwl_bridge_writel(pcie, upper_32_bits(base), I_MSII_BASE_HI); + + /* + * For high range MSI interrupts: disable, clear any pending, + * and enable + */ + nwl_bridge_writel(pcie, (u32)~MSGF_MSI_SR_HI_MASK, MSGF_MSI_MASK_HI); + + nwl_bridge_writel(pcie, nwl_bridge_readl(pcie, MSGF_MSI_STATUS_HI) & + MSGF_MSI_SR_HI_MASK, MSGF_MSI_STATUS_HI); + + nwl_bridge_writel(pcie, MSGF_MSI_SR_HI_MASK, MSGF_MSI_MASK_HI); + + /* + * For low range MSI interrupts: disable, clear any pending, + * and enable + */ + nwl_bridge_writel(pcie, (u32)~MSGF_MSI_SR_LO_MASK, MSGF_MSI_MASK_LO); + + nwl_bridge_writel(pcie, nwl_bridge_readl(pcie, MSGF_MSI_STATUS_LO) & + MSGF_MSI_SR_LO_MASK, MSGF_MSI_STATUS_LO); + + nwl_bridge_writel(pcie, MSGF_MSI_SR_LO_MASK, MSGF_MSI_MASK_LO); + + return 0; +err: + kfree(msi->bitmap); + msi->bitmap = NULL; + return ret; +} + +static int nwl_pcie_bridge_init(struct nwl_pcie *pcie) +{ + struct platform_device *pdev = to_platform_device(pcie->dev); + u32 breg_val, ecam_val, first_busno = 0; + int err; + + breg_val = nwl_bridge_readl(pcie, E_BREG_CAPABILITIES) & BREG_PRESENT; + if (!breg_val) { + dev_err(pcie->dev, "BREG is not present\n"); + return breg_val; + } + + /* Write bridge_off to breg base */ + nwl_bridge_writel(pcie, lower_32_bits(pcie->phys_breg_base), + E_BREG_BASE_LO); + nwl_bridge_writel(pcie, upper_32_bits(pcie->phys_breg_base), + E_BREG_BASE_HI); + + /* Enable BREG */ + nwl_bridge_writel(pcie, ~BREG_ENABLE_FORCE & BREG_ENABLE, + E_BREG_CONTROL); + + /* Disable DMA channel registers */ + nwl_bridge_writel(pcie, nwl_bridge_readl(pcie, BRCFG_PCIE_RX0) | + CFG_DMA_REG_BAR, BRCFG_PCIE_RX0); + + /* Enable Ingress subtractive decode translation */ + nwl_bridge_writel(pcie, SET_ISUB_CONTROL, I_ISUB_CONTROL); + + /* Enable msg filtering details */ + nwl_bridge_writel(pcie, CFG_ENABLE_MSG_FILTER_MASK, + BRCFG_PCIE_RX_MSG_FILTER); + + err = nwl_wait_for_link(pcie); + if (err) + return err; + + ecam_val = nwl_bridge_readl(pcie, E_ECAM_CAPABILITIES) & E_ECAM_PRESENT; + if (!ecam_val) { + dev_err(pcie->dev, "ECAM is not present\n"); + return ecam_val; + } + + /* Enable ECAM */ + nwl_bridge_writel(pcie, nwl_bridge_readl(pcie, E_ECAM_CONTROL) | + E_ECAM_CR_ENABLE, E_ECAM_CONTROL); + + nwl_bridge_writel(pcie, nwl_bridge_readl(pcie, E_ECAM_CONTROL) | + (pcie->ecam_value << E_ECAM_SIZE_SHIFT), + E_ECAM_CONTROL); + + nwl_bridge_writel(pcie, lower_32_bits(pcie->phys_ecam_base), + E_ECAM_BASE_LO); + nwl_bridge_writel(pcie, upper_32_bits(pcie->phys_ecam_base), + E_ECAM_BASE_HI); + + /* Get bus range */ + ecam_val = nwl_bridge_readl(pcie, E_ECAM_CONTROL); + pcie->last_busno = (ecam_val & E_ECAM_SIZE_LOC) >> E_ECAM_SIZE_SHIFT; + /* Write primary, secondary and subordinate bus numbers */ + ecam_val = first_busno; + ecam_val |= (first_busno + 1) << 8; + ecam_val |= (pcie->last_busno << E_ECAM_SIZE_SHIFT); + writel(ecam_val, (pcie->ecam_base + PCI_PRIMARY_BUS)); + + if (nwl_pcie_link_up(pcie)) + dev_info(pcie->dev, "Link is UP\n"); + else + dev_info(pcie->dev, "Link is DOWN\n"); + + /* Get misc IRQ number */ + pcie->irq_misc = platform_get_irq_byname(pdev, "misc"); + if (pcie->irq_misc < 0) { + dev_err(&pdev->dev, "failed to get misc IRQ %d\n", + pcie->irq_misc); + return -EINVAL; + } + + err = devm_request_irq(pcie->dev, pcie->irq_misc, + nwl_pcie_misc_handler, IRQF_SHARED, + "nwl_pcie:misc", pcie); + if (err) { + dev_err(pcie->dev, "fail to register misc IRQ#%d\n", + pcie->irq_misc); + return err; + } + + /* Disable all misc interrupts */ + nwl_bridge_writel(pcie, (u32)~MSGF_MISC_SR_MASKALL, MSGF_MISC_MASK); + + /* Clear pending misc interrupts */ + nwl_bridge_writel(pcie, nwl_bridge_readl(pcie, MSGF_MISC_STATUS) & + MSGF_MISC_SR_MASKALL, MSGF_MISC_STATUS); + + /* Enable all misc interrupts */ + nwl_bridge_writel(pcie, MSGF_MISC_SR_MASKALL, MSGF_MISC_MASK); + + + /* Disable all legacy interrupts */ + nwl_bridge_writel(pcie, (u32)~MSGF_LEG_SR_MASKALL, MSGF_LEG_MASK); + + /* Clear pending legacy interrupts */ + nwl_bridge_writel(pcie, nwl_bridge_readl(pcie, MSGF_LEG_STATUS) & + MSGF_LEG_SR_MASKALL, MSGF_LEG_STATUS); + + /* Enable all legacy interrupts */ + nwl_bridge_writel(pcie, MSGF_LEG_SR_MASKALL, MSGF_LEG_MASK); + + /* Enable the bridge config interrupt */ + nwl_bridge_writel(pcie, nwl_bridge_readl(pcie, BRCFG_INTERRUPT) | + BRCFG_INTERRUPT_MASK, BRCFG_INTERRUPT); + + return 0; +} + +static int nwl_pcie_parse_dt(struct nwl_pcie *pcie, + struct platform_device *pdev) +{ + struct device_node *node = pcie->dev->of_node; + struct resource *res; + const char *type; + + /* Check for device type */ + type = of_get_property(node, "device_type", NULL); + if (!type || strcmp(type, "pci")) { + dev_err(pcie->dev, "invalid \"device_type\" %s\n", type); + return -EINVAL; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "breg"); + pcie->breg_base = devm_ioremap_resource(pcie->dev, res); + if (IS_ERR(pcie->breg_base)) + return PTR_ERR(pcie->breg_base); + pcie->phys_breg_base = res->start; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pcireg"); + pcie->pcireg_base = devm_ioremap_resource(pcie->dev, res); + if (IS_ERR(pcie->pcireg_base)) + return PTR_ERR(pcie->pcireg_base); + pcie->phys_pcie_reg_base = res->start; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "cfg"); + pcie->ecam_base = devm_ioremap_resource(pcie->dev, res); + if (IS_ERR(pcie->ecam_base)) + return PTR_ERR(pcie->ecam_base); + pcie->phys_ecam_base = res->start; + + /* Get intx IRQ number */ + pcie->irq_intx = platform_get_irq_byname(pdev, "intx"); + if (pcie->irq_intx < 0) { + dev_err(&pdev->dev, "failed to get intx IRQ %d\n", + pcie->irq_intx); + return -EINVAL; + } + + irq_set_chained_handler_and_data(pcie->irq_intx, + nwl_pcie_leg_handler, pcie); + + return 0; +} + +static const struct of_device_id nwl_pcie_of_match[] = { + { .compatible = "xlnx,nwl-pcie-2.11", }, + {} +}; + +static int nwl_pcie_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct nwl_pcie *pcie; + struct pci_bus *bus; + struct pci_bus *child; + int err; + resource_size_t iobase = 0; + LIST_HEAD(res); + + pcie = devm_kzalloc(&pdev->dev, sizeof(*pcie), GFP_KERNEL); + if (!pcie) + return -ENOMEM; + + pcie->dev = &pdev->dev; + pcie->ecam_value = NWL_ECAM_VALUE_DEFAULT; + + err = nwl_pcie_parse_dt(pcie, pdev); + if (err) { + dev_err(pcie->dev, "Parsing DT failed\n"); + return err; + } + + err = nwl_pcie_bridge_init(pcie); + if (err) { + dev_err(pcie->dev, "HW Initalization failed\n"); + return err; + } + + err = of_pci_get_host_bridge_resources(node, 0, 0xff, &res, &iobase); + if (err) { + pr_err("Getting bridge resources failed\n"); + return err; + } + + err = nwl_pcie_init_irq_domain(pcie); + if (err) { + dev_err(pcie->dev, "Failed creating IRQ Domain\n"); + return err; + } + + bus = pci_create_root_bus(&pdev->dev, pcie->root_busno, + &nwl_pcie_ops, pcie, &res); + if (!bus) + return -ENOMEM; + + if (IS_ENABLED(CONFIG_PCI_MSI)) { + err = nwl_pcie_enable_msi(pcie, bus); + if (err < 0) { + dev_err(&pdev->dev, + "failed to enable MSI support: %d\n", err); + return err; + } + } + pci_scan_child_bus(bus); + pci_assign_unassigned_bus_resources(bus); + list_for_each_entry(child, &bus->children, node) + pcie_bus_configure_settings(child); + pci_bus_add_devices(bus); + platform_set_drvdata(pdev, pcie); + return 0; +} + +static int nwl_pcie_remove(struct platform_device *pdev) +{ + struct nwl_pcie *pcie = platform_get_drvdata(pdev); + + nwl_pcie_free_irq_domain(pcie); + platform_set_drvdata(pdev, NULL); + return 0; +} + +static struct platform_driver nwl_pcie_driver = { + .driver = { + .name = "nwl-pcie", + .of_match_table = nwl_pcie_of_match, + }, + .probe = nwl_pcie_probe, + .remove = nwl_pcie_remove, +}; +module_platform_driver(nwl_pcie_driver); + +MODULE_AUTHOR("Xilinx, Inc"); +MODULE_DESCRIPTION("NWL PCIe driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pci/host/pcie-xilinx.c b/drivers/pci/host/pcie-xilinx.c index 4cfa463..65f0fe0 100644 --- a/drivers/pci/host/pcie-xilinx.c +++ b/drivers/pci/host/pcie-xilinx.c @@ -94,9 +94,6 @@ /* Number of MSI IRQs */ #define XILINX_NUM_MSI_IRQS 128 -/* Number of Memory Resources */ -#define XILINX_MAX_NUM_RESOURCES 3 - /** * struct xilinx_pcie_port - PCIe port information * @reg_base: IO Mapped Register Base @@ -105,7 +102,6 @@ * @root_busno: Root Bus number * @dev: Device pointer * @irq_domain: IRQ domain pointer - * @bus_range: Bus range * @resources: Bus Resources */ struct xilinx_pcie_port { @@ -115,17 +111,11 @@ struct xilinx_pcie_port { u8 root_busno; struct device *dev; struct irq_domain *irq_domain; - struct resource bus_range; struct list_head resources; }; static DECLARE_BITMAP(msi_irq_in_use, XILINX_NUM_MSI_IRQS); -static inline struct xilinx_pcie_port *sys_to_pcie(struct pci_sys_data *sys) -{ - return sys->private_data; -} - static inline u32 pcie_read(struct xilinx_pcie_port *port, u32 reg) { return readl(port->reg_base + reg); @@ -167,7 +157,7 @@ static void xilinx_pcie_clear_err_interrupts(struct xilinx_pcie_port *port) */ static bool xilinx_pcie_valid_device(struct pci_bus *bus, unsigned int devfn) { - struct xilinx_pcie_port *port = sys_to_pcie(bus->sysdata); + struct xilinx_pcie_port *port = bus->sysdata; /* Check if link is up when trying to access downstream ports */ if (bus->number != port->root_busno) @@ -200,7 +190,7 @@ static bool xilinx_pcie_valid_device(struct pci_bus *bus, unsigned int devfn) static void __iomem *xilinx_pcie_map_bus(struct pci_bus *bus, unsigned int devfn, int where) { - struct xilinx_pcie_port *port = sys_to_pcie(bus->sysdata); + struct xilinx_pcie_port *port = bus->sysdata; int relbus; if (!xilinx_pcie_valid_device(bus, devfn)) @@ -232,7 +222,7 @@ static void xilinx_pcie_destroy_msi(unsigned int irq) if (!test_bit(irq, msi_irq_in_use)) { msi = irq_get_msi_desc(irq); - port = sys_to_pcie(msi_desc_to_pci_sysdata(msi)); + port = msi_desc_to_pci_sysdata(msi); dev_err(port->dev, "Trying to free unused MSI#%d\n", irq); } else { clear_bit(irq, msi_irq_in_use); @@ -281,7 +271,7 @@ static int xilinx_pcie_msi_setup_irq(struct msi_controller *chip, struct pci_dev *pdev, struct msi_desc *desc) { - struct xilinx_pcie_port *port = sys_to_pcie(pdev->bus->sysdata); + struct xilinx_pcie_port *port = pdev->bus->sysdata; unsigned int irq; int hwirq; struct msi_msg msg; @@ -618,138 +608,6 @@ static void xilinx_pcie_init_port(struct xilinx_pcie_port *port) } /** - * xilinx_pcie_setup - Setup memory resources - * @nr: Bus number - * @sys: Per controller structure - * - * Return: '1' on success and error value on failure - */ -static int xilinx_pcie_setup(int nr, struct pci_sys_data *sys) -{ - struct xilinx_pcie_port *port = sys_to_pcie(sys); - - list_splice_init(&port->resources, &sys->resources); - - return 1; -} - -/** - * xilinx_pcie_scan_bus - Scan PCIe bus for devices - * @nr: Bus number - * @sys: Per controller structure - * - * Return: Valid Bus pointer on success and NULL on failure - */ -static struct pci_bus *xilinx_pcie_scan_bus(int nr, struct pci_sys_data *sys) -{ - struct xilinx_pcie_port *port = sys_to_pcie(sys); - struct pci_bus *bus; - - port->root_busno = sys->busnr; - - if (IS_ENABLED(CONFIG_PCI_MSI)) - bus = pci_scan_root_bus_msi(port->dev, sys->busnr, - &xilinx_pcie_ops, sys, - &sys->resources, - &xilinx_pcie_msi_chip); - else - bus = pci_scan_root_bus(port->dev, sys->busnr, - &xilinx_pcie_ops, sys, &sys->resources); - return bus; -} - -/** - * xilinx_pcie_parse_and_add_res - Add resources by parsing ranges - * @port: PCIe port information - * - * Return: '0' on success and error value on failure - */ -static int xilinx_pcie_parse_and_add_res(struct xilinx_pcie_port *port) -{ - struct device *dev = port->dev; - struct device_node *node = dev->of_node; - struct resource *mem; - resource_size_t offset; - struct of_pci_range_parser parser; - struct of_pci_range range; - struct resource_entry *win; - int err = 0, mem_resno = 0; - - /* Get the ranges */ - if (of_pci_range_parser_init(&parser, node)) { - dev_err(dev, "missing \"ranges\" property\n"); - return -EINVAL; - } - - /* Parse the ranges and add the resources found to the list */ - for_each_of_pci_range(&parser, &range) { - - if (mem_resno >= XILINX_MAX_NUM_RESOURCES) { - dev_err(dev, "Maximum memory resources exceeded\n"); - return -EINVAL; - } - - mem = devm_kmalloc(dev, sizeof(*mem), GFP_KERNEL); - if (!mem) { - err = -ENOMEM; - goto free_resources; - } - - of_pci_range_to_resource(&range, node, mem); - - switch (mem->flags & IORESOURCE_TYPE_BITS) { - case IORESOURCE_MEM: - offset = range.cpu_addr - range.pci_addr; - mem_resno++; - break; - default: - err = -EINVAL; - break; - } - - if (err < 0) { - dev_warn(dev, "Invalid resource found %pR\n", mem); - continue; - } - - err = request_resource(&iomem_resource, mem); - if (err) - goto free_resources; - - pci_add_resource_offset(&port->resources, mem, offset); - } - - /* Get the bus range */ - if (of_pci_parse_bus_range(node, &port->bus_range)) { - u32 val = pcie_read(port, XILINX_PCIE_REG_BIR); - u8 last; - - last = (val & XILINX_PCIE_BIR_ECAM_SZ_MASK) >> - XILINX_PCIE_BIR_ECAM_SZ_SHIFT; - - port->bus_range = (struct resource) { - .name = node->name, - .start = 0, - .end = last, - .flags = IORESOURCE_BUS, - }; - } - - /* Register bus resource */ - pci_add_resource(&port->resources, &port->bus_range); - - return 0; - -free_resources: - release_child_resources(&iomem_resource); - resource_list_for_each_entry(win, &port->resources) - devm_kfree(dev, win->res); - pci_free_resource_list(&port->resources); - - return err; -} - -/** * xilinx_pcie_parse_dt - Parse Device tree * @port: PCIe port information * @@ -800,9 +658,12 @@ static int xilinx_pcie_parse_dt(struct xilinx_pcie_port *port) static int xilinx_pcie_probe(struct platform_device *pdev) { struct xilinx_pcie_port *port; - struct hw_pci hw; struct device *dev = &pdev->dev; + struct pci_bus *bus; + int err; + resource_size_t iobase = 0; + LIST_HEAD(res); if (!dev->of_node) return -ENODEV; @@ -827,34 +688,28 @@ static int xilinx_pcie_probe(struct platform_device *pdev) return err; } - /* - * Parse PCI ranges, configuration bus range and - * request their resources - */ - INIT_LIST_HEAD(&port->resources); - err = xilinx_pcie_parse_and_add_res(port); + err = of_pci_get_host_bridge_resources(dev->of_node, 0, 0xff, &res, + &iobase); if (err) { - dev_err(dev, "Failed adding resources\n"); + dev_err(dev, "Getting bridge resources failed\n"); return err; } - - platform_set_drvdata(pdev, port); - - /* Register the device */ - memset(&hw, 0, sizeof(hw)); - hw = (struct hw_pci) { - .nr_controllers = 1, - .private_data = (void **)&port, - .setup = xilinx_pcie_setup, - .map_irq = of_irq_parse_and_map_pci, - .scan = xilinx_pcie_scan_bus, - .ops = &xilinx_pcie_ops, - }; + bus = pci_create_root_bus(&pdev->dev, 0, + &xilinx_pcie_ops, port, &res); + if (!bus) + return -ENOMEM; #ifdef CONFIG_PCI_MSI xilinx_pcie_msi_chip.dev = port->dev; + bus->msi = &xilinx_pcie_msi_chip; #endif - pci_common_init_dev(dev, &hw); + pci_scan_child_bus(bus); + pci_assign_unassigned_bus_resources(bus); +#ifndef CONFIG_MICROBLAZE + pci_fixup_irqs(pci_common_swizzle, of_irq_parse_and_map_pci); +#endif + pci_bus_add_devices(bus); + platform_set_drvdata(pdev, port); return 0; } diff --git a/drivers/pci/iov.c b/drivers/pci/iov.c index 31f31d4..fe4bd0a 100644 --- a/drivers/pci/iov.c +++ b/drivers/pci/iov.c @@ -387,10 +387,6 @@ static int sriov_init(struct pci_dev *dev, int pos) struct resource *res; struct pci_dev *pdev; - if (pci_pcie_type(dev) != PCI_EXP_TYPE_RC_END && - pci_pcie_type(dev) != PCI_EXP_TYPE_ENDPOINT) - return -ENODEV; - pci_read_config_word(dev, pos + PCI_SRIOV_CTRL, &ctrl); if (ctrl & PCI_SRIOV_CTRL_VFE) { pci_write_config_word(dev, pos + PCI_SRIOV_CTRL, 0); diff --git a/drivers/pci/pci-label.c b/drivers/pci/pci-label.c index 0ae74d9..5135737 100644 --- a/drivers/pci/pci-label.c +++ b/drivers/pci/pci-label.c @@ -16,7 +16,7 @@ * the instance number and string from the type 41 record and exports * it to sysfs. * - * Please see http://linux.dell.com/wiki/index.php/Oss/libnetdevname for more + * Please see http://linux.dell.com/files/biosdevname/ for more * information. */ diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c index 95d9e7b..ed39c09 100644 --- a/drivers/pci/pci-sysfs.c +++ b/drivers/pci/pci-sysfs.c @@ -769,10 +769,12 @@ static ssize_t read_vpd_attr(struct file *filp, struct kobject *kobj, { struct pci_dev *dev = to_pci_dev(kobj_to_dev(kobj)); - if (off > bin_attr->size) - count = 0; - else if (count > bin_attr->size - off) - count = bin_attr->size - off; + if (bin_attr->size > 0) { + if (off > bin_attr->size) + count = 0; + else if (count > bin_attr->size - off) + count = bin_attr->size - off; + } return pci_read_vpd(dev, off, count, buf); } @@ -783,10 +785,12 @@ static ssize_t write_vpd_attr(struct file *filp, struct kobject *kobj, { struct pci_dev *dev = to_pci_dev(kobj_to_dev(kobj)); - if (off > bin_attr->size) - count = 0; - else if (count > bin_attr->size - off) - count = bin_attr->size - off; + if (bin_attr->size > 0) { + if (off > bin_attr->size) + count = 0; + else if (count > bin_attr->size - off) + count = bin_attr->size - off; + } return pci_write_vpd(dev, off, count, buf); } @@ -1319,7 +1323,7 @@ static int pci_create_capabilities_sysfs(struct pci_dev *dev) return -ENOMEM; sysfs_bin_attr_init(attr); - attr->size = dev->vpd->len; + attr->size = 0; attr->attr.name = "vpd"; attr->attr.mode = S_IRUSR | S_IWUSR; attr->read = read_vpd_attr; diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 602eb42..eaf4ffc 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -25,7 +25,6 @@ #include <linux/device.h> #include <linux/pm_runtime.h> #include <linux/pci_hotplug.h> -#include <asm-generic/pci-bridge.h> #include <asm/setup.h> #include <linux/aer.h> #include "pci.h" @@ -3386,18 +3385,6 @@ bool pci_check_and_unmask_intx(struct pci_dev *dev) } EXPORT_SYMBOL_GPL(pci_check_and_unmask_intx); -int pci_set_dma_max_seg_size(struct pci_dev *dev, unsigned int size) -{ - return dma_set_max_seg_size(&dev->dev, size); -} -EXPORT_SYMBOL(pci_set_dma_max_seg_size); - -int pci_set_dma_seg_boundary(struct pci_dev *dev, unsigned long mask) -{ - return dma_set_seg_boundary(&dev->dev, mask); -} -EXPORT_SYMBOL(pci_set_dma_seg_boundary); - /** * pci_wait_for_pending_transaction - waits for pending transaction * @dev: the PCI device to operate on @@ -3414,6 +3401,29 @@ int pci_wait_for_pending_transaction(struct pci_dev *dev) } EXPORT_SYMBOL(pci_wait_for_pending_transaction); +/* + * We should only need to wait 100ms after FLR, but some devices take longer. + * Wait for up to 1000ms for config space to return something other than -1. + * Intel IGD requires this when an LCD panel is attached. We read the 2nd + * dword because VFs don't implement the 1st dword. + */ +static void pci_flr_wait(struct pci_dev *dev) +{ + int i = 0; + u32 id; + + do { + msleep(100); + pci_read_config_dword(dev, PCI_COMMAND, &id); + } while (i++ < 10 && id == ~0); + + if (id == ~0) + dev_warn(&dev->dev, "Failed to return from FLR\n"); + else if (i > 1) + dev_info(&dev->dev, "Required additional %dms to return from FLR\n", + (i - 1) * 100); +} + static int pcie_flr(struct pci_dev *dev, int probe) { u32 cap; @@ -3429,7 +3439,7 @@ static int pcie_flr(struct pci_dev *dev, int probe) dev_err(&dev->dev, "timed out waiting for pending transaction; performing function level reset anyway\n"); pcie_capability_set_word(dev, PCI_EXP_DEVCTL, PCI_EXP_DEVCTL_BCR_FLR); - msleep(100); + pci_flr_wait(dev); return 0; } @@ -3459,7 +3469,7 @@ static int pci_af_flr(struct pci_dev *dev, int probe) dev_err(&dev->dev, "timed out waiting for pending transaction; performing AF function level reset anyway\n"); pci_write_config_byte(dev, pos + PCI_AF_CTRL, PCI_AF_CTRL_FLR); - msleep(100); + pci_flr_wait(dev); return 0; } diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index 9a1660f..d0fb934 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -97,21 +97,21 @@ static inline bool pci_has_subordinate(struct pci_dev *pci_dev) struct pci_vpd_ops { ssize_t (*read)(struct pci_dev *dev, loff_t pos, size_t count, void *buf); ssize_t (*write)(struct pci_dev *dev, loff_t pos, size_t count, const void *buf); - void (*release)(struct pci_dev *dev); }; struct pci_vpd { - unsigned int len; const struct pci_vpd_ops *ops; struct bin_attribute *attr; /* descriptor for sysfs VPD entry */ + struct mutex lock; + unsigned int len; + u16 flag; + u8 cap; + u8 busy:1; + u8 valid:1; }; -int pci_vpd_pci22_init(struct pci_dev *dev); -static inline void pci_vpd_release(struct pci_dev *dev) -{ - if (dev->vpd) - dev->vpd->ops->release(dev); -} +int pci_vpd_init(struct pci_dev *dev); +void pci_vpd_release(struct pci_dev *dev); /* PCI /proc functions */ #ifdef CONFIG_PROC_FS diff --git a/drivers/pci/pcie/Kconfig b/drivers/pci/pcie/Kconfig index e294713..72db7f4 100644 --- a/drivers/pci/pcie/Kconfig +++ b/drivers/pci/pcie/Kconfig @@ -44,6 +44,7 @@ config PCIEASPM /sys/module/pcie_aspm/parameters/policy When in doubt, say Y. + config PCIEASPM_DEBUG bool "Debug PCI Express ASPM" depends on PCIEASPM @@ -58,20 +59,20 @@ choice depends on PCIEASPM config PCIEASPM_DEFAULT - bool "BIOS default" + bool "BIOS default" depends on PCIEASPM help Use the BIOS defaults for PCI Express ASPM. config PCIEASPM_POWERSAVE - bool "Powersave" + bool "Powersave" depends on PCIEASPM help Enable PCI Express ASPM L0s and L1 where possible, even if the BIOS did not. config PCIEASPM_PERFORMANCE - bool "Performance" + bool "Performance" depends on PCIEASPM help Disable PCI Express ASPM L0s and L1, even if the BIOS enabled them. diff --git a/drivers/pci/pcie/aer/aer_inject.c b/drivers/pci/pcie/aer/aer_inject.c index 20db790..db553dc 100644 --- a/drivers/pci/pcie/aer/aer_inject.c +++ b/drivers/pci/pcie/aer/aer_inject.c @@ -25,6 +25,7 @@ #include <linux/fs.h> #include <linux/uaccess.h> #include <linux/stddef.h> +#include <linux/device.h> #include "aerdrv.h" /* Override the existing corrected and uncorrected error masks */ @@ -124,16 +125,13 @@ static struct pci_ops *__find_pci_bus_ops(struct pci_bus *bus) static struct pci_bus_ops *pci_bus_ops_pop(void) { unsigned long flags; - struct pci_bus_ops *bus_ops = NULL; + struct pci_bus_ops *bus_ops; spin_lock_irqsave(&inject_lock, flags); - if (list_empty(&pci_bus_ops_list)) - bus_ops = NULL; - else { - struct list_head *lh = pci_bus_ops_list.next; - list_del(lh); - bus_ops = list_entry(lh, struct pci_bus_ops, list); - } + bus_ops = list_first_entry_or_null(&pci_bus_ops_list, + struct pci_bus_ops, list); + if (bus_ops) + list_del(&bus_ops->list); spin_unlock_irqrestore(&inject_lock, flags); return bus_ops; } @@ -181,14 +179,16 @@ static u32 *find_pci_config_dword(struct aer_error *err, int where, return target; } -static int pci_read_aer(struct pci_bus *bus, unsigned int devfn, int where, - int size, u32 *val) +static int aer_inj_read_config(struct pci_bus *bus, unsigned int devfn, + int where, int size, u32 *val) { u32 *sim; struct aer_error *err; unsigned long flags; struct pci_ops *ops; + struct pci_ops *my_ops; int domain; + int rv; spin_lock_irqsave(&inject_lock, flags); if (size != sizeof(u32)) @@ -208,19 +208,32 @@ static int pci_read_aer(struct pci_bus *bus, unsigned int devfn, int where, } out: ops = __find_pci_bus_ops(bus); + /* + * pci_lock must already be held, so we can directly + * manipulate bus->ops. Many config access functions, + * including pci_generic_config_read() require the original + * bus->ops be installed to function, so temporarily put them + * back. + */ + my_ops = bus->ops; + bus->ops = ops; + rv = ops->read(bus, devfn, where, size, val); + bus->ops = my_ops; spin_unlock_irqrestore(&inject_lock, flags); - return ops->read(bus, devfn, where, size, val); + return rv; } -static int pci_write_aer(struct pci_bus *bus, unsigned int devfn, int where, - int size, u32 val) +static int aer_inj_write_config(struct pci_bus *bus, unsigned int devfn, + int where, int size, u32 val) { u32 *sim; struct aer_error *err; unsigned long flags; int rw1cs; struct pci_ops *ops; + struct pci_ops *my_ops; int domain; + int rv; spin_lock_irqsave(&inject_lock, flags); if (size != sizeof(u32)) @@ -243,13 +256,24 @@ static int pci_write_aer(struct pci_bus *bus, unsigned int devfn, int where, } out: ops = __find_pci_bus_ops(bus); + /* + * pci_lock must already be held, so we can directly + * manipulate bus->ops. Many config access functions, + * including pci_generic_config_write() require the original + * bus->ops be installed to function, so temporarily put them + * back. + */ + my_ops = bus->ops; + bus->ops = ops; + rv = ops->write(bus, devfn, where, size, val); + bus->ops = my_ops; spin_unlock_irqrestore(&inject_lock, flags); - return ops->write(bus, devfn, where, size, val); + return rv; } -static struct pci_ops pci_ops_aer = { - .read = pci_read_aer, - .write = pci_write_aer, +static struct pci_ops aer_inj_pci_ops = { + .read = aer_inj_read_config, + .write = aer_inj_write_config, }; static void pci_bus_ops_init(struct pci_bus_ops *bus_ops, @@ -270,9 +294,9 @@ static int pci_bus_set_aer_ops(struct pci_bus *bus) bus_ops = kmalloc(sizeof(*bus_ops), GFP_KERNEL); if (!bus_ops) return -ENOMEM; - ops = pci_bus_set_ops(bus, &pci_ops_aer); + ops = pci_bus_set_ops(bus, &aer_inj_pci_ops); spin_lock_irqsave(&inject_lock, flags); - if (ops == &pci_ops_aer) + if (ops == &aer_inj_pci_ops) goto out; pci_bus_ops_init(bus_ops, bus, ops); list_add(&bus_ops->list, &pci_bus_ops_list); @@ -334,13 +358,15 @@ static int aer_inject(struct aer_error_inj *einj) return -ENODEV; rpdev = pcie_find_root_port(dev); if (!rpdev) { + dev_err(&dev->dev, "aer_inject: Root port not found\n"); ret = -ENODEV; goto out_put; } pos_cap_err = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ERR); if (!pos_cap_err) { - ret = -EPERM; + dev_err(&dev->dev, "aer_inject: Device doesn't support AER\n"); + ret = -EPROTONOSUPPORT; goto out_put; } pci_read_config_dword(dev, pos_cap_err + PCI_ERR_UNCOR_SEVER, &sever); @@ -350,7 +376,9 @@ static int aer_inject(struct aer_error_inj *einj) rp_pos_cap_err = pci_find_ext_capability(rpdev, PCI_EXT_CAP_ID_ERR); if (!rp_pos_cap_err) { - ret = -EPERM; + dev_err(&rpdev->dev, + "aer_inject: Root port doesn't support AER\n"); + ret = -EPROTONOSUPPORT; goto out_put; } @@ -397,14 +425,16 @@ static int aer_inject(struct aer_error_inj *einj) if (!aer_mask_override && einj->cor_status && !(einj->cor_status & ~cor_mask)) { ret = -EINVAL; - printk(KERN_WARNING "The correctable error(s) is masked by device\n"); + dev_warn(&dev->dev, + "aer_inject: The correctable error(s) is masked by device\n"); spin_unlock_irqrestore(&inject_lock, flags); goto out_put; } if (!aer_mask_override && einj->uncor_status && !(einj->uncor_status & ~uncor_mask)) { ret = -EINVAL; - printk(KERN_WARNING "The uncorrectable error(s) is masked by device\n"); + dev_warn(&dev->dev, + "aer_inject: The uncorrectable error(s) is masked by device\n"); spin_unlock_irqrestore(&inject_lock, flags); goto out_put; } @@ -457,13 +487,19 @@ static int aer_inject(struct aer_error_inj *einj) if (find_aer_device(rpdev, &edev)) { if (!get_service_data(edev)) { - printk(KERN_WARNING "AER service is not initialized\n"); - ret = -EINVAL; + dev_warn(&edev->device, + "aer_inject: AER service is not initialized\n"); + ret = -EPROTONOSUPPORT; goto out_put; } + dev_info(&edev->device, + "aer_inject: Injecting errors %08x/%08x into device %s\n", + einj->cor_status, einj->uncor_status, pci_name(dev)); aer_irq(-1, edev); - } else - ret = -EINVAL; + } else { + dev_err(&rpdev->dev, "aer_inject: AER device not found\n"); + ret = -ENODEV; + } out_put: kfree(err_alloc); kfree(rperr_alloc); diff --git a/drivers/pci/pcie/pme.c b/drivers/pci/pcie/pme.c index 63fc639..1ae4c73 100644 --- a/drivers/pci/pcie/pme.c +++ b/drivers/pci/pcie/pme.c @@ -396,7 +396,7 @@ static int pcie_pme_suspend(struct pcie_device *srv) { struct pcie_pme_service_data *data = get_service_data(srv); struct pci_dev *port = srv->port; - bool wakeup; + bool wakeup, wake_irq_enabled = false; int ret; if (device_may_wakeup(&port->dev)) { @@ -409,11 +409,12 @@ static int pcie_pme_suspend(struct pcie_device *srv) spin_lock_irq(&data->lock); if (wakeup) { ret = enable_irq_wake(srv->irq); - data->suspend_level = PME_SUSPEND_WAKEUP; + if (ret == 0) { + data->suspend_level = PME_SUSPEND_WAKEUP; + wake_irq_enabled = true; + } } - if (!wakeup || ret) { - struct pci_dev *port = srv->port; - + if (!wake_irq_enabled) { pcie_pme_interrupt_enable(port, false); pcie_clear_root_pme_status(port); data->suspend_level = PME_SUSPEND_NOIRQ; diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index 6d7ab9b..b234499 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -15,7 +15,6 @@ #include <linux/pci-aspm.h> #include <linux/aer.h> #include <linux/acpi.h> -#include <asm-generic/pci-bridge.h> #include "pci.h" #define CARDBUS_LATENCY_TIMER 176 /* secondary latency timer */ @@ -758,6 +757,12 @@ add_dev: pcibios_add_bus(child); + if (child->ops->add_bus) { + ret = child->ops->add_bus(child); + if (WARN_ON(ret < 0)) + dev_err(&child->dev, "failed to add bus: %d\n", ret); + } + /* Create legacy_io and legacy_mem files for this bus */ pci_create_legacy_files(child); @@ -1608,7 +1613,7 @@ static void pci_init_capabilities(struct pci_dev *dev) pci_pm_init(dev); /* Vital Product Data */ - pci_vpd_pci22_init(dev); + pci_vpd_init(dev); /* Alternative Routing-ID Forwarding */ pci_configure_ari(dev); @@ -1803,6 +1808,13 @@ static int only_one_child(struct pci_bus *bus) return 0; if (pci_pcie_type(parent) == PCI_EXP_TYPE_ROOT_PORT) return 1; + + /* + * PCIe downstream ports are bridges that normally lead to only a + * device 0, but if PCI_SCAN_ALL_PCIE_DEVS is set, scan all + * possible devices, not just device 0. See PCIe spec r3.0, + * sec 7.3.1. + */ if (parent->has_secondary_link && !pci_has_flag(PCI_SCAN_ALL_PCIE_DEVS)) return 1; diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index 0575a1e..8e67802 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -438,7 +438,7 @@ static void quirk_amd_nl_class(struct pci_dev *pdev) u32 class = pdev->class; /* Use "USB Device (not host controller)" class */ - pdev->class = (PCI_CLASS_SERIAL_USB << 8) | 0xfe; + pdev->class = PCI_CLASS_SERIAL_USB_DEVICE; dev_info(&pdev->dev, "PCI class overridden (%#08x -> %#08x) so dwc3 driver can claim this instead of xhci\n", class, pdev->class); } @@ -2135,6 +2135,35 @@ static void quirk_via_cx700_pci_parking_caching(struct pci_dev *dev) DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_VIA, 0x324e, quirk_via_cx700_pci_parking_caching); /* + * If a device follows the VPD format spec, the PCI core will not read or + * write past the VPD End Tag. But some vendors do not follow the VPD + * format spec, so we can't tell how much data is safe to access. Devices + * may behave unpredictably if we access too much. Blacklist these devices + * so we don't touch VPD at all. + */ +static void quirk_blacklist_vpd(struct pci_dev *dev) +{ + if (dev->vpd) { + dev->vpd->len = 0; + dev_warn(&dev->dev, FW_BUG "VPD access disabled\n"); + } +} + +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_LSI_LOGIC, 0x0060, quirk_blacklist_vpd); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_LSI_LOGIC, 0x007c, quirk_blacklist_vpd); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_LSI_LOGIC, 0x0413, quirk_blacklist_vpd); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_LSI_LOGIC, 0x0078, quirk_blacklist_vpd); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_LSI_LOGIC, 0x0079, quirk_blacklist_vpd); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_LSI_LOGIC, 0x0073, quirk_blacklist_vpd); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_LSI_LOGIC, 0x0071, quirk_blacklist_vpd); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_LSI_LOGIC, 0x005b, quirk_blacklist_vpd); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_LSI_LOGIC, 0x002f, quirk_blacklist_vpd); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_LSI_LOGIC, 0x005d, quirk_blacklist_vpd); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_LSI_LOGIC, 0x005f, quirk_blacklist_vpd); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_ATTANSIC, PCI_ANY_ID, + quirk_blacklist_vpd); + +/* * For Broadcom 5706, 5708, 5709 rev. A nics, any read beyond the * VPD end tag will hang the device. This problem was initially * observed when a vpd entry was created in sysfs @@ -3832,6 +3861,19 @@ static int pci_quirk_amd_sb_acs(struct pci_dev *dev, u16 acs_flags) #endif } +static int pci_quirk_cavium_acs(struct pci_dev *dev, u16 acs_flags) +{ + /* + * Cavium devices matching this quirk do not perform peer-to-peer + * with other functions, allowing masking out these bits as if they + * were unimplemented in the ACS capability. + */ + acs_flags &= ~(PCI_ACS_SV | PCI_ACS_TB | PCI_ACS_RR | + PCI_ACS_CR | PCI_ACS_UF | PCI_ACS_DT); + + return acs_flags ? 0 : 1; +} + /* * Many Intel PCH root ports do provide ACS-like features to disable peer * transactions and validate bus numbers in requests, but do not provide an @@ -3984,6 +4026,8 @@ static const struct pci_dev_acs_enabled { { PCI_VENDOR_ID_INTEL, PCI_ANY_ID, pci_quirk_intel_pch_acs }, { 0x19a2, 0x710, pci_quirk_mf_endpoint_acs }, /* Emulex BE3-R */ { 0x10df, 0x720, pci_quirk_mf_endpoint_acs }, /* Emulex Skyhawk-R */ + /* Cavium ThunderX */ + { PCI_VENDOR_ID_CAVIUM, PCI_ANY_ID, pci_quirk_cavium_acs }, { 0 } }; diff --git a/drivers/pci/remove.c b/drivers/pci/remove.c index 8a280e9..d35c7fc 100644 --- a/drivers/pci/remove.c +++ b/drivers/pci/remove.c @@ -54,6 +54,10 @@ void pci_remove_bus(struct pci_bus *bus) pci_bus_release_busn_res(bus); up_write(&pci_bus_sem); pci_remove_legacy_files(bus); + + if (bus->ops->remove_bus) + bus->ops->remove_bus(bus); + pcibios_remove_bus(bus); device_unregister(&bus->dev); } diff --git a/drivers/pci/setup-bus.c b/drivers/pci/setup-bus.c index 7796d0a..55641a3 100644 --- a/drivers/pci/setup-bus.c +++ b/drivers/pci/setup-bus.c @@ -25,7 +25,6 @@ #include <linux/ioport.h> #include <linux/cache.h> #include <linux/slab.h> -#include <asm-generic/pci-bridge.h> #include "pci.h" unsigned int pci_flags; |