From patchwork Thu Dec 3 13:35:22 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stanimir Varbanov X-Patchwork-Id: 7760011 X-Patchwork-Delegate: agross@codeaurora.org Return-Path: X-Original-To: patchwork-linux-arm-msm@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork1.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.136]) by patchwork1.web.kernel.org (Postfix) with ESMTP id B1AA89F30B for ; Thu, 3 Dec 2015 13:38:18 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 190EC20574 for ; Thu, 3 Dec 2015 13:38:17 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 049632050E for ; Thu, 3 Dec 2015 13:38:15 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1760247AbbLCNhO (ORCPT ); Thu, 3 Dec 2015 08:37:14 -0500 Received: from mail-wm0-f54.google.com ([74.125.82.54]:37140 "EHLO mail-wm0-f54.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1760238AbbLCNgP (ORCPT ); Thu, 3 Dec 2015 08:36:15 -0500 Received: by wmww144 with SMTP id w144so22391257wmw.0 for ; Thu, 03 Dec 2015 05:36:13 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro-org.20150623.gappssmtp.com; s=20150623; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=/AuKURLCfYmbjOqn0DE+27V+70IokXUDv0LXtAOXtRA=; b=V8mBr2OO1fCRuFqPfZRs7nqSXDqYKn4qFBu8kibWMiY/MgPppwBtCWYirvpXBRwsir b/ktMX5q3+oqnaSP80c9QUlBYGBoHUyxGntkd7C5MV/evAVJMwlDmPaykPEuI3HP5ogM zNZ3Fuuf0kEK1/A9AE5OE+qQXDVYlYToYrP6t7kMSY3ayNctinswWfCxPOcOzxXzQ5KC PXCHujiJaTYigJ6FQ9H3pnvExE1zVdMioJLIiHZgmflez0PCKe/Wwox4QuxkgzEnGcQF wLqnyD3gLnZJeqrDepKkMJ6cGoSi/AuKatJO7FdUparx1lBSrz1G7YLuYWsZ+fM+8zny Ts/w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=/AuKURLCfYmbjOqn0DE+27V+70IokXUDv0LXtAOXtRA=; b=XyF3p52cxH3mSCo/eByLF1o+zv0Mjjf9sU1xynllAIg5s8CBWy7aH2A6VXun6ALEuu 09LWtcpYoEOmN9ngRJzhubzp7t+GX/FNyc/r1oiDGZlPt+re0KhEtb+I1T2NsO1Ddm+Z bMiHTTHFoXaRGj0RT6+BQ8MSgfhiJRApXgsarcsGtXxV0i1L8Xln1nuGzAb7lo6BUBlm ylfU/R4CkXGsPJ2Ob48OldUSk1wPwXHdN1M2gmhUj/fLQRhfjvGCzZSJIlh56XzSpIVl xSu+CM+lEJM8Hx/11d6KAOeLT4YEbe8DO4qQ3TcYRpFIvNWrKBDcYf5C7Zvkwmf00pED 03Xg== X-Gm-Message-State: ALoCoQkVmtn52eM/d18BvqK+MQsOyMuA2EdOhsxE1W1mkD+gEjeGaNAx+cQmntx3tv5Gv4uY3MBX X-Received: by 10.28.141.140 with SMTP id p134mr51820077wmd.6.1449149772904; Thu, 03 Dec 2015 05:36:12 -0800 (PST) Received: from mms734.qualcomm.mm-sol.com ([37.157.136.206]) by smtp.gmail.com with ESMTPSA id h189sm35769187wme.1.2015.12.03.05.36.11 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Thu, 03 Dec 2015 05:36:12 -0800 (PST) From: Stanimir Varbanov To: 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, Bjorn Helgaas Cc: Srinivas Kandagatla , Rob Herring , Rob Herring , Mark Rutland , Pawel Moll , Ian Campbell , Arnd Bergmann , Jingoo Han , Pratyush Anand , Bjorn Andersson , Stanimir Varbanov , Stanimir Varbanov Subject: [PATCH v4 3/5] PCI: qcom: Add Qualcomm PCIe controller driver Date: Thu, 3 Dec 2015 15:35:22 +0200 Message-Id: <1449149725-27607-4-git-send-email-stanimir.varbanov@linaro.org> X-Mailer: git-send-email 1.7.9.5 In-Reply-To: <1449149725-27607-1-git-send-email-stanimir.varbanov@linaro.org> References: <1449149725-27607-1-git-send-email-stanimir.varbanov@linaro.org> Sender: linux-arm-msm-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-arm-msm@vger.kernel.org X-Spam-Status: No, score=-6.8 required=5.0 tests=BAYES_00,DKIM_SIGNED, RCVD_IN_DNSWL_HI,T_DKIM_INVALID,T_RP_MATCHES_RCVD,UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP From: Stanimir Varbanov 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 Signed-off-by: Stanimir Varbanov --- MAINTAINERS | 7 + drivers/pci/host/Kconfig | 10 + drivers/pci/host/Makefile | 1 + drivers/pci/host/pcie-qcom.c | 624 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 642 insertions(+) create mode 100644 drivers/pci/host/pcie-qcom.c diff --git a/MAINTAINERS b/MAINTAINERS index cba790b42f23..c07377148399 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8247,6 +8247,13 @@ S: Maintained F: Documentation/devicetree/bindings/pci/hisilicon-pcie.txt F: drivers/pci/host/pcie-hisi.c +PCIE DRIVER FOR QUALCOMM MSM +M: Stanimir Varbanov +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 diff --git a/drivers/pci/host/Kconfig b/drivers/pci/host/Kconfig index f131ba947dc6..64d33ba3e336 100644 --- a/drivers/pci/host/Kconfig +++ b/drivers/pci/host/Kconfig @@ -172,4 +172,14 @@ config PCI_HISI 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 || 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 diff --git a/drivers/pci/host/Makefile b/drivers/pci/host/Makefile index 9d4d3c6924a1..e523c171febf 100644 --- a/drivers/pci/host/Makefile +++ b/drivers/pci/host/Makefile @@ -20,3 +20,4 @@ obj-$(CONFIG_PCIE_IPROC_BCMA) += pcie-iproc-bcma.o obj-$(CONFIG_PCIE_ALTERA) += pcie-altera.o obj-$(CONFIG_PCIE_ALTERA_MSI) += pcie-altera-msi.o obj-$(CONFIG_PCI_HISI) += pcie-hisi.o +obj-$(CONFIG_PCIE_QCOM) += pcie-qcom.o diff --git a/drivers/pci/host/pcie-qcom.c b/drivers/pci/host/pcie-qcom.c new file mode 100644 index 000000000000..7d9b7f14354b --- /dev/null +++ b/drivers/pci/host/pcie-qcom.c @@ -0,0 +1,624 @@ +/* + * Copyright (c) 2014-2015, The Linux Foundation. All rights reserved. + * Copyright 2015 Linaro Limited. + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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_CTRL_LT_ENABLE BIT(0) + +#define PCIE20_ELBI_SYS_STTS 0x08 +#define XMLH_LINK_UP BIT(10) + +#define PCIE20_CAP 0x70 +#define PCIE20_CAP_LINKCTRLSTATUS (PCIE20_CAP + 0x10) +#define PCIE20_CAP_LINKCTRLSTATUS_LINK_UP BIT(29) + +#define PERST_DELAY_US 1000 + +#define LINKUP_DELAY_US 5000 +#define LINKUP_TIMEOUT_US 100000 + +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 qcom_pcie_resources { + struct qcom_pcie_resources_v0 v0; + struct qcom_pcie_resources_v1 v1; +}; + +struct qcom_pcie; + +struct qcom_pcie_ops { + int (*get_resources)(struct qcom_pcie *pcie); + int (*init)(struct qcom_pcie *pcie); + void (*deinit)(struct qcom_pcie *pcie); +}; + +struct qcom_pcie { + struct pcie_port pp; + struct device *dev; + union qcom_pcie_resources res; + void __iomem *parf; + void __iomem *dbi; + void __iomem *elbi; + struct phy *phy; + struct gpio_desc *reset; + struct qcom_pcie_ops *ops; +}; + +#define to_qcom_pcie(x) container_of(x, struct qcom_pcie, pp) + +static void qcom_ep_reset_assert(struct qcom_pcie *pcie) +{ + gpiod_set_value(pcie->reset, 1); + usleep_range(PERST_DELAY_US, PERST_DELAY_US + 500); +} + +static void qcom_ep_reset_deassert(struct qcom_pcie *pcie) +{ + gpiod_set_value(pcie->reset, 0); + usleep_range(PERST_DELAY_US, PERST_DELAY_US + 500); +} + +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_enable_link_training(struct qcom_pcie *pcie) +{ + struct device *dev = pcie->dev; + u32 val; + int ret; + + /* enable link training */ + val = readl(pcie->elbi + PCIE20_ELBI_SYS_CTRL); + val |= PCIE20_ELBI_SYS_CTRL_LT_ENABLE; + writel(val, pcie->elbi + PCIE20_ELBI_SYS_CTRL); + + /* wait for up to 100ms for the link to come up */ + ret = readl_poll_timeout(pcie->elbi + PCIE20_ELBI_SYS_STTS, val, + val & XMLH_LINK_UP, LINKUP_DELAY_US, + LINKUP_TIMEOUT_US); + + if (ret < 0 || !dw_pcie_link_up(&pcie->pp)) { + dev_err(dev, "link initialization failed\n"); + return -ETIMEDOUT; + } + + return 0; +} + +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)) + return PTR_ERR(res->aux); + + 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 void qcom_pcie_deinit_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 int qcom_pcie_init_v0(struct qcom_pcie *pcie) +{ + struct qcom_pcie_resources_v0 *res = &pcie->res.v0; + struct device *dev = pcie->dev; + u32 val; + 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 = reset_control_assert(res->ahb_reset); + if (ret) { + dev_err(dev, "cannot assert ahb reset\n"); + goto err_assert_ahb; + } + + ret = clk_prepare_enable(res->iface_clk); + if (ret) { + dev_err(dev, "cannot prepare/enable iface clock\n"); + goto err_assert_ahb; + } + + ret = clk_prepare_enable(res->phy_clk); + if (ret) { + dev_err(dev, "cannot prepare/enable phy clock\n"); + goto err_clk_phy; + } + + ret = clk_prepare_enable(res->core_clk); + if (ret) { + dev_err(dev, "cannot prepare/enable core clock\n"); + goto err_clk_core; + } + + ret = reset_control_deassert(res->ahb_reset); + if (ret) { + dev_err(dev, "cannot deassert ahb reset\n"); + goto err_deassert_ahb; + } + + /* enable PCIe clocks and resets */ + val = readl(pcie->parf + PCIE20_PARF_PHY_CTRL); + val &= ~BIT(0); + writel(val, pcie->parf + PCIE20_PARF_PHY_CTRL); + + /* enable external reference clock */ + val = readl(pcie->parf + PCIE20_PARF_PHY_REFCLK); + val |= BIT(16); + writel(val, pcie->parf + PCIE20_PARF_PHY_REFCLK); + + ret = reset_control_deassert(res->phy_reset); + if (ret) { + dev_err(dev, "cannot deassert phy reset\n"); + return ret; + } + + ret = reset_control_deassert(res->pci_reset); + if (ret) { + dev_err(dev, "cannot deassert pci reset\n"); + return ret; + } + + ret = reset_control_deassert(res->por_reset); + if (ret) { + dev_err(dev, "cannot deassert por reset\n"); + return ret; + } + + ret = reset_control_deassert(res->axi_reset); + if (ret) { + dev_err(dev, "cannot deassert axi reset\n"); + return ret; + } + + /* wait for clock acquisition */ + usleep_range(1000, 1500); + + return 0; + +err_deassert_ahb: + clk_disable_unprepare(res->core_clk); +err_clk_core: + clk_disable_unprepare(res->phy_clk); +err_clk_phy: + clk_disable_unprepare(res->iface_clk); +err_assert_ahb: + regulator_disable(res->vdda_phy); +err_vdda_phy: + regulator_disable(res->vdda_refclk); +err_refclk: + regulator_disable(res->vdda); + + return ret; +} + +static void qcom_pcie_deinit_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_init_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; + } + + + /* change DBI base address */ + writel(0, pcie->parf + PCIE20_PARF_DBI_BASE_ADDR); + + if (IS_ENABLED(CONFIG_PCI_MSI)) { + u32 val = readl(pcie->parf + PCIE20_PARF_AXI_MSTR_WR_ADDR_HALT); + + val |= BIT(31); + writel(val, pcie->parf + PCIE20_PARF_AXI_MSTR_WR_ADDR_HALT); + } + + 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_link_up(struct pcie_port *pp) +{ + struct qcom_pcie *pcie = to_qcom_pcie(pp); + u32 val = readl(pcie->dbi + PCIE20_CAP_LINKCTRLSTATUS); + + return !!(val & PCIE20_CAP_LINKCTRLSTATUS_LINK_UP); +} + +static void qcom_pcie_host_init(struct pcie_port *pp) +{ + struct qcom_pcie *pcie = to_qcom_pcie(pp); + int ret; + + qcom_ep_reset_assert(pcie); + + ret = pcie->ops->init(pcie); + if (ret) + goto err_deinit; + + ret = phy_power_on(pcie->phy); + if (ret) + goto err_deinit; + + 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(pcie); + if (ret) + goto err; + + return; +err: + qcom_ep_reset_assert(pcie); + phy_power_off(pcie->phy); +err_deinit: + pcie->ops->deinit(pcie); +} + +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 &= 0xff; /* keep revision id */ + *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_dw_ops = { + .link_up = qcom_pcie_link_up, + .host_init = qcom_pcie_host_init, + .rd_own_conf = qcom_pcie_rd_own_conf, +}; + +static const struct qcom_pcie_ops ops_v0 = { + .get_resources = qcom_pcie_get_resources_v0, + .init = qcom_pcie_init_v0, + .deinit = qcom_pcie_deinit_v0, +}; + +static const struct qcom_pcie_ops ops_v1 = { + .get_resources = qcom_pcie_get_resources_v1, + .init = qcom_pcie_init_v1, + .deinit = qcom_pcie_deinit_v1, +}; + +static int qcom_pcie_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *res; + struct qcom_pcie *pcie; + struct pcie_port *pp; + int ret; + + pcie = devm_kzalloc(dev, sizeof(*pcie), GFP_KERNEL); + if (!pcie) + return -ENOMEM; + + pcie->ops = (struct qcom_pcie_ops *)of_device_get_match_data(dev); + pcie->dev = dev; + + pcie->reset = devm_gpiod_get_optional(dev, "perst", GPIOD_OUT_LOW); + if (IS_ERR(pcie->reset)) + 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); + + ret = pcie->ops->get_resources(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_dw_ops; + + if (IS_ENABLED(CONFIG_PCI_MSI)) { + pp->msi_irq = platform_get_irq_byname(pdev, "msi"); + if (pp->msi_irq < 0) + 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 = phy_init(pcie->phy); + if (ret) + 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); + pcie->ops->deinit(pcie); + + return 0; +} + +static const struct of_device_id qcom_pcie_match[] = { + { .compatible = "qcom,pcie-ipq8064", .data = &ops_v0 }, + { .compatible = "qcom,pcie-apq8064", .data = &ops_v0 }, + { .compatible = "qcom,pcie-apq8084", .data = &ops_v1 }, + { } +}; +MODULE_DEVICE_TABLE(of, qcom_pcie_match); + +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 "); +MODULE_DESCRIPTION("Qualcomm PCIe root complex driver"); +MODULE_LICENSE("GPL v2");