Patchwork [RFCv1,09/11] pci: mvebu: add MSI support

login
register
mail settings
Submitter Thomas Petazzoni
Date March 26, 2013, 4:52 p.m.
Message ID <1364316746-8702-10-git-send-email-thomas.petazzoni@free-electrons.com>
Download mbox | patch
Permalink /patch/231467/
State Not Applicable
Headers show

Comments

Thomas Petazzoni - March 26, 2013, 4:52 p.m.
This commit adds the MSI support for the Marvell EBU PCIe driver. The
driver now looks at the 'msi-parent' property of the PCIe controller
DT node, and if it exists, it gets the associated IRQ domain, which
should be the MSI interrupt controller registered by the IRQ
controller driver.

Using this, the PCIe driver registers the ->setup_irq() and
->teardown_irq() callbacks using the newly introduced msi_chip
infrastructure, which allows the kernel PCI core to use the MSI
functionality.

Signed-off-by: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
---
 .../devicetree/bindings/pci/mvebu-pci.txt          |    5 +
 drivers/pci/host/pci-mvebu.c                       |  128 ++++++++++++++++++++
 2 files changed, 133 insertions(+)
Andrew Murray - March 27, 2013, 10:07 a.m.
On Tue, Mar 26, 2013 at 04:52:24PM +0000, Thomas Petazzoni wrote:
> This commit adds the MSI support for the Marvell EBU PCIe driver. The
> driver now looks at the 'msi-parent' property of the PCIe controller
> DT node, and if it exists, it gets the associated IRQ domain, which
> should be the MSI interrupt controller registered by the IRQ
> controller driver.
> 
> Using this, the PCIe driver registers the ->setup_irq() and
> ->teardown_irq() callbacks using the newly introduced msi_chip
> infrastructure, which allows the kernel PCI core to use the MSI
> functionality.
> 
> Signed-off-by: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
> ---
>  .../devicetree/bindings/pci/mvebu-pci.txt          |    5 +
>  drivers/pci/host/pci-mvebu.c                       |  128 ++++++++++++++++++++
>  2 files changed, 133 insertions(+)
> 
> diff --git a/Documentation/devicetree/bindings/pci/mvebu-pci.txt b/Documentation/devicetree/bindings/pci/mvebu-pci.txt
> index 192bdfb..53cc437 100644
> --- a/Documentation/devicetree/bindings/pci/mvebu-pci.txt
> +++ b/Documentation/devicetree/bindings/pci/mvebu-pci.txt
> @@ -14,6 +14,10 @@ Mandatory properties:
>    corresponding registers
>  - ranges: ranges for the PCI memory and I/O regions
>  
> +Optional properties:
> +- msi-parent: a phandle pointing to the interrupt controller that
> +  handles the MSI interrupts.
> +
>  In addition, the Device Tree node must have sub-nodes describing each
>  PCIe interface, having the following mandatory properties:
>  - reg: used only for interrupt mapping, so only the first four bytes
> @@ -43,6 +47,7 @@ pcie-controller {
>  	#address-cells = <3>;
>  	#size-cells = <2>;
>  
> +	msi-parent = <&msi>;
>  	bus-range = <0x00 0xff>;
>  
>  	reg = <0xd0040000 0x2000>, <0xd0042000 0x2000>,
> diff --git a/drivers/pci/host/pci-mvebu.c b/drivers/pci/host/pci-mvebu.c
> index 9e6b137..b46fab8 100644
> --- a/drivers/pci/host/pci-mvebu.c
> +++ b/drivers/pci/host/pci-mvebu.c
> @@ -7,17 +7,23 @@
>   */
>  
>  #include <linux/kernel.h>
> +#include <linux/irq.h>
> +#include <linux/irqdomain.h>
>  #include <linux/pci.h>
> +#include <linux/msi.h>
>  #include <linux/clk.h>
>  #include <linux/module.h>
>  #include <linux/mbus.h>
>  #include <linux/slab.h>
>  #include <linux/platform_device.h>
> +#include <linux/interrupt.h>
>  #include <linux/of_address.h>
>  #include <linux/of_pci.h>
>  #include <linux/of_irq.h>
>  #include <linux/of_platform.h>
>  
> +#define INT_PCI_MSI_NR (16)
> +
>  /*
>   * PCIe unit register offsets.
>   */
> @@ -101,10 +107,19 @@ struct mvebu_sw_pci_bridge {
>  
>  struct mvebu_pcie_port;
>

This structure...
 
> +struct mvebu_pcie_msi {
> +	DECLARE_BITMAP(used, INT_PCI_MSI_NR);
> +	struct irq_domain *domain;
> +	struct mutex lock;
> +	struct msi_chip chip;
> +	phys_addr_t doorbell;
> +};

And these functions...

> +#if defined(CONFIG_PCI_MSI)
> +static int mvebu_pcie_msi_alloc(struct mvebu_pcie_msi *chip)
> +{
> +	int msi;
> +
> +	mutex_lock(&chip->lock);
> +
> +	msi = find_first_zero_bit(chip->used, INT_PCI_MSI_NR);
> +
> +	if (msi < INT_PCI_MSI_NR)
> +		set_bit(msi, chip->used);
> +	else
> +		msi = -ENOSPC;
> +
> +	mutex_unlock(&chip->lock);
> +
> +	return msi;
> +}
> +
> +static void mvebu_pcie_msi_free(struct mvebu_pcie_msi *chip, unsigned long irq)
> +{
> +	struct device *dev = chip->chip.dev;
> +
> +	mutex_lock(&chip->lock);
> +
> +	if (!test_bit(irq, chip->used))
> +		dev_err(dev, "trying to free unused MSI#%lu\n", irq);
> +	else
> +		clear_bit(irq, chip->used);
> +
> +	mutex_unlock(&chip->lock);
> +}
> +
> +static int mvebu_pcie_setup_msi_irq(struct msi_chip *chip,
> +				    struct pci_dev *pdev,
> +				    struct msi_desc *desc)
> +{
> +	struct mvebu_pcie_msi *msi = to_mvebu_msi(chip);
> +	struct msi_msg msg;
> +	unsigned int irq;
> +	int hwirq;
> +
> +	hwirq = mvebu_pcie_msi_alloc(msi);
> +	if (hwirq < 0)
> +		return hwirq;
> +
> +	irq = irq_create_mapping(msi->domain, hwirq);
> +	if (!irq)
> +		return -EINVAL;
> +
> +	irq_set_msi_desc(irq, desc);
> +
> +	msg.address_lo = msi->doorbell;
> +	msg.address_hi = 0;
> +	msg.data = 0xf00 | (hwirq + 16);
> +
> +	write_msi_msg(irq, &msg);
> +
> +	return 0;
> +}
> +
> +static void mvebu_pcie_teardown_msi_irq(struct msi_chip *chip,
> +					unsigned int irq)
> +{
> +	struct mvebu_pcie_msi *msi = to_mvebu_msi(chip);
> +	struct irq_data *d = irq_get_irq_data(irq);
> +
> +	mvebu_pcie_msi_free(msi, d->hwirq);
> +}

...are not related to your PCIe driver or PCI. I remember you said that the MSI
registers are all inter-mixed with the interrupt controller. Therefore as the
above don't interact with PCIe controller registers can you move this out to an
irqchip driver or include it inside your interrupt controller?

> +
> +static int mvebu_pcie_enable_msi(struct mvebu_pcie *pcie)
> +{
> +	struct device_node *msi_node;
> +	struct mvebu_pcie_msi *msi;
> +
> +	msi = &pcie->msi;
> +
> +	mutex_init(&msi->lock);
> +
> +	msi_node = of_parse_phandle(pcie->pdev->dev.of_node,
> +				    "msi-parent", 0);
> +	if (!msi_node)
> +		return -EINVAL;
> +
> +	msi->domain = irq_find_host(msi_node);
> +	if (!msi->domain)
> +		return -EINVAL;
> +
> +	if (of_property_read_u32(msi_node, "marvell,doorbell", &msi->doorbell))
> +		return -EINVAL;
> +
> +	msi->chip.dev = &pcie->pdev->dev;
> +	msi->chip.setup_irq = mvebu_pcie_setup_msi_irq;
> +	msi->chip.teardown_irq = mvebu_pcie_teardown_msi_irq;
> +
> +	return 0;
> +}

This can change too, as per Thierry's suggestion you can call something like:

msi_chip_add(&msi)

from your interrupt controller. And then in this file you can call something
like:

of_find_msi_chip_by_node(msi_node)

and use the returned chip to assign to pcie->msi (and register with the pci_bus).
Perhaps then you may not need to expose the doorbell register as that would be
knowledge that the interrupt controller may already have (i.e. offset from base
address already provided in DT).

Andrew Murray

> +#endif /* CONFIG_PCI_MSI */
> +
>  static int __init mvebu_pcie_probe(struct platform_device *pdev)
>  {
>  	struct mvebu_pcie *pcie;
> @@ -903,6 +1024,13 @@ static int __init mvebu_pcie_probe(struct platform_device *pdev)
>  
>  	mvebu_pcie_enable(pcie);
>  
> +#ifdef CONFIG_PCI_MSI
> +	ret = mvebu_pcie_enable_msi(pcie);
> +	if (ret)
> +		dev_warn(&pdev->dev, "could not enable MSI support: %d\n",
> +			 ret);
> +#endif
> +
>  	return 0;
>  }
>  
> -- 
> 1.7.9.5
> 
> 
--
To unsubscribe from this list: send the line "unsubscribe linux-pci" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Bjorn Helgaas - April 8, 2013, 10:29 p.m.
On Tue, Mar 26, 2013 at 10:52 AM, Thomas Petazzoni
<thomas.petazzoni@free-electrons.com> wrote:
> This commit adds the MSI support for the Marvell EBU PCIe driver. The
> driver now looks at the 'msi-parent' property of the PCIe controller
> DT node, and if it exists, it gets the associated IRQ domain, which
> should be the MSI interrupt controller registered by the IRQ
> controller driver.
>
> Using this, the PCIe driver registers the ->setup_irq() and
> ->teardown_irq() callbacks using the newly introduced msi_chip
> infrastructure, which allows the kernel PCI core to use the MSI
> functionality.
>
> Signed-off-by: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
> ---
>  .../devicetree/bindings/pci/mvebu-pci.txt          |    5 +
>  drivers/pci/host/pci-mvebu.c                       |  128 ++++++++++++++++++++
>  2 files changed, 133 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/pci/mvebu-pci.txt b/Documentation/devicetree/bindings/pci/mvebu-pci.txt
> index 192bdfb..53cc437 100644
> --- a/Documentation/devicetree/bindings/pci/mvebu-pci.txt
> +++ b/Documentation/devicetree/bindings/pci/mvebu-pci.txt
> @@ -14,6 +14,10 @@ Mandatory properties:
>    corresponding registers
>  - ranges: ranges for the PCI memory and I/O regions
>
> +Optional properties:
> +- msi-parent: a phandle pointing to the interrupt controller that
> +  handles the MSI interrupts.
> +
>  In addition, the Device Tree node must have sub-nodes describing each
>  PCIe interface, having the following mandatory properties:
>  - reg: used only for interrupt mapping, so only the first four bytes
> @@ -43,6 +47,7 @@ pcie-controller {
>         #address-cells = <3>;
>         #size-cells = <2>;
>
> +       msi-parent = <&msi>;
>         bus-range = <0x00 0xff>;
>
>         reg = <0xd0040000 0x2000>, <0xd0042000 0x2000>,
> diff --git a/drivers/pci/host/pci-mvebu.c b/drivers/pci/host/pci-mvebu.c
> index 9e6b137..b46fab8 100644
> --- a/drivers/pci/host/pci-mvebu.c
> +++ b/drivers/pci/host/pci-mvebu.c
> @@ -7,17 +7,23 @@
>   */
>
>  #include <linux/kernel.h>
> +#include <linux/irq.h>
> +#include <linux/irqdomain.h>
>  #include <linux/pci.h>
> +#include <linux/msi.h>
>  #include <linux/clk.h>
>  #include <linux/module.h>
>  #include <linux/mbus.h>
>  #include <linux/slab.h>
>  #include <linux/platform_device.h>
> +#include <linux/interrupt.h>
>  #include <linux/of_address.h>
>  #include <linux/of_pci.h>
>  #include <linux/of_irq.h>
>  #include <linux/of_platform.h>
>
> +#define INT_PCI_MSI_NR (16)
> +
>  /*
>   * PCIe unit register offsets.
>   */
> @@ -101,10 +107,19 @@ struct mvebu_sw_pci_bridge {
>
>  struct mvebu_pcie_port;
>
> +struct mvebu_pcie_msi {
> +       DECLARE_BITMAP(used, INT_PCI_MSI_NR);
> +       struct irq_domain *domain;
> +       struct mutex lock;
> +       struct msi_chip chip;
> +       phys_addr_t doorbell;
> +};
> +
>  /* Structure representing all PCIe interfaces */
>  struct mvebu_pcie {
>         struct platform_device *pdev;
>         struct mvebu_pcie_port *ports;
> +       struct mvebu_pcie_msi msi;
>         struct resource io;
>         struct resource realio;
>         struct resource mem;
> @@ -546,6 +561,11 @@ static inline struct mvebu_pcie *sys_to_pcie(struct pci_sys_data *sys)
>         return sys->private_data;
>  }
>
> +static inline struct mvebu_pcie_msi *to_mvebu_msi(struct msi_chip *chip)
> +{
> +       return container_of(chip, struct mvebu_pcie_msi, chip);
> +}
> +
>  /* Find the PCIe interface that corresponds to the given bus */
>  static struct mvebu_pcie_port *mvebu_find_port_from_bus(struct mvebu_pcie *pcie,
>                                                         int bus)
> @@ -710,6 +730,8 @@ static struct pci_bus *mvebu_pcie_scan_bus(int nr, struct pci_sys_data *sys)
>         if (!bus)
>                 return NULL;
>
> +       bus->msi = &pcie->msi.chip;

As I mentioned in the 08/11 patch, I'd like to keep arch-specific
stuff out of the PCI scanning path to preserve the ability to move
toward pci_scan_root_bus() eventually.

>         pci_scan_child_bus(bus);
>
>         return bus;
> @@ -786,6 +808,105 @@ static void __iomem *mvebu_pcie_map_registers(struct platform_device *pdev,
>         return devm_request_and_ioremap(&pdev->dev, &regs);
>  }
>
> +#if defined(CONFIG_PCI_MSI)
> +static int mvebu_pcie_msi_alloc(struct mvebu_pcie_msi *chip)
> +{
> +       int msi;
> +
> +       mutex_lock(&chip->lock);
> +
> +       msi = find_first_zero_bit(chip->used, INT_PCI_MSI_NR);
> +
> +       if (msi < INT_PCI_MSI_NR)
> +               set_bit(msi, chip->used);
> +       else
> +               msi = -ENOSPC;
> +
> +       mutex_unlock(&chip->lock);
> +
> +       return msi;
> +}
> +
> +static void mvebu_pcie_msi_free(struct mvebu_pcie_msi *chip, unsigned long irq)
> +{
> +       struct device *dev = chip->chip.dev;
> +
> +       mutex_lock(&chip->lock);
> +
> +       if (!test_bit(irq, chip->used))
> +               dev_err(dev, "trying to free unused MSI#%lu\n", irq);
> +       else
> +               clear_bit(irq, chip->used);
> +
> +       mutex_unlock(&chip->lock);
> +}
> +
> +static int mvebu_pcie_setup_msi_irq(struct msi_chip *chip,
> +                                   struct pci_dev *pdev,
> +                                   struct msi_desc *desc)
> +{
> +       struct mvebu_pcie_msi *msi = to_mvebu_msi(chip);

If this took only the pci_dev (not the msi_chip), I think you could do this:

  struct mvebu_pcie_msi *msi = &pdev->bus->sysdata->msi;

> +       struct msi_msg msg;
> +       unsigned int irq;
> +       int hwirq;
> +
> +       hwirq = mvebu_pcie_msi_alloc(msi);
> +       if (hwirq < 0)
> +               return hwirq;
> +
> +       irq = irq_create_mapping(msi->domain, hwirq);
> +       if (!irq)
> +               return -EINVAL;
> +
> +       irq_set_msi_desc(irq, desc);
> +
> +       msg.address_lo = msi->doorbell;
> +       msg.address_hi = 0;
> +       msg.data = 0xf00 | (hwirq + 16);
> +
> +       write_msi_msg(irq, &msg);
> +
> +       return 0;
> +}
> +
> +static void mvebu_pcie_teardown_msi_irq(struct msi_chip *chip,
> +                                       unsigned int irq)
> +{
> +       struct mvebu_pcie_msi *msi = to_mvebu_msi(chip);
> +       struct irq_data *d = irq_get_irq_data(irq);
> +
> +       mvebu_pcie_msi_free(msi, d->hwirq);
> +}
> +
> +static int mvebu_pcie_enable_msi(struct mvebu_pcie *pcie)
> +{
> +       struct device_node *msi_node;
> +       struct mvebu_pcie_msi *msi;
> +
> +       msi = &pcie->msi;
> +
> +       mutex_init(&msi->lock);
> +
> +       msi_node = of_parse_phandle(pcie->pdev->dev.of_node,
> +                                   "msi-parent", 0);
> +       if (!msi_node)
> +               return -EINVAL;
> +
> +       msi->domain = irq_find_host(msi_node);
> +       if (!msi->domain)
> +               return -EINVAL;
> +
> +       if (of_property_read_u32(msi_node, "marvell,doorbell", &msi->doorbell))
> +               return -EINVAL;
> +
> +       msi->chip.dev = &pcie->pdev->dev;
> +       msi->chip.setup_irq = mvebu_pcie_setup_msi_irq;
> +       msi->chip.teardown_irq = mvebu_pcie_teardown_msi_irq;
> +
> +       return 0;
> +}
> +#endif /* CONFIG_PCI_MSI */
> +
>  static int __init mvebu_pcie_probe(struct platform_device *pdev)
>  {
>         struct mvebu_pcie *pcie;
> @@ -903,6 +1024,13 @@ static int __init mvebu_pcie_probe(struct platform_device *pdev)
>
>         mvebu_pcie_enable(pcie);
>
> +#ifdef CONFIG_PCI_MSI
> +       ret = mvebu_pcie_enable_msi(pcie);
> +       if (ret)
> +               dev_warn(&pdev->dev, "could not enable MSI support: %d\n",
> +                        ret);
> +#endif
> +
>         return 0;
>  }
>
> --
> 1.7.9.5
>
--
To unsubscribe from this list: send the line "unsubscribe linux-pci" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Thierry Reding - May 30, 2013, 12:15 p.m.
On Mon, Apr 08, 2013 at 04:29:07PM -0600, Bjorn Helgaas wrote:
> On Tue, Mar 26, 2013 at 10:52 AM, Thomas Petazzoni <thomas.petazzoni@free-electrons.com> wrote:
[...]
> > +static int mvebu_pcie_setup_msi_irq(struct msi_chip *chip,
> > +                                   struct pci_dev *pdev,
> > +                                   struct msi_desc *desc)
> > +{
> > +       struct mvebu_pcie_msi *msi = to_mvebu_msi(chip);
> 
> If this took only the pci_dev (not the msi_chip), I think you could do this:
> 
>   struct mvebu_pcie_msi *msi = &pdev->bus->sysdata->msi;

That would mean that the arch_setup_msi_irq() and friends could still be
architecture-agnostic because they only pass around pci_dev, and the
driver specific implementations would know how to lookup sysdata and
from there the MSI chip.

So I was almost convinced that putting the struct msi_chip pointer into
sysdata is a good idea. However that also means that each PCI host
bridge driver becomes architecture-specific. If we ever get a driver
that can be used on multiple architectures (however unlikely), the only
way to make it work would be to #ifdef those parts. We could make that
easier to deal with by providing an accessor (pci_sysdata_set_msi_chip()
or similar), though.

But maybe it's something we don't need to be concerned about because no
PCI host bridge driver will ever support two different architecture?

One related point is compile coverage. If the drivers are completely
architecture-agnostic it makes it a lot easier to compile-test all
drivers, which might come in useful when doing core changes and such.

Thierry
Bjorn Helgaas - May 30, 2013, 6:13 p.m.
On Thu, May 30, 2013 at 6:15 AM, Thierry Reding
<thierry.reding@gmail.com> wrote:
> On Mon, Apr 08, 2013 at 04:29:07PM -0600, Bjorn Helgaas wrote:
>> On Tue, Mar 26, 2013 at 10:52 AM, Thomas Petazzoni <thomas.petazzoni@free-electrons.com> wrote:
> [...]
>> > +static int mvebu_pcie_setup_msi_irq(struct msi_chip *chip,
>> > +                                   struct pci_dev *pdev,
>> > +                                   struct msi_desc *desc)
>> > +{
>> > +       struct mvebu_pcie_msi *msi = to_mvebu_msi(chip);
>>
>> If this took only the pci_dev (not the msi_chip), I think you could do this:
>>
>>   struct mvebu_pcie_msi *msi = &pdev->bus->sysdata->msi;
>
> That would mean that the arch_setup_msi_irq() and friends could still be
> architecture-agnostic because they only pass around pci_dev, and the
> driver specific implementations would know how to lookup sysdata and
> from there the MSI chip.
>
> So I was almost convinced that putting the struct msi_chip pointer into
> sysdata is a good idea. However that also means that each PCI host
> bridge driver becomes architecture-specific. If we ever get a driver
> that can be used on multiple architectures (however unlikely), the only
> way to make it work would be to #ifdef those parts. We could make that
> easier to deal with by providing an accessor (pci_sysdata_set_msi_chip()
> or similar), though.
>
> But maybe it's something we don't need to be concerned about because no
> PCI host bridge driver will ever support two different architecture?
>
> One related point is compile coverage. If the drivers are completely
> architecture-agnostic it makes it a lot easier to compile-test all
> drivers, which might come in useful when doing core changes and such.

Hmm, I've forgotten what I was thinking here.  (And I'm the *worst*
about reviving ancient threads myself).  So I drop whatever objection
I had :)

Bjorn
--
To unsubscribe from this list: send the line "unsubscribe linux-pci" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Patch

diff --git a/Documentation/devicetree/bindings/pci/mvebu-pci.txt b/Documentation/devicetree/bindings/pci/mvebu-pci.txt
index 192bdfb..53cc437 100644
--- a/Documentation/devicetree/bindings/pci/mvebu-pci.txt
+++ b/Documentation/devicetree/bindings/pci/mvebu-pci.txt
@@ -14,6 +14,10 @@  Mandatory properties:
   corresponding registers
 - ranges: ranges for the PCI memory and I/O regions
 
+Optional properties:
+- msi-parent: a phandle pointing to the interrupt controller that
+  handles the MSI interrupts.
+
 In addition, the Device Tree node must have sub-nodes describing each
 PCIe interface, having the following mandatory properties:
 - reg: used only for interrupt mapping, so only the first four bytes
@@ -43,6 +47,7 @@  pcie-controller {
 	#address-cells = <3>;
 	#size-cells = <2>;
 
+	msi-parent = <&msi>;
 	bus-range = <0x00 0xff>;
 
 	reg = <0xd0040000 0x2000>, <0xd0042000 0x2000>,
diff --git a/drivers/pci/host/pci-mvebu.c b/drivers/pci/host/pci-mvebu.c
index 9e6b137..b46fab8 100644
--- a/drivers/pci/host/pci-mvebu.c
+++ b/drivers/pci/host/pci-mvebu.c
@@ -7,17 +7,23 @@ 
  */
 
 #include <linux/kernel.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
 #include <linux/pci.h>
+#include <linux/msi.h>
 #include <linux/clk.h>
 #include <linux/module.h>
 #include <linux/mbus.h>
 #include <linux/slab.h>
 #include <linux/platform_device.h>
+#include <linux/interrupt.h>
 #include <linux/of_address.h>
 #include <linux/of_pci.h>
 #include <linux/of_irq.h>
 #include <linux/of_platform.h>
 
+#define INT_PCI_MSI_NR (16)
+
 /*
  * PCIe unit register offsets.
  */
@@ -101,10 +107,19 @@  struct mvebu_sw_pci_bridge {
 
 struct mvebu_pcie_port;
 
+struct mvebu_pcie_msi {
+	DECLARE_BITMAP(used, INT_PCI_MSI_NR);
+	struct irq_domain *domain;
+	struct mutex lock;
+	struct msi_chip chip;
+	phys_addr_t doorbell;
+};
+
 /* Structure representing all PCIe interfaces */
 struct mvebu_pcie {
 	struct platform_device *pdev;
 	struct mvebu_pcie_port *ports;
+	struct mvebu_pcie_msi msi;
 	struct resource io;
 	struct resource realio;
 	struct resource mem;
@@ -546,6 +561,11 @@  static inline struct mvebu_pcie *sys_to_pcie(struct pci_sys_data *sys)
 	return sys->private_data;
 }
 
+static inline struct mvebu_pcie_msi *to_mvebu_msi(struct msi_chip *chip)
+{
+	return container_of(chip, struct mvebu_pcie_msi, chip);
+}
+
 /* Find the PCIe interface that corresponds to the given bus */
 static struct mvebu_pcie_port *mvebu_find_port_from_bus(struct mvebu_pcie *pcie,
 							int bus)
@@ -710,6 +730,8 @@  static struct pci_bus *mvebu_pcie_scan_bus(int nr, struct pci_sys_data *sys)
 	if (!bus)
 		return NULL;
 
+	bus->msi = &pcie->msi.chip;
+
 	pci_scan_child_bus(bus);
 
 	return bus;
@@ -786,6 +808,105 @@  static void __iomem *mvebu_pcie_map_registers(struct platform_device *pdev,
 	return devm_request_and_ioremap(&pdev->dev, &regs);
 }
 
+#if defined(CONFIG_PCI_MSI)
+static int mvebu_pcie_msi_alloc(struct mvebu_pcie_msi *chip)
+{
+	int msi;
+
+	mutex_lock(&chip->lock);
+
+	msi = find_first_zero_bit(chip->used, INT_PCI_MSI_NR);
+
+	if (msi < INT_PCI_MSI_NR)
+		set_bit(msi, chip->used);
+	else
+		msi = -ENOSPC;
+
+	mutex_unlock(&chip->lock);
+
+	return msi;
+}
+
+static void mvebu_pcie_msi_free(struct mvebu_pcie_msi *chip, unsigned long irq)
+{
+	struct device *dev = chip->chip.dev;
+
+	mutex_lock(&chip->lock);
+
+	if (!test_bit(irq, chip->used))
+		dev_err(dev, "trying to free unused MSI#%lu\n", irq);
+	else
+		clear_bit(irq, chip->used);
+
+	mutex_unlock(&chip->lock);
+}
+
+static int mvebu_pcie_setup_msi_irq(struct msi_chip *chip,
+				    struct pci_dev *pdev,
+				    struct msi_desc *desc)
+{
+	struct mvebu_pcie_msi *msi = to_mvebu_msi(chip);
+	struct msi_msg msg;
+	unsigned int irq;
+	int hwirq;
+
+	hwirq = mvebu_pcie_msi_alloc(msi);
+	if (hwirq < 0)
+		return hwirq;
+
+	irq = irq_create_mapping(msi->domain, hwirq);
+	if (!irq)
+		return -EINVAL;
+
+	irq_set_msi_desc(irq, desc);
+
+	msg.address_lo = msi->doorbell;
+	msg.address_hi = 0;
+	msg.data = 0xf00 | (hwirq + 16);
+
+	write_msi_msg(irq, &msg);
+
+	return 0;
+}
+
+static void mvebu_pcie_teardown_msi_irq(struct msi_chip *chip,
+					unsigned int irq)
+{
+	struct mvebu_pcie_msi *msi = to_mvebu_msi(chip);
+	struct irq_data *d = irq_get_irq_data(irq);
+
+	mvebu_pcie_msi_free(msi, d->hwirq);
+}
+
+static int mvebu_pcie_enable_msi(struct mvebu_pcie *pcie)
+{
+	struct device_node *msi_node;
+	struct mvebu_pcie_msi *msi;
+
+	msi = &pcie->msi;
+
+	mutex_init(&msi->lock);
+
+	msi_node = of_parse_phandle(pcie->pdev->dev.of_node,
+				    "msi-parent", 0);
+	if (!msi_node)
+		return -EINVAL;
+
+	msi->domain = irq_find_host(msi_node);
+	if (!msi->domain)
+		return -EINVAL;
+
+	if (of_property_read_u32(msi_node, "marvell,doorbell", &msi->doorbell))
+		return -EINVAL;
+
+	msi->chip.dev = &pcie->pdev->dev;
+	msi->chip.setup_irq = mvebu_pcie_setup_msi_irq;
+	msi->chip.teardown_irq = mvebu_pcie_teardown_msi_irq;
+
+	return 0;
+}
+#endif /* CONFIG_PCI_MSI */
+
 static int __init mvebu_pcie_probe(struct platform_device *pdev)
 {
 	struct mvebu_pcie *pcie;
@@ -903,6 +1024,13 @@  static int __init mvebu_pcie_probe(struct platform_device *pdev)
 
 	mvebu_pcie_enable(pcie);
 
+#ifdef CONFIG_PCI_MSI
+	ret = mvebu_pcie_enable_msi(pcie);
+	if (ret)
+		dev_warn(&pdev->dev, "could not enable MSI support: %d\n",
+			 ret);
+#endif
+
 	return 0;
 }