diff mbox series

[6/8] PCI: host: brcmstb: add MSI capability

Message ID 1508868949-16652-7-git-send-email-jim2101024@gmail.com
State Changes Requested
Headers show
Series [1/8] SOC: brcmstb: add memory API | expand

Commit Message

Jim Quinlan Oct. 24, 2017, 6:15 p.m. UTC
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/host/Kconfig           |  12 ++
 drivers/pci/host/Makefile          |   1 +
 drivers/pci/host/pci-brcmstb-msi.c | 318 +++++++++++++++++++++++++++++++++++++
 drivers/pci/host/pci-brcmstb.c     |  72 +++++++--
 drivers/pci/host/pci-brcmstb.h     |  26 +++
 5 files changed, 419 insertions(+), 10 deletions(-)
 create mode 100644 drivers/pci/host/pci-brcmstb-msi.c

Comments

Florian Fainelli Oct. 24, 2017, 6:57 p.m. UTC | #1
Hi Jim,

On 10/24/2017 11:15 AM, Jim Quinlan wrote:
> 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/host/Kconfig           |  12 ++
>  drivers/pci/host/Makefile          |   1 +
>  drivers/pci/host/pci-brcmstb-msi.c | 318 +++++++++++++++++++++++++++++++++++++
>  drivers/pci/host/pci-brcmstb.c     |  72 +++++++--
>  drivers/pci/host/pci-brcmstb.h     |  26 +++
>  5 files changed, 419 insertions(+), 10 deletions(-)
>  create mode 100644 drivers/pci/host/pci-brcmstb-msi.c
> 
> diff --git a/drivers/pci/host/Kconfig b/drivers/pci/host/Kconfig
> index b9b4f11..54aa5d2 100644
> --- a/drivers/pci/host/Kconfig
> +++ b/drivers/pci/host/Kconfig
> @@ -228,4 +228,16 @@ config PCI_BRCMSTB
>  	default ARCH_BRCMSTB || BMIPS_GENERIC
>  	help
>  	  Adds support for Broadcom Settop Box PCIe host controller.
> +	  To compile this driver as a module, choose m here.
> +
> +config PCI_BRCMSTB_MSI
> +	bool "Broadcom Brcmstb PCIe MSI support"
> +	depends on ARCH_BRCMSTB || BMIPS_GENERIC

This could probably be depends on PCI_BRCMSTB, which would imply these
two conditions. PCI_BRCMSTB_MSI on its own is probably not very useful
without the parent RC driver.

> +	depends on OF
> +	depends on PCI_MSI
> +	default PCI_BRCMSTB
> +	help
> +	  Say Y here if you want to enable MSI support for Broadcom's iProc
> +	  PCIe controller
> +
>  endmenu
> diff --git a/drivers/pci/host/Makefile b/drivers/pci/host/Makefile
> index c283321..1026d6f 100644
> --- a/drivers/pci/host/Makefile
> +++ b/drivers/pci/host/Makefile
> @@ -23,6 +23,7 @@ obj-$(CONFIG_PCIE_TANGO_SMP8759) += pcie-tango.o
>  obj-$(CONFIG_VMD) += vmd.o
>  obj-$(CONFIG_PCI_BRCMSTB) += brcmstb-pci.o
>  brcmstb-pci-objs := pci-brcmstb.o pci-brcmstb-dma.o
> +obj-$(CONFIG_PCI_BRCMSTB_MSI) += pci-brcmstb-msi.o

Should we combine this file with the brcmstb-pci.o? There is probably no
functional difference, except that pci-brcmstb-msi.ko needs to be loaded
first, right?
Bjorn Helgaas Oct. 25, 2017, 1:22 p.m. UTC | #2
On Tue, Oct 24, 2017 at 02:15:47PM -0400, Jim Quinlan wrote:
> 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/host/Kconfig           |  12 ++
>  drivers/pci/host/Makefile          |   1 +
>  drivers/pci/host/pci-brcmstb-msi.c | 318 +++++++++++++++++++++++++++++++++++++
>  drivers/pci/host/pci-brcmstb.c     |  72 +++++++--
>  drivers/pci/host/pci-brcmstb.h     |  26 +++
>  5 files changed, 419 insertions(+), 10 deletions(-)
>  create mode 100644 drivers/pci/host/pci-brcmstb-msi.c
> 
> diff --git a/drivers/pci/host/Kconfig b/drivers/pci/host/Kconfig
> index b9b4f11..54aa5d2 100644
> --- a/drivers/pci/host/Kconfig
> +++ b/drivers/pci/host/Kconfig
> @@ -228,4 +228,16 @@ config PCI_BRCMSTB
>  	default ARCH_BRCMSTB || BMIPS_GENERIC
>  	help
>  	  Adds support for Broadcom Settop Box PCIe host controller.
> +	  To compile this driver as a module, choose m here.
> +
> +config PCI_BRCMSTB_MSI
> +	bool "Broadcom Brcmstb PCIe MSI support"
> +	depends on ARCH_BRCMSTB || BMIPS_GENERIC
> +	depends on OF
> +	depends on PCI_MSI
> +	default PCI_BRCMSTB
> +	help
> +	  Say Y here if you want to enable MSI support for Broadcom's iProc
> +	  PCIe controller

Is there any reason *not* to enable MSI?  Most drivers just include
MSI support in the base driver, in the same file and under the same
config symbol.

> +
>  endmenu
> diff --git a/drivers/pci/host/Makefile b/drivers/pci/host/Makefile
> index c283321..1026d6f 100644
> --- a/drivers/pci/host/Makefile
> +++ b/drivers/pci/host/Makefile
> @@ -23,6 +23,7 @@ obj-$(CONFIG_PCIE_TANGO_SMP8759) += pcie-tango.o
>  obj-$(CONFIG_VMD) += vmd.o
>  obj-$(CONFIG_PCI_BRCMSTB) += brcmstb-pci.o
>  brcmstb-pci-objs := pci-brcmstb.o pci-brcmstb-dma.o
> +obj-$(CONFIG_PCI_BRCMSTB_MSI) += pci-brcmstb-msi.o
>  
>  # The following drivers are for devices that use the generic ACPI
>  # pci_root.c driver but don't support standard ECAM config access.
> diff --git a/drivers/pci/host/pci-brcmstb-msi.c b/drivers/pci/host/pci-brcmstb-msi.c
> new file mode 100644
> index 0000000..c805e2f
> --- /dev/null
> +++ b/drivers/pci/host/pci-brcmstb-msi.c
> @@ -0,0 +1,318 @@
> +/*
> + * Copyright (C) 2015-2017 Broadcom
> + *
> + * 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.
> + *
> + */

Remove last blank line of comment and add a blank line between comment
and #includes.

> +#include <linux/bitops.h>
> +#include <linux/compiler.h>
> +#include <linux/init.h>
> +#include <linux/interrupt.h>
> +#include <linux/io.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_irq.h>
> +#include <linux/of_pci.h>
> +#include <linux/of_platform.h>
> +#include <linux/pci.h>
> +#include <linux/types.h>
> +
> +#include "pci-brcmstb.h"
> +
> +#define PCIE_MISC_MSI_DATA_CONFIG			0x404c
> +#define PCIE_MSI_INTR2_BASE				0x4500
> +#define PCIE_MISC_MSI_BAR_CONFIG_LO			0x4044
> +#define PCIE_MISC_MSI_BAR_CONFIG_HI			0x4048
> +
> +/* Offsets from PCIE_INTR2_CPU_BASE and PCIE_MSI_INTR2_BASE */
> +#define STATUS				0x0
> +#define SET				0x4
> +#define CLR				0x8
> +#define MASK_STATUS			0xc
> +#define MASK_SET			0x10
> +#define MASK_CLR			0x14
> +
> +struct brcm_msi {
> +	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;
> +
> +	void __iomem *base;
> +	struct device *dev;
> +	struct device_node *dn;
> +	unsigned int rev;
> +};
> +
> +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;
> +
> +	chained_irq_enter(chip, desc);
> +	msi = irq_desc_get_handler_data(desc);
> +	mask = msi->intr_legacy_mask;
> +
> +	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(msi->dev, "unhandled MSI %d\n",
> +						 hwirq);
> +			} else {
> +				/* Unknown MSI, just clear it */
> +				dev_dbg(msi->dev, "unexpected MSI\n");

Use a local "struct device *dev" to avoid repeating the "msi->dev"
here and elsewhere.

> +			}
> +		}
> +	}
> +	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",

Why the different name from brcm_msi_irq_chip above ("Brcm_MSI" vs
"Brcm MSI")?  If the "_" signifies something, it should be made more
explicit.

> +	.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);
> +}
> +
> +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
> +		 */

Multi-line comment style.

> +		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);
> +}
> +EXPORT_SYMBOL(brcm_msi_set_regs);
> +
> +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);
> +
> +	msi->inner_domain = irq_domain_add_linear(NULL, BRCM_INT_PCI_MSI_NR,
> +						  &msi_domain_ops, msi);
> +	if (!msi->inner_domain) {
> +		dev_err(msi->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(msi->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);
> +}
> +
> +void brcm_msi_remove(struct brcm_msi *msi)
> +{
> +	if (!msi)
> +		return;
> +	irq_set_chained_handler(msi->irq, NULL);
> +	irq_set_handler_data(msi->irq, NULL);
> +	brcm_free_domains(msi);
> +}
> +EXPORT_SYMBOL(brcm_msi_remove);
> +
> +int brcm_msi_probe(struct platform_device *pdev, struct brcm_info *info)
> +{
> +	struct brcm_msi *msi;
> +	int irq, ret;
> +
> +	irq = irq_of_parse_and_map(pdev->dev.of_node, 1);
> +	if (irq <= 0) {
> +		dev_err(&pdev->dev, "cannot map msi intr\n");
> +		return -ENODEV;
> +	}
> +
> +	msi = devm_kzalloc(&pdev->dev, sizeof(struct brcm_msi), GFP_KERNEL);
> +	if (!msi)
> +		return -ENOMEM;
> +
> +	msi->dev = &pdev->dev;
> +	msi->base = info->base;
> +	msi->rev =  info->rev;
> +	msi->dn = pdev->dev.of_node;
> +	msi->target_addr = info->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);
> +	info->msi = msi;
> +
> +	return 0;
> +}
> +EXPORT_SYMBOL(brcm_msi_probe);
> diff --git a/drivers/pci/host/pci-brcmstb.c b/drivers/pci/host/pci-brcmstb.c
> index f5d80e6..99900d9 100644
> --- a/drivers/pci/host/pci-brcmstb.c
> +++ b/drivers/pci/host/pci-brcmstb.c
> @@ -22,6 +22,7 @@
>  #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>
> @@ -327,10 +328,13 @@ struct brcm_pcie {
>  	int			num_out_wins;
>  	bool			ssc;
>  	int			gen;
> +	u64			msi_target_addr;
>  	struct brcm_window	out_wins[BRCM_NUM_PCI_OUT_WINS];
>  	struct list_head	resources;
>  	struct device		*dev;
>  	struct list_head	pwr_supplies;
> +	struct brcm_msi		*msi;
> +	bool			msi_internal;
>  	unsigned int		rev;
>  	unsigned int		num;
>  	bool			bridge_setup_done;
> @@ -763,6 +767,7 @@ static void brcm_pcie_setup_prep(struct brcm_pcie *pcie)
>  	void __iomem *base = pcie->base;
>  	unsigned int scb_size_val;
>  	u64 rc_bar2_size = 0, rc_bar2_offset = 0, total_mem_size = 0;
> +	u64 msi_target_addr;
>  	u32 tmp, burst;
>  	int i;
>  
> @@ -806,14 +811,17 @@ static void brcm_pcie_setup_prep(struct brcm_pcie *pcie)
>  	/* The PCI 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 pci-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
> +	 * pci-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);
>  
> @@ -828,6 +836,14 @@ static void brcm_pcie_setup_prep(struct brcm_pcie *pcie)
>  		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
>  			 * cannot start the inbound region at location
> @@ -836,15 +852,24 @@ static void brcm_pcie_setup_prep(struct brcm_pcie *pcie)
>  			 * 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
>  		 * only.  We always start the viewport at address 0,
>  		 * 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,
> @@ -1053,6 +1078,9 @@ static int brcm_pcie_resume(struct device *dev)
>  	if (ret)
>  		return ret;
>  
> +	if (pcie->msi && pcie->msi_internal)
> +		brcm_msi_set_regs(pcie->msi);
> +
>  	pcie->suspended = false;
>  
>  	return 0;
> @@ -1116,6 +1144,7 @@ static void __attribute__((__section__("pci_fixup_early")))
>  
>  static void _brcm_pcie_remove(struct brcm_pcie *pcie)
>  {
> +	brcm_msi_remove(pcie->msi);
>  	turn_off(pcie);
>  	clk_disable_unprepare(pcie->clk);
>  	clk_put(pcie->clk);
> @@ -1125,7 +1154,7 @@ static void _brcm_pcie_remove(struct brcm_pcie *pcie)
>  
>  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;
>  	struct property *pp;
>  	const struct of_device_id *of_id;
>  	const struct pcie_cfg_data *data;
> @@ -1244,6 +1273,29 @@ static int brcm_pcie_probe(struct platform_device *pdev)
>  	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 (IS_ENABLED(CONFIG_PCI_MSI) && pci_msi_enabled()
> +	    && msi_dn == pcie->dn) {
> +		struct brcm_info info;
> +
> +		info.rev = pcie->rev;
> +		info.msi_target_addr = pcie->msi_target_addr;
> +		info.base = pcie->base;
> +
> +		ret = brcm_msi_probe(pdev, &info);
> +		if (ret)
> +			dev_err(pcie->dev,
> +				"probe of internal MSI failed: %d)", ret);
> +		else
> +			pcie->msi_internal = true;
> +
> +		pcie->msi = info.msi;
> +	}
> +
>  	list_splice_init(&pcie->resources, &bridge->windows);
>  	bridge->dev.parent = &pdev->dev;
>  	bridge->busnr = 0;
> diff --git a/drivers/pci/host/pci-brcmstb.h b/drivers/pci/host/pci-brcmstb.h
> index c3b148a..f969c2a 100644
> --- a/drivers/pci/host/pci-brcmstb.h
> +++ b/drivers/pci/host/pci-brcmstb.h
> @@ -27,6 +27,32 @@
>  extern struct of_pci_range *dma_ranges;
>  extern int num_dma_ranges;
>  
> +struct brcm_msi;
> +struct brcm_info;
> +struct platform_device;
> +
> +#ifdef CONFIG_PCI_BRCMSTB_MSI
> +int brcm_msi_probe(struct platform_device *pdev, struct brcm_info *info);
> +void brcm_msi_set_regs(struct brcm_msi *chip);
> +void brcm_msi_remove(struct brcm_msi *chip);
> +#else
> +static inline int brcm_msi_probe(struct platform_device *pdev,
> +				 struct brcm_info *info)
> +{
> +	return -ENODEV;
> +}
> +
> +static inline void brcm_msi_set_regs(struct brcm_msi *chip) {}
> +static inline void brcm_msi_remove(struct brcm_msi *chip) {}
> +#endif /* CONFIG_PCI_BRCMSTB_MSI */
> +
> +struct brcm_info {
> +	int rev;
> +	u64 msi_target_addr;
> +	void __iomem *base;
> +	struct brcm_msi *msi;
> +};
> +
>  #if defined(CONFIG_MIPS)
>  /* Broadcom MIPs HW implicitly does the swapping if necessary */
>  #define bcm_readl(a)		__raw_readl(a)
> -- 
> 1.9.0.138.g2de3478
>
Jim Quinlan Oct. 25, 2017, 3:28 p.m. UTC | #3
On Tue, Oct 24, 2017 at 2:57 PM, Florian Fainelli <f.fainelli@gmail.com> wrote:
> Hi Jim,
>
> On 10/24/2017 11:15 AM, Jim Quinlan wrote:
>> 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/host/Kconfig           |  12 ++
>>  drivers/pci/host/Makefile          |   1 +
>>  drivers/pci/host/pci-brcmstb-msi.c | 318 +++++++++++++++++++++++++++++++++++++
>>  drivers/pci/host/pci-brcmstb.c     |  72 +++++++--
>>  drivers/pci/host/pci-brcmstb.h     |  26 +++
>>  5 files changed, 419 insertions(+), 10 deletions(-)
>>  create mode 100644 drivers/pci/host/pci-brcmstb-msi.c
>>
>> diff --git a/drivers/pci/host/Kconfig b/drivers/pci/host/Kconfig
>> index b9b4f11..54aa5d2 100644
>> --- a/drivers/pci/host/Kconfig
>> +++ b/drivers/pci/host/Kconfig
>> @@ -228,4 +228,16 @@ config PCI_BRCMSTB
>>       default ARCH_BRCMSTB || BMIPS_GENERIC
>>       help
>>         Adds support for Broadcom Settop Box PCIe host controller.
>> +       To compile this driver as a module, choose m here.
>> +
>> +config PCI_BRCMSTB_MSI
>> +     bool "Broadcom Brcmstb PCIe MSI support"
>> +     depends on ARCH_BRCMSTB || BMIPS_GENERIC
>
> This could probably be depends on PCI_BRCMSTB, which would imply these
> two conditions. PCI_BRCMSTB_MSI on its own is probably not very useful
> without the parent RC driver.
>
>> +     depends on OF
>> +     depends on PCI_MSI
>> +     default PCI_BRCMSTB
>> +     help
>> +       Say Y here if you want to enable MSI support for Broadcom's iProc
>> +       PCIe controller
>> +
>>  endmenu
>> diff --git a/drivers/pci/host/Makefile b/drivers/pci/host/Makefile
>> index c283321..1026d6f 100644
>> --- a/drivers/pci/host/Makefile
>> +++ b/drivers/pci/host/Makefile
>> @@ -23,6 +23,7 @@ obj-$(CONFIG_PCIE_TANGO_SMP8759) += pcie-tango.o
>>  obj-$(CONFIG_VMD) += vmd.o
>>  obj-$(CONFIG_PCI_BRCMSTB) += brcmstb-pci.o
>>  brcmstb-pci-objs := pci-brcmstb.o pci-brcmstb-dma.o
>> +obj-$(CONFIG_PCI_BRCMSTB_MSI) += pci-brcmstb-msi.o
>
> Should we combine this file with the brcmstb-pci.o? There is probably no
> functional difference, except that pci-brcmstb-msi.ko needs to be loaded
> first, right?
> --
> Florian

If you look at the pci/host/Kconfig you will see that other drivers
also have a separate MSI config (eg iproc, altera, xgene) so there is
precedent.  The reason that pci-brcmstb-msi.c is its own file is
because it depends on an irq function that is not exported.  That is
why CONFIG_PCI_BRCMSTB_MSI is bool, and CONFIG_PCI_BRCMSTB is
tristate.  -- Jim
Jim Quinlan Oct. 25, 2017, 3:50 p.m. UTC | #4
On Wed, Oct 25, 2017 at 9:22 AM, Bjorn Helgaas <helgaas@kernel.org> wrote:
> On Tue, Oct 24, 2017 at 02:15:47PM -0400, Jim Quinlan wrote:
>> This commit adds MSI to the Broadcom STB PCIe host controller. It does
>> not add MSIX since that functiostbsrvnality 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/host/Kconfig           |  12 ++
>>  drivers/pci/host/Makefile          |   1 +
>>  drivers/pci/host/pci-brcmstb-msi.c | 318 +++++++++++++++++++++++++++++++++++++
>>  drivers/pci/host/pci-brcmstb.c     |  72 +++++++--
>>  drivers/pci/host/pci-brcmstb.h     |  26 +++
>>  5 files changed, 419 insertions(+), 10 deletions(-)
>>  create mode 100644 drivers/pci/host/pci-brcmstb-msi.c
>>
>> diff --git a/drivers/pci/host/Kconfig b/drivers/pci/host/Kconfig
>> index b9b4f11..54aa5d2 100644
>> --- a/drivers/pci/host/Kconfig
>> +++ b/drivers/pci/host/Kconfig
>> @@ -228,4 +228,16 @@ config PCI_BRCMSTB
>>       default ARCH_BRCMSTB || BMIPS_GENERIC
>>       help
>>         Adds support for Broadcom Settop Box PCIe host controller.
>> +       To compile this driver as a module, choose m here.
>> +
>> +config PCI_BRCMSTB_MSI
>> +     bool "Broadcom Brcmstb PCIe MSI support"
>> +     depends on ARCH_BRCMSTB || BMIPS_GENERIC
>> +     depends on OF
>> +     depends on PCI_MSI
>> +     default PCI_BRCMSTB
>> +     help
>> +       Say Y here if you want to enable MSI support for Broadcom's iProc
>> +       PCIe controller
>
> Is there any reason *not* to enable MSI?  Most drivers just include
> MSI support in the base driver, in the same file and under the same
> config symbol.
>
CONFIG_PCI_BRCMSTB_MSI is bool, CONFIG_PCI_BRCMSTB is tri-state.  The
reason for this -- and also for the separate msi file -- is that there
is an irq call  that is not exported (I can dig it up if you'd like).
We have had requests that the PCIe RC driver doesn't run at boot, so
we consider the idea of it being a module valuable.  You can see the
same thing going on with PCIE_IPROC_MSI.

>> +
>>  endmenu
>> diff --git a/drivers/pci/host/Makefile b/drivers/pci/host/Makefile
>> index c283321..1026d6f 100644
>> --- a/drivers/pci/host/Makefile
>> +++ b/drivers/pci/host/Makefile
>> @@ -23,6 +23,7 @@ obj-$(CONFIG_PCIE_TANGO_SMP8759) += pcie-tango.o
>>  obj-$(CONFIG_VMD) += vmd.o
>>  obj-$(CONFIG_PCI_BRCMSTB) += brcmstb-pci.o
>>  brcmstb-pci-objs := pci-brcmstb.o pci-brcmstb-dma.o
>> +obj-$(CONFIG_PCI_BRCMSTB_MSI) += pci-brcmstb-msi.o
>>
>>  # The following drivers are for devices that use the generic ACPI
>>  # pci_root.c driver but don't support standard ECAM config access.
>> diff --git a/drivers/pci/host/pci-brcmstb-msi.c b/drivers/pci/host/pci-brcmstb-msi.c
>> new file mode 100644
>> index 0000000..c805e2f
>> --- /dev/null
>> +++ b/drivers/pci/host/pci-brcmstb-msi.c
>> @@ -0,0 +1,318 @@
>> +/*
>> + * Copyright (C) 2015-2017 Broadcom
>> + *
>> + * 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.
>> + *
>> + */
>
> Remove last blank line of comment and add a blank line between comment
> and #includes.
Will fix.

>
>> +#include <linux/bitops.h>
>> +#include <linux/compiler.h>
>> +#include <linux/init.h>
>> +#include <linux/interrupt.h>
>> +#include <linux/io.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_irq.h>
>> +#include <linux/of_pci.h>
>> +#include <linux/of_platform.h>
>> +#include <linux/pci.h>
>> +#include <linux/types.h>
>> +
>> +#include "pci-brcmstb.h"
>> +
>> +#define PCIE_MISC_MSI_DATA_CONFIG                    0x404c
>> +#define PCIE_MSI_INTR2_BASE                          0x4500
>> +#define PCIE_MISC_MSI_BAR_CONFIG_LO                  0x4044
>> +#define PCIE_MISC_MSI_BAR_CONFIG_HI                  0x4048
>> +
>> +/* Offsets from PCIE_INTR2_CPU_BASE and PCIE_MSI_INTR2_BASE */
>> +#define STATUS                               0x0
>> +#define SET                          0x4
>> +#define CLR                          0x8
>> +#define MASK_STATUS                  0xc
>> +#define MASK_SET                     0x10
>> +#define MASK_CLR                     0x14
>> +
>> +struct brcm_msi {
>> +     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;
>> +
>> +     void __iomem *base;
>> +     struct device *dev;
>> +     struct device_node *dn;
>> +     unsigned int rev;
>> +};
>> +
>> +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;
>> +
>> +     chained_irq_enter(chip, desc);
>> +     msi = irq_desc_get_handler_data(desc);
>> +     mask = msi->intr_legacy_mask;
>> +
>> +     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(msi->dev, "unhandled MSI %d\n",
>> +                                              hwirq);
>> +                     } else {
>> +                             /* Unknown MSI, just clear it */
>> +                             dev_dbg(msi->dev, "unexpected MSI\n");
>
> Use a local "struct device *dev" to avoid repeating the "msi->dev"
> here and elsewhere.
Will fix.

>
>> +                     }
>> +             }
>> +     }
>> +     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",PCIE_IPROC_MSI
>
> Why the different name from brcm_msi_irq_chip above ("Brcm_MSI" vs
> "Brcm MSI")?  If the "_" signifies something, it should be made more
> explicit.
>
Will fix.

>> +     .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);
>> +}
>> +
>> +void brcm_msi_set_regs(struct brcm_msi *msi)
>> +{
>> +     u32 data_val, msi_lo, msi_hi;
>> +
>> +     if (msi->rev >= BRCM_PCIE_HW_REV_33) {PCIE_IPROC_MSI
>> +             /* ffe0 -- least sig 5 bits are 0 indicating 32 msgs
>> +              * 6540 -- this is our arbitrary unique data value
>> +              */PCIE_IPROC_MSI
>
> Multi-line comment style.
>
Will fix.

>> +             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,PCIE_IPROC_MSI
>> +                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);
>> +}
>> +EXPORT_SYMBOL(brcm_msi_set_regs);
>> +
>> +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);
>> +
>> +     msi->inner_domain = irq_domain_add_linear(NULL, BRCM_INT_PCI_MSI_NR,
>> +                                               &msi_domain_ops, msi);
>> +     if (!msi->inner_domain) {
>> +             dev_err(msi->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(msi->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);
>> +}
>> +
>> +void brcm_msi_remove(struct brcm_msi *msi)
>> +{
>> +     if (!msi)
>> +             return;
>> +     irq_set_chained_handler(msi->irq, NULL);
>> +     irq_set_handler_data(msi->irq, NULL);
>> +     brcm_free_domains(msi);
>> +}
>> +EXPORT_SYMBOL(brcm_msi_remove);
>> +
>> +int brcm_msi_probe(struct platform_device *pdev, struct brcm_info *info)
>> +{
>> +     struct brcm_msi *msi;
>> +     int irq, ret;
>> +
>> +     irq = irq_of_parse_and_map(pdev->dev.of_node, 1);
>> +     if (irq <= 0) {
>> +             dev_err(&pdev->dev, "cannot map msi intr\n");
>> +             return -ENODEV;
>> +     }
>> +
>> +     msi = devm_kzalloc(&pdev->dev, sizeof(struct brcm_msi), GFP_KERNEL);
>> +     if (!msi)
>> +             return -ENOMEM;
>> +
>> +     msi->dev = &pdev->dev;
>> +     msi->base = info->base;
>> +     msi->rev =  info->rev;
>> +     msi->dn = pdev->dev.of_node;
>> +     msi->target_addr = info->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) {PCIE_IPROC_MSI
>> +             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;
>> +     }PCIE_IPROC_MSI
>> +
>> +     brcm_msi_set_regs(msi);
>> +     info->msi = msi;
>> +
>> +     return 0;
>> +}
>> +EXPORT_SYMBOL(brcm_msi_probe);
>> diff --git a/drivers/pci/host/pci-brcmstb.c b/drivers/pci/host/pci-brcmstb.c
>> index f5d80e6..99900d9 100644
>> --- a/drivers/pci/host/pci-brcmstb.c
>> +++ b/drivers/pci/host/pci-brcmstb.c
>> @@ -22,6 +22,7 @@
>>  #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>
>> @@ -327,10 +328,13 @@ struct brcm_pcie {
>>       int                     num_out_wins;
>>       bool                    ssc;
>>       int                     gen;
>> +     u64                     msi_target_addr;
>>       struct brcm_window      out_wins[BRCM_NUM_PCI_OUT_WINS];
>>       struct list_head        resources;
>>       struct device           *dev;
>>       struct list_head        pwr_supplies;
>> +     struct brcm_msi         *msi;
>> +     bool                    msi_internal;
>>       unsigned int            rev;
>>       unsigned int            num;
>>       bool                    bridge_setup_done;
>> @@ -763,6 +767,7 @@ static void brcm_pcie_setup_prep(struct brcm_pcie *pcie)
>>       void __iomem *base = pcie->base;
>>       unsigned int scb_size_val;
>>       u64 rc_bar2_size = 0, rc_bar2_offset = 0, total_mem_size = 0;
>> +     u64 msi_target_addr;
>>       u32 tmp, burst;
>>       int i;
>>
>> @@ -806,14 +811,17 @@ static void brcm_pcie_setup_prep(struct brcm_pcie *pcie)
>>       /* The PCI 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 pci-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
>> +      * pci-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);
>>
>> @@ -828,6 +836,14 @@ static void brcm_pcie_setup_prep(struct brcm_pcie *pcie)
>>               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
>>                        * cannot start the inbound region at location
>> @@ -836,15 +852,24 @@ static void brcm_pcie_setup_prep(struct brcm_pcie *pcie)
>>                        * 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
>>                * only.  We always start the viewport at address 0,
>>                * 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,
>> @@ -1053,6 +1078,9 @@ static int brcm_pcie_resume(struct device *dev)
>>       if (ret)
>>               return ret;
>>
>> +     if (pcie->msi && pcie->msi_internal)
>> +             brcm_msi_set_regs(pcie->msi);
>> +
>>       pcie->suspended = false;
>>
>>       return 0;
>> @@ -1116,6 +1144,7 @@ static void __attribute__((__section__("pci_fixup_early")))
>>
>>  static void _brcm_pcie_remove(struct brcm_pcie *pcie)
>>  {
>> +     brcm_msi_remove(pcie->msi);
>>       turn_off(pcie);
>>       clk_disable_unprepare(pcie->clk);
>>       clk_put(pcie->clk);PCIE_IPROC_MSI
>> @@ -1125,7 +1154,7 @@ static void _brcm_pcie_remove(struct brcm_pcie *pcie)
>>
>>  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;
>>       struct property *pp;
>>       const struct of_device_id *of_id;
>>       const struct pcie_cfg_data *data;
>> @@ -1244,6 +1273,29 @@ static int brcm_pcie_probe(struct platform_device *pdev)
>>       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 (IS_ENABLED(CONFIG_PCI_MSI) && pci_msi_enabled()
>> +         && msi_dn == pcie->dn) {
>> +             struct brcm_info info;
>> +
>> +             info.rev = pcie->rev;
>> +             info.msi_target_addr = pcie->msi_target_addr;
>> +             info.base = pcie->base;
>> +
>> +             ret = brcm_msi_probe(pdev, &info);
>> +             if (ret)
>> +                     dev_err(pcie->dev,
>> +                             "probe of internal MSI failed: %d)", ret);
>> +             else
>> +                     pcie->msi_internal = true;
>> +
>> +             pcie->msi = info.msi;
>> +     }
>> +
>>       list_splice_init(&pcie->resources, &bridge->windows);
>>       bridge->dev.parent = &pdev->dev;
>>       bridge->busnr = 0;
>> diff --git a/drivers/pci/host/pci-brcmstb.h b/drivers/pci/host/pci-brcmstb.h
>> index c3b148a..f969c2a 100644
>> --- a/drivers/pci/host/pci-brcmstb.h
>> +++ b/drivers/pci/host/pci-brcmstb.h
>> @@ -27,6 +27,32 @@
>>  extern struct of_pci_range *dma_ranges;
>>  extern int num_dma_ranges;
>>
>> +struct brcm_msi;
>> +struct brcm_info;
>> +struct platform_device;
>> +
>> +#ifdef CONFIG_PCI_BRCMSTB_MSI
>> +int brcm_msi_probe(struct platform_device *pdev, struct brcm_info *info);
>> +void brcm_msi_set_regs(struct brcm_msi *chip);
>> +void brcm_msi_remove(struct brcm_msi *chip);
>> +#else
>> +static inline int brcm_msi_probe(struct platform_device *pdev,
>> +                              struct brcm_info *info)
>> +{
>> +     return -ENODEV;
>> +}
>> +
>> +static inline void brcm_msi_set_regs(struct brcm_msi *chip) {}
>> +static inline void brcm_msi_remove(struct brcm_msi *chip) {}
>> +#endif /* CONFIG_PCI_BRCMSTB_MSI */
>> +
>> +struct brcm_info {PCIE_IPROC_MSI
>> +     int rev;
>> +     u64 msi_target_addr;
>> +     void __iomem *base;
>> +     struct brcm_msi *msi;
>> +};
>> +
>>  #if defined(CONFIG_MIPS)PCIE_IPROC_MSI
>>  /* Broadcom MIPs HW implicitly does the swapping if necessary */
>>  #define bcm_readl(a)         __raw_readl(a)
>> --
>> 1.9.0.138.g2de3478
>>
Bjorn Helgaas Oct. 25, 2017, 5:23 p.m. UTC | #5
[+cc Ray, Scott, Jon]

On Wed, Oct 25, 2017 at 11:28:07AM -0400, Jim Quinlan wrote:
> On Tue, Oct 24, 2017 at 2:57 PM, Florian Fainelli <f.fainelli@gmail.com> wrote:
> > Hi Jim,
> >
> > On 10/24/2017 11:15 AM, Jim Quinlan wrote:
> >> 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/host/Kconfig           |  12 ++
> >>  drivers/pci/host/Makefile          |   1 +
> >>  drivers/pci/host/pci-brcmstb-msi.c | 318 +++++++++++++++++++++++++++++++++++++
> >>  drivers/pci/host/pci-brcmstb.c     |  72 +++++++--
> >>  drivers/pci/host/pci-brcmstb.h     |  26 +++
> >>  5 files changed, 419 insertions(+), 10 deletions(-)
> >>  create mode 100644 drivers/pci/host/pci-brcmstb-msi.c
> >>
> >> diff --git a/drivers/pci/host/Kconfig b/drivers/pci/host/Kconfig
> >> index b9b4f11..54aa5d2 100644
> >> --- a/drivers/pci/host/Kconfig
> >> +++ b/drivers/pci/host/Kconfig
> >> @@ -228,4 +228,16 @@ config PCI_BRCMSTB
> >>       default ARCH_BRCMSTB || BMIPS_GENERIC
> >>       help
> >>         Adds support for Broadcom Settop Box PCIe host controller.
> >> +       To compile this driver as a module, choose m here.
> >> +
> >> +config PCI_BRCMSTB_MSI
> >> +     bool "Broadcom Brcmstb PCIe MSI support"
> >> +     depends on ARCH_BRCMSTB || BMIPS_GENERIC
> >
> > This could probably be depends on PCI_BRCMSTB, which would imply these
> > two conditions. PCI_BRCMSTB_MSI on its own is probably not very useful
> > without the parent RC driver.
> >
> >> +     depends on OF
> >> +     depends on PCI_MSI
> >> +     default PCI_BRCMSTB
> >> +     help
> >> +       Say Y here if you want to enable MSI support for Broadcom's iProc
> >> +       PCIe controller
> >> +
> >>  endmenu
> >> diff --git a/drivers/pci/host/Makefile b/drivers/pci/host/Makefile
> >> index c283321..1026d6f 100644
> >> --- a/drivers/pci/host/Makefile
> >> +++ b/drivers/pci/host/Makefile
> >> @@ -23,6 +23,7 @@ obj-$(CONFIG_PCIE_TANGO_SMP8759) += pcie-tango.o
> >>  obj-$(CONFIG_VMD) += vmd.o
> >>  obj-$(CONFIG_PCI_BRCMSTB) += brcmstb-pci.o
> >>  brcmstb-pci-objs := pci-brcmstb.o pci-brcmstb-dma.o
> >> +obj-$(CONFIG_PCI_BRCMSTB_MSI) += pci-brcmstb-msi.o
> >
> > Should we combine this file with the brcmstb-pci.o? There is probably no
> > functional difference, except that pci-brcmstb-msi.ko needs to be loaded
> > first, right?
> > --
> > Florian
> 
> If you look at the pci/host/Kconfig you will see that other drivers
> also have a separate MSI config (eg iproc, altera, xgene) so there is
> precedent.  The reason that pci-brcmstb-msi.c is its own file is
> because it depends on an irq function that is not exported.  That is
> why CONFIG_PCI_BRCMSTB_MSI is bool, and CONFIG_PCI_BRCMSTB is
> tristate.  -- Jim

There is precedent, but that doesn't mean I like it :)
I would strongly prefer one file per driver when possible.

Take iproc for example.  iproc-msi.c is enabled by a Kconfig bool.  It
contains a bunch of code with the only external entry points being
iproc_msi_init() and iproc_msi_exit().  These are only called via
iproc_pcie_bcma_probe() or iproc_pcie_pltfm_probe(), both of which are
tristate.  So iproc-msi.c is only compiled if CONFIG_IPROC_BCMA or
CONFIG_IPROC_PLATFORM are enabled, but all that text is loaded even if
neither module is loaded, which seems suboptimal.

I don't care if you have several config options to enable the BCMA
probe and the platform probe (although these could probably be
replaced in the code by a simple "#ifdef CONFIG_BCMA" and "#ifdef
CONFIG_OF"), and making CONFIG_PCIE_IPROC tristate so it can be a
module makes sense.  But I think it would be better to put all the
code in one file instead of five, and probably remove
CONFIG_PCIE_IPROC_MSI.  Maybe this requires exporting some IRQ
function that currently isn't exported.  But that seems like a simpler
solution than what we currently have.

Bjorn
Scott Branden Oct. 25, 2017, 6:40 p.m. UTC | #6
Hi Bjorn,


On 17-10-25 10:23 AM, Bjorn Helgaas wrote:
> [+cc Ray, Scott, Jon]
>
> On Wed, Oct 25, 2017 at 11:28:07AM -0400, Jim Quinlan wrote:
>> On Tue, Oct 24, 2017 at 2:57 PM, Florian Fainelli <f.fainelli@gmail.com> wrote:
>>> Hi Jim,
>>>
>>> On 10/24/2017 11:15 AM, Jim Quinlan wrote:
>>>> 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/host/Kconfig           |  12 ++
>>>>   drivers/pci/host/Makefile          |   1 +
>>>>   drivers/pci/host/pci-brcmstb-msi.c | 318 +++++++++++++++++++++++++++++++++++++
>>>>   drivers/pci/host/pci-brcmstb.c     |  72 +++++++--
>>>>   drivers/pci/host/pci-brcmstb.h     |  26 +++
>>>>   5 files changed, 419 insertions(+), 10 deletions(-)
>>>>   create mode 100644 drivers/pci/host/pci-brcmstb-msi.c
>>>>
>>>> diff --git a/drivers/pci/host/Kconfig b/drivers/pci/host/Kconfig
>>>> index b9b4f11..54aa5d2 100644
>>>> --- a/drivers/pci/host/Kconfig
>>>> +++ b/drivers/pci/host/Kconfig
>>>> @@ -228,4 +228,16 @@ config PCI_BRCMSTB
>>>>        default ARCH_BRCMSTB || BMIPS_GENERIC
>>>>        help
>>>>          Adds support for Broadcom Settop Box PCIe host controller.
>>>> +       To compile this driver as a module, choose m here.
>>>> +
>>>> +config PCI_BRCMSTB_MSI
>>>> +     bool "Broadcom Brcmstb PCIe MSI support"
>>>> +     depends on ARCH_BRCMSTB || BMIPS_GENERIC
>>> This could probably be depends on PCI_BRCMSTB, which would imply these
>>> two conditions. PCI_BRCMSTB_MSI on its own is probably not very useful
>>> without the parent RC driver.
>>>
>>>> +     depends on OF
>>>> +     depends on PCI_MSI
>>>> +     default PCI_BRCMSTB
>>>> +     help
>>>> +       Say Y here if you want to enable MSI support for Broadcom's iProc
>>>> +       PCIe controller
>>>> +
>>>>   endmenu
>>>> diff --git a/drivers/pci/host/Makefile b/drivers/pci/host/Makefile
>>>> index c283321..1026d6f 100644
>>>> --- a/drivers/pci/host/Makefile
>>>> +++ b/drivers/pci/host/Makefile
>>>> @@ -23,6 +23,7 @@ obj-$(CONFIG_PCIE_TANGO_SMP8759) += pcie-tango.o
>>>>   obj-$(CONFIG_VMD) += vmd.o
>>>>   obj-$(CONFIG_PCI_BRCMSTB) += brcmstb-pci.o
>>>>   brcmstb-pci-objs := pci-brcmstb.o pci-brcmstb-dma.o
>>>> +obj-$(CONFIG_PCI_BRCMSTB_MSI) += pci-brcmstb-msi.o
>>> Should we combine this file with the brcmstb-pci.o? There is probably no
>>> functional difference, except that pci-brcmstb-msi.ko needs to be loaded
>>> first, right?
>>> --
>>> Florian
>> If you look at the pci/host/Kconfig you will see that other drivers
>> also have a separate MSI config (eg iproc, altera, xgene) so there is
>> precedent.  The reason that pci-brcmstb-msi.c is its own file is
>> because it depends on an irq function that is not exported.  That is
>> why CONFIG_PCI_BRCMSTB_MSI is bool, and CONFIG_PCI_BRCMSTB is
>> tristate.  -- Jim
> There is precedent, but that doesn't mean I like it :)
> I would strongly prefer one file per driver when possible.
>
> Take iproc for example.  iproc-msi.c is enabled by a Kconfig bool.  It
> contains a bunch of code with the only external entry points being
> iproc_msi_init() and iproc_msi_exit().  These are only called via
> iproc_pcie_bcma_probe() or iproc_pcie_pltfm_probe(), both of which are
> tristate.  So iproc-msi.c is only compiled if CONFIG_IPROC_BCMA or
> CONFIG_IPROC_PLATFORM are enabled, but all that text is loaded even if
> neither module is loaded, which seems suboptimal.
>
> I don't care if you have several config options to enable the BCMA
> probe and the platform probe (although these could probably be
> replaced in the code by a simple "#ifdef CONFIG_BCMA" and "#ifdef
> CONFIG_OF"), and making CONFIG_PCIE_IPROC tristate so it can be a
> module makes sense.  But I think it would be better to put all the
> code in one file instead of five, and probably remove
> CONFIG_PCIE_IPROC_MSI.  Maybe this requires exporting some IRQ
> function that currently isn't exported.  But that seems like a simpler
> solution than what we currently have.
Placing pcie-iproc-bcma.c in its own file is useful in being able to 
read the code that is actually used.  BCMA is really unnecessary if a 
few platforms stopped using BCMA and declared everything via devicetree 
or ACPI.  Same with pcie-iproc-platform.c.  Both keep the mess out of 
pcie-iproc.c.

It looks like pcie-iproc-msi.c followed existing pci drivers in place.  
So if msi was cleaned up through the entire pci drivers then yes it 
would make sense to remove CONFIG_PCIE_IPROC_MSI and combine code in 
pcie-iproc.c.  But I think leaving the bcma and platform code in their 
own files makes it easier for us to work with the code rather than 
placing unused code in ifdefs in the same file.
>
> Bjorn
Regards,
Scott
Bjorn Helgaas Oct. 25, 2017, 8:16 p.m. UTC | #7
On Wed, Oct 25, 2017 at 11:40:47AM -0700, Scott Branden wrote:
> Hi Bjorn,
> 
> 
> On 17-10-25 10:23 AM, Bjorn Helgaas wrote:
> >[+cc Ray, Scott, Jon]
> >
> >On Wed, Oct 25, 2017 at 11:28:07AM -0400, Jim Quinlan wrote:
> >>On Tue, Oct 24, 2017 at 2:57 PM, Florian Fainelli <f.fainelli@gmail.com> wrote:
> >>>Hi Jim,
> >>>
> >>>On 10/24/2017 11:15 AM, Jim Quinlan wrote:
> >>>>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/host/Kconfig           |  12 ++
> >>>>  drivers/pci/host/Makefile          |   1 +
> >>>>  drivers/pci/host/pci-brcmstb-msi.c | 318 +++++++++++++++++++++++++++++++++++++
> >>>>  drivers/pci/host/pci-brcmstb.c     |  72 +++++++--
> >>>>  drivers/pci/host/pci-brcmstb.h     |  26 +++
> >>>>  5 files changed, 419 insertions(+), 10 deletions(-)
> >>>>  create mode 100644 drivers/pci/host/pci-brcmstb-msi.c
> >>>>
> >>>>diff --git a/drivers/pci/host/Kconfig b/drivers/pci/host/Kconfig
> >>>>index b9b4f11..54aa5d2 100644
> >>>>--- a/drivers/pci/host/Kconfig
> >>>>+++ b/drivers/pci/host/Kconfig
> >>>>@@ -228,4 +228,16 @@ config PCI_BRCMSTB
> >>>>       default ARCH_BRCMSTB || BMIPS_GENERIC
> >>>>       help
> >>>>         Adds support for Broadcom Settop Box PCIe host controller.
> >>>>+       To compile this driver as a module, choose m here.
> >>>>+
> >>>>+config PCI_BRCMSTB_MSI
> >>>>+     bool "Broadcom Brcmstb PCIe MSI support"
> >>>>+     depends on ARCH_BRCMSTB || BMIPS_GENERIC
> >>>This could probably be depends on PCI_BRCMSTB, which would imply these
> >>>two conditions. PCI_BRCMSTB_MSI on its own is probably not very useful
> >>>without the parent RC driver.
> >>>
> >>>>+     depends on OF
> >>>>+     depends on PCI_MSI
> >>>>+     default PCI_BRCMSTB
> >>>>+     help
> >>>>+       Say Y here if you want to enable MSI support for Broadcom's iProc
> >>>>+       PCIe controller
> >>>>+
> >>>>  endmenu
> >>>>diff --git a/drivers/pci/host/Makefile b/drivers/pci/host/Makefile
> >>>>index c283321..1026d6f 100644
> >>>>--- a/drivers/pci/host/Makefile
> >>>>+++ b/drivers/pci/host/Makefile
> >>>>@@ -23,6 +23,7 @@ obj-$(CONFIG_PCIE_TANGO_SMP8759) += pcie-tango.o
> >>>>  obj-$(CONFIG_VMD) += vmd.o
> >>>>  obj-$(CONFIG_PCI_BRCMSTB) += brcmstb-pci.o
> >>>>  brcmstb-pci-objs := pci-brcmstb.o pci-brcmstb-dma.o
> >>>>+obj-$(CONFIG_PCI_BRCMSTB_MSI) += pci-brcmstb-msi.o
> >>>Should we combine this file with the brcmstb-pci.o? There is probably no
> >>>functional difference, except that pci-brcmstb-msi.ko needs to be loaded
> >>>first, right?
> >>>--
> >>>Florian
> >>If you look at the pci/host/Kconfig you will see that other drivers
> >>also have a separate MSI config (eg iproc, altera, xgene) so there is
> >>precedent.  The reason that pci-brcmstb-msi.c is its own file is
> >>because it depends on an irq function that is not exported.  That is
> >>why CONFIG_PCI_BRCMSTB_MSI is bool, and CONFIG_PCI_BRCMSTB is
> >>tristate.  -- Jim
> >There is precedent, but that doesn't mean I like it :)
> >I would strongly prefer one file per driver when possible.
> >
> >Take iproc for example.  iproc-msi.c is enabled by a Kconfig bool.  It
> >contains a bunch of code with the only external entry points being
> >iproc_msi_init() and iproc_msi_exit().  These are only called via
> >iproc_pcie_bcma_probe() or iproc_pcie_pltfm_probe(), both of which are
> >tristate.  So iproc-msi.c is only compiled if CONFIG_IPROC_BCMA or
> >CONFIG_IPROC_PLATFORM are enabled, but all that text is loaded even if
> >neither module is loaded, which seems suboptimal.
> >
> >I don't care if you have several config options to enable the BCMA
> >probe and the platform probe (although these could probably be
> >replaced in the code by a simple "#ifdef CONFIG_BCMA" and "#ifdef
> >CONFIG_OF"), and making CONFIG_PCIE_IPROC tristate so it can be a
> >module makes sense.  But I think it would be better to put all the
> >code in one file instead of five, and probably remove
> >CONFIG_PCIE_IPROC_MSI.  Maybe this requires exporting some IRQ
> >function that currently isn't exported.  But that seems like a simpler
> >solution than what we currently have.
> Placing pcie-iproc-bcma.c in its own file is useful in being able to
> read the code that is actually used.  BCMA is really unnecessary if
> a few platforms stopped using BCMA and declared everything via
> devicetree or ACPI.  Same with pcie-iproc-platform.c.  Both keep the
> mess out of pcie-iproc.c.

Maybe.  Both pcie-iproc-bcma.c and pcie-iproc-platform.c are small
(280 lines combined) relative to pcie-iproc.c + pcie-iproc-msi.c (2150
lines combined), and keeping them separate requires pcie-iproc.h,
which could otherwise be folded into pcie-iproc.c.  So I'm still a
little skeptical.  You think of combining them as a mess, but I think
of it as a big convenience because I could see all the iproc-related
code in one place :)

> It looks like pcie-iproc-msi.c followed existing pci drivers in
> place.  So if msi was cleaned up through the entire pci drivers then
> yes it would make sense to remove CONFIG_PCIE_IPROC_MSI and combine
> code in pcie-iproc.c.  But I think leaving the bcma and platform
> code in their own files makes it easier for us to work with the code
> rather than placing unused code in ifdefs in the same file.

But I do object *more* to putting MSI in a separate file, especially
given the fact that all PCIe devices that generate interrupts must
support MSI.  So any reasonable PCIe root complex must also support
MSI, and I don't really see the benefit of configuring and building it
separately.

Anyway, iproc is already in the tree, and we *could* maybe change
something eventually.

The question we need to decide now is about how brcmstb should be
structured.  Somebody mentioned an IRQ function that would have to be
exported for the brcmstb main driver & MSI pieces to be combined.  I
don't know what specifically that is, but I thought maybe iproc had
the same issue.

Bjorn
Jim Quinlan Oct. 25, 2017, 9:11 p.m. UTC | #8
On Wed, Oct 25, 2017 at 4:16 PM, Bjorn Helgaas <helgaas@kernel.org> wrote:
> On Wed, Oct 25, 2017 at 11:40:47AM -0700, Scott Branden wrote:
>> Hi Bjorn,
>>
>>
>> On 17-10-25 10:23 AM, Bjorn Helgaas wrote:
>> >[+cc Ray, Scott, Jon]
>> >
>> >On Wed, Oct 25, 2017 at 11:28:07AM -0400, Jim Quinlan wrote:
>> >>On Tue, Oct 24, 2017 at 2:57 PM, Florian Fainelli <f.fainelli@gmail.com> wrote:
>> >>>Hi Jim,
>> >>>
>> >>>On 10/24/2017 11:15 AM, Jim Quinlan wrote:
>> >>>>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/host/Kconfig           |  12 ++
>> >>>>  drivers/pci/host/Makefile          |   1 +
>> >>>>  drivers/pci/host/pci-brcmstb-msi.c | 318 +++++++++++++++++++++++++++++++++++++
>> >>>>  drivers/pci/host/pci-brcmstb.c     |  72 +++++++--
>> >>>>  drivers/pci/host/pci-brcmstb.h     |  26 +++
>> >>>>  5 files changed, 419 insertions(+), 10 deletions(-)
>> >>>>  create mode 100644 drivers/pci/host/pci-brcmstb-msi.c
>> >>>>
>> >>>>diff --git a/drivers/pci/host/Kconfig b/drivers/pci/host/Kconfig
>> >>>>index b9b4f11..54aa5d2 100644
>> >>>>--- a/drivers/pci/host/Kconfig
>> >>>>+++ b/drivers/pci/host/Kconfig
>> >>>>@@ -228,4 +228,16 @@ config PCI_BRCMSTB
>> >>>>       default ARCH_BRCMSTB || BMIPS_GENERIC
>> >>>>       help
>> >>>>         Adds support for Broadcom Settop Box PCIe host controller.
>> >>>>+       To compile this driver as a module, choose m here.
>> >>>>+
>> >>>>+config PCI_BRCMSTB_MSI
>> >>>>+     bool "Broadcom Brcmstb PCIe MSI support"
>> >>>>+     depends on ARCH_BRCMSTB || BMIPS_GENERIC
>> >>>This could probably be depends on PCI_BRCMSTB, which would imply these
>> >>>two conditions. PCI_BRCMSTB_MSI on its own is probably not very useful
>> >>>without the parent RC driver.
>> >>>
>> >>>>+     depends on OF
>> >>>>+     depends on PCI_MSI
>> >>>>+     default PCI_BRCMSTB
>> >>>>+     help
>> >>>>+       Say Y here if you want to enable MSI support for Broadcom's iProc
>> >>>>+       PCIe controller
>> >>>>+
>> >>>>  endmenu
>> >>>>diff --git a/drivers/pci/host/Makefile b/drivers/pci/host/Makefile
>> >>>>index c283321..1026d6f 100644
>> >>>>--- a/drivers/pci/host/Makefile
>> >>>>+++ b/drivers/pci/host/Makefile
>> >>>>@@ -23,6 +23,7 @@ obj-$(CONFIG_PCIE_TANGO_SMP8759) += pcie-tango.o
>> >>>>  obj-$(CONFIG_VMD) += vmd.o
>> >>>>  obj-$(CONFIG_PCI_BRCMSTB) += brcmstb-pci.o
>> >>>>  brcmstb-pci-objs := pci-brcmstb.o pci-brcmstb-dma.o
>> >>>>+obj-$(CONFIG_PCI_BRCMSTB_MSI) += pci-brcmstb-msi.o
>> >>>Should we combine this file with the brcmstb-pci.o? There is probably no
>> >>>functional difference, except that pci-brcmstb-msi.ko needs to be loaded
>> >>>first, right?
>> >>>--
>> >>>Florian
>> >>If you look at the pci/host/Kconfig you will see that other drivers
>> >>also have a separate MSI config (eg iproc, altera, xgene) so there is
>> >>precedent.  The reason that pci-brcmstb-msi.c is its own file is
>> >>because it depends on an irq function that is not exported.  That is
>> >>why CONFIG_PCI_BRCMSTB_MSI is bool, and CONFIG_PCI_BRCMSTB is
>> >>tristate.  -- Jim
>> >There is precedent, but that doesn't mean I like it :)
>> >I would strongly prefer one file per driver when possible.
>> >
>> >Take iproc for example.  iproc-msi.c is enabled by a Kconfig bool.  It
>> >contains a bunch of code with the only external entry points being
>> >iproc_msi_init() and iproc_msi_exit().  These are only called via
>> >iproc_pcie_bcma_probe() or iproc_pcie_pltfm_probe(), both of which are
>> >tristate.  So iproc-msi.c is only compiled if CONFIG_IPROC_BCMA or
>> >CONFIG_IPROC_PLATFORM are enabled, but all that text is loaded even if
>> >neither module is loaded, which seems suboptimal.
>> >
>> >I don't care if you have several config options to enable the BCMA
>> >probe and the platform probe (although these could probably be
>> >replaced in the code by a simple "#ifdef CONFIG_BCMA" and "#ifdef
>> >CONFIG_OF"), and making CONFIG_PCIE_IPROC tristate so it can be a
>> >module makes sense.  But I think it would be better to put all the
>> >code in one file instead of five, and probably remove
>> >CONFIG_PCIE_IPROC_MSI.  Maybe this requires exporting some IRQ
>> >function that currently isn't exported.  But that seems like a simpler
>> >solution than what we currently have.
>> Placing pcie-iproc-bcma.c in its own file is useful in being able to
>> read the code that is actually used.  BCMA is really unnecessary if
>> a few platforms stopped using BCMA and declared everything via
>> devicetree or ACPI.  Same with pcie-iproc-platform.c.  Both keep the
>> mess out of pcie-iproc.c.
>
> Maybe.  Both pcie-iproc-bcma.c and pcie-iproc-platform.c are small
> (280 lines combined) relative to pcie-iproc.c + pcie-iproc-msi.c (2150
> lines combined), and keeping them separate requires pcie-iproc.h,
> which could otherwise be folded into pcie-iproc.c.  So I'm still a
> little skeptical.  You think of combining them as a mess, but I think
> of it as a big convenience because I could see all the iproc-related
> code in one place :)
>
>> It looks like pcie-iproc-msi.c followed existing pci drivers in
>> place.  So if msi was cleaned up through the entire pci drivers then
>> yes it would make sense to remove CONFIG_PCIE_IPROC_MSI and combine
>> code in pcie-iproc.c.  But I think leaving the bcma and platform
>> code in their own files makes it easier for us to work with the code
>> rather than placing unused code in ifdefs in the same file.
>
> But I do object *more* to putting MSI in a separate file, especially
> given the fact that all PCIe devices that generate interrupts must
> support MSI.  So any reasonable PCIe root complex must also support
> MSI, and I don't really see the benefit of configuring and building it
> separately.
>
> Anyway, iproc is already in the tree, and we *could* maybe change
> something eventually.
>
> The question we need to decide now is about how brcmstb should be
> structured.  Somebody mentioned an IRQ function that would have to be
> exported for the brcmstb main driver & MSI pieces to be combined.  I
> don't know what specifically that is, but I thought maybe iproc had
> the same issue.
I was one the one who said an export was needed but I just recompiled
the MSI code as a module and had no problem.  I was probably using an
older tree when I concluded this.  So I'm fine with combining the MSI
code in the same file as the driver.

>
> Bjorn
diff mbox series

Patch

diff --git a/drivers/pci/host/Kconfig b/drivers/pci/host/Kconfig
index b9b4f11..54aa5d2 100644
--- a/drivers/pci/host/Kconfig
+++ b/drivers/pci/host/Kconfig
@@ -228,4 +228,16 @@  config PCI_BRCMSTB
 	default ARCH_BRCMSTB || BMIPS_GENERIC
 	help
 	  Adds support for Broadcom Settop Box PCIe host controller.
+	  To compile this driver as a module, choose m here.
+
+config PCI_BRCMSTB_MSI
+	bool "Broadcom Brcmstb PCIe MSI support"
+	depends on ARCH_BRCMSTB || BMIPS_GENERIC
+	depends on OF
+	depends on PCI_MSI
+	default PCI_BRCMSTB
+	help
+	  Say Y here if you want to enable MSI support for Broadcom's iProc
+	  PCIe controller
+
 endmenu
diff --git a/drivers/pci/host/Makefile b/drivers/pci/host/Makefile
index c283321..1026d6f 100644
--- a/drivers/pci/host/Makefile
+++ b/drivers/pci/host/Makefile
@@ -23,6 +23,7 @@  obj-$(CONFIG_PCIE_TANGO_SMP8759) += pcie-tango.o
 obj-$(CONFIG_VMD) += vmd.o
 obj-$(CONFIG_PCI_BRCMSTB) += brcmstb-pci.o
 brcmstb-pci-objs := pci-brcmstb.o pci-brcmstb-dma.o
+obj-$(CONFIG_PCI_BRCMSTB_MSI) += pci-brcmstb-msi.o
 
 # The following drivers are for devices that use the generic ACPI
 # pci_root.c driver but don't support standard ECAM config access.
diff --git a/drivers/pci/host/pci-brcmstb-msi.c b/drivers/pci/host/pci-brcmstb-msi.c
new file mode 100644
index 0000000..c805e2f
--- /dev/null
+++ b/drivers/pci/host/pci-brcmstb-msi.c
@@ -0,0 +1,318 @@ 
+/*
+ * Copyright (C) 2015-2017 Broadcom
+ *
+ * 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.
+ *
+ */
+#include <linux/bitops.h>
+#include <linux/compiler.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.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_irq.h>
+#include <linux/of_pci.h>
+#include <linux/of_platform.h>
+#include <linux/pci.h>
+#include <linux/types.h>
+
+#include "pci-brcmstb.h"
+
+#define PCIE_MISC_MSI_DATA_CONFIG			0x404c
+#define PCIE_MSI_INTR2_BASE				0x4500
+#define PCIE_MISC_MSI_BAR_CONFIG_LO			0x4044
+#define PCIE_MISC_MSI_BAR_CONFIG_HI			0x4048
+
+/* Offsets from PCIE_INTR2_CPU_BASE and PCIE_MSI_INTR2_BASE */
+#define STATUS				0x0
+#define SET				0x4
+#define CLR				0x8
+#define MASK_STATUS			0xc
+#define MASK_SET			0x10
+#define MASK_CLR			0x14
+
+struct brcm_msi {
+	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;
+
+	void __iomem *base;
+	struct device *dev;
+	struct device_node *dn;
+	unsigned int rev;
+};
+
+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;
+
+	chained_irq_enter(chip, desc);
+	msi = irq_desc_get_handler_data(desc);
+	mask = msi->intr_legacy_mask;
+
+	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(msi->dev, "unhandled MSI %d\n",
+						 hwirq);
+			} else {
+				/* Unknown MSI, just clear it */
+				dev_dbg(msi->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);
+}
+
+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);
+}
+EXPORT_SYMBOL(brcm_msi_set_regs);
+
+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);
+
+	msi->inner_domain = irq_domain_add_linear(NULL, BRCM_INT_PCI_MSI_NR,
+						  &msi_domain_ops, msi);
+	if (!msi->inner_domain) {
+		dev_err(msi->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(msi->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);
+}
+
+void brcm_msi_remove(struct brcm_msi *msi)
+{
+	if (!msi)
+		return;
+	irq_set_chained_handler(msi->irq, NULL);
+	irq_set_handler_data(msi->irq, NULL);
+	brcm_free_domains(msi);
+}
+EXPORT_SYMBOL(brcm_msi_remove);
+
+int brcm_msi_probe(struct platform_device *pdev, struct brcm_info *info)
+{
+	struct brcm_msi *msi;
+	int irq, ret;
+
+	irq = irq_of_parse_and_map(pdev->dev.of_node, 1);
+	if (irq <= 0) {
+		dev_err(&pdev->dev, "cannot map msi intr\n");
+		return -ENODEV;
+	}
+
+	msi = devm_kzalloc(&pdev->dev, sizeof(struct brcm_msi), GFP_KERNEL);
+	if (!msi)
+		return -ENOMEM;
+
+	msi->dev = &pdev->dev;
+	msi->base = info->base;
+	msi->rev =  info->rev;
+	msi->dn = pdev->dev.of_node;
+	msi->target_addr = info->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);
+	info->msi = msi;
+
+	return 0;
+}
+EXPORT_SYMBOL(brcm_msi_probe);
diff --git a/drivers/pci/host/pci-brcmstb.c b/drivers/pci/host/pci-brcmstb.c
index f5d80e6..99900d9 100644
--- a/drivers/pci/host/pci-brcmstb.c
+++ b/drivers/pci/host/pci-brcmstb.c
@@ -22,6 +22,7 @@ 
 #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>
@@ -327,10 +328,13 @@  struct brcm_pcie {
 	int			num_out_wins;
 	bool			ssc;
 	int			gen;
+	u64			msi_target_addr;
 	struct brcm_window	out_wins[BRCM_NUM_PCI_OUT_WINS];
 	struct list_head	resources;
 	struct device		*dev;
 	struct list_head	pwr_supplies;
+	struct brcm_msi		*msi;
+	bool			msi_internal;
 	unsigned int		rev;
 	unsigned int		num;
 	bool			bridge_setup_done;
@@ -763,6 +767,7 @@  static void brcm_pcie_setup_prep(struct brcm_pcie *pcie)
 	void __iomem *base = pcie->base;
 	unsigned int scb_size_val;
 	u64 rc_bar2_size = 0, rc_bar2_offset = 0, total_mem_size = 0;
+	u64 msi_target_addr;
 	u32 tmp, burst;
 	int i;
 
@@ -806,14 +811,17 @@  static void brcm_pcie_setup_prep(struct brcm_pcie *pcie)
 	/* The PCI 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 pci-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
+	 * pci-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);
 
@@ -828,6 +836,14 @@  static void brcm_pcie_setup_prep(struct brcm_pcie *pcie)
 		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
 			 * cannot start the inbound region at location
@@ -836,15 +852,24 @@  static void brcm_pcie_setup_prep(struct brcm_pcie *pcie)
 			 * 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
 		 * only.  We always start the viewport at address 0,
 		 * 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,
@@ -1053,6 +1078,9 @@  static int brcm_pcie_resume(struct device *dev)
 	if (ret)
 		return ret;
 
+	if (pcie->msi && pcie->msi_internal)
+		brcm_msi_set_regs(pcie->msi);
+
 	pcie->suspended = false;
 
 	return 0;
@@ -1116,6 +1144,7 @@  static void __attribute__((__section__("pci_fixup_early")))
 
 static void _brcm_pcie_remove(struct brcm_pcie *pcie)
 {
+	brcm_msi_remove(pcie->msi);
 	turn_off(pcie);
 	clk_disable_unprepare(pcie->clk);
 	clk_put(pcie->clk);
@@ -1125,7 +1154,7 @@  static void _brcm_pcie_remove(struct brcm_pcie *pcie)
 
 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;
 	struct property *pp;
 	const struct of_device_id *of_id;
 	const struct pcie_cfg_data *data;
@@ -1244,6 +1273,29 @@  static int brcm_pcie_probe(struct platform_device *pdev)
 	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 (IS_ENABLED(CONFIG_PCI_MSI) && pci_msi_enabled()
+	    && msi_dn == pcie->dn) {
+		struct brcm_info info;
+
+		info.rev = pcie->rev;
+		info.msi_target_addr = pcie->msi_target_addr;
+		info.base = pcie->base;
+
+		ret = brcm_msi_probe(pdev, &info);
+		if (ret)
+			dev_err(pcie->dev,
+				"probe of internal MSI failed: %d)", ret);
+		else
+			pcie->msi_internal = true;
+
+		pcie->msi = info.msi;
+	}
+
 	list_splice_init(&pcie->resources, &bridge->windows);
 	bridge->dev.parent = &pdev->dev;
 	bridge->busnr = 0;
diff --git a/drivers/pci/host/pci-brcmstb.h b/drivers/pci/host/pci-brcmstb.h
index c3b148a..f969c2a 100644
--- a/drivers/pci/host/pci-brcmstb.h
+++ b/drivers/pci/host/pci-brcmstb.h
@@ -27,6 +27,32 @@ 
 extern struct of_pci_range *dma_ranges;
 extern int num_dma_ranges;
 
+struct brcm_msi;
+struct brcm_info;
+struct platform_device;
+
+#ifdef CONFIG_PCI_BRCMSTB_MSI
+int brcm_msi_probe(struct platform_device *pdev, struct brcm_info *info);
+void brcm_msi_set_regs(struct brcm_msi *chip);
+void brcm_msi_remove(struct brcm_msi *chip);
+#else
+static inline int brcm_msi_probe(struct platform_device *pdev,
+				 struct brcm_info *info)
+{
+	return -ENODEV;
+}
+
+static inline void brcm_msi_set_regs(struct brcm_msi *chip) {}
+static inline void brcm_msi_remove(struct brcm_msi *chip) {}
+#endif /* CONFIG_PCI_BRCMSTB_MSI */
+
+struct brcm_info {
+	int rev;
+	u64 msi_target_addr;
+	void __iomem *base;
+	struct brcm_msi *msi;
+};
+
 #if defined(CONFIG_MIPS)
 /* Broadcom MIPs HW implicitly does the swapping if necessary */
 #define bcm_readl(a)		__raw_readl(a)