From d2be826d622f7c03eeec82e5a6f391a6c52b9681 Mon Sep 17 00:00:00 2001
From: Tien Hock Loh <thloh@pg-thloh-lv.altera.com>
Date: Tue, 12 Aug 2014 14:57:37 +0800
Subject: [PATCH] Add PCIe RP and MSI drivers

---
 .../devicetree/bindings/pci/altera-pcie-msi.txt    |   29 +
 .../devicetree/bindings/pci/altera-pcie.txt        |   30 +
 arch/arm/configs/socfpga_defconfig                 |    4 +
 arch/arm/mach-socfpga/Kconfig                      |    2 +
 drivers/pci/Kconfig                                |    2 +
 drivers/pci/Makefile                               |    3 +
 drivers/pci/host/Kconfig                           |   16 +
 drivers/pci/host/Makefile                          |    2 +
 drivers/pci/host/pci-altera-msi.c                  |  340 ++++++++++++
 drivers/pci/host/pci-altera.c                      |  562 ++++++++++++++++++++
 drivers/pci/host/pci-altera.h                      |   56 ++
 drivers/pci/msi.c                                  |    2 +-
 12 files changed, 1047 insertions(+), 1 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/pci/altera-pcie-msi.txt
 create mode 100644 Documentation/devicetree/bindings/pci/altera-pcie.txt
 create mode 100644 drivers/pci/host/Kconfig
 create mode 100644 drivers/pci/host/Makefile
 create mode 100644 drivers/pci/host/pci-altera-msi.c
 create mode 100644 drivers/pci/host/pci-altera.c
 create mode 100644 drivers/pci/host/pci-altera.h

diff --git a/Documentation/devicetree/bindings/pci/altera-pcie-msi.txt b/Documentation/devicetree/bindings/pci/altera-pcie-msi.txt
new file mode 100644
index 0000000..b0e6cec
--- /dev/null
+++ b/Documentation/devicetree/bindings/pci/altera-pcie-msi.txt
@@ -0,0 +1,29 @@
+* Altera MSI-to-GIC controller
+
+Required properties:
+- compatible : should contain "altr,msi-1.0"
+- reg: specifies the physical base address of the controller and
+     the length of the memory mapped region.
+- reg-names: Must include the following entries:
+  "csr": CSR registers
+  "vector_slave": vectors region
+-interrupts: specifies the interrupt source of the parent interrupt
+     controller. The format of the interrupt specifier depends on the
+     parent interrupt controller.
+- num-vectors: Number of vectors, range 1 to 32.
+- msi-controller: set to <1>
+
+
+Example
+msi0: msi@0xFF200000 {
+	compatible = "altr,msi-1.0";
+	reg = < 0xFF200000 0x00000010
+		0xFF200010 0x00000080 >;
+	reg-names = "csr", "vector_slave";
+	interrupt-parent = < &hps_0_arm_gic_0 >;
+	interrupts = < 0 42 4 >;
+	msi-controller = < 1 >;
+	num-vectors = < 32 >;
+};
+
+
diff --git a/Documentation/devicetree/bindings/pci/altera-pcie.txt b/Documentation/devicetree/bindings/pci/altera-pcie.txt
new file mode 100644
index 0000000..7c8fb96
--- /dev/null
+++ b/Documentation/devicetree/bindings/pci/altera-pcie.txt
@@ -0,0 +1,30 @@
+* Altera PCIe controller
+
+Required properties:
+- compatible : should contain "altr,pcie-root-port-1.0"
+- reg: A list of physical base address and length for TXS and CRA.
+- reg-names: Must include the following entries:
+  "Txs": TXS region
+  "Cra": Cra registers
+-interrupts: specifies the interrupt source of the parent interrupt
+     controller. The format of the interrupt specifier depends on the
+     parent interrupt controller.
+- device_type: must be "pci"
+
+Optional properties:
+- altr,msi = phandle to the MSI component
+
+Example
+	pcie_0: pcie@0x0 {
+		compatible = "altr,pcie-root-port-1.0";
+		reg = < 0xc0000000 0x20000000
+			0xFF240000 0x00004000  >;
+		reg-names = “Txs”, “Cra”;
+		interrupt-parent = < &hps_0_arm_gic_0 >;
+		interrupts = < 0 43 4 >;
+		interrupt-controller;
+		#interrupt-cells = < 1 >;
+		altr,msi = <&msi_0>;
+		device_type = "pci";
+	};
+
diff --git a/arch/arm/configs/socfpga_defconfig b/arch/arm/configs/socfpga_defconfig
index dab0039..08e2946 100644
--- a/arch/arm/configs/socfpga_defconfig
+++ b/arch/arm/configs/socfpga_defconfig
@@ -160,3 +160,7 @@ CONFIG_FTRACE_MCOUNT_RECORD=y
 CONFIG_OLD_MCOUNT=y
 CONFIG_FPGA_BRIDGE=y
 CONFIG_ALTERA_SOCFPGA_BRIDGE=y
+CONFIG_PCI_MSI=y
+CONFIG_PCI_ALTERA=y
+CONFIG_PCI_ALTERA_MSI=y
+CONFIG_E1000E=y
diff --git a/arch/arm/mach-socfpga/Kconfig b/arch/arm/mach-socfpga/Kconfig
index 6c4d040..0892120 100644
--- a/arch/arm/mach-socfpga/Kconfig
+++ b/arch/arm/mach-socfpga/Kconfig
@@ -1,5 +1,6 @@
 config ARCH_SOCFPGA
 	bool "Altera SOCFPGA family" if ARCH_MULTI_V7
+	select ARCH_SUPPORTS_MSI
 	select ARCH_WANT_OPTIONAL_GPIOLIB
 	select ARM_AMBA
 	select ARM_GIC
@@ -20,6 +21,7 @@ config ARCH_SOCFPGA
 	select ARM_ERRATA_754322
 	select ARM_ERRATA_764369 if SMP
 	select ARM_ERRATA_775420
+	select PCI
 	select PL310_ERRATA_588369
 	select PL310_ERRATA_727915
 	select PL310_ERRATA_753970
diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig
index 6d51aa6..ac45398 100644
--- a/drivers/pci/Kconfig
+++ b/drivers/pci/Kconfig
@@ -119,3 +119,5 @@ config PCI_IOAPIC
 config PCI_LABEL
 	def_bool y if (DMI || ACPI)
 	select NLS
+
+source "drivers/pci/host/Kconfig"
diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile
index 0c3efcf..6ebf5bf 100644
--- a/drivers/pci/Makefile
+++ b/drivers/pci/Makefile
@@ -67,3 +67,6 @@ obj-$(CONFIG_XEN_PCIDEV_FRONTEND) += xen-pcifront.o
 obj-$(CONFIG_OF) += of.o
 
 ccflags-$(CONFIG_PCI_DEBUG) := -DDEBUG
+
+# PCI host controller drivers
+obj-y += host/
diff --git a/drivers/pci/host/Kconfig b/drivers/pci/host/Kconfig
new file mode 100644
index 0000000..67d1107
--- /dev/null
+++ b/drivers/pci/host/Kconfig
@@ -0,0 +1,16 @@
+menu "PCI host controller drivers"
+	depends on PCI
+
+config PCI_ALTERA
+	bool "Altera PCIe controller"
+	depends on ARCH_SOCFPGA
+	help
+	  The Altera PCIe Root Port driver.
+
+config PCI_ALTERA_MSI
+	bool "Altera MSI-to-GIC support"
+	depends on PCI_MSI && PCI_ALTERA
+	help
+	  This selects support for Altera MSI to GIC controller IP. Select
+	  this option if the IP is present on system.
+endmenu
diff --git a/drivers/pci/host/Makefile b/drivers/pci/host/Makefile
new file mode 100644
index 0000000..0b9c5aa
--- /dev/null
+++ b/drivers/pci/host/Makefile
@@ -0,0 +1,2 @@
+obj-$(CONFIG_PCI_ALTERA)	+= pci-altera.o
+obj-$(CONFIG_PCI_ALTERA_MSI)	+= pci-altera-msi.o
diff --git a/drivers/pci/host/pci-altera-msi.c b/drivers/pci/host/pci-altera-msi.c
new file mode 100644
index 0000000..ef7496e
--- /dev/null
+++ b/drivers/pci/host/pci-altera-msi.c
@@ -0,0 +1,340 @@
+/*
+ * Copyright Altera Corporation (C) 2013. 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/module.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <linux/interrupt.h>
+#include <linux/of_irq.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/msi.h>
+#include "pci-altera.h"
+
+#define MSI_STATUS		0x0
+#define MSI_ERROR		0x4
+#define MSI_INTMASK		0x8
+
+static DEFINE_SPINLOCK(list_lock);	/* protect msi_list */
+static LIST_HEAD(msi_list);
+
+static inline void msi_writel(struct altera_pci_msi *msi, u32 value, u32 reg)
+{
+	writel(value, msi->csr_base + reg);
+}
+
+static inline u32 msi_readl(struct altera_pci_msi *msi, u32 reg)
+{
+	return readl(msi->csr_base + reg);
+}
+
+static int altera_pci_msi_alloc(struct altera_pci_msi *msi)
+{
+	int bit;
+
+	mutex_lock(&msi->lock);
+
+	bit = find_first_zero_bit(msi->used, msi->num_of_vectors);
+	if (bit < msi->num_of_vectors)
+		set_bit(bit, msi->used);
+	else
+		bit = -ENOSPC;
+
+	mutex_unlock(&msi->lock);
+
+	return bit;
+}
+
+static void altera_pci_msi_irq_free(struct altera_pci_msi *msi,
+	unsigned long irq)
+{
+	struct device *dev = &msi->pdev->dev;
+	u32 mask;
+
+	mutex_lock(&msi->lock);
+
+	if (!test_bit(irq, msi->used))
+		dev_err(dev, "trying to free unused MSI#%lu\n", irq);
+	else {
+		clear_bit(irq, msi->used);
+		mask = msi_readl(msi, MSI_INTMASK);
+		mask &= ~(1 << irq);
+		msi_writel(msi, mask, MSI_INTMASK);
+	}
+
+	mutex_unlock(&msi->lock);
+}
+
+static irqreturn_t altera_pci_msi_isr(int irq, void *data)
+{
+	struct altera_pci_msi *msi = data;
+	unsigned long status;
+	u32 num_of_vectors = msi->num_of_vectors;
+	u32 processed = 0;
+	u32 offset;
+
+	do {
+		status = msi_readl(msi, MSI_STATUS);
+		if (!status)
+			break;
+
+		do {
+			offset = find_first_bit(&status, num_of_vectors);
+			/* Dummy read from vector to clear the interrupt */
+			readl(msi->vector_base + (offset * sizeof(u32)));
+
+			irq = irq_find_mapping(msi->domain, offset);
+			if (irq) {
+				if (test_bit(offset, msi->used))
+					generic_handle_irq(irq);
+			}
+
+			/* Clear the bit from status and repeat without reading
+			 * again status register. */
+			clear_bit(offset, &status);
+			processed++;
+		} while (status);
+	} while (1);
+
+	return processed > 0 ? IRQ_HANDLED : IRQ_NONE;
+}
+
+static struct irq_chip altera_pci_msi_irq_chip = {
+	.name = "altera-pci-msi",
+	.irq_enable = unmask_msi_irq,
+	.irq_disable = mask_msi_irq,
+	.irq_mask = mask_msi_irq,
+	.irq_unmask = unmask_msi_irq,
+};
+
+static int altera_pci_msi_map(struct irq_domain *domain, unsigned int irq,
+			 irq_hw_number_t hwirq)
+{
+	irq_set_chip_and_handler(irq, &altera_pci_msi_irq_chip,
+		handle_simple_irq);
+	irq_set_chip_data(irq, domain->host_data);
+	set_irq_flags(irq, IRQF_VALID);
+
+	return 0;
+}
+
+static const struct irq_domain_ops msi_domain_ops = {
+	.map = altera_pci_msi_map,
+};
+
+int altera_pci_msi_probe(struct platform_device *pdev)
+{
+	struct altera_pci_msi *msi;
+	struct device_node *np = pdev->dev.of_node;
+	struct resource *res;
+	int ret;
+
+	msi = devm_kzalloc(&pdev->dev, sizeof(struct altera_pci_msi),
+		GFP_KERNEL);
+	if (!msi)
+		return -ENOMEM;
+
+	mutex_init(&msi->lock);
+	msi->pdev = pdev;
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "csr");
+	msi->csr_base = devm_request_and_ioremap(&pdev->dev, res);
+	if (!msi->csr_base) {
+		dev_err(&pdev->dev, "get csr resource failed\n");
+		return -EADDRNOTAVAIL;
+	}
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+			"vector_slave");
+	msi->vector_base = devm_request_and_ioremap(&pdev->dev, res);
+	if (!msi->vector_base) {
+		dev_err(&pdev->dev, "get vector slave resource failed\n");
+		return -EADDRNOTAVAIL;
+	}
+
+	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;
+	}
+
+	msi->domain = irq_domain_add_linear(np, msi->num_of_vectors,
+					    &msi_domain_ops, msi);
+	if (!msi->domain) {
+		dev_err(&pdev->dev, "failed to create IRQ domain\n");
+		return -ENODEV;
+	}
+
+	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;
+	}
+
+	ret = devm_request_irq(&pdev->dev, msi->irq, altera_pci_msi_isr, 0,
+				altera_pci_msi_irq_chip.name, msi);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to request IRQ: %d\n", ret);
+		goto err;
+	}
+
+	spin_lock(&list_lock);
+	list_add_tail(&msi->list, &msi_list);
+	spin_unlock(&list_lock);
+
+	platform_set_drvdata(pdev, msi);
+	return 0;
+
+err:
+	irq_domain_remove(msi->domain);
+	return ret;
+}
+
+static int altera_pci_msi_remove(struct platform_device *pdev)
+{
+	struct altera_pci_msi *msi = platform_get_drvdata(pdev);
+
+	msi_writel(msi, 0, MSI_INTMASK);
+
+	if (msi->domain)
+		irq_domain_remove(msi->domain);
+
+	spin_lock(&list_lock);
+	list_del(&msi->list);
+	spin_unlock(&list_lock);
+
+	platform_set_drvdata(pdev, NULL);
+	return 0;
+}
+
+struct altera_pci_msi *altera_pci_msi_request(struct device_node *msi_np)
+{
+	struct altera_pci_msi *msi;
+
+	spin_lock(&list_lock);
+	list_for_each_entry(msi, &msi_list, list) {
+		if (msi_np == msi->pdev->dev.of_node) {
+			if (!msi->requested) {
+				msi->requested = true;
+				spin_unlock(&list_lock);
+				return msi;
+			} else {
+				pr_info("MSI device is in use.\n");
+				spin_unlock(&list_lock);
+				return NULL;
+			}
+		}
+	}
+	spin_unlock(&list_lock);
+	pr_info("MSI device not found!\n");
+	return NULL;
+}
+
+int altera_pci_msi_free(struct altera_pci_msi *msi)
+{
+	if (!msi || !msi->requested)
+		return -EINVAL;
+
+	spin_lock(&list_lock);
+	msi->requested = false;
+	spin_unlock(&list_lock);
+
+	return 0;
+}
+
+/* Arch hooks */
+int arch_setup_msi_irq(struct pci_dev *pdev, struct msi_desc *desc)
+{
+	struct altera_pcie *pcie = sys_to_pcie(pdev->bus->sysdata);
+	struct altera_pci_msi *msi = pcie->msi;
+	struct msi_msg msg;
+	u32 irq;
+	u32 mask;
+	int hwirq;
+
+	hwirq = altera_pci_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->vector_phy + (hwirq * sizeof(u32));
+	msg.address_hi = 0;		/* 32 bit address only */
+	msg.data = hwirq;
+
+	write_msi_msg(irq, &msg);
+
+	mask = msi_readl(msi, MSI_INTMASK);
+	mask |= 1 << hwirq;
+	msi_writel(msi, mask, MSI_INTMASK);
+	dev_dbg(&pdev->dev, "msi#%d virq %d address_lo 0x%x\n", hwirq, irq,
+		msg.address_lo);
+	return 0;
+}
+
+void arch_teardown_msi_irq(unsigned int irq)
+{
+	struct msi_desc *entry = irq_get_msi_desc(irq);
+	struct pci_dev *pdev = entry->dev;
+	struct altera_pcie *pcie = sys_to_pcie(pdev->bus->sysdata);
+	struct altera_pci_msi *msi = pcie->msi;
+	struct irq_data *d = irq_get_irq_data(irq);
+
+	altera_pci_msi_irq_free(msi, d->hwirq);
+	irq_set_msi_desc(entry->irq, NULL);
+	irq_dispose_mapping(entry->irq);
+}
+
+int arch_msi_check_device(struct pci_dev *pdev, int nvec, int type)
+{
+	/* Doesn't support MSIX and multiple MSI */
+	if ((type == PCI_CAP_ID_MSIX) || (type == PCI_CAP_ID_MSI && nvec > 1))
+		return 1;
+	return 0;
+}
+
+
+static const struct of_device_id altera_pci_msi_of_match[] = {
+	{ .compatible = "altr,msi-1.0", NULL },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, altera_pci_msi_of_match);
+
+static struct platform_driver altera_pci_msi_driver = {
+	.driver = {
+		.name = "altera-pci-msi",
+		.owner = THIS_MODULE,
+		.of_match_table = of_match_ptr(altera_pci_msi_of_match),
+	},
+	.probe = altera_pci_msi_probe,
+	.remove = altera_pci_msi_remove,
+};
+
+static int __init altera_pci_msi_init(void)
+{
+	return platform_driver_register(&altera_pci_msi_driver);
+}
+arch_initcall_sync(altera_pci_msi_init);
+
+MODULE_AUTHOR("Ley Foon Tan <lftan@altera.com>");
+MODULE_DESCRIPTION("Altera PCIe MSI support");
+MODULE_LICENSE("GPL v2");
+
diff --git a/drivers/pci/host/pci-altera.c b/drivers/pci/host/pci-altera.c
new file mode 100644
index 0000000..0938d8d
--- /dev/null
+++ b/drivers/pci/host/pci-altera.c
@@ -0,0 +1,562 @@
+/*
+ * Copyright Altera Corporation (C) 2013. All rights reserved
+ *
+ * Bits taken from arch/arm/mach-tegra/pcie.c
+ *
+ * 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/module.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <linux/interrupt.h>
+#include <linux/of_irq.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+
+#include "pci-altera.h"
+
+/* Registers offset */
+#define A2P_ADDR_MAP_LO0  0x1000
+#define A2P_ADDR_MAP_HI0  0x1004
+#define RP_TX_REG0        0x2000
+#define RP_TX_REG1        0x2004
+#define RP_TX_CNTRL       0x2008
+#define RP_TX_EOP         0x2
+#define RP_TX_SOP         0x1
+#define RP_RXCPL_STATUS   0x2010
+#define RP_RXCPL_EOP      0x2
+#define RP_RXCPL_SOP      0x1
+#define RP_RXCPL_REG0     0x2014
+#define RP_RXCPL_REG1     0x2018
+#define P2A_INT_STATUS    0x3060
+#define P2A_INT_STS_ALL   0x0F
+#define RPRX_CPL_REV      0x10
+#define P2A_INT_ENABLE    0x3070
+#define P2A_INT_ENA_ALL   0x0F
+
+/* TLP CFG RD WR */
+#define TLP_FMTTYPE_CFGRD0 0x04  /* Configuration Read  Type 0 */
+#define TLP_FMTTYPE_CFGWR0 0x44  /* Configuration Write Type 0 */
+#define TLP_FMTTYPE_CFGRD1 0x05  /* Configuration Read  Type 1 */
+#define TLP_FMTTYPE_CFGWR1 0x45  /* Configuration Write Type 1 */
+#define TLP_PAYLOAD_SIZE   0x01  /* TLP Data Payload Size      */
+#define TLP_CFG_DW0(fmttype)            ((fmttype << 24) | TLP_PAYLOAD_SIZE)
+#define TLP_CFG_DW1(reqid, tag)         ((reqid << 16) | (tag << 8) | 0xF)
+#define TLP_READ_TAG       0x1D  /* statically define */
+#define TLP_WRITE_TAG      0x10  /* statically define */
+#define TLP_CFG_DW2(bus, devfn, offset) ((bus << 24) | (devfn << 16) | offset)
+
+/* Address translation table entry size */
+#define ATT_ENTRY_SIZE		8
+
+#define SOP_LOOP	1
+#define HEADER_LOOP	2
+#define DETECTION_LOOP	3
+
+#define DWORD_MASK	3
+
+struct tlp_rp_regpair_t {
+	u32 rp_ctrl;
+	u32 rp_reg0;
+	u32 rp_reg1;
+};
+
+/*
+ * The Root Port is boot/initialized with bus 0 device 0
+ * pribus use TYPE0 CFG, secbus use TYPE1 CFG, subbus use TYPE1 CFG
+ */
+#define ALTRPCIERP_DEVFN 0
+static u8 rp_pribus;
+static u8 rp_secbus;
+
+static void altera_pcie_retrain(struct pci_dev *dev)
+{
+	u16 linkcap, linkstat;
+
+	/*
+	 * Set the retrain bit if the PCIe rootport support > 2.5GB/s, but
+	 * current speed is 2.5 GB/s.
+	 */
+	pcie_capability_read_word(dev, PCI_EXP_LNKCAP, &linkcap);
+
+	if ((linkcap & PCI_EXP_LNKCAP_SLS) <= PCI_EXP_LNKCAP_SLS_2_5GB)
+		return;
+
+	pcie_capability_read_word(dev, PCI_EXP_LNKSTA, &linkstat);
+	if ((linkstat & PCI_EXP_LNKSTA_CLS) == PCI_EXP_LNKSTA_CLS_2_5GB)
+		pcie_capability_set_word(dev, PCI_EXP_LNKCTL,
+					PCI_EXP_LNKCTL_RL);
+
+	return;
+}
+DECLARE_PCI_FIXUP_EARLY(PCI_ANY_ID, PCI_ANY_ID, altera_pcie_retrain);
+
+static inline void cra_writel(struct altera_pcie *pcie, u32 value, u32 reg)
+{
+	writel(value, pcie->cra_base + reg);
+}
+
+static inline u32 cra_readl(struct altera_pcie *pcie, u32 reg)
+{
+	return readl(pcie->cra_base + reg);
+}
+
+/* TLP packet RX pair */
+static void tlp_read_rp_rx(struct altera_pcie *pcie,
+			   struct tlp_rp_regpair_t *tlp_rp_regdata)
+{
+	tlp_rp_regdata->rp_ctrl = cra_readl(pcie, RP_RXCPL_STATUS);
+	tlp_rp_regdata->rp_reg0 = cra_readl(pcie, RP_RXCPL_REG0);
+	tlp_rp_regdata->rp_reg1 = cra_readl(pcie, RP_RXCPL_REG1);
+}
+
+/* TLP packet TX pair */
+static void tlp_write_rp_tx(struct altera_pcie *pcie,
+			    struct tlp_rp_regpair_t *tlp_rp_regdata)
+{
+	cra_writel(pcie, tlp_rp_regdata->rp_reg0, RP_TX_REG0);
+	cra_writel(pcie, tlp_rp_regdata->rp_reg1, RP_TX_REG1);
+	cra_writel(pcie, tlp_rp_regdata->rp_ctrl, RP_TX_CNTRL);
+}
+
+/* TLP packet detect SOP and EOP */
+static int tlp_read_packet(struct altera_pcie *pcie, u32 *value)
+{
+	int err = PCIBIOS_SUCCESSFUL;
+	u8 loop;
+	struct tlp_rp_regpair_t *tlp_rp_regdata;
+
+	/* allocate tlp_rp_regdata */
+	tlp_rp_regdata = kzalloc(sizeof(struct tlp_rp_regpair_t), GFP_KERNEL);
+	if (!tlp_rp_regdata) {
+		dev_err(pcie->dev, "out of memory !!!\n");
+		err = -ENOMEM;
+		goto ret_error;
+	}
+
+	/*
+	 * Detect RP_RXCPL_SOP is not require,
+	 * read till EOP to avoid completion packet misalignment.
+	 * Detect RP_RXCPL_EOP with loop of
+	 * TLP_PAYLOAD_SIZE + 1(SOP) + 2(header) + 3(detection loop)
+	 */
+	for (loop = (TLP_PAYLOAD_SIZE + SOP_LOOP + HEADER_LOOP +
+		DETECTION_LOOP); loop > 0; loop--) {
+		tlp_read_rp_rx(pcie, tlp_rp_regdata);
+		/* detect EOP */
+		if (tlp_rp_regdata->rp_ctrl & RP_RXCPL_EOP)
+			break;
+	}
+	/* if no EOP detected */
+	if (loop == 0) {
+		err = -ENOENT;
+		goto ret_freemem;
+	}
+
+	/* read data */
+	if (value)
+		*value = tlp_rp_regdata->rp_reg0;
+
+ret_freemem:
+	kfree(tlp_rp_regdata);
+ret_error:
+	return err;
+}
+
+/* pci_ops read using TLP packet receive */
+static int tlp_cfg_dword_read(struct altera_pcie *pcie, u8 bus, u32 devfn,
+			      int where, u32 *value)
+{
+	struct tlp_rp_regpair_t *tlp_rp_regdata;
+	int ret;
+
+	/* if not DWORD align */
+	if (!IS_ALIGNED(where, sizeof(u32))) {
+		dev_err(pcie->dev, "data not DWORD align\n");
+		return PCIBIOS_BAD_REGISTER_NUMBER;
+	}
+
+	/*
+	 * Implement the device number filtering at the lowest level layer.
+	 * Per PCIe spec section 7.3.1 - Configuration Requests
+	 * targeting the Bus Number associated with a Link specifying
+	 * Device Number 0 are delivered to the device attached to the Link;
+	 * Configuration Requests specifying all other Device Numbers (1-31)
+	 * must be terminated by the Switch Downstream Port or the Root Port
+	 * with an Unsupported Request Completion Status.
+	 */
+	if (((bus == rp_pribus) || (bus == rp_secbus)) &&
+		(PCI_SLOT(devfn) > 0)) {
+		*value = ~0UL;
+		return PCIBIOS_SUCCESSFUL;
+	}
+
+	/* allocate tlp_rp_regdata */
+	tlp_rp_regdata = kzalloc(sizeof(struct tlp_rp_regpair_t), GFP_KERNEL);
+	if (!tlp_rp_regdata) {
+		dev_err(pcie->dev, "out of memory !!!\n");
+		return -ENOMEM;
+	}
+
+	/* sending TLP packet */
+	if (bus == rp_pribus)
+		tlp_rp_regdata->rp_reg0 = TLP_CFG_DW0(TLP_FMTTYPE_CFGRD0);
+	else
+		tlp_rp_regdata->rp_reg0 = TLP_CFG_DW0(TLP_FMTTYPE_CFGRD1);
+	tlp_rp_regdata->rp_reg1 = TLP_CFG_DW1(
+				  ((rp_pribus << 8) | ALTRPCIERP_DEVFN),
+				  TLP_READ_TAG);
+	tlp_rp_regdata->rp_ctrl = RP_TX_SOP;
+	tlp_write_rp_tx(pcie, tlp_rp_regdata);
+	tlp_rp_regdata->rp_reg0 = TLP_CFG_DW2(bus, devfn, where);
+	tlp_rp_regdata->rp_reg1 = 0x0;
+	tlp_rp_regdata->rp_ctrl = RP_TX_EOP;
+	tlp_write_rp_tx(pcie, tlp_rp_regdata);
+
+	/* read data from completion package */
+	ret = tlp_read_packet(pcie, value);
+	if (ret)
+		*value = ~0UL;	/* return 0xFFFFFFFF if error */
+
+	kfree(tlp_rp_regdata);
+	return ret;
+}
+
+/* pci_ops write using TLP packet send */
+static int tlp_cfg_dword_write(struct altera_pcie *pcie, u8 bus, u32 devfn,
+			       int where, u32 value)
+{
+	struct tlp_rp_regpair_t *tlp_rp_regdata;
+
+	/* if not DWORD align */
+	if (!IS_ALIGNED(where, sizeof(u32))) {
+		dev_err(pcie->dev, "data not DWORD align\n");
+		return PCIBIOS_BAD_REGISTER_NUMBER;
+	}
+
+	/*
+	 * Implement the device number filtering at the lowest level layer.
+	 * Per PCIe spec section 7.3.1 - Configuration Requests
+	 * targeting the Bus Number associated with a Link specifying
+	 * Device Number 0 are delivered to the device attached to the Link;
+	 * Configuration Requests specifying all other Device Numbers (1-31)
+	 * must be terminated by the Switch Downstream Port or the Root Port
+	 * with an Unsupported Request Completion Status.
+	 */
+	if (((bus == rp_pribus) || (bus == rp_secbus)) &&
+		(PCI_SLOT(devfn) > 0))
+		return PCIBIOS_SET_FAILED;
+
+	/* allocate tlp_rp_regdata */
+	tlp_rp_regdata = kzalloc(sizeof(struct tlp_rp_regpair_t), GFP_KERNEL);
+	if (!tlp_rp_regdata) {
+		dev_err(pcie->dev, "out of memory !!!\n");
+		return -ENOMEM;
+	}
+
+	/* sending TLP packet */
+	if (bus == rp_pribus)
+		tlp_rp_regdata->rp_reg0 = TLP_CFG_DW0(TLP_FMTTYPE_CFGWR0);
+	else
+		tlp_rp_regdata->rp_reg0 = TLP_CFG_DW0(TLP_FMTTYPE_CFGWR1);
+	tlp_rp_regdata->rp_reg1 = TLP_CFG_DW1(
+				  ((rp_pribus << 8) | ALTRPCIERP_DEVFN),
+				  TLP_WRITE_TAG);
+	tlp_rp_regdata->rp_ctrl = RP_TX_SOP;
+	tlp_write_rp_tx(pcie, tlp_rp_regdata);
+	tlp_rp_regdata->rp_reg0 = TLP_CFG_DW2(bus, devfn, where);
+	tlp_rp_regdata->rp_reg1 = value;
+	tlp_rp_regdata->rp_ctrl = RP_TX_EOP;
+	tlp_write_rp_tx(pcie, tlp_rp_regdata);
+
+	/* TLP read response */
+	tlp_read_packet(pcie, NULL);
+
+	/* if change altrpcierp bus number, update variable... */
+	if ((bus == rp_pribus) && (devfn == ALTRPCIERP_DEVFN) &&
+	    (where == PCI_PRIMARY_BUS)) {
+		rp_pribus = (u8)(value);
+		rp_secbus = (u8)(value >> 8);
+	}
+
+	kfree(tlp_rp_regdata);
+	return PCIBIOS_SUCCESSFUL;
+}
+
+/* pci_ops read */
+static int altera_pcie_cfg_read(struct pci_bus *bus, unsigned int devfn,
+			       int where, int size, u32 *value)
+{
+	struct altera_pcie *pcie = sys_to_pcie(bus->sysdata);
+	int ret;
+
+	/* if cross DWORD boundary */
+	if (((where & DWORD_MASK) + size) > 4) {
+		dev_err(pcie->dev, "Data cross DWORD boundary\n");
+		return PCIBIOS_BAD_REGISTER_NUMBER;
+	}
+
+	/* TLP read */
+	ret = tlp_cfg_dword_read(pcie, bus->number, devfn,
+		(where & ~DWORD_MASK), value);
+	if (ret != PCIBIOS_SUCCESSFUL)
+		return ret;
+
+	*value = *value >> ((where & DWORD_MASK) << 3);  /* position */
+	*value = *value & (~0UL >> ((4 - size) << 3));  /* mask */
+	return PCIBIOS_SUCCESSFUL;
+}
+
+/* pci_ops write */
+static int altera_pcie_cfg_write(struct pci_bus *bus, unsigned int devfn,
+				int where, int size, u32 value)
+{
+	struct altera_pcie *pcie = sys_to_pcie(bus->sysdata);
+	u32 data32 = value;
+	int ret;
+
+	/* if cross DWORD boundary */
+	if (((where & DWORD_MASK) + size) > 4) {
+		dev_err(pcie->dev, "Data cross DWORD boundary\n");
+		return PCIBIOS_BAD_REGISTER_NUMBER;
+	}
+
+	/* TLP read and merge DWORD */
+	if (IS_ALIGNED(where, sizeof(u32)) || (size != sizeof(u32))) {
+		ret = tlp_cfg_dword_read(pcie, bus->number, devfn,
+			where & ~DWORD_MASK, &data32);
+		if (ret)
+			return ret;
+		data32 = data32 & ~((~0UL >> ((4 - size) << 3))
+				    << ((where & DWORD_MASK) << 3));
+		data32 = data32 | (value << ((where & DWORD_MASK) << 3));
+	}
+
+	/* TLP write */
+	return tlp_cfg_dword_write(pcie, bus->number, devfn,
+		(where & ~DWORD_MASK), data32);
+}
+
+/* pci_ops */
+static struct pci_ops altera_pcie_ops = {
+	.read = altera_pcie_cfg_read,
+	.write = altera_pcie_cfg_write,
+};
+
+/* hw_pci setup */
+static int altera_pcie_setup(int nr, struct pci_sys_data *sys)
+{
+	struct altera_pcie *pcie = sys_to_pcie(sys);
+	int err;
+	u32  pages, i;
+	u32 translation_size;
+	u32 txs_size;
+	struct resource *res;
+
+	/* get translation table size */
+	cra_writel(pcie, ~0UL, A2P_ADDR_MAP_LO0);
+	translation_size = (~(cra_readl(pcie, A2P_ADDR_MAP_LO0)
+		& 0xFFFFFFFC)) + 1;
+
+	txs_size = resource_size(pcie->txs);
+	/* get number of translation table pages */
+	pages = txs_size / translation_size;
+
+	/* allocate resource memory */
+	res = kzalloc(sizeof(struct resource) * pages, GFP_KERNEL);
+	if (!res) {
+		dev_err(pcie->dev, "out of memory !!!\n");
+		err = -ENOMEM;
+		goto ret_error;
+	}
+	pcie->table_res = res;
+	/* IORESOURCE_MEM use A2P_ADDR_MAP_LO0 */
+	if (pages >= 1) {
+		res[0].name  = "ALTERA PCIE RP MEM";
+		res[0].start = pcie->txs->start;
+		res[0].end   = res[0].start + translation_size - 1;
+		res[0].flags = IORESOURCE_MEM;
+		if (request_resource(&iomem_resource, &res[0])) {
+			dev_err(pcie->dev, "out of memory !!!\n");
+			err = -ENOMEM;
+			goto ret_freeres;
+		}
+		pci_add_resource_offset(&sys->resources, &res[0],
+					sys->mem_offset);
+	}
+
+	/* IORESOURCE_MEM | IORESOURCE_PREFETCH */
+	if (pages >= 2) {
+		res[1].name  = "ALTERA PCIE RP PREF MEM";
+		res[1].start = res[0].end + 1;
+		res[1].end   = res[1].start + translation_size - 1;
+		res[1].flags = IORESOURCE_MEM | IORESOURCE_PREFETCH;
+		if (request_resource(&iomem_resource, &res[1])) {
+			dev_err(pcie->dev, "out of memory !!!\n");
+			err = -ENOMEM;
+			goto ret_relres;
+		}
+		pci_add_resource_offset(&sys->resources, &res[1],
+					sys->mem_offset);
+	}
+
+	/* Configure Avalon-MM-to-PCI Express Address Translation Table */
+	for (i = 0; i < pages; i++) {
+		cra_writel(pcie, res[i].start,
+			A2P_ADDR_MAP_LO0 + (ATT_ENTRY_SIZE * i));
+		cra_writel(pcie, 0, A2P_ADDR_MAP_HI0 + (ATT_ENTRY_SIZE * i));
+	}
+
+	return 1;
+
+ret_relres:
+	release_resource(res);
+ret_freeres:
+	kfree(res);
+ret_error:
+	return err;
+}
+
+/* hw_pci map_irq */
+static int altera_pcie_map_irq(const struct pci_dev *pdev, u8 slot, u8 pin)
+{
+	struct altera_pcie *pcie = sys_to_pcie(pdev->bus->sysdata);
+	return pcie->hwirq;
+}
+
+/* hw_pci */
+static struct hw_pci altera_pcie_hw __initdata = {
+#ifdef CONFIG_PCI_DOMAINS
+	.domain			= 0,
+#endif
+	.nr_controllers		= 1,
+	.ops			= &altera_pcie_ops,
+	.setup			= altera_pcie_setup,
+	.map_irq		= altera_pcie_map_irq,
+};
+
+/* RP ISR - clear status */
+static irqreturn_t altera_pcie_isr(int irq, void *arg)
+{
+	struct altera_pcie *pcie = arg;
+	cra_writel(pcie, P2A_INT_STS_ALL, P2A_INT_STATUS);
+	return IRQ_HANDLED;
+}
+
+/* RP device init and resources, based on Device Tree */
+static int altera_pcie_probe(struct platform_device *pdev)
+{
+	struct altera_pcie *pcie;
+	struct resource *cra;
+	int ret;
+
+	pcie = devm_kzalloc(&pdev->dev, sizeof(*pcie), GFP_KERNEL);
+	if (!pcie) {
+		dev_err(&pdev->dev, "no memory for altera pcie\n");
+		return -ENOMEM;
+	}
+	pcie->dev = &pdev->dev;
+
+	cra = platform_get_resource_byname(pdev, IORESOURCE_MEM, "Cra");
+	pcie->cra_base = devm_request_and_ioremap(&pdev->dev, cra);
+	if (!pcie->cra_base) {
+		dev_err(&pdev->dev, "get Cra resource failed\n");
+		return -EADDRNOTAVAIL;
+	}
+
+	pcie->txs = platform_get_resource_byname(pdev, IORESOURCE_MEM, "Txs");
+	if (!pcie->txs) {
+		dev_err(&pdev->dev, "get Txs resource failed\n");
+		return PTR_ERR(pcie->txs);
+	}
+
+	/* setup IRQ */
+	pcie->hwirq = platform_get_irq(pdev, 0);
+	if (pcie->hwirq <= 0) {
+		dev_err(&pdev->dev, "failed to get IRQ: %d\n", pcie->hwirq);
+		return -EINVAL;
+	}
+	ret = devm_request_irq(&pdev->dev, pcie->hwirq, altera_pcie_isr,
+			IRQF_SHARED, pdev->name, pcie);
+
+	if (ret) {
+		dev_err(&pdev->dev, "failed to register legacy IRQ\n");
+		return ret;
+	}
+
+#ifdef CONFIG_PCI_ALTERA_MSI
+	{
+		struct device_node *np_msi;
+		np_msi = of_parse_phandle(pdev->dev.of_node, "altr,msi", 0);
+		if (!np_msi)
+			return -EINVAL;
+
+		pcie->msi = altera_pci_msi_request(np_msi);
+		if (!pcie->msi) {
+			dev_err(&pdev->dev, "failed to request MSI support\n");
+			return -EINVAL;
+		}
+	}
+#endif
+	/* clear PCIe to AvMM IRQ status */
+	cra_writel(pcie, P2A_INT_STS_ALL, P2A_INT_STATUS);
+	/* enable PCIe to AvMM IRQ */
+	cra_writel(pcie, P2A_INT_ENA_ALL, P2A_INT_ENABLE);
+
+	altera_pcie_hw.private_data = (void **)&pcie;
+	pci_common_init(&altera_pcie_hw);
+
+	platform_set_drvdata(pdev, pcie);
+	return 0;
+}
+
+static int __exit altera_pcie_remove(struct platform_device *pdev)
+{
+	struct altera_pcie *pcie = platform_get_drvdata(pdev);
+#ifdef CONFIG_PCI_MSI
+	altera_pci_msi_free(pcie->msi);
+#endif
+	free_irq(pcie->hwirq, pcie);
+	release_resource(pcie->table_res);
+	kfree(pcie->table_res);
+	platform_set_drvdata(pdev, NULL);
+	return 0;
+}
+
+static const struct of_device_id altera_pcie_of_match[] = {
+	{ .compatible = "ALTR,root-port-13.0", },
+	{ .compatible = "ALTR,pcie-root-port-13.0", },
+	{ .compatible = "altr,pcie-root-port-1.0", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, altera_pcie_of_match);
+
+static struct platform_driver altera_pcie_driver = {
+	.probe		= altera_pcie_probe,
+	.remove		= altera_pcie_remove,
+	.driver = {
+		.name	= "altera-pcie",
+		.owner	= THIS_MODULE,
+		.of_match_table = of_match_ptr(altera_pcie_of_match),
+	},
+};
+
+static int __init altera_pcie_init(void)
+{
+	return platform_driver_register(&altera_pcie_driver);
+}
+subsys_initcall(altera_pcie_init);
+
+MODULE_AUTHOR("Ley Foon Tan <lftan@altera.com>");
+MODULE_DESCRIPTION("Altera PCIe host controller driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/pci/host/pci-altera.h b/drivers/pci/host/pci-altera.h
new file mode 100644
index 0000000..03afd36
--- /dev/null
+++ b/drivers/pci/host/pci-altera.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright Altera Corporation (C) 2013. 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/>.
+ */
+#ifndef PCI_ALTERA_H
+#define PCI_ALTERA_H
+
+#ifdef CONFIG_PCI_MSI
+
+#define MAX_MSI_VECTORS		32
+struct altera_pci_msi {
+	DECLARE_BITMAP(used, MAX_MSI_VECTORS);
+	struct mutex		lock;	/* proctect used variable */
+	struct list_head	list;
+	struct platform_device	*pdev;
+	struct irq_domain	*domain;
+	void __iomem		*csr_base;
+	void __iomem		*vector_base;
+	u32			vector_phy;
+	u32			num_of_vectors;
+	int			irq;
+	bool			requested;
+};
+
+struct altera_pci_msi *altera_pci_msi_request(struct device_node *msi_np);
+int altera_pci_msi_free(struct altera_pci_msi *msi);
+
+#endif /* CONFIG_PCI_MSI */
+
+struct altera_pcie {
+	struct device		*dev;
+	struct resource		*txs;
+	struct resource		*table_res;
+	struct altera_pci_msi	*msi;
+	void __iomem		*cra_base;
+	int			hwirq;
+};
+
+static inline struct altera_pcie *sys_to_pcie(struct pci_sys_data *sys)
+{
+	return sys->private_data;
+}
+
+#endif /* PCI_ALTERA_H */
+
diff --git a/drivers/pci/msi.c b/drivers/pci/msi.c
index 00cc78c..97ad4d1 100644
--- a/drivers/pci/msi.c
+++ b/drivers/pci/msi.c
@@ -29,7 +29,7 @@ static int pci_msi_enable = 1;
 /* Arch hooks */
 
 #ifndef arch_msi_check_device
-int arch_msi_check_device(struct pci_dev *dev, int nvec, int type)
+int __weak arch_msi_check_device(struct pci_dev *dev, int nvec, int type)
 {
 	return 0;
 }
-- 
1.7.1

