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 00000000000..1112d4e0729 --- /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 +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 +Signed-off-by: Lei Wei +--- + .../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 ++ ++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 ++ ++ 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 */ 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 00000000000..bae262a01c1 --- /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 +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 +--- + 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 ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#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 "); 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 00000000000..1e7453a35c9 --- /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 +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 +--- + 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 + #include + #include ++#include ++#include ++#include + #include ++#include + #include + #include + + #include + ++/* 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 00000000000..b1cddffc6af --- /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 +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 +--- + 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 00000000000..8c90f9fbb80 --- /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 +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 +--- + 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 ++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 + L: linux-mtd@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 00000000000..6fc9652f086 --- /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 +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 +--- + 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 00000000000..ac4c44a7205 --- /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 +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 +--- + 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 00000000000..64a5bf62290 --- /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 +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 +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 +--- + 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 + #include + #include ++#include + #include + #include + #include + #include ++#include + + #include + +@@ -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 00000000000..ede62bd840c --- /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 +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 +--- + 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 + #include + #include ++#include + #include + #include + #include +@@ -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 {