diff options
Diffstat (limited to 'target/linux/layerscape/patches-5.4/812-pcie-0009-PCI-mobiveil-Refactor-Mobiveil-PCIe-Host-Bridge-IP-d.patch')
-rw-r--r-- | target/linux/layerscape/patches-5.4/812-pcie-0009-PCI-mobiveil-Refactor-Mobiveil-PCIe-Host-Bridge-IP-d.patch | 2184 |
1 files changed, 2184 insertions, 0 deletions
diff --git a/target/linux/layerscape/patches-5.4/812-pcie-0009-PCI-mobiveil-Refactor-Mobiveil-PCIe-Host-Bridge-IP-d.patch b/target/linux/layerscape/patches-5.4/812-pcie-0009-PCI-mobiveil-Refactor-Mobiveil-PCIe-Host-Bridge-IP-d.patch new file mode 100644 index 0000000000..9494317368 --- /dev/null +++ b/target/linux/layerscape/patches-5.4/812-pcie-0009-PCI-mobiveil-Refactor-Mobiveil-PCIe-Host-Bridge-IP-d.patch @@ -0,0 +1,2184 @@ +From ab6a07d577dbd45d00a1738a6b5a28a6666be754 Mon Sep 17 00:00:00 2001 +From: Hou Zhiqiang <Zhiqiang.Hou@nxp.com> +Date: Tue, 25 Jun 2019 09:09:07 +0000 +Subject: [PATCH] PCI: mobiveil: Refactor Mobiveil PCIe Host Bridge IP driver + +Refactor the Mobiveil PCIe Host Bridge IP driver to make +it easier to add support for both RC and EP mode driver. +This patch moved the Mobiveil driver to an new directory +'drivers/pci/controller/mobiveil' and refactor it according +to the RC and EP abstraction. + +Signed-off-by: Hou Zhiqiang <Zhiqiang.Hou@nxp.com> +Reviewed-by: Minghuan Lian <Minghuan.Lian@nxp.com> +Reviewed-by: Subrahmanya Lingappa <l.subrahmanya@mobiveil.co.in> +--- + MAINTAINERS | 2 +- + drivers/pci/controller/Kconfig | 11 +- + drivers/pci/controller/Makefile | 2 +- + drivers/pci/controller/mobiveil/Kconfig | 24 + + drivers/pci/controller/mobiveil/Makefile | 4 + + .../pci/controller/mobiveil/pcie-mobiveil-host.c | 611 +++++++++++++ + .../pci/controller/mobiveil/pcie-mobiveil-plat.c | 59 ++ + drivers/pci/controller/mobiveil/pcie-mobiveil.c | 227 +++++ + drivers/pci/controller/mobiveil/pcie-mobiveil.h | 189 ++++ + drivers/pci/controller/pcie-mobiveil.c | 964 --------------------- + 10 files changed, 1117 insertions(+), 976 deletions(-) + create mode 100644 drivers/pci/controller/mobiveil/Kconfig + create mode 100644 drivers/pci/controller/mobiveil/Makefile + create mode 100644 drivers/pci/controller/mobiveil/pcie-mobiveil-host.c + create mode 100644 drivers/pci/controller/mobiveil/pcie-mobiveil-plat.c + create mode 100644 drivers/pci/controller/mobiveil/pcie-mobiveil.c + create mode 100644 drivers/pci/controller/mobiveil/pcie-mobiveil.h + delete mode 100644 drivers/pci/controller/pcie-mobiveil.c + +--- a/MAINTAINERS ++++ b/MAINTAINERS +@@ -12502,7 +12502,7 @@ M: Hou Zhiqiang <Zhiqiang.Hou@nxp.com> + L: linux-pci@vger.kernel.org + S: Supported + F: Documentation/devicetree/bindings/pci/mobiveil-pcie.txt +-F: drivers/pci/controller/pcie-mobiveil.c ++F: drivers/pci/controller/mobiveil/pcie-mobiveil* + + PCI DRIVER FOR MVEBU (Marvell Armada 370 and Armada XP SOC support) + M: Thomas Petazzoni <thomas.petazzoni@bootlin.com> +--- a/drivers/pci/controller/Kconfig ++++ b/drivers/pci/controller/Kconfig +@@ -241,16 +241,6 @@ config PCIE_MEDIATEK + Say Y here if you want to enable PCIe controller support on + MediaTek SoCs. + +-config PCIE_MOBIVEIL +- bool "Mobiveil AXI PCIe controller" +- depends on ARCH_ZYNQMP || COMPILE_TEST +- depends on OF +- depends on PCI_MSI_IRQ_DOMAIN +- help +- Say Y here if you want to enable support for the Mobiveil AXI PCIe +- Soft IP. It has up to 8 outbound and inbound windows +- for address translation and it is a PCIe Gen4 IP. +- + config PCIE_TANGO_SMP8759 + bool "Tango SMP8759 PCIe controller (DANGEROUS)" + depends on ARCH_TANGO && PCI_MSI && OF +@@ -289,4 +279,5 @@ config PCI_HYPERV_INTERFACE + have a common interface with the Hyper-V PCI frontend driver. + + source "drivers/pci/controller/dwc/Kconfig" ++source "drivers/pci/controller/mobiveil/Kconfig" + endmenu +--- a/drivers/pci/controller/Makefile ++++ b/drivers/pci/controller/Makefile +@@ -27,11 +27,11 @@ obj-$(CONFIG_PCIE_ROCKCHIP) += pcie-rock + obj-$(CONFIG_PCIE_ROCKCHIP_EP) += pcie-rockchip-ep.o + obj-$(CONFIG_PCIE_ROCKCHIP_HOST) += pcie-rockchip-host.o + obj-$(CONFIG_PCIE_MEDIATEK) += pcie-mediatek.o +-obj-$(CONFIG_PCIE_MOBIVEIL) += pcie-mobiveil.o + obj-$(CONFIG_PCIE_TANGO_SMP8759) += pcie-tango.o + obj-$(CONFIG_VMD) += vmd.o + # pcie-hisi.o quirks are needed even without CONFIG_PCIE_DW + obj-y += dwc/ ++obj-y += mobiveil/ + + + # The following drivers are for devices that use the generic ACPI +--- /dev/null ++++ b/drivers/pci/controller/mobiveil/Kconfig +@@ -0,0 +1,24 @@ ++# SPDX-License-Identifier: GPL-2.0 ++ ++menu "Mobiveil PCIe Core Support" ++ depends on PCI ++ ++config PCIE_MOBIVEIL ++ bool ++ ++config PCIE_MOBIVEIL_HOST ++ bool ++ depends on PCI_MSI_IRQ_DOMAIN ++ select PCIE_MOBIVEIL ++ ++config PCIE_MOBIVEIL_PLAT ++ bool "Mobiveil AXI PCIe controller" ++ depends on ARCH_ZYNQMP || COMPILE_TEST ++ depends on OF ++ select PCIE_MOBIVEIL_HOST ++ help ++ Say Y here if you want to enable support for the Mobiveil AXI PCIe ++ Soft IP. It has up to 8 outbound and inbound windows ++ for address translation and it is a PCIe Gen4 IP. ++ ++endmenu +--- /dev/null ++++ b/drivers/pci/controller/mobiveil/Makefile +@@ -0,0 +1,4 @@ ++# SPDX-License-Identifier: GPL-2.0 ++obj-$(CONFIG_PCIE_MOBIVEIL) += pcie-mobiveil.o ++obj-$(CONFIG_PCIE_MOBIVEIL_HOST) += pcie-mobiveil-host.o ++obj-$(CONFIG_PCIE_MOBIVEIL_PLAT) += pcie-mobiveil-plat.o +--- /dev/null ++++ b/drivers/pci/controller/mobiveil/pcie-mobiveil-host.c +@@ -0,0 +1,611 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * PCIe host controller driver for Mobiveil PCIe Host controller ++ * ++ * Copyright (c) 2018 Mobiveil Inc. ++ * Copyright 2019 NXP ++ * ++ * Author: Subrahmanya Lingappa <l.subrahmanya@mobiveil.co.in> ++ * Refactor: Zhiqiang Hou <Zhiqiang.Hou@nxp.com> ++ */ ++ ++#include <linux/init.h> ++#include <linux/interrupt.h> ++#include <linux/irq.h> ++#include <linux/irqchip/chained_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_irq.h> ++#include <linux/of_platform.h> ++#include <linux/of_pci.h> ++#include <linux/pci.h> ++#include <linux/platform_device.h> ++#include <linux/slab.h> ++ ++#include "pcie-mobiveil.h" ++ ++static bool mobiveil_pcie_valid_device(struct pci_bus *bus, unsigned int devfn) ++{ ++ struct mobiveil_pcie *pcie = bus->sysdata; ++ ++ /* Only one device down on each root port */ ++ if ((bus->number == pcie->rp.root_bus_nr) && (devfn > 0)) ++ return false; ++ ++ /* ++ * Do not read more than one device on the bus directly ++ * attached to RC ++ */ ++ if ((bus->primary == pcie->rp.root_bus_nr) && (PCI_SLOT(devfn) > 0)) ++ return false; ++ ++ return true; ++} ++ ++/* ++ * mobiveil_pcie_map_bus - routine to get the configuration base of either ++ * root port or endpoint ++ */ ++static void __iomem *mobiveil_pcie_map_bus(struct pci_bus *bus, ++ unsigned int devfn, int where) ++{ ++ struct mobiveil_pcie *pcie = bus->sysdata; ++ u32 value; ++ ++ if (!mobiveil_pcie_valid_device(bus, devfn)) ++ return NULL; ++ ++ /* RC config access */ ++ if (bus->number == pcie->rp.root_bus_nr) ++ return pcie->csr_axi_slave_base + where; ++ ++ /* ++ * EP config access (in Config/APIO space) ++ * Program PEX Address base (31..16 bits) with appropriate value ++ * (BDF) in PAB_AXI_AMAP_PEX_WIN_L0 Register. ++ * Relies on pci_lock serialization ++ */ ++ value = bus->number << PAB_BUS_SHIFT | ++ PCI_SLOT(devfn) << PAB_DEVICE_SHIFT | ++ PCI_FUNC(devfn) << PAB_FUNCTION_SHIFT; ++ ++ csr_writel(pcie, value, PAB_AXI_AMAP_PEX_WIN_L(WIN_NUM_0)); ++ ++ return pcie->rp.config_axi_slave_base + where; ++} ++ ++static struct pci_ops mobiveil_pcie_ops = { ++ .map_bus = mobiveil_pcie_map_bus, ++ .read = pci_generic_config_read, ++ .write = pci_generic_config_write, ++}; ++ ++static void mobiveil_pcie_isr(struct irq_desc *desc) ++{ ++ struct irq_chip *chip = irq_desc_get_chip(desc); ++ struct mobiveil_pcie *pcie = irq_desc_get_handler_data(desc); ++ struct device *dev = &pcie->pdev->dev; ++ struct mobiveil_msi *msi = &pcie->rp.msi; ++ u32 msi_data, msi_addr_lo, msi_addr_hi; ++ u32 intr_status, msi_status; ++ unsigned long shifted_status; ++ u32 bit, virq, val, mask; ++ ++ /* ++ * The core provides a single interrupt for both INTx/MSI messages. ++ * So we'll read both INTx and MSI status ++ */ ++ ++ chained_irq_enter(chip, desc); ++ ++ /* read INTx status */ ++ val = csr_readl(pcie, PAB_INTP_AMBA_MISC_STAT); ++ mask = csr_readl(pcie, PAB_INTP_AMBA_MISC_ENB); ++ intr_status = val & mask; ++ ++ /* Handle INTx */ ++ if (intr_status & PAB_INTP_INTX_MASK) { ++ shifted_status = csr_readl(pcie, PAB_INTP_AMBA_MISC_STAT); ++ shifted_status &= PAB_INTP_INTX_MASK; ++ shifted_status >>= PAB_INTX_START; ++ do { ++ for_each_set_bit(bit, &shifted_status, PCI_NUM_INTX) { ++ virq = irq_find_mapping(pcie->rp.intx_domain, ++ bit + 1); ++ if (virq) ++ generic_handle_irq(virq); ++ else ++ dev_err_ratelimited(dev, "unexpected IRQ, INT%d\n", ++ bit); ++ ++ /* clear interrupt handled */ ++ csr_writel(pcie, 1 << (PAB_INTX_START + bit), ++ PAB_INTP_AMBA_MISC_STAT); ++ } ++ ++ shifted_status = csr_readl(pcie, ++ PAB_INTP_AMBA_MISC_STAT); ++ shifted_status &= PAB_INTP_INTX_MASK; ++ shifted_status >>= PAB_INTX_START; ++ } while (shifted_status != 0); ++ } ++ ++ /* read extra MSI status register */ ++ msi_status = readl_relaxed(pcie->apb_csr_base + MSI_STATUS_OFFSET); ++ ++ /* handle MSI interrupts */ ++ while (msi_status & 1) { ++ msi_data = readl_relaxed(pcie->apb_csr_base + MSI_DATA_OFFSET); ++ ++ /* ++ * MSI_STATUS_OFFSET register gets updated to zero ++ * once we pop not only the MSI data but also address ++ * from MSI hardware FIFO. So keeping these following ++ * two dummy reads. ++ */ ++ msi_addr_lo = readl_relaxed(pcie->apb_csr_base + ++ MSI_ADDR_L_OFFSET); ++ msi_addr_hi = readl_relaxed(pcie->apb_csr_base + ++ MSI_ADDR_H_OFFSET); ++ dev_dbg(dev, "MSI registers, data: %08x, addr: %08x:%08x\n", ++ msi_data, msi_addr_hi, msi_addr_lo); ++ ++ virq = irq_find_mapping(msi->dev_domain, msi_data); ++ if (virq) ++ generic_handle_irq(virq); ++ ++ msi_status = readl_relaxed(pcie->apb_csr_base + ++ MSI_STATUS_OFFSET); ++ } ++ ++ /* Clear the interrupt status */ ++ csr_writel(pcie, intr_status, PAB_INTP_AMBA_MISC_STAT); ++ chained_irq_exit(chip, desc); ++} ++ ++static int mobiveil_pcie_parse_dt(struct mobiveil_pcie *pcie) ++{ ++ struct device *dev = &pcie->pdev->dev; ++ struct platform_device *pdev = pcie->pdev; ++ struct device_node *node = dev->of_node; ++ struct resource *res; ++ ++ /* map config resource */ ++ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, ++ "config_axi_slave"); ++ pcie->rp.config_axi_slave_base = devm_pci_remap_cfg_resource(dev, res); ++ if (IS_ERR(pcie->rp.config_axi_slave_base)) ++ return PTR_ERR(pcie->rp.config_axi_slave_base); ++ pcie->rp.ob_io_res = res; ++ ++ /* map csr resource */ ++ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, ++ "csr_axi_slave"); ++ pcie->csr_axi_slave_base = devm_pci_remap_cfg_resource(dev, res); ++ if (IS_ERR(pcie->csr_axi_slave_base)) ++ return PTR_ERR(pcie->csr_axi_slave_base); ++ pcie->pcie_reg_base = res->start; ++ ++ /* read the number of windows requested */ ++ if (of_property_read_u32(node, "apio-wins", &pcie->apio_wins)) ++ pcie->apio_wins = MAX_PIO_WINDOWS; ++ ++ if (of_property_read_u32(node, "ppio-wins", &pcie->ppio_wins)) ++ pcie->ppio_wins = MAX_PIO_WINDOWS; ++ ++ return 0; ++} ++ ++static void mobiveil_pcie_enable_msi(struct mobiveil_pcie *pcie) ++{ ++ phys_addr_t msg_addr = pcie->pcie_reg_base; ++ struct mobiveil_msi *msi = &pcie->rp.msi; ++ ++ msi->num_of_vectors = PCI_NUM_MSI; ++ msi->msi_pages_phys = (phys_addr_t)msg_addr; ++ ++ writel_relaxed(lower_32_bits(msg_addr), ++ pcie->apb_csr_base + MSI_BASE_LO_OFFSET); ++ writel_relaxed(upper_32_bits(msg_addr), ++ pcie->apb_csr_base + MSI_BASE_HI_OFFSET); ++ writel_relaxed(4096, pcie->apb_csr_base + MSI_SIZE_OFFSET); ++ writel_relaxed(1, pcie->apb_csr_base + MSI_ENABLE_OFFSET); ++} ++ ++static int mobiveil_host_init(struct mobiveil_pcie *pcie) ++{ ++ u32 value, pab_ctrl, type; ++ struct resource_entry *win; ++ ++ /* setup bus numbers */ ++ value = csr_readl(pcie, PCI_PRIMARY_BUS); ++ value &= 0xff000000; ++ value |= 0x00ff0100; ++ csr_writel(pcie, value, PCI_PRIMARY_BUS); ++ ++ /* ++ * program Bus Master Enable Bit in Command Register in PAB Config ++ * Space ++ */ ++ value = csr_readl(pcie, PCI_COMMAND); ++ value |= PCI_COMMAND_IO | PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER; ++ csr_writel(pcie, value, PCI_COMMAND); ++ ++ /* ++ * program PIO Enable Bit to 1 (and PEX PIO Enable to 1) in PAB_CTRL ++ * register ++ */ ++ pab_ctrl = csr_readl(pcie, PAB_CTRL); ++ pab_ctrl |= (1 << AMBA_PIO_ENABLE_SHIFT) | (1 << PEX_PIO_ENABLE_SHIFT); ++ csr_writel(pcie, pab_ctrl, PAB_CTRL); ++ ++ /* ++ * program PIO Enable Bit to 1 and Config Window Enable Bit to 1 in ++ * PAB_AXI_PIO_CTRL Register ++ */ ++ value = csr_readl(pcie, PAB_AXI_PIO_CTRL); ++ value |= APIO_EN_MASK; ++ csr_writel(pcie, value, PAB_AXI_PIO_CTRL); ++ ++ /* Enable PCIe PIO master */ ++ value = csr_readl(pcie, PAB_PEX_PIO_CTRL); ++ value |= 1 << PIO_ENABLE_SHIFT; ++ csr_writel(pcie, value, PAB_PEX_PIO_CTRL); ++ ++ /* ++ * we'll program one outbound window for config reads and ++ * another default inbound window for all the upstream traffic ++ * rest of the outbound windows will be configured according to ++ * the "ranges" field defined in device tree ++ */ ++ ++ /* config outbound translation window */ ++ program_ob_windows(pcie, WIN_NUM_0, pcie->rp.ob_io_res->start, 0, ++ CFG_WINDOW_TYPE, resource_size(pcie->rp.ob_io_res)); ++ ++ /* memory inbound translation window */ ++ program_ib_windows(pcie, WIN_NUM_0, 0, 0, MEM_WINDOW_TYPE, IB_WIN_SIZE); ++ ++ /* Get the I/O and memory ranges from DT */ ++ resource_list_for_each_entry(win, &pcie->resources) { ++ if (resource_type(win->res) == IORESOURCE_MEM) { ++ type = MEM_WINDOW_TYPE; ++ } else if (resource_type(win->res) == IORESOURCE_IO) { ++ type = IO_WINDOW_TYPE; ++ } else if (resource_type(win->res) == IORESOURCE_BUS) { ++ pcie->rp.root_bus_nr = win->res->start; ++ continue; ++ } else { ++ continue; ++ } ++ ++ /* configure outbound translation window */ ++ program_ob_windows(pcie, pcie->ob_wins_configured, ++ win->res->start, ++ win->res->start - win->offset, ++ type, resource_size(win->res)); ++ } ++ ++ /* fixup for PCIe class register */ ++ value = csr_readl(pcie, PAB_INTP_AXI_PIO_CLASS); ++ value &= 0xff; ++ value |= (PCI_CLASS_BRIDGE_PCI << 16); ++ csr_writel(pcie, value, PAB_INTP_AXI_PIO_CLASS); ++ ++ return 0; ++} ++ ++static void mobiveil_mask_intx_irq(struct irq_data *data) ++{ ++ struct irq_desc *desc = irq_to_desc(data->irq); ++ struct mobiveil_pcie *pcie; ++ unsigned long flags; ++ u32 mask, shifted_val; ++ ++ pcie = irq_desc_get_chip_data(desc); ++ mask = 1 << ((data->hwirq + PAB_INTX_START) - 1); ++ raw_spin_lock_irqsave(&pcie->rp.intx_mask_lock, flags); ++ shifted_val = csr_readl(pcie, PAB_INTP_AMBA_MISC_ENB); ++ shifted_val &= ~mask; ++ csr_writel(pcie, shifted_val, PAB_INTP_AMBA_MISC_ENB); ++ raw_spin_unlock_irqrestore(&pcie->rp.intx_mask_lock, flags); ++} ++ ++static void mobiveil_unmask_intx_irq(struct irq_data *data) ++{ ++ struct irq_desc *desc = irq_to_desc(data->irq); ++ struct mobiveil_pcie *pcie; ++ unsigned long flags; ++ u32 shifted_val, mask; ++ ++ pcie = irq_desc_get_chip_data(desc); ++ mask = 1 << ((data->hwirq + PAB_INTX_START) - 1); ++ raw_spin_lock_irqsave(&pcie->rp.intx_mask_lock, flags); ++ shifted_val = csr_readl(pcie, PAB_INTP_AMBA_MISC_ENB); ++ shifted_val |= mask; ++ csr_writel(pcie, shifted_val, PAB_INTP_AMBA_MISC_ENB); ++ raw_spin_unlock_irqrestore(&pcie->rp.intx_mask_lock, flags); ++} ++ ++static struct irq_chip intx_irq_chip = { ++ .name = "mobiveil_pcie:intx", ++ .irq_enable = mobiveil_unmask_intx_irq, ++ .irq_disable = mobiveil_mask_intx_irq, ++ .irq_mask = mobiveil_mask_intx_irq, ++ .irq_unmask = mobiveil_unmask_intx_irq, ++}; ++ ++/* routine to setup the INTx related data */ ++static int mobiveil_pcie_intx_map(struct irq_domain *domain, unsigned int irq, ++ irq_hw_number_t hwirq) ++{ ++ irq_set_chip_and_handler(irq, &intx_irq_chip, handle_level_irq); ++ irq_set_chip_data(irq, domain->host_data); ++ ++ return 0; ++} ++ ++/* INTx domain operations structure */ ++static const struct irq_domain_ops intx_domain_ops = { ++ .map = mobiveil_pcie_intx_map, ++}; ++ ++static struct irq_chip mobiveil_msi_irq_chip = { ++ .name = "Mobiveil PCIe MSI", ++ .irq_mask = pci_msi_mask_irq, ++ .irq_unmask = pci_msi_unmask_irq, ++}; ++ ++static struct msi_domain_info mobiveil_msi_domain_info = { ++ .flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS | ++ MSI_FLAG_PCI_MSIX), ++ .chip = &mobiveil_msi_irq_chip, ++}; ++ ++static void mobiveil_compose_msi_msg(struct irq_data *data, struct msi_msg *msg) ++{ ++ struct mobiveil_pcie *pcie = irq_data_get_irq_chip_data(data); ++ phys_addr_t addr = pcie->pcie_reg_base + (data->hwirq * sizeof(int)); ++ ++ msg->address_lo = lower_32_bits(addr); ++ msg->address_hi = upper_32_bits(addr); ++ msg->data = data->hwirq; ++ ++ dev_dbg(&pcie->pdev->dev, "msi#%d address_hi %#x address_lo %#x\n", ++ (int)data->hwirq, msg->address_hi, msg->address_lo); ++} ++ ++static int mobiveil_msi_set_affinity(struct irq_data *irq_data, ++ const struct cpumask *mask, bool force) ++{ ++ return -EINVAL; ++} ++ ++static struct irq_chip mobiveil_msi_bottom_irq_chip = { ++ .name = "Mobiveil MSI", ++ .irq_compose_msi_msg = mobiveil_compose_msi_msg, ++ .irq_set_affinity = mobiveil_msi_set_affinity, ++}; ++ ++static int mobiveil_irq_msi_domain_alloc(struct irq_domain *domain, ++ unsigned int virq, ++ unsigned int nr_irqs, void *args) ++{ ++ struct mobiveil_pcie *pcie = domain->host_data; ++ struct mobiveil_msi *msi = &pcie->rp.msi; ++ unsigned long bit; ++ ++ WARN_ON(nr_irqs != 1); ++ mutex_lock(&msi->lock); ++ ++ bit = find_first_zero_bit(msi->msi_irq_in_use, msi->num_of_vectors); ++ if (bit >= msi->num_of_vectors) { ++ mutex_unlock(&msi->lock); ++ return -ENOSPC; ++ } ++ ++ set_bit(bit, msi->msi_irq_in_use); ++ ++ mutex_unlock(&msi->lock); ++ ++ irq_domain_set_info(domain, virq, bit, &mobiveil_msi_bottom_irq_chip, ++ domain->host_data, handle_level_irq, NULL, NULL); ++ return 0; ++} ++ ++static void mobiveil_irq_msi_domain_free(struct irq_domain *domain, ++ unsigned int virq, ++ unsigned int nr_irqs) ++{ ++ struct irq_data *d = irq_domain_get_irq_data(domain, virq); ++ struct mobiveil_pcie *pcie = irq_data_get_irq_chip_data(d); ++ struct mobiveil_msi *msi = &pcie->rp.msi; ++ ++ mutex_lock(&msi->lock); ++ ++ if (!test_bit(d->hwirq, msi->msi_irq_in_use)) ++ dev_err(&pcie->pdev->dev, "trying to free unused MSI#%lu\n", ++ d->hwirq); ++ else ++ __clear_bit(d->hwirq, msi->msi_irq_in_use); ++ ++ mutex_unlock(&msi->lock); ++} ++static const struct irq_domain_ops msi_domain_ops = { ++ .alloc = mobiveil_irq_msi_domain_alloc, ++ .free = mobiveil_irq_msi_domain_free, ++}; ++ ++static int mobiveil_allocate_msi_domains(struct mobiveil_pcie *pcie) ++{ ++ struct device *dev = &pcie->pdev->dev; ++ struct fwnode_handle *fwnode = of_node_to_fwnode(dev->of_node); ++ struct mobiveil_msi *msi = &pcie->rp.msi; ++ ++ mutex_init(&msi->lock); ++ msi->dev_domain = irq_domain_add_linear(NULL, msi->num_of_vectors, ++ &msi_domain_ops, pcie); ++ if (!msi->dev_domain) { ++ dev_err(dev, "failed to create IRQ domain\n"); ++ return -ENOMEM; ++ } ++ ++ msi->msi_domain = pci_msi_create_irq_domain(fwnode, ++ &mobiveil_msi_domain_info, ++ msi->dev_domain); ++ if (!msi->msi_domain) { ++ dev_err(dev, "failed to create MSI domain\n"); ++ irq_domain_remove(msi->dev_domain); ++ return -ENOMEM; ++ } ++ ++ return 0; ++} ++ ++static int mobiveil_pcie_init_irq_domain(struct mobiveil_pcie *pcie) ++{ ++ struct device *dev = &pcie->pdev->dev; ++ struct device_node *node = dev->of_node; ++ int ret; ++ ++ /* setup INTx */ ++ pcie->rp.intx_domain = irq_domain_add_linear(node, PCI_NUM_INTX, ++ &intx_domain_ops, pcie); ++ ++ if (!pcie->rp.intx_domain) { ++ dev_err(dev, "Failed to get a INTx IRQ domain\n"); ++ return -ENOMEM; ++ } ++ ++ raw_spin_lock_init(&pcie->rp.intx_mask_lock); ++ ++ /* setup MSI */ ++ ret = mobiveil_allocate_msi_domains(pcie); ++ if (ret) ++ return ret; ++ ++ return 0; ++} ++ ++static int mobiveil_pcie_interrupt_init(struct mobiveil_pcie *pcie) ++{ ++ struct device *dev = &pcie->pdev->dev; ++ struct resource *res; ++ int ret; ++ ++ if (pcie->rp.ops->interrupt_init) ++ return pcie->rp.ops->interrupt_init(pcie); ++ ++ /* map MSI config resource */ ++ res = platform_get_resource_byname(pcie->pdev, IORESOURCE_MEM, ++ "apb_csr"); ++ pcie->apb_csr_base = devm_pci_remap_cfg_resource(dev, res); ++ if (IS_ERR(pcie->apb_csr_base)) ++ return PTR_ERR(pcie->apb_csr_base); ++ ++ /* setup MSI hardware registers */ ++ mobiveil_pcie_enable_msi(pcie); ++ ++ pcie->rp.irq = platform_get_irq(pcie->pdev, 0); ++ if (pcie->rp.irq <= 0) { ++ dev_err(dev, "failed to map IRQ: %d\n", pcie->rp.irq); ++ return -ENODEV; ++ } ++ ++ /* initialize the IRQ domains */ ++ ret = mobiveil_pcie_init_irq_domain(pcie); ++ if (ret) { ++ dev_err(dev, "Failed creating IRQ Domain\n"); ++ return ret; ++ } ++ ++ irq_set_chained_handler_and_data(pcie->rp.irq, ++ mobiveil_pcie_isr, pcie); ++ ++ /* Enable interrupts */ ++ csr_writel(pcie, (PAB_INTP_INTX_MASK | PAB_INTP_MSI_MASK), ++ PAB_INTP_AMBA_MISC_ENB); ++ ++ return 0; ++} ++ ++int mobiveil_pcie_host_probe(struct mobiveil_pcie *pcie) ++{ ++ struct pci_bus *bus; ++ struct pci_bus *child; ++ struct pci_host_bridge *bridge = pcie->bridge; ++ struct device *dev = &pcie->pdev->dev; ++ resource_size_t iobase; ++ int ret; ++ ++ INIT_LIST_HEAD(&pcie->resources); ++ ++ ret = mobiveil_pcie_parse_dt(pcie); ++ if (ret) { ++ dev_err(dev, "Parsing DT failed, ret: %x\n", ret); ++ return ret; ++ } ++ ++ /* parse the host bridge base addresses from the device tree file */ ++ ret = devm_of_pci_get_host_bridge_resources(dev, 0, 0xff, ++ &pcie->resources, &iobase); ++ if (ret) { ++ dev_err(dev, "Getting bridge resources failed\n"); ++ return ret; ++ } ++ ++ /* ++ * configure all inbound and outbound windows and prepare the RC for ++ * config access ++ */ ++ ret = mobiveil_host_init(pcie); ++ if (ret) { ++ dev_err(dev, "Failed to initialize host\n"); ++ goto error; ++ } ++ ++ ret = mobiveil_pcie_interrupt_init(pcie); ++ if (ret) { ++ dev_err(dev, "Interrupt init failed\n"); ++ goto error; ++ } ++ ++ ret = devm_request_pci_bus_resources(dev, &pcie->resources); ++ if (ret) ++ goto error; ++ ++ /* Initialize bridge */ ++ list_splice_init(&pcie->resources, &bridge->windows); ++ bridge->dev.parent = dev; ++ bridge->sysdata = pcie; ++ bridge->busnr = pcie->rp.root_bus_nr; ++ bridge->ops = &mobiveil_pcie_ops; ++ bridge->map_irq = of_irq_parse_and_map_pci; ++ bridge->swizzle_irq = pci_common_swizzle; ++ ++ ret = mobiveil_bringup_link(pcie); ++ if (ret) { ++ dev_info(dev, "link bring-up failed\n"); ++ goto error; ++ } ++ ++ /* setup the kernel resources for the newly added PCIe root bus */ ++ ret = pci_scan_root_bus_bridge(bridge); ++ if (ret) ++ goto error; ++ ++ bus = bridge->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); ++ ++ return 0; ++error: ++ pci_free_resource_list(&pcie->resources); ++ return ret; ++} +--- /dev/null ++++ b/drivers/pci/controller/mobiveil/pcie-mobiveil-plat.c +@@ -0,0 +1,59 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * PCIe host controller driver for Mobiveil PCIe Host controller ++ * ++ * Copyright (c) 2018 Mobiveil Inc. ++ * Copyright 2019 NXP ++ * ++ * Author: Subrahmanya Lingappa <l.subrahmanya@mobiveil.co.in> ++ * Refactor: Zhiqiang Hou <Zhiqiang.Hou@nxp.com> ++ */ ++ ++#include <linux/init.h> ++#include <linux/kernel.h> ++#include <linux/module.h> ++#include <linux/of_pci.h> ++#include <linux/pci.h> ++#include <linux/platform_device.h> ++#include <linux/slab.h> ++ ++#include "pcie-mobiveil.h" ++ ++static int mobiveil_pcie_probe(struct platform_device *pdev) ++{ ++ struct mobiveil_pcie *pcie; ++ struct pci_host_bridge *bridge; ++ struct device *dev = &pdev->dev; ++ ++ bridge = devm_pci_alloc_host_bridge(dev, sizeof(*pcie)); ++ if (!bridge) ++ return -ENOMEM; ++ ++ pcie = pci_host_bridge_priv(bridge); ++ ++ pcie->pdev = pdev; ++ ++ return mobiveil_pcie_host_probe(pcie); ++} ++ ++static const struct of_device_id mobiveil_pcie_of_match[] = { ++ {.compatible = "mbvl,gpex40-pcie",}, ++ {}, ++}; ++ ++MODULE_DEVICE_TABLE(of, mobiveil_pcie_of_match); ++ ++static struct platform_driver mobiveil_pcie_driver = { ++ .probe = mobiveil_pcie_probe, ++ .driver = { ++ .name = "mobiveil-pcie", ++ .of_match_table = mobiveil_pcie_of_match, ++ .suppress_bind_attrs = true, ++ }, ++}; ++ ++builtin_platform_driver(mobiveil_pcie_driver); ++ ++MODULE_LICENSE("GPL v2"); ++MODULE_DESCRIPTION("Mobiveil PCIe host controller driver"); ++MODULE_AUTHOR("Subrahmanya Lingappa <l.subrahmanya@mobiveil.co.in>"); +--- /dev/null ++++ b/drivers/pci/controller/mobiveil/pcie-mobiveil.c +@@ -0,0 +1,227 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * PCIe host controller driver for Mobiveil PCIe Host controller ++ * ++ * Copyright (c) 2018 Mobiveil Inc. ++ * Copyright 2019 NXP ++ * ++ * Author: Subrahmanya Lingappa <l.subrahmanya@mobiveil.co.in> ++ * Refactor: Zhiqiang Hou <Zhiqiang.Hou@nxp.com> ++ */ ++ ++#include <linux/delay.h> ++#include <linux/init.h> ++#include <linux/kernel.h> ++#include <linux/pci.h> ++#include <linux/platform_device.h> ++ ++#include "pcie-mobiveil.h" ++ ++/* ++ * mobiveil_pcie_sel_page - routine to access paged register ++ * ++ * Registers whose address greater than PAGED_ADDR_BNDRY (0xc00) are paged, ++ * for this scheme to work extracted higher 6 bits of the offset will be ++ * written to pg_sel field of PAB_CTRL register and rest of the lower 10 ++ * bits enabled with PAGED_ADDR_BNDRY are used as offset of the register. ++ */ ++static void mobiveil_pcie_sel_page(struct mobiveil_pcie *pcie, u8 pg_idx) ++{ ++ u32 val; ++ ++ val = readl(pcie->csr_axi_slave_base + PAB_CTRL); ++ val &= ~(PAGE_SEL_MASK << PAGE_SEL_SHIFT); ++ val |= (pg_idx & PAGE_SEL_MASK) << PAGE_SEL_SHIFT; ++ ++ writel(val, pcie->csr_axi_slave_base + PAB_CTRL); ++} ++ ++static void *mobiveil_pcie_comp_addr(struct mobiveil_pcie *pcie, u32 off) ++{ ++ if (off < PAGED_ADDR_BNDRY) { ++ /* For directly accessed registers, clear the pg_sel field */ ++ mobiveil_pcie_sel_page(pcie, 0); ++ return pcie->csr_axi_slave_base + off; ++ } ++ ++ mobiveil_pcie_sel_page(pcie, OFFSET_TO_PAGE_IDX(off)); ++ return pcie->csr_axi_slave_base + OFFSET_TO_PAGE_ADDR(off); ++} ++ ++static int mobiveil_pcie_read(void __iomem *addr, int size, u32 *val) ++{ ++ if ((uintptr_t)addr & (size - 1)) { ++ *val = 0; ++ return PCIBIOS_BAD_REGISTER_NUMBER; ++ } ++ ++ switch (size) { ++ case 4: ++ *val = readl(addr); ++ break; ++ case 2: ++ *val = readw(addr); ++ break; ++ case 1: ++ *val = readb(addr); ++ break; ++ default: ++ *val = 0; ++ return PCIBIOS_BAD_REGISTER_NUMBER; ++ } ++ ++ return PCIBIOS_SUCCESSFUL; ++} ++ ++static int mobiveil_pcie_write(void __iomem *addr, int size, u32 val) ++{ ++ if ((uintptr_t)addr & (size - 1)) ++ return PCIBIOS_BAD_REGISTER_NUMBER; ++ ++ switch (size) { ++ case 4: ++ writel(val, addr); ++ break; ++ case 2: ++ writew(val, addr); ++ break; ++ case 1: ++ writeb(val, addr); ++ break; ++ default: ++ return PCIBIOS_BAD_REGISTER_NUMBER; ++ } ++ ++ return PCIBIOS_SUCCESSFUL; ++} ++ ++u32 csr_read(struct mobiveil_pcie *pcie, u32 off, size_t size) ++{ ++ void *addr; ++ u32 val; ++ int ret; ++ ++ addr = mobiveil_pcie_comp_addr(pcie, off); ++ ++ ret = mobiveil_pcie_read(addr, size, &val); ++ if (ret) ++ dev_err(&pcie->pdev->dev, "read CSR address failed\n"); ++ ++ return val; ++} ++ ++void csr_write(struct mobiveil_pcie *pcie, u32 val, u32 off, size_t size) ++{ ++ void *addr; ++ int ret; ++ ++ addr = mobiveil_pcie_comp_addr(pcie, off); ++ ++ ret = mobiveil_pcie_write(addr, size, val); ++ if (ret) ++ dev_err(&pcie->pdev->dev, "write CSR address failed\n"); ++} ++ ++bool mobiveil_pcie_link_up(struct mobiveil_pcie *pcie) ++{ ++ if (pcie->ops->link_up) ++ return pcie->ops->link_up(pcie); ++ ++ return (csr_readl(pcie, LTSSM_STATUS) & ++ LTSSM_STATUS_L0_MASK) == LTSSM_STATUS_L0; ++} ++ ++void program_ib_windows(struct mobiveil_pcie *pcie, int win_num, u64 cpu_addr, ++ u64 pci_addr, u32 type, u64 size) ++{ ++ u32 value; ++ u64 size64 = ~(size - 1); ++ ++ if (win_num >= pcie->ppio_wins) { ++ dev_err(&pcie->pdev->dev, ++ "ERROR: max inbound windows reached !\n"); ++ return; ++ } ++ ++ value = csr_readl(pcie, PAB_PEX_AMAP_CTRL(win_num)); ++ value &= ~(AMAP_CTRL_TYPE_MASK << AMAP_CTRL_TYPE_SHIFT | WIN_SIZE_MASK); ++ value |= type << AMAP_CTRL_TYPE_SHIFT | 1 << AMAP_CTRL_EN_SHIFT | ++ (lower_32_bits(size64) & WIN_SIZE_MASK); ++ csr_writel(pcie, value, PAB_PEX_AMAP_CTRL(win_num)); ++ ++ csr_writel(pcie, upper_32_bits(size64), ++ PAB_EXT_PEX_AMAP_SIZEN(win_num)); ++ ++ csr_writel(pcie, lower_32_bits(cpu_addr), ++ PAB_PEX_AMAP_AXI_WIN(win_num)); ++ csr_writel(pcie, upper_32_bits(cpu_addr), ++ PAB_EXT_PEX_AMAP_AXI_WIN(win_num)); ++ ++ csr_writel(pcie, lower_32_bits(pci_addr), ++ PAB_PEX_AMAP_PEX_WIN_L(win_num)); ++ csr_writel(pcie, upper_32_bits(pci_addr), ++ PAB_PEX_AMAP_PEX_WIN_H(win_num)); ++ ++ pcie->ib_wins_configured++; ++} ++ ++/* ++ * routine to program the outbound windows ++ */ ++void program_ob_windows(struct mobiveil_pcie *pcie, int win_num, u64 cpu_addr, ++ u64 pci_addr, u32 type, u64 size) ++{ ++ u32 value; ++ u64 size64 = ~(size - 1); ++ ++ if (win_num >= pcie->apio_wins) { ++ dev_err(&pcie->pdev->dev, ++ "ERROR: max outbound windows reached !\n"); ++ return; ++ } ++ ++ /* ++ * program Enable Bit to 1, Type Bit to (00) base 2, AXI Window Size Bit ++ * to 4 KB in PAB_AXI_AMAP_CTRL register ++ */ ++ value = csr_readl(pcie, PAB_AXI_AMAP_CTRL(win_num)); ++ value &= ~(WIN_TYPE_MASK << WIN_TYPE_SHIFT | WIN_SIZE_MASK); ++ value |= 1 << WIN_ENABLE_SHIFT | type << WIN_TYPE_SHIFT | ++ (lower_32_bits(size64) & WIN_SIZE_MASK); ++ csr_writel(pcie, value, PAB_AXI_AMAP_CTRL(win_num)); ++ ++ csr_writel(pcie, upper_32_bits(size64), PAB_EXT_AXI_AMAP_SIZE(win_num)); ++ ++ /* ++ * program AXI window base with appropriate value in ++ * PAB_AXI_AMAP_AXI_WIN0 register ++ */ ++ csr_writel(pcie, lower_32_bits(cpu_addr) & (~AXI_WINDOW_ALIGN_MASK), ++ PAB_AXI_AMAP_AXI_WIN(win_num)); ++ csr_writel(pcie, upper_32_bits(cpu_addr), ++ PAB_EXT_AXI_AMAP_AXI_WIN(win_num)); ++ ++ csr_writel(pcie, lower_32_bits(pci_addr), ++ PAB_AXI_AMAP_PEX_WIN_L(win_num)); ++ csr_writel(pcie, upper_32_bits(pci_addr), ++ PAB_AXI_AMAP_PEX_WIN_H(win_num)); ++ ++ pcie->ob_wins_configured++; ++} ++ ++int mobiveil_bringup_link(struct mobiveil_pcie *pcie) ++{ ++ int retries; ++ ++ /* check if the link is up or not */ ++ for (retries = 0; retries < LINK_WAIT_MAX_RETRIES; retries++) { ++ if (mobiveil_pcie_link_up(pcie)) ++ return 0; ++ ++ usleep_range(LINK_WAIT_MIN, LINK_WAIT_MAX); ++ } ++ ++ dev_err(&pcie->pdev->dev, "link never came up\n"); ++ ++ return -ETIMEDOUT; ++} +--- /dev/null ++++ b/drivers/pci/controller/mobiveil/pcie-mobiveil.h +@@ -0,0 +1,189 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * PCIe host controller driver for Mobiveil PCIe Host controller ++ * ++ * Copyright (c) 2018 Mobiveil Inc. ++ * Copyright 2019 NXP ++ * ++ * Author: Subrahmanya Lingappa <l.subrahmanya@mobiveil.co.in> ++ * Refactor: Zhiqiang Hou <Zhiqiang.Hou@nxp.com> ++ */ ++ ++#ifndef _PCIE_MOBIVEIL_H ++#define _PCIE_MOBIVEIL_H ++ ++#include <linux/pci.h> ++#include <linux/irq.h> ++#include <linux/msi.h> ++#include "../../pci.h" ++ ++/* register offsets and bit positions */ ++ ++/* ++ * translation tables are grouped into windows, each window registers are ++ * grouped into blocks of 4 or 16 registers each ++ */ ++#define PAB_REG_BLOCK_SIZE 16 ++#define PAB_EXT_REG_BLOCK_SIZE 4 ++ ++#define PAB_REG_ADDR(offset, win) \ ++ (offset + (win * PAB_REG_BLOCK_SIZE)) ++#define PAB_EXT_REG_ADDR(offset, win) \ ++ (offset + (win * PAB_EXT_REG_BLOCK_SIZE)) ++ ++#define LTSSM_STATUS 0x0404 ++#define LTSSM_STATUS_L0_MASK 0x3f ++#define LTSSM_STATUS_L0 0x2d ++ ++#define PAB_CTRL 0x0808 ++#define AMBA_PIO_ENABLE_SHIFT 0 ++#define PEX_PIO_ENABLE_SHIFT 1 ++#define PAGE_SEL_SHIFT 13 ++#define PAGE_SEL_MASK 0x3f ++#define PAGE_LO_MASK 0x3ff ++#define PAGE_SEL_OFFSET_SHIFT 10 ++ ++#define PAB_AXI_PIO_CTRL 0x0840 ++#define APIO_EN_MASK 0xf ++ ++#define PAB_PEX_PIO_CTRL 0x08c0 ++#define PIO_ENABLE_SHIFT 0 ++ ++#define PAB_INTP_AMBA_MISC_ENB 0x0b0c ++#define PAB_INTP_AMBA_MISC_STAT 0x0b1c ++#define PAB_INTP_INTX_MASK 0x01e0 ++#define PAB_INTP_MSI_MASK 0x8 ++ ++#define PAB_AXI_AMAP_CTRL(win) PAB_REG_ADDR(0x0ba0, win) ++#define WIN_ENABLE_SHIFT 0 ++#define WIN_TYPE_SHIFT 1 ++#define WIN_TYPE_MASK 0x3 ++#define WIN_SIZE_MASK 0xfffffc00 ++ ++#define PAB_EXT_AXI_AMAP_SIZE(win) PAB_EXT_REG_ADDR(0xbaf0, win) ++ ++#define PAB_EXT_AXI_AMAP_AXI_WIN(win) PAB_EXT_REG_ADDR(0x80a0, win) ++#define PAB_AXI_AMAP_AXI_WIN(win) PAB_REG_ADDR(0x0ba4, win) ++#define AXI_WINDOW_ALIGN_MASK 3 ++ ++#define PAB_AXI_AMAP_PEX_WIN_L(win) PAB_REG_ADDR(0x0ba8, win) ++#define PAB_BUS_SHIFT 24 ++#define PAB_DEVICE_SHIFT 19 ++#define PAB_FUNCTION_SHIFT 16 ++ ++#define PAB_AXI_AMAP_PEX_WIN_H(win) PAB_REG_ADDR(0x0bac, win) ++#define PAB_INTP_AXI_PIO_CLASS 0x474 ++ ++#define PAB_PEX_AMAP_CTRL(win) PAB_REG_ADDR(0x4ba0, win) ++#define AMAP_CTRL_EN_SHIFT 0 ++#define AMAP_CTRL_TYPE_SHIFT 1 ++#define AMAP_CTRL_TYPE_MASK 3 ++ ++#define PAB_EXT_PEX_AMAP_SIZEN(win) PAB_EXT_REG_ADDR(0xbef0, win) ++#define PAB_EXT_PEX_AMAP_AXI_WIN(win) PAB_EXT_REG_ADDR(0xb4a0, win) ++#define PAB_PEX_AMAP_AXI_WIN(win) PAB_REG_ADDR(0x4ba4, win) ++#define PAB_PEX_AMAP_PEX_WIN_L(win) PAB_REG_ADDR(0x4ba8, win) ++#define PAB_PEX_AMAP_PEX_WIN_H(win) PAB_REG_ADDR(0x4bac, win) ++ ++/* starting offset of INTX bits in status register */ ++#define PAB_INTX_START 5 ++ ++/* supported number of MSI interrupts */ ++#define PCI_NUM_MSI 16 ++ ++/* MSI registers */ ++#define MSI_BASE_LO_OFFSET 0x04 ++#define MSI_BASE_HI_OFFSET 0x08 ++#define MSI_SIZE_OFFSET 0x0c ++#define MSI_ENABLE_OFFSET 0x14 ++#define MSI_STATUS_OFFSET 0x18 ++#define MSI_DATA_OFFSET 0x20 ++#define MSI_ADDR_L_OFFSET 0x24 ++#define MSI_ADDR_H_OFFSET 0x28 ++ ++/* outbound and inbound window definitions */ ++#define WIN_NUM_0 0 ++#define WIN_NUM_1 1 ++#define CFG_WINDOW_TYPE 0 ++#define IO_WINDOW_TYPE 1 ++#define MEM_WINDOW_TYPE 2 ++#define IB_WIN_SIZE ((u64)256 * 1024 * 1024 * 1024) ++#define MAX_PIO_WINDOWS 8 ++ ++/* Parameters for the waiting for link up routine */ ++#define LINK_WAIT_MAX_RETRIES 10 ++#define LINK_WAIT_MIN 90000 ++#define LINK_WAIT_MAX 100000 ++ ++#define PAGED_ADDR_BNDRY 0xc00 ++#define OFFSET_TO_PAGE_ADDR(off) \ ++ ((off & PAGE_LO_MASK) | PAGED_ADDR_BNDRY) ++#define OFFSET_TO_PAGE_IDX(off) \ ++ ((off >> PAGE_SEL_OFFSET_SHIFT) & PAGE_SEL_MASK) ++ ++struct mobiveil_pcie; ++ ++struct mobiveil_msi { /* MSI information */ ++ struct mutex lock; /* protect bitmap variable */ ++ struct irq_domain *msi_domain; ++ struct irq_domain *dev_domain; ++ phys_addr_t msi_pages_phys; ++ int num_of_vectors; ++ DECLARE_BITMAP(msi_irq_in_use, PCI_NUM_MSI); ++}; ++ ++struct mobiveil_rp_ops { ++ int (*interrupt_init)(struct mobiveil_pcie *pcie); ++}; ++ ++struct root_port { ++ u8 root_bus_nr; ++ void __iomem *config_axi_slave_base; /* endpoint config base */ ++ struct resource *ob_io_res; ++ struct mobiveil_rp_ops *ops; ++ int irq; ++ raw_spinlock_t intx_mask_lock; ++ struct irq_domain *intx_domain; ++ struct mobiveil_msi msi; ++}; ++ ++struct mobiveil_pab_ops { ++ int (*link_up)(struct mobiveil_pcie *pcie); ++}; ++ ++struct mobiveil_pcie { ++ struct platform_device *pdev; ++ struct list_head resources; ++ void __iomem *csr_axi_slave_base; /* PAB registers base */ ++ phys_addr_t pcie_reg_base; /* Physical PCIe Controller Base */ ++ void __iomem *apb_csr_base; /* MSI register base */ ++ u32 apio_wins; ++ u32 ppio_wins; ++ u32 ob_wins_configured; /* configured outbound windows */ ++ u32 ib_wins_configured; /* configured inbound windows */ ++ const struct mobiveil_pab_ops *ops; ++ struct root_port rp; ++ struct pci_host_bridge *bridge; ++}; ++ ++int mobiveil_pcie_host_probe(struct mobiveil_pcie *pcie); ++bool mobiveil_pcie_link_up(struct mobiveil_pcie *pcie); ++int mobiveil_bringup_link(struct mobiveil_pcie *pcie); ++void program_ob_windows(struct mobiveil_pcie *pcie, int win_num, u64 cpu_addr, ++ u64 pci_addr, u32 type, u64 size); ++void program_ib_windows(struct mobiveil_pcie *pcie, int win_num, u64 cpu_addr, ++ u64 pci_addr, u32 type, u64 size); ++u32 csr_read(struct mobiveil_pcie *pcie, u32 off, size_t size); ++void csr_write(struct mobiveil_pcie *pcie, u32 val, u32 off, size_t size); ++ ++static inline u32 csr_readl(struct mobiveil_pcie *pcie, u32 off) ++{ ++ return csr_read(pcie, off, 0x4); ++} ++ ++static inline void csr_writel(struct mobiveil_pcie *pcie, u32 val, u32 off) ++{ ++ csr_write(pcie, val, off, 0x4); ++} ++ ++#endif /* _PCIE_MOBIVEIL_H */ +--- a/drivers/pci/controller/pcie-mobiveil.c ++++ /dev/null +@@ -1,964 +0,0 @@ +-// SPDX-License-Identifier: GPL-2.0 +-/* +- * PCIe host controller driver for Mobiveil PCIe Host controller +- * +- * Copyright (c) 2018 Mobiveil Inc. +- * Author: Subrahmanya Lingappa <l.subrahmanya@mobiveil.co.in> +- */ +- +-#include <linux/delay.h> +-#include <linux/init.h> +-#include <linux/interrupt.h> +-#include <linux/irq.h> +-#include <linux/irqchip/chained_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_irq.h> +-#include <linux/of_platform.h> +-#include <linux/of_pci.h> +-#include <linux/pci.h> +-#include <linux/platform_device.h> +-#include <linux/slab.h> +- +-#include "../pci.h" +- +-/* register offsets and bit positions */ +- +-/* +- * translation tables are grouped into windows, each window registers are +- * grouped into blocks of 4 or 16 registers each +- */ +-#define PAB_REG_BLOCK_SIZE 16 +-#define PAB_EXT_REG_BLOCK_SIZE 4 +- +-#define PAB_REG_ADDR(offset, win) \ +- (offset + (win * PAB_REG_BLOCK_SIZE)) +-#define PAB_EXT_REG_ADDR(offset, win) \ +- (offset + (win * PAB_EXT_REG_BLOCK_SIZE)) +- +-#define LTSSM_STATUS 0x0404 +-#define LTSSM_STATUS_L0_MASK 0x3f +-#define LTSSM_STATUS_L0 0x2d +- +-#define PAB_CTRL 0x0808 +-#define AMBA_PIO_ENABLE_SHIFT 0 +-#define PEX_PIO_ENABLE_SHIFT 1 +-#define PAGE_SEL_SHIFT 13 +-#define PAGE_SEL_MASK 0x3f +-#define PAGE_LO_MASK 0x3ff +-#define PAGE_SEL_OFFSET_SHIFT 10 +- +-#define PAB_AXI_PIO_CTRL 0x0840 +-#define APIO_EN_MASK 0xf +- +-#define PAB_PEX_PIO_CTRL 0x08c0 +-#define PIO_ENABLE_SHIFT 0 +- +-#define PAB_INTP_AMBA_MISC_ENB 0x0b0c +-#define PAB_INTP_AMBA_MISC_STAT 0x0b1c +-#define PAB_INTP_INTX_MASK 0x01e0 +-#define PAB_INTP_MSI_MASK 0x8 +- +-#define PAB_AXI_AMAP_CTRL(win) PAB_REG_ADDR(0x0ba0, win) +-#define WIN_ENABLE_SHIFT 0 +-#define WIN_TYPE_SHIFT 1 +-#define WIN_TYPE_MASK 0x3 +-#define WIN_SIZE_MASK 0xfffffc00 +- +-#define PAB_EXT_AXI_AMAP_SIZE(win) PAB_EXT_REG_ADDR(0xbaf0, win) +- +-#define PAB_EXT_AXI_AMAP_AXI_WIN(win) PAB_EXT_REG_ADDR(0x80a0, win) +-#define PAB_AXI_AMAP_AXI_WIN(win) PAB_REG_ADDR(0x0ba4, win) +-#define AXI_WINDOW_ALIGN_MASK 3 +- +-#define PAB_AXI_AMAP_PEX_WIN_L(win) PAB_REG_ADDR(0x0ba8, win) +-#define PAB_BUS_SHIFT 24 +-#define PAB_DEVICE_SHIFT 19 +-#define PAB_FUNCTION_SHIFT 16 +- +-#define PAB_AXI_AMAP_PEX_WIN_H(win) PAB_REG_ADDR(0x0bac, win) +-#define PAB_INTP_AXI_PIO_CLASS 0x474 +- +-#define PAB_PEX_AMAP_CTRL(win) PAB_REG_ADDR(0x4ba0, win) +-#define AMAP_CTRL_EN_SHIFT 0 +-#define AMAP_CTRL_TYPE_SHIFT 1 +-#define AMAP_CTRL_TYPE_MASK 3 +- +-#define PAB_EXT_PEX_AMAP_SIZEN(win) PAB_EXT_REG_ADDR(0xbef0, win) +-#define PAB_EXT_PEX_AMAP_AXI_WIN(win) PAB_EXT_REG_ADDR(0xb4a0, win) +-#define PAB_PEX_AMAP_AXI_WIN(win) PAB_REG_ADDR(0x4ba4, win) +-#define PAB_PEX_AMAP_PEX_WIN_L(win) PAB_REG_ADDR(0x4ba8, win) +-#define PAB_PEX_AMAP_PEX_WIN_H(win) PAB_REG_ADDR(0x4bac, win) +- +-/* starting offset of INTX bits in status register */ +-#define PAB_INTX_START 5 +- +-/* supported number of MSI interrupts */ +-#define PCI_NUM_MSI 16 +- +-/* MSI registers */ +-#define MSI_BASE_LO_OFFSET 0x04 +-#define MSI_BASE_HI_OFFSET 0x08 +-#define MSI_SIZE_OFFSET 0x0c +-#define MSI_ENABLE_OFFSET 0x14 +-#define MSI_STATUS_OFFSET 0x18 +-#define MSI_DATA_OFFSET 0x20 +-#define MSI_ADDR_L_OFFSET 0x24 +-#define MSI_ADDR_H_OFFSET 0x28 +- +-/* outbound and inbound window definitions */ +-#define WIN_NUM_0 0 +-#define WIN_NUM_1 1 +-#define CFG_WINDOW_TYPE 0 +-#define IO_WINDOW_TYPE 1 +-#define MEM_WINDOW_TYPE 2 +-#define IB_WIN_SIZE ((u64)256 * 1024 * 1024 * 1024) +-#define MAX_PIO_WINDOWS 8 +- +-/* Parameters for the waiting for link up routine */ +-#define LINK_WAIT_MAX_RETRIES 10 +-#define LINK_WAIT_MIN 90000 +-#define LINK_WAIT_MAX 100000 +- +-#define PAGED_ADDR_BNDRY 0xc00 +-#define OFFSET_TO_PAGE_ADDR(off) \ +- ((off & PAGE_LO_MASK) | PAGED_ADDR_BNDRY) +-#define OFFSET_TO_PAGE_IDX(off) \ +- ((off >> PAGE_SEL_OFFSET_SHIFT) & PAGE_SEL_MASK) +- +-struct mobiveil_msi { /* MSI information */ +- struct mutex lock; /* protect bitmap variable */ +- struct irq_domain *msi_domain; +- struct irq_domain *dev_domain; +- phys_addr_t msi_pages_phys; +- int num_of_vectors; +- DECLARE_BITMAP(msi_irq_in_use, PCI_NUM_MSI); +-}; +- +-struct mobiveil_pcie { +- struct platform_device *pdev; +- struct list_head resources; +- void __iomem *config_axi_slave_base; /* endpoint config base */ +- void __iomem *csr_axi_slave_base; /* root port config base */ +- void __iomem *apb_csr_base; /* MSI register base */ +- phys_addr_t pcie_reg_base; /* Physical PCIe Controller Base */ +- struct irq_domain *intx_domain; +- raw_spinlock_t intx_mask_lock; +- int irq; +- int apio_wins; +- int ppio_wins; +- int ob_wins_configured; /* configured outbound windows */ +- int ib_wins_configured; /* configured inbound windows */ +- struct resource *ob_io_res; +- char root_bus_nr; +- struct mobiveil_msi msi; +-}; +- +-/* +- * mobiveil_pcie_sel_page - routine to access paged register +- * +- * Registers whose address greater than PAGED_ADDR_BNDRY (0xc00) are paged, +- * for this scheme to work extracted higher 6 bits of the offset will be +- * written to pg_sel field of PAB_CTRL register and rest of the lower 10 +- * bits enabled with PAGED_ADDR_BNDRY are used as offset of the register. +- */ +-static void mobiveil_pcie_sel_page(struct mobiveil_pcie *pcie, u8 pg_idx) +-{ +- u32 val; +- +- val = readl(pcie->csr_axi_slave_base + PAB_CTRL); +- val &= ~(PAGE_SEL_MASK << PAGE_SEL_SHIFT); +- val |= (pg_idx & PAGE_SEL_MASK) << PAGE_SEL_SHIFT; +- +- writel(val, pcie->csr_axi_slave_base + PAB_CTRL); +-} +- +-static void *mobiveil_pcie_comp_addr(struct mobiveil_pcie *pcie, u32 off) +-{ +- if (off < PAGED_ADDR_BNDRY) { +- /* For directly accessed registers, clear the pg_sel field */ +- mobiveil_pcie_sel_page(pcie, 0); +- return pcie->csr_axi_slave_base + off; +- } +- +- mobiveil_pcie_sel_page(pcie, OFFSET_TO_PAGE_IDX(off)); +- return pcie->csr_axi_slave_base + OFFSET_TO_PAGE_ADDR(off); +-} +- +-static int mobiveil_pcie_read(void __iomem *addr, int size, u32 *val) +-{ +- if ((uintptr_t)addr & (size - 1)) { +- *val = 0; +- return PCIBIOS_BAD_REGISTER_NUMBER; +- } +- +- switch (size) { +- case 4: +- *val = readl(addr); +- break; +- case 2: +- *val = readw(addr); +- break; +- case 1: +- *val = readb(addr); +- break; +- default: +- *val = 0; +- return PCIBIOS_BAD_REGISTER_NUMBER; +- } +- +- return PCIBIOS_SUCCESSFUL; +-} +- +-static int mobiveil_pcie_write(void __iomem *addr, int size, u32 val) +-{ +- if ((uintptr_t)addr & (size - 1)) +- return PCIBIOS_BAD_REGISTER_NUMBER; +- +- switch (size) { +- case 4: +- writel(val, addr); +- break; +- case 2: +- writew(val, addr); +- break; +- case 1: +- writeb(val, addr); +- break; +- default: +- return PCIBIOS_BAD_REGISTER_NUMBER; +- } +- +- return PCIBIOS_SUCCESSFUL; +-} +- +-static u32 csr_read(struct mobiveil_pcie *pcie, u32 off, size_t size) +-{ +- void *addr; +- u32 val; +- int ret; +- +- addr = mobiveil_pcie_comp_addr(pcie, off); +- +- ret = mobiveil_pcie_read(addr, size, &val); +- if (ret) +- dev_err(&pcie->pdev->dev, "read CSR address failed\n"); +- +- return val; +-} +- +-static void csr_write(struct mobiveil_pcie *pcie, u32 val, u32 off, size_t size) +-{ +- void *addr; +- int ret; +- +- addr = mobiveil_pcie_comp_addr(pcie, off); +- +- ret = mobiveil_pcie_write(addr, size, val); +- if (ret) +- dev_err(&pcie->pdev->dev, "write CSR address failed\n"); +-} +- +-static u32 csr_readl(struct mobiveil_pcie *pcie, u32 off) +-{ +- return csr_read(pcie, off, 0x4); +-} +- +-static void csr_writel(struct mobiveil_pcie *pcie, u32 val, u32 off) +-{ +- csr_write(pcie, val, off, 0x4); +-} +- +-static bool mobiveil_pcie_link_up(struct mobiveil_pcie *pcie) +-{ +- return (csr_readl(pcie, LTSSM_STATUS) & +- LTSSM_STATUS_L0_MASK) == LTSSM_STATUS_L0; +-} +- +-static bool mobiveil_pcie_valid_device(struct pci_bus *bus, unsigned int devfn) +-{ +- struct mobiveil_pcie *pcie = bus->sysdata; +- +- /* Only one device down on each root port */ +- if ((bus->number == pcie->root_bus_nr) && (devfn > 0)) +- return false; +- +- /* +- * Do not read more than one device on the bus directly +- * attached to RC +- */ +- if ((bus->primary == pcie->root_bus_nr) && (PCI_SLOT(devfn) > 0)) +- return false; +- +- return true; +-} +- +-/* +- * mobiveil_pcie_map_bus - routine to get the configuration base of either +- * root port or endpoint +- */ +-static void __iomem *mobiveil_pcie_map_bus(struct pci_bus *bus, +- unsigned int devfn, int where) +-{ +- struct mobiveil_pcie *pcie = bus->sysdata; +- u32 value; +- +- if (!mobiveil_pcie_valid_device(bus, devfn)) +- return NULL; +- +- /* RC config access */ +- if (bus->number == pcie->root_bus_nr) +- return pcie->csr_axi_slave_base + where; +- +- /* +- * EP config access (in Config/APIO space) +- * Program PEX Address base (31..16 bits) with appropriate value +- * (BDF) in PAB_AXI_AMAP_PEX_WIN_L0 Register. +- * Relies on pci_lock serialization +- */ +- value = bus->number << PAB_BUS_SHIFT | +- PCI_SLOT(devfn) << PAB_DEVICE_SHIFT | +- PCI_FUNC(devfn) << PAB_FUNCTION_SHIFT; +- +- csr_writel(pcie, value, PAB_AXI_AMAP_PEX_WIN_L(WIN_NUM_0)); +- +- return pcie->config_axi_slave_base + where; +-} +- +-static struct pci_ops mobiveil_pcie_ops = { +- .map_bus = mobiveil_pcie_map_bus, +- .read = pci_generic_config_read, +- .write = pci_generic_config_write, +-}; +- +-static void mobiveil_pcie_isr(struct irq_desc *desc) +-{ +- struct irq_chip *chip = irq_desc_get_chip(desc); +- struct mobiveil_pcie *pcie = irq_desc_get_handler_data(desc); +- struct device *dev = &pcie->pdev->dev; +- struct mobiveil_msi *msi = &pcie->msi; +- u32 msi_data, msi_addr_lo, msi_addr_hi; +- u32 intr_status, msi_status; +- unsigned long shifted_status; +- u32 bit, virq, val, mask; +- +- /* +- * The core provides a single interrupt for both INTx/MSI messages. +- * So we'll read both INTx and MSI status +- */ +- +- chained_irq_enter(chip, desc); +- +- /* read INTx status */ +- val = csr_readl(pcie, PAB_INTP_AMBA_MISC_STAT); +- mask = csr_readl(pcie, PAB_INTP_AMBA_MISC_ENB); +- intr_status = val & mask; +- +- /* Handle INTx */ +- if (intr_status & PAB_INTP_INTX_MASK) { +- shifted_status = csr_readl(pcie, PAB_INTP_AMBA_MISC_STAT); +- shifted_status &= PAB_INTP_INTX_MASK; +- shifted_status >>= PAB_INTX_START; +- do { +- for_each_set_bit(bit, &shifted_status, PCI_NUM_INTX) { +- virq = irq_find_mapping(pcie->intx_domain, +- bit + 1); +- if (virq) +- generic_handle_irq(virq); +- else +- dev_err_ratelimited(dev, "unexpected IRQ, INT%d\n", +- bit); +- +- /* clear interrupt handled */ +- csr_writel(pcie, 1 << (PAB_INTX_START + bit), +- PAB_INTP_AMBA_MISC_STAT); +- } +- +- shifted_status = csr_readl(pcie, +- PAB_INTP_AMBA_MISC_STAT); +- shifted_status &= PAB_INTP_INTX_MASK; +- shifted_status >>= PAB_INTX_START; +- } while (shifted_status != 0); +- } +- +- /* read extra MSI status register */ +- msi_status = readl_relaxed(pcie->apb_csr_base + MSI_STATUS_OFFSET); +- +- /* handle MSI interrupts */ +- while (msi_status & 1) { +- msi_data = readl_relaxed(pcie->apb_csr_base + MSI_DATA_OFFSET); +- +- /* +- * MSI_STATUS_OFFSET register gets updated to zero +- * once we pop not only the MSI data but also address +- * from MSI hardware FIFO. So keeping these following +- * two dummy reads. +- */ +- msi_addr_lo = readl_relaxed(pcie->apb_csr_base + +- MSI_ADDR_L_OFFSET); +- msi_addr_hi = readl_relaxed(pcie->apb_csr_base + +- MSI_ADDR_H_OFFSET); +- dev_dbg(dev, "MSI registers, data: %08x, addr: %08x:%08x\n", +- msi_data, msi_addr_hi, msi_addr_lo); +- +- virq = irq_find_mapping(msi->dev_domain, msi_data); +- if (virq) +- generic_handle_irq(virq); +- +- msi_status = readl_relaxed(pcie->apb_csr_base + +- MSI_STATUS_OFFSET); +- } +- +- /* Clear the interrupt status */ +- csr_writel(pcie, intr_status, PAB_INTP_AMBA_MISC_STAT); +- chained_irq_exit(chip, desc); +-} +- +-static int mobiveil_pcie_parse_dt(struct mobiveil_pcie *pcie) +-{ +- struct device *dev = &pcie->pdev->dev; +- struct platform_device *pdev = pcie->pdev; +- struct device_node *node = dev->of_node; +- struct resource *res; +- +- /* map config resource */ +- res = platform_get_resource_byname(pdev, IORESOURCE_MEM, +- "config_axi_slave"); +- pcie->config_axi_slave_base = devm_pci_remap_cfg_resource(dev, res); +- if (IS_ERR(pcie->config_axi_slave_base)) +- return PTR_ERR(pcie->config_axi_slave_base); +- pcie->ob_io_res = res; +- +- /* map csr resource */ +- res = platform_get_resource_byname(pdev, IORESOURCE_MEM, +- "csr_axi_slave"); +- pcie->csr_axi_slave_base = devm_pci_remap_cfg_resource(dev, res); +- if (IS_ERR(pcie->csr_axi_slave_base)) +- return PTR_ERR(pcie->csr_axi_slave_base); +- pcie->pcie_reg_base = res->start; +- +- /* map MSI config resource */ +- res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "apb_csr"); +- pcie->apb_csr_base = devm_pci_remap_cfg_resource(dev, res); +- if (IS_ERR(pcie->apb_csr_base)) +- return PTR_ERR(pcie->apb_csr_base); +- +- /* read the number of windows requested */ +- if (of_property_read_u32(node, "apio-wins", &pcie->apio_wins)) +- pcie->apio_wins = MAX_PIO_WINDOWS; +- +- if (of_property_read_u32(node, "ppio-wins", &pcie->ppio_wins)) +- pcie->ppio_wins = MAX_PIO_WINDOWS; +- +- pcie->irq = platform_get_irq(pdev, 0); +- if (pcie->irq <= 0) { +- dev_err(dev, "failed to map IRQ: %d\n", pcie->irq); +- return -ENODEV; +- } +- +- return 0; +-} +- +-static void program_ib_windows(struct mobiveil_pcie *pcie, int win_num, +- u64 cpu_addr, u64 pci_addr, u32 type, u64 size) +-{ +- u32 value; +- u64 size64 = ~(size - 1); +- +- if (win_num >= pcie->ppio_wins) { +- dev_err(&pcie->pdev->dev, +- "ERROR: max inbound windows reached !\n"); +- return; +- } +- +- value = csr_readl(pcie, PAB_PEX_AMAP_CTRL(win_num)); +- value &= ~(AMAP_CTRL_TYPE_MASK << AMAP_CTRL_TYPE_SHIFT | WIN_SIZE_MASK); +- value |= type << AMAP_CTRL_TYPE_SHIFT | 1 << AMAP_CTRL_EN_SHIFT | +- (lower_32_bits(size64) & WIN_SIZE_MASK); +- csr_writel(pcie, value, PAB_PEX_AMAP_CTRL(win_num)); +- +- csr_writel(pcie, upper_32_bits(size64), +- PAB_EXT_PEX_AMAP_SIZEN(win_num)); +- +- csr_writel(pcie, lower_32_bits(cpu_addr), +- PAB_PEX_AMAP_AXI_WIN(win_num)); +- csr_writel(pcie, upper_32_bits(cpu_addr), +- PAB_EXT_PEX_AMAP_AXI_WIN(win_num)); +- +- csr_writel(pcie, lower_32_bits(pci_addr), +- PAB_PEX_AMAP_PEX_WIN_L(win_num)); +- csr_writel(pcie, upper_32_bits(pci_addr), +- PAB_PEX_AMAP_PEX_WIN_H(win_num)); +- +- pcie->ib_wins_configured++; +-} +- +-/* +- * routine to program the outbound windows +- */ +-static void program_ob_windows(struct mobiveil_pcie *pcie, int win_num, +- u64 cpu_addr, u64 pci_addr, u32 type, u64 size) +-{ +- u32 value; +- u64 size64 = ~(size - 1); +- +- if (win_num >= pcie->apio_wins) { +- dev_err(&pcie->pdev->dev, +- "ERROR: max outbound windows reached !\n"); +- return; +- } +- +- /* +- * program Enable Bit to 1, Type Bit to (00) base 2, AXI Window Size Bit +- * to 4 KB in PAB_AXI_AMAP_CTRL register +- */ +- value = csr_readl(pcie, PAB_AXI_AMAP_CTRL(win_num)); +- value &= ~(WIN_TYPE_MASK << WIN_TYPE_SHIFT | WIN_SIZE_MASK); +- value |= 1 << WIN_ENABLE_SHIFT | type << WIN_TYPE_SHIFT | +- (lower_32_bits(size64) & WIN_SIZE_MASK); +- csr_writel(pcie, value, PAB_AXI_AMAP_CTRL(win_num)); +- +- csr_writel(pcie, upper_32_bits(size64), PAB_EXT_AXI_AMAP_SIZE(win_num)); +- +- /* +- * program AXI window base with appropriate value in +- * PAB_AXI_AMAP_AXI_WIN0 register +- */ +- csr_writel(pcie, lower_32_bits(cpu_addr) & (~AXI_WINDOW_ALIGN_MASK), +- PAB_AXI_AMAP_AXI_WIN(win_num)); +- csr_writel(pcie, upper_32_bits(cpu_addr), +- PAB_EXT_AXI_AMAP_AXI_WIN(win_num)); +- +- csr_writel(pcie, lower_32_bits(pci_addr), +- PAB_AXI_AMAP_PEX_WIN_L(win_num)); +- csr_writel(pcie, upper_32_bits(pci_addr), +- PAB_AXI_AMAP_PEX_WIN_H(win_num)); +- +- pcie->ob_wins_configured++; +-} +- +-static int mobiveil_bringup_link(struct mobiveil_pcie *pcie) +-{ +- int retries; +- +- /* check if the link is up or not */ +- for (retries = 0; retries < LINK_WAIT_MAX_RETRIES; retries++) { +- if (mobiveil_pcie_link_up(pcie)) +- return 0; +- +- usleep_range(LINK_WAIT_MIN, LINK_WAIT_MAX); +- } +- +- dev_err(&pcie->pdev->dev, "link never came up\n"); +- +- return -ETIMEDOUT; +-} +- +-static void mobiveil_pcie_enable_msi(struct mobiveil_pcie *pcie) +-{ +- phys_addr_t msg_addr = pcie->pcie_reg_base; +- struct mobiveil_msi *msi = &pcie->msi; +- +- pcie->msi.num_of_vectors = PCI_NUM_MSI; +- msi->msi_pages_phys = (phys_addr_t)msg_addr; +- +- writel_relaxed(lower_32_bits(msg_addr), +- pcie->apb_csr_base + MSI_BASE_LO_OFFSET); +- writel_relaxed(upper_32_bits(msg_addr), +- pcie->apb_csr_base + MSI_BASE_HI_OFFSET); +- writel_relaxed(4096, pcie->apb_csr_base + MSI_SIZE_OFFSET); +- writel_relaxed(1, pcie->apb_csr_base + MSI_ENABLE_OFFSET); +-} +- +-static int mobiveil_host_init(struct mobiveil_pcie *pcie) +-{ +- u32 value, pab_ctrl, type; +- struct resource_entry *win; +- +- /* setup bus numbers */ +- value = csr_readl(pcie, PCI_PRIMARY_BUS); +- value &= 0xff000000; +- value |= 0x00ff0100; +- csr_writel(pcie, value, PCI_PRIMARY_BUS); +- +- /* +- * program Bus Master Enable Bit in Command Register in PAB Config +- * Space +- */ +- value = csr_readl(pcie, PCI_COMMAND); +- value |= PCI_COMMAND_IO | PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER; +- csr_writel(pcie, value, PCI_COMMAND); +- +- /* +- * program PIO Enable Bit to 1 (and PEX PIO Enable to 1) in PAB_CTRL +- * register +- */ +- pab_ctrl = csr_readl(pcie, PAB_CTRL); +- pab_ctrl |= (1 << AMBA_PIO_ENABLE_SHIFT) | (1 << PEX_PIO_ENABLE_SHIFT); +- csr_writel(pcie, pab_ctrl, PAB_CTRL); +- +- csr_writel(pcie, (PAB_INTP_INTX_MASK | PAB_INTP_MSI_MASK), +- PAB_INTP_AMBA_MISC_ENB); +- +- /* +- * program PIO Enable Bit to 1 and Config Window Enable Bit to 1 in +- * PAB_AXI_PIO_CTRL Register +- */ +- value = csr_readl(pcie, PAB_AXI_PIO_CTRL); +- value |= APIO_EN_MASK; +- csr_writel(pcie, value, PAB_AXI_PIO_CTRL); +- +- /* Enable PCIe PIO master */ +- value = csr_readl(pcie, PAB_PEX_PIO_CTRL); +- value |= 1 << PIO_ENABLE_SHIFT; +- csr_writel(pcie, value, PAB_PEX_PIO_CTRL); +- +- /* +- * we'll program one outbound window for config reads and +- * another default inbound window for all the upstream traffic +- * rest of the outbound windows will be configured according to +- * the "ranges" field defined in device tree +- */ +- +- /* config outbound translation window */ +- program_ob_windows(pcie, WIN_NUM_0, pcie->ob_io_res->start, 0, +- CFG_WINDOW_TYPE, resource_size(pcie->ob_io_res)); +- +- /* memory inbound translation window */ +- program_ib_windows(pcie, WIN_NUM_0, 0, 0, MEM_WINDOW_TYPE, IB_WIN_SIZE); +- +- /* Get the I/O and memory ranges from DT */ +- resource_list_for_each_entry(win, &pcie->resources) { +- if (resource_type(win->res) == IORESOURCE_MEM) +- type = MEM_WINDOW_TYPE; +- else if (resource_type(win->res) == IORESOURCE_IO) +- type = IO_WINDOW_TYPE; +- else +- continue; +- +- /* configure outbound translation window */ +- program_ob_windows(pcie, pcie->ob_wins_configured, +- win->res->start, +- win->res->start - win->offset, +- type, resource_size(win->res)); +- } +- +- /* fixup for PCIe class register */ +- value = csr_readl(pcie, PAB_INTP_AXI_PIO_CLASS); +- value &= 0xff; +- value |= (PCI_CLASS_BRIDGE_PCI << 16); +- csr_writel(pcie, value, PAB_INTP_AXI_PIO_CLASS); +- +- /* setup MSI hardware registers */ +- mobiveil_pcie_enable_msi(pcie); +- +- return 0; +-} +- +-static void mobiveil_mask_intx_irq(struct irq_data *data) +-{ +- struct irq_desc *desc = irq_to_desc(data->irq); +- struct mobiveil_pcie *pcie; +- unsigned long flags; +- u32 mask, shifted_val; +- +- pcie = irq_desc_get_chip_data(desc); +- mask = 1 << ((data->hwirq + PAB_INTX_START) - 1); +- raw_spin_lock_irqsave(&pcie->intx_mask_lock, flags); +- shifted_val = csr_readl(pcie, PAB_INTP_AMBA_MISC_ENB); +- shifted_val &= ~mask; +- csr_writel(pcie, shifted_val, PAB_INTP_AMBA_MISC_ENB); +- raw_spin_unlock_irqrestore(&pcie->intx_mask_lock, flags); +-} +- +-static void mobiveil_unmask_intx_irq(struct irq_data *data) +-{ +- struct irq_desc *desc = irq_to_desc(data->irq); +- struct mobiveil_pcie *pcie; +- unsigned long flags; +- u32 shifted_val, mask; +- +- pcie = irq_desc_get_chip_data(desc); +- mask = 1 << ((data->hwirq + PAB_INTX_START) - 1); +- raw_spin_lock_irqsave(&pcie->intx_mask_lock, flags); +- shifted_val = csr_readl(pcie, PAB_INTP_AMBA_MISC_ENB); +- shifted_val |= mask; +- csr_writel(pcie, shifted_val, PAB_INTP_AMBA_MISC_ENB); +- raw_spin_unlock_irqrestore(&pcie->intx_mask_lock, flags); +-} +- +-static struct irq_chip intx_irq_chip = { +- .name = "mobiveil_pcie:intx", +- .irq_enable = mobiveil_unmask_intx_irq, +- .irq_disable = mobiveil_mask_intx_irq, +- .irq_mask = mobiveil_mask_intx_irq, +- .irq_unmask = mobiveil_unmask_intx_irq, +-}; +- +-/* routine to setup the INTx related data */ +-static int mobiveil_pcie_intx_map(struct irq_domain *domain, unsigned int irq, +- irq_hw_number_t hwirq) +-{ +- irq_set_chip_and_handler(irq, &intx_irq_chip, handle_level_irq); +- irq_set_chip_data(irq, domain->host_data); +- +- return 0; +-} +- +-/* INTx domain operations structure */ +-static const struct irq_domain_ops intx_domain_ops = { +- .map = mobiveil_pcie_intx_map, +-}; +- +-static struct irq_chip mobiveil_msi_irq_chip = { +- .name = "Mobiveil PCIe MSI", +- .irq_mask = pci_msi_mask_irq, +- .irq_unmask = pci_msi_unmask_irq, +-}; +- +-static struct msi_domain_info mobiveil_msi_domain_info = { +- .flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS | +- MSI_FLAG_PCI_MSIX), +- .chip = &mobiveil_msi_irq_chip, +-}; +- +-static void mobiveil_compose_msi_msg(struct irq_data *data, struct msi_msg *msg) +-{ +- struct mobiveil_pcie *pcie = irq_data_get_irq_chip_data(data); +- phys_addr_t addr = pcie->pcie_reg_base + (data->hwirq * sizeof(int)); +- +- msg->address_lo = lower_32_bits(addr); +- msg->address_hi = upper_32_bits(addr); +- msg->data = data->hwirq; +- +- dev_dbg(&pcie->pdev->dev, "msi#%d address_hi %#x address_lo %#x\n", +- (int)data->hwirq, msg->address_hi, msg->address_lo); +-} +- +-static int mobiveil_msi_set_affinity(struct irq_data *irq_data, +- const struct cpumask *mask, bool force) +-{ +- return -EINVAL; +-} +- +-static struct irq_chip mobiveil_msi_bottom_irq_chip = { +- .name = "Mobiveil MSI", +- .irq_compose_msi_msg = mobiveil_compose_msi_msg, +- .irq_set_affinity = mobiveil_msi_set_affinity, +-}; +- +-static int mobiveil_irq_msi_domain_alloc(struct irq_domain *domain, +- unsigned int virq, +- unsigned int nr_irqs, void *args) +-{ +- struct mobiveil_pcie *pcie = domain->host_data; +- struct mobiveil_msi *msi = &pcie->msi; +- unsigned long bit; +- +- WARN_ON(nr_irqs != 1); +- mutex_lock(&msi->lock); +- +- bit = find_first_zero_bit(msi->msi_irq_in_use, msi->num_of_vectors); +- if (bit >= msi->num_of_vectors) { +- mutex_unlock(&msi->lock); +- return -ENOSPC; +- } +- +- set_bit(bit, msi->msi_irq_in_use); +- +- mutex_unlock(&msi->lock); +- +- irq_domain_set_info(domain, virq, bit, &mobiveil_msi_bottom_irq_chip, +- domain->host_data, handle_level_irq, NULL, NULL); +- return 0; +-} +- +-static void mobiveil_irq_msi_domain_free(struct irq_domain *domain, +- unsigned int virq, +- unsigned int nr_irqs) +-{ +- struct irq_data *d = irq_domain_get_irq_data(domain, virq); +- struct mobiveil_pcie *pcie = irq_data_get_irq_chip_data(d); +- struct mobiveil_msi *msi = &pcie->msi; +- +- mutex_lock(&msi->lock); +- +- if (!test_bit(d->hwirq, msi->msi_irq_in_use)) +- dev_err(&pcie->pdev->dev, "trying to free unused MSI#%lu\n", +- d->hwirq); +- else +- __clear_bit(d->hwirq, msi->msi_irq_in_use); +- +- mutex_unlock(&msi->lock); +-} +-static const struct irq_domain_ops msi_domain_ops = { +- .alloc = mobiveil_irq_msi_domain_alloc, +- .free = mobiveil_irq_msi_domain_free, +-}; +- +-static int mobiveil_allocate_msi_domains(struct mobiveil_pcie *pcie) +-{ +- struct device *dev = &pcie->pdev->dev; +- struct fwnode_handle *fwnode = of_node_to_fwnode(dev->of_node); +- struct mobiveil_msi *msi = &pcie->msi; +- +- mutex_init(&pcie->msi.lock); +- msi->dev_domain = irq_domain_add_linear(NULL, msi->num_of_vectors, +- &msi_domain_ops, pcie); +- if (!msi->dev_domain) { +- dev_err(dev, "failed to create IRQ domain\n"); +- return -ENOMEM; +- } +- +- msi->msi_domain = pci_msi_create_irq_domain(fwnode, +- &mobiveil_msi_domain_info, +- msi->dev_domain); +- if (!msi->msi_domain) { +- dev_err(dev, "failed to create MSI domain\n"); +- irq_domain_remove(msi->dev_domain); +- return -ENOMEM; +- } +- +- return 0; +-} +- +-static int mobiveil_pcie_init_irq_domain(struct mobiveil_pcie *pcie) +-{ +- struct device *dev = &pcie->pdev->dev; +- struct device_node *node = dev->of_node; +- int ret; +- +- /* setup INTx */ +- pcie->intx_domain = irq_domain_add_linear(node, PCI_NUM_INTX, +- &intx_domain_ops, pcie); +- +- if (!pcie->intx_domain) { +- dev_err(dev, "Failed to get a INTx IRQ domain\n"); +- return -ENOMEM; +- } +- +- raw_spin_lock_init(&pcie->intx_mask_lock); +- +- /* setup MSI */ +- ret = mobiveil_allocate_msi_domains(pcie); +- if (ret) +- return ret; +- +- return 0; +-} +- +-static int mobiveil_pcie_probe(struct platform_device *pdev) +-{ +- struct mobiveil_pcie *pcie; +- struct pci_bus *bus; +- struct pci_bus *child; +- struct pci_host_bridge *bridge; +- struct device *dev = &pdev->dev; +- resource_size_t iobase; +- int ret; +- +- /* allocate the PCIe port */ +- bridge = devm_pci_alloc_host_bridge(dev, sizeof(*pcie)); +- if (!bridge) +- return -ENOMEM; +- +- pcie = pci_host_bridge_priv(bridge); +- +- pcie->pdev = pdev; +- +- ret = mobiveil_pcie_parse_dt(pcie); +- if (ret) { +- dev_err(dev, "Parsing DT failed, ret: %x\n", ret); +- return ret; +- } +- +- INIT_LIST_HEAD(&pcie->resources); +- +- /* parse the host bridge base addresses from the device tree file */ +- ret = devm_of_pci_get_host_bridge_resources(dev, 0, 0xff, +- &pcie->resources, &iobase); +- if (ret) { +- dev_err(dev, "Getting bridge resources failed\n"); +- return ret; +- } +- +- /* +- * configure all inbound and outbound windows and prepare the RC for +- * config access +- */ +- ret = mobiveil_host_init(pcie); +- if (ret) { +- dev_err(dev, "Failed to initialize host\n"); +- goto error; +- } +- +- /* initialize the IRQ domains */ +- ret = mobiveil_pcie_init_irq_domain(pcie); +- if (ret) { +- dev_err(dev, "Failed creating IRQ Domain\n"); +- goto error; +- } +- +- irq_set_chained_handler_and_data(pcie->irq, mobiveil_pcie_isr, pcie); +- +- ret = devm_request_pci_bus_resources(dev, &pcie->resources); +- if (ret) +- goto error; +- +- /* Initialize bridge */ +- list_splice_init(&pcie->resources, &bridge->windows); +- bridge->dev.parent = dev; +- bridge->sysdata = pcie; +- bridge->busnr = pcie->root_bus_nr; +- bridge->ops = &mobiveil_pcie_ops; +- bridge->map_irq = of_irq_parse_and_map_pci; +- bridge->swizzle_irq = pci_common_swizzle; +- +- ret = mobiveil_bringup_link(pcie); +- if (ret) { +- dev_info(dev, "link bring-up failed\n"); +- goto error; +- } +- +- /* setup the kernel resources for the newly added PCIe root bus */ +- ret = pci_scan_root_bus_bridge(bridge); +- if (ret) +- goto error; +- +- bus = bridge->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); +- +- return 0; +-error: +- pci_free_resource_list(&pcie->resources); +- return ret; +-} +- +-static const struct of_device_id mobiveil_pcie_of_match[] = { +- {.compatible = "mbvl,gpex40-pcie",}, +- {}, +-}; +- +-MODULE_DEVICE_TABLE(of, mobiveil_pcie_of_match); +- +-static struct platform_driver mobiveil_pcie_driver = { +- .probe = mobiveil_pcie_probe, +- .driver = { +- .name = "mobiveil-pcie", +- .of_match_table = mobiveil_pcie_of_match, +- .suppress_bind_attrs = true, +- }, +-}; +- +-builtin_platform_driver(mobiveil_pcie_driver); +- +-MODULE_LICENSE("GPL v2"); +-MODULE_DESCRIPTION("Mobiveil PCIe host controller driver"); +-MODULE_AUTHOR("Subrahmanya Lingappa <l.subrahmanya@mobiveil.co.in>"); |