From nobody Thu Apr 9 07:17:16 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 3452A391E61; Tue, 10 Mar 2026 14:02:21 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773151341; cv=none; b=mcDM2YbGXSuUVhUEraV+8TKUmgHUkASOZEZUaBFR7eiIDCBFWBIt9nIFu1EPvDHYOQ++zHmfkTFvV/49lbG9KNVfjZcLz9148Q24M9ids4Be+d09udgF5ulhnkLiqU4HZVB+dWsKfSwrb6+I+MNWQdhWKrzw2aepU9ulJQJzQfU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773151341; c=relaxed/simple; bh=c/AIkQgskmKPbT6YOeIH8T/UhvLySetuMa6ErBkhX/E=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=q7PVEc73+1R/GjXFtv7O3MgetaekS937kU3Ml79hYxq8xWeUg4XXwQCULc4xJuTos4Kn2m9FwNPnQwz174SnJkGDZA3OBiBVXGdds8nBskhY45+EhVQ3STi7kNcKxHOKXkrBKRU0OY/OZHYu5popdS3BUj5jv0PxmIOKuxFWtrw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=sv0wATPo; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="sv0wATPo" Received: by smtp.kernel.org (Postfix) with ESMTPS id E1443C2BC86; Tue, 10 Mar 2026 14:02:20 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1773151341; bh=c/AIkQgskmKPbT6YOeIH8T/UhvLySetuMa6ErBkhX/E=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=sv0wATPoyBX1VvgTavdHIpQsY0FZIavnh/1S4bXVTrjqdygpUFEFc2GqTyupg5EbY +x10RiyEw8RCgdXZTsWy6Zkj8FjWLVJxFqDKii8DICSMUmnffhCERDV67h2BKv/cqb nI/fTr9b5O6EfVsLn1NhQkm0PMbQgeNs5E2Yyq7wH5sSe5/B9ydyn7XlkKyVVLadmD WymlQT2qW8QTFaNj0pvH+VJf9ADtrt/K1zr040FT1VNchgTfPiLYgpfVcQKv56cc0t 6v6JYtt5BB7qxNKZD9rkLzKfjj7M+SRineI1Sj+6DLp1/sFG5QGMJ8dJ6gUSPWiVJL C9sjUKeJx6Dbg== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id D8BE1FD2D9F; Tue, 10 Mar 2026 14:02:20 +0000 (UTC) From: Manivannan Sadhasivam via B4 Relay Date: Tue, 10 Mar 2026 19:32:01 +0530 Subject: [PATCH v7 3/4] PCI: qcom: Add support for resetting the Root Port due to link down event Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260310-pci-port-reset-v7-3-9dd00ccc25ab@oss.qualcomm.com> References: <20260310-pci-port-reset-v7-0-9dd00ccc25ab@oss.qualcomm.com> In-Reply-To: <20260310-pci-port-reset-v7-0-9dd00ccc25ab@oss.qualcomm.com> To: Bjorn Helgaas , Mahesh J Salgaonkar , Oliver O'Halloran , Will Deacon , Lorenzo Pieralisi , =?utf-8?q?Krzysztof_Wilczy=C5=84ski?= , Manivannan Sadhasivam , Rob Herring , Heiko Stuebner , Philipp Zabel Cc: linux-pci@vger.kernel.org, linux-kernel@vger.kernel.org, linuxppc-dev@lists.ozlabs.org, linux-arm-kernel@lists.infradead.org, linux-arm-msm@vger.kernel.org, linux-rockchip@lists.infradead.org, Niklas Cassel , Wilfred Mallawa , Krishna Chaitanya Chundru , mani@kernel.org, Lukas Wunner , Richard Zhu , Brian Norris , Wilson Ding , Manivannan Sadhasivam , Manivannan Sadhasivam X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=8416; i=manivannan.sadhasivam@oss.qualcomm.com; h=from:subject:message-id; bh=CwOvSrZ8KQJ6kWNG43kBWKGjoiyD3NMx5WS7RNFKvtQ=; b=owEBbQGS/pANAwAKAVWfEeb+kc71AcsmYgBpsCRqm6D1dy8cldnK3AjRKyOQy5OvmSK88PgQI PlOCP7+AUOJATMEAAEKAB0WIQRnpUMqgUjL2KRYJ5dVnxHm/pHO9QUCabAkagAKCRBVnxHm/pHO 9cscCACpTOAAWV9vlYRVzpQvJj37u5thsUwgvk2Uy+aSgVsM881zNKOsYpWUg/C1dy9RWeCBqMr NppjYH5mDwZF6/EtRZcJa80DUFnGrEXVlBqrJfFgmOucDU5vkwn7P/iWTF5VcmFLMmi4OtTAhUF k604fTkx2mfroC8RkIrixzIIRCM2vADyio5uAqeDIAk7zCbAShy7DX0yX7XhZ+iXvTTQTYuIMtX KcRm1gIe7UoITUxDqsDSIGyriXVXJ4cXuAWzIhohFnd/IXnOXYlGvHyuD6Y0PMp5zj6+xpFHKiy hwW4yGz7tYwT9GuRhJnBqWyUMEgWQ63oLJLRTFhw6IuHF/M/ X-Developer-Key: i=manivannan.sadhasivam@oss.qualcomm.com; a=openpgp; fpr=C668AEC3C3188E4C611465E7488550E901166008 X-Endpoint-Received: by B4 Relay for manivannan.sadhasivam@oss.qualcomm.com/default with auth_id=461 X-Original-From: Manivannan Sadhasivam Reply-To: manivannan.sadhasivam@oss.qualcomm.com From: Manivannan Sadhasivam The PCIe link can go down under circumstances such as the device firmware crash, link instability, etc... When that happens, the PCIe Root Port needs to be reset to make it operational again. Currently, the driver is not handling the link down event, due to which the users have to restart the machine to make PCIe link operational again. So fix it by detecting the link down event and resetting the Root Port. Since the Qcom PCIe controllers report the link down event through the 'global' IRQ, enable the link down event by setting PARF_INT_ALL_LINK_DOWN bit in PARF_INT_ALL_MASK register. In the case of the event, iterate through the available Root Ports and call pci_host_handle_link_down() API with Root Port 'pci_dev' to let the PCI core handle the link down condition. Since Qcom PCIe controllers only support one Root Port per controller instance, the API will be called only once. But the looping is necessary as there is no PCI API available to fetch the Root Port instance without the child 'pci_dev'. The API will internally call, 'pci_host_bridge::reset_root_port()' callback to reset the Root Port in a platform specific way. So implement the callback to reset the Root Port by first resetting the PCIe core, followed by reinitializing the resources and then finally starting the link again. Signed-off-by: Manivannan Sadhasivam Signed-off-by: Manivannan Sadhasivam Tested-by: Krishna Chaitanya Chundru --- drivers/pci/controller/dwc/pcie-qcom.c | 143 +++++++++++++++++++++++++++++= +++- 1 file changed, 142 insertions(+), 1 deletion(-) diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controlle= r/dwc/pcie-qcom.c index 67a16af69ddc..e3f08139d33f 100644 --- a/drivers/pci/controller/dwc/pcie-qcom.c +++ b/drivers/pci/controller/dwc/pcie-qcom.c @@ -56,6 +56,10 @@ #define PARF_AXI_MSTR_WR_ADDR_HALT_V2 0x1a8 #define PARF_Q2A_FLUSH 0x1ac #define PARF_LTSSM 0x1b0 +#define PARF_INT_ALL_STATUS 0x224 +#define PARF_INT_ALL_CLEAR 0x228 +#define PARF_INT_ALL_MASK 0x22c +#define PARF_STATUS 0x230 #define PARF_SID_OFFSET 0x234 #define PARF_BDF_TRANSLATE_CFG 0x24c #define PARF_DBI_BASE_ADDR_V2 0x350 @@ -131,6 +135,13 @@ =20 /* PARF_LTSSM register fields */ #define LTSSM_EN BIT(8) +#define SW_CLEAR_FLUSH_MODE BIT(10) +#define FLUSH_MODE BIT(11) + +/* PARF_INT_ALL_{STATUS/CLEAR/MASK} register fields */ +#define INT_ALL_LINK_DOWN 1 +#define PARF_INT_ALL_LINK_DOWN BIT(INT_ALL_LINK_DOWN) +#define PARF_INT_MSI_DEV_0_7 GENMASK(30, 23) =20 /* PARF_NO_SNOOP_OVERRIDE register fields */ #define WR_NO_SNOOP_OVERRIDE_EN BIT(1) @@ -142,6 +153,9 @@ /* PARF_BDF_TO_SID_CFG fields */ #define BDF_TO_SID_BYPASS BIT(0) =20 +/* PARF_STATUS fields */ +#define FLUSH_COMPLETED BIT(8) + /* ELBI_SYS_CTRL register fields */ #define ELBI_SYS_CTRL_LT_ENABLE BIT(0) =20 @@ -166,6 +180,7 @@ PCIE_CAP_SLOT_POWER_LIMIT_SCALE) =20 #define PERST_DELAY_US 1000 +#define FLUSH_TIMEOUT_US 100 =20 #define QCOM_PCIE_CRC8_POLYNOMIAL (BIT(2) | BIT(1) | BIT(0)) =20 @@ -282,11 +297,14 @@ struct qcom_pcie { const struct qcom_pcie_cfg *cfg; struct dentry *debugfs; struct list_head ports; + int global_irq; bool suspended; bool use_pm_opp; }; =20 #define to_qcom_pcie(x) dev_get_drvdata((x)->dev) +static int qcom_pcie_reset_root_port(struct pci_host_bridge *bridge, + struct pci_dev *pdev); =20 static void __qcom_pcie_perst_assert(struct qcom_pcie *pcie, bool assert) { @@ -1325,6 +1343,8 @@ static int qcom_pcie_host_init(struct dw_pcie_rp *pp) goto err_assert_reset; } =20 + pp->bridge->reset_root_port =3D qcom_pcie_reset_root_port; + return 0; =20 err_assert_reset: @@ -1608,6 +1628,78 @@ static void qcom_pcie_icc_opp_update(struct qcom_pci= e *pcie) } } =20 +/* + * Qcom PCIe controllers only support one Root Port per controller instanc= e. So + * this function ignores the 'pci_dev' associated with the Root Port and j= ust + * resets the host bridge, which in turn resets the Root Port also. + */ +static int qcom_pcie_reset_root_port(struct pci_host_bridge *bridge, + struct pci_dev *pdev) +{ + struct pci_bus *bus =3D bridge->bus; + struct dw_pcie_rp *pp =3D bus->sysdata; + struct dw_pcie *pci =3D to_dw_pcie_from_pp(pp); + struct qcom_pcie *pcie =3D to_qcom_pcie(pci); + struct device *dev =3D pcie->pci->dev; + u32 val; + int ret; + + /* Wait for the pending transactions to be completed */ + ret =3D readl_relaxed_poll_timeout(pcie->parf + PARF_STATUS, val, + val & FLUSH_COMPLETED, 10, + FLUSH_TIMEOUT_US); + if (ret) { + dev_err(dev, "Flush completion failed: %d\n", ret); + goto err_host_deinit; + } + + /* Clear the FLUSH_MODE to allow the core to be reset */ + val =3D readl(pcie->parf + PARF_LTSSM); + val |=3D SW_CLEAR_FLUSH_MODE; + writel(val, pcie->parf + PARF_LTSSM); + + /* Wait for the FLUSH_MODE to clear */ + ret =3D readl_relaxed_poll_timeout(pcie->parf + PARF_LTSSM, val, + !(val & FLUSH_MODE), 10, + FLUSH_TIMEOUT_US); + if (ret) { + dev_err(dev, "Flush mode clear failed: %d\n", ret); + goto err_host_deinit; + } + + qcom_pcie_host_deinit(pp); + + ret =3D qcom_pcie_host_init(pp); + if (ret) { + dev_err(dev, "Host init failed\n"); + return ret; + } + + ret =3D dw_pcie_setup_rc(pp); + if (ret) + goto err_host_deinit; + + /* + * Re-enable global IRQ events as the PARF_INT_ALL_MASK register is + * non-sticky. + */ + if (pcie->global_irq) + writel_relaxed(PARF_INT_ALL_LINK_DOWN | PARF_INT_MSI_DEV_0_7, + pcie->parf + PARF_INT_ALL_MASK); + + qcom_pcie_start_link(pci); + dw_pcie_wait_for_link(pci); + + dev_dbg(dev, "Root Port reset completed\n"); + + return 0; + +err_host_deinit: + qcom_pcie_host_deinit(pp); + + return ret; +} + static int qcom_pcie_link_transition_count(struct seq_file *s, void *data) { struct qcom_pcie *pcie =3D (struct qcom_pcie *)dev_get_drvdata(s->private= ); @@ -1645,6 +1737,27 @@ static void qcom_pcie_init_debugfs(struct qcom_pcie = *pcie) qcom_pcie_link_transition_count); } =20 +static irqreturn_t qcom_pcie_global_irq_thread(int irq, void *data) +{ + struct qcom_pcie *pcie =3D data; + struct dw_pcie_rp *pp =3D &pcie->pci->pp; + struct device *dev =3D pcie->pci->dev; + struct pci_dev *port; + unsigned long status =3D readl_relaxed(pcie->parf + PARF_INT_ALL_STATUS); + + writel_relaxed(status, pcie->parf + PARF_INT_ALL_CLEAR); + + if (test_and_clear_bit(INT_ALL_LINK_DOWN, &status)) { + dev_dbg(dev, "Received Link down event\n"); + for_each_pci_bridge(port, pp->bridge->bus) { + if (pci_pcie_type(port) =3D=3D PCI_EXP_TYPE_ROOT_PORT) + pci_host_handle_link_down(port); + } + } + + return IRQ_HANDLED; +} + static void qcom_pci_free_msi(void *ptr) { struct dw_pcie_rp *pp =3D (struct dw_pcie_rp *)ptr; @@ -1847,7 +1960,7 @@ static int qcom_pcie_probe(struct platform_device *pd= ev) struct dw_pcie_rp *pp; struct resource *res; struct dw_pcie *pci; - int ret; + int ret, irq; =20 pcie_cfg =3D of_device_get_match_data(dev); if (!pcie_cfg) { @@ -2004,6 +2117,32 @@ static int qcom_pcie_probe(struct platform_device *p= dev) goto err_phy_exit; } =20 + irq =3D platform_get_irq_byname_optional(pdev, "global"); + if (irq > 0) { + const char *name; + + name =3D devm_kasprintf(dev, GFP_KERNEL, "qcom_pcie_global_irq%d", + pci_domain_nr(pp->bridge->bus)); + if (!name) { + ret =3D -ENOMEM; + goto err_host_deinit; + } + + ret =3D devm_request_threaded_irq(&pdev->dev, irq, NULL, + qcom_pcie_global_irq_thread, + IRQF_ONESHOT, name, pcie); + if (ret) { + dev_err_probe(&pdev->dev, ret, + "Failed to request Global IRQ\n"); + goto err_host_deinit; + } + + writel_relaxed(PARF_INT_ALL_LINK_DOWN | PARF_INT_MSI_DEV_0_7, + pcie->parf + PARF_INT_ALL_MASK); + + pcie->global_irq =3D irq; + } + qcom_pcie_icc_opp_update(pcie); =20 if (pcie->mhi) @@ -2011,6 +2150,8 @@ static int qcom_pcie_probe(struct platform_device *pd= ev) =20 return 0; =20 +err_host_deinit: + dw_pcie_host_deinit(pp); err_phy_exit: list_for_each_entry_safe(port, tmp_port, &pcie->ports, list) { list_for_each_entry_safe(perst, tmp_perst, &port->perst, list) --=20 2.51.0