3c1f6e358d
Patches are generated using the "format-patch" command from the following location: *https://www.codeaurora.org/cgit/quic/kernel/galak-msm/log/?h=apq_ipq_base *rev=0771849495b4128cac2faf7d49c85c729fc48b20 Patches numbered 76/77/102/103 have already been integrated in 3.14.12, so they're not in this list. All these patches are either integrated are pending integration into kernel.org, therefore these patches should go away once the kernel gets upgraded to 3.16. Support is currently limited to AP148 board but can be extended to other platforms in the future. These changes do not cover ethernet connectivity. Signed-off-by: Mathieu Olivari <mathieu@codeaurora.org> SVN-Revision: 42334
726 lines
22 KiB
Diff
726 lines
22 KiB
Diff
From 98567c99b4dcd80fc9e5dd97229ebb9a7f6dab03 Mon Sep 17 00:00:00 2001
|
|
From: Kumar Gala <galak@codeaurora.org>
|
|
Date: Fri, 16 May 2014 11:53:23 -0500
|
|
Subject: [PATCH 138/182] PCI: qcom: Add support for pcie controllers on
|
|
IPQ8064
|
|
|
|
---
|
|
arch/arm/mach-qcom/Kconfig | 2 +
|
|
drivers/pci/host/Makefile | 1 +
|
|
drivers/pci/host/pci-qcom.c | 682 +++++++++++++++++++++++++++++++++++++++++++
|
|
3 files changed, 685 insertions(+)
|
|
create mode 100644 drivers/pci/host/pci-qcom.c
|
|
|
|
diff --git a/arch/arm/mach-qcom/Kconfig b/arch/arm/mach-qcom/Kconfig
|
|
index 63502cc..4d242e5 100644
|
|
--- a/arch/arm/mach-qcom/Kconfig
|
|
+++ b/arch/arm/mach-qcom/Kconfig
|
|
@@ -7,6 +7,8 @@ config ARCH_QCOM
|
|
select GENERIC_CLOCKEVENTS
|
|
select HAVE_SMP
|
|
select PINCTRL
|
|
+ select MIGHT_HAVE_PCI
|
|
+ select PCI_DOMAINS if PCI
|
|
select QCOM_SCM if SMP
|
|
help
|
|
Support for Qualcomm's devicetree based systems.
|
|
diff --git a/drivers/pci/host/Makefile b/drivers/pci/host/Makefile
|
|
index 13fb333..be80744 100644
|
|
--- a/drivers/pci/host/Makefile
|
|
+++ b/drivers/pci/host/Makefile
|
|
@@ -4,3 +4,4 @@ obj-$(CONFIG_PCI_IMX6) += pci-imx6.o
|
|
obj-$(CONFIG_PCI_MVEBU) += pci-mvebu.o
|
|
obj-$(CONFIG_PCI_TEGRA) += pci-tegra.o
|
|
obj-$(CONFIG_PCI_RCAR_GEN2) += pci-rcar-gen2.o
|
|
+obj-$(CONFIG_ARCH_QCOM) += pci-qcom.o
|
|
diff --git a/drivers/pci/host/pci-qcom.c b/drivers/pci/host/pci-qcom.c
|
|
new file mode 100644
|
|
index 0000000..76d7b88
|
|
--- /dev/null
|
|
+++ b/drivers/pci/host/pci-qcom.c
|
|
@@ -0,0 +1,682 @@
|
|
+/* Copyright (c) 2012-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.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * QCOM MSM PCIe controller driver.
|
|
+ */
|
|
+
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/pci.h>
|
|
+#include <linux/gpio.h>
|
|
+#include <linux/of_gpio.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/of_address.h>
|
|
+#include <linux/clk.h>
|
|
+#include <linux/reset.h>
|
|
+#include <linux/delay.h>
|
|
+
|
|
+/* Root Complex Port vendor/device IDs */
|
|
+#define PCIE_VENDOR_ID_RCP 0x17cb
|
|
+#define PCIE_DEVICE_ID_RCP 0x0101
|
|
+
|
|
+#define __set(v, a, b) (((v) << (b)) & GENMASK(a, b))
|
|
+
|
|
+#define PCIE20_PARF_PCS_DEEMPH 0x34
|
|
+#define PCIE20_PARF_PCS_DEEMPH_TX_DEEMPH_GEN1(x) __set(x, 21, 16)
|
|
+#define PCIE20_PARF_PCS_DEEMPH_TX_DEEMPH_GEN2_3_5DB(x) __set(x, 13, 8)
|
|
+#define PCIE20_PARF_PCS_DEEMPH_TX_DEEMPH_GEN2_6DB(x) __set(x, 5, 0)
|
|
+
|
|
+#define PCIE20_PARF_PCS_SWING 0x38
|
|
+#define PCIE20_PARF_PCS_SWING_TX_SWING_FULL(x) __set(x, 14, 8)
|
|
+#define PCIE20_PARF_PCS_SWING_TX_SWING_LOW(x) __set(x, 6, 0)
|
|
+
|
|
+#define PCIE20_PARF_PHY_CTRL 0x40
|
|
+#define PCIE20_PARF_PHY_CTRL_PHY_TX0_TERM_OFFST(x) __set(x, 20, 16)
|
|
+#define PCIE20_PARF_PHY_CTRL_PHY_LOS_LEVEL(x) __set(x, 12, 8)
|
|
+#define PCIE20_PARF_PHY_CTRL_PHY_RTUNE_REQ (1 << 4)
|
|
+#define PCIE20_PARF_PHY_CTRL_PHY_TEST_BURNIN (1 << 2)
|
|
+#define PCIE20_PARF_PHY_CTRL_PHY_TEST_BYPASS (1 << 1)
|
|
+#define PCIE20_PARF_PHY_CTRL_PHY_TEST_PWR_DOWN (1 << 0)
|
|
+
|
|
+#define PCIE20_PARF_PHY_REFCLK 0x4C
|
|
+#define PCIE20_PARF_CONFIG_BITS 0x50
|
|
+
|
|
+#define PCIE20_ELBI_SYS_CTRL 0x04
|
|
+#define PCIE20_ELBI_SYS_CTRL_LTSSM_EN 0x01
|
|
+
|
|
+#define PCIE20_CAP 0x70
|
|
+#define PCIE20_CAP_LINKCTRLSTATUS (PCIE20_CAP + 0x10)
|
|
+
|
|
+#define PCIE20_COMMAND_STATUS 0x04
|
|
+#define PCIE20_BUSNUMBERS 0x18
|
|
+#define PCIE20_MEMORY_BASE_LIMIT 0x20
|
|
+
|
|
+#define PCIE20_AXI_MSTR_RESP_COMP_CTRL0 0x818
|
|
+#define PCIE20_AXI_MSTR_RESP_COMP_CTRL1 0x81c
|
|
+#define PCIE20_PLR_IATU_VIEWPORT 0x900
|
|
+#define PCIE20_PLR_IATU_CTRL1 0x904
|
|
+#define PCIE20_PLR_IATU_CTRL2 0x908
|
|
+#define PCIE20_PLR_IATU_LBAR 0x90C
|
|
+#define PCIE20_PLR_IATU_UBAR 0x910
|
|
+#define PCIE20_PLR_IATU_LAR 0x914
|
|
+#define PCIE20_PLR_IATU_LTAR 0x918
|
|
+#define PCIE20_PLR_IATU_UTAR 0x91c
|
|
+
|
|
+#define MSM_PCIE_DEV_CFG_ADDR 0x01000000
|
|
+
|
|
+#define RD 0
|
|
+#define WR 1
|
|
+
|
|
+#define MAX_RC_NUM 3
|
|
+#define PCIE_BUS_PRIV_DATA(pdev) \
|
|
+ (((struct pci_sys_data *)pdev->bus->sysdata)->private_data)
|
|
+
|
|
+/* PCIe TLP types that we are interested in */
|
|
+#define PCI_CFG0_RDWR 0x4
|
|
+#define PCI_CFG1_RDWR 0x5
|
|
+
|
|
+#define readl_poll_timeout(addr, val, cond, sleep_us, timeout_us) \
|
|
+({ \
|
|
+ unsigned long timeout = jiffies + usecs_to_jiffies(timeout_us); \
|
|
+ might_sleep_if(timeout_us); \
|
|
+ for (;;) { \
|
|
+ (val) = readl(addr); \
|
|
+ if (cond) \
|
|
+ break; \
|
|
+ if (timeout_us && time_after(jiffies, timeout)) { \
|
|
+ (val) = readl(addr); \
|
|
+ break; \
|
|
+ } \
|
|
+ if (sleep_us) \
|
|
+ usleep_range(DIV_ROUND_UP(sleep_us, 4), sleep_us); \
|
|
+ } \
|
|
+ (cond) ? 0 : -ETIMEDOUT; \
|
|
+})
|
|
+
|
|
+struct qcom_pcie {
|
|
+ void __iomem *elbi_base;
|
|
+ void __iomem *parf_base;
|
|
+ void __iomem *dwc_base;
|
|
+ void __iomem *cfg_base;
|
|
+ int reset_gpio;
|
|
+ struct clk *iface_clk;
|
|
+ struct clk *bus_clk;
|
|
+ struct clk *phy_clk;
|
|
+ int irq_int[4];
|
|
+ struct reset_control *axi_reset;
|
|
+ struct reset_control *ahb_reset;
|
|
+ struct reset_control *por_reset;
|
|
+ struct reset_control *pci_reset;
|
|
+ struct reset_control *phy_reset;
|
|
+
|
|
+ struct resource conf;
|
|
+ struct resource io;
|
|
+ struct resource mem;
|
|
+};
|
|
+
|
|
+static int qcom_pcie_map_irq(const struct pci_dev *dev, u8 slot, u8 pin);
|
|
+static int qcom_pcie_setup(int nr, struct pci_sys_data *sys);
|
|
+static int msm_pcie_rd_conf(struct pci_bus *bus, u32 devfn, int where,
|
|
+ int size, u32 *val);
|
|
+static int msm_pcie_wr_conf(struct pci_bus *bus, u32 devfn,
|
|
+ int where, int size, u32 val);
|
|
+
|
|
+static struct pci_ops qcom_pcie_ops = {
|
|
+ .read = msm_pcie_rd_conf,
|
|
+ .write = msm_pcie_wr_conf,
|
|
+};
|
|
+
|
|
+static struct hw_pci qcom_hw_pci[MAX_RC_NUM] = {
|
|
+ {
|
|
+#ifdef CONFIG_PCI_DOMAINS
|
|
+ .domain = 0,
|
|
+#endif
|
|
+ .ops = &qcom_pcie_ops,
|
|
+ .nr_controllers = 1,
|
|
+ .swizzle = pci_common_swizzle,
|
|
+ .setup = qcom_pcie_setup,
|
|
+ .map_irq = qcom_pcie_map_irq,
|
|
+ },
|
|
+ {
|
|
+#ifdef CONFIG_PCI_DOMAINS
|
|
+ .domain = 1,
|
|
+#endif
|
|
+ .ops = &qcom_pcie_ops,
|
|
+ .nr_controllers = 1,
|
|
+ .swizzle = pci_common_swizzle,
|
|
+ .setup = qcom_pcie_setup,
|
|
+ .map_irq = qcom_pcie_map_irq,
|
|
+ },
|
|
+ {
|
|
+#ifdef CONFIG_PCI_DOMAINS
|
|
+ .domain = 2,
|
|
+#endif
|
|
+ .ops = &qcom_pcie_ops,
|
|
+ .nr_controllers = 1,
|
|
+ .swizzle = pci_common_swizzle,
|
|
+ .setup = qcom_pcie_setup,
|
|
+ .map_irq = qcom_pcie_map_irq,
|
|
+ },
|
|
+};
|
|
+
|
|
+static int nr_controllers;
|
|
+static DEFINE_SPINLOCK(qcom_hw_pci_lock);
|
|
+
|
|
+static inline struct qcom_pcie *sys_to_pcie(struct pci_sys_data *sys)
|
|
+{
|
|
+ return sys->private_data;
|
|
+}
|
|
+
|
|
+inline int is_msm_pcie_rc(struct pci_bus *bus)
|
|
+{
|
|
+ return (bus->number == 0);
|
|
+}
|
|
+
|
|
+static int qcom_pcie_is_link_up(struct qcom_pcie *dev)
|
|
+{
|
|
+ return readl_relaxed(dev->dwc_base + PCIE20_CAP_LINKCTRLSTATUS) & BIT(29);
|
|
+}
|
|
+
|
|
+inline int msm_pcie_get_cfgtype(struct pci_bus *bus)
|
|
+{
|
|
+ /*
|
|
+ * http://www.tldp.org/LDP/tlk/dd/pci.html
|
|
+ * Pass it onto the secondary bus interface unchanged if the
|
|
+ * bus number specified is greater than the secondary bus
|
|
+ * number and less than or equal to the subordinate bus
|
|
+ * number.
|
|
+ *
|
|
+ * Read/Write to the RC and Device/Switch connected to the RC
|
|
+ * are CFG0 type transactions. Rest have to be forwarded
|
|
+ * down stream as CFG1 transactions.
|
|
+ *
|
|
+ */
|
|
+ if (bus->number == 0)
|
|
+ return PCI_CFG0_RDWR;
|
|
+
|
|
+ return PCI_CFG0_RDWR;
|
|
+}
|
|
+
|
|
+void msm_pcie_config_cfgtype(struct pci_bus *bus, u32 devfn)
|
|
+{
|
|
+ uint32_t bdf, cfgtype;
|
|
+ struct qcom_pcie *dev = sys_to_pcie(bus->sysdata);
|
|
+
|
|
+ cfgtype = msm_pcie_get_cfgtype(bus);
|
|
+
|
|
+ if (cfgtype == PCI_CFG0_RDWR) {
|
|
+ bdf = MSM_PCIE_DEV_CFG_ADDR;
|
|
+ } else {
|
|
+ /*
|
|
+ * iATU Lower Target Address Register
|
|
+ * Bits Description
|
|
+ * *-1:0 Forms bits [*:0] of the
|
|
+ * start address of the new
|
|
+ * address of the translated
|
|
+ * region. The start address
|
|
+ * must be aligned to a
|
|
+ * CX_ATU_MIN_REGION_SIZE kB
|
|
+ * boundary, so these bits are
|
|
+ * always 0. A write to this
|
|
+ * location is ignored by the
|
|
+ * PCIe core.
|
|
+ * 31:*1 Forms bits [31:*] of the of
|
|
+ * the new address of the
|
|
+ * translated region.
|
|
+ *
|
|
+ * * is log2(CX_ATU_MIN_REGION_SIZE)
|
|
+ */
|
|
+ bdf = (((bus->number & 0xff) << 24) & 0xff000000) |
|
|
+ (((devfn & 0xff) << 16) & 0x00ff0000);
|
|
+ }
|
|
+
|
|
+ writel_relaxed(0, dev->dwc_base + PCIE20_PLR_IATU_VIEWPORT);
|
|
+ wmb();
|
|
+
|
|
+ /* Program Bdf Address */
|
|
+ writel_relaxed(bdf, dev->dwc_base + PCIE20_PLR_IATU_LTAR);
|
|
+ wmb();
|
|
+
|
|
+ /* Write Config Request Type */
|
|
+ writel_relaxed(cfgtype, dev->dwc_base + PCIE20_PLR_IATU_CTRL1);
|
|
+ wmb();
|
|
+}
|
|
+
|
|
+static inline int msm_pcie_oper_conf(struct pci_bus *bus, u32 devfn, int oper,
|
|
+ int where, int size, u32 *val)
|
|
+{
|
|
+ uint32_t word_offset, byte_offset, mask;
|
|
+ uint32_t rd_val, wr_val;
|
|
+ struct qcom_pcie *dev = sys_to_pcie(bus->sysdata);
|
|
+ void __iomem *config_base;
|
|
+ int rc;
|
|
+
|
|
+ rc = is_msm_pcie_rc(bus);
|
|
+
|
|
+ /*
|
|
+ * For downstream bus, make sure link is up
|
|
+ */
|
|
+ if (rc && (devfn != 0)) {
|
|
+ *val = ~0;
|
|
+ return PCIBIOS_DEVICE_NOT_FOUND;
|
|
+ } else if ((!rc) && (!qcom_pcie_is_link_up(dev))) {
|
|
+ *val = ~0;
|
|
+ return PCIBIOS_DEVICE_NOT_FOUND;
|
|
+ }
|
|
+
|
|
+ msm_pcie_config_cfgtype(bus, devfn);
|
|
+
|
|
+ word_offset = where & ~0x3;
|
|
+ byte_offset = where & 0x3;
|
|
+ mask = (~0 >> (8 * (4 - size))) << (8 * byte_offset);
|
|
+
|
|
+ config_base = (rc) ? dev->dwc_base : dev->cfg_base;
|
|
+ rd_val = readl_relaxed(config_base + word_offset);
|
|
+
|
|
+ if (oper == RD) {
|
|
+ *val = ((rd_val & mask) >> (8 * byte_offset));
|
|
+ } else {
|
|
+ wr_val = (rd_val & ~mask) |
|
|
+ ((*val << (8 * byte_offset)) & mask);
|
|
+ writel_relaxed(wr_val, config_base + word_offset);
|
|
+ wmb(); /* ensure config data is written to hardware register */
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int msm_pcie_rd_conf(struct pci_bus *bus, u32 devfn, int where,
|
|
+ int size, u32 *val)
|
|
+{
|
|
+ return msm_pcie_oper_conf(bus, devfn, RD, where, size, val);
|
|
+}
|
|
+
|
|
+static int msm_pcie_wr_conf(struct pci_bus *bus, u32 devfn,
|
|
+ int where, int size, u32 val)
|
|
+{
|
|
+ /*
|
|
+ *Attempt to reset secondary bus is causing PCIE core to reset.
|
|
+ *Disable secondary bus reset functionality.
|
|
+ */
|
|
+ if ((bus->number == 0) && (where == PCI_BRIDGE_CONTROL) &&
|
|
+ (val & PCI_BRIDGE_CTL_BUS_RESET)) {
|
|
+ pr_info("PCIE secondary bus reset not supported\n");
|
|
+ val &= ~PCI_BRIDGE_CTL_BUS_RESET;
|
|
+ }
|
|
+
|
|
+ return msm_pcie_oper_conf(bus, devfn, WR, where, size, &val);
|
|
+}
|
|
+
|
|
+static int qcom_pcie_map_irq(const struct pci_dev *dev, u8 slot, u8 pin)
|
|
+{
|
|
+ struct qcom_pcie *pcie_dev = PCIE_BUS_PRIV_DATA(dev);
|
|
+
|
|
+ return pcie_dev->irq_int[pin-1];
|
|
+}
|
|
+
|
|
+static int qcom_pcie_setup(int nr, struct pci_sys_data *sys)
|
|
+{
|
|
+ struct qcom_pcie *qcom_pcie = sys->private_data;
|
|
+
|
|
+ /*
|
|
+ * specify linux PCI framework to allocate device memory (BARs)
|
|
+ * from msm_pcie_dev.dev_mem_res resource.
|
|
+ */
|
|
+ sys->mem_offset = 0;
|
|
+ sys->io_offset = 0;
|
|
+
|
|
+ pci_add_resource(&sys->resources, &qcom_pcie->mem);
|
|
+ pci_add_resource(&sys->resources, &qcom_pcie->io);
|
|
+
|
|
+ return 1;
|
|
+}
|
|
+
|
|
+static inline void qcom_elbi_writel_relaxed(struct qcom_pcie *pcie, u32 val, u32 reg)
|
|
+{
|
|
+ writel_relaxed(val, pcie->elbi_base + reg);
|
|
+}
|
|
+
|
|
+static inline u32 qcom_elbi_readl_relaxed(struct qcom_pcie *pcie, u32 reg)
|
|
+{
|
|
+ return readl_relaxed(pcie->elbi_base + reg);
|
|
+}
|
|
+
|
|
+static inline void qcom_parf_writel_relaxed(struct qcom_pcie *pcie, u32 val, u32 reg)
|
|
+{
|
|
+ writel_relaxed(val, pcie->parf_base + reg);
|
|
+}
|
|
+
|
|
+static inline u32 qcom_parf_readl_relaxed(struct qcom_pcie *pcie, u32 reg)
|
|
+{
|
|
+ return readl_relaxed(pcie->parf_base + reg);
|
|
+}
|
|
+
|
|
+static void msm_pcie_write_mask(void __iomem *addr,
|
|
+ uint32_t clear_mask, uint32_t set_mask)
|
|
+{
|
|
+ uint32_t val;
|
|
+
|
|
+ val = (readl_relaxed(addr) & ~clear_mask) | set_mask;
|
|
+ writel_relaxed(val, addr);
|
|
+ wmb(); /* ensure data is written to hardware register */
|
|
+}
|
|
+
|
|
+static void qcom_pcie_config_controller(struct qcom_pcie *dev)
|
|
+{
|
|
+ /*
|
|
+ * program and enable address translation region 0 (device config
|
|
+ * address space); region type config;
|
|
+ * axi config address range to device config address range
|
|
+ */
|
|
+ writel_relaxed(0, dev->dwc_base + PCIE20_PLR_IATU_VIEWPORT);
|
|
+ /* ensure that hardware locks the region before programming it */
|
|
+ wmb();
|
|
+
|
|
+ writel_relaxed(4, dev->dwc_base + PCIE20_PLR_IATU_CTRL1);
|
|
+ writel_relaxed(BIT(31), dev->dwc_base + PCIE20_PLR_IATU_CTRL2);
|
|
+ writel_relaxed(dev->conf.start, dev->dwc_base + PCIE20_PLR_IATU_LBAR);
|
|
+ writel_relaxed(0, dev->dwc_base + PCIE20_PLR_IATU_UBAR);
|
|
+ writel_relaxed(dev->conf.end, dev->dwc_base + PCIE20_PLR_IATU_LAR);
|
|
+ writel_relaxed(MSM_PCIE_DEV_CFG_ADDR,
|
|
+ dev->dwc_base + PCIE20_PLR_IATU_LTAR);
|
|
+ writel_relaxed(0, dev->dwc_base + PCIE20_PLR_IATU_UTAR);
|
|
+ /* ensure that hardware registers the configuration */
|
|
+ wmb();
|
|
+
|
|
+ /*
|
|
+ * program and enable address translation region 2 (device resource
|
|
+ * address space); region type memory;
|
|
+ * axi device bar address range to device bar address range
|
|
+ */
|
|
+ writel_relaxed(2, dev->dwc_base + PCIE20_PLR_IATU_VIEWPORT);
|
|
+ /* ensure that hardware locks the region before programming it */
|
|
+ wmb();
|
|
+
|
|
+ writel_relaxed(0, dev->dwc_base + PCIE20_PLR_IATU_CTRL1);
|
|
+ writel_relaxed(BIT(31), dev->dwc_base + PCIE20_PLR_IATU_CTRL2);
|
|
+ writel_relaxed(dev->mem.start, dev->dwc_base + PCIE20_PLR_IATU_LBAR);
|
|
+ writel_relaxed(0, dev->dwc_base + PCIE20_PLR_IATU_UBAR);
|
|
+ writel_relaxed(dev->mem.end, dev->dwc_base + PCIE20_PLR_IATU_LAR);
|
|
+ writel_relaxed(dev->mem.start,
|
|
+ dev->dwc_base + PCIE20_PLR_IATU_LTAR);
|
|
+ writel_relaxed(0, dev->dwc_base + PCIE20_PLR_IATU_UTAR);
|
|
+ /* ensure that hardware registers the configuration */
|
|
+ wmb();
|
|
+
|
|
+ /* 1K PCIE buffer setting */
|
|
+ writel_relaxed(0x3, dev->dwc_base + PCIE20_AXI_MSTR_RESP_COMP_CTRL0);
|
|
+ writel_relaxed(0x1, dev->dwc_base + PCIE20_AXI_MSTR_RESP_COMP_CTRL1);
|
|
+ /* ensure that hardware registers the configuration */
|
|
+ wmb();
|
|
+}
|
|
+
|
|
+static int qcom_pcie_probe(struct platform_device *pdev)
|
|
+{
|
|
+ unsigned long flags;
|
|
+ struct qcom_pcie *qcom_pcie;
|
|
+ struct device_node *np = pdev->dev.of_node;
|
|
+ struct resource *elbi_base, *parf_base, *dwc_base;
|
|
+ struct hw_pci *hw;
|
|
+ struct of_pci_range range;
|
|
+ struct of_pci_range_parser parser;
|
|
+ int ret, i;
|
|
+ u32 val;
|
|
+
|
|
+ qcom_pcie = devm_kzalloc(&pdev->dev, sizeof(*qcom_pcie), GFP_KERNEL);
|
|
+ if (!qcom_pcie) {
|
|
+ dev_err(&pdev->dev, "no memory for qcom_pcie\n");
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ elbi_base = platform_get_resource_byname(pdev, IORESOURCE_MEM, "elbi");
|
|
+ qcom_pcie->elbi_base = devm_ioremap_resource(&pdev->dev, elbi_base);
|
|
+ if (IS_ERR(qcom_pcie->elbi_base)) {
|
|
+ dev_err(&pdev->dev, "Failed to ioremap elbi space\n");
|
|
+ return PTR_ERR(qcom_pcie->elbi_base);
|
|
+ }
|
|
+
|
|
+ parf_base = platform_get_resource_byname(pdev, IORESOURCE_MEM, "parf");
|
|
+ qcom_pcie->parf_base = devm_ioremap_resource(&pdev->dev, parf_base);
|
|
+ if (IS_ERR(qcom_pcie->parf_base)) {
|
|
+ dev_err(&pdev->dev, "Failed to ioremap parf space\n");
|
|
+ return PTR_ERR(qcom_pcie->parf_base);
|
|
+ }
|
|
+
|
|
+ dwc_base = platform_get_resource_byname(pdev, IORESOURCE_MEM, "base");
|
|
+ qcom_pcie->dwc_base = devm_ioremap_resource(&pdev->dev, dwc_base);
|
|
+ if (IS_ERR(qcom_pcie->dwc_base)) {
|
|
+ dev_err(&pdev->dev, "Failed to ioremap dwc_base space\n");
|
|
+ return PTR_ERR(qcom_pcie->dwc_base);
|
|
+ }
|
|
+
|
|
+ if (of_pci_range_parser_init(&parser, np)) {
|
|
+ dev_err(&pdev->dev, "missing ranges property\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ /* Get the I/O and memory ranges from DT */
|
|
+ for_each_of_pci_range(&parser, &range) {
|
|
+ switch (range.pci_space & 0x3) {
|
|
+ case 0: /* cfg */
|
|
+ of_pci_range_to_resource(&range, np, &qcom_pcie->conf);
|
|
+ qcom_pcie->conf.flags = IORESOURCE_MEM;
|
|
+ break;
|
|
+ case 1: /* io */
|
|
+ of_pci_range_to_resource(&range, np, &qcom_pcie->io);
|
|
+ break;
|
|
+ default: /* mem */
|
|
+ of_pci_range_to_resource(&range, np, &qcom_pcie->mem);
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ qcom_pcie->cfg_base = devm_ioremap_resource(&pdev->dev, &qcom_pcie->conf);
|
|
+ if (IS_ERR(qcom_pcie->cfg_base)) {
|
|
+ dev_err(&pdev->dev, "Failed to ioremap PCIe cfg space\n");
|
|
+ return PTR_ERR(qcom_pcie->cfg_base);
|
|
+ }
|
|
+
|
|
+ qcom_pcie->reset_gpio = of_get_named_gpio(np, "reset-gpio", 0);
|
|
+ if (!gpio_is_valid(qcom_pcie->reset_gpio)) {
|
|
+ dev_err(&pdev->dev, "pcie reset gpio is not valid\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ ret = devm_gpio_request_one(&pdev->dev, qcom_pcie->reset_gpio,
|
|
+ GPIOF_DIR_OUT, "pcie_reset");
|
|
+ if (ret) {
|
|
+ dev_err(&pdev->dev, "Failed to request pcie reset gpio\n");
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ qcom_pcie->iface_clk = devm_clk_get(&pdev->dev, "iface");
|
|
+ if (IS_ERR(qcom_pcie->iface_clk)) {
|
|
+ dev_err(&pdev->dev, "Failed to get pcie iface clock\n");
|
|
+ return PTR_ERR(qcom_pcie->iface_clk);
|
|
+ }
|
|
+
|
|
+ qcom_pcie->phy_clk = devm_clk_get(&pdev->dev, "phy");
|
|
+ if (IS_ERR(qcom_pcie->phy_clk)) {
|
|
+ dev_err(&pdev->dev, "Failed to get pcie phy clock\n");
|
|
+ return PTR_ERR(qcom_pcie->phy_clk);
|
|
+ }
|
|
+
|
|
+ qcom_pcie->bus_clk = devm_clk_get(&pdev->dev, "core");
|
|
+ if (IS_ERR(qcom_pcie->bus_clk)) {
|
|
+ dev_err(&pdev->dev, "Failed to get pcie core clock\n");
|
|
+ return PTR_ERR(qcom_pcie->bus_clk);
|
|
+ }
|
|
+
|
|
+ qcom_pcie->axi_reset = devm_reset_control_get(&pdev->dev, "axi");
|
|
+ if (IS_ERR(qcom_pcie->axi_reset)) {
|
|
+ dev_err(&pdev->dev, "Failed to get axi reset\n");
|
|
+ return PTR_ERR(qcom_pcie->axi_reset);
|
|
+ }
|
|
+
|
|
+ qcom_pcie->ahb_reset = devm_reset_control_get(&pdev->dev, "ahb");
|
|
+ if (IS_ERR(qcom_pcie->ahb_reset)) {
|
|
+ dev_err(&pdev->dev, "Failed to get ahb reset\n");
|
|
+ return PTR_ERR(qcom_pcie->ahb_reset);
|
|
+ }
|
|
+
|
|
+ qcom_pcie->por_reset = devm_reset_control_get(&pdev->dev, "por");
|
|
+ if (IS_ERR(qcom_pcie->por_reset)) {
|
|
+ dev_err(&pdev->dev, "Failed to get por reset\n");
|
|
+ return PTR_ERR(qcom_pcie->por_reset);
|
|
+ }
|
|
+
|
|
+ qcom_pcie->pci_reset = devm_reset_control_get(&pdev->dev, "pci");
|
|
+ if (IS_ERR(qcom_pcie->pci_reset)) {
|
|
+ dev_err(&pdev->dev, "Failed to get pci reset\n");
|
|
+ return PTR_ERR(qcom_pcie->pci_reset);
|
|
+ }
|
|
+
|
|
+ qcom_pcie->phy_reset = devm_reset_control_get(&pdev->dev, "phy");
|
|
+ if (IS_ERR(qcom_pcie->phy_reset)) {
|
|
+ dev_err(&pdev->dev, "Failed to get phy reset\n");
|
|
+ return PTR_ERR(qcom_pcie->phy_reset);
|
|
+ }
|
|
+
|
|
+ for (i = 0; i < 4; i++) {
|
|
+ qcom_pcie->irq_int[i] = platform_get_irq(pdev, i+1);
|
|
+ if (qcom_pcie->irq_int[i] < 0) {
|
|
+ dev_err(&pdev->dev, "failed to get irq resource\n");
|
|
+ return qcom_pcie->irq_int[i];
|
|
+ }
|
|
+ }
|
|
+
|
|
+ gpio_set_value(qcom_pcie->reset_gpio, 0);
|
|
+ usleep_range(10000, 15000);
|
|
+
|
|
+ /* assert PCIe PARF reset while powering the core */
|
|
+ reset_control_assert(qcom_pcie->ahb_reset);
|
|
+
|
|
+ /* enable clocks */
|
|
+ ret = clk_prepare_enable(qcom_pcie->iface_clk);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ ret = clk_prepare_enable(qcom_pcie->phy_clk);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ ret = clk_prepare_enable(qcom_pcie->bus_clk);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ /*
|
|
+ * de-assert PCIe PARF reset;
|
|
+ * wait 1us before accessing PARF registers
|
|
+ */
|
|
+ reset_control_deassert(qcom_pcie->ahb_reset);
|
|
+ udelay(1);
|
|
+
|
|
+ /* enable PCIe clocks and resets */
|
|
+ msm_pcie_write_mask(qcom_pcie->parf_base + PCIE20_PARF_PHY_CTRL, BIT(0), 0);
|
|
+
|
|
+ /* Set Tx Termination Offset */
|
|
+ val = qcom_parf_readl_relaxed(qcom_pcie, PCIE20_PARF_PHY_CTRL);
|
|
+ val |= PCIE20_PARF_PHY_CTRL_PHY_TX0_TERM_OFFST(7);
|
|
+ qcom_parf_writel_relaxed(qcom_pcie, val, PCIE20_PARF_PHY_CTRL);
|
|
+
|
|
+ /* PARF programming */
|
|
+ qcom_parf_writel_relaxed(qcom_pcie, PCIE20_PARF_PCS_DEEMPH_TX_DEEMPH_GEN1(0x18) |
|
|
+ PCIE20_PARF_PCS_DEEMPH_TX_DEEMPH_GEN2_3_5DB(0x18) |
|
|
+ PCIE20_PARF_PCS_DEEMPH_TX_DEEMPH_GEN2_6DB(0x22),
|
|
+ PCIE20_PARF_PCS_DEEMPH);
|
|
+ qcom_parf_writel_relaxed(qcom_pcie, PCIE20_PARF_PCS_SWING_TX_SWING_FULL(0x78) |
|
|
+ PCIE20_PARF_PCS_SWING_TX_SWING_LOW(0x78),
|
|
+ PCIE20_PARF_PCS_SWING);
|
|
+ qcom_parf_writel_relaxed(qcom_pcie, (4<<24), PCIE20_PARF_CONFIG_BITS);
|
|
+ /* ensure that hardware registers the PARF configuration */
|
|
+ wmb();
|
|
+
|
|
+ /* enable reference clock */
|
|
+ msm_pcie_write_mask(qcom_pcie->parf_base + PCIE20_PARF_PHY_REFCLK, BIT(12), BIT(16));
|
|
+
|
|
+ /* ensure that access is enabled before proceeding */
|
|
+ wmb();
|
|
+
|
|
+ /* de-assert PICe PHY, Core, POR and AXI clk domain resets */
|
|
+ reset_control_deassert(qcom_pcie->phy_reset);
|
|
+ reset_control_deassert(qcom_pcie->pci_reset);
|
|
+ reset_control_deassert(qcom_pcie->por_reset);
|
|
+ reset_control_deassert(qcom_pcie->axi_reset);
|
|
+
|
|
+ /* wait 150ms for clock acquisition */
|
|
+ usleep_range(10000, 15000);
|
|
+
|
|
+ /* de-assert PCIe reset link to bring EP out of reset */
|
|
+ gpio_set_value(qcom_pcie->reset_gpio, 1 - 0);
|
|
+ usleep_range(10000, 15000);
|
|
+
|
|
+ /* enable link training */
|
|
+ val = qcom_elbi_readl_relaxed(qcom_pcie, PCIE20_ELBI_SYS_CTRL);
|
|
+ val |= PCIE20_ELBI_SYS_CTRL_LTSSM_EN;
|
|
+ qcom_elbi_writel_relaxed(qcom_pcie, val, PCIE20_ELBI_SYS_CTRL);
|
|
+ wmb();
|
|
+
|
|
+ /* poll for link to come up for upto 100ms */
|
|
+ ret = readl_poll_timeout(
|
|
+ (qcom_pcie->dwc_base + PCIE20_CAP_LINKCTRLSTATUS),
|
|
+ val, (val & BIT(29)), 10000, 100000);
|
|
+
|
|
+ printk("link initialized %d\n", ret);
|
|
+
|
|
+ qcom_pcie_config_controller(qcom_pcie);
|
|
+
|
|
+ platform_set_drvdata(pdev, qcom_pcie);
|
|
+
|
|
+ spin_lock_irqsave(&qcom_hw_pci_lock, flags);
|
|
+ qcom_hw_pci[nr_controllers].private_data = (void **)&qcom_pcie;
|
|
+ hw = &qcom_hw_pci[nr_controllers];
|
|
+ nr_controllers++;
|
|
+ spin_unlock_irqrestore(&qcom_hw_pci_lock, flags);
|
|
+
|
|
+ pci_common_init(hw);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int __exit qcom_pcie_remove(struct platform_device *pdev)
|
|
+{
|
|
+ struct qcom_pcie *qcom_pcie = platform_get_drvdata(pdev);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static struct of_device_id qcom_pcie_match[] = {
|
|
+ { .compatible = "qcom,pcie-ipq8064", },
|
|
+ {}
|
|
+};
|
|
+
|
|
+static struct platform_driver qcom_pcie_driver = {
|
|
+ .probe = qcom_pcie_probe,
|
|
+ .remove = qcom_pcie_remove,
|
|
+ .driver = {
|
|
+ .name = "qcom_pcie",
|
|
+ .owner = THIS_MODULE,
|
|
+ .of_match_table = qcom_pcie_match,
|
|
+ },
|
|
+};
|
|
+
|
|
+static int qcom_pcie_init(void)
|
|
+{
|
|
+ return platform_driver_register(&qcom_pcie_driver);
|
|
+}
|
|
+subsys_initcall(qcom_pcie_init);
|
|
+
|
|
+/* RC do not represent the right class; set it to PCI_CLASS_BRIDGE_PCI */
|
|
+static void msm_pcie_fixup_early(struct pci_dev *dev)
|
|
+{
|
|
+ if (dev->hdr_type == 1)
|
|
+ dev->class = (dev->class & 0xff) | (PCI_CLASS_BRIDGE_PCI << 8);
|
|
+}
|
|
+DECLARE_PCI_FIXUP_EARLY(PCIE_VENDOR_ID_RCP, PCIE_DEVICE_ID_RCP, msm_pcie_fixup_early);
|
|
--
|
|
1.7.10.4
|
|
|