[OpenWrt-Devel] [PATCH] ipq806x: add pcie support to ipq806x based platforms

Mathieu Olivari mathieu at codeaurora.org
Tue May 5 18:44:29 EDT 2015


This change adds PCIe support to IPQ806x based platforms. The driver is
actually cherry-picked from the following LKML thread:
*https://lwn.net/Articles/643086/ (patches 110-111)

We also add here an additional fix to support multiple PCI controllers
on the same platform (patch 112), and to patch the ap148 & dbs149 DTS
files (patch 113).

Signed-off-by: Mathieu Olivari <mathieu at codeaurora.org>
---
 target/linux/ipq806x/config-3.18                   |   6 +
 target/linux/ipq806x/config-4.0                    |  10 +-
 ...CI-qcom-Document-PCIe-devicetree-bindings.patch | 266 ++++++++
 ...-qcom-Add-Qualcomm-PCIe-controller-driver.patch | 753 +++++++++++++++++++++
 ...-qcom-add-pcie-nodes-to-ipq806x-platforms.patch | 268 ++++++++
 ...tomatically-select-PCI_DOMAINS-if-PCI-is-.patch |  29 +
 .../patches-3.18/700-add-gmac-dts-suport.patch     |  19 +-
 ...CI-qcom-Document-PCIe-devicetree-bindings.patch | 266 ++++++++
 ...-qcom-Add-Qualcomm-PCIe-controller-driver.patch | 753 +++++++++++++++++++++
 ...-qcom-add-pcie-nodes-to-ipq806x-platforms.patch | 260 +++++++
 ...tomatically-select-PCI_DOMAINS-if-PCI-is-.patch |  29 +
 .../patches-4.0/700-add-gmac-dts-suport.patch      |  26 +-
 12 files changed, 2662 insertions(+), 23 deletions(-)
 create mode 100644 target/linux/ipq806x/patches-3.18/110-DT-PCI-qcom-Document-PCIe-devicetree-bindings.patch
 create mode 100644 target/linux/ipq806x/patches-3.18/111-PCI-qcom-Add-Qualcomm-PCIe-controller-driver.patch
 create mode 100644 target/linux/ipq806x/patches-3.18/112-ARM-dts-qcom-add-pcie-nodes-to-ipq806x-platforms.patch
 create mode 100644 target/linux/ipq806x/patches-3.18/113-ARM-qcom-automatically-select-PCI_DOMAINS-if-PCI-is-.patch
 create mode 100644 target/linux/ipq806x/patches-4.0/110-DT-PCI-qcom-Document-PCIe-devicetree-bindings.patch
 create mode 100644 target/linux/ipq806x/patches-4.0/111-PCI-qcom-Add-Qualcomm-PCIe-controller-driver.patch
 create mode 100644 target/linux/ipq806x/patches-4.0/112-ARM-dts-qcom-add-pcie-nodes-to-ipq806x-platforms.patch
 create mode 100644 target/linux/ipq806x/patches-4.0/113-ARM-qcom-automatically-select-PCI_DOMAINS-if-PCI-is-.patch

diff --git a/target/linux/ipq806x/config-3.18 b/target/linux/ipq806x/config-3.18
index 8de4cb4..32f4b67 100644
--- a/target/linux/ipq806x/config-3.18
+++ b/target/linux/ipq806x/config-3.18
@@ -257,6 +257,11 @@ CONFIG_OLD_SIGSUSPEND3=y
 CONFIG_PAGEFLAGS_EXTENDED=y
 CONFIG_PAGE_OFFSET=0xC0000000
 CONFIG_PCI=y
+CONFIG_PCIEAER=y
+CONFIG_PCIEPORTBUS=y
+CONFIG_PCIE_DW=y
+CONFIG_PCIE_QCOM=y
+CONFIG_PCI_DOMAINS=y
 CONFIG_PCI_MSI=y
 CONFIG_PERF_EVENTS=y
 CONFIG_PERF_USE_VMALLOC=y
@@ -294,6 +299,7 @@ CONFIG_QCOM_BAM_DMA=y
 CONFIG_QCOM_GSBI=y
 CONFIG_QCOM_SCM=y
 CONFIG_QCOM_WDT=y
+CONFIG_RAS=y
 # CONFIG_RCU_BOOST is not set
 CONFIG_RCU_CPU_STALL_TIMEOUT=21
 CONFIG_RCU_CPU_STALL_VERBOSE=y
diff --git a/target/linux/ipq806x/config-4.0 b/target/linux/ipq806x/config-4.0
index c7bbcd9..1786abb 100644
--- a/target/linux/ipq806x/config-4.0
+++ b/target/linux/ipq806x/config-4.0
@@ -266,7 +266,14 @@ CONFIG_OLD_SIGSUSPEND3=y
 CONFIG_PAGEFLAGS_EXTENDED=y
 CONFIG_PAGE_OFFSET=0xC0000000
 CONFIG_PCI=y
-# CONFIG_PCI_DOMAINS_GENERIC is not set
+CONFIG_PCIEAER=y
+CONFIG_PCIEPORTBUS=y
+CONFIG_PCIE_DW=y
+CONFIG_PCIE_PME=y
+CONFIG_PCIE_QCOM=y
+CONFIG_PCI_DEBUG=y
+CONFIG_PCI_DOMAINS=y
+CONFIG_PCI_DOMAINS_GENERIC=y
 CONFIG_PCI_MSI=y
 CONFIG_PERF_EVENTS=y
 CONFIG_PERF_USE_VMALLOC=y
@@ -306,6 +313,7 @@ CONFIG_QCOM_BAM_DMA=y
 CONFIG_QCOM_GSBI=y
 CONFIG_QCOM_SCM=y
 CONFIG_QCOM_WDT=y
+CONFIG_RAS=y
 # CONFIG_RCU_BOOST is not set
 CONFIG_RCU_CPU_STALL_TIMEOUT=21
 CONFIG_RCU_STALL_COMMON=y
diff --git a/target/linux/ipq806x/patches-3.18/110-DT-PCI-qcom-Document-PCIe-devicetree-bindings.patch b/target/linux/ipq806x/patches-3.18/110-DT-PCI-qcom-Document-PCIe-devicetree-bindings.patch
new file mode 100644
index 0000000..95398e0
--- /dev/null
+++ b/target/linux/ipq806x/patches-3.18/110-DT-PCI-qcom-Document-PCIe-devicetree-bindings.patch
@@ -0,0 +1,266 @@
+Content-Type: text/plain; charset="utf-8"
+MIME-Version: 1.0
+Content-Transfer-Encoding: 7bit
+Subject: [v2,3/5] DT: PCI: qcom: Document PCIe devicetree bindings
+From: Stanimir Varbanov <svarbanov at mm-sol.com>
+X-Patchwork-Id: 6326181
+Message-Id: <1430743338-10441-4-git-send-email-svarbanov at mm-sol.com>
+To: Rob Herring <robh+dt at kernel.org>, Kumar Gala <galak at codeaurora.org>,
+	Mark Rutland <mark.rutland at arm.com>,
+	Grant Likely <grant.likely at linaro.org>,
+	Bjorn Helgaas <bhelgaas at google.com>,
+	Kishon Vijay Abraham I <kishon at ti.com>,
+	Russell King <linux at arm.linux.org.uk>, Arnd Bergmann <arnd at arndb.de>
+Cc: linux-arm-msm at vger.kernel.org, linux-kernel at vger.kernel.org,
+	linux-arm-kernel at lists.infradead.org, devicetree at vger.kernel.org,
+	linux-pci at vger.kernel.org, Mathieu Olivari <mathieu at codeaurora.org>,
+	Srinivas Kandagatla <srinivas.kandagatla at linaro.org>,
+	Stanimir Varbanov <svarbanov at mm-sol.com>
+Date: Mon,  4 May 2015 15:42:16 +0300
+
+Document Qualcomm PCIe driver devicetree bindings.
+
+Signed-off-by: Stanimir Varbanov <svarbanov at mm-sol.com>
+
+---
+.../devicetree/bindings/pci/qcom,pcie.txt          |  231 ++++++++++++++++++++
+ 1 files changed, 231 insertions(+), 0 deletions(-)
+ create mode 100644 Documentation/devicetree/bindings/pci/qcom,pcie.txt
+
+diff --git a/Documentation/devicetree/bindings/pci/qcom,pcie.txt b/Documentation/devicetree/bindings/pci/qcom,pcie.txt
+new file mode 100644
+index 0000000..dcf7348
+--- /dev/null
++++ b/Documentation/devicetree/bindings/pci/qcom,pcie.txt
+@@ -0,0 +1,231 @@
++* Qualcomm PCI express root complex
++
++- compatible:
++	Usage: required
++	Value type: <stringlist>
++	Definition: Value shall include
++		    - "qcom,pcie-v0" for apq/ipq8064
++		    - "qcom,pcie-v1" for apq8084
++
++- reg:
++	Usage: required
++	Value type: <prop-encoded-array>
++	Definition: Register ranges as listed in the reg-names property
++
++- reg-names:
++	Usage: required
++	Value type: <stringlist>
++	Definition: Must include the following entries
++		    - "parf"   Qualcomm specific registers
++		    - "dbi"    Designware PCIe registers
++		    - "elbi"   External local bus interface registers
++		    - "config" PCIe configuration space
++
++- device_type:
++	Usage: required
++	Value type: <string>
++	Definition: Should be "pci". As specified in designware-pcie.txt
++
++- #address-cells:
++	Usage: required
++	Value type: <u32>
++	Definition: Should be set to 3. As specified in designware-pcie.txt
++
++- #size-cells:
++	Usage: required
++	Value type: <u32>
++	Definition: Should be set 2. As specified in designware-pcie.txt
++
++- ranges:
++	Usage: required
++	Value type: <prop-encoded-array>
++	Definition: As specified in designware-pcie.txt
++
++- interrupts:
++	Usage: required
++	Value type: <prop-encoded-array>
++	Definition: MSI interrupt
++
++- interrupt-names:
++	Usage: required
++	Value type: <stringlist>
++	Definition: Should contain "msi"
++
++- #interrupt-cells:
++	Usage: required
++	Value type: <u32>
++	Definition: Should be 1. As specified in designware-pcie.txt
++
++- interrupt-map-mask:
++	Usage: required
++	Value type: <prop-encoded-array>
++	Definition: As specified in designware-pcie.txt
++
++- interrupt-map:
++	Usage: required
++	Value type: <prop-encoded-array>
++	Definition: As specified in designware-pcie.txt
++
++- clocks:
++	Usage: required
++	Value type: <prop-encoded-array>
++	Definition: List of phandle and clock specifier pairs as listed
++		    in clock-names property
++
++- clock-names:
++	Usage: required
++	Value type: <stringlist>
++	Definition: Should contain the following entries
++		    * should be populated for v0 and v1
++		    	- "iface"      Configuration AHB clock
++
++		    * should be populated for v0
++		    	- "core"       Clocks the pcie hw block
++		    	- "phy"        Clocks the pcie PHY block
++
++		    * should be populated for v1
++			- "aux"        Auxiliary (AUX) clock
++		    	- "bus_master" Master AXI clock
++		    	- "bus_slave"  Slave AXI clock
++
++- resets:
++	Usage: required
++	Value type: <prop-encoded-array>
++	Definition: List of phandle and reset specifier pairs as listed
++		    in reset-names property
++
++- reset-names:
++	Usage: required
++	Value type: <stringlist>
++	Definition: Should contain the following entries
++		    * should be populated for v0
++			- "axi"  AXI reset
++			- "ahb"  AHB reset
++			- "por"  POR reset
++			- "pci"  PCI reset
++			- "phy"  PHY reset
++
++		    * should be populated for v1
++			- "core" Core reset
++
++- power-domains:
++	Usage: required (for v1 only)
++	Value type: <prop-encoded-array>
++	Definition: A phandle and power domain specifier pair to the
++		    power domain which is responsible for collapsing
++		    and restoring power to the peripheral
++
++- <name>-supply:
++	Usage: required
++	Value type: <phandle>
++	Definition: List of phandles to the power supply regulator(s)
++		    * should be populated for v0 and v1
++			- "vdda"        core analog power supply
++
++		    * should be populated for v0
++			- "vdda_phy"    analog power supply for PHY
++			- "vdda_refclk" analog power supply for IC which generate
++					reference clock
++
++- phys:
++	Usage: required (for v1 only)
++	Value type: <phandle>
++	Definition: List of phandle(s) as listed in phy-names property
++
++- phy-names:
++	Usage: required (for v1 only)
++	Value type: <stringlist>
++	Definition: Should contain "pciephy"
++
++- <name>-gpio:
++	Usage: optional
++	Value type: <prop-encoded-array>
++	Definition: List of phandle and gpio specifier pairs. Should contain
++		    - "perst"  PCIe endpoint reset signal line
++		    - "pewake" PCIe endpoint wake signal line
++
++- pinctrl-0:
++	Usage: required
++	Value type: <phandle>
++	Definition: List of phandles pointing at a pin(s) configuration
++
++- pinctrl-names
++	Usage: required
++	Value type: <stringlist>
++	Definition: List of names of pinctrl-0 state
++
++* Example for v0
++	pcie0: pci at 1b500000 {
++		compatible = "qcom,pcie-v0";
++		reg = <0x1b500000 0x1000
++		       0x1b502000 0x80
++		       0x1b600000 0x100
++		       0x0ff00000 0x100000>;
++		reg-names = "dbi", "elbi", "parf", "config";
++		device_type = "pci";
++		linux,pci-domain = <0>;
++		bus-range = <0x00 0xff>;
++		num-lanes = <1>;
++		#address-cells = <3>;
++		#size-cells = <2>;
++		ranges = <0x81000000 0 0	  0x0fe00000 0 0x00100000   /* I/O */
++			  0x82000000 0 0x00000000 0x08000000 0 0x07e00000>; /* memory */
++		interrupts = <GIC_SPI 35 IRQ_TYPE_NONE>;
++		interrupt-names = "msi";
++		#interrupt-cells = <1>;
++		interrupt-map-mask = <0 0 0 0x7>;
++		interrupt-map = <0 0 0 1 &intc 0 36 IRQ_TYPE_LEVEL_HIGH>, /* int_a */
++				<0 0 0 2 &intc 0 37 IRQ_TYPE_LEVEL_HIGH>, /* int_b */
++				<0 0 0 3 &intc 0 38 IRQ_TYPE_LEVEL_HIGH>, /* int_c */
++				<0 0 0 4 &intc 0 39 IRQ_TYPE_LEVEL_HIGH>; /* int_d */
++		clocks = <&gcc PCIE_A_CLK>,
++			 <&gcc PCIE_H_CLK>,
++			 <&gcc PCIE_PHY_CLK>;
++		clock-names = "core", "iface", "phy";
++		resets = <&gcc PCIE_ACLK_RESET>,
++			 <&gcc PCIE_HCLK_RESET>,
++			 <&gcc PCIE_POR_RESET>,
++			 <&gcc PCIE_PCI_RESET>,
++			 <&gcc PCIE_PHY_RESET>;
++		reset-names = "axi", "ahb", "por", "pci", "phy";
++	};
++
++* Example for v1
++	pcie0 at fc520000 {
++		compatible = "qcom,pcie-v1";
++		reg = <0xfc520000 0x2000>,
++		      <0xff000000 0x1000>,
++		      <0xff001000 0x1000>,
++		      <0xff002000 0x2000>;
++		reg-names = "parf", "dbi", "elbi", "config";
++		device_type = "pci";
++		linux,pci-domain = <0>;
++		bus-range = <0x00 0xff>;
++		num-lanes = <1>;
++		#address-cells = <3>;
++		#size-cells = <2>;
++		ranges = <0x81000000 0 0          0xff200000 0 0x00100000   /* I/O */
++			  0x82000000 0 0x00300000 0xff300000 0 0x00d00000>; /* memory */
++		interrupts = <GIC_SPI 243 IRQ_TYPE_NONE>;
++		interrupt-names = "msi";
++		#interrupt-cells = <1>;
++		interrupt-map-mask = <0 0 0 0x7>;
++		interrupt-map = <0 0 0 1 &intc 0 244 IRQ_TYPE_LEVEL_HIGH>, /* int_a */
++				<0 0 0 2 &intc 0 245 IRQ_TYPE_LEVEL_HIGH>, /* int_b */
++				<0 0 0 3 &intc 0 247 IRQ_TYPE_LEVEL_HIGH>, /* int_c */
++				<0 0 0 4 &intc 0 248 IRQ_TYPE_LEVEL_HIGH>; /* int_d */
++		clocks = <&gcc GCC_PCIE_0_CFG_AHB_CLK>,
++			 <&gcc GCC_PCIE_0_MSTR_AXI_CLK>,
++			 <&gcc GCC_PCIE_0_SLV_AXI_CLK>,
++			 <&gcc GCC_PCIE_0_AUX_CLK>;
++		clock-names = "iface", "master_bus", "slave_bus", "aux";
++		resets = <&gcc GCC_PCIE_0_BCR>;
++		reset-names = "core";
++		power-domains = <&gcc PCIE0_GDSC>;
++		vdda-supply = <&pma8084_l3>;
++		phys = <&pciephy0>;
++		phy-names = "pciephy";
++		perst-gpio = <&tlmm 70 GPIO_ACTIVE_LOW>;
++		pinctrl-0 = <&pcie0_pins_default>;
++		pinctrl-names = "default";
++	};
diff --git a/target/linux/ipq806x/patches-3.18/111-PCI-qcom-Add-Qualcomm-PCIe-controller-driver.patch b/target/linux/ipq806x/patches-3.18/111-PCI-qcom-Add-Qualcomm-PCIe-controller-driver.patch
new file mode 100644
index 0000000..a0c9c7c
--- /dev/null
+++ b/target/linux/ipq806x/patches-3.18/111-PCI-qcom-Add-Qualcomm-PCIe-controller-driver.patch
@@ -0,0 +1,753 @@
+Content-Type: text/plain; charset="utf-8"
+MIME-Version: 1.0
+Content-Transfer-Encoding: 7bit
+Subject: [v2,4/5] PCI: qcom: Add Qualcomm PCIe controller driver
+From: Stanimir Varbanov <svarbanov at mm-sol.com>
+X-Patchwork-Id: 6326161
+Message-Id: <1430743338-10441-5-git-send-email-svarbanov at mm-sol.com>
+To: Rob Herring <robh+dt at kernel.org>, Kumar Gala <galak at codeaurora.org>,
+	Mark Rutland <mark.rutland at arm.com>,
+	Grant Likely <grant.likely at linaro.org>,
+	Bjorn Helgaas <bhelgaas at google.com>,
+	Kishon Vijay Abraham I <kishon at ti.com>,
+	Russell King <linux at arm.linux.org.uk>, Arnd Bergmann <arnd at arndb.de>
+Cc: linux-arm-msm at vger.kernel.org, linux-kernel at vger.kernel.org,
+	linux-arm-kernel at lists.infradead.org, devicetree at vger.kernel.org,
+	linux-pci at vger.kernel.org, Mathieu Olivari <mathieu at codeaurora.org>,
+	Srinivas Kandagatla <srinivas.kandagatla at linaro.org>,
+	Stanimir Varbanov <svarbanov at mm-sol.com>
+Date: Mon,  4 May 2015 15:42:17 +0300
+
+The PCIe driver reuse the Designware common code for host
+and MSI initialization, and also program the Qualcomm
+application specific registers.
+
+Signed-off-by: Stanimir Varbanov <svarbanov at mm-sol.com>
+
+---
+MAINTAINERS                  |    7 +
+ drivers/pci/host/Kconfig     |    9 +
+ drivers/pci/host/Makefile    |    1 +
+ drivers/pci/host/pcie-qcom.c |  677 ++++++++++++++++++++++++++++++++++++++++++
+ 4 files changed, 694 insertions(+), 0 deletions(-)
+ create mode 100644 drivers/pci/host/pcie-qcom.c
+
+--- a/MAINTAINERS
++++ b/MAINTAINERS
+@@ -7127,6 +7127,13 @@ L:	linux-pci at vger.kernel.org
+ S:	Maintained
+ F:	drivers/pci/host/*spear*
+ 
++PCIE DRIVER FOR QUALCOMM MSM
++M:	Stanimir Varbanov <svarbanov at mm-sol.com>
++L:	linux-pci at vger.kernel.org
++L:	linux-arm-msm at vger.kernel.org
++S:	Maintained
++F:	drivers/pci/host/*qcom*
++
+ PCMCIA SUBSYSTEM
+ P:	Linux PCMCIA Team
+ L:	linux-pcmcia at lists.infradead.org
+--- a/drivers/pci/host/Kconfig
++++ b/drivers/pci/host/Kconfig
+@@ -91,4 +91,13 @@ config PCI_XGENE
+ 	  There are 5 internal PCIe ports available. Each port is GEN3 capable
+ 	  and have varied lanes from x1 to x8.
+ 
++config PCIE_QCOM
++	bool "Qualcomm PCIe controller"
++	depends on ARCH_QCOM && OF || (ARM && COMPILE_TEST)
++	select PCIE_DW
++	select PCIEPORTBUS
++	help
++	  Say Y here to enable PCIe controller support on Qualcomm SoCs. The
++	  PCIe controller use Designware core plus Qualcomm specific hardware
++	  wrappers.
+ endmenu
+--- /dev/null
++++ b/drivers/pci/host/pcie-qcom.c
+@@ -0,0 +1,677 @@
++/*
++ * Copyright (c) 2014, The Linux Foundation. All rights reserved.
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License version 2 and
++ * only version 2 as published by the Free Software Foundation.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++ * GNU General Public License for more details.
++ */
++
++#include <linux/clk.h>
++#include <linux/delay.h>
++#include <linux/gpio.h>
++#include <linux/interrupt.h>
++#include <linux/io.h>
++#include <linux/kernel.h>
++#include <linux/module.h>
++#include <linux/of_gpio.h>
++#include <linux/pci.h>
++#include <linux/platform_device.h>
++#include <linux/phy/phy.h>
++#include <linux/regulator/consumer.h>
++#include <linux/reset.h>
++#include <linux/slab.h>
++#include <linux/types.h>
++
++#include "pcie-designware.h"
++
++#define PCIE20_PARF_PHY_CTRL			0x40
++#define PCIE20_PARF_PHY_REFCLK			0x4C
++#define PCIE20_PARF_DBI_BASE_ADDR		0x168
++#define PCIE20_PARF_SLV_ADDR_SPACE_SIZE		0x16c
++#define PCIE20_PARF_AXI_MSTR_WR_ADDR_HALT	0x178
++
++#define PCIE20_ELBI_SYS_CTRL			0x04
++#define PCIE20_ELBI_SYS_STTS			0x08
++#define XMLH_LINK_UP				BIT(10)
++
++#define PCIE20_CAP				0x70
++#define PCIE20_CAP_LINKCTRLSTATUS		(PCIE20_CAP + 0x10)
++
++#define PERST_DELAY_MIN_US			1000
++#define PERST_DELAY_MAX_US			1005
++
++#define LINKUP_DELAY_MIN_US			5000
++#define LINKUP_DELAY_MAX_US			5100
++#define LINKUP_RETRIES_COUNT			20
++
++#define PCIE_V0					0	/* apq8064 */
++#define PCIE_V1					1	/* apq8084 */
++
++struct qcom_pcie_resources_v0 {
++	struct clk *iface_clk;
++	struct clk *core_clk;
++	struct clk *phy_clk;
++	struct reset_control *pci_reset;
++	struct reset_control *axi_reset;
++	struct reset_control *ahb_reset;
++	struct reset_control *por_reset;
++	struct reset_control *phy_reset;
++	struct regulator *vdda;
++	struct regulator *vdda_phy;
++	struct regulator *vdda_refclk;
++};
++
++struct qcom_pcie_resources_v1 {
++	struct clk *iface;
++	struct clk *aux;
++	struct clk *master_bus;
++	struct clk *slave_bus;
++	struct reset_control *core;
++	struct regulator *vdda;
++};
++
++union pcie_resources {
++	struct qcom_pcie_resources_v0 v0;
++	struct qcom_pcie_resources_v1 v1;
++};
++
++struct qcom_pcie {
++	struct pcie_port pp;
++	struct device *dev;
++	union pcie_resources res;
++	void __iomem *parf;
++	void __iomem *dbi;
++	void __iomem *elbi;
++	struct phy *phy;
++	struct gpio_desc *reset;
++	unsigned int version;
++};
++
++#define to_qcom_pcie(x)		container_of(x, struct qcom_pcie, pp)
++
++static inline void
++writel_masked(void __iomem *addr, u32 clear_mask, u32 set_mask)
++{
++	u32 val = readl(addr);
++
++	val &= ~clear_mask;
++	val |= set_mask;
++	writel(val, addr);
++}
++
++static void qcom_ep_reset_assert_deassert(struct qcom_pcie *pcie, int assert)
++{
++	int val, active_low;
++
++	if (IS_ERR_OR_NULL(pcie->reset))
++		return;
++
++	active_low = gpiod_is_active_low(pcie->reset);
++
++	if (assert)
++		val = !!active_low;
++	else
++		val = !active_low;
++
++	gpiod_set_value(pcie->reset, val);
++
++	usleep_range(PERST_DELAY_MIN_US, PERST_DELAY_MAX_US);
++}
++
++static void qcom_ep_reset_assert(struct qcom_pcie *pcie)
++{
++	qcom_ep_reset_assert_deassert(pcie, 1);
++}
++
++static void qcom_ep_reset_deassert(struct qcom_pcie *pcie)
++{
++	qcom_ep_reset_assert_deassert(pcie, 0);
++}
++
++static irqreturn_t qcom_pcie_msi_irq_handler(int irq, void *arg)
++{
++	struct pcie_port *pp = arg;
++
++	return dw_handle_msi_irq(pp);
++}
++
++static int qcom_pcie_link_up(struct pcie_port *pp)
++{
++	struct qcom_pcie *pcie = to_qcom_pcie(pp);
++	u32 val = readl(pcie->dbi + PCIE20_CAP_LINKCTRLSTATUS);
++
++	return val & BIT(29) ? 1 : 0;
++}
++
++static void qcom_pcie_disable_resources_v0(struct qcom_pcie *pcie)
++{
++	struct qcom_pcie_resources_v0 *res = &pcie->res.v0;
++
++	reset_control_assert(res->pci_reset);
++	reset_control_assert(res->axi_reset);
++	reset_control_assert(res->ahb_reset);
++	reset_control_assert(res->por_reset);
++	reset_control_assert(res->pci_reset);
++	clk_disable_unprepare(res->iface_clk);
++	clk_disable_unprepare(res->core_clk);
++	clk_disable_unprepare(res->phy_clk);
++	regulator_disable(res->vdda);
++	regulator_disable(res->vdda_phy);
++	regulator_disable(res->vdda_refclk);
++}
++
++static void qcom_pcie_disable_resources_v1(struct qcom_pcie *pcie)
++{
++	struct qcom_pcie_resources_v1 *res = &pcie->res.v1;
++
++	reset_control_assert(res->core);
++	clk_disable_unprepare(res->slave_bus);
++	clk_disable_unprepare(res->master_bus);
++	clk_disable_unprepare(res->iface);
++	clk_disable_unprepare(res->aux);
++	regulator_disable(res->vdda);
++}
++
++static int qcom_pcie_enable_resources_v0(struct qcom_pcie *pcie)
++{
++	struct qcom_pcie_resources_v0 *res = &pcie->res.v0;
++	struct device *dev = pcie->dev;
++	int ret;
++
++	ret = regulator_enable(res->vdda);
++	if (ret) {
++		dev_err(dev, "cannot enable vdda regulator\n");
++		return ret;
++	}
++
++	ret = regulator_enable(res->vdda_refclk);
++	if (ret) {
++		dev_err(dev, "cannot enable vdda_refclk regulator\n");
++		goto err_refclk;
++	}
++
++	ret = regulator_enable(res->vdda_phy);
++	if (ret) {
++		dev_err(dev, "cannot enable vdda_phy regulator\n");
++		goto err_vdda_phy;
++	}
++
++	ret = clk_prepare_enable(res->iface_clk);
++	if (ret) {
++		dev_err(dev, "cannot prepare/enable iface clock\n");
++		goto err_iface;
++	}
++
++	ret = clk_prepare_enable(res->core_clk);
++	if (ret) {
++		dev_err(dev, "cannot prepare/enable core clock\n");
++		goto err_clk_core;
++	}
++
++	ret = clk_prepare_enable(res->phy_clk);
++	if (ret) {
++		dev_err(dev, "cannot prepare/enable phy clock\n");
++		goto err_clk_phy;
++	}
++
++	ret = reset_control_deassert(res->ahb_reset);
++	if (ret) {
++		dev_err(dev, "cannot deassert ahb reset\n");
++		goto err_reset_ahb;
++	}
++
++	return 0;
++
++err_reset_ahb:
++	clk_disable_unprepare(res->phy_clk);
++err_clk_phy:
++	clk_disable_unprepare(res->core_clk);
++err_clk_core:
++	clk_disable_unprepare(res->iface_clk);
++err_iface:
++	regulator_disable(res->vdda_phy);
++err_vdda_phy:
++	regulator_disable(res->vdda_refclk);
++err_refclk:
++	regulator_disable(res->vdda);
++	return ret;
++}
++
++static int qcom_pcie_enable_resources_v1(struct qcom_pcie *pcie)
++{
++	struct qcom_pcie_resources_v1 *res = &pcie->res.v1;
++	struct device *dev = pcie->dev;
++	int ret;
++
++	ret = reset_control_deassert(res->core);
++	if (ret) {
++		dev_err(dev, "cannot deassert core reset\n");
++		return ret;
++	}
++
++	ret = clk_prepare_enable(res->aux);
++	if (ret) {
++		dev_err(dev, "cannot prepare/enable aux clock\n");
++		goto err_res;
++	}
++
++	ret = clk_prepare_enable(res->iface);
++	if (ret) {
++		dev_err(dev, "cannot prepare/enable iface clock\n");
++		goto err_aux;
++	}
++
++	ret = clk_prepare_enable(res->master_bus);
++	if (ret) {
++		dev_err(dev, "cannot prepare/enable master_bus clock\n");
++		goto err_iface;
++	}
++
++	ret = clk_prepare_enable(res->slave_bus);
++	if (ret) {
++		dev_err(dev, "cannot prepare/enable slave_bus clock\n");
++		goto err_master;
++	}
++
++	ret = regulator_enable(res->vdda);
++	if (ret) {
++		dev_err(dev, "cannot enable vdda regulator\n");
++		goto err_slave;
++	}
++
++	return 0;
++
++err_slave:
++	clk_disable_unprepare(res->slave_bus);
++err_master:
++	clk_disable_unprepare(res->master_bus);
++err_iface:
++	clk_disable_unprepare(res->iface);
++err_aux:
++	clk_disable_unprepare(res->aux);
++err_res:
++	reset_control_assert(res->core);
++
++	return ret;
++}
++
++static int qcom_pcie_get_resources_v0(struct qcom_pcie *pcie)
++{
++	struct qcom_pcie_resources_v0 *res = &pcie->res.v0;
++	struct device *dev = pcie->dev;
++
++	res->vdda = devm_regulator_get(dev, "vdda");
++	if (IS_ERR(res->vdda))
++		return PTR_ERR(res->vdda);
++
++	res->vdda_phy = devm_regulator_get(dev, "vdda_phy");
++	if (IS_ERR(res->vdda_phy))
++		return PTR_ERR(res->vdda_phy);
++
++	res->vdda_refclk = devm_regulator_get(dev, "vdda_refclk");
++	if (IS_ERR(res->vdda_refclk))
++		return PTR_ERR(res->vdda_refclk);
++
++	res->iface_clk = devm_clk_get(dev, "iface");
++	if (IS_ERR(res->iface_clk))
++		return PTR_ERR(res->iface_clk);
++
++	res->core_clk = devm_clk_get(dev, "core");
++	if (IS_ERR(res->core_clk))
++		return PTR_ERR(res->core_clk);
++
++	res->phy_clk = devm_clk_get(dev, "phy");
++	if (IS_ERR(res->phy_clk))
++		return PTR_ERR(res->phy_clk);
++
++	res->pci_reset = devm_reset_control_get(dev, "pci");
++	if (IS_ERR(res->pci_reset))
++		return PTR_ERR(res->pci_reset);
++
++	res->axi_reset = devm_reset_control_get(dev, "axi");
++	if (IS_ERR(res->axi_reset))
++		return PTR_ERR(res->axi_reset);
++
++	res->ahb_reset = devm_reset_control_get(dev, "ahb");
++	if (IS_ERR(res->ahb_reset))
++		return PTR_ERR(res->ahb_reset);
++
++	res->por_reset = devm_reset_control_get(dev, "por");
++	if (IS_ERR(res->por_reset))
++		return PTR_ERR(res->por_reset);
++
++	res->phy_reset = devm_reset_control_get(dev, "phy");
++	if (IS_ERR(res->phy_reset))
++		return PTR_ERR(res->phy_reset);
++
++	return 0;
++}
++
++static int qcom_pcie_get_resources_v1(struct qcom_pcie *pcie)
++{
++	struct qcom_pcie_resources_v1 *res = &pcie->res.v1;
++	struct device *dev = pcie->dev;
++
++	res->vdda = devm_regulator_get(dev, "vdda");
++	if (IS_ERR(res->vdda))
++		return PTR_ERR(res->vdda);
++
++	res->iface = devm_clk_get(dev, "iface");
++	if (IS_ERR(res->iface))
++		return PTR_ERR(res->iface);
++
++	res->aux = devm_clk_get(dev, "aux");
++	if (IS_ERR(res->aux) && PTR_ERR(res->aux) == -EPROBE_DEFER)
++		return -EPROBE_DEFER;
++	else if (IS_ERR(res->aux))
++		res->aux = NULL;
++
++	res->master_bus = devm_clk_get(dev, "master_bus");
++	if (IS_ERR(res->master_bus))
++		return PTR_ERR(res->master_bus);
++
++	res->slave_bus = devm_clk_get(dev, "slave_bus");
++	if (IS_ERR(res->slave_bus))
++		return PTR_ERR(res->slave_bus);
++
++	res->core = devm_reset_control_get(dev, "core");
++	if (IS_ERR(res->core))
++		return PTR_ERR(res->core);
++
++	return 0;
++}
++
++static int qcom_pcie_enable_link_training(struct pcie_port *pp)
++{
++	struct qcom_pcie *pcie = to_qcom_pcie(pp);
++	struct device *dev = pp->dev;
++	int retries;
++	u32 val;
++
++	/* enable link training */
++	writel_masked(pcie->elbi + PCIE20_ELBI_SYS_CTRL, 0, BIT(0));
++
++	/* wait for up to 100ms for the link to come up */
++	retries = LINKUP_RETRIES_COUNT;
++	do {
++		val = readl(pcie->elbi + PCIE20_ELBI_SYS_STTS);
++		if (val & XMLH_LINK_UP)
++			break;
++		usleep_range(LINKUP_DELAY_MIN_US, LINKUP_DELAY_MAX_US);
++	} while (retries--);
++
++	if (retries < 0 || !dw_pcie_link_up(pp)) {
++		dev_err(dev, "link initialization failed\n");
++		return -ENXIO;
++	}
++
++	return 0;
++}
++
++static void qcom_pcie_host_init_v1(struct pcie_port *pp)
++{
++	struct qcom_pcie *pcie = to_qcom_pcie(pp);
++	int ret;
++
++	qcom_ep_reset_assert(pcie);
++
++	ret = qcom_pcie_enable_resources_v1(pcie);
++	if (ret)
++		return;
++
++	/* change DBI base address */
++	writel(0, pcie->parf + PCIE20_PARF_DBI_BASE_ADDR);
++
++	if (IS_ENABLED(CONFIG_PCI_MSI))
++		writel_masked(pcie->parf + PCIE20_PARF_AXI_MSTR_WR_ADDR_HALT,
++			      0, BIT(31));
++
++	ret = phy_init(pcie->phy);
++	if (ret)
++		goto err_res;
++
++	ret = phy_power_on(pcie->phy);
++	if (ret)
++		goto err_phy;
++
++	dw_pcie_setup_rc(pp);
++
++	if (IS_ENABLED(CONFIG_PCI_MSI))
++		dw_pcie_msi_init(pp);
++
++	qcom_ep_reset_deassert(pcie);
++
++	ret = qcom_pcie_enable_link_training(pp);
++	if (ret)
++		goto err;
++
++	return;
++
++err:
++	qcom_ep_reset_assert(pcie);
++	phy_power_off(pcie->phy);
++err_phy:
++	phy_exit(pcie->phy);
++err_res:
++	qcom_pcie_disable_resources_v1(pcie);
++}
++
++static void qcom_pcie_host_init_v0(struct pcie_port *pp)
++{
++	struct qcom_pcie *pcie = to_qcom_pcie(pp);
++	struct qcom_pcie_resources_v0 *res = &pcie->res.v0;
++	struct device *dev = pcie->dev;
++	int ret;
++
++	qcom_ep_reset_assert(pcie);
++
++	ret = qcom_pcie_enable_resources_v0(pcie);
++	if (ret)
++		return;
++
++	writel_masked(pcie->parf + PCIE20_PARF_PHY_CTRL, BIT(0), 0);
++
++	/* enable external reference clock */
++	writel_masked(pcie->parf + PCIE20_PARF_PHY_REFCLK, 0, BIT(16));
++
++	ret = reset_control_deassert(res->phy_reset);
++	if (ret) {
++		dev_err(dev, "cannot deassert phy reset\n");
++		return;
++	}
++
++	ret = reset_control_deassert(res->pci_reset);
++	if (ret) {
++		dev_err(dev, "cannot deassert pci reset\n");
++		return;
++	}
++
++	ret = reset_control_deassert(res->por_reset);
++	if (ret) {
++		dev_err(dev, "cannot deassert por reset\n");
++		return;
++	}
++
++	ret = reset_control_deassert(res->axi_reset);
++	if (ret) {
++		dev_err(dev, "cannot deassert axi reset\n");
++		return;
++	}
++
++	/* wait 150ms for clock acquisition */
++	usleep_range(10000, 15000);
++
++	dw_pcie_setup_rc(pp);
++
++	if (IS_ENABLED(CONFIG_PCI_MSI))
++		dw_pcie_msi_init(pp);
++
++	qcom_ep_reset_deassert(pcie);
++
++	ret = qcom_pcie_enable_link_training(pp);
++	if (ret)
++		goto err;
++
++	return;
++err:
++	qcom_ep_reset_assert(pcie);
++	qcom_pcie_disable_resources_v0(pcie);
++}
++
++static void qcom_pcie_host_init(struct pcie_port *pp)
++{
++	struct qcom_pcie *pcie = to_qcom_pcie(pp);
++
++	if (pcie->version == PCIE_V0)
++		return qcom_pcie_host_init_v0(pp);
++	else
++		return qcom_pcie_host_init_v1(pp);
++}
++
++static int
++qcom_pcie_rd_own_conf(struct pcie_port *pp, int where, int size, u32 *val)
++{
++	/* the device class is not reported correctly from the register */
++	if (where == PCI_CLASS_REVISION && size == 4) {
++		*val = readl(pp->dbi_base + PCI_CLASS_REVISION);
++		*val &= ~(0xffff << 16);
++		*val |= PCI_CLASS_BRIDGE_PCI << 16;
++		return PCIBIOS_SUCCESSFUL;
++	}
++
++	return dw_pcie_cfg_read(pp->dbi_base + (where & ~0x3), where,
++				size, val);
++}
++
++static struct pcie_host_ops qcom_pcie_ops = {
++	.link_up = qcom_pcie_link_up,
++	.host_init = qcom_pcie_host_init,
++	.rd_own_conf = qcom_pcie_rd_own_conf,
++};
++
++static const struct of_device_id qcom_pcie_match[] = {
++	{ .compatible = "qcom,pcie-v0", .data = (void *)PCIE_V0 },
++	{ .compatible = "qcom,pcie-v1", .data = (void *)PCIE_V1 },
++	{ }
++};
++
++static int qcom_pcie_probe(struct platform_device *pdev)
++{
++	struct device *dev = &pdev->dev;
++	const struct of_device_id *match;
++	struct resource *res;
++	struct qcom_pcie *pcie;
++	struct pcie_port *pp;
++	int ret;
++
++	match = of_match_node(qcom_pcie_match, dev->of_node);
++	if (!match)
++		return -ENXIO;
++
++	pcie = devm_kzalloc(dev, sizeof(*pcie), GFP_KERNEL);
++	if (!pcie)
++		return -ENOMEM;
++
++	pcie->version = (unsigned int)match->data;
++
++	pcie->reset = devm_gpiod_get_optional(dev, "perst");
++	if (IS_ERR(pcie->reset) && PTR_ERR(pcie->reset) == -EPROBE_DEFER)
++		return PTR_ERR(pcie->reset);
++
++	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "parf");
++	pcie->parf = devm_ioremap_resource(dev, res);
++	if (IS_ERR(pcie->parf))
++		return PTR_ERR(pcie->parf);
++
++	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dbi");
++	pcie->dbi = devm_ioremap_resource(dev, res);
++	if (IS_ERR(pcie->dbi))
++		return PTR_ERR(pcie->dbi);
++
++	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "elbi");
++	pcie->elbi = devm_ioremap_resource(dev, res);
++	if (IS_ERR(pcie->elbi))
++		return PTR_ERR(pcie->elbi);
++
++	pcie->phy = devm_phy_optional_get(dev, "pciephy");
++	if (IS_ERR(pcie->phy))
++		return PTR_ERR(pcie->phy);
++
++	pcie->dev = dev;
++
++	if (pcie->version == PCIE_V0)
++		ret = qcom_pcie_get_resources_v0(pcie);
++	else
++		ret = qcom_pcie_get_resources_v1(pcie);
++
++	if (ret)
++		return ret;
++
++	pp = &pcie->pp;
++	pp->dev = dev;
++	pp->dbi_base = pcie->dbi;
++	pp->root_bus_nr = -1;
++	pp->ops = &qcom_pcie_ops;
++
++	if (IS_ENABLED(CONFIG_PCI_MSI)) {
++		pp->msi_irq = platform_get_irq_byname(pdev, "msi");
++		if (pp->msi_irq < 0) {
++			dev_err(dev, "cannot get msi irq\n");
++			return pp->msi_irq;
++		}
++
++		ret = devm_request_irq(dev, pp->msi_irq,
++				       qcom_pcie_msi_irq_handler,
++				       IRQF_SHARED, "qcom-pcie-msi", pp);
++		if (ret) {
++			dev_err(dev, "cannot request msi irq\n");
++			return ret;
++		}
++	}
++
++	ret = dw_pcie_host_init(pp);
++	if (ret) {
++		dev_err(dev, "cannot initialize host\n");
++		return ret;
++	}
++
++	platform_set_drvdata(pdev, pcie);
++
++	return 0;
++}
++
++static int qcom_pcie_remove(struct platform_device *pdev)
++{
++	struct qcom_pcie *pcie = platform_get_drvdata(pdev);
++
++	qcom_ep_reset_assert(pcie);
++	phy_power_off(pcie->phy);
++	phy_exit(pcie->phy);
++	if (pcie->version == PCIE_V0)
++		qcom_pcie_disable_resources_v0(pcie);
++	else
++		qcom_pcie_disable_resources_v1(pcie);
++
++	return 0;
++}
++
++static struct platform_driver qcom_pcie_driver = {
++	.probe = qcom_pcie_probe,
++	.remove = qcom_pcie_remove,
++	.driver = {
++		.name = "qcom-pcie",
++		.of_match_table = qcom_pcie_match,
++	},
++};
++
++module_platform_driver(qcom_pcie_driver);
++
++MODULE_AUTHOR("Stanimir Varbanov <svarbanov at mm-sol.com>");
++MODULE_DESCRIPTION("Qualcomm PCIe root complex driver");
++MODULE_LICENSE("GPL v2");
++MODULE_ALIAS("platform:qcom-pcie");
+--- a/drivers/pci/host/Makefile
++++ b/drivers/pci/host/Makefile
+@@ -11,3 +11,4 @@ obj-$(CONFIG_PCIE_SPEAR13XX) += pcie-spe
+ obj-$(CONFIG_PCI_KEYSTONE) += pci-keystone-dw.o pci-keystone.o
+ obj-$(CONFIG_PCIE_XILINX) += pcie-xilinx.o
+ obj-$(CONFIG_PCI_XGENE) += pci-xgene.o
++obj-$(CONFIG_PCIE_QCOM) += pcie-qcom.o
diff --git a/target/linux/ipq806x/patches-3.18/112-ARM-dts-qcom-add-pcie-nodes-to-ipq806x-platforms.patch b/target/linux/ipq806x/patches-3.18/112-ARM-dts-qcom-add-pcie-nodes-to-ipq806x-platforms.patch
new file mode 100644
index 0000000..2613dff
--- /dev/null
+++ b/target/linux/ipq806x/patches-3.18/112-ARM-dts-qcom-add-pcie-nodes-to-ipq806x-platforms.patch
@@ -0,0 +1,268 @@
+From 5b40516b2f5fb9b2a7d6d3e2e924f12ec9d183a8 Mon Sep 17 00:00:00 2001
+From: Mathieu Olivari <mathieu at codeaurora.org>
+Date: Tue, 21 Apr 2015 19:01:42 -0700
+Subject: [PATCH 8/9] ARM: dts: qcom: add pcie nodes to ipq806x platforms
+
+qcom-pcie driver now supports version 0 of the controller. This change
+adds the corresponding entries to the IPQ806x dtsi file and
+corresponding platform (AP148).
+
+Signed-off-by: Mathieu Olivari <mathieu at codeaurora.org>
+---
+ arch/arm/boot/dts/qcom-ipq8064-ap148.dts |  30 ++++++++
+ arch/arm/boot/dts/qcom-ipq8064.dtsi      | 124 +++++++++++++++++++++++++++++++
+ 2 files changed, 154 insertions(+)
+
+--- a/arch/arm/boot/dts/qcom-ipq8064-ap148.dts
++++ b/arch/arm/boot/dts/qcom-ipq8064-ap148.dts
+@@ -30,6 +30,22 @@
+ 				bias-disable;
+ 			};
+ 
++			pcie1_pins: pcie1_pinmux {
++				mux {
++					pins = "gpio3";
++					drive-strength = <2>;
++					bias-disable;
++				};
++			};
++
++			pcie2_pins: pcie2_pinmux {
++				mux {
++					pins = "gpio48";
++					drive-strength = <2>;
++					bias-disable;
++				};
++			};
++
+ 			spi_pins: spi_pins {
+ 				mux {
+ 					pins = "gpio18", "gpio19", "gpio21";
+@@ -136,5 +152,19 @@
+ 		usb30 at 1 {
+ 			status = "ok";
+ 		};
++
++		pcie0: pci at 1b500000 {
++			status = "ok";
++			reset-gpio = <&qcom_pinmux 3 0>;
++			pinctrl-0 = <&pcie1_pins>;
++			pinctrl-names = "default";
++		};
++
++		pcie1: pci at 1b700000 {
++			status = "ok";
++			reset-gpio = <&qcom_pinmux 48 0>;
++			pinctrl-0 = <&pcie2_pins>;
++			pinctrl-names = "default";
++		};
+ 	};
+ };
+--- a/arch/arm/boot/dts/qcom-ipq8064-db149.dts
++++ b/arch/arm/boot/dts/qcom-ipq8064-db149.dts
+@@ -37,6 +37,30 @@
+ 				bias-disable;
+ 			};
+ 
++			pcie1_pins: pcie1_pinmux {
++				mux {
++					pins = "gpio3";
++					drive-strength = <2>;
++					bias-disable;
++				};
++			};
++
++			pcie2_pins: pcie2_pinmux {
++				mux {
++					pins = "gpio48";
++					drive-strength = <2>;
++					bias-disable;
++				};
++			};
++
++			pcie3_pins: pcie3_pinmux {
++				mux {
++					pins = "gpio63";
++					drive-strength = <2>;
++					bias-disable;
++				};
++			};
++
+ 			spi_pins: spi_pins {
+ 				mux {
+ 					pins = "gpio18", "gpio19", "gpio21";
+@@ -153,6 +177,27 @@
+ 			status = "ok";
+ 		};
+ 
++		pcie0: pci at 1b500000 {
++			status = "ok";
++			reset-gpio = <&qcom_pinmux 3 0>;
++			pinctrl-0 = <&pcie1_pins>;
++			pinctrl-names = "default";
++		};
++
++		pcie1: pci at 1b700000 {
++			status = "ok";
++			reset-gpio = <&qcom_pinmux 48 0>;
++			pinctrl-0 = <&pcie2_pins>;
++			pinctrl-names = "default";
++		};
++
++		pcie2: pci at 1b900000 {
++			status = "ok";
++			reset-gpio = <&qcom_pinmux 63 0>;
++			pinctrl-0 = <&pcie3_pins>;
++			pinctrl-names = "default";
++		};
++
+ 		mdio0: mdio {
+ 			compatible = "virtual,mdio-gpio";
+ 			#address-cells = <1>;
+--- a/arch/arm/boot/dts/qcom-ipq8064.dtsi
++++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi
+@@ -3,6 +3,7 @@
+ #include "skeleton.dtsi"
+ #include <dt-bindings/clock/qcom,gcc-ipq806x.h>
+ #include <dt-bindings/soc/qcom,gsbi.h>
++#include <dt-bindings/reset/qcom,gcc-ipq806x.h>
+ 
+ / {
+ 	model = "Qualcomm IPQ8064";
+@@ -306,6 +307,129 @@
+ 			#reset-cells = <1>;
+ 		};
+ 
++		pcie0: pci at 1b500000 {
++			compatible = "qcom,pcie-v0";
++			reg = <0x1b500000 0x1000
++			       0x1b502000 0x80
++			       0x1b600000 0x100
++			       0x0ff00000 0x100000>;
++			reg-names = "dbi", "elbi", "parf", "config";
++			device_type = "pci";
++			linux,pci-domain = <0>;
++			bus-range = <0x00 0xff>;
++			num-lanes = <1>;
++			#address-cells = <3>;
++			#size-cells = <2>;
++
++			ranges = <0x81000000 0 0	  0x0fe00000 0 0x00100000   /* downstream I/O */
++				  0x82000000 0 0x00000000 0x08000000 0 0x07e00000>; /* non-prefetchable memory */
++
++			interrupts = <GIC_SPI 35 IRQ_TYPE_NONE>;
++			interrupt-names = "msi";
++			#interrupt-cells = <1>;
++			interrupt-map-mask = <0 0 0 0x7>;
++			interrupt-map = <0 0 0 1 &intc 0 36 IRQ_TYPE_LEVEL_HIGH>, /* int_a */
++					<0 0 0 2 &intc 0 37 IRQ_TYPE_LEVEL_HIGH>, /* int_b */
++					<0 0 0 3 &intc 0 38 IRQ_TYPE_LEVEL_HIGH>, /* int_c */
++					<0 0 0 4 &intc 0 39 IRQ_TYPE_LEVEL_HIGH>; /* int_d */
++
++			clocks = <&gcc PCIE_A_CLK>,
++				 <&gcc PCIE_H_CLK>,
++				 <&gcc PCIE_PHY_CLK>;
++			clock-names = "core", "iface", "phy";
++
++			resets = <&gcc PCIE_ACLK_RESET>,
++				 <&gcc PCIE_HCLK_RESET>,
++				 <&gcc PCIE_POR_RESET>,
++				 <&gcc PCIE_PCI_RESET>,
++				 <&gcc PCIE_PHY_RESET>;
++			reset-names = "axi", "ahb", "por", "pci", "phy";
++
++			status = "disabled";
++		};
++
++		pcie1: pci at 1b700000 {
++			compatible = "qcom,pcie-v0";
++			reg = <0x1b700000 0x1000
++			       0x1b702000 0x80
++			       0x1b800000 0x100
++			       0x31f00000 0x100000>;
++			reg-names = "dbi", "elbi", "parf", "config";
++			device_type = "pci";
++			linux,pci-domain = <1>;
++			bus-range = <0x00 0xff>;
++			num-lanes = <1>;
++			#address-cells = <3>;
++			#size-cells = <2>;
++
++			ranges = <0x81000000 0 0	  0x31e00000 0 0x00100000   /* downstream I/O */
++				  0x82000000 0 0x00000000 0x2e000000 0 0x03e00000>; /* non-prefetchable memory */
++
++			interrupts = <GIC_SPI 57 IRQ_TYPE_NONE>;
++			interrupt-names = "msi";
++			#interrupt-cells = <1>;
++			interrupt-map-mask = <0 0 0 0x7>;
++			interrupt-map = <0 0 0 1 &intc 0 58 IRQ_TYPE_LEVEL_HIGH>, /* int_a */
++					<0 0 0 2 &intc 0 59 IRQ_TYPE_LEVEL_HIGH>, /* int_b */
++					<0 0 0 3 &intc 0 60 IRQ_TYPE_LEVEL_HIGH>, /* int_c */
++					<0 0 0 4 &intc 0 61 IRQ_TYPE_LEVEL_HIGH>; /* int_d */
++
++			clocks = <&gcc PCIE_1_A_CLK>,
++				 <&gcc PCIE_1_H_CLK>,
++				 <&gcc PCIE_1_PHY_CLK>;
++			clock-names = "core", "iface", "phy";
++
++			resets = <&gcc PCIE_1_ACLK_RESET>,
++				 <&gcc PCIE_1_HCLK_RESET>,
++				 <&gcc PCIE_1_POR_RESET>,
++				 <&gcc PCIE_1_PCI_RESET>,
++				 <&gcc PCIE_1_PHY_RESET>;
++			reset-names = "axi", "ahb", "por", "pci", "phy";
++
++			status = "disabled";
++		};
++
++		pcie2: pci at 1b900000 {
++			compatible = "qcom,pcie-v0";
++			reg = <0x1b900000 0x1000
++			       0x1b902000 0x80
++			       0x1ba00000 0x100
++			       0x35f00000 0x100000>;
++			reg-names = "dbi", "elbi", "parf", "config";
++			device_type = "pci";
++			linux,pci-domain = <2>;
++			bus-range = <0x00 0xff>;
++			num-lanes = <1>;
++			#address-cells = <3>;
++			#size-cells = <2>;
++
++			ranges = <0x81000000 0 0	  0x35e00000 0 0x00100000   /* downstream I/O */
++				  0x82000000 0 0x00000000 0x32000000 0 0x03e00000>; /* non-prefetchable memory */
++
++			interrupts = <GIC_SPI 71 IRQ_TYPE_NONE>;
++			interrupt-names = "msi";
++			#interrupt-cells = <1>;
++			interrupt-map-mask = <0 0 0 0x7>;
++			interrupt-map = <0 0 0 1 &intc 0 72 IRQ_TYPE_LEVEL_HIGH>, /* int_a */
++					<0 0 0 2 &intc 0 73 IRQ_TYPE_LEVEL_HIGH>, /* int_b */
++					<0 0 0 3 &intc 0 74 IRQ_TYPE_LEVEL_HIGH>, /* int_c */
++					<0 0 0 4 &intc 0 75 IRQ_TYPE_LEVEL_HIGH>; /* int_d */
++
++			clocks = <&gcc PCIE_2_A_CLK>,
++				 <&gcc PCIE_2_H_CLK>,
++				 <&gcc PCIE_2_PHY_CLK>;
++			clock-names = "core", "iface", "phy";
++
++			resets = <&gcc PCIE_2_ACLK_RESET>,
++				 <&gcc PCIE_2_HCLK_RESET>,
++				 <&gcc PCIE_2_POR_RESET>,
++				 <&gcc PCIE_2_PCI_RESET>,
++				 <&gcc PCIE_2_PHY_RESET>;
++			reset-names = "axi", "ahb", "por", "pci", "phy";
++
++			status = "disabled";
++		};
++
+ 		hs_phy_1: phy at 100f8800 {
+ 			compatible = "qcom,dwc3-hs-usb-phy";
+ 			reg = <0x100f8800 0x30>;
+@@ -389,6 +513,5 @@
+ 				dr_mode = "host";
+ 			};
+ 		};
+-
+ 	};
+ };
diff --git a/target/linux/ipq806x/patches-3.18/113-ARM-qcom-automatically-select-PCI_DOMAINS-if-PCI-is-.patch b/target/linux/ipq806x/patches-3.18/113-ARM-qcom-automatically-select-PCI_DOMAINS-if-PCI-is-.patch
new file mode 100644
index 0000000..4f0e45f
--- /dev/null
+++ b/target/linux/ipq806x/patches-3.18/113-ARM-qcom-automatically-select-PCI_DOMAINS-if-PCI-is-.patch
@@ -0,0 +1,29 @@
+From f004aa1dec6e2e206be025de15b115d60f2b21e3 Mon Sep 17 00:00:00 2001
+From: Mathieu Olivari <mathieu at codeaurora.org>
+Date: Tue, 21 Apr 2015 19:09:07 -0700
+Subject: [PATCH 9/9] ARM: qcom: automatically select PCI_DOMAINS if PCI is
+ enabled
+
+If multiple PCIe devices are present in the system, the kernel will
+panic at boot time when trying to scan the PCI buses. This happens on
+IPQ806x based platforms, which has 3 PCIe ports.
+
+Enabling this option allows the kernel to assign the pci-domains
+according to the device-tree content. This allows multiple PCIe
+controllers to coexist in the system.
+
+Signed-off-by: Mathieu Olivari <mathieu at codeaurora.org>
+---
+ arch/arm/mach-qcom/Kconfig | 1 +
+ 1 file changed, 1 insertion(+)
+
+--- a/arch/arm/mach-qcom/Kconfig
++++ b/arch/arm/mach-qcom/Kconfig
+@@ -6,6 +6,7 @@ menuconfig ARCH_QCOM
+ 	select CLKSRC_OF
+ 	select PINCTRL
+ 	select QCOM_SCM if SMP
++	select PCI_DOMAINS if PCI
+ 	help
+ 	  Support for Qualcomm's devicetree based systems.
+ 
diff --git a/target/linux/ipq806x/patches-3.18/700-add-gmac-dts-suport.patch b/target/linux/ipq806x/patches-3.18/700-add-gmac-dts-suport.patch
index 348f24b..fe670cf 100644
--- a/target/linux/ipq806x/patches-3.18/700-add-gmac-dts-suport.patch
+++ b/target/linux/ipq806x/patches-3.18/700-add-gmac-dts-suport.patch
@@ -16,7 +16,7 @@
  			i2c4_pins: i2c4_pinmux {
  				pins = "gpio12", "gpio13";
  				function = "gsbi4";
-@@ -38,6 +45,25 @@
+@@ -54,6 +61,25 @@
  					bias-none;
  				};
  			};
@@ -42,7 +42,7 @@
  		};
  
  		gsbi at 16300000 {
-@@ -76,6 +102,7 @@
+@@ -92,6 +118,7 @@
  					#size-cells = <1>;
  					spi-max-frequency = <50000000>;
  					reg = <0>;
@@ -50,9 +50,9 @@
  
  					partition at 0 {
  						label = "lowlevel_init";
-@@ -136,5 +163,66 @@
- 		usb30 at 1 {
- 			status = "ok";
+@@ -166,5 +193,66 @@
+ 			pinctrl-0 = <&pcie2_pins>;
+ 			pinctrl-names = "default";
  		};
 +
 +		mdio0: mdio {
@@ -119,18 +119,19 @@
  };
 --- a/arch/arm/boot/dts/qcom-ipq8064.dtsi
 +++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi
-@@ -3,6 +3,7 @@
- #include "skeleton.dtsi"
+@@ -4,6 +4,7 @@
  #include <dt-bindings/clock/qcom,gcc-ipq806x.h>
  #include <dt-bindings/soc/qcom,gsbi.h>
+ #include <dt-bindings/reset/qcom,gcc-ipq806x.h>
 +#include <dt-bindings/interrupt-controller/arm-gic.h>
  
  / {
  	model = "Qualcomm IPQ8064";
-@@ -390,5 +391,41 @@
+@@ -513,5 +514,42 @@
+ 				dr_mode = "host";
  			};
  		};
- 
++
 +		nss-gmac-common {
 +			reg = <0x03000000 0x0000FFFF 0x1bb00000 0x0000FFFF 0x00900000 0x00004000>;
 +			reg-names = "nss_reg_base" , "qsgmii_reg_base", "clk_ctl_base";
diff --git a/target/linux/ipq806x/patches-4.0/110-DT-PCI-qcom-Document-PCIe-devicetree-bindings.patch b/target/linux/ipq806x/patches-4.0/110-DT-PCI-qcom-Document-PCIe-devicetree-bindings.patch
new file mode 100644
index 0000000..95398e0
--- /dev/null
+++ b/target/linux/ipq806x/patches-4.0/110-DT-PCI-qcom-Document-PCIe-devicetree-bindings.patch
@@ -0,0 +1,266 @@
+Content-Type: text/plain; charset="utf-8"
+MIME-Version: 1.0
+Content-Transfer-Encoding: 7bit
+Subject: [v2,3/5] DT: PCI: qcom: Document PCIe devicetree bindings
+From: Stanimir Varbanov <svarbanov at mm-sol.com>
+X-Patchwork-Id: 6326181
+Message-Id: <1430743338-10441-4-git-send-email-svarbanov at mm-sol.com>
+To: Rob Herring <robh+dt at kernel.org>, Kumar Gala <galak at codeaurora.org>,
+	Mark Rutland <mark.rutland at arm.com>,
+	Grant Likely <grant.likely at linaro.org>,
+	Bjorn Helgaas <bhelgaas at google.com>,
+	Kishon Vijay Abraham I <kishon at ti.com>,
+	Russell King <linux at arm.linux.org.uk>, Arnd Bergmann <arnd at arndb.de>
+Cc: linux-arm-msm at vger.kernel.org, linux-kernel at vger.kernel.org,
+	linux-arm-kernel at lists.infradead.org, devicetree at vger.kernel.org,
+	linux-pci at vger.kernel.org, Mathieu Olivari <mathieu at codeaurora.org>,
+	Srinivas Kandagatla <srinivas.kandagatla at linaro.org>,
+	Stanimir Varbanov <svarbanov at mm-sol.com>
+Date: Mon,  4 May 2015 15:42:16 +0300
+
+Document Qualcomm PCIe driver devicetree bindings.
+
+Signed-off-by: Stanimir Varbanov <svarbanov at mm-sol.com>
+
+---
+.../devicetree/bindings/pci/qcom,pcie.txt          |  231 ++++++++++++++++++++
+ 1 files changed, 231 insertions(+), 0 deletions(-)
+ create mode 100644 Documentation/devicetree/bindings/pci/qcom,pcie.txt
+
+diff --git a/Documentation/devicetree/bindings/pci/qcom,pcie.txt b/Documentation/devicetree/bindings/pci/qcom,pcie.txt
+new file mode 100644
+index 0000000..dcf7348
+--- /dev/null
++++ b/Documentation/devicetree/bindings/pci/qcom,pcie.txt
+@@ -0,0 +1,231 @@
++* Qualcomm PCI express root complex
++
++- compatible:
++	Usage: required
++	Value type: <stringlist>
++	Definition: Value shall include
++		    - "qcom,pcie-v0" for apq/ipq8064
++		    - "qcom,pcie-v1" for apq8084
++
++- reg:
++	Usage: required
++	Value type: <prop-encoded-array>
++	Definition: Register ranges as listed in the reg-names property
++
++- reg-names:
++	Usage: required
++	Value type: <stringlist>
++	Definition: Must include the following entries
++		    - "parf"   Qualcomm specific registers
++		    - "dbi"    Designware PCIe registers
++		    - "elbi"   External local bus interface registers
++		    - "config" PCIe configuration space
++
++- device_type:
++	Usage: required
++	Value type: <string>
++	Definition: Should be "pci". As specified in designware-pcie.txt
++
++- #address-cells:
++	Usage: required
++	Value type: <u32>
++	Definition: Should be set to 3. As specified in designware-pcie.txt
++
++- #size-cells:
++	Usage: required
++	Value type: <u32>
++	Definition: Should be set 2. As specified in designware-pcie.txt
++
++- ranges:
++	Usage: required
++	Value type: <prop-encoded-array>
++	Definition: As specified in designware-pcie.txt
++
++- interrupts:
++	Usage: required
++	Value type: <prop-encoded-array>
++	Definition: MSI interrupt
++
++- interrupt-names:
++	Usage: required
++	Value type: <stringlist>
++	Definition: Should contain "msi"
++
++- #interrupt-cells:
++	Usage: required
++	Value type: <u32>
++	Definition: Should be 1. As specified in designware-pcie.txt
++
++- interrupt-map-mask:
++	Usage: required
++	Value type: <prop-encoded-array>
++	Definition: As specified in designware-pcie.txt
++
++- interrupt-map:
++	Usage: required
++	Value type: <prop-encoded-array>
++	Definition: As specified in designware-pcie.txt
++
++- clocks:
++	Usage: required
++	Value type: <prop-encoded-array>
++	Definition: List of phandle and clock specifier pairs as listed
++		    in clock-names property
++
++- clock-names:
++	Usage: required
++	Value type: <stringlist>
++	Definition: Should contain the following entries
++		    * should be populated for v0 and v1
++		    	- "iface"      Configuration AHB clock
++
++		    * should be populated for v0
++		    	- "core"       Clocks the pcie hw block
++		    	- "phy"        Clocks the pcie PHY block
++
++		    * should be populated for v1
++			- "aux"        Auxiliary (AUX) clock
++		    	- "bus_master" Master AXI clock
++		    	- "bus_slave"  Slave AXI clock
++
++- resets:
++	Usage: required
++	Value type: <prop-encoded-array>
++	Definition: List of phandle and reset specifier pairs as listed
++		    in reset-names property
++
++- reset-names:
++	Usage: required
++	Value type: <stringlist>
++	Definition: Should contain the following entries
++		    * should be populated for v0
++			- "axi"  AXI reset
++			- "ahb"  AHB reset
++			- "por"  POR reset
++			- "pci"  PCI reset
++			- "phy"  PHY reset
++
++		    * should be populated for v1
++			- "core" Core reset
++
++- power-domains:
++	Usage: required (for v1 only)
++	Value type: <prop-encoded-array>
++	Definition: A phandle and power domain specifier pair to the
++		    power domain which is responsible for collapsing
++		    and restoring power to the peripheral
++
++- <name>-supply:
++	Usage: required
++	Value type: <phandle>
++	Definition: List of phandles to the power supply regulator(s)
++		    * should be populated for v0 and v1
++			- "vdda"        core analog power supply
++
++		    * should be populated for v0
++			- "vdda_phy"    analog power supply for PHY
++			- "vdda_refclk" analog power supply for IC which generate
++					reference clock
++
++- phys:
++	Usage: required (for v1 only)
++	Value type: <phandle>
++	Definition: List of phandle(s) as listed in phy-names property
++
++- phy-names:
++	Usage: required (for v1 only)
++	Value type: <stringlist>
++	Definition: Should contain "pciephy"
++
++- <name>-gpio:
++	Usage: optional
++	Value type: <prop-encoded-array>
++	Definition: List of phandle and gpio specifier pairs. Should contain
++		    - "perst"  PCIe endpoint reset signal line
++		    - "pewake" PCIe endpoint wake signal line
++
++- pinctrl-0:
++	Usage: required
++	Value type: <phandle>
++	Definition: List of phandles pointing at a pin(s) configuration
++
++- pinctrl-names
++	Usage: required
++	Value type: <stringlist>
++	Definition: List of names of pinctrl-0 state
++
++* Example for v0
++	pcie0: pci at 1b500000 {
++		compatible = "qcom,pcie-v0";
++		reg = <0x1b500000 0x1000
++		       0x1b502000 0x80
++		       0x1b600000 0x100
++		       0x0ff00000 0x100000>;
++		reg-names = "dbi", "elbi", "parf", "config";
++		device_type = "pci";
++		linux,pci-domain = <0>;
++		bus-range = <0x00 0xff>;
++		num-lanes = <1>;
++		#address-cells = <3>;
++		#size-cells = <2>;
++		ranges = <0x81000000 0 0	  0x0fe00000 0 0x00100000   /* I/O */
++			  0x82000000 0 0x00000000 0x08000000 0 0x07e00000>; /* memory */
++		interrupts = <GIC_SPI 35 IRQ_TYPE_NONE>;
++		interrupt-names = "msi";
++		#interrupt-cells = <1>;
++		interrupt-map-mask = <0 0 0 0x7>;
++		interrupt-map = <0 0 0 1 &intc 0 36 IRQ_TYPE_LEVEL_HIGH>, /* int_a */
++				<0 0 0 2 &intc 0 37 IRQ_TYPE_LEVEL_HIGH>, /* int_b */
++				<0 0 0 3 &intc 0 38 IRQ_TYPE_LEVEL_HIGH>, /* int_c */
++				<0 0 0 4 &intc 0 39 IRQ_TYPE_LEVEL_HIGH>; /* int_d */
++		clocks = <&gcc PCIE_A_CLK>,
++			 <&gcc PCIE_H_CLK>,
++			 <&gcc PCIE_PHY_CLK>;
++		clock-names = "core", "iface", "phy";
++		resets = <&gcc PCIE_ACLK_RESET>,
++			 <&gcc PCIE_HCLK_RESET>,
++			 <&gcc PCIE_POR_RESET>,
++			 <&gcc PCIE_PCI_RESET>,
++			 <&gcc PCIE_PHY_RESET>;
++		reset-names = "axi", "ahb", "por", "pci", "phy";
++	};
++
++* Example for v1
++	pcie0 at fc520000 {
++		compatible = "qcom,pcie-v1";
++		reg = <0xfc520000 0x2000>,
++		      <0xff000000 0x1000>,
++		      <0xff001000 0x1000>,
++		      <0xff002000 0x2000>;
++		reg-names = "parf", "dbi", "elbi", "config";
++		device_type = "pci";
++		linux,pci-domain = <0>;
++		bus-range = <0x00 0xff>;
++		num-lanes = <1>;
++		#address-cells = <3>;
++		#size-cells = <2>;
++		ranges = <0x81000000 0 0          0xff200000 0 0x00100000   /* I/O */
++			  0x82000000 0 0x00300000 0xff300000 0 0x00d00000>; /* memory */
++		interrupts = <GIC_SPI 243 IRQ_TYPE_NONE>;
++		interrupt-names = "msi";
++		#interrupt-cells = <1>;
++		interrupt-map-mask = <0 0 0 0x7>;
++		interrupt-map = <0 0 0 1 &intc 0 244 IRQ_TYPE_LEVEL_HIGH>, /* int_a */
++				<0 0 0 2 &intc 0 245 IRQ_TYPE_LEVEL_HIGH>, /* int_b */
++				<0 0 0 3 &intc 0 247 IRQ_TYPE_LEVEL_HIGH>, /* int_c */
++				<0 0 0 4 &intc 0 248 IRQ_TYPE_LEVEL_HIGH>; /* int_d */
++		clocks = <&gcc GCC_PCIE_0_CFG_AHB_CLK>,
++			 <&gcc GCC_PCIE_0_MSTR_AXI_CLK>,
++			 <&gcc GCC_PCIE_0_SLV_AXI_CLK>,
++			 <&gcc GCC_PCIE_0_AUX_CLK>;
++		clock-names = "iface", "master_bus", "slave_bus", "aux";
++		resets = <&gcc GCC_PCIE_0_BCR>;
++		reset-names = "core";
++		power-domains = <&gcc PCIE0_GDSC>;
++		vdda-supply = <&pma8084_l3>;
++		phys = <&pciephy0>;
++		phy-names = "pciephy";
++		perst-gpio = <&tlmm 70 GPIO_ACTIVE_LOW>;
++		pinctrl-0 = <&pcie0_pins_default>;
++		pinctrl-names = "default";
++	};
diff --git a/target/linux/ipq806x/patches-4.0/111-PCI-qcom-Add-Qualcomm-PCIe-controller-driver.patch b/target/linux/ipq806x/patches-4.0/111-PCI-qcom-Add-Qualcomm-PCIe-controller-driver.patch
new file mode 100644
index 0000000..b64b759
--- /dev/null
+++ b/target/linux/ipq806x/patches-4.0/111-PCI-qcom-Add-Qualcomm-PCIe-controller-driver.patch
@@ -0,0 +1,753 @@
+Content-Type: text/plain; charset="utf-8"
+MIME-Version: 1.0
+Content-Transfer-Encoding: 7bit
+Subject: [v2,4/5] PCI: qcom: Add Qualcomm PCIe controller driver
+From: Stanimir Varbanov <svarbanov at mm-sol.com>
+X-Patchwork-Id: 6326161
+Message-Id: <1430743338-10441-5-git-send-email-svarbanov at mm-sol.com>
+To: Rob Herring <robh+dt at kernel.org>, Kumar Gala <galak at codeaurora.org>,
+	Mark Rutland <mark.rutland at arm.com>,
+	Grant Likely <grant.likely at linaro.org>,
+	Bjorn Helgaas <bhelgaas at google.com>,
+	Kishon Vijay Abraham I <kishon at ti.com>,
+	Russell King <linux at arm.linux.org.uk>, Arnd Bergmann <arnd at arndb.de>
+Cc: linux-arm-msm at vger.kernel.org, linux-kernel at vger.kernel.org,
+	linux-arm-kernel at lists.infradead.org, devicetree at vger.kernel.org,
+	linux-pci at vger.kernel.org, Mathieu Olivari <mathieu at codeaurora.org>,
+	Srinivas Kandagatla <srinivas.kandagatla at linaro.org>,
+	Stanimir Varbanov <svarbanov at mm-sol.com>
+Date: Mon,  4 May 2015 15:42:17 +0300
+
+The PCIe driver reuse the Designware common code for host
+and MSI initialization, and also program the Qualcomm
+application specific registers.
+
+Signed-off-by: Stanimir Varbanov <svarbanov at mm-sol.com>
+
+---
+MAINTAINERS                  |    7 +
+ drivers/pci/host/Kconfig     |    9 +
+ drivers/pci/host/Makefile    |    1 +
+ drivers/pci/host/pcie-qcom.c |  677 ++++++++++++++++++++++++++++++++++++++++++
+ 4 files changed, 694 insertions(+), 0 deletions(-)
+ create mode 100644 drivers/pci/host/pcie-qcom.c
+
+--- a/MAINTAINERS
++++ b/MAINTAINERS
+@@ -7511,6 +7511,13 @@ L:	linux-pci at vger.kernel.org
+ S:	Maintained
+ F:	drivers/pci/host/*spear*
+ 
++PCIE DRIVER FOR QUALCOMM MSM
++M:	Stanimir Varbanov <svarbanov at mm-sol.com>
++L:	linux-pci at vger.kernel.org
++L:	linux-arm-msm at vger.kernel.org
++S:	Maintained
++F:	drivers/pci/host/*qcom*
++
+ PCMCIA SUBSYSTEM
+ P:	Linux PCMCIA Team
+ L:	linux-pcmcia at lists.infradead.org
+--- a/drivers/pci/host/Kconfig
++++ b/drivers/pci/host/Kconfig
+@@ -106,4 +106,13 @@ config PCI_VERSATILE
+ 	bool "ARM Versatile PB PCI controller"
+ 	depends on ARCH_VERSATILE
+ 
++config PCIE_QCOM
++	bool "Qualcomm PCIe controller"
++	depends on ARCH_QCOM && OF || (ARM && COMPILE_TEST)
++	select PCIE_DW
++	select PCIEPORTBUS
++	help
++	  Say Y here to enable PCIe controller support on Qualcomm SoCs. The
++	  PCIe controller use Designware core plus Qualcomm specific hardware
++	  wrappers.
+ endmenu
+--- /dev/null
++++ b/drivers/pci/host/pcie-qcom.c
+@@ -0,0 +1,677 @@
++/*
++ * Copyright (c) 2014, The Linux Foundation. All rights reserved.
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License version 2 and
++ * only version 2 as published by the Free Software Foundation.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++ * GNU General Public License for more details.
++ */
++
++#include <linux/clk.h>
++#include <linux/delay.h>
++#include <linux/gpio.h>
++#include <linux/interrupt.h>
++#include <linux/io.h>
++#include <linux/kernel.h>
++#include <linux/module.h>
++#include <linux/of_gpio.h>
++#include <linux/pci.h>
++#include <linux/platform_device.h>
++#include <linux/phy/phy.h>
++#include <linux/regulator/consumer.h>
++#include <linux/reset.h>
++#include <linux/slab.h>
++#include <linux/types.h>
++
++#include "pcie-designware.h"
++
++#define PCIE20_PARF_PHY_CTRL			0x40
++#define PCIE20_PARF_PHY_REFCLK			0x4C
++#define PCIE20_PARF_DBI_BASE_ADDR		0x168
++#define PCIE20_PARF_SLV_ADDR_SPACE_SIZE		0x16c
++#define PCIE20_PARF_AXI_MSTR_WR_ADDR_HALT	0x178
++
++#define PCIE20_ELBI_SYS_CTRL			0x04
++#define PCIE20_ELBI_SYS_STTS			0x08
++#define XMLH_LINK_UP				BIT(10)
++
++#define PCIE20_CAP				0x70
++#define PCIE20_CAP_LINKCTRLSTATUS		(PCIE20_CAP + 0x10)
++
++#define PERST_DELAY_MIN_US			1000
++#define PERST_DELAY_MAX_US			1005
++
++#define LINKUP_DELAY_MIN_US			5000
++#define LINKUP_DELAY_MAX_US			5100
++#define LINKUP_RETRIES_COUNT			20
++
++#define PCIE_V0					0	/* apq8064 */
++#define PCIE_V1					1	/* apq8084 */
++
++struct qcom_pcie_resources_v0 {
++	struct clk *iface_clk;
++	struct clk *core_clk;
++	struct clk *phy_clk;
++	struct reset_control *pci_reset;
++	struct reset_control *axi_reset;
++	struct reset_control *ahb_reset;
++	struct reset_control *por_reset;
++	struct reset_control *phy_reset;
++	struct regulator *vdda;
++	struct regulator *vdda_phy;
++	struct regulator *vdda_refclk;
++};
++
++struct qcom_pcie_resources_v1 {
++	struct clk *iface;
++	struct clk *aux;
++	struct clk *master_bus;
++	struct clk *slave_bus;
++	struct reset_control *core;
++	struct regulator *vdda;
++};
++
++union pcie_resources {
++	struct qcom_pcie_resources_v0 v0;
++	struct qcom_pcie_resources_v1 v1;
++};
++
++struct qcom_pcie {
++	struct pcie_port pp;
++	struct device *dev;
++	union pcie_resources res;
++	void __iomem *parf;
++	void __iomem *dbi;
++	void __iomem *elbi;
++	struct phy *phy;
++	struct gpio_desc *reset;
++	unsigned int version;
++};
++
++#define to_qcom_pcie(x)		container_of(x, struct qcom_pcie, pp)
++
++static inline void
++writel_masked(void __iomem *addr, u32 clear_mask, u32 set_mask)
++{
++	u32 val = readl(addr);
++
++	val &= ~clear_mask;
++	val |= set_mask;
++	writel(val, addr);
++}
++
++static void qcom_ep_reset_assert_deassert(struct qcom_pcie *pcie, int assert)
++{
++	int val, active_low;
++
++	if (IS_ERR_OR_NULL(pcie->reset))
++		return;
++
++	active_low = gpiod_is_active_low(pcie->reset);
++
++	if (assert)
++		val = !!active_low;
++	else
++		val = !active_low;
++
++	gpiod_set_value(pcie->reset, val);
++
++	usleep_range(PERST_DELAY_MIN_US, PERST_DELAY_MAX_US);
++}
++
++static void qcom_ep_reset_assert(struct qcom_pcie *pcie)
++{
++	qcom_ep_reset_assert_deassert(pcie, 1);
++}
++
++static void qcom_ep_reset_deassert(struct qcom_pcie *pcie)
++{
++	qcom_ep_reset_assert_deassert(pcie, 0);
++}
++
++static irqreturn_t qcom_pcie_msi_irq_handler(int irq, void *arg)
++{
++	struct pcie_port *pp = arg;
++
++	return dw_handle_msi_irq(pp);
++}
++
++static int qcom_pcie_link_up(struct pcie_port *pp)
++{
++	struct qcom_pcie *pcie = to_qcom_pcie(pp);
++	u32 val = readl(pcie->dbi + PCIE20_CAP_LINKCTRLSTATUS);
++
++	return val & BIT(29) ? 1 : 0;
++}
++
++static void qcom_pcie_disable_resources_v0(struct qcom_pcie *pcie)
++{
++	struct qcom_pcie_resources_v0 *res = &pcie->res.v0;
++
++	reset_control_assert(res->pci_reset);
++	reset_control_assert(res->axi_reset);
++	reset_control_assert(res->ahb_reset);
++	reset_control_assert(res->por_reset);
++	reset_control_assert(res->pci_reset);
++	clk_disable_unprepare(res->iface_clk);
++	clk_disable_unprepare(res->core_clk);
++	clk_disable_unprepare(res->phy_clk);
++	regulator_disable(res->vdda);
++	regulator_disable(res->vdda_phy);
++	regulator_disable(res->vdda_refclk);
++}
++
++static void qcom_pcie_disable_resources_v1(struct qcom_pcie *pcie)
++{
++	struct qcom_pcie_resources_v1 *res = &pcie->res.v1;
++
++	reset_control_assert(res->core);
++	clk_disable_unprepare(res->slave_bus);
++	clk_disable_unprepare(res->master_bus);
++	clk_disable_unprepare(res->iface);
++	clk_disable_unprepare(res->aux);
++	regulator_disable(res->vdda);
++}
++
++static int qcom_pcie_enable_resources_v0(struct qcom_pcie *pcie)
++{
++	struct qcom_pcie_resources_v0 *res = &pcie->res.v0;
++	struct device *dev = pcie->dev;
++	int ret;
++
++	ret = regulator_enable(res->vdda);
++	if (ret) {
++		dev_err(dev, "cannot enable vdda regulator\n");
++		return ret;
++	}
++
++	ret = regulator_enable(res->vdda_refclk);
++	if (ret) {
++		dev_err(dev, "cannot enable vdda_refclk regulator\n");
++		goto err_refclk;
++	}
++
++	ret = regulator_enable(res->vdda_phy);
++	if (ret) {
++		dev_err(dev, "cannot enable vdda_phy regulator\n");
++		goto err_vdda_phy;
++	}
++
++	ret = clk_prepare_enable(res->iface_clk);
++	if (ret) {
++		dev_err(dev, "cannot prepare/enable iface clock\n");
++		goto err_iface;
++	}
++
++	ret = clk_prepare_enable(res->core_clk);
++	if (ret) {
++		dev_err(dev, "cannot prepare/enable core clock\n");
++		goto err_clk_core;
++	}
++
++	ret = clk_prepare_enable(res->phy_clk);
++	if (ret) {
++		dev_err(dev, "cannot prepare/enable phy clock\n");
++		goto err_clk_phy;
++	}
++
++	ret = reset_control_deassert(res->ahb_reset);
++	if (ret) {
++		dev_err(dev, "cannot deassert ahb reset\n");
++		goto err_reset_ahb;
++	}
++
++	return 0;
++
++err_reset_ahb:
++	clk_disable_unprepare(res->phy_clk);
++err_clk_phy:
++	clk_disable_unprepare(res->core_clk);
++err_clk_core:
++	clk_disable_unprepare(res->iface_clk);
++err_iface:
++	regulator_disable(res->vdda_phy);
++err_vdda_phy:
++	regulator_disable(res->vdda_refclk);
++err_refclk:
++	regulator_disable(res->vdda);
++	return ret;
++}
++
++static int qcom_pcie_enable_resources_v1(struct qcom_pcie *pcie)
++{
++	struct qcom_pcie_resources_v1 *res = &pcie->res.v1;
++	struct device *dev = pcie->dev;
++	int ret;
++
++	ret = reset_control_deassert(res->core);
++	if (ret) {
++		dev_err(dev, "cannot deassert core reset\n");
++		return ret;
++	}
++
++	ret = clk_prepare_enable(res->aux);
++	if (ret) {
++		dev_err(dev, "cannot prepare/enable aux clock\n");
++		goto err_res;
++	}
++
++	ret = clk_prepare_enable(res->iface);
++	if (ret) {
++		dev_err(dev, "cannot prepare/enable iface clock\n");
++		goto err_aux;
++	}
++
++	ret = clk_prepare_enable(res->master_bus);
++	if (ret) {
++		dev_err(dev, "cannot prepare/enable master_bus clock\n");
++		goto err_iface;
++	}
++
++	ret = clk_prepare_enable(res->slave_bus);
++	if (ret) {
++		dev_err(dev, "cannot prepare/enable slave_bus clock\n");
++		goto err_master;
++	}
++
++	ret = regulator_enable(res->vdda);
++	if (ret) {
++		dev_err(dev, "cannot enable vdda regulator\n");
++		goto err_slave;
++	}
++
++	return 0;
++
++err_slave:
++	clk_disable_unprepare(res->slave_bus);
++err_master:
++	clk_disable_unprepare(res->master_bus);
++err_iface:
++	clk_disable_unprepare(res->iface);
++err_aux:
++	clk_disable_unprepare(res->aux);
++err_res:
++	reset_control_assert(res->core);
++
++	return ret;
++}
++
++static int qcom_pcie_get_resources_v0(struct qcom_pcie *pcie)
++{
++	struct qcom_pcie_resources_v0 *res = &pcie->res.v0;
++	struct device *dev = pcie->dev;
++
++	res->vdda = devm_regulator_get(dev, "vdda");
++	if (IS_ERR(res->vdda))
++		return PTR_ERR(res->vdda);
++
++	res->vdda_phy = devm_regulator_get(dev, "vdda_phy");
++	if (IS_ERR(res->vdda_phy))
++		return PTR_ERR(res->vdda_phy);
++
++	res->vdda_refclk = devm_regulator_get(dev, "vdda_refclk");
++	if (IS_ERR(res->vdda_refclk))
++		return PTR_ERR(res->vdda_refclk);
++
++	res->iface_clk = devm_clk_get(dev, "iface");
++	if (IS_ERR(res->iface_clk))
++		return PTR_ERR(res->iface_clk);
++
++	res->core_clk = devm_clk_get(dev, "core");
++	if (IS_ERR(res->core_clk))
++		return PTR_ERR(res->core_clk);
++
++	res->phy_clk = devm_clk_get(dev, "phy");
++	if (IS_ERR(res->phy_clk))
++		return PTR_ERR(res->phy_clk);
++
++	res->pci_reset = devm_reset_control_get(dev, "pci");
++	if (IS_ERR(res->pci_reset))
++		return PTR_ERR(res->pci_reset);
++
++	res->axi_reset = devm_reset_control_get(dev, "axi");
++	if (IS_ERR(res->axi_reset))
++		return PTR_ERR(res->axi_reset);
++
++	res->ahb_reset = devm_reset_control_get(dev, "ahb");
++	if (IS_ERR(res->ahb_reset))
++		return PTR_ERR(res->ahb_reset);
++
++	res->por_reset = devm_reset_control_get(dev, "por");
++	if (IS_ERR(res->por_reset))
++		return PTR_ERR(res->por_reset);
++
++	res->phy_reset = devm_reset_control_get(dev, "phy");
++	if (IS_ERR(res->phy_reset))
++		return PTR_ERR(res->phy_reset);
++
++	return 0;
++}
++
++static int qcom_pcie_get_resources_v1(struct qcom_pcie *pcie)
++{
++	struct qcom_pcie_resources_v1 *res = &pcie->res.v1;
++	struct device *dev = pcie->dev;
++
++	res->vdda = devm_regulator_get(dev, "vdda");
++	if (IS_ERR(res->vdda))
++		return PTR_ERR(res->vdda);
++
++	res->iface = devm_clk_get(dev, "iface");
++	if (IS_ERR(res->iface))
++		return PTR_ERR(res->iface);
++
++	res->aux = devm_clk_get(dev, "aux");
++	if (IS_ERR(res->aux) && PTR_ERR(res->aux) == -EPROBE_DEFER)
++		return -EPROBE_DEFER;
++	else if (IS_ERR(res->aux))
++		res->aux = NULL;
++
++	res->master_bus = devm_clk_get(dev, "master_bus");
++	if (IS_ERR(res->master_bus))
++		return PTR_ERR(res->master_bus);
++
++	res->slave_bus = devm_clk_get(dev, "slave_bus");
++	if (IS_ERR(res->slave_bus))
++		return PTR_ERR(res->slave_bus);
++
++	res->core = devm_reset_control_get(dev, "core");
++	if (IS_ERR(res->core))
++		return PTR_ERR(res->core);
++
++	return 0;
++}
++
++static int qcom_pcie_enable_link_training(struct pcie_port *pp)
++{
++	struct qcom_pcie *pcie = to_qcom_pcie(pp);
++	struct device *dev = pp->dev;
++	int retries;
++	u32 val;
++
++	/* enable link training */
++	writel_masked(pcie->elbi + PCIE20_ELBI_SYS_CTRL, 0, BIT(0));
++
++	/* wait for up to 100ms for the link to come up */
++	retries = LINKUP_RETRIES_COUNT;
++	do {
++		val = readl(pcie->elbi + PCIE20_ELBI_SYS_STTS);
++		if (val & XMLH_LINK_UP)
++			break;
++		usleep_range(LINKUP_DELAY_MIN_US, LINKUP_DELAY_MAX_US);
++	} while (retries--);
++
++	if (retries < 0 || !dw_pcie_link_up(pp)) {
++		dev_err(dev, "link initialization failed\n");
++		return -ENXIO;
++	}
++
++	return 0;
++}
++
++static void qcom_pcie_host_init_v1(struct pcie_port *pp)
++{
++	struct qcom_pcie *pcie = to_qcom_pcie(pp);
++	int ret;
++
++	qcom_ep_reset_assert(pcie);
++
++	ret = qcom_pcie_enable_resources_v1(pcie);
++	if (ret)
++		return;
++
++	/* change DBI base address */
++	writel(0, pcie->parf + PCIE20_PARF_DBI_BASE_ADDR);
++
++	if (IS_ENABLED(CONFIG_PCI_MSI))
++		writel_masked(pcie->parf + PCIE20_PARF_AXI_MSTR_WR_ADDR_HALT,
++			      0, BIT(31));
++
++	ret = phy_init(pcie->phy);
++	if (ret)
++		goto err_res;
++
++	ret = phy_power_on(pcie->phy);
++	if (ret)
++		goto err_phy;
++
++	dw_pcie_setup_rc(pp);
++
++	if (IS_ENABLED(CONFIG_PCI_MSI))
++		dw_pcie_msi_init(pp);
++
++	qcom_ep_reset_deassert(pcie);
++
++	ret = qcom_pcie_enable_link_training(pp);
++	if (ret)
++		goto err;
++
++	return;
++
++err:
++	qcom_ep_reset_assert(pcie);
++	phy_power_off(pcie->phy);
++err_phy:
++	phy_exit(pcie->phy);
++err_res:
++	qcom_pcie_disable_resources_v1(pcie);
++}
++
++static void qcom_pcie_host_init_v0(struct pcie_port *pp)
++{
++	struct qcom_pcie *pcie = to_qcom_pcie(pp);
++	struct qcom_pcie_resources_v0 *res = &pcie->res.v0;
++	struct device *dev = pcie->dev;
++	int ret;
++
++	qcom_ep_reset_assert(pcie);
++
++	ret = qcom_pcie_enable_resources_v0(pcie);
++	if (ret)
++		return;
++
++	writel_masked(pcie->parf + PCIE20_PARF_PHY_CTRL, BIT(0), 0);
++
++	/* enable external reference clock */
++	writel_masked(pcie->parf + PCIE20_PARF_PHY_REFCLK, 0, BIT(16));
++
++	ret = reset_control_deassert(res->phy_reset);
++	if (ret) {
++		dev_err(dev, "cannot deassert phy reset\n");
++		return;
++	}
++
++	ret = reset_control_deassert(res->pci_reset);
++	if (ret) {
++		dev_err(dev, "cannot deassert pci reset\n");
++		return;
++	}
++
++	ret = reset_control_deassert(res->por_reset);
++	if (ret) {
++		dev_err(dev, "cannot deassert por reset\n");
++		return;
++	}
++
++	ret = reset_control_deassert(res->axi_reset);
++	if (ret) {
++		dev_err(dev, "cannot deassert axi reset\n");
++		return;
++	}
++
++	/* wait 150ms for clock acquisition */
++	usleep_range(10000, 15000);
++
++	dw_pcie_setup_rc(pp);
++
++	if (IS_ENABLED(CONFIG_PCI_MSI))
++		dw_pcie_msi_init(pp);
++
++	qcom_ep_reset_deassert(pcie);
++
++	ret = qcom_pcie_enable_link_training(pp);
++	if (ret)
++		goto err;
++
++	return;
++err:
++	qcom_ep_reset_assert(pcie);
++	qcom_pcie_disable_resources_v0(pcie);
++}
++
++static void qcom_pcie_host_init(struct pcie_port *pp)
++{
++	struct qcom_pcie *pcie = to_qcom_pcie(pp);
++
++	if (pcie->version == PCIE_V0)
++		return qcom_pcie_host_init_v0(pp);
++	else
++		return qcom_pcie_host_init_v1(pp);
++}
++
++static int
++qcom_pcie_rd_own_conf(struct pcie_port *pp, int where, int size, u32 *val)
++{
++	/* the device class is not reported correctly from the register */
++	if (where == PCI_CLASS_REVISION && size == 4) {
++		*val = readl(pp->dbi_base + PCI_CLASS_REVISION);
++		*val &= ~(0xffff << 16);
++		*val |= PCI_CLASS_BRIDGE_PCI << 16;
++		return PCIBIOS_SUCCESSFUL;
++	}
++
++	return dw_pcie_cfg_read(pp->dbi_base + (where & ~0x3), where,
++				size, val);
++}
++
++static struct pcie_host_ops qcom_pcie_ops = {
++	.link_up = qcom_pcie_link_up,
++	.host_init = qcom_pcie_host_init,
++	.rd_own_conf = qcom_pcie_rd_own_conf,
++};
++
++static const struct of_device_id qcom_pcie_match[] = {
++	{ .compatible = "qcom,pcie-v0", .data = (void *)PCIE_V0 },
++	{ .compatible = "qcom,pcie-v1", .data = (void *)PCIE_V1 },
++	{ }
++};
++
++static int qcom_pcie_probe(struct platform_device *pdev)
++{
++	struct device *dev = &pdev->dev;
++	const struct of_device_id *match;
++	struct resource *res;
++	struct qcom_pcie *pcie;
++	struct pcie_port *pp;
++	int ret;
++
++	match = of_match_node(qcom_pcie_match, dev->of_node);
++	if (!match)
++		return -ENXIO;
++
++	pcie = devm_kzalloc(dev, sizeof(*pcie), GFP_KERNEL);
++	if (!pcie)
++		return -ENOMEM;
++
++	pcie->version = (unsigned int)match->data;
++
++	pcie->reset = devm_gpiod_get_optional(dev, "perst");
++	if (IS_ERR(pcie->reset) && PTR_ERR(pcie->reset) == -EPROBE_DEFER)
++		return PTR_ERR(pcie->reset);
++
++	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "parf");
++	pcie->parf = devm_ioremap_resource(dev, res);
++	if (IS_ERR(pcie->parf))
++		return PTR_ERR(pcie->parf);
++
++	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dbi");
++	pcie->dbi = devm_ioremap_resource(dev, res);
++	if (IS_ERR(pcie->dbi))
++		return PTR_ERR(pcie->dbi);
++
++	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "elbi");
++	pcie->elbi = devm_ioremap_resource(dev, res);
++	if (IS_ERR(pcie->elbi))
++		return PTR_ERR(pcie->elbi);
++
++	pcie->phy = devm_phy_optional_get(dev, "pciephy");
++	if (IS_ERR(pcie->phy))
++		return PTR_ERR(pcie->phy);
++
++	pcie->dev = dev;
++
++	if (pcie->version == PCIE_V0)
++		ret = qcom_pcie_get_resources_v0(pcie);
++	else
++		ret = qcom_pcie_get_resources_v1(pcie);
++
++	if (ret)
++		return ret;
++
++	pp = &pcie->pp;
++	pp->dev = dev;
++	pp->dbi_base = pcie->dbi;
++	pp->root_bus_nr = -1;
++	pp->ops = &qcom_pcie_ops;
++
++	if (IS_ENABLED(CONFIG_PCI_MSI)) {
++		pp->msi_irq = platform_get_irq_byname(pdev, "msi");
++		if (pp->msi_irq < 0) {
++			dev_err(dev, "cannot get msi irq\n");
++			return pp->msi_irq;
++		}
++
++		ret = devm_request_irq(dev, pp->msi_irq,
++				       qcom_pcie_msi_irq_handler,
++				       IRQF_SHARED, "qcom-pcie-msi", pp);
++		if (ret) {
++			dev_err(dev, "cannot request msi irq\n");
++			return ret;
++		}
++	}
++
++	ret = dw_pcie_host_init(pp);
++	if (ret) {
++		dev_err(dev, "cannot initialize host\n");
++		return ret;
++	}
++
++	platform_set_drvdata(pdev, pcie);
++
++	return 0;
++}
++
++static int qcom_pcie_remove(struct platform_device *pdev)
++{
++	struct qcom_pcie *pcie = platform_get_drvdata(pdev);
++
++	qcom_ep_reset_assert(pcie);
++	phy_power_off(pcie->phy);
++	phy_exit(pcie->phy);
++	if (pcie->version == PCIE_V0)
++		qcom_pcie_disable_resources_v0(pcie);
++	else
++		qcom_pcie_disable_resources_v1(pcie);
++
++	return 0;
++}
++
++static struct platform_driver qcom_pcie_driver = {
++	.probe = qcom_pcie_probe,
++	.remove = qcom_pcie_remove,
++	.driver = {
++		.name = "qcom-pcie",
++		.of_match_table = qcom_pcie_match,
++	},
++};
++
++module_platform_driver(qcom_pcie_driver);
++
++MODULE_AUTHOR("Stanimir Varbanov <svarbanov at mm-sol.com>");
++MODULE_DESCRIPTION("Qualcomm PCIe root complex driver");
++MODULE_LICENSE("GPL v2");
++MODULE_ALIAS("platform:qcom-pcie");
+--- a/drivers/pci/host/Makefile
++++ b/drivers/pci/host/Makefile
+@@ -13,3 +13,4 @@ obj-$(CONFIG_PCIE_XILINX) += pcie-xilinx
+ obj-$(CONFIG_PCI_XGENE) += pci-xgene.o
+ obj-$(CONFIG_PCI_LAYERSCAPE) += pci-layerscape.o
+ obj-$(CONFIG_PCI_VERSATILE) += pci-versatile.o
++obj-$(CONFIG_PCIE_QCOM) += pcie-qcom.o
diff --git a/target/linux/ipq806x/patches-4.0/112-ARM-dts-qcom-add-pcie-nodes-to-ipq806x-platforms.patch b/target/linux/ipq806x/patches-4.0/112-ARM-dts-qcom-add-pcie-nodes-to-ipq806x-platforms.patch
new file mode 100644
index 0000000..593bc32
--- /dev/null
+++ b/target/linux/ipq806x/patches-4.0/112-ARM-dts-qcom-add-pcie-nodes-to-ipq806x-platforms.patch
@@ -0,0 +1,260 @@
+From 5b40516b2f5fb9b2a7d6d3e2e924f12ec9d183a8 Mon Sep 17 00:00:00 2001
+From: Mathieu Olivari <mathieu at codeaurora.org>
+Date: Tue, 21 Apr 2015 19:01:42 -0700
+Subject: [PATCH 8/9] ARM: dts: qcom: add pcie nodes to ipq806x platforms
+
+qcom-pcie driver now supports version 0 of the controller. This change
+adds the corresponding entries to the IPQ806x dtsi file and
+corresponding platform (AP148).
+
+Signed-off-by: Mathieu Olivari <mathieu at codeaurora.org>
+---
+ arch/arm/boot/dts/qcom-ipq8064-ap148.dts |  30 ++++++++
+ arch/arm/boot/dts/qcom-ipq8064.dtsi      | 124 +++++++++++++++++++++++++++++++
+ 2 files changed, 154 insertions(+)
+
+--- a/arch/arm/boot/dts/qcom-ipq8064-ap148.dts
++++ b/arch/arm/boot/dts/qcom-ipq8064-ap148.dts
+@@ -30,6 +30,22 @@
+ 				bias-disable;
+ 			};
+ 
++			pcie1_pins: pcie1_pinmux {
++				mux {
++					pins = "gpio3";
++					drive-strength = <2>;
++					bias-disable;
++				};
++			};
++
++			pcie2_pins: pcie2_pinmux {
++				mux {
++					pins = "gpio48";
++					drive-strength = <2>;
++					bias-disable;
++				};
++			};
++
+ 			spi_pins: spi_pins {
+ 				mux {
+ 					pins = "gpio18", "gpio19", "gpio21";
+@@ -112,5 +128,19 @@
+ 		sata at 29000000 {
+ 			status = "ok";
+ 		};
++
++		pcie0: pci at 1b500000 {
++			status = "ok";
++			reset-gpio = <&qcom_pinmux 3 0>;
++			pinctrl-0 = <&pcie1_pins>;
++			pinctrl-names = "default";
++		};
++
++		pcie1: pci at 1b700000 {
++			status = "ok";
++			reset-gpio = <&qcom_pinmux 48 0>;
++			pinctrl-0 = <&pcie2_pins>;
++			pinctrl-names = "default";
++		};
+ 	};
+ };
+--- a/arch/arm/boot/dts/qcom-ipq8064-db149.dts
++++ b/arch/arm/boot/dts/qcom-ipq8064-db149.dts
+@@ -37,6 +37,30 @@
+ 				bias-disable;
+ 			};
+ 
++			pcie1_pins: pcie1_pinmux {
++				mux {
++					pins = "gpio3";
++					drive-strength = <2>;
++					bias-disable;
++				};
++			};
++
++			pcie2_pins: pcie2_pinmux {
++				mux {
++					pins = "gpio48";
++					drive-strength = <2>;
++					bias-disable;
++				};
++			};
++
++			pcie3_pins: pcie3_pinmux {
++				mux {
++					pins = "gpio63";
++					drive-strength = <2>;
++					bias-disable;
++				};
++			};
++
+ 			spi_pins: spi_pins {
+ 				mux {
+ 					pins = "gpio18", "gpio19", "gpio21";
+@@ -153,6 +177,27 @@
+ 			status = "ok";
+ 		};
+ 
++		pcie0: pci at 1b500000 {
++			status = "ok";
++			reset-gpio = <&qcom_pinmux 3 0>;
++			pinctrl-0 = <&pcie1_pins>;
++			pinctrl-names = "default";
++		};
++
++		pcie1: pci at 1b700000 {
++			status = "ok";
++			reset-gpio = <&qcom_pinmux 48 0>;
++			pinctrl-0 = <&pcie2_pins>;
++			pinctrl-names = "default";
++		};
++
++		pcie2: pci at 1b900000 {
++			status = "ok";
++			reset-gpio = <&qcom_pinmux 63 0>;
++			pinctrl-0 = <&pcie3_pins>;
++			pinctrl-names = "default";
++		};
++
+ 		mdio0: mdio {
+ 			compatible = "virtual,mdio-gpio";
+ 			#address-cells = <1>;
+--- a/arch/arm/boot/dts/qcom-ipq8064.dtsi
++++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi
+@@ -3,6 +3,7 @@
+ #include "skeleton.dtsi"
+ #include <dt-bindings/clock/qcom,gcc-ipq806x.h>
+ #include <dt-bindings/soc/qcom,gsbi.h>
++#include <dt-bindings/reset/qcom,gcc-ipq806x.h>
+ 
+ / {
+ 	model = "Qualcomm IPQ8064";
+@@ -291,5 +292,128 @@
+ 			#clock-cells = <1>;
+ 			#reset-cells = <1>;
+ 		};
++
++		pcie0: pci at 1b500000 {
++			compatible = "qcom,pcie-v0";
++			reg = <0x1b500000 0x1000
++			       0x1b502000 0x80
++			       0x1b600000 0x100
++			       0x0ff00000 0x100000>;
++			reg-names = "dbi", "elbi", "parf", "config";
++			device_type = "pci";
++			linux,pci-domain = <0>;
++			bus-range = <0x00 0xff>;
++			num-lanes = <1>;
++			#address-cells = <3>;
++			#size-cells = <2>;
++
++			ranges = <0x81000000 0 0	  0x0fe00000 0 0x00100000   /* downstream I/O */
++				  0x82000000 0 0x00000000 0x08000000 0 0x07e00000>; /* non-prefetchable memory */
++
++			interrupts = <GIC_SPI 35 IRQ_TYPE_NONE>;
++			interrupt-names = "msi";
++			#interrupt-cells = <1>;
++			interrupt-map-mask = <0 0 0 0x7>;
++			interrupt-map = <0 0 0 1 &intc 0 36 IRQ_TYPE_LEVEL_HIGH>, /* int_a */
++					<0 0 0 2 &intc 0 37 IRQ_TYPE_LEVEL_HIGH>, /* int_b */
++					<0 0 0 3 &intc 0 38 IRQ_TYPE_LEVEL_HIGH>, /* int_c */
++					<0 0 0 4 &intc 0 39 IRQ_TYPE_LEVEL_HIGH>; /* int_d */
++
++			clocks = <&gcc PCIE_A_CLK>,
++				 <&gcc PCIE_H_CLK>,
++				 <&gcc PCIE_PHY_CLK>;
++			clock-names = "core", "iface", "phy";
++
++			resets = <&gcc PCIE_ACLK_RESET>,
++				 <&gcc PCIE_HCLK_RESET>,
++				 <&gcc PCIE_POR_RESET>,
++				 <&gcc PCIE_PCI_RESET>,
++				 <&gcc PCIE_PHY_RESET>;
++			reset-names = "axi", "ahb", "por", "pci", "phy";
++
++			status = "disabled";
++		};
++
++		pcie1: pci at 1b700000 {
++			compatible = "qcom,pcie-v0";
++			reg = <0x1b700000 0x1000
++			       0x1b702000 0x80
++			       0x1b800000 0x100
++			       0x31f00000 0x100000>;
++			reg-names = "dbi", "elbi", "parf", "config";
++			device_type = "pci";
++			linux,pci-domain = <1>;
++			bus-range = <0x00 0xff>;
++			num-lanes = <1>;
++			#address-cells = <3>;
++			#size-cells = <2>;
++
++			ranges = <0x81000000 0 0	  0x31e00000 0 0x00100000   /* downstream I/O */
++				  0x82000000 0 0x00000000 0x2e000000 0 0x03e00000>; /* non-prefetchable memory */
++
++			interrupts = <GIC_SPI 57 IRQ_TYPE_NONE>;
++			interrupt-names = "msi";
++			#interrupt-cells = <1>;
++			interrupt-map-mask = <0 0 0 0x7>;
++			interrupt-map = <0 0 0 1 &intc 0 58 IRQ_TYPE_LEVEL_HIGH>, /* int_a */
++					<0 0 0 2 &intc 0 59 IRQ_TYPE_LEVEL_HIGH>, /* int_b */
++					<0 0 0 3 &intc 0 60 IRQ_TYPE_LEVEL_HIGH>, /* int_c */
++					<0 0 0 4 &intc 0 61 IRQ_TYPE_LEVEL_HIGH>; /* int_d */
++
++			clocks = <&gcc PCIE_1_A_CLK>,
++				 <&gcc PCIE_1_H_CLK>,
++				 <&gcc PCIE_1_PHY_CLK>;
++			clock-names = "core", "iface", "phy";
++
++			resets = <&gcc PCIE_1_ACLK_RESET>,
++				 <&gcc PCIE_1_HCLK_RESET>,
++				 <&gcc PCIE_1_POR_RESET>,
++				 <&gcc PCIE_1_PCI_RESET>,
++				 <&gcc PCIE_1_PHY_RESET>;
++			reset-names = "axi", "ahb", "por", "pci", "phy";
++
++			status = "disabled";
++		};
++
++		pcie2: pci at 1b900000 {
++			compatible = "qcom,pcie-v0";
++			reg = <0x1b900000 0x1000
++			       0x1b902000 0x80
++			       0x1ba00000 0x100
++			       0x35f00000 0x100000>;
++			reg-names = "dbi", "elbi", "parf", "config";
++			device_type = "pci";
++			linux,pci-domain = <2>;
++			bus-range = <0x00 0xff>;
++			num-lanes = <1>;
++			#address-cells = <3>;
++			#size-cells = <2>;
++
++			ranges = <0x81000000 0 0	  0x35e00000 0 0x00100000   /* downstream I/O */
++				  0x82000000 0 0x00000000 0x32000000 0 0x03e00000>; /* non-prefetchable memory */
++
++			interrupts = <GIC_SPI 71 IRQ_TYPE_NONE>;
++			interrupt-names = "msi";
++			#interrupt-cells = <1>;
++			interrupt-map-mask = <0 0 0 0x7>;
++			interrupt-map = <0 0 0 1 &intc 0 72 IRQ_TYPE_LEVEL_HIGH>, /* int_a */
++					<0 0 0 2 &intc 0 73 IRQ_TYPE_LEVEL_HIGH>, /* int_b */
++					<0 0 0 3 &intc 0 74 IRQ_TYPE_LEVEL_HIGH>, /* int_c */
++					<0 0 0 4 &intc 0 75 IRQ_TYPE_LEVEL_HIGH>; /* int_d */
++
++			clocks = <&gcc PCIE_2_A_CLK>,
++				 <&gcc PCIE_2_H_CLK>,
++				 <&gcc PCIE_2_PHY_CLK>;
++			clock-names = "core", "iface", "phy";
++
++			resets = <&gcc PCIE_2_ACLK_RESET>,
++				 <&gcc PCIE_2_HCLK_RESET>,
++				 <&gcc PCIE_2_POR_RESET>,
++				 <&gcc PCIE_2_PCI_RESET>,
++				 <&gcc PCIE_2_PHY_RESET>;
++			reset-names = "axi", "ahb", "por", "pci", "phy";
++
++			status = "disabled";
++		};
+ 	};
+ };
diff --git a/target/linux/ipq806x/patches-4.0/113-ARM-qcom-automatically-select-PCI_DOMAINS-if-PCI-is-.patch b/target/linux/ipq806x/patches-4.0/113-ARM-qcom-automatically-select-PCI_DOMAINS-if-PCI-is-.patch
new file mode 100644
index 0000000..e2d3135
--- /dev/null
+++ b/target/linux/ipq806x/patches-4.0/113-ARM-qcom-automatically-select-PCI_DOMAINS-if-PCI-is-.patch
@@ -0,0 +1,29 @@
+From f004aa1dec6e2e206be025de15b115d60f2b21e3 Mon Sep 17 00:00:00 2001
+From: Mathieu Olivari <mathieu at codeaurora.org>
+Date: Tue, 21 Apr 2015 19:09:07 -0700
+Subject: [PATCH 9/9] ARM: qcom: automatically select PCI_DOMAINS if PCI is
+ enabled
+
+If multiple PCIe devices are present in the system, the kernel will
+panic at boot time when trying to scan the PCI buses. This happens on
+IPQ806x based platforms, which has 3 PCIe ports.
+
+Enabling this option allows the kernel to assign the pci-domains
+according to the device-tree content. This allows multiple PCIe
+controllers to coexist in the system.
+
+Signed-off-by: Mathieu Olivari <mathieu at codeaurora.org>
+---
+ arch/arm/mach-qcom/Kconfig | 1 +
+ 1 file changed, 1 insertion(+)
+
+--- a/arch/arm/mach-qcom/Kconfig
++++ b/arch/arm/mach-qcom/Kconfig
+@@ -5,6 +5,7 @@ menuconfig ARCH_QCOM
+ 	select ARM_AMBA
+ 	select PINCTRL
+ 	select QCOM_SCM if SMP
++	select PCI_DOMAINS if PCI
+ 	help
+ 	  Support for Qualcomm's devicetree based systems.
+ 
diff --git a/target/linux/ipq806x/patches-4.0/700-add-gmac-dts-suport.patch b/target/linux/ipq806x/patches-4.0/700-add-gmac-dts-suport.patch
index 89ebe66..16defd1 100644
--- a/target/linux/ipq806x/patches-4.0/700-add-gmac-dts-suport.patch
+++ b/target/linux/ipq806x/patches-4.0/700-add-gmac-dts-suport.patch
@@ -1,7 +1,7 @@
 --- a/arch/arm/boot/dts/qcom-ipq8064-ap148.dts
 +++ b/arch/arm/boot/dts/qcom-ipq8064-ap148.dts
-@@ -18,8 +18,15 @@
- 		bootargs = "console=ttyMSM0,115200 root=/dev/mtdblock12 rootfstype=squashfs,jffs2";
+@@ -22,8 +22,15 @@
+ 		linux,stdout-path = "serial0:115200n8";
  	};
  
 +	aliases {
@@ -16,7 +16,7 @@
  			i2c4_pins: i2c4_pinmux {
  				pins = "gpio12", "gpio13";
  				function = "gsbi4";
-@@ -34,6 +41,25 @@
+@@ -54,6 +61,25 @@
  					bias-none;
  				};
  			};
@@ -42,17 +42,17 @@
  		};
  
  		gsbi at 16300000 {
-@@ -72,6 +98,7 @@
+@@ -92,6 +118,7 @@
  					#size-cells = <1>;
  					spi-max-frequency = <50000000>;
  					reg = <0>;
 +					m25p,fast-read;
  
  					partition at 0 {
- 						label = "0:SBL1";
-@@ -148,5 +175,66 @@
- 		sata at 29000000 {
- 			status = "ok";
+ 						label = "lowlevel_init";
+@@ -142,5 +169,66 @@
+ 			pinctrl-0 = <&pcie2_pins>;
+ 			pinctrl-names = "default";
  		};
 +
 +		mdio0: mdio {
@@ -119,17 +119,17 @@
  };
 --- a/arch/arm/boot/dts/qcom-ipq8064.dtsi
 +++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi
-@@ -3,6 +3,7 @@
- #include "skeleton.dtsi"
+@@ -4,6 +4,7 @@
  #include <dt-bindings/clock/qcom,gcc-ipq806x.h>
  #include <dt-bindings/soc/qcom,gsbi.h>
+ #include <dt-bindings/reset/qcom,gcc-ipq806x.h>
 +#include <dt-bindings/interrupt-controller/arm-gic.h>
  
  / {
  	model = "Qualcomm IPQ8064";
-@@ -279,5 +280,42 @@
- 			#clock-cells = <1>;
- 			#reset-cells = <1>;
+@@ -415,5 +416,42 @@
+ 
+ 			status = "disabled";
  		};
 +
 +		nss-gmac-common {
-- 
2.1.4

_______________________________________________
openwrt-devel mailing list
openwrt-devel at lists.openwrt.org
https://lists.openwrt.org/cgi-bin/mailman/listinfo/openwrt-devel



More information about the openwrt-devel mailing list