diff mbox

[10/11] ARM: tegra: pcie: Add MSI support

Message ID 1331218291-16119-11-git-send-email-thierry.reding@avionic-design.de
State Superseded, archived
Headers show

Commit Message

Thierry Reding March 8, 2012, 2:51 p.m. UTC
This commit adds support for message signaled interrupts to the Tegra
PCIe controller.

Signed-off-by: Thierry Reding <thierry.reding@avionic-design.de>
---
This code is taken from the NVIDIA Vibrante kernel and therefore has no
appropriate Signed-off-by from the original author. Maybe someone at
NVIDIA can find out who wrote this code and maybe provide a proper
Signed-off-by that I can add?

 arch/arm/mach-tegra/Kconfig             |    1 +
 arch/arm/mach-tegra/devices.c           |    7 +
 arch/arm/mach-tegra/include/mach/irqs.h |    5 +-
 arch/arm/mach-tegra/pcie.c              |  239 +++++++++++++++++++++++++++++++
 4 files changed, 251 insertions(+), 1 deletion(-)

Comments

Stephen Warren March 8, 2012, 9:14 p.m. UTC | #1
On 03/08/2012 07:51 AM, Thierry Reding wrote:
> This commit adds support for message signaled interrupts to the Tegra
> PCIe controller.
> 
> Signed-off-by: Thierry Reding <thierry.reding@avionic-design.de>
> ---
> This code is taken from the NVIDIA Vibrante kernel and therefore has no
> appropriate Signed-off-by from the original author. Maybe someone at
> NVIDIA can find out who wrote this code and maybe provide a proper
> Signed-off-by that I can add?

I think if you look in:
git://nv-tegra.nvidia.com/linux-2.6.git android-tegra-2.6.36

the following commits are what you're after:

de7fd8768b32da66eaf4eaf58473c65f7a76808d
arm: tegra: pcie: enabling MSI support for pcie

ac1f8310811c64a084511d2afc27f66334b31a81
ARM: tegra: pcie: fix return value from MSI irq routine

Although the patch below only partially resembles those patches, I guess
because you've rewritten the code a lot to conform to the current kernel
APIs, clean stuff up, etc. Perhaps just saying "based on code by Krishna
Kishore <kthota@nvidia.com>" is enough...

> diff --git a/arch/arm/mach-tegra/pcie.c b/arch/arm/mach-tegra/pcie.c

> +static int tegra_pcie_enable_msi(struct platform_device *pdev)
> +{
> +	struct tegra_pcie_info *pcie = platform_get_drvdata(pdev);
> +	volatile void *pages;
> +	unsigned long base;
> +	unsigned int msi;
> +	int msi_base;
> +	int err;
> +	u32 reg;
> +
> +	mutex_init(&pcie->msi_lock);
> +
> +	msi_base = irq_alloc_descs(-1, 0, INT_PCI_MSI_NR, 0);
> +	if (msi_base < 0) {
> +		dev_err(&pdev->dev, "failed to allocate IRQs\n");
> +		return msi_base;
> +	}
> +
> +	pcie->msi_domain = irq_domain_add_legacy(pcie->dev->of_node,
> +						 INT_PCI_MSI_NR, msi_base,
> +						 0, &irq_domain_simple_ops,
> +						 NULL);
> +	if (!pcie->msi_domain) {
> +		dev_err(&pdev->dev, "failed to create IRQ domain\n");

Free the IRQ descriptors in the error paths?

> +		return -ENOMEM;
> +	}
> +
> +	pcie->msi_chip.name = "PCIe-MSI";
> +	pcie->msi_chip.irq_enable = unmask_msi_irq;
> +	pcie->msi_chip.irq_disable = mask_msi_irq;
> +	pcie->msi_chip.irq_mask = mask_msi_irq;
> +	pcie->msi_chip.irq_unmask = unmask_msi_irq;
> +
> +	for (msi = 0; msi < INT_PCI_MSI_NR; msi++) {
> +		unsigned int irq = irq_find_mapping(pcie->msi_domain, msi);
> +
> +		irq_set_chip_data(irq, pcie);
> +		irq_set_chip_and_handler(irq, &pcie->msi_chip,
> +					 handle_simple_irq);
> +		set_irq_flags(irq, IRQF_VALID);
> +	}
> +
> +	err = platform_get_irq(pdev, 1);
> +	if (err < 0) {
> +		dev_err(&pdev->dev, "failed to get IRQ: %d\n", err);

Same here, and undo setting IRQF_VALID?

> +		return err;
> +	}
...

> +static int tegra_pcie_disable_msi(struct platform_device *pdev)
> +{
> +	return 0;
> +}

This is empty in both the ifdef(CONFIG_PCI_MSI) case and otherwise. It
should probably clean everything up here right?
--
To unsubscribe from this list: send the line "unsubscribe linux-tegra" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Thierry Reding March 9, 2012, 6:50 a.m. UTC | #2
* Stephen Warren wrote:
> On 03/08/2012 07:51 AM, Thierry Reding wrote:
> > This commit adds support for message signaled interrupts to the Tegra
> > PCIe controller.
> > 
> > Signed-off-by: Thierry Reding <thierry.reding@avionic-design.de>
> > ---
> > This code is taken from the NVIDIA Vibrante kernel and therefore has no
> > appropriate Signed-off-by from the original author. Maybe someone at
> > NVIDIA can find out who wrote this code and maybe provide a proper
> > Signed-off-by that I can add?
> 
> I think if you look in:
> git://nv-tegra.nvidia.com/linux-2.6.git android-tegra-2.6.36
> 
> the following commits are what you're after:
> 
> de7fd8768b32da66eaf4eaf58473c65f7a76808d
> arm: tegra: pcie: enabling MSI support for pcie
> 
> ac1f8310811c64a084511d2afc27f66334b31a81
> ARM: tegra: pcie: fix return value from MSI irq routine
> 
> Although the patch below only partially resembles those patches, I guess
> because you've rewritten the code a lot to conform to the current kernel
> APIs, clean stuff up, etc. Perhaps just saying "based on code by Krishna
> Kishore <kthota@nvidia.com>" is enough...

Yes, it is indeed a major rewrite because the original code had some
peculiarities and FIXME that I thought wouldn't make it through the review
anyway so I fixed them up.

I'll add some comment about the original authorship. There is no official
Signed-off-by in the original commit. Do I still need one or is it enough to
mention the original authors in the commit message and add keep my own
Signed-off-by?

Thierry

> 
> > diff --git a/arch/arm/mach-tegra/pcie.c b/arch/arm/mach-tegra/pcie.c
> 
> > +static int tegra_pcie_enable_msi(struct platform_device *pdev)
> > +{
> > +	struct tegra_pcie_info *pcie = platform_get_drvdata(pdev);
> > +	volatile void *pages;
> > +	unsigned long base;
> > +	unsigned int msi;
> > +	int msi_base;
> > +	int err;
> > +	u32 reg;
> > +
> > +	mutex_init(&pcie->msi_lock);
> > +
> > +	msi_base = irq_alloc_descs(-1, 0, INT_PCI_MSI_NR, 0);
> > +	if (msi_base < 0) {
> > +		dev_err(&pdev->dev, "failed to allocate IRQs\n");
> > +		return msi_base;
> > +	}
> > +
> > +	pcie->msi_domain = irq_domain_add_legacy(pcie->dev->of_node,
> > +						 INT_PCI_MSI_NR, msi_base,
> > +						 0, &irq_domain_simple_ops,
> > +						 NULL);
> > +	if (!pcie->msi_domain) {
> > +		dev_err(&pdev->dev, "failed to create IRQ domain\n");
> 
> Free the IRQ descriptors in the error paths?

Yes, that would make sense.

> > +		return -ENOMEM;
> > +	}
> > +
> > +	pcie->msi_chip.name = "PCIe-MSI";
> > +	pcie->msi_chip.irq_enable = unmask_msi_irq;
> > +	pcie->msi_chip.irq_disable = mask_msi_irq;
> > +	pcie->msi_chip.irq_mask = mask_msi_irq;
> > +	pcie->msi_chip.irq_unmask = unmask_msi_irq;
> > +
> > +	for (msi = 0; msi < INT_PCI_MSI_NR; msi++) {
> > +		unsigned int irq = irq_find_mapping(pcie->msi_domain, msi);
> > +
> > +		irq_set_chip_data(irq, pcie);
> > +		irq_set_chip_and_handler(irq, &pcie->msi_chip,
> > +					 handle_simple_irq);
> > +		set_irq_flags(irq, IRQF_VALID);
> > +	}
> > +
> > +	err = platform_get_irq(pdev, 1);
> > +	if (err < 0) {
> > +		dev_err(&pdev->dev, "failed to get IRQ: %d\n", err);
> 
> Same here, and undo setting IRQF_VALID?

Right. I assume it would be best to also free the struct irq_domain and set
the chip data and handler back to NULL? AFAICT there is no canonical way to
teardown an irq_domain.

> > +		return err;
> > +	}
> ...
> 
> > +static int tegra_pcie_disable_msi(struct platform_device *pdev)
> > +{
> > +	return 0;
> > +}
> 
> This is empty in both the ifdef(CONFIG_PCI_MSI) case and otherwise. It
> should probably clean everything up here right?

Yes, initially this contained a call to free_irq(), which I removed when I
switched to using a chained handler. I can probably put all of the cleanup
code from your comments above in here and perhaps even call that in the error
paths of tegra_pcie_enable_msi().

Thierry
Stephen Warren March 9, 2012, 4:45 p.m. UTC | #3
On 03/08/2012 11:50 PM, Thierry Reding wrote:
> * Stephen Warren wrote:
>> On 03/08/2012 07:51 AM, Thierry Reding wrote:
>>> This commit adds support for message signaled interrupts to the Tegra
>>> PCIe controller.
>>>
>>> Signed-off-by: Thierry Reding <thierry.reding@avionic-design.de>
>>> ---
>>> This code is taken from the NVIDIA Vibrante kernel and therefore has no
>>> appropriate Signed-off-by from the original author. Maybe someone at
>>> NVIDIA can find out who wrote this code and maybe provide a proper
>>> Signed-off-by that I can add?
>>
>> I think if you look in:
>> git://nv-tegra.nvidia.com/linux-2.6.git android-tegra-2.6.36
>>
>> the following commits are what you're after:
>>
>> de7fd8768b32da66eaf4eaf58473c65f7a76808d
>> arm: tegra: pcie: enabling MSI support for pcie
>>
>> ac1f8310811c64a084511d2afc27f66334b31a81
>> ARM: tegra: pcie: fix return value from MSI irq routine
>>
>> Although the patch below only partially resembles those patches, I guess
>> because you've rewritten the code a lot to conform to the current kernel
>> APIs, clean stuff up, etc. Perhaps just saying "based on code by Krishna
>> Kishore <kthota@nvidia.com>" is enough...
> 
> Yes, it is indeed a major rewrite because the original code had some
> peculiarities and FIXME that I thought wouldn't make it through the review
> anyway so I fixed them up.
> 
> I'll add some comment about the original authorship. There is no official
> Signed-off-by in the original commit. Do I still need one or is it enough to
> mention the original authors in the commit message and add keep my own
> Signed-off-by?

Yes, I think just mentioning the original code author in free-form text,
and including only your S-o-b sounds good to me.
--
To unsubscribe from this list: send the line "unsubscribe linux-tegra" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Thierry Reding March 12, 2012, 8 a.m. UTC | #4
* Stephen Warren wrote:
> On 03/08/2012 07:51 AM, Thierry Reding wrote:
> > diff --git a/arch/arm/mach-tegra/pcie.c b/arch/arm/mach-tegra/pcie.c
> 
> > +static int tegra_pcie_enable_msi(struct platform_device *pdev)
> > +{
> > +	struct tegra_pcie_info *pcie = platform_get_drvdata(pdev);
> > +	volatile void *pages;
> > +	unsigned long base;
> > +	unsigned int msi;
> > +	int msi_base;
> > +	int err;
> > +	u32 reg;
> > +
> > +	mutex_init(&pcie->msi_lock);
> > +
> > +	msi_base = irq_alloc_descs(-1, 0, INT_PCI_MSI_NR, 0);
> > +	if (msi_base < 0) {
> > +		dev_err(&pdev->dev, "failed to allocate IRQs\n");
> > +		return msi_base;
> > +	}
> > +
> > +	pcie->msi_domain = irq_domain_add_legacy(pcie->dev->of_node,
> > +						 INT_PCI_MSI_NR, msi_base,
> > +						 0, &irq_domain_simple_ops,
> > +						 NULL);
> > +	if (!pcie->msi_domain) {
> > +		dev_err(&pdev->dev, "failed to create IRQ domain\n");
> 
> Free the IRQ descriptors in the error paths?
> 
> > +		return -ENOMEM;
> > +	}
> > +
> > +	pcie->msi_chip.name = "PCIe-MSI";
> > +	pcie->msi_chip.irq_enable = unmask_msi_irq;
> > +	pcie->msi_chip.irq_disable = mask_msi_irq;
> > +	pcie->msi_chip.irq_mask = mask_msi_irq;
> > +	pcie->msi_chip.irq_unmask = unmask_msi_irq;
> > +
> > +	for (msi = 0; msi < INT_PCI_MSI_NR; msi++) {
> > +		unsigned int irq = irq_find_mapping(pcie->msi_domain, msi);
> > +
> > +		irq_set_chip_data(irq, pcie);
> > +		irq_set_chip_and_handler(irq, &pcie->msi_chip,
> > +					 handle_simple_irq);
> > +		set_irq_flags(irq, IRQF_VALID);
> > +	}
> > +
> > +	err = platform_get_irq(pdev, 1);
> > +	if (err < 0) {
> > +		dev_err(&pdev->dev, "failed to get IRQ: %d\n", err);
> 
> Same here, and undo setting IRQF_VALID?

Does it make sense to explicitly unset the IRQF_VALID flag when the IRQ
descriptors are free'd afterwards anyway? I also think it would be necessary
to free the struct irq_domain, but I wasn't able to find any function that
does this other than just calling kfree(), which obviously wouldn't be
enough. Maybe Grant can shed some light onto this. I'm also Cc'ing Thomas
Gleixner as maintainer of the IRQ subsystem, he probably knows best how
dynamically allocated interrupts should be cleaned up.

Thierry
Stephen Warren March 12, 2012, 4:57 p.m. UTC | #5
On 03/12/2012 02:00 AM, Thierry Reding wrote:
> * Stephen Warren wrote:
>> On 03/08/2012 07:51 AM, Thierry Reding wrote:
>>> diff --git a/arch/arm/mach-tegra/pcie.c b/arch/arm/mach-tegra/pcie.c
...
>> Free the IRQ descriptors in the error paths?
...
>>> +	for (msi = 0; msi < INT_PCI_MSI_NR; msi++) {
>>> +		unsigned int irq = irq_find_mapping(pcie->msi_domain, msi);
>>> +
>>> +		irq_set_chip_data(irq, pcie);
>>> +		irq_set_chip_and_handler(irq, &pcie->msi_chip,
>>> +					 handle_simple_irq);
>>> +		set_irq_flags(irq, IRQF_VALID);
>>> +	}
>>> +
>>> +	err = platform_get_irq(pdev, 1);
>>> +	if (err < 0) {
>>> +		dev_err(&pdev->dev, "failed to get IRQ: %d\n", err);
>>
>> Same here, and undo setting IRQF_VALID?
> 
> Does it make sense to explicitly unset the IRQF_VALID flag when the IRQ
> descriptors are free'd afterwards anyway?

Good point. Probably not per my gut instinct.

...
> I'm also Cc'ing Thomas
> Gleixner as maintainer of the IRQ subsystem, he probably knows best how
> dynamically allocated interrupts should be cleaned up.

But yes, best to check with someone more familiar with interrupts.
--
To unsubscribe from this list: send the line "unsubscribe linux-tegra" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/arch/arm/mach-tegra/Kconfig b/arch/arm/mach-tegra/Kconfig
index 1651119..7c596e6 100644
--- a/arch/arm/mach-tegra/Kconfig
+++ b/arch/arm/mach-tegra/Kconfig
@@ -48,6 +48,7 @@  config ARCH_TEGRA_3x_SOC
 config TEGRA_PCI
 	bool "PCI Express support"
 	depends on ARCH_TEGRA_2x_SOC
+	select ARCH_SUPPORTS_MSI
 	select PCI
 
 comment "Tegra board type"
diff --git a/arch/arm/mach-tegra/devices.c b/arch/arm/mach-tegra/devices.c
index 09e24e1..195f165 100644
--- a/arch/arm/mach-tegra/devices.c
+++ b/arch/arm/mach-tegra/devices.c
@@ -721,6 +721,13 @@  static struct resource tegra_pcie_resources[] = {
 		.end = INT_PCIE_INTR,
 		.flags = IORESOURCE_IRQ,
 	},
+#ifdef CONFIG_PCI_MSI
+	[3] = {
+		.start = INT_PCIE_MSI,
+		.end = INT_PCIE_MSI,
+		.flags = IORESOURCE_IRQ,
+	},
+#endif
 };
 
 struct platform_device tegra_pcie_device = {
diff --git a/arch/arm/mach-tegra/include/mach/irqs.h b/arch/arm/mach-tegra/include/mach/irqs.h
index aad1a2c..02e84bc 100644
--- a/arch/arm/mach-tegra/include/mach/irqs.h
+++ b/arch/arm/mach-tegra/include/mach/irqs.h
@@ -172,7 +172,10 @@ 
 /* Tegra30 has 8 banks of 32 GPIOs */
 #define INT_GPIO_NR			(32 * 8)
 
-#define TEGRA_NR_IRQS			(INT_GPIO_BASE + INT_GPIO_NR)
+#define INT_PCI_MSI_BASE		(INT_GPIO_BASE + INT_GPIO_NR)
+#define INT_PCI_MSI_NR			(32 * 8)
+
+#define TEGRA_NR_IRQS			(INT_PCI_MSI_BASE + INT_PCI_MSI_NR)
 
 #define INT_BOARD_BASE			TEGRA_NR_IRQS
 #define NR_BOARD_IRQS			32
diff --git a/arch/arm/mach-tegra/pcie.c b/arch/arm/mach-tegra/pcie.c
index 8b20bc5..85db9fb 100644
--- a/arch/arm/mach-tegra/pcie.c
+++ b/arch/arm/mach-tegra/pcie.c
@@ -31,11 +31,14 @@ 
 #include <linux/platform_device.h>
 #include <linux/interrupt.h>
 #include <linux/irq.h>
+#include <linux/irqdomain.h>
 #include <linux/clk.h>
 #include <linux/delay.h>
 #include <linux/export.h>
+#include <linux/msi.h>
 
 #include <asm/sizes.h>
+#include <asm/mach/irq.h>
 #include <asm/mach/pci.h>
 
 #include <mach/iomap.h>
@@ -81,6 +84,24 @@ 
 #define AFI_MSI_FPCI_BAR_ST	0x64
 #define AFI_MSI_AXI_BAR_ST	0x68
 
+#define AFI_MSI_VEC0		0x6c
+#define AFI_MSI_VEC1		0x70
+#define AFI_MSI_VEC2		0x74
+#define AFI_MSI_VEC3		0x78
+#define AFI_MSI_VEC4		0x7c
+#define AFI_MSI_VEC5		0x80
+#define AFI_MSI_VEC6		0x84
+#define AFI_MSI_VEC7		0x88
+
+#define AFI_MSI_EN_VEC0		0x8c
+#define AFI_MSI_EN_VEC1		0x90
+#define AFI_MSI_EN_VEC2		0x94
+#define AFI_MSI_EN_VEC3		0x98
+#define AFI_MSI_EN_VEC4		0x9c
+#define AFI_MSI_EN_VEC5		0xa0
+#define AFI_MSI_EN_VEC6		0xa4
+#define AFI_MSI_EN_VEC7		0xa8
+
 #define AFI_CONFIGURATION		0xac
 #define  AFI_CONFIGURATION_EN_FPCI	(1 << 0)
 
@@ -211,6 +232,14 @@  struct tegra_pcie_info {
 	struct clk		*afi_clk;
 	struct clk		*pcie_xclk;
 	struct clk		*pll_e;
+
+#ifdef CONFIG_PCI_MSI
+	int msi_irq;
+	struct irq_chip msi_chip;
+	DECLARE_BITMAP(msi_in_use, INT_PCI_MSI_NR);
+	struct irq_domain *msi_domain;
+	struct mutex msi_lock;
+#endif
 };
 
 static inline struct tegra_pcie_info *sys_to_pcie(struct pci_sys_data *sys)
@@ -939,6 +968,208 @@  static void __devinit tegra_pcie_add_port(struct tegra_pcie_info *pcie,
 	memset(pp->res, 0, sizeof(pp->res));
 }
 
+#ifdef CONFIG_PCI_MSI
+static int tegra_pcie_msi_alloc(struct tegra_pcie_info *pcie)
+{
+	int msi;
+
+	mutex_lock(&pcie->msi_lock);
+
+	msi = find_first_zero_bit(pcie->msi_in_use, INT_PCI_MSI_NR);
+	if (msi < INT_PCI_MSI_NR)
+		set_bit(msi, pcie->msi_in_use);
+	else
+		msi = -ENOSPC;
+
+	mutex_unlock(&pcie->msi_lock);
+
+	return msi;
+}
+
+static void tegra_pcie_msi_free(struct tegra_pcie_info *pcie, unsigned long irq)
+{
+	mutex_lock(&pcie->msi_lock);
+
+	if (!test_bit(irq, pcie->msi_in_use))
+		dev_err(pcie->dev, "trying to free unused MSI#%lu\n", irq);
+	else
+		clear_bit(irq, pcie->msi_in_use);
+
+	mutex_unlock(&pcie->msi_lock);
+}
+
+static void tegra_pcie_msi_isr(unsigned int irq, struct irq_desc *desc)
+{
+	struct tegra_pcie_info *pcie = irq_get_handler_data(irq);
+	struct irq_chip *chip = irq_desc_get_chip(desc);
+	unsigned int i;
+
+	chained_irq_enter(chip, desc);
+
+	for (i = 0; i < 8; i++) {
+		unsigned long reg = afi_readl(pcie, AFI_MSI_VEC0 + i * 4);
+
+		while (reg) {
+			unsigned int offset = find_first_bit(&reg, 32);
+			unsigned int index = i * 32 + offset;
+			unsigned int irq;
+
+			irq = irq_find_mapping(pcie->msi_domain, index);
+			if (irq) {
+				if (test_bit(index, pcie->msi_in_use))
+					generic_handle_irq(irq);
+				else
+					dev_info(pcie->dev, "unhandled MSI\n");
+			} else {
+				/*
+				 * that's weird who triggered this?
+				 * just clear it
+				 */
+				dev_info(pcie->dev, "unexpected MSI\n");
+			}
+
+			/* clear the interrupt */
+			afi_writel(pcie, 1 << offset, AFI_MSI_VEC0 + i * 4);
+			/* see if there's any more pending in this vector */
+			reg = afi_readl(pcie, AFI_MSI_VEC0 + i * 4);
+		}
+	}
+
+	chained_irq_exit(chip, desc);
+}
+
+/* called by arch_setup_msi_irqs in drivers/pci/msi.c */
+int arch_setup_msi_irq(struct pci_dev *pdev, struct msi_desc *desc)
+{
+	struct tegra_pcie_info *pcie = sys_to_pcie(pdev->bus->sysdata);
+	struct msi_msg msg;
+	unsigned int irq;
+	int hwirq;
+
+	hwirq = tegra_pcie_msi_alloc(pcie);
+	if (hwirq < 0)
+		return hwirq;
+
+	irq = irq_find_mapping(pcie->msi_domain, hwirq);
+	if (!irq)
+		return -EINVAL;
+
+	irq_set_msi_desc(irq, desc);
+
+	msg.address_lo = afi_readl(pcie, AFI_MSI_AXI_BAR_ST);
+	/* 32 bit address only */
+	msg.address_hi = 0;
+	msg.data = hwirq;
+
+	write_msi_msg(irq, &msg);
+
+	return 0;
+}
+
+void arch_teardown_msi_irq(unsigned int irq)
+{
+	struct tegra_pcie_info *pcie = irq_get_chip_data(irq);
+	struct irq_data *d = irq_get_irq_data(irq);
+
+	tegra_pcie_msi_free(pcie, d->hwirq);
+}
+
+static int tegra_pcie_enable_msi(struct platform_device *pdev)
+{
+	struct tegra_pcie_info *pcie = platform_get_drvdata(pdev);
+	volatile void *pages;
+	unsigned long base;
+	unsigned int msi;
+	int msi_base;
+	int err;
+	u32 reg;
+
+	mutex_init(&pcie->msi_lock);
+
+	msi_base = irq_alloc_descs(-1, 0, INT_PCI_MSI_NR, 0);
+	if (msi_base < 0) {
+		dev_err(&pdev->dev, "failed to allocate IRQs\n");
+		return msi_base;
+	}
+
+	pcie->msi_domain = irq_domain_add_legacy(pcie->dev->of_node,
+						 INT_PCI_MSI_NR, msi_base,
+						 0, &irq_domain_simple_ops,
+						 NULL);
+	if (!pcie->msi_domain) {
+		dev_err(&pdev->dev, "failed to create IRQ domain\n");
+		return -ENOMEM;
+	}
+
+	pcie->msi_chip.name = "PCIe-MSI";
+	pcie->msi_chip.irq_enable = unmask_msi_irq;
+	pcie->msi_chip.irq_disable = mask_msi_irq;
+	pcie->msi_chip.irq_mask = mask_msi_irq;
+	pcie->msi_chip.irq_unmask = unmask_msi_irq;
+
+	for (msi = 0; msi < INT_PCI_MSI_NR; msi++) {
+		unsigned int irq = irq_find_mapping(pcie->msi_domain, msi);
+
+		irq_set_chip_data(irq, pcie);
+		irq_set_chip_and_handler(irq, &pcie->msi_chip,
+					 handle_simple_irq);
+		set_irq_flags(irq, IRQF_VALID);
+	}
+
+	err = platform_get_irq(pdev, 1);
+	if (err < 0) {
+		dev_err(&pdev->dev, "failed to get IRQ: %d\n", err);
+		return err;
+	}
+
+	pcie->msi_irq = err;
+
+	irq_set_chained_handler(pcie->msi_irq, tegra_pcie_msi_isr);
+	irq_set_handler_data(pcie->msi_irq, pcie);
+
+	/* setup AFI/FPCI range */
+	pages = (volatile void *)__get_free_pages(GFP_KERNEL, 3);
+	base = virt_to_phys(pages);
+
+	afi_writel(pcie, base, AFI_MSI_FPCI_BAR_ST);
+	afi_writel(pcie, base, AFI_MSI_AXI_BAR_ST);
+	/* this register is in 4K increments */
+	afi_writel(pcie, 1, AFI_MSI_BAR_SZ);
+
+	/* enable all MSI vectors */
+	afi_writel(pcie, 0xffffffff, AFI_MSI_EN_VEC0);
+	afi_writel(pcie, 0xffffffff, AFI_MSI_EN_VEC1);
+	afi_writel(pcie, 0xffffffff, AFI_MSI_EN_VEC2);
+	afi_writel(pcie, 0xffffffff, AFI_MSI_EN_VEC3);
+	afi_writel(pcie, 0xffffffff, AFI_MSI_EN_VEC4);
+	afi_writel(pcie, 0xffffffff, AFI_MSI_EN_VEC5);
+	afi_writel(pcie, 0xffffffff, AFI_MSI_EN_VEC6);
+	afi_writel(pcie, 0xffffffff, AFI_MSI_EN_VEC7);
+
+	/* and unmask the MSI interrupt */
+	reg = afi_readl(pcie, AFI_INTR_MASK);
+	reg |= AFI_INTR_MASK_MSI_MASK;
+	afi_writel(pcie, reg, AFI_INTR_MASK);
+
+	return 0;
+}
+
+static int tegra_pcie_disable_msi(struct platform_device *pdev)
+{
+	return 0;
+}
+#else
+static int tegra_pcie_enable_msi(struct platform_device *pdev)
+{
+	return 0;
+}
+
+static int tegra_pcie_disable_msi(struct platform_device *pdev)
+{
+	return 0;
+}
+#endif
+
 static int __devinit tegra_pcie_probe(struct platform_device *pdev)
 {
 	struct tegra_pcie_pdata *pdata = pdev->dev.platform_data;
@@ -978,6 +1209,10 @@  static int __devinit tegra_pcie_probe(struct platform_device *pdev)
 	/* setup the AFI address translations */
 	tegra_pcie_setup_translations(pcie);
 
+	err = tegra_pcie_enable_msi(pdev);
+	if (err < 0)
+		dev_err(&pdev->dev, "failed to enable MSI support: %d\n", err);
+
 	if (pdata->enable_ports[0])
 		tegra_pcie_add_port(pcie, 0, RP0_OFFSET, AFI_PEX0_CTRL);
 
@@ -1000,6 +1235,10 @@  static int __devexit tegra_pcie_remove(struct platform_device *pdev)
 	struct tegra_pcie_pdata *pdata = pdev->dev.platform_data;
 	int err;
 
+	err = tegra_pcie_disable_msi(pdev);
+	if (err < 0)
+		return err;
+
 	err = tegra_pcie_put_resources(pdev);
 	if (err < 0)
 		return err;