From a60d92f6d941bd77bf3aaec724a7c95857c0165b Mon Sep 17 00:00:00 2001 From: Mayank Rana Date: Mon, 16 Jun 2025 15:42:56 -0700 Subject: [PATCH 1/6] PCI: dwc: Export DWC MSI controller related APIs Export dw_pcie_msi_host_init(), dw_pcie_msi_init(), and dw_pcie_free_msi() APIs to allow them to be reused by the upcoming DWC based ECAM driver implementation. Also, move MSI IRQ related initialization code to dw_pcie_msi_init(), as this code must be executed before dw_pcie_msi_init() API can be used with ECAM driver. Signed-off-by: Mayank Rana [mani: commit message rewording] Signed-off-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20250616224259.3549811-2-mayank.rana@oss.qualcomm.com --- .../pci/controller/dwc/pcie-designware-host.c | 38 ++++++++++--------- drivers/pci/controller/dwc/pcie-designware.h | 14 +++++++ 2 files changed, 34 insertions(+), 18 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-designware-host.c b/drivers/pci/controller/dwc/pcie-designware-host.c index 906277f9ffaf..af6c91ec7312 100644 --- a/drivers/pci/controller/dwc/pcie-designware-host.c +++ b/drivers/pci/controller/dwc/pcie-designware-host.c @@ -250,7 +250,7 @@ int dw_pcie_allocate_domains(struct dw_pcie_rp *pp) return 0; } -static void dw_pcie_free_msi(struct dw_pcie_rp *pp) +void dw_pcie_free_msi(struct dw_pcie_rp *pp) { u32 ctrl; @@ -263,19 +263,34 @@ static void dw_pcie_free_msi(struct dw_pcie_rp *pp) irq_domain_remove(pp->msi_domain); irq_domain_remove(pp->irq_domain); } +EXPORT_SYMBOL_GPL(dw_pcie_free_msi); -static void dw_pcie_msi_init(struct dw_pcie_rp *pp) +void dw_pcie_msi_init(struct dw_pcie_rp *pp) { struct dw_pcie *pci = to_dw_pcie_from_pp(pp); u64 msi_target = (u64)pp->msi_data; + u32 ctrl, num_ctrls; if (!pci_msi_enabled() || !pp->has_msi_ctrl) return; + num_ctrls = pp->num_vectors / MAX_MSI_IRQS_PER_CTRL; + + /* Initialize IRQ Status array */ + for (ctrl = 0; ctrl < num_ctrls; ctrl++) { + dw_pcie_writel_dbi(pci, PCIE_MSI_INTR0_MASK + + (ctrl * MSI_REG_CTRL_BLOCK_SIZE), + pp->irq_mask[ctrl]); + dw_pcie_writel_dbi(pci, PCIE_MSI_INTR0_ENABLE + + (ctrl * MSI_REG_CTRL_BLOCK_SIZE), + ~0); + } + /* Program the msi_data */ dw_pcie_writel_dbi(pci, PCIE_MSI_ADDR_LO, lower_32_bits(msi_target)); dw_pcie_writel_dbi(pci, PCIE_MSI_ADDR_HI, upper_32_bits(msi_target)); } +EXPORT_SYMBOL_GPL(dw_pcie_msi_init); static int dw_pcie_parse_split_msi_irq(struct dw_pcie_rp *pp) { @@ -317,7 +332,7 @@ static int dw_pcie_parse_split_msi_irq(struct dw_pcie_rp *pp) return 0; } -static int dw_pcie_msi_host_init(struct dw_pcie_rp *pp) +int dw_pcie_msi_host_init(struct dw_pcie_rp *pp) { struct dw_pcie *pci = to_dw_pcie_from_pp(pp); struct device *dev = pci->dev; @@ -391,6 +406,7 @@ static int dw_pcie_msi_host_init(struct dw_pcie_rp *pp) return 0; } +EXPORT_SYMBOL_GPL(dw_pcie_msi_host_init); static void dw_pcie_host_request_msg_tlp_res(struct dw_pcie_rp *pp) { @@ -909,7 +925,7 @@ static void dw_pcie_config_presets(struct dw_pcie_rp *pp) int dw_pcie_setup_rc(struct dw_pcie_rp *pp) { struct dw_pcie *pci = to_dw_pcie_from_pp(pp); - u32 val, ctrl, num_ctrls; + u32 val; int ret; /* @@ -920,20 +936,6 @@ int dw_pcie_setup_rc(struct dw_pcie_rp *pp) dw_pcie_setup(pci); - if (pp->has_msi_ctrl) { - num_ctrls = pp->num_vectors / MAX_MSI_IRQS_PER_CTRL; - - /* Initialize IRQ Status array */ - for (ctrl = 0; ctrl < num_ctrls; ctrl++) { - dw_pcie_writel_dbi(pci, PCIE_MSI_INTR0_MASK + - (ctrl * MSI_REG_CTRL_BLOCK_SIZE), - pp->irq_mask[ctrl]); - dw_pcie_writel_dbi(pci, PCIE_MSI_INTR0_ENABLE + - (ctrl * MSI_REG_CTRL_BLOCK_SIZE), - ~0); - } - } - dw_pcie_msi_init(pp); /* Setup RC BARs */ diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h index ce9e18554e42..4165c49a0a50 100644 --- a/drivers/pci/controller/dwc/pcie-designware.h +++ b/drivers/pci/controller/dwc/pcie-designware.h @@ -759,6 +759,9 @@ static inline enum dw_pcie_ltssm dw_pcie_get_ltssm(struct dw_pcie *pci) int dw_pcie_suspend_noirq(struct dw_pcie *pci); int dw_pcie_resume_noirq(struct dw_pcie *pci); irqreturn_t dw_handle_msi_irq(struct dw_pcie_rp *pp); +void dw_pcie_msi_init(struct dw_pcie_rp *pp); +int dw_pcie_msi_host_init(struct dw_pcie_rp *pp); +void dw_pcie_free_msi(struct dw_pcie_rp *pp); int dw_pcie_setup_rc(struct dw_pcie_rp *pp); int dw_pcie_host_init(struct dw_pcie_rp *pp); void dw_pcie_host_deinit(struct dw_pcie_rp *pp); @@ -781,6 +784,17 @@ static inline irqreturn_t dw_handle_msi_irq(struct dw_pcie_rp *pp) return IRQ_NONE; } +static inline void dw_pcie_msi_init(struct dw_pcie_rp *pp) +{ } + +static inline int dw_pcie_msi_host_init(struct dw_pcie_rp *pp) +{ + return -ENODEV; +} + +static inline void dw_pcie_free_msi(struct dw_pcie_rp *pp) +{ } + static inline int dw_pcie_setup_rc(struct dw_pcie_rp *pp) { return 0; From fefbc58271be1eaaab1b3c8815569109764a32c4 Mon Sep 17 00:00:00 2001 From: Mayank Rana Date: Mon, 16 Jun 2025 15:42:57 -0700 Subject: [PATCH 2/6] PCI: host-generic: Rename and export gen_pci_init() for PCIe controller drivers Rename gen_pci_init() API as pci_host_common_ecam_create() and export it to allow the PCIe controller drivers to create and configure the ECAM region. Note that this API should only used by the drivers managing the drvdata on their own. Rest should continue using pci_host_common_init() API. Signed-off-by: Mayank Rana [mani: commit message rewording] Signed-off-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20250616224259.3549811-3-mayank.rana@oss.qualcomm.com --- drivers/pci/controller/pci-host-common.c | 5 +++-- drivers/pci/controller/pci-host-common.h | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/pci/controller/pci-host-common.c b/drivers/pci/controller/pci-host-common.c index b0992325dd65..5b61b5a9e0f9 100644 --- a/drivers/pci/controller/pci-host-common.c +++ b/drivers/pci/controller/pci-host-common.c @@ -22,7 +22,7 @@ static void gen_pci_unmap_cfg(void *ptr) pci_ecam_free((struct pci_config_window *)ptr); } -static struct pci_config_window *gen_pci_init(struct device *dev, +struct pci_config_window *pci_host_common_ecam_create(struct device *dev, struct pci_host_bridge *bridge, const struct pci_ecam_ops *ops) { int err; @@ -50,6 +50,7 @@ static struct pci_config_window *gen_pci_init(struct device *dev, return cfg; } +EXPORT_SYMBOL_GPL(pci_host_common_ecam_create); int pci_host_common_init(struct platform_device *pdev, const struct pci_ecam_ops *ops) @@ -65,7 +66,7 @@ int pci_host_common_init(struct platform_device *pdev, of_pci_check_probe_only(); /* Parse and map our Configuration Space windows */ - cfg = gen_pci_init(dev, bridge, ops); + cfg = pci_host_common_ecam_create(dev, bridge, ops); if (IS_ERR(cfg)) return PTR_ERR(cfg); diff --git a/drivers/pci/controller/pci-host-common.h b/drivers/pci/controller/pci-host-common.h index 65bd9e032353..51c35ec0cf37 100644 --- a/drivers/pci/controller/pci-host-common.h +++ b/drivers/pci/controller/pci-host-common.h @@ -17,4 +17,6 @@ int pci_host_common_init(struct platform_device *pdev, const struct pci_ecam_ops *ops); void pci_host_common_remove(struct platform_device *pdev); +struct pci_config_window *pci_host_common_ecam_create(struct device *dev, + struct pci_host_bridge *bridge, const struct pci_ecam_ops *ops); #endif From d7c7c051e8e5f781c98310709f3feaf7e634251b Mon Sep 17 00:00:00 2001 From: Mayank Rana Date: Mon, 16 Jun 2025 15:42:58 -0700 Subject: [PATCH 3/6] dt-bindings: PCI: qcom,pcie-sa8255p: Document ECAM compliant PCIe root complex Document the required configuration to enable the PCIe Root Complex on SA8255p, which is managed by firmware using power-domain based handling and configured as ECAM compliant. Signed-off-by: Mayank Rana Signed-off-by: Manivannan Sadhasivam [bhelgaas: add "ECAM" in reg description] Signed-off-by: Bjorn Helgaas Reviewed-by: Rob Herring (Arm) Link: https://patch.msgid.link/20250616224259.3549811-4-mayank.rana@oss.qualcomm.com --- .../bindings/pci/qcom,pcie-sa8255p.yaml | 122 ++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 Documentation/devicetree/bindings/pci/qcom,pcie-sa8255p.yaml diff --git a/Documentation/devicetree/bindings/pci/qcom,pcie-sa8255p.yaml b/Documentation/devicetree/bindings/pci/qcom,pcie-sa8255p.yaml new file mode 100644 index 000000000000..ef705a02fcd9 --- /dev/null +++ b/Documentation/devicetree/bindings/pci/qcom,pcie-sa8255p.yaml @@ -0,0 +1,122 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/pci/qcom,pcie-sa8255p.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm SA8255p based firmware managed and ECAM compliant PCIe Root Complex + +maintainers: + - Bjorn Andersson + - Manivannan Sadhasivam + +description: + Qualcomm SA8255p SoC PCIe root complex controller is based on the Synopsys + DesignWare PCIe IP which is managed by firmware, and configured in ECAM mode. + +properties: + compatible: + const: qcom,pcie-sa8255p + + reg: + description: + The base address and size of the ECAM area for accessing PCI + Configuration Space, as accessed from the parent bus. The base + address corresponds to the first bus in the "bus-range" property. If + no "bus-range" is specified, this will be bus 0 (the default). + maxItems: 1 + + ranges: + description: + As described in IEEE Std 1275-1994, but must provide at least a + definition of non-prefetchable memory. One or both of prefetchable Memory + may also be provided. + minItems: 1 + maxItems: 2 + + interrupts: + minItems: 8 + maxItems: 8 + + interrupt-names: + items: + - const: msi0 + - const: msi1 + - const: msi2 + - const: msi3 + - const: msi4 + - const: msi5 + - const: msi6 + - const: msi7 + + power-domains: + maxItems: 1 + + dma-coherent: true + iommu-map: true + +required: + - compatible + - reg + - ranges + - power-domains + - interrupts + - interrupt-names + +allOf: + - $ref: /schemas/pci/pci-host-bridge.yaml# + +unevaluatedProperties: false + +examples: + - | + #include + + soc { + #address-cells = <2>; + #size-cells = <2>; + + pci@1c00000 { + compatible = "qcom,pcie-sa8255p"; + reg = <0x4 0x00000000 0 0x10000000>; + device_type = "pci"; + #address-cells = <3>; + #size-cells = <2>; + ranges = <0x02000000 0x0 0x40100000 0x0 0x40100000 0x0 0x1ff00000>, + <0x43000000 0x4 0x10100000 0x4 0x10100000 0x0 0x40000000>; + bus-range = <0x00 0xff>; + dma-coherent; + linux,pci-domain = <0>; + power-domains = <&scmi5_pd 0>; + iommu-map = <0x0 &pcie_smmu 0x0000 0x1>, + <0x100 &pcie_smmu 0x0001 0x1>; + interrupt-parent = <&intc>; + interrupts = , + , + , + , + , + , + , + ; + interrupt-names = "msi0", "msi1", "msi2", "msi3", + "msi4", "msi5", "msi6", "msi7"; + + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 0 0x7>; + interrupt-map = <0 0 0 1 &intc GIC_SPI 148 IRQ_TYPE_LEVEL_HIGH>, + <0 0 0 2 &intc GIC_SPI 149 IRQ_TYPE_LEVEL_HIGH>, + <0 0 0 3 &intc GIC_SPI 150 IRQ_TYPE_LEVEL_HIGH>, + <0 0 0 4 &intc GIC_SPI 151 IRQ_TYPE_LEVEL_HIGH>; + + pcie@0 { + device_type = "pci"; + reg = <0x0 0x0 0x0 0x0 0x0>; + bus-range = <0x01 0xff>; + + #address-cells = <3>; + #size-cells = <2>; + ranges; + }; + }; + }; From 7d944c0f146986a532087e15abb66a27c7890ca1 Mon Sep 17 00:00:00 2001 From: Mayank Rana Date: Mon, 16 Jun 2025 15:42:59 -0700 Subject: [PATCH 4/6] PCI: qcom: Add support for Qualcomm SA8255p based PCIe Root Complex Add functionality to enable resource management (like clocks, regulators, PHY) through firmware and enumerate ECAM compliant Root Complex on SA8255p SoC, where the PCIe Root Complex is firmware managed and configured into ECAM compliant mode. Signed-off-by: Mayank Rana [mani: minor code cleanups and commit message rewording] Signed-off-by: Manivannan Sadhasivam [bhelgaas: add "ECAM" in comment] Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20250616224259.3549811-5-mayank.rana@oss.qualcomm.com --- drivers/pci/controller/dwc/Kconfig | 1 + drivers/pci/controller/dwc/pcie-qcom.c | 123 ++++++++++++++++++++++--- 2 files changed, 112 insertions(+), 12 deletions(-) diff --git a/drivers/pci/controller/dwc/Kconfig b/drivers/pci/controller/dwc/Kconfig index d9f0386396ed..ce04ee6fbd99 100644 --- a/drivers/pci/controller/dwc/Kconfig +++ b/drivers/pci/controller/dwc/Kconfig @@ -296,6 +296,7 @@ config PCIE_QCOM select PCIE_DW_HOST select CRC8 select PCIE_QCOM_COMMON + select PCI_HOST_COMMON help Say Y here to enable PCIe controller support on Qualcomm SoCs. The PCIe controller uses the DesignWare core plus Qualcomm-specific diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c index c789e3f85655..3014a0db022d 100644 --- a/drivers/pci/controller/dwc/pcie-qcom.c +++ b/drivers/pci/controller/dwc/pcie-qcom.c @@ -21,7 +21,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -34,6 +36,7 @@ #include #include "../../pci.h" +#include "../pci-host-common.h" #include "pcie-designware.h" #include "pcie-qcom-common.h" @@ -255,10 +258,12 @@ struct qcom_pcie_ops { * @ops: qcom PCIe ops structure * @override_no_snoop: Override NO_SNOOP attribute in TLP to enable cache * snooping + * @firmware_managed: Set if the Root Complex is firmware managed */ struct qcom_pcie_cfg { const struct qcom_pcie_ops *ops; bool override_no_snoop; + bool firmware_managed; bool no_l0s; }; @@ -1426,6 +1431,10 @@ static const struct qcom_pcie_cfg cfg_sc8280xp = { .no_l0s = true, }; +static const struct qcom_pcie_cfg cfg_fw_managed = { + .firmware_managed = true, +}; + static const struct dw_pcie_ops dw_pcie_ops = { .link_up = qcom_pcie_link_up, .start_link = qcom_pcie_start_link, @@ -1579,6 +1588,49 @@ static irqreturn_t qcom_pcie_global_irq_thread(int irq, void *data) return IRQ_HANDLED; } +static void qcom_pci_free_msi(void *ptr) +{ + struct dw_pcie_rp *pp = (struct dw_pcie_rp *)ptr; + + if (pp && pp->has_msi_ctrl) + dw_pcie_free_msi(pp); +} + +static int qcom_pcie_ecam_host_init(struct pci_config_window *cfg) +{ + struct device *dev = cfg->parent; + struct dw_pcie_rp *pp; + struct dw_pcie *pci; + int ret; + + pci = devm_kzalloc(dev, sizeof(*pci), GFP_KERNEL); + if (!pci) + return -ENOMEM; + + pci->dev = dev; + pp = &pci->pp; + pci->dbi_base = cfg->win; + pp->num_vectors = MSI_DEF_NUM_VECTORS; + + ret = dw_pcie_msi_host_init(pp); + if (ret) + return ret; + + pp->has_msi_ctrl = true; + dw_pcie_msi_init(pp); + + return devm_add_action_or_reset(dev, qcom_pci_free_msi, pp); +} + +static const struct pci_ecam_ops pci_qcom_ecam_ops = { + .init = qcom_pcie_ecam_host_init, + .pci_ops = { + .map_bus = pci_ecam_map_bus, + .read = pci_generic_config_read, + .write = pci_generic_config_write, + } +}; + static int qcom_pcie_probe(struct platform_device *pdev) { const struct qcom_pcie_cfg *pcie_cfg; @@ -1593,24 +1645,62 @@ static int qcom_pcie_probe(struct platform_device *pdev) char *name; pcie_cfg = of_device_get_match_data(dev); - if (!pcie_cfg || !pcie_cfg->ops) { - dev_err(dev, "Invalid platform data\n"); - return -EINVAL; + if (!pcie_cfg) { + dev_err(dev, "No platform data\n"); + return -ENODATA; } - pcie = devm_kzalloc(dev, sizeof(*pcie), GFP_KERNEL); - if (!pcie) - return -ENOMEM; - - pci = devm_kzalloc(dev, sizeof(*pci), GFP_KERNEL); - if (!pci) - return -ENOMEM; + if (!pcie_cfg->firmware_managed && !pcie_cfg->ops) { + dev_err(dev, "No platform ops\n"); + return -ENODATA; + } pm_runtime_enable(dev); ret = pm_runtime_get_sync(dev); if (ret < 0) goto err_pm_runtime_put; + if (pcie_cfg->firmware_managed) { + struct pci_host_bridge *bridge; + struct pci_config_window *cfg; + + bridge = devm_pci_alloc_host_bridge(dev, 0); + if (!bridge) { + ret = -ENOMEM; + goto err_pm_runtime_put; + } + + /* Parse and map our ECAM configuration space area */ + cfg = pci_host_common_ecam_create(dev, bridge, + &pci_qcom_ecam_ops); + if (IS_ERR(cfg)) { + ret = PTR_ERR(cfg); + goto err_pm_runtime_put; + } + + bridge->sysdata = cfg; + bridge->ops = (struct pci_ops *)&pci_qcom_ecam_ops.pci_ops; + bridge->msi_domain = true; + + ret = pci_host_probe(bridge); + if (ret) + goto err_pm_runtime_put; + + return 0; + } + + pcie = devm_kzalloc(dev, sizeof(*pcie), GFP_KERNEL); + if (!pcie) { + ret = -ENOMEM; + goto err_pm_runtime_put; + } + + pci = devm_kzalloc(dev, sizeof(*pci), GFP_KERNEL); + if (!pci) { + ret = -ENOMEM; + goto err_pm_runtime_put; + } + pci->dev = dev; pci->ops = &dw_pcie_ops; pp = &pci->pp; @@ -1756,9 +1846,13 @@ static int qcom_pcie_probe(struct platform_device *pdev) static int qcom_pcie_suspend_noirq(struct device *dev) { - struct qcom_pcie *pcie = dev_get_drvdata(dev); + struct qcom_pcie *pcie; int ret = 0; + pcie = dev_get_drvdata(dev); + if (!pcie) + return 0; + /* * Set minimum bandwidth required to keep data path functional during * suspend. @@ -1812,9 +1906,13 @@ static int qcom_pcie_suspend_noirq(struct device *dev) static int qcom_pcie_resume_noirq(struct device *dev) { - struct qcom_pcie *pcie = dev_get_drvdata(dev); + struct qcom_pcie *pcie; int ret; + pcie = dev_get_drvdata(dev); + if (!pcie) + return 0; + if (pm_suspend_target_state != PM_SUSPEND_MEM) { ret = icc_enable(pcie->icc_cpu); if (ret) { @@ -1849,6 +1947,7 @@ static const struct of_device_id qcom_pcie_match[] = { { .compatible = "qcom,pcie-ipq9574", .data = &cfg_2_9_0 }, { .compatible = "qcom,pcie-msm8996", .data = &cfg_2_3_2 }, { .compatible = "qcom,pcie-qcs404", .data = &cfg_2_4_0 }, + { .compatible = "qcom,pcie-sa8255p", .data = &cfg_fw_managed }, { .compatible = "qcom,pcie-sa8540p", .data = &cfg_sc8280xp }, { .compatible = "qcom,pcie-sa8775p", .data = &cfg_1_34_0}, { .compatible = "qcom,pcie-sc7280", .data = &cfg_1_9_0 }, From 38fcbfbd4207ec3fe47f66c2a16df7f5a857e198 Mon Sep 17 00:00:00 2001 From: Krishna Chaitanya Chundru Date: Wed, 2 Jul 2025 16:50:41 +0530 Subject: [PATCH 5/6] dt-bindings: PCI: qcom: Move PHY & reset GPIO to Root Port node Move the phys, phy-names, reset-gpios properties to the PCIe Root Port node from Host Bridge node, as agreed upon here [1]. Update the qcom,pcie-common.yaml to include the 'phys' property in the Root Port node. 'phy-names' property is not needed in Root Port since each Root Port supports only one PHY. Also, there is already 'reset-gpios' property defined for PERST# in pci-bus-common.yaml, so use that property instead of 'perst-gpios'. For backward compatibility, do not remove any existing properties in the bridge node, but mark them as 'deprecated' instead. [1] https://lore.kernel.org/linux-pci/20241211192014.GA3302752@bhelgaas/ Signed-off-by: Krishna Chaitanya Chundru [mani: commit message rewording] Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Reviewed-by: Rob Herring (Arm) Link: https://patch.msgid.link/20250702-perst-v5-1-920b3d1f6ee1@qti.qualcomm.com --- .../bindings/pci/qcom,pcie-common.yaml | 32 +++++++++++++++++-- .../bindings/pci/qcom,pcie-sc7280.yaml | 16 +++++++--- 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/Documentation/devicetree/bindings/pci/qcom,pcie-common.yaml b/Documentation/devicetree/bindings/pci/qcom,pcie-common.yaml index 0480c58f7d99..ab2509ec1c4b 100644 --- a/Documentation/devicetree/bindings/pci/qcom,pcie-common.yaml +++ b/Documentation/devicetree/bindings/pci/qcom,pcie-common.yaml @@ -51,10 +51,18 @@ properties: phys: maxItems: 1 + deprecated: true + description: + This property is deprecated, instead of referencing this property from + the host bridge node, use the property from the PCIe root port node. phy-names: items: - const: pciephy + deprecated: true + description: + Phandle to the register map node. This property is deprecated, and not + required to add in the root port also, as the root port has only one phy. power-domains: maxItems: 1 @@ -71,12 +79,18 @@ properties: maxItems: 12 perst-gpios: - description: GPIO controlled connection to PERST# signal + description: GPIO controlled connection to PERST# signal. This property is + deprecated, instead of referencing this property from the host bridge node, + use the reset-gpios property from the root port node. maxItems: 1 + deprecated: true wake-gpios: - description: GPIO controlled connection to WAKE# signal + description: GPIO controlled connection to WAKE# signal. This property is + deprecated, instead of referencing this property from the host bridge node, + use the property from the PCIe root port node. maxItems: 1 + deprecated: true vddpe-3v3-supply: description: PCIe endpoint power supply @@ -85,6 +99,20 @@ properties: opp-table: type: object +patternProperties: + "^pcie@": + type: object + $ref: /schemas/pci/pci-pci-bridge.yaml# + + properties: + reg: + maxItems: 1 + + phys: + maxItems: 1 + + unevaluatedProperties: false + required: - reg - reg-names diff --git a/Documentation/devicetree/bindings/pci/qcom,pcie-sc7280.yaml b/Documentation/devicetree/bindings/pci/qcom,pcie-sc7280.yaml index ff508f592a1a..4d0a91556603 100644 --- a/Documentation/devicetree/bindings/pci/qcom,pcie-sc7280.yaml +++ b/Documentation/devicetree/bindings/pci/qcom,pcie-sc7280.yaml @@ -165,9 +165,6 @@ examples: iommu-map = <0x0 &apps_smmu 0x1c80 0x1>, <0x100 &apps_smmu 0x1c81 0x1>; - phys = <&pcie1_phy>; - phy-names = "pciephy"; - pinctrl-names = "default"; pinctrl-0 = <&pcie1_clkreq_n>; @@ -176,7 +173,18 @@ examples: resets = <&gcc GCC_PCIE_1_BCR>; reset-names = "pci"; - perst-gpios = <&tlmm 2 GPIO_ACTIVE_LOW>; vddpe-3v3-supply = <&pp3300_ssd>; + pcie1_port0: pcie@0 { + device_type = "pci"; + reg = <0x0 0x0 0x0 0x0 0x0>; + bus-range = <0x01 0xff>; + + #address-cells = <3>; + #size-cells = <2>; + ranges; + phys = <&pcie1_phy>; + + reset-gpios = <&tlmm 2 GPIO_ACTIVE_LOW>; + }; }; }; From a2fbecdbbb9d7706fd3ec25f0dead83a2d542943 Mon Sep 17 00:00:00 2001 From: Krishna Chaitanya Chundru Date: Wed, 2 Jul 2025 16:50:42 +0530 Subject: [PATCH 6/6] PCI: qcom: Add support for parsing the new Root Port binding The DT binding has moved the PHY, PERST# properties to Root Port node from the Host Bridge node. So add support for parsing the new binding. The new binding uses 'reset-gpios' property for PERST#, hence parse the same property in the driver instead of the legacy 'perst-gpios'. To maintain DT backwards compatibility, fallback to the legacy method of parsing the host bridge node if the properties are not present in the Root Port node. Signed-off-by: Krishna Chaitanya Chundru [mani: refactored the root port parsing code, fixed a bug & commit message rewording] Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20250702-perst-v5-2-920b3d1f6ee1@qti.qualcomm.com --- drivers/pci/controller/dwc/pcie-qcom.c | 203 +++++++++++++++++++++---- 1 file changed, 176 insertions(+), 27 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c index 3014a0db022d..29b5db7aefe9 100644 --- a/drivers/pci/controller/dwc/pcie-qcom.c +++ b/drivers/pci/controller/dwc/pcie-qcom.c @@ -267,6 +267,12 @@ struct qcom_pcie_cfg { bool no_l0s; }; +struct qcom_pcie_port { + struct list_head list; + struct gpio_desc *reset; + struct phy *phy; +}; + struct qcom_pcie { struct dw_pcie *pci; void __iomem *parf; /* DT parf */ @@ -279,24 +285,37 @@ struct qcom_pcie { struct icc_path *icc_cpu; const struct qcom_pcie_cfg *cfg; struct dentry *debugfs; + struct list_head ports; bool suspended; bool use_pm_opp; }; #define to_qcom_pcie(x) dev_get_drvdata((x)->dev) +static void qcom_perst_assert(struct qcom_pcie *pcie, bool assert) +{ + struct qcom_pcie_port *port; + int val = assert ? 1 : 0; + + if (list_empty(&pcie->ports)) + gpiod_set_value_cansleep(pcie->reset, val); + else + list_for_each_entry(port, &pcie->ports, list) + gpiod_set_value_cansleep(port->reset, val); + + usleep_range(PERST_DELAY_US, PERST_DELAY_US + 500); +} + static void qcom_ep_reset_assert(struct qcom_pcie *pcie) { - gpiod_set_value_cansleep(pcie->reset, 1); - usleep_range(PERST_DELAY_US, PERST_DELAY_US + 500); + qcom_perst_assert(pcie, true); } static void qcom_ep_reset_deassert(struct qcom_pcie *pcie) { /* Ensure that PERST has been asserted for at least 100 ms */ msleep(PCIE_T_PVPERL_MS); - gpiod_set_value_cansleep(pcie->reset, 0); - usleep_range(PERST_DELAY_US, PERST_DELAY_US + 500); + qcom_perst_assert(pcie, false); } static int qcom_pcie_start_link(struct dw_pcie *pci) @@ -1234,6 +1253,59 @@ static bool qcom_pcie_link_up(struct dw_pcie *pci) return val & PCI_EXP_LNKSTA_DLLLA; } +static void qcom_pcie_phy_exit(struct qcom_pcie *pcie) +{ + struct qcom_pcie_port *port; + + if (list_empty(&pcie->ports)) + phy_exit(pcie->phy); + else + list_for_each_entry(port, &pcie->ports, list) + phy_exit(port->phy); +} + +static void qcom_pcie_phy_power_off(struct qcom_pcie *pcie) +{ + struct qcom_pcie_port *port; + + if (list_empty(&pcie->ports)) { + phy_power_off(pcie->phy); + } else { + list_for_each_entry(port, &pcie->ports, list) + phy_power_off(port->phy); + } +} + +static int qcom_pcie_phy_power_on(struct qcom_pcie *pcie) +{ + struct qcom_pcie_port *port; + int ret = 0; + + if (list_empty(&pcie->ports)) { + ret = phy_set_mode_ext(pcie->phy, PHY_MODE_PCIE, PHY_MODE_PCIE_RC); + if (ret) + return ret; + + ret = phy_power_on(pcie->phy); + if (ret) + return ret; + } else { + list_for_each_entry(port, &pcie->ports, list) { + ret = phy_set_mode_ext(port->phy, PHY_MODE_PCIE, PHY_MODE_PCIE_RC); + if (ret) + return ret; + + ret = phy_power_on(port->phy); + if (ret) { + qcom_pcie_phy_power_off(pcie); + return ret; + } + } + } + + return ret; +} + static int qcom_pcie_host_init(struct dw_pcie_rp *pp) { struct dw_pcie *pci = to_dw_pcie_from_pp(pp); @@ -1246,11 +1318,7 @@ static int qcom_pcie_host_init(struct dw_pcie_rp *pp) if (ret) return ret; - ret = phy_set_mode_ext(pcie->phy, PHY_MODE_PCIE, PHY_MODE_PCIE_RC); - if (ret) - goto err_deinit; - - ret = phy_power_on(pcie->phy); + ret = qcom_pcie_phy_power_on(pcie); if (ret) goto err_deinit; @@ -1273,7 +1341,7 @@ static int qcom_pcie_host_init(struct dw_pcie_rp *pp) err_assert_reset: qcom_ep_reset_assert(pcie); err_disable_phy: - phy_power_off(pcie->phy); + qcom_pcie_phy_power_off(pcie); err_deinit: pcie->cfg->ops->deinit(pcie); @@ -1286,7 +1354,7 @@ static void qcom_pcie_host_deinit(struct dw_pcie_rp *pp) struct qcom_pcie *pcie = to_qcom_pcie(pci); qcom_ep_reset_assert(pcie); - phy_power_off(pcie->phy); + qcom_pcie_phy_power_off(pcie); pcie->cfg->ops->deinit(pcie); } @@ -1631,10 +1699,85 @@ static const struct pci_ecam_ops pci_qcom_ecam_ops = { } }; +static int qcom_pcie_parse_port(struct qcom_pcie *pcie, struct device_node *node) +{ + struct device *dev = pcie->pci->dev; + struct qcom_pcie_port *port; + struct gpio_desc *reset; + struct phy *phy; + int ret; + + reset = devm_fwnode_gpiod_get(dev, of_fwnode_handle(node), + "reset", GPIOD_OUT_HIGH, "PERST#"); + if (IS_ERR(reset)) + return PTR_ERR(reset); + + phy = devm_of_phy_get(dev, node, NULL); + if (IS_ERR(phy)) + return PTR_ERR(phy); + + port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL); + if (!port) + return -ENOMEM; + + ret = phy_init(phy); + if (ret) + return ret; + + port->reset = reset; + port->phy = phy; + INIT_LIST_HEAD(&port->list); + list_add_tail(&port->list, &pcie->ports); + + return 0; +} + +static int qcom_pcie_parse_ports(struct qcom_pcie *pcie) +{ + struct device *dev = pcie->pci->dev; + struct qcom_pcie_port *port, *tmp; + int ret = -ENOENT; + + for_each_available_child_of_node_scoped(dev->of_node, of_port) { + ret = qcom_pcie_parse_port(pcie, of_port); + if (ret) + goto err_port_del; + } + + return ret; + +err_port_del: + list_for_each_entry_safe(port, tmp, &pcie->ports, list) + list_del(&port->list); + + return ret; +} + +static int qcom_pcie_parse_legacy_binding(struct qcom_pcie *pcie) +{ + struct device *dev = pcie->pci->dev; + int ret; + + pcie->phy = devm_phy_optional_get(dev, "pciephy"); + if (IS_ERR(pcie->phy)) + return PTR_ERR(pcie->phy); + + pcie->reset = devm_gpiod_get_optional(dev, "perst", GPIOD_OUT_HIGH); + if (IS_ERR(pcie->reset)) + return PTR_ERR(pcie->reset); + + ret = phy_init(pcie->phy); + if (ret) + return ret; + + return 0; +} + static int qcom_pcie_probe(struct platform_device *pdev) { const struct qcom_pcie_cfg *pcie_cfg; unsigned long max_freq = ULONG_MAX; + struct qcom_pcie_port *port, *tmp; struct device *dev = &pdev->dev; struct dev_pm_opp *opp; struct qcom_pcie *pcie; @@ -1701,6 +1844,8 @@ static int qcom_pcie_probe(struct platform_device *pdev) goto err_pm_runtime_put; } + INIT_LIST_HEAD(&pcie->ports); + pci->dev = dev; pci->ops = &dw_pcie_ops; pp = &pci->pp; @@ -1709,12 +1854,6 @@ static int qcom_pcie_probe(struct platform_device *pdev) pcie->cfg = pcie_cfg; - pcie->reset = devm_gpiod_get_optional(dev, "perst", GPIOD_OUT_HIGH); - if (IS_ERR(pcie->reset)) { - ret = PTR_ERR(pcie->reset); - goto err_pm_runtime_put; - } - pcie->parf = devm_platform_ioremap_resource_byname(pdev, "parf"); if (IS_ERR(pcie->parf)) { ret = PTR_ERR(pcie->parf); @@ -1737,12 +1876,6 @@ static int qcom_pcie_probe(struct platform_device *pdev) } } - pcie->phy = devm_phy_optional_get(dev, "pciephy"); - if (IS_ERR(pcie->phy)) { - ret = PTR_ERR(pcie->phy); - goto err_pm_runtime_put; - } - /* OPP table is optional */ ret = devm_pm_opp_of_add_table(dev); if (ret && ret != -ENODEV) { @@ -1789,9 +1922,23 @@ static int qcom_pcie_probe(struct platform_device *pdev) pp->ops = &qcom_pcie_dw_ops; - ret = phy_init(pcie->phy); - if (ret) - goto err_pm_runtime_put; + ret = qcom_pcie_parse_ports(pcie); + if (ret) { + if (ret != -ENOENT) { + dev_err_probe(pci->dev, ret, + "Failed to parse Root Port: %d\n", ret); + goto err_pm_runtime_put; + } + + /* + * In the case of properties not populated in Root Port node, + * fallback to the legacy method of parsing the Host Bridge + * node. This is to maintain DT backwards compatibility. + */ + ret = qcom_pcie_parse_legacy_binding(pcie); + if (ret) + goto err_pm_runtime_put; + } platform_set_drvdata(pdev, pcie); @@ -1836,7 +1983,9 @@ static int qcom_pcie_probe(struct platform_device *pdev) err_host_deinit: dw_pcie_host_deinit(pp); err_phy_exit: - phy_exit(pcie->phy); + qcom_pcie_phy_exit(pcie); + list_for_each_entry_safe(port, tmp, &pcie->ports, list) + list_del(&port->list); err_pm_runtime_put: pm_runtime_put(dev); pm_runtime_disable(dev);