[openwrt/openwrt] qualcommbe: v6.12: add ethernet PCS driver for IPQ9574

LEDE Commits lede-commits at lists.infradead.org
Sat May 31 03:25:59 PDT 2025


robimarko pushed a commit to openwrt/openwrt.git, branch main:
https://git.openwrt.org/3744b73602efc32af76c0f2c725df776d01edb46

commit 3744b73602efc32af76c0f2c725df776d01edb46
Author: Alexandru Gagniuc <mr.nuke.me at gmail.com>
AuthorDate: Tue May 13 21:45:35 2025 -0500

    qualcommbe: v6.12: add ethernet PCS driver for IPQ9574
    
    Add the v5 of the PCS patch. This is the latest submission as of this
    writing. THe last four patches are not part of the submission. They
    make the series work with v6.12 kernel, resolve a circular dependency
    with the clocks, and add the DTS node. Include them as bundle.
    
    Link: https://lore.kernel.org/lkml/20250207-ipq_pcs_6-14_rc1-v5-0-be2ebec32921@quicinc.com/
    Signed-off-by: Alexandru Gagniuc <mr.nuke.me at gmail.com>
    Link: https://github.com/openwrt/openwrt/pull/18796
    Signed-off-by: Robert Marko <robimarko at gmail.com>
---
 ...-net-pcs-Add-Ethernet-PCS-for-Qualcomm-IP.patch | 234 +++++++++
 ...s-Add-PCS-driver-for-Qualcomm-IPQ9574-SoC.patch | 301 +++++++++++
 ...m-ipq9574-Add-PCS-instantiation-and-phyli.patch | 554 +++++++++++++++++++++
 ...m-ipq9574-Add-USXGMII-interface-mode-supp.patch | 272 ++++++++++
 ...-Add-maintainer-for-Qualcomm-IPQ9574-PCS-.patch |  31 ++
 ...m-ipq9574-remove-neg_mode-argument-from-i.patch |  29 ++
 ...m-ipq9574-delay-mii-clock-probing-until-i.patch |  82 +++
 ...m-ipq9574-add-changes-not-submitted-upstr.patch | 362 ++++++++++++++
 ...m64-dts-qcom-ipq9574-add-PCS-uniphy-nodes.patch | 155 ++++++
 9 files changed, 2020 insertions(+)

diff --git a/target/linux/qualcommbe/patches-6.12/0314-dt-bindings-net-pcs-Add-Ethernet-PCS-for-Qualcomm-IP.patch b/target/linux/qualcommbe/patches-6.12/0314-dt-bindings-net-pcs-Add-Ethernet-PCS-for-Qualcomm-IP.patch
new file mode 100644
index 0000000000..1112d4e072
--- /dev/null
+++ b/target/linux/qualcommbe/patches-6.12/0314-dt-bindings-net-pcs-Add-Ethernet-PCS-for-Qualcomm-IP.patch
@@ -0,0 +1,234 @@
+From 5f650721c4b232a14a1a3e25b686f2234faee961 Mon Sep 17 00:00:00 2001
+From: Lei Wei <quic_leiwei at quicinc.com>
+Date: Fri, 7 Feb 2025 23:53:12 +0800
+Subject: [PATCH] dt-bindings: net: pcs: Add Ethernet PCS for Qualcomm IPQ9574
+ SoC
+
+The 'UNIPHY' PCS block in the IPQ9574 SoC includes PCS and SerDes
+functions. It supports different interface modes to enable Ethernet
+MAC connections to different types of external PHYs/switch. It includes
+PCS functions for 1Gbps and 2.5Gbps interface modes and XPCS functions
+for 10Gbps interface modes. There are three UNIPHY (PCS) instances
+in IPQ9574 SoC which provide PCS/XPCS functions to the six Ethernet
+ports.
+
+Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski at linaro.org>
+Signed-off-by: Lei Wei <quic_leiwei at quicinc.com>
+---
+ .../bindings/net/pcs/qcom,ipq9574-pcs.yaml    | 190 ++++++++++++++++++
+ include/dt-bindings/net/qcom,ipq9574-pcs.h    |  15 ++
+ 2 files changed, 205 insertions(+)
+ create mode 100644 Documentation/devicetree/bindings/net/pcs/qcom,ipq9574-pcs.yaml
+ create mode 100644 include/dt-bindings/net/qcom,ipq9574-pcs.h
+
+--- /dev/null
++++ b/Documentation/devicetree/bindings/net/pcs/qcom,ipq9574-pcs.yaml
+@@ -0,0 +1,190 @@
++# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
++%YAML 1.2
++---
++$id: http://devicetree.org/schemas/net/pcs/qcom,ipq9574-pcs.yaml#
++$schema: http://devicetree.org/meta-schemas/core.yaml#
++
++title: Ethernet PCS for Qualcomm IPQ9574 SoC
++
++maintainers:
++  - Lei Wei <quic_leiwei at quicinc.com>
++
++description:
++  The UNIPHY hardware blocks in the Qualcomm IPQ SoC include PCS and SerDes
++  functions. They enable connectivity between the Ethernet MAC inside the
++  PPE (packet processing engine) and external Ethernet PHY/switch. There are
++  three UNIPHY instances in IPQ9574 SoC which provide PCS functions to the
++  six Ethernet ports.
++
++  For SGMII (1Gbps PHY) or 2500BASE-X (2.5Gbps PHY) interface modes, the PCS
++  function is enabled by using the PCS block inside UNIPHY. For USXGMII (10Gbps
++  PHY), the XPCS block in UNIPHY is used.
++
++  The SerDes provides 125M (1Gbps mode) or 312.5M (2.5Gbps and 10Gbps modes)
++  RX and TX clocks to the NSSCC (Networking Sub System Clock Controller). The
++  NSSCC divides these clocks and generates the MII RX and TX clocks to each
++  of the MII interfaces between the PCS and MAC, as per the link speeds and
++  interface modes.
++
++  Different IPQ SoC may support different number of UNIPHYs (PCSes) since the
++  number of ports and their capabilities can be different between these SoCs
++
++  Below diagram depicts the UNIPHY (PCS) connections for an IPQ9574 SoC based
++  board. In this example, the PCS0 has four GMIIs/XGMIIs, which can connect
++  with four MACs to support QSGMII (4 x 1Gbps) or 10G_QXGMII (4 x 2.5Gbps)
++  interface modes.
++
++  -           +-------+ +---------+  +-------------------------+
++    +---------+CMN PLL| |  GCC    |  |   NSSCC (Divider)       |
++    |         +----+--+ +----+----+  +--+-------+--------------+
++    |              |         |          ^       |
++    |       31.25M |  SYS/AHB|clk  RX/TX|clk    +------------+
++    |       ref clk|         |          |       |            |
++    |              |         v          | MII RX|TX clk   MAC| RX/TX clk
++    |25/50M     +--+---------+----------+-------+---+      +-+---------+
++    |ref clk    |  |   +----------------+       |   |      | |     PPE |
++    v           |  |   |     UNIPHY0            V   |      | V         |
++  +-------+     |  v   |       +-----------+ (X)GMII|      |           |
++  |       |     |  +---+---+   |           |--------|------|-- MAC0    |
++  |       |     |  |       |   |           | (X)GMII|      |           |
++  |  Quad |     |  |SerDes |   | PCS/XPCS  |--------|------|-- MAC1    |
++  |       +<----+  |       |   |           | (X)GMII|      |           |
++  |(X)GPHY|     |  |       |   |           |--------|------|-- MAC2    |
++  |       |     |  |       |   |           | (X)GMII|      |           |
++  |       |     |  +-------+   |           |--------|------|-- MAC3    |
++  +-------+     |              |           |        |      |           |
++                |              +-----------+        |      |           |
++                +-----------------------------------+      |           |
++                +--+---------+----------+-------+---+      |           |
++  +-------+     |            UNIPHY1                |      |           |
++  |       |     |              +-----------+        |      |           |
++  |(X)GPHY|     | +-------+    |           | (X)GMII|      |           |
++  |       +<----+ |SerDes |    | PCS/XPCS  |--------|------|- MAC4     |
++  |       |     | |       |    |           |        |      |           |
++  +-------+     | +-------+    |           |        |      |           |
++                |              +-----------+        |      |           |
++                +-----------------------------------+      |           |
++                +--+---------+----------+-------+---+      |           |
++  +-------+     |           UNIPHY2                 |      |           |
++  |       |     |              +-----------+        |      |           |
++  |(X)GPHY|     | +-------+    |           | (X)GMII|      |           |
++  |       +<----+ |SerDes |    | PCS/XPCS  |--------|------|- MAC5     |
++  |       |     | |       |    |           |        |      |           |
++  +-------+     | +-------+    |           |        |      |           |
++                |              +-----------+        |      |           |
++                +-----------------------------------+      +-----------+
++
++properties:
++  compatible:
++    enum:
++      - qcom,ipq9574-pcs
++
++  reg:
++    maxItems: 1
++
++  '#address-cells':
++    const: 1
++
++  '#size-cells':
++    const: 0
++
++  clocks:
++    items:
++      - description: System clock
++      - description: AHB clock needed for register interface access
++
++  clock-names:
++    items:
++      - const: sys
++      - const: ahb
++
++  '#clock-cells':
++    const: 1
++    description: See include/dt-bindings/net/qcom,ipq9574-pcs.h for constants
++
++patternProperties:
++  '^pcs-mii@[0-4]$':
++    type: object
++    description: PCS MII interface.
++
++    properties:
++      reg:
++        minimum: 0
++        maximum: 4
++        description: MII index
++
++      clocks:
++        items:
++          - description: PCS MII RX clock
++          - description: PCS MII TX clock
++
++      clock-names:
++        items:
++          - const: rx
++          - const: tx
++
++    required:
++      - reg
++      - clocks
++      - clock-names
++
++    additionalProperties: false
++
++required:
++  - compatible
++  - reg
++  - '#address-cells'
++  - '#size-cells'
++  - clocks
++  - clock-names
++  - '#clock-cells'
++
++additionalProperties: false
++
++examples:
++  - |
++    #include <dt-bindings/clock/qcom,ipq9574-gcc.h>
++
++    ethernet-pcs at 7a00000 {
++        compatible = "qcom,ipq9574-pcs";
++        reg = <0x7a00000 0x10000>;
++        #address-cells = <1>;
++        #size-cells = <0>;
++        clocks = <&gcc GCC_UNIPHY0_SYS_CLK>,
++                 <&gcc GCC_UNIPHY0_AHB_CLK>;
++        clock-names = "sys",
++                      "ahb";
++        #clock-cells = <1>;
++
++        pcs-mii at 0 {
++            reg = <0>;
++            clocks = <&nsscc 116>,
++                     <&nsscc 117>;
++            clock-names = "rx",
++                          "tx";
++        };
++
++        pcs-mii at 1 {
++            reg = <1>;
++            clocks = <&nsscc 118>,
++                     <&nsscc 119>;
++            clock-names = "rx",
++                          "tx";
++        };
++
++        pcs-mii at 2 {
++            reg = <2>;
++            clocks = <&nsscc 120>,
++                     <&nsscc 121>;
++            clock-names = "rx",
++                          "tx";
++        };
++
++        pcs-mii at 3 {
++            reg = <3>;
++            clocks = <&nsscc 122>,
++                     <&nsscc 123>;
++            clock-names = "rx",
++                          "tx";
++        };
++    };
+--- /dev/null
++++ b/include/dt-bindings/net/qcom,ipq9574-pcs.h
+@@ -0,0 +1,15 @@
++/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
++/*
++ * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
++ *
++ * Device Tree constants for the Qualcomm IPQ9574 PCS
++ */
++
++#ifndef _DT_BINDINGS_PCS_QCOM_IPQ9574_H
++#define _DT_BINDINGS_PCS_QCOM_IPQ9574_H
++
++/* The RX and TX clocks which are provided from the SerDes to NSSCC. */
++#define PCS_RX_CLK		0
++#define PCS_TX_CLK		1
++
++#endif /* _DT_BINDINGS_PCS_QCOM_IPQ9574_H */
diff --git a/target/linux/qualcommbe/patches-6.12/0315-net-pcs-Add-PCS-driver-for-Qualcomm-IPQ9574-SoC.patch b/target/linux/qualcommbe/patches-6.12/0315-net-pcs-Add-PCS-driver-for-Qualcomm-IPQ9574-SoC.patch
new file mode 100644
index 0000000000..bae262a01c
--- /dev/null
+++ b/target/linux/qualcommbe/patches-6.12/0315-net-pcs-Add-PCS-driver-for-Qualcomm-IPQ9574-SoC.patch
@@ -0,0 +1,301 @@
+From e404519d9f3e5e7d661cb105d3766d87e37e4ef5 Mon Sep 17 00:00:00 2001
+From: Lei Wei <quic_leiwei at quicinc.com>
+Date: Fri, 7 Feb 2025 23:53:13 +0800
+Subject: [PATCH] net: pcs: Add PCS driver for Qualcomm IPQ9574 SoC
+
+The 'UNIPHY' PCS hardware block in Qualcomm's IPQ SoC supports
+different interface modes to enable Ethernet MAC connections
+for different types of external PHYs/switch. Each UNIPHY block
+includes a SerDes and PCS/XPCS blocks, and can operate in either
+PCS or XPCS modes. It supports 1Gbps and 2.5Gbps interface modes
+(Ex: SGMII) using the PCS, and 10Gbps interface modes (Ex: USXGMII)
+using the XPCS. There are three UNIPHY (PCS) instances in IPQ9574
+SoC which support the six Ethernet ports in the SoC.
+
+This patch adds support for the platform driver, probe and clock
+registrations for the PCS driver. The platform driver creates an
+'ipq_pcs' instance for each of the UNIPHY used on the given board.
+
+Signed-off-by: Lei Wei <quic_leiwei at quicinc.com>
+---
+ drivers/net/pcs/Kconfig            |   9 ++
+ drivers/net/pcs/Makefile           |   1 +
+ drivers/net/pcs/pcs-qcom-ipq9574.c | 245 +++++++++++++++++++++++++++++
+ 3 files changed, 255 insertions(+)
+ create mode 100644 drivers/net/pcs/pcs-qcom-ipq9574.c
+
+--- a/drivers/net/pcs/Kconfig
++++ b/drivers/net/pcs/Kconfig
+@@ -36,6 +36,15 @@ config PCS_MTK_USXGMII
+ 	  1000Base-X, 2500Base-X and Cisco SGMII are supported on the same
+ 	  differential pairs via an embedded LynxI PHY.
+ 
++config PCS_QCOM_IPQ9574
++	tristate "Qualcomm IPQ9574 PCS"
++	depends on OF && (ARCH_QCOM || COMPILE_TEST)
++	depends on HAS_IOMEM && COMMON_CLK
++	help
++	  This module provides driver for UNIPHY PCS available on Qualcomm
++	  IPQ9574 SoC. The UNIPHY PCS supports both PCS and XPCS functions
++	  to support different interface modes for MAC to PHY connections.
++
+ config PCS_RZN1_MIIC
+ 	tristate "Renesas RZ/N1 MII converter"
+ 	depends on OF && (ARCH_RZN1 || COMPILE_TEST)
+--- a/drivers/net/pcs/Makefile
++++ b/drivers/net/pcs/Makefile
+@@ -7,5 +7,6 @@ pcs_xpcs-$(CONFIG_PCS_XPCS)	:= pcs-xpcs.
+ obj-$(CONFIG_PCS_XPCS)		+= pcs_xpcs.o
+ obj-$(CONFIG_PCS_LYNX)		+= pcs-lynx.o
+ obj-$(CONFIG_PCS_MTK_LYNXI)	+= pcs-mtk-lynxi.o
++obj-$(CONFIG_PCS_QCOM_IPQ9574)	+= pcs-qcom-ipq9574.o
+ obj-$(CONFIG_PCS_RZN1_MIIC)	+= pcs-rzn1-miic.o
+ obj-$(CONFIG_PCS_MTK_USXGMII)	+= pcs-mtk-usxgmii.o
+--- /dev/null
++++ b/drivers/net/pcs/pcs-qcom-ipq9574.c
+@@ -0,0 +1,245 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/*
++ * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
++ */
++
++#include <linux/clk.h>
++#include <linux/clk-provider.h>
++#include <linux/device.h>
++#include <linux/phy.h>
++#include <linux/platform_device.h>
++#include <linux/regmap.h>
++
++#include <dt-bindings/net/qcom,ipq9574-pcs.h>
++
++#define XPCS_INDIRECT_ADDR		0x8000
++#define XPCS_INDIRECT_AHB_ADDR		0x83fc
++#define XPCS_INDIRECT_ADDR_H		GENMASK(20, 8)
++#define XPCS_INDIRECT_ADDR_L		GENMASK(7, 0)
++#define XPCS_INDIRECT_DATA_ADDR(reg)	(FIELD_PREP(GENMASK(15, 10), 0x20) | \
++					 FIELD_PREP(GENMASK(9, 2), \
++					 FIELD_GET(XPCS_INDIRECT_ADDR_L, reg)))
++
++/* PCS private data */
++struct ipq_pcs {
++	struct device *dev;
++	void __iomem *base;
++	struct regmap *regmap;
++	phy_interface_t interface;
++
++	/* RX clock supplied to NSSCC */
++	struct clk_hw rx_hw;
++	/* TX clock supplied to NSSCC */
++	struct clk_hw tx_hw;
++};
++
++static unsigned long ipq_pcs_clk_rate_get(struct ipq_pcs *qpcs)
++{
++	switch (qpcs->interface) {
++	case PHY_INTERFACE_MODE_USXGMII:
++		return 312500000;
++	default:
++		return 125000000;
++	}
++}
++
++/* Return clock rate for the RX clock supplied to NSSCC
++ * as per the interface mode.
++ */
++static unsigned long ipq_pcs_rx_clk_recalc_rate(struct clk_hw *hw,
++						unsigned long parent_rate)
++{
++	struct ipq_pcs *qpcs = container_of(hw, struct ipq_pcs, rx_hw);
++
++	return ipq_pcs_clk_rate_get(qpcs);
++}
++
++/* Return clock rate for the TX clock supplied to NSSCC
++ * as per the interface mode.
++ */
++static unsigned long ipq_pcs_tx_clk_recalc_rate(struct clk_hw *hw,
++						unsigned long parent_rate)
++{
++	struct ipq_pcs *qpcs = container_of(hw, struct ipq_pcs, tx_hw);
++
++	return ipq_pcs_clk_rate_get(qpcs);
++}
++
++static int ipq_pcs_clk_determine_rate(struct clk_hw *hw,
++				      struct clk_rate_request *req)
++{
++	switch (req->rate) {
++	case 125000000:
++	case 312500000:
++		return 0;
++	default:
++		return -EINVAL;
++	}
++}
++
++/* Clock ops for the RX clock supplied to NSSCC */
++static const struct clk_ops ipq_pcs_rx_clk_ops = {
++	.determine_rate = ipq_pcs_clk_determine_rate,
++	.recalc_rate = ipq_pcs_rx_clk_recalc_rate,
++};
++
++/* Clock ops for the TX clock supplied to NSSCC */
++static const struct clk_ops ipq_pcs_tx_clk_ops = {
++	.determine_rate = ipq_pcs_clk_determine_rate,
++	.recalc_rate = ipq_pcs_tx_clk_recalc_rate,
++};
++
++static struct clk_hw *ipq_pcs_clk_hw_get(struct of_phandle_args *clkspec,
++					 void *data)
++{
++	struct ipq_pcs *qpcs = data;
++
++	switch (clkspec->args[0]) {
++	case PCS_RX_CLK:
++		return &qpcs->rx_hw;
++	case PCS_TX_CLK:
++		return &qpcs->tx_hw;
++	}
++
++	return ERR_PTR(-EINVAL);
++}
++
++/* Register the RX and TX clock which are output from SerDes to
++ * the NSSCC. The NSSCC driver assigns the RX and TX clock as
++ * parent, divides them to generate the MII RX and TX clock to
++ * each MII interface of the PCS as per the link speeds and
++ * interface modes.
++ */
++static int ipq_pcs_clk_register(struct ipq_pcs *qpcs)
++{
++	struct clk_init_data init = { };
++	int ret;
++
++	init.ops = &ipq_pcs_rx_clk_ops;
++	init.name = devm_kasprintf(qpcs->dev, GFP_KERNEL, "%s::rx_clk",
++				   dev_name(qpcs->dev));
++	if (!init.name)
++		return -ENOMEM;
++
++	qpcs->rx_hw.init = &init;
++	ret = devm_clk_hw_register(qpcs->dev, &qpcs->rx_hw);
++	if (ret)
++		return ret;
++
++	init.ops = &ipq_pcs_tx_clk_ops;
++	init.name = devm_kasprintf(qpcs->dev, GFP_KERNEL, "%s::tx_clk",
++				   dev_name(qpcs->dev));
++	if (!init.name)
++		return -ENOMEM;
++
++	qpcs->tx_hw.init = &init;
++	ret = devm_clk_hw_register(qpcs->dev, &qpcs->tx_hw);
++	if (ret)
++		return ret;
++
++	return devm_of_clk_add_hw_provider(qpcs->dev, ipq_pcs_clk_hw_get, qpcs);
++}
++
++static int ipq_pcs_regmap_read(void *context, unsigned int reg,
++			       unsigned int *val)
++{
++	struct ipq_pcs *qpcs = context;
++
++	/* PCS uses direct AHB access while XPCS uses indirect AHB access */
++	if (reg >= XPCS_INDIRECT_ADDR) {
++		writel(FIELD_GET(XPCS_INDIRECT_ADDR_H, reg),
++		       qpcs->base + XPCS_INDIRECT_AHB_ADDR);
++		*val = readl(qpcs->base + XPCS_INDIRECT_DATA_ADDR(reg));
++	} else {
++		*val = readl(qpcs->base + reg);
++	}
++
++	return 0;
++}
++
++static int ipq_pcs_regmap_write(void *context, unsigned int reg,
++				unsigned int val)
++{
++	struct ipq_pcs *qpcs = context;
++
++	/* PCS uses direct AHB access while XPCS uses indirect AHB access */
++	if (reg >= XPCS_INDIRECT_ADDR) {
++		writel(FIELD_GET(XPCS_INDIRECT_ADDR_H, reg),
++		       qpcs->base + XPCS_INDIRECT_AHB_ADDR);
++		writel(val, qpcs->base + XPCS_INDIRECT_DATA_ADDR(reg));
++	} else {
++		writel(val, qpcs->base + reg);
++	}
++
++	return 0;
++}
++
++static const struct regmap_config ipq_pcs_regmap_cfg = {
++	.reg_bits = 32,
++	.val_bits = 32,
++	.reg_read = ipq_pcs_regmap_read,
++	.reg_write = ipq_pcs_regmap_write,
++	.fast_io = true,
++};
++
++static int ipq9574_pcs_probe(struct platform_device *pdev)
++{
++	struct device *dev = &pdev->dev;
++	struct ipq_pcs *qpcs;
++	struct clk *clk;
++	int ret;
++
++	qpcs = devm_kzalloc(dev, sizeof(*qpcs), GFP_KERNEL);
++	if (!qpcs)
++		return -ENOMEM;
++
++	qpcs->dev = dev;
++
++	qpcs->base = devm_platform_ioremap_resource(pdev, 0);
++	if (IS_ERR(qpcs->base))
++		return dev_err_probe(dev, PTR_ERR(qpcs->base),
++				     "Failed to ioremap resource\n");
++
++	qpcs->regmap = devm_regmap_init(dev, NULL, qpcs, &ipq_pcs_regmap_cfg);
++	if (IS_ERR(qpcs->regmap))
++		return dev_err_probe(dev, PTR_ERR(qpcs->regmap),
++				     "Failed to allocate register map\n");
++
++	clk = devm_clk_get_enabled(dev, "sys");
++	if (IS_ERR(clk))
++		return dev_err_probe(dev, PTR_ERR(clk),
++				     "Failed to enable SYS clock\n");
++
++	clk = devm_clk_get_enabled(dev, "ahb");
++	if (IS_ERR(clk))
++		return dev_err_probe(dev, PTR_ERR(clk),
++				     "Failed to enable AHB clock\n");
++
++	ret = ipq_pcs_clk_register(qpcs);
++	if (ret)
++		return ret;
++
++	platform_set_drvdata(pdev, qpcs);
++
++	return 0;
++}
++
++static const struct of_device_id ipq9574_pcs_of_mtable[] = {
++	{ .compatible = "qcom,ipq9574-pcs" },
++	{ /* sentinel */ },
++};
++MODULE_DEVICE_TABLE(of, ipq9574_pcs_of_mtable);
++
++static struct platform_driver ipq9574_pcs_driver = {
++	.driver = {
++		.name = "ipq9574_pcs",
++		.suppress_bind_attrs = true,
++		.of_match_table = ipq9574_pcs_of_mtable,
++	},
++	.probe = ipq9574_pcs_probe,
++};
++module_platform_driver(ipq9574_pcs_driver);
++
++MODULE_LICENSE("GPL");
++MODULE_DESCRIPTION("Qualcomm IPQ9574 PCS driver");
++MODULE_AUTHOR("Lei Wei <quic_leiwei at quicinc.com>");
diff --git a/target/linux/qualcommbe/patches-6.12/0316-net-pcs-qcom-ipq9574-Add-PCS-instantiation-and-phyli.patch b/target/linux/qualcommbe/patches-6.12/0316-net-pcs-qcom-ipq9574-Add-PCS-instantiation-and-phyli.patch
new file mode 100644
index 0000000000..1e7453a35c
--- /dev/null
+++ b/target/linux/qualcommbe/patches-6.12/0316-net-pcs-qcom-ipq9574-Add-PCS-instantiation-and-phyli.patch
@@ -0,0 +1,554 @@
+From 240ae5e0ca2ed858e25d7da6d5291d9c1f2c660a Mon Sep 17 00:00:00 2001
+From: Lei Wei <quic_leiwei at quicinc.com>
+Date: Fri, 7 Feb 2025 23:53:14 +0800
+Subject: [PATCH] net: pcs: qcom-ipq9574: Add PCS instantiation and phylink
+ operations
+
+This patch adds the following PCS functionality for the PCS driver
+for IPQ9574 SoC:
+
+a.) Parses PCS MII DT nodes and instantiate each MII PCS instance.
+b.) Exports PCS instance get and put APIs. The network driver calls
+the PCS get API to get and associate the PCS instance with the port
+MAC.
+c.) PCS phylink operations for SGMII/QSGMII interface modes.
+
+Signed-off-by: Lei Wei <quic_leiwei at quicinc.com>
+---
+ drivers/net/pcs/pcs-qcom-ipq9574.c   | 469 +++++++++++++++++++++++++++
+ include/linux/pcs/pcs-qcom-ipq9574.h |  15 +
+ 2 files changed, 484 insertions(+)
+ create mode 100644 include/linux/pcs/pcs-qcom-ipq9574.h
+
+--- a/drivers/net/pcs/pcs-qcom-ipq9574.c
++++ b/drivers/net/pcs/pcs-qcom-ipq9574.c
+@@ -6,12 +6,46 @@
+ #include <linux/clk.h>
+ #include <linux/clk-provider.h>
+ #include <linux/device.h>
++#include <linux/of.h>
++#include <linux/of_platform.h>
++#include <linux/pcs/pcs-qcom-ipq9574.h>
+ #include <linux/phy.h>
++#include <linux/phylink.h>
+ #include <linux/platform_device.h>
+ #include <linux/regmap.h>
+ 
+ #include <dt-bindings/net/qcom,ipq9574-pcs.h>
+ 
++/* Maximum number of MIIs per PCS instance. There are 5 MIIs for PSGMII. */
++#define PCS_MAX_MII_NRS			5
++
++#define PCS_CALIBRATION			0x1e0
++#define PCS_CALIBRATION_DONE		BIT(7)
++
++#define PCS_MODE_CTRL			0x46c
++#define PCS_MODE_SEL_MASK		GENMASK(12, 8)
++#define PCS_MODE_SGMII			FIELD_PREP(PCS_MODE_SEL_MASK, 0x4)
++#define PCS_MODE_QSGMII			FIELD_PREP(PCS_MODE_SEL_MASK, 0x1)
++
++#define PCS_MII_CTRL(x)			(0x480 + 0x18 * (x))
++#define PCS_MII_ADPT_RESET		BIT(11)
++#define PCS_MII_FORCE_MODE		BIT(3)
++#define PCS_MII_SPEED_MASK		GENMASK(2, 1)
++#define PCS_MII_SPEED_1000		FIELD_PREP(PCS_MII_SPEED_MASK, 0x2)
++#define PCS_MII_SPEED_100		FIELD_PREP(PCS_MII_SPEED_MASK, 0x1)
++#define PCS_MII_SPEED_10		FIELD_PREP(PCS_MII_SPEED_MASK, 0x0)
++
++#define PCS_MII_STS(x)			(0x488 + 0x18 * (x))
++#define PCS_MII_LINK_STS		BIT(7)
++#define PCS_MII_STS_DUPLEX_FULL		BIT(6)
++#define PCS_MII_STS_SPEED_MASK		GENMASK(5, 4)
++#define PCS_MII_STS_SPEED_10		0
++#define PCS_MII_STS_SPEED_100		1
++#define PCS_MII_STS_SPEED_1000		2
++
++#define PCS_PLL_RESET			0x780
++#define PCS_ANA_SW_RESET		BIT(6)
++
+ #define XPCS_INDIRECT_ADDR		0x8000
+ #define XPCS_INDIRECT_AHB_ADDR		0x83fc
+ #define XPCS_INDIRECT_ADDR_H		GENMASK(20, 8)
+@@ -20,6 +54,18 @@
+ 					 FIELD_PREP(GENMASK(9, 2), \
+ 					 FIELD_GET(XPCS_INDIRECT_ADDR_L, reg)))
+ 
++/* Per PCS MII private data */
++struct ipq_pcs_mii {
++	struct ipq_pcs *qpcs;
++	struct phylink_pcs pcs;
++	int index;
++
++	/* RX clock from NSSCC to PCS MII */
++	struct clk *rx_clk;
++	/* TX clock from NSSCC to PCS MII */
++	struct clk *tx_clk;
++};
++
+ /* PCS private data */
+ struct ipq_pcs {
+ 	struct device *dev;
+@@ -31,8 +77,359 @@ struct ipq_pcs {
+ 	struct clk_hw rx_hw;
+ 	/* TX clock supplied to NSSCC */
+ 	struct clk_hw tx_hw;
++
++	struct ipq_pcs_mii *qpcs_mii[PCS_MAX_MII_NRS];
+ };
+ 
++#define phylink_pcs_to_qpcs_mii(_pcs)	\
++	container_of(_pcs, struct ipq_pcs_mii, pcs)
++
++static void ipq_pcs_get_state_sgmii(struct ipq_pcs *qpcs,
++				    int index,
++				    struct phylink_link_state *state)
++{
++	unsigned int val;
++	int ret;
++
++	ret = regmap_read(qpcs->regmap, PCS_MII_STS(index), &val);
++	if (ret) {
++		state->link = 0;
++		return;
++	}
++
++	state->link = !!(val & PCS_MII_LINK_STS);
++
++	if (!state->link)
++		return;
++
++	switch (FIELD_GET(PCS_MII_STS_SPEED_MASK, val)) {
++	case PCS_MII_STS_SPEED_1000:
++		state->speed = SPEED_1000;
++		break;
++	case PCS_MII_STS_SPEED_100:
++		state->speed = SPEED_100;
++		break;
++	case PCS_MII_STS_SPEED_10:
++		state->speed = SPEED_10;
++		break;
++	default:
++		state->link = false;
++		return;
++	}
++
++	if (val & PCS_MII_STS_DUPLEX_FULL)
++		state->duplex = DUPLEX_FULL;
++	else
++		state->duplex = DUPLEX_HALF;
++}
++
++static int ipq_pcs_config_mode(struct ipq_pcs *qpcs,
++			       phy_interface_t interface)
++{
++	unsigned int val;
++	int ret;
++
++	/* Configure PCS interface mode */
++	switch (interface) {
++	case PHY_INTERFACE_MODE_SGMII:
++		val = PCS_MODE_SGMII;
++		break;
++	case PHY_INTERFACE_MODE_QSGMII:
++		val = PCS_MODE_QSGMII;
++		break;
++	default:
++		return -EOPNOTSUPP;
++	}
++
++	ret = regmap_update_bits(qpcs->regmap, PCS_MODE_CTRL,
++				 PCS_MODE_SEL_MASK, val);
++	if (ret)
++		return ret;
++
++	/* PCS PLL reset */
++	ret = regmap_clear_bits(qpcs->regmap, PCS_PLL_RESET, PCS_ANA_SW_RESET);
++	if (ret)
++		return ret;
++
++	fsleep(1000);
++	ret = regmap_set_bits(qpcs->regmap, PCS_PLL_RESET, PCS_ANA_SW_RESET);
++	if (ret)
++		return ret;
++
++	/* Wait for calibration completion */
++	ret = regmap_read_poll_timeout(qpcs->regmap, PCS_CALIBRATION,
++				       val, val & PCS_CALIBRATION_DONE,
++				       1000, 100000);
++	if (ret) {
++		dev_err(qpcs->dev, "PCS calibration timed-out\n");
++		return ret;
++	}
++
++	qpcs->interface = interface;
++
++	return 0;
++}
++
++static int ipq_pcs_config_sgmii(struct ipq_pcs *qpcs,
++				int index,
++				unsigned int neg_mode,
++				phy_interface_t interface)
++{
++	int ret;
++
++	/* Configure the PCS mode if required */
++	if (qpcs->interface != interface) {
++		ret = ipq_pcs_config_mode(qpcs, interface);
++		if (ret)
++			return ret;
++	}
++
++	/* Nothing to do here as in-band autoneg mode is enabled
++	 * by default for each PCS MII port.
++	 */
++	if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED)
++		return 0;
++
++	/* Set force speed mode */
++	return regmap_set_bits(qpcs->regmap,
++			       PCS_MII_CTRL(index), PCS_MII_FORCE_MODE);
++}
++
++static int ipq_pcs_link_up_config_sgmii(struct ipq_pcs *qpcs,
++					int index,
++					unsigned int neg_mode,
++					int speed)
++{
++	unsigned int val;
++	int ret;
++
++	/* PCS speed need not be configured if in-band autoneg is enabled */
++	if (neg_mode != PHYLINK_PCS_NEG_INBAND_ENABLED) {
++		/* PCS speed set for force mode */
++		switch (speed) {
++		case SPEED_1000:
++			val = PCS_MII_SPEED_1000;
++			break;
++		case SPEED_100:
++			val = PCS_MII_SPEED_100;
++			break;
++		case SPEED_10:
++			val = PCS_MII_SPEED_10;
++			break;
++		default:
++			dev_err(qpcs->dev, "Invalid SGMII speed %d\n", speed);
++			return -EINVAL;
++		}
++
++		ret = regmap_update_bits(qpcs->regmap, PCS_MII_CTRL(index),
++					 PCS_MII_SPEED_MASK, val);
++		if (ret)
++			return ret;
++	}
++
++	/* PCS adapter reset */
++	ret = regmap_clear_bits(qpcs->regmap,
++				PCS_MII_CTRL(index), PCS_MII_ADPT_RESET);
++	if (ret)
++		return ret;
++
++	return regmap_set_bits(qpcs->regmap,
++			       PCS_MII_CTRL(index), PCS_MII_ADPT_RESET);
++}
++
++static int ipq_pcs_validate(struct phylink_pcs *pcs, unsigned long *supported,
++			    const struct phylink_link_state *state)
++{
++	switch (state->interface) {
++	case PHY_INTERFACE_MODE_SGMII:
++	case PHY_INTERFACE_MODE_QSGMII:
++		return 0;
++	default:
++		return -EINVAL;
++	}
++}
++
++static unsigned int ipq_pcs_inband_caps(struct phylink_pcs *pcs,
++					phy_interface_t interface)
++{
++	switch (interface) {
++	case PHY_INTERFACE_MODE_SGMII:
++	case PHY_INTERFACE_MODE_QSGMII:
++		return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE;
++	default:
++		return 0;
++	}
++}
++
++static int ipq_pcs_enable(struct phylink_pcs *pcs)
++{
++	struct ipq_pcs_mii *qpcs_mii = phylink_pcs_to_qpcs_mii(pcs);
++	struct ipq_pcs *qpcs = qpcs_mii->qpcs;
++	int index = qpcs_mii->index;
++	int ret;
++
++	ret = clk_prepare_enable(qpcs_mii->rx_clk);
++	if (ret) {
++		dev_err(qpcs->dev, "Failed to enable MII %d RX clock\n", index);
++		return ret;
++	}
++
++	ret = clk_prepare_enable(qpcs_mii->tx_clk);
++	if (ret) {
++		/* This is a fatal event since phylink does not support unwinding
++		 * the state back for this error. So, we only report the error
++		 * and do not disable the clocks.
++		 */
++		dev_err(qpcs->dev, "Failed to enable MII %d TX clock\n", index);
++		return ret;
++	}
++
++	return 0;
++}
++
++static void ipq_pcs_disable(struct phylink_pcs *pcs)
++{
++	struct ipq_pcs_mii *qpcs_mii = phylink_pcs_to_qpcs_mii(pcs);
++
++	clk_disable_unprepare(qpcs_mii->rx_clk);
++	clk_disable_unprepare(qpcs_mii->tx_clk);
++}
++
++static void ipq_pcs_get_state(struct phylink_pcs *pcs, unsigned int neg_mode,
++			      struct phylink_link_state *state)
++{
++	struct ipq_pcs_mii *qpcs_mii = phylink_pcs_to_qpcs_mii(pcs);
++	struct ipq_pcs *qpcs = qpcs_mii->qpcs;
++	int index = qpcs_mii->index;
++
++	switch (state->interface) {
++	case PHY_INTERFACE_MODE_SGMII:
++	case PHY_INTERFACE_MODE_QSGMII:
++		ipq_pcs_get_state_sgmii(qpcs, index, state);
++		break;
++	default:
++		break;
++	}
++
++	dev_dbg_ratelimited(qpcs->dev,
++			    "mode=%s/%s/%s link=%u\n",
++			    phy_modes(state->interface),
++			    phy_speed_to_str(state->speed),
++			    phy_duplex_to_str(state->duplex),
++			    state->link);
++}
++
++static int ipq_pcs_config(struct phylink_pcs *pcs,
++			  unsigned int neg_mode,
++			  phy_interface_t interface,
++			  const unsigned long *advertising,
++			  bool permit)
++{
++	struct ipq_pcs_mii *qpcs_mii = phylink_pcs_to_qpcs_mii(pcs);
++	struct ipq_pcs *qpcs = qpcs_mii->qpcs;
++	int index = qpcs_mii->index;
++
++	switch (interface) {
++	case PHY_INTERFACE_MODE_SGMII:
++	case PHY_INTERFACE_MODE_QSGMII:
++		return ipq_pcs_config_sgmii(qpcs, index, neg_mode, interface);
++	default:
++		return -EOPNOTSUPP;
++	};
++}
++
++static void ipq_pcs_link_up(struct phylink_pcs *pcs,
++			    unsigned int neg_mode,
++			    phy_interface_t interface,
++			    int speed, int duplex)
++{
++	struct ipq_pcs_mii *qpcs_mii = phylink_pcs_to_qpcs_mii(pcs);
++	struct ipq_pcs *qpcs = qpcs_mii->qpcs;
++	int index = qpcs_mii->index;
++	int ret;
++
++	switch (interface) {
++	case PHY_INTERFACE_MODE_SGMII:
++	case PHY_INTERFACE_MODE_QSGMII:
++		ret = ipq_pcs_link_up_config_sgmii(qpcs, index,
++						   neg_mode, speed);
++		break;
++	default:
++		return;
++	}
++
++	if (ret)
++		dev_err(qpcs->dev, "PCS link up fail for interface %s\n",
++			phy_modes(interface));
++}
++
++static const struct phylink_pcs_ops ipq_pcs_phylink_ops = {
++	.pcs_validate = ipq_pcs_validate,
++	.pcs_inband_caps = ipq_pcs_inband_caps,
++	.pcs_enable = ipq_pcs_enable,
++	.pcs_disable = ipq_pcs_disable,
++	.pcs_get_state = ipq_pcs_get_state,
++	.pcs_config = ipq_pcs_config,
++	.pcs_link_up = ipq_pcs_link_up,
++};
++
++/* Parse the PCS MII DT nodes which are child nodes of the PCS node,
++ * and instantiate each MII PCS instance.
++ */
++static int ipq_pcs_create_miis(struct ipq_pcs *qpcs)
++{
++	struct device *dev = qpcs->dev;
++	struct ipq_pcs_mii *qpcs_mii;
++	struct device_node *mii_np;
++	u32 index;
++	int ret;
++
++	for_each_available_child_of_node(dev->of_node, mii_np) {
++		ret = of_property_read_u32(mii_np, "reg", &index);
++		if (ret) {
++			dev_err(dev, "Failed to read MII index\n");
++			of_node_put(mii_np);
++			return ret;
++		}
++
++		if (index >= PCS_MAX_MII_NRS) {
++			dev_err(dev, "Invalid MII index\n");
++			of_node_put(mii_np);
++			return -EINVAL;
++		}
++
++		qpcs_mii = devm_kzalloc(dev, sizeof(*qpcs_mii), GFP_KERNEL);
++		if (!qpcs_mii) {
++			of_node_put(mii_np);
++			return -ENOMEM;
++		}
++
++		qpcs_mii->qpcs = qpcs;
++		qpcs_mii->index = index;
++		qpcs_mii->pcs.ops = &ipq_pcs_phylink_ops;
++		qpcs_mii->pcs.neg_mode = true;
++		qpcs_mii->pcs.poll = true;
++
++		qpcs_mii->rx_clk = devm_get_clk_from_child(dev, mii_np, "rx");
++		if (IS_ERR(qpcs_mii->rx_clk)) {
++			of_node_put(mii_np);
++			return dev_err_probe(dev, PTR_ERR(qpcs_mii->rx_clk),
++					     "Failed to get MII %d RX clock\n", index);
++		}
++
++		qpcs_mii->tx_clk = devm_get_clk_from_child(dev, mii_np, "tx");
++		if (IS_ERR(qpcs_mii->tx_clk)) {
++			of_node_put(mii_np);
++			return dev_err_probe(dev, PTR_ERR(qpcs_mii->tx_clk),
++					     "Failed to get MII %d TX clock\n", index);
++		}
++
++		qpcs->qpcs_mii[index] = qpcs_mii;
++	}
++
++	return 0;
++}
++
+ static unsigned long ipq_pcs_clk_rate_get(struct ipq_pcs *qpcs)
+ {
+ 	switch (qpcs->interface) {
+@@ -219,6 +616,10 @@ static int ipq9574_pcs_probe(struct plat
+ 	if (ret)
+ 		return ret;
+ 
++	ret = ipq_pcs_create_miis(qpcs);
++	if (ret)
++		return ret;
++
+ 	platform_set_drvdata(pdev, qpcs);
+ 
+ 	return 0;
+@@ -230,6 +631,74 @@ static const struct of_device_id ipq9574
+ };
+ MODULE_DEVICE_TABLE(of, ipq9574_pcs_of_mtable);
+ 
++/**
++ * ipq_pcs_get() - Get the IPQ PCS MII instance
++ * @np: Device tree node to the PCS MII
++ *
++ * Description: Get the phylink PCS instance for the given PCS MII node @np.
++ * This instance is associated with the specific MII of the PCS and the
++ * corresponding Ethernet netdevice.
++ *
++ * Return: A pointer to the phylink PCS instance or an error-pointer value.
++ */
++struct phylink_pcs *ipq_pcs_get(struct device_node *np)
++{
++	struct platform_device *pdev;
++	struct ipq_pcs_mii *qpcs_mii;
++	struct ipq_pcs *qpcs;
++	u32 index;
++
++	if (of_property_read_u32(np, "reg", &index))
++		return ERR_PTR(-EINVAL);
++
++	if (index >= PCS_MAX_MII_NRS)
++		return ERR_PTR(-EINVAL);
++
++	if (!of_match_node(ipq9574_pcs_of_mtable, np->parent))
++		return ERR_PTR(-EINVAL);
++
++	/* Get the parent device */
++	pdev = of_find_device_by_node(np->parent);
++	if (!pdev)
++		return ERR_PTR(-ENODEV);
++
++	qpcs = platform_get_drvdata(pdev);
++	if (!qpcs) {
++		put_device(&pdev->dev);
++
++		/* If probe is not yet completed, return DEFER to
++		 * the dependent driver.
++		 */
++		return ERR_PTR(-EPROBE_DEFER);
++	}
++
++	qpcs_mii = qpcs->qpcs_mii[index];
++	if (!qpcs_mii) {
++		put_device(&pdev->dev);
++		return ERR_PTR(-ENOENT);
++	}
++
++	return &qpcs_mii->pcs;
++}
++EXPORT_SYMBOL(ipq_pcs_get);
++
++/**
++ * ipq_pcs_put() - Release the IPQ PCS MII instance
++ * @pcs: PCS instance
++ *
++ * Description: Release a phylink PCS instance.
++ */
++void ipq_pcs_put(struct phylink_pcs *pcs)
++{
++	struct ipq_pcs_mii *qpcs_mii = phylink_pcs_to_qpcs_mii(pcs);
++
++	/* Put reference taken by of_find_device_by_node() in
++	 * ipq_pcs_get().
++	 */
++	put_device(qpcs_mii->qpcs->dev);
++}
++EXPORT_SYMBOL(ipq_pcs_put);
++
+ static struct platform_driver ipq9574_pcs_driver = {
+ 	.driver = {
+ 		.name = "ipq9574_pcs",
+--- /dev/null
++++ b/include/linux/pcs/pcs-qcom-ipq9574.h
+@@ -0,0 +1,15 @@
++/* SPDX-License-Identifier: GPL-2.0-only */
++/*
++ * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
++ */
++
++#ifndef __LINUX_PCS_QCOM_IPQ9574_H
++#define __LINUX_PCS_QCOM_IPQ9574_H
++
++struct device_node;
++struct phylink_pcs;
++
++struct phylink_pcs *ipq_pcs_get(struct device_node *np);
++void ipq_pcs_put(struct phylink_pcs *pcs);
++
++#endif /* __LINUX_PCS_QCOM_IPQ9574_H */
diff --git a/target/linux/qualcommbe/patches-6.12/0317-net-pcs-qcom-ipq9574-Add-USXGMII-interface-mode-supp.patch b/target/linux/qualcommbe/patches-6.12/0317-net-pcs-qcom-ipq9574-Add-USXGMII-interface-mode-supp.patch
new file mode 100644
index 0000000000..b1cddffc6a
--- /dev/null
+++ b/target/linux/qualcommbe/patches-6.12/0317-net-pcs-qcom-ipq9574-Add-USXGMII-interface-mode-supp.patch
@@ -0,0 +1,272 @@
+From 4923ca63214a4e6bbee1b3f8f6b9b79f0fd3a3be Mon Sep 17 00:00:00 2001
+From: Lei Wei <quic_leiwei at quicinc.com>
+Date: Fri, 7 Feb 2025 23:53:15 +0800
+Subject: [PATCH] net: pcs: qcom-ipq9574: Add USXGMII interface mode support
+
+USXGMII mode is enabled by PCS when 10Gbps PHYs are connected, such as
+Aquantia 10Gbps PHY.
+
+Signed-off-by: Lei Wei <quic_leiwei at quicinc.com>
+---
+ drivers/net/pcs/pcs-qcom-ipq9574.c | 170 +++++++++++++++++++++++++++++
+ 1 file changed, 170 insertions(+)
+
+--- a/drivers/net/pcs/pcs-qcom-ipq9574.c
++++ b/drivers/net/pcs/pcs-qcom-ipq9574.c
+@@ -26,6 +26,7 @@
+ #define PCS_MODE_SEL_MASK		GENMASK(12, 8)
+ #define PCS_MODE_SGMII			FIELD_PREP(PCS_MODE_SEL_MASK, 0x4)
+ #define PCS_MODE_QSGMII			FIELD_PREP(PCS_MODE_SEL_MASK, 0x1)
++#define PCS_MODE_XPCS			FIELD_PREP(PCS_MODE_SEL_MASK, 0x10)
+ 
+ #define PCS_MII_CTRL(x)			(0x480 + 0x18 * (x))
+ #define PCS_MII_ADPT_RESET		BIT(11)
+@@ -54,6 +55,34 @@
+ 					 FIELD_PREP(GENMASK(9, 2), \
+ 					 FIELD_GET(XPCS_INDIRECT_ADDR_L, reg)))
+ 
++#define XPCS_DIG_CTRL			0x38000
++#define XPCS_USXG_ADPT_RESET		BIT(10)
++#define XPCS_USXG_EN			BIT(9)
++
++#define XPCS_MII_CTRL			0x1f0000
++#define XPCS_MII_AN_EN			BIT(12)
++#define XPCS_DUPLEX_FULL		BIT(8)
++#define XPCS_SPEED_MASK			(BIT(13) | BIT(6) | BIT(5))
++#define XPCS_SPEED_10000		(BIT(13) | BIT(6))
++#define XPCS_SPEED_5000			(BIT(13) | BIT(5))
++#define XPCS_SPEED_2500			BIT(5)
++#define XPCS_SPEED_1000			BIT(6)
++#define XPCS_SPEED_100			BIT(13)
++#define XPCS_SPEED_10			0
++
++#define XPCS_MII_AN_CTRL		0x1f8001
++#define XPCS_MII_AN_8BIT		BIT(8)
++
++#define XPCS_MII_AN_INTR_STS		0x1f8002
++#define XPCS_USXG_AN_LINK_STS		BIT(14)
++#define XPCS_USXG_AN_SPEED_MASK		GENMASK(12, 10)
++#define XPCS_USXG_AN_SPEED_10		0
++#define XPCS_USXG_AN_SPEED_100		1
++#define XPCS_USXG_AN_SPEED_1000		2
++#define XPCS_USXG_AN_SPEED_2500		4
++#define XPCS_USXG_AN_SPEED_5000		5
++#define XPCS_USXG_AN_SPEED_10000	3
++
+ /* Per PCS MII private data */
+ struct ipq_pcs_mii {
+ 	struct ipq_pcs *qpcs;
+@@ -123,9 +152,54 @@ static void ipq_pcs_get_state_sgmii(stru
+ 		state->duplex = DUPLEX_HALF;
+ }
+ 
++static void ipq_pcs_get_state_usxgmii(struct ipq_pcs *qpcs,
++				      struct phylink_link_state *state)
++{
++	unsigned int val;
++	int ret;
++
++	ret = regmap_read(qpcs->regmap, XPCS_MII_AN_INTR_STS, &val);
++	if (ret) {
++		state->link = 0;
++		return;
++	}
++
++	state->link = !!(val & XPCS_USXG_AN_LINK_STS);
++
++	if (!state->link)
++		return;
++
++	switch (FIELD_GET(XPCS_USXG_AN_SPEED_MASK, val)) {
++	case XPCS_USXG_AN_SPEED_10000:
++		state->speed = SPEED_10000;
++		break;
++	case XPCS_USXG_AN_SPEED_5000:
++		state->speed = SPEED_5000;
++		break;
++	case XPCS_USXG_AN_SPEED_2500:
++		state->speed = SPEED_2500;
++		break;
++	case XPCS_USXG_AN_SPEED_1000:
++		state->speed = SPEED_1000;
++		break;
++	case XPCS_USXG_AN_SPEED_100:
++		state->speed = SPEED_100;
++		break;
++	case XPCS_USXG_AN_SPEED_10:
++		state->speed = SPEED_10;
++		break;
++	default:
++		state->link = false;
++		return;
++	}
++
++	state->duplex = DUPLEX_FULL;
++}
++
+ static int ipq_pcs_config_mode(struct ipq_pcs *qpcs,
+ 			       phy_interface_t interface)
+ {
++	unsigned long rate = 125000000;
+ 	unsigned int val;
+ 	int ret;
+ 
+@@ -137,6 +211,10 @@ static int ipq_pcs_config_mode(struct ip
+ 	case PHY_INTERFACE_MODE_QSGMII:
+ 		val = PCS_MODE_QSGMII;
+ 		break;
++	case PHY_INTERFACE_MODE_USXGMII:
++		val = PCS_MODE_XPCS;
++		rate = 312500000;
++		break;
+ 	default:
+ 		return -EOPNOTSUPP;
+ 	}
+@@ -167,6 +245,21 @@ static int ipq_pcs_config_mode(struct ip
+ 
+ 	qpcs->interface = interface;
+ 
++	/* Configure the RX and TX clock to NSSCC as 125M or 312.5M based
++	 * on current interface mode.
++	 */
++	ret = clk_set_rate(qpcs->rx_hw.clk, rate);
++	if (ret) {
++		dev_err(qpcs->dev, "Failed to set RX clock rate\n");
++		return ret;
++	}
++
++	ret = clk_set_rate(qpcs->tx_hw.clk, rate);
++	if (ret) {
++		dev_err(qpcs->dev, "Failed to set TX clock rate\n");
++		return ret;
++	}
++
+ 	return 0;
+ }
+ 
+@@ -195,6 +288,29 @@ static int ipq_pcs_config_sgmii(struct i
+ 			       PCS_MII_CTRL(index), PCS_MII_FORCE_MODE);
+ }
+ 
++static int ipq_pcs_config_usxgmii(struct ipq_pcs *qpcs)
++{
++	int ret;
++
++	/* Configure the XPCS for USXGMII mode if required */
++	if (qpcs->interface == PHY_INTERFACE_MODE_USXGMII)
++		return 0;
++
++	ret = ipq_pcs_config_mode(qpcs, PHY_INTERFACE_MODE_USXGMII);
++	if (ret)
++		return ret;
++
++	ret = regmap_set_bits(qpcs->regmap, XPCS_DIG_CTRL, XPCS_USXG_EN);
++	if (ret)
++		return ret;
++
++	ret = regmap_set_bits(qpcs->regmap, XPCS_MII_AN_CTRL, XPCS_MII_AN_8BIT);
++	if (ret)
++		return ret;
++
++	return regmap_set_bits(qpcs->regmap, XPCS_MII_CTRL, XPCS_MII_AN_EN);
++}
++
+ static int ipq_pcs_link_up_config_sgmii(struct ipq_pcs *qpcs,
+ 					int index,
+ 					unsigned int neg_mode,
+@@ -237,6 +353,46 @@ static int ipq_pcs_link_up_config_sgmii(
+ 			       PCS_MII_CTRL(index), PCS_MII_ADPT_RESET);
+ }
+ 
++static int ipq_pcs_link_up_config_usxgmii(struct ipq_pcs *qpcs, int speed)
++{
++	unsigned int val;
++	int ret;
++
++	switch (speed) {
++	case SPEED_10000:
++		val = XPCS_SPEED_10000;
++		break;
++	case SPEED_5000:
++		val = XPCS_SPEED_5000;
++		break;
++	case SPEED_2500:
++		val = XPCS_SPEED_2500;
++		break;
++	case SPEED_1000:
++		val = XPCS_SPEED_1000;
++		break;
++	case SPEED_100:
++		val = XPCS_SPEED_100;
++		break;
++	case SPEED_10:
++		val = XPCS_SPEED_10;
++		break;
++	default:
++		dev_err(qpcs->dev, "Invalid USXGMII speed %d\n", speed);
++		return -EINVAL;
++	}
++
++	/* Configure XPCS speed */
++	ret = regmap_update_bits(qpcs->regmap, XPCS_MII_CTRL,
++				 XPCS_SPEED_MASK, val | XPCS_DUPLEX_FULL);
++	if (ret)
++		return ret;
++
++	/* XPCS adapter reset */
++	return regmap_set_bits(qpcs->regmap,
++			       XPCS_DIG_CTRL, XPCS_USXG_ADPT_RESET);
++}
++
+ static int ipq_pcs_validate(struct phylink_pcs *pcs, unsigned long *supported,
+ 			    const struct phylink_link_state *state)
+ {
+@@ -244,6 +400,11 @@ static int ipq_pcs_validate(struct phyli
+ 	case PHY_INTERFACE_MODE_SGMII:
+ 	case PHY_INTERFACE_MODE_QSGMII:
+ 		return 0;
++	case PHY_INTERFACE_MODE_USXGMII:
++		/* USXGMII only supports full duplex mode */
++		phylink_clear(supported, 100baseT_Half);
++		phylink_clear(supported, 10baseT_Half);
++		return 0;
+ 	default:
+ 		return -EINVAL;
+ 	}
+@@ -255,6 +416,7 @@ static unsigned int ipq_pcs_inband_caps(
+ 	switch (interface) {
+ 	case PHY_INTERFACE_MODE_SGMII:
+ 	case PHY_INTERFACE_MODE_QSGMII:
++	case PHY_INTERFACE_MODE_USXGMII:
+ 		return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE;
+ 	default:
+ 		return 0;
+@@ -307,6 +469,9 @@ static void ipq_pcs_get_state(struct phy
+ 	case PHY_INTERFACE_MODE_QSGMII:
+ 		ipq_pcs_get_state_sgmii(qpcs, index, state);
+ 		break;
++	case PHY_INTERFACE_MODE_USXGMII:
++		ipq_pcs_get_state_usxgmii(qpcs, state);
++		break;
+ 	default:
+ 		break;
+ 	}
+@@ -333,6 +498,8 @@ static int ipq_pcs_config(struct phylink
+ 	case PHY_INTERFACE_MODE_SGMII:
+ 	case PHY_INTERFACE_MODE_QSGMII:
+ 		return ipq_pcs_config_sgmii(qpcs, index, neg_mode, interface);
++	case PHY_INTERFACE_MODE_USXGMII:
++		return ipq_pcs_config_usxgmii(qpcs);
+ 	default:
+ 		return -EOPNOTSUPP;
+ 	};
+@@ -354,6 +521,9 @@ static void ipq_pcs_link_up(struct phyli
+ 		ret = ipq_pcs_link_up_config_sgmii(qpcs, index,
+ 						   neg_mode, speed);
+ 		break;
++	case PHY_INTERFACE_MODE_USXGMII:
++		ret = ipq_pcs_link_up_config_usxgmii(qpcs, speed);
++		break;
+ 	default:
+ 		return;
+ 	}
diff --git a/target/linux/qualcommbe/patches-6.12/0318-MAINTAINERS-Add-maintainer-for-Qualcomm-IPQ9574-PCS-.patch b/target/linux/qualcommbe/patches-6.12/0318-MAINTAINERS-Add-maintainer-for-Qualcomm-IPQ9574-PCS-.patch
new file mode 100644
index 0000000000..8c90f9fbb8
--- /dev/null
+++ b/target/linux/qualcommbe/patches-6.12/0318-MAINTAINERS-Add-maintainer-for-Qualcomm-IPQ9574-PCS-.patch
@@ -0,0 +1,31 @@
+From 34d10a4eb8fea32bb79e3012dc9d8bd2dffb0df3 Mon Sep 17 00:00:00 2001
+From: Lei Wei <quic_leiwei at quicinc.com>
+Date: Fri, 7 Feb 2025 23:53:16 +0800
+Subject: [PATCH] MAINTAINERS: Add maintainer for Qualcomm IPQ9574 PCS driver
+
+Add maintainer for the Ethernet PCS driver supported for Qualcomm
+IPQ9574 SoC.
+
+Signed-off-by: Lei Wei <quic_leiwei at quicinc.com>
+---
+ MAINTAINERS | 9 +++++++++
+ 1 file changed, 9 insertions(+)
+
+--- a/MAINTAINERS
++++ b/MAINTAINERS
+@@ -19114,6 +19114,15 @@ S:	Maintained
+ F:	Documentation/devicetree/bindings/regulator/vqmmc-ipq4019-regulator.yaml
+ F:	drivers/regulator/vqmmc-ipq4019-regulator.c
+ 
++QUALCOMM IPQ9574 Ethernet PCS DRIVER
++M:	Lei Wei <quic_leiwei at quicinc.com>
++L:	netdev at vger.kernel.org
++S:	Supported
++F:	Documentation/devicetree/bindings/net/pcs/qcom,ipq9574-pcs.yaml
++F:	drivers/net/pcs/pcs-qcom-ipq9574.c
++F:	include/dt-bindings/net/qcom,ipq9574-pcs.h
++F:	include/linux/pcs/pcs-qcom-ipq9574.h
++
+ QUALCOMM NAND CONTROLLER DRIVER
+ M:	Manivannan Sadhasivam <manivannan.sadhasivam at linaro.org>
+ L:	linux-mtd at lists.infradead.org
diff --git a/target/linux/qualcommbe/patches-6.12/0319-net-pcs-qcom-ipq9574-remove-neg_mode-argument-from-i.patch b/target/linux/qualcommbe/patches-6.12/0319-net-pcs-qcom-ipq9574-remove-neg_mode-argument-from-i.patch
new file mode 100644
index 0000000000..6fc9652f08
--- /dev/null
+++ b/target/linux/qualcommbe/patches-6.12/0319-net-pcs-qcom-ipq9574-remove-neg_mode-argument-from-i.patch
@@ -0,0 +1,29 @@
+From ffe2a80fb76ccdc1781f817f6bbc9a8aa919816e Mon Sep 17 00:00:00 2001
+From: Alexandru Gagniuc <mr.nuke.me at gmail.com>
+Date: Mon, 12 May 2025 09:11:05 -0500
+Subject: [PATCH] net: pcs: qcom-ipq9574: remove "neg_mode" argument from
+ ipq_pcs_get_state
+
+Since commit c6739623c91bb ("net: phylink: pass neg_mode into
+.pcs_get_state() method"), the "neg_mode" parameter is part of the
+argument list of .pcs_get_state(). This is available starting with
+v6.14. However, we want to use the backported IPQ9574 driver on v6.12.
+Remove this parameter from ipq_pcs_get_state(), as it is not part of
+.pcs_get_state() in v6.12.
+
+Signed-off-by: Alexandru Gagniuc <mr.nuke.me at gmail.com>
+---
+ drivers/net/pcs/pcs-qcom-ipq9574.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/net/pcs/pcs-qcom-ipq9574.c
++++ b/drivers/net/pcs/pcs-qcom-ipq9574.c
+@@ -457,7 +457,7 @@ static void ipq_pcs_disable(struct phyli
+ 	clk_disable_unprepare(qpcs_mii->tx_clk);
+ }
+ 
+-static void ipq_pcs_get_state(struct phylink_pcs *pcs, unsigned int neg_mode,
++static void ipq_pcs_get_state(struct phylink_pcs *pcs,
+ 			      struct phylink_link_state *state)
+ {
+ 	struct ipq_pcs_mii *qpcs_mii = phylink_pcs_to_qpcs_mii(pcs);
diff --git a/target/linux/qualcommbe/patches-6.12/0320-net-pcs-qcom-ipq9574-delay-mii-clock-probing-until-i.patch b/target/linux/qualcommbe/patches-6.12/0320-net-pcs-qcom-ipq9574-delay-mii-clock-probing-until-i.patch
new file mode 100644
index 0000000000..ac4c44a720
--- /dev/null
+++ b/target/linux/qualcommbe/patches-6.12/0320-net-pcs-qcom-ipq9574-delay-mii-clock-probing-until-i.patch
@@ -0,0 +1,82 @@
+From 5b2f02ccca7b9496f0a8da6ade063b82810c75e7 Mon Sep 17 00:00:00 2001
+From: Alexandru Gagniuc <mr.nuke.me at gmail.com>
+Date: Mon, 12 May 2025 09:27:17 -0500
+Subject: [PATCH] net: pcs: qcom-ipq9574: delay mii clock probing until
+ ipq_pcs_get()
+
+NSSCC generates the SYS and AHB clocks for the PCS block The PCS then
+feeds the uniphy clocks back to the NSSCC, which are in turn, used to
+feed the PCS MII clocks. This works fine in hardware:
+
+    GCC -> NSSCC -> PCS -> NSSCC -> PCS(MII)
+
+However, when the PCS MII clocks are probed within the .probe() of
+the PCS block, it creates a circular dependency. The MII clocks depend
+on the uniphy clocks, which depend on the PCS block being probed.
+Since we are in the process of probing the PCS block, this results in
+both blocks returning with -EPROBE_DEFER:
+
+    platform 39b00000.clock-controller: deferred probe pending: platform: supplier 7a00000.ethernet-pcs not ready
+    mdio_bus 90000.mdio-1:18: deferred probe pending: mdio_bus: supplier 7a20000.ethernet-pcs not ready
+    mdio_bus 90000.mdio-1:00: deferred probe pending: mdio_bus: supplier 90000.mdio-1:18 not ready
+    mdio_bus 90000.mdio-1:01: deferred probe pending: mdio_bus: supplier 90000.mdio-1:18 not ready
+    mdio_bus 90000.mdio-1:02: deferred probe pending: mdio_bus: supplier 90000.mdio-1:18 not ready
+    mdio_bus 90000.mdio-1:03: deferred probe pending: mdio_bus: supplier 90000.mdio-1:18 not ready
+    platform 7a00000.ethernet-pcs: deferred probe pending: ipq9574_pcs: Failed to get MII 0 RX clock
+    platform 7a20000.ethernet-pcs: deferred probe pending: ipq9574_pcs: Failed to get MII 0 RX clock
+    platform 3a000000.qcom-ppe: deferred probe pending: platform: supplier 39b00000.clock-controller not ready
+
+To break this dependency, let the PCS block probe, and only probe the
+PCS MII clocks from ipq_pcs_get().
+
+Signed-off-by: Alexandru Gagniuc <mr.nuke.me at gmail.com>
+---
+ drivers/net/pcs/pcs-qcom-ipq9574.c | 30 ++++++++++++++++--------------
+ 1 file changed, 16 insertions(+), 14 deletions(-)
+
+--- a/drivers/net/pcs/pcs-qcom-ipq9574.c
++++ b/drivers/net/pcs/pcs-qcom-ipq9574.c
+@@ -580,20 +580,6 @@ static int ipq_pcs_create_miis(struct ip
+ 		qpcs_mii->pcs.neg_mode = true;
+ 		qpcs_mii->pcs.poll = true;
+ 
+-		qpcs_mii->rx_clk = devm_get_clk_from_child(dev, mii_np, "rx");
+-		if (IS_ERR(qpcs_mii->rx_clk)) {
+-			of_node_put(mii_np);
+-			return dev_err_probe(dev, PTR_ERR(qpcs_mii->rx_clk),
+-					     "Failed to get MII %d RX clock\n", index);
+-		}
+-
+-		qpcs_mii->tx_clk = devm_get_clk_from_child(dev, mii_np, "tx");
+-		if (IS_ERR(qpcs_mii->tx_clk)) {
+-			of_node_put(mii_np);
+-			return dev_err_probe(dev, PTR_ERR(qpcs_mii->tx_clk),
+-					     "Failed to get MII %d TX clock\n", index);
+-		}
+-
+ 		qpcs->qpcs_mii[index] = qpcs_mii;
+ 	}
+ 
+@@ -848,6 +834,22 @@ struct phylink_pcs *ipq_pcs_get(struct d
+ 		return ERR_PTR(-ENOENT);
+ 	}
+ 
++	qpcs_mii->rx_clk = devm_get_clk_from_child(&pdev->dev, np, "rx");
++	if (IS_ERR(qpcs_mii->rx_clk)) {
++		put_device(&pdev->dev);
++		return dev_err_ptr_probe(&pdev->dev, PTR_ERR(qpcs_mii->rx_clk),
++					 "Failed to get MII %d RX clock\n",
++					 index);
++	}
++
++	qpcs_mii->tx_clk = devm_get_clk_from_child(&pdev->dev, np, "tx");
++	if (IS_ERR(qpcs_mii->tx_clk)) {
++		put_device(&pdev->dev);
++		return dev_err_ptr_probe(&pdev->dev, PTR_ERR(qpcs_mii->tx_clk),
++					 "Failed to get MII %d TX clock\n",
++					 index);
++	}
++
+ 	return &qpcs_mii->pcs;
+ }
+ EXPORT_SYMBOL(ipq_pcs_get);
diff --git a/target/linux/qualcommbe/patches-6.12/0321-net-pcs-qcom-ipq9574-add-changes-not-submitted-upstr.patch b/target/linux/qualcommbe/patches-6.12/0321-net-pcs-qcom-ipq9574-add-changes-not-submitted-upstr.patch
new file mode 100644
index 0000000000..64a5bf6229
--- /dev/null
+++ b/target/linux/qualcommbe/patches-6.12/0321-net-pcs-qcom-ipq9574-add-changes-not-submitted-upstr.patch
@@ -0,0 +1,362 @@
+From 7de372abe7a4b5b380fdbeedd268445f234990c8 Mon Sep 17 00:00:00 2001
+From: Lei Wei <quic_leiwei at quicinc.com>
+Date: Mon, 29 Jan 2024 11:39:36 +0800
+Subject: [PATCH] net: pcs: qcom-ipq9574: add changes not submitted upstream
+
+Was ("net: pcs: Add driver for Qualcomm IPQ UNIPHY PCS").
+
+The UNIPHY hardware block in Qualcomm's IPQ SoC based boards enables
+PCS and XPCS functions, and helps in interfacing the Ethernet MAC in
+IPQ SoC to external PHYs.
+
+This patch adds the PCS driver support for the UNIPHY hardware used in
+IPQ SoC based boards. Support for SGMII/QSGMII/PSGMII and USXGMII
+interface modes are added in the driver.
+
+Change-Id: Id2c8f993f121098f7b02186b53770b75bb539a93
+Signed-off-by: Lei Wei <quic_leiwei at quicinc.com>
+Alex G: Rebase original patch on top of 20250207 uniphy submission
+        Remove mutex that is not required according to
+        https://lore.kernel.org/lkml/Z3ZwURgIErzpzpEr@shell.armlinux.org.uk/
+Signed-off-by: Alexandru Gagniuc <mr.nuke.me at gmail.com>
+---
+ drivers/net/pcs/pcs-qcom-ipq9574.c      | 180 +++++++++++++++++++++++-
+ include/linux/pcs/pcs-qcom-ipq-uniphy.h |  13 ++
+ 2 files changed, 192 insertions(+), 1 deletion(-)
+ create mode 100644 include/linux/pcs/pcs-qcom-ipq-uniphy.h
+
+--- a/drivers/net/pcs/pcs-qcom-ipq9574.c
++++ b/drivers/net/pcs/pcs-qcom-ipq9574.c
+@@ -9,10 +9,12 @@
+ #include <linux/of.h>
+ #include <linux/of_platform.h>
+ #include <linux/pcs/pcs-qcom-ipq9574.h>
++#include <linux/pcs/pcs-qcom-ipq-uniphy.h>
+ #include <linux/phy.h>
+ #include <linux/phylink.h>
+ #include <linux/platform_device.h>
+ #include <linux/regmap.h>
++#include <linux/reset.h>
+ 
+ #include <dt-bindings/net/qcom,ipq9574-pcs.h>
+ 
+@@ -26,6 +28,7 @@
+ #define PCS_MODE_SEL_MASK		GENMASK(12, 8)
+ #define PCS_MODE_SGMII			FIELD_PREP(PCS_MODE_SEL_MASK, 0x4)
+ #define PCS_MODE_QSGMII			FIELD_PREP(PCS_MODE_SEL_MASK, 0x1)
++#define PCS_MODE_PSGMII			FIELD_PREP(PCS_MODE_SEL_MASK, 0x2)
+ #define PCS_MODE_XPCS			FIELD_PREP(PCS_MODE_SEL_MASK, 0x10)
+ 
+ #define PCS_MII_CTRL(x)			(0x480 + 0x18 * (x))
+@@ -43,6 +46,8 @@
+ #define PCS_MII_STS_SPEED_10		0
+ #define PCS_MII_STS_SPEED_100		1
+ #define PCS_MII_STS_SPEED_1000		2
++#define PCS_MII_STS_PAUSE_TX_EN		BIT(1)
++#define PCS_MII_STS_PAUSE_RX_EN		BIT(0)
+ 
+ #define PCS_PLL_RESET			0x780
+ #define PCS_ANA_SW_RESET		BIT(6)
+@@ -95,12 +100,35 @@ struct ipq_pcs_mii {
+ 	struct clk *tx_clk;
+ };
+ 
++/* UNIPHY PCS reset ID */
++enum {
++	PCS_SYS_RESET,
++	PCS_AHB_RESET,
++	XPCS_RESET,
++	PCS_RESET_MAX
++};
++
++/* UNIPHY PCS reset name */
++static const char *const pcs_reset_name[PCS_RESET_MAX] = {
++	"sys",
++	"ahb",
++	"xpcs",
++};
++
++/* UNIPHY PCS channel clock ID */
++enum {
++	PCS_CH_RX_CLK,
++	PCS_CH_TX_CLK,
++	PCS_CH_CLK_MAX
++};
++
+ /* PCS private data */
+ struct ipq_pcs {
+ 	struct device *dev;
+ 	void __iomem *base;
+ 	struct regmap *regmap;
+ 	phy_interface_t interface;
++	struct reset_control *reset[PCS_RESET_MAX];
+ 
+ 	/* RX clock supplied to NSSCC */
+ 	struct clk_hw rx_hw;
+@@ -150,6 +178,11 @@ static void ipq_pcs_get_state_sgmii(stru
+ 		state->duplex = DUPLEX_FULL;
+ 	else
+ 		state->duplex = DUPLEX_HALF;
++
++	if (val & PCS_MII_STS_PAUSE_TX_EN)
++		state->pause |= MLO_PAUSE_TX;
++	if (val & PCS_MII_STS_PAUSE_RX_EN)
++		state->pause |= MLO_PAUSE_RX;
+ }
+ 
+ static void ipq_pcs_get_state_usxgmii(struct ipq_pcs *qpcs,
+@@ -203,6 +236,9 @@ static int ipq_pcs_config_mode(struct ip
+ 	unsigned int val;
+ 	int ret;
+ 
++	/* Assert XPCS reset */
++	reset_control_assert(qpcs->reset[XPCS_RESET]);
++
+ 	/* Configure PCS interface mode */
+ 	switch (interface) {
+ 	case PHY_INTERFACE_MODE_SGMII:
+@@ -211,11 +247,16 @@ static int ipq_pcs_config_mode(struct ip
+ 	case PHY_INTERFACE_MODE_QSGMII:
+ 		val = PCS_MODE_QSGMII;
+ 		break;
++	case PHY_INTERFACE_MODE_PSGMII:
++		val = PCS_MODE_PSGMII;
++		break;
+ 	case PHY_INTERFACE_MODE_USXGMII:
+ 		val = PCS_MODE_XPCS;
+ 		rate = 312500000;
+ 		break;
+ 	default:
++		dev_err(qpcs->dev,
++			"interface %s not supported\n", phy_modes(interface));
+ 		return -EOPNOTSUPP;
+ 	}
+ 
+@@ -300,6 +341,9 @@ static int ipq_pcs_config_usxgmii(struct
+ 	if (ret)
+ 		return ret;
+ 
++	/* Deassert XPCS and configure XPCS USXGMII */
++	reset_control_deassert(qpcs->reset[XPCS_RESET]);
++
+ 	ret = regmap_set_bits(qpcs->regmap, XPCS_DIG_CTRL, XPCS_USXG_EN);
+ 	if (ret)
+ 		return ret;
+@@ -311,6 +355,91 @@ static int ipq_pcs_config_usxgmii(struct
+ 	return regmap_set_bits(qpcs->regmap, XPCS_MII_CTRL, XPCS_MII_AN_EN);
+ }
+ 
++static unsigned long ipq_unipcs_clock_rate_get_gmii(int speed)
++{
++	unsigned long rate = 0;
++
++	switch (speed) {
++	case SPEED_1000:
++		rate = 125000000;
++		break;
++	case SPEED_100:
++		rate = 25000000;
++		break;
++	case SPEED_10:
++		rate = 2500000;
++		break;
++	default:
++		break;
++	}
++
++	return rate;
++}
++
++static unsigned long ipq_unipcs_clock_rate_get_xgmii(int speed)
++{
++	unsigned long rate = 0;
++
++	switch (speed) {
++	case SPEED_10000:
++		rate = 312500000;
++		break;
++	case SPEED_5000:
++		rate = 156250000;
++		break;
++	case SPEED_2500:
++		rate = 78125000;
++		break;
++	case SPEED_1000:
++		rate = 125000000;
++		break;
++	case SPEED_100:
++		rate = 12500000;
++		break;
++	case SPEED_10:
++		rate = 1250000;
++		break;
++	default:
++		break;
++	}
++
++	return rate;
++}
++
++static void
++ipq_unipcs_link_up_clock_rate_set(struct ipq_pcs_mii *qunipcs_ch,
++				  phy_interface_t interface,
++				  int speed)
++{
++	struct ipq_pcs *qpcs = qunipcs_ch->qpcs;
++	unsigned long rate = 0;
++
++	switch (interface) {
++	case PHY_INTERFACE_MODE_SGMII:
++	case PHY_INTERFACE_MODE_QSGMII:
++	case PHY_INTERFACE_MODE_PSGMII:
++		rate = ipq_unipcs_clock_rate_get_gmii(speed);
++		break;
++	case PHY_INTERFACE_MODE_USXGMII:
++		rate = ipq_unipcs_clock_rate_get_xgmii(speed);
++		break;
++	default:
++		dev_err(qpcs->dev,
++			"interface %s not supported\n", phy_modes(interface));
++		return;
++	}
++
++	if (rate == 0) {
++		dev_err(qpcs->dev, "Invalid PCS clock rate\n");
++		return;
++	}
++
++	clk_set_rate(qunipcs_ch->rx_clk, rate);
++	clk_set_rate(qunipcs_ch->tx_clk, rate);
++
++	fsleep(10000);
++}
++
+ static int ipq_pcs_link_up_config_sgmii(struct ipq_pcs *qpcs,
+ 					int index,
+ 					unsigned int neg_mode,
+@@ -467,6 +596,7 @@ static void ipq_pcs_get_state(struct phy
+ 	switch (state->interface) {
+ 	case PHY_INTERFACE_MODE_SGMII:
+ 	case PHY_INTERFACE_MODE_QSGMII:
++	case PHY_INTERFACE_MODE_PSGMII:
+ 		ipq_pcs_get_state_sgmii(qpcs, index, state);
+ 		break;
+ 	case PHY_INTERFACE_MODE_USXGMII:
+@@ -497,10 +627,13 @@ static int ipq_pcs_config(struct phylink
+ 	switch (interface) {
+ 	case PHY_INTERFACE_MODE_SGMII:
+ 	case PHY_INTERFACE_MODE_QSGMII:
++	case PHY_INTERFACE_MODE_PSGMII:
+ 		return ipq_pcs_config_sgmii(qpcs, index, neg_mode, interface);
+ 	case PHY_INTERFACE_MODE_USXGMII:
+ 		return ipq_pcs_config_usxgmii(qpcs);
+ 	default:
++		dev_err(qpcs->dev,
++			"interface %s not supported\n", phy_modes(interface));
+ 		return -EOPNOTSUPP;
+ 	};
+ }
+@@ -515,9 +648,14 @@ static void ipq_pcs_link_up(struct phyli
+ 	int index = qpcs_mii->index;
+ 	int ret;
+ 
++	/* Configure PCS channel interface clock rate */
++	ipq_unipcs_link_up_clock_rate_set(qpcs_mii, interface, speed);
++
++	/* Configure PCS speed and reset PCS adapter */
+ 	switch (interface) {
+ 	case PHY_INTERFACE_MODE_SGMII:
+ 	case PHY_INTERFACE_MODE_QSGMII:
++	case PHY_INTERFACE_MODE_PSGMII:
+ 		ret = ipq_pcs_link_up_config_sgmii(qpcs, index,
+ 						   neg_mode, speed);
+ 		break;
+@@ -525,6 +663,8 @@ static void ipq_pcs_link_up(struct phyli
+ 		ret = ipq_pcs_link_up_config_usxgmii(qpcs, speed);
+ 		break;
+ 	default:
++		dev_err(qpcs->dev,
++			"interface %s not supported\n", phy_modes(interface));
+ 		return;
+ 	}
+ 
+@@ -735,12 +875,38 @@ static const struct regmap_config ipq_pc
+ 	.fast_io = true,
+ };
+ 
++/**
++ * ipq_unipcs_create() - Create Qualcomm IPQ UNIPHY PCS
++ * @np: Device tree node to the PCS
++ *
++ * Description: Create a phylink PCS instance for a PCS node @np.
++ *
++ * Return: A pointer to the phylink PCS instance or an error-pointer value.
++ */
++struct phylink_pcs *ipq_unipcs_create(struct device_node *np)
++{
++	return ipq_pcs_get(np);
++}
++EXPORT_SYMBOL(ipq_unipcs_create);
++
++/**
++ * ipq_unipcs_destroy() - Destroy Qualcomm IPQ UNIPHY PCS
++ * @pcs: PCS instance
++ *
++ * Description: Destroy a phylink PCS instance.
++ */
++void ipq_unipcs_destroy(struct phylink_pcs *pcs)
++{
++	ipq_pcs_put(pcs);
++}
++EXPORT_SYMBOL(ipq_unipcs_destroy);
++
+ static int ipq9574_pcs_probe(struct platform_device *pdev)
+ {
+ 	struct device *dev = &pdev->dev;
+ 	struct ipq_pcs *qpcs;
+ 	struct clk *clk;
+-	int ret;
++	int i, ret;
+ 
+ 	qpcs = devm_kzalloc(dev, sizeof(*qpcs), GFP_KERNEL);
+ 	if (!qpcs)
+@@ -762,11 +928,23 @@ static int ipq9574_pcs_probe(struct plat
+ 	if (IS_ERR(clk))
+ 		return dev_err_probe(dev, PTR_ERR(clk),
+ 				     "Failed to enable SYS clock\n");
++	clk_set_rate(clk, 24000000);
+ 
+ 	clk = devm_clk_get_enabled(dev, "ahb");
+ 	if (IS_ERR(clk))
+ 		return dev_err_probe(dev, PTR_ERR(clk),
+ 				     "Failed to enable AHB clock\n");
++	clk_set_rate(clk, 100000000);
++
++	for (i = 0; i < PCS_RESET_MAX; i++) {
++		qpcs->reset[i] =
++			devm_reset_control_get_optional_exclusive(dev,
++								  pcs_reset_name[i]);
++
++		if (IS_ERR(qpcs->reset[i]))
++			dev_err(dev, "Failed to get the reset ID %s\n",
++				pcs_reset_name[i]);
++	}
+ 
+ 	ret = ipq_pcs_clk_register(qpcs);
+ 	if (ret)
+--- /dev/null
++++ b/include/linux/pcs/pcs-qcom-ipq-uniphy.h
+@@ -0,0 +1,13 @@
++/* SPDX-License-Identifier: GPL-2.0-only */
++/*
++ * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
++ *
++ */
++
++#ifndef __LINUX_PCS_QCOM_IPQ_UNIPHY_H
++#define __LINUX_PCS_QCOM_IPQ_UNIPHY_H
++
++struct phylink_pcs *ipq_unipcs_create(struct device_node *np);
++void ipq_unipcs_destroy(struct phylink_pcs *pcs);
++
++#endif /* __LINUX_PCS_QCOM_IPQ_UNIPHY_H */
diff --git a/target/linux/qualcommbe/patches-6.12/0322-arm64-dts-qcom-ipq9574-add-PCS-uniphy-nodes.patch b/target/linux/qualcommbe/patches-6.12/0322-arm64-dts-qcom-ipq9574-add-PCS-uniphy-nodes.patch
new file mode 100644
index 0000000000..ede62bd840
--- /dev/null
+++ b/target/linux/qualcommbe/patches-6.12/0322-arm64-dts-qcom-ipq9574-add-PCS-uniphy-nodes.patch
@@ -0,0 +1,155 @@
+From 8c02b6438167e1b73b908040c4ec3d4877c16f83 Mon Sep 17 00:00:00 2001
+From: Alexandru Gagniuc <mr.nuke.me at gmail.com>
+Date: Sun, 11 May 2025 18:21:00 -0500
+Subject: [PATCH] arm64: dts: qcom: ipq9574: add PCS uniphy nodes
+
+IPQ9574 has three uniphy blocks. IPQ9554 lacks uniphy1. They take
+their system and AHB clocks from NSSCC, and also feed NSSCC with
+the clocks that are intended for the PHYs. This is not a cirular
+dependency. Add nodes for these uniphy blocks, and the clocks they
+feed back to the NSSCC node.
+
+Signed-off-by: Alexandru Gagniuc <mr.nuke.me at gmail.com>
+---
+ arch/arm64/boot/dts/qcom/ipq9574.dtsi | 116 ++++++++++++++++++++++++--
+ 1 file changed, 110 insertions(+), 6 deletions(-)
+
+--- a/arch/arm64/boot/dts/qcom/ipq9574.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq9574.dtsi
+@@ -9,6 +9,7 @@
+ #include <dt-bindings/clock/qcom,apss-ipq.h>
+ #include <dt-bindings/clock/qcom,ipq-cmn-pll.h>
+ #include <dt-bindings/clock/qcom,ipq9574-gcc.h>
++#include <dt-bindings/clock/qcom,ipq9574-nsscc.h>
+ #include <dt-bindings/interconnect/qcom,ipq9574.h>
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+ #include <dt-bindings/reset/qcom,ipq9574-gcc.h>
+@@ -1245,12 +1246,12 @@
+ 				 <&cmn_pll NSS_1200MHZ_CLK>,
+ 				 <&cmn_pll PPE_353MHZ_CLK>,
+ 				 <&gcc GPLL0_OUT_AUX>,
+-				 <0>,
+-				 <0>,
+-				 <0>,
+-				 <0>,
+-				 <0>,
+-				 <0>,
++				 <&pcs_uniphy0 0>,
++				 <&pcs_uniphy0 1>,
++				 <&pcs_uniphy1 0>,
++				 <&pcs_uniphy1 1>,
++				 <&pcs_uniphy2 0>,
++				 <&pcs_uniphy2 1>,
+ 				 <&gcc GCC_NSSCC_CLK>;
+ 			clock-names = "xo",
+ 				      "nss_1200",
+@@ -1267,6 +1268,109 @@
+ 			#reset-cells = <1>;
+ 			#interconnect-cells = <1>;
+ 		};
++
++		pcs_uniphy0: ethernet-pcs at 7a00000 {
++			compatible = "qcom,ipq9574-pcs";
++			reg = <0x7a00000 0x10000>;
++			#address-cells = <1>;
++			#size-cells = <0>;
++			clocks = <&gcc GCC_UNIPHY0_SYS_CLK>,
++				 <&gcc GCC_UNIPHY0_AHB_CLK>;
++			clock-names = "sys",
++				      "ahb";
++			resets = <&gcc GCC_UNIPHY0_SYS_RESET>,
++				 <&gcc GCC_UNIPHY0_AHB_RESET>,
++				 <&gcc GCC_UNIPHY0_XPCS_RESET>;
++			reset-names = "sys",
++				      "ahb",
++				      "xpcs";
++
++			#clock-cells = <1>;
++
++			pcsuniphy0_ch0: pcs-mii at 0 {
++				reg = <0>;
++				clocks = <&nsscc NSS_CC_UNIPHY_PORT1_RX_CLK>,
++					 <&nsscc NSS_CC_UNIPHY_PORT1_TX_CLK>;
++				clock-names = "rx",
++					      "tx";
++			};
++
++			pcsuniphy0_ch1: pcs-mii at 1 {
++				reg = <1>;
++				clocks = <&nsscc NSS_CC_UNIPHY_PORT2_RX_CLK>,
++					 <&nsscc NSS_CC_UNIPHY_PORT2_TX_CLK>;
++				clock-names = "rx",
++					      "tx";
++			};
++
++			pcsuniphy0_ch2: pcs-mii at 2 {
++				reg = <2>;
++				clocks = <&nsscc NSS_CC_UNIPHY_PORT3_RX_CLK>,
++					 <&nsscc NSS_CC_UNIPHY_PORT3_TX_CLK>;
++				clock-names = "rx",
++					      "tx";
++			};
++
++			pcsuniphy0_ch3: pcs-mii at 3 {
++				reg = <3>;
++				clocks = <&nsscc NSS_CC_UNIPHY_PORT4_RX_CLK>,
++					 <&nsscc NSS_CC_UNIPHY_PORT4_TX_CLK>;
++				clock-names = "rx",
++					      "tx";
++			};
++		};
++
++		pcs_uniphy1: ethernet-uniphy at 7a10000 {
++			#address-cells = <1>;
++			#size-cells = <0>;
++			compatible = "qcom,ipq9574-uniphy";
++			reg = <0x7a10000 0x10000>;
++			clocks = <&gcc GCC_UNIPHY1_SYS_CLK>,
++				 <&gcc GCC_UNIPHY1_AHB_CLK>;
++			clock-names = "sys",
++				      "ahb";
++			resets = <&gcc GCC_UNIPHY1_SYS_RESET>,
++				 <&gcc GCC_UNIPHY1_AHB_RESET>,
++				 <&gcc GCC_UNIPHY1_XPCS_RESET>;
++			reset-names = "sys",
++				      "ahb",
++				      "xpcs";
++			#clock-cells = <1>;
++
++			pcsuniphy1_ch0: uniphy-ch at 0 {
++				reg = <0>;
++				clocks = <&nsscc NSS_CC_UNIPHY_PORT5_RX_CLK>,
++					 <&nsscc NSS_CC_UNIPHY_PORT5_TX_CLK>;
++				clock-names = "rx",
++					      "tx";
++			};
++		};
++
++		pcs_uniphy2: ethernet-pcs at 7a20000 {
++			compatible = "qcom,ipq9574-pcs";
++			reg = <0x7a20000 0x10000>;
++			#address-cells = <1>;
++			#size-cells = <0>;
++			clocks = <&gcc GCC_UNIPHY2_SYS_CLK>,
++				 <&gcc GCC_UNIPHY2_AHB_CLK>;
++			clock-names = "sys",
++				      "ahb";
++			resets = <&gcc GCC_UNIPHY2_SYS_RESET>,
++				 <&gcc GCC_UNIPHY2_AHB_RESET>,
++				 <&gcc GCC_UNIPHY2_XPCS_RESET>;
++			reset-names = "sys",
++				      "ahb",
++				      "xpcs";
++			#clock-cells = <1>;
++
++			pcsuniphy2_ch0: pcs-mii at 0 {
++				reg = <0>;
++				clocks = <&nsscc NSS_CC_UNIPHY_PORT6_RX_CLK>,
++					 <&nsscc NSS_CC_UNIPHY_PORT6_TX_CLK>;
++				clock-names = "rx",
++					      "tx";
++			};
++		};
+ 	};
+ 
+ 	thermal-zones {




More information about the lede-commits mailing list