mirror of
https://github.com/openwrt/openwrt.git
synced 2024-12-25 16:31:13 +00:00
5e49c57956
1)Changes - Rebased the patches for linux-4.4.7 - Added patch to fix spi nor fifo and dma support - Added patch to configure watchdog barktime 2)Testing Tested on IPQ AP148 Board: a. NOR boot and NAND boot b. ethernet network and ath10k wifi c. ubi sysupgrade UnTested dwc3 usb has not been validated on IPQ board(AP148) 3)Known Issues: Once we flash ubi image on AP148, and if we reset the board, uboot on first boot creates PEB and LEB for dynamic sized partitions, which is incorrect and not what linux expects which causes errors when trying to mount rootfs. In order to test this, we can use the below steps: a. Flash the ubi image on board and don't reset the board b. load the kernel fit image in RAM and boot from there. Signed-off-by: Ram Chandra Jangir <rjangi@codeaurora.org>
753 lines
19 KiB
Diff
753 lines
19 KiB
Diff
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@mm-sol.com>
|
|
X-Patchwork-Id: 6326161
|
|
Message-Id: <1430743338-10441-5-git-send-email-svarbanov@mm-sol.com>
|
|
To: Rob Herring <robh+dt@kernel.org>, Kumar Gala <galak@codeaurora.org>,
|
|
Mark Rutland <mark.rutland@arm.com>,
|
|
Grant Likely <grant.likely@linaro.org>,
|
|
Bjorn Helgaas <bhelgaas@google.com>,
|
|
Kishon Vijay Abraham I <kishon@ti.com>,
|
|
Russell King <linux@arm.linux.org.uk>, Arnd Bergmann <arnd@arndb.de>
|
|
Cc: linux-arm-msm@vger.kernel.org, linux-kernel@vger.kernel.org,
|
|
linux-arm-kernel@lists.infradead.org, devicetree@vger.kernel.org,
|
|
linux-pci@vger.kernel.org, Mathieu Olivari <mathieu@codeaurora.org>,
|
|
Srinivas Kandagatla <srinivas.kandagatla@linaro.org>,
|
|
Stanimir Varbanov <svarbanov@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@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
|
|
@@ -8253,6 +8253,13 @@
|
|
F: Documentation/devicetree/bindings/pci/hisilicon-pcie.txt
|
|
F: drivers/pci/host/pcie-hisi.c
|
|
|
|
+PCIE DRIVER FOR QUALCOMM MSM
|
|
+M: Stanimir Varbanov <svarbanov@mm-sol.com>
|
|
+L: linux-pci@vger.kernel.org
|
|
+L: linux-arm-msm@vger.kernel.org
|
|
+S: Maintained
|
|
+F: drivers/pci/host/*qcom*
|
|
+
|
|
PCMCIA SUBSYSTEM
|
|
P: Linux PCMCIA Team
|
|
L: linux-pcmcia@lists.infradead.org
|
|
--- a/drivers/pci/host/Kconfig
|
|
+++ b/drivers/pci/host/Kconfig
|
|
@@ -173,4 +173,13 @@
|
|
help
|
|
Say Y here if you want PCIe controller support on HiSilicon HIP05 SoC
|
|
|
|
+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,676 @@
|
|
+/*
|
|
+ * 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, 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", GPIOD_OUT_LOW);
|
|
+ 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@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
|
|
@@ -20,3 +20,4 @@
|
|
obj-$(CONFIG_PCIE_ALTERA) += pcie-altera.o
|
|
obj-$(CONFIG_PCIE_ALTERA_MSI) += pcie-altera-msi.o
|
|
obj-$(CONFIG_PCI_HISI) += pcie-hisi.o
|
|
+obj-$(CONFIG_PCIE_QCOM) += pcie-qcom.o
|