--- /dev/null
+From 5f650721c4b232a14a1a3e25b686f2234faee961 Mon Sep 17 00:00:00 2001
+From: Lei Wei <quic_leiwei@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@linaro.org>
+Signed-off-by: Lei Wei <quic_leiwei@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@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@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@0 {
++ reg = <0>;
++ clocks = <&nsscc 116>,
++ <&nsscc 117>;
++ clock-names = "rx",
++ "tx";
++ };
++
++ pcs-mii@1 {
++ reg = <1>;
++ clocks = <&nsscc 118>,
++ <&nsscc 119>;
++ clock-names = "rx",
++ "tx";
++ };
++
++ pcs-mii@2 {
++ reg = <2>;
++ clocks = <&nsscc 120>,
++ <&nsscc 121>;
++ clock-names = "rx",
++ "tx";
++ };
++
++ pcs-mii@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 */
--- /dev/null
+From e404519d9f3e5e7d661cb105d3766d87e37e4ef5 Mon Sep 17 00:00:00 2001
+From: Lei Wei <quic_leiwei@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@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@quicinc.com>");
--- /dev/null
+From 240ae5e0ca2ed858e25d7da6d5291d9c1f2c660a Mon Sep 17 00:00:00 2001
+From: Lei Wei <quic_leiwei@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@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 */
--- /dev/null
+From 4923ca63214a4e6bbee1b3f8f6b9b79f0fd3a3be Mon Sep 17 00:00:00 2001
+From: Lei Wei <quic_leiwei@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@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;
+ }
--- /dev/null
+From 34d10a4eb8fea32bb79e3012dc9d8bd2dffb0df3 Mon Sep 17 00:00:00 2001
+From: Lei Wei <quic_leiwei@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@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@quicinc.com>
++L: netdev@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@linaro.org>
+ L: linux-mtd@lists.infradead.org
--- /dev/null
+From ffe2a80fb76ccdc1781f817f6bbc9a8aa919816e Mon Sep 17 00:00:00 2001
+From: Alexandru Gagniuc <mr.nuke.me@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@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);
--- /dev/null
+From 5b2f02ccca7b9496f0a8da6ade063b82810c75e7 Mon Sep 17 00:00:00 2001
+From: Alexandru Gagniuc <mr.nuke.me@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@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);
--- /dev/null
+From 7de372abe7a4b5b380fdbeedd268445f234990c8 Mon Sep 17 00:00:00 2001
+From: Lei Wei <quic_leiwei@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@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@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 */
--- /dev/null
+From 8c02b6438167e1b73b908040c4ec3d4877c16f83 Mon Sep 17 00:00:00 2001
+From: Alexandru Gagniuc <mr.nuke.me@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@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@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@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@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@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@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@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@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@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@0 {
++ reg = <0>;
++ clocks = <&nsscc NSS_CC_UNIPHY_PORT6_RX_CLK>,
++ <&nsscc NSS_CC_UNIPHY_PORT6_TX_CLK>;
++ clock-names = "rx",
++ "tx";
++ };
++ };
+ };
+
+ thermal-zones {