* [PATCH v5 0/3] Add QMP PCIe multiple link-mode PHY support
@ 2026-07-17 9:58 Qiang Yu
2026-07-17 9:58 ` [PATCH v5 1/3] dt-bindings: phy: qcom: Add Glymur QMP PCIe multiple link-mode PHY Qiang Yu
` (2 more replies)
0 siblings, 3 replies; 6+ messages in thread
From: Qiang Yu @ 2026-07-17 9:58 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Manivannan Sadhasivam, Philipp Zabel,
Bjorn Andersson, Konrad Dybcio
Cc: Qiang Yu, linux-arm-msm, linux-phy, devicetree, linux-kernel
Hi all,
Some QMP PCIe PHY hardware blocks can be configured through a link-mode
register into different link topologies, such as a single wide link, or
multiple independent narrower links (e.g. x8 or x4+x4 mode on the Glymur
PCIe3 PHY).
Earlier revisions tried to extend the existing single-instance
phy-qcom-qmp-pcie.c driver to cover this hardware. That added a large
amount of conditional, multi-PHY logic to a driver whose data model
assumes one PHY per node.
This series adds a dedicated PHY provider driver,
phy-qcom-qmp-pcie-multiphy.c:
A single PHY provider node describes the shared hardware block. A new
"qcom,link-mode" property points at the TCSR syscon register that reports
the active topology; the driver reads it once at probe. #phy-cells = <1>
lets consumers pass a logical PHY index to obtain their sub-PHY. Match
data is indexed by link mode, and each link mode has its own array of
per-PHY config tables, so one shared provider exposes a different set of
logical PHYs depending on the active mode. The driver inherits the PHY
settings and link mode already programmed by UEFI, so only the no-CSR
reset is used, and no PHY setting tables or related structures are
defined. Each sub-PHY owns its register regions and power domain, so in a
bifurcated mode the links are brought up, and powered independently.
The driver is implemented and validated on Glymur, and is intended to
be extensible to other multi-mode QMP PCIe PHYs.
This series depends on a prerequisite patch by Krzysztof Kozlowski:
https://lore.kernel.org/r/20260420133616.88740-2-krzysztof.kozlowski@oss.qualcomm.com
Thanks,
Qiang
Signed-off-by: Qiang Yu <qiang.yu@oss.qualcomm.com>
---
Changes in v5:
- Split the Glymur multi PHY binding out of the shared qcom,sc8280xp-qmp-pcie-phy.yaml into its own schema.
- Reworked phy-qcom-qmp-pcie-multiphy.c into a standalone driver instead of extending phy-qcom-qmp-pcie.c with conditional multi-PHYlogic.
- Dropped the "link mode value" cell from qcom,link-mode.
- Removed the QMP_PHY_SELECTOR_0/1 dt-bindings macros.
- Link to v4: https://lore.kernel.org/all/20260518-link_mode_0519-v4-0-269cd73cc5d1@oss.qualcomm.com/
Changes in v4:
- Replaced the static bifurcation probe model with a link-mode
architecture: the active topology (x8 or x4+x4) is selected via a
new "qcom,link-mode" DT property and written to a TCSR register at
power-on. This replaces v3 patches 4 and 5 with four new patches
(driver refactor, secondary PHY clock/reset lists, link-mode probe
infrastructure, and Glymur config).
- Updated dt-bindings to describe "qcom,link-mode", #phy-cells = <1>,
per-mode validation rules, and added a new header with
QMP_PHY_SELECTOR_* and QMP_PCIE_GLYMUR_MODE_* macros.
- Patches 2 and 3 (multiple power-domains, multiple nocsr resets) are
unchanged from v3.
- Link to v3: https://lore.kernel.org/r/20260412-glymur_gen5x8_phy_0413-v3-0-affcebc16b8b@oss.qualcomm.com
Changes in v3:
- Add description of each power-domain.
- Add 64bit prefetchable memory range required by some EPs eg. AI100 ultra.
- Move PCIe3a after PCIe3b and move PCIe3a PHY before PCIe3b PHY.
- Link to v2: https://lore.kernel.org/all/20260323-glymur_gen5x8_phy_0323-v2-0-ce0fc07f0e52@oss.qualcomm.com/
Changes in v2:
- Remove pd_list from qmp_pcie struct as it is not used in phy driver.
- align clk-names on "
- Link to v1: https://lore.kernel.org/all/20260304-glymur_gen5x8_phy-v1-0-849e9a72e125@oss.qualcomm.com/
To: Vinod Koul <vkoul@kernel.org>
To: Neil Armstrong <neil.armstrong@linaro.org>
To: Rob Herring <robh@kernel.org>
To: Krzysztof Kozlowski <krzk+dt@kernel.org>
To: Conor Dooley <conor+dt@kernel.org>
To: Qiang Yu <qiang.yu@oss.qualcomm.com>
To: Manivannan Sadhasivam <manivannan.sadhasivam@oss.qualcomm.com>
To: Philipp Zabel <p.zabel@pengutronix.de>
To: Bjorn Andersson <andersson@kernel.org>
To: Konrad Dybcio <konradybcio@kernel.org>
Cc: linux-arm-msm@vger.kernel.org
Cc: linux-phy@lists.infradead.org
Cc: devicetree@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
---
Qiang Yu (3):
dt-bindings: phy: qcom: Add Glymur QMP PCIe multiple link-mode PHY
phy: qcom: qmp-pcie: Add QMP PCIe Multi-PHY driver
arm64: dts: qcom: glymur: Wire PCIe3a/3b to shared Gen5x8 PHY
.../phy/qcom,glymur-qmp-pcie-multiphy.yaml | 176 +++++
arch/arm64/boot/dts/qcom/glymur-crd.dtsi | 8 +
arch/arm64/boot/dts/qcom/glymur.dtsi | 336 ++++++++-
drivers/phy/qualcomm/Kconfig | 11 +
drivers/phy/qualcomm/Makefile | 1 +
drivers/phy/qualcomm/phy-qcom-qmp-pcie-multiphy.c | 770 +++++++++++++++++++++
6 files changed, 1300 insertions(+), 2 deletions(-)
---
base-commit: 59ccadbf59aedee486fce5b4a5486a07c3f4b5d3
change-id: 20260716-glymur_linkmode_0717-70eacf08a5c5
Best regards,
--
Qiang Yu <qiang.yu@oss.qualcomm.com>
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH v5 1/3] dt-bindings: phy: qcom: Add Glymur QMP PCIe multiple link-mode PHY
2026-07-17 9:58 [PATCH v5 0/3] Add QMP PCIe multiple link-mode PHY support Qiang Yu
@ 2026-07-17 9:58 ` Qiang Yu
2026-07-17 9:58 ` [PATCH v5 2/3] phy: qcom: qmp-pcie: Add QMP PCIe Multi-PHY driver Qiang Yu
2026-07-17 9:58 ` [PATCH v5 3/3] arm64: dts: qcom: glymur: Wire PCIe3a/3b to shared Gen5x8 PHY Qiang Yu
2 siblings, 0 replies; 6+ messages in thread
From: Qiang Yu @ 2026-07-17 9:58 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Manivannan Sadhasivam, Philipp Zabel,
Bjorn Andersson, Konrad Dybcio
Cc: Qiang Yu, linux-arm-msm, linux-phy, devicetree, linux-kernel
Add qcom,glymur-qmp-pcie-multiphy.yaml as a standalone binding
for the Glymur Gen5 PCIe PHY hardware block. This block supports two
link modes, selected at runtime via a TCSR syscon register:
1. x8 - a single 8-lane PHY instance is exposed
2. x4+x4 - two independent 4-lane PHY instances are exposed
Keep this as a separate schema from qcom,sc8280xp-qmp-pcie-phy.yaml
rather than folding it into the shared compatible list there, since the
two PHY instances active in x8 mode require twice as many clocks,
resets, and power-domains as any other entry in that file, and adding
Glymur-specific properties like qcom,link-mode and reg-names there
would only apply to this one compatible.
Document the required clocks, resets, and power-domains for both PHY
instances, and use #phy-cells = <1>, where the cell value is the PHY
index within the active link mode.
Signed-off-by: Qiang Yu <qiang.yu@oss.qualcomm.com>
---
.../phy/qcom,glymur-qmp-pcie-multiphy.yaml | 176 +++++++++++++++++++++
1 file changed, 176 insertions(+)
diff --git a/Documentation/devicetree/bindings/phy/qcom,glymur-qmp-pcie-multiphy.yaml b/Documentation/devicetree/bindings/phy/qcom,glymur-qmp-pcie-multiphy.yaml
new file mode 100644
index 000000000000..73550f77e9b6
--- /dev/null
+++ b/Documentation/devicetree/bindings/phy/qcom,glymur-qmp-pcie-multiphy.yaml
@@ -0,0 +1,176 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/phy/qcom,glymur-qmp-pcie-multiphy.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Qualcomm QMP PCIe multiple link-mode PHY controller (PCIe, Glymur)
+
+maintainers:
+ - Qiang Yu <qiang.yu@oss.qualcomm.com>
+ - Manivannan Sadhasivam <manivannan.sadhasivam@oss.qualcomm.com>
+
+description: |
+ The Glymur SoC uses a single PCIe Gen5 PHY hardware block for the
+ PCIe3a/PCIe3b controllers. This block supports two link modes, selected
+ at runtime via a TCSR syscon register:
+
+ 1. x8 - a single 8-lane PHY instance is exposed (PCIe3a only)
+ 2. x4+x4 - two independent 4-lane PHY instances are exposed (PCIe3a and
+ PCIe3b)
+
+ The node always describes both PHY instances ("port a" and "port b"),
+ regardless of which link mode is active on the board.
+
+properties:
+ compatible:
+ enum:
+ - qcom,glymur-qmp-gen5x8-pcie-phy
+
+ reg:
+ minItems: 2
+ maxItems: 2
+
+ reg-names:
+ items:
+ - const: port_a
+ - const: port_b
+
+ clocks:
+ minItems: 10
+ maxItems: 10
+
+ clock-names:
+ items:
+ - const: aux
+ - const: cfg_ahb
+ - const: ref
+ - const: rchng
+ - const: pipe
+ - const: phy_b_aux
+ - const: cfg_ahb_b
+ - const: rchng_b
+ - const: pipe_b
+ - const: pipediv2_b
+
+ power-domains:
+ minItems: 2
+ maxItems: 2
+
+ power-domain-names:
+ items:
+ - const: phy_a_gdsc
+ - const: phy_b_gdsc
+
+ resets:
+ minItems: 4
+ maxItems: 4
+
+ reset-names:
+ items:
+ - const: phy_a
+ - const: phy_a_nocsr
+ - const: phy_b
+ - const: phy_b_nocsr
+
+ vdda-phy-supply: true
+
+ vdda-pll-supply: true
+
+ vdda-refgen0p9-supply: true
+
+ vdda-refgen1p2-supply: true
+
+ qcom,link-mode:
+ description:
+ Reference to a register in the TCSR syscon that reports the link
+ mode the PCIe PHY is currently configured for, either a single x8
+ link or two independent x4 links. The link mode is programmed by
+ firmware before Linux boots; this property is only used to read
+ the active link mode, specified as a phandle to the syscon and
+ the register offset.
+ $ref: /schemas/types.yaml#/definitions/phandle-array
+ items:
+ - items:
+ - description: phandle of TCSR syscon
+ - description: offset of link mode register
+
+ "#clock-cells":
+ const: 1
+
+ clock-output-names:
+ items:
+ - description: Name of the PHY A pipe clock output.
+ - description: Name of the PHY B pipe clock output.
+
+ "#phy-cells":
+ const: 1
+
+required:
+ - compatible
+ - reg
+ - reg-names
+ - clocks
+ - clock-names
+ - power-domains
+ - power-domain-names
+ - resets
+ - reset-names
+ - vdda-phy-supply
+ - vdda-pll-supply
+ - vdda-refgen0p9-supply
+ - vdda-refgen1p2-supply
+ - qcom,link-mode
+ - "#clock-cells"
+ - clock-output-names
+ - "#phy-cells"
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/clock/qcom,glymur-gcc.h>
+ #include <dt-bindings/clock/qcom,glymur-tcsr.h>
+
+ pcie3_phy: phy@f00000 {
+ compatible = "qcom,glymur-qmp-gen5x8-pcie-phy";
+ reg = <0x00f00000 0x10000>, <0x00f10000 0x10000>;
+ reg-names = "port_a", "port_b";
+
+ clocks = <&gcc GCC_PCIE_PHY_3A_AUX_CLK>,
+ <&gcc GCC_PCIE_3A_CFG_AHB_CLK>,
+ <&tcsr TCSR_PCIE_3_CLKREF_EN>,
+ <&gcc GCC_PCIE_3A_PHY_RCHNG_CLK>,
+ <&gcc GCC_PCIE_3A_PIPE_CLK>,
+ <&gcc GCC_PCIE_PHY_3B_AUX_CLK>,
+ <&gcc GCC_PCIE_3B_CFG_AHB_CLK>,
+ <&gcc GCC_PCIE_3B_PHY_RCHNG_CLK>,
+ <&gcc GCC_PCIE_3B_PIPE_CLK>,
+ <&gcc GCC_PCIE_3B_PIPE_DIV2_CLK>;
+ clock-names = "aux", "cfg_ahb", "ref", "rchng", "pipe",
+ "phy_b_aux", "cfg_ahb_b", "rchng_b", "pipe_b",
+ "pipediv2_b";
+
+ resets = <&gcc GCC_PCIE_3A_PHY_BCR>,
+ <&gcc GCC_PCIE_3A_NOCSR_COM_PHY_BCR>,
+ <&gcc GCC_PCIE_3B_PHY_BCR>,
+ <&gcc GCC_PCIE_3B_NOCSR_COM_PHY_BCR>;
+ reset-names = "phy_a", "phy_a_nocsr", "phy_b", "phy_b_nocsr";
+
+ power-domains = <&gcc GCC_PCIE_3A_PHY_GDSC>,
+ <&gcc GCC_PCIE_3B_PHY_GDSC>;
+ power-domain-names = "phy_a_gdsc", "phy_b_gdsc";
+
+ vdda-phy-supply = <&vreg_l3c>;
+ vdda-pll-supply = <&vreg_l2c>;
+
+ vdda-refgen0p9-supply = <&vreg_l3c>;
+ vdda-refgen1p2-supply = <&vreg_l2c>;
+
+ qcom,link-mode = <&tcsr 0x5000>;
+
+ #clock-cells = <1>;
+ clock-output-names = "pcie3a_pipe_clk", "pcie3b_pipe_clk";
+
+ #phy-cells = <1>;
+ };
--
2.34.1
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH v5 2/3] phy: qcom: qmp-pcie: Add QMP PCIe Multi-PHY driver
2026-07-17 9:58 [PATCH v5 0/3] Add QMP PCIe multiple link-mode PHY support Qiang Yu
2026-07-17 9:58 ` [PATCH v5 1/3] dt-bindings: phy: qcom: Add Glymur QMP PCIe multiple link-mode PHY Qiang Yu
@ 2026-07-17 9:58 ` Qiang Yu
2026-07-17 22:45 ` Bjorn Andersson
2026-07-17 9:58 ` [PATCH v5 3/3] arm64: dts: qcom: glymur: Wire PCIe3a/3b to shared Gen5x8 PHY Qiang Yu
2 siblings, 1 reply; 6+ messages in thread
From: Qiang Yu @ 2026-07-17 9:58 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Manivannan Sadhasivam, Philipp Zabel,
Bjorn Andersson, Konrad Dybcio
Cc: Qiang Yu, linux-arm-msm, linux-phy, devicetree, linux-kernel
Some QMP PCIe PHY hardware blocks support multiple link topologies (e.g.
x8 or x4+x4) selected via a TCSR register. The existing single-instance
QMP PCIe PHY driver has no way to model this: it assumes a single cfg per
DT node and instantiates exactly one PHY.
Add a dedicated driver for this class of PHY. Match data carries a
per-mode cfg table; qmp_pcie_multiphy_probe() reads the current link
mode from the TCSR register pointed to by "qcom,link-mode", looks up the
corresponding cfg array, and instantiates one qmp_pcie per sub-PHY
required by that link mode, registering the clock and #phy-cells = <1> phy
providers so consumers can address individual sub-PHYs by index.
The driver inherits the phy setting and link-mode programmed by firmware,
so only the no_csr reset is used and no phy setting tables are provided.
Add the first match data and compatible, qcom,glymur-qmp-gen5x8-pcie-phy,
for the Glymur Gen5 PCIe PHY that can bifurcate into two x4 links or
operate as a single x8 link.
Signed-off-by: Qiang Yu <qiang.yu@oss.qualcomm.com>
---
drivers/phy/qualcomm/Kconfig | 11 +
drivers/phy/qualcomm/Makefile | 1 +
drivers/phy/qualcomm/phy-qcom-qmp-pcie-multiphy.c | 770 ++++++++++++++++++++++
3 files changed, 782 insertions(+)
diff --git a/drivers/phy/qualcomm/Kconfig b/drivers/phy/qualcomm/Kconfig
index 60a0ead127fa..31241d1bbef2 100644
--- a/drivers/phy/qualcomm/Kconfig
+++ b/drivers/phy/qualcomm/Kconfig
@@ -77,6 +77,17 @@ config PHY_QCOM_QMP_PCIE
Enable this to support the QMP PCIe PHY transceiver that is used
with PCIe controllers on Qualcomm chips.
+config PHY_QCOM_QMP_PCIE_MULTIPHY
+ tristate "Qualcomm QMP PCIe Multiple Link-mode PHY Driver"
+ depends on PCI || COMPILE_TEST
+ select GENERIC_PHY
+ default PHY_QCOM_QMP
+ help
+ Enable this to support the QMP PCIe PHY transceiver that is used
+ with PCIe controllers on Qualcomm chips. This PHY is a single
+ multi-lane QMP block that can be configured either as one wide
+ link or as multiple narrower independent links (bifurcation).
+
config PHY_QCOM_QMP_PCIE_8996
tristate "Qualcomm QMP PCIe 8996 PHY Driver"
depends on PCI || COMPILE_TEST
diff --git a/drivers/phy/qualcomm/Makefile b/drivers/phy/qualcomm/Makefile
index b71a6a0bed3f..8bf887d58ee4 100644
--- a/drivers/phy/qualcomm/Makefile
+++ b/drivers/phy/qualcomm/Makefile
@@ -10,6 +10,7 @@ obj-$(CONFIG_PHY_QCOM_PCIE2) += phy-qcom-pcie2.o
obj-$(CONFIG_PHY_QCOM_QMP_COMBO) += phy-qcom-qmp-combo.o phy-qcom-qmp-usbc.o
obj-$(CONFIG_PHY_QCOM_QMP_PCIE) += phy-qcom-qmp-pcie.o
+obj-$(CONFIG_PHY_QCOM_QMP_PCIE_MULTIPHY) += phy-qcom-qmp-pcie-multiphy.o
obj-$(CONFIG_PHY_QCOM_QMP_PCIE_8996) += phy-qcom-qmp-pcie-msm8996.o
obj-$(CONFIG_PHY_QCOM_QMP_UFS) += phy-qcom-qmp-ufs.o
obj-$(CONFIG_PHY_QCOM_QMP_USB) += phy-qcom-qmp-usb.o
diff --git a/drivers/phy/qualcomm/phy-qcom-qmp-pcie-multiphy.c b/drivers/phy/qualcomm/phy-qcom-qmp-pcie-multiphy.c
new file mode 100644
index 000000000000..b79edf2b7d5f
--- /dev/null
+++ b/drivers/phy/qualcomm/phy-qcom-qmp-pcie-multiphy.c
@@ -0,0 +1,770 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2026, Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/pm_domain.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/reset.h>
+#include <linux/slab.h>
+
+#include "phy-qcom-qmp.h"
+
+#define PHY_INIT_COMPLETE_TIMEOUT 10000
+
+enum qmp_pcie_glymur_link_mode {
+ QMP_PCIE_GLYMUR_MODE_X8,
+ QMP_PCIE_GLYMUR_MODE_X4X4,
+};
+
+enum qphy_reg_layout {
+ QPHY_PCS_STATUS,
+ QPHY_LAYOUT_SIZE
+};
+
+static const unsigned int pciephy_v8_50_regs_layout[QPHY_LAYOUT_SIZE] = {
+ [QPHY_PCS_STATUS] = QPHY_V8_50_PCS_STATUS1,
+};
+
+struct qmp_pcie_offsets {
+ u16 pcs;
+};
+
+struct qmp_phy_cfg {
+ const struct qmp_pcie_offsets *offsets;
+
+ const char * const *reg_names;
+ int num_regs;
+
+ const char * const *pd_names;
+ int num_pds;
+
+ const char * const *nocsr_reset_list;
+ int num_nocsr_resets;
+
+ const char * const *vreg_list;
+ int num_vregs;
+
+ const unsigned int *regs;
+
+ unsigned int phy_status;
+
+ const char * const *clk_list;
+ int num_clks;
+ const char * const *pipe_clk_list;
+
+ int num_pipe_clks;
+};
+
+struct qmp_pcie {
+ struct device *dev;
+
+ const struct qmp_phy_cfg *cfg;
+
+ void __iomem **base;
+
+ struct clk_bulk_data *clks;
+ struct clk_bulk_data *pipe_clks;
+
+ struct reset_control_bulk_data *nocsr_resets;
+
+ struct regulator_bulk_data *vregs;
+
+ struct device **pd_devs;
+
+ struct clk_fixed_rate pipe_clk_fixed;
+};
+
+struct qmp_pcie_multiphy {
+ struct phy **phys;
+ const struct qmp_pcie_link_mode_cfg *mode_cfg;
+
+ int num_pipe_outputs;
+ struct clk_fixed_rate *pipe_out_clks;
+};
+
+struct qmp_pcie_link_mode_cfg {
+ const struct qmp_phy_cfg * const *cfgs;
+ u32 num_phys;
+};
+
+struct qmp_pcie_match_data {
+ const struct qmp_pcie_link_mode_cfg *mode_cfgs;
+ u32 num_modes;
+};
+
+static const char * const glymur_pciephy_clk_l[] = {
+ "aux", "cfg_ahb", "ref", "rchng", "phy_b_aux",
+};
+
+static const char * const glymur_pciephy_a_clk_l[] = {
+ "aux", "cfg_ahb", "ref", "rchng",
+};
+
+static const char * const glymur_pciephy_b_clk_l[] = {
+ "phy_b_aux", "cfg_ahb_b", "ref", "rchng_b",
+};
+
+static const char * const glymur_pciephy_pipeclk_l[] = {
+ "pipe",
+};
+
+static const char * const glymur_pipephy_b_pipeclk_l[] = {
+ "pipe_b", "pipediv2_b",
+};
+
+static const char * const glymur_vreg_l[] = {
+ "vdda-phy", "vdda-pll", "vdda-refgen0p9", "vdda-refgen1p2",
+};
+
+static const char * const glymur_pciephy_a_reg_l[] = {
+ "port_a",
+};
+
+static const char * const glymur_pciephy_b_reg_l[] = {
+ "port_b",
+};
+
+static const char * const glymur_pciephy_reg_l[] = {
+ "port_a", "port_b",
+};
+
+static const char * const glymur_pciephy_a_pd_l[] = {
+ "phy_a_gdsc",
+};
+
+static const char * const glymur_pciephy_b_pd_l[] = {
+ "phy_b_gdsc",
+};
+
+static const char * const glymur_pciephy_pd_l[] = {
+ "phy_a_gdsc", "phy_b_gdsc",
+};
+
+static const char * const glymur_pciephy_a_nocsr_reset_l[] = {
+ "phy_a_nocsr",
+};
+
+static const char * const glymur_pciephy_nocsr_reset_l[] = {
+ "phy_a_nocsr", "phy_b_nocsr",
+};
+
+static const char * const glymur_pciephy_b_nocsr_reset_l[] = {
+ "phy_b_nocsr",
+};
+
+static const struct qmp_pcie_offsets glymur_pcie_offsets_v8_50 = {
+ .pcs = 0x9000,
+};
+
+static const struct qmp_phy_cfg glymur_qmp_gen5x4_pciephy_a_cfg = {
+ .offsets = &glymur_pcie_offsets_v8_50,
+ .reg_names = glymur_pciephy_a_reg_l,
+ .num_regs = ARRAY_SIZE(glymur_pciephy_a_reg_l),
+ .pd_names = glymur_pciephy_a_pd_l,
+ .num_pds = ARRAY_SIZE(glymur_pciephy_a_pd_l),
+ .nocsr_reset_list = glymur_pciephy_a_nocsr_reset_l,
+ .num_nocsr_resets = ARRAY_SIZE(glymur_pciephy_a_nocsr_reset_l),
+ .vreg_list = glymur_vreg_l,
+ .num_vregs = ARRAY_SIZE(glymur_vreg_l),
+ .regs = pciephy_v8_50_regs_layout,
+ .phy_status = PHYSTATUS_4_20,
+ .pipe_clk_list = glymur_pciephy_pipeclk_l,
+ .num_pipe_clks = ARRAY_SIZE(glymur_pciephy_pipeclk_l),
+ .clk_list = glymur_pciephy_a_clk_l,
+ .num_clks = ARRAY_SIZE(glymur_pciephy_a_clk_l),
+};
+
+static const struct qmp_phy_cfg glymur_qmp_gen5x4_pciephy_b_cfg = {
+ .offsets = &glymur_pcie_offsets_v8_50,
+ .reg_names = glymur_pciephy_b_reg_l,
+ .num_regs = ARRAY_SIZE(glymur_pciephy_b_reg_l),
+ .pd_names = glymur_pciephy_b_pd_l,
+ .num_pds = ARRAY_SIZE(glymur_pciephy_b_pd_l),
+ .nocsr_reset_list = glymur_pciephy_b_nocsr_reset_l,
+ .num_nocsr_resets = ARRAY_SIZE(glymur_pciephy_b_nocsr_reset_l),
+ .vreg_list = glymur_vreg_l,
+ .num_vregs = ARRAY_SIZE(glymur_vreg_l),
+ .regs = pciephy_v8_50_regs_layout,
+ .phy_status = PHYSTATUS_4_20,
+ .pipe_clk_list = glymur_pipephy_b_pipeclk_l,
+ .num_pipe_clks = ARRAY_SIZE(glymur_pipephy_b_pipeclk_l),
+ .clk_list = glymur_pciephy_b_clk_l,
+ .num_clks = ARRAY_SIZE(glymur_pciephy_b_clk_l),
+};
+
+static const struct qmp_phy_cfg glymur_qmp_gen5x8_pciephy_cfg = {
+ .offsets = &glymur_pcie_offsets_v8_50,
+ .reg_names = glymur_pciephy_reg_l,
+ .num_regs = ARRAY_SIZE(glymur_pciephy_reg_l),
+ .pd_names = glymur_pciephy_pd_l,
+ .num_pds = ARRAY_SIZE(glymur_pciephy_pd_l),
+ .nocsr_reset_list = glymur_pciephy_nocsr_reset_l,
+ .num_nocsr_resets = ARRAY_SIZE(glymur_pciephy_nocsr_reset_l),
+ .vreg_list = glymur_vreg_l,
+ .num_vregs = ARRAY_SIZE(glymur_vreg_l),
+ .regs = pciephy_v8_50_regs_layout,
+ .phy_status = PHYSTATUS_4_20,
+ .pipe_clk_list = glymur_pciephy_pipeclk_l,
+ .num_pipe_clks = ARRAY_SIZE(glymur_pciephy_pipeclk_l),
+ .clk_list = glymur_pciephy_clk_l,
+ .num_clks = ARRAY_SIZE(glymur_pciephy_clk_l),
+};
+
+static const struct qmp_phy_cfg * const glymur_qmp_gen5x8_mode_x8_cfgs[] = {
+ &glymur_qmp_gen5x8_pciephy_cfg,
+};
+
+static const struct qmp_phy_cfg * const glymur_qmp_gen5x8_mode_x4x4_cfgs[] = {
+ &glymur_qmp_gen5x4_pciephy_a_cfg,
+ &glymur_qmp_gen5x4_pciephy_b_cfg,
+};
+
+static const struct qmp_pcie_link_mode_cfg glymur_qmp_gen5x8_mode_cfgs[] = {
+ [QMP_PCIE_GLYMUR_MODE_X8] = {
+ .cfgs = glymur_qmp_gen5x8_mode_x8_cfgs,
+ .num_phys = ARRAY_SIZE(glymur_qmp_gen5x8_mode_x8_cfgs),
+ },
+ [QMP_PCIE_GLYMUR_MODE_X4X4] = {
+ .cfgs = glymur_qmp_gen5x8_mode_x4x4_cfgs,
+ .num_phys = ARRAY_SIZE(glymur_qmp_gen5x8_mode_x4x4_cfgs),
+ },
+};
+
+static const struct qmp_pcie_match_data glymur_qmp_gen5x8_match_data = {
+ .mode_cfgs = glymur_qmp_gen5x8_mode_cfgs,
+ .num_modes = ARRAY_SIZE(glymur_qmp_gen5x8_mode_cfgs),
+};
+
+static int qmp_pcie_pd_power_on(struct qmp_pcie *qmp)
+{
+ const struct qmp_phy_cfg *cfg = qmp->cfg;
+ int i, ret;
+
+ for (i = 0; i < cfg->num_pds; i++) {
+ ret = pm_runtime_get_sync(qmp->pd_devs[i]);
+ if (ret < 0) {
+ dev_err(qmp->dev, "failed to power on %s domain\n",
+ cfg->pd_names[i]);
+ goto err_power_off;
+ }
+ }
+
+ return 0;
+
+err_power_off:
+ while (--i >= 0)
+ pm_runtime_put(qmp->pd_devs[i]);
+
+ return ret;
+}
+
+static void qmp_pcie_pd_power_off(struct qmp_pcie *qmp)
+{
+ const struct qmp_phy_cfg *cfg = qmp->cfg;
+ int i;
+
+ for (i = cfg->num_pds - 1; i >= 0; i--)
+ pm_runtime_put(qmp->pd_devs[i]);
+}
+
+static int qmp_pcie_init(struct phy *phy)
+{
+ struct qmp_pcie *qmp = phy_get_drvdata(phy);
+ const struct qmp_phy_cfg *cfg = qmp->cfg;
+ int ret;
+
+ ret = qmp_pcie_pd_power_on(qmp);
+ if (ret)
+ return ret;
+
+ ret = regulator_bulk_enable(cfg->num_vregs, qmp->vregs);
+ if (ret) {
+ dev_err(qmp->dev, "failed to enable regulators, err=%d\n", ret);
+ goto err_pd_power_off;
+ }
+
+ ret = reset_control_bulk_assert(qmp->cfg->num_nocsr_resets, qmp->nocsr_resets);
+ if (ret) {
+ dev_err(qmp->dev, "no-csr reset assert failed\n");
+ goto err_disable_regulators;
+ }
+
+ usleep_range(200, 300);
+
+ ret = clk_bulk_prepare_enable(qmp->cfg->num_clks, qmp->clks);
+ if (ret)
+ goto err_disable_regulators;
+
+ return 0;
+
+err_disable_regulators:
+ regulator_bulk_disable(cfg->num_vregs, qmp->vregs);
+err_pd_power_off:
+ qmp_pcie_pd_power_off(qmp);
+
+ return ret;
+}
+
+static int qmp_pcie_exit(struct phy *phy)
+{
+ struct qmp_pcie *qmp = phy_get_drvdata(phy);
+ const struct qmp_phy_cfg *cfg = qmp->cfg;
+
+ reset_control_bulk_assert(qmp->cfg->num_nocsr_resets, qmp->nocsr_resets);
+
+ clk_bulk_disable_unprepare(qmp->cfg->num_clks, qmp->clks);
+ regulator_bulk_disable(cfg->num_vregs, qmp->vregs);
+ qmp_pcie_pd_power_off(qmp);
+
+ return 0;
+}
+
+static int qmp_pcie_power_on(struct phy *phy)
+{
+ struct qmp_pcie *qmp = phy_get_drvdata(phy);
+ const struct qmp_phy_cfg *cfg = qmp->cfg;
+ const struct qmp_pcie_offsets *offs = cfg->offsets;
+ void __iomem *status;
+ unsigned int val;
+ int i, ret;
+
+ ret = clk_bulk_prepare_enable(qmp->cfg->num_pipe_clks, qmp->pipe_clks);
+ if (ret)
+ return ret;
+
+ ret = reset_control_bulk_deassert(qmp->cfg->num_nocsr_resets, qmp->nocsr_resets);
+ if (ret) {
+ dev_err(qmp->dev, "no-csr reset deassert failed\n");
+ goto err_disable_pipe_clk;
+ }
+
+ for (i = 0; i < cfg->num_regs; i++) {
+ status = qmp->base[i] + offs->pcs + cfg->regs[QPHY_PCS_STATUS];
+ ret = readl_poll_timeout(status, val, !(val & cfg->phy_status), 200,
+ PHY_INIT_COMPLETE_TIMEOUT);
+ if (ret) {
+ dev_err(qmp->dev, "phy initialization timed-out (%s)\n",
+ cfg->reg_names[i]);
+ goto err_disable_pipe_clk;
+ }
+ }
+
+ return 0;
+
+err_disable_pipe_clk:
+ clk_bulk_disable_unprepare(qmp->cfg->num_pipe_clks, qmp->pipe_clks);
+
+ return ret;
+}
+
+
+static int qmp_pcie_power_off(struct phy *phy)
+{
+ struct qmp_pcie *qmp = phy_get_drvdata(phy);
+
+ clk_bulk_disable_unprepare(qmp->cfg->num_pipe_clks, qmp->pipe_clks);
+
+ return 0;
+}
+
+static int qmp_pcie_enable(struct phy *phy)
+{
+ int ret;
+
+ ret = qmp_pcie_init(phy);
+ if (ret)
+ return ret;
+
+ ret = qmp_pcie_power_on(phy);
+ if (ret)
+ qmp_pcie_exit(phy);
+
+ return ret;
+}
+
+static int qmp_pcie_disable(struct phy *phy)
+{
+ int ret;
+
+ ret = qmp_pcie_power_off(phy);
+ if (ret)
+ return ret;
+
+ return qmp_pcie_exit(phy);
+}
+
+static const struct phy_ops qmp_pcie_phy_ops = {
+ .power_on = qmp_pcie_enable,
+ .power_off = qmp_pcie_disable,
+ .owner = THIS_MODULE,
+};
+
+static void qmp_pcie_pd_detach(void *data)
+{
+ struct qmp_pcie *qmp = data;
+ const struct qmp_phy_cfg *cfg = qmp->cfg;
+ int i;
+
+ for (i = 0; i < cfg->num_pds; i++) {
+ if (!IS_ERR_OR_NULL(qmp->pd_devs[i]))
+ dev_pm_domain_detach(qmp->pd_devs[i], true);
+ }
+}
+
+static int qmp_pcie_pd_init(struct qmp_pcie *qmp)
+{
+ const struct qmp_phy_cfg *cfg = qmp->cfg;
+ struct device *dev = qmp->dev;
+ int i, ret;
+
+ if (!cfg->num_pds)
+ return 0;
+
+ qmp->pd_devs = devm_kcalloc(dev, cfg->num_pds, sizeof(*qmp->pd_devs),
+ GFP_KERNEL);
+ if (!qmp->pd_devs)
+ return -ENOMEM;
+
+ for (i = 0; i < cfg->num_pds; i++) {
+ qmp->pd_devs[i] = dev_pm_domain_attach_by_name(dev,
+ cfg->pd_names[i]);
+ if (IS_ERR(qmp->pd_devs[i])) {
+ ret = PTR_ERR(qmp->pd_devs[i]);
+ goto err_detach;
+ }
+ }
+
+ return devm_add_action_or_reset(dev, qmp_pcie_pd_detach, qmp);
+
+err_detach:
+ while (--i >= 0)
+ dev_pm_domain_detach(qmp->pd_devs[i], false);
+
+ return ret;
+}
+
+static int qmp_pcie_vreg_init(struct qmp_pcie *qmp)
+{
+ const struct qmp_phy_cfg *cfg = qmp->cfg;
+ struct device *dev = qmp->dev;
+ int i;
+
+ qmp->vregs = devm_kcalloc(dev, cfg->num_vregs, sizeof(*qmp->vregs),
+ GFP_KERNEL);
+ if (!qmp->vregs)
+ return -ENOMEM;
+
+ for (i = 0; i < cfg->num_vregs; i++)
+ qmp->vregs[i].supply = cfg->vreg_list[i];
+
+ return devm_regulator_bulk_get(dev, cfg->num_vregs, qmp->vregs);
+}
+
+static int qmp_pcie_reset_init(struct qmp_pcie *qmp)
+{
+ const struct qmp_phy_cfg *cfg = qmp->cfg;
+ struct device *dev = qmp->dev;
+ int i, ret;
+
+ qmp->nocsr_resets = devm_kcalloc(dev, cfg->num_nocsr_resets,
+ sizeof(*qmp->nocsr_resets),
+ GFP_KERNEL);
+ if (!qmp->nocsr_resets)
+ return -ENOMEM;
+
+ for (i = 0; i < cfg->num_nocsr_resets; i++)
+ qmp->nocsr_resets[i].id = cfg->nocsr_reset_list[i];
+
+ ret = devm_reset_control_bulk_get_exclusive(dev,
+ cfg->num_nocsr_resets,
+ qmp->nocsr_resets);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to get no-csr resets\n");
+
+ return 0;
+}
+
+static int qmp_pcie_clk_init(struct qmp_pcie *qmp)
+{
+ const struct qmp_phy_cfg *cfg = qmp->cfg;
+ struct device *dev = qmp->dev;
+ int i, ret;
+
+ qmp->clks = devm_kcalloc(dev, cfg->num_clks, sizeof(*qmp->clks),
+ GFP_KERNEL);
+ if (!qmp->clks)
+ return -ENOMEM;
+
+ for (i = 0; i < cfg->num_clks; i++)
+ qmp->clks[i].id = cfg->clk_list[i];
+
+ ret = devm_clk_bulk_get_optional(dev, cfg->num_clks, qmp->clks);
+ if (ret)
+ return ret;
+
+ qmp->pipe_clks = devm_kcalloc(dev, cfg->num_pipe_clks,
+ sizeof(*qmp->pipe_clks), GFP_KERNEL);
+ if (!qmp->pipe_clks)
+ return -ENOMEM;
+
+ for (i = 0; i < cfg->num_pipe_clks; i++)
+ qmp->pipe_clks[i].id = cfg->pipe_clk_list[i];
+
+ return devm_clk_bulk_get_optional(dev, cfg->num_pipe_clks,
+ qmp->pipe_clks);
+}
+
+static int __phy_pipe_clk_register(struct device *dev, struct device_node *np,
+ int idx, struct clk_fixed_rate *fixed)
+{
+ struct clk_init_data init = { };
+ int ret;
+
+ ret = of_property_read_string_index(np, "clock-output-names", idx,
+ &init.name);
+ if (ret) {
+ dev_err(dev, "%pOFn: No clock-output-names\n", np);
+ return ret;
+ }
+
+ init.ops = &clk_fixed_rate_ops;
+
+ if (!fixed->fixed_rate)
+ fixed->fixed_rate = 125000000;
+
+ fixed->hw.init = &init;
+
+ return devm_clk_hw_register(dev, &fixed->hw);
+}
+
+static struct clk_hw *qmp_pcie_multiphy_clk_hw_get(struct of_phandle_args *clkspec,
+ void *data)
+{
+ struct qmp_pcie_multiphy *qmp_data = data;
+ unsigned int idx = 0;
+
+ if (clkspec->args_count)
+ idx = clkspec->args[0];
+
+ if (idx < (unsigned int)qmp_data->num_pipe_outputs)
+ return &qmp_data->pipe_out_clks[idx].hw;
+
+ return ERR_PTR(-EINVAL);
+}
+
+static int qmp_pcie_multiphy_register_clocks(struct device *dev,
+ struct device_node *np,
+ struct qmp_pcie_multiphy *qmp_data)
+{
+ int num_pipe_outputs;
+ int i, ret;
+
+ num_pipe_outputs = of_property_count_strings(np, "clock-output-names");
+ if (num_pipe_outputs < 0)
+ num_pipe_outputs = 1;
+
+ qmp_data->num_pipe_outputs = num_pipe_outputs;
+ qmp_data->pipe_out_clks = devm_kcalloc(dev, num_pipe_outputs,
+ sizeof(*qmp_data->pipe_out_clks),
+ GFP_KERNEL);
+ if (!qmp_data->pipe_out_clks)
+ return -ENOMEM;
+
+ for (i = 0; i < num_pipe_outputs; i++) {
+ ret = __phy_pipe_clk_register(dev, np, i,
+ &qmp_data->pipe_out_clks[i]);
+ if (ret)
+ return ret;
+ }
+
+ return devm_of_clk_add_hw_provider(dev, qmp_pcie_multiphy_clk_hw_get, qmp_data);
+}
+
+static int qmp_pcie_get_mmio(struct qmp_pcie *qmp)
+{
+ struct platform_device *pdev = to_platform_device(qmp->dev);
+ const struct qmp_phy_cfg *cfg = qmp->cfg;
+ struct device *dev = qmp->dev;
+ void __iomem *base;
+ int i;
+
+ qmp->base = devm_kcalloc(dev, cfg->num_regs, sizeof(*qmp->base),
+ GFP_KERNEL);
+ if (!qmp->base)
+ return -ENOMEM;
+
+ for (i = 0; i < cfg->num_regs; i++) {
+ base = devm_platform_ioremap_resource_byname(pdev, cfg->reg_names[i]);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ qmp->base[i] = base;
+ }
+
+ return 0;
+}
+
+static int qmp_pcie_read_link_mode(struct device *dev, unsigned int *link_mode)
+{
+ struct regmap *map;
+ unsigned int args[1];
+ int ret;
+
+ map = syscon_regmap_lookup_by_phandle_args(dev->of_node, "qcom,link-mode",
+ ARRAY_SIZE(args), args);
+ if (IS_ERR(map))
+ return PTR_ERR(map);
+
+ ret = regmap_read(map, args[0], link_mode);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static struct phy *qmp_pcie_multiphy_xlate(struct device *dev,
+ const struct of_phandle_args *args)
+{
+ struct qmp_pcie_multiphy *qmp_data = dev_get_drvdata(dev);
+ unsigned int idx;
+
+ if (!qmp_data || args->args_count < 1)
+ return ERR_PTR(-EINVAL);
+
+ idx = args->args[0];
+
+ if (idx < (unsigned int)qmp_data->mode_cfg->num_phys)
+ return qmp_data->phys[idx] ?: ERR_PTR(-EINVAL);
+
+ return ERR_PTR(-EINVAL);
+}
+
+static int qmp_pcie_probe_phy(struct qmp_pcie *qmp, struct device_node *np,
+ struct phy **out_phy)
+{
+ int ret;
+
+ ret = qmp_pcie_get_mmio(qmp);
+ if (ret)
+ return ret;
+
+ ret = qmp_pcie_clk_init(qmp);
+ if (ret)
+ return ret;
+
+ ret = qmp_pcie_reset_init(qmp);
+ if (ret)
+ return ret;
+
+ ret = qmp_pcie_vreg_init(qmp);
+ if (ret)
+ return ret;
+
+ ret = qmp_pcie_pd_init(qmp);
+ if (ret)
+ return ret;
+
+ *out_phy = devm_phy_create(qmp->dev, np, &qmp_pcie_phy_ops);
+ if (IS_ERR(*out_phy))
+ return PTR_ERR(*out_phy);
+
+ phy_set_drvdata(*out_phy, qmp);
+
+ return 0;
+}
+
+
+static int qmp_pcie_multiphy_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct phy_provider *phy_provider;
+ struct qmp_pcie_multiphy *qmp_data;
+ const struct qmp_pcie_match_data *match_data;
+ struct qmp_pcie *qmp;
+ struct phy **phys;
+ unsigned int link_mode;
+ int phy_index;
+ int ret;
+
+ qmp_data = devm_kzalloc(dev, sizeof(*qmp_data), GFP_KERNEL);
+
+ match_data = of_device_get_match_data(dev);
+ if (!match_data)
+ return -EINVAL;
+
+ if (!qmp_data)
+ return -ENOMEM;
+
+ ret = qmp_pcie_read_link_mode(dev, &link_mode);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to read qcom,link-mode\n");
+
+ if (link_mode >= match_data->num_modes)
+ return dev_err_probe(dev, -EINVAL, "invalid qcom,link-mode: %u\n",
+ link_mode);
+
+ qmp_data->mode_cfg = &match_data->mode_cfgs[link_mode];
+
+ qmp = devm_kcalloc(dev, qmp_data->mode_cfg->num_phys, sizeof(*qmp), GFP_KERNEL);
+ if (!qmp)
+ return -ENOMEM;
+
+ phys = devm_kcalloc(dev, qmp_data->mode_cfg->num_phys, sizeof(*phys), GFP_KERNEL);
+ if (!phys)
+ return -ENOMEM;
+
+ qmp_data->phys = phys;
+ dev_set_drvdata(dev, qmp_data);
+
+ for (phy_index = 0; phy_index < qmp_data->mode_cfg->num_phys; phy_index++) {
+ qmp[phy_index].dev = dev;
+ qmp[phy_index].cfg = qmp_data->mode_cfg->cfgs[phy_index];
+ ret = qmp_pcie_probe_phy(&qmp[phy_index], dev->of_node, &phys[phy_index]);
+ if (ret)
+ return ret;
+ }
+
+ ret = qmp_pcie_multiphy_register_clocks(dev, dev->of_node, qmp_data);
+ if (ret)
+ return ret;
+
+ phy_provider = devm_of_phy_provider_register(dev, qmp_pcie_multiphy_xlate);
+
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static const struct of_device_id qmp_pcie_multiphy_of_match_table[] = {
+ {
+ .compatible = "qcom,glymur-qmp-gen5x8-pcie-phy",
+ .data = &glymur_qmp_gen5x8_match_data,
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(of, qmp_pcie_multiphy_of_match_table);
+
+static struct platform_driver qmp_pcie_multiphy_driver = {
+ .probe = qmp_pcie_multiphy_probe,
+ .driver = {
+ .name = "qcom-qmp-pcie-multiphy",
+ .of_match_table = qmp_pcie_multiphy_of_match_table,
+ },
+};
+module_platform_driver(qmp_pcie_multiphy_driver);
+
+MODULE_AUTHOR("Qiang Yu <qiang.yu@oss.qualcomm.com>");
+MODULE_DESCRIPTION("Qualcomm QMP PCIe PHY driver for Glymur");
+MODULE_LICENSE("GPL");
--
2.34.1
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH v5 3/3] arm64: dts: qcom: glymur: Wire PCIe3a/3b to shared Gen5x8 PHY
2026-07-17 9:58 [PATCH v5 0/3] Add QMP PCIe multiple link-mode PHY support Qiang Yu
2026-07-17 9:58 ` [PATCH v5 1/3] dt-bindings: phy: qcom: Add Glymur QMP PCIe multiple link-mode PHY Qiang Yu
2026-07-17 9:58 ` [PATCH v5 2/3] phy: qcom: qmp-pcie: Add QMP PCIe Multi-PHY driver Qiang Yu
@ 2026-07-17 9:58 ` Qiang Yu
2026-07-17 16:50 ` Konrad Dybcio
2 siblings, 1 reply; 6+ messages in thread
From: Qiang Yu @ 2026-07-17 9:58 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Manivannan Sadhasivam, Philipp Zabel,
Bjorn Andersson, Konrad Dybcio
Cc: Qiang Yu, linux-arm-msm, linux-phy, devicetree, linux-kernel
Glymur's PCIe3a and PCIe3b controllers share a single Gen5x8 QMP PHY block
that can be bifurcated into two independent x4 links, rather than each
controller owning its own dedicated PHY.
Add a pcie3_phy node describing the shared PHY block, add the missing
PCIe3a controller node, and point both PCIe3a's and PCIe3b's port phys
at &pcie3_phy (index 0 and 1 respectively) so each controller picks up
its half of the bifurcated PHY. Update the GCC pipe clock parent array
to reference the new PHY's clock outputs instead of the placeholders.
Signed-off-by: Qiang Yu <qiang.yu@oss.qualcomm.com>
---
arch/arm64/boot/dts/qcom/glymur-crd.dtsi | 8 +
arch/arm64/boot/dts/qcom/glymur.dtsi | 336 ++++++++++++++++++++++++++++++-
2 files changed, 342 insertions(+), 2 deletions(-)
diff --git a/arch/arm64/boot/dts/qcom/glymur-crd.dtsi b/arch/arm64/boot/dts/qcom/glymur-crd.dtsi
index c12808abbfe1..e8e81bf9a6a1 100644
--- a/arch/arm64/boot/dts/qcom/glymur-crd.dtsi
+++ b/arch/arm64/boot/dts/qcom/glymur-crd.dtsi
@@ -750,6 +750,14 @@ &pcie3b {
pinctrl-names = "default";
};
+&pcie3_phy {
+ vdda-phy-supply = <&vreg_l3c_e1_0p89>;
+ vdda-pll-supply = <&vreg_l2c_e1_1p14>;
+
+ vdda-refgen0p9-supply = <&vreg_l1c_e1_0p82>;
+ vdda-refgen1p2-supply = <&vreg_l4f_e1_1p08>;
+};
+
&pcie3b_port0 {
reset-gpios = <&tlmm 155 GPIO_ACTIVE_LOW>;
wake-gpios = <&tlmm 157 GPIO_ACTIVE_LOW>;
diff --git a/arch/arm64/boot/dts/qcom/glymur.dtsi b/arch/arm64/boot/dts/qcom/glymur.dtsi
index 6d058d591039..edd09b84b8b9 100644
--- a/arch/arm64/boot/dts/qcom/glymur.dtsi
+++ b/arch/arm64/boot/dts/qcom/glymur.dtsi
@@ -801,8 +801,8 @@ gcc: clock-controller@100000 {
<0>, /* USB 2 Phy PCIE PIPEGMUX */
<0>, /* USB 2 Phy PIPEGMUX */
<0>, /* USB 2 Phy SYS PCIE PIPEGMUX */
- <0>, /* PCIe 3a */
- <0>, /* PCIe 3b */
+ <&pcie3_phy 0>, /* PCIe 3a pipe */
+ <&pcie3_phy 1>, /* PCIe 3b pipe */
<&pcie4_phy>, /* PCIe 4 */
<&pcie5_phy>, /* PCIe 5 */
<&pcie6_phy>, /* PCIe 6 */
@@ -2329,6 +2329,62 @@ &config_noc SLAVE_QUP_0 QCOM_ICC_TAG_ALWAYS>,
};
};
+ pcie3_phy: phy@f00000 {
+ compatible = "qcom,glymur-qmp-gen5x8-pcie-phy";
+ reg = <0x0 0x00f00000 0x0 0x10000>,
+ <0x0 0x00f10000 0x0 0x10000>;
+ reg-names = "port_a",
+ "port_b";
+
+ clocks = <&gcc GCC_PCIE_PHY_3A_AUX_CLK>,
+ <&gcc GCC_PCIE_3A_CFG_AHB_CLK>,
+ <&tcsr TCSR_PCIE_3_CLKREF_EN>,
+ <&gcc GCC_PCIE_3A_PHY_RCHNG_CLK>,
+ <&gcc GCC_PCIE_3A_PIPE_CLK>,
+ <&gcc GCC_PCIE_PHY_3B_AUX_CLK>,
+ <&gcc GCC_PCIE_3B_CFG_AHB_CLK>,
+ <&gcc GCC_PCIE_3B_PHY_RCHNG_CLK>,
+ <&gcc GCC_PCIE_3B_PIPE_CLK>,
+ <&gcc GCC_PCIE_3B_PIPE_DIV2_CLK>;
+ clock-names = "aux",
+ "cfg_ahb",
+ "ref",
+ "rchng",
+ "pipe",
+ "phy_b_aux",
+ "cfg_ahb_b",
+ "rchng_b",
+ "pipe_b",
+ "pipediv2_b";
+
+ resets = <&gcc GCC_PCIE_3A_PHY_BCR>,
+ <&gcc GCC_PCIE_3A_NOCSR_COM_PHY_BCR>,
+ <&gcc GCC_PCIE_3B_PHY_BCR>,
+ <&gcc GCC_PCIE_3B_NOCSR_COM_PHY_BCR>;
+ reset-names = "phy_a",
+ "phy_a_nocsr",
+ "phy_b",
+ "phy_b_nocsr";
+
+ assigned-clocks = <&gcc GCC_PCIE_3A_PHY_RCHNG_CLK>,
+ <&gcc GCC_PCIE_3B_PHY_RCHNG_CLK>;
+ assigned-clock-rates = <100000000>, <100000000>;
+
+ power-domains = <&gcc GCC_PCIE_3A_PHY_GDSC>,
+ <&gcc GCC_PCIE_3B_PHY_GDSC>;
+ power-domain-names = "phy_a_gdsc", "phy_b_gdsc";
+
+ qcom,link-mode = <&tcsr 0x5000>;
+
+ #clock-cells = <1>;
+ clock-output-names = "pcie3a_pipe_clk",
+ "pcie3b_pipe_clk";
+
+ #phy-cells = <1>;
+
+ status = "disabled";
+ };
+
usb_hs_phy: phy@fa0000 {
compatible = "qcom,glymur-m31-eusb2-phy",
"qcom,sm8750-m31-eusb2-phy";
@@ -3691,6 +3747,282 @@ pcie3b_port0: pcie@0 {
reg = <0x0 0x0 0x0 0x0 0x0>;
bus-range = <0x01 0xff>;
+ phys = <&pcie3_phy 1>;
+
+ #address-cells = <3>;
+ #size-cells = <2>;
+ ranges;
+ };
+ };
+
+ pcie3a: pci@1c10000 {
+ device_type = "pci";
+ compatible = "qcom,glymur-pcie", "qcom,pcie-x1e80100";
+ reg = <0x0 0x01c10000 0x0 0x3000>,
+ <0x0 0x70000000 0x0 0xf20>,
+ <0x0 0x70000f40 0x0 0xa8>,
+ <0x0 0x70001000 0x0 0x4000>,
+ <0x0 0x70100000 0x0 0x100000>,
+ <0x0 0x01c13000 0x0 0x1000>;
+ reg-names = "parf",
+ "dbi",
+ "elbi",
+ "atu",
+ "config",
+ "mhi";
+ #address-cells = <3>;
+ #size-cells = <2>;
+ ranges = <0x01000000 0x0 0x00000000 0x0 0x70200000 0x0 0x100000>,
+ <0x02000000 0x0 0x70000000 0x0 0x70300000 0x0 0x3d00000>,
+ <0x03000000 0x7 0x00000000 0x7 0x00000000 0x0 0x40000000>,
+ <0x43000000 0x70 0x00000000 0x70 0x00000000 0x10 0x00000000>;
+
+ bus-range = <0 0xff>;
+
+ dma-coherent;
+
+ linux,pci-domain = <3>;
+ num-lanes = <8>;
+
+ operating-points-v2 = <&pcie3a_opp_table>;
+
+ msi-map = <0x0 &gic_its 0xb0000 0x10000>;
+ iommu-map = <0x0 &pcie_smmu 0x30000 0x10000>;
+
+ interrupts = <GIC_SPI 948 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 949 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 844 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 845 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 194 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 202 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 846 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 847 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 942 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-names = "msi0",
+ "msi1",
+ "msi2",
+ "msi3",
+ "msi4",
+ "msi5",
+ "msi6",
+ "msi7",
+ "global";
+
+ #interrupt-cells = <1>;
+ interrupt-map-mask = <0 0 0 0x7>;
+ interrupt-map = <0 0 0 1 &intc 0 0 0 848 IRQ_TYPE_LEVEL_HIGH>,
+ <0 0 0 2 &intc 0 0 0 849 IRQ_TYPE_LEVEL_HIGH>,
+ <0 0 0 3 &intc 0 0 0 850 IRQ_TYPE_LEVEL_HIGH>,
+ <0 0 0 4 &intc 0 0 0 851 IRQ_TYPE_LEVEL_HIGH>;
+
+ clocks = <&gcc GCC_PCIE_3A_AUX_CLK>,
+ <&gcc GCC_PCIE_3A_CFG_AHB_CLK>,
+ <&gcc GCC_PCIE_3A_MSTR_AXI_CLK>,
+ <&gcc GCC_PCIE_3A_SLV_AXI_CLK>,
+ <&gcc GCC_PCIE_3A_SLV_Q2A_AXI_CLK>,
+ <&gcc GCC_AGGRE_NOC_PCIE_3A_WEST_SF_AXI_CLK>;
+ clock-names = "aux",
+ "cfg",
+ "bus_master",
+ "bus_slave",
+ "slave_q2a",
+ "noc_aggr";
+
+ assigned-clocks = <&gcc GCC_PCIE_3A_AUX_CLK>;
+ assigned-clock-rates = <19200000>;
+
+ interconnects = <&pcie_west_anoc MASTER_PCIE_3A QCOM_ICC_TAG_ALWAYS
+ &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>,
+ <&hsc_noc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS
+ &pcie_west_slv_noc SLAVE_PCIE_3A QCOM_ICC_TAG_ALWAYS>;
+ interconnect-names = "pcie-mem",
+ "cpu-pcie";
+
+ resets = <&gcc GCC_PCIE_3A_BCR>,
+ <&gcc GCC_PCIE_3A_LINK_DOWN_BCR>;
+ reset-names = "pci",
+ "link_down";
+
+ power-domains = <&gcc GCC_PCIE_3A_GDSC>;
+
+ eq-presets-8gts = /bits/ 16 <0x5555 0x5555 0x5555 0x5555
+ 0x5555 0x5555 0x5555 0x5555>;
+ eq-presets-16gts = /bits/ 8 <0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55>;
+ eq-presets-32gts = /bits/ 8 <0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55>;
+
+ status = "disabled";
+
+ pcie3a_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+ /* GEN 1 x1 */
+ opp-2500000-1 {
+ opp-hz = /bits/ 64 <2500000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ opp-peak-kBps = <250000 1>;
+ opp-level = <1>;
+ };
+
+ /* GEN 1 x2 */
+ opp-5000000-1 {
+ opp-hz = /bits/ 64 <5000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ opp-peak-kBps = <500000 1>;
+ opp-level = <1>;
+ };
+
+ /* GEN 1 x4 */
+ opp-10000000-1 {
+ opp-hz = /bits/ 64 <10000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ opp-peak-kBps = <1000000 1>;
+ opp-level = <1>;
+ };
+
+ /* GEN 1 x8 */
+ opp-20000000-1 {
+ opp-hz = /bits/ 64 <20000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ opp-peak-kBps = <2000000 1>;
+ opp-level = <1>;
+ };
+
+ /* GEN 2 x1 */
+ opp-5000000-2 {
+ opp-hz = /bits/ 64 <5000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ opp-peak-kBps = <500000 1>;
+ opp-level = <2>;
+ };
+
+ /* GEN 2 x2 */
+ opp-10000000-2 {
+ opp-hz = /bits/ 64 <10000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ opp-peak-kBps = <1000000 1>;
+ opp-level = <2>;
+ };
+
+ /* GEN 2 x4 */
+ opp-20000000-2 {
+ opp-hz = /bits/ 64 <20000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ opp-peak-kBps = <2000000 1>;
+ opp-level = <2>;
+ };
+
+ /* GEN 2 x8 */
+ opp-40000000-2 {
+ opp-hz = /bits/ 64 <40000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ opp-peak-kBps = <4000000 1>;
+ opp-level = <2>;
+ };
+
+ /* GEN 3 x1 */
+ opp-8000000-3 {
+ opp-hz = /bits/ 64 <8000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ opp-peak-kBps = <984500 1>;
+ opp-level = <3>;
+ };
+
+ /* GEN 3 x2 */
+ opp-16000000-3 {
+ opp-hz = /bits/ 64 <16000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ opp-peak-kBps = <1969000 1>;
+ opp-level = <3>;
+ };
+
+ /* GEN 3 x4 */
+ opp-32000000-3 {
+ opp-hz = /bits/ 64 <32000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ opp-peak-kBps = <3938000 1>;
+ opp-level = <3>;
+ };
+
+ /* GEN 3 x8 */
+ opp-64000000-3 {
+ opp-hz = /bits/ 64 <64000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ opp-peak-kBps = <7876000 1>;
+ opp-level = <3>;
+ };
+
+ /* GEN 4 x1 */
+ opp-16000000-4 {
+ opp-hz = /bits/ 64 <16000000>;
+ required-opps = <&rpmhpd_opp_svs>;
+ opp-peak-kBps = <1969000 1>;
+ opp-level = <4>;
+ };
+
+ /* GEN 4 x2 */
+ opp-32000000-4 {
+ opp-hz = /bits/ 64 <32000000>;
+ required-opps = <&rpmhpd_opp_svs>;
+ opp-peak-kBps = <3938000 1>;
+ opp-level = <4>;
+ };
+
+ /* GEN 4 x4 */
+ opp-64000000-4 {
+ opp-hz = /bits/ 64 <64000000>;
+ required-opps = <&rpmhpd_opp_svs>;
+ opp-peak-kBps = <7876000 1>;
+ opp-level = <4>;
+ };
+
+ /* GEN 4 x8 */
+ opp-128000000-4 {
+ opp-hz = /bits/ 64 <128000000>;
+ required-opps = <&rpmhpd_opp_svs>;
+ opp-peak-kBps = <15753000 1>;
+ opp-level = <4>;
+ };
+
+ /* GEN 5 x1 */
+ opp-32000000-5 {
+ opp-hz = /bits/ 64 <32000000>;
+ required-opps = <&rpmhpd_opp_nom>;
+ opp-peak-kBps = <3938000 1>;
+ opp-level = <5>;
+ };
+
+ /* GEN 5 x2 */
+ opp-64000000-5 {
+ opp-hz = /bits/ 64 <64000000>;
+ required-opps = <&rpmhpd_opp_nom>;
+ opp-peak-kBps = <7876000 1>;
+ opp-level = <5>;
+ };
+
+ /* GEN 5 x4 */
+ opp-128000000-5 {
+ opp-hz = /bits/ 64 <128000000>;
+ required-opps = <&rpmhpd_opp_nom>;
+ opp-peak-kBps = <15753000 1>;
+ opp-level = <5>;
+ };
+
+ /* GEN 5 x8 */
+ opp-256000000-5 {
+ opp-hz = /bits/ 64 <256000000>;
+ required-opps = <&rpmhpd_opp_nom>;
+ opp-peak-kBps = <31506000 1>;
+ opp-level = <5>;
+ };
+ };
+
+ pcie3a_port0: pcie@0 {
+ device_type = "pci";
+ reg = <0x0 0x0 0x0 0x0 0x0>;
+ bus-range = <0x01 0xff>;
+
+ phys = <&pcie3_phy 0>;
+
#address-cells = <3>;
#size-cells = <2>;
ranges;
--
2.34.1
^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH v5 3/3] arm64: dts: qcom: glymur: Wire PCIe3a/3b to shared Gen5x8 PHY
2026-07-17 9:58 ` [PATCH v5 3/3] arm64: dts: qcom: glymur: Wire PCIe3a/3b to shared Gen5x8 PHY Qiang Yu
@ 2026-07-17 16:50 ` Konrad Dybcio
0 siblings, 0 replies; 6+ messages in thread
From: Konrad Dybcio @ 2026-07-17 16:50 UTC (permalink / raw)
To: Qiang Yu, Vinod Koul, Neil Armstrong, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Manivannan Sadhasivam,
Philipp Zabel, Bjorn Andersson, Konrad Dybcio
Cc: linux-arm-msm, linux-phy, devicetree, linux-kernel
On 7/17/26 11:58 AM, Qiang Yu wrote:
> Glymur's PCIe3a and PCIe3b controllers share a single Gen5x8 QMP PHY block
> that can be bifurcated into two independent x4 links, rather than each
> controller owning its own dedicated PHY.
>
> Add a pcie3_phy node describing the shared PHY block, add the missing
> PCIe3a controller node, and point both PCIe3a's and PCIe3b's port phys
> at &pcie3_phy (index 0 and 1 respectively) so each controller picks up
> its half of the bifurcated PHY. Update the GCC pipe clock parent array
> to reference the new PHY's clock outputs instead of the placeholders.
>
> Signed-off-by: Qiang Yu <qiang.yu@oss.qualcomm.com>
> ---
> arch/arm64/boot/dts/qcom/glymur-crd.dtsi | 8 +
> arch/arm64/boot/dts/qcom/glymur.dtsi | 336 ++++++++++++++++++++++++++++++-
These changes really shouldn't be in the same commit
> 2 files changed, 342 insertions(+), 2 deletions(-)
>
> diff --git a/arch/arm64/boot/dts/qcom/glymur-crd.dtsi b/arch/arm64/boot/dts/qcom/glymur-crd.dtsi
> index c12808abbfe1..e8e81bf9a6a1 100644
> --- a/arch/arm64/boot/dts/qcom/glymur-crd.dtsi
> +++ b/arch/arm64/boot/dts/qcom/glymur-crd.dtsi
> @@ -750,6 +750,14 @@ &pcie3b {
> pinctrl-names = "default";
> };
>
> +&pcie3_phy {
> + vdda-phy-supply = <&vreg_l3c_e1_0p89>;
> + vdda-pll-supply = <&vreg_l2c_e1_1p14>;
> +
> + vdda-refgen0p9-supply = <&vreg_l1c_e1_0p82>;
> + vdda-refgen1p2-supply = <&vreg_l4f_e1_1p08>;
> +};
This node is disabled
Konrad
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH v5 2/3] phy: qcom: qmp-pcie: Add QMP PCIe Multi-PHY driver
2026-07-17 9:58 ` [PATCH v5 2/3] phy: qcom: qmp-pcie: Add QMP PCIe Multi-PHY driver Qiang Yu
@ 2026-07-17 22:45 ` Bjorn Andersson
0 siblings, 0 replies; 6+ messages in thread
From: Bjorn Andersson @ 2026-07-17 22:45 UTC (permalink / raw)
To: Qiang Yu
Cc: Vinod Koul, Neil Armstrong, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Manivannan Sadhasivam, Philipp Zabel, Konrad Dybcio,
linux-arm-msm, linux-phy, devicetree, linux-kernel
On Fri, Jul 17, 2026 at 02:58:34AM -0700, Qiang Yu wrote:
[..]
> diff --git a/drivers/phy/qualcomm/phy-qcom-qmp-pcie-multiphy.c b/drivers/phy/qualcomm/phy-qcom-qmp-pcie-multiphy.c
> new file mode 100644
> index 000000000000..b79edf2b7d5f
> --- /dev/null
> +++ b/drivers/phy/qualcomm/phy-qcom-qmp-pcie-multiphy.c
> @@ -0,0 +1,770 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (c) 2026, Qualcomm Technologies, Inc. and/or its subsidiaries.
No, there should not be a year in there.
> + */
> +
[..]
> +static int qmp_pcie_pd_power_on(struct qmp_pcie *qmp)
> +{
> + const struct qmp_phy_cfg *cfg = qmp->cfg;
> + int i, ret;
> +
> + for (i = 0; i < cfg->num_pds; i++) {
> + ret = pm_runtime_get_sync(qmp->pd_devs[i]);
In the error path you will put pd_devs[0..i-1], but
pm_runtime_get_sync() requires that you put pd_devs[i] as well.
Please use pm_runtime_resume_and_get() instead.
> + if (ret < 0) {
> + dev_err(qmp->dev, "failed to power on %s domain\n",
> + cfg->pd_names[i]);
> + goto err_power_off;
> + }
> + }
> +
> + return 0;
> +
> +err_power_off:
> + while (--i >= 0)
> + pm_runtime_put(qmp->pd_devs[i]);
> +
> + return ret;
> +}
Regards,
Bjorn
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-07-17 22:45 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-17 9:58 [PATCH v5 0/3] Add QMP PCIe multiple link-mode PHY support Qiang Yu
2026-07-17 9:58 ` [PATCH v5 1/3] dt-bindings: phy: qcom: Add Glymur QMP PCIe multiple link-mode PHY Qiang Yu
2026-07-17 9:58 ` [PATCH v5 2/3] phy: qcom: qmp-pcie: Add QMP PCIe Multi-PHY driver Qiang Yu
2026-07-17 22:45 ` Bjorn Andersson
2026-07-17 9:58 ` [PATCH v5 3/3] arm64: dts: qcom: glymur: Wire PCIe3a/3b to shared Gen5x8 PHY Qiang Yu
2026-07-17 16:50 ` Konrad Dybcio
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox