diff --git a/Documentation/devicetree/bindings/pci/arm,pcie-xr3.txt b/Documentation/devicetree/bindings/pci/arm,pcie-xr3.txt new file mode 100644 index 000000000000..e2ddb57f7a47 --- /dev/null +++ b/Documentation/devicetree/bindings/pci/arm,pcie-xr3.txt @@ -0,0 +1,57 @@ +PLDA XpressRICH3-AXI PCIe controller. + +These bindings describe the host controller as implemented on ARM's Juno +platforms. Because IP integration has added additional registers to the +original IP it is expected that other vendors will have different bindings +if not different drivers. + +Required properties: +- compatible: Should be "arm,pcie-xr3" to identify the controller +- device_type: Must be "pci" +- reg: A list of physical base addresses and lengths. There must be 3 + entries: + - PLDA's XpressRICH3-AXI configuration registers + - ARM's reset registers + - Configuration space (ECAM compliant) +- bus-range: Range of bus numbers associated with this controller +- linux,pci-domain: PCI domain number (ACPI's segment) associated with + this controller. +- #address-cells: Address representation for root ports (must be 3), + in accordance with the ePAPR specification. +- #size-cells: Size representation for root ports (must be 2) +- ranges: Describes the translation of addresses for standard PCI + regions. Please consult the standard device tree bindings for PCI + host bridges on how to encode the ranges. +- #interrupt-cells: Size representation for legacy interrupts (must be 1) +- interrupt-map-mask and +- interrupt-map: Standard PCI IRQ mapping properties. Please refer to the + standard PCI bus binding document for a more detailed explanation + +Optional properties: +- msi-parent: Handle to an MSI controller that will be used to request + allocation of MSI interrupts. + +Example: + + pcie-controller@30000000 { + compatible = "arm,pcie-xr3"; + device_type = "pci"; + reg = <0 0x7ff30000 0 0x1000 /* XR3 config registers */ + 0 0x7ff20000 0 0x10000 /* XR3 reset registers */ + 0 0x40000000 0 0x10000000>; /* ECAM config space */ + bus-range = <0 255>; + linux,pci-domain = <0>; + #address-cells = <3>; + #size-cells = <2>; + ranges = <0x01000000 0x00 0x5ff00000 0x00 0x5ff00000 0x0 0x00100000 + 0x02000000 0x00 0x50000000 0x00 0x50000000 0x0 0x0f000000 + 0x42000000 0x40 0x00000000 0x40 0x00000000 0x0 0x80000000 + 0x02000000 0x40 0x80000000 0x40 0x80000000 0x0 0x80000000>; + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 0 7>; + interrupt-map = <0 0 0 1 &gic 0 0 0 136 4 + 0 0 0 2 &gic 0 0 0 137 4 + 0 0 0 3 &gic 0 0 0 138 4 + 0 0 0 4 &gic 0 0 0 139 4>; + msi-parent = <&v2m_0>; + }; diff --git a/drivers/pci/host/Kconfig b/drivers/pci/host/Kconfig index 7b892a9cc4fc..c073faebb8ec 100644 --- a/drivers/pci/host/Kconfig +++ b/drivers/pci/host/Kconfig @@ -106,4 +106,10 @@ config PCI_VERSATILE bool "ARM Versatile PB PCI controller" depends on ARCH_VERSATILE +config PCI_HOST_XR3 + bool "XpressRICH 3 PCI Host Bridge" + depends on PCI && ARM64 + help + XpressRICH3-AXI PCI Host Bridge + endmenu diff --git a/drivers/pci/host/Makefile b/drivers/pci/host/Makefile index e61d91c92bf1..bf5e8d62e2e7 100644 --- a/drivers/pci/host/Makefile +++ b/drivers/pci/host/Makefile @@ -13,3 +13,5 @@ obj-$(CONFIG_PCIE_XILINX) += pcie-xilinx.o obj-$(CONFIG_PCI_XGENE) += pci-xgene.o obj-$(CONFIG_PCI_LAYERSCAPE) += pci-layerscape.o obj-$(CONFIG_PCI_VERSATILE) += pci-versatile.o +obj-$(CONFIG_PCI_HOST_XR3) += pci-xr3.o + diff --git a/drivers/pci/host/pci-xr3.c b/drivers/pci/host/pci-xr3.c new file mode 100644 index 000000000000..77f30cf67a7f --- /dev/null +++ b/drivers/pci/host/pci-xr3.c @@ -0,0 +1,340 @@ +/* + * XpressRICH3-AXI PCIe Host Bridge Driver. + * + * Copyright (C) 2012-2013 ARM Ltd. + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include + +#include + +#include "pci-xr3.h" + +struct xr3pci_port { + void __iomem *base; + void __iomem *reset; + void __iomem *ecam; + struct resource ecam_space; +#ifdef CONFIG_PCI_MSI + struct resource msi_res; +#endif +}; + +void __iomem *xr3pci_map_bus(struct pci_bus *bus, unsigned int devfn, int where) +{ + struct xr3pci_port *pp = bus->sysdata; + + return pp->ecam + XR3PCI_ECAM_OFFSET(bus->number, devfn, where); +} + +struct pci_ops xr3pci_ops = { + .map_bus = xr3pci_map_bus, + .read = pci_generic_config_read, + .write = pci_generic_config_write, +}; + +static int xr3pci_enable_device(struct xr3pci_port *pp) +{ + u32 val; + int timeout = 200; + + /* add credits */ + writel(0x00f0b818, pp->base + XR3PCI_VIRTCHAN_CREDITS); + writel(0x1, pp->base + XR3PCI_VIRTCHAN_CREDITS + 4); + + /* allow ECRC */ + writel(0x6006, pp->base + XR3PCI_PEX_SPC2); + + writel(JUNO_RESET_CTRL_PHY | JUNO_RESET_CTRL_RC, + pp->reset + JUNO_RESET_CTRL); + do { + msleep(1); + val = readl(pp->reset + JUNO_RESET_STATUS); + } while (--timeout && + (val & JUNO_RESET_STATUS_MASK) != JUNO_RESET_STATUS_MASK); + + if (!timeout) { + pr_err("Unable to bring " DEVICE_NAME " out of reset"); + return -EAGAIN; + } + + msleep(20); + timeout = 20; + do { + msleep(1); + val = readl(pp->base + XR3PCI_BASIC_STATUS); + } while (--timeout && !(val & XR3PCI_BS_LINK_MASK)); + + if (!(val & XR3PCI_BS_LINK_MASK)) { + pr_warn(DEVICE_NAME ": No link negotiated\n"); + return -EIO; + } + + pr_info(DEVICE_NAME " %dx link negotiated (gen %d), maxpayload %d, maxreqsize %d\n", + val & XR3PCI_BS_LINK_MASK, (val & XR3PCI_BS_GEN_MASK) >> 8, + 2 << (7 + ((val & XR3PCI_BS_NEG_PAYLOAD_MASK) << 24)), + 2 << (7 + ((val & XR3PCI_BS_NEG_REQSIZE_MASK) >> 28))); + + return 0; +} + +static void xr3pci_update_atr_entry(void __iomem *base, + resource_size_t src_addr, resource_size_t trsl_addr, + int trsl_param, int window_size) +{ + /* bit 0: enable entry, bits 1-6: ATR window size (2^window_size + 1) */ + writel(src_addr | (window_size << 1) | 0x1, base + XR3PCI_ATR_SRC_ADDR_LOW); + writel(trsl_addr, base + XR3PCI_ATR_TRSL_ADDR_LOW); + +#ifdef CONFIG_PHYS_ADDR_T_64BIT + writel(src_addr >> 32, base + XR3PCI_ATR_SRC_ADDR_HIGH); + writel(trsl_addr >> 32, base + XR3PCI_ATR_TRSL_ADDR_HIGH); +#endif + + writel(trsl_param, base + XR3PCI_ATR_TRSL_PARAM); +} + +static int xr3pci_setup_atr(struct xr3pci_port *pp, struct device *dev, + struct list_head *resources, resource_size_t io_base) +{ + int window_size; + struct resource_entry *window; + struct resource *res; + resource_size_t offset; + void __iomem *table_base; + + /* Address translation from PCIe to CPU */ + table_base = pp->base + XR3PCI_ATR_PCIE_WIN0; +#ifdef CONFIG_PCI_MSI + /* map the MSI resources as accessible device from PCIe transactions */ + window_size = ilog2(resource_size(&pp->msi_res)) - 1; + xr3pci_update_atr_entry(table_base, pp->msi_res.start, pp->msi_res.start, + XR3PCI_ATR_TRSLID_AXIDEVICE, window_size); + table_base += XR3PCI_ATR_TABLE_SIZE; +#endif + /* 1:1 mapping for inbound PCIe transactions to memory */ + xr3pci_update_atr_entry(table_base, 0x80000000, 0x80000000, + XR3PCI_ATR_TRSLID_AXIMEMORY, 0x1e); + table_base += XR3PCI_ATR_TABLE_SIZE; + xr3pci_update_atr_entry(table_base, 0x880000000, 0x880000000, + XR3PCI_ATR_TRSLID_AXIMEMORY, 0x1f); + + /* Address translation from CPU to PCIe */ + table_base = pp->base + XR3PCI_ATR_AXI4_SLV0; + /* map ECAM space to bus configuration interface */ + window_size = ilog2(resource_size(&pp->ecam_space)) - 1; + xr3pci_update_atr_entry(pp->base + XR3PCI_ATR_AXI4_SLV0, + pp->ecam_space.start, 0, + XR3PCI_ATR_TRSLID_PCIE_CONF, window_size); + table_base += XR3PCI_ATR_TABLE_SIZE; + + resource_list_for_each_entry(window, resources) { + res = window->res; + offset = window->offset; + window_size = ilog2(resource_size(res)) - 1; + + if (resource_type(res) == IORESOURCE_MEM) { + if (devm_request_resource(dev, &iomem_resource, res)) { + dev_info(dev, "failed to request MEM resource %pR\n", res); + } else { + xr3pci_update_atr_entry(table_base, res->start, + res->start - offset, + XR3PCI_ATR_TRSLID_PCIE_MEMORY, + window_size); + } + } else if (resource_type(res) == IORESOURCE_IO) { + pci_remap_iospace(res, res->start + io_base); + if (devm_request_resource(dev, &ioport_resource, res)) { + dev_info(dev, "failed to request IO resource %pR\n", res); + } else { + xr3pci_update_atr_entry(table_base, + res->start + io_base, + res->start - offset, + XR3PCI_ATR_TRSLID_PCIE_IO, + window_size); + } + } + table_base += XR3PCI_ATR_TABLE_SIZE; + } + + return 0; +} + +static int xr3pci_setup_int(struct xr3pci_port *pp) +{ + /* Enable IRQs for MSIs and legacy interrupts */ + writel(~(XR3PCI_INT_MSI | XR3PCI_INT_INTx), + pp->base + XR3PCI_LOCAL_INT_MASK); + + return 0; +} + +static int xr3pci_get_resources(struct xr3pci_port *pp, struct device *dev) +{ + int err; + struct resource res; + struct device_node *np = dev->of_node; + + err = of_address_to_resource(np, 0, &res); + if (err) { + dev_err(dev, "Failed to find configuration registers\n"); + return err; + } + pp->base = devm_ioremap_resource(dev, &res); + if (IS_ERR(pp->base)) + return PTR_ERR(pp->base); + + err = of_address_to_resource(np, 1, &res); + if (err) { + dev_err(dev, "Failed to find reset registers\n"); + return err; + } + pp->reset = devm_ioremap_resource(dev, &res); + if (IS_ERR(pp->reset)) + return PTR_ERR(pp->reset); + + err = of_address_to_resource(np, 2, &pp->ecam_space); + if (err) { + dev_err(dev, "Failed to find ECAM configuration space\n"); + return -EINVAL; + } + pp->ecam = devm_ioremap_resource(dev, &pp->ecam_space); + if (IS_ERR(pp->ecam)) + return PTR_ERR(pp->ecam); + + return 0; +} + +static int xr3pci_setup(struct xr3pci_port *pp, struct device *dev, + struct list_head *resources, resource_size_t io_base) +{ + int err; + + if ((err = xr3pci_get_resources(pp, dev)) != 0) + return err; + + if ((err = xr3pci_setup_atr(pp, dev, resources, io_base)) != 0) + return err; + + if ((err = xr3pci_enable_device(pp)) != 0) + return err; + + if ((err = xr3pci_setup_int(pp)) != 0) + return err; + + return 0; +} + +/* + * The XpressRICH3 doesn't describe itself as a bridge. This is required for + * correct/normal enumeration. This quirk changes that. + */ +static void xr3pci_quirk_class(struct pci_dev *pdev) +{ + pdev->class = PCI_CLASS_BRIDGE_PCI << 8; +} +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_PLDA, PCI_DEVICE_ID_XR3PCI, + xr3pci_quirk_class); + +static int xr3pci_probe(struct platform_device *pdev) +{ + int err = 0; + struct device_node *dn; +#ifdef CONFIG_PCI_MSI + struct device_node *msi_parent; +#endif + struct xr3pci_port *pp; + struct pci_bus *bus; + resource_size_t io_base = 0; /* physical address for start of I/O area */ + LIST_HEAD(res); + + dn = pdev->dev.of_node; + + if (!of_device_is_available(dn)) { + pr_warn("%s: disabled\n", dn->full_name); + return -ENODEV; + } + + pp = kzalloc(sizeof(*pp), GFP_KERNEL); + if (!pp) + return -ENOMEM; + + err = of_pci_get_host_bridge_resources(dn, 0, 0xff, &res, &io_base); + if (err) + goto probe_err; + + err = xr3pci_setup(pp, &pdev->dev, &res, io_base); + if (err) + goto probe_err; + + /* We always enable PCI domains and we keep domain 0 backward + * compatible in /proc for video cards + */ + pci_add_flags(PCI_ENABLE_PROC_DOMAINS); + pci_add_flags(PCI_REASSIGN_ALL_BUS | PCI_REASSIGN_ALL_RSRC); + + bus = pci_scan_root_bus(&pdev->dev, 0, &xr3pci_ops, pp, &res); + if (!bus) + err = -ENXIO; + +#ifdef CONFIG_PCI_MSI + msi_parent = of_parse_phandle(dn, "msi-parent", 0); + if (!msi_parent) { + dev_err(&pdev->dev, "Unable to locate msi-parent node.\n"); + goto probe_err; + } + err = of_address_to_resource(msi_parent, 0, &pp->msi_res); + if (err) { + dev_err(&pdev->dev, "Failed to parse MSI parent resource\n"); + goto probe_err; + } + bus->msi = of_pci_find_msi_chip_by_node(msi_parent); +#endif + + pci_assign_unassigned_bus_resources(bus); + pci_bus_add_devices(bus); + +probe_err: + if (err) + kfree(pp); + pci_free_resource_list(&res); + return err; + +} + +static const struct of_device_id xr3pci_device_id[] = { + { .compatible = "arm,pcie-xr3", }, +}; +MODULE_DEVICE_TABLE(of, xr3pci_device_id); + +static struct platform_driver xr3pci_driver = { + .driver = { + .name = "pcie-xr3", + .owner = THIS_MODULE, + .of_match_table = xr3pci_device_id, + }, + .probe = xr3pci_probe, +}; + +module_platform_driver(xr3pci_driver); + +MODULE_AUTHOR("Liviu Dudau "); +MODULE_DESCRIPTION("XpressRICH3-AXI PCIe Host Bridge"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pci/host/pci-xr3.h b/drivers/pci/host/pci-xr3.h new file mode 100644 index 000000000000..85ccb698164f --- /dev/null +++ b/drivers/pci/host/pci-xr3.h @@ -0,0 +1,86 @@ +/* + * XpressRICH3-AXI PCIe Host Bridge Driver. + * + * Copyright (C) 2012 ARM Ltd. + * Author: Andrew Murray + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __XPRESS_RICH3_H__ +#define __XPRESS_RICH3_H__ + +/* Host Bridge Identification */ +#define DEVICE_NAME "XpressRICH3-AXI PCIe Host Bridge" +#define DEVICE_VENDOR_ID 0x1556 +#define DEVICE_DEVICE_ID 0x1100 + +/* Host Bridge Internal Registers */ +#define XR3PCI_BASIC_STATUS 0x18 +#define XR3PCI_BS_LINK_MASK 0xff +#define XR3PCI_BS_GEN_MASK (0xf << 8) +#define XR3PCI_BS_NEG_PAYLOAD_MASK (0xf << 24) +#define XR3PCI_BS_NEG_REQSIZE_MASK (0xf << 28) + +#define XR3PCI_LOCAL_INT_MASK 0x180 +#define XR3PCI_LOCAL_INT_STATUS 0x184 +#define XR3PCI_MSI_INT_STATUS 0x194 + +#define XR3PCI_INT_A (1 << 24) +#define XR3PCI_INT_B (1 << 25) +#define XR3PCI_INT_C (1 << 26) +#define XR3PCI_INT_D (1 << 27) +#define XR3PCI_INT_INTx (XR3PCI_INT_A | XR3PCI_INT_B | \ + XR3PCI_INT_C | XR3PCI_INT_D) +#define XR3PCI_INT_MSI (1 << 28) + + +#define XR3PCI_VIRTCHAN_CREDITS 0x90 +#define XR3PCI_PEX_SPC2 0xd8 + +/* Address Translation Register */ +#define XR3PCI_ATR_PCIE_WIN0 0x600 +#define XR3PCI_ATR_PCIE_WIN1 0x700 +#define XR3PCI_ATR_AXI4_SLV0 0x800 + +#define XR3PCI_ATR_TABLE_SIZE 0x20 +#define XR3PCI_ATR_SRC_ADDR_LOW 0x0 +#define XR3PCI_ATR_SRC_ADDR_HIGH 0x4 +#define XR3PCI_ATR_TRSL_ADDR_LOW 0x8 +#define XR3PCI_ATR_TRSL_ADDR_HIGH 0xc +#define XR3PCI_ATR_TRSL_PARAM 0x10 +/* IDs used in the XR3PCI_ATR_TRSL_PARAM */ +#define XR3PCI_ATR_TRSLID_AXIDEVICE (0x420004) +#define XR3PCI_ATR_TRSLID_AXIMEMORY (0x4e0004) /* Write-through, read/write allocate */ +#define XR3PCI_ATR_TRSLID_PCIE_CONF (0x000001) +#define XR3PCI_ATR_TRSLID_PCIE_IO (0x020000) +#define XR3PCI_ATR_TRSLID_PCIE_MEMORY (0x000000) + +#define XR3PCI_ECAM_OFFSET(b, d, o) (((b) << 20) | \ + (PCI_SLOT(d) << 15) | \ + (PCI_FUNC(d) << 12) | o) + + +#define JUNO_RESET_CTRL 0x1004 +#define JUNO_RESET_CTRL_PHY (1 << 0) +#define JUNO_RESET_CTRL_RC (1 << 1) + +#define JUNO_RESET_STATUS 0x1008 +#define JUNO_RESET_STATUS_PLL (1 << 0) +#define JUNO_RESET_STATUS_PHY (1 << 1) +#define JUNO_RESET_STATUS_RC (1 << 2) +#define JUNO_RESET_STATUS_MASK (JUNO_RESET_STATUS_PLL | \ + JUNO_RESET_STATUS_PHY | \ + JUNO_RESET_STATUS_RC) + +#endif