2019-09-19 14:43:19 +00:00
|
|
|
From cd3af4fa73ab25353f0865ebe8e0d2af1fd2a50b Mon Sep 17 00:00:00 2001
|
2019-07-09 18:32:28 +00:00
|
|
|
From: Phil Elwell <phil@raspberrypi.org>
|
|
|
|
Date: Tue, 19 Feb 2019 22:06:59 +0000
|
2019-12-23 12:42:55 +00:00
|
|
|
Subject: [PATCH] PCI: brcmstb: Add MSI capability
|
2019-07-09 18:32:28 +00:00
|
|
|
|
|
|
|
This commit adds MSI to the Broadcom STB PCIe host controller. It does
|
|
|
|
not add MSIX since that functionality is not in the HW. The MSI
|
|
|
|
controller is physically located within the PCIe block, however, there
|
|
|
|
is no reason why the MSI controller could not be moved elsewhere in
|
|
|
|
the future.
|
|
|
|
|
|
|
|
Since the internal Brcmstb MSI controller is intertwined with the PCIe
|
|
|
|
controller, it is not its own platform device but rather part of the
|
|
|
|
PCIe platform device.
|
|
|
|
|
|
|
|
Signed-off-by: Jim Quinlan <jim2101024@gmail.com>
|
|
|
|
---
|
|
|
|
drivers/pci/controller/pcie-brcmstb.c | 374 ++++++++++++++++++++++++--
|
|
|
|
1 file changed, 353 insertions(+), 21 deletions(-)
|
|
|
|
|
|
|
|
--- a/drivers/pci/controller/pcie-brcmstb.c
|
|
|
|
+++ b/drivers/pci/controller/pcie-brcmstb.c
|
|
|
|
@@ -1,6 +1,7 @@
|
|
|
|
// SPDX-License-Identifier: GPL-2.0
|
|
|
|
/* Copyright (C) 2009 - 2017 Broadcom */
|
|
|
|
|
|
|
|
+#include <linux/bitops.h>
|
|
|
|
#include <linux/clk.h>
|
|
|
|
#include <linux/compiler.h>
|
|
|
|
#include <linux/delay.h>
|
|
|
|
@@ -9,11 +10,13 @@
|
|
|
|
#include <linux/interrupt.h>
|
|
|
|
#include <linux/io.h>
|
|
|
|
#include <linux/ioport.h>
|
|
|
|
+#include <linux/irqchip/chained_irq.h>
|
|
|
|
#include <linux/irqdomain.h>
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/list.h>
|
|
|
|
#include <linux/log2.h>
|
|
|
|
#include <linux/module.h>
|
|
|
|
+#include <linux/msi.h>
|
|
|
|
#include <linux/of_address.h>
|
|
|
|
#include <linux/of_irq.h>
|
|
|
|
#include <linux/of_pci.h>
|
|
|
|
@@ -47,6 +50,9 @@
|
|
|
|
#define PCIE_MISC_RC_BAR2_CONFIG_LO 0x4034
|
|
|
|
#define PCIE_MISC_RC_BAR2_CONFIG_HI 0x4038
|
|
|
|
#define PCIE_MISC_RC_BAR3_CONFIG_LO 0x403c
|
|
|
|
+#define PCIE_MISC_MSI_BAR_CONFIG_LO 0x4044
|
|
|
|
+#define PCIE_MISC_MSI_BAR_CONFIG_HI 0x4048
|
|
|
|
+#define PCIE_MISC_MSI_DATA_CONFIG 0x404c
|
|
|
|
#define PCIE_MISC_PCIE_CTRL 0x4064
|
|
|
|
#define PCIE_MISC_PCIE_STATUS 0x4068
|
|
|
|
#define PCIE_MISC_REVISION 0x406c
|
|
|
|
@@ -55,6 +61,7 @@
|
|
|
|
#define PCIE_MISC_CPU_2_PCIE_MEM_WIN0_LIMIT_HI 0x4084
|
|
|
|
#define PCIE_MISC_HARD_PCIE_HARD_DEBUG 0x4204
|
|
|
|
#define PCIE_INTR2_CPU_BASE 0x4300
|
|
|
|
+#define PCIE_MSI_INTR2_BASE 0x4500
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Broadcom Settop Box PCIe Register Field shift and mask info. The
|
|
|
|
@@ -115,6 +122,8 @@
|
|
|
|
|
|
|
|
#define BRCM_NUM_PCIE_OUT_WINS 0x4
|
|
|
|
#define BRCM_MAX_SCB 0x4
|
|
|
|
+#define BRCM_INT_PCI_MSI_NR 32
|
|
|
|
+#define BRCM_PCIE_HW_REV_33 0x0303
|
|
|
|
|
|
|
|
#define BRCM_MSI_TARGET_ADDR_LT_4GB 0x0fffffffcULL
|
|
|
|
#define BRCM_MSI_TARGET_ADDR_GT_4GB 0xffffffffcULL
|
|
|
|
@@ -203,6 +212,33 @@ struct brcm_window {
|
|
|
|
dma_addr_t size;
|
|
|
|
};
|
|
|
|
|
|
|
|
+struct brcm_msi {
|
|
|
|
+ struct device *dev;
|
|
|
|
+ void __iomem *base;
|
|
|
|
+ struct device_node *dn;
|
|
|
|
+ struct irq_domain *msi_domain;
|
|
|
|
+ struct irq_domain *inner_domain;
|
|
|
|
+ struct mutex lock; /* guards the alloc/free operations */
|
|
|
|
+ u64 target_addr;
|
|
|
|
+ int irq;
|
|
|
|
+
|
|
|
|
+ /* intr_base is the base pointer for interrupt status/set/clr regs */
|
|
|
|
+ void __iomem *intr_base;
|
|
|
|
+
|
|
|
|
+ /* intr_legacy_mask indicates how many bits are MSI interrupts */
|
|
|
|
+ u32 intr_legacy_mask;
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * intr_legacy_offset indicates bit position of MSI_01. It is
|
|
|
|
+ * to map the register bit position to a hwirq that starts at 0.
|
|
|
|
+ */
|
|
|
|
+ u32 intr_legacy_offset;
|
|
|
|
+
|
|
|
|
+ /* used indicates which MSI interrupts have been alloc'd */
|
|
|
|
+ unsigned long used;
|
|
|
|
+ unsigned int rev;
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
/* Internal PCIe Host Controller Information.*/
|
|
|
|
struct brcm_pcie {
|
|
|
|
struct device *dev;
|
|
|
|
@@ -217,7 +253,10 @@ struct brcm_pcie {
|
|
|
|
int num_out_wins;
|
|
|
|
bool ssc;
|
|
|
|
int gen;
|
|
|
|
+ u64 msi_target_addr;
|
|
|
|
struct brcm_window out_wins[BRCM_NUM_PCIE_OUT_WINS];
|
|
|
|
+ struct brcm_msi *msi;
|
|
|
|
+ bool msi_internal;
|
|
|
|
unsigned int rev;
|
|
|
|
const int *reg_offsets;
|
|
|
|
const int *reg_field_info;
|
|
|
|
@@ -225,9 +264,9 @@ struct brcm_pcie {
|
|
|
|
};
|
|
|
|
|
|
|
|
struct pcie_cfg_data {
|
|
|
|
- const int *reg_field_info;
|
|
|
|
- const int *offsets;
|
|
|
|
- const enum pcie_type type;
|
|
|
|
+ const int *reg_field_info;
|
|
|
|
+ const int *offsets;
|
|
|
|
+ const enum pcie_type type;
|
|
|
|
};
|
|
|
|
|
|
|
|
static const int pcie_reg_field_info[] = {
|
|
|
|
@@ -828,6 +867,267 @@ static void brcm_pcie_set_outbound_win(s
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
+static struct irq_chip brcm_msi_irq_chip = {
|
|
|
|
+ .name = "Brcm_MSI",
|
|
|
|
+ .irq_mask = pci_msi_mask_irq,
|
|
|
|
+ .irq_unmask = pci_msi_unmask_irq,
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+static struct msi_domain_info brcm_msi_domain_info = {
|
|
|
|
+ .flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS |
|
|
|
|
+ MSI_FLAG_PCI_MSIX),
|
|
|
|
+ .chip = &brcm_msi_irq_chip,
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+static void brcm_pcie_msi_isr(struct irq_desc *desc)
|
|
|
|
+{
|
|
|
|
+ struct irq_chip *chip = irq_desc_get_chip(desc);
|
|
|
|
+ struct brcm_msi *msi;
|
|
|
|
+ unsigned long status, virq;
|
|
|
|
+ u32 mask, bit, hwirq;
|
|
|
|
+ struct device *dev;
|
|
|
|
+
|
|
|
|
+ chained_irq_enter(chip, desc);
|
|
|
|
+ msi = irq_desc_get_handler_data(desc);
|
|
|
|
+ mask = msi->intr_legacy_mask;
|
|
|
|
+ dev = msi->dev;
|
|
|
|
+
|
|
|
|
+ while ((status = bcm_readl(msi->intr_base + STATUS) & mask)) {
|
|
|
|
+ for_each_set_bit(bit, &status, BRCM_INT_PCI_MSI_NR) {
|
|
|
|
+ /* clear the interrupt */
|
|
|
|
+ bcm_writel(1 << bit, msi->intr_base + CLR);
|
|
|
|
+
|
|
|
|
+ /* Account for legacy interrupt offset */
|
|
|
|
+ hwirq = bit - msi->intr_legacy_offset;
|
|
|
|
+
|
|
|
|
+ virq = irq_find_mapping(msi->inner_domain, hwirq);
|
|
|
|
+ if (virq) {
|
|
|
|
+ if (msi->used & (1 << hwirq))
|
|
|
|
+ generic_handle_irq(virq);
|
|
|
|
+ else
|
|
|
|
+ dev_info(dev, "unhandled MSI %d\n",
|
|
|
|
+ hwirq);
|
|
|
|
+ } else {
|
|
|
|
+ /* Unknown MSI, just clear it */
|
|
|
|
+ dev_dbg(dev, "unexpected MSI\n");
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ chained_irq_exit(chip, desc);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void brcm_compose_msi_msg(struct irq_data *data, struct msi_msg *msg)
|
|
|
|
+{
|
|
|
|
+ struct brcm_msi *msi = irq_data_get_irq_chip_data(data);
|
|
|
|
+ u32 temp;
|
|
|
|
+
|
|
|
|
+ msg->address_lo = lower_32_bits(msi->target_addr);
|
|
|
|
+ msg->address_hi = upper_32_bits(msi->target_addr);
|
|
|
|
+ temp = bcm_readl(msi->base + PCIE_MISC_MSI_DATA_CONFIG);
|
|
|
|
+ msg->data = ((temp >> 16) & (temp & 0xffff)) | data->hwirq;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int brcm_msi_set_affinity(struct irq_data *irq_data,
|
|
|
|
+ const struct cpumask *mask, bool force)
|
|
|
|
+{
|
|
|
|
+ return -EINVAL;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static struct irq_chip brcm_msi_bottom_irq_chip = {
|
|
|
|
+ .name = "Brcm_MSI",
|
|
|
|
+ .irq_compose_msi_msg = brcm_compose_msi_msg,
|
|
|
|
+ .irq_set_affinity = brcm_msi_set_affinity,
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+static int brcm_msi_alloc(struct brcm_msi *msi)
|
|
|
|
+{
|
|
|
|
+ int bit, hwirq;
|
|
|
|
+
|
|
|
|
+ mutex_lock(&msi->lock);
|
|
|
|
+ bit = ~msi->used ? ffz(msi->used) : -1;
|
|
|
|
+
|
|
|
|
+ if (bit >= 0 && bit < BRCM_INT_PCI_MSI_NR) {
|
|
|
|
+ msi->used |= (1 << bit);
|
|
|
|
+ hwirq = bit - msi->intr_legacy_offset;
|
|
|
|
+ } else {
|
|
|
|
+ hwirq = -ENOSPC;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ mutex_unlock(&msi->lock);
|
|
|
|
+ return hwirq;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void brcm_msi_free(struct brcm_msi *msi, unsigned long hwirq)
|
|
|
|
+{
|
|
|
|
+ mutex_lock(&msi->lock);
|
|
|
|
+ msi->used &= ~(1 << (hwirq + msi->intr_legacy_offset));
|
|
|
|
+ mutex_unlock(&msi->lock);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int brcm_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,
|
|
|
|
+ unsigned int nr_irqs, void *args)
|
|
|
|
+{
|
|
|
|
+ struct brcm_msi *msi = domain->host_data;
|
|
|
|
+ int hwirq;
|
|
|
|
+
|
|
|
|
+ hwirq = brcm_msi_alloc(msi);
|
|
|
|
+
|
|
|
|
+ if (hwirq < 0)
|
|
|
|
+ return hwirq;
|
|
|
|
+
|
|
|
|
+ irq_domain_set_info(domain, virq, (irq_hw_number_t)hwirq,
|
|
|
|
+ &brcm_msi_bottom_irq_chip, domain->host_data,
|
|
|
|
+ handle_simple_irq, NULL, NULL);
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void brcm_irq_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 brcm_msi *msi = irq_data_get_irq_chip_data(d);
|
|
|
|
+
|
|
|
|
+ brcm_msi_free(msi, d->hwirq);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void brcm_msi_set_regs(struct brcm_msi *msi)
|
|
|
|
+{
|
|
|
|
+ u32 data_val, msi_lo, msi_hi;
|
|
|
|
+
|
|
|
|
+ if (msi->rev >= BRCM_PCIE_HW_REV_33) {
|
|
|
|
+ /*
|
|
|
|
+ * ffe0 -- least sig 5 bits are 0 indicating 32 msgs
|
|
|
|
+ * 6540 -- this is our arbitrary unique data value
|
|
|
|
+ */
|
|
|
|
+ data_val = 0xffe06540;
|
|
|
|
+ } else {
|
|
|
|
+ /*
|
|
|
|
+ * fff8 -- least sig 3 bits are 0 indicating 8 msgs
|
|
|
|
+ * 6540 -- this is our arbitrary unique data value
|
|
|
|
+ */
|
|
|
|
+ data_val = 0xfff86540;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * Make sure we are not masking MSIs. Note that MSIs can be masked,
|
|
|
|
+ * but that occurs on the PCIe EP device
|
|
|
|
+ */
|
|
|
|
+ bcm_writel(0xffffffff & msi->intr_legacy_mask,
|
|
|
|
+ msi->intr_base + MASK_CLR);
|
|
|
|
+
|
|
|
|
+ msi_lo = lower_32_bits(msi->target_addr);
|
|
|
|
+ msi_hi = upper_32_bits(msi->target_addr);
|
|
|
|
+ /*
|
|
|
|
+ * The 0 bit of PCIE_MISC_MSI_BAR_CONFIG_LO is repurposed to MSI
|
|
|
|
+ * enable, which we set to 1.
|
|
|
|
+ */
|
|
|
|
+ bcm_writel(msi_lo | 1, msi->base + PCIE_MISC_MSI_BAR_CONFIG_LO);
|
|
|
|
+ bcm_writel(msi_hi, msi->base + PCIE_MISC_MSI_BAR_CONFIG_HI);
|
|
|
|
+ bcm_writel(data_val, msi->base + PCIE_MISC_MSI_DATA_CONFIG);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static const struct irq_domain_ops msi_domain_ops = {
|
|
|
|
+ .alloc = brcm_irq_domain_alloc,
|
|
|
|
+ .free = brcm_irq_domain_free,
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+static int brcm_allocate_domains(struct brcm_msi *msi)
|
|
|
|
+{
|
|
|
|
+ struct fwnode_handle *fwnode = of_node_to_fwnode(msi->dn);
|
|
|
|
+ struct device *dev = msi->dev;
|
|
|
|
+
|
|
|
|
+ msi->inner_domain = irq_domain_add_linear(NULL, BRCM_INT_PCI_MSI_NR,
|
|
|
|
+ &msi_domain_ops, msi);
|
|
|
|
+ if (!msi->inner_domain) {
|
|
|
|
+ dev_err(dev, "failed to create IRQ domain\n");
|
|
|
|
+ return -ENOMEM;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ msi->msi_domain = pci_msi_create_irq_domain(fwnode,
|
|
|
|
+ &brcm_msi_domain_info,
|
|
|
|
+ msi->inner_domain);
|
|
|
|
+ if (!msi->msi_domain) {
|
|
|
|
+ dev_err(dev, "failed to create MSI domain\n");
|
|
|
|
+ irq_domain_remove(msi->inner_domain);
|
|
|
|
+ return -ENOMEM;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void brcm_free_domains(struct brcm_msi *msi)
|
|
|
|
+{
|
|
|
|
+ irq_domain_remove(msi->msi_domain);
|
|
|
|
+ irq_domain_remove(msi->inner_domain);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void brcm_msi_remove(struct brcm_pcie *pcie)
|
|
|
|
+{
|
|
|
|
+ struct brcm_msi *msi = pcie->msi;
|
|
|
|
+
|
|
|
|
+ if (!msi)
|
|
|
|
+ return;
|
|
|
|
+ irq_set_chained_handler(msi->irq, NULL);
|
|
|
|
+ irq_set_handler_data(msi->irq, NULL);
|
|
|
|
+ brcm_free_domains(msi);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int brcm_pcie_enable_msi(struct brcm_pcie *pcie)
|
|
|
|
+{
|
|
|
|
+ struct brcm_msi *msi;
|
|
|
|
+ int irq, ret;
|
|
|
|
+ struct device *dev = pcie->dev;
|
|
|
|
+
|
|
|
|
+ irq = irq_of_parse_and_map(dev->of_node, 1);
|
|
|
|
+ if (irq <= 0) {
|
|
|
|
+ dev_err(dev, "cannot map msi intr\n");
|
|
|
|
+ return -ENODEV;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ msi = devm_kzalloc(dev, sizeof(struct brcm_msi), GFP_KERNEL);
|
|
|
|
+ if (!msi)
|
|
|
|
+ return -ENOMEM;
|
|
|
|
+
|
|
|
|
+ msi->dev = dev;
|
|
|
|
+ msi->base = pcie->base;
|
|
|
|
+ msi->rev = pcie->rev;
|
|
|
|
+ msi->dn = pcie->dn;
|
|
|
|
+ msi->target_addr = pcie->msi_target_addr;
|
|
|
|
+ msi->irq = irq;
|
|
|
|
+
|
|
|
|
+ ret = brcm_allocate_domains(msi);
|
|
|
|
+ if (ret)
|
|
|
|
+ return ret;
|
|
|
|
+
|
|
|
|
+ irq_set_chained_handler_and_data(msi->irq, brcm_pcie_msi_isr, msi);
|
|
|
|
+
|
|
|
|
+ if (msi->rev >= BRCM_PCIE_HW_REV_33) {
|
|
|
|
+ msi->intr_base = msi->base + PCIE_MSI_INTR2_BASE;
|
|
|
|
+ /*
|
|
|
|
+ * This version of PCIe hw has only 32 intr bits
|
|
|
|
+ * starting at bit position 0.
|
|
|
|
+ */
|
|
|
|
+ msi->intr_legacy_mask = 0xffffffff;
|
|
|
|
+ msi->intr_legacy_offset = 0x0;
|
|
|
|
+ msi->used = 0x0;
|
|
|
|
+
|
|
|
|
+ } else {
|
|
|
|
+ msi->intr_base = msi->base + PCIE_INTR2_CPU_BASE;
|
|
|
|
+ /*
|
|
|
|
+ * This version of PCIe hw has only 8 intr bits starting
|
|
|
|
+ * at bit position 24.
|
|
|
|
+ */
|
|
|
|
+ msi->intr_legacy_mask = 0xff000000;
|
|
|
|
+ msi->intr_legacy_offset = 24;
|
|
|
|
+ msi->used = 0x00ffffff;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ brcm_msi_set_regs(msi);
|
|
|
|
+ pcie->msi = msi;
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
/* Configuration space read/write support */
|
|
|
|
static int cfg_index(int busnr, int devfn, int reg)
|
|
|
|
{
|
|
|
|
@@ -1072,6 +1372,7 @@ static int brcm_pcie_setup(struct brcm_p
|
|
|
|
u16 nlw, cls, lnksta;
|
|
|
|
bool ssc_good = false;
|
|
|
|
struct device *dev = pcie->dev;
|
|
|
|
+ u64 msi_target_addr;
|
|
|
|
|
|
|
|
/* Reset the bridge */
|
|
|
|
brcm_pcie_bridge_sw_init_set(pcie, 1);
|
|
|
|
@@ -1116,27 +1417,24 @@ static int brcm_pcie_setup(struct brcm_p
|
|
|
|
* The PCIe host controller by design must set the inbound
|
|
|
|
* viewport to be a contiguous arrangement of all of the
|
|
|
|
* system's memory. In addition, its size mut be a power of
|
|
|
|
- * two. To further complicate matters, the viewport must
|
|
|
|
- * start on a pcie-address that is aligned on a multiple of its
|
|
|
|
- * size. If a portion of the viewport does not represent
|
|
|
|
- * system memory -- e.g. 3GB of memory requires a 4GB viewport
|
|
|
|
- * -- we can map the outbound memory in or after 3GB and even
|
|
|
|
- * though the viewport will overlap the outbound memory the
|
|
|
|
- * controller will know to send outbound memory downstream and
|
|
|
|
- * everything else upstream.
|
|
|
|
+ * two. Further, the MSI target address must NOT be placed
|
|
|
|
+ * inside this region, as the decoding logic will consider its
|
|
|
|
+ * address to be inbound memory traffic. To further
|
|
|
|
+ * complicate matters, the viewport must start on a
|
|
|
|
+ * pcie-address that is aligned on a multiple of its size.
|
|
|
|
+ * If a portion of the viewport does not represent system
|
|
|
|
+ * memory -- e.g. 3GB of memory requires a 4GB viewport --
|
|
|
|
+ * we can map the outbound memory in or after 3GB and even
|
|
|
|
+ * though the viewport will overlap the outbound memory
|
|
|
|
+ * the controller will know to send outbound memory downstream
|
|
|
|
+ * and everything else upstream.
|
|
|
|
*/
|
|
|
|
rc_bar2_size = roundup_pow_of_two_64(total_mem_size);
|
|
|
|
|
|
|
|
- /*
|
|
|
|
- * Set simple configuration based on memory sizes
|
|
|
|
- * only. We always start the viewport at address 0.
|
|
|
|
- */
|
|
|
|
- rc_bar2_offset = 0;
|
|
|
|
-
|
|
|
|
if (dma_ranges) {
|
|
|
|
/*
|
|
|
|
* The best-case scenario is to place the inbound
|
|
|
|
- * region in the first 4GB of pci-space, as some
|
|
|
|
+ * region in the first 4GB of pcie-space, as some
|
|
|
|
* legacy devices can only address 32bits.
|
|
|
|
* We would also like to put the MSI under 4GB
|
|
|
|
* as well, since some devices require a 32bit
|
|
|
|
@@ -1145,6 +1443,14 @@ static int brcm_pcie_setup(struct brcm_p
|
|
|
|
if (total_mem_size <= 0xc0000000ULL &&
|
|
|
|
rc_bar2_size <= 0x100000000ULL) {
|
|
|
|
rc_bar2_offset = 0;
|
|
|
|
+ /* If the viewport is less then 4GB we can fit
|
|
|
|
+ * the MSI target address under 4GB. Otherwise
|
|
|
|
+ * put it right below 64GB.
|
|
|
|
+ */
|
|
|
|
+ msi_target_addr =
|
|
|
|
+ (rc_bar2_size == 0x100000000ULL)
|
|
|
|
+ ? BRCM_MSI_TARGET_ADDR_GT_4GB
|
|
|
|
+ : BRCM_MSI_TARGET_ADDR_LT_4GB;
|
|
|
|
} else {
|
|
|
|
/*
|
|
|
|
* The system memory is 4GB or larger so we
|
|
|
|
@@ -1154,8 +1460,12 @@ static int brcm_pcie_setup(struct brcm_p
|
|
|
|
* start it at the 1x multiple of its size
|
|
|
|
*/
|
|
|
|
rc_bar2_offset = rc_bar2_size;
|
|
|
|
- }
|
|
|
|
|
|
|
|
+ /* Since we are starting the viewport at 4GB or
|
|
|
|
+ * higher, put the MSI target address below 4GB
|
|
|
|
+ */
|
|
|
|
+ msi_target_addr = BRCM_MSI_TARGET_ADDR_LT_4GB;
|
|
|
|
+ }
|
|
|
|
} else {
|
|
|
|
/*
|
|
|
|
* Set simple configuration based on memory sizes
|
|
|
|
@@ -1163,7 +1473,12 @@ static int brcm_pcie_setup(struct brcm_p
|
|
|
|
* and set the MSI target address accordingly.
|
|
|
|
*/
|
|
|
|
rc_bar2_offset = 0;
|
|
|
|
+
|
|
|
|
+ msi_target_addr = (rc_bar2_size >= 0x100000000ULL)
|
|
|
|
+ ? BRCM_MSI_TARGET_ADDR_GT_4GB
|
|
|
|
+ : BRCM_MSI_TARGET_ADDR_LT_4GB;
|
|
|
|
}
|
|
|
|
+ pcie->msi_target_addr = msi_target_addr;
|
|
|
|
|
|
|
|
tmp = lower_32_bits(rc_bar2_offset);
|
|
|
|
tmp = INSERT_FIELD(tmp, PCIE_MISC_RC_BAR2_CONFIG_LO, SIZE,
|
|
|
|
@@ -1333,6 +1648,9 @@ static int brcm_pcie_resume(struct devic
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
+ if (pcie->msi && pcie->msi_internal)
|
|
|
|
+ brcm_msi_set_regs(pcie->msi);
|
|
|
|
+
|
|
|
|
pcie->suspended = false;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
@@ -1340,6 +1658,7 @@ static int brcm_pcie_resume(struct devic
|
|
|
|
|
|
|
|
static void _brcm_pcie_remove(struct brcm_pcie *pcie)
|
|
|
|
{
|
|
|
|
+ brcm_msi_remove(pcie);
|
|
|
|
turn_off(pcie);
|
|
|
|
clk_disable_unprepare(pcie->clk);
|
|
|
|
clk_put(pcie->clk);
|
|
|
|
@@ -1368,7 +1687,7 @@ MODULE_DEVICE_TABLE(of, brcm_pcie_match)
|
|
|
|
|
|
|
|
static int brcm_pcie_probe(struct platform_device *pdev)
|
|
|
|
{
|
|
|
|
- struct device_node *dn = pdev->dev.of_node;
|
|
|
|
+ struct device_node *dn = pdev->dev.of_node, *msi_dn;
|
|
|
|
const struct of_device_id *of_id;
|
|
|
|
const struct pcie_cfg_data *data;
|
|
|
|
int ret;
|
|
|
|
@@ -1448,6 +1767,20 @@ static int brcm_pcie_probe(struct platfo
|
|
|
|
if (ret)
|
|
|
|
goto fail;
|
|
|
|
|
|
|
|
+ msi_dn = of_parse_phandle(pcie->dn, "msi-parent", 0);
|
|
|
|
+ /* Use the internal MSI if no msi-parent property */
|
|
|
|
+ if (!msi_dn)
|
|
|
|
+ msi_dn = pcie->dn;
|
|
|
|
+
|
|
|
|
+ if (pci_msi_enabled() && msi_dn == pcie->dn) {
|
|
|
|
+ ret = brcm_pcie_enable_msi(pcie);
|
|
|
|
+ if (ret)
|
|
|
|
+ dev_err(pcie->dev,
|
|
|
|
+ "probe of internal MSI failed: %d)", ret);
|
|
|
|
+ else
|
|
|
|
+ pcie->msi_internal = true;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
list_splice_init(&pcie->resources, &bridge->windows);
|
|
|
|
bridge->dev.parent = &pdev->dev;
|
|
|
|
bridge->busnr = 0;
|
|
|
|
@@ -1470,7 +1803,6 @@ static int brcm_pcie_probe(struct platfo
|
|
|
|
pcie->root_bus = bridge->bus;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
-
|
|
|
|
fail:
|
|
|
|
_brcm_pcie_remove(pcie);
|
|
|
|
return ret;
|