From ca9d82679916c3b6bdb846319e343a43a6bbb31c Mon Sep 17 00:00:00 2001 From: Liviu Dudau Date: Fri, 28 Feb 2014 15:43:08 +0000 Subject: [PATCH] pci: Add support for PLDA's XpressRICH3 PCIe host bridge This adds support for the XpressRich3-AXI IP block when configured as a PCIE host bridge. Support for generating MSI interrupts is ignored here as the platform where this driver has been developed for does not have support for passing the interrupts from the IP into the CPU, but uses the GICv2m functionality. Signed-off-by: Liviu Dudau --- .../devicetree/bindings/pci/arm,pcie-xr3.txt | 57 +++ drivers/pci/host/Kconfig | 6 + drivers/pci/host/Makefile | 2 + drivers/pci/host/pci-xr3.c | 340 ++++++++++++++++++ drivers/pci/host/pci-xr3.h | 86 +++++ 5 files changed, 491 insertions(+) create mode 100644 Documentation/devicetree/bindings/pci/arm,pcie-xr3.txt create mode 100644 drivers/pci/host/pci-xr3.c create mode 100644 drivers/pci/host/pci-xr3.h 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