diff mbox

[v8,4/6] pci: altera: Add Altera PCIe MSI driver

Message ID 1444297394-3122-5-git-send-email-lftan@altera.com (mailing list archive)
State New, archived
Delegated to: Bjorn Helgaas
Headers show

Commit Message

Ley Foon Tan Oct. 8, 2015, 9:43 a.m. UTC
This patch adds Altera PCIe MSI driver. This soft IP supports configurable
number of vectors, which is a dts parameter.

Signed-off-by: Ley Foon Tan <lftan@altera.com>
Reviewed-by: Marc Zyngier <marc.zyngier@arm.com>
---
 drivers/pci/host/Kconfig           |   8 +
 drivers/pci/host/Makefile          |   1 +
 drivers/pci/host/pcie-altera-msi.c | 310 +++++++++++++++++++++++++++++++++++++
 3 files changed, 319 insertions(+)
 create mode 100644 drivers/pci/host/pcie-altera-msi.c

Comments

kernel test robot Oct. 8, 2015, 2:38 p.m. UTC | #1
Hi Ley,

[auto build test ERROR on v4.3-rc4 -- if it's inappropriate base, please ignore]

config: sparc-allmodconfig (attached as .config)
reproduce:
        wget https://git.kernel.org/cgit/linux/kernel/git/wfg/lkp-tests.git/plain/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # save the attached .config to linux build tree
        make.cross ARCH=sparc 

All error/warnings (new ones prefixed by >>):

   In file included from include/linux/of_pci.h:5:0,
                    from drivers/pci//host/pcie-altera.c:22:
>> include/linux/msi.h:199:10: error: unknown type name 'msi_alloc_info_t'
             msi_alloc_info_t *arg);
             ^
   include/linux/msi.h:203:9: error: unknown type name 'msi_alloc_info_t'
            msi_alloc_info_t *arg);
            ^
   include/linux/msi.h:212:12: error: unknown type name 'msi_alloc_info_t'
               msi_alloc_info_t *arg);
               ^
   include/linux/msi.h:213:22: error: unknown type name 'msi_alloc_info_t'
     void  (*msi_finish)(msi_alloc_info_t *arg, int retval);
                         ^
   include/linux/msi.h:214:20: error: unknown type name 'msi_alloc_info_t'
     void  (*set_desc)(msi_alloc_info_t *arg,
                       ^
   drivers/pci//host/pcie-altera.c: In function 'tlp_cfg_dword_read':
   drivers/pci//host/pcie-altera.c:243:12: warning: large integer implicitly truncated to unsigned type [-Woverflow]
      *value = ~0UL; /* return 0xFFFFFFFF if error */
               ^
   drivers/pci//host/pcie-altera.c: In function 'altera_pcie_cfg_read':
   drivers/pci//host/pcie-altera.c:291:12: warning: large integer implicitly truncated to unsigned type [-Woverflow]
      *value = ~0UL;
               ^
   drivers/pci//host/pcie-altera.c: In function 'altera_pcie_parse_request_of_pci_ranges':
   drivers/pci//host/pcie-altera.c:410:2: error: implicit declaration of function 'of_pci_get_host_bridge_resources' [-Werror=implicit-function-declaration]
     err = of_pci_get_host_bridge_resources(np, 0, 0xff, &pcie->resources,
     ^
   cc1: some warnings being treated as errors
--
   In file included from drivers/pci//host/pcie-altera-msi.c:19:0:
>> include/linux/msi.h:199:10: error: unknown type name 'msi_alloc_info_t'
             msi_alloc_info_t *arg);
             ^
   include/linux/msi.h:203:9: error: unknown type name 'msi_alloc_info_t'
            msi_alloc_info_t *arg);
            ^
   include/linux/msi.h:212:12: error: unknown type name 'msi_alloc_info_t'
               msi_alloc_info_t *arg);
               ^
   include/linux/msi.h:213:22: error: unknown type name 'msi_alloc_info_t'
     void  (*msi_finish)(msi_alloc_info_t *arg, int retval);
                         ^
   include/linux/msi.h:214:20: error: unknown type name 'msi_alloc_info_t'
     void  (*set_desc)(msi_alloc_info_t *arg,
                       ^
--
   In file included from drivers/base/platform-msi.c:24:0:
>> include/linux/msi.h:199:10: error: unknown type name 'msi_alloc_info_t'
             msi_alloc_info_t *arg);
             ^
   include/linux/msi.h:203:9: error: unknown type name 'msi_alloc_info_t'
            msi_alloc_info_t *arg);
            ^
   include/linux/msi.h:212:12: error: unknown type name 'msi_alloc_info_t'
               msi_alloc_info_t *arg);
               ^
   include/linux/msi.h:213:22: error: unknown type name 'msi_alloc_info_t'
     void  (*msi_finish)(msi_alloc_info_t *arg, int retval);
                         ^
   include/linux/msi.h:214:20: error: unknown type name 'msi_alloc_info_t'
     void  (*set_desc)(msi_alloc_info_t *arg,
                       ^
   drivers/base/platform-msi.c: In function 'platform_msi_update_dom_ops':
>> drivers/base/platform-msi.c:80:9: error: 'struct msi_domain_ops' has no member named 'msi_init'
     if (ops->msi_init == NULL)
            ^
   drivers/base/platform-msi.c:81:6: error: 'struct msi_domain_ops' has no member named 'msi_init'
      ops->msi_init = platform_msi_init;
         ^
>> drivers/base/platform-msi.c:82:9: error: 'struct msi_domain_ops' has no member named 'set_desc'
     if (ops->set_desc == NULL)
            ^
   drivers/base/platform-msi.c:83:6: error: 'struct msi_domain_ops' has no member named 'set_desc'
      ops->set_desc = platform_msi_set_desc;
         ^
--
   In file included from drivers/pci/msi.c:17:0:
>> include/linux/msi.h:199:10: error: unknown type name 'msi_alloc_info_t'
             msi_alloc_info_t *arg);
             ^
   include/linux/msi.h:203:9: error: unknown type name 'msi_alloc_info_t'
            msi_alloc_info_t *arg);
            ^
   include/linux/msi.h:212:12: error: unknown type name 'msi_alloc_info_t'
               msi_alloc_info_t *arg);
               ^
   include/linux/msi.h:213:22: error: unknown type name 'msi_alloc_info_t'
     void  (*msi_finish)(msi_alloc_info_t *arg, int retval);
                         ^
   include/linux/msi.h:214:20: error: unknown type name 'msi_alloc_info_t'
     void  (*set_desc)(msi_alloc_info_t *arg,
                       ^
>> drivers/pci/msi.c:1218:2: error: unknown field 'set_desc' specified in initializer
     .set_desc = pci_msi_domain_set_desc,
     ^
   drivers/pci/msi.c: In function 'pci_msi_domain_update_dom_ops':
>> drivers/pci/msi.c:1230:10: error: 'struct msi_domain_ops' has no member named 'set_desc'
      if (ops->set_desc == NULL)
             ^
   drivers/pci/msi.c:1231:7: error: 'struct msi_domain_ops' has no member named 'set_desc'
       ops->set_desc = pci_msi_domain_set_desc;
          ^
--
   In file included from kernel/irq/msi.c:16:0:
>> include/linux/msi.h:199:10: error: unknown type name 'msi_alloc_info_t'
             msi_alloc_info_t *arg);
             ^
   include/linux/msi.h:203:9: error: unknown type name 'msi_alloc_info_t'
            msi_alloc_info_t *arg);
            ^
   include/linux/msi.h:212:12: error: unknown type name 'msi_alloc_info_t'
               msi_alloc_info_t *arg);
               ^
   include/linux/msi.h:213:22: error: unknown type name 'msi_alloc_info_t'
     void  (*msi_finish)(msi_alloc_info_t *arg, int retval);
                         ^
   include/linux/msi.h:214:20: error: unknown type name 'msi_alloc_info_t'
     void  (*set_desc)(msi_alloc_info_t *arg,
                       ^
   kernel/irq/msi.c: In function 'msi_domain_alloc':
>> kernel/irq/msi.c:106:29: error: 'struct msi_domain_ops' has no member named 'get_hwirq'
     irq_hw_number_t hwirq = ops->get_hwirq(info, arg);
                                ^
>> kernel/irq/msi.c:117:12: error: 'struct msi_domain_ops' has no member named 'msi_init'
      ret = ops->msi_init(domain, info, virq + i, hwirq + i, arg);
               ^
   kernel/irq/msi.c: At top level:
>> kernel/irq/msi.c:179:11: error: unknown type name 'msi_alloc_info_t'
              msi_alloc_info_t *arg)
              ^
>> kernel/irq/msi.c:199:2: error: unknown field 'get_hwirq' specified in initializer
     .get_hwirq = msi_domain_ops_get_hwirq,
     ^
>> kernel/irq/msi.c:200:2: error: unknown field 'msi_init' specified in initializer
     .msi_init = msi_domain_ops_init,
     ^
>> kernel/irq/msi.c:200:14: error: 'msi_domain_ops_init' undeclared here (not in a function)
     .msi_init = msi_domain_ops_init,
                 ^
>> kernel/irq/msi.c:202:2: error: unknown field 'msi_prepare' specified in initializer
     .msi_prepare = msi_domain_ops_prepare,
     ^
>> kernel/irq/msi.c:203:2: error: unknown field 'set_desc' specified in initializer
     .set_desc = msi_domain_ops_set_desc,
     ^
>> kernel/irq/msi.c:203:2: warning: excess elements in struct initializer
>> kernel/irq/msi.c:203:2: warning: (near initialization for 'msi_domain_ops_default')
   kernel/irq/msi.c: In function 'msi_domain_update_dom_ops':
   kernel/irq/msi.c:215:9: error: 'struct msi_domain_ops' has no member named 'get_hwirq'
     if (ops->get_hwirq == NULL)
            ^
   kernel/irq/msi.c:216:6: error: 'struct msi_domain_ops' has no member named 'get_hwirq'
      ops->get_hwirq = msi_domain_ops_default.get_hwirq;
         ^
   kernel/irq/msi.c:216:42: error: 'struct msi_domain_ops' has no member named 'get_hwirq'
      ops->get_hwirq = msi_domain_ops_default.get_hwirq;
                                             ^
   kernel/irq/msi.c:217:9: error: 'struct msi_domain_ops' has no member named 'msi_init'
     if (ops->msi_init == NULL)
            ^
   kernel/irq/msi.c:218:6: error: 'struct msi_domain_ops' has no member named 'msi_init'
      ops->msi_init = msi_domain_ops_default.msi_init;
         ^
   kernel/irq/msi.c:218:41: error: 'struct msi_domain_ops' has no member named 'msi_init'
      ops->msi_init = msi_domain_ops_default.msi_init;
                                            ^
>> kernel/irq/msi.c:221:9: error: 'struct msi_domain_ops' has no member named 'msi_prepare'
     if (ops->msi_prepare == NULL)
            ^
   kernel/irq/msi.c:222:6: error: 'struct msi_domain_ops' has no member named 'msi_prepare'
      ops->msi_prepare = msi_domain_ops_default.msi_prepare;
         ^
   kernel/irq/msi.c:222:44: error: 'struct msi_domain_ops' has no member named 'msi_prepare'
      ops->msi_prepare = msi_domain_ops_default.msi_prepare;
                                               ^
>> kernel/irq/msi.c:223:9: error: 'struct msi_domain_ops' has no member named 'set_desc'
     if (ops->set_desc == NULL)
            ^
   kernel/irq/msi.c:224:6: error: 'struct msi_domain_ops' has no member named 'set_desc'
      ops->set_desc = msi_domain_ops_default.set_desc;
         ^
   kernel/irq/msi.c:224:41: error: 'struct msi_domain_ops' has no member named 'set_desc'
      ops->set_desc = msi_domain_ops_default.set_desc;
                                            ^
   kernel/irq/msi.c: In function 'msi_domain_alloc_irqs':
   kernel/irq/msi.c:273:2: error: unknown type name 'msi_alloc_info_t'
     msi_alloc_info_t arg;
     ^
   kernel/irq/msi.c:279:12: error: 'struct msi_domain_ops' has no member named 'msi_prepare'
      ret = ops->msi_prepare(domain, dev, nvec, &arg);
               ^
   kernel/irq/msi.c:284:6: error: 'struct msi_domain_ops' has no member named 'set_desc'
      ops->set_desc(&arg, desc);
         ^
   kernel/irq/msi.c:286:19: error: 'struct msi_domain_ops' has no member named 'get_hwirq'
       virq = (int)ops->get_hwirq(info, &arg);
                      ^
>> kernel/irq/msi.c:296:11: error: 'struct msi_domain_ops' has no member named 'msi_finish'
       if (ops->msi_finish)
              ^
   kernel/irq/msi.c:297:8: error: 'struct msi_domain_ops' has no member named 'msi_finish'
        ops->msi_finish(&arg, ret);
           ^
   kernel/irq/msi.c:305:9: error: 'struct msi_domain_ops' has no member named 'msi_finish'
     if (ops->msi_finish)
            ^
   kernel/irq/msi.c:306:6: error: 'struct msi_domain_ops' has no member named 'msi_finish'
      ops->msi_finish(&arg, 0);
         ^
--
   In file included from include/linux/of_pci.h:5:0,
                    from drivers/pci/host/pcie-altera.c:22:
>> include/linux/msi.h:199:10: error: unknown type name 'msi_alloc_info_t'
             msi_alloc_info_t *arg);
             ^
   include/linux/msi.h:203:9: error: unknown type name 'msi_alloc_info_t'
            msi_alloc_info_t *arg);
            ^
   include/linux/msi.h:212:12: error: unknown type name 'msi_alloc_info_t'
               msi_alloc_info_t *arg);
               ^
   include/linux/msi.h:213:22: error: unknown type name 'msi_alloc_info_t'
     void  (*msi_finish)(msi_alloc_info_t *arg, int retval);
                         ^
   include/linux/msi.h:214:20: error: unknown type name 'msi_alloc_info_t'
     void  (*set_desc)(msi_alloc_info_t *arg,
                       ^
   drivers/pci/host/pcie-altera.c: In function 'tlp_cfg_dword_read':
   drivers/pci/host/pcie-altera.c:243:12: warning: large integer implicitly truncated to unsigned type [-Woverflow]
      *value = ~0UL; /* return 0xFFFFFFFF if error */
               ^
   drivers/pci/host/pcie-altera.c: In function 'altera_pcie_cfg_read':
   drivers/pci/host/pcie-altera.c:291:12: warning: large integer implicitly truncated to unsigned type [-Woverflow]
      *value = ~0UL;
               ^
   drivers/pci/host/pcie-altera.c: In function 'altera_pcie_parse_request_of_pci_ranges':
   drivers/pci/host/pcie-altera.c:410:2: error: implicit declaration of function 'of_pci_get_host_bridge_resources' [-Werror=implicit-function-declaration]
     err = of_pci_get_host_bridge_resources(np, 0, 0xff, &pcie->resources,
     ^
   cc1: some warnings being treated as errors

vim +/msi_alloc_info_t +199 include/linux/msi.h

d9109698 Jiang Liu 2014-11-15  193   * @msi_check, @msi_prepare, @msi_finish, @set_desc and @handle_error
d9109698 Jiang Liu 2014-11-15  194   * are callbacks used by msi_irq_domain_alloc_irqs() and related
d9109698 Jiang Liu 2014-11-15  195   * interfaces which are based on msi_desc.
f3cf8bb0 Jiang Liu 2014-11-12  196   */
f3cf8bb0 Jiang Liu 2014-11-12  197  struct msi_domain_ops {
aeeb5965 Jiang Liu 2014-11-15  198  	irq_hw_number_t	(*get_hwirq)(struct msi_domain_info *info,
aeeb5965 Jiang Liu 2014-11-15 @199  				     msi_alloc_info_t *arg);
f3cf8bb0 Jiang Liu 2014-11-12  200  	int		(*msi_init)(struct irq_domain *domain,
f3cf8bb0 Jiang Liu 2014-11-12  201  				    struct msi_domain_info *info,
f3cf8bb0 Jiang Liu 2014-11-12  202  				    unsigned int virq, irq_hw_number_t hwirq,

:::::: The code at line 199 was first introduced by commit
:::::: aeeb59657c35da64068336c20068da237f41ab76 genirq: Provide default callbacks for msi_domain_ops

:::::: TO: Jiang Liu <jiang.liu@linux.intel.com>
:::::: CC: Thomas Gleixner <tglx@linutronix.de>

---
0-DAY kernel test infrastructure                Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all                   Intel Corporation
diff mbox

Patch

diff --git a/drivers/pci/host/Kconfig b/drivers/pci/host/Kconfig
index 08f2543..0500bb3 100644
--- a/drivers/pci/host/Kconfig
+++ b/drivers/pci/host/Kconfig
@@ -152,4 +152,12 @@  config PCIE_ALTERA
 	  Say Y here if you want to enable PCIe controller support for Altera
 	  SoCFPGA family of SoCs.
 
+config PCIE_ALTERA_MSI
+	bool "Altera PCIe MSI feature"
+	depends on PCI_MSI
+	select PCI_MSI_IRQ_DOMAIN
+	help
+	  Say Y here if you want PCIe MSI support for the Altera SocFPGA SoC.
+	  This MSI driver supports Altera MSI to GIC controller IP.
+
 endmenu
diff --git a/drivers/pci/host/Makefile b/drivers/pci/host/Makefile
index 6954f76..6c4913d 100644
--- a/drivers/pci/host/Makefile
+++ b/drivers/pci/host/Makefile
@@ -18,3 +18,4 @@  obj-$(CONFIG_PCIE_IPROC) += pcie-iproc.o
 obj-$(CONFIG_PCIE_IPROC_PLATFORM) += pcie-iproc-platform.o
 obj-$(CONFIG_PCIE_IPROC_BCMA) += pcie-iproc-bcma.o
 obj-$(CONFIG_PCIE_ALTERA) += pcie-altera.o
+obj-$(CONFIG_PCIE_ALTERA_MSI) += pcie-altera-msi.o
diff --git a/drivers/pci/host/pcie-altera-msi.c b/drivers/pci/host/pcie-altera-msi.c
new file mode 100644
index 0000000..37a358e
--- /dev/null
+++ b/drivers/pci/host/pcie-altera-msi.c
@@ -0,0 +1,310 @@ 
+/*
+ * Copyright Altera Corporation (C) 2013-2015. All rights reserved
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#include <linux/interrupt.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/module.h>
+#include <linux/msi.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/of_pci.h>
+#include <linux/pci.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#define MSI_STATUS		0x0
+#define MSI_ERROR		0x4
+#define MSI_INTMASK		0x8
+
+#define MAX_MSI_VECTORS		32
+struct altera_msi {
+	DECLARE_BITMAP(used, MAX_MSI_VECTORS);
+	struct mutex		lock;	/* proctect used variable */
+	struct platform_device	*pdev;
+	struct irq_domain		*msi_domain;
+	struct irq_domain		*inner_domain;
+	void __iomem		*csr_base;
+	void __iomem		*vector_base;
+	phys_addr_t		vector_phy;
+	u32			num_of_vectors;
+	int			irq;
+};
+
+static inline void msi_writel(struct altera_msi *msi, u32 value, u32 reg)
+{
+	writel_relaxed(value, msi->csr_base + reg);
+}
+
+static inline u32 msi_readl(struct altera_msi *msi, u32 reg)
+{
+	return readl_relaxed(msi->csr_base + reg);
+}
+
+static void altera_msi_isr(struct irq_desc *desc)
+{
+	struct irq_chip *chip = irq_desc_get_chip(desc);
+	struct altera_msi *msi;
+	unsigned long status;
+	u32 num_of_vectors;
+	u32 bit;
+	u32 virq;
+
+	chained_irq_enter(chip, desc);
+	msi = irq_desc_get_handler_data(desc);
+	num_of_vectors = msi->num_of_vectors;
+
+	while ((status = msi_readl(msi, MSI_STATUS)) != 0) {
+		for_each_set_bit(bit, &status, msi->num_of_vectors) {
+			/* Dummy read from vector to clear the interrupt */
+			readl_relaxed(msi->vector_base + (bit * sizeof(u32)));
+
+			virq = irq_find_mapping(msi->inner_domain, bit);
+			if (virq)
+				generic_handle_irq(virq);
+			else
+				dev_err(&msi->pdev->dev, "unexpected MSI\n");
+		}
+	}
+
+	chained_irq_exit(chip, desc);
+}
+
+static struct irq_chip altera_msi_irq_chip = {
+	.name = "Altera PCIe MSI",
+	.irq_mask = pci_msi_mask_irq,
+	.irq_unmask = pci_msi_unmask_irq,
+};
+
+static struct msi_domain_info altera_msi_domain_info = {
+	.flags	= (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS |
+		     MSI_FLAG_PCI_MSIX),
+	.chip	= &altera_msi_irq_chip,
+};
+
+static void altera_compose_msi_msg(struct irq_data *data, struct msi_msg *msg)
+{
+	struct altera_msi *msi = irq_data_get_irq_chip_data(data);
+	phys_addr_t addr = msi->vector_phy + (data->hwirq * sizeof(u32));
+
+	msg->address_lo = lower_32_bits(addr);
+	msg->address_hi = upper_32_bits(addr);
+	msg->data = data->hwirq;
+
+	dev_dbg(&msi->pdev->dev, "msi#%d address_hi 0x%x address_lo 0x%x\n",
+		(int)data->hwirq, msg->address_hi, msg->address_lo);
+}
+
+static int altera_msi_set_affinity(struct irq_data *irq_data,
+				   const struct cpumask *mask, bool force)
+{
+	 return -EINVAL;
+}
+
+static struct irq_chip altera_msi_bottom_irq_chip = {
+	.name			= "Altera MSI",
+	.irq_compose_msi_msg	= altera_compose_msi_msg,
+	.irq_set_affinity	= altera_msi_set_affinity,
+};
+
+static int altera_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,
+				   unsigned int nr_irqs, void *args)
+{
+	struct altera_msi *msi = domain->host_data;
+	unsigned long bit;
+	u32 mask;
+
+	WARN_ON(nr_irqs != 1);
+	mutex_lock(&msi->lock);
+
+	bit = find_first_zero_bit(msi->used, msi->num_of_vectors);
+	if (bit >= msi->num_of_vectors)
+		return -ENOSPC;
+
+	set_bit(bit, msi->used);
+
+	mutex_unlock(&msi->lock);
+
+	irq_domain_set_info(domain, virq, bit, &altera_msi_bottom_irq_chip,
+			    domain->host_data, handle_simple_irq,
+			    NULL, NULL);
+
+	mask = msi_readl(msi, MSI_INTMASK);
+	mask |= 1 << bit;
+	msi_writel(msi, mask, MSI_INTMASK);
+
+	return 0;
+}
+
+static void altera_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 altera_msi *msi = irq_data_get_irq_chip_data(d);
+	u32 mask;
+
+	mutex_lock(&msi->lock);
+
+	if (!test_bit(d->hwirq, msi->used)) {
+		dev_err(&msi->pdev->dev, "trying to free unused MSI#%lu\n",
+			d->hwirq);
+	} else {
+		__clear_bit(d->hwirq, msi->used);
+		mask = msi_readl(msi, MSI_INTMASK);
+		mask &= ~(1 << d->hwirq);
+		msi_writel(msi, mask, MSI_INTMASK);
+	}
+
+	mutex_unlock(&msi->lock);
+}
+
+static const struct irq_domain_ops msi_domain_ops = {
+	.alloc	= altera_irq_domain_alloc,
+	.free	= altera_irq_domain_free,
+};
+
+static int altera_allocate_domains(struct altera_msi *msi)
+{
+	msi->inner_domain = irq_domain_add_linear(NULL, msi->num_of_vectors,
+					     &msi_domain_ops, msi);
+	if (!msi->inner_domain) {
+		dev_err(&msi->pdev->dev, "failed to create IRQ domain\n");
+		return -ENOMEM;
+	}
+
+	msi->msi_domain = pci_msi_create_irq_domain(msi->pdev->dev.of_node,
+				&altera_msi_domain_info, msi->inner_domain);
+	if (!msi->msi_domain) {
+		dev_err(&msi->pdev->dev, "failed to create MSI domain\n");
+		irq_domain_remove(msi->inner_domain);
+		return -ENOMEM;
+	}
+
+	return 0;
+}
+
+static void altera_free_domains(struct altera_msi *msi)
+{
+	irq_domain_remove(msi->msi_domain);
+	irq_domain_remove(msi->inner_domain);
+}
+
+static int altera_msi_remove(struct platform_device *pdev)
+{
+	struct altera_msi *msi = platform_get_drvdata(pdev);
+
+	msi_writel(msi, 0, MSI_INTMASK);
+	irq_set_chained_handler(msi->irq, NULL);
+	irq_set_handler_data(msi->irq, NULL);
+
+	altera_free_domains(msi);
+
+	platform_set_drvdata(pdev, NULL);
+	return 0;
+}
+
+static int altera_msi_probe(struct platform_device *pdev)
+{
+	struct altera_msi *msi;
+	struct device_node *np = pdev->dev.of_node;
+	struct resource *res;
+	int ret;
+
+	msi = devm_kzalloc(&pdev->dev, sizeof(struct altera_msi),
+			   GFP_KERNEL);
+	if (!msi)
+		return -ENOMEM;
+
+	mutex_init(&msi->lock);
+	msi->pdev = pdev;
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "csr");
+	if (!res) {
+		dev_err(&pdev->dev,
+			"no csr memory resource defined\n");
+		return -ENODEV;
+	}
+
+	msi->csr_base = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(msi->csr_base)) {
+		dev_err(&pdev->dev, "failed to map csr memory\n");
+		return PTR_ERR(msi->csr_base);
+	}
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+					   "vector_slave");
+	if (!res) {
+		dev_err(&pdev->dev,
+			"no vector_slave memory resource defined\n");
+		return -ENODEV;
+	}
+
+	msi->vector_base = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(msi->vector_base)) {
+		dev_err(&pdev->dev, "failed to map vector_slave memory\n");
+		return PTR_ERR(msi->vector_base);
+	}
+
+	msi->vector_phy = res->start;
+
+	if (of_property_read_u32(np, "num-vectors", &msi->num_of_vectors)) {
+		dev_err(&pdev->dev, "failed to parse the number of vectors\n");
+		return -EINVAL;
+	}
+
+	ret = altera_allocate_domains(msi);
+	if (ret)
+		return ret;
+
+	msi->irq = platform_get_irq(pdev, 0);
+	if (msi->irq <= 0) {
+		dev_err(&pdev->dev, "failed to map IRQ: %d\n", msi->irq);
+		ret = -ENODEV;
+		goto err;
+	}
+
+	irq_set_chained_handler_and_data(msi->irq, altera_msi_isr, msi);
+	platform_set_drvdata(pdev, msi);
+
+	return 0;
+
+err:
+	altera_msi_remove(pdev);
+	return ret;
+}
+
+static const struct of_device_id altera_msi_of_match[] = {
+	{ .compatible = "altr,msi-1.0", NULL },
+	{ },
+};
+
+static struct platform_driver altera_msi_driver = {
+	.driver = {
+		.name = "altera-msi",
+		.of_match_table = altera_msi_of_match,
+	},
+	.probe = altera_msi_probe,
+	.remove = altera_msi_remove,
+};
+
+static int __init altera_msi_init(void)
+{
+	return platform_driver_register(&altera_msi_driver);
+}
+
+subsys_initcall(altera_msi_init);
+
+MODULE_AUTHOR("Ley Foon Tan <lftan@altera.com>");
+MODULE_DESCRIPTION("Altera PCIe MSI support");
+MODULE_LICENSE("GPL v2");